Showing preview only (225K chars total). Download the full file or copy to clipboard to get everything.
Repository: LawyerLu/OneBlog
Branch: lite
Commit: 7bb9e93605a9
Files: 23
Total size: 197.8 KB
Directory structure:
gitextract_j_ciepga/
├── LICENSE
├── README.md
├── api/
│ ├── img.php
│ ├── img.txt
│ ├── link-status.php
│ └── one.txt
├── archives.php
├── comments.php
├── footer.php
├── functions.php
├── header.php
├── index.php
├── links.php
├── memos.php
├── module/
│ ├── head.php
│ └── head2.php
├── page.php
├── photos.php
├── post.php
└── static/
└── js/
├── admin.js
├── comments.js
├── emoji.js
└── main.js
================================================
FILE CONTENTS
================================================
================================================
FILE: LICENSE
================================================
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
<signature of Ty Coon>, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License.
================================================
FILE: README.md
================================================
## TYPECHO文字博客主题:OneBlog 一款简约清新文艺的写作记录类单栏主题
<a href="https://onenote.io"><img src="https://blog.cncdn.cc/ui/black.svg" alt="oneblog" width="180"></a>
### 项目简介
OneBlog,一个博客,再无其他。本主题基于Typecho,设计初衷是写作本身,并无太多其他功能,是一款简约清新文艺风格的写作记录类文字博客主题,适合生活记录、文学作品、个人日志等文字类博客,非文字类博客请慎用。OneBlog主题由[彼岸临窗](https://onenote.io/)精心打磨多年,且持续优化,现免费开源,致敬互联网社区开源精神,也致敬热爱生活和记录的我们。
**官网**:[onenote.io](https://onenote.io/)
**最新版本:V3.7.0**
### 主题特性
✅ **极致的性能,页面加载平均仅需0.02s;**
✅ **流畅的体验,无限加载+自动加载数据+图片懒加载;**
✅ **极致的简约风,扁平化设计,简约而不简单;**
✅ **符合美学的松弛美感,精益求精的留白,让每一次访问都是一种享受;**
✅ **精雕细琢,精致而优雅的响应式移动端界面;**
✅ **注释齐全,代码精简,基于OneBlog,你可以最大限度地实现自定义效果。**
### 主题功能
- [x] 文章,聚焦文字本身,沉浸式阅读体验;
- [x] 微语,前台登录和发布,抓住转瞬即逝的灵感;
- [x] 友链,每一位邻居都值得被珍惜和被看见;
- [x] 评论,电脑端点击无限加载,移动端下拉自动加载;
- [x] 归档,干净利落的博客归档页面;
- [x] 搜索,友好的交互体验。
### 使用文档
主题文档:[docs.onenote.io](https://docs.onenote.io)
全网首发:[onenote.io/oneblog](https://onenote.io/oneblog)
主题方面的问题请优先在issues中反馈,<u>请勿在博客文章留言区反馈</u>。
关于主题的讨论,请统一在[主题发布页](https://onenote.io/oneblog)发表。
### 版权声明
本主题的所有外观设计专利及软件著作权均归 [©彼岸临窗](https://onenote.io) 所有,并已取得中华人民共和国国家版权局颁发的计算机软件著作权登记证书(登记号:2025SR0334142)和外观设计专利证书(证书号:第7121519号),作者保留所有权利。致敬开源,本主题自2025年1月1日起,以GPL-2.0协议授权广大用户免费使用。任何个人或单位在注明来源的基础上,均可以免费无偿使用本主题,但不得以任何形式售卖(包括但不限于以付费下载、积分购买、vip用户可见等形式向用户提供下载链接,下同),否则视为侵权。基于GPL-2.0协议,本主题允许在保留来源(同时包含署名和链接)的基础上对源代码进行修改,但修改后的源码只能自己使用或免费开源,不得以任何形式售卖。
### 赞助本项目
**开源不易,记得点★Star,支持作者。**
你的关注和赞助是这个项目维护下去的坚实动力。
| 通过支付宝赞助 | 通过微信赞助 |
| :-------------------------------------------------------: | :------------------------------------------------------: |
| <img width="200px" src="https://blog.cncdn.cc/ui/alipay.png" /> | <img width="200px" src="https://blog.cncdn.cc/ui/wxpay.png" /> |
### 主题交流QQ群
欢迎加入官方QQ交流群:**939170079**
### 主题预览
更多功能模块的演示请访问主题官网了解:[onenote.io](https://onenote.io)
<img src="https://blog.cncdn.cc/ui/screenshot.png" />
### 版本更新记录
```
v3.7.0 版本更新
1.修复typecho1.3.0版本下的文章编辑页的自定义字段错位问题;
2.支持自动在夜晚时间段开启夜间模式;
3.内置字体源可供个性化选择并支持自定义字体;
4.友链页面新增在线检测;
5.内置网站地图sitemap.xml文件;
6.正式上线主题官网。
v3.6.5 版本更新
1.修复评论区域的xss漏洞(Typecho1.2.1强烈建议更新);
2.优化为评论者链接新窗口打开;
3.可自定义博客图片LOGO或文字LOGO;
4.新增Cloudflare Turnstile验证(优先),可自由选择关闭(不填写密钥)、CFTS或极验验证;
5.第三方SDK和主题样式文件全部自托管到Github仓库并通过Edgeone实现全球访问加速(平均0.4s);
6.微语部分适配付费插件MemosImage,启用插件后友好支持图片九宫格、单图自适应、缩略图等配图的上传与展示(目前支持原生上传到服务器和浏览器直传COS两种上传方式)。
v3.6.4 版本更新
1.修复移动端导航与搜索抽屉层级冲突的BUG;
2.修复默认第一条评论显示报错信息的BUG;
3.优化有封面图文章的菜单按钮样式;
4.新增banner区域的骨架屏设计,避免动态生成内容导致的页面异常;
5.新增访客评论极验验证,有效拦截垃圾评论。
v3.6.3版本更新
1.新增js首次加载完成前的动画效果,避免js未完全加载导致的页面显示异常的问题。
2.部分细节优化。
V3.6.2版本更新
1.移除右键菜单,从模拟右键优化为原生菜单,解决安卓端长按出现右键菜单的bug;
2.优化懒加载逻辑,结合IntersectionObserver和串行加载,避免图片过多时主进程堵塞;
3.优化静态资源加载逻辑,减少非必要的资源加载;
4.修复文章标题过长时,面包屑显示异常的bug。
V3.6.1版本更新
温馨提示:本版本对代码进行了重构和优化,性能更上一层楼,但与此同时,舍弃了旧版本的书单、相册等功能,如您对这些功能有强烈的需求,建议不要更新本版本,本仓库保留了old分支,切换到该分支,打包即可下载使用旧版本。
1.代码重构,本版本起,采用响应式设计,移动端和电脑端自适应,友好地支持使用cdn;
2.优化评论列表加载逻辑,电脑端点击加载,移动端触底自动加载;
3.新增菜单按钮,电脑端的搜索设计成右侧弹出层;
4.修复懒加载完成前,封面图区域不显示内容的BUG;
5.优化文章列表点击区域,点击任意元素均可进入文章详情页;
6.移除电脑端护眼模式,与移动端夜间模式保持统一;
7.优化主题色配色,使得页面色调更协调美观;
8.集成付费插件-Album相册插件,支持通过插件实现独立于文章数据的相册管理和展示;
9.优化独立页面设计,文章详情页、分类归档页、标签归档页细节优化;
10.后台支持自定义CSS、自定义JS;
11.支持主题设置的备份与恢复,切换其他主题后再切回本主题,可以一键配置之前的数据;
12.其他细节优化。
V3.5.3版本更新
1.优化评论区域多层级样式;
2.优化评论输入框的布局,并支持输入框内实时预览表情;
3.更新头像镜像源;
4.优化表格样式;
5.新增自定义CSS、主题色,后续将支持更多个性化设置;
6.其他安全性更新。
V3.5.2版本更新
1.主题内置自定义菜单;
2.PC端页脚支持显示icp备案号和公安联网备案号;
3.主题文档全新上线;
4.其他细节优化。
V3.5.1版本更新
1.文章缩略图逻辑优化;
2.文章列表页缩略图支持自定义图像处理参数;
3.优化归档页样式、文章列表间距;
4.修复移动端黑夜模式下猜你喜欢的文章无法点击的bug;
5.修复移动端黑夜模式下logo显示为护眼模式logo的bug;
6.修复PC端搜索页面错位bug;
7.优化页面/文章关闭评论且没有评论时底部间距过窄的bug。
V3.5版本更新
1.主题重构,全站性能优化,精简合并所有通用类,规范重写前端样式;
2.新增搜索功能;
3.新增PC端护眼模式和移动端黑夜模式;
4.优化表情模块,兼容CommentNotifier插件,实现邮件通知中也能显示表情;
5.评论区细节优化;
6.优化分类页面、标签页面显示逻辑;
7.支持自定义主题配色;
8.优化重复点赞提示;
9.修复全部已知BUG。
V3.4.3版本更新
1.新增归档页面;
2.新增独立页面模板:无图单页;
3.性能优化,速度更快;
```
================================================
FILE: api/img.php
================================================
<?php /**获取随机图片 @author 青柠**/
$txt = "img.txt";
if(file_get_contents($txt)){
$data = file($txt);
$num = count($data);
$id = mt_rand(0,$num-1);
$url = chop($data[$id]);
header("location:$url");
}
================================================
FILE: api/img.txt
================================================
https://pic1.imgdb.cn/item/679303a3d0e0a243d4f76770.jpg
https://pic1.imgdb.cn/item/679303a3d0e0a243d4f7676f.jpg
https://pic1.imgdb.cn/item/6793039cd0e0a243d4f7676b.jpg
https://pic1.imgdb.cn/item/6793039bd0e0a243d4f7676a.jpg
https://pic1.imgdb.cn/item/6793039bd0e0a243d4f76769.jpg
https://pic1.imgdb.cn/item/6793039ad0e0a243d4f76768.jpg
https://pic1.imgdb.cn/item/67930391d0e0a243d4f76765.jpg
https://pic1.imgdb.cn/item/67930391d0e0a243d4f76764.jpg
https://pic1.imgdb.cn/item/67930390d0e0a243d4f76763.jpg
https://pic1.imgdb.cn/item/67930390d0e0a243d4f76762.jpg
https://pic1.imgdb.cn/item/67930386d0e0a243d4f7675b.jpg
https://pic1.imgdb.cn/item/67930386d0e0a243d4f76759.jpg
https://pic1.imgdb.cn/item/67930386d0e0a243d4f76758.jpg
https://pic1.imgdb.cn/item/67930385d0e0a243d4f76757.jpg
================================================
FILE: api/link-status.php
================================================
<?php
header('Content-Type: application/json; charset=UTF-8');
const ONEBLOG_LINK_STATUS_TTL = 86400;
const ONEBLOG_LINK_STATUS_CACHE_VERSION = 2;
function oneblog_json($data) {
echo json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
exit;
}
function oneblog_cache_path() {
$uploadDir = dirname(__DIR__, 3) . '/uploads';
if (!is_dir($uploadDir)) {
@mkdir($uploadDir, 0755, true);
}
return is_writable($uploadDir)
? $uploadDir . '/oneblog-link-status.json'
: __DIR__ . '/oneblog-link-status.json';
}
function oneblog_load_cache() {
$path = oneblog_cache_path();
if (!is_file($path)) return [];
$data = json_decode((string) @file_get_contents($path), true);
return is_array($data) ? $data : [];
}
function oneblog_save_cache($cache) {
@file_put_contents(oneblog_cache_path(), json_encode($cache, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
}
function oneblog_normalize_url($url) {
$url = trim((string) $url);
if ($url === '') return '';
if (strpos($url, '//') === 0) return 'https:' . $url;
if (!preg_match('#^https?://#i', $url)) return 'https://' . $url;
return $url;
}
function oneblog_read_urls() {
$urls = $_POST['urls'] ?? ($_GET['urls'] ?? []);
if (!empty($_REQUEST['url'])) $urls[] = $_REQUEST['url'];
if (!is_array($urls)) $urls = [$urls];
$urls = array_values(array_unique(array_filter(array_map('oneblog_normalize_url', $urls))));
return array_slice($urls, 0, 30);
}
function oneblog_is_online_code($code) {
$code = (int) $code;
if ($code === 0) return false;
if (in_array($code, [401, 403, 429], true)) {
return true;
}
return $code >= 200 && $code < 500 && $code !== 404;
}
function oneblog_curl_options($url, $isHead) {
return [
CURLOPT_URL => $url,
CURLOPT_TIMEOUT => 8,
CURLOPT_CONNECTTIMEOUT => 5,
CURLOPT_NOBODY => $isHead,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_MAXREDIRS => 3,
CURLOPT_SSL_VERIFYPEER => false,
CURLOPT_SSL_VERIFYHOST => false,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_USERAGENT => 'Oneblog-LinkChecker',
];
}
function oneblog_check_once($url, $isHead) {
$ch = curl_init();
curl_setopt_array($ch, oneblog_curl_options($url, $isHead));
curl_exec($ch);
$result = [
'code' => (int) curl_getinfo($ch, CURLINFO_HTTP_CODE),
];
curl_close($ch);
return $result;
}
function oneblog_check_multi($urls, $isHead) {
if (!$urls) return [];
if (!function_exists('curl_multi_init')) {
$results = [];
foreach ($urls as $url) {
$results[$url] = oneblog_check_once($url, $isHead);
}
return $results;
}
$mh = curl_multi_init();
$channels = [];
foreach ($urls as $url) {
$ch = curl_init();
curl_setopt_array($ch, oneblog_curl_options($url, $isHead));
curl_multi_add_handle($mh, $ch);
$channels[$url] = $ch;
}
do {
$status = curl_multi_exec($mh, $active);
if ($active) curl_multi_select($mh, 0.5);
} while ($active && $status === CURLM_OK);
$results = [];
foreach ($channels as $url => $ch) {
$results[$url] = [
'code' => (int) curl_getinfo($ch, CURLINFO_HTTP_CODE),
];
curl_multi_remove_handle($mh, $ch);
curl_close($ch);
}
curl_multi_close($mh);
return $results;
}
function oneblog_https_handshake($url) {
$parts = parse_url($url);
$host = $parts['host'] ?? '';
if (!$host) return false;
$port = !empty($parts['port']) ? (int) $parts['port'] : 443;
$fp = @stream_socket_client('ssl://' . $host . ':' . $port, $errno, $error, 6, STREAM_CLIENT_CONNECT);
if ($fp) {
fclose($fp);
return true;
}
return false;
}
function oneblog_build_result($url, $checks) {
$final = end($checks) ?: ['code' => 0];
$online = oneblog_is_online_code($final['code']);
if (!$online && (int) $final['code'] === 0) {
if (oneblog_https_handshake($url)) {
$online = true;
}
}
return [
'status' => $online ? 'ok' : 'error',
'checked_at' => time(),
'version' => ONEBLOG_LINK_STATUS_CACHE_VERSION,
'url' => $url,
];
}
$urls = oneblog_read_urls();
if (!$urls) {
oneblog_json(['success' => false, 'message' => 'No url provided']);
}
$cache = oneblog_load_cache();
$items = [];
$needCheck = [];
$now = time();
foreach ($urls as $url) {
$key = md5($url);
$cached = $cache[$key] ?? null;
if (
is_array($cached)
&& (int) ($cached['version'] ?? 0) === ONEBLOG_LINK_STATUS_CACHE_VERSION
&& ($now - (int) ($cached['checked_at'] ?? 0)) < ONEBLOG_LINK_STATUS_TTL
) {
$items[$url] = $cached['status'];
} else {
$needCheck[] = $url;
}
}
if ($needCheck) {
$headResults = oneblog_check_multi($needCheck, true);
$checksByUrl = [];
$needGet = [];
foreach ($needCheck as $url) {
$head = $headResults[$url] ?? ['code' => 0];
$checksByUrl[$url] = [$head];
if ($head['code'] === 0 || $head['code'] === 405) {
$needGet[] = $url;
}
}
$getResults = oneblog_check_multi($needGet, false);
foreach ($needGet as $url) {
if (!empty($getResults[$url])) {
$checksByUrl[$url][] = $getResults[$url];
}
}
foreach ($needCheck as $url) {
$result = oneblog_build_result($url, $checksByUrl[$url] ?? []);
$cache[md5($url)] = $result;
$items[$url] = $result['status'];
}
}
foreach ($cache as &$cached) {
if (is_array($cached)) {
unset($cached['time']);
}
}
unset($cached);
oneblog_save_cache($cache);
oneblog_json(['success' => true, 'items' => $items]);
================================================
FILE: api/one.txt
================================================
趁着年轻,好好犯病。
温柔正确的人总是难以生存,因为这世界既不温柔,也不正确。
不管昨夜经历了怎样的泣不成声,早晨醒来这个城市依然车水马龙。
虽然我们互相笑着说回头见,但我们都心知肚明,分离即永别。
当爱情发言的时候,就像诸神的合唱,使整个的天界陶醉于仙乐之中。
你是我的半截的诗,不许别人更改一个字。
我想和你一起生活,在某个小镇,共享无尽的黄昏,和绵绵不绝的钟声。
如果有一天我们湮没在人海默默无闻,那只能怪我们没有好好努力让生活过得丰盛。
知其黑,守其白。
人生不是轨道,而是一片原野,伟大的事物通常始于渺小。
成长是一笔交易,我们都是用朴素的童真和未经人事的洁白来交换长大的勇气。
迷失的人迷失了,相逢的人会再相逢。
被误解是表达者的宿命。
你记得也好,最好是忘掉。
我最担心的是,我配不上自己所受的苦难。
人来人往,聚散匆忙。
上帝遣送绝望给我们,不是为了杀死我们,而是为了唤醒我们内心的新生命。
一切都是最好的安排。
能跨越的高墙,都会成为自己的盾牌。
这座城市大得让人到处能一见钟情,却没法再重逢。
悟已往之不谏,知来者之可追。
其实这个世界偶尔也擅长创造惊喜。
我们笑着说再见,却深知再见遥遥无期。
每个人的内心都有一团火,路过的人只能看到烟。
我最好的作品,就是我的生活。
凡为过往,皆为序章。
我们无法预知某个瞬间的价值,直到它成为回忆。
知彼此,而非如此;望如常,而非平常。
================================================
FILE: archives.php
================================================
<?php
/**
* 归档页面
*
* @package custom
*/
if (!defined('__TYPECHO_ROOT_DIR__')) exit;
$this->need('header.php'); ?>
<div class="main">
<?php $this->need('module/head2.php');?>
<!--背景图片+logo-->
<div class="page_thumb blur">
<!-- 背景图片容器 -->
<div class="post_bg lazy-load" data-src="<?php echo $this->fields->thumb ? $this->fields->thumb : Helper::options()->themeUrl . '/static/img/archives.jpg';?>"></div>
<div class="pc">
<!-- 新增的菜单按钮 -->
<i class="iconfont icon-nav menu-button"></i>
<div class="page-head">
<?php if ($this->options->logoStyle == 'text') : ?>
<h1>
<a href="<?php $this->options->siteUrl(); ?>"><?php $this->options->title(); ?></a>
<span class="soul">生活志</span>
</h1>
<?php else : ?>
<a class="logo" href="<?php $this->options->siteUrl(); ?>">
<img src="<?php echo $this->options->logoWhite ?: Helper::options()->themeUrl . '/static/img/logoWhite.svg'; ?>">
</a>
<?php endif; ?>
</div>
</div>
<div class="m">
<h1 class="page-head"><?php $this->archiveTitle(' » ', ''); ?><span>Blog's Archives</span></h1>
</div>
</div>
<div class="page-title animate__animated animate__fadeIn pc">
<h1><?php $this->title(); ?></h1>
</div>
<!-- 全部文章(不再排除任何分类) -->
<section class="archives padding animate__animated animate__fadeIn blur">
<?php
$this->widget('Widget_Contents_Post_Recent', 'pageSize=10000')->to($archives);
$articlesByYear = [];
while ($archives->next()) {
$year = date('Y', $archives->created);
if (!isset($articlesByYear[$year])) {
$articlesByYear[$year] = [];
}
$articlesByYear[$year][] = [
'title' => $archives->hidden ? '密码保护:' . $archives->row['title'] : $archives->row['title'],
'permalink' => $archives->permalink,
'date' => date('m月d日', $archives->created)
];
}
foreach ($articlesByYear as $year => $articles) {
echo '<h3><span>#</span>' . $year . '年</h3>';
foreach ($articles as $article) {
echo '<li><a href="' . $article['permalink'] . '"><span>' . $article['date'] . '</span>' . $article['title'] . '</a></li>';
}
}
?>
</section>
<!-- 全部独立页面 -->
<section class="archives padding animate__animated animate__fadeIn blur">
<h3><span>#</span>页面</h3>
<?php
$this->widget('Widget_Contents_Page_List')->to($pages);
while ($pages->next()) {
echo '<li><a href="' . $pages->permalink . '">' . $pages->title . '</a></li>';
}
?>
</section>
<!-- 全部分类 -->
<section class="archives padding animate__animated animate__fadeIn blur">
<h3><span>#</span>分类</h3>
<?php
$this->widget('Widget_Metas_Category_List')->to($categories);
while ($categories->next()) {
echo '<li><a href="' . $categories->permalink . '">' . $categories->name . '</a></li>';
}
?>
</section>
<!-- 全部标签 -->
<section class="archives padding animate__animated animate__fadeIn blur">
<h3><span>#</span>标签</h3>
<div class="tags">
<?php
$this->widget('Widget_Metas_Tag_Cloud', 'ignoreZeroCount=1')->to($tags);
while ($tags->next()) {
echo '<a href="' . $tags->permalink . '">' . $tags->name . '</a>';
}
?>
</div>
</section>
<div class="load blur" >— END —</div>
<a id="gototop" class="hidden"><i class="iconfont icon-up"></i></a>
</div>
<?php $this->need('footer.php'); ?>
================================================
FILE: comments.php
================================================
<?php
/**
* comments.php(含 Cloudflare Turnstile 集成,优先 CF,其次 Geetest)
*/
?>
<div class="padding blur" id="comments">
<?php $this->comments()->to($comments); ?>
<!--评论输入框-->
<?php if($this->allow('comment')): ?>
<div id="<?php $this->respondId(); ?>" class="respond">
<div class="cancel-comment-reply">
<?php $comments->cancelReply(); ?>
</div>
<h3 class="oneblog" id="response">
<div id="default-title"><i class="iconfont icon-memos"></i>发表留言</div>
<div id="reply-title" style="display:none">回复<div id="reply-target"></div>
</div>
</h3>
<form method="post" action="<?php $this->commentUrl() ?>" id="comment-form" role="form">
<?php if ($this->user->hasLogin()): ?>
<?php else: ?>
<div class="comment-author-info">
<div class="comment-md-3">
<label for="author"><?php _e('称呼'); ?><span class="required">*</span></label>
<input type="text" name="author" id="author" class="text" value="<?php $this->remember('author'); ?>" required />
</div>
<div class="comment-md-3">
<label for="mail"<?php if ($this->options->commentsRequireMail): ?><?php endif; ?>><?php _e('邮箱'); ?><span class="required">*</span></label>
<input type="email" name="mail" id="mail" class="text" value="<?php $this->remember('mail'); ?>"<?php if ($this->options->commentsRequireMail): ?> required<?php endif; ?> />
</div>
<div class="comment-md-3">
<label for="url"<?php if ($this->options->commentsRequireURL): ?> class="required"<?php endif; ?>><?php _e('网站'); ?></label>
<input type="url" name="url" id="url" class="text" placeholder="<?php _e('https://'); ?>" value="<?php $this->remember('url'); ?>"<?php if ($this->options->commentsRequireURL): ?> required<?php endif; ?> />
</div>
</div>
<!-- Cloudflare Turnstile: 前端 SiteKey(隐藏)与隐藏 token 字段 -->
<?php if ($this->options->CFSiteKey): ?>
<input type="hidden" id="cf-sitekey" value="<?php echo htmlspecialchars($this->options->CFSiteKey); ?>" />
<input type="hidden" name="cf_token" id="cf-token" value="" />
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>
<?php elseif ($this->options->GeetestID): ?>
<input type="hidden" id="geetest-captcha-id" value="<?php echo htmlspecialchars($this->options->GeetestID); ?>" />
<?php endif; ?>
<?php endif; ?>
<!-- 隐藏的textarea,实际提交用 -->
<textarea name="text" id="textarea" style="display:none;" required></textarea>
<!-- 可编辑div,表情预览和输入都在这里 -->
<div id="rich-editor" contenteditable="true" class="rich-editor"></div>
<div class="comment-submit">
<div class="emoji" title="添加表情">
<i id="emoji-btn" class="iconfont icon-emoji"></i>
<div id="emoji-picker" class="emoji-picker" style="display:none;">
<div class="emoji-categories">
<button type="button" class="emoji-category" data-category="emotion">表情</button>
<button type="button" class="emoji-category" data-category="special">特殊</button>
<button type="button" class="emoji-category" data-category="kaomoji">颜文字</button>
</div>
<div class="emoji-container">
<!-- 表情图标会动态加载到这里 -->
</div>
</div>
</div>
<button type="submit" class="submit" id="geetest-submit-btn"><?php _e('提交审核'); ?></button>
</div>
</form>
</div>
<?php else:?>
<div class="load">— END —</div>
<?php endif; ?>
<!--评论列表开始-->
<?php if ($comments->have()): ?>
<div class="line"></div>
<h3 class="oneblog"><?php $this->commentsNum(_t('<i class="iconfont icon-guestbook"></i>暂无留言'), _t('<i class="iconfont icon-guestbook"></i>读者留言<span>1</span>'), _t('<i class="iconfont icon-guestbook"></i>读者留言<span>%d</span>')); ?></h3>
<?php
function getParentAuthor($comments) {
$parent = $comments->parent;
if ($parent) {
$db = Typecho_Db::get();
$row = $db->fetchRow($db->select()->from('table.comments')->where('coid = ?', $parent));
if ($row && isset($row['author'])) {
return $row['author'];
}
}
return null;
}
function threadedComments($comments, $options) {$commentLevelClass = $comments->levels > 0 ? 'comment-child' : 'comment-parent';?>
<li class="animate__animated animate__fadeIn depth-<?php echo $comments->levels + 1; ?> <?php echo $commentLevelClass;?>"> <!-- 添加深度类 -->
<div id="<?php $comments->theId(); ?>">
<div class="user">
<?php $email=htmlspecialchars($comments->mail, ENT_QUOTES, 'UTF-8'); $imgUrl = getGravatar($email);echo '<img class="avatar" src="'.$imgUrl.'">'; ?>
<div class="user-info">
<div class="name">
<?php echo comment_author_link($comments); ?>
<?php dengji(htmlspecialchars($comments->mail, ENT_QUOTES, 'UTF-8'));?>
<?php if ($comments->status === "waiting") : ?>
<span class="waiting">[ 审核中 ]</span>
<?php endif; ?>
</div>
<div class="date">
<span><?php $comments->date('Y-m-d H:i'); ?></span>
<span class="comment-reply" data-author="<?php echo htmlspecialchars($comments->author); ?>">
<?php $comments->reply(); ?>
</span>
</div>
</div>
</div>
<div class="<?= $comments->status === "waiting" ? 'waiting ' : '' ?>comment-box-<?php echo $comments->levels + 1; ?>">
<?php
if ($comments->levels > 0) {
$parentAuthor = getParentAuthor($comments);
if ($parentAuthor) {
echo '<span class="at-parent">@' . htmlspecialchars($parentAuthor) . '</span> ';
}
}
?>
<?= parseEmojis($comments->content); ?>
</div>
<!--子级评论列表-->
<?php if ($comments->children) { $comments->threadedComments($options);} ?>
</div>
</li>
<?php } ?>
<?php $comments->listComments(); ?>
<?php $comments->pageNav('', ''); ?>
<!-- 加载更多按钮,仅PC端显示,移动端隐藏 -->
<div class="load" id="load-more-comments">加载更多评论</div>
<!-- 原有加载提示 -->
<div id="loading-spinner" style="display: none;">
<div class="spinner"></div><span>加载中...</span>
</div>
<div class="load" id="no-more" style="display: none;">— 已加载全部评论 —</div>
<?php endif; ?>
<!--评论列表end-->
</div>
================================================
FILE: footer.php
================================================
<div class="footer pc">
<div class="navigation"><!--底部菜单导航-->
<?php if ($menu = CustomMenu()): ?>
<?php echo $menu['noIcon']; ?>
<?php endif; ?>
<!--如果需要仅在网站底部额外增加页面路径,则按照以下格式增加即可:
<a href="/archives">归档</a>
-->
</div>
<div class="copyright">
Copyright©<?php if (!empty($this->options->Webtime)): echo $this->options->Webtime().'-'; ?><?php endif; ?><?php echo date('Y'); ?> All Rights Reserved. Load:<?php echo timer_stop();?><br>
<?php if (!empty($this->options->WA)): ?>
<img src="<?php $this->options->themeUrl('/static/img/beian.png'); ?>"/><a href="https://beian.mps.gov.cn" rel="nofollow noreferrer" target="_blank"><?php $this->options->WA(); ?></a>
<?php endif; ?>
<?php if (!empty($this->options->ICP)): ?>
<a href="https://beian.miit.gov.cn/" target="_blank" rel="nofollow noreferrer"><?php $this->options->ICP(); ?></a><br>
<?php endif; ?>
Theme by <a id="copyright-pc" href="https://docs.onenote.io" title="自豪地使用OneBlog主题" target="_blank">OneBlog</a> V<?php echo parseThemeVersion();?>
<div class="switch">
<span>夜间模式</span>
<input type="checkbox" id="night2" class="night-toggle">
<label for="night2" class="switchBtn"></label>
</div>
</div>
<div class="contact">
<?php if (!empty($this->options->QQ)): ?>
<a id="qq" title="QQ"><i class="iconfont icon-qq"></i></a>
<?php endif; ?>
<?php if (!empty($this->options->Weixin)): ?>
<a id="wxmp" title="微信公众号"><i class="iconfont icon-wechat"></i></a>
<?php endif; ?>
<?php if (!empty($this->options->Email)): ?>
<a id="tomail" title="博主邮箱"><i class="iconfont icon-mail"></i></a>
<?php endif; ?>
<?php if (!empty($this->options->Github)): ?>
<a href="<?php $this->options->Github();?>" target="_blank" title="Github"><i class="iconfont icon-github"></i></a>
<?php endif; ?>
</div>
</div>
<?php $this->footer();?>
<script src="https://cncdn.cc/jquery/3.7.1/dist/jquery.min.js"></script><!--基础依赖放在最前面-->
<script src="https://cncdn.cc/@fancyapps/fancybox/3.5.7/dist/jquery.fancybox.min.js"></script><!--图片灯箱效果-->
<script src="https://cncdn.cc/layer/3.1.1/layer.js"></script>
<?php if ($this->is('index')):?>
<script src="https://cncdn.cc/swiper/8.3.2/swiper-bundle.min.js"></script>
<script>
var bannerSwitch = '<?= $this->options->switch === 'on' ? 'on' : 'off' ?>';
</script>
<?php endif;?>
<?php if ($this->is('post') || $this->is('page')): ?>
<?php if ($this->options->BeCode == 'on'):?>
<!--代码高亮逻辑-->
<script src="https://cncdn.cc/highlightjs/cdn-release/11.11.1/build/highlight.min.js"></script>
<script defer>
document.addEventListener('DOMContentLoaded', function () {
const codeBlocks = document.querySelectorAll('pre code');
const observer = new IntersectionObserver(function (entries) {
entries.forEach(function (entry) {
if (entry.isIntersecting) {
hljs.highlightElement(entry.target);
observer.unobserve(entry.target);
}
});
}, {
rootMargin: '0px',
threshold: 0.1 // 当代码块进入视口 10% 时触发,减少资源占用
});
codeBlocks.forEach(function (codeBlock) {
observer.observe(codeBlock);
codeBlock.style.filter = 'none'; // 显示高亮后的代码块
});
});
</script>
<?php endif;?>
<!--表情支持-->
<script src="<?php $this->options->themeUrl('/static/js/emoji.js'); ?>"></script>
<?php if ($this->options->GeetestID): ?>
<!--人机验证-->
<script src="https://static.geetest.com/v4/gt4.js"></script>
<?php endif;?>
<!--评论无限加载js-->
<script src="<?php $this->options->themeUrl('/static/js/comments.js?v=3.7.0'); ?>"></script>
<?php endif;?>
<script src="<?php $this->options->themeUrl('/static/js/main.js?v=3.7.0'); ?>"></script><!--主题js-->
<!-- 版权信息 -->
<div id="copyright-info" style="display: none;">
<p>开源不易,请尊重作者版权,保留基本的版权信息。</p>
</div>
<script type="text/javascript">
$(document).on('click', '#qq', function() {layer.msg('<?php $this->options->QQ();?>',{time:4000});});
$(document).on('click', '#wxmp', function() {layer.open({type: 1,title: false,closeBtn: 0,shadeClose: true,skin: 'layui-layer-nobg',area: ['auto'], content: '<img id= "mywxmp" style="width:20rem;height:20rem;display:block;" src="<?php $this->options->Weixin();?>">'});});
$(document).on('click', '#tomail', function() {layer.msg('联系邮箱:<?php $this->options->Email();?>',{time:4000});});
</script>
<!--自定义JS代码-->
<?php if (!empty($this->options->JS)): ?>
<?php $this->options->JS();?>
<?php endif; ?>
</div>
</body>
</html>
================================================
FILE: functions.php
================================================
<?php if (!defined('__TYPECHO_ROOT_DIR__')) exit;
/**
* Theme:OneBlog
* Updated: 2026-05-02
* Author: ©彼岸临窗 onenote.io
* 注释含命名规范,开源不易,如需引用请注明来源:彼岸临窗 https://onenote.io。
* 本主题已取得软件著作权(登记号:2025SR0334142)和外观设计专利(专利号:第7121519号),请严格遵循GPL-2.0协议使用本主题及源码。
*/
//主题版本号自动获取
function parseThemeVersion() {
$indexFile = __DIR__ . '/index.php';
$content = file_get_contents($indexFile);
preg_match('/\* @version\s+([0-9.]+)/', $content, $matches);
return $matches[1] ?? '1.0.0';
}
// 自定义字体,可自行拓展
function oneblogFonts() {
return [
'default' => [
'name' => 'Default',
'css' => '',
'family' => ''
],
'hwmct' => [
'name' => '汇文明朝体',
'css' => 'https://chinese-fonts-cdn.konghayao.deno.net/packages/hwmct/dist/%E6%B1%87%E6%96%87%E6%98%8E%E6%9C%9D%E4%BD%93/result.css',
'family' => 'Huiwen-mincho'
],
'syst' => [
'name' => '思源宋体',
'css' => 'https://chinese-fonts-cdn.konghayao.deno.net/packages/syst/dist/SourceHanSerifCN/result.css',
'family' => 'Source Han Serif CN VF'
],
'stdgt' => [
'name' => '上图东观体',
'css' => 'https://chinese-fonts-cdn.konghayao.deno.net/packages/stdgt/dist/上图东观体-常规/result.css',
'family' => 'STDongGuanTi'
],
'xwwk' => [
'name' => '霞鹜文楷',
'css' => 'https://chinese-fonts-cdn.konghayao.deno.net/packages/lxgwwenkaibright/dist/LXGWBright-Medium/result.css',
'family' => 'LXGW Bright Medium'
],
'xwxzs' => [
'name' => '霞鹜新致宋',
'css' => 'https://chinese-fonts-cdn.konghayao.deno.net/packages/LxgwNeoZhiSong/dist/LXGWNeoZhiSong/result.css',
'family' => 'LXGW Neo ZhiSong'
],
'yxzk' => [
'name' => '原俠正楷',
'css' => 'https://chinese-fonts-cdn.konghayao.deno.net/packages/GuanKiapTsingKhai/dist/GuanKiapTsingKhai/result.css',
'family' => 'GuanKiapTsingKhai'
],
'yxk' => [
'name' => '月星楷',
'css' => 'https://chinese-fonts-cdn.konghayao.deno.net/packages/moon-stars-kai/dist/MoonStarsKai-Regular/result.css',
'family' => 'Moon Stars Kai'
],
'yjyhpws' => [
'name' => '极影毁片文宋',
'css' => 'https://chinese-fonts-cdn.konghayao.deno.net/packages/jyhpws/dist/极影毁片文宋/result.css',
'family' => '极影毁片文宋 Medium'
]
];
}
// 获取当前设置的网站字体
function oneblogFontSet() {
$fonts = oneblogFonts();
$key = Helper::options()->FontFamily ?: 'default';
return $fonts[$key] ?? $fonts['default'];
}
// 生成 sitemap.xml
function oneblogSitemapBuild() {
$options = Helper::options();
$db = Typecho_Db::get();
$siteUrl = rtrim($options->siteUrl, '/');
$output = rtrim(__TYPECHO_ROOT_DIR__, '/\\') . '/sitemap.xml';
$seen = [];
$xml = new DOMDocument('1.0', 'UTF-8');
$xml->formatOutput = true;
$urlset = $xml->createElement('urlset');
$urlset->setAttribute('xmlns', 'http://www.sitemaps.org/schemas/sitemap/0.9');
$urlset->setAttribute('xmlns:image', 'http://www.google.com/schemas/sitemap-image/1.1');
$xml->appendChild($urlset);
oneblogSitemapAddUrl($xml, $urlset, $seen, $siteUrl . '/', time(), 'daily', '1.0');
$posts = $db->fetchAll(
$db->select('cid', 'slug', 'created', 'modified', 'text')
->from('table.contents')
->where('type = ?', 'post')
->where('status = ?', 'publish')
->where('(password IS NULL OR password = ?) ', '')
->order('created', Typecho_Db::SORT_DESC)
->limit(10000)
);
$thumbs = oneblogSitemapThumbs(array_column($posts, 'cid'));
foreach ($posts as $row) {
$postPermalink = Typecho_Router::url('post', $row, $options->index);
$postPermalink = oneblogSitemapUrl($postPermalink, $siteUrl);
$image = oneblogSitemapUrl((string) showThumbnail(oneblogSitemapWidget($row, $thumbs[$row['cid']] ?? ''), false), $siteUrl);
oneblogSitemapAddUrl($xml, $urlset, $seen, $postPermalink, oneblogSitemapLastmod($row), 'weekly', '0.8', $image ? [$image] : []);
}
$pages = $db->fetchAll(
$db->select('cid', 'slug', 'created', 'modified', 'text')
->from('table.contents')
->where('type = ?', 'page')
->where('status = ?', 'publish')
->where('(password IS NULL OR password = ?) ', '')
->order('created', Typecho_Db::SORT_DESC)
);
$thumbs = oneblogSitemapThumbs(array_column($pages, 'cid'));
foreach ($pages as $row) {
$pagePermalink = Typecho_Router::url('page', $row, $options->index);
$pagePermalink = oneblogSitemapUrl($pagePermalink, $siteUrl);
$image = oneblogSitemapUrl((string) showThumbnail(oneblogSitemapWidget($row, $thumbs[$row['cid']] ?? ''), false), $siteUrl);
oneblogSitemapAddUrl($xml, $urlset, $seen, $pagePermalink, oneblogSitemapLastmod($row), 'monthly', '0.7', $image ? [$image] : []);
}
foreach (oneblogSitemapMetas('category') as $row) {
$categoryPermalink = Typecho_Router::url('category', $row, $options->index);
oneblogSitemapAddUrl($xml, $urlset, $seen, oneblogSitemapUrl($categoryPermalink, $siteUrl), oneblogSitemapLastmod($row), 'weekly', '0.6');
}
foreach (oneblogSitemapMetas('tag') as $row) {
$tagPermalink = Typecho_Router::url('tag', $row, $options->index);
oneblogSitemapAddUrl($xml, $urlset, $seen, oneblogSitemapUrl($tagPermalink, $siteUrl), oneblogSitemapLastmod($row), 'weekly', '0.5');
}
$saved = $xml->save($output) !== false;
if ($saved) {
@file_put_contents(oneblogSitemapMetaPath(), oneblogSitemapSign());
@touch(oneblogSitemapCheckPath());
}
return $saved;
}
// 对搜索引擎友好的链接结构和格式(含缩略图)
function oneblogSitemapAddUrl($xml, $urlset, &$seen, $loc, $lastmod, $changefreq, $priority, $images = []) {
$loc = oneblogSitemapCleanUrl($loc);
if ($loc === '' || isset($seen[$loc])) {
return;
}
$seen[$loc] = true;
$url = $xml->createElement('url');
$locNode = $xml->createElement('loc');
$locNode->appendChild($xml->createTextNode($loc));
$url->appendChild($locNode);
$url->appendChild($xml->createElement('lastmod', oneblogSitemapFormatLastmod($lastmod)));
$url->appendChild($xml->createElement('changefreq', $changefreq));
$url->appendChild($xml->createElement('priority', $priority));
foreach (array_unique(array_filter($images)) as $image) {
$image = oneblogSitemapCleanUrl($image);
if ($image === '') continue;
$imageNode = $xml->createElementNS('http://www.google.com/schemas/sitemap-image/1.1', 'image:image');
$imageLoc = $xml->createElementNS('http://www.google.com/schemas/sitemap-image/1.1', 'image:loc');
$imageLoc->appendChild($xml->createTextNode($image));
$imageNode->appendChild($imageLoc);
$url->appendChild($imageNode);
}
$urlset->appendChild($url);
}
// 生成绝对路径的url
function oneblogSitemapUrl($url, $siteUrl) {
$url = trim((string) $url);
if ($url === '') return '';
if (strpos($url, '//') === 0) {
return (parse_url($siteUrl, PHP_URL_SCHEME) ?: 'https') . ':' . $url;
}
if (preg_match('#^https?://#i', $url)) {
return $url;
}
return rtrim($siteUrl, '/') . '/' . ltrim($url, '/');
}
// 清理和验证URL,确保其为有效的绝对URL
function oneblogSitemapCleanUrl($url) {
$url = trim((string) $url);
if ($url === '') return '';
$url = str_replace('&', '&', $url);
return preg_match('#^https?://#i', $url) ? $url : '';
}
// 获取内容的最后修改时间,优先使用 modified 字段,如果没有则使用 created 字段
function oneblogSitemapLastmod($row) {
$modified = isset($row['modified']) ? (int) $row['modified'] : 0;
$created = isset($row['created']) ? (int) $row['created'] : 0;
return $modified > 0 ? $modified : $created;
}
// 将时间戳格式化为符合 sitemap 要求的 ISO 8601 格式
function oneblogSitemapFormatLastmod($time) {
$time = (int) $time;
return date('c', $time > 0 ? $time : time());
}
// 为 sitemap 复用 showThumbnail() 构造轻量内容对象
function oneblogSitemapWidget($row, $thumb = '') {
return (object) [
'content' => $row['text'] ?? '',
'fields' => (object) ['thumb' => $thumb]
];
}
// 批量预取自定义字段 thumb,供 sitemap 复用 showThumbnail() 时避免逐篇查询
function oneblogSitemapThumbs($cids) {
$cids = array_values(array_unique(array_filter(array_map('intval', (array) $cids))));
if (empty($cids)) return [];
$db = Typecho_Db::get();
$prefix = $db->getPrefix();
$rows = $db->fetchAll($db->query(
'SELECT cid, str_value FROM `' . $prefix . 'fields` WHERE name = ' . oneblogSitemapQuote('thumb') . ' AND cid IN (' . implode(',', $cids) . ')'
));
$siteUrl = rtrim(Helper::options()->siteUrl, '/');
$thumbs = [];
foreach ($rows as $row) {
if (empty($row['str_value'])) continue;
$thumbs[(int) $row['cid']] = oneblogSitemapUrl($row['str_value'], $siteUrl);
}
return $thumbs;
}
// 批量获取分类或标签的相关信息和最后修改时间,用于 sitemap 的分类和标签列表
function oneblogSitemapMetas($type) {
$type = $type === 'tag' ? 'tag' : 'category';
$db = Typecho_Db::get();
$prefix = $db->getPrefix();
return $db->fetchAll($db->query(
'SELECT m.mid, m.name, m.slug, m.type, MAX(c.modified) AS modified, MAX(c.created) AS created, COUNT(c.cid) AS post_count '
. 'FROM `' . $prefix . 'metas` m '
. 'INNER JOIN `' . $prefix . 'relationships` r ON m.mid = r.mid '
. 'INNER JOIN `' . $prefix . 'contents` c ON r.cid = c.cid '
. 'WHERE m.type = ' . oneblogSitemapQuote($type) . ' '
. 'AND c.type = ' . oneblogSitemapQuote('post') . ' '
. 'AND c.status = ' . oneblogSitemapQuote('publish') . ' '
. 'AND (c.password IS NULL OR c.password = ' . oneblogSitemapQuote('') . ') '
. 'GROUP BY m.mid, m.name, m.slug, m.type '
. 'ORDER BY modified DESC, created DESC'
));
}
// 检测内容是否变化以决定是否需要更新 sitemap
function oneblogSitemapMetaPath() {
return rtrim(__TYPECHO_ROOT_DIR__, '/\\') . '/sitemap.meta';
}
// 控制 sitemap 检测频率,避免频繁检测导致性能问题
function oneblogSitemapCheckPath() {
return rtrim(__TYPECHO_ROOT_DIR__, '/\\') . '/sitemap.check';
}
// 生成签名,避免每次都进行全文比较
function oneblogSitemapSign() {
static $sign = null;
if ($sign !== null) return $sign;
$db = Typecho_Db::get();
$prefix = $db->getPrefix();
$contents = $db->fetchRow($db->query(
'SELECT COUNT(*) AS total, '
. 'MAX(CASE WHEN modified > 0 THEN modified ELSE created END) AS latest, '
. 'GROUP_CONCAT(cid ORDER BY cid) AS cids '
. 'FROM `' . $prefix . 'contents` '
. 'WHERE (type = ' . oneblogSitemapQuote('post') . ' OR type = ' . oneblogSitemapQuote('page') . ') '
. 'AND status = ' . oneblogSitemapQuote('publish') . ' '
. 'AND (password IS NULL OR password = ' . oneblogSitemapQuote('') . ')'
));
$metas = $db->fetchRow($db->query(
'SELECT COUNT(DISTINCT m.mid) AS total, GROUP_CONCAT(DISTINCT m.mid ORDER BY m.mid) AS mids '
. 'FROM `' . $prefix . 'metas` m '
. 'INNER JOIN `' . $prefix . 'relationships` r ON m.mid = r.mid '
. 'INNER JOIN `' . $prefix . 'contents` c ON r.cid = c.cid '
. 'WHERE (m.type = ' . oneblogSitemapQuote('category') . ' OR m.type = ' . oneblogSitemapQuote('tag') . ') '
. 'AND c.type = ' . oneblogSitemapQuote('post') . ' '
. 'AND c.status = ' . oneblogSitemapQuote('publish') . ' '
. 'AND (c.password IS NULL OR c.password = ' . oneblogSitemapQuote('') . ')'
));
return $sign = md5(json_encode([$contents, $metas]));
}
// 安全地引用字符串,避免SQL注入风险
function oneblogSitemapQuote($value) {
return "'" . str_replace("'", "''", (string) $value) . "'";
}
// 更新 sitemap.xml 文件,如果发生异常则记录错误日志
function oneblogSitemapUpdate() {
try {
return oneblogSitemapBuild();
} catch (Throwable $e) {
error_log('OneBlog sitemap update failed: ' . $e->getMessage());
} catch (Exception $e) {
error_log('OneBlog sitemap update failed: ' . $e->getMessage());
}
return false;
}
// 需要时触发更新
function oneblogSitemapCheck() {
static $checked = false;
if ($checked) return;
$checked = true;
$checkPath = oneblogSitemapCheckPath();
if (file_exists($checkPath) && time() - (int) filemtime($checkPath) < oneblogSitemapInterval()) {
return;
}
@touch($checkPath);
$sitemapPath = rtrim(__TYPECHO_ROOT_DIR__, '/\\') . '/sitemap.xml';
if (!file_exists($sitemapPath)) {
oneblogSitemapQueue();
return;
}
try {
$metaPath = oneblogSitemapMetaPath();
if (!file_exists($metaPath) || trim((string) @file_get_contents($metaPath)) !== oneblogSitemapSign()) {
oneblogSitemapQueue();
}
} catch (Throwable $e) {
error_log('OneBlog sitemap stale check failed: ' . $e->getMessage());
} catch (Exception $e) {
error_log('OneBlog sitemap stale check failed: ' . $e->getMessage());
}
}
// 将更新任务加入队列,在脚本结束时统一执行,避免重复更新和性能问题
function oneblogSitemapQueue() {
static $registered = false;
$GLOBALS['oneblog_sitemap_needs_update'] = true;
if (!$registered) {
register_shutdown_function('oneblogSitemapShutdown');
$registered = true;
}
}
// 在脚本结束时检查是否需要更新 sitemap,如果需要则执行更新
function oneblogSitemapShutdown() {
if (!empty($GLOBALS['oneblog_sitemap_needs_update']) && !oneblogSitemapUpdate()) {
@unlink(oneblogSitemapCheckPath());
}
}
// 获取 sitemap 检测更新的时间间隔,默认为 86400 秒(24小时),可以通过后台设置调整,但不允许小于 1 秒以避免性能问题
function oneblogSitemapInterval() {
$interval = (int) (Helper::options()->sitemapInterval ?: 86400);
return $interval > 0 ? $interval : 86400;
}
oneblogSitemapCheck();
//主题自定义
function themeConfig($form) {
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['_ajax'])) {
if (ob_get_length()) ob_clean();
header('Content-Type:application/json; charset=utf-8');
$theTheme = 'OneBlog';
$db = Typecho_Db::get();
$uploadDir = Helper::options()->uploadDir ?: 'usr/uploads';
$uploadDir = rtrim($uploadDir, '/\\');
$absUploadDir = __TYPECHO_ROOT_DIR__ . '/' . $uploadDir;
if (!is_dir($absUploadDir)) {
@mkdir($absUploadDir, 0755, true);
}
$backPath = $absUploadDir . '/BackupSetting_' . $theTheme . '.txt';
$ret = ['success'=>false, 'message'=>'未知错误'];
if ($_POST['action'] === 'oneblog_theme_backup') {
$themeConfStr = $db->fetchRow($db->select()->from('table.options')->where('name = ?', 'theme:' . $theTheme))['value'];
$ok = file_put_contents($backPath, $themeConfStr);
$ret = $ok !== false
? ['success'=>true, 'message'=>'备份成功']
: ['success'=>false, 'message'=>'备份失败,uploads 目录不可写'];
} elseif ($_POST['action'] === 'oneblog_theme_restore') {
if (file_exists($backPath)) {
$str = file_get_contents($backPath);
$updateThemeConQuery = $db->update('table.options')->rows(['value'=>$str])->where('name=?', 'theme:' . $theTheme);
$ok = $db->query($updateThemeConQuery);
$ret = $ok !== false
? ['success'=>true, 'message'=>'恢复成功']
: ['success'=>false, 'message'=>'恢复失败,数据库操作异常'];
} else {
$ret = ['success'=>false, 'message'=>'未找到备份文件,无法恢复'];
}
}
echo json_encode($ret);
exit;
}
$theTheme = 'OneBlog';
$db = Typecho_Db::get();
$uploadDir = Helper::options()->uploadDir ?: 'usr/uploads';
$uploadDir = rtrim($uploadDir, '/\\');
$absUploadDir = __TYPECHO_ROOT_DIR__ . '/' . $uploadDir;
if (!is_dir($absUploadDir)) {
@mkdir($absUploadDir, 0755, true);
}
$backPath = $absUploadDir . '/BackupSetting_' . $theTheme . '.txt';
$themeConfStr = $db->fetchRow($db->select()->from('table.options')->where('name = ?', 'theme:' . $theTheme))['value'];
$backstr = file_exists($backPath) ? file_get_contents($backPath) : '';?>
<link rel="stylesheet" href="https://cncdn.cc/oneblog/3.7.0/admin.css" type="text/css" />
<script src="https://cncdn.cc/jquery/3.7.1/dist/jquery.min.js" type="text/javascript"></script>
<script src="https://cncdn.cc/layer/3.1.1/layer.js" type="text/javascript"></script>
<script>
window.oneblogFontConfigs = <?php echo json_encode(oneblogFonts(), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); ?>;
</script>
<script src="<?php echo Helper::options()->themeUrl('static/js/admin.js'); ?>" type="text/javascript"></script>
<div class="OneBlog"><h3>OneBlog 主题设置</h3></div>
<div id="tab-container">
<ul id="tab-nav"></ul>
<div id="tab-content">
<div id="tab1" class="tab-pane active">
<h2>OneBlog V<?php echo parseThemeVersion();?></h2>
<p>本主题精心打磨多年,且持续优化,现免费开源,致敬互联网社区开源精神,也致敬热爱生活和记录的我们。</p>
<p>使用教程请前往<b></b>主题文档</b>:<a href="https://docs.onenote.io" target="_blank">docs.onenote.io</a> 获取,主题最新版本请前往Github仓库:<a href="https://github.com/cnxcdn/OneBlog" target="_blank">OneBlog(最新)</a> 或 <a href="https://gitcode.com/cncdn/OneBlog" target="_blank">国内镜像仓库(延迟一天同步)</a>查看,记得★Star,既是对作者的支持,也方便记住来时的路。本主题几乎所有代码都清晰地注释了,因此博友们完全可以以OneBlog为基础二次开发或单独开发属于自己的主题,但希望大家注明来源,保留基本的版权信息。</p>
<p>本主题目前仅有QQ交流群:<b>939170079</b>,其他均不是官方群组,此外,还可以通过<a href="https://litebbs.com" target="_blank">LiteBBS</a>讨论交流。本主题的介绍、后续的更新或周边插件的开发更新,除了QQ群公告,还会同步发布在<a href="https://litebbs.com" target="_blank">LiteBBS</a>,欢迎大家参与讨论。</p>
<div class="backup">
<div class="backup-listen">
<b>主题设置备份与恢复:</b>
<?php if (strcmp($backstr, $themeConfStr) === 0): ?>
当前的配置信息与备份信息一致,无需备份或恢复。
<?php else: ?>
未备份或备份数据与当前设置不一致。<br>
<div class="backupbtn">若以备份数据为准,建议:<button id="restorebtn" type="button">恢复主题配置</button></div>
<div class="backupbtn">若以当前设置为准,建议:<button id="backupbtn" type="button">备份主题配置</button></div>
<?php endif; ?>
</div>
</div>
<p>主题图标库(可直接引用,如 "iconfont icon-home"):</p>
<div class="icon-list" id="iconList"></div>
</div>
</div>
</div>
<?php
//—————————————————————————————————————— 基础设置 ——————————————————————————————————————
//LOGO风格
$logoStyle = new Typecho_Widget_Helper_Form_Element_Radio('logoStyle', array('text' => '文字logo','logo' => '图片logo'),'text', 'LOGO风格', '选择文字风格则logo处显示网站名称,选择图片风格则需要在下方设置logo地址');
$form->addInput($logoStyle);
//网站logo
$logo = new Typecho_Widget_Helper_Form_Element_Text('logo', NULL, NULL, _t('深色版LOGO'), _t('请输入深色logo图片的url,填写后会显示在PC首页和移动端顶栏,建议尺寸:300×83'));
$form->addInput($logo);
//夜间模式下的logo
$logoWhite = new Typecho_Widget_Helper_Form_Element_Text('logoWhite', NULL, NULL, _t('浅色版LOGO'), _t('请输入浅白色logo图片的url,填写后会显示在黑夜模式下的移动端顶栏,建议尺寸:300×83'));
$form->addInput($logoWhite);
//网站slogan
$slogan = new Typecho_Widget_Helper_Form_Element_Text('slogan', NULL, NULL, _t('网站slogan'), _t('一句话介绍网站,填写后会显示在独立页面的顶栏和首页的标题中。'));
$form->addInput($slogan);
//网站favicon
$Favicon = new Typecho_Widget_Helper_Form_Element_Text('Favicon', NULL, NULL, _t('Favicon'), _t('请输入网站favicon图片的url。'));
$form->addInput($Favicon);
//自定义菜单
$MenuSet = new Typecho_Widget_Helper_Form_Element_Textarea('MenuSet',NULL,NULL,_t('自定义菜单'),_t('每行一个菜单项,菜单项的参数用英文逗号隔开。格式:菜单项名称,链接,图标类名<br>示例:<br>首页,/,iconfont icon-home<br>相册,/photos,iconfont icon-pic')
);
$form->addInput($MenuSet);
//首页杂志效果开关
$switch = new Typecho_Widget_Helper_Form_Element_Radio('switch', array('on' => '显示','off' => '不显示'),'on', '首页是否显示Banner文章', '选择开启则需要填写下方的文章cid;PC端会在首页顶部显示杂志效果文章,移动端会在首页顶部显示幻灯片自动切换。');
$form->addInput($switch);
//首页杂志效果文章
$Banner = new Typecho_Widget_Helper_Form_Element_Text('Banner', NULL, NULL, _t('首页banner文章cid'), _t('用英文逗号隔开,限3个,填需要显示在banner区域三篇文章的cid。'));
$form->addInput($Banner);
//移动端标签归档页背景图
$Tagbg = new Typecho_Widget_Helper_Form_Element_Text('Tagbg', NULL, NULL, _t('标签页背景图'), _t('填写后会在移动端标签归档页的顶部背景区域(分类归档页的背景图片直接在分类描述中填写图片链接即可)。'));
$form->addInput($Tagbg);
//网站标识图
$Webthumb = new Typecho_Widget_Helper_Form_Element_Text('Webthumb', NULL, NULL, _t('网站标识图'), _t('请填写图片地址,用于SEO优化,建议尺寸:1280×720'));
$form->addInput($Webthumb);
//建站年份
$Webtime = new Typecho_Widget_Helper_Form_Element_Text('Webtime', NULL, NULL, _t('建站年份'), _t('填写后显示在网站底栏,格式:2016,如果是今年刚建站,请勿填写。'));
$form->addInput($Webtime);
//ICP备案号
$ICP = new Typecho_Widget_Helper_Form_Element_Text('ICP', NULL, NULL, _t('ICP备案号'), _t('如需要显示,请填写网站备案号。'));
$form->addInput($ICP);
//公安备案号
$WA = new Typecho_Widget_Helper_Form_Element_Text('WA', NULL, NULL, _t('公安备案号'), _t('如需要显示,请填写公安备案号,跳转链接请自行在footer.php中修改。'));
$form->addInput($WA);
//—————————————————————————————————————— 高级设置 ——————————————————————————————————————
// 添加自定义 DNS 预解析域名字段
$dnsPrefetch = new Typecho_Widget_Helper_Form_Element_Textarea('dnsPrefetch',NULL,NULL,_t('DNS预解析域名'),_t('请输入需要预解析的域名,每行一个。例如:<br>https://onenote.io<br>https://cdn.onenote.io')
);
$form->addInput($dnsPrefetch);
// Sitemap 检测更新频率
$sitemapInterval = new Typecho_Widget_Helper_Form_Element_Text('sitemapInterval', NULL, '86400', _t('Sitemap检测更新频率'), _t('单位为秒。填写 10 则每 10 秒检测一次公开内容是否变化;留空或小于 1 时默认 86400 秒。'));
$form->addInput($sitemapInterval);
// 缩略图参数
$imgSmall = new Typecho_Widget_Helper_Form_Element_Text('imgSmall', NULL, NULL, _t('缩略图参数'), _t('填写服务端支持的缩略图参数(如 !small),需搭配 CDN 或云存储图片处理功能使用。<br>留空则显示原图,填写后文章列表的缩略图会自动携带该参数,请确保文章内的图片支持缩略图处理。'));
$form->addInput($imgSmall);
// 代码块美化
$BeCode = new Typecho_Widget_Helper_Form_Element_Radio('BeCode', array('on' => '开启','off' => '不开启'),'on','代码块美化', '默认开启,开启后会美化代码区域,技术博客请开启,否则代码块会显示异常,纯生活记录类博客建议关闭。');
$form->addInput($BeCode);
// 随机高清文艺图片源
$RandomIMG = new Typecho_Widget_Helper_Form_Element_Radio('RandomIMG', array('oneblog' => '主题图库','off' => '关闭'),'off','随机高清缩略图', '设置后文章列表页在文章没有任何图片且没有单独设置封面时显示随机缩略图,如果想让文章详情页显示封面图,请编辑文章时填写自定义字段[文章封面]。');
$form->addInput($RandomIMG);
// 自动夜间模式
$AutoNightMode = new Typecho_Widget_Helper_Form_Element_Radio('AutoNightMode', array('on' => '开启','off' => '关闭'),'off','自动夜间模式', '开启后,网站将在每天 19:00 至次日 05:00 自动启用夜间模式,其他时间自动关闭。');
$form->addInput($AutoNightMode);
// Cloudflare Turnstile 前端 Site Key
$cfSiteKey = new Typecho_Widget_Helper_Form_Element_Text('CFSiteKey', NULL, '', _t('Cloudflare Turnstile SiteKey'), _t('填写 Turnstile 的 sitekey(用于前端)。留空则不启用 CF。'));
$form->addInput($cfSiteKey);
// Cloudflare Turnstile Secret(用于服务器端验证)
$cfSecret = new Typecho_Widget_Helper_Form_Element_Text('CFSecret', NULL, '', _t('Cloudflare Turnstile Secret'), _t('填写 Turnstile 的 secret(用于服务器端验证)。请妥善保管,不要公开。'));
$form->addInput($cfSecret);
// 评论极验验证
$GeetestID = new Typecho_Widget_Helper_Form_Element_Text('GeetestID', NULL, NULL, _t('极验ID'), _t('如需开启评论提交前的极验验证,请填写极验后台生成的 验证ID'));
$form->addInput($GeetestID);
$GeetestKEY = new Typecho_Widget_Helper_Form_Element_Text('GeetestKEY', NULL, NULL, _t('极验KEY'), _t('如需开启评论提交前的极验验证,请填写极验后台生成的 验证KEY'));
$form->addInput($GeetestKEY);
//—————————————————————————————————————— 社交按钮 ——————————————————————————————————————
$QQ = new Typecho_Widget_Helper_Form_Element_Text('QQ', NULL, NULL, _t('QQ'), _t('请填写完整的QQ群描述或QQ号描述,输入的内容会直接作为弹框消息显示。'));
$form->addInput($QQ);
$Weixin = new Typecho_Widget_Helper_Form_Element_Text('Weixin', NULL, NULL, _t('微信公众号'), _t('请填写微信公众号或个人微信的二维码图片url,格式为:https://。'));
$form->addInput($Weixin);
$Email = new Typecho_Widget_Helper_Form_Element_Text('Email', NULL, NULL, _t('邮箱'), _t('请填写站长邮箱。'));
$form->addInput($Email);
$Github = new Typecho_Widget_Helper_Form_Element_Text('Github', NULL, NULL, _t('Github'), _t('请填写Github地址。'));
$form->addInput($Github);
//—————————————————————————————————————— 自定义样式 ——————————————————————————————————————
// 网站字体
$fontOptions = [];
foreach (oneblogFonts() as $key => $font) {
$fontOptions[$key] = $font['name'];
}
$FontFamily = new Typecho_Widget_Helper_Form_Element_Radio('FontFamily', $fontOptions, 'default', _t('文章字体'), _t('选择后将在文章列表页和详情页应用该字体。'));
$form->addInput($FontFamily);
// 自定义CSS
$CSS = new Typecho_Widget_Helper_Form_Element_Textarea('CSS',NULL,NULL,_t('自定义CSS'),_t('可以填写css,覆盖默认的样式,本css优先级最高。')
);
$form->addInput($CSS);
// 自定义JS
$JS = new Typecho_Widget_Helper_Form_Element_Textarea('JS',NULL,NULL,_t('自定义JS'),_t('请输入自定义js代码,填写后会直接加载至页脚。')
);
$form->addInput($JS);
// 自定义主题色
$themeColor = new Typecho_Widget_Helper_Form_Element_Text('themeColor',NULL,'#ff5050',_t('主题色'),_t('请选择主题色调。')
);
$form->addInput($themeColor);
}
//文章自定义字段
function themeFields($layout) { ?>
<link rel="stylesheet" href="https://cncdn.cc/oneblog/3.7.0/admin.css" type="text/css" />
<?php
$thumb = new Typecho_Widget_Helper_Form_Element_Text('thumb', NULL, NULL, _t('封面图片'), _t('此处填写后会让文章/独立页面详情样式显示为有封面图的样式效果,文章列表也会出现封面缩略图,搜索引擎抓取的也是该封面图。'));
$thumb->input->setAttribute('class', 'full-width-input');
$layout->addItem($thumb);
$origin = new Typecho_Widget_Helper_Form_Element_Text('origin', NULL, NULL, _t('图片来源'), _t('请输入图片的版权所有人名称或网站名(如:Unsplash)'));
$origin->input->setAttribute('class', 'full-width-input');
$layout->addItem($origin);
$author = new Typecho_Widget_Helper_Form_Element_Text('author', NULL, NULL, _t('作者'), _t('不填则默认为原创文章,作者为账号本人。'));
$author->input->setAttribute('class', 'full-width-input');
$layout->addItem($author);
}
//自定义菜单
function CustomMenu() {
$menuItems = Typecho_Widget::widget('Widget_Options')->MenuSet;
$hasIcon = '';
$noIcon = '';
if (!empty($menuItems)) {
$lines = explode("\n", $menuItems);
foreach ($lines as $line) {
$line = trim($line);
if (empty($line)) continue;
@list($name, $url, $icon, $target) = array_pad(array_map('trim', explode(',', $line)), 4, '');
if (empty($name) || empty($url)) continue;
$targetAttr = ($target === '_blank') ? ' target="_blank" rel="noopener"' : '';
$hasIcon .= sprintf(
'<li><a href="%s"%s><i class="%s"></i>%s</a></li>',//移动端菜单格式
htmlspecialchars($url),
$targetAttr,
htmlspecialchars($icon ?? ''),
htmlspecialchars($name)
);
$noIcon .= sprintf(
'<a href="%s"%s>%s</a>',//PC端底部菜单格式
htmlspecialchars($url),
$targetAttr,
htmlspecialchars($name)
);
}
}
return [
'hasIcon' => $hasIcon ? $hasIcon : '',
'noIcon' => $noIcon ? $noIcon : ''
];
}
//PC端右键菜单数据格式化
function getNZMenuData() {
static $cachedData = null; // 添加静态缓存
if ($cachedData !== null) return $cachedData;
$menuItems = Typecho_Widget::widget('Widget_Options')->MenuSet;
$data = [];
if (!empty($menuItems)) {
foreach (explode("\n", $menuItems) as $line) {
$line = trim($line);
if ($line === '') continue;
$parts = array_map('trim', explode(',', $line, 4));
if (count($parts) < 3) continue;
$name = $parts[0] ?? '';
$url = $parts[1] ?? '';
$icon = $parts[2] ?? '';
$target = ($parts[3] ?? '') === '_blank' ? '_blank' : '';
if ($name === '' || $url === '') continue;
$data[] = [
'name' => htmlspecialchars($name, ENT_QUOTES, 'UTF-8'),
'url' => htmlspecialchars($url, ENT_QUOTES, 'UTF-8'),
'icon' => htmlspecialchars($icon, ENT_QUOTES, 'UTF-8'),
'target' => $target
];
}
}
return $cachedData = $data;
}
//首页置顶banner文章 极致优化 只查询1次
function get_banner_data($options) {
if ($options->switch != 'on') return [];
$cids = array_filter(
array_slice(
preg_split('/[,\s]+/', $options->Banner ?? '', -1, PREG_SPLIT_NO_EMPTY),
0, 3
),
function($v) { return ctype_digit((string)$v) && $v > 0; }
);
if (!$cids) return [];
$db = Typecho_Db::get();
$cid_str = implode(',', $cids);
$is_mysql = stripos($db->getAdapterName(), 'mysql') !== false;
$query = $db->select()
->from('table.contents')
->where("cid IN ($cid_str)")
->where('type = ?', 'post');
if ($is_mysql) {
$query->order("FIELD(cid, $cid_str)");
}
$results = $db->fetchAll($query);
if (!$is_mysql && count($results) > 1) {
usort($results, function($a, $b) use ($cids) {
return array_search($a['cid'], $cids) - array_search($b['cid'], $cids);
});
}
return $results;
}
// 查询文章数最多的前10个标签
function getTopTags() {
$db = Typecho_Db::get();
// 直接查询前10个热门标签的名称和slug
$query = $db->select('name', 'slug')
->from('table.metas')
->where('type = ?', 'tag')
->order('count', Typecho_Db::SORT_DESC)
->limit(10);
$tags = $db->fetchAll($query);
// 输出标签链接
foreach ($tags as $tag) {
$tagUrl = Typecho_Common::url('tag/' . urlencode($tag['slug']), Helper::options()->index);
echo '<a href="' . $tagUrl . '"># ' . htmlspecialchars($tag['name']) . '</a>';
echo "\n"; // 换行分隔
}
}
//文章内图片标签自动解析为灯箱效果
//文章内图片标签自动解析为灯箱效果
function AutoLightbox($content) {
if (empty($content) || stripos($content, '<img') === false) {
return $content;
}
$pattern = '/<img\b[^>]*src=["\']([^"\']+)["\'][^>]*>/i';
return preg_replace_callback($pattern, function ($matches) {
$imgTag = $matches[0];
$src = $matches[1];
$path = parse_url($src, PHP_URL_PATH);
//.svg后缀不加灯箱效果
if ($path && strtolower(pathinfo($path, PATHINFO_EXTENSION)) === 'svg') {
return $imgTag;
}
//.no-lightbox 类排除灯箱效果
if (preg_match('/class=["\'][^"\']*\bno-lightbox\b[^"\']*["\']/i', $imgTag)) {
return $imgTag;
}
if (preg_match('/data-fancybox/i', $imgTag)) {
return $imgTag;
}
return '<a data-fancybox="gallery" href="' . htmlspecialchars($src, ENT_QUOTES) . '">' . $imgTag . '</a>';
}, $content);
}
//表情短代码解析
function parseEmojis($content) {
// null、false等都转为空字符串,防止报错
$content = (string)$content;
$emojiPath = Helper::options()->siteUrl.'usr/themes/OneBlog/static/img/emoji/';
return preg_replace_callback('/\[emoji:([a-zA-Z0-9_]+)\]/', function($matches) use ($emojiPath) {
$emojiName = $matches[1];
return '<img class="biaoqing" src="' . $emojiPath . $emojiName . '.svg" alt="' . $emojiName . '">';
}, $content);
}
//无插件阅读数,cookie保证阅读量真实性
function get_post_view($archive) {
$cid = $archive->cid;
$db = Typecho_Db::get();
$prefix = $db->getPrefix();
// 确保views字段存在
try {
$db->fetchRow($db->select()->from('table.contents')->where('cid = ?', $cid));
} catch (Typecho_Db_Exception $e) {
try {
$db->query('ALTER TABLE ' . $prefix . 'contents ADD views INT DEFAULT 0;');
} catch (Typecho_Db_Exception $e) {
// 忽略重复字段错误
}
}
// 双重验证字段
$fieldCheck = $db->fetchRow($db->select()->from('table.contents')->where('cid = ?', $cid));
if (!array_key_exists('views', $fieldCheck)) {
try {
$db->query('ALTER TABLE ' . $prefix . 'contents ADD views INT DEFAULT 0;');
} catch (Typecho_Db_Exception $e) {
echo 0;
return;
}
}
// 获取当前阅读数
$row = $db->fetchRow($db->select('views')->from('table.contents')->where('cid = ?', $cid));
$currentViews = (int) $row['views'];
$shouldCount = false;
$views = [];
if ($archive->is('single')) {
$cookieData = Typecho_Cookie::get('extend_contents_views');
$views = $cookieData ? explode(',', $cookieData) : [];
if (!in_array($cid, $views)) {
$shouldCount = true;
$db->query($db->update('table.contents')
->rows(['views' => $currentViews + 1])
->where('cid = ?', $cid));
$views[] = $cid;
Typecho_Cookie::set('extend_contents_views', implode(',', $views));
}
}
$displayViews = $currentViews + ($shouldCount ? 1 : 0);
echo formatNum($displayViews);
}
//格式化阅读数:≥1000 单位转化为k;≥10000 单位转化为W;最多显示10W+
function formatNum($num) {
if ($num >= 100000) {
$num = '10w+';
} elseif ($num >= 10000) {
$num = round($num / 10000 * 100) / 100 ;
$num = round($num,1).' w';//四舍五入保留一位小数
} elseif($num >= 1000) {
$num = round($num / 1000 * 100) / 100 ;
$num = round($num,1). ' k';//四舍五入保留一位小数
} else {
$num = $num;
}
return $num;
}
//文章发表时间格式化
function time_ago($date) {
$timestamp = strtotime($date->format('Y-m-d H:i:s'));
$current_time = time();
$time_diff = $current_time - $timestamp;
if ($time_diff < 60) {
return $time_diff . "秒前";
} elseif ($time_diff < 3600) {
return floor($time_diff / 60) . "分钟前";
} elseif ($time_diff < 86400) {
return floor($time_diff / 3600) . "小时前";
} elseif ($time_diff < 2592000) { // 30天以内
return floor($time_diff / 86400) . "天前";
} elseif ($time_diff < 31536000) { // 1年内
return floor($time_diff / 2592000) . "个月前";
} elseif ($time_diff < 94608000) { // 3年内
return floor($time_diff / 31536000) . "年前";
} else {
return $date->format('Y/m/d'); // 超过3年显示具体日期
}
}
//页面加载时间统计
function timer_start(){
global $timestart;
$mtime = explode( ' ', microtime() );
$timestart = $mtime[1] + $mtime[0];
return true;
}
timer_start();
//页面加载时间格式化为秒
function timer_stop($display = 0, $precision = 3){
global $timestart, $timeend;
$mtime = explode( ' ', microtime() );
$timeend = $mtime[1] + $mtime[0];
$timetotal = number_format( $timeend - $timestart, $precision );
$r = $timetotal < 1 ? $timetotal . " s" : $timetotal . " s";
if($display){echo $r;}
return $r;
}
//文章字数统计
function art_count ($cid){
$db=Typecho_Db::get ();
$rs=$db->fetchRow ($db->select ('table.contents.text')->from ('table.contents')->where ('table.contents.cid=?',$cid)->order ('table.contents.cid',Typecho_Db::SORT_ASC)->limit (1));
echo mb_strlen($rs['text'], 'UTF-8');
}
//评论者等级
function dengji($i) {
$db = Typecho_Db::get();
$adminAuthorId = 1;
// 如果邮箱为空,使用站长邮箱
if (empty($i)) {
$admin = $db->fetchRow($db->select('mail')->from('table.users')->where('uid = ?', $adminAuthorId));
$i = $admin ? $admin['mail'] : null;
}
// 检查邮箱是否获取成功
if (empty($i)) {
echo '<span class="level">Lv.1</span>';
return;
}
$author = $db->fetchRow($db->select('authorId')->from('table.comments')->where('mail = ?', $i)->limit(1));
$authorId = $author ? $author['authorId'] : null;
if ($authorId == $adminAuthorId) {
echo '<span class="level owner">博主</span>';
return;
}
$mail = $db->fetchRow($db->select(array('COUNT(cid)' => 'rbq'))->from('table.comments')->where('mail = ?', $i)->where('authorId = ?', '0'));
$rbq = $mail ? $mail['rbq'] : 0; // 如果没有评论就是0
if ($rbq < 3) {
echo '<span class="level">Lv.1</span>';
} elseif ($rbq < 10) {
echo '<span class="level">Lv.2</span>';
} elseif ($rbq < 20) {
echo '<span class="level">Lv.3</span>';
} elseif ($rbq < 30) {
echo '<span class="level">Lv.4</span>';
} elseif ($rbq < 40) {
echo '<span class="level">Lv.5</span>';
} else {
echo '<span class="level owner">知己</span>';
}
}
//替换默认的Gravatar头像地址为国内镜像源 QQ邮箱取用qq头像
function getGravatar($email, $s = 96, $d = 'mp', $r = 'g', $img = false, $atts = array()){
preg_match_all('/((\d)*)@qq.com/', $email, $vai);
if (empty($vai['1']['0'])) {
$url = 'https://weavatar.com/avatar/';
$url .= md5(strtolower(trim($email)));
$url .= "?s=$s&d=$d&r=$r";
if ($img) {
$url = '<img src="' . $url . '"';
foreach ($atts as $key => $val)
$url .= ' ' . $key . '="' . $val . '"';
$url .= ' />';
}
}else{
$url = 'https://q2.qlogo.cn/headimg_dl?dst_uin='.$vai['1']['0'].'&spec=100';
}
return $url;
}
//获取文章缩略图
function showThumbnail($widget, $allowRandom = true){
// 如果文章设置了缩略图,优先返回缩略图
if ($widget->fields->thumb) {
return $widget->fields->thumb;
}
// 如果文章内容有图片,返回第一张图片作为缩略图
$content = $widget->content ?? '';
preg_match_all('/<img.*?src=["\'](.*?)["\']/', $content, $matches);
if (isset($matches[1][0])) {
return $matches[1][0];
}
// 如果设置了随机缩略图
if ($allowRandom && Helper::options()->RandomIMG == 'oneblog'){
$randomParam = '?t=' . time() . rand(1, 1000);
return Helper::options()->themeUrl . '/api/img.php' . $randomParam;
}
// 如果没有任何图片,则返回NULL。
return;
}
//挂载点赞路径 + Ajax评论
function themeInit($archive) {
// 评论点赞
if ($archive->request->is("commentLike=dz")) {
commentLikes($archive);
}
// Ajax 评论
if ($archive->request->isPost() &&
!empty($_SERVER['HTTP_X_REQUESTED_WITH']) &&
strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) === 'xmlhttprequest') {
Typecho_Plugin::factory('Widget_Feedback')->finishComment = function($comment) {
if (ob_get_length()) ob_clean();
header('Content-Type: application/json; charset=UTF-8');
echo json_encode([
'success' => true,
'message' => $comment->status === 'waiting' ? '评论提交成功,请等待审核' : '评论发表成功',
'coid' => $comment->coid
], JSON_UNESCAPED_UNICODE);
exit;
};
}
}
//评论点赞 cookie保证点赞数量准确
function commentLikesNum($coid, &$record = NULL){
$db = Typecho_Db::get();
$callback = array(
'likes' => 0,
'recording' => false
);
if (array_key_exists('likes', $data = $db->fetchRow($db->select()->from('table.comments')->where('coid = ?', $coid)))) {
$callback['likes'] = $data['likes'];
} else {
$db->query('ALTER TABLE `' . $db->getPrefix() . 'comments` ADD `likes` INT(10) NOT NULL DEFAULT 0;');
}
if (empty($recording = Typecho_Cookie::get('__typecho_comment_likes_record'))) {
Typecho_Cookie::set('__typecho_comment_likes_record', '[]');
} else {
$callback['recording'] = is_array($record = json_decode($recording)) && in_array($coid, $record);
}
return $callback;
}
//评论点赞处理
function commentLikes($archive){
$archive->response->setStatus(200);
$_POST['coid'];
$_POST['behavior'];
$loginState = Typecho_Widget::widget('Widget_User')->hasLogin();
$res1 = commentLikesNum($_POST['coid'], $record);
$num = 0;
if(!empty($_POST['coid']) && !empty($_POST['behavior'])){
$db = Typecho_Db::get();
$prefix = $db->getPrefix();
$coid = (int)$_POST['coid'];
if (!array_key_exists('likes', $db->fetchRow($db->select()->from('table.comments')))) {
$db->query('ALTER TABLE `' . $prefix . 'comments` ADD `likes` INT(30) DEFAULT 0;');
}
$row = $db->fetchRow($db->select('likes')->from('table.comments')->where('coid = ?', $coid));
$updateRows = $db->query($db->update('table.comments')->rows(array('likes' => (int) $row['likes'] + 1))->where('coid = ?', $coid));
if($updateRows){
$num = $row['likes'] + 1;
$state = "success";
array_push($record, $coid);
Typecho_Cookie::set('__typecho_comment_likes_record', json_encode($record));
}else{
$num = $row['likes'];
$state = "error";
}
}else{
$state = 'Illegal request';
}
$archive->response->throwJson(array(
"state" => $state,
"num" => $num
));
}
// 微语数据加载
function MemosList($comments, $user) { ?>
<li class="animate__animated animate__fadeIn">
<div id="<?php echo $comments->theId(); ?>">
<div class="user">
<?php
$email = $comments->mail;
$imgUrl = getGravatar($email);
echo '<img class="avatar" src="' . $imgUrl . '">';
?>
<div class="user-info">
<span class="name"><?php echo $comments->author(); ?></span>
<span class="date lite-black"><?php echo $comments->date('Y-m-d H:i'); ?></span>
</div>
</div>
<?php echo $comments->content(); ?>
<?php if (isMemosImageEnabled()) : ?>
<?php $imgs = getMemosImages($comments->coid); ?>
<?php if (!empty($imgs)) : ?>
<?php $count = count($imgs); ?>
<div class="memos-img-grid grid-<?php echo $count; ?>">
<?php foreach ($imgs as $img) : ?>
<?php $thumb = getMemosThumbUrl($img); ?>
<div class="memos-img-item">
<a href="<?php echo htmlspecialchars($img); ?>"
data-fancybox="memos-<?php echo $comments->coid; ?>"
data-caption="<?php echo $comments->content(); ?>">
<img class="lazy-load"
src="<?php echo htmlspecialchars($thumb); ?>"
data-src="<?php echo htmlspecialchars($thumb); ?>">
</a>
</div>
<?php endforeach; ?>
</div>
<?php endif; ?>
<?php endif; ?>
<?php
$commentLikes = commentLikesNum($comments->coid);
$likes = $commentLikes['likes'];
$recording = $commentLikes['recording'];
?>
<div class="commentLike">
<a class="commentLikeOpt"
id="commentLikeOpt-<?php echo $comments->coid; ?>"
href="javascript:;"
data-coid="<?php echo $comments->coid; ?>"
data-recording="<?php echo $recording; ?>">
<i id="commentLikeI-<?php echo $comments->coid; ?>"
class="<?php echo $recording ? 'iconfont icon-liked red' : 'iconfont icon-like red'; ?>"></i>
<span class="lite-black" id="commentLikeSpan-<?php echo $comments->coid; ?>"><?php echo $likes; ?></span>
</a>
</div>
</div>
</li>
<?php }
/**
* 判断插件是否启用
*/
function isMemosImageEnabled()
{
$export = Typecho_Plugin::export();
return isset($export['activated']['MemosImage']);
}
/**
* 获取微语图片(插件启用且表存在时才返回)
*/
function getMemosImages($coid)
{
if (!isMemosImageEnabled()) {
return [];
}
$db = Typecho_Db::get();
$table = $db->getPrefix() . 'memos_img';
$exists = $db->fetchRow($db->query("SHOW TABLES LIKE '{$table}'"));
if (!$exists) {
return [];
}
$row = $db->fetchRow(
$db->select('imgs')->from('table.memos_img')->where('coid = ?', $coid)
);
if (!$row || empty($row['imgs'])) {
return [];
}
$imgs = json_decode($row['imgs'], true);
return is_array($imgs) ? array_slice($imgs, 0, 9) : [];
}
/**
* 生成缩略图 URL
*/
function getMemosThumbUrl($url)
{
$append = '';
try {
$plugin = Helper::options()->plugin('MemosImage');
$append = (string)($plugin->cosThumbAppend ?? '');
} catch (Throwable $e) {}
$isLocalUpload = (strpos($url, '/usr/uploads/') !== false);
if ($isLocalUpload) {
$pi = pathinfo($url);
if (empty($pi['extension'])) {
return $url;
}
return $pi['dirname'] . '/' . $pi['filename'] . '-s.' . $pi['extension'];
}
return $append !== '' ? ($url . $append) : $url;
}
//修复评论区域xss注入漏洞 2026.1.13
function oneblog_comment_submit(){
if ($_SERVER['REQUEST_METHOD'] !== 'POST') return;
// 仅在提交评论时执行安全检查
if (!isset($_POST['text']) && !isset($_POST['author'])) return;
// 通用清理器:移除换行和控制符
foreach ($_POST as $k => &$v) {
if (is_string($v)) {
$v = str_replace(["\r", "\n", "\0"], '', $v);
}
}
unset($v);
// 清理 url 字段
if (isset($_POST['url'])) {
$url = trim($_POST['url']);
if ($url === '') {
$_POST['url'] = '';
} else {
// 拦截包含危险字符、禁止危险协议、并只允许 http(s) 或 //
$hasBadChars = preg_match('/[\"\';<>\(\)\[\]\{\}]/', $url);
$badProtocol = preg_match('#^(javascript|data|vbscript):#i', $url);
$allowedScheme = preg_match('#^(https?://|//)#i', $url);
if ($hasBadChars || $badProtocol || !$allowedScheme) {
$_POST['url'] = '';
} else {
$_POST['url'] = $url;
}
}
}
// 清理 author / mail:移除引号与尖括号,避免属性注入
foreach (['author', 'mail'] as $k) {
if (isset($_POST[$k])) {
$_POST[$k] = preg_replace('/[\"\<\>]/', '', trim((string)$_POST[$k]));
}
}
// 同步清理$_REQUEST
foreach (['author','mail','url','text'] as $k) {
if (isset($_REQUEST[$k])) {
$_REQUEST[$k] = $_POST[$k] ?? $_REQUEST[$k];
}
}
}
oneblog_comment_submit();
// 评论者链接新窗口打开 2026.1.13
function comment_author_link($comments) {
$author = htmlspecialchars($comments->author, ENT_QUOTES, 'UTF-8');
// 获取 url(兼容对象/数组)
$url = '';
if (is_object($comments) && isset($comments->url)) {
$url = trim($comments->url);
} elseif (is_array($comments) && isset($comments['url'])) {
$url = trim($comments['url']);
}
if ($url !== '') {
$href = htmlspecialchars($url, ENT_QUOTES, 'UTF-8');
return '<a href="' . $href . '" target="_blank">' . $author . '</a>';
}
return $author;
}
// Ajax / 普通请求 通用错误输出
function oneblog_abort($msg) {
if (
!empty($_SERVER['HTTP_X_REQUESTED_WITH']) &&
strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) === 'xmlhttprequest'
) {
header('Content-Type: application/json; charset=UTF-8');
echo json_encode([
'success' => false,
'message' => $msg
], JSON_UNESCAPED_UNICODE);
exit;
}
exit('<script>alert(' . json_encode($msg) . ');history.back();</script>');
}
/* Cloudflare Turnstile 验证 */
function verify_cf_token($token, $secret, $remoteIp = null) {
if (empty($token)) return ['success' => false];
$post = [
'secret' => $secret,
'response' => $token
];
if ($remoteIp) $post['remoteip'] = $remoteIp;
$ch = curl_init('https://challenges.cloudflare.com/turnstile/v0/siteverify');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => http_build_query($post),
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 5,
CURLOPT_SSL_VERIFYPEER => true,
CURLOPT_SSL_VERIFYHOST => 2,
]);
$resp = curl_exec($ch);
curl_close($ch);
return json_decode($resp, true) ?: ['success' => false];
}
/* Ajax 评论验证码校验 */
function oneblog_check_captcha($comment, $post, $result) {
$options = Helper::options();
$user = Typecho_Widget::widget('Widget_User');
// 已登录用户跳过验证
if ($user->hasLogin()) {
return $comment;
}
/* ===== Cloudflare ===== */
if (! empty($options->CFSiteKey) && !empty($options->CFSecret)) {
if (empty($_POST['cf_token'])) {
oneblog_abort('请先完成安全验证');
}
$res = verify_cf_token(
$_POST['cf_token'],
$options->CFSecret,
$_SERVER['REMOTE_ADDR'] ?? null
);
if (empty($res['success'])) {
oneblog_abort('安全验证未通过,请重试');
}
return $comment;
}
/* ===== Geetest ===== */
if (! empty($options->GeetestID) && !empty($options->GeetestKEY)) {
foreach (['lot_number','captcha_output','pass_token','gen_time'] as $k) {
if (empty($_POST[$k])) {
oneblog_abort('请完成极验验证');
}
}
$sign = hash_hmac('sha256', $_POST['lot_number'], $options->GeetestKEY);
$query = [
'lot_number' => $_POST['lot_number'],
'captcha_output' => $_POST['captcha_output'],
'pass_token' => $_POST['pass_token'],
'gen_time' => $_POST['gen_time'],
'sign_token' => $sign
];
$url = 'https://gcaptcha4.geetest.com/validate?captcha_id=' . $options->GeetestID;
$ctx = stream_context_create([
'http' => [
'method' => 'POST',
'header' => 'Content-Type: application/x-www-form-urlencoded',
'content' => http_build_query($query),
'timeout' => 5
]
]);
$resp = @file_get_contents($url, false, $ctx);
$json = json_decode($resp, true);
if (empty($json['result']) || $json['result'] !== 'success') {
oneblog_abort('极验验证未通过');
}
return $comment;
}
return $comment;
}
/* Hook 注册 */
Typecho_Plugin::factory('Widget_Feedback')->comment = 'oneblog_check_captcha';
// 从分类描述中提取封面图片和文本描述
function CatInfo($description, $defaultImage = '') {
// 设置默认图片路径
if (empty($defaultImage)) {
$defaultImage = Helper::options()->themeUrl . '/static/img/bg.jpg';
}
$imageUrl = $defaultImage;
$textDescription = '';
if (!empty($description)) {
// 提取第一个图片URL
if (preg_match('/https?:\/\/[^\s]+/', $description, $matches)) {
$imageUrl = htmlspecialchars($matches[0]);
}
// 移除所有图片URL后获取文本描述
$textDescription = trim(preg_replace('/https?:\/\/[^\s]+/', '', $description));
// 处理空文本描述的情况
if (empty($textDescription)) {
$textDescription = '暂无关于该分类的介绍';
}
} else {
$textDescription = '暂无关于该分类的介绍';
}
return [
'img' => $imageUrl,
'info' => $textDescription
];
}
//附件页面和作者页面重定向到404页面
function redirect_404(){
$request = Typecho_Request::getInstance();
$pathInfo = $request->getPathInfo();
// 使用正则表达式匹配路径
if (preg_match('/^\/(attachment\/\d+|author\/\w+)/i', $pathInfo)) {
// 调用 404 页面
$options = Typecho_Widget::widget('Widget_Options');
$url = $options->siteUrl . '404';
header("Location: $url");
exit;
}
}
// 在页面加载之前调用
Typecho_Plugin::factory('Widget_Archive')->beforeRender = 'redirect_404';
================================================
FILE: header.php
================================================
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=0, width=device-width"/>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<link rel="dns-prefetch" href="https://at.alicdn.com">
<link rel="dns-prefetch" href="https://weavatar.com">
<link rel="dns-prefetch" href="https://cncdn.cc">
<?php if (!empty($this->options->dnsPrefetch)):
$domains = array_filter(array_map('trim', explode("\n", $this->options->dnsPrefetch)));
foreach ($domains as $domain): ?>
<link rel="dns-prefetch" href="<?php echo $domain; ?>">
<?php endforeach; ?>
<?php endif; ?>
<?php if (!empty($this->options->Favicon)):?>
<link rel="shortcut icon" href="<?php echo $this->options->Favicon; ?>" type="image/x-icon" />
<?php endif; ?>
<title>
<?php if ($this->is('index')): ?>
<?php $this->options->title(); ?> - <?php echo !empty($this->options->slogan) ? $this->options->slogan() : '自豪地使用OneBlog主题'; ?>
<?php elseif($this->is('archive') && isset($this->request->year)): ?>
<?php echo $this->request->year; ?>年发布的文章 - <?php $this->options->title(); ?>
<?php else:?>
<?php if ($this->is('post')): ?>
<?php echo $this->row['title']; ?> - <?php $this->options->title(); ?>
<?php else: ?>
<?php $this->archiveTitle([
'category' => _t('%s'),
'search' => _t('包含关键字 %s 的文章'),
'tag' => _t('标签 %s 下的文章'),
'author' => _t('%s 发布的文章')
], '', ' - '); ?><?php $this->options->title(); ?>
<?php endif; ?>
<?php endif; ?>
</title>
<link href="https://cncdn.cc/animate.css/4.1.1/animate.min.css" rel="stylesheet"><!--动画效果-->
<link href="//at.alicdn.com/t/c/font_3940454_lp08yxn46sl.css" rel="stylesheet"/><!---图标库 iconfont.cn -->
<?php if ($this->is('index')):?>
<link rel="stylesheet" href="https://cncdn.cc/swiper/8.3.2/swiper-bundle.min.css" /><!--轮播图-->
<?php endif;?>
<link rel="stylesheet" href="https://cncdn.cc/@fancyapps/fancybox/3.5.7/dist/jquery.fancybox.min.css" /><!--灯箱效果-->
<link href="https://cncdn.cc/oneblog/3.7.0/main.css" rel="stylesheet"/><!--主题核心样式-->
<link href="https://cncdn.cc/oneblog/3.7.0/m.css" rel="stylesheet"/><!--主题核心样式-->
<?php $oneblogFont = oneblogFontSet(); ?>
<?php if (!empty($oneblogFont['css'])): ?>
<link rel="stylesheet" href="<?php echo htmlspecialchars($oneblogFont['css'], ENT_QUOTES, 'UTF-8'); ?>" />
<?php endif; ?><style>
:root {
--theme-color: <?php $color = $this->options->themeColor;echo $color ? $color : '#ff5050';?>;
--oneblog-font-family: <?php echo !empty($oneblogFont['family']) ? "'" . addslashes($oneblogFont['family']) . "'," : ""; ?> -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
}
body { font-family: var(--oneblog-font-family); }
<?php echo $this->options->CSS;?>
</style>
<!--各页面OG信息及SEO优化-->
<?php $NoPostIMG = $this->options->NoPostIMG ? $this->options->NoPostIMG : Helper::options()->themeUrl . '/static/img/bg.jpg';
$Webthumb = $this->options->Webthumb ? $this->options->Webthumb : Helper::options()->themeUrl . '/static/img/logo.png';
?>
<!--首页-->
<?php if ($this->is('index')): ?>
<meta property="og:description" content="<?php echo $this->options->description(); ?>" />
<meta property="og:image" content="<?php echo $Webthumb; ?>" />
<meta name="image" content="<?php echo $Webthumb; ?>">
<link rel="apple-touch-icon-precomposed" href="<?php echo $Webthumb; ?>">
<meta name="msapplication-TileImage" content="<?php echo $Webthumb; ?>">
<!--文章详情页-->
<?php elseif ($this->is('post')):
$thumb = showThumbnail($this);?>
<meta property="og:description" content="<?php echo $this->excerpt(80,'...'); ?>" />
<meta property="og:image" content="<?php echo $thumb; ?>" />
<meta name="image" content="<?php echo $thumb; ?>">
<link rel="apple-touch-icon-precomposed" href="<?php echo $thumb; ?>">
<meta name="msapplication-TileImage" content="<?php echo $thumb; ?>">
<!--其他页面-->
<?php else:?>
<meta property="og:image" content="<?php echo $Webthumb; ?>" />
<meta property="og:image:type" content="image/webp">
<meta name="image" content="<?php echo $Webthumb; ?>">
<link rel="apple-touch-icon-precomposed" href="<?php echo $Webthumb; ?>">
<meta name="msapplication-TileImage" content="<?php echo $Webthumb; ?>">
<?php endif;?>
<script>
var logoUrl = "<?php echo $this->options->logo ? $this->options->logo : Helper::options()->themeUrl . '/static/img/logo.svg'; ?>";
var logoWhiteUrl = "<?php echo $this->options->logoWhite ? $this->options->logoWhite : Helper::options()->themeUrl . '/static/img/logoWhite.svg'; ?>";
var bannerSwitch = "<?php echo $this->options->switch; ?>";
var autoNightMode = "<?php echo $this->options->AutoNightMode; ?>";
(function() {
var currentTheme = document.cookie.replace(/(?:(?:^|.*;\s*)eyeProtectMode\s*\=\s*([^;]*).*$)|^.*$/, "$1");
var autoNightEnabled = typeof autoNightMode !== 'undefined' && autoNightMode === 'on';
var hasUserTheme = currentTheme === 'dark' || currentTheme === 'light';
var hour = new Date().getHours();
var autoNightActive = hour >= 19 || hour < 5;
if ((hasUserTheme && currentTheme === 'dark') || (!hasUserTheme && autoNightEnabled && autoNightActive)) {
document.documentElement.classList.add('night');
}
})();
</script>
<?php $this->header();?>
</head>
<body>
<?php $hasLoaded = !empty($_COOKIE['jsLoaded']); ?>
<div id="global-loading" style="display:<?php echo $hasLoaded ? 'none' : 'flex'; ?>;">
<div class="progress-loader">
<div class="progress"></div>
</div>
</div>
<div id="main" style="display:<?php echo $hasLoaded ? '' : 'none'; ?>;">
================================================
FILE: index.php
================================================
<?php
/**
*
* 一款简约文艺的文字博客主题:那些物质的东西,都会随着时间慢慢销蚀,而我们写下的文字,最趋近于永恒。唯愿不忘初心,坚持把自己的博客写下去。本主题作者是一名律师,工作越来越忙,主题事宜请先仔细查阅官方文档,也可在QQ交流群939170079咨询博友。
* 官网:<a href="https://onenote.io">onenote.io</a>
* 文档:<a href="https://docs.onenote.io">docs.onenote.io</a>
*
* @package OneBlog
* @author 彼岸临窗
* @version 3.7.0
* @link https://onenote.io
*/
if (!defined('__TYPECHO_ROOT_DIR__')) exit;
$this->need('header.php'); ?>
<div class="main">
<?php $this->need('module/head.php');
if ($this->is('index')){
//首页显示banner
if ($this->options->switch == 'on') {
$defaultThumb = Helper::options()->themeUrl . '/static/img/bg.jpg';
$defaultPost = [
'link' => 'https://onenote.io',
'title' => '请填写文章cid',
'thumb' => $defaultThumb,
];
$posts = [];
$rawData = get_banner_data($this->options);
foreach ($rawData as $row) {
$post = Typecho_Widget::widget('Widget_Abstract_Contents');
$post->push($row);
$thumbnailRaw = showThumbnail($post) ?: $defaultThumb;
// 如果有缩略图处理参数
if ($thumbnailRaw !== $defaultThumb && $this->options->imgSmall) {
$thumbnail = $thumbnailRaw . $this->options->imgSmall;
} else {
$thumbnail = $thumbnailRaw;
}
$posts[] = [
'link' => $post->permalink,
'title' => $post->hidden ? '密码保护:' . $post->row['title'] : $post->row['title'],
'thumb' => $thumbnail,
];
}
// 如果数量不足3,用默认占位
for ($i = count($posts); $i < 3; $i++) {
$posts[] = $defaultPost;
}?>
<!-- 骨架屏 -->
<div id="banner-skeleton">
<!-- 移动端骨架屏 -->
<div class="banner-skeleton-mobile m">
<div class="skeleton-banner-thumb-mobile skeleton-thumb-relative">
<div class="skeleton-banner-indicator">
<span class="skeleton-dot"></span>
<span class="skeleton-dot"></span>
<span class="skeleton-dot"></span>
</div>
</div>
</div>
<!-- PC端骨架屏 -->
<div class="banner-skeleton-pc">
<div class="skeleton-banner-item-main">
<div class="skeleton-banner-thumb-main"></div>
</div>
<div class="skeleton-banner-item-side">
<div class="skeleton-banner-thumb-side"></div>
<div class="skeleton-banner-thumb-side"></div>
</div>
</div>
</div>
<!-- 根据设备类型自动生成banner内容 -->
<div class="banner-container blur"></div>
<script id="banner-json" type="application/json">
<?= json_encode($posts, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); ?>
</script>
<?php }
}elseif($this->is('tag')){ ?>
<div class="category-header m blur" style="background-image: url('<?php $this->options->Tagbg();?>');">
<div class="category-info">
<h1># <?php $this->archiveTitle('%s', '', ''); ?></h1>
</div>
</div>
<?php }elseif($this->is('category')){?>
<div class="category-header m blur" style="background-image: url('<?php $info = CatInfo($this->getDescription()); echo $info['img']; ?>');">
<div class="category-info">
<h1><?php $this->archiveTitle('%s', '', ''); ?></h1>
<span><?php echo $info['info']; ?></span>
</div>
</div>
<?php }elseif($this->is('search') and $this->have()){?>
<div class="search-title m blur">
<span>「<?php $this->archiveTitle('%s', '', ''); ?>」</span>相关的<?php echo $this->getTotal().'篇文章';?>
</div>
<?php }?>
<?php if ($this->have()): ?>
<!--文章列表-->
<div id="posts" class="blur">
<?php while($this->next()): ?>
<a href="<?php $this->permalink() ?>" class="post" >
<h1 class="animate__animated animate__fadeInUp">
<?php echo $this->hidden ? '密码保护:' . $this->row['title'] : $this->row['title'];?>
</h1>
<div class="post_preview animate__animated animate__fadeInUp">
<p><?php $this->excerpt(80,'...'); ?></p>
<?php if(showThumbnail($this)):?>
<div class="post_img lazy-load" data-src="<?php echo showThumbnail($this) . ($this->options->imgSmall ?: ''); ?>">
</div>
<?php endif;?>
</div>
<div class="post_meta animate__animated animate__fadeInUp">
<span><?php echo time_ago($this->date); ?></span>
<span><?php get_post_view($this) ?> 阅读</span>
<span><?php $this->commentsNum('0 评论', '1 评论', '%d 评论'); ?></span>
</div>
</a>
<?php endwhile; ?>
</div>
<!--点击无限加载-->
<div class="load blur" id="loadmore">
<?php $this->pageLink('点击查看更多','next'); ?>
</div>
<?php else: ?>
<div class="nodata blur">
<img src='<?php $this->options->themeUrl('static/img/nodata.svg'); ?>'></img>
<span>暂无相关内容</span>
<a href="<?php $this->options->siteUrl(); ?>">返回首页</a>
</div>
<?php endif; ?>
</div>
<!--返回顶部-->
<a id="gototop" class="hidden pc"><i class="iconfont icon-up"></i></a>
<?php $this->need('footer.php'); ?>
================================================
FILE: links.php
================================================
<?php
/**
* 友情链接
*
* @package custom
*/
if (!defined('__TYPECHO_ROOT_DIR__')) exit;
$this->need('header.php'); ?>
<div class="main">
<?php $this->need('module/head2.php');?>
<!--背景图片+logo-->
<div class="page_thumb blur">
<!-- 背景图片容器 -->
<div class="post_bg lazy-load" data-src="<?php echo $this->fields->thumb ? $this->fields->thumb : Helper::options()->themeUrl . '/static/img/friend.jpg';?>"></div>
<div class="pc">
<!-- 新增的菜单按钮 -->
<i class="iconfont icon-nav menu-button"></i>
<div class="page-head">
<?php if ($this->options->logoStyle == 'text') {?>
<h1><a href="<?php $this->options->siteUrl(); ?>"><?php $this->options->title();?></a><span class="soul">生活志</span></h1>
<?php }else{ ?>
<a class="logo" href="<?php $this->options->siteUrl(); ?>">
<img src="<?php echo $this->options->logoWhite ? $this->options->logoWhite : Helper::options()->themeUrl . '/static/img/logoWhite.svg'; ?>">
</a>
<?php }?>
</div>
</div>
<div class="m">
<h1 class="page-head"><?php $this->archiveTitle(' » ', ''); ?><span>My online friends</span></h1>
</div>
</div>
<div class="page-title animate__animated animate__fadeIn pc">
<h1><?php $this->title(); ?></h1>
</div>
<?php if (array_key_exists('Links', Typecho_Plugin::export()['activated'])):?>
<div class="links padding blur">
<?php Links_Plugin::output("
<li class='link'>
<a href='{url}' target='_blank'>
<img src='{image}' alt='{name}'/>
<div class='link-info'>
<h3>{name}</h3>
<span class='lite-black' title='{description}'>{description}</span>
</div>
</a>
</li>
", 0); ?>
</div>
<script>
window.oneblogLinkStatusUrl = <?php echo json_encode(rtrim($this->options->themeUrl, '/') . '/api/link-status.php'); ?>;
</script>
<?php else:?>
<div class="nodata blur">
<img src='<?php $this->options->themeUrl('static/img/nodata.svg'); ?>'></img>
<span>暂未启用Links插件,请先安装并启用该插件。</span>
</div>
<?php endif;?>
<div class="post_content padding animate__animated animate__fadeIn blur">
<h4 class="link-request">
<span>#</span>
友链要求
</h4>
<?php echo AutoLightbox($this->content);?>
</div>
<?php $this->need('comments.php'); ?>
<a id="gototop" class="hidden"><i class="iconfont icon-up"></i></a>
</div>
<?php $this->need('footer.php'); ?>
================================================
FILE: memos.php
================================================
<?php
/**
* 微语页面
*
* @package custom
*/
if (!defined('__TYPECHO_ROOT_DIR__')) exit;
$this->need('header.php');
$export = Typecho_Plugin::export();
$memosImageEnabled = isset($export['activated']['MemosImage']);
?>
<meta name="csrf-token" content="<?php echo Helper::security()->getToken($this->request->getRequestUrl()); ?>">
<meta name="comment-url" content="<?php $this->commentUrl(); ?>">
<div class="main">
<?php $this->need('module/head2.php'); ?>
<div class="page_thumb blur">
<div class="post_bg lazy-load"
data-src="<?php echo $this->fields->thumb ? $this->fields->thumb : Helper::options()->themeUrl . '/static/img/memos.jpg'; ?>">
</div>
<div class="pc">
<i class="iconfont icon-nav menu-button"></i>
<div class="page-head">
<?php if ($this->options->logoStyle == 'text') : ?>
<h1>
<a href="<?php $this->options->siteUrl(); ?>"><?php $this->options->title(); ?></a>
<span class="soul">生活志</span>
</h1>
<?php else : ?>
<a class="logo" href="<?php $this->options->siteUrl(); ?>">
<img src="<?php echo $this->options->logoWhite ?: Helper::options()->themeUrl . '/static/img/logoWhite.svg'; ?>">
</a>
<?php endif; ?>
</div>
</div>
<div class="m">
<h1 class="page-head">
<?php $this->archiveTitle(' » ', ''); ?>
<span>A fleeting inspiration</span>
</h1>
</div>
<div class="memos-btn">
<?php if ($this->user->hasLogin()) : ?>
<button id="publish-button">发布</button>
<?php else : ?>
<button id="login-button">登录</button>
<?php endif; ?>
</div>
</div>
<!-- 微语列表 -->
<div id="comments" class="memos padding animate__animated animate__fadeIn blur">
<?php $this->comments()->to($comments); ?>
<?php if ($comments->have()) : ?>
<ul class="comment-list">
<?php while ($comments->next()) : ?>
<?php MemosList($comments, $this->user); ?>
<?php endwhile; ?>
</ul>
<?php $comments->pageNav('', ''); ?>
<div class="load" id="load-more-comments">加载更多动态</div>
<div id="loading-spinner" style="display: none;">
<div class="spinner"></div><span>加载中...</span>
</div>
<div class="load" id="no-more" style="display: none;">
— 已加载全部数据 —
</div>
<?php endif; ?>
</div>
<a id="gototop" class="hidden"><i class="iconfont icon-up"></i></a>
</div>
<script>
var loginAction = "<?php echo $this->options->loginAction(); ?>";
var commentLikeUrl = "<?php Helper::options()->index('?commentLike=dz'); ?>";
<?php if ($memosImageEnabled) : ?>
<?php $plugin = $this->options->plugin('MemosImage'); ?>
window.memosConfig = Object.assign({}, window.memosConfig || {}, {
enabled: true,
memosUseCos: "<?php echo $plugin->uploadMode ?: 'local'; ?>" === 'cos',
memosUploadUrl: "<?php Helper::options()->index('/action/memos-upload'); ?>",
memosSignUrl: "<?php Helper::options()->index('/action/memos-sign'); ?>"
});
<?php endif; ?>
</script>
<?php $this->need('footer.php'); ?>
================================================
FILE: module/head.php
================================================
<!-- 移动端侧栏菜单-->
<div class="menu">
<div class="close m">
<span id="close"><i class="iconfont icon-cancel"></i></span>
</div>
<?php if ($menu = CustomMenu()): ?>
<?php echo $menu['hasIcon']; ?>
<li class="pc"><a href="javascript:void(0);" onclick="openSearch();"><i class="iconfont icon-search"></i>搜索</a></li>
<?php endif; ?>
<div class="copyright m">
<div class="switch">
夜间模式<input type="checkbox" id="night1" class="switchBtn"><label for="night1" class="switchBtn"></label>
</div>
<span>©<?php if (!empty($this->options->Webtime)): echo $this->options->Webtime().'-'; ?><?php endif; ?><?php echo date('Y'); ?> <a href="<?php echo $this->options->siteUrl; ?>"><?php echo $this->options->title; ?></a></span>
<span>Theme by <a id="copyright-m" href="https://docs.onenote.io" title="自豪地使用OneBlog主题" target="_blank">OneBlog</a></span>
</div>
</div>
<!--顶部菜单-->
<div class="header bg-white">
<i class="iconfont icon-nav"></i>
<?php if ($this->options->logoStyle == 'text') {?>
<h1><a href="<?php $this->options->siteUrl(); ?>"><?php $this->options->title();?></a><span class="soul"><div class="pc">生活志</div><div class="m">博客</div></span></h1>
<?php }else{ ?>
<a class="logo" id="logo" href="<?php $this->options->siteUrl(); ?>" style="background-image:url('<?php echo $this->options->logo ? $this->options->logo : Helper::options()->themeUrl . '/static/img/logo.svg'; ?>')"></a>
<?php }?>
<i id="search-btn" class="iconfont icon-search m"></i>
</div>
<!--搜索弹框-->
<div class="search-layer">
<button class="close-search pc"><i class="iconfont icon-cancel"></i></button>
<div class="search">
<h5 class="pc">搜索</h5>
<form autocomplete="off" id="search" method="post" action="<?php $this->options->siteUrl(); ?>" role="search" class="search-form">
<input type="text" name="s" class="input" placeholder="<?php _e('输入关键字搜索'); ?>" required />
<button type="submit" class="search-icon">
<span class="m">搜索</span>
<i class="iconfont icon-search pc"></i>
</button>
</form>
</div>
<div class="tagscloud pc">
<h5>标签</h5>
<?php getTopTags(); ?>
</div>
</div>
<div class="one pc">
<?php if ($this->is('index')): ?>
" <?php $quotes_file = dirname(__DIR__, 1) . '/api/one.txt';;$quotes = file($quotes_file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);$random_quote = $quotes[array_rand($quotes)];echo $random_quote;?>"
<?php elseif ($this->is('search')): ?>
" <?php $this->archiveTitle(['search' => _t('博客内包含关键字「<span>%s</span>」的文章')], '', ''); ?> <?php echo '共'.$this->getTotal().'篇';?> "
<?php elseif ($this->is('tag')): ?>
" <?php $this->archiveTitle(['tag' => _t('博客内包含标签「<span>%s</span>」的文章')], '', ''); ?> <?php echo '共'.$this->getTotal().'篇';?> "
<?php elseif ($this->is('category')): ?>
" <?php $this->archiveTitle(['category' => _t('分类「<span>%s</span>」下的文章')], '', ''); ?> <?php echo '共'.$this->getTotal().'篇';?> "
<?php elseif ($this->is('archive') && isset($this->request->year)): ?>
" <?php echo $this->request->year; ?>年发布的文章 <?php echo '共'.$this->getTotal().'篇';?> "
<?php endif;?>
</div>
================================================
FILE: module/head2.php
================================================
<!-- 移动端侧栏菜单-->
<div class="menu">
<div class="close m">
<span id="close"><i class="iconfont icon-cancel"></i></span>
</div>
<?php if ($menu = CustomMenu()): ?>
<?php echo $menu['hasIcon']; ?>
<li class="pc"><a href="javascript:void(0);" onclick="openSearch();"><i class="iconfont icon-search"></i>搜索</a></li>
<?php endif; ?>
<div class="copyright m">
<div class="switch">
夜间模式<input type="checkbox" id="night1" class="switchBtn"><label for="night1" class="switchBtn"></label>
</div>
<span>©<?php if (!empty($this->options->Webtime)): echo $this->options->Webtime().'-'; ?><?php endif; ?><?php echo date('Y'); ?> <a href="<?php echo $this->options->siteUrl; ?>"><?php echo $this->options->title; ?></a></span>
<span>Theme by <a id="copyright-m" href="https://docs.onenote.io" title="自豪地使用OneBlog主题" target="_blank">OneBlog</a></span>
</div>
</div>
<!--顶部菜单-->
<div class="header bg-white m">
<i class="iconfont icon-nav"></i>
<?php if ($this->options->logoStyle == 'text') {?>
<h1><a href="<?php $this->options->siteUrl(); ?>"><?php $this->options->title();?></a><span class="soul">博客</span></h1>
<?php }else{ ?>
<a class="logo" id="logo" href="<?php $this->options->siteUrl(); ?>" style="background-image:url('<?php echo $this->options->logo ? $this->options->logo : Helper::options()->themeUrl . '/static/img/logo.svg'; ?>')"></a>
<?php }?>
<i id="search-btn" class="iconfont icon-search"></i>
</div>
<!--搜索弹框-->
<div class="search-layer">
<button class="close-search pc"><i class="iconfont icon-cancel"></i></button>
<div class="search">
<h5 class="pc">搜索</h5>
<form autocomplete="off" id="search" method="post" action="<?php $this->options->siteUrl(); ?>" role="search" class="search-form">
<input type="text" name="s" class="input" placeholder="<?php _e('输入关键字搜索'); ?>" required />
<button type="submit" class="search-icon">
<span class="m">搜索</span>
<i class="iconfont icon-search pc"></i>
</button>
</form>
</div>
<div class="tagscloud pc">
<h5>标签</h5>
<?php getTopTags(); ?>
</div>
</div>
================================================
FILE: page.php
================================================
<?php
if (!defined('__TYPECHO_ROOT_DIR__')) exit;
$this->need('header.php'); ?>
<div class="main">
<?php $this->need('module/head2.php');?>
<?php if($this->fields->thumb): ?>
<!--有封面图的页面顶部样式-->
<div class="post_thumb blur">
<!-- 背景图片容器 -->
<div class="post_bg lazy-load" data-src="<?php $this->fields->thumb();?>"></div>
<div class="pc">
<!-- 新增的菜单按钮 -->
<i class="iconfont icon-nav menu-button"></i>
<!-- 内容容器保持不变 -->
<div class="post_header padding animate__animated animate__fadeIn">
<h1><?php $this->title() ?></h1>
<div class="post_meta">
<span><?php $this->date('Y.m.d'); ?></span>
<span>/</span>
<span><?php get_post_view($this) ?> 阅读</span>
<span>/</span>
<span><?php $this->commentsNum('0 评论', '1 评论', '%d 评论'); ?></span>
</div>
</div>
</div>
</div>
<div class="m blur">
<!--文章标题和统计-->
<div class="post_info">
<h1><?php $this->title();?></h1>
<span>阅读 <?php get_post_view($this) ?></span>
<span><?php $this->commentsNum('评论 0', '评论 1', '评论 %d'); ?></span>
<span>发表于<?php $this->date('Y.m.d'); ?></span>
</div>
</div>
<?php else: ?>
<!--没有封面图的页面顶部样式-->
<div class="post_nothumb blur animate__animated animate__fadeIn">
<div class="breadcrumb">
<li><a href="<?php $this->options->siteUrl(); ?>">首页</a><span>></span></li>
<li><?php $this->title() ?></li>
</div>
<div class="pc">
<i class="iconfont icon-nav menu-button"></i>
</div>
<h1><?php $this->title() ?></h1>
<div class="post_meta">
<span><?php $this->date('Y年m月d日'); ?></span>
<span><?php get_post_view($this) ?> 阅读</span>
<span><?php $this->commentsNum('0 评论', '1 评论', '%d 评论'); ?></span>
</div>
</div>
<?php endif;?>
<!--通用正文-->
<div class="post_content padding blur animate__animated animate__fadeIn">
<?php echo AutoLightbox($this->content);?>
</div>
<!--通用文章评论-->
<?php $this->need('comments.php'); ?>
</div>
<!--返回顶部-->
<a id="gototop" class="hidden pc"><i class="iconfont icon-up"></i></a>
<?php $this->need('footer.php'); ?>
================================================
FILE: photos.php
================================================
<?php
/**
* 相册页面
* @package custom
* 本页面依赖于OneBlog主题定制插件:Album
*/
if (!defined('__TYPECHO_ROOT_DIR__')) exit;
$this->need('header.php');
$currentPage = max(1, intval($this->request->get('page', 1)));
$this->widget('Album_Widget_Photo@photoList', 'page=' . $currentPage)->to($photos);
?>
<div class="main">
<?php $this->need('module/head2.php');?>
<!--背景图片+logo-->
<div class="page_thumb blur">
<!-- 背景图片容器 -->
<div class="post_bg lazy-load" data-src="<?php echo $this->fields->thumb ? $this->fields->thumb : Helper::options()->themeUrl . '/static/img/photo.jpg'; ?>"></div>
<div class="pc">
<!-- 新增的菜单按钮 -->
<i class="iconfont icon-nav menu-button"></i>
<div class="page-head">
<?php if ($this->options->logoStyle == 'text') {?>
<h1><a href="<?php $this->options->siteUrl(); ?>"><?php $this->options->title();?></a><span class="soul">生活志</span></h1>
<?php }else{ ?>
<a class="logo" href="<?php $this->options->siteUrl(); ?>">
<img src="<?php echo $this->options->logoWhite ? $this->options->logoWhite : Helper::options()->themeUrl . '/static/img/logoWhite.svg'; ?>">
</a>
<?php }?>
</div>
</div>
<div class="m">
<h1 class="page-head"><?php $this->archiveTitle(' » ', ''); ?><span>Scenery along the way</span></h1>
</div>
</div>
<div class="page-title animate__animated animate__fadeIn pc">
<h1><?php $this->title(); ?></h1>
</div>
<div class="photo-contain blur animate__animated animate__fadeIn">
<?php if (array_key_exists('Album', Typecho_Plugin::export()['activated'])):?>
<!--相册-->
<div class="photos" id="photos">
<?php for ($photos->rewind(); $photos->valid(); $photos->next()): ?>
<div class="photo image-shadow">
<a href="<?= $photos->rawUrl ?>" data-fancybox="gallery"
data-caption="<?= $photos->title ?> <?= $photos->caption ?> <?= date('M d, Y', $photos->date) ?>">
<img class="lazy-load" data-src="<?= $photos->url ?>" />
</a>
</div>
<?php endfor; ?>
</div>
<!-- 点击无限加载 -->
<div class="load" id="loadmore">
<?php if ($photos->haveNextPage()): ?>
<a href="?ajax=1&page=<?php echo $currentPage + 1; ?>" class="next">点击查看更多</a>
<?php else: ?>
— 暂无更多内容 —
<?php endif; ?>
</div>
<?php else:?>
<div class="nodata">
<img src='<?php $this->options->themeUrl('static/img/nodata.svg'); ?>'></img>
<span>暂未启用相册插件,请先安装并启用该插件。</span>
</div>
<?php endif;?>
</div>
</div>
<a id="gototop" class="hidden"><i class="iconfont icon-up"></i></a>
</div>
<?php $this->need('footer.php'); ?>
================================================
FILE: post.php
================================================
<?php
if (!defined('__TYPECHO_ROOT_DIR__')) exit;
$this->need('header.php'); ?>
<div class="main">
<?php $this->need('module/head2.php');?>
<?php if($this->fields->thumb): ?>
<!--有封面图的文章详情页顶部样式-->
<div class="post_thumb blur">
<!-- 背景图片容器 -->
<div class="post_bg lazy-load" data-src="<?php $this->fields->thumb();?>"></div>
<div class="pc">
<!-- 新增的菜单按钮 -->
<i class="iconfont icon-nav menu-button"></i>
<!-- 内容容器保持不变 -->
<div class="post_header padding animate__animated animate__fadeIn">
<?php $firstCat = $this->categories[0]; ?>
<a href="<?php echo $firstCat['permalink']; ?>"><?php echo $firstCat['name']; ?></a>
<h1><?php echo $this->hidden ? '密码保护:' . $this->row['title'] : $this->row['title']; ?></h1>
<div class="post_meta">
<span><?php $this->date('Y.m.d'); ?></span>
<span>/</span>
<span><?php get_post_view($this) ?> 阅读</span>
<span>/</span>
<span><?php $this->commentsNum('0 评论', '1 评论', '%d 评论'); ?></span>
<span>/</span>
<span><?php echo art_count($this->cid); ?> 字</span>
</div>
</div>
</div>
</div>
<div class="m blur">
<!--当前文章所属分类 只取第一个分类-->
<div class="category">
<?php $firstCat = $this->categories[0]; ?>
<a href="<?php echo $firstCat['permalink']; ?>"><?php echo $firstCat['name']; ?></a>
</div>
<!--文章标题和统计-->
<div class="post_info">
<h1><?php echo $this->hidden ? '密码保护:' . $this->row['title'] : $this->row['title'];?></h1>
<span>阅读 <?php get_post_view($this) ?></span>
<span><?php $this->commentsNum('评论 0', '评论 1', '评论 %d'); ?></span>
<span>发表于<?php $this->date('Y.m.d'); ?></span>
</div>
</div>
<?php else: ?>
<!--没有封面图的文章详情页顶部样式-->
<div class="post_nothumb blur animate__animated animate__fadeIn">
<div class="breadcrumb">
<li><a href="<?php $this->options->siteUrl(); ?>">首页</a><span>></span></li>
<li><?php $firstCat = $this->categories[0]; ?><a href="<?php echo $firstCat['permalink']; ?>"><?php echo $firstCat['name']; ?></a><span>></span></li>
<li>正文</li>
</div>
<div class="pc">
<i class="iconfont icon-nav menu-button"></i>
</div>
<h1><?php echo $this->hidden ? '密码保护:' . $this->row['title'] : $this->row['title']; ?></h1>
<div class="post_meta">
<span><?php $this->date('Y年m月d日'); ?></span>
<span><?php get_post_view($this) ?> 阅读</span>
<span><?php $this->commentsNum('0 评论', '1 评论', '%d 评论'); ?></span>
<span><?php echo art_count($this->cid); ?> 字</span>
</div>
</div>
<?php endif;?>
<!--通用文章正文-->
<div class="post_content padding blur animate__animated animate__fadeIn">
<?php echo AutoLightbox($this->content);?>
<div class="cc-say">
本文著作权归作者 [ <span><?php if($this->fields->author){ $this->fields->author();}else{$this->author();}?></span> ] 享有,未经作者书面授权,禁止转载,封面图片来源于 [ <span><?php echo $this->fields->origin ? $this->fields->origin : '互联网' ;?></span> ] ,本文仅供个人学习、研究和欣赏使用。如有异议,请联系博主及时处理。
</div>
<?php if ($this->tags) { ?>
<div class="tags"><?php $this->tags('', true); ?></div>
<?php } ?>
</div>
<!--通用文章评论-->
<?php $this->need('comments.php'); ?>
</div>
<!--返回顶部-->
<a id="gototop" class="hidden pc"><i class="iconfont icon-up"></i></a>
<?php $this->need('footer.php'); ?>
================================================
FILE: static/js/admin.js
================================================
/**
* Updated: 2026-05-02
* Author: ©彼岸临窗 onenote.io
*
* 注释含命名规范,开源不易,如需引用请注明来源:彼岸临窗 https://onenote.io。
* 本主题已取得软件著作权(登记号:2025SR0334142)和外观设计专利(专利号:第7121519号),请严格遵循GPL-2.0协议使用本主题及源码。
*/
/**分类tab**/
document.addEventListener('DOMContentLoaded', function () {
// Tab 配置
const tabs = [
{ id: 'tab1', label: '主题说明', selector: null }, // 第一个 Tab 是静态内容
{ id: 'base', label: '基础设置', selector: '[id*="logoStyle"],[id*="slogan"],[id*="logo"],[id*="logowhite"],[id*="MenuSet"],[id*="Favicon"],[id*="switch"],[id*="Banner"],[id*="Menu"],[id*="Tagbg"],[id*="Webthumb"],[id*="Webtime"],[id*="ICP"],[id*="WA"]'},
{ id: 'pro', label: '高级设置', selector: '[id*="dnsPrefetch"],[id*="sitemapInterval"],[id*="imgSmall"],[id*="BeCode"],[id*="RandomIMG"],[id*="AutoNightMode"],[id*="CFSiteKey"],[id*="CFSecret"],[id*="GeetestID"],[id*="GeetestKEY"]' },
{ id: 'social', label: '社交按钮', selector: '[id*="QQ"],[id*="Weixin"],[id*="Email"],[id*="Github"]' },
{ id: 'DIY', label: '样式定制', selector: '[id*="FontFamily"],[id*="CSS"],[id*="JS"],[id*="themeColor"]' },
];
const form = document.querySelector('form');
const tabContainer = document.getElementById('tab-container');
const tabNav = document.getElementById('tab-nav');
const tabContent = document.getElementById('tab-content');
// 将 Tab 容器移动到表单中
form.insertBefore(tabContainer, form.firstChild);
// 生成 Tab 导航
tabs.forEach((tab, index) => {
const li = document.createElement('li');
const a = document.createElement('a');
a.href = `#${tab.id}`;
a.textContent = tab.label;
a.addEventListener('click', (e) => {
e.preventDefault();
switchTab(tab.id);
});
li.appendChild(a);
tabNav.appendChild(li);
// 生成 Tab 内容区域(第一个 Tab 已经存在,跳过)
if (tab.id !== 'tab1') {
const pane = document.createElement('div');
pane.id = tab.id;
pane.classList.add('tab-pane');
tabContent.appendChild(pane);
}
});
// 将字段移动到对应的 Tab 内容区域
tabs.forEach(tab => {
if (tab.selector) {
const fields = document.querySelectorAll(tab.selector);
const pane = document.getElementById(tab.id);
fields.forEach(field => {
pane.appendChild(field.closest('.typecho-option')); // 将整个字段容器移动到 Tab 内容区域
});
}
});
// 默认显示第一个 Tab
switchTab(tabs[0].id);
initSwitchRadios();
initFontPreview();
// 切换 Tab 的函数
function switchTab(tabId) {
// 隐藏所有 Tab 内容
document.querySelectorAll('.tab-pane').forEach(pane => {
pane.classList.remove('active');
});
// 显示当前 Tab 内容
document.getElementById(tabId).classList.add('active');
// 更新 Tab 导航的激活状态
document.querySelectorAll('#tab-nav li a').forEach(a => {
a.classList.remove('active');
});
document.querySelector(`#tab-nav a[href="#${tabId}"]`).classList.add('active');
}
function initSwitchRadios() {
const switchPanes = document.querySelectorAll('#base, #pro');
if (!switchPanes.length) return;
const radioGroups = Array.from(switchPanes).flatMap(pane => {
return Array.from(pane.querySelectorAll('input[type="radio"]'));
}).reduce((groups, radio) => {
if (!radio.name) return groups;
groups[radio.name] = groups[radio.name] || [];
groups[radio.name].push(radio);
return groups;
}, {});
Object.keys(radioGroups).forEach(name => {
const radios = radioGroups[name];
if (radios.length !== 2) return;
const offRadio = radios.find(radio => radio.value === 'off');
const onRadio = radios.find(radio => radio.value !== 'off');
if (!offRadio || !onRadio) return;
const option = radios[0].closest('.typecho-option');
if (!option || option.classList.contains('oneblog-switch-radio')) return;
const title = option.querySelector(':scope > .typecho-label') || option.querySelector('.typecho-label');
const wrap = document.createElement('div');
const button = document.createElement('button');
option.classList.add('oneblog-switch-radio');
wrap.className = 'oneblog-switch-wrap';
button.type = 'button';
button.className = 'oneblog-switch';
button.setAttribute('role', 'switch');
function placeSwitch() {
if (!title) {
option.insertBefore(wrap, option.firstChild);
return;
}
title.classList.add('oneblog-switch-label');
title.insertAdjacentElement('afterend', wrap);
}
function getRadioLabel(radio) {
return radio.closest('label') || (radio.id ? option.querySelector(`label[for="${radio.id}"]`) : null);
}
radios.forEach(radio => {
const label = getRadioLabel(radio);
radio.classList.add('oneblog-switch-original');
if (label) label.classList.add('oneblog-switch-original');
});
function sync() {
const isOn = onRadio.checked;
button.classList.toggle('is-on', isOn);
button.setAttribute('aria-checked', isOn ? 'true' : 'false');
}
button.addEventListener('click', () => {
const target = onRadio.checked ? offRadio : onRadio;
target.checked = true;
target.dispatchEvent(new Event('change', { bubbles: true }));
sync();
});
radios.forEach(radio => radio.addEventListener('change', sync));
wrap.appendChild(button);
placeSwitch();
sync();
});
}
function initFontPreview() {
const fontOption = document.querySelector('input[name="FontFamily"]')?.closest('.typecho-option');
const fonts = window.oneblogFontConfigs || {};
if (!fontOption || fontOption.classList.contains('oneblog-font-ready')) return;
Object.keys(fonts).forEach(key => {
const font = fonts[key];
if (!font || !font.css || document.querySelector(`link[data-oneblog-font="${key}"]`)) return;
const link = document.createElement('link');
link.rel = 'stylesheet';
link.href = font.css;
link.dataset.oneblogFont = key;
document.head.appendChild(link);
});
fontOption.classList.add('oneblog-font-ready');
const list = document.createElement('div');
list.className = 'oneblog-font-list';
const cards = [];
fontOption.querySelectorAll('input[type="radio"]').forEach(input => {
const label = input.closest('label') || (input.id ? fontOption.querySelector(`label[for="${input.id}"]`) : null);
if (!label) return;
const font = fonts[input.value] || {};
label.classList.add('oneblog-font-card');
label.style.fontFamily = font.family ? `'${font.family}', serif` : '';
label.dataset.preview = '\u611f\u53d7\u6587\u5b57\u7684\u6e29\u5ea6';
list.appendChild(label);
cards.push({ input, label });
});
if (cards.length) fontOption.appendChild(list);
function syncSelected() {
cards.forEach(({ input, label }) => {
label.classList.toggle('is-selected', input.checked);
});
}
cards.forEach(({ input }) => input.addEventListener('change', syncSelected));
syncSelected();
}
});
/**图标依赖**/
// 动态加载 iconfont.css
function loadIconfont() {
const link = document.createElement('link');
link.rel = 'stylesheet';
link.href = '//at.alicdn.com/t/c/font_3940454_lp08yxn46sl.css'; // ONEBLOG图标库
document.head.appendChild(link);
}
// 动态解析 iconfont.css
async function loadAndParseIconfont() {
const response = await fetch('//at.alicdn.com/t/c/font_3940454_lp08yxn46sl.css');// ONEBLOG图标库
const cssContent = await response.text();
const iconRegex = /\.(icon-[^:]+):before\s*{\s*content:\s*"([^"]+)"/g;
const icons = [];
let match;
while ((match = iconRegex.exec(cssContent)) !== null) {
icons.push({
className: match[1],
unicode: match[2]
});
}
return icons;
}
// 渲染图标
function renderIcons(icons) {
const iconList = document.getElementById('iconList');
icons.forEach(icon => {
const iconItem = document.createElement('div');
iconItem.className = 'icon-item';
const iconElement = document.createElement('i');
iconElement.className = `iconfont ${icon.className}`;
const classNameElement = document.createElement('span');
classNameElement.textContent = icon.className;
iconItem.appendChild(iconElement);
iconItem.appendChild(classNameElement);
iconList.appendChild(iconItem);
});
}
// 初始化
loadIconfont();
loadAndParseIconfont().then(icons => {
renderIcons(icons);
});
// 主题色
document.addEventListener('DOMContentLoaded', function(){
var inp = document.querySelector('input[name="themeColor"]');
if(inp) inp.type = "color";
});
// 主题设置备份与恢复
jQuery(function($){
function showResult(msg, icon, reload){
if (typeof layer !== 'undefined') {
layer.msg(msg, {icon: icon, time: 1200}, function(){
if(reload){ location.reload(); }
});
} else {
alert(msg);
if (reload) location.reload();
}
}
$(document).on('click', '#backupbtn', function(){
if(typeof layer !== 'undefined'){
layer.confirm('确定要备份当前主题配置吗?', {icon:3, title:'确认'}, function(index){
layer.close(index);
doBackup();
});
} else {
if(confirm('确定要备份当前主题配置吗?')) doBackup();
}
function doBackup(){
$.post(location.href, {action:'oneblog_theme_backup', _ajax:1}, function(res){
if(res && res.success){
showResult(res.message, 1, true);
}else{
showResult(res.message||'备份失败', 2);
}
}, 'json').fail(function(){
showResult('请求失败,请检查网络', 2);
});
}
});
$(document).on('click', '#restorebtn', function(){
if(typeof layer !== 'undefined'){
layer.confirm('确定要恢复主题配置吗?<br><span style="color:red">恢复后会覆盖当前设置项,请谨慎!</span>', {icon:3, title:'确认'}, function(index){
layer.close(index);
doRestore();
});
} else {
if(confirm('确定要恢复主题配置吗?恢复操作不可逆,请谨慎!')) doRestore();
}
function doRestore(){
$.post(location.href, {action:'oneblog_theme_restore', _ajax:1}, function(res){
if(res && res.success){
showResult(res.message, 1, true);
}else{
showResult(res.message||'恢复失败', 2);
}
}, 'json').fail(function(){
showResult('请求失败,请检查网络', 2);
});
}
});
});
================================================
FILE: static/js/comments.js
================================================
/**
* Updated: 2026-01-22
* Author: ©彼岸临窗 onenote.io
*/
document.addEventListener('DOMContentLoaded', function () {
var commentList = document.querySelector('.comment-list');
if (!commentList) return;
var isLoading = false;
var noMoreComments = false;
var loadingSpinner = document.getElementById('loading-spinner');
var noMoreElement = document.getElementById('no-more');
var loadMoreBtn = document.getElementById('load-more-comments');
var isMobile = window.innerWidth <= 768;
function checkNoMore() {
var hasNext = document.querySelector('.page-navigator .next a');
if (!hasNext) {
noMoreComments = true;
noMoreElement.style.display = 'flex';
if (loadMoreBtn) loadMoreBtn.style.display = 'none';
}
return ! hasNext;
}
function loadMoreComments() {
if (isLoading || noMoreComments) return;
var nextPageUrl = document.querySelector('.page-navigator .next a')?.getAttribute('href');
if (!nextPageUrl) return checkNoMore();
isLoading = true;
if (loadingSpinner) loadingSpinner.style.display = 'flex';
if (loadMoreBtn) loadMoreBtn.style.display = 'none';
setTimeout(function () {
var xhr = new XMLHttpRequest();
xhr.open('GET', nextPageUrl, true);
xhr.onload = function () {
if (xhr.status >= 200 && xhr.status < 400) {
var tempDiv = document.createElement('div');
tempDiv.innerHTML = xhr.responseText;
commentList.insertAdjacentHTML('beforeend', tempDiv.querySelector('.comment-list').innerHTML);
// 重新观察视图区域的图片数据
if (typeof initLazyLoad === 'function') {
initLazyLoad();
}
var newNav = tempDiv.querySelector('.page-navigator')?. innerHTML;
if (newNav) document.querySelector('.page-navigator').innerHTML = newNav;
if (! checkNoMore() && loadMoreBtn) loadMoreBtn.style.display = 'flex';
} else if (loadMoreBtn) {
loadMoreBtn.style.display = 'flex';
}
isLoading = false;
if (loadingSpinner) loadingSpinner.style.display = 'none';
};
xhr.onerror = function () {
isLoading = false;
if (loadingSpinner) loadingSpinner.style.display = 'none';
if (loadMoreBtn) loadMoreBtn.style.display = 'flex';
};
xhr.send();
}, 500);
}
if (! isMobile && loadMoreBtn) {
loadMoreBtn.style.display = 'flex';
loadMoreBtn.addEventListener('click', loadMoreComments);
}
if (isMobile) {
window.addEventListener('scroll', function () {
if (isLoading || noMoreComments) return;
var scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
var scrollHeight = document.documentElement.scrollHeight || document.body. scrollHeight;
var clientHeight = document.documentElement.clientHeight || document.body.clientHeight;
if (scrollTop + clientHeight >= scrollHeight - 200) loadMoreComments();
});
}
checkNoMore();
});
/** 回复标题切换 **/
document.addEventListener('DOMContentLoaded', function() {
if (! document.querySelector('.comment-list')) return;
document.addEventListener('click', function(e) {
var replyBtn = e.target.closest('.comment-reply');
if (replyBtn) {
document.getElementById('reply-target').textContent = replyBtn.getAttribute('data-author');
document.getElementById('default-title').style.display = 'none';
document.getElementById('reply-title').style.display = '';
}
});
document.querySelector('.cancel-comment-reply a')?.addEventListener('click', function() {
document.getElementById('reply-title').style.display = 'none';
document.getElementById('default-title').style.display = '';
});
});
/** Ajax 评论提交 **/
document.addEventListener('DOMContentLoaded', function () {
const form = document. getElementById('comment-form');
const submitBtn = document.getElementById('geetest-submit-btn');
if (! form || !submitBtn) return;
const textarea = document.getElementById('textarea');
const richEditor = document.getElementById('rich-editor');
const cfSiteKey = document.getElementById('cf-sitekey')?.value;
const geetestId = document.getElementById('geetest-captcha-id')?.value;
let submitting = false;
let captchaObj = null;
let gtReady = false;
const originText = submitBtn.innerText;
function setBtn(text, loading) {
submitBtn.innerHTML = '<span class="oneblog-blank"></span>' + text;
submitBtn.disabled = true;
submitBtn.classList. toggle('is-loading', loading);
}
function resetBtn() {
submitBtn.innerText = originText;
submitBtn.disabled = false;
submitBtn.classList.remove('is-loading');
submitting = false;
}
function extractText(html) {
const div = document.createElement('div');
div.innerHTML = html;
div.querySelectorAll('script,style,noscript').forEach(el => el.remove());
for (const sel of ['.container h1', '.container p', 'body h1', 'body p', 'pre']) {
const el = div. querySelector(sel);
const txt = el?. textContent?. trim();
if (txt && txt.length < 300) return txt;
}
let text = (div.textContent || '').replace(/\s+/g, ' ').trim();
return text. length > 150 ? text.substring(0, 150) + '...' : text;
}
// 局部刷新评论列表
function reloadComments(newCoid) {
fetch(location.href, { credentials: 'same-origin' })
.then(res => res. text())
.then(html => {
const doc = new DOMParser().parseFromString(html, 'text/html');
const newList = doc.querySelector('.comment-list');
const oldList = document.querySelector('.comment-list');
if (newList) {
if (oldList) {
oldList.innerHTML = newList.innerHTML;
} else {
// 原本无评论,插入新列表
const respond = document.querySelector('.respond');
if (respond) {
respond. insertAdjacentHTML('afterend', '<ol class="comment-list">' + newList.innerHTML + '</ol>');
}
}
}
// 更新分页
const newNav = doc.querySelector('.page-navigator');
const oldNav = document.querySelector('.page-navigator');
if (newNav && oldNav) oldNav.innerHTML = newNav.innerHTML;
// 滚动到新评论并高亮
if (newCoid) {
setTimeout(() => {
const el = document.getElementById('comment-' + newCoid);
if (el) {
el.classList.add('comment-new');
el.scrollIntoView({ behavior: 'smooth', block: 'center' });
setTimeout(() => el.classList.remove('comment-new'), 3000);
}
}, 100);
}
})
.catch(() => location.reload());
}
function ajaxSubmit() {
setBtn('提交中...', true);
fetch(form. action, {
method: 'POST',
body: new FormData(form),
credentials: 'same-origin',
headers: { 'X-Requested-With': 'XMLHttpRequest' }
})
.then(res => res. text().then(text => ({ status: res.status, text })))
.then(({ status, text }) => {
let json = null;
try { json = JSON.parse(text); } catch (e) {}
if (status >= 400) {
layer.msg(json?.message || extractText(text) || '请求失败(' + status + ')', { time: 3000 });
resetBtn();
return;
}
if (json && json.success) {
layer.msg(json.message || '评论提交成功', { time: 2000 });
textarea.value = '';
if (richEditor) richEditor.innerHTML = '';
document.querySelector('.cancel-comment-reply a')?.click();
reloadComments(json.coid);
resetBtn();
return;
}
if (json && ! json.success) {
layer.msg(json.message || '提交失败', { time: 3000 });
resetBtn();
return;
}
const keywords = ['失败', '错误', '禁止', '拒绝', '不允许', '太快', '垃圾', '不合规','error', 'fail', 'spam'];
if (keywords.some(k => text.toLowerCase().includes(k))) {
layer.msg(extractText(text) || '提交失败', { time: 3000 });
} else {
layer.msg('提交状态未知,正在刷新...', { time: 1500 });
setTimeout(() => location.reload(), 1800);
}
resetBtn();
})
.catch(() => {
layer.msg('网络错误,请稍后重试', { time: 3000 });
resetBtn();
});
}
function renderCF() {
setBtn('等待验证...', true);
let wrap = document.getElementById('cf-rich-wrap');
if (!wrap) {
wrap = document. createElement('div');
wrap.id = 'cf-rich-wrap';
richEditor.after(wrap);
} else {
wrap.innerHTML = '';
}
turnstile.render(wrap, {
sitekey: cfSiteKey,
size: 'flexible',
theme: document. documentElement.classList.contains('night') ? 'dark' : 'light',
callback: token => {
let input = form.querySelector('[name="cf_token"]');
if (! input) {
input = document.createElement('input');
input. type = 'hidden';
input.name = 'cf_token';
form. appendChild(input);
}
input.value = token;
setTimeout(() => {
if (wrap) {
wrap.remove();
}
}, 2000);
ajaxSubmit();
},
'error-callback': () => {
layer.msg('验证加载失败,请刷新重试');
resetBtn();
}
});
}
if (geetestId && !cfSiteKey) {
window.initGeetest4({ captchaId: geetestId, product: 'bind' }, obj => {
captchaObj = obj;
obj.onReady(() => gtReady = true);
obj.onSuccess(() => {
setBtn('提交中...', true);
const result = obj.getValidate();
Object.keys(result).forEach(k => {
let input = form.querySelector(`[name="${k}"]`);
if (! input) {
input = document.createElement('input');
input.type = 'hidden';
input.name = k;
form.appendChild(input);
}
input.value = result[k];
});
ajaxSubmit();
});
obj.onError(() => {
layer.msg('验证组件出错,请刷新重试');
resetBtn();
});
});
}
submitBtn.addEventListener('click', function (e) {
e.preventDefault();
if (submitting) return;
submitting = true;
if (! textarea.value.trim()) {
layer.msg('评论内容不能为空');
resetBtn();
return;
}
if (cfSiteKey) {
renderCF();
} else if (geetestId) {
if (! captchaObj || !gtReady) {
layer. msg('验证组件加载中,请稍后');
resetBtn();
return;
}
setBtn('等待验证...', true);
captchaObj.showCaptcha();
} else {
ajaxSubmit();
}
});
});
================================================
FILE: static/js/emoji.js
================================================
/**
* Updated: 2025-07-26
* Author: ©彼岸临窗 onenote.io
*
* 注释含命名规范,开源不易,如需引用请注明来源:彼岸临窗 https://onenote.io。
* 本主题已取得软件著作权(登记号:2025SR0334142)和外观设计专利(专利号:第7121519号),请严格遵循GPL-2.0协议使用本主题及源码。
*/
document.addEventListener('DOMContentLoaded', function() {
var emojiBox = document.querySelector('.icon-emoji');
if (!emojiBox) return; // 如果当前页面不存在表情按钮,则不执行该JS
const richEditor = document.getElementById('rich-editor');
const textarea = document.getElementById('textarea');
const emojiBtn = document.getElementById('emoji-btn');
const emojiPicker = document.getElementById('emoji-picker');
const emojiContainer = document.querySelector('.emoji-container');
const emojiCategories = document.querySelectorAll('.emoji-category');
const emojiBaseUrl = '/usr/themes/OneBlog/static/img/emoji/';
// 保存最近一次在编辑器内的selection
let savedRange = null;
function saveSelection() {
const sel = window.getSelection();
if (!sel.rangeCount) return;
const range = sel.getRangeAt(0);
// 只保存在编辑器内的range
let node = range.commonAncestorContainer;
while (node) {
if (node === richEditor) {
savedRange = range.cloneRange();
return;
}
node = node.parentNode;
}
}
function restoreSelection() {
if (savedRange) {
const sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(savedRange);
richEditor.focus();
} else {
// 没有保存,直接移到末尾
moveCursorToEnd(richEditor);
}
}
function moveCursorToEnd(element) {
const range = document.createRange();
range.selectNodeContents(element);
range.collapse(false);
const sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
element.focus();
}
// 只要编辑器有操作就保存selection
['mouseup', 'keyup', 'selectionchange'].forEach(evt => {
document.addEventListener(evt, function() {
// 只在编辑器内操作时保存
if (document.activeElement === richEditor) {
saveSelection();
}
});
});
// 富文本输入时同步
richEditor.addEventListener('input', function() {
syncTextarea();
});
// 粘贴支持
richEditor.addEventListener('paste', function(e) {
e.preventDefault();
let text = (e.clipboardData || window.clipboardData).getData('text');
text = text.replace(/\[emoji:([a-zA-Z0-9_]+)\]/g, function(match, p1) {
return `<img class="biaoqing" src="${emojiBaseUrl}${p1}.svg" alt="[emoji:${p1}]" data-emoji="${p1}">`;
});
document.execCommand('insertHTML', false, text);
syncTextarea();
});
// 插入表情
function insertEmojiImg(emojiName) {
restoreSelection();
let img = document.createElement('img');
img.src = emojiBaseUrl + emojiName + '.svg';
img.alt = `[emoji:${emojiName}]`;
img.className = 'biaoqing';
img.setAttribute('data-emoji', emojiName);
insertNodeAtCursor(img);
// 光标放到表情后
placeCaretAfterNode(img);
syncTextarea();
// 重新保存selection
saveSelection();
}
function insertNodeAtCursor(node) {
const sel = window.getSelection();
if (sel.rangeCount > 0) {
let range = sel.getRangeAt(0);
range.deleteContents();
range.insertNode(node);
} else {
richEditor.appendChild(node);
}
}
function placeCaretAfterNode(node) {
const range = document.createRange();
range.setStartAfter(node);
range.collapse(true);
const sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
richEditor.focus();
}
function loadEmojis(category) {
emojiContainer.innerHTML = '';
const emojiData = {
emotion: [
'101','102','103','104','105','106','107',
'108','109','110','111','112','113','114',
'115','116','117','118','119','120','121',
'122','123','124','125','126','127','128',
'129','130','131','132','133','134',
],
special: [
'201','202','203','204','205','206','207',
'208','209','210',
],
kaomoji: [
'(✪ω✪)','(*^▽^*)','٩(๑❛ᴗ❛๑)۶',
'(๑´ㅂ`๑) ','(◕ᴗ◕✿)',
'(๑¯∀¯๑)','(^ω^)','(★ᴗ★)',
'(*^__^*) ','(╯︵╰)','(T_T)',
'╥﹏╥','(。•́︿•̀。)','>_<',
'(•ˇ‸ˇ•。)','。◕ᴗ◕。','(´•༝•`)',
]
};
const emojis = emojiData[category];
emojis.forEach(emoji => {
if(category === 'kaomoji') {
const span = document.createElement('span');
span.textContent = emoji;
span.className = 'kaomoji';
emojiContainer.appendChild(span);
span.addEventListener('click', () => {
restoreSelection();
insertNodeAtCursor(document.createTextNode(emoji));
syncTextarea();
saveSelection();
emojiPicker.style.display = 'none';
});
} else {
const img = document.createElement('img');
img.src = emojiBaseUrl + emoji + '.svg';
img.alt = `[emoji:${emoji}]`;
img.setAttribute('data-emoji', emoji);
emojiContainer.appendChild(img);
img.addEventListener('click', () => {
insertEmojiImg(emoji);
emojiPicker.style.display = 'none';
});
}
});
}
// emoji按钮,只负责弹窗
let lastActiveCategory = 'emotion';
emojiBtn.addEventListener('click', (e) => {
e.stopPropagation();
if (emojiPicker.style.display === 'none') {
emojiPicker.style.display = 'block';
loadEmojis(lastActiveCategory);
setActiveClass(lastActiveCategory);
} else {
emojiPicker.style.display = 'none';
clearActiveClass();
}
});
emojiCategories.forEach(button => {
button.addEventListener('click', (event) => {
event.preventDefault();
const category = button.getAttribute('data-category');
lastActiveCategory = category;
loadEmojis(category);
setActiveClass(category);
});
});
document.addEventListener('click', (event) => {
if (!emojiPicker.contains(event.target) && event.target !== emojiBtn) {
emojiPicker.style.display = 'none';
clearActiveClass();
}
});
function clearActiveClass() {
emojiCategories.forEach(btn => btn.classList.remove('active'));
}
function setActiveClass(category) {
emojiCategories.forEach(button => {
if (button.getAttribute('data-category') === category) {
button.classList.add('active');
} else {
button.classList.remove('active');
}
});
}
function htmlToShortcode(html) {
html = html.replace(/<div>|<br\s*\/?>/gi, '\n');
html = html.replace(/<img[^>]*data-emoji="([a-zA-Z0-9_]+)"[^>]*>/gi, function(_, emoji) {
return `[emoji:${emoji}]`;
});
let tmp = document.createElement('div');
tmp.innerHTML = html;
return tmp.textContent || tmp.innerText || '';
}
function syncTextarea() {
textarea.value = htmlToShortcode(richEditor.innerHTML);
}
syncTextarea();
const commentForm = document.getElementById('comment-form');
if(commentForm){
commentForm.addEventListener('submit', function() {
syncTextarea();
});
}
});
================================================
FILE: static/js/main.js
================================================
/**
* Updated: 2026-05-02
* Author: ©彼岸临窗 onenote.io
*
* 注释含命名规范,开源不易,如需引用请注明来源:彼岸临窗 https://onenote.io。
* 本主题已取得软件著作权(登记号:2025SR0334142)和外观设计专利(专利号:第7121519号),请严格遵循GPL-2.0协议使用本主题及源码。
*/
/**核心依赖请勿改动或删除 否则会出现各种异常**/
const _0x5a1b=['T25lQmxvZw==','aHR0cHM6Ly9kb2NzLm9uZW5vdGUuaW8=','Y29weXJpZ2h0LXBj','Y29weXJpZ2h0LW0=','aHJlZg==','dGV4dENvbnRlbnQ=','dHJpbQ==','PGRpdiBjbGFzcz0iY29weXJpZ2h0LWluZm8iPuW8gOa6kOS4jeaYk++8jOivt+WwiumHjeS9nOiAheeJiOadg++8jOS/neeVmeWfuuacrOeahOeJiOadg+S/oeaBr+OAgjwvZGl2Pg==','bG9hZA=='];const _0x2f9c=function(_0x5a1b3a,_0x2f9c42){_0x5a1b3a=_0x5a1b3a-0x0;let _0x3c8d9f=_0x5a1b[_0x5a1b3a];if(_0x2f9c['init']===undefined){(function(){const _0x1='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';window['atob']||(window['atob']=function(_0x2){const _0x3=String(_0x2)['replace'](/=+$/,'');let _0x4='';for(let _0x5=0x0,_0x6,_0x7,_0x8=0x0;_0x7=_0x3['charAt'](_0x8++);~_0x7&&(_0x6=_0x5%0x4?_0x6*0x40+_0x7:_0x7,_0x5++%0x4)?_0x4+=String['fromCharCode'](0xff&_0x6>>(-0x2*_0x5&0x6)):0x0){_0x7=_0x1['indexOf'](_0x7);}return _0x4;});}());_0x2f9c['decode']=function(_0x9){const _0xa=atob(_0x9);let _0xb='';for(let _0xc=0x0;_0xc<_0xa['length'];_0xc++){_0xb+='%'+('00'+_0xa['charCodeAt'](_0xc)['toString'](0x10))['slice'](-0x2);}return decodeURIComponent(_0xb);};_0x2f9c['cache']={};_0x2f9c['init']=true;}const _0xd=_0x2f9c['cache'][_0x5a1b3a];if(_0xd===undefined){_0x3c8d9f=_0x2f9c['decode'](_0x3c8d9f);_0x2f9c['cache'][_0x5a1b3a]=_0x3c8d9f;}else{_0x3c8d9f=_0xd;}return _0x3c8d9f;};function base(){const _0xtext=_0x2f9c('0x0');const _0xhref=_0x2f9c('0x1');const _0xids=[_0x2f9c('0x2'),_0x2f9c('0x3')];let _0xok=true;for(const _0xid of _0xids){const _0xel=document['getElementById'](_0xid);if(!_0xel){_0xok=false;break;}const _0xh=_0xel['getAttribute'](_0x2f9c('0x4'));const _0xt=_0xel[_0x2f9c('0x5')][_0x2f9c('0x6')]();if(!_0xh||!_0xt||_0xh!==_0xhref||_0xt!==_0xtext){_0xok=false;break;}}if(!_0xok){document['body']['innerHTML']=_0x2f9c('0x7');}}window['addEventListener'](_0x2f9c('0x8'),base);
//自动显示与隐藏顶部菜单,给阅读区域留出更大空间
(function () {
if (window.innerWidth < 768) {
var topMenu = document.querySelector(".header");
if (!topMenu) return;
var lastScrollTop = 50;
function throttle(func, delay) {
var lastTime = 0;
return function () {
var now = Date.now();
if (now - lastTime >= delay) {
func.apply(this, arguments);
lastTime = now;
}
};
}
function handleScroll() {
var scrollTop = window.pageYOffset || document.documentElement.scrollTop;
if (scrollTop > 50 && scrollTop > lastScrollTop) {
topMenu.classList.add("hide");
} else {
topMenu.classList.remove("hide");
}
lastScrollTop = scrollTop <= 50 ? 50 : scrollTop;
}
window.addEventListener("scroll", throttle(handleScroll, 100), false);
}
})();
/*自定义菜单效果*/
const $menu = $(".menu");
const $openBtn = $(".icon-nav");
const $body = $("body");
const $header = $(".header");
const $commonElements = $(".blur");
const searchLayer = $('.search-layer');
const $main = $(".main");
function showNativeMenu() {
if (!$main.length) return;
const mainRect = $main[0].getBoundingClientRect();
const scrollTop = $(window).scrollTop();
const menuTop = mainRect.top + scrollTop;
const menuLeft = mainRect.right + 10;
$menu.css({ left: menuLeft, top: menuTop }).addClass('active');
setTimeout(() => {
$(document).on('click.menu', hideNativeMenu);
}, 10);
$('.menu > li').each(function(idx) {
$(this)
.addClass('animate__animated fadeInLeftShort')
.css('animation-delay', (idx * 0.09) + 's');
});
}
function hideNativeMenu() {
$menu.removeClass('active');
$(document).off('click.menu');
$('.menu > li').each(function(){
$(this)
.removeClass('animate__animated fadeInLeftShort')
.css('animation-delay', '');
});
}
// 切换移动菜单状态
function toggleMenuState(isOpen) {
$menu.toggleClass("active", isOpen);
$body.toggleClass("noscroll", isOpen);
$commonElements.add($header).toggleClass("no-scroll", isOpen);
}
// 搜索框功能
function openSearch() {
hideNativeMenu();//打开搜索框需要关闭菜单
searchLayer.fadeIn(200).addClass('search-active');
if (window.innerWidth < 768) {
$body.addClass('noscroll');
$commonElements.addClass('no-scroll');
$header.addClass('bottom-line');
}
}
function closeSearch() {
searchLayer.removeClass('search-active').fadeOut(200);
if (window.innerWidth < 768) {
$body.removeClass('noscroll');
$commonElements.removeClass('no-scroll');
$header.removeClass('bottom-line');
}
}
function closeSearchIfOpen() {
if (searchLayer.hasClass('search-active')) {
closeSearch();
return true;
}
return false;
}
// 菜单按钮点击事件
$('.icon-nav').on('click', function (e) {
e.stopPropagation();
if (window.innerWidth >= 768) {
// PC端 - 使用现有菜单容器
showNativeMenu();
} else {
// 移动端逻辑
const wasSearchOpen = closeSearchIfOpen();
const isMenuOpen = $menu.hasClass("active");
if (wasSearchOpen && !isMenuOpen) {
setTimeout(() => toggleMenuState(true), 10);
} else {
toggleMenuState(!isMenuOpen);
}
}
});
// 事件委托处理
$(document)
.on("click", ".menu", function(e) {
e.stopPropagation();
})
.on("click", "#close", function(e) {
e.stopPropagation();
toggleMenuState(false);
})
.on("click", ".close-search", function(e) {
e.stopPropagation();
closeSearch();
})
.on("click", function(e) {
// 移动端菜单关闭
if ($menu.hasClass("active") && !$(e.target).closest('.menu, .icon-nav').length) {
toggleMenuState(false);
}
// PC端菜单关闭
if ($menu.hasClass('active') && !$(e.target).closest('.menu, .icon-nav').length) {
hideNativeMenu();
}
// 搜索框关闭
if (searchLayer.hasClass('search-active') && !$(e.target).closest('.search-layer, #search-btn').length) {
closeSearch();
}
});
// 搜索按钮
$('#search-btn').on('click', function(e) {
e.stopPropagation();
searchLayer.hasClass('search-active') ? closeSearch() : openSearch();
});
// ESC键处理
$(document).keyup(function(e) {
if (e.key === 'Escape') {
closeSearch();
if (window.innerWidth < 768) {
if ($menu.hasClass("active")) {
toggleMenuState(false);
}
} else {
if ($menu.hasClass('active')) {
hideNativeMenu();
}
}
}
});
/** 顶部菜单结束 **/
/**首页轮播图初始化**/
function renderBanner(options = {}) {
const {
bannerSwitch = 'on',
jsonId = 'banner-json',
containerSelector = '.banner-container'
} = options;
const isMobile = window.innerWidth < 768;
const isHome = location.pathname === '/' || location.pathname === '/index';
if (bannerSwitch !== 'on' || !isHome) return;
const jsonEl = document.getElementById(jsonId);
if (!jsonEl) return;
let posts = [];
try {
posts = JSON.parse(jsonEl.textContent);
} catch (e) {
console.error('无效的 banner JSON', e);
return;
}
if (!posts.length) return;
const container = document.querySelector(containerSelector);
if (!container) return;
container.innerHTML = ''; // 清空容器
// 移除骨架屏
const skeleton = document.getElementById('banner-skeleton');
if (skeleton) skeleton.remove();
if (isMobile) {
// 生成移动端 swiper
container.innerHTML = `
<div class="swiper m">
<div class="swiper-wrapper">
${posts.slice(0, 3).map(post => `
<div class="swiper-slide">
<a href="${post.link}" title="${post.title}" style="background-image:url('${post.thumb}')">
<h1>${post.title}</h1>
</a>
</div>
`).join('')}
</div>
<div class="swiper-pagination m"></div>
</div>
`;
if (typeof Swiper !== 'undefined') {
new Swiper('.swiper', {
autoplay: true,
loop: true,
pagination: {
el: '.swiper-pagination',
type: 'custom',
renderCustom: function (swiper, current, total) {
return Array.from({ length: total }).map((_, i) =>
`<span class="swiper-pagination-bullet${i + 1 === current ? ' swiper-pagination-bullet-active' : ''}"></span>`
).join('');
}
}
});
}
} else {
// 生成 PC 端 banner
const banner = document.createElement('div');
banner.className = 'banner pc';
const item1 = document.createElement('div');
item1.className = 'banner-item';
item1.innerHTML = `
<a href="${posts[0].link}" title="${posts[0].title}">
<div class="banner-thumb lazy-load" data-src="${posts[0].thumb}">
<div class="banner-title"><h1>${posts[0].title}</h1></div>
</div>
</a>
`;
const item2 = document.createElement('div');
item2.className = 'banner-item';
for (let i = 1; i <= 2; i++) {
if (!posts[i]) continue;
item2.innerHTML += `
<a href="${posts[i].link}" title="${posts[i].title}">
<div class="banner-thumb lazy-load" data-src="${posts[i].thumb}">
<div class="banner-title"><h1>${posts[i].title}</h1></div>
</div>
</a>
`;
}
banner.appendChild(item1);
banner.appendChild(item2);
container.appendChild(banner);
// 调用懒加载函数,如有需要
if (typeof initLazyLoad === 'function') {
initLazyLoad();
}
}
}
// 懒加载逻辑
function initLazyLoad() {
const lazyImages = Array.from(document.querySelectorAll('.lazy-load:not(.loaded):not(.failed)'));
let loading = false;
// 队列中第一个进入视口的图片加载
function tryLoadNext() {
if (loading) return;
const next = lazyImages.find(img => img.classList.contains('in-view') && !img.classList.contains('loaded') && !img.classList.contains('failed'));
if (!next) return;
loading = true;
const src = next.getAttribute('data-src');
const tempImg = new Image();
tempImg.src = src;
tempImg.onload = () => {
if (next.tagName.toLowerCase() === 'img') {
next.src = src;
} else {
next.style.backgroundImage = `url('${src}')`;
}
next.classList.add('loaded');
loading = false;
tryLoadNext();
};
tempImg.onerror = () => {
if (next.tagName.toLowerCase() === 'img') {
next.src = '/usr/themes/OneBlog/static/img/error.jpg';
} else {
next.style.backgroundImage = `url('/usr/themes/OneBlog/static/img/error.jpg')`;
}
next.classList.add('failed');
loading = false;
tryLoadNext();
};
}
// 只标记进入视口,不立即加载
const io = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.classList.add('in-view');
tryLoadNext();
}
});
}, {
rootMargin: '0px',
threshold: 0.1
});
lazyImages.forEach(img => io.observe(img));
}
//加载更多
jQuery(document).ready(function($) {
// 初始化懒加载
initLazyLoad();
let isLoading = false;
function loadNextPage() {
if (isLoading) return;
var $next = $('.next');
var href = $next.attr('href');
if (!href) return;
isLoading = true;
$next.addClass('loading').text('正在努力加载…');
$.ajax({
url: href,
type: 'get',
error: function(request) {
console.error('加载失败:', request);
$next.removeClass('loading').text('点击查看更多');
isLoading = false;
},
success: function(data) {
$next.removeClass('loading').text('点击查看更多');
// 提取新文章内容
var $res = $(data).find('.post,.photo');
$('#posts,#photos').append($res.fadeIn(300));
// 替换下一页链接或结束提示
var newhref = $(data).find('.next').attr('href');
if (newhref) {
$next.attr('href', newhref);
} else {
$next.remove();
document.getElementById("loadmore").innerHTML = "— 暂无更多内容 —";
}
initLazyLoad();
$res.each(function() {
applyExcerptTruncate(this); // 仅对新增元素处理
});
isLoading = false;
}
});
}
// PC 端点击加载
$('.next').click(function(e) {
e.preventDefault();
loadNextPage();
});
// 移动端自动触底加载
if ($(window).width() < 768) {
$(window).on('scroll', function() {
if (isLoading) return;
// 距离底部 100px 内触发加载
if ($(window).scrollTop() + $(window).height() + 100 >= $(document).height()) {
loadNextPage();
}
});
}
});
/*返回顶部,按钮在页面最底部固定浮动*/
$(document).ready(function(){
// 判断是否为移动端(屏幕宽度 < 768px)
var isMobile = window.innerWidth < 768;
if (isMobile) return; // 移动端不执行返回顶部逻辑
$(window).scroll(function(){
var scroTop = $(window).scrollTop();
var awayBtm = $(document).height() - $(window).scrollTop() - $(window).height();
var minAwayBtm = 270;
if(scroTop > 400){
$('#gototop').fadeIn(500);
$('#gototop').removeClass('hidden');
} else {
$('#gototop').fadeOut(500);
}
if (awayBtm <= minAwayBtm){
$('#gototop').addClass('newtotop');
} else {
$('#gototop').removeClass('newtotop');
}
});
$('#gototop').click(function(){
$('html,body').animate({scrollTop: 0}, 'fast');
});
});
// 摘要截取函数:移动端显示40字符摘要
function applyExcerptTruncate(context = document) {
if (window.innerWidth > 768) return; // 只在移动端执行
context.querySelectorAll('.post_preview p').forEach(el => {
let text = el.getAttribute('data-full') || el.textContent.trim();
// 首次设置 data-full 保证加载更多时不重复截断
if (!el.getAttribute('data-full')) {
el.setAttribute('data-full', text);
}
if (text.length > 40) {
el.textContent = text.slice(0, 40) + '...';
} else {
el.textContent = text;
}
});
}
// 首次加载时执行
document.addEventListener('DOMContentLoaded', function () {
renderBanner({
bannerSwitch: typeof bannerSwitch !== 'undefined' ? bannerSwitch : 'on'
});
applyExcerptTruncate();
finishLoading(1000);
});
/** 用户登录弹框 **/
document.addEventListener('DOMContentLoaded', function() {
var loginButton = document.getElementById('login-button');
if (!loginButton) {
return;
}
var maxAttempts = 5; // 最大尝试次数
var lockoutMinutes = 180; // 锁定时间,以分钟为单位
loginButton.addEventListener('click', openLoginPopup);
function openLoginPopup() {
if (isLockedOut()) {
layer.msg(`登录过于频繁,请稍后再试!`);
return;
} else {
clearLoginAttempts();
}
layer.open({
type: 1,
title: ' ',
area: ['320px', 'auto'],
skin: 'layui-memos',
shadeClose: true,
closeBtn: 1,
content: `
<form class="memos-form" id="login-form" method="post">
<h3>登录</h3>
<div class="flex-column">
<label for="name">账号</label>
<div class="inputForm">
<i class="iconfont icon-zhanghao"></i>
<input required class="input" type="text" name="name" id="name" placeholder="请输入账号" />
</div>
</div>
<div class="flex-column">
<label for="password">密码</label>
<div class="inputForm">
<i class="iconfont icon-mima"></i>
<input required class="input" type="password" name="password" id="password" placeholder="请输入密码" />
<i class="iconfont icon-eye" id="toggle-password"></i>
</div>
</div>
<button type="submit" id="submit-button" class="button-submit">登录</button>
</form>
`,
success: function(layero, index) {
var togglePassword = document.getElementById('toggle-password');
var passwordInput = document.getElementById('password');
togglePassword.addEventListener('click', function() {
if (passwordInput.type === 'password') {
passwordInput.type = 'text';
togglePassword.classList.replace('icon-eye', 'icon-noeye');
} else {
passwordInput.type = 'password';
togglePassword.classList.replace('icon-noeye', 'icon-eye');
}
});
var loginForm = document.getElementById('login-form');
var submitButton = document.getElementById('submit-button');
loginForm.addEventListener('submit', function(e) {
e.preventDefault();
submitButton.disabled = true;
submitButton.textContent = '正在登录,请稍后...';
submitButton.classList.add('not-allowed');
var formData = new FormData(loginForm);
var xhr = new XMLHttpRequest();
xhr.open('POST', loginAction, true);
xhr.onreadystatechange = function() {
if (xhr.readyState === XMLHttpRequest.DONE) {
if (xhr.status === 200) {
if (xhr.responseURL.includes('/admin/')) {
clearLoginAttempts();
location.reload();
} else {
handleFailedLogin();
}
} else {
handleFailedLogin();
}
resetButtonState();
}
};
xhr.onerror = function() {
handleFailedLogin();
resetButtonState();
};
xhr.send(formData);
});
}
});
}
function handleFailedLogin() {
var attempts = parseInt(localStorage.getItem('loginAttempts') || '0');
attempts += 1;
localStorage.setItem('loginAttempts', attempts);
if (attempts >= maxAttempts) {
var lockoutTime = Date.now() + lockoutMinutes * 60 * 1000;
localStorage.setItem('lockoutTime', lockoutTime);
var lockoutHours = formatMinutesToHours(lockoutMinutes);
layer.msg(`尝试次数过多,您已被锁定${lockoutHours}!`, {
time: 3000
}, function() {
layer.closeAll();
});
} else {
layer.msg(`账号或密码错误,请检查后重新登录!`, {
time: 2000
});
}
}
function isLockedOut() {
var lockoutTime = parseInt(localStorage.getItem('lockoutTime') || '0');
return Date.now() < lockoutTime;
}
function clearLoginAttempts() {
localStorage.removeItem('loginAttempts');
localStorage.removeItem('lockoutTime');
}
function resetButtonState() {
var submitButton = document.getElementById('submit-button');
submitButton.disabled = false;
submitButton.textContent = '登录';
submitButton.classList.remove('not-allowed');
}
function formatMinutesToHours(minutes) {
var hours = Math.floor(minutes / 60);
var remainingMinutes = minutes % 60;
return remainingMinutes > 0 ? `${hours}小时${remainingMinutes}分钟` : `${hours}小时`;
}
});
/** 用户登录弹框结束 **/
/** 动态发布弹框(适配插件九宫格上传,延迟上传)**/
$(function () {
const cfg = window.memosConfig || {};
const imageEnabled = !!cfg.enabled || !!window.__MEMOS_IMAGE__;
const $publishBtn = $('#publish-button');
if (!$publishBtn.length) return;
const uploadUrl = cfg.memosUploadUrl || '/action/memos-upload';
const signUrl = cfg.memosSignUrl || '/action/memos-sign';
const useCos = !!cfg.memosUseCos;
let fileQueue = [];
let uploading = false;
let layerIndex = null;
function updateLayerHeight() {
if (layerIndex == null) return;
const $layer = $('#layui-layer' + layerIndex);
if (!$layer.length) return;
const $content = $layer.find('.layui-layer-content');
$content.css({ height: 'auto', overflow: 'visible' });
const titleH = $layer.find('.layui-layer-title').outerHeight() || 0;
const contentH = $content.outerHeight() || 0;
layer.style(layerIndex, { height: titleH + contentH });
}
function updateAddButtonVisibility() {
$('#memos-image-add').toggle(fileQueue.length < 9);
updateLayerHeight();
}
function clearQueue() {
fileQueue.forEach(f => {
try { URL.revokeObjectURL(f.previewUrl); } catch (e) {}
});
fileQueue = [];
}
function buildFormHtml(commentUrl, csrfToken) {
// 插件未启用:仅文本发布(无图片 UI、无 memos_imgs)
if (!imageEnabled) {
return `
<form class="memos-form" id="comment-form" method="post" action="${commentUrl}">
<h3>发布动态</h3>
<textarea name="text" id="textarea" required></textarea>
<input type="hidden" name="_" value="${csrfToken}">
<button type="button" id="submit-memos" class="button-submit">发布</button>
</form>
`;
}
// 插件启用:带图片 UI
return `
<form class="memos-form" id="comment-form" method="post" action="${commentUrl}">
<h3>发布动态</h3>
<textarea name="text" id="textarea" required></textarea>
<div class="memos-images">
<div class="memos-image-list" id="memos-image-list">
<label class="memos-image-add" id="memos-image-add">
<i class="iconfont icon-add"></i>
<input type="file" id="memos-image-input" accept="image/*" multiple hidden>
</label>
</div>
</div>
<input type="hidden" name="memos_imgs" id="memos_imgs">
<input type="hidden" name="_" value="${csrfToken}">
<button type="button" id="submit-memos" class="button-submit">发布</button>
</form>
`;
}
$publishBtn.on('click', function () {
const csrfToken = document.querySelector('meta[name="csrf-token"]')?.getAttribute('content') || '';
const commentUrl = document.querySelector('meta[name="comment-url"]')?.getAttribute('content') || '';
if (!commentUrl) {
layer.msg('评论接口不存在');
return;
}
clearQueue();
layerIndex = layer.open({
type: 1,
move: false,
skin: 'layui-memos',
area: ['420px', 'auto'],
title: ' ',
shadeClose: true,
closeBtn: 1,
content: buildFormHtml(commentUrl, csrfToken),
success: updateLayerHeight,
end: function () {
// 关闭弹框时释放预览 URL,避免内存泄漏
clearQueue();
}
});
});
// 插件未启用:不注册图片相关事件
if (!imageEnabled) {
// 仅保留发布逻辑
} else {
// 选择图片:仅当插件启用时才会出现该 input
$(document).on('change', '#memos-image-input', function () {
const files = this.files;
if (!files || !files.length) return;
if (fileQueue.length + files.length > 9) {
layer.msg('最多只能上传 9 张图片');
this.value = '';
return;
}
Array.from(files).forEach(file => {
if (!file.type || !file.type.startsWith('image/')) {
layer.msg('仅支持图片文件');
return;
}
const id = Date.now() + '-' + Math.random().toString(36).slice(2);
const previewUrl = URL.createObjectURL(file);
fileQueue.push({ id, file, previewUrl });
$('#memos-image-add').before(`
<div class="memos-image-item" data-id="${id}">
<img src="${previewUrl}">
<span class="remove">×</span>
<div class="progress-text">0%</div>
</div>
`);
});
updateAddButtonVisibility();
this.value = '';
});
// 删除图片
$(document).on('click', '.memos-image-item .remove', function () {
const $item = $(this).closest('.memos-image-item');
const id = $item.data('id');
fileQueue = fileQueue.filter(f => {
if (f.id === id) {
try { URL.revokeObjectURL(f.previewUrl); } catch (e) {}
return false;
}
return true;
});
$item.remove();
updateAddButtonVisibility();
});
}
function uploadFile(fileItem) {
return new Promise((resolve, reject) => {
const $item = $(`.memos-image-item[data-id="${fileItem.id}"]`);
const $text = $item.find('.progress-text');
let last = 0;
const setPercent = (p) => {
const percent = Math.max(last, Math.min(100, p));
last = percent;
if (percent >= 100) {
$item.removeClass('loading').addClass('processing');
$text.text('正在处理');
} else {
$item.addClass('loading');
$text.text(percent + '%');
}
};
setPercent(0);
if (useCos) {
const signForm = new FormData();
signForm.append('fileName', fileItem.file.name);
$.ajax({
url: signUrl,
type: 'POST',
data: signForm,
processData: false,
contentType: false,
dataType: 'json',
success(res) {
if (!res || !res.uploadUrl || !res.publicUrl) {
reject(res && res.error ? res.error : '签名失败');
return;
}
const xhr = new XMLHttpRequest();
xhr.open('PUT', res.uploadUrl, true);
xhr.upload.addEventListener('progress', function (e) {
if (e.lengthComputable) {
setPercent(Math.round((e.loaded / e.total) * 100));
}
}, false);
xhr.upload.addEventListener('load', function () {
setPercent(100);
}, false);
xhr.onload = function () {
if (xhr.status >= 200 && xhr.status < 300) {
resolve(res.publicUrl);
} else {
console.error('COS PUT failed', xhr.status, xhr.responseText);
reject('COS 上传失败(' + xhr.status + ')');
}
};
xhr.onerror = function () {
reject('COS 上传失败');
};
xhr.send(fileItem.file);
},
error(xhr) {
console.error('memos-sign error', xhr && xhr.status, xhr && xhr.responseText);
reject('签名接口不可用');
}
});
return;
}
// 本地上传
const formData = new FormData();
formData.append('file', fileItem.file);
$.ajax({
url: uploadUrl,
type: 'POST',
data: formData,
processData: false,
contentType: false,
dataType: 'json',
xhr() {
const xhr = new window.XMLHttpRequest();
xhr.upload.addEventListener('progress', function (e) {
if (e.lengthComputable) {
setPercent(Math.round((e.loaded / e.total) * 100));
}
}, false);
xhr.upload.addEventListener('load', function () {
setPercent(100);
}, false);
return xhr;
},
success(res) {
setPercent(100);
if (res && res.url) resolve(res.url);
else reject(res && res.error ? res.error : '图片上传失败');
},
error() {
$item.removeClass('loading processing');
$text.text('');
reject('图片上传失败(接口不可用)');
}
});
});
}
// 发布(无论插件是否启用都要支持)
$(document).on('click', '#submit-memos', function () {
if (uploading) return;
const text = $('#textarea').val().trim();
if (!text) {
layer.msg('请输入内容');
return;
}
uploading = true;
const $btn = $('#submit-memos');
$btn.prop('disabled', true).addClass('is-disabled').text('正在发布...');
const reset = () => {
$('.memos-image-item')
.removeClass('loading processing')
.find('.progress-text').text('');
};
const submitComment = () => {
$.ajax({
url: $('#comment-form').attr('action'),
type: 'POST',
data: $('#comment-form').serialize(),
success(res) {
if (res && res.error) layer.msg(res.error);
else {
layer.closeAll();
layer.msg('发布成功');
setTimeout(() => location.reload(), 1000);
}
},
error() {
layer.msg('发布失败');
},
complete() {
reset();
uploading = false;
$btn.prop('disabled', false).removeClass('is-disabled').text('发布');
}
});
};
// 插件未启用:没有图片字段,直接提交
if (!imageEnabled) {
submitComment();
return;
}
// 插件启用但未选图:清空 memos_imgs 直接提交
if (!fileQueue.length) {
$('#memos_imgs').val('');
submitComment();
return;
}
Promise.all(fileQueue.map(uploadFile))
.then(urls => {
$('#memos_imgs').val(JSON.stringify(urls));
submitComment();
})
.catch(err => {
reset();
uploading = false;
$btn.prop('disabled', false).removeClass('is-disabled').text('发布');
layer.msg(err);
});
});
});
/***评论点赞以及计数***/
$(document).ready(function() {
$("#comments").on('click', "a[id^='commentLikeOpt']", function() {
var coid = $(this).data("coid");
var recording = $(this).attr("data-recording");
if(recording){
layer.msg('你已经点过赞啦!感谢你的喜爱!');
return;
}
$.ajax({
url: commentLikeUrl,
type: "POST",
data: {
coid: coid,
behavior: 'dz'
},
async: true,
dataType: "json",
success: function(data) {
if (data == null) {} else {
if(data.state == 'success'){
$('#commentLikeSpan-'+coid).text(data.num);
$('#commentLikeI-'+coid).removeClass("icon-like").addClass("icon-liked");
$('#commentLikeOpt-'+coid).attr("data-recording", "1");
} else {
alert(data.message || "点赞失败,请稍后重试");
}
}
},
error: function(err) {
alert("点赞失败,请稍后重试");
}
});
});
});
/***评论点赞结束***/
/**文件完整性检查**/
var _0x1f3a=['aW5uZXJIVE1M','PGRpdiBjbGFzcz0iY29weXJpZ2h0LWluZm8iPuW8gOa6kOS4jeaYk++8jOivt+WwiumHjeS9nOiAheeJiOadg++8jOS/neeVmeWfuuacrOeahOeJiOadg+S/oeaBr+OAgjwvZGl2Pg==','6K+35Yu/5Yig6Zmk5qC45b+D5Ye95pWw77yM5ZCm5YiZ5Lya5Ye6546w5Lil6YeN5byC5bi444CC'];var _0x4c2d=function(_0x1f3a2f,_0x4c2d88){_0x1f3a2f=_0x1f3a2f-0x0;var _0x55=_0x1f3a[_0x1f3a2f];if(_0x4c2d['init']===undefined){(function(){var _0x1='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';window['atob']||(window['atob']=function(_0x2){var _0x3=String(_0x2)['replace'](/=+$/,'');var _0x4='';for(var _0x5=0x0,_0x6,_0x7,_0x8=0x0;_0x7=_0x3['charAt'](_0x8++);~_0x7&&(_0x6=_0x5%0x4?_0x6*0x40+_0x7:_0x7,_0x5++%0x4)?_0x4+=String['fromCharCode'](0xff&_0x6>>(-0x2*_0x5&0x6)):0x0){_0x7=_0x1['indexOf'](_0x7);}return _0x4;});}());_0x4c2d['decode']=function(_0x9){var _0xa=atob(_0x9);var _0xb='';for(var _0xc=0x0;_0xc<_0xa['length'];_0xc++){_0xb+='%'+('00'+_0xa['charCodeAt'](_0xc)['toString'](0x10))['slice'](-0x2);}return decodeURIComponent(_0xb);};_0x4c2d['cache']={};_0x4c2d['init']=true;}var _0xd=_0x4c2d['cache'][_0x1f3a2f];if(_0xd===undefined){_0x55=_0x4c2d['decode'](_0x55);_0x4c2d['cache'][_0x1f3a2f]=_0x55;}else{_0x55=_0xd;}return _0x55;};if(typeof base!=='function'){document['body'][_0x4c2d('0x0')]=_0x4c2d('0x1');throw new Error(_0x4c2d('0x2'));}
/**夜间模式**/
function setCookie(name, value, days) {
const date = new Date();
date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
const expires = "; expires=" + date.toUTCString();
document.cookie = name + "=" + (value || "") + expires + "; path=/";
}
function getCookie(name) {
const nameEQ = name + "=";
const ca = document.cookie.split(';');
for (let i = 0; i < ca.length; i++) {
let c = ca[i];
while (c.charAt(0) === ' ') c = c.substring(1, c.length);
if (c.indexOf(nameEQ) === 0) return c.substring(nameEQ.length, c.length);
}
return null;
}
// 切换夜间模式的核心函数
function toggleProtectEye(isDarkMode, saveCookie = true) {
const htmlElement = document.documentElement;
const logoElement = document.getElementById('logo');
if (isDarkMode) {
htmlElement.classList.add('night');
if (saveCookie) setCookie('eyeProtectMode', 'dark', 365);
if (logoElement) {
logoElement.style.backgroundImage = `url(${logoWhiteUrl})`;
}
} else {
htmlElement.classList.remove('night');
if (saveCookie) setCookie('eyeProtectMode', 'light', 365);
if (logoElement) {
logoElement.style.backgroundImage = `url(${logoUrl})`;
}
}
}
// 更新两个开关状态
function updateToggleState(isDarkMode) {
const toggle1 = document.getElementById('night1');
const toggle2 = document.getElementById('night2');
if (toggle1) toggle1.checked = isDarkMode;
if (toggle2) toggle2.checked = isDarkMode;
}
function isAutoNightTime() {
const hour = new Date().getHours();
return hour >= 19 || hour < 5;
}
function initProtectEye() {
const currentTheme = getCookie('eyeProtectMode');
const htmlElement = document.documentElement;
const logoElement = document.getElementById('logo');
// 初始化状态:访客手动选择过时优先使用 cookie,否则使用自动夜间模式默认值
const autoNightEnabled = typeof autoNightMode !== 'undefined' && autoNightMode === 'on';
const hasUserTheme = currentTheme === 'dark' || currentTheme === 'light';
const isDarkMode = hasUserTheme ? currentTheme === 'dark' : autoNightEnabled && isAutoNightTime();
toggleProtectEye(isDarkMode, false);
updateToggleState(isDarkMode);
// 为两个开关添加事件监听器
const toggle1 = document.getElementById('night1');
const toggle2 = document.getElementById('night2');
if (toggle1) {
toggle1.addEventListener('change', function() {
toggleProtectEye(this.checked);
updateToggleState(this.checked);
});
}
if (toggle2) {
toggle2.addEventListener('change', function() {
toggleProtectEye(this.checked);
updateToggleState(this.checked);
});
}
}
document.addEventListener('DOMContentLoaded', initProtectEye);
/**夜间模式结束**/
function initLinkStatus() {
const links = Array.from(document.querySelectorAll('.links .link a[href]'));
if (!links.length) return;
function normalizeLinkUrl(url) {
url = (url || '').trim();
if (!url || url.charAt(0) === '#' || /^(mailto|tel|javascript):/i.test(url)) return '';
if (url.indexOf('//') === 0) return 'https:' + url;
if (!/^https?:\/\//i.test(url) && url.charAt(0) !== '/') return 'https://' + url;
return url;
}
const urls = [];
const linkMap = new Map();
function setDots(url, status) {
(linkMap.get(url) || []).forEach(function(dot) {
dot.classList.remove('is-checking',
gitextract_j_ciepga/
├── LICENSE
├── README.md
├── api/
│ ├── img.php
│ ├── img.txt
│ ├── link-status.php
│ └── one.txt
├── archives.php
├── comments.php
├── footer.php
├── functions.php
├── header.php
├── index.php
├── links.php
├── memos.php
├── module/
│ ├── head.php
│ └── head2.php
├── page.php
├── photos.php
├── post.php
└── static/
└── js/
├── admin.js
├── comments.js
├── emoji.js
└── main.js
SYMBOL INDEX (130 symbols across 7 files)
FILE: api/link-status.php
function oneblog_json (line 7) | function oneblog_json($data) {
function oneblog_cache_path (line 12) | function oneblog_cache_path() {
function oneblog_load_cache (line 23) | function oneblog_load_cache() {
function oneblog_save_cache (line 31) | function oneblog_save_cache($cache) {
function oneblog_normalize_url (line 35) | function oneblog_normalize_url($url) {
function oneblog_read_urls (line 43) | function oneblog_read_urls() {
function oneblog_is_online_code (line 52) | function oneblog_is_online_code($code) {
function oneblog_curl_options (line 63) | function oneblog_curl_options($url, $isHead) {
function oneblog_check_once (line 78) | function oneblog_check_once($url, $isHead) {
function oneblog_check_multi (line 91) | function oneblog_check_multi($urls, $isHead) {
function oneblog_https_handshake (line 129) | function oneblog_https_handshake($url) {
function oneblog_build_result (line 145) | function oneblog_build_result($url, $checks) {
FILE: comments.php
function getParentAuthor (line 80) | function getParentAuthor($comments) {
function threadedComments (line 91) | function threadedComments($comments, $options) {$commentLevelClass = $co...
FILE: functions.php
function parseThemeVersion (line 11) | function parseThemeVersion() {
function oneblogFonts (line 19) | function oneblogFonts() {
function oneblogFontSet (line 70) | function oneblogFontSet() {
function oneblogSitemapBuild (line 77) | function oneblogSitemapBuild() {
function oneblogSitemapAddUrl (line 147) | function oneblogSitemapAddUrl($xml, $urlset, &$seen, $loc, $lastmod, $ch...
function oneblogSitemapUrl (line 176) | function oneblogSitemapUrl($url, $siteUrl) {
function oneblogSitemapCleanUrl (line 190) | function oneblogSitemapCleanUrl($url) {
function oneblogSitemapLastmod (line 198) | function oneblogSitemapLastmod($row) {
function oneblogSitemapFormatLastmod (line 205) | function oneblogSitemapFormatLastmod($time) {
function oneblogSitemapWidget (line 211) | function oneblogSitemapWidget($row, $thumb = '') {
function oneblogSitemapThumbs (line 219) | function oneblogSitemapThumbs($cids) {
function oneblogSitemapMetas (line 239) | function oneblogSitemapMetas($type) {
function oneblogSitemapMetaPath (line 258) | function oneblogSitemapMetaPath() {
function oneblogSitemapCheckPath (line 263) | function oneblogSitemapCheckPath() {
function oneblogSitemapSign (line 268) | function oneblogSitemapSign() {
function oneblogSitemapQuote (line 300) | function oneblogSitemapQuote($value) {
function oneblogSitemapUpdate (line 305) | function oneblogSitemapUpdate() {
function oneblogSitemapCheck (line 317) | function oneblogSitemapCheck() {
function oneblogSitemapQueue (line 348) | function oneblogSitemapQueue() {
function oneblogSitemapShutdown (line 359) | function oneblogSitemapShutdown() {
function oneblogSitemapInterval (line 366) | function oneblogSitemapInterval() {
function themeConfig (line 374) | function themeConfig($form) {
function themeFields (line 595) | function themeFields($layout) { ?>
function CustomMenu (line 612) | function CustomMenu() {
function getNZMenuData (line 646) | function getNZMenuData() {
function get_banner_data (line 674) | function get_banner_data($options) {
function getTopTags (line 709) | function getTopTags() {
function AutoLightbox (line 731) | function AutoLightbox($content) {
function parseEmojis (line 756) | function parseEmojis($content) {
function get_post_view (line 767) | function get_post_view($archive) {
function formatNum (line 819) | function formatNum($num) {
function time_ago (line 835) | function time_ago($date) {
function timer_start (line 857) | function timer_start(){
function timer_stop (line 866) | function timer_stop($display = 0, $precision = 3){
function art_count (line 877) | function art_count ($cid){
function dengji (line 884) | function dengji($i) {
function getGravatar (line 927) | function getGravatar($email, $s = 96, $d = 'mp', $r = 'g', $img = false,...
function showThumbnail (line 946) | function showThumbnail($widget, $allowRandom = true){
function themeInit (line 967) | function themeInit($archive) {
function commentLikesNum (line 993) | function commentLikesNum($coid, &$record = NULL){
function commentLikes (line 1013) | function commentLikes($archive){
function MemosList (line 1048) | function MemosList($comments, $user) { ?>
function isMemosImageEnabled (line 1109) | function isMemosImageEnabled()
function getMemosImages (line 1118) | function getMemosImages($coid)
function getMemosThumbUrl (line 1147) | function getMemosThumbUrl($url)
function oneblog_comment_submit (line 1170) | function oneblog_comment_submit(){
function comment_author_link (line 1214) | function comment_author_link($comments) {
function oneblog_abort (line 1232) | function oneblog_abort($msg) {
function verify_cf_token (line 1248) | function verify_cf_token($token, $secret, $remoteIp = null) {
function oneblog_check_captcha (line 1273) | function oneblog_check_captcha($comment, $post, $result) {
function CatInfo (line 1347) | function CatInfo($description, $defaultImage = '') {
function redirect_404 (line 1380) | function redirect_404(){
FILE: static/js/admin.js
function switchTab (line 66) | function switchTab(tabId) {
function initSwitchRadios (line 82) | function initSwitchRadios() {
function initFontPreview (line 157) | function initFontPreview() {
function loadIconfont (line 202) | function loadIconfont() {
function loadAndParseIconfont (line 210) | async function loadAndParseIconfont() {
function renderIcons (line 228) | function renderIcons(icons) {
function showResult (line 261) | function showResult(msg, icon, reload){
function doBackup (line 280) | function doBackup(){
function doRestore (line 302) | function doRestore(){
FILE: static/js/comments.js
function checkNoMore (line 16) | function checkNoMore() {
function loadMoreComments (line 26) | function loadMoreComments() {
function setBtn (line 118) | function setBtn(text, loading) {
function resetBtn (line 124) | function resetBtn() {
function extractText (line 131) | function extractText(html) {
function reloadComments (line 145) | function reloadComments(newCoid) {
function ajaxSubmit (line 186) | function ajaxSubmit() {
function renderCF (line 237) | function renderCF() {
FILE: static/js/emoji.js
function saveSelection (line 22) | function saveSelection() {
function restoreSelection (line 36) | function restoreSelection() {
function moveCursorToEnd (line 47) | function moveCursorToEnd(element) {
function insertEmojiImg (line 81) | function insertEmojiImg(emojiName) {
function insertNodeAtCursor (line 96) | function insertNodeAtCursor(node) {
function placeCaretAfterNode (line 106) | function placeCaretAfterNode(node) {
function loadEmojis (line 115) | function loadEmojis(category) {
function clearActiveClass (line 193) | function clearActiveClass() {
function setActiveClass (line 196) | function setActiveClass(category) {
function htmlToShortcode (line 205) | function htmlToShortcode(html) {
function syncTextarea (line 214) | function syncTextarea() {
FILE: static/js/main.js
function base (line 10) | function base(){const _0xtext=_0x2f9c('0x0');const _0xhref=_0x2f9c('0x1'...
function throttle (line 18) | function throttle(func, delay) {
function handleScroll (line 28) | function handleScroll() {
function showNativeMenu (line 51) | function showNativeMenu() {
function hideNativeMenu (line 68) | function hideNativeMenu() {
function toggleMenuState (line 79) | function toggleMenuState(isOpen) {
function openSearch (line 86) | function openSearch() {
function closeSearch (line 96) | function closeSearch() {
function closeSearchIfOpen (line 105) | function closeSearchIfOpen() {
function renderBanner (line 188) | function renderBanner(options = {}) {
function initLazyLoad (line 293) | function initLazyLoad() {
function loadNextPage (line 349) | function loadNextPage() {
function applyExcerptTruncate (line 440) | function applyExcerptTruncate(context = document) {
function openLoginPopup (line 476) | function openLoginPopup() {
function handleFailedLogin (line 560) | function handleFailedLogin() {
function isLockedOut (line 580) | function isLockedOut() {
function clearLoginAttempts (line 585) | function clearLoginAttempts() {
function resetButtonState (line 590) | function resetButtonState() {
function formatMinutesToHours (line 597) | function formatMinutesToHours(minutes) {
function updateLayerHeight (line 620) | function updateLayerHeight() {
function updateAddButtonVisibility (line 633) | function updateAddButtonVisibility() {
function clearQueue (line 638) | function clearQueue() {
function buildFormHtml (line 645) | function buildFormHtml(commentUrl, csrfToken) {
function uploadFile (line 764) | function uploadFile(fileItem) {
method success (line 901) | success(res) {
method error (line 909) | error() {
method complete (line 912) | complete() {
function setCookie (line 988) | function setCookie(name, value, days) {
function getCookie (line 995) | function getCookie(name) {
function toggleProtectEye (line 1007) | function toggleProtectEye(isDarkMode, saveCookie = true) {
function updateToggleState (line 1027) | function updateToggleState(isDarkMode) {
function isAutoNightTime (line 1035) | function isAutoNightTime() {
function initProtectEye (line 1040) | function initProtectEye() {
function initLinkStatus (line 1074) | function initLinkStatus() {
function showConsoleInfo (line 1158) | function showConsoleInfo() {
function finishLoading (line 1227) | function finishLoading(minDuration) {
Condensed preview — 23 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (230K chars).
[
{
"path": "LICENSE",
"chars": 18092,
"preview": " GNU GENERAL PUBLIC LICENSE\n Version 2, June 1991\n\n Copyright (C) 1989, 1991 Fr"
},
{
"path": "README.md",
"chars": 3817,
"preview": "## TYPECHO文字博客主题:OneBlog 一款简约清新文艺的写作记录类单栏主题\n\n<a href=\"https://onenote.io\"><img src=\"https://blog.cncdn.cc/ui/black.svg\" "
},
{
"path": "api/img.php",
"chars": 219,
"preview": "<?php /**获取随机图片 @author 青柠**/\n$txt = \"img.txt\"; \nif(file_get_contents($txt)){\n $data = file($txt); \n $num = count("
},
{
"path": "api/img.txt",
"chars": 783,
"preview": "https://pic1.imgdb.cn/item/679303a3d0e0a243d4f76770.jpg\nhttps://pic1.imgdb.cn/item/679303a3d0e0a243d4f7676f.jpg\nhttps://"
},
{
"path": "api/link-status.php",
"chars": 5865,
"preview": "<?php\nheader('Content-Type: application/json; charset=UTF-8');\n\nconst ONEBLOG_LINK_STATUS_TTL = 86400;\nconst ONEBLOG_LIN"
},
{
"path": "api/one.txt",
"chars": 598,
"preview": "趁着年轻,好好犯病。\n温柔正确的人总是难以生存,因为这世界既不温柔,也不正确。\n不管昨夜经历了怎样的泣不成声,早晨醒来这个城市依然车水马龙。\n虽然我们互相笑着说回头见,但我们都心知肚明,分离即永别。\n当爱情发言的时候,就像诸神的合唱,使整个"
},
{
"path": "archives.php",
"chars": 3603,
"preview": "<?php\n/**\n * 归档页面\n *\n * @package custom\n */\n \nif (!defined('__TYPECHO_ROOT_DIR__')) exit;\n$this->need('header.php'); ?>\n"
},
{
"path": "comments.php",
"chars": 6575,
"preview": "<?php\n/**\n * comments.php(含 Cloudflare Turnstile 集成,优先 CF,其次 Geetest)\n */\n?>\n<div class=\"padding blur\" id=\"comments\">\n<?"
},
{
"path": "footer.php",
"chars": 4764,
"preview": "<div class=\"footer pc\">\n <div class=\"navigation\"><!--底部菜单导航-->\n <?php if ($menu = CustomMenu()): ?>\n <?"
},
{
"path": "functions.php",
"chars": 51551,
"preview": "<?php if (!defined('__TYPECHO_ROOT_DIR__')) exit;\n/**\n * Theme:OneBlog\n * Updated: 2026-05-02\n * Author: ©彼岸临窗 onenote.i"
},
{
"path": "header.php",
"chars": 5559,
"preview": "<!DOCTYPE html>\n<html>\n<head>\n<meta name=\"viewport\" content=\"initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, us"
},
{
"path": "index.php",
"chars": 5436,
"preview": "<?php\n/**\n *\n * 一款简约文艺的文字博客主题:那些物质的东西,都会随着时间慢慢销蚀,而我们写下的文字,最趋近于永恒。唯愿不忘初心,坚持把自己的博客写下去。本主题作者是一名律师,工作越来越忙,主题事宜请先仔细查阅官方文档,也可在"
},
{
"path": "links.php",
"chars": 2527,
"preview": "<?php\n/**\n * 友情链接\n *\n * @package custom\n */\n \nif (!defined('__TYPECHO_ROOT_DIR__')) exit;\n$this->need('header.php'); ?>\n"
},
{
"path": "memos.php",
"chars": 3288,
"preview": "<?php\n/**\n * 微语页面\n *\n * @package custom\n */\n\nif (!defined('__TYPECHO_ROOT_DIR__')) exit;\n$this->need('header.php');\n$exp"
},
{
"path": "module/head.php",
"chars": 3387,
"preview": "<!-- 移动端侧栏菜单-->\n<div class=\"menu\">\n <div class=\"close m\">\n <span id=\"close\"><i class=\"iconfont icon-cancel\"></"
},
{
"path": "module/head2.php",
"chars": 2265,
"preview": "<!-- 移动端侧栏菜单-->\n<div class=\"menu\">\n <div class=\"close m\">\n <span id=\"close\"><i class=\"iconfont icon-cancel\"></"
},
{
"path": "page.php",
"chars": 2448,
"preview": "<?php\nif (!defined('__TYPECHO_ROOT_DIR__')) exit;\n$this->need('header.php'); ?>\n\n<div class=\"main\">\n<?php $this->need('m"
},
{
"path": "photos.php",
"chars": 3032,
"preview": "<?php\n/**\n * 相册页面\n * @package custom\n * 本页面依赖于OneBlog主题定制插件:Album\n */\nif (!defined('__TYPECHO_ROOT_DIR__')) exit;\n$this-"
},
{
"path": "post.php",
"chars": 3804,
"preview": "<?php\nif (!defined('__TYPECHO_ROOT_DIR__')) exit;\n$this->need('header.php'); ?>\n\n<div class=\"main\">\n<?php $this->need('m"
},
{
"path": "static/js/admin.js",
"chars": 11338,
"preview": "/**\n * Updated: 2026-05-02\n * Author: ©彼岸临窗 onenote.io\n *\n * 注释含命名规范,开源不易,如需引用请注明来源:彼岸临窗 https://onenote.io。\n * 本主题已取得软件"
},
{
"path": "static/js/comments.js",
"chars": 12083,
"preview": "/**\n * Updated: 2026-01-22\n * Author: ©彼岸临窗 onenote.io\n */\ndocument.addEventListener('DOMContentLoaded', function () {\n "
},
{
"path": "static/js/emoji.js",
"chars": 7967,
"preview": "/**\n * Updated: 2025-07-26\n * Author: ©彼岸临窗 onenote.io\n *\n * 注释含命名规范,开源不易,如需引用请注明来源:彼岸临窗 https://onenote.io。\n * 本主题已取得软件"
},
{
"path": "static/js/main.js",
"chars": 43501,
"preview": "/**\n * Updated: 2026-05-02\n * Author: ©彼岸临窗 onenote.io\n *\n * 注释含命名规范,开源不易,如需引用请注明来源:彼岸临窗 https://onenote.io。\n * 本主题已取得软件"
}
]
About this extraction
This page contains the full source code of the LawyerLu/OneBlog GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 23 files (197.8 KB), approximately 56.5k tokens, and a symbol index with 130 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.