@yesdgq/claude-buddy 1.1.2000 → 1.1.3010
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/assets/config.html +59 -45
- package/assets/config.js +645 -47
- package/assets/editor.css +219 -0
- package/assets/editor.html +60 -0
- package/assets/editor.js +299 -0
- package/assets/images/workbuddy.png +0 -0
- package/assets/libs/style.css +318 -16
- package/dist/index.js +1 -1
- package/package.json +5 -4
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
.editor-page {
|
|
2
|
+
min-height: 100vh;
|
|
3
|
+
padding: var(--space-xl) var(--space-md);
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
.editor-shell {
|
|
7
|
+
max-width: 1100px;
|
|
8
|
+
margin: 0 auto;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.editor-header {
|
|
12
|
+
display: flex;
|
|
13
|
+
align-items: flex-start;
|
|
14
|
+
gap: var(--space-lg);
|
|
15
|
+
margin-bottom: var(--space-lg);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.editor-back-link {
|
|
19
|
+
display: inline-flex;
|
|
20
|
+
align-items: center;
|
|
21
|
+
gap: var(--space-sm);
|
|
22
|
+
flex-shrink: 0;
|
|
23
|
+
padding: 9px 12px;
|
|
24
|
+
border: 1px solid var(--border-default);
|
|
25
|
+
border-radius: var(--radius-sm);
|
|
26
|
+
color: var(--text-secondary);
|
|
27
|
+
text-decoration: none;
|
|
28
|
+
transition: all var(--duration-fast) var(--ease-out-quart);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.editor-back-link:hover {
|
|
32
|
+
border-color: var(--accent-primary);
|
|
33
|
+
color: var(--text-primary);
|
|
34
|
+
background: var(--bg-tertiary);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.editor-heading {
|
|
38
|
+
min-width: 0;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.editor-title-row {
|
|
42
|
+
display: flex;
|
|
43
|
+
align-items: center;
|
|
44
|
+
flex-wrap: wrap;
|
|
45
|
+
gap: var(--space-sm);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.editor-title-row h1 {
|
|
49
|
+
margin: 0;
|
|
50
|
+
color: var(--text-primary);
|
|
51
|
+
font-size: 1.5rem;
|
|
52
|
+
line-height: 1.3;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.editor-format {
|
|
56
|
+
padding: 3px 8px;
|
|
57
|
+
border: 1px solid var(--border-default);
|
|
58
|
+
border-radius: var(--radius-sm);
|
|
59
|
+
color: var(--text-tertiary);
|
|
60
|
+
font-family: var(--font-mono);
|
|
61
|
+
font-size: 0.75rem;
|
|
62
|
+
text-transform: uppercase;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.editor-path {
|
|
66
|
+
display: block;
|
|
67
|
+
margin-top: 4px;
|
|
68
|
+
padding: 0;
|
|
69
|
+
overflow-wrap: anywhere;
|
|
70
|
+
background: transparent;
|
|
71
|
+
color: var(--accent-primary);
|
|
72
|
+
font-size: 0.875rem;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
.editor-panel {
|
|
76
|
+
overflow: hidden;
|
|
77
|
+
border: 1px solid var(--border-default);
|
|
78
|
+
border-radius: var(--radius-md);
|
|
79
|
+
background: var(--bg-secondary);
|
|
80
|
+
box-shadow: var(--shadow-md);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.editor-toolbar {
|
|
84
|
+
display: flex;
|
|
85
|
+
align-items: center;
|
|
86
|
+
justify-content: space-between;
|
|
87
|
+
gap: var(--space-md);
|
|
88
|
+
padding: var(--space-md);
|
|
89
|
+
border-bottom: 1px solid var(--border-subtle);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
.editor-state {
|
|
93
|
+
display: inline-flex;
|
|
94
|
+
align-items: center;
|
|
95
|
+
gap: var(--space-sm);
|
|
96
|
+
min-width: 0;
|
|
97
|
+
color: var(--text-tertiary);
|
|
98
|
+
font-size: 0.8125rem;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.editor-state.is-dirty {
|
|
102
|
+
color: var(--accent-orange);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
.editor-state.is-success {
|
|
106
|
+
color: var(--accent-success);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
.editor-state.is-error {
|
|
110
|
+
color: var(--accent-danger);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
.editor-actions {
|
|
114
|
+
display: flex;
|
|
115
|
+
flex-wrap: wrap;
|
|
116
|
+
justify-content: flex-end;
|
|
117
|
+
gap: var(--space-sm);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
.editor-button {
|
|
121
|
+
display: inline-flex;
|
|
122
|
+
align-items: center;
|
|
123
|
+
justify-content: center;
|
|
124
|
+
gap: 6px;
|
|
125
|
+
min-height: 36px;
|
|
126
|
+
padding: 7px 12px;
|
|
127
|
+
border: 1px solid transparent;
|
|
128
|
+
border-radius: var(--radius-sm);
|
|
129
|
+
font-size: 0.8125rem;
|
|
130
|
+
cursor: pointer;
|
|
131
|
+
transition: all var(--duration-fast) var(--ease-out-quart);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
.editor-button:disabled {
|
|
135
|
+
cursor: not-allowed;
|
|
136
|
+
opacity: 0.5;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
.editor-button-secondary {
|
|
140
|
+
border-color: var(--border-default);
|
|
141
|
+
background: var(--bg-tertiary);
|
|
142
|
+
color: var(--text-secondary);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
.editor-button-secondary:hover:not(:disabled) {
|
|
146
|
+
border-color: var(--accent-primary);
|
|
147
|
+
background: var(--bg-hover);
|
|
148
|
+
color: var(--text-primary);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
.editor-button-primary {
|
|
152
|
+
background: var(--accent-primary);
|
|
153
|
+
color: white;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
.editor-button-primary:hover:not(:disabled) {
|
|
157
|
+
background: var(--accent-primary-hover);
|
|
158
|
+
transform: translateY(-1px);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
.content-editor {
|
|
162
|
+
display: block;
|
|
163
|
+
width: 100%;
|
|
164
|
+
min-height: min(68vh, 720px);
|
|
165
|
+
padding: var(--space-lg);
|
|
166
|
+
resize: vertical;
|
|
167
|
+
border: 0;
|
|
168
|
+
outline: 0;
|
|
169
|
+
background: var(--bg-primary);
|
|
170
|
+
color: var(--text-primary);
|
|
171
|
+
font-family: var(--font-mono);
|
|
172
|
+
font-size: 0.875rem;
|
|
173
|
+
line-height: 1.7;
|
|
174
|
+
tab-size: 2;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
.content-editor:focus {
|
|
178
|
+
box-shadow: inset 0 0 0 2px var(--accent-primary-subtle);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
.content-editor:disabled {
|
|
182
|
+
cursor: wait;
|
|
183
|
+
opacity: 0.75;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
.editor-footer {
|
|
187
|
+
display: flex;
|
|
188
|
+
justify-content: space-between;
|
|
189
|
+
gap: var(--space-md);
|
|
190
|
+
padding: 9px var(--space-md);
|
|
191
|
+
border-top: 1px solid var(--border-subtle);
|
|
192
|
+
color: var(--text-tertiary);
|
|
193
|
+
font-size: 0.75rem;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
@media (max-width: 640px) {
|
|
197
|
+
.editor-page {
|
|
198
|
+
padding: var(--space-md) var(--space-sm);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
.editor-header,
|
|
202
|
+
.editor-toolbar {
|
|
203
|
+
align-items: stretch;
|
|
204
|
+
flex-direction: column;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
.editor-actions {
|
|
208
|
+
justify-content: stretch;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
.editor-button {
|
|
212
|
+
flex: 1;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
.content-editor {
|
|
216
|
+
min-height: 65vh;
|
|
217
|
+
padding: var(--space-md);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="zh-CN" data-theme="dark">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>配置文件编辑器</title>
|
|
7
|
+
<link rel="stylesheet" href="libs/font-awesome.min.css">
|
|
8
|
+
<link rel="stylesheet" href="libs/style.css">
|
|
9
|
+
<link rel="stylesheet" href="editor.css">
|
|
10
|
+
</head>
|
|
11
|
+
<body class="editor-page">
|
|
12
|
+
<main class="editor-shell">
|
|
13
|
+
<header class="editor-header">
|
|
14
|
+
<a href="/" class="editor-back-link">
|
|
15
|
+
<i class="fa fa-arrow-left"></i>
|
|
16
|
+
<span id="back-label">返回配置</span>
|
|
17
|
+
</a>
|
|
18
|
+
<div class="editor-heading">
|
|
19
|
+
<div class="editor-title-row">
|
|
20
|
+
<h1 id="file-title">配置文件</h1>
|
|
21
|
+
<span id="file-format" class="editor-format"></span>
|
|
22
|
+
</div>
|
|
23
|
+
<code id="file-path" class="editor-path"></code>
|
|
24
|
+
</div>
|
|
25
|
+
</header>
|
|
26
|
+
|
|
27
|
+
<section class="editor-panel">
|
|
28
|
+
<div class="editor-toolbar">
|
|
29
|
+
<div class="editor-state">
|
|
30
|
+
<i id="state-icon" class="fa fa-circle-o-notch fa-spin"></i>
|
|
31
|
+
<span id="state-label">加载中...</span>
|
|
32
|
+
</div>
|
|
33
|
+
<div class="editor-actions">
|
|
34
|
+
<button id="format-button" type="button" class="editor-button editor-button-secondary" hidden>
|
|
35
|
+
<i class="fa fa-indent"></i>
|
|
36
|
+
<span id="format-label">格式化 JSON</span>
|
|
37
|
+
</button>
|
|
38
|
+
<button id="reload-button" type="button" class="editor-button editor-button-secondary">
|
|
39
|
+
<i class="fa fa-refresh"></i>
|
|
40
|
+
<span id="reload-label">重新加载</span>
|
|
41
|
+
</button>
|
|
42
|
+
<button id="save-button" type="button" class="editor-button editor-button-primary" disabled>
|
|
43
|
+
<i class="fa fa-save"></i>
|
|
44
|
+
<span id="save-label">保存</span>
|
|
45
|
+
</button>
|
|
46
|
+
</div>
|
|
47
|
+
</div>
|
|
48
|
+
|
|
49
|
+
<textarea id="content-editor" class="content-editor" spellcheck="false" disabled></textarea>
|
|
50
|
+
|
|
51
|
+
<footer class="editor-footer">
|
|
52
|
+
<span id="file-state"></span>
|
|
53
|
+
<span id="line-count">0 行</span>
|
|
54
|
+
</footer>
|
|
55
|
+
</section>
|
|
56
|
+
</main>
|
|
57
|
+
<div id="toast-container"></div>
|
|
58
|
+
<script src="editor.js" defer></script>
|
|
59
|
+
</body>
|
|
60
|
+
</html>
|
package/assets/editor.js
ADDED
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
const FILE_DEFINITIONS = {
|
|
2
|
+
'claude-settings': {
|
|
3
|
+
title: { zh: 'Claude 配置', en: 'Claude Configuration' },
|
|
4
|
+
format: 'json'
|
|
5
|
+
},
|
|
6
|
+
'codex-config': {
|
|
7
|
+
title: { zh: 'Codex 配置', en: 'Codex Configuration' },
|
|
8
|
+
format: 'toml'
|
|
9
|
+
},
|
|
10
|
+
'workbuddy-models': {
|
|
11
|
+
title: { zh: 'WorkBuddy 配置', en: 'WorkBuddy Configuration' },
|
|
12
|
+
format: 'json'
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
const CONFIG_FILE_REFRESH_STORAGE_KEY = 'ccby-config-file-refresh';
|
|
16
|
+
|
|
17
|
+
const translations = {
|
|
18
|
+
zh: {
|
|
19
|
+
back: '返回配置',
|
|
20
|
+
loading: '加载中...',
|
|
21
|
+
loaded: '已加载',
|
|
22
|
+
missing: '文件不存在,可保存创建',
|
|
23
|
+
unsaved: '有未保存的修改',
|
|
24
|
+
saved: '已保存',
|
|
25
|
+
saveSuccess: '配置文件保存成功!',
|
|
26
|
+
loadFailed: '加载失败',
|
|
27
|
+
saveFailed: '保存失败',
|
|
28
|
+
saving: '保存中...',
|
|
29
|
+
invalidJson: 'JSON 格式错误',
|
|
30
|
+
formatJson: '格式化 JSON',
|
|
31
|
+
reload: '重新加载',
|
|
32
|
+
save: '保存',
|
|
33
|
+
lines: '行',
|
|
34
|
+
confirmReload: '当前有未保存的修改,确定重新加载吗?',
|
|
35
|
+
openFailed: '无法打开文件编辑器'
|
|
36
|
+
},
|
|
37
|
+
en: {
|
|
38
|
+
back: 'Back to configuration',
|
|
39
|
+
loading: 'Loading...',
|
|
40
|
+
loaded: 'Loaded',
|
|
41
|
+
missing: 'File does not exist; save to create it',
|
|
42
|
+
unsaved: 'Unsaved changes',
|
|
43
|
+
saved: 'Saved',
|
|
44
|
+
saveSuccess: 'Configuration file saved successfully!',
|
|
45
|
+
loadFailed: 'Load failed',
|
|
46
|
+
saveFailed: 'Save failed',
|
|
47
|
+
saving: 'Saving...',
|
|
48
|
+
invalidJson: 'Invalid JSON',
|
|
49
|
+
formatJson: 'Format JSON',
|
|
50
|
+
reload: 'Reload',
|
|
51
|
+
save: 'Save',
|
|
52
|
+
lines: 'lines',
|
|
53
|
+
confirmReload: 'There are unsaved changes. Reload anyway?',
|
|
54
|
+
openFailed: 'Unable to open file editor'
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const lang = localStorage.getItem('ccby-lang') === 'zh' ? 'zh' : 'en';
|
|
59
|
+
const t = translations[lang];
|
|
60
|
+
const params = new URLSearchParams(window.location.search);
|
|
61
|
+
const fileKey = params.get('fileKey') || '';
|
|
62
|
+
const fileDefinition = FILE_DEFINITIONS[fileKey];
|
|
63
|
+
const elements = {
|
|
64
|
+
backLink: document.querySelector('.editor-back-link'),
|
|
65
|
+
backLabel: document.getElementById('back-label'),
|
|
66
|
+
fileTitle: document.getElementById('file-title'),
|
|
67
|
+
fileFormat: document.getElementById('file-format'),
|
|
68
|
+
filePath: document.getElementById('file-path'),
|
|
69
|
+
stateIcon: document.getElementById('state-icon'),
|
|
70
|
+
stateLabel: document.getElementById('state-label'),
|
|
71
|
+
formatButton: document.getElementById('format-button'),
|
|
72
|
+
formatLabel: document.getElementById('format-label'),
|
|
73
|
+
reloadButton: document.getElementById('reload-button'),
|
|
74
|
+
reloadLabel: document.getElementById('reload-label'),
|
|
75
|
+
saveButton: document.getElementById('save-button'),
|
|
76
|
+
saveLabel: document.getElementById('save-label'),
|
|
77
|
+
contentEditor: document.getElementById('content-editor'),
|
|
78
|
+
fileState: document.getElementById('file-state'),
|
|
79
|
+
lineCount: document.getElementById('line-count')
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
let originalContent = '';
|
|
83
|
+
let fileExists = false;
|
|
84
|
+
let hasSavedChanges = false;
|
|
85
|
+
let refreshNoticeSent = false;
|
|
86
|
+
|
|
87
|
+
function applyStoredTheme() {
|
|
88
|
+
const savedTheme = localStorage.getItem('ccby-theme');
|
|
89
|
+
document.documentElement.setAttribute('data-theme', savedTheme === 'light' ? 'light' : 'dark');
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function setTextContent() {
|
|
93
|
+
elements.backLabel.textContent = t.back;
|
|
94
|
+
elements.formatLabel.textContent = t.formatJson;
|
|
95
|
+
elements.reloadLabel.textContent = t.reload;
|
|
96
|
+
elements.saveLabel.textContent = t.save;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function setState(message, type = '') {
|
|
100
|
+
elements.stateLabel.textContent = message;
|
|
101
|
+
elements.stateIcon.className = type === 'error'
|
|
102
|
+
? 'fa fa-exclamation-circle'
|
|
103
|
+
: (type === 'success' ? 'fa fa-check-circle' : 'fa fa-circle-o-notch fa-spin');
|
|
104
|
+
elements.stateLabel.parentElement.className = `editor-state${type ? ` is-${type}` : ''}`;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function updateEditorMeta() {
|
|
108
|
+
const content = elements.contentEditor.value;
|
|
109
|
+
const lineCount = content ? content.split('\n').length : 0;
|
|
110
|
+
const isDirty = content !== originalContent;
|
|
111
|
+
elements.lineCount.textContent = `${lineCount} ${t.lines}`;
|
|
112
|
+
elements.saveButton.disabled = !fileDefinition || !isDirty;
|
|
113
|
+
elements.fileState.textContent = isDirty
|
|
114
|
+
? t.unsaved
|
|
115
|
+
: (fileExists ? t.loaded : t.missing);
|
|
116
|
+
elements.fileState.className = isDirty ? 'editor-state is-dirty' : 'editor-state';
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function getErrorMessage(error) {
|
|
120
|
+
return error instanceof Error ? error.message : String(error);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function showToast(message, type = 'success') {
|
|
124
|
+
const toastContainer = document.getElementById('toast-container');
|
|
125
|
+
if (!toastContainer) {
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const toast = document.createElement('div');
|
|
130
|
+
toast.className = `toast toast-${type}`;
|
|
131
|
+
|
|
132
|
+
const icon = document.createElement('i');
|
|
133
|
+
icon.className = `fa ${type === 'success' ? 'fa-check-circle' : 'fa-exclamation-circle'}`;
|
|
134
|
+
|
|
135
|
+
const messageElement = document.createElement('span');
|
|
136
|
+
messageElement.className = 'flex-1';
|
|
137
|
+
messageElement.textContent = message;
|
|
138
|
+
|
|
139
|
+
const closeButton = document.createElement('i');
|
|
140
|
+
closeButton.className = 'fa fa-times toast-close';
|
|
141
|
+
closeButton.addEventListener('click', () => toast.remove());
|
|
142
|
+
|
|
143
|
+
toast.append(icon, messageElement, closeButton);
|
|
144
|
+
toastContainer.appendChild(toast);
|
|
145
|
+
|
|
146
|
+
setTimeout(() => {
|
|
147
|
+
if (!toast.isConnected) {
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
toast.style.animation = 'slideOut 0.3s ease-out';
|
|
151
|
+
setTimeout(() => toast.remove(), 300);
|
|
152
|
+
}, 3000);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function notifyConfigPageToRefresh() {
|
|
156
|
+
if (!hasSavedChanges || refreshNoticeSent) {
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
try {
|
|
161
|
+
localStorage.setItem(CONFIG_FILE_REFRESH_STORAGE_KEY, JSON.stringify({
|
|
162
|
+
fileKey,
|
|
163
|
+
timestamp: Date.now()
|
|
164
|
+
}));
|
|
165
|
+
refreshNoticeSent = true;
|
|
166
|
+
} catch {
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
async function loadFile() {
|
|
171
|
+
if (!fileDefinition) {
|
|
172
|
+
setState(t.loadFailed, 'error');
|
|
173
|
+
elements.fileState.textContent = 'Invalid file key';
|
|
174
|
+
elements.contentEditor.disabled = true;
|
|
175
|
+
elements.reloadButton.disabled = true;
|
|
176
|
+
elements.saveButton.disabled = true;
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
setState(t.loading);
|
|
181
|
+
elements.contentEditor.disabled = true;
|
|
182
|
+
elements.reloadButton.disabled = true;
|
|
183
|
+
elements.saveButton.disabled = true;
|
|
184
|
+
|
|
185
|
+
try {
|
|
186
|
+
const response = await fetch(`/api/config-file?fileKey=${encodeURIComponent(fileKey)}`);
|
|
187
|
+
const data = await response.json();
|
|
188
|
+
if (!response.ok) {
|
|
189
|
+
throw new Error(data.error || t.loadFailed);
|
|
190
|
+
}
|
|
191
|
+
originalContent = data.content;
|
|
192
|
+
fileExists = data.exists;
|
|
193
|
+
elements.contentEditor.value = data.content;
|
|
194
|
+
elements.filePath.textContent = data.path;
|
|
195
|
+
elements.fileFormat.textContent = data.format;
|
|
196
|
+
elements.contentEditor.disabled = false;
|
|
197
|
+
elements.reloadButton.disabled = false;
|
|
198
|
+
setState(fileExists ? t.loaded : t.missing, fileExists ? 'success' : '');
|
|
199
|
+
updateEditorMeta();
|
|
200
|
+
} catch (error) {
|
|
201
|
+
setState(`${t.loadFailed}: ${getErrorMessage(error)}`, 'error');
|
|
202
|
+
elements.reloadButton.disabled = false;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
async function saveFile() {
|
|
207
|
+
const content = elements.contentEditor.value;
|
|
208
|
+
if (fileDefinition.format === 'json') {
|
|
209
|
+
try {
|
|
210
|
+
JSON.parse(content.replace(/^\uFEFF/, ''));
|
|
211
|
+
} catch (error) {
|
|
212
|
+
setState(`${t.invalidJson}: ${getErrorMessage(error)}`, 'error');
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
elements.saveButton.disabled = true;
|
|
218
|
+
setState(t.saving);
|
|
219
|
+
try {
|
|
220
|
+
const response = await fetch('/api/config-file', {
|
|
221
|
+
method: 'POST',
|
|
222
|
+
headers: { 'Content-Type': 'application/json' },
|
|
223
|
+
body: JSON.stringify({ fileKey, content })
|
|
224
|
+
});
|
|
225
|
+
const data = await response.json();
|
|
226
|
+
if (!response.ok) {
|
|
227
|
+
throw new Error(data.error || t.saveFailed);
|
|
228
|
+
}
|
|
229
|
+
originalContent = content;
|
|
230
|
+
fileExists = true;
|
|
231
|
+
hasSavedChanges = true;
|
|
232
|
+
setState(t.saved, 'success');
|
|
233
|
+
showToast(t.saveSuccess, 'success');
|
|
234
|
+
updateEditorMeta();
|
|
235
|
+
} catch (error) {
|
|
236
|
+
setState(`${t.saveFailed}: ${getErrorMessage(error)}`, 'error');
|
|
237
|
+
showToast(`${t.saveFailed}: ${getErrorMessage(error)}`, 'error');
|
|
238
|
+
updateEditorMeta();
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function formatJson() {
|
|
243
|
+
try {
|
|
244
|
+
const parsed = JSON.parse(elements.contentEditor.value.replace(/^\uFEFF/, ''));
|
|
245
|
+
elements.contentEditor.value = `${JSON.stringify(parsed, null, 2)}\n`;
|
|
246
|
+
updateEditorMeta();
|
|
247
|
+
elements.contentEditor.focus();
|
|
248
|
+
} catch (error) {
|
|
249
|
+
setState(`${t.invalidJson}: ${getErrorMessage(error)}`, 'error');
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function closeEditor() {
|
|
254
|
+
notifyConfigPageToRefresh();
|
|
255
|
+
window.close();
|
|
256
|
+
setTimeout(() => {
|
|
257
|
+
if (!window.closed) {
|
|
258
|
+
window.location.replace('/');
|
|
259
|
+
}
|
|
260
|
+
}, 150);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
window.addEventListener('beforeunload', notifyConfigPageToRefresh);
|
|
264
|
+
window.addEventListener('storage', event => {
|
|
265
|
+
if (event.key === 'ccby-theme') {
|
|
266
|
+
applyStoredTheme();
|
|
267
|
+
}
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
applyStoredTheme();
|
|
271
|
+
setTextContent();
|
|
272
|
+
if (fileDefinition) {
|
|
273
|
+
elements.fileTitle.textContent = fileDefinition.title[lang];
|
|
274
|
+
elements.fileFormat.textContent = fileDefinition.format;
|
|
275
|
+
elements.formatButton.hidden = fileDefinition.format !== 'json';
|
|
276
|
+
document.title = `${fileDefinition.title[lang]} - Claude Buddy`;
|
|
277
|
+
}
|
|
278
|
+
elements.contentEditor.addEventListener('input', updateEditorMeta);
|
|
279
|
+
elements.backLink.addEventListener('click', event => {
|
|
280
|
+
event.preventDefault();
|
|
281
|
+
closeEditor();
|
|
282
|
+
});
|
|
283
|
+
elements.reloadButton.addEventListener('click', () => {
|
|
284
|
+
if (elements.contentEditor.value !== originalContent && !window.confirm(t.confirmReload)) {
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
loadFile();
|
|
288
|
+
});
|
|
289
|
+
elements.saveButton.addEventListener('click', saveFile);
|
|
290
|
+
elements.formatButton.addEventListener('click', formatJson);
|
|
291
|
+
window.addEventListener('keydown', event => {
|
|
292
|
+
if ((event.ctrlKey || event.metaKey) && event.key.toLowerCase() === 's') {
|
|
293
|
+
event.preventDefault();
|
|
294
|
+
if (!elements.saveButton.disabled) {
|
|
295
|
+
saveFile();
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
});
|
|
299
|
+
loadFile();
|
|
Binary file
|