From ac0d1944abc6b1c2a01500f90683e41e98e115e4 Mon Sep 17 00:00:00 2001 From: bisnsh Date: Tue, 12 Aug 2025 12:19:25 +0800 Subject: [PATCH] add theme --- themes/butterfly/_config.yml | 1121 +++++++++++++++++ themes/butterfly/languages/default.yml | 122 ++ themes/butterfly/languages/en.yml | 122 ++ themes/butterfly/languages/ja.yml | 122 ++ themes/butterfly/languages/ko.yml | 122 ++ themes/butterfly/languages/zh-CN.yml | 123 ++ themes/butterfly/languages/zh-HK.yml | 122 ++ themes/butterfly/languages/zh-TW.yml | 122 ++ themes/butterfly/layout/archive.pug | 8 + themes/butterfly/layout/category.pug | 12 + .../layout/includes/additional-js.pug | 61 + themes/butterfly/layout/includes/footer.pug | 39 + themes/butterfly/layout/includes/head.pug | 77 ++ .../layout/includes/head/Open_Graph.pug | 16 + .../layout/includes/head/analytics.pug | 45 + .../butterfly/layout/includes/head/config.pug | 126 ++ .../layout/includes/head/config_site.pug | 25 + .../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/head/structured_data.pug | 55 + .../layout/includes/header/index.pug | 52 + .../layout/includes/header/menu_item.pug | 27 + .../butterfly/layout/includes/header/nav.pug | 26 + .../layout/includes/header/post-info.pug | 149 +++ .../layout/includes/header/social.pug | 8 + themes/butterfly/layout/includes/layout.pug | 38 + .../includes/loading/fullpage-loading.pug | 42 + .../layout/includes/loading/index.pug | 5 + .../layout/includes/loading/pace.pug | 12 + .../layout/includes/mixins/article-sort.pug | 23 + .../layout/includes/mixins/indexPostUI.pug | 116 ++ 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 | 188 +++ .../butterfly/layout/includes/page/tags.pug | 2 + .../butterfly/layout/includes/pagination.pug | 38 + .../layout/includes/post/outdate-notice.pug | 8 + .../layout/includes/post/post-copyright.pug | 23 + .../layout/includes/post/post-summary.pug | 7 + .../butterfly/layout/includes/post/reward.pug | 12 + .../butterfly/layout/includes/rightmenu.pug | 75 ++ .../butterfly/layout/includes/rightside.pug | 54 + themes/butterfly/layout/includes/sidebar.pug | 18 + .../includes/third-party/abcjs/abcjs.pug | 46 + .../includes/third-party/abcjs/index.pug | 3 + .../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 | 39 + .../third-party/card-post-count/valine.pug | 20 + .../third-party/card-post-count/waline.pug | 21 + .../includes/third-party/chat/chatra.pug | 38 + .../includes/third-party/chat/crisp.pug | 32 + .../includes/third-party/chat/index.pug | 7 + .../includes/third-party/chat/tidio.pug | 45 + .../includes/third-party/comments/artalk.pug | 73 ++ .../includes/third-party/comments/disqus.pug | 80 ++ .../third-party/comments/disqusjs.pug | 87 ++ .../comments/facebook_comments.pug | 64 + .../includes/third-party/comments/giscus.pug | 82 ++ .../includes/third-party/comments/gitalk.pug | 64 + .../includes/third-party/comments/index.pug | 46 + .../includes/third-party/comments/js.pug | 26 + .../includes/third-party/comments/livere.pug | 47 + .../third-party/comments/remark42.pug | 78 ++ .../includes/third-party/comments/twikoo.pug | 64 + .../third-party/comments/utterances.pug | 63 + .../includes/third-party/comments/valine.pug | 60 + .../includes/third-party/comments/waline.pug | 61 + .../layout/includes/third-party/effect.pug | 35 + .../includes/third-party/math/chartjs.pug | 91 ++ .../includes/third-party/math/index.pug | 14 + .../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 | 67 + .../third-party/newest-comments/common.pug | 61 + .../newest-comments/disqus-comment.pug | 34 + .../newest-comments/github-issues.pug | 62 + .../third-party/newest-comments/index.pug | 34 + .../third-party/newest-comments/remark42.pug | 31 + .../newest-comments/twikoo-comment.pug | 45 + .../third-party/newest-comments/valine.pug | 51 + .../third-party/newest-comments/waline.pug | 32 + .../layout/includes/third-party/pjax.pug | 68 + .../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 | 113 ++ .../includes/third-party/umami_analytics.pug | 65 + .../layout/includes/widget/card_ad.pug | 3 + .../includes/widget/card_announcement.pug | 7 + .../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 | 14 + .../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 | 44 + .../layout/includes/widget/index.pug | 36 + themes/butterfly/layout/index.pug | 5 + themes/butterfly/layout/page.pug | 32 + themes/butterfly/layout/post.pug | 37 + themes/butterfly/layout/tag.pug | 12 + themes/butterfly/package.json | 34 + themes/butterfly/plugins.yml | 211 ++++ themes/butterfly/scripts/common/postDesc.js | 37 + themes/butterfly/scripts/events/404.js | 20 + themes/butterfly/scripts/events/cdn.js | 97 ++ themes/butterfly/scripts/events/comment.js | 24 + themes/butterfly/scripts/events/init.js | 20 + .../butterfly/scripts/events/merge_config.js | 593 +++++++++ themes/butterfly/scripts/events/stylus.js | 24 + themes/butterfly/scripts/events/welcome.js | 13 + .../scripts/filters/post_lazyload.js | 31 + .../butterfly/scripts/filters/random_cover.js | 82 ++ .../scripts/helpers/aside_archives.js | 126 ++ .../scripts/helpers/aside_categories.js | 81 ++ .../scripts/helpers/getArchiveLength.js | 45 + .../scripts/helpers/inject_head_js.js | 155 +++ themes/butterfly/scripts/helpers/page.js | 152 +++ .../butterfly/scripts/helpers/related_post.js | 97 ++ themes/butterfly/scripts/helpers/series.js | 22 + themes/butterfly/scripts/tag/button.js | 21 + themes/butterfly/scripts/tag/chartjs.js | 49 + themes/butterfly/scripts/tag/flink.js | 34 + themes/butterfly/scripts/tag/gallery.js | 76 ++ themes/butterfly/scripts/tag/hide.js | 52 + themes/butterfly/scripts/tag/inlineImg.js | 19 + themes/butterfly/scripts/tag/label.js | 14 + themes/butterfly/scripts/tag/link.js | 70 + themes/butterfly/scripts/tag/mermaid.js | 17 + themes/butterfly/scripts/tag/note.js | 27 + themes/butterfly/scripts/tag/score.js | 50 + themes/butterfly/scripts/tag/series.js | 63 + themes/butterfly/scripts/tag/tabs.js | 51 + themes/butterfly/scripts/tag/timeline.js | 50 + .../source/css/_global/function.styl | 280 ++++ .../butterfly/source/css/_global/index.styl | 228 ++++ .../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 ++ .../source/css/_layout/ai-summary.styl | 150 +++ .../butterfly/source/css/_layout/aside.styl | 424 +++++++ themes/butterfly/source/css/_layout/chat.styl | 9 + .../source/css/_layout/comments.styl | 81 ++ .../butterfly/source/css/_layout/footer.styl | 87 ++ themes/butterfly/source/css/_layout/head.styl | 463 +++++++ .../butterfly/source/css/_layout/loading.styl | 95 ++ .../source/css/_layout/pagination.styl | 106 ++ themes/butterfly/source/css/_layout/post.styl | 264 ++++ .../source/css/_layout/relatedposts.styl | 31 + .../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 | 187 +++ .../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 | 175 +++ .../butterfly/source/css/_page/rightmenu.styl | 78 ++ .../butterfly/source/css/_page/shuoshuo.styl | 78 ++ themes/butterfly/source/css/_page/tags.styl | 27 + .../butterfly/source/css/_search/algolia.styl | 93 ++ .../butterfly/source/css/_search/index.styl | 56 + .../source/css/_search/local-search.styl | 57 + 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/link.styl | 78 ++ 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/calendar.css | 222 ++++ themes/butterfly/source/css/font.css | 5 + themes/butterfly/source/css/gkai.woff2 | Bin 0 -> 1961576 bytes themes/butterfly/source/css/index.styl | 15 + themes/butterfly/source/css/rightmenu.css | 83 ++ themes/butterfly/source/css/shuoshuo.css | 292 +++++ themes/butterfly/source/css/style.css | 295 +++++ themes/butterfly/source/css/var.styl | 186 +++ themes/butterfly/source/css/welcome.css | 25 + 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/ai-summary.js | 65 + themes/butterfly/source/js/calendar.js | 111 ++ themes/butterfly/source/js/main.js | 930 ++++++++++++++ themes/butterfly/source/js/random.js | 13 + themes/butterfly/source/js/rightmenu.js | 332 +++++ themes/butterfly/source/js/search/algolia.js | 174 +++ .../source/js/search/local-search.js | 360 ++++++ themes/butterfly/source/js/shuoshuo.js | 415 ++++++ themes/butterfly/source/js/tw_cn.js | 117 ++ themes/butterfly/source/js/txmap.js | 668 ++++++++++ themes/butterfly/source/js/utils.js | 350 +++++ 227 files changed, 18962 insertions(+) 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/head/structured_data.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/outdate-notice.pug create mode 100644 themes/butterfly/layout/includes/post/post-copyright.pug create mode 100644 themes/butterfly/layout/includes/post/post-summary.pug create mode 100644 themes/butterfly/layout/includes/post/reward.pug create mode 100644 themes/butterfly/layout/includes/rightmenu.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/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/chartjs.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/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/common/postDesc.js 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/chartjs.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/link.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/ai-summary.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/rightmenu.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/link.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/calendar.css create mode 100644 themes/butterfly/source/css/font.css create mode 100644 themes/butterfly/source/css/gkai.woff2 create mode 100644 themes/butterfly/source/css/index.styl create mode 100644 themes/butterfly/source/css/rightmenu.css create mode 100644 themes/butterfly/source/css/shuoshuo.css create mode 100644 themes/butterfly/source/css/style.css create mode 100644 themes/butterfly/source/css/var.styl create mode 100644 themes/butterfly/source/css/welcome.css 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/ai-summary.js create mode 100644 themes/butterfly/source/js/calendar.js create mode 100644 themes/butterfly/source/js/main.js create mode 100644 themes/butterfly/source/js/random.js create mode 100644 themes/butterfly/source/js/rightmenu.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/shuoshuo.js create mode 100644 themes/butterfly/source/js/tw_cn.js create mode 100644 themes/butterfly/source/js/txmap.js create mode 100644 themes/butterfly/source/js/utils.js diff --git a/themes/butterfly/_config.yml b/themes/butterfly/_config.yml new file mode 100644 index 0000000..1fafd60 --- /dev/null +++ b/themes/butterfly/_config.yml @@ -0,0 +1,1121 @@ +# -------------------------------------- +# 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 + display_post_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 - https://api.aa1.cn/doc/yiyan.html + # 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: + nav: + owner: + enable: true + since: 2025 + # Copyright of theme and framework + copyright: + enable: true + version: true + custom_text: + +# -------------------------------------- +# 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: + +# Animation for the bottom right config button +rightside_config_animation: true + +# -------------------------------------- +# 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: en_US + +# 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/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: + +# 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: + +# https://www.googletagmanager.com/ +google_tag_manager: + tag_id: + # optional + domain: + +# -------------------------------------- +# 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 + +# chartjs +# see https://www.chartjs.org/docs/latest/ +chartjs: + enable: false + # Do not modify unless you understand how they work. + # The default settings are only used when the MD syntax is not specified. + # General font color for the chart + fontColor: + light: 'rgba(0, 0, 0, 0.8)' + dark: 'rgba(255, 255, 255, 0.8)' + # General border color for the chart + borderColor: + light: 'rgba(0, 0, 0, 0.1)' + dark: 'rgba(255, 255, 255, 0.2)' + # Background color for scale labels on radar and polar area charts + scale_ticks_backdropColor: + light: 'transparent' + dark: 'transparent' + +# 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 + +# Lazyload +# https://github.com/verlok/vanilla-lazyload +lazyload: + enable: false + # Use browser's native lazyload instead of vanilla-lazyload + native: 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: + +# Structured Data +# https://developers.google.com/search/docs/guides/intro-structured-data +structured_data: true + +# 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: + # chartjs: + # 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: + # 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..42da35d --- /dev/null +++ b/themes/butterfly/languages/default.yml @@ -0,0 +1,122 @@ +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 + back_to_home: Back to Home + +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..42da35d --- /dev/null +++ b/themes/butterfly/languages/en.yml @@ -0,0 +1,122 @@ +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 + back_to_home: Back to Home + +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..b533c2e --- /dev/null +++ b/themes/butterfly/languages/ja.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: 編集 + back_to_home: ホームに戻る + +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..a336644 --- /dev/null +++ b/themes/butterfly/languages/ko.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: 편집 + back_to_home: 홈으로 돌아가기 + +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..b06a242 --- /dev/null +++ b/themes/butterfly/languages/zh-CN.yml @@ -0,0 +1,123 @@ +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: 编辑 + back_to_home: 返回首页 + +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..442deff --- /dev/null +++ b/themes/butterfly/languages/zh-HK.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: 編輯 + back_to_home: 返回首頁 + +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..bf3d47c --- /dev/null +++ b/themes/butterfly/languages/zh-TW.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: 編輯 + back_to_home: 返回首頁 + +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..ebc8c99 --- /dev/null +++ b/themes/butterfly/layout/includes/additional-js.pug @@ -0,0 +1,61 @@ +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 && !theme.lazyload.native + script(src=url_for(theme.asset.lazyload)) + + if theme.snackbar.enable + script(src=url_for(theme.asset.snackbar)) + + .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 || (globalPageType !== '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 }) + + if theme.google_tag_manager && theme.google_tag_manager.tag_id + noscript + iframe(src=`${theme.google_tag_manager.domain ? theme.google_tag_manager.domain : 'https://www.googletagmanager.com'}/ns.html?id=${theme.google_tag_manager.tag_id}` height="0" width="0" style="display:none;visibility:hidden") \ 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..4102b42 --- /dev/null +++ b/themes/butterfly/layout/includes/footer.pug @@ -0,0 +1,39 @@ +- const { nav, owner, copyright, custom_text } = theme.footer + +if nav + .footer-flex + for block in nav + .footer-flex-items(style=`${ block.width ? 'flex-grow:' + block.width : '' }`) + for blockItem in block.content + .footer-flex-item + .footer-flex-title= blockItem.title + .footer-flex-content + for subitem in blockItem.item + if subitem.html + div!= subitem.html + else if subitem.url + a(href=url_for(subitem.url), target='_blank' title=subitem.title)= subitem.title + else if subitem.title + div!= subitem.title +.footer-other + .footer-copyright + if owner.enable + - const currentYear = new Date().getFullYear() + - const sinceYear = owner.since + span.copyright + if sinceYear && sinceYear != currentYear + != `© ${sinceYear} - ${currentYear} By ${config.author}` + else + != `© ${currentYear} By ${config.author}` + if copyright.enable + - const v = copyright.version ? getVersion() : false + span.framework-info + if owner.enable && nav + span.footer-separator | + span= _p('footer.framework') + ' ' + a(href='https://hexo.io')= `Hexo${ v ? ' ' + v.hexo : '' }` + span.footer-separator | + span= _p('footer.theme') + ' ' + a(href='https://github.com/jerryc127/hexo-theme-butterfly')= `Butterfly${ v ? ' ' + v.theme : '' }` + if theme.footer.custom_text + .footer_custom_text!= theme.footer.custom_text diff --git a/themes/butterfly/layout/includes/head.pug b/themes/butterfly/layout/includes/head.pug new file mode 100644 index 0000000..3e77573 --- /dev/null +++ b/themes/butterfly/layout/includes/head.pug @@ -0,0 +1,77 @@ +- var pageTitle +- globalPageType === 'archive' ? page.title = findArchivesTitle(page, theme.menu, date) : '' +case globalPageType + when 'tag' + - pageTitle = _p('page.tag') + ': ' + page.tag + when 'category' + - pageTitle = _p('page.category') + ': ' + page.category + when '404' + - pageTitle = _p('error404') + default + - pageTitle = page.title || config.title || '' + + +- var isSubtitle = config.subtitle ? ' - ' + config.subtitle : '' +- var tabTitle = globalPageType === '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 + +//- Structured Data +include ./head/structured_data.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..b3628d0 --- /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: globalPageType === '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..4ca6bf3 --- /dev/null +++ b/themes/butterfly/layout/includes/head/analytics.pug @@ -0,0 +1,45 @@ +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}"); + +if (theme.google_tag_manager && theme.google_tag_manager.tag_id) + script. + (function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start': + new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0], + j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src= + "!{theme.google_tag_manager.domain ? theme.google_tag_manager.domain : 'https://www.googletagmanager.com'}/gtm.js?id="+i+dl;f.parentNode.insertBefore(j,f); + })(window,document,'script','dataLayer','!{theme.google_tag_manager.tag_id}'); + btf.addGlobalFn('pjaxComplete', () => { + dataLayer.push({'event': 'pjaxComplete', 'page_title': document.title, 'page_location': location.href, 'page_path': window.location.pathname}) + }, 'google_tag_manager') \ 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..efa3654 --- /dev/null +++ b/themes/butterfly/layout/includes/head/config.pug @@ -0,0 +1,126 @@ +- + 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 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}, + 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}, + islazyloadPlugin: !{theme.lazyload.enable && !theme.lazyload.native}, + 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..5c1c67d --- /dev/null +++ b/themes/butterfly/layout/includes/head/config_site.pug @@ -0,0 +1,25 @@ +- + 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 (globalPageType === 'post' && theme.toc.post) tocEnable = true + else if (globalPageType === '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}', + isHighlightShrink: !{isHighlightShrink}, + isToc: !{showToc}, + pageType: '!{page.type == 'shuoshuo' ? 'shuoshuo' : globalPageType}' + } 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..cefe9fc --- /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/head/structured_data.pug b/themes/butterfly/layout/includes/head/structured_data.pug new file mode 100644 index 0000000..3fbb06f --- /dev/null +++ b/themes/butterfly/layout/includes/head/structured_data.pug @@ -0,0 +1,55 @@ +if theme.structured_data + if page.layout === 'post' + - + // https://developers.google.com/search/docs/appearance/structured-data/article + + const title = page.title + const url = page.permalink + const imageVal = page.cover_type === 'img' ? page.cover : theme.avatar.img + const image = imageVal ? full_url_for(imageVal) : '' + const datePublished = page.date.toISOString() + const dateModified = (page.updated || page.date).toISOString() + const author = page.copyright_author || config.author + const authorHrefVal = page.copyright_author_href || theme.post_copyright.author_href || config.url + const authorHref = full_url_for(authorHrefVal) + + const jsonLd = { + "@context": "https://schema.org", + "@type": "BlogPosting", + "headline": title, + "url": url, + "image": image, + "datePublished": datePublished, + "dateModified": dateModified, + "author": [{ + "@type": "Person", + "name": author, + "url": authorHref + }] + } + + jsonLdScript = JSON.stringify(jsonLd, null, 2) + - + + else if is_home() && (!page.current || page.current === 1) + - + // https://developers.google.com/search/docs/appearance/site-names#website + + const baseUrl = config.url; + const currentPath = url_for('/'); + const isRootOrSubdomain = currentPath.split('/').filter(Boolean).length === 0; + + if (isRootOrSubdomain) { + const jsonLd = { + "@context": "https://schema.org", + "@type": "WebSite", + "name": config.title, + "url": full_url_for('/'), + } + + jsonLdScript = JSON.stringify(jsonLd, null, 2) + } + - + + script(type="application/ld+json"). + !{jsonLdScript} diff --git a/themes/butterfly/layout/includes/header/index.pug b/themes/butterfly/layout/includes/header/index.pug new file mode 100644 index 0000000..481db70 --- /dev/null +++ b/themes/butterfly/layout/includes/header/index.pug @@ -0,0 +1,52 @@ +- + 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 + case globalPageType + when 'post' + - top_img = page.top_img || page.cover || theme.default_top_img + when 'page' + - top_img = page.top_img || theme.default_top_img + when 'tag' + - top_img = theme.tag_per_img && theme.tag_per_img[page.tag] || returnTopImg(theme.tag_img) + when 'category' + - top_img = theme.category_per_img && theme.category_per_img[page.category] || returnTopImg(theme.category_img) + when 'home' + - top_img = returnTopImg(theme.index_img) + when 'archive' + - top_img = returnTopImg(theme.archive_img) + default + - top_img = page.top_img || theme.default_top_img + + if top_img !== false + - bg_img = getBgPath(top_img) + - headerClassName = globalPageType === 'home' ? 'full_page' : globalPageType === 'post' ? 'post-bg' : 'not-home-page' + +header#page-header(class=`${headerClassName + isFixedClass}` style=bg_img) + include ./nav.pug + if top_img !== false + if globalPageType === 'post' + include ./post-info.pug + else if globalPageType === '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 + //- improve seo + if globalPageType !== '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..a6be8e2 --- /dev/null +++ b/themes/butterfly/layout/includes/header/nav.pug @@ -0,0 +1,26 @@ +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 globalPageType === 'post' && theme.nav.display_post_title + a.nav-page-title(href=url_for('/')) + span.site-name=(page.title || config.title) + span.site-name + i.fa-solid.fa-circle-arrow-left + span= ' ' + _p('post.back_to_home') + + #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..f346f83 --- /dev/null +++ b/themes/butterfly/layout/includes/header/post-info.pug @@ -0,0 +1,149 @@ +- 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 + + mixin otherPV() + 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 + + - const commentUse = comments.use && comments.use[0] + if page.comments !== false && commentUse && !comments.lazyload + if commentUse === 'Valine' && theme.valine.visitor + +pvBlock(url_for(page.path), 'leancloud_visitors', page.title) + span.leancloud-visitors-count + i.fa-solid.fa-spinner.fa-spin + else if commentUse === 'Waline' && theme.waline.pageview + +pvBlock('', '', '') + span.waline-pageview-count(data-path=url_for(page.path)) + i.fa-solid.fa-spinner.fa-spin + else if commentUse === 'Twikoo' && theme.twikoo.visitor + +pvBlock('', '', '') + span#twikoo_visitors + i.fa-solid.fa-spinner.fa-spin + else if commentUse === 'Artalk' && theme.artalk.visitor + +pvBlock('', '', '') + span#ArtalkPV + i.fa-solid.fa-spinner.fa-spin + else + +otherPV() + else + +otherPV() + + 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..8983dac --- /dev/null +++ b/themes/butterfly/layout/includes/layout.pug @@ -0,0 +1,38 @@ +- var globalPageType = getPageType(page, is_home) +- var htmlClassHideAside = theme.aside.enable && theme.aside.hide ? 'hide-aside' : '' +- page.aside = globalPageType === 'archive' ? theme.aside.display.archive: globalPageType === 'category' ? theme.aside.display.category : globalPageType === 'tag' ? theme.aside.display.tag : page.aside +- var hideAside = !theme.aside.enable || page.aside === false ? 'hide-aside' : '' +- var pageType = globalPageType === '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='background: transparent') + !=partial('includes/footer', {}, {cache: true}) + + include ./rightside.pug + include ./additional-js.pug + !=partial('includes/rightmenu',{}, {cache:true}) \ 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..2cea198 --- /dev/null +++ b/themes/butterfly/layout/includes/loading/fullpage-loading.pug @@ -0,0 +1,42 @@ +#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: () => { + if ($loadingBox.classList.contains('loaded')) return + $body.style.overflow = '' + $loadingBox.classList.add('loaded') + }, + initLoading: () => { + $body.style.overflow = 'hidden' + $loadingBox.classList.remove('loaded') + } + } + + preloader.initLoading() + + if (document.readyState === 'complete') { + preloader.endLoading() + } else { + window.addEventListener('load', preloader.endLoading) + document.addEventListener('DOMContentLoaded', preloader.endLoading) + // Add timeout protection: force end after 7 seconds + setTimeout(preloader.endLoading, 7000) + } + + 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..4f2c3ee --- /dev/null +++ b/themes/butterfly/layout/includes/mixins/indexPostUI.pug @@ -0,0 +1,116 @@ +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 globalPageType === '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 + - const content = postDesc(article) + if content + .content!=content + + 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..4a7d515 --- /dev/null +++ b/themes/butterfly/layout/includes/page/default-page.pug @@ -0,0 +1,2 @@ +#article-container.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..f94fffc --- /dev/null +++ b/themes/butterfly/layout/includes/page/flink.pug @@ -0,0 +1,82 @@ +#article-container.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..3bcb01d --- /dev/null +++ b/themes/butterfly/layout/includes/page/shuoshuo.pug @@ -0,0 +1,188 @@ +//- - author: +//- avatar: +//- date: +//- content: +//- tags: +//- - tag1 +//- - tag2 + +- page.toc = false + +#article-container + if page.comments !== false && theme.comments.use + - commentsJsLoad = true + + script. + (() => { + const commentDiv = `!{partial('includes/third-party/comments/index', {}, {cache: true})}` + + const runDestroy = (shuoshuoComment) => { + if (!shuoshuoComment) return + + for (const [key, fn] of Object.entries(shuoshuoComment)) { + if (key.startsWith('destroy')) fn() + } + } + + window.addCommentToShuoshuo = e => { + const btn = e.target.closest('.shuoshuo-comment-btn') + if (!btn) return + + const ele = btn.closest('.container').nextElementSibling + const { shuoshuoComment } = window + const isInclude = ele.classList.contains('no-comment') + runDestroy(shuoshuoComment) + if (isInclude) { + ele.classList.remove('no-comment') + ele.innerHTML = commentDiv + const key = `${location.pathname.replace(/\/$/, '')}?key=${ele.getAttribute('data-key')}` + btf.switchComments(ele, key) + shuoshuoComment.loadComment && shuoshuoComment.loadComment(ele, key) + } + } + })() + + if page.shuoshuo_url + script. + (() => { + const limitConfig = !{ JSON.stringify(page.limit || {}) } + + const sortDataByDate = data => data.sort((a, b) => new Date(b.date) - new Date(a.date)) + + const filterDataByLimit = (data, limit) => { + if (!limit || !limit.type) return data + if (limit.type === 'num') return data.slice(0, limit.value) + if (limit.type === 'date') { + const limitDate = new Date(limit.value) + return data.filter(item => new Date(item.date) >= limitDate) + } + return data + }; + + const formatToTimeZone = (date) => { + const fullDate = date.length === 10 ? `${date} 00:00:00` : date + const visitorTimeZone = '#{config.timezone}' || Intl.DateTimeFormat().resolvedOptions().timeZone + const options = { + timeZone: visitorTimeZone, + year: 'numeric', + month: '2-digit', + day: '2-digit', + hour: '2-digit', + minute: '2-digit', + second: '2-digit', + hour12: false + } + const [day, month, year, hour, minute, second] = new Intl.DateTimeFormat('en-GB', options) + .format(new Date(fullDate)) + .match(/\d+/g) + return `${year}-${month}-${day} ${hour}:${minute}:${second}` + } + + const loadShuoshuo = async () => { + try { + const response = await fetch('!{url_for(page.shuoshuo_url)}') + let data = await response.json() + + data = filterDataByLimit(sortDataByDate(data), limitConfig) + + const container = document.getElementById('article-container') + let start = 0 + + const renderData = (dataSlice) => { + const content = dataSlice.map(item => { + const formattedDate = formatToTimeZone(item.date) + const tags = item.tags && item.tags.map(tag => `${tag}`).join('') || '' + const commentButton = item.key && !{commentsJsLoad} + ? `
+ +
` + : '' + const commentContainer = item.key + ? `
` + : '' + + return ` +
+
+
+
+ +
+
+
${item.author || '!{config.author}'}
+ +
+
+
${item.content}
+ +
+ ${commentContainer} +
` + }).join('') + + container.insertAdjacentHTML('beforeend', content) + + window.lazyLoadInstance.update() + btf.loadLightbox(document.querySelectorAll('#article-container img:not(.no-lightbox)')) + } + + const handleIntersection = (entries) => { + if (!entries[0].isIntersecting) return + observer.unobserve(entries[0].target) + + const slice = data.slice(start, start + 10) + renderData(slice) + start += 10 + + if (start < data.length) { + setTimeout(() => observer.observe(container.lastElementChild), 100) + } else { + observer.disconnect() + } + }; + + const observer = new IntersectionObserver(handleIntersection, { + root: null, + rootMargin: '0px', + threshold: 1.0 + }) + + renderData(data.slice(start, 10)) + start += 10 + + if (container.lastElementChild) observer.observe(container.lastElementChild) + } catch (error) { + console.error(error) + } + }; + + window.pjax ? loadShuoshuo() : window.addEventListener('load', loadShuoshuo) + })() + else + if site.data.shuoshuo + each i in shuoshuoFN(site.data.shuoshuo, page) + .shuoshuo-item + .container + .shuoshuo-item-header + .shuoshuo-avatar + img.no-lightbox(src=i.avatar || url_for(theme.avatar.img)) + .shuoshuo-info + .shuoshuo-author=i.author || config.author + time.shuoshuo-date(title=i.date)=i.date + .shuoshuo-content + !=markdown(i.content) + .shuoshuo-footer(class=i.tags && i.tags.length ? 'flex-between' : 'flex-end') + if i.tags + .shuoshuo-tags + each tag in i.tags + span.shuoshuo-tag=tag + if i.key && commentsJsLoad + .shuoshuo-comment-btn(onclick='addCommentToShuoshuo(event)') + i.fa-solid.fa-comments + if i.key && commentsJsLoad + .shuoshuo-comment.no-comment(data-key=i.key) \ 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..b5b62cd --- /dev/null +++ b/themes/butterfly/layout/includes/page/tags.pug @@ -0,0 +1,2 @@ +.tag-cloud-list.text-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..5e597d1 --- /dev/null +++ b/themes/butterfly/layout/includes/pagination.pug @@ -0,0 +1,38 @@ +if page.total !== 1 + - + var options = { + prev_text: '', + next_text: '', + mid_size: 1, + escape: false + } + + if globalPageType === 'post' + - let paginationOrder = theme.post_pagination === 2 ? { prev: page.prev, next: page.next } : { prev: page.next, next: page.prev } + + nav#pagination.pagination-post + each direction, key in paginationOrder + if direction + - const getPostDesc = direction.postDesc || postDesc(direction) + - let className = key === 'prev' ? (paginationOrder.next ? '' : 'full-width') : (paginationOrder.prev ? '' : 'full-width') + - className = getPostDesc ? className : className + ' no-desc' + + a.pagination-related(class=className href=url_for(direction.path) title=direction.title) + if direction.cover_type === 'img' + img.cover(src=url_for(direction.cover) onerror=`onerror=null;src='${url_for(theme.error_img.post_page)}'` alt=`cover of ${key === 'prev' ? 'previous' : 'next'} post`) + else + .cover(style=`background: ${direction.cover || 'var(--default-bg-color)'}`) + + .info(class=key === 'prev' ? '' : 'text-right') + .info-1 + .info-item-1=_p(`pagination.${key}`) + .info-item-2!=direction.title + if getPostDesc + .info-2 + .info-item-1!=getPostDesc + else + nav#pagination + .pagination + if globalPageType === 'home' + - options.format = 'page/%d/#content-inner' + !=paginator(options) \ No newline at end of file diff --git a/themes/butterfly/layout/includes/post/outdate-notice.pug b/themes/butterfly/layout/includes/post/outdate-notice.pug new file mode 100644 index 0000000..ef72772 --- /dev/null +++ b/themes/butterfly/layout/includes/post/outdate-notice.pug @@ -0,0 +1,8 @@ +- const { limit_day, message_prev, message_next, position} = theme.noticeOutdate +- const notice_data = { limitDay: limit_day, messagePrev: message_prev, messageNext: message_next, postUpdate: full_date(page.updated)} +if position === 'top' + #post-outdate-notice(data=notice_data hidden) + !=page.content +else + !=page.content + #post-outdate-notice(data=notice_data hidden) \ 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/post-summary.pug b/themes/butterfly/layout/includes/post/post-summary.pug new file mode 100644 index 0000000..209fa4f --- /dev/null +++ b/themes/butterfly/layout/includes/post/post-summary.pug @@ -0,0 +1,7 @@ +.ai-summary + .ai-explanation(style="display: block;" data-summary=page.summary)=theme.ai_summary.loadingText + .ai-title + .ai-title-left + i.fa-brands.fa-slack + .ai-title-text=theme.ai_summary.title + .ai-tag#ai-tag= theme.ai_summary.modelName 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/rightmenu.pug b/themes/butterfly/layout/includes/rightmenu.pug new file mode 100644 index 0000000..229808b --- /dev/null +++ b/themes/butterfly/layout/includes/rightmenu.pug @@ -0,0 +1,75 @@ +#rightMenu.js-pjax + .rightMenu-group.rightMenu-small + a.rightMenu-item(href="javascript:window.history.back();") + i.fa.fa-arrow-left + a.rightMenu-item(href="javascript:window.history.forward();") + i.fa.fa-arrow-right + a.rightMenu-item(href="javascript:window.location.reload();") + i.fa.fa-refresh + a.rightMenu-item(href="javascript:window.scrollTo(0, 0);") + i.fa.fa-arrow-up + .rightMenu-group.rightMenu-line.hide#menu-text + a.rightMenu-item(href="javascript:rmf.copySelect();") + i.fa.fa-copy + span='复制' + a.rightMenu-item(href="javascript:rmf.searchinThisPage();") + i.fas.fa-search + span='站内搜索' + .rightMenu-group.rightMenu-line.hide#menu-too + a.rightMenu-item(href="javascript:window.open(window.getSelection().toString());window.location.reload();") + i.fa.fa-link + span='转到链接' + .rightMenu-group.rightMenu-line.hide#menu-paste + a.rightMenu-item(href='javascript:rmf.paste()') + i.fa.fa-copy + span='粘贴' + .rightMenu-group.rightMenu-line.hide#menu-post + a.rightMenu-item(href="javascript:rmf.copyWordsLink()") + i.fa.fa-link + span='复制本文地址' + .rightMenu-group.rightMenu-line.hide#menu-to + a.rightMenu-item(href="javascript:rmf.openWithNewTab()") + i.fa.fa-window-restore + span='新窗口打开' + a.rightMenu-item(href="javascript:rmf.open()") + i.fa.fa-link + span='转到链接' + a.rightMenu-item(href="javascript:rmf.copyLink()") + i.fa.fa-copy + span='复制链接' + .rightMenu-group.rightMenu-line.hide#menu-img + a.rightMenu-item(href="javascript:rmf.saveAs()") + i.fa.fa-download + span='保存图片' + a.rightMenu-item(href="javascript:rmf.openWithNewTab()") + i.fa.fa-window-restore + span='在新窗口打开' + a.rightMenu-item(href="javascript:rmf.click()") + i.fa.fa-arrows-alt + span='全屏显示' + a.rightMenu-item(href="javascript:rmf.copyLink()") + i.fa.fa-copy + span='复制图片链接' + .rightMenu-group.rightMenu-line + a.rightMenu-item(href="javascript:randomPost()") + i.fa.fa-paper-plane + span='随便逛逛' + a.rightMenu-item(href="javascript:rmf.switchDarkMode();") + i.fa.fa-moon + span='昼夜切换' + a.rightMenu-item(href="javascript:rmf.translate();") + i.iconfont.icon-fanti + span='繁简转换' + if is_post()||is_page() + a.rightMenu-item(href="javascript:rmf.switchReadMode();") + i.fa.fa-book + span='阅读模式' + a.rightMenu-item(href="javascript:pjax.loadUrl(\"/privacy/\");") + i.fa.fa-info-circle + span='隐私声明' + a.rightMenu-item(href="javascript:pjax.loadUrl(\"/cookie/\");") + i.fa.fa-info-circle + span='Cookie协议' + a.rightMenu-item(href="javascript:pjax.loadUrl(\"/cc/\");") + i.fa.fa-info-circle + span='版权声明' \ 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..fd7182c --- /dev/null +++ b/themes/butterfly/layout/includes/rightside.pug @@ -0,0 +1,54 @@ +- const { readmode, translate, darkmode, aside, chat } = theme + +mixin rightsideItem(array) + each item in array + case item + when 'readmode' + if globalPageType === '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") style="display:none") + i.fas.fa-message + when 'comment' + if commentsJsLoad + a#to_comment(href="#post-comment" title=_p("rightside.scroll_to_comment")) + i.fas.fa-comments + +- 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'] +- const needCogBtn = (enable && hide) || (!enable && ((globalPageType === 'post' && (readmode || translate.enable || (darkmode.enable && darkmode.button))) || (translate.enable || (darkmode.enable && darkmode.button)))) + +#rightside + #rightside-config-hide + if hideArray.length + +rightsideItem(hideArray) + + #rightside-config-show + if needCogBtn + button#rightside-config(type="button" title=_p("rightside.setting")) + i.fas.fa-cog(class=theme.rightside_config_animation ? 'fa-spin' : '') + + if showArray.length + +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..178c420 --- /dev/null +++ b/themes/butterfly/layout/includes/sidebar.pug @@ -0,0 +1,18 @@ +if theme.menu + #sidebar + #menu-mask + #sidebar-menus + .avatar-img.text-center + img(src=url_for(theme.avatar.img) onerror=`this.onerror=null;this.src='${url_for(theme.error_img.flink)}'` alt="avatar") + .site-data.text-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 }) \ No newline at end of file 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..51a5f20 --- /dev/null +++ b/themes/butterfly/layout/includes/third-party/abcjs/abcjs.pug @@ -0,0 +1,46 @@ +script. + (() => { + const abcjsInit = () => { + const abcjsFn = () => { + setTimeout(() => { + const sheets = document.querySelectorAll(".abc-music-sheet") + for (let i = 0; i < sheets.length; i++) { + const ele = sheets[i] + if (ele.children.length > 0) continue + + // Parse parameters from data-params attribute + let params = {} + const dp = ele.getAttribute("data-params") + if (dp) { + try { + params = JSON.parse(dp) + } catch (e) { + console.error("Failed to parse data-params:", e) + } + } + + // Merge parsed parameters with the responsive option + // Ensures params content appears before responsive + const options = { ...params, responsive: "resize" } + + // Render the music score using ABCJS.renderAbc + ABCJS.renderAbc(ele, ele.innerHTML, options) + } + }, 100) + } + + if (typeof ABCJS === "object") { + abcjsFn() + } else { + btf.getScript("!{url_for(theme.asset.abcjs_basic_js)}").then(abcjsFn) + } + } + + if (window.pjax) { + abcjsInit() + } else { + window.addEventListener("load", abcjsInit) + } + + btf.addGlobalFn("encrypt", abcjsInit, "abcjs") + })() 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..d187d55 --- /dev/null +++ b/themes/butterfly/layout/includes/third-party/abcjs/index.pug @@ -0,0 +1,3 @@ +if theme.abcjs.enable + if theme.abcjs.per_page && (['post','page'].includes(globalPageType)) || 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..8c58e1c --- /dev/null +++ b/themes/butterfly/layout/includes/third-party/card-post-count/twikoo.pug @@ -0,0 +1,39 @@ +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) => { + if (res[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..0fad991 --- /dev/null +++ b/themes/butterfly/layout/includes/third-party/chat/chatra.pug @@ -0,0 +1,38 @@ +//- https://chatra.io/help/api/ +script. + (() => { + window.ChatraID = '#{theme.chatra.id}' + window.Chatra = window.Chatra || function() { + (window.Chatra.q = window.Chatra.q || []).push(arguments) + } + + btf.getScript('https://call.chatra.io/chatra.js').then(() => { + 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() + + document.getElementById('chat-btn').style.display = 'block' + } else if (isChatHideShow) { + window.chatBtn = { + hide: () => Chatra('hide'), + show: () => Chatra('show') + } + } + }) + })() + + 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..08a1916 --- /dev/null +++ b/themes/butterfly/layout/includes/third-party/chat/crisp.pug @@ -0,0 +1,32 @@ +script. + (() => { + window.$crisp = ['safe', true] + window.CRISP_WEBSITE_ID = "!{theme.crisp.website_id}" + + btf.getScript('https://client.crisp.chat/l.js').then(() => { + 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() + + document.getElementById('chat-btn').style.display = 'block' + } 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/index.pug b/themes/butterfly/layout/includes/third-party/chat/index.pug new file mode 100644 index 0000000..273d040 --- /dev/null +++ b/themes/butterfly/layout/includes/third-party/chat/index.pug @@ -0,0 +1,7 @@ +case theme.chat.use + when 'chatra' + include ./chatra.pug + when 'tidio' + include ./tidio.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..f9d40ea --- /dev/null +++ b/themes/butterfly/layout/includes/third-party/chat/tidio.pug @@ -0,0 +1,45 @@ +script. + (() => { + btf.getScript('//code.tidio.co/!{theme.tidio.public_key}.js').then(() => { + 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() + } + + document.getElementById('chat-btn').style.display = 'block' + + } 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..0113fc3 --- /dev/null +++ b/themes/butterfly/layout/includes/third-party/comments/artalk.pug @@ -0,0 +1,73 @@ +- const { server, site, option } = theme.artalk +- const { use, lazyload } = theme.comments + +script. + (() => { + let artalkItem = null + const option = !{JSON.stringify(option)} + const isShuoshuo = GLOBAL_CONFIG_SITE.pageType === 'shuoshuo' + + const destroyArtalk = () => { + if (artalkItem) { + artalkItem.destroy() + artalkItem = null + } + } + + const artalkChangeMode = theme => artalkItem && artalkItem.setDarkMode(theme === 'dark') + + const initArtalk = (el = document, pageKey = location.pathname) => { + artalkItem = Artalk.init({ + el: el.querySelector('#artalk-wrap'), + server: '!{server}', + site: '!{site}', + darkMode: document.documentElement.getAttribute('data-theme') === 'dark', + ...option, + pageKey: isShuoshuo ? pageKey : (option && option.pageKey) || pageKey + }) + + 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])')) + }) + }) + + if (isShuoshuo) { + window.shuoshuoComment.destroyArtalk = () => { + destroyArtalk() + if (el.children.length) { + el.innerHTML = '' + el.classList.add('no-comment') + } + } + } + + btf.addGlobalFn('pjaxSendOnce', destroyArtalk, 'destroyArtalk') + btf.addGlobalFn('themeChange', artalkChangeMode, 'artalk') + } + + const loadArtalk = async (el, pageKey) => { + if (typeof Artalk === 'object') initArtalk(el, pageKey) + else { + await btf.getCSS('!{theme.asset.artalk_css}') + await btf.getScript('!{theme.asset.artalk_js}') + initArtalk(el, pageKey) + } + } + + if (isShuoshuo) { + '!{use[0]}' === 'Artalk' + ? window.shuoshuoComment = { loadComment: loadArtalk } + : window.loadOtherComment = loadArtalk + return + } + + 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..eeca5c4 --- /dev/null +++ b/themes/butterfly/layout/includes/third-party/comments/disqus.pug @@ -0,0 +1,80 @@ +- const disqusPageTitle = page.title.replace(/'/ig,"\\'") +- const { shortname, apikey } = theme.disqus +- const { use, lazyload, count } = theme.comments + +script. + (() => { + const isShuoshuo = GLOBAL_CONFIG_SITE.pageType === 'shuoshuo' + + const disqusReset = conf => { + window.DISQUS && window.DISQUS.reset({ + reload: true, + config: conf + }) + } + + const loadDisqus = (el, path) => { + if (isShuoshuo) { + window.shuoshuoComment.destroyDisqus = () => { + if (el.children.length) { + el.innerHTML = '' + el.classList.add('no-comment') + } + } + } + + window.disqus_identifier = isShuoshuo ? path : '!{ url_for(page.path) }' + window.disqus_url = isShuoshuo ? location.origin + path : '!{ page.permalink }' + + const disqus_config = function () { + this.page.url = disqus_url + this.page.identifier = disqus_identifier + this.page.title = '!{ disqusPageTitle }' + } + + if (window.DISQUS) disqusReset(disqus_config) + else { + const script = document.createElement('script') + script.src = 'https://!{shortname}.disqus.com/embed.js' + script.setAttribute('data-timestamp', +new Date()) + document.head.appendChild(script) + } + + btf.addGlobalFn('themeChange', () => disqusReset(disqus_config), 'disqus') + } + + 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 (isShuoshuo) { + '!{use[0]}' === 'Disqus' + ? window.shuoshuoComment = { loadComment: loadDisqus } + : window.loadOtherComment = loadDisqus + return + } + + if ('!{use[0]}' === 'Disqus' || !!{lazyload}) { + if (!{lazyload}) btf.loadComment(document.getElementById('disqus_thread'), loadDisqus) + else { + loadDisqus() + !{ count ? `GLOBAL_CONFIG_SITE.pageType === 'post' && 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..ae7ff06 --- /dev/null +++ b/themes/butterfly/layout/includes/third-party/comments/disqusjs.pug @@ -0,0 +1,87 @@ +- let disqusjsPageTitle = page.title && page.title.replace(/'/ig,"\\'") +- const { shortname:dqShortname, apikey:dqApikey, option:dqOption } = theme.disqusjs + +script. + (() => { + const isShuoshuo = GLOBAL_CONFIG_SITE.pageType === 'shuoshuo'== 'shuoshuo' + const dqOption = !{JSON.stringify(dqOption)} + + const destroyDisqusjs = () => { + disqusjs.destroy() + window.disqusjs = null + } + + const themeChange = (el, path) => { + destroyDisqusjs() + initDisqusjs(el, path) + } + + const initDisqusjs = (el = document, path) => { + if (isShuoshuo) { + window.shuoshuoComment.destroyDisqusjs = () => { + destroyDisqusjs() + if (el.children.length) { + el.innerHTML = '' + el.classList.add('no-comment') + } + } + } + + disqusjs = new DisqusJS({ + shortname: '!{dqShortname}', + title: '!{ disqusjsPageTitle }', + apikey: '!{dqApikey}', + ...dqOption, + identifier: isShuoshuo ? path : (dqOption && dqOption.identifier) || '!{ url_for(page.path) }', + url: isShuoshuo ? location.origin + path : (dqOption && dqOption.url) || '!{ page.permalink }' + }) + + disqusjs.render(el.querySelector('#disqusjs-wrap')) + + btf.addGlobalFn('themeChange', () => themeChange(el, path), 'disqusjs') + } + + const loadDisqusjs = async(el, path) => { + if (window.disqusJsLoad) initDisqusjs(el, path) + else { + await btf.getCSS('!{url_for(theme.asset.disqusjs_css)}') + await btf.getScript('!{url_for(theme.asset.disqusjs)}') + initDisqusjs(el, path) + 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 (isShuoshuo) { + '!{theme.comments.use[0]}' === 'Disqusjs' + ? window.shuoshuoComment = { loadComment: loadDisqusjs } + : window.loadOtherComment = loadDisqusjs + return + } + + 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.pageType === 'post' && 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..c62fa30 --- /dev/null +++ b/themes/butterfly/layout/includes/third-party/comments/facebook_comments.pug @@ -0,0 +1,64 @@ +- const fbSDKVer = 'v20.0' +- const fbSDK = `https://connect.facebook.net/${theme.facebook_comments.lang}/sdk.js#xfbml=1&version=${fbSDKVer}` + +script. + (()=>{ + const isShuoshuo = GLOBAL_CONFIG_SITE.pageType === 'shuoshuo'== 'shuoshuo' + + const loadFBComment = (el = document, path) => { + if (isShuoshuo) { + window.shuoshuoComment.destroyFB = () => { + if (el.children.length) { + el.innerHTML = '' + el.classList.add('no-comment') + } + } + } + + document.getElementById('fb-root') ? '' : document.body.insertAdjacentHTML('afterend', '
') + + const themeNow = document.documentElement.getAttribute('data-theme') === 'dark' ? 'dark' : 'light' + const $fbComment = el.getElementsByClassName('fb-comments')[0] + $fbComment.setAttribute('data-colorscheme',themeNow) + $fbComment.setAttribute('data-href', isShuoshuo ? '!{urlNoIndex(page.permalink)}' + '#' + path : '!{urlNoIndex(page.permalink)}') + + if (typeof FB === 'object') { + FB.XFBML.parse(document.getElementsByClassName('post-meta-commentcount')[0]) + FB.XFBML.parse(el.querySelector('#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 (isShuoshuo) { + '!{theme.comments.use[0]}' === 'Facebook Comments' + ? window.shuoshuoComment = { loadComment: loadFBComment } + : window.loadOtherComment = loadFBComment + return + } + + 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..c370e98 --- /dev/null +++ b/themes/butterfly/layout/includes/third-party/comments/giscus.pug @@ -0,0 +1,82 @@ +- 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 isShuoshuo = GLOBAL_CONFIG_SITE.pageType === 'shuoshuo' + const option = !{JSON.stringify(option)} + + const getGiscusTheme = theme => theme === 'dark' ? '!{dark_theme}' : '!{light_theme}' + + const createScriptElement = config => { + const ele = document.createElement('script') + Object.entries(config).forEach(([key, value]) => { + ele.setAttribute(key, value) + }) + return ele + } + + const loadGiscus = (el = document, key) => { + const mappingConfig = isShuoshuo + ? { 'data-mapping': 'specific', 'data-term': key } + : { 'data-mapping': (option && option['data-mapping']) || 'pathname' } + + const giscusConfig = { + src: '!{giscusUrl}', + 'data-repo': '!{repo}', + 'data-repo-id': '!{repo_id}', + 'data-category-id': '!{category_id}', + 'data-theme': getGiscusTheme(document.documentElement.getAttribute('data-theme')), + 'data-reactions-enabled': '1', + crossorigin: 'anonymous', + async: true, + ...option, + ...mappingConfig + } + + const scriptElement = createScriptElement(giscusConfig) + + el.querySelector('#giscus-wrap').appendChild(scriptElement) + + if (isShuoshuo) { + window.shuoshuoComment.destroyGiscus = () => { + if (el.children.length) { + el.innerHTML = '' + el.classList.add('no-comment') + } + } + } + } + + 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 (isShuoshuo) { + '!{use[0]}' === 'Giscus' + ? window.shuoshuoComment = { loadComment: loadGiscus } + : window.loadOtherComment = loadGiscus + return + } + + 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..d13bcb3 --- /dev/null +++ b/themes/butterfly/layout/includes/third-party/comments/gitalk.pug @@ -0,0 +1,64 @@ +- const { client_id, client_secret, repo, owner, admin, option } = theme.gitalk + +script. + (() => { + const isShuoshuo = GLOBAL_CONFIG_SITE.pageType === 'shuoshuo' + const option = !{JSON.stringify(option)} + + const commentCount = n => { + const isCommentCount = document.querySelector('#post-meta .gitalk-comment-count') + if (isCommentCount) { + isCommentCount.textContent= n + } + } + + const initGitalk = (el, path) => { + if (isShuoshuo) { + window.shuoshuoComment.destroyGitalk = () => { + if (el.children.length) { + el.innerHTML = '' + el.classList.add('no-comment') + } + } + } + + const gitalk = new Gitalk({ + clientID: '!{client_id}', + clientSecret: '!{client_secret}', + repo: '!{repo}', + owner: '!{owner}', + admin: ['!{admin}'], + updateCountCallback: commentCount, + ...option, + id: isShuoshuo ? path : (option && option.id) || '!{md5(page.path)}' + }) + + gitalk.render('gitalk-container') + } + + const loadGitalk = async(el, path) => { + if (typeof Gitalk === 'function') initGitalk(el, path) + else { + await btf.getCSS('!{url_for(theme.asset.gitalk_css)}') + await btf.getScript('!{url_for(theme.asset.gitalk)}') + initGitalk(el, path) + } + } + + if (isShuoshuo) { + '!{theme.comments.use[0]}' === 'Gitalk' + ? window.shuoshuoComment = { loadComment: loadGitalk } + : window.loadOtherComment = loadGitalk + return + } + + 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..09f06f7 --- /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..eab9904 --- /dev/null +++ b/themes/butterfly/layout/includes/third-party/comments/livere.pug @@ -0,0 +1,47 @@ +- const { use, lazyload } = theme.comments + +script. + (() => { + const isShuoshuo = GLOBAL_CONFIG_SITE.pageType === 'shuoshuo' + + const loadLivere = (el, path) => { + window.livereOptions = { + refer: path || location.pathname + } + + if (isShuoshuo) { + window.shuoshuoComment.destroyLivere = () => { + if (el.children.length) { + el.innerHTML = '' + el.classList.add('no-comment') + } + } + } + + 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 (isShuoshuo) { + '!{use[0]}' === 'Livere' + ? window.shuoshuoComment = { loadComment: loadLivere } + : window.loadOtherComment = loadLivere + return + } + + 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..cf648fe --- /dev/null +++ b/themes/butterfly/layout/includes/third-party/comments/remark42.pug @@ -0,0 +1,78 @@ +- const { host, siteId, option } = theme.remark42 + +script. + (() => { + const isShuoshuo = GLOBAL_CONFIG_SITE.pageType === 'shuoshuo' + const options = !{JSON.stringify(option)} + + const loadScript = src => { + const script = document.createElement('script') + script.src = src + script.defer = true + document.head.appendChild(script) + } + + const addRemark42 = () => loadScript('!{host}/web/embed.js') + + const getCount = () => document.querySelector('.remark42__counter') && loadScript('!{host}/web/count.js') + + const destroyRemark42 = () => window.remark42Instance && window.remark42Instance.destroy() + + const initRemark42 = remark_config => { + if (window.REMARK42) { + destroyRemark42() + window.remark42Instance = window.REMARK42.createInstance({ + ...remark_config + }) + } + } + + const loadRemark42 = (el, path) => { + if (isShuoshuo) { + window.shuoshuoComment.destroyRemark42 = () => { + destroyRemark42() + if (el.children.length) { + el.innerHTML = '' + el.classList.add('no-comment') + } + } + } + + window.remark_config = { + host: '!{host}', + site_id: '!{siteId}', + theme: document.documentElement.getAttribute('data-theme') === 'dark' ? 'dark' : 'light', + ...options, + url: isShuoshuo ? window.location.origin + path : (options && options.url) || window.location.origin + window.location.pathname + } + + if (window.REMARK42) { + initRemark42(remark_config) + getCount() + } else { + addRemark42() + window.addEventListener('REMARK42::ready', () => { + initRemark42(remark_config) + getCount() + }) + } + } + + const remarkChangeMode = theme => window.REMARK42 && window.REMARK42.changeTheme(theme) + + btf.addGlobalFn('themeChange', remarkChangeMode, 'remark42') + + if (isShuoshuo) { + '!{theme.comments.use[0]}' === 'Remark42' + ? window.shuoshuoComment = { loadComment: loadRemark42 } + : window.loadOtherComment = loadRemark42 + return + } + + if ('!{theme.comments.use[0]}' === 'Remark42' || !!{theme.comments.lazyload}) { + if (!{theme.comments.lazyload}) btf.loadComment(document.getElementById('remark42'), loadRemark42) + else loadRemark42() + } else { + window.loadOtherComment = loadRemark42 + } + })() \ No newline at end of file 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..31fec77 --- /dev/null +++ b/themes/butterfly/layout/includes/third-party/comments/twikoo.pug @@ -0,0 +1,64 @@ +- const { envId, region, option } = theme.twikoo +- const { use, lazyload, count } = theme.comments + +script. + (() => { + const isShuoshuo = GLOBAL_CONFIG_SITE.pageType === 'shuoshuo' + const option = !{JSON.stringify(option)} + + 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 = (el = document, path = location.pathname) => { + twikoo.init({ + el: el.querySelector('#twikoo-wrap'), + envId: '!{envId}', + region: '!{region}', + onCommentLoaded: () => { + btf.loadLightbox(document.querySelectorAll('#twikoo .tk-content img:not(.tk-owo-emotion)')) + }, + ...option, + path: isShuoshuo ? path : (option && option.path) || path + }) + + !{count ? `GLOBAL_CONFIG_SITE.pageType === 'post' && getCount()` : ''} + + isShuoshuo && (window.shuoshuoComment.destroyTwikoo = () => { + if (el.children.length) { + el.innerHTML = '' + el.classList.add('no-comment') + } + }) + } + + const loadTwikoo = (el, path) => { + if (typeof twikoo === 'object') setTimeout(() => init(el, path), 0) + else btf.getScript('!{url_for(theme.asset.twikoo)}').then(() => init(el, path)) + } + + if (isShuoshuo) { + '!{use[0]}' === 'Twikoo' + ? window.shuoshuoComment = { loadComment: loadTwikoo } + : window.loadOtherComment = loadTwikoo + return + } + + 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..4836557 --- /dev/null +++ b/themes/butterfly/layout/includes/third-party/comments/utterances.pug @@ -0,0 +1,63 @@ +- 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 isShuoshuo = GLOBAL_CONFIG_SITE.pageType === 'shuoshuo' + const option = !{JSON.stringify(option)} + const getUtterancesTheme = theme => theme === 'dark' ? '#{dark_theme}' : '#{light_theme}' + + const loadUtterances = (el = document, key) => { + if (isShuoshuo) { + window.shuoshuoComment.destroyUtterances = () => { + if (el.children.length) { + el.innerHTML = '' + el.classList.add('no-comment') + } + } + } + + const config = { + src: '!{utterancesUrl}', + repo: '!{repo}', + theme: getUtterancesTheme(document.documentElement.getAttribute('data-theme')), + crossorigin: 'anonymous', + async: true, + ...option, + 'issue-term': isShuoshuo ? key : (option && option['issue-term']) || '!{issue_term}' + } + + const ele = document.createElement('script') + Object.entries(config).forEach(([key, value]) => ele.setAttribute(key, value)) + el.querySelector('#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 (isShuoshuo) { + '!{use[0]}' === 'Utterances' + ? window.shuoshuoComment = { loadComment: loadUtterances } + : window.loadOtherComment = loadUtterances + return + } + + 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..9ac2c43 --- /dev/null +++ b/themes/butterfly/layout/includes/third-party/comments/valine.pug @@ -0,0 +1,60 @@ +- 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 isShuoshuo = GLOBAL_CONFIG_SITE.pageType === 'shuoshuo' + const option = !{JSON.stringify(option)} + + const initValine = (el, path) => { + if (isShuoshuo) { + window.shuoshuoComment.destroyValine = () => { + if (el.children.length) { + el.innerHTML = '' + el.classList.add('no-comment') + } + } + } + + const valineConfig = { + el: '#vcomment', + appId: '#{appId}', + appKey: '#{appKey}', + avatar: '#{avatar}', + serverURLs: '#{serverURLs}', + emojiMaps: !{emojiMaps}, + visitor: #{visitor}, + ...option, + path: isShuoshuo ? path : (option && option.path) || window.location.pathname + } + + new Valine(valineConfig) + } + + const loadValine = async (el, path) => { + if (typeof Valine === 'function') { + initValine(el, path) + } else { + await btf.getScript('!{url_for(theme.asset.valine)}') + initValine(el, path) + } + } + + if (isShuoshuo) { + '!{use[0]}' === 'Valine' + ? window.shuoshuoComment = { loadComment: loadValine } + : window.loadOtherComment = loadValine + return + } + + if ('!{use[0]}' === 'Valine' || !!{lazyload}) { + if (!{lazyload}) btf.loadComment(document.getElementById('vcomment'),loadValine) + else setTimeout(loadValine, 0) + } else { + window.loadOtherComment = loadValine + } + })() \ No newline at end of file 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..99657e7 --- /dev/null +++ b/themes/butterfly/layout/includes/third-party/comments/waline.pug @@ -0,0 +1,61 @@ +- const { serverURL, option, pageview } = theme.waline +- const { lazyload, count, use } = theme.comments + +script. + (() => { + let initFn = window.walineFn || null + const isShuoshuo = GLOBAL_CONFIG_SITE.pageType === 'shuoshuo' + const option = !{JSON.stringify(option)} + + const destroyWaline = ele => ele.destroy() + + const initWaline = (Fn, el = document, path = window.location.pathname) => { + const waline = Fn({ + el: el.querySelector('#waline-wrap'), + serverURL: '!{serverURL}', + pageview: !{lazyload ? false : pageview}, + dark: 'html[data-theme="dark"]', + comment: !{lazyload ? false : count}, + ...option, + path: isShuoshuo ? path : (option && option.path) || path + }) + + if (isShuoshuo) { + window.shuoshuoComment.destroyWaline = () => { + destroyWaline(waline) + if (el.children.length) { + el.innerHTML = '' + el.classList.add('no-comment') + } + } + } + } + + const loadWaline = (el, path) => { + if (initFn) initWaline(initFn, el, path) + 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, el, path) + window.walineFn = initFn + }) + } + } + + if (isShuoshuo) { + '!{use[0]}' === 'Waline' + ? window.shuoshuoComment = { loadComment: loadWaline } + : window.loadOtherComment = loadWaline + return + } + + 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/chartjs.pug b/themes/butterfly/layout/includes/third-party/math/chartjs.pug new file mode 100644 index 0000000..235d8e4 --- /dev/null +++ b/themes/butterfly/layout/includes/third-party/math/chartjs.pug @@ -0,0 +1,91 @@ +- const { fontColor, borderColor, scale_ticks_backdropColor } = theme.chartjs + +script. + (() => { + const applyThemeDefaultsConfig = theme => { + if (theme === 'dark-mode') { + Chart.defaults.color = "!{fontColor.dark}" + Chart.defaults.borderColor = "!{borderColor.dark}" + Chart.defaults.scale.ticks.backdropColor = "!{scale_ticks_backdropColor.dark}" + } else { + Chart.defaults.color = "!{fontColor.light}" + Chart.defaults.borderColor = "!{borderColor.light}" + Chart.defaults.scale.ticks.backdropColor = "!{scale_ticks_backdropColor.light}" + } + } + + // Recursively traverse the config object and automatically apply theme-specific color schemes + const applyThemeConfig = (obj, theme) => { + if (typeof obj !== 'object' || obj === null) return + + Object.keys(obj).forEach(key => { + const value = obj[key] + // If the property is an object and has theme-specific options, apply them + if (typeof value === 'object' && value !== null) { + if (value[theme]) { + obj[key] = value[theme] // Apply the value for the current theme + } else { + // Recursively process child objects + applyThemeConfig(value, theme) + } + } + }) + } + + const runChartJS = ele => { + window.loadChartJS = true + + Array.from(ele).forEach((item, index) => { + const chartSrc = item.firstElementChild + const chartID = item.getAttribute('data-chartjs-id') || ('chartjs-' + index) // Use custom ID or default ID + const width = item.getAttribute('data-width') + const existingCanvas = document.getElementById(chartID) + + // If a canvas already exists, remove it to avoid rendering duplicates + if (existingCanvas) { + existingCanvas.parentNode.remove() + } + + const chartDefinition = chartSrc.textContent + const canvas = document.createElement('canvas') + canvas.id = chartID + + const div = document.createElement('div') + div.className = 'chartjs-wrap' + + if (width) { + div.style.width = width + } + + div.appendChild(canvas) + chartSrc.insertAdjacentElement('afterend', div) + + const ctx = document.getElementById(chartID).getContext('2d') + + const config = JSON.parse(chartDefinition) + + const theme = document.documentElement.getAttribute('data-theme') === 'dark' ? 'dark-mode' : 'light-mode' + + // Set default styles (initial setup) + applyThemeDefaultsConfig(theme) + + // Automatically traverse the config and apply dual-mode color schemes + applyThemeConfig(config, theme) + + new Chart(ctx, config) + }) + } + + const loadChartJS = () => { + const chartJSEle = document.querySelectorAll('#article-container .chartjs-container') + if (chartJSEle.length === 0) return + + window.loadChartJS ? runChartJS(chartJSEle) : btf.getScript('!{url_for(theme.asset.chartjs)}').then(() => runChartJS(chartJSEle)) + } + + // Listen for theme change events + btf.addGlobalFn('themeChange', loadChartJS, 'chartjs') + btf.addGlobalFn('encrypt', loadChartJS, 'chartjs') + + window.pjax ? loadChartJS() : document.addEventListener('DOMContentLoaded', loadChartJS) + })() 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..93302f8 --- /dev/null +++ b/themes/butterfly/layout/includes/third-party/math/index.pug @@ -0,0 +1,14 @@ +case theme.math.use + when 'mathjax' + if (theme.math.per_page && (['post','page'].includes(globalPageType))) || page.mathjax + include ./mathjax.pug + + when 'katex' + if (theme.math.per_page && (['post','page'].includes(globalPageType))) || page.katex + include ./katex.pug + +if theme.mermaid.enable + include ./mermaid.pug + +if theme.chartjs.enable + include ./chartjs.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..f0483ef --- /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..4e703b2 --- /dev/null +++ b/themes/butterfly/layout/includes/third-party/newest-comments/artalk.pug @@ -0,0 +1,67 @@ +- 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': '!{newestCommentsLimit * 2}', // Fetch more comments to filter pending comments + }) + + 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 + .filter(e => !e.is_pending) // Filter pending comments + .slice(0, !{newestCommentsLimit}) // Limit the number of comments + .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..f3b88f5 --- /dev/null +++ b/themes/butterfly/layout/includes/third-party/newest-comments/common.pug @@ -0,0 +1,61 @@ +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 && !theme.lazyload.native ? "data-lazy-src" : "src"}' + const lazyloadNative = '!{theme.lazyload.enable && theme.lazyload.native ? "loading=\"lazy\"" : ""}' + 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..e26bb41 --- /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=!{newestCommentsLimit}&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..7dc418c --- /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=!{newestCommentsLimit}&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..a2f2c0d --- /dev/null +++ b/themes/butterfly/layout/includes/third-party/newest-comments/index.pug @@ -0,0 +1,34 @@ +- let { use } = theme.comments + +if use + - + let forum,apiKey,userRepo + let { limit:newestCommentsLimit } = theme.aside.card_newest_comments + if (newestCommentsLimit > 10 || newestCommentsLimit < 1) newestCommentsLimit = 6 + + 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..4321b3b --- /dev/null +++ b/themes/butterfly/layout/includes/third-party/newest-comments/remark42.pug @@ -0,0 +1,31 @@ +- const { host, siteId } = theme.remark42 +!= partial("includes/third-party/newest-comments/common.pug", {}, { cache: true }) + +script. + window.addEventListener('load', () => { + const keyName = 'remark42-newest-comments' + const { changeContent, generateHtml, run } = window.newestComments + + const getComment = ele => { + fetch('!{host}/api/v1/last/!{newestCommentsLimit}?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..772c8ec --- /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: !{newestCommentsLimit}, + 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..08af70c --- /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=!{newestCommentsLimit}&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..72743ea --- /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=!{newestCommentsLimit}') + 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/pjax.pug b/themes/butterfly/layout/includes/third-party/pjax.pug new file mode 100644 index 0000000..d6b69bb --- /dev/null +++ b/themes/butterfly/layout/includes/third-party/pjax.pug @@ -0,0 +1,68 @@ +- 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 choose.includes('Livere') || choose.includes('Utterances') || choose.includes('Giscus') + - pjaxSelectors.unshift('link[rel="canonical"]') + if theme.Open_Graph_meta.enable + - pjaxSelectors.unshift('meta[property="og:image"]', 'meta[property="og:title"]', 'meta[property="og:url"]', 'meta[property="og:description"]') + else + - pjaxSelectors.unshift('meta[name="description"]') + +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) { + const usePjax = !{theme.pjax && theme.pjax.enable} + !{theme.error_404 && theme.error_404.enable} + ? (usePjax ? pjax.loadUrl('!{url_for("/404.html")}') : window.location.href = '!{url_for("/404.html")}') + : window.location.href = e.request.responseURL + } + }) + })() \ 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..4ef6001 --- /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..e459a94 --- /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.text-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..2dfbdb5 --- /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.share.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..6692b85 --- /dev/null +++ b/themes/butterfly/layout/includes/third-party/subtitle.pug @@ -0,0 +1,113 @@ +- 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() + } + }, + processSubtitle: (content, extraContents = []) => { + if (!{effect}) { + const sub = !{JSON.stringify(subContent)}.slice() + + if (extraContents.length > 0) { + sub.unshift(...extraContents) + } + + if (typeof content === 'string') { + sub.unshift(content) + } else if (Array.isArray(content)) { + sub.unshift(...content) + } + + sub.length > 0 && typedJSFn.init(sub) + } else { + document.getElementById('subtitle').textContent = typeof content === 'string' ? content : + (Array.isArray(content) && content.length > 0 ? content[0] : '') + } + } + } + btf.addGlobalFn('pjaxSendOnce', () => { typed.destroy() }, 'typedDestroy') + +case source + when 1 + script. + function subtitleType () { + fetch('https://v1.hitokoto.cn') + .then(response => response.json()) + .then(data => { + const from = '出自 ' + data.from + typedJSFn.processSubtitle(data.hitokoto, [from]) + }) + .catch(err => { + console.error('Failed to get the Hitokoto API:', err) + typedJSFn.processSubtitle(!{JSON.stringify(subContent)}) + }) + } + typedJSFn.run(subtitleType) + + when 2 + script. + function subtitleType () { + fetch('https://v.api.aa1.cn/api/yiyan/index.php') + .then(response => response.text()) + .then(data => { + const reg = /

(.*?)<\/p>/g + const result = reg.exec(data) + if (result && result[1]) { + typedJSFn.processSubtitle(result[1]) + } else { + throw new Error('Failed to parse the return value of the Yiyan API') + } + }) + .catch(err => { + console.error('Failed to get the Yiyan API:', err) + typedJSFn.processSubtitle(!{JSON.stringify(subContent.length)}) + }) + } + typedJSFn.run(subtitleType) + + when 3 + script. + function subtitleType () { + btf.getScript('https://sdk.jinrishici.com/v2/browser/jinrishici.js') + .then(() => { + jinrishici.load(result => { + if (result && result.data && result.data.content) { + typedJSFn.processSubtitle(result.data.content) + } else { + throw new Error('Failed to parse the return value of Jinrishici API') + } + }) + }) + .catch(err => { + console.error('Failed to get the Jinrishici API:', err) + typedJSFn.processSubtitle(!{JSON.stringify(subContent.length)}) + }) + } + typedJSFn.run(subtitleType) + + default + if subContent.length > 0 + script. + function subtitleType () { + typedJSFn.processSubtitle(!{JSON.stringify(subContent)}) + } + 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..3534c20 --- /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.pageType === 'post' && 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..171ec9c --- /dev/null +++ b/themes/butterfly/layout/includes/widget/card_announcement.pug @@ -0,0 +1,7 @@ +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 + #welcome-info \ 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..9f7e687 --- /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.text-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..cd30d50 --- /dev/null +++ b/themes/butterfly/layout/includes/widget/card_post_toc.pug @@ -0,0 +1,14 @@ +- 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}) 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..7ea4d98 --- /dev/null +++ b/themes/butterfly/layout/includes/widget/card_webinfo.pug @@ -0,0 +1,44 @@ +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 \ No newline at end of file diff --git a/themes/butterfly/layout/includes/widget/index.pug b/themes/butterfly/layout/includes/widget/index.pug new file mode 100644 index 0000000..b298878 --- /dev/null +++ b/themes/butterfly/layout/includes/widget/index.pug @@ -0,0 +1,36 @@ +#aside-content.aside-content + //- post + if globalPageType === '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..2374681 --- /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..23a7d54 --- /dev/null +++ b/themes/butterfly/layout/post.pug @@ -0,0 +1,37 @@ +extends includes/layout.pug + +block content + #post + if top_img === false + include includes/header/post-info.pug + + article#article-container.container.post-content + if page.summary && theme.ai_summary.enable + include includes/post/post-summary.pug + if theme.noticeOutdate.enable && page.noticeOutdate !== false + include includes/post/outdate-notice.pug + else + !=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}) 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..ad9f537 --- /dev/null +++ b/themes/butterfly/package.json @@ -0,0 +1,34 @@ +{ + "name": "hexo-theme-butterfly", + "version": "5.4.3", + "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-pug": "^3.0.0", + "hexo-renderer-stylus": "^3.0.1", + "hexo-util": "^3.3.0", + "moment-timezone": "^0.5.48" + }, + "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..1cdcbaf --- /dev/null +++ b/themes/butterfly/plugins.yml @@ -0,0 +1,211 @@ +abcjs_basic_js: + name: abcjs + file: dist/abcjs-basic-min.js + version: 6.5.1 +activate_power_mode: + name: butterfly-extsrc + file: dist/activate-power-mode.min.js + version: 1.1.4 +algolia_search: + name: algoliasearch + file: dist/lite/builds/browser.umd.js + version: 5.34.1 +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 +chartjs: + name: chart.js + file: dist/chart.umd.js + version: 4.5.0 +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.1.0 +disqusjs_css: + name: disqusjs + file: dist/browser/styles/disqusjs.css + version: 3.1.0 +docsearch_css: + name: '@docsearch/css' + other_name: docsearch-css + file: dist/style.css + version: 3.9.0 +docsearch_js: + name: '@docsearch/js' + other_name: docsearch-js + file: dist/umd/index.js + version: 3.9.0 +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: 6.0.17 + other_name: fancyapps-ui +fancybox_css: + name: '@fancyapps/ui' + file: dist/fancybox/fancybox.css + version: 6.0.17 + 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.7.2 +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.79.2 +katex: + name: katex + file: dist/katex.min.css + other_name: KaTeX + version: 0.16.22 +katex_copytex: + name: katex + file: dist/contrib/copy-tex.min.js + other_name: KaTeX + version: 0.16.22 +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.9.0 +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 +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.30.0 +prismjs_js: + name: prismjs + file: prism.js + other_name: prism + version: 1.30.0 +prismjs_lineNumber_js: + name: prismjs + file: plugins/line-numbers/prism-line-numbers.min.js + other_name: prism + version: 1.30.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.44 +typed: + name: typed.js + file: dist/typed.umd.js + version: 2.1.0 +valine: + name: valine + file: dist/Valine.min.js + version: 1.5.3 +waline_css: + name: '@waline/client' + file: dist/waline.css + other_name: waline + version: 3.6.0 +waline_js: + name: '@waline/client' + file: dist/waline.js + other_name: waline + version: 3.6.0 diff --git a/themes/butterfly/scripts/common/postDesc.js b/themes/butterfly/scripts/common/postDesc.js new file mode 100644 index 0000000..a295557 --- /dev/null +++ b/themes/butterfly/scripts/common/postDesc.js @@ -0,0 +1,37 @@ +'use strict' + +const { stripHTML, truncate } = require('hexo-util') + +// Truncates the given content to a specified length, removing HTML tags and replacing newlines with spaces. +const truncateContent = (content, length, encrypt = false) => { + if (!content || encrypt) return '' + return truncate(stripHTML(content).replace(/\n/g, ' '), { length }) +} + +// Generates a post description based on the provided data and theme configuration. +const postDesc = (data, hexo) => { + const { description, content, postDesc, encrypt } = data + + if (postDesc) return postDesc + + const { length, method } = hexo.theme.config.index_post_content + + if (method === false) return + + let result + switch (method) { + case 1: + result = description + break + case 2: + result = description || truncateContent(content, length, encrypt) + break + default: + result = truncateContent(content, length, encrypt) + } + + data.postDesc = result + return result +} + +module.exports = { truncateContent, postDesc } 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..16da948 --- /dev/null +++ b/themes/butterfly/scripts/events/comment.js @@ -0,0 +1,24 @@ +/** + * 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 + + // Make sure use is an array + use = Array.isArray(use) ? use : use.split(',') + + // Capitalize the first letter of each comment name + use = use.map(item => + item.trim().toLowerCase().replace(/\b[a-z]/g, s => s.toUpperCase()) + ) + + // Disqus and Disqusjs conflict, only keep the first one + if (use.includes('Disqus') && use.includes('Disqusjs')) { + use = [use[0]] + } + + themeConfig.comments.use = use +}) 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..babba3d --- /dev/null +++ b/themes/butterfly/scripts/events/merge_config.js @@ -0,0 +1,593 @@ +const { deepMerge } = require('hexo-util') + +hexo.extend.filter.register('before_generate', () => { + const defaultConfig = { + nav: { + logo: null, + display_title: true, + display_post_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: { + nav: null, + owner: { + enable: true, + since: 2024 + }, + copyright: { + enable: true, + version: true + }, + custom_text: null + }, + 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 + }, + rightside_config_animation: true, + 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: 'en_US' + }, + 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 + }, + crisp: { + website_id: null + }, + google_tag_manager: { + tag_id: null, + domain: 'https://www.googletagmanager.com' + }, + 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' + } + }, + chartjs: { + enable: false, + fontColor: { + light: 'rgba(0, 0, 0, 0.8)', + dark: 'rgba(255, 255, 255, 0.8)' + }, + borderColor: { + light: 'rgba(0, 0, 0, 0.1)', + dark: 'rgba(255, 255, 255, 0.2)' + }, + scale_ticks_backdropColor: { + light: 'transparent', + dark: 'transparent' + } + }, + 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, + lazyload: { + enable: false, + native: 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 + }, + structured_data: true, + 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..7ad6339 --- /dev/null +++ b/themes/butterfly/scripts/filters/post_lazyload.js @@ -0,0 +1,31 @@ +/** + * Butterfly + * lazyload + * replace src to data-lazy-src + */ + +'use strict' + +const urlFor = require('hexo-util').url_for.bind(hexo) + +const lazyload = htmlContent => { + if (hexo.theme.config.lazyload.native) { + return htmlContent.replace(/()/ig, '$1 loading=\'lazy\'$2') + } + + 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..e7cc24d --- /dev/null +++ b/themes/butterfly/scripts/filters/random_cover.js @@ -0,0 +1,82 @@ +/** + * Random cover for posts + */ + +'use strict' + +hexo.extend.generator.register('post', locals => { + const previousIndexes = [] + + const getRandomCover = defaultCover => { + if (!defaultCover) return false + if (!Array.isArray(defaultCover)) return defaultCover + + const coverCount = defaultCover.length + + if (coverCount === 1) { + return defaultCover[0] + } + + const maxPreviousIndexes = coverCount === 2 ? 1 : (coverCount === 3 ? 2 : 3) + + let index + do { + index = Math.floor(Math.random() * coverCount) + } while (previousIndexes.includes(index) && previousIndexes.length < coverCount) + + previousIndexes.push(index) + if (previousIndexes.length > maxPreviousIndexes) { + previousIndexes.shift() + } + + return defaultCover[index] + } + + const handleImg = data => { + const imgTestReg = /\.(png|jpe?g|gif|svg|webp|avif)(\?.*)?$/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}` + } + } + + if (coverVal === false) return data + + // If cover is not set, use random cover + if (!coverVal) { + const { cover: { default_cover: defaultCover } } = hexo.theme.config + const randomCover = getRandomCover(defaultCover) + data.cover = randomCover + coverVal = randomCover // update coverVal + } + + if (coverVal && (coverVal.indexOf('//') !== -1 || imgTestReg.test(coverVal))) { + data.cover_type = 'img' + } + + return data + } + + // https://github.com/hexojs/hexo/blob/master/lib%2Fplugins%2Fgenerator%2Fpost.ts + const posts = locals.posts.sort('date').toArray() + const { length } = posts + + return posts.map((post, i) => { + if (i) post.prev = posts[i - 1] + if (i < length - 1) post.next = posts[i + 1] + + post.__post = true + + return { + data: handleImg(post), + layout: 'post', + path: post.path + } + }) +}) diff --git a/themes/butterfly/scripts/helpers/aside_archives.js b/themes/butterfly/scripts/helpers/aside_archives.js new file mode 100644 index 0000000..cb33c06 --- /dev/null +++ b/themes/butterfly/scripts/helpers/aside_archives.js @@ -0,0 +1,126 @@ +'use strict' + +hexo.extend.helper.register('aside_archives', function (options = {}) { + const { config, page, site, url_for, _p } = this + const { archive_dir: archiveDir, timezone, language } = config + + // Destructure and set default options with object destructuring + const { + type = 'monthly', + format = type === 'monthly' ? 'MMMM YYYY' : 'YYYY', + show_count: showCount = true, + order = -1, + limit, + transform + } = options + + // Optimize locale handling + const lang = toMomentLocale(page.lang || page.language || language) + + // Memoize comparison function to improve performance + const compareFunc = + type === 'monthly' + ? (yearA, monthA, yearB, monthB) => yearA === yearB && monthA === monthB + : (yearA, yearB) => yearA === yearB + + // Early return if no posts + if (!site.posts.length) return '' + + // Use reduce for more efficient data processing + const data = site.posts.sort('date', order).reduce((acc, post) => { + let date = post.date.clone() + if (timezone) date = date.tz(timezone) + + const year = date.year() + const month = date.month() + 1 + + if (lang) date = date.locale(lang) + + // Find or create archive entry + const lastEntry = acc[acc.length - 1] + + if (type === 'yearly') { + const existingYearIndex = acc.findIndex(entry => entry.year === year) + if (existingYearIndex !== -1) { + acc[existingYearIndex].count++ + } else { + // 否則創建新條目 + acc.push({ + name: date.format(format), + year, + month, + count: 1 + }) + } + } else { + if (!lastEntry || !compareFunc(lastEntry.year, lastEntry.month, year, month)) { + acc.push({ + name: date.format(format), + year, + month, + count: 1 + }) + } else { + lastEntry.count++ + } + } + + return acc + }, []) + + // Create link generator function + const createArchiveLink = item => { + let url = `${archiveDir}/${item.year}/` + if (type === 'monthly') { + url += item.month < 10 ? `0${item.month}/` : `${item.month}/` + } + return url_for(url) + } + + // Limit results efficiently + const limitedData = limit > 0 ? data.slice(0, Math.min(data.length, limit)) : data + + // Use template literal for better readability + const archiveHeader = ` +

+ + ${_p('aside.card_archives')} + ${ + data.length > limitedData.length + ? ` + + ` + : '' + } +
+ ` + + // Use map for generating list items, join for performance + const archiveList = ` + + ` + + return archiveHeader + archiveList +}) + +// Improved locale conversion function +const toMomentLocale = lang => { + if (!lang || ['en', 'default'].includes(lang)) 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..0fe4abd --- /dev/null +++ b/themes/butterfly/scripts/helpers/inject_head_js.js @@ -0,0 +1,155 @@ +'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] || {} + 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..60819d3 --- /dev/null +++ b/themes/butterfly/scripts/helpers/page.js @@ -0,0 +1,152 @@ +'use strict' + +const { truncateContent, postDesc } = require('../common/postDesc') +const { prettyUrls } = require('hexo-util') +const crypto = require('crypto') +const moment = require('moment-timezone') + +hexo.extend.helper.register('truncate', truncateContent) + +hexo.extend.helper.register('postDesc', data => { + return postDesc(data, hexo) +}) + +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', function(path) { + if (!path) return '' + + const absoluteUrlPattern = /^(?:[a-z][a-z\d+.-]*:)?\/\//i + const relativeUrlPattern = /^(\.\/|\.\.\/|\/|[^/]+\/).*$/ + const colorPattern = /^(#|rgb|rgba|hsl|hsla)/i + + if (colorPattern.test(path)) { + return `background-color: ${path};` + } else if (absoluteUrlPattern.test(path) || relativeUrlPattern.test(path)) { + return `background-image: url(${this.url_for(path)});` + } else { + return `background: ${path};` + } +}) + +hexo.extend.helper.register('shuoshuoFN', (data, page) => { + const { limit } = page + let finalResult = '' + + // Check if limit.value is a valid date + const isValidDate = date => !isNaN(Date.parse(date)) + + // order by date + const orderByDate = data => data.sort((a, b) => Date.parse(b.date) - Date.parse(a.date)) + + // Apply number limit or time limit conditionally + const limitData = data => { + if (limit && limit.type === 'num' && limit.value > 0) { + return data.slice(0, limit.value) + } else if (limit && limit.type === 'date' && isValidDate(limit.value)) { + const limitDate = Date.parse(limit.value) + return data.filter(item => Date.parse(item.date) >= limitDate) + } + + return data + } + + orderByDate(data) + finalResult = limitData(data) + + // This is a hack method, because hexo treats time as UTC time + // so you need to manually convert the time zone + finalResult.forEach(item => { + const utcDate = moment.utc(item.date).format('YYYY-MM-DD HH:mm:ss') + item.date = moment.tz(utcDate, hexo.config.timezone).format('YYYY-MM-DD HH:mm:ss') + }) + + return finalResult +}) + +hexo.extend.helper.register('getPageType', (page, isHome) => { + const { layout, tag, category, type, archive } = page + if (layout) return layout + if (tag) return 'tag' + if (category) return 'category' + if (archive) return 'archive' + if (type) { + if (type === 'tags' || type === 'categories') return type + else return 'page' + } + if (isHome) return 'home' + return 'post' +}) + +hexo.extend.helper.register('getVersion', () => { + const { version } = require('../../package.json') + return { hexo: hexo.version, theme: version } +}) diff --git a/themes/butterfly/scripts/helpers/related_post.js b/themes/butterfly/scripts/helpers/related_post.js new file mode 100644 index 0000000..8059dbe --- /dev/null +++ b/themes/butterfly/scripts/helpers/related_post.js @@ -0,0 +1,97 @@ +/* eslint-disable camelcase */ +/** + * Butterfly + * Related Posts + * According the tag + */ + +'use strict' + +const { postDesc } = require('../common/postDesc') + +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 getPostDesc = post.postDesc || postDesc(post, hexo) + const relatedPost = { + title: post.title, + path: post.path, + cover: post.cover, + cover_type: post.cover_type, + weight: 1, + updated: post.updated, + created: post.date, + postDesc: getPostDesc + } + 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 += '