codex-overleaf-link 2.6.0 → 2.6.5
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 +9 -9
- package/README.zh-CN.md +9 -9
- package/extension/bootstrap/manifest.template.json +1 -1
- package/extension/src/background.js +70 -0
- package/extension/src/content/composerAttachments.js +35 -8
- package/extension/src/content/contentRuntime.js +200 -51
- package/extension/src/content/diagnosticsController.js +77 -34
- package/extension/src/content/diagnosticsExport.js +1 -1
- package/extension/src/content/diagnosticsPanel.js +129 -42
- package/extension/src/content/generated/content.bundle.js +1283 -331
- package/extension/src/content/generated/content.bundle.meta.json +3 -3
- package/extension/src/content/localSkillsPanel.js +4 -2
- package/extension/src/content/markdownText.js +11 -9
- package/extension/src/content/modelPicker.js +28 -19
- package/extension/src/content/nativeCompatibilityController.js +23 -9
- package/extension/src/content/pageBridgeClient.js +5 -5
- package/extension/src/content/panelMaintenance.js +4 -0
- package/extension/src/content/panelRenderer.js +4 -1
- package/extension/src/content/providerSettingsDialog.js +26 -18
- package/extension/src/content/recentProjects.js +26 -16
- package/extension/src/content/runQueueScheduler.js +19 -9
- package/extension/src/content/runResultActions.js +51 -4
- package/extension/src/content/runReviewController.js +9 -3
- package/extension/src/content/scopedPersistencePanelState.js +34 -25
- package/extension/src/content/selectionContextView.js +30 -3
- package/extension/src/content/sharedSessionViewSync.js +9 -1
- package/extension/src/content/writebackOrchestrator.js +266 -50
- package/extension/src/content/writingStyleSettings.js +70 -17
- package/extension/src/page/overleafEditor.js +12 -3
- package/extension/src/page/overleafProjectSnapshot.js +56 -3
- package/extension/src/page/snapshotRouter.js +75 -66
- package/extension/src/page/treeOperations.js +200 -19
- package/extension/src/page/writebackRouter.js +152 -40
- package/extension/src/pageBridge.js +17 -7
- package/extension/src/shared/agentTranscript.js +23 -2
- package/extension/src/shared/auditRecords.js +1 -0
- package/extension/src/shared/compatibility.js +1 -1
- package/extension/src/shared/composerAttachmentDraft.js +22 -7
- package/extension/src/shared/failureReasons.js +15 -0
- package/extension/src/shared/i18n.js +16 -0
- package/extension/src/shared/pageRpcContract.js +1 -1
- package/extension/src/shared/projectFiles.js +2 -1
- package/extension/src/shared/scopedPersistenceBrowserAdapter.js +34 -0
- package/extension/src/shared/staleGuard.js +58 -0
- package/extension/src/shared/storageMigration.js +104 -1
- package/extension/styles/panel.css +191 -63
- package/native-host/src/codexSessionRunner.js +1 -0
- package/native-host/src/debugLog.js +27 -3
- package/native-host/src/index.js +42 -11
- package/native-host/src/localSkills.js +1 -1
- package/native-host/src/mirrorWorkspace.js +47 -3
- package/native-host/src/nativeEnvironment.js +27 -5
- package/native-host/src/providerRuntime.js +1 -0
- package/native-host/src/skills/reviewer-response/SKILL.md +65 -0
- package/native-host/src/taskRunnerRuntime.js +11 -3
- package/native-host/src/updateManager.js +24 -6
- package/native-host/src/writingStyleRuntime.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
<h1>Codex Overleaf Link</h1>
|
|
4
4
|
<p><strong>Empower Overleaf with Codex.</strong></p>
|
|
5
5
|
<p>
|
|
6
|
-
<img src="https://img.shields.io/badge/version-2.6.
|
|
6
|
+
<img src="https://img.shields.io/badge/version-2.6.5-blue" alt="version 2.6.5">
|
|
7
7
|
<img src="https://img.shields.io/badge/chrome-MV3-green" alt="chrome manifest v3">
|
|
8
8
|
<img src="https://img.shields.io/badge/node-%3E%3D20-brightgreen" alt="node version">
|
|
9
9
|
<a href="https://github.com/Ghqqqq/codex-overleaf-link/actions/workflows/test.yml"><img src="https://github.com/Ghqqqq/codex-overleaf-link/actions/workflows/test.yml/badge.svg" alt="tests"></a>
|
|
@@ -112,7 +112,7 @@ Install Codex Overleaf Link on this computer. Follow the install steps in https:
|
|
|
112
112
|
### Install with npm
|
|
113
113
|
|
|
114
114
|
```bash
|
|
115
|
-
npm exec --yes codex-overleaf-link@2.6.
|
|
115
|
+
npm exec --yes codex-overleaf-link@2.6.5 -- install-managed
|
|
116
116
|
```
|
|
117
117
|
|
|
118
118
|
The installer sets up the native host and a managed copy of the extension, then prints the extension's folder.
|
|
@@ -126,13 +126,13 @@ In `chrome://extensions`, turn on **Developer mode**, click **Load unpacked** an
|
|
|
126
126
|
|
|
127
127
|
```bash
|
|
128
128
|
# macOS / Linux
|
|
129
|
-
CODEX_OVERLEAF_REF=v2.6.
|
|
129
|
+
CODEX_OVERLEAF_REF=v2.6.5 bash -c "$(curl -fsSL https://raw.githubusercontent.com/Ghqqqq/codex-overleaf-link/v2.6.5/install.sh)"
|
|
130
130
|
```
|
|
131
131
|
|
|
132
132
|
```powershell
|
|
133
133
|
# Windows
|
|
134
|
-
iwr https://raw.githubusercontent.com/Ghqqqq/codex-overleaf-link/v2.6.
|
|
135
|
-
$env:CODEX_OVERLEAF_REF='v2.6.
|
|
134
|
+
iwr https://raw.githubusercontent.com/Ghqqqq/codex-overleaf-link/v2.6.5/install.ps1 -OutFile install.ps1
|
|
135
|
+
$env:CODEX_OVERLEAF_REF='v2.6.5'
|
|
136
136
|
powershell -ExecutionPolicy Bypass -File install.ps1
|
|
137
137
|
```
|
|
138
138
|
|
|
@@ -210,10 +210,10 @@ Source checkouts and release-zip installs are unmanaged; update them by hand. Ea
|
|
|
210
210
|
|
|
211
211
|
| Task | Command |
|
|
212
212
|
|---|---|
|
|
213
|
-
| Install, repair or migrate | `npm exec --yes codex-overleaf-link@2.6.
|
|
214
|
-
| Diagnose | `npm exec --yes codex-overleaf-link@2.6.
|
|
215
|
-
| Uninstall | `npm exec --yes codex-overleaf-link@2.6.
|
|
216
|
-
| Native host only, for an unmanaged extension | `npm exec --yes codex-overleaf-link@2.6.
|
|
213
|
+
| Install, repair or migrate | `npm exec --yes codex-overleaf-link@2.6.5 -- install-managed` |
|
|
214
|
+
| Diagnose | `npm exec --yes codex-overleaf-link@2.6.5 -- doctor` |
|
|
215
|
+
| Uninstall | `npm exec --yes codex-overleaf-link@2.6.5 -- uninstall-managed` |
|
|
216
|
+
| Native host only, for an unmanaged extension | `npm exec --yes codex-overleaf-link@2.6.5 -- install-native` / `uninstall-native` |
|
|
217
217
|
|
|
218
218
|
`uninstall-managed` removes the native host registration, the bridge, the managed extension and its runtimes. Your history, settings, mirrors, keys and skills stay. To remove everything, clear the history under **Settings → All projects → Local data → Clear local history…**, uninstall, remove the extension in `chrome://extensions`, and delete `~/.codex-overleaf` and the `~/Codex Overleaf Link Extension` shortcut (on Windows, `%USERPROFILE%\.codex-overleaf` and `%LOCALAPPDATA%\CodexOverleaf`).
|
|
219
219
|
|
package/README.zh-CN.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
<h1>Codex Overleaf Link</h1>
|
|
4
4
|
<p><strong>Empower Overleaf with Codex.</strong></p>
|
|
5
5
|
<p>
|
|
6
|
-
<img src="https://img.shields.io/badge/version-2.6.
|
|
6
|
+
<img src="https://img.shields.io/badge/version-2.6.5-blue" alt="version 2.6.5">
|
|
7
7
|
<img src="https://img.shields.io/badge/chrome-MV3-green" alt="chrome manifest v3">
|
|
8
8
|
<img src="https://img.shields.io/badge/node-%3E%3D20-brightgreen" alt="node version">
|
|
9
9
|
<a href="https://github.com/Ghqqqq/codex-overleaf-link/actions/workflows/test.yml"><img src="https://github.com/Ghqqqq/codex-overleaf-link/actions/workflows/test.yml/badge.svg" alt="tests"></a>
|
|
@@ -112,7 +112,7 @@ Codex Overleaf Link 在 Overleaf 编辑器旁边加了一个 Codex 面板。你
|
|
|
112
112
|
### 用 npm 安装
|
|
113
113
|
|
|
114
114
|
```bash
|
|
115
|
-
npm exec --yes codex-overleaf-link@2.6.
|
|
115
|
+
npm exec --yes codex-overleaf-link@2.6.5 -- install-managed
|
|
116
116
|
```
|
|
117
117
|
|
|
118
118
|
安装器会装好 native host 和一份托管的扩展,最后输出扩展所在的文件夹。
|
|
@@ -126,13 +126,13 @@ npm exec --yes codex-overleaf-link@2.6.0 -- install-managed
|
|
|
126
126
|
|
|
127
127
|
```bash
|
|
128
128
|
# macOS / Linux
|
|
129
|
-
CODEX_OVERLEAF_REF=v2.6.
|
|
129
|
+
CODEX_OVERLEAF_REF=v2.6.5 bash -c "$(curl -fsSL https://raw.githubusercontent.com/Ghqqqq/codex-overleaf-link/v2.6.5/install.sh)"
|
|
130
130
|
```
|
|
131
131
|
|
|
132
132
|
```powershell
|
|
133
133
|
# Windows
|
|
134
|
-
iwr https://raw.githubusercontent.com/Ghqqqq/codex-overleaf-link/v2.6.
|
|
135
|
-
$env:CODEX_OVERLEAF_REF='v2.6.
|
|
134
|
+
iwr https://raw.githubusercontent.com/Ghqqqq/codex-overleaf-link/v2.6.5/install.ps1 -OutFile install.ps1
|
|
135
|
+
$env:CODEX_OVERLEAF_REF='v2.6.5'
|
|
136
136
|
powershell -ExecutionPolicy Bypass -File install.ps1
|
|
137
137
|
```
|
|
138
138
|
|
|
@@ -210,10 +210,10 @@ flowchart TD
|
|
|
210
210
|
|
|
211
211
|
| 用途 | 命令 |
|
|
212
212
|
|---|---|
|
|
213
|
-
| 安装、修复或迁移 | `npm exec --yes codex-overleaf-link@2.6.
|
|
214
|
-
| 诊断 | `npm exec --yes codex-overleaf-link@2.6.
|
|
215
|
-
| 卸载 | `npm exec --yes codex-overleaf-link@2.6.
|
|
216
|
-
| 只装 native host(扩展自行管理时) | `npm exec --yes codex-overleaf-link@2.6.
|
|
213
|
+
| 安装、修复或迁移 | `npm exec --yes codex-overleaf-link@2.6.5 -- install-managed` |
|
|
214
|
+
| 诊断 | `npm exec --yes codex-overleaf-link@2.6.5 -- doctor` |
|
|
215
|
+
| 卸载 | `npm exec --yes codex-overleaf-link@2.6.5 -- uninstall-managed` |
|
|
216
|
+
| 只装 native host(扩展自行管理时) | `npm exec --yes codex-overleaf-link@2.6.5 -- install-native` / `uninstall-native` |
|
|
217
217
|
|
|
218
218
|
`uninstall-managed` 会删除 native host 注册、bridge、托管的扩展和运行时,历史、设置、镜像、密钥和技能都会保留。要彻底清理:先在**设置 → 所有项目 → 本地数据 → 清理本地历史…**中清掉历史,再卸载,然后在 `chrome://extensions` 里移除扩展,最后删除 `~/.codex-overleaf` 和 `~/Codex Overleaf Link Extension` 快捷方式(Windows 上是 `%USERPROFILE%\.codex-overleaf` 和 `%LOCALAPPDATA%\CodexOverleaf`)。
|
|
219
219
|
|
|
@@ -66,6 +66,8 @@ if (!globalThis.CodexOverleafBackgroundUpdateSafety) {
|
|
|
66
66
|
'use strict';
|
|
67
67
|
|
|
68
68
|
const HOST_NAME = 'com.codex.overleaf';
|
|
69
|
+
const NATIVE_PING_TIMEOUT_MS = 20000;
|
|
70
|
+
const NATIVE_REQUEST_TIMEOUT_MS = 60000;
|
|
69
71
|
const MANAGED_UPDATE_STATE_KEY = 'codex-overleaf-managed-update-state-v1';
|
|
70
72
|
const MANAGED_UPDATE_CONSENT_KEY = 'codex-overleaf-update-consent-v1';
|
|
71
73
|
const MANAGED_UPDATE_TABS_KEY = 'codex-overleaf-managed-update-tabs-v1';
|
|
@@ -398,11 +400,18 @@ if (!globalThis.CodexOverleafBackgroundUpdateSafety) {
|
|
|
398
400
|
method: request.method,
|
|
399
401
|
retryClass: getNativeRetryClass(request.method),
|
|
400
402
|
retryCount: 0,
|
|
403
|
+
deadlineTimer: null,
|
|
401
404
|
nativePort: null,
|
|
402
405
|
finalResponseReceived: false,
|
|
403
406
|
eventForwarded: false
|
|
404
407
|
};
|
|
405
408
|
pending.set(id, pendingRequest);
|
|
409
|
+
const timeoutMs = getNativeRequestTimeoutMs(request.method);
|
|
410
|
+
if (timeoutMs) {
|
|
411
|
+
pendingRequest.deadlineTimer = setTimeout(() => {
|
|
412
|
+
expireNativeRequest(id, pendingRequest);
|
|
413
|
+
}, timeoutMs);
|
|
414
|
+
}
|
|
406
415
|
if (request.method === 'codex.run') {
|
|
407
416
|
createRunJournal(id, pendingRequest);
|
|
408
417
|
}
|
|
@@ -415,6 +424,64 @@ if (!globalThis.CodexOverleafBackgroundUpdateSafety) {
|
|
|
415
424
|
});
|
|
416
425
|
}
|
|
417
426
|
|
|
427
|
+
function getNativeRequestTimeoutMs(method) {
|
|
428
|
+
if (method === 'bridge.ping') return NATIVE_PING_TIMEOUT_MS;
|
|
429
|
+
switch (method) {
|
|
430
|
+
case 'mirror.status':
|
|
431
|
+
case 'codex.models':
|
|
432
|
+
case 'codex.providers.list':
|
|
433
|
+
case 'codex.providers.upsert':
|
|
434
|
+
case 'codex.providers.activate':
|
|
435
|
+
case 'codex.providers.clear-secret':
|
|
436
|
+
case 'codex.providers.delete':
|
|
437
|
+
case 'writing-style.get':
|
|
438
|
+
case 'writing-style.set':
|
|
439
|
+
case 'skills.list':
|
|
440
|
+
case 'asset.readChunk':
|
|
441
|
+
case 'update.status':
|
|
442
|
+
case 'update.check':
|
|
443
|
+
case 'update.canApply':
|
|
444
|
+
return NATIVE_REQUEST_TIMEOUT_MS;
|
|
445
|
+
default:
|
|
446
|
+
return 0;
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
function expireNativeRequest(id, pendingRequest) {
|
|
451
|
+
if (pending.get(id) !== pendingRequest || pendingRequest.finalResponseReceived) return;
|
|
452
|
+
pending.delete(id);
|
|
453
|
+
clearTimeout(pendingRequest.deadlineTimer);
|
|
454
|
+
pendingRequest.finalResponseReceived = true;
|
|
455
|
+
pendingRequest.resolve({
|
|
456
|
+
id,
|
|
457
|
+
ok: false,
|
|
458
|
+
error: {
|
|
459
|
+
code: 'native_request_timeout',
|
|
460
|
+
message: 'The local service did not respond.'
|
|
461
|
+
}
|
|
462
|
+
});
|
|
463
|
+
if (pendingRequest.method !== 'bridge.ping') return;
|
|
464
|
+
const failedPort = pendingRequest.nativePort;
|
|
465
|
+
if (!failedPort) return;
|
|
466
|
+
const hasActiveWork = Array.from(pending.values()).some(request => (
|
|
467
|
+
request.nativePort === failedPort && (
|
|
468
|
+
request.retryClass === 'safe_sync_retry' ||
|
|
469
|
+
(request.retryClass === 'no_silent_retry' && !getNativeRequestTimeoutMs(request.method))
|
|
470
|
+
)
|
|
471
|
+
));
|
|
472
|
+
// A failed diagnostic ping must not abort execution or mirror writes.
|
|
473
|
+
if (hasActiveWork) return;
|
|
474
|
+
if (port === failedPort) port = null;
|
|
475
|
+
// A hung Host is retired without replaying requests or reconnecting until
|
|
476
|
+
// the next caller asks, only when no execution or mirror write owns it.
|
|
477
|
+
for (const [pendingId, interrupted] of Array.from(pending.entries())) {
|
|
478
|
+
if (interrupted.nativePort === failedPort) {
|
|
479
|
+
rejectInterruptedNativeRequest(pendingId, interrupted, 'The local service did not respond.');
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
try { failedPort.disconnect(); } catch (_error) { /* The port is already retired. */ }
|
|
483
|
+
}
|
|
484
|
+
|
|
418
485
|
async function requestManagedInternal(payload) {
|
|
419
486
|
if (managedBootstrapRuntime && ['update.status', 'update.confirm'].includes(payload?.method)) {
|
|
420
487
|
payload = { ...payload, params: { ...payload.params,
|
|
@@ -840,6 +907,7 @@ if (!globalThis.CodexOverleafBackgroundUpdateSafety) {
|
|
|
840
907
|
return;
|
|
841
908
|
}
|
|
842
909
|
pendingRequest.finalResponseReceived = true;
|
|
910
|
+
clearTimeout(pendingRequest.deadlineTimer);
|
|
843
911
|
pending.delete(id);
|
|
844
912
|
finalizeRunJournal(id, message).catch(() => {}).finally(() => {
|
|
845
913
|
pendingRequest.resolve(message);
|
|
@@ -901,6 +969,7 @@ if (!globalThis.CodexOverleafBackgroundUpdateSafety) {
|
|
|
901
969
|
|
|
902
970
|
function rejectInterruptedNativeRequest(pendingId, pendingRequest, errorMessage) {
|
|
903
971
|
pending.delete(pendingId);
|
|
972
|
+
clearTimeout(pendingRequest.deadlineTimer);
|
|
904
973
|
finalizeRunJournal(pendingId, {
|
|
905
974
|
ok: false,
|
|
906
975
|
error: {
|
|
@@ -937,6 +1006,7 @@ if (!globalThis.CodexOverleafBackgroundUpdateSafety) {
|
|
|
937
1006
|
if (pending.size === 1) {
|
|
938
1007
|
const [pendingId, pendingRequest] = pending.entries().next().value;
|
|
939
1008
|
pendingRequest.finalResponseReceived = true;
|
|
1009
|
+
clearTimeout(pendingRequest.deadlineTimer);
|
|
940
1010
|
pendingRequest.resolve({
|
|
941
1011
|
...message,
|
|
942
1012
|
id: pendingId
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
const maxPreviewDataUrlChars = Number(limits.maxPreviewDataUrlChars) || 768 * 1024;
|
|
16
16
|
|
|
17
17
|
let attachments = [];
|
|
18
|
-
const pendingAttachmentKeys = new
|
|
18
|
+
const pendingAttachmentKeys = new Map();
|
|
19
19
|
let pendingAttachmentBytes = 0;
|
|
20
20
|
let attachmentGeneration = 0;
|
|
21
21
|
let attachmentRevision = 0;
|
|
@@ -88,8 +88,15 @@
|
|
|
88
88
|
continue;
|
|
89
89
|
}
|
|
90
90
|
attachmentRevision += 1;
|
|
91
|
-
pendingAttachmentKeys.
|
|
91
|
+
pendingAttachmentKeys.set(dedupeKey, {
|
|
92
|
+
name: normalizeAttachmentName(file.name),
|
|
93
|
+
mimeType: String(file.type || ''),
|
|
94
|
+
size: fileSize,
|
|
95
|
+
loading: true
|
|
96
|
+
});
|
|
92
97
|
pendingAttachmentBytes += fileSize;
|
|
98
|
+
renderComposerAttachments();
|
|
99
|
+
onChange();
|
|
93
100
|
try {
|
|
94
101
|
const dataUrl = await readFileAsDataUrl(file, tx);
|
|
95
102
|
if (generation !== attachmentGeneration) return;
|
|
@@ -118,6 +125,8 @@
|
|
|
118
125
|
if (generation === attachmentGeneration) {
|
|
119
126
|
pendingAttachmentBytes = Math.max(0, pendingAttachmentBytes - fileSize);
|
|
120
127
|
pendingAttachmentKeys.delete(dedupeKey);
|
|
128
|
+
renderComposerAttachments();
|
|
129
|
+
onChange();
|
|
121
130
|
}
|
|
122
131
|
}
|
|
123
132
|
}
|
|
@@ -155,8 +164,9 @@
|
|
|
155
164
|
if (!strip) {
|
|
156
165
|
return;
|
|
157
166
|
}
|
|
158
|
-
|
|
159
|
-
|
|
167
|
+
const visibleAttachments = [...attachments, ...pendingAttachmentKeys.values()];
|
|
168
|
+
strip.hidden = visibleAttachments.length === 0;
|
|
169
|
+
renderAttachmentPreviewList(visibleAttachments, strip, {
|
|
160
170
|
root: getPanel() || document.body,
|
|
161
171
|
tr,
|
|
162
172
|
tx,
|
|
@@ -258,8 +268,8 @@
|
|
|
258
268
|
}
|
|
259
269
|
|
|
260
270
|
// v1.8.0 C4: survive a page refresh. Only small attachments persist
|
|
261
|
-
// (single base64 <= 1MB, max 3)
|
|
262
|
-
//
|
|
271
|
+
// (single base64 <= 1MB, max 3). Omitted names are saved separately so
|
|
272
|
+
// recovery can disclose files that need reattaching without saving bytes.
|
|
263
273
|
const PERSIST_MAX_CONTENT_CHARS = 1024 * 1024;
|
|
264
274
|
const PERSIST_MAX_COUNT = 3;
|
|
265
275
|
|
|
@@ -294,6 +304,15 @@
|
|
|
294
304
|
return attachments.length;
|
|
295
305
|
}
|
|
296
306
|
|
|
307
|
+
function getOmittedAttachmentNames() {
|
|
308
|
+
let persistedCount = 0;
|
|
309
|
+
return attachments.filter(attachment => {
|
|
310
|
+
if ((attachment.contentBase64 || '').length > PERSIST_MAX_CONTENT_CHARS || persistedCount >= PERSIST_MAX_COUNT) return true;
|
|
311
|
+
persistedCount += 1;
|
|
312
|
+
return false;
|
|
313
|
+
}).map(attachment => attachment.name);
|
|
314
|
+
}
|
|
315
|
+
|
|
297
316
|
// Only a just-rejected local-skill submission uses this in-memory recovery.
|
|
298
317
|
// It keeps the normal composer limits, without durable-draft truncation.
|
|
299
318
|
function restoreSubmittedAttachments(value, expectedRevision) {
|
|
@@ -315,6 +334,8 @@
|
|
|
315
334
|
|
|
316
335
|
return {
|
|
317
336
|
serializePersistableAttachments,
|
|
337
|
+
getOmittedAttachmentNames,
|
|
338
|
+
hasPendingReads: () => pendingAttachmentKeys.size > 0,
|
|
318
339
|
restorePersistedAttachments,
|
|
319
340
|
restoreSubmittedAttachments,
|
|
320
341
|
getRevision: () => attachmentRevision,
|
|
@@ -428,6 +449,10 @@
|
|
|
428
449
|
const card = document.createElement('div');
|
|
429
450
|
card.className = 'codex-attachment-preview-card';
|
|
430
451
|
card.dataset.kind = isImage ? 'image' : 'file';
|
|
452
|
+
if (attachment.loading) {
|
|
453
|
+
card.dataset.loading = 'true';
|
|
454
|
+
card.setAttribute('aria-busy', 'true');
|
|
455
|
+
}
|
|
431
456
|
card.title = label + ' · ' + formatFileSize(attachment.size);
|
|
432
457
|
if (isImage) {
|
|
433
458
|
const preview = document.createElement('button');
|
|
@@ -456,11 +481,13 @@
|
|
|
456
481
|
if (!isImage) {
|
|
457
482
|
const size = document.createElement('span');
|
|
458
483
|
size.className = 'codex-attachment-preview-size';
|
|
459
|
-
size.textContent =
|
|
484
|
+
size.textContent = attachment.loading
|
|
485
|
+
? (options.tx ? options.tx('Loading…', '读取中…') : 'Loading…')
|
|
486
|
+
: formatFileSize(attachment.size);
|
|
460
487
|
meta.append(size);
|
|
461
488
|
}
|
|
462
489
|
card.append(meta);
|
|
463
|
-
if (options.removable) {
|
|
490
|
+
if (options.removable && !attachment.loading) {
|
|
464
491
|
const remove = document.createElement('button');
|
|
465
492
|
remove.type = 'button';
|
|
466
493
|
remove.className = 'codex-attachment-preview-remove';
|