From 9f5a885f4bc604254e4f60a545b59c75e2993dad Mon Sep 17 00:00:00 2001 From: Linloir <3145078758@qq.com> Date: Thu, 10 Oct 2024 00:06:54 +0800 Subject: [PATCH] feat: upload butterfly theme --- .gitea/workflows/deploy.yml | 12 +- themes/butterfly | 1 - themes/butterfly/.github/FUNDING.yml | 13 + .../.github/ISSUE_TEMPLATE/bug_report.yml | 83 ++ .../.github/ISSUE_TEMPLATE/config.yml | 18 + .../ISSUE_TEMPLATE/feature_request.yml | 14 + .../butterfly/.github/workflows/publish.yml | 19 + themes/butterfly/.github/workflows/stale.yml | 19 + themes/butterfly/LICENSE | 202 +++ themes/butterfly/README.md | 116 ++ themes/butterfly/README_CN.md | 116 ++ themes/butterfly/_config.yml | 1094 +++++++++++++++++ themes/butterfly/languages/default.yml | 121 ++ themes/butterfly/languages/en.yml | 121 ++ themes/butterfly/languages/ja.yml | 121 ++ themes/butterfly/languages/ko.yml | 121 ++ themes/butterfly/languages/zh-CN.yml | 122 ++ themes/butterfly/languages/zh-HK.yml | 121 ++ themes/butterfly/languages/zh-TW.yml | 121 ++ themes/butterfly/layout/archive.pug | 8 + themes/butterfly/layout/category.pug | 12 + .../layout/includes/additional-js.pug | 60 + themes/butterfly/layout/includes/footer.pug | 18 + themes/butterfly/layout/includes/head.pug | 68 + .../layout/includes/head/Open_Graph.pug | 16 + .../layout/includes/head/analytics.pug | 34 + .../butterfly/layout/includes/head/config.pug | 137 +++ .../layout/includes/head/config_site.pug | 27 + .../layout/includes/head/google_adsense.pug | 9 + .../layout/includes/head/preconnect.pug | 35 + themes/butterfly/layout/includes/head/pwa.pug | 13 + .../includes/head/site_verification.pug | 3 + .../layout/includes/header/index.pug | 53 + .../layout/includes/header/menu_item.pug | 27 + .../butterfly/layout/includes/header/nav.pug | 22 + .../layout/includes/header/post-info.pug | 158 +++ .../layout/includes/header/social.pug | 8 + themes/butterfly/layout/includes/layout.pug | 36 + .../includes/loading/fullpage-loading.pug | 33 + .../layout/includes/loading/index.pug | 5 + .../layout/includes/loading/pace.pug | 12 + .../layout/includes/mixins/article-sort.pug | 23 + .../layout/includes/mixins/indexPostUI.pug | 122 ++ themes/butterfly/layout/includes/page/404.pug | 8 + .../layout/includes/page/categories.pug | 1 + .../layout/includes/page/default-page.pug | 2 + .../butterfly/layout/includes/page/flink.pug | 82 ++ .../layout/includes/page/shuoshuo.pug | 103 ++ .../butterfly/layout/includes/page/tags.pug | 2 + .../butterfly/layout/includes/pagination.pug | 39 + .../layout/includes/post/post-copyright.pug | 23 + .../butterfly/layout/includes/post/reward.pug | 12 + .../butterfly/layout/includes/rightside.pug | 61 + themes/butterfly/layout/includes/sidebar.pug | 18 + .../includes/third-party/abcjs/abcjs.pug | 17 + .../includes/third-party/abcjs/index.pug | 6 + .../layout/includes/third-party/aplayer.pug | 23 + .../third-party/card-post-count/artalk.pug | 31 + .../third-party/card-post-count/disqus.pug | 25 + .../third-party/card-post-count/fb.pug | 18 + .../third-party/card-post-count/index.pug | 16 + .../third-party/card-post-count/remark42.pug | 18 + .../third-party/card-post-count/twikoo.pug | 37 + .../third-party/card-post-count/valine.pug | 20 + .../third-party/card-post-count/waline.pug | 21 + .../includes/third-party/chat/chatra.pug | 42 + .../includes/third-party/chat/crisp.pug | 37 + .../includes/third-party/chat/daovoice.pug | 40 + .../includes/third-party/chat/index.pug | 9 + .../includes/third-party/chat/tidio.pug | 41 + .../includes/third-party/comments/artalk.pug | 55 + .../includes/third-party/comments/disqus.pug | 59 + .../third-party/comments/disqusjs.pug | 64 + .../comments/facebook_comments.pug | 46 + .../includes/third-party/comments/giscus.pug | 52 + .../includes/third-party/comments/gitalk.pug | 44 + .../includes/third-party/comments/index.pug | 46 + .../includes/third-party/comments/js.pug | 26 + .../includes/third-party/comments/livere.pug | 25 + .../third-party/comments/remark42.pug | 68 + .../includes/third-party/comments/twikoo.pug | 45 + .../third-party/comments/utterances.pug | 47 + .../includes/third-party/comments/valine.pug | 38 + .../includes/third-party/comments/waline.pug | 45 + .../layout/includes/third-party/effect.pug | 35 + .../includes/third-party/math/index.pug | 11 + .../includes/third-party/math/katex.pug | 16 + .../includes/third-party/math/mathjax.pug | 47 + .../includes/third-party/math/mermaid.pug | 51 + .../third-party/newest-comments/artalk.pug | 64 + .../third-party/newest-comments/common.pug | 60 + .../newest-comments/disqus-comment.pug | 34 + .../newest-comments/github-issues.pug | 62 + .../third-party/newest-comments/index.pug | 30 + .../third-party/newest-comments/remark42.pug | 30 + .../newest-comments/twikoo-comment.pug | 45 + .../third-party/newest-comments/valine.pug | 51 + .../third-party/newest-comments/waline.pug | 32 + .../layout/includes/third-party/pangu.pug | 23 + .../layout/includes/third-party/pjax.pug | 63 + .../layout/includes/third-party/prismjs.pug | 23 + .../includes/third-party/search/algolia.pug | 22 + .../includes/third-party/search/docsearch.pug | 29 + .../includes/third-party/search/index.pug | 7 + .../third-party/search/local-search.pug | 22 + .../includes/third-party/share/addtoany.pug | 10 + .../includes/third-party/share/index.pug | 9 + .../includes/third-party/share/share-js.pug | 4 + .../layout/includes/third-party/subtitle.pug | 93 ++ .../includes/third-party/umami_analytics.pug | 65 + .../layout/includes/widget/card_ad.pug | 3 + .../includes/widget/card_announcement.pug | 6 + .../layout/includes/widget/card_archives.pug | 7 + .../layout/includes/widget/card_author.pug | 26 + .../includes/widget/card_bottom_self.pug | 9 + .../includes/widget/card_categories.pug | 4 + .../includes/widget/card_newest_comment.pug | 7 + .../includes/widget/card_post_series.pug | 21 + .../layout/includes/widget/card_post_toc.pug | 15 + .../includes/widget/card_recent_post.pug | 27 + .../layout/includes/widget/card_tags.pug | 14 + .../layout/includes/widget/card_top_self.pug | 8 + .../layout/includes/widget/card_webinfo.pug | 45 + .../layout/includes/widget/index.pug | 36 + themes/butterfly/layout/index.pug | 5 + themes/butterfly/layout/page.pug | 32 + themes/butterfly/layout/post.pug | 32 + themes/butterfly/layout/tag.pug | 12 + themes/butterfly/package.json | 32 + themes/butterfly/plugins.yml | 211 ++++ themes/butterfly/scripts/events/404.js | 20 + themes/butterfly/scripts/events/cdn.js | 97 ++ themes/butterfly/scripts/events/comment.js | 17 + themes/butterfly/scripts/events/init.js | 20 + .../butterfly/scripts/events/merge_config.js | 573 +++++++++ themes/butterfly/scripts/events/stylus.js | 24 + themes/butterfly/scripts/events/welcome.js | 13 + .../scripts/filters/post_lazyload.js | 27 + .../butterfly/scripts/filters/random_cover.js | 40 + .../scripts/helpers/aside_archives.js | 77 ++ .../scripts/helpers/aside_categories.js | 81 ++ .../scripts/helpers/getArchiveLength.js | 45 + .../scripts/helpers/inject_head_js.js | 156 +++ themes/butterfly/scripts/helpers/page.js | 94 ++ .../butterfly/scripts/helpers/related_post.js | 85 ++ themes/butterfly/scripts/helpers/series.js | 22 + themes/butterfly/scripts/tag/button.js | 21 + themes/butterfly/scripts/tag/flink.js | 34 + themes/butterfly/scripts/tag/gallery.js | 64 + themes/butterfly/scripts/tag/hide.js | 65 + themes/butterfly/scripts/tag/inlineImg.js | 19 + themes/butterfly/scripts/tag/label.js | 14 + themes/butterfly/scripts/tag/mermaid.js | 17 + themes/butterfly/scripts/tag/note.js | 27 + themes/butterfly/scripts/tag/score.js | 22 + themes/butterfly/scripts/tag/series.js | 63 + themes/butterfly/scripts/tag/tabs.js | 51 + themes/butterfly/scripts/tag/timeline.js | 41 + .../source/css/_global/function.styl | 280 +++++ .../butterfly/source/css/_global/index.styl | 227 ++++ .../source/css/_highlight/highlight.styl | 281 +++++ .../source/css/_highlight/highlight/diff.styl | 81 ++ .../css/_highlight/highlight/index.styl | 39 + .../source/css/_highlight/prismjs/diff.styl | 302 +++++ .../source/css/_highlight/prismjs/index.styl | 24 + .../css/_highlight/prismjs/line-number.styl | 42 + .../source/css/_highlight/theme.styl | 121 ++ .../butterfly/source/css/_layout/aside.styl | 423 +++++++ themes/butterfly/source/css/_layout/chat.styl | 9 + .../source/css/_layout/comments.styl | 81 ++ .../butterfly/source/css/_layout/footer.styl | 35 + themes/butterfly/source/css/_layout/head.styl | 436 +++++++ .../butterfly/source/css/_layout/loading.styl | 95 ++ .../source/css/_layout/pagination.styl | 77 ++ themes/butterfly/source/css/_layout/post.styl | 261 ++++ .../source/css/_layout/relatedposts.styl | 41 + .../butterfly/source/css/_layout/reward.styl | 78 ++ .../source/css/_layout/rightside.styl | 72 ++ .../butterfly/source/css/_layout/sidebar.styl | 78 ++ .../source/css/_layout/third-party.styl | 161 +++ .../butterfly/source/css/_mode/darkmode.styl | 160 +++ .../butterfly/source/css/_mode/readmode.styl | 185 +++ themes/butterfly/source/css/_page/404.styl | 66 + .../butterfly/source/css/_page/archives.styl | 115 ++ .../source/css/_page/categories.styl | 37 + themes/butterfly/source/css/_page/common.styl | 60 + themes/butterfly/source/css/_page/flink.styl | 87 ++ .../butterfly/source/css/_page/homepage.styl | 174 +++ .../butterfly/source/css/_page/shuoshuo.styl | 53 + themes/butterfly/source/css/_page/tags.styl | 27 + .../butterfly/source/css/_search/algolia.styl | 92 ++ .../butterfly/source/css/_search/index.styl | 59 + .../source/css/_search/local-search.styl | 69 ++ themes/butterfly/source/css/_tags/button.styl | 56 + .../butterfly/source/css/_tags/gallery.styl | 218 ++++ themes/butterfly/source/css/_tags/hexo.styl | 30 + themes/butterfly/source/css/_tags/hide.styl | 48 + .../butterfly/source/css/_tags/inlineImg.styl | 6 + themes/butterfly/source/css/_tags/label.styl | 11 + themes/butterfly/source/css/_tags/note.styl | 124 ++ themes/butterfly/source/css/_tags/series.styl | 5 + themes/butterfly/source/css/_tags/tabs.styl | 77 ++ .../butterfly/source/css/_tags/timeline.styl | 68 + .../source/css/_third-party/normalize.min.css | 180 +++ themes/butterfly/source/css/index.styl | 15 + themes/butterfly/source/css/var.styl | 186 +++ themes/butterfly/source/img/404.jpg | Bin 0 -> 16393 bytes .../butterfly/source/img/butterfly-icon.png | Bin 0 -> 275383 bytes themes/butterfly/source/img/error-page.png | Bin 0 -> 35850 bytes themes/butterfly/source/img/favicon.ico | Bin 0 -> 15406 bytes themes/butterfly/source/img/friend_404.gif | Bin 0 -> 65097 bytes themes/butterfly/source/js/main.js | 943 ++++++++++++++ themes/butterfly/source/js/search/algolia.js | 173 +++ .../source/js/search/local-search.js | 360 ++++++ themes/butterfly/source/js/tw_cn.js | 117 ++ themes/butterfly/source/js/utils.js | 297 +++++ 216 files changed, 15557 insertions(+), 10 deletions(-) delete mode 160000 themes/butterfly create mode 100644 themes/butterfly/.github/FUNDING.yml create mode 100644 themes/butterfly/.github/ISSUE_TEMPLATE/bug_report.yml create mode 100644 themes/butterfly/.github/ISSUE_TEMPLATE/config.yml create mode 100644 themes/butterfly/.github/ISSUE_TEMPLATE/feature_request.yml create mode 100644 themes/butterfly/.github/workflows/publish.yml create mode 100644 themes/butterfly/.github/workflows/stale.yml create mode 100644 themes/butterfly/LICENSE create mode 100644 themes/butterfly/README.md create mode 100644 themes/butterfly/README_CN.md create mode 100644 themes/butterfly/_config.yml create mode 100644 themes/butterfly/languages/default.yml create mode 100644 themes/butterfly/languages/en.yml create mode 100644 themes/butterfly/languages/ja.yml create mode 100644 themes/butterfly/languages/ko.yml create mode 100644 themes/butterfly/languages/zh-CN.yml create mode 100644 themes/butterfly/languages/zh-HK.yml create mode 100644 themes/butterfly/languages/zh-TW.yml create mode 100644 themes/butterfly/layout/archive.pug create mode 100644 themes/butterfly/layout/category.pug create mode 100644 themes/butterfly/layout/includes/additional-js.pug create mode 100644 themes/butterfly/layout/includes/footer.pug create mode 100644 themes/butterfly/layout/includes/head.pug create mode 100644 themes/butterfly/layout/includes/head/Open_Graph.pug create mode 100644 themes/butterfly/layout/includes/head/analytics.pug create mode 100644 themes/butterfly/layout/includes/head/config.pug create mode 100644 themes/butterfly/layout/includes/head/config_site.pug create mode 100644 themes/butterfly/layout/includes/head/google_adsense.pug create mode 100644 themes/butterfly/layout/includes/head/preconnect.pug create mode 100644 themes/butterfly/layout/includes/head/pwa.pug create mode 100644 themes/butterfly/layout/includes/head/site_verification.pug create mode 100644 themes/butterfly/layout/includes/header/index.pug create mode 100644 themes/butterfly/layout/includes/header/menu_item.pug create mode 100644 themes/butterfly/layout/includes/header/nav.pug create mode 100644 themes/butterfly/layout/includes/header/post-info.pug create mode 100644 themes/butterfly/layout/includes/header/social.pug create mode 100644 themes/butterfly/layout/includes/layout.pug create mode 100644 themes/butterfly/layout/includes/loading/fullpage-loading.pug create mode 100644 themes/butterfly/layout/includes/loading/index.pug create mode 100644 themes/butterfly/layout/includes/loading/pace.pug create mode 100644 themes/butterfly/layout/includes/mixins/article-sort.pug create mode 100644 themes/butterfly/layout/includes/mixins/indexPostUI.pug create mode 100644 themes/butterfly/layout/includes/page/404.pug create mode 100644 themes/butterfly/layout/includes/page/categories.pug create mode 100644 themes/butterfly/layout/includes/page/default-page.pug create mode 100644 themes/butterfly/layout/includes/page/flink.pug create mode 100644 themes/butterfly/layout/includes/page/shuoshuo.pug create mode 100644 themes/butterfly/layout/includes/page/tags.pug create mode 100644 themes/butterfly/layout/includes/pagination.pug create mode 100644 themes/butterfly/layout/includes/post/post-copyright.pug create mode 100644 themes/butterfly/layout/includes/post/reward.pug create mode 100644 themes/butterfly/layout/includes/rightside.pug create mode 100644 themes/butterfly/layout/includes/sidebar.pug create mode 100644 themes/butterfly/layout/includes/third-party/abcjs/abcjs.pug create mode 100644 themes/butterfly/layout/includes/third-party/abcjs/index.pug create mode 100644 themes/butterfly/layout/includes/third-party/aplayer.pug create mode 100644 themes/butterfly/layout/includes/third-party/card-post-count/artalk.pug create mode 100644 themes/butterfly/layout/includes/third-party/card-post-count/disqus.pug create mode 100644 themes/butterfly/layout/includes/third-party/card-post-count/fb.pug create mode 100644 themes/butterfly/layout/includes/third-party/card-post-count/index.pug create mode 100644 themes/butterfly/layout/includes/third-party/card-post-count/remark42.pug create mode 100644 themes/butterfly/layout/includes/third-party/card-post-count/twikoo.pug create mode 100644 themes/butterfly/layout/includes/third-party/card-post-count/valine.pug create mode 100644 themes/butterfly/layout/includes/third-party/card-post-count/waline.pug create mode 100644 themes/butterfly/layout/includes/third-party/chat/chatra.pug create mode 100644 themes/butterfly/layout/includes/third-party/chat/crisp.pug create mode 100644 themes/butterfly/layout/includes/third-party/chat/daovoice.pug create mode 100644 themes/butterfly/layout/includes/third-party/chat/index.pug create mode 100644 themes/butterfly/layout/includes/third-party/chat/tidio.pug create mode 100644 themes/butterfly/layout/includes/third-party/comments/artalk.pug create mode 100644 themes/butterfly/layout/includes/third-party/comments/disqus.pug create mode 100644 themes/butterfly/layout/includes/third-party/comments/disqusjs.pug create mode 100644 themes/butterfly/layout/includes/third-party/comments/facebook_comments.pug create mode 100644 themes/butterfly/layout/includes/third-party/comments/giscus.pug create mode 100644 themes/butterfly/layout/includes/third-party/comments/gitalk.pug create mode 100644 themes/butterfly/layout/includes/third-party/comments/index.pug create mode 100644 themes/butterfly/layout/includes/third-party/comments/js.pug create mode 100644 themes/butterfly/layout/includes/third-party/comments/livere.pug create mode 100644 themes/butterfly/layout/includes/third-party/comments/remark42.pug create mode 100644 themes/butterfly/layout/includes/third-party/comments/twikoo.pug create mode 100644 themes/butterfly/layout/includes/third-party/comments/utterances.pug create mode 100644 themes/butterfly/layout/includes/third-party/comments/valine.pug create mode 100644 themes/butterfly/layout/includes/third-party/comments/waline.pug create mode 100644 themes/butterfly/layout/includes/third-party/effect.pug create mode 100644 themes/butterfly/layout/includes/third-party/math/index.pug create mode 100644 themes/butterfly/layout/includes/third-party/math/katex.pug create mode 100644 themes/butterfly/layout/includes/third-party/math/mathjax.pug create mode 100644 themes/butterfly/layout/includes/third-party/math/mermaid.pug create mode 100644 themes/butterfly/layout/includes/third-party/newest-comments/artalk.pug create mode 100644 themes/butterfly/layout/includes/third-party/newest-comments/common.pug create mode 100644 themes/butterfly/layout/includes/third-party/newest-comments/disqus-comment.pug create mode 100644 themes/butterfly/layout/includes/third-party/newest-comments/github-issues.pug create mode 100644 themes/butterfly/layout/includes/third-party/newest-comments/index.pug create mode 100644 themes/butterfly/layout/includes/third-party/newest-comments/remark42.pug create mode 100644 themes/butterfly/layout/includes/third-party/newest-comments/twikoo-comment.pug create mode 100644 themes/butterfly/layout/includes/third-party/newest-comments/valine.pug create mode 100644 themes/butterfly/layout/includes/third-party/newest-comments/waline.pug create mode 100644 themes/butterfly/layout/includes/third-party/pangu.pug create mode 100644 themes/butterfly/layout/includes/third-party/pjax.pug create mode 100644 themes/butterfly/layout/includes/third-party/prismjs.pug create mode 100644 themes/butterfly/layout/includes/third-party/search/algolia.pug create mode 100644 themes/butterfly/layout/includes/third-party/search/docsearch.pug create mode 100644 themes/butterfly/layout/includes/third-party/search/index.pug create mode 100644 themes/butterfly/layout/includes/third-party/search/local-search.pug create mode 100644 themes/butterfly/layout/includes/third-party/share/addtoany.pug create mode 100644 themes/butterfly/layout/includes/third-party/share/index.pug create mode 100644 themes/butterfly/layout/includes/third-party/share/share-js.pug create mode 100644 themes/butterfly/layout/includes/third-party/subtitle.pug create mode 100644 themes/butterfly/layout/includes/third-party/umami_analytics.pug create mode 100644 themes/butterfly/layout/includes/widget/card_ad.pug create mode 100644 themes/butterfly/layout/includes/widget/card_announcement.pug create mode 100644 themes/butterfly/layout/includes/widget/card_archives.pug create mode 100644 themes/butterfly/layout/includes/widget/card_author.pug create mode 100644 themes/butterfly/layout/includes/widget/card_bottom_self.pug create mode 100644 themes/butterfly/layout/includes/widget/card_categories.pug create mode 100644 themes/butterfly/layout/includes/widget/card_newest_comment.pug create mode 100644 themes/butterfly/layout/includes/widget/card_post_series.pug create mode 100644 themes/butterfly/layout/includes/widget/card_post_toc.pug create mode 100644 themes/butterfly/layout/includes/widget/card_recent_post.pug create mode 100644 themes/butterfly/layout/includes/widget/card_tags.pug create mode 100644 themes/butterfly/layout/includes/widget/card_top_self.pug create mode 100644 themes/butterfly/layout/includes/widget/card_webinfo.pug create mode 100644 themes/butterfly/layout/includes/widget/index.pug create mode 100644 themes/butterfly/layout/index.pug create mode 100644 themes/butterfly/layout/page.pug create mode 100644 themes/butterfly/layout/post.pug create mode 100644 themes/butterfly/layout/tag.pug create mode 100644 themes/butterfly/package.json create mode 100644 themes/butterfly/plugins.yml create mode 100644 themes/butterfly/scripts/events/404.js create mode 100644 themes/butterfly/scripts/events/cdn.js create mode 100644 themes/butterfly/scripts/events/comment.js create mode 100644 themes/butterfly/scripts/events/init.js create mode 100644 themes/butterfly/scripts/events/merge_config.js create mode 100644 themes/butterfly/scripts/events/stylus.js create mode 100644 themes/butterfly/scripts/events/welcome.js create mode 100644 themes/butterfly/scripts/filters/post_lazyload.js create mode 100644 themes/butterfly/scripts/filters/random_cover.js create mode 100644 themes/butterfly/scripts/helpers/aside_archives.js create mode 100644 themes/butterfly/scripts/helpers/aside_categories.js create mode 100644 themes/butterfly/scripts/helpers/getArchiveLength.js create mode 100644 themes/butterfly/scripts/helpers/inject_head_js.js create mode 100644 themes/butterfly/scripts/helpers/page.js create mode 100644 themes/butterfly/scripts/helpers/related_post.js create mode 100644 themes/butterfly/scripts/helpers/series.js create mode 100644 themes/butterfly/scripts/tag/button.js create mode 100644 themes/butterfly/scripts/tag/flink.js create mode 100644 themes/butterfly/scripts/tag/gallery.js create mode 100644 themes/butterfly/scripts/tag/hide.js create mode 100644 themes/butterfly/scripts/tag/inlineImg.js create mode 100644 themes/butterfly/scripts/tag/label.js create mode 100644 themes/butterfly/scripts/tag/mermaid.js create mode 100644 themes/butterfly/scripts/tag/note.js create mode 100644 themes/butterfly/scripts/tag/score.js create mode 100644 themes/butterfly/scripts/tag/series.js create mode 100644 themes/butterfly/scripts/tag/tabs.js create mode 100644 themes/butterfly/scripts/tag/timeline.js create mode 100644 themes/butterfly/source/css/_global/function.styl create mode 100644 themes/butterfly/source/css/_global/index.styl create mode 100644 themes/butterfly/source/css/_highlight/highlight.styl create mode 100644 themes/butterfly/source/css/_highlight/highlight/diff.styl create mode 100644 themes/butterfly/source/css/_highlight/highlight/index.styl create mode 100644 themes/butterfly/source/css/_highlight/prismjs/diff.styl create mode 100644 themes/butterfly/source/css/_highlight/prismjs/index.styl create mode 100644 themes/butterfly/source/css/_highlight/prismjs/line-number.styl create mode 100644 themes/butterfly/source/css/_highlight/theme.styl create mode 100644 themes/butterfly/source/css/_layout/aside.styl create mode 100644 themes/butterfly/source/css/_layout/chat.styl create mode 100644 themes/butterfly/source/css/_layout/comments.styl create mode 100644 themes/butterfly/source/css/_layout/footer.styl create mode 100644 themes/butterfly/source/css/_layout/head.styl create mode 100644 themes/butterfly/source/css/_layout/loading.styl create mode 100644 themes/butterfly/source/css/_layout/pagination.styl create mode 100644 themes/butterfly/source/css/_layout/post.styl create mode 100644 themes/butterfly/source/css/_layout/relatedposts.styl create mode 100644 themes/butterfly/source/css/_layout/reward.styl create mode 100644 themes/butterfly/source/css/_layout/rightside.styl create mode 100644 themes/butterfly/source/css/_layout/sidebar.styl create mode 100644 themes/butterfly/source/css/_layout/third-party.styl create mode 100644 themes/butterfly/source/css/_mode/darkmode.styl create mode 100644 themes/butterfly/source/css/_mode/readmode.styl create mode 100644 themes/butterfly/source/css/_page/404.styl create mode 100644 themes/butterfly/source/css/_page/archives.styl create mode 100644 themes/butterfly/source/css/_page/categories.styl create mode 100644 themes/butterfly/source/css/_page/common.styl create mode 100644 themes/butterfly/source/css/_page/flink.styl create mode 100644 themes/butterfly/source/css/_page/homepage.styl create mode 100644 themes/butterfly/source/css/_page/shuoshuo.styl create mode 100644 themes/butterfly/source/css/_page/tags.styl create mode 100644 themes/butterfly/source/css/_search/algolia.styl create mode 100644 themes/butterfly/source/css/_search/index.styl create mode 100644 themes/butterfly/source/css/_search/local-search.styl create mode 100644 themes/butterfly/source/css/_tags/button.styl create mode 100644 themes/butterfly/source/css/_tags/gallery.styl create mode 100644 themes/butterfly/source/css/_tags/hexo.styl create mode 100644 themes/butterfly/source/css/_tags/hide.styl create mode 100644 themes/butterfly/source/css/_tags/inlineImg.styl create mode 100644 themes/butterfly/source/css/_tags/label.styl create mode 100644 themes/butterfly/source/css/_tags/note.styl create mode 100644 themes/butterfly/source/css/_tags/series.styl create mode 100644 themes/butterfly/source/css/_tags/tabs.styl create mode 100644 themes/butterfly/source/css/_tags/timeline.styl create mode 100644 themes/butterfly/source/css/_third-party/normalize.min.css create mode 100644 themes/butterfly/source/css/index.styl create mode 100644 themes/butterfly/source/css/var.styl create mode 100644 themes/butterfly/source/img/404.jpg create mode 100644 themes/butterfly/source/img/butterfly-icon.png create mode 100644 themes/butterfly/source/img/error-page.png create mode 100644 themes/butterfly/source/img/favicon.ico create mode 100644 themes/butterfly/source/img/friend_404.gif create mode 100644 themes/butterfly/source/js/main.js create mode 100644 themes/butterfly/source/js/search/algolia.js create mode 100644 themes/butterfly/source/js/search/local-search.js create mode 100644 themes/butterfly/source/js/tw_cn.js create mode 100644 themes/butterfly/source/js/utils.js diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml index 63ae7b6..db5f45a 100644 --- a/.gitea/workflows/deploy.yml +++ b/.gitea/workflows/deploy.yml @@ -23,9 +23,7 @@ jobs: - name: Install dependencies run: npm install - name: Install hexo-cli - run: npm install hexo-cli -g - - name: Check repository - run: ls -al + run: npm install -g hexo-cli - name: Generate pages run: hexo generate - name: Copy pages @@ -35,13 +33,9 @@ jobs: git fetch git checkout publish - name: Remove old files - run: | - rm -rf `ls | grep -v .git` - ls -al + run: rm -rf `ls | grep -v .git` - name: Copy new files - run: | - cp -r ../pages/* . - ls -al + run: cp -r ../pages/* . - name: Commit and Push back Changes uses: https://github.com/stefanzweifel/git-auto-commit-action@v5 with: diff --git a/themes/butterfly b/themes/butterfly deleted file mode 160000 index f0e147c..0000000 --- a/themes/butterfly +++ /dev/null @@ -1 +0,0 @@ -Subproject commit f0e147c12570b84cdd870d9b7475ff00b47f7e8b diff --git a/themes/butterfly/.github/FUNDING.yml b/themes/butterfly/.github/FUNDING.yml new file mode 100644 index 0000000..0cd9243 --- /dev/null +++ b/themes/butterfly/.github/FUNDING.yml @@ -0,0 +1,13 @@ +# These are supported funding model platforms + +github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] +patreon: # Replace with a single Patreon username +open_collective: # Replace with a single Open Collective username +ko_fi: # Replace with a single Ko-fi username +tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel +community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry +liberapay: # Replace with a single Liberapay username +issuehunt: # Replace with a single IssueHunt username +otechie: # Replace with a single Otechie username +lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry +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'] diff --git a/themes/butterfly/.github/ISSUE_TEMPLATE/bug_report.yml b/themes/butterfly/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 0000000..d12e9e8 --- /dev/null +++ b/themes/butterfly/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,83 @@ +name: Bug report +description: Create a report to help us improve +title: '[Bug]: ' + +body: + - type: markdown + attributes: + value: | + 重要:請依照該模板來提交 + Important: Please follow the template to create a new issue + + - type: input + id: butterfly-ver + attributes: + label: 使用的 Butterfly 版本? | What version of Butterfly are you using? + description: 檢視主題的 package.json | Check the theme's package.json + validations: + required: true + + - type: dropdown + id: modify + attributes: + label: 是否修改過主題文件? | Has the theme files been modified? + options: + - 是 (Yes) + - 否 (No) + validations: + required: true + + - type: dropdown + id: browser + attributes: + label: 使用的瀏覽器? | What browser are you using? + options: + - Chrome + - Edge + - Safari + - Opera + - Other + validations: + required: true + + - type: dropdown + id: platform + attributes: + label: 使用的系統? | What operating system are you using? + options: + - Windows + - macOS + - Linux + - Android + - iOS + - Other + validations: + required: true + + - type: textarea + id: dependencies + attributes: + label: 依賴插件 | Package dependencies information + description: 在 Hexo 根目錄下執行 `npm ls --depth 0` | Run `npm ls --depth 0` in Hexo root directory + render: Text + validations: + required: true + + - type: textarea + id: description + attributes: + label: 問題描述 | Describe the bug + description: 請描述你的問題現象 | A clear and concise description of what the bug is. + placeholder: 請儘量提供截圖來定位問題 | If applicable, add screenshots to help explain your problem + value: + validations: + required: true + + - type: input + id: website + attributes: + label: 出現問題的網站 | Website with the issue + description: 請提供可復現問題的網站地址 | Please provide a website URL where the problem can be reproduced. + placeholder: 請填寫具體的網址,不要填寫 localhost 網站 | Please provide a specific URL, do not use localhost. + validations: + required: true \ No newline at end of file diff --git a/themes/butterfly/.github/ISSUE_TEMPLATE/config.yml b/themes/butterfly/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..9af6ea1 --- /dev/null +++ b/themes/butterfly/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,18 @@ +blank_issues_enabled: false +contact_links: + - name: Questions about Butterfly + url: https://github.com/jerryc127/hexo-theme-butterfly/discussions + about: 一些使用問題請到 Discussion 詢問。 Please ask questions in Discussion. + + - name: Butterfly Q&A + url: https://butterfly.js.org/posts/98d20436/ + about: Butterfly Q&A + + - name: Telegram + url: https://t.me/bu2fly + about: 'Official Telegram Group' + + - name: QQ 群 + url: https://jq.qq.com/?_wv=1027&k=KU9105XR + about: '群號 1070540070' + diff --git a/themes/butterfly/.github/ISSUE_TEMPLATE/feature_request.yml b/themes/butterfly/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 0000000..3bf7c30 --- /dev/null +++ b/themes/butterfly/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,14 @@ +name: Feature request +description: Suggest an idea for this project +title: '[Feature]: ' + +body: + - type: textarea + id: feature-request + attributes: + label: 想要的功能 | What feature do you want? + description: 請描述你需要的新功能 | A clear and concise description of what the feature is. + placeholder: + value: + validations: + require: true \ No newline at end of file diff --git a/themes/butterfly/.github/workflows/publish.yml b/themes/butterfly/.github/workflows/publish.yml new file mode 100644 index 0000000..d68365d --- /dev/null +++ b/themes/butterfly/.github/workflows/publish.yml @@ -0,0 +1,19 @@ +name: npm publish + +on: + release: + types: [created] +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + # Setup .npmrc file to publish to npm + - uses: actions/setup-node@v1 + with: + node-version: '12.x' + registry-url: 'https://registry.npmjs.org' + - run: npm install + - run: npm publish + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} \ No newline at end of file diff --git a/themes/butterfly/.github/workflows/stale.yml b/themes/butterfly/.github/workflows/stale.yml new file mode 100644 index 0000000..a50d9de --- /dev/null +++ b/themes/butterfly/.github/workflows/stale.yml @@ -0,0 +1,19 @@ +name: 'Close stale issues and PRs' +on: + schedule: + - cron: '30 1 * * *' + +jobs: + stale: + runs-on: ubuntu-latest + steps: + - uses: actions/stale@v5 + with: + days-before-issue-stale: 30 + days-before-pr-stale: -1 + days-before-close: 7 + 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.' + close-pr-message: 'This issue has not seen any activity since it was marked stale. Closing.' + stale-issue-label: 'Stale' + exempt-issue-labels: 'pinned,bug,enhancement,documentation,Plan' + operations-per-run: 1000 \ No newline at end of file diff --git a/themes/butterfly/LICENSE b/themes/butterfly/LICENSE new file mode 100644 index 0000000..7a4a3ea --- /dev/null +++ b/themes/butterfly/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file diff --git a/themes/butterfly/README.md b/themes/butterfly/README.md new file mode 100644 index 0000000..7589930 --- /dev/null +++ b/themes/butterfly/README.md @@ -0,0 +1,116 @@ +
+中文 +
+ +
+ + + +# hexo-theme-butterfly + +![master version](https://img.shields.io/github/package-json/v/jerryc127/hexo-theme-butterfly/master?color=%231ab1ad&label=master) +![master version](https://img.shields.io/github/package-json/v/jerryc127/hexo-theme-butterfly/dev?label=dev) +![https://img.shields.io/npm/v/hexo-theme-butterfly?color=%09%23bf00ff](https://img.shields.io/npm/v/hexo-theme-butterfly?color=%09%23bf00ff) +![hexo version](https://img.shields.io/badge/hexo-5.3.0+-0e83c) +![license](https://img.shields.io/github/license/jerryc127/hexo-theme-butterfly?color=FF5531) + +📢 Demo: [Butterfly](https://butterfly.js.org/) / [CrazyWong](https://blog.crazywong.com/) + +📖 Docs: [English](https://butterfly.js.org/en/posts/butterfly-docs-en-get-started/) / [Chinese](https://butterfly.js.org/posts/21cfbf15/) + +![](https://cdn.jsdelivr.net/gh/jerryc127/CDN@m2/img/theme-butterfly-readme.png) + +
+ +--- + +## 💻 Installation + +### GIT + +> If you are in Mainland China, you can download in [Gitee](https://gitee.com/immyw/hexo-theme-butterfly.git) + +Stable branch [recommend]: + +``` +git clone -b master https://github.com/jerryc127/hexo-theme-butterfly.git themes/butterfly +``` + +Dev branch: + +``` +git clone -b dev https://github.com/jerryc127/hexo-theme-butterfly.git themes/butterfly +``` + +### NPM + +> It supports Hexo 5.0.0 or later + +In Hexo site root directory + +```powershell +npm i hexo-theme-butterfly +``` + +## ⚙ Configuration + + Set theme in the hexo work folder's root config file `_config.yml`: + +> theme: butterfly + + If you don't have pug & stylus renderer, try this: + +> npm install hexo-renderer-pug hexo-renderer-stylus + +## 🎉 Features + +- [x] Card UI Design +- [x] Rounded Design/Squared Design +- [X] Support sub-menu +- [x] Two-column layout +- [x] Responsive Web Design +- [x] Dark Mode +- [x] Pjax +- [x] Read Mode +- [x] Conversion between Traditional and Simplified Chinese +- [X] TOC catalog is available for both computers and mobile phones +- [X] Built-in Syntax Highlighting Themes (darker/pale night/light/ocean), also support customization +- [X] Code Blocks (Display code language/close or expand Code Blocks/Copy Button/word wrap) +- [X] Disable copy/Add a Copyright Notice to the Copied Text +- [X] Search (Algolia Search/Local Search) +- [x] Mathjax and Katex +- [x] Built-in 404 page +- [x] WordCount +- [x] Related articles +- [x] Displays outdated notice for a post +- [x] Share (Sharejs/Addtoany) +- [X] Comment (Disqus/Disqusjs/Livere/Gitalk/Valine/Waline/Utterances/Facebook Comments/Twikoo/Giscus/Remark42/artalk) +- [x] Multiple Comment System Support +- [x] Online Chats (Chatra/Tidio/Daovoice/Crisp) +- [x] Web analytics +- [x] Google AdSense +- [x] Webmaster Verification +- [x] Change website colour scheme +- [x] Typewriter Effect: activate_power_mode +- [x] Background effects (Canvas ribbon/canvas_ribbon_piao/canvas_nest) +- [x] Mouse click effects (Fireworks/Heart/Text) +- [x] Preloader/Loading Animation/pace.js +- [x] Busuanzi visitor counter +- [x] Medium Zoom/Fancybox +- [x] Mermaid +- [x] Justified Gallery +- [x] Lazyload images +- [x] Instantpage/Pangu/Snackbar notification toast/PWA...... + +## ✨ Contributors + + + + + +## 📷 Screenshots + +![](https://cdn.jsdelivr.net/gh/jerryc127/CDN@m2/img/butterfly-readme-screenshots-1.jpg) +![](https://cdn.jsdelivr.net/gh/jerryc127/CDN@m2/img/butterfly-readme-screenshots-2.jpg) +![](https://cdn.jsdelivr.net/gh/jerryc127/CDN@m2/img/butterfly-readme-screenshots-3.jpg) +![](https://cdn.jsdelivr.net/gh/jerryc127/CDN@m2/img/butterfly-readme-screenshots-4.jpg) diff --git a/themes/butterfly/README_CN.md b/themes/butterfly/README_CN.md new file mode 100644 index 0000000..552205b --- /dev/null +++ b/themes/butterfly/README_CN.md @@ -0,0 +1,116 @@ +
+ English +
+ +
+ + + +# hexo-theme-butterfly + +![master version](https://img.shields.io/github/package-json/v/jerryc127/hexo-theme-butterfly/master?color=%231ab1ad&label=master) +![master version](https://img.shields.io/github/package-json/v/jerryc127/hexo-theme-butterfly/dev?label=dev) +![https://img.shields.io/npm/v/hexo-theme-butterfly?color=%09%23bf00ff](https://img.shields.io/npm/v/hexo-theme-butterfly?color=%09%23bf00ff) +![hexo version](https://img.shields.io/badge/hexo-5.3.0+-0e83c) +![license](https://img.shields.io/github/license/jerryc127/hexo-theme-butterfly?color=FF5531) + +📢 預覽: [Butterfly](https://butterfly.js.org/) / [CrazyWong](https://blog.crazywong.com/) + +📖 文檔: [中文](https://butterfly.js.org/posts/21cfbf15/) / [English](https://butterfly.js.org/en/posts/butterfly-docs-en-get-started/) + +![](https://cdn.jsdelivr.net/gh/jerryc127/CDN@m2/img/theme-butterfly-readme.png) + +
+ +--- + +## 💻 安裝 + +### Git 安裝 + +> 本倉庫同時上傳到 [Gitee](https://gitee.com/immyw/hexo-theme-butterfly.git),如果你訪問 Github 緩慢,可從 Gitee 中下載。 + +在博客根目錄裡安裝穩定版【推薦】 + +```powershell +git clone -b master https://github.com/jerryc127/hexo-theme-butterfly.git themes/butterfly +``` + +如果想要安裝比較新的dev分支,可以 + +```powershell +git clone -b dev https://github.com/jerryc127/hexo-theme-butterfly.git themes/butterfly +``` + +### npm 安裝 + +> 此方法只支持Hexo 5.0.0以上版本 + +在博客根目錄裡 + +```powershell +npm i hexo-theme-butterfly +``` + +## ⚙ 應用主題 + +修改hexo配置文件`_config.yml`,把主題改為`Butterfly` + +``` +theme: butterfly +``` + +>如果你沒有pug以及stylus的渲染器,請下載安裝: npm install hexo-renderer-pug hexo-renderer-stylus --save + +## 🎉 特色 + +- [x] 卡片化設計 +- [x] 圓角化設計/直角化設計 +- [X] 支持二級目錄 +- [x] 雙欄設計 +- [x] 響應式主題 +- [x] 夜間模式 +- [x] Pjax +- [x] 文章閲讀模式 +- [x] 簡體和繁體轉換 +- [X] 電腦和手機都可查看TOC目錄 +- [X] 內置多種代碼配色(darker/pale night/light/ocean),可自定義代碼配色 +- [X] 代碼塊顯示代碼語言/關閉或展開代碼塊/代碼複製/代碼自動換行 +- [X] 可關閉文字複製/可開啟內容複製增加版權信息) +- [X] 兩種搜索( Algolia 搜索和本地搜索) +- [x] Mathjax 和 Katex +- [x] 內置404頁面 +- [x] 顯示字數統計 +- [x] 顯示相關文章 +- [x] 過期文章提醒 +- [x] 多種分享系統(Sharejs/Addtoany) +- [X] 多種評論系統(Disqus/Disqusjs/Livere/Gitalk/Valine/Waline/Utterances/Facebook Comments/Twikoo/Giscus/Remark42/artalk) +- [x] 支持雙評論部署 +- [x] 多種在線聊天(Chatra/Tidio/Daovoice/Crisp) +- [x] 多種分析系統 +- [x] 谷歌廣告/手動廣告位置 +- [x] 各種站長驗證 +- [x] 修改網站配色 +- [x] 打字特效 activate_power_mode +- [x] 多種背景特效(靜止彩帶/動態彩帶/Canvas Nest) +- [x] 多種鼠標點擊特效(煙花/文字/愛心) +- [x] 內置一種 Preloader 加載動畫和 pace.js 加載動畫條 +- [x] 不蒜子訪問統計 +- [x] 兩種大圖模式(Medium Zoom/Fancybox) +- [x] Mermaid 圖表顯示 +- [x] 照片牆 +- [x] 圖片懶加載 +- [x] Instantpage/Pangu/Snackbar彈窗/PWA...... + +## ✨ 貢獻者 + + + + + +## 📷 截圖 + +![](https://cdn.jsdelivr.net/gh/jerryc127/CDN@m2/img/butterfly-readme-screenshots-1.jpg) +![](https://cdn.jsdelivr.net/gh/jerryc127/CDN@m2/img/butterfly-readme-screenshots-2.jpg) +![](https://cdn.jsdelivr.net/gh/jerryc127/CDN@m2/img/butterfly-readme-screenshots-3.jpg) +![](https://cdn.jsdelivr.net/gh/jerryc127/CDN@m2/img/butterfly-readme-screenshots-4.jpg) diff --git a/themes/butterfly/_config.yml b/themes/butterfly/_config.yml new file mode 100644 index 0000000..a448599 --- /dev/null +++ b/themes/butterfly/_config.yml @@ -0,0 +1,1094 @@ +# -------------------------------------- +# Hexo Butterfly Theme Configuration +# If you have any questions, please refer to the documentation +# Chinese: https://butterfly.js.org/ +# English: https://butterfly.js.org/en/ +# -------------------------------------- + +# -------------------------------------- +# Navigation Settings +# -------------------------------------- + +nav: + # Navigation bar logo image + logo: + display_title: true + # Whether to fix navigation bar + fixed: false + +menu: + # Home: / || fas fa-home + # List||fas fa-list: + # Music: /music/ || fas fa-music + # Movie: /movies/ || fas fa-video + +# -------------------------------------- +# Code Blocks Settings +# -------------------------------------- + +code_blocks: + # Code block theme: darker / pale night / light / ocean / false + theme: light + macStyle: false + # Code block height limit (unit: px) + height_limit: false + word_wrap: false + + # Toolbar + copy: true + language: true + # true: shrink the code blocks | false: expand the code blocks | none: expand code blocks and hide the button + shrink: false + fullpage: false + +# Social media links +# Formal: +# icon: link || the description || color +social: + # fab fa-github: https://github.com/xxxxx || Github || '#24292e' + # fas fa-envelope: mailto:xxxxxx@gmail.com || Email || '#4a7dbe' + +# -------------------------------------- +# Image Settings +# -------------------------------------- + +favicon: /img/favicon.png + +avatar: + img: /img/butterfly-icon.png + effect: false + +# Disable all banner images +disable_top_img: false + +# If the banner of page not setting, it will show the default_top_img +default_top_img: + +# The banner image of index page +index_img: + +# The banner image of archive page +archive_img: + +# Note: tag page, not tags page +tag_img: + +# The banner image of tag page, you can set the banner image for each tag +# Format: +# - tag name: xxxxx +tag_per_img: + +# Note: category page, not categories page +category_img: + +# The banner image of category page, you can set the banner image for each category +# Format: +# - category name: xxxxx +category_per_img: + +# The background image of footer +footer_img: false + +# Website Background +# Can set it to color or image url +background: + +cover: + # Disable the cover or not + index_enable: true + aside_enable: true + archives_enable: true + # When cover is not set, the default cover is displayed + default_cover: + # - xxx.jpg + +# Replace Broken Images +error_img: + flink: /img/friend_404.gif + post_page: /img/404.jpg + +# A simple 404 page +error_404: + enable: false + subtitle: 'Page Not Found' + background: /img/error-page.png + +post_meta: + # Home Page + page: + # Choose: created / updated / both + date_type: created + # Choose: date / relative + date_format: date + categories: true + tags: false + label: true + post: + # Choose: left / center + position: left + # Choose: created / updated / both + date_type: both + # Choose: date / relative + date_format: date + categories: true + tags: true + label: true + +# -------------------------------------- +# Index page settings +# -------------------------------------- + +# The top_img settings of home page +# default: top img - full screen, site info - middle +# The position of site info, eg: 300px/300em/300rem/10% +index_site_info_top: +# The height of top_img, eg: 300px/300em/300rem +index_top_img_height: + +# The subtitle on homepage +subtitle: + enable: false + # Typewriter Effect + effect: true + # Customize typed.js + # https://github.com/mattboldt/typed.js/#customization + typed_option: + # Source - Call the third-party service API (Chinese only) + # It will show the source first, then show the content of sub + # Choose: false/1/2/3 + # false - disable the function + # 1 - hitokoto.cn + # 2 - yijuzhan.com + # 3 - jinrishici.com + source: false + # If you close the typewriter effect, the subtitle will only show the first line of sub + sub: + +# Article layout on the homepage +# 1: Cover on the left, info on the right +# 2: Cover on the right, info on the left +# 3: Cover and info alternate between left and right +# 4: Cover on top, info on the bottom +# 5: Info displayed on the cover +# 6: Masonry layout - Cover on top, info on the bottom +# 7: Masonry layout - Info displayed on the cover +index_layout: 3 + +# Display the article introduction on homepage +# 1: description +# 2: both (if the description exists, it will show description, or show the auto_excerpt) +# 3: auto_excerpt (default) +# false: do not show the article introduction +index_post_content: + method: 3 + # If you set method to 2 or 3, the length need to config + length: 500 + +# -------------------------------------- +# Post Settings +# -------------------------------------- + +toc: + post: true + page: false + number: true + expand: false + # Only for post + style_simple: false + scroll_percent: true + +post_copyright: + enable: true + decode: false + author_href: + license: CC BY-NC-SA 4.0 + license_url: https://creativecommons.org/licenses/by-nc-sa/4.0/ + +# Sponsor/reward +reward: + enable: false + text: + QR_code: + # - img: /img/wechat.jpg + # link: + # text: wechat + # - img: /img/alipay.jpg + # link: + # text: alipay + +# Post edit +# Easily browse and edit blog source code online. +post_edit: + enable: false + # url: https://github.com/user-name/repo-name/edit/branch-name/subdirectory-name/ + # For example: https://github.com/jerryc127/butterfly.js.org/edit/main/source/ + url: + +# Related Articles +related_post: + enable: true + # Number of posts displayed + limit: 6 + # Choose: created / updated + date_type: created + +# Choose: 1 / 2 / false +# 1: The 'next post' will link to old post +# 2: The 'next post' will link to new post +# false: disable pagination +post_pagination: 1 + +# Displays outdated notice for a post +noticeOutdate: + enable: false + # Style: simple / flat + style: flat + # When will it be shown + limit_day: 365 + # Position: top / bottom + position: top + message_prev: It has been + message_next: days since the last update, the content of the article may be outdated. + +# -------------------------------------- +# Footer Settings +# -------------------------------------- +footer: + owner: + enable: true + since: 2019 + custom_text: + # Copyright of theme and framework + copyright: true + +# -------------------------------------- +# Aside Settings +# -------------------------------------- + +aside: + enable: true + hide: false + # Show the button to hide the aside in bottom right button + button: true + mobile: true + # Position: left / right + position: right + display: + archive: true + tag: true + category: true + card_author: + enable: true + description: + button: + enable: true + icon: fab fa-github + text: Follow Me + link: https://github.com/xxxxxx + card_announcement: + enable: true + content: This is my Blog + card_recent_post: + enable: true + # If set 0 will show all + limit: 5 + # Sort: date / updated + sort: date + sort_order: + card_newest_comments: + enable: false + sort_order: + limit: 6 + # Unit: mins, save data to localStorage + storage: 10 + avatar: true + card_categories: + enable: true + # If set 0 will show all + limit: 8 + # Choose: none / true / false + expand: none + sort_order: + card_tags: + enable: true + # If set 0 will show all + limit: 40 + color: false + # Order of tags, random/name/length + orderby: random + # Sort of order. 1, asc for ascending; -1, desc for descending + order: 1 + sort_order: + card_archives: + enable: true + # Type: monthly / yearly + type: monthly + # Eg: YYYY年MM月 + format: MMMM YYYY + # Sort of order. 1, asc for ascending; -1, desc for descending + order: -1 + # If set 0 will show all + limit: 8 + sort_order: + card_post_series: + enable: true + # The title shows the series name + series_title: false + # Order by title or date + orderBy: 'date' + # Sort of order. 1, asc for ascending; -1, desc for descending + order: -1 + card_webinfo: + enable: true + post_count: true + last_push_date: true + sort_order: + # Time difference between publish date and now + # Formal: Month/Day/Year Time or Year/Month/Day Time + # Leave it empty if you don't enable this feature + runtime_date: + +# -------------------------------------- +# Bottom right button +# -------------------------------------- + +# The distance between the bottom right button and the bottom (default unit: px) +rightside_bottom: + +# Conversion between Traditional and Simplified Chinese +translate: + enable: false + # The text of a button + default: 繁 + # the language of website (1 - Traditional Chinese/ 2 - Simplified Chinese) + defaultEncoding: 2 + # Time delay + translateDelay: 0 + # The text of the button when the language is Simplified Chinese + msgToTraditionalChinese: '繁' + # The text of the button when the language is Traditional Chinese + msgToSimplifiedChinese: '簡' + +# Read Mode +readmode: true + +# Dark Mode +darkmode: + enable: true + # Toggle Button to switch dark/light mode + button: true + # Switch dark/light mode automatically + # autoChangeMode: 1 Following System Settings, if the system doesn't support dark mode, it will switch dark mode between 6 pm to 6 am + # autoChangeMode: 2 Switch dark mode between 6 pm to 6 am + # autoChangeMode: false + autoChangeMode: false + # Set the light mode time. The value is between 0 and 24. If not set, the default value is 6 and 18 + start: + end: + +# Show scroll percent in scroll-to-top button +rightside_scroll_percent: false + +# Don't modify the following settings unless you know how they work +# Choose: readmode,translate,darkmode,hideAside,toc,chat,comment +# Don't repeat the same value +rightside_item_order: + enable: false + # Default: readmode,translate,darkmode,hideAside + hide: + # Default: toc,chat,comment + show: + +# -------------------------------------- +# Global Settings +# -------------------------------------- + +anchor: + # When you scroll, the URL will update according to header id. + auto_update: false + # Click the headline to scroll and update the anchor + click_to_scroll: false + +photofigcaption: false + +copy: + enable: true + # Add the copyright information after copied content + copyright: + enable: false + limit_count: 150 + +# Need to install the hexo-wordcount plugin +wordcount: + enable: false + # Display the word count of the article in post meta + post_wordcount: true + # Display the time to read the article in post meta + min2read: true + # Display the total word count of the website in aside's webinfo + total_wordcount: true + +# Busuanzi count for PV / UV in site +busuanzi: + site_uv: true + site_pv: true + page_pv: true + +# -------------------------------------- +# Math +# -------------------------------------- + +# About the per_page +# if you set it to true, it will load mathjax/katex script in each page +# if you set it to false, it will load mathjax/katex script according to your setting (add the 'mathjax: true' or 'katex: true' in page's front-matter) +math: + # Choose: mathjax, katex + # Leave it empty if you don't need math + use: + per_page: true + hide_scrollbar: false + + mathjax: + # Enable the contextual menu + enableMenu: true + # Choose: all / ams / none, This controls whether equations are numbered and how + tags: none + + katex: + # Enable the copy KaTeX formula + copy_tex: false + +# -------------------------------------- +# Search +# -------------------------------------- + +search: + # Choose: algolia_search / local_search / docsearch + # leave it empty if you don't need search + use: + placeholder: + + # Algolia Search + algolia_search: + # Number of search results per page + hitsPerPage: 6 + + # Local Search + local_search: + # Preload the search data when the page loads. + preload: false + # Show top n results per article, show all results by setting to -1 + top_n_per_article: 1 + # Unescape html strings to the readable one. + unescape: false + CDN: + + # Docsearch + # https://docsearch.algolia.com/ + docsearch: + appId: + apiKey: + indexName: + option: + +# -------------------------------------- +# Share System +# -------------------------------------- + +share: + # Choose: sharejs / addtoany + # Leave it empty if you don't need share + use: sharejs + + # Share.js + # https://github.com/overtrue/share.js + sharejs: + sites: facebook,twitter,wechat,weibo,qq + + # AddToAny + # https://www.addtoany.com/ + addtoany: + item: facebook,twitter,wechat,sina_weibo,facebook_messenger,email,copy_link + +# -------------------------------------- +# Comments System +# -------------------------------------- + +comments: + # Up to two comments system, the first will be shown as default + # Leave it empty if you don't need comments + # Choose: Disqus/Disqusjs/Livere/Gitalk/Valine/Waline/Utterances/Facebook Comments/Twikoo/Giscus/Remark42/Artalk + # Format of two comments system : Disqus,Waline + use: + # Display the comment name next to the button + text: true + # Lazyload: The comment system will be load when comment element enters the browser's viewport. + # If you set it to true, the comment count will be invalid + lazyload: false + # Display comment count in post's top_img + count: false + # Display comment count in Home Page + card_post_count: false + +# Disqus +# https://disqus.com/ +disqus: + shortname: + # For newest comments widget + apikey: + +# Alternative Disqus - Render comments with Disqus API +# https://github.com/SukkaW/DisqusJS +disqusjs: + shortname: + apikey: + option: + +# Livere +# https://www.livere.com/ +livere: + uid: + +# Gitalk +# https://github.com/gitalk/gitalk +gitalk: + client_id: + client_secret: + repo: + owner: + admin: + option: + +# Valine +# https://valine.js.org +valine: + appId: + appKey: + avatar: monsterid + # This configuration is suitable for domestic custom domain name users, overseas version will be automatically detected (no need to manually fill in) + serverURLs: + bg: + # Use Valine visitor count as the page view count + visitor: false + option: + +# Waline - A simple comment system with backend support fork from Valine +# https://waline.js.org/ +waline: + serverURL: + bg: + # Use Waline pageview count as the page view count + pageview: false + option: + +# Utterances +# https://utteranc.es/ +utterances: + repo: + # Issue Mapping: pathname/url/title/og:title + issue_term: pathname + # Theme: github-light/github-dark/github-dark-orange/icy-dark/dark-blue/photon-dark + light_theme: github-light + dark_theme: photon-dark + js: + option: + +# Facebook Comments Plugin +# https://developers.facebook.com/docs/plugins/comments/ +facebook_comments: + app_id: + # optional + user_id: + pageSize: 10 + # Choose: social / time / reverse_time + order_by: social + lang: zh_TW + +# Twikoo +# https://github.com/imaegoo/twikoo +twikoo: + envId: + region: + # Use Twikoo visitor count as the page view count + visitor: false + option: + +# Giscus +# https://giscus.app/ +giscus: + repo: + repo_id: + category_id: + light_theme: light + dark_theme: dark + js: + option: + +# Remark42 +# https://remark42.com/docs/configuration/frontend/ +remark42: + host: + siteId: + option: + +# Artalk +# https://artalk.js.org/guide/frontend/config.html +artalk: + server: + site: + # Use Artalk visitor count as the page view count + visitor: false + option: + +# -------------------------------------- +# Chat Services +# -------------------------------------- + +chat: + # Choose: chatra/tidio/daovoice/crisp + # Leave it empty if you don't need chat + use: + # Chat Button [recommend] + # It will create a button in the bottom right corner of website, and hide the origin button + rightside_button: false + # The origin chat button is displayed when scrolling up, and the button is hidden when scrolling down + button_hide_show: false + +# https://chatra.io/ +chatra: + id: + +# https://www.tidio.com/ +tidio: + public_key: + +# http://dashboard.daovoice.io/app +daovoice: + app_id: + +# https://crisp.chat/en/ +crisp: + website_id: + +# -------------------------------------- +# Analysis +# -------------------------------------- + +# https://tongji.baidu.com/web/welcome/login +baidu_analytics: + +# https://analytics.google.com/analytics/web/ +google_analytics: + +# https://www.cloudflare.com/zh-tw/web-analytics/ +cloudflare_analytics: + +# https://clarity.microsoft.com/ +microsoft_clarity: + +# https://umami.is/ +umami_analytics: + enable: false + # For self-hosted setups, configure the hostname of the Umami instance + serverURL: + website_id: + option: + UV_PV: + site_uv: false + site_pv: false + page_pv: false + # Umami Cloud (API key) / self-hosted Umami (token) + token: + +# -------------------------------------- +# Advertisement +# -------------------------------------- + +# Google Adsense +google_adsense: + enable: false + auto_ads: true + js: https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js + client: + enable_page_level_ads: true + +# Insert ads manually +# Leave it empty if you don't need ads +ad: + # Insert ads in the index (every three posts) + index: + # Insert ads in aside + aside: + # Insert ads in the post (before pagination) + post: + +# -------------------------------------- +# Verification +# -------------------------------------- + +site_verification: + # - name: google-site-verification + # content: xxxxxx + # - name: baidu-site-verification + # content: xxxxxxx + +# -------------------------------------- +# Beautify / Effect +# -------------------------------------- + +# Theme color for customize +# Notice: color value must in double quotes like "#000" or may cause error! + +# theme_color: +# enable: true +# main: "#49B1F5" +# paginator: "#00c4b6" +# button_hover: "#FF7242" +# text_selection: "#00c4b6" +# link_color: "#99a9bf" +# meta_color: "#858585" +# hr_color: "#A4D8FA" +# code_foreground: "#F47466" +# code_background: "rgba(27, 31, 35, .05)" +# toc_color: "#00c4b6" +# blockquote_padding_color: "#49b1f5" +# blockquote_background_color: "#49b1f5" +# scrollbar_color: "#49b1f5" +# meta_theme_color_light: "ffffff" +# meta_theme_color_dark: "#0d0d0d" + +# The user interface setting of category and tag page +# Choose: index - same as Homepage UI / default - same as archives UI +# leave it empty or index +category_ui: +tag_ui: + +# Rounded corners for UI elements +rounded_corners_ui: true + +# Stretches the lines so that each line has equal width +text_align_justify: false + +# Add a mask to the header and footer +mask: + header: true + footer: true + +# Loading Animation +preloader: + enable: false + # source + # 1. fullpage-loading + # 2. pace (progress bar) + source: 1 + # pace theme (see https://codebyzach.github.io/pace/) + pace_css_url: + +# Page Transition +enter_transitions: true + +# Default display mode - light (default) / dark +display_mode: light + +# Configuration for beautifying the content of the article +beautify: + enable: false + # Specify the field to beautify (site or post) + field: post + # Specify the icon to be used as a prefix for the title, such as '\f0c1' + title_prefix_icon: + # Specify the color of the title prefix icon, such as '#F47466' + title_prefix_icon_color: + +# Global font settings +# Don't modify the following settings unless you know how they work +font: + global_font_size: + code_font_size: + font_family: + code_font_family: + +# Font settings for the site title and site subtitle +blog_title_font: + font_link: + font_family: + +# The setting of divider icon +hr_icon: + enable: true + # The unicode value of Font Awesome icon, such as '\3423' + icon: + icon_top: + +# Typewriter Effect +# https://github.com/disjukr/activate-power-mode +activate_power_mode: + enable: false + colorful: true + shake: true + mobile: false + +# Background effects +# -------------------------------------- + +# canvas_ribbon +# See: https://github.com/hustcc/ribbon.js +canvas_ribbon: + enable: false + # The size of ribbon + size: 150 + # The opacity of ribbon (0 ~ 1) + alpha: 0.6 + zIndex: -1 + click_to_change: false + mobile: false + +# Fluttering Ribbon +canvas_fluttering_ribbon: + enable: false + mobile: false + +# canvas_nest +# https://github.com/hustcc/canvas-nest.js +canvas_nest: + enable: false + # Color of lines, default: '0,0,0'; RGB values: (R,G,B).(note: use ',' to separate.) + color: '0,0,255' + # The opacity of line (0~1) + opacity: 0.7 + # The z-index property of the background + zIndex: -1 + # The number of lines + count: 99 + mobile: false + +# Mouse click effects: fireworks +fireworks: + enable: false + zIndex: 9999 + mobile: false + +# Mouse click effects: Heart symbol +click_heart: + enable: false + mobile: false + +# Mouse click effects: words +clickShowText: + enable: false + text: + # - I + # - LOVE + # - YOU + fontSize: 15px + random: false + mobile: false + +# -------------------------------------- +# Lightbox Settings +# -------------------------------------- + +# Choose: fancybox / medium_zoom +# https://github.com/francoischalifour/medium-zoom +# https://fancyapps.com/fancybox/ +# Leave it empty if you don't need lightbox +lightbox: + +# -------------------------------------- +# Tag Plugins settings +# -------------------------------------- + +# Series +series: + enable: false + # Order by title or date + orderBy: 'title' + # Sort of order. 1, asc for ascending; -1, desc for descending + order: 1 + number: true + +# ABCJS - The ABC Music Notation Plugin +# https://github.com/paulrosen/abcjs +abcjs: + enable: false + per_page: true + +# Mermaid +# https://github.com/mermaid-js/mermaid +mermaid: + enable: false + # Write Mermaid diagrams using code blocks + code_write: false + # built-in themes: default / forest / dark / neutral + theme: + light: default + dark: dark + +# Note - Bootstrap Callout +note: + # Note tag style values: + # - simple bs-callout old alert style. Default. + # - modern bs-callout new (v2-v3) alert style. + # - flat flat callout style with background, like on Mozilla or StackOverflow. + # - disabled disable all CSS styles import of note tag. + style: flat + icons: true + border_radius: 3 + # Offset lighter of background in % for modern and flat styles (modern: -12 | 12; flat: -18 | 6). + # Offset also applied to label tag variables. This option can work with disabled note tag. + light_bg_offset: 0 + +# -------------------------------------- +# Other Settings +# -------------------------------------- + +# https://github.com/MoOx/pjax +pjax: + enable: false + # Exclude the specified pages from pjax, such as '/music/' + exclude: + # - /xxxxxx/ + +# Inject the css and script (aplayer/meting) +aplayerInject: + enable: false + per_page: true + +# Snackbar - Toast Notification +# https://github.com/polonel/SnackBar +# position: top-left / top-center / top-right / bottom-left / bottom-center / bottom-right +snackbar: + enable: false + position: bottom-left + # The background color of Toast Notification in light mode and dark mode + bg_light: '#49b1f5' + bg_dark: '#1f1f1f' + +# Instant.page +# https://instant.page/ +instantpage: false + +# Pangu - Insert a space between Chinese character and English character +# https://github.com/vinta/pangu.js +pangu: + enable: false + # Specify the field to use pangu (site or post) + field: site + +# Lazyload +# https://github.com/verlok/vanilla-lazyload +lazyload: + enable: false + # Specify the field to use lazyload (site or post) + field: site + placeholder: + blur: false + +# PWA +# See https://github.com/JLHwung/hexo-offline +# --------------- +pwa: + enable: false + manifest: + apple_touch_icon: + favicon_32_32: + favicon_16_16: + mask_icon: + +# Open graph meta tags +# https://hexo.io/docs/helpers#open-graph +Open_Graph_meta: + enable: true + option: + # twitter_card: + # twitter_image: + # twitter_id: + # twitter_site: + # google_plus: + # fb_admins: + # fb_app_id: + +# Add the vendor prefixes to ensure compatibility +css_prefix: true + +# Inject +# Insert the code to head (before '' tag) and the bottom (before '' tag) +inject: + head: + # - + bottom: + # - + +# CDN Settings +# Don't modify the following settings unless you know how they work +CDN: + # The CDN provider for internal and third-party scripts + # Options for both: local/jsdelivr/unpkg/cdnjs/custom + # Note: Dev version can only use 'local' for internal scripts + # Note: When setting third-party scripts to 'local', you need to install hexo-butterfly-extjs + internal_provider: local + third_party_provider: jsdelivr + + # Add version number to url, true or false + version: false + + # Custom format + # For example: https://cdn.staticfile.org/${cdnjs_name}/${version}/${min_cdnjs_file} + custom_format: + + option: + # abcjs_basic_js: + # activate_power_mode: + # algolia_js: + # algolia_search: + # aplayer_css: + # aplayer_js: + # artalk_css: + # artalk_js: + # blueimp_md5: + # busuanzi: + # canvas_fluttering_ribbon: + # canvas_nest: + # canvas_ribbon: + # click_heart: + # clickShowText: + # disqusjs: + # disqusjs_css: + # docsearch_css: + # docsearch_js: + # egjs_infinitegrid: + # fancybox: + # fancybox_css: + # fireworks: + # fontawesome: + # gitalk: + # gitalk_css: + # giscus: + # instantpage: + # instantsearch: + # katex: + # katex_copytex: + # lazyload: + # local_search: + # main: + # main_css: + # mathjax: + # medium_zoom: + # mermaid: + # meting_js: + # pangu: + # prismjs_autoloader: + # prismjs_js: + # prismjs_lineNumber_js: + # pjax: + # sharejs: + # sharejs_css: + # snackbar: + # snackbar_css: + # translate: + # twikoo: + # typed: + # utils: + # valine: + # waline_css: + # waline_js: diff --git a/themes/butterfly/languages/default.yml b/themes/butterfly/languages/default.yml new file mode 100644 index 0000000..f35cb35 --- /dev/null +++ b/themes/butterfly/languages/default.yml @@ -0,0 +1,121 @@ +footer: + framework: Framework + theme: Theme + +copy: + success: Copy Successful + error: Copy Failed + noSupport: Browser Not Supported + +page: + articles: All Articles + tag: Tag + category: Category + archives: Archives + +card_post_count: comments + +no_title: Untitled + +post: + created: Created + updated: Updated + wordcount: Word Count + min2read: Reading Time + min2read_unit: mins + page_pv: Post Views + comments: Comments + copyright: + author: Author + link: Link + copyright_notice: Copyright Notice + copyright_content: 'All articles on this blog are licensed under %s unless otherwise stated.' + recommend: Related Articles + edit: Edit + +search: + title: Search + load_data: Loading Database + input_placeholder: Search for Posts + algolia_search: + hits_empty: 'No results found for: ${query}' + hits_stats: '${hits} results found in ${time} ms' + local_search: + hits_empty: 'No results found for: ${query}' + hits_stats: '${hits} articles found' + +pagination: + prev: Previous + next: Next + +comment: Comments + +aside: + articles: Articles + tags: Tags + categories: Categories + card_announcement: Announcement + card_categories: Categories + card_tags: Tags + card_archives: Archives + card_recent_post: Recent Posts + card_webinfo: + headline: Website Info + article_name: Article Count + runtime: + name: Runtime + unit: days + last_push_date: + name: Last Update + site_wordcount: Total Word Count + site_uv_name: Unique Visitors + site_pv_name: Page Views + more_button: View More + card_newest_comments: + headline: Latest Comments + loading_text: Loading... + error: Unable to retrieve comments, please check the configuration + zero: No comments + image: Image + link: Link + code: Code + card_toc: Contents + card_post_series: Post Series + +date_suffix: + just: Just now + min: minutes ago + hour: hours ago + day: days ago + month: months ago + +donate: Sponsor +share: Share + +rightside: + readmode_title: Reading Mode + translate_title: Toggle Between Traditional and Simplified Chinese + night_mode_title: Toggle Between Light and Dark Mode + back_to_top: Back to Top + toc: Table of Contents + scroll_to_comment: Scroll to Comments + setting: Settings + aside: Toggle Between Single-column and Double-column + chat: Chat + +copy_copyright: + author: Author + link: Link + source: Source + info: Copyright belongs to the author. For commercial use, please contact the author for authorization. For non-commercial use, please indicate the source. + +Snackbar: + chs_to_cht: You have switched to Traditional Chinese + cht_to_chs: You have switched to Simplified Chinese + day_to_night: You have switched to Dark Mode + night_to_day: You have switched to Light Mode + +loading: Loading... +load_more: Load More + +error404: Page Not Found diff --git a/themes/butterfly/languages/en.yml b/themes/butterfly/languages/en.yml new file mode 100644 index 0000000..f35cb35 --- /dev/null +++ b/themes/butterfly/languages/en.yml @@ -0,0 +1,121 @@ +footer: + framework: Framework + theme: Theme + +copy: + success: Copy Successful + error: Copy Failed + noSupport: Browser Not Supported + +page: + articles: All Articles + tag: Tag + category: Category + archives: Archives + +card_post_count: comments + +no_title: Untitled + +post: + created: Created + updated: Updated + wordcount: Word Count + min2read: Reading Time + min2read_unit: mins + page_pv: Post Views + comments: Comments + copyright: + author: Author + link: Link + copyright_notice: Copyright Notice + copyright_content: 'All articles on this blog are licensed under %s unless otherwise stated.' + recommend: Related Articles + edit: Edit + +search: + title: Search + load_data: Loading Database + input_placeholder: Search for Posts + algolia_search: + hits_empty: 'No results found for: ${query}' + hits_stats: '${hits} results found in ${time} ms' + local_search: + hits_empty: 'No results found for: ${query}' + hits_stats: '${hits} articles found' + +pagination: + prev: Previous + next: Next + +comment: Comments + +aside: + articles: Articles + tags: Tags + categories: Categories + card_announcement: Announcement + card_categories: Categories + card_tags: Tags + card_archives: Archives + card_recent_post: Recent Posts + card_webinfo: + headline: Website Info + article_name: Article Count + runtime: + name: Runtime + unit: days + last_push_date: + name: Last Update + site_wordcount: Total Word Count + site_uv_name: Unique Visitors + site_pv_name: Page Views + more_button: View More + card_newest_comments: + headline: Latest Comments + loading_text: Loading... + error: Unable to retrieve comments, please check the configuration + zero: No comments + image: Image + link: Link + code: Code + card_toc: Contents + card_post_series: Post Series + +date_suffix: + just: Just now + min: minutes ago + hour: hours ago + day: days ago + month: months ago + +donate: Sponsor +share: Share + +rightside: + readmode_title: Reading Mode + translate_title: Toggle Between Traditional and Simplified Chinese + night_mode_title: Toggle Between Light and Dark Mode + back_to_top: Back to Top + toc: Table of Contents + scroll_to_comment: Scroll to Comments + setting: Settings + aside: Toggle Between Single-column and Double-column + chat: Chat + +copy_copyright: + author: Author + link: Link + source: Source + info: Copyright belongs to the author. For commercial use, please contact the author for authorization. For non-commercial use, please indicate the source. + +Snackbar: + chs_to_cht: You have switched to Traditional Chinese + cht_to_chs: You have switched to Simplified Chinese + day_to_night: You have switched to Dark Mode + night_to_day: You have switched to Light Mode + +loading: Loading... +load_more: Load More + +error404: Page Not Found diff --git a/themes/butterfly/languages/ja.yml b/themes/butterfly/languages/ja.yml new file mode 100644 index 0000000..1a291f8 --- /dev/null +++ b/themes/butterfly/languages/ja.yml @@ -0,0 +1,121 @@ +footer: + framework: フレームワーク + theme: テーマ + +copy: + success: コピー成功 + error: コピー失敗 + noSupport: ブラウザが対応していません + +page: + articles: 記事一覧 + tag: タグ + category: カテゴリ + archives: アーカイブ + +card_post_count: コメント数 + +no_title: タイトルなし + +post: + created: 作成日 + updated: 更新日 + wordcount: 総文字数 + min2read: 読む時間 + min2read_unit: 分 + page_pv: 閲覧数 + comments: コメント数 + copyright: + author: 著者 + link: リンク + copyright_notice: 著作権表示 + copyright_content: 'このブログのすべての記事は、%s ライセンスの下で提供されており、特に明記されていない限り、すべての権利を留保します。転載時には出典を明記してください: %s。' + recommend: 関連記事 + edit: 編集 + +search: + title: 検索 + load_data: データベースを読み込んでいます + input_placeholder: 記事を検索 + algolia_search: + hits_empty: '${query} の検索結果が見つかりませんでした。' + hits_stats: '${hits} 件の結果が ${time}ms で見つかりました' + local_search: + hits_empty: '${query} の検索結果が見つかりませんでした。' + hits_stats: '${hits} 件の記事が見つかりました' + +pagination: + prev: 前へ + next: 次へ + +comment: コメント + +aside: + articles: 記事 + tags: タグ + categories: カテゴリ + card_announcement: お知らせ + card_categories: カテゴリ + card_tags: タグ + card_archives: アーカイブ + card_recent_post: 最近の記事 + card_webinfo: + headline: サイト情報 + article_name: 記事数 + runtime: + name: 稼働時間 + unit: 日 + last_push_date: + name: 最終更新日 + site_wordcount: 総文字数 + site_uv_name: ユーザー数 + site_pv_name: ページビュー数 + more_button: もっと見る + card_newest_comments: + headline: 最新コメント + loading_text: ローディング中... + error: コメントを取得できませんでした。設定を確認してください。 + zero: コメントがありません + image: 画像 + link: リンク + code: コード + card_toc: 目次 + card_post_series: シリーズ記事 + +date_suffix: + just: たった今 + min: 分前 + hour: 時間前 + day: 日前 + month: ヶ月前 + +donate: 寄付 +share: 共有 + +rightside: + readmode_title: 読書モード + translate_title: 簡体字と繁体字の切り替え + night_mode_title: ライトモード/ダークモード切り替え + back_to_top: トップに戻る + toc: 目次 + scroll_to_comment: コメントへ移動 + setting: 設定 + aside: シングルカラムとダブルカラムの切り替え + chat: チャット + +copy_copyright: + author: 著者 + link: リンク + source: ソース + info: 著作権は著者に帰属します。商業的利用の場合は著者に連絡して許可を得てください。非商業的利用の場合は出典を明記してください。 + +Snackbar: + chs_to_cht: 繁体字に切り替えました + cht_to_chs: 簡体字に切り替えました + day_to_night: ダークモードに切り替えました + night_to_day: ライトモードに切り替えました + +loading: ローディング中... +load_more: もっと見る + +error404: ページが見つかりません diff --git a/themes/butterfly/languages/ko.yml b/themes/butterfly/languages/ko.yml new file mode 100644 index 0000000..7dca8da --- /dev/null +++ b/themes/butterfly/languages/ko.yml @@ -0,0 +1,121 @@ +footer: + framework: 프레임워크 + theme: 테마 + +copy: + success: 복사 성공 + error: 복사 실패 + noSupport: 브라우저가 지원되지 않음 + +page: + articles: 모든 글 + tag: 태그 + category: 카테고리 + archives: 아카이브 + +card_post_count: 댓글 수 + +no_title: 제목 없음 + +post: + created: 작성일 + updated: 수정일 + wordcount: 총 글자 수 + min2read: 읽기 시간 + min2read_unit: 분 + page_pv: 조회수 + comments: 댓글 + copyright: + author: 작성자 + link: 링크 + copyright_notice: 저작권 고지 + copyright_content: '이 블로그의 모든 글은 %s 라이선스를 따르며, 별도로 명시되지 않는 한 모든 권리를 보유합니다. 재배포 시 출처를 명시해 주세요: %s.' + recommend: 관련 글 + edit: 편집 + +search: + title: 검색 + load_data: 데이터베이스 로드 중 + input_placeholder: 글 검색 + algolia_search: + hits_empty: '${query}에 대한 결과를 찾을 수 없습니다.' + hits_stats: '${hits}개의 결과를 ${time}ms 만에 찾음' + local_search: + hits_empty: '${query}에 대한 결과를 찾을 수 없습니다.' + hits_stats: '${hits}개의 글을 찾음' + +pagination: + prev: 이전 + next: 다음 + +comment: 댓글 + +aside: + articles: 글 + tags: 태그 + categories: 카테고리 + card_announcement: 공지 + card_categories: 카테고리 + card_tags: 태그 + card_archives: 아카이브 + card_recent_post: 최근 글 + card_webinfo: + headline: 사이트 정보 + article_name: 글 수 + runtime: + name: 운영 시간 + unit: 일 + last_push_date: + name: 마지막 업데이트 + site_wordcount: 총 글자 수 + site_uv_name: 방문자 수 + site_pv_name: 총 조회수 + more_button: 더 보기 + card_newest_comments: + headline: 최신 댓글 + loading_text: 로딩 중... + error: 댓글을 가져올 수 없습니다. 설정을 확인해 주세요. + zero: 댓글 없음 + image: 이미지 + link: 링크 + code: 코드 + card_toc: 목차 + card_post_series: 시리즈 글 + +date_suffix: + just: 방금 + min: 분 전 + hour: 시간 전 + day: 일 전 + month: 달 전 + +donate: 후원 +share: 공유 + +rightside: + readmode_title: 읽기 모드 + translate_title: 번체와 간체 전환 + night_mode_title: 라이트/다크 모드 전환 + back_to_top: 맨 위로 + toc: 목차 + scroll_to_comment: 댓글로 이동 + setting: 설정 + aside: 단일/이중 열 전환 + chat: 채팅 + +copy_copyright: + author: 작성자 + link: 링크 + source: 출처 + info: 저작권은 작성자에게 있습니다. 상업적 사용을 위해서는 작성자의 허가를 받아야 하며, 비상업적 사용 시에는 출처를 명시해 주세요. + +Snackbar: + chs_to_cht: 번체로 전환되었습니다. + cht_to_chs: 간체로 전환되었습니다. + day_to_night: 다크 모드로 전환되었습니다. + night_to_day: 라이트 모드로 전환되었습니다. + +loading: 로딩 중... +load_more: 더 보기 + +error404: 페이지를 찾을 수 없습니다. diff --git a/themes/butterfly/languages/zh-CN.yml b/themes/butterfly/languages/zh-CN.yml new file mode 100644 index 0000000..1afa96b --- /dev/null +++ b/themes/butterfly/languages/zh-CN.yml @@ -0,0 +1,122 @@ +footer: + framework: 框架 + theme: 主题 + +copy: + success: 复制成功 + error: 复制失败 + noSupport: 浏览器不支持 + +page: + articles: 全部文章 + tag: 标签 + category: 分类 + archives: 归档 + +card_post_count: 条评论 + +no_title: 无标题 + +post: + created: 发表于 + updated: 更新于 + wordcount: 总字数 + min2read: 阅读时长 + min2read_unit: 分钟 + page_pv: 浏览量 + comments: 评论数 + copyright: + author: 文章作者 + link: 文章链接 + copyright_notice: 版权声明 + copyright_content: '本博客所有文章除特别声明外,均采用 + %s 许可协议。转载请注明来源 %s!' + recommend: 相关推荐 + edit: 编辑 + +search: + title: 搜索 + load_data: 数据加载中 + input_placeholder: 搜索文章 + algolia_search: + hits_empty: '未找到符合您查询的内容:${query}' + hits_stats: '找到 ${hits} 条结果,耗时 ${time} 毫秒' + local_search: + hits_empty: '未找到符合您查询的内容:${query}' + hits_stats: '共找到 ${hits} 篇文章' + +pagination: + prev: 上一篇 + next: 下一篇 + +comment: 评论 + +aside: + articles: 文章 + tags: 标签 + categories: 分类 + card_announcement: 公告 + card_categories: 分类 + card_tags: 标签 + card_archives: 归档 + card_recent_post: 最新文章 + card_webinfo: + headline: 网站信息 + article_name: 文章数目 + runtime: + name: 运行时间 + unit: 天 + last_push_date: + name: 最后更新时间 + site_wordcount: 本站总字数 + site_uv_name: 本站访客数 + site_pv_name: 本站总浏览量 + more_button: 查看更多 + card_newest_comments: + headline: 最新评论 + loading_text: 加载中... + error: 无法获取评论,请确认相关配置是否正确 + zero: 暂无评论 + image: 图片 + link: 链接 + code: 代码 + card_toc: 目录 + card_post_series: 系列文章 + +date_suffix: + just: 刚刚 + min: 分钟前 + hour: 小时前 + day: 天前 + month: 个月前 + +donate: 赞助 +share: 分享 + +rightside: + readmode_title: 阅读模式 + translate_title: 简繁转换 + night_mode_title: 日间和夜间模式切换 + back_to_top: 回到顶部 + toc: 目录 + scroll_to_comment: 前往评论 + setting: 设置 + aside: 单栏和双栏切换 + chat: 聊天 + +copy_copyright: + author: 作者 + link: 链接 + source: 来源 + info: 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 + +Snackbar: + chs_to_cht: 已切换为繁体中文 + cht_to_chs: 已切换为简体中文 + day_to_night: 已切换为深色模式 + night_to_day: 已切换为浅色模式 + +loading: 加载中... +load_more: 加载更多 + +error404: 页面未找到 diff --git a/themes/butterfly/languages/zh-HK.yml b/themes/butterfly/languages/zh-HK.yml new file mode 100644 index 0000000..b1f99d7 --- /dev/null +++ b/themes/butterfly/languages/zh-HK.yml @@ -0,0 +1,121 @@ +footer: + framework: 框架 + theme: 主題 + +copy: + success: 複製成功 + error: 複製失敗 + noSupport: 瀏覽器不支援 + +page: + articles: 全部文章 + tag: 標籤 + category: 分類 + archives: 歸檔 + +card_post_count: 條評論 + +no_title: 無標題 + +post: + created: 發表於 + updated: 更新於 + wordcount: 字數統計 + min2read: 閱讀時間 + min2read_unit: 分鐘 + page_pv: 瀏覽量 + comments: 評論數 + copyright: + author: 文章作者 + link: 文章連結 + copyright_notice: 版權聲明 + copyright_content: '除特別聲明外,本博客所有文章均採用%s 授權協議。轉載請註明出處:%s。' + recommend: 相關文章 + edit: 編輯 + +search: + title: 搜尋 + load_data: 正在加載數據庫 + input_placeholder: 搜尋文章 + algolia_search: + hits_empty: '未找到相關內容:${query}' + hits_stats: '找到 ${hits} 條結果,耗時 ${time} 毫秒' + local_search: + hits_empty: '未找到相關內容:${query}' + hits_stats: '找到 ${hits} 篇文章' + +pagination: + prev: 上一頁 + next: 下一頁 + +comment: 評論 + +aside: + articles: 文章 + tags: 標籤 + categories: 分類 + card_announcement: 公告 + card_categories: 分類 + card_tags: 標籤 + card_archives: 歸檔 + card_recent_post: 最新文章 + card_webinfo: + headline: 網站資訊 + article_name: 文章數目 + runtime: + name: 運行時間 + unit: 天 + last_push_date: + name: 最後更新時間 + site_wordcount: 總字數 + site_uv_name: 訪客數 + site_pv_name: 總瀏覽量 + more_button: 查看更多 + card_newest_comments: + headline: 最新評論 + loading_text: 正在加載... + error: 無法取得評論,請確認配置是否正確 + zero: 暫無評論 + image: 圖片 + link: 連結 + code: 代碼 + card_toc: 目錄 + card_post_series: 系列文章 + +date_suffix: + just: 剛剛 + min: 分鐘前 + hour: 小時前 + day: 天前 + month: 個月前 + +donate: 贊助 +share: 分享 + +rightside: + readmode_title: 閱讀模式 + translate_title: 簡繁轉換 + night_mode_title: 切換日夜模式 + back_to_top: 回到頂部 + toc: 目錄 + scroll_to_comment: 前往評論 + setting: 設定 + aside: 單欄與雙欄切換 + chat: 聊天 + +copy_copyright: + author: 作者 + link: 連結 + source: 來源 + info: 版權屬於作者所有。商業用途請聯絡作者獲得授權,非商業用途請註明出處。 + +Snackbar: + chs_to_cht: 已切換為繁體中文 + cht_to_chs: 已切換為簡體中文 + day_to_night: 已切換為深色模式 + night_to_day: 已切換為淺色模式 + +loading: 正在加載... +load_more: 加載更多 + +error404: 未找到頁面 diff --git a/themes/butterfly/languages/zh-TW.yml b/themes/butterfly/languages/zh-TW.yml new file mode 100644 index 0000000..c91cb73 --- /dev/null +++ b/themes/butterfly/languages/zh-TW.yml @@ -0,0 +1,121 @@ +footer: + framework: 框架 + theme: 主題 + +copy: + success: 複製成功 + error: 複製失敗 + noSupport: 瀏覽器不支援 + +page: + articles: 所有文章 + tag: 標籤 + category: 分類 + archives: 歸檔 + +card_post_count: 則評論 + +no_title: 無標題 + +post: + created: 發表於 + updated: 更新於 + wordcount: 總字數 + min2read: 閱讀時間 + min2read_unit: 分鐘 + page_pv: 瀏覽量 + comments: 評論數 + copyright: + author: 文章作者 + link: 文章連結 + copyright_notice: 版權聲明 + copyright_content: '本部落格所有文章除特別聲明外,均採用%s 授權協議。轉載請註明來源 %s!' + recommend: 相關推薦 + edit: 編輯 + +search: + title: 搜尋 + load_data: 資料載入中 + input_placeholder: 搜尋文章 + algolia_search: + hits_empty: '找不到符合您查詢的內容:${query}' + hits_stats: '找到 ${hits} 筆結果,耗時 ${time} 毫秒' + local_search: + hits_empty: '找不到符合您查詢的內容:${query}' + hits_stats: '共找到 ${hits} 篇文章' + +pagination: + prev: 上一篇 + next: 下一篇 + +comment: 評論 + +aside: + articles: 文章 + tags: 標籤 + categories: 分類 + card_announcement: 公告 + card_categories: 分類 + card_tags: 標籤 + card_archives: 歸檔 + card_recent_post: 最新文章 + card_webinfo: + headline: 網站資訊 + article_name: 文章數量 + runtime: + name: 運行時間 + unit: 天 + last_push_date: + name: 最後更新時間 + site_wordcount: 總字數 + site_uv_name: 訪客數 + site_pv_name: 總瀏覽量 + more_button: 檢視更多 + card_newest_comments: + headline: 最新評論 + loading_text: 載入中... + error: 無法獲取評論,請確認相關配置是否正確 + zero: 尚無評論 + image: 圖片 + link: 連結 + code: 程式碼 + card_toc: 目錄 + card_post_series: 系列文章 + +date_suffix: + just: 剛剛 + min: 分鐘前 + hour: 小時前 + day: 天前 + month: 個月前 + +donate: 贊助 +share: 分享 + +rightside: + readmode_title: 閱讀模式 + translate_title: 繁簡轉換 + night_mode_title: 日夜模式切換 + back_to_top: 回到頂端 + toc: 目錄 + scroll_to_comment: 前往評論 + setting: 設定 + aside: 單欄和雙欄切換 + chat: 聊天 + +copy_copyright: + author: 作者 + link: 連結 + source: 來源 + info: 著作權歸作者所有。如需商業轉載,請聯絡作者獲得授權,非商業轉載請註明出處。 + +Snackbar: + chs_to_cht: 已切換為繁體中文 + cht_to_chs: 已切換為簡體中文 + day_to_night: 已切換為深色模式 + night_to_day: 已切換為淺色模式 + +loading: 載入中... +load_more: 載入更多 + +error404: 找不到頁面 diff --git a/themes/butterfly/layout/archive.pug b/themes/butterfly/layout/archive.pug new file mode 100644 index 0000000..913dedc --- /dev/null +++ b/themes/butterfly/layout/archive.pug @@ -0,0 +1,8 @@ +extends includes/layout.pug + +block content + include ./includes/mixins/article-sort.pug + #archive + .article-sort-title= `${_p('page.articles')} - ${getArchiveLength()}` + +articleSort(page.posts) + include includes/pagination.pug \ No newline at end of file diff --git a/themes/butterfly/layout/category.pug b/themes/butterfly/layout/category.pug new file mode 100644 index 0000000..092be9a --- /dev/null +++ b/themes/butterfly/layout/category.pug @@ -0,0 +1,12 @@ +extends includes/layout.pug + +block content + if theme.category_ui == 'index' + include ./includes/mixins/indexPostUI.pug + +indexPostUI + else + include ./includes/mixins/article-sort.pug + #category + .article-sort-title= _p('page.category') + ' - ' + page.category + +articleSort(page.posts) + include includes/pagination.pug \ No newline at end of file diff --git a/themes/butterfly/layout/includes/additional-js.pug b/themes/butterfly/layout/includes/additional-js.pug new file mode 100644 index 0000000..7728937 --- /dev/null +++ b/themes/butterfly/layout/includes/additional-js.pug @@ -0,0 +1,60 @@ +div + script(src=url_for(theme.asset.utils)) + script(src=url_for(theme.asset.main)) + + if theme.translate.enable + script(src=url_for(theme.asset.translate)) + + if theme.lightbox + script(src=url_for(theme.asset[theme.lightbox])) + + if theme.instantpage + script(src=url_for(theme.asset.instantpage), type='module') + + if theme.lazyload.enable + script(src=url_for(theme.asset.lazyload)) + + if theme.snackbar.enable + script(src=url_for(theme.asset.snackbar)) + + if theme.pangu.enable + != partial("includes/third-party/pangu.pug", {}, { cache: true }) + + .js-pjax + if needLoadCountJs + != partial("includes/third-party/card-post-count/index", {}, { cache: true }) + + if loadSubJs + include ./third-party/subtitle.pug + + include ./third-party/math/index.pug + include ./third-party/abcjs/index.pug + + if commentsJsLoad + include ./third-party/comments/js.pug + + != partial("includes/third-party/prismjs", {}, { cache: true }) + + if theme.aside.enable && theme.aside.card_newest_comments.enable + if theme.pjax.enable || (!is_post() && page.aside !== false) + != partial("includes/third-party/newest-comments/index", {}, { cache: true }) + + != fragment_cache('injectBottom', function(){return injectHtml(theme.inject.bottom)}) + + != partial("includes/third-party/effect", {}, { cache: true }) + != partial("includes/third-party/chat/index", {}, { cache: true }) + + if theme.aplayerInject && theme.aplayerInject.enable + if theme.pjax.enable || theme.aplayerInject.per_page || page.aplayer + include ./third-party/aplayer.pug + + if theme.pjax.enable + != partial("includes/third-party/pjax", {}, { cache: true }) + + if theme.umami_analytics.enable + != partial("includes/third-party/umami_analytics", {}, { cache: true }) + + if theme.busuanzi.site_uv || theme.busuanzi.site_pv || theme.busuanzi.page_pv + script(async data-pjax src= theme.asset.busuanzi || '//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js') + + != partial('includes/third-party/search/index', {}, { cache: true }) \ No newline at end of file diff --git a/themes/butterfly/layout/includes/footer.pug b/themes/butterfly/layout/includes/footer.pug new file mode 100644 index 0000000..f6c3e87 --- /dev/null +++ b/themes/butterfly/layout/includes/footer.pug @@ -0,0 +1,18 @@ +#footer-wrap + if theme.footer.owner.enable + - const currentYear = new Date().getFullYear() + - const sinceYear = theme.footer.owner.since + .copyright + if sinceYear && sinceYear != currentYear + != `©${sinceYear} - ${currentYear} By ${config.author}` + else + != `©${currentYear} By ${config.author}` + if theme.footer.copyright + .framework-info + span= _p('footer.framework') + ' ' + a(href='https://hexo.io')= 'Hexo' + span.footer-separator | + span= _p('footer.theme') + ' ' + a(href='https://github.com/jerryc127/hexo-theme-butterfly')= 'Butterfly' + if theme.footer.custom_text + .footer_custom_text!= theme.footer.custom_text \ No newline at end of file diff --git a/themes/butterfly/layout/includes/head.pug b/themes/butterfly/layout/includes/head.pug new file mode 100644 index 0000000..56c29f0 --- /dev/null +++ b/themes/butterfly/layout/includes/head.pug @@ -0,0 +1,68 @@ +- var pageTitle +- is_archive() ? page.title = findArchivesTitle(page, theme.menu, date) : '' +- if (is_tag()) pageTitle = _p('page.tag') + ': ' + page.tag +- else if (is_category()) pageTitle = _p('page.category') + ': ' + page.category +- else if (is_current('/404.html', [strict])) pageTitle = _p('error404') +- else pageTitle = page.title || config.title || '' + +- var isSubtitle = config.subtitle ? ' - ' + config.subtitle : '' +- var tabTitle = is_home() || !pageTitle ? config.title + isSubtitle : pageTitle + ' | ' + config.title +- var pageAuthor = config.email ? config.author + ',' + config.email : config.author +- var pageCopyright = config.copyright || config.author +- var themeColorLight = theme.theme_color && theme.theme_color.enable && theme.theme_color.meta_theme_color_light || '#ffffff' +- var themeColorDark = theme.theme_color && theme.theme_color.enable && theme.theme_color.meta_theme_color_dark || '#0d0d0d' +- var themeColor = theme.display_mode === 'dark' ? themeColorDark : themeColorLight + +meta(charset='UTF-8') +meta(http-equiv="X-UA-Compatible" content="IE=edge") +meta(name="viewport" content="width=device-width, initial-scale=1.0,viewport-fit=cover") +title= tabTitle +meta(name="author" content=pageAuthor) +meta(name="copyright" content=pageCopyright) +meta(name ="format-detection" content="telephone=no") +meta(name="theme-color" content=themeColor) + +//- Open_Graph +include ./head/Open_Graph.pug + +!=favicon_tag(theme.favicon || config.favicon) +link(rel="canonical" href=urlNoIndex(null,config.pretty_urls.trailing_index,config.pretty_urls.trailing_html)) + +//- 預解析 +!=partial('includes/head/preconnect', {}, {cache: true}) + +//- 網站驗證 +!=partial('includes/head/site_verification', {}, {cache: true}) + +//- PWA +if (theme.pwa && theme.pwa.enable) + !=partial('includes/head/pwa', {}, {cache: true}) + +//- main css +link(rel='stylesheet', href=url_for(theme.asset.main_css)) +link(rel='stylesheet', href=url_for(theme.asset.fontawesome)) + +if (theme.snackbar && theme.snackbar.enable) + link(rel='stylesheet', href=url_for(theme.asset.snackbar_css) media="print" onload="this.media='all'") + +if theme.lightbox === 'fancybox' + link(rel='stylesheet' href=url_for(theme.asset.fancybox_css) media="print" onload="this.media='all'") + +!=fragment_cache('injectHeadJs', function(){return inject_head_js()}) + +//- google_adsense +!=partial('includes/head/google_adsense', {}, {cache: true}) + +//- analytics +!=partial('includes/head/analytics', {}, {cache: true}) + +//- font +if theme.blog_title_font && theme.blog_title_font.font_link + link(rel='stylesheet' href=url_for(theme.blog_title_font.font_link) media="print" onload="this.media='all'") + +//- global config +!=partial('includes/head/config', {}, {cache: true}) + +include ./head/config_site.pug + +!=fragment_cache('injectHead', function(){return injectHtml(theme.inject.head)}) diff --git a/themes/butterfly/layout/includes/head/Open_Graph.pug b/themes/butterfly/layout/includes/head/Open_Graph.pug new file mode 100644 index 0000000..abde7e6 --- /dev/null +++ b/themes/butterfly/layout/includes/head/Open_Graph.pug @@ -0,0 +1,16 @@ +if theme.Open_Graph_meta.enable + - + const coverVal = page.cover_type === 'img' ? page.cover : theme.avatar.img + let ogOption = Object.assign({ + type: is_post() ? 'article' : 'website', + image: coverVal ? full_url_for(coverVal) : '', + fb_admins: theme.facebook_comments.user_id || '', + fb_app_id: theme.facebook_comments.app_id || '', + }, theme.Open_Graph_meta.option) + - + != open_graph(ogOption) +else + - const description = page.description || page.content || page.title || config.description + if description + meta(name="description" content=truncate(description, 150)) + diff --git a/themes/butterfly/layout/includes/head/analytics.pug b/themes/butterfly/layout/includes/head/analytics.pug new file mode 100644 index 0000000..5c389ba --- /dev/null +++ b/themes/butterfly/layout/includes/head/analytics.pug @@ -0,0 +1,34 @@ +if theme.baidu_analytics + script. + var _hmt = _hmt || []; + (function() { + var hm = document.createElement("script"); + hm.src = "https://hm.baidu.com/hm.js?!{theme.baidu_analytics}"; + var s = document.getElementsByTagName("script")[0]; + s.parentNode.insertBefore(hm, s); + })(); + btf.addGlobalFn('pjaxComplete', () => { + _hmt.push(['_trackPageview',window.location.pathname]) + }, 'baidu_analytics') + +if theme.google_analytics + script(async src=`https://www.googletagmanager.com/gtag/js?id=${theme.google_analytics}`) + script. + window.dataLayer = window.dataLayer || [] + function gtag(){dataLayer.push(arguments)} + gtag('js', new Date()) + gtag('config', '!{theme.google_analytics}') + btf.addGlobalFn('pjaxComplete', () => { + gtag('config', '!{theme.google_analytics}', {'page_path': window.location.pathname}) + }, 'google_analytics') + +if theme.cloudflare_analytics + script(defer data-pjax src='https://static.cloudflareinsights.com/beacon.min.js' data-cf-beacon=`{"token": "${theme.cloudflare_analytics}"}`) + +if theme.microsoft_clarity + script. + (function(c,l,a,r,i,t,y){ + c[a]=c[a]||function(){(c[a].q=c[a].q||[]).push(arguments)}; + t=l.createElement(r);t.async=1;t.src="https://www.clarity.ms/tag/"+i; + y=l.getElementsByTagName(r)[0];y.parentNode.insertBefore(t,y); + })(window, document, "clarity", "script", "!{theme.microsoft_clarity}"); \ No newline at end of file diff --git a/themes/butterfly/layout/includes/head/config.pug b/themes/butterfly/layout/includes/head/config.pug new file mode 100644 index 0000000..f47a21f --- /dev/null +++ b/themes/butterfly/layout/includes/head/config.pug @@ -0,0 +1,137 @@ +- + let algolia = 'undefined' + if (theme.search.use === 'algolia_search') { + const { ALGOLIA_APP_ID, ALGOLIA_API_KEY, ALGOLIA_INDEX_NAME } = process.env + const { appId, applicationID, apiKey, indexName } = config.algolia + algolia = JSON.stringify({ + appId: ALGOLIA_APP_ID || appId || applicationID, + apiKey: ALGOLIA_API_KEY || apiKey, + indexName: ALGOLIA_INDEX_NAME || indexName, + hitsPerPage: theme.search.algolia_search.hitsPerPage, + // search languages + languages: { + input_placeholder: theme.search.placeholder || _p("search.input_placeholder"), + hits_empty: _p("search.algolia_search.hits_empty"), + hits_stats: _p("search.algolia_search.hits_stats"), + } + }) + } + + let localSearch = 'undefined' + if (theme.search.use === 'local_search') { + const { CDN, preload, top_n_per_article, unescape } = theme.search.local_search + localSearch = JSON.stringify({ + path: CDN || config.root + config.search.path, + preload, + top_n_per_article, + unescape, + languages: { + // search languages + hits_empty: _p("search.local_search.hits_empty"), + hits_stats: _p("search.local_search.hits_stats"), + } + }) + } + + let translate = 'undefined' + if (theme.translate && theme.translate.enable){ + translate = JSON.stringify({ + defaultEncoding: theme.translate.defaultEncoding, + translateDelay: theme.translate.translateDelay, + msgToTraditionalChinese: theme.translate.msgToTraditionalChinese, + msgToSimplifiedChinese: theme.translate.msgToSimplifiedChinese + }) + } + + let copyright = 'undefined' + if (theme.copy.enable && theme.copy.copyright.enable){ + copyright = JSON.stringify({ + limitCount: theme.copy.copyright.limit_count, + languages: { + author: _p("copy_copyright.author") + ': ' + config.author, + link: _p("copy_copyright.link") + ': ', + source: _p("copy_copyright.source") + ': ' + config.title, + info: _p("copy_copyright.info") + } + }) + } + + let Snackbar = 'undefined' + if (theme.snackbar && theme.snackbar.enable) { + Snackbar = JSON.stringify({ + chs_to_cht: _p("Snackbar.chs_to_cht"), + cht_to_chs: _p("Snackbar.cht_to_chs"), + day_to_night: _p("Snackbar.day_to_night"), + night_to_day: _p("Snackbar.night_to_day"), + bgLight: theme.snackbar.bg_light, + bgDark: theme.snackbar.bg_dark, + position: theme.snackbar.position, + }) + } + + let noticeOutdate = 'undefined' + if (theme.noticeOutdate && theme.noticeOutdate.enable) { + noticeOutdate = JSON.stringify({ + limitDay: theme.noticeOutdate.limit_day, + position: theme.noticeOutdate.position, + messagePrev: theme.noticeOutdate.message_prev, + messageNext: theme.noticeOutdate.message_next, + }) + } + + let highlight = 'undefined' + let syntaxHighlighter = config.syntax_highlighter + let highlightEnable = syntaxHighlighter ? ['highlight.js', 'prismjs'].includes(syntaxHighlighter) : (config.highlight.enable || config.prismjs.enable) + if (highlightEnable) { + const { copy, language, height_limit, fullpage, macStyle } = theme.code_blocks + highlight = JSON.stringify({ + plugin: syntaxHighlighter ? syntaxHighlighter : config.highlight.enable ? 'highlight.js' : 'prismjs', + highlightCopy: copy, + highlightLang: language, + highlightHeightLimit: height_limit, + highlightFullpage: fullpage, + highlightMacStyle: macStyle + }) + } + +script. + const GLOBAL_CONFIG = { + root: '!{config.root}', + algolia: !{algolia}, + localSearch: !{localSearch}, + translate: !{translate}, + noticeOutdate: !{noticeOutdate}, + highlight: !{highlight}, + copy: { + success: '!{_p("copy.success")}', + error: '!{_p("copy.error")}', + noSupport: '!{_p("copy.noSupport")}' + }, + relativeDate: { + homepage: !{theme.post_meta.page.date_format === 'relative'}, + post: !{theme.post_meta.post.date_format === 'relative'} + }, + runtime: '!{theme.aside.card_webinfo.runtime_date ? _p("aside.card_webinfo.runtime.unit") : ""}', + dateSuffix: { + just: '!{_p("date_suffix.just")}', + min: '!{_p("date_suffix.min")}', + hour: '!{_p("date_suffix.hour")}', + day: '!{_p("date_suffix.day")}', + month: '!{_p("date_suffix.month")}' + }, + copyright: !{copyright}, + lightbox: '!{ theme.lightbox || 'null' }', + Snackbar: !{Snackbar}, + infinitegrid: { + js: '!{url_for(theme.asset.egjs_infinitegrid)}', + buttonText: '!{_p("load_more")}' + }, + isPhotoFigcaption: !{theme.photofigcaption}, + islazyload: !{theme.lazyload.enable}, + isAnchor: !{theme.anchor.auto_update || false}, + percent: { + toc: !{theme.toc.scroll_percent}, + rightside: !{theme.rightside_scroll_percent}, + }, + autoDarkmode: !{theme.darkmode.enable && theme.darkmode.autoChangeMode === 1} + } diff --git a/themes/butterfly/layout/includes/head/config_site.pug b/themes/butterfly/layout/includes/head/config_site.pug new file mode 100644 index 0000000..f457549 --- /dev/null +++ b/themes/butterfly/layout/includes/head/config_site.pug @@ -0,0 +1,27 @@ +- + const titleVal = pageTitle.replace(/'/ig,"\\'") + + let isHighlightShrink + if (theme.code_blocks.shrink == 'none') isHighlightShrink = 'undefined' + else if (typeof page.highlight_shrink == 'boolean') isHighlightShrink = page.highlight_shrink + else isHighlightShrink = theme.code_blocks.shrink + + var showToc = false + if (theme.aside.enable && page.aside !== false) { + let tocEnable = false + if (is_post() && theme.toc.post) tocEnable = true + else if (is_page() && theme.toc.page) tocEnable = true + const pageToc = typeof page.toc === 'boolean' ? page.toc : tocEnable + showToc = pageToc && (toc(page.content) !== '' || page.encrypt === true) + } +- + +script#config-diff. + var GLOBAL_CONFIG_SITE = { + title: '!{titleVal}', + isPost: !{is_post()}, + isHome: !{is_home()}, + isHighlightShrink: !{isHighlightShrink}, + isToc: !{showToc}, + postUpdate: '!{full_date(page.updated)}' + } diff --git a/themes/butterfly/layout/includes/head/google_adsense.pug b/themes/butterfly/layout/includes/head/google_adsense.pug new file mode 100644 index 0000000..3ef1af9 --- /dev/null +++ b/themes/butterfly/layout/includes/head/google_adsense.pug @@ -0,0 +1,9 @@ +if (theme.google_adsense && theme.google_adsense.enable) + script(async src=theme.google_adsense.js) + + if theme.google_adsense.auto_ads + script. + (adsbygoogle = window.adsbygoogle || []).push({ + google_ad_client: '!{theme.google_adsense.client}', + enable_page_level_ads: '!{theme.google_adsense.enable_page_level_ads}' + }); \ No newline at end of file diff --git a/themes/butterfly/layout/includes/head/preconnect.pug b/themes/butterfly/layout/includes/head/preconnect.pug new file mode 100644 index 0000000..f8af380 --- /dev/null +++ b/themes/butterfly/layout/includes/head/preconnect.pug @@ -0,0 +1,35 @@ +- + const { internal_provider, third_party_provider, custom_format } = theme.CDN + const providers = { + 'jsdelivr': '//cdn.jsdelivr.net', + 'cdnjs': '//cdnjs.cloudflare.com', + 'unpkg': '//unpkg.com', + 'custom': custom_format && custom_format.match(/^((https?:)?(\/\/[^/]+)|([^/]+))(\/|$)/)[1] + } +- + +if internal_provider === third_party_provider && internal_provider !== 'local' + link(rel="preconnect" href=providers[internal_provider]) +else + if internal_provider !== 'local' + link(rel="preconnect" href=providers[internal_provider]) + if third_party_provider !== 'local' + link(rel="preconnect" href=providers[third_party_provider]) + +if theme.google_analytics + link(rel="preconnect" href="//www.google-analytics.com" crossorigin='') + +if theme.baidu_analytics + link(rel="preconnect" href="//hm.baidu.com") + +if theme.cloudflare_analytics + link(rel="preconnect" href="//static.cloudflareinsights.com") + +if theme.microsoft_clarity + link(rel="preconnect" href="//www.clarity.ms") + +if theme.blog_title_font && theme.blog_title_font.font_link && theme.blog_title_font.font_link.indexOf('//fonts.googleapis.com') != -1 + link(rel="preconnect" href="//fonts.googleapis.com" crossorigin='') + +if !theme.asset.busuanzi && (theme.busuanzi.site_uv || theme.busuanzi.site_pv || theme.busuanzi.page_pv) + link(rel="preconnect" href="//busuanzi.ibruce.info") \ No newline at end of file diff --git a/themes/butterfly/layout/includes/head/pwa.pug b/themes/butterfly/layout/includes/head/pwa.pug new file mode 100644 index 0000000..816026a --- /dev/null +++ b/themes/butterfly/layout/includes/head/pwa.pug @@ -0,0 +1,13 @@ +- const { manifest, theme_color, apple_touch_icon, favicon_32_32, favicon_16_16, mask_icon } = theme.pwa + +link(rel="manifest" href=url_for(manifest)) +if theme_color + meta(name="msapplication-TileColor" content=theme_color) +if apple_touch_icon + link(rel="apple-touch-icon" sizes="180x180" href=url_for(apple_touch_icon)) +if favicon_32_32 + link(rel="icon" type="image/png" sizes="32x32" href=url_for(favicon_32_32)) +if favicon_16_16 + link(rel="icon" type="image/png" sizes="16x16" href=url_for(favicon_16_16)) +if mask_icon + link(rel="mask-icon" href=url_for(mask_icon) color="#5bbad5") diff --git a/themes/butterfly/layout/includes/head/site_verification.pug b/themes/butterfly/layout/includes/head/site_verification.pug new file mode 100644 index 0000000..8947644 --- /dev/null +++ b/themes/butterfly/layout/includes/head/site_verification.pug @@ -0,0 +1,3 @@ +if theme.site_verification + each item in theme.site_verification + meta(name=item.name content=item.content) \ No newline at end of file diff --git a/themes/butterfly/layout/includes/header/index.pug b/themes/butterfly/layout/includes/header/index.pug new file mode 100644 index 0000000..caa7998 --- /dev/null +++ b/themes/butterfly/layout/includes/header/index.pug @@ -0,0 +1,53 @@ +- + const returnTopImg = img => img !== false ? img || theme.default_top_img : false + const isFixedClass = theme.nav.fixed ? ' fixed' : '' + var top_img = false + let headerClassName = 'not-top-img' + var bg_img = '' + +if !theme.disable_top_img && page.top_img !== false + if is_post() + - top_img = page.top_img || page.cover || theme.default_top_img + else if is_page() + - top_img = page.top_img || theme.default_top_img + else if is_tag() + - top_img = theme.tag_per_img && theme.tag_per_img[page.tag] + - top_img = top_img || returnTopImg(theme.tag_img) + else if is_category() + - top_img = theme.category_per_img && theme.category_per_img[page.category] + - top_img = top_img || returnTopImg(theme.category_img) + else if is_home() + - top_img = returnTopImg(theme.index_img) + else if is_archive() + - top_img = returnTopImg(theme.archive_img) + else + - top_img = page.top_img || theme.default_top_img + + if top_img !== false + - bg_img = getBgPath(top_img) + - headerClassName = is_home() ? 'full_page' : is_post() ? 'post-bg' : 'not-home-page' + +header#page-header(class=`${headerClassName + isFixedClass}` style=bg_img) + include ./nav.pug + if top_img !== false + if is_post() + include ./post-info.pug + else if is_home() + #site-info + h1#site-title=config.title + if theme.subtitle.enable + - var loadSubJs = true + #site-subtitle + span#subtitle + if theme.social + #site_social_icons + !=partial('includes/header/social', {}, {cache: true}) + #scroll-down + i.fas.fa-angle-down.scroll-down-effects + else + #page-site-info + h1#site-title=page.title || page.tag || page.category + else + //- improvement seo + if !is_post() + h1.title-seo=page.title || page.tag || page.category || config.title \ No newline at end of file diff --git a/themes/butterfly/layout/includes/header/menu_item.pug b/themes/butterfly/layout/includes/header/menu_item.pug new file mode 100644 index 0000000..6302ee4 --- /dev/null +++ b/themes/butterfly/layout/includes/header/menu_item.pug @@ -0,0 +1,27 @@ +if theme.menu + .menus_items + each value, label in theme.menu + if typeof value !== 'object' + .menus_item + - const [link, icon] = value.split('||').map(part => trim(part)) + a.site-page(href=url_for(link)) + if icon + i.fa-fw(class=icon) + span= ' ' + label + else + .menus_item + - const [groupLabel, groupIcon, groupClass] = label.split('||').map(part => trim(part)) + - const hideClass = groupClass === 'hide' ? 'hide' : '' + span.site-page.group(class=hideClass) + if groupIcon + i.fa-fw(class=groupIcon) + span= ' ' + groupLabel + i.fas.fa-chevron-down + ul.menus_item_child + each val, lab in value + - const [childLink, childIcon] = val.split('||').map(part => trim(part)) + li + a.site-page.child(href=url_for(childLink)) + if childIcon + i.fa-fw(class=childIcon) + span= ' ' + lab \ No newline at end of file diff --git a/themes/butterfly/layout/includes/header/nav.pug b/themes/butterfly/layout/includes/header/nav.pug new file mode 100644 index 0000000..97d5b1e --- /dev/null +++ b/themes/butterfly/layout/includes/header/nav.pug @@ -0,0 +1,22 @@ +nav#nav + span#blog-info + a.nav-site-title(href=url_for('/')) + if theme.nav.logo + img.site-icon(src=url_for(theme.nav.logo) alt='Logo') + if theme.nav.display_title + span.site-name=config.title + if is_post() + a.nav-page-title(href=url_for('/')) + span.site-name=(page.title || config.title) + #menus + if theme.search.use + #search-button + span.site-page.social-icon.search + i.fas.fa-search.fa-fw + span= ' ' + _p('search.title') + if theme.menu + != partial('includes/header/menu_item', {}, {cache: true}) + + #toggle-menu + span.site-page + i.fas.fa-bars.fa-fw \ No newline at end of file diff --git a/themes/butterfly/layout/includes/header/post-info.pug b/themes/butterfly/layout/includes/header/post-info.pug new file mode 100644 index 0000000..b75dc96 --- /dev/null +++ b/themes/butterfly/layout/includes/header/post-info.pug @@ -0,0 +1,158 @@ +- let comments = theme.comments +#post-info + h1.post-title= page.title || _p('no_title') + if theme.post_edit.enable + a.post-edit-link(href=theme.post_edit.url + page.source title=_p('post.edit') target="_blank") + i.fas.fa-pencil-alt + + #post-meta + .meta-firstline + if theme.post_meta.post.date_type + span.post-meta-date + if theme.post_meta.post.date_type === 'both' + i.far.fa-calendar-alt.fa-fw.post-meta-icon + span.post-meta-label= _p('post.created') + time.post-meta-date-created(datetime=date_xml(page.date) title=_p('post.created') + ' ' + full_date(page.date))= date(page.date, config.date_format) + span.post-meta-separator | + i.fas.fa-history.fa-fw.post-meta-icon + span.post-meta-label= _p('post.updated') + time.post-meta-date-updated(datetime=date_xml(page.updated) title=_p('post.updated') + ' ' + full_date(page.updated))= date(page.updated, config.date_format) + else + - let data_type_update = theme.post_meta.post.date_type === 'updated' + - let date_type = data_type_update ? 'updated' : 'date' + - let date_icon = data_type_update ? 'fas fa-history' : 'far fa-calendar-alt' + - let date_title = data_type_update ? _p('post.updated') : _p('post.created') + i.fa-fw.post-meta-icon(class=date_icon) + span.post-meta-label= date_title + time(datetime=date_xml(page[date_type]) title=date_title + ' ' + full_date(page[date_type]))= date(page[date_type], config.date_format) + if theme.post_meta.post.categories && page.categories.data.length > 0 + span.post-meta-categories + if theme.post_meta.post.date_type + span.post-meta-separator | + each item, index in page.categories.data + i.fas.fa-inbox.fa-fw.post-meta-icon + a(href=url_for(item.path)).post-meta-categories #[=item.name] + if index < page.categories.data.length - 1 + i.fas.fa-angle-right.post-meta-separator + + .meta-secondline + - let postWordcount = theme.wordcount.enable && (theme.wordcount.post_wordcount || theme.wordcount.min2read) + if postWordcount + span.post-meta-separator | + span.post-meta-wordcount + if theme.wordcount.post_wordcount + i.far.fa-file-word.fa-fw.post-meta-icon + span.post-meta-label= _p('post.wordcount') + ':' + span.word-count= wordcount(page.content) + if theme.wordcount.min2read + span.post-meta-separator | + if theme.wordcount.min2read + i.far.fa-clock.fa-fw.post-meta-icon + span.post-meta-label= _p('post.min2read') + ':' + span= min2read(page.content, {cn: 350, en: 160}) + _p('post.min2read_unit') + + //- for pv and count + mixin pvBlock(parent_id, parent_class, parent_title) + span.post-meta-separator | + span(class=parent_class id=parent_id data-flag-title=parent_title) + i.far.fa-eye.fa-fw.post-meta-icon + span.post-meta-label= _p('post.page_pv') + ':' + if block + block + + - const commentUse = comments.use && comments.use[0] + if page.comments !== false && commentUse && !comments.lazyload + case commentUse + when 'Valine' + if theme.valine.visitor + +pvBlock(url_for(page.path), 'leancloud_visitors', page.title) + span.leancloud-visitors-count + i.fa-solid.fa-spinner.fa-spin + when 'Waline' + if theme.waline.pageview + +pvBlock('', '', '') + span.waline-pageview-count(data-path=url_for(page.path)) + i.fa-solid.fa-spinner.fa-spin + when 'Twikoo' + if theme.twikoo.visitor + +pvBlock('', '', '') + span#twikoo_visitors + i.fa-solid.fa-spinner.fa-spin + when 'Artalk' + if theme.artalk.visitor + +pvBlock('', '', '') + span#ArtalkPV + i.fa-solid.fa-spinner.fa-spin + default + if theme.umami_analytics.enable && theme.umami_analytics.UV_PV.page_pv + +pvBlock('', '', '') + span#umamiPV(data-path=url_for(page.path)) + i.fa-solid.fa-spinner.fa-spin + else if theme.busuanzi.page_pv + +pvBlock('', 'post-meta-pv-cv', '') + span#busuanzi_value_page_pv + i.fa-solid.fa-spinner.fa-spin + else + if theme.umami_analytics.enable && theme.umami_analytics.UV_PV.page_pv + +pvBlock('', '', '') + span#umamiPV(data-path=url_for(page.path)) + i.fa-solid.fa-spinner.fa-spin + else if theme.busuanzi.page_pv + +pvBlock('', 'post-meta-pv-cv', '') + span#busuanzi_value_page_pv + i.fa-solid.fa-spinner.fa-spin + + if comments.count && !comments.lazyload && page.comments !== false && comments.use + - var whichCount = comments.use[0] + + mixin countBlock + span.post-meta-separator | + span.post-meta-commentcount + i.far.fa-comments.fa-fw.post-meta-icon + span.post-meta-label= _p('post.comments') + ':' + if block + block + + case whichCount + when 'Disqus' + +countBlock + a.disqus-comment-count(href=full_url_for(page.path) + '#post-comment') + i.fa-solid.fa-spinner.fa-spin + when 'Disqusjs' + +countBlock + a.disqusjs-comment-count(href=full_url_for(page.path) + '#post-comment') + i.fa-solid.fa-spinner.fa-spin + when 'Valine' + +countBlock + a(href=url_for(page.path) + '#post-comment' itemprop="discussionUrl") + span.valine-comment-count(data-xid=url_for(page.path) itemprop="commentCount") + i.fa-solid.fa-spinner.fa-spin + when 'Waline' + +countBlock + a(href=url_for(page.path) + '#post-comment') + span.waline-comment-count(data-path=url_for(page.path)) + i.fa-solid.fa-spinner.fa-spin + when 'Gitalk' + +countBlock + a(href=url_for(page.path) + '#post-comment') + span.gitalk-comment-count + i.fa-solid.fa-spinner.fa-spin + when 'Twikoo' + +countBlock + a(href=url_for(page.path) + '#post-comment') + span#twikoo-count + i.fa-solid.fa-spinner.fa-spin + when 'Facebook Comments' + +countBlock + a(href=url_for(page.path) + '#post-comment') + span.fb-comments-count(data-href=urlNoIndex()) + when 'Remark42' + +countBlock + a(href=url_for(page.path) + '#post-comment') + span.remark42__counter(data-url=urlNoIndex()) + i.fa-solid.fa-spinner.fa-spin + when 'Artalk' + +countBlock + a(href=url_for(page.path) + '#post-comment') + span#ArtalkCount + i.fa-solid.fa-spinner.fa-spin \ No newline at end of file diff --git a/themes/butterfly/layout/includes/header/social.pug b/themes/butterfly/layout/includes/header/social.pug new file mode 100644 index 0000000..81b47cc --- /dev/null +++ b/themes/butterfly/layout/includes/header/social.pug @@ -0,0 +1,8 @@ +each url, icon in theme.social + - + const [link, title, color] = url.split('||').map(i => trim(i)) + const href = url_for(link) + const iconStyle = color ? `color: ${color.replace(/[\'\"]/g, '')};` : '' + const iconTitle = title || '' + a.social-icon(href=href target="_blank" title=iconTitle) + i(class=icon style=iconStyle) \ No newline at end of file diff --git a/themes/butterfly/layout/includes/layout.pug b/themes/butterfly/layout/includes/layout.pug new file mode 100644 index 0000000..81e7e25 --- /dev/null +++ b/themes/butterfly/layout/includes/layout.pug @@ -0,0 +1,36 @@ +- var htmlClassHideAside = theme.aside.enable && theme.aside.hide ? 'hide-aside' : '' +- page.aside = is_archive() ? theme.aside.display.archive: is_category() ? theme.aside.display.category : is_tag() ? theme.aside.display.tag : page.aside +- var hideAside = !theme.aside.enable || page.aside === false ? 'hide-aside' : '' +- var pageType = is_post() ? 'post' : 'page' +- pageType = page.type ? pageType + ' type-' + page.type : pageType + +doctype html +html(lang=config.language data-theme=theme.display_mode class=htmlClassHideAside) + head + include ./head.pug + body + !=partial('includes/loading/index', {}, {cache: true}) + + if theme.background + #web_bg(style=getBgPath(theme.background)) + + !=partial('includes/sidebar', {}, {cache: true}) + + #body-wrap(class=pageType) + include ./header/index.pug + + main#content-inner.layout(class=hideAside) + if body + div!= body + else + block content + if theme.aside.enable && page.aside !== false + include widget/index.pug + + - const footerBg = theme.footer_img + - const footer_bg = footerBg ? footerBg === true ? bg_img : getBgPath(footerBg) : '' + footer#footer(style=footer_bg) + !=partial('includes/footer', {}, {cache: true}) + + include ./rightside.pug + include ./additional-js.pug \ No newline at end of file diff --git a/themes/butterfly/layout/includes/loading/fullpage-loading.pug b/themes/butterfly/layout/includes/loading/fullpage-loading.pug new file mode 100644 index 0000000..774a6b9 --- /dev/null +++ b/themes/butterfly/layout/includes/loading/fullpage-loading.pug @@ -0,0 +1,33 @@ +#loading-box + .loading-left-bg + .loading-right-bg + .spinner-box + .configure-border-1 + .configure-core + .configure-border-2 + .configure-core + .loading-word= _p('loading') + +script. + (()=>{ + const $loadingBox = document.getElementById('loading-box') + const $body = document.body + const preloader = { + endLoading: () => { + $body.style.overflow = '' + $loadingBox.classList.add('loaded') + }, + initLoading: () => { + $body.style.overflow = 'hidden' + $loadingBox.classList.remove('loaded') + } + } + + preloader.initLoading() + window.addEventListener('load', preloader.endLoading) + + if (!{theme.pjax && theme.pjax.enable}) { + btf.addGlobalFn('pjaxSend', preloader.initLoading, 'preloader_init') + btf.addGlobalFn('pjaxComplete', preloader.endLoading, 'preloader_end') + } + })() \ No newline at end of file diff --git a/themes/butterfly/layout/includes/loading/index.pug b/themes/butterfly/layout/includes/loading/index.pug new file mode 100644 index 0000000..6a6facc --- /dev/null +++ b/themes/butterfly/layout/includes/loading/index.pug @@ -0,0 +1,5 @@ +if theme.preloader.enable + if theme.preloader.source === 1 + include ./fullpage-loading.pug + else + include ./pace.pug \ No newline at end of file diff --git a/themes/butterfly/layout/includes/loading/pace.pug b/themes/butterfly/layout/includes/loading/pace.pug new file mode 100644 index 0000000..4cf2a4e --- /dev/null +++ b/themes/butterfly/layout/includes/loading/pace.pug @@ -0,0 +1,12 @@ +script. + window.paceOptions = { + restartOnPushState: false + } + + btf.addGlobalFn('pjaxSend', () => { + Pace.restart() + }, 'pace_restart') + + +link(rel="stylesheet", href=url_for(theme.preloader.pace_css_url || theme.asset.pace_default_css)) +script(src=url_for(theme.asset.pace_js)) \ No newline at end of file diff --git a/themes/butterfly/layout/includes/mixins/article-sort.pug b/themes/butterfly/layout/includes/mixins/article-sort.pug new file mode 100644 index 0000000..acc5d9f --- /dev/null +++ b/themes/butterfly/layout/includes/mixins/article-sort.pug @@ -0,0 +1,23 @@ +mixin articleSort(posts) + .article-sort + - let year + - posts.forEach(article => { + - const tempYear = date(article.date, 'YYYY') + - const noCoverClass = article.cover === false || !theme.cover.archives_enable ? 'no-article-cover' : '' + - const title = article.title || _p('no_title') + if tempYear !== year + - year = tempYear + .article-sort-item.year= year + .article-sort-item(class=noCoverClass) + if article.cover && theme.cover.archives_enable + a.article-sort-item-img(href=url_for(article.path) title=title) + if article.cover_type === 'img' + img(src=url_for(article.cover) alt=title onerror=`this.onerror=null;this.src='${url_for(theme.error_img.post_page)}'`) + else + div(style=`background: ${article.cover}`) + .article-sort-item-info + .article-sort-item-time + i.far.fa-calendar-alt + time.post-meta-date-created(datetime=date_xml(article.date) title=_p('post.created') + ' ' + full_date(article.date))= date(article.date, config.date_format) + a.article-sort-item-title(href=url_for(article.path) title=title)= title + - }) \ No newline at end of file diff --git a/themes/butterfly/layout/includes/mixins/indexPostUI.pug b/themes/butterfly/layout/includes/mixins/indexPostUI.pug new file mode 100644 index 0000000..7e80df9 --- /dev/null +++ b/themes/butterfly/layout/includes/mixins/indexPostUI.pug @@ -0,0 +1,122 @@ +mixin indexPostUI() + - const indexLayout = theme.index_layout + - const masonryLayoutClass = (indexLayout === 6 || indexLayout === 7) ? 'masonry' : '' + #recent-posts.recent-posts.nc(class=masonryLayoutClass) + .recent-post-items + each article, index in page.posts.data + .recent-post-item + - const link = article.link || article.path + - const title = article.title || _p('no_title') + - const leftOrRight = indexLayout === 3 ? (index % 2 === 0 ? 'left' : 'right') : (indexLayout === 2 ? 'right' : '') + - const post_cover = article.cover + - const no_cover = article.cover === false || !theme.cover.index_enable ? 'no-cover' : '' + + if post_cover && theme.cover.index_enable + .post_cover(class=leftOrRight) + a(href=url_for(link) title=title) + if article.cover_type === 'img' + img.post-bg(src=url_for(post_cover) onerror=`this.onerror=null;this.src='${url_for(theme.error_img.post_page)}'` alt=title) + else + div.post-bg(style=`background: ${post_cover}`) + .recent-post-info(class=no_cover) + a.article-title(href=url_for(link) title=title) + if is_home() && (article.top || article.sticky > 0) + i.fas.fa-thumbtack.sticky + = title + .article-meta-wrap + if theme.post_meta.page.date_type + span.post-meta-date + if theme.post_meta.page.date_type === 'both' + i.far.fa-calendar-alt + span.article-meta-label=_p('post.created') + time.post-meta-date-created(datetime=date_xml(article.date) title=_p('post.created') + ' ' + full_date(article.date))= date(article.date, config.date_format) + span.article-meta-separator | + i.fas.fa-history + span.article-meta-label=_p('post.updated') + time.post-meta-date-updated(datetime=date_xml(article.updated) title=_p('post.updated') + ' ' + full_date(article.updated))= date(article.updated, config.date_format) + else + - const data_type_updated = theme.post_meta.page.date_type === 'updated' + - const date_type = data_type_updated ? 'updated' : 'date' + - const date_icon = data_type_updated ? 'fas fa-history' : 'far fa-calendar-alt' + - const date_title = data_type_updated ? _p('post.updated') : _p('post.created') + i(class=date_icon) + span.article-meta-label= date_title + time(datetime=date_xml(article[date_type]) title=date_title + ' ' + full_date(article[date_type]))= date(article[date_type], config.date_format) + if theme.post_meta.page.categories && article.categories.data.length > 0 + span.article-meta + span.article-meta-separator | + each item, index in article.categories.data + i.fas.fa-inbox + a(href=url_for(item.path)).article-meta__categories #[=item.name] + if index < article.categories.data.length - 1 + i.fas.fa-angle-right.article-meta-link + if theme.post_meta.page.tags && article.tags.length > 0 + span.article-meta.tags + span.article-meta-separator | + each item, index in article.tags.data + i.fas.fa-tag + a(href=url_for(item.path)).article-meta__tags #[=item.name] + if index < article.tags.data.length - 1 + span.article-meta-link #[='•'] + + mixin countBlockInIndex + - needLoadCountJs = true + span.article-meta + span.article-meta-separator | + i.fas.fa-comments + if block + block + span.article-meta-label= ' ' + _p('card_post_count') + + if theme.comments.card_post_count && theme.comments.use + case theme.comments.use[0] + when 'Disqus' + when 'Disqusjs' + +countBlockInIndex + a.disqus-count(href=full_url_for(link) + '#post-comment') + i.fa-solid.fa-spinner.fa-spin + when 'Valine' + +countBlockInIndex + a(href=url_for(link) + '#post-comment') + span.valine-comment-count(data-xid=url_for(link)) + i.fa-solid.fa-spinner.fa-spin + when 'Waline' + +countBlockInIndex + a(href=url_for(link) + '#post-comment') + span.waline-comment-count(data-path=url_for(link)) + i.fa-solid.fa-spinner.fa-spin + when 'Twikoo' + +countBlockInIndex + a.twikoo-count(href=url_for(link) + '#post-comment') + i.fa-solid.fa-spinner.fa-spin + when 'Facebook Comments' + +countBlockInIndex + a(href=url_for(link) + '#post-comment') + span.fb-comments-count(data-href=urlNoIndex(article.permalink)) + when 'Remark42' + +countBlockInIndex + a(href=url_for(link) + '#post-comment') + span.remark42__counter(data-url=urlNoIndex(article.permalink)) + i.fa-solid.fa-spinner.fa-spin + when 'Artalk' + +countBlockInIndex + a(href=url_for(link) + '#post-comment') + span.artalk-count(data-page-key=url_for(link)) + i.fa-solid.fa-spinner.fa-spin + + //- Display the article introduction on homepage + case theme.index_post_content.method + when false + - break + when 1 + .content!= article.description + when 2 + .content!= article.description || truncate(article.content, theme.index_post_content.length) + default + .content!= truncate(article.content, theme.index_post_content.length) + + if theme.ad && theme.ad.index + if (index + 1) % 3 === 0 + .recent-post-item.ads-wrap!= theme.ad.index + + include ../pagination.pug \ No newline at end of file diff --git a/themes/butterfly/layout/includes/page/404.pug b/themes/butterfly/layout/includes/page/404.pug new file mode 100644 index 0000000..9444204 --- /dev/null +++ b/themes/butterfly/layout/includes/page/404.pug @@ -0,0 +1,8 @@ +- var top_img_404 = theme.error_404.background || theme.default_top_img + +.error-content + .error-img + img(src=url_for(top_img_404) alt='Page not found') + .error-info + h1.error_title= '404' + .error_subtitle= theme.error_404.subtitle || _p('error404') diff --git a/themes/butterfly/layout/includes/page/categories.pug b/themes/butterfly/layout/includes/page/categories.pug new file mode 100644 index 0000000..79153c8 --- /dev/null +++ b/themes/butterfly/layout/includes/page/categories.pug @@ -0,0 +1 @@ +.category-lists!= list_categories() \ No newline at end of file diff --git a/themes/butterfly/layout/includes/page/default-page.pug b/themes/butterfly/layout/includes/page/default-page.pug new file mode 100644 index 0000000..e7057f7 --- /dev/null +++ b/themes/butterfly/layout/includes/page/default-page.pug @@ -0,0 +1,2 @@ +#article-container + != page.content \ No newline at end of file diff --git a/themes/butterfly/layout/includes/page/flink.pug b/themes/butterfly/layout/includes/page/flink.pug new file mode 100644 index 0000000..10d3893 --- /dev/null +++ b/themes/butterfly/layout/includes/page/flink.pug @@ -0,0 +1,82 @@ +#article-container + .flink + - let { content, random, flink_url } = page + - let pageContent = content + + if flink_url || random + - const linkData = flink_url ? false : site.data.link || false + script. + (()=>{ + const replaceSymbol = (str) => { + return str.replace(/[\p{P}\p{S}]/gu, "-") + } + + let result = "" + const add = (str) => { + for(let i = 0; i < str.length; i++){ + const replaceClassName = replaceSymbol(str[i].class_name) + const className = str[i].class_name ? `

${str[i].class_name}

` : "" + const classDesc = str[i].class_desc ? `` : "" + + let listResult = "" + const lists = str[i].link_list + if (!{random === true}) { + lists.sort(() => Math.random() - 0.5) + } + for(let j = 0; j < lists.length; j++){ + listResult += ` + ` + } + + result += `${className}${classDesc} ` + } + + document.querySelector(".flink").insertAdjacentHTML("afterbegin", result) + window.lazyLoadInstance && window.lazyLoadInstance.update() + } + + const linkData = !{JSON.stringify(linkData)} + if (!{Boolean(flink_url)}) { + fetch("!{url_for(flink_url)}") + .then(response => response.json()) + .then(add) + } else if (linkData) { + add(linkData) + } + })() + + else + if site.data.link + - let result = "" + each i in site.data.link + - let className = i.class_name ? markdown(`## ${i.class_name}`) : "" + - let classDesc = i.class_desc ? `` : "" + + - let listResult = "" + + each j in i.link_list + - + listResult += ` + ` + - + + - result += `${className}${classDesc} ` + + - pageContent = result + pageContent + != pageContent diff --git a/themes/butterfly/layout/includes/page/shuoshuo.pug b/themes/butterfly/layout/includes/page/shuoshuo.pug new file mode 100644 index 0000000..745b048 --- /dev/null +++ b/themes/butterfly/layout/includes/page/shuoshuo.pug @@ -0,0 +1,103 @@ +//- - author: +//- avatar: +//- date: +//- content: +//- tags: +//- - tag1 +//- - tag2 + + +- page.comments = false +- page.toc = false +#article-container + if page.shuoshuo_url + script. + (() => { + const loadShuoshuo = async () => { + try { + const fetchContent = await fetch('!{url_for(page.shuoshuo_url)}') + const shuoshuo = await fetchContent.json() + + let start = 0 + const container = document.getElementById('article-container') + + const addData = data => { + const cLength = data.length + const end = start + 10 > cLength ? cLength : start + 10 + let result = '' + data.slice(start, end).forEach((item) => { + result += ` +
+
+
+ +
+
+
${item.author || '!{config.author}'}
+
${btf.diffDate(item.date, true)}
+
+
+
+ ${item.content} +
+ +
+ ` + }) + + start = end + container.insertAdjacentHTML('beforeend', result) + + if (start >= cLength) { + observer.disconnect() + } else { + setTimeout(() => observer.observe(container.lastElementChild), 100) + } + + window.lazyLoadInstance.update() + btf.loadLightbox(document.querySelectorAll('#article-container img:not(.no-lightbox)')) + } + + addData(shuoshuo) + + const observer = new IntersectionObserver((entries) => { + if (entries[0].isIntersecting) { + observer.unobserve(entries[0].target) + addData(shuoshuo) + } + }, { + root: null, + rootMargin: '0px', + threshold: 1.0 + }) + + if (container.lastElementChild) { + observer.observe(container.lastElementChild) + } + } catch (e) { + console.error(e) + } + } + + window.pjax ? loadShuoshuo() : window.addEventListener('load', loadShuoshuo) + })() + else + if site.data.shuoshuo + each i in site.data.shuoshuo + .shuoshuo-item + .shuoshuo-item-header + .shuoshuo-avatar + img.no-lightbox(src=i.avatar || url_for(theme.avatar.img)) + .shuoshuo-info + .shuoshuo-author=i.author || config.author + .shuoshuo-date=relative_date(i.date) + .shuoshuo-content + !=markdown(i.content) + .shuoshuo-footer + .shuoshuo-tags + each tag in i.tags + span.shuoshuo-tag=tag \ No newline at end of file diff --git a/themes/butterfly/layout/includes/page/tags.pug b/themes/butterfly/layout/includes/page/tags.pug new file mode 100644 index 0000000..b579112 --- /dev/null +++ b/themes/butterfly/layout/includes/page/tags.pug @@ -0,0 +1,2 @@ +.tag-cloud-list.is-center + !=cloudTags({source: site.tags, orderby: page.orderby || 'random', order: page.order || 1, minfontsize: 1.2, maxfontsize: 1.5, limit: 0, unit: 'em'}) \ No newline at end of file diff --git a/themes/butterfly/layout/includes/pagination.pug b/themes/butterfly/layout/includes/pagination.pug new file mode 100644 index 0000000..7cbe946 --- /dev/null +++ b/themes/butterfly/layout/includes/pagination.pug @@ -0,0 +1,39 @@ +- + var options = { + prev_text: '', + next_text: '', + mid_size: 1, + escape: false + } + +if is_post() + - let prev = theme.post_pagination === 1 ? page.prev : page.next + - let next = theme.post_pagination === 1 ? page.next : page.prev + nav#pagination.pagination-post + if(prev) + - var hasPageNext = next ? 'pull-left' : 'pull-full' + a.prev-post(class=hasPageNext href=url_for(prev.path) title=prev.title) + if prev.cover_type === 'img' + img.cover(src=url_for(prev.cover) onerror=`onerror=null;src='${url_for(theme.error_img.post_page)}'` alt='cover of previous post') + else + .cover(style=`background: ${prev.cover || 'var(--default-bg-color)'}`) + .pagination-info + .label=_p('pagination.prev') + .prev_info=prev.title + + if(next) + - var hasPagePrev = prev ? 'pull-right' : 'pull-full' + a.next-post(class=hasPagePrev href=url_for(next.path) title=next.title) + if next.cover_type === 'img' + img.cover(src=url_for(next.cover) onerror=`onerror=null;src='${url_for(theme.error_img.post_page)}'` alt='cover of next post') + else + .cover(style=`background: ${next.cover || 'var(--default-bg-color)'}`) + .pagination-info + .label=_p('pagination.next') + .next_info=next.title +else + nav#pagination + .pagination + if is_home() + - options.format = 'page/%d/#content-inner' + !=paginator(options) \ No newline at end of file diff --git a/themes/butterfly/layout/includes/post/post-copyright.pug b/themes/butterfly/layout/includes/post/post-copyright.pug new file mode 100644 index 0000000..c76f4b4 --- /dev/null +++ b/themes/butterfly/layout/includes/post/post-copyright.pug @@ -0,0 +1,23 @@ +if theme.post_copyright.enable && page.copyright !== false + - const author = page.copyright_author || config.author + - const authorHref = page.copyright_author_href || theme.post_copyright.author_href || config.url + - const url = page.copyright_url || page.permalink + - const info = page.copyright_info || _p('post.copyright.copyright_content', theme.post_copyright.license_url, theme.post_copyright.license, config.url, config.title) + .post-copyright + .post-copyright__author + span.post-copyright-meta + i.fas.fa-circle-user.fa-fw + = _p('post.copyright.author') + ": " + span.post-copyright-info + a(href=authorHref)= author + .post-copyright__type + span.post-copyright-meta + i.fas.fa-square-arrow-up-right.fa-fw + = _p('post.copyright.link') + ": " + span.post-copyright-info + a(href=url_for(url))= theme.post_copyright.decode ? decodeURI(url) : url + .post-copyright__notice + span.post-copyright-meta + i.fas.fa-circle-exclamation.fa-fw + = _p('post.copyright.copyright_notice') + ": " + span.post-copyright-info!= info \ No newline at end of file diff --git a/themes/butterfly/layout/includes/post/reward.pug b/themes/butterfly/layout/includes/post/reward.pug new file mode 100644 index 0000000..fe62f14 --- /dev/null +++ b/themes/butterfly/layout/includes/post/reward.pug @@ -0,0 +1,12 @@ +.post-reward + .reward-button + i.fas.fa-qrcode + = theme.reward.text || _p('donate') + .reward-main + ul.reward-all + each item in theme.reward.QR_code + - const clickTo = item.link || item.img + li.reward-item + a(href=url_for(clickTo) target='_blank') + img.post-qr-code-img(src=url_for(item.img) alt=item.text) + .post-qr-code-desc=item.text \ No newline at end of file diff --git a/themes/butterfly/layout/includes/rightside.pug b/themes/butterfly/layout/includes/rightside.pug new file mode 100644 index 0000000..01c8021 --- /dev/null +++ b/themes/butterfly/layout/includes/rightside.pug @@ -0,0 +1,61 @@ +- const { readmode, translate, darkmode, aside, chat } = theme +mixin rightsideItem(array) + each item in array + case item + when 'readmode' + if is_post() && readmode + button#readmode(type="button" title=_p('rightside.readmode_title')) + i.fas.fa-book-open + when 'translate' + if translate.enable + button#translateLink(type="button" title=_p('rightside.translate_title'))= translate.default + when 'darkmode' + if darkmode.enable && darkmode.button + button#darkmode(type="button" title=_p('rightside.night_mode_title')) + i.fas.fa-adjust + when 'hideAside' + if aside.enable && aside.button && page.aside !== false + button#hide-aside-btn(type="button" title=_p('rightside.aside')) + i.fas.fa-arrows-alt-h + when 'toc' + if showToc + button#mobile-toc-button.close(type="button" title=_p("rightside.toc")) + i.fas.fa-list-ul + when 'chat' + if chat.rightside_button && chat.use + button#chat-btn(type="button" title=_p("rightside.chat")) + i.fas.fa-message + when 'comment' + if commentsJsLoad + a#to_comment(href="#post-comment" title=_p("rightside.scroll_to_comment")) + i.fas.fa-comments + +#rightside + - const { enable, hide, show } = theme.rightside_item_order + - const hideArray = enable ? hide && hide.split(',') : ['readmode','translate','darkmode','hideAside'] + - const showArray = enable ? show && show.split(',') : ['toc','chat','comment'] + + + #rightside-config-hide + if hideArray + +rightsideItem(hideArray) + #rightside-config-show + if enable + if hide + button#rightside-config(type="button" title=_p("rightside.setting")) + i.fas.fa-cog.fa-spin + else + if is_post() + if (readmode || translate.enable || (darkmode.enable && darkmode.button)) + button#rightside-config(type="button" title=_p("rightside.setting")) + i.fas.fa-cog.fa-spin + else if translate.enable || (darkmode.enable && darkmode.button) + button#rightside-config(type="button" title=_p("rightside.setting")) + i.fas.fa-cog.fa-spin + + if showArray + +rightsideItem(showArray) + + button#go-up(type="button" title=_p("rightside.back_to_top")) + span.scroll-percent + i.fas.fa-arrow-up \ No newline at end of file diff --git a/themes/butterfly/layout/includes/sidebar.pug b/themes/butterfly/layout/includes/sidebar.pug new file mode 100644 index 0000000..7eabbf1 --- /dev/null +++ b/themes/butterfly/layout/includes/sidebar.pug @@ -0,0 +1,18 @@ +if theme.menu + #sidebar + #menu-mask + #sidebar-menus + .avatar-img.is-center + img(src=url_for(theme.avatar.img) onerror=`onerror=null;src='${theme.error_img.flink}'` alt="avatar") + .site-data.is-center + a(href=url_for(config.archive_dir) + '/') + .headline= _p('aside.articles') + .length-num= site.posts.length + a(href=url_for(config.tag_dir) + '/' ) + .headline= _p('aside.tags') + .length-num= site.tags.length + a(href=url_for(config.category_dir) + '/') + .headline= _p('aside.categories') + .length-num= site.categories.length + + !=partial('includes/header/menu_item', {}, {cache: true}) diff --git a/themes/butterfly/layout/includes/third-party/abcjs/abcjs.pug b/themes/butterfly/layout/includes/third-party/abcjs/abcjs.pug new file mode 100644 index 0000000..089ee2b --- /dev/null +++ b/themes/butterfly/layout/includes/third-party/abcjs/abcjs.pug @@ -0,0 +1,17 @@ +script. + (() => { + const abcjsInit = () => { + const abcjsFn = () => { + document.querySelectorAll(".abc-music-sheet").forEach(ele => { + if (ele.children.length > 0) return + ABCJS.renderAbc(ele, ele.innerHTML, {responsive: 'resize'}) + }) + } + + typeof ABCJS === 'object' ? abcjsFn() + : btf.getScript('!{url_for(theme.asset.abcjs_basic_js)}').then(abcjsFn) + } + + window.pjax ? abcjsInit() : window.addEventListener('load', abcjsInit) + btf.addGlobalFn('encrypt', abcjsInit, 'abcjs') + })() \ No newline at end of file diff --git a/themes/butterfly/layout/includes/third-party/abcjs/index.pug b/themes/butterfly/layout/includes/third-party/abcjs/index.pug new file mode 100644 index 0000000..12d1478 --- /dev/null +++ b/themes/butterfly/layout/includes/third-party/abcjs/index.pug @@ -0,0 +1,6 @@ +if theme.abcjs.enable + if theme.abcjs.per_page + if is_post() || is_page() + include ./abcjs.pug + else if page.abcjs + include ./abcjs.pug diff --git a/themes/butterfly/layout/includes/third-party/aplayer.pug b/themes/butterfly/layout/includes/third-party/aplayer.pug new file mode 100644 index 0000000..cf875b6 --- /dev/null +++ b/themes/butterfly/layout/includes/third-party/aplayer.pug @@ -0,0 +1,23 @@ +link(rel='stylesheet' href=url_for(theme.asset.aplayer_css) media="print" onload="this.media='all'") +script(src=url_for(theme.asset.aplayer_js)) +script(src=url_for(theme.asset.meting_js)) +if theme.pjax.enable + script. + (() => { + const destroyAplayer = () => { + if (window.aplayers) { + for (let i = 0; i < window.aplayers.length; i++) { + if (!window.aplayers[i].options.fixed) { + window.aplayers[i].destroy() + } + } + } + } + + const runMetingJS = () => { + typeof loadMeting === 'function' && document.getElementsByClassName('aplayer').length && loadMeting() + } + + btf.addGlobalFn('pjaxSend', destroyAplayer, 'destroyAplayer') + btf.addGlobalFn('pjaxComplete', loadMeting, 'runMetingJS') + })() diff --git a/themes/butterfly/layout/includes/third-party/card-post-count/artalk.pug b/themes/butterfly/layout/includes/third-party/card-post-count/artalk.pug new file mode 100644 index 0000000..b5a1056 --- /dev/null +++ b/themes/butterfly/layout/includes/third-party/card-post-count/artalk.pug @@ -0,0 +1,31 @@ +- const { server, site } = theme.artalk + +script. + (() => { + const getArtalkCount = async() => { + try { + const eleGroup = document.querySelectorAll('#recent-posts .artalk-count') + const keyArray = Array.from(eleGroup).map(i => i.getAttribute('data-page-key')) + + const headerList = { + method: 'GET', + } + + const searchParams = new URLSearchParams({ + 'site_name': '!{site}', + 'page_keys': keyArray + }) + + const res = await fetch(`!{server}/api/v2/stats/page_comment?${searchParams}`, headerList) + const result = await res.json() + + keyArray.forEach((key, index) => { + eleGroup[index].textContent = result.data[key] || 0 + }) + } catch (err) { + console.error(err) + } + } + + window.pjax ? getArtalkCount() : window.addEventListener('load', getArtalkCount) + })() \ No newline at end of file diff --git a/themes/butterfly/layout/includes/third-party/card-post-count/disqus.pug b/themes/butterfly/layout/includes/third-party/card-post-count/disqus.pug new file mode 100644 index 0000000..92a2d5b --- /dev/null +++ b/themes/butterfly/layout/includes/third-party/card-post-count/disqus.pug @@ -0,0 +1,25 @@ +- const { shortname, apikey } = theme.disqus +script. + (() => { + const getCount = async () => { + try { + const eleGroup = document.querySelectorAll('#recent-posts .disqus-count') + const cleanedLinks = Array.from(eleGroup).map(i => `thread:link=${i.href.replace(/#post-comment$/, '')}`); + + const res = await fetch(`https://disqus.com/api/3.0/threads/set.json?forum=!{shortname}&api_key=!{apikey}&${cleanedLinks.join('&')}`,{ + method: 'GET' + }) + const result = await res.json() + + eleGroup.forEach(i => { + const cleanedLink = i.href.replace(/#post-comment$/, '') + const urlData = result.response.find(data => data.link === cleanedLink) || { posts: 0 } + i.textContent = urlData.posts + }) + } catch (err) { + console.error(err) + } + } + + window.pjax ? getCount() : window.addEventListener('load', getCount) + })() diff --git a/themes/butterfly/layout/includes/third-party/card-post-count/fb.pug b/themes/butterfly/layout/includes/third-party/card-post-count/fb.pug new file mode 100644 index 0000000..e6daba1 --- /dev/null +++ b/themes/butterfly/layout/includes/third-party/card-post-count/fb.pug @@ -0,0 +1,18 @@ +- const fbSDKVer = 'v20.0' +- const fbSDK = `https://connect.facebook.net/${theme.facebook_comments.lang}/sdk.js#xfbml=1&version=${fbSDKVer}` + +script. + (()=>{ + function loadFBComment () { + if (typeof FB === 'object') FB.XFBML.parse(document.getElementById('recent-posts')) + else { + let ele = document.createElement('script') + ele.setAttribute('src','!{fbSDK}') + ele.setAttribute('async', 'true') + ele.setAttribute('defer', 'true') + ele.setAttribute('crossorigin', 'anonymous') + document.body.appendChild(ele) + } + } + window.pjax ? loadFBComment() : window.addEventListener('load', loadFBComment) + })() diff --git a/themes/butterfly/layout/includes/third-party/card-post-count/index.pug b/themes/butterfly/layout/includes/third-party/card-post-count/index.pug new file mode 100644 index 0000000..5b2685c --- /dev/null +++ b/themes/butterfly/layout/includes/third-party/card-post-count/index.pug @@ -0,0 +1,16 @@ +case theme.comments.use[0] + when 'Twikoo' + include ./twikoo.pug + when 'Disqus' + when 'Disqusjs' + include ./disqus.pug + when 'Valine' + include ./valine.pug + when 'Waline' + include ./waline.pug + when 'Facebook Comments' + include ./fb.pug + when 'Remark42' + include ./remark42.pug + when 'Artalk' + include ./artalk.pug \ No newline at end of file diff --git a/themes/butterfly/layout/includes/third-party/card-post-count/remark42.pug b/themes/butterfly/layout/includes/third-party/card-post-count/remark42.pug new file mode 100644 index 0000000..b67164f --- /dev/null +++ b/themes/butterfly/layout/includes/third-party/card-post-count/remark42.pug @@ -0,0 +1,18 @@ +- const { host, siteId, option } = theme.remark42 + +script. + (()=>{ + window.remark_config = Object.assign({ + host: '!{host}', + site_id: '!{siteId}', + },!{JSON.stringify(option)}) + + function getCount () { + const s = document.createElement('script') + s.src = remark_config.host + '/web/counter.js' + s.defer = true + document.head.appendChild(s) + } + + window.pjax ? getCount() : window.addEventListener('load', getCount) + })() \ No newline at end of file diff --git a/themes/butterfly/layout/includes/third-party/card-post-count/twikoo.pug b/themes/butterfly/layout/includes/third-party/card-post-count/twikoo.pug new file mode 100644 index 0000000..8d47c05 --- /dev/null +++ b/themes/butterfly/layout/includes/third-party/card-post-count/twikoo.pug @@ -0,0 +1,37 @@ +script. + (() => { + const getCommentUrl = () => { + const eleGroup = document.querySelectorAll('#recent-posts .article-title') + let urlArray = [] + eleGroup.forEach(i=>{ + urlArray.push(i.getAttribute('href')) + }) + return urlArray + } + + const getCount = () => { + const runTwikoo = () => { + twikoo.getCommentsCount({ + envId: '!{theme.twikoo.envId}', + region: '!{theme.twikoo.region}', + urls: getCommentUrl(), + includeReply: false + }).then(function (res) { + document.querySelectorAll('#recent-posts .twikoo-count').forEach((item,index) => { + item.textContent = res[index].count + }) + }).catch(function (err) { + console.log(err) + }) + } + + if (typeof twikoo === 'object') { + runTwikoo() + } else { + btf.getScript('!{url_for(theme.asset.twikoo)}').then(runTwikoo) + } + } + + window.pjax ? getCount() : window.addEventListener('load', getCount) + + })() \ No newline at end of file diff --git a/themes/butterfly/layout/includes/third-party/card-post-count/valine.pug b/themes/butterfly/layout/includes/third-party/card-post-count/valine.pug new file mode 100644 index 0000000..1bbddde --- /dev/null +++ b/themes/butterfly/layout/includes/third-party/card-post-count/valine.pug @@ -0,0 +1,20 @@ +script. + (() => { + function loadValine () { + function initValine () { + let initData = { + el: '#vcomment', + appId: '#{theme.valine.appId}', + appKey: '#{theme.valine.appKey}', + serverURLs: '#{theme.valine.serverURLs}' + } + + const valine = new Valine(initData) + } + + if (typeof Valine === 'function') initValine() + else btf.getScript('!{url_for(theme.asset.valine)}').then(initValine) + } + + window.pjax ? loadValine() : window.addEventListener('load', loadValine) + })() diff --git a/themes/butterfly/layout/includes/third-party/card-post-count/waline.pug b/themes/butterfly/layout/includes/third-party/card-post-count/waline.pug new file mode 100644 index 0000000..a8faf96 --- /dev/null +++ b/themes/butterfly/layout/includes/third-party/card-post-count/waline.pug @@ -0,0 +1,21 @@ +- const serverURL = theme.waline.serverURL.replace(/\/$/, '') +script. + (() => { + async function loadWaline () { + try { + const eleGroup = document.querySelectorAll('#recent-posts .waline-comment-count') + const keyArray = Array.from(eleGroup).map(i => i.getAttribute('data-path')) + + const res = await fetch(`!{serverURL}/api/comment?type=count&url=${keyArray}`, { method: 'GET' }) + const result = await res.json() + + result.data.forEach((count, index) => { + eleGroup[index].textContent = count + }) + } catch (err) { + console.error(err) + } + } + + window.pjax ? loadWaline() : window.addEventListener('load', loadWaline) + })() diff --git a/themes/butterfly/layout/includes/third-party/chat/chatra.pug b/themes/butterfly/layout/includes/third-party/chat/chatra.pug new file mode 100644 index 0000000..41f0423 --- /dev/null +++ b/themes/butterfly/layout/includes/third-party/chat/chatra.pug @@ -0,0 +1,42 @@ +//- https://chatra.io/help/api/ +script. + (() => { + const isChatBtn = !{theme.chat.rightside_button} + const isChatHideShow = !{theme.chat.button_hide_show} + + if (isChatBtn) { + const close = () => { + Chatra('minimizeWidget') + Chatra('hide') + } + + const open = () => { + Chatra('openChat', true) + Chatra('show') + } + + window.ChatraSetup = { startHidden: true } + + window.chatBtnFn = () => { + document.getElementById('chatra').classList.contains('chatra--expanded') ? close() : open() + } + } else if (isChatHideShow) { + window.chatBtn = { + hide: () => Chatra('hide'), + show: () => Chatra('show') + } + } + + (function(d, w, c) { + w.ChatraID = '#{theme.chatra.id}' + var s = d.createElement('script') + w[c] = w[c] || function() { + (w[c].q = w[c].q || []).push(arguments) + } + s.async = true + s.src = 'https://call.chatra.io/chatra.js' + if (d.head) d.head.appendChild(s) + })(document, window, 'Chatra') + })() + + diff --git a/themes/butterfly/layout/includes/third-party/chat/crisp.pug b/themes/butterfly/layout/includes/third-party/chat/crisp.pug new file mode 100644 index 0000000..e25a17f --- /dev/null +++ b/themes/butterfly/layout/includes/third-party/chat/crisp.pug @@ -0,0 +1,37 @@ +script. + (() => { + window.$crisp = []; + window.CRISP_WEBSITE_ID = "!{theme.crisp.website_id}"; + (function () { + d = document; + s = d.createElement("script"); + s.src = "https://client.crisp.chat/l.js"; + s.async = 1; + d.getElementsByTagName("head")[0].appendChild(s); + })(); + $crisp.push(["safe", true]) + + const isChatBtn = !{theme.chat.rightside_button} + const isChatHideShow = !{theme.chat.button_hide_show} + + if (isChatBtn) { + const open = () => { + $crisp.push(["do", "chat:show"]) + $crisp.push(["do", "chat:open"]) + } + + const close = () => $crisp.push(["do", "chat:hide"]) + + close() + + $crisp.push(["on", "chat:closed", close]) + + window.chatBtnFn = () => $crisp.is("chat:visible") ? close() : open() + + } else if (isChatHideShow) { + window.chatBtn = { + hide: () => $crisp.push(["do", "chat:hide"]), + show: () => $crisp.push(["do", "chat:show"]) + } + } + })() \ No newline at end of file diff --git a/themes/butterfly/layout/includes/third-party/chat/daovoice.pug b/themes/butterfly/layout/includes/third-party/chat/daovoice.pug new file mode 100644 index 0000000..41ccfeb --- /dev/null +++ b/themes/butterfly/layout/includes/third-party/chat/daovoice.pug @@ -0,0 +1,40 @@ +//- https://guide.daocloud.io/daovoice/javascript-api-5869833.html +script. + (() => { + (function(i,s,o,g,r,a,m){i["DaoVoiceObject"]=r;i[r]=i[r]||function(){(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;a.charset="utf-8";m.parentNode.insertBefore(a,m)})(window,document,"script",('https:' == document.location.protocol ? 'https:' : 'http:') + "//widget.daovoice.io/widget/!{theme.daovoice.app_id}.js","daovoice") + + const isChatBtn = !{theme.chat.rightside_button} + const isChatHideShow = !{theme.chat.button_hide_show} + + daovoice('init', { + app_id: '!{theme.daovoice.app_id}',},{ + launcher: { + disableLauncherIcon: isChatBtn + }, + }); + daovoice('update'); + + if (isChatBtn) { + window.chatBtnFn = () => { + const isShow = document.getElementById('daodream-messenger').classList.contains('daodream-messenger-active') + isShow ? daovoice('hide') : daovoice('show') + } + } else if (isChatHideShow) { + window.chatBtn = { + hide: () => { + daovoice('update', {},{ + launcher: { + disableLauncherIcon: true + } + }) + }, + show: () => { + daovoice('update', {}, { + launcher: { + disableLauncherIcon: false + } + }) + } + } + } + })() \ No newline at end of file diff --git a/themes/butterfly/layout/includes/third-party/chat/index.pug b/themes/butterfly/layout/includes/third-party/chat/index.pug new file mode 100644 index 0000000..c534849 --- /dev/null +++ b/themes/butterfly/layout/includes/third-party/chat/index.pug @@ -0,0 +1,9 @@ +case theme.chat.use + when 'chatra' + include ./chatra.pug + when 'tidio' + include ./tidio.pug + when 'daovoice' + include ./daovoice.pug + when 'crisp' + include ./crisp.pug \ No newline at end of file diff --git a/themes/butterfly/layout/includes/third-party/chat/tidio.pug b/themes/butterfly/layout/includes/third-party/chat/tidio.pug new file mode 100644 index 0000000..78361af --- /dev/null +++ b/themes/butterfly/layout/includes/third-party/chat/tidio.pug @@ -0,0 +1,41 @@ +script(src=`//code.tidio.co/${theme.tidio.public_key}.js` async) +script. + (() => { + const isChatBtn = !{theme.chat.rightside_button} + const isChatHideShow = !{theme.chat.button_hide_show} + + if (isChatBtn) { + let isShow = false + const close = () => { + window.tidioChatApi.hide() + isShow = false + } + + const open = () => { + window.tidioChatApi.open() + window.tidioChatApi.show() + isShow = true + } + + const onTidioChatApiReady = () => { + window.tidioChatApi.hide() + window.tidioChatApi.on("close", close) + } + if (window.tidioChatApi) { + window.tidioChatApi.on("ready", onTidioChatApiReady) + } else { + document.addEventListener("tidioChat-ready", onTidioChatApiReady) + } + + window.chatBtnFn = () => { + if (!window.tidioChatApi) return + isShow ? close() : open() + } + } else if (isChatHideShow) { + window.chatBtn = { + hide: () => window.tidioChatApi && window.tidioChatApi.hide(), + show: () => window.tidioChatApi && window.tidioChatApi.show() + } + } + })() + diff --git a/themes/butterfly/layout/includes/third-party/comments/artalk.pug b/themes/butterfly/layout/includes/third-party/comments/artalk.pug new file mode 100644 index 0000000..befc32e --- /dev/null +++ b/themes/butterfly/layout/includes/third-party/comments/artalk.pug @@ -0,0 +1,55 @@ +- const { server, site, option } = theme.artalk +- const { use, lazyload } = theme.comments + +script. + (() => { + let artalkItem = null + const initArtalk = () => { + artalkItem = Artalk.init(Object.assign({ + el: '#artalk-wrap', + server: '!{server}', + site: '!{site}', + pageKey: location.pathname, + darkMode: document.documentElement.getAttribute('data-theme') === 'dark', + },!{JSON.stringify(option)})) + + if (GLOBAL_CONFIG.lightbox === 'null') return + artalkItem.on('list-loaded', () => { + artalkItem.ctx.get('list').getCommentNodes().forEach(comment => { + const $content = comment.getRender().$content + btf.loadLightbox($content.querySelectorAll('img:not([atk-emoticon])')) + }) + }) + + const destroyArtalk = () => { + artalkItem.destroy() + } + + btf.addGlobalFn('pjaxSendOnce', destroyArtalk, 'destroyArtalk') + } + + const loadArtalk = async () => { + if (typeof Artalk === 'object') initArtalk() + else { + await btf.getCSS('!{theme.asset.artalk_css}') + await btf.getScript('!{theme.asset.artalk_js}') + initArtalk() + } + } + + const artalkChangeMode = theme => { + const artalkWrap = document.getElementById('artalk-wrap') + if (!(artalkWrap && artalkWrap.children.length)) return + const isDark = theme === 'dark' + artalkItem.setDarkMode(isDark) + } + + btf.addGlobalFn('themeChange', artalkChangeMode, 'artalk') + + if ('!{use[0]}' === 'Artalk' || !!{lazyload}) { + if (!{lazyload}) btf.loadComment(document.getElementById('artalk-wrap'), loadArtalk) + else setTimeout(loadArtalk, 100) + } else { + window.loadOtherComment = loadArtalk + } + })() \ No newline at end of file diff --git a/themes/butterfly/layout/includes/third-party/comments/disqus.pug b/themes/butterfly/layout/includes/third-party/comments/disqus.pug new file mode 100644 index 0000000..8d8864c --- /dev/null +++ b/themes/butterfly/layout/includes/third-party/comments/disqus.pug @@ -0,0 +1,59 @@ +- const disqusPageTitle = page.title.replace(/'/ig,"\\'") +- const { shortname, apikey } = theme.disqus +- const { use, lazyload, count } = theme.comments + +script. + (() => { + const disqus_config = function () { + this.page.url = '!{ page.permalink }' + this.page.identifier = '!{ url_for(page.path) }' + this.page.title = '!{ disqusPageTitle }' + } + + const disqusReset = () => { + window.DISQUS && window.DISQUS.reset({ + reload: true, + config: disqus_config + }) + } + + btf.addGlobalFn('themeChange', disqusReset, 'disqus') + + const loadDisqus = () =>{ + if (window.DISQUS) disqusReset() + else { + const script = document.createElement('script') + script.src = 'https://!{shortname}.disqus.com/embed.js' + script.setAttribute('data-timestamp', +new Date()) + document.head.appendChild(script) + } + } + + const getCount = async() => { + try { + const eleGroup = document.querySelector('#post-meta .disqus-comment-count') + if (!eleGroup) return + const cleanedLinks = eleGroup.href.replace(/#post-comment$/, '') + + const res = await fetch(`https://disqus.com/api/3.0/threads/set.json?forum=!{shortname}&api_key=!{apikey}&thread:link=${cleanedLinks}`,{ + method: 'GET' + }) + const result = await res.json() + + const count = result.response.length ? result.response[0].posts : 0 + eleGroup.textContent = count + } catch (err) { + console.error(err) + } + } + + if ('!{use[0]}' === 'Disqus' || !!{lazyload}) { + if (!{lazyload}) btf.loadComment(document.getElementById('disqus_thread'), loadDisqus) + else { + loadDisqus() + !{ count ? 'GLOBAL_CONFIG_SITE.isPost && getCount()' : '' } + } + } else { + window.loadOtherComment = loadDisqus + } + })() diff --git a/themes/butterfly/layout/includes/third-party/comments/disqusjs.pug b/themes/butterfly/layout/includes/third-party/comments/disqusjs.pug new file mode 100644 index 0000000..3550392 --- /dev/null +++ b/themes/butterfly/layout/includes/third-party/comments/disqusjs.pug @@ -0,0 +1,64 @@ +- let disqusjsPageTitle = page.title.replace(/'/ig,"\\'") +- const { shortname:dqShortname, apikey:dqApikey, option:dqOption } = theme.disqusjs + +script. + (() => { + const initDisqusjs = () => { + window.disqusjs = null + disqusjs = new DisqusJS(Object.assign({ + shortname: '!{dqShortname}', + identifier: '!{ url_for(page.path) }', + url: '!{ page.permalink }', + title: '!{ disqusjsPageTitle }', + apikey: '!{dqApikey}', + },!{JSON.stringify(dqOption)})) + + disqusjs.render(document.getElementById('disqusjs-wrap')) + } + + const themeChange = () => { + const ele = document.getElementById('disqus_thread') + if(!ele) return + disqusjs.destroy() + initDisqusjs() + } + + btf.addGlobalFn('themeChange', themeChange, 'disqusjs') + + const loadDisqusjs = async() => { + if (window.disqusJsLoad) initDisqusjs() + else { + await btf.getCSS('!{url_for(theme.asset.disqusjs_css)}') + await btf.getScript('!{url_for(theme.asset.disqusjs)}') + initDisqusjs() + window.disqusJsLoad = true + } + } + + const getCount = async() => { + try { + const eleGroup = document.querySelector('#post-meta .disqusjs-comment-count') + if (!eleGroup) return + const cleanedLinks = eleGroup.href.replace(/#post-comment$/, '') + + const res = await fetch(`https://disqus.com/api/3.0/threads/set.json?forum=!{dqShortname}&api_key=!{dqApikey}&thread:link=${cleanedLinks}`,{ + method: 'GET' + }) + const result = await res.json() + const count = result.response.length ? result.response[0].posts : 0 + eleGroup.textContent = count + } catch (err) { + console.error(err) + } + } + + if ('!{theme.comments.use[0]}' === 'Disqusjs' || !!{theme.comments.lazyload}) { + if (!{theme.comments.lazyload}) btf.loadComment(document.getElementById('disqusjs-wrap'), loadDisqusjs) + else { + loadDisqusjs() + !{ theme.comments.count ? 'GLOBAL_CONFIG_SITE.isPost && getCount()' : '' } + } + } else { + window.loadOtherComment = loadDisqusjs + } + })() \ No newline at end of file diff --git a/themes/butterfly/layout/includes/third-party/comments/facebook_comments.pug b/themes/butterfly/layout/includes/third-party/comments/facebook_comments.pug new file mode 100644 index 0000000..3b47452 --- /dev/null +++ b/themes/butterfly/layout/includes/third-party/comments/facebook_comments.pug @@ -0,0 +1,46 @@ +- const fbSDKVer = 'v20.0' +- const fbSDK = `https://connect.facebook.net/${theme.facebook_comments.lang}/sdk.js#xfbml=1&version=${fbSDKVer}` + +script. + (()=>{ + const loadFBComment = () => { + document.getElementById('fb-root') ? '' : document.body.insertAdjacentHTML('afterend', '
') + + const themeNow = document.documentElement.getAttribute('data-theme') === 'dark' ? 'dark' : 'light' + const $fbComment = document.getElementsByClassName('fb-comments')[0] + $fbComment.setAttribute('data-colorscheme',themeNow) + $fbComment.setAttribute('data-href', '!{urlNoIndex(page.permalink)}') + + if (typeof FB === 'object') { + FB.XFBML.parse(document.getElementsByClassName('post-meta-commentcount')[0]) + FB.XFBML.parse(document.getElementById('post-comment')) + } + else { + let ele = document.createElement('script') + ele.setAttribute('src','!{fbSDK}') + ele.setAttribute('async', 'true') + ele.setAttribute('defer', 'true') + ele.setAttribute('crossorigin', 'anonymous') + ele.setAttribute('id', 'facebook-jssdk') + document.getElementById('fb-root').insertAdjacentElement('afterbegin',ele) + } + } + + const fbModeChange = theme => { + const $fbComment = document.getElementsByClassName('fb-comments')[0] + if ($fbComment && typeof FB === 'object') { + $fbComment.setAttribute('data-colorscheme',theme) + FB.XFBML.parse(document.getElementById('post-comment')) + } + } + + btf.addGlobalFn('themeChange', fbModeChange, 'facebook_comments') + + if ('!{theme.comments.use[0]}' === 'Facebook Comments' || !!{theme.comments.lazyload}) { + if (!{theme.comments.lazyload}) btf.loadComment(document.querySelector('#post-comment .fb-comments'), loadFBComment) + else loadFBComment() + } else { + window.loadOtherComment = loadFBComment + } + })() + diff --git a/themes/butterfly/layout/includes/third-party/comments/giscus.pug b/themes/butterfly/layout/includes/third-party/comments/giscus.pug new file mode 100644 index 0000000..b55a4ec --- /dev/null +++ b/themes/butterfly/layout/includes/third-party/comments/giscus.pug @@ -0,0 +1,52 @@ +- const { use, lazyload } = theme.comments +- const { repo, repo_id, category_id, light_theme, dark_theme, js, option } = theme.giscus +- const giscusUrl = js || 'https://giscus.app/client.js' +- const giscusOriginUrl = new URL(giscusUrl).origin + +script. + (()=>{ + const getGiscusTheme = theme => theme === 'dark' ? '!{dark_theme}' : '!{light_theme}' + + const loadGiscus = () => { + const config = Object.assign({ + src: '!{giscusUrl}', + 'data-repo': '!{repo}', + 'data-repo-id': '!{repo_id}', + 'data-category-id': '!{category_id}', + 'data-mapping': 'pathname', + 'data-theme': getGiscusTheme(document.documentElement.getAttribute('data-theme')), + 'data-reactions-enabled': '1', + crossorigin: 'anonymous', + async: true + },!{JSON.stringify(option)}) + + const ele = document.createElement('script') + for (let key in config) { + ele.setAttribute(key, config[key]) + } + document.getElementById('giscus-wrap').appendChild(ele) + } + + const changeGiscusTheme = theme => { + const iframe = document.querySelector('#giscus-wrap iframe') + if (iframe) { + const message = { + giscus: { + setConfig: { + theme: getGiscusTheme(theme) + } + } + } + iframe.contentWindow.postMessage(message, '!{giscusOriginUrl}') + } + } + + btf.addGlobalFn('themeChange', changeGiscusTheme, 'giscus') + + if ('!{use[0]}' === 'Giscus' || !!{lazyload}) { + if (!{lazyload}) btf.loadComment(document.getElementById('giscus-wrap'), loadGiscus) + else loadGiscus() + } else { + window.loadOtherComment= loadGiscus + } + })() diff --git a/themes/butterfly/layout/includes/third-party/comments/gitalk.pug b/themes/butterfly/layout/includes/third-party/comments/gitalk.pug new file mode 100644 index 0000000..fb2d694 --- /dev/null +++ b/themes/butterfly/layout/includes/third-party/comments/gitalk.pug @@ -0,0 +1,44 @@ +- const { client_id, client_secret, repo, owner, admin, option } = theme.gitalk + +script. + (() => { + const initGitalk = () => { + const gitalk = new Gitalk(Object.assign({ + clientID: '!{client_id}', + clientSecret: '!{client_secret}', + repo: '!{repo}', + owner: '!{owner}', + admin: ['!{admin}'], + id: '!{md5(page.path)}', + updateCountCallback: commentCount + },!{JSON.stringify(option)})) + + gitalk.render('gitalk-container') + } + + const loadGitalk = async() => { + if (typeof Gitalk === 'function') initGitalk() + else { + await btf.getCSS('!{url_for(theme.asset.gitalk_css)}') + await btf.getScript('!{url_for(theme.asset.gitalk)}') + initGitalk() + } + } + + const commentCount = n => { + const isCommentCount = document.querySelector('#post-meta .gitalk-comment-count') + if (isCommentCount) { + isCommentCount.textContent= n + } + } + + if ('!{theme.comments.use[0]}' === 'Gitalk' || !!{theme.comments.lazyload}) { + if (!{theme.comments.lazyload}) btf.loadComment(document.getElementById('gitalk-container'), loadGitalk) + else loadGitalk() + } else { + window.loadOtherComment = loadGitalk + } + })() + + + diff --git a/themes/butterfly/layout/includes/third-party/comments/index.pug b/themes/butterfly/layout/includes/third-party/comments/index.pug new file mode 100644 index 0000000..3b8ceff --- /dev/null +++ b/themes/butterfly/layout/includes/third-party/comments/index.pug @@ -0,0 +1,46 @@ +- let defaultComment = theme.comments.use[0] +hr.custom-hr +#post-comment + .comment-head + .comment-headline + i.fas.fa-comments.fa-fw + span= ' ' + _p('comment') + + if theme.comments.use.length > 1 + .comment-switch + span.first-comment=defaultComment + span#switch-btn + span.second-comment=theme.comments.use[1] + + + .comment-wrap + each name in theme.comments.use + div + case name + when 'Disqus' + #disqus_thread + when 'Valine' + #vcomment.vcomment + when 'Disqusjs' + #disqusjs-wrap + when 'Livere' + #lv-container(data-id="city" data-uid=theme.livere.uid) + when 'Gitalk' + #gitalk-container + when 'Utterances' + #utterances-wrap + when 'Twikoo' + #twikoo-wrap + when 'Waline' + #waline-wrap + when 'Giscus' + #giscus-wrap + when 'Facebook Comments' + .fb-comments(data-colorscheme = theme.display_mode === 'dark' ? 'dark' : 'light' + data-numposts= theme.facebook_comments.pageSize || 10 + data-order-by= theme.facebook_comments.order_by || 'social' + data-width="100%") + when 'Remark42' + #remark42 + when 'Artalk' + #artalk-wrap diff --git a/themes/butterfly/layout/includes/third-party/comments/js.pug b/themes/butterfly/layout/includes/third-party/comments/js.pug new file mode 100644 index 0000000..bf1d872 --- /dev/null +++ b/themes/butterfly/layout/includes/third-party/comments/js.pug @@ -0,0 +1,26 @@ +each name in theme.comments.use + case name + when 'Valine' + !=partial('includes/third-party/comments/valine', {}, {cache: true}) + when 'Disqus' + include ./disqus.pug + when 'Disqusjs' + include ./disqusjs.pug + when 'Livere' + !=partial('includes/third-party/comments/livere', {}, {cache: true}) + when 'Gitalk' + include ./gitalk.pug + when 'Utterances' + !=partial('includes/third-party/comments/utterances', {}, {cache: true}) + when 'Twikoo' + !=partial('includes/third-party/comments/twikoo', {}, {cache: true}) + when 'Waline' + !=partial('includes/third-party/comments/waline', {}, {cache: true}) + when 'Giscus' + !=partial('includes/third-party/comments/giscus', {}, {cache: true}) + when 'Facebook Comments' + include ./facebook_comments.pug + when 'Remark42' + !=partial('includes/third-party/comments/remark42', {}, {cache: true}) + when 'Artalk' + !=partial('includes/third-party/comments/artalk', {}, {cache: true}) \ No newline at end of file diff --git a/themes/butterfly/layout/includes/third-party/comments/livere.pug b/themes/butterfly/layout/includes/third-party/comments/livere.pug new file mode 100644 index 0000000..ecacb59 --- /dev/null +++ b/themes/butterfly/layout/includes/third-party/comments/livere.pug @@ -0,0 +1,25 @@ +- const { use, lazyload } = theme.comments + +script. + (()=>{ + const loadLivere = () => { + if (typeof LivereTower === 'object') window.LivereTower.init() + else { + (function(d, s) { + var j, e = d.getElementsByTagName(s)[0]; + if (typeof LivereTower === 'function') { return; } + j = d.createElement(s); + j.src = 'https://cdn-city.livere.com/js/embed.dist.js'; + j.async = true; + e.parentNode.insertBefore(j, e); + })(document, 'script'); + } + } + + if ('!{use[0]}' === 'Livere' || !!{lazyload}) { + if (!{lazyload}) btf.loadComment(document.getElementById('lv-container'), loadLivere) + else loadLivere() + } else { + window.loadOtherComment = loadLivere + } + })() \ No newline at end of file diff --git a/themes/butterfly/layout/includes/third-party/comments/remark42.pug b/themes/butterfly/layout/includes/third-party/comments/remark42.pug new file mode 100644 index 0000000..388fb97 --- /dev/null +++ b/themes/butterfly/layout/includes/third-party/comments/remark42.pug @@ -0,0 +1,68 @@ +- const { host, siteId, option } = theme.remark42 +script. + var remark_config = Object.assign({ + host: '!{host}', + site_id: '!{siteId}', + components: ['embed'], + theme: document.documentElement.getAttribute('data-theme') === 'dark' ? 'dark' : 'light' + },!{JSON.stringify(option)}) + + function addRemark42(){ + for (let i = 0; i < remark_config.components.length; i++) { + const s = document.createElement('script') + s.src = remark_config.host + '/web/' + remark_config.components[i] + '.js' + s.defer = true + document.head.appendChild(s) + } + } + + function initRemark42() { + if (window.REMARK42) { + if (this.remark42Instance) { + this.remark42Instance.destroy() + } + + this.remark42Instance = window.REMARK42.createInstance({ + ...remark_config + }) + } + } + + function getCount () { + const ele = document.querySelector('.remark42__counter') + if (ele) { + const s = document.createElement('script') + s.src = remark_config.host + '/web/counter.js' + s.defer = true + document.head.appendChild(s) + } + } + + function loadRemark42 () { + if (window.REMARK42) { + this.initRemark42() + getCount() + } else { + addRemark42() + window.addEventListener('REMARK42::ready', () => { + this.initRemark42() + getCount() + }) + } + } + + function remarkChangeMode (theme) { + if (!window.REMARK42) return + window.REMARK42.changeTheme(theme) + } + + btf.addGlobalFn('themeChange', remarkChangeMode, 'remark42') + + if ('!{theme.comments.use[0]}' === 'Remark42' || !!{theme.comments.lazyload}) { + if (!{theme.comments.lazyload}) btf.loadComment(document.getElementById('remark42'), loadRemark42) + else loadRemark42() + } else { + function loadOtherComment () { + loadRemark42() + } + } diff --git a/themes/butterfly/layout/includes/third-party/comments/twikoo.pug b/themes/butterfly/layout/includes/third-party/comments/twikoo.pug new file mode 100644 index 0000000..8f3bc42 --- /dev/null +++ b/themes/butterfly/layout/includes/third-party/comments/twikoo.pug @@ -0,0 +1,45 @@ +- const { envId, region, option } = theme.twikoo +- const { use, lazyload, count } = theme.comments + +script. + (() => { + const getCount = () => { + const countELement = document.getElementById('twikoo-count') + if(!countELement) return + twikoo.getCommentsCount({ + envId: '!{envId}', + region: '!{region}', + urls: [window.location.pathname], + includeReply: false + }).then(res => { + countELement.textContent = res[0].count + }).catch(err => { + console.error(err) + }) + } + + const init = () => { + twikoo.init(Object.assign({ + el: '#twikoo-wrap', + envId: '!{envId}', + region: '!{region}', + onCommentLoaded: () => { + btf.loadLightbox(document.querySelectorAll('#twikoo .tk-content img:not(.tk-owo-emotion)')) + } + }, !{JSON.stringify(option)})) + + !{count ? 'GLOBAL_CONFIG_SITE.isPost && getCount()' : ''} + } + + const loadTwikoo = () => { + if (typeof twikoo === 'object') setTimeout(init,0) + else btf.getScript('!{url_for(theme.asset.twikoo)}').then(init) + } + + if ('!{use[0]}' === 'Twikoo' || !!{lazyload}) { + if (!{lazyload}) btf.loadComment(document.getElementById('twikoo-wrap'), loadTwikoo) + else loadTwikoo() + } else { + window.loadOtherComment = loadTwikoo + } + })() \ No newline at end of file diff --git a/themes/butterfly/layout/includes/third-party/comments/utterances.pug b/themes/butterfly/layout/includes/third-party/comments/utterances.pug new file mode 100644 index 0000000..1fdc0da --- /dev/null +++ b/themes/butterfly/layout/includes/third-party/comments/utterances.pug @@ -0,0 +1,47 @@ +- const { use, lazyload } = theme.comments +- const { repo, issue_term, light_theme, dark_theme, js, option } = theme.utterances +- const utterancesUrl = js || 'https://utteranc.es/client.js' +- const utterancesOriginUrl = new URL(utterancesUrl).origin + +script. + (() => { + const getUtterancesTheme = theme => theme === 'dark' ? '#{dark_theme}' : '#{light_theme}' + + const loadUtterances = () => { + const config = Object.assign({ + id: 'utterances_comment', + src: '!{utterancesUrl}', + repo: '!{repo}', + 'issue-term': '!{issue_term}', + theme: getUtterancesTheme(document.documentElement.getAttribute('data-theme')), + crossorigin: 'anonymous', + async: true + },!{JSON.stringify(option)}) + + const ele = document.createElement('script') + for (let key in config) { + ele.setAttribute(key, config[key]) + } + document.getElementById('utterances-wrap').appendChild(ele) + } + + const changeUtterancesTheme = theme => { + const iframe = document.querySelector('#utterances-wrap iframe') + if (iframe) { + const message = { + type: 'set-theme', + theme: getUtterancesTheme(theme) + }; + iframe.contentWindow.postMessage(message, '!{utterancesOriginUrl}') + } + } + + btf.addGlobalFn('themeChange', changeUtterancesTheme, 'utterances') + + if ('!{use[0]}' === 'Utterances' || !!{lazyload}) { + if (!{lazyload}) btf.loadComment(document.getElementById('utterances-wrap'), loadUtterances) + else loadUtterances() + } else { + window.loadOtherComment = loadUtterances + } + })() \ No newline at end of file diff --git a/themes/butterfly/layout/includes/third-party/comments/valine.pug b/themes/butterfly/layout/includes/third-party/comments/valine.pug new file mode 100644 index 0000000..b53e3fc --- /dev/null +++ b/themes/butterfly/layout/includes/third-party/comments/valine.pug @@ -0,0 +1,38 @@ +- const { use, lazyload } = theme.comments +- const { appId, appKey, avatar, serverURLs, visitor, option } = theme.valine + +- let emojiMaps = '""' +if site.data.valine + - emojiMaps = JSON.stringify(site.data.valine) + +script. + (() => { + const initValine = () => { + const valine = new Valine(Object.assign({ + el: '#vcomment', + appId: '#{appId}', + appKey: '#{appKey}', + avatar: '#{avatar}', + serverURLs: '#{serverURLs}', + emojiMaps: !{emojiMaps}, + path: window.location.pathname, + visitor: #{visitor} + }, !{JSON.stringify(option)})) + } + + const loadValine = async () => { + if (typeof Valine === 'function') initValine() + else { + await btf.getScript('!{url_for(theme.asset.valine)}') + initValine() + } + } + + if ('!{use[0]}' === 'Valine' || !!{lazyload}) { + if (!{lazyload}) btf.loadComment(document.getElementById('vcomment'),loadValine) + else setTimeout(loadValine, 0) + } else { + window.loadOtherComment = loadValine + } + })() + diff --git a/themes/butterfly/layout/includes/third-party/comments/waline.pug b/themes/butterfly/layout/includes/third-party/comments/waline.pug new file mode 100644 index 0000000..47ef32f --- /dev/null +++ b/themes/butterfly/layout/includes/third-party/comments/waline.pug @@ -0,0 +1,45 @@ +- const { serverURL, option, pageview } = theme.waline +- const { lazyload, count, use } = theme.comments + +script. + (() => { + let initFn = window.walineFn || null + + const initWaline = (Fn) => { + const waline = Fn(Object.assign({ + el: '#waline-wrap', + serverURL: '!{serverURL}', + pageview: !{lazyload ? false : pageview}, + dark: 'html[data-theme="dark"]', + path: window.location.pathname, + comment: !{lazyload ? false : count}, + }, !{JSON.stringify(option)})) + + const destroyWaline = () => { + waline.destroy() + } + + btf.addGlobalFn('pjaxSendOnce', destroyWaline, 'destroyWaline') + } + + const loadWaline = () => { + if (initFn) initWaline(initFn) + else { + btf.getCSS('!{url_for(theme.asset.waline_css)}') + .then(() => import('!{url_for(theme.asset.waline_js)}')) + .then(({ init }) => { + initFn = init || Waline.init + initWaline(initFn) + window.walineFn = initFn + }) + } + } + + if ('!{use[0]}' === 'Waline' || !!{lazyload}) { + if (!{lazyload}) btf.loadComment(document.getElementById('waline-wrap'),loadWaline) + else setTimeout(loadWaline, 0) + } else { + window.loadOtherComment = loadWaline + } + })() + diff --git a/themes/butterfly/layout/includes/third-party/effect.pug b/themes/butterfly/layout/includes/third-party/effect.pug new file mode 100644 index 0000000..a3b2995 --- /dev/null +++ b/themes/butterfly/layout/includes/third-party/effect.pug @@ -0,0 +1,35 @@ +if theme.fireworks && theme.fireworks.enable + canvas.fireworks(mobile=`${theme.fireworks.mobile}`) + script(src=url_for(theme.asset.fireworks)) + +if (theme.canvas_ribbon && theme.canvas_ribbon.enable) + script(defer id="ribbon" src=url_for(theme.asset.canvas_ribbon) size=theme.canvas_ribbon.size + alpha=theme.canvas_ribbon.alpha zIndex=theme.canvas_ribbon.zIndex mobile=`${theme.canvas_ribbon.mobile}` data-click=`${theme.canvas_ribbon.click_to_change}`) + +if (theme.canvas_fluttering_ribbon && theme.canvas_fluttering_ribbon.enable) + script(defer id="fluttering_ribbon" mobile=`${theme.canvas_fluttering_ribbon.mobile}` src=url_for(theme.asset.canvas_fluttering_ribbon)) + +if (theme.canvas_nest && theme.canvas_nest.enable) + 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)) + +if theme.activate_power_mode.enable + script(src=url_for(theme.asset.activate_power_mode)) + script. + POWERMODE.colorful = !{theme.activate_power_mode.colorful}; + POWERMODE.shake = !{theme.activate_power_mode.shake}; + POWERMODE.mobile = !{theme.activate_power_mode.mobile}; + document.body.addEventListener('input', POWERMODE); + +//- 鼠標特效 +if theme.click_heart && theme.click_heart.enable + script#click-heart(src=url_for(theme.asset.click_heart) async mobile=`${theme.click_heart.mobile}`) + +if theme.clickShowText && theme.clickShowText.enable + script#click-show-text( + src= url_for(theme.asset.clickShowText) + data-mobile= `${theme.clickShowText.mobile}` + data-text= theme.clickShowText.text.join(",") + data-fontsize= theme.clickShowText.fontSize + data-random= `${theme.clickShowText.random}` + async + ) \ No newline at end of file diff --git a/themes/butterfly/layout/includes/third-party/math/index.pug b/themes/butterfly/layout/includes/third-party/math/index.pug new file mode 100644 index 0000000..068a78f --- /dev/null +++ b/themes/butterfly/layout/includes/third-party/math/index.pug @@ -0,0 +1,11 @@ +case theme.math.use + when 'mathjax' + if (theme.math.per_page && (is_post() || is_page())) || page.mathjax + include ./mathjax.pug + + when 'katex' + if (theme.math.per_page && (is_post() || is_page())) || page.katex + include ./katex.pug + +if theme.mermaid.enable + include ./mermaid.pug \ No newline at end of file diff --git a/themes/butterfly/layout/includes/third-party/math/katex.pug b/themes/butterfly/layout/includes/third-party/math/katex.pug new file mode 100644 index 0000000..0e4195d --- /dev/null +++ b/themes/butterfly/layout/includes/third-party/math/katex.pug @@ -0,0 +1,16 @@ +script. + (async () => { + const showKatex = () => { + document.querySelectorAll('#article-container .katex').forEach(el => el.classList.add('katex-show')) + } + + if (!window.katex_js_css) { + window.katex_js_css = true + await btf.getCSS('!{url_for(theme.asset.katex)}') + if (!{theme.math.katex.copy_tex}) { + await btf.getScript('!{url_for(theme.asset.katex_copytex)}') + } + } + + showKatex() + })() \ No newline at end of file diff --git a/themes/butterfly/layout/includes/third-party/math/mathjax.pug b/themes/butterfly/layout/includes/third-party/math/mathjax.pug new file mode 100644 index 0000000..75775b5 --- /dev/null +++ b/themes/butterfly/layout/includes/third-party/math/mathjax.pug @@ -0,0 +1,47 @@ +//- Mathjax 3 +- const { tags, enableMenu } = theme.math.mathjax +script. + (() => { + const loadMathjax = () => { + if (!window.MathJax) { + window.MathJax = { + tex: { + inlineMath: [['$', '$'], ['\\(', '\\)']], + tags: '!{tags}', + }, + chtml: { + scale: 1.1 + }, + options: { + enableMenu: !{enableMenu}, + renderActions: { + findScript: [10, doc => { + for (const node of document.querySelectorAll('script[type^="math/tex"]')) { + const display = !!node.type.match(/; *mode=display/) + const math = new doc.options.MathItem(node.textContent, doc.inputJax[0], display) + const text = document.createTextNode('') + node.parentNode.replaceChild(text, node) + math.start = {node: text, delim: '', n: 0} + math.end = {node: text, delim: '', n: 0} + doc.math.push(math) + } + }, ''] + } + } + } + + const script = document.createElement('script') + script.src = '!{url_for(theme.asset.mathjax)}' + script.id = 'MathJax-script' + script.async = true + document.head.appendChild(script) + } else { + MathJax.startup.document.state(0) + MathJax.texReset() + MathJax.typesetPromise() + } + } + + btf.addGlobalFn('encrypt', loadMathjax, 'mathjax') + window.pjax ? loadMathjax() : window.addEventListener('load', loadMathjax) + })() \ No newline at end of file diff --git a/themes/butterfly/layout/includes/third-party/math/mermaid.pug b/themes/butterfly/layout/includes/third-party/math/mermaid.pug new file mode 100644 index 0000000..a668659 --- /dev/null +++ b/themes/butterfly/layout/includes/third-party/math/mermaid.pug @@ -0,0 +1,51 @@ +script. + (() => { + const runMermaid = ele => { + window.loadMermaid = true + const theme = document.documentElement.getAttribute('data-theme') === 'dark' ? '!{theme.mermaid.theme.dark}' : '!{theme.mermaid.theme.light}' + + ele.forEach((item, index) => { + const mermaidSrc = item.firstElementChild + const mermaidThemeConfig = `%%{init:{ 'theme':'${theme}'}}%%\n` + const mermaidID = `mermaid-${index}` + const mermaidDefinition = mermaidThemeConfig + mermaidSrc.textContent + + const renderFn = mermaid.render(mermaidID, mermaidDefinition) + const renderMermaid = svg => { + mermaidSrc.insertAdjacentHTML('afterend', svg) + } + + // mermaid v9 and v10 compatibility + typeof renderFn === 'string' ? renderMermaid(renderFn) : renderFn.then(({ svg }) => renderMermaid(svg)) + }) + } + + const codeToMermaid = () => { + const codeMermaidEle = document.querySelectorAll('pre > code.mermaid') + if (codeMermaidEle.length === 0) return + + codeMermaidEle.forEach(ele => { + const preEle = document.createElement('pre') + preEle.className = 'mermaid-src' + preEle.hidden = true + preEle.textContent = ele.textContent + const newEle = document.createElement('div') + newEle.className = 'mermaid-wrap' + newEle.appendChild(preEle) + ele.parentNode.replaceWith(newEle) + }) + } + + const loadMermaid = () => { + if (!{theme.mermaid.code_write}) codeToMermaid() + const $mermaid = document.querySelectorAll('#article-container .mermaid-wrap') + if ($mermaid.length === 0) return + + const runMermaidFn = () => runMermaid($mermaid) + btf.addGlobalFn('themeChange', runMermaidFn, 'mermaid') + window.loadMermaid ? runMermaidFn() : btf.getScript('!{url_for(theme.asset.mermaid)}').then(runMermaidFn) + } + + btf.addGlobalFn('encrypt', loadMermaid, 'mermaid') + window.pjax ? loadMermaid() : document.addEventListener('DOMContentLoaded', loadMermaid) + })() \ No newline at end of file diff --git a/themes/butterfly/layout/includes/third-party/newest-comments/artalk.pug b/themes/butterfly/layout/includes/third-party/newest-comments/artalk.pug new file mode 100644 index 0000000..af669ee --- /dev/null +++ b/themes/butterfly/layout/includes/third-party/newest-comments/artalk.pug @@ -0,0 +1,64 @@ +- const { server, site, option } = theme.artalk +- const avatarCdn = (option !== null && option.gravatar && option.gravatar.mirror) || '' +- const avatarDefault = (option !== null && option.gravatar && (option.gravatar.params || option.gravatar.default)) || '' + +!= partial("includes/third-party/newest-comments/common.pug", {}, { cache: true }) + +script. + window.addEventListener('load', () => { + const keyName = 'artalk-newest-comments' + const { changeContent, generateHtml, run } = window.newestComments + + const getAvatarValue = async () => { + const predefinedAvatarCdn = '!{avatarCdn}' + const predefinedAvatarDefault = '!{avatarDefault}' + + const avatarDefaultFormat = e => e.startsWith('d=') ? e : `d=${e}` + + if (predefinedAvatarCdn && predefinedAvatarDefault) { + return { avatarCdn: predefinedAvatarCdn, avatarDefault: avatarDefaultFormat(predefinedAvatarDefault) } + } + + try { + const res = await fetch('!{server}/api/v2/conf') + const result = await res.json() + const { mirror, params, default: defaults } = result.frontend_conf.gravatar + const avatarCdn = predefinedAvatarCdn || mirror + let avatarDefault = avatarDefaultFormat(predefinedAvatarDefault || params || defaults) + return { avatarCdn, avatarDefault} + } catch (e) { + console.error(e) + return { avatarCdn: predefinedAvatarCdn, avatarDefault: avatarDefaultFormat(predefinedAvatarDefault) } + } + } + + const searchParams = new URLSearchParams({ + 'site_name': '!{site}', + 'limit': '!{theme.aside.card_newest_comments.limit}', + }) + + const getComment = async (ele) => { + try { + const res = await fetch(`!{server}/api/v2/stats/latest_comments?${searchParams}`) + const result = await res.json() + const { avatarCdn, avatarDefault } = await getAvatarValue() + const artalk = result.data.map(e => { + const avatar = avatarCdn && e.email_encrypted ? `${avatarCdn}${e.email_encrypted}?${avatarDefault}` : '' + return { + 'avatar': avatar, + 'content': changeContent(e.content_marked), + 'nick': e.nick, + 'url': e.page_url, + 'date': e.date, + } + }) + btf.saveToLocal.set(keyName, JSON.stringify(artalk), !{theme.aside.card_newest_comments.storage}/(60*24)) + generateHtml(artalk, ele) + } catch (e) { + console.log(e) + ele.textContent= "!{_p('aside.card_newest_comments.error')}" + } + } + + run(keyName, getComment) + }) \ No newline at end of file diff --git a/themes/butterfly/layout/includes/third-party/newest-comments/common.pug b/themes/butterfly/layout/includes/third-party/newest-comments/common.pug new file mode 100644 index 0000000..093bd27 --- /dev/null +++ b/themes/butterfly/layout/includes/third-party/newest-comments/common.pug @@ -0,0 +1,60 @@ +script. + window.newestComments = { + changeContent: content => { + if (content === '') return content + + content = content.replace(/]+>/ig, '[!{_p("aside.card_newest_comments.image")}]') // replace image link + content = content.replace(/]+?href=["']?([^"']+)["']?[^>]*>([^<]+)<\/a>/gi, '[!{_p("aside.card_newest_comments.link")}]') // replace url + content = content.replace(/
.*?<\/pre>/gi, '[!{_p("aside.card_newest_comments.code")}]') // replace code
+      content = content.replace(/.*?<\/code>/gi, '[!{_p("aside.card_newest_comments.code")}]') // replace code      
+      content = content.replace(/<[^>]+>/g, "") // remove html tag
+
+      if (content.length > 150) {
+        content = content.substring(0, 150) + '...'
+      }
+      return content
+    },
+
+    generateHtml: (array, ele) => {
+      let result = ''
+
+      if (array.length) {
+        for (let i = 0; i < array.length; i++) {
+          result += '
' + + if (!{theme.aside.card_newest_comments.avatar} && array[i].avatar) { + const imgAttr = '!{theme.lazyload.enable ? "data-lazy-src" : "src"}' + result += `${array[i].nick}` + } + + result += `
+ ${array[i].content} +
${array[i].nick} /
+
` + } + } else { + result += '!{_p("aside.card_newest_comments.zero")}' + } + + ele.innerHTML = result + window.lazyLoadInstance && window.lazyLoadInstance.update() + window.pjax && window.pjax.refresh(ele) + }, + + newestCommentInit: (name, getComment) => { + const $dom = document.querySelector('#card-newest-comments .aside-list') + if ($dom) { + const data = btf.saveToLocal.get(name) + if (data) { + newestComments.generateHtml(JSON.parse(data), $dom) + } else { + getComment($dom) + } + } + }, + + run: (name, getComment) => { + newestComments.newestCommentInit(name, getComment) + btf.addGlobalFn('pjaxComplete', () => newestComments.newestCommentInit(name, getComment), name) + } + } \ No newline at end of file diff --git a/themes/butterfly/layout/includes/third-party/newest-comments/disqus-comment.pug b/themes/butterfly/layout/includes/third-party/newest-comments/disqus-comment.pug new file mode 100644 index 0000000..96eaa02 --- /dev/null +++ b/themes/butterfly/layout/includes/third-party/newest-comments/disqus-comment.pug @@ -0,0 +1,34 @@ +!= partial("includes/third-party/newest-comments/common.pug", {}, { cache: true }) + +script. + window.addEventListener('load', () => { + const keyName = 'disqus-newest-comments' + const { changeContent, generateHtml, run } = window.newestComments + + const getComment = ele => { + fetch('https://disqus.com/api/3.0/forums/listPosts.json?forum=!{forum}&related=thread&limit=!{theme.aside.card_newest_comments.limit}&api_key=!{apiKey}') + .then(response => response.json()) + .then(data => { + const disqusArray = data.response.map(item => { + return { + 'avatar': item.author.avatar.cache, + 'content': changeContent(item.message), + 'nick': item.author.name, + 'url': item.url, + 'date': item.createdAt + } + }) + + btf.saveToLocal.set(keyName, JSON.stringify(disqusArray), !{theme.aside.card_newest_comments.storage}/(60*24)) + generateHtml(disqusArray, ele) + }).catch(e => { + console.error(e) + ele.textContent= "!{_p('aside.card_newest_comments.error')}" + }) + } + + run(keyName, getComment) + }) + + + diff --git a/themes/butterfly/layout/includes/third-party/newest-comments/github-issues.pug b/themes/butterfly/layout/includes/third-party/newest-comments/github-issues.pug new file mode 100644 index 0000000..d36f1b2 --- /dev/null +++ b/themes/butterfly/layout/includes/third-party/newest-comments/github-issues.pug @@ -0,0 +1,62 @@ +!= partial("includes/third-party/newest-comments/common.pug", {}, { cache: true }) + +script. + window.addEventListener('load', () => { + const keyName = 'github-newest-comments' + const { changeContent, generateHtml, run } = window.newestComments + + const findTrueUrl = (array, ele) => { + Promise.all(array.map(item => + fetch(item.url).then(resp => resp.json()).then(data => { + let urlArray = data.body ? data.body.match(/(https?:\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?/ig) : [] + if (!Array.isArray(urlArray) || urlArray.length === 0) { + urlArray = [`${data.html_url}`] + } + if (data.user.login === 'utterances-bot') { + return urlArray.pop() + } else { + return urlArray.shift() + } + }) + )).then(res => { + array = array.map((i,index)=> { + return { + ...i, + url: res[index] + } + }) + + btf.saveToLocal.set(keyName, JSON.stringify(array), !{theme.aside.card_newest_comments.storage}/(60*24)) + generateHtml(array, ele) + }); + } + + const getComment = ele => { + fetch('https://api.github.com/repos/!{userRepo}/issues/comments?sort=updated&direction=desc&per_page=!{theme.aside.card_newest_comments.limit}&page=1',{ + "headers": { + Accept: 'application/vnd.github.v3.html+json' + } + }) + .then(response => response.json()) + .then(data => { + const githubArray = data.map(item => { + return { + 'avatar': item.user.avatar_url, + 'content': changeContent(item.body_html || item.body), + 'nick': item.user.login, + 'url': item.issue_url, + 'date': item.updated_at + } + }) + findTrueUrl(githubArray, ele) + }).catch(e => { + console.error(e) + ele.textContent= "!{_p('aside.card_newest_comments.error')}" + }) + } + run(keyName, getComment) + }) + + + + diff --git a/themes/butterfly/layout/includes/third-party/newest-comments/index.pug b/themes/butterfly/layout/includes/third-party/newest-comments/index.pug new file mode 100644 index 0000000..8ceaccf --- /dev/null +++ b/themes/butterfly/layout/includes/third-party/newest-comments/index.pug @@ -0,0 +1,30 @@ +- let { use } = theme.comments + +if use + - let forum,apiKey,userRepo + case use[0] + when 'Valine' + include ./valine.pug + when 'Waline' + include ./waline.pug + when 'Twikoo' + include ./twikoo-comment.pug + when 'Disqus' + - forum = theme.disqus.shortname + - apiKey = theme.disqus.apikey + include ./disqus-comment.pug + when 'Disqusjs' + - forum = theme.disqusjs.shortname + - apiKey = theme.disqusjs.apikey + include ./disqus-comment.pug + when 'Gitalk' + - let { repo,owner } = theme.gitalk + - userRepo = owner + '/' + repo + include ./github-issues.pug + when 'Utterances' + - userRepo = theme.utterances.repo + include ./github-issues.pug + when 'Remark42' + include ./remark42.pug + when 'Artalk' + include ./artalk.pug \ No newline at end of file diff --git a/themes/butterfly/layout/includes/third-party/newest-comments/remark42.pug b/themes/butterfly/layout/includes/third-party/newest-comments/remark42.pug new file mode 100644 index 0000000..c83fd0c --- /dev/null +++ b/themes/butterfly/layout/includes/third-party/newest-comments/remark42.pug @@ -0,0 +1,30 @@ +- const { host, siteId } = theme.remark42 + +script. + window.addEventListener('load', () => { + const keyName = 'remark42-newest-comments' + const { changeContent, generateHtml, run } = window.newestComments + + const getComment = ele => { + fetch('!{host}/api/v1/last/!{theme.aside.card_newest_comments.limit}?site=!{siteId}') + .then(response => response.json()) + .then(data => { + const remark42 = data.map(e => { + return { + 'avatar': e.user.picture, + 'content': changeContent(e.text), + 'nick': e.user.name, + 'url': e.locator.url, + 'date': e.time, + } + }) + btf.saveToLocal.set(keyName, JSON.stringify(remark42), !{theme.aside.card_newest_comments.storage}/(60*24)) + generateHtml(remark42, ele) + }).catch(e => { + console.error(e) + ele.textContent= "!{_p('aside.card_newest_comments.error')}" + }) + } + + run(keyName, getComment) + }) diff --git a/themes/butterfly/layout/includes/third-party/newest-comments/twikoo-comment.pug b/themes/butterfly/layout/includes/third-party/newest-comments/twikoo-comment.pug new file mode 100644 index 0000000..941f42b --- /dev/null +++ b/themes/butterfly/layout/includes/third-party/newest-comments/twikoo-comment.pug @@ -0,0 +1,45 @@ +!= partial("includes/third-party/newest-comments/common.pug", {}, { cache: true }) + +script. + window.addEventListener('load', () => { + const keyName = 'twikoo-newest-comments' + const { changeContent, generateHtml, run } = window.newestComments + + const getComment = ele => { + const runTwikoo = () => { + twikoo.getRecentComments({ + envId: '!{theme.twikoo.envId}', + region: '!{theme.twikoo.region}', + pageSize: !{theme.aside.card_newest_comments.limit}, + includeReply: true + }).then(res => { + const twikooArray = res.map(e => { + return { + 'content': changeContent(e.comment), + 'avatar': e.avatar, + 'nick': e.nick, + 'url': e.url + '#' + e.id, + 'date': new Date(e.created).toISOString() + } + }) + + btf.saveToLocal.set(keyName, JSON.stringify(twikooArray), !{theme.aside.card_newest_comments.storage}/(60*24)) + generateHtml(twikooArray, ele) + }).catch(err => { + console.error(err) + ele.textContent= "!{_p('aside.card_newest_comments.error')}" + }) + } + + if (typeof twikoo === 'object') { + runTwikoo() + } else { + btf.getScript('!{url_for(theme.asset.twikoo)}').then(runTwikoo) + } + } + + run(keyName, getComment) + }) + + + diff --git a/themes/butterfly/layout/includes/third-party/newest-comments/valine.pug b/themes/butterfly/layout/includes/third-party/newest-comments/valine.pug new file mode 100644 index 0000000..6b87683 --- /dev/null +++ b/themes/butterfly/layout/includes/third-party/newest-comments/valine.pug @@ -0,0 +1,51 @@ +- let default_avatar = theme.valine.avatar + +script(src=url_for(theme.asset.blueimp_md5)) +!= partial("includes/third-party/newest-comments/common.pug", {}, { cache: true }) + +script. + window.addEventListener('load', () => { + const keyName = 'valine-newest-comments' + const { changeContent, generateHtml, run } = window.newestComments + + const getIcon = (icon, mail) => { + if (icon) return icon + let defaultIcon = '!{ default_avatar ? `?d=${default_avatar}` : ''}' + let iconUrl = `https://gravatar.loli.net/avatar/${md5(mail.toLowerCase()) + defaultIcon}` + return iconUrl + } + + const getComment = ele => { + const serverURL = '!{theme.valine.serverURLs || `https://${theme.valine.appId.substring(0,8)}.api.lncldglobal.com` }' + + var settings = { + "method": "GET", + "headers": { + "X-LC-Id": '!{theme.valine.appId}', + "X-LC-Key": '!{theme.valine.appKey}', + "Content-Type": "application/json" + }, + } + + fetch(`${serverURL}/1.1/classes/Comment?limit=!{theme.aside.card_newest_comments.limit}&order=-createdAt`,settings) + .then(response => response.json()) + .then(data => { + const valineArray = data.results.map(e => { + return { + 'avatar': getIcon(e.QQAvatar, e.mail), + 'content': changeContent(e.comment), + 'nick': e.nick, + 'url': e.url + '#' + e.objectId, + 'date': e.updatedAt, + } + }) + btf.saveToLocal.set(keyName, JSON.stringify(valineArray), !{theme.aside.card_newest_comments.storage}/(60*24)) + generateHtml(valineArray, ele) + }).catch(e => { + console.error(e) + ele.textContent= "!{_p('aside.card_newest_comments.error')}" + }) + } + + run(keyName, getComment) + }) diff --git a/themes/butterfly/layout/includes/third-party/newest-comments/waline.pug b/themes/butterfly/layout/includes/third-party/newest-comments/waline.pug new file mode 100644 index 0000000..4031d34 --- /dev/null +++ b/themes/butterfly/layout/includes/third-party/newest-comments/waline.pug @@ -0,0 +1,32 @@ +- const serverURL = theme.waline.serverURL.replace(/\/$/, '') + +!= partial("includes/third-party/newest-comments/common.pug", {}, { cache: true }) + +script. + window.addEventListener('load', () => { + const keyName = 'waline-newest-comments' + const { changeContent, generateHtml, run } = window.newestComments + + const getComment = async (ele) => { + try { + const res = await fetch('!{serverURL}/api/comment?type=recent&count=!{theme.aside.card_newest_comments.limit}', { method: 'GET' }) + const result = await res.json() + const walineArray = result.data.map(e => { + return { + 'content': changeContent(e.comment), + 'avatar': e.avatar, + 'nick': e.nick, + 'url': e.url + '#' + e.objectId, + 'date': e.time || e.insertedAt + } + }) + btf.saveToLocal.set(keyName, JSON.stringify(walineArray), !{theme.aside.card_newest_comments.storage}/(60*24)) + generateHtml(walineArray, ele) + } catch (err) { + console.error(err) + ele.textContent= "!{_p('aside.card_newest_comments.error')}" + } + } + + run(keyName, getComment) + }) diff --git a/themes/butterfly/layout/includes/third-party/pangu.pug b/themes/butterfly/layout/includes/third-party/pangu.pug new file mode 100644 index 0000000..53bb064 --- /dev/null +++ b/themes/butterfly/layout/includes/third-party/pangu.pug @@ -0,0 +1,23 @@ +script. + (() => { + const panguFn = () => { + if (typeof pangu === 'object') pangu.autoSpacingPage() + else { + btf.getScript('!{url_for(theme.asset.pangu)}') + .then(() => { + pangu.autoSpacingPage() + }) + } + } + + const panguInit = () => { + if (!{theme.pangu.field === 'post'}){ + GLOBAL_CONFIG_SITE.isPost && panguFn() + } else { + panguFn() + } + } + + btf.addGlobalFn('pjaxComplete', panguInit, 'pangu') + document.addEventListener('DOMContentLoaded', panguInit) + })() diff --git a/themes/butterfly/layout/includes/third-party/pjax.pug b/themes/butterfly/layout/includes/third-party/pjax.pug new file mode 100644 index 0000000..c63caf1 --- /dev/null +++ b/themes/butterfly/layout/includes/third-party/pjax.pug @@ -0,0 +1,63 @@ +- var pjaxExclude = 'a:not([target="_blank"])' +if theme.pjax.exclude + each val in theme.pjax.exclude + - pjaxExclude += `:not([href="${val}"])` + +- let pjaxSelectors = ['head > title', '#config-diff', '#body-wrap', '#rightside-config-hide', '#rightside-config-show', '.js-pjax'] + +- let choose = theme.comments.use +if choose + if theme.Open_Graph_meta.enable && (choose.includes('Livere') || choose.includes('Utterances') || choose.includes('Giscus')) + - pjaxSelectors.unshift('meta[property="og:image"]', 'meta[property="og:title"]', 'meta[property="og:url"]') + if choose.includes('Utterances') || choose.includes('Giscus') + - pjaxSelectors.unshift('link[rel="canonical"]') + +script(src=url_for(theme.asset.pjax)) +script. + (() => { + const pjaxSelectors = !{JSON.stringify(pjaxSelectors)} + + window.pjax = new Pjax({ + elements: '!{pjaxExclude}', + selectors: pjaxSelectors, + cacheBust: false, + analytics: !{theme.google_analytics ? true : false}, + scrollRestoration: false + }) + + const triggerPjaxFn = (val) => { + if (!val) return + Object.values(val).forEach(fn => fn()) + } + + document.addEventListener('pjax:send', () => { + // removeEventListener + btf.removeGlobalFnEvent('pjaxSendOnce') + btf.removeGlobalFnEvent('themeChange') + + // reset readmode + const $bodyClassList = document.body.classList + if ($bodyClassList.contains('read-mode')) $bodyClassList.remove('read-mode') + + triggerPjaxFn(window.globalFn.pjaxSend) + }) + + document.addEventListener('pjax:complete', () => { + btf.removeGlobalFnEvent('pjaxCompleteOnce') + document.querySelectorAll('script[data-pjax]').forEach(item => { + const newScript = document.createElement('script') + const content = item.text || item.textContent || item.innerHTML || "" + Array.from(item.attributes).forEach(attr => newScript.setAttribute(attr.name, attr.value)) + newScript.appendChild(document.createTextNode(content)) + item.parentNode.replaceChild(newScript, item) + }) + + triggerPjaxFn(window.globalFn.pjaxComplete) + }) + + document.addEventListener('pjax:error', e => { + if (e.request.status === 404) { + pjax.loadUrl('!{url_for("/404.html")}') + } + }) + })() \ No newline at end of file diff --git a/themes/butterfly/layout/includes/third-party/prismjs.pug b/themes/butterfly/layout/includes/third-party/prismjs.pug new file mode 100644 index 0000000..ee30e8b --- /dev/null +++ b/themes/butterfly/layout/includes/third-party/prismjs.pug @@ -0,0 +1,23 @@ +- const { prismjs_js, prismjs_autoloader, prismjs_lineNumber_js } = theme.asset +- const { prismjs, syntax_highlighter } = config +- const { enable, preprocess, line_number } = prismjs + +if (syntax_highlighter === 'prismjs' || enable) && !preprocess + script. + (() => { + window.Prism = window.Prism || {} + window.Prism.manual = true + + const highlightAll = () => { + window.Prism.highlightAll() + } + + window.addEventListener('load', highlightAll) + btf.addGlobalFn('pjaxComplete', highlightAll, 'prismjs') + btf.addGlobalFn('encrypt', highlightAll, 'prismjs') + })() + + script(src=url_for(prismjs_js)) + script(src=url_for(prismjs_autoloader)) + if (line_number) + script(src=url_for(prismjs_lineNumber_js)) \ No newline at end of file diff --git a/themes/butterfly/layout/includes/third-party/search/algolia.pug b/themes/butterfly/layout/includes/third-party/search/algolia.pug new file mode 100644 index 0000000..b1f3c3d --- /dev/null +++ b/themes/butterfly/layout/includes/third-party/search/algolia.pug @@ -0,0 +1,22 @@ +#algolia-search + .search-dialog + nav.search-nav + span.search-dialog-title= _p('search.title') + button.search-close-button + i.fas.fa-times + + .search-wrap + #algolia-search-input + hr + #algolia-search-results + #algolia-hits + #algolia-pagination + #algolia-info + .algolia-stats + .algolia-poweredBy + + #search-mask + + script(src=url_for(theme.asset.algolia_search)) + script(src=url_for(theme.asset.instantsearch)) + script(src=url_for(theme.asset.algolia_js)) \ No newline at end of file diff --git a/themes/butterfly/layout/includes/third-party/search/docsearch.pug b/themes/butterfly/layout/includes/third-party/search/docsearch.pug new file mode 100644 index 0000000..b8e3140 --- /dev/null +++ b/themes/butterfly/layout/includes/third-party/search/docsearch.pug @@ -0,0 +1,29 @@ +- const { placeholder, docsearch: { appId, apiKey, indexName, option } } = theme.search + +.docsearch-wrap + #docsearch(style="display:none") + link(rel="stylesheet" href=url_for(theme.asset.docsearch_css)) + script(src=url_for(theme.asset.docsearch_js)) + script. + (() => { + docsearch(Object.assign({ + appId: '!{appId}', + apiKey: '!{apiKey}', + indexName: '!{indexName}', + container: '#docsearch', + placeholder: '!{ placeholder || _p("search.input_placeholder")}', + }, !{JSON.stringify(option)})) + + const handleClick = () => { + document.querySelector('.DocSearch-Button').click() + } + + const searchClickFn = () => { + btf.addEventListenerPjax(document.querySelector('#search-button > .search'), 'click', handleClick) + } + + searchClickFn() + window.addEventListener('pjax:complete', searchClickFn) + })() + + diff --git a/themes/butterfly/layout/includes/third-party/search/index.pug b/themes/butterfly/layout/includes/third-party/search/index.pug new file mode 100644 index 0000000..a9d0c69 --- /dev/null +++ b/themes/butterfly/layout/includes/third-party/search/index.pug @@ -0,0 +1,7 @@ +case theme.search.use + when 'algolia_search' + include ./algolia.pug + when 'local_search' + include ./local-search.pug + when 'docsearch' + include ./docsearch.pug \ No newline at end of file diff --git a/themes/butterfly/layout/includes/third-party/search/local-search.pug b/themes/butterfly/layout/includes/third-party/search/local-search.pug new file mode 100644 index 0000000..af96ab0 --- /dev/null +++ b/themes/butterfly/layout/includes/third-party/search/local-search.pug @@ -0,0 +1,22 @@ +#local-search + .search-dialog + nav.search-nav + span.search-dialog-title= _p('search.title') + span#loading-status + button.search-close-button + i.fas.fa-times + + #loading-database.is-center + i.fas.fa-spinner.fa-pulse + span= ' ' + _p("search.load_data") + + .search-wrap + #local-search-input + .local-search-box + input(placeholder=theme.search.placeholder || _p("search.input_placeholder") type="text").local-search-box--input + hr + #local-search-results + #local-search-stats-wrap + #search-mask + + script(src=url_for(theme.asset.local_search)) \ No newline at end of file diff --git a/themes/butterfly/layout/includes/third-party/share/addtoany.pug b/themes/butterfly/layout/includes/third-party/share/addtoany.pug new file mode 100644 index 0000000..d42f3be --- /dev/null +++ b/themes/butterfly/layout/includes/third-party/share/addtoany.pug @@ -0,0 +1,10 @@ +.addtoany + .a2a_kit.a2a_kit_size_32.a2a_default_style + - let addtoanyItem = theme.addtoany.item.split(',') + each name in addtoanyItem + a(class="a2a_button_" + name) + + a.a2a_dd(href="https://www.addtoany.com/share") +script(async src='https://static.addtoany.com/menu/page.js') + + diff --git a/themes/butterfly/layout/includes/third-party/share/index.pug b/themes/butterfly/layout/includes/third-party/share/index.pug new file mode 100644 index 0000000..447c589 --- /dev/null +++ b/themes/butterfly/layout/includes/third-party/share/index.pug @@ -0,0 +1,9 @@ +- const { use } = theme.share + +if use + .post-share + case use + when 'addtoany' + !=partial('includes/third-party/share/addtoany', {}, {cache: true}) + when 'sharejs' + include ./share-js.pug \ No newline at end of file diff --git a/themes/butterfly/layout/includes/third-party/share/share-js.pug b/themes/butterfly/layout/includes/third-party/share/share-js.pug new file mode 100644 index 0000000..50d5528 --- /dev/null +++ b/themes/butterfly/layout/includes/third-party/share/share-js.pug @@ -0,0 +1,4 @@ +- const coverVal = page.cover_type === 'img' ? page.cover : theme.avatar.img +.social-share(data-image=url_for(coverVal) data-sites= theme.share.sharejs.sites) +link(rel='stylesheet' href=url_for(theme.asset.sharejs_css) media="print" onload="this.media='all'") +script(src=url_for(theme.asset.sharejs) defer) \ No newline at end of file diff --git a/themes/butterfly/layout/includes/third-party/subtitle.pug b/themes/butterfly/layout/includes/third-party/subtitle.pug new file mode 100644 index 0000000..4689340 --- /dev/null +++ b/themes/butterfly/layout/includes/third-party/subtitle.pug @@ -0,0 +1,93 @@ +- const { effect,source,sub,typed_option } = theme.subtitle +- let subContent = sub || new Array() + +script. + window.typedJSFn = { + init: str => { + window.typed = new Typed('#subtitle', Object.assign({ + strings: str, + startDelay: 300, + typeSpeed: 150, + loop: true, + backSpeed: 50, + }, !{JSON.stringify(typed_option)})) + }, + run: subtitleType => { + if (!{effect}) { + if (typeof Typed === 'function') { + subtitleType() + } else { + btf.getScript('!{url_for(theme.asset.typed)}').then(subtitleType) + } + } else { + subtitleType() + } + } + } + btf.addGlobalFn('pjaxSendOnce', () => { typed.destroy() }, 'typedDestroy') + +case source + when 1 + script. + function subtitleType () { + fetch('https://v1.hitokoto.cn') + .then(response => response.json()) + .then(data => { + if (!{effect}) { + const from = '出自 ' + data.from + const sub = !{JSON.stringify(subContent)} + sub.unshift(data.hitokoto, from) + typedJSFn.init(sub) + } else { + document.getElementById('subtitle').textContent = data.hitokoto + } + }) + } + typedJSFn.run(subtitleType) + + when 2 + script. + function subtitleType () { + btf.getScript('https://yijuzhan.com/api/word.php?m=js').then(() => { + const con = str[0] + if (!{effect}) { + const from = '出自 ' + str[1] + const sub = !{JSON.stringify(subContent)} + sub.unshift(con, from) + typedJSFn.init(sub) + } else { + document.getElementById('subtitle').textContent = con + } + }) + } + typedJSFn.run(subtitleType) + + when 3 + script. + function subtitleType () { + btf.getScript('https://sdk.jinrishici.com/v2/browser/jinrishici.js').then(() => { + jinrishici.load(result =>{ + if (!{effect}) { + const sub = !{JSON.stringify(subContent)} + const content = result.data.content + sub.unshift(content) + typedJSFn.init(sub) + } else { + document.getElementById('subtitle').textContent = result.data.content + } + }) + }) + } + typedJSFn.run(subtitleType) + + default + - subContent = subContent.length ? subContent : new Array(config.subtitle) + script. + function subtitleType () { + if (!{effect}) { + typedJSFn.init(!{JSON.stringify(subContent)}) + } else { + document.getElementById("subtitle").textContent = !{JSON.stringify(subContent[0])} + } + } + typedJSFn.run(subtitleType) \ No newline at end of file diff --git a/themes/butterfly/layout/includes/third-party/umami_analytics.pug b/themes/butterfly/layout/includes/third-party/umami_analytics.pug new file mode 100644 index 0000000..518afd5 --- /dev/null +++ b/themes/butterfly/layout/includes/third-party/umami_analytics.pug @@ -0,0 +1,65 @@ +- let { serverURL, website_id, option, UV_PV } = theme.umami_analytics +- const isServerURL = !!serverURL +- const baseURL = serverURL ? serverURL.replace(/\/$/, '') : 'https://cloud.umami.is' +- const apiUrl = serverURL ? serverURL.replace(/\/$/, '') + '/api' : 'https://api.umami.is/v1' + +script. + (() => { + const option = !{JSON.stringify(option)} + const config = !{JSON.stringify(UV_PV)} + + const runTrack = () => { + umami.track(props => ({ ...props, url: window.location.pathname, title: GLOBAL_CONFIG_SITE.title })) + } + + const loadUmamiJS = () => { + btf.getScript('!{baseURL}/script.js', { + 'data-website-id': '!{website_id}', + 'data-auto-track': 'false', + ...option + }).then(runTrack) + } + + const getData = async (isPost) => { + const now = Date.now() + const keyUrl = isPost ? `&url=${window.location.pathname}` : '' + const headerList = { 'Accept': 'application/json' } + if (!{isServerURL}) headerList['Authorization'] = `Bearer ${config.token}` + else headerList['x-umami-api-key'] = config.token + const res = await fetch(`!{apiUrl}/websites/!{website_id}/stats?startAt=0000000000&endAt=${now}${keyUrl}`, { + method: "GET", + headers: headerList + }) + return await res.json() + } + + const insertData = async () => { + try { + if (GLOBAL_CONFIG_SITE.isPost && config.page_pv) { + const pagePV = document.getElementById('umamiPV') + if (pagePV) { + const data = await getData(true) + pagePV.textContent = data.pageviews.value + } + } else { + const data = (config.site_uv || config.site_pv) && await getData() + if (config.site_uv) { + const siteUV = document.getElementById('umami-site-uv') + if (siteUV) siteUV.textContent = data.visitors.value + } + if (config.site_pv) { + const sitePV = document.getElementById('umami-site-pv') + if (sitePV) sitePV.textContent = data.pageviews.value + } + } + } catch (e) { + console.error('Failed to load Umami Analytics:', e) + } + } + + btf.addGlobalFn('pjaxComplete', runTrack, 'umami_analytics_run_track') + btf.addGlobalFn('pjaxComplete', insertData, 'umami_analytics_insert') + + loadUmamiJS() + insertData() + })() \ No newline at end of file diff --git a/themes/butterfly/layout/includes/widget/card_ad.pug b/themes/butterfly/layout/includes/widget/card_ad.pug new file mode 100644 index 0000000..b8e00fd --- /dev/null +++ b/themes/butterfly/layout/includes/widget/card_ad.pug @@ -0,0 +1,3 @@ +if theme.ad && theme.ad.aside + .card-widget.ads-wrap + != theme.ad.aside diff --git a/themes/butterfly/layout/includes/widget/card_announcement.pug b/themes/butterfly/layout/includes/widget/card_announcement.pug new file mode 100644 index 0000000..9e63627 --- /dev/null +++ b/themes/butterfly/layout/includes/widget/card_announcement.pug @@ -0,0 +1,6 @@ +if theme.aside.card_announcement.enable + .card-widget.card-announcement + .item-headline + i.fas.fa-bullhorn.fa-shake + span= _p('aside.card_announcement') + .announcement_content!= theme.aside.card_announcement.content \ No newline at end of file diff --git a/themes/butterfly/layout/includes/widget/card_archives.pug b/themes/butterfly/layout/includes/widget/card_archives.pug new file mode 100644 index 0000000..bb0e78e --- /dev/null +++ b/themes/butterfly/layout/includes/widget/card_archives.pug @@ -0,0 +1,7 @@ +if theme.aside.card_archives.enable + .card-widget.card-archives + - let type = theme.aside.card_archives.type || 'monthly' + - let format = theme.aside.card_archives.format || 'MMMM YYYY' + - let order = theme.aside.card_archives.order || -1 + - let limit = theme.aside.card_archives.limit === 0 ? 0 : theme.aside.card_archives.limit || 8 + != aside_archives({ type:type, format: format, order: order, limit: limit }) diff --git a/themes/butterfly/layout/includes/widget/card_author.pug b/themes/butterfly/layout/includes/widget/card_author.pug new file mode 100644 index 0000000..c4f01d4 --- /dev/null +++ b/themes/butterfly/layout/includes/widget/card_author.pug @@ -0,0 +1,26 @@ +if theme.aside.card_author.enable + .card-widget.card-info.is-center + .avatar-img + img(src=url_for(theme.avatar.img) onerror=`this.onerror=null;this.src='` + url_for(theme.error_img.flink) + `'` alt="avatar") + .author-info-name= config.author + .author-info-description!= theme.aside.card_author.description || config.description + + .site-data + a(href=url_for(config.archive_dir) + '/') + .headline= _p('aside.articles') + .length-num= site.posts.length + a(href=url_for(config.tag_dir) + '/') + .headline= _p('aside.tags') + .length-num= site.tags.length + a(href=url_for(config.category_dir) + '/') + .headline= _p('aside.categories') + .length-num= site.categories.length + + if theme.aside.card_author.button.enable + a#card-info-btn(href=theme.aside.card_author.button.link) + i(class=theme.aside.card_author.button.icon) + span=theme.aside.card_author.button.text + + if(theme.social) + .card-info-social-icons + !=partial('includes/header/social', {}, {cache: true}) diff --git a/themes/butterfly/layout/includes/widget/card_bottom_self.pug b/themes/butterfly/layout/includes/widget/card_bottom_self.pug new file mode 100644 index 0000000..e32907d --- /dev/null +++ b/themes/butterfly/layout/includes/widget/card_bottom_self.pug @@ -0,0 +1,9 @@ +if site.data.widget && site.data.widget.bottom + each item in site.data.widget.bottom + .card-widget(class=item.class_name id=item.id_name style=item.order ? `order: ${item.order}` : '') + .item-headline + i(class=item.icon) + span=item.name + .item-content + !=item.html + diff --git a/themes/butterfly/layout/includes/widget/card_categories.pug b/themes/butterfly/layout/includes/widget/card_categories.pug new file mode 100644 index 0000000..529ea55 --- /dev/null +++ b/themes/butterfly/layout/includes/widget/card_categories.pug @@ -0,0 +1,4 @@ +if theme.aside.card_categories.enable + if site.categories.length + .card-widget.card-categories + !=aside_categories({ limit: theme.aside.card_categories.limit === 0 ? 0 : theme.aside.card_categories.limit || 8 , expand: theme.aside.card_categories.expand }) diff --git a/themes/butterfly/layout/includes/widget/card_newest_comment.pug b/themes/butterfly/layout/includes/widget/card_newest_comment.pug new file mode 100644 index 0000000..3a0bb18 --- /dev/null +++ b/themes/butterfly/layout/includes/widget/card_newest_comment.pug @@ -0,0 +1,7 @@ +if theme.aside.card_newest_comments.enable && theme.comments.use && !['Livere','Facebook Comments','Giscus'].includes(theme.comments.use[0]) + .card-widget#card-newest-comments + .item-headline + i.fas.fa-comment-dots + span= _p('aside.card_newest_comments.headline') + .aside-list + span= _p('aside.card_newest_comments.loading_text') diff --git a/themes/butterfly/layout/includes/widget/card_post_series.pug b/themes/butterfly/layout/includes/widget/card_post_series.pug new file mode 100644 index 0000000..38f857b --- /dev/null +++ b/themes/butterfly/layout/includes/widget/card_post_series.pug @@ -0,0 +1,21 @@ +if theme.aside.card_post_series.enable + - const array = fragment_cache('seriesArr', groupPosts) + .card-widget.card-post-series + .item-headline + i.fa-solid.fa-layer-group + span= theme.aside.card_post_series.series_title ? page.series : _p('aside.card_post_series') + .aside-list + each item in array[page.series] + - const { path, title = _p('no_title'), cover, cover_type, date:dateA } = item + - let link = url_for(path) + - let no_cover = cover === false || !theme.cover.aside_enable ? 'no-cover' : '' + .aside-list-item(class=no_cover) + if cover && theme.cover.aside_enable + a.thumbnail(href=link title=title) + if cover_type === 'img' + img(src=url_for(cover) onerror=`this.onerror=null;this.src='${url_for(theme.error_img.post_page)}'` alt=title) + else + div(style=`background: ${cover}`) + .content + a.title(href=link title=title)= title + time(datetime=date_xml(dateA) title=_p('post.created') + ' ' + full_date(dateA)) #[=date(dateA, config.date_format)] diff --git a/themes/butterfly/layout/includes/widget/card_post_toc.pug b/themes/butterfly/layout/includes/widget/card_post_toc.pug new file mode 100644 index 0000000..b811210 --- /dev/null +++ b/themes/butterfly/layout/includes/widget/card_post_toc.pug @@ -0,0 +1,15 @@ +- let tocNumber = typeof page.toc_number === 'boolean' ? page.toc_number : theme.toc.number +- let tocExpand = typeof page.toc_expand === 'boolean' ? page.toc_expand : theme.toc.expand +- let tocExpandClass = tocExpand ? 'is-expand' : '' + +#card-toc.card-widget + .item-headline + i.fas.fa-stream + span= _p('aside.card_toc') + span.toc-percentage + + if (page.encrypt == true) + .toc-content.toc-div-class(class=tocExpandClass style="display:none")!=toc(page.origin, {list_number: tocNumber}) + else + .toc-content(class=tocExpandClass)!=toc(page.content, {list_number: tocNumber}) + \ No newline at end of file diff --git a/themes/butterfly/layout/includes/widget/card_recent_post.pug b/themes/butterfly/layout/includes/widget/card_recent_post.pug new file mode 100644 index 0000000..dddf0fc --- /dev/null +++ b/themes/butterfly/layout/includes/widget/card_recent_post.pug @@ -0,0 +1,27 @@ +if theme.aside.card_recent_post.enable + .card-widget.card-recent-post + .item-headline + i.fas.fa-history + span= _p('aside.card_recent_post') + .aside-list + - let postLimit = theme.aside.card_recent_post.limit === 0 ? site.posts.length : theme.aside.card_recent_post.limit || 5 + - let sort = theme.aside.card_recent_post.sort === 'updated' ? 'updated' : 'date' + - site.posts.sort(sort, -1).limit(postLimit).each(function(article){ + - let link = article.link || article.path + - let title = article.title || _p('no_title') + - let no_cover = article.cover === false || !theme.cover.aside_enable ? 'no-cover' : '' + - let post_cover = article.cover + .aside-list-item(class=no_cover) + if post_cover && theme.cover.aside_enable + a.thumbnail(href=url_for(link) title=title) + if article.cover_type === 'img' + img(src=url_for(post_cover) onerror=`this.onerror=null;this.src='${url_for(theme.error_img.post_page)}'` alt=title) + else + div(style=`background: ${post_cover}`) + .content + a.title(href=url_for(link) title=title)= title + if theme.aside.card_recent_post.sort === 'updated' + time(datetime=date_xml(article.updated) title=_p('post.updated') + ' ' + full_date(article.updated)) #[=date(article.updated, config.date_format)] + else + time(datetime=date_xml(article.date) title=_p('post.created') + ' ' + full_date(article.date)) #[=date(article.date, config.date_format)] + - }) \ No newline at end of file diff --git a/themes/butterfly/layout/includes/widget/card_tags.pug b/themes/butterfly/layout/includes/widget/card_tags.pug new file mode 100644 index 0000000..49296b7 --- /dev/null +++ b/themes/butterfly/layout/includes/widget/card_tags.pug @@ -0,0 +1,14 @@ +if theme.aside.card_tags.enable + if site.tags.length + .card-widget.card-tags + .item-headline + i.fas.fa-tags + span= _p('aside.card_tags') + + - let { limit, orderby, order } = theme.aside.card_tags + - limit = limit === 0 ? 0 : limit || 40 + + if theme.aside.card_tags.color + .card-tag-cloud!= cloudTags({source: site.tags, orderby: orderby, order: order, minfontsize: 1.15, maxfontsize: 1.45, limit: limit, unit: 'em'}) + else + .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'}) diff --git a/themes/butterfly/layout/includes/widget/card_top_self.pug b/themes/butterfly/layout/includes/widget/card_top_self.pug new file mode 100644 index 0000000..6e81059 --- /dev/null +++ b/themes/butterfly/layout/includes/widget/card_top_self.pug @@ -0,0 +1,8 @@ +if site.data.widget && site.data.widget.top + each item in site.data.widget.top + .card-widget(class=item.class_name id=item.id_name) + .item-headline + i(class=item.icon) + span=item.name + .item-content + !=item.html \ No newline at end of file diff --git a/themes/butterfly/layout/includes/widget/card_webinfo.pug b/themes/butterfly/layout/includes/widget/card_webinfo.pug new file mode 100644 index 0000000..09ea9c4 --- /dev/null +++ b/themes/butterfly/layout/includes/widget/card_webinfo.pug @@ -0,0 +1,45 @@ +if theme.aside.card_webinfo.enable + .card-widget.card-webinfo + .item-headline + i.fas.fa-chart-line + span= _p('aside.card_webinfo.headline') + .webinfo + if theme.aside.card_webinfo.post_count + .webinfo-item + .item-name= _p('aside.card_webinfo.article_name') + " :" + .item-count= site.posts.length + if theme.aside.card_webinfo.runtime_date + .webinfo-item + .item-name= _p('aside.card_webinfo.runtime.name') + " :" + .item-count#runtimeshow(data-publishDate=date_xml(theme.aside.card_webinfo.runtime_date)) + i.fa-solid.fa-spinner.fa-spin + if theme.wordcount.enable && theme.wordcount.total_wordcount + .webinfo-item + .item-name=_p('aside.card_webinfo.site_wordcount') + " :" + .item-count=totalcount(site) + if theme.umami_analytics.enable && theme.umami_analytics.UV_PV.site_uv + .webinfo-item + .item-name= _p('aside.card_webinfo.site_uv_name') + " :" + .item-count#umami-site-uv + i.fa-solid.fa-spinner.fa-spin + else if theme.busuanzi.site_uv + .webinfo-item + .item-name= _p('aside.card_webinfo.site_uv_name') + " :" + .item-count#busuanzi_value_site_uv + i.fa-solid.fa-spinner.fa-spin + if theme.umami_analytics.enable && theme.umami_analytics.UV_PV.site_pv + .webinfo-item + .item-name= _p('aside.card_webinfo.site_pv_name') + " :" + .item-count#umami-site-pv + i.fa-solid.fa-spinner.fa-spin + else if theme.busuanzi.site_pv + .webinfo-item + .item-name= _p('aside.card_webinfo.site_pv_name') + " :" + .item-count#busuanzi_value_site_pv + i.fa-solid.fa-spinner.fa-spin + if theme.aside.card_webinfo.last_push_date + .webinfo-item + .item-name= _p('aside.card_webinfo.last_push_date.name') + " :" + .item-count#last-push-date(data-lastPushDate=date_xml(Date.now())) + i.fa-solid.fa-spinner.fa-spin + diff --git a/themes/butterfly/layout/includes/widget/index.pug b/themes/butterfly/layout/includes/widget/index.pug new file mode 100644 index 0000000..388ea1c --- /dev/null +++ b/themes/butterfly/layout/includes/widget/index.pug @@ -0,0 +1,36 @@ +#aside-content.aside-content + //- post + if is_post() + - const tocStyle = page.toc_style_simple + - const tocStyleVal = tocStyle === true || tocStyle === false ? tocStyle : theme.toc.style_simple + if showToc && tocStyleVal + .sticky_layout + include ./card_post_toc.pug + else + !=partial('includes/widget/card_author', {}, {cache: true}) + !=partial('includes/widget/card_announcement', {}, {cache: true}) + !=partial('includes/widget/card_top_self', {}, {cache: true}) + .sticky_layout + if showToc + include ./card_post_toc.pug + if page.series + include ./card_post_series.pug + !=partial('includes/widget/card_recent_post', {}, {cache: true}) + !=partial('includes/widget/card_ad', {}, {cache: true}) + else + //- page + !=partial('includes/widget/card_author', {}, {cache: true}) + !=partial('includes/widget/card_announcement', {}, {cache: true}) + !=partial('includes/widget/card_top_self', {}, {cache: true}) + + .sticky_layout + if showToc + include ./card_post_toc.pug + !=partial('includes/widget/card_recent_post', {}, {cache: true}) + !=partial('includes/widget/card_ad', {}, {cache: true}) + !=partial('includes/widget/card_newest_comment', {}, {cache: true}) + !=partial('includes/widget/card_categories', {}, {cache: true}) + !=partial('includes/widget/card_tags', {}, {cache: true}) + !=partial('includes/widget/card_archives', {}, {cache: true}) + !=partial('includes/widget/card_webinfo', {}, {cache: true}) + !=partial('includes/widget/card_bottom_self', {}, {cache: true}) \ No newline at end of file diff --git a/themes/butterfly/layout/index.pug b/themes/butterfly/layout/index.pug new file mode 100644 index 0000000..7705655 --- /dev/null +++ b/themes/butterfly/layout/index.pug @@ -0,0 +1,5 @@ +extends includes/layout.pug + +block content + include ./includes/mixins/indexPostUI.pug + +indexPostUI \ No newline at end of file diff --git a/themes/butterfly/layout/page.pug b/themes/butterfly/layout/page.pug new file mode 100644 index 0000000..5fdbbbd --- /dev/null +++ b/themes/butterfly/layout/page.pug @@ -0,0 +1,32 @@ +extends includes/layout.pug + +block content + - const noCardLayout = ['shuoshuo', '404'].includes(page.type) ? 'nc' : '' + - var commentsJsLoad = false + + mixin commentLoad + if page.comments !== false && theme.comments.use + - commentsJsLoad = true + !=partial('includes/third-party/comments/index', {}, {cache: true}) + + #page(class=noCardLayout) + if top_img === false && page.title + .page-title= page.title + + case page.type + when 'tags' + include includes/page/tags.pug + +commentLoad + when 'link' + include includes/page/flink.pug + +commentLoad + when 'categories' + include includes/page/categories.pug + +commentLoad + when '404' + include includes/page/404.pug + when 'shuoshuo' + include includes/page/shuoshuo.pug + default + include includes/page/default-page.pug + +commentLoad \ No newline at end of file diff --git a/themes/butterfly/layout/post.pug b/themes/butterfly/layout/post.pug new file mode 100644 index 0000000..07c696e --- /dev/null +++ b/themes/butterfly/layout/post.pug @@ -0,0 +1,32 @@ +extends includes/layout.pug + +block content + #post + if top_img === false + include includes/header/post-info.pug + + article#article-container.post-content!=page.content + include includes/post/post-copyright.pug + .tag_share + if (page.tags.length > 0 && theme.post_meta.post.tags) + .post-meta__tag-list + each item, index in page.tags.data + a(href=url_for(item.path)).post-meta__tags #[=item.name] + include includes/third-party/share/index.pug + + if theme.reward.enable && theme.reward.QR_code + !=partial('includes/post/reward', {}, {cache: true}) + + //- ad + if theme.ad && theme.ad.post + .ads-wrap!=theme.ad.post + + if theme.post_pagination + include includes/pagination.pug + if theme.related_post && theme.related_post.enable + != related_posts(page,site.posts) + + if page.comments !== false && theme.comments.use + - var commentsJsLoad = true + !=partial('includes/third-party/comments/index', {}, {cache: true}) + \ No newline at end of file diff --git a/themes/butterfly/layout/tag.pug b/themes/butterfly/layout/tag.pug new file mode 100644 index 0000000..3d4c067 --- /dev/null +++ b/themes/butterfly/layout/tag.pug @@ -0,0 +1,12 @@ +extends includes/layout.pug + +block content + if theme.tag_ui == 'index' + include ./includes/mixins/indexPostUI.pug + +indexPostUI + else + include ./includes/mixins/article-sort.pug + #tag + .article-sort-title= _p('page.tag') + ' - ' + page.tag + +articleSort(page.posts) + include includes/pagination.pug \ No newline at end of file diff --git a/themes/butterfly/package.json b/themes/butterfly/package.json new file mode 100644 index 0000000..b03744e --- /dev/null +++ b/themes/butterfly/package.json @@ -0,0 +1,32 @@ +{ + "name": "hexo-theme-butterfly", + "version": "5.0.0", + "description": "A Simple and Card UI Design theme for Hexo", + "main": "package.json", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [ + "hexo", + "theme", + "butterfly", + "Card UI Design", + "Jerry", + "hexo-theme-butterfly" + ], + "repository": { + "type" : "git", + "url" : "https://github.com/jerryc127/hexo-theme-butterfly.git" + }, + "bugs": { + "url": "https://github.com/jerryc127/hexo-theme-butterfly/issues", + "email": "my@crazywong.com" + }, + "dependencies": { + "hexo-renderer-stylus": "^3.0.1", + "hexo-renderer-pug": "^3.0.0" + }, + "homepage": "https://butterfly.js.org/", + "author": "Jerry ", + "license": "Apache-2.0" +} diff --git a/themes/butterfly/plugins.yml b/themes/butterfly/plugins.yml new file mode 100644 index 0000000..9b4bd9a --- /dev/null +++ b/themes/butterfly/plugins.yml @@ -0,0 +1,211 @@ +abcjs_basic_js: + name: abcjs + file: dist/abcjs-basic-min.js + version: 6.4.3 +activate_power_mode: + name: butterfly-extsrc + file: dist/activate-power-mode.min.js + version: 1.1.4 +algolia_search: + name: algoliasearch + file: dist/algoliasearch-lite.umd.js + version: 5.7.0 +aplayer_css: + name: aplayer + file: dist/APlayer.min.css + version: 1.10.1 +aplayer_js: + name: aplayer + file: dist/APlayer.min.js + version: 1.10.1 +artalk_css: + name: artalk + file: dist/Artalk.css + version: 2.9.1 +artalk_js: + name: artalk + file: dist/Artalk.js + version: 2.9.1 +blueimp_md5: + name: blueimp-md5 + file: js/md5.min.js + version: 2.19.0 +canvas_fluttering_ribbon: + name: butterfly-extsrc + file: dist/canvas-fluttering-ribbon.min.js + version: 1.1.4 +canvas_nest: + name: butterfly-extsrc + file: dist/canvas-nest.min.js + version: 1.1.4 +canvas_ribbon: + name: butterfly-extsrc + file: dist/canvas-ribbon.min.js + version: 1.1.4 +clickShowText: + name: butterfly-extsrc + file: dist/click-show-text.min.js + version: 1.1.4 +click_heart: + name: butterfly-extsrc + file: dist/click-heart.min.js + version: 1.1.4 +disqusjs: + name: disqusjs + file: dist/browser/disqusjs.es2015.umd.min.js + version: 3.0.2 +disqusjs_css: + name: disqusjs + file: dist/browser/styles/disqusjs.css + version: 3.0.2 +docsearch_css: + name: '@docsearch/css' + other_name: docsearch-css + file: dist/style.css + version: 3.6.2 +docsearch_js: + name: '@docsearch/js' + other_name: docsearch-js + file: dist/umd/index.js + version: 3.6.2 +egjs_infinitegrid: + name: '@egjs/infinitegrid' + other_name: egjs-infinitegrid + file: dist/infinitegrid.min.js + version: 4.12.0 +fancybox: + name: '@fancyapps/ui' + file: dist/fancybox/fancybox.umd.js + version: 5.0.36 + other_name: fancyapps-ui +fancybox_css: + name: '@fancyapps/ui' + file: dist/fancybox/fancybox.css + version: 5.0.36 + other_name: fancyapps-ui +fireworks: + name: butterfly-extsrc + file: dist/fireworks.min.js + version: 1.1.4 +fontawesome: + name: '@fortawesome/fontawesome-free' + file: css/all.min.css + other_name: font-awesome + version: 6.6.0 +gitalk: + name: gitalk + file: dist/gitalk.min.js + version: 1.8.0 +gitalk_css: + name: gitalk + file: dist/gitalk.css + version: 1.8.0 +instantpage: + name: instant.page + file: instantpage.js + version: 5.2.0 +instantsearch: + name: instantsearch.js + file: dist/instantsearch.production.min.js + version: 4.74.2 +katex: + name: katex + file: dist/katex.min.css + other_name: KaTeX + version: 0.16.11 +katex_copytex: + name: katex + file: dist/contrib/copy-tex.min.js + other_name: KaTeX + version: 0.16.11 +lazyload: + name: vanilla-lazyload + file: dist/lazyload.iife.min.js + version: 19.1.3 +mathjax: + name: mathjax + file: es5/tex-mml-chtml.js + version: 3.2.2 +medium_zoom: + name: medium-zoom + file: dist/medium-zoom.min.js + version: 1.1.0 +mermaid: + name: mermaid + file: dist/mermaid.min.js + version: 11.2.1 +meting_js: + name: butterfly-extsrc + file: metingjs/dist/Meting.min.js + version: 1.1.4 +pace_default_css: + name: pace-js + other_name: pace + file: themes/blue/pace-theme-minimal.css + version: 1.2.4 +pace_js: + name: pace-js + other_name: pace + file: pace.min.js + version: 1.2.4 +pangu: + name: pangu + file: dist/browser/pangu.min.js + version: 4.0.7 +pjax: + name: pjax + file: pjax.min.js + version: 0.2.8 +prismjs_autoloader: + name: prismjs + file: plugins/autoloader/prism-autoloader.min.js + other_name: prism + version: 1.29.0 +prismjs_js: + name: prismjs + file: prism.js + other_name: prism + version: 1.29.0 +prismjs_lineNumber_js: + name: prismjs + file: plugins/line-numbers/prism-line-numbers.min.js + other_name: prism + version: 1.29.0 +sharejs: + name: butterfly-extsrc + file: sharejs/dist/js/social-share.min.js + version: 1.1.4 +sharejs_css: + name: butterfly-extsrc + file: sharejs/dist/css/share.min.css + version: 1.1.4 +snackbar: + name: node-snackbar + file: dist/snackbar.min.js + version: 0.1.16 +snackbar_css: + name: node-snackbar + file: dist/snackbar.min.css + version: 0.1.16 +twikoo: + name: twikoo + file: dist/twikoo.all.min.js + version: 1.6.39 +typed: + name: typed.js + file: dist/typed.umd.js + version: 2.1.0 +valine: + name: valine + file: dist/Valine.min.js + version: 1.5.2 +waline_css: + name: '@waline/client' + file: dist/waline.css + other_name: waline + version: 3.3.2 +waline_js: + name: '@waline/client' + file: dist/waline.js + other_name: waline + version: 3.3.2 diff --git a/themes/butterfly/scripts/events/404.js b/themes/butterfly/scripts/events/404.js new file mode 100644 index 0000000..17a05e9 --- /dev/null +++ b/themes/butterfly/scripts/events/404.js @@ -0,0 +1,20 @@ +/** + * Butterfly + * 404 error page + */ + +'use strict' + +hexo.extend.generator.register('404', function (locals) { + if (!hexo.theme.config.error_404.enable) return + return { + path: '404.html', + layout: ['page'], + data: { + type: '404', + top_img: false, + comments: false, + aside: false + } + } +}) diff --git a/themes/butterfly/scripts/events/cdn.js b/themes/butterfly/scripts/events/cdn.js new file mode 100644 index 0000000..b83404f --- /dev/null +++ b/themes/butterfly/scripts/events/cdn.js @@ -0,0 +1,97 @@ +/** + * Butterfly + * Merge CDN + */ + +'use strict' + +const { version } = require('../../package.json') +const path = require('path') + +hexo.extend.filter.register('before_generate', () => { + const themeConfig = hexo.theme.config + const { CDN } = themeConfig + + const thirdPartySrc = hexo.render.renderSync({ path: path.join(hexo.theme_dir, '/plugins.yml'), engine: 'yaml' }) + const internalSrc = { + main: { + name: 'hexo-theme-butterfly', + file: 'js/main.js', + version + }, + utils: { + name: 'hexo-theme-butterfly', + file: 'js/utils.js', + version + }, + translate: { + name: 'hexo-theme-butterfly', + file: 'js/tw_cn.js', + version + }, + local_search: { + name: 'hexo-theme-butterfly', + file: 'js/search/local-search.js', + version + }, + algolia_js: { + name: 'hexo-theme-butterfly', + file: 'js/search/algolia.js', + version + } + } + + const minFile = file => { + return file.replace(/(? '.min' + ext) + } + + const createCDNLink = (data, type, cond = '') => { + Object.keys(data).forEach(key => { + let { name, version, file, other_name } = data[key] + const cdnjs_name = other_name || name + const cdnjs_file = file.replace(/^[lib|dist]*\/|browser\//g, '') + const min_cdnjs_file = minFile(cdnjs_file) + if (cond === 'internal') file = `source/${file}` + const min_file = minFile(file) + const verType = CDN.version ? (type === 'local' ? `?v=${version}` : `@${version}`) : '' + + const value = { + version, + name, + file, + cdnjs_file, + min_file, + min_cdnjs_file, + cdnjs_name + } + + const cdnSource = { + local: cond === 'internal' ? `${cdnjs_file + verType}` : `/pluginsSrc/${name}/${file + verType}`, + jsdelivr: `https://cdn.jsdelivr.net/npm/${name}${verType}/${min_file}`, + unpkg: `https://unpkg.com/${name}${verType}/${file}`, + cdnjs: `https://cdnjs.cloudflare.com/ajax/libs/${cdnjs_name}/${version}/${min_cdnjs_file}`, + custom: (CDN.custom_format || '').replace(/\$\{(.+?)\}/g, (match, $1) => value[$1]) + } + + data[key] = cdnSource[type] + }) + + if (cond === 'internal') data.main_css = 'css/index.css' + (CDN.version ? `?v=${version}` : '') + return data + } + + // delete null value + const deleteNullValue = obj => { + if (!obj) return + for (const i in obj) { + obj[i] === null && delete obj[i] + } + return obj + } + + themeConfig.asset = Object.assign( + createCDNLink(internalSrc, CDN.internal_provider, 'internal'), + createCDNLink(thirdPartySrc, CDN.third_party_provider), + deleteNullValue(CDN.option) + ) +}) diff --git a/themes/butterfly/scripts/events/comment.js b/themes/butterfly/scripts/events/comment.js new file mode 100644 index 0000000..b38e1de --- /dev/null +++ b/themes/butterfly/scripts/events/comment.js @@ -0,0 +1,17 @@ +/** + * Capitalize the first letter of comment name + */ + +hexo.extend.filter.register('before_generate', () => { + const themeConfig = hexo.theme.config + let { use } = themeConfig.comments + if (!use) return + + // 確保 use 是一個陣列 + use = Array.isArray(use) ? use : use.split(',') + + // 將每個項目轉換為小寫並將首字母大寫 + themeConfig.comments.use = use.map(item => + item.trim().toLowerCase().replace(/\b[a-z]/g, s => s.toUpperCase()) + ) +}) diff --git a/themes/butterfly/scripts/events/init.js b/themes/butterfly/scripts/events/init.js new file mode 100644 index 0000000..36304d8 --- /dev/null +++ b/themes/butterfly/scripts/events/init.js @@ -0,0 +1,20 @@ +hexo.extend.filter.register('before_generate', () => { + // Get first two digits of the Hexo version number + const { version, log, locals } = hexo + const hexoVer = version.replace(/(^.*\..*)\..*/, '$1') + + if (hexoVer < 5.3) { + log.error('Please update Hexo to V5.3.0 or higher!') + log.error('請把 Hexo 升級到 V5.3.0 或更高的版本!') + process.exit(-1) + } + + if (locals.get) { + const data = locals.get('data') + if (data && data.butterfly) { + log.error("'butterfly.yml' is deprecated. Please use '_config.butterfly.yml'") + log.error("'butterfly.yml' 已經棄用,請使用 '_config.butterfly.yml'") + process.exit(-1) + } + } +}) diff --git a/themes/butterfly/scripts/events/merge_config.js b/themes/butterfly/scripts/events/merge_config.js new file mode 100644 index 0000000..07e8bce --- /dev/null +++ b/themes/butterfly/scripts/events/merge_config.js @@ -0,0 +1,573 @@ +const { deepMerge } = require('hexo-util') + +hexo.extend.filter.register('before_generate', () => { + const defaultConfig = { + nav: { + logo: null, + display_title: true, + fixed: false + }, + menu: null, + code_blocks: { + theme: 'light', + macStyle: false, + height_limit: false, + word_wrap: false, + copy: true, + language: true, + shrink: false, + fullpage: false + }, + social: null, + favicon: '/img/favicon.png', + avatar: { + img: '/img/butterfly-icon.png', + effect: false + }, + disable_top_img: false, + default_top_img: null, + index_img: null, + archive_img: null, + tag_img: null, + tag_per_img: null, + category_img: null, + category_per_img: null, + footer_img: false, + background: null, + cover: { + index_enable: true, + aside_enable: true, + archives_enable: true, + default_cover: null + }, + error_img: { + flink: '/img/friend_404.gif', + post_page: '/img/404.jpg' + }, + error_404: { + enable: false, + subtitle: 'Page Not Found', + background: '/img/error-page.png' + }, + post_meta: { + page: { + date_type: 'created', + date_format: 'date', + categories: true, + tags: false, + label: true + }, + post: { + position: 'left', + date_type: 'both', + date_format: 'date', + categories: true, + tags: true, + label: true + } + }, + index_site_info_top: null, + index_top_img_height: null, + subtitle: { + enable: false, + effect: true, + typed_option: null, + source: false, + sub: null + }, + index_layout: 3, + index_post_content: { + method: 3, + length: 500 + }, + toc: { + post: true, + page: false, + number: true, + expand: false, + style_simple: false, + scroll_percent: true + }, + post_copyright: { + enable: true, + decode: false, + author_href: null, + license: 'CC BY-NC-SA 4.0', + license_url: 'https://creativecommons.org/licenses/by-nc-sa/4.0/' + }, + reward: { + enable: false, + text: null, + QR_code: null + }, + post_edit: { + enable: false, + url: null + }, + related_post: { + enable: true, + limit: 6, + date_type: 'created' + }, + post_pagination: 1, + noticeOutdate: { + enable: false, + style: 'flat', + limit_day: 365, + position: 'top', + message_prev: 'It has been', + message_next: 'days since the last update, the content of the article may be outdated.' + }, + footer: { + owner: { + enable: true, + since: 2019 + }, + custom_text: null, + copyright: true + }, + aside: { + enable: true, + hide: false, + button: true, + mobile: true, + position: 'right', + display: { + archive: true, + tag: true, + category: true + }, + card_author: { + enable: true, + description: null, + button: { + enable: true, + icon: 'fab fa-github', + text: 'Follow Me', + link: 'https://github.com/xxxxxx' + } + }, + card_announcement: { + enable: true, + content: 'This is my Blog' + }, + card_recent_post: { + enable: true, + limit: 5, + sort: 'date', + sort_order: null + }, + card_newest_comments: { + enable: false, + sort_order: null, + limit: 6, + storage: 10, + avatar: true + }, + card_categories: { + enable: true, + limit: 8, + expand: 'none', + sort_order: null + }, + card_tags: { + enable: true, + limit: 40, + color: false, + orderby: 'random', + order: 1, + sort_order: null + }, + card_archives: { + enable: true, + type: 'monthly', + format: 'MMMM YYYY', + order: -1, + limit: 8, + sort_order: null + }, + card_post_series: { + enable: true, + series_title: false, + orderBy: 'date', + order: -1 + }, + card_webinfo: { + enable: true, + post_count: true, + last_push_date: true, + sort_order: null, + runtime_date: null + } + }, + rightside_bottom: null, + translate: { + enable: false, + default: '繁', + defaultEncoding: 2, + translateDelay: 0, + msgToTraditionalChinese: '繁', + msgToSimplifiedChinese: '簡' + }, + readmode: true, + darkmode: { + enable: true, + button: true, + autoChangeMode: false, + start: null, + end: null + }, + rightside_scroll_percent: false, + rightside_item_order: { + enable: false, + hide: null, + show: null + }, + anchor: { + auto_update: false, + click_to_scroll: false + }, + photofigcaption: false, + copy: { + enable: true, + copyright: { + enable: false, + limit_count: 150 + } + }, + wordcount: { + enable: false, + post_wordcount: true, + min2read: true, + total_wordcount: true + }, + busuanzi: { + site_uv: true, + site_pv: true, + page_pv: true + }, + math: { + use: null, + per_page: true, + hide_scrollbar: false, + mathjax: { + enableMenu: true, + tags: 'none' + }, + katex: { + copy_tex: false + } + }, + search: { + use: null, + placeholder: null, + algolia_search: { + hitsPerPage: 6 + }, + local_search: { + preload: false, + top_n_per_article: 1, + unescape: false, + CDN: null + }, + docsearch: { + appId: null, + apiKey: null, + indexName: null, + option: null + } + }, + share: { + use: 'sharejs', + sharejs: { + sites: 'facebook,twitter,wechat,weibo,qq' + }, + addtoany: { + item: 'facebook,twitter,wechat,sina_weibo,facebook_messenger,email,copy_link' + } + }, + comments: { + use: null, + text: true, + lazyload: false, + count: false, + card_post_count: false + }, + disqus: { + shortname: null, + apikey: null + }, + disqusjs: { + shortname: null, + apikey: null, + option: null + }, + livere: { + uid: null + }, + gitalk: { + client_id: null, + client_secret: null, + repo: null, + owner: null, + admin: null, + option: null + }, + valine: { + appId: null, + appKey: null, + avatar: 'monsterid', + serverURLs: null, + bg: null, + visitor: false, + option: null + }, + waline: { + serverURL: null, + bg: null, + pageview: false, + option: null + }, + utterances: { + repo: null, + issue_term: 'pathname', + light_theme: 'github-light', + dark_theme: 'photon-dark', + js: null, + option: null + }, + facebook_comments: { + app_id: null, + user_id: null, + pageSize: 10, + order_by: 'social', + lang: 'zh_TW' + }, + twikoo: { + envId: null, + region: null, + visitor: false, + option: null + }, + giscus: { + repo: null, + repo_id: null, + category_id: null, + light_theme: 'light', + dark_theme: 'dark', + js: null, + option: null + }, + remark42: { + host: null, + siteId: null, + option: null + }, + artalk: { + server: null, + site: null, + visitor: false, + option: null + }, + chat: { + use: null, + rightside_button: false, + button_hide_show: false + }, + chatra: { + id: null + }, + tidio: { + public_key: null + }, + daovoice: { + app_id: null + }, + crisp: { + website_id: null + }, + baidu_analytics: null, + google_analytics: null, + cloudflare_analytics: null, + microsoft_clarity: null, + umami_analytics: { + enable: false, + serverURL: null, + website_id: null, + option: null, + UV_PV: { + site_uv: false, + site_pv: false, + page_pv: false, + token: null + } + }, + google_adsense: { + enable: false, + auto_ads: true, + js: 'https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js', + client: null, + enable_page_level_ads: true + }, + ad: { + index: null, + aside: null, + post: null + }, + site_verification: null, + category_ui: null, + tag_ui: null, + rounded_corners_ui: true, + text_align_justify: false, + mask: { + header: true, + footer: true + }, + preloader: { + enable: false, + source: 1, + pace_css_url: null + }, + enter_transitions: true, + display_mode: 'light', + beautify: { + enable: false, + field: 'post', + title_prefix_icon: null, + title_prefix_icon_color: null + }, + font: { + global_font_size: null, + code_font_size: null, + font_family: null, + code_font_family: null + }, + blog_title_font: { + font_link: null, + font_family: null + }, + hr_icon: { + enable: true, + icon: null, + icon_top: null + }, + activate_power_mode: { + enable: false, + colorful: true, + shake: true, + mobile: false + }, + canvas_ribbon: { + enable: false, + size: 150, + alpha: 0.6, + zIndex: -1, + click_to_change: false, + mobile: false + }, + canvas_fluttering_ribbon: { + enable: false, + mobile: false + }, + canvas_nest: { + enable: false, + color: '0,0,255', + opacity: 0.7, + zIndex: -1, + count: 99, + mobile: false + }, + fireworks: { + enable: false, + zIndex: 9999, + mobile: false + }, + click_heart: { + enable: false, + mobile: false + }, + clickShowText: { + enable: false, + text: null, + fontSize: '15px', + random: false, + mobile: false + }, + lightbox: null, + series: { + enable: false, + orderBy: 'title', + order: 1, + number: true + }, + abcjs: { + enable: false, + per_page: true + }, + mermaid: { + enable: false, + code_write: false, + theme: { + light: 'default', + dark: 'dark' + } + }, + note: { + style: 'flat', + icons: true, + border_radius: 3, + light_bg_offset: 0 + }, + pjax: { + enable: false, + exclude: null + }, + aplayerInject: { + enable: false, + per_page: true + }, + snackbar: { + enable: false, + position: 'bottom-left', + bg_light: '#49b1f5', + bg_dark: '#1f1f1f' + }, + instantpage: false, + pangu: { + enable: false, + field: 'site' + }, + lazyload: { + enable: false, + field: 'site', + placeholder: null, + blur: false + }, + pwa: { + enable: false, + manifest: null, + apple_touch_icon: null, + favicon_32_32: null, + favicon_16_16: null, + mask_icon: null + }, + Open_Graph_meta: { + enable: true, + option: null + }, + css_prefix: true, + inject: { + head: null, + bottom: null + }, + CDN: { + internal_provider: 'local', + third_party_provider: 'jsdelivr', + version: false, + custom_format: null, + option: null + } + } + + hexo.theme.config = deepMerge(defaultConfig, hexo.theme.config) +}, 1) diff --git a/themes/butterfly/scripts/events/stylus.js b/themes/butterfly/scripts/events/stylus.js new file mode 100644 index 0000000..15ca4ff --- /dev/null +++ b/themes/butterfly/scripts/events/stylus.js @@ -0,0 +1,24 @@ +/** + * Stylus renderer + */ + +'use strict' + +hexo.extend.filter.register('stylus:renderer', style => { + const { syntax_highlighter: syntaxHighlighter, highlight, prismjs } = hexo.config + let { enable: highlightEnable, line_number: highlightLineNumber } = highlight + let { enable: prismjsEnable, line_number: prismjsLineNumber } = prismjs + + // for hexo > 7.0 + if (syntaxHighlighter) { + highlightEnable = syntaxHighlighter === 'highlight.js' + prismjsEnable = syntaxHighlighter === 'prismjs' + } + + style.define('$highlight_enable', highlightEnable) + .define('$highlight_line_number', highlightLineNumber) + .define('$prismjs_enable', prismjsEnable) + .define('$prismjs_line_number', prismjsLineNumber) + .define('$language', hexo.config.language) + // .import(`${this.source_dir.replace(/\\/g, '/')}_data/css/*`) +}) diff --git a/themes/butterfly/scripts/events/welcome.js b/themes/butterfly/scripts/events/welcome.js new file mode 100644 index 0000000..f4c018a --- /dev/null +++ b/themes/butterfly/scripts/events/welcome.js @@ -0,0 +1,13 @@ +hexo.on('ready', () => { + const { version } = require('../../package.json') + hexo.log.info(` + =================================================================== + ##### # # ##### ##### ###### ##### ###### # # # + # # # # # # # # # # # # # + ##### # # # # ##### # # ##### # # + # # # # # # # ##### # # # + # # # # # # # # # # # # + ##### #### # # ###### # # # ###### # + ${version} + ===================================================================`) +}) diff --git a/themes/butterfly/scripts/filters/post_lazyload.js b/themes/butterfly/scripts/filters/post_lazyload.js new file mode 100644 index 0000000..cecc6ce --- /dev/null +++ b/themes/butterfly/scripts/filters/post_lazyload.js @@ -0,0 +1,27 @@ +/** + * Butterfly + * lazyload + * replace src to data-lazy-src + */ + +'use strict' + +const urlFor = require('hexo-util').url_for.bind(hexo) + +const lazyload = htmlContent => { + const bg = hexo.theme.config.lazyload.placeholder ? urlFor(hexo.theme.config.lazyload.placeholder) : 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7' + return htmlContent.replace(/( { + const { enable, field } = hexo.theme.config.lazyload + if (!enable || field !== 'site') return + return lazyload(data) +}) + +hexo.extend.filter.register('after_post_render', data => { + const { enable, field } = hexo.theme.config.lazyload + if (!enable || field !== 'post') return + data.content = lazyload(data.content) + return data +}) diff --git a/themes/butterfly/scripts/filters/random_cover.js b/themes/butterfly/scripts/filters/random_cover.js new file mode 100644 index 0000000..7dac3cf --- /dev/null +++ b/themes/butterfly/scripts/filters/random_cover.js @@ -0,0 +1,40 @@ +/** + * Butterfly + * ramdom cover + */ + +'use strict' + +hexo.extend.filter.register('before_post_render', data => { + const imgTestReg = /\.(png|jpe?g|gif|svg|webp)(\?.*)?$/i + let { cover: coverVal, top_img: topImg } = data + + // Add path to top_img and cover if post_asset_folder is enabled + if (hexo.config.post_asset_folder) { + if (topImg && topImg.indexOf('/') === -1 && imgTestReg.test(topImg)) data.top_img = `${data.path}${topImg}` + if (coverVal && coverVal.indexOf('/') === -1 && imgTestReg.test(coverVal)) data.cover = `${data.path}${coverVal}` + } + + const randomCoverFn = () => { + const { cover: { default_cover: defaultCover } } = hexo.theme.config + if (!defaultCover) return false + if (!Array.isArray(defaultCover)) return defaultCover + const num = Math.floor(Math.random() * defaultCover.length) + return defaultCover[num] + } + + if (coverVal === false) return data + + // If cover is not set, use random cover + if (!coverVal) { + const randomCover = randomCoverFn() + data.cover = randomCover + coverVal = randomCover // update coverVal + } + + if (coverVal && (coverVal.indexOf('//') !== -1 || imgTestReg.test(coverVal))) { + data.cover_type = 'img' + } + + return data +}) diff --git a/themes/butterfly/scripts/helpers/aside_archives.js b/themes/butterfly/scripts/helpers/aside_archives.js new file mode 100644 index 0000000..1cf7521 --- /dev/null +++ b/themes/butterfly/scripts/helpers/aside_archives.js @@ -0,0 +1,77 @@ +'use strict' + +hexo.extend.helper.register('aside_archives', function (options = {}) { + const { config, page, site, url_for, _p } = this + const archiveDir = config.archive_dir + const { timezone } = config + const lang = toMomentLocale(page.lang || page.language || config.language) + const type = options.type || 'monthly' + const format = options.format || (type === 'monthly' ? 'MMMM YYYY' : 'YYYY') + const showCount = Object.prototype.hasOwnProperty.call(options, 'show_count') ? options.show_count : true + const order = options.order || -1 + const limit = options.limit + const compareFunc = type === 'monthly' + ? (yearA, monthA, yearB, monthB) => yearA === yearB && monthA === monthB + : (yearA, monthA, yearB, monthB) => yearA === yearB + + const posts = site.posts.sort('date', order) + if (!posts.length) return '' + + const data = [] + posts.forEach(post => { + let date = post.date.clone() + if (timezone) date = date.tz(timezone) + + const year = date.year() + const month = date.month() + 1 + + if (!data.length || !compareFunc(data[data.length - 1].year, data[data.length - 1].month, year, month)) { + if (lang) date = date.locale(lang) + data.push({ name: date.format(format), year, month, count: 1 }) + } else { + data[data.length - 1].count++ + } + }) + + const link = item => { + let url = `${archiveDir}/${item.year}/` + if (type === 'monthly') { + url += item.month < 10 ? `0${item.month}/` : `${item.month}/` + } + return url_for(url) + } + + const len = data.length + const limitLength = limit === 0 ? len : Math.min(len, limit) + + let result = ` +
+ + ${_p('aside.card_archives')} + ${len > limitLength ? `` : ''} +
+ ' + return result +}) + +const toMomentLocale = function (lang) { + if (!lang || lang === 'en' || lang === 'default') { + return 'en' + } + return lang.toLowerCase().replace('_', '-') +} diff --git a/themes/butterfly/scripts/helpers/aside_categories.js b/themes/butterfly/scripts/helpers/aside_categories.js new file mode 100644 index 0000000..4fb3135 --- /dev/null +++ b/themes/butterfly/scripts/helpers/aside_categories.js @@ -0,0 +1,81 @@ +'use strict' + +hexo.extend.helper.register('aside_categories', function (categories, options = {}) { + if (!categories || !Object.prototype.hasOwnProperty.call(categories, 'length')) { + options = categories || {} + categories = this.site.categories + } + + if (!categories || !categories.length) return '' + + const { config } = this + const showCount = Object.prototype.hasOwnProperty.call(options, 'show_count') ? options.show_count : true + const depth = options.depth ? parseInt(options.depth, 10) : 0 + const orderby = options.orderby || 'name' + const order = options.order || 1 + const categoryDir = this.url_for(config.category_dir) + const limit = options.limit === 0 ? categories.length : (options.limit || categories.length) + const isExpand = options.expand !== 'none' + const expandClass = isExpand && options.expand === true ? 'expand' : '' + const buttonLabel = this._p('aside.more_button') + + const prepareQuery = parent => { + const query = parent ? { parent } : { parent: { $exists: false } } + return categories.find(query).sort(orderby, order).filter(cat => cat.length) + } + + const hierarchicalList = (remaining, level = 0, parent) => { + let result = '' + if (remaining > 0) { + prepareQuery(parent).forEach(cat => { + if (remaining > 0) { + remaining -= 1 + let child = '' + if (!depth || level + 1 < depth) { + const childList = hierarchicalList(remaining, level + 1, cat._id) + child = childList.result + remaining = childList.remaining + } + + const parentClass = isExpand && !parent && child ? 'parent' : '' + result += `
  • ` + result += `` + result += `${cat.name}` + + if (showCount) { + result += `${cat.length}` + } + + if (isExpand && !parent && child) { + result += `` + } + + result += '' + + if (child) { + result += `
      ${child}
    ` + } + + result += '
  • ' + } + }) + } + return { result, remaining } + } + + const list = hierarchicalList(limit) + + const moreButton = categories.length > limit + ? ` + ` + : '' + + return `
    + + ${this._p('aside.card_categories')} + ${moreButton} +
    +
      + ${list.result} +
    ` +}) diff --git a/themes/butterfly/scripts/helpers/getArchiveLength.js b/themes/butterfly/scripts/helpers/getArchiveLength.js new file mode 100644 index 0000000..1ff35c8 --- /dev/null +++ b/themes/butterfly/scripts/helpers/getArchiveLength.js @@ -0,0 +1,45 @@ +hexo.extend.helper.register('getArchiveLength', function () { + const archiveGenerator = hexo.config.archive_generator + const posts = this.site.posts + + const { yearly, monthly, daily } = archiveGenerator + const { year, month, day } = this.page + + // Archives Page + if (!year) return posts.length + + // Function to generate a unique key based on the granularity + const getKey = (post, type) => { + const date = post.date.clone() + const y = date.year() + const m = date.month() + 1 + const d = date.date() + if (type === 'year') return `${y}` + if (type === 'month') return `${y}-${m}` + if (type === 'day') return `${y}-${m}-${d}` + } + + // Create a map to count posts per period + const mapData = this.fragment_cache('createArchiveObj', () => { + const map = new Map() + posts.forEach(post => { + const keyYear = getKey(post, 'year') + const keyMonth = getKey(post, 'month') + const keyDay = getKey(post, 'day') + + if (yearly) map.set(keyYear, (map.get(keyYear) || 0) + 1) + if (monthly) map.set(keyMonth, (map.get(keyMonth) || 0) + 1) + if (daily) map.set(keyDay, (map.get(keyDay) || 0) + 1) + }) + return map + }) + + // Determine the appropriate key to fetch based on current page context + let key + if (yearly && year) key = `${year}` + if (monthly && month) key = `${year}-${month}` + if (daily && day) key = `${year}-${month}-${day}` + + // Return the count for the current period or default to the total posts + return mapData.get(key) || posts.length +}) diff --git a/themes/butterfly/scripts/helpers/inject_head_js.js b/themes/butterfly/scripts/helpers/inject_head_js.js new file mode 100644 index 0000000..63723af --- /dev/null +++ b/themes/butterfly/scripts/helpers/inject_head_js.js @@ -0,0 +1,156 @@ +'use strict' + +hexo.extend.helper.register('inject_head_js', function () { + const { darkmode, aside, pjax } = this.theme + const start = darkmode.start || 6 + const end = darkmode.end || 18 + const { theme_color } = hexo.theme.config + const themeColorLight = theme_color && theme_color.enable ? theme_color.meta_theme_color_light : '#ffffff' + const themeColorDark = theme_color && theme_color.enable ? theme_color.meta_theme_color_dark : '#0d0d0d' + + const createCustomJs = () => ` + const saveToLocal = { + set: (key, value, ttl) => { + if (!ttl) return + const expiry = Date.now() + ttl * 86400000 + localStorage.setItem(key, JSON.stringify({ value, expiry })) + }, + get: key => { + const itemStr = localStorage.getItem(key) + if (!itemStr) return undefined + const { value, expiry } = JSON.parse(itemStr) + if (Date.now() > expiry) { + localStorage.removeItem(key) + return undefined + } + return value + } + } + + window.btf = { + saveToLocal, + getScript: (url, attr = {}) => new Promise((resolve, reject) => { + const script = document.createElement('script') + script.src = url + script.async = true + Object.entries(attr).forEach(([key, val]) => script.setAttribute(key, val)) + script.onload = script.onreadystatechange = () => { + if (!script.readyState || /loaded|complete/.test(script.readyState)) resolve() + } + script.onerror = reject + document.head.appendChild(script) + }), + getCSS: (url, id) => new Promise((resolve, reject) => { + const link = document.createElement('link') + link.rel = 'stylesheet' + link.href = url + if (id) link.id = id + link.onload = link.onreadystatechange = () => { + if (!link.readyState || /loaded|complete/.test(link.readyState)) resolve() + } + link.onerror = reject + document.head.appendChild(link) + }), + addGlobalFn: (key, fn, name = false, parent = window) => { + if (!${pjax.enable} && key.startsWith('pjax')) return + const globalFn = parent.globalFn || {} + globalFn[key] = globalFn[key] || {} + if (name && globalFn[key][name]) return + globalFn[key][name || Object.keys(globalFn[key]).length] = fn + parent.globalFn = globalFn + } + } + ` + + const createDarkmodeJs = () => { + if (!darkmode.enable) return '' + + let darkmodeJs = ` + const activateDarkMode = () => { + document.documentElement.setAttribute('data-theme', 'dark') + if (document.querySelector('meta[name="theme-color"]') !== null) { + document.querySelector('meta[name="theme-color"]').setAttribute('content', '${themeColorDark}') + } + } + const activateLightMode = () => { + document.documentElement.setAttribute('data-theme', 'light') + if (document.querySelector('meta[name="theme-color"]') !== null) { + document.querySelector('meta[name="theme-color"]').setAttribute('content', '${themeColorLight}') + } + } + + btf.activateDarkMode = activateDarkMode + btf.activateLightMode = activateLightMode + + const theme = saveToLocal.get('theme') + ` + + switch (darkmode.autoChangeMode) { + case 1: + darkmodeJs += ` + const mediaQueryDark = window.matchMedia('(prefers-color-scheme: dark)') + const mediaQueryLight = window.matchMedia('(prefers-color-scheme: light)') + + if (theme === undefined) { + if (mediaQueryLight.matches) activateLightMode() + else if (mediaQueryDark.matches) activateDarkMode() + else { + const hour = new Date().getHours() + const isNight = hour <= ${start} || hour >= ${end} + isNight ? activateDarkMode() : activateLightMode() + } + mediaQueryDark.addEventListener('change', () => { + if (saveToLocal.get('theme') === undefined) { + e.matches ? activateDarkMode() : activateLightMode() + } + }) + } else { + theme === 'light' ? activateLightMode() : activateDarkMode() + } + ` + break + case 2: + darkmodeJs += ` + const hour = new Date().getHours() + const isNight = hour <= ${start} || hour >= ${end} + if (theme === undefined) isNight ? activateDarkMode() : activateLightMode() + else theme === 'light' ? activateLightMode() : activateDarkMode() + ` + break + default: + darkmodeJs += ` + theme === 'dark' ? activateDarkMode() : theme === 'light' ? activateLightMode() : null + ` + } + + return darkmodeJs + } + + const createAsideStatusJs = () => { + if (!aside.enable || !aside.button) return '' + return ` + const asideStatus = saveToLocal.get('aside-status') + if (asideStatus !== undefined) { + document.documentElement.classList.toggle('hide-aside', asideStatus === 'hide') + } + ` + } + + const createDetectAppleJs = () => ` + const detectApple = () => { + if (/iPad|iPhone|iPod|Macintosh/.test(navigator.userAgent)) { + document.documentElement.classList.add('apple') + } + } + detectApple() + ` + + return `` +}) diff --git a/themes/butterfly/scripts/helpers/page.js b/themes/butterfly/scripts/helpers/page.js new file mode 100644 index 0000000..2bdcffd --- /dev/null +++ b/themes/butterfly/scripts/helpers/page.js @@ -0,0 +1,94 @@ +'use strict' + +const { stripHTML, prettyUrls, truncate } = require('hexo-util') +const crypto = require('crypto') + +hexo.extend.helper.register('truncate', (content, length) => { + return truncate(stripHTML(content), { length, separator: ' ' }).replace(/\n/g, ' ') +}) + +hexo.extend.helper.register('cloudTags', function (options = {}) { + const env = this + let { source, minfontsize, maxfontsize, limit, unit = 'px', orderby, order } = options + + if (limit > 0) { + source = source.limit(limit) + } + + const sizes = [...new Set(source.map(tag => tag.length).sort((a, b) => a - b))] + + const getRandomColor = () => { + const randomColor = () => Math.floor(Math.random() * 201) + const r = randomColor() + const g = randomColor() + const b = randomColor() + return `rgb(${Math.max(r, 50)}, ${Math.max(g, 50)}, ${Math.max(b, 50)})` + } + + const generateStyle = (size, unit) => + `font-size: ${parseFloat(size.toFixed(2)) + unit}; color: ${getRandomColor()};` + + const length = sizes.length - 1 + const result = source.sort(orderby, order).map(tag => { + const ratio = length ? sizes.indexOf(tag.length) / length : 0 + const size = minfontsize + ((maxfontsize - minfontsize) * ratio) + const style = generateStyle(size, unit) + return `${tag.name}` + }).join('') + + return result +}) + +hexo.extend.helper.register('urlNoIndex', function (url = null, trailingIndex = false, trailingHtml = false) { + return prettyUrls(url || this.url, { trailing_index: trailingIndex, trailing_html: trailingHtml }) +}) + +hexo.extend.helper.register('md5', function (path) { + return crypto.createHash('md5').update(decodeURI(this.url_for(path))).digest('hex') +}) + +hexo.extend.helper.register('injectHtml', data => { + return data ? data.join('') : '' +}) + +hexo.extend.helper.register('findArchivesTitle', function (page, menu, date) { + if (page.year) { + const dateStr = page.month ? `${page.year}-${page.month}` : `${page.year}` + const dateFormat = page.month ? hexo.theme.config.aside.card_archives.format : 'YYYY' + return date(dateStr, dateFormat) + } + + const defaultTitle = this._p('page.archives') + if (!menu) return defaultTitle + + const loop = (m) => { + for (const key in m) { + if (typeof m[key] === 'object') { + const result = loop(m[key]) + if (result) return result + } + + if (/\/archives\//.test(m[key])) { + return key + } + } + } + + return loop(menu) || defaultTitle +}) + +hexo.extend.helper.register('getBgPath', path => { + if (!path) return '' + + const absoluteUrlPattern = /^(?:[a-z][a-z\d+.-]*:)?\/\//i + const relativeUrlPattern = /^(\.\/|\.\.\/|\/|[^/]+\/).*$/ + const colorPattern = /^(#|rgb|rgba|hsl|hsla|linear-gradient|radial-gradient)/i + + if (colorPattern.test(path)) { + return `background-color: ${path};` + } else if (absoluteUrlPattern.test(path) || relativeUrlPattern.test(path)) { + return `background-image: url(${path});` + } else { + return `background: ${path};` + } +}) diff --git a/themes/butterfly/scripts/helpers/related_post.js b/themes/butterfly/scripts/helpers/related_post.js new file mode 100644 index 0000000..46f4381 --- /dev/null +++ b/themes/butterfly/scripts/helpers/related_post.js @@ -0,0 +1,85 @@ +/** + * Butterfly + * Related Posts + * According the tag + */ + +'use strict' + +hexo.extend.helper.register('related_posts', function (currentPost, allPosts) { + let relatedPosts = [] + const tagsData = currentPost.tags + tagsData.length && tagsData.forEach(function (tag) { + allPosts.forEach(function (post) { + if (currentPost.path !== post.path && isTagRelated(tag.name, post.tags)) { + const relatedPost = { + title: post.title, + path: post.path, + cover: post.cover, + cover_type: post.cover_type, + weight: 1, + updated: post.updated, + created: post.date + } + const index = findItem(relatedPosts, 'path', post.path) + if (index !== -1) { + relatedPosts[index].weight += 1 + } else { + relatedPosts.push(relatedPost) + } + } + }) + }) + + if (relatedPosts.length === 0) { + return '' + } + let result = '' + const hexoConfig = hexo.config + const config = hexo.theme.config + + const limitNum = config.related_post.limit || 6 + const dateType = config.related_post.date_type || 'created' + const headlineLang = this._p('post.recommend') + + relatedPosts = relatedPosts.sort(compare('weight')) + + if (relatedPosts.length > 0) { + result += '