├── LICENSE ├── README.md ├── archive.php ├── assets ├── css │ ├── main.css │ └── main.min.css └── js │ ├── argon.min.js │ ├── bbrender.js │ ├── jquery.pjax.js │ └── progress.js ├── comments.php ├── footer.php ├── functions.php ├── header.php ├── images ├── Loading.gif ├── avatar.png └── logo.png ├── index.php ├── page.php ├── post.php └── screenshot.jpg /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 trinitrotofu 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Bubble 2 | 3 | Typecho 清新风格响应式主题。 4 | 5 | 化繁为简,如沐清风。 6 | 7 | Demo: [https://typecho.tntofu.com/](https://typecho.tntofu.com/) 8 | 9 | 如果觉得本主题还不错的话可以给个 star 哦,这是对开源主题作者们最大的鼓励。 10 | 11 | # 特性 12 | 13 | + 清新的界面:大气简洁的页面布局,采用 argon design system,元素间隔恰到好处 14 | + 人性化的设计:登陆后显示后台管理按钮、醒目的文字、方便操作的按钮摆放,以及页面自适应 15 | + 流畅的用户体验:支持全站 pjax、ajax 实现评论无刷新,采用 jsDelivr CDN 全球加速主题大部分静态资源加载,减小服务器压力 16 | + 短代码功能:使用主题特殊 HTML 标签实现更丰富的功能,如高亮代码框以及友链卡片 17 | + 自定义壁纸:首页固定壁纸、其他页面随机壁纸,彰显个性爱好 18 | + 代码高亮:自带 prism.js 代码高亮,主题丰富,解析迅速 19 | + 数学公式:自带 KaTeX 数学公式渲染,相比 MathJax 更为轻量、快速 20 | + 自定义样式:支持用户添加 css 样式,随意调整主题外观 21 | + 文件轻量:主题`.zip`格式安装包(去掉`.git`文件夹后)大小不到 250 KB,简洁而不简单 22 | 23 | ~~我真佩服自己写文案的水平。~~ 24 | 25 | # 使用 26 | ## 安装 27 | 28 | 在 [Releases](https://github.com/trinitrotofu/Bubble/releases) 下载 zip 源码,解压后移动到 Typecho 主题目录。 29 | 30 | ## 设置 31 | 32 | 主题设置页面位置:Typecho 后台->控制台->外观->设置外观。 33 | 34 | ## 更新 35 | 36 | 主题有自动检查更新功能,并且能够一键安装更新。 37 | 38 | 自动更新按钮设置在主题设置页面中。 39 | 40 | 自动更新将从 jsdelivr 的 CDN 上下载最新版本的主题以替换旧版本主题。 41 | 42 | 你也可以手动更新,手动更新步骤为:删除旧版本,并安装新版本。 43 | 44 | 请注意,由于 Typecho 本身设计的原因,主题更新后可能会添加新的设置项,如果不禁用主题并重新启用(通过启用其他主题),新设置项将被留空并可能导致 Bug,所以请在更新后重启主题,或者请仔细检查是否有留空的设置项(这里主要指选择型设置项,文本类设置项似乎没这个问题)。 45 | 46 | ### 站点副标题 47 | 48 | 该项用以设定首页的标题栏中站点标题后显示的文字。 49 | 50 | ### 站点 LOGO 51 | 52 | 该项应为一个图片的 URL 地址,将显示在浏览器顶部标签标题左边。 53 | 54 | 请勿使用相对地址。 55 | 56 | ### 站点头像 57 | 58 | 该项应为一个图片的 URL 地址,将显示在首页大标题上方。 59 | 60 | 请勿使用相对地址。 61 | 62 | ### 首页背景图像 63 | 64 | 该项应为一个图片的 URL 地址,用以设定网站首页背景图片,留空则使用默认紫色渐变背景。 65 | 66 | 请勿使用相对地址。 67 | 68 | ### 随机背景图像地址 69 | 70 | 该项应为一个或多个图片的 URL 地址,用以设定网站文章页、独立页面以及其他页面的头图,设定后将从给定的图片中随机抽取一个显示,留空则使用默认紫色渐变背景。 71 | 72 | URL 之间用换行隔开,即每行一个 URL,**请勿包含多余字符**。 73 | 74 | 请勿使用相对地址。 75 | 76 | ### 背景气泡 77 | 78 | 该项用以选择是否在首页以及文章页顶部背景处显示半透明气泡。 79 | 80 | ### 页脚左下角文字 81 | 82 | 该项用以设定页脚左下角的说明文字,如 Copyright 和 备案信息。 83 | 84 | 可添加 HTML 代码以实现更丰富的功能,如跳转链接等。 85 | 86 | ### 页脚小工具 87 | 88 | 该项用以选择是否在页面底部显示“最新评论”、“最新文章”和“近期归档”。 89 | 90 | ### 自定义 css 91 | 92 | 该项用以提供自定义 css 接口,用户可以填入自己需要的 css 代码来改变主题外观,例如更改字体大小。 93 | 94 | 自定义 css 的生效不需要清空缓存。 95 | 96 | ### 全站 pjax 模式 97 | 98 | 该项用以选择是否启用全站 pjax 模式提升用户访问体验。 99 | 100 | 请注意:开启该功能后可能会导致站点一些功能不正常,例如如果您未使用主题自带数学公式渲染,而是选择使用其他插件实现该功能,则会导致无刷新打开页面时数学公式插件不工作,因此请仔细检查后决定是否开启该模式。 101 | 102 | 如果您发现部分功能确实出现了问题,而您又希望开启该模式,则请查看“pjax 回调代码”一项的说明。 103 | 104 | ### pjax 回调代码 105 | 106 | 该项用以设定 pjax 渲染完毕后需执行的 JS 代码,以此解决上一项中提到的插件不工作之类的问题。 107 | 108 | 例如您有一个叫做`myFunction()`的函数在其他插件中调用了,但您发现它在全站 pjax 模式下不工作,则您应该在该项中填入以下内容: 109 | 110 | ```js 111 | myFunction(); 112 | ``` 113 | 114 | 那么在 pjax 执行完毕之后会调用`myFunction()`,问题就解决了。 115 | 116 | ### katex 数学公式渲染 117 | 118 | 该项用以选择是否启用 katex 数学公式渲染。 119 | 120 | ### prism.js 代码高亮 121 | 122 | 该项用以选择是否启用 prism.js 代码高亮。 123 | 124 | ### prism.js 行号显示 125 | 126 | 该项用以选择代码高亮是否显示行号。 127 | 128 | ### prism.js 高亮主题 129 | 130 | 该项用以选择 prism.js 代码高亮的主题配色。 131 | 132 | ### TOC 文章目录 133 | 134 | 该项用以选择是否启用 TOC 文章目录功能。 135 | 136 | 启用后将在文章页右侧显示一个可展开和关闭的 TOC 目录。 137 | 138 | ### TOC 目录展开状态 139 | 140 | 该项用以选择 TOC 目录栏是否默认展开。 141 | 142 | ### 评论缩进风格 143 | 144 | 该项用以选择评论缩进风格。 145 | 146 | Typecho风格:每一层回复都会缩进一次,和Typecho默认主题一致,看起来像下面这样。 147 | 148 | ``` 149 | a: --- 150 | b: --- 151 | a: --- 152 | b: --- 153 | ``` 154 | 155 | Bubble风格:会在必要的时候合并评论到同一层里,方便阅读,看起来像下面这样。 156 | 157 | ``` 158 | a: --- 159 | b: --- 160 | a: --- 161 | b: --- 162 | ``` 163 | 164 | ### 被回复人的昵称显示 165 | 166 | 该项用以选择是否在评论人的昵称后面显示被回复人的昵称 167 | 168 | 如果显示:`aa 回复 bb · 2021-05-14 22:16`,如果不显示:`aa · 2021-05-14 22:16` 169 | 170 | ## 短代码 171 | 172 | *注意:若无法正常使用此功能,请尝试关闭第三方插件,如第三方文章编辑器。* 173 | 174 | ### 高亮代码框 175 | 176 | 标签名: 177 | 178 | + `bbcode`:高亮代码框标签 179 | 180 | 语法: 181 | 182 | ```html 183 | 代码框内容 184 | ``` 185 | 186 | 示例: 187 | 188 | ```html 189 | 这是绿色高亮代码框,用以显示推荐信息 190 | 这是红色高亮代码框,用以显示警告信息 191 | 这是橙色高亮代码框,用以显示注意信息 192 | 这是灰色高亮代码框,用以显示引用信息 193 | 这是蓝绿色高亮代码框,用以显示高亮信息 194 | 这是深蓝色高亮代码框,用以显示高亮信息 195 | 这是紫色高亮代码框,用以显示高亮信息 196 | ``` 197 | 198 | 效果: 199 | 200 | ![bbcode.png](https://i.loli.net/2020/04/07/VEk37bUuXc1lyf6.png) 201 | 202 | ### 友链 203 | 204 | 标签名: 205 | 206 | + `bblist`:友链列表标签 207 | + `bblink`:友链项标签 208 | 209 | 语法: 210 | 211 | ```html 212 | 213 | 友链名称 214 | 215 | ``` 216 | 217 | 示例: 218 | 219 | ```html 220 | 221 | 豆腐蒸锅 222 | Rorical 223 | 224 | ``` 225 | 226 | 效果: 227 | 228 | ![bblink.png](https://i.loli.net/2020/04/07/13ZtBldaNuxqrch.png) 229 | 230 | # 截图 231 | 232 | ![screenshot.jpg](./screenshot.jpg) 233 | 234 | # License 235 | 236 | Open sourced under the MIT license. 237 | 238 | # 其他 239 | 240 | + 主题由[三硝基豆腐](https://github.com/trinitrotofu)、[Rorical](https://github.com/Liupaperbox)和[Totorato](https://github.com/totorato)三人共同完成 241 | + 基于[Argon Design System](https://www.creative-tim.com/product/argon-design-system) 242 | + 评论模块中部分代码参照了 Typecho 官方主题 Default 243 | + Rorical 的 Rorical 主题(同样基于 argon design system):[https://github.com/Liupaperbox/Rorical](https://github.com/Rorical/Rorical) 244 | + Totorator 的修改版 Bubble 主题:[https://github.com/totorato/Bubble](https://github.com/totorato/Bubble) 245 | 246 | # 更新历史 247 | 248 | ## 4.0.1 249 | 250 | + 修复了副标题显示不正确的问题(于是 4.0.0 只存活了不到一个小时) 251 | 252 | ## 4.0.0 253 | 254 | + 更改页面布局为卡片式布局 255 | + 新增自动更新功能 256 | + 新增 TOC 目录功能 257 | + 新增 viewer.js 图片查看器(点击放大) 258 | + 新增回到顶部按钮 259 | + 新增副标题设置功能 260 | 261 | 262 | ## 3.0.3 263 | 264 | + 优化 pjax 进度条显示 265 | + 添加图片圆角 266 | + 修改高分辨率内容卡片边距 267 | + 美化搜索框 268 | 269 | ## 3.0.2 270 | 271 | + 调整首页壁纸全屏显示以美化高分屏显示效果 272 | + 首页翻页自动滚动至文章列表 273 | 274 | ## 3.0.1 275 | 276 | + 修复评论卡托条问题 277 | + 修复左上角标题颜色问题 278 | 279 | ## 3.0.0 280 | 281 | + 新增 pjax 支持、ajax 评论无刷新及其部分细节优化 282 | + 新增 KaTeX 数学公式渲染 283 | + 新增 prism.js 代码高亮 284 | + 新增短代码功能(高亮代码框以及友链功能) 285 | + 使用 jsdelivr CDN 加速静态文件加载 286 | + 更新部分页面布局 287 | + 文章列表采用分页导航栏代替翻页按钮 288 | + 修正评论区头像被挤压的问题 289 | + 修正评论卡由于采用 flex 而高度塌缩的问题 290 | 291 | 注:此次更新主要由 Rorical 贡献代码,十分感谢 292 | 293 | ## 2.3.0 294 | 295 | + 支持自定义首页背景图像和其他页面标题栏背景图像随机显示 296 | + 调整页面按钮样式 297 | + 评论区布局调整 298 | + 新增首页头像旋转特效 299 | + 调整修复部分页面布局以及样式错误 300 | + 更新截图 301 | 302 | ## 2.2.2 303 | 304 | + 调整内容页宽度 305 | + 支持自定义页脚说明文字 306 | 307 | ## 2.2.1 308 | 309 | + 修复分类显示 Bug(issue #2) 310 | + 规整代码 311 | 312 | ## 2.2 313 | 314 | + 优化内容宽度设定 315 | + 更新换行规则,避免某单词或网址过长导致 316 | 317 | ## 2.1 318 | 319 | + 页脚显示“最新评论”、“最新文章”和“文章归档” 320 | + 新增后下角快捷编辑按钮(需登录后显示),方便快速进入后台、编辑文章 321 | 322 | ## 2.0 323 | 324 | + 采用卡片式页面,提升美观性 325 | + 美化顶部导航栏样式 326 | + 新增后台设置 Logo 和 首页头像功能 327 | + 美化文章列表和文章阅读页中文章状态标签(如分类、作者等) 328 | + 美化评论区显示,添加博主标志,调整头像大小,提升布局美观性,解决评论卡片无法显示于子评论下方的问题,适配移动端,提升阅读舒适度 329 | + 采用 font awesome 代替 nucleo 图标集,丰富图标种类 330 | + 美化加密文章密码表单 331 | + 调整隐藏/加密文章的评论卡显示机制 332 | + 调整页面 title 显示机制 333 | 334 | ## 2.0 之前 335 | 336 | 我不记得了,也懒得看 337 | -------------------------------------------------------------------------------- /archive.php: -------------------------------------------------------------------------------- 1 | need('header.php'); 4 | ?> 5 | 6 |
7 |
8 | options->randomImage), $this->options->bubbleShow); ?> 9 |
10 |
11 |
12 |

13 | archiveTitle(array( 14 | 'category'=>_t('%s'), 15 | 'search'=>_t('%s的搜索结果'), 16 | 'tag' =>_t('%s'), 17 | 'author'=>_t('%s的文章') 18 | ), ''); 19 | ?> 20 |

21 |
22 |
23 |
24 |
25 |
26 |
27 | 28 | have()): ?> 29 | 30 | next()): ?> 31 | 32 | 33 | 34 | 35 | 36 |
37 |
38 |
39 |
40 |

这里空空如也

41 |
42 |

不如换个地方看看吧?

43 |
44 |
45 |
46 |
47 | 48 |
49 |
50 | 51 | need('footer.php'); ?> -------------------------------------------------------------------------------- /assets/js/argon.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | 3 | ========================================================= 4 | * Argon Design System - v1.1.1 5 | ========================================================= 6 | 7 | * Product Page: https://www.creative-tim.com/product/argon-design-system 8 | * Copyright 2020 Creative Tim (https://www.creative-tim.com) 9 | * Licensed under MIT (https://github.com/creativetimofficial/argon-design-system/blob/master/LICENSE.md) 10 | 11 | * Coded by www.creative-tim.com 12 | 13 | ========================================================= 14 | 15 | * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 16 | 17 | */ 18 | "use strict";$(document).ready(function(){$(".navbar-main .collapse").on("hide.bs.collapse",function(){$(this).addClass("collapsing-out")}),$(".navbar-main .collapse").on("hidden.bs.collapse",function(){$(this).removeClass("collapsing-out")}),$(".navbar-main .dropdown").on("hide.bs.dropdown",function(){var e=$(this).find(".dropdown-menu");e.addClass("close"),setTimeout(function(){e.removeClass("close")},200)}),$(".headroom")[0]&&new Headroom(document.querySelector("#navbar-main"),{offset:300,tolerance:{up:30,down:30}}).init();if($(".datepicker")[0]&&$(".datepicker").each(function(){$(".datepicker").datepicker({disableTouchKeyboard:!0,autoclose:!1})}),$('[data-toggle="tooltip"]').tooltip(),$('[data-toggle="popover"]').each(function(){var e="";$(this).data("color")&&(e="popover-"+$(this).data("color")),$(this).popover({trigger:"focus",template:''})}),$(".form-control").on("focus blur",function(e){$(this).parents(".form-group").toggleClass("focused","focus"===e.type||0'; 6 | switch (tp) { 7 | case 'success': 8 | res += ''; 9 | break; 10 | case 'danger': 11 | res += ''; 12 | break; 13 | case 'warning': 14 | res += ''; 15 | break; 16 | case 'secondary': 17 | res += ''; 18 | break; 19 | default: 20 | res += ''; 21 | break; 22 | } 23 | res += '' + obj[i].innerHTML + ''; 24 | obj[i].innerHTML = res; 25 | } 26 | } 27 | 28 | function parseBblink() { 29 | let obj = document.getElementsByTagName('bblink'); 30 | for (let i = 0; i < obj.length; i++) { 31 | obj[i].setAttribute('class', 'card shadow card-lift--hover friend-card'); 32 | let ico = obj[i].getAttribute('icon'); 33 | let lnk = obj[i].getAttribute('link'); 34 | let des = obj[i].getAttribute('des'); 35 | let res = '
' 38 | res += obj[i].innerHTML + '
' + des + '
'; 39 | obj[i].innerHTML = res; 40 | } 41 | obj = document.getElementsByTagName('bblist'); 42 | for (let i = 0; i < obj.length; i++) 43 | obj[i].innerHTML = '
' + obj[i].innerHTML + '
'; 44 | } -------------------------------------------------------------------------------- /assets/js/jquery.pjax.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright 2012, Chris Wanstrath 3 | * Released under the MIT License 4 | * https://github.com/defunkt/jquery-pjax 5 | */ 6 | 7 | (function($){ 8 | 9 | // When called on a container with a selector, fetches the href with 10 | // ajax into the container or with the data-pjax attribute on the link 11 | // itself. 12 | // 13 | // Tries to make sure the back button and ctrl+click work the way 14 | // you'd expect. 15 | // 16 | // Exported as $.fn.pjax 17 | // 18 | // Accepts a jQuery ajax options object that may include these 19 | // pjax specific options: 20 | // 21 | // 22 | // container - String selector for the element where to place the response body. 23 | // push - Whether to pushState the URL. Defaults to true (of course). 24 | // replace - Want to use replaceState instead? That's cool. 25 | // 26 | // For convenience the second parameter can be either the container or 27 | // the options object. 28 | // 29 | // Returns the jQuery object 30 | function fnPjax(selector, container, options) { 31 | options = optionsFor(container, options) 32 | return this.on('click.pjax', selector, function(event) { 33 | var opts = options 34 | if (!opts.container) { 35 | opts = $.extend({}, options) 36 | opts.container = $(this).attr('data-pjax') 37 | } 38 | handleClick(event, opts) 39 | }) 40 | } 41 | 42 | // Public: pjax on click handler 43 | // 44 | // Exported as $.pjax.click. 45 | // 46 | // event - "click" jQuery.Event 47 | // options - pjax options 48 | // 49 | // Examples 50 | // 51 | // $(document).on('click', 'a', $.pjax.click) 52 | // // is the same as 53 | // $(document).pjax('a') 54 | // 55 | // Returns nothing. 56 | function handleClick(event, container, options) { 57 | options = optionsFor(container, options) 58 | 59 | var link = event.currentTarget 60 | var $link = $(link) 61 | 62 | if (link.tagName.toUpperCase() !== 'A') 63 | throw "$.fn.pjax or $.pjax.click requires an anchor element" 64 | 65 | // Middle click, cmd click, and ctrl click should open 66 | // links in a new tab as normal. 67 | if ( event.which > 1 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey ) 68 | return 69 | 70 | // Ignore cross origin links 71 | if ( location.protocol !== link.protocol || location.hostname !== link.hostname ) 72 | return 73 | 74 | // Ignore case when a hash is being tacked on the current URL 75 | if ( link.href.indexOf('#') > -1 && stripHash(link) == stripHash(location) ) 76 | return 77 | 78 | // Ignore event with default prevented 79 | if (event.isDefaultPrevented()) 80 | return 81 | 82 | var defaults = { 83 | url: link.href, 84 | container: $link.attr('data-pjax'), 85 | target: link 86 | } 87 | 88 | var opts = $.extend({}, defaults, options) 89 | var clickEvent = $.Event('pjax:click') 90 | $link.trigger(clickEvent, [opts]) 91 | 92 | if (!clickEvent.isDefaultPrevented()) { 93 | pjax(opts) 94 | event.preventDefault() 95 | $link.trigger('pjax:clicked', [opts]) 96 | } 97 | } 98 | 99 | // Public: pjax on form submit handler 100 | // 101 | // Exported as $.pjax.submit 102 | // 103 | // event - "click" jQuery.Event 104 | // options - pjax options 105 | // 106 | // Examples 107 | // 108 | // $(document).on('submit', 'form', function(event) { 109 | // $.pjax.submit(event, '[data-pjax-container]') 110 | // }) 111 | // 112 | // Returns nothing. 113 | function handleSubmit(event, container, options) { 114 | options = optionsFor(container, options) 115 | 116 | var form = event.currentTarget 117 | var $form = $(form) 118 | 119 | if (form.tagName.toUpperCase() !== 'FORM') 120 | throw "$.pjax.submit requires a form element" 121 | 122 | var defaults = { 123 | type: ($form.attr('method') || 'GET').toUpperCase(), 124 | url: $form.attr('action'), 125 | container: $form.attr('data-pjax'), 126 | target: form 127 | } 128 | 129 | if (defaults.type !== 'GET' && window.FormData !== undefined) { 130 | defaults.data = new FormData(form) 131 | defaults.processData = false 132 | defaults.contentType = false 133 | } else { 134 | // Can't handle file uploads, exit 135 | if ($form.find(':file').length) { 136 | return 137 | } 138 | 139 | // Fallback to manually serializing the fields 140 | defaults.data = $form.serializeArray() 141 | } 142 | 143 | pjax($.extend({}, defaults, options)) 144 | 145 | event.preventDefault() 146 | } 147 | 148 | // Loads a URL with ajax, puts the response body inside a container, 149 | // then pushState()'s the loaded URL. 150 | // 151 | // Works just like $.ajax in that it accepts a jQuery ajax 152 | // settings object (with keys like url, type, data, etc). 153 | // 154 | // Accepts these extra keys: 155 | // 156 | // container - String selector for where to stick the response body. 157 | // push - Whether to pushState the URL. Defaults to true (of course). 158 | // replace - Want to use replaceState instead? That's cool. 159 | // 160 | // Use it just like $.ajax: 161 | // 162 | // var xhr = $.pjax({ url: this.href, container: '#main' }) 163 | // console.log( xhr.readyState ) 164 | // 165 | // Returns whatever $.ajax returns. 166 | function pjax(options) { 167 | options = $.extend(true, {}, $.ajaxSettings, pjax.defaults, options) 168 | 169 | if ($.isFunction(options.url)) { 170 | options.url = options.url() 171 | } 172 | 173 | var hash = parseURL(options.url).hash 174 | 175 | var containerType = $.type(options.container) 176 | if (containerType !== 'string') { 177 | throw "expected string value for 'container' option; got " + containerType 178 | } 179 | var context = options.context = $(options.container) 180 | if (!context.length) { 181 | throw "the container selector '" + options.container + "' did not match anything" 182 | } 183 | 184 | // We want the browser to maintain two separate internal caches: one 185 | // for pjax'd partial page loads and one for normal page loads. 186 | // Without adding this secret parameter, some browsers will often 187 | // confuse the two. 188 | if (!options.data) options.data = {} 189 | if ($.isArray(options.data)) { 190 | options.data.push({name: '_pjax', value: options.container}) 191 | } else { 192 | options.data._pjax = options.container 193 | } 194 | 195 | function fire(type, args, props) { 196 | if (!props) props = {} 197 | props.relatedTarget = options.target 198 | var event = $.Event(type, props) 199 | context.trigger(event, args) 200 | return !event.isDefaultPrevented() 201 | } 202 | 203 | var timeoutTimer 204 | 205 | options.beforeSend = function(xhr, settings) { 206 | // No timeout for non-GET requests 207 | // Its not safe to request the resource again with a fallback method. 208 | if (settings.type !== 'GET') { 209 | settings.timeout = 0 210 | } 211 | 212 | xhr.setRequestHeader('X-PJAX', 'true') 213 | xhr.setRequestHeader('X-PJAX-Container', options.container) 214 | 215 | if (!fire('pjax:beforeSend', [xhr, settings])) 216 | return false 217 | 218 | if (settings.timeout > 0) { 219 | timeoutTimer = setTimeout(function() { 220 | if (fire('pjax:timeout', [xhr, options])) 221 | xhr.abort('timeout') 222 | }, settings.timeout) 223 | 224 | // Clear timeout setting so jquerys internal timeout isn't invoked 225 | settings.timeout = 0 226 | } 227 | 228 | var url = parseURL(settings.url) 229 | if (hash) url.hash = hash 230 | options.requestUrl = stripInternalParams(url) 231 | } 232 | 233 | options.complete = function(xhr, textStatus) { 234 | if (timeoutTimer) 235 | clearTimeout(timeoutTimer) 236 | 237 | fire('pjax:complete', [xhr, textStatus, options]) 238 | 239 | fire('pjax:end', [xhr, options]) 240 | } 241 | 242 | options.error = function(xhr, textStatus, errorThrown) { 243 | var container = extractContainer("", xhr, options) 244 | 245 | var allowed = fire('pjax:error', [xhr, textStatus, errorThrown, options]) 246 | if (options.type == 'GET' && textStatus !== 'abort' && allowed) { 247 | locationReplace(container.url) 248 | } 249 | } 250 | 251 | options.success = function(data, status, xhr) { 252 | var previousState = pjax.state 253 | 254 | // If $.pjax.defaults.version is a function, invoke it first. 255 | // Otherwise it can be a static string. 256 | var currentVersion = typeof $.pjax.defaults.version === 'function' ? 257 | $.pjax.defaults.version() : 258 | $.pjax.defaults.version 259 | 260 | var latestVersion = xhr.getResponseHeader('X-PJAX-Version') 261 | 262 | var container = extractContainer(data, xhr, options) 263 | 264 | var url = parseURL(container.url) 265 | if (hash) { 266 | url.hash = hash 267 | container.url = url.href 268 | } 269 | 270 | // If there is a layout version mismatch, hard load the new url 271 | if (currentVersion && latestVersion && currentVersion !== latestVersion) { 272 | locationReplace(container.url) 273 | return 274 | } 275 | 276 | // If the new response is missing a body, hard load the page 277 | if (!container.contents) { 278 | locationReplace(container.url) 279 | return 280 | } 281 | 282 | pjax.state = { 283 | id: options.id || uniqueId(), 284 | url: container.url, 285 | title: container.title, 286 | container: options.container, 287 | fragment: options.fragment, 288 | timeout: options.timeout 289 | } 290 | 291 | if (options.push || options.replace) { 292 | window.history.replaceState(pjax.state, container.title, container.url) 293 | } 294 | 295 | // Only blur the focus if the focused element is within the container. 296 | var blurFocus = $.contains(context, document.activeElement) 297 | 298 | // Clear out any focused controls before inserting new page contents. 299 | if (blurFocus) { 300 | try { 301 | document.activeElement.blur() 302 | } catch (e) { /* ignore */ } 303 | } 304 | 305 | if (container.title) document.title = container.title 306 | 307 | fire('pjax:beforeReplace', [container.contents, options], { 308 | state: pjax.state, 309 | previousState: previousState 310 | }) 311 | context.html(container.contents) 312 | 313 | // FF bug: Won't autofocus fields that are inserted via JS. 314 | // This behavior is incorrect. So if theres no current focus, autofocus 315 | // the last field. 316 | // 317 | // http://www.w3.org/html/wg/drafts/html/master/forms.html 318 | var autofocusEl = context.find('input[autofocus], textarea[autofocus]').last()[0] 319 | if (autofocusEl && document.activeElement !== autofocusEl) { 320 | autofocusEl.focus() 321 | } 322 | 323 | executeScriptTags(container.scripts) 324 | 325 | var scrollTo = options.scrollTo 326 | 327 | // Ensure browser scrolls to the element referenced by the URL anchor 328 | if (hash) { 329 | var name = decodeURIComponent(hash.slice(1)) 330 | var target = document.getElementById(name) || document.getElementsByName(name)[0] 331 | if (target) scrollTo = $(target).offset().top 332 | } 333 | 334 | if (typeof scrollTo == 'number') $(window).scrollTop(scrollTo) 335 | 336 | fire('pjax:success', [data, status, xhr, options]) 337 | } 338 | 339 | 340 | // Initialize pjax.state for the initial page load. Assume we're 341 | // using the container and options of the link we're loading for the 342 | // back button to the initial page. This ensures good back button 343 | // behavior. 344 | if (!pjax.state) { 345 | pjax.state = { 346 | id: uniqueId(), 347 | url: window.location.href, 348 | title: document.title, 349 | container: options.container, 350 | fragment: options.fragment, 351 | timeout: options.timeout 352 | } 353 | window.history.replaceState(pjax.state, document.title) 354 | } 355 | 356 | // Cancel the current request if we're already pjaxing 357 | abortXHR(pjax.xhr) 358 | 359 | pjax.options = options 360 | var xhr = pjax.xhr = $.ajax(options) 361 | 362 | if (xhr.readyState > 0) { 363 | if (options.push && !options.replace) { 364 | // Cache current container element before replacing it 365 | cachePush(pjax.state.id, [options.container, cloneContents(context)]) 366 | 367 | window.history.pushState(null, "", options.requestUrl) 368 | } 369 | 370 | fire('pjax:start', [xhr, options]) 371 | fire('pjax:send', [xhr, options]) 372 | } 373 | 374 | return pjax.xhr 375 | } 376 | 377 | // Public: Reload current page with pjax. 378 | // 379 | // Returns whatever $.pjax returns. 380 | function pjaxReload(container, options) { 381 | var defaults = { 382 | url: window.location.href, 383 | push: false, 384 | replace: true, 385 | scrollTo: false 386 | } 387 | 388 | return pjax($.extend(defaults, optionsFor(container, options))) 389 | } 390 | 391 | // Internal: Hard replace current state with url. 392 | // 393 | // Work for around WebKit 394 | // https://bugs.webkit.org/show_bug.cgi?id=93506 395 | // 396 | // Returns nothing. 397 | function locationReplace(url) { 398 | window.history.replaceState(null, "", pjax.state.url) 399 | window.location.replace(url) 400 | } 401 | 402 | 403 | var initialPop = true 404 | var initialURL = window.location.href 405 | var initialState = window.history.state 406 | 407 | // Initialize $.pjax.state if possible 408 | // Happens when reloading a page and coming forward from a different 409 | // session history. 410 | if (initialState && initialState.container) { 411 | pjax.state = initialState 412 | } 413 | 414 | // Non-webkit browsers don't fire an initial popstate event 415 | if ('state' in window.history) { 416 | initialPop = false 417 | } 418 | 419 | // popstate handler takes care of the back and forward buttons 420 | // 421 | // You probably shouldn't use pjax on pages with other pushState 422 | // stuff yet. 423 | function onPjaxPopstate(event) { 424 | 425 | // Hitting back or forward should override any pending PJAX request. 426 | if (!initialPop) { 427 | abortXHR(pjax.xhr) 428 | } 429 | 430 | var previousState = pjax.state 431 | var state = event.state 432 | var direction 433 | 434 | if (state && state.container) { 435 | // When coming forward from a separate history session, will get an 436 | // initial pop with a state we are already at. Skip reloading the current 437 | // page. 438 | if (initialPop && initialURL == state.url) return 439 | 440 | if (previousState) { 441 | // If popping back to the same state, just skip. 442 | // Could be clicking back from hashchange rather than a pushState. 443 | if (previousState.id === state.id) return 444 | 445 | // Since state IDs always increase, we can deduce the navigation direction 446 | direction = previousState.id < state.id ? 'forward' : 'back' 447 | } 448 | 449 | var cache = cacheMapping[state.id] || [] 450 | var containerSelector = cache[0] || state.container 451 | var container = $(containerSelector), contents = cache[1] 452 | 453 | if (container.length) { 454 | if (previousState) { 455 | // Cache current container before replacement and inform the 456 | // cache which direction the history shifted. 457 | cachePop(direction, previousState.id, [containerSelector, cloneContents(container)]) 458 | } 459 | 460 | var popstateEvent = $.Event('pjax:popstate', { 461 | state: state, 462 | direction: direction 463 | }) 464 | container.trigger(popstateEvent) 465 | 466 | var options = { 467 | id: state.id, 468 | url: state.url, 469 | container: containerSelector, 470 | push: false, 471 | fragment: state.fragment, 472 | timeout: state.timeout, 473 | scrollTo: false 474 | } 475 | 476 | if (contents) { 477 | container.trigger('pjax:start', [null, options]) 478 | 479 | pjax.state = state 480 | if (state.title) document.title = state.title 481 | var beforeReplaceEvent = $.Event('pjax:beforeReplace', { 482 | state: state, 483 | previousState: previousState 484 | }) 485 | container.trigger(beforeReplaceEvent, [contents, options]) 486 | container.html(contents) 487 | 488 | container.trigger('pjax:end', [null, options]) 489 | } else { 490 | pjax(options) 491 | } 492 | 493 | // Force reflow/relayout before the browser tries to restore the 494 | // scroll position. 495 | container[0].offsetHeight // eslint-disable-line no-unused-expressions 496 | } else { 497 | locationReplace(location.href) 498 | } 499 | } 500 | initialPop = false 501 | } 502 | 503 | // Fallback version of main pjax function for browsers that don't 504 | // support pushState. 505 | // 506 | // Returns nothing since it retriggers a hard form submission. 507 | function fallbackPjax(options) { 508 | var url = $.isFunction(options.url) ? options.url() : options.url, 509 | method = options.type ? options.type.toUpperCase() : 'GET' 510 | 511 | var form = $('
', { 512 | method: method === 'GET' ? 'GET' : 'POST', 513 | action: url, 514 | style: 'display:none' 515 | }) 516 | 517 | if (method !== 'GET' && method !== 'POST') { 518 | form.append($('', { 519 | type: 'hidden', 520 | name: '_method', 521 | value: method.toLowerCase() 522 | })) 523 | } 524 | 525 | var data = options.data 526 | if (typeof data === 'string') { 527 | $.each(data.split('&'), function(index, value) { 528 | var pair = value.split('=') 529 | form.append($('', {type: 'hidden', name: pair[0], value: pair[1]})) 530 | }) 531 | } else if ($.isArray(data)) { 532 | $.each(data, function(index, value) { 533 | form.append($('', {type: 'hidden', name: value.name, value: value.value})) 534 | }) 535 | } else if (typeof data === 'object') { 536 | var key 537 | for (key in data) 538 | form.append($('', {type: 'hidden', name: key, value: data[key]})) 539 | } 540 | 541 | $(document.body).append(form) 542 | form.submit() 543 | } 544 | 545 | // Internal: Abort an XmlHttpRequest if it hasn't been completed, 546 | // also removing its event handlers. 547 | function abortXHR(xhr) { 548 | if ( xhr && xhr.readyState < 4) { 549 | xhr.onreadystatechange = $.noop 550 | xhr.abort() 551 | } 552 | } 553 | 554 | // Internal: Generate unique id for state object. 555 | // 556 | // Use a timestamp instead of a counter since ids should still be 557 | // unique across page loads. 558 | // 559 | // Returns Number. 560 | function uniqueId() { 561 | return (new Date).getTime() 562 | } 563 | 564 | function cloneContents(container) { 565 | var cloned = container.clone() 566 | // Unmark script tags as already being eval'd so they can get executed again 567 | // when restored from cache. HAXX: Uses jQuery internal method. 568 | cloned.find('script').each(function(){ 569 | if (!this.src) $._data(this, 'globalEval', false) 570 | }) 571 | return cloned.contents() 572 | } 573 | 574 | // Internal: Strip internal query params from parsed URL. 575 | // 576 | // Returns sanitized url.href String. 577 | function stripInternalParams(url) { 578 | url.search = url.search.replace(/([?&])(_pjax|_)=[^&]*/g, '').replace(/^&/, '') 579 | return url.href.replace(/\?($|#)/, '$1') 580 | } 581 | 582 | // Internal: Parse URL components and returns a Locationish object. 583 | // 584 | // url - String URL 585 | // 586 | // Returns HTMLAnchorElement that acts like Location. 587 | function parseURL(url) { 588 | var a = document.createElement('a') 589 | a.href = url 590 | return a 591 | } 592 | 593 | // Internal: Return the `href` component of given URL object with the hash 594 | // portion removed. 595 | // 596 | // location - Location or HTMLAnchorElement 597 | // 598 | // Returns String 599 | function stripHash(location) { 600 | return location.href.replace(/#.*/, '') 601 | } 602 | 603 | // Internal: Build options Object for arguments. 604 | // 605 | // For convenience the first parameter can be either the container or 606 | // the options object. 607 | // 608 | // Examples 609 | // 610 | // optionsFor('#container') 611 | // // => {container: '#container'} 612 | // 613 | // optionsFor('#container', {push: true}) 614 | // // => {container: '#container', push: true} 615 | // 616 | // optionsFor({container: '#container', push: true}) 617 | // // => {container: '#container', push: true} 618 | // 619 | // Returns options Object. 620 | function optionsFor(container, options) { 621 | if (container && options) { 622 | options = $.extend({}, options) 623 | options.container = container 624 | return options 625 | } else if ($.isPlainObject(container)) { 626 | return container 627 | } else { 628 | return {container: container} 629 | } 630 | } 631 | 632 | // Internal: Filter and find all elements matching the selector. 633 | // 634 | // Where $.fn.find only matches descendants, findAll will test all the 635 | // top level elements in the jQuery object as well. 636 | // 637 | // elems - jQuery object of Elements 638 | // selector - String selector to match 639 | // 640 | // Returns a jQuery object. 641 | function findAll(elems, selector) { 642 | return elems.filter(selector).add(elems.find(selector)) 643 | } 644 | 645 | function parseHTML(html) { 646 | return $.parseHTML(html, document, true) 647 | } 648 | 649 | // Internal: Extracts container and metadata from response. 650 | // 651 | // 1. Extracts X-PJAX-URL header if set 652 | // 2. Extracts inline tags 653 | // 3. Builds response Element and extracts fragment if set 654 | // 655 | // data - String response data 656 | // xhr - XHR response 657 | // options - pjax options Object 658 | // 659 | // Returns an Object with url, title, and contents keys. 660 | function extractContainer(data, xhr, options) { 661 | var obj = {}, fullDocument = /<html/i.test(data) 662 | 663 | // Prefer X-PJAX-URL header if it was set, otherwise fallback to 664 | // using the original requested url. 665 | var serverUrl = xhr.getResponseHeader('X-PJAX-URL') 666 | obj.url = serverUrl ? stripInternalParams(parseURL(serverUrl)) : options.requestUrl 667 | 668 | var $head, $body 669 | // Attempt to parse response html into elements 670 | if (fullDocument) { 671 | $body = $(parseHTML(data.match(/<body[^>]*>([\s\S.]*)<\/body>/i)[0])) 672 | var head = data.match(/<head[^>]*>([\s\S.]*)<\/head>/i) 673 | $head = head != null ? $(parseHTML(head[0])) : $body 674 | } else { 675 | $head = $body = $(parseHTML(data)) 676 | } 677 | 678 | // If response data is empty, return fast 679 | if ($body.length === 0) 680 | return obj 681 | 682 | // If there's a <title> tag in the header, use it as 683 | // the page's title. 684 | obj.title = findAll($head, 'title').last().text() 685 | 686 | if (options.fragment) { 687 | var $fragment = $body 688 | // If they specified a fragment, look for it in the response 689 | // and pull it out. 690 | if (options.fragment !== 'body') { 691 | $fragment = findAll($fragment, options.fragment).first() 692 | } 693 | 694 | if ($fragment.length) { 695 | obj.contents = options.fragment === 'body' ? $fragment : $fragment.contents() 696 | 697 | // If there's no title, look for data-title and title attributes 698 | // on the fragment 699 | if (!obj.title) 700 | obj.title = $fragment.attr('title') || $fragment.data('title') 701 | } 702 | 703 | } else if (!fullDocument) { 704 | obj.contents = $body 705 | } 706 | 707 | // Clean up any <title> tags 708 | if (obj.contents) { 709 | // Remove any parent title elements 710 | obj.contents = obj.contents.not(function() { return $(this).is('title') }) 711 | 712 | // Then scrub any titles from their descendants 713 | obj.contents.find('title').remove() 714 | 715 | // Gather all script[src] elements 716 | obj.scripts = findAll(obj.contents, 'script[src]').remove() 717 | obj.contents = obj.contents.not(obj.scripts) 718 | } 719 | 720 | // Trim any whitespace off the title 721 | if (obj.title) obj.title = $.trim(obj.title) 722 | 723 | return obj 724 | } 725 | 726 | // Load an execute scripts using standard script request. 727 | // 728 | // Avoids jQuery's traditional $.getScript which does a XHR request and 729 | // globalEval. 730 | // 731 | // scripts - jQuery object of script Elements 732 | // 733 | // Returns nothing. 734 | function executeScriptTags(scripts) { 735 | if (!scripts) return 736 | 737 | var existingScripts = $('script[src]') 738 | 739 | scripts.each(function() { 740 | var src = this.src 741 | var matchedScripts = existingScripts.filter(function() { 742 | return this.src === src 743 | }) 744 | if (matchedScripts.length) return 745 | 746 | var script = document.createElement('script') 747 | var type = $(this).attr('type') 748 | if (type) script.type = type 749 | script.src = $(this).attr('src') 750 | document.head.appendChild(script) 751 | }) 752 | } 753 | 754 | // Internal: History DOM caching class. 755 | var cacheMapping = {} 756 | var cacheForwardStack = [] 757 | var cacheBackStack = [] 758 | 759 | // Push previous state id and container contents into the history 760 | // cache. Should be called in conjunction with `pushState` to save the 761 | // previous container contents. 762 | // 763 | // id - State ID Number 764 | // value - DOM Element to cache 765 | // 766 | // Returns nothing. 767 | function cachePush(id, value) { 768 | cacheMapping[id] = value 769 | cacheBackStack.push(id) 770 | 771 | // Remove all entries in forward history stack after pushing a new page. 772 | trimCacheStack(cacheForwardStack, 0) 773 | 774 | // Trim back history stack to max cache length. 775 | trimCacheStack(cacheBackStack, pjax.defaults.maxCacheLength) 776 | } 777 | 778 | // Shifts cache from directional history cache. Should be 779 | // called on `popstate` with the previous state id and container 780 | // contents. 781 | // 782 | // direction - "forward" or "back" String 783 | // id - State ID Number 784 | // value - DOM Element to cache 785 | // 786 | // Returns nothing. 787 | function cachePop(direction, id, value) { 788 | var pushStack, popStack 789 | cacheMapping[id] = value 790 | 791 | if (direction === 'forward') { 792 | pushStack = cacheBackStack 793 | popStack = cacheForwardStack 794 | } else { 795 | pushStack = cacheForwardStack 796 | popStack = cacheBackStack 797 | } 798 | 799 | pushStack.push(id) 800 | id = popStack.pop() 801 | if (id) delete cacheMapping[id] 802 | 803 | // Trim whichever stack we just pushed to to max cache length. 804 | trimCacheStack(pushStack, pjax.defaults.maxCacheLength) 805 | } 806 | 807 | // Trim a cache stack (either cacheBackStack or cacheForwardStack) to be no 808 | // longer than the specified length, deleting cached DOM elements as necessary. 809 | // 810 | // stack - Array of state IDs 811 | // length - Maximum length to trim to 812 | // 813 | // Returns nothing. 814 | function trimCacheStack(stack, length) { 815 | while (stack.length > length) 816 | delete cacheMapping[stack.shift()] 817 | } 818 | 819 | // Public: Find version identifier for the initial page load. 820 | // 821 | // Returns String version or undefined. 822 | function findVersion() { 823 | return $('meta').filter(function() { 824 | var name = $(this).attr('http-equiv') 825 | return name && name.toUpperCase() === 'X-PJAX-VERSION' 826 | }).attr('content') 827 | } 828 | 829 | // Install pjax functions on $.pjax to enable pushState behavior. 830 | // 831 | // Does nothing if already enabled. 832 | // 833 | // Examples 834 | // 835 | // $.pjax.enable() 836 | // 837 | // Returns nothing. 838 | function enable() { 839 | $.fn.pjax = fnPjax 840 | $.pjax = pjax 841 | $.pjax.enable = $.noop 842 | $.pjax.disable = disable 843 | $.pjax.click = handleClick 844 | $.pjax.submit = handleSubmit 845 | $.pjax.reload = pjaxReload 846 | $.pjax.defaults = { 847 | timeout: 650, 848 | push: true, 849 | replace: false, 850 | type: 'GET', 851 | dataType: 'html', 852 | scrollTo: 0, 853 | maxCacheLength: 20, 854 | version: findVersion 855 | } 856 | $(window).on('popstate.pjax', onPjaxPopstate) 857 | } 858 | 859 | // Disable pushState behavior. 860 | // 861 | // This is the case when a browser doesn't support pushState. It is 862 | // sometimes useful to disable pushState for debugging on a modern 863 | // browser. 864 | // 865 | // Examples 866 | // 867 | // $.pjax.disable() 868 | // 869 | // Returns nothing. 870 | function disable() { 871 | $.fn.pjax = function() { return this } 872 | $.pjax = fallbackPjax 873 | $.pjax.enable = enable 874 | $.pjax.disable = $.noop 875 | $.pjax.click = $.noop 876 | $.pjax.submit = $.noop 877 | $.pjax.reload = function() { window.location.reload() } 878 | 879 | $(window).off('popstate.pjax', onPjaxPopstate) 880 | } 881 | 882 | 883 | // Add the state property to jQuery's event object so we can use it in 884 | // $(window).bind('popstate') 885 | if ($.event.props && $.inArray('state', $.event.props) < 0) { 886 | $.event.props.push('state') 887 | } else if (!('state' in $.Event.prototype)) { 888 | $.event.addProp('state') 889 | } 890 | 891 | // Is pjax supported by this browser? 892 | $.support.pjax = 893 | window.history && window.history.pushState && window.history.replaceState && 894 | // pushState isn't reliable on iOS until 5. 895 | !navigator.userAgent.match(/((iPod|iPhone|iPad).+\bOS\s+[1-4]\D|WebApps\/.+CFNetwork)/) 896 | 897 | if ($.support.pjax) { 898 | enable() 899 | } else { 900 | disable() 901 | } 902 | 903 | })(jQuery) 904 | -------------------------------------------------------------------------------- /assets/js/progress.js: -------------------------------------------------------------------------------- 1 | changeprogress = function(progress){ 2 | progress -= 4 3 | $("#argprogress .progress-bar").css("width",`${progress}%`) 4 | } 5 | function start_progress(){ 6 | var tick = 1 7 | $("#argprogress").fadeIn(150) 8 | var loadingtime = window.performance.timing["loadEventEnd"] - window.performance.timing["navigationStart"] 9 | var nowtime = 0 10 | var clock = setInterval(function(){ 11 | nowtime += tick 12 | if(nowtime > loadingtime){ 13 | }else{ 14 | nowprogress = Math.round(nowtime / loadingtime * 100) 15 | changeprogress(nowprogress) 16 | } 17 | }, tick); 18 | return clock 19 | } 20 | 21 | function stop_progress(clock){ 22 | $("#argprogress div").css("transition","all 600ms ease-in-out 0s") 23 | clearInterval(clock) 24 | changeprogress(104) 25 | setTimeout(function() { 26 | $("#argprogress").fadeOut(150) 27 | setTimeout(function() { 28 | changeprogress(-4) 29 | $("#argprogress div").css("transition","all 200ms linear 0s") 30 | }, 150); 31 | }, 500); 32 | } 33 | 34 | addclass = "bg-gradient-primary" 35 | 36 | $("body").append("<style>#argprogress{position: fixed;top: 0;width: 100%;left: 0;border: none;z-index: 105;overflow: hidden;height: 5px;display: flex;background-color: #e9ecef;}</style>") 37 | $("body").append(`<div id="argprogress" style="display:none;"><div class="progress-bar ${addclass}" style="width:0%;border: none;"></div></div>`) 38 | $("#argprogress div").css("transition","all 200ms linear 0s") -------------------------------------------------------------------------------- /comments.php: -------------------------------------------------------------------------------- 1 | 2 | <?php function threadedComments($comments, $options) { 3 | $commentLevelClass = $comments->_levels > 0 ? ' comment-child' : ' comment-parent'; 4 | 5 | $commentLine = []; 6 | $indent = shouldCommentIndent($comments, $commentLine); 7 | $parentAuthor = count($commentLine) >= 2? $commentLine[1]['author']:NULL; 8 | $isTopLevel = count($commentLine) == 1; 9 | $objectNick = Helper::options()->comment_object_nick!=='0'; 10 | ?> 11 | 12 | <li id="li-<?php $comments->theId(); ?>" class="<?php echo($indent? 'comment-child-indent':''); ?>"> 13 | <div id="<?php $comments->theId(); ?>"> 14 | <div class="comment-item"> 15 | <div class="<?php 16 | if ($comments->_levels > 0) { 17 | echo 'comment-child'; 18 | } else { 19 | echo 'comment-parent'; 20 | } 21 | ?>"> 22 | <?php $comments->gravatar(80, ''); ?> 23 | </div> 24 | <div class="comment-body"> 25 | <div class="comment-head"> 26 | <h5><?php if ($comments->url) { ?><a target="_blank" rel="external nofollow" href="<?php echo $comments->url; ?>"><?php echo $comments->author; ?></a><?php } else { ?><?php echo $comments->author; ?><?php } ?><?php echo((!$isTopLevel && $objectNick)?(' <small>回复</small> ' . $parentAuthor):''); ?> · <small><?php $comments->date('Y-m-d H:i'); ?></small><?php 27 | if ($comments->status == 'waiting') { 28 | ?><span class="badge badge-pill badge-default text-white">评论审核ing...</span><?php 29 | } 30 | if ($comments->authorId) { 31 | if ($comments->authorId == $comments->ownerId) { 32 | _e(' <span class="badge badge-pill badge-primary"><i class="fa fa-user-o" aria-hidden="true"></i> 作者</span>'); 33 | } 34 | } 35 | ?></h5> 36 | </div> 37 | <?php $comments->content(); ?> 38 | <div style="float: right;"> 39 | <?php $comments->reply('<i class="fa fa-reply" aria-hidden="true"></i> 回复'); ?> 40 | </div> 41 | </div> 42 | </div> 43 | </div> 44 | <?php if ($comments->children) { ?> 45 | <div class="comment-children"> 46 | <?php $comments->threadedComments($options); ?> 47 | </div> 48 | <?php } ?> 49 | </li> 50 | 51 | <?php } ?> 52 | 53 | <?php if (!defined('__TYPECHO_ROOT_DIR__')) exit; ?> 54 | <section class="section"> 55 | <div class="container" id="comments"> 56 | <div class="content"> 57 | <div class="row align-items-center justify-content-center"> 58 | <h3><?php $this->commentsNum(_t('暂无评论'), _t('仅有一条评论'), _t('%d 条评论')); ?></h3> 59 | </div> 60 | <?php $this->comments()->to($comments); ?> 61 | <?php if ($comments->have()): ?> 62 | <?php $comments->listComments(); ?> 63 | <div class="row align-items-center justify-content-center"><nav class="page-nav"><?php $comments->pageNav('<i class="fa fa-angle-left" aria-hidden="true"></i>', '<i class="fa fa-angle-right" aria-hidden="true"></i>', 1, '...', array('wrapTag' => 'ul', 'wrapClass' => 'pagination', 'textTag' => 'a', 'currentClass' => 'active', 'prevClass' => '', 'nextClass' => '')); ?></nav></div> 64 | <?php endif; ?> 65 | <div class="comment-card"> 66 | <?php if($this->allow('comment')): ?> 67 | <div id="respond-post" class="comment-reply"> 68 | <div class="row align-items-center justify-content-center"> 69 | <h3 id="response"><?php _e('发表评论'); ?></h3> 70 | </div> 71 | <div class="row align-items-center justify-content-center"> 72 | <?php $comments->cancelReply(); ?> 73 | </div> 74 | <br/> 75 | <form method="post" action="<?php $this->commentUrl() ?>" id="comment-form" role="form" class="container" style="overflow: auto; zoom: 1;"> 76 | <?php if($this->user->hasLogin()): ?> 77 | <p><?php _e('已登录为'); ?><a no-pjax href="<?php $this->options->profileUrl(); ?>"><?php $this->user->screenName(); ?></a>;<a href="<?php $this->options->logoutUrl(); ?>" title="Logout"><?php _e('注销?'); ?></a></p> 78 | <?php else: ?> 79 | <div class="row"> 80 | <div class="col-md-4"> 81 | <div class="form-group"> 82 | <div class="input-group mb-4"> 83 | <div class="input-group-prepend"> 84 | <span class="input-group-text" style="padding: 0rem .5rem;"> 85 | <div id="author-head" class="icon-shape rounded-circle text-white" style="width: 2rem;height: 2rem;background-image: url(https://secure.gravatar.com/avatar/);background-position: center;background-size: cover;background-repeat: no-repeat;"></div> 86 | </span> 87 | </div> 88 | <input type="text" name="author" id="author" class="form-control" placeholder="名称" value="<?php $this->remember('author'); ?>" required /> 89 | </div> 90 | </div> 91 | </div> 92 | <div class="col-md-4"> 93 | <div class="form-group"> 94 | <div class="input-group mb-4"> 95 | <div class="input-group-prepend"> 96 | <span class="input-group-text"><i class="fa fa-envelope-o" aria-hidden="true"></i></span> 97 | </div> 98 | <input type="email" name="mail" id="mail" class="form-control" placeholder="邮箱" value="<?php $this->remember('mail'); ?>"<?php if ($this->options->commentsRequireMail): ?> required<?php endif; ?> /> 99 | </div> 100 | </div> 101 | </div> 102 | <div class="col-md-4"> 103 | <div class="form-group"> 104 | <div class="input-group mb-4"> 105 | <div class="input-group-prepend"> 106 | <span class="input-group-text"><i class="fa fa-globe" aria-hidden="true"></i></span> 107 | </div> 108 | <input type="url" name="url" id="url" class="form-control" placeholder="网站" value="<?php $this->remember('url'); ?>"<?php if ($this->options->commentsRequireURL): ?> required<?php endif; ?> /> 109 | </div> 110 | </div> 111 | </div> 112 | </div> 113 | <?php endif; ?> 114 | <p> 115 | <textarea rows="8" cols="50" name="text" id="textarea" class="form-control" required ><?php $this->remember('text'); ?></textarea> 116 | </p> 117 | <p> 118 | <button type="submit" class="btn btn-outline-success" id="add-comment-button" style="float: right;"><?php _e('提交评论'); ?></button> 119 | </p> 120 | </form> 121 | </div> 122 | <?php else: ?> 123 | <div class="row align-items-center justify-content-center"><h3 id="response"><h3><?php _e('评论已关闭'); ?></h3></div> 124 | <?php endif; ?> 125 | </div> 126 | </div> 127 | </div> 128 | </section> 129 | <?php if($this->options->Pjax=="1"): ?> 130 | <script> 131 | function focusToComment(username){ 132 | var comments = $("#comments").children("div") 133 | getcomments = function(eles){ 134 | var comlist = [] 135 | var childrenlist = [] 136 | for(var p = 0; p<eles.length; p++){ 137 | var ele = $(eles[p]) 138 | var lis = ele.children("ol").children("li") 139 | for(var j = 0; j<lis.length; j++){ 140 | var h5 = $(lis[j]).children("div[id^='comment']").children(".comment-item").children(".comment-body").children(".comment-head").children("h5") 141 | var name 142 | for(var i = 0; i<h5.length; i++){ 143 | name = h5[i].innerText 144 | name = $.trim(name.split("·")[0]) 145 | if(name==username){ 146 | comlist.push(parseInt($(lis[j]).attr("id").split("-")[2])) 147 | } 148 | } 149 | var child = $(lis[j]).children(".comment-children") 150 | if(child.length>0){ 151 | childrenlist.push(child) 152 | } 153 | } 154 | } 155 | if(childrenlist.length>0){ 156 | return comlist.concat(getcomments(childrenlist)) 157 | }else{ 158 | return comlist 159 | } 160 | } 161 | var commentIds = getcomments(comments) 162 | var commentId = Math.max(...commentIds) 163 | if(commentId!=-Infinity){ 164 | $('html,body').animate({ scrollTop: $('#comment-'+commentId).offset().top-100}, 500) 165 | setTimeout(() => { 166 | $('#comment-'+commentId).fadeToggle(90); 167 | $('#comment-'+commentId).fadeToggle(110); 168 | }, 500); 169 | } 170 | } 171 | function bindsubmit(){ 172 | $("#comment-form").submit(function() { 173 | var pgid = start_progress() 174 | $("#add-comment-button").attr("disabled",true) 175 | var data = $(this).serializeArray() 176 | 177 | <?php if($this->options->commentsAntiSpam){ ?> 178 | var rubbish = <?php echo Typecho_Common::shuffleScriptVar( 179 | $this->security->getToken(clear_urlcan($this->request->getRequestUrl()))); ?> 180 | data.push({"name":"_","value":rubbish}) 181 | <?php } ?> 182 | 183 | $.ajax({ 184 | url: $(this).attr("action"), 185 | type: $(this).attr("method"), 186 | data: data, 187 | complete: function(){ 188 | $("#add-comment-button").attr("disabled",false) 189 | stop_progress(pgid) 190 | }, 191 | error: function() { 192 | alert("网络请求错误","请重新尝试提交评论") 193 | }, 194 | success: function(html) { 195 | var newdocument = new DOMParser().parseFromString(html, "text/html") 196 | if(newdocument.title == "Error"){ 197 | var error = $.trim(newdocument.getElementsByClassName("container")[0].innerText) 198 | alert("评论提交错误",error) 199 | }else{ 200 | $("#comments").html(newdocument.getElementById("comments").innerHTML) 201 | bindsubmit() 202 | var authorName = $("#author").val() ? $("#author").val() : $("a[href$='profile.php']").text() 203 | if(authorName){ 204 | focusToComment(authorName) 205 | } 206 | } 207 | } 208 | }) 209 | return false; 210 | }) 211 | } 212 | 213 | bindsubmit() 214 | </script> 215 | 216 | <?php endif; ?> 217 | 218 | <script> 219 | window.TypechoComment = { 220 | dom : function (id) { 221 | return document.getElementById(id); 222 | }, 223 | 224 | create : function (tag, attr) { 225 | var el = document.createElement(tag); 226 | 227 | for (var key in attr) { 228 | el.setAttribute(key, attr[key]); 229 | } 230 | 231 | return el; 232 | }, 233 | 234 | reply : function (cid, coid) { 235 | var comment = this.dom(cid), parent = comment.parentNode, 236 | response = this.dom('respond-post'), input = this.dom('comment-parent'), 237 | form = 'form' == response.tagName ? response : response.getElementsByTagName('form')[0], 238 | textarea = response.getElementsByTagName('textarea')[0]; 239 | 240 | if (null == input) { 241 | input = this.create('input', { 242 | 'type' : 'hidden', 243 | 'name' : 'parent', 244 | 'id' : 'comment-parent' 245 | }); 246 | 247 | form.appendChild(input); 248 | } 249 | 250 | input.setAttribute('value', coid); 251 | 252 | if (null == this.dom('comment-form-place-holder')) { 253 | var holder = this.create('div', { 254 | 'id' : 'comment-form-place-holder' 255 | }); 256 | 257 | response.parentNode.insertBefore(holder, response); 258 | } 259 | 260 | comment.appendChild(response); 261 | this.dom('cancel-comment-reply-link').style.display = ''; 262 | 263 | if (null != textarea && 'text' == textarea.name) { 264 | textarea.focus(); 265 | } 266 | 267 | return false; 268 | }, 269 | 270 | cancelReply : function () { 271 | var response = this.dom('respond-post'), 272 | holder = this.dom('comment-form-place-holder'), input = this.dom('comment-parent'); 273 | 274 | if (null != input) { 275 | input.parentNode.removeChild(input); 276 | } 277 | 278 | if (null == holder) { 279 | return true; 280 | } 281 | 282 | this.dom('cancel-comment-reply-link').style.display = 'none'; 283 | holder.parentNode.insertBefore(response, holder); 284 | return false; 285 | } 286 | }; 287 | 288 | $("#mail").on('blur',function(){ 289 | url = "https://secure.gravatar.com/avatar/" + md5($(this).val()) + "?s=40&d=" 290 | $("#author-head").css('background-image','url(' + url + ')'); 291 | }) 292 | 293 | 294 | </script> -------------------------------------------------------------------------------- /footer.php: -------------------------------------------------------------------------------- 1 | <?php if (!defined('__TYPECHO_ROOT_DIR__')) exit; ?> 2 | <?php if ($this->user->hasLogin()) { ?> 3 | <?php if ($this->is('single')) { ?> 4 | <a href="<?php $this->options->adminUrl(); ?>write-<?php echo $this->is('post')?'post':'page'; ?>.php?cid=<?php echo $this->cid;?>"> 5 | <button id="adminbtn" class="btn btn-icon-only rounded-circle btn-primary admin-btn"> 6 | <span class="btn-inner--icon"><i class="fa fa-pencil" aria-hidden="true"></i></span> 7 | </button> 8 | </a> 9 | <?php } else { ?> 10 | <a href="<?php $this->options->adminUrl(); ?>"> 11 | <button id="adminbtn" class="btn btn-icon-only rounded-circle btn-primary admin-btn"> 12 | <span class="btn-inner--icon"><i class="fa fa-cogs" aria-hidden="true"></i></span> 13 | </button> 14 | </a> 15 | <?php } ?> 16 | <?php } ?> 17 | </main> 18 | 19 | <!-- Footer --> 20 | <footer class="footer"> 21 | <div class="container"> 22 | <?php if ($this->options->footerWidget) { ?> 23 | <div class="row"> 24 | <div class="col-md-4 widget"> 25 | <h5>最新评论</h5> 26 | <?php $comments_recent = $this->widget('Widget_Comments_Recent', 'pageSize=5'); 27 | if ($comments_recent->have()) 28 | { 29 | _e('<ul>'); 30 | while($comments_recent->next()) 31 | { 32 | _e('<li><a href="' . "$comments_recent->permalink" . '" class="footer-link">' . "$comments_recent->author" . ': '); 33 | $comments_recent->excerpt(35, '...'); 34 | _e('</a></li>'); 35 | } 36 | _e('</ul>'); 37 | } 38 | else 39 | { 40 | _e('暂无评论'); 41 | } 42 | ?> 43 | </div> 44 | <div class="col-md-4 widget"> 45 | <h5>最新文章</h5> 46 | <ul><?php $this->widget('Widget_Contents_Post_Recent', 'pageSize=6')->parse('<li><a href="{permalink}" class="footer-link">{title}</a></li>'); ?></ul> 47 | </div> 48 | <div class="col-md-4 widget"> 49 | <h5>近期归档</h5> 50 | <ul><?php $this->widget('Widget_Contents_Post_Date', 'limit=6&type=month&format=F Y')->parse('<li><a href="{permalink}" class="footer-link">{date}</a></li>'); ?></ul> 51 | </div> 52 | </div> 53 | <hr/> 54 | <?php } ?> 55 | <div class="row"> 56 | <div class="col-md-6"> 57 | <div class="copyright"> 58 | <?php _e($this->options->footerText); ?> 59 | </div> 60 | </div> 61 | <div class="col-md-6"> 62 | <ul class="nav nav-footer justify-content-end"> 63 | <li class="nav-item"> 64 | <a class="nav-link" href="<?php $this->options->siteUrl(); ?>">首页</a> 65 | </li> 66 | <?php $this->widget('Widget_Contents_Page_List')->to($pages); 67 | while($pages->next()): ?> 68 | <li class="nav-item"> 69 | <a class="nav-link" href="<?php $pages->permalink(); ?>"><?php $pages->title(); ?></a> 70 | </li> 71 | <?php endwhile; ?> 72 | <?php if($this->user->hasLogin()): ?> 73 | <li class="nav-item"><a class="nav-link" href="<?php $this->options->adminUrl(); ?>">进入后台(<?php $this->user->screenName(); ?>)</a></li> 74 | <li class="nav-item"><a class="nav-link" href="<?php $this->options->logoutUrl(); ?>">退出</a></li> 75 | <?php else: ?> 76 | <li class="nav-item"><a class="nav-link" href="<?php $this->options->adminUrl('login.php'); ?>">登录</a></li> 77 | <?php endif; ?> 78 | </ul> 79 | </div> 80 | </div> 81 | </div> 82 | </footer> 83 | <?php if($this->options->Pjax) _e('</div>'); ?> 84 | <a id="scrollup" href="#" style="display: none;"> 85 | <button id="scrollbtn" class="btn btn-icon-only rounded-circle btn-secondary scrollup-btn"> 86 | <span class="btn-inner--icon"><i class="fa fa-arrow-up" aria-hidden="true"></i></span> 87 | </button> 88 | </a> 89 | <!-- Core --> 90 | <script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js"></script> 91 | <script src="https://cdn.jsdelivr.net/npm/bootstrap@4.4.1/dist/js/bootstrap.min.js"></script> 92 | <!-- Optional plugins --> 93 | <script src="https://cdn.jsdelivr.net/npm/headroom.js@0.11.0/dist/headroom.min.js"></script> 94 | <!-- Theme JS --> 95 | <script src="<?php $this->options->themeUrl("assets/js/argon.min.js"); ?>"></script> 96 | <script src="<?php $this->options->themeUrl("assets/js/bbrender.js"); ?>"></script> 97 | <!-- scrollup --> 98 | <script> 99 | $(function(){ 100 | var scrollBottom = parseInt($("#adminbtn").css("bottom")) + parseInt($("#adminbtn").css("height")) + 25; 101 | $("#scrollbtn").css("bottom", scrollBottom); 102 | var resizeTimer; 103 | $(window).resize(function(e) { 104 | if ($("#adminbtn").length > 0) 105 | { 106 | clearTimeout(resizeTimer); 107 | resizeTimer = setTimeout(function() { 108 | scrollBottom = parseInt($("#adminbtn").css("bottom")) + parseInt($("#adminbtn").css("height")) + 25; 109 | $("#scrollbtn").css("bottom", scrollBottom); 110 | }, 250); 111 | } 112 | }); 113 | var scrollLock = 0; 114 | if ($(window).scrollTop() > 500) $("#scrollup").fadeIn(400); 115 | $(window).scroll(function() { 116 | if (!scrollLock) 117 | { 118 | if ($(window).scrollTop() > 500) $("#scrollup").fadeIn(400); 119 | else $("#scrollup").fadeOut(400); 120 | } 121 | }); 122 | $("#scrollup").click(function() { 123 | scrollLock = 1; 124 | $("#scrollup").fadeOut(400); 125 | $("html,body").animate({scrollTop: "0px"}, 500, function() { 126 | scrollLock = 0; 127 | }); 128 | }); 129 | }); 130 | </script> 131 | <!-- Pjax --> 132 | 133 | <script> 134 | function init(){ 135 | <?php if($this->options->prismjs and $this->options->prismLine): ?> 136 | var pres = document.querySelectorAll('pre'); 137 | var lineNumberClassName = 'line-numbers'; 138 | pres.forEach(function (item, index) { 139 | item.className = item.className == '' ? lineNumberClassName : item.className + ' ' + lineNumberClassName; 140 | }); 141 | Prism.highlightAll(false,null); 142 | <?php endif; ?> 143 | $("img").Lazy({ 144 | threshold: 700, 145 | effect: 'fadeIn', 146 | effectTime: 1000, 147 | defaultImage: "<?php $this->options->themeUrl("images/Loading.gif"); ?>" 148 | }); 149 | $("div[data-src]").Lazy({ 150 | threshold: 700, 151 | effect: 'fadeIn', 152 | placeholder: "<?php $this->options->themeUrl("images/Loading.gif"); ?>", 153 | effectTime: 1000 154 | }); 155 | <?php if($this->options->katex): ?> 156 | try{ 157 | renderMathInElement(document.body,{ 158 | delimiters: [ 159 | {left: "$$", right: "$$", display: true}, 160 | {left: "$", right: "$", display: false} 161 | ] 162 | }) 163 | }catch(e){} 164 | <?php endif; ?> 165 | 166 | parseBbcode() 167 | parseBblink() 168 | 169 | <?php if($this->options->Pjax): ?> 170 | ;<?php $this->options->pjaxcomp() ?>; 171 | 172 | try{ 173 | window.onload() 174 | }catch(e){} 175 | <?php endif; ?> 176 | <?php if ($this->options->viewerEnable): ?> 177 | setTimeout(() => { 178 | $('.content').viewer({ 179 | url: 'data-src' 180 | }) 181 | },300) 182 | <?php endif; ?> 183 | } 184 | 185 | <?php if ($this->options->viewerEnable): ?> 186 | function destroy(){ 187 | // viewerjs 188 | var viewer = $('.content').data('viewer'); 189 | if(viewer){ 190 | viewer.destroy() 191 | } 192 | } 193 | window.addEventListener("popstate", function(e) { 194 | setTimeout(() => { 195 | $('.content').viewer({ 196 | url: 'data-src' 197 | }) 198 | },300) 199 | }, false); 200 | <?php endif; ?> 201 | </script> 202 | <?php if($this->options->Pjax): ?> 203 | <script src="https://cdn.jsdelivr.net/npm/jquery-pjax@2.0.1/jquery.pjax.js"></script> 204 | <script src="<?php $this->options->themeUrl("assets/js/progress.js"); ?>"></script> 205 | <script> 206 | var pgid = 0 207 | $(document).pjax('a[href^="<?php Helper::options()->siteUrl()?>"]:not(a[target="_blank"], a[no-pjax], a[href^="<?php Helper::options()->siteUrl()?>/admin"])', 208 | { 209 | container: '#pjax-container', 210 | fragment: '#pjax-container', 211 | timeout: 8000 212 | }).on('pjax:send', function() { 213 | pgid = start_progress() 214 | $(".black-cover").fadeIn(400) 215 | $('html,body').animate({ scrollTop: $('html').offset().top}, 500) 216 | 217 | <?php if ($this->options->viewerEnable): ?> 218 | destroy() 219 | <?php endif; ?> 220 | 221 | }).on('pjax:complete', function() { 222 | $(".black-cover").fadeOut(400) 223 | stop_progress(pgid) 224 | init() 225 | 226 | }) 227 | $("#search").submit(function() { 228 | var att = $(this).serializeArray() 229 | for(var i in att){ 230 | if(att[i].name=="s"){ 231 | $.pjax({url: <?php if ($this->options->rewrite): ?>"<?php $this->options->siteUrl(); ?>search/"+att[i].value+"/"<?php else: ?>"<?php $this->options->siteUrl(); ?>index.php/search/"+att[i].value+"/"<?php endif; ?>, container: '#pjax-container',fragment: '#pjax-container',timeout:8000}) 232 | } 233 | } 234 | return false 235 | }) 236 | </script> 237 | <div class="black-cover" style="display: none;"></div> 238 | <?php endif; ?> 239 | <!-- KaTeX JS --> 240 | <?php if($this->options->katex): ?> 241 | <script src="https://cdn.jsdelivr.net/npm/katex@0.11.1/dist/katex.min.js"></script> 242 | <script src="https://cdn.jsdelivr.net/npm/katex@0.11.1/dist/contrib/auto-render.min.js"></script> 243 | <?php endif; ?> 244 | <!-- Prism JS --> 245 | <?php if($this->options->prismjs): ?> 246 | <script src="https://cdn.jsdelivr.net/npm/prismjs@1.20.0/components/prism-core.min.js"></script> 247 | <script src="https://cdn.jsdelivr.net/npm/prismjs@1.20.0/plugins/autoloader/prism-autoloader.min.js"></script> 248 | <script src="https://cdn.jsdelivr.net/npm/prismjs@1.20.0/plugins/toolbar/prism-toolbar.min.js"></script> 249 | <script src="https://cdn.jsdelivr.net/npm/prismjs@1.20.0/plugins/show-language/prism-show-language.min.js"></script> 250 | <script src="https://cdn.jsdelivr.net/npm/prismjs@1.20.0/plugins/copy-to-clipboard/prism-copy-to-clipboard.min.js"></script> 251 | <?php if($this->options->prismLine): ?> 252 | <script src="https://cdn.jsdelivr.net/npm/prismjs@1.20.0/plugins/line-numbers/prism-line-numbers.min.js"></script> 253 | <?php endif; ?> 254 | <?php endif; ?> 255 | <!-- Alert --> 256 | <div id="modal-notification" class="modal fade show" id="modal-notification" style="z-index: 102;display: none;"> 257 | <div class="modal-dialog modal-sm"> 258 | <div class="modal-content"> 259 | <div class="modal-header"> 260 | <h5 id="msgMain" class="modal-title" id="mySmallModalLabel"></h5> 261 | <button type="button" class="close" data-dismiss="modal" aria-label="Close" onclick="$('#modal-notification').hide('normal');"> 262 | <span aria-hidden="true">×</span> 263 | </button> 264 | </div> 265 | <div id="msgDetail" class="modal-body"></div> 266 | </div> 267 | </div> 268 | </div> 269 | <script> 270 | function alert(main,detail){ 271 | $("#msgMain").html(main) 272 | if(detail) $("#msgDetail").html(detail) 273 | else $("#msgDetail").html("") 274 | $("#modal-notification").show("normal"); 275 | } 276 | init() 277 | </script> 278 | <!-- Typecho footer --> 279 | <?php $this->footer(); ?> 280 | </body> 281 | </html> -------------------------------------------------------------------------------- /functions.php: -------------------------------------------------------------------------------- 1 | <?php 2 | if (!defined('__TYPECHO_ROOT_DIR__')) exit; 3 | function themeConfig($form) { 4 | Typecho_Widget::widget('Widget_Themes_List')->to($themes); 5 | foreach ($themes -> stack as $key => $value){ 6 | if($value["activated"]==1){ 7 | break; 8 | } 9 | } 10 | 11 | if(!file_exists("themeupdater.php")){ 12 | $updater = fopen("themeupdater.php", "w"); 13 | $txt = ' 14 | <html> 15 | <head> 16 | <title>Updater 17 | 18 | 47 | 48 | 49 |
50 | $value){ 99 | $filecontent = getRequest("https://cdn.jsdelivr.net/gh/trinitrotofu/Bubble@" . $version . "/" .$value["name"]); 100 | if (!file_exists(dirname($dir.$value["name"]))){ 101 | mkdir(dirname($dir.$value["name"]),0755,true); 102 | } 103 | $fileobj = fopen($dir.$value["name"], "w"); 104 | fwrite($fileobj, $filecontent); 105 | fclose($fileobj); 106 | } 107 | 108 | echo "主题更新成功!即将返回主题页面。"; 109 | echo \'\'; 110 | @unlink ("themeupdater.php"); 111 | }catch(Exception $e){ 112 | echo "更新失败!请查看错误信息或者手动更新。
"; 113 | echo $e; 114 | } 115 | ?> 116 |
117 | 118 | '; 119 | fwrite($updater, $txt); 120 | fclose($updater); 121 | } 122 | 123 | echo ''; 138 | 139 | echo 140 | ' 141 | 142 |
    143 |
  • 144 | 147 |
  • 148 |
  • 149 |

    150 | 正在检查更新... 151 |

    152 |
  • 153 | 157 |
158 | '; 180 | 181 | $subtitle = new Typecho_Widget_Helper_Form_Element_Text('subtitle', NULL, '', _t('站点副标题'), _t('在这里填入站点副标题,以在网站标题后显示')); 182 | $form->addInput($subtitle); 183 | $logoUrl = new Typecho_Widget_Helper_Form_Element_Text('logoUrl', NULL, '', _t('站点 LOGO 地址'), _t('在这里填入一个图片 URL 地址,以在网站标题前加上一个 LOGO')); 184 | $form->addInput($logoUrl); 185 | $avatarUrl = new Typecho_Widget_Helper_Form_Element_Text('avatarUrl', NULL, '', _t('站点头像地址'), _t('在这里填入一个图片 URL 地址,以在网站首页上加上一个头像')); 186 | $form->addInput($avatarUrl); 187 | $indexImage = new Typecho_Widget_Helper_Form_Element_Text('indexImage', NULL, '', _t('首页背景图像地址'), _t('在这里填入一个图片 URL 地址, 以设定网站首页背景图片,留空则使用默认紫色渐变背景')); 188 | $form->addInput($indexImage); 189 | $randomImage = new Typecho_Widget_Helper_Form_Element_Textarea('randomImage', NULL, '', _t('随机背景图像地址'), _t('在这里填入一个或多个图片 URL 地址,每行一个,请勿包含多余字符,以设定网站文章页、独立页面以及其他页面的头图,设定后将随机显示,留空则使用默认紫色渐变背景')); 190 | $form->addInput($randomImage); 191 | $bubbleShow = new Typecho_Widget_Helper_Form_Element_Radio('bubbleShow', array('0' => _t('不显示'), '1' => _t('显示')), '1', _t('背景气泡'), _t('选择是否在首页以及文章页顶部背景处显示半透明气泡')); 192 | $form->addInput($bubbleShow); 193 | $footerText = new Typecho_Widget_Helper_Form_Element_Text('footerText', NULL, 'Powered by Typecho | Theme by Bubble', _t('页脚左下角文字'), _t('在这里填入页脚左下角的说明文字,如 Copyright 和 备案信息,可添加 HTML 标签')); 194 | $form->addInput($footerText); 195 | $footerWidget = new Typecho_Widget_Helper_Form_Element_Radio('footerWidget', array('0' => _t('不显示'), '1' => _t('显示')), '1', _t('页脚小工具'), _t('选择是否在页面底部显示“最新评论”、“最新文章”等栏目')); 196 | $form->addInput($footerWidget); 197 | $customCss = new Typecho_Widget_Helper_Form_Element_Textarea('customCss', NULL, '', _t('自定义 css'), _t('在这里填入所需要的 css,以实现自定义页面样式,如调整字体大小等')); 198 | $form->addInput($customCss); 199 | $viewerEnable = new Typecho_Widget_Helper_Form_Element_Radio('viewerEnable', array('0' => _t('关闭'), '1' => _t('打开'),), '1', _t('开启 viewer.js 图片查看器(点击放大)'), _t('选择是否启用 viewer.js 图片查看器')); 200 | $form->addInput($viewerEnable); 201 | $Pjax = new Typecho_Widget_Helper_Form_Element_Radio('Pjax', array('0' => _t('关闭'), '1' => _t('打开')), '1', _t('开启全站 pjax 模式'), _t('选择是否启用全站 pjax 模式提升用户访问体验。注意:启用该项可能带来页面加载问题,请仔细阅读主题说明文档。')); 202 | $form->addInput($Pjax); 203 | $pjaxcomp = new Typecho_Widget_Helper_Form_Element_Textarea('pjaxcomp', NULL, '', _t('pjax 回调代码'), _t('在这里填入 pjax 渲染完毕后需执行的 JS 代码,具体使用方法请仔细阅读主题说明文档')); 204 | $form->addInput($pjaxcomp); 205 | $katex = new Typecho_Widget_Helper_Form_Element_Radio('katex', array('0' => _t('关闭'), '1' => _t('打开')), '0', _t('开启 katex 数学公式渲染'), _t('选择是否启用 katex 数学公式渲染')); 206 | $form->addInput($katex); 207 | $prismjs = new Typecho_Widget_Helper_Form_Element_Radio('prismjs', array('0' => _t('关闭'), '1' => _t('打开')), '0', _t('开启 prism.js 代码高亮'), _t('选择是否启用 prism.js 代码高亮')); 208 | $form->addInput($prismjs); 209 | $prismLine = new Typecho_Widget_Helper_Form_Element_Radio('prismLine', array('0' => _t('关闭'), '1' => _t('打开')), '0', _t('开启 prism.js 行号显示'), _t('选择是否显示 prism.js 代码高亮左侧行号')); 210 | $form->addInput($prismLine); 211 | $prismTheme = new Typecho_Widget_Helper_Form_Element_Select('prismTheme', 212 | array('prism' => _t('default'), 213 | 'prism-coy' => _t('coy'), 214 | 'prism-dark' => _t('dark'), 215 | 'prism-funky' => _t('funky'), 216 | 'prism-okaidia' => _t('okaidia'), 217 | 'prism-solarizedlight' => _t('solarizedlight'), 218 | 'prism-tomorrow' => _t('tomorrow'), 219 | 'prism-twilight' => _t('twilight') 220 | ), 221 | 'prism', _t('prism.js 高亮主题'), _t('选择 prism.js 代码高亮的主题配色')); 222 | $form->addInput($prismTheme); 223 | $toc = new Typecho_Widget_Helper_Form_Element_Radio('toc', 224 | array('0' => _t('关闭'), 225 | '1' => _t('打开'), 226 | ), 227 | '1', _t('开启 TOC 文章目录功能'), _t('选择是否开启 TOC 文章目录功能')); 228 | $form->addInput($toc); 229 | $toc_enable = new Typecho_Widget_Helper_Form_Element_Radio('toc_enable', 230 | array('0' => _t('关闭'), 231 | '1' => _t('展开'), 232 | ), 233 | '0', _t('默认 TOC 目录展开状态'), _t('选择打开文章时 TOC 目录的展开状态')); 234 | $form->addInput($toc_enable); 235 | $comment_indent_style = new Typecho_Widget_Helper_Form_Element_Radio('comment_indent_style', 236 | array('native' => _t('Typecho风格'), 237 | 'bubble' => _t('Bubble风格'), 238 | ), 239 | 'bubble', _t('评论缩进风格'), _t('选择评论缩进的风格,Typecho风格会为每一层回复进行缩进,Bubble风格会在需要的时候合并评论到同一层里,方便阅读')); 240 | $form->addInput($comment_indent_style); 241 | $comment_object_nick = new Typecho_Widget_Helper_Form_Element_Radio('comment_object_nick', 242 | array('0' => _t('不显示'), 243 | '1' => _t('显示'), 244 | ), 245 | '1', _t('被回复人的昵称显示'), _t('选择是否显示被回复人的昵称,显示"aa 回复 bb",或者只显示"aa"')); 246 | $form->addInput($comment_object_nick); 247 | 248 | $header_links_html = ' 249 | 254 | 324 |

325 | 326 |
    327 |
328 |

329 |
330 |
331 | 332 | 333 |
334 |
335 | 336 | 337 |
338 |
339 | 340 | 341 | 342 |
343 |
344 | 345 |

编辑在顶部显示的链接'; 346 | $headerLinks = new Typecho_Widget_Helper_Form_Element_Text('headerLinks', NULL, '', _t('顶部跳转链接'), $header_links_html); 347 | $form->addInput($headerLinks); 348 | } 349 | 350 | function printCategory($that, $icon = 0) { ?> 351 | 352 | 353 | categories as $categories): ?> 354 | 355 | 356 | 357 | 360 | 361 | 362 | tags) > 0): ?> 363 | tags as $tags): ?> 364 | 365 | 366 | 367 | 无标签 368 | 369 | 370 | 373 | fields->pic){ ?> 374 | 375 | 376 |

377 | 378 |
379 |
380 |

title() ?>

381 |
382 | 383 | commentsNum('%d');?> 条评论 384 | 385 | 386 | author();?> 387 |
388 | excerpt(200,'...'); ?> 389 |
390 |
391 |
392 |
393 | 394 | 395 | 396 |
397 |
398 |

title() ?>

399 |
400 | 401 | commentsNum('%d');?> 条评论 402 | 403 | 404 | author();?> 405 |
406 | excerpt(200,'...'); ?> 407 |
408 |
409 |
410 |
411 | 412 | getTotal() > $that->parameter->pageSize) { ?> 416 |
417 |
418 | 419 |
420 |
421 | '); 429 | if ($show) 430 | _e(' 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | '); 440 | _e(''); 441 | } 442 | 443 | function getRandomImage($str) 444 | { 445 | if ($str == '') return ''; 446 | $arr = explode(PHP_EOL, $str); 447 | return $arr[rand(0, sizeof($arr) - 1)]; 448 | } 449 | 450 | function clear_urlcan($url) 451 | { 452 | $rstr=''; 453 | $tmparr=parse_url($url); 454 | $rstr=empty($tmparr['scheme'])?'http://':$tmparr['scheme'].'://'; 455 | $rstr.=$tmparr['host'].$tmparr['path']; 456 | return $rstr; 457 | } 458 | 459 | function createCatalog($obj) { 460 | global $catalog; 461 | global $catalog_count; 462 | $catalog = array(); 463 | $catalog_count = 0; 464 | $obj = preg_replace_callback('/(.*?)<\/h\1>/i', function($obj) { 465 | global $catalog; 466 | global $catalog_count; 467 | $catalog_count ++; 468 | $catalog[] = array('text' => trim(strip_tags($obj[3])), 'depth' => $obj[1], 'count' => $catalog_count); 469 | return ''.$obj[3].''; 470 | }, $obj); 471 | return $obj; 472 | } 473 | 474 | function getCatalog() { 475 | global $catalog; 476 | $index = ''; 477 | if ($catalog) { 478 | $index = '
    '."\n"; 479 | $prev_depth = ''; 480 | $to_depth = 0; 481 | foreach($catalog as $catalog_item) { 482 | $catalog_depth = $catalog_item['depth']; 483 | if ($prev_depth) { 484 | if ($catalog_depth == $prev_depth) { 485 | $index .= ''."\n"; 486 | } elseif ($catalog_depth > $prev_depth) { 487 | $to_depth++; 488 | $index .= '
      '."\n"; 489 | } else { 490 | $to_depth2 = ($to_depth > ($prev_depth - $catalog_depth)) ? ($prev_depth - $catalog_depth) : $to_depth; 491 | if ($to_depth2) { 492 | for ($i=0; $i<$to_depth2; $i++) { 493 | $index .= ''."\n".'
    '."\n"; 494 | $to_depth--; 495 | } 496 | } 497 | $index .= ''; 498 | } 499 | } 500 | $index .= '
  • '.$catalog_item['text'].''; 501 | $prev_depth = $catalog_item['depth']; 502 | } 503 | for ($i=0; $i<=$to_depth; $i++) { 504 | $index .= '
  • '."\n".'
'."\n"; 505 | } 506 | } 507 | echo $index; 508 | } 509 | 510 | function getCommentLineInDb($coid, $depth=3) { // 3 for getting this comment, the parent and the grandparent by default 511 | $db = Typecho_Db::get(); 512 | $commentLine = []; 513 | while((count($commentLine) < $depth) and (isset($coid) and 0 != $coid)) { 514 | $row = $db->fetchRow($db->select()->from('table.comments')->where('coid = ? ', $coid)); 515 | if(empty($row)) break; 516 | array_push($commentLine, $row); 517 | $coid = $row['parent']; 518 | } 519 | return $commentLine; 520 | } 521 | 522 | function shouldCommentIndent($comment, &$comment_line=NULL) { 523 | $commentIndentStyle = Helper::options()->comment_indent_style; 524 | $commentLine = getCommentLineInDb($comment->coid); 525 | $isTopLevel = count($commentLine) == 1; 526 | $thisAuthor = $comment->author; 527 | $parentAuthor = count($commentLine) >= 2? $commentLine[1]['author']:NULL; 528 | $grandparentAuthor = count($commentLine) >= 3? $commentLine[2]['author']:NULL; 529 | $indent = false; 530 | if($commentIndentStyle==NULL || $commentIndentStyle=='bubble') { 531 | $indent = !$isTopLevel; // 顶层回复不需要缩进,非顶层回复才需要缩进 532 | // 有父评论和爷评论 533 | if($parentAuthor && $grandparentAuthor) { 534 | // 本评论和爷评论或者父评论是同一人发布时,不需要缩进 535 | $indent &= !($thisAuthor == $parentAuthor || $thisAuthor == $grandparentAuthor); 536 | } 537 | } else { 538 | $indent = true; 539 | } 540 | if($comment_line!==NULL) { 541 | $comment_line = $commentLine; 542 | } 543 | return $indent; 544 | } 545 | 546 | function themeInit($archive) { 547 | 548 | } 549 | 550 | function themeFields($layout) { 551 | $logoUrl = new Typecho_Widget_Helper_Form_Element_Text('pic', NULL, NULL, _t('文章头图地址'), _t('在这里填入一个图片URL地址, 就可以让文章加上头图')); 552 | $layout->addItem($logoUrl); 553 | } -------------------------------------------------------------------------------- /header.php: -------------------------------------------------------------------------------- 1 | is('single')) { 3 | if ($this->options->toc) { 4 | $this->content = createCatalog($this->content); 5 | } 6 | $this->content = preg_replace('//i',"",$this->content); 7 | } 8 | ?> 9 | 10 | 11 | 12 | 13 | 14 | 15 | <?php $this->archiveTitle(array( 16 | 'category' => _t('%s 下的文章'), 17 | 'search' => _t('包含关键字 %s 的文章'), 18 | 'tag' => _t('标签 %s 下的文章'), 19 | 'author' => _t('%s 的文章') 20 | ), '', ' - '); ?><?php 21 | $this->options->title(); 22 | if ($this->is('index') && $this->options->subtitle != '') echo " - {$this->options->subtitle}"; 23 | ?> 24 | 25 | 26 | options->logoUrl(); 31 | } 32 | ?>" rel="icon" type="image/png"> 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | " rel="stylesheet"> 42 | 43 | 44 | options->katex): ?> 45 | 46 | 47 | 48 | 49 | options->prismjs): ?> 50 | 51 | 52 | options->prismLine): ?> 53 | 54 | 55 | 56 | 57 | 58 | options->viewerEnable): ?> 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | options->customCss): ?> 67 | 68 | 69 | 70 | 71 | options->viewerEnable): ?> 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | options->Pjax=="1"): ?> 83 | header('commentReply=&antiSpam='); ?> 84 | 85 | header('commentReply='); ?> 86 | 87 | 88 | 89 |
90 | 146 |
147 | options->Pjax) _e('
'); ?> -------------------------------------------------------------------------------- /images/Loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trinitrotofu/Bubble/e5ac03db4176914736939b56f6cf8679ce4303b9/images/Loading.gif -------------------------------------------------------------------------------- /images/avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trinitrotofu/Bubble/e5ac03db4176914736939b56f6cf8679ce4303b9/images/avatar.png -------------------------------------------------------------------------------- /images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trinitrotofu/Bubble/e5ac03db4176914736939b56f6cf8679ce4303b9/images/logo.png -------------------------------------------------------------------------------- /index.php: -------------------------------------------------------------------------------- 1 | need('header.php'); 13 | ?> 14 | 15 |
16 |
17 | options->indexImage, $this->options->bubbleShow); ?> 18 |
19 |
20 |
21 |
22 |
23 | options->avatarUrl(); 28 | } 29 | ?>" class="index-avatar"> 30 |
31 |

options->title() ?>

32 |
33 |

options->description() ?>

34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 | 42 | 43 | next()): ?> 44 | 45 | 46 | 47 | 48 |
49 |
50 | _currentPage>1) echo("") ?> 51 | need('footer.php'); ?> 52 | -------------------------------------------------------------------------------- /page.php: -------------------------------------------------------------------------------- 1 | need('header.php'); 4 | ?> 5 | 6 |
7 |
8 | 9 | fields->pic?$this->fields->pic:getRandomImage($this->options->randomImage)), $this->options->bubbleShow); ?> 10 |
11 |
12 |
13 |

title() ?>

14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | 22 |
23 |
24 |
25 | content(); 27 | ?> 28 |
29 |
30 |
31 |
32 | 33 | hidden && $this->allow('comment')): ?> 34 |
35 | need('comments.php'); ?> 36 |
37 | 38 |
39 |
40 | need('footer.php'); ?> -------------------------------------------------------------------------------- /post.php: -------------------------------------------------------------------------------- 1 | need('header.php'); 4 | ?> 5 | 6 |
7 | 8 | options->toc) : ?> 9 |
10 | 11 | 12 | 13 |
14 | 文章目录 15 |
16 | 17 |
18 |
19 |
20 | 67 | 68 |
69 | fields->pic ? $this->fields->pic : getRandomImage($this->options->randomImage)), $this->options->bubbleShow); ?> 70 |
71 |
72 |
73 |

title() ?>

74 |
75 |
76 |
author(); ?> 发布
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 | 85 |
86 |
87 |
88 | hidden){ ?> 89 |
90 |
91 | 92 |

写一下密码啦

93 |
94 |
95 | 96 |
97 |
98 | 99 |
100 |
101 |
102 | 141 |
142 | 143 | content(); 145 | ?> 146 |
147 |
    148 |
  • 分类:
  • 149 |
  • 标签:
  • 150 |
151 | 152 |
153 |
154 |
155 |
156 |
157 | 158 | hidden && $this->allow('comment')) $this->need('comments.php'); ?> 159 |
160 |
161 |
162 | need('footer.php'); ?> -------------------------------------------------------------------------------- /screenshot.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trinitrotofu/Bubble/e5ac03db4176914736939b56f6cf8679ce4303b9/screenshot.jpg --------------------------------------------------------------------------------