@flun/html-template 4.3.1 → 4.4.1
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/customize/routes.js +1 -1
- package/f-CHANGELOG.md +7 -14
- package/package.json +1 -1
- package/static/script.css +143 -111
- package/static/script.js +304 -206
- package/templates/script.html +36 -39
package/customize/routes.js
CHANGED
package/f-CHANGELOG.md
CHANGED
|
@@ -1,17 +1,10 @@
|
|
|
1
1
|
# 变更日志
|
|
2
|
+
## [4.4.1] - 2026-05-31 14:39
|
|
3
|
+
### 优化
|
|
4
|
+
- 删除了 customize/routes.js 文件中上一次优化造成的冗余路由;
|
|
5
|
+
## [4.4.0] - 2026-05-31 14:25
|
|
6
|
+
### 优化
|
|
7
|
+
- 修改辅助功能(在线编辑css文件)中预览逻辑:删除单预览按钮,增加上、下、左、右和单页面预览按钮,及相关逻辑的全面优化,让体验更好;
|
|
2
8
|
## [4.3.1] - 2026-05-30 11:42
|
|
3
9
|
### 优化
|
|
4
|
-
- https启用成功后,不在打印证书路径,只给出成功提示;
|
|
5
|
-
## [4.3.0] - 2026-05-29 10:05
|
|
6
|
-
### 新增
|
|
7
|
-
- 开发服务器支持 HTTPS 协议,可通过示例文件: `dev.js` 中的 `https`、`httpsKey`、`httpsCert` 参数启用。
|
|
8
|
-
- 集成 `@flun/dns-auto-ssl` 自动生成受信任的证书,简化本地 HTTPS 配置(推荐方式)。
|
|
9
|
-
- 在配置选项中补充完整的 HTTPS 启用说明(含自定义证书与自动 SSL 两种方式)。
|
|
10
|
-
- 启动服务器时增加对证书文件存在性的校验,避免因路径错误导致服务崩溃。
|
|
11
|
-
- 控制台输出增加 HTTPS 协议访问提示,并在使用 localhost 访问 HTTPS 时给出警告。
|
|
12
|
-
### 修复:
|
|
13
|
-
- 修复个人资料页中硬件信息过长时导致删除按钮文字挤压样式改变的问题;
|
|
14
|
-
- 修复打包后运行项目时,自定义目录中的 hotReloadInjector.js 文件逻辑中对是否发送 IO 到页面判断的缺失,造成的错误提示;
|
|
15
|
-
### 优化:
|
|
16
|
-
- 将 account.js 中 cookie 的 secure 属性由固定 false 改为 'auto';
|
|
17
|
-
现在会根据请求是否为 HTTPS 自动设置 Secure 标志:HTTPS 下自动设为 true,HTTP 下保持 false,从而同时兼容本地开发环境和线上安全传输;
|
|
10
|
+
- https启用成功后,不在打印证书路径,只给出成功提示;
|
package/package.json
CHANGED
package/static/script.css
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
/*
|
|
1
|
+
/* ========== 全局与容器样式 ========== */
|
|
2
|
+
.container {
|
|
3
|
+
max-width: 100%;
|
|
4
|
+
margin: 0px;
|
|
5
|
+
padding: 0px;
|
|
6
|
+
}
|
|
7
|
+
|
|
2
8
|
.modal {
|
|
3
9
|
position: fixed;
|
|
4
10
|
top: 0;
|
|
@@ -7,32 +13,106 @@
|
|
|
7
13
|
height: 100%;
|
|
8
14
|
background-color: rgba(0, 0, 0, 0.7);
|
|
9
15
|
z-index: 1000;
|
|
16
|
+
display: none;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/* ========== 布局工具栏 ========== */
|
|
20
|
+
.layout-toolbar {
|
|
21
|
+
display: flex;
|
|
22
|
+
align-items: center;
|
|
23
|
+
justify-content: center;
|
|
24
|
+
gap: 10px;
|
|
25
|
+
padding: 8px 16px;
|
|
26
|
+
background: #2c2e3e;
|
|
27
|
+
border-bottom: 1px solid #44475a;
|
|
28
|
+
border-radius: 8px;
|
|
29
|
+
width: 100%;
|
|
30
|
+
box-sizing: border-box;
|
|
31
|
+
position: relative;
|
|
32
|
+
z-index: 1000;
|
|
33
|
+
flex-wrap: wrap;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.layout-btn {
|
|
37
|
+
background: #44475a;
|
|
38
|
+
color: var(--text-color);
|
|
39
|
+
border: none;
|
|
40
|
+
padding: 6px 16px;
|
|
41
|
+
border-radius: 30px;
|
|
42
|
+
font-size: 13px;
|
|
43
|
+
cursor: pointer;
|
|
44
|
+
transition: 0.2s;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.layout-btn.active {
|
|
48
|
+
background: #14712a;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.layout-btn:hover {
|
|
52
|
+
background: #6272a4;
|
|
53
|
+
transform: translateY(-1px);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
#cancelPreviewBtn {
|
|
57
|
+
display: none;
|
|
58
|
+
background: #d94bec;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
#cancelPreviewBtn:hover {
|
|
62
|
+
background-color: #821d90;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/* ========== 主工作区 ========== */
|
|
66
|
+
.workspace {
|
|
67
|
+
display: flex;
|
|
68
|
+
flex: 1;
|
|
69
|
+
gap: 5px;
|
|
70
|
+
overflow: hidden;
|
|
71
|
+
background: #282a36;
|
|
72
|
+
transition: all 0.2s ease;
|
|
73
|
+
width: 100%;
|
|
74
|
+
height: calc(100vh - 50px);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.workspace > #cssEditor,
|
|
78
|
+
.workspace > #preview {
|
|
79
|
+
flex: 1 1 0;
|
|
80
|
+
min-width: 0;
|
|
81
|
+
min-height: 0;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.workspace.single-preview #cssEditor {
|
|
85
|
+
display: none;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.workspace.single-preview #preview {
|
|
89
|
+
width: 100%;
|
|
90
|
+
flex: 1;
|
|
10
91
|
}
|
|
11
92
|
|
|
93
|
+
/* ========== 编辑器面板 #cssEditor ========== */
|
|
12
94
|
#cssEditor {
|
|
13
95
|
display: flex;
|
|
14
96
|
flex-direction: column;
|
|
15
97
|
background: rgb(175, 63, 63);
|
|
16
|
-
padding:
|
|
17
|
-
border-radius:
|
|
18
|
-
width:
|
|
19
|
-
|
|
20
|
-
height: 800px;
|
|
21
|
-
min-width: 300px;
|
|
22
|
-
min-height: 200px;
|
|
23
|
-
overflow: auto;
|
|
24
|
-
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);
|
|
25
|
-
position: fixed;
|
|
26
|
-
top: 50%;
|
|
27
|
-
left: 50%;
|
|
28
|
-
cursor: grab;
|
|
98
|
+
padding: 0;
|
|
99
|
+
border-radius: 0;
|
|
100
|
+
width: 100%;
|
|
101
|
+
height: 100%;
|
|
29
102
|
box-sizing: border-box;
|
|
30
|
-
|
|
31
|
-
|
|
103
|
+
overflow: auto;
|
|
104
|
+
position: relative;
|
|
105
|
+
top: auto;
|
|
106
|
+
left: auto;
|
|
107
|
+
transform: none;
|
|
108
|
+
max-width: none;
|
|
109
|
+
box-shadow: none;
|
|
110
|
+
cursor: default;
|
|
111
|
+
z-index: auto;
|
|
32
112
|
}
|
|
33
113
|
|
|
34
114
|
#cssEditor:active {
|
|
35
|
-
cursor:
|
|
115
|
+
cursor: default;
|
|
36
116
|
}
|
|
37
117
|
|
|
38
118
|
#cssEditor .header {
|
|
@@ -40,17 +120,18 @@
|
|
|
40
120
|
justify-content: space-between;
|
|
41
121
|
align-items: center;
|
|
42
122
|
margin-bottom: 10px;
|
|
43
|
-
padding
|
|
123
|
+
padding: 15px 20px 10px 20px;
|
|
44
124
|
border-bottom: 1px solid #eee;
|
|
45
|
-
cursor:
|
|
125
|
+
cursor: default;
|
|
126
|
+
background: inherit;
|
|
46
127
|
}
|
|
47
128
|
|
|
48
129
|
#cssEditor .header:active {
|
|
49
|
-
cursor:
|
|
130
|
+
cursor: default;
|
|
50
131
|
}
|
|
51
132
|
|
|
52
133
|
#cssEditor h2 {
|
|
53
|
-
margin
|
|
134
|
+
margin: 0;
|
|
54
135
|
color: #2c3e50;
|
|
55
136
|
}
|
|
56
137
|
|
|
@@ -67,30 +148,28 @@
|
|
|
67
148
|
|
|
68
149
|
.editor-info {
|
|
69
150
|
background: #069d57;
|
|
70
|
-
padding:
|
|
71
|
-
|
|
72
|
-
margin-bottom: 15px;
|
|
151
|
+
padding: 8px 20px;
|
|
152
|
+
margin: 0;
|
|
73
153
|
font-size: 14px;
|
|
74
154
|
color: #333aa6;
|
|
75
155
|
}
|
|
76
156
|
|
|
77
|
-
/* 隐藏原始 textarea */
|
|
78
157
|
#cssContent {
|
|
79
158
|
display: none;
|
|
80
159
|
}
|
|
81
160
|
|
|
82
|
-
/* CodeMirror
|
|
161
|
+
/* CodeMirror 基础样式 */
|
|
83
162
|
#cssEditor .CodeMirror {
|
|
84
163
|
flex: 1;
|
|
85
164
|
width: 100%;
|
|
86
165
|
height: auto;
|
|
87
166
|
min-height: 200px;
|
|
88
|
-
border:
|
|
89
|
-
border-radius:
|
|
167
|
+
border: none;
|
|
168
|
+
border-radius: 0;
|
|
90
169
|
font-family: 'Courier New', monospace;
|
|
91
170
|
font-size: 14px;
|
|
92
171
|
line-height: 1.6;
|
|
93
|
-
margin-bottom:
|
|
172
|
+
margin-bottom: 0;
|
|
94
173
|
background: #282a36;
|
|
95
174
|
}
|
|
96
175
|
|
|
@@ -103,12 +182,11 @@
|
|
|
103
182
|
color: #6272a4;
|
|
104
183
|
}
|
|
105
184
|
|
|
106
|
-
/* 注释颜色 */
|
|
107
185
|
.CodeMirror .cm-comment {
|
|
108
186
|
color: #3b9e3b !important;
|
|
109
187
|
}
|
|
110
188
|
|
|
111
|
-
/*
|
|
189
|
+
/* 颜色部件样式 */
|
|
112
190
|
.cm-color-widget {
|
|
113
191
|
display: inline-flex;
|
|
114
192
|
align-items: center;
|
|
@@ -139,7 +217,7 @@
|
|
|
139
217
|
}
|
|
140
218
|
|
|
141
219
|
.cm-color-text {
|
|
142
|
-
color:
|
|
220
|
+
color: var(--text-color);
|
|
143
221
|
background: rgba(0, 0, 0, 0.2);
|
|
144
222
|
padding: 0 4px;
|
|
145
223
|
border-radius: 2px;
|
|
@@ -150,35 +228,17 @@
|
|
|
150
228
|
cursor: text;
|
|
151
229
|
}
|
|
152
230
|
|
|
153
|
-
/*
|
|
154
|
-
|
|
231
|
+
/* 按钮区域 */
|
|
155
232
|
#cssEditor .actions {
|
|
156
233
|
display: flex;
|
|
157
234
|
justify-content: flex-end;
|
|
158
235
|
gap: 10px;
|
|
159
|
-
|
|
236
|
+
padding: 15px 20px;
|
|
237
|
+
border-top: 1px solid rgba(255, 255, 255, 0.1);
|
|
160
238
|
}
|
|
161
239
|
|
|
162
|
-
#
|
|
163
|
-
background: #
|
|
164
|
-
color: var(--text-color);
|
|
165
|
-
padding: 12px 24px;
|
|
166
|
-
border: none;
|
|
167
|
-
border-radius: 5px;
|
|
168
|
-
cursor: pointer;
|
|
169
|
-
font-size: 16px;
|
|
170
|
-
transition: all 0.3s;
|
|
171
|
-
min-width: 120px;
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
#previewBtn:hover {
|
|
175
|
-
background-color: #2980b9;
|
|
176
|
-
transform: translateY(-2px);
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
#cancelPreviewBtn {
|
|
180
|
-
display: none;
|
|
181
|
-
background: #d94bec;
|
|
240
|
+
#saveBtn {
|
|
241
|
+
background: #0ca55e;
|
|
182
242
|
color: var(--text-color);
|
|
183
243
|
padding: 12px 24px;
|
|
184
244
|
border: none;
|
|
@@ -189,23 +249,11 @@
|
|
|
189
249
|
min-width: 120px;
|
|
190
250
|
}
|
|
191
251
|
|
|
192
|
-
#
|
|
193
|
-
background-color: #
|
|
252
|
+
#saveBtn:hover {
|
|
253
|
+
background-color: #17723d;
|
|
194
254
|
transform: translateY(-2px);
|
|
195
255
|
}
|
|
196
256
|
|
|
197
|
-
#saveBtn {
|
|
198
|
-
background: #0ca55e;
|
|
199
|
-
color: var(--text-color);
|
|
200
|
-
padding: 12px 24px;
|
|
201
|
-
border: none;
|
|
202
|
-
border-radius: 5px;
|
|
203
|
-
cursor: pointer;
|
|
204
|
-
font-size: 16px;
|
|
205
|
-
transition: all 0.3s;
|
|
206
|
-
min-width: 120px;
|
|
207
|
-
}
|
|
208
|
-
|
|
209
257
|
#cancelBtn {
|
|
210
258
|
background: #bdb330;
|
|
211
259
|
color: var(--text-color);
|
|
@@ -218,53 +266,52 @@
|
|
|
218
266
|
min-width: 120px;
|
|
219
267
|
}
|
|
220
268
|
|
|
221
|
-
#saveBtn:hover {
|
|
222
|
-
background-color: #17723d;
|
|
223
|
-
transform: translateY(-2px);
|
|
224
|
-
}
|
|
225
|
-
|
|
226
269
|
#cancelBtn:hover {
|
|
227
270
|
background-color: #566214;
|
|
228
271
|
transform: translateY(-2px);
|
|
229
272
|
}
|
|
230
273
|
|
|
274
|
+
/* 编辑器附加强制左对齐 */
|
|
275
|
+
#cssEditor .CodeMirror,
|
|
276
|
+
#cssEditor .CodeMirror *,
|
|
277
|
+
.CodeMirror pre,
|
|
278
|
+
.CodeMirror-lines,
|
|
279
|
+
.CodeMirror-line {
|
|
280
|
+
text-align: left !important;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/* ========== 预览容器 #preview ========== */
|
|
231
284
|
#preview {
|
|
232
285
|
display: none;
|
|
233
|
-
|
|
234
|
-
width: 800px;
|
|
235
|
-
height: 600px;
|
|
236
|
-
top: 40px;
|
|
237
|
-
left: 20px;
|
|
286
|
+
flex-direction: column;
|
|
238
287
|
background: #181f3a;
|
|
239
|
-
border-radius:
|
|
240
|
-
box-shadow: 0 5px 25px rgba(0, 0, 0, 0.5);
|
|
241
|
-
z-index: 1001;
|
|
288
|
+
border-radius: 0;
|
|
242
289
|
overflow: hidden;
|
|
243
|
-
transition: opacity 0.3s ease;
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
#preview::before {
|
|
247
|
-
content: '';
|
|
248
|
-
position: absolute;
|
|
249
|
-
top: 0;
|
|
250
|
-
left: 0;
|
|
251
290
|
width: 100%;
|
|
252
291
|
height: 100%;
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
292
|
+
position: relative;
|
|
293
|
+
box-shadow: none;
|
|
294
|
+
z-index: auto;
|
|
256
295
|
}
|
|
257
296
|
|
|
258
|
-
|
|
259
|
-
|
|
297
|
+
.preview-header {
|
|
298
|
+
padding: 8px 16px;
|
|
299
|
+
background: #2c2e3e;
|
|
300
|
+
color: #8be9fd;
|
|
301
|
+
font-size: 13px;
|
|
302
|
+
border-bottom: 1px solid #444;
|
|
303
|
+
display: flex;
|
|
304
|
+
justify-content: space-between;
|
|
260
305
|
}
|
|
261
306
|
|
|
262
307
|
#previewFrame {
|
|
263
308
|
width: 100%;
|
|
264
309
|
height: 100%;
|
|
265
310
|
border: none;
|
|
311
|
+
flex: 1;
|
|
266
312
|
}
|
|
267
313
|
|
|
314
|
+
/* ========== 加载指示器 ========== */
|
|
268
315
|
#loader {
|
|
269
316
|
display: none;
|
|
270
317
|
}
|
|
@@ -283,22 +330,8 @@
|
|
|
283
330
|
text-align: center;
|
|
284
331
|
}
|
|
285
332
|
|
|
286
|
-
|
|
287
|
-
0% {
|
|
288
|
-
transform: rotate(0deg);
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
100% {
|
|
292
|
-
transform: rotate(360deg);
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
|
|
333
|
+
/* ========== 响应式布局 ========== */
|
|
296
334
|
@media (max-width: 768px) {
|
|
297
|
-
#cssEditor {
|
|
298
|
-
width: 95%;
|
|
299
|
-
height: 100%;
|
|
300
|
-
}
|
|
301
|
-
|
|
302
335
|
#cssEditor .actions {
|
|
303
336
|
flex-wrap: wrap;
|
|
304
337
|
}
|
|
@@ -307,7 +340,6 @@
|
|
|
307
340
|
font-size: 18px;
|
|
308
341
|
}
|
|
309
342
|
|
|
310
|
-
#previewBtn,
|
|
311
343
|
#cancelPreviewBtn,
|
|
312
344
|
#saveBtn,
|
|
313
345
|
#cancelBtn {
|
package/static/script.js
CHANGED
|
@@ -2,20 +2,25 @@
|
|
|
2
2
|
function scriptFun() {
|
|
3
3
|
const modal = document.querySelector('.modal'), cssEditor = document.getElementById('cssEditor'),
|
|
4
4
|
closeBtn = document.querySelector('.close-btn'), saveBtn = document.getElementById('saveBtn'),
|
|
5
|
-
cancelBtn = document.getElementById('cancelBtn'), loader = document.getElementById('loader'),
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
urlParams = new URLSearchParams(window.location.search),
|
|
9
|
-
|
|
5
|
+
cancelBtn = document.getElementById('cancelBtn'), loader = document.getElementById('loader'),
|
|
6
|
+
preview = document.getElementById('preview'), previewFrame = document.getElementById('previewFrame'),
|
|
7
|
+
cancelPreviewBtn = document.getElementById('cancelPreviewBtn'),
|
|
8
|
+
urlParams = new URLSearchParams(window.location.search),
|
|
9
|
+
fileDir = urlParams.get('fileDir'), returnUrl = urlParams.get('return');
|
|
10
|
+
|
|
11
|
+
let cm = window.cssEditor;
|
|
12
|
+
if (!cm) {
|
|
13
|
+
console.error('CodeMirror not ready, retry');
|
|
14
|
+
setTimeout(scriptFun, 100);
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
10
17
|
|
|
11
18
|
let isEditingColor = false, editColorRange = null, isPreviewMode = false, globalColorPicker = null;
|
|
12
19
|
|
|
13
|
-
// ----- 阻止 CodeMirror 编辑器区域事件冒泡,避免触发父容器拖拽和阻止默认菜单 -----
|
|
14
20
|
cm.getWrapperElement().addEventListener('mousedown', e => e.stopPropagation());
|
|
15
21
|
cm.getWrapperElement().addEventListener('touchstart', e => e.stopPropagation(), { passive: true });
|
|
16
22
|
cm.getWrapperElement().addEventListener('contextmenu', e => e.stopPropagation());
|
|
17
23
|
|
|
18
|
-
// ---------- 🎨 全局颜色选择器 ----------
|
|
19
24
|
function initGlobalColorPicker() {
|
|
20
25
|
if (globalColorPicker) return;
|
|
21
26
|
globalColorPicker = document.createElement('input');
|
|
@@ -32,51 +37,46 @@ function scriptFun() {
|
|
|
32
37
|
}
|
|
33
38
|
initGlobalColorPicker();
|
|
34
39
|
|
|
35
|
-
//
|
|
40
|
+
// 颜色关键字映射(完整)
|
|
36
41
|
const COLOR_KEYWORDS = {
|
|
37
42
|
'aliceblue': '#F0F8FF', 'antiquewhite': '#FAEBD7', 'aqua': '#00FFFF', 'aquamarine': '#7FFFD4', 'azure': '#F0FFFF',
|
|
38
43
|
'beige': '#F5F5DC', 'bisque': '#FFE4C4', 'black': '#000000', 'blanchedalmond': '#FFEBCD', 'blue': '#0000FF',
|
|
39
44
|
'blueviolet': '#8A2BE2', 'brown': '#A52A2A', 'burlywood': '#DEB887', 'cadetblue': '#5F9EA0', 'chartreuse': '#7FFF00',
|
|
40
45
|
'chocolate': '#D2691E', 'coral': '#FF7F50', 'cornflowerblue': '#6495ED', 'cornsilk': '#FFF8DC', 'crimson': '#DC143C',
|
|
41
46
|
'cyan': '#00FFFF', 'darkblue': '#00008B', 'darkcyan': '#008B8B', 'darkgoldenrod': '#B8860B', 'darkgray': '#A9A9A9',
|
|
42
|
-
'darkgreen': '#006400', 'darkgrey': '#A9A9A9', 'darkkhaki': '#BDB76B', '
|
|
43
|
-
'
|
|
44
|
-
'darkorchid': '#9932CC', 'darkolivegreen': '#556B2F', 'darksalmon': '#E9967A', 'darkseagreen': '#8FBC8F',
|
|
47
|
+
'darkgreen': '#006400', 'darkgrey': '#A9A9A9', 'darkkhaki': '#BDB76B', 'darkmagenta': '#8B008B', 'darkolivegreen': '#556B2F',
|
|
48
|
+
'darkorange': '#FF8C00', 'darkorchid': '#9932CC', 'darkred': '#8B0000', 'darksalmon': '#E9967A', 'darkseagreen': '#8FBC8F',
|
|
45
49
|
'darkslateblue': '#483D8B', 'darkslategray': '#2F4F4F', 'darkslategrey': '#2F4F4F', 'darkturquoise': '#00CED1',
|
|
46
|
-
'darkviolet': '#9400D3', 'deepskyblue': '#00BFFF', '
|
|
47
|
-
'
|
|
48
|
-
'
|
|
49
|
-
'
|
|
50
|
-
'
|
|
51
|
-
'
|
|
52
|
-
'lightgoldenrodyellow': '#FAFAD2', 'lightgray': '#D3D3D3', 'lightgreen': '#90EE90', 'lightgrey': '#D3D3D3',
|
|
53
|
-
'lightpink': '#FFB6C1', 'lightsalmon': '#FFA07A', 'lightseagreen': '#20B2AA', 'lightskyblue': '#87CEFA',
|
|
54
|
-
'
|
|
55
|
-
'
|
|
56
|
-
'
|
|
57
|
-
'
|
|
58
|
-
'
|
|
59
|
-
'
|
|
60
|
-
'
|
|
61
|
-
'
|
|
62
|
-
'
|
|
63
|
-
'
|
|
64
|
-
'saddlebrown': '#8B4513', 'salmon': '#FA8072', 'sandybrown': '#F4A460', 'seagreen': '#2E8B57', 'snow': '#FFFAFA',
|
|
50
|
+
'darkviolet': '#9400D3', 'deeppink': '#FF1493', 'deepskyblue': '#00BFFF', 'dimgray': '#696969', 'dimgrey': '#696969',
|
|
51
|
+
'dodgerblue': '#1E90FF', 'firebrick': '#B22222', 'floralwhite': '#FFFAF0', 'forestgreen': '#228B22', 'fuchsia': '#FF00FF',
|
|
52
|
+
'gainsboro': '#DCDCDC', 'ghostwhite': '#F8F8FF', 'gold': '#FFD700', 'goldenrod': '#DAA520', 'gray': '#808080',
|
|
53
|
+
'green': '#008000', 'greenyellow': '#ADFF2F', 'grey': '#808080', 'honeydew': '#F0FFF0', 'hotpink': '#FF69B4',
|
|
54
|
+
'indianred': '#CD5C5C', 'indigo': '#4B0082', 'ivory': '#FFFFF0', 'khaki': '#F0E68C', 'lavender': '#E6E6FA',
|
|
55
|
+
'lavenderblush': '#FFF0F5', 'lawngreen': '#7CFC00', 'lemonchiffon': '#FFFACD', 'lightblue': '#ADD8E6', 'lightcoral': '#F08080',
|
|
56
|
+
'lightcyan': '#E0FFFF', 'lightgoldenrodyellow': '#FAFAD2', 'lightgray': '#D3D3D3', 'lightgreen': '#90EE90', 'lightgrey': '#D3D3D3',
|
|
57
|
+
'lightpink': '#FFB6C1', 'lightsalmon': '#FFA07A', 'lightseagreen': '#20B2AA', 'lightskyblue': '#87CEFA', 'lightslategray': '#778899',
|
|
58
|
+
'lightslategrey': '#778899', 'lightsteelblue': '#B0C4DE', 'lightyellow': '#FFFFE0', 'lime': '#00FF00', 'limegreen': '#32CD32',
|
|
59
|
+
'linen': '#FAF0E6', 'magenta': '#FF00FF', 'maroon': '#800000', 'mediumaquamarine': '#66CDAA', 'mediumblue': '#0000CD',
|
|
60
|
+
'mediumorchid': '#BA55D3', 'mediumpurple': '#9370DB', 'mediumseagreen': '#3CB371', 'mediumslateblue': '#7B68EE',
|
|
61
|
+
'mediumspringgreen': '#00FA9A', 'mediumturquoise': '#48D1CC', 'mediumvioletred': '#C71585', 'midnightblue': '#191970',
|
|
62
|
+
'mintcream': '#F5FFFA', 'mistyrose': '#FFE4E1', 'moccasin': '#FFE4B5', 'navajowhite': '#FFDEAD', 'navy': '#000080',
|
|
63
|
+
'oldlace': '#FDF5E6', 'olive': '#808000', 'olivedrab': '#6B8E23', 'orange': '#FFA500', 'orangered': '#FF4500',
|
|
64
|
+
'orchid': '#DA70D6', 'palegoldenrod': '#EEE8AA', 'palegreen': '#98FB98', 'paleturquoise': '#AFEEEE', 'palevioletred': '#DB7093',
|
|
65
|
+
'papayawhip': '#FFEFD5', 'peachpuff': '#FFDAB9', 'peru': '#CD853F', 'pink': '#FFC0CB', 'plum': '#DDA0DD',
|
|
66
|
+
'powderblue': '#B0E0E6', 'purple': '#800080', 'rebeccapurple': '#663399', 'red': '#FF0000', 'rosybrown': '#BC8F8F',
|
|
67
|
+
'royalblue': '#4169E1', 'saddlebrown': '#8B4513', 'salmon': '#FA8072', 'sandybrown': '#F4A460', 'seagreen': '#2E8B57',
|
|
65
68
|
'seashell': '#FFF5EE', 'sienna': '#A0522D', 'silver': '#C0C0C0', 'skyblue': '#87CEEB', 'slateblue': '#6A5ACD',
|
|
66
|
-
'slategray': '#708090', 'slategrey': '#708090', '
|
|
67
|
-
'
|
|
68
|
-
'
|
|
69
|
-
'transparent': 'transparent'
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
const KEYWORDS = Object.keys(COLOR_KEYWORDS).concat('transparent').join('|'),
|
|
74
|
-
HEX = '#(?:[0-9a-fA-F]{3,4}){1,2}\\b', RGB = 'rgba?\\(\\s*\\d+\\s*,\\s*\\d+\\s*,\\s*\\d+\\s*(?:,\\s*[\\d.]+\\s*)?\\)',
|
|
69
|
+
'slategray': '#708090', 'slategrey': '#708090', 'snow': '#FFFAFA', 'springgreen': '#00FF7F', 'steelblue': '#4682B4',
|
|
70
|
+
'tan': '#D2B48C', 'teal': '#008080', 'thistle': '#D8BFD8', 'tomato': '#FF6347', 'turquoise': '#40E0D0',
|
|
71
|
+
'violet': '#EE82EE', 'wheat': '#F5DEB3', 'white': '#FFFFFF', 'whitesmoke': '#F5F5F5', 'yellow': '#FFFF00',
|
|
72
|
+
'yellowgreen': '#9ACD32', 'transparent': 'transparent'
|
|
73
|
+
},
|
|
74
|
+
KEYWORDS = Object.keys(COLOR_KEYWORDS).join('|'), HEX = '#(?:[0-9a-fA-F]{3,4}){1,2}\\b',
|
|
75
|
+
RGB = 'rgba?\\(\\s*\\d+\\s*,\\s*\\d+\\s*,\\s*\\d+\\s*(?:,\\s*[\\d.]+\\s*)?\\)',
|
|
75
76
|
HSL = 'hsla?\\(\\s*\\d+\\s*,\\s*\\d+%\\s*,\\s*\\d+%\\s*(?:,\\s*[\\d.]+\\s*)?\\)',
|
|
76
77
|
COLOR_REGEX = new RegExp(`${HEX}|${RGB}|${HSL}|\\b(${KEYWORDS})\\b`, 'gi'),
|
|
77
78
|
RGB_EXTRACT = /rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*([\d.]+))?\)/i;
|
|
78
79
|
|
|
79
|
-
// ---------- 颜色工具函数 ----------
|
|
80
80
|
function rgbToHex(r, g, b) {
|
|
81
81
|
return '#' + [r, g, b].map(x => {
|
|
82
82
|
const h = parseInt(x).toString(16);
|
|
@@ -87,40 +87,40 @@ function scriptFun() {
|
|
|
87
87
|
function extractColorAndAlpha(c) {
|
|
88
88
|
const l = c.toLowerCase(), kw = COLOR_KEYWORDS[l];
|
|
89
89
|
let r = 0, g = 0, b = 0, a = 1, t = 'keyword';
|
|
90
|
-
|
|
91
|
-
// 1. 颜色关键字(含 transparent)
|
|
92
90
|
if (kw) {
|
|
93
91
|
if (l === 'transparent') return { r: 0, g: 0, b: 0, a: 0, t, o: c, tr: true };
|
|
94
|
-
const hex = kw.slice(1);
|
|
95
|
-
[r, g, b] = hex.match(/.{2}/g).map(v => parseInt(v, 16))
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
else if (c.startsWith('#')) {
|
|
92
|
+
const hex = kw.slice(1);
|
|
93
|
+
[r, g, b] = hex.match(/.{2}/g).map(v => parseInt(v, 16));
|
|
94
|
+
a = 1;
|
|
95
|
+
} else if (c.startsWith('#')) {
|
|
99
96
|
t = 'hex';
|
|
100
97
|
let h = c.slice(1).toLowerCase();
|
|
101
98
|
if (h.length === 3 || h.length === 4) h = h.split('').map(x => x + x).join('');
|
|
102
99
|
const hexMatch = h.match(/^([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})?$/);
|
|
103
|
-
if (hexMatch)
|
|
104
|
-
[r, g, b] = hexMatch.slice(1, 4).map(v => parseInt(v, 16))
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
else if (c.startsWith('rgb')) {
|
|
100
|
+
if (hexMatch) {
|
|
101
|
+
[r, g, b] = hexMatch.slice(1, 4).map(v => parseInt(v, 16));
|
|
102
|
+
a = hexMatch[4] ? parseInt(hexMatch[4], 16) / 255 : 1;
|
|
103
|
+
}
|
|
104
|
+
} else if (c.startsWith('rgb')) {
|
|
108
105
|
const m = RGB_EXTRACT.exec(c);
|
|
109
|
-
if (m)
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
106
|
+
if (m) {
|
|
107
|
+
t = 'rgb';
|
|
108
|
+
[r, g, b] = [m[1], m[2], m[3]].map(Number);
|
|
109
|
+
a = m[4] ? parseFloat(m[4]) : 1;
|
|
110
|
+
}
|
|
111
|
+
} else if (c.startsWith('hsl')) {
|
|
113
112
|
t = 'hsl';
|
|
114
113
|
const d = document.createElement('div');
|
|
115
114
|
d.style.color = c, document.body.append(d);
|
|
116
|
-
const computed = window.getComputedStyle(d).color;
|
|
115
|
+
const computed = window.getComputedStyle(d).color;
|
|
117
116
|
d.remove();
|
|
118
|
-
|
|
119
117
|
const m = RGB_EXTRACT.exec(computed);
|
|
120
|
-
if (m)
|
|
121
|
-
|
|
118
|
+
if (m) {
|
|
119
|
+
[r, g, b] = [m[1], m[2], m[3]].map(Number);
|
|
120
|
+
a = m[4] ? parseFloat(m[4]) : 1;
|
|
121
|
+
}
|
|
122
|
+
else r = 128; g = 128; b = 128; a = 1;
|
|
122
123
|
}
|
|
123
|
-
|
|
124
124
|
return { r, g, b, a, t, o: c };
|
|
125
125
|
}
|
|
126
126
|
|
|
@@ -128,134 +128,162 @@ function scriptFun() {
|
|
|
128
128
|
const o = extractColorAndAlpha(c);
|
|
129
129
|
if (o.tr || c.toLowerCase() === 'transparent') return 'transparent';
|
|
130
130
|
if (o.a < 1) return `rgba(${o.r},${o.g},${o.b},${o.a})`;
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
131
|
+
if (c.startsWith('#')) return c;
|
|
132
|
+
if (c.startsWith('rgb')) return `rgb(${o.r},${o.g},${o.b})`;
|
|
133
|
+
return c;
|
|
134
134
|
}
|
|
135
135
|
|
|
136
|
-
// ---------- 创建颜色部件 ----------
|
|
137
136
|
function createColorWidget(ct, fr, to) {
|
|
138
137
|
const w = document.createElement('span'), s = document.createElement('span'), t = document.createElement('span');
|
|
139
|
-
w.className = 'cm-color-widget'
|
|
140
|
-
|
|
138
|
+
w.className = 'cm-color-widget';
|
|
139
|
+
w.style.display = 'inline-flex';
|
|
140
|
+
w.style.alignItems = 'center';
|
|
141
|
+
w.style.margin = '0 2px';
|
|
142
|
+
w.style.padding = '2px 4px';
|
|
143
|
+
w.style.borderRadius = '3px';
|
|
144
|
+
w.style.backgroundColor = 'rgba(0,0,0,0.1)';
|
|
141
145
|
|
|
142
146
|
const bg = getColorForSwatch(ct);
|
|
143
|
-
s.className = 'cm-color-swatch'
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
s.style.
|
|
147
|
+
s.className = 'cm-color-swatch';
|
|
148
|
+
s.style.backgroundColor = bg;
|
|
149
|
+
s.title = '点击修改颜色';
|
|
150
|
+
s.style.display = 'inline-block';
|
|
151
|
+
s.style.width = '16px';
|
|
152
|
+
s.style.height = '16px';
|
|
153
|
+
s.style.borderRadius = '3px';
|
|
154
|
+
s.style.marginRight = '6px';
|
|
155
|
+
s.style.border = bg === 'transparent' ? '1px dashed #999' : '1px solid rgba(255,255,255,1)';
|
|
156
|
+
s.style.cursor = 'pointer';
|
|
157
|
+
s.style.flexShrink = '0';
|
|
158
|
+
|
|
159
|
+
t.className = 'cm-color-text';
|
|
160
|
+
t.textContent = ct;
|
|
161
|
+
t.style.color = '#f8f8f2';
|
|
162
|
+
t.style.fontSize = '13px';
|
|
163
|
+
t.style.userSelect = 'text';
|
|
164
|
+
t.style.cursor = 'text';
|
|
165
|
+
t.style.marginRight = '8px';
|
|
166
|
+
w.append(s, t);
|
|
147
167
|
|
|
148
|
-
t.className = 'cm-color-text', t.textContent = ct, t.style.color = '#f8f8f2', t.style.fontSize = '13px';
|
|
149
|
-
t.style.userSelect = 'text', t.style.cursor = 'text', t.style.marginRight = '8px', w.append(s, t);
|
|
150
|
-
|
|
151
|
-
// 透明度滑块
|
|
152
168
|
const o = extractColorAndAlpha(ct), as = document.createElement('input'), av = document.createElement('span');
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
cm.replaceRange(nc, fr, to), updateColorWidgets(cm);
|
|
198
|
-
});
|
|
169
|
+
as.type = 'range';
|
|
170
|
+
as.min = 0;
|
|
171
|
+
as.max = 100;
|
|
172
|
+
as.value = Math.round(o.a * 100);
|
|
173
|
+
as.style.width = '120px';
|
|
174
|
+
as.style.marginRight = '8px';
|
|
175
|
+
as.title = '调整透明度 (0-100%)';
|
|
176
|
+
as.className = 'cm-alpha-slider';
|
|
177
|
+
av.className = 'cm-alpha-value';
|
|
178
|
+
av.textContent = `${Math.round(o.a * 100)}%`;
|
|
179
|
+
av.style.fontSize = '12px';
|
|
180
|
+
av.style.color = '#ccc';
|
|
181
|
+
av.style.minWidth = '30px';
|
|
182
|
+
av.style.textAlign = 'center';
|
|
183
|
+
|
|
184
|
+
const originalColor = ct, { a: originalAlpha } = extractColorAndAlpha(originalColor);
|
|
185
|
+
// 根据原始颜色和新透明度生成颜色字符串(仅供预览和生成新颜色)
|
|
186
|
+
function makeColorFromAlpha(alphaPercent) {
|
|
187
|
+
const newAlpha = alphaPercent / 100, lc = originalColor.toLowerCase();
|
|
188
|
+
if (lc === 'transparent') return `rgba(0,0,0,${newAlpha})`;
|
|
189
|
+
if (COLOR_KEYWORDS[lc]) {
|
|
190
|
+
const hex = COLOR_KEYWORDS[lc], r = parseInt(hex.slice(1, 3), 16), g = parseInt(hex.slice(3, 5), 16),
|
|
191
|
+
b = parseInt(hex.slice(5, 7), 16);
|
|
192
|
+
return `rgba(${r},${g},${b},${newAlpha})`;
|
|
193
|
+
}
|
|
194
|
+
if (originalColor.includes('rgba') || originalColor.includes('hsla')) {
|
|
195
|
+
if (originalColor.includes('rgba')) return originalColor.replace(RGB_EXTRACT, `rgba($1,$2,$3,${newAlpha})`);
|
|
196
|
+
else return originalColor.replace(/hsla?\((\d+,\s*\d+%,\s*\d+%)(?:,\s*[\d.]+)?\)/i, `hsla($1,${newAlpha})`);
|
|
197
|
+
}
|
|
198
|
+
if (originalColor.startsWith('rgb(')) return originalColor.replace('rgb(', 'rgba(').replace(')', `,${newAlpha})`);
|
|
199
|
+
if (originalColor.startsWith('hsl(')) return originalColor.replace('hsl(', 'hsla(').replace(')', `,${newAlpha})`);
|
|
200
|
+
if (originalColor.startsWith('#')) {
|
|
201
|
+
let hex = originalColor.slice(1);
|
|
202
|
+
let r, g, b;
|
|
203
|
+
if (hex.length === 3)
|
|
204
|
+
r = parseInt(hex[0] + hex[0], 16), g = parseInt(hex[1] + hex[1], 16), b = parseInt(hex[2] + hex[2], 16);
|
|
205
|
+
else if (hex.length === 6)
|
|
206
|
+
r = parseInt(hex.slice(0, 2), 16), g = parseInt(hex.slice(2, 4), 16), b = parseInt(hex.slice(4, 6), 16);
|
|
207
|
+
else r = g = b = 128;
|
|
208
|
+
|
|
209
|
+
return `rgba(${r},${g},${b},${newAlpha})`;
|
|
210
|
+
}
|
|
211
|
+
return originalColor;
|
|
212
|
+
}
|
|
199
213
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
214
|
+
as.addEventListener('input', function () {
|
|
215
|
+
av.textContent = `${this.value}%`;
|
|
216
|
+
const previewColor = makeColorFromAlpha(this.value);
|
|
217
|
+
s.style.backgroundColor = getColorForSwatch(previewColor);
|
|
218
|
+
});
|
|
203
219
|
|
|
204
|
-
|
|
205
|
-
|
|
220
|
+
as.addEventListener('change', function () {
|
|
221
|
+
const mark = w._colorMark;
|
|
222
|
+
if (!mark) return;
|
|
223
|
+
const pos = mark.find();
|
|
224
|
+
if (!pos) return;
|
|
225
|
+
const currentPercent = Number(this.value);
|
|
226
|
+
let newColor;
|
|
227
|
+
if (Math.abs(currentPercent - originalAlpha * 100) < 0.1) newColor = originalColor;
|
|
228
|
+
else newColor = makeColorFromAlpha(currentPercent);
|
|
229
|
+
|
|
230
|
+
cm.replaceRange(newColor, pos.from, pos.to);
|
|
231
|
+
setTimeout(() => updateColorWidgets(cm), 10);
|
|
232
|
+
});
|
|
233
|
+
as.addEventListener('mousedown', e => e.stopPropagation());
|
|
234
|
+
as.addEventListener('touchstart', e => e.stopPropagation(), { passive: true });
|
|
235
|
+
w.append(as, av);
|
|
206
236
|
|
|
207
|
-
// 色块点击事件
|
|
208
237
|
s.addEventListener('click', e => {
|
|
209
238
|
e.stopPropagation();
|
|
210
239
|
const r = s.getBoundingClientRect(), cc = extractColorAndAlpha(ct), ch = rgbToHex(cc.r, cc.g, cc.b);
|
|
211
|
-
globalColorPicker.style.left = `${r.left}px
|
|
240
|
+
globalColorPicker.style.left = `${r.left}px`;
|
|
241
|
+
globalColorPicker.style.top = `${r.bottom + 5}px`;
|
|
212
242
|
globalColorPicker.value = ch;
|
|
213
|
-
|
|
214
243
|
if (globalColorPicker._ch) globalColorPicker.removeEventListener('change', globalColorPicker._ch);
|
|
215
|
-
const chf =
|
|
244
|
+
const chf = () => {
|
|
216
245
|
const nh = this.value, na = as ? as.value / 100 : cc.a, hr = parseInt(nh.slice(1, 3), 16),
|
|
217
|
-
hg = parseInt(nh.slice(3, 5), 16), hb = parseInt(nh.slice(5, 7), 16)
|
|
246
|
+
hg = parseInt(nh.slice(3, 5), 16), hb = parseInt(nh.slice(5, 7), 16), nhu = nh.toUpperCase(),
|
|
247
|
+
kw = Object.keys(COLOR_KEYWORDS).find(k => COLOR_KEYWORDS[k].toUpperCase() === nhu);
|
|
248
|
+
|
|
218
249
|
let nc = na < 1 ? `rgba(${hr},${hg},${hb},${na})` : nh;
|
|
219
|
-
const nhu = nh.toUpperCase(), kw = Object.keys(COLOR_KEYWORDS).find(k => COLOR_KEYWORDS[k].toUpperCase() === nhu);
|
|
220
250
|
if (kw && na >= 1) nc = kw;
|
|
221
251
|
cm.replaceRange(nc, fr, to), updateColorWidgets(cm), t.textContent = nc;
|
|
222
252
|
s.style.backgroundColor = getColorForSwatch(nc);
|
|
253
|
+
|
|
223
254
|
if (av) av.textContent = `${Math.round(na * 100)}%`;
|
|
224
255
|
to = { line: to.line, ch: fr.ch + nc.length };
|
|
225
|
-
this.removeEventListener('change', chf)
|
|
256
|
+
this.removeEventListener('change', chf);
|
|
257
|
+
globalColorPicker._ch = null;
|
|
226
258
|
};
|
|
227
|
-
globalColorPicker._ch = chf
|
|
259
|
+
globalColorPicker._ch = chf;
|
|
260
|
+
globalColorPicker.addEventListener('change', chf);
|
|
228
261
|
if (typeof globalColorPicker.showPicker === 'function') globalColorPicker.showPicker();
|
|
229
262
|
else globalColorPicker.click();
|
|
230
263
|
});
|
|
231
|
-
|
|
232
264
|
return w;
|
|
233
265
|
}
|
|
234
266
|
|
|
235
|
-
// ---------- 扫描编辑器添加部件 ----------
|
|
236
267
|
function updateColorWidgets(cm) {
|
|
237
|
-
isEditingColor = false, editColorRange = null;
|
|
238
|
-
|
|
268
|
+
isEditingColor = false, editColorRange = null;
|
|
239
269
|
cm.getAllMarks().forEach(m => { if (m.isColorWidget) m.clear(); });
|
|
240
270
|
const d = cm.getDoc(), lc = d.lineCount();
|
|
241
271
|
for (let i = 0; i < lc; i++) {
|
|
242
|
-
const l = d.getLine(i);
|
|
272
|
+
const l = d.getLine(i);
|
|
273
|
+
let m;
|
|
243
274
|
COLOR_REGEX.lastIndex = 0;
|
|
244
275
|
while ((m = COLOR_REGEX.exec(l)) !== null) {
|
|
245
276
|
const s = m.index, e = s + m[0].length, fr = { line: i, ch: s }, to = { line: i, ch: e };
|
|
246
277
|
if (d.findMarksAt(fr).some(x => x.replacedWith)) continue;
|
|
247
|
-
|
|
248
278
|
const w = createColorWidget(m[0], fr, to),
|
|
249
279
|
mark = cm.markText(fr, to, {
|
|
250
280
|
replacedWith: w, inclusiveLeft: false, inclusiveRight: false, clearOnEnter: true
|
|
251
281
|
});
|
|
252
282
|
mark.isColorWidget = true, w._colorMark = mark;
|
|
253
|
-
|
|
254
|
-
// 为颜色值文本添加双击事件
|
|
255
283
|
const textSpan = w.querySelector('.cm-color-text');
|
|
256
284
|
if (textSpan) {
|
|
257
285
|
textSpan.title = '双击编辑颜色值';
|
|
258
|
-
textSpan.addEventListener('dblclick',
|
|
286
|
+
textSpan.addEventListener('dblclick', e => {
|
|
259
287
|
e.stopPropagation();
|
|
260
288
|
const widget = e.target.closest('.cm-color-widget');
|
|
261
289
|
if (!widget) return;
|
|
@@ -263,7 +291,6 @@ function scriptFun() {
|
|
|
263
291
|
if (mark) {
|
|
264
292
|
const pos = mark.find();
|
|
265
293
|
if (pos) {
|
|
266
|
-
// 记录当前正在编辑的颜色值范围
|
|
267
294
|
isEditingColor = true;
|
|
268
295
|
editColorRange = {
|
|
269
296
|
from: { line: pos.from.line, ch: pos.from.ch },
|
|
@@ -279,92 +306,128 @@ function scriptFun() {
|
|
|
279
306
|
}
|
|
280
307
|
|
|
281
308
|
updateColorWidgets(cm);
|
|
282
|
-
|
|
283
|
-
// 编辑期间不自动重建部件,但允许手动触发重建
|
|
284
309
|
cm.on('change', () => {
|
|
285
310
|
if (isEditingColor) return;
|
|
286
311
|
setTimeout(() => updateColorWidgets(cm), 10);
|
|
287
312
|
});
|
|
288
|
-
|
|
289
|
-
// 监听光标活动,检测是否离开正在编辑的颜色值区域
|
|
290
313
|
cm.on('cursorActivity', () => {
|
|
291
314
|
if (isEditingColor && editColorRange) {
|
|
292
315
|
const cursor = cm.getCursor(), range = editColorRange,
|
|
293
|
-
// 检查光标是否离开了正在编辑的颜色值范围
|
|
294
316
|
isOutsideRange = cursor.line < range.from.line || cursor.line > range.to.line ||
|
|
295
317
|
(cursor.line === range.from.line && cursor.ch < range.from.ch) ||
|
|
296
318
|
(cursor.line === range.to.line && cursor.ch > range.to.ch);
|
|
297
|
-
|
|
298
319
|
if (isOutsideRange) isEditingColor = false, editColorRange = null, updateColorWidgets(cm);
|
|
299
320
|
}
|
|
300
321
|
});
|
|
301
322
|
|
|
302
|
-
//
|
|
303
|
-
|
|
304
|
-
isPreviewMode = true, preview.style.display = 'block';
|
|
305
|
-
const er = cssEditor.getBoundingClientRect();
|
|
306
|
-
preview.style.width = `${er.width}px`, preview.style.height = `${er.height}px`;
|
|
307
|
-
previewBtn.style.display = 'none', cancelPreviewBtn.style.display = 'block';
|
|
308
|
-
previewFrame.src = returnUrl, cm.on('change', updatePreviewStyles);
|
|
309
|
-
}
|
|
323
|
+
// ================= 预览控制逻辑(使用静态取消预览按钮) =================
|
|
324
|
+
let styleUpdateHandler = null; // 编辑器 change 监听函数
|
|
310
325
|
|
|
326
|
+
// 更新预览样式
|
|
311
327
|
function updatePreviewStyles() {
|
|
312
328
|
if (!isPreviewMode || !previewFrame.contentWindow?.document) return;
|
|
313
329
|
try {
|
|
314
|
-
const
|
|
315
|
-
|
|
316
|
-
if (
|
|
317
|
-
|
|
318
|
-
|
|
330
|
+
const css = cm.getValue(), doc = previewFrame.contentDocument || previewFrame.contentWindow.document;
|
|
331
|
+
let styleEl = doc.getElementById('dynamic-css');
|
|
332
|
+
if (!styleEl) styleEl = doc.createElement('style'), styleEl.id = 'dynamic-css', doc.head.appendChild(styleEl);
|
|
333
|
+
|
|
334
|
+
styleEl.textContent = css;
|
|
335
|
+
} catch (e) {
|
|
336
|
+
console.log('预览更新失败', e);
|
|
337
|
+
}
|
|
319
338
|
}
|
|
320
339
|
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
340
|
+
// 开启预览模式
|
|
341
|
+
function startPreview() {
|
|
342
|
+
if (isPreviewMode) return;
|
|
343
|
+
isPreviewMode = true, preview.style.display = 'flex';
|
|
344
|
+
|
|
345
|
+
if (cancelPreviewBtn) cancelPreviewBtn.style.display = 'flex';
|
|
346
|
+
if (previewFrame.src !== returnUrl && returnUrl && returnUrl !== 'about:blank') previewFrame.src = returnUrl;
|
|
347
|
+
|
|
348
|
+
// 等待 iframe 加载完成后注入样式
|
|
349
|
+
const applyStyles = () => {
|
|
350
|
+
if (previewFrame.contentDocument) updatePreviewStyles();
|
|
351
|
+
else setTimeout(applyStyles, 50);
|
|
352
|
+
};
|
|
353
|
+
if (previewFrame.contentDocument && previewFrame.contentDocument.readyState === 'complete') updatePreviewStyles();
|
|
354
|
+
else {
|
|
355
|
+
previewFrame.addEventListener('load', function onLoad() {
|
|
356
|
+
previewFrame.removeEventListener('load', onLoad), updatePreviewStyles();
|
|
357
|
+
});
|
|
358
|
+
applyStyles();
|
|
359
|
+
}
|
|
360
|
+
if (styleUpdateHandler) cm.off('change', styleUpdateHandler);
|
|
361
|
+
styleUpdateHandler = () => { if (isPreviewMode) updatePreviewStyles(); };
|
|
362
|
+
cm.on('change', styleUpdateHandler);
|
|
324
363
|
}
|
|
325
364
|
|
|
326
|
-
|
|
365
|
+
// 关闭预览模式
|
|
366
|
+
function stopPreview() {
|
|
367
|
+
if (!isPreviewMode) return;
|
|
368
|
+
isPreviewMode = false, preview.style.display = 'none';
|
|
327
369
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
modal.append(cssEditor, preview), loadCssContent(fileDir);
|
|
370
|
+
if (cancelPreviewBtn) cancelPreviewBtn.style.display = 'none';
|
|
371
|
+
if (styleUpdateHandler) cm.off('change', styleUpdateHandler), styleUpdateHandler = null;
|
|
331
372
|
|
|
332
|
-
|
|
333
|
-
|
|
373
|
+
// 重置布局高亮和布局样式
|
|
374
|
+
const workspace = document.getElementById('workspace'),
|
|
375
|
+
layoutBtns = document.querySelectorAll('.layout-btn:not(#cancelPreviewBtn)');
|
|
334
376
|
|
|
335
|
-
|
|
377
|
+
layoutBtns.forEach(btn => btn.classList.remove('active'));
|
|
378
|
+
if (workspace) workspace.classList.remove('single-preview'), workspace.style.flexDirection = '';
|
|
379
|
+
}
|
|
336
380
|
|
|
337
|
-
//
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
if (
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
381
|
+
// 切换布局(不改变预览状态)
|
|
382
|
+
function setLayout(layout) {
|
|
383
|
+
const workspace = document.getElementById('workspace');
|
|
384
|
+
if (!workspace) return;
|
|
385
|
+
workspace.classList.remove('single-preview');
|
|
386
|
+
workspace.style.flexDirection = '';
|
|
387
|
+
switch (layout) {
|
|
388
|
+
case 'left': workspace.style.flexDirection = 'row-reverse'; break;
|
|
389
|
+
case 'right': workspace.style.flexDirection = 'row'; break;
|
|
390
|
+
case 'top': workspace.style.flexDirection = 'column-reverse'; break;
|
|
391
|
+
case 'bottom': workspace.style.flexDirection = 'column'; break;
|
|
392
|
+
case 'single': workspace.classList.add('single-preview'); break;
|
|
393
|
+
default: break;
|
|
349
394
|
}
|
|
350
|
-
if (
|
|
351
|
-
|
|
395
|
+
if (isPreviewMode && previewFrame.contentWindow) updatePreviewStyles();
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// 为所有布局按钮绑定事件
|
|
399
|
+
const layoutBtns = document.querySelectorAll('.layout-btn:not(#cancelPreviewBtn)');
|
|
400
|
+
layoutBtns.forEach(btn => {
|
|
401
|
+
btn.addEventListener('click', () => {
|
|
402
|
+
const layout = btn.getAttribute('data-layout');
|
|
403
|
+
if (!layout) return;
|
|
404
|
+
layoutBtns.forEach(b => b.classList.remove('active'));
|
|
405
|
+
btn.classList.add('active');
|
|
406
|
+
if (!isPreviewMode) startPreview();
|
|
407
|
+
setLayout(layout);
|
|
408
|
+
});
|
|
352
409
|
});
|
|
410
|
+
if (cancelPreviewBtn) cancelPreviewBtn.addEventListener('click', () => stopPreview());
|
|
353
411
|
|
|
354
|
-
//
|
|
412
|
+
// ================= 样式保存/加载/颜色等功能 =================
|
|
413
|
+
function showLoader() { if (loader) loader.style.display = 'block'; }
|
|
414
|
+
function hideLoader() { if (loader) loader.style.display = 'none'; }
|
|
415
|
+
function updateEditorTitle(fd) {
|
|
416
|
+
const te = document.querySelector('#cssEditor h2');
|
|
417
|
+
if (te) te.textContent = `编辑文件: ${fd}`;
|
|
418
|
+
}
|
|
355
419
|
async function loadCssContent(fd) {
|
|
356
420
|
showLoader();
|
|
357
421
|
try {
|
|
358
422
|
const r = await fetch(`/api/css?fileDir=${encodeURIComponent(fd)}`);
|
|
359
|
-
if (r.ok) {
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
if (te) te.textContent = `编辑文件:${fd}`;
|
|
423
|
+
if (r.ok) {
|
|
424
|
+
const ct = await r.text();
|
|
425
|
+
cm.setValue(ct), updateEditorTitle(fd);
|
|
426
|
+
} else throw new Error(`无法加载CSS文件: ${fd}`);
|
|
427
|
+
} catch (err) {
|
|
428
|
+
alert(`加载CSS失败: ${err.message}`);
|
|
429
|
+
updateEditorTitle(fd);
|
|
430
|
+
} finally { hideLoader() }
|
|
368
431
|
}
|
|
369
432
|
|
|
370
433
|
async function saveCSS() {
|
|
@@ -376,17 +439,52 @@ function scriptFun() {
|
|
|
376
439
|
body: JSON.stringify({ fileDir, content: cm.getValue() })
|
|
377
440
|
});
|
|
378
441
|
if (r.ok) window.location.href = returnUrl;
|
|
379
|
-
else {
|
|
380
|
-
|
|
381
|
-
|
|
442
|
+
else {
|
|
443
|
+
const et = await r.text();
|
|
444
|
+
throw new Error(et || '保存失败');
|
|
445
|
+
}
|
|
446
|
+
} catch (err) {
|
|
447
|
+
alert(`保存CSS失败: ${err.message}`);
|
|
448
|
+
} finally { hideLoader() }
|
|
382
449
|
}
|
|
383
450
|
|
|
384
|
-
function cancelEdit() {
|
|
451
|
+
function cancelEdit() {
|
|
452
|
+
if (confirm('确定要取消编辑吗')) window.location.href = returnUrl;
|
|
453
|
+
}
|
|
385
454
|
|
|
386
|
-
|
|
387
|
-
|
|
455
|
+
addTapSupport(saveBtn, saveCSS);
|
|
456
|
+
addTapSupport(cancelBtn, cancelEdit);
|
|
457
|
+
addTapSupport(closeBtn, cancelEdit);
|
|
458
|
+
|
|
459
|
+
// 键盘快捷键
|
|
460
|
+
document.addEventListener('keydown', e => {
|
|
461
|
+
if (e.key === 'Escape') {
|
|
462
|
+
if (isEditingColor) {
|
|
463
|
+
e.preventDefault(), isEditingColor = false, editColorRange = null, updateColorWidgets(cm);
|
|
464
|
+
return;
|
|
465
|
+
}
|
|
466
|
+
isPreviewMode ? stopPreview() : cancelEdit();
|
|
467
|
+
}
|
|
468
|
+
if (e.ctrlKey && e.key === 's') { e.preventDefault(); saveCSS(); }
|
|
469
|
+
if (e.ctrlKey && e.key === 'p') {
|
|
470
|
+
e.preventDefault();
|
|
471
|
+
if (!isPreviewMode) {
|
|
472
|
+
startPreview(), setLayout('right');
|
|
473
|
+
layoutBtns.forEach(btn => {
|
|
474
|
+
if (btn.getAttribute('data-layout') === 'right') btn.classList.add('active');
|
|
475
|
+
else btn.classList.remove('active');
|
|
476
|
+
});
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
if (fileDir) loadCssContent(fileDir);
|
|
482
|
+
else console.warn('未提供 fileDir 参数');
|
|
388
483
|
}
|
|
389
484
|
|
|
390
|
-
|
|
391
|
-
if (
|
|
392
|
-
else
|
|
485
|
+
function waitForEditor(callback) {
|
|
486
|
+
if (window.cssEditor) callback();
|
|
487
|
+
else setTimeout(() => waitForEditor(callback), 50);
|
|
488
|
+
}
|
|
489
|
+
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', () => waitForEditor(scriptFun));
|
|
490
|
+
else waitForEditor(scriptFun);
|
package/templates/script.html
CHANGED
|
@@ -13,10 +13,22 @@
|
|
|
13
13
|
[~style]
|
|
14
14
|
|
|
15
15
|
[!my] [~my]
|
|
16
|
-
[!header]
|
|
16
|
+
[!header]
|
|
17
|
+
<!-- 布局工具栏 -->
|
|
18
|
+
<div class="layout-toolbar">
|
|
19
|
+
<button data-layout="left" class="layout-btn" type="button">◀ 左边预览</button>
|
|
20
|
+
<button data-layout="right" class="layout-btn" type="button">右边预览 ▶</button>
|
|
21
|
+
<button data-layout="top" class="layout-btn" type="button">▲ 上边预览</button>
|
|
22
|
+
<button data-layout="bottom" class="layout-btn" type="button">下边预览 ▼</button>
|
|
23
|
+
<button data-layout="single" class="layout-btn" type="button">📺 单页预览</button>
|
|
24
|
+
<button id="cancelPreviewBtn" class="layout-btn" type="button">✖ 取消预览</button>
|
|
25
|
+
</div>
|
|
26
|
+
[~header]
|
|
27
|
+
|
|
17
28
|
[!content]
|
|
18
|
-
<!--
|
|
19
|
-
<div class="
|
|
29
|
+
<!-- 主工作区 -->
|
|
30
|
+
<div id="workspace" class="workspace">
|
|
31
|
+
<!-- 编辑器面板 -->
|
|
20
32
|
<div id="cssEditor">
|
|
21
33
|
<div class="header">
|
|
22
34
|
<h2>编辑CSS样式</h2>
|
|
@@ -27,17 +39,20 @@
|
|
|
27
39
|
</h3>
|
|
28
40
|
<textarea id="cssContent" placeholder="CSS内容将在这里显示..." spellcheck="false"></textarea>
|
|
29
41
|
<div class="actions">
|
|
30
|
-
|
|
31
|
-
<button id="cancelPreviewBtn" type="button">取消预览</button>
|
|
42
|
+
<!-- 只保留保存和取消按钮,预览按钮已移除 -->
|
|
32
43
|
<button id="saveBtn" type="button">保存并应用</button>
|
|
33
44
|
<button id="cancelBtn" type="button">取消</button>
|
|
34
45
|
</div>
|
|
35
46
|
</div>
|
|
36
|
-
</div>
|
|
37
47
|
|
|
38
|
-
<!-- 预览容器 -->
|
|
39
|
-
<div id="preview" class="preview-container">
|
|
40
|
-
|
|
48
|
+
<!-- 预览容器 -->
|
|
49
|
+
<div id="preview" class="preview-container">
|
|
50
|
+
<div class="preview-header">
|
|
51
|
+
<span>预览页面</span>
|
|
52
|
+
<span id="previewUrlHint"></span>
|
|
53
|
+
</div>
|
|
54
|
+
<iframe id="previewFrame" title="预览页面"></iframe>
|
|
55
|
+
</div>
|
|
41
56
|
</div>
|
|
42
57
|
|
|
43
58
|
<!-- 加载指示器 -->
|
|
@@ -62,7 +77,7 @@
|
|
|
62
77
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/6.65.7/addon/fold/brace-fold.min.js"></script>
|
|
63
78
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/6.65.7/addon/fold/comment-fold.min.js"></script>
|
|
64
79
|
|
|
65
|
-
<!--
|
|
80
|
+
<!-- 语法检查依赖 -->
|
|
66
81
|
<script src="/static/utils/css-lint.js"></script>
|
|
67
82
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/6.65.7/addon/lint/lint.min.js"></script>
|
|
68
83
|
<script src="https://cdn.jsdelivr.net/npm/css-tree@3.1.0/dist/csstree.min.js"></script>
|
|
@@ -77,9 +92,8 @@
|
|
|
77
92
|
<script src="/static/utils/match-highlighter.js"></script>
|
|
78
93
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/6.65.7/addon/selection/active-line.min.js"></script>
|
|
79
94
|
|
|
80
|
-
<!-- 库插件样式自定义 -->
|
|
81
95
|
<style>
|
|
82
|
-
/*
|
|
96
|
+
/* 折叠按钮增大 */
|
|
83
97
|
#cssEditor .CodeMirror .CodeMirror-foldgutter-open,
|
|
84
98
|
#cssEditor .CodeMirror .CodeMirror-foldgutter-folded,
|
|
85
99
|
#cssEditor .CodeMirror .CodeMirror-foldgutter-open::after,
|
|
@@ -87,17 +101,16 @@
|
|
|
87
101
|
font-size: 25px;
|
|
88
102
|
}
|
|
89
103
|
|
|
90
|
-
/* 增加折叠 gutter 的宽度,避免按钮显示不全 */
|
|
91
104
|
#cssEditor .CodeMirror .CodeMirror-foldgutter {
|
|
92
105
|
width: 26px;
|
|
93
106
|
}
|
|
94
107
|
|
|
95
|
-
/*
|
|
108
|
+
/* ========== CodeMirror 扩展样式(提示、校验等) ========== */
|
|
96
109
|
.CodeMirror-lint-tooltip {
|
|
97
110
|
z-index: 10000;
|
|
98
111
|
background-color: #ffd;
|
|
99
112
|
border: 1px solid #000;
|
|
100
|
-
color:
|
|
113
|
+
color: var(--text-color);
|
|
101
114
|
font-family: monospace;
|
|
102
115
|
font-size: 12px;
|
|
103
116
|
padding: 6px 10px;
|
|
@@ -105,10 +118,8 @@
|
|
|
105
118
|
word-wrap: break-word;
|
|
106
119
|
white-space: pre-wrap;
|
|
107
120
|
line-height: 1.5;
|
|
108
|
-
transition: none;
|
|
109
121
|
}
|
|
110
122
|
|
|
111
|
-
/* 自动补全列表样式 */
|
|
112
123
|
.CodeMirror-hints {
|
|
113
124
|
background-color: #282a36;
|
|
114
125
|
border: 1px solid #6272a4;
|
|
@@ -122,7 +133,7 @@
|
|
|
122
133
|
}
|
|
123
134
|
|
|
124
135
|
.CodeMirror-hint {
|
|
125
|
-
color:
|
|
136
|
+
color: var(--text-color);
|
|
126
137
|
padding: 4px 8px;
|
|
127
138
|
cursor: pointer;
|
|
128
139
|
white-space: pre;
|
|
@@ -139,26 +150,26 @@
|
|
|
139
150
|
color: #f8f8f2;
|
|
140
151
|
}
|
|
141
152
|
|
|
142
|
-
/* 匹配括号高亮 */
|
|
143
153
|
.CodeMirror-matchingbracket {
|
|
144
154
|
background-color: rgba(124, 136, 126, 1) !important;
|
|
145
155
|
font-weight: bold;
|
|
146
156
|
text-decoration: none !important;
|
|
147
157
|
}
|
|
148
158
|
|
|
149
|
-
/* 自定义高亮 */
|
|
150
159
|
.custom-highlight {
|
|
151
160
|
background-color: rgba(255, 255, 0, 0.3) !important;
|
|
152
161
|
}
|
|
162
|
+
|
|
163
|
+
.CodeMirror-linenumber {
|
|
164
|
+
text-align: right !important;
|
|
165
|
+
}
|
|
153
166
|
</style>
|
|
154
167
|
|
|
155
168
|
<script>
|
|
156
|
-
// 等待 DOM 加载完成后初始化编辑器
|
|
157
169
|
(function () {
|
|
158
170
|
function initEditor() {
|
|
159
171
|
const textarea = document.getElementById('cssContent');
|
|
160
172
|
if (!textarea) return;
|
|
161
|
-
|
|
162
173
|
window.cssEditor = CodeMirror.fromTextArea(textarea, {
|
|
163
174
|
lineNumbers: true,
|
|
164
175
|
mode: 'css',
|
|
@@ -169,41 +180,27 @@
|
|
|
169
180
|
matchBrackets: true,
|
|
170
181
|
foldGutter: true,
|
|
171
182
|
gutters: ["CodeMirror-linenumbers", "CodeMirror-foldgutter", "CodeMirror-lint-markers"],
|
|
172
|
-
lint: {
|
|
173
|
-
getAnnotations: window.customCssLint,
|
|
174
|
-
async: true
|
|
175
|
-
},
|
|
183
|
+
lint: { getAnnotations: window.customCssLint, async: true },
|
|
176
184
|
styleActiveLine: true,
|
|
177
|
-
hintOptions: {
|
|
178
|
-
completeSingle: false, // 即使只有一个匹配也显示列表
|
|
179
|
-
closeOnUnfocus: false
|
|
180
|
-
}
|
|
185
|
+
hintOptions: { completeSingle: false, closeOnUnfocus: false }
|
|
181
186
|
});
|
|
182
|
-
|
|
183
187
|
window.cssEditor.setSize('100%', '100%');
|
|
184
|
-
|
|
185
|
-
// ----- 输入时自动弹出补全 -----
|
|
186
188
|
window.cssEditor.on("inputRead", (cm, change) => {
|
|
187
|
-
// 如果输入内容包含冒号,立即触发补全(优先响应)
|
|
188
189
|
if (change.text.join('').includes(':')) {
|
|
189
190
|
clearTimeout(cm.autocompletionTimeout);
|
|
190
191
|
cm.autocompletionTimeout = setTimeout(() => cm.showHint({ completeSingle: false }), 0);
|
|
191
192
|
return;
|
|
192
193
|
}
|
|
193
|
-
// 其他输入(字母、数字、删除等)延迟触发
|
|
194
194
|
clearTimeout(cm.autocompletionTimeout);
|
|
195
195
|
cm.autocompletionTimeout = setTimeout(() => cm.showHint({ completeSingle: false }), 150);
|
|
196
196
|
});
|
|
197
|
-
|
|
198
|
-
attachCustomHighlight(window.cssEditor); // 高亮匹配选中词(自定义)
|
|
197
|
+
attachCustomHighlight(window.cssEditor);
|
|
199
198
|
setTimeout(() => window.cssEditor.performLint(), 100);
|
|
200
199
|
}
|
|
201
|
-
|
|
202
200
|
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', initEditor);
|
|
203
201
|
else initEditor();
|
|
204
202
|
})();
|
|
205
203
|
</script>
|
|
206
204
|
|
|
207
|
-
<!-- 本地文件 -->
|
|
208
205
|
<script src="/static/script.js"></script>
|
|
209
206
|
[~script]
|