codex-overleaf-link 2.3.3 → 2.3.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 +318 -211
- package/extension/bootstrap/manifest.template.json +1 -1
- package/extension/src/background.js +5 -0
- package/extension/src/content/composerPanel.js +1 -1
- package/extension/src/content/contentRuntime.js +68 -33
- package/extension/src/content/generated/content.bundle.js +604 -132
- package/extension/src/content/generated/content.bundle.meta.json +5 -3
- package/extension/src/content/globalPreferencesController.js +125 -0
- package/extension/src/content/legacyGlobalRegistry.js +2 -0
- package/extension/src/content/modelPicker.js +1 -1
- package/extension/src/content/panelMaintenance.js +2 -0
- package/extension/src/content/panelRenderer.js +86 -2
- package/extension/src/content/projectSettingsCoordinator.js +1 -0
- package/extension/src/content/providerSettingsCoordinator.js +15 -0
- package/extension/src/content/providerSettingsDialog.js +24 -15
- package/extension/src/content/runController.js +35 -0
- package/extension/src/content/settingsPanel.js +5 -3
- package/extension/src/content/themeController.js +3 -0
- package/extension/src/popup.js +10 -10
- package/extension/src/shared/compatibility.js +1 -1
- package/extension/src/shared/globalPreferences.js +131 -0
- package/extension/src/shared/i18n.js +2 -2
- package/extension/styles/panel.css +89 -2
- package/package.json +1 -1
- package/scripts/install-managed.mjs +1 -1
|
@@ -51,6 +51,9 @@
|
|
|
51
51
|
const resolved = resolveTheme(preference, mediaQueryList);
|
|
52
52
|
if (panelRoot && typeof panelRoot.setAttribute === 'function') {
|
|
53
53
|
panelRoot.setAttribute('data-theme', resolved);
|
|
54
|
+
for (const target of panelRoot.ownerDocument?.querySelectorAll?.('.codex-provider-dialog-root, #codex-overleaf-launcher') || []) {
|
|
55
|
+
target.setAttribute('data-theme', resolved);
|
|
56
|
+
}
|
|
54
57
|
}
|
|
55
58
|
return resolved;
|
|
56
59
|
}
|
package/extension/src/popup.js
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
let currentInstallCommand = DEFAULT_INSTALL_COMMAND;
|
|
19
19
|
|
|
20
20
|
installCommand.textContent = currentInstallCommand;
|
|
21
|
-
button.textContent = '
|
|
21
|
+
button.textContent = 'Show Codex button in Overleaf';
|
|
22
22
|
setVersionStatus({
|
|
23
23
|
status: 'native_missing',
|
|
24
24
|
classification: 'incompatible',
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
return;
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
await chrome.tabs.sendMessage(tab.id, { type: 'codex-overleaf/toggle-
|
|
35
|
+
await chrome.tabs.sendMessage(tab.id, { type: 'codex-overleaf/toggle-launcher' });
|
|
36
36
|
window.close();
|
|
37
37
|
});
|
|
38
38
|
|
|
@@ -56,20 +56,20 @@
|
|
|
56
56
|
const tab = await getActiveOverleafProjectTab();
|
|
57
57
|
activeOverleafTab = tab;
|
|
58
58
|
if (!tab?.id) {
|
|
59
|
-
button.textContent = '
|
|
59
|
+
button.textContent = 'Show Codex button in Overleaf';
|
|
60
60
|
return;
|
|
61
61
|
}
|
|
62
62
|
|
|
63
63
|
try {
|
|
64
64
|
const response = await chrome.tabs.sendMessage(tab.id, { type: 'codex-overleaf/get-panel-state' });
|
|
65
|
-
const
|
|
66
|
-
button.textContent =
|
|
67
|
-
status.textContent =
|
|
68
|
-
? '
|
|
69
|
-
: '
|
|
65
|
+
const visible = response?.launcherVisible !== false;
|
|
66
|
+
button.textContent = visible ? 'Hide Codex edge button' : 'Show Codex edge button';
|
|
67
|
+
status.textContent = visible
|
|
68
|
+
? 'The edge button appears whenever the Codex panel is closed.'
|
|
69
|
+
: 'The Codex edge button is hidden in Overleaf.';
|
|
70
70
|
} catch (_error) {
|
|
71
|
-
button.textContent = '
|
|
72
|
-
status.textContent = 'Refresh the Overleaf tab, then
|
|
71
|
+
button.textContent = 'Show Codex button in Overleaf';
|
|
72
|
+
status.textContent = 'Refresh the Overleaf tab, then show the Codex button.';
|
|
73
73
|
}
|
|
74
74
|
}
|
|
75
75
|
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
const MIN_NATIVE_VERSION = '1.0.0';
|
|
13
13
|
const MIN_COMPATIBLE_NATIVE_VERSION = '1.0.0';
|
|
14
14
|
const MIN_COMPATIBLE_EXTENSION_VERSION = '1.0.0';
|
|
15
|
-
const BUILD_TARGET_VERSION = '2.3.
|
|
15
|
+
const BUILD_TARGET_VERSION = '2.3.5';
|
|
16
16
|
const DEFAULT_CHROME_EXTENSION_ID = 'illdpneeeopfffmiepaejglgmhpmdhdc';
|
|
17
17
|
const REQUIRED_CAPABILITIES = Object.freeze([
|
|
18
18
|
'bridgePing',
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
(function (root, factory) {
|
|
2
|
+
const api = factory();
|
|
3
|
+
if (typeof module === 'object' && module.exports) module.exports = api;
|
|
4
|
+
if (root) root.CodexOverleafGlobalPreferences = api;
|
|
5
|
+
})(typeof globalThis !== 'undefined' ? globalThis : this, function () {
|
|
6
|
+
'use strict';
|
|
7
|
+
|
|
8
|
+
const STORAGE_KEY = 'codexOverleafGlobalPrefsV1';
|
|
9
|
+
const LEGACY_KEY = 'codexOverleafPrefs';
|
|
10
|
+
const READ_MESSAGE = 'codex-overleaf/global-preferences/read';
|
|
11
|
+
const PATCH_MESSAGE = 'codex-overleaf/global-preferences/patch';
|
|
12
|
+
const DEFAULTS = Object.freeze({ theme: 'dark', locale: 'en', preloadProjectContext: true,
|
|
13
|
+
loadCodexLocalSkills: true, loadCodexOverleafSkills: true });
|
|
14
|
+
const FIELDS = Object.freeze([...Object.keys(DEFAULTS), 'codexOverleafSkillEnabled']);
|
|
15
|
+
|
|
16
|
+
function validSkillId(id) {
|
|
17
|
+
return /^[a-zA-Z0-9][a-zA-Z0-9._-]{0,79}$/.test(id)
|
|
18
|
+
&& !['__proto__', 'prototype', 'constructor'].includes(id) && !id.includes('..');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function pick(value = {}) {
|
|
22
|
+
const skills = {};
|
|
23
|
+
for (const [id, enabled] of Object.entries(value.codexOverleafSkillEnabled || {}).slice(0, 200)) {
|
|
24
|
+
if (validSkillId(id) && typeof enabled === 'boolean') skills[id] = enabled;
|
|
25
|
+
}
|
|
26
|
+
return {
|
|
27
|
+
theme: ['dark', 'light', 'auto'].includes(value.theme) ? value.theme : 'dark',
|
|
28
|
+
locale: value.locale === 'zh' ? 'zh' : 'en',
|
|
29
|
+
preloadProjectContext: value.preloadProjectContext !== false,
|
|
30
|
+
loadCodexLocalSkills: value.loadCodexLocalSkills !== false,
|
|
31
|
+
loadCodexOverleafSkills: value.loadCodexOverleafSkills !== false,
|
|
32
|
+
codexOverleafSkillEnabled: skills
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function validatePatch(value) {
|
|
37
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) throw new Error('Invalid global preference patch.');
|
|
38
|
+
const patch = {};
|
|
39
|
+
for (const [key, item] of Object.entries(value)) {
|
|
40
|
+
if (!FIELDS.includes(key)) throw new Error('Unknown global preference: ' + key);
|
|
41
|
+
if (key === 'theme' && !['dark', 'light', 'auto'].includes(item)
|
|
42
|
+
|| key === 'locale' && !['en', 'zh'].includes(item)
|
|
43
|
+
|| typeof DEFAULTS[key] === 'boolean' && typeof item !== 'boolean') {
|
|
44
|
+
throw new Error('Invalid global preference: ' + key);
|
|
45
|
+
}
|
|
46
|
+
if (key === 'codexOverleafSkillEnabled') {
|
|
47
|
+
if (!item || typeof item !== 'object' || Array.isArray(item) || Object.keys(item).length > 200) {
|
|
48
|
+
throw new Error('Invalid skill preference map.');
|
|
49
|
+
}
|
|
50
|
+
for (const [id, enabled] of Object.entries(item)) {
|
|
51
|
+
if (!validSkillId(id) || typeof enabled !== 'boolean') throw new Error('Invalid skill preference.');
|
|
52
|
+
}
|
|
53
|
+
patch[key] = { ...item };
|
|
54
|
+
} else patch[key] = item;
|
|
55
|
+
}
|
|
56
|
+
return patch;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function applyPatch(values, patch) {
|
|
60
|
+
return { ...pick(values), ...patch, codexOverleafSkillEnabled: {
|
|
61
|
+
...pick(values).codexOverleafSkillEnabled, ...(patch.codexOverleafSkillEnabled || {})
|
|
62
|
+
} };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function changedKeys(left, right) {
|
|
66
|
+
return FIELDS.filter(key => JSON.stringify(left[key]) !== JSON.stringify(right[key]));
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function validateSnapshot(value) {
|
|
70
|
+
if (!value || value.schemaVersion !== 1 || !Number.isSafeInteger(value.revision) || value.revision < 1) {
|
|
71
|
+
throw new Error('Invalid global preference revision.');
|
|
72
|
+
}
|
|
73
|
+
validatePatch(value.values);
|
|
74
|
+
if (!FIELDS.every(key => Object.hasOwn(value.values, key))) throw new Error('Incomplete global preferences.');
|
|
75
|
+
return { schemaVersion: 1, revision: value.revision, values: pick(value.values) };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function createStore(storage) {
|
|
79
|
+
let tail = Promise.resolve();
|
|
80
|
+
function serial(work) {
|
|
81
|
+
const pending = tail.then(work);
|
|
82
|
+
tail = pending.catch(() => {});
|
|
83
|
+
return pending;
|
|
84
|
+
}
|
|
85
|
+
async function read() {
|
|
86
|
+
const stored = await storage.get([STORAGE_KEY, LEGACY_KEY]);
|
|
87
|
+
if (stored[STORAGE_KEY] !== undefined) return validateSnapshot(stored[STORAGE_KEY]);
|
|
88
|
+
const snapshot = { schemaVersion: 1, revision: 1, values: pick(stored[LEGACY_KEY] || {}) };
|
|
89
|
+
await storage.set({ [STORAGE_KEY]: snapshot });
|
|
90
|
+
return snapshot;
|
|
91
|
+
}
|
|
92
|
+
return Object.freeze({
|
|
93
|
+
read: () => serial(read),
|
|
94
|
+
patch: input => serial(async () => {
|
|
95
|
+
const patch = validatePatch(input);
|
|
96
|
+
const previous = await read();
|
|
97
|
+
const values = applyPatch(previous.values, patch);
|
|
98
|
+
if (Object.keys(values.codexOverleafSkillEnabled).length > 200) throw new Error('Too many skill preferences.');
|
|
99
|
+
if (!changedKeys(previous.values, values).length) return previous;
|
|
100
|
+
const snapshot = { schemaVersion: 1, revision: previous.revision + 1, values };
|
|
101
|
+
await storage.set({ [STORAGE_KEY]: snapshot });
|
|
102
|
+
return snapshot;
|
|
103
|
+
})
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function installBackground(chromeApi) {
|
|
108
|
+
const store = createStore(chromeApi.storage?.local);
|
|
109
|
+
chromeApi.runtime.onMessage.addListener((message, sender, respond) => {
|
|
110
|
+
if (![READ_MESSAGE, PATCH_MESSAGE].includes(message?.type)) return undefined;
|
|
111
|
+
let allowed = false;
|
|
112
|
+
try {
|
|
113
|
+
const url = new URL(sender?.tab?.url || '');
|
|
114
|
+
allowed = sender.id === chromeApi.runtime.id && url.protocol === 'https:'
|
|
115
|
+
&& ['overleaf.com', 'www.overleaf.com'].includes(url.hostname)
|
|
116
|
+
&& /^\/project(?:\/|$)/.test(url.pathname);
|
|
117
|
+
} catch (_error) { /* reject unrelated contexts */ }
|
|
118
|
+
if (!allowed) {
|
|
119
|
+
respond({ ok: false, error: { message: 'Global preferences require an Overleaf tab.' } });
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
const pending = message.type === READ_MESSAGE ? store.read() : store.patch(message.patch);
|
|
123
|
+
pending.then(result => respond({ ok: true, result }), error => respond({ ok: false, error: { message: error.message } }));
|
|
124
|
+
return true;
|
|
125
|
+
});
|
|
126
|
+
return store;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return Object.freeze({ STORAGE_KEY, LEGACY_KEY, READ_MESSAGE, PATCH_MESSAGE, FIELDS,
|
|
130
|
+
pick, validatePatch, validateSnapshot, applyPatch, changedKeys, createStore, installBackground });
|
|
131
|
+
});
|
|
@@ -380,7 +380,7 @@
|
|
|
380
380
|
contextReadFailed: 'Could not read files: {message}',
|
|
381
381
|
contextNoFiles: 'No usable text files were read from this Overleaf project.',
|
|
382
382
|
contextSelectedFiles: 'Added {count} @file items. Future tasks will focus on them first.',
|
|
383
|
-
contextDefaultWholeProject: 'Default is the whole project.
|
|
383
|
+
contextDefaultWholeProject: 'Default is the whole project. Choose files or add @compile-log. For a section, name it in your request.',
|
|
384
384
|
preloadProjectContextTitle: 'Preload project context',
|
|
385
385
|
preloadProjectContextDescription: 'Fetch the exact project file list in the background after an Overleaf project becomes idle.',
|
|
386
386
|
contextResourceTitle: '{path} (non-text resource, shown only as project structure for now)',
|
|
@@ -900,7 +900,7 @@
|
|
|
900
900
|
contextReadFailed: '读取文件失败:{message}',
|
|
901
901
|
contextNoFiles: '没有从当前 Overleaf 项目读取到可用文本文件。',
|
|
902
902
|
contextSelectedFiles: '已添加 {count} 个 @file;后续任务会优先围绕它们。',
|
|
903
|
-
contextDefaultWholeProject: '
|
|
903
|
+
contextDefaultWholeProject: '默认使用整个项目。可选择文件或添加 @compile-log;处理章节时请在请求中注明章节名称。',
|
|
904
904
|
preloadProjectContextTitle: '预加载项目上下文',
|
|
905
905
|
preloadProjectContextDescription: 'Overleaf 项目进入空闲状态后,在后台读取精确的项目文件列表。',
|
|
906
906
|
contextResourceTitle: '{path}(非文本资源,当前只作为项目结构显示)',
|
|
@@ -170,14 +170,14 @@
|
|
|
170
170
|
}
|
|
171
171
|
|
|
172
172
|
.codex-provider-dialog-root {
|
|
173
|
-
--provider-bg:
|
|
173
|
+
--provider-bg: #151716;
|
|
174
174
|
--provider-surface: #1c1f1d;
|
|
175
175
|
--provider-surface-raised: #222622;
|
|
176
176
|
--provider-line: rgba(255, 255, 255, 0.12);
|
|
177
177
|
--provider-muted: #a0a59f;
|
|
178
178
|
--provider-accent: #79d58a;
|
|
179
179
|
--provider-danger: #ef8176;
|
|
180
|
-
--provider-text:
|
|
180
|
+
--provider-text: #eef0ec;
|
|
181
181
|
--provider-field-label: #dfe3dd;
|
|
182
182
|
--provider-control-bg: rgba(255, 255, 255, 0.035);
|
|
183
183
|
--provider-hover: rgba(255, 255, 255, 0.07);
|
|
@@ -191,6 +191,14 @@
|
|
|
191
191
|
position: fixed;
|
|
192
192
|
inset: 0;
|
|
193
193
|
z-index: 2147483600;
|
|
194
|
+
margin: 0;
|
|
195
|
+
padding: 0;
|
|
196
|
+
border: 0;
|
|
197
|
+
width: 100vw;
|
|
198
|
+
max-width: none;
|
|
199
|
+
height: 100vh;
|
|
200
|
+
max-height: none;
|
|
201
|
+
background: transparent;
|
|
194
202
|
color: var(--provider-text);
|
|
195
203
|
font-family: "Aptos", "SF Pro Text", sans-serif;
|
|
196
204
|
}
|
|
@@ -217,6 +225,7 @@
|
|
|
217
225
|
}
|
|
218
226
|
|
|
219
227
|
.codex-provider-dialog-root[hidden] { display: none !important; }
|
|
228
|
+
.codex-provider-dialog-root::backdrop { background: transparent; }
|
|
220
229
|
|
|
221
230
|
.codex-provider-dialog-backdrop {
|
|
222
231
|
position: absolute;
|
|
@@ -650,6 +659,78 @@
|
|
|
650
659
|
overflow: hidden;
|
|
651
660
|
}
|
|
652
661
|
|
|
662
|
+
#codex-overleaf-launcher {
|
|
663
|
+
position: fixed;
|
|
664
|
+
top: 50%;
|
|
665
|
+
right: 0;
|
|
666
|
+
z-index: 2147483646;
|
|
667
|
+
display: grid;
|
|
668
|
+
width: 26px;
|
|
669
|
+
height: 36px;
|
|
670
|
+
padding: 0;
|
|
671
|
+
place-items: center;
|
|
672
|
+
border: 1px solid #3a3a3a;
|
|
673
|
+
border-right: 0;
|
|
674
|
+
border-radius: 7px 0 0 7px;
|
|
675
|
+
background: #181818;
|
|
676
|
+
box-shadow: -1px 1px 5px rgba(0, 0, 0, 0.18);
|
|
677
|
+
color: #8a8a8a;
|
|
678
|
+
cursor: pointer;
|
|
679
|
+
opacity: 0.82;
|
|
680
|
+
transform: translateY(-50%);
|
|
681
|
+
transition: width 120ms ease, background 120ms ease, color 120ms ease, opacity 120ms ease;
|
|
682
|
+
appearance: none;
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
#codex-overleaf-launcher[hidden] { display: none !important; }
|
|
686
|
+
|
|
687
|
+
#codex-overleaf-launcher:hover {
|
|
688
|
+
width: 29px;
|
|
689
|
+
background: #232323;
|
|
690
|
+
color: #b7b7b7;
|
|
691
|
+
opacity: 1;
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
#codex-overleaf-launcher:active {
|
|
695
|
+
background: #2a2d2e;
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
#codex-overleaf-launcher:focus-visible {
|
|
699
|
+
outline: 2px solid #6aa4d8;
|
|
700
|
+
outline-offset: 2px;
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
#codex-overleaf-launcher .codex-icon,
|
|
704
|
+
#codex-overleaf-launcher .codex-icon svg {
|
|
705
|
+
display: block;
|
|
706
|
+
width: 14px;
|
|
707
|
+
height: 14px;
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
#codex-overleaf-launcher .codex-icon svg {
|
|
711
|
+
fill: none;
|
|
712
|
+
stroke: currentColor;
|
|
713
|
+
stroke-width: 1.45;
|
|
714
|
+
stroke-linecap: round;
|
|
715
|
+
stroke-linejoin: round;
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
#codex-overleaf-panel[data-theme="light"] ~ #codex-overleaf-launcher {
|
|
719
|
+
border-color: #d7dbe0;
|
|
720
|
+
background: #f3f4f6;
|
|
721
|
+
box-shadow: -1px 1px 5px rgba(31, 35, 40, 0.12);
|
|
722
|
+
color: #6a7280;
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
#codex-overleaf-panel[data-theme="light"] ~ #codex-overleaf-launcher:hover {
|
|
726
|
+
background: #eef0f3;
|
|
727
|
+
color: #3d4651;
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
@media (prefers-reduced-motion: reduce) {
|
|
731
|
+
#codex-overleaf-launcher { transition: none; }
|
|
732
|
+
}
|
|
733
|
+
|
|
653
734
|
:root.codex-overleaf-panel-compact body {
|
|
654
735
|
width: auto !important;
|
|
655
736
|
max-width: none !important;
|
|
@@ -1729,9 +1810,15 @@
|
|
|
1729
1810
|
box-sizing: border-box;
|
|
1730
1811
|
width: var(--codex-overleaf-panel-width);
|
|
1731
1812
|
height: 100vh;
|
|
1813
|
+
max-width: none;
|
|
1814
|
+
max-height: none;
|
|
1815
|
+
margin: 0;
|
|
1816
|
+
border: 0;
|
|
1817
|
+
color: inherit;
|
|
1732
1818
|
padding: 14px;
|
|
1733
1819
|
background: rgba(12, 12, 12, 0.68);
|
|
1734
1820
|
}
|
|
1821
|
+
#codex-overleaf-panel .codex-plugin-confirm::backdrop { background: transparent; }
|
|
1735
1822
|
|
|
1736
1823
|
#codex-overleaf-panel .codex-plugin-confirm-card {
|
|
1737
1824
|
width: 100%;
|
package/package.json
CHANGED
|
@@ -21,7 +21,7 @@ export function formatInstallManagedHuman(result) {
|
|
|
21
21
|
'1. Open chrome://extensions and enable Developer mode.',
|
|
22
22
|
'2. Remove the previous Codex Overleaf Link entry if Chrome shows a duplicate.',
|
|
23
23
|
'3. Choose Load unpacked and select: ' + result.loadUnpackedPath,
|
|
24
|
-
'Future stable
|
|
24
|
+
'Future signed stable updates are checked automatically. Choose Update now to authorize a version; installation waits until Overleaf is saved and idle.',
|
|
25
25
|
''
|
|
26
26
|
].join('\n');
|
|
27
27
|
}
|