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