Compare commits

...

383 Commits

Author SHA1 Message Date
8aeec60181 optimize code and update package version 2024-08-31 20:01:52 +08:00
58d3e4f645 optimize code and update package version 2024-08-21 21:11:27 +08:00
e592a753bf add some func 2024-06-24 21:19:24 +08:00
d598fb5ce5 add some func 2024-06-23 21:30:57 +08:00
63f769796f add some func 2024-06-23 19:18:54 +08:00
7cbfdf8601 update and add func 2024-06-22 11:32:48 +08:00
9c60d10568 optimize sign mechanism 2024-06-19 16:10:42 +08:00
dcbe760f09 optimize code 2024-06-12 22:38:49 +08:00
d220bb2a6c optimize code 2024-06-12 17:51:47 +08:00
eb7ccd4f2d remake safety slice 2024-06-12 00:14:07 +08:00
39c9dc1b09 add rwmap 2024-06-11 22:10:34 +08:00
68beb0fcbb optimize code 2024-06-11 02:21:57 +08:00
1f5f7f465d add httptool.BodyBuffer 2024-06-10 22:50:18 +08:00
b7c09cb5a2 add zeroDefault func and optimize httptool and update package 2024-06-10 17:44:41 +08:00
5942f76972 fix bug 2024-06-06 22:59:03 +08:00
1f40810b78 optimize wp reg route 2024-04-18 11:48:49 +08:00
da858d55e0 fix bug 2024-04-18 11:26:58 +08:00
166bb08ed0 optimize code and add hook gin action and set noroute 2024-04-18 01:39:36 +08:00
5eaf798a6c optimize reload schema and optimize cachemanager package 2024-04-15 15:25:19 +08:00
e2e6bcc8ce fix bug 2024-04-14 23:08:22 +08:00
daa7101493 optimize reload package add append executed once func when reload and add hook scheme 2024-04-14 22:49:40 +08:00
6f51f1c295 update docker golang version 2024-04-13 22:12:20 +08:00
a8d1dcdd5b optimize code and fix bug 2024-04-10 23:48:23 +08:00
9af2257650 fix bug 2024-04-10 22:41:26 +08:00
63591899bb optimize code 2024-04-10 22:31:08 +08:00
ee9ba3fcf0 revise digest add more complex config 2024-04-09 23:51:45 +08:00
a78815f3d3 revise digest add set tag occupied number 2024-04-07 21:30:57 +08:00
6209f2b5e5 optimize digest 2024-04-07 20:02:39 +08:00
08d3bca39c upgrade dependency packages 2024-04-06 20:41:45 +08:00
9b8e597066 remove SetSqlxDB func 2024-04-06 16:44:56 +08:00
3215b0c8ea add get customize config func and add wpHandle add err level 2024-04-06 16:43:18 +08:00
c7c97c469f optimize code 2024-04-05 16:55:34 +08:00
b45ad0e54e fix enlighterjs config 2024-04-05 16:23:59 +08:00
a0c6e48e83 add customheader err measure 2024-04-05 11:53:44 +08:00
3c3ed73971 db use context method 2024-03-27 13:09:46 +08:00
be58a35245 Merge remote-tracking branch 'origin/master'
# Conflicts:
#	README.md
#	readme_en.md
2024-03-22 12:24:51 +08:00
67bdf1e070 change logo size 2024-03-22 12:23:34 +08:00
981e0ad85b change logo size 2024-03-22 12:21:59 +08:00
74159940b5 add thanks jetbrains product license support 2024-03-22 12:14:12 +08:00
206e5902d1 add running description 2024-03-21 17:09:11 +08:00
2d38b36525 optimize pagination interface and update dependence package 2024-03-18 19:12:16 +08:00
af712f06a5 fix bug and optimize code 2024-01-25 14:12:51 +08:00
73575965d3 update docker go version 2024-01-24 15:25:12 +08:00
f49ad731e3 optimize code and fix bug 2024-01-24 15:13:29 +08:00
802c7c7dcc optimize code 2024-01-24 00:25:37 +08:00
20ea36c727 fix bug and optimize code and update dependence package version 2024-01-23 22:59:15 +08:00
fd3199a83c remove sitetest 2024-01-22 21:14:16 +08:00
e25f679ea6 optimize code 2024-01-22 20:29:17 +08:00
4842d53316 fix bug and optimize code 2024-01-21 21:29:36 +08:00
65bfccdd48 fix bug 2024-01-19 22:36:44 +08:00
c60146d614 optimize code and fix bug 2024-01-19 15:18:55 +08:00
9285698ee4 rename a func 2024-01-19 00:14:04 +08:00
5e18c9babd optimize code, fix bug and add some comments 2024-01-18 23:29:50 +08:00
8bdc53d175 fix bug 2024-01-15 13:25:29 +08:00
ad9bdc7574 optimize code 2024-01-14 22:07:16 +08:00
0352dd0fd0 fix bug, optimize code, add some cache comments, add middleware,change pagination nav to mix url plain 2024-01-13 21:15:54 +08:00
6044b8eaec perfect comments relates display and optimize some code 2024-01-10 23:50:23 +08:00
8d3197ee98 optimize 2024-01-05 00:01:57 +08:00
8fcf3ecca2 fix bug 2024-01-04 23:36:00 +08:00
4d9d011213 remove commentIncreaseUpdate simplify comment cache 2024-01-04 23:32:10 +08:00
12b75b9d82 optimize mutex lock fn 2023-12-28 20:42:11 +08:00
568ab15a34 map cache add mutex lock fn 2023-12-26 23:09:30 +08:00
c38b62c82a fix bug 2023-12-24 00:20:35 +08:00
0774b122ee fix bug 2023-12-23 22:49:34 +08:00
f7d2377101 optimize pagination interface and add twentyseventeen comments pagination nav 2023-12-23 20:17:42 +08:00
088fc306de fix bug,improve pagination cache and revise comment render 2023-12-20 22:30:55 +08:00
57d345e445 fix var memory cache bug 2023-12-17 19:34:04 +08:00
f257a966e6 fix pre comment render 2023-12-13 11:26:22 +08:00
ceffeccf8d fix comment render bug 2023-12-12 18:59:08 +08:00
eed45f51ba fix bug and add redis cache drive example 2023-12-11 22:32:00 +08:00
d72bed0c8c refine cache 2023-12-10 19:15:49 +08:00
0cdb3ba040 refine pagination cache 2023-12-10 17:32:49 +08:00
227de8bdc8 add pagination cache 2023-12-08 21:33:09 +08:00
74304b5b12 fix slice.range,pipe middleware bug, add slice.simpleOrder func 2023-12-05 21:39:13 +08:00
ac29cf2448 code optimize 2023-12-03 22:42:44 +08:00
137b2a9738 var cache increaseUpdate 2023-11-29 18:24:41 +08:00
9f49a274cd fix bugs 2023-11-29 00:00:25 +08:00
7c3f8baaa2 map cache add increase interface 2023-11-28 22:46:22 +08:00
547d8e59e6 optimize cache add set cache function 2023-11-25 17:49:20 +08:00
7bbc961f72 optimize 2023-11-16 22:52:14 +08:00
66c02f7fc9 update dependent package 2023-11-13 22:53:20 +08:00
a5294e2e20 revise reload package 2023-11-12 21:39:04 +08:00
7978e9ee74 optimize 2023-11-07 15:39:38 +08:00
f6a5cf4255 optimize 2023-11-07 15:36:00 +08:00
928a608878 change package name 2023-11-07 15:19:44 +08:00
42d2795a05 map cache add func get map 2023-11-07 15:18:34 +08:00
041d06104b optimize cachemanger improve var cache 2023-11-02 22:40:13 +08:00
d1fb560578 fix bug and test set expireTime 2023-10-31 19:23:38 +08:00
42484e3f96 fix 2023-10-30 22:21:08 +08:00
64a2c2e33b optimize and expand map cache remove ver add dynamic set expired time 2023-10-30 21:52:15 +08:00
86d1616732 fix bug 2023-10-29 18:58:06 +08:00
2eb58f732b expend cache map 2023-10-29 18:46:01 +08:00
936d033547 fix 2023-10-28 18:40:21 +08:00
cd4787991d optimize optimize...... 2023-10-28 18:37:00 +08:00
71ddf299e4 optimize again again...... 2023-10-28 15:19:39 +08:00
0f467bbc98 optimize again 2023-10-28 15:03:14 +08:00
171b3bc59c optimize again 2023-10-28 14:50:06 +08:00
f369cf4f22 optimize 2023-10-28 13:30:32 +08:00
ddf6e62e5a fix 2023-10-28 00:08:04 +08:00
e502f581e1 fix cache bug 2023-10-27 23:57:15 +08:00
acb064b762 improve cache interface and memorymapcache.go implement 2023-10-27 20:51:46 +08:00
f0c1744998 fix 2023-10-26 21:45:02 +08:00
876a81c062 improve map cache and enhance cachemanager add delete element and get any val 2023-10-26 21:38:31 +08:00
78f5157efe fix 2023-10-24 16:07:48 +08:00
acbcbf455f improve 2023-10-24 16:06:59 +08:00
179545f2bb reload_test.go 2023-10-24 16:05:33 +08:00
48a3fe6588 fix 2023-10-23 22:47:21 +08:00
3fd4f412b1 improve 2023-10-23 22:38:52 +08:00
5273f4ecf9 reload optimize add flush single element 2023-10-23 22:32:20 +08:00
b9c2527f4c update package 2023-10-14 19:57:14 +08:00
1286338af0 http tool, fix digest, reload add sort 2023-10-14 14:57:57 +08:00
21a4ff3b3e http tool get 2023-10-04 21:48:21 +08:00
2302aa7ff8 fix 2023-08-26 22:03:49 +08:00
bc71be7238 func rename and little optimize 2023-08-26 22:01:20 +08:00
a4274b74dd 优化 maps setstrval 2023-06-30 00:06:26 +08:00
03b448e70c set val 2023-06-29 23:29:25 +08:00
879238cda1 map RecursiveSetStrVal 2023-06-28 22:49:45 +08:00
a00bfff91c no route 2023-06-18 19:47:47 +08:00
96daa93656 delete static file 2023-06-16 19:26:56 +08:00
173eef2adc 去掉googleapi.css 2023-06-15 18:30:42 +08:00
c5c3d5f78b 使用gin 1.91 2023-06-15 18:18:57 +08:00
228e19ee85 order数值调整 2023-06-15 18:00:57 +08:00
b1ebedd24c fix bug 2023-06-13 12:03:52 +08:00
282e0c26e0 fix bug 2023-06-13 12:02:46 +08:00
e3d57830b0 fix bug 2023-06-13 00:17:15 +08:00
937e766294 tweentysevent 页眉媒体支持视频 2023-06-12 23:08:46 +08:00
19c0952005 关联已查询的结果 2023-06-09 22:28:23 +08:00
3493eee919 fix 2023-06-01 22:46:59 +08:00
e4fa93e893 优化 yaml本身就兼容json.... 2023-06-01 22:46:46 +08:00
a08b9538ae 完善 2023-06-01 21:30:14 +08:00
7431823ac7 config json转yaml 2023-06-01 15:06:57 +08:00
23c9cc1596 优化 2023-05-31 23:12:16 +08:00
ae6e496dd8 配置可为url,添加wordpress目录为静态资源目录并限制.php后缀 2023-05-31 21:54:23 +08:00
e408efacc8 注释 2023-05-31 12:56:57 +08:00
448d6a6baf 完善 2023-05-30 23:49:05 +08:00
b6091c6b42 注释 2023-05-30 20:04:05 +08:00
f2d69196bc 加个接口 2023-05-30 20:02:52 +08:00
b41b6e0a09 Merge branch 'query_middle_table'
# Conflicts:
#	model/relation.go
#	model/relation_test.go
2023-05-29 19:44:13 +08:00
fe9ac0d126 测试 2023-05-29 19:40:11 +08:00
1c33665e34 query remote relation model 2023-05-27 14:42:14 +08:00
bafd839637 小优 2023-05-24 21:58:30 +08:00
b4d3a41459 Revert "小优"
This reverts commit 4705d17e0e.
2023-05-24 21:57:28 +08:00
4705d17e0e 小优 2023-05-24 21:57:02 +08:00
60fd016b5f query 套娃测试 2023-05-24 21:41:49 +08:00
95e3298b7e db middle table 2023-05-24 21:33:30 +08:00
d5a546c01a db middle table 2023-05-24 20:41:24 +08:00
9580678b55 注释 2023-05-22 22:06:22 +08:00
2d90b05cf8 小优 2023-05-22 14:49:35 +08:00
0827cdc551 优化完善 加注释 2023-05-21 21:30:00 +08:00
7c50f2b1a1 优化完善 2023-05-21 19:53:37 +08:00
c95fd7e5da has one/many complete 2023-05-21 01:38:19 +08:00
4242d850ff 优化 hasone/many不用反射 2023-05-19 23:14:58 +08:00
6caf07b575 hasone hasmany 改 2023-05-18 22:27:28 +08:00
044e55a399 hasone hasmany 待完善 2023-05-17 22:22:31 +08:00
a6ee333232 完善 2023-05-13 18:52:19 +08:00
1353541c94 配置 2023-05-13 18:51:49 +08:00
8f995d23dc 额外引入脚本 2023-05-13 18:50:10 +08:00
871649a7e9 en readme 2023-05-12 14:27:31 +08:00
debcad6292 en readme 2023-05-11 13:40:28 +08:00
65a0ca94f6 stable sort 2023-05-10 17:02:58 +08:00
40451970a0 小优化 2023-05-10 14:28:21 +08:00
dfe7bb3181 调整文档列表的插件 2023-05-10 13:18:47 +08:00
b0618cf800 优化文档列表的插件机制 2023-05-09 13:44:20 +08:00
21d0b8c041 小调整 2023-05-09 00:56:28 +08:00
471a58658f fix bug 2023-05-09 00:44:56 +08:00
68c345f928 优化代码 2023-05-08 22:24:39 +08:00
4058ff9b7b 优化代码 2023-05-08 21:43:20 +08:00
036f1a1212 路由小优化 2023-05-07 22:56:23 +08:00
87f639da58 示例 2023-05-06 23:00:43 +08:00
ec211e8970 路由 2023-05-06 22:57:34 +08:00
b69b01f27b 优化完善 2023-05-05 20:45:20 +08:00
7c571654a6 完善 2023-05-04 21:44:49 +08:00
90d2432575 完善 2023-05-04 21:44:17 +08:00
972ff7d815 添加示例 2023-05-04 21:39:29 +08:00
2d2c6445e8 完善 2023-05-04 21:06:27 +08:00
f9b565294f 调整目录 2023-05-04 20:37:49 +08:00
b87a1b4a1c 修改包名 2023-05-04 20:37:06 +08:00
625da92f10 修改包名 2023-05-04 20:36:17 +08:00
0d3481f722 so插件 2023-05-04 19:46:06 +08:00
4323c508ba 优化 hook pipe 2023-05-03 23:57:49 +08:00
3ba2c02db5 fix bug 2023-05-01 13:34:23 +08:00
2d61243ef1 fix bug 2023-04-30 21:33:59 +08:00
120c874a8d 完善 components 2023-04-30 21:17:33 +08:00
da7dd410cd 补漏 2023-04-29 22:08:26 +08:00
43ec093e4c 优化代码 components hook 2023-04-29 22:05:24 +08:00
43754377e8 小优化 2023-04-25 21:05:10 +08:00
3f299e5a84 小优化 2023-04-25 20:58:22 +08:00
4760f65b9b 优化 2023-04-24 21:51:43 +08:00
345cdcd4e0 评论表单校验 2023-04-23 22:56:02 +08:00
72bad0fa36 宇宙究极无知hook handle 2023-04-22 01:12:39 +08:00
023212ba7b 优化 2023-04-19 15:50:22 +08:00
8a9209196e 优化完善 2023-04-19 15:27:58 +08:00
8da369b166 摘要调整 2023-04-18 21:05:37 +08:00
9e71de8e97 格式化 2023-04-17 20:39:08 +08:00
f735211a92 完善 2023-04-17 17:24:42 +08:00
5266e85f21 优化 2023-04-16 16:37:27 +08:00
98cee2f18b body class 2023-04-16 00:57:50 +08:00
b53819f733 重写body class机制 2023-04-15 13:22:21 +08:00
125764711d 优化 2023-04-11 13:42:14 +08:00
db49f99c04 reload时close db 2023-04-11 13:11:21 +08:00
0d8ee89580 完善添加评论 2023-04-10 22:28:16 +08:00
e505ee2e03 重构列表文档页插件 优化日志及去掉一些无用代码 2023-04-09 21:51:12 +08:00
df7bd8c1c6 完善 enlightjs插件 2023-04-09 14:21:59 +08:00
9e293e7f39 错误日志路径及优化 2023-04-07 22:59:07 +08:00
1f3ca99441 日志显示文件和行数 2023-04-06 21:13:02 +08:00
c09f54b9c0 db reload and fix bug 2023-04-05 20:54:38 +08:00
78f0c0a87a 修复bug reload升级 handlecall 拉伸 2023-04-04 23:02:54 +08:00
1ecb338af5 主题配置复制 2023-04-02 23:37:12 +08:00
7e55253126 小优化 2023-03-31 14:33:09 +08:00
e7e643f5d3 修复customlogo和归档的并发读写问题 2023-03-30 19:50:29 +08:00
522a358819 修复并发读写错误 todo customlogo还没处理完全 2023-03-30 00:46:33 +08:00
96368234de 去掉页眉缓存时间配置 2023-03-29 23:31:16 +08:00
49e4210365 class 2023-03-29 22:00:35 +08:00
54448f68b0 顶置 2023-03-29 21:58:39 +08:00
e10dc4e45e 优化widget参数 2023-03-28 21:45:20 +08:00
13520a0d43 说明 2023-03-27 22:23:18 +08:00
bebe6bac81 完善 2023-03-27 22:03:01 +08:00
a1f36da904 block版 分类 2023-03-27 12:59:29 +08:00
6f800230b0 优化 完善 2023-03-27 11:37:24 +08:00
e1c1da6083 小优化 2023-03-20 23:41:57 +08:00
ab56f45790 函数重命名 2023-03-20 13:02:40 +08:00
341b7197b8 优化代码 2023-03-19 22:48:23 +08:00
26bdcb44ac 优化 2023-03-19 22:14:42 +08:00
e7ea2bf334 fix bug 2023-03-19 20:53:27 +08:00
d48a156983 fix bug 及优化 2023-03-19 20:40:08 +08:00
00c16c03a4 小优化 2023-03-18 22:26:01 +08:00
ec4dfad86a 优化 fix bug 2023-03-18 21:02:24 +08:00
322d2ebe0a 优化代码 2023-03-18 13:17:21 +08:00
57a21010a8 优化 fix bug 2023-03-17 19:51:53 +08:00
14aad9e15b 优化代码 2023-03-17 18:26:08 +08:00
3f96c09d36 fix bug 优化 2023-03-17 13:35:22 +08:00
ca94295eb7 其它操作 加隐藏登录插件 及优化完善 2023-03-17 00:45:04 +08:00
aaa1f3c937 tree 测试 2023-03-16 12:28:53 +08:00
decea28316 完善分类 2023-03-16 00:56:40 +08:00
f6e2f86ee7 完善分类下拉 2023-03-15 21:40:45 +08:00
cb1ce2e878 优化 2023-03-14 19:47:18 +08:00
e03194b768 优化 2023-03-14 19:21:00 +08:00
8d398016ac 分类 2023-03-14 18:35:48 +08:00
a313210f71 分类 2023-03-14 13:50:13 +08:00
2d476ea4f6 优化 调整目录 2023-03-12 20:41:10 +08:00
b6b92ede34 优化 2023-03-12 20:28:57 +08:00
12a1fea5ed 优化 2023-03-12 14:25:22 +08:00
d211e49036 归档组件化 及动态加载侧边栏 2023-03-11 23:59:00 +08:00
cc09668fd7 最近评论组件化 2023-03-10 19:38:32 +08:00
179e1e14e2 完善 2023-03-10 18:33:09 +08:00
ad875857d8 最近文章组件化 2023-03-10 18:20:38 +08:00
1da5062356 优化 使搜索组件化 2023-03-09 22:36:41 +08:00
1f7e51858b 接入后台近期评论设置 2023-03-07 17:13:12 +08:00
096514a677 接入后台近期文章设置 2023-03-06 23:43:58 +08:00
240a41f1eb 接入后台规档设置 2023-03-06 20:53:51 +08:00
4f1a2f717e 小调整 2023-03-06 00:25:17 +08:00
8b7edea000 小调整 2023-03-05 22:22:54 +08:00
01e10f69dd 修复bug 2023-03-05 21:23:19 +08:00
2f456f62c0 调整优化修复bug 2023-03-04 23:57:19 +08:00
a905f59eae 调整优化修复bug 2023-03-04 14:44:51 +08:00
997839e98a 调整优化修复bug 2023-03-03 23:09:39 +08:00
f3595874ae fix bug 2023-03-03 19:11:27 +08:00
5665e0021b 优化 2023-03-03 13:50:11 +08:00
xing
a3b1e054d0 fix 2023-03-03 13:21:35 +08:00
xing
2ea52f1752 elighterjs做成插件 2023-03-02 23:49:28 +08:00
xing
d3be997b8f 小优化 2023-03-02 21:16:43 +08:00
xing
0d62ebd5a5 优化完善代码 2023-03-02 20:36:58 +08:00
xing
5ce707e427 修复bug 2023-03-01 23:34:05 +08:00
xing
2ca1f10bfc 优化 2023-03-01 23:01:38 +08:00
xing
ae20829209 完善twentyseventeen的 配色 2023-03-01 22:52:05 +08:00
xing
9af2a04a97 优化代码 2023-03-01 13:17:12 +08:00
xing
4d50f60c62 优化代码 2023-02-28 23:59:17 +08:00
xing
7d27668159 优化代码 2023-02-28 23:38:23 +08:00
xing
cbf3cabb24 完善 2023-02-28 20:51:02 +08:00
xing
dd384b0169 添加error 2023-02-28 20:29:18 +08:00
xing
e1d8c0098f 完善seventeen 添加customheader 2023-02-28 19:44:19 +08:00
xing
ccc02399ef 优化 完善代码 2023-02-28 19:19:24 +08:00
xing
9b46a86d5d 优化 components 添加顺序 2023-02-28 15:17:16 +08:00
xing
26950a37bb 优化代码 2023-02-27 22:58:35 +08:00
xing
eab6db83eb 分页器调整 2023-02-27 21:34:48 +08:00
xing
1b1e3bf8f3 优化 2023-02-26 22:20:20 +08:00
xing
5ae0ad646e 优化 2023-02-26 22:03:30 +08:00
xing
477d8fe6f9 优化 2023-02-26 22:01:08 +08:00
xing
b4cc570e8a map的分页 2023-02-26 21:55:03 +08:00
xing
9c89f44841 优化代码 2023-02-26 13:55:05 +08:00
xing
11e0c32c6f 优化代码 2023-02-25 23:38:12 +08:00
xing
0f0c3997c0 去掉无用代码 2023-02-25 23:14:19 +08:00
xing
e0786f7f8b 优化数据库查询相关 2023-02-25 23:10:42 +08:00
xing
00e42c2d56 修复safety.map flush bug 2023-02-25 14:46:13 +08:00
xing
e978e86304 优化代码,seventeen配色 2023-02-25 00:56:52 +08:00
xing
8a5fc02247 优化代码 2023-02-24 19:43:12 +08:00
xing
ccbba30ae2 再优化代码 2023-02-24 19:34:19 +08:00
xing
865b37cf89 组合优于继承。。。。。。。 2023-02-23 23:42:51 +08:00
xing
bd7d20160a 优化代码 2023-02-23 20:22:42 +08:00
xing
2a87ebdfd6 优化代码 2023-02-23 18:21:51 +08:00
xing
6d1fde655d 调整代码,index 2023-02-23 17:26:20 +08:00
xing
efeeb8b675 优化查询条件解析,where中添加可以不用参数绑定的条件 2023-02-23 11:43:02 +08:00
xing
4aa54b1ca7 调整代码 2023-02-22 22:11:25 +08:00
xing
2aacb682e1 小优 2023-02-22 20:59:26 +08:00
xing
2a68b73773 测试 2023-02-22 19:05:47 +08:00
xing
f885b5c8f0 添加from 可以实现子查询 2023-02-22 18:50:27 +08:00
xing
0bc93850ed 添加 gets 查询 2023-02-21 22:15:35 +08:00
xing
409ffd8081 小优化代码 2023-02-21 21:49:59 +08:00
xing
df11d6522d 字符串转int无默认值版 2023-02-21 20:31:00 +08:00
xing
b588b7e6d4 ... 2023-02-21 20:27:29 +08:00
xing
56f869c10f 比较下了下scanner的性能,似乎性能不是太高,可能类型断言太多了些???需要泛型方法啊。。。。。。 2023-02-21 20:15:39 +08:00
xing
226c98cbd9 优化 加个scanner 2023-02-21 19:49:13 +08:00
xing
73fb725960 优化查询 去掉anymap 转为 stringmap 2023-02-21 18:25:52 +08:00
xing
a319e7595f 优化代码,添加getoption函数,添加db查询函数,可返回map[string]any,添加getthememods函数返回不太通用的值 2023-02-21 01:07:32 +08:00
xing
cb3d043c90 修复bug 2023-02-19 23:11:22 +08:00
xing
ccfe077947 修复bug 2023-02-19 22:15:19 +08:00
xing
75c9a47799 优化 2023-02-19 21:48:41 +08:00
xing
d043e75dfd 修复样式bug 2023-02-19 13:42:07 +08:00
xing
a18616f50f 再优化reload机制 2023-02-19 00:14:33 +08:00
xing
5ca1a2c592 fifteen 主题配色 2023-02-18 23:35:39 +08:00
xing
0faac992cf 重构reload config和flush cache 机制 2023-02-17 23:36:54 +08:00
xing
6ad2b3cf64 完善 2023-02-17 00:25:13 +08:00
xing
7d95109e78 完善twenty seventeen 2023-02-17 00:20:38 +08:00
xing
cd5a9eadd7 customcss 2023-02-16 23:12:16 +08:00
xing
49b51dadcb 完善 bodyclass 2023-02-16 22:57:53 +08:00
xing
a635b9a8ea customlogo 2023-02-16 20:48:08 +08:00
xing
3c21187ee3 sitelogo 2023-02-16 19:03:31 +08:00
xing
1d80534333 custom-header fifteen页眉图片设置 2023-02-16 14:36:43 +08:00
xing
f722d685ea 优化 2023-02-16 11:53:24 +08:00
xing
c60c11769a 字符builder 2023-02-16 00:58:59 +08:00
xing
a6f80c2639 小优化 2023-02-16 00:34:36 +08:00
xing
bc6b3675ab 背景设置 custom-background 2023-02-16 00:32:02 +08:00
xing
729da0495d fix bug 2023-02-15 00:28:57 +08:00
xing
8dded98f39 小调整,修复一bug 2023-02-14 23:44:48 +08:00
xing
72a37d3bc8 优化代码 2023-02-14 23:28:45 +08:00
xing
f96f364a69 计算body class 2023-02-14 19:47:47 +08:00
xing
1451b33af4 小优化 2023-02-13 23:03:17 +08:00
xing
b1e824b27d 完善 2023-02-12 19:19:53 +08:00
xing
bfd4e4ac4d 完善状态码 2023-02-11 23:58:40 +08:00
xing
23f32c8fed 优化代码 继承方式重构详情页 骚操作。。。。。 2023-02-11 23:51:07 +08:00
xing
a2cebfbb3f 优化代码 使用继承方式重构列表页 2023-02-11 00:32:46 +08:00
xing
cd636e2dc9 优化代码,添加两个配置 2023-02-10 21:23:30 +08:00
xing
ef71192add 去掉无用 2023-02-09 23:19:32 +08:00
xing
daa9a26e00 插件支持配置、热加载 2023-02-09 23:15:10 +08:00
xing
3069bc3892 Merge remote-tracking branch 'origin/master' 2023-02-09 21:00:57 +08:00
xing
efd174631b fix bug 2023-02-09 20:57:09 +08:00
xing
b495dda30f 修复bug 完善代码 2023-02-09 20:49:33 +08:00
xing
55a9e90ec9 优化 2023-02-09 17:12:33 +08:00
xing
abfcc9b1ad 优化 2023-02-09 16:08:18 +08:00
xing
1100b89312 优化 2023-02-09 15:43:20 +08:00
xing
d88dd6b853 小优化 2023-02-09 12:57:18 +08:00
xing
febaab279d 去掉无用代码 2023-02-09 00:10:19 +08:00
xing
796ae6f0fa 优化 2023-02-09 00:07:53 +08:00
xing
439a49cf12 优化代码,实现新的插件机制 2023-02-08 23:49:48 +08:00
xing
f0028e72f4 小修 2023-02-07 23:46:34 +08:00
xing
fd951b7ab8 部分查询函数重命名 2023-02-07 23:05:31 +08:00
xing
e474fefdae 配置 2023-02-06 22:59:16 +08:00
d69a04e65d
Merge pull request #15 from fthvgb1/dev
Dev
2023-02-06 22:57:52 +08:00
xing
fda83e5d1a 小优化 2023-02-06 22:56:36 +08:00
xing
562520c080 优化代码 2023-02-06 20:50:25 +08:00
xing
b453910f82 优化 2023-02-06 19:07:55 +08:00
xing
0b0e701bef 优化 2023-02-06 18:51:58 +08:00
xing
b1859409da 优化 2023-02-06 18:42:36 +08:00
xing
4ec03cba9b 小优化,查询添加 column函数 2023-02-06 18:34:35 +08:00
xing
f28c41c84a 优化代码 调整 model query 2023-02-06 17:58:24 +08:00
xing
dc203f87b8 小优 2023-02-06 15:59:44 +08:00
xing
404f27f693 优化代码,添加字符串转整数函数 2023-02-06 12:13:38 +08:00
xing
f31f7a4cc0 静态文件缓存 2023-02-05 21:29:09 +08:00
xing
907d5be596 优化代码 2023-02-05 21:06:04 +08:00
xing
eee0b75d19 优化代码 2023-02-05 20:33:33 +08:00
a75c48cdb4
Merge pull request #14 from fthvgb1/dev
优化
2023-02-05 18:45:50 +08:00
xing
09e99ac379 优化 2023-02-05 18:29:55 +08:00
7997a9f512
Merge pull request #13 from fthvgb1/dev
修复默认页眉图片bug并优化代码
2023-02-05 00:15:00 +08:00
xing
285887d0e2 修复默认页眉图片bug并优化代码 2023-02-05 00:07:10 +08:00
996beb12a8
Merge pull request #12 from fthvgb1/dev
Dev
2023-02-04 21:16:52 +08:00
xing
da236257cc 去掉无用配置项 2023-02-04 12:46:51 +08:00
xing
1380c0e1e4 优化 去掉不太有用的方法 2023-02-04 12:35:58 +08:00
xing
8664d1181d 优化 2023-02-03 22:43:20 +08:00
xing
4596f4541b 读写测试 2023-02-03 22:08:54 +08:00
0155d29f77
Merge pull request #11 from fthvgb1/dev
Dev
2023-02-03 21:57:09 +08:00
xing
d185f78d6c 测试 2023-02-03 21:55:00 +08:00
xing
29bbeef9dc 优化 2023-02-03 14:46:25 +08:00
504b0efd66
Merge pull request #10 from fthvgb1/dev
Dev
2023-02-03 14:34:42 +08:00
xing
f831499341 修复bug 2023-02-03 14:27:32 +08:00
c130c46476
Merge pull request #9 from fthvgb1/dev
Dev
2023-02-01 23:53:53 +08:00
74808e22fb
Merge pull request #8 from fthvgb1/dev
Dev
2023-02-01 01:00:19 +08:00
0c0fafedec
Merge pull request #7 from fthvgb1/dev
Dev
2023-01-30 20:50:41 +08:00
7e935a8fa6
Merge pull request #6 from fthvgb1/dev
Dev
2023-01-28 23:35:14 +08:00
8d8ab4ab9b
Merge pull request #5 from fthvgb1/dev
Dev
2023-01-28 22:38:01 +08:00
1267d45ffb
Merge pull request #4 from fthvgb1/dev
评论抽象出来 完成主题twentyseventeen详情页
2023-01-27 00:10:10 +08:00
68d045157c
Merge pull request #3 from fthvgb1/dev
Dev
2023-01-26 02:33:40 +08:00
f470a0c31f
Merge pull request #2 from fthvgb1/dev
Dev
2023-01-23 23:45:35 +08:00
06e91853c1
Merge pull request #1 from fthvgb1/dev
增加tag的验证
2023-01-21 23:17:16 +08:00
392 changed files with 18645 additions and 39470 deletions

8
.gitignore vendored
View File

@ -1,3 +1,7 @@
.idea .idea
wp-go.iml /wp-go.iml
config.yaml /config.yaml
err.log
/plugins/
/config.json
go.sum

View File

@ -1,8 +1,7 @@
FROM golang:latest as gobulidIso FROM golang:1.22.2-alpine as gobulidIso
COPY ./ /go/src/wp-go COPY ./ /go/src/wp-go
WORKDIR /go/src/wp-go WORKDIR /go/src/wp-go
ENV GOPROXY="https://goproxy.cn" RUN go build -ldflags "-w" -tags netgo -o wp-go app/cmd/main.go
RUN go build -ldflags "-w" -tags netgo -o wp-go internal/cmd/main.go
FROM alpine:latest FROM alpine:latest
WORKDIR /opt/wp-go WORKDIR /opt/wp-go

View File

@ -1,19 +1,87 @@
## wp-go ## wp-go
a simple front of WordPress build with golang.
一个go写的WordPress的前端功能比较简单只有列表页和详情页,rss2主题只有twentyfifteen和twentyseventeen两套主题插件的话只有一个简单的列表页的摘要生成和enlighter代码高亮。本身只用于展示文章添加评论走的转发请求到php的WordPress。因为大量用了泛型功能所以要求go的版本在1.18及以上。 [en readme](https://github.com/fthvgb1/wp-go/blob/master/readme_en.md)
一个go写的WordPress的前端功能比较简单只有列表页和详情页,rss2主题只有twentyfifteen和twentyseventeen两套主题插件的话只有一个简单的列表页的摘要生成和enlighter代码高亮。本身只用于展示文章及评论。要求go的版本在1.20以上,越新越好。。。
#### 特色功能 #### 特色功能
- 多种缓存配置 - 基本实现全站缓存,并且可防止缓存击穿
- 列表页也可以高亮语法格式化显示代码
- 简易插件扩展开发机制、配置后支持热加载更新
- 使用.so扩展主题、插件、路由等
- 丰富繁杂的配置,呃,配置是有点儿多,虽然大部分都是可选项。。。
- 添加评论或panic时发邮件通知包涵栈调用和请求信息 - 添加评论或panic时发邮件通知包涵栈调用和请求信息
- 简单的流量限制中间件 - 简单的流量限制中间件,可以限制全瞬时最大请求数量
- 除配置文件外将所有静态资源都打包到执行文件中 - 除配置文件外将所有静态资源都打包到执行文件中
- 支持密码查看且cookie信息可被php版所验证 - 支持密码查看且cookie信息可被php版所验证
- 支持rss2订阅 - 支持rss2订阅
- 热更新配置、清空缓存 - 热更新配置、切换主题、清空缓存
- kill -SIGUSR1 PID 更新配置和清空缓存 - kill -SIGUSR1 PID 更新配置和清空缓存
- kill -SIGUSR2 PID 清空缓存 - kill -SIGUSR2 PID 清空缓存
#### 运行
```
go run app/cmd/main.go [-c configpath] [-p port]
```
#### 数据显示支持程度
| 页表 | 支持程度 |
|-----|---------------------------------------------|
| 列表页 | 首页/搜索/归档/分类/标签/作者 分页列表 |
| 详情页 | 显示内容、评论并可以添加评论(转发的php处理需要配置php版的添加评论的url) |
| 侧边栏 | 支持旧版 近期文章、近期评论、规档、分类、其它操作 显示及设置, 支持新版 分类 |
#### 后台设置支持程度
- 仪表盘
- 外观
- 小工具
- 搜索
- 规档
- 近期文章
- 近期评论
- 分类
- 其它操作
- 设置-
- 常规
- 站点标题
- 副标题
- 阅读
- 博客页面至多显示数量
- Feed中显示最近数量
- 讨论
- 其他评论设置
- `启用|禁止`评论嵌套,最多嵌套层数
- 分页显示评论,每页显示评论条数,默认显示`最前/后`页
- 在每个页面顶部显示 `新旧`评论
#### 主题支持程度
| twentyfifteen | twentyseventeen |
|---------------|-----------------|
| 站点身份 | 站点身份 |
| 颜色 | 颜色 |
| 页眉图片 | 页眉媒体 |
| 背景图片 | 额外css |
| 额外css | |
#### 插件机制
分为对列表页文章数据的修改的插件和对影响整个程序表现的插件
| 列表页文章数据插件 | 整个程序表现的插件 |
|---------------------|--------------------------------------|
| digest 自动生成指定长度的摘要 | enlighter 代码高亮(需要在后台安装enlighterjs插件) |
| | hiddenLogin 隐藏登录入口 |
#### 其它 #### 其它
用的gin框架和sqlx,在外面封装了层查询的方法。后台可以设置的比较少,大部分设置还没打通。
用的gin框架和sqlx,在外面封装了层查询的方法。
#### 鸣谢
<a href="https://jb.gg/OpenSourceSupport"><img src="https://resources.jetbrains.com/storage/products/company/brand/logos/jb_beam.png" alt="JetBrains Logo (Main) logo." width="30%"></a>

293
app/actions/comment.go Normal file
View File

@ -0,0 +1,293 @@
package actions
import (
"bytes"
"compress/gzip"
"errors"
"fmt"
"github.com/fthvgb1/wp-go/app/mail"
"github.com/fthvgb1/wp-go/app/pkg/cache"
"github.com/fthvgb1/wp-go/app/pkg/config"
"github.com/fthvgb1/wp-go/app/pkg/logs"
"github.com/fthvgb1/wp-go/app/wpconfig"
"github.com/fthvgb1/wp-go/helper/slice"
str "github.com/fthvgb1/wp-go/helper/strings"
"github.com/gin-gonic/gin"
"github.com/go-playground/validator/v10"
"io"
"net/http"
"net/url"
"strings"
"time"
)
type CommentForm struct {
CommentPostId uint64 `form:"comment_post_ID" binding:"required" json:"comment_post_ID"`
Author string `form:"author" binding:"required" label:"显示名称" json:"author"`
Email string `form:"email" binding:"required,email"`
Comment string `form:"comment" binding:"required" label:"评论" json:"comment"`
}
func PostComment(c *gin.Context) {
cli := &http.Client{
Timeout: time.Second * 3,
CheckRedirect: func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
},
}
data, err := c.GetRawData()
defer func() {
if err != nil {
c.Writer.WriteHeader(http.StatusConflict)
c.Writer.Header().Set("Content-Type", "text/html; charset=utf-8")
var v validator.ValidationErrors
if errors.As(err, &v) {
e := v.Translate(config.GetZh())
for _, v := range e {
fmt.Fprintf(c.Writer, fail, v)
return
}
} else {
c.Writer.WriteString("评论出错,请联系管理员或稍后再度")
}
}
}()
conf := config.GetConfig()
if err != nil {
logs.Error(err, "获取评论数据错误")
return
}
c.Request.Body = io.NopCloser(bytes.NewBuffer(data))
var comment CommentForm
if err = c.ShouldBind(&comment); err != nil {
return
}
c.Request.Body = io.NopCloser(bytes.NewBuffer(data))
req, err := http.NewRequest("POST", conf.PostCommentUrl, strings.NewReader(c.Request.PostForm.Encode()))
if err != nil {
logs.Error(err, "创建评论请求错误")
return
}
defer req.Body.Close()
req.Header = c.Request.Header.Clone()
home, err := url.Parse(wpconfig.GetOption("siteurl"))
if err != nil {
logs.Error(err, "解析评论接口错误")
return
}
req.Host = home.Host
res, err := cli.Do(req)
if err != nil && !errors.Is(err, http.ErrUseLastResponse) {
logs.Error(err, "请求评论接口错误")
return
}
if res.StatusCode == http.StatusFound {
for _, cookie := range res.Cookies() {
c.SetCookie(cookie.Name, cookie.Value, cookie.MaxAge, cookie.Path, cookie.Domain, cookie.Secure, cookie.HttpOnly)
}
u := res.Header.Get("Location")
up, er := url.Parse(u)
if er != nil {
err = er
return
}
cu, er := url.Parse(conf.PostCommentUrl)
if er != nil {
err = er
return
}
up.Host = cu.Host
up.Scheme = "http"
newReq, _ := http.NewRequest("GET", up.String(), nil)
newReq.Host = home.Host
newReq.Header.Set("Cookie", strings.Join(slice.Map(c.Request.Cookies(), func(t *http.Cookie) string {
return fmt.Sprintf("%s=%s", t.Name, t.Value)
}), "; "))
ress, er := http.DefaultClient.Do(newReq)
if er != nil {
err = er
return
}
cc := c.Copy()
go func() {
if gin.Mode() != gin.ReleaseMode {
return
}
id := comment.CommentPostId
if id <= 0 {
logs.Error(errors.New("获取文档id错误"), "", comment.CommentPostId)
return
}
post, err := cache.GetPostById(cc, id)
if err != nil {
logs.Error(err, "获取文档错误", id)
return
}
su := fmt.Sprintf("%s: %s[%s]发表了评论对文档[%v]的评论", wpconfig.GetOption("siteurl"), comment.Author, comment.Email, post.PostTitle)
err = mail.SendMail([]string{conf.Mail.User}, su, comment.Comment)
logs.IfError(err, "发送邮件", conf.Mail.User, su, comment)
}()
s, er := io.ReadAll(ress.Body)
if er != nil {
err = er
return
}
uuu := ""
uu, _ := url.Parse(res.Header.Get("Location"))
if up.RawQuery != "" {
cache.NewCommentCache().Set(c, up.RawQuery, string(s))
uuu = str.Join(uu.Path, "?", uu.RawQuery)
} else {
uuu = str.Join(uu.Path)
}
c.Redirect(http.StatusFound, uuu)
return
}
var r io.Reader
if res.Header.Get("Content-Encoding") == "gzip" {
r, err = gzip.NewReader(res.Body)
if err != nil {
logs.Error(err, "gzip解压错误")
return
}
} else {
r = res.Body
}
s, err := io.ReadAll(r)
if err != nil {
logs.Error(err, "读取结果错误")
return
}
c.Writer.Header().Set("Content-Type", "text/html; charset=utf-8")
c.Writer.WriteHeader(res.StatusCode)
_, _ = c.Writer.Write(s)
}
var fail = `
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta name="viewport" content="width=device-width">
<meta name='robots' content='max-image-preview:large, noindex, follow' />
<title>评论提交失败</title>
<style type="text/css">
html {
background: #f1f1f1;
}
body {
background: #fff;
border: 1px solid #ccd0d4;
color: #444;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
margin: 2em auto;
padding: 1em 2em;
max-width: 700px;
-webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, .04);
box-shadow: 0 1px 1px rgba(0, 0, 0, .04);
}
h1 {
border-bottom: 1px solid #dadada;
clear: both;
color: #666;
font-size: 24px;
margin: 30px 0 0 0;
padding: 0;
padding-bottom: 7px;
}
#error-page {
margin-top: 50px;
}
#error-page p,
#error-page .wp-die-message {
font-size: 14px;
line-height: 1.5;
margin: 25px 0 20px;
}
#error-page code {
font-family: Consolas, Monaco, monospace;
}
ul li {
margin-bottom: 10px;
font-size: 14px ;
}
a {
color: #0073aa;
}
a:hover,
a:active {
color: #006799;
}
a:focus {
color: #124964;
-webkit-box-shadow:
0 0 0 1px #5b9dd9,
0 0 2px 1px rgba(30, 140, 190, 0.8);
box-shadow:
0 0 0 1px #5b9dd9,
0 0 2px 1px rgba(30, 140, 190, 0.8);
outline: none;
}
.button {
background: #f3f5f6;
border: 1px solid #016087;
color: #016087;
display: inline-block;
text-decoration: none;
font-size: 13px;
line-height: 2;
height: 28px;
margin: 0;
padding: 0 10px 1px;
cursor: pointer;
-webkit-border-radius: 3px;
-webkit-appearance: none;
border-radius: 3px;
white-space: nowrap;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
vertical-align: top;
}
.button.button-large {
line-height: 2.30769231;
min-height: 32px;
padding: 0 12px;
}
.button:hover,
.button:focus {
background: #f1f1f1;
}
.button:focus {
background: #f3f5f6;
border-color: #007cba;
-webkit-box-shadow: 0 0 0 1px #007cba;
box-shadow: 0 0 0 1px #007cba;
color: #016087;
outline: 2px solid transparent;
outline-offset: 0;
}
.button:active {
background: #f3f5f6;
border-color: #7e8993;
-webkit-box-shadow: none;
box-shadow: none;
}
</style>
</head>
<body id="error-page">
<div class="wp-die-message"><p><strong>错误</strong>%s</p></div>
<p><a href='javascript:history.back()'>&laquo; 返回</a></p></body>
</html>
`

108
app/actions/feed.go Normal file
View File

@ -0,0 +1,108 @@
package actions
import (
"github.com/fthvgb1/wp-go/app/pkg/cache"
str "github.com/fthvgb1/wp-go/helper/strings"
"github.com/gin-gonic/gin"
"net/http"
"time"
)
var tmp = "Mon, 02 Jan 2006 15:04:05 GMT"
func Feed(c *gin.Context) {
v, ok := c.GetQuery("feed")
if !ok || v == "" {
c.Next()
return
}
switch v {
case "rss2":
p, ok := c.GetQuery("p")
if ok && p != "" {
c.AddParam("id", p)
PostFeed(c)
} else {
SiteFeed(c)
}
c.Abort()
return
case "comments-rss2":
CommentsFeed(c)
c.Abort()
return
}
}
func isCacheExpired(c *gin.Context, lastTime time.Time) bool {
eTag := str.Md5(lastTime.Format(tmp))
since := c.Request.Header.Get("If-Modified-Since")
cTag := c.Request.Header.Get("If-None-Match")
if since != "" && cTag != "" {
cGMT, err := time.Parse(tmp, since)
if err == nil && lastTime.Unix() <= cGMT.Unix() && eTag == cTag {
c.Status(http.StatusNotModified)
return false
}
}
return true
}
func SiteFeed(c *gin.Context) {
feed := cache.FeedCache()
if !isCacheExpired(c, feed.GetLastSetTime(c)) {
c.Status(http.StatusNotModified)
return
}
r, err := feed.GetCache(c, time.Second, c)
if err != nil {
c.Status(http.StatusInternalServerError)
c.Abort()
c.Error(err)
return
}
setFeed(r[0], c, feed.GetLastSetTime(c))
}
func setFeed(s string, c *gin.Context, t time.Time) {
lastTimeGMT := t.Format(tmp)
eTag := str.Md5(lastTimeGMT)
c.Header("Content-Type", "application/rss+xml; charset=UTF-8")
c.Header("Last-Modified", lastTimeGMT)
c.Header("ETag", eTag)
c.String(http.StatusOK, s)
}
func PostFeed(c *gin.Context) {
id := c.Param("id")
postFeed := cache.PostFeedCache()
if !isCacheExpired(c, postFeed.GetLastSetTime(c, id)) {
c.Status(http.StatusNotModified)
return
}
s, err := postFeed.GetCache(c, id, time.Second)
if err != nil {
c.Status(http.StatusInternalServerError)
c.Abort()
c.Error(err)
return
}
setFeed(s, c, postFeed.GetLastSetTime(c, id))
}
func CommentsFeed(c *gin.Context) {
feed := cache.CommentsFeedCache()
if !isCacheExpired(c, feed.GetLastSetTime(c)) {
c.Status(http.StatusNotModified)
return
}
r, err := feed.GetCache(c, time.Second, c)
if err != nil {
c.Status(http.StatusInternalServerError)
c.Abort()
c.Error(err)
return
}
setFeed(r[0], c, feed.GetLastSetTime(c))
}

View File

@ -2,9 +2,9 @@ package actions
import ( import (
"fmt" "fmt"
"github.com/fthvgb1/wp-go/app/phphelper"
"github.com/fthvgb1/wp-go/app/wpconfig"
str "github.com/fthvgb1/wp-go/helper/strings" str "github.com/fthvgb1/wp-go/helper/strings"
"github.com/fthvgb1/wp-go/internal/phpass"
"github.com/fthvgb1/wp-go/internal/wpconfig"
"github.com/gin-contrib/sessions" "github.com/gin-contrib/sessions"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"net/http" "net/http"
@ -28,12 +28,12 @@ func Login(c *gin.Context) {
c.Error(err) c.Error(err)
return return
} }
pass, err := phpass.NewPasswordHash(8, true).HashPassword(password) pass, err := phphelper.NewPasswordHash(8, true).HashPassword(password)
if err != nil { if err != nil {
c.Error(err) c.Error(err)
return return
} }
cohash := fmt.Sprintf("wp-postpass_%s", str.Md5(wpconfig.Options.Value("siteurl"))) cohash := fmt.Sprintf("wp-postpass_%s", str.Md5(wpconfig.GetOption("siteurl")))
c.SetCookie(cohash, pass, 24*3600, "/", "", false, false) c.SetCookie(cohash, pass, 24*3600, "/", "", false, false)
c.Redirect(http.StatusFound, ref) c.Redirect(http.StatusFound, ref)

15
app/actions/themehook.go Normal file
View File

@ -0,0 +1,15 @@
package actions
import (
"github.com/fthvgb1/wp-go/app/theme"
"github.com/fthvgb1/wp-go/app/theme/wp"
"github.com/gin-gonic/gin"
)
func ThemeHook(scene string) func(*gin.Context) {
return func(c *gin.Context) {
t := theme.GetCurrentTheme()
h := wp.NewHandle(c, scene, t)
theme.Hook(t, h)
}
}

111
app/cmd/main.go Normal file
View File

@ -0,0 +1,111 @@
package main
import (
"flag"
"fmt"
"github.com/fthvgb1/wp-go/app/ossigns"
"github.com/fthvgb1/wp-go/app/pkg/cache"
"github.com/fthvgb1/wp-go/app/pkg/config"
"github.com/fthvgb1/wp-go/app/pkg/db"
"github.com/fthvgb1/wp-go/app/pkg/logs"
"github.com/fthvgb1/wp-go/app/plugins"
"github.com/fthvgb1/wp-go/app/plugins/wphandle"
"github.com/fthvgb1/wp-go/app/route"
"github.com/fthvgb1/wp-go/app/theme"
"github.com/fthvgb1/wp-go/app/wpconfig"
"github.com/fthvgb1/wp-go/cache/cachemanager"
"github.com/fthvgb1/wp-go/model"
"os"
"regexp"
"strings"
"time"
)
var confPath string
var address string
var intReg = regexp.MustCompile(`^\d`)
func inits() {
flag.StringVar(&confPath, "c", "config.yaml", "config file support json,yaml or url")
flag.StringVar(&address, "p", "", "listen address and port")
flag.Parse()
if address == "" && os.Getenv("PORT") == "" {
address = "80"
}
if intReg.MatchString(address) && !strings.Contains(address, ":") {
address = ":" + address
}
err := initConf(confPath)
if err != nil {
panic(err)
}
cache.InitActionsCommonCache()
plugins.InitDigestCache()
theme.InitTheme()
go cronClearCache()
}
func initConf(c string) (err error) {
err = config.InitConfig(c)
if err != nil {
return
}
err = config.InitTrans()
if err != nil {
return err
}
err = logs.InitLogger()
if err != nil {
return err
}
database, err := db.InitDb()
if err != nil {
return
}
model.InitDB(db.QueryDb(database))
err = wpconfig.InitOptions()
if err != nil {
return
}
err = wpconfig.InitTerms()
if err != nil {
return
}
wphandle.LoadPlugins()
return
}
func cronClearCache() {
t := time.NewTicker(config.GetConfig().CacheTime.CrontabClearCacheTime)
for {
select {
case <-t.C:
cachemanager.ClearExpired()
}
}
}
func main() {
defer func() {
if r := recover(); r != nil {
fmt.Println(r)
os.Exit(1)
}
}()
inits()
ossigns.SetConfPath(confPath)
go ossigns.SignalNotify()
Gin := route.SetupRouter()
c := config.GetConfig()
if c.Ssl.Key != "" && c.Ssl.Cert != "" {
err := Gin.RunTLS(address, c.Ssl.Cert, c.Ssl.Key)
if err != nil {
panic(err)
}
return
}
err := Gin.Run(address)
if err != nil {
panic(err)
}
}

View File

@ -3,8 +3,8 @@ package mail
import ( import (
"crypto/tls" "crypto/tls"
"fmt" "fmt"
"github.com/fthvgb1/wp-go/internal/pkg/config" "github.com/fthvgb1/wp-go/app/pkg/config"
"github.com/soxfmr/gomail" "gopkg.in/gomail.v2"
"mime" "mime"
"path" "path"
) )
@ -18,7 +18,7 @@ func SendMail(mailTo []string, subject string, body string, files ...string) err
m := gomail.NewMessage( m := gomail.NewMessage(
gomail.SetEncoding(gomail.Base64), gomail.SetEncoding(gomail.Base64),
) )
c := config.Conf.Load() c := config.GetConfig()
m.SetHeader("From", m.SetHeader("From",
m.FormatAddress(c.Mail.User, m.FormatAddress(c.Mail.User,
c.Mail.Alias, c.Mail.Alias,
@ -45,7 +45,7 @@ func SendMail(mailTo []string, subject string, body string, files ...string) err
c.Mail.User, c.Mail.User,
c.Mail.Pass, c.Mail.Pass,
) )
if !c.Mail.Ssl { if c.Mail.InsecureSkipVerify {
d.TLSConfig = &tls.Config{InsecureSkipVerify: true} d.TLSConfig = &tls.Config{InsecureSkipVerify: true}
} }
err := d.DialAndSend(m) err := d.DialAndSend(m)

View File

@ -1,7 +1,7 @@
package mail package mail
import ( import (
"github.com/fthvgb1/wp-go/internal/pkg/config" "github.com/fthvgb1/wp-go/app/pkg/config"
"testing" "testing"
) )

View File

@ -19,9 +19,9 @@ func FlowLimit(maxRequestSleepNum, maxRequestNum int64, sleepTime []time.Duratio
} }
s := safety.Var[[]time.Duration]{} s := safety.Var[[]time.Duration]{}
s.Store(sleepTime) s.Store(sleepTime)
fn := func(msn, mn int64, st []time.Duration) { fn := func(sleepNum, maxNum int64, st []time.Duration) {
atomic.StoreInt64(&maxRequestSleepNum, msn) atomic.StoreInt64(&maxRequestSleepNum, sleepNum)
atomic.StoreInt64(&maxRequestNum, mn) atomic.StoreInt64(&maxRequestNum, maxNum)
s.Store(st) s.Store(st)
} }
return func(c *gin.Context) { return func(c *gin.Context) {

View File

@ -25,14 +25,25 @@ func IpLimit(num int64) (func(ctx *gin.Context), func(int64)) {
fn(num) fn(num)
return func(c *gin.Context) { return func(c *gin.Context) {
if atomic.LoadInt64(m.limitNum) <= 0 {
c.Next()
return
}
ip := c.ClientIP() ip := c.ClientIP()
s := false
m.mux.RLock() m.mux.RLock()
i, ok := m.m[ip] i, ok := m.m[ip]
m.mux.RUnlock() m.mux.RUnlock()
if !ok {
m.mux.Lock()
i = new(int64)
m.m[ip] = i
m.mux.Unlock()
}
defer func() { defer func() {
ii := atomic.LoadInt64(i) ii := atomic.LoadInt64(i)
if s && ii > 0 { if ii > 0 {
atomic.AddInt64(i, -1) atomic.AddInt64(i, -1)
if atomic.LoadInt64(i) == 0 { if atomic.LoadInt64(i) == 0 {
m.mux.Lock() m.mux.Lock()
@ -42,20 +53,12 @@ func IpLimit(num int64) (func(ctx *gin.Context), func(int64)) {
} }
}() }()
if !ok { if atomic.LoadInt64(i) >= atomic.LoadInt64(m.limitNum) {
m.mux.Lock()
i = new(int64)
m.m[ip] = i
m.mux.Unlock()
}
if atomic.LoadInt64(m.limitNum) > 0 && atomic.LoadInt64(i) >= atomic.LoadInt64(m.limitNum) {
c.Status(http.StatusForbidden) c.Status(http.StatusForbidden)
c.Abort() c.Abort()
return return
} }
atomic.AddInt64(i, 1) atomic.AddInt64(i, 1)
s = true
c.Next() c.Next()
}, fn }, fn
} }

View File

@ -0,0 +1,22 @@
package middleware
import (
"github.com/fthvgb1/wp-go/app/pkg/config"
"github.com/fthvgb1/wp-go/cache/reload"
"github.com/gin-gonic/gin"
)
func SearchLimit(num int64) func(ctx *gin.Context) {
fn, reFn := IpLimit(num)
reload.Append(func() {
reFn(config.GetConfig().SingleIpSearchNum)
}, "search-ip-limit-number")
return func(c *gin.Context) {
if c.Query("s") != "" {
fn(c)
} else {
c.Next()
}
}
}

View File

@ -3,15 +3,16 @@ package middleware
import ( import (
"bytes" "bytes"
"fmt" "fmt"
"github.com/fthvgb1/wp-go/internal/mail" "github.com/fthvgb1/wp-go/app/mail"
"github.com/fthvgb1/wp-go/internal/pkg/config" "github.com/fthvgb1/wp-go/app/pkg/config"
"github.com/fthvgb1/wp-go/internal/pkg/logs" "github.com/fthvgb1/wp-go/app/pkg/logs"
"github.com/fthvgb1/wp-go/internal/wpconfig" "github.com/fthvgb1/wp-go/app/wpconfig"
str "github.com/fthvgb1/wp-go/helper/strings"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"io" "io"
"io/ioutil"
"net/http" "net/http"
"net/http/httputil" "net/http/httputil"
"os"
"runtime" "runtime"
"strings" "strings"
"time" "time"
@ -43,11 +44,11 @@ func RecoverAndSendMail(w io.Writer) func(ctx *gin.Context) {
) )
er := mail.SendMail( er := mail.SendMail(
[]string{config.Conf.Load().Mail.User}, []string{config.GetConfig().Mail.User},
fmt.Sprintf("%s%s %s 发生错误", fmt.Sprintf(wpconfig.Options.Value("siteurl")), c.FullPath(), time.Now().Format(time.RFC1123Z)), content) fmt.Sprintf("%s%s %s 发生错误", fmt.Sprintf(wpconfig.GetOption("siteurl")), c.FullPath(), time.Now().Format(time.RFC1123Z)), content)
if er != nil { if er != nil {
logs.ErrPrintln(er, "recover send mail fail", fmt.Sprintf("%v", err)) logs.IfError(er, "recover send mail fail", fmt.Sprintf("%v", err))
} }
}() }()
} }
@ -64,17 +65,13 @@ var (
) )
func formatStack(s string) (r string) { func formatStack(s string) (r string) {
ss := strings.Builder{} ss := str.NewBuilder()
t := strings.Split(s, "\n") t := strings.Split(s, "\n")
for i, line := range t { for i, line := range t {
if i%2 == 0 { if i%2 == 0 {
ss.WriteString("<dt>") ss.WriteString("<dt>", line, "</dt>")
ss.WriteString(line)
ss.WriteString("</dt>")
} else { } else {
ss.WriteString("<dd>") ss.WriteString("<dt>", strings.Trim(line, "\t"), "</dt>")
ss.WriteString(strings.Trim(line, "\t"))
ss.WriteString("</dd>")
} }
} }
r = ss.String() r = ss.String()
@ -95,7 +92,7 @@ func stack(skip int) []byte {
// Print this much at least. If we can't find the source, it won't show. // Print this much at least. If we can't find the source, it won't show.
fmt.Fprintf(buf, "%s:%d (0x%x)\n", file, line, pc) fmt.Fprintf(buf, "%s:%d (0x%x)\n", file, line, pc)
if file != lastFile { if file != lastFile {
data, err := ioutil.ReadFile(file) data, err := os.ReadFile(file)
if err != nil { if err != nil {
continue continue
} }

View File

@ -0,0 +1,33 @@
package middleware
import (
"fmt"
"github.com/fthvgb1/wp-go/app/pkg/config"
"github.com/gin-gonic/gin"
"net/http"
"path/filepath"
"strings"
)
var path = map[string]struct{}{
"wp-includes": {},
"wp-content": {},
"favicon.ico": {},
}
func SetStaticFileCache(c *gin.Context) {
f := strings.Split(strings.TrimLeft(c.FullPath(), "/"), "/")
if _, ok := path[f[0]]; ok {
if ".php" == filepath.Ext(c.Request.URL.Path) {
c.Abort()
c.Status(http.StatusForbidden)
return
}
t := config.GetConfig().CacheTime.CacheControl
if t > 0 {
c.Header("Cache-Control", fmt.Sprintf("private, max-age=%d", int(t.Seconds())))
}
}
c.Next()
}

View File

@ -0,0 +1,33 @@
package middleware
import (
"github.com/fthvgb1/wp-go/app/pkg/config"
"github.com/fthvgb1/wp-go/cache/reload"
"github.com/fthvgb1/wp-go/helper/maps"
"github.com/gin-gonic/gin"
"net/http"
"strings"
)
func ValidateServerNames() func(ctx *gin.Context) {
sites := reload.VarsBy(func() map[string]struct{} {
r := config.GetConfig().TrustServerNames
m := map[string]struct{}{}
if len(r) > 0 {
for _, name := range r {
m[name] = struct{}{}
}
}
return m
}, "site-names")
return func(c *gin.Context) {
m := sites.Load()
if len(m) > 0 && !maps.IsExists(m, strings.Split(c.Request.Host, ":")[0]) {
c.Status(http.StatusForbidden)
c.Abort()
return
}
c.Next()
}
}

69
app/ossigns/signs.go Normal file
View File

@ -0,0 +1,69 @@
package ossigns
import (
"fmt"
"github.com/fthvgb1/wp-go/app/mail"
"github.com/fthvgb1/wp-go/app/pkg/config"
"github.com/fthvgb1/wp-go/app/pkg/db"
"github.com/fthvgb1/wp-go/app/pkg/logs"
"github.com/fthvgb1/wp-go/app/plugins/wphandle"
"github.com/fthvgb1/wp-go/app/wpconfig"
"github.com/fthvgb1/wp-go/cache/cachemanager"
"github.com/fthvgb1/wp-go/cache/reload"
"github.com/fthvgb1/wp-go/signs"
"log"
"syscall"
)
var confPath string
func SetConfPath(path string) {
confPath = path
}
func FlushCache() {
defer func() {
if r := recover(); r != nil {
err := mail.SendMail([]string{config.GetConfig().Mail.User}, "清空缓存失败", fmt.Sprintf("err:[%s]", r))
logs.IfError(err, "发邮件失败")
}
}()
cachemanager.Flush()
log.Println("all cache flushed")
}
func Reloads() {
defer func() {
if r := recover(); r != nil {
log.Println(r)
}
}()
err := config.InitConfig(confPath)
logs.IfError(err, "获取配置文件失败", confPath)
err = logs.InitLogger()
logs.IfError(err, "日志配置错误")
_, err = db.InitDb()
logs.IfError(err, "重新读取db失败", config.GetConfig().Mysql)
err = wpconfig.InitOptions()
logs.IfError(err, "获取网站设置WpOption失败")
err = wpconfig.InitTerms()
logs.IfError(err, "获取WpTerms表失败")
wphandle.LoadPlugins()
reload.Reloads("themeArgAndConfig")
FlushCache()
log.Println("reload complete")
}
func SignalNotify() {
rel := func() bool {
go Reloads()
return true
}
flu := func() bool {
go FlushCache()
return true
}
signs.Install(syscall.SIGUSR1, rel, "reload")
signs.Install(syscall.SIGUSR2, flu, "flush")
signs.Wait()
}

View File

@ -1,4 +1,4 @@
package phpass package phphelper
import ( import (
"crypto/md5" "crypto/md5"

View File

@ -0,0 +1,37 @@
package phphelper
import (
"github.com/elliotchance/phpserialize"
"github.com/fthvgb1/wp-go/helper/maps"
)
// UnPHPSerializeToStruct 使用 json tag
func UnPHPSerializeToStruct[T any](s string) (r T, err error) {
var rr map[any]any
err = phpserialize.Unmarshal([]byte(s), &rr)
if err == nil {
rx := maps.AnyAnyToStrAny(rr)
r, err = maps.StrAnyMapToStruct[T](rx)
}
return
}
func UnPHPSerializeToStrAnyMap(s string) (map[string]any, error) {
m := map[string]any{}
var r map[any]any
err := phpserialize.Unmarshal([]byte(s), &r)
if err != nil {
return nil, err
}
m = maps.AnyAnyToStrAny(r)
return m, err
}
func UnPHPSerializeToAnyAnyMap(s string) (map[any]any, error) {
var r map[any]any
err := phpserialize.Unmarshal([]byte(s), &r)
if err != nil {
return nil, err
}
return r, err
}

126
app/pkg/cache/cache.go vendored Normal file
View File

@ -0,0 +1,126 @@
package cache
import (
"context"
"github.com/fthvgb1/wp-go/app/pkg/config"
"github.com/fthvgb1/wp-go/app/pkg/dao"
"github.com/fthvgb1/wp-go/app/pkg/logs"
"github.com/fthvgb1/wp-go/app/pkg/models"
"github.com/fthvgb1/wp-go/cache/cachemanager"
"github.com/fthvgb1/wp-go/cache/reload"
"time"
)
func InitActionsCommonCache() {
c := config.GetConfig()
cachemanager.NewMemoryMapCache(nil, dao.SearchPostIds, c.CacheTime.SearchPostCacheTime, "searchPostIds", func() time.Duration {
return config.GetConfig().CacheTime.SearchPostCacheTime
})
cachemanager.NewMemoryMapCache(nil, dao.SearchPostIds, c.CacheTime.PostListCacheTime, "listPostIds", func() time.Duration {
return config.GetConfig().CacheTime.PostListCacheTime
})
cachemanager.NewMemoryMapCache(nil, dao.MonthPost, c.CacheTime.MonthPostCacheTime, "monthPostIds", func() time.Duration {
return config.GetConfig().CacheTime.MonthPostCacheTime
})
cachemanager.NewMemoryMapCache(nil, dao.GetPostContext, c.CacheTime.ContextPostCacheTime, "postContext", func() time.Duration {
return config.GetConfig().CacheTime.ContextPostCacheTime
})
cachemanager.NewMemoryMapCache(dao.GetPostsByIds, nil, c.CacheTime.PostDataCacheTime, "postData", func() time.Duration {
return config.GetConfig().CacheTime.PostDataCacheTime
})
cachemanager.NewMemoryMapCache(dao.GetPostMetaByPostIds, nil, c.CacheTime.PostDataCacheTime, "postMetaData", func() time.Duration {
return config.GetConfig().CacheTime.PostDataCacheTime
})
cachemanager.NewMemoryMapCache(nil, dao.CategoriesAndTags, c.CacheTime.CategoryCacheTime, "categoryAndTagsData", func() time.Duration {
return config.GetConfig().CacheTime.CategoryCacheTime
})
cachemanager.NewVarMemoryCache(dao.RecentPosts, c.CacheTime.RecentPostCacheTime, "recentPosts", func() time.Duration {
return config.GetConfig().CacheTime.RecentPostCacheTime
})
cachemanager.NewVarMemoryCache(RecentComment, c.CacheTime.RecentCommentsCacheTime, "recentComments", func() time.Duration {
return config.GetConfig().CacheTime.RecentCommentsCacheTime
})
cachemanager.NewMemoryMapCache(nil, dao.CommentNum, 30*time.Second, "commentNumber", func() time.Duration {
return config.GetConfig().CacheTime.CommentsIncreaseUpdateTime
})
cachemanager.NewMemoryMapCache(nil, PostTopComments, 30*time.Second, "PostCommentsIds", func() time.Duration {
return config.GetConfig().CacheTime.CommentsIncreaseUpdateTime
})
cachemanager.NewMemoryMapCache(nil, dao.PostTopCommentNum, 30*time.Second, "postTopCommentsNum", func() time.Duration {
return config.GetConfig().CacheTime.CommentsIncreaseUpdateTime
})
cachemanager.NewMemoryMapCache(dao.GetCommentByIds, nil, time.Hour, "postCommentData", func() time.Duration {
return config.GetConfig().CacheTime.CommentsCacheTime
})
cachemanager.NewMemoryMapCache(dao.CommentChildren, nil, time.Minute, "commentChildren", func() time.Duration {
return config.GetConfig().CacheTime.CommentsIncreaseUpdateTime
})
cachemanager.NewVarMemoryCache(dao.GetMaxPostId, c.CacheTime.MaxPostIdCacheTime, "maxPostId", func() time.Duration {
return config.GetConfig().CacheTime.MaxPostIdCacheTime
})
cachemanager.NewMemoryMapCache(nil, dao.GetUserById, c.CacheTime.UserInfoCacheTime, "userData", func() time.Duration {
return config.GetConfig().CacheTime.UserInfoCacheTime
})
cachemanager.NewMemoryMapCache(nil, dao.GetUserByName, c.CacheTime.UserInfoCacheTime, "usernameToUserData", func() time.Duration {
return config.GetConfig().CacheTime.UserInfoCacheTime
})
cachemanager.NewVarMemoryCache(dao.AllUsername, c.CacheTime.UserInfoCacheTime, "allUsername", func() time.Duration {
return config.GetConfig().CacheTime.UserInfoCacheTime
})
cachemanager.NewVarMemoryCache(SiteFeed, time.Hour, "siteFeed")
cachemanager.NewMemoryMapCache(nil, PostFeed, time.Hour, "postFeed")
cachemanager.NewVarMemoryCache(CommentsFeed, time.Hour, "commentsFeed")
cachemanager.NewMemoryMapCache[string, string](nil, nil, 15*time.Minute, "NewComment")
InitFeed()
}
type Arch struct {
data []models.PostArchive
fn func(context.Context) ([]models.PostArchive, error)
month time.Month
}
var arch = reload.Vars(Arch{
fn: dao.Archives,
}, "archives-year-month-data")
func Archives(ctx context.Context) []models.PostArchive {
a := arch.Load()
data := a.data
l := len(data)
m := time.Now().Month()
if l < 1 || a.month != m {
r, err := a.fn(ctx)
if err != nil {
logs.Error(err, "set cache Archives fail")
return nil
}
a.month = m
a.data = r
arch.Store(a)
data = r
}
return data
}

39
app/pkg/cache/categoryandtag.go vendored Normal file
View File

@ -0,0 +1,39 @@
package cache
import (
"context"
"github.com/fthvgb1/wp-go/app/pkg/logs"
"github.com/fthvgb1/wp-go/app/pkg/models"
"github.com/fthvgb1/wp-go/cache/cachemanager"
"github.com/fthvgb1/wp-go/helper/slice"
"time"
)
// CategoriesTags get all categories or tags
//
// query func see dao.CategoriesAndTags
//
// t is constraints.Tag or constraints.Category
func CategoriesTags(ctx context.Context, t ...string) []models.TermsMy {
tt := ""
if len(t) > 0 {
tt = t[0]
}
r, err := cachemanager.GetBy[[]models.TermsMy]("categoryAndTagsData", ctx, tt, time.Second)
logs.IfError(err, "get category fail")
return r
}
func AllCategoryTagsNames(ctx context.Context, t ...string) map[string]struct{} {
tt := ""
if len(t) > 0 {
tt = t[0]
}
r, err := cachemanager.GetBy[[]models.TermsMy]("categoryAndTagsData", ctx, tt, time.Second)
if err != nil {
logs.Error(err, "get category fail")
return nil
}
return slice.ToMap(r, func(t models.TermsMy) (string, struct{}) {
return t.Name, struct{}{}
}, true)
}

124
app/pkg/cache/comments.go vendored Normal file
View File

@ -0,0 +1,124 @@
package cache
import (
"context"
"fmt"
"github.com/fthvgb1/wp-go/app/pkg/dao"
"github.com/fthvgb1/wp-go/app/pkg/logs"
"github.com/fthvgb1/wp-go/app/pkg/models"
"github.com/fthvgb1/wp-go/app/wpconfig"
"github.com/fthvgb1/wp-go/cache"
"github.com/fthvgb1/wp-go/cache/cachemanager"
"github.com/fthvgb1/wp-go/helper"
"github.com/fthvgb1/wp-go/helper/number"
str "github.com/fthvgb1/wp-go/helper/strings"
"time"
)
// RecentComments query func see RecentComment
func RecentComments(ctx context.Context, n int) (r []models.Comments) {
nn := number.Max(n, 10)
r, err := cachemanager.GetVarVal[[]models.Comments]("recentComments", ctx, time.Second, ctx, nn)
if len(r) > n {
r = r[0:n]
}
logs.IfError(err, "get recent comment fail")
return
}
// PostTopLevelCommentIds query func see PostTopComments
func PostTopLevelCommentIds(ctx context.Context, postId uint64, page, limit, total int, order string, a ...any) ([]uint64, error) {
var key string
if len(a) > 0 {
key = helper.ParseArgs("", a...)
}
if key == "" {
key = fmt.Sprintf("%d-%d-%d-%d-%s", postId, page, limit, total, order)
}
return cachemanager.GetBy[[]uint64]("PostCommentsIds", ctx,
key, time.Second, postId, page, limit, 0, order)
}
// GetCommentById query func see dao.GetCommentByIds
func GetCommentById(ctx context.Context, id uint64) (models.Comments, error) {
return cachemanager.GetBy[models.Comments]("postCommentData", ctx, id, time.Second)
}
// GetCommentDataByIds query func see dao.GetCommentByIds
func GetCommentDataByIds(ctx context.Context, ids []uint64) ([]models.Comments, error) {
return cachemanager.GetBatchBy[models.Comments]("postCommentData", ctx, ids, time.Second)
}
func NewCommentCache() *cache.MapCache[string, string] {
r, _ := cachemanager.GetMapCache[string, string]("NewComment")
return r
}
func PostTopComments(ctx context.Context, _ string, a ...any) ([]uint64, error) {
postId := a[0].(uint64)
page := a[1].(int)
limit := a[2].(int)
total := a[3].(int)
order := helper.ParseArgs("", a...)
if order == "" {
order = wpconfig.GetOption("comment_order")
}
v, _, err := dao.PostCommentsIds(ctx, postId, page, limit, total, order)
if err != nil {
return nil, err
}
return v, nil
}
func RecentComment(ctx context.Context, a ...any) (r []models.Comments, err error) {
r, err = dao.RecentComments(ctx, a...)
if err != nil {
return r, err
}
for i, comment := range r {
r[i].CommentAuthorUrl, err = GetCommentUrl(ctx, comment.CommentId, comment.CommentPostId)
if err != nil {
return nil, err
}
}
return
}
func GetCommentUrl(ctx context.Context, commentId, postId uint64) (string, error) {
if wpconfig.GetOption("page_comments") != "1" {
return fmt.Sprintf("/p/%d#comment-%d", postId, commentId), nil
}
commentsPerPage := str.ToInteger(wpconfig.GetOption("comments_per_page"), 5)
topCommentId, err := AncestorCommentId(ctx, commentId)
if err != nil {
return "", err
}
totalNum, err := cachemanager.GetBy[int]("postTopCommentsNum", ctx, postId, time.Second)
if err != nil {
return "", err
}
if totalNum <= commentsPerPage {
return fmt.Sprintf("/p/%d#comment-%d", postId, commentId), nil
}
num, err := dao.PreviousCommentNum(ctx, topCommentId, postId)
if err != nil {
return "", err
}
order := wpconfig.GetOption("comment_order")
page := number.DivideCeil(num+1, commentsPerPage)
if order == "desc" {
page = number.DivideCeil(totalNum-num, commentsPerPage)
}
return fmt.Sprintf("/p/%d/comment-page-%d/#comment-%d", postId, page, commentId), nil
}
func AncestorCommentId(ctx context.Context, commentId uint64) (uint64, error) {
comment, err := GetCommentById(ctx, commentId)
if err != nil {
return 0, err
}
if comment.CommentParent == 0 {
return comment.CommentId, nil
}
return AncestorCommentId(ctx, comment.CommentParent)
}

View File

@ -1,17 +1,20 @@
package cache package cache
import ( import (
"context"
"errors"
"fmt" "fmt"
"github.com/fthvgb1/wp-go/app/pkg/logs"
"github.com/fthvgb1/wp-go/app/pkg/models"
"github.com/fthvgb1/wp-go/app/plugins"
"github.com/fthvgb1/wp-go/app/plugins/wpposts"
"github.com/fthvgb1/wp-go/app/wpconfig"
"github.com/fthvgb1/wp-go/cache" "github.com/fthvgb1/wp-go/cache"
"github.com/fthvgb1/wp-go/cache/cachemanager"
"github.com/fthvgb1/wp-go/helper/slice" "github.com/fthvgb1/wp-go/helper/slice"
"github.com/fthvgb1/wp-go/internal/pkg/logs" str "github.com/fthvgb1/wp-go/helper/strings"
"github.com/fthvgb1/wp-go/internal/pkg/models"
"github.com/fthvgb1/wp-go/internal/plugins"
"github.com/fthvgb1/wp-go/internal/wpconfig"
"github.com/fthvgb1/wp-go/plugin/digest" "github.com/fthvgb1/wp-go/plugin/digest"
"github.com/fthvgb1/wp-go/rss2" "github.com/fthvgb1/wp-go/rss2"
"github.com/gin-gonic/gin"
"strconv"
"strings" "strings"
"time" "time"
) )
@ -21,31 +24,36 @@ var templateRss rss2.Rss2
func InitFeed() { func InitFeed() {
templateRss = rss2.Rss2{ templateRss = rss2.Rss2{
Title: wpconfig.Options.Value("blogname"), Title: wpconfig.GetOption("blogname"),
AtomLink: fmt.Sprintf("%s/feed", wpconfig.Options.Value("home")), AtomLink: fmt.Sprintf("%s/feed", wpconfig.GetOption("home")),
Link: wpconfig.Options.Value("siteurl"), Link: wpconfig.GetOption("siteurl"),
Description: wpconfig.Options.Value("blogdescription"), Description: wpconfig.GetOption("blogdescription"),
Language: "zh-CN", Language: wpconfig.GetLang(),
UpdatePeriod: "hourly", UpdatePeriod: "hourly",
UpdateFrequency: 1, UpdateFrequency: 1,
Generator: wpconfig.Options.Value("home"), Generator: wpconfig.GetOption("home"),
} }
} }
// CommentsFeedCache query func see CommentsFeed
func CommentsFeedCache() *cache.VarCache[[]string] { func CommentsFeedCache() *cache.VarCache[[]string] {
return commentsFeedCache r, _ := cachemanager.GetVarCache[[]string]("commentsFeed")
return r
} }
// FeedCache query func see SiteFeed
func FeedCache() *cache.VarCache[[]string] { func FeedCache() *cache.VarCache[[]string] {
return feedCache r, _ := cachemanager.GetVarCache[[]string]("siteFeed")
return r
} }
// PostFeedCache query func see PostFeed
func PostFeedCache() *cache.MapCache[string, string] { func PostFeedCache() *cache.MapCache[string, string] {
return postFeedCache r, _ := cachemanager.GetMapCache[string, string]("postFeed")
return r
} }
func feed(arg ...any) (xml []string, err error) { func SiteFeed(c context.Context, _ ...any) (xml []string, err error) {
c := arg[0].(*gin.Context)
r := RecentPosts(c, 10) r := RecentPosts(c, 10)
ids := slice.Map(r, func(t models.Posts) uint64 { ids := slice.Map(r, func(t models.Posts) uint64 {
return t.Id return t.Id
@ -54,21 +62,22 @@ func feed(arg ...any) (xml []string, err error) {
if err != nil { if err != nil {
return return
} }
site := wpconfig.GetOption("siteurl")
rs := templateRss rs := templateRss
rs.LastBuildDate = time.Now().Format(timeFormat) rs.LastBuildDate = time.Now().Format(timeFormat)
rs.Items = slice.Map(posts, func(t models.Posts) rss2.Item { rs.Items = slice.Map(posts, func(t models.Posts) rss2.Item {
desc := "无法提供摘要。这是一篇受保护的文章。" desc := "无法提供摘要。这是一篇受保护的文章。"
plugins.PasswordProjectTitle(&t)
if t.PostPassword != "" { if t.PostPassword != "" {
plugins.PasswdProjectContent(&t) wpposts.PasswordProjectTitle(&t)
wpposts.PasswdProjectContent(&t)
} else { } else {
desc = digest.Raw(t.PostContent, 55, fmt.Sprintf("/p/%d", t.Id)) desc = plugins.Digests(t.PostContent, t.Id, 55, nil)
} }
l := "" l := ""
if t.CommentStatus == "open" && t.CommentCount > 0 { if t.CommentStatus == "open" && t.CommentCount > 0 {
l = fmt.Sprintf("%s/p/%d#comments", wpconfig.Options.Value("siteurl"), t.Id) l = fmt.Sprintf("%s/p/%d#comments", site, t.Id)
} else if t.CommentStatus == "open" && t.CommentCount == 0 { } else if t.CommentStatus == "open" && t.CommentCount == 0 {
l = fmt.Sprintf("%s/p/%d#respond", wpconfig.Options.Value("siteurl"), t.Id) l = fmt.Sprintf("%s/p/%d#respond", site, t.Id)
} }
user := GetUserById(c, t.PostAuthor) user := GetUserById(c, t.PostAuthor)
@ -80,8 +89,8 @@ func feed(arg ...any) (xml []string, err error) {
Content: t.PostContent, Content: t.PostContent,
Category: strings.Join(t.Categories, "、"), Category: strings.Join(t.Categories, "、"),
CommentLink: l, CommentLink: l,
CommentRss: fmt.Sprintf("%s/p/%d/feed", wpconfig.Options.Value("siteurl"), t.Id), CommentRss: fmt.Sprintf("%s/p/%d/feed", site, t.Id),
Link: fmt.Sprintf("%s/p/%d", wpconfig.Options.Value("siteurl"), t.Id), Link: fmt.Sprintf("%s/p/%d", site, t.Id),
Description: desc, Description: desc,
PubDate: t.PostDateGmt.Format(timeFormat), PubDate: t.PostDateGmt.Format(timeFormat),
} }
@ -90,45 +99,46 @@ func feed(arg ...any) (xml []string, err error) {
return return
} }
func postFeed(arg ...any) (x string, err error) { func PostFeed(c context.Context, id string, _ ...any) (x string, err error) {
c := arg[0].(*gin.Context) ID := str.ToInteger[uint64](id, 0)
id := arg[1].(string)
Id := 0
if id != "" {
Id, err = strconv.Atoi(id)
if err != nil {
return
}
}
ID := uint64(Id)
maxId, err := GetMaxPostId(c) maxId, err := GetMaxPostId(c)
logs.ErrPrintln(err, "get max post id") logs.IfError(err, "get max post id")
if ID > maxId || err != nil { if ID < 1 || ID > maxId || err != nil {
return return
} }
post, err := GetPostById(c, ID) post, err := GetPostById(c, ID)
if post.Id == 0 || err != nil { if post.Id == 0 || err != nil {
return return
} }
plugins.PasswordProjectTitle(&post) limit := str.ToInteger(wpconfig.GetOption("comments_per_page"), 10)
comments, err := PostComments(c, post.Id) ids, err := PostTopLevelCommentIds(c, ID, 1, limit, 0, "desc", "latest-comment")
if err != nil {
return
}
comments, err := GetCommentDataByIds(c, ids)
if err != nil { if err != nil {
return return
} }
rs := templateRss rs := templateRss
site := wpconfig.GetOption("siteurl")
rs.Title = fmt.Sprintf("《%s》的评论", post.PostTitle) rs.Title = fmt.Sprintf("《%s》的评论", post.PostTitle)
rs.AtomLink = fmt.Sprintf("%s/p/%d/feed", wpconfig.Options.Value("siteurl"), post.Id) rs.AtomLink = fmt.Sprintf("%s/p/%d/feed", site, post.Id)
rs.Link = fmt.Sprintf("%s/p/%d", wpconfig.Options.Value("siteurl"), post.Id) rs.Link = fmt.Sprintf("%s/p/%d", site, post.Id)
rs.LastBuildDate = time.Now().Format(timeFormat) rs.LastBuildDate = time.Now().Format(timeFormat)
if post.PostPassword != "" { if post.PostPassword != "" {
wpposts.PasswordProjectTitle(&post)
wpposts.PasswdProjectContent(&post)
if len(comments) > 0 { if len(comments) > 0 {
plugins.PasswdProjectContent(&post)
t := comments[len(comments)-1] t := comments[len(comments)-1]
u, err := GetCommentUrl(c, t.CommentId, t.CommentPostId)
if err != nil {
return "", err
}
rs.Items = []rss2.Item{ rs.Items = []rss2.Item{
{ {
Title: fmt.Sprintf("评价者:%s", t.CommentAuthor), Title: fmt.Sprintf("评价者:%s", t.CommentAuthor),
Link: fmt.Sprintf("%s/p/%d#comment-%d", wpconfig.Options.Value("siteurl"), post.Id, t.CommentId), Link: fmt.Sprintf("%s%s", site, u),
Creator: t.CommentAuthor, Creator: t.CommentAuthor,
PubDate: t.CommentDateGmt.Format(timeFormat), PubDate: t.CommentDateGmt.Format(timeFormat),
Guid: fmt.Sprintf("%s#comment-%d", post.Guid, t.CommentId), Guid: fmt.Sprintf("%s#comment-%d", post.Guid, t.CommentId),
@ -139,9 +149,14 @@ func postFeed(arg ...any) (x string, err error) {
} }
} else { } else {
rs.Items = slice.Map(comments, func(t models.Comments) rss2.Item { rs.Items = slice.Map(comments, func(t models.Comments) rss2.Item {
u, er := GetCommentUrl(c, t.CommentId, t.CommentPostId)
if er != nil {
err = errors.Join(err, er)
return rss2.Item{}
}
return rss2.Item{ return rss2.Item{
Title: fmt.Sprintf("评价者:%s", t.CommentAuthor), Title: fmt.Sprintf("评价者:%s", t.CommentAuthor),
Link: fmt.Sprintf("%s/p/%d#comment-%d", wpconfig.Options.Value("siteurl"), post.Id, t.CommentId), Link: fmt.Sprintf("%s%s", site, u),
Creator: t.CommentAuthor, Creator: t.CommentAuthor,
PubDate: t.CommentDateGmt.Format(timeFormat), PubDate: t.CommentDateGmt.Format(timeFormat),
Guid: fmt.Sprintf("%s#comment-%d", post.Guid, t.CommentId), Guid: fmt.Sprintf("%s#comment-%d", post.Guid, t.CommentId),
@ -154,14 +169,14 @@ func postFeed(arg ...any) (x string, err error) {
return return
} }
func commentsFeed(args ...any) (r []string, err error) { func CommentsFeed(c context.Context, _ ...any) (r []string, err error) {
c := args[0].(*gin.Context)
commens := RecentComments(c, 10) commens := RecentComments(c, 10)
rs := templateRss rs := templateRss
rs.Title = fmt.Sprintf("\"%s\"的评论", wpconfig.Options.Value("blogname")) rs.Title = fmt.Sprintf("\"%s\"的评论", wpconfig.GetOption("blogname"))
rs.LastBuildDate = time.Now().Format(timeFormat) rs.LastBuildDate = time.Now().Format(timeFormat)
rs.AtomLink = fmt.Sprintf("%s/comments/feed", wpconfig.Options.Value("siteurl")) site := wpconfig.GetOption("siteurl")
com, err := GetCommentByIds(c, slice.Map(commens, func(t models.Comments) uint64 { rs.AtomLink = fmt.Sprintf("%s/comments/feed", site)
com, err := GetCommentDataByIds(c, slice.Map(commens, func(t models.Comments) uint64 {
return t.CommentId return t.CommentId
})) }))
if nil != err { if nil != err {
@ -169,19 +184,23 @@ func commentsFeed(args ...any) (r []string, err error) {
} }
rs.Items = slice.Map(com, func(t models.Comments) rss2.Item { rs.Items = slice.Map(com, func(t models.Comments) rss2.Item {
post, _ := GetPostById(c, t.CommentPostId) post, _ := GetPostById(c, t.CommentPostId)
plugins.PasswordProjectTitle(&post)
desc := "评论受保护:要查看请输入密码。" desc := "评论受保护:要查看请输入密码。"
content := t.CommentContent content := t.CommentContent
if post.PostPassword != "" { if post.PostPassword != "" {
plugins.PasswdProjectContent(&post) wpposts.PasswordProjectTitle(&post)
wpposts.PasswdProjectContent(&post)
content = post.PostContent content = post.PostContent
} else { } else {
desc = digest.ClearHtml(t.CommentContent) content = digest.StripTags(t.CommentContent, "")
content = desc
} }
u, er := GetCommentUrl(c, t.CommentId, t.CommentPostId)
if er != nil {
errors.Join(err, er)
}
u = str.Join(site, u)
return rss2.Item{ return rss2.Item{
Title: fmt.Sprintf("%s对《%s》的评论", t.CommentAuthor, post.PostTitle), Title: fmt.Sprintf("%s对《%s》的评论", t.CommentAuthor, post.PostTitle),
Link: fmt.Sprintf("%s/p/%d#comment-%d", wpconfig.Options.Value("siteurl"), post.Id, t.CommentId), Link: u,
Creator: t.CommentAuthor, Creator: t.CommentAuthor,
Description: desc, Description: desc,
PubDate: t.CommentDateGmt.Format(timeFormat), PubDate: t.CommentDateGmt.Format(timeFormat),

17
app/pkg/cache/postmeta.go vendored Normal file
View File

@ -0,0 +1,17 @@
package cache
import (
"context"
"github.com/fthvgb1/wp-go/cache/cachemanager"
"time"
)
// GetPostMetaByPostIds query func see dao.GetPostMetaByPostIds
func GetPostMetaByPostIds(ctx context.Context, ids []uint64) ([]map[string]any, error) {
return cachemanager.GetBatchBy[map[string]any]("postMetaData", ctx, ids, time.Second)
}
// GetPostMetaByPostId query func see dao.GetPostMetaByPostIds
func GetPostMetaByPostId(ctx context.Context, id uint64) (map[string]any, error) {
return cachemanager.GetBy[map[string]any]("postMetaData", ctx, id, time.Second)
}

91
app/pkg/cache/posts.go vendored Normal file
View File

@ -0,0 +1,91 @@
package cache
import (
"context"
"fmt"
"github.com/fthvgb1/wp-go/app/pkg/dao"
"github.com/fthvgb1/wp-go/app/pkg/logs"
"github.com/fthvgb1/wp-go/app/pkg/models"
"github.com/fthvgb1/wp-go/app/wpconfig"
"github.com/fthvgb1/wp-go/cache/cachemanager"
"github.com/fthvgb1/wp-go/helper/number"
"github.com/fthvgb1/wp-go/helper/slice"
str "github.com/fthvgb1/wp-go/helper/strings"
"time"
)
// GetPostById query func see dao.GetPostsByIds
func GetPostById(ctx context.Context, id uint64) (models.Posts, error) {
return cachemanager.GetBy[models.Posts]("postData", ctx, id, time.Second)
}
// GetPostsByIds query func see dao.GetPostsByIds
func GetPostsByIds(ctx context.Context, ids []uint64) ([]models.Posts, error) {
return cachemanager.GetBatchBy[models.Posts]("postData", ctx, ids, time.Second)
}
// SearchPost query func see dao.SearchPostIds
func SearchPost(ctx context.Context, key string, args ...any) (r []models.Posts, total int, err error) {
ids, err := cachemanager.GetBy[dao.PostIds]("searchPostIds", ctx, key, time.Second, args...)
if err != nil {
return
}
total = ids.Length
r, err = GetPostsByIds(ctx, ids.Ids)
return
}
// PostLists query func see dao.SearchPostIds
func PostLists(ctx context.Context, key string, args ...any) (r []models.Posts, total int, err error) {
ids, err := cachemanager.GetBy[dao.PostIds]("listPostIds", ctx, key, time.Second, args...)
if err != nil {
return
}
total = ids.Length
r, err = GetPostsByIds(ctx, ids.Ids)
return
}
// GetMaxPostId query func see dao.GetMaxPostId
func GetMaxPostId(ctx context.Context) (uint64, error) {
return cachemanager.GetVarVal[uint64]("maxPostId", ctx, time.Second)
}
// RecentPosts query func see dao.RecentPosts
func RecentPosts(ctx context.Context, n int) (r []models.Posts) {
nn := n
feedNum := str.ToInteger(wpconfig.GetOption("posts_per_rss"), 10)
nn = number.Max(n, feedNum)
r, err := cachemanager.GetVarVal[[]models.Posts]("recentPosts", ctx, time.Second, nn)
if n < len(r) {
r = r[:n]
}
logs.IfError(err, "get recent post")
return
}
// GetContextPost query func see dao.GetPostContext
func GetContextPost(ctx context.Context, id uint64, date time.Time) (prev, next models.Posts, err error) {
postCtx, err := cachemanager.GetBy[dao.PostContext]("postContext", ctx, id, time.Second, date)
if err != nil {
return models.Posts{}, models.Posts{}, err
}
prev = postCtx.Prev
next = postCtx.Next
return
}
// GetMonthPostIds query func see dao.MonthPost
func GetMonthPostIds(ctx context.Context, year, month string, page, limit int, order string) (r []models.Posts, total int, err error) {
res, err := cachemanager.GetBy[[]uint64]("monthPostIds", ctx, fmt.Sprintf("%s%s", year, month), time.Second, year, month)
if err != nil {
return
}
if order == "desc" {
res = slice.Reverse(res)
}
total = len(res)
rr := slice.Pagination(res, page, limit)
r, err = GetPostsByIds(ctx, rr)
return
}

26
app/pkg/cache/users.go vendored Normal file
View File

@ -0,0 +1,26 @@
package cache
import (
"context"
"github.com/fthvgb1/wp-go/app/pkg/logs"
"github.com/fthvgb1/wp-go/app/pkg/models"
"github.com/fthvgb1/wp-go/cache/cachemanager"
"time"
)
// GetUserByName query func see dao.GetUserByName
func GetUserByName(ctx context.Context, username string) (models.Users, error) {
return cachemanager.GetBy[models.Users]("usernameToUserData", ctx, username, time.Second)
}
// GetAllUsername query func see dao.AllUsername
func GetAllUsername(ctx context.Context) (map[string]uint64, error) {
return cachemanager.GetVarVal[map[string]uint64]("allUsername", ctx, time.Second)
}
// GetUserById query func see dao.GetUserById
func GetUserById(ctx context.Context, uid uint64) models.Users {
r, err := cachemanager.GetBy[models.Users]("userData", ctx, uid, time.Second)
logs.IfError(err, "get user", uid)
return r
}

147
app/pkg/config/config.go Normal file
View File

@ -0,0 +1,147 @@
package config
import (
"fmt"
"github.com/fthvgb1/wp-go/safety"
"gopkg.in/yaml.v2"
"io"
"net/http"
"os"
"strings"
"time"
)
var config safety.Var[Config]
func GetConfig() Config {
return config.Load()
}
type Config struct {
Ssl Ssl `yaml:"ssl" json:"ssl"`
Mysql Mysql `yaml:"mysql" json:"mysql"`
Mail Mail `yaml:"mail" json:"mail"`
CacheTime CacheTime `yaml:"cacheTime" json:"cacheTime"`
PluginPath string `yaml:"pluginPath" json:"pluginPath"`
ExternScript []string `json:"externScript" yaml:"externScript"`
DigestWordCount int `yaml:"digestWordCount" json:"digestWordCount,omitempty"`
DigestAllowTag string `yaml:"digestAllowTag" json:"digestAllowTag"`
MaxRequestSleepNum int64 `yaml:"maxRequestSleepNum" json:"maxRequestSleepNum,omitempty"`
MaxRequestNum int64 `yaml:"maxRequestNum" json:"maxRequestNum,omitempty"`
SingleIpSearchNum int64 `yaml:"singleIpSearchNum" json:"singleIpSearchNum,omitempty"`
Gzip bool `yaml:"gzip" json:"gzip,omitempty"`
PostCommentUrl string `yaml:"postCommentUrl" json:"postCommentUrl,omitempty"`
TrustIps []string `yaml:"trustIps" json:"trustIps,omitempty"`
TrustServerNames []string `yaml:"trustServerNames" json:"trustServerNames,omitempty"`
Theme string `yaml:"theme" json:"theme,omitempty"`
PostOrder string `yaml:"postOrder" json:"postOrder,omitempty"`
UploadDir string `yaml:"uploadDir" json:"uploadDir,omitempty"`
Pprof string `yaml:"pprof" json:"pprof,omitempty"`
ListPagePlugins []string `yaml:"listPagePlugins" json:"listPagePlugins,omitempty"`
PaginationStep int `yaml:"paginationStep" json:"paginationStep,omitempty"`
ShowQuerySql bool `yaml:"showQuerySql" json:"showQuerySql,omitempty"`
Plugins []string `yaml:"plugins" json:"plugins,omitempty"`
LogOutput string `yaml:"logOutput" json:"logOutput,omitempty"`
WpDir string `yaml:"wpDir" json:"wpDir"`
}
type CacheTime struct {
CacheControl time.Duration `yaml:"cacheControl" json:"cacheControl,omitempty"`
RecentPostCacheTime time.Duration `yaml:"recentPostCacheTime" json:"recentPostCacheTime,omitempty"`
CategoryCacheTime time.Duration `yaml:"categoryCacheTime" json:"categoryCacheTime,omitempty"`
ArchiveCacheTime time.Duration `yaml:"archiveCacheTime" json:"archiveCacheTime,omitempty"`
ContextPostCacheTime time.Duration `yaml:"contextPostCacheTime" json:"contextPostCacheTime,omitempty"`
RecentCommentsCacheTime time.Duration `yaml:"recentCommentsCacheTime" json:"recentCommentsCacheTime,omitempty"`
DigestCacheTime time.Duration `yaml:"digestCacheTime" json:"digestCacheTime,omitempty"`
PostListCacheTime time.Duration `yaml:"postListCacheTime" json:"postListCacheTime,omitempty"`
SearchPostCacheTime time.Duration `yaml:"searchPostCacheTime" json:"searchPostCacheTime,omitempty"`
MonthPostCacheTime time.Duration `yaml:"monthPostCacheTime" json:"monthPostCacheTime,omitempty"`
PostDataCacheTime time.Duration `yaml:"postDataCacheTime" json:"postDataCacheTime,omitempty"`
PostCommentsCacheTime time.Duration `yaml:"postCommentsCacheTime" json:"postCommentsCacheTime,omitempty"`
CrontabClearCacheTime time.Duration `yaml:"crontabClearCacheTime" json:"crontabClearCacheTime,omitempty"`
MaxPostIdCacheTime time.Duration `yaml:"maxPostIdCacheTime" json:"maxPostIdCacheTime,omitempty"`
UserInfoCacheTime time.Duration `yaml:"userInfoCacheTime" json:"userInfoCacheTime,omitempty"`
CommentsCacheTime time.Duration `yaml:"commentsCacheTime" json:"commentsCacheTime,omitempty"`
SleepTime []time.Duration `yaml:"sleepTime" json:"sleepTime,omitempty"`
CommentsIncreaseUpdateTime time.Duration `yaml:"commentsIncreaseUpdateTime" json:"commentsIncreaseUpdateTime"`
}
type Ssl struct {
Cert string `yaml:"cert" json:"cert,omitempty"`
Key string `yaml:"key" json:"key,omitempty"`
}
type Mail struct {
User string `yaml:"user" json:"user,omitempty"`
Alias string `yaml:"alias" json:"alias,omitempty"`
Pass string `yaml:"pass" json:"pass,omitempty"`
Host string `yaml:"host" json:"host,omitempty"`
Port int `yaml:"port" json:"port,omitempty"`
InsecureSkipVerify bool `yaml:"insecureSkipVerify" json:"insecureSkipVerify,omitempty"`
}
type Mysql struct {
Dsn Dsn `yaml:"dsn" json:"dsn"`
Pool Pool `yaml:"pool" json:"pool"`
}
func GetCustomizedConfig[T any]() (T, error) {
var r T
err := yaml.Unmarshal(fileData.Load(), &r)
return r, err
}
var fileData = safety.NewVar([]byte{})
func InitConfig(conf string) error {
if conf == "" {
conf = "config.yaml"
}
var file []byte
var err error
if strings.Contains(conf, "http") {
get, err := http.Get(conf)
if err != nil {
return err
}
defer get.Body.Close()
file, err = io.ReadAll(get.Body)
} else {
file, err = os.ReadFile(conf)
}
if err != nil {
return err
}
fileData.Store(file)
var c Config
err = yaml.Unmarshal(file, &c)
if err != nil {
return err
}
config.Store(c)
return nil
}
type Dsn struct {
Host string `yaml:"host" json:"host,omitempty"`
Port string `yaml:"port" json:"port,omitempty"`
Db string `yaml:"db" json:"db,omitempty"`
User string `yaml:"user" json:"user,omitempty"`
Password string `yaml:"password" json:"password,omitempty"`
Charset string `yaml:"charset" json:"charset,omitempty"`
}
func (m Dsn) GetDsn() string {
if m.Charset == "" {
m.Charset = "utf8"
}
t := "%s:%s@tcp(%s:%s)/%s?charset=%s&parseTime=True&loc=Local"
return fmt.Sprintf(t, m.User, m.Password, m.Host, m.Port, m.Db, m.Charset)
}
type Pool struct {
ConnMaxIdleTime time.Duration `yaml:"connMaxIdleTime" json:"connMaxIdleTime,omitempty"`
MaxOpenConn int `yaml:"maxOpenConn" json:"maxOpenConn,omitempty"`
MaxIdleConn int `yaml:"maxIdleConn" json:"maxIdleConn,omitempty"`
ConnMaxLifetime time.Duration `yaml:"connMaxLifetime" json:"connMaxLifetime,omitempty"`
}

View File

@ -0,0 +1,43 @@
package config
import (
"github.com/gin-gonic/gin/binding"
"github.com/go-playground/locales/en"
"github.com/go-playground/locales/zh"
ut "github.com/go-playground/universal-translator"
"github.com/go-playground/validator/v10"
enTrans "github.com/go-playground/validator/v10/translations/en"
zhTrans "github.com/go-playground/validator/v10/translations/zh"
"reflect"
)
var enT ut.Translator
var zhT ut.Translator
func GetZh() ut.Translator {
return zhT
}
func GetEn() ut.Translator {
return enT
}
func InitTrans() error {
if validate, ok := binding.Validator.Engine().(*validator.Validate); ok {
ens := en.New()
uni := ut.New(ens, zh.New(), ens)
zhT, _ = uni.GetTranslator("zh")
enT, _ = uni.GetTranslator("en")
err := enTrans.RegisterDefaultTranslations(validate, enT)
if err != nil {
return err
}
validate.RegisterTagNameFunc(func(field reflect.StructField) string {
return field.Tag.Get("label")
})
err = zhTrans.RegisterDefaultTranslations(validate, zhT)
if err != nil {
return err
}
}
return nil
}

View File

@ -0,0 +1,10 @@
package blocks
const (
Search = "block-search"
RecentPosts = "block-recent-posts"
RecentComments = "block-recent-comments"
Archive = "block-archives"
Categories = "block-categories"
Meta = "block-meta"
)

View File

@ -0,0 +1,30 @@
package constraints
const (
Home = "Home"
Archive = "Archive"
Category = "Category"
Tag = "Tag"
Search = "Search"
Author = "Author"
Detail = "Detail"
NoRoute = "NoRoute"
Ok = "Ok"
Error404 = "Error404"
ParamError = "ParamError"
InternalErr = "InternalErr"
AllStats = "AllStats"
AllScene = "AllScene"
PipeData = "PipeData"
PipeMiddleware = "PipeMiddleware"
PipeRender = "PipeRender"
Defaults = "default"
HeadScript = "headScript"
FooterScript = "footerScript"
SidebarsWidgets = "sidebarsWidgets"
)

View File

@ -0,0 +1,12 @@
package widgets
const (
Search = "widget-search"
RecentPosts = "widget-recent-posts"
RecentComments = "widget-recent-comments"
Archive = "widget-archives"
Categories = "widget-categories"
Meta = "widget-meta"
Widget = "widget"
)

183
app/pkg/dao/comments.go Normal file
View File

@ -0,0 +1,183 @@
package dao
import (
"context"
"database/sql"
"errors"
"github.com/fthvgb1/wp-go/app/pkg/models"
"github.com/fthvgb1/wp-go/app/wpconfig"
"github.com/fthvgb1/wp-go/helper"
"github.com/fthvgb1/wp-go/helper/number"
"github.com/fthvgb1/wp-go/helper/slice"
str "github.com/fthvgb1/wp-go/helper/strings"
"github.com/fthvgb1/wp-go/model"
)
// RecentComments
// param context.Context
func RecentComments(ctx context.Context, a ...any) (r []models.Comments, err error) {
n := helper.ParseArgs(10, a...)
return model.Finds[models.Comments](ctx, model.Conditions(
model.Where(model.SqlBuilder{
{"comment_approved", "1"},
{"post_status", "publish"},
}),
model.Fields("comment_ID,comment_author,comment_post_ID,post_title"),
model.Order(model.SqlBuilder{{"comment_date_gmt", "desc"}}),
model.Join(model.SqlBuilder{{"a", "left join", "wp_posts b", "a.comment_post_ID=b.ID"}}),
model.Limit(n),
))
}
// PostComments
// param1 context.Context
// param2 postId
func PostComments(ctx context.Context, postId uint64, _ ...any) ([]uint64, error) {
r, err := model.ChunkFind[models.Comments](ctx, 300, model.Conditions(
model.Where(model.SqlBuilder{
{"comment_approved", "1"},
{"comment_post_ID", "=", number.IntToString(postId), "int"},
}),
model.Fields("comment_ID"),
model.Order(model.SqlBuilder{
{"comment_date_gmt", "asc"},
{"comment_ID", "asc"},
})),
)
if err != nil {
return nil, err
}
return slice.Map(r, func(t models.Comments) uint64 {
return t.CommentId
}), err
}
func GetCommentByIds(ctx context.Context, ids []uint64, _ ...any) (map[uint64]models.Comments, error) {
if len(ids) < 1 {
return nil, nil
}
m := make(map[uint64]models.Comments)
off := 0
for {
id := slice.Slice(ids, off, 500)
if len(id) < 1 {
break
}
r, err := model.Finds[models.Comments](ctx, model.Conditions(
model.Where(model.SqlBuilder{
{"comment_ID", "in", ""}, {"comment_approved", "1"},
}),
model.Fields("*"),
model.In(slice.ToAnySlice(id)),
))
if err != nil {
return m, err
}
for _, comments := range r {
m[comments.CommentId] = comments
}
off += 500
}
return m, nil
}
func CommentNum(ctx context.Context, postId uint64, _ ...any) (int, error) {
n, err := model.GetField[models.Posts](ctx, "comment_count", model.Conditions(
model.Where(model.SqlBuilder{{"ID", "=", number.IntToString(postId), "int"}})))
if err != nil {
return 0, err
}
return str.ToInteger(n, 0), err
}
func PostTopCommentNum(ctx context.Context, postId uint64, _ ...any) (int, error) {
v, err := model.GetField[models.Comments](ctx, "count(*) num", model.Conditions(
model.Where(postTopCommentNumWhere(postId)),
))
if err != nil {
return 0, err
}
return str.ToInteger(v, 0), nil
}
func postTopCommentNumWhere(postId uint64) model.SqlBuilder {
threadComments := wpconfig.GetOption("thread_comments")
pageComments := wpconfig.GetOption("page_comments")
where := model.SqlBuilder{
{"comment_approved", "1"},
{"comment_post_ID", "=", number.IntToString(postId), "int"},
}
if pageComments != "1" || threadComments == "1" || "1" == wpconfig.GetOption("thread_comments_depth") {
where = append(where, []string{"comment_parent", "0"})
}
return where
}
func PostCommentsIds(ctx context.Context, postId uint64, page, limit, totalRaw int, order string) ([]uint64, int, error) {
condition := model.Conditions(
model.Where(postTopCommentNumWhere(postId)),
model.TotalRaw(totalRaw),
model.Fields("comment_ID"),
model.Order(model.SqlBuilder{
{"comment_date_gmt", order},
{"comment_ID", "asc"},
}),
)
var r []models.Comments
var total int
var err error
if limit < 1 {
r, err = model.ChunkFind[models.Comments](ctx, 300, condition)
total = len(r)
} else {
r, total, err = model.Pagination[models.Comments](ctx, condition, page, limit)
}
if err != nil && errors.Is(err, sql.ErrNoRows) {
err = nil
}
return slice.Map(r, func(t models.Comments) uint64 {
return t.CommentId
}), total, err
}
func CommentChildren(ctx context.Context, commentIds []uint64, _ ...any) (r map[uint64][]uint64, err error) {
rr, err := model.Finds[models.Comments](ctx, model.Conditions(
model.Where(model.SqlBuilder{
{"comment_parent", "in", ""},
{"comment_approved", "1"},
}),
model.In(slice.ToAnySlice(commentIds)),
model.Fields("comment_ID,comment_parent"),
))
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
err = nil
}
return
}
rrr := slice.GroupBy(rr, func(v models.Comments) (uint64, uint64) {
return v.CommentParent, v.CommentId
})
r = make(map[uint64][]uint64)
for _, id := range commentIds {
r[id] = rrr[id]
}
return
}
func PreviousCommentNum(ctx context.Context, commentId, postId uint64) (int, error) {
v, err := model.GetField[models.Comments](ctx, "count(*)", model.Conditions(
model.Where(model.SqlBuilder{
{"comment_approved", "1"},
{"comment_post_ID", "=", number.IntToString(postId), "int"},
{"comment_ID", "<", number.IntToString(commentId), "int"},
{"comment_parent", "=", "0", "int"},
}),
))
if err != nil {
return 0, err
}
return str.ToInteger(v, 0), nil
}

76
app/pkg/dao/common.go Normal file
View File

@ -0,0 +1,76 @@
package dao
import (
"context"
"github.com/fthvgb1/wp-go/app/pkg/constraints"
"github.com/fthvgb1/wp-go/app/pkg/models"
"github.com/fthvgb1/wp-go/app/wpconfig"
"github.com/fthvgb1/wp-go/helper"
"github.com/fthvgb1/wp-go/model"
)
var TotalRaw int64
type PostIds struct {
Ids []uint64
Length int
}
type PostContext struct {
Prev models.Posts
Next models.Posts
}
func CategoriesAndTags(ctx context.Context, t string, _ ...any) (terms []models.TermsMy, err error) {
var in = []any{"category", "post_tag"}
switch t {
case constraints.Category:
in = []any{"category"}
case constraints.Tag:
in = []any{"post_tag"}
}
w := model.SqlBuilder{
{"tt.taxonomy", "in", ""},
}
if helper.GetContextVal(ctx, "showOnlyTopLevel", false) {
w = append(w, []string{"tt.parent", "=", "0", "int"})
}
if !helper.GetContextVal(ctx, "showEmpty", false) {
w = append(w, []string{"tt.count", ">", "0", "int"})
}
order := []string{"name", "asc"}
ord := helper.GetContextVal[[]string](ctx, "order", nil)
if ord != nil {
order = ord
}
terms, err = model.Finds[models.TermsMy](ctx, model.Conditions(
model.Where(w),
model.Fields("t.term_id"),
model.Order(model.SqlBuilder{order}),
model.Join(model.SqlBuilder{
{"t", "inner join", "wp_term_taxonomy tt", "t.term_id = tt.term_id"},
}),
model.In(in),
))
for i := 0; i < len(terms); i++ {
if v, ok := wpconfig.GetTerm(terms[i].Terms.TermId); ok {
terms[i].Terms = v
}
if v, ok := wpconfig.GetTermTaxonomy(terms[i].Terms.TermId); ok {
terms[i].TermTaxonomy = v
}
}
return
}
func Archives(ctx context.Context) ([]models.PostArchive, error) {
return model.Finds[models.PostArchive](ctx, model.Conditions(
model.Where(model.SqlBuilder{
{"post_type", "post"},
{"post_status", "publish"},
}),
model.Fields("YEAR(post_date) AS `year`, MONTH(post_date) AS `month`, count(ID) as posts"),
model.Group("year,month"),
model.Order(model.SqlBuilder{{"year", "desc"}, {"month", "desc"}}),
))
}

View File

@ -2,21 +2,21 @@ package dao
import ( import (
"context" "context"
"github.com/fthvgb1/wp-go/app/phphelper"
"github.com/fthvgb1/wp-go/app/pkg/logs"
"github.com/fthvgb1/wp-go/app/pkg/models"
"github.com/fthvgb1/wp-go/app/wpconfig"
"github.com/fthvgb1/wp-go/helper/slice" "github.com/fthvgb1/wp-go/helper/slice"
"github.com/fthvgb1/wp-go/internal/pkg/logs"
"github.com/fthvgb1/wp-go/internal/pkg/models"
"github.com/fthvgb1/wp-go/internal/plugins"
"github.com/fthvgb1/wp-go/model" "github.com/fthvgb1/wp-go/model"
"strconv" "strconv"
) )
func GetPostMetaByPostIds(args ...any) (r map[uint64]map[string]any, err error) { func GetPostMetaByPostIds(ctx context.Context, ids []uint64, _ ...any) (r map[uint64]map[string]any, err error) {
r = make(map[uint64]map[string]any) r = make(map[uint64]map[string]any)
ctx := args[0].(context.Context) rr, err := model.Finds[models.PostMeta](ctx, model.Conditions(
ids := args[1].([]uint64) model.Where(model.SqlBuilder{{"post_id", "in", ""}}),
rr, err := model.Find[models.PostMeta](ctx, model.SqlBuilder{ model.In(slice.ToAnySlice(ids)),
{"post_id", "in", ""}, ))
}, "*", "", nil, nil, nil, 0, slice.ToAnySlice(ids))
if err != nil { if err != nil {
return return
} }
@ -24,18 +24,15 @@ func GetPostMetaByPostIds(args ...any) (r map[uint64]map[string]any, err error)
if _, ok := r[postmeta.PostId]; !ok { if _, ok := r[postmeta.PostId]; !ok {
r[postmeta.PostId] = make(map[string]any) r[postmeta.PostId] = make(map[string]any)
} }
r[postmeta.PostId][postmeta.MetaKey] = postmeta.MetaValue
if postmeta.MetaKey == "_wp_attachment_metadata" { if postmeta.MetaKey == "_wp_attachment_metadata" {
metadata, err := plugins.UnPHPSerialize[models.WpAttachmentMetadata](postmeta.MetaValue) metadata, err := phphelper.UnPHPSerializeToStruct[models.WpAttachmentMetadata](postmeta.MetaValue)
if err != nil { if err != nil {
logs.ErrPrintln(err, "解析postmeta失败", postmeta.MetaId, postmeta.MetaValue) logs.Error(err, "解析postmeta失败", postmeta.MetaId, postmeta.MetaValue)
continue continue
} }
r[postmeta.PostId][postmeta.MetaKey] = metadata r[postmeta.PostId][postmeta.MetaKey] = metadata
} else {
r[postmeta.PostId][postmeta.MetaKey] = postmeta.MetaValue
} }
} }
return return
} }
@ -62,7 +59,7 @@ func ToPostThumb(c context.Context, meta map[string]any, host string) (r models.
if ok { if ok {
metadata, ok := x.(models.WpAttachmentMetadata) metadata, ok := x.(models.WpAttachmentMetadata)
if ok { if ok {
r = plugins.Thumbnail(metadata, "post-thumbnail", host, "thumbnail") r = wpconfig.Thumbnail(metadata, "post-thumbnail", host, "thumbnail")
} }
} }
} }

204
app/pkg/dao/posts.go Normal file
View File

@ -0,0 +1,204 @@
package dao
import (
"context"
"database/sql"
"errors"
"fmt"
"github.com/fthvgb1/wp-go/app/pkg/models"
"github.com/fthvgb1/wp-go/app/pkg/models/relation"
"github.com/fthvgb1/wp-go/app/wpconfig"
"github.com/fthvgb1/wp-go/helper"
"github.com/fthvgb1/wp-go/helper/slice"
"github.com/fthvgb1/wp-go/model"
"strings"
"sync/atomic"
"time"
)
func GetPostsByIds(ctx context.Context, ids []uint64, _ ...any) (m map[uint64]models.Posts, err error) {
m = make(map[uint64]models.Posts)
q := model.Conditions(
model.Where(model.SqlBuilder{{"Id", "in", ""}}),
model.Join(model.SqlBuilder{
{"a", "left join", "wp_term_relationships b", "a.Id=b.object_id"},
{"left join", "wp_term_taxonomy c", "b.term_taxonomy_id=c.term_taxonomy_id"},
{"left join", "wp_terms d", "c.term_id=d.term_id"},
}),
model.Fields("a.*,ifnull(d.name,'') category_name,ifnull(c.term_id,0) terms_id,ifnull(taxonomy,'') `taxonomy`"),
model.In(slice.ToAnySlice(ids)),
)
if helper.GetContextVal(ctx, "getPostAuthor", false) {
q.RelationFn = append(q.RelationFn, model.AddRelationFn(true, false, helper.GetContextVal[*model.QueryCondition](ctx, "postAuthorQueryCondition", nil), relation.PostsWithAuthor))
}
rawPosts, err := model.Finds[models.Posts](ctx, q)
if err != nil {
return m, err
}
postsMap := make(map[uint64]models.Posts)
for i, post := range rawPosts {
v, ok := postsMap[post.Id]
if !ok {
v = rawPosts[i]
}
if post.Taxonomy == "category" {
v.Categories = append(v.Categories, post.CategoryName)
} else if post.Taxonomy == "post_tag" {
v.Tags = append(v.Tags, post.CategoryName)
}
if post.TermsId > 0 {
v.TermIds = append(v.TermIds, post.TermsId)
}
postsMap[post.Id] = v
}
//host, _ := wpconfig.Options.Load("siteurl")
host := ""
meta, _ := GetPostMetaByPostIds(ctx, ids)
for k, pp := range postsMap {
if len(pp.Categories) > 0 {
t := make([]string, 0, len(pp.Categories))
for _, cat := range pp.Categories {
t = append(t, fmt.Sprintf(`<a href="/p/category/%s" rel="category tag">%s</a>`, cat, cat))
}
pp.CategoriesHtml = strings.Join(t, "、")
}
mm, ok := meta[pp.Id]
if ok {
pp.Metas = mm
attMeta, ok := mm["_wp_attachment_metadata"]
if ok {
att, ok := attMeta.(models.WpAttachmentMetadata)
if ok {
pp.AttachmentMetadata = att
}
}
if pp.PostType != "attachment" {
thumb := ToPostThumb(ctx, mm, host)
if thumb.Path != "" {
pp.Thumbnail = thumb
}
} else if pp.PostType == "attachment" && pp.AttachmentMetadata.File != "" {
thumb := wpconfig.Thumbnail(pp.AttachmentMetadata, "thumbnail", host, "thumbnail", "post-thumbnail")
if thumb.Path != "" {
pp.Thumbnail = thumb
}
}
}
if len(pp.Tags) > 0 {
t := make([]string, 0, len(pp.Tags))
for _, cat := range pp.Tags {
t = append(t, fmt.Sprintf(`<a href="/p/tag/%s" rel="tag">%s</a>`, cat, cat))
}
pp.TagsHtml = strings.Join(t, "、")
}
m[k] = pp
}
return
}
func SearchPostIds(ctx context.Context, _ string, args ...any) (ids PostIds, err error) {
q := args[0].(*model.QueryCondition)
page := args[1].(int)
pageSize := args[2].(int)
q.Fields = "ID"
res, total, err := model.Pagination[models.Posts](ctx, q, page, pageSize)
for _, posts := range res {
ids.Ids = append(ids.Ids, posts.Id)
}
ids.Length = total
totalR := int(atomic.LoadInt64(&TotalRaw))
if total > totalR {
tt := int64(total)
atomic.StoreInt64(&TotalRaw, tt)
}
return
}
func GetMaxPostId(ctx context.Context, _ ...any) (uint64, error) {
r, err := model.Finds[models.Posts](ctx,
model.Conditions(
model.Where(model.SqlBuilder{
{"post_type", "post"},
{"post_status", "publish"}},
),
model.Fields("ID"),
model.Order(model.SqlBuilder{
{"ID", "desc"},
}),
model.Limit(1),
),
)
var id uint64
if len(r) > 0 {
id = r[0].Id
}
return id, err
}
func RecentPosts(ctx context.Context, a ...any) (r []models.Posts, err error) {
num := helper.ParseArgs(10, a...)
r, err = model.Finds[models.Posts](ctx, model.Conditions(
model.Where(model.SqlBuilder{
{"post_type", "post"},
{"post_status", "publish"},
}),
model.Fields("ID,post_title,post_password,post_date_gmt"),
model.Order([][]string{{"post_date", "desc"}}),
model.Limit(num),
))
return
}
func GetPostContext(ctx context.Context, _ uint64, arg ...any) (r PostContext, err error) {
t := arg[0].(time.Time)
next, err := model.FirstOne[models.Posts](ctx, model.SqlBuilder{
{"post_date", ">", t.Format("2006-01-02 15:04:05")},
{"post_status", "in", ""},
{"post_type", "post"},
}, "ID,post_title,post_password", nil, []any{"publish"})
if errors.Is(err, sql.ErrNoRows) {
err = nil
}
if err != nil {
return
}
prev, err := model.FirstOne[models.Posts](ctx, model.SqlBuilder{
{"post_date", "<", t.Format("2006-01-02 15:04:05")},
{"post_status", "in", ""},
{"post_type", "post"},
}, "ID,post_title", model.SqlBuilder{{"post_date", "desc"}}, []any{"publish"})
if errors.Is(err, sql.ErrNoRows) {
err = nil
}
if err != nil {
return
}
r = PostContext{
Prev: prev,
Next: next,
}
return
}
func MonthPost(ctx context.Context, _ string, args ...any) (r []uint64, err error) {
year, month := args[0].(string), args[1].(string)
where := model.SqlBuilder{
{"post_type", "post"},
{"post_status", "publish"},
{"year(post_date)", year},
{"month(post_date)", month},
}
r, err = model.Column[models.Posts, uint64](ctx, func(v models.Posts) (uint64, bool) {
return v.Id, true
}, model.Conditions(
model.Fields("ID"),
model.Where(where),
))
l := int64(len(r))
if l > atomic.LoadInt64(&TotalRaw) {
atomic.StoreInt64(&TotalRaw, l)
}
return
}

32
app/pkg/dao/users.go Normal file
View File

@ -0,0 +1,32 @@
package dao
import (
"context"
"github.com/fthvgb1/wp-go/app/pkg/models"
"github.com/fthvgb1/wp-go/helper/slice"
"github.com/fthvgb1/wp-go/model"
)
func GetUserById(ctx context.Context, uid uint64, _ ...any) (r models.Users, err error) {
r, err = model.FindOneById[models.Users](ctx, uid)
return
}
func AllUsername(ctx context.Context, _ ...any) (map[string]uint64, error) {
r, err := model.SimpleFind[models.Users](ctx, model.SqlBuilder{
{"user_status", "=", "0", "int"},
}, "display_name,ID")
if err != nil {
return nil, err
}
return slice.ToMap(r, func(t models.Users) (string, uint64) {
return t.DisplayName, t.Id
}, true), nil
}
func GetUserByName(ctx context.Context, u string, _ ...any) (r models.Users, err error) {
r, err = model.FirstOne[models.Users](ctx, model.SqlBuilder{{
"display_name", u,
}}, "*", nil)
return
}

77
app/pkg/db/db.go Normal file
View File

@ -0,0 +1,77 @@
package db
import (
"context"
"github.com/fthvgb1/wp-go/app/pkg/config"
"github.com/fthvgb1/wp-go/cache/reload"
"github.com/fthvgb1/wp-go/model"
"github.com/fthvgb1/wp-go/safety"
_ "github.com/go-sql-driver/mysql"
"github.com/jmoiron/sqlx"
"log"
"runtime"
)
var safeDb = safety.NewVar[*sqlx.DB](nil)
var showQuerySql func() bool
func GetSqlxDB() *sqlx.DB {
return safeDb.Load()
}
func InitDb() (*safety.Var[*sqlx.DB], error) {
c := config.GetConfig()
dsn := c.Mysql.Dsn.GetDsn()
db, err := sqlx.Open("mysql", dsn)
if err != nil {
return nil, err
}
preDb := safeDb.Load()
if c.Mysql.Pool.ConnMaxIdleTime != 0 {
db.SetConnMaxIdleTime(c.Mysql.Pool.ConnMaxLifetime)
}
if c.Mysql.Pool.MaxIdleConn != 0 {
db.SetMaxIdleConns(c.Mysql.Pool.MaxIdleConn)
}
if c.Mysql.Pool.MaxOpenConn != 0 {
db.SetMaxOpenConns(c.Mysql.Pool.MaxOpenConn)
}
if c.Mysql.Pool.ConnMaxLifetime != 0 {
db.SetConnMaxLifetime(c.Mysql.Pool.ConnMaxLifetime)
}
safeDb.Store(db)
if preDb != nil {
_ = preDb.Close()
}
if showQuerySql == nil {
showQuerySql = reload.BuildFnVal("showQuerySql", false, func() bool {
return config.GetConfig().ShowQuerySql
})
}
return safeDb, err
}
func QueryDb(db *safety.Var[*sqlx.DB]) *model.SqlxQuery {
query := model.NewSqlxQuery(db, model.NewUniversalDb(
nil,
nil))
model.SetSelect(query, func(ctx context.Context, a any, s string, args ...any) error {
if showQuerySql() {
_, f, l, _ := runtime.Caller(5)
go func() {
log.Printf("%v:%v %v\n", f, l, model.FormatSql(s, args...))
}()
}
return query.Selects(ctx, a, s, args...)
})
model.SetGet(query, func(ctx context.Context, a any, s string, args ...any) error {
if showQuerySql() {
_, f, l, _ := runtime.Caller(5)
go func() {
log.Printf("%v:%v %v\n", f, l, model.FormatSql(s, args...))
}()
}
return query.Gets(ctx, a, s, args...)
})
return query
}

76
app/pkg/logs/log.go Normal file
View File

@ -0,0 +1,76 @@
package logs
import (
"fmt"
"github.com/fthvgb1/wp-go/app/pkg/config"
"github.com/fthvgb1/wp-go/safety"
"io"
"log"
"os"
"runtime"
"strings"
)
var logs = safety.NewVar[*log.Logger](nil)
var logFile = safety.NewVar[*os.File](nil)
func InitLogger() error {
c := config.GetConfig()
return SetLogger(c.LogOutput)
}
func SetLogger(loggerFile string) error {
if loggerFile == "" {
loggerFile = "stderr"
}
preFD := logFile.Load()
l := &log.Logger{}
var out io.Writer
switch loggerFile {
case "stdout":
out = os.Stdout
case "stderr":
out = os.Stderr
default:
file, err := os.OpenFile(loggerFile, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0777)
if err != nil {
return err
}
out = file
logFile.Store(file)
}
logs.Store(l)
if preFD != nil {
_ = preFD.Close()
}
l.SetFlags(log.Ldate | log.Ltime)
l.SetOutput(out)
return nil
}
func Errs(err error, depth int, desc string, args ...any) {
var pcs [1]uintptr
runtime.Callers(depth, pcs[:])
f := runtime.CallersFrames([]uintptr{pcs[0]})
ff, _ := f.Next()
s := strings.Builder{}
_, _ = fmt.Fprintf(&s, "%s:%d %s err:[%s]", ff.File, ff.Line, desc, err)
if len(args) > 0 {
s.WriteString(" args:")
for _, arg := range args {
_, _ = fmt.Fprintf(&s, "%v", arg)
}
}
logs.Load().Println(s.String())
}
func Error(err error, desc string, args ...any) {
Errs(err, 3, desc, args...)
}
func IfError(err error, desc string, args ...any) {
if err == nil {
return
}
Errs(err, 3, desc, args...)
}

View File

@ -0,0 +1,19 @@
package relation
import (
"github.com/fthvgb1/wp-go/app/pkg/models"
"github.com/fthvgb1/wp-go/model"
)
var PostsWithAuthor = model.RelationHasOne(func(m *models.Posts) uint64 {
return m.PostAuthor
}, func(p *models.Users) uint64 {
return p.Id
}, func(m *models.Posts, p *models.Users) {
m.Author = p
}, model.Relationship{
RelationType: model.HasOne,
Table: "wp_users user",
ForeignKey: "ID",
Local: "post_author",
})

View File

@ -20,6 +20,7 @@ type Comments struct {
UserId uint64 `gorm:"column:user_id" db:"user_id" json:"user_id" form:"user_id"` UserId uint64 `gorm:"column:user_id" db:"user_id" json:"user_id" form:"user_id"`
//扩展字段 //扩展字段
PostTitle string `db:"post_title"` PostTitle string `db:"post_title"`
UpdateTime time.Time `gorm:"update_time" form:"update_time" json:"update_time" db:"update_time"`
} }
func (w Comments) PrimaryKey() string { func (w Comments) PrimaryKey() string {
@ -29,3 +30,8 @@ func (w Comments) PrimaryKey() string {
func (w Comments) Table() string { func (w Comments) Table() string {
return "wp_comments" return "wp_comments"
} }
type PostComments struct {
Comments
Children []uint64
}

View File

@ -22,6 +22,7 @@ type WpAttachmentMetadata struct {
FileSize int `json:"filesize,omitempty"` FileSize int `json:"filesize,omitempty"`
Sizes map[string]MetaDataFileSize `json:"sizes,omitempty"` Sizes map[string]MetaDataFileSize `json:"sizes,omitempty"`
ImageMeta ImageMeta `json:"image_meta"` ImageMeta ImageMeta `json:"image_meta"`
VideoMeta
} }
type ImageMeta struct { type ImageMeta struct {
@ -39,6 +40,16 @@ type ImageMeta struct {
Keywords []string `json:"keywords,omitempty"` Keywords []string `json:"keywords,omitempty"`
} }
type VideoMeta struct {
Bitrate int `json:"bitrate,omitempty"`
MimeType string `json:"mime_type,omitempty"`
Length int `json:"length,omitempty"`
LengthFormatted string `json:"length_formatted,omitempty"`
FileFormat string `json:"fileformat,omitempty"`
DataFormat string `json:"dataformat,omitempty"`
CreatedTimestamp int64 `json:"created_timestamp"`
}
type MetaDataFileSize struct { type MetaDataFileSize struct {
File string `json:"file,omitempty"` File string `json:"file,omitempty"`
Width int `json:"width,omitempty"` Width int `json:"width,omitempty"`

View File

@ -3,6 +3,7 @@ package models
import "time" import "time"
type Posts struct { type Posts struct {
post
Id uint64 `gorm:"column:ID" db:"ID" json:"ID" form:"ID"` Id uint64 `gorm:"column:ID" db:"ID" json:"ID" form:"ID"`
PostAuthor uint64 `gorm:"column:post_author" db:"post_author" json:"post_author" form:"post_author"` PostAuthor uint64 `gorm:"column:post_author" db:"post_author" json:"post_author" form:"post_author"`
PostDate time.Time `gorm:"column:post_date" db:"post_date" json:"post_date" form:"post_date"` PostDate time.Time `gorm:"column:post_date" db:"post_date" json:"post_date" form:"post_date"`
@ -28,14 +29,19 @@ type Posts struct {
CommentCount int64 `gorm:"column:comment_count" db:"comment_count" json:"comment_count" form:"comment_count"` CommentCount int64 `gorm:"column:comment_count" db:"comment_count" json:"comment_count" form:"comment_count"`
//扩展字段 //扩展字段
TermsId uint64 `db:"terms_id" json:"terms_id"`
TermIds []uint64 `db:"term_ids" json:"term_ids"`
Taxonomy string `db:"taxonomy" json:"taxonomy"` Taxonomy string `db:"taxonomy" json:"taxonomy"`
CategoryName string `db:"category_name" json:"category_name"` CategoryName string `db:"category_name" json:"category_name"`
Categories []string `json:"categories"` Categories []string `json:"categories"`
Tags []string `json:"tags"` Tags []string `json:"tags"`
CategoriesHtml string CategoriesHtml string
TagsHtml string TagsHtml string
IsSticky bool
Thumbnail PostThumbnail Thumbnail PostThumbnail
AttachmentMetadata WpAttachmentMetadata AttachmentMetadata WpAttachmentMetadata
Metas map[string]any
Author *Users
} }
type PostThumbnail struct { type PostThumbnail struct {
@ -47,23 +53,19 @@ type PostThumbnail struct {
OriginAttachmentData WpAttachmentMetadata OriginAttachmentData WpAttachmentMetadata
} }
func (w Posts) PrimaryKey() string { type post struct {
}
func (w post) PrimaryKey() string {
return "ID" return "ID"
} }
func (w Posts) Table() string { func (w post) Table() string {
return "wp_posts"
}
func (w PostArchive) PrimaryKey() string {
return "ID"
}
func (w PostArchive) Table() string {
return "wp_posts" return "wp_posts"
} }
type PostArchive struct { type PostArchive struct {
post
Year string `db:"year"` Year string `db:"year"`
Month string `db:"month"` Month string `db:"month"`
Posts int `db:"posts"` Posts int `db:"posts"`

View File

@ -1,10 +1,13 @@
package plugins package plugins
import ( import (
"context"
"github.com/fthvgb1/wp-go/app/pkg/models"
"github.com/fthvgb1/wp-go/app/wpconfig"
"github.com/fthvgb1/wp-go/helper/number"
"github.com/fthvgb1/wp-go/helper/slice" "github.com/fthvgb1/wp-go/helper/slice"
"github.com/fthvgb1/wp-go/internal/pkg/models" str "github.com/fthvgb1/wp-go/helper/strings"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"net/url"
"strconv" "strconv"
"strings" "strings"
) )
@ -16,6 +19,7 @@ type CommentHandler struct {
depth int depth int
isTls bool isTls bool
i CommentHtml i CommentHtml
isThreadComments bool
} }
type Comments struct { type Comments struct {
@ -24,19 +28,18 @@ type Comments struct {
} }
type CommentHtml interface { type CommentHtml interface {
Sort(i, j *Comments) bool FormatLi(c context.Context, m models.Comments, depth, maxDepth, page int, isTls, isThreadComments bool, eo, parent string) string
FormatLi(c *gin.Context, m models.Comments, depth int, isTls bool, eo, parent string) string FloorOrder(i, j models.Comments) bool
} }
func FormatComments(c *gin.Context, i CommentHtml, comments []models.Comments, maxDepth int) string { func FormatComments(c *gin.Context, i CommentHtml, comments []models.Comments, maxDepth int) string {
tree := treeComments(comments) tree := treeComments(comments)
u := c.Request.Header.Get("Referer")
var isTls bool var isTls bool
if u != "" { if c.Request.TLS != nil {
uu, _ := url.Parse(u)
if uu.Scheme == "https" {
isTls = true isTls = true
} } else {
isTls = "https" == strings.ToLower(c.Request.Header.Get("X-Forwarded-Proto"))
} }
h := CommentHandler{ h := CommentHandler{
Context: c, Context: c,
@ -46,15 +49,21 @@ func FormatComments(c *gin.Context, i CommentHtml, comments []models.Comments, m
isTls: isTls, isTls: isTls,
i: i, i: i,
} }
return h.formatComment(h.comments, true) return h.formatComment(h.comments)
} }
func (d CommentHandler) formatComment(comments []*Comments, isTop bool) (html string) { func (d CommentHandler) formatComment(comments []*Comments) (html string) {
s := strings.Builder{} s := str.NewBuilder()
if d.depth > d.maxDepth { if d.depth >= d.maxDepth {
comments = d.findComments(comments) comments = d.findComments(comments)
} }
slice.SortSelf(comments, d.i.Sort) order := wpconfig.GetOption("comment_order")
slice.Sort(comments, func(i, j *Comments) bool {
if order == "asc" {
return i.CommentDate.Sub(j.CommentDate) < 0
}
return i.CommentDate.Sub(j.CommentDate) > 0
})
for i, comment := range comments { for i, comment := range comments {
eo := "even" eo := "even"
if (i+1)%2 == 0 { if (i+1)%2 == 0 {
@ -66,15 +75,11 @@ func (d CommentHandler) formatComment(comments []*Comments, isTop bool) (html st
parent = "parent" parent = "parent"
fl = true fl = true
} }
s.WriteString(d.i.FormatLi(d.Context, comment.Comments, d.depth, d.isTls, eo, parent)) s.WriteString(d.i.FormatLi(d.Context, comment.Comments, d.depth, d.maxDepth, 1, d.isTls, d.isThreadComments, eo, parent))
if fl { if fl {
d.depth++ d.depth++
s.WriteString(`<ol class="children">`) s.WriteString(`<ol class="children">`, d.formatComment(comment.Children), `</ol>`)
s.WriteString(d.formatComment(comment.Children, false)) d.depth--
s.WriteString(`</ol>`)
if isTop {
d.depth = 1
}
} }
s.WriteString("</li><!-- #comment-## -->") s.WriteString("</li><!-- #comment-## -->")
} }
@ -87,7 +92,7 @@ func (d CommentHandler) findComments(comments []*Comments) []*Comments {
var r []*Comments var r []*Comments
for _, comment := range comments { for _, comment := range comments {
tmp := *comment tmp := *comment
comment.Children = nil tmp.Children = nil
r = append(r, &tmp) r = append(r, &tmp)
if len(comment.Children) > 0 { if len(comment.Children) > 0 {
t := d.findComments(comment.Children) t := d.findComments(comment.Children)
@ -137,12 +142,35 @@ func CommentRender() CommonCommentFormat {
type CommonCommentFormat struct { type CommonCommentFormat struct {
} }
func (c CommonCommentFormat) Sort(i, j *Comments) bool { func (c CommonCommentFormat) FormatLi(_ context.Context, m models.Comments, currentDepth, maxDepth, page int, isTls, isThreadComments bool, eo, parent string) string {
return i.CommentDate.UnixNano() < j.CommentDate.UnixNano() return FormatLi(li, m, respondsFn, currentDepth, maxDepth, page, isTls, isThreadComments, eo, parent)
} }
func (c CommonCommentFormat) FormatLi(ctx *gin.Context, m models.Comments, depth int, isTls bool, eo, parent string) string { func (c CommonCommentFormat) FloorOrder(i, j models.Comments) bool {
return FormatLi(CommonLi(), ctx, m, depth, isTls, eo, parent) return i.CommentId > j.CommentId
}
type RespondFn func(m models.Comments, depth, maxDepth int, isThreadComments bool) string
var respondsFn = Responds(respondTml)
func RespondsFn() RespondFn {
return respondsFn
}
func Responds(respondTml string) RespondFn {
return func(m models.Comments, depth, maxDepth int, isThreadComments bool) string {
if !isThreadComments || depth >= maxDepth {
return ""
}
pId := number.IntToString(m.CommentPostId)
cId := number.IntToString(m.CommentId)
return str.Replace(respondTml, map[string]string{
"{{PostId}}": pId,
"{{CommentId}}": cId,
"{{CommentAuthor}}": m.CommentAuthor,
})
}
} }
var li = ` var li = `
@ -154,14 +182,11 @@ var li = `
src="{{Gravatar}}" src="{{Gravatar}}"
srcset="{{Gravatar}} 2x" srcset="{{Gravatar}} 2x"
class="avatar avatar-56 photo" height="56" width="56" loading="lazy"> class="avatar avatar-56 photo" height="56" width="56" loading="lazy">
<b class="fn"> <b class="fn">{{CommentAuthor}}</b>
<a href="{{CommentAuthorUrl}}" rel="external nofollow ugc"
class="url">{{CommentAuthor}}</a>
</b>
<span class="says">说道</span></div><!-- .comment-author --> <span class="says">说道</span></div><!-- .comment-author -->
<div class="comment-metadata"> <div class="comment-metadata">
<a href="/p/{{PostId}}#comment-{{CommentId}}"> <a href="/p/{{PostId}}/comment-page-{{page}}#comment-{{CommentId}}">
<time datetime="{{CommentDateGmt}}">{{CommentDate}}</time> <time datetime="{{CommentDateGmt}}">{{CommentDate}}</time>
</a></div><!-- .comment-metadata --> </a></div><!-- .comment-metadata -->
@ -171,30 +196,34 @@ var li = `
<p>{{CommentContent}}</p> <p>{{CommentContent}}</p>
</div><!-- .comment-content --> </div><!-- .comment-content -->
<div class="reply"> {{respond}}
</article><!-- .comment-body -->
`
var respondTml = `<div class="reply">
<a rel="nofollow" class="comment-reply-link" <a rel="nofollow" class="comment-reply-link"
href="/p/{{PostId}}?replytocom={{CommentId}}#respond" data-commentid="{{CommentId}}" data-postid="{{PostId}}" href="/p/{{PostId}}?replytocom={{CommentId}}#respond" data-commentid="{{CommentId}}" data-postid="{{PostId}}"
data-belowelement="div-comment-{{CommentId}}" data-respondelement="respond" data-belowelement="div-comment-{{CommentId}}" data-respondelement="respond"
data-replyto="回复给{{CommentAuthor}}" data-replyto="回复给{{CommentAuthor}}"
aria-label="回复给{{CommentAuthor}}">回复</a> aria-label="回复给{{CommentAuthor}}">回复</a>
</div> </div>`
</article><!-- .comment-body -->
` func FormatLi(li string, comments models.Comments, respond RespondFn, currentDepth, maxDepth, page int, isTls, isThreadComments bool, eo, parent string) string {
func FormatLi(li string, c *gin.Context, comments models.Comments, depth int, isTls bool, eo, parent string) string {
for k, v := range map[string]string{ for k, v := range map[string]string{
"{{CommentId}}": strconv.FormatUint(comments.CommentId, 10), "{{CommentId}}": strconv.FormatUint(comments.CommentId, 10),
"{{Depth}}": strconv.Itoa(depth), "{{Depth}}": strconv.Itoa(currentDepth),
"{{Gravatar}}": Gravatar(comments.CommentAuthorEmail, isTls), "{{Gravatar}}": Gravatar(comments.CommentAuthorEmail, isTls),
"{{CommentAuthorUrl}}": comments.CommentAuthorUrl, "{{CommentAuthorUrl}}": comments.CommentAuthorUrl,
"{{CommentAuthor}}": comments.CommentAuthor, "{{CommentAuthor}}": comments.CommentAuthor,
"{{PostId}}": strconv.FormatUint(comments.CommentPostId, 10), "{{PostId}}": strconv.FormatUint(comments.CommentPostId, 10),
"{{page}}": strconv.Itoa(page),
"{{CommentDateGmt}}": comments.CommentDateGmt.String(), "{{CommentDateGmt}}": comments.CommentDateGmt.String(),
"{{CommentDate}}": comments.CommentDate.Format("2006-01-02 15:04"), "{{CommentDate}}": comments.CommentDate.Format("2006-01-02 15:04"),
"{{CommentContent}}": comments.CommentContent, "{{CommentContent}}": comments.CommentContent,
"{{eo}}": eo, "{{eo}}": eo,
"{{parent}}": parent, "{{parent}}": parent,
"{{respond}}": respond(comments, currentDepth, maxDepth, isThreadComments),
} { } {
li = strings.Replace(li, k, v, -1) li = strings.Replace(li, k, v, -1)
} }

View File

@ -0,0 +1,9 @@
<div>
{{.aa}}
</div>
<div>
{{ range $k,$v := .posts}}
<h2>{{$v.PostTitle}} </h2>
{{end}}
</div>

View File

@ -0,0 +1,81 @@
package main
import (
"embed"
"github.com/fthvgb1/wp-go/app/cmd/route"
"github.com/fthvgb1/wp-go/app/pkg/constraints"
"github.com/fthvgb1/wp-go/app/pkg/logs"
"github.com/fthvgb1/wp-go/app/plugins/wphandle"
"github.com/fthvgb1/wp-go/app/theme"
"github.com/fthvgb1/wp-go/app/theme/wp"
"github.com/fthvgb1/wp-go/app/theme/wp/components"
"github.com/fthvgb1/wp-go/app/theme/wp/components/widget"
route2 "github.com/fthvgb1/wp-go/app/theme/wp/route"
"github.com/gin-gonic/gin"
"html/template"
"net/http"
"plugintt/xx"
)
//go:embed a.gohtml
var em embed.FS
var tt *template.Template
func init() {
// register as theme
theme.AddThemeHookFunc("themename", hook)
//use the local template
//note: must use embed.FS
t, err := template.ParseFS(em, "a.gohtml")
if err != nil {
logs.Error(err, "")
}
tt = t
// register gin route. it will be effecting when server restart.
route.Hook(func(r *gin.Engine) {
r.GET("xx", func(c *gin.Context) {
c.String(http.StatusOK, "xxoo")
})
})
}
func hook(h *wp.Handle) {
wp.Run(h, config)
}
func config(h *wp.Handle) {
// same theme config
wphandle.UsePlugins(h)
wp.InitPipe(h)
h.PushHandler(constraints.PipeMiddleware, constraints.Home,
wp.NewHandleFn(widget.IsCategory, 100, "widget.IsCategory"))
components.WidgetArea(h)
h.PushHandler(constraints.PipeRender, constraints.Home, wp.NewHandleFn(func(h *wp.Handle) {
h.SetData("aa", "xyxxxx")
h.RenderHtml(tt, http.StatusOK, "a.gohtml")
h.Abort()
h.StopPipe()
}, 10, "renderHome"))
// use simple reg route
route2.PushRoute(`(?P<control>\w+)/(?P<method>\w+)`, route2.Route{
Path: `(?P<control>\w+)/(?P<method>\w+)`,
Scene: constraints.Home,
Method: []string{"GET"},
Type: "reg",
})
//...
}
// Xo to be a func when theme init
func Xo(h *wp.Handle) {
xx.Xo()
route2.Delete(`(?P<control>\w+)/(?P<method>\w+)`)
h.ReplaceHandle(constraints.PipeRender, "wp.RenderTemplate", func(h *wp.Handle) {
h.SetData("aa", "xyxxxx")
h.RenderHtml(tt, http.StatusOK, "a.gohtml")
h.StopPipe()
})
}

View File

@ -0,0 +1,7 @@
#/bin/bash
# copy plugintt to other dir and remove .dev suffix
# note the go version and build tool flag must same to server build
# eg: -gcflags all="-N -l" --race may used in ide debug
go mod tidy
go build -buildmode=plugin -o xx.so main.go

View File

@ -0,0 +1,141 @@
package main
import (
"context"
"errors"
"fmt"
"github.com/fthvgb1/wp-go/app/pkg/config"
"github.com/fthvgb1/wp-go/app/pkg/dao"
"github.com/fthvgb1/wp-go/app/pkg/logs"
"github.com/fthvgb1/wp-go/app/theme/wp"
"github.com/fthvgb1/wp-go/cache/cachemanager"
"github.com/fthvgb1/wp-go/cache/reload"
"github.com/fthvgb1/wp-go/helper/number"
"github.com/fthvgb1/wp-go/helper/slice"
"github.com/fthvgb1/wp-go/helper/strings"
"github.com/redis/go-redis/v9"
"strconv"
str "strings"
"time"
)
type RdmCache[K comparable, V any] struct {
expired func() time.Duration
rdb *redis.Client
keyFn func(K) string
name string
resFn func(map[string]string) V
saveData func(V) map[string]string
}
func (r *RdmCache[K, V]) SetExpiredTime(f func() time.Duration) {
r.expired = f
}
func (r *RdmCache[K, V]) Get(ctx context.Context, key K) (V, bool) {
var re V
result, err := r.rdb.Exists(ctx, r.keyFn(key)).Result()
if result <= 0 || err != nil {
return re, false
}
rr, err := r.rdb.HGetAll(ctx, r.keyFn(key)).Result()
if errors.Is(err, redis.Nil) {
return re, false
}
if err != nil {
return re, false
}
return r.resFn(rr), true
}
func (r *RdmCache[K, V]) Set(ctx context.Context, key K, val V) {
k := r.keyFn(key)
result, err := r.rdb.HSet(ctx, k, r.saveData(val)).Result()
b, err := r.rdb.Expire(ctx, k, r.expired()).Result()
if err != nil {
fmt.Println(result, b, err)
return
}
fmt.Println(result, err)
}
func (r *RdmCache[K, V]) GetExpireTime(ctx context.Context) time.Duration {
return r.expired()
}
func (r *RdmCache[K, V]) Ttl(ctx context.Context, key K) time.Duration {
result, err := r.rdb.TTL(ctx, r.keyFn(key)).Result()
if err != nil {
return 0
}
return result
}
func (r *RdmCache[K, V]) Flush(ctx context.Context) {
fmt.Println("flush redis cache")
}
func (r *RdmCache[K, V]) Del(ctx context.Context, key ...K) {
r.rdb.Del(ctx, slice.Map(key, r.keyFn)...)
}
func (r *RdmCache[K, V]) ClearExpired(ctx context.Context) {
fmt.Println("clear expired redis cache")
}
// RedisCache use step:
// 1 go build -gcflags all="-N -l" --race -buildmode=plugin -o redisCache.so main.go && cp ./redisCache.so ../wp-go/plugins/
// 2 wp-go config add redisCache plugin
func RedisCache(h *wp.Handle) {
vv, ok := cachemanager.GetMapCache[string, dao.PostIds]("listPostIds")
if ok {
_, ok := any(vv.Cache).(*RdmCache[string, dao.PostIds])
if ok {
return
}
}
reload.AppendOnceFn(func() {
err := cachemanager.SetMapCache("listPostIds", vv)
if err != nil {
logs.Error(err, "set recovery listPostIds cache err")
} else {
cachemanager.PushOrSetFlush(cachemanager.Queue{Name: "listPostIds", Fn: vv.Flush})
cachemanager.PushOrSetClearExpired(cachemanager.Queue{Name: "listPostIds", Fn: vv.Flush})
fmt.Println("recovery listPostIds cache ok")
}
})
rdm := redis.NewClient(&redis.Options{
Addr: "localhost:6379",
Password: "", // no password set
DB: 0, // use default DB
})
r := RdmCache[string, dao.PostIds]{
expired: func() time.Duration {
return time.Minute
},
keyFn: func(u string) string {
return strings.Join("postIds:", u)
},
rdb: rdm,
name: "",
resFn: func(m map[string]string) dao.PostIds {
return dao.PostIds{
Ids: slice.Map(str.Split(m["ids"], ","), strings.ToInt[uint64]),
Length: strings.ToInt[int](m["length"]),
}
},
saveData: func(ids dao.PostIds) map[string]string {
t := slice.Map(ids.Ids, number.IntToString[uint64])
return map[string]string{
"ids": str.Join(t, ","),
"length": strconv.Itoa(ids.Length),
}
},
}
cachemanager.NewMapCache[string, dao.PostIds](&r, nil, dao.SearchPostIds, config.GetConfig().CacheTime.PostListCacheTime, "listPostIds", func() time.Duration {
return config.GetConfig().CacheTime.PostListCacheTime
})
fmt.Println("redis cache inited ok")
}

View File

@ -0,0 +1,50 @@
module redisCache
go 1.21
require (
github.com/fthvgb1/wp-go v0.0.0-20231210111549-d72bed0c8c4e
github.com/redis/go-redis/v9 v9.3.0
)
replace github.com/fthvgb1/wp-go v0.0.0-20231210111549-d72bed0c8c4e => ../wp-go
require (
github.com/bytedance/sonic v1.10.2 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect
github.com/chenzhuoyu/iasm v0.9.1 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/dlclark/regexp2 v1.10.0 // indirect
github.com/elliotchance/phpserialize v1.3.3 // indirect
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
github.com/gin-contrib/sessions v0.0.5 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/gin-gonic/gin v1.9.1 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.16.0 // indirect
github.com/goccy/go-json v0.10.2 // indirect
github.com/gorilla/context v1.1.2 // indirect
github.com/gorilla/securecookie v1.1.2 // indirect
github.com/gorilla/sessions v1.2.2 // indirect
github.com/jmoiron/sqlx v1.3.5 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.2.6 // indirect
github.com/leodido/go-urn v1.2.4 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.1.0 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.11 // indirect
golang.org/x/arch v0.6.0 // indirect
golang.org/x/crypto v0.15.0 // indirect
golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa // indirect
golang.org/x/net v0.18.0 // indirect
golang.org/x/sys v0.14.0 // indirect
golang.org/x/text v0.14.0 // indirect
google.golang.org/protobuf v1.31.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

View File

@ -0,0 +1,11 @@
package xx
import (
"fmt"
"github.com/shopspring/decimal"
)
func Xo() {
fmt.Println("xxoo")
fmt.Println(decimal.Max(decimal.NewFromFloat(32.3333333333333), decimal.NewFromFloat(32.33333333333331)).String())
}

187
app/plugins/digest.go Normal file
View File

@ -0,0 +1,187 @@
package plugins
import (
"context"
"fmt"
"github.com/fthvgb1/wp-go/app/pkg/config"
"github.com/fthvgb1/wp-go/app/pkg/logs"
"github.com/fthvgb1/wp-go/app/pkg/models"
"github.com/fthvgb1/wp-go/cache/cachemanager"
"github.com/fthvgb1/wp-go/cache/reload"
"github.com/fthvgb1/wp-go/helper"
"github.com/fthvgb1/wp-go/helper/maps"
str "github.com/fthvgb1/wp-go/helper/strings"
"github.com/fthvgb1/wp-go/plugin/digest"
"github.com/fthvgb1/wp-go/safety"
"regexp"
"strings"
"time"
"unicode/utf8"
)
var more = regexp.MustCompile("<!--more(.*?)?-->")
var removeWpBlock = regexp.MustCompile("<!-- /?wp:.*-->")
type DigestConfig struct {
DigestWordCount int `yaml:"digestWordCount"`
DigestAllowTag string `yaml:"digestAllowTag"`
DigestRegex string `yaml:"digestRegex"`
DigestTagOccupyNum []struct {
Tag string `yaml:"tag"`
Num int `yaml:"num"`
ChuckOvered bool `yaml:"chuckOvered"`
EscapeCharacter []struct {
Tags string `yaml:"tags"`
Character []string `yaml:"character"`
Num int `yaml:"num"`
ChuckOvered bool `yaml:"chuckOvered"`
} `yaml:"escapeCharacter"`
} `yaml:"digestTagOccupyNum"`
specialSolve map[string]digest.SpecialSolveConf
}
var digestConfig *safety.Var[DigestConfig]
func InitDigestCache() {
cachemanager.NewMemoryMapCache(nil, digestRaw, config.GetConfig().CacheTime.DigestCacheTime, "digestPlugin", func() time.Duration {
return config.GetConfig().CacheTime.DigestCacheTime
})
digestConfig = reload.VarsBy(func() DigestConfig {
c, err := config.GetCustomizedConfig[DigestConfig]()
if err != nil {
logs.Error(err, "get digest config fail")
c.DigestWordCount = config.GetConfig().DigestWordCount
c.DigestAllowTag = config.GetConfig().DigestAllowTag
return c
}
if c.DigestRegex != "" {
digest.SetQutos(c.DigestRegex)
}
if len(c.DigestTagOccupyNum) <= 1 {
return c
}
c.specialSolve = ParseDigestConf(c)
return c
}, "digestConfig")
}
func ParseDigestConf(c DigestConfig) map[string]digest.SpecialSolveConf {
specialSolve := map[string]digest.SpecialSolveConf{}
for _, item := range c.DigestTagOccupyNum {
tags := strings.Split(strings.ReplaceAll(item.Tag, " ", ""), "<")
for _, tag := range tags {
if tag == "" {
continue
}
ec := make(map[rune]digest.SpecialSolve)
specialTags := make(map[string]digest.SpecialSolve)
tag = str.Join("<", tag)
if len(item.EscapeCharacter) > 0 {
for _, esc := range item.EscapeCharacter {
for _, i := range esc.Character {
s := []rune(i)
if len(s) == 1 {
ec[s[0]] = digest.SpecialSolve{
Num: esc.Num,
ChuckOvered: esc.ChuckOvered,
}
}
}
if esc.Tags == "" {
continue
}
tagss := strings.Split(strings.ReplaceAll(esc.Tags, " ", ""), "<")
for _, t := range tagss {
if t == "" {
continue
}
t = str.Join("<", t)
specialTags[t] = digest.SpecialSolve{
Num: esc.Num,
ChuckOvered: esc.ChuckOvered,
}
}
}
}
v, ok := specialSolve[tag]
if !ok {
specialSolve[tag] = digest.SpecialSolveConf{
Num: item.Num,
ChuckOvered: item.ChuckOvered,
EscapeCharacter: ec,
Tags: specialTags,
}
continue
}
v.Num = item.Num
v.ChuckOvered = item.ChuckOvered
v.EscapeCharacter = maps.Merge(v.EscapeCharacter, ec)
v.Tags = maps.Merge(v.Tags, specialTags)
specialSolve[tag] = v
}
}
return specialSolve
}
func RemoveWpBlock(s string) string {
return removeWpBlock.ReplaceAllString(s, "")
}
func digestRaw(ctx context.Context, id uint64, arg ...any) (string, error) {
s := arg[1].(string)
limit := arg[3].(int)
if limit < 0 {
return s, nil
} else if limit == 0 {
return "", nil
}
s = more.ReplaceAllString(s, "")
fn := helper.GetContextVal(ctx, "postMoreFn", PostsMore)
return Digests(s, id, limit, fn), nil
}
func Digests(content string, id uint64, limit int, fn func(id uint64, content, closeTag string) string) string {
closeTag := ""
content = RemoveWpBlock(content)
c := digestConfig.Load()
tag := c.DigestAllowTag
if tag == "" {
tag = "<a><b><blockquote><br><cite><code><dd><del><div><dl><dt><em><h1><h2><h3><h4><h5><h6><i><img><li><ol><p><pre><span><strong><ul>"
}
content = digest.StripTags(content, tag)
length := utf8.RuneCountInString(content) + 1
if length <= limit {
return content
}
if len(c.specialSolve) > 0 {
content, closeTag = digest.CustomizeHtml(content, limit, c.specialSolve)
} else {
content, closeTag = digest.Html(content, limit)
}
if fn == nil {
return PostsMore(id, content, closeTag)
}
return fn(id, content, closeTag)
}
func PostsMore(id uint64, content, closeTag string) string {
tmp := `%s......%s<p class="read-more"><a href="/p/%d">继续阅读</a></p>`
if strings.Contains(closeTag, "pre") || strings.Contains(closeTag, "code") {
tmp = `%s%s......<p class="read-more"><a href="/p/%d">继续阅读</a></p>`
}
content = fmt.Sprintf(tmp, content, closeTag, id)
return content
}
func Digest(ctx context.Context, post *models.Posts, limit int) {
content, _ := cachemanager.GetBy[string]("digestPlugin", ctx, post.Id, time.Second, ctx, post.PostContent, post.Id, limit)
post.PostContent = content
}
func PostExcerpt(post *models.Posts) {
post.PostContent = strings.Replace(post.PostExcerpt, "\n", "<br/>", -1)
}

View File

@ -2,9 +2,9 @@ package plugins
import ( import (
"fmt" "fmt"
"github.com/fthvgb1/wp-go/app/wpconfig"
"github.com/fthvgb1/wp-go/helper/number" "github.com/fthvgb1/wp-go/helper/number"
str "github.com/fthvgb1/wp-go/helper/strings" str "github.com/fthvgb1/wp-go/helper/strings"
"github.com/fthvgb1/wp-go/internal/wpconfig"
"net/url" "net/url"
"strings" "strings"
) )
@ -25,7 +25,7 @@ func Gravatar(email string, isTls bool) (u string) {
q := url.Values{} q := url.Values{}
q.Add("s", "112") q.Add("s", "112")
q.Add("d", "mm") q.Add("d", "mm")
q.Add("r", strings.ToLower(wpconfig.Options.Value("avatar_rating"))) q.Add("r", strings.ToLower(wpconfig.GetOption("avatar_rating")))
u = fmt.Sprintf("%s?%s", u, q.Encode()) u = fmt.Sprintf("%s?%s", u, q.Encode())
return return
} }

176
app/plugins/pagination.go Normal file
View File

@ -0,0 +1,176 @@
package plugins
import (
"fmt"
"github.com/fthvgb1/wp-go/app/wpconfig"
"github.com/fthvgb1/wp-go/helper"
str "github.com/fthvgb1/wp-go/helper/strings"
"net/url"
"regexp"
"strconv"
"strings"
)
type PageEle struct {
PrevEle string
NextEle string
DotsEle string
MiddleEle string
CurrentEle string
}
func TwentyFifteenPagination() PageEle {
return twentyFifteen
}
func TwentyFifteenCommentPagination() CommentPageEle {
return twentyFifteenComment
}
type CommentPageEle struct {
PageEle
}
var twentyFifteen = PageEle{
PrevEle: `<a class="prev page-numbers" href="%s">上一页</a>`,
NextEle: `<a class="next page-numbers" href="%s">下一页</a>`,
DotsEle: `<span class="page-numbers dots">…</span>`,
MiddleEle: `<a class="page-numbers" href="%s"><span class="meta-nav screen-reader-text"> </span>%d</a>
`,
CurrentEle: `<span aria-current="page" class="page-numbers current">
<span class="meta-nav screen-reader-text"> </span>%d</span>`,
}
var twentyFifteenComment = CommentPageEle{
PageEle{
PrevEle: `<div class="nav-previous"><a href="%s">%s</a></div>`,
NextEle: `<div class="nav-next"><a href="%s">%s</a></div>`,
},
}
func (p PageEle) Current(page, totalPage, totalRow int) string {
return fmt.Sprintf(p.CurrentEle, page)
}
func (p PageEle) Prev(url string) string {
return fmt.Sprintf(p.PrevEle, url)
}
func (p PageEle) Next(url string) string {
return fmt.Sprintf(p.NextEle, url)
}
func (p PageEle) Dots() string {
return p.DotsEle
}
func (p PageEle) Middle(page int, url string) string {
return fmt.Sprintf(p.MiddleEle, url, page)
}
var reg = regexp.MustCompile(`(/page)/(\d+)`)
var commentReg = regexp.MustCompile(`/comment-page-(\d+)`)
var queryParam = []string{"paged", "cat", "m", "author", "tag"}
func (p PageEle) Urls(u url.URL, page int, isTLS bool) string {
var path, query = u.Path, u.RawQuery
if path == "/" {
v := u.Query()
for _, q := range queryParam {
if v.Get(q) != "" {
v.Set("paged", strconv.Itoa(page))
return str.Join(path, "?", v.Encode())
}
}
}
if !strings.Contains(path, "/page/") {
path = fmt.Sprintf("%s%s", path, "/page/1")
}
if page == 1 {
path = reg.ReplaceAllString(path, "")
} else {
s := fmt.Sprintf("$1/%d", page)
path = reg.ReplaceAllString(path, s)
}
path = strings.Replace(path, "//", "/", -1)
if path == "" {
path = "/"
}
if query != "" {
return str.Join(path, "?", query)
}
return path
}
func (p CommentPageEle) Urls(u url.URL, page int, isTLS bool) string {
var path, query = u.Path, u.RawQuery
if path == "/" {
v := u.Query()
if v.Get("p") != "" {
v.Set("cpage", strconv.Itoa(page))
return str.Join(path, "?", v.Encode(), "#comments")
}
}
if !strings.Contains(path, "/comment-page-") {
path = fmt.Sprintf("%s%s", path, "/comment-page-1")
}
path = commentReg.ReplaceAllString(path, fmt.Sprintf("/comment-page-%d", page))
path = strings.Replace(path, "//", "/", -1)
ur := path
if query != "" {
ur = str.Join(path, "?", query)
}
ur = str.Join(ur, "#comments")
return ur
}
func (p CommentPageEle) Middle(page int, url string) string {
return ""
}
func (p CommentPageEle) Dots() string {
return ""
}
func (p CommentPageEle) Current(page, totalPage, totalRow int) string {
return ""
}
func (p CommentPageEle) Prev(url string) string {
return fmt.Sprintf(p.PrevEle, url, helper.Or(wpconfig.GetOption("comment_order") == "asc", "较早评论", "较新评论"))
}
func (p CommentPageEle) Next(url string) string {
return fmt.Sprintf(p.NextEle, url, helper.Or(wpconfig.GetOption("comment_order") == "asc", "较新评论", "较早评论"))
}
type PaginationNav struct {
Currents func(page, totalPage, totalRows int) string
Prevs func(url string) string
Nexts func(url string) string
Dotss func() string
Middles func(page int, url string) string
Urlss func(u url.URL, page int, isTLS bool) string
}
func (p PaginationNav) Current(page, totalPage, totalRows int) string {
return p.Currents(page, totalPage, totalRows)
}
func (p PaginationNav) Prev(url string) string {
return p.Prevs(url)
}
func (p PaginationNav) Next(url string) string {
return p.Nexts(url)
}
func (p PaginationNav) Dots() string {
return p.Dotss()
}
func (p PaginationNav) Middle(page int, url string) string {
return p.Middles(page, url)
}
func (p PaginationNav) Urls(u url.URL, page int, isTLS bool) string {
return p.Urlss(u, page, isTLS)
}

View File

@ -0,0 +1,30 @@
package apply
import "github.com/fthvgb1/wp-go/safety"
var contains = safety.NewMap[string, any]()
func SetVal(key string, val any) {
contains.Store(key, val)
}
func DelVal(key string) {
contains.Delete(key)
}
func GetVal[V any](key string) (V, bool) {
v, ok := contains.Load(key)
if !ok {
var vv V
return vv, ok
}
return v.(V), ok
}
func GetRawVal(key string) (any, bool) {
return contains.Load(key)
}
func GetPlugins() any {
v, _ := contains.Load("wp-plugins")
return v
}

View File

@ -0,0 +1,83 @@
package enlightjs
import (
"fmt"
"github.com/fthvgb1/wp-go/app/phphelper"
"github.com/fthvgb1/wp-go/app/pkg/constraints"
"github.com/fthvgb1/wp-go/app/pkg/logs"
"github.com/fthvgb1/wp-go/app/theme/wp"
"github.com/fthvgb1/wp-go/app/wpconfig"
"github.com/fthvgb1/wp-go/helper/maps"
"github.com/goccy/go-json"
)
type Config struct {
Selectors Selectors `json:"selectors"`
Options Options `json:"options"`
}
type Options struct {
Indent int64 `json:"indent,omitempty"`
AmpersandCleanup bool `json:"ampersandCleanup,omitempty"`
Linehover bool `json:"linehover,omitempty"`
RawcodeDbclick bool `json:"rawcodeDbclick,omitempty"`
TextOverflow string `json:"textOverflow,omitempty"`
Linenumbers bool `json:"linenumbers,omitempty"`
Theme string `json:"theme,omitempty"`
Language string `json:"language,omitempty"`
RetainCssClasses bool `json:"retainCssClasses,omitempty"`
Collapse bool `json:"collapse,omitempty"`
ToolbarOuter string `json:"toolbarOuter,omitempty"`
ToolbarTop string `json:"toolbarTop,omitempty"`
ToolbarBottom string `json:"toolbarBottom,omitempty"`
}
type Selectors struct {
Block string `json:"block,omitempty"`
Inline string `json:"inline,omitempty"`
}
func EnlighterJS(h *wp.Handle) {
h.PushGroupHeadScript(constraints.AllScene, "enlighterjs-css", 20, `<link rel='stylesheet' id='enlighterjs-css' href='/wp-content/plugins/enlighter/cache/enlighterjs.min.css' media='all' />`)
h.PushCacheGroupFooterScript(constraints.AllScene, "enlighterJs", 10, func(h *wp.Handle) string {
op := wpconfig.GetOption("enlighter-options")
opp, err := phphelper.UnPHPSerializeToStrAnyMap(op)
if err != nil {
logs.Error(err, "获取enlighter-option失败", op)
return ""
}
v := Config{
Selectors: Selectors{
Block: maps.GetStrAnyValWithDefaults(opp, "enlighterjs-selector-block", "pre.EnlighterJSRAW"),
Inline: maps.GetStrAnyValWithDefaults(opp, "enlighterjs-selector-inline", "code.EnlighterJSRAW"),
},
Options: Options{
Indent: maps.GetStrAnyValWithDefaults[int64](opp, "enlighterjs-indent", 4),
AmpersandCleanup: maps.GetStrAnyValWithDefaults(opp, "enlighterjs-ampersandcleanup", true),
Linehover: maps.GetStrAnyValWithDefaults(opp, "enlighterjs-linehover", true),
RawcodeDbclick: maps.GetStrAnyValWithDefaults(opp, "enlighterjs-rawcodedbclick", true),
TextOverflow: maps.GetStrAnyValWithDefaults(opp, "enlighterjs-textoverflow", "break"),
Linenumbers: maps.GetStrAnyValWithDefaults(opp, "enlighterjs-linenumbers", true),
Theme: maps.GetStrAnyValWithDefaults(opp, "enlighterjs-theme", "enlighter"),
Language: maps.GetStrAnyValWithDefaults(opp, "enlighterjs-language", "generic"),
RetainCssClasses: maps.GetStrAnyValWithDefaults(opp, "enlighterjs-retaincss", false),
Collapse: false,
ToolbarOuter: "",
ToolbarTop: "{BTN_RAW}{BTN_COPY}{BTN_WINDOW}{BTN_WEBSITE}",
ToolbarBottom: "",
},
}
conf, err := json.Marshal(v)
if err != nil {
logs.Error(err, "json化enlighterjs配置失败")
return ""
}
return fmt.Sprintf(enlighterjs, conf)
})
}
var enlighterjs = `<script src='/wp-content/plugins/enlighter/cache/enlighterjs.min.js?ver=0A0B0C' id='enlighterjs-js'></script>
<script id='enlighterjs-js-after'>
!function(e,n){if("undefined"!=typeof EnlighterJS){var o=%s;(e.EnlighterJSINIT=function(){EnlighterJS.init(o.selectors.block,o.selectors.inline,o.options)})()}else{(n&&(n.error||n.log)||function(){})("Error: EnlighterJS resources not loaded yet!")}}(window,console);
</script>`

View File

@ -0,0 +1,72 @@
package wphandle
import (
"errors"
"github.com/fthvgb1/wp-go/app/pkg/config"
"github.com/fthvgb1/wp-go/app/pkg/logs"
"github.com/fthvgb1/wp-go/app/plugins/wphandle/apply"
"github.com/fthvgb1/wp-go/app/plugins/wphandle/enlightjs"
"github.com/fthvgb1/wp-go/app/plugins/wphandle/hiddenlogin"
"github.com/fthvgb1/wp-go/app/theme/wp"
str "github.com/fthvgb1/wp-go/helper/strings"
"github.com/fthvgb1/wp-go/safety"
"path/filepath"
"plugin"
)
var plugins = func() *safety.Map[string, func(*wp.Handle)] {
m := safety.NewMap[string, func(*wp.Handle)]()
m.Store("Enlightjs", enlightjs.EnlighterJS)
m.Store("HiddenLogin", hiddenlogin.HiddenLogin)
return m
}()
func RegisterPlugin(name string, fn func(*wp.Handle)) {
plugins.Store(name, fn)
}
func UsePlugins(h *wp.Handle, calls ...string) {
calls = append(calls, config.GetConfig().Plugins...)
for _, call := range calls {
call = str.FirstUpper(call)
if fn, ok := plugins.Load(call); ok {
fn(h)
}
}
}
func LoadPlugins() {
dirPath := config.GetConfig().PluginPath
if dirPath == "" {
return
}
glob, err := filepath.Glob(filepath.Join(dirPath, "*.so"))
if err != nil {
logs.Error(err, "读取插件目录错误", dirPath)
return
}
for _, entry := range glob {
p, err := plugin.Open(entry)
if err != nil {
logs.Error(err, "读取插件错误", entry)
continue
}
name := filepath.Ext(entry)
name = filepath.Base(entry[0 : len(entry)-len(name)])
name = str.FirstUpper(name)
pl, err := p.Lookup(name)
if err != nil {
logs.Error(err, "插件lookup错误", entry)
continue
}
plu, ok := pl.(func(*wp.Handle))
if !ok {
logs.Error(errors.New("switch func(*wp.Handle) fail"), "插件转换错误", entry)
continue
}
RegisterPlugin(name, plu)
}
apply.SetVal("wp-plugins", func(h *wp.Handle) {
UsePlugins(h)
})
}

View File

@ -0,0 +1,17 @@
package hiddenlogin
import (
"github.com/fthvgb1/wp-go/app/pkg/constraints/widgets"
"github.com/fthvgb1/wp-go/app/theme/wp"
str "github.com/fthvgb1/wp-go/helper/strings"
)
func HiddenLogin(h *wp.Handle) {
h.AddActionFilter(widgets.Meta, func(h *wp.Handle, s string, args ...any) string {
return str.Replace(s, map[string]string{
`<li><a href="/wp-login.php">登录</a></li>`: "",
`<li><a href="/feed">登录</a></li>`: "",
`<li><a href="/comments/feed">登录</a></li>`: "",
})
})
}

View File

@ -0,0 +1,22 @@
package tests
import (
"fmt"
"github.com/fthvgb1/wp-go/app/pkg/constraints"
"github.com/fthvgb1/wp-go/app/theme/wp"
)
func Tt(h *wp.Handle) {
h.HookHandle(constraints.PipeMiddleware, constraints.AllScene, func(call wp.HandleCall) (wp.HandleCall, bool) {
return call, false
})
/*h.PushPipeHook(constraints.Home, func(pipe wp.Pipe) (wp.Pipe, bool) {
return wp.Pipe{}, false
})*/
//h.DeletePipe(constraints.Home, constraints.PipeMiddleware)
h.ReplacePipe(constraints.Home, constraints.PipeMiddleware, wp.NewPipe("log", 500, func(next wp.HandleFn[*wp.Handle], h *wp.Handle) {
fmt.Println("ffff")
next(h)
fmt.Println("iiiii")
}))
}

View File

@ -0,0 +1,19 @@
package wpposts
import (
"fmt"
"github.com/fthvgb1/wp-go/app/pkg/models"
)
func PasswordProjectTitle(post *models.Posts) {
post.PostTitle = fmt.Sprintf("密码保护:%s", post.PostTitle)
}
func PasswdProjectContent(post *models.Posts) {
format := `
<form action="/login" class="post-password-form" method="post">
<p>此内容受密码保护如需查阅请在下列字段中输入您的密码</p>
<p><label for="pwbox-%d">密码 <input name="post_password" id="pwbox-%d" type="password" size="20"></label> <input type="submit" name="Submit" value="提交"></p>
</form>`
post.PostContent = fmt.Sprintf(format, post.Id, post.Id)
}

147
app/route/route.go Normal file
View File

@ -0,0 +1,147 @@
package route
import (
"github.com/fthvgb1/wp-go/app/actions"
"github.com/fthvgb1/wp-go/app/middleware"
"github.com/fthvgb1/wp-go/app/pkg/config"
"github.com/fthvgb1/wp-go/app/pkg/constraints"
"github.com/fthvgb1/wp-go/app/theme"
"github.com/fthvgb1/wp-go/app/wpconfig"
"github.com/fthvgb1/wp-go/cache/reload"
"github.com/fthvgb1/wp-go/helper/slice"
"github.com/fthvgb1/wp-go/helper/slice/mockmap"
str "github.com/fthvgb1/wp-go/helper/strings"
"github.com/gin-contrib/gzip"
"github.com/gin-contrib/pprof"
"github.com/gin-contrib/sessions"
"github.com/gin-contrib/sessions/cookie"
"github.com/gin-gonic/gin"
)
type GinSetter func(*gin.Engine)
var setters mockmap.Map[string, GinSetter]
var setterHooks []func(item mockmap.Item[string, GinSetter]) (mockmap.Item[string, GinSetter], bool)
// SetGinAction 方便插件在init时使用
func SetGinAction(name string, hook GinSetter, orders ...float64) {
setters.Set(name, hook, orders...)
}
func HookGinSetter(fn func(item mockmap.Item[string, GinSetter]) (mockmap.Item[string, GinSetter], bool)) {
setterHooks = append(setterHooks, fn)
}
// DelGinSetter 方便插件在init时使用
func DelGinSetter(name string) {
setterHooks = append(setterHooks, func(item mockmap.Item[string, GinSetter]) (mockmap.Item[string, GinSetter], bool) {
return item, item.Name != name
})
}
func SetupRouter() *gin.Engine {
// Disable Console Color
// gin.DisableConsoleColor()
r := gin.New()
c := config.GetConfig()
SetGinAction("initTrustIp", func(r *gin.Engine) {
if len(c.TrustIps) > 0 {
err := r.SetTrustedProxies(c.TrustIps)
if err != nil {
panic(err)
}
}
}, 99.5)
SetGinAction("setTemplate", func(r *gin.Engine) {
r.HTMLRender = theme.BuildTemplate()
wpconfig.SetTemplateFs(theme.TemplateFs)
}, 90.5)
siteFlowLimitMiddleware, siteFlow := middleware.FlowLimit(c.MaxRequestSleepNum, c.MaxRequestNum, c.CacheTime.SleepTime)
reload.Append(func() {
c = config.GetConfig()
siteFlow(c.MaxRequestSleepNum, c.MaxRequestNum, c.CacheTime.SleepTime)
}, "site-flowLimit-config")
SetGinAction("setGlobalMiddleware", func(r *gin.Engine) {
r.Use(
gin.Logger(),
middleware.ValidateServerNames(),
middleware.RecoverAndSendMail(gin.DefaultErrorWriter),
siteFlowLimitMiddleware,
middleware.SetStaticFileCache,
)
}, 88.5)
SetGinAction("setGzip", func(r *gin.Engine) {
//gzip 因为一般会用nginx做反代时自动使用gzip,所以go这边本身可以不用
if c.Gzip {
r.Use(gzip.Gzip(gzip.DefaultCompression, gzip.WithExcludedPaths([]string{
"/wp-includes/", "/wp-content/",
})))
}
}, 87.6)
SetGinAction("setWpDir", func(r *gin.Engine) {
if c.WpDir == "" {
panic("wordpress path can't be empty")
}
r.Static("/wp-content/uploads", str.Join(c.WpDir, "/wp-content/uploads"))
r.Static("/wp-content/themes", str.Join(c.WpDir, "/wp-content/themes"))
r.Static("/wp-content/plugins", str.Join(c.WpDir, "/wp-content/plugins"))
r.Static("/wp-includes/css", str.Join(c.WpDir, "/wp-includes/css"))
r.Static("/wp-includes/fonts", str.Join(c.WpDir, "/wp-includes/fonts"))
r.Static("/wp-includes/js", str.Join(c.WpDir, "/wp-includes/js"))
}, 86.1)
SetGinAction("setSession", func(r *gin.Engine) {
store := cookie.NewStore([]byte("secret"))
r.Use(sessions.Sessions("go-wp", store))
}, 85.1)
SetGinAction("setRoute", func(r *gin.Engine) {
r.GET("/", actions.Feed, middleware.SearchLimit(c.SingleIpSearchNum),
actions.ThemeHook(constraints.Home))
r.GET("/page/:page", actions.ThemeHook(constraints.Home))
r.GET("/p/category/:category", actions.ThemeHook(constraints.Category))
r.GET("/p/category/:category/page/:page", actions.ThemeHook(constraints.Category))
r.GET("/p/tag/:tag", actions.ThemeHook(constraints.Tag))
r.GET("/p/tag/:tag/page/:page", actions.ThemeHook(constraints.Tag))
r.GET("/p/date/:year/:month", actions.ThemeHook(constraints.Archive))
r.GET("/p/date/:year/:month/page/:page", actions.ThemeHook(constraints.Archive))
r.GET("/p/author/:author", actions.ThemeHook(constraints.Author))
r.GET("/p/author/:author/page/:page", actions.ThemeHook(constraints.Author))
r.POST("/login", actions.Login)
r.GET("/p/:id", actions.ThemeHook(constraints.Detail))
r.GET("/p/:id/comment-page-:page", actions.ThemeHook(constraints.Detail))
r.GET("/p/:id/feed", actions.PostFeed)
r.GET("/feed", actions.SiteFeed)
r.GET("/comments/feed", actions.CommentsFeed)
commentMiddleWare, _ := middleware.FlowLimit(c.MaxRequestSleepNum, 5, c.CacheTime.SleepTime)
r.POST("/comment", commentMiddleWare, actions.PostComment)
r.NoRoute(actions.ThemeHook(constraints.NoRoute))
}, 84.6)
SetGinAction("setpprof", func(r *gin.Engine) {
if c.Pprof != "" {
pprof.Register(r, c.Pprof)
}
}, 80.8)
for _, hook := range setterHooks {
setters = slice.FilterAndMap(setters, hook)
}
slice.SimpleSort(setters, slice.DESC, func(t mockmap.Item[string, GinSetter]) float64 {
return t.Order
})
for _, fn := range setters {
fn.Value(r)
}
return r
}

82
app/theme/fs.go Normal file
View File

@ -0,0 +1,82 @@
package theme
import (
"embed"
"github.com/fthvgb1/wp-go/multipTemplate"
"github.com/fthvgb1/wp-go/safety"
"html/template"
"io/fs"
"path/filepath"
"strings"
)
//go:embed *[^.go]
var TemplateFs embed.FS
var templates = safety.NewMap[string, *template.Template]() //方便外部获取模板render后的字符串不然在gin中获取不了
var multiple *multipTemplate.MultipleFsTemplate
func BuildTemplate() *multipTemplate.MultipleFsTemplate {
if multiple != nil {
tt := multipTemplate.NewFsTemplate(TemplateFs)
commonTemplate(tt)
for k, v := range map[string]*template.Template(any(tt.Template).(multipTemplate.TemplateMaps)) {
multiple.Template.Store(k, v)
}
} else {
multiple = multipTemplate.NewFsTemplates(TemplateFs, templates)
commonTemplate(multiple)
}
/*t.AddTemplate("twentyfifteen/*[^layout]/*.gohtml", FuncMap(), "twentyfifteen/layout/*.gohtml","wp/template.gohtml"). //单个主题设置
AddTemplate("twentyseventeen/*[^layout]/*.gohtml", FuncMap(), "twentyseventeen/layout/*.gohtml","wp/template.gohtml")*/
return multiple
}
func GetMultipleTemplate() *multipTemplate.MultipleFsTemplate {
if multiple == nil {
BuildTemplate()
}
return multiple
}
func GetTemplate(name string) (*template.Template, bool) {
t, ok := templates.Load(name)
return t, ok
}
// 所有主题模板通用设置
func commonTemplate(t *multipTemplate.MultipleFsTemplate) {
m, err := fs.Glob(t.Fs, "*/posts/*.gohtml")
if err != nil {
panic(err)
}
funMap := FuncMap()
for _, main := range m {
file := filepath.Base(main)
dir := strings.Split(main, "/")[0]
templ := template.Must(template.New(file).Funcs(funMap).ParseFS(t.Fs, main, filepath.Join(dir, "layout/*.gohtml"), "wp/template.gohtml"))
t.SetTemplate(main, templ)
}
}
func IsTemplateDirExists(tml string) bool {
arr, err := TemplateFs.ReadDir(tml)
if err != nil {
return false
}
if len(arr) > 0 {
return true
}
return false
}
func IsTemplateExists(tml string) bool {
t, ok := templates.Load(tml)
return ok && t != nil
}
func SetTemplate(name string, val *template.Template) {
templates.Store(name, val)
}

35
app/theme/hook.go Normal file
View File

@ -0,0 +1,35 @@
package theme
import (
"github.com/fthvgb1/wp-go/app/theme/wp"
str "github.com/fthvgb1/wp-go/helper/strings"
"github.com/fthvgb1/wp-go/safety"
)
var themeMap = safety.NewMap[string, func(*wp.Handle)]()
func AddTheme(name string, fn func(handle *wp.Handle)) {
themeMap.Store(name, fn)
}
func DelTheme(name string) {
themeMap.Delete(name)
}
func GetTheme(name string) (func(*wp.Handle), bool) {
return themeMap.Load(name)
}
func IsThemeHookFuncExist(name string) bool {
_, ok := themeMap.Load(name)
return ok
}
func Hook(themeName string, h *wp.Handle) {
fn, ok := themeMap.Load(themeName)
if ok && fn != nil {
fn(h)
return
}
panic(str.Join("theme ", themeName, " don't exist"))
}

View File

@ -0,0 +1,37 @@
package theme
import (
"github.com/fthvgb1/wp-go/app/pkg/models"
"github.com/fthvgb1/wp-go/app/wpconfig"
"html/template"
"time"
)
func postsFn(fn func(models.Posts) string, a models.Posts) string {
return fn(a)
}
func FuncMap() template.FuncMap {
return template.FuncMap{
"unescaped": func(s string) any {
return template.HTML(s)
},
"dateCh": func(t time.Time) any {
return t.Format("2006年 01月 02日")
},
"timeFormat": func(t time.Time, format string) any {
return t.Format(format)
},
"getOption": func(k string) string {
return wpconfig.GetOption(k)
},
"getLang": wpconfig.GetLang,
"postsFn": postsFn,
"exec": func(fn func() string) template.HTML {
return template.HTML(fn())
},
"callFuncString": func(fn func(string) string, s string) template.HTML {
return template.HTML(fn(s))
},
}
}

24
app/theme/theme.go Normal file
View File

@ -0,0 +1,24 @@
package theme
import (
"github.com/fthvgb1/wp-go/app/pkg/config"
"github.com/fthvgb1/wp-go/app/theme/twentyfifteen"
"github.com/fthvgb1/wp-go/app/theme/twentyseventeen"
"github.com/fthvgb1/wp-go/app/wpconfig"
)
func InitTheme() {
AddTheme(twentyfifteen.ThemeName, twentyfifteen.Hook)
AddTheme(twentyseventeen.ThemeName, twentyseventeen.Hook)
}
func GetCurrentTheme() string {
themeName := config.GetConfig().Theme
if themeName == "" {
themeName = wpconfig.GetOption("template")
}
if !IsTemplateDirExists(themeName) {
themeName = "twentyfifteen"
}
return themeName
}

View File

@ -0,0 +1,614 @@
package twentyfifteen
import (
"fmt"
"github.com/fthvgb1/wp-go/app/theme/wp"
"github.com/fthvgb1/wp-go/helper/slice"
"strconv"
"strings"
)
func colorSchemeCss(h *wp.Handle) string {
s := slice.Filter([]string{calColorSchemeCss(h), calSidebarTextColorCss(h), calHeaderBackgroundColorCss(h)}, func(s string, i int) bool {
return s != ""
})
if len(s) < 1 {
return ""
}
return fmt.Sprintf(`<style id='%s-inline-css'%s>\n%s\n</style>`, "twentyfifteen-style", "", strings.Join(s, "\n"))
}
func calColorSchemeCss(h *wp.Handle) (r string) {
color := getColorScheme(h)
if "default" == h.CommonThemeMods().ColorScheme || len(color) < 1 {
return
}
textColorRgb := slice.ToAnySlice(Hex2RgbUint8(color[3]))
sidebarTextColorRgb := Hex2RgbUint8(color[4])
sidebarTextColorRgbs := slice.ToAnySlice(sidebarTextColorRgb)
colors := map[string]string{
"background_color": color[0],
"header_background_color": color[1],
"box_background_color": color[2],
"textcolor": color[3],
"secondary_textcolor": fmt.Sprintf("rgba(%d, %d, %d, 0.7)", textColorRgb...),
"border_color": fmt.Sprintf("rgba(%d, %d, %d, 0.7)", textColorRgb...),
"border_focus_color": fmt.Sprintf("rgba(%d, %d, %d, 0.7)", textColorRgb...),
"sidebar_textcolor": color[4],
"sidebar_border_color": fmt.Sprintf("rgba(%d, %d, %d, 0.7)", sidebarTextColorRgbs...),
"sidebar_border_focus_color": fmt.Sprintf("rgba(%d, %d, %d, 0.7)", sidebarTextColorRgbs...),
"secondary_sidebar_textcolor": fmt.Sprintf("rgba(%d, %d, %d, 0.7)", sidebarTextColorRgbs...),
"meta_box_background_color": color[5],
}
r = cssTemplate
for k, v := range colors {
r = strings.ReplaceAll(r, fmt.Sprintf(`{$colors['%s']}`, k), v)
}
return
}
func calSidebarTextColorCss(h *wp.Handle) (r string) {
colors := getColorScheme(h)
themeMods := h.CommonThemeMods()
if themeMods.SidebarTextcolor == "" || themeMods.SidebarTextcolor == colors[4] {
return
}
linkColorRgb := Hex2RgbUint8(themeMods.SidebarTextcolor)
color := slice.ToAnySlice(linkColorRgb)
textColor := fmt.Sprintf(`rgba( %[1]v, %[2]v, %[3]v, 0.7)`, color...)
borderColor := fmt.Sprintf(`rgba( %[1]v, %[2]v, %[3]v, 0.1)`, color...)
borderFocusColor := fmt.Sprintf(`rgba( %[1]v, %[2]v, %[3]v, 0.3)`, color...)
r = fmt.Sprintf(sidebarTextColorTemplate, themeMods.SidebarTextcolor, textColor, borderColor, borderFocusColor)
return
}
func calHeaderBackgroundColorCss(h *wp.Handle) (r string) {
colors := getColorScheme(h)
themeMods := h.CommonThemeMods()
if themeMods.HeaderBackgroundColor == "" || themeMods.HeaderBackgroundColor == colors[1] {
return
}
r = fmt.Sprintf(headerBackgroundColorCssTemplate, themeMods.HeaderBackgroundColor, themeMods.HeaderBackgroundColor)
return
}
func getColorScheme(h *wp.Handle) (r []string) {
x, ok := colorscheme[h.CommonThemeMods().ColorScheme]
if ok {
r = x.Colors
}
return
}
type ColorScheme struct {
Label string `json:"label,omitempty"`
Colors []string `json:"colors,omitempty"`
}
func Hex2RgbUint8(color string) []uint8 {
var r []uint8
color = strings.TrimLeft(color, "#")
fn := func(s string) uint8 {
n, _ := strconv.ParseInt(s, 16, 0)
return uint8(n)
}
switch len(color) {
case 3:
r = []uint8{color[0], color[1], color[2]}
case 6:
r = []uint8{fn(color[:2]), fn(color[2:4]), fn(color[4:])}
}
return r
}
var cssTemplate = `
/* Color Scheme */
/* Background Color */
body {
background-color: {$colors['background_color']};
}
/* Sidebar Background Color */
body:before,
.site-header {
background-color: {$colors['header_background_color']};
}
/* Box Background Color */
.post-navigation,
.pagination,
.secondary,
.site-footer,
.hentry,
.page-header,
.page-content,
.comments-area,
.widecolumn {
background-color: {$colors['box_background_color']};
}
/* Box Background Color */
button,
input[type="button"],
input[type="reset"],
input[type="submit"],
.pagination .prev,
.pagination .next,
.widget_calendar tbody a,
.widget_calendar tbody a:hover,
.widget_calendar tbody a:focus,
.page-links a,
.page-links a:hover,
.page-links a:focus,
.sticky-post {
color: {$colors['box_background_color']};
}
/* Main Text Color */
button,
input[type="button"],
input[type="reset"],
input[type="submit"],
.pagination .prev,
.pagination .next,
.widget_calendar tbody a,
.page-links a,
.sticky-post {
background-color: {$colors['textcolor']};
}
/* Main Text Color */
body,
blockquote cite,
blockquote small,
a,
.dropdown-toggle:after,
.image-navigation a:hover,
.image-navigation a:focus,
.comment-navigation a:hover,
.comment-navigation a:focus,
.widget-title,
.entry-footer a:hover,
.entry-footer a:focus,
.comment-metadata a:hover,
.comment-metadata a:focus,
.pingback .edit-link a:hover,
.pingback .edit-link a:focus,
.comment-list .reply a:hover,
.comment-list .reply a:focus,
.site-info a:hover,
.site-info a:focus {
color: {$colors['textcolor']};
}
/* Main Text Color */
.entry-content a,
.entry-summary a,
.page-content a,
.comment-content a,
.pingback .comment-body > a,
.author-description a,
.taxonomy-description a,
.textwidget a,
.entry-footer a:hover,
.comment-metadata a:hover,
.pingback .edit-link a:hover,
.comment-list .reply a:hover,
.site-info a:hover {
border-color: {$colors['textcolor']};
}
/* Secondary Text Color */
button:hover,
button:focus,
input[type="button"]:hover,
input[type="button"]:focus,
input[type="reset"]:hover,
input[type="reset"]:focus,
input[type="submit"]:hover,
input[type="submit"]:focus,
.pagination .prev:hover,
.pagination .prev:focus,
.pagination .next:hover,
.pagination .next:focus,
.widget_calendar tbody a:hover,
.widget_calendar tbody a:focus,
.page-links a:hover,
.page-links a:focus {
background-color: {$colors['textcolor']}; /* Fallback for IE7 and IE8 */
background-color: {$colors['secondary_textcolor']};
}
/* Secondary Text Color */
blockquote,
a:hover,
a:focus,
.main-navigation .menu-item-description,
.post-navigation .meta-nav,
.post-navigation a:hover .post-title,
.post-navigation a:focus .post-title,
.image-navigation,
.image-navigation a,
.comment-navigation,
.comment-navigation a,
.widget,
.author-heading,
.entry-footer,
.entry-footer a,
.taxonomy-description,
.page-links > .page-links-title,
.entry-caption,
.comment-author,
.comment-metadata,
.comment-metadata a,
.pingback .edit-link,
.pingback .edit-link a,
.post-password-form label,
.comment-form label,
.comment-notes,
.comment-awaiting-moderation,
.logged-in-as,
.form-allowed-tags,
.no-comments,
.site-info,
.site-info a,
.wp-caption-text,
.gallery-caption,
.comment-list .reply a,
.widecolumn label,
.widecolumn .mu_register label {
color: {$colors['textcolor']}; /* Fallback for IE7 and IE8 */
color: {$colors['secondary_textcolor']};
}
/* Secondary Text Color */
blockquote,
.logged-in-as a:hover,
.comment-author a:hover {
border-color: {$colors['textcolor']}; /* Fallback for IE7 and IE8 */
border-color: {$colors['secondary_textcolor']};
}
/* Border Color */
hr,
.dropdown-toggle:hover,
.dropdown-toggle:focus {
background-color: {$colors['textcolor']}; /* Fallback for IE7 and IE8 */
background-color: {$colors['border_color']};
}
/* Border Color */
pre,
abbr[title],
table,
th,
td,
input,
textarea,
.main-navigation ul,
.main-navigation li,
.post-navigation,
.post-navigation div + div,
.pagination,
.comment-navigation,
.widget li,
.widget_categories .children,
.widget_nav_menu .sub-menu,
.widget_pages .children,
.site-header,
.site-footer,
.hentry + .hentry,
.author-info,
.entry-content .page-links a,
.page-links > span,
.page-header,
.comments-area,
.comment-list + .comment-respond,
.comment-list article,
.comment-list .pingback,
.comment-list .trackback,
.comment-list .reply a,
.no-comments {
border-color: {$colors['textcolor']}; /* Fallback for IE7 and IE8 */
border-color: {$colors['border_color']};
}
/* Border Focus Color */
a:focus,
button:focus,
input:focus {
outline-color: {$colors['textcolor']}; /* Fallback for IE7 and IE8 */
outline-color: {$colors['border_focus_color']};
}
input:focus,
textarea:focus {
border-color: {$colors['textcolor']}; /* Fallback for IE7 and IE8 */
border-color: {$colors['border_focus_color']};
}
/* Sidebar Link Color */
.secondary-toggle:before {
color: {$colors['sidebar_textcolor']};
}
.site-title a,
.site-description {
color: {$colors['sidebar_textcolor']};
}
/* Sidebar Text Color */
.site-title a:hover,
.site-title a:focus {
color: {$colors['secondary_sidebar_textcolor']};
}
/* Sidebar Border Color */
.secondary-toggle {
border-color: {$colors['sidebar_textcolor']}; /* Fallback for IE7 and IE8 */
border-color: {$colors['sidebar_border_color']};
}
/* Sidebar Border Focus Color */
.secondary-toggle:hover,
.secondary-toggle:focus {
border-color: {$colors['sidebar_textcolor']}; /* Fallback for IE7 and IE8 */
border-color: {$colors['sidebar_border_focus_color']};
}
.site-title a {
outline-color: {$colors['sidebar_textcolor']}; /* Fallback for IE7 and IE8 */
outline-color: {$colors['sidebar_border_focus_color']};
}
/* Meta Background Color */
.entry-footer {
background-color: {$colors['meta_box_background_color']};
}
@media screen and (min-width: 38.75em) {
/* Main Text Color */
.page-header {
border-color: {$colors['textcolor']};
}
}
@media screen and (min-width: 59.6875em) {
/* Make sure its transparent on desktop */
.site-header,
.secondary {
background-color: transparent;
}
/* Sidebar Background Color */
.widget button,
.widget input[type="button"],
.widget input[type="reset"],
.widget input[type="submit"],
.widget_calendar tbody a,
.widget_calendar tbody a:hover,
.widget_calendar tbody a:focus {
color: {$colors['header_background_color']};
}
/* Sidebar Link Color */
.secondary a,
.dropdown-toggle:after,
.widget-title,
.widget blockquote cite,
.widget blockquote small {
color: {$colors['sidebar_textcolor']};
}
.widget button,
.widget input[type="button"],
.widget input[type="reset"],
.widget input[type="submit"],
.widget_calendar tbody a {
background-color: {$colors['sidebar_textcolor']};
}
.textwidget a {
border-color: {$colors['sidebar_textcolor']};
}
/* Sidebar Text Color */
.secondary a:hover,
.secondary a:focus,
.main-navigation .menu-item-description,
.widget,
.widget blockquote,
.widget .wp-caption-text,
.widget .gallery-caption {
color: {$colors['secondary_sidebar_textcolor']};
}
.widget button:hover,
.widget button:focus,
.widget input[type="button"]:hover,
.widget input[type="button"]:focus,
.widget input[type="reset"]:hover,
.widget input[type="reset"]:focus,
.widget input[type="submit"]:hover,
.widget input[type="submit"]:focus,
.widget_calendar tbody a:hover,
.widget_calendar tbody a:focus {
background-color: {$colors['secondary_sidebar_textcolor']};
}
.widget blockquote {
border-color: {$colors['secondary_sidebar_textcolor']};
}
/* Sidebar Border Color */
.main-navigation ul,
.main-navigation li,
.widget input,
.widget textarea,
.widget table,
.widget th,
.widget td,
.widget pre,
.widget li,
.widget_categories .children,
.widget_nav_menu .sub-menu,
.widget_pages .children,
.widget abbr[title] {
border-color: {$colors['sidebar_border_color']};
}
.dropdown-toggle:hover,
.dropdown-toggle:focus,
.widget hr {
background-color: {$colors['sidebar_border_color']};
}
.widget input:focus,
.widget textarea:focus {
border-color: {$colors['sidebar_border_focus_color']};
}
.sidebar a:focus,
.dropdown-toggle:focus {
outline-color: {$colors['sidebar_border_focus_color']};
}
}
`
var headerBackgroundColorCssTemplate = `
/* Custom Header Background Color */
body:before,
.site-header {
background-color: %s;
}
@media screen and (min-width: 59.6875em) {
.site-header,
.secondary {
background-color: transparent;
}
.widget button,
.widget input[type="button"],
.widget input[type="reset"],
.widget input[type="submit"],
.widget_calendar tbody a,
.widget_calendar tbody a:hover,
.widget_calendar tbody a:focus {
color: %s;
}
}
`
var sidebarTextColorTemplate = `
/* Custom Sidebar Text Color */
.site-title a,
.site-description,
.secondary-toggle:before {
color: %[1]v;
}
.site-title a:hover,
.site-title a:focus {
color: %[1]v; /* Fallback for IE7 and IE8 */
color: %[2]v;
}
.secondary-toggle {
border-color: %[1]v; /* Fallback for IE7 and IE8 */
border-color: %[3]v;
}
.secondary-toggle:hover,
.secondary-toggle:focus {
border-color: %[1]v; /* Fallback for IE7 and IE8 */
border-color: %[4]v;
}
.site-title a {
outline-color: %[1]v; /* Fallback for IE7 and IE8 */
outline-color: %[4]v;
}
@media screen and (min-width: 59.6875em) {
.secondary a,
.dropdown-toggle:after,
.widget-title,
.widget blockquote cite,
.widget blockquote small {
color: %[1]v;
}
.widget button,
.widget input[type="button"],
.widget input[type="reset"],
.widget input[type="submit"],
.widget_calendar tbody a {
background-color: %[1]v;
}
.textwidget a {
border-color: %[1]v;
}
.secondary a:hover,
.secondary a:focus,
.main-navigation .menu-item-description,
.widget,
.widget blockquote,
.widget .wp-caption-text,
.widget .gallery-caption {
color: %[2]v;
}
.widget button:hover,
.widget button:focus,
.widget input[type="button"]:hover,
.widget input[type="button"]:focus,
.widget input[type="reset"]:hover,
.widget input[type="reset"]:focus,
.widget input[type="submit"]:hover,
.widget input[type="submit"]:focus,
.widget_calendar tbody a:hover,
.widget_calendar tbody a:focus {
background-color: %[2]v;
}
.widget blockquote {
border-color: %[2]v;
}
.main-navigation ul,
.main-navigation li,
.secondary-toggle,
.widget input,
.widget textarea,
.widget table,
.widget th,
.widget td,
.widget pre,
.widget li,
.widget_categories .children,
.widget_nav_menu .sub-menu,
.widget_pages .children,
.widget abbr[title] {
border-color: %[3]v;
}
.dropdown-toggle:hover,
.dropdown-toggle:focus,
.widget hr {
background-color: %[3]v;
}
.widget input:focus,
.widget textarea:focus {
border-color: %[4]v;
}
.sidebar a:focus,
.dropdown-toggle:focus {
outline-color: %[4]v;
}
}
`

View File

@ -0,0 +1,65 @@
package twentyfifteen
import (
"fmt"
"github.com/fthvgb1/wp-go/app/theme/wp"
"github.com/fthvgb1/wp-go/helper"
"github.com/fthvgb1/wp-go/helper/maps"
str "github.com/fthvgb1/wp-go/helper/strings"
)
var postx = map[string]string{
"left": "left",
"right": "right",
"center": "center",
}
var posty = map[string]string{
"top": "top",
"bottom": "bottom",
"center": "center",
}
var size = map[string]string{
"auto": "auto",
"contain": "contain",
"cover": "cover",
}
var repeat = map[string]string{
"repeat-x": "repeat-x",
"repeat-y": "repeat-y",
"repeat": "repeat",
"no-repeat": "no-repeat",
}
func CalCustomBackGround(h *wp.Handle) (r string) {
themeMods := h.CommonThemeMods()
if themeMods.BackgroundImage == "" && (themeMods.BackgroundColor == "" || themeMods.BackgroundColor == themesupport.CustomBackground.DefaultColor) {
return
}
s := str.NewBuilder()
if themeMods.BackgroundImage != "" {
s.Sprintf(` background-image: url("%s");`, helper.CutUrlHost(themeMods.BackgroundImage))
}
backgroundPositionX := helper.Defaults(themeMods.BackgroundPositionX, themesupport.CustomBackground.DefaultPositionX)
backgroundPositionX = maps.WithDefaultVal(postx, backgroundPositionX, "left")
backgroundPositionY := helper.Defaults(themeMods.BackgroundPositionY, themesupport.CustomBackground.DefaultPositionY)
backgroundPositionY = maps.WithDefaultVal(posty, backgroundPositionY, "top")
positon := fmt.Sprintf(" background-position: %s %s;", backgroundPositionX, backgroundPositionY)
siz := helper.DefaultVal(themeMods.BackgroundSize, themesupport.CustomBackground.DefaultSize)
siz = maps.WithDefaultVal(size, siz, "auto")
siz = fmt.Sprintf(" background-size: %s;", siz)
repeats := helper.Defaults(themeMods.BackgroundRepeat, themesupport.CustomBackground.DefaultRepeat)
repeats = maps.WithDefaultVal(repeat, repeats, "repeat")
repeats = fmt.Sprintf(" background-repeat: %s;", repeats)
attachment := helper.Defaults(themeMods.BackgroundAttachment, themesupport.CustomBackground.DefaultAttachment)
attachment = helper.Or(attachment == "fixed", "fixed", "scroll")
attachment = fmt.Sprintf(" background-attachment: %s;", attachment)
s.WriteString(positon, siz, repeats, attachment)
r = fmt.Sprintf(`<style id="custom-background-css">
body.custom-background {%s}
</style>`, s.String())
return
}

View File

@ -0,0 +1,123 @@
package twentyfifteen
import (
"github.com/fthvgb1/wp-go/app/pkg/constraints"
"github.com/fthvgb1/wp-go/app/theme/wp"
"github.com/fthvgb1/wp-go/cache/reload"
str "github.com/fthvgb1/wp-go/helper/strings"
)
var style = `<style type="text/css" id="twentyfifteen-header-css">`
var defaultTextStyle = `.site-header {
padding-top: 14px;
padding-bottom: 14px;
}
.site-branding {
min-height: 42px;
}
@media screen and (min-width: 46.25em) {
.site-header {
padding-top: 21px;
padding-bottom: 21px;
}
.site-branding {
min-height: 56px;
}
}
@media screen and (min-width: 55em) {
.site-header {
padding-top: 25px;
padding-bottom: 25px;
}
.site-branding {
min-height: 62px;
}
}
@media screen and (min-width: 59.6875em) {
.site-header {
padding-top: 0;
padding-bottom: 0;
}
.site-branding {
min-height: 0;
}
}`
var imgStyle = `.site-header {
/*
* No shorthand so the Customizer can override individual properties.
* @see https://core.trac.wordpress.org/ticket/31460
*/
background-image: url("%s");
background-repeat: no-repeat;
background-position: 50% 50%;
-webkit-background-size: cover;
-moz-background-size: cover;
-o-background-size: cover;
background-size: cover;
}
@media screen and (min-width: 59.6875em) {
body:before {
/*
* No shorthand so the Customizer can override individual properties.
* @see https://core.trac.wordpress.org/ticket/31460
*/
background-image: url("%s");
background-repeat: no-repeat;
background-position: 100% 50%;
-webkit-background-size: cover;
-moz-background-size: cover;
-o-background-size: cover;
background-size: cover;
border-right: 0;
}
.site-header {
background: transparent;
}
}`
var header = reload.Vars(constraints.Defaults, "twentyfifteen-customheader")
func calCustomHeaderImg(h *wp.Handle) (r string, rand bool) {
img, rand := h.GetCustomHeaderImg()
if img.Path == "" && h.DisplayHeaderText() {
return
}
ss := str.NewBuilder()
ss.WriteString(style)
if img.Path == "" && !h.DisplayHeaderText() {
ss.WriteString(defaultTextStyle)
}
if img.Path != "" {
ss.Sprintf(imgStyle, img.Path, img.Path)
}
if !h.DisplayHeaderText() {
ss.WriteString(`.site-title,
.site-description {
clip: rect(1px, 1px, 1px, 1px);
position: absolute;
}`)
}
ss.WriteString("</style>")
r = ss.String()
return
}
func customHeader(h *wp.Handle) func() string {
return func() string {
headers := header.Load()
if headers == constraints.Defaults {
headerss, rand := calCustomHeaderImg(h)
headers = headerss
if !rand {
header.Store(headers)
}
}
return headers
}
}

View File

@ -1,12 +1,9 @@
{{ define "layout/base"}} {{ define "layout/base"}}
<!DOCTYPE html> <!DOCTYPE html>
<html lang="{{getLang}}" class="no-js"> <html lang="{{getLang}}" class="no-js">
<head>
{{template "layout/head" .}} {{template "layout/head" .}}
{{block "head" .}}
{{end}} <body class="{{.calBodyClass|exec}}">
</head>
<body>
{{template "svg"}} {{template "svg"}}
<div id="page" class="hfeed site"> <div id="page" class="hfeed site">
<a class="skip-link screen-reader-text" href="#content"> <a class="skip-link screen-reader-text" href="#content">
@ -16,6 +13,7 @@
<div id="sidebar" class="sidebar" style="position: relative; "> <div id="sidebar" class="sidebar" style="position: relative; ">
<header id="masthead" class="site-header"> <header id="masthead" class="site-header">
<div class="site-branding"> <div class="site-branding">
{{.customLogo|exec}}
<h1 class="site-title"> <h1 class="site-title">
<a href="/" rel="home">{{ "blogname"| getOption }}</a> <a href="/" rel="home">{{ "blogname"| getOption }}</a>
</h1> </h1>
@ -33,11 +31,7 @@
{{end}} {{end}}
</div> </div>
<footer id="colophon" class="site-footer"> {{template "common/colophon" .}}
<div class="site-info">
<a href="https://cn.wordpress.org/" class="imprint">自豪地采用WordPress</a>
</div>
</footer>
</div> </div>
{{template "layout/footer" .}} {{template "layout/footer" .}}

View File

@ -21,7 +21,7 @@
这儿似乎什么都没有,试试搜索? 这儿似乎什么都没有,试试搜索?
{{end}} {{end}}
</p> </p>
<form role="search" method="get" class="search-form" action="https://www.xloyy.com/"> <form role="search" method="get" class="search-form" action="/">
<label> <label>
<span class="screen-reader-text">搜索:</span> <span class="screen-reader-text">搜索:</span>
<input type="search" class="search-field" placeholder="搜索…" value="{{.search}}" name="s"> <input type="search" class="search-field" placeholder="搜索…" value="{{.search}}" name="s">

View File

@ -8,10 +8,8 @@
var screenReaderText = {"expand":"<span class=\"screen-reader-text\">\u5c55\u5f00\u5b50\u83dc\u5355<\/span>","collapse":"<span class=\"screen-reader-text\">\u6298\u53e0\u5b50\u83dc\u5355<\/span>"}; var screenReaderText = {"expand":"<span class=\"screen-reader-text\">\u5c55\u5f00\u5b50\u83dc\u5355<\/span>","collapse":"<span class=\"screen-reader-text\">\u6298\u53e0\u5b50\u83dc\u5355<\/span>"};
</script> </script>
<script src='/wp-content/themes/twentyfifteen/js/functions.js?ver=20220524' id='twentyfifteen-script-js'></script> <script src='/wp-content/themes/twentyfifteen/js/functions.js?ver=20220524' id='twentyfifteen-script-js'></script>
<script src='/wp-content/plugins/enlighter/cache/enlighterjs.min.js?ver=0A0B0C' id='enlighterjs-js'></script>
<script id='enlighterjs-js-after'> {{template "common/footer" .}}
!function(e,n){if("undefined"!=typeof EnlighterJS){var o={"selectors":{"block":"pre.EnlighterJSRAW","inline":"code.EnlighterJSRAW"},"options":{"indent":4,"ampersandCleanup":true,"linehover":true,"rawcodeDbclick":false,"textOverflow":"break","linenumbers":true,"theme":"enlighter","language":"generic","retainCssClasses":false,"collapse":false,"toolbarOuter":"","toolbarTop":"{BTN_RAW}{BTN_COPY}{BTN_WINDOW}{BTN_WEBSITE}","toolbarBottom":""}};(e.EnlighterJSINIT=function(){EnlighterJS.init(o.selectors.block,o.selectors.inline,o.options)})()}else{(n&&(n.error||n.log)||function(){})("Error: EnlighterJS resources not loaded yet!")}}(window,console);
</script>
{{ block "footer" .}} {{ block "footer" .}}
{{end}} {{end}}
{{end}} {{end}}

View File

@ -0,0 +1,5 @@
{{define "layout/sidebar" }}
<div id="widget-area" class="widget-area" role="complementary">
{{template "common/sidebarWidget" .}}
</div>
{{end}}

View File

@ -1,11 +1,15 @@
{{template "layout/base" .}} {{template "layout/base" .}}
{{define "content"}} {{define "content"}}
{{ if .post.PostContent}} {{ if and (.post) (gt .post.Id 0)}}
<div id="primary" class="content-area"> <div id="primary" class="content-area">
<main id="main" class="site-main"> <main id="main" class="site-main">
<article id="post-{{.post.Id}}" <article class="{{ .post|postsFn .calPostClass}}">
class="post-{{.post.Id}} post type-post status-publish format-standard hentry category-uncategorized"> {{if .post.Thumbnail.Path }}
<div class="post-thumbnail">
<img width="{{.post.Thumbnail.Width}}" height="{{.post.Thumbnail.Height}}" src="{{.post.Thumbnail.Path}}" class="attachment-post-thumbnail size-post-thumbnail wp-post-image" alt="" decoding="async">
</div>
{{end}}
<header class="entry-header"> <header class="entry-header">
<h1 class="entry-title">{{.post.PostTitle}}</h1></header><!-- .entry-header --> <h1 class="entry-title">{{.post.PostTitle}}</h1></header><!-- .entry-header -->
@ -38,8 +42,7 @@
{{if .post.TagsHtml}} {{if .post.TagsHtml}}
<span class="tags-links"> <span class="tags-links">
<span class="screen-reader-text">标签 </span> <span class="screen-reader-text">标签 </span>{{.post.TagsHtml|unescaped}}
{{.post.TagsHtml|unescaped}}
</span> </span>
{{end}} {{end}}
</footer> </footer>
@ -49,11 +52,27 @@
{{ if .showComment}} {{ if .showComment}}
<div id="comments" class="comments-area"> <div id="comments" class="comments-area">
{{ if gt .post.CommentCount 0}} {{ if ne .comments ""}}
<h2 class="comments-title">《{{.post.PostTitle}}》上有{{.post.CommentCount}}条评论 </h2> <h2 class="comments-title">《{{.post.PostTitle}}》上有{{.totalCommentNum}}条评论 </h2>
{{if gt .totalCommentPage 1}}
<nav class="navigation comment-navigation">
<h2 class="screen-reader-text">评论导航</h2>
<div class="nav-links">
{{ .commentPageNav|unescaped}}
</div><!-- .nav-links -->
</nav>
{{end}}
<ol class="comment-list"> <ol class="comment-list">
{{.comments|unescaped}} {{.comments|unescaped}}
</ol> </ol>
{{if gt .totalCommentPage 1}}
<nav class="navigation comment-navigation">
<h2 class="screen-reader-text">评论导航</h2>
<div class="nav-links">
{{ .commentPageNav|unescaped}}
</div><!-- .nav-links -->
</nav>
{{end}}
{{end}} {{end}}
{{if eq .post.CommentStatus "open"}} {{if eq .post.CommentStatus "open"}}
@ -130,6 +149,3 @@
{{template "layout/empty"}} {{template "layout/empty"}}
{{end }} {{end }}
{{end}} {{end}}
{{ define "footer"}}
<script src='/wp-includes/js/comment-reply.min.js?ver=6.0.2' id='comment-reply-js'></script>
{{end}}

View File

@ -7,17 +7,12 @@
{{if .header}} {{if .header}}
<header class="page-header"> <header class="page-header">
<h1 class="page-title"> <h1 class="page-title">
{{if .search}}
{{.header}}
{{else}}
{{.header |unescaped}} {{.header |unescaped}}
{{end}}
</h1> </h1>
</header> </header>
{{end}} {{end}}
{{ range $k,$v:=.posts}} {{ range $k,$v:=.posts}}
<article id="post-{{$v.Id}}" <article class="{{ $v|postsFn $.calPostClass}}">
class="post-{{$v.Id}} post {{if $v.Thumbnail.Path}}has-post-thumbnail{{end}} type-post status-publish format-standard hentry category">
{{if $v.Thumbnail.Path}} {{if $v.Thumbnail.Path}}
<a class="post-thumbnail" href="/p/{{$v.Id}}" aria-hidden="true"> <a class="post-thumbnail" href="/p/{{$v.Id}}" aria-hidden="true">
<img width="{{$v.Thumbnail.Width}}" height="{{$v.Thumbnail.Height}}" src="{{$v.Thumbnail.Path}}" class="attachment-post-thumbnail size-post-thumbnail wp-post-image" alt="{{$v.PostTitle}}" decoding="async"> <img width="{{$v.Thumbnail.Width}}" height="{{$v.Thumbnail.Height}}" src="{{$v.Thumbnail.Path}}" class="attachment-post-thumbnail size-post-thumbnail wp-post-image" alt="{{$v.PostTitle}}" decoding="async">
@ -36,6 +31,9 @@
</div><!-- .entry-content --> </div><!-- .entry-content -->
<footer class="entry-footer"> <footer class="entry-footer">
{{if $v.IsSticky}}
<span class="sticky-post">特色</span>
{{end}}
<span class="posted-on"> <span class="posted-on">
<span class="screen-reader-text">发布于 </span> <span class="screen-reader-text">发布于 </span>
<a href="/p/{{$v.Id}}" rel="bookmark"> <a href="/p/{{$v.Id}}" rel="bookmark">

View File

@ -0,0 +1,295 @@
package twentyfifteen
import "github.com/fthvgb1/wp-go/app/wpconfig"
type themeSupport struct {
CustomBackground customBackground `json:"custom-background"`
EditorColorPalette []EditorColorPalette `json:"editor-color-palette"`
EditorGradientPresets []EditorGradientPresets `json:"editor-gradient-presets"`
}
type customBackground struct {
DefaultImage string `json:"default-image"`
DefaultPreset string `json:"default-preset"`
DefaultPositionX string `json:"default-position-x"`
DefaultPositionY string `json:"default-position-y"`
DefaultSize string `json:"default-size"`
DefaultRepeat string `json:"default-repeat"`
DefaultAttachment string `json:"default-attachment"`
DefaultColor string `json:"default-color"`
WpHeadCallback string `json:"wp-head-callback"`
AdminHeadCallback string `json:"admin-head-callback"`
AdminPreviewCallback string `json:"admin-preview-callback"`
}
type EditorColorPalette struct {
Name string `json:"name"`
Slug string `json:"slug"`
Color string `json:"color"`
}
type EditorGradientPresets struct {
Name string `json:"name"`
Slug string `json:"slug"`
Gradient string `json:"gradient"`
}
var themesupport = themeSupport{
CustomBackground: customBackground{
DefaultImage: "",
DefaultPreset: "default",
DefaultPositionX: "left",
DefaultPositionY: "top",
DefaultSize: "auto",
DefaultRepeat: "repeat",
DefaultAttachment: "fixed",
DefaultColor: "f1f1f1",
WpHeadCallback: "_custom_background_cb",
AdminHeadCallback: "",
AdminPreviewCallback: "",
},
EditorColorPalette: []EditorColorPalette{
{
Name: "暗灰色",
Slug: "dark-gray",
Color: "#111",
},
{
Name: "亮灰色",
Slug: "light-gray",
Color: "#f1f1f1",
},
{
Name: "白色",
Slug: "white",
Color: "#fff",
},
{
Name: "黄色",
Slug: "yellow",
Color: "#f4ca16",
},
{
Name: "暗棕色",
Slug: "dark-brown",
Color: "#352712",
},
{
Name: "粉色",
Slug: "medium-pink",
Color: "#e53b51",
},
{
Name: "浅粉色",
Slug: "light-pink",
Color: "#ffe5d1",
},
{
Name: "暗紫色",
Slug: "dark-purple",
Color: "#2e2256",
},
{
Name: "紫色",
Slug: "purple",
Color: "#674970",
},
{
Name: "蓝灰色",
Slug: "blue-gray",
Color: "#22313f",
},
{
Name: "亮蓝色",
Slug: "bright-blue",
Color: "#55c3dc",
},
{
Name: "浅蓝色",
Slug: "light-blue",
Color: "#e9f2f9",
},
},
EditorGradientPresets: []EditorGradientPresets{
{
Name: "Dark Gray Gradient",
Slug: "dark-gray-gradient-gradient",
Gradient: "linear-gradient(90deg, rgba(17,17,17,1) 0%, rgba(42,42,42,1) 100%)",
},
{
Name: "Light Gray Gradient",
Slug: "light-gray-gradient",
Gradient: "linear-gradient(90deg, rgba(241,241,241,1) 0%, rgba(215,215,215,1) 100%)",
},
{
Name: "White Gradient",
Slug: "white-gradient",
Gradient: "linear-gradient(90deg, rgba(255,255,255,1) 0%, rgba(230,230,230,1) 100%)",
},
{
Name: "Yellow Gradient",
Slug: "yellow-gradient",
Gradient: "linear-gradient(90deg, rgba(244,202,22,1) 0%, rgba(205,168,10,1) 100%)",
},
{
Name: "Dark Brown Gradient",
Slug: "dark-brown-gradient",
Gradient: "linear-gradient(90deg, rgba(53,39,18,1) 0%, rgba(91,67,31,1) 100%)",
},
{
Name: "Medium Pink Gradient",
Slug: "medium-pink-gradient",
Gradient: "linear-gradient(90deg, rgba(229,59,81,1) 0%, rgba(209,28,51,1) 100%)",
},
{
Name: "Light Pink Gradient",
Slug: "light-pink-gradient",
Gradient: "linear-gradient(90deg, rgba(255,229,209,1) 0%, rgba(255,200,158,1) 100%)",
},
{
Name: "Dark Purple Gradient",
Slug: "dark-purple-gradient",
Gradient: "linear-gradient(90deg, rgba(46,34,86,1) 0%, rgba(66,48,123,1) 100%)",
},
{
Name: "Purple Gradient",
Slug: "purple-gradient",
Gradient: "linear-gradient(90deg, rgba(103,73,112,1) 0%, rgba(131,93,143,1) 100%)",
},
{
Name: "Blue Gray Gradient",
Slug: "blue-gray-gradient",
Gradient: "linear-gradient(90deg, rgba(34,49,63,1) 0%, rgba(52,75,96,1) 100%)",
},
{
Name: "Bright Blue Gradient",
Slug: "bright-blue-gradient",
Gradient: "linear-gradient(90deg, rgba(85,195,220,1) 0%, rgba(43,180,211,1) 100%)",
},
{
Name: "Light Blue Gradient",
Slug: "light-blue-gradient",
Gradient: "linear-gradient(90deg, rgba(233,242,249,1) 0%, rgba(193,218,238,1) 100%)",
},
},
}
var colorscheme = map[string]ColorScheme{
"default": {
Label: "Default",
Colors: []string{
"#f1f1f1",
"#ffffff",
"#ffffff",
"#333333",
"#333333",
"#f7f7f7",
},
},
"dark": {
Label: "Dark",
Colors: []string{
"#111111",
"#202020",
"#202020",
"#bebebe",
"#bebebe",
"#1b1b1b",
},
},
"pink": {
Label: "Pink",
Colors: []string{
"#ffe5d1",
"#e53b51",
"#ffffff",
"#352712",
"#ffffff",
"#f1f1f1",
},
},
"purple": {
Label: "Purple",
Colors: []string{
"#674970",
"#2e2256",
"#ffffff",
"#2e2256",
"#ffffff",
"#f1f1f1",
},
},
"blue": {
Label: "Blue",
Colors: []string{
"#e9f2f9",
"#55c3dc",
"#ffffff",
"#22313f",
"#ffffff",
"#f1f1f1",
},
},
}
var _ = func() struct{} {
v := wpconfig.ThemeSupport{
CoreBlockPatterns: true,
WidgetsBlockEditor: true,
AutomaticFeedLinks: true,
TitleTag: true,
PostThumbnails: true,
Menus: true,
HTML5: []string{
"search-form",
"comment-form",
"comment-list",
"gallery",
"caption",
"script",
"style",
"navigation-widgets",
},
PostFormats: []string{
"aside",
"image",
"video",
"quote",
"link",
"gallery",
"status",
"audio",
"chat",
},
CustomLogo: wpconfig.CustomLogo{
Width: 248,
Height: 248,
FlexWidth: false,
FlexHeight: true,
HeaderText: "",
UnlinkHomepageLogo: false,
},
CustomizeSelectiveRefreshWidgets: true,
EditorStyle: true,
EditorStyles: true,
WpBlockStyles: true,
ResponsiveEmbeds: true,
CustomHeader: wpconfig.CustomHeader{
DefaultImage: "",
RandomDefault: false,
Width: 954,
Height: 1300,
FlexHeight: false,
FlexWidth: false,
DefaultTextColor: "333333",
HeaderText: true,
Uploads: true,
WpHeadCallback: "twentyfifteen_header_style",
AdminHeadCallback: "",
AdminPreviewCallback: "",
Video: false,
VideoActiveCallback: "is_front_page",
},
Widgets: true,
}
wpconfig.SetThemeSupport(ThemeName, v)
return struct{}{}
}()

View File

@ -0,0 +1,63 @@
package twentyfifteen
import (
"github.com/fthvgb1/wp-go/app/pkg/constraints"
"github.com/fthvgb1/wp-go/app/pkg/constraints/widgets"
"github.com/fthvgb1/wp-go/app/plugins"
"github.com/fthvgb1/wp-go/app/theme/wp"
"github.com/fthvgb1/wp-go/app/theme/wp/components"
"github.com/fthvgb1/wp-go/app/theme/wp/middleware"
"github.com/fthvgb1/wp-go/app/wpconfig"
"strings"
)
const ThemeName = "twentyfifteen"
func Hook(h *wp.Handle) {
wp.Run(h, configs)
}
func configs(h *wp.Handle) {
h.AddActionFilter(widgets.Search, func(h *wp.Handle, s string, args ...any) string {
return strings.ReplaceAll(s, `class="search-submit"`, `class="search-submit screen-reader-text"`)
})
wp.InitPipe(h)
middleware.CommonMiddleware(h)
setPaginationAndRender(h)
h.PushCacheGroupHeadScript(constraints.AllScene, "CalCustomBackGround", 10.005, CalCustomBackGround)
h.PushCacheGroupHeadScript(constraints.AllScene, "colorSchemeCss", 10.0056, colorSchemeCss)
h.CommonComponents()
components.WidgetArea(h)
h.PushRender(constraints.AllScene, wp.NewHandleFn(renderCustomHeader, 20.5, "renderCustomHeader"))
wp.PushIndexHandler(constraints.PipeRender, h, wp.NewHandleFn(wp.IndexRender, 50.005, "wp.IndexRender"))
h.PushRender(constraints.Detail, wp.NewHandleFn(wp.DetailRender, 50.005, "wp.DetailRender"))
h.PushRender(constraints.Detail, wp.NewHandleFn(postThumb, 60.005, "postThumb"))
h.PushDataHandler(constraints.Detail, wp.NewHandleFn(wp.Detail, 100.005, "wp.Detail"))
wp.PushIndexHandler(constraints.PipeData, h, wp.NewHandleFn(wp.Index, 100.005, "wp.Index"))
h.PushDataHandler(constraints.AllScene, wp.NewHandleFn(wp.PreCodeAndStats, 80.005, "wp.PreCodeAndStats"))
h.PushRender(constraints.AllScene, wp.NewHandleFn(wp.PreTemplate, 70.005, "wp.PreTemplate"))
}
func setPaginationAndRender(h *wp.Handle) {
h.PushHandler(constraints.PipeRender, constraints.Detail, wp.NewHandleFn(func(hh *wp.Handle) {
d := hh.GetDetailHandle()
d.CommentRender = plugins.CommentRender()
d.CommentPageEle = plugins.TwentyFifteenCommentPagination()
}, 150, "setPaginationAndRender"))
wp.PushIndexHandler(constraints.PipeRender, h, wp.NewHandleFn(func(hh *wp.Handle) {
i := hh.GetIndexHandle()
i.SetPageEle(plugins.TwentyFifteenPagination())
}, 150, "setPaginationAndRender"))
}
func postThumb(h *wp.Handle) {
d := h.GetDetailHandle()
if d.Post.Thumbnail.Path != "" {
d.Post.Thumbnail = wpconfig.Thumbnail(d.Post.Thumbnail.OriginAttachmentData, "post-thumbnail", "")
}
}
func renderCustomHeader(h *wp.Handle) {
h.SetData("customHeader", customHeader(h))
}

View File

@ -0,0 +1,574 @@
package twentyseventeen
import (
"fmt"
"github.com/fthvgb1/wp-go/app/theme/wp"
"github.com/fthvgb1/wp-go/app/wpconfig"
"github.com/fthvgb1/wp-go/helper/number"
str "github.com/fthvgb1/wp-go/helper/strings"
)
func colorScheme(h *wp.Handle) (r string) {
if "custom" != wpconfig.GetThemeModsVal(ThemeName, "colorscheme", "light") {
return
}
s := str.NewBuilder()
hue := number.ToString(wpconfig.GetThemeModsVal[int64](ThemeName, "colorscheme_hue", 250))
reducedSaturation := fmt.Sprintf("%d%%", int(.8*50))
saturation := fmt.Sprintf("%d%%", 50)
css := str.Replace(customCss, map[string]string{
"' . $hue . '": hue,
"' . esc_attr( $hue ) . '": hue,
"' . $saturation . '": saturation,
"' . esc_attr( $saturation ) .' ": saturation,
"' . $reduced_saturation . '": reducedSaturation,
})
s.Sprintf(`<style type="text/css" id="custom-theme-colors">%s</style>`, css)
r = s.String()
return
}
var customCss = `
/**
* Twenty Seventeen: Color Patterns
*
* Colors are ordered from dark to light.
*/
.colors-custom a:hover,
.colors-custom a:active,
.colors-custom .entry-content a:focus,
.colors-custom .entry-content a:hover,
.colors-custom .entry-summary a:focus,
.colors-custom .entry-summary a:hover,
.colors-custom .comment-content a:focus,
.colors-custom .comment-content a:hover,
.colors-custom .widget a:focus,
.colors-custom .widget a:hover,
.colors-custom .site-footer .widget-area a:focus,
.colors-custom .site-footer .widget-area a:hover,
.colors-custom .posts-navigation a:focus,
.colors-custom .posts-navigation a:hover,
.colors-custom .comment-metadata a:focus,
.colors-custom .comment-metadata a:hover,
.colors-custom .comment-metadata a.comment-edit-link:focus,
.colors-custom .comment-metadata a.comment-edit-link:hover,
.colors-custom .comment-reply-link:focus,
.colors-custom .comment-reply-link:hover,
.colors-custom .widget_authors a:focus strong,
.colors-custom .widget_authors a:hover strong,
.colors-custom .entry-title a:focus,
.colors-custom .entry-title a:hover,
.colors-custom .entry-meta a:focus,
.colors-custom .entry-meta a:hover,
.colors-custom.blog .entry-meta a.post-edit-link:focus,
.colors-custom.blog .entry-meta a.post-edit-link:hover,
.colors-custom.archive .entry-meta a.post-edit-link:focus,
.colors-custom.archive .entry-meta a.post-edit-link:hover,
.colors-custom.search .entry-meta a.post-edit-link:focus,
.colors-custom.search .entry-meta a.post-edit-link:hover,
.colors-custom .page-links a:focus .page-number,
.colors-custom .page-links a:hover .page-number,
.colors-custom .entry-footer a:focus,
.colors-custom .entry-footer a:hover,
.colors-custom .entry-footer .cat-links a:focus,
.colors-custom .entry-footer .cat-links a:hover,
.colors-custom .entry-footer .tags-links a:focus,
.colors-custom .entry-footer .tags-links a:hover,
.colors-custom .post-navigation a:focus,
.colors-custom .post-navigation a:hover,
.colors-custom .pagination a:not(.prev):not(.next):focus,
.colors-custom .pagination a:not(.prev):not(.next):hover,
.colors-custom .comments-pagination a:not(.prev):not(.next):focus,
.colors-custom .comments-pagination a:not(.prev):not(.next):hover,
.colors-custom .logged-in-as a:focus,
.colors-custom .logged-in-as a:hover,
.colors-custom a:focus .nav-title,
.colors-custom a:hover .nav-title,
.colors-custom .edit-link a:focus,
.colors-custom .edit-link a:hover,
.colors-custom .site-info a:focus,
.colors-custom .site-info a:hover,
.colors-custom .widget .widget-title a:focus,
.colors-custom .widget .widget-title a:hover,
.colors-custom .widget ul li a:focus,
.colors-custom .widget ul li a:hover {
color: hsl( ' . $hue . ', ' . $saturation . ', 0% ); /* base: #000; */
}
.colors-custom .entry-content a,
.colors-custom .entry-summary a,
.colors-custom .comment-content a,
.colors-custom .widget a,
.colors-custom .site-footer .widget-area a,
.colors-custom .posts-navigation a,
.colors-custom .widget_authors a strong {
-webkit-box-shadow: inset 0 -1px 0 hsl( ' . $hue . ', ' . $saturation . ', 6% ); /* base: rgba(15, 15, 15, 1); */
box-shadow: inset 0 -1px 0 hsl( ' . $hue . ', ' . $saturation . ', 6% ); /* base: rgba(15, 15, 15, 1); */
}
.colors-custom button,
.colors-custom input[type="button"],
.colors-custom input[type="submit"],
.colors-custom .entry-footer .edit-link a.post-edit-link {
background-color: hsl( ' . $hue . ', ' . $saturation . ', 13% ); /* base: #222; */
}
.colors-custom input[type="text"]:focus,
.colors-custom input[type="email"]:focus,
.colors-custom input[type="url"]:focus,
.colors-custom input[type="password"]:focus,
.colors-custom input[type="search"]:focus,
.colors-custom input[type="number"]:focus,
.colors-custom input[type="tel"]:focus,
.colors-custom input[type="range"]:focus,
.colors-custom input[type="date"]:focus,
.colors-custom input[type="month"]:focus,
.colors-custom input[type="week"]:focus,
.colors-custom input[type="time"]:focus,
.colors-custom input[type="datetime"]:focus,
.colors-custom .colors-custom input[type="datetime-local"]:focus,
.colors-custom input[type="color"]:focus,
.colors-custom textarea:focus,
.colors-custom button.secondary,
.colors-custom input[type="reset"],
.colors-custom input[type="button"].secondary,
.colors-custom input[type="reset"].secondary,
.colors-custom input[type="submit"].secondary,
.colors-custom a,
.colors-custom .site-title,
.colors-custom .site-title a,
.colors-custom .navigation-top a,
.colors-custom .dropdown-toggle,
.colors-custom .menu-toggle,
.colors-custom .page .panel-content .entry-title,
.colors-custom .page-title,
.colors-custom.page:not(.twentyseventeen-front-page) .entry-title,
.colors-custom .page-links a .page-number,
.colors-custom .comment-metadata a.comment-edit-link,
.colors-custom .comment-reply-link .icon,
.colors-custom h2.widget-title,
.colors-custom mark,
.colors-custom .post-navigation a:focus .icon,
.colors-custom .post-navigation a:hover .icon,
.colors-custom .site-content .site-content-light,
.colors-custom .twentyseventeen-panel .recent-posts .entry-header .edit-link {
color: hsl( ' . $hue . ', ' . $saturation . ', 13% ); /* base: #222; */
}
.colors-custom .entry-content a:focus,
.colors-custom .entry-content a:hover,
.colors-custom .entry-summary a:focus,
.colors-custom .entry-summary a:hover,
.colors-custom .comment-content a:focus,
.colors-custom .comment-content a:hover,
.colors-custom .widget a:focus,
.colors-custom .widget a:hover,
.colors-custom .site-footer .widget-area a:focus,
.colors-custom .site-footer .widget-area a:hover,
.colors-custom .posts-navigation a:focus,
.colors-custom .posts-navigation a:hover,
.colors-custom .comment-metadata a:focus,
.colors-custom .comment-metadata a:hover,
.colors-custom .comment-metadata a.comment-edit-link:focus,
.colors-custom .comment-metadata a.comment-edit-link:hover,
.colors-custom .comment-reply-link:focus,
.colors-custom .comment-reply-link:hover,
.colors-custom .widget_authors a:focus strong,
.colors-custom .widget_authors a:hover strong,
.colors-custom .entry-title a:focus,
.colors-custom .entry-title a:hover,
.colors-custom .entry-meta a:focus,
.colors-custom .entry-meta a:hover,
.colors-custom.blog .entry-meta a.post-edit-link:focus,
.colors-custom.blog .entry-meta a.post-edit-link:hover,
.colors-custom.archive .entry-meta a.post-edit-link:focus,
.colors-custom.archive .entry-meta a.post-edit-link:hover,
.colors-custom.search .entry-meta a.post-edit-link:focus,
.colors-custom.search .entry-meta a.post-edit-link:hover,
.colors-custom .page-links a:focus .page-number,
.colors-custom .page-links a:hover .page-number,
.colors-custom .entry-footer .cat-links a:focus,
.colors-custom .entry-footer .cat-links a:hover,
.colors-custom .entry-footer .tags-links a:focus,
.colors-custom .entry-footer .tags-links a:hover,
.colors-custom .post-navigation a:focus,
.colors-custom .post-navigation a:hover,
.colors-custom .pagination a:not(.prev):not(.next):focus,
.colors-custom .pagination a:not(.prev):not(.next):hover,
.colors-custom .comments-pagination a:not(.prev):not(.next):focus,
.colors-custom .comments-pagination a:not(.prev):not(.next):hover,
.colors-custom .logged-in-as a:focus,
.colors-custom .logged-in-as a:hover,
.colors-custom a:focus .nav-title,
.colors-custom a:hover .nav-title,
.colors-custom .edit-link a:focus,
.colors-custom .edit-link a:hover,
.colors-custom .site-info a:focus,
.colors-custom .site-info a:hover,
.colors-custom .widget .widget-title a:focus,
.colors-custom .widget .widget-title a:hover,
.colors-custom .widget ul li a:focus,
.colors-custom .widget ul li a:hover {
-webkit-box-shadow: inset 0 0 0 hsl( ' . $hue . ', ' . $saturation . ', 13% ), 0 3px 0 hsl( ' . $hue . ', ' . $saturation . ', 13% );
box-shadow: inset 0 0 0 hsl( ' . $hue . ', ' . $saturation . ' , 13% ), 0 3px 0 hsl( ' . $hue . ', ' . $saturation . ', 13% );
}
body.colors-custom,
.colors-custom button,
.colors-custom input,
.colors-custom select,
.colors-custom textarea,
.colors-custom h3,
.colors-custom h4,
.colors-custom h6,
.colors-custom label,
.colors-custom .entry-title a,
.colors-custom.twentyseventeen-front-page .panel-content .recent-posts article,
.colors-custom .entry-footer .cat-links a,
.colors-custom .entry-footer .tags-links a,
.colors-custom .format-quote blockquote,
.colors-custom .nav-title,
.colors-custom .comment-body,
.colors-custom .site-content .wp-playlist-light .wp-playlist-current-item .wp-playlist-item-album {
color: hsl( ' . $hue . ', ' . $reduced_saturation . ', 20% ); /* base: #333; */
}
.colors-custom .social-navigation a:hover,
.colors-custom .social-navigation a:focus {
background: hsl( ' . $hue . ', ' . $reduced_saturation . ', 20% ); /* base: #333; */
}
.colors-custom input[type="text"]:focus,
.colors-custom input[type="email"]:focus,
.colors-custom input[type="url"]:focus,
.colors-custom input[type="password"]:focus,
.colors-custom input[type="search"]:focus,
.colors-custom input[type="number"]:focus,
.colors-custom input[type="tel"]:focus,
.colors-custom input[type="range"]:focus,
.colors-custom input[type="date"]:focus,
.colors-custom input[type="month"]:focus,
.colors-custom input[type="week"]:focus,
.colors-custom input[type="time"]:focus,
.colors-custom input[type="datetime"]:focus,
.colors-custom input[type="datetime-local"]:focus,
.colors-custom input[type="color"]:focus,
.colors-custom textarea:focus,
.bypostauthor > .comment-body > .comment-meta > .comment-author .avatar {
border-color: hsl( ' . $hue . ', ' . $reduced_saturation . ', 20% ); /* base: #333; */
}
.colors-custom h2,
.colors-custom blockquote,
.colors-custom input[type="text"],
.colors-custom input[type="email"],
.colors-custom input[type="url"],
.colors-custom input[type="password"],
.colors-custom input[type="search"],
.colors-custom input[type="number"],
.colors-custom input[type="tel"],
.colors-custom input[type="range"],
.colors-custom input[type="date"],
.colors-custom input[type="month"],
.colors-custom input[type="week"],
.colors-custom input[type="time"],
.colors-custom input[type="datetime"],
.colors-custom input[type="datetime-local"],
.colors-custom input[type="color"],
.colors-custom textarea,
.colors-custom .site-description,
.colors-custom .entry-content blockquote.alignleft,
.colors-custom .entry-content blockquote.alignright,
.colors-custom .colors-custom .taxonomy-description,
.colors-custom .site-info a,
.colors-custom .wp-caption,
.colors-custom .gallery-caption {
color: hsl( ' . $hue . ', ' . $saturation . ', 40% ); /* base: #666; */
}
.colors-custom abbr,
.colors-custom acronym {
border-bottom-color: hsl( ' . $hue . ', ' . $saturation . ', 40% ); /* base: #666; */
}
.colors-custom h5,
.colors-custom .entry-meta,
.colors-custom .entry-meta a,
.colors-custom.blog .entry-meta a.post-edit-link,
.colors-custom.archive .entry-meta a.post-edit-link,
.colors-custom.search .entry-meta a.post-edit-link,
.colors-custom .nav-subtitle,
.colors-custom .comment-metadata,
.colors-custom .comment-metadata a,
.colors-custom .no-comments,
.colors-custom .comment-awaiting-moderation,
.colors-custom .page-numbers.current,
.colors-custom .page-links .page-number,
.colors-custom .navigation-top .current-menu-item > a,
.colors-custom .navigation-top .current_page_item > a,
.colors-custom .main-navigation a:hover,
.colors-custom .site-content .wp-playlist-light .wp-playlist-current-item .wp-playlist-item-artist {
color: hsl( ' . $hue . ', ' . $saturation . ', 46% ); /* base: #767676; */
}
.colors-custom :not( .mejs-button ) > button:hover,
.colors-custom :not( .mejs-button ) > button:focus,
.colors-custom input[type="button"]:hover,
.colors-custom input[type="button"]:focus,
.colors-custom input[type="submit"]:hover,
.colors-custom input[type="submit"]:focus,
.colors-custom .entry-footer .edit-link a.post-edit-link:hover,
.colors-custom .entry-footer .edit-link a.post-edit-link:focus,
.colors-custom .social-navigation a,
.colors-custom .prev.page-numbers:focus,
.colors-custom .prev.page-numbers:hover,
.colors-custom .next.page-numbers:focus,
.colors-custom .next.page-numbers:hover,
.colors-custom .site-content .wp-playlist-light .wp-playlist-item:hover,
.colors-custom .site-content .wp-playlist-light .wp-playlist-item:focus {
background: hsl( ' . esc_attr( $hue ) . ', ' . esc_attr( $saturation ) . ', 46% ); /* base: #767676; */
}
.colors-custom button.secondary:hover,
.colors-custom button.secondary:focus,
.colors-custom input[type="reset"]:hover,
.colors-custom input[type="reset"]:focus,
.colors-custom input[type="button"].secondary:hover,
.colors-custom input[type="button"].secondary:focus,
.colors-custom input[type="reset"].secondary:hover,
.colors-custom input[type="reset"].secondary:focus,
.colors-custom input[type="submit"].secondary:hover,
.colors-custom input[type="submit"].secondary:focus,
.colors-custom hr {
background: hsl( ' . $hue . ', ' . $saturation . ', 73% ); /* base: #bbb; */
}
.colors-custom input[type="text"],
.colors-custom input[type="email"],
.colors-custom input[type="url"],
.colors-custom input[type="password"],
.colors-custom input[type="search"],
.colors-custom input[type="number"],
.colors-custom input[type="tel"],
.colors-custom input[type="range"],
.colors-custom input[type="date"],
.colors-custom input[type="month"],
.colors-custom input[type="week"],
.colors-custom input[type="time"],
.colors-custom input[type="datetime"],
.colors-custom input[type="datetime-local"],
.colors-custom input[type="color"],
.colors-custom textarea,
.colors-custom select,
.colors-custom fieldset,
.colors-custom .widget .tagcloud a:hover,
.colors-custom .widget .tagcloud a:focus,
.colors-custom .widget.widget_tag_cloud a:hover,
.colors-custom .widget.widget_tag_cloud a:focus,
.colors-custom .wp_widget_tag_cloud a:hover,
.colors-custom .wp_widget_tag_cloud a:focus {
border-color: hsl( ' . $hue . ', ' . $saturation . ', 73% ); /* base: #bbb; */
}
.colors-custom thead th {
border-bottom-color: hsl( ' . $hue . ', ' . $saturation . ', 73% ); /* base: #bbb; */
}
.colors-custom .entry-footer .cat-links .icon,
.colors-custom .entry-footer .tags-links .icon {
color: hsl( ' . $hue . ', ' . $saturation . ', 73% ); /* base: #bbb; */
}
.colors-custom button.secondary,
.colors-custom input[type="reset"],
.colors-custom input[type="button"].secondary,
.colors-custom input[type="reset"].secondary,
.colors-custom input[type="submit"].secondary,
.colors-custom .prev.page-numbers,
.colors-custom .next.page-numbers {
background-color: hsl( ' . $hue . ', ' . $saturation . ', 87% ); /* base: #ddd; */
}
.colors-custom .widget .tagcloud a,
.colors-custom .widget.widget_tag_cloud a,
.colors-custom .wp_widget_tag_cloud a {
border-color: hsl( ' . $hue . ', ' . $saturation . ', 87% ); /* base: #ddd; */
}
.colors-custom.twentyseventeen-front-page article:not(.has-post-thumbnail):not(:first-child),
.colors-custom .widget ul li {
border-top-color: hsl( ' . $hue . ', ' . $saturation . ', 87% ); /* base: #ddd; */
}
.colors-custom .widget ul li {
border-bottom-color: hsl( ' . $hue . ', ' . $saturation . ', 87% ); /* base: #ddd; */
}
.colors-custom pre,
.colors-custom mark,
.colors-custom ins {
background: hsl( ' . $hue . ', ' . $saturation . ', 93% ); /* base: #eee; */
}
.colors-custom .navigation-top,
.colors-custom .main-navigation > div > ul,
.colors-custom .pagination,
.colors-custom .comments-pagination,
.colors-custom .entry-footer,
.colors-custom .site-footer {
border-top-color: hsl( ' . $hue . ', ' . $saturation . ', 93% ); /* base: #eee; */
}
.colors-custom .navigation-top,
.colors-custom .main-navigation li,
.colors-custom .entry-footer,
.colors-custom .single-featured-image-header,
.colors-custom .site-content .wp-playlist-light .wp-playlist-item,
.colors-custom tr {
border-bottom-color: hsl( ' . $hue . ', ' . $saturation . ', 93% ); /* base: #eee; */
}
.colors-custom .site-content .wp-playlist-light {
border-color: hsl( ' . $hue . ', ' . $saturation . ', 93% ); /* base: #eee; */
}
.colors-custom .site-header,
.colors-custom .single-featured-image-header {
background-color: hsl( ' . $hue . ', ' . $saturation . ', 98% ); /* base: #fafafa; */
}
.colors-custom button,
.colors-custom input[type="button"],
.colors-custom input[type="submit"],
.colors-custom .entry-footer .edit-link a.post-edit-link,
.colors-custom .social-navigation a,
.colors-custom .site-content .wp-playlist-light a.wp-playlist-caption:hover,
.colors-custom .site-content .wp-playlist-light .wp-playlist-item:hover a,
.colors-custom .site-content .wp-playlist-light .wp-playlist-item:focus a,
.colors-custom .site-content .wp-playlist-light .wp-playlist-item:hover,
.colors-custom .site-content .wp-playlist-light .wp-playlist-item:focus,
.colors-custom .prev.page-numbers:focus,
.colors-custom .prev.page-numbers:hover,
.colors-custom .next.page-numbers:focus,
.colors-custom .next.page-numbers:hover,
.colors-custom.has-header-image .site-title,
.colors-custom.has-header-video .site-title,
.colors-custom.has-header-image .site-title a,
.colors-custom.has-header-video .site-title a,
.colors-custom.has-header-image .site-description,
.colors-custom.has-header-video .site-description {
color: hsl( ' . $hue . ', ' . $saturation . ', 100% ); /* base: #fff; */
}
body.colors-custom,
.colors-custom .navigation-top,
.colors-custom .main-navigation ul {
background: hsl( ' . $hue . ', ' . $saturation . ', 100% ); /* base: #fff; */
}
.colors-custom .widget ul li a,
.colors-custom .site-footer .widget-area ul li a {
-webkit-box-shadow: inset 0 -1px 0 hsl( ' . $hue . ', ' . $saturation . ', 100% ); /* base: rgba(255, 255, 255, 1); */
box-shadow: inset 0 -1px 0 hsl( ' . $hue . ', ' . $saturation . ', 100% ); /* base: rgba(255, 255, 255, 1); */
}
.colors-custom .menu-toggle,
.colors-custom .menu-toggle:hover,
.colors-custom .menu-toggle:focus,
.colors-custom .menu .dropdown-toggle,
.colors-custom .menu-scroll-down,
.colors-custom .menu-scroll-down:hover,
.colors-custom .menu-scroll-down:focus {
background-color: transparent;
}
.colors-custom .widget .tagcloud a,
.colors-custom .widget .tagcloud a:focus,
.colors-custom .widget .tagcloud a:hover,
.colors-custom .widget.widget_tag_cloud a,
.colors-custom .widget.widget_tag_cloud a:focus,
.colors-custom .widget.widget_tag_cloud a:hover,
.colors-custom .wp_widget_tag_cloud a,
.colors-custom .wp_widget_tag_cloud a:focus,
.colors-custom .wp_widget_tag_cloud a:hover,
.colors-custom .entry-footer .edit-link a.post-edit-link:focus,
.colors-custom .entry-footer .edit-link a.post-edit-link:hover {
-webkit-box-shadow: none !important;
box-shadow: none !important;
}
/* Reset non-customizable hover styling for links */
.colors-custom .entry-content a:hover,
.colors-custom .entry-content a:focus,
.colors-custom .entry-summary a:hover,
.colors-custom .entry-summary a:focus,
.colors-custom .comment-content a:focus,
.colors-custom .comment-content a:hover,
.colors-custom .widget a:hover,
.colors-custom .widget a:focus,
.colors-custom .site-footer .widget-area a:hover,
.colors-custom .site-footer .widget-area a:focus,
.colors-custom .posts-navigation a:hover,
.colors-custom .posts-navigation a:focus,
.colors-custom .widget_authors a:hover strong,
.colors-custom .widget_authors a:focus strong {
-webkit-box-shadow: inset 0 0 0 rgba(0, 0, 0, 0), 0 3px 0 rgba(0, 0, 0, 1);
box-shadow: inset 0 0 0 rgba(0, 0, 0, 0), 0 3px 0 rgba(0, 0, 0, 1);
}
.colors-custom .gallery-item a,
.colors-custom .gallery-item a:hover,
.colors-custom .gallery-item a:focus {
-webkit-box-shadow: none;
box-shadow: none;
}
@media screen and (min-width: 48em) {
.colors-custom .nav-links .nav-previous .nav-title .icon,
.colors-custom .nav-links .nav-next .nav-title .icon {
color: hsl( ' . $hue . ', ' . $saturation . ', 20% ); /* base: #222; */
}
.colors-custom .main-navigation li li:hover,
.colors-custom .main-navigation li li.focus {
background: hsl( ' . $hue . ', ' . $saturation . ', 46% ); /* base: #767676; */
}
.colors-custom .navigation-top .menu-scroll-down {
color: hsl( ' . $hue . ', ' . $saturation . ', 46% ); /* base: #767676; */;
}
.colors-custom abbr[title] {
border-bottom-color: hsl( ' . $hue . ', ' . $saturation . ', 46% ); /* base: #767676; */;
}
.colors-custom .main-navigation ul ul {
border-color: hsl( ' . $hue . ', ' . $saturation . ', 73% ); /* base: #bbb; */
background: hsl( ' . $hue . ', ' . $saturation . ', 100% ); /* base: #fff; */
}
.colors-custom .main-navigation ul li.menu-item-has-children:before,
.colors-custom .main-navigation ul li.page_item_has_children:before {
border-bottom-color: hsl( ' . $hue . ', ' . $saturation . ', 73% ); /* base: #bbb; */
}
.colors-custom .main-navigation ul li.menu-item-has-children:after,
.colors-custom .main-navigation ul li.page_item_has_children:after {
border-bottom-color: hsl( ' . $hue . ', ' . $saturation . ', 100% ); /* base: #fff; */
}
.colors-custom .main-navigation li li.focus > a,
.colors-custom .main-navigation li li:focus > a,
.colors-custom .main-navigation li li:hover > a,
.colors-custom .main-navigation li li a:hover,
.colors-custom .main-navigation li li a:focus,
.colors-custom .main-navigation li li.current_page_item a:hover,
.colors-custom .main-navigation li li.current-menu-item a:hover,
.colors-custom .main-navigation li li.current_page_item a:focus,
.colors-custom .main-navigation li li.current-menu-item a:focus {
color: hsl( ' . $hue . ', ' . $saturation . ', 100% ); /* base: #fff; */
}
}
`

View File

@ -0,0 +1,48 @@
package twentyseventeen
import (
"fmt"
"github.com/fthvgb1/wp-go/app/theme/wp"
)
func customHeader(h *wp.Handle) (r string) {
themeMods := h.CommonThemeMods()
headerTextColor := themeMods.HeaderTextcolor
if headerTextColor == "" || headerTextColor == themeMods.ThemeSupport.CustomHeader.DefaultTextColor {
return
}
css := `
.site-title,
.site-description {
position: absolute;
clip: rect(1px, 1px, 1px, 1px);
}`
if headerTextColor != "blank" {
css = fmt.Sprintf(customHeaderCss, headerTextColor)
}
r = fmt.Sprintf(`<style id="twentyseventeen-custom-header-styles" type="text/css">%s</style>`, css)
return
}
var customHeaderCss = `
.site-title a,
.colors-dark .site-title a,
.colors-custom .site-title a,
body.has-header-image .site-title a,
body.has-header-video .site-title a,
body.has-header-image.colors-dark .site-title a,
body.has-header-video.colors-dark .site-title a,
body.has-header-image.colors-custom .site-title a,
body.has-header-video.colors-custom .site-title a,
.site-description,
.colors-dark .site-description,
.colors-custom .site-description,
body.has-header-image .site-description,
body.has-header-video .site-description,
body.has-header-image.colors-dark .site-description,
body.has-header-video.colors-dark .site-description,
body.has-header-image.colors-custom .site-description,
body.has-header-video.colors-custom .site-description {
color: #%s;
}
`

View File

@ -1,19 +1,17 @@
{{ define "layout/base"}} {{ define "layout/base"}}
<!DOCTYPE html> <!DOCTYPE html>
<html lang="{{getLang}}" class="no-js no-svg"> <html lang="{{getLang}}" class="no-js no-svg">
<head>
{{template "layout/head" .}} {{template "layout/head" .}}
{{block "head" .}}
{{end}} <body class="{{.calBodyClass|exec}}">
</head>
<body class="{{.bodyClass}} wp-embed-responsive hfeed has-header-image has-sidebar colors-light">
{{template "svg"}} {{template "svg"}}
<div id="page" class="site"> <div id="page" class="site">
<a class="skip-link screen-reader-text" href="#content">跳至内容</a> <a class="skip-link screen-reader-text" href="#content">跳至内容</a>
<header id="masthead" class="site-header"> <header id="masthead" class="site-header">
<div class="custom-header" style="margin-bottom: 0px;"> <div class="custom-header" style="margin-bottom: 0;">
<div class="custom-header-media"> <div class="custom-header-media">
<div id="wp-custom-header" class="wp-custom-header"> <div id="wp-custom-header" class="wp-custom-header">
<img src="{{.HeaderImage.Path}}" width="{{.HeaderImage.Width}}" height="{{.HeaderImage.Height}}" alt="" {{if .HeaderImage.Srcset}}srcset="{{.HeaderImage.Srcset}}" {{end}} {{if .HeaderImage.Sizes}}sizes="{{.HeaderImage.Sizes}}" {{end}}> <img src="{{.HeaderImage.Path}}" width="{{.HeaderImage.Width}}" height="{{.HeaderImage.Height}}" alt="" {{if .HeaderImage.Srcset}}srcset="{{.HeaderImage.Srcset}}" {{end}} {{if .HeaderImage.Sizes}}sizes="{{.HeaderImage.Sizes}}" {{end}}>
@ -22,41 +20,32 @@
<div class="site-branding" style="margin-bottom: 0px;"> <div class="site-branding" style="margin-bottom: 0px;">
<div class="wrap"> <div class="wrap">
{{.customLogo|exec}}
<div class="site-branding-text"> <div class="site-branding-text">
<h1 class="site-title"> <h1 class="site-title">
<a href="/" rel="home">{{ "blogname"| getOption }}</a> <a href="/" rel="home">{{ "blogname"| getOption }}</a>
</h1> </h1>
<p class="site-description">{{"blogdescription"| getOption}}</p> <p class="site-description">{{"blogdescription"| getOption}}</p>
</div><!-- .site-branding-text --> </div><!-- .site-branding-text -->
{{if eq .scene "Home"}}
<a href="#content" class="menu-scroll-down"> <a href="#content" class="menu-scroll-down">
<svg class="icon icon-arrow-right" aria-hidden="true" role="img"> <svg class="icon icon-arrow-right" aria-hidden="true" role="img">
<use href="#icon-arrow-right" xlink:href="#icon-arrow-right"></use> <use href="#icon-arrow-right" xlink:href="#icon-arrow-right"></use>
</svg> </svg>
<span class="screen-reader-text">向下滚动到内容</span> <span class="screen-reader-text">向下滚动到内容</span>
</a> </a>
{{end}}
</div><!-- .wrap --> </div><!-- .wrap -->
</div><!-- .site-branding --> </div><!-- .site-branding -->
</div><!-- .custom-header --> </div><!-- .custom-header -->
</header> </header>
{{block "content" .}} {{block "content" .}}
{{end}} {{end}}
<footer id="colophon" class="site-footer">
<div class="wrap">
<div class="site-info">
<a href="https://cn.wordpress.org/" class="imprint">自豪地采用WordPress</a>
</div>
</div>
</footer>
</div> </div>
{{template "layout/footer" .}} {{template "layout/footer" .}}
</body> </body>

View File

@ -0,0 +1,48 @@
{{define "layout/empty"}}
<div class="wrap">
<div id="primary" class="content-area">
<main id="main" class="site-main">
<section class="error-404 not-found">
<header class="page-header">
<h1 class="page-title">
{{if .search}}
未找到
{{else}}
有点尴尬诶!该页无法显示。
{{end}}
</h1>
</header><!-- .page-header -->
<div class="page-content">
<p>{{if .search}}
抱歉,没有符合您搜索条件的结果。请换其它关键词再试。
{{else}}
这儿似乎什么都没有,试试搜索?
{{end}}
</p>
<form role="search" method="get" class="search-form" action="/">
<label for="search-form-1">
<span class="screen-reader-text">搜索:</span>
</label>
<input type="search" id="search-form-1" class="search-field" placeholder="搜索…" value="{{.search}}" name="s">
<button type="submit" class="search-submit">
<svg class="icon icon-search" aria-hidden="true" role="img"> <use href="#icon-search" xlink:href="#icon-search"></use> </svg>
<span class="screen-reader-text">搜索</span>
</button>
</form>
</div><!-- .page-content -->
</section><!-- .no-results -->
</main><!-- .site-main -->
</div>
{{if .search }}
<aside id="secondary" class="widget-area" aria-label="博客边栏">
{{template "layout/sidebar" .}}
</aside>
{{end}}
</div>
{{end}}

View File

@ -0,0 +1,5 @@
{{define "layout/footer"}}
{{template "common/footer" .}}
{{ block "footer" .}}
{{end}}
{{end}}

View File

@ -161,7 +161,4 @@
</symbol> </symbol>
</defs> </defs>
</svg> </svg>
{{block "footerx" .}}
{{end}}
{{end}} {{end}}

View File

@ -1,11 +1,9 @@
{{define "layout/head"}} {{define "layout/head"}}
<head>
<meta charset="{{"blog_charset"| getOption}}"> <meta charset="{{"blog_charset"| getOption}}">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="profile" href="https://gmpg.org/xfn/11"> <link rel="profile" href="https://gmpg.org/xfn/11">
<link rel="pingback" href="/xmlrpc.php"> <link rel="pingback" href="/xmlrpc.php">
<!--[if lt IE 9]>
<script src="/wp-content/themes/twentyfifteen/js/html5.js?ver=3.7.0"></script>
<![endif]-->
<script>(function(html){html.className = html.className.replace(/\bno-js\b/,'js')})(document.documentElement);</script> <script>(function(html){html.className = html.className.replace(/\bno-js\b/,'js')})(document.documentElement);</script>
<title>{{ .title }}</title> <title>{{ .title }}</title>
<meta name='robots' content='max-image-preview:large' /> <meta name='robots' content='max-image-preview:large' />
@ -47,21 +45,8 @@
.wp-block-pullquote{font-size: 1.5em;line-height: 1.6;} .wp-block-pullquote{font-size: 1.5em;line-height: 1.6;}
</style> </style>
<link rel='stylesheet' id='twentyseventeen-style-css' href='/wp-content/themes/twentyseventeen/style.css?ver=20221101' media='all' /> {{template "common/head" .}}
<link rel='stylesheet' id='twentyseventeen-block-style-css' href='/wp-content/themes/twentyseventeen/assets/css/blocks.css?ver=20220912' media='all' /> {{block "head" .}}
<!--[if lt IE 9]> {{end}}
<link rel='stylesheet' id='twentyseventeen-ie8-css' href='/wp-content/themes/twentyseventeen/assets/css/ie8.css?ver=20161202' media='all' /> </head>
<![endif]-->
<link rel='stylesheet' id='enlighterjs-css' href='/wp-content/plugins/enlighter/cache/enlighterjs.min.css?ver=0A0B0C' media='all' />
<!--[if lt IE 9]>
<script src='/wp-content/themes/twentyseventeen/assets/js/html5.js?ver=20161020' id='html5-js'></script>
<![endif]-->
<script src='/wp-includes/js/jquery/jquery.min.js?ver=3.6.0' id='jquery-core-js'></script>
<script src='/wp-includes/js/jquery/jquery-migrate.min.js?ver=3.3.2' id='jquery-migrate-js'></script>
<link rel="https://api.w.org/" href="/wp-json/" /><link rel="EditURI" type="application/rsd+xml" title="RSD" href="/xmlrpc.php?rsd" />
<link rel="wlwmanifest" type="application/wlwmanifest+xml" href="/wp-includes/wlwmanifest.xml" />
<meta name="generator" content="WordPress 6.1.1" />
<style>.recentcomments a{display:inline !important;padding:0 !important;margin:0 !important;}</style>
{{end}} {{end}}

View File

@ -0,0 +1,3 @@
{{define "layout/sidebar" }}
{{template "common/sidebarWidget" .}}
{{end}}

View File

@ -1,7 +1,7 @@
{{template "layout/base" .}} {{template "layout/base" .}}
{{define "content"}} {{define "content"}}
{{ if .post.PostContent}} {{ if and (.post) (gt .post.Id 0) }}
{{if .post.Thumbnail.Path}} {{if .post.Thumbnail.Path}}
<div class="single-featured-image-header"> <div class="single-featured-image-header">
<img width="{{.post.Thumbnail.OriginAttachmentData.Width}}" height="{{.post.Thumbnail.OriginAttachmentData.Height}}" src="{{.post.Thumbnail.Path}}" class="attachment-twentyseventeen-featured-image size-twentyseventeen-featured-image wp-post-image" alt="{{.post.PostTitle}}" decoding="async" loading="lazy" srcset="{{.post.Thumbnail.Srcset}}" sizes="{{.post.Thumbnail.Sizes}}"> <img width="{{.post.Thumbnail.OriginAttachmentData.Width}}" height="{{.post.Thumbnail.OriginAttachmentData.Height}}" src="{{.post.Thumbnail.Path}}" class="attachment-twentyseventeen-featured-image size-twentyseventeen-featured-image wp-post-image" alt="{{.post.PostTitle}}" decoding="async" loading="lazy" srcset="{{.post.Thumbnail.Srcset}}" sizes="{{.post.Thumbnail.Sizes}}">
@ -12,7 +12,7 @@
<div class="wrap"> <div class="wrap">
<div id="primary" class="content-area"> <div id="primary" class="content-area">
<main id="main" class="site-main"> <main id="main" class="site-main">
<article id="post-{{.post.Id}}" class="post-{{.post.Id}} post type-post status-publish format-standard hentry {{if .post.Thumbnail.Path}}has-post-thumbnail{{end}} category-uncategorized"> <article id="post-{{.post.Id}}" class="{{ .post|postsFn .calPostClass}}">
<header class="entry-header"> <header class="entry-header">
<div class="entry-meta"> <div class="entry-meta">
@ -64,12 +64,21 @@
{{ if .showComment}} {{ if .showComment}}
<div id="comments" class="comments-area"> <div id="comments" class="comments-area">
{{ if gt .post.CommentCount 0}} {{ if ne .comments ""}}
<h2 class="comments-title">“{{.post.PostTitle}}”的{{.post.CommentCount}}个回复 </h2> <h2 class="comments-title">“{{.post.PostTitle}}”的{{.totalCommentNum}}个回复 </h2>
<ol class="comment-list"> <ol class="comment-list">
{{.comments|unescaped}} {{.comments|unescaped}}
</ol> </ol>
{{if gt .totalCommentPage 1}}
<nav class="navigation comments-pagination" aria-label="评论">
<h2 class="screen-reader-text">评论导航</h2>
<div class="nav-links">
{{ .commentPageNav|unescaped}}
</div><!-- .nav-links -->
</nav>
{{end}}
{{end}} {{end}}
{{if eq .post.CommentStatus "open"}} {{if eq .post.CommentStatus "open"}}
{{template "respond" .}} {{template "respond" .}}
@ -122,6 +131,9 @@
</aside> </aside>
</div> </div>
</div> </div>
{{template "common/colophon" .}}
</div> </div>
{{else}} {{else}}
@ -129,8 +141,3 @@
{{end }} {{end }}
{{end}} {{end}}
{{ define "footerx"}}
<script src='/wp-includes/js/comment-reply.min.js?ver=6.0.2' id='comment-reply-js'></script>
{{end}}

View File

@ -0,0 +1,16 @@
{{template "layout/base" .}}
{{define "content"}}
<div class="site-content-contain">
<div id="content" class="site-content">
<div class="wrap">
<div id="primary" class="content-area">
<main id="main" class="site-main">
{{template "layout/empty"}}
</main>
</div>
</div>
</div>
</div>
{{end}}

View File

@ -7,26 +7,22 @@
<div class="wrap"> <div class="wrap">
<header class="page-header"> <header class="page-header">
{{if .header}}
<h1 class="page-title">{{.header | unescaped}}</h1>
{{else}}
<h2 class="page-title">文章</h2> <h2 class="page-title">文章</h2>
{{end}}
</header> </header>
<div id="primary" class="content-area"> <div id="primary" class="content-area">
<main id="main" class="site-main"> <main id="main" class="site-main">
{{if .header}}
<header class="page-header">
<h1 class="page-title">
{{if .search}}
{{.header}}
{{else}}
{{.header |unescaped}}
{{end}}
</h1>
</header>
{{end}}
{{ range $k,$v:=.posts}} {{ range $k,$v:=.posts}}
<article id="post-{{$v.Id}}" <article id="post-{{$v.Id}}" class="{{ $v|postsFn $.calPostClass}}">
class="post-{{$v.Id}} post {{if $v.Thumbnail.Path}}has-post-thumbnail{{end}} type-post status-publish format-standard hentry category"> {{if $v.IsSticky}}
<svg class="icon icon-thumb-tack" aria-hidden="true" role="img">
<use href="#icon-thumb-tack" xlink:href="#icon-thumb-tack"></use>
</svg>
{{end}}
<header class="entry-header"> <header class="entry-header">
<div class="entry-meta"> <div class="entry-meta">
<span class="screen-reader-text">发布于 </span> <span class="screen-reader-text">发布于 </span>
@ -80,7 +76,7 @@
{{template "layout/empty" .}} {{template "layout/empty" .}}
{{end}} {{end}}
{{end}}
</div> </div>
{{template "common/colophon" .}}
</div> </div>
{{end}}

View File

@ -0,0 +1,43 @@
package twentyseventeen
import (
"fmt"
"github.com/fthvgb1/wp-go/app/pkg/constraints"
"github.com/fthvgb1/wp-go/app/theme/wp"
"github.com/fthvgb1/wp-go/app/wpconfig"
)
func pushScripts(h *wp.Handle) {
h.PushCacheGroupHeadScript(constraints.AllScene, "{theme}.head", 30, func(h *wp.Handle) string {
head := headScript
if "dark" == wpconfig.GetThemeModsVal(ThemeName, "colorscheme", "light") {
head = fmt.Sprintf("%s\n%s", headScript, ` <link rel="stylesheet" id="twentyseventeen-colors-dark-css" href="/wp-content/themes/twentyseventeen/assets/css/colors-dark.css?ver=20191025" media="all">`)
}
return head
})
h.PushGroupFooterScript(constraints.AllScene, "{theme}.footer", 20.005, footerScript)
}
var headScript = `<link rel='stylesheet' id='twentyseventeen-style-css' href='/wp-content/themes/twentyseventeen/style.css?ver=20221101' media='all' />
<link rel='stylesheet' id='twentyseventeen-block-style-css' href='/wp-content/themes/twentyseventeen/assets/css/blocks.css?ver=20220912' media='all' />
<!--[if lt IE 9]>
<link rel='stylesheet' id='twentyseventeen-ie8-css' href='/wp-content/themes/twentyseventeen/assets/css/ie8.css?ver=20161202' media='all' />
<![endif]-->
<!--[if lt IE 9]>
<script src='/wp-content/themes/twentyseventeen/assets/js/html5.js?ver=20161020' id='html5-js'></script>
<![endif]-->
<script src='/wp-includes/js/jquery/jquery.min.js?ver=3.6.0' id='jquery-core-js'></script>
<script src='/wp-includes/js/jquery/jquery-migrate.min.js?ver=3.3.2' id='jquery-migrate-js'></script>
<link rel="https://api.w.org/" href="/wp-json/" /><link rel="EditURI" type="application/rsd+xml" title="RSD" href="/xmlrpc.php?rsd" />
<link rel="wlwmanifest" type="application/wlwmanifest+xml" href="/wp-includes/wlwmanifest.xml" />
<meta name="generator" content="WordPress 6.1.1" />
<style>.recentcomments a{display:inline !important;padding:0 !important;margin:0 !important;}</style>`
var footerScript = `<script id="twentyseventeen-skip-link-focus-fix-js-extra">
var twentyseventeenScreenReaderText = {"quote":"<svg class=\"icon icon-quote-right\" aria-hidden=\"true\" role=\"img\"> <use href=\"#icon-quote-right\" xlink:href=\"#icon-quote-right\"><\/use> <\/svg>"};
</script>
<script src="/wp-content/themes/twentyseventeen/assets/js/skip-link-focus-fix.js?ver=20161114" id="twentyseventeen-skip-link-focus-fix-js"></script>
<script src="/wp-content/themes/twentyseventeen/assets/js/global.js?ver=20211130" id="twentyseventeen-global-js"></script>
<script src="/wp-content/themes/twentyseventeen/assets/js/jquery.scrollTo.js?ver=2.1.3" id="jquery-scrollto-js"></script>`

View File

@ -0,0 +1,212 @@
package twentyseventeen
import "github.com/fthvgb1/wp-go/app/wpconfig"
type themeSupport struct {
CustomLineHeight bool `json:"custom-line-height"`
StarterContent StarterContent `json:"starter-content"`
}
type Widgets struct {
Sidebar1 []string `json:"sidebar-1"`
Sidebar2 []string `json:"sidebar-2"`
Sidebar3 []string `json:"sidebar-3"`
}
type About struct {
Thumbnail string `json:"thumbnail"`
}
type Contact struct {
Thumbnail string `json:"thumbnail"`
}
type Blog struct {
Thumbnail string `json:"thumbnail"`
}
type HomepageSection struct {
Thumbnail string `json:"thumbnail"`
}
type ImageEspresso struct {
PostTitle string `json:"post_title"`
File string `json:"file"`
}
type ImageSandwich struct {
PostTitle string `json:"post_title"`
File string `json:"file"`
}
type ImageCoffee struct {
PostTitle string `json:"post_title"`
File string `json:"file"`
}
type Attachments struct {
ImageEspresso ImageEspresso `json:"image-espresso"`
ImageSandwich ImageSandwich `json:"image-sandwich"`
ImageCoffee ImageCoffee `json:"image-coffee"`
}
type Image struct {
PostTitle string `json:"post_title"`
File string `json:"file"`
}
type Options struct {
ShowOnFront string `json:"show_on_front"`
PageOnFront string `json:"page_on_front"`
PageForPosts string `json:"page_for_posts"`
}
type ThemeMods struct {
Panel1 string `json:"panel_1"`
Panel2 string `json:"panel_2"`
Panel3 string `json:"panel_3"`
Panel4 string `json:"panel_4"`
}
type Menus struct {
Name string `json:"name"`
Items []string `json:"items"`
}
type NavMenus struct {
Top Menus `json:"top"`
Social Menus `json:"social"`
}
type StarterContent struct {
Widgets Widgets `json:"widgets"`
Posts map[string]map[string]string `json:"posts"`
Attachments map[string]Image `json:"attachments"`
Options Options `json:"options"`
ThemeMods ThemeMods `json:"theme_mods"`
NavMenus NavMenus `json:"nav_menus"`
}
var themesupport = themeSupport{
CustomLineHeight: true,
StarterContent: StarterContent{
Widgets: Widgets{
Sidebar1: []string{"text_business_info", "search", "text_about"},
Sidebar2: []string{"text_business_info"},
Sidebar3: []string{"text_about", "search"},
},
Posts: map[string]map[string]string{
"0": {
"home": "home",
},
"about": {
"thumbnail": "{{image-sandwich}}",
},
"contact": {
"thumbnail": "{{image-espresso}}",
},
"blog": {
"thumbnail": "{{image-coffee}}",
},
"homepage-section": {
"thumbnail": "{{image-espresso}}",
},
},
Attachments: map[string]Image{
"image-espresso": {
PostTitle: "浓缩咖啡",
File: "assets/images/espresso.jpg",
},
"image-sandwich": {
PostTitle: "三明治",
File: "assets/images/sandwich.jpg",
},
"image-coffee": {
PostTitle: "咖啡",
File: "assets/images/coffee.jpg",
},
},
Options: Options{
ShowOnFront: "page",
PageOnFront: "{{home}}",
PageForPosts: "{{blog}}",
},
ThemeMods: ThemeMods{
Panel1: "{{homepage-section}}",
Panel2: "{{about}}",
Panel3: "{{blog}}",
Panel4: "{{contact}}",
},
NavMenus: NavMenus{
Top: Menus{
Name: "顶部菜单",
Items: []string{
"link_home",
"page_about",
"page_blog",
"page_contact",
},
},
Social: Menus{
Name: "社交网络链接菜单",
Items: []string{
"link_yelp",
"link_facebook",
"link_twitter",
"link_instagram",
"link_email",
},
},
},
},
}
var _ = func() struct{} {
v := wpconfig.ThemeSupport{
CoreBlockPatterns: true,
WidgetsBlockEditor: true,
AutomaticFeedLinks: true,
TitleTag: true,
PostThumbnails: true,
Menus: true,
HTML5: []string{
"comment-form",
"comment-list",
"gallery",
"caption",
"script",
"style",
"navigation-widgets",
},
PostFormats: []string{
"aside",
"image",
"video",
"quote",
"link",
"gallery",
"audio",
},
CustomLogo: wpconfig.CustomLogo{
Width: 250,
Height: 250,
FlexWidth: true,
FlexHeight: false,
HeaderText: "",
UnlinkHomepageLogo: false,
},
CustomizeSelectiveRefreshWidgets: true,
EditorStyle: true,
EditorStyles: true,
WpBlockStyles: true,
ResponsiveEmbeds: true,
CustomHeader: wpconfig.CustomHeader{
DefaultImage: "http://wp.test/wp-content/themes/twentyseventeen/assets/images/header.jpg",
RandomDefault: false,
Width: 2000,
Height: 1200,
FlexHeight: true,
FlexWidth: false,
DefaultTextColor: "",
HeaderText: true,
Uploads: true,
WpHeadCallback: "twentyseventeen_header_style",
AdminHeadCallback: "",
AdminPreviewCallback: "",
Video: true,
VideoActiveCallback: "is_front_page",
},
Widgets: true,
}
wpconfig.SetThemeSupport(ThemeName, v)
return struct{}{}
}()

View File

@ -0,0 +1,229 @@
package twentyseventeen
import (
"context"
"fmt"
"github.com/fthvgb1/wp-go/app/pkg/constraints"
"github.com/fthvgb1/wp-go/app/pkg/constraints/widgets"
"github.com/fthvgb1/wp-go/app/pkg/logs"
"github.com/fthvgb1/wp-go/app/pkg/models"
"github.com/fthvgb1/wp-go/app/plugins"
"github.com/fthvgb1/wp-go/app/theme/wp"
"github.com/fthvgb1/wp-go/app/theme/wp/components"
"github.com/fthvgb1/wp-go/app/theme/wp/middleware"
"github.com/fthvgb1/wp-go/app/wpconfig"
"github.com/fthvgb1/wp-go/cache/reload"
"github.com/fthvgb1/wp-go/helper"
str "github.com/fthvgb1/wp-go/helper/strings"
"github.com/fthvgb1/wp-go/plugin/pagination"
"strings"
)
const ThemeName = "twentyseventeen"
var paginate = func() plugins.PageEle {
p := plugins.TwentyFifteenPagination()
p.PrevEle = `<a class="prev page-numbers" href="%s"><svg class="icon icon-arrow-left" aria-hidden="true" role="img"> <use href="#icon-arrow-left" xlink:href="#icon-arrow-left"></use> </svg>
<span class="screen-reader-text">上一页</span></a>`
p.NextEle = strings.Replace(p.NextEle, "下一页", `<span class="screen-reader-text">下一页</span>
<svg class="icon icon-arrow-right" aria-hidden="true" role="img"> <use href="#icon-arrow-right" xlink:href="#icon-arrow-right"></use>
</svg>`, 1)
commentPageEle = plugins.PaginationNav{
Currents: p.Current,
Prevs: p.Prev,
Nexts: p.Next,
Dotss: p.Dots,
Middles: p.Middle,
Urlss: plugins.TwentyFifteenCommentPagination().Urls,
}
return p
}()
var commentPageEle pagination.Render
func Hook(h *wp.Handle) {
wp.Run(h, configs)
}
func configs(h *wp.Handle) {
wp.InitPipe(h)
middleware.CommonMiddleware(h)
h.AddActionFilter("bodyClass", calClass)
h.PushCacheGroupHeadScript(constraints.AllScene, "colorScheme-customHeader", 10, colorScheme, customHeader)
components.WidgetArea(h)
pushScripts(h)
h.PushRender(constraints.AllStats, wp.NewHandleFn(calCustomHeader, 10.005, "calCustomHeader"))
wp.SetComponentsArgs(widgets.Widget, map[string]string{
"{$before_widget}": `<section id="%s" class="%s">`,
"{$after_widget}": `</section>`,
})
h.PushRender(constraints.AllStats,
wp.NewHandleFn(wp.PreTemplate, 70.005, "wp.PreTemplate"),
wp.NewHandleFn(errorsHandle, 80.005, "errorsHandle"),
)
videoHeader(h)
h.SetData("colophon", colophon)
setPaginationAndRender(h)
h.CommonComponents()
h.PushPostPlugin(postThumbnail)
wp.SetComponentsArgsForMap(widgets.Search, "{$form}", searchForm)
wp.PushIndexHandler(constraints.PipeRender, h, wp.NewHandleFn(wp.IndexRender, 10.005, "wp.IndexRender"))
h.PushRender(constraints.Detail, wp.NewHandleFn(wp.DetailRender, 10.005, "wp.DetailRender"))
h.PushDataHandler(constraints.Detail, wp.NewHandleFn(wp.Detail, 100.005, "wp.Detail"), wp.NewHandleFn(postThumb, 90.005, "{theme}.postThumb"))
wp.PushIndexHandler(constraints.PipeData, h, wp.NewHandleFn(wp.Index, 100.005, "wp.Index"))
h.PushDataHandler(constraints.AllScene, wp.NewHandleFn(wp.PreCodeAndStats, 90.005, "wp.PreCodeAndStats"))
}
func setPaginationAndRender(h *wp.Handle) {
h.PushHandler(constraints.PipeRender, constraints.Detail, wp.NewHandleFn(func(hh *wp.Handle) {
d := hh.GetDetailHandle()
d.CommentRender = commentFormat
d.CommentPageEle = commentPageEle
d.CommentStep = 2
}, 150, "setPaginationAndRender"))
wp.PushIndexHandler(constraints.PipeRender, h, wp.NewHandleFn(func(hh *wp.Handle) {
i := hh.GetIndexHandle()
i.SetPageEle(paginate)
}, 150, "setPaginationAndRender"))
}
var searchForm = `<form role="search" method="get" class="search-form" action="/">
<label for="search-form-1">
<span class="screen-reader-text">{$label}</span>
</label>
<input type="search" id="search-form-1" class="search-field" placeholder="{$placeholder}…" value="{$value}" name="s">
<button type="submit" class="search-submit">
<svg class="icon icon-search" aria-hidden="true" role="img"> <use href="#icon-search" xlink:href="#icon-search"></use> </svg>
<span class="screen-reader-text">{$button}</span>
</button>
</form>`
func errorsHandle(h *wp.Handle) {
switch h.Stats {
case constraints.Error404, constraints.InternalErr, constraints.ParamError:
logs.IfError(h.Err(), "报错:")
h.SetTempl("twentyseventeen/posts/error.gohtml")
}
}
func postThumb(h *wp.Handle) {
d := h.GetDetailHandle()
if d.Post.Thumbnail.Path != "" {
img := wpconfig.Thumbnail(d.Post.Thumbnail.OriginAttachmentData, "full", "", "thumbnail", "post-thumbnail")
img.Sizes = "100vw"
img.Srcset = fmt.Sprintf("%s %dw, %s", img.Path, img.Width, img.Srcset)
d.Post.Thumbnail = img
}
}
var commentFormat = comment{}
type comment struct {
plugins.CommonCommentFormat
}
var commentLi = plugins.CommonLi()
var respondFn = plugins.Responds(respondStr)
func (c comment) FormatLi(_ context.Context, m models.Comments, depth, maxDepth, page int, isTls, isThreadComments bool, eo, parent string) string {
return plugins.FormatLi(commentLi, m, respondFn, depth, maxDepth, page, isTls, isThreadComments, eo, parent)
}
var colophon = `<footer id="colophon" class="site-footer">
<div class="wrap">
<div class="site-info">
<a href="https://github.com/fthvgb1/wp-go" class="imprint">自豪地采用 wp-go</a>
</div>
</div>
</footer>`
var respondStr = `<a rel="nofollow" class="comment-reply-link"
href="/p/{{PostId}}?replytocom={{CommentId}}#respond" data-commentid="{{CommentId}}" data-postid="{{PostId}}"
data-belowelement="div-comment-{{CommentId}}" data-respondelement="respond"
data-replyto="回复给{{CommentAuthor}}"
aria-label="回复给{{CommentAuthor}}"><svg class="icon icon-mail-reply" aria-hidden="true" role="img"> <use href="#icon-mail-reply" xlink:href="#icon-mail-reply"></use> </svg>回复</a>`
func postThumbnail(h *wp.Handle, posts *models.Posts) {
if posts.Thumbnail.Path != "" {
posts.Thumbnail.Sizes = "(max-width: 767px) 89vw, (max-width: 1000px) 54vw, (max-width: 1071px) 543px, 580px"
if h.Scene() == constraints.Detail {
posts.Thumbnail.Sizes = "100vw"
}
}
}
var header = reload.Vars(models.PostThumbnail{}, "twentyseventeen-headerImage")
func calCustomHeader(h *wp.Handle) {
h.SetData("HeaderImage", getHeaderImage(h))
}
func getHeaderImage(h *wp.Handle) (r models.PostThumbnail) {
img := header.Load()
if img.Path != "" {
return img
}
image, rand := h.GetCustomHeaderImg()
if image.Path != "" {
r = image
r.Sizes = "100vw"
if !rand {
header.Store(r)
}
return
}
r.Path = helper.CutUrlHost(h.CommonThemeMods().ThemeSupport.CustomHeader.DefaultImage)
r.Width = 2000
r.Height = 1200
header.Store(r)
return
}
func calClass(h *wp.Handle, s string, _ ...any) string {
class := strings.Split(s, " ")
themeMods := h.CommonThemeMods()
u := wpconfig.GetThemeModsVal(ThemeName, "header_image", themeMods.ThemeSupport.CustomHeader.DefaultImage)
if u != "" && u != "remove-header" {
class = append(class, "has-header-image")
}
if len(themeMods.SidebarsWidgets.Data.Sidebar1) > 0 {
class = append(class, "has-sidebar")
}
if themeMods.HeaderTextcolor == "blank" {
class = append(class, "title-tagline-hidden")
}
class = append(class, "hfeed")
class = append(class, str.Join("colors-", wpconfig.GetThemeModsVal(ThemeName, "colorscheme", "light")))
if h.Scene() == constraints.Archive {
if "one-column" == wpconfig.GetThemeModsVal(ThemeName, "page_layout", "") {
class = append(class, "page-one-column")
} else {
class = append(class, "page-two-column")
}
}
return strings.Join(class, " ")
}
func videoHeader(h *wp.Handle) {
h.AddActionFilter("videoSetting", videoPlay)
wp.CustomVideo(h, constraints.Home)
}
func videoPlay(h *wp.Handle, _ string, a ...any) string {
if len(a) < 1 {
return ""
}
v, ok := a[0].(*wp.VideoSetting)
if !ok {
return ""
}
img := getHeaderImage(h)
v.Width = img.Width
v.Height = img.Height
v.PosterUrl = img.Path
v.L10n.Play = `<span class="screen-reader-text">播放背景视频</span><svg class="icon icon-play" aria-hidden="true" role="img"> <use href="#icon-play" xlink:href="#icon-play"></use> </svg>`
v.L10n.Pause = `<span class="screen-reader-text">暂停背景视频</span><svg class="icon icon-pause" aria-hidden="true" role="img"> <use href="#icon-pause" xlink:href="#icon-pause"></use> </svg>`
return ""
}

Some files were not shown because too many files have changed in this diff Show More