大家好呀

Utools插件版本

utools下载地址

如标题所说,我把之前的小软件做成了utools的插件

image-20260309224834069

为什么呢? 因为我觉得—–要是不宣传一波一辈子都没人发现我的仓库(T¬T)/~~

不废话了,先看看我的插件长什么样子吧

Snipaste_2026-03-09_22-19-31 Snipaste_2026-03-09_22-20-12 Snipaste_2026-03-09_22-20-41 Snipaste_2026-03-09_22-20-56

不得不说现在Claude是真的牛逼,完美复刻了我原先的软件还美化了许多

文件设置存储位置为

  • Windows: C:\Users{用户名}\Documents\TyporaSuite\utools-config.json
  • macOS: /Users/{用户名}/Documents/TyporaSuite/utools-config.json
  • Linux: /home/{用户名}/Documents/TyporaSuite/utools-config.json

就是这里

image-20260309232903466

exe版本

这是我原先的软件的样子,这个是我原先的版本,可以看到现在的utools插件基本上都是移植下面这个软件的功能

Snipaste_2026-03-09_22-52-34 image-20260309225323256 image-20260309225356390 image-20260309225522901

文件设置存储位置为 “ Documents\TyporaSuite\TyporaSettings.ini

就是这里

image-20260309232800503

你可以看看我原先制作这个软件的过程

源代码

(本质为Autohotkey v2脚本)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
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
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
#Requires AutoHotkey v2.0
#SingleInstance Force
Persistent
SendMode "Input"

; ==============================================================================
; 全局变量声明 (YAML主程序) - 核心修复:配置路径持久化
; ==============================================================================
; 这里的逻辑修改为:不存放在脚本目录,而是存放在用户的【文档】目录
; 这样无论 exe 被打包在哪里运行,配置都会被保存在固定的位置,不会丢失。
global ConfigDir := A_MyDocuments "\TyporaSuite"
if !DirExist(ConfigDir)
{
try DirCreate(ConfigDir)
}
global IniFile := ConfigDir "\TyporaSettings.ini"

global MainGui := 0, SettingsGui := 0
global FieldControls := Map()
global ListFieldControls := Map()

; 核心受保护属性
global ProtectedFields := ["Title", "Date", "Tags", "Categories", "Cover"]
global FullBgPath := ""

; 设置界面变量
global LBFields := 0, DDLPreColors := 0, EdtHexInput := 0, EdtColorName := 0, ColorPreview := 0
global EdtBgPathDisp := 0, SliOp := 0, SliW := 0, EdtW := 0, SliH := 0, EdtH := 0
global BtnDelColor := 0

; 主界面预设颜色表
global DefaultPresets := Map(
"极简白", "FFFFFF", "夜间黑", "202020", "护眼绿", "C7EDCC", "少女粉", "FFF0F5",
"天空蓝", "E0F7FA", "高级灰", "F5F5F5", "深海蓝", "1A237E", "暗夜紫", "2D1B4E",
"薄荷绿", "E0F2F1", "柠檬黄", "FFFDE7", "日落橙", "FFCCBC", "樱花红", "FFCDD2",
"薰衣草", "E1BEE7", "极客黑", "121212", "深空灰", "37474F", "茶色", "D7CCC8",
"青柠", "F0F4C3", "琥珀", "FFECB3", "紫罗兰", "F3E5F5", "冰川蓝", "B3E5FC"
)
global RuntimeColors := DefaultPresets.Clone()

; =========================
; 全局变量声明 (颜色工具 & 帮助)
; =========================
global CT_Gui := 0 ; 颜色主窗口
global CT_CustomGui := 0 ; 自定义颜色窗口
global MainHelpGui := 0 ; YAML主说明书
global ColorHelpGui := 0 ; 颜色工具说明书
global CT_CustomHex := "FF0000"
global CT_ContextMenu := 0

; 颜色工具的默认 18 色 (中文名 + HEX)
global CT_DefaultColors := [
["焦橙色", "FF8C00"], ["红色", "FF0000"], ["天蓝", "87CEFA"],
["绿松石", "40E0D0"], ["紫红", "C71585"], ["蓝绿色", "008080"],
["金黄色", "FFD700"], ["灰黑色", "696969"], ["亮粉色", "FF1493"],
["亮蓝", "1E90FF"], ["鲜绿", "32CD32"], ["橙红", "FF4500"],
["岩蓝", "6A5ACD"], ["巧克力", "D2691E"], ["深红", "DC143C"],
["海绿", "2E8B57"], ["钢蓝", "4682B4"], ["纯黑", "000000"]
]
; 运行时颜色列表 (从INI加载)
global CT_RuntimeList := []

; 确保初始化配置
InitConfig()
LoadCustomColors()
LoadColorToolData()

; =========================
; 托盘菜单
; =========================
A_TrayMenu.Delete()
A_TrayMenu.Add("显示主窗口", (*) => ShowMainGui())
A_TrayMenu.Add("全局设置", (*) => ShowSettingsGui())
A_TrayMenu.Add("颜色工具", (*) => ShowColorTool())
A_TrayMenu.Add()
A_TrayMenu.Add("重启", (*) => Reload())
A_TrayMenu.Add("退出", (*) => ExitApp())

ShowMainGui()

; =========================
; 快捷键
; =========================
#HotIf WinActive("ahk_exe Typora.exe")
^!i:: ShowMainGui()
^!c:: ShowColorTool()
#HotIf

F4:: ShowMainGui()

; ==============================================================================
; PART 1: YAML 生成器主界面
; ==============================================================================
ShowMainGui()
{
global MainGui, IniFile, FieldControls, ListFieldControls

if IsObject(MainGui)
{
try
{
MainGui.Show()
return
}
catch
{
MainGui := 0
}
}

; === 读取配置 ===
bgColor := IniRead(IniFile, "Appearance", "BgColor", "FFFFFF")
bgPath := IniRead(IniFile, "Appearance", "Background", "")
opacity := IniRead(IniFile, "Appearance", "Opacity", 255)
winW := IniRead(IniFile, "Appearance", "WinWidth", 450)
winH_Config := IniRead(IniFile, "Appearance", "WinHeight", 650)

txtColor := "Black"
; 只要没有背景图,就应用背景色逻辑
if (bgPath == "")
{
txtColor := IsDarkColor(bgColor) ? "White" : "Black"
}

; === 创建窗口 ===
MainGui := Gui("+MinimizeBox -MaximizeBox -Resize", "Typora 小助手(Huyangahuo & Gemini3 pro)")
MainGui.SetFont("s10 c" txtColor, "Microsoft YaHei UI")

if (bgPath != "")
{
MainGui.BackColor := "White" ; 有图时底色设为白,防止边缘杂色
}
else
{
MainGui.BackColor := bgColor ; 无图时使用用户设定的背景色
}

MainGui.MarginX := 20, MainGui.MarginY := 20

; === 背景图 ===
if (bgPath != "" && FileExist(bgPath))
{
try
{
MainGui.Add("Picture", "x0 y0 w" winW " h" winH_Config " +0x4000000", bgPath)
}
}

; === 动态字段构建 ===
fieldListStr := IniRead(IniFile, "Structure", "Fields", "Title|Date|Tags|Categories|Cover")
fields := StrSplit(fieldListStr, "|")
FieldControls := Map()
ListFieldControls := Map()

; --- 布局核心参数 ---
topMargin := 20
sideMargin := 20
labelW := 70
gapX := 10
inputX := sideMargin + labelW + gapX
inputW := winW - inputX - sideMargin

if (inputW < 150)
{
inputW := 150
}

currentY := topMargin + 25

; GroupBox 容器
gbStart := MainGui.Add("GroupBox", "x" sideMargin " y" topMargin " w" (winW - sideMargin * 2) " h500", " 文章属性 ")

gbInnerY := topMargin + 30

for index, fieldName in fields
{
if (fieldName = "")
{
continue
}

; 1. 标签
MainGui.SetFont("s10 w600 c" txtColor)
MainGui.Add("Text", "x" (sideMargin + 10) " y" gbInnerY " w" labelW " Right +BackgroundTrans", fieldName . ":")

MainGui.SetFont("s10 w400 cBlack")

; 2. 输入控件
if (fieldName = "Tags" || fieldName = "Categories")
{
; --- 列表模式 ---
defValRaw := IniRead(IniFile, "DefaultValues", fieldName, "")
defArr := StrSplit(StrReplace(defValRaw, "`n", ","), ",")

; 列表框
lb := MainGui.Add("ListBox", "x" inputX " y" gbInnerY " w" (inputW - 10) " h80", defArr)
ListFieldControls[fieldName] := lb

; 操作行
opY := gbInnerY + 85
smallInputW := inputW - 80

addInput := MainGui.Add("Edit", "x" inputX " y" opY " w" smallInputW, "")
btnAddItem := MainGui.Add("Button", "x+5 yp-1 w30 h26", "+")
btnDelItem := MainGui.Add("Button", "x+5 yp w30 h26", "-")

btnAddItem.OnEvent("Click", AddToList.Bind(lb, addInput))
btnDelItem.OnEvent("Click", DelFromList.Bind(lb))

gbInnerY += 125
}
else
{
; --- 单行模式 ---
defVal := IniRead(IniFile, "DefaultValues", fieldName, "")
if (fieldName = "Date")
{
defVal := FormatTime(A_Now, "yyyy-MM-dd HH:mm:ss")
}

ctl := MainGui.Add("Edit", "x" inputX " y" gbInnerY " w" (inputW - 10) " v" fieldName, defVal)
FieldControls[fieldName] := ctl

gbInnerY += 40
}
}

; 调整 GroupBox 高度
gbHeight := gbInnerY - topMargin + 10
gbStart.Move(, , , gbHeight)

; === 底部按钮区域 ===
btnStartY := topMargin + gbHeight + 20
btnW := (winW - 60) / 2

MainGui.SetFont("s10 c" txtColor)

; 第一排按钮
btnSet := MainGui.Add("Button", "x20 y" btnStartY " w" btnW " h35", "⚙️ 设置")
btnIns := MainGui.Add("Button", "x+20 yp w" btnW " h35 Default", "插入 YAML")

; 第二排按钮
btnStartY += 45
btnHelp := MainGui.Add("Button", "x20 y" btnStartY " w" btnW " h35", "📖 使用说明")
btnColorTool := MainGui.Add("Button", "x+20 yp w" btnW " h35", "🎨 MD字体颜色")

; 计算最终窗口高度
finalWinH := btnStartY + 55

; === 事件绑定 ===
btnSet.OnEvent("Click", (*) => ShowSettingsGui())
btnIns.OnEvent("Click", (*) => DoInsert(fields))
btnHelp.OnEvent("Click", (*) => ShowMainHelpGui())
btnColorTool.OnEvent("Click", (*) => ShowColorTool())

MainGui.OnEvent("Close", (*) => ExitApp())

MainGui.Show("w" winW " h" finalWinH)

if (opacity < 255)
{
try WinSetTransparent(opacity, MainGui.Hwnd)
}
}

; ==============================================================================
; PART 2: 辅助功能 (列表, 颜色判断等)
; ==============================================================================
AddToList(lbObj, editObj, *)
{
txt := Trim(editObj.Value)
if (txt != "")
{
lbObj.Add([txt])
editObj.Value := ""
items := ControlGetItems(lbObj.Hwnd)
if (items.Length > 0)
{
lbObj.Choose(items.Length)
}
}
}

DelFromList(lbObj, *)
{
idx := lbObj.Value
if (idx > 0)
{
lbObj.Delete(idx)
}
}

IsDarkColor(hexColor)
{
ifStr := "0x" . hexColor
if !IsInteger(ifStr)
{
return false
}
r := (ifStr >> 16) & 0xFF
g := (ifStr >> 8) & 0xFF
b := ifStr & 0xFF
return (0.2126 * r + 0.7152 * g + 0.0722 * b) < 128
}

; ==============================================================================
; PART 3: 设置界面
; ==============================================================================
ShowSettingsGui()
{
global SettingsGui, MainGui, IniFile
global LBFields, DDLPreColors, EdtHexInput, EdtColorName, ColorPreview
global EdtBgPathDisp, FullBgPath, SliOp, SliW, EdtW, SliH, EdtH
global RuntimeColors, DefaultPresets, BtnDelColor

if IsObject(SettingsGui)
{
try
{
SettingsGui.Show()
return
}
catch
{
SettingsGui := 0
}
}

if IsObject(MainGui)
{
MainGui.Hide()
}

currBgColor := IniRead(IniFile, "Appearance", "BgColor", "FFFFFF")
currTxtColor := IsDarkColor(currBgColor) ? "White" : "Black"

; 禁止改变大小
SettingsGui := Gui("+AlwaysOnTop -MaximizeBox -Resize", "全局配置")
SettingsGui.SetFont("s9 c" currTxtColor, "Microsoft YaHei UI")
SettingsGui.BackColor := currBgColor

Tabs := SettingsGui.Add("Tab3", "x10 y10 w500 h440", ["属性管理", "外观样式"])

; Tab 1
Tabs.UseTab("属性管理")
SettingsGui.Add("Text", "x30 y50 w300", "属性列表 (上限10个):")
fieldListStr := IniRead(IniFile, "Structure", "Fields", "")
fieldArr := StrSplit(fieldListStr, "|")
SettingsGui.SetFont("cBlack")
LBFields := SettingsGui.Add("ListBox", "x30 y70 w200 h340", fieldArr)
SettingsGui.SetFont("c" currTxtColor)

btnAdd := SettingsGui.Add("Button", "x250 y70 w110 h30", "➕ 新增属性")
btnDel := SettingsGui.Add("Button", "xp y+10 w110 h30", "➖ 删除属性")
btnRen := SettingsGui.Add("Button", "xp y+10 w110 h30", "✏️ 重命名")
btnDef := SettingsGui.Add("Button", "xp y+10 w110 h30", "📝 默认值")
SettingsGui.Add("Text", "xp y+20 w110 h2 0x10")
btnUp := SettingsGui.Add("Button", "xp y+20 w50 h30", "▲")
btnDown := SettingsGui.Add("Button", "x+10 yp w50 h30", "▼")

; Tab 2
Tabs.UseTab("外观样式")
SettingsGui.Add("GroupBox", "x30 y50 w460 h150", "窗口主题颜色")
SettingsGui.Add("Text", "x50 y80", "预设风格:")
colorNames := []
for name, hex in RuntimeColors
{
colorNames.Push(name)
}
SettingsGui.SetFont("cBlack")
DDLPreColors := SettingsGui.Add("DropDownList", "x+10 yp-3 w120 Sort", colorNames)
SettingsGui.SetFont("c" currTxtColor)
BtnDelColor := SettingsGui.Add("Button", "x+10 yp-1 w80 h24 Disabled", "删除此颜色")

SettingsGui.Add("Text", "x50 y+20", "HEX:")
SettingsGui.SetFont("cBlack")
EdtHexInput := SettingsGui.Add("Edit", "x+5 yp-3 w60 Limit6", currBgColor)
SettingsGui.SetFont("c" currTxtColor)
SettingsGui.Add("Text", "x+10 yp+3", "名称:")
SettingsGui.SetFont("cBlack")
EdtColorName := SettingsGui.Add("Edit", "x+5 yp-3 w70 Limit6", "自定义")
SettingsGui.SetFont("c" currTxtColor)
ColorPreview := SettingsGui.Add("Text", "x+10 yp-1 w30 h24 +Border", "")
ColorPreview.Opt("+Background" currBgColor)
btnRefresh := SettingsGui.Add("Button", "x50 y+15 w90 h26", "刷新预览")
btnAddColor := SettingsGui.Add("Button", "x+10 yp w90 h26", "确定添加")

SettingsGui.Add("GroupBox", "x30 y210 w460 h110", "背景与透明度")
SettingsGui.Add("Text", "x50 y240", "背景图片:")

; 核心修复:每次打开设置时,从INI读取背景路径到全局变量
FullBgPath := IniRead(IniFile, "Appearance", "Background", "")
dispPath := ShortenPath(FullBgPath, 35)

SettingsGui.SetFont("cBlack")
EdtBgPathDisp := SettingsGui.Add("Edit", "x+10 yp-3 w270 ReadOnly", dispPath)
SettingsGui.SetFont("c" currTxtColor)
btnBrowse := SettingsGui.Add("Button", "x+5 yp-1 w40 h24", "...")
btnClearBg := SettingsGui.Add("Button", "x+5 yp w40 h24", "清除")
SettingsGui.Add("Text", "x50 y+20", "窗口透明度:")
SliOp := SettingsGui.Add("Slider", "x+10 yp w200 Range50-255 ToolTip", IniRead(IniFile, "Appearance", "Opacity", 255))

SettingsGui.Add("GroupBox", "x30 y330 w460 h70", "窗口尺寸")
currW := IniRead(IniFile, "Appearance", "WinWidth", 450)
currH := IniRead(IniFile, "Appearance", "WinHeight", 650)
SettingsGui.Add("Text", "x50 y355", "宽:")
SliW := SettingsGui.Add("Slider", "x+5 yp w130 Range350-800", currW)
EdtW := SettingsGui.Add("Edit", "x+5 yp-3 w40 Number", currW)
SettingsGui.Add("Text", "x+20 yp+3", "高:")
SliH := SettingsGui.Add("Slider", "x+5 yp w130 Range400-900", currH)
EdtH := SettingsGui.Add("Edit", "x+5 yp-3 w40 Number", currH)
Tabs.UseTab()

btnCancel := SettingsGui.Add("Button", "x20 y460 w150 h40", "❌ 取消")
btnSave := SettingsGui.Add("Button", "x350 yp w150 h40 Default", "✅ 保存并重启")

btnAdd.OnEvent("Click", (*) => CustomInputBox("新增属性", "请输入属性名(英文):", DoAddField))
btnDel.OnEvent("Click", DelField)
btnRen.OnEvent("Click", RenameField)
btnDef.OnEvent("Click", EditDefaultValue)
btnUp.OnEvent("Click", (*) => MoveField(-1))
btnDown.OnEvent("Click", (*) => MoveField(1))
DDLPreColors.OnEvent("Change", SelectPresetColor)
btnRefresh.OnEvent("Click", RefreshColorPreview)
btnAddColor.OnEvent("Click", AddCustomColor)
BtnDelColor.OnEvent("Click", DeleteCustomColor)
btnBrowse.OnEvent("Click", BrowseBg)
btnClearBg.OnEvent("Click", ClearBg)
SliW.OnEvent("Change", (*) => EdtW.Value := SliW.Value)
EdtW.OnEvent("Change", (*) => SliW.Value := EdtW.Value)
SliH.OnEvent("Change", (*) => EdtH.Value := SliH.Value)
EdtH.OnEvent("Change", (*) => SliH.Value := EdtH.Value)
btnSave.OnEvent("Click", SaveAllSettings)
btnCancel.OnEvent("Click", CancelSettings)

SettingsGui.OnEvent("Close", CancelSettings)
SettingsGui.Show("w520 h515")
}

; ==============================================================================
; PART 4: 逻辑处理
; ==============================================================================
LoadCustomColors()
{
global RuntimeColors, IniFile
customSection := IniRead(IniFile, "CustomColors", , "")
Loop Parse, customSection, "`n", "`r"
{
if (A_LoopField = "")
{
continue
}
parts := StrSplit(A_LoopField, "=")
if (parts.Length = 2)
{
RuntimeColors[parts[1]] := parts[2]
}
}
}

SelectPresetColor(*)
{
global DDLPreColors, EdtHexInput, RuntimeColors, BtnDelColor, DefaultPresets, EdtColorName
choice := DDLPreColors.Text
if (choice != "" && RuntimeColors.Has(choice))
{
hex := RuntimeColors[choice]
EdtHexInput.Value := hex
EdtColorName.Value := choice
RefreshColorPreview()
if (DefaultPresets.Has(choice))
{
BtnDelColor.Enabled := false
BtnDelColor.Text := "系统预设"
}
else
{
BtnDelColor.Enabled := true
BtnDelColor.Text := "删除此颜色"
}
}
}

RefreshColorPreview(*)
{
global EdtHexInput, ColorPreview
hex := EdtHexInput.Value
if RegExMatch(hex, "^[0-9A-Fa-f]{6}$")
{
ColorPreview.Opt("+Background" hex)
ColorPreview.Redraw()
}
}

AddCustomColor(*)
{
global EdtHexInput, EdtColorName, RuntimeColors, DDLPreColors, IniFile
hex := EdtHexInput.Value
name := Trim(EdtColorName.Value)
if !RegExMatch(hex, "^[0-9A-Fa-f]{6}$")
{
SafeMsgBox("HEX 代码必须是 6 位颜色代码 (例如 FFFFFF)")
return
}
if (StrLen(name) < 1 || StrLen(name) > 7)
{
SafeMsgBox("颜色名称长度必须在 1 到 7 个字之间。")
return
}
RuntimeColors[name] := hex
IniWrite(hex, IniFile, "CustomColors", name)
UpdateColorDDL(name)
SafeMsgBox("颜色 [" name "] 已保存!")
}

DeleteCustomColor(*)
{
global DDLPreColors, RuntimeColors, IniFile, DefaultPresets
choice := DDLPreColors.Text
if (DefaultPresets.Has(choice))
{
SafeMsgBox("不可删除预设颜色。")
return
}
if (choice != "")
{
RuntimeColors.Delete(choice)
IniDelete(IniFile, "CustomColors", choice)
UpdateColorDDL()
SafeMsgBox("已删除颜色 [" choice "]")
}
}

UpdateColorDDL(selectItem := "")
{
global DDLPreColors, RuntimeColors
items := []
for k, v in RuntimeColors
{
items.Push(k)
}
DDLPreColors.Delete()
DDLPreColors.Add(items)
if (selectItem != "")
{
try
{
DDLPreColors.Choose(selectItem)
}
}
else
{
DDLPreColors.Choose(1)
}
SelectPresetColor()
}

DoAddField(val)
{
global LBFields
items := ControlGetItems(LBFields.Hwnd)
if (items.Length >= 10)
{
SafeMsgBox("上限10个属性。", "提示", "Icon!")
return
}
if (val = "")
{
return
}
val := Trim(val)
for item in items
{
if (item = val)
{
SafeMsgBox("属性已存在")
return
}
}
LBFields.Add([val])
}

RenameField(*)
{
global LBFields, IniFile, ProtectedFields
if (!LBFields.Value)
{
SafeMsgBox("请先选中属性")
return
}
oldName := LBFields.Text
if HasValue(ProtectedFields, oldName)
{
SafeMsgBox("核心属性禁止重命名。", "禁止", "Icon!")
return
}
CustomInputBox("重命名", "重命名 [" oldName "] 为:", DoRename, oldName)
}

DoRename(newName)
{
global LBFields, IniFile
if (newName = "")
{
return
}
idx := LBFields.Value
oldName := LBFields.Text
LBFields.Delete(idx)
LBFields.Insert(idx, [newName])
LBFields.Choose(idx)
oldDef := IniRead(IniFile, "DefaultValues", oldName, "")
if (oldDef != "")
{
IniWrite(oldDef, IniFile, "DefaultValues", newName)
IniDelete(IniFile, "DefaultValues", oldName)
}
}

DelField(*)
{
global LBFields, ProtectedFields
if (!LBFields.Value)
{
return
}
fName := LBFields.Text
if HasValue(ProtectedFields, fName)
{
SafeMsgBox("核心属性禁止删除。", "禁止", "Icon!")
return
}
LBFields.Delete(LBFields.Value)
}

ShortenPath(path, maxLen)
{
if (StrLen(path) <= maxLen)
{
return path
}
SplitPath(path, &name, &dir)
if (StrLen(name) >= maxLen)
{
return SubStr(name, 1, maxLen - 3) "..."
}
drive := SubStr(path, 1, 3)
remain := maxLen - StrLen(drive) - StrLen(name) - 4
if (remain < 1)
{
return drive "..." name
}
return drive "..." SubStr(dir, -remain) "\" name
}

BrowseBg(*)
{
global EdtBgPathDisp, FullBgPath
s := FileSelect(3, , "选择背景图片", "Images (*.jpg; *.png)")
if s
{
FullBgPath := s
EdtBgPathDisp.Value := ShortenPath(s, 35)
}
}

ClearBg(*)
{
global EdtBgPathDisp, FullBgPath
FullBgPath := ""
EdtBgPathDisp.Value := ""
}

HasValue(arr, val)
{
for index, value in arr
{
if (value = val)
{
return true
}
}
return false
}

SafeMsgBox(text, title := "助手", options := "")
{
global SettingsGui
ownOpt := ""
if IsObject(SettingsGui)
{
ownOpt := "Owner" SettingsGui.Hwnd
}
; 262144 = MB_TOPMOST (强制置顶)
MsgBox(text, title, options " " ownOpt " 262144")
}

; === 核心修复: 智能判断父窗口的输入框 ===
CustomInputBox(title, prompt, callback, defaultVal := "")
{
global SettingsGui, CT_CustomGui, MainGui

ownerOpt := ""

; 优先级: 设置窗口 > 颜色自定义窗口 > 主窗口
if (IsObject(SettingsGui) && SettingsGui != 0)
{
ownerOpt := "+Owner" SettingsGui.Hwnd
}
else if (IsObject(CT_CustomGui) && CT_CustomGui != 0)
{
ownerOpt := "+Owner" CT_CustomGui.Hwnd
}
else if (IsObject(MainGui) && MainGui != 0)
{
ownerOpt := "+Owner" MainGui.Hwnd
}

; 无论父窗口是谁,都加上置顶属性
InputGui := Gui(ownerOpt " +AlwaysOnTop -MaximizeBox -Resize", title)
InputGui.SetFont("s9", "Microsoft YaHei UI")
InputGui.Add("Text", "xm w280", prompt)
edt := InputGui.Add("Edit", "xm y+10 w280", defaultVal)
btnOk := InputGui.Add("Button", "xm y+10 w80 Default", "确定")
btnCancel := InputGui.Add("Button", "x+10 yp w80", "取消")
btnOk.OnEvent("Click", (*) => (callback(edt.Value), InputGui.Destroy()))
btnCancel.OnEvent("Click", (*) => InputGui.Destroy())
InputGui.Show()
}

MoveField(offset)
{
global LBFields
idx := LBFields.Value
if (idx = 0)
{
return
}
items := ControlGetItems(LBFields.Hwnd)
count := items.Length
newIdx := idx + offset
if (newIdx < 1 || newIdx > count)
{
return
}
temp := items[idx]
items[idx] := items[newIdx]
items[newIdx] := temp
LBFields.Delete()
LBFields.Add(items)
LBFields.Choose(newIdx)
}

EditDefaultValue(*)
{
global LBFields, IniFile
if (!LBFields.Value)
{
SafeMsgBox("请先选中属性")
return
}
fName := LBFields.Text
currDef := IniRead(IniFile, "DefaultValues", fName, "")
CustomInputBox("默认值", "编辑 [" fName "] 默认值:", FinishEditDefault, currDef)
}

FinishEditDefault(val)
{
global LBFields, IniFile
fName := LBFields.Text
IniWrite(val, IniFile, "DefaultValues", fName)
}

CancelSettings(*)
{
global SettingsGui, MainGui
SettingsGui.Destroy()
SettingsGui := 0
if IsObject(MainGui)
{
if (MainGui != 0)
MainGui.Destroy()
MainGui := 0
}
ShowMainGui()
}

; === 核心修复:更健壮的保存逻辑 ===
SaveAllSettings(*)
{
global SettingsGui, MainGui, IniFile
global LBFields, FullBgPath, SliOp, EdtHexInput, SliW, SliH
items := ControlGetItems(LBFields.Hwnd)
s := ""
for i in items
{
s .= i "|"
}
IniWrite(RTrim(s, "|"), IniFile, "Structure", "Fields")

; 确保写入背景路径
IniWrite(FullBgPath, IniFile, "Appearance", "Background")

IniWrite(SliOp.Value, IniFile, "Appearance", "Opacity")

; 确保写入背景色 (如果为空,默认为白色)
valBgColor := EdtHexInput.Value
if (valBgColor = "")
valBgColor := "FFFFFF"
IniWrite(valBgColor, IniFile, "Appearance", "BgColor")

IniWrite(SliW.Value, IniFile, "Appearance", "WinWidth")
IniWrite(SliH.Value, IniFile, "Appearance", "WinHeight")

SettingsGui.Destroy()
SettingsGui := 0
if IsObject(MainGui)
{
if (MainGui != 0)
MainGui.Destroy()
MainGui := 0
}
ShowMainGui()
}

DoInsert(fieldsOrder)
{
global FieldControls, ListFieldControls
finalStr := "---`n"
for idx, fName in fieldsOrder
{
if (fName = "")
{
continue
}
if (ListFieldControls.Has(fName))
{
lb := ListFieldControls[fName]
items := ControlGetItems(lb.Hwnd)
if (items.Length > 0)
{
finalStr .= StrLower(fName) . ":`n"
for item in items
{
finalStr .= " - " item "`n"
}
}
else
{
finalStr .= StrLower(fName) . ": []`n"
}
}
else if (FieldControls.Has(fName))
{
val := FieldControls[fName].Value
finalStr .= StrLower(fName) . ": " . val . "`n"
}
}
finalStr .= "---`n"
A_Clipboard := finalStr
SafeMsgBox("已生成并复制到剪贴板!", "成功")
if WinExist("ahk_exe Typora.exe")
{
WinActivate "ahk_exe Typora.exe"
if WinWaitActive("ahk_exe Typora.exe", , 1)
{
Send "^v"
}
}
}

InitConfig()
{
if !FileExist(IniFile)
{
IniWrite("Title|Date|Tags|Categories|Cover", IniFile, "Structure", "Fields")
IniWrite("My Title", IniFile, "DefaultValues", "Title")
IniWrite("AHK,Demo", IniFile, "DefaultValues", "Tags")
IniWrite("450", IniFile, "Appearance", "WinWidth")
IniWrite("650", IniFile, "Appearance", "WinHeight")
IniWrite("FFFFFF", IniFile, "Appearance", "BgColor")
IniWrite("255", IniFile, "Appearance", "Opacity")
}
}

; ==============================================================================
; PART 5: 帮助 & 颜色工具 (增强版:修复报错+滚动条)
; ==============================================================================
ShowMainHelpGui()
{
global MainHelpGui, MainGui
if IsObject(MainHelpGui)
{
try
{
MainHelpGui.Show()
return
}
catch
{
MainHelpGui := 0
}
}

; 禁止改变大小
MainHelpGui := Gui("+AlwaysOnTop +Owner" MainGui.Hwnd " -MaximizeBox -Resize", "YAML 生成器说明")
MainHelpGui.SetFont("s10", "Microsoft YaHei UI")
MainHelpGui.BackColor := "White"

; +VScroll 允许垂直滚动
MainHelpGui.AddEdit(
"xm ym w400 h260 ReadOnly +VScroll +Wrap",
"【YAML 生成器使用指南】`n`n"
"1. 在下方输入框填写文章信息。`n"
"2. Tags 和 Categories 支持添加多个,点击 + 号添加或点击 - 号删除。`n"
"3. 点击【插入 YAML】自动生成并粘贴到 Typora。`n"
"4. 点击【设置】可以自定义背景图、主题颜色、窗口大小和字段顺序。`n"
"5. 快捷键:在typora中 使用 Ctrl+Alt+I 快速呼出或按 F4 快速打开。`n"
"6. 存储的设置文件可以在这个Documents(文档)\TyporaSuite\TyporaSettings.ini 路径找到"
)

MainHelpGui.Show("w420 h280")
}

ShowColorHelpGui()
{
global ColorHelpGui, CT_Gui
if IsObject(ColorHelpGui)
{
try
{
ColorHelpGui.Show()
return
}
catch
{
ColorHelpGui := 0
}
}

; 禁止改变大小
ColorHelpGui := Gui("+AlwaysOnTop +Owner" CT_Gui.Hwnd " -MaximizeBox -Resize", "颜色工具说明")
ColorHelpGui.SetFont("s10", "Microsoft YaHei UI")
ColorHelpGui.BackColor := "White"

; +VScroll 允许垂直滚动
ColorHelpGui.AddEdit(
"xm ym w400 h260 ReadOnly +VScroll +Wrap",
"【MD 字体颜色工具使用说明】`n`n"
"1. 基本使用:`n"
" - 在 Typora 中选中文字。`n"
" - 点击色块即可应用颜色。`n"
" - 若未选中文字,将只会插入带颜色的空标签。`n`n"
"2. 自定义颜色:`n"
" - 点击【自定义添加】可输入任意 HEX 颜色,程序会把 RGB 值自动转换为 Hex 值`n"
" - 支持保存到色板,最大支持 18 个颜色。`n`n"
"3. 更多功能:`n"
" - 在任意色块上可以右键点击选择删除该颜色。`n"
" - 可以使用快捷键Ctrl + Alt + C快速打开最小化窗口`n`n"
"4. 故障排除:`n"
" - 如果文字意外消失,请按 Ctrl+Z 撤销。`n"
" - 若要在已添加颜色字体上换颜色,只有先删除已有颜色代码,才能腾出位置添加新颜色。"
)

ColorHelpGui.Show("w420 h280")
}

; === 颜色工具数据处理 (核心新增逻辑) ===
LoadColorToolData()
{
global CT_RuntimeList, IniFile, CT_DefaultColors
; 从 INI 读取
rawStr := IniRead(IniFile, "ColorTool", "UserColors", "")

CT_RuntimeList := []

if (rawStr = "")
{
; 首次运行或被清空,使用默认值
CT_RuntimeList := CT_DefaultColors.Clone()
SaveColorToolData() ; 回写默认值
}
else
{
; 解析字符串 "Name:Hex|Name:Hex"
Loop Parse, rawStr, "|"
{
if (A_LoopField == "")
continue
parts := StrSplit(A_LoopField, ":")
if (parts.Length = 2)
CT_RuntimeList.Push([parts[1], parts[2]])
}

; 双重保险:如果读取为空数组(比如格式坏了),恢复默认
if (CT_RuntimeList.Length == 0)
{
CT_RuntimeList := CT_DefaultColors.Clone()
SaveColorToolData()
}
}
}

SaveColorToolData()
{
global CT_RuntimeList, IniFile
saveStr := ""
for item in CT_RuntimeList
{
saveStr .= item[1] . ":" . item[2] . "|"
}
IniWrite(RTrim(saveStr, "|"), IniFile, "ColorTool", "UserColors")
}

RestoreDefaultColors(*)
{
global CT_RuntimeList, CT_DefaultColors, CT_Gui

; 这里的 MsgBox 强制置顶
result := MsgBox("确定要恢复默认的 18 种颜色吗?`n自定义的颜色将会丢失。", "恢复默认", "YesNo Icon? 262144")
if (result == "Yes")
{
CT_RuntimeList := CT_DefaultColors.Clone()
SaveColorToolData()

if IsObject(CT_Gui)
{
CT_Gui.Destroy()
CT_Gui := 0
}
ShowColorTool()
}
}

; === 颜色工具主窗口 ===
ShowColorTool()
{
global CT_Gui, MainGui, CT_RuntimeList

if IsObject(CT_Gui)
{
try
{
CT_Gui.Show()
return
}
catch
{
CT_Gui := 0
}
}

; 禁止改变大小
CT_Gui := Gui("+AlwaysOnTop -MaximizeBox -Resize", "MD字体颜色工具")
CT_Gui.SetFont("s9", "Microsoft YaHei UI")
CT_Gui.BackColor := "White"

; 顶部功能区
infoBtn := CT_Gui.AddButton("xm w80", "使用说明")
customBtn := CT_Gui.AddButton("x+10 yp w90", "自定义添加")
resetBtn := CT_Gui.AddButton("x+10 yp w80", "恢复默认")

infoBtn.OnEvent("Click", (*) => ShowColorHelpGui())
customBtn.OnEvent("Click", (*) => ShowCustomColorGui())
resetBtn.OnEvent("Click", RestoreDefaultColors)

colW := 120
rowH := 28
gapY := 6
startY := 45

; 动态渲染色块
Loop CT_RuntimeList.Length
{
if (A_Index > 18) ; 最多显示18个
break

cName := CT_RuntimeList[A_Index][1]
cHex := CT_RuntimeList[A_Index][2]

; 0为左列, 1为右列
col := (A_Index <= 9) ? 0 : 1
row := Mod(A_Index - 1, 9)

; 左边是 xm,右边是 xm+140
xPosStr := (col == 0) ? "xm" : "xm+140"
yPos := startY + row * (rowH + gapY)

; 绘制色块
t := CT_Gui.AddText(
xPosStr " y" yPos " w" colW " h" rowH
" 0x200 Center Border Background" cHex,
cName
)

; 文字颜色适配
if IsDarkColor(cHex)
t.SetFont("cWhite")
else
t.SetFont("cBlack")

t.Tag := cHex
t.OnEvent("Click", ApplyColorFromText)

; === 核心修复:直接绑定事件处理,不在这里 Bind 数据 ===
; 将具体的 HEX 和 Name 存入控件属性,点击时再提取
t.ColorName := cName
t.ColorHex := cHex
t.OnEvent("ContextMenu", ShowColorContextMenu)
}

CT_Gui.Show("w290 h370")
}

; 核心修复:动态右键菜单
ShowColorContextMenu(ctrl, *)
{
; 动态创建一个菜单
tempMenu := Menu()
; 绑定删除函数,传入该控件的 Hex 和 Name
tempMenu.Add("删除 [" ctrl.ColorName "]", (*) => DeleteColorByHex(ctrl.ColorHex, ctrl.ColorName))
tempMenu.Show()
}

DeleteColorByHex(targetHex, targetName)
{
global CT_RuntimeList, CT_Gui

foundIndex := 0
for idx, item in CT_RuntimeList
{
if (item[2] == targetHex)
{
foundIndex := idx
break
}
}

if (foundIndex > 0)
{
CT_RuntimeList.RemoveAt(foundIndex)
SaveColorToolData()
SafeMsgBox("已删除颜色: " targetName)

; 刷新界面
CT_Gui.Destroy()
CT_Gui := 0
ShowColorTool()
}
else
{
SafeMsgBox("删除失败:颜色未找到 (可能已更改)")
}
}

ApplyColorFromText(ctrl, *)
{
if WinExist("ahk_exe Typora.exe")
{
WinActivate "ahk_exe Typora.exe"
WinWaitActive "ahk_exe Typora.exe", , 1
AddFontColor("#" ctrl.Tag)
}
}

; === 自定义颜色窗口 ===
ShowCustomColorGui()
{
global CT_CustomGui, CT_CustomHex, CT_Gui

if IsObject(CT_CustomGui)
{
try
{
CT_CustomGui.Show()
return
}
catch
{
CT_CustomGui := 0
}
}

CT_CustomGui := Gui("+AlwaysOnTop -MaximizeBox -Resize +Owner" CT_Gui.Hwnd, "自定义颜色")
CT_CustomGui.SetFont("s9", "Microsoft YaHei UI")
CT_CustomGui.BackColor := "White"

CT_CustomGui.AddText("xm", "HEX (不带 #):")
HexEdit := CT_CustomGui.AddEdit("xm w260", CT_CustomHex)

CT_CustomGui.AddText("xm y+10", "RGB:")
R := CT_CustomGui.AddEdit("xm w80", "255")
G := CT_CustomGui.AddEdit("x+10 yp w80", "0")
B := CT_CustomGui.AddEdit("x+10 yp w80", "0")

Preview := CT_CustomGui.AddText(
"xm y+10 w260 h40 0x200 Center Border Background" CT_CustomHex,
"预览"
)

R.OnEvent("Change", (*) => UpdateFromRGB(R, G, B, HexEdit, Preview))
G.OnEvent("Change", (*) => UpdateFromRGB(R, G, B, HexEdit, Preview))
B.OnEvent("Change", (*) => UpdateFromRGB(R, G, B, HexEdit, Preview))
HexEdit.OnEvent("Change", (*) => UpdateFromHex(HexEdit, Preview))

refresh := CT_CustomGui.AddButton("xm y+10 w260", "刷新预览")
refresh.OnEvent("Click", (*) => RefreshCustomPreview(R, G, B, HexEdit, Preview))

; 新增:添加到列表
addBtn := CT_CustomGui.AddButton("xm y+6 w260", "添加到列表 (并保存)")
addBtn.OnEvent("Click", (*) => AddColorToTool(HexEdit.Value))

apply := CT_CustomGui.AddButton("xm y+6 w260", "仅使用该颜色")
apply.OnEvent("Click", (*) => ApplyCustomColor(HexEdit.Value))

; 这里的 Back 按钮逻辑修改为销毁窗口,避免僵尸对象
back := CT_CustomGui.AddButton("xm y+6 w260", "返回")
back.OnEvent("Click", (*) => (CT_CustomGui.Destroy(), CT_CustomGui := 0))

CT_CustomGui.Show("w300 h400")
}

; 核心新增:添加到色板逻辑
AddColorToTool(hex)
{
global CT_RuntimeList, CT_Gui, CT_CustomGui

if !RegExMatch(hex, "^[0-9A-Fa-f]{6}$")
{
SafeMsgBox("HEX 代码无效,无法添加。")
return
}

if (CT_RuntimeList.Length >= 18)
{
SafeMsgBox("颜色数量已达上限 (18个)。`n请先右键删除一些现有颜色。")
return
}

; 查重
for item in CT_RuntimeList
{
if (item[2] = hex)
{
SafeMsgBox("该颜色已存在!")
return
}
}

; 弹出名称输入框 (置顶)
CustomInputBox("颜色名称", "请为颜色命名 (建议4字以内):", DoAddColorConfirm, hex)
}

DoAddColorConfirm(name)
{
global CT_RuntimeList, CT_CustomHex, CT_Gui

if (name == "")
name := "自定义"

CT_RuntimeList.Push([name, CT_CustomHex])
SaveColorToolData()

SafeMsgBox("颜色 [" name "] 已添加!")

; 刷新主界面
if IsObject(CT_Gui)
{
CT_Gui.Destroy()
CT_Gui := 0
}
ShowColorTool()
}

UpdateFromRGB(R, G, B, HexEdit, Preview)
{
global CT_CustomHex
CT_CustomHex := Format("{:02X}{:02X}{:02X}", Clamp(R.Value), Clamp(G.Value), Clamp(B.Value))
HexEdit.Value := CT_CustomHex
try
{
Preview.Opt("+Background" CT_CustomHex)
}
}

UpdateFromHex(HexEdit, Preview)
{
global CT_CustomHex
if RegExMatch(HexEdit.Value, "^[0-9A-Fa-f]{6}$")
{
CT_CustomHex := HexEdit.Value
try
{
Preview.Opt("+Background" CT_CustomHex)
}
}
}

RefreshCustomPreview(R, G, B, HexEdit, Preview)
{
global CT_CustomHex, CT_CustomGui
if RegExMatch(HexEdit.Value, "^[0-9A-Fa-f]{6}$")
{
CT_CustomHex := HexEdit.Value
}
else
{
CT_CustomHex := Format("{:02X}{:02X}{:02X}", Clamp(R.Value), Clamp(G.Value), Clamp(B.Value))
}

CT_CustomGui.Hide()
try
{
Preview.Opt("+Background" CT_CustomHex)
}
CT_CustomGui.Show()
}

ApplyCustomColor(hex)
{
if WinExist("ahk_exe Typora.exe")
{
WinActivate "ahk_exe Typora.exe"
WinWaitActive "ahk_exe Typora.exe", , 1
AddFontColor("#" hex)
}
}

Clamp(v)
{
if (v = "" || !IsNumber(v))
{
return 0
}
v := Integer(v)
return v < 0 ? 0 : v > 255 ? 255 : v
}

AddFontColor(colorStr)
{
ClipSaved := ClipboardAll()
A_Clipboard := ""

Send "^c"
if !ClipWait(0.5)
{
A_Clipboard := "<font color='" colorStr "'></font>"
Send "^v"
Send "{Left 7}"
Sleep 300
A_Clipboard := ClipSaved
return
}

if (A_Clipboard != "")
{
A_Clipboard := "<font color='" colorStr "'>" A_Clipboard "</font>"
Send "^v"
Sleep 300
}

A_Clipboard := ClipSaved
}

点击跳转Github仓库

软件迭代过程

utools版本的源码

1
2
3
4
5
6
7
8
9
TyporaWriterKit
├─ index.html
├─ logo.png
├─ plugin.json
├─ preload.js
├─ js
│ └─ app.js
└─ css
└─ style.css

① logo.png

首先确保你有这些文件,logo自己选一张尺寸120×120大小以上的图片 例如这样的

logo

② index.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
<!DOCTYPE html>
<html lang="zh-CN">

<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Typora 小助手</title>
<link rel="stylesheet" href="css/style.css">
</head>

<body>
<div id="app">
<div class="typora-status" id="typora-status">
<span class="status-dot" id="status-dot"></span>
<span class="status-text" id="status-text">检测中...</span>
<button class="refresh-btn" id="refresh-status" title="刷新">🔄</button>
</div>

<nav class="nav-tabs">
<button class="tab-btn active" data-tab="yaml">📝 YAML</button>
<button class="tab-btn" data-tab="color">🎨 颜色</button>
<button class="tab-btn" data-tab="settings">⚙️ 设置</button>
</nav>

<div id="yaml-panel" class="panel active">
<div class="panel-content">
<div class="panel-header">
<h2>文章属性</h2>
</div>
<div class="form-container" id="yaml-form"></div>
</div>
<div class="panel-footer">
<div class="btn-group">
<button id="btn-copy" class="btn btn-primary">📋 复制到剪贴板</button>
<button id="btn-preview" class="btn btn-secondary">👁️ 预览</button>
<button id="btn-yaml-help" class="btn btn-info">📖</button>
</div>
<div id="yaml-preview" class="preview-box hidden">
<pre id="yaml-output"></pre>
</div>
</div>
</div>

<div id="color-panel" class="panel">
<div class="panel-content">
<div class="panel-header">
<h2>MD 字体颜色</h2>
<p class="hint">💡 点击色块复制颜色代码,粘贴到 Typora 即可</p>
</div>
<div class="color-grid" id="color-grid"></div>
<div class="custom-color-section">
<h3>自定义颜色</h3>
<div class="custom-color-form">
<div class="input-group">
<label>HEX:</label>
<input type="text" id="custom-hex" placeholder="FF0000" maxlength="6">
</div>
<div class="input-group">
<label>RGB:</label>
<input type="number" id="custom-r" placeholder="R" min="0" max="255">
<input type="number" id="custom-g" placeholder="G" min="0" max="255">
<input type="number" id="custom-b" placeholder="B" min="0" max="255">
</div>
<div class="input-group">
<label>名称:</label>
<input type="text" id="custom-name" placeholder="自定义" maxlength="7">
</div>
<div class="color-preview-box">
<div id="color-preview"></div>
</div>
</div>
<div class="btn-group">
<button id="btn-refresh-preview" class="btn btn-secondary btn-small">刷新</button>
<button id="btn-add-color" class="btn btn-primary btn-small">添加</button>
<button id="btn-use-color" class="btn btn-success btn-small">复制</button>
</div>
</div>
</div>
<div class="panel-footer">
<div class="btn-group">
<button id="btn-restore-colors" class="btn btn-warning">恢复默认</button>
<button id="btn-color-help" class="btn btn-info">📖 使用说明</button>
</div>
</div>
</div>

<div id="settings-panel" class="panel">
<div class="panel-content">
<div class="panel-header">
<h2>全局设置</h2>
</div>
<div class="settings-section">
<h3>属性管理 <span class="hint-inline">(右键编辑默认值)</span></h3>
<div class="field-manager">
<div class="field-list" id="field-list"></div>
<div class="field-controls">
<input type="text" id="new-field-name" placeholder="新属性名 (英文)">
<button id="btn-add-field" class="btn btn-small btn-primary"></button>
</div>
</div>
</div>
<div class="settings-section">
<h3>界面主题</h3>
<div class="theme-color-section">
<label>选择主题颜色:</label>
<div class="theme-color-grid" id="theme-color-grid"></div>
</div>
</div>
</div>
<div class="panel-footer">
<div class="btn-group">
<button id="btn-save-settings" class="btn btn-primary">💾 保存设置</button>
<button id="btn-reset-settings" class="btn btn-danger">🔄 重置</button>
</div>
</div>
</div>
</div>

<div id="toast" class="toast hidden"></div>

<div id="modal-overlay" class="modal-overlay hidden">
<div class="modal">
<div class="modal-header">
<h3 id="modal-title">编辑默认值</h3>
<button class="modal-close" id="modal-close" type="button">&times;</button>
</div>
<div class="modal-body">
<p id="modal-field-name"></p>
<div class="modal-input-group">
<label for="modal-default-input">默认值:</label>
<textarea id="modal-default-input" rows="3" placeholder="输入默认值"></textarea>
</div>
<p class="hint">💡 Tags/Categories 用逗号分隔多个值</p>
</div>
<div class="modal-footer">
<button id="modal-cancel" class="btn btn-secondary" type="button">取消</button>
<button id="modal-save" class="btn btn-primary" type="button">保存</button>
</div>
</div>
</div>

<div id="help-modal-overlay" class="modal-overlay hidden">
<div class="modal modal-help">
<div class="modal-header">
<h3 id="help-modal-title">使用说明</h3>
<button class="modal-close" id="help-modal-close" type="button">&times;</button>
</div>
<div class="modal-body modal-body-scroll">
<div id="help-content"></div>
</div>
<div class="modal-footer">
<button id="help-modal-ok" class="btn btn-primary" type="button">我知道了</button>
</div>
</div>
</div>

<script src="js/app.js"></script>
</body>

</html>

③ plugin.json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
{
"pluginName": "Typora 小助手",
"description": "YAML 生成器 & MD 字体颜色工具",
"main": "index.html",
"preload": "preload.js",
"logo": "logo.png",
"version": "1.0.0",
"author": "funingna-wakawaka",
"homepage": "",
"features": [
{
"code": "yaml",
"explain": "生成 Typora YAML 头部",
"cmds": [
"yaml",
"YAML生成器",
"typora",
"文章"
]
},
{
"code": "color",
"explain": "MD 字体颜色工具",
"cmds": [
"字体颜色",
"color",
"颜色",
"font"
]
}
]
}

④ preload.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
const { clipboard } = require("electron");
const { exec } = require("child_process");
const fs = require("fs");
const path = require("path");

var configDir = path.join(require("os").homedir(), "Documents", "TyporaSuite");
var configFile = path.join(configDir, "utools-config.json");

if (!fs.existsSync(configDir)) {
fs.mkdirSync(configDir, { recursive: true });
}

function loadConfig() {
try {
if (fs.existsSync(configFile)) {
return JSON.parse(fs.readFileSync(configFile, "utf-8"));
}
} catch (e) {}
return null;
}

function saveConfig(config) {
try {
fs.writeFileSync(configFile, JSON.stringify(config, null, 2), "utf-8");
return true;
} catch (e) {
return false;
}
}

function getCurrentDateTime() {
var now = new Date();
var y = now.getFullYear();
var m = String(now.getMonth() + 1).padStart(2, "0");
var d = String(now.getDate()).padStart(2, "0");
var h = String(now.getHours()).padStart(2, "0");
var min = String(now.getMinutes()).padStart(2, "0");
var s = String(now.getSeconds()).padStart(2, "0");
return y + "-" + m + "-" + d + " " + h + ":" + min + ":" + s;
}

function isTyporaRunning() {
return new Promise(function (resolve) {
var platform = process.platform;
var cmd;
if (platform === "win32") {
cmd = 'tasklist /FI "IMAGENAME eq Typora.exe" /NH';
} else if (platform === "darwin") {
cmd = "pgrep -x Typora";
} else {
cmd = "pgrep -x typora";
}
exec(cmd, function (error, stdout) {
if (platform === "win32") {
resolve(stdout.toLowerCase().indexOf("typora.exe") !== -1);
} else {
resolve(!error && stdout.trim().length > 0);
}
});
});
}

window.services = {
copyToClipboard: function (text) {
clipboard.writeText(text);
},
readClipboard: function () {
return clipboard.readText();
},
loadConfig: loadConfig,
saveConfig: saveConfig,
getCurrentDateTime: getCurrentDateTime,
isTyporaRunning: isTyporaRunning,
};

⑤ app.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
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
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
(function () {
"use strict";

var config = null;
var currentEditingField = null;
var typoraOnline = false;

var defaultColors = [
{ name: "焦橙色", hex: "FF8C00" },
{ name: "红色", hex: "FF0000" },
{ name: "天蓝", hex: "87CEFA" },
{ name: "绿松石", hex: "40E0D0" },
{ name: "紫红", hex: "C71585" },
{ name: "蓝绿色", hex: "008080" },
{ name: "金黄色", hex: "FFD700" },
{ name: "灰黑色", hex: "696969" },
{ name: "亮粉色", hex: "FF1493" },
{ name: "亮蓝", hex: "1E90FF" },
{ name: "鲜绿", hex: "32CD32" },
{ name: "橙红", hex: "FF4500" },
{ name: "岩蓝", hex: "6A5ACD" },
{ name: "巧克力", hex: "D2691E" },
{ name: "深红", hex: "DC143C" },
{ name: "海绿", hex: "2E8B57" },
{ name: "钢蓝", hex: "4682B4" },
{ name: "纯黑", hex: "000000" },
];

var themeColors = [
{ name: "经典蓝", hex: "007bff" },
{ name: "翠绿", hex: "28a745" },
{ name: "活力橙", hex: "fd7e14" },
{ name: "玫瑰红", hex: "e83e8c" },
{ name: "深紫", hex: "6f42c1" },
{ name: "青色", hex: "17a2b8" },
{ name: "珊瑚", hex: "ff6b6b" },
{ name: "薄荷绿", hex: "20c997" },
{ name: "琥珀", hex: "ffc107" },
{ name: "石板蓝", hex: "6c757d" },
{ name: "深海蓝", hex: "1A237E" },
{ name: "暗夜紫", hex: "4a148c" },
{ name: "森林绿", hex: "2E7D32" },
{ name: "酒红", hex: "880E4F" },
{ name: "巧克力", hex: "5D4037" },
{ name: "钢铁灰", hex: "455a64" },
{ name: "天际蓝", hex: "0288D1" },
{ name: "落日橙", hex: "EF6C00" },
{ name: "樱花粉", hex: "EC407A" },
{ name: "极客黑", hex: "263238" },
];

var protectedFields = ["Title", "Date", "Tags", "Categories", "Cover"];

document.addEventListener("DOMContentLoaded", init);

function init() {
loadConfig();
setupTabs();
renderYAMLForm();
renderColorGrid();
renderFieldList();
renderThemeColorGrid();
setupEventListeners();
applyTheme();
checkTyporaStatus();
setInterval(checkTyporaStatus, 5000);
}

function checkTyporaStatus() {
var dot = document.getElementById("status-dot");
var text = document.getElementById("status-text");
if (!dot || !text) return;

if (window.services && window.services.isTyporaRunning) {
window.services
.isTyporaRunning()
.then(function (running) {
typoraOnline = running;
if (running) {
dot.classList.add("online");
text.textContent = "Typora 运行中";
} else {
dot.classList.remove("online");
text.textContent = "Typora 未运行";
}
})
.catch(function () {
dot.classList.remove("online");
text.textContent = "检测失败";
});
} else {
text.textContent = "测试模式";
}
}

function loadConfig() {
var loaded = null;
if (window.services && window.services.loadConfig) {
loaded = window.services.loadConfig();
}
if (!loaded) {
var saved = localStorage.getItem("typora-suite-config");
if (saved) {
try {
loaded = JSON.parse(saved);
} catch (e) {}
}
}
config = loaded || getDefaultConfig();
if (!config.defaultValues) config.defaultValues = {};
if (!config.appearance) config.appearance = { themeColor: "007bff" };
if (!config.colors)
config.colors = JSON.parse(JSON.stringify(defaultColors));
if (!config.fields)
config.fields = ["Title", "Date", "Tags", "Categories", "Cover"];
}

function saveConfig() {
if (window.services && window.services.saveConfig) {
window.services.saveConfig(config);
}
localStorage.setItem("typora-suite-config", JSON.stringify(config));
}

function getDefaultConfig() {
return {
fields: ["Title", "Date", "Tags", "Categories", "Cover"],
defaultValues: { Title: "", Tags: [], Categories: [], Cover: "" },
colors: JSON.parse(JSON.stringify(defaultColors)),
appearance: { themeColor: "007bff" },
};
}

function setupTabs() {
var tabs = document.querySelectorAll(".tab-btn");
for (var i = 0; i < tabs.length; i++) {
tabs[i].addEventListener("click", function () {
var allTabs = document.querySelectorAll(".tab-btn");
var allPanels = document.querySelectorAll(".panel");
for (var j = 0; j < allTabs.length; j++)
allTabs[j].classList.remove("active");
for (var k = 0; k < allPanels.length; k++)
allPanels[k].classList.remove("active");
this.classList.add("active");
document
.getElementById(this.dataset.tab + "-panel")
.classList.add("active");
});
}
}

function renderYAMLForm() {
var container = document.getElementById("yaml-form");
if (!container) return;
container.innerHTML = "";

for (var i = 0; i < config.fields.length; i++) {
var fieldName = config.fields[i];
var group = document.createElement("div");
group.className = "form-group";

var label = document.createElement("label");
label.textContent = fieldName + ":";
group.appendChild(label);

if (fieldName === "Tags" || fieldName === "Categories") {
group.appendChild(createListField(fieldName));
} else {
var input = document.createElement("input");
input.type = "text";
input.id = "field-" + fieldName;
if (fieldName === "Date") {
input.value = getCurrentDateTime();
} else {
input.value = config.defaultValues[fieldName] || "";
}
group.appendChild(input);
}
container.appendChild(group);
}
}

function createListField(fieldName) {
var wrapper = document.createElement("div");
wrapper.className = "list-field";

var itemsEl = document.createElement("div");
itemsEl.className = "list-items";
itemsEl.id = "list-" + fieldName;

var items = config.defaultValues[fieldName] || [];
if (typeof items === "string") {
var temp = [];
var parts = items.split(",");
for (var x = 0; x < parts.length; x++) {
var s = parts[x].trim();
if (s) temp.push(s);
}
items = temp;
}

for (var j = 0; j < items.length; j++) {
addListItem(itemsEl, items[j]);
}

var inputRow = document.createElement("div");
inputRow.className = "list-input-row";

var input = document.createElement("input");
input.type = "text";
input.placeholder = "输入后添加";

var btn = document.createElement("button");
btn.className = "btn btn-primary btn-small";
btn.textContent = "+";

btn.addEventListener("click", function () {
var v = input.value.trim();
if (v) {
addListItem(itemsEl, v);
input.value = "";
}
});

input.addEventListener("keypress", function (e) {
if (e.key === "Enter") {
var v = input.value.trim();
if (v) {
addListItem(itemsEl, v);
input.value = "";
}
}
});

inputRow.appendChild(input);
inputRow.appendChild(btn);
wrapper.appendChild(itemsEl);
wrapper.appendChild(inputRow);
return wrapper;
}

function addListItem(container, text) {
var item = document.createElement("span");
item.className = "list-item";
item.innerHTML = escapeHtml(text) + '<span class="remove-btn">×</span>';
item.querySelector(".remove-btn").addEventListener("click", function () {
item.remove();
});
container.appendChild(item);
}

function generateYAML() {
var yaml = "---\n";
for (var i = 0; i < config.fields.length; i++) {
var field = config.fields[i];
var key = field.toLowerCase();

if (field === "Tags" || field === "Categories") {
var container = document.getElementById("list-" + field);
var items = container ? container.querySelectorAll(".list-item") : [];
if (items.length > 0) {
yaml += key + ":\n";
for (var j = 0; j < items.length; j++) {
var txt = items[j].textContent.replace("×", "").trim();
yaml += " - " + txt + "\n";
}
} else {
yaml += key + ": []\n";
}
} else {
var input = document.getElementById("field-" + field);
var val = input ? input.value : "";
yaml += key + ": " + val + "\n";
}
}
yaml += "---\n";
return yaml;
}

function copyYAML() {
var yaml = generateYAML();
copyToClipboard(yaml);
showToast("📋 已复制到剪贴板", "success");
}

function renderColorGrid() {
var grid = document.getElementById("color-grid");
if (!grid) return;
grid.innerHTML = "";

for (var i = 0; i < config.colors.length; i++) {
var color = config.colors[i];
var block = document.createElement("div");
block.className = "color-block";

if (isDarkColor(color.hex)) {
block.classList.add("light-text");
} else {
block.classList.add("dark-text");
}

block.style.backgroundColor = "#" + color.hex;
block.textContent = color.name;
block.setAttribute("data-hex", color.hex);
block.setAttribute("data-index", i);

block.addEventListener("click", function () {
var hex = this.getAttribute("data-hex");
var name = this.textContent;
copyColorCode(hex, name);
});

block.addEventListener("contextmenu", function (e) {
e.preventDefault();
var hex = this.getAttribute("data-hex");
var idx = parseInt(this.getAttribute("data-index"));
var name = this.textContent;
showColorMenu(e.pageX, e.pageY, { hex: hex, name: name }, idx);
});

grid.appendChild(block);
}
}

function copyColorCode(hex, name) {
var code = "<font color='#" + hex + "'></font>";
copyToClipboard(code);
showToast("📋 " + name + " 已复制", "success");
}

function showColorMenu(x, y, color, index) {
removeContextMenu();

var menu = document.createElement("div");
menu.className = "context-menu";
menu.style.left = Math.min(x, window.innerWidth - 140) + "px";
menu.style.top = Math.min(y, window.innerHeight - 90) + "px";

var copyItem = document.createElement("div");
copyItem.className = "context-menu-item";
copyItem.textContent = "📋 复制 #" + color.hex;
copyItem.addEventListener("click", function () {
copyToClipboard("#" + color.hex);
showToast("已复制");
menu.remove();
});

var divider = document.createElement("div");
divider.className = "context-menu-divider";

var deleteItem = document.createElement("div");
deleteItem.className = "context-menu-item danger";
deleteItem.textContent = "🗑️ 删除";
deleteItem.addEventListener("click", function () {
config.colors.splice(index, 1);
saveConfig();
renderColorGrid();
showToast("已删除");
menu.remove();
});

menu.appendChild(copyItem);
menu.appendChild(divider);
menu.appendChild(deleteItem);
document.body.appendChild(menu);

setTimeout(function () {
document.addEventListener("click", function handler() {
if (menu.parentNode) menu.remove();
document.removeEventListener("click", handler);
});
}, 10);
}

function updateColorPreview() {
var hexInput = document.getElementById("custom-hex");
var preview = document.getElementById("color-preview");
if (!hexInput || !preview) return;
var hex = hexInput.value.replace("#", "");
if (/^[0-9A-Fa-f]{6}$/.test(hex)) {
preview.style.backgroundColor = "#" + hex;
}
}

function updateHexFromRGB() {
var rEl = document.getElementById("custom-r");
var gEl = document.getElementById("custom-g");
var bEl = document.getElementById("custom-b");
var hexEl = document.getElementById("custom-hex");
if (!rEl || !gEl || !bEl || !hexEl) return;

var r = Math.min(255, Math.max(0, parseInt(rEl.value) || 0));
var g = Math.min(255, Math.max(0, parseInt(gEl.value) || 0));
var b = Math.min(255, Math.max(0, parseInt(bEl.value) || 0));

var rH = r.toString(16);
if (rH.length === 1) rH = "0" + rH;
var gH = g.toString(16);
if (gH.length === 1) gH = "0" + gH;
var bH = b.toString(16);
if (bH.length === 1) bH = "0" + bH;

hexEl.value = (rH + gH + bH).toUpperCase();
updateColorPreview();
}

function addCustomColor() {
var hexEl = document.getElementById("custom-hex");
var nameEl = document.getElementById("custom-name");
if (!hexEl || !nameEl) return;

var hex = hexEl.value.replace("#", "").toUpperCase();
var name = nameEl.value.trim() || "自定义";

if (!/^[0-9A-Fa-f]{6}$/.test(hex)) {
showToast("HEX 无效", "error");
return;
}
if (config.colors.length >= 18) {
showToast("已达上限", "error");
return;
}

for (var i = 0; i < config.colors.length; i++) {
if (config.colors[i].hex.toUpperCase() === hex) {
showToast("已存在", "error");
return;
}
}

config.colors.push({ name: name, hex: hex });
saveConfig();
renderColorGrid();
showToast("已添加 " + name, "success");
}

function useCustomColor() {
var hexEl = document.getElementById("custom-hex");
if (!hexEl) return;
var hex = hexEl.value.replace("#", "").toUpperCase();
if (/^[0-9A-Fa-f]{6}$/.test(hex)) {
copyColorCode(hex, "自定义");
} else {
showToast("HEX 无效", "error");
}
}

function restoreDefaultColors() {
if (confirm("恢复默认颜色?")) {
config.colors = JSON.parse(JSON.stringify(defaultColors));
saveConfig();
renderColorGrid();
showToast("已恢复", "success");
}
}

function renderFieldList() {
var container = document.getElementById("field-list");
if (!container) return;
container.innerHTML = "";

for (var i = 0; i < config.fields.length; i++) {
var field = config.fields[i];
var item = document.createElement("div");
item.className = "field-item";
item.setAttribute("data-field", field);
item.setAttribute("data-index", i);

var nameWrapper = document.createElement("div");
var nameSpan = document.createElement("span");
nameSpan.className = "field-name";
nameSpan.textContent = field;
nameWrapper.appendChild(nameSpan);

var defaultVal = config.defaultValues[field];
if (
defaultVal &&
(Array.isArray(defaultVal) ? defaultVal.length : defaultVal)
) {
var defaultSpan = document.createElement("span");
defaultSpan.className = "field-default";
var preview = "";
if (Array.isArray(defaultVal)) {
preview = defaultVal.slice(0, 2).join(", ");
if (defaultVal.length > 2) preview += "...";
} else {
preview = String(defaultVal).substring(0, 10);
if (String(defaultVal).length > 10) preview += "...";
}
defaultSpan.textContent = "(" + preview + ")";
nameWrapper.appendChild(defaultSpan);
}

var actions = document.createElement("div");
actions.className = "field-actions";

if (i > 0) {
var upBtn = document.createElement("button");
upBtn.textContent = "▲";
upBtn.setAttribute("data-index", i);
upBtn.addEventListener("click", function (e) {
e.stopPropagation();
moveField(parseInt(this.getAttribute("data-index")), -1);
});
actions.appendChild(upBtn);
}

if (i < config.fields.length - 1) {
var downBtn = document.createElement("button");
downBtn.textContent = "▼";
downBtn.setAttribute("data-index", i);
downBtn.addEventListener("click", function (e) {
e.stopPropagation();
moveField(parseInt(this.getAttribute("data-index")), 1);
});
actions.appendChild(downBtn);
}

if (protectedFields.indexOf(field) === -1) {
var delBtn = document.createElement("button");
delBtn.textContent = "🗑️";
delBtn.style.color = "var(--danger-color)";
delBtn.setAttribute("data-field", field);
delBtn.setAttribute("data-index", i);
delBtn.addEventListener("click", function (e) {
e.stopPropagation();
var f = this.getAttribute("data-field");
var idx = parseInt(this.getAttribute("data-index"));
if (confirm('删除 "' + f + '"?')) {
config.fields.splice(idx, 1);
delete config.defaultValues[f];
saveConfig();
renderFieldList();
renderYAMLForm();
}
});
actions.appendChild(delBtn);
}

item.appendChild(nameWrapper);
item.appendChild(actions);

item.addEventListener("contextmenu", function (e) {
e.preventDefault();
var f = this.getAttribute("data-field");
var idx = parseInt(this.getAttribute("data-index"));
showFieldMenu(e.pageX, e.pageY, f, idx);
});

container.appendChild(item);
}
}

function showFieldMenu(x, y, field, index) {
removeContextMenu();

var menu = document.createElement("div");
menu.className = "context-menu";
menu.style.left = Math.min(x, window.innerWidth - 140) + "px";
menu.style.top = Math.min(y, window.innerHeight - 100) + "px";

var editItem = document.createElement("div");
editItem.className = "context-menu-item";
editItem.textContent = "✏️ 编辑默认值";
editItem.addEventListener("click", function () {
menu.remove();
showDefaultValueModal(field);
});
menu.appendChild(editItem);

if (protectedFields.indexOf(field) === -1) {
var divider = document.createElement("div");
divider.className = "context-menu-divider";
menu.appendChild(divider);

var deleteItem = document.createElement("div");
deleteItem.className = "context-menu-item danger";
deleteItem.textContent = "🗑️ 删除";
deleteItem.addEventListener("click", function () {
menu.remove();
if (confirm('删除 "' + field + '"?')) {
config.fields.splice(index, 1);
delete config.defaultValues[field];
saveConfig();
renderFieldList();
renderYAMLForm();
}
});
menu.appendChild(deleteItem);
}

document.body.appendChild(menu);

setTimeout(function () {
document.addEventListener("click", function handler() {
if (menu.parentNode) menu.remove();
document.removeEventListener("click", handler);
});
}, 10);
}

function showDefaultValueModal(field) {
currentEditingField = field;
var overlay = document.getElementById("modal-overlay");
var fieldNameEl = document.getElementById("modal-field-name");
var inputEl = document.getElementById("modal-default-input");

if (!overlay || !fieldNameEl || !inputEl) return;

fieldNameEl.innerHTML = "属性: <strong>" + field + "</strong>";

var val = config.defaultValues[field];
if (val === undefined || val === null) val = "";
if (Array.isArray(val)) {
inputEl.value = val.join(", ");
} else {
inputEl.value = String(val);
}

overlay.classList.remove("hidden");
overlay.style.display = "flex";
setTimeout(function () {
inputEl.focus();
}, 100);
}

function saveDefaultValue() {
if (!currentEditingField) return;
var inputEl = document.getElementById("modal-default-input");
if (!inputEl) return;

var value = inputEl.value.trim();

if (
currentEditingField === "Tags" ||
currentEditingField === "Categories"
) {
if (value === "") {
value = [];
} else {
var arr = value.split(",");
var result = [];
for (var i = 0; i < arr.length; i++) {
var s = arr[i].trim();
if (s) result.push(s);
}
value = result;
}
}

config.defaultValues[currentEditingField] = value;
saveConfig();
closeModal();
renderFieldList();
renderYAMLForm();
showToast("已保存", "success");
currentEditingField = null;
}

function closeModal() {
var overlay = document.getElementById("modal-overlay");
if (overlay) {
overlay.classList.add("hidden");
overlay.style.display = "none";
}
currentEditingField = null;
}

function closeHelpModal() {
var overlay = document.getElementById("help-modal-overlay");
if (overlay) {
overlay.classList.add("hidden");
overlay.style.display = "none";
}
}

function moveField(index, direction) {
var newIndex = index + direction;
if (newIndex >= 0 && newIndex < config.fields.length) {
var temp = config.fields[index];
config.fields[index] = config.fields[newIndex];
config.fields[newIndex] = temp;
saveConfig();
renderFieldList();
}
}

function addField() {
var input = document.getElementById("new-field-name");
if (!input) return;
var name = input.value.trim();
if (!name) {
showToast("请输入名称", "error");
return;
}
if (config.fields.length >= 10) {
showToast("已达上限", "error");
return;
}
if (config.fields.indexOf(name) !== -1) {
showToast("已存在", "error");
return;
}
config.fields.push(name);
config.defaultValues[name] = "";
input.value = "";
saveConfig();
renderFieldList();
renderYAMLForm();
showToast("已添加", "success");
}

function renderThemeColorGrid() {
var grid = document.getElementById("theme-color-grid");
if (!grid) return;
grid.innerHTML = "";

for (var i = 0; i < themeColors.length; i++) {
var color = themeColors[i];
var item = document.createElement("div");
item.className = "theme-color-item";

if (isDarkColor(color.hex)) {
item.classList.add("light-check");
} else {
item.classList.add("dark-check");
}

item.style.backgroundColor = "#" + color.hex;
item.title = color.name;
item.setAttribute("data-hex", color.hex);

if (config.appearance.themeColor === color.hex) {
item.classList.add("active");
}

item.addEventListener("click", function () {
var all = document.querySelectorAll(".theme-color-item");
for (var j = 0; j < all.length; j++) all[j].classList.remove("active");
this.classList.add("active");
config.appearance.themeColor = this.getAttribute("data-hex");
});

grid.appendChild(item);
}
}

function saveSettings() {
saveConfig();
applyTheme();
showToast("已保存", "success");
}

function resetSettings() {
if (confirm("重置所有设置?")) {
config = getDefaultConfig();
saveConfig();
renderYAMLForm();
renderColorGrid();
renderFieldList();
renderThemeColorGrid();
applyTheme();
showToast("已重置", "success");
}
}

function showYAMLHelp() {
var content = '<div class="help-content">';
content += "<h4>📝 基本使用</h4>";
content += "<ul>";
content += "<li>填写文章属性(标题、标签等)</li>";
content += "<li>Tags 和 Categories 点击 <code>+</code> 添加多个</li>";
content += "<li>点击【复制到剪贴板】</li>";
content += "<li>在 Typora 中 <code>Ctrl+V</code> 粘贴</li>";
content += "</ul>";
content += "<h4>⚙️ 自定义属性</h4>";
content += "<ul>";
content += "<li>在【设置】中添加/删除/排序属性</li>";
content += "<li><strong>右键属性</strong> 可编辑默认值</li>";
content += "<li>Date 字段自动填充当前时间</li>";
content += "<li>最多支持 10 个属性</li>";
content += "</ul>";
content += '<div class="tip">💡 设置默认值后,每次打开自动填充</div>';
content += "<h4>📋 生成示例</h4>";
content +=
"<pre>---\ntitle: 文章标题\ndate: 2024-03-09 12:00:00\ntags:\n - 标签1\n - 标签2\ncategories:\n - 分类1\ncover: /img/cover.jpg\n---</pre>";
content += "<h4>🔒 受保护属性</h4>";
content +=
"<p>Title、Date、Tags、Categories、Cover 为核心属性,不可删除。</p>";
content += "</div>";
showHelpModal("YAML 生成器说明", content);
}

function showColorHelp() {
var content = '<div class="help-content">';
content += "<h4>🎨 基本使用</h4>";
content += "<ul>";
content += "<li>点击色块,复制颜色代码到剪贴板</li>";
content += "<li>在 Typora 中将文字放入标签内</li>";
content +=
"<li>示例:<code>&lt;font color='#FF0000'&gt;红色文字&lt;/font&gt;</code></li>";
content += "</ul>";
content += "<h4>📝 使用步骤</h4>";
content += "<ol>";
content += "<li>点击想要的颜色</li>";
content += "<li>在 Typora 中 <code>Ctrl+V</code> 粘贴</li>";
content += "<li>在标签中间输入文字</li>";
content += "</ol>";
content += "<h4>🎯 自定义颜色</h4>";
content += "<ul>";
content += "<li>输入 HEX 值(如 FF5500)</li>";
content += "<li>或输入 RGB 值(0-255)自动转换</li>";
content += "<li>填写颜色名称(可选)</li>";
content += "<li>点击【添加】保存到色板</li>";
content += "<li>点击【复制】直接使用</li>";
content += "</ul>";
content += '<div class="tip">💡 最多保存 18 个颜色</div>';
content += "<h4>🗑️ 管理颜色</h4>";
content += "<ul>";
content += "<li><strong>右键色块</strong> 可删除颜色或复制 HEX</li>";
content += "<li>点击【恢复默认】重置为默认 18 色</li>";
content += "</ul>";
content += "<h4>💡 小技巧</h4>";
content += "<ul>";
content += "<li>深色背景用浅色文字</li>";
content += "<li>浅色背景用深色文字</li>";
content += "<li>常用颜色建议添加到色板</li>";
content += "</ul>";
content += "</div>";
showHelpModal("字体颜色说明", content);
}

function showHelpModal(title, content) {
var overlay = document.getElementById("help-modal-overlay");
var titleEl = document.getElementById("help-modal-title");
var contentEl = document.getElementById("help-content");
if (!overlay || !titleEl || !contentEl) return;
titleEl.textContent = title;
contentEl.innerHTML = content;
overlay.classList.remove("hidden");
overlay.style.display = "flex";
}

function setupEventListeners() {
var btnCopy = document.getElementById("btn-copy");
if (btnCopy) btnCopy.addEventListener("click", copyYAML);

var btnPreview = document.getElementById("btn-preview");
if (btnPreview) {
btnPreview.addEventListener("click", function () {
var box = document.getElementById("yaml-preview");
var output = document.getElementById("yaml-output");
if (box && output) {
output.textContent = generateYAML();
box.classList.toggle("hidden");
}
});
}

var btnYamlHelp = document.getElementById("btn-yaml-help");
if (btnYamlHelp) btnYamlHelp.addEventListener("click", showYAMLHelp);

var btnRefreshPreview = document.getElementById("btn-refresh-preview");
if (btnRefreshPreview)
btnRefreshPreview.addEventListener("click", updateColorPreview);

var btnAddColor = document.getElementById("btn-add-color");
if (btnAddColor) btnAddColor.addEventListener("click", addCustomColor);

var btnUseColor = document.getElementById("btn-use-color");
if (btnUseColor) btnUseColor.addEventListener("click", useCustomColor);

var btnRestoreColors = document.getElementById("btn-restore-colors");
if (btnRestoreColors)
btnRestoreColors.addEventListener("click", restoreDefaultColors);

var btnColorHelp = document.getElementById("btn-color-help");
if (btnColorHelp) btnColorHelp.addEventListener("click", showColorHelp);

var rgbIds = ["custom-r", "custom-g", "custom-b"];
for (var i = 0; i < rgbIds.length; i++) {
var el = document.getElementById(rgbIds[i]);
if (el) el.addEventListener("input", updateHexFromRGB);
}

var customHex = document.getElementById("custom-hex");
if (customHex) customHex.addEventListener("input", updateColorPreview);

var btnAddField = document.getElementById("btn-add-field");
if (btnAddField) btnAddField.addEventListener("click", addField);

var newFieldName = document.getElementById("new-field-name");
if (newFieldName) {
newFieldName.addEventListener("keypress", function (e) {
if (e.key === "Enter") addField();
});
}

var btnSaveSettings = document.getElementById("btn-save-settings");
if (btnSaveSettings)
btnSaveSettings.addEventListener("click", saveSettings);

var btnResetSettings = document.getElementById("btn-reset-settings");
if (btnResetSettings)
btnResetSettings.addEventListener("click", resetSettings);

var refreshStatus = document.getElementById("refresh-status");
if (refreshStatus)
refreshStatus.addEventListener("click", checkTyporaStatus);

var modalClose = document.getElementById("modal-close");
if (modalClose) modalClose.addEventListener("click", closeModal);

var modalCancel = document.getElementById("modal-cancel");
if (modalCancel) modalCancel.addEventListener("click", closeModal);

var modalSave = document.getElementById("modal-save");
if (modalSave) modalSave.addEventListener("click", saveDefaultValue);

var modalOverlay = document.getElementById("modal-overlay");
if (modalOverlay) {
modalOverlay.addEventListener("click", function (e) {
if (e.target === modalOverlay) closeModal();
});
}

var helpModalClose = document.getElementById("help-modal-close");
if (helpModalClose)
helpModalClose.addEventListener("click", closeHelpModal);

var helpModalOk = document.getElementById("help-modal-ok");
if (helpModalOk) helpModalOk.addEventListener("click", closeHelpModal);

var helpModalOverlay = document.getElementById("help-modal-overlay");
if (helpModalOverlay) {
helpModalOverlay.addEventListener("click", function (e) {
if (e.target === helpModalOverlay) closeHelpModal();
});
}

document.addEventListener("keydown", function (e) {
if (e.key === "Escape") {
closeModal();
closeHelpModal();
removeContextMenu();
}
});
}

function isDarkColor(hex) {
hex = hex.replace("#", "");
var r = parseInt(hex.substr(0, 2), 16);
var g = parseInt(hex.substr(2, 2), 16);
var b = parseInt(hex.substr(4, 2), 16);
return 0.2126 * r + 0.7152 * g + 0.0722 * b < 128;
}

function getCurrentDateTime() {
if (window.services && window.services.getCurrentDateTime) {
return window.services.getCurrentDateTime();
}
var now = new Date();
var y = now.getFullYear();
var m = String(now.getMonth() + 1).padStart(2, "0");
var d = String(now.getDate()).padStart(2, "0");
var h = String(now.getHours()).padStart(2, "0");
var min = String(now.getMinutes()).padStart(2, "0");
var s = String(now.getSeconds()).padStart(2, "0");
return y + "-" + m + "-" + d + " " + h + ":" + min + ":" + s;
}

function copyToClipboard(text) {
if (window.services && window.services.copyToClipboard) {
window.services.copyToClipboard(text);
return;
}
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(text);
return;
}
var textarea = document.createElement("textarea");
textarea.value = text;
textarea.style.position = "fixed";
textarea.style.left = "-9999px";
document.body.appendChild(textarea);
textarea.select();
document.execCommand("copy");
document.body.removeChild(textarea);
}

function escapeHtml(text) {
var div = document.createElement("div");
div.textContent = text;
return div.innerHTML;
}

function showToast(message, type) {
var toast = document.getElementById("toast");
if (!toast) return;
toast.textContent = message;
toast.className = "toast";
if (type) toast.classList.add(type);
setTimeout(function () {
toast.classList.add("hidden");
}, 2000);
}

function removeContextMenu() {
var existing = document.querySelector(".context-menu");
if (existing && existing.parentNode) existing.remove();
}

function applyTheme() {
var themeColor = config.appearance.themeColor || "007bff";
document.documentElement.style.setProperty(
"--theme-accent",
"#" + themeColor,
);

var r = parseInt(themeColor.substr(0, 2), 16);
var g = parseInt(themeColor.substr(2, 2), 16);
var b = parseInt(themeColor.substr(4, 2), 16);

document.documentElement.style.setProperty(
"--theme-bg",
"rgba(" + r + "," + g + "," + b + ",0.03)",
);
document.documentElement.style.setProperty(
"--theme-bg-light",
"rgba(" + r + "," + g + "," + b + ",0.08)",
);
document.documentElement.style.setProperty(
"--theme-border",
"rgba(" + r + "," + g + "," + b + ",0.2)",
);
}
})();

⑥ style.css

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
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
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
:root {
--theme-accent: #007bff;
--theme-bg: #ffffff;
--theme-bg-light: #f8f9fa;
--theme-bg-lighter: #ffffff;
--theme-text: #333333;
--theme-text-light: #666666;
--theme-border: #e0e0e0;
--success-color: #28a745;
--warning-color: #ffc107;
--danger-color: #dc3545;
--shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}

* {
box-sizing: border-box;
margin: 0;
padding: 0;
}

html,
body {
height: 100%;
overflow: hidden;
}

body {
font-family: 'Microsoft YaHei UI', 'Segoe UI', sans-serif;
background: linear-gradient(135deg, var(--theme-bg) 0%, var(--theme-bg-light) 100%);
color: var(--theme-text);
font-size: 14px;
line-height: 1.5;
}

#app {
width: 100%;
height: 100%;
max-width: 480px;
max-height: 580px;
margin: 0 auto;
padding: 12px;
display: flex;
flex-direction: column;
overflow: hidden;
}

.typora-status {
display: flex;
align-items: center;
gap: 8px;
padding: 8px 12px;
margin-bottom: 10px;
background: var(--theme-bg-lighter);
border-radius: 8px;
font-size: 12px;
border: 1px solid var(--theme-border);
box-shadow: var(--shadow);
flex-shrink: 0;
}

.typora-status .status-dot {
width: 8px;
height: 8px;
border-radius: 50%;
background: #dc3545;
flex-shrink: 0;
}

.typora-status .status-dot.online {
background: #28a745;
box-shadow: 0 0 6px #28a745;
animation: pulse 2s infinite;
}

@keyframes pulse {

0%,
100% {
opacity: 1;
}

50% {
opacity: 0.5;
}
}

.typora-status .status-text {
flex: 1;
color: var(--theme-text-light);
}

.typora-status .refresh-btn {
padding: 2px 6px;
font-size: 12px;
background: transparent;
border: 1px solid var(--theme-border);
border-radius: 4px;
cursor: pointer;
}

.typora-status .refresh-btn:hover {
background: var(--theme-accent);
color: white;
border-color: var(--theme-accent);
}

.nav-tabs {
display: flex;
gap: 6px;
margin-bottom: 12px;
background: var(--theme-bg-lighter);
padding: 5px;
border-radius: 10px;
box-shadow: var(--shadow);
flex-shrink: 0;
}

.tab-btn {
flex: 1;
padding: 8px 12px;
border: none;
background: transparent;
color: var(--theme-text);
font-size: 13px;
cursor: pointer;
border-radius: 6px;
transition: all 0.2s;
font-weight: 500;
}

.tab-btn:hover {
background: var(--theme-bg-light);
}

.tab-btn.active {
background: var(--theme-accent);
color: white;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15);
}

.panel {
display: none;
flex-direction: column;
flex: 1;
min-height: 0;
animation: fadeIn 0.2s ease;
}

.panel.active {
display: flex;
}

@keyframes fadeIn {
from {
opacity: 0;
}

to {
opacity: 1;
}
}

.panel-content {
flex: 1;
overflow-y: auto;
overflow-x: hidden;
padding-right: 4px;
}

.panel-content::-webkit-scrollbar {
width: 6px;
}

.panel-content::-webkit-scrollbar-track {
background: transparent;
}

.panel-content::-webkit-scrollbar-thumb {
background: var(--theme-border);
border-radius: 3px;
}

.panel-content::-webkit-scrollbar-thumb:hover {
background: var(--theme-accent);
}

.panel-footer {
flex-shrink: 0;
padding-top: 12px;
border-top: 1px solid var(--theme-border);
margin-top: 12px;
}

.panel-header {
margin-bottom: 12px;
}

.panel-header h2 {
font-size: 16px;
font-weight: 600;
color: var(--theme-accent);
margin-bottom: 4px;
}

.panel-header .hint {
font-size: 11px;
color: var(--theme-text-light);
padding: 8px 10px;
background: linear-gradient(135deg, rgba(0, 123, 255, 0.08), rgba(0, 123, 255, 0.03));
border-radius: 6px;
border-left: 3px solid var(--theme-accent);
margin-top: 6px;
}

.hint-inline {
font-size: 11px;
color: var(--theme-text-light);
font-weight: normal;
}

.form-container {
background: var(--theme-bg-lighter);
border-radius: 10px;
padding: 12px;
border: 1px solid var(--theme-border);
box-shadow: var(--shadow);
}

.form-group {
margin-bottom: 12px;
}

.form-group:last-child {
margin-bottom: 0;
}

.form-group label {
display: block;
font-weight: 600;
margin-bottom: 4px;
font-size: 12px;
color: var(--theme-accent);
}

.form-group input[type="text"] {
width: 100%;
padding: 8px 10px;
border: 1px solid var(--theme-border);
border-radius: 6px;
font-size: 13px;
background: var(--theme-bg);
color: var(--theme-text);
transition: all 0.2s;
}

.form-group input:focus {
outline: none;
border-color: var(--theme-accent);
box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.1);
}

.list-field {
display: flex;
flex-direction: column;
gap: 6px;
}

.list-items {
display: flex;
flex-wrap: wrap;
gap: 4px;
min-height: 32px;
padding: 6px;
background: var(--theme-bg);
border: 1px solid var(--theme-border);
border-radius: 6px;
}

.list-item {
display: inline-flex;
align-items: center;
gap: 4px;
padding: 3px 8px;
background: var(--theme-accent);
color: white;
border-radius: 12px;
font-size: 11px;
font-weight: 500;
}

.list-item .remove-btn {
cursor: pointer;
font-size: 12px;
line-height: 1;
opacity: 0.8;
}

.list-item .remove-btn:hover {
opacity: 1;
}

.list-input-row {
display: flex;
gap: 6px;
}

.list-input-row input {
flex: 1;
padding: 6px 10px;
border: 1px solid var(--theme-border);
border-radius: 6px;
background: var(--theme-bg);
color: var(--theme-text);
font-size: 12px;
}

.list-input-row input:focus {
outline: none;
border-color: var(--theme-accent);
}

.btn {
padding: 8px 14px;
border: none;
border-radius: 6px;
font-size: 13px;
cursor: pointer;
transition: all 0.2s;
font-weight: 500;
display: inline-flex;
align-items: center;
justify-content: center;
gap: 4px;
}

.btn:hover {
transform: translateY(-1px);
box-shadow: 0 3px 8px rgba(0, 0, 0, 0.15);
}

.btn:active {
transform: translateY(0);
}

.btn-primary {
background: var(--theme-accent);
color: white;
}

.btn-secondary {
background: var(--theme-bg-light);
color: var(--theme-text);
border: 1px solid var(--theme-border);
}

.btn-secondary:hover {
background: var(--theme-border);
}

.btn-success {
background: var(--success-color);
color: white;
}

.btn-warning {
background: var(--warning-color);
color: #333;
}

.btn-danger {
background: var(--danger-color);
color: white;
}

.btn-info {
background: var(--theme-accent);
color: white;
opacity: 0.85;
}

.btn-info:hover {
opacity: 1;
}

.btn-small {
padding: 5px 10px;
font-size: 12px;
}

.btn-group {
display: flex;
gap: 8px;
flex-wrap: wrap;
}

.preview-box {
margin-top: 10px;
background: #1e1e1e;
border-radius: 8px;
padding: 12px;
max-height: 120px;
overflow-y: auto;
}

.preview-box pre {
color: #d4d4d4;
font-family: 'Consolas', 'Monaco', monospace;
font-size: 11px;
white-space: pre-wrap;
margin: 0;
}

.hidden {
display: none !important;
}

.color-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 8px;
margin-bottom: 14px;
}

.color-block {
padding: 10px 6px;
border-radius: 8px;
text-align: center;
cursor: pointer;
font-size: 11px;
font-weight: 500;
transition: all 0.2s;
border: 2px solid transparent;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}

.color-block:hover {
transform: scale(1.05);
border-color: var(--theme-accent);
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.2);
}

.color-block:active {
transform: scale(0.98);
}

.color-block.light-text {
color: white;
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.3);
}

.color-block.dark-text {
color: #333;
}

.custom-color-section {
background: var(--theme-bg-lighter);
border-radius: 10px;
padding: 12px;
border: 1px solid var(--theme-border);
box-shadow: var(--shadow);
}

.custom-color-section h3 {
font-size: 13px;
margin-bottom: 10px;
color: var(--theme-accent);
font-weight: 600;
}

.custom-color-form {
display: flex;
flex-direction: column;
gap: 8px;
margin-bottom: 10px;
}

.input-group {
display: flex;
align-items: center;
gap: 6px;
}

.input-group label {
min-width: 45px;
font-size: 12px;
color: var(--theme-text-light);
}

.input-group input {
flex: 1;
padding: 6px 8px;
border: 1px solid var(--theme-border);
border-radius: 5px;
font-size: 12px;
background: var(--theme-bg);
color: var(--theme-text);
}

.input-group input:focus {
outline: none;
border-color: var(--theme-accent);
}

.input-group input[type="number"] {
width: 50px;
flex: none;
text-align: center;
}

.color-preview-box {
margin-top: 4px;
}

#color-preview {
width: 100%;
height: 30px;
border-radius: 6px;
border: 2px solid var(--theme-border);
background-color: #FF0000;
}

.settings-section {
background: var(--theme-bg-lighter);
border-radius: 10px;
padding: 12px;
margin-bottom: 12px;
border: 1px solid var(--theme-border);
box-shadow: var(--shadow);
}

.settings-section:last-child {
margin-bottom: 0;
}

.settings-section h3 {
font-size: 13px;
margin-bottom: 10px;
padding-bottom: 8px;
border-bottom: 1px solid var(--theme-border);
color: var(--theme-accent);
font-weight: 600;
}

.field-list {
display: flex;
flex-direction: column;
gap: 6px;
margin-bottom: 10px;
max-height: 140px;
overflow-y: auto;
}

.field-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 8px 10px;
background: var(--theme-bg);
border-radius: 6px;
border: 1px solid var(--theme-border);
cursor: context-menu;
transition: all 0.2s;
}

.field-item:hover {
border-color: var(--theme-accent);
}

.field-item .field-name {
font-weight: 500;
font-size: 12px;
}

.field-item .field-default {
font-size: 10px;
color: var(--theme-text-light);
margin-left: 6px;
}

.field-item .field-actions {
display: flex;
gap: 4px;
}

.field-item .field-actions button {
padding: 2px 6px;
font-size: 11px;
border: none;
background: transparent;
cursor: pointer;
border-radius: 3px;
}

.field-item .field-actions button:hover {
background: var(--theme-bg-light);
}

.field-controls {
display: flex;
gap: 6px;
}

.field-controls input {
flex: 1;
padding: 6px 10px;
border: 1px solid var(--theme-border);
border-radius: 5px;
background: var(--theme-bg);
color: var(--theme-text);
font-size: 12px;
}

.field-controls input:focus {
outline: none;
border-color: var(--theme-accent);
}

.theme-color-section {
margin-top: 8px;
}

.theme-color-section>label {
display: block;
font-size: 12px;
margin-bottom: 8px;
font-weight: 500;
color: var(--theme-text);
}

.theme-color-grid {
display: grid;
grid-template-columns: repeat(5, 1fr);
gap: 6px;
}

.theme-color-item {
width: 100%;
aspect-ratio: 1;
border-radius: 6px;
cursor: pointer;
border: 2px solid transparent;
transition: all 0.2s;
position: relative;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}

.theme-color-item:hover {
transform: scale(1.1);
z-index: 1;
}

.theme-color-item.active {
border-color: var(--theme-text);
box-shadow: 0 3px 8px rgba(0, 0, 0, 0.2);
}

.theme-color-item.active::after {
content: '✓';
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 14px;
font-weight: bold;
}

.theme-color-item.light-check::after {
color: white;
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.5);
}

.theme-color-item.dark-check::after {
color: #333;
}

.toast {
position: fixed;
bottom: 20px;
left: 50%;
transform: translateX(-50%);
padding: 10px 20px;
background: var(--theme-text);
color: white;
border-radius: 8px;
font-size: 13px;
z-index: 99999;
animation: toastIn 0.3s ease;
max-width: 85%;
text-align: center;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.3);
}

.toast.success {
background: var(--success-color);
}

.toast.error {
background: var(--danger-color);
}

.toast.warning {
background: var(--warning-color);
color: #333;
}

@keyframes toastIn {
from {
opacity: 0;
transform: translateX(-50%) translateY(15px);
}

to {
opacity: 1;
transform: translateX(-50%) translateY(0);
}
}

.context-menu {
position: fixed;
background: #fff;
border: 1px solid #ddd;
border-radius: 8px;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
z-index: 99998;
overflow: hidden;
min-width: 130px;
}

.context-menu-item {
padding: 8px 14px;
cursor: pointer;
font-size: 12px;
display: flex;
align-items: center;
gap: 6px;
color: #333;
background: #fff;
}

.context-menu-item:hover {
background: #f5f5f5;
}

.context-menu-item.danger {
color: var(--danger-color);
}

.context-menu-divider {
height: 1px;
background: #eee;
margin: 3px 0;
}

.modal-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 99999;
}

.modal-overlay.hidden {
display: none;
}

.modal {
background: #fff;
border-radius: 12px;
width: 90%;
max-width: 360px;
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.3);
animation: modalIn 0.25s ease;
overflow: hidden;
}

.modal-help {
max-width: 420px;
max-height: 80vh;
display: flex;
flex-direction: column;
}

@keyframes modalIn {
from {
opacity: 0;
transform: scale(0.9);
}

to {
opacity: 1;
transform: scale(1);
}
}

.modal-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 12px 16px;
background: var(--theme-accent);
color: white;
flex-shrink: 0;
}

.modal-header h3 {
font-size: 14px;
margin: 0;
font-weight: 600;
}

.modal-close {
background: none;
border: none;
font-size: 20px;
cursor: pointer;
color: white;
opacity: 0.8;
line-height: 1;
padding: 0;
width: 24px;
height: 24px;
}

.modal-close:hover {
opacity: 1;
}

.modal-body {
padding: 16px;
background: #fff;
color: #333;
font-size: 13px;
}

.modal-body-scroll {
overflow-y: auto;
max-height: 400px;
}

.modal-body-scroll::-webkit-scrollbar {
width: 6px;
}

.modal-body-scroll::-webkit-scrollbar-track {
background: #f0f0f0;
border-radius: 3px;
}

.modal-body-scroll::-webkit-scrollbar-thumb {
background: #ccc;
border-radius: 3px;
}

.modal-body-scroll::-webkit-scrollbar-thumb:hover {
background: var(--theme-accent);
}

.modal-body p {
margin-bottom: 12px;
color: #333;
}

.modal-body .hint {
font-size: 11px;
color: #666;
margin-top: 10px;
padding: 8px 10px;
background: #f5f5f5;
border-radius: 6px;
border-left: 3px solid var(--theme-accent);
}

.modal-input-group {
margin-bottom: 10px;
}

.modal-input-group label {
display: block;
font-size: 12px;
margin-bottom: 5px;
font-weight: 500;
color: #333;
}

.modal-input-group textarea,
.modal-input-group input {
width: 100%;
padding: 10px 12px;
border: 1px solid #ddd;
border-radius: 6px;
font-size: 14px;
background: #fff;
color: #333;
resize: vertical;
font-family: inherit;
line-height: 1.5;
}

.modal-input-group textarea {
min-height: 80px;
}

.modal-input-group textarea:focus,
.modal-input-group input:focus {
outline: none;
border-color: var(--theme-accent);
box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.15);
}

.modal-footer {
display: flex;
justify-content: flex-end;
gap: 10px;
padding: 12px 16px;
border-top: 1px solid #eee;
background: #fafafa;
flex-shrink: 0;
}

.help-content {
line-height: 1.7;
font-size: 13px;
}

.help-content h4 {
margin: 14px 0 8px;
color: var(--theme-accent);
font-size: 13px;
font-weight: 600;
}

.help-content h4:first-child {
margin-top: 0;
}

.help-content ul {
padding-left: 18px;
margin: 6px 0;
}

.help-content li {
margin: 4px 0;
}

.help-content code {
background: #f0f0f0;
padding: 1px 5px;
border-radius: 3px;
font-family: 'Consolas', monospace;
font-size: 12px;
color: var(--theme-accent);
}

.help-content .tip {
background: rgba(40, 167, 69, 0.1);
border-left: 3px solid var(--success-color);
padding: 8px 10px;
margin: 10px 0;
border-radius: 0 6px 6px 0;
font-size: 12px;
}

.help-content pre {
background: #1e1e1e;
color: #d4d4d4;
padding: 10px;
border-radius: 6px;
font-size: 11px;
overflow-x: auto;
margin: 8px 0;
}

打包成upxs插件自用

image-20260309231118078

image-20260309231201509

之前第一次不会用这个直接上传了结果没发现居然不能用没通过审核,现在使用前端重新写了这个软件这次应该能过…..把?

image-20260309231351632

image-20260309231413099

估计过两天应该就通过了