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.
@@ -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
  }
@@ -18,7 +18,7 @@
18
18
  let currentInstallCommand = DEFAULT_INSTALL_COMMAND;
19
19
 
20
20
  installCommand.textContent = currentInstallCommand;
21
- button.textContent = 'Open panel in Overleaf';
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-panel' });
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 = 'Open panel in Overleaf';
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 open = response?.open === true;
66
- button.textContent = open ? 'Close panel in Overleaf' : 'Open panel in Overleaf';
67
- status.textContent = open
68
- ? 'Panel is open in this Overleaf tab.'
69
- : 'Panel is closed in this Overleaf tab.';
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 = 'Open panel in Overleaf';
72
- status.textContent = 'Refresh the Overleaf tab, then open the panel.';
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.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. Add @file; @compile-log and @current-section are available as context options.',
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: '默认使用整个项目。可添加 @file;@compile-log 和 @current-section 将作为后续上下文能力。',
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: var(--provider-panel-bg, #151716);
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: var(--provider-panel-color, #eef0ec);
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codex-overleaf-link",
3
- "version": "2.3.3",
3
+ "version": "2.3.5",
4
4
  "description": "Cross-platform Chrome bridge that connects Codex to the active Overleaf project.",
5
5
  "license": "MIT",
6
6
  "type": "commonjs",
@@ -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 extension and native-host updates will install automatically when Overleaf is saved and idle.',
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
  }