├── layout ├── includes │ ├── page │ │ ├── default-page.pug │ │ ├── categories.pug │ │ ├── notice.pug │ │ ├── tags.pug │ │ ├── stars.pug │ │ └── 404.pug │ ├── widget │ │ ├── card_ad.pug │ │ ├── card_announcement.pug │ │ ├── card_top_self.pug │ │ ├── card_categories.pug │ │ ├── card_bottom_self.pug │ │ ├── card_newest_comment.pug │ │ ├── card_clock.pug │ │ ├── card_weibo.pug │ │ ├── card_archives.pug │ │ ├── card_history.pug │ │ ├── card_post_toc.pug │ │ ├── card_tags.pug │ │ ├── card_post_series.pug │ │ ├── card_recent_post.pug │ │ └── card_author.pug │ ├── head │ │ ├── site_verification.pug │ │ ├── google_adsense.pug │ │ ├── pwa.pug │ │ ├── Open_Graph.pug │ │ ├── config_site.pug │ │ └── preconnect.pug │ ├── third-party │ │ ├── chat │ │ │ ├── index.pug │ │ │ ├── crisp.pug │ │ │ ├── chatra.pug │ │ │ └── tidio.pug │ │ ├── abcjs │ │ │ ├── index.pug │ │ │ └── abcjs.pug │ │ ├── search │ │ │ ├── index.pug │ │ │ ├── algolia.pug │ │ │ ├── local-search.pug │ │ │ └── docsearch.pug │ │ ├── share │ │ │ ├── index.pug │ │ │ ├── share-js.pug │ │ │ └── addtoany.pug │ │ ├── math │ │ │ ├── index.pug │ │ │ ├── katex.pug │ │ │ ├── mathjax.pug │ │ │ └── mermaid.pug │ │ ├── card-post-count │ │ │ ├── index.pug │ │ │ ├── remark42.pug │ │ │ ├── valine.pug │ │ │ ├── fb.pug │ │ │ ├── waline.pug │ │ │ ├── artalk.pug │ │ │ ├── disqus.pug │ │ │ └── twikoo.pug │ │ ├── pangu.pug │ │ ├── prismjs.pug │ │ ├── comments │ │ │ ├── livere.pug │ │ │ ├── js.pug │ │ │ ├── valine.pug │ │ │ ├── gitalk.pug │ │ │ ├── index.pug │ │ │ ├── utterances.pug │ │ │ ├── giscus.pug │ │ │ ├── artalk.pug │ │ │ ├── disqus.pug │ │ │ ├── facebook_comments.pug │ │ │ ├── remark42.pug │ │ │ └── disqusjs.pug │ │ ├── aplayer.pug │ │ ├── newest-comments │ │ │ ├── index.pug │ │ │ ├── remark42.pug │ │ │ ├── waline.pug │ │ │ ├── disqus-comment.pug │ │ │ ├── twikoo-comment.pug │ │ │ ├── valine.pug │ │ │ └── github-issues.pug │ │ ├── effect.pug │ │ └── pjax.pug │ ├── loading │ │ ├── index.pug │ │ ├── pace.pug │ │ ├── fullpage-loading.pug │ │ └── spinner-loading.pug │ ├── header │ │ ├── social.pug │ │ ├── menu_item.pug │ │ └── nav.pug │ ├── post │ │ ├── reward.pug │ │ └── post-copyright.pug │ ├── sidebar.pug │ ├── swiper-notice.pug │ ├── layout.pug │ ├── pagination.pug │ └── additional-js.pug ├── index.pug ├── archive.pug ├── category.pug ├── tag.pug ├── post.pug └── page.pug ├── source ├── img │ ├── 404.jpg │ ├── favicon.ico │ ├── error-page.png │ ├── friend_404.gif │ └── butterfly-icon.png ├── css │ ├── _tags │ │ ├── series.styl │ │ ├── inlineImg.styl │ │ ├── plugins │ │ │ ├── ghcard.styl │ │ │ ├── image.styl │ │ │ ├── notation.styl │ │ │ ├── poem.styl │ │ │ ├── inline-labels.styl │ │ │ ├── bubble.styl │ │ │ ├── media.styl │ │ │ ├── progress.styl │ │ │ ├── span.styl │ │ │ ├── link.styl │ │ │ └── site-card.styl │ │ ├── label.styl │ │ ├── hexo.styl │ │ ├── hide.styl │ │ ├── button.styl │ │ ├── tabs.styl │ │ └── timeline.styl │ ├── _layout │ │ ├── chat.styl │ │ ├── relatedposts.styl │ │ ├── footer.styl │ │ ├── rightside.styl │ │ ├── reward.styl │ │ ├── rightmenu.styl │ │ ├── sidebar.styl │ │ ├── comments.styl │ │ └── pagination.styl │ ├── index.styl │ ├── _highlight │ │ ├── prismjs │ │ │ ├── index.styl │ │ │ └── line-number.styl │ │ └── highlight │ │ │ ├── index.styl │ │ │ └── diff.styl │ ├── _page │ │ ├── tags.styl │ │ ├── categories.styl │ │ ├── shuoshuo.styl │ │ ├── common.styl │ │ └── 404.styl │ └── _search │ │ ├── index.styl │ │ ├── local-search.styl │ │ └── algolia.styl └── js │ └── eurkon │ ├── rgbaster.min.js │ └── refresh.js ├── scripts ├── tag │ ├── plugins │ │ ├── notation.js │ │ ├── iconfont.js │ │ ├── span.js │ │ ├── tip.js │ │ ├── poem.js │ │ ├── progress.js │ │ ├── bubble.js │ │ ├── inline-labels.js │ │ ├── carousel.js │ │ ├── ghbdage.js │ │ ├── media.js │ │ ├── btns.js │ │ ├── reference.js │ │ ├── folding.js │ │ ├── link.js │ │ ├── issues.js │ │ ├── ghcard.js │ │ ├── checkbox.js │ │ └── site.js │ ├── label.js │ ├── mermaid.js │ ├── score.js │ ├── inlineImg.js │ ├── button.js │ ├── note.js │ ├── flink.js │ ├── timeline.js │ ├── chartjs.js │ ├── gallery.js │ ├── series.js │ ├── tabs.js │ └── hide.js ├── events │ ├── 404.js │ ├── comment.js │ ├── init.js │ ├── welcome.js │ └── stylus.js ├── helpers │ ├── catalog_list.js │ ├── stars.js │ ├── year.js │ ├── series.js │ ├── random.js │ └── getArchiveLength.js ├── filters │ ├── post_lazyload.js │ └── random_cover.js └── common │ └── postDesc.js ├── .github ├── ISSUE_TEMPLATE │ ├── feature_request.yml │ ├── config.yml │ └── bug_report.yml ├── workflows │ ├── publish.yml │ └── stale.yml └── FUNDING.yml └── package.json /layout/includes/page/default-page.pug: -------------------------------------------------------------------------------- 1 | #article-container 2 | != page.content -------------------------------------------------------------------------------- /source/img/404.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Eurkon/hexo-theme-butterfly-eurkon/HEAD/source/img/404.jpg -------------------------------------------------------------------------------- /source/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Eurkon/hexo-theme-butterfly-eurkon/HEAD/source/img/favicon.ico -------------------------------------------------------------------------------- /layout/includes/widget/card_ad.pug: -------------------------------------------------------------------------------- 1 | if theme.ad && theme.ad.aside 2 | .card-widget.ads-wrap 3 | != theme.ad.aside 4 | -------------------------------------------------------------------------------- /source/img/error-page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Eurkon/hexo-theme-butterfly-eurkon/HEAD/source/img/error-page.png -------------------------------------------------------------------------------- /source/img/friend_404.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Eurkon/hexo-theme-butterfly-eurkon/HEAD/source/img/friend_404.gif -------------------------------------------------------------------------------- /source/img/butterfly-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Eurkon/hexo-theme-butterfly-eurkon/HEAD/source/img/butterfly-icon.png -------------------------------------------------------------------------------- /layout/index.pug: -------------------------------------------------------------------------------- 1 | extends includes/layout.pug 2 | 3 | block content 4 | include ./includes/mixins/indexPostUI.pug 5 | +indexPostUI -------------------------------------------------------------------------------- /source/css/_tags/series.styl: -------------------------------------------------------------------------------- 1 | #article-container 2 | .series-items 3 | a 4 | &:hover 5 | color: var(--pseudo-hover) -------------------------------------------------------------------------------- /layout/includes/head/site_verification.pug: -------------------------------------------------------------------------------- 1 | if theme.site_verification 2 | each item in theme.site_verification 3 | meta(name=item.name content=item.content) -------------------------------------------------------------------------------- /source/css/_tags/inlineImg.styl: -------------------------------------------------------------------------------- 1 | #article-container 2 | .inline-img 3 | display: inline 4 | margin: 0 3px 5 | height: 1.1em 6 | vertical-align: text-bottom -------------------------------------------------------------------------------- /layout/includes/third-party/chat/index.pug: -------------------------------------------------------------------------------- 1 | case theme.chat.use 2 | when 'chatra' 3 | include ./chatra.pug 4 | when 'tidio' 5 | include ./tidio.pug 6 | when 'crisp' 7 | include ./crisp.pug -------------------------------------------------------------------------------- /layout/includes/third-party/abcjs/index.pug: -------------------------------------------------------------------------------- 1 | if theme.abcjs.enable 2 | if theme.abcjs.per_page 3 | if is_post() || is_page() 4 | include ./abcjs.pug 5 | else if page.abcjs 6 | include ./abcjs.pug 7 | -------------------------------------------------------------------------------- /layout/includes/third-party/search/index.pug: -------------------------------------------------------------------------------- 1 | case theme.search.use 2 | when 'algolia_search' 3 | include ./algolia.pug 4 | when 'local_search' 5 | include ./local-search.pug 6 | when 'docsearch' 7 | include ./docsearch.pug -------------------------------------------------------------------------------- /layout/includes/loading/index.pug: -------------------------------------------------------------------------------- 1 | if theme.preloader.enable 2 | if theme.preloader.source === 1 3 | include ./fullpage-loading.pug 4 | if theme.preloader.source === 3 5 | include ./spinner-loading.pug 6 | else 7 | include ./pace.pug -------------------------------------------------------------------------------- /source/css/_tags/plugins/ghcard.styl: -------------------------------------------------------------------------------- 1 | a.ghcard 2 | display: inline-block 3 | line-height: 0 4 | 5 | .md .ghcard-group 6 | column-count: 2 7 | column-gap: 0 8 | margin: 0 0 - 16px * 0.5 9 | .ghcard 10 | margin: 16px * 0.5 11 | -------------------------------------------------------------------------------- /layout/includes/page/categories.pug: -------------------------------------------------------------------------------- 1 | .category-lists!= list_categories() 2 | 3 | //-
4 | //- #categories-chart(data-parent="true" style="height: 300px; padding: 10px;") -------------------------------------------------------------------------------- /source/css/_tags/plugins/image.styl: -------------------------------------------------------------------------------- 1 | .md .img 2 | object-fit: contain 3 | 4 | img.inline 5 | display: inline !important 6 | vertical-align: middle 7 | transform: translateY(-4px) 8 | p .img-alt 9 | display: inline-block 10 | width: 100% 11 | -------------------------------------------------------------------------------- /layout/includes/third-party/share/index.pug: -------------------------------------------------------------------------------- 1 | - const { use } = theme.share 2 | 3 | if use 4 | .post-share 5 | case use 6 | when 'addtoany' 7 | !=partial('includes/third-party/share/addtoany', {}, {cache: true}) 8 | when 'sharejs' 9 | include ./share-js.pug -------------------------------------------------------------------------------- /layout/includes/widget/card_announcement.pug: -------------------------------------------------------------------------------- 1 | if theme.aside.card_announcement.enable 2 | .card-widget.card-announcement 3 | .item-headline 4 | i.fas.fa-bullhorn.fa-shake 5 | span= _p('aside.card_announcement') 6 | .announcement_content!= theme.aside.card_announcement.content -------------------------------------------------------------------------------- /source/css/_tags/label.styl: -------------------------------------------------------------------------------- 1 | .hl-label 2 | padding: 2px 4px 3 | color: $btn-color 4 | addBorderRadius(3) 5 | 6 | &.default 7 | background-color: $btn-default-color 8 | 9 | for $type in $color-types 10 | &.{$type} 11 | background-color: lookup('$tagsP-' + $type + '-color') 12 | -------------------------------------------------------------------------------- /layout/includes/widget/card_top_self.pug: -------------------------------------------------------------------------------- 1 | if site.data.widget && site.data.widget.top 2 | each item in site.data.widget.top 3 | .card-widget(class=item.class_name id=item.id_name) 4 | .item-headline 5 | i(class=item.icon) 6 | span=item.name 7 | .item-content 8 | !=item.html -------------------------------------------------------------------------------- /layout/includes/widget/card_categories.pug: -------------------------------------------------------------------------------- 1 | if theme.aside.card_categories.enable 2 | if site.categories.length 3 | .card-widget.card-categories 4 | !=aside_categories({ limit: theme.aside.card_categories.limit === 0 ? 0 : theme.aside.card_categories.limit || 8 , expand: theme.aside.card_categories.expand }) 5 | -------------------------------------------------------------------------------- /source/css/_layout/chat.styl: -------------------------------------------------------------------------------- 1 | // chat 2 | if hexo-config('chat.rightside_button') == true 3 | if hexo-config('chat.use') == 'chatra' 4 | #chatra:not(.chatra--expanded) 5 | visibility: hidden !important 6 | width: 1px !important 7 | height: 1px !important 8 | opacity: 0 !important 9 | pointer-events: none -------------------------------------------------------------------------------- /scripts/tag/plugins/notation.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function Nota(args) { 4 | args = args.join(' ').split(',') 5 | let p0 = args[0].trim() 6 | let p1 = args[1].trim() 7 | return `${p0}`; 8 | } 9 | 10 | hexo.extend.tag.register('nota', Nota); 11 | // {% nota 注释词汇 ,'注释内容,使用逗号间隔开了即可' %} 12 | -------------------------------------------------------------------------------- /scripts/tag/label.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Butterfly 3 | * label 4 | * {% label text color %} 5 | */ 6 | 7 | 'use strict' 8 | 9 | const addLabel = args => { 10 | const [text, className = 'default'] = args 11 | return `${text}` 12 | } 13 | 14 | hexo.extend.tag.register('label', addLabel, { ends: false }) 15 | -------------------------------------------------------------------------------- /layout/includes/third-party/share/share-js.pug: -------------------------------------------------------------------------------- 1 | - const coverVal = page.cover_type === 'img' ? page.cover : theme.avatar.img 2 | .social-share(data-image=url_for(coverVal) data-sites= theme.share.sharejs.sites) 3 | link(rel='stylesheet' href=url_for(theme.asset.sharejs_css) media="print" onload="this.media='all'") 4 | script(src=url_for(theme.asset.sharejs) defer) -------------------------------------------------------------------------------- /layout/includes/loading/pace.pug: -------------------------------------------------------------------------------- 1 | script. 2 | window.paceOptions = { 3 | restartOnPushState: false 4 | } 5 | 6 | btf.addGlobalFn('pjaxSend', () => { 7 | Pace.restart() 8 | }, 'pace_restart') 9 | 10 | 11 | link(rel="stylesheet", href=url_for(theme.preloader.pace_css_url || theme.asset.pace_default_css)) 12 | script(src=url_for(theme.asset.pace_js)) -------------------------------------------------------------------------------- /scripts/tag/plugins/iconfont.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function iconFont(args) { 4 | args = args.join(' ').split(',') 5 | let p0 = args[0] 6 | let p1 = args[1]?args[1]:1 7 | return ``; 8 | } 9 | 10 | hexo.extend.tag.register('icon',iconFont); 11 | -------------------------------------------------------------------------------- /layout/includes/page/notice.pug: -------------------------------------------------------------------------------- 1 | #article-container 2 | h1= _p('aside.card_announcement') 3 | .timeline.notice 4 | if site.data.notice 5 | each i in site.data.notice 6 | .timeline-item 7 | .timeline-item-title 8 | .item-circle 9 | p!=i.date 10 | .timeline-item-content 11 | p!=i.msg -------------------------------------------------------------------------------- /layout/includes/page/tags.pug: -------------------------------------------------------------------------------- 1 | .tag-cloud-list.text-center 2 | !=cloudTags({source: site.tags, orderby: page.orderby || 'random', order: page.order || 1, minfontsize: 1.2, maxfontsize: 1.5, limit: 0, unit: 'em'}) 3 | 4 | //-
5 | //- #tags-chart(data-length="10" style="height: 300px; padding: 10px;") -------------------------------------------------------------------------------- /layout/includes/third-party/share/addtoany.pug: -------------------------------------------------------------------------------- 1 | .addtoany 2 | .a2a_kit.a2a_kit_size_32.a2a_default_style 3 | - let addtoanyItem = theme.addtoany.item.split(',') 4 | each name in addtoanyItem 5 | a(class="a2a_button_" + name) 6 | 7 | a.a2a_dd(href="https://www.addtoany.com/share") 8 | script(async src='https://static.addtoany.com/menu/page.js') 9 | 10 | 11 | -------------------------------------------------------------------------------- /layout/includes/widget/card_bottom_self.pug: -------------------------------------------------------------------------------- 1 | if site.data.widget && site.data.widget.bottom 2 | each item in site.data.widget.bottom 3 | .card-widget(class=item.class_name id=item.id_name style=item.order ? `order: ${item.order}` : '') 4 | .item-headline 5 | i(class=item.icon) 6 | span=item.name 7 | .item-content 8 | !=item.html 9 | 10 | -------------------------------------------------------------------------------- /layout/includes/header/social.pug: -------------------------------------------------------------------------------- 1 | each url, icon in theme.social 2 | - 3 | const [link, title, color] = url.split('||').map(i => trim(i)) 4 | const href = url_for(link) 5 | const iconStyle = color ? `color: ${color.replace(/[\'\"]/g, '')};` : '' 6 | const iconTitle = title || '' 7 | a.social-icon(href=href target="_blank" title=iconTitle) 8 | i(class=icon style=iconStyle) -------------------------------------------------------------------------------- /layout/includes/head/google_adsense.pug: -------------------------------------------------------------------------------- 1 | if (theme.google_adsense && theme.google_adsense.enable) 2 | script(async src=theme.google_adsense.js) 3 | 4 | if theme.google_adsense.auto_ads 5 | script. 6 | (adsbygoogle = window.adsbygoogle || []).push({ 7 | google_ad_client: '!{theme.google_adsense.client}', 8 | enable_page_level_ads: '!{theme.google_adsense.enable_page_level_ads}' 9 | }); -------------------------------------------------------------------------------- /layout/includes/widget/card_newest_comment.pug: -------------------------------------------------------------------------------- 1 | if theme.aside.card_newest_comments.enable && theme.comments.use && !['Livere','Facebook Comments','Giscus'].includes(theme.comments.use[0]) 2 | .card-widget#card-newest-comments 3 | .item-headline 4 | i.fas.fa-comment-dots 5 | span= _p('aside.card_newest_comments.headline') 6 | .aside-list 7 | span= _p('aside.card_newest_comments.loading_text') 8 | -------------------------------------------------------------------------------- /scripts/events/404.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Butterfly 3 | * 404 error page 4 | */ 5 | 6 | 'use strict' 7 | 8 | hexo.extend.generator.register('404', function (locals) { 9 | if (!hexo.theme.config.error_404.enable) return 10 | return { 11 | path: '404.html', 12 | layout: ['page'], 13 | data: { 14 | type: '404', 15 | top_img: false, 16 | comments: false, 17 | aside: false 18 | } 19 | } 20 | }) 21 | -------------------------------------------------------------------------------- /scripts/tag/mermaid.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Butterfly 3 | * mermaid 4 | * https://github.com/mermaid-js/mermaid 5 | */ 6 | 7 | 'use strict' 8 | 9 | const { escapeHTML } = require('hexo-util') 10 | 11 | const mermaid = (args, content) => { 12 | return `
` 15 | } 16 | 17 | hexo.extend.tag.register('mermaid', mermaid, { ends: true }) 18 | -------------------------------------------------------------------------------- /layout/includes/third-party/math/index.pug: -------------------------------------------------------------------------------- 1 | case theme.math.use 2 | when 'mathjax' 3 | if (theme.math.per_page && (is_post() || is_page())) || page.mathjax 4 | include ./mathjax.pug 5 | 6 | when 'katex' 7 | if (theme.math.per_page && (is_post() || is_page())) || page.katex 8 | include ./katex.pug 9 | 10 | if theme.mermaid.enable 11 | include ./mermaid.pug 12 | 13 | if theme.chartjs.enable 14 | include ./chartjs.pug -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yml: -------------------------------------------------------------------------------- 1 | name: Feature request 2 | description: Suggest an idea for this project 3 | title: '[Feature]: ' 4 | 5 | body: 6 | - type: textarea 7 | id: feature-request 8 | attributes: 9 | label: 想要的功能 | What feature do you want? 10 | description: 請描述你需要的新功能 | A clear and concise description of what the feature is. 11 | placeholder: 12 | value: 13 | validations: 14 | require: true -------------------------------------------------------------------------------- /source/css/index.styl: -------------------------------------------------------------------------------- 1 | if hexo-config('css_prefix') 2 | @import 'nib' 3 | 4 | @import '_third-party/normalize.min.css' 5 | // project 6 | @import 'var' 7 | @import '_global/*' 8 | @import '_highlight/highlight' 9 | @import '_page/*' 10 | @import '_layout/*' 11 | @import '_tags/*' 12 | @import '_mode/*' 13 | 14 | // search 15 | @import '_search/index' 16 | 17 | // 魔改代码START 18 | @import '_tags/plugins/*' 19 | @import 'eurkon/eurkon.css' 20 | // 魔改代码END -------------------------------------------------------------------------------- /layout/includes/third-party/card-post-count/index.pug: -------------------------------------------------------------------------------- 1 | case theme.comments.use[0] 2 | when 'Twikoo' 3 | include ./twikoo.pug 4 | when 'Disqus' 5 | when 'Disqusjs' 6 | include ./disqus.pug 7 | when 'Valine' 8 | include ./valine.pug 9 | when 'Waline' 10 | include ./waline.pug 11 | when 'Facebook Comments' 12 | include ./fb.pug 13 | when 'Remark42' 14 | include ./remark42.pug 15 | when 'Artalk' 16 | include ./artalk.pug -------------------------------------------------------------------------------- /layout/includes/widget/card_clock.pug: -------------------------------------------------------------------------------- 1 | if config.card_clock && config.card_clock.enable 2 | .card-widget.card-clock 3 | .card-content() 4 | #electric_clock 5 | #card-clock-loading 6 | center 7 | i.fa.fas.fa-spinner.fa-spin(style="font-size: 50px; padding: 30px") 8 | script(defer data-pjax src=url_for('https://pv.sohu.com/cityjson?ie=utf-8')) 9 | script(defer data-pjax src=url_for(config.card_clock.CDN)) -------------------------------------------------------------------------------- /layout/includes/post/reward.pug: -------------------------------------------------------------------------------- 1 | .post-reward 2 | .reward-button 3 | i.fas.fa-qrcode 4 | = theme.reward.text || _p('donate') 5 | .reward-main 6 | ul.reward-all 7 | each item in theme.reward.QR_code 8 | - const clickTo = item.link || item.img 9 | li.reward-item 10 | a(href=url_for(clickTo) target='_blank') 11 | img.post-qr-code-img(src=url_for(item.img) alt=item.text) 12 | .post-qr-code-desc=item.text -------------------------------------------------------------------------------- /layout/includes/widget/card_weibo.pug: -------------------------------------------------------------------------------- 1 | if config.card_weibo && config.card_weibo.enable 2 | .card-widget.card-weibo 3 | .card-content 4 | .item-headline 5 | i.fab.fa-weibo 6 | span= _p('微博热搜') 7 | #weibo-container(style="width: 100%; height: 150px;font-size: 95%;") 8 | center 9 | i.fa.fas.fa-spinner.fa-spin(style="font-size: 90px; padding: 30px") 10 | script(defer data-pjax src=url_for(config.card_weibo.CDN)) -------------------------------------------------------------------------------- /scripts/helpers/catalog_list.js: -------------------------------------------------------------------------------- 1 | hexo.extend.helper.register('catalog_list', function (type) { 2 | let html = `` 3 | hexo.locals.get(type).map(function (item) { 4 | html += ` 5 |
6 | ${(hexo.config.emoji && hexo.config.emoji[type] && hexo.config.emoji[type][item.name] || '') + item.name}${item.length} 7 |
8 | ` 9 | }) 10 | return html 11 | }) -------------------------------------------------------------------------------- /layout/includes/widget/card_archives.pug: -------------------------------------------------------------------------------- 1 | if theme.aside.card_archives.enable 2 | .card-widget.card-archives 3 | - let type = theme.aside.card_archives.type || 'monthly' 4 | - let format = theme.aside.card_archives.format || 'MMMM YYYY' 5 | - let order = theme.aside.card_archives.order || -1 6 | - let limit = theme.aside.card_archives.limit === 0 ? 0 : theme.aside.card_archives.limit || 8 7 | != aside_archives({ type:type, format: format, order: order, limit: limit }) 8 | -------------------------------------------------------------------------------- /scripts/helpers/stars.js: -------------------------------------------------------------------------------- 1 | hexo.extend.helper.register('numToStars', function (num, max = 5) { 2 | let tmp = '' 3 | for (let i = 0; i < Math.min(Math.floor(num), max); i++) { tmp += '' } // 整数部分加实心星星 4 | if (Math.min(num, max) - Math.min(Math.floor(num), max) !== 0) tmp += '' // 小数部分转成半星 5 | for (let i = 0; i < max - Math.ceil(num); i++) { tmp += '' } // 不够5个补空心星星 6 | return tmp 7 | }); -------------------------------------------------------------------------------- /scripts/tag/plugins/span.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function postP(args) { 4 | args = args.join(' ').split(',') 5 | let p0 = args[0].trim() 6 | let p1 = args[1].trim() 7 | return `

${p1}

`; 8 | } 9 | function postSpan(args) { 10 | args = args.join(' ').split(',') 11 | let p0 = args[0].trim() 12 | let p1 = args[1].trim() 13 | return `${p1}`; 14 | } 15 | 16 | hexo.extend.tag.register('p', postP); 17 | hexo.extend.tag.register('span', postSpan); 18 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: npm publish 2 | 3 | on: 4 | release: 5 | types: [created] 6 | jobs: 7 | build: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v2 11 | # Setup .npmrc file to publish to npm 12 | - uses: actions/setup-node@v1 13 | with: 14 | node-version: '12.x' 15 | registry-url: 'https://registry.npmjs.org' 16 | - run: npm install 17 | - run: npm publish 18 | env: 19 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} -------------------------------------------------------------------------------- /layout/archive.pug: -------------------------------------------------------------------------------- 1 | extends includes/layout.pug 2 | 3 | block content 4 | div 5 | include ./includes/mixins/article-sort.pug 6 | #archive 7 | //- 二选一 8 | //-
9 | //- #posts-chart(data-start="2021-01" style="height: 300px; padding: 10px;") 10 | .article-sort-title= `${_p('page.articles')} - ${getArchiveLength()}` 11 | +articleSort(page.posts, page.current) 12 | include includes/pagination.pug -------------------------------------------------------------------------------- /scripts/events/comment.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Capitalize the first letter of comment name 3 | */ 4 | 5 | hexo.extend.filter.register('before_generate', () => { 6 | const themeConfig = hexo.theme.config 7 | let { use } = themeConfig.comments 8 | if (!use) return 9 | 10 | // 確保 use 是一個陣列 11 | use = Array.isArray(use) ? use : use.split(',') 12 | 13 | // 將每個項目轉換為小寫並將首字母大寫 14 | themeConfig.comments.use = use.map(item => 15 | item.trim().toLowerCase().replace(/\b[a-z]/g, s => s.toUpperCase()) 16 | ) 17 | }) 18 | -------------------------------------------------------------------------------- /scripts/helpers/year.js: -------------------------------------------------------------------------------- 1 | hexo.extend.helper.register('getAnimalIcon', function (year) { 2 | var index = parseInt(year) % 12; 3 | var icon = { 4 | 0: 'icon-monkey', 5 | 1: 'icon-rooster', 6 | 2: 'icon-dog', 7 | 3: 'icon-boar', 8 | 4: 'icon-rat', 9 | 5: 'icon-ox', 10 | 6: 'icon-tiger', 11 | 7: 'icon-rabbit', 12 | 8: 'icon-dragon', 13 | 9: 'icon-snake', 14 | 10: 'icon-horse', 15 | 11: 'icon-goat', 16 | } 17 | return icon[index] 18 | }); -------------------------------------------------------------------------------- /scripts/tag/plugins/tip.js: -------------------------------------------------------------------------------- 1 | // 'use strict' 2 | // 3 | // function poem (args, content) { 4 | // return `
${content}
` 5 | // } 6 | // 7 | // hexo.extend.tag.register('tip', tip, { ends: true }) 8 | 9 | 'use strict' 10 | 11 | function tip (args, content) { 12 | const tipclass = args ? args.join(' ') : 'info' 13 | return `
${hexo.render.renderSync({ text: content, engine: 'markdown' })}
` 14 | } 15 | 16 | hexo.extend.tag.register('tip',tip, { ends: true }) 17 | -------------------------------------------------------------------------------- /layout/includes/third-party/math/katex.pug: -------------------------------------------------------------------------------- 1 | script. 2 | (async () => { 3 | const showKatex = () => { 4 | document.querySelectorAll('#article-container .katex').forEach(el => el.classList.add('katex-show')) 5 | } 6 | 7 | if (!window.katex_js_css) { 8 | window.katex_js_css = true 9 | await btf.getCSS('!{url_for(theme.asset.katex)}') 10 | if (!{theme.math.katex.copy_tex}) { 11 | await btf.getScript('!{url_for(theme.asset.katex_copytex)}') 12 | } 13 | } 14 | 15 | showKatex() 16 | })() -------------------------------------------------------------------------------- /layout/includes/widget/card_history.pug: -------------------------------------------------------------------------------- 1 | if config.card_history && config.card_history.enable 2 | .card-widget.card-history 3 | .card-content 4 | .item-headline 5 | i.fas.fa-clock.fa-spin 6 | span= _p('历史今日') 7 | #history-baidu(style='height: 100px; overflow: hidden; font-size:95%') 8 | #history-container.history_swiper-container(style="width: 100%; height: 100%;") 9 | #history_container_wrapper.swiper-wrapper(style="height: 20px;") 10 | script(defer data-pjax src=url_for(config.card_history.CDN)) -------------------------------------------------------------------------------- /scripts/tag/score.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Music Score 3 | * {% score %} 4 | */ 5 | 6 | 'use strict' 7 | 8 | const score = (args, content) => { 9 | const escapeHtmlTags = s => { 10 | const lookup = { 11 | '&': '&', 12 | '"': '"', 13 | '\'': ''', 14 | '<': '<', 15 | '>': '>' 16 | } 17 | return s.replace(/[&"'<>]/g, c => lookup[c]) 18 | } 19 | return `
${escapeHtmlTags(content)}
` 20 | } 21 | 22 | hexo.extend.tag.register('score', score, { ends: true }) 23 | -------------------------------------------------------------------------------- /scripts/tag/inlineImg.js: -------------------------------------------------------------------------------- 1 | /** 2 | * inlineImg 圖片 3 | * @param {Array} args 圖片名稱和高度 4 | * @param {string} args[0] 圖片名稱 5 | * @param {number} args[1] 圖片高度 6 | * @returns {string} 圖片標籤 7 | */ 8 | 9 | 'use strict' 10 | 11 | const urlFor = require('hexo-util').url_for.bind(hexo) 12 | 13 | const inlineImg = ([img, height = '']) => { 14 | const heightStyle = height ? `style="height:${height}"` : '' 15 | const src = urlFor(img) 16 | return `` 17 | } 18 | 19 | hexo.extend.tag.register('inlineImg', inlineImg, { ends: false }) 20 | -------------------------------------------------------------------------------- /layout/includes/third-party/card-post-count/remark42.pug: -------------------------------------------------------------------------------- 1 | - const { host, siteId, option } = theme.remark42 2 | 3 | script. 4 | (()=>{ 5 | window.remark_config = Object.assign({ 6 | host: '!{host}', 7 | site_id: '!{siteId}', 8 | },!{JSON.stringify(option)}) 9 | 10 | function getCount () { 11 | const s = document.createElement('script') 12 | s.src = remark_config.host + '/web/counter.js' 13 | s.defer = true 14 | document.head.appendChild(s) 15 | } 16 | 17 | window.pjax ? getCount() : window.addEventListener('load', getCount) 18 | })() -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Questions about Butterfly 4 | url: https://github.com/jerryc127/hexo-theme-butterfly/discussions 5 | about: 一些使用問題請到 Discussion 詢問。 Please ask questions in Discussion. 6 | 7 | - name: Butterfly Q&A 8 | url: https://butterfly.js.org/posts/98d20436/ 9 | about: Butterfly Q&A 10 | 11 | - name: Telegram 12 | url: https://t.me/bu2fly 13 | about: 'Official Telegram Group' 14 | 15 | - name: QQ 群 16 | url: https://jq.qq.com/?_wv=1027&k=KU9105XR 17 | about: '群號 1070540070' 18 | 19 | -------------------------------------------------------------------------------- /source/css/_tags/hexo.styl: -------------------------------------------------------------------------------- 1 | // pullquote 2 | blockquote 3 | &.pullquote 4 | position: relative 5 | max-width: 45% 6 | font-size: 110% 7 | 8 | &.left 9 | float: left 10 | margin: 1em .5em 0 0 11 | 12 | &.right 13 | float: right 14 | margin: 1em 0 0 .5em 15 | 16 | // hexo tag video 17 | .video-container 18 | position: relative 19 | overflow: hidden 20 | margin-bottom: 16px 21 | padding-top: 56.25% 22 | height: 0 23 | 24 | iframe 25 | position: absolute 26 | top: 0 27 | left: 0 28 | margin-top: 0 29 | width: 100% 30 | height: 100% 31 | -------------------------------------------------------------------------------- /scripts/tag/plugins/poem.js: -------------------------------------------------------------------------------- 1 | // 'use strict' 2 | // 3 | // function poem (args, content) { 4 | // return `
${content}
` 5 | // } 6 | // 7 | // hexo.extend.tag.register('poem', poem, { ends: true }) 8 | 9 | 'use strict' 10 | 11 | function poem (args, content) { 12 | args = args.join(' ').split(',') 13 | let p0 = args[0] 14 | let p1 = args[1]?args[1]:'' 15 | return `
${p0}
${p1}
${hexo.render.renderSync({ text: content, engine: 'markdown' })}
` 16 | } 17 | 18 | hexo.extend.tag.register('poem',poem,{ ends: true }); 19 | -------------------------------------------------------------------------------- /scripts/tag/plugins/progress.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | function postprogress(args) { 3 | args = args.join(',').split(',') 4 | if (args.length > 1) { 5 | let pwidth = args[0].trim() 6 | let pcolor = args[1].trim() 7 | let text = args[2].trim() 8 | return `
${hexo.render.renderSync({text: text, engine: 'markdown'}).split('\n').join('')}
`; 9 | } 10 | } 11 | hexo.extend.tag.register('progress', postprogress); 12 | -------------------------------------------------------------------------------- /source/css/_highlight/prismjs/index.styl: -------------------------------------------------------------------------------- 1 | if $prismjs_line_number 2 | @require 'line-number' 3 | 4 | if $highlight_theme != false 5 | @require 'diff' 6 | 7 | #article-container 8 | pre[class*='language-'] 9 | // scrollbar - firefox 10 | @-moz-document url-prefix() 11 | scrollbar-color: var(--hlscrollbar-bg) transparent 12 | 13 | &::-webkit-scrollbar-thumb 14 | background: var(--hlscrollbar-bg) 15 | 16 | &:not(.line-numbers) 17 | padding: 10px 20px 18 | 19 | .caption 20 | margin-left: -3.8em 21 | padding: 4px 16px !important 22 | 23 | a 24 | padding: 0 !important 25 | -------------------------------------------------------------------------------- /source/css/_page/tags.styl: -------------------------------------------------------------------------------- 1 | .tag-cloud 2 | &-list 3 | a 4 | display: inline-block 5 | margin: 2px 6 | padding: 2px 7px 7 | line-height: 1.7 8 | transition: all .3s 9 | addBorderRadius(5) 10 | 11 | &:hover 12 | background: var(--btn-bg) !important 13 | box-shadow: 2px 2px 6px rgba(0, 0, 0, .2) 14 | color: var(--btn-color) !important 15 | 16 | +maxWidth768() 17 | zoom: .85 18 | 19 | &-title 20 | font-size: 2.57em 21 | 22 | +maxWidth768() 23 | font-size: 2em 24 | 25 | .page-title 26 | & + .tag-cloud-list 27 | text-align: left 28 | -------------------------------------------------------------------------------- /scripts/tag/plugins/bubble.js: -------------------------------------------------------------------------------- 1 | /** 2 | * notation 3 | * {% bubble content,notation[,background-color] %} 4 | */ 5 | 6 | 'use strict' 7 | 8 | const urlFor = require('hexo-util').url_for.bind(hexo) 9 | 10 | function bubble (args) { 11 | args = args.join(' ').split(',') 12 | const content = args[0] 13 | const notation = args[1] 14 | const color = args[2] ? args[2] : '#71a4e3' 15 | 16 | return `${content}${notation}` 17 | } 18 | 19 | hexo.extend.tag.register('bubble', bubble, { ends: false }) 20 | -------------------------------------------------------------------------------- /layout/includes/head/pwa.pug: -------------------------------------------------------------------------------- 1 | - const { manifest, theme_color, apple_touch_icon, favicon_32_32, favicon_16_16, mask_icon } = theme.pwa 2 | 3 | link(rel="manifest" href=url_for(manifest)) 4 | if theme_color 5 | meta(name="msapplication-TileColor" content=theme_color) 6 | if apple_touch_icon 7 | link(rel="apple-touch-icon" sizes="180x180" href=url_for(apple_touch_icon)) 8 | if favicon_32_32 9 | link(rel="icon" type="image/png" sizes="32x32" href=url_for(favicon_32_32)) 10 | if favicon_16_16 11 | link(rel="icon" type="image/png" sizes="16x16" href=url_for(favicon_16_16)) 12 | if mask_icon 13 | link(rel="mask-icon" href=url_for(mask_icon) color="#5bbad5") 14 | -------------------------------------------------------------------------------- /layout/includes/third-party/search/algolia.pug: -------------------------------------------------------------------------------- 1 | #algolia-search 2 | .search-dialog 3 | nav.search-nav 4 | span.search-dialog-title= _p('search.title') 5 | button.search-close-button 6 | i.fas.fa-times 7 | 8 | .search-wrap 9 | #algolia-search-input 10 | hr 11 | #algolia-search-results 12 | #algolia-hits 13 | #algolia-pagination 14 | #algolia-info 15 | .algolia-stats 16 | .algolia-poweredBy 17 | 18 | #search-mask 19 | 20 | script(src=url_for(theme.asset.algolia_search)) 21 | script(src=url_for(theme.asset.instantsearch)) 22 | script(src=url_for(theme.asset.algolia_js)) -------------------------------------------------------------------------------- /layout/includes/third-party/abcjs/abcjs.pug: -------------------------------------------------------------------------------- 1 | script. 2 | (() => { 3 | const abcjsInit = () => { 4 | const abcjsFn = () => setTimeout(() => { 5 | document.querySelectorAll(".abc-music-sheet").forEach(ele => { 6 | if (ele.children.length > 0) return 7 | ABCJS.renderAbc(ele, ele.innerHTML, {responsive: 'resize'}) 8 | }) 9 | }, 100) 10 | 11 | typeof ABCJS === 'object' ? abcjsFn() 12 | : btf.getScript('!{url_for(theme.asset.abcjs_basic_js)}').then(abcjsFn) 13 | } 14 | 15 | window.pjax ? abcjsInit() : window.addEventListener('load', abcjsInit) 16 | btf.addGlobalFn('encrypt', abcjsInit, 'abcjs') 17 | })() -------------------------------------------------------------------------------- /layout/includes/widget/card_post_toc.pug: -------------------------------------------------------------------------------- 1 | - let tocNumber = typeof page.toc_number === 'boolean' ? page.toc_number : theme.toc.number 2 | - let tocExpand = typeof page.toc_expand === 'boolean' ? page.toc_expand : theme.toc.expand 3 | - let tocExpandClass = tocExpand ? 'is-expand' : '' 4 | 5 | #card-toc.card-widget 6 | .item-headline 7 | i.fas.fa-stream 8 | span= _p('aside.card_toc') 9 | span.toc-percentage 10 | 11 | if (page.encrypt == true) 12 | .toc-content.toc-div-class(class=tocExpandClass style="display:none")!=toc(page.origin, {list_number: tocNumber}) 13 | else 14 | .toc-content(class=tocExpandClass)!=toc(page.content, {list_number: tocNumber}) 15 | -------------------------------------------------------------------------------- /layout/includes/third-party/pangu.pug: -------------------------------------------------------------------------------- 1 | script. 2 | (() => { 3 | const panguFn = () => { 4 | if (typeof pangu === 'object') pangu.autoSpacingPage() 5 | else { 6 | btf.getScript('!{url_for(theme.asset.pangu)}') 7 | .then(() => { 8 | pangu.autoSpacingPage() 9 | }) 10 | } 11 | } 12 | 13 | const panguInit = () => { 14 | if (!{theme.pangu.field === 'post'}){ 15 | GLOBAL_CONFIG_SITE.isPost && panguFn() 16 | } else { 17 | panguFn() 18 | } 19 | } 20 | 21 | btf.addGlobalFn('pjaxComplete', panguInit, 'pangu') 22 | document.addEventListener('DOMContentLoaded', panguInit) 23 | })() 24 | -------------------------------------------------------------------------------- /layout/includes/third-party/card-post-count/valine.pug: -------------------------------------------------------------------------------- 1 | script. 2 | (() => { 3 | function loadValine () { 4 | function initValine () { 5 | let initData = { 6 | el: '#vcomment', 7 | appId: '#{theme.valine.appId}', 8 | appKey: '#{theme.valine.appKey}', 9 | serverURLs: '#{theme.valine.serverURLs}' 10 | } 11 | 12 | const valine = new Valine(initData) 13 | } 14 | 15 | if (typeof Valine === 'function') initValine() 16 | else btf.getScript('!{url_for(theme.asset.valine)}').then(initValine) 17 | } 18 | 19 | window.pjax ? loadValine() : window.addEventListener('load', loadValine) 20 | })() 21 | -------------------------------------------------------------------------------- /scripts/helpers/series.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | hexo.extend.helper.register('groupPosts', function () { 4 | const getGroupArray = array => { 5 | return array.reduce((groups, item) => { 6 | const key = item.series 7 | if (key) { 8 | groups[key] = groups[key] || [] 9 | groups[key].push(item) 10 | } 11 | return groups 12 | }, {}) 13 | } 14 | 15 | const sortPosts = posts => { 16 | const { orderBy = 'date', order = 1 } = this.theme.aside.card_post_series 17 | if (orderBy === 'title') return posts.sort('title', order) 18 | return posts.sort('date', order) 19 | } 20 | 21 | return getGroupArray(sortPosts(this.site.posts)) 22 | }) 23 | -------------------------------------------------------------------------------- /scripts/tag/plugins/inline-labels.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | 4 | hexo.extend.tag.register('u', function(args) { 5 | return `${args.join(' ')}`; 6 | }); 7 | hexo.extend.tag.register('emp', function(args) { 8 | return `${args.join(' ')}`; 9 | }); 10 | hexo.extend.tag.register('wavy', function(args) { 11 | return `${args.join(' ')}`; 12 | }); 13 | hexo.extend.tag.register('del', function(args) { 14 | return `${args.join(' ')}`; 15 | }); 16 | hexo.extend.tag.register('kbd', function(args) { 17 | return `${args.join(' ')}`; 18 | }); 19 | hexo.extend.tag.register('psw', function(args) { 20 | return `${args.join(' ')}`; 21 | }); 22 | -------------------------------------------------------------------------------- /layout/includes/head/Open_Graph.pug: -------------------------------------------------------------------------------- 1 | if theme.Open_Graph_meta.enable 2 | - 3 | const coverVal = page.cover_type === 'img' ? page.cover : theme.avatar.img 4 | let ogOption = Object.assign({ 5 | type: is_post() ? 'article' : 'website', 6 | image: coverVal ? full_url_for(coverVal) : '', 7 | fb_admins: theme.facebook_comments.user_id || '', 8 | fb_app_id: theme.facebook_comments.app_id || '', 9 | }, theme.Open_Graph_meta.option) 10 | - 11 | != open_graph(ogOption) 12 | else 13 | - const description = page.description || page.content || page.title || config.description 14 | if description 15 | meta(name="description" content=truncate(description, 150)) 16 | 17 | -------------------------------------------------------------------------------- /source/css/_layout/relatedposts.styl: -------------------------------------------------------------------------------- 1 | .relatedPosts 2 | margin-top: 40px 3 | 4 | & > .headline 5 | margin-bottom: 5px 6 | font-weight: 700 7 | font-size: 1.43em 8 | 9 | & > .relatedPosts-list 10 | & > a 11 | margin: 3px 12 | width: calc(33.333% - 6px) 13 | height: 200px 14 | addBorderRadius() 15 | 16 | +maxWidth768() 17 | margin: 2px 18 | width: calc(50% - 4px) 19 | height: 150px 20 | 21 | +maxWidth600() 22 | width: calc(100% - 4px) 23 | 24 | .info 25 | .info-1 26 | .info-item-2 27 | -webkit-line-clamp: 2 28 | 29 | .info-2 30 | .info-item-1 31 | -webkit-line-clamp: 3 -------------------------------------------------------------------------------- /scripts/events/init.js: -------------------------------------------------------------------------------- 1 | hexo.extend.filter.register('before_generate', () => { 2 | // Get first two digits of the Hexo version number 3 | const { version, log, locals } = hexo 4 | const hexoVer = version.replace(/(^.*\..*)\..*/, '$1') 5 | 6 | if (hexoVer < 5.3) { 7 | log.error('Please update Hexo to V5.3.0 or higher!') 8 | log.error('請把 Hexo 升級到 V5.3.0 或更高的版本!') 9 | process.exit(-1) 10 | } 11 | 12 | if (locals.get) { 13 | const data = locals.get('data') 14 | if (data && data.butterfly) { 15 | log.error("'butterfly.yml' is deprecated. Please use '_config.butterfly.yml'") 16 | log.error("'butterfly.yml' 已經棄用,請使用 '_config.butterfly.yml'") 17 | process.exit(-1) 18 | } 19 | } 20 | }) 21 | -------------------------------------------------------------------------------- /scripts/events/welcome.js: -------------------------------------------------------------------------------- 1 | hexo.on('ready', () => { 2 | const { version } = require('../../package.json') 3 | hexo.log.info(` 4 | =================================================================== 5 | ##### # # ##### ##### ###### ##### ###### # # # 6 | # # # # # # # # # # # # # 7 | ##### # # # # ##### # # ##### # # 8 | # # # # # # # ##### # # # 9 | # # # # # # # # # # # # 10 | ##### #### # # ###### # # # ###### # 11 | ${version} 12 | ===================================================================`) 13 | }) 14 | -------------------------------------------------------------------------------- /scripts/tag/button.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Button 3 | * {% btn url text icon option %} 4 | * option: color outline center block larger 5 | * color : default/blue/pink/red/purple/orange/green 6 | */ 7 | 8 | 'use strict' 9 | 10 | const urlFor = require('hexo-util').url_for.bind(hexo) 11 | 12 | const btn = args => { 13 | const [url = '', text = '', icon = '', option = ''] = args.join(' ').split(',').map(arg => arg.trim()) 14 | 15 | const iconHTML = icon ? `` : '' 16 | const textHTML = text ? `${text}` : '' 17 | 18 | return `${iconHTML}${textHTML}` 19 | } 20 | 21 | hexo.extend.tag.register('btn', btn, { ends: false }) 22 | -------------------------------------------------------------------------------- /layout/includes/widget/card_tags.pug: -------------------------------------------------------------------------------- 1 | if theme.aside.card_tags.enable 2 | if site.tags.length 3 | .card-widget.card-tags 4 | .item-headline 5 | i.fas.fa-tags 6 | span= _p('aside.card_tags') 7 | 8 | - let { limit, orderby, order } = theme.aside.card_tags 9 | - limit = limit === 0 ? 0 : limit || 40 10 | 11 | if theme.aside.card_tags.color 12 | .card-tag-cloud!= cloudTags({source: site.tags, orderby: orderby, order: order, minfontsize: 1.15, maxfontsize: 1.45, limit: limit, unit: 'em'}) 13 | else 14 | .card-tag-cloud!= tagcloud({orderby: orderby, order: order, min_font: 1.1, max_font: 1.5, amount: limit , color: true, start_color: '#999', end_color: '#99a9bf', unit: 'em'}) 15 | -------------------------------------------------------------------------------- /source/css/_page/categories.styl: -------------------------------------------------------------------------------- 1 | .category-lists 2 | .category-title 3 | font-size: 2.57em 4 | 5 | +maxWidth768() 6 | font-size: 2em 7 | 8 | .category-list 9 | margin-bottom: 0 10 | 11 | a 12 | color: var(--font-color) 13 | 14 | &:hover 15 | color: $text-hover 16 | 17 | .category-list-count 18 | margin-left: 8px 19 | color: var(--card-meta) 20 | 21 | &:before 22 | content: '(' 23 | 24 | &:after 25 | content: ')' 26 | 27 | ul 28 | padding: 0 0 0 20px 29 | @extend .list-beauty 30 | 31 | ul 32 | padding-left: 4px 33 | 34 | li 35 | position: relative 36 | margin: 6px 0 37 | padding: .12em .4em .12em 1.4em 38 | -------------------------------------------------------------------------------- /layout/includes/third-party/search/local-search.pug: -------------------------------------------------------------------------------- 1 | #local-search 2 | .search-dialog 3 | nav.search-nav 4 | span.search-dialog-title= _p('search.title') 5 | span#loading-status 6 | button.search-close-button 7 | i.fas.fa-times 8 | 9 | #loading-database.text-center 10 | i.fas.fa-spinner.fa-pulse 11 | span= ' ' + _p("search.load_data") 12 | 13 | .search-wrap 14 | #local-search-input 15 | .local-search-box 16 | input(placeholder=theme.search.placeholder || _p("search.input_placeholder") type="text").local-search-box--input 17 | hr 18 | #local-search-results 19 | #local-search-stats-wrap 20 | #search-mask 21 | 22 | script(src=url_for(theme.asset.local_search)) -------------------------------------------------------------------------------- /layout/includes/sidebar.pug: -------------------------------------------------------------------------------- 1 | if theme.menu 2 | #sidebar 3 | #menu-mask 4 | #sidebar-menus 5 | .avatar-img.text-center 6 | img(src=url_for(theme.avatar.img) onerror=`onerror=null;src='${theme.error_img.flink}'` alt="avatar") 7 | .site-data.text-center 8 | a(href=url_for(config.archive_dir) + '/') 9 | .headline= _p('aside.articles') 10 | .length-num= site.posts.length 11 | a(href=url_for(config.tag_dir) + '/' ) 12 | .headline= _p('aside.tags') 13 | .length-num= site.tags.length 14 | a(href=url_for(config.category_dir) + '/') 15 | .headline= _p('aside.categories') 16 | .length-num= site.categories.length 17 | 18 | !=partial('includes/header/menu_item', {}, {cache: true}) 19 | -------------------------------------------------------------------------------- /layout/includes/third-party/card-post-count/fb.pug: -------------------------------------------------------------------------------- 1 | - const fbSDKVer = 'v20.0' 2 | - const fbSDK = `https://connect.facebook.net/${theme.facebook_comments.lang}/sdk.js#xfbml=1&version=${fbSDKVer}` 3 | 4 | script. 5 | (()=>{ 6 | function loadFBComment () { 7 | if (typeof FB === 'object') FB.XFBML.parse(document.getElementById('recent-posts')) 8 | else { 9 | let ele = document.createElement('script') 10 | ele.setAttribute('src','!{fbSDK}') 11 | ele.setAttribute('async', 'true') 12 | ele.setAttribute('defer', 'true') 13 | ele.setAttribute('crossorigin', 'anonymous') 14 | document.body.appendChild(ele) 15 | } 16 | } 17 | window.pjax ? loadFBComment() : window.addEventListener('load', loadFBComment) 18 | })() 19 | -------------------------------------------------------------------------------- /layout/includes/page/stars.pug: -------------------------------------------------------------------------------- 1 | #article-container 2 | .flink.stars 3 | if site.data.stars 4 | each i in site.data.stars 5 | if i.class_name 6 | h2!= i.class_name 7 | if i.class_desc 8 | .flink-desc!=i.class_desc 9 | .flink-list 10 | each item in i.link_list 11 | .flink-list-item 12 | a(href=url_for(item.link) title=item.name target="_blank") 13 | .flink-item-icon.info 14 | img.no-lightbox(src=url_for(item.avatar) onerror=`this.onerror=null;this.src='` + url_for(theme.error_img.flink) + `'` alt=item.name ) 15 | .flink-item-info 16 | .flink-item-name.flink-sitename= item.name 17 | .flink-item-desc(title=item.descr)= item.descr 18 | != page.content -------------------------------------------------------------------------------- /.github/workflows/stale.yml: -------------------------------------------------------------------------------- 1 | name: 'Close stale issues and PRs' 2 | on: 3 | schedule: 4 | - cron: '30 1 * * *' 5 | 6 | jobs: 7 | stale: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/stale@v5 11 | with: 12 | days-before-issue-stale: 30 13 | days-before-pr-stale: -1 14 | days-before-close: 7 15 | stale-issue-message: 'This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.' 16 | close-pr-message: 'This issue has not seen any activity since it was marked stale. Closing.' 17 | stale-issue-label: 'Stale' 18 | exempt-issue-labels: 'pinned,bug,enhancement,documentation,Plan' 19 | operations-per-run: 1000 -------------------------------------------------------------------------------- /source/css/_tags/plugins/notation.styl: -------------------------------------------------------------------------------- 1 | span.nota 2 | color: #99a9bf 3 | text-decoration: none 4 | word-wrap: break-word 5 | transition: all .2s 6 | overflow-wrap: break-word 7 | 8 | &:hover 9 | color: var(--text-bg-hover) 10 | 11 | /*标题悬停显示文章描述*/ 12 | span.nota 13 | &:hover 14 | &:before 15 | position: fixed 16 | width:fit-content 17 | max-width: 80% 18 | margin:auto 19 | left:0; 20 | right:0 21 | top:10% 22 | border-radius: 10px 23 | text-align: center 24 | z-index: 100 25 | content: attr(data-nota) 26 | font-size: 16px 27 | color: #fff 28 | padding: 10px 29 | background-color: var(--text-bg-hover) 30 | [data-theme=dark] 31 | span.nota 32 | &:hover 33 | &:before 34 | background-color: rgba(#121212,0.8) 35 | -------------------------------------------------------------------------------- /layout/includes/third-party/card-post-count/waline.pug: -------------------------------------------------------------------------------- 1 | - const serverURL = theme.waline.serverURL.replace(/\/$/, '') 2 | script. 3 | (() => { 4 | async function loadWaline () { 5 | try { 6 | const eleGroup = document.querySelectorAll('#recent-posts .waline-comment-count') 7 | const keyArray = Array.from(eleGroup).map(i => i.getAttribute('data-path')) 8 | 9 | const res = await fetch(`!{serverURL}/api/comment?type=count&url=${keyArray}`, { method: 'GET' }) 10 | const result = await res.json() 11 | 12 | result.data.forEach((count, index) => { 13 | eleGroup[index].textContent = count 14 | }) 15 | } catch (err) { 16 | console.error(err) 17 | } 18 | } 19 | 20 | window.pjax ? loadWaline() : window.addEventListener('load', loadWaline) 21 | })() 22 | -------------------------------------------------------------------------------- /scripts/tag/plugins/carousel.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | function carousel (args, content) { 4 | args = args.join(' ').split(',') 5 | let carouselId = args[0] 6 | let carouselname = args[1]?args[1]:'carousel' 7 | return `` 8 | } 9 | 10 | hexo.extend.tag.register('carousel',carousel,{ ends: true }); 11 | /* 12 | {% carousel Id,name %} 13 | ![](/img/1.jpg) 14 | ![](/img/2.jpg) 15 | {% endcarousel %} 16 | */ 17 | -------------------------------------------------------------------------------- /layout/category.pug: -------------------------------------------------------------------------------- 1 | extends includes/layout.pug 2 | 3 | block content 4 | div 5 | if theme.category_ui == 'index' 6 | include ./includes/mixins/indexPostUI.pug 7 | +indexPostUI 8 | else 9 | include ./includes/mixins/article-sort.pug 10 | #category 11 | //- 二选一 12 | //-
13 | //- #categories-chart(data-parent="true" style="height: 300px; padding: 10px;") 14 | #catalog-bar 15 | i.fa-fw.fas.fa-shapes 16 | #catalog-list 17 | !=catalog_list("categories") 18 | a.catalog-more(href="/categories/")!= '更多' 19 | .article-sort-title= _p('page.category') + ' - ' + page.category 20 | +articleSort(page.posts, page.current) 21 | include includes/pagination.pug -------------------------------------------------------------------------------- /source/css/_layout/footer.styl: -------------------------------------------------------------------------------- 1 | #footer 2 | position: relative 3 | background-color: $light-blue 4 | background-attachment: scroll 5 | background-position: bottom 6 | background-size: cover 7 | 8 | if hexo-config('footer_img') != false && hexo-config('mask.footer') 9 | &:before 10 | position: absolute 11 | width: 100% 12 | height: 100% 13 | background-color: var(--mark-bg) 14 | content: '' 15 | 16 | #footer-wrap 17 | position: relative 18 | padding: 40px 20px 19 | color: var(--light-grey) 20 | text-align: center 21 | 22 | a 23 | color: var(--light-grey) 24 | 25 | &:hover 26 | text-decoration: underline 27 | 28 | .footer-separator 29 | margin: 0 4px 30 | 31 | .icp-icon 32 | padding: 0 4px 33 | max-height: 1.4em 34 | width: auto 35 | vertical-align: text-bottom 36 | -------------------------------------------------------------------------------- /layout/includes/third-party/prismjs.pug: -------------------------------------------------------------------------------- 1 | - const { prismjs_js, prismjs_autoloader, prismjs_lineNumber_js } = theme.asset 2 | - const { prismjs, syntax_highlighter } = config 3 | - const { enable, preprocess, line_number } = prismjs 4 | 5 | if (syntax_highlighter === 'prismjs' || enable) && !preprocess 6 | script. 7 | (() => { 8 | window.Prism = window.Prism || {} 9 | window.Prism.manual = true 10 | 11 | const highlightAll = () => { 12 | window.Prism.highlightAll() 13 | } 14 | 15 | window.addEventListener('load', highlightAll) 16 | btf.addGlobalFn('pjaxComplete', highlightAll, 'prismjs') 17 | btf.addGlobalFn('encrypt', highlightAll, 'prismjs') 18 | })() 19 | 20 | script(src=url_for(prismjs_js)) 21 | script(src=url_for(prismjs_autoloader)) 22 | if (line_number) 23 | script(src=url_for(prismjs_lineNumber_js)) -------------------------------------------------------------------------------- /source/css/_tags/plugins/poem.styl: -------------------------------------------------------------------------------- 1 | @media (min-width: 1200px) 2 | .poem 3 | margin 0 auto 4 | height auto 5 | writing-mode vertical-rl 6 | writing-mode tb-rl 7 | p 8 | text-decoration underline 9 | text-decoration-color rgba(193, 11, 11, 0.72) 10 | text-decoration-style dashed 11 | @font-face 12 | font-family 'Poem' 13 | src url('https://unpkg.zhimg.com/akilar-candyassets/fonts/Poem.ttf') 14 | font-display swap 15 | 16 | .poem 17 | p 18 | font-family 'Poem','KaiTi',sans-serif!important 19 | font-size 25px 20 | text-align center 21 | 22 | .poem-title 23 | font-family 'Poem','KaiTi',sans-serif!important 24 | font-size 2.5em 25 | text-align center 26 | 27 | .poem-author 28 | text-align center!important 29 | font-family 'Poem','KaiTi',sans-serif!important 30 | font-size 16px 31 | color rgb(66, 66, 66) 32 | -------------------------------------------------------------------------------- /scripts/tag/note.js: -------------------------------------------------------------------------------- 1 | /** 2 | * note.js 3 | * transplant from hexo-theme-next 4 | * Modify by Jerry 5 | */ 6 | 7 | 'use strict' 8 | 9 | const postNote = (args, content) => { 10 | const styleConfig = hexo.theme.config.note.style 11 | const noteTag = ['flat', 'modern', 'simple', 'disabled'] 12 | if (!noteTag.includes(args[args.length - 1])) { 13 | args.push(styleConfig) 14 | } 15 | 16 | let icon = '' 17 | const iconArray = args[args.length - 2] 18 | if (iconArray && iconArray.startsWith('fa')) { 19 | icon = `` 20 | args[args.length - 2] = 'icon-padding' 21 | } 22 | 23 | return `
${icon + hexo.render.renderSync({ text: content, engine: 'markdown' })}
` 24 | } 25 | 26 | hexo.extend.tag.register('note', postNote, { ends: true }) 27 | hexo.extend.tag.register('subnote', postNote, { ends: true }) 28 | -------------------------------------------------------------------------------- /scripts/tag/plugins/ghbdage.js: -------------------------------------------------------------------------------- 1 | /*{% bdage right,left,[logo]||[color],[link],[title]||option %}*/ 2 | 3 | function bdage (args) { 4 | 5 | args = args.join(' ').split('||') 6 | 7 | let base= args[0]?args[0].split(','):'' 8 | let right = base[0]?encodeURI(base[0].trim()):'' 9 | let left = base[1]?encodeURI(base[1].trim()):'' 10 | let logo = base[2]?base[2].trim():'' 11 | 12 | let message = args[1]?args[1].split(','):'' 13 | let color = message[0]?message[0].trim():'orange' 14 | let link = message[1]?message[1].trim():'' 15 | let title = message[2]?message[2].trim():'' 16 | 17 | let option = args[2]?args[2].trim():'' 18 | 19 | return `` 20 | } 21 | hexo.extend.tag.register('bdage',bdage); 22 | -------------------------------------------------------------------------------- /layout/includes/third-party/comments/livere.pug: -------------------------------------------------------------------------------- 1 | - const { use, lazyload } = theme.comments 2 | 3 | script. 4 | (()=>{ 5 | const loadLivere = () => { 6 | if (typeof LivereTower === 'object') window.LivereTower.init() 7 | else { 8 | (function(d, s) { 9 | var j, e = d.getElementsByTagName(s)[0]; 10 | if (typeof LivereTower === 'function') { return; } 11 | j = d.createElement(s); 12 | j.src = 'https://cdn-city.livere.com/js/embed.dist.js'; 13 | j.async = true; 14 | e.parentNode.insertBefore(j, e); 15 | })(document, 'script'); 16 | } 17 | } 18 | 19 | if ('!{use[0]}' === 'Livere' || !!{lazyload}) { 20 | if (!{lazyload}) btf.loadComment(document.getElementById('lv-container'), loadLivere) 21 | else loadLivere() 22 | } else { 23 | window.loadOtherComment = loadLivere 24 | } 25 | })() -------------------------------------------------------------------------------- /layout/includes/third-party/aplayer.pug: -------------------------------------------------------------------------------- 1 | link(rel='stylesheet' href=url_for(theme.asset.aplayer_css) media="print" onload="this.media='all'") 2 | script(src=url_for(theme.asset.aplayer_js)) 3 | script(src=url_for(theme.asset.meting_js)) 4 | if theme.pjax.enable 5 | script. 6 | (() => { 7 | const destroyAplayer = () => { 8 | if (window.aplayers) { 9 | for (let i = 0; i < window.aplayers.length; i++) { 10 | if (!window.aplayers[i].options.fixed) { 11 | window.aplayers[i].destroy() 12 | } 13 | } 14 | } 15 | } 16 | 17 | const runMetingJS = () => { 18 | typeof loadMeting === 'function' && document.getElementsByClassName('aplayer').length && loadMeting() 19 | } 20 | 21 | btf.addGlobalFn('pjaxSend', destroyAplayer, 'destroyAplayer') 22 | btf.addGlobalFn('pjaxComplete', loadMeting, 'runMetingJS') 23 | })() 24 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hexo-theme-butterfly", 3 | "version": "5.1.0", 4 | "description": "A Simple and Card UI Design theme for Hexo", 5 | "main": "package.json", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [ 10 | "hexo", 11 | "theme", 12 | "butterfly", 13 | "Card UI Design", 14 | "Jerry", 15 | "hexo-theme-butterfly" 16 | ], 17 | "repository": { 18 | "type" : "git", 19 | "url" : "https://github.com/jerryc127/hexo-theme-butterfly.git" 20 | }, 21 | "bugs": { 22 | "url": "https://github.com/jerryc127/hexo-theme-butterfly/issues", 23 | "email": "my@crazywong.com" 24 | }, 25 | "dependencies": { 26 | "hexo-renderer-stylus": "^3.0.1", 27 | "hexo-renderer-pug": "^3.0.0" 28 | }, 29 | "homepage": "https://butterfly.js.org/", 30 | "author": "Jerry ", 31 | "license": "Apache-2.0" 32 | } 33 | -------------------------------------------------------------------------------- /scripts/events/stylus.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Stylus renderer 3 | */ 4 | 5 | 'use strict' 6 | 7 | hexo.extend.filter.register('stylus:renderer', style => { 8 | const { syntax_highlighter: syntaxHighlighter, highlight, prismjs } = hexo.config 9 | let { enable: highlightEnable, line_number: highlightLineNumber } = highlight 10 | let { enable: prismjsEnable, line_number: prismjsLineNumber } = prismjs 11 | 12 | // for hexo > 7.0 13 | if (syntaxHighlighter) { 14 | highlightEnable = syntaxHighlighter === 'highlight.js' 15 | prismjsEnable = syntaxHighlighter === 'prismjs' 16 | } 17 | 18 | style.define('$highlight_enable', highlightEnable) 19 | .define('$highlight_line_number', highlightLineNumber) 20 | .define('$prismjs_enable', prismjsEnable) 21 | .define('$prismjs_line_number', prismjsLineNumber) 22 | .define('$language', hexo.config.language) 23 | // .import(`${this.source_dir.replace(/\\/g, '/')}_data/css/*`) 24 | }) 25 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 13 | custom: ['https://buy.stripe.com/3cs6rP6YA91sbbG5kk','https://jsd.012700.xyz/gh/jerryc127/CDN/Photo/wechat.jpg'] # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 14 | -------------------------------------------------------------------------------- /scripts/filters/post_lazyload.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Butterfly 3 | * lazyload 4 | * replace src to data-lazy-src 5 | */ 6 | 7 | 'use strict' 8 | 9 | const urlFor = require('hexo-util').url_for.bind(hexo) 10 | 11 | const lazyload = htmlContent => { 12 | const bg = hexo.theme.config.lazyload.placeholder ? urlFor(hexo.theme.config.lazyload.placeholder) : 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7' 13 | return htmlContent.replace(/( { 17 | const { enable, field } = hexo.theme.config.lazyload 18 | if (!enable || field !== 'site') return 19 | return lazyload(data) 20 | }) 21 | 22 | hexo.extend.filter.register('after_post_render', data => { 23 | const { enable, field } = hexo.theme.config.lazyload 24 | if (!enable || field !== 'post') return 25 | data.content = lazyload(data.content) 26 | return data 27 | }) 28 | -------------------------------------------------------------------------------- /layout/includes/third-party/newest-comments/index.pug: -------------------------------------------------------------------------------- 1 | - let { use } = theme.comments 2 | 3 | if use 4 | - let forum,apiKey,userRepo 5 | case use[0] 6 | when 'Valine' 7 | include ./valine.pug 8 | when 'Waline' 9 | include ./waline.pug 10 | when 'Twikoo' 11 | include ./twikoo-comment.pug 12 | when 'Disqus' 13 | - forum = theme.disqus.shortname 14 | - apiKey = theme.disqus.apikey 15 | include ./disqus-comment.pug 16 | when 'Disqusjs' 17 | - forum = theme.disqusjs.shortname 18 | - apiKey = theme.disqusjs.apikey 19 | include ./disqus-comment.pug 20 | when 'Gitalk' 21 | - let { repo,owner } = theme.gitalk 22 | - userRepo = owner + '/' + repo 23 | include ./github-issues.pug 24 | when 'Utterances' 25 | - userRepo = theme.utterances.repo 26 | include ./github-issues.pug 27 | when 'Remark42' 28 | include ./remark42.pug 29 | when 'Artalk' 30 | include ./artalk.pug -------------------------------------------------------------------------------- /source/css/_highlight/highlight/index.styl: -------------------------------------------------------------------------------- 1 | if $highlight_theme != false 2 | @require 'diff' 3 | 4 | #article-container 5 | figure.highlight 6 | .line 7 | if wordWrap 8 | &:before 9 | display: inline-block 10 | padding: 0 6px 0 0 11 | min-width: 30px 12 | color: var(--hlnumber-color) 13 | content: counter(line) 14 | counter-increment: line 15 | text-align: left 16 | 17 | &.marked 18 | background-color: $highlight-selection 19 | 20 | table 21 | display: block 22 | overflow: auto 23 | border: none 24 | 25 | td 26 | padding: 0 27 | border: none 28 | 29 | .gutter pre 30 | padding-right: 10px 31 | padding-left: 10px 32 | background-color: var(--hlnumber-bg) 33 | color: var(--hlnumber-color) 34 | text-align: right 35 | 36 | .code pre 37 | padding-right: 10px 38 | padding-left: 10px 39 | width: 100% 40 | -------------------------------------------------------------------------------- /layout/tag.pug: -------------------------------------------------------------------------------- 1 | extends includes/layout.pug 2 | 3 | block content 4 | div 5 | if theme.tag_ui == 'index' 6 | include ./includes/mixins/indexPostUI.pug 7 | +indexPostUI 8 | else 9 | include ./includes/mixins/article-sort.pug 10 | #tag 11 | //- 二选一 12 | //-
13 | //- #tags-chart(data-length="10" style="height: 300px; padding: 10px;") 14 | #catalog-bar 15 | i.fa-fw.fas.fa-tags 16 | #catalog-list 17 | !=catalog_list("tags") 18 | a.catalog-more(href="/tags/")!= '更多' 19 | .tag-cloud-list.is-center 20 | !=cloudTags({source: site.tags, orderby: page.orderby || 'random', order: page.order || 1, minfontsize: 1.2, maxfontsize: 2.1, limit: 0, unit: 'em'}) 21 | .article-sort-title= _p('page.tag') + ' - ' + page.tag 22 | +articleSort(page.posts, page.current) 23 | include includes/pagination.pug -------------------------------------------------------------------------------- /source/css/_tags/hide.styl: -------------------------------------------------------------------------------- 1 | // tag-hide 2 | .hide-inline, 3 | .hide-block 4 | & > .hide-button 5 | display: inline-block 6 | padding: 5px 18px 7 | background: $tag-hide-bg 8 | color: var(--white) 9 | addBorderRadius() 10 | 11 | &:hover 12 | background-color: var(--btn-hover-color) 13 | 14 | &.open 15 | display: none 16 | 17 | & + div 18 | display: block 19 | 20 | & + span 21 | display: inline 22 | 23 | & > .hide-content 24 | display: none 25 | 26 | .hide-inline 27 | & > .hide-button 28 | margin: 0 6px 29 | 30 | & > .hide-content 31 | margin: 0 6px 32 | 33 | .hide-block 34 | margin: 0 0 16px 35 | 36 | .toggle 37 | margin-bottom: 20px 38 | border: 1px solid $tag-hide-toggle-bg 39 | addBorderRadius(5, true) 40 | 41 | & > .toggle-button 42 | padding: 6px 15px 43 | background: $tag-hide-toggle-bg 44 | color: #1F2D3D 45 | cursor: pointer 46 | 47 | & > .toggle-content 48 | margin: 30px 24px 49 | -------------------------------------------------------------------------------- /layout/includes/swiper-notice.pug: -------------------------------------------------------------------------------- 1 | .notice-title 2 | i.fas.fa-bullhorn.fa-shake 3 | a(href='/notice/')= _p('aside.card_announcement') 4 | #notice-container 5 | .swiper-wrapper 6 | if site.data.notice 7 | each i in site.data.notice 8 | .notice-item.swiper-slide 9 | span.notice-item-date!= '【' + i.date + '】' 10 | span.notice-item-msg!= i.msg 11 | .notice-goto 12 | a(href='/notice/' title='查看更多...') 13 | i.fas.fa-arrow-circle-right 14 | script. 15 | var noticeContainer = document.getElementById('notice-container'); 16 | var noticeSwiper = new Swiper('#notice-container', { 17 | direction: 'vertical', 18 | loop: true, 19 | mousewheel: true, 20 | autoplay: { 21 | delay: 3000, 22 | disableOnInteraction: true, 23 | } 24 | }) 25 | noticeContainer.onmouseenter = function () { 26 | noticeSwiper.autoplay.stop(); 27 | }; 28 | noticeContainer.onmouseleave = function () { 29 | noticeSwiper.autoplay.start(); 30 | }; -------------------------------------------------------------------------------- /layout/includes/loading/fullpage-loading.pug: -------------------------------------------------------------------------------- 1 | #loading-box 2 | .loading-left-bg 3 | .loading-right-bg 4 | .spinner-box 5 | .configure-border-1 6 | .configure-core 7 | .configure-border-2 8 | .configure-core 9 | .loading-word= _p('loading') 10 | 11 | script. 12 | (()=>{ 13 | const $loadingBox = document.getElementById('loading-box') 14 | const $body = document.body 15 | const preloader = { 16 | endLoading: () => { 17 | $body.style.overflow = '' 18 | $loadingBox.classList.add('loaded') 19 | }, 20 | initLoading: () => { 21 | $body.style.overflow = 'hidden' 22 | $loadingBox.classList.remove('loaded') 23 | } 24 | } 25 | 26 | preloader.initLoading() 27 | window.addEventListener('load', preloader.endLoading) 28 | 29 | if (!{theme.pjax && theme.pjax.enable}) { 30 | btf.addGlobalFn('pjaxSend', preloader.initLoading, 'preloader_init') 31 | btf.addGlobalFn('pjaxComplete', preloader.endLoading, 'preloader_end') 32 | } 33 | })() -------------------------------------------------------------------------------- /layout/includes/head/config_site.pug: -------------------------------------------------------------------------------- 1 | - 2 | const titleVal = pageTitle.replace(/'/ig,"\\'") 3 | 4 | let isHighlightShrink 5 | if (theme.code_blocks.shrink == 'none') isHighlightShrink = 'undefined' 6 | else if (typeof page.highlight_shrink == 'boolean') isHighlightShrink = page.highlight_shrink 7 | else isHighlightShrink = theme.code_blocks.shrink 8 | 9 | var showToc = false 10 | if (theme.aside.enable && page.aside !== false) { 11 | let tocEnable = false 12 | if (is_post() && theme.toc.post) tocEnable = true 13 | else if (is_page() && theme.toc.page) tocEnable = true 14 | const pageToc = typeof page.toc === 'boolean' ? page.toc : tocEnable 15 | showToc = pageToc && (toc(page.content) !== '' || page.encrypt === true) 16 | } 17 | - 18 | 19 | script#config-diff. 20 | var GLOBAL_CONFIG_SITE = { 21 | title: '!{titleVal}', 22 | isPost: !{is_post()}, 23 | isHome: !{is_home()}, 24 | isHighlightShrink: !{isHighlightShrink}, 25 | isToc: !{showToc}, 26 | postUpdate: '!{full_date(page.updated)}' 27 | } 28 | -------------------------------------------------------------------------------- /layout/includes/third-party/search/docsearch.pug: -------------------------------------------------------------------------------- 1 | - const { placeholder, docsearch: { appId, apiKey, indexName, option } } = theme.search 2 | 3 | .docsearch-wrap 4 | #docsearch(style="display:none") 5 | link(rel="stylesheet" href=url_for(theme.asset.docsearch_css)) 6 | script(src=url_for(theme.asset.docsearch_js)) 7 | script. 8 | (() => { 9 | docsearch(Object.assign({ 10 | appId: '!{appId}', 11 | apiKey: '!{apiKey}', 12 | indexName: '!{indexName}', 13 | container: '#docsearch', 14 | placeholder: '!{ placeholder || _p("search.input_placeholder")}', 15 | }, !{JSON.stringify(option)})) 16 | 17 | const handleClick = () => { 18 | document.querySelector('.DocSearch-Button').click() 19 | } 20 | 21 | const searchClickFn = () => { 22 | btf.addEventListenerPjax(document.querySelector('#search-button > .search'), 'click', handleClick) 23 | } 24 | 25 | searchClickFn() 26 | window.addEventListener('pjax:complete', searchClickFn) 27 | })() 28 | 29 | 30 | -------------------------------------------------------------------------------- /layout/includes/third-party/card-post-count/artalk.pug: -------------------------------------------------------------------------------- 1 | - const { server, site } = theme.artalk 2 | 3 | script. 4 | (() => { 5 | const getArtalkCount = async() => { 6 | try { 7 | const eleGroup = document.querySelectorAll('#recent-posts .artalk-count') 8 | const keyArray = Array.from(eleGroup).map(i => i.getAttribute('data-page-key')) 9 | 10 | const headerList = { 11 | method: 'GET', 12 | } 13 | 14 | const searchParams = new URLSearchParams({ 15 | 'site_name': '!{site}', 16 | 'page_keys': keyArray 17 | }) 18 | 19 | const res = await fetch(`!{server}/api/v2/stats/page_comment?${searchParams}`, headerList) 20 | const result = await res.json() 21 | 22 | keyArray.forEach((key, index) => { 23 | eleGroup[index].textContent = result.data[key] || 0 24 | }) 25 | } catch (err) { 26 | console.error(err) 27 | } 28 | } 29 | 30 | window.pjax ? getArtalkCount() : window.addEventListener('load', getArtalkCount) 31 | })() -------------------------------------------------------------------------------- /scripts/tag/plugins/media.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function postAudio(args) { 4 | let src = args[0].trim() 5 | return `
`; 6 | } 7 | 8 | function postVideo(args) { 9 | let src = args[0].trim() 10 | return `
`; 11 | } 12 | 13 | function postVideos(args, content) { 14 | args = args.join(' ').split(',') 15 | var cls = args[0] 16 | if (cls.length > 0) { 17 | cls = ' ' + cls 18 | } 19 | var col = Number(args[1]) || 0; 20 | if (col > 0) { 21 | return `
${content}
` 22 | } else { 23 | return `
${content}
` 24 | } 25 | } 26 | 27 | hexo.extend.tag.register('audio', postAudio); 28 | hexo.extend.tag.register('video', postVideo); 29 | hexo.extend.tag.register('videos', postVideos, {ends: true}); 30 | -------------------------------------------------------------------------------- /layout/includes/third-party/card-post-count/disqus.pug: -------------------------------------------------------------------------------- 1 | - const { shortname, apikey } = theme.disqus 2 | script. 3 | (() => { 4 | const getCount = async () => { 5 | try { 6 | const eleGroup = document.querySelectorAll('#recent-posts .disqus-count') 7 | const cleanedLinks = Array.from(eleGroup).map(i => `thread:link=${i.href.replace(/#post-comment$/, '')}`); 8 | 9 | const res = await fetch(`https://disqus.com/api/3.0/threads/set.json?forum=!{shortname}&api_key=!{apikey}&${cleanedLinks.join('&')}`,{ 10 | method: 'GET' 11 | }) 12 | const result = await res.json() 13 | 14 | eleGroup.forEach(i => { 15 | const cleanedLink = i.href.replace(/#post-comment$/, '') 16 | const urlData = result.response.find(data => data.link === cleanedLink) || { posts: 0 } 17 | i.textContent = urlData.posts 18 | }) 19 | } catch (err) { 20 | console.error(err) 21 | } 22 | } 23 | 24 | window.pjax ? getCount() : window.addEventListener('load', getCount) 25 | })() 26 | -------------------------------------------------------------------------------- /source/js/eurkon/rgbaster.min.js: -------------------------------------------------------------------------------- 1 | !function(n){"use strict";var t=function(){return document.createElement("canvas").getContext("2d")},e=function(n,e){var a=new Image,o=n.src||n;"data:"!==o.substring(0,5)&&(a.crossOrigin="Anonymous"),a.onload=function(){var n=t("2d");n.drawImage(a,0,0);var o=n.getImageData(0,0,a.width,a.height);e&&e(o.data)},a.src=o},a=function(n){return["rgb(",n,")"].join("")},o=function(n){return n.map(function(n){return a(n.name)})},r=5,i=10,c={};c.colors=function(n,t){t=t||{};var c=t.exclude||[],u=t.paletteSize||i;e(n,function(e){for(var i=n.width*n.height||e.length,m={},s="",d=[],f={dominant:{name:"",count:0},palette:Array.apply(null,new Array(u)).map(Boolean).map(function(){return{name:"0,0,0",count:0}})},l=0;i>l;){if(d[0]=e[l],d[1]=e[l+1],d[2]=e[l+2],s=d.join(","),m[s]=s in m?m[s]+1:1,-1===c.indexOf(a(s))){var g=m[s];g>f.dominant.count?(f.dominant.name=s,f.dominant.count=g):f.palette.some(function(n){return g>n.count?(n.name=s,n.count=g,!0):void 0})}l+=4*r}if(t.success){var p=o(f.palette);t.success({dominant:a(f.dominant.name),secondary:p[0],palette:p})}})},n.RGBaster=n.RGBaster||c}(window); -------------------------------------------------------------------------------- /scripts/tag/plugins/btns.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function postBtns(args, content) { 4 | return `
5 | ${content} 6 |
`; 7 | } 8 | 9 | function postCell(args, content) { 10 | args = args.join(' ').split(',') 11 | let text = args[0] || '' 12 | let url = args[1] || '' 13 | text = text.trim() 14 | url = url.trim() 15 | if (url.length > 0) { 16 | url = "href='" + url + "'" 17 | } 18 | let icon = '' 19 | let img = 'https://unpkg.zhimg.com/hexo-butterfly-tag-plugins-plus@latest/lib/assets/default.svg' 20 | if (args.length > 2) { 21 | if (args[2].indexOf(' fa-') > -1) { 22 | icon = args[2].trim() 23 | } else { 24 | img = args[2].trim() 25 | } 26 | } 27 | if (icon.length > 0) { 28 | return `${text}` 29 | } else { 30 | return `${text}` 31 | } 32 | } 33 | 34 | hexo.extend.tag.register('btns', postBtns, {ends: true}); 35 | hexo.extend.tag.register('cell', postCell); 36 | -------------------------------------------------------------------------------- /scripts/common/postDesc.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { stripHTML, truncate } = require('hexo-util') 4 | 5 | // Truncates the given content to a specified length, removing HTML tags and replacing newlines with spaces. 6 | const truncateContent = (content, length) => { 7 | return truncate(stripHTML(content), { length, separator: ' ' }).replace(/\n/g, ' ') 8 | } 9 | 10 | // Generates a post description based on the provided data and theme configuration. 11 | const postDesc = (data, hexo) => { 12 | const { description, content, postDesc } = data 13 | 14 | if (postDesc) return postDesc 15 | 16 | const { length, method } = hexo.theme.config.index_post_content 17 | 18 | if (method === false) return 19 | 20 | let result 21 | switch (method) { 22 | case 1: 23 | result = description 24 | break 25 | case 2: 26 | result = description || truncateContent(content, length) 27 | break 28 | default: 29 | result = truncateContent(content, length) 30 | } 31 | 32 | data.postDesc = result 33 | return result 34 | } 35 | 36 | module.exports = { truncateContent, postDesc } 37 | -------------------------------------------------------------------------------- /layout/includes/third-party/comments/js.pug: -------------------------------------------------------------------------------- 1 | each name in theme.comments.use 2 | case name 3 | when 'Valine' 4 | !=partial('includes/third-party/comments/valine', {}, {cache: true}) 5 | when 'Disqus' 6 | include ./disqus.pug 7 | when 'Disqusjs' 8 | include ./disqusjs.pug 9 | when 'Livere' 10 | !=partial('includes/third-party/comments/livere', {}, {cache: true}) 11 | when 'Gitalk' 12 | include ./gitalk.pug 13 | when 'Utterances' 14 | !=partial('includes/third-party/comments/utterances', {}, {cache: true}) 15 | when 'Twikoo' 16 | !=partial('includes/third-party/comments/twikoo', {}, {cache: true}) 17 | when 'Waline' 18 | !=partial('includes/third-party/comments/waline', {}, {cache: true}) 19 | when 'Giscus' 20 | !=partial('includes/third-party/comments/giscus', {}, {cache: true}) 21 | when 'Facebook Comments' 22 | include ./facebook_comments.pug 23 | when 'Remark42' 24 | !=partial('includes/third-party/comments/remark42', {}, {cache: true}) 25 | when 'Artalk' 26 | !=partial('includes/third-party/comments/artalk', {}, {cache: true}) -------------------------------------------------------------------------------- /layout/includes/header/menu_item.pug: -------------------------------------------------------------------------------- 1 | if theme.menu 2 | .menus_items 3 | each value, label in theme.menu 4 | if typeof value !== 'object' 5 | .menus_item 6 | - const [link, icon] = value.split('||').map(part => trim(part)) 7 | a.site-page(href=url_for(link)) 8 | if icon 9 | i.fa-fw(class=icon) 10 | span= ' ' + label 11 | else 12 | .menus_item 13 | - const [groupLabel, groupIcon, groupClass] = label.split('||').map(part => trim(part)) 14 | - const hideClass = groupClass === 'hide' ? 'hide' : '' 15 | span.site-page.group(class=hideClass) 16 | if groupIcon 17 | i.fa-fw(class=groupIcon) 18 | span= ' ' + groupLabel 19 | i.fas.fa-chevron-down 20 | ul.menus_item_child 21 | each val, lab in value 22 | - const [childLink, childIcon] = val.split('||').map(part => trim(part)) 23 | li 24 | a.site-page.child(href=url_for(childLink)) 25 | if childIcon 26 | i.fa-fw(class=childIcon) 27 | span= ' ' + lab -------------------------------------------------------------------------------- /layout/includes/third-party/chat/crisp.pug: -------------------------------------------------------------------------------- 1 | script. 2 | (() => { 3 | window.$crisp = []; 4 | window.CRISP_WEBSITE_ID = "!{theme.crisp.website_id}"; 5 | (function () { 6 | d = document; 7 | s = d.createElement("script"); 8 | s.src = "https://client.crisp.chat/l.js"; 9 | s.async = 1; 10 | d.getElementsByTagName("head")[0].appendChild(s); 11 | })(); 12 | $crisp.push(["safe", true]) 13 | 14 | const isChatBtn = !{theme.chat.rightside_button} 15 | const isChatHideShow = !{theme.chat.button_hide_show} 16 | 17 | if (isChatBtn) { 18 | const open = () => { 19 | $crisp.push(["do", "chat:show"]) 20 | $crisp.push(["do", "chat:open"]) 21 | } 22 | 23 | const close = () => $crisp.push(["do", "chat:hide"]) 24 | 25 | close() 26 | 27 | $crisp.push(["on", "chat:closed", close]) 28 | 29 | window.chatBtnFn = () => $crisp.is("chat:visible") ? close() : open() 30 | 31 | } else if (isChatHideShow) { 32 | window.chatBtn = { 33 | hide: () => $crisp.push(["do", "chat:hide"]), 34 | show: () => $crisp.push(["do", "chat:show"]) 35 | } 36 | } 37 | })() -------------------------------------------------------------------------------- /layout/includes/third-party/newest-comments/remark42.pug: -------------------------------------------------------------------------------- 1 | - const { host, siteId } = theme.remark42 2 | 3 | script. 4 | window.addEventListener('load', () => { 5 | const keyName = 'remark42-newest-comments' 6 | const { changeContent, generateHtml, run } = window.newestComments 7 | 8 | const getComment = ele => { 9 | fetch('!{host}/api/v1/last/!{theme.aside.card_newest_comments.limit}?site=!{siteId}') 10 | .then(response => response.json()) 11 | .then(data => { 12 | const remark42 = data.map(e => { 13 | return { 14 | 'avatar': e.user.picture, 15 | 'content': changeContent(e.text), 16 | 'nick': e.user.name, 17 | 'url': e.locator.url, 18 | 'date': e.time, 19 | } 20 | }) 21 | btf.saveToLocal.set(keyName, JSON.stringify(remark42), !{theme.aside.card_newest_comments.storage}/(60*24)) 22 | generateHtml(remark42, ele) 23 | }).catch(e => { 24 | console.error(e) 25 | ele.textContent= "!{_p('aside.card_newest_comments.error')}" 26 | }) 27 | } 28 | 29 | run(keyName, getComment) 30 | }) 31 | -------------------------------------------------------------------------------- /source/css/_page/shuoshuo.styl: -------------------------------------------------------------------------------- 1 | #article-container 2 | .shuoshuo-item 3 | @extend .cardHover 4 | margin-bottom: 20px 5 | padding: 35px 30px 30px 6 | 7 | +maxWidth768() 8 | padding: 25px 20px 20px 9 | 10 | .shuoshuo-item-header 11 | display: flex 12 | align-items: center 13 | cursor: default 14 | 15 | .shuoshuo-avatar 16 | overflow: hidden 17 | width: 40px 18 | height: 40px 19 | border-radius: 40px 20 | 21 | img 22 | margin: 0 23 | width: 100% 24 | height: 100% 25 | 26 | .shuoshuo-info 27 | margin-left: 10px 28 | line-height: 1.5 29 | 30 | .shuoshuo-date 31 | color: #858585 32 | font-size: .8em 33 | 34 | .shuoshuo-content 35 | padding: 15px 0 10px 36 | 37 | & > *:last-child 38 | margin-bottom: 0 39 | 40 | .shuoshuo-tag 41 | display: inline-block 42 | margin-right: 8px 43 | padding: 0 8px 44 | width: fit-content 45 | border: 1px solid $light-blue 46 | border-radius: 12px 47 | color: $light-blue 48 | font-size: .85em 49 | cursor: default 50 | transition: all .2s ease-in-out 51 | 52 | &:hover 53 | background: $light-blue 54 | color: var(--white) 55 | -------------------------------------------------------------------------------- /layout/includes/widget/card_post_series.pug: -------------------------------------------------------------------------------- 1 | if theme.aside.card_post_series.enable 2 | - const array = fragment_cache('seriesArr', groupPosts) 3 | .card-widget.card-post-series 4 | .item-headline 5 | i.fa-solid.fa-layer-group 6 | span= theme.aside.card_post_series.series_title ? page.series : _p('aside.card_post_series') 7 | .aside-list 8 | each item in array[page.series] 9 | - const { path, title = _p('no_title'), cover, cover_type, date:dateA } = item 10 | - let link = url_for(path) 11 | - let no_cover = cover === false || !theme.cover.aside_enable ? 'no-cover' : '' 12 | .aside-list-item(class=no_cover) 13 | if cover && theme.cover.aside_enable 14 | a.thumbnail(href=link title=title) 15 | if cover_type === 'img' 16 | img(src=url_for(cover) onerror=`this.onerror=null;this.src='${url_for(theme.error_img.post_page)}'` alt=title) 17 | else 18 | div(style=`background: ${cover}`) 19 | .content 20 | a.title(href=link title=title)= title 21 | time(datetime=date_xml(dateA) title=_p('post.created') + ' ' + full_date(dateA)) #[=date(dateA, config.date_format)] 22 | -------------------------------------------------------------------------------- /layout/includes/third-party/card-post-count/twikoo.pug: -------------------------------------------------------------------------------- 1 | script. 2 | (() => { 3 | const getCommentUrl = () => { 4 | const eleGroup = document.querySelectorAll('#recent-posts .article-title') 5 | let urlArray = [] 6 | eleGroup.forEach(i=>{ 7 | urlArray.push(i.getAttribute('href')) 8 | }) 9 | return urlArray 10 | } 11 | 12 | const getCount = () => { 13 | const runTwikoo = () => { 14 | twikoo.getCommentsCount({ 15 | envId: '!{theme.twikoo.envId}', 16 | region: '!{theme.twikoo.region}', 17 | urls: getCommentUrl(), 18 | includeReply: false 19 | }).then(function (res) { 20 | document.querySelectorAll('#recent-posts .twikoo-count').forEach((item,index) => { 21 | item.textContent = res[index].count 22 | }) 23 | }).catch(function (err) { 24 | console.log(err) 25 | }) 26 | } 27 | 28 | if (typeof twikoo === 'object') { 29 | runTwikoo() 30 | } else { 31 | btf.getScript('!{url_for(theme.asset.twikoo)}').then(runTwikoo) 32 | } 33 | } 34 | 35 | window.pjax ? getCount() : window.addEventListener('load', getCount) 36 | 37 | })() -------------------------------------------------------------------------------- /source/css/_search/index.styl: -------------------------------------------------------------------------------- 1 | .search-dialog 2 | position: fixed 3 | top: 10% 4 | left: 50% 5 | z-index: 1001 6 | display: none 7 | margin-left: -300px 8 | padding: 20px 9 | width: 600px 10 | background: var(--search-bg) 11 | --search-height: 100vh 12 | addBorderRadius(8) 13 | 14 | +maxWidth768() 15 | top: 0 16 | left: 0 17 | margin: 0 18 | width: 100% 19 | height: 100% 20 | border-radius: 0 21 | 22 | .search-nav 23 | margin: 0 0 14px 24 | color: $search-color 25 | font-size: 1.4em 26 | line-height: 1 27 | 28 | .search-dialog-title 29 | margin-right: 10px 30 | 31 | .search-close-button 32 | float: right 33 | color: $grey 34 | transition: color .2s ease-in-out 35 | 36 | &:hover 37 | color: $search-color 38 | 39 | hr 40 | margin: 15px auto 41 | @extend .custom-hr 42 | 43 | #search-mask 44 | position: fixed 45 | top: 0 46 | right: 0 47 | bottom: 0 48 | left: 0 49 | z-index: 1000 50 | display: none 51 | background: rgba($dark-black, .6) 52 | 53 | if hexo-config('search.use') == 'algolia_search' 54 | @require 'algolia' 55 | else if hexo-config('search.use') == 'local_search' 56 | @require 'local-search' -------------------------------------------------------------------------------- /layout/includes/third-party/comments/valine.pug: -------------------------------------------------------------------------------- 1 | - const { use, lazyload } = theme.comments 2 | - const { appId, appKey, avatar, serverURLs, visitor, option } = theme.valine 3 | 4 | - let emojiMaps = '""' 5 | if site.data.valine 6 | - emojiMaps = JSON.stringify(site.data.valine) 7 | 8 | script. 9 | (() => { 10 | const initValine = () => { 11 | const valine = new Valine(Object.assign({ 12 | el: '#vcomment', 13 | appId: '#{appId}', 14 | appKey: '#{appKey}', 15 | avatar: '#{avatar}', 16 | serverURLs: '#{serverURLs}', 17 | emojiMaps: !{emojiMaps}, 18 | path: window.location.pathname, 19 | visitor: #{visitor} 20 | }, !{JSON.stringify(option)})) 21 | } 22 | 23 | const loadValine = async () => { 24 | if (typeof Valine === 'function') initValine() 25 | else { 26 | await btf.getScript('!{url_for(theme.asset.valine)}') 27 | initValine() 28 | } 29 | } 30 | 31 | if ('!{use[0]}' === 'Valine' || !!{lazyload}) { 32 | if (!{lazyload}) btf.loadComment(document.getElementById('vcomment'),loadValine) 33 | else setTimeout(loadValine, 0) 34 | } else { 35 | window.loadOtherComment = loadValine 36 | } 37 | })() 38 | 39 | -------------------------------------------------------------------------------- /source/css/_highlight/prismjs/line-number.styl: -------------------------------------------------------------------------------- 1 | #article-container 2 | pre[class*='language-'] 3 | &.line-numbers 4 | position: relative 5 | padding-left: 3.8em 6 | counter-reset: linenumber 7 | line-height: $line-height-code-block 8 | 9 | > code 10 | position: relative 11 | line-height: $line-height-code-block 12 | 13 | if hexo-config('code_blocks.word_wrap') 14 | white-space: pre-wrap 15 | else 16 | white-space: inherit 17 | word-wrap: normal 18 | word-break: normal 19 | overflow-wrap: normal 20 | 21 | .line-numbers-rows 22 | position: absolute 23 | top: 0 24 | left: -3.8em 25 | width: 3em 26 | letter-spacing: -1px 27 | font-size: 100% 28 | pointer-events: none 29 | user-select: none 30 | -webkit-user-select: none 31 | 32 | & > span 33 | display: block 34 | counter-increment: linenumber 35 | pointer-events: none 36 | 37 | &:before 38 | display: block 39 | padding-right: .8em 40 | color: var(--hlnumber-color) 41 | content: counter(linenumber) 42 | text-align: right 43 | -------------------------------------------------------------------------------- /layout/post.pug: -------------------------------------------------------------------------------- 1 | extends includes/layout.pug 2 | 3 | block content 4 | div 5 | #post 6 | if top_img === false 7 | include includes/header/post-info.pug 8 | 9 | article#article-container.post-content!=page.content 10 | include includes/post/post-copyright.pug 11 | .tag_share 12 | if (page.tags.length > 0 && theme.post_meta.post.tags) 13 | .post-meta__tag-list 14 | each item, index in page.tags.data 15 | a(href=url_for(item.path)).post-meta__tags #[=(config.emoji && config.emoji.tags && config.emoji.tags[item.name] || '') + item.name] 16 | include includes/third-party/share/index.pug 17 | 18 | if theme.reward.enable && theme.reward.QR_code 19 | !=partial('includes/post/reward', {}, {cache: true}) 20 | 21 | //- ad 22 | if theme.ad && theme.ad.post 23 | .ads-wrap!=theme.ad.post 24 | 25 | if theme.post_pagination 26 | include includes/pagination.pug 27 | if theme.related_post && theme.related_post.enable 28 | != related_posts(page,site.posts) 29 | 30 | if page.comments !== false && theme.comments.use 31 | - var commentsJsLoad = true 32 | !=partial('includes/third-party/comments/index', {}, {cache: true}) -------------------------------------------------------------------------------- /layout/includes/third-party/chat/chatra.pug: -------------------------------------------------------------------------------- 1 | //- https://chatra.io/help/api/ 2 | script. 3 | (() => { 4 | const isChatBtn = !{theme.chat.rightside_button} 5 | const isChatHideShow = !{theme.chat.button_hide_show} 6 | 7 | if (isChatBtn) { 8 | const close = () => { 9 | Chatra('minimizeWidget') 10 | Chatra('hide') 11 | } 12 | 13 | const open = () => { 14 | Chatra('openChat', true) 15 | Chatra('show') 16 | } 17 | 18 | window.ChatraSetup = { startHidden: true } 19 | 20 | window.chatBtnFn = () => { 21 | document.getElementById('chatra').classList.contains('chatra--expanded') ? close() : open() 22 | } 23 | } else if (isChatHideShow) { 24 | window.chatBtn = { 25 | hide: () => Chatra('hide'), 26 | show: () => Chatra('show') 27 | } 28 | } 29 | 30 | (function(d, w, c) { 31 | w.ChatraID = '#{theme.chatra.id}' 32 | var s = d.createElement('script') 33 | w[c] = w[c] || function() { 34 | (w[c].q = w[c].q || []).push(arguments) 35 | } 36 | s.async = true 37 | s.src = 'https://call.chatra.io/chatra.js' 38 | if (d.head) d.head.appendChild(s) 39 | })(document, window, 'Chatra') 40 | })() 41 | 42 | 43 | -------------------------------------------------------------------------------- /source/css/_page/common.styl: -------------------------------------------------------------------------------- 1 | #body-wrap 2 | display: flex 3 | flex-direction: column 4 | min-height: 100vh 5 | 6 | .layout 7 | display: flex 8 | flex: 1 auto 9 | margin: 0 auto 10 | padding: 40px 15px 11 | max-width: 1200px 12 | width: 100% 13 | 14 | +maxWidth900() 15 | flex-direction: column 16 | 17 | +maxWidth768() 18 | padding: 20px 5px 19 | 20 | +minWidth2000() 21 | max-width: 70% 22 | 23 | & > div:first-child:not(.nc) > div 24 | @extend .cardHover 25 | align-self: flex-start 26 | padding: 50px 40px 27 | 28 | +maxWidth768() 29 | padding: 36px 14px 30 | 31 | & > div:first-child 32 | width: 74% 33 | transition: all .3s 34 | 35 | +maxWidth900() 36 | width: 100% !important 37 | 38 | if hexo-config('aside.position') == 'left' 39 | +minWidth900() 40 | order: 2 41 | 42 | // 隱藏aside 43 | &.hide-aside 44 | max-width: 1000px 45 | 46 | +minWidth2000() 47 | max-width: 1300px 48 | 49 | & > div 50 | width: 100% !important 51 | 52 | // for apple device 53 | .apple 54 | #page-header.full_page 55 | background-attachment: scroll !important 56 | 57 | .recent-post-item, 58 | .avatar-img, 59 | .flink-item-icon 60 | transform: translateZ(0) 61 | -------------------------------------------------------------------------------- /scripts/tag/plugins/reference.js: -------------------------------------------------------------------------------- 1 | /* 2 | {% referto 'id','literature' %} 3 | {% referfrom 'id','literature','url' %} 4 | */ 5 | 'use strict' 6 | const urlFor = require('hexo-util').url_for.bind(hexo) 7 | 8 | function referto (args) { 9 | args = args.join(' ').split(',') 10 | let referid = args[0] 11 | let literature = args[1] 12 | return `${referid}${literature}参考资料`; 13 | } 14 | 15 | 16 | function referfrom (args) { 17 | args = args.join(' ').split(',') 18 | let fromid = args[0] 19 | let fromliterature = args[1] 20 | let referurl = args[2] ? urlFor(args[2]) : 'javascript:void' 21 | return ``; 22 | 23 | } 24 | 25 | hexo.extend.tag.register('referto',referto); 26 | hexo.extend.tag.register('referfrom',referfrom); 27 | -------------------------------------------------------------------------------- /scripts/tag/plugins/folding.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function postFolding(args, content) { 4 | args = args.join(' ').split(','); 5 | let style = '' 6 | let title = '' 7 | if (args.length > 1) { 8 | style = args[0].trim() 9 | title = args[1].trim() 10 | } else if (args.length > 0) { 11 | title = args[0].trim() 12 | } 13 | if (style != undefined) { 14 | return `
${title} 15 |
16 | ${hexo.render.renderSync({text: content, engine: 'markdown'}).split('\n').join('')} 17 | 18 |
19 |
`; 20 | } else { 21 | return `
${title} 22 |
23 | ${hexo.render.renderSync({text: content, engine: 'markdown'}).split('\n').join('')} 24 | 25 |
26 |
`; 27 | } 28 | 29 | } 30 | 31 | hexo.extend.tag.register('folding', postFolding, {ends: true}); 32 | -------------------------------------------------------------------------------- /layout/page.pug: -------------------------------------------------------------------------------- 1 | extends includes/layout.pug 2 | 3 | block content 4 | - const noCardLayout = ['shuoshuo', '404'].includes(page.type) ? 'nc' : '' 5 | - var commentsJsLoad = false 6 | 7 | mixin commentLoad 8 | if page.comments !== false && theme.comments.use 9 | - commentsJsLoad = true 10 | !=partial('includes/third-party/comments/index', {}, {cache: true}) 11 | 12 | div 13 | #page(class=noCardLayout) 14 | if top_img === false && page.title 15 | .page-title= page.title 16 | 17 | case page.type 18 | when 'tags' 19 | include includes/page/tags.pug 20 | +commentLoad 21 | when 'link' 22 | include includes/page/flink.pug 23 | +commentLoad 24 | when 'categories' 25 | include includes/page/categories.pug 26 | +commentLoad 27 | when '404' 28 | include includes/page/404.pug 29 | when 'shuoshuo' 30 | include includes/page/shuoshuo.pug 31 | when 'notice' 32 | include includes/page/notice.pug 33 | when 'stars' 34 | include includes/page/stars.pug 35 | when 'about' 36 | include includes/page/about.pug 37 | default 38 | include includes/page/default-page.pug 39 | +commentLoad -------------------------------------------------------------------------------- /source/css/_tags/plugins/inline-labels.styl: -------------------------------------------------------------------------------- 1 | s,del 2 | color: mix(#444, #fff, 60) 3 | text-decoration-color: @color 4 | u 5 | color: #444 6 | text-decoration: none 7 | border-bottom: 1px solid #FE5F58 8 | emp 9 | color: #444 10 | border-bottom: 4px dotted #FE5F58 11 | wavy 12 | color: #444 13 | text-decoration-style: wavy 14 | text-decoration-line: underline 15 | text-decoration-color: #FE5F58 16 | psw 17 | color: transparent 18 | background: mix(#444, #fff, 50) 19 | border-radius: 2px 20 | trans() 21 | &:hover 22 | color: #444 23 | background: none 24 | kbd 25 | display inline-block 26 | color #666 27 | font bold 9pt arial 28 | text-decoration none 29 | text-align center 30 | padding 2px 5px 31 | margin 0 5px 32 | background #eff0f2 33 | -moz-border-radius 4px 34 | border-radius 4px 35 | border-top 1px solid #f5f5f5 36 | -webkit-box-shadow inset 0 0 20px #e8e8e8, 0 1px 0 #c3c3c3, 0 1px 0 #c9c9c9, 37 | 0 1px 2px #333 38 | -moz-box-shadow inset 0 0 20px #e8e8e8, 0 1px 0 #c3c3c3, 0 1px 0 #c9c9c9, 39 | 0 1px 2px #333 40 | -webkit-box-shadow inset 0 0 20px #e8e8e8, 0 1px 0 #c3c3c3, 0 1px 0 #c9c9c9, 41 | 0 1px 2px #333 42 | box-shadow inset 0 0 20px #e8e8e8, 0 1px 0 #c3c3c3, 0 1px 0 #c9c9c9, 43 | 0 1px 2px #333 44 | text-shadow 0 1px 0 #f5f5f5 45 | -------------------------------------------------------------------------------- /layout/includes/third-party/newest-comments/waline.pug: -------------------------------------------------------------------------------- 1 | - const serverURL = theme.waline.serverURL.replace(/\/$/, '') 2 | 3 | != partial("includes/third-party/newest-comments/common.pug", {}, { cache: true }) 4 | 5 | script. 6 | window.addEventListener('load', () => { 7 | const keyName = 'waline-newest-comments' 8 | const { changeContent, generateHtml, run } = window.newestComments 9 | 10 | const getComment = async (ele) => { 11 | try { 12 | const res = await fetch('!{serverURL}/api/comment?type=recent&count=!{theme.aside.card_newest_comments.limit}', { method: 'GET' }) 13 | const result = await res.json() 14 | const walineArray = result.data.map(e => { 15 | return { 16 | 'content': changeContent(e.comment), 17 | 'avatar': e.avatar, 18 | 'nick': e.nick, 19 | 'url': e.url + '#' + e.objectId, 20 | 'date': e.time || e.insertedAt 21 | } 22 | }) 23 | btf.saveToLocal.set(keyName, JSON.stringify(walineArray), !{theme.aside.card_newest_comments.storage}/(60*24)) 24 | generateHtml(walineArray, ele) 25 | } catch (err) { 26 | console.error(err) 27 | ele.textContent= "!{_p('aside.card_newest_comments.error')}" 28 | } 29 | } 30 | 31 | run(keyName, getComment) 32 | }) 33 | -------------------------------------------------------------------------------- /layout/includes/third-party/chat/tidio.pug: -------------------------------------------------------------------------------- 1 | script(src=`//code.tidio.co/${theme.tidio.public_key}.js` async) 2 | script. 3 | (() => { 4 | const isChatBtn = !{theme.chat.rightside_button} 5 | const isChatHideShow = !{theme.chat.button_hide_show} 6 | 7 | if (isChatBtn) { 8 | let isShow = false 9 | const close = () => { 10 | window.tidioChatApi.hide() 11 | isShow = false 12 | } 13 | 14 | const open = () => { 15 | window.tidioChatApi.open() 16 | window.tidioChatApi.show() 17 | isShow = true 18 | } 19 | 20 | const onTidioChatApiReady = () => { 21 | window.tidioChatApi.hide() 22 | window.tidioChatApi.on("close", close) 23 | } 24 | if (window.tidioChatApi) { 25 | window.tidioChatApi.on("ready", onTidioChatApiReady) 26 | } else { 27 | document.addEventListener("tidioChat-ready", onTidioChatApiReady) 28 | } 29 | 30 | window.chatBtnFn = () => { 31 | if (!window.tidioChatApi) return 32 | isShow ? close() : open() 33 | } 34 | } else if (isChatHideShow) { 35 | window.chatBtn = { 36 | hide: () => window.tidioChatApi && window.tidioChatApi.hide(), 37 | show: () => window.tidioChatApi && window.tidioChatApi.show() 38 | } 39 | } 40 | })() 41 | 42 | -------------------------------------------------------------------------------- /scripts/tag/flink.js: -------------------------------------------------------------------------------- 1 | /** 2 | * flink 3 | */ 4 | 5 | 'use strict' 6 | 7 | const urlFor = require('hexo-util').url_for.bind(hexo) 8 | 9 | const flinkFn = (args, content) => { 10 | const data = hexo.render.renderSync({ text: content, engine: 'yaml' }) 11 | let result = '' 12 | 13 | data.forEach(item => { 14 | const className = item.class_name ? `` : '' 15 | const classDesc = item.class_desc ? `` : '' 16 | 17 | const listResult = item.link_list.map(link => ` 18 | `).join('') 27 | 28 | result += `${className}${classDesc}` 29 | }) 30 | 31 | return `` 32 | } 33 | 34 | hexo.extend.tag.register('flink', flinkFn, { ends: true }) 35 | -------------------------------------------------------------------------------- /scripts/tag/plugins/link.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // {% link title, url %} 4 | // {% link title, url, img %} 5 | hexo.extend.tag.register('link', function(args) { 6 | var configtemp = hexo.config.tag_plugins || hexo.theme.config.tag_plugins 7 | args = args.join(' ').split(',') 8 | let text = '' 9 | let url = '' 10 | let img = '' 11 | if (args.length < 2) { 12 | return 13 | } else if (args.length == 2) { 14 | text = args[0].trim() 15 | url = args[1].trim() 16 | } else if (args.length == 3) { 17 | text = args[0].trim() 18 | url = args[1].trim() 19 | img = args[2].trim() 20 | } 21 | let result = ''; 22 | // 发现如果不套一层 div 在其它可渲染 md 的容器中容易被分解 23 | result += ''; 31 | 32 | return result; 33 | }); 34 | 35 | hexo.extend.tag.register('linkgroup', function(args, content) { 36 | let ret = ''; 37 | ret += ''; 40 | return ret; 41 | }, {ends: true}); 42 | -------------------------------------------------------------------------------- /scripts/tag/plugins/issues.js: -------------------------------------------------------------------------------- 1 | /** 2 | * issues.js | https://github.com/volantis-x/hexo-theme-volantis 3 | */ 4 | 5 | 'use strict'; 6 | 7 | // 从 issues 加载动态数据 8 | // {% issues sites/timeline/friends | api=xxx | group=key:a,b,c %} 9 | // 例如: 10 | // {% issues sites | api=https://api.github.com/repos/volantis-x/examples/issues?sort=updated&state=open&page=1&per_page=100 | group=version:latest,v6,v5,v4,v3,v2,v1,v0 %} 11 | hexo.extend.tag.register('issues', function(args) { 12 | args = args.join(' ').split(' | '); 13 | // 所有支持的参数 14 | let type = args[0].trim(); 15 | let api = ''; 16 | let group = ''; 17 | // 解析 18 | if (args.length > 1) { 19 | for (let i = 1; i < args.length; i++) { 20 | let tmp = args[i].trim(); 21 | if (tmp.includes('type=')) { 22 | type = tmp.substring(5, tmp.length); 23 | } else if (tmp.includes('api=')) { 24 | api = tmp.substring(4, tmp.length); 25 | } else if (tmp.includes('group=')) { 26 | group = tmp.substring(6, tmp.length); 27 | } 28 | } 29 | } 30 | if (type.length == 0 || api.length == 0) { 31 | return; 32 | } 33 | // 布局 34 | let ret = '
0) { 37 | ret += 'group="' + group + '"'; 38 | } 39 | ret += '>
'; 40 | return ret; 41 | }); 42 | -------------------------------------------------------------------------------- /layout/includes/third-party/newest-comments/disqus-comment.pug: -------------------------------------------------------------------------------- 1 | != partial("includes/third-party/newest-comments/common.pug", {}, { cache: true }) 2 | 3 | script. 4 | window.addEventListener('load', () => { 5 | const keyName = 'disqus-newest-comments' 6 | const { changeContent, generateHtml, run } = window.newestComments 7 | 8 | const getComment = ele => { 9 | fetch('https://disqus.com/api/3.0/forums/listPosts.json?forum=!{forum}&related=thread&limit=!{theme.aside.card_newest_comments.limit}&api_key=!{apiKey}') 10 | .then(response => response.json()) 11 | .then(data => { 12 | const disqusArray = data.response.map(item => { 13 | return { 14 | 'avatar': item.author.avatar.cache, 15 | 'content': changeContent(item.message), 16 | 'nick': item.author.name, 17 | 'url': item.url, 18 | 'date': item.createdAt 19 | } 20 | }) 21 | 22 | btf.saveToLocal.set(keyName, JSON.stringify(disqusArray), !{theme.aside.card_newest_comments.storage}/(60*24)) 23 | generateHtml(disqusArray, ele) 24 | }).catch(e => { 25 | console.error(e) 26 | ele.textContent= "!{_p('aside.card_newest_comments.error')}" 27 | }) 28 | } 29 | 30 | run(keyName, getComment) 31 | }) 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /source/css/_tags/plugins/bubble.styl: -------------------------------------------------------------------------------- 1 | :root 2 | --bubble-text-color: #e9a218 3 | --bubble-text-hover-color: #2C7FE7 4 | --bubble-text-shadow: rgba(35,35,35,0.5) 5 | [data-theme="dark"] 6 | --bubble-text-color: #f2b94b 7 | --bubble-text-hover-color: #2C7FE7 8 | --bubble-text-shadow: rgba(35,35,35,0.5) 9 | 10 | 11 | .bubble-content 12 | display inline-block 13 | color var(--bubble-text-color) 14 | font-weight bold 15 | transition: all 0.2s ease-in-out 16 | text-shadow: var(--bubble-text-shadow) 17 | &:hover 18 | transition: all 0.2s ease-in-out 19 | color: var(--bubble-text-hover-color) 20 | 21 | & + .bubble-notation 22 | .bubble-item 23 | transform translate(-40px, 10px) rotateX(0deg) 24 | transition: all 0.5s ease-in-out 25 | opacity: 1 26 | 27 | .bubble-notation 28 | display: inline-block 29 | 30 | .bubble-item 31 | transition: all 0.5s ease-in-out 32 | opacity 0 33 | color white 34 | z-index: 99 35 | display flex 36 | position absolute 37 | transform translate(-40px, 10px) rotateX(90deg) 38 | width auto 39 | height auto 40 | max-width 400px 41 | overflow hidden 42 | padding 20px 10px 10px 10px 43 | clip-path polygon(5px 10px,20px 10px,30px 0,40px 10px,calc(100% - 5px) 10px,100% 15px,100% calc(100% - 5px),calc(100% - 5px) 100%,5px 100%,0 calc(100% - 5px),0 15px,5px 10px) 44 | -------------------------------------------------------------------------------- /scripts/tag/plugins/ghcard.js: -------------------------------------------------------------------------------- 1 | /** 2 | * https://github.com/anuraghazra/github-readme-stats 3 | */ 4 | 5 | 'use strict'; 6 | 7 | // {% ghcard volantis-x %} 8 | // {% ghcard volantis-x/hexo-theme-volantis %} 9 | hexo.extend.tag.register('ghcard', function(args) { 10 | args = args.join(' ').split(', '); 11 | let path = args[0].trim(); 12 | let card = ''; 13 | card += ''; 14 | let url = ''; 15 | if (path.includes('/')) { 16 | // is repo 17 | let ps = path.split('/'); 18 | url += 'https://github-readme-stats.eurkon.com/api/pin/?username=' + ps[0] + "&repo=" + ps[1]; 19 | } else { 20 | // is user 21 | url += 'https://github-readme-stats.eurkon.com/api/?username=' + path; 22 | } 23 | if (args.length > 1) { 24 | for (let i = 1; i < args.length; i++) { 25 | let tmp = args[i].trim(); 26 | url += "&" + tmp; 27 | } 28 | } 29 | if (!url.includes('&show_owner=')) { 30 | url += '&show_owner=true'; 31 | } 32 | card += ''; 33 | card += ''; 34 | return card; 35 | }); 36 | 37 | hexo.extend.tag.register('ghcardgroup', function(args, content) { 38 | let ret = ''; 39 | // wrap 40 | ret += '
'; 41 | ret += content; 42 | ret += '
'; 43 | return ret; 44 | }, {ends: true}); 45 | -------------------------------------------------------------------------------- /scripts/filters/random_cover.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Butterfly 3 | * ramdom cover 4 | */ 5 | 6 | 'use strict' 7 | 8 | hexo.extend.filter.register('before_post_render', data => { 9 | const imgTestReg = /\.(png|jpe?g|gif|svg|webp)(\?.*)?$/i 10 | let { cover: coverVal, top_img: topImg } = data 11 | 12 | // Add path to top_img and cover if post_asset_folder is enabled 13 | if (hexo.config.post_asset_folder) { 14 | if (topImg && topImg.indexOf('/') === -1 && imgTestReg.test(topImg)) data.top_img = `${data.path}${topImg}` 15 | if (coverVal && coverVal.indexOf('/') === -1 && imgTestReg.test(coverVal)) data.cover = `${data.path}${coverVal}` 16 | } 17 | 18 | const randomCoverFn = () => { 19 | const { cover: { default_cover: defaultCover } } = hexo.theme.config 20 | if (!defaultCover) return false 21 | if (!Array.isArray(defaultCover)) return defaultCover 22 | const num = Math.floor(Math.random() * defaultCover.length) 23 | return defaultCover[num] 24 | } 25 | 26 | if (coverVal === false) return data 27 | 28 | // If cover is not set, use random cover 29 | if (!coverVal) { 30 | const randomCover = randomCoverFn() 31 | data.cover = randomCover 32 | coverVal = randomCover // update coverVal 33 | } 34 | 35 | if (coverVal && (coverVal.indexOf('//') !== -1 || imgTestReg.test(coverVal))) { 36 | data.cover_type = 'img' 37 | } 38 | 39 | return data 40 | }) 41 | -------------------------------------------------------------------------------- /layout/includes/loading/spinner-loading.pug: -------------------------------------------------------------------------------- 1 | #loading-box 2 | .loading-left-bg 3 | .loading-right-bg 4 | i.fa.fas.fa-spinner.fa-spin.load-spinner 5 | 6 | script. 7 | const preloader = { 8 | endLoading: () => { 9 | document.body.style.overflow = 'auto'; 10 | document.getElementById('loading-box').style.transition = 'opacity 1s'; 11 | document.getElementById('loading-box').style.opacity = '0'; 12 | setTimeout(function () { document.getElementById('loading-box').classList.add("loaded") }, 1000) 13 | document.body.classList.remove("hidden-y") 14 | }, 15 | initLoading: () => { 16 | document.body.style.overflow = ''; 17 | document.getElementById('loading-box').style.transition = ''; 18 | document.getElementById('loading-box').style.opacity = '1'; 19 | document.getElementById('loading-box').classList.remove("loaded") 20 | document.body.classList.add("hidden-y") 21 | } 22 | } 23 | window.addEventListener('load',()=> { preloader.endLoading() }) 24 | 25 | if (!{theme.pjax && theme.pjax.enable}) { 26 | document.addEventListener('pjax:send', () => { preloader.initLoading() }) 27 | document.addEventListener('pjax:complete', () => { preloader.endLoading() }) 28 | } 29 | 30 | setTimeout(function () { preloader.endLoading() }, 3000) 31 | document.getElementById('loading-box').addEventListener('click', () => { preloader.endLoading() }) -------------------------------------------------------------------------------- /source/css/_tags/plugins/media.styl: -------------------------------------------------------------------------------- 1 | trans($time = 0.28s) 2 | transition: all $time ease 3 | -moz-transition: all $time ease 4 | -webkit-transition: all $time ease 5 | -o-transition: all $time ease 6 | 7 | audio,video 8 | border-radius: 4px 9 | max-width: 100% 10 | video 11 | z-index: 1 12 | trans() 13 | &:hover 14 | box-shadow: 0 4px 8px 0px rgba(0, 0, 0, 0.24), 0 8px 16px 0px rgba(0, 0, 0, 0.24) 15 | 16 | div.video 17 | line-height: 0 18 | text-align: center 19 | 20 | div.videos 21 | max-width: "calc(100% + 2 * %s)" % 4px 22 | display: flex 23 | flex-wrap: wrap 24 | justify-content: flex-start 25 | align-items: flex-end 26 | margin: 1em 0 - 4px 27 | .video,iframe 28 | width: 100% 29 | margin: 4px 30 | 31 | iframe 32 | border-radius: 4px 33 | width: 100% 34 | min-height: 300px 35 | &.left 36 | justify-content: flex-start 37 | &.center 38 | justify-content: center 39 | &.right 40 | justify-content: flex-end 41 | &.stretch 42 | align-items: stretch 43 | &[col='1'] 44 | .video,iframe 45 | width: 100% 46 | &[col='2'] 47 | .video,iframe 48 | width: "calc(50% - 2 * %s)" % 4px 49 | &[col='3'] 50 | .video,iframe 51 | width: "calc(33.33% - 2 * %s)" % 4px 52 | &[col='4'] 53 | .video,iframe 54 | width: "calc(25% - 2 * %s)" % 4px 55 | [data-theme="dark"] 56 | audio,video 57 | filter brightness(0.7) 58 | -------------------------------------------------------------------------------- /source/css/_search/local-search.styl: -------------------------------------------------------------------------------- 1 | #local-search 2 | .search-dialog 3 | .local-search-box 4 | margin: 0 auto 5 | max-width: 100% 6 | width: 100% 7 | 8 | input 9 | padding: 5px 14px 10 | width: 100% 11 | outline: none 12 | border: 2px solid $search-color 13 | border-radius: 40px 14 | background: var(--search-bg) 15 | color: var(--search-input-color) 16 | -webkit-appearance: none 17 | 18 | .search-wrap 19 | display: none 20 | 21 | .local-search-hit-item 22 | margin-left: 24px 23 | padding-left: 3px 24 | line-height: 1.8 25 | 26 | &::marker 27 | color: $search-color 28 | font-weight: bold 29 | font-style: italic 30 | 31 | a 32 | color: var(--search-a-color) 33 | 34 | &:hover 35 | color: $search-color 36 | 37 | .search-result-title 38 | font-weight: 600 39 | 40 | .search-result 41 | margin: 0 0 8px 42 | word-break: break-all 43 | font-size: .9em 44 | 45 | .search-result-list 46 | overflow-y: overlay 47 | margin: 0 -20px 48 | padding: 0 22px 49 | max-height: calc(80vh - 180px) 50 | 51 | +maxWidth768() 52 | max-height: calc(var(--search-height) - 190px) !important 53 | 54 | .search-keyword 55 | background: transparent 56 | color: $search-keyword-highlight 57 | font-weight: 600 -------------------------------------------------------------------------------- /source/css/_tags/button.styl: -------------------------------------------------------------------------------- 1 | #article-container 2 | .btn-center 3 | margin: 0 0 20px 4 | text-align: center 5 | 6 | .btn-beautify 7 | display: inline-block 8 | margin: 0 4px 6px 9 | padding: 0 15px 10 | background-color: var(--btn-beautify-color, $btn-default-color) 11 | color: $btn-color 12 | line-height: 2 13 | addBorderRadius() 14 | 15 | for $type in $color-types 16 | &.{$type} 17 | --btn-beautify-color: lookup('$tagsP-' + $type + '-color') 18 | 19 | &:hover 20 | background-color: var(--btn-hover-color) 21 | 22 | i + span 23 | margin-left: 6px 24 | 25 | &:not(.block) + .btn-beautify:not(.block) 26 | margin: 0 4px 20px 27 | 28 | &.block 29 | display: block 30 | margin: 0 0 20px 31 | width: fit-content 32 | width: -moz-fit-content 33 | 34 | &.center 35 | margin: 0 auto 20px 36 | 37 | &.right 38 | margin: 0 0 20px auto 39 | 40 | &.larger 41 | padding: 6px 15px 42 | 43 | &:hover 44 | text-decoration: none 45 | 46 | &.outline 47 | border: 1px solid transparent 48 | border-color: var(--btn-beautify-color, $btn-default-color) 49 | background-color: transparent 50 | color: var(--btn-beautify-color, $btn-default-color) 51 | 52 | &:hover 53 | background-color: var(--btn-beautify-color, $btn-default-color) 54 | 55 | &:hover 56 | color: white !important 57 | -------------------------------------------------------------------------------- /layout/includes/third-party/comments/gitalk.pug: -------------------------------------------------------------------------------- 1 | - const { client_id, client_secret, repo, owner, admin, option } = theme.gitalk 2 | 3 | script. 4 | (() => { 5 | const initGitalk = () => { 6 | const gitalk = new Gitalk(Object.assign({ 7 | clientID: '!{client_id}', 8 | clientSecret: '!{client_secret}', 9 | repo: '!{repo}', 10 | owner: '!{owner}', 11 | admin: ['!{admin}'], 12 | id: '!{md5(page.path)}', 13 | updateCountCallback: commentCount 14 | },!{JSON.stringify(option)})) 15 | 16 | gitalk.render('gitalk-container') 17 | } 18 | 19 | const loadGitalk = async() => { 20 | if (typeof Gitalk === 'function') initGitalk() 21 | else { 22 | await btf.getCSS('!{url_for(theme.asset.gitalk_css)}') 23 | await btf.getScript('!{url_for(theme.asset.gitalk)}') 24 | initGitalk() 25 | } 26 | } 27 | 28 | const commentCount = n => { 29 | const isCommentCount = document.querySelector('#post-meta .gitalk-comment-count') 30 | if (isCommentCount) { 31 | isCommentCount.textContent= n 32 | } 33 | } 34 | 35 | if ('!{theme.comments.use[0]}' === 'Gitalk' || !!{theme.comments.lazyload}) { 36 | if (!{theme.comments.lazyload}) btf.loadComment(document.getElementById('gitalk-container'), loadGitalk) 37 | else loadGitalk() 38 | } else { 39 | window.loadOtherComment = loadGitalk 40 | } 41 | })() 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /scripts/tag/timeline.js: -------------------------------------------------------------------------------- 1 | /** 2 | * timeline 3 | * by Jerry 4 | */ 5 | 6 | 'use strict' 7 | 8 | const timeLineFn = (args, content) => { 9 | const tlBlock = /\n([\w\W\s\S]*?)/g 10 | 11 | let result = '' 12 | let color = '' 13 | let text = '' 14 | if (args.length) { 15 | [text, color] = args.join(' ').split(',') 16 | const mdContent = hexo.render.renderSync({ text, engine: 'markdown' }) 17 | result += `
${mdContent}
` 18 | } 19 | 20 | const matches = [] 21 | let match 22 | 23 | while ((match = tlBlock.exec(content)) !== null) { 24 | matches.push(match[1]) 25 | matches.push(match[2]) 26 | } 27 | 28 | for (let i = 0; i < matches.length; i += 2) { 29 | const tlChildTitle = hexo.render.renderSync({ text: matches[i], engine: 'markdown' }) 30 | const tlChildContent = hexo.render.renderSync({ text: matches[i + 1], engine: 'markdown' }) 31 | 32 | const tlTitleHtml = `
${tlChildTitle}
` 33 | const tlContentHtml = `
${tlChildContent}
` 34 | 35 | result += `
${tlTitleHtml + tlContentHtml}
` 36 | } 37 | 38 | return `
${result}
` 39 | } 40 | 41 | hexo.extend.tag.register('timeline', timeLineFn, { ends: true }) 42 | -------------------------------------------------------------------------------- /layout/includes/head/preconnect.pug: -------------------------------------------------------------------------------- 1 | - 2 | const { internal_provider, third_party_provider, custom_format } = theme.CDN 3 | const providers = { 4 | 'jsdelivr': '//cdn.jsdelivr.net', 5 | 'cdnjs': '//cdnjs.cloudflare.com', 6 | 'unpkg': '//unpkg.com', 7 | 'custom': custom_format && custom_format.match(/^((https?:)?(\/\/[^/]+)|([^/]+))(\/|$)/)[1] 8 | } 9 | - 10 | 11 | if internal_provider === third_party_provider && internal_provider !== 'local' 12 | link(rel="preconnect" href=providers[internal_provider]) 13 | else 14 | if internal_provider !== 'local' 15 | link(rel="preconnect" href=providers[internal_provider]) 16 | if third_party_provider !== 'local' 17 | link(rel="preconnect" href=providers[third_party_provider]) 18 | 19 | if theme.google_analytics 20 | link(rel="preconnect" href="//www.google-analytics.com" crossorigin='') 21 | 22 | if theme.baidu_analytics 23 | link(rel="preconnect" href="//hm.baidu.com") 24 | 25 | if theme.cloudflare_analytics 26 | link(rel="preconnect" href="//static.cloudflareinsights.com") 27 | 28 | if theme.microsoft_clarity 29 | link(rel="preconnect" href="//www.clarity.ms") 30 | 31 | if theme.blog_title_font && theme.blog_title_font.font_link && theme.blog_title_font.font_link.indexOf('//fonts.googleapis.com') != -1 32 | link(rel="preconnect" href="//fonts.googleapis.com" crossorigin='') 33 | 34 | if !theme.asset.busuanzi && (theme.busuanzi.site_uv || theme.busuanzi.site_pv || theme.busuanzi.page_pv) 35 | link(rel="preconnect" href="//busuanzi.ibruce.info") -------------------------------------------------------------------------------- /source/css/_tags/plugins/progress.styl: -------------------------------------------------------------------------------- 1 | .progress 2 | display flex 3 | font-size var(--global-font-size) 4 | background-color rgba(88,88,88,0.6) 5 | border-radius .25rem 6 | margin 1rem 0 7 | height 2rem 8 | overflow hidden 9 | p 10 | margin 0 0 0 10px !important 11 | 12 | .progress-bar-animated 13 | background-color #a7b5fd !important 14 | animation progress-bar-stripes 1s linear infinite 15 | 16 | .progress-bar-striped 17 | background-image linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent) 18 | background-size 1rem 1rem 19 | 20 | .progress-bar 21 | display flex 22 | flex-direction column 23 | justify-content center 24 | overflow visible 25 | color #fff 26 | text-align center 27 | white-space nowrap 28 | background-color #0d6efd 29 | transition width .6s ease 30 | 31 | @media (prefers-reduced-motion:reduce) 32 | .progress-bar 33 | transition none 34 | 35 | .bg-green 36 | background-color #28a745 !important 37 | 38 | .bg-yellow 39 | background-color #ffc107 !important 40 | 41 | .bg-red 42 | background-color #dc3545 !important 43 | 44 | .bg-cyan 45 | background-color #17a2b8 !important 46 | 47 | .bg-blue 48 | background-color #0d6efd !important 49 | 50 | .bg-gray 51 | background-color #7f838a !important 52 | 53 | @keyframes progress-bar-stripes 54 | 0% 55 | background-position-x 1rem 56 | -------------------------------------------------------------------------------- /layout/includes/layout.pug: -------------------------------------------------------------------------------- 1 | - var htmlClassHideAside = theme.aside.enable && theme.aside.hide ? 'hide-aside' : '' 2 | - page.aside = is_archive() ? theme.aside.display.archive: is_category() ? theme.aside.display.category : is_tag() ? theme.aside.display.tag : page.aside 3 | - var hideAside = !theme.aside.enable || page.aside === false ? 'hide-aside' : '' 4 | - var pageType = is_post() ? 'post' : 'page' 5 | - pageType = page.type ? pageType + ' type-' + page.type : pageType 6 | 7 | doctype html 8 | html(lang=config.language data-theme=theme.display_mode class=htmlClassHideAside) 9 | head 10 | include ./head.pug 11 | body 12 | !=partial('includes/loading/index', {}, {cache: true}) 13 | 14 | if theme.background 15 | #web_bg(style=getBgPath(theme.background)) 16 | 17 | !=partial('includes/sidebar', {}, {cache: true}) 18 | 19 | #body-wrap(class=pageType) 20 | include ./header/index.pug 21 | include ./home.pug 22 | main#content-inner.layout(class=hideAside) 23 | if body 24 | div!= body 25 | else 26 | block content 27 | if theme.aside.enable && page.aside !== false 28 | include widget/index.pug 29 | 30 | - const footerBg = theme.footer_img 31 | - const footer_bg = footerBg ? footerBg === true ? bg_img : getBgPath(footerBg) : '' 32 | footer#footer(style=footer_bg) 33 | !=partial('includes/footer', {}, {cache: true}) 34 | 35 | include ./rightside.pug 36 | include ./rightmenu.pug 37 | include ./additional-js.pug -------------------------------------------------------------------------------- /source/js/eurkon/refresh.js: -------------------------------------------------------------------------------- 1 | eurkon.switchThemeColor(eurkon.getMainColor()) 2 | eurkon.catalogActive() 3 | eurkon.postAddToc() 4 | eurkon.footerRandomFlink(flinks, 3) 5 | eurkon.listenToPageInputPress() 6 | 7 | // 顶部菜单栏 评论按钮 8 | if (document.getElementById('comment-button')) document.getElementById('comment-button').style.display = document.getElementById('post-comment') ? 'block' : 'none' 9 | if (document.getElementById('barrage-btn')) document.getElementById('barrage-btn').style.display = document.getElementById('post-comment') ? 'block' : 'none' 10 | 11 | document.addEventListener('keydown', function (event) { 12 | if (event.keyCode == 27) { // ESC 13 | try { 14 | document.getElementById('rightside')?.classList.add('hidden') 15 | document.getElementById('rightside-mask')?.classList.add('hidden') 16 | document.getElementById('fc-overlay')?.classList.add('hidden') 17 | document.getElementById('fc-overshow')?.classList.add('hidden') 18 | // eurkon.exitFullScreen() 19 | } 20 | catch (error) { 21 | console.log(error) 22 | } 23 | } 24 | }); 25 | 26 | // 页面滚动百分比 27 | // window.addEventListener('scroll', function () { 28 | // let totalH = document.body.scrollHeight || document.documentElement.scrollHeight // 页面总高 29 | // let clientH = window.innerHeight || document.documentElement.clientHeight // 可视高 30 | // document.querySelector('#nav #hotkey #top-button a.site-page i').dataset.percent = ((document.body.scrollTop || document.documentElement.scrollTop) / (totalH - clientH) * 100).toFixed(0) 31 | // }) 32 | 33 | -------------------------------------------------------------------------------- /source/css/_page/404.styl: -------------------------------------------------------------------------------- 1 | if hexo-config('error_404.enable') 2 | .type-404 3 | .error-content 4 | @extend .cardHover 5 | overflow: hidden 6 | margin: 0 20px 7 | height: 360px 8 | 9 | +maxWidth768() 10 | margin: 0 11 | height: 500px 12 | 13 | .error-img 14 | display: inline-block 15 | overflow: hidden 16 | width: 50% 17 | height: 100% 18 | 19 | +maxWidth768() 20 | width: 100% 21 | height: 45% 22 | 23 | img 24 | @extend .imgHover 25 | background-color: $theme-color 26 | 27 | .error-info 28 | display: inline-flex 29 | flex-direction: column 30 | justify-content: center 31 | align-content: center 32 | width: 50% 33 | height: 100% 34 | vertical-align: top 35 | text-align: center 36 | 37 | if $site-name-font 38 | font-family: $site-name-font 39 | 40 | +maxWidth768() 41 | width: 100% 42 | height: 55% 43 | 44 | .error_title 45 | margin-top: -.6em 46 | font-size: 9em 47 | 48 | +maxWidth768() 49 | font-size: 8em 50 | 51 | .error_subtitle 52 | @extend .limit-more-line 53 | margin-top: -3em 54 | word-break: break-word 55 | font-size: 1.6em 56 | -webkit-line-clamp: 2 57 | 58 | .nc 59 | margin-top: 5% 60 | padding: 0 20px 61 | 62 | #footer 63 | display: none 64 | 65 | & + #rightside 66 | display: none -------------------------------------------------------------------------------- /source/css/_layout/rightside.styl: -------------------------------------------------------------------------------- 1 | #rightside 2 | position: fixed 3 | right: -48px 4 | bottom: $rightside-bottom 5 | z-index: 100 6 | opacity: 0 7 | transition: all .5s 8 | 9 | &.rightside-show 10 | opacity: .8 11 | transform: translate(-58px, 0) 12 | 13 | #rightside-config-hide 14 | height: 0 15 | opacity: 0 16 | transition: transform .4s 17 | transform: translate(45px, 0) 18 | 19 | &.show 20 | height: auto 21 | opacity: 1 22 | transform: translate(0, 0) 23 | 24 | &.status 25 | height: auto 26 | opacity: 1 27 | 28 | & > div 29 | & > button, 30 | & > a 31 | display: block 32 | margin-bottom: 5px 33 | width: w = 35px 34 | height: w 35 | background-color: var(--btn-bg) 36 | color: var(--btn-color) 37 | text-align: center 38 | font-size: 16px 39 | line-height: w 40 | addBorderRadius(5) 41 | 42 | &:hover 43 | background-color: var(--btn-hover-color) 44 | 45 | #mobile-toc-button 46 | display: none 47 | 48 | +maxWidth900() 49 | display: block 50 | 51 | +maxWidth900() 52 | #hide-aside-btn 53 | display: none 54 | 55 | if hexo-config('rightside_scroll_percent') 56 | #go-up 57 | .scroll-percent 58 | display: none 59 | 60 | &.show-percent 61 | .scroll-percent 62 | display: block 63 | 64 | & + i 65 | display: none 66 | 67 | &:hover 68 | .scroll-percent 69 | display: none 70 | 71 | & + i 72 | display: block 73 | -------------------------------------------------------------------------------- /layout/includes/third-party/newest-comments/twikoo-comment.pug: -------------------------------------------------------------------------------- 1 | != partial("includes/third-party/newest-comments/common.pug", {}, { cache: true }) 2 | 3 | script. 4 | window.addEventListener('load', () => { 5 | const keyName = 'twikoo-newest-comments' 6 | const { changeContent, generateHtml, run } = window.newestComments 7 | 8 | const getComment = ele => { 9 | const runTwikoo = () => { 10 | twikoo.getRecentComments({ 11 | envId: '!{theme.twikoo.envId}', 12 | region: '!{theme.twikoo.region}', 13 | pageSize: !{theme.aside.card_newest_comments.limit}, 14 | includeReply: true 15 | }).then(res => { 16 | const twikooArray = res.map(e => { 17 | return { 18 | 'content': changeContent(e.comment), 19 | 'avatar': e.avatar, 20 | 'nick': e.nick, 21 | 'url': e.url + '#' + e.id, 22 | 'date': new Date(e.created).toISOString() 23 | } 24 | }) 25 | 26 | btf.saveToLocal.set(keyName, JSON.stringify(twikooArray), !{theme.aside.card_newest_comments.storage}/(60*24)) 27 | generateHtml(twikooArray, ele) 28 | }).catch(err => { 29 | console.error(err) 30 | ele.textContent= "!{_p('aside.card_newest_comments.error')}" 31 | }) 32 | } 33 | 34 | if (typeof twikoo === 'object') { 35 | runTwikoo() 36 | } else { 37 | btf.getScript('!{url_for(theme.asset.twikoo)}').then(runTwikoo) 38 | } 39 | } 40 | 41 | run(keyName, getComment) 42 | }) 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /layout/includes/page/404.pug: -------------------------------------------------------------------------------- 1 | - var top_img_404 = theme.error_404.background || theme.default_top_img 2 | 3 | #error-wrap 4 | .error-content 5 | .error-img 6 | img(src=url_for(top_img_404) alt='Page not found') 7 | .error-info 8 | h1.error_title= '404' 9 | .error_subtitle= theme.error_404.subtitle || _p('error404') 10 | .aside-list 11 | - let postLimit = theme.aside.card_recent_post.limit === 0 ? site.posts.length : theme.aside.card_recent_post.limit || 5 12 | - let sort = theme.aside.card_recent_post.sort === 'updated' ? 'updated' : 'date' 13 | - site.posts.sort(sort, -1).limit(postLimit).each(function(article){ 14 | - let link = article.link || article.path 15 | - let title = article.title || _p('no_title') 16 | - let no_cover = article.cover === false || !theme.cover.aside_enable ? 'no-cover' : '' 17 | - let post_cover = article.cover 18 | .aside-list-item(class=no_cover) 19 | if post_cover && theme.cover.aside_enable 20 | a.thumbnail(href=url_for(link) title=title) 21 | img(src=url_for(post_cover) onerror=`this.onerror=null;this.src='${url_for(theme.error_img.post_page)}'` alt=title) 22 | .content 23 | a.title(href=url_for(link) title=title)= title 24 | if theme.aside.card_recent_post.sort === 'updated' 25 | time(datetime=date_xml(article.updated) title=_p('post.updated') + ' ' + full_date(article.updated)) #[=date(article.updated, config.date_format)] 26 | else 27 | time(datetime=date_xml(article.date) title=_p('post.created') + ' ' + full_date(article.date)) #[=date(article.date, config.date_format)] 28 | - }) -------------------------------------------------------------------------------- /layout/includes/widget/card_recent_post.pug: -------------------------------------------------------------------------------- 1 | if theme.aside.card_recent_post.enable 2 | .card-widget.card-recent-post 3 | .item-headline 4 | i.fas.fa-history 5 | span= _p('aside.card_recent_post') 6 | .aside-list 7 | - let postLimit = theme.aside.card_recent_post.limit === 0 ? site.posts.length : theme.aside.card_recent_post.limit || 5 8 | - let sort = theme.aside.card_recent_post.sort === 'updated' ? 'updated' : 'date' 9 | - site.posts.sort(sort, -1).limit(postLimit).each(function(article){ 10 | - let link = article.link || article.path 11 | - let title = article.title || _p('no_title') 12 | - let no_cover = article.cover === false || !theme.cover.aside_enable ? 'no-cover' : '' 13 | - let post_cover = article.cover 14 | .aside-list-item(class=no_cover) 15 | if post_cover && theme.cover.aside_enable 16 | a.thumbnail(href=url_for(link) title=title) 17 | if article.cover_type === 'img' 18 | img(src=url_for(post_cover) onerror=`this.onerror=null;this.src='${url_for(theme.error_img.post_page)}'` alt=title) 19 | else 20 | div(style=`background: ${post_cover}`) 21 | .content 22 | a.title(href=url_for(link) title=title)= title 23 | if theme.aside.card_recent_post.sort === 'updated' 24 | time(datetime=date_xml(article.updated) title=_p('post.updated') + ' ' + full_date(article.updated)) #[=date(article.updated, config.date_format)] 25 | else 26 | time(datetime=date_xml(article.date) title=_p('post.created') + ' ' + full_date(article.date)) #[=date(article.date, config.date_format)] 27 | - }) -------------------------------------------------------------------------------- /scripts/helpers/random.js: -------------------------------------------------------------------------------- 1 | hexo.extend.filter.register('after_render:html', function (data) { 2 | try { 3 | const posts = [] 4 | hexo.locals.get('posts').forEach(function (post) { 5 | if (post.random !== false) { 6 | posts.push({ 7 | 'title': post.title, 8 | 'path': '/' + post.path, 9 | 'categories': post.categories.data.map(function (item) { return { 'name': item.name, 'path': '/' + item.path } }), 10 | 'tags': post.tags.data.map(function (item) { return { 'name': item.name, 'path': '/' + item.path } }), 11 | 'cover': post.cover, 12 | // 'description': post.description, 13 | // 'wordCount': post.content.replace(/<(?:.|\s)*?>/g, '').length, 14 | // 'content': strip_html(post.content), 15 | 'date': post.date, 16 | 'updated': post.updated 17 | }) 18 | } 19 | }) 20 | data += `` 21 | } catch (e) { } finally { return data } 22 | }) 23 | 24 | hexo.extend.filter.register('after_render:html', function (data) { 25 | try { 26 | const flinks = [] 27 | hexo.locals.get('data').link.forEach(function (list) { 28 | list.link_list.map(function (flink) { 29 | flinks.push(flink) 30 | }) 31 | }) 32 | data += `` 33 | } catch (e) { } finally { return data } 34 | }) -------------------------------------------------------------------------------- /source/css/_tags/plugins/span.styl: -------------------------------------------------------------------------------- 1 | p.p.subtitle 2 | font-weight: bold 3 | color: mix(#44D7B6, #444, 75) 4 | font-size: 1.25rem !important 5 | padding-top: 1.5rem 6 | &:first-child 7 | padding-top: 1rem 8 | 9 | span.p,p.p 10 | &.left 11 | display: block 12 | text-align: left 13 | &.center 14 | display: block 15 | text-align: center 16 | &.right 17 | display: block 18 | text-align: right 19 | 20 | span.p,p.p 21 | &.small 22 | font-size: var(--global-font-size) 23 | &.large 24 | font-size: 2.5rem 25 | line-height: 1.4 26 | &.huge 27 | font-size: 4rem 28 | line-height: 1.4 29 | &.ultra 30 | font-size: 6rem 31 | line-height: 1.4 32 | &.small,&.large,&.huge,&.ultra 33 | margin: 0 34 | padding: 0 35 | &.bold 36 | font-weight: bold 37 | &.h1,&.h2 38 | padding-bottom: .2rem 39 | font-weight: 500 40 | &.h1 41 | font-size: 1.625rem 42 | color: var(--color-h1) 43 | padding-top: 1em * 2 44 | &.h2 45 | font-size: 1.625rem 46 | color: var(--color-h2) 47 | padding-top: 1em * 2 48 | border-bottom: 1px solid alpha(#444, .1) 49 | &.h3 50 | font-size: 1.375rem 51 | color: var(--color-h3) 52 | padding-top: 1em * 2 53 | &.h4 54 | font-size: 1.125rem 55 | color: var(--color-h4) 56 | padding-top: 1em * 2 57 | &.h5 58 | font-size: 1rem 59 | color: var(--color-h5) 60 | padding-top: 1em * 1.5 61 | 62 | span.p,p.p 63 | &.red 64 | color: #E8453C 65 | &.yellow 66 | color: #FCEC60 67 | &.green 68 | color: #3DC550 69 | &.cyan 70 | color: #1BCDFC 71 | &.blue 72 | color: #2196f3 73 | &.purple 74 | color: #9c27b0 75 | &.gray 76 | color: #999 77 | -------------------------------------------------------------------------------- /scripts/helpers/getArchiveLength.js: -------------------------------------------------------------------------------- 1 | hexo.extend.helper.register('getArchiveLength', function () { 2 | const archiveGenerator = hexo.config.archive_generator 3 | const posts = this.site.posts 4 | 5 | const { yearly, monthly, daily } = archiveGenerator 6 | const { year, month, day } = this.page 7 | 8 | // Archives Page 9 | if (!year) return posts.length 10 | 11 | // Function to generate a unique key based on the granularity 12 | const getKey = (post, type) => { 13 | const date = post.date.clone() 14 | const y = date.year() 15 | const m = date.month() + 1 16 | const d = date.date() 17 | if (type === 'year') return `${y}` 18 | if (type === 'month') return `${y}-${m}` 19 | if (type === 'day') return `${y}-${m}-${d}` 20 | } 21 | 22 | // Create a map to count posts per period 23 | const mapData = this.fragment_cache('createArchiveObj', () => { 24 | const map = new Map() 25 | posts.forEach(post => { 26 | const keyYear = getKey(post, 'year') 27 | const keyMonth = getKey(post, 'month') 28 | const keyDay = getKey(post, 'day') 29 | 30 | if (yearly) map.set(keyYear, (map.get(keyYear) || 0) + 1) 31 | if (monthly) map.set(keyMonth, (map.get(keyMonth) || 0) + 1) 32 | if (daily) map.set(keyDay, (map.get(keyDay) || 0) + 1) 33 | }) 34 | return map 35 | }) 36 | 37 | // Determine the appropriate key to fetch based on current page context 38 | let key 39 | if (yearly && year) key = `${year}` 40 | if (monthly && month) key = `${year}-${month}` 41 | if (daily && day) key = `${year}-${month}-${day}` 42 | 43 | // Return the count for the current period or default to the total posts 44 | return mapData.get(key) || posts.length 45 | }) 46 | -------------------------------------------------------------------------------- /scripts/tag/plugins/checkbox.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function postCheckbox(args) { 4 | args = args.join(' ').split(',') 5 | var cls = '' 6 | var text = '' 7 | var checked = false 8 | if (args.length > 1) { 9 | cls = (args[0] || '').trim() 10 | if (cls.length > 0) { 11 | cls = ' ' + cls 12 | } 13 | if (cls.indexOf('checked') > -1) { 14 | checked = true 15 | } 16 | text = (args[1] || '').trim() 17 | } else if (args.length > 0) { 18 | text = (args[0] || '').trim() 19 | } 20 | if (text.length > 0) { 21 | return `
22 | ${hexo.render.renderSync({text: text, engine: 'markdown'}).split('\n').join('')} 23 |
` 24 | } 25 | } 26 | function postRadio(args) { 27 | args = args.join(' ').split(',') 28 | var cls = '' 29 | var text = '' 30 | var checked = false 31 | if (args.length > 1) { 32 | cls = (args[0] || '').trim() 33 | if (cls.length > 0) { 34 | cls = ' ' + cls 35 | } 36 | if (cls.indexOf('checked') > -1) { 37 | checked = true 38 | } 39 | text = (args[1] || '').trim() 40 | } else if (args.length > 0) { 41 | text = (args[0] || '').trim() 42 | } 43 | if (text.length > 0) { 44 | return `
45 | ${hexo.render.renderSync({text: text, engine: 'markdown'}).split('\n').join('')} 46 |
` 47 | } 48 | } 49 | // {% checkbox text %} 50 | // {% checkbox checked, text %} 51 | // {% checkbox color checked, text %} 52 | hexo.extend.tag.register('checkbox', postCheckbox); 53 | hexo.extend.tag.register('radio', postRadio); 54 | -------------------------------------------------------------------------------- /scripts/tag/chartjs.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Butterfly 3 | * chartjs 4 | * https://www.chartjs.org/ 5 | * {% chartjs [width, abreast, chartId] %} 6 | * 7 | * 8 | * 9 | * 10 | * {% endchartjs %} 11 | */ 12 | 13 | 'use strict' 14 | 15 | const { escapeHTML } = require('hexo-util') 16 | 17 | const chartjs = (args, content) => { 18 | if (!content) return 19 | 20 | const chartRegex = /\n([\w\W\s\S]*?)/ 21 | const descRegex = /\n([\w\W\s\S]*?)/ 22 | const selfConfig = args.join(' ').trim() 23 | 24 | const [width = '', layout = false, chartId = ''] = selfConfig.split(',').map(s => s.trim()) 25 | 26 | const chartMatch = content.match(chartRegex) 27 | const descMatch = content.match(descRegex) 28 | 29 | if (!chartMatch) { 30 | hexo.log.warn('chartjs tag: chart content is required!') 31 | return 32 | } 33 | 34 | const chartConfig = chartMatch && chartMatch[1] ? chartMatch[1] : '' 35 | const descContent = descMatch && descMatch[1] ? descMatch[1] : '' 36 | 37 | const renderedDesc = descContent ? hexo.render.renderSync({ text: descContent, engine: 'markdown' }).trim() : '' 38 | 39 | const descDOM = renderedDesc ? `
${renderedDesc}
` : '' 40 | const abreastClass = layout ? ' chartjs-abreast' : '' 41 | const widthStyle = width ? `data-width="${width}%"` : '' 42 | 43 | return `
44 | 45 | ${descDOM} 46 |
` 47 | } 48 | 49 | hexo.extend.tag.register('chartjs', chartjs, { ends: true }) 50 | -------------------------------------------------------------------------------- /layout/includes/third-party/math/mathjax.pug: -------------------------------------------------------------------------------- 1 | //- Mathjax 3 2 | - const { tags, enableMenu } = theme.math.mathjax 3 | script. 4 | (() => { 5 | const loadMathjax = () => { 6 | if (!window.MathJax) { 7 | window.MathJax = { 8 | tex: { 9 | inlineMath: [['$', '$'], ['\\(', '\\)']], 10 | tags: '!{tags}', 11 | }, 12 | chtml: { 13 | scale: 1.1 14 | }, 15 | options: { 16 | enableMenu: !{enableMenu}, 17 | renderActions: { 18 | findScript: [10, doc => { 19 | for (const node of document.querySelectorAll('script[type^="math/tex"]')) { 20 | const display = !!node.type.match(/; *mode=display/) 21 | const math = new doc.options.MathItem(node.textContent, doc.inputJax[0], display) 22 | const text = document.createTextNode('') 23 | node.parentNode.replaceChild(text, node) 24 | math.start = {node: text, delim: '', n: 0} 25 | math.end = {node: text, delim: '', n: 0} 26 | doc.math.push(math) 27 | } 28 | }, ''] 29 | } 30 | } 31 | } 32 | 33 | const script = document.createElement('script') 34 | script.src = '!{url_for(theme.asset.mathjax)}' 35 | script.id = 'MathJax-script' 36 | script.async = true 37 | document.head.appendChild(script) 38 | } else { 39 | MathJax.startup.document.state(0) 40 | MathJax.texReset() 41 | MathJax.typesetPromise() 42 | } 43 | } 44 | 45 | btf.addGlobalFn('encrypt', loadMathjax, 'mathjax') 46 | window.pjax ? loadMathjax() : window.addEventListener('load', loadMathjax) 47 | })() -------------------------------------------------------------------------------- /source/css/_highlight/highlight/diff.styl: -------------------------------------------------------------------------------- 1 | figure.highlight 2 | table 3 | // scrollbar - firefox 4 | @-moz-document url-prefix() 5 | scrollbar-color: var(--hlscrollbar-bg) transparent 6 | 7 | &::-webkit-scrollbar-thumb 8 | background: var(--hlscrollbar-bg) 9 | 10 | pre .deletion 11 | color: $highlight-deletion 12 | 13 | pre .addition 14 | color: $highlight-addition 15 | 16 | pre .meta 17 | color: $highlight-purple 18 | 19 | pre 20 | .comment 21 | color: $highlight-comment 22 | 23 | .variable, 24 | .attribute, 25 | .regexp, 26 | .ruby .constant, 27 | .xml .tag .title, 28 | .xml .pi, 29 | .xml .doctype, 30 | .html .doctype, 31 | .css .id, 32 | .tag .name, 33 | .css .class, 34 | .css .pseudo 35 | color: $highlight-red 36 | 37 | .tag 38 | color: $highlight-aqua 39 | 40 | .number, 41 | .preprocessor, 42 | .literal, 43 | .params, 44 | .constant, 45 | .command 46 | color: $highlight-orange 47 | 48 | .built_in 49 | color: $highlight-yellow 50 | 51 | .ruby .class .title, 52 | .css .rules .attribute, 53 | .string, 54 | .value, 55 | .inheritance, 56 | .header, 57 | .ruby .symbol, 58 | .xml .cdata, 59 | .special, 60 | .number, 61 | .formula 62 | color: $highlight-green 63 | 64 | .keyword, 65 | .title, 66 | .css .hexcolor 67 | color: $highlight-aqua 68 | 69 | .function, 70 | .python .decorator, 71 | .python .title, 72 | .ruby .function .title, 73 | .ruby .title .keyword, 74 | .perl .sub, 75 | .javascript .title, 76 | .coffeescript .title 77 | color: $highlight-blue 78 | 79 | .tag .attr, 80 | .javascript .function 81 | color: $highlight-purple 82 | -------------------------------------------------------------------------------- /scripts/tag/gallery.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Butterfly 3 | * galleryGroup and gallery 4 | * {% galleryGroup [name] [descr] [url] [img] %} 5 | * 6 | * {% gallery [button],%} 7 | * {% gallery url,[url],[button]%} 8 | */ 9 | 10 | 'use strict' 11 | 12 | const urlFor = require('hexo-util').url_for.bind(hexo) 13 | 14 | const gallery = (args, content) => { 15 | args = args.join(' ').split(',') 16 | let button = false 17 | let type = 'data' 18 | let dataStr = '' 19 | 20 | if (args[0] === 'url') { 21 | [type, dataStr, button] = args // url,[link],[lazyload] 22 | dataStr = urlFor(dataStr) 23 | } else { 24 | [button] = args // [lazyload] 25 | const regex = /!\[(.*?)\]\(([^\s]*)\s*(?:["'](.*?)["']?)?\s*\)/g 26 | let m 27 | const arr = [] 28 | while ((m = regex.exec(content)) !== null) { 29 | if (m.index === regex.lastIndex) { 30 | regex.lastIndex++ 31 | } 32 | arr.push({ 33 | url: m[2], 34 | alt: m[1], 35 | title: m[3] 36 | }) 37 | } 38 | 39 | dataStr = JSON.stringify(arr) 40 | } 41 | 42 | return `` 45 | } 46 | 47 | const galleryGroup = args => { 48 | const [name, descr, url, img] = args 49 | const imgUrl = urlFor(img) 50 | const urlLink = urlFor(url) 51 | 52 | return ` 60 | ` 61 | } 62 | 63 | hexo.extend.tag.register('gallery', gallery, { ends: true }) 64 | hexo.extend.tag.register('galleryGroup', galleryGroup) 65 | -------------------------------------------------------------------------------- /source/css/_layout/reward.styl: -------------------------------------------------------------------------------- 1 | .post-reward 2 | position: relative 3 | margin-top: 80px 4 | width: 100% 5 | text-align: center 6 | pointer-events: none 7 | 8 | & > * 9 | pointer-events: auto 10 | 11 | .reward-button 12 | display: inline-block 13 | padding: 4px 24px 14 | background: var(--btn-bg) 15 | color: var(--btn-color) 16 | cursor: pointer 17 | addBorderRadius() 18 | 19 | i 20 | margin-right: 5px 21 | 22 | &:hover 23 | .reward-button 24 | background: var(--btn-hover-color) 25 | 26 | & > .reward-main 27 | display: block 28 | 29 | .reward-main 30 | position: absolute 31 | bottom: 40px 32 | left: 0 33 | z-index: 100 34 | display: none 35 | padding: 0 0 15px 36 | width: 100% 37 | addBorderRadius() 38 | 39 | .reward-all 40 | display: inline-block 41 | margin: 0 42 | padding: 20px 10px 43 | background: var(--reward-pop) 44 | 45 | &:before 46 | position: absolute 47 | bottom: -10px 48 | left: 0 49 | width: 100% 50 | height: 20px 51 | content: '' 52 | 53 | &:after 54 | position: absolute 55 | right: 0 56 | bottom: 2px 57 | left: 0 58 | margin: 0 auto 59 | width: 0 60 | height: 0 61 | border-top: 13px solid var(--reward-pop) 62 | border-right: 13px solid transparent 63 | border-left: 13px solid transparent 64 | content: '' 65 | 66 | .reward-item 67 | display: inline-block 68 | padding: 0 8px 69 | list-style-type: none 70 | vertical-align: top 71 | 72 | img 73 | width: 130px 74 | height: 130px 75 | 76 | .post-qr-code-desc 77 | width: 130px 78 | color: $reward-pop-up-color 79 | -------------------------------------------------------------------------------- /layout/includes/third-party/comments/index.pug: -------------------------------------------------------------------------------- 1 | - let defaultComment = theme.comments.use[0] 2 | //- hr.custom-hr 3 | #post-comment 4 | .comment-head 5 | .comment-headline 6 | i.fas.fa-comments.fa-fw 7 | span= ' ' + _p('comment') 8 | #comment-close(onclick="custom.switchCommentMode();") 9 | i.fas.fa-xmark 10 | 11 | //- .comment-protocol 12 | //- a(href='/protocol/comment/' title='用户发言前,请认真阅读本条例。一经发言,即视为同意接受本条例;如不同意,请勿发言。')= '评论协议' 13 | 14 | if theme.comments.use.length > 1 15 | .comment-switch 16 | span.first-comment=defaultComment 17 | span#switch-btn 18 | span.second-comment=theme.comments.use[1] 19 | 20 | 21 | .comment-wrap 22 | each name in theme.comments.use 23 | div 24 | case name 25 | when 'Disqus' 26 | #disqus_thread 27 | when 'Valine' 28 | #vcomment.vcomment 29 | when 'Disqusjs' 30 | #disqusjs-wrap 31 | when 'Livere' 32 | #lv-container(data-id="city" data-uid=theme.livere.uid) 33 | when 'Gitalk' 34 | #gitalk-container 35 | when 'Utterances' 36 | #utterances-wrap 37 | when 'Twikoo' 38 | #twikoo-wrap 39 | when 'Waline' 40 | #waline-wrap 41 | when 'Giscus' 42 | #giscus-wrap 43 | when 'Facebook Comments' 44 | .fb-comments(data-colorscheme = theme.display_mode === 'dark' ? 'dark' : 'light' 45 | data-numposts= theme.facebook_comments.pageSize || 10 46 | data-order-by= theme.facebook_comments.order_by || 'social' 47 | data-width="100%") 48 | when 'Remark42' 49 | #remark42 50 | when 'Artalk' 51 | #artalk-wrap 52 | 53 | #comment-mask -------------------------------------------------------------------------------- /layout/includes/third-party/comments/utterances.pug: -------------------------------------------------------------------------------- 1 | - const { use, lazyload } = theme.comments 2 | - const { repo, issue_term, light_theme, dark_theme, js, option } = theme.utterances 3 | - const utterancesUrl = js || 'https://utteranc.es/client.js' 4 | - const utterancesOriginUrl = new URL(utterancesUrl).origin 5 | 6 | script. 7 | (() => { 8 | const getUtterancesTheme = theme => theme === 'dark' ? '#{dark_theme}' : '#{light_theme}' 9 | 10 | const loadUtterances = () => { 11 | const config = Object.assign({ 12 | id: 'utterances_comment', 13 | src: '!{utterancesUrl}', 14 | repo: '!{repo}', 15 | 'issue-term': '!{issue_term}', 16 | theme: getUtterancesTheme(document.documentElement.getAttribute('data-theme')), 17 | crossorigin: 'anonymous', 18 | async: true 19 | },!{JSON.stringify(option)}) 20 | 21 | const ele = document.createElement('script') 22 | for (let key in config) { 23 | ele.setAttribute(key, config[key]) 24 | } 25 | document.getElementById('utterances-wrap').appendChild(ele) 26 | } 27 | 28 | const changeUtterancesTheme = theme => { 29 | const iframe = document.querySelector('#utterances-wrap iframe') 30 | if (iframe) { 31 | const message = { 32 | type: 'set-theme', 33 | theme: getUtterancesTheme(theme) 34 | }; 35 | iframe.contentWindow.postMessage(message, '!{utterancesOriginUrl}') 36 | } 37 | } 38 | 39 | btf.addGlobalFn('themeChange', changeUtterancesTheme, 'utterances') 40 | 41 | if ('!{use[0]}' === 'Utterances' || !!{lazyload}) { 42 | if (!{lazyload}) btf.loadComment(document.getElementById('utterances-wrap'), loadUtterances) 43 | else loadUtterances() 44 | } else { 45 | window.loadOtherComment = loadUtterances 46 | } 47 | })() -------------------------------------------------------------------------------- /source/css/_tags/plugins/link.styl: -------------------------------------------------------------------------------- 1 | #article-container 2 | .tag.link 3 | text-align center 4 | a 5 | &.link-card 6 | margin 0.25rem auto 7 | background #f6f6f6 8 | display inline-flex 9 | align-items center 10 | cursor pointer 11 | text-align center 12 | min-width 200px 13 | max-width 361px 14 | color #444 15 | border-radius 12px 16 | text-decoration none 17 | &:hover 18 | box-shadow 0 4px 8px 0 rgba(0, 0, 0, 0.1) 19 | div 20 | &.left 21 | width 48px 22 | height 48px 23 | margin 12px 24 | overflow hidden 25 | flex-shrink 0 26 | position relative 27 | i 28 | font-size 32px 29 | line-height 48px 30 | margin-left 4px 31 | img 32 | display block 33 | position absolute 34 | border-radius 2px 35 | top 50% 36 | left 50% 37 | transform translate(-50%, -50%) 38 | &.right 39 | overflow hidden 40 | margin-right 12px 41 | p 42 | margin 0 43 | &.text 44 | font-weight bold 45 | &.url 46 | flex-shrink 0 47 | color rgba(68, 68, 68, 0.65) 48 | font-size 13px 49 | 50 | @media screen and (max-width: 425px) 51 | #article-container 52 | a 53 | &.link-card 54 | max-width 100% 55 | 56 | @media screen and (max-width: 375px) 57 | #article-container 58 | a 59 | &.link-card 60 | width 100% 61 | 62 | #article-container a.link-card div.left, 63 | #article-container a.link-card div.right 64 | pointer-events none 65 | 66 | [data-theme="dark"] 67 | #article-container 68 | a 69 | &.link-card 70 | filter brightness(0.7) 71 | img 72 | filter brightness(1) 73 | -------------------------------------------------------------------------------- /scripts/tag/series.js: -------------------------------------------------------------------------------- 1 | /** 2 | * series plugin 3 | * Syntax: 4 | * {% series [series name] %} 5 | * Usage: 6 | * {% series %} 7 | * {% series series1 %} 8 | */ 9 | 10 | 'use strict' 11 | 12 | const urlFor = require('hexo-util').url_for.bind(hexo) 13 | const groups = {} 14 | 15 | hexo.extend.filter.register('before_post_render', data => { 16 | if (!hexo.theme.config.series.enable) return data 17 | 18 | const { layout, series } = data 19 | if (layout === 'post' && series) { 20 | if (!groups[series]) groups[series] = [] 21 | groups[series].push({ 22 | title: data.title, 23 | path: data.path, 24 | date: data.date.unix() 25 | }) 26 | } 27 | return data 28 | }) 29 | 30 | function series (args) { 31 | const { series } = hexo.theme.config 32 | if (!series.enable) { 33 | hexo.log.warn('Series plugin is disabled in the theme config') 34 | return '' 35 | } 36 | 37 | const seriesArr = args.length ? groups[args[0]] : groups[this.series] 38 | 39 | if (!seriesArr) { 40 | hexo.log.warn(`There is no series named "${args[0]}"`) 41 | return '' 42 | } 43 | 44 | const isAsc = (series.order || 1) === 1 // 1: asc, -1: desc 45 | const isSortByTitle = series.orderBy === 'title' 46 | 47 | const compareFn = (a, b) => { 48 | const itemA = isSortByTitle ? a.title.toUpperCase() : a.date 49 | const itemB = isSortByTitle ? b.title.toUpperCase() : b.date 50 | 51 | return itemA < itemB ? (isAsc ? -1 : 1) : itemA > itemB ? (isAsc ? 1 : -1) : 0 52 | } 53 | 54 | seriesArr.sort(compareFn) 55 | 56 | const listItems = seriesArr.map(ele => 57 | `
  • ${ele.title}
  • ` 58 | ).join('') 59 | 60 | return series.number ? `
      ${listItems}
    ` : `
      ${listItems}
    ` 61 | } 62 | 63 | hexo.extend.tag.register('series', series, { ends: false }) 64 | -------------------------------------------------------------------------------- /source/css/_layout/rightmenu.styl: -------------------------------------------------------------------------------- 1 | #rightmenu 2 | display: none 3 | position: fixed 4 | top: 10% 5 | left: 10% 6 | width: 10rem 7 | height: fit-content 8 | padding: 0 .3rem 9 | background-color: var(--card-bg) 10 | box-shadow: var(--card-box-shadow) 11 | border: var(--card-border) 12 | border-radius: 8px 13 | z-index: 9999 14 | transition: all .3s 15 | 16 | &:hover 17 | border-color: var(--main) 18 | box-shadow: var(--main-shadow) 19 | 20 | .rightmenu-group 21 | padding: .4rem .35rem 22 | 23 | &:not(:nth-last-child(1)) 24 | border-bottom: 2px dashed var(--tab-button-hover-bg) 25 | 26 | .rightmenu-small 27 | display: flex 28 | justify-content: space-between 29 | 30 | .rightmenu-item 31 | border-radius: 8px 32 | transition: 0.3s 33 | cursor: pointer 34 | 35 | &:hover 36 | color: var(--second) 37 | background-color: var(--main) 38 | box-shadow: var(--main-shadow) 39 | 40 | i 41 | display: inline-block 42 | text-align: center 43 | padding: 0 8px 44 | 45 | .rightmenu-line 46 | .rightmenu-item 47 | color: var(--font-color) 48 | display: flex 49 | padding: .3rem 0 50 | border-radius: 8px 51 | cursor: pointer 52 | transition: 0.3s 53 | 54 | &:hover 55 | color: var(--second) 56 | background-color: var(--main) 57 | box-shadow: var(--main-shadow) 58 | 59 | i 60 | width: 3rem 61 | display: inline-block 62 | text-align: center 63 | line-height: 1.5 64 | padding: 0 8px 65 | margin: 0 auto 66 | 67 | span 68 | width: 6rem 69 | line-height: 1.5 70 | 71 | #rightmenu-mask 72 | position: fixed 73 | top: 0 74 | right: 0 75 | bottom: 0 76 | left: 0 77 | z-index: 9998 78 | display: none 79 | background: rgba(0,0,0,0.6) 80 | -------------------------------------------------------------------------------- /scripts/tag/plugins/site.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function postSiteCardGroup(args, content) { 4 | if (args.length > 0) { 5 | return `

    ${args}

    ${content}
    `; 6 | } else { 7 | return `
    ${content}
    `; 8 | } 9 | } 10 | function postSiteCard(args) { 11 | args = args.join(' ').split(', ') 12 | // 所有支持的参数 13 | let title = args[0].trim(); 14 | let url = ''; 15 | let screenshot = ''; 16 | let avatar = ''; 17 | let description = ''; 18 | // 解析 19 | if (args.length > 1) { 20 | for (let i = 1; i < args.length; i++) { 21 | let tmp = args[i].trim(); 22 | if (tmp.includes('url=')) { 23 | url = tmp.substring(4, tmp.length); 24 | } else if (tmp.includes('screenshot=')) { 25 | screenshot = tmp.substring(11, tmp.length); 26 | } else if (tmp.includes('avatar=')) { 27 | avatar = tmp.substring(7, tmp.length); 28 | } else if (tmp.includes('description=')) { 29 | description = tmp.substring(12, tmp.length); 30 | } 31 | } 32 | } 33 | // 布局 34 | let result = ''; 35 | result += ''; 36 | result += '
    '; 37 | result += '
    '; 38 | if (avatar.length > 0) { 39 | result += ''; 40 | } else { 41 | 42 | } 43 | 44 | result += '' + title + ''; 45 | if (description.length > 0) { 46 | result += '' + description + ''; 47 | } else { 48 | 49 | } 50 | 51 | result += '
    '; 52 | return result; 53 | 54 | } 55 | 56 | // {% site link, img, title %} 57 | // {% site link, img, title, description %} 58 | hexo.extend.tag.register('site', postSiteCard); 59 | hexo.extend.tag.register('sitegroup', postSiteCardGroup, {ends: true}); 60 | -------------------------------------------------------------------------------- /layout/includes/third-party/comments/giscus.pug: -------------------------------------------------------------------------------- 1 | - const { use, lazyload } = theme.comments 2 | - const { repo, repo_id, category_id, light_theme, dark_theme, js, option } = theme.giscus 3 | - const giscusUrl = js || 'https://giscus.app/client.js' 4 | - const giscusOriginUrl = new URL(giscusUrl).origin 5 | 6 | script. 7 | (()=>{ 8 | const getGiscusTheme = theme => theme === 'dark' ? '!{dark_theme}' : '!{light_theme}' 9 | 10 | const loadGiscus = () => { 11 | const config = Object.assign({ 12 | src: '!{giscusUrl}', 13 | 'data-repo': '!{repo}', 14 | 'data-repo-id': '!{repo_id}', 15 | 'data-category-id': '!{category_id}', 16 | 'data-mapping': 'pathname', 17 | 'data-theme': getGiscusTheme(document.documentElement.getAttribute('data-theme')), 18 | 'data-reactions-enabled': '1', 19 | crossorigin: 'anonymous', 20 | async: true 21 | },!{JSON.stringify(option)}) 22 | 23 | const ele = document.createElement('script') 24 | for (let key in config) { 25 | ele.setAttribute(key, config[key]) 26 | } 27 | document.getElementById('giscus-wrap').appendChild(ele) 28 | } 29 | 30 | const changeGiscusTheme = theme => { 31 | const iframe = document.querySelector('#giscus-wrap iframe') 32 | if (iframe) { 33 | const message = { 34 | giscus: { 35 | setConfig: { 36 | theme: getGiscusTheme(theme) 37 | } 38 | } 39 | } 40 | iframe.contentWindow.postMessage(message, '!{giscusOriginUrl}') 41 | } 42 | } 43 | 44 | btf.addGlobalFn('themeChange', changeGiscusTheme, 'giscus') 45 | 46 | if ('!{use[0]}' === 'Giscus' || !!{lazyload}) { 47 | if (!{lazyload}) btf.loadComment(document.getElementById('giscus-wrap'), loadGiscus) 48 | else loadGiscus() 49 | } else { 50 | window.loadOtherComment= loadGiscus 51 | } 52 | })() 53 | -------------------------------------------------------------------------------- /layout/includes/third-party/effect.pug: -------------------------------------------------------------------------------- 1 | if theme.fireworks && theme.fireworks.enable 2 | canvas.fireworks(mobile=`${theme.fireworks.mobile}`) 3 | script(src=url_for(theme.asset.fireworks)) 4 | 5 | if (theme.canvas_ribbon && theme.canvas_ribbon.enable) 6 | script(defer id="ribbon" src=url_for(theme.asset.canvas_ribbon) size=theme.canvas_ribbon.size 7 | alpha=theme.canvas_ribbon.alpha zIndex=theme.canvas_ribbon.zIndex mobile=`${theme.canvas_ribbon.mobile}` data-click=`${theme.canvas_ribbon.click_to_change}`) 8 | 9 | if (theme.canvas_fluttering_ribbon && theme.canvas_fluttering_ribbon.enable) 10 | script(defer id="fluttering_ribbon" mobile=`${theme.canvas_fluttering_ribbon.mobile}` src=url_for(theme.asset.canvas_fluttering_ribbon)) 11 | 12 | if (theme.canvas_nest && theme.canvas_nest.enable) 13 | script#canvas_nest(defer color=theme.canvas_nest.color opacity=theme.canvas_nest.opacity zIndex=theme.canvas_nest.zIndex count=theme.canvas_nest.count mobile=`${theme.canvas_nest.mobile}` src=url_for(theme.asset.canvas_nest)) 14 | 15 | if theme.activate_power_mode.enable 16 | script(src=url_for(theme.asset.activate_power_mode)) 17 | script. 18 | POWERMODE.colorful = !{theme.activate_power_mode.colorful}; 19 | POWERMODE.shake = !{theme.activate_power_mode.shake}; 20 | POWERMODE.mobile = !{theme.activate_power_mode.mobile}; 21 | document.body.addEventListener('input', POWERMODE); 22 | 23 | //- 鼠標特效 24 | if theme.click_heart && theme.click_heart.enable 25 | script#click-heart(src=url_for(theme.asset.click_heart) async mobile=`${theme.click_heart.mobile}`) 26 | 27 | if theme.clickShowText && theme.clickShowText.enable 28 | script#click-show-text( 29 | src= url_for(theme.asset.clickShowText) 30 | data-mobile= `${theme.clickShowText.mobile}` 31 | data-text= theme.clickShowText.text.join(",") 32 | data-fontsize= theme.clickShowText.fontSize 33 | data-random= `${theme.clickShowText.random}` 34 | async 35 | ) -------------------------------------------------------------------------------- /source/css/_layout/sidebar.styl: -------------------------------------------------------------------------------- 1 | #sidebar 2 | #menu-mask 3 | position: fixed 4 | z-index: 102 5 | display: none 6 | width: 100% 7 | height: 100% 8 | background: alpha($dark-black, .8) 9 | 10 | #sidebar-menus 11 | position: fixed 12 | top: 0 13 | right: -($sidebar-width) 14 | z-index: 103 15 | overflow-x: hidden 16 | overflow-y: scroll 17 | padding-left: 5px 18 | width: $sidebar-width 19 | height: 100% 20 | background: var(--sidebar-bg) 21 | transition: all .5s 22 | 23 | &.open 24 | transform: translate3d(-100%, 0, 0) 25 | 26 | & > .avatar-img 27 | margin: 20px auto 28 | 29 | .site-data 30 | padding: 0 10px 31 | 32 | hr 33 | margin: 20px auto 34 | 35 | .menus_items 36 | margin: 20px 37 | padding: 15px 38 | background: var(--sidebar-menu-bg) 39 | box-shadow: 0 0 1px 1px rgba(7, 17, 27, .05) 40 | addBorderRadius(10) 41 | 42 | .site-page 43 | @extend .limit-one-line 44 | position: relative 45 | display: block 46 | margin: 4px 0 47 | padding: 2px 23px 2px 15px 48 | color: var(--font-color) 49 | font-size: 1.15em 50 | cursor: pointer 51 | addBorderRadius(6) 52 | 53 | &:hover 54 | background: var(--text-bg-hover) 55 | color: var(--white) 56 | 57 | i:first-child 58 | width: 15% 59 | text-align: left 60 | 61 | &.group 62 | & > i:last-child 63 | position: absolute 64 | top: .6em 65 | right: 10px 66 | transition: transform .3s 67 | 68 | &.hide 69 | & > i:last-child 70 | transform: rotate(90deg) 71 | 72 | & + .menus_item_child 73 | display: none 74 | 75 | .menus_item_child 76 | margin: 0 77 | padding-left: 25px 78 | list-style: none -------------------------------------------------------------------------------- /layout/includes/third-party/comments/artalk.pug: -------------------------------------------------------------------------------- 1 | - const { server, site, option } = theme.artalk 2 | - const { use, lazyload } = theme.comments 3 | 4 | script. 5 | (() => { 6 | let artalkItem = null 7 | const initArtalk = () => { 8 | artalkItem = Artalk.init(Object.assign({ 9 | el: '#artalk-wrap', 10 | server: '!{server}', 11 | site: '!{site}', 12 | pageKey: location.pathname, 13 | darkMode: document.documentElement.getAttribute('data-theme') === 'dark', 14 | },!{JSON.stringify(option)})) 15 | 16 | if (GLOBAL_CONFIG.lightbox === 'null') return 17 | artalkItem.on('list-loaded', () => { 18 | artalkItem.ctx.get('list').getCommentNodes().forEach(comment => { 19 | const $content = comment.getRender().$content 20 | btf.loadLightbox($content.querySelectorAll('img:not([atk-emoticon])')) 21 | }) 22 | }) 23 | 24 | const destroyArtalk = () => { 25 | artalkItem.destroy() 26 | } 27 | 28 | btf.addGlobalFn('pjaxSendOnce', destroyArtalk, 'destroyArtalk') 29 | } 30 | 31 | const loadArtalk = async () => { 32 | if (typeof Artalk === 'object') initArtalk() 33 | else { 34 | await btf.getCSS('!{theme.asset.artalk_css}') 35 | await btf.getScript('!{theme.asset.artalk_js}') 36 | initArtalk() 37 | } 38 | } 39 | 40 | const artalkChangeMode = theme => { 41 | const artalkWrap = document.getElementById('artalk-wrap') 42 | if (!(artalkWrap && artalkWrap.children.length)) return 43 | const isDark = theme === 'dark' 44 | artalkItem.setDarkMode(isDark) 45 | } 46 | 47 | btf.addGlobalFn('themeChange', artalkChangeMode, 'artalk') 48 | 49 | if ('!{use[0]}' === 'Artalk' || !!{lazyload}) { 50 | if (!{lazyload}) btf.loadComment(document.getElementById('artalk-wrap'), loadArtalk) 51 | else setTimeout(loadArtalk, 100) 52 | } else { 53 | window.loadOtherComment = loadArtalk 54 | } 55 | })() -------------------------------------------------------------------------------- /source/css/_tags/tabs.styl: -------------------------------------------------------------------------------- 1 | 2 | #article-container 3 | .tabs 4 | position: relative 5 | margin: 0 0 20px 6 | border-right: 1px solid var(--tab-border-color) 7 | border-bottom: 1px solid var(--tab-border-color) 8 | border-left: 1px solid var(--tab-border-color) 9 | addBorderRadius() 10 | overflow: hidden 11 | 12 | > .nav-tabs 13 | display: flex 14 | flex-wrap: wrap 15 | margin: 0 16 | padding: 0 17 | background: var(--tab-botton-bg) 18 | 19 | > .tab 20 | flex-grow: 1 21 | padding: 8px 18px 22 | border-top: 2px solid var(--tab-border-color) 23 | background: var(--tab-botton-bg) 24 | color: var(--tab-botton-color) 25 | line-height: 2 26 | transition: all .4s 27 | 28 | i 29 | width: 1.5em 30 | 31 | &.active 32 | border-top: 2px solid $tab-active-border-color 33 | background: var(--tab-button-active-bg) 34 | cursor: default 35 | 36 | &:not(.active) 37 | &:hover 38 | border-top: 2px solid var(--tab-button-hover-bg) 39 | background: var(--tab-button-hover-bg) 40 | 41 | &.no-default 42 | & ~ .tab-to-top 43 | display: none 44 | 45 | > .tab-contents 46 | .tab-item-content 47 | position: relative 48 | display: none 49 | padding: 36px 24px 10px 50 | 51 | +maxWidth768() 52 | padding: 24px 14px 53 | 54 | &.active 55 | display: block 56 | animation: tabshow .5s 57 | 58 | > :last-child 59 | margin-bottom: 0 60 | 61 | > .tab-to-top 62 | padding: 0 16px 10px 0 63 | width: 100% 64 | text-align: right 65 | 66 | button 67 | color: $tab-to-top-color 68 | 69 | &:hover 70 | color: $tab-to-top-hover-color 71 | 72 | @keyframes tabshow 73 | 0% 74 | transform: translateY(15px) 75 | 76 | 100% 77 | transform: translateY(0) 78 | -------------------------------------------------------------------------------- /source/css/_layout/comments.styl: -------------------------------------------------------------------------------- 1 | #post-comment 2 | .comment-head 3 | margin-bottom: 20px 4 | 5 | &:after 6 | display: block 7 | clear: both 8 | content: '' 9 | 10 | .comment-headline 11 | display: inline-block 12 | vertical-align: middle 13 | font-weight: 700 14 | font-size: 1.43em 15 | 16 | .comment-switch 17 | display: inline-block 18 | 19 | if hexo-config('comments.text') 20 | float: right 21 | margin: 2px auto 0 22 | padding: 4px 16px 23 | width: max-content 24 | border-radius: 8px 25 | background: $comments-switch-bg 26 | else 27 | vertical-align: middle 28 | 29 | > span 30 | display: none 31 | 32 | .first-comment 33 | color: $comments-switch-first-text 34 | 35 | .second-comment 36 | color: $comments-switch-second-text 37 | 38 | #switch-btn 39 | position: relative 40 | display: inline-block 41 | margin: -4px 8px 0 42 | width: 42px 43 | height: 22px 44 | border-radius: 34px 45 | background-color: $comments-switch-first-text 46 | vertical-align: middle 47 | cursor: pointer 48 | transition: .4s 49 | 50 | &:before 51 | position: absolute 52 | bottom: 4px 53 | left: 4px 54 | width: 14px 55 | height: 14px 56 | border-radius: 50% 57 | background-color: $comments-switch-round 58 | content: '' 59 | transition: .4s 60 | 61 | .comment-wrap 62 | > div 63 | animation: tabshow .5s 64 | 65 | &:nth-child(2) 66 | display: none 67 | 68 | &.move 69 | #switch-btn 70 | background-color: $comments-switch-second-text 71 | 72 | &:before 73 | transform: translateX(20px) 74 | 75 | .comment-wrap 76 | > div 77 | &:first-child 78 | display: none 79 | 80 | &:last-child 81 | display: block -------------------------------------------------------------------------------- /scripts/tag/tabs.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Tabs 3 | * transplant from hexo-theme-next 4 | * modify by Jerry 5 | */ 6 | 7 | 'use strict' 8 | 9 | const postTabs = (args, content) => { 10 | const tabBlock = /\n([\w\W\s\S]*?)/g 11 | args = args.join(' ').split(',') 12 | const tabName = args[0] || 'tab' 13 | const tabActive = Number(args[1]) || 0 14 | const matches = [] 15 | let match 16 | let tabId = 0 17 | let tabNav = '' 18 | let tabContent = '' 19 | let noDefault = true 20 | 21 | while ((match = tabBlock.exec(content)) !== null) { 22 | matches.push(match[1], match[2]) 23 | } 24 | 25 | for (let i = 0; i < matches.length; i += 2) { 26 | const [tabCaption = '', tabIcon = ''] = matches[i].split('@') 27 | let postContent = matches[i + 1] 28 | 29 | postContent = hexo.render.renderSync({ text: postContent, engine: 'markdown' }).trim() 30 | tabId += 1 31 | 32 | const caption = (tabCaption || tabIcon) ? tabCaption : `${tabName} ${tabId}` 33 | const iconHtml = tabIcon ? `` : '' 34 | const isActive = (tabActive > 0 && tabActive === tabId) || (tabActive === 0 && tabId === 1) ? ' active' : '' 35 | 36 | if (isActive) noDefault = false 37 | 38 | tabNav += `` 39 | tabContent += `
    ${postContent}
    ` 40 | } 41 | 42 | const toTop = '
    ' 43 | tabNav = `` 44 | tabContent = `
    ${tabContent}
    ` 45 | 46 | return `
    ${tabNav}${tabContent}${toTop}
    ` 47 | } 48 | 49 | hexo.extend.tag.register('tabs', postTabs, { ends: true }) 50 | hexo.extend.tag.register('subtabs', postTabs, { ends: true }) 51 | hexo.extend.tag.register('subsubtabs', postTabs, { ends: true }) 52 | -------------------------------------------------------------------------------- /layout/includes/pagination.pug: -------------------------------------------------------------------------------- 1 | - 2 | var options = { 3 | prev_text: '
    上页
    ', 4 | next_text: '
    下页
    ', 5 | mid_size: 1, 6 | escape: false 7 | } 8 | 9 | if is_post() 10 | - let paginationOrder = theme.post_pagination === 1 ? { prev: page.prev, next: page.next } : { prev: page.next, next: page.prev } 11 | 12 | nav#pagination.pagination-post 13 | each direction, key in paginationOrder 14 | if direction 15 | - const getPostDesc = direction.postDesc || postDesc(direction) 16 | - let className = key === 'prev' ? (paginationOrder.next ? '' : 'full-width') : (paginationOrder.prev ? '' : 'full-width') 17 | - className = getPostDesc ? className : className + ' no-desc' 18 | 19 | a.pagination-related(class=className href=url_for(direction.path) title=direction.title) 20 | if direction.cover_type === 'img' 21 | img.cover(src=url_for(direction.cover) onerror=`onerror=null;src='${url_for(theme.error_img.post_page)}'` alt=`cover of ${key === 'prev' ? 'previous' : 'next'} post`) 22 | else 23 | .cover(style=`background: ${direction.cover || 'var(--default-bg-color)'}`) 24 | 25 | .info(class=key === 'prev' ? '' : 'text-right') 26 | .info-1 27 | .info-item-1=_p(`pagination.${key}`) 28 | .info-item-2!=direction.title 29 | if getPostDesc 30 | .info-2 31 | .info-item-1!=getPostDesc 32 | else 33 | nav#pagination 34 | .pagination 35 | if is_home() 36 | - options.format = 'page/%d/' 37 | !=paginator(options) 38 | .toPageGroup 39 | input#toPageText(oninput="value=value.replace(/[^0-9]/g, '')" maxlength="3" title="跳转到指定页面" onkeyup="if(this.value === '0') this.value = ''") 40 | a#toPageButton(onclick="eurkon.toPage()") 41 | i.fa.fas.fa-fw.fa-angle-double-right -------------------------------------------------------------------------------- /source/css/_tags/timeline.styl: -------------------------------------------------------------------------------- 1 | #article-container 2 | .timeline 3 | margin: 0 10px 20px 4 | padding: 14px 0 5px 20px 5 | border-left: 2px solid var(--timeline-color, $theme-color) 6 | 7 | for $type in $color-types 8 | &.{$type} 9 | --timeline-color: lookup('$tagsP-' + $type + '-color') 10 | --timeline-bg: s('rgba(%s,%s,%s, 0.2)', red(lookup('$tagsP-' + $type + '-color')), green(lookup('$tagsP-' + $type + '-color')), blue(lookup('$tagsP-' + $type + '-color'))) 11 | 12 | .timeline-item 13 | margin: 0 0 15px 14 | 15 | &:hover 16 | .item-circle 17 | &:before 18 | border-color: var(--timeline-color, $theme-color) 19 | 20 | &.headline 21 | .timeline-item-title 22 | .item-circle 23 | > p 24 | font-weight: 600 25 | font-size: 1.2em 26 | 27 | &:before 28 | left: -28px 29 | border: 4px solid var(--timeline-color, $theme-color) 30 | 31 | &:hover 32 | .item-circle 33 | &:before 34 | border-color: var(--pseudo-hover) 35 | 36 | .timeline-item-title 37 | position: relative 38 | 39 | .item-circle 40 | &:before 41 | position: absolute 42 | top: 50% 43 | left: -27px 44 | width: 6px 45 | height: 6px 46 | border: 3px solid var(--pseudo-hover) 47 | border-radius: 50% 48 | background: var(--card-bg) 49 | content: '' 50 | transition: all .3s 51 | transform: translate(0, -50%) 52 | 53 | > p 54 | margin: 0 0 8px 55 | font-weight: 500 56 | 57 | .timeline-item-content 58 | position: relative 59 | padding: 12px 15px 60 | border-radius: 8px 61 | background: var(--timeline-bg, lighten($theme-color, 85%)) 62 | font-size: .93em 63 | 64 | & > :last-child 65 | margin-bottom: 0 66 | 67 | & + .timeline 68 | margin-top: -20px -------------------------------------------------------------------------------- /layout/includes/third-party/newest-comments/valine.pug: -------------------------------------------------------------------------------- 1 | - let default_avatar = theme.valine.avatar 2 | 3 | script(src=url_for(theme.asset.blueimp_md5)) 4 | != partial("includes/third-party/newest-comments/common.pug", {}, { cache: true }) 5 | 6 | script. 7 | window.addEventListener('load', () => { 8 | const keyName = 'valine-newest-comments' 9 | const { changeContent, generateHtml, run } = window.newestComments 10 | 11 | const getIcon = (icon, mail) => { 12 | if (icon) return icon 13 | let defaultIcon = '!{ default_avatar ? `?d=${default_avatar}` : ''}' 14 | let iconUrl = `https://gravatar.loli.net/avatar/${md5(mail.toLowerCase()) + defaultIcon}` 15 | return iconUrl 16 | } 17 | 18 | const getComment = ele => { 19 | const serverURL = '!{theme.valine.serverURLs || `https://${theme.valine.appId.substring(0,8)}.api.lncldglobal.com` }' 20 | 21 | var settings = { 22 | "method": "GET", 23 | "headers": { 24 | "X-LC-Id": '!{theme.valine.appId}', 25 | "X-LC-Key": '!{theme.valine.appKey}', 26 | "Content-Type": "application/json" 27 | }, 28 | } 29 | 30 | fetch(`${serverURL}/1.1/classes/Comment?limit=!{theme.aside.card_newest_comments.limit}&order=-createdAt`,settings) 31 | .then(response => response.json()) 32 | .then(data => { 33 | const valineArray = data.results.map(e => { 34 | return { 35 | 'avatar': getIcon(e.QQAvatar, e.mail), 36 | 'content': changeContent(e.comment), 37 | 'nick': e.nick, 38 | 'url': e.url + '#' + e.objectId, 39 | 'date': e.updatedAt, 40 | } 41 | }) 42 | btf.saveToLocal.set(keyName, JSON.stringify(valineArray), !{theme.aside.card_newest_comments.storage}/(60*24)) 43 | generateHtml(valineArray, ele) 44 | }).catch(e => { 45 | console.error(e) 46 | ele.textContent= "!{_p('aside.card_newest_comments.error')}" 47 | }) 48 | } 49 | 50 | run(keyName, getComment) 51 | }) 52 | -------------------------------------------------------------------------------- /layout/includes/header/nav.pug: -------------------------------------------------------------------------------- 1 | nav#nav 2 | #nav-group 3 | span#blog-info 4 | if theme.nav.logo 5 | a(href=url_for('/') title=config.title) 6 | img.site-icon(src=url_for(theme.nav.logo)) 7 | 8 | if theme.nav.display_title 9 | a.site-page(href=url_for('/') title=config.title) 10 | span.site-name=config.title 11 | i.fas.fa-home 12 | 13 | //- if is_post() 14 | //- a.nav-page-title(href=url_for('/') ) 15 | //- span.site-name=(page.title || config.title) 16 | 17 | a.nav-page-title(title=_p("rightside.back_to_top") onclick="btf.scrollToDest(0, 500)") 18 | span#page-title.site-name=(pageTitle || page.title || config.title) 19 | 20 | if theme.menu 21 | #menus 22 | != partial('includes/header/menu_item', {}, {cache: true}) 23 | 24 | #hotkey 25 | if theme.search.use 26 | #search-button 27 | span.site-page.social-icon.search(href="javascript:void(0);" title=_p('search.title')) 28 | i.fas.fa-search.fa-fw 29 | span= ' ' + _p('search.title') 30 | //- if (theme.darkmode.enable && theme.darkmode.button) 31 | //- #mode-button 32 | //- a.site-page(title=_p('rightside.night_mode_title') onclick="btf.switchDarkMode()") 33 | //- i.fas.fa-sun.fa-fw 34 | //- i.fas.fa-moon.fa-fw 35 | if page.comments !== false && theme.comments.use 36 | #comment-button 37 | a.site-page(href="#post-comment" title=_p("rightside.scroll_to_comment")) 38 | i.fas.fa-comments.fa-fw 39 | #setting-button(onclick="eurkon.switchRightSide();") 40 | a.site-page(href="javascript:void(0);" title=_p("rightside.setting")) 41 | i.fas.fa-screwdriver-wrench.fa-fw 42 | #top-button 43 | a.site-page(href="javascript:void(0);" title=_p("rightside.back_to_top") onclick="btf.scrollToDest(0, 500)") 44 | span.scroll-percent 45 | i.fas.fa-arrow-up.fa-fw 46 | #toggle-menu 47 | a.site-page(href="javascript:void(0);") 48 | i.fas.fa-bars.fa-fw -------------------------------------------------------------------------------- /layout/includes/widget/card_author.pug: -------------------------------------------------------------------------------- 1 | if theme.aside.card_author.enable 2 | .card-widget.card-info.text-center 3 | //- 源代码START 4 | //- .avatar-img 5 | //- img(src=url_for(theme.avatar.img) onerror=`this.onerror=null;this.src='` + url_for(theme.error_img.flink) + `'` alt="avatar") 6 | //- .author-info-name= config.author 7 | //- .author-info-description!= theme.aside.card_author.description || config.description 8 | //- 源代码END 9 | 10 | //- 魔改代码START 11 | if config.card_info && config.card_info.enable 12 | .card-info-top 13 | #card-info-hello 14 | .card-info-img 15 | img(src=url_for(theme.avatar.img) onerror=`this.onerror=null;this.src='` + url_for(theme.error_img.flink) + `'` alt="avatar") 16 | .author-info-name.is-center= config.author 17 | .author-info-description!= theme.aside.card_author.description || config.description 18 | script(defer data-pjax src=url_for(config.card_info.CDN)) 19 | else 20 | .avatar-img 21 | img(src=url_for(theme.avatar.img) onerror=`this.onerror=null;this.src='` + url_for(theme.error_img.flink) + `'` alt="avatar") 22 | .author-info-name= config.author 23 | .author-info-description!= theme.aside.card_author.description || config.description 24 | //- 魔改代码END 25 | 26 | .site-data 27 | a(href=url_for(config.archive_dir) + '/') 28 | .headline= _p('aside.articles') 29 | .length-num= site.posts.length 30 | a(href=url_for(config.tag_dir) + '/') 31 | .headline= _p('aside.tags') 32 | .length-num= site.tags.length 33 | a(href=url_for(config.category_dir) + '/') 34 | .headline= _p('aside.categories') 35 | .length-num= site.categories.length 36 | 37 | if theme.aside.card_author.button.enable 38 | a#card-info-btn(href=theme.aside.card_author.button.link) 39 | i(class=theme.aside.card_author.button.icon) 40 | span=theme.aside.card_author.button.text 41 | 42 | if(theme.social) 43 | .card-info-social-icons 44 | !=partial('includes/header/social', {}, {cache: true}) 45 | -------------------------------------------------------------------------------- /layout/includes/third-party/comments/disqus.pug: -------------------------------------------------------------------------------- 1 | - const disqusPageTitle = page.title.replace(/'/ig,"\\'") 2 | - const { shortname, apikey } = theme.disqus 3 | - const { use, lazyload, count } = theme.comments 4 | 5 | script. 6 | (() => { 7 | const disqus_config = function () { 8 | this.page.url = '!{ page.permalink }' 9 | this.page.identifier = '!{ url_for(page.path) }' 10 | this.page.title = '!{ disqusPageTitle }' 11 | } 12 | 13 | const disqusReset = () => { 14 | window.DISQUS && window.DISQUS.reset({ 15 | reload: true, 16 | config: disqus_config 17 | }) 18 | } 19 | 20 | btf.addGlobalFn('themeChange', disqusReset, 'disqus') 21 | 22 | const loadDisqus = () =>{ 23 | if (window.DISQUS) disqusReset() 24 | else { 25 | const script = document.createElement('script') 26 | script.src = 'https://!{shortname}.disqus.com/embed.js' 27 | script.setAttribute('data-timestamp', +new Date()) 28 | document.head.appendChild(script) 29 | } 30 | } 31 | 32 | const getCount = async() => { 33 | try { 34 | const eleGroup = document.querySelector('#post-meta .disqus-comment-count') 35 | if (!eleGroup) return 36 | const cleanedLinks = eleGroup.href.replace(/#post-comment$/, '') 37 | 38 | const res = await fetch(`https://disqus.com/api/3.0/threads/set.json?forum=!{shortname}&api_key=!{apikey}&thread:link=${cleanedLinks}`,{ 39 | method: 'GET' 40 | }) 41 | const result = await res.json() 42 | 43 | const count = result.response.length ? result.response[0].posts : 0 44 | eleGroup.textContent = count 45 | } catch (err) { 46 | console.error(err) 47 | } 48 | } 49 | 50 | if ('!{use[0]}' === 'Disqus' || !!{lazyload}) { 51 | if (!{lazyload}) btf.loadComment(document.getElementById('disqus_thread'), loadDisqus) 52 | else { 53 | loadDisqus() 54 | !{ count ? 'GLOBAL_CONFIG_SITE.isPost && getCount()' : '' } 55 | } 56 | } else { 57 | window.loadOtherComment = loadDisqus 58 | } 59 | })() 60 | -------------------------------------------------------------------------------- /layout/includes/third-party/comments/facebook_comments.pug: -------------------------------------------------------------------------------- 1 | - const fbSDKVer = 'v20.0' 2 | - const fbSDK = `https://connect.facebook.net/${theme.facebook_comments.lang}/sdk.js#xfbml=1&version=${fbSDKVer}` 3 | 4 | script. 5 | (()=>{ 6 | const loadFBComment = () => { 7 | document.getElementById('fb-root') ? '' : document.body.insertAdjacentHTML('afterend', '
    ') 8 | 9 | const themeNow = document.documentElement.getAttribute('data-theme') === 'dark' ? 'dark' : 'light' 10 | const $fbComment = document.getElementsByClassName('fb-comments')[0] 11 | $fbComment.setAttribute('data-colorscheme',themeNow) 12 | $fbComment.setAttribute('data-href', '!{urlNoIndex(page.permalink)}') 13 | 14 | if (typeof FB === 'object') { 15 | FB.XFBML.parse(document.getElementsByClassName('post-meta-commentcount')[0]) 16 | FB.XFBML.parse(document.getElementById('post-comment')) 17 | } 18 | else { 19 | let ele = document.createElement('script') 20 | ele.setAttribute('src','!{fbSDK}') 21 | ele.setAttribute('async', 'true') 22 | ele.setAttribute('defer', 'true') 23 | ele.setAttribute('crossorigin', 'anonymous') 24 | ele.setAttribute('id', 'facebook-jssdk') 25 | document.getElementById('fb-root').insertAdjacentElement('afterbegin',ele) 26 | } 27 | } 28 | 29 | const fbModeChange = theme => { 30 | const $fbComment = document.getElementsByClassName('fb-comments')[0] 31 | if ($fbComment && typeof FB === 'object') { 32 | $fbComment.setAttribute('data-colorscheme',theme) 33 | FB.XFBML.parse(document.getElementById('post-comment')) 34 | } 35 | } 36 | 37 | btf.addGlobalFn('themeChange', fbModeChange, 'facebook_comments') 38 | 39 | if ('!{theme.comments.use[0]}' === 'Facebook Comments' || !!{theme.comments.lazyload}) { 40 | if (!{theme.comments.lazyload}) btf.loadComment(document.querySelector('#post-comment .fb-comments'), loadFBComment) 41 | else loadFBComment() 42 | } else { 43 | window.loadOtherComment = loadFBComment 44 | } 45 | })() 46 | 47 | -------------------------------------------------------------------------------- /layout/includes/third-party/comments/remark42.pug: -------------------------------------------------------------------------------- 1 | - const { host, siteId, option } = theme.remark42 2 | script. 3 | var remark_config = Object.assign({ 4 | host: '!{host}', 5 | site_id: '!{siteId}', 6 | components: ['embed'], 7 | theme: document.documentElement.getAttribute('data-theme') === 'dark' ? 'dark' : 'light' 8 | },!{JSON.stringify(option)}) 9 | 10 | function addRemark42(){ 11 | for (let i = 0; i < remark_config.components.length; i++) { 12 | const s = document.createElement('script') 13 | s.src = remark_config.host + '/web/' + remark_config.components[i] + '.js' 14 | s.defer = true 15 | document.head.appendChild(s) 16 | } 17 | } 18 | 19 | function initRemark42() { 20 | if (window.REMARK42) { 21 | if (this.remark42Instance) { 22 | this.remark42Instance.destroy() 23 | } 24 | 25 | this.remark42Instance = window.REMARK42.createInstance({ 26 | ...remark_config 27 | }) 28 | } 29 | } 30 | 31 | function getCount () { 32 | const ele = document.querySelector('.remark42__counter') 33 | if (ele) { 34 | const s = document.createElement('script') 35 | s.src = remark_config.host + '/web/counter.js' 36 | s.defer = true 37 | document.head.appendChild(s) 38 | } 39 | } 40 | 41 | function loadRemark42 () { 42 | if (window.REMARK42) { 43 | this.initRemark42() 44 | getCount() 45 | } else { 46 | addRemark42() 47 | window.addEventListener('REMARK42::ready', () => { 48 | this.initRemark42() 49 | getCount() 50 | }) 51 | } 52 | } 53 | 54 | function remarkChangeMode (theme) { 55 | if (!window.REMARK42) return 56 | window.REMARK42.changeTheme(theme) 57 | } 58 | 59 | btf.addGlobalFn('themeChange', remarkChangeMode, 'remark42') 60 | 61 | if ('!{theme.comments.use[0]}' === 'Remark42' || !!{theme.comments.lazyload}) { 62 | if (!{theme.comments.lazyload}) btf.loadComment(document.getElementById('remark42'), loadRemark42) 63 | else loadRemark42() 64 | } else { 65 | function loadOtherComment () { 66 | loadRemark42() 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /scripts/tag/hide.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Butterfly 3 | * @example 4 | * hideInline 5 | * {% hideInline content,display,bg,color %} 6 | * content不能包含當引號,可用 ' 7 | * hideBlock 8 | * {% hideBlock display,bg,color %} 9 | * content 10 | * {% endhideBlock %} 11 | * hideToggle 12 | * {% hideToggle display,bg,color %} 13 | * content 14 | * {% endhideToggle %} 15 | */ 16 | 17 | 'use strict' 18 | 19 | const parseArgs = args => { 20 | return args.join(' ').split(',') 21 | } 22 | 23 | const generateStyle = (bg, color) => { 24 | let style = 'style="' 25 | if (bg) { 26 | style += `background-color: ${bg};` 27 | } 28 | if (color) { 29 | style += `color: ${color}` 30 | } 31 | style += '"' 32 | return style 33 | } 34 | 35 | const hideInline = args => { 36 | const [content, display = 'Click', bg = false, color = false] = parseArgs(args) 37 | const group = generateStyle(bg, color) 38 | 39 | return `${content}` 41 | } 42 | 43 | const hideBlock = (args, content) => { 44 | const [display = 'Click', bg = false, color = false] = parseArgs(args) 45 | const group = generateStyle(bg, color) 46 | 47 | return `
    ${hexo.render.renderSync({ text: content, engine: 'markdown' })}
    ` 49 | } 50 | 51 | const hideToggle = (args, content) => { 52 | const [display, bg = false, color = false] = parseArgs(args) 53 | const group = generateStyle(bg, color) 54 | let border = '' 55 | 56 | if (bg) { 57 | border = `style="border: 1px solid ${bg}"` 58 | } 59 | 60 | return `
    ${display}
    ${hexo.render.renderSync({ text: content, engine: 'markdown' })}
    ` 61 | } 62 | 63 | hexo.extend.tag.register('hideInline', hideInline) 64 | hexo.extend.tag.register('hideBlock', hideBlock, { ends: true }) 65 | hexo.extend.tag.register('hideToggle', hideToggle, { ends: true }) 66 | -------------------------------------------------------------------------------- /layout/includes/third-party/math/mermaid.pug: -------------------------------------------------------------------------------- 1 | script. 2 | (() => { 3 | const runMermaid = ele => { 4 | window.loadMermaid = true 5 | const theme = document.documentElement.getAttribute('data-theme') === 'dark' ? '!{theme.mermaid.theme.dark}' : '!{theme.mermaid.theme.light}' 6 | 7 | ele.forEach((item, index) => { 8 | const mermaidSrc = item.firstElementChild 9 | const mermaidThemeConfig = `%%{init:{ 'theme':'${theme}'}}%%\n` 10 | const mermaidID = `mermaid-${index}` 11 | const mermaidDefinition = mermaidThemeConfig + mermaidSrc.textContent 12 | 13 | const renderFn = mermaid.render(mermaidID, mermaidDefinition) 14 | const renderMermaid = svg => { 15 | mermaidSrc.insertAdjacentHTML('afterend', svg) 16 | } 17 | 18 | // mermaid v9 and v10 compatibility 19 | typeof renderFn === 'string' ? renderMermaid(renderFn) : renderFn.then(({ svg }) => renderMermaid(svg)) 20 | }) 21 | } 22 | 23 | const codeToMermaid = () => { 24 | const codeMermaidEle = document.querySelectorAll('pre > code.mermaid') 25 | if (codeMermaidEle.length === 0) return 26 | 27 | codeMermaidEle.forEach(ele => { 28 | const preEle = document.createElement('pre') 29 | preEle.className = 'mermaid-src' 30 | preEle.hidden = true 31 | preEle.textContent = ele.textContent 32 | const newEle = document.createElement('div') 33 | newEle.className = 'mermaid-wrap' 34 | newEle.appendChild(preEle) 35 | ele.parentNode.replaceWith(newEle) 36 | }) 37 | } 38 | 39 | const loadMermaid = () => { 40 | if (!{theme.mermaid.code_write}) codeToMermaid() 41 | const $mermaid = document.querySelectorAll('#article-container .mermaid-wrap') 42 | if ($mermaid.length === 0) return 43 | 44 | const runMermaidFn = () => runMermaid($mermaid) 45 | btf.addGlobalFn('themeChange', runMermaidFn, 'mermaid') 46 | window.loadMermaid ? runMermaidFn() : btf.getScript('!{url_for(theme.asset.mermaid)}').then(runMermaidFn) 47 | } 48 | 49 | btf.addGlobalFn('encrypt', loadMermaid, 'mermaid') 50 | window.pjax ? loadMermaid() : document.addEventListener('DOMContentLoaded', loadMermaid) 51 | })() -------------------------------------------------------------------------------- /source/css/_tags/plugins/site-card.styl: -------------------------------------------------------------------------------- 1 | trans($time = 0.28s) 2 | transition: all $time ease 3 | -moz-transition: all $time ease 4 | -webkit-transition: all $time ease 5 | -o-transition: all $time ease 6 | 7 | 8 | .site-card-group 9 | display: flex 10 | flex-wrap: wrap 11 | justify-content: flex-start 12 | margin: -0.5 * 16px 13 | align-items: stretch 14 | .site-card 15 | margin: 16px * 0.5 16 | width: "calc(100% / 4 - %s)" % 16px 17 | @media screen and (min-width: 2048px) 18 | width: "calc(100% / 5 - %s)" % 16px 19 | @media screen and (max-width: 768px) 20 | width: "calc(100% / 3 - %s)" % 16px 21 | @media screen and (max-width: 500px) 22 | width: "calc(100% / 2 - %s)" % 16px 23 | display: block 24 | line-height: 1.4 25 | height 100% 26 | .img 27 | width: 100% 28 | height 120px 29 | @media screen and (max-width: 500px) 30 | height 100px 31 | overflow: hidden 32 | border-radius: 12px * 0.5 33 | box-shadow: 0 1px 2px 0px rgba(0, 0, 0, 0.2) 34 | background: #f6f6f6 35 | img 36 | width: 100% 37 | height 100% 38 | pointer-events:none; 39 | // trans(.75s) 40 | transition: transform 2s ease 41 | object-fit: cover 42 | 43 | .info 44 | margin-top: 16px * 0.5 45 | img 46 | width: 32px 47 | height: 32px 48 | pointer-events:none; 49 | border-radius: 16px 50 | float: left 51 | margin-right: 8px 52 | margin-top: 2px 53 | span 54 | display: block 55 | .title 56 | font-weight: 600 57 | font-size: var(--global-font-size) 58 | color: #444 59 | display: -webkit-box 60 | -webkit-box-orient: vertical 61 | overflow: hidden 62 | -webkit-line-clamp: 1 63 | trans() 64 | .desc 65 | font-size: var(--global-font-size) 66 | word-wrap: break-word; 67 | line-height: 1.2 68 | color: #888 69 | display: -webkit-box 70 | -webkit-box-orient: vertical 71 | overflow: hidden 72 | -webkit-line-clamp: 2 73 | .img 74 | trans() 75 | &:hover 76 | .img 77 | box-shadow: 0 4px 8px 0px rgba(0, 0, 0, 0.1), 0 2px 4px 0px rgba(0, 0, 0, 0.1), 0 4px 8px 0px rgba(0, 0, 0, 0.1), 0 8px 16px 0px rgba(0, 0, 0, 0.1) 78 | .info .title 79 | color: #ff5722 80 | -------------------------------------------------------------------------------- /source/css/_search/algolia.styl: -------------------------------------------------------------------------------- 1 | #algolia-search 2 | .search-dialog 3 | .ais-SearchBox 4 | input 5 | padding: 5px 14px 6 | width: 100% 7 | outline: none 8 | border: 2px solid $search-color 9 | border-radius: 40px 10 | background: var(--search-bg) 11 | color: var(--search-input-color) 12 | 13 | .ais-SearchBox-loadingIndicator 14 | position: absolute 15 | top: 18px 16 | left: 67px 17 | 18 | .ais-Hits-list 19 | margin: 0 20 | padding: 0 21 | @extend .list-beauty 22 | 23 | a 24 | color: var(--search-a-color) 25 | 26 | &:hover 27 | color: $search-color 28 | 29 | mark 30 | background: transparent 31 | color: $search-keyword-highlight 32 | font-weight: bold 33 | 34 | .algolia-hits-item-title 35 | font-weight: 600 36 | 37 | .algolia-hit-item-content 38 | margin: 0 0 8px 39 | word-break: break-word 40 | 41 | .ais-Pagination 42 | margin: 15px 0 0 43 | padding: 0 44 | text-align: center 45 | 46 | .ais-Pagination-list 47 | margin: 0 48 | padding: 0 49 | list-style: none 50 | 51 | .ais-Pagination-item 52 | display: inline 53 | margin: 0 4px 54 | padding: 0 55 | 56 | .ais-Pagination-link 57 | display: inline-block 58 | min-width: 24px 59 | height: 24px 60 | text-align: center 61 | line-height: 24px 62 | addBorderRadius() 63 | 64 | .ais-Pagination-item--selected 65 | a 66 | background: $theme-paginator-color 67 | color: #eee 68 | cursor: default 69 | 70 | .ais-Pagination-item--disabled 71 | visibility: hidden 72 | 73 | #algolia-hits 74 | > div 75 | overflow-y: overlay 76 | margin: 0 -20px 77 | padding: 0 22px 78 | max-height: calc(80vh - 220px) 79 | 80 | +maxWidth768() 81 | max-height: none 82 | height: calc(var(--search-height) - 235px) 83 | 84 | #algolia-info 85 | div 86 | display: inline 87 | 88 | .algolia-poweredBy 89 | float: right 90 | vertical-align: text-top 91 | 92 | svg 93 | height: 1.1em -------------------------------------------------------------------------------- /layout/includes/third-party/newest-comments/github-issues.pug: -------------------------------------------------------------------------------- 1 | != partial("includes/third-party/newest-comments/common.pug", {}, { cache: true }) 2 | 3 | script. 4 | window.addEventListener('load', () => { 5 | const keyName = 'github-newest-comments' 6 | const { changeContent, generateHtml, run } = window.newestComments 7 | 8 | const findTrueUrl = (array, ele) => { 9 | Promise.all(array.map(item => 10 | fetch(item.url).then(resp => resp.json()).then(data => { 11 | let urlArray = data.body ? data.body.match(/(https?:\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?/ig) : [] 12 | if (!Array.isArray(urlArray) || urlArray.length === 0) { 13 | urlArray = [`${data.html_url}`] 14 | } 15 | if (data.user.login === 'utterances-bot') { 16 | return urlArray.pop() 17 | } else { 18 | return urlArray.shift() 19 | } 20 | }) 21 | )).then(res => { 22 | array = array.map((i,index)=> { 23 | return { 24 | ...i, 25 | url: res[index] 26 | } 27 | }) 28 | 29 | btf.saveToLocal.set(keyName, JSON.stringify(array), !{theme.aside.card_newest_comments.storage}/(60*24)) 30 | generateHtml(array, ele) 31 | }); 32 | } 33 | 34 | const getComment = ele => { 35 | fetch('https://api.github.com/repos/!{userRepo}/issues/comments?sort=updated&direction=desc&per_page=!{theme.aside.card_newest_comments.limit}&page=1',{ 36 | "headers": { 37 | Accept: 'application/vnd.github.v3.html+json' 38 | } 39 | }) 40 | .then(response => response.json()) 41 | .then(data => { 42 | const githubArray = data.map(item => { 43 | return { 44 | 'avatar': item.user.avatar_url, 45 | 'content': changeContent(item.body_html || item.body), 46 | 'nick': item.user.login, 47 | 'url': item.issue_url, 48 | 'date': item.updated_at 49 | } 50 | }) 51 | findTrueUrl(githubArray, ele) 52 | }).catch(e => { 53 | console.error(e) 54 | ele.textContent= "!{_p('aside.card_newest_comments.error')}" 55 | }) 56 | } 57 | run(keyName, getComment) 58 | }) 59 | 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /source/css/_layout/pagination.styl: -------------------------------------------------------------------------------- 1 | #pagination 2 | .pagination 3 | margin-top: 20px 4 | text-align: center 5 | 6 | .page-number 7 | &.current 8 | background: $theme-paginator-color 9 | color: var(--white) 10 | 11 | .full-width 12 | width: 100% !important 13 | 14 | .pagination-related 15 | width: 50% 16 | height: 150px 17 | 18 | +maxWidth768() 19 | width: 100% 20 | 21 | .info-1 22 | .info-item-2 23 | -webkit-line-clamp: 1 24 | 25 | .info-2 26 | .info-item-1 27 | -webkit-line-clamp: 2 28 | 29 | &.pagination-post 30 | overflow: hidden 31 | margin-top: 40px 32 | width: 100% 33 | addBorderRadius() 34 | 35 | .layout 36 | .pagination 37 | & > * 38 | display: inline-block 39 | margin: 0 6px 40 | width: w = 2.5em 41 | height: w 42 | line-height: w 43 | 44 | & > *:not(.space) 45 | @extend .cardHover 46 | 47 | &:hover 48 | background: var(--btn-hover-color) 49 | color: var(--btn-color) 50 | 51 | #archive 52 | .pagination 53 | margin-top: 30px 54 | 55 | & > *:not(.space) 56 | box-shadow: none 57 | 58 | .pagination-related 59 | position: relative 60 | display: inline-block 61 | overflow: hidden 62 | background: $dark-black 63 | vertical-align: bottom 64 | @extend .postImgHover 65 | 66 | &.next-post 67 | .info 68 | text-align: right 69 | 70 | .info 71 | .info-1, 72 | .info-2 73 | @extend .verticalCenter 74 | padding: 20px 40px 75 | color: var(--white) 76 | transition: transform .3s, opacity .3s 77 | 78 | .info-1 79 | .info-item-1 80 | color: var(--light-grey) 81 | text-transform: uppercase 82 | font-size: 90% 83 | 84 | .info-item-2 85 | @extend .limit-more-line 86 | color: var(--white) 87 | font-weight: 500 88 | 89 | .info-2 90 | opacity: 0 91 | transform: translate(0, 0) 92 | 93 | .info-item-1 94 | @extend .limit-more-line 95 | 96 | &:not(.no-desc):hover 97 | .info-1 98 | opacity: 0 99 | transform: translate(0, -100%) 100 | 101 | .info-2 102 | opacity: 1 103 | transform: translate(0, -50%) -------------------------------------------------------------------------------- /layout/includes/third-party/comments/disqusjs.pug: -------------------------------------------------------------------------------- 1 | - let disqusjsPageTitle = page.title.replace(/'/ig,"\\'") 2 | - const { shortname:dqShortname, apikey:dqApikey, option:dqOption } = theme.disqusjs 3 | 4 | script. 5 | (() => { 6 | const initDisqusjs = () => { 7 | window.disqusjs = null 8 | disqusjs = new DisqusJS(Object.assign({ 9 | shortname: '!{dqShortname}', 10 | identifier: '!{ url_for(page.path) }', 11 | url: '!{ page.permalink }', 12 | title: '!{ disqusjsPageTitle }', 13 | apikey: '!{dqApikey}', 14 | },!{JSON.stringify(dqOption)})) 15 | 16 | disqusjs.render(document.getElementById('disqusjs-wrap')) 17 | } 18 | 19 | const themeChange = () => { 20 | const ele = document.getElementById('disqus_thread') 21 | if(!ele) return 22 | disqusjs.destroy() 23 | initDisqusjs() 24 | } 25 | 26 | btf.addGlobalFn('themeChange', themeChange, 'disqusjs') 27 | 28 | const loadDisqusjs = async() => { 29 | if (window.disqusJsLoad) initDisqusjs() 30 | else { 31 | await btf.getCSS('!{url_for(theme.asset.disqusjs_css)}') 32 | await btf.getScript('!{url_for(theme.asset.disqusjs)}') 33 | initDisqusjs() 34 | window.disqusJsLoad = true 35 | } 36 | } 37 | 38 | const getCount = async() => { 39 | try { 40 | const eleGroup = document.querySelector('#post-meta .disqusjs-comment-count') 41 | if (!eleGroup) return 42 | const cleanedLinks = eleGroup.href.replace(/#post-comment$/, '') 43 | 44 | const res = await fetch(`https://disqus.com/api/3.0/threads/set.json?forum=!{dqShortname}&api_key=!{dqApikey}&thread:link=${cleanedLinks}`,{ 45 | method: 'GET' 46 | }) 47 | const result = await res.json() 48 | const count = result.response.length ? result.response[0].posts : 0 49 | eleGroup.textContent = count 50 | } catch (err) { 51 | console.error(err) 52 | } 53 | } 54 | 55 | if ('!{theme.comments.use[0]}' === 'Disqusjs' || !!{theme.comments.lazyload}) { 56 | if (!{theme.comments.lazyload}) btf.loadComment(document.getElementById('disqusjs-wrap'), loadDisqusjs) 57 | else { 58 | loadDisqusjs() 59 | !{ theme.comments.count ? 'GLOBAL_CONFIG_SITE.isPost && getCount()' : '' } 60 | } 61 | } else { 62 | window.loadOtherComment = loadDisqusjs 63 | } 64 | })() -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: Bug report 2 | description: Create a report to help us improve 3 | title: '[Bug]: ' 4 | 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | 重要:請依照該模板來提交 10 | Important: Please follow the template to create a new issue 11 | 12 | - type: input 13 | id: butterfly-ver 14 | attributes: 15 | label: 使用的 Butterfly 版本? | What version of Butterfly are you using? 16 | description: 檢視主題的 package.json | Check the theme's package.json 17 | validations: 18 | required: true 19 | 20 | - type: dropdown 21 | id: modify 22 | attributes: 23 | label: 是否修改過主題文件? | Has the theme files been modified? 24 | options: 25 | - 是 (Yes) 26 | - 否 (No) 27 | validations: 28 | required: true 29 | 30 | - type: dropdown 31 | id: browser 32 | attributes: 33 | label: 使用的瀏覽器? | What browser are you using? 34 | options: 35 | - Chrome 36 | - Edge 37 | - Safari 38 | - Opera 39 | - Other 40 | validations: 41 | required: true 42 | 43 | - type: dropdown 44 | id: platform 45 | attributes: 46 | label: 使用的系統? | What operating system are you using? 47 | options: 48 | - Windows 49 | - macOS 50 | - Linux 51 | - Android 52 | - iOS 53 | - Other 54 | validations: 55 | required: true 56 | 57 | - type: textarea 58 | id: dependencies 59 | attributes: 60 | label: 依賴插件 | Package dependencies information 61 | description: 在 Hexo 根目錄下執行 `npm ls --depth 0` | Run `npm ls --depth 0` in Hexo root directory 62 | render: Text 63 | validations: 64 | required: true 65 | 66 | - type: textarea 67 | id: description 68 | attributes: 69 | label: 問題描述 | Describe the bug 70 | description: 請描述你的問題現象 | A clear and concise description of what the bug is. 71 | placeholder: 請儘量提供截圖來定位問題 | If applicable, add screenshots to help explain your problem 72 | value: 73 | validations: 74 | required: true 75 | 76 | - type: input 77 | id: website 78 | attributes: 79 | label: 出現問題的網站 | Website with the issue 80 | description: 請提供可復現問題的網站地址 | Please provide a website URL where the problem can be reproduced. 81 | placeholder: 請填寫具體的網址,不要填寫 localhost 網站 | Please provide a specific URL, do not use localhost. 82 | validations: 83 | required: true -------------------------------------------------------------------------------- /layout/includes/additional-js.pug: -------------------------------------------------------------------------------- 1 | div 2 | script(src=url_for(theme.asset.utils)) 3 | script(src=url_for(theme.asset.main)) 4 | //- script(src=url_for('/js/eurkon/rgbaster.min.js')) 5 | script(src=url_for('/js/eurkon/color-thief.min.js')) 6 | script(defer src=url_for('/js/eurkon/eurkon.js')) 7 | script(defer data-pjax src=url_for('/js/eurkon/refresh.js')) 8 | 9 | if theme.translate.enable 10 | script(src=url_for(theme.asset.translate)) 11 | 12 | if theme.lightbox 13 | script(src=url_for(theme.asset[theme.lightbox])) 14 | 15 | if theme.instantpage 16 | script(src=url_for(theme.asset.instantpage), type='module') 17 | 18 | if theme.lazyload.enable 19 | script(src=url_for(theme.asset.lazyload)) 20 | 21 | if theme.snackbar.enable 22 | script(src=url_for(theme.asset.snackbar)) 23 | 24 | if theme.pangu.enable 25 | != partial("includes/third-party/pangu.pug", {}, { cache: true }) 26 | 27 | .js-pjax 28 | if needLoadCountJs 29 | != partial("includes/third-party/card-post-count/index", {}, { cache: true }) 30 | 31 | if loadSubJs 32 | include ./third-party/subtitle.pug 33 | 34 | include ./third-party/math/index.pug 35 | include ./third-party/abcjs/index.pug 36 | 37 | if commentsJsLoad 38 | include ./third-party/comments/js.pug 39 | 40 | != partial("includes/third-party/prismjs", {}, { cache: true }) 41 | 42 | if theme.aside.enable && theme.aside.card_newest_comments.enable 43 | if theme.pjax.enable || (!is_post() && page.aside !== false) 44 | != partial("includes/third-party/newest-comments/index", {}, { cache: true }) 45 | 46 | != fragment_cache('injectBottom', function(){return injectHtml(theme.inject.bottom)}) 47 | 48 | != partial("includes/third-party/effect", {}, { cache: true }) 49 | != partial("includes/third-party/chat/index", {}, { cache: true }) 50 | 51 | if theme.aplayerInject && theme.aplayerInject.enable 52 | if theme.pjax.enable || theme.aplayerInject.per_page || page.aplayer 53 | include ./third-party/aplayer.pug 54 | 55 | if theme.pjax.enable 56 | != partial("includes/third-party/pjax", {}, { cache: true }) 57 | 58 | if theme.umami_analytics.enable 59 | != partial("includes/third-party/umami_analytics", {}, { cache: true }) 60 | 61 | if theme.busuanzi.site_uv || theme.busuanzi.site_pv || theme.busuanzi.page_pv 62 | script(async data-pjax src= theme.asset.busuanzi || '//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js') 63 | 64 | != partial('includes/third-party/search/index', {}, { cache: true }) -------------------------------------------------------------------------------- /layout/includes/third-party/pjax.pug: -------------------------------------------------------------------------------- 1 | - var pjaxExclude = 'a:not([target="_blank"])' 2 | if theme.pjax.exclude 3 | each val in theme.pjax.exclude 4 | - pjaxExclude += `:not([href="${val}"])` 5 | 6 | - let pjaxSelectors = ['head > title', '#config-diff', '#body-wrap', '#rightside-config-hide', '#rightside-config-show', '.js-pjax'] 7 | 8 | - let choose = theme.comments.use 9 | if choose 10 | if theme.Open_Graph_meta.enable && (choose.includes('Livere') || choose.includes('Utterances') || choose.includes('Giscus')) 11 | - pjaxSelectors.unshift('meta[property="og:image"]', 'meta[property="og:title"]', 'meta[property="og:url"]') 12 | if choose.includes('Utterances') || choose.includes('Giscus') 13 | - pjaxSelectors.unshift('link[rel="canonical"]') 14 | 15 | script(src=url_for(theme.asset.pjax)) 16 | script. 17 | (() => { 18 | const pjaxSelectors = !{JSON.stringify(pjaxSelectors)} 19 | 20 | window.pjax = new Pjax({ 21 | elements: '!{pjaxExclude}', 22 | selectors: pjaxSelectors, 23 | cacheBust: false, 24 | analytics: !{theme.google_analytics ? true : false}, 25 | scrollRestoration: false 26 | }) 27 | 28 | const triggerPjaxFn = (val) => { 29 | if (!val) return 30 | Object.values(val).forEach(fn => fn()) 31 | } 32 | 33 | document.addEventListener('pjax:send', () => { 34 | // removeEventListener 35 | btf.removeGlobalFnEvent('pjaxSendOnce') 36 | btf.removeGlobalFnEvent('themeChange') 37 | 38 | // reset readmode 39 | const $bodyClassList = document.body.classList 40 | if ($bodyClassList.contains('read-mode')) $bodyClassList.remove('read-mode') 41 | 42 | triggerPjaxFn(window.globalFn.pjaxSend) 43 | }) 44 | 45 | document.addEventListener('pjax:complete', () => { 46 | btf.removeGlobalFnEvent('pjaxCompleteOnce') 47 | document.querySelectorAll('script[data-pjax]').forEach(item => { 48 | const newScript = document.createElement('script') 49 | const content = item.text || item.textContent || item.innerHTML || "" 50 | Array.from(item.attributes).forEach(attr => newScript.setAttribute(attr.name, attr.value)) 51 | newScript.appendChild(document.createTextNode(content)) 52 | item.parentNode.replaceChild(newScript, item) 53 | }) 54 | 55 | triggerPjaxFn(window.globalFn.pjaxComplete) 56 | }) 57 | 58 | document.addEventListener('pjax:error', e => { 59 | if (e.request.status === 404) { 60 | pjax.loadUrl('!{url_for("/404.html")}') 61 | } 62 | }) 63 | })() -------------------------------------------------------------------------------- /layout/includes/post/post-copyright.pug: -------------------------------------------------------------------------------- 1 | //- 源代码 2 | //- if theme.post_copyright.enable && page.copyright !== false 3 | //- - const author = page.copyright_author || config.author 4 | //- - const authorHref = page.copyright_author_href || theme.post_copyright.author_href || config.url 5 | //- - const url = page.copyright_url || page.permalink 6 | //- - const info = page.copyright_info || _p('post.copyright.copyright_content', theme.post_copyright.license_url, theme.post_copyright.license, config.url, config.title) 7 | //- .post-copyright 8 | //- .post-copyright__author 9 | //- span.post-copyright-meta 10 | //- i.fas.fa-circle-user.fa-fw 11 | //- = _p('post.copyright.author') + ": " 12 | //- span.post-copyright-info 13 | //- a(href=authorHref)= author 14 | //- .post-copyright__type 15 | //- span.post-copyright-meta 16 | //- i.fas.fa-square-arrow-up-right.fa-fw 17 | //- = _p('post.copyright.link') + ": " 18 | //- span.post-copyright-info 19 | //- a(href=url_for(url))= theme.post_copyright.decode ? decodeURI(url) : url 20 | //- .post-copyright__notice 21 | //- span.post-copyright-meta 22 | //- i.fas.fa-circle-exclamation.fa-fw 23 | //- = _p('post.copyright.copyright_notice') + ": " 24 | //- span.post-copyright-info!= info 25 | 26 | if theme.post_copyright.enable && page.copyright !== false 27 | - let author = page.copyright_author || config.author 28 | - let authorHref = page.copyright_author_href || theme.post_copyright.author_href || config.url 29 | - let url = page.copyright_url || page.permalink 30 | - let info = page.copyright_info || _p('post.copyright.copyright_content', theme.post_copyright.license_url, theme.post_copyright.license, config.url, config.title) 31 | - let license = page.license ? page.license : theme.post_copyright.license 32 | - let license_url = page.license_url ? page.license_url : theme.post_copyright.license_url 33 | .post-copyright 34 | .post-copyright__title 35 | span.post-copyright-info #[=page.title] 36 | .post-copyright__url 37 | span.post-copyright-info 38 | a#post-url(href=url_for(url))= theme.post_copyright.decode ? decodeURI(url) : url 39 | a#post-url-copy(title='复制文章链接' onclick='btf.copyFn("' + url + '")') 40 | i.fas.fa-paste.copy-button 41 | .post-copyright__cc 42 | span.post-copyright-info!= '转载前请阅读本站 版权协议,文章著作权归 ' + author + ' 所有,转载请注明出处。' --------------------------------------------------------------------------------