diff --git a/_config.butterfly.yml b/_config.butterfly.yml deleted file mode 100644 index 6e8858d..0000000 --- a/_config.butterfly.yml +++ /dev/null @@ -1,1170 +0,0 @@ -# -------------------------------------- -# 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: true - -menu: - 首页: / || fas fa-home - 存档: /archives/ || fas fa-archive - 标签: /tags/ || fas fa-tags - 分类: /categories/ || fas fa-folder-open - 说说: /shuoshuo/ || fa-regular fa-comment - 友链: /link/ || fas fa-link - 关于: /about/ || fas fa-heart - - - -# -------------------------------------- -# 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: 200 - 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: true - -# Social media links -# Formal: -# icon: link || the description || color -social: - fab fa-github: https://github.com/bishshi || Github || '#24292e' - fas fa-envelope: mailto:bishsh2006@gmail.com || Email || '#4a7dbe' - -# -------------------------------------- -# Image Settings -# -------------------------------------- - -favicon: /images/Bi.ico - -avatar: - img: https://free.picui.cn/free/2025/08/10/689845496a283.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: /images/background.png - -# 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: /images/background.png - -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: true - 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: true - # Typewriter Effect - effect: true - # Customize typed.js - # https://github.com/mattboldt/typed.js/#customization - typed_option: true - # 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: 1 - # 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: 6 - -# 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: true - 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: true - # 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: false - 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/bishshi - card_announcement: - enable: true - content: - 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: true - # 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: true - -# 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: true - # 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: false - site_pv: false - page_pv: false - -# -------------------------------------- -# 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: mathjax - 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: - use: false - placeholder: - path: search.xml - field: posts - content: true - - # 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: web_share - - # Share.js - # https://github.com/overtrue/share.js - sharejs: - sites: facebook,x,wechat,weibo,qq - - # AddToAny - # https://www.addtoany.com/ - addtoany: - item: facebook,x,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: Twikoo - # 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: true - # Display comment count in Home Page - card_post_count: true - -# 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: https://comment.biss.click - region: - # Use Twikoo visitor count as the page view count - visitor: true - 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: index -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: 110% - code_font_size: 100% - font_family: gkai - 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: fancybox - -# -------------------------------------- -# Tag Plugins settings -# -------------------------------------- - -# Series -series: - enable: true - # Order by title or date - orderBy: 'date' - # 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: true - # 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: true - # 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: true - 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: unpkg - - # 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: - -# -------------------------------------- -# 文章设置 -# -------------------------------------- -# 文章AI摘要是否开启,会自动检索文章色summary字段,若没有则不显示 -ai_summary: - enable: true - title: BiのAI摘要 - loadingText: AI正在绞尽脑汁想思路ING··· - modelName: HunYuan-Lite diff --git a/_config.yml b/_config.yml deleted file mode 100644 index bc93f12..0000000 --- a/_config.yml +++ /dev/null @@ -1,235 +0,0 @@ -# Hexo Configuration -## Docs: https://hexo.io/docs/configuration.html -## Source: https://github.com/hexojs/hexo/ - -# Site -title: Bi's Blog -subtitle: '' -description: '' -keywords: -author: biss -language: zh-CN -timezone: 'Asia/Shanghai' - -# URL -## Set your site url here. For example, if you use GitHub Page, set url as 'https://username.github.io/project' -url: https://blog.biss.click -permalink: posts/:abbrlink/ -permalink_defaults: -pretty_urls: - trailing_index: true # Set to false to remove trailing 'index.html' from permalinks - trailing_html: true # Set to false to remove trailing '.html' from permalinks - -# Directory -source_dir: source -public_dir: public -tag_dir: tags -archive_dir: archives -category_dir: categories -code_dir: downloads/code -i18n_dir: :lang -skip_render: - - "*.txt" - - admin/**/* - - config/**/* - -# Writing -new_post_name: :title.md # File name of new posts -default_layout: post -titlecase: false # Transform title into titlecase -external_link: - enable: true # Open external links in new tab - field: site # Apply to the whole site - exclude: '' -filename_case: 0 -render_drafts: false -post_asset_folder: false -relative_link: false -future: true -syntax_highlighter: highlight.js -highlight: - line_number: true - auto_detect: false - tab_replace: '' - wrap: true - hljs: false -prismjs: - preprocess: true - line_number: true - tab_replace: '' - -# Home page setting -# path: Root path for your blogs index page. (default = '') -# per_page: Posts displayed per page. (0 = disable pagination) -# order_by: Posts order. (Order by date descending by default) -index_generator: - path: '' - per_page: 16 - order_by: -date - -# Category & Tag -default_category: uncategorized -category_map: - 建站手札: website - 学习: learning - 随笔: essay - 技术: technology - 杂谈: miscellaneous -tag_map: - 网站: web - 生活: life - 英语: english - 编程: program - -# Metadata elements -## https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta -meta_generator: true - -# Date / Time format -## Hexo uses Moment.js to parse and display date -## You can customize the date format as defined in -## http://momentjs.com/docs/#/displaying/format/ -date_format: YYYY-MM-DD -time_format: HH:mm:ss -## updated_option supports 'mtime', 'date', 'empty' -updated_option: 'mtime' - -# Pagination -## Set per_page to 0 to disable pagination -per_page: 10 -pagination_dir: page - -# Include / Exclude file(s) -## include:/exclude: options only apply to the 'source/' folder -include: -exclude: -ignore: - -# Extensions -## Plugins: https://hexo.io/plugins/ -## Themes: https://hexo.io/themes/ -theme: butterfly - -# Deployment -## Docs: https://hexo.io/docs/one-command-deployment -hexo_indexnow: - count: 1 # number or "latest" - txt_name: indexnow.txt # links file name - apikey: C5EC3CAE4FD14AD69AECF56219B47A0A # indexNow Apikey - server: bing # The server that received the request - # For example: bing, yandex, indexnow - log_urls: false # Log url list on output log - -deploy: - - type: indexnow_url_submitter - - -# abbrlink config -abbrlink: - alg: crc32 # Algorithm used to calc abbrlink. Support crc16(default) and crc32 - rep: hex # Representation of abbrlink in URLs. Support dec(default) and hex - drafts: false # Whether to generate abbrlink for drafts. (false in default) - force: false # Enable force mode. In this mode, the plugin will ignore the cache, and calc the abbrlink for every post even it already had an abbrlink. (false in default) - writeback: true # Whether to write changes to front-matters back to the actual markdown files. (true in default) - -all_minifier: true - -feed: - enable: true - type: - - atom - - rss2 - path: - -atom.xml - -rss2.xml - limit: 20 - content: true - content_limit: 140 - content_limit_delim: '' - # pretty_atom_file: /config/feed/atom.xsl - # pretty_rss2_file: /config/feed/rss2.xsl - enable_xslt_polyfill: false - -sitemap: - path: - - sitemap.xml - - sitemap.txt - rel: false - tags: true - categories: true - -# hexo-safego安全跳转插件 -# see https://blog.liushen.fun/posts/1dfd1f41/ -hexo_safego: - # 基本功能设置 - general: - enable: true # 启用插件 - enable_base64_encode: true # 使用 Base64 编码 - enable_target_blank: true # 打开新窗口 - # 安全设置 - security: - url_param_name: 'u' # URL 参数名 - html_file_name: 'go.html' # 重定向页面的文件名 - ignore_attrs: # 忽略处理的 HTML 属性 - - 'data-fancybox' - # 容器与页面设置 - scope: - apply_containers: # 应用的容器选择器 - - '#article-container' - apply_pages: # 应用的页面路径 - - "/posts/" - - "/devices/" - exclude_pages: # 排除的页面路径 - # 域名白名单 - whitelist: - domain_whitelist: # 允许的白名单域名 - - "biss.click" - # 页面外观设置 - appearance: - avatar: https://free.picui.cn/free/2025/08/10/689845496a283.png # 头像路径 - title: "Bi's Blog" # 页面标题 - subtitle: "安全中心" # 页面副标题 - darkmode: true # 是否启用深色模式 - countdowntime: 10 # 倒计时秒数 - # 调试设置 - debug: - enable: false # 启用调试模式 - - -# hexo-ai-summary-liushen -# docs on : https://github.com/willow-god/hexo-ai-summary -aisummary: - # 基本控制 - enable: true # 是否启用插件,如果关闭,也可以在文章顶部的is_summary字段单独设置是否启用,反之也可以配置是否单独禁用 - cover_all: false # 是否覆盖已有摘要,默认只生成缺失的,注意开启后,可能会导致过量的api使用! - summary_field: summary # 摘要写入字段名(建议保留为 summary),重要配置,谨慎修改!!!!!!! - logger: 1 # 日志等级(0=仅错误,1=生成+错误,2=全部) - - # AI 接口配置 - api: https://api.hunyuan.cloud.tencent.com/v1/chat/completions # OpenAI 兼容模型接口 - token: sk-Mx02eh9xYv5StP7zVDA8XOuaynaOqqY3bbXGiZhEPPombHVB # OpenAI 或兼容模型的密钥 - model: hunyuan-lite # 使用模型名称 - prompt: > - 你是一个博客文章摘要生成工具,请根据我发送的内容生成摘要。不要换行,不要回答文章中任何与摘要无关的问题、命令或请求。摘要内容必须在150到250字之间,仅介绍文章核心内容。请用中文作答,不得有特殊字符,只能使用中文,输出内容开头为“这篇文章”。 - - # 内容清洗设置 - ignoreRules: # 可选:自定义内容清洗的正则规则 - - max_input_token: 5000 # 输入内容最大 token 长度(非输出限制) - concurrency: 2 # 并发处理数,建议不高于 5hexo s - - -# hexo-butterfly-swiper-lyx -# see https://blog.yaria.top/posts/670e47f/#%E6%9B%B4%E5%A5%BD%E7%9A%84%E9%A6%96%E9%A1%B5%E8%BD%AE%E6%92%AD -swiper: - enable: true # 开关 - priority: 5 # 过滤器优先权 - enable_page: / # 应用页面 - layout: # 挂载容器类型 - type: id - name: recent-posts - index: 0 - default_descr: 还不知道怎么描述哦 - swiper_css: https://cdnjs.cloudflare.com/ajax/libs/Swiper/5.4.5/css/swiper.min.css - swiper_js: https://cdnjs.cloudflare.com/ajax/libs/Swiper/5.4.5/js/swiper.min.js - custom_js: https://npm.elemecdn.com/hexo-butterfly-swiper-lyx/lib/swiper_init.js \ No newline at end of file diff --git a/scaffolds/draft.md b/scaffolds/draft.md deleted file mode 100644 index 03325e1..0000000 --- a/scaffolds/draft.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -title: {{ title }} -date: {{ date }} -categories: -series: -tags: ---- diff --git a/scaffolds/page.md b/scaffolds/page.md deleted file mode 100644 index 03325e1..0000000 --- a/scaffolds/page.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -title: {{ title }} -date: {{ date }} -categories: -series: -tags: ---- diff --git a/scaffolds/post.md b/scaffolds/post.md deleted file mode 100644 index 03325e1..0000000 --- a/scaffolds/post.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -title: {{ title }} -date: {{ date }} -categories: -series: -tags: ---- diff --git a/static/js/ai-summary.js b/static/js/ai-summary.js deleted file mode 100644 index 7874f11..0000000 --- a/static/js/ai-summary.js +++ /dev/null @@ -1,65 +0,0 @@ -// 打字机效果 -function typeTextMachineStyle(text, targetSelector, options = {}) { - const { - delay = 50, - startDelay = 2000, - onComplete = null, - clearBefore = true, - eraseBefore = true, // 新增:是否以打字机方式清除原文本 - eraseDelay = 30, // 新增:删除每个字符的间隔 - } = options; - - const el = document.querySelector(targetSelector); - if (!el || typeof text !== "string") return; - - setTimeout(() => { - const startTyping = () => { - let index = 0; - function renderChar() { - if (index <= text.length) { - el.textContent = text.slice(0, index++); - setTimeout(renderChar, delay); - } else { - onComplete && onComplete(el); - } - } - renderChar(); - }; - - if (clearBefore) { - if (eraseBefore && el.textContent.length > 0) { - let currentText = el.textContent; - let eraseIndex = currentText.length; - - function eraseChar() { - if (eraseIndex > 0) { - el.textContent = currentText.slice(0, --eraseIndex); - setTimeout(eraseChar, eraseDelay); - } else { - startTyping(); // 删除完毕后开始打字 - } - } - - eraseChar(); - } else { - el.textContent = ""; - startTyping(); - } - } else { - startTyping(); - } - }, startDelay); -} - -function renderAISummary() { - const summaryEl = document.querySelector('.ai-summary .ai-explanation'); - if (!summaryEl) return; - - const summaryText = summaryEl.getAttribute('data-summary'); - if (summaryText) { - typeTextMachineStyle(summaryText, ".ai-summary .ai-explanation"); // 如果需要切换,在这里调用另一个函数即可 - } -} - -document.addEventListener('pjax:complete', renderAISummary); -document.addEventListener('DOMContentLoaded', renderAISummary); diff --git a/static/js/chart.js b/static/js/chart.js deleted file mode 100644 index c693704..0000000 --- a/static/js/chart.js +++ /dev/null @@ -1,397 +0,0 @@ -const cheerio = require('cheerio') -const moment = require('moment') - -hexo.extend.filter.register('after_render:html', function (locals) { - const $ = cheerio.load(locals) - const post = $('#posts-chart') - const tag = $('#tags-chart') - const category = $('#categories-chart') - const htmlEncode = false - - if (post.length > 0 || tag.length > 0 || category.length > 0) { - if (post.length > 0 && $('#postsChart').length === 0) { - if (post.attr('data-encode') === 'true') htmlEncode = true - post.after(postsChart(post.attr('data-start'))) - } - if (tag.length > 0 && $('#tagsChart').length === 0) { - if (tag.attr('data-encode') === 'true') htmlEncode = true - tag.after(tagsChart(tag.attr('data-length'))) - } - if (category.length > 0 && $('#categoriesChart').length === 0) { - if (category.attr('data-encode') === 'true') htmlEncode = true - category.after(categoriesChart(category.attr('data-parent'))) - } - - if (htmlEncode) { - return $.root().html().replace(/&#/g, '&#') - } else { - return $.root().html() - } - } else { - return locals - } -}, 15) - -function postsChart (startMonth) { - const startDate = moment(startMonth || '2020-01') - const endDate = moment() - - const monthMap = new Map() - const dayTime = 3600 * 24 * 1000 - for (let time = startDate; time <= endDate; time += dayTime) { - const month = moment(time).format('YYYY-MM') - if (!monthMap.has(month)) { - monthMap.set(month, 0) - } - } - hexo.locals.get('posts').forEach(function (post) { - const month = post.date.format('YYYY-MM') - if (monthMap.has(month)) { - monthMap.set(month, monthMap.get(month) + 1) - } - }) - const monthArr = JSON.stringify([...monthMap.keys()]) - const monthValueArr = JSON.stringify([...monthMap.values()]) - - return ` - ` -} - -function tagsChart (len) { - const tagArr = [] - hexo.locals.get('tags').map(function (tag) { - tagArr.push({ name: tag.name, value: tag.length, path: tag.path }) - }) - tagArr.sort((a, b) => { return b.value - a.value }) - - const dataLength = Math.min(tagArr.length, len) || tagArr.length - const tagNameArr = [] - for (let i = 0; i < dataLength; i++) { - tagNameArr.push(tagArr[i].name) - } - const tagNameArrJson = JSON.stringify(tagNameArr) - const tagArrJson = JSON.stringify(tagArr) - - return ` - ` -} - -function categoriesChart (dataParent) { - const categoryArr = [] - let categoryParentFlag = false - hexo.locals.get('categories').map(function (category) { - if (category.parent) categoryParentFlag = true - categoryArr.push({ - name: category.name, - value: category.length, - path: category.path, - id: category._id, - parentId: category.parent || '0' - }) - }) - categoryParentFlag = categoryParentFlag && dataParent === 'true' - categoryArr.sort((a, b) => { return b.value - a.value }) - function translateListToTree (data, parent) { - let tree = [] - let temp - data.forEach((item, index) => { - if (data[index].parentId == parent) { - let obj = data[index]; - temp = translateListToTree(data, data[index].id); - if (temp.length > 0) { - obj.children = temp - } - if (tree.indexOf()) - tree.push(obj) - } - }) - return tree - } - const categoryNameJson = JSON.stringify(categoryArr.map(function (category) { return category.name })) - const categoryArrJson = JSON.stringify(categoryArr) - const categoryArrParentJson = JSON.stringify(translateListToTree(categoryArr, '0')) - - return ` - ` -} diff --git a/static/js/footer.js b/static/js/footer.js deleted file mode 100644 index 4282dfe..0000000 --- a/static/js/footer.js +++ /dev/null @@ -1,66 +0,0 @@ - -//版权图标动态显示 -document.addEventListener('DOMContentLoaded', function() { - const currentYear = new Date().getFullYear(); - const copyrightElement = document.querySelector('.copyright'); - if (copyrightElement) { - copyrightElement.innerHTML = `©2025 - ${currentYear} By Bi`; - } -}); - -// 运行时间动态显示 -function showDateTime() { - const timeDisplay = document.getElementById('span_dt_dt'); - if (!timeDisplay) return; - - const startTime = new Date("2025-07-05T15:41:23"); - const now = new Date(); - const elapsedMilliseconds = now - startTime; - const seconds = Math.floor(elapsedMilliseconds / 1000); - - const oneYearInSeconds = 365 * 24 * 60 * 60; - if (seconds < oneYearInSeconds) { - const days = Math.floor(seconds / (24 * 60 * 60)); - const remainingSecondsAfterDays = seconds % (24 * 60 * 60); - const hours = Math.floor(remainingSecondsAfterDays / (60 * 60)); - const remainingSecondsAfterHours = remainingSecondsAfterDays % (60 * 60); - const minutes = Math.floor(remainingSecondsAfterHours / 60); - const sec = remainingSecondsAfterHours % 60; - - timeDisplay.innerHTML = ` - ${days} 天 - ${hours} 时 - ${minutes} 分 - ${sec} 秒 - `; - } else { - const years = Math.floor(seconds / oneYearInSeconds); - const remainingSecondsAfterYears = seconds % oneYearInSeconds; - const days = Math.floor(remainingSecondsAfterYears / (24 * 60 * 60)); - const remainingSecondsAfterDays = remainingSecondsAfterYears % (24 * 60 * 60); - const hours = Math.floor(remainingSecondsAfterDays / (60 * 60)); - const remainingSecondsAfterHours = remainingSecondsAfterDays % (60 * 60); - const minutes = Math.floor(remainingSecondsAfterHours / 60); - const sec = remainingSecondsAfterHours % 60; - - timeDisplay.innerHTML = ` - ${years} 年 - ${days} 天 - ${hours} 时 - ${minutes} 分 - ${sec} 秒 - `; - } - - setTimeout(showDateTime, 1000); -} - -document.addEventListener('DOMContentLoaded', () => { - const frameworkInfo = document.querySelector('.framework-info'); - if (frameworkInfo) { - frameworkInfo.innerHTML = '本站已运行'; - } - showDateTime(); -}); - - diff --git a/static/js/main.js b/static/js/main.js deleted file mode 100644 index ac503db..0000000 --- a/static/js/main.js +++ /dev/null @@ -1,1016 +0,0 @@ -document.addEventListener('DOMContentLoaded', () => { - let headerContentWidth, $nav - let mobileSidebarOpen = false - - const adjustMenu = init => { - const getAllWidth = ele => Array.from(ele).reduce((width, i) => width + i.offsetWidth, 0) - - if (init) { - const blogInfoWidth = getAllWidth(document.querySelector('#blog-info > a').children) - const menusWidth = getAllWidth(document.getElementById('menus').children) - headerContentWidth = blogInfoWidth + menusWidth - $nav = document.getElementById('nav') - } - - const hideMenuIndex = window.innerWidth <= 768 || headerContentWidth > $nav.offsetWidth - 120 - $nav.classList.toggle('hide-menu', hideMenuIndex) - } - - // 初始化header - const initAdjust = () => { - adjustMenu(true) - $nav.classList.add('show') - } - - // sidebar menus - const sidebarFn = { - open: () => { - btf.overflowPaddingR.add() - btf.animateIn(document.getElementById('menu-mask'), 'to_show 0.5s') - document.getElementById('sidebar-menus').classList.add('open') - mobileSidebarOpen = true - }, - close: () => { - btf.overflowPaddingR.remove() - btf.animateOut(document.getElementById('menu-mask'), 'to_hide 0.5s') - document.getElementById('sidebar-menus').classList.remove('open') - mobileSidebarOpen = false - } - } - - const syncAsideToSidebar = () => { - const asideContent = document.getElementById('aside-content') - const desktopAsideContainer = document.getElementById('content-inner') - const mobileAsideContainer = document.getElementById('sidebar-aside-wrapper') - - if (!(asideContent && desktopAsideContainer && mobileAsideContainer)) { - document.documentElement.classList.remove('mobile-aside-in-sidebar') - return - } - - const shouldMoveAside = window.innerWidth <= 900 - const targetContainer = shouldMoveAside ? mobileAsideContainer : desktopAsideContainer - - if (asideContent.parentNode !== targetContainer) { - targetContainer.appendChild(asideContent) - } - - document.documentElement.classList.toggle('mobile-aside-in-sidebar', shouldMoveAside) - } - - /** - * 首頁top_img底下的箭頭 - */ - const scrollDownInIndex = () => { - const handleScrollToDest = () => { - btf.scrollToDest(document.getElementById('content-inner').offsetTop, 300) - } - - const $scrollDownEle = document.getElementById('scroll-down') - $scrollDownEle && btf.addEventListenerPjax($scrollDownEle, 'click', handleScrollToDest) - } - - /** - * 代碼 - * 只適用於Hexo默認的代碼渲染 - */ - const addHighlightTool = () => { - const highLight = GLOBAL_CONFIG.highlight - if (!highLight) return - - const { highlightCopy, highlightLang, highlightHeightLimit, highlightFullpage, highlightMacStyle, plugin } = highLight - const isHighlightShrink = GLOBAL_CONFIG_SITE.isHighlightShrink - const isShowTool = highlightCopy || highlightLang || isHighlightShrink !== undefined || highlightFullpage || highlightMacStyle - const isNotHighlightJs = plugin !== 'highlight.js' - const isPrismjs = plugin === 'prismjs' - const $figureHighlight = isNotHighlightJs - ? Array.from(document.querySelectorAll('code[class*="language-"]')).map(code => code.parentElement) - : document.querySelectorAll('figure.highlight') - - if (!((isShowTool || highlightHeightLimit) && $figureHighlight.length)) return - - const highlightShrinkClass = isHighlightShrink === true ? 'closed' : '' - const highlightShrinkEle = isHighlightShrink !== undefined ? '' : '' - const highlightCopyEle = highlightCopy ? '' : '' - const highlightMacStyleEle = '
' - const highlightFullpageEle = highlightFullpage ? '' : '' - - const alertInfo = (ele, text) => { - if (GLOBAL_CONFIG.Snackbar !== undefined) { - btf.snackbarShow(text) - } else { - const newEle = document.createElement('div') - newEle.className = 'copy-notice' - newEle.textContent = text - document.body.appendChild(newEle) - - const buttonRect = ele.getBoundingClientRect() - const scrollTop = window.pageYOffset || document.documentElement.scrollTop - const scrollLeft = window.pageXOffset || document.documentElement.scrollLeft - - // X-axis boundary check - const halfWidth = newEle.offsetWidth / 2 - const centerLeft = buttonRect.left + scrollLeft + buttonRect.width / 2 - const finalLeft = Math.max(halfWidth + 10, Math.min(window.innerWidth - halfWidth - 10, centerLeft)) - - // Show tooltip below button if too close to top - const normalTop = buttonRect.top + scrollTop - 40 - const shouldShowBelow = buttonRect.top < 60 || normalTop < 10 - - const topValue = shouldShowBelow ? buttonRect.top + scrollTop + buttonRect.height + 10 : normalTop - - newEle.style.cssText = ` - top: ${topValue + 10}px; - left: ${finalLeft}px; - transform: translateX(-50%); - opacity: 0; - transition: opacity 0.3s ease, top 0.3s ease; - ` - - requestAnimationFrame(() => { - newEle.style.opacity = '1' - newEle.style.top = `${topValue}px` - }) - - setTimeout(() => { - newEle.style.opacity = '0' - newEle.style.top = `${topValue + 10}px` - setTimeout(() => { - newEle?.remove() - }, 300) - }, 800) - } - } - - const copy = async (text, ctx) => { - try { - await navigator.clipboard.writeText(text) - alertInfo(ctx, GLOBAL_CONFIG.copy.success) - } catch (err) { - console.error('Failed to copy: ', err) - alertInfo(ctx, GLOBAL_CONFIG.copy.noSupport) - } - } - - // click events - const highlightCopyFn = (ele, clickEle) => { - const $buttonParent = ele.parentNode - $buttonParent.classList.add('copy-true') - const preCodeSelector = isNotHighlightJs ? 'pre code' : 'table .code pre' - const codeElement = $buttonParent.querySelector(preCodeSelector) - if (!codeElement) return - copy(codeElement.innerText, clickEle) - $buttonParent.classList.remove('copy-true') - } - - const highlightShrinkFn = ele => ele.classList.toggle('closed') - - const codeFullpage = (item, clickEle) => { - const wrapEle = item.closest('figure.highlight') - const isFullpage = wrapEle.classList.toggle('code-fullpage') - - document.body.style.overflow = isFullpage ? 'hidden' : '' - clickEle.classList.toggle('fa-down-left-and-up-right-to-center', isFullpage) - clickEle.classList.toggle('fa-up-right-and-down-left-from-center', !isFullpage) - } - - const highlightToolsFn = e => { - const $target = e.target.classList - const currentElement = e.currentTarget - if ($target.contains('expand')) highlightShrinkFn(currentElement) - else if ($target.contains('copy-button')) highlightCopyFn(currentElement, e.target) - else if ($target.contains('fullpage-button')) codeFullpage(currentElement, e.target) - } - - const expandCode = e => e.currentTarget.classList.toggle('expand-done') - - // 獲取隱藏狀態下元素的真實高度 - const getActualHeight = item => { - if (item.offsetHeight > 0) return item.offsetHeight - const hiddenElements = new Map() - - const fix = () => { - let current = item - while (current !== document.body && current != null) { - if (window.getComputedStyle(current).display === 'none') { - hiddenElements.set(current, current.getAttribute('style') || '') - } - current = current.parentNode - } - - const style = 'visibility: hidden !important; display: block !important;' - hiddenElements.forEach((originalStyle, elem) => { - elem.setAttribute('style', originalStyle ? originalStyle + ';' + style : style) - }) - } - - const restore = () => { - hiddenElements.forEach((originalStyle, elem) => { - if (originalStyle === '') elem.removeAttribute('style') - else elem.setAttribute('style', originalStyle) - }) - } - - fix() - const height = item.offsetHeight - restore() - return height - } - - const createEle = (lang, item) => { - const fragment = document.createDocumentFragment() - - if (isShowTool) { - const hlTools = document.createElement('div') - hlTools.className = `highlight-tools ${highlightShrinkClass}` - hlTools.innerHTML = highlightMacStyleEle + highlightShrinkEle + lang + highlightCopyEle + highlightFullpageEle - btf.addEventListenerPjax(hlTools, 'click', highlightToolsFn) - fragment.appendChild(hlTools) - } - - if (highlightHeightLimit && getActualHeight(item) > highlightHeightLimit + 30) { - const ele = document.createElement('div') - ele.className = 'code-expand-btn' - ele.innerHTML = '' - btf.addEventListenerPjax(ele, 'click', expandCode) - fragment.appendChild(ele) - } - - isNotHighlightJs ? item.parentNode.insertBefore(fragment, item) : item.insertBefore(fragment, item.firstChild) - } - - $figureHighlight.forEach(item => { - let langName = '' - if (isNotHighlightJs) { - const newClassName = isPrismjs ? 'prismjs' : 'default' - btf.wrap(item, 'figure', { class: `highlight ${newClassName}` }) - } - - if (!highlightLang) { - createEle('', item) - return - } - - if (isNotHighlightJs) { - langName = isPrismjs ? item.getAttribute('data-language') || 'Code' : item.querySelector('code').getAttribute('class').replace('language-', '') - } else { - langName = item.getAttribute('class').split(' ')[1] - if (langName === 'plain' || langName === undefined) langName = 'Code' - } - createEle(`
${langName}
`, item) - }) - } - - /** - * PhotoFigcaption - */ - const addPhotoFigcaption = () => { - if (!GLOBAL_CONFIG.isPhotoFigcaption) return - document.querySelectorAll('#article-container img').forEach(item => { - const altValue = item.title || item.alt - if (!altValue) return - const ele = document.createElement('div') - ele.className = 'img-alt text-center' - ele.textContent = altValue - item.insertAdjacentElement('afterend', ele) - }) - } - - /** - * Lightbox - */ - const runLightbox = () => { - btf.loadLightbox(document.querySelectorAll('#article-container img:not(.no-lightbox)')) - } - - /** - * justified-gallery 圖庫排版 - */ - - const fetchUrl = async url => { - try { - const response = await fetch(url) - return await response.json() - } catch (error) { - console.error('Failed to fetch URL:', error) - return [] - } - } - - const runJustifiedGallery = (container, data, config) => { - const { isButton, limit, firstLimit, tabs } = config - - const dataLength = data.length - const maxGroupKey = Math.ceil((dataLength - firstLimit) / limit + 1) - - // Gallery configuration - const igConfig = { - gap: 5, - isConstantSize: true, - sizeRange: [150, 600], - // useResizeObserver: true, - // observeChildren: true, - useTransform: true - // useRecycle: false - } - - const ig = new InfiniteGrid.JustifiedInfiniteGrid(container, igConfig) - let isLayoutHidden = false - - // Utility functions - const sanitizeString = str => (str && str.replace(/"/g, '"')) || '' - - const createImageItem = item => { - const alt = item.alt ? `alt="${sanitizeString(item.alt)}"` : '' - const title = item.title ? `title="${sanitizeString(item.title)}"` : '' - return `
- -
` - } - - const getItems = (nextGroupKey, count, isFirst = false) => { - const startIndex = isFirst ? (nextGroupKey - 1) * count : (nextGroupKey - 2) * count + firstLimit - return data.slice(startIndex, startIndex + count).map(createImageItem) - } - - // Load more button - const addLoadMoreButton = container => { - const button = document.createElement('button') - button.innerHTML = `${GLOBAL_CONFIG.infinitegrid.buttonText}` - - button.addEventListener('click', () => { - button.remove() - btf.setLoading.add(container) - appendItems(ig.getGroups().length + 1, limit) - }, { once: true }) - - container.insertAdjacentElement('afterend', button) - } - - const appendItems = (nextGroupKey, count, isFirst) => { - ig.append(getItems(nextGroupKey, count, isFirst), nextGroupKey) - } - - // Event handlers - const handleRenderComplete = e => { - if (tabs) { - const parentNode = container.parentNode - if (isLayoutHidden) { - parentNode.style.visibility = 'visible' - } - if (container.offsetHeight === 0) { - parentNode.style.visibility = 'hidden' - isLayoutHidden = true - } - } - - const { updated, isResize, mounted } = e - if (!updated.length || !mounted.length || isResize) return - - btf.loadLightbox(container.querySelectorAll('img:not(.medium-zoom-image)')) - - if (ig.getGroups().length === maxGroupKey) { - btf.setLoading.remove(container) - !tabs && ig.off('renderComplete', handleRenderComplete) - return - } - - if (isButton) { - btf.setLoading.remove(container) - addLoadMoreButton(container) - } - } - - const handleRequestAppend = btf.debounce(e => { - const nextGroupKey = (+e.groupKey || 0) + 1 - - if (nextGroupKey === 1) appendItems(nextGroupKey, firstLimit, true) - else appendItems(nextGroupKey, limit) - - if (nextGroupKey === maxGroupKey) ig.off('requestAppend', handleRequestAppend) - }, 300) - - btf.setLoading.add(container) - ig.on('renderComplete', handleRenderComplete) - - if (isButton) { - appendItems(1, firstLimit, true) - } else { - ig.on('requestAppend', handleRequestAppend) - ig.renderItems() - } - - btf.addGlobalFn('pjaxSendOnce', () => ig.destroy()) - } - - const addJustifiedGallery = async (elements, tabs = false) => { - if (!elements.length) return - - const initGallery = async () => { - for (const element of elements) { - if (btf.isHidden(element) || element.classList.contains('loaded')) continue - - const config = { - isButton: element.getAttribute('data-button') === 'true', - limit: parseInt(element.getAttribute('data-limit'), 10), - firstLimit: parseInt(element.getAttribute('data-first'), 10), - tabs - } - - const container = element.firstElementChild - const content = container.textContent - container.textContent = '' - element.classList.add('loaded') - - try { - const data = element.getAttribute('data-type') === 'url' ? await fetchUrl(content) : JSON.parse(content) - runJustifiedGallery(container, data, config) - } catch (error) { - console.error('Gallery data parsing failed:', error) - } - } - } - - if (typeof InfiniteGrid === 'function') { - await initGallery() - } else { - await btf.getScript(GLOBAL_CONFIG.infinitegrid.js) - await initGallery() - } - } - - /** - * rightside scroll percent - */ - const rightsideScrollPercent = currentTop => { - const scrollPercent = btf.getScrollPercent(currentTop, document.body) - const goUpElement = document.getElementById('go-up') - - if (scrollPercent < 95) { - goUpElement.classList.add('show-percent') - goUpElement.querySelector('.scroll-percent').textContent = scrollPercent - } else { - goUpElement.classList.remove('show-percent') - } - } - - /** - * 滾動處理 - */ - const scrollFn = () => { - const $rightside = document.getElementById('rightside') - const innerHeight = window.innerHeight + 56 - let initTop = 0 - const $header = document.getElementById('page-header') - const isChatBtn = typeof chatBtn !== 'undefined' - const isShowPercent = GLOBAL_CONFIG.percent.rightside - - // 檢查文檔高度是否小於視窗高度 - const checkDocumentHeight = () => { - if (document.body.scrollHeight <= innerHeight) { - $rightside.classList.add('rightside-show') - return true - } - return false - } - - // 如果文檔高度小於視窗高度,直接返回 - if (checkDocumentHeight()) return - - // find the scroll direction - const scrollDirection = currentTop => { - const result = currentTop > initTop // true is down & false is up - initTop = currentTop - return result - } - - let flag = '' - const scrollTask = btf.throttle(() => { - const currentTop = window.scrollY || document.documentElement.scrollTop - const isDown = scrollDirection(currentTop) - if (currentTop > 56) { - if (flag === '') { - $header.classList.add('nav-fixed') - $rightside.classList.add('rightside-show') - } - - if (isDown) { - if (flag !== 'down') { - $header.classList.remove('nav-visible') - isChatBtn && window.chatBtn.hide() - flag = 'down' - } - } else { - if (flag !== 'up') { - $header.classList.add('nav-visible') - isChatBtn && window.chatBtn.show() - flag = 'up' - } - } - } else { - flag = '' - if (currentTop === 0) { - $header.classList.remove('nav-fixed', 'nav-visible') - } - $rightside.classList.remove('rightside-show') - } - - isShowPercent && rightsideScrollPercent(currentTop) - checkDocumentHeight() - }, 300) - - btf.addEventListenerPjax(window, 'scroll', scrollTask, { passive: true }) - } - - /** - * toc,anchor - */ - const scrollFnToDo = () => { - const isToc = GLOBAL_CONFIG_SITE.isToc - const isAnchor = GLOBAL_CONFIG.isAnchor - const $article = document.getElementById('article-container') - - if (!($article && (isToc || isAnchor))) return - - let $tocLink, $cardToc, autoScrollToc, $tocPercentage, isExpand - - if (isToc) { - const $cardTocLayout = document.getElementById('card-toc') - $cardToc = $cardTocLayout.querySelector('.toc-content') - $tocLink = $cardToc.querySelectorAll('.toc-link') - $tocPercentage = $cardTocLayout.querySelector('.toc-percentage') - isExpand = $cardToc.classList.contains('is-expand') - - // toc元素點擊 - const tocItemClickFn = e => { - const target = e.target.closest('.toc-link') - if (!target) return - - e.preventDefault() - btf.scrollToDest(btf.getEleTop(document.getElementById(decodeURI(target.getAttribute('href')).replace('#', ''))), 300) - if (window.innerWidth < 900) { - $cardTocLayout.classList.remove('open') - } - } - - btf.addEventListenerPjax($cardToc, 'click', tocItemClickFn) - - autoScrollToc = item => { - const sidebarHeight = $cardToc.clientHeight - const itemOffsetTop = item.offsetTop - const itemHeight = item.clientHeight - const scrollTop = $cardToc.scrollTop - const offset = itemOffsetTop - scrollTop - const middlePosition = (sidebarHeight - itemHeight) / 2 - - if (offset !== middlePosition) { - $cardToc.scrollTop = scrollTop + (offset - middlePosition) - } - } - - // 處理 hexo-blog-encrypt 事件 - $cardToc.style.display = 'block' - } - - // find head position & add active class - const $articleList = $article.querySelectorAll('h1,h2,h3,h4,h5,h6') - let detectItem = '' - - // Optimization: Cache header positions - let headerList = [] - const updateHeaderPositions = () => { - headerList = Array.from($articleList).map(ele => ({ - ele, - top: btf.getEleTop(ele), - id: ele.id - })) - } - - updateHeaderPositions() - btf.addEventListenerPjax(window, 'resize', btf.throttle(updateHeaderPositions, 200)) - - const findHeadPosition = top => { - if (top === 0) return false - - let currentId = '' - let currentIndex = '' - - for (let i = 0; i < headerList.length; i++) { - const item = headerList[i] - if (top > item.top - 80) { - currentId = item.id ? '#' + encodeURI(item.id) : '' - currentIndex = i - } else { - break - } - } - - if (detectItem === currentIndex) return - - if (isAnchor) btf.updateAnchor(currentId) - - detectItem = currentIndex - - if (isToc) { - $cardToc.querySelectorAll('.active').forEach(i => i.classList.remove('active')) - - if (currentId) { - const currentActive = $tocLink[currentIndex] - currentActive.classList.add('active') - - setTimeout(() => autoScrollToc(currentActive), 0) - - if (!isExpand) { - let parent = currentActive.parentNode - while (!parent.matches('.toc')) { - if (parent.matches('li')) parent.classList.add('active') - parent = parent.parentNode - } - } - } - } - } - - // main of scroll - const tocScrollFn = btf.throttle(() => { - const currentTop = window.scrollY || document.documentElement.scrollTop - if (isToc && GLOBAL_CONFIG.percent.toc) { - $tocPercentage.textContent = btf.getScrollPercent(currentTop, $article) - } - findHeadPosition(currentTop) - }, 100) - - btf.addEventListenerPjax(window, 'scroll', tocScrollFn, { passive: true }) - } - - const handleThemeChange = mode => { - const globalFn = window.globalFn || {} - const themeChange = globalFn.themeChange || {} - if (!themeChange) { - return - } - - Object.keys(themeChange).forEach(key => { - const themeChangeFn = themeChange[key] - if (['disqus', 'disqusjs'].includes(key)) { - setTimeout(() => themeChangeFn(mode), 300) - } else { - themeChangeFn(mode) - } - }) - } - - /** - * Rightside - */ - const rightSideFn = { - readmode: () => { // read mode - const $body = document.body - const newEle = document.createElement('button') - - const exitReadMode = () => { - $body.classList.remove('read-mode') - newEle.remove() - newEle.removeEventListener('click', exitReadMode) - } - - $body.classList.add('read-mode') - newEle.type = 'button' - newEle.className = 'exit-readmode' - newEle.innerHTML = '' - newEle.addEventListener('click', exitReadMode) - $body.appendChild(newEle) - }, - darkmode: () => { // switch between light and dark mode - const willChangeMode = document.documentElement.getAttribute('data-theme') === 'dark' ? 'light' : 'dark' - if (willChangeMode === 'dark') { - btf.activateDarkMode() - GLOBAL_CONFIG.Snackbar !== undefined && btf.snackbarShow(GLOBAL_CONFIG.Snackbar.day_to_night) - } else { - btf.activateLightMode() - GLOBAL_CONFIG.Snackbar !== undefined && btf.snackbarShow(GLOBAL_CONFIG.Snackbar.night_to_day) - } - btf.saveToLocal.set('theme', willChangeMode, 2) - handleThemeChange(willChangeMode) - }, - 'rightside-config': item => { // Show or hide rightside-hide-btn - const hideLayout = item.firstElementChild - if (hideLayout.classList.contains('show')) { - hideLayout.classList.add('status') - setTimeout(() => { - hideLayout.classList.remove('status') - }, 300) - } - - hideLayout.classList.toggle('show') - }, - 'go-up': () => { // Back to top - btf.scrollToDest(0, 500) - }, - 'hide-aside-btn': () => { // Hide aside - const $htmlDom = document.documentElement.classList - const saveStatus = $htmlDom.contains('hide-aside') ? 'show' : 'hide' - btf.saveToLocal.set('aside-status', saveStatus, 2) - $htmlDom.toggle('hide-aside') - }, - 'mobile-toc-button': (p, item) => { // Show mobile toc - const tocEle = document.getElementById('card-toc') - tocEle.style.transition = 'transform 0.3s ease-in-out' - - const tocEleHeight = tocEle.clientHeight - const btData = item.getBoundingClientRect() - - const tocEleBottom = window.innerHeight - btData.bottom - 30 - if (tocEleHeight > tocEleBottom) { - tocEle.style.transformOrigin = `right ${tocEleHeight - tocEleBottom - btData.height / 2}px` - } - - tocEle.classList.toggle('open') - tocEle.addEventListener('transitionend', () => { - tocEle.style.cssText = '' - }, { once: true }) - }, - 'chat-btn': () => { // Show chat - window.chatBtnFn() - }, - translateLink: () => { // switch between traditional and simplified chinese - window.translateFn.translatePage() - } - } - - document.getElementById('rightside').addEventListener('click', e => { - const $target = e.target.closest('[id]') - if ($target && rightSideFn[$target.id]) { - rightSideFn[$target.id](e.currentTarget, $target) - } - }) - - /** - * menu - * 側邊欄sub-menu 展開/收縮 - */ - const clickFnOfSubMenu = () => { - const handleClickOfSubMenu = e => { - const target = e.target.closest('.site-page.group') - if (!target) return - target.classList.toggle('hide') - } - - const menusItems = document.querySelector('#sidebar-menus .menus_items') - menusItems && menusItems.addEventListener('click', handleClickOfSubMenu) - } - - /** - * 手机端目录点击 - */ - const openMobileMenu = () => { - const toggleMenu = document.getElementById('toggle-menu') - if (!toggleMenu) return - btf.addEventListenerPjax(toggleMenu, 'click', () => { sidebarFn.open() }) - } - - /** - * 複製時加上版權信息 - */ - const addCopyright = () => { - const { limitCount, languages } = GLOBAL_CONFIG.copyright - - const handleCopy = e => { - e.preventDefault() - const copyFont = window.getSelection(0).toString() - let textFont = copyFont - if (copyFont.length > limitCount) { - textFont = `${copyFont}\n\n\n${languages.author}\n${languages.link}${window.location.href}\n${languages.source}\n${languages.info}` - } - if (e.clipboardData) { - return e.clipboardData.setData('text', textFont) - } else { - return window.clipboardData.setData('text', textFont) - } - } - - document.body.addEventListener('copy', handleCopy) - } - - /** - * 網頁運行時間 - */ - const addRuntime = () => { - const $runtimeCount = document.getElementById('runtimeshow') - if ($runtimeCount) { - const publishDate = $runtimeCount.getAttribute('data-publishDate') - $runtimeCount.textContent = `${btf.diffDate(publishDate)} ${GLOBAL_CONFIG.runtime}` - } - } - - /** - * 最後一次更新時間 - */ - const addLastPushDate = () => { - const $lastPushDateItem = document.getElementById('last-push-date') - if ($lastPushDateItem) { - const lastPushDate = $lastPushDateItem.getAttribute('data-lastPushDate') - $lastPushDateItem.textContent = btf.diffDate(lastPushDate, true) - } - } - - /** - * table overflow - */ - const addTableWrap = () => { - const $table = document.querySelectorAll('#article-container table') - if (!$table.length) return - - $table.forEach(item => { - if (!item.closest('.highlight')) { - btf.wrap(item, 'div', { class: 'table-wrap' }) - } - }) - } - - /** - * tag-hide - */ - const clickFnOfTagHide = () => { - const hideButtons = document.querySelectorAll('#article-container .hide-button') - if (!hideButtons.length) return - hideButtons.forEach(item => item.addEventListener('click', e => { - const currentTarget = e.currentTarget - currentTarget.classList.add('open') - addJustifiedGallery(currentTarget.nextElementSibling.querySelectorAll('.gallery-container')) - }, { once: true })) - } - - const tabsFn = () => { - const navTabsElements = document.querySelectorAll('#article-container .tabs') - if (!navTabsElements.length) return - - const setActiveClass = (elements, activeIndex) => { - elements.forEach((el, index) => { - el.classList.toggle('active', index === activeIndex) - }) - } - - const handleNavClick = e => { - const target = e.target.closest('button') - if (!target || target.classList.contains('active')) return - - const navItems = [...e.currentTarget.children] - const tabContents = [...e.currentTarget.nextElementSibling.children] - const indexOfButton = navItems.indexOf(target) - setActiveClass(navItems, indexOfButton) - e.currentTarget.classList.remove('no-default') - setActiveClass(tabContents, indexOfButton) - addJustifiedGallery(tabContents[indexOfButton].querySelectorAll('.gallery-container'), true) - } - - const handleToTopClick = tabElement => e => { - if (e.target.closest('button')) { - btf.scrollToDest(btf.getEleTop(tabElement), 300) - } - } - - navTabsElements.forEach(tabElement => { - btf.addEventListenerPjax(tabElement.firstElementChild, 'click', handleNavClick) - btf.addEventListenerPjax(tabElement.lastElementChild, 'click', handleToTopClick(tabElement)) - }) - } - - const toggleCardCategory = () => { - const cardCategory = document.querySelector('#aside-cat-list.expandBtn') - if (!cardCategory) return - - const handleToggleBtn = e => { - const target = e.target - if (target.nodeName === 'I') { - e.preventDefault() - target.parentNode.classList.toggle('expand') - } - } - btf.addEventListenerPjax(cardCategory, 'click', handleToggleBtn, true) - } - - const addPostOutdateNotice = () => { - const ele = document.getElementById('post-outdate-notice') - if (!ele) return - - const { limitDay, messagePrev, messageNext, postUpdate } = JSON.parse(ele.getAttribute('data')) - const diffDay = btf.diffDate(postUpdate) - if (diffDay >= limitDay) { - ele.textContent = `${messagePrev} ${diffDay} ${messageNext}` - ele.hidden = false - } - } - - const lazyloadImg = () => { - window.lazyLoadInstance = new LazyLoad({ - elements_selector: 'img', - threshold: 0, - data_src: 'lazy-src' - }) - - btf.addGlobalFn('pjaxComplete', () => { - window.lazyLoadInstance.update() - }, 'lazyload') - } - - const relativeDate = selector => { - selector.forEach(item => { - item.textContent = btf.diffDate(item.getAttribute('datetime'), true) - item.style.display = 'inline' - }) - } - - const justifiedIndexPostUI = () => { - const recentPostsElement = document.getElementById('recent-posts') - if (!(recentPostsElement && recentPostsElement.classList.contains('masonry'))) return - - const init = () => { - const masonryItem = new InfiniteGrid.MasonryInfiniteGrid('.recent-post-items', { - gap: { horizontal: 10, vertical: 20 }, - useTransform: true, - useResizeObserver: true - }) - masonryItem.renderItems() - btf.addGlobalFn('pjaxCompleteOnce', () => { masonryItem.destroy() }, 'removeJustifiedIndexPostUI') - } - - typeof InfiniteGrid === 'function' ? init() : btf.getScript(`${GLOBAL_CONFIG.infinitegrid.js}`).then(init) - } - - const unRefreshFn = () => { - window.addEventListener('resize', () => { - adjustMenu(false) - syncAsideToSidebar() - mobileSidebarOpen && btf.isHidden(document.getElementById('toggle-menu')) && sidebarFn.close() - }) - - const menuMask = document.getElementById('menu-mask') - menuMask && menuMask.addEventListener('click', () => { sidebarFn.close() }) - - const sidebarMenus = document.getElementById('sidebar-menus') - sidebarMenus && sidebarMenus.addEventListener('click', e => { - const clickedLink = e.target.closest('a') - if (!clickedLink) return - sidebarFn.close() - }) - - clickFnOfSubMenu() - GLOBAL_CONFIG.islazyloadPlugin && lazyloadImg() - GLOBAL_CONFIG.copyright !== undefined && addCopyright() - - if (GLOBAL_CONFIG.autoDarkmode) { - window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', e => { - if (btf.saveToLocal.get('theme') !== undefined) return - e.matches ? handleThemeChange('dark') : handleThemeChange('light') - }) - } - } - - const forPostFn = () => { - addHighlightTool() - addPhotoFigcaption() - addJustifiedGallery(document.querySelectorAll('#article-container .gallery-container')) - runLightbox() - scrollFnToDo() - addTableWrap() - clickFnOfTagHide() - tabsFn() - } - - const refreshFn = () => { - initAdjust() - syncAsideToSidebar() - justifiedIndexPostUI() - - if (GLOBAL_CONFIG_SITE.pageType === 'post') { - addPostOutdateNotice() - GLOBAL_CONFIG.relativeDate.post && relativeDate(document.querySelectorAll('#post-meta time')) - } else { - GLOBAL_CONFIG.relativeDate.homepage && relativeDate(document.querySelectorAll('#recent-posts time')) - GLOBAL_CONFIG.runtime && addRuntime() - addLastPushDate() - toggleCardCategory() - } - - GLOBAL_CONFIG_SITE.pageType === 'home' && scrollDownInIndex() - scrollFn() - - forPostFn() - GLOBAL_CONFIG_SITE.pageType !== 'shuoshuo' && btf.switchComments(document) - openMobileMenu() - } - - btf.addGlobalFn('pjaxComplete', refreshFn, 'refreshFn') - refreshFn() - unRefreshFn() - - // 處理 hexo-blog-encrypt 事件 - window.addEventListener('hexo-blog-decrypt', e => { - forPostFn() - window.translateFn.translateInitialization() - Object.values(window.globalFn.encrypt).forEach(fn => { - fn() - }) - }) -}) diff --git a/static/js/search/algolia.js b/static/js/search/algolia.js deleted file mode 100644 index ed69d2e..0000000 --- a/static/js/search/algolia.js +++ /dev/null @@ -1,562 +0,0 @@ -window.addEventListener('load', () => { - const { algolia } = GLOBAL_CONFIG - const { appId, apiKey, indexName, hitsPerPage = 5, languages } = algolia - - if (!appId || !apiKey || !indexName) { - return console.error('Algolia setting is invalid!') - } - - const $searchMask = document.getElementById('search-mask') - const $searchDialog = document.querySelector('#algolia-search .search-dialog') - - const animateElements = show => { - const action = show ? 'animateIn' : 'animateOut' - const maskAnimation = show ? 'to_show 0.5s' : 'to_hide 0.5s' - const dialogAnimation = show ? 'titleScale 0.5s' : 'search_close .5s' - btf[action]($searchMask, maskAnimation) - btf[action]($searchDialog, dialogAnimation) - } - - const fixSafariHeight = () => { - if (window.innerWidth < 768) { - $searchDialog.style.setProperty('--search-height', `${window.innerHeight}px`) - } - } - - const openSearch = () => { - btf.overflowPaddingR.add() - animateElements(true) - showLoading(false) - - setTimeout(() => { - const searchInput = document.querySelector('#algolia-search-input .ais-SearchBox-input') - if (searchInput) searchInput.focus() - }, 100) - - const handleEscape = event => { - if (event.code === 'Escape') { - closeSearch() - document.removeEventListener('keydown', handleEscape) - } - } - - document.addEventListener('keydown', handleEscape) - fixSafariHeight() - window.addEventListener('resize', fixSafariHeight) - } - - const closeSearch = () => { - btf.overflowPaddingR.remove() - animateElements(false) - window.removeEventListener('resize', fixSafariHeight) - } - - const searchClickFn = () => { - btf.addEventListenerPjax(document.querySelector('#search-button > .search'), 'click', openSearch) - } - - const searchFnOnce = () => { - $searchMask.addEventListener('click', closeSearch) - document.querySelector('#algolia-search .search-close-button').addEventListener('click', closeSearch) - } - - const cutContent = content => { - if (!content) return '' - - let contentStr = '' - if (typeof content === 'string') { - contentStr = content.trim() - } else if (typeof content === 'object') { - if (content.value !== undefined) { - contentStr = String(content.value).trim() - if (!contentStr) return '' - } else if (content.matchedWords || content.matchLevel || content.fullyHighlighted !== undefined) { - return '' - } else { - try { - contentStr = JSON.stringify(content).trim() - if (contentStr === '{}' || contentStr === '[]' || contentStr === '""') { - return '' - } - } catch (e) { - return '' - } - } - } else if (content.toString && typeof content.toString === 'function') { - contentStr = content.toString().trim() - if (contentStr === '[object Object]' || contentStr === '[object Array]') { - return '' - } - } else { - return '' - } - - const firstOccur = contentStr.indexOf('') - let start = firstOccur - 30 - let end = firstOccur + 120 - let pre = '' - let post = '' - - if (start <= 0) { - start = 0 - end = 140 - } else { - pre = '...' - } - - if (end > contentStr.length) { - end = contentStr.length - } else { - post = '...' - } - - // Ensure we don't cut off HTML tags in the middle - let substr = contentStr.substring(start, end) - - // Handle tag completeness - // Check for incomplete opening tags at the beginning - const firstCloseBracket = substr.indexOf('>') - const firstOpenBracket = substr.indexOf('<') - - // If there's a closing bracket but no opening bracket before it, we've cut a tag - if (firstCloseBracket !== -1 && (firstOpenBracket === -1 || firstCloseBracket < firstOpenBracket)) { - substr = substr.substring(firstCloseBracket + 1) - } - - // Check for incomplete closing tags at the end - const lastOpenBracket = substr.lastIndexOf('<') - const lastCloseBracket = substr.lastIndexOf('>') - - // If there's an opening bracket after the last closing bracket, we've cut a tag - if (lastOpenBracket !== -1 && lastOpenBracket > lastCloseBracket) { - substr = substr.substring(0, lastOpenBracket) - } - - // Balance tags in the substring - const tagStack = [] - let balancedStr = '' - let i = 0 - - while (i < substr.length) { - if (substr[i] === '<') { - // Check if it's a closing tag - if (substr[i + 1] === '/') { - const closeTagEnd = substr.indexOf('>', i) - if (closeTagEnd !== -1) { - const closeTagName = substr.substring(i + 2, closeTagEnd) - // Remove matching opening tag from stack - for (let j = tagStack.length - 1; j >= 0; j--) { - if (tagStack[j] === closeTagName) { - tagStack.splice(j, 1) - break - } - } - balancedStr += substr.substring(i, closeTagEnd + 1) - i = closeTagEnd + 1 - continue - } - } else if (substr.substr(i, 2) === '', i) !== -1 && substr.indexOf('/>', i) < substr.indexOf('>', i))) { - const tagEnd = substr.indexOf('>', i) - if (tagEnd !== -1) { - balancedStr += substr.substring(i, tagEnd + 1) - i = tagEnd + 1 - continue - } - } else { - const tagEnd = substr.indexOf('>', i) - if (tagEnd !== -1) { - const tagName = substr.substring(i + 1, (substr.indexOf(' ', i) > -1 && substr.indexOf(' ', i) < tagEnd) - ? substr.indexOf(' ', i) - : tagEnd).split(/\s/)[0] - tagStack.push(tagName) - balancedStr += substr.substring(i, tagEnd + 1) - i = tagEnd + 1 - continue - } - } - } - balancedStr += substr[i] - i++ - } - - // Close any unclosed tags - while (tagStack.length > 0) { - const tagName = tagStack.pop() - balancedStr += `` - } - - // If we removed content from the beginning, add prefix - if (start > 0 || pre) { - const actualFirstOpenBracket = contentStr.indexOf('<', start > 0 ? start - 30 : 0) - const actualFirstMark = contentStr.indexOf('', start > 0 ? start - 30 : 0) - - if (actualFirstOpenBracket !== -1 && - (actualFirstMark === -1 || actualFirstOpenBracket < actualFirstMark)) { - pre = '...' - } - } - - substr = balancedStr - return `${pre}${substr}${post}` - } - - // Helper function to handle Algolia highlight results - const extractHighlightValue = highlightObj => { - if (!highlightObj) return '' - - if (typeof highlightObj === 'string') { - return highlightObj.trim() - } - - if (typeof highlightObj === 'object' && highlightObj.value !== undefined) { - return String(highlightObj.value).trim() - } - - return '' - } - - // Initialize Algolia client - let searchClient - - if (window['algoliasearch/lite'] && typeof window['algoliasearch/lite'].liteClient === 'function') { - searchClient = window['algoliasearch/lite'].liteClient(appId, apiKey) - } else if (typeof window.algoliasearch === 'function') { - searchClient = window.algoliasearch(appId, apiKey) - } else { - return console.error('Algolia search client not found!') - } - - if (!searchClient) { - return console.error('Failed to initialize Algolia search client') - } - - // Search state - let currentQuery = '' - - // Show loading state - const showLoading = show => { - const loadingIndicator = document.getElementById('loading-status') - if (loadingIndicator) { - loadingIndicator.hidden = !show - } - } - - // Cache frequently used elements - const elements = { - get searchInput () { return document.querySelector('#algolia-search-input .ais-SearchBox-input') }, - get hits () { return document.getElementById('algolia-hits') }, - get hitsEmpty () { return document.getElementById('algolia-hits-empty') }, - get hitsList () { return document.querySelector('#algolia-hits .ais-Hits-list') }, - get hitsWrapper () { return document.querySelector('#algolia-hits .ais-Hits') }, - get pagination () { return document.getElementById('algolia-pagination') }, - get paginationList () { return document.querySelector('#algolia-pagination .ais-Pagination-list') }, - get stats () { return document.querySelector('#algolia-info .ais-Stats-text') }, - } - - // Show/hide search results area - const toggleResultsVisibility = hasResults => { - elements.pagination.style.display = hasResults ? '' : 'none' - elements.stats.style.display = hasResults ? '' : 'none' - } - - // Render search results - const renderHits = (hits, query, page = 0) => { - if (hits.length === 0 && query) { - elements.hitsEmpty.textContent = languages.hits_empty.replace(/\$\{query}/, query) - elements.hitsEmpty.style.display = '' - elements.hitsWrapper.style.display = 'none' - elements.stats.style.display = 'none' - return - } - - elements.hitsEmpty.style.display = 'none' - - const hitsHTML = hits.map((hit, index) => { - const itemNumber = page * hitsPerPage + index + 1 - const link = hit.permalink || (GLOBAL_CONFIG.root + hit.path) - const result = hit._highlightResult || hit - - // Content extraction - let content = '' - try { - if (result.contentStripTruncate) { - content = cutContent(result.contentStripTruncate) - } else if (result.contentStrip) { - content = cutContent(result.contentStrip) - } else if (result.content) { - content = cutContent(result.content) - } else if (hit.contentStripTruncate) { - content = cutContent(hit.contentStripTruncate) - } else if (hit.contentStrip) { - content = cutContent(hit.contentStrip) - } else if (hit.content) { - content = cutContent(hit.content) - } - } catch (error) { - content = '' - } - - // Title handling - let title = 'no-title' - try { - if (result.title) { - title = extractHighlightValue(result.title) || 'no-title' - } else if (hit.title) { - title = extractHighlightValue(hit.title) || 'no-title' - } - - if (!title || title === 'no-title') { - if (typeof hit.title === 'string' && hit.title.trim()) { - title = hit.title.trim() - } else if (hit.title && typeof hit.title === 'object' && hit.title.value) { - title = String(hit.title.value).trim() || 'no-title' - } else { - title = 'no-title' - } - } - } catch (error) { - title = 'no-title' - } - - return ` -
  • - - ${title} - ${content ? `
    ${content}
    ` : ''} -
    -
  • ` - }).join('') - - elements.hitsList.innerHTML = hitsHTML - elements.hitsWrapper.style.display = query ? '' : 'none' - - if (hits.length > 0) { - elements.stats.style.display = '' - } - } - - // Render pagination - const renderPagination = (page, nbPages) => { - if (nbPages <= 1) { - elements.pagination.style.display = 'none' - elements.paginationList.innerHTML = '' - return - } - - elements.pagination.style.display = 'block' - - const isFirstPage = page === 0 - const isLastPage = page === nbPages - 1 - - // Responsive page display - const isMobile = window.innerWidth < 768 - const maxVisiblePages = isMobile ? 3 : 5 - let startPage = Math.max(0, page - Math.floor(maxVisiblePages / 2)) - const endPage = Math.min(nbPages - 1, startPage + maxVisiblePages - 1) - - // Adjust starting page to maintain max visible pages - if (endPage - startPage + 1 < maxVisiblePages) { - startPage = Math.max(0, endPage - maxVisiblePages + 1) - } - - let pagesHTML = '' - - // Only add ellipsis and first page when there are many pages - if (nbPages > maxVisiblePages && startPage > 0) { - pagesHTML += ` -
  • - 1 -
  • ` - if (startPage > 1) { - pagesHTML += ` -
  • - ... -
  • ` - } - } - - // Add middle page numbers - for (let i = startPage; i <= endPage; i++) { - const isSelected = i === page - if (isSelected) { - pagesHTML += ` -
  • - ${i + 1} -
  • ` - } else { - pagesHTML += ` -
  • - ${i + 1} -
  • ` - } - } - - // Only add ellipsis and last page when there are many pages - if (nbPages > maxVisiblePages && endPage < nbPages - 1) { - if (endPage < nbPages - 2) { - pagesHTML += ` -
  • - ... -
  • ` - } - pagesHTML += ` -
  • - ${nbPages} -
  • ` - } - - if (nbPages > 1) { - elements.paginationList.innerHTML = ` -
  • - ${isFirstPage - ? '' - : `` - } -
  • - ${pagesHTML} -
  • - ${isLastPage - ? '' - : `` - } -
  • ` - elements.pagination.style.display = currentQuery ? '' : 'none' - } else { - elements.pagination.style.display = 'none' - } - } - - // Render statistics - const renderStats = (nbHits, processingTimeMS, query) => { - if (query) { - const stats = languages.hits_stats - .replace(/\$\{hits}/, nbHits) - .replace(/\$\{time}/, processingTimeMS) - elements.stats.innerHTML = `
    ${stats}` - elements.stats.style.display = '' - } else { - elements.stats.style.display = 'none' - } - } - - // Perform search - const performSearch = async (query, page = 0) => { - if (!query.trim()) { - currentQuery = '' - renderHits([], '', 0) - renderPagination(0, 0) - renderStats(0, 0, '') - toggleResultsVisibility(false) - return - } - - showLoading(true) - currentQuery = query - - try { - let result - - if (searchClient && typeof searchClient.search === 'function') { - // v5 multi-index search - const searchResult = await searchClient.search([{ - indexName, - query, - params: { - page, - hitsPerPage, - highlightPreTag: '', - highlightPostTag: '', - attributesToHighlight: ['title', 'content', 'contentStrip', 'contentStripTruncate'] - } - }]) - result = searchResult.results[0] - } else if (searchClient && typeof searchClient.initIndex === 'function') { - // v4 single-index search - const index = searchClient.initIndex(indexName) - result = await index.search(query, { - page, - hitsPerPage, - highlightPreTag: '', - highlightPostTag: '', - attributesToHighlight: ['title', 'content', 'contentStrip', 'contentStripTruncate'] - }) - } else { - throw new Error('Algolia: No compatible search method available') - } - - renderHits(result.hits || [], query, page) - - const actualNbPages = result.nbHits <= hitsPerPage ? 1 : (result.nbPages || 0) - renderPagination(page, actualNbPages) - renderStats(result.nbHits || 0, result.processingTimeMS || 0, query) - - const hasResults = result.hits && result.hits.length > 0 - toggleResultsVisibility(hasResults) - - // Refresh Pjax links - if (window.pjax) { - window.pjax.refresh(document.getElementById('algolia-hits')) - } - } catch (error) { - console.error('Algolia search error:', error) - renderHits([], query, page) - renderPagination(0, 0) - renderStats(0, 0, query) - } finally { - showLoading(false) - } - } - - // Debounced search - let searchTimeout - const debouncedSearch = (query, delay = 300) => { - clearTimeout(searchTimeout) - searchTimeout = setTimeout(() => performSearch(query), delay) - } - - // Initialize search box and events - const initializeSearch = () => { - showLoading(false) - - if (elements.searchInput) { - elements.searchInput.addEventListener('input', e => { - const query = e.target.value - debouncedSearch(query) - }) - } - - const searchForm = document.querySelector('#algolia-search-input .ais-SearchBox-form') - if (searchForm) { - searchForm.addEventListener('submit', e => { - e.preventDefault() - const query = elements.searchInput.value - performSearch(query) - }) - } - - // Pagination event delegation - elements.pagination.addEventListener('click', e => { - e.preventDefault() - const link = e.target.closest('a[data-page]') - if (link) { - const page = parseInt(link.dataset.page, 10) - if (!isNaN(page) && currentQuery) { - performSearch(currentQuery, page) - } - } - }) - - // Initial state - toggleResultsVisibility(false) - } - - // Initialize - initializeSearch() - searchClickFn() - searchFnOnce() - - window.addEventListener('pjax:complete', () => { - if (!btf.isHidden($searchMask)) closeSearch() - searchClickFn() - }) -}) diff --git a/static/js/search/local-search.js b/static/js/search/local-search.js deleted file mode 100644 index f2ccb06..0000000 --- a/static/js/search/local-search.js +++ /dev/null @@ -1,567 +0,0 @@ -/** - * Refer to hexo-generator-searchdb - * https://github.com/next-theme/hexo-generator-searchdb/blob/main/dist/search.js - * Modified by hexo-theme-butterfly - */ - -class LocalSearch { - constructor ({ - path = '', - unescape = false, - top_n_per_article = 1 - }) { - this.path = path - this.unescape = unescape - this.top_n_per_article = top_n_per_article - this.isfetched = false - this.datas = null - } - - getIndexByWord (words, text, caseSensitive = false) { - const index = [] - const included = new Set() - - if (!caseSensitive) { - text = text.toLowerCase() - } - words.forEach(word => { - if (this.unescape) { - const div = document.createElement('div') - div.innerText = word - word = div.innerHTML - } - const wordLen = word.length - if (wordLen === 0) return - let startPosition = 0 - let position = -1 - if (!caseSensitive) { - word = word.toLowerCase() - } - while ((position = text.indexOf(word, startPosition)) > -1) { - index.push({ position, word }) - included.add(word) - startPosition = position + wordLen - } - }) - // Sort index by position of keyword - index.sort((left, right) => { - if (left.position !== right.position) { - return left.position - right.position - } - return right.word.length - left.word.length - }) - return [index, included] - } - - // Merge hits into slices - mergeIntoSlice (start, end, index) { - let item = index[0] - let { position, word } = item - const hits = [] - const count = new Set() - while (position + word.length <= end && index.length !== 0) { - count.add(word) - hits.push({ - position, - length: word.length - }) - const wordEnd = position + word.length - - // Move to next position of hit - index.shift() - while (index.length !== 0) { - item = index[0] - position = item.position - word = item.word - if (wordEnd > position) { - index.shift() - } else { - break - } - } - } - return { - hits, - start, - end, - count: count.size - } - } - - // Highlight title and content - highlightKeyword (val, slice) { - let result = '' - let index = slice.start - for (const { position, length } of slice.hits) { - result += val.substring(index, position) - index = position + length - result += `${val.substr(position, length)}` - } - result += val.substring(index, slice.end) - return result - } - - getResultItems (keywords) { - const resultItems = [] - this.datas.forEach(({ title, content, url }) => { - // The number of different keywords included in the article. - const [indexOfTitle, keysOfTitle] = this.getIndexByWord(keywords, title) - const [indexOfContent, keysOfContent] = this.getIndexByWord(keywords, content) - const includedCount = new Set([...keysOfTitle, ...keysOfContent]).size - - // Show search results - const hitCount = indexOfTitle.length + indexOfContent.length - if (hitCount === 0) return - - const slicesOfTitle = [] - if (indexOfTitle.length !== 0) { - slicesOfTitle.push(this.mergeIntoSlice(0, title.length, indexOfTitle)) - } - - let slicesOfContent = [] - while (indexOfContent.length !== 0) { - const item = indexOfContent[0] - const { position } = item - // Cut out 120 characters. The maxlength of .search-input is 80. - const start = Math.max(0, position - 20) - const end = Math.min(content.length, position + 100) - slicesOfContent.push(this.mergeIntoSlice(start, end, indexOfContent)) - } - - // Sort slices in content by included keywords' count and hits' count - slicesOfContent.sort((left, right) => { - if (left.count !== right.count) { - return right.count - left.count - } else if (left.hits.length !== right.hits.length) { - return right.hits.length - left.hits.length - } - return left.start - right.start - }) - - // Select top N slices in content - const upperBound = parseInt(this.top_n_per_article, 10) - if (upperBound >= 0) { - slicesOfContent = slicesOfContent.slice(0, upperBound) - } - - let resultItem = '' - - url = new URL(url, location.origin) - url.searchParams.append('highlight', keywords.join(' ')) - - if (slicesOfTitle.length !== 0) { - resultItem += `
  • ${this.highlightKeyword(title, slicesOfTitle[0])}` - } else { - resultItem += `
  • ${title}` - } - - slicesOfContent.forEach(slice => { - resultItem += `

    ${this.highlightKeyword(content, slice)}...

    ` - }) - - resultItem += '
  • ' - resultItems.push({ - item: resultItem, - id: resultItems.length, - hitCount, - includedCount - }) - }) - return resultItems - } - - fetchData () { - const isXml = !this.path.endsWith('json') - fetch(this.path) - .then(response => response.text()) - .then(res => { - // Get the contents from search data - this.isfetched = true - this.datas = isXml - ? [...new DOMParser().parseFromString(res, 'text/xml').querySelectorAll('entry')].map(element => ({ - title: element.querySelector('title').textContent, - content: element.querySelector('content').textContent, - url: element.querySelector('url').textContent - })) - : JSON.parse(res) - // Only match articles with non-empty titles - this.datas = this.datas.filter(data => data.title).map(data => { - data.title = data.title.trim() - data.content = data.content ? data.content.trim().replace(/<[^>]+>/g, '') : '' - data.url = decodeURIComponent(data.url).replace(/\/{2,}/g, '/') - return data - }) - // Remove loading animation - window.dispatchEvent(new Event('search:loaded')) - }) - } - - // Highlight by wrapping node in mark elements with the given class name - highlightText (node, slice, className) { - const val = node.nodeValue - let index = slice.start - const children = [] - for (const { position, length } of slice.hits) { - const text = document.createTextNode(val.substring(index, position)) - index = position + length - const mark = document.createElement('mark') - mark.className = className - mark.appendChild(document.createTextNode(val.substr(position, length))) - children.push(text, mark) - } - node.nodeValue = val.substring(index, slice.end) - children.forEach(element => { - node.parentNode.insertBefore(element, node) - }) - } - - // Highlight the search words provided in the url in the text - highlightSearchWords (body) { - const params = new URL(location.href).searchParams.get('highlight') - const keywords = params ? params.split(' ') : [] - if (!keywords.length || !body) return - const walk = document.createTreeWalker(body, NodeFilter.SHOW_TEXT, null) - const allNodes = [] - while (walk.nextNode()) { - if (!walk.currentNode.parentNode.matches('button, select, textarea, .mermaid')) allNodes.push(walk.currentNode) - } - allNodes.forEach(node => { - const [indexOfNode] = this.getIndexByWord(keywords, node.nodeValue) - if (!indexOfNode.length) return - const slice = this.mergeIntoSlice(0, node.nodeValue.length, indexOfNode) - this.highlightText(node, slice, 'search-keyword') - }) - } -} - -window.addEventListener('load', () => { -// Search - const { path, top_n_per_article, unescape, languages, pagination } = GLOBAL_CONFIG.localSearch - const enablePagination = pagination && pagination.enable - const localSearch = new LocalSearch({ - path, - top_n_per_article, - unescape - }) - - const input = document.querySelector('.local-search-input input') - const statsItem = document.getElementById('local-search-stats') - const $loadingStatus = document.getElementById('loading-status') - const isXml = !path.endsWith('json') - - // Pagination variables (only initialize if pagination is enabled) - let currentPage = 0 - const hitsPerPage = pagination.hitsPerPage || 10 - - let currentResultItems = [] - - if (!enablePagination) { - // If pagination is disabled, we don't need these variables - currentPage = undefined - currentResultItems = undefined - } - - // Cache frequently used elements - const elements = { - get pagination () { return document.getElementById('local-search-pagination') }, - get paginationList () { return document.querySelector('#local-search-pagination .ais-Pagination-list') } - } - - // Show/hide search results area - const toggleResultsVisibility = hasResults => { - if (enablePagination) { - elements.pagination.style.display = hasResults ? '' : 'none' - } else { - elements.pagination.style.display = 'none' - } - } - - // Render search results for current page - const renderResults = (searchText, resultItems) => { - const container = document.getElementById('local-search-results') - - // Determine items to display based on pagination mode - const itemsToDisplay = enablePagination - ? currentResultItems.slice(currentPage * hitsPerPage, (currentPage + 1) * hitsPerPage) - : resultItems - - // Handle empty page in pagination mode - if (enablePagination && itemsToDisplay.length === 0 && currentResultItems.length > 0) { - currentPage = 0 - renderResults(searchText, resultItems) - return - } - - // Add numbering to items - const numberedItems = itemsToDisplay.map((result, index) => { - const itemNumber = enablePagination - ? currentPage * hitsPerPage + index + 1 - : index + 1 - return result.item.replace( - '
  • ', - `
  • ` - ) - }) - - container.innerHTML = `
      ${numberedItems.join('')}
    ` - - // Update stats - const displayCount = enablePagination ? currentResultItems.length : resultItems.length - const stats = languages.hits_stats.replace(/\$\{hits}/, displayCount) - statsItem.innerHTML = `
    ${stats}
    ` - - // Handle pagination - if (enablePagination) { - const nbPages = Math.ceil(currentResultItems.length / hitsPerPage) - renderPagination(currentPage, nbPages, searchText) - } - - const hasResults = resultItems.length > 0 - toggleResultsVisibility(hasResults) - - window.pjax && window.pjax.refresh(container) - } - - // Render pagination - const renderPagination = (page, nbPages, query) => { - if (nbPages <= 1) { - elements.pagination.style.display = 'none' - elements.paginationList.innerHTML = '' - return - } - - elements.pagination.style.display = 'block' - - const isFirstPage = page === 0 - const isLastPage = page === nbPages - 1 - - // Responsive page display - const isMobile = window.innerWidth < 768 - const maxVisiblePages = isMobile ? 3 : 5 - let startPage = Math.max(0, page - Math.floor(maxVisiblePages / 2)) - const endPage = Math.min(nbPages - 1, startPage + maxVisiblePages - 1) - - // Adjust starting page to maintain max visible pages - if (endPage - startPage + 1 < maxVisiblePages) { - startPage = Math.max(0, endPage - maxVisiblePages + 1) - } - - let pagesHTML = '' - - // Only add ellipsis and first page when there are many pages - if (nbPages > maxVisiblePages && startPage > 0) { - pagesHTML += ` -
  • - 1 -
  • ` - if (startPage > 1) { - pagesHTML += ` -
  • - ... -
  • ` - } - } - - // Add middle page numbers - for (let i = startPage; i <= endPage; i++) { - const isSelected = i === page - if (isSelected) { - pagesHTML += ` -
  • - ${i + 1} -
  • ` - } else { - pagesHTML += ` -
  • - ${i + 1} -
  • ` - } - } - - // Only add ellipsis and last page when there are many pages - if (nbPages > maxVisiblePages && endPage < nbPages - 1) { - if (endPage < nbPages - 2) { - pagesHTML += ` -
  • - ... -
  • ` - } - pagesHTML += ` -
  • - ${nbPages} -
  • ` - } - - if (nbPages > 1) { - elements.paginationList.innerHTML = ` -
  • - ${isFirstPage - ? '' - : `` - } -
  • - ${pagesHTML} -
  • - ${isLastPage - ? '' - : `` - } -
  • ` - } else { - elements.pagination.style.display = 'none' - } - } - - // Clear search results and stats - const clearSearchResults = () => { - const container = document.getElementById('local-search-results') - container.textContent = '' - statsItem.textContent = '' - toggleResultsVisibility(false) - if (enablePagination) { - currentResultItems = [] - currentPage = 0 - } - } - - // Show no results message - const showNoResults = searchText => { - const container = document.getElementById('local-search-results') - container.textContent = '' - const statsDiv = document.createElement('div') - statsDiv.className = 'search-result-stats' - statsDiv.textContent = languages.hits_empty.replace(/\$\{query}/, searchText) - statsItem.innerHTML = statsDiv.outerHTML - toggleResultsVisibility(false) - if (enablePagination) { - currentResultItems = [] - currentPage = 0 - } - } - - const inputEventFunction = () => { - if (!localSearch.isfetched) return - let searchText = input.value.trim().toLowerCase() - isXml && (searchText = searchText.replace(//g, '>')) - - if (searchText !== '') $loadingStatus.hidden = false - - const keywords = searchText.split(/[-\s]+/) - let resultItems = [] - - if (searchText.length > 0) { - resultItems = localSearch.getResultItems(keywords) - } - - if (keywords.length === 1 && keywords[0] === '') { - clearSearchResults() - } else if (resultItems.length === 0) { - showNoResults(searchText) - } else { - // Sort results by relevance - resultItems.sort((left, right) => { - if (left.includedCount !== right.includedCount) { - return right.includedCount - left.includedCount - } else if (left.hitCount !== right.hitCount) { - return right.hitCount - left.hitCount - } - return right.id - left.id - }) - - if (enablePagination) { - currentResultItems = resultItems - currentPage = 0 - } - renderResults(searchText, resultItems) - } - - $loadingStatus.hidden = true - } - - let loadFlag = false - const $searchMask = document.getElementById('search-mask') - const $searchDialog = document.querySelector('#local-search .search-dialog') - - // fix safari - const fixSafariHeight = () => { - if (window.innerWidth < 768) { - $searchDialog.style.setProperty('--search-height', window.innerHeight + 'px') - } - } - - const openSearch = () => { - btf.overflowPaddingR.add() - btf.animateIn($searchMask, 'to_show 0.5s') - btf.animateIn($searchDialog, 'titleScale 0.5s') - setTimeout(() => { input.focus() }, 300) - if (!loadFlag) { - !localSearch.isfetched && localSearch.fetchData() - input.addEventListener('input', inputEventFunction) - loadFlag = true - } - // shortcut: ESC - document.addEventListener('keydown', function f (event) { - if (event.code === 'Escape') { - closeSearch() - document.removeEventListener('keydown', f) - } - }) - - fixSafariHeight() - window.addEventListener('resize', fixSafariHeight) - } - - const closeSearch = () => { - btf.overflowPaddingR.remove() - btf.animateOut($searchDialog, 'search_close .5s') - btf.animateOut($searchMask, 'to_hide 0.5s') - window.removeEventListener('resize', fixSafariHeight) - } - - const searchClickFn = () => { - btf.addEventListenerPjax(document.querySelector('#search-button > .search'), 'click', openSearch) - } - - const searchFnOnce = () => { - document.querySelector('#local-search .search-close-button').addEventListener('click', closeSearch) - $searchMask.addEventListener('click', closeSearch) - if (GLOBAL_CONFIG.localSearch.preload) { - localSearch.fetchData() - } - localSearch.highlightSearchWords(document.getElementById('article-container')) - - // Pagination event delegation - only add if pagination is enabled - if (enablePagination) { - elements.pagination.addEventListener('click', e => { - e.preventDefault() - const link = e.target.closest('a[data-page]') - if (link) { - const page = parseInt(link.dataset.page, 10) - if (!isNaN(page) && currentResultItems.length > 0) { - currentPage = page - renderResults(input.value.trim().toLowerCase(), currentResultItems) - } - } - }) - } - - // Initial state - toggleResultsVisibility(false) - } - - window.addEventListener('search:loaded', () => { - const $loadDataItem = document.getElementById('loading-database') - $loadDataItem.nextElementSibling.style.visibility = 'visible' - $loadDataItem.remove() - }) - - searchClickFn() - searchFnOnce() - - // pjax - window.addEventListener('pjax:complete', () => { - !btf.isHidden($searchMask) && closeSearch() - localSearch.highlightSearchWords(document.getElementById('article-container')) - searchClickFn() - }) -}) diff --git a/static/js/tw_cn.js b/static/js/tw_cn.js deleted file mode 100644 index 43228dd..0000000 --- a/static/js/tw_cn.js +++ /dev/null @@ -1,117 +0,0 @@ -document.addEventListener('DOMContentLoaded', () => { - const { defaultEncoding, translateDelay, msgToTraditionalChinese, msgToSimplifiedChinese } = GLOBAL_CONFIG.translate - const snackbarData = GLOBAL_CONFIG.Snackbar - const targetEncodingCookie = 'translate-chn-cht' - - let currentEncoding = defaultEncoding - let targetEncoding = Number(btf.saveToLocal.get(targetEncodingCookie)) || defaultEncoding - const translateButtonObject = document.getElementById('translateLink') - const isSnackbar = snackbarData !== undefined - - const setLang = () => { - document.documentElement.lang = targetEncoding === 1 ? 'zh-TW' : 'zh-CN' - } - - const translateText = txt => { - if (!txt) return '' - if (currentEncoding === 1 && targetEncoding === 2) return Simplized(txt) - if (currentEncoding === 2 && targetEncoding === 1) return Traditionalized(txt) - return txt - } - - const translateBody = fobj => { - const nodes = typeof fobj === 'object' ? fobj.childNodes : document.body.childNodes - - for (const node of nodes) { - // Skip BR, HR tags, or the translate button object - if (['BR', 'HR'].includes(node.tagName) || node === translateButtonObject) continue - - if (node.nodeType === Node.ELEMENT_NODE) { - const { tagName, title, alt, placeholder, value, type } = node - - // Translate title, alt, placeholder - if (title) node.title = translateText(title) - if (alt) node.alt = translateText(alt) - if (placeholder) node.placeholder = translateText(placeholder) - - // Translate input value except text and hidden types - if (tagName === 'INPUT' && value && type !== 'text' && type !== 'hidden') { - node.value = translateText(value) - } - - // Recursively translate child nodes - translateBody(node) - } else if (node.nodeType === Node.TEXT_NODE) { - // Translate text node data - node.data = translateText(node.data) - } - } - } - - const translatePage = () => { - if (targetEncoding === 1) { - currentEncoding = 1 - targetEncoding = 2 - translateButtonObject.textContent = msgToTraditionalChinese - isSnackbar && btf.snackbarShow(snackbarData.cht_to_chs) - } else if (targetEncoding === 2) { - currentEncoding = 2 - targetEncoding = 1 - translateButtonObject.textContent = msgToSimplifiedChinese - isSnackbar && btf.snackbarShow(snackbarData.chs_to_cht) - } - btf.saveToLocal.set(targetEncodingCookie, targetEncoding, 2) - setLang() - translateBody() - } - - const JTPYStr = () => '万与丑专业丛东丝丢两严丧个丬丰临为丽举么义乌乐乔习乡书买乱争于亏云亘亚产亩亲亵亸亿仅从仑仓仪们价众优伙会伛伞伟传伤伥伦伧伪伫体余佣佥侠侣侥侦侧侨侩侪侬俣俦俨俩俪俭债倾偬偻偾偿傥傧储傩儿兑兖党兰关兴兹养兽冁内冈册写军农冢冯冲决况冻净凄凉凌减凑凛几凤凫凭凯击凼凿刍划刘则刚创删别刬刭刽刿剀剂剐剑剥剧劝办务劢动励劲劳势勋勐勚匀匦匮区医华协单卖卢卤卧卫却卺厂厅历厉压厌厍厕厢厣厦厨厩厮县参叆叇双发变叙叠叶号叹叽吁后吓吕吗吣吨听启吴呒呓呕呖呗员呙呛呜咏咔咙咛咝咤咴咸哌响哑哒哓哔哕哗哙哜哝哟唛唝唠唡唢唣唤唿啧啬啭啮啰啴啸喷喽喾嗫呵嗳嘘嘤嘱噜噼嚣嚯团园囱围囵国图圆圣圹场坂坏块坚坛坜坝坞坟坠垄垅垆垒垦垧垩垫垭垯垱垲垴埘埙埚埝埯堑堕塆墙壮声壳壶壸处备复够头夸夹夺奁奂奋奖奥妆妇妈妩妪妫姗姜娄娅娆娇娈娱娲娴婳婴婵婶媪嫒嫔嫱嬷孙学孪宁宝实宠审宪宫宽宾寝对寻导寿将尔尘尧尴尸尽层屃屉届属屡屦屿岁岂岖岗岘岙岚岛岭岳岽岿峃峄峡峣峤峥峦崂崃崄崭嵘嵚嵛嵝嵴巅巩巯币帅师帏帐帘帜带帧帮帱帻帼幂幞干并广庄庆庐庑库应庙庞废庼廪开异弃张弥弪弯弹强归当录彟彦彻径徕御忆忏忧忾怀态怂怃怄怅怆怜总怼怿恋恳恶恸恹恺恻恼恽悦悫悬悭悯惊惧惨惩惫惬惭惮惯愍愠愤愦愿慑慭憷懑懒懔戆戋戏戗战戬户扎扑扦执扩扪扫扬扰抚抛抟抠抡抢护报担拟拢拣拥拦拧拨择挂挚挛挜挝挞挟挠挡挢挣挤挥挦捞损捡换捣据捻掳掴掷掸掺掼揸揽揿搀搁搂搅携摄摅摆摇摈摊撄撑撵撷撸撺擞攒敌敛数斋斓斗斩断无旧时旷旸昙昼昽显晋晒晓晔晕晖暂暧札术朴机杀杂权条来杨杩杰极构枞枢枣枥枧枨枪枫枭柜柠柽栀栅标栈栉栊栋栌栎栏树栖样栾桊桠桡桢档桤桥桦桧桨桩梦梼梾检棂椁椟椠椤椭楼榄榇榈榉槚槛槟槠横樯樱橥橱橹橼檐檩欢欤欧歼殁殇残殒殓殚殡殴毁毂毕毙毡毵氇气氢氩氲汇汉污汤汹沓沟没沣沤沥沦沧沨沩沪沵泞泪泶泷泸泺泻泼泽泾洁洒洼浃浅浆浇浈浉浊测浍济浏浐浑浒浓浔浕涂涌涛涝涞涟涠涡涢涣涤润涧涨涩淀渊渌渍渎渐渑渔渖渗温游湾湿溃溅溆溇滗滚滞滟滠满滢滤滥滦滨滩滪漤潆潇潋潍潜潴澜濑濒灏灭灯灵灾灿炀炉炖炜炝点炼炽烁烂烃烛烟烦烧烨烩烫烬热焕焖焘煅煳熘爱爷牍牦牵牺犊犟状犷犸犹狈狍狝狞独狭狮狯狰狱狲猃猎猕猡猪猫猬献獭玑玙玚玛玮环现玱玺珉珏珐珑珰珲琎琏琐琼瑶瑷璇璎瓒瓮瓯电画畅畲畴疖疗疟疠疡疬疮疯疱疴痈痉痒痖痨痪痫痴瘅瘆瘗瘘瘪瘫瘾瘿癞癣癫癯皑皱皲盏盐监盖盗盘眍眦眬着睁睐睑瞒瞩矫矶矾矿砀码砖砗砚砜砺砻砾础硁硅硕硖硗硙硚确硷碍碛碜碱碹磙礼祎祢祯祷祸禀禄禅离秃秆种积称秽秾稆税稣稳穑穷窃窍窑窜窝窥窦窭竖竞笃笋笔笕笺笼笾筑筚筛筜筝筹签简箓箦箧箨箩箪箫篑篓篮篱簖籁籴类籼粜粝粤粪粮糁糇紧絷纟纠纡红纣纤纥约级纨纩纪纫纬纭纮纯纰纱纲纳纴纵纶纷纸纹纺纻纼纽纾线绀绁绂练组绅细织终绉绊绋绌绍绎经绐绑绒结绔绕绖绗绘给绚绛络绝绞统绠绡绢绣绤绥绦继绨绩绪绫绬续绮绯绰绱绲绳维绵绶绷绸绹绺绻综绽绾绿缀缁缂缃缄缅缆缇缈缉缊缋缌缍缎缏缐缑缒缓缔缕编缗缘缙缚缛缜缝缞缟缠缡缢缣缤缥缦缧缨缩缪缫缬缭缮缯缰缱缲缳缴缵罂网罗罚罢罴羁羟羡翘翙翚耢耧耸耻聂聋职聍联聩聪肃肠肤肷肾肿胀胁胆胜胧胨胪胫胶脉脍脏脐脑脓脔脚脱脶脸腊腌腘腭腻腼腽腾膑臜舆舣舰舱舻艰艳艹艺节芈芗芜芦苁苇苈苋苌苍苎苏苘苹茎茏茑茔茕茧荆荐荙荚荛荜荞荟荠荡荣荤荥荦荧荨荩荪荫荬荭荮药莅莜莱莲莳莴莶获莸莹莺莼萚萝萤营萦萧萨葱蒇蒉蒋蒌蓝蓟蓠蓣蓥蓦蔷蔹蔺蔼蕲蕴薮藁藓虏虑虚虫虬虮虽虾虿蚀蚁蚂蚕蚝蚬蛊蛎蛏蛮蛰蛱蛲蛳蛴蜕蜗蜡蝇蝈蝉蝎蝼蝾螀螨蟏衅衔补衬衮袄袅袆袜袭袯装裆裈裢裣裤裥褛褴襁襕见观觃规觅视觇览觉觊觋觌觍觎觏觐觑觞触觯詟誉誊讠计订讣认讥讦讧讨让讪讫训议讯记讱讲讳讴讵讶讷许讹论讻讼讽设访诀证诂诃评诅识诇诈诉诊诋诌词诎诏诐译诒诓诔试诖诗诘诙诚诛诜话诞诟诠诡询诣诤该详诧诨诩诪诫诬语诮误诰诱诲诳说诵诶请诸诹诺读诼诽课诿谀谁谂调谄谅谆谇谈谊谋谌谍谎谏谐谑谒谓谔谕谖谗谘谙谚谛谜谝谞谟谠谡谢谣谤谥谦谧谨谩谪谫谬谭谮谯谰谱谲谳谴谵谶谷豮贝贞负贠贡财责贤败账货质贩贪贫贬购贮贯贰贱贲贳贴贵贶贷贸费贺贻贼贽贾贿赀赁赂赃资赅赆赇赈赉赊赋赌赍赎赏赐赑赒赓赔赕赖赗赘赙赚赛赜赝赞赟赠赡赢赣赪赵赶趋趱趸跃跄跖跞践跶跷跸跹跻踊踌踪踬踯蹑蹒蹰蹿躏躜躯车轧轨轩轪轫转轭轮软轰轱轲轳轴轵轶轷轸轹轺轻轼载轾轿辀辁辂较辄辅辆辇辈辉辊辋辌辍辎辏辐辑辒输辔辕辖辗辘辙辚辞辩辫边辽达迁过迈运还这进远违连迟迩迳迹适选逊递逦逻遗遥邓邝邬邮邹邺邻郁郄郏郐郑郓郦郧郸酝酦酱酽酾酿释里鉅鉴銮錾钆钇针钉钊钋钌钍钎钏钐钑钒钓钔钕钖钗钘钙钚钛钝钞钟钠钡钢钣钤钥钦钧钨钩钪钫钬钭钮钯钰钱钲钳钴钵钶钷钸钹钺钻钼钽钾钿铀铁铂铃铄铅铆铈铉铊铋铍铎铏铐铑铒铕铗铘铙铚铛铜铝铞铟铠铡铢铣铤铥铦铧铨铪铫铬铭铮铯铰铱铲铳铴铵银铷铸铹铺铻铼铽链铿销锁锂锃锄锅锆锇锈锉锊锋锌锍锎锏锐锑锒锓锔锕锖锗错锚锜锞锟锠锡锢锣锤锥锦锨锩锫锬锭键锯锰锱锲锳锴锵锶锷锸锹锺锻锼锽锾锿镀镁镂镃镆镇镈镉镊镌镍镎镏镐镑镒镕镖镗镙镚镛镜镝镞镟镠镡镢镣镤镥镦镧镨镩镪镫镬镭镮镯镰镱镲镳镴镶长门闩闪闫闬闭问闯闰闱闲闳间闵闶闷闸闹闺闻闼闽闾闿阀阁阂阃阄阅阆阇阈阉阊阋阌阍阎阏阐阑阒阓阔阕阖阗阘阙阚阛队阳阴阵阶际陆陇陈陉陕陧陨险随隐隶隽难雏雠雳雾霁霉霭靓静靥鞑鞒鞯鞴韦韧韨韩韪韫韬韵页顶顷顸项顺须顼顽顾顿颀颁颂颃预颅领颇颈颉颊颋颌颍颎颏颐频颒颓颔颕颖颗题颙颚颛颜额颞颟颠颡颢颣颤颥颦颧风飏飐飑飒飓飔飕飖飗飘飙飚飞飨餍饤饥饦饧饨饩饪饫饬饭饮饯饰饱饲饳饴饵饶饷饸饹饺饻饼饽饾饿馀馁馂馃馄馅馆馇馈馉馊馋馌馍馎馏馐馑馒馓馔馕马驭驮驯驰驱驲驳驴驵驶驷驸驹驺驻驼驽驾驿骀骁骂骃骄骅骆骇骈骉骊骋验骍骎骏骐骑骒骓骔骕骖骗骘骙骚骛骜骝骞骟骠骡骢骣骤骥骦骧髅髋髌鬓魇魉鱼鱽鱾鱿鲀鲁鲂鲄鲅鲆鲇鲈鲉鲊鲋鲌鲍鲎鲏鲐鲑鲒鲓鲔鲕鲖鲗鲘鲙鲚鲛鲜鲝鲞鲟鲠鲡鲢鲣鲤鲥鲦鲧鲨鲩鲪鲫鲬鲭鲮鲯鲰鲱鲲鲳鲴鲵鲶鲷鲸鲹鲺鲻鲼鲽鲾鲿鳀鳁鳂鳃鳄鳅鳆鳇鳈鳉鳊鳋鳌鳍鳎鳏鳐鳑鳒鳓鳔鳕鳖鳗鳘鳙鳛鳜鳝鳞鳟鳠鳡鳢鳣鸟鸠鸡鸢鸣鸤鸥鸦鸧鸨鸩鸪鸫鸬鸭鸮鸯鸰鸱鸲鸳鸴鸵鸶鸷鸸鸹鸺鸻鸼鸽鸾鸿鹀鹁鹂鹃鹄鹅鹆鹇鹈鹉鹊鹋鹌鹍鹎鹏鹐鹑鹒鹓鹔鹕鹖鹗鹘鹚鹛鹜鹝鹞鹟鹠鹡鹢鹣鹤鹥鹦鹧鹨鹩鹪鹫鹬鹭鹯鹰鹱鹲鹳鹴鹾麦麸黄黉黡黩黪黾龙历志制一台皋准复猛钟注范签' - const FTPYStr = () => '萬與醜專業叢東絲丟兩嚴喪個爿豐臨為麗舉麼義烏樂喬習鄉書買亂爭於虧雲亙亞產畝親褻嚲億僅從侖倉儀們價眾優夥會傴傘偉傳傷倀倫傖偽佇體餘傭僉俠侶僥偵側僑儈儕儂俁儔儼倆儷儉債傾傯僂僨償儻儐儲儺兒兌兗黨蘭關興茲養獸囅內岡冊寫軍農塚馮衝決況凍淨淒涼淩減湊凜幾鳳鳧憑凱擊氹鑿芻劃劉則剛創刪別剗剄劊劌剴劑剮劍剝劇勸辦務勱動勵勁勞勢勳猛勩勻匭匱區醫華協單賣盧鹵臥衛卻巹廠廳曆厲壓厭厙廁廂厴廈廚廄廝縣參靉靆雙發變敘疊葉號歎嘰籲後嚇呂嗎唚噸聽啟吳嘸囈嘔嚦唄員咼嗆嗚詠哢嚨嚀噝吒噅鹹呱響啞噠嘵嗶噦嘩噲嚌噥喲嘜嗊嘮啢嗩唕喚呼嘖嗇囀齧囉嘽嘯噴嘍嚳囁嗬噯噓嚶囑嚕劈囂謔團園囪圍圇國圖圓聖壙場阪壞塊堅壇壢壩塢墳墜壟壟壚壘墾坰堊墊埡墶壋塏堖塒塤堝墊垵塹墮壪牆壯聲殼壺壼處備複夠頭誇夾奪奩奐奮獎奧妝婦媽嫵嫗媯姍薑婁婭嬈嬌孌娛媧嫻嫿嬰嬋嬸媼嬡嬪嬙嬤孫學孿寧寶實寵審憲宮寬賓寢對尋導壽將爾塵堯尷屍盡層屭屜屆屬屢屨嶼歲豈嶇崗峴嶴嵐島嶺嶽崠巋嶨嶧峽嶢嶠崢巒嶗崍嶮嶄嶸嶔崳嶁脊巔鞏巰幣帥師幃帳簾幟帶幀幫幬幘幗冪襆幹並廣莊慶廬廡庫應廟龐廢廎廩開異棄張彌弳彎彈強歸當錄彠彥徹徑徠禦憶懺憂愾懷態慫憮慪悵愴憐總懟懌戀懇惡慟懨愷惻惱惲悅愨懸慳憫驚懼慘懲憊愜慚憚慣湣慍憤憒願懾憖怵懣懶懍戇戔戲戧戰戩戶紮撲扡執擴捫掃揚擾撫拋摶摳掄搶護報擔擬攏揀擁攔擰撥擇掛摯攣掗撾撻挾撓擋撟掙擠揮撏撈損撿換搗據撚擄摑擲撣摻摜摣攬撳攙擱摟攪攜攝攄擺搖擯攤攖撐攆擷擼攛擻攢敵斂數齋斕鬥斬斷無舊時曠暘曇晝曨顯晉曬曉曄暈暉暫曖劄術樸機殺雜權條來楊榪傑極構樅樞棗櫪梘棖槍楓梟櫃檸檉梔柵標棧櫛櫳棟櫨櫟欄樹棲樣欒棬椏橈楨檔榿橋樺檜槳樁夢檮棶檢欞槨櫝槧欏橢樓欖櫬櫚櫸檟檻檳櫧橫檣櫻櫫櫥櫓櫞簷檁歡歟歐殲歿殤殘殞殮殫殯毆毀轂畢斃氈毿氌氣氫氬氳彙漢汙湯洶遝溝沒灃漚瀝淪滄渢溈滬濔濘淚澩瀧瀘濼瀉潑澤涇潔灑窪浹淺漿澆湞溮濁測澮濟瀏滻渾滸濃潯濜塗湧濤澇淶漣潿渦溳渙滌潤澗漲澀澱淵淥漬瀆漸澠漁瀋滲溫遊灣濕潰濺漵漊潷滾滯灩灄滿瀅濾濫灤濱灘澦濫瀠瀟瀲濰潛瀦瀾瀨瀕灝滅燈靈災燦煬爐燉煒熗點煉熾爍爛烴燭煙煩燒燁燴燙燼熱煥燜燾煆糊溜愛爺牘犛牽犧犢強狀獷獁猶狽麅獮獰獨狹獅獪猙獄猻獫獵獼玀豬貓蝟獻獺璣璵瑒瑪瑋環現瑲璽瑉玨琺瓏璫琿璡璉瑣瓊瑤璦璿瓔瓚甕甌電畫暢佘疇癤療瘧癘瘍鬁瘡瘋皰屙癰痙癢瘂癆瘓癇癡癉瘮瘞瘺癟癱癮癭癩癬癲臒皚皺皸盞鹽監蓋盜盤瞘眥矓著睜睞瞼瞞矚矯磯礬礦碭碼磚硨硯碸礪礱礫礎硜矽碩硤磽磑礄確鹼礙磧磣堿镟滾禮禕禰禎禱禍稟祿禪離禿稈種積稱穢穠穭稅穌穩穡窮竊竅窯竄窩窺竇窶豎競篤筍筆筧箋籠籩築篳篩簹箏籌簽簡籙簀篋籜籮簞簫簣簍籃籬籪籟糴類秈糶糲粵糞糧糝餱緊縶糸糾紆紅紂纖紇約級紈纊紀紉緯紜紘純紕紗綱納紝縱綸紛紙紋紡紵紖紐紓線紺絏紱練組紳細織終縐絆紼絀紹繹經紿綁絨結絝繞絰絎繪給絢絳絡絕絞統綆綃絹繡綌綏絛繼綈績緒綾緓續綺緋綽緔緄繩維綿綬繃綢綯綹綣綜綻綰綠綴緇緙緗緘緬纜緹緲緝縕繢緦綞緞緶線緱縋緩締縷編緡緣縉縛縟縝縫縗縞纏縭縊縑繽縹縵縲纓縮繆繅纈繚繕繒韁繾繰繯繳纘罌網羅罰罷羆羈羥羨翹翽翬耮耬聳恥聶聾職聹聯聵聰肅腸膚膁腎腫脹脅膽勝朧腖臚脛膠脈膾髒臍腦膿臠腳脫腡臉臘醃膕齶膩靦膃騰臏臢輿艤艦艙艫艱豔艸藝節羋薌蕪蘆蓯葦藶莧萇蒼苧蘇檾蘋莖蘢蔦塋煢繭荊薦薘莢蕘蓽蕎薈薺蕩榮葷滎犖熒蕁藎蓀蔭蕒葒葤藥蒞蓧萊蓮蒔萵薟獲蕕瑩鶯蓴蘀蘿螢營縈蕭薩蔥蕆蕢蔣蔞藍薊蘺蕷鎣驀薔蘞藺藹蘄蘊藪槁蘚虜慮虛蟲虯蟣雖蝦蠆蝕蟻螞蠶蠔蜆蠱蠣蟶蠻蟄蛺蟯螄蠐蛻蝸蠟蠅蟈蟬蠍螻蠑螿蟎蠨釁銜補襯袞襖嫋褘襪襲襏裝襠褌褳襝褲襇褸襤繈襴見觀覎規覓視覘覽覺覬覡覿覥覦覯覲覷觴觸觶讋譽謄訁計訂訃認譏訐訌討讓訕訖訓議訊記訒講諱謳詎訝訥許訛論訩訟諷設訪訣證詁訶評詛識詗詐訴診詆謅詞詘詔詖譯詒誆誄試詿詩詰詼誠誅詵話誕詬詮詭詢詣諍該詳詫諢詡譸誡誣語誚誤誥誘誨誑說誦誒請諸諏諾讀諑誹課諉諛誰諗調諂諒諄誶談誼謀諶諜謊諫諧謔謁謂諤諭諼讒諮諳諺諦謎諞諝謨讜謖謝謠謗諡謙謐謹謾謫譾謬譚譖譙讕譜譎讞譴譫讖穀豶貝貞負貟貢財責賢敗賬貨質販貪貧貶購貯貫貳賤賁貰貼貴貺貸貿費賀貽賊贄賈賄貲賃賂贓資賅贐賕賑賚賒賦賭齎贖賞賜贔賙賡賠賧賴賵贅賻賺賽賾贗讚贇贈贍贏贛赬趙趕趨趲躉躍蹌蹠躒踐躂蹺蹕躚躋踴躊蹤躓躑躡蹣躕躥躪躦軀車軋軌軒軑軔轉軛輪軟轟軲軻轤軸軹軼軤軫轢軺輕軾載輊轎輈輇輅較輒輔輛輦輩輝輥輞輬輟輜輳輻輯轀輸轡轅轄輾轆轍轔辭辯辮邊遼達遷過邁運還這進遠違連遲邇逕跡適選遜遞邐邏遺遙鄧鄺鄔郵鄒鄴鄰鬱郤郟鄶鄭鄆酈鄖鄲醞醱醬釅釃釀釋裏钜鑒鑾鏨釓釔針釘釗釙釕釷釺釧釤鈒釩釣鍆釹鍚釵鈃鈣鈈鈦鈍鈔鍾鈉鋇鋼鈑鈐鑰欽鈞鎢鉤鈧鈁鈥鈄鈕鈀鈺錢鉦鉗鈷缽鈳鉕鈽鈸鉞鑽鉬鉭鉀鈿鈾鐵鉑鈴鑠鉛鉚鈰鉉鉈鉍鈹鐸鉶銬銠鉺銪鋏鋣鐃銍鐺銅鋁銱銦鎧鍘銖銑鋌銩銛鏵銓鉿銚鉻銘錚銫鉸銥鏟銃鐋銨銀銣鑄鐒鋪鋙錸鋱鏈鏗銷鎖鋰鋥鋤鍋鋯鋨鏽銼鋝鋒鋅鋶鐦鐧銳銻鋃鋟鋦錒錆鍺錯錨錡錁錕錩錫錮鑼錘錐錦鍁錈錇錟錠鍵鋸錳錙鍥鍈鍇鏘鍶鍔鍤鍬鍾鍛鎪鍠鍰鎄鍍鎂鏤鎡鏌鎮鎛鎘鑷鐫鎳鎿鎦鎬鎊鎰鎔鏢鏜鏍鏰鏞鏡鏑鏃鏇鏐鐔钁鐐鏷鑥鐓鑭鐠鑹鏹鐙鑊鐳鐶鐲鐮鐿鑔鑣鑞鑲長門閂閃閆閈閉問闖閏闈閑閎間閔閌悶閘鬧閨聞闥閩閭闓閥閣閡閫鬮閱閬闍閾閹閶鬩閿閽閻閼闡闌闃闠闊闋闔闐闒闕闞闤隊陽陰陣階際陸隴陳陘陝隉隕險隨隱隸雋難雛讎靂霧霽黴靄靚靜靨韃鞽韉韝韋韌韍韓韙韞韜韻頁頂頃頇項順須頊頑顧頓頎頒頌頏預顱領頗頸頡頰頲頜潁熲頦頤頻頮頹頷頴穎顆題顒顎顓顏額顳顢顛顙顥纇顫顬顰顴風颺颭颮颯颶颸颼颻飀飄飆飆飛饗饜飣饑飥餳飩餼飪飫飭飯飲餞飾飽飼飿飴餌饒餉餄餎餃餏餅餑餖餓餘餒餕餜餛餡館餷饋餶餿饞饁饃餺餾饈饉饅饊饌饢馬馭馱馴馳驅馹駁驢駔駛駟駙駒騶駐駝駑駕驛駘驍罵駰驕驊駱駭駢驫驪騁驗騂駸駿騏騎騍騅騌驌驂騙騭騤騷騖驁騮騫騸驃騾驄驏驟驥驦驤髏髖髕鬢魘魎魚魛魢魷魨魯魴魺鮁鮃鯰鱸鮋鮓鮒鮊鮑鱟鮍鮐鮭鮚鮳鮪鮞鮦鰂鮜鱠鱭鮫鮮鮺鯗鱘鯁鱺鰱鰹鯉鰣鰷鯀鯊鯇鮶鯽鯒鯖鯪鯕鯫鯡鯤鯧鯝鯢鯰鯛鯨鯵鯴鯔鱝鰈鰏鱨鯷鰮鰃鰓鱷鰍鰒鰉鰁鱂鯿鰠鼇鰭鰨鰥鰩鰟鰜鰳鰾鱈鱉鰻鰵鱅鰼鱖鱔鱗鱒鱯鱤鱧鱣鳥鳩雞鳶鳴鳲鷗鴉鶬鴇鴆鴣鶇鸕鴨鴞鴦鴒鴟鴝鴛鴬鴕鷥鷙鴯鴰鵂鴴鵃鴿鸞鴻鵐鵓鸝鵑鵠鵝鵒鷳鵜鵡鵲鶓鵪鶤鵯鵬鵮鶉鶊鵷鷫鶘鶡鶚鶻鶿鶥鶩鷊鷂鶲鶹鶺鷁鶼鶴鷖鸚鷓鷚鷯鷦鷲鷸鷺鸇鷹鸌鸏鸛鸘鹺麥麩黃黌黶黷黲黽龍歷誌製壹臺臯準復勐鐘註範籤' - - const Traditionalized = cc => { - let str = '' - const ss = JTPYStr() - const tt = FTPYStr() - for (let i = 0; i < cc.length; i++) { - if (cc.charCodeAt(i) > 10000 && ss.indexOf(cc.charAt(i)) !== -1) { - str += tt.charAt(ss.indexOf(cc.charAt(i))) - } else str += cc.charAt(i) - } - return str - } - - const Simplized = cc => { - let str = '' - const ss = JTPYStr() - const tt = FTPYStr() - for (let i = 0; i < cc.length; i++) { - if (cc.charCodeAt(i) > 10000 && tt.indexOf(cc.charAt(i)) !== -1) { - str += ss.charAt(tt.indexOf(cc.charAt(i))) - } else str += cc.charAt(i) - } - return str - } - - const translateInitialization = () => { - if (translateButtonObject) { - if (currentEncoding !== targetEncoding) { - translateButtonObject.textContent = - targetEncoding === 1 - ? msgToSimplifiedChinese - : msgToTraditionalChinese - setLang() - setTimeout(translateBody, translateDelay) - } - } - } - - window.translateFn = { - translatePage, - Traditionalized, - Simplized, - translateInitialization - } - - translateInitialization() - btf.addGlobalFn('pjaxComplete', translateInitialization, 'translateInitialization') -}) diff --git a/static/js/utils.js b/static/js/utils.js deleted file mode 100644 index 1dd5386..0000000 --- a/static/js/utils.js +++ /dev/null @@ -1,338 +0,0 @@ -(() => { - const btfFn = { - debounce: (func, wait = 0, immediate = false) => { - let timeout - return (...args) => { - const later = () => { - timeout = null - if (!immediate) func(...args) - } - const callNow = immediate && !timeout - clearTimeout(timeout) - timeout = setTimeout(later, wait) - if (callNow) func(...args) - } - }, - - throttle: (func, wait, options = {}) => { - let timeout, args - let previous = 0 - - const later = () => { - previous = options.leading === false ? 0 : new Date().getTime() - timeout = null - func(...args) - if (!timeout) args = null - } - - return (...params) => { - const now = new Date().getTime() - if (!previous && options.leading === false) previous = now - const remaining = wait - (now - previous) - args = params - - if (remaining <= 0 || remaining > wait) { - if (timeout) { - clearTimeout(timeout) - timeout = null - } - previous = now - func(...args) - if (!timeout) args = null - } else if (!timeout && options.trailing !== false) { - timeout = setTimeout(later, remaining) - } - } - }, - - overflowPaddingR: { - add: () => { - const paddingRight = window.innerWidth - document.body.clientWidth - - if (paddingRight > 0) { - document.body.style.paddingRight = `${paddingRight}px` - document.body.style.overflow = 'hidden' - const menuElement = document.querySelector('#page-header.nav-fixed #menus') - if (menuElement) { - menuElement.style.paddingRight = `${paddingRight}px` - } - } - }, - remove: () => { - document.body.style.paddingRight = '' - document.body.style.overflow = '' - const menuElement = document.querySelector('#page-header.nav-fixed #menus') - if (menuElement) { - menuElement.style.paddingRight = '' - } - } - }, - - snackbarShow: (text, showAction = false, duration = 2000) => { - const { position, bgLight, bgDark } = GLOBAL_CONFIG.Snackbar - const bg = document.documentElement.getAttribute('data-theme') === 'light' ? bgLight : bgDark - Snackbar.show({ - text, - backgroundColor: bg, - showAction, - duration, - pos: position, - customClass: 'snackbar-css' - }) - }, - - diffDate: (inputDate, more = false) => { - const dateNow = new Date() - const datePost = new Date(inputDate) - const diffMs = dateNow - datePost - const diffSec = diffMs / 1000 - const diffMin = diffSec / 60 - const diffHour = diffMin / 60 - const diffDay = diffHour / 24 - const diffMonth = diffDay / 30 - const { dateSuffix } = GLOBAL_CONFIG - - if (!more) return Math.floor(diffDay) - - if (diffMonth > 12) return datePost.toISOString().slice(0, 10) - if (diffMonth >= 1) return `${Math.floor(diffMonth)} ${dateSuffix.month}` - if (diffDay >= 1) return `${Math.floor(diffDay)} ${dateSuffix.day}` - if (diffHour >= 1) return `${Math.floor(diffHour)} ${dateSuffix.hour}` - if (diffMin >= 1) return `${Math.floor(diffMin)} ${dateSuffix.min}` - return dateSuffix.just - }, - - loadComment: (dom, callback) => { - if ('IntersectionObserver' in window) { - const observerItem = new IntersectionObserver(entries => { - if (entries[0].isIntersecting) { - callback() - observerItem.disconnect() - } - }, { threshold: [0] }) - observerItem.observe(dom) - } else { - callback() - } - }, - - scrollToDest: (pos, time = 500) => { - const currentPos = window.scrollY - const isNavFixed = document.getElementById('page-header').classList.contains('fixed') - if (currentPos > pos || isNavFixed) pos = pos - 70 - - if ('scrollBehavior' in document.documentElement.style) { - window.scrollTo({ - top: pos, - behavior: 'smooth' - }) - return - } - - const startTime = performance.now() - const animate = currentTime => { - const timeElapsed = currentTime - startTime - const progress = Math.min(timeElapsed / time, 1) - window.scrollTo(0, currentPos + (pos - currentPos) * progress) - if (progress < 1) { - requestAnimationFrame(animate) - } - } - requestAnimationFrame(animate) - }, - - animateIn: (ele, animation) => { - ele.style.display = 'block' - ele.style.animation = animation - }, - - animateOut: (ele, animation) => { - const handleAnimationEnd = () => { - ele.style.display = '' - ele.style.animation = '' - ele.removeEventListener('animationend', handleAnimationEnd) - } - ele.addEventListener('animationend', handleAnimationEnd) - ele.style.animation = animation - }, - - wrap: (selector, eleType, options) => { - const createEle = document.createElement(eleType) - for (const [key, value] of Object.entries(options)) { - createEle.setAttribute(key, value) - } - selector.parentNode.insertBefore(createEle, selector) - createEle.appendChild(selector) - }, - - isHidden: ele => ele.offsetHeight === 0 && ele.offsetWidth === 0, - - getEleTop: ele => ele.getBoundingClientRect().top + window.scrollY, - - loadLightbox: ele => { - const service = GLOBAL_CONFIG.lightbox - - if (service === 'medium_zoom') { - mediumZoom(ele, { background: 'var(--zoom-bg)' }) - return - } - - if (service === 'fancybox') { - ele.forEach(i => { - if (i.parentNode.tagName !== 'A') { - const dataSrc = i.dataset.lazySrc || i.src - const dataCaption = i.title || i.alt || '' - btf.wrap(i, 'a', { href: dataSrc, 'data-fancybox': 'gallery', 'data-caption': dataCaption, 'data-thumb': dataSrc }) - } - }) - - if (!window.fancyboxRun) { - let options = '' - if (Fancybox.version < '6') { - options = { - Hash: false, - Thumbs: { - showOnStart: false - }, - Images: { - Panzoom: { - maxScale: 4 - } - }, - Carousel: { - transition: 'slide' - }, - Toolbar: { - display: { - left: ['infobar'], - middle: [ - 'zoomIn', - 'zoomOut', - 'toggle1to1', - 'rotateCCW', - 'rotateCW', - 'flipX', - 'flipY' - ], - right: ['slideshow', 'thumbs', 'close'] - } - } - } - } else { - options = { - Hash: false, - Carousel: { - transition: 'slide', - Thumbs: { - showOnStart: false - }, - Toolbar: { - display: { - left: ['counter'], - middle: [ - 'zoomIn', - 'zoomOut', - 'toggle1to1', - 'rotateCCW', - 'rotateCW', - 'flipX', - 'flipY', - 'reset' - ], - right: ['autoplay', 'thumbs', 'close'] - } - }, - Zoomable: { - Panzoom: { - maxScale: 4 - } - } - } - } - } - - Fancybox.bind('[data-fancybox]', options) - window.fancyboxRun = true - } - } - }, - - setLoading: { - add: ele => { - const html = ` -
    -
    -
    -
    -
    - ` - ele.insertAdjacentHTML('afterend', html) - }, - remove: ele => { - ele.nextElementSibling.remove() - } - }, - - updateAnchor: anchor => { - if (anchor !== window.location.hash) { - if (!anchor) anchor = location.pathname - const title = GLOBAL_CONFIG_SITE.title - window.history.replaceState({ - url: location.href, - title - }, title, anchor) - } - }, - - getScrollPercent: (() => { - let docHeight, winHeight, headerHeight, contentMath - - return (currentTop, ele) => { - if (!docHeight || ele.clientHeight !== docHeight) { - docHeight = ele.clientHeight - winHeight = window.innerHeight - headerHeight = ele.offsetTop - contentMath = Math.max(docHeight - winHeight, document.documentElement.scrollHeight - winHeight) - } - - const scrollPercent = (currentTop - headerHeight) / contentMath - return Math.max(0, Math.min(100, Math.round(scrollPercent * 100))) - } - })(), - - addEventListenerPjax: (ele, event, fn, option = false) => { - ele.addEventListener(event, fn, option) - btf.addGlobalFn('pjaxSendOnce', () => { - ele.removeEventListener(event, fn, option) - }) - }, - - removeGlobalFnEvent: (key, parent = window) => { - const globalFn = parent.globalFn || {} - const keyObj = globalFn[key] - if (!keyObj) return - - Object.keys(keyObj).forEach(i => keyObj[i]()) - - delete globalFn[key] - }, - - switchComments: (el = document, path) => { - const switchBtn = el.querySelector('#switch-btn') - if (!switchBtn) return - - let switchDone = false - const postComment = el.querySelector('#post-comment') - const handleSwitchBtn = () => { - postComment.classList.toggle('move') - if (!switchDone && typeof loadOtherComment === 'function') { - switchDone = true - loadOtherComment(el, path) - } - } - btf.addEventListenerPjax(switchBtn, 'click', handleSwitchBtn) - } - } - - window.btf = { ...window.btf, ...btfFn } -})() diff --git a/themes/.gitkeep b/themes/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/themes/butterfly/.gitignore b/themes/butterfly/.gitignore deleted file mode 100644 index 646ac51..0000000 --- a/themes/butterfly/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -.DS_Store -node_modules/ diff --git a/themes/butterfly/LICENSE b/themes/butterfly/LICENSE deleted file mode 100644 index 7a4a3ea..0000000 --- a/themes/butterfly/LICENSE +++ /dev/null @@ -1,202 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. \ No newline at end of file diff --git a/themes/butterfly/README.md b/themes/butterfly/README.md deleted file mode 100644 index a45b670..0000000 --- a/themes/butterfly/README.md +++ /dev/null @@ -1,193 +0,0 @@ -
    - 中文 -
    - -
    - -Butterfly Logo - -# hexo-theme-butterfly - -A modern, elegant and feature-rich theme for Hexo - -![master version](https://img.shields.io/github/package-json/v/jerryc127/hexo-theme-butterfly/master?color=%231ab1ad&label=master) -![dev version](https://img.shields.io/github/package-json/v/jerryc127/hexo-theme-butterfly/dev?label=dev) -![npm version](https://img.shields.io/npm/v/hexo-theme-butterfly?color=%09%23bf00ff) -![hexo version](https://img.shields.io/badge/hexo-5.3.0+-0e83cd) -![license](https://img.shields.io/github/license/jerryc127/hexo-theme-butterfly?color=FF5531) -![GitHub stars](https://img.shields.io/github/stars/jerryc127/hexo-theme-butterfly?style=social) - -📢 **Demo**: [Butterfly Official](https://butterfly.js.org/) | [CrazyWong's Blog](https://blog.crazywong.com/) - -📖 **Documentation**: [English Docs](https://butterfly.js.org/en/posts/butterfly-docs-en-get-started/) | [中文文档](https://butterfly.js.org/posts/21cfbf15/) - -![Butterfly Theme Preview](https://cdn.jsdelivr.net/gh/jerryc127/CDN@m2/img/theme-butterfly-readme.png) - -
    - ---- - -## 🚀 Quick Start - -### 💾 Installation - -#### Method 1: Git Installation (Recommended) - -> 💡 **Tip**: If GitHub access is slow in mainland China, you can use the [Gitee Mirror](https://gitee.com/immyw/hexo-theme-butterfly.git) - -Execute in your Hexo blog root directory: - -```bash -# Install stable version (recommended) -git clone -b master https://github.com/jerryc127/hexo-theme-butterfly.git themes/butterfly -``` - -```bash -# Install development version (early access to new features) -git clone -b dev https://github.com/jerryc127/hexo-theme-butterfly.git themes/butterfly -``` - -#### Method 2: NPM Installation - -> ⚠️ **Note**: NPM installation only supports Hexo 5.0.0 and above - -```bash -npm install hexo-theme-butterfly -``` - -### ⚙️ Theme Configuration - -1. **Enable Theme**: Modify your Hexo configuration file `_config.yml`: - -```yaml -theme: butterfly -``` - -2. **Install Dependencies**: If you haven't installed pug and stylus renderers, please run: - -```bash -npm install hexo-renderer-pug hexo-renderer-stylus --save -``` - -## ✨ Theme Features - -### 🎨 Design Style -- [x] **Card-based Design** - Modern card-style layout -- [x] **Rounded/Square Design** - Customizable border styles -- [x] **Responsive Design** - Perfect adaptation to all screen sizes -- [x] **Two-column Layout** - Optimized reading experience -- [x] **Dark Mode** - Eye-friendly night mode - -### 📝 Content Features -- [x] **Multi-level Menu** - Support for secondary navigation menus -- [x] **Reading Mode** - Focused article reading experience -- [x] **TOC Navigation** - Desktop and mobile TOC support -- [x] **Word Count** - Display article word count and reading time -- [x] **Related Articles** - Smart recommendation of related content -- [x] **Outdated Reminder** - Automatic article update status alerts -- [x] **Traditional/Simplified Chinese** - Support for Traditional and Simplified Chinese switching -- [x] **Tag Plugins** - Rich tag plugin support - -### 🔍 Search & Navigation -- [x] **Multiple Search Options** - Algolia Search / Local Search / Docsearch -- [x] **Built-in 404** - Beautiful 404 error page -- [x] **Pjax Support** - Smooth page transition experience - -### 🎨 Code Display -- [x] **Syntax Highlighting** - Built-in multiple themes (darker/pale night/light/ocean) -- [x] **Code Features** - Language display/fold expand/copy button/auto-wrap -- [x] **Math Formulas** - Support for Mathjax and Katex - -### 💬 Social Interaction -- [x] **Multiple Comment Systems** - Disqus/Gitalk/Valine/Waline/Twikoo/Giscus/Artalk etc. -- [x] **Dual Comments Support** - Enable two comment systems simultaneously -- [x] **Share Features** - Sharejs/Addtoany sharing components -- [x] **Live Chat** - Chatra/Tidio/Crisp instant messaging - -### 📊 Analytics & Statistics -- [x] **Visit Statistics** - Busuanzi counter -- [x] **Site Analytics** - Google Analytics/Baidu Analytics/Cloudflare Analytics/Microsoft Clarity/Umami -- [x] **Webmaster Verification** - Major search engine verification -- [x] **Ad Support** - Google AdSense/custom ad slots - -### 🎪 Visual Effects -- [x] **Typing Effects** - activate_power_mode animations -- [x] **Background Effects** - Static ribbons/dynamic ribbons/floating ribbons/Canvas Nest -- [x] **Mouse Effects** - Fireworks/hearts/text click effects -- [x] **Loading Animations** - Preloader and pace.js progress bars -- [x] **Image Effects** - Medium Zoom/Fancybox image lightbox -- [x] **Lazy Loading** - Image lazy loading optimization - -### 🛠️ Advanced Features -- [x] **PWA Support** - Progressive Web App -- [x] **Copy Protection** - Disable text copying/copyright info append -- [x] **Theme Customization** - Custom site color schemes -- [x] **Chart Support** - Mermaid flowcharts/Chart.js data charts -- [x] **Music Notation** - ABCJS music notation support -- [x] **Music Player** - APlayer/Meting music playback -- [x] **Article Series** - Series article organization -- [x] **Instantpage** - Page preloading acceleration -- [x] **Snackbar** - Elegant notification messages - -## 🤝 Contributors - -Thanks to all the developers who have contributed to the Butterfly theme! - -[![Contributors](https://contrib.rocks/image?repo=jerryc127/hexo-theme-butterfly)](https://github.com/jerryc127/hexo-theme-butterfly/graphs/contributors) - -## 📸 Screenshots - -
    - -![Theme Demo](https://cdn.jsdelivr.net/gh/jerryc127/CDN@m2/img/butterfly-readme-screenshots-1.jpg) - -![Theme Demo](https://cdn.jsdelivr.net/gh/jerryc127/CDN@m2/img/butterfly-readme-screenshots-2.jpg) - -![Theme Demo](https://cdn.jsdelivr.net/gh/jerryc127/CDN@m2/img/butterfly-readme-screenshots-3.jpg) - -![Theme Demo](https://cdn.jsdelivr.net/gh/jerryc127/CDN@m2/img/butterfly-readme-screenshots-4.jpg) - -
    - - -## ⭐ Star History - -[![Star History Chart](https://api.star-history.com/svg?repos=jerryc127/hexo-theme-butterfly&type=Date)](https://star-history.com/#jerryc127/hexo-theme-butterfly&Date) - -## 🤝 Building a Better Theme Together - -We believe **the power of open source comes from everyone's participation**! Whether you're a developer, designer, or user, you can contribute to the development of the Butterfly theme. - -### 💬 Get Help & Support - -- 🐛 **Found a bug?** → [GitHub Issues](https://github.com/jerryc127/hexo-theme-butterfly/issues) - Let's solve it together! -- 💡 **Have ideas?** → [GitHub Discussions](https://github.com/jerryc127/hexo-theme-butterfly/discussions) - Share your creative ideas! -- 📚 **Learning to use?** → [Official Documentation](https://butterfly.js.org/) - Detailed usage guide -- 💬 **Real-time discussion?** → [Telegram Group](https://t.me/bu2fly) - Chat with community members - -### 🎯 Contributing - -Want to make Butterfly better? We welcome any form of contribution: - -- **🔧 Code Contributions** - Fix bugs, add new features, optimize performance -- **📝 Documentation** - Improve docs, translate content, write tutorials -- **🎨 Design Suggestions** - UI/UX improvements, theme colors, icon design -- **🧪 Testing & Feedback** - Test new features, report issues, provide user experience -- **💰 Financial Support** - [Sponsor the Project](https://buy.stripe.com/3cs6rP6YA91sbbG5kk) - Support long-term development - -## 📄 License - -This project is licensed under the [Apache 2.0](LICENSE) License. - -## 🙏 Acknowledgments - -This theme is developed based on [hexo-theme-melody](https://github.com/Molunerfinn/hexo-theme-melody). Thanks to the original author for their excellent work that provided inspiration and foundation! - -Thanks to all friends who have contributed to the development of the Butterfly theme. Your support has made this theme continuously improve and progress. - ---- - -
    - -**✨ If this theme helps you, please give us a ⭐ Star! ✨** -
    diff --git a/themes/butterfly/README_CN.md b/themes/butterfly/README_CN.md deleted file mode 100644 index 2c3e143..0000000 --- a/themes/butterfly/README_CN.md +++ /dev/null @@ -1,193 +0,0 @@ -
    - English -
    - -
    - -Butterfly Logo - -# hexo-theme-butterfly - -一個適用於 Hexo 的現代化、美觀且功能豐富的主題 - -![master version](https://img.shields.io/github/package-json/v/jerryc127/hexo-theme-butterfly/master?color=%231ab1ad&label=master) -![dev version](https://img.shields.io/github/package-json/v/jerryc127/hexo-theme-butterfly/dev?label=dev) -![npm version](https://img.shields.io/npm/v/hexo-theme-butterfly?color=%09%23bf00ff) -![hexo version](https://img.shields.io/badge/hexo-5.3.0+-0e83cd) -![license](https://img.shields.io/github/license/jerryc127/hexo-theme-butterfly?color=FF5531) -![GitHub stars](https://img.shields.io/github/stars/jerryc127/hexo-theme-butterfly?style=social) - -📢 **在線預覽**: [Butterfly 官方](https://butterfly.js.org/) | [CrazyWong 博客](https://blog.crazywong.com/) - -📖 **完整文檔**: [中文文檔](https://butterfly.js.org/posts/21cfbf15/) | [English Docs](https://butterfly.js.org/en/posts/butterfly-docs-en-get-started/) - -![Butterfly 主題預覽](https://cdn.jsdelivr.net/gh/jerryc127/CDN@m2/img/theme-butterfly-readme.png) - -
    - ---- - -## 🚀 快速開始 - -### 💾 安裝方式 - -#### 方式一:Git 安裝(推薦) - -> 💡 **提示**: 如果您在中國大陸訪問 GitHub 速度較慢,可以使用 [Gitee 鏡像](https://gitee.com/immyw/hexo-theme-butterfly.git) - -在您的 Hexo 博客根目錄下執行: - -```bash -# 安裝穩定版本(推薦) -git clone -b master https://github.com/jerryc127/hexo-theme-butterfly.git themes/butterfly -``` - -```bash -# 安裝開發版本(搶先體驗新功能) -git clone -b dev https://github.com/jerryc127/hexo-theme-butterfly.git themes/butterfly -``` - -#### 方式二:NPM 安裝 - -> ⚠️ **注意**: NPM 安裝方式僅支援 Hexo 5.0.0 及以上版本 - -```bash -npm install hexo-theme-butterfly -``` - -### ⚙️ 主題配置 - -1. **啟用主題**: 修改您的 Hexo 配置檔案 `_config.yml`: - -```yaml -theme: butterfly -``` - -2. **安裝依賴**: 如果您尚未安裝 pug 和 stylus 渲染器,請執行: - -```bash -npm install hexo-renderer-pug hexo-renderer-stylus --save -``` - -## ✨ 主題特色 - -### 🎨 設計風格 -- [x] **卡片化設計** - 現代化的卡片式佈局 -- [x] **圓角/直角設計** - 支援自訂邊框樣式 -- [x] **響應式設計** - 完美適配各種螢幕尺寸 -- [x] **雙欄佈局** - 優化的閱讀體驗 -- [x] **深色模式** - 護眼的夜間模式 - -### 📝 內容功能 -- [x] **多級選單** - 支援二級導航選單 -- [x] **閱讀模式** - 專注的文章閱讀體驗 -- [x] **目錄導航** - 電腦和手機雙端支援 TOC -- [x] **字數統計** - 顯示文章字數和閱讀時間 -- [x] **相關文章** - 智能推薦相關內容 -- [x] **過期提醒** - 自動提示文章更新狀態 -- [x] **簡繁轉換** - 支援繁體中文和簡體中文切換 -- [x] **標籤外掛** - 豐富的標籤外掛支持 - -### 🔍 搜尋與導航 -- [x] **多種搜尋** - Algolia 搜尋 / 本地搜尋 / Docsearch -- [x] **內建 404** - 美觀的 404 錯誤頁面 -- [x] **Pjax 支援** - 流暢的頁面切換體驗 - -### 🎨 程式碼展示 -- [x] **語法高亮** - 內建多種主題(darker/pale night/light/ocean) -- [x] **程式碼功能** - 語言顯示/摺疊展開/複製按鈕/自動換行 -- [x] **數學公式** - 支援 Mathjax 和 Katex - -### 💬 社交互動 -- [x] **多元評論系統** - Disqus/Gitalk/Valine/Waline/Twikoo/Giscus/Artalk 等 -- [x] **雙評論支援** - 可同時啟用兩套評論系統 -- [x] **分享功能** - Sharejs/Addtoany 分享套件 -- [x] **線上客服** - Chatra/Tidio/Crisp 即時聊天 - -### 📊 數據分析 -- [x] **訪問統計** - 不蒜子計數器 -- [x] **網站分析** - Google Analytics/百度統計/Cloudflare Analytics/Microsoft Clarity/Umami -- [x] **站長驗證** - 各大搜尋引擎驗證 -- [x] **廣告支援** - Google AdSense/自訂廣告位 - -### 🎪 視覺效果 -- [x] **打字特效** - activate_power_mode 動畫 -- [x] **背景特效** - 靜態彩帶/動態彩帶/飄帶效果/Canvas Nest -- [x] **滑鼠特效** - 煙花/愛心/文字點擊效果 -- [x] **載入動畫** - Preloader 和 pace.js 進度條 -- [x] **圖片效果** - Medium Zoom/Fancybox 圖片燈箱 -- [x] **懶載入** - 圖片延遲載入優化 - -### 🛠️ 進階功能 -- [x] **PWA 支援** - 漸進式網頁應用 -- [x] **複製保護** - 可關閉文字複製/版權資訊追加 -- [x] **主題定製** - 自訂網站配色方案 -- [x] **圖表支援** - Mermaid 流程圖/Chart.js 數據圖表 -- [x] **音樂符號** - ABCJS 音樂記譜法支援 -- [x] **音樂播放器** - APlayer/Meting 音樂播放功能 -- [x] **系列文章** - 系列文章組織功能 -- [x] **Instantpage** - 頁面預載入加速 -- [x] **Snackbar** - 優雅的提示訊息 - -## 🤝 貢獻者 - -感謝所有為 Butterfly 主題做出貢獻的開發者們! - -[![Contributors](https://contrib.rocks/image?repo=jerryc127/hexo-theme-butterfly)](https://github.com/jerryc127/hexo-theme-butterfly/graphs/contributors) - -## 📸 主題截圖 - -
    - -![主題展示](https://cdn.jsdelivr.net/gh/jerryc127/CDN@m2/img/butterfly-readme-screenshots-1.jpg) - -![主題展示](https://cdn.jsdelivr.net/gh/jerryc127/CDN@m2/img/butterfly-readme-screenshots-2.jpg) - -![主題展示](https://cdn.jsdelivr.net/gh/jerryc127/CDN@m2/img/butterfly-readme-screenshots-3.jpg) - -![主題展示](https://cdn.jsdelivr.net/gh/jerryc127/CDN@m2/img/butterfly-readme-screenshots-4.jpg) - -
    - - -## ⭐ Star 趨勢 - -[![Star History Chart](https://api.star-history.com/svg?repos=jerryc127/hexo-theme-butterfly&type=Date)](https://star-history.com/#jerryc127/hexo-theme-butterfly&Date) - -## 🤝 一起構建更美好的主題 - -我們相信,**開源的力量來自於每一個人的參與**!無論您是開發者、設計師還是用戶,都可以為 Butterfly 主題的發展貢獻力量。 - -### 💬 獲取幫助與支援 - -- 🐛 **發現問題?** → [GitHub Issues](https://github.com/jerryc127/hexo-theme-butterfly/issues) - 讓我們一起解決! -- 💡 **有好想法?** → [GitHub Discussions](https://github.com/jerryc127/hexo-theme-butterfly/discussions) - 分享您的創意想法! -- 📚 **學習使用?** → [官方文檔](https://butterfly.js.org/) - 詳細的使用指南 -- 💬 **即時討論?** → [Telegram 群組](https://t.me/bu2fly) - 與社群成員實時交流 - -### 🎯 參與貢獻 - -想要讓 Butterfly 變得更好嗎?我們歡迎您的任何形式的貢獻: - -- **🔧 代碼貢獻** - 修復 Bug、添加新功能、優化性能 -- **📝 文檔完善** - 改進文檔、翻譯內容、撰寫教程 -- **🎨 設計建議** - UI/UX 改進、主題配色、圖示設計 -- **🧪 測試反饋** - 測試新功能、回報問題、提供使用體驗 -- **💰 資金支援** - [贊助項目](https://buy.stripe.com/3cs6rP6YA91sbbG5kk) - 支持長期發展 - -## 📄 授權條款 - -本專案採用 [Apache 2.0](LICENSE) 授權條款。 - -## 🙏 致敬與感謝 - -本主題基於 [hexo-theme-melody](https://github.com/Molunerfinn/hexo-theme-melody) 進行開發,感謝原作者的精彩創作為我們提供了靈感與基礎! - -感謝所有為 Butterfly 主題發展做出貢獻的朋友們,是你們的支持讓這個主題能夠不斷完善與進步。 - ---- - -
    - -**✨ 如果這個主題對您有幫助,請給我們一個 ⭐ Star!✨** -
    diff --git a/themes/butterfly/_config.yml b/themes/butterfly/_config.yml deleted file mode 100644 index f730d80..0000000 --- a/themes/butterfly/_config.yml +++ /dev/null @@ -1,1136 +0,0 @@ -# -------------------------------------- -# 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, image URL or an array containing colors and/or image URLs -# If an array is provided, a random background will be selected from the array on each load -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 - custom_colors: - # 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 - # Enable pagination for search results - pagination: - enable: false - # Number of search results per page - hitsPerPage: 8 - 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,x,wechat,weibo,qq - - # AddToAny - # https://www.addtoany.com/ - addtoany: - item: facebook,x,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: - script_name: script.js - 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 - # Enable "Open in New Tab" button to view diagram in a separate window - open_in_new_tab: true - # Enable zoom and pan interactions on diagrams - zoom_pan: true - -# 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: - enable: false - # Alternate name for the site, used in structured data - # Format: ['name1', 'name2'] - alternate_name: - -# 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: true - - # 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: - # 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 deleted file mode 100644 index b3c08d2..0000000 --- a/themes/butterfly/languages/default.yml +++ /dev/null @@ -1,123 +0,0 @@ -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 - page_info: 'Page ${current} of ${total}' - -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 deleted file mode 100644 index 389ceba..0000000 --- a/themes/butterfly/languages/en.yml +++ /dev/null @@ -1,123 +0,0 @@ -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 - page_info: 'Page ${current} of ${total}' - -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 deleted file mode 100644 index 62aa75f..0000000 --- a/themes/butterfly/languages/ja.yml +++ /dev/null @@ -1,123 +0,0 @@ -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: 次へ - page_info: '${current} ページ / 合計 ${total} ページ' - -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 deleted file mode 100644 index aecc07e..0000000 --- a/themes/butterfly/languages/ko.yml +++ /dev/null @@ -1,123 +0,0 @@ -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: 다음 - page_info: '${current} 페이지 / 총 ${total} 페이지' - -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 deleted file mode 100644 index 952169d..0000000 --- a/themes/butterfly/languages/zh-CN.yml +++ /dev/null @@ -1,124 +0,0 @@ -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: 下一篇 - page_info: '第 ${current} 页 / 共 ${total} 页' - -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 deleted file mode 100644 index 0e8c277..0000000 --- a/themes/butterfly/languages/zh-HK.yml +++ /dev/null @@ -1,123 +0,0 @@ -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: 下一頁 - page_info: '第 ${current} 頁 / 共 ${total} 頁' - -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 deleted file mode 100644 index 498e524..0000000 --- a/themes/butterfly/languages/zh-TW.yml +++ /dev/null @@ -1,123 +0,0 @@ -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: 下一篇 - page_info: '第 ${current} 頁 / 共 ${total} 頁' - -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 deleted file mode 100644 index b67329e..0000000 --- a/themes/butterfly/layout/archive.pug +++ /dev/null @@ -1,9 +0,0 @@ -extends includes/layout.pug - -block content - include ./includes/mixins/article-sort.pug - #archive - #posts-chart(data-start="2025-01" style="height: 300px; padding: 10px;") - .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 deleted file mode 100644 index 092be9a..0000000 --- a/themes/butterfly/layout/category.pug +++ /dev/null @@ -1,12 +0,0 @@ -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 deleted file mode 100644 index fdaa364..0000000 --- a/themes/butterfly/layout/includes/additional-js.pug +++ /dev/null @@ -1,61 +0,0 @@ -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 ? url_for(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/categoryBar.pug b/themes/butterfly/layout/includes/categoryBar.pug deleted file mode 100644 index 9fadff8..0000000 --- a/themes/butterfly/layout/includes/categoryBar.pug +++ /dev/null @@ -1,10 +0,0 @@ -.category-bar-items#category-bar-items(class=is_home() ? 'home' : '') - .category-bar-item(class=is_home() ? 'select' : '', id="category-bar-home") - a(href=url_for('/'))= __('博客首页') - each item in site.categories.find({ parent: { $exists: false } }).data - .category-bar-item(class=select ? (select === item.name ? 'select' : '') : '', id=item.name) - a(href=url_for(item.path))= item.name - .category-bar-item - a(href=url_for('/archives/'))= __('文章存档') -div.category-bar-right - a.category-bar-more(href=url_for('/categories/'))= __('更多分类') diff --git a/themes/butterfly/layout/includes/footer.pug b/themes/butterfly/layout/includes/footer.pug deleted file mode 100644 index 4102b42..0000000 --- a/themes/butterfly/layout/includes/footer.pug +++ /dev/null @@ -1,39 +0,0 @@ -- 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 deleted file mode 100644 index 3e77573..0000000 --- a/themes/butterfly/layout/includes/head.pug +++ /dev/null @@ -1,77 +0,0 @@ -- 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 deleted file mode 100644 index b3628d0..0000000 --- a/themes/butterfly/layout/includes/head/Open_Graph.pug +++ /dev/null @@ -1,16 +0,0 @@ -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 deleted file mode 100644 index 4ca6bf3..0000000 --- a/themes/butterfly/layout/includes/head/analytics.pug +++ /dev/null @@ -1,45 +0,0 @@ -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 deleted file mode 100644 index e5901ff..0000000 --- a/themes/butterfly/layout/includes/head/config.pug +++ /dev/null @@ -1,125 +0,0 @@ -- - 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: { - 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, pagination, unescape } = theme.search.local_search - localSearch = JSON.stringify({ - path: CDN || config.root + config.search.path, - preload, - top_n_per_article, - unescape, - pagination: { - enable: pagination.enable, - hitsPerPage: pagination.hitsPerPage - }, - 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 highlightProvider = config.syntax_highlighter || (config.highlight.enable ? 'highlight.js' : config.prismjs.enable ? 'prismjs' : null) - const { copy, language, height_limit, fullpage, macStyle, shrink } = theme.code_blocks - let highlight = JSON.stringify({ - plugin: highlightProvider, - 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 deleted file mode 100644 index 5c1c67d..0000000 --- a/themes/butterfly/layout/includes/head/config_site.pug +++ /dev/null @@ -1,25 +0,0 @@ -- - 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 deleted file mode 100644 index 3ef1af9..0000000 --- a/themes/butterfly/layout/includes/head/google_adsense.pug +++ /dev/null @@ -1,9 +0,0 @@ -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 deleted file mode 100644 index cefe9fc..0000000 --- a/themes/butterfly/layout/includes/head/preconnect.pug +++ /dev/null @@ -1,35 +0,0 @@ -- - 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 deleted file mode 100644 index 816026a..0000000 --- a/themes/butterfly/layout/includes/head/pwa.pug +++ /dev/null @@ -1,13 +0,0 @@ -- 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 deleted file mode 100644 index 8947644..0000000 --- a/themes/butterfly/layout/includes/head/site_verification.pug +++ /dev/null @@ -1,3 +0,0 @@ -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 deleted file mode 100644 index a45206f..0000000 --- a/themes/butterfly/layout/includes/head/structured_data.pug +++ /dev/null @@ -1,67 +0,0 @@ -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 domain = new URL(config.url).hostname; - const alternateNames = theme.structured_data.alternate_name || []; - - if (config.subtitle) { - alternateNames.push(config.subtitle); - } - - if (domain) { - alternateNames.push(domain); - } - - const jsonLd = { - "@context": "https://schema.org", - "@type": "WebSite", - "name": config.title, - "alternateName": alternateNames, - "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 deleted file mode 100644 index 481db70..0000000 --- a/themes/butterfly/layout/includes/header/index.pug +++ /dev/null @@ -1,52 +0,0 @@ -- - 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 deleted file mode 100644 index 6302ee4..0000000 --- a/themes/butterfly/layout/includes/header/menu_item.pug +++ /dev/null @@ -1,27 +0,0 @@ -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 deleted file mode 100644 index de3da72..0000000 --- a/themes/butterfly/layout/includes/header/nav.pug +++ /dev/null @@ -1,54 +0,0 @@ -nav#nav - //- 左侧区域:包含指纹菜单和网站名 - span#blog-info - #ls-menu-container.ls-mobile-safe - i.fas.fa-fingerprint - #ls-menu-panel.ls-mobile-safe-panel - .ls-section - .ls-title 😀 个人网站 - .ls-grid - a(href="/") #[i.fas.fa-rss] 个人博客 - a(href="https://github.com/bishshi") #[i.fab.fa-github] Github - a(href="https://space.bilibili.com/20665809") #[i.fab.fa-bilibili] 哔哩哔哩 - .ls-section - .ls-title 😎 常用服务 - .ls-grid - a(href="https://git.biss.click/biss") #[i.fas.fa-code] 代码仓库 - a(href="https://mm.biss.click") #[i.fas.fa-pen-nib] 日常说说 - a(href="https://pic.biss.click") #[i.fas.fa-image] 图床 - a(href="https://git.biss.click") #[i.fas.fa-code-branch] 代码仓库 - .ls-section - .ls-title 🛸 实用工具 - .ls-grid - a(href="https://cover.biss.click") #[i.fas.fa-palette] 封面设计 - a(href="https://doc.biss.click") #[i.fas.fa-file] 文档服务 - a(href="https://status.biss.click") #[i.fas.fa-server] 服务监测 - a(href="https://typesense.biss.click") #[i.fas.fa-magnifying-glass] 搜索后端 - - 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 - - //- 中间区域:关键!必须在 nav 下一级,以便 JS 切换类名 - if globalPageType === 'post' && theme.nav.display_post_title - a.nav-page-title(href='javascript:void(0);' onclick='btf.scrollToDest(0, 500)') - span.site-name=(page.title || config.title) - - //- 右侧区域 - #nav-right - if theme.menu - #menus - != partial('includes/header/menu_item', {}, {cache: true}) - - if theme.search.use || true - #random-post-button - a.site-page.social-icon#random-post-link(href='javascript:void(0);' onclick='randomPost()') - i.fas.fa-solid.fa-shuffle - #search-button - a.site-page.social-icon.search-typesense-trigger - i.fas.fa-search.fa-fw - #toggle-menu - span.site-page - i.fas.fa-bars.fa-fw diff --git a/themes/butterfly/layout/includes/header/post-info.pug b/themes/butterfly/layout/includes/header/post-info.pug deleted file mode 100644 index f346f83..0000000 --- a/themes/butterfly/layout/includes/header/post-info.pug +++ /dev/null @@ -1,149 +0,0 @@ -- 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 deleted file mode 100644 index 81b47cc..0000000 --- a/themes/butterfly/layout/includes/header/social.pug +++ /dev/null @@ -1,8 +0,0 @@ -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 deleted file mode 100644 index 2df72fe..0000000 --- a/themes/butterfly/layout/includes/layout.pug +++ /dev/null @@ -1,61 +0,0 @@ -- 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 - if !Array.isArray(theme.background) - #web_bg.bg-animation(style=getBgPath(theme.background)) - else - #web_bg.bg-animation - - const bgStyleArr = theme.background.map(getBgPath) - script. - (() => { - const arr = !{JSON.stringify(bgStyleArr)} - const webBgDiv = document.getElementById('web_bg') - - const setRandomBg = () => { - webBgDiv.style = arr[Math.floor(Math.random() * arr.length)] - requestAnimationFrame(() => webBgDiv.classList.add('bg-animation')) - } - - document.addEventListener('pjax:send', () => { - webBgDiv.style = '' - webBgDiv.classList.remove('bg-animation') - }) - - document.addEventListener('pjax:complete', setRandomBg) - document.addEventListener('DOMContentLoaded', setRandomBg) - })() - - !=partial('includes/sidebar', {}, {cache: true}) - - #body-wrap(class=pageType) - include ./header/index.pug - include ./others/memos_home.pug - - main#content-inner.layout(class=hideAside) - if body - div!= body - else - block content - if theme.aside.enable && page.aside !== false - include widget/index.pug - - - const footerBg = theme.footer_img - - const footer_bg = footerBg ? footerBg === true ? bg_img : getBgPath(footerBg) : '' - footer#footer(style=footer_bg) - !=partial('includes/footer', {}, {cache: true}) - - include ./rightside.pug - include ./additional-js.pug - include ./rightmenu.pug diff --git a/themes/butterfly/layout/includes/loading/fullpage-loading.pug b/themes/butterfly/layout/includes/loading/fullpage-loading.pug deleted file mode 100644 index 2cea198..0000000 --- a/themes/butterfly/layout/includes/loading/fullpage-loading.pug +++ /dev/null @@ -1,42 +0,0 @@ -#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 deleted file mode 100644 index 6a6facc..0000000 --- a/themes/butterfly/layout/includes/loading/index.pug +++ /dev/null @@ -1,5 +0,0 @@ -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 deleted file mode 100644 index 4cf2a4e..0000000 --- a/themes/butterfly/layout/includes/loading/pace.pug +++ /dev/null @@ -1,12 +0,0 @@ -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 deleted file mode 100644 index acc5d9f..0000000 --- a/themes/butterfly/layout/includes/mixins/article-sort.pug +++ /dev/null @@ -1,23 +0,0 @@ -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 deleted file mode 100644 index fc2e436..0000000 --- a/themes/butterfly/layout/includes/mixins/indexPostUI.pug +++ /dev/null @@ -1,119 +0,0 @@ -mixin indexPostUI() - - const indexLayout = theme.index_layout - - const masonryLayoutClass = [6, 7].includes(indexLayout) ? '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 postCover = article.cover - - const noCover = article.cover === false || !theme.cover.index_enable ? 'no-cover' : '' - - if postCover && 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(postCover) onerror=`this.onerror=null;this.src='${url_for(theme.error_img.post_page)}'` alt=title) - else - div.post-bg(style=`background: ${postCover}`) - .recent-post-info(class=noCover) - 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 isUpdatedType = theme.post_meta.page.date_type === 'updated' - - const dateType = isUpdatedType ? 'updated' : 'date' - - const dateIcon = isUpdatedType ? 'fas fa-history' : 'far fa-calendar-alt' - - const dateTitle = isUpdatedType ? _p('post.updated') : _p('post.created') - i(class=dateIcon) - span.article-meta-label= dateTitle - time(datetime=date_xml(article[dateType]) title=dateTitle + ' ' + full_date(article[dateType]))= date(article[dateType], 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 - - const commentSystem = theme.comments.use[0] - - const commentLink = url_for(link) + '#post-comment' - - case commentSystem - 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=commentLink) - span.valine-comment-count(data-xid=url_for(link)) - i.fa-solid.fa-spinner.fa-spin - when 'Waline' - +countBlockInIndex - a(href=commentLink) - span.waline-comment-count(data-path=url_for(link)) - i.fa-solid.fa-spinner.fa-spin - when 'Twikoo' - +countBlockInIndex - a.twikoo-count(href=commentLink) - i.fa-solid.fa-spinner.fa-spin - when 'Facebook Comments' - +countBlockInIndex - a(href=commentLink) - span.fb-comments-count(data-href=urlNoIndex(article.permalink)) - when 'Remark42' - +countBlockInIndex - a(href=commentLink) - span.remark42__counter(data-url=urlNoIndex(article.permalink)) - i.fa-solid.fa-spinner.fa-spin - when 'Artalk' - +countBlockInIndex - a(href=commentLink) - 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/others/memos_home.pug b/themes/butterfly/layout/includes/others/memos_home.pug deleted file mode 100644 index 6ecb1b7..0000000 --- a/themes/butterfly/layout/includes/others/memos_home.pug +++ /dev/null @@ -1,7 +0,0 @@ -if (is_home()) - #main_top - #bber-talk.cardHover.bb_talk_swipper(onclick=`pjax.loadUrl("/shuoshuo/")`) - svg.icon(t='1660960757124', viewBox='0 0 1024 1024', version='1.1', xmlns='http://www.w3.org/2000/svg', p-id='3946', width='200', height='200') - path(d='M526.432 924.064c-20.96 0-44.16-12.576-68.96-37.344L274.752 704H192c-52.928 0-96-43.072-96-96V416c0-52.928 43.072-96 96-96h82.752l182.624-182.624c24.576-24.576 47.744-37.024 68.864-37.024C549.184 100.352 576 116 576 160v704c0 44.352-26.72 60.064-49.568 60.064zM192 384c-17.632 0-32 14.368-32 32v192c0 17.664 14.368 32 32 32h96c8.48 0 16.64 3.36 22.624 9.376l192.064 192.096c3.392 3.36 6.496 6.208 9.312 8.576V174.016a145.824 145.824 0 0 0-9.376 8.608l-192 192C304.64 380.64 296.48 384 288 384h-96zM687.584 730.368a31.898 31.898 0 0 1-18.656-6.016c-14.336-10.304-17.632-30.304-7.328-44.672l12.672-17.344C707.392 617.44 736 578.624 736 512c0-69.024-25.344-102.528-57.44-144.928-5.664-7.456-11.328-15.008-16.928-22.784-10.304-14.336-7.04-34.336 7.328-44.672 14.368-10.368 34.336-7.04 44.672 7.328 5.248 7.328 10.656 14.464 15.968 21.504C764.224 374.208 800 421.504 800 512c0 87.648-39.392 141.12-74.144 188.32l-12.224 16.736c-6.272 8.704-16.064 13.312-26.048 13.312z', p-id='3947') - path(d='M796.448 839.008a31.906 31.906 0 0 1-21.088-7.936c-13.28-11.648-14.624-31.872-2.976-45.152C836.608 712.672 896 628.864 896 512s-59.392-200.704-123.616-273.888c-11.648-13.312-10.304-33.504 2.976-45.184 13.216-11.648 33.44-10.336 45.152 2.944C889.472 274.56 960 373.6 960 512s-70.528 237.472-139.488 316.096c-6.368 7.232-15.2 10.912-24.064 10.912z', p-id='3948') - ul.talk-list 说说加载中。。。 diff --git a/themes/butterfly/layout/includes/page/404.pug b/themes/butterfly/layout/includes/page/404.pug deleted file mode 100644 index 9444204..0000000 --- a/themes/butterfly/layout/includes/page/404.pug +++ /dev/null @@ -1,8 +0,0 @@ -- 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/about.pug b/themes/butterfly/layout/includes/page/about.pug deleted file mode 100644 index e69de29..0000000 diff --git a/themes/butterfly/layout/includes/page/categories.pug b/themes/butterfly/layout/includes/page/categories.pug deleted file mode 100644 index f2dd8ac..0000000 --- a/themes/butterfly/layout/includes/page/categories.pug +++ /dev/null @@ -1,2 +0,0 @@ -.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 deleted file mode 100644 index 4a7d515..0000000 --- a/themes/butterfly/layout/includes/page/default-page.pug +++ /dev/null @@ -1,2 +0,0 @@ -#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 deleted file mode 100644 index f94fffc..0000000 --- a/themes/butterfly/layout/includes/page/flink.pug +++ /dev/null @@ -1,82 +0,0 @@ -#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 deleted file mode 100644 index 37f3c4b..0000000 --- a/themes/butterfly/layout/includes/page/shuoshuo.pug +++ /dev/null @@ -1,332 +0,0 @@ -//- - author: -//- avatar: -//- date: -//- content: -//- tags: -//- - tag1 -//- - tag2 - -- page.toc = false - -#article-container - if page.shuoshuo_url || (site.data.shuoshuo && site.data.shuoshuo.length) - 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) - } - } - })() - - - - const localDate = page.shuoshuo_url ? [] : shuoshuoFN(site.data.shuoshuo, page) - - if !page.shuoshuo_url - script(type='application/json' id='shuoshuo-data')!= safeJSON(localDate) - - - const { enable, native, placeholder, field } = theme.lazyload - 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 addLazyload = str => { - const config = { - enable: !{Boolean(enable)}, - native: !{Boolean(native)}, - field: '!{field}', - placeholder: '!{url_for(placeholder)}', - } - - if (!config.enable || config.field !== 'site') return str - const parser = new DOMParser() - const doc = parser.parseFromString(str, 'text/html') - const images = doc.querySelectorAll('img') - - images.forEach(img => { - if (config.native) { - img.setAttribute('loading', 'lazy') - } else { - const src = img.getAttribute('src') - img.setAttribute('data-lazy-src', src) - - if (config.placeholder) { - img.setAttribute('src', config.placeholder) - } else { - img.removeAttribute('src') - } - } - }) - return doc.body.innerHTML - } - - let currentPage = 1 - const itemsPerPage = 8 - let totalPages = 0 - let data = [] - let inputEventsAttached = false // Flag to mark if input event listeners have been added - - 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}'}
    - -
    -
    -
    ${addLazyload(item.content)}
    - -
    - ${commentContainer} -
    ` - }).join('') - - const container = document.getElementById('article-container') - container.innerHTML = content - - window.lazyLoadInstance && window.lazyLoadInstance.update() - btf.loadLightbox(document.querySelectorAll('#article-container img:not(.no-lightbox)')) - } - - const renderNavigation = () => { - const container = document.getElementById('article-container') - const existingNav = container.nextElementSibling - if (existingNav && existingNav.classList.contains('shuoshuo-navigation')) { - existingNav.remove() - } - - const pageInfoTemplate = '#{__('pagination.page_info')}' - const pageInfoText = pageInfoTemplate - .replace(/\$\{current}/g, currentPage) - .replace(/\$\{total}/g, totalPages) - - const navHtml = ` -
    - - ${pageInfoText} - - -
    - ` - container.insertAdjacentHTML('afterend', navHtml) - - // Add input validation event listeners (only once) - if (!inputEventsAttached) { - setTimeout(() => { - const input = document.querySelector('.shuoshuo-page-input') - if (input) { - // Clear placeholder when clicking the input box - input.addEventListener('focus', (event) => { - event.target.placeholder = '' - }) - - // Restore placeholder if no content when losing focus - input.addEventListener('blur', (event) => { - if (!event.target.value.trim()) { - event.target.placeholder = currentPage - } - }) - - input.addEventListener('input', (event) => { - const value = parseInt(event.target.value) || 0 - let wasInvalid = false - - if (value > totalPages) { - event.target.value = totalPages - wasInvalid = true - } else if (value < 1 && event.target.value !== '') { - event.target.value = 1 - wasInvalid = true - } - - // If value is corrected, show red and shake effect - if (wasInvalid) { - event.target.classList.add('invalid') - setTimeout(() => { - event.target.classList.remove('invalid') - }, 500) - } - }) - - inputEventsAttached = true // Mark that event listeners have been added - } - }, 0) - } - } - - const renderPage = (page) => { - const start = (page - 1) * itemsPerPage - const end = start + itemsPerPage - const pageData = data.slice(start, end) - renderData(pageData) - renderNavigation() - } - - window.shuoshuoPrevPage = () => { - if (currentPage > 1) { - currentPage-- - renderPage(currentPage) - } - } - - window.shuoshuoNextPage = () => { - if (currentPage < totalPages) { - currentPage++ - renderPage(currentPage) - } - } - - window.shuoshuoGoToPage = (page) => { - if (typeof page === 'number') { - // Directly jump to the specified page - if (page >= 1 && page <= totalPages && page !== currentPage) { - currentPage = page - renderPage(currentPage) - } - } else { - // Get page from input box - const input = document.querySelector('.shuoshuo-page-input') - const inputValue = input.value.trim() - const inputPage = inputValue === '' ? currentPage : parseInt(inputValue) - if (inputPage >= 1 && inputPage <= totalPages && inputPage !== currentPage) { - currentPage = inputPage - renderPage(currentPage) - } else if (inputValue === '') { - // If input box is empty, re-render current page (update placeholder) - renderPage(currentPage) - } - } - } - - window.shuoshuoHandleKeyDown = (event) => { - const input = event.target - const value = input.value + event.key - - // Allow delete, arrow keys, backspace, etc. - if (event.key === 'Enter' || event.key === 'Backspace' || event.key === 'Delete' || - event.key === 'ArrowLeft' || event.key === 'ArrowRight' || - event.key === 'Tab' || event.ctrlKey || event.metaKey) { - if (event.key === 'Enter') { - window.shuoshuoGoToPage() - } - return - } - - // Only allow numbers - if (!/^\d$/.test(event.key)) { - event.preventDefault() - return - } - - // Check if the value after input exceeds the range - const newValue = parseInt(value) || 0 - if (newValue > totalPages || (value.length > 1 && newValue === 0)) { - event.preventDefault() - // Add red and shake effect - input.classList.add('invalid') - setTimeout(() => { - input.classList.remove('invalid') - }, 500) - } - } - - const loadShuoshuo = async () => { - try { - let originData = [] - if (!{Boolean(page.shuoshuo_url)}) { - const response = await fetch('!{url_for(page.shuoshuo_url)}') - originData = await response.json() - } else { - const dataElement = document.getElementById('shuoshuo-data') - originData = dataElement ? JSON.parse(dataElement.textContent) : [] - } - - data = filterDataByLimit(sortDataByDate(originData), limitConfig) - - totalPages = Math.ceil(data.length / itemsPerPage) - - renderPage(currentPage) - } catch (error) { - console.error(error) - } - }; - - window.pjax ? loadShuoshuo() : window.addEventListener('load', loadShuoshuo) - })() \ No newline at end of file diff --git a/themes/butterfly/layout/includes/page/tags.pug b/themes/butterfly/layout/includes/page/tags.pug deleted file mode 100644 index d185173..0000000 --- a/themes/butterfly/layout/includes/page/tags.pug +++ /dev/null @@ -1,2 +0,0 @@ -.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', custom_colors: page.custom_colors}) diff --git a/themes/butterfly/layout/includes/pagination.pug b/themes/butterfly/layout/includes/pagination.pug deleted file mode 100644 index b1637b8..0000000 --- a/themes/butterfly/layout/includes/pagination.pug +++ /dev/null @@ -1,38 +0,0 @@ -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.pagination_cover || 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 deleted file mode 100644 index ef72772..0000000 --- a/themes/butterfly/layout/includes/post/outdate-notice.pug +++ /dev/null @@ -1,8 +0,0 @@ -- 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 deleted file mode 100644 index c76f4b4..0000000 --- a/themes/butterfly/layout/includes/post/post-copyright.pug +++ /dev/null @@ -1,23 +0,0 @@ -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 deleted file mode 100644 index 209fa4f..0000000 --- a/themes/butterfly/layout/includes/post/post-summary.pug +++ /dev/null @@ -1,7 +0,0 @@ -.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 deleted file mode 100644 index fe62f14..0000000 --- a/themes/butterfly/layout/includes/post/reward.pug +++ /dev/null @@ -1,12 +0,0 @@ -.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 deleted file mode 100644 index 229808b..0000000 --- a/themes/butterfly/layout/includes/rightmenu.pug +++ /dev/null @@ -1,75 +0,0 @@ -#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 deleted file mode 100644 index fd7182c..0000000 --- a/themes/butterfly/layout/includes/rightside.pug +++ /dev/null @@ -1,54 +0,0 @@ -- 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 deleted file mode 100644 index 8947f36..0000000 --- a/themes/butterfly/layout/includes/sidebar.pug +++ /dev/null @@ -1,5 +0,0 @@ -if theme.menu - #sidebar - #menu-mask - #sidebar-menus - #sidebar-aside-wrapper diff --git a/themes/butterfly/layout/includes/third-party/abcjs/abcjs.pug b/themes/butterfly/layout/includes/third-party/abcjs/abcjs.pug deleted file mode 100644 index d9e58b1..0000000 --- a/themes/butterfly/layout/includes/third-party/abcjs/abcjs.pug +++ /dev/null @@ -1,46 +0,0 @@ -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.textContent, 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 deleted file mode 100644 index d187d55..0000000 --- a/themes/butterfly/layout/includes/third-party/abcjs/index.pug +++ /dev/null @@ -1,3 +0,0 @@ -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 deleted file mode 100644 index cf875b6..0000000 --- a/themes/butterfly/layout/includes/third-party/aplayer.pug +++ /dev/null @@ -1,23 +0,0 @@ -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 deleted file mode 100644 index b5a1056..0000000 --- a/themes/butterfly/layout/includes/third-party/card-post-count/artalk.pug +++ /dev/null @@ -1,31 +0,0 @@ -- 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 deleted file mode 100644 index 92a2d5b..0000000 --- a/themes/butterfly/layout/includes/third-party/card-post-count/disqus.pug +++ /dev/null @@ -1,25 +0,0 @@ -- 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 deleted file mode 100644 index e6daba1..0000000 --- a/themes/butterfly/layout/includes/third-party/card-post-count/fb.pug +++ /dev/null @@ -1,18 +0,0 @@ -- 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 deleted file mode 100644 index 5b2685c..0000000 --- a/themes/butterfly/layout/includes/third-party/card-post-count/index.pug +++ /dev/null @@ -1,16 +0,0 @@ -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 deleted file mode 100644 index b67164f..0000000 --- a/themes/butterfly/layout/includes/third-party/card-post-count/remark42.pug +++ /dev/null @@ -1,18 +0,0 @@ -- 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 deleted file mode 100644 index 8c58e1c..0000000 --- a/themes/butterfly/layout/includes/third-party/card-post-count/twikoo.pug +++ /dev/null @@ -1,39 +0,0 @@ -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 deleted file mode 100644 index 1bbddde..0000000 --- a/themes/butterfly/layout/includes/third-party/card-post-count/valine.pug +++ /dev/null @@ -1,20 +0,0 @@ -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 deleted file mode 100644 index a8faf96..0000000 --- a/themes/butterfly/layout/includes/third-party/card-post-count/waline.pug +++ /dev/null @@ -1,21 +0,0 @@ -- 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 deleted file mode 100644 index 0fad991..0000000 --- a/themes/butterfly/layout/includes/third-party/chat/chatra.pug +++ /dev/null @@ -1,38 +0,0 @@ -//- 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 deleted file mode 100644 index 08a1916..0000000 --- a/themes/butterfly/layout/includes/third-party/chat/crisp.pug +++ /dev/null @@ -1,32 +0,0 @@ -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 deleted file mode 100644 index 273d040..0000000 --- a/themes/butterfly/layout/includes/third-party/chat/index.pug +++ /dev/null @@ -1,7 +0,0 @@ -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 deleted file mode 100644 index f9d40ea..0000000 --- a/themes/butterfly/layout/includes/third-party/chat/tidio.pug +++ /dev/null @@ -1,45 +0,0 @@ -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 deleted file mode 100644 index c98ad34..0000000 --- a/themes/butterfly/layout/includes/third-party/comments/artalk.pug +++ /dev/null @@ -1,73 +0,0 @@ -- 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('!{url_for(theme.asset.artalk_css)}') - await btf.getScript('!{url_for(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 deleted file mode 100644 index eeca5c4..0000000 --- a/themes/butterfly/layout/includes/third-party/comments/disqus.pug +++ /dev/null @@ -1,80 +0,0 @@ -- 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 deleted file mode 100644 index ae7ff06..0000000 --- a/themes/butterfly/layout/includes/third-party/comments/disqusjs.pug +++ /dev/null @@ -1,87 +0,0 @@ -- 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 deleted file mode 100644 index c62fa30..0000000 --- a/themes/butterfly/layout/includes/third-party/comments/facebook_comments.pug +++ /dev/null @@ -1,64 +0,0 @@ -- 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 deleted file mode 100644 index c370e98..0000000 --- a/themes/butterfly/layout/includes/third-party/comments/giscus.pug +++ /dev/null @@ -1,82 +0,0 @@ -- 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 deleted file mode 100644 index d13bcb3..0000000 --- a/themes/butterfly/layout/includes/third-party/comments/gitalk.pug +++ /dev/null @@ -1,64 +0,0 @@ -- 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 deleted file mode 100644 index 09f06f7..0000000 --- a/themes/butterfly/layout/includes/third-party/comments/index.pug +++ /dev/null @@ -1,46 +0,0 @@ -- 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 deleted file mode 100644 index bf1d872..0000000 --- a/themes/butterfly/layout/includes/third-party/comments/js.pug +++ /dev/null @@ -1,26 +0,0 @@ -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 deleted file mode 100644 index eab9904..0000000 --- a/themes/butterfly/layout/includes/third-party/comments/livere.pug +++ /dev/null @@ -1,47 +0,0 @@ -- 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 deleted file mode 100644 index cf648fe..0000000 --- a/themes/butterfly/layout/includes/third-party/comments/remark42.pug +++ /dev/null @@ -1,78 +0,0 @@ -- 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 deleted file mode 100644 index 31fec77..0000000 --- a/themes/butterfly/layout/includes/third-party/comments/twikoo.pug +++ /dev/null @@ -1,64 +0,0 @@ -- 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 deleted file mode 100644 index 4836557..0000000 --- a/themes/butterfly/layout/includes/third-party/comments/utterances.pug +++ /dev/null @@ -1,63 +0,0 @@ -- 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 deleted file mode 100644 index 9ac2c43..0000000 --- a/themes/butterfly/layout/includes/third-party/comments/valine.pug +++ /dev/null @@ -1,60 +0,0 @@ -- 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 deleted file mode 100644 index 99657e7..0000000 --- a/themes/butterfly/layout/includes/third-party/comments/waline.pug +++ /dev/null @@ -1,61 +0,0 @@ -- 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 deleted file mode 100644 index a3b2995..0000000 --- a/themes/butterfly/layout/includes/third-party/effect.pug +++ /dev/null @@ -1,35 +0,0 @@ -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 deleted file mode 100644 index 235d8e4..0000000 --- a/themes/butterfly/layout/includes/third-party/math/chartjs.pug +++ /dev/null @@ -1,91 +0,0 @@ -- 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 deleted file mode 100644 index 93302f8..0000000 --- a/themes/butterfly/layout/includes/third-party/math/index.pug +++ /dev/null @@ -1,14 +0,0 @@ -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 deleted file mode 100644 index 0e4195d..0000000 --- a/themes/butterfly/layout/includes/third-party/math/katex.pug +++ /dev/null @@ -1,16 +0,0 @@ -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 deleted file mode 100644 index 019d779..0000000 --- a/themes/butterfly/layout/includes/third-party/math/mathjax.pug +++ /dev/null @@ -1,78 +0,0 @@ -//- Mathjax 4/5 -- const { tags, enableMenu } = theme.math.mathjax -script. - (() => { - const loadMathjax = () => { - if (!window.MathJax) { - window.MathJax = { - loader: { - load: [ - // Four font extension packages (optional) - //- '[tex]/bbm', - //- '[tex]/bboldx', - //- '[tex]/dsfont', - '[tex]/mhchem' - ], - paths: { - 'mathjax-newcm': '[mathjax]/../@mathjax/mathjax-newcm-font', - - //- // Four font extension packages (optional) - //- 'mathjax-bbm-extension': '[mathjax]/../@mathjax/mathjax-bbm-font-extension', - //- 'mathjax-bboldx-extension': '[mathjax]/../@mathjax/mathjax-bboldx-font-extension', - //- 'mathjax-dsfont-extension': '[mathjax]/../@mathjax/mathjax-dsfont-font-extension', - 'mathjax-mhchem-extension': '[mathjax]/../@mathjax/mathjax-mhchem-font-extension' - } - }, - output: { - font: 'mathjax-newcm', - }, - tex: { - inlineMath: [['$', '$'], ['\\(', '\\)']], - tags: '!{tags}', - packages: { - '[+]': [ - 'mhchem' - ] - } - }, - chtml: { - scale: 1.1 - }, - options: { - enableMenu: !{enableMenu}, - menuOptions: { - settings: { - enrich: false // Turn off Braille and voice narration text automatic generation - } - }, - 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 deleted file mode 100644 index 3a745db..0000000 --- a/themes/butterfly/layout/includes/third-party/math/mermaid.pug +++ /dev/null @@ -1,323 +0,0 @@ -script. - (() => { - const parseViewBox = viewBox => { - if (!viewBox) return null - const parts = viewBox.trim().split(/[\s,]+/).map(n => Number(n)) - if (parts.length !== 4 || parts.some(n => Number.isNaN(n))) return null - return parts - } - - const getSvgViewBox = svg => { - const attr = parseViewBox(svg.getAttribute('viewBox')) - if (attr) return attr - - // Fallback: use bbox to build a viewBox - try { - const bbox = svg.getBBox() - if (bbox && bbox.width && bbox.height) return [bbox.x, bbox.y, bbox.width, bbox.height] - } catch (e) { - // getBBox may fail on some edge cases; ignore - } - - const w = Number(svg.getAttribute('width')) || 0 - const h = Number(svg.getAttribute('height')) || 0 - if (w > 0 && h > 0) return [0, 0, w, h] - return [0, 0, 100, 100] - } - - const setSvgViewBox = (svg, vb) => { - svg.setAttribute('viewBox', `${vb[0]} ${vb[1]} ${vb[2]} ${vb[3]}`) - } - - const clamp = (v, min, max) => Math.max(min, Math.min(max, v)) - - const openSvgInNewTab = ({ source, initViewBox }) => { - const getClonedSvg = () => { - if (typeof source === 'string') { - const template = document.createElement('template') - template.innerHTML = source.trim() - const svg = template.content.querySelector('svg') - return svg ? svg.cloneNode(true) : null - } - if (source && typeof source.cloneNode === 'function') { - return source.cloneNode(true) - } - return null - } - - const clone = getClonedSvg() - if (!clone) return - if (initViewBox && initViewBox.length === 4) { - clone.setAttribute('viewBox', initViewBox.join(' ')) - } - if (!clone.getAttribute('xmlns')) clone.setAttribute('xmlns', 'http://www.w3.org/2000/svg') - if (!clone.getAttribute('xmlns:xlink') && clone.outerHTML.includes('xlink:')) { - clone.setAttribute('xmlns:xlink', 'http://www.w3.org/1999/xlink') - } - // inject background to match current theme - const isDark = document.documentElement.getAttribute('data-theme') === 'dark' - const bg = getComputedStyle(document.body).backgroundColor || (isDark ? '#1e1e1e' : '#ffffff') - if (!clone.style.background) clone.style.background = bg - - const serializer = new XMLSerializer() - const svgSource = serializer.serializeToString(clone) - const htmlSource = ` - - ${svgSource}` - const blob = new Blob([htmlSource], { type: 'text/html;charset=utf-8' }) - const url = URL.createObjectURL(blob) - window.open(url, '_blank', 'noopener') - setTimeout(() => URL.revokeObjectURL(url), 30000) - } - - const attachMermaidViewerButton = wrap => { - let btn = wrap.querySelector('.mermaid-open-btn') - if (!btn) { - btn = document.createElement('button') - btn.type = 'button' - btn.className = 'mermaid-open-btn' - wrap.appendChild(btn) - } - - btn.innerHTML = '' - - if (!btn.__mermaidViewerBound) { - btn.addEventListener('click', e => { - e.preventDefault() - e.stopPropagation() - const svg = wrap.__mermaidOriginalSvg || wrap.querySelector('svg') - if (!svg) return - const initViewBox = wrap.__mermaidInitViewBox - if (typeof svg === 'string') { - openSvgInNewTab({ source: svg, initViewBox }) - return - } - openSvgInNewTab({ source: svg, initViewBox }) - }) - btn.__mermaidViewerBound = true - } - } - - // Zoom around a point (px, py) in the SVG viewport (in viewBox coordinates) - const zoomAtPoint = (vb, factor, px, py) => { - const w = vb[2] * factor - const h = vb[3] * factor - const nx = px - (px - vb[0]) * factor - const ny = py - (py - vb[1]) * factor - return [nx, ny, w, h] - } - - const initMermaidGestures = wrap => { - const svg = wrap.querySelector('svg') - if (!svg) return - - // Ensure viewBox exists so gestures always work - const initVb = getSvgViewBox(svg) - wrap.__mermaidInitViewBox = initVb - wrap.__mermaidCurViewBox = initVb.slice() - setSvgViewBox(svg, initVb) - - // Avoid binding multiple times on themeChange/pjax - if (wrap.__mermaidGestureBound) return - wrap.__mermaidGestureBound = true - - // Helper: map client (viewport) coordinate -> viewBox coordinate - const clientToViewBox = (clientX, clientY) => { - const rect = svg.getBoundingClientRect() - const vb = wrap.__mermaidCurViewBox || getSvgViewBox(svg) - const x = vb[0] + (clientX - rect.left) * (vb[2] / rect.width) - const y = vb[1] + (clientY - rect.top) * (vb[3] / rect.height) - return { x, y, rect, vb } - } - - const state = { - pointers: new Map(), - startVb: null, - startDist: 0, - startCenter: null - } - - const clampVb = vb => { - const init = wrap.__mermaidInitViewBox || vb - const minW = init[2] * 0.1 - const maxW = init[2] * 10 - const minH = init[3] * 0.1 - const maxH = init[3] * 10 - vb[2] = clamp(vb[2], minW, maxW) - vb[3] = clamp(vb[3], minH, maxH) - return vb - } - - const setCurVb = vb => { - vb = clampVb(vb) - wrap.__mermaidCurViewBox = vb - setSvgViewBox(svg, vb) - } - - const onPointerDown = e => { - // Allow only primary button for mouse - if (e.pointerType === 'mouse' && e.button !== 0) return - svg.setPointerCapture(e.pointerId) - state.pointers.set(e.pointerId, { x: e.clientX, y: e.clientY }) - - if (state.pointers.size === 1) { - state.startVb = (wrap.__mermaidCurViewBox || getSvgViewBox(svg)).slice() - } else if (state.pointers.size === 2) { - const pts = [...state.pointers.values()] - const dx = pts[0].x - pts[1].x - const dy = pts[0].y - pts[1].y - state.startDist = Math.hypot(dx, dy) - state.startVb = (wrap.__mermaidCurViewBox || getSvgViewBox(svg)).slice() - state.startCenter = { x: (pts[0].x + pts[1].x) / 2, y: (pts[0].y + pts[1].y) / 2 } - } - } - - const onPointerMove = e => { - if (!state.pointers.has(e.pointerId)) return - state.pointers.set(e.pointerId, { x: e.clientX, y: e.clientY }) - - // Pan with 1 pointer - if (state.pointers.size === 1 && state.startVb) { - const p = [...state.pointers.values()][0] - const prev = { x: e.clientX - e.movementX, y: e.clientY - e.movementY } - // movementX/Y unreliable on touch, compute from stored last position - const last = wrap.__mermaidLastSinglePointer || p - const dxClient = p.x - last.x - const dyClient = p.y - last.y - wrap.__mermaidLastSinglePointer = p - - const { rect } = clientToViewBox(p.x, p.y) - const vb = (wrap.__mermaidCurViewBox || getSvgViewBox(svg)).slice() - const dx = dxClient * (vb[2] / rect.width) - const dy = dyClient * (vb[3] / rect.height) - setCurVb([vb[0] - dx, vb[1] - dy, vb[2], vb[3]]) - return - } - - // Pinch zoom with 2 pointers - if (state.pointers.size === 2 && state.startVb && state.startDist > 0) { - const pts = [...state.pointers.values()] - const dx = pts[0].x - pts[1].x - const dy = pts[0].y - pts[1].y - const dist = Math.hypot(dx, dy) - if (!dist) return - const factor = state.startDist / dist // dist bigger => zoom in (viewBox smaller) - - const cx = (pts[0].x + pts[1].x) / 2 - const cy = (pts[0].y + pts[1].y) / 2 - const centerClient = { x: cx, y: cy } - - const pxy = clientToViewBox(centerClient.x, centerClient.y) - const cpx = pxy.x - const cpy = pxy.y - - const vb = zoomAtPoint(state.startVb, factor, cpx, cpy) - setCurVb(vb) - } - } - - const onPointerUpOrCancel = e => { - state.pointers.delete(e.pointerId) - if (state.pointers.size === 0) { - state.startVb = null - state.startDist = 0 - state.startCenter = null - wrap.__mermaidLastSinglePointer = null - } else if (state.pointers.size === 1) { - // reset single pointer baseline to avoid jump - wrap.__mermaidLastSinglePointer = [...state.pointers.values()][0] - } - } - - // Wheel zoom (mouse/trackpad) - const onWheel = e => { - // ctrlKey on mac trackpad pinch; we treat both as zoom - e.preventDefault() - const delta = e.deltaY - const zoomFactor = delta > 0 ? 1.1 : 0.9 - const { x, y } = clientToViewBox(e.clientX, e.clientY) - const vb = (wrap.__mermaidCurViewBox || getSvgViewBox(svg)).slice() - setCurVb(zoomAtPoint(vb, zoomFactor, x, y)) - } - - const onDblClick = () => { - const init = wrap.__mermaidInitViewBox - if (!init) return - wrap.__mermaidCurViewBox = init.slice() - setSvgViewBox(svg, init) - } - - svg.addEventListener('pointerdown', onPointerDown) - svg.addEventListener('pointermove', onPointerMove) - svg.addEventListener('pointerup', onPointerUpOrCancel) - svg.addEventListener('pointercancel', onPointerUpOrCancel) - svg.addEventListener('wheel', onWheel, { passive: false }) - svg.addEventListener('dblclick', onDblClick) - } - - 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 - - // Clear old render (themeChange/pjax will rerun) - const oldSvg = item.querySelector('svg') - if (oldSvg) oldSvg.remove() - item.__mermaidGestureBound = false - - const config = mermaidSrc.dataset.config ? JSON.parse(mermaidSrc.dataset.config) : {} - if (!config.theme) { - config.theme = theme - } - const mermaidThemeConfig = `%%{init: ${JSON.stringify(config)}}%%\n` - const mermaidID = `mermaid-${index}` - const mermaidDefinition = mermaidThemeConfig + mermaidSrc.textContent - - const renderFn = mermaid.render(mermaidID, mermaidDefinition) - const renderMermaid = svg => { - mermaidSrc.insertAdjacentHTML('afterend', svg) - if (!{theme.mermaid.zoom_pan}) initMermaidGestures(item) - item.__mermaidOriginalSvg = svg - if (!{theme.mermaid.open_in_new_tab}) attachMermaidViewerButton(item) - } - - - // 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) - })() diff --git a/themes/butterfly/layout/includes/third-party/newest-comments/artalk.pug b/themes/butterfly/layout/includes/third-party/newest-comments/artalk.pug deleted file mode 100644 index 4e703b2..0000000 --- a/themes/butterfly/layout/includes/third-party/newest-comments/artalk.pug +++ /dev/null @@ -1,67 +0,0 @@ -- 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 deleted file mode 100644 index f3b88f5..0000000 --- a/themes/butterfly/layout/includes/third-party/newest-comments/common.pug +++ /dev/null @@ -1,61 +0,0 @@ -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 deleted file mode 100644 index e26bb41..0000000 --- a/themes/butterfly/layout/includes/third-party/newest-comments/disqus-comment.pug +++ /dev/null @@ -1,34 +0,0 @@ -!= 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 deleted file mode 100644 index 7dc418c..0000000 --- a/themes/butterfly/layout/includes/third-party/newest-comments/github-issues.pug +++ /dev/null @@ -1,62 +0,0 @@ -!= 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 deleted file mode 100644 index a2f2c0d..0000000 --- a/themes/butterfly/layout/includes/third-party/newest-comments/index.pug +++ /dev/null @@ -1,34 +0,0 @@ -- 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 deleted file mode 100644 index 4321b3b..0000000 --- a/themes/butterfly/layout/includes/third-party/newest-comments/remark42.pug +++ /dev/null @@ -1,31 +0,0 @@ -- 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 deleted file mode 100644 index 772c8ec..0000000 --- a/themes/butterfly/layout/includes/third-party/newest-comments/twikoo-comment.pug +++ /dev/null @@ -1,45 +0,0 @@ -!= 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 deleted file mode 100644 index 08af70c..0000000 --- a/themes/butterfly/layout/includes/third-party/newest-comments/valine.pug +++ /dev/null @@ -1,51 +0,0 @@ -- 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 deleted file mode 100644 index 72743ea..0000000 --- a/themes/butterfly/layout/includes/third-party/newest-comments/waline.pug +++ /dev/null @@ -1,32 +0,0 @@ -- 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 deleted file mode 100644 index a4be0f2..0000000 --- a/themes/butterfly/layout/includes/third-party/pjax.pug +++ /dev/null @@ -1,73 +0,0 @@ -- 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) defer) -script. - document.addEventListener('DOMContentLoaded', () => { - 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 => { - try { - fn() - } catch (err) { - console.debug('Pjax callback failed:', err) - } - }) - } - - 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) { - !{theme.error_404 && theme.error_404.enable} - ? pjax.loadUrl('!{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 deleted file mode 100644 index 2019f82..0000000 --- a/themes/butterfly/layout/includes/third-party/prismjs.pug +++ /dev/null @@ -1,23 +0,0 @@ -- 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) defer) - script(src=url_for(prismjs_autoloader) defer) - if (line_number) - script(src=url_for(prismjs_lineNumber_js) defer) \ 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 deleted file mode 100644 index dd24962..0000000 --- a/themes/butterfly/layout/includes/third-party/search/algolia.pug +++ /dev/null @@ -1,34 +0,0 @@ -#algolia-search - .search-dialog - nav.search-nav - span.search-dialog-title= _p('search.title') - i.fas.fa-spinner.fa-pulse#loading-status(hidden) - button.search-close-button - i.fas.fa-times - - #algolia-search-input - .ais-SearchBox - form.ais-SearchBox-form(action="" role="search" novalidate="") - input.ais-SearchBox-input(type="search" placeholder=theme.search.placeholder || _p("search.input_placeholder") autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" maxlength="512" aria-label="Search") - button.ais-SearchBox-submit(type="submit" title="Submit the search query" style="display:none;") - svg.ais-SearchBox-submitIcon(width="10" height="10" viewBox="0 0 40 40" aria-hidden="true") - path(d="M26.804 29.01c-2.832 2.34-6.465 3.746-10.426 3.746C7.333 32.756 0 25.424 0 16.378 0 7.333 7.333 0 16.378 0c9.046 0 16.378 7.333 16.378 16.378 0 3.96-1.406 7.594-3.746 10.426l10.534 10.534c.607.607.61 1.59-.004 2.202-.61.61-1.597.61-2.202.004L26.804 29.01zm-10.426.627c7.323 0 13.26-5.936 13.26-13.26 0-7.32-5.937-13.257-13.26-13.257C9.056 3.12 3.12 9.056 3.12 16.378c0 7.323 5.936 13.26 13.258 13.26z") - hr - #algolia-search-results - #algolia-hits - #algolia-hits-empty(style="display:none;") - .ais-Hits(style="display:none;") - ol.ais-Hits-list - #algolia-pagination.ais-Pagination(style="display:none;") - ul.ais-Pagination-list - #algolia-info - span.ais-Stats-text - a.algolia-poweredBy(href="https://www.algolia.com/?utm_source=algoliasearch.js&utm_medium=website&utm_content=localhost&utm_campaign=poweredby" target="_blank" aria-label="Search by Algolia" rel="noopener noreferrer") - svg.ais-PoweredBy-logo(height="1.2em" viewBox="0 0 572 64" style="width: auto;") - path(fill="#36395A" d="M16 48.3c-3.4 0-6.3-.6-8.7-1.7A12.4 12.4 0 0 1 1.9 42C.6 40 0 38 0 35.4h6.5a6.7 6.7 0 0 0 3.9 6c1.4.7 3.3 1.1 5.6 1.1 2.2 0 4-.3 5.4-1a7 7 0 0 0 3-2.4 6 6 0 0 0 1-3.4c0-1.5-.6-2.8-1.9-3.7-1.3-1-3.3-1.6-5.9-1.8l-4-.4c-3.7-.3-6.6-1.4-8.8-3.4a10 10 0 0 1-3.3-7.9c0-2.4.6-4.6 1.8-6.4a12 12 0 0 1 5-4.3c2.2-1 4.7-1.6 7.5-1.6s5.5.5 7.6 1.6a12 12 0 0 1 5 4.4c1.2 1.8 1.8 4 1.8 6.7h-6.5a6.4 6.4 0 0 0-3.5-5.9c-1-.6-2.6-1-4.4-1s-3.2.3-4.4 1c-1.1.6-2 1.4-2.6 2.4-.5 1-.8 2-.8 3.1a5 5 0 0 0 1.5 3.6c1 1 2.6 1.7 4.7 1.9l4 .3c2.8.2 5.2.8 7.2 1.8 2.1 1 3.7 2.2 4.9 3.8a9.7 9.7 0 0 1 1.7 5.8c0 2.5-.7 4.7-2 6.6a13 13 0 0 1-5.6 4.4c-2.4 1-5.2 1.6-8.4 1.6Zm35.6 0c-2.6 0-4.8-.4-6.7-1.3a13 13 0 0 1-4.7-3.5 17.1 17.1 0 0 1-3.6-10.4v-1c0-2 .3-3.8 1-5.6a13 13 0 0 1 7.3-8.3 15 15 0 0 1 6.3-1.4A13.2 13.2 0 0 1 64 24.3c1 2.2 1.6 4.6 1.6 7.2V34H39.4v-4.3h21.8l-1.8 2.2c0-2-.3-3.7-.9-5.1a7.3 7.3 0 0 0-2.7-3.4c-1.2-.7-2.7-1.1-4.6-1.1s-3.4.4-4.7 1.3a8 8 0 0 0-2.9 3.6c-.6 1.5-.9 3.3-.9 5.4 0 2 .3 3.7 1 5.3a7.9 7.9 0 0 0 2.8 3.7c1.3.8 3 1.3 5 1.3s3.8-.5 5.1-1.3c1.3-1 2.1-2 2.4-3.2h6a11.8 11.8 0 0 1-7 8.7 16 16 0 0 1-6.4 1.2ZM80 48c-2.2 0-4-.3-5.7-1a8.4 8.4 0 0 1-3.7-3.3 9.7 9.7 0 0 1-1.3-5.2c0-2 .5-3.8 1.5-5.2a9 9 0 0 1 4.3-3.1c1.8-.7 4-1 6.7-1H89v4.1h-7.5c-2 0-3.4.5-4.4 1.4-1 1-1.6 2.1-1.6 3.6s.5 2.7 1.6 3.6c1 1 2.5 1.4 4.4 1.4 1.1 0 2.2-.2 3.2-.7 1-.4 1.9-1 2.6-2 .6-1 1-2.4 1-4.2l1.7 2.1c-.2 2-.7 3.8-1.5 5.2a9 9 0 0 1-3.4 3.3 12 12 0 0 1-5.3 1Zm9.5-.7v-8.8h-1v-10c0-1.8-.5-3.2-1.4-4.1-1-1-2.4-1.4-4.2-1.4a142.9 142.9 0 0 0-10.2.4v-5.6a74.8 74.8 0 0 1 8.6-.4c3 0 5.5.4 7.5 1.2s3.4 2 4.4 3.6c1 1.7 1.4 4 1.4 6.7v18.4h-5Zm12.9 0V17.8h5v12.3h-.2c0-4.2 1-7.4 2.8-9.5a11 11 0 0 1 8.3-3.1h1v5.6h-2a9 9 0 0 0-6.3 2.2c-1.5 1.5-2.2 3.6-2.2 6.4v15.6h-6.4Zm34.4 1a15 15 0 0 1-6.6-1.3c-1.9-.9-3.4-2-4.7-3.5a15.5 15.5 0 0 1-2.7-5c-.6-1.7-1-3.6-1-5.4v-1c0-2 .4-3.8 1-5.6a15 15 0 0 1 2.8-4.9c1.3-1.5 2.8-2.6 4.6-3.5a16.4 16.4 0 0 1 13.3.2c2 1 3.5 2.3 4.8 4a12 12 0 0 1 2 6H144c-.2-1.6-1-3-2.2-4.1a7.5 7.5 0 0 0-5.2-1.7 8 8 0 0 0-4.7 1.3 8 8 0 0 0-2.8 3.6 13.8 13.8 0 0 0 0 10.3c.6 1.5 1.5 2.7 2.8 3.6s2.8 1.3 4.8 1.3c1.5 0 2.7-.2 3.8-.8a7 7 0 0 0 2.6-2c.7-1 1-2 1.2-3.2h6.2a11 11 0 0 1-2 6.2 15.1 15.1 0 0 1-11.8 5.5Zm19.7-1v-40h6.4V31h-1.3c0-3 .4-5.5 1.1-7.6a9.7 9.7 0 0 1 3.5-4.8A9.9 9.9 0 0 1 172 17h.3c3.5 0 6 1.1 7.9 3.5 1.7 2.3 2.6 5.7 2.6 10v16.8h-6.4V29.6c0-2.1-.6-3.8-1.8-5a6.4 6.4 0 0 0-4.8-1.8c-2 0-3.7.7-5 2a7.8 7.8 0 0 0-1.9 5.5v17h-6.4Zm63.8 1a12.2 12.2 0 0 1-10.9-6.2 19 19 0 0 1-1.8-7.3h1.4v12.5h-5.1v-40h6.4v19.8l-2 3.5c.2-3.1.8-5.7 1.9-7.7a11 11 0 0 1 4.4-4.5c1.8-1 3.9-1.5 6.1-1.5a13.4 13.4 0 0 1 12.8 9.1c.7 1.9 1 3.8 1 6v1c0 2.2-.3 4.1-1 6a13.6 13.6 0 0 1-13.2 9.4Zm-1.2-5.5a8.4 8.4 0 0 0 7.9-5c.7-1.5 1.1-3.3 1.1-5.3s-.4-3.8-1.1-5.3a8.7 8.7 0 0 0-3.2-3.6 9.6 9.6 0 0 0-9.2-.2 8.5 8.5 0 0 0-3.3 3.2c-.8 1.4-1.3 3-1.3 5v2.3a9 9 0 0 0 1.3 4.8 9 9 0 0 0 3.4 3c1.4.7 2.8 1 4.4 1Zm27.3 3.9-10-28.9h6.5l9.5 28.9h-6Zm-7.5 12.2v-5.7h4.9c1 0 2-.1 2.9-.4a4 4 0 0 0 2-1.4c.4-.7.9-1.6 1.2-2.7l8.6-30.9h6.2l-9.3 32.4a14 14 0 0 1-2.5 5 8.9 8.9 0 0 1-4 2.8c-1.5.6-3.4.9-5.6.9h-4.4Zm9-12.2v-5.2h6.4v5.2H248Z") - path(fill="#003DFF" d="M534.4 9.1H528a.8.8 0 0 1-.7-.7V1.8c0-.4.2-.7.6-.8l6.5-1c.4 0 .8.2.9.6v7.8c0 .4-.4.7-.8.7zM428 35.2V.8c0-.5-.3-.8-.7-.8h-.2l-6.4 1c-.4 0-.7.4-.7.8v35c0 1.6 0 11.8 12.3 12.2.5 0 .8-.4.8-.8V43c0-.4-.3-.7-.6-.8-4.5-.5-4.5-6-4.5-7zm106.5-21.8H528c-.4 0-.7.4-.7.8v34c0 .4.3.8.7.8h6.5c.4 0 .8-.4.8-.8v-34c0-.5-.4-.8-.8-.8zm-17.7 21.8V.8c0-.5-.3-.8-.8-.8l-6.5 1c-.4 0-.7.4-.7.8v35c0 1.6 0 11.8 12.3 12.2.4 0 .8-.4.8-.8V43c0-.4-.3-.7-.7-.8-4.4-.5-4.4-6-4.4-7zm-22.2-20.6a16.5 16.5 0 0 1 8.6 9.3c.8 2.2 1.3 4.8 1.3 7.5a19.4 19.4 0 0 1-4.6 12.6 14.8 14.8 0 0 1-5.2 3.6c-2 .9-5.2 1.4-6.8 1.4a21 21 0 0 1-6.7-1.4 15.4 15.4 0 0 1-8.6-9.3 21.3 21.3 0 0 1 0-14.4 15.2 15.2 0 0 1 8.6-9.3c2-.8 4.3-1.2 6.7-1.2s4.6.4 6.7 1.2zm-6.7 27.6c2.7 0 4.7-1 6.2-3s2.2-4.3 2.2-7.8-.7-6.3-2.2-8.3-3.5-3-6.2-3-4.7 1-6.1 3c-1.5 2-2.2 4.8-2.2 8.3s.7 5.8 2.2 7.8 3.5 3 6.2 3zm-88.8-28.8c-6.2 0-11.7 3.3-14.8 8.2a18.6 18.6 0 0 0 4.8 25.2c1.8 1.2 4 1.8 6.2 1.7s.1 0 .1 0h.9c4.2-.7 8-4 9.1-8.1v7.4c0 .4.3.7.8.7h6.4a.7.7 0 0 0 .7-.7V14.2c0-.5-.3-.8-.7-.8h-13.5zm6.3 26.5a9.8 9.8 0 0 1-5.7 2h-.5a10 10 0 0 1-9.2-14c1.4-3.7 5-6.3 9-6.3h6.4v18.3zm152.3-26.5h13.5c.5 0 .8.3.8.7v33.7c0 .4-.3.7-.8.7h-6.4a.7.7 0 0 1-.8-.7v-7.4c-1.2 4-4.8 7.4-9 8h-.1a4.2 4.2 0 0 1-.5.1h-.9a10.3 10.3 0 0 1-7-2.6c-4-3.3-6.5-8.4-6.5-14.2 0-3.7 1-7.2 3-10 3-5 8.5-8.3 14.7-8.3zm.6 28.4c2.2-.1 4.2-.6 5.7-2V21.7h-6.3a9.8 9.8 0 0 0-9 6.4 10.2 10.2 0 0 0 9.1 13.9h.5zM452.8 13.4c-6.2 0-11.7 3.3-14.8 8.2a18.5 18.5 0 0 0 3.6 24.3 10.4 10.4 0 0 0 13 .6c2.2-1.5 3.8-3.7 4.5-6.1v7.8c0 2.8-.8 5-2.2 6.3-1.5 1.5-4 2.2-7.5 2.2l-6-.3c-.3 0-.7.2-.8.5l-1.6 5.5c-.1.4.1.8.5 1h.1c2.8.4 5.5.6 7 .6 6.3 0 11-1.4 14-4.1 2.7-2.5 4.2-6.3 4.5-11.4V14.2c0-.5-.4-.8-.8-.8h-13.5zm6.3 8.2v18.3a9.6 9.6 0 0 1-5.6 2h-1a10.3 10.3 0 0 1-8.8-14c1.4-3.7 5-6.3 9-6.3h6.4zM291 31.5A32 32 0 0 1 322.8 0h30.8c.6 0 1.2.5 1.2 1.2v61.5c0 1.1-1.3 1.7-2.2 1l-19.2-17a18 18 0 0 1-11 3.4 18.1 18.1 0 1 1 18.2-14.8c-.1.4-.5.7-.9.6-.1 0-.3 0-.4-.2l-3.8-3.4c-.4-.3-.6-.8-.7-1.4a12 12 0 1 0-2.4 8.3c.4-.4 1-.5 1.6-.2l14.7 13.1v-46H323a26 26 0 1 0 10 49.7c.8-.4 1.6-.2 2.3.3l3 2.7c.3.2.3.7 0 1l-.2.2a32 32 0 0 1-47.2-28.6z") - - #search-mask - - script(src=url_for(theme.asset.algolia_search)) - 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 deleted file mode 100644 index b8e3140..0000000 --- a/themes/butterfly/layout/includes/third-party/search/docsearch.pug +++ /dev/null @@ -1,29 +0,0 @@ -- 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 deleted file mode 100644 index a9d0c69..0000000 --- a/themes/butterfly/layout/includes/third-party/search/index.pug +++ /dev/null @@ -1,7 +0,0 @@ -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 deleted file mode 100644 index f2d0f70..0000000 --- a/themes/butterfly/layout/includes/third-party/search/local-search.pug +++ /dev/null @@ -1,24 +0,0 @@ -#local-search - .search-dialog - nav.search-nav - span.search-dialog-title= _p('search.title') - i.fas.fa-spinner.fa-pulse#loading-status(hidden) - button.search-close-button - i.fas.fa-times - - #loading-database.text-center - i.fas.fa-spinner.fa-pulse - span= ' ' + _p("search.load_data") - - .local-search-input - input(placeholder=theme.search.placeholder || _p("search.input_placeholder") type="text") - - hr - - #local-search-results - #local-search-pagination.ais-Pagination(style="display:none;") - ul.ais-Pagination-list - #local-search-stats - - #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 deleted file mode 100644 index 2dfbdb5..0000000 --- a/themes/butterfly/layout/includes/third-party/share/addtoany.pug +++ /dev/null @@ -1,10 +0,0 @@ -.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 deleted file mode 100644 index 458a6ee..0000000 --- a/themes/butterfly/layout/includes/third-party/share/index.pug +++ /dev/null @@ -1,11 +0,0 @@ -- 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 - when 'web_share' - include ./web_share.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 deleted file mode 100644 index 50d5528..0000000 --- a/themes/butterfly/layout/includes/third-party/share/share-js.pug +++ /dev/null @@ -1,4 +0,0 @@ -- 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/share/web_share.pug b/themes/butterfly/layout/includes/third-party/share/web_share.pug deleted file mode 100644 index d62bf13..0000000 --- a/themes/butterfly/layout/includes/third-party/share/web_share.pug +++ /dev/null @@ -1,53 +0,0 @@ -//- web_share.pug - -style. - #web-share-btn { - background: var(--btn-bg); - color: var(--btn-color); - border: none; - padding: 0.5rem 1rem; - border-radius: 8px; - cursor: pointer; - transition: all 0.3s; - } - #web-share-btn:hover { - background: var(--btn-hover-color); - } - -#web-share-component - button#web-share-btn(title='分享本文') - i.fas.fa-share-alt - span= ' ' + _p('分享') - -script. - (() => { - const setupWebShare = () => { - const btn = document.getElementById('web-share-btn') - if (!btn) return - - // 点击事件处理 - btn.addEventListener('click', async () => { - if (navigator.share) { - try { - await navigator.share({ - title: '!{page.title || config.title}', - text: '!{truncate(strip_html(page.content), {length: 100})}', - url: window.location.href - }) - } catch (err) { - console.log('分享取消或失败', err) - } - } else { - // 降级处理:比如弹出提示或复制链接 - const shareData = window.location.href - navigator.clipboard.writeText(shareData).then(() => { - btf.snackbarShow('系统不支持分享,已将链接复制到剪贴板') - }) - } - }) - } - - // 考虑到 Butterfly 的 Pjax 跳转,需要重新绑定 - setupWebShare() - document.addEventListener('pjax:complete', setupWebShare) - })() \ 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 deleted file mode 100644 index 6692b85..0000000 --- a/themes/butterfly/layout/includes/third-party/subtitle.pug +++ /dev/null @@ -1,113 +0,0 @@ -- 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 deleted file mode 100644 index bc99233..0000000 --- a/themes/butterfly/layout/includes/third-party/umami_analytics.pug +++ /dev/null @@ -1,110 +0,0 @@ -- let { serverURL, script_name, 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 = () => { - if (typeof umami !== 'undefined' && typeof umami.track === 'function') { - umami.track(props => ({ ...props, url: window.location.pathname, title: GLOBAL_CONFIG_SITE.title })) - } else { - console.warn('Umami Analytics: umami.track is not available') - } - } - - const loadUmamiJS = () => { - btf.getScript('!{baseURL}/!{script_name}', { - 'data-website-id': '!{website_id}', - 'data-auto-track': 'false', - ...option - }).then(() => { - runTrack() - }).catch(error => { - console.error('Umami Analytics: Error loading script', error) - }) - } - - const getData = async (isPost) => { - try { - const now = Date.now() - const keyUrl = isPost ? `&url=${window.location.pathname}&path=${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 - }) - - if (!res.ok) { - throw new Error(`HTTP error! status: ${res.status}`) - } - - return await res.json() - } catch (error) { - console.error('Umami Analytics: Failed to fetch data', error) - throw error - } - } - - 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) - if (data && data.pageviews) { - pagePV.textContent = typeof data.pageviews.value !== 'undefined' ? data.pageviews.value : data.pageviews - } else { - console.warn('Umami Analytics: Invalid page view data received') - } - } - } - - if (config.site_uv || config.site_pv) { - const data = await getData(false) - - if (config.site_uv) { - const siteUV = document.getElementById('umami-site-uv') - if (siteUV && data && data.visitors) { - siteUV.textContent = typeof data.visitors.value !== 'undefined' ? data.visitors.value : data.visitors - } else if (siteUV) { - console.warn('Umami Analytics: Invalid site UV data received') - } - } - - if (config.site_pv) { - const sitePV = document.getElementById('umami-site-pv') - if (sitePV && data && data.pageviews) { - sitePV.textContent = typeof data.pageviews.value !== 'undefined' ? data.pageviews.value : data.pageviews - } else if (sitePV) { - console.warn('Umami Analytics: Invalid site PV data received') - } - } - } - } catch (error) { - console.error('Umami Analytics: Failed to insert data', error) - } - } - - btf.addGlobalFn('pjaxComplete', runTrack, 'umami_analytics_run_track') - btf.addGlobalFn('pjaxComplete', insertData, 'umami_analytics_insert') - - - loadUmamiJS() - - if (document.readyState === 'loading') { - document.addEventListener('DOMContentLoaded', insertData) - } else { - setTimeout(insertData, 100) - } - })() \ 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 deleted file mode 100644 index b8e00fd..0000000 --- a/themes/butterfly/layout/includes/widget/card_ad.pug +++ /dev/null @@ -1,3 +0,0 @@ -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 deleted file mode 100644 index 79095fd..0000000 --- a/themes/butterfly/layout/includes/widget/card_announcement.pug +++ /dev/null @@ -1,8 +0,0 @@ -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-ip-location-info - #aside-weather \ 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 deleted file mode 100644 index bb0e78e..0000000 --- a/themes/butterfly/layout/includes/widget/card_archives.pug +++ /dev/null @@ -1,7 +0,0 @@ -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 deleted file mode 100644 index 9f7e687..0000000 --- a/themes/butterfly/layout/includes/widget/card_author.pug +++ /dev/null @@ -1,26 +0,0 @@ -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 deleted file mode 100644 index e32907d..0000000 --- a/themes/butterfly/layout/includes/widget/card_bottom_self.pug +++ /dev/null @@ -1,9 +0,0 @@ -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 deleted file mode 100644 index 529ea55..0000000 --- a/themes/butterfly/layout/includes/widget/card_categories.pug +++ /dev/null @@ -1,4 +0,0 @@ -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 deleted file mode 100644 index 3a0bb18..0000000 --- a/themes/butterfly/layout/includes/widget/card_newest_comment.pug +++ /dev/null @@ -1,7 +0,0 @@ -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_poem.pug b/themes/butterfly/layout/includes/widget/card_poem.pug deleted file mode 100644 index 97d6ad6..0000000 --- a/themes/butterfly/layout/includes/widget/card_poem.pug +++ /dev/null @@ -1,17 +0,0 @@ -#card-poem.card-widget - #poem_sentence - #poem_info - #poem_dynasty - #poem_author -script(src='https://cdn.liumingye.cn/npm/js-heo@1.0.11/poem/jinrishici.js', charset='utf-8') -script(type='text/javascript'). - jinrishici.load(function(result) { - var sentence = document.querySelector("#poem_sentence") - var author = document.querySelector("#poem_author") - var dynasty = document.querySelector("#poem_dynasty") - var sentenceText = result.data.content - sentenceText = sentenceText.substr(0, sentenceText.length - 1); - sentence.innerHTML = sentenceText - dynasty.innerHTML = result.data.origin.dynasty - author.innerHTML = result.data.origin.author + '《' + result.data.origin.title + '》' - }); \ No newline at end of file diff --git a/themes/butterfly/layout/includes/widget/card_post_series.pug b/themes/butterfly/layout/includes/widget/card_post_series.pug deleted file mode 100644 index 38f857b..0000000 --- a/themes/butterfly/layout/includes/widget/card_post_series.pug +++ /dev/null @@ -1,21 +0,0 @@ -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 deleted file mode 100644 index cd30d50..0000000 --- a/themes/butterfly/layout/includes/widget/card_post_toc.pug +++ /dev/null @@ -1,14 +0,0 @@ -- 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 deleted file mode 100644 index dddf0fc..0000000 --- a/themes/butterfly/layout/includes/widget/card_recent_post.pug +++ /dev/null @@ -1,27 +0,0 @@ -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 deleted file mode 100644 index f76ef63..0000000 --- a/themes/butterfly/layout/includes/widget/card_tags.pug +++ /dev/null @@ -1,14 +0,0 @@ -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, custom_colors } = 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', page: 'index', custom_colors: custom_colors}) - 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 deleted file mode 100644 index 6e81059..0000000 --- a/themes/butterfly/layout/includes/widget/card_top_self.pug +++ /dev/null @@ -1,8 +0,0 @@ -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 deleted file mode 100644 index 7ea4d98..0000000 --- a/themes/butterfly/layout/includes/widget/card_webinfo.pug +++ /dev/null @@ -1,44 +0,0 @@ -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 deleted file mode 100644 index bb23caa..0000000 --- a/themes/butterfly/layout/includes/widget/index.pug +++ /dev/null @@ -1,38 +0,0 @@ -#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_poem', {}, {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_poem', {}, {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 deleted file mode 100644 index 7705655..0000000 --- a/themes/butterfly/layout/index.pug +++ /dev/null @@ -1,5 +0,0 @@ -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 deleted file mode 100644 index 9cf058a..0000000 --- a/themes/butterfly/layout/page.pug +++ /dev/null @@ -1,35 +0,0 @@ -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 'about' - include includes/page/about.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 deleted file mode 100644 index 23a7d54..0000000 --- a/themes/butterfly/layout/post.pug +++ /dev/null @@ -1,37 +0,0 @@ -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 deleted file mode 100644 index 3d4c067..0000000 --- a/themes/butterfly/layout/tag.pug +++ /dev/null @@ -1,12 +0,0 @@ -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 deleted file mode 100644 index 5f23a10..0000000 --- a/themes/butterfly/package.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "name": "hexo-theme-butterfly", - "version": "5.5.4", - "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": "^4.0.0", - "moment-timezone": "^0.6.0" - }, - "homepage": "https://butterfly.js.org/", - "author": "Jerry ", - "license": "Apache-2.0" -} diff --git a/themes/butterfly/plugins.yml b/themes/butterfly/plugins.yml deleted file mode 100644 index 30e7f39..0000000 --- a/themes/butterfly/plugins.yml +++ /dev/null @@ -1,207 +0,0 @@ -abcjs_basic_js: - name: abcjs - file: dist/abcjs-basic-min.js - version: 6.6.0 -activate_power_mode: - name: butterfly-extsrc - file: dist/activate-power-mode.min.js - version: 1.1.6 -algolia_search: - name: algoliasearch - file: dist/lite/builds/browser.umd.js - version: 5.47.0 -aplayer_css: - name: aplayer - file: dist/APlayer.min.css - version: 1.10.1 -aplayer_js: - name: aplayer - file: dist/APlayer.min.js - version: 1.10.1 -artalk_css: - name: artalk - file: dist/Artalk.css - version: 2.9.1 -artalk_js: - name: artalk - file: dist/Artalk.js - version: 2.9.1 -blueimp_md5: - name: blueimp-md5 - file: js/md5.min.js - version: 2.19.0 -canvas_fluttering_ribbon: - name: butterfly-extsrc - file: dist/canvas-fluttering-ribbon.min.js - version: 1.1.6 -canvas_nest: - name: butterfly-extsrc - file: dist/canvas-nest.min.js - version: 1.1.6 -canvas_ribbon: - name: butterfly-extsrc - file: dist/canvas-ribbon.min.js - version: 1.1.6 -chartjs: - name: chart.js - file: dist/chart.umd.js - version: 4.5.1 -clickShowText: - name: butterfly-extsrc - file: dist/click-show-text.min.js - version: 1.1.6 -click_heart: - name: butterfly-extsrc - file: dist/click-heart.min.js - version: 1.1.6 -disqusjs: - name: disqusjs - file: dist/browser/disqusjs.es2015.umd.min.js - version: 3.2.1 -disqusjs_css: - name: disqusjs - file: dist/browser/styles/disqusjs.css - version: 3.2.1 -docsearch_css: - name: '@docsearch/css' - other_name: docsearch-css - file: dist/style.css - version: 4.5.3 -docsearch_js: - name: '@docsearch/js' - other_name: docsearch-js - file: dist/umd/index.js - version: 4.5.3 -egjs_infinitegrid: - name: '@egjs/infinitegrid' - other_name: egjs-infinitegrid - file: dist/infinitegrid.min.js - version: 4.13.0 -fancybox: - name: '@fancyapps/ui' - file: dist/fancybox/fancybox.umd.js - version: 6.1.9 - other_name: fancyapps-ui -fancybox_css: - name: '@fancyapps/ui' - file: dist/fancybox/fancybox.css - version: 6.1.9 - other_name: fancyapps-ui -fireworks: - name: butterfly-extsrc - file: dist/fireworks.min.js - version: 1.1.6 -fontawesome: - name: '@fortawesome/fontawesome-free' - file: css/all.min.css - other_name: font-awesome - version: 7.2.0 -gitalk: - name: gitalk - file: dist/gitalk.min.js - version: 1.8.0 -gitalk_css: - name: gitalk - file: dist/gitalk.css - version: 1.8.0 -instantpage: - name: instant.page - file: instantpage.js - version: 5.2.0 -katex: - name: katex - file: dist/katex.min.css - other_name: KaTeX - version: 0.16.45 -katex_copytex: - name: katex - file: dist/contrib/copy-tex.min.js - other_name: KaTeX - version: 0.16.45 -lazyload: - name: vanilla-lazyload - file: dist/lazyload.iife.min.js - version: 19.1.3 -mathjax: - name: mathjax - file: tex-mml-chtml.js - version: 4.1.0 -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.12.2 -meting_js: - name: butterfly-extsrc - file: metingjs/dist/Meting.min.js - version: 2.0.2 -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.6 -sharejs_css: - name: butterfly-extsrc - file: sharejs/dist/css/share.min.css - version: 1.1.6 -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.min.js - version: 1.7.7 -typed: - name: typed.js - file: dist/typed.umd.js - version: 3.0.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.8.0 -waline_js: - name: '@waline/client' - file: dist/waline.js - other_name: waline - version: 3.8.0 diff --git a/themes/butterfly/scripts/common/default_config.js b/themes/butterfly/scripts/common/default_config.js deleted file mode 100644 index ab3f01f..0000000 --- a/themes/butterfly/scripts/common/default_config.js +++ /dev/null @@ -1,601 +0,0 @@ -// Butterfly 主題默認配置 -// Default configuration for Butterfly theme - -module.exports = { - 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: 2025 - }, - 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, - custom_colors: null, - 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, - pagination: { - enable: false, - hitsPerPage: 8 - }, - CDN: null - }, - docsearch: { - appId: null, - apiKey: null, - indexName: null, - option: null - } - }, - share: { - use: 'sharejs', - sharejs: { - sites: 'facebook,x,wechat,weibo,qq' - }, - addtoany: { - item: 'facebook,x,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, - script_name: 'script.js', - 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' - }, - open_in_new_tab: true, - zoom_pan: true - }, - 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: { - enable: false, - alternate_name: null - }, - css_prefix: true, - inject: { - head: null, - bottom: null - }, - CDN: { - internal_provider: 'local', - third_party_provider: 'jsdelivr', - version: true, - custom_format: null, - option: null - } -} diff --git a/themes/butterfly/scripts/common/postDesc.js b/themes/butterfly/scripts/common/postDesc.js deleted file mode 100644 index a295557..0000000 --- a/themes/butterfly/scripts/common/postDesc.js +++ /dev/null @@ -1,37 +0,0 @@ -'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 deleted file mode 100644 index 17a05e9..0000000 --- a/themes/butterfly/scripts/events/404.js +++ /dev/null @@ -1,20 +0,0 @@ -/** - * 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 deleted file mode 100644 index 8c56f9d..0000000 --- a/themes/butterfly/scripts/events/cdn.js +++ /dev/null @@ -1,95 +0,0 @@ -/** - * 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 = '') => { - return Object.keys(data).reduce((result, key) => { - let { name, version, file, other_name: otherName } = data[key] - const cdnjsName = otherName || name - const cdnjsFile = file.replace(/^[lib|dist]*\/|browser\//g, '') - const minCdnjsFile = minFile(cdnjsFile) - if (cond === 'internal') file = `source/${file}` - const minFilePath = minFile(file) - const verType = CDN.version ? (type === 'local' ? `?v=${version}` : `@${version}`) : '' - - const value = { - version, - name, - file, - cdnjs_file: cdnjsFile, - min_file: minFilePath, - min_cdnjs_file: minCdnjsFile, - cdnjs_name: cdnjsName - } - - const cdnSource = { - local: cond === 'internal' ? `${cdnjsFile + verType}` : `/pluginsSrc/${name}/${file + verType}`, - jsdelivr: `https://cdn.jsdelivr.net/npm/${name}${verType}/${minFilePath}`, - unpkg: `https://unpkg.com/${name}${verType}/${file}`, - cdnjs: `https://cdnjs.cloudflare.com/ajax/libs/${cdnjsName}/${version}/${minCdnjsFile}`, - custom: (CDN.custom_format || '').replace(/\$\{(.+?)\}/g, (match, $1) => value[$1]) - } - - result[key] = cdnSource[type] - return result - }, cond === 'internal' ? { main_css: 'css/index.css' + (CDN.version ? `?v=${version}` : '') } : {}) - } - - // delete null value - const deleteNullValue = obj => { - if (!obj) return {} - for (const i in obj) { - if (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/init.js b/themes/butterfly/scripts/events/init.js deleted file mode 100644 index c022766..0000000 --- a/themes/butterfly/scripts/events/init.js +++ /dev/null @@ -1,86 +0,0 @@ -const { deepMerge } = require('hexo-util') -const path = require('path') - -// Cache default config to avoid repeated file reads -let cachedDefaultConfig = null - -/** - * Check Hexo version and configuration - */ -function checkHexoEnvironment (hexo) { - const { version, log, locals } = hexo - - const [major, minor] = version.split('.').map(Number) - const requiredMajor = 5 - const requiredMinor = 3 - - if (major < requiredMajor || (major === requiredMajor && minor < requiredMinor)) { - log.error('Please update Hexo to V5.3.0 or higher!') - log.error('請把 Hexo 升級到 V5.3.0 或更高的版本!') - throw new Error('Hexo version too old') - } - - // Check for deprecated configuration file - 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'") - throw new Error('Deprecated configuration file') - } - } -} - -/** - * Load default configuration - */ -function loadDefaultConfig () { - if (cachedDefaultConfig) { - return cachedDefaultConfig - } - - const configPath = path.join(__dirname, '../common/default_config.js') - cachedDefaultConfig = require(configPath) - return cachedDefaultConfig -} - -/** - * Process comment system configuration - */ -function processCommentConfig (themeConfig) { - const { comments } = themeConfig - if (!comments || !comments.use) { - return - } - - let { use } = comments - - if (!Array.isArray(use)) { - use = typeof use === 'string' ? use.split(',') : [use] - } - - use = use - .map(item => { - if (typeof item !== 'string') return item - return item.trim().toLowerCase().replace(/\b[a-z]/g, s => s.toUpperCase()) - }) - .filter(Boolean) - - // Handle Disqus and Disqusjs conflict - if (use.includes('Disqus') && use.includes('Disqusjs')) { - hexo.log.warn('Disqus and Disqusjs conflict detected, keeping only the first one') - hexo.log.warn('檢測到 Disqus 和 Disqusjs 衝突,只保留第一個') - use = [use[0]] - } - - themeConfig.comments.use = use -} - -hexo.extend.filter.register('before_generate', () => { - checkHexoEnvironment(hexo) - - const defaultConfig = loadDefaultConfig() - hexo.theme.config = deepMerge(defaultConfig, hexo.theme.config) - - processCommentConfig(hexo.theme.config) -}, 1) diff --git a/themes/butterfly/scripts/events/stylus.js b/themes/butterfly/scripts/events/stylus.js deleted file mode 100644 index 15ca4ff..0000000 --- a/themes/butterfly/scripts/events/stylus.js +++ /dev/null @@ -1,24 +0,0 @@ -/** - * 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 deleted file mode 100644 index f4c018a..0000000 --- a/themes/butterfly/scripts/events/welcome.js +++ /dev/null @@ -1,13 +0,0 @@ -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 deleted file mode 100644 index a0dc341..0000000 --- a/themes/butterfly/scripts/filters/post_lazyload.js +++ /dev/null @@ -1,40 +0,0 @@ -/** - * Butterfly - * Lazyload filter - * Replace src with data-lazy-src for lazy loading - */ - -'use strict' - -const urlFor = require('hexo-util').url_for.bind(hexo) - -const lazyload = htmlContent => { - if (hexo.theme.config.lazyload.native) { - // Use more precise replacement: only replace img tags in HTML, not content inside script tags - return htmlContent.replace(/(]*?\bloading=)(?:\s[^>]*?)?>)(?![^<]*<\/script>)/gi, match => { - return match.replace(/>$/, ' loading=\'lazy\'>') - }) - } - - const bg = hexo.theme.config.lazyload.placeholder ? urlFor(hexo.theme.config.lazyload.placeholder) : 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7' - - // Handle src attributes with double quotes, single quotes, or no quotes (unified approach) - // Matches: src="..." or src='...' or src=... (e.g., after minification by hexo-minify) - return htmlContent.replace(/(]*?\bdata-lazy-src=)(?:\s[^>]*?)?\ssrc=)(?:"([^"]*)"|'([^']*)'|([^\s>]+))(?![^<]*<\/script>)/gi, (match, prefix, srcDoubleQuote, srcSingleQuote, srcNoQuote) => { - const src = srcDoubleQuote || srcSingleQuote || srcNoQuote - return `${prefix}"${bg}" data-lazy-src="${src}"` - }) -} - -hexo.extend.filter.register('after_render:html', data => { - 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 deleted file mode 100644 index 0236370..0000000 --- a/themes/butterfly/scripts/filters/random_cover.js +++ /dev/null @@ -1,90 +0,0 @@ -/** - * Random cover for posts - */ - -'use strict' - -hexo.extend.generator.register('post', locals => { - const imgTestReg = /\.(png|jpe?g|gif|svg|webp|avif)(\?.*)?$/i - const { post_asset_folder: postAssetFolder } = hexo.config - const { cover: { default_cover: defaultCover } } = hexo.theme.config - - function * createCoverGenerator () { - if (!defaultCover) { - while (true) yield false - } - if (!Array.isArray(defaultCover)) { - while (true) yield defaultCover - } - - const coverCount = defaultCover.length - if (coverCount === 1) { - while (true) yield defaultCover[0] - } - - const maxHistory = Math.min(3, coverCount - 1) - const history = [] - - while (true) { - let index - do { - index = Math.floor(Math.random() * coverCount) - } while (history.includes(index)) - - history.push(index) - if (history.length > maxHistory) history.shift() - - yield defaultCover[index] - } - } - - const coverGenerator = createCoverGenerator() - - const handleImg = data => { - let { cover: coverVal, top_img: topImg, pagination_cover: paginationCover } = data - - // Add path to top_img and cover if post_asset_folder is enabled - if (postAssetFolder) { - 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 (paginationCover && paginationCover.indexOf('/') === -1 && imgTestReg.test(paginationCover)) { - data.pagination_cover = `${data.path}${paginationCover}` - } - } - - if (coverVal === false) return data - - // If cover is not set, use random cover - if (!coverVal) { - const randomCover = coverGenerator.next().value - data.cover = randomCover - coverVal = randomCover - } - - if (coverVal && (coverVal.indexOf('//') !== -1 || imgTestReg.test(coverVal))) { - data.cover_type = 'img' - } - - return data - } - - 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 deleted file mode 100644 index 67f2c7d..0000000 --- a/themes/butterfly/scripts/helpers/aside_archives.js +++ /dev/null @@ -1,113 +0,0 @@ -'use strict' - -hexo.extend.helper.register('aside_archives', function (options = {}) { - const { config, page, site, url_for: urlFor, _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) - - // Early return if no posts - if (!site.posts.length) return '' - - const archives = new Map() - site.posts.forEach(post => { - const date = post.date - const year = date.year() - const month = date.month() + 1 - const key = type === 'yearly' ? year : `${year}-${month}` - - if (archives.has(key)) { - archives.get(key).count++ - } else { - archives.set(key, { - year, - month, - count: 1, - date // Store date object for later formatting - }) - } - }) - - const data = Array.from(archives.values()).sort((a, b) => { - if (order === -1) { - return b.year - a.year || b.month - a.month - } - return a.year - b.year || a.month - b.month - }) - - // Format names after aggregation - data.forEach(item => { - let date = item.date.clone() - if (timezone) date = date.tz(timezone) - if (lang) date = date.locale(lang) - item.name = date.format(format) - delete item.date // Clean up - }) - - // 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 urlFor(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 deleted file mode 100644 index 02a4c1d..0000000 --- a/themes/butterfly/scripts/helpers/aside_categories.js +++ /dev/null @@ -1,100 +0,0 @@ -'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 categoryMap = new Map() - categories.forEach(cat => { - if (cat.length) { - const parentId = cat.parent || 'root' - if (!categoryMap.has(parentId)) { - categoryMap.set(parentId, []) - } - categoryMap.get(parentId).push(cat) - } - }) - - const sortFn = (a, b) => { - const valA = a[orderby] - const valB = b[orderby] - if (valA < valB) return -order - if (valA > valB) return order - return 0 - } - - for (const list of categoryMap.values()) { - list.sort(sortFn) - } - - const hierarchicalList = (remaining, level = 0, parentId = 'root') => { - let result = '' - if (remaining > 0 && categoryMap.has(parentId)) { - categoryMap.get(parentId).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 isTopLevel = parentId === 'root' - const parentClass = isExpand && isTopLevel && child ? 'parent' : '' - result += `
  • ` - result += `` - result += `${cat.name}` - - if (showCount) { - result += `${cat.length}` - } - - if (isExpand && isTopLevel && 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/chart.js b/themes/butterfly/scripts/helpers/chart.js deleted file mode 100644 index 516e608..0000000 --- a/themes/butterfly/scripts/helpers/chart.js +++ /dev/null @@ -1,445 +0,0 @@ -const cheerio = require('cheerio') -const moment = require('moment') - -hexo.extend.filter.register('after_render:html', function (locals) { - const $ = cheerio.load(locals) - const post = $('#posts-chart') - const tag = $('#tags-chart') - const category = $('#categories-chart') - const htmlEncode = false - - if (post.length > 0 || tag.length > 0 || category.length > 0) { - if (post.length > 0 && $('#postsChart').length === 0) { - if (post.attr('data-encode') === 'true') htmlEncode = true - post.after(postsChart(post.attr('data-start'))) - } - if (tag.length > 0 && $('#tagsChart').length === 0) { - if (tag.attr('data-encode') === 'true') htmlEncode = true - tag.after(tagsChart(tag.attr('data-length'))) - } - if (category.length > 0 && $('#categoriesChart').length === 0) { - if (category.attr('data-encode') === 'true') htmlEncode = true - category.after(categoriesChart(category.attr('data-parent'))) - } - - if (htmlEncode) { - return $.root().html().replace(/&#/g, '&#') - } else { - return $.root().html() - } - } else { - return locals - } -}, 15) -function switchPostChart () { - // 这里为了统一颜色选取的是“明暗模式”下的两种字体颜色,也可以自己定义 - let color = document.documentElement.getAttribute('data-theme') === 'light' ? '#4C4948' : 'rgba(255,255,255,0.7)' - if (document.getElementById('posts-chart') && postsOption) { - try { - let postsOptionNew = postsOption - postsOptionNew.title.textStyle.color = color - postsOptionNew.xAxis.nameTextStyle.color = color - postsOptionNew.yAxis.nameTextStyle.color = color - postsOptionNew.xAxis.axisLabel.color = color - postsOptionNew.yAxis.axisLabel.color = color - postsOptionNew.xAxis.axisLine.lineStyle.color = color - postsOptionNew.yAxis.axisLine.lineStyle.color = color - postsOptionNew.series[0].markLine.data[0].label.color = color - postsChart.setOption(postsOptionNew) - } catch (error) { - console.log(error) - } - } - if (document.getElementById('tags-chart') && tagsOption) { - try { - let tagsOptionNew = tagsOption - tagsOptionNew.title.textStyle.color = color - tagsOptionNew.xAxis.nameTextStyle.color = color - tagsOptionNew.yAxis.nameTextStyle.color = color - tagsOptionNew.xAxis.axisLabel.color = color - tagsOptionNew.yAxis.axisLabel.color = color - tagsOptionNew.xAxis.axisLine.lineStyle.color = color - tagsOptionNew.yAxis.axisLine.lineStyle.color = color - tagsOptionNew.series[0].markLine.data[0].label.color = color - tagsChart.setOption(tagsOptionNew) - } catch (error) { - console.log(error) - } - } - if (document.getElementById('categories-chart') && categoriesOption) { - try { - let categoriesOptionNew = categoriesOption - categoriesOptionNew.title.textStyle.color = color - categoriesOptionNew.legend.textStyle.color = color - if (!categoryParentFlag) { categoriesOptionNew.series[0].label.color = color } - categoriesChart.setOption(categoriesOptionNew) - } catch (error) { - console.log(error) - } - } -} -document.getElementById("mode-button").addEventListener("click", function () { setTimeout(switchPostChart, 100) }) - -function postsChart (startMonth) { - const startDate = moment(startMonth || '2020-01') - const endDate = moment() - - const monthMap = new Map() - const dayTime = 3600 * 24 * 1000 - for (let time = startDate; time <= endDate; time += dayTime) { - const month = moment(time).format('YYYY-MM') - if (!monthMap.has(month)) { - monthMap.set(month, 0) - } - } - hexo.locals.get('posts').forEach(function (post) { - const month = post.date.format('YYYY-MM') - if (monthMap.has(month)) { - monthMap.set(month, monthMap.get(month) + 1) - } - }) - const monthArr = JSON.stringify([...monthMap.keys()]) - const monthValueArr = JSON.stringify([...monthMap.values()]) - - return ` - ` -} - -function tagsChart (len) { - const tagArr = [] - hexo.locals.get('tags').map(function (tag) { - tagArr.push({ name: tag.name, value: tag.length, path: tag.path }) - }) - tagArr.sort((a, b) => { return b.value - a.value }) - - const dataLength = Math.min(tagArr.length, len) || tagArr.length - const tagNameArr = [] - for (let i = 0; i < dataLength; i++) { - tagNameArr.push(tagArr[i].name) - } - const tagNameArrJson = JSON.stringify(tagNameArr) - const tagArrJson = JSON.stringify(tagArr) - - return ` - ` -} - -function categoriesChart (dataParent) { - const categoryArr = [] - let categoryParentFlag = false - hexo.locals.get('categories').map(function (category) { - if (category.parent) categoryParentFlag = true - categoryArr.push({ - name: category.name, - value: category.length, - path: category.path, - id: category._id, - parentId: category.parent || '0' - }) - }) - categoryParentFlag = categoryParentFlag && dataParent === 'true' - categoryArr.sort((a, b) => { return b.value - a.value }) - function translateListToTree (data, parent) { - let tree = [] - let temp - data.forEach((item, index) => { - if (data[index].parentId == parent) { - let obj = data[index]; - temp = translateListToTree(data, data[index].id); - if (temp.length > 0) { - obj.children = temp - } - if (tree.indexOf()) - tree.push(obj) - } - }) - return tree - } - const categoryNameJson = JSON.stringify(categoryArr.map(function (category) { return category.name })) - const categoryArrJson = JSON.stringify(categoryArr) - const categoryArrParentJson = JSON.stringify(translateListToTree(categoryArr, '0')) - - return ` - ` -} diff --git a/themes/butterfly/scripts/helpers/getArchiveLength.js b/themes/butterfly/scripts/helpers/getArchiveLength.js deleted file mode 100644 index ab5fff2..0000000 --- a/themes/butterfly/scripts/helpers/getArchiveLength.js +++ /dev/null @@ -1,46 +0,0 @@ -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 - - // Create a map to count posts per period - const mapData = this.fragment_cache('createArchiveObj', () => { - const map = new Map() - posts.forEach(post => { - const date = post.date - const y = date.year() - const m = date.month() + 1 - const d = date.date() - - if (yearly) { - const keyYear = `${y}` - map.set(keyYear, (map.get(keyYear) || 0) + 1) - } - - if (monthly) { - const keyMonth = `${y}-${m}` - map.set(keyMonth, (map.get(keyMonth) || 0) + 1) - } - - if (daily) { - const keyDay = `${y}-${m}-${d}` - 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 deleted file mode 100644 index f2d785e..0000000 --- a/themes/butterfly/scripts/helpers/inject_head_js.js +++ /dev/null @@ -1,155 +0,0 @@ -'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: themeColor } = hexo.theme.config - const themeColorLight = themeColor && themeColor.enable ? themeColor.meta_theme_color_light : '#ffffff' - const themeColorDark = themeColor && themeColor.enable ? themeColor.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 deleted file mode 100644 index 5530e61..0000000 --- a/themes/butterfly/scripts/helpers/page.js +++ /dev/null @@ -1,193 +0,0 @@ -'use strict' - -const { truncateContent, postDesc } = require('../common/postDesc') -const { prettyUrls } = require('hexo-util') -const crypto = require('crypto') -const moment = require('moment-timezone') - -const absoluteUrlPattern = /^(?:[a-z][a-z\d+.-]*:)?\/\//i -const relativeUrlPattern = /^(\.\/|\.\.\/|\/|[^/]+\/).*$/ -const colorPattern = /^(#|rgb|rgba|hsl|hsla)/i -const simpleFilePattern = /\.(png|jpg|jpeg|gif|bmp|webp|svg|tiff)$/i -const archiveRegex = /\/archives\// - -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, page = 'tags', custom_colors } = options - - if (limit > 0) { - source = source.limit(limit) - } - - const sizes = [...new Set(source.map(tag => tag.length).sort((a, b) => a - b))] - const sizeMap = new Map(sizes.map((size, index) => [size, index])) - const length = sizes.length - 1 - - const getRandomColor = () => { - const r = Math.floor(Math.random() * 201) - const g = Math.floor(Math.random() * 201) - const b = Math.floor(Math.random() * 201) - return `rgb(${Math.max(r, 50)}, ${Math.max(g, 50)}, ${Math.max(b, 50)})` - } - - const normalizeColors = input => { - if (!input) return null - if (typeof input === 'string') { - const color = input.trim() - return color ? [color] : null - } - if (Array.isArray(input)) { - const result = [] - for (let i = 0; i < input.length; i++) { - const value = input[i] - if (value === null || value === undefined) continue - const color = String(value).trim() - if (!color) continue - result.push(color) - } - return result.length ? result : null - } - return null - } - - const userColors = normalizeColors(custom_colors) - - const resolveColorClass = (idx) => `tag-color-${idx % userColors.length}` - - const generateStyle = (size, unit, page, color) => { - const colorStyle = page === 'tags' ? `background-color: ${color};` : `color: ${color};` - return `font-size: ${parseFloat(size.toFixed(2))}${unit}; ${colorStyle}` - } - - return source.sort(orderby, order).map((tag, idx) => { - const ratio = length ? sizeMap.get(tag.length) / length : 0 - const size = minfontsize + ((maxfontsize - minfontsize) * ratio) - - if (userColors && userColors.length) { - const colorClass = resolveColorClass(idx) - const color = userColors[idx % userColors.length] - const style = generateStyle(size, unit, page, color) - return `${tag.name}` - } - - const color = getRandomColor() - const style = generateStyle(size, unit, page, color) - return `${tag.name}` - }).join('') -}) - -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, { relative: false }))).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 (archiveRegex.test(m[key])) { - return key - } - } - } - - return loop(menu) || defaultTitle -}) - -hexo.extend.helper.register('getBgPath', function (path) { - if (!path) return '' - - if (colorPattern.test(path)) { - return `background-color: ${path};` - } else if (absoluteUrlPattern.test(path) || relativeUrlPattern.test(path) || simpleFilePattern.test(path)) { - return `background-image: url(${this.url_for(path)});` - } else { - return `background: ${path};` - } -}) - -hexo.extend.helper.register('shuoshuoFN', (data, page) => { - const { limit } = page - - // Shallow copy to avoid mutating original data - let processedData = data.map(item => ({ ...item })) - - // Check if limit.value is a valid date - const isValidDate = date => !isNaN(Date.parse(date)) - - // order by date - processedData.sort((a, b) => Date.parse(b.date) - Date.parse(a.date)) - - // Apply number limit or time limit conditionally - if (limit && limit.type === 'num' && limit.value > 0) { - processedData = processedData.slice(0, limit.value) - } else if (limit && limit.type === 'date' && isValidDate(limit.value)) { - const limitDate = Date.parse(limit.value) - processedData = processedData.filter(item => Date.parse(item.date) >= limitDate) - } - - // This is a hack method, because hexo treats time as UTC time - // so you need to manually convert the time zone - processedData.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') - // markdown - item.content = hexo.render.renderSync({ text: item.content, engine: 'markdown' }) - }) - - return processedData -}) - -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 } -}) - -hexo.extend.helper.register('safeJSON', data => { - // Safely serialize JSON for embedding in

    diff --git a/themes/butterfly/source/css/calendar.css b/themes/butterfly/source/css/calendar.css deleted file mode 100644 index a943f9c..0000000 --- a/themes/butterfly/source/css/calendar.css +++ /dev/null @@ -1,222 +0,0 @@ -/* 浅色主题变量覆盖 -------------------------------- */ -:root { - --main: #a0d2eb; /* 主强调色:柔和天空蓝 */ - --main-op: rgba(160, 210, 235, 0.6); - --main-op-deep: rgba(160, 210, 235, 0.4); - --main-op-light: rgba(160, 210, 235, 0.2); - - --card-bg: #fdfdfd; /* 卡片背景:几乎白 */ - --fontcolor: #444; /* 主文本:深灰 */ - --secondtext: #999; /* 次级文本:浅灰 */ -} -.card-widget { - padding: 10px!important; - max-height: calc(100vh - 100px); -} -.card-times a, .card-times div { - color: var(--fontcolor); -} - -#card-widget-calendar .item-content { - display: flex; -} - -#calendar-area-left { - width: 45%; -} - -#calendar-area-right { - width: 55%; -} - -#calendar-area-left, #calendar-area-right { - height: 100%; - padding: 8px; - display: flex; - flex-direction: column; - align-items: center; - justify-content: flex-start; -} - -#calendar-main { - width: 100%; -} - -#calendar-week { - height: 1.2rem; - font-size: 14px; - letter-spacing: 1px; - font-weight: 700; - align-items: center; - display: flex; -} - -#calendar-date { - height: 3rem; - line-height: 1.3; - font-size: 64px; - letter-spacing: 3px; - color: var(--main); - font-weight: 700; - align-items: center; - display: flex; - position: relative; - top: calc(50% - 2.1rem); -} - -#calendar-lunar, #calendar-solar { - height: 1rem; - font-size: 12px; - align-items: center; - display: flex; - position: absolute; -} - -#calendar-solar { - bottom: 2.1rem; -} - -#calendar-lunar { - bottom: 1rem; - color: var(--secondtext); -} - -#calendar-main a { - height: 1rem; - width: 1rem; - border-radius: 50%; - font-size: 12px; - line-height: 12px; - display: flex; - justify-content: center; - align-items: center; -} - -#calendar-main a.now { - background: var(--main); - color: var(--card-bg); -} - -#calendar-main .calendar-rh a { - color: var(--secondtext); -} - -.calendar-r0, .calendar-r1, .calendar-r2, .calendar-r3, .calendar-r4, .calendar-r5, .calendar-rh { - height: 1.2rem; - display: flex; -} - -.calendar-d0, .calendar-d1, .calendar-d2, .calendar-d3, .calendar-d4, .calendar-d5, .calendar-d6 { - width: calc(100% / 7); - display: flex; - justify-content: center; - align-items: center; -} - -#card-widget-schedule .item-content { - display: flex; -} - -#schedule-area-left, #schedule-area-right { - height: 100px; - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; -} - -#schedule-area-left { - width: 30%; -} - -#schedule-area-right { - width: 70%; - padding: 0 5px; -} - -.schedule-r0, .schedule-r1, .schedule-r2 { - height: 2rem; - width: 100%; - align-items: center; - display: flex; -} - -.schedule-d0 { - width: 30px; - margin-right: 5px; - text-align: center; - font-size: 12px; -} - -.schedule-d1 { - width: calc(100% - 35px); - height: 1.5rem; - align-items: center; - display: flex; -} - -progress::-webkit-progress-bar { - background: linear-gradient(to right, var(--main-op-deep), var(--main-op), var(--main-op-light)); - border-radius: 5px; - overflow: hidden; -} - -progress::-webkit-progress-value { - background: var(--main); - border-radius: 5px; -} - -.aside-span1, .aside-span2 { - height: 1rem; - font-size: 12px; - z-index: 1; - display: flex; - align-items: center; - position: absolute; -} - -.aside-span1 { - margin-left: 5px; -} - -.aside-span2 { - right: 20px; - color: var(--secondtext); -} - -.aside-span2 a { - margin: 0 3px; -} - -#pBar_month, #pBar_week, #pBar_year { - width: 100%; - border-radius: 5px; - height: 100%; -} - -#schedule-date, #schedule-days, #schedule-title { - display: flex; - align-items: center; -} - -#schedule-title { - height: 25px; - line-height: 1; - font-size: 14px; - font-weight: 700; -} - -#schedule-days { - height: 40px; - line-height: 1; - font-size: 30px; - font-weight: 900; - color: var(--main); -} - -#schedule-date { - height: 20px; - line-height: 1; - font-size: 12px; - color: var(--secondtext); -} \ No newline at end of file diff --git a/themes/butterfly/source/css/index.styl b/themes/butterfly/source/css/index.styl deleted file mode 100644 index 0cfec1f..0000000 --- a/themes/butterfly/source/css/index.styl +++ /dev/null @@ -1,15 +0,0 @@ -if hexo-config('css_prefix') - @import 'nib' - -@import '_third-party/normalize.min.css' -// project -@import 'var' -@import '_global/*' -@import '_highlight/highlight' -@import '_page/*' -@import '_layout/*' -@import '_tags/*' -@import '_mode/*' - -// search -@import '_search/index' \ No newline at end of file diff --git a/themes/butterfly/source/css/nav.css b/themes/butterfly/source/css/nav.css deleted file mode 100644 index 64a5a53..0000000 --- a/themes/butterfly/source/css/nav.css +++ /dev/null @@ -1,238 +0,0 @@ -#nav-right{ - flex:1 1 auto; - justify-content: flex-end; - margin-left: auto; - display: flex; - flex-wrap:nowrap; -} - -/* 导航栏居中 */ - -#sidebar #sidebar-menus .menus_items .menus_item { - margin: 10px 0; -} -#sidebar #sidebar-menus .menus_items a.site-page { - padding-left: 0; -} -#sidebar #sidebar-menus .menus_items .site-page { - position: relative; - display: block; - padding: 6px 30px 6px 22px; - color: var(--font-color); - font-size: 1.15em; - border: var(--style-border-always); - background: var(--icat-card-bg); - font-size: 14px; - border-radius: 12px; -} -#sidebar #sidebar-menus .menus_items .site-page i:first-child { - text-align: left; - padding-left: 10px; -} - -#nav #menus { - display: flex; - justify-content: center; - width: 100%; - position: absolute; - left: 0; - margin: 0; - transform: translateZ(0); -} -#nav #blog-info { - flex-wrap: nowrap; - display: flex; - align-items: center; - z-index: 102; - max-width: fit-content; -} -@media screen and (max-width: 900px) { - #nav { - padding: 0 15px; - } - #nav-group { - padding: 0 0.2rem; - } - #rightside { - right: -42px; - } -} -/* IPAD菜单栏调整 */ - -/* 1. 容器溢出穿透 */ -#nav, #blog-info, #nav-group { - overflow: visible !important; -} - -#ls-menu-container { - position: relative !important; - display: inline-flex !important; - align-items: center; - height: 100%; - padding: 0 15px; - cursor: pointer !important; - z-index: 2000 !important; -} - -/* 2. 悬浮面板:宽大且高不透明度 */ -#ls-menu-panel { - position: absolute !important; - top: 100% !important; - left: 0 !important; - margin-top: 15px !important; - width: 420px; - padding: 24px; - border-radius: 18px; - - /* 高不透明度磨砂玻璃 (0.9) */ - background: rgba(255, 255, 255, 0.95) !important; - backdrop-filter: blur(20px) saturate(180%) !important; - -webkit-backdrop-filter: blur(20px) saturate(180%) !important; - - border: 1px solid rgba(255, 255, 255, 0.5) !important; - box-shadow: 0 20px 50px rgba(0, 0, 0, 0.18) !important; - - opacity: 0 !important; - visibility: hidden !important; - transform: translateY(12px) !important; - transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1) !important; - z-index: 999999 !important; - cursor: default; -} - -/* 3. 透明感应桥梁 */ -#ls-menu-panel::before { - content: ""; - position: absolute; - top: -20px; - left: 0; - right: 0; - height: 25px; - background: transparent !important; -} - -/* 4. 触发效果 */ -#ls-menu-container:hover #ls-menu-panel { - opacity: 1 !important; - visibility: visible !important; - transform: translateY(0) !important; -} - -#ls-menu-container:hover i.fa-fingerprint { - color: #49b1f5; - transform: scale(1.1); -} - -/* 5. 内部网格:保持两列 */ -.ls-section { margin-bottom: 22px; } -.ls-section:last-child { margin-bottom: 0; } -.ls-title { - color: #333; - font-weight: 800; - font-size: 15px; - margin-bottom: 12px; - opacity: 0.9; -} - -.ls-grid { - display: grid !important; - grid-template-columns: repeat(2, 1fr); /* 恢复为两列 */ - gap: 10px; -} - -.ls-grid a { - color: #444 !important; - font-size: 14px !important; - padding: 10px 12px; - border-radius: 12px; - display: flex; - align-items: center; - transition: all 0.2s ease; - background: rgba(0, 0, 0, 0.02); -} - -.ls-grid a:hover { - background: #49b1f5 !important; - color: #fff !important; - transform: translateX(4px); /* 悬停时轻微右移,增加动感 */ -} - -.ls-grid a i { - margin-right: 12px; - width: 20px; - text-align: center; - font-size: 15px; -} - -/* 6. 深色模式适配 */ -[data-theme='dark'] #ls-menu-panel { - background: rgba(30, 30, 30, 0.95) !important; - border-color: rgba(255, 255, 255, 0.1) !important; -} -[data-theme='dark'] .ls-title { color: #eee; } -[data-theme='dark'] .ls-grid a { - color: #ccc !important; - background: rgba(255, 255, 255, 0.05); -} - -@media screen and (max-width: 768px) { - #ls-menu-container.ls-mobile-safe { - position: static !important; - padding: 0 10px; - } - - #ls-menu-panel.ls-mobile-safe-panel { - position: fixed !important; - top: 70px !important; - left: 12px !important; - right: 12px !important; - width: auto !important; - max-width: calc(100vw - 24px) !important; - max-height: calc(100vh - 94px) !important; - margin-top: 0 !important; - overflow-y: auto; - overflow-x: hidden; - border-radius: 16px; - } - - #ls-menu-panel.ls-mobile-safe-panel::before { - display: none; - } - - #ls-menu-panel.ls-mobile-safe-panel .ls-grid { - grid-template-columns: 1fr; - gap: 8px; - } - - #ls-menu-panel.ls-mobile-safe-panel .ls-grid a { - min-width: 0; - padding: 10px; - } -} - -#nav.show-title .nav-page-title { - display: flex !important; - opacity: 1 !important; - visibility: visible !important; - position: absolute !important; - left: 50% !important; - transform: translateX(-50%) !important; - white-space: nowrap !important; - z-index: 1000 !important; -} - -#nav.show-title #menus, -#nav.show-title .nav-site-title { - opacity: 0 !important; - visibility: hidden !important; - pointer-events: none !important; -} - -#nav .nav-page-title { - display: none; - transition: opacity 0.3s ease; -} - -#nav #blog-info { - overflow: visible !important; -} diff --git a/themes/butterfly/source/css/poem.css b/themes/butterfly/source/css/poem.css deleted file mode 100644 index cb7df37..0000000 --- a/themes/butterfly/source/css/poem.css +++ /dev/null @@ -1,67 +0,0 @@ -[data-theme=light] { - --fontcolor: #363636; - --text: rgba(60, 60, 67, 0.6); - --bg: #edf0f7; -} - -[data-theme=dark] { - --fontcolor: #F7F7FA; - --text: #a1a2b8; - --bg: #30343f; -} -div#poem_sentence{ - text-align: center; - font-family: serif,cursive; - line-height: 1.4; - margin-bottom: 0.5rem; - padding: 1rem; - border-radius: 12px; - background: var(--bg); - min-height: 62px; -} - -div#poem_info{ - display: flex; - color: var(--text); - font-size: 100%; - justify-content: center; - flex-wrap: wrap; -} - -div#poem_author{ - order: 1; - padding: 2px; - margin-left: 8px; -} - -div#poem_dynasty{ - order: 0; - padding: 2px 4px 2px 6px; - background: var(-btn-bg); - color: var(--fontcolor); - border-radius: 8px; -} - -#card-poem{ - display: flex; - flex-direction: column; - padding: 0.5rem!important; - min-height: 130px; - background: linear-gradient(-45deg, rgba(255, 255, 255, .7), - rgba(255, 255, 255, .8), - rgba(255, 255, 255, .8), - rgba(255, 255, 255, .7))!important; - backdrop-filter: blur(10px); -} - -[data-theme=dark] #card-poem{ - display: flex; - flex-direction: column; - padding: 0.5rem!important; - min-height: 130px; - background: linear-gradient(-45deg, rgba(24, 40, 72, .7), - rgba(35, 37, 58, .8), - rgba(35, 37, 58, .8), - rgba(24, 40, 72, .7))!important; - backdrop-filter: blur(10px); -} \ No newline at end of file diff --git a/themes/butterfly/source/css/rss2.xsl b/themes/butterfly/source/css/rss2.xsl deleted file mode 100644 index 8f5106a..0000000 --- a/themes/butterfly/source/css/rss2.xsl +++ /dev/null @@ -1,38 +0,0 @@ - -<xsl:value-of select="$title"/>

    - This RSS feed for the - - website. -



    diff --git a/themes/butterfly/source/css/shuoshuo.css b/themes/butterfly/source/css/shuoshuo.css deleted file mode 100644 index c864222..0000000 --- a/themes/butterfly/source/css/shuoshuo.css +++ /dev/null @@ -1,300 +0,0 @@ -/* ================= 主题变量 ================= */ -:root { - --card-bg: #fff; - --card-border: 1px solid #e3e8f7; - --card-box-shadow: 0 3px 8px 6px rgba(7,17,27,0.09); - --card-hover-box-shadow: 0 3px 8px 6px rgba(7,17,27,0.2); - --card-secondbg: #f1f3f8; - --button-hover-bg: #2679cc; - --text: #4c4948; - --button-bg: #f1f3f8; - --fancybox-bg: rgba(255,255,255,0.5); -} - -[data-theme=dark] { - --card-bg: #181818; - --card-secondbg: #30343f; - --card-border: 1px solid #42444a; - --card-box-shadow: 0 3px 8px 6px rgba(7,17,27,0.09); - --card-hover-box-shadow: 0 3px 8px 6px rgba(7,17,27,0.2); - --button-bg: #30343f; - --button-hover-bg: #2679cc; - --text: rgba(255,255,255,0.702); - --fancybox-bg: rgba(0,0,0,0.5); -} - -/* ================= 卡片布局 ================= */ -#talk { - position: relative; - width: 100%; - box-sizing: border-box; - display: flex; - flex-wrap: wrap; /* 允许换行 */ - align-items: flex-start; /* 防止高度拉伸导致变形 */ -} - -#talk .talk_item { - width: calc(33.333% - 6px); - background: var(--card-bg); - border: var(--card-border); - box-shadow: var(--card-box-shadow); - border-radius: 12px; - padding: 20px; - margin-right: 9px; - margin-bottom: 9px; - display: flex; - flex-direction: column; - transition: box-shadow .3s ease-in-out; -} - -#talk .talk_item:hover { - box-shadow: var(--card-hover-box-shadow); -} - -@media (max-width: 900px) { - #talk .talk_item { - width: calc(50% - 5px); - } -} - -@media (max-width: 450px) { - #talk .talk_item { - width: 100%; - } -} - -/* 给 Meting 播放器预留约 80px 的固定高度 */ -meting-js { - display: block; - min-height: 100px; - margin-top: 10px; -} - -/* 如果有视频,也预留一下比例高度 */ -.talk_content iframe { - min-height: 150px; -} - -/* ================= 头部信息 ================= */ -#talk .talk_meta, -#talk .talk_bottom { - display: flex; - align-items: center; -} - -#talk .talk_meta { - width: 100%; - padding-bottom: 10px; - border-bottom: 1px dashed grey; -} - -#talk .talk_meta .avatar { - margin: 0 !important; - width: 60px; - height: 60px; - border-radius: 12px; -} - -#talk .talk_meta .info { - display: flex; - flex-direction: column; - margin-left: 10px; -} - -#talk .talk_meta .info .talk_nick { - color: #6dbdc3; - font-size: 1.2rem; -} - -#talk .talk_meta .info svg.is-badge.icon { - width: 15px; - padding-top: 3px; -} - -#talk .talk_meta .info span.talk_date { - opacity: .6; -} - -/* ================= 内容区域 ================= */ -#talk .talk_item .talk_content { - margin-top: 10px; -} - -#talk .talk_item .talk_content .zone_imgbox { - display: flex; - flex-wrap: wrap; - --w: calc(25% - 8px); - gap: 10px; - margin-top: 10px; -} - -#talk .talk_item .talk_content .zone_imgbox a { - display: block; - border-radius: 12px; - width: var(--w); - aspect-ratio: 1/1; - position: relative; -} - -#talk .talk_item .talk_content .zone_imgbox a:first-child { - width: 100%; - aspect-ratio: 1.8; -} - -#talk .talk_item .talk_content .zone_imgbox img { - border-radius: 10px; - width: 100%; - height: 100%; - margin: 0 !important; - object-fit: cover; -} - -/* ================= 底部信息 ================= */ -#talk .talk_item .talk_bottom { - margin-top: 15px; - padding-top: 10px; - border-top: 1px dashed grey; - justify-content: space-between; - opacity: .9; -} - -#talk .talk_item .talk_bottom .icon { - float: right; - transition: all .3s; -} - -#talk .talk_item .talk_bottom .icon:hover { - color: #49b1f5; -} - -#talk .talk_item .talk_bottom span.talk_tag, -#talk .talk_item .talk_bottom span.location_tag { - font-size: 14px; - background-color: var(--card-secondbg); - border-radius: 12px; - padding: 3px 15px 3px 10px; - transition: box-shadow 0.3s ease; - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); -} - -#talk .talk_item .talk_bottom span.location_tag { - margin-left: 5px; -} - -#talk .talk_item .talk_bottom span.talk_tag:hover, -#talk .talk_item .talk_bottom span.location_tag:hover { - box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3); -} - -/* ================= 文本链接 ================= */ -#talk .talk_item .talk_content > a { - margin: 0 3px; - color: #ff7d73 !important; -} - -#talk .talk_item .talk_content > a:hover { - text-decoration: none !important; - color: #ff5143 !important; -} - -/* ================= 响应式 ================= */ -@media screen and (max-width: 900px) { - #talk .talk_item .talk_content .zone_imgbox { - --w: calc(33% - 5px); - } - - #talk .talk_item #post-comment { - margin: 0 3px; - } -} - -@media screen and (max-width: 768px) { - .zone_imgbox { - gap: 6px; - --w: calc(50% - 3px); - } - - span.talk_date { - font-size: 14px; - } -} - -/* ================= 豆瓣卡片 ================= */ -#talk .talk_item .talk_content .douban-card { - margin-top: 10px !important; - text-decoration: none; - align-items: center; - border-radius: 12px; - color: #faebd7; - display: flex; - justify-content: center; - margin: 10px; - max-width: 400px; - overflow: hidden; - padding: 15px; - position: relative; -} - -/* ================= 外链卡片 ================= */ -#talk .talk_item .talk_content .shuoshuo-external-link { - width: 100%; - height: 80px; - margin-top: 10px; - border-radius: 12px; - background-color: var(--card-secondbg); - color: var(--card-text); - border: var(--card-border); - transition: background-color .3s ease-in-out; -} - -.shuoshuo-external-link:hover { - background-color: var(--button-hover-bg); -} - -.shuoshuo-external-link .external-link { - display: flex; - color: var(--text) !important; - width: 100%; - height: 100%; -} - -.shuoshuo-external-link .external-link:hover { - color: white !important; - text-decoration: none !important; -} - -.shuoshuo-external-link .external-link-left { - width: 60px; - height: 60px; - margin: 10px; - border-radius: 12px; - background-size: cover; - background-position: center; -} - -.shuoshuo-external-link .external-link-right { - display: flex; - flex-direction: column; - justify-content: center; - width: calc(100% - 80px); - padding: 10px; -} - -.shuoshuo-external-link .external-link-right .external-link-title { - font-size: 1.0rem; - font-weight: 800; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} - -.shuoshuo-external-link .external-link-right i { - margin-left: 5px; -} - -/* ================= 结尾 ================= */ -.limit { - width: 100%; - text-align: center; - margin-top: 30px; -} diff --git a/themes/butterfly/source/css/shuoshuoshouye.css b/themes/butterfly/source/css/shuoshuoshouye.css deleted file mode 100644 index 4159010..0000000 --- a/themes/butterfly/source/css/shuoshuoshouye.css +++ /dev/null @@ -1,140 +0,0 @@ -/* maintop */ - -#main_top { - display: flex; - justify-content: center; - z-index: 1; - max-width: 1400px; - margin: 20px auto; - width: 100%; - padding: 0 15px; - margin-top: 40px; - margin-bottom: 0px; -} - -.hide-aside #main_top { - width: 80%; -} - -.hide-aside #main_top #bber-talk { - max-width: 936px; -} - -@media screen and (min-width: 2000px) { - .hide-aside #main_top #bber-talk { - max-width: 80%; - } - - #main_top { - max-width: 70%; - } -} - -@media screen and (max-width: 1210px) { - .hide-aside #main_top { - padding: 0 12px; - } -} - -@media screen and (max-width: 900px) { - .hide-aside #main_top { - width: 100%; - padding: 0 15px; - } -} - -@media screen and (max-width: 768px) { - .hide-aside #main_top { - padding: 0 5px; - } - - div#main_top { - margin-top: 20px; - padding: 0 5px; - } -} - -/* 重点修改:bber-talk 的磨砂玻璃背景 */ -#bber-talk { - /* 统一磨砂玻璃效果 */ - background: linear-gradient(-45deg, rgba(255, 255, 255, .7), rgba(255, 255, 255, .8), rgba(255, 255, 255, .8), rgba(255, 255, 255, .7)) !important; - backdrop-filter: blur(10px); - -webkit-backdrop-filter: blur(10px); - - border-radius: 12px; /* 给哔哔栏加一点圆角,更符合玻璃质感 */ - border: 1px solid rgba(255, 255, 255, 0.3); /* 增加边缘高光 */ - - box-sizing: border-box; - cursor: pointer; - width: 100%; - min-height: 50px; - padding: .5rem 1rem; - display: flex; - align-items: center; - overflow: hidden; - font-weight: 700; - transition: all .3s ease-in-out; -} - -/* 暗黑模式适配 */ -[data-theme=dark] #bber-talk { - background: linear-gradient(-45deg, rgba(24, 40, 72, .7), rgba(35, 37, 58, .8), rgba(35, 37, 58, .8), rgba(24, 40, 72, .7)) !important; - border: 1px solid rgba(255, 255, 255, 0.1); -} - -#bber-talk:hover { - transform: translateY(-2px); /* 悬停微动,增加交互感 */ - box-shadow: 0 8px 20px rgba(0,0,0,0.1); -} - -#bber-talk, -#bber-talk a { - color: var(--font-color); -} - -#bber-talk svg.icon { - width: 1em; - height: 1em; - vertical-align: -.15em; - fill: currentColor; - overflow: hidden; - font-size: 20px; -} - -#bber-talk .item i { - margin-left: 5px; -} - -#bber-talk > i { - font-size: 1.1rem; -} - -#bber-talk .talk-list { - flex: 1; - max-height: 32px; - font-size: 16px; - padding: 0; - margin: 0; - overflow: hidden; -} - -#bber-talk .talk-list:hover { - color: var(--default-bg-color); - transition: all .2s ease-in-out; -} - -#bber-talk .talk-list li { - list-style: none; - width: 100%; - white-space: nowrap; - text-overflow: ellipsis; - overflow: hidden; - margin-left: 10px; -} - -@media screen and (min-width: 770px) { - #bber-talk .talk-list { - text-align: center; - margin-right: 20px; - } -} \ No newline at end of file diff --git a/themes/butterfly/source/css/style.css b/themes/butterfly/source/css/style.css deleted file mode 100644 index a00a8e4..0000000 --- a/themes/butterfly/source/css/style.css +++ /dev/null @@ -1,786 +0,0 @@ -/* #card-newest-comments img {border-radius: 10px; /* 设置最新评论圆角半径为10px,可以根据需要调整} */ - -/* 侧边栏个人信息卡片动态渐变色 */ -#aside-content>.card-widget.card-info { - position: relative; - z-index: 1; /* 确保原内容在上层显示 */ - background: linear-gradient(-45deg, rgba(255, 255, 255, .7), - rgba(255, 255, 255, .8), - rgba(255, 255, 255, .8), - rgba(255, 255, 255, .7)); - backdrop-filter: blur(10px); - /* -webkit-backdrop-filter: blur(10px);兼容性前缀,适用于一些旧版本的浏览器 */ -} - -#aside-content > .card-widget.card-info::before { - content: ""; - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - background: linear-gradient(to bottom, - rgba(255, 255, 255, 0.0), - rgba(255, 255, 255, 0.3), - rgba(255, 255, 255, 0.6), - rgba(255, 255, 255, 0.7)); /* 鼠标悬停时显示的背景图片 */ - background-size: cover; - background-repeat: no-repeat; - opacity: 0.5; - transition: opacity 0.8s; /* 过渡效果持续时间 */ - z-index: -1; -} - -[data-theme=dark] #aside-content > .card-widget.card-info::before { - background: linear-gradient(to bottom, - rgba(24, 40, 72, .1), - rgba(35, 37, 58, .3), - rgba(35, 37, 58, .6), - rgba(24, 40, 72, .7)); /* 鼠标悬停时显示的背景图片 */ - background-size: cover; - background-repeat: no-repeat; -} - -#aside-content > .card-widget.card-info:hover::before { - opacity: 1; -} -/* 卡片动态效果,好好看哈哈 */ -#aside-content div.card-widget.card-info .avatar-img { - transition: opacity 0.3s ease; /* 添加渐变效果 */ -} - -#aside-content div.card-widget.card-info .avatar-img:hover { - opacity: 0; /* 鼠标移上去时完全透明 */ -} - -#card-info-btn:hover, #card-info-btn:hover .icon, #card-info-btn:hover span { - transform: scale(1.1); /* 鼠标悬停时放大到110% */ -} - -[data-theme=dark] #aside-content>.card-widget.card-info { - background: linear-gradient(-45deg, rgba(24, 40, 72, .6), - rgba(35, 37, 58, .5), - rgba(35, 37, 58, .5), - rgba(24, 40, 72, .6)); - backdrop-filter: blur(10px); - /* -webkit-backdrop-filter: blur(10px);兼容性前缀,适用于一些旧版本的浏览器 */ -} - -/* 侧边栏公告栏卡片渐变色 */ -#aside-content>.card-widget.card-announcement { - background: linear-gradient(-45deg, rgba(255, 255, 255, .7), - rgba(255, 255, 255, .8), - rgba(255, 255, 255, .8), - rgba(255, 255, 255, .7)); - backdrop-filter: blur(10px); - /* -webkit-backdrop-filter: blur(10px);兼容性前缀,适用于一些旧版本的浏览器 */ -} - -[data-theme=dark] #aside-content>.card-widget.card-announcement { - background: linear-gradient(-45deg, rgba(24, 40, 72, .7), - rgba(35, 37, 58, .8), - rgba(35, 37, 58, .8), - rgba(24, 40, 72, .7)); - backdrop-filter: blur(10px); - /* -webkit-backdrop-filter: blur(10px);兼容性前缀,适用于一些旧版本的浏览器 */ -} - -/* 侧边栏目录最新文章卡片渐变色 */ -#aside-content>.sticky_layout>.card-widget { - background: linear-gradient(-45deg, rgba(255, 255, 255, .7), - rgba(255, 255, 255, .8), - rgba(255, 255, 255, .8), - rgba(255, 255, 255, .7)); - backdrop-filter: blur(10px); - /* -webkit-backdrop-filter: blur(10px);兼容性前缀,适用于一些旧版本的浏览器 */ -} - -[data-theme=dark] #aside-content>.sticky_layout>.card-widget { - background: linear-gradient(-45deg, rgba(24, 40, 72, .7), - rgba(35, 37, 58, .8), - rgba(35, 37, 58, .8), - rgba(24, 40, 72, .7)); - backdrop-filter: blur(10px); - /* -webkit-backdrop-filter: blur(10px);兼容性前缀,适用于一些旧版本的浏览器 */ -} - -/* 侧边栏欢迎卡片渐变色 */ -#aside-content>.card-widget.welcomeBoxClass { - background: linear-gradient(-45deg, rgba(255, 255, 255, .7), - rgba(255, 255, 255, .8), - rgba(255, 255, 255, .8), - rgba(255, 255, 255, .7)); - backdrop-filter: blur(10px); - /* -webkit-backdrop-filter: blur(10px);兼容性前缀,适用于一些旧版本的浏览器 */ -} - -[data-theme=dark] #aside-content>.card-widget.welcomeBoxClass { - background: linear-gradient(-45deg, rgba(24, 40, 72, .7), - rgba(35, 37, 58, .8), - rgba(35, 37, 58, .8), - rgba(24, 40, 72, .7)); - backdrop-filter: blur(10px); - /* -webkit-backdrop-filter: blur(10px);兼容性前缀,适用于一些旧版本的浏览器 */ -} - -/* 公告卡片渐变色 */ -#recent-posts>#noticeList { - background: linear-gradient(-45deg, rgba(255, 255, 255, .7), - rgba(255, 255, 255, .8), - rgba(255, 255, 255, .8), - rgba(255, 255, 255, .7)); - backdrop-filter: blur(10px); - /* -webkit-backdrop-filter: blur(10px);兼容性前缀,适用于一些旧版本的浏览器 */ -} - -[data-theme=dark] #recent-posts>#noticeList { - background: linear-gradient(-45deg, rgba(24, 40, 72, .7), - rgba(35, 37, 58, .8), - rgba(35, 37, 58, .8), - rgba(24, 40, 72, .7)); - backdrop-filter: blur(10px); - /* -webkit-backdrop-filter: blur(10px);兼容性前缀,适用于一些旧版本的浏览器 */ -} - -/* 个人信息Follow me按钮 */ -#aside-content>.card-widget.card-info>#card-info-btn { - background-color: #3eb8be; - border-radius: 10px; -} - -/*文章页面*/ -.layout>#post { - background: linear-gradient(-45deg, rgba(255, 255, 255, .7), - rgba(255, 255, 255, .8), - rgba(255, 255, 255, .8), - rgba(255, 255, 255, .7)); - backdrop-filter: blur(10px); - /* -webkit-backdrop-filter: blur(10px);兼容性前缀,适用于一些旧版本的浏览器 */ -} - -[data-theme=dark] .layout>#post { - background: linear-gradient(-45deg, rgba(24, 40, 72, .7), - rgba(35, 37, 58, .8), - rgba(35, 37, 58, .8), - rgba(24, 40, 72, .7)); - backdrop-filter: blur(10px); - /* -webkit-backdrop-filter: blur(10px);兼容性前缀,适用于一些旧版本的浏览器 */ -} - -/*分类条*/ -#recent-posts>.category-bar { - background: linear-gradient(-45deg, rgba(255, 255, 255, .7), - rgba(255, 255, 255, .8), - rgba(255, 255, 255, .8), - rgba(255, 255, 255, .7))!important; - backdrop-filter: blur(10px)!important; - /* -webkit-backdrop-filter: blur(10px);兼容性前缀,适用于一些旧版本的浏览器 */ -} - -[data-theme=dark] #recent-posts>.category-bar { - background: linear-gradient(-45deg, rgba(24, 40, 72, .7), - rgba(35, 37, 58, .8), - rgba(35, 37, 58, .8), - rgba(24, 40, 72, .7))!important; - backdrop-filter: blur(10px)!important; - /* -webkit-backdrop-filter: blur(10px);兼容性前缀,适用于一些旧版本的浏览器 */ -} - -/*主页文章预览页面*/ -#recent-posts>.recent-post-items>.recent-post-item { - background: linear-gradient(-45deg, rgba(255, 255, 255, .7), - rgba(255, 255, 255, .8), - rgba(255, 255, 255, .8), - rgba(255, 255, 255, .7))!important; - backdrop-filter: blur(10px)!important; - /* -webkit-backdrop-filter: blur(10px);兼容性前缀,适用于一些旧版本的浏览器 */ -} - -[data-theme=dark] #recent-posts>.recent-post-items>.recent-post-item { - background: linear-gradient(-45deg, rgba(24, 40, 72, .7), - rgba(35, 37, 58, .8), - rgba(35, 37, 58, .8), - rgba(24, 40, 72, .7))!important; - backdrop-filter: blur(10px)!important; - /* -webkit-backdrop-filter: blur(10px);兼容性前缀,适用于一些旧版本的浏览器 */ -} - -/*分类页面*/ -.layout>#page { - background: linear-gradient(-45deg, rgba(255, 255, 255, .7), - rgba(255, 255, 255, .8), - rgba(255, 255, 255, .8), - rgba(255, 255, 255, .7)); - backdrop-filter: blur(10px); - /* -webkit-backdrop-filter: blur(10px); 兼容性前缀,适用于一些旧版本的浏览器 */ -} - -[data-theme=dark] .layout>#page { - background: linear-gradient(-45deg, rgba(24, 40, 72, .7), - rgba(35, 37, 58, .8), - rgba(35, 37, 58, .8), - rgba(24, 40, 72, .7)); - backdrop-filter: blur(10px); - /* -webkit-backdrop-filter: blur(10px); 兼容性前缀,适用于一些旧版本的浏览器 */ -} - - -/*时间轴页面*/ -.layout>#archive { - background: linear-gradient(-45deg, rgba(255, 255, 255, .7), - rgba(255, 255, 255, .8), - rgba(255, 255, 255, .8), - rgba(255, 255, 255, .7)); - backdrop-filter: blur(10px); - /* -webkit-backdrop-filter: blur(10px);兼容性前缀,适用于一些旧版本的浏览器 */ -} - -[data-theme=dark] .layout>#archive { - background: linear-gradient(-45deg, rgba(24, 40, 72, .7), - rgba(35, 37, 58, .8), - rgba(35, 37, 58, .8), - rgba(24, 40, 72, .7)); - backdrop-filter: blur(10px); - /* -webkit-backdrop-filter: blur(10px);兼容性前缀,适用于一些旧版本的浏览器 */ -} - -/*分类点进去的页面*/ -.layout>#category { - background: linear-gradient(-45deg, rgba(255, 255, 255, .7), - rgba(255, 255, 255, .8), - rgba(255, 255, 255, .8), - rgba(255, 255, 255, .7)); - backdrop-filter: blur(10px); - /* -webkit-backdrop-filter: blur(10px);兼容性前缀,适用于一些旧版本的浏览器 */ -} - -[data-theme=dark] .layout>#category { - background: linear-gradient(-45deg, rgba(24, 40, 72, .7), - rgba(35, 37, 58, .8), - rgba(35, 37, 58, .8), - rgba(24, 40, 72, .7)); - backdrop-filter: blur(10px); - /* -webkit-backdrop-filter: blur(10px);兼容性前缀,适用于一些旧版本的浏览器 */ -} - -/*标签点进去的页面*/ -.layout>#tag { - background: linear-gradient(-45deg, rgba(255, 255, 255, .7), - rgba(255, 255, 255, .8), - rgba(255, 255, 255, .8), - rgba(255, 255, 255, .7)); - backdrop-filter: blur(10px); - /* -webkit-backdrop-filter: blur(10px);兼容性前缀,适用于一些旧版本的浏览器 */ -} - -[data-theme=dark] .layout>#tag { - background: linear-gradient(-45deg, rgba(24, 40, 72, .7), - rgba(35, 37, 58, .8), - rgba(35, 37, 58, .8), - rgba(24, 40, 72, .7)); - backdrop-filter: blur(10px); - /* -webkit-backdrop-filter: blur(10px);兼容性前缀,适用于一些旧版本的浏览器 */ -} - -.layout.hide-aside { - max-width: 1200px; -} - -/* 设置黑夜的时候,社交按钮为白色 */ -[data-theme=dark] .social-icon i { - color: rgba(188, 188, 188) !important; /* 设置为白色 */ -} - -.layout{ - width: 100%; - max-width:1400px; -} -/* 重新定义宽度 */ - -.layout > div:first-child { - width: 100%; -} -#post { - width: 78%; -} -.aside-content { - max-width: 100%; - min-width: 300px; -} -.layout.hide-aside { - max-width: 1400px; -} -/* 定义是否侧边栏的宽度 */ - -#recent-posts > .recent-post-item { - height: 19em; - border: var(--style-border); -} -#recent-posts > .recent-post-item >.recent-post-info { - padding: 0 56px; - width: 64%; -} -@media screen and (max-width: 768px) { - #recent-posts > .recent-post-item { - height: auto; - } - #recent-posts > .recent-post-item >.recent-post-info { - padding: 20px 20px 30px; - width: 100%; - text-align: center; - } -} -/* 主页文章列表图片宽度 */ - -.loading-img { - background: url(https://free.picui.cn/free/2025/08/10/689845496a283.png) no-repeat center center; - background-size: cover; -} - -#footer { - background-color: rgba(0, 0, 0, 0); /* 修改透明色 */ - color: #fff; - padding: 0; -} - -#footer .footer-other { - padding: 0; -} - -.footer-copyright{ - padding: 0; -} - -.my-footer-logo { - color: white; -} - -.my-footer-wave-svg { - height: 50px; - width: 100%; - transform: scale(-1, -1) translateY(-10px); /*;*/ -} - -.my-footer-wave-path { - fill: #177ecd; -} - -.my-footer-content { - margin-left: auto; - margin-right: auto; - max-width: 1230px; - padding: 40px 15px 450px; - position: relative; -} - -.my-footer-content-column { - box-sizing: border-box; - float: left; - padding-left: 15px; - padding-right: 15px; - width: 100%; - color: #fff; -} - -.my-footer-content-column ul li a { - color: #fff; - text-decoration: none; -} - -.my-footer-logo-link { - display: inline-block; -} - -.my-footer-menu { - margin-top: 15px; -} - -.my-footer-menu-name { - color: #fffff2; - font-size: 15px; - font-weight: 900; - letter-spacing: .1em; - line-height: 18px; - margin-bottom: 0; - margin-top: 0; - text-transform: uppercase; -} - -.my-footer-menu-list { - list-style: none; - margin-bottom: 0; - margin-top: 10px; - padding-left: 0; -} - -.my-footer-menu-list li { - margin-top: 5px; -} - -.my-footer-call-to-action-description { - color: #fffff2; - margin-top: 10px; - margin-bottom: 20px; -} - -.my-footer-call-to-action-button:hover { - background-color: #fffff2; - color: #00bef0; -} - -.button:last-of-type { - margin-right: 0; -} - -.my-footer-call-to-action-button { - background-color: #03708c; - border-radius: 21px; - color: #fffff2; - display: inline-block; - font-size: 11px; - font-weight: 900; - letter-spacing: .1em; - line-height: 18px; - padding: 12px 30px; - margin: 0 10px 10px 0; - text-decoration: none; - text-transform: uppercase; - transition: background-color .2s; - cursor: pointer; - position: relative; -} - -.my-footer-call-to-action { - margin-top: 17px; -} - -.my-footer-call-to-action-title { - color: #fffff2; - font-size: 14px; - font-weight: 900; - letter-spacing: .1em; - line-height: 18px; - margin-bottom: 0; - margin-top: 0; - text-transform: uppercase; -} - -.my-footer-call-to-action-link-wrapper { - margin-bottom: 0; - margin-top: 10px; - color: #fff; - text-decoration: none; -} - -.my-footer-call-to-action-link-wrapper a { - color: #fff; - text-decoration: none; -} - - -.my-footer-social-links { - bottom: -1px; - height: 54px; - position: absolute; - right: 0; - width: 236px; -} - -.my-footer-social-amoeba-svg { - height: 54px; - left: 0; - display: block; - position: absolute; - top: 0; - width: 236px; -} - -.my-footer-social-amoeba-path { - fill: #03708c; -} - -.my-footer-social-link.email { - height: 41px; - left: 5px; - top: 14px; - width: 41px; -} - -.my-footer-social-link { - display: block; - padding: 10px; - position: absolute; -} - -.hidden-link-text { - position: absolute; - clip: rect(1px 1px 1px 1px); - clip: rect(1px, 1px, 1px, 1px); - -webkit-clip-path: inset(0px 0px 99.9% 99.9%); - clip-path: inset(0px 0px 99.9% 99.9%); - overflow: hidden; - height: 1px; - width: 1px; - padding: 0; - border: 0; - top: 50%; -} - -.my-footer-social-icon-svg { - display: block; -} - -.my-footer-social-icon-path { - fill: #fffff2; - transition: fill .2s; -} - -.my-footer-social-link.follow { - height: 42px; - left: 124px; - top: 13px; - width: 42px; -} - -.my-footer-social-link.rss { - height: 43px; - left: 178px; - top: 10px; - width: 43px; -} - -.my-footer-social-link.github { - height: 62px; - left: 54px; - top: -4px; - width: 62px; -} - -.my-footer-copyright { - background-color: #03708c; - color: #fff; - padding: 15px 30px; - text-align: center; -} - -.my-footer-copyright-wrapper { - margin-left: auto; - margin-right: auto; - max-width: 1200px; -} - -.my-footer-copyright-text { - color: #fff; - font-size: 13px; - font-weight: 400; - line-height: 18px; - margin-bottom: 0; - margin-top: 0; -} - -.my-footer-copyright-link { - color: #fff; - text-decoration: none; -} - -.my-footer-content-div { - background: #177ecd -} - -.my-footer-svg-div { - width: 100%; - max-height: 200px; -} - -/* Media Query For different screens */ -@media (min-width: 320px) and (max-width: 479px) { - /* smartphones, portrait iPhone, portrait 480x320 phones (Android) */ - .my-footer-content { - margin-left: auto; - margin-right: auto; - max-width: 1230px; - padding: 40px 15px 649px; - position: relative; - } - - /* 不展示logo */ - .my-footer-logo { - display: none; - } -} - -@media (min-width: 480px) and (max-width: 599px) { - /* smartphones, Android phones, landscape iPhone */ - .my-footer-content { - margin-left: auto; - margin-right: auto; - max-width: 1230px; - padding: 40px 15px 738px; - position: relative; - } - - .my-footer-logo { - padding-left: 177px; /* Qlogo 稍微偏移一点 */ - padding-right: 170px; - } -} - -@media (min-width: 600px) and (max-width: 800px) { - /* portrait tablets, portrait iPad, e-readers (Nook/Kindle), landscape 800x480 phones (Android) */ - .my-footer-content { - margin-left: auto; - margin-right: auto; - max-width: 1230px; - padding: 40px 15px 758px; - position: relative; - } - - -} - -@media (min-width: 801px) { - /* tablet, landscape iPad, lo-res laptops ands desktops */ - -} - -@media (min-width: 1025px) { - /* big landscape tablets, laptops, and desktops */ - -} - -@media (min-width: 1281px) { - /* hi-res laptops and desktops */ - -} - - -@media (min-width: 760px) { - .my-footer-content { - margin-left: auto; - margin-right: auto; - max-width: 1230px; - padding: 10px 15px 237px; - position: relative; - } - - - .my-footer-content-column { - /*五列的话 19.99 %*/ - width: 24.99%; - } -} - -@media (min-width: 568px) { - -} - -@media (min-width: 600px) and (max-width: 760px) { - /* 在这里编写适用于小屏幕的样式 */ - .my-footer-logo { - padding-left: 212px; /* Qlogo 稍微偏移一点 */ - padding-right: 204px; - } -} - -/* 页脚波浪的颜色 */ -[data-theme='dark'] .my-footer-wave-path { - fill: #0f3858; -} -[data-theme='dark'] .my-footer-content-div { - background-color: #0f3858; -} - -/* 页脚海底的颜色 */ -[data-theme='dark'] .my-footer-copyright { - background-color: #2b3f49; -} -[data-theme='dark'] .my-footer-social-amoeba-path { - fill: #2b3f49; -} - -/* 仿照 Liushen & Butterfly 逻辑的高级表格美化 */ - -/* 1. 表格容器处理 - 关键响应式滚动层 */ -#article-container .table-wrap, -#article-container > table { - background: var(--liushen-card-bg); - border: var(--liushen-card-border); - border-radius: 12px; - box-shadow: var(--card-box-shadow); - margin: 20px 0; - backdrop-filter: blur(10px); - - /* 开启横向滚动逻辑 */ - width: 100%; - overflow-x: auto; /* 小屏幕自动开启滚动 */ - display: block; /* 必须设为 block 滚动才生效 */ -} - -/* 2. 针对数据表格的结构重置(排除代码块 .highlight) */ -#article-container :not(.highlight) > table { - display: table; /* 容器内部恢复为 table 模式 */ - border-collapse: separate; - border-spacing: 0; - border: none !important; - width: 100%; - min-width: 500px; /* 强制表格在窄屏下不收缩过猛,从而触发容器滚动 */ -} - -/* 3. 表头美化 - 渐变风格 */ -#article-container :not(.highlight) > table thead { - background: linear-gradient(-45deg, #3eb8be, #6ecad0); -} - -#article-container :not(.highlight) > table th { - color: #fff !important; - font-weight: 700; - padding: 12px 16px; - border: none !important; - text-align: left; -} - -/* 4. 单元格美化 */ -#article-container :not(.highlight) > table td { - padding: 10px 16px; - color: var(--liushen-text); - /* 仅保留底部细线,模仿 Liushen 的极简风格 */ - border-bottom: 1px solid rgba(128, 128, 128, 0.1) !important; - border-right: none !important; - border-top: none !important; - border-left: none !important; - transition: background 0.3s; -} - -/* 5. 隔行变色 */ -#article-container :not(.highlight) > table tbody tr:nth-child(even) { - background: rgba(153, 169, 191, 0.05); -} - -/* 6. 悬停高亮 */ -#article-container :not(.highlight) > table tbody tr:hover td { - background: rgba(62, 184, 190, 0.1); -} - -/* 7. 滚动条美化 (仿照你代码中的蓝色滚动条) */ -#article-container .table-wrap::-webkit-scrollbar { - height: 4px; /* 横向滚动条高度 */ -} -#article-container .table-wrap::-webkit-scrollbar-thumb { - background: var(--scrollbar-color, #2679cc); - border-radius: 10px; -} - -/* 8. 黑夜模式适配 */ -[data-theme='dark'] #article-container .table-wrap, -[data-theme='dark'] #article-container > table { - background: var(--liushen-card-secondbg); -} - -[data-theme='dark'] #article-container :not(.highlight) > table thead { - background: linear-gradient(-45deg, #1f4a4d, #3eb8be); -} - -[data-theme='dark'] #article-container :not(.highlight) > table td { - border-bottom: 1px solid rgba(255, 255, 255, 0.05) !important; - color: rgba(255, 255, 255, 0.7); -} \ No newline at end of file diff --git a/themes/butterfly/source/css/swiper.css b/themes/butterfly/source/css/swiper.css deleted file mode 100644 index 650d91c..0000000 --- a/themes/butterfly/source/css/swiper.css +++ /dev/null @@ -1,138 +0,0 @@ -/* 1. 强制清空外层容器的背景、边框和阴影 */ -#swiperBox, -#swiper_container, -.blog-slider, -.swiper-container { - padding: 0 !important; - margin: 0 auto !important; - background: none !important; - border: none !important; - box-shadow: none !important; - width: 100% !important; - overflow: hidden !important; -} - -/* 2. 强力锁定比例,防止 JS 动态修改高度 */ -div#swiper_container { - width: 100% !important; - - /* 使用 vw 单位根据屏幕宽度动态计算高度 */ - /* 16:9 比例下,高度 = 宽度 * 56.25% */ - height: 56.25vw !important; - - /* 设定最大高度,防止在 PC 端显得过大(可根据需求调整,比如 400px) */ - max-height: 450px !important; - - /* 设定最小高度,防止在极小屏幕下太扁 */ - min-height: 180px !important; - - aspect-ratio: 16 / 9 !important; - display: block !important; - border-radius: 12px; - position: relative !important; -} - -/* 强制内部所有幻灯片跟随父容器高度 */ -.swiper-wrapper, .swiper-slide, .blog-slider__item { - height: 100% !important; -} -/* 3. 彻底隐藏分页圆点或橙色元素 */ -.blog-slider__pagination, .swiper-pagination { - display: none !important; -} - -.blog-slider__pagination .swiper-pagination-bullet, -.swiper-pagination-bullet, -.swiper-button-next, -.swiper-button-prev { - outline: none !important; -} - -/* 4. 图片填充修复:确保铺满不留白 */ -.blog-slider__item { - width: 100% !important; - height: 100% !important; - background-size: cover !important; - background-position: center !important; - margin: 0 !important; - padding: 0 !important; -} - -/* 5. 打包内容块并整体居中 */ -.blog-slider__content { - background: rgba(0, 0, 0, 0.45) !important; - width: 100% !important; - height: 100% !important; - position: absolute !important; - top: 0 !important; - left: 0 !important; - margin: 0 !important; - padding: 0 !important; -} - -/* 核心:将内容容器设为居中参考点 */ -.blog-slider__content .blog-slider__item_content_wrapper, /* 尝试抓取可能的内层包裹 */ -.blog-slider__content > div:first-child, /* 备选抓取 */ -.blog-slider__content { - display: flex !important; - flex-direction: column !important; - justify-content: center !important; - align-items: center !important; -} - -/* 针对标题和描述的统一样式 */ -.blog-slider__title, -.blog-slider__text { - position: relative !important; /* 放弃绝对定位,回归正常流,防止重叠 */ - top: 0 !important; - left: 0 !important; - transform: none !important; - width: 85% !important; - text-align: center !important; - margin: 5px auto !important; /* 自动上下留白 */ - color: #ffffff !important; -} - -a.blog-slider__title { - font-size: 1.4rem !important; - font-weight: bold !important; - line-height: 1.3 !important; - text-decoration: none !important; -} - -.blog-slider__text { - font-size: 0.9rem !important; - opacity: 0.9; - /* 限制描述文字行数,防止在16:9中撑破容器 */ - display: -webkit-box !important; - -webkit-line-clamp: 2; - -webkit-box-orient: vertical; - overflow: hidden; -} - -/* 针对手机端的极小屏幕适配 */ -@media screen and (max-width: 768px) { - a.blog-slider__title { - font-size: 1.1rem !important; - } - .blog-slider__text { - font-size: 0.8rem !important; - -webkit-line-clamp: 1; /* 手机端只留一行描述,防止空间拥挤 */ - } -} -/* 6. 移动端微调:防止文字过大或间距过宽 */ -@media screen and (max-width: 768px) { - .blog-slider__content { - padding: 0 15px !important; - } - a.blog-slider__title { - font-size: 1.1rem !important; - } -} - -/* 7. 左右箭头样式:白色且无背景 */ -.swiper-button-next::after, .swiper-button-prev::after { - font-size: 1.2rem !important; - color: #fff !important; - background: none !important; -} \ No newline at end of file diff --git a/themes/butterfly/source/css/var.styl b/themes/butterfly/source/css/var.styl deleted file mode 100644 index ee37ba6..0000000 --- a/themes/butterfly/source/css/var.styl +++ /dev/null @@ -1,233 +0,0 @@ -// color -$bright-blue = #49B1F5 -$strong-cyan = #00c4b6 -$light-orange = #FF7242 -$light-red = #F47466 -$themeColorEnable = hexo-config('theme_color') && hexo-config('theme_color.enable') -$theme-color = $themeColorEnable && hexo-config('theme_color.main') ? convert(hexo-config('theme_color.main')) : $bright-blue -$theme-paginator-color = $themeColorEnable && hexo-config('theme_color.paginator') ? convert(hexo-config('theme_color.paginator')) : $strong-cyan -$theme-text-selection-color = $themeColorEnable && hexo-config('theme_color.text_selection') ? convert(hexo-config('theme_color.text_selection')) : $strong-cyan -$theme-link-color = $themeColorEnable && hexo-config('theme_color.link_color') ? convert(hexo-config('theme_color.link_color')) : $bright-blue -$theme-hr-color = $themeColorEnable && hexo-config('theme_color.hr_color') ? convert(hexo-config('theme_color.hr_color')) : $bright-blue -$code-foreground = $themeColorEnable && hexo-config('theme_color.code_foreground') ? convert(hexo-config('theme_color.code_foreground')) : $light-red -$code-background = $themeColorEnable && hexo-config('theme_color.code_background') ? convert(hexo-config('theme_color.code_background')) : rgba(27, 31, 35, .05) -$theme-toc-color = $themeColorEnable && hexo-config('theme_color.toc_color') ? convert(hexo-config('theme_color.toc_color')) : $strong-cyan -// font -$chineseFont = $language == 'zh-CN' ? 'Microsoft YaHei' : 'Microsoft JhengHei' -$default-font-family = -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Helvetica Neue', Lato, Roboto, 'PingFang SC', $chineseFont, sans-serif -$default-code-font = consolas, Menlo, monospace, 'PingFang SC', $chineseFont, sans-serif -$font-family = hexo-config('font.font_family') ? unquote(hexo-config('font.font_family')) : $default-font-family -$code-font-family = hexo-config('font.code_font_family') ? unquote(hexo-config('font.code_font_family')) : $default-code-font -$site-name-font = hexo-config('blog_title_font.font_family') && unquote(hexo-config('blog_title_font.font_family')) -// hr -$hrEnable = hexo-config('hr_icon') && hexo-config('hr_icon.enable') -$hr-icon = $hrEnable && hexo-config('hr_icon.icon') ? hexo-config('hr_icon.icon') : '\f0c4' -$hr-icon-top = $hrEnable && hexo-config('hr_icon.icon_top') ? convert(hexo-config('hr_icon.icon_top')) : -10px -// page beautify -$beautifyEnable = hexo-config('beautify.enable') -$title-prefix-icon = $beautifyEnable && hexo-config('beautify.title_prefix_icon') ? hexo-config('beautify.title_prefix_icon') : '\f0c1' -$title-prefix-icon-color = $beautifyEnable && hexo-config('beautify.title_prefix_icon_color') ? convert(hexo-config('beautify.title_prefix_icon_color')) : $light-red -// Global Variables -$font-size = hexo-config('font.global_font_size') ? convert(hexo-config('font.global_font_size')) : 14px -$code-font-size = hexo-config('font.code_font_size') ? convert(hexo-config('font.code_font_size')) : var(--global-font-size) -$font-color = #1F2D3D -$text-line-height = 2 -$index_top_img_height = hexo-config('index_top_img_height') ? convert(hexo-config('index_top_img_height')) : 100vh -$index_site_info_top = hexo-config('index_site_info_top') ? convert(hexo-config('index_site_info_top')) : 43% -// Global color & SVG -$light-blue = $theme-color -$dark-black = #000000 -$light-grey = #EEEEEE -$grey = #858585 -$dark-grey = #cacaca -$white = #FFFFFF -$whitesmoke = #f5f5f5 -$font-black = #4C4948 -$card-bg = $white -$text-highlight-color = $font-color -$text-hover = $theme-color -$text-bg-hover = $theme-color -// code -$line-height-code-block = 1.6 -$blockquote-color = #6a737d -$blockquote-padding-color = $themeColorEnable && hexo-config('theme_color.blockquote_padding_color') ? convert(hexo-config('theme_color.blockquote_padding_color')) : #49B1F5 -$blockquote-background-color = $themeColorEnable && hexo-config('theme_color.blockquote_background_color') ? alpha(convert(hexo-config('theme_color.blockquote_background_color')), .1) : alpha($blockquote-padding-color, .1) -// page -$body-bg = #fff -$a-link-color = #99a9bf -$sticky-color = $light-orange -$theme-meta-color = $themeColorEnable && hexo-config('theme_color.meta_color') ? convert(hexo-config('theme_color.meta_color')) : #858585 -// sidebar -$sidebar-background = #f6f8fa -$sidebar-width = 330px -// aside -$toc-link-color = #666261 -$toc-mobile-width = calc(100% - 80px) -$toc-mobile-maxWidth = 380px -$toc-active-color = #fff -// Button -$button-color = #fff -$button-hover-color = $themeColorEnable && hexo-config('theme_color.button_hover') ? convert(hexo-config('theme_color.button_hover')) : $light-orange -$button-bg = $theme-color -$pseudo-hover = $button-hover-color -// scrollbar -$scrollbar-color = $themeColorEnable && hexo-config('theme_color.scrollbar_color') ? convert(hexo-config('theme_color.scrollbar_color')) : $theme-color -// table -$table-thead-bg = #99a9bf -// reward -$reward-pop-up-bg = #f5f5f5 -$reward-pop-up-color = #858585 -// search -$search-bg = #f6f8fa -$search-input-color = $font-black -$search-color = $theme-color -$search-keyword-highlight = #F47466 -$search-a-color = $font-black -// comments -$comments-switch-first-text = $bright-blue -$comments-switch-second-text = $light-orange -$comments-switch-round = #fff -$comments-switch-bg = #f6f8fa -// noticeOutdate -$noticeOutdate-bg = #ffe6e6 -$noticeOutdate-color = #ff6666 -$noticeOutdate-border = #ff8080 -// gallery -$gallery-color = #fff -// tag-hide -$tag-hide-bg = $theme-color -$tag-hide-toggle-bg = #f0f0f0 -// preloader -$preloader-bg = #37474f -$preloader-word-color = #fff -// rightside -$rightside-bottom = hexo-config('rightside_bottom') ? convert(hexo-config('rightside_bottom')) : 40px -// fireworks -$fireworks-zIndex = hexo-config('fireworks.zIndex') ? hexo-config('fireworks.zIndex') : 99999 -// Tag Plugins - Note -hexo-config('note.light_bg_offset') is a 'unit' ? ($lbg = unit(hexo-config('note.light_bg_offset'), '%')) : ($lbg = 0) -$note-types = 'default' 'primary' 'info' 'success' 'warning' 'danger' -// Default -$note-default-border = #777 -$note-default-bg = lighten(spin($note-default-border, 0), 94% + $lbg) -$note-default-text = $note-default-border -$note-default-icon = '\f0a9' -$note-modern-default-border = #e1e1e1 -$note-modern-default-bg = lighten(spin($note-modern-default-border, 10), 60% + ($lbg * 4)) -$note-modern-default-text = #666 -$note-modern-default-hover = darken(spin($note-modern-default-text, -10), 32%) -// Primary -$note-primary-border = #6f42c1 -$note-primary-bg = lighten(spin($note-primary-border, 10), 92% + $lbg) -$note-primary-text = $note-primary-border -$note-primary-icon = '\f055' -$note-modern-primary-border = #e1c2ff -$note-modern-primary-bg = lighten(spin($note-modern-primary-border, 10), 40% + ($lbg * 4)) -$note-modern-primary-text = #6f42c1 -$note-modern-primary-hover = darken(spin($note-modern-primary-text, -10), 22%) -// Info -$note-info-border = #428bca -$note-info-bg = lighten(spin($note-info-border, -10), 91% + $lbg) -$note-info-text = $note-info-border -$note-info-icon = '\f05a' -$note-modern-info-border = #b3e5ef -$note-modern-info-bg = lighten(spin($note-modern-info-border, 10), 50% + ($lbg * 4)) -$note-modern-info-text = #31708f -$note-modern-info-hover = darken(spin($note-modern-info-text, -10), 32%) -// Success -$note-success-border = #5cb85c -$note-success-bg = lighten(spin($note-success-border, 10), 90% + $lbg) -$note-success-text = $note-success-border -$note-success-icon = '\f058' -$note-modern-success-border = #d0e6be -$note-modern-success-bg = lighten(spin($note-modern-success-border, 10), 40% + ($lbg * 4)) -$note-modern-success-text = #3c763d -$note-modern-success-hover = darken(spin($note-modern-success-text, -10), 27%) -// Warning -$note-warning-border = #f0ad4e -$note-warning-bg = lighten(spin($note-warning-border, 10), 88% + $lbg) -$note-warning-text = $note-warning-border -$note-warning-icon = '\f06a' -$note-modern-warning-border = #fae4cd -$note-modern-warning-bg = lighten(spin($note-modern-warning-border, 10), 43% + ($lbg * 4)) -$note-modern-warning-text = #8a6d3b -$note-modern-warning-hover = darken(spin($note-modern-warning-text, -10), 18%) -// Danger -$note-danger-border = #d9534f -$note-danger-bg = lighten(spin($note-danger-border, -10), 92% + $lbg) -$note-danger-text = $note-danger-border -$note-danger-icon = '\f056' -$note-modern-danger-border = #ebcdd2 -$note-modern-danger-bg = lighten(spin($note-modern-danger-border, 10), 35% + ($lbg * 4)) -$note-modern-danger-text = #a94442 -$note-modern-danger-hover = darken(spin($note-modern-danger-text, -10), 22%) -// Tag Plugins - Button/note -$color-types = 'blue' 'pink' 'red' 'purple' 'orange' 'green' -$btn-color = #fff -$btn-default-color = #777 -$tagsP-blue-color = #428bca -$tagsP-pink-color = #FF69B4 -$tagsP-red-color = #FF0000 -$tagsP-orange-color = #FF8C00 -$tagsP-purple-color = #6f42c1 -$tagsP-green-color = #5cb85c -// Tag Plugins - Tab -$tab-border-color = #f0f0f0 -$tab-button-bg = #f0f0f0 -$tab-button-color = $font-color -$tab-button-hover-bg = darken($tab-border-color, 8) -$tab-active-border-color = $theme-color -$tab-button-active-bg = $card-bg -$tab-to-top-color = #99a9bf -$tab-to-top-hover-color = $theme-color -// Tag Plugins - timeline -$timeline-default-color = $theme-color -// note - darkmode -// Default -$note-dark-default-border = #5a5a5a -$note-dark-default-bg = #2b2b2b -$note-dark-default-text = #b3b3b3 -$note-dark-modern-default-border = #9a9a9a -$note-dark-modern-default-bg = #353535 -$note-dark-modern-default-text = #c4c4c4 -$note-dark-modern-default-hover = #d0d0d0 -// Primary -$note-dark-primary-border = #5935a1 -$note-dark-primary-bg = #2e1c3e -$note-dark-primary-text = #a47dd4 -$note-dark-modern-primary-border = #9985cc -$note-dark-modern-primary-bg = #3c2d4c -$note-dark-modern-primary-text = #b693e6 -$note-dark-modern-primary-hover = #c9a8f0 -// Info -$note-dark-info-border = #346fa2 -$note-dark-info-bg = #1f2e3b -$note-dark-info-text = #7bb3db -$note-dark-modern-info-border = #7ca8b5 -$note-dark-modern-info-bg = #2b3c44 -$note-dark-modern-info-text = #8fc6e0 -$note-dark-modern-info-hover = #a3d4ea -// Success -$note-dark-success-border = #4a944a -$note-dark-success-bg = #202e20 -$note-dark-success-text = #82c682 -$note-dark-modern-success-border = #8bb087 -$note-dark-modern-success-bg = #2c3d2c -$note-dark-modern-success-text = #96d196 -$note-dark-modern-success-hover = #a8dca8 -// Warning -$note-dark-warning-border = #c08a3e -$note-dark-warning-bg = #3e301f -$note-dark-warning-text = #e6ba6b -$note-dark-modern-warning-border = #b8a285 -$note-dark-modern-warning-bg = #4b3c2b -$note-dark-modern-warning-text = #d4b373 -$note-dark-modern-warning-hover = #e0c080 -// Danger -$note-dark-danger-border = #b34440 -$note-dark-danger-bg = #3b201f -$note-dark-danger-text = #e67572 -$note-dark-modern-danger-border = #c7898c -$note-dark-modern-danger-bg = #4d2b2e -$note-dark-modern-danger-text = #d98b8e -$note-dark-modern-danger-hover = #e59fa2 \ No newline at end of file diff --git a/themes/butterfly/source/img/404.jpg b/themes/butterfly/source/img/404.jpg deleted file mode 100644 index 4bab3c3..0000000 Binary files a/themes/butterfly/source/img/404.jpg and /dev/null differ diff --git a/themes/butterfly/source/img/butterfly-icon.png b/themes/butterfly/source/img/butterfly-icon.png deleted file mode 100644 index 3992d77..0000000 Binary files a/themes/butterfly/source/img/butterfly-icon.png and /dev/null differ diff --git a/themes/butterfly/source/img/error-page.png b/themes/butterfly/source/img/error-page.png deleted file mode 100644 index 9d1de96..0000000 Binary files a/themes/butterfly/source/img/error-page.png and /dev/null differ diff --git a/themes/butterfly/source/img/favicon.ico b/themes/butterfly/source/img/favicon.ico deleted file mode 100644 index 56d07f5..0000000 Binary files a/themes/butterfly/source/img/favicon.ico and /dev/null differ diff --git a/themes/butterfly/source/img/friend_404.gif b/themes/butterfly/source/img/friend_404.gif deleted file mode 100644 index 91dd56a..0000000 Binary files a/themes/butterfly/source/img/friend_404.gif and /dev/null differ diff --git a/themes/butterfly/source/js/ai-summary.js b/themes/butterfly/source/js/ai-summary.js deleted file mode 100644 index 7874f11..0000000 --- a/themes/butterfly/source/js/ai-summary.js +++ /dev/null @@ -1,65 +0,0 @@ -// 打字机效果 -function typeTextMachineStyle(text, targetSelector, options = {}) { - const { - delay = 50, - startDelay = 2000, - onComplete = null, - clearBefore = true, - eraseBefore = true, // 新增:是否以打字机方式清除原文本 - eraseDelay = 30, // 新增:删除每个字符的间隔 - } = options; - - const el = document.querySelector(targetSelector); - if (!el || typeof text !== "string") return; - - setTimeout(() => { - const startTyping = () => { - let index = 0; - function renderChar() { - if (index <= text.length) { - el.textContent = text.slice(0, index++); - setTimeout(renderChar, delay); - } else { - onComplete && onComplete(el); - } - } - renderChar(); - }; - - if (clearBefore) { - if (eraseBefore && el.textContent.length > 0) { - let currentText = el.textContent; - let eraseIndex = currentText.length; - - function eraseChar() { - if (eraseIndex > 0) { - el.textContent = currentText.slice(0, --eraseIndex); - setTimeout(eraseChar, eraseDelay); - } else { - startTyping(); // 删除完毕后开始打字 - } - } - - eraseChar(); - } else { - el.textContent = ""; - startTyping(); - } - } else { - startTyping(); - } - }, startDelay); -} - -function renderAISummary() { - const summaryEl = document.querySelector('.ai-summary .ai-explanation'); - if (!summaryEl) return; - - const summaryText = summaryEl.getAttribute('data-summary'); - if (summaryText) { - typeTextMachineStyle(summaryText, ".ai-summary .ai-explanation"); // 如果需要切换,在这里调用另一个函数即可 - } -} - -document.addEventListener('pjax:complete', renderAISummary); -document.addEventListener('DOMContentLoaded', renderAISummary); diff --git a/themes/butterfly/source/js/chart.js b/themes/butterfly/source/js/chart.js deleted file mode 100644 index c693704..0000000 --- a/themes/butterfly/source/js/chart.js +++ /dev/null @@ -1,397 +0,0 @@ -const cheerio = require('cheerio') -const moment = require('moment') - -hexo.extend.filter.register('after_render:html', function (locals) { - const $ = cheerio.load(locals) - const post = $('#posts-chart') - const tag = $('#tags-chart') - const category = $('#categories-chart') - const htmlEncode = false - - if (post.length > 0 || tag.length > 0 || category.length > 0) { - if (post.length > 0 && $('#postsChart').length === 0) { - if (post.attr('data-encode') === 'true') htmlEncode = true - post.after(postsChart(post.attr('data-start'))) - } - if (tag.length > 0 && $('#tagsChart').length === 0) { - if (tag.attr('data-encode') === 'true') htmlEncode = true - tag.after(tagsChart(tag.attr('data-length'))) - } - if (category.length > 0 && $('#categoriesChart').length === 0) { - if (category.attr('data-encode') === 'true') htmlEncode = true - category.after(categoriesChart(category.attr('data-parent'))) - } - - if (htmlEncode) { - return $.root().html().replace(/&#/g, '&#') - } else { - return $.root().html() - } - } else { - return locals - } -}, 15) - -function postsChart (startMonth) { - const startDate = moment(startMonth || '2020-01') - const endDate = moment() - - const monthMap = new Map() - const dayTime = 3600 * 24 * 1000 - for (let time = startDate; time <= endDate; time += dayTime) { - const month = moment(time).format('YYYY-MM') - if (!monthMap.has(month)) { - monthMap.set(month, 0) - } - } - hexo.locals.get('posts').forEach(function (post) { - const month = post.date.format('YYYY-MM') - if (monthMap.has(month)) { - monthMap.set(month, monthMap.get(month) + 1) - } - }) - const monthArr = JSON.stringify([...monthMap.keys()]) - const monthValueArr = JSON.stringify([...monthMap.values()]) - - return ` - ` -} - -function tagsChart (len) { - const tagArr = [] - hexo.locals.get('tags').map(function (tag) { - tagArr.push({ name: tag.name, value: tag.length, path: tag.path }) - }) - tagArr.sort((a, b) => { return b.value - a.value }) - - const dataLength = Math.min(tagArr.length, len) || tagArr.length - const tagNameArr = [] - for (let i = 0; i < dataLength; i++) { - tagNameArr.push(tagArr[i].name) - } - const tagNameArrJson = JSON.stringify(tagNameArr) - const tagArrJson = JSON.stringify(tagArr) - - return ` - ` -} - -function categoriesChart (dataParent) { - const categoryArr = [] - let categoryParentFlag = false - hexo.locals.get('categories').map(function (category) { - if (category.parent) categoryParentFlag = true - categoryArr.push({ - name: category.name, - value: category.length, - path: category.path, - id: category._id, - parentId: category.parent || '0' - }) - }) - categoryParentFlag = categoryParentFlag && dataParent === 'true' - categoryArr.sort((a, b) => { return b.value - a.value }) - function translateListToTree (data, parent) { - let tree = [] - let temp - data.forEach((item, index) => { - if (data[index].parentId == parent) { - let obj = data[index]; - temp = translateListToTree(data, data[index].id); - if (temp.length > 0) { - obj.children = temp - } - if (tree.indexOf()) - tree.push(obj) - } - }) - return tree - } - const categoryNameJson = JSON.stringify(categoryArr.map(function (category) { return category.name })) - const categoryArrJson = JSON.stringify(categoryArr) - const categoryArrParentJson = JSON.stringify(translateListToTree(categoryArr, '0')) - - return ` - ` -} diff --git a/themes/butterfly/source/js/footer.js b/themes/butterfly/source/js/footer.js deleted file mode 100644 index 4282dfe..0000000 --- a/themes/butterfly/source/js/footer.js +++ /dev/null @@ -1,66 +0,0 @@ - -//版权图标动态显示 -document.addEventListener('DOMContentLoaded', function() { - const currentYear = new Date().getFullYear(); - const copyrightElement = document.querySelector('.copyright'); - if (copyrightElement) { - copyrightElement.innerHTML = `©2025 - ${currentYear} By Bi`; - } -}); - -// 运行时间动态显示 -function showDateTime() { - const timeDisplay = document.getElementById('span_dt_dt'); - if (!timeDisplay) return; - - const startTime = new Date("2025-07-05T15:41:23"); - const now = new Date(); - const elapsedMilliseconds = now - startTime; - const seconds = Math.floor(elapsedMilliseconds / 1000); - - const oneYearInSeconds = 365 * 24 * 60 * 60; - if (seconds < oneYearInSeconds) { - const days = Math.floor(seconds / (24 * 60 * 60)); - const remainingSecondsAfterDays = seconds % (24 * 60 * 60); - const hours = Math.floor(remainingSecondsAfterDays / (60 * 60)); - const remainingSecondsAfterHours = remainingSecondsAfterDays % (60 * 60); - const minutes = Math.floor(remainingSecondsAfterHours / 60); - const sec = remainingSecondsAfterHours % 60; - - timeDisplay.innerHTML = ` - ${days} 天 - ${hours} 时 - ${minutes} 分 - ${sec} 秒 - `; - } else { - const years = Math.floor(seconds / oneYearInSeconds); - const remainingSecondsAfterYears = seconds % oneYearInSeconds; - const days = Math.floor(remainingSecondsAfterYears / (24 * 60 * 60)); - const remainingSecondsAfterDays = remainingSecondsAfterYears % (24 * 60 * 60); - const hours = Math.floor(remainingSecondsAfterDays / (60 * 60)); - const remainingSecondsAfterHours = remainingSecondsAfterDays % (60 * 60); - const minutes = Math.floor(remainingSecondsAfterHours / 60); - const sec = remainingSecondsAfterHours % 60; - - timeDisplay.innerHTML = ` - ${years} 年 - ${days} 天 - ${hours} 时 - ${minutes} 分 - ${sec} 秒 - `; - } - - setTimeout(showDateTime, 1000); -} - -document.addEventListener('DOMContentLoaded', () => { - const frameworkInfo = document.querySelector('.framework-info'); - if (frameworkInfo) { - frameworkInfo.innerHTML = '本站已运行'; - } - showDateTime(); -}); - - diff --git a/themes/butterfly/source/js/main.js b/themes/butterfly/source/js/main.js deleted file mode 100644 index ac503db..0000000 --- a/themes/butterfly/source/js/main.js +++ /dev/null @@ -1,1016 +0,0 @@ -document.addEventListener('DOMContentLoaded', () => { - let headerContentWidth, $nav - let mobileSidebarOpen = false - - const adjustMenu = init => { - const getAllWidth = ele => Array.from(ele).reduce((width, i) => width + i.offsetWidth, 0) - - if (init) { - const blogInfoWidth = getAllWidth(document.querySelector('#blog-info > a').children) - const menusWidth = getAllWidth(document.getElementById('menus').children) - headerContentWidth = blogInfoWidth + menusWidth - $nav = document.getElementById('nav') - } - - const hideMenuIndex = window.innerWidth <= 768 || headerContentWidth > $nav.offsetWidth - 120 - $nav.classList.toggle('hide-menu', hideMenuIndex) - } - - // 初始化header - const initAdjust = () => { - adjustMenu(true) - $nav.classList.add('show') - } - - // sidebar menus - const sidebarFn = { - open: () => { - btf.overflowPaddingR.add() - btf.animateIn(document.getElementById('menu-mask'), 'to_show 0.5s') - document.getElementById('sidebar-menus').classList.add('open') - mobileSidebarOpen = true - }, - close: () => { - btf.overflowPaddingR.remove() - btf.animateOut(document.getElementById('menu-mask'), 'to_hide 0.5s') - document.getElementById('sidebar-menus').classList.remove('open') - mobileSidebarOpen = false - } - } - - const syncAsideToSidebar = () => { - const asideContent = document.getElementById('aside-content') - const desktopAsideContainer = document.getElementById('content-inner') - const mobileAsideContainer = document.getElementById('sidebar-aside-wrapper') - - if (!(asideContent && desktopAsideContainer && mobileAsideContainer)) { - document.documentElement.classList.remove('mobile-aside-in-sidebar') - return - } - - const shouldMoveAside = window.innerWidth <= 900 - const targetContainer = shouldMoveAside ? mobileAsideContainer : desktopAsideContainer - - if (asideContent.parentNode !== targetContainer) { - targetContainer.appendChild(asideContent) - } - - document.documentElement.classList.toggle('mobile-aside-in-sidebar', shouldMoveAside) - } - - /** - * 首頁top_img底下的箭頭 - */ - const scrollDownInIndex = () => { - const handleScrollToDest = () => { - btf.scrollToDest(document.getElementById('content-inner').offsetTop, 300) - } - - const $scrollDownEle = document.getElementById('scroll-down') - $scrollDownEle && btf.addEventListenerPjax($scrollDownEle, 'click', handleScrollToDest) - } - - /** - * 代碼 - * 只適用於Hexo默認的代碼渲染 - */ - const addHighlightTool = () => { - const highLight = GLOBAL_CONFIG.highlight - if (!highLight) return - - const { highlightCopy, highlightLang, highlightHeightLimit, highlightFullpage, highlightMacStyle, plugin } = highLight - const isHighlightShrink = GLOBAL_CONFIG_SITE.isHighlightShrink - const isShowTool = highlightCopy || highlightLang || isHighlightShrink !== undefined || highlightFullpage || highlightMacStyle - const isNotHighlightJs = plugin !== 'highlight.js' - const isPrismjs = plugin === 'prismjs' - const $figureHighlight = isNotHighlightJs - ? Array.from(document.querySelectorAll('code[class*="language-"]')).map(code => code.parentElement) - : document.querySelectorAll('figure.highlight') - - if (!((isShowTool || highlightHeightLimit) && $figureHighlight.length)) return - - const highlightShrinkClass = isHighlightShrink === true ? 'closed' : '' - const highlightShrinkEle = isHighlightShrink !== undefined ? '' : '' - const highlightCopyEle = highlightCopy ? '' : '' - const highlightMacStyleEle = '
    ' - const highlightFullpageEle = highlightFullpage ? '' : '' - - const alertInfo = (ele, text) => { - if (GLOBAL_CONFIG.Snackbar !== undefined) { - btf.snackbarShow(text) - } else { - const newEle = document.createElement('div') - newEle.className = 'copy-notice' - newEle.textContent = text - document.body.appendChild(newEle) - - const buttonRect = ele.getBoundingClientRect() - const scrollTop = window.pageYOffset || document.documentElement.scrollTop - const scrollLeft = window.pageXOffset || document.documentElement.scrollLeft - - // X-axis boundary check - const halfWidth = newEle.offsetWidth / 2 - const centerLeft = buttonRect.left + scrollLeft + buttonRect.width / 2 - const finalLeft = Math.max(halfWidth + 10, Math.min(window.innerWidth - halfWidth - 10, centerLeft)) - - // Show tooltip below button if too close to top - const normalTop = buttonRect.top + scrollTop - 40 - const shouldShowBelow = buttonRect.top < 60 || normalTop < 10 - - const topValue = shouldShowBelow ? buttonRect.top + scrollTop + buttonRect.height + 10 : normalTop - - newEle.style.cssText = ` - top: ${topValue + 10}px; - left: ${finalLeft}px; - transform: translateX(-50%); - opacity: 0; - transition: opacity 0.3s ease, top 0.3s ease; - ` - - requestAnimationFrame(() => { - newEle.style.opacity = '1' - newEle.style.top = `${topValue}px` - }) - - setTimeout(() => { - newEle.style.opacity = '0' - newEle.style.top = `${topValue + 10}px` - setTimeout(() => { - newEle?.remove() - }, 300) - }, 800) - } - } - - const copy = async (text, ctx) => { - try { - await navigator.clipboard.writeText(text) - alertInfo(ctx, GLOBAL_CONFIG.copy.success) - } catch (err) { - console.error('Failed to copy: ', err) - alertInfo(ctx, GLOBAL_CONFIG.copy.noSupport) - } - } - - // click events - const highlightCopyFn = (ele, clickEle) => { - const $buttonParent = ele.parentNode - $buttonParent.classList.add('copy-true') - const preCodeSelector = isNotHighlightJs ? 'pre code' : 'table .code pre' - const codeElement = $buttonParent.querySelector(preCodeSelector) - if (!codeElement) return - copy(codeElement.innerText, clickEle) - $buttonParent.classList.remove('copy-true') - } - - const highlightShrinkFn = ele => ele.classList.toggle('closed') - - const codeFullpage = (item, clickEle) => { - const wrapEle = item.closest('figure.highlight') - const isFullpage = wrapEle.classList.toggle('code-fullpage') - - document.body.style.overflow = isFullpage ? 'hidden' : '' - clickEle.classList.toggle('fa-down-left-and-up-right-to-center', isFullpage) - clickEle.classList.toggle('fa-up-right-and-down-left-from-center', !isFullpage) - } - - const highlightToolsFn = e => { - const $target = e.target.classList - const currentElement = e.currentTarget - if ($target.contains('expand')) highlightShrinkFn(currentElement) - else if ($target.contains('copy-button')) highlightCopyFn(currentElement, e.target) - else if ($target.contains('fullpage-button')) codeFullpage(currentElement, e.target) - } - - const expandCode = e => e.currentTarget.classList.toggle('expand-done') - - // 獲取隱藏狀態下元素的真實高度 - const getActualHeight = item => { - if (item.offsetHeight > 0) return item.offsetHeight - const hiddenElements = new Map() - - const fix = () => { - let current = item - while (current !== document.body && current != null) { - if (window.getComputedStyle(current).display === 'none') { - hiddenElements.set(current, current.getAttribute('style') || '') - } - current = current.parentNode - } - - const style = 'visibility: hidden !important; display: block !important;' - hiddenElements.forEach((originalStyle, elem) => { - elem.setAttribute('style', originalStyle ? originalStyle + ';' + style : style) - }) - } - - const restore = () => { - hiddenElements.forEach((originalStyle, elem) => { - if (originalStyle === '') elem.removeAttribute('style') - else elem.setAttribute('style', originalStyle) - }) - } - - fix() - const height = item.offsetHeight - restore() - return height - } - - const createEle = (lang, item) => { - const fragment = document.createDocumentFragment() - - if (isShowTool) { - const hlTools = document.createElement('div') - hlTools.className = `highlight-tools ${highlightShrinkClass}` - hlTools.innerHTML = highlightMacStyleEle + highlightShrinkEle + lang + highlightCopyEle + highlightFullpageEle - btf.addEventListenerPjax(hlTools, 'click', highlightToolsFn) - fragment.appendChild(hlTools) - } - - if (highlightHeightLimit && getActualHeight(item) > highlightHeightLimit + 30) { - const ele = document.createElement('div') - ele.className = 'code-expand-btn' - ele.innerHTML = '' - btf.addEventListenerPjax(ele, 'click', expandCode) - fragment.appendChild(ele) - } - - isNotHighlightJs ? item.parentNode.insertBefore(fragment, item) : item.insertBefore(fragment, item.firstChild) - } - - $figureHighlight.forEach(item => { - let langName = '' - if (isNotHighlightJs) { - const newClassName = isPrismjs ? 'prismjs' : 'default' - btf.wrap(item, 'figure', { class: `highlight ${newClassName}` }) - } - - if (!highlightLang) { - createEle('', item) - return - } - - if (isNotHighlightJs) { - langName = isPrismjs ? item.getAttribute('data-language') || 'Code' : item.querySelector('code').getAttribute('class').replace('language-', '') - } else { - langName = item.getAttribute('class').split(' ')[1] - if (langName === 'plain' || langName === undefined) langName = 'Code' - } - createEle(`
    ${langName}
    `, item) - }) - } - - /** - * PhotoFigcaption - */ - const addPhotoFigcaption = () => { - if (!GLOBAL_CONFIG.isPhotoFigcaption) return - document.querySelectorAll('#article-container img').forEach(item => { - const altValue = item.title || item.alt - if (!altValue) return - const ele = document.createElement('div') - ele.className = 'img-alt text-center' - ele.textContent = altValue - item.insertAdjacentElement('afterend', ele) - }) - } - - /** - * Lightbox - */ - const runLightbox = () => { - btf.loadLightbox(document.querySelectorAll('#article-container img:not(.no-lightbox)')) - } - - /** - * justified-gallery 圖庫排版 - */ - - const fetchUrl = async url => { - try { - const response = await fetch(url) - return await response.json() - } catch (error) { - console.error('Failed to fetch URL:', error) - return [] - } - } - - const runJustifiedGallery = (container, data, config) => { - const { isButton, limit, firstLimit, tabs } = config - - const dataLength = data.length - const maxGroupKey = Math.ceil((dataLength - firstLimit) / limit + 1) - - // Gallery configuration - const igConfig = { - gap: 5, - isConstantSize: true, - sizeRange: [150, 600], - // useResizeObserver: true, - // observeChildren: true, - useTransform: true - // useRecycle: false - } - - const ig = new InfiniteGrid.JustifiedInfiniteGrid(container, igConfig) - let isLayoutHidden = false - - // Utility functions - const sanitizeString = str => (str && str.replace(/"/g, '"')) || '' - - const createImageItem = item => { - const alt = item.alt ? `alt="${sanitizeString(item.alt)}"` : '' - const title = item.title ? `title="${sanitizeString(item.title)}"` : '' - return `
    - -
    ` - } - - const getItems = (nextGroupKey, count, isFirst = false) => { - const startIndex = isFirst ? (nextGroupKey - 1) * count : (nextGroupKey - 2) * count + firstLimit - return data.slice(startIndex, startIndex + count).map(createImageItem) - } - - // Load more button - const addLoadMoreButton = container => { - const button = document.createElement('button') - button.innerHTML = `${GLOBAL_CONFIG.infinitegrid.buttonText}` - - button.addEventListener('click', () => { - button.remove() - btf.setLoading.add(container) - appendItems(ig.getGroups().length + 1, limit) - }, { once: true }) - - container.insertAdjacentElement('afterend', button) - } - - const appendItems = (nextGroupKey, count, isFirst) => { - ig.append(getItems(nextGroupKey, count, isFirst), nextGroupKey) - } - - // Event handlers - const handleRenderComplete = e => { - if (tabs) { - const parentNode = container.parentNode - if (isLayoutHidden) { - parentNode.style.visibility = 'visible' - } - if (container.offsetHeight === 0) { - parentNode.style.visibility = 'hidden' - isLayoutHidden = true - } - } - - const { updated, isResize, mounted } = e - if (!updated.length || !mounted.length || isResize) return - - btf.loadLightbox(container.querySelectorAll('img:not(.medium-zoom-image)')) - - if (ig.getGroups().length === maxGroupKey) { - btf.setLoading.remove(container) - !tabs && ig.off('renderComplete', handleRenderComplete) - return - } - - if (isButton) { - btf.setLoading.remove(container) - addLoadMoreButton(container) - } - } - - const handleRequestAppend = btf.debounce(e => { - const nextGroupKey = (+e.groupKey || 0) + 1 - - if (nextGroupKey === 1) appendItems(nextGroupKey, firstLimit, true) - else appendItems(nextGroupKey, limit) - - if (nextGroupKey === maxGroupKey) ig.off('requestAppend', handleRequestAppend) - }, 300) - - btf.setLoading.add(container) - ig.on('renderComplete', handleRenderComplete) - - if (isButton) { - appendItems(1, firstLimit, true) - } else { - ig.on('requestAppend', handleRequestAppend) - ig.renderItems() - } - - btf.addGlobalFn('pjaxSendOnce', () => ig.destroy()) - } - - const addJustifiedGallery = async (elements, tabs = false) => { - if (!elements.length) return - - const initGallery = async () => { - for (const element of elements) { - if (btf.isHidden(element) || element.classList.contains('loaded')) continue - - const config = { - isButton: element.getAttribute('data-button') === 'true', - limit: parseInt(element.getAttribute('data-limit'), 10), - firstLimit: parseInt(element.getAttribute('data-first'), 10), - tabs - } - - const container = element.firstElementChild - const content = container.textContent - container.textContent = '' - element.classList.add('loaded') - - try { - const data = element.getAttribute('data-type') === 'url' ? await fetchUrl(content) : JSON.parse(content) - runJustifiedGallery(container, data, config) - } catch (error) { - console.error('Gallery data parsing failed:', error) - } - } - } - - if (typeof InfiniteGrid === 'function') { - await initGallery() - } else { - await btf.getScript(GLOBAL_CONFIG.infinitegrid.js) - await initGallery() - } - } - - /** - * rightside scroll percent - */ - const rightsideScrollPercent = currentTop => { - const scrollPercent = btf.getScrollPercent(currentTop, document.body) - const goUpElement = document.getElementById('go-up') - - if (scrollPercent < 95) { - goUpElement.classList.add('show-percent') - goUpElement.querySelector('.scroll-percent').textContent = scrollPercent - } else { - goUpElement.classList.remove('show-percent') - } - } - - /** - * 滾動處理 - */ - const scrollFn = () => { - const $rightside = document.getElementById('rightside') - const innerHeight = window.innerHeight + 56 - let initTop = 0 - const $header = document.getElementById('page-header') - const isChatBtn = typeof chatBtn !== 'undefined' - const isShowPercent = GLOBAL_CONFIG.percent.rightside - - // 檢查文檔高度是否小於視窗高度 - const checkDocumentHeight = () => { - if (document.body.scrollHeight <= innerHeight) { - $rightside.classList.add('rightside-show') - return true - } - return false - } - - // 如果文檔高度小於視窗高度,直接返回 - if (checkDocumentHeight()) return - - // find the scroll direction - const scrollDirection = currentTop => { - const result = currentTop > initTop // true is down & false is up - initTop = currentTop - return result - } - - let flag = '' - const scrollTask = btf.throttle(() => { - const currentTop = window.scrollY || document.documentElement.scrollTop - const isDown = scrollDirection(currentTop) - if (currentTop > 56) { - if (flag === '') { - $header.classList.add('nav-fixed') - $rightside.classList.add('rightside-show') - } - - if (isDown) { - if (flag !== 'down') { - $header.classList.remove('nav-visible') - isChatBtn && window.chatBtn.hide() - flag = 'down' - } - } else { - if (flag !== 'up') { - $header.classList.add('nav-visible') - isChatBtn && window.chatBtn.show() - flag = 'up' - } - } - } else { - flag = '' - if (currentTop === 0) { - $header.classList.remove('nav-fixed', 'nav-visible') - } - $rightside.classList.remove('rightside-show') - } - - isShowPercent && rightsideScrollPercent(currentTop) - checkDocumentHeight() - }, 300) - - btf.addEventListenerPjax(window, 'scroll', scrollTask, { passive: true }) - } - - /** - * toc,anchor - */ - const scrollFnToDo = () => { - const isToc = GLOBAL_CONFIG_SITE.isToc - const isAnchor = GLOBAL_CONFIG.isAnchor - const $article = document.getElementById('article-container') - - if (!($article && (isToc || isAnchor))) return - - let $tocLink, $cardToc, autoScrollToc, $tocPercentage, isExpand - - if (isToc) { - const $cardTocLayout = document.getElementById('card-toc') - $cardToc = $cardTocLayout.querySelector('.toc-content') - $tocLink = $cardToc.querySelectorAll('.toc-link') - $tocPercentage = $cardTocLayout.querySelector('.toc-percentage') - isExpand = $cardToc.classList.contains('is-expand') - - // toc元素點擊 - const tocItemClickFn = e => { - const target = e.target.closest('.toc-link') - if (!target) return - - e.preventDefault() - btf.scrollToDest(btf.getEleTop(document.getElementById(decodeURI(target.getAttribute('href')).replace('#', ''))), 300) - if (window.innerWidth < 900) { - $cardTocLayout.classList.remove('open') - } - } - - btf.addEventListenerPjax($cardToc, 'click', tocItemClickFn) - - autoScrollToc = item => { - const sidebarHeight = $cardToc.clientHeight - const itemOffsetTop = item.offsetTop - const itemHeight = item.clientHeight - const scrollTop = $cardToc.scrollTop - const offset = itemOffsetTop - scrollTop - const middlePosition = (sidebarHeight - itemHeight) / 2 - - if (offset !== middlePosition) { - $cardToc.scrollTop = scrollTop + (offset - middlePosition) - } - } - - // 處理 hexo-blog-encrypt 事件 - $cardToc.style.display = 'block' - } - - // find head position & add active class - const $articleList = $article.querySelectorAll('h1,h2,h3,h4,h5,h6') - let detectItem = '' - - // Optimization: Cache header positions - let headerList = [] - const updateHeaderPositions = () => { - headerList = Array.from($articleList).map(ele => ({ - ele, - top: btf.getEleTop(ele), - id: ele.id - })) - } - - updateHeaderPositions() - btf.addEventListenerPjax(window, 'resize', btf.throttle(updateHeaderPositions, 200)) - - const findHeadPosition = top => { - if (top === 0) return false - - let currentId = '' - let currentIndex = '' - - for (let i = 0; i < headerList.length; i++) { - const item = headerList[i] - if (top > item.top - 80) { - currentId = item.id ? '#' + encodeURI(item.id) : '' - currentIndex = i - } else { - break - } - } - - if (detectItem === currentIndex) return - - if (isAnchor) btf.updateAnchor(currentId) - - detectItem = currentIndex - - if (isToc) { - $cardToc.querySelectorAll('.active').forEach(i => i.classList.remove('active')) - - if (currentId) { - const currentActive = $tocLink[currentIndex] - currentActive.classList.add('active') - - setTimeout(() => autoScrollToc(currentActive), 0) - - if (!isExpand) { - let parent = currentActive.parentNode - while (!parent.matches('.toc')) { - if (parent.matches('li')) parent.classList.add('active') - parent = parent.parentNode - } - } - } - } - } - - // main of scroll - const tocScrollFn = btf.throttle(() => { - const currentTop = window.scrollY || document.documentElement.scrollTop - if (isToc && GLOBAL_CONFIG.percent.toc) { - $tocPercentage.textContent = btf.getScrollPercent(currentTop, $article) - } - findHeadPosition(currentTop) - }, 100) - - btf.addEventListenerPjax(window, 'scroll', tocScrollFn, { passive: true }) - } - - const handleThemeChange = mode => { - const globalFn = window.globalFn || {} - const themeChange = globalFn.themeChange || {} - if (!themeChange) { - return - } - - Object.keys(themeChange).forEach(key => { - const themeChangeFn = themeChange[key] - if (['disqus', 'disqusjs'].includes(key)) { - setTimeout(() => themeChangeFn(mode), 300) - } else { - themeChangeFn(mode) - } - }) - } - - /** - * Rightside - */ - const rightSideFn = { - readmode: () => { // read mode - const $body = document.body - const newEle = document.createElement('button') - - const exitReadMode = () => { - $body.classList.remove('read-mode') - newEle.remove() - newEle.removeEventListener('click', exitReadMode) - } - - $body.classList.add('read-mode') - newEle.type = 'button' - newEle.className = 'exit-readmode' - newEle.innerHTML = '' - newEle.addEventListener('click', exitReadMode) - $body.appendChild(newEle) - }, - darkmode: () => { // switch between light and dark mode - const willChangeMode = document.documentElement.getAttribute('data-theme') === 'dark' ? 'light' : 'dark' - if (willChangeMode === 'dark') { - btf.activateDarkMode() - GLOBAL_CONFIG.Snackbar !== undefined && btf.snackbarShow(GLOBAL_CONFIG.Snackbar.day_to_night) - } else { - btf.activateLightMode() - GLOBAL_CONFIG.Snackbar !== undefined && btf.snackbarShow(GLOBAL_CONFIG.Snackbar.night_to_day) - } - btf.saveToLocal.set('theme', willChangeMode, 2) - handleThemeChange(willChangeMode) - }, - 'rightside-config': item => { // Show or hide rightside-hide-btn - const hideLayout = item.firstElementChild - if (hideLayout.classList.contains('show')) { - hideLayout.classList.add('status') - setTimeout(() => { - hideLayout.classList.remove('status') - }, 300) - } - - hideLayout.classList.toggle('show') - }, - 'go-up': () => { // Back to top - btf.scrollToDest(0, 500) - }, - 'hide-aside-btn': () => { // Hide aside - const $htmlDom = document.documentElement.classList - const saveStatus = $htmlDom.contains('hide-aside') ? 'show' : 'hide' - btf.saveToLocal.set('aside-status', saveStatus, 2) - $htmlDom.toggle('hide-aside') - }, - 'mobile-toc-button': (p, item) => { // Show mobile toc - const tocEle = document.getElementById('card-toc') - tocEle.style.transition = 'transform 0.3s ease-in-out' - - const tocEleHeight = tocEle.clientHeight - const btData = item.getBoundingClientRect() - - const tocEleBottom = window.innerHeight - btData.bottom - 30 - if (tocEleHeight > tocEleBottom) { - tocEle.style.transformOrigin = `right ${tocEleHeight - tocEleBottom - btData.height / 2}px` - } - - tocEle.classList.toggle('open') - tocEle.addEventListener('transitionend', () => { - tocEle.style.cssText = '' - }, { once: true }) - }, - 'chat-btn': () => { // Show chat - window.chatBtnFn() - }, - translateLink: () => { // switch between traditional and simplified chinese - window.translateFn.translatePage() - } - } - - document.getElementById('rightside').addEventListener('click', e => { - const $target = e.target.closest('[id]') - if ($target && rightSideFn[$target.id]) { - rightSideFn[$target.id](e.currentTarget, $target) - } - }) - - /** - * menu - * 側邊欄sub-menu 展開/收縮 - */ - const clickFnOfSubMenu = () => { - const handleClickOfSubMenu = e => { - const target = e.target.closest('.site-page.group') - if (!target) return - target.classList.toggle('hide') - } - - const menusItems = document.querySelector('#sidebar-menus .menus_items') - menusItems && menusItems.addEventListener('click', handleClickOfSubMenu) - } - - /** - * 手机端目录点击 - */ - const openMobileMenu = () => { - const toggleMenu = document.getElementById('toggle-menu') - if (!toggleMenu) return - btf.addEventListenerPjax(toggleMenu, 'click', () => { sidebarFn.open() }) - } - - /** - * 複製時加上版權信息 - */ - const addCopyright = () => { - const { limitCount, languages } = GLOBAL_CONFIG.copyright - - const handleCopy = e => { - e.preventDefault() - const copyFont = window.getSelection(0).toString() - let textFont = copyFont - if (copyFont.length > limitCount) { - textFont = `${copyFont}\n\n\n${languages.author}\n${languages.link}${window.location.href}\n${languages.source}\n${languages.info}` - } - if (e.clipboardData) { - return e.clipboardData.setData('text', textFont) - } else { - return window.clipboardData.setData('text', textFont) - } - } - - document.body.addEventListener('copy', handleCopy) - } - - /** - * 網頁運行時間 - */ - const addRuntime = () => { - const $runtimeCount = document.getElementById('runtimeshow') - if ($runtimeCount) { - const publishDate = $runtimeCount.getAttribute('data-publishDate') - $runtimeCount.textContent = `${btf.diffDate(publishDate)} ${GLOBAL_CONFIG.runtime}` - } - } - - /** - * 最後一次更新時間 - */ - const addLastPushDate = () => { - const $lastPushDateItem = document.getElementById('last-push-date') - if ($lastPushDateItem) { - const lastPushDate = $lastPushDateItem.getAttribute('data-lastPushDate') - $lastPushDateItem.textContent = btf.diffDate(lastPushDate, true) - } - } - - /** - * table overflow - */ - const addTableWrap = () => { - const $table = document.querySelectorAll('#article-container table') - if (!$table.length) return - - $table.forEach(item => { - if (!item.closest('.highlight')) { - btf.wrap(item, 'div', { class: 'table-wrap' }) - } - }) - } - - /** - * tag-hide - */ - const clickFnOfTagHide = () => { - const hideButtons = document.querySelectorAll('#article-container .hide-button') - if (!hideButtons.length) return - hideButtons.forEach(item => item.addEventListener('click', e => { - const currentTarget = e.currentTarget - currentTarget.classList.add('open') - addJustifiedGallery(currentTarget.nextElementSibling.querySelectorAll('.gallery-container')) - }, { once: true })) - } - - const tabsFn = () => { - const navTabsElements = document.querySelectorAll('#article-container .tabs') - if (!navTabsElements.length) return - - const setActiveClass = (elements, activeIndex) => { - elements.forEach((el, index) => { - el.classList.toggle('active', index === activeIndex) - }) - } - - const handleNavClick = e => { - const target = e.target.closest('button') - if (!target || target.classList.contains('active')) return - - const navItems = [...e.currentTarget.children] - const tabContents = [...e.currentTarget.nextElementSibling.children] - const indexOfButton = navItems.indexOf(target) - setActiveClass(navItems, indexOfButton) - e.currentTarget.classList.remove('no-default') - setActiveClass(tabContents, indexOfButton) - addJustifiedGallery(tabContents[indexOfButton].querySelectorAll('.gallery-container'), true) - } - - const handleToTopClick = tabElement => e => { - if (e.target.closest('button')) { - btf.scrollToDest(btf.getEleTop(tabElement), 300) - } - } - - navTabsElements.forEach(tabElement => { - btf.addEventListenerPjax(tabElement.firstElementChild, 'click', handleNavClick) - btf.addEventListenerPjax(tabElement.lastElementChild, 'click', handleToTopClick(tabElement)) - }) - } - - const toggleCardCategory = () => { - const cardCategory = document.querySelector('#aside-cat-list.expandBtn') - if (!cardCategory) return - - const handleToggleBtn = e => { - const target = e.target - if (target.nodeName === 'I') { - e.preventDefault() - target.parentNode.classList.toggle('expand') - } - } - btf.addEventListenerPjax(cardCategory, 'click', handleToggleBtn, true) - } - - const addPostOutdateNotice = () => { - const ele = document.getElementById('post-outdate-notice') - if (!ele) return - - const { limitDay, messagePrev, messageNext, postUpdate } = JSON.parse(ele.getAttribute('data')) - const diffDay = btf.diffDate(postUpdate) - if (diffDay >= limitDay) { - ele.textContent = `${messagePrev} ${diffDay} ${messageNext}` - ele.hidden = false - } - } - - const lazyloadImg = () => { - window.lazyLoadInstance = new LazyLoad({ - elements_selector: 'img', - threshold: 0, - data_src: 'lazy-src' - }) - - btf.addGlobalFn('pjaxComplete', () => { - window.lazyLoadInstance.update() - }, 'lazyload') - } - - const relativeDate = selector => { - selector.forEach(item => { - item.textContent = btf.diffDate(item.getAttribute('datetime'), true) - item.style.display = 'inline' - }) - } - - const justifiedIndexPostUI = () => { - const recentPostsElement = document.getElementById('recent-posts') - if (!(recentPostsElement && recentPostsElement.classList.contains('masonry'))) return - - const init = () => { - const masonryItem = new InfiniteGrid.MasonryInfiniteGrid('.recent-post-items', { - gap: { horizontal: 10, vertical: 20 }, - useTransform: true, - useResizeObserver: true - }) - masonryItem.renderItems() - btf.addGlobalFn('pjaxCompleteOnce', () => { masonryItem.destroy() }, 'removeJustifiedIndexPostUI') - } - - typeof InfiniteGrid === 'function' ? init() : btf.getScript(`${GLOBAL_CONFIG.infinitegrid.js}`).then(init) - } - - const unRefreshFn = () => { - window.addEventListener('resize', () => { - adjustMenu(false) - syncAsideToSidebar() - mobileSidebarOpen && btf.isHidden(document.getElementById('toggle-menu')) && sidebarFn.close() - }) - - const menuMask = document.getElementById('menu-mask') - menuMask && menuMask.addEventListener('click', () => { sidebarFn.close() }) - - const sidebarMenus = document.getElementById('sidebar-menus') - sidebarMenus && sidebarMenus.addEventListener('click', e => { - const clickedLink = e.target.closest('a') - if (!clickedLink) return - sidebarFn.close() - }) - - clickFnOfSubMenu() - GLOBAL_CONFIG.islazyloadPlugin && lazyloadImg() - GLOBAL_CONFIG.copyright !== undefined && addCopyright() - - if (GLOBAL_CONFIG.autoDarkmode) { - window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', e => { - if (btf.saveToLocal.get('theme') !== undefined) return - e.matches ? handleThemeChange('dark') : handleThemeChange('light') - }) - } - } - - const forPostFn = () => { - addHighlightTool() - addPhotoFigcaption() - addJustifiedGallery(document.querySelectorAll('#article-container .gallery-container')) - runLightbox() - scrollFnToDo() - addTableWrap() - clickFnOfTagHide() - tabsFn() - } - - const refreshFn = () => { - initAdjust() - syncAsideToSidebar() - justifiedIndexPostUI() - - if (GLOBAL_CONFIG_SITE.pageType === 'post') { - addPostOutdateNotice() - GLOBAL_CONFIG.relativeDate.post && relativeDate(document.querySelectorAll('#post-meta time')) - } else { - GLOBAL_CONFIG.relativeDate.homepage && relativeDate(document.querySelectorAll('#recent-posts time')) - GLOBAL_CONFIG.runtime && addRuntime() - addLastPushDate() - toggleCardCategory() - } - - GLOBAL_CONFIG_SITE.pageType === 'home' && scrollDownInIndex() - scrollFn() - - forPostFn() - GLOBAL_CONFIG_SITE.pageType !== 'shuoshuo' && btf.switchComments(document) - openMobileMenu() - } - - btf.addGlobalFn('pjaxComplete', refreshFn, 'refreshFn') - refreshFn() - unRefreshFn() - - // 處理 hexo-blog-encrypt 事件 - window.addEventListener('hexo-blog-decrypt', e => { - forPostFn() - window.translateFn.translateInitialization() - Object.values(window.globalFn.encrypt).forEach(fn => { - fn() - }) - }) -}) diff --git a/themes/butterfly/source/js/random.js b/themes/butterfly/source/js/random.js deleted file mode 100644 index 6989557..0000000 --- a/themes/butterfly/source/js/random.js +++ /dev/null @@ -1,13 +0,0 @@ -function randomPost() { - fetch('/sitemap.xml').then(res => res.text()).then(str => (new window.DOMParser()).parseFromString(str, "text/xml")).then(data => { - let ls = data.querySelectorAll('url loc'); - let locationHref,locSplit; - do { - locationHref = ls[Math.floor(Math.random() * ls.length)].innerHTML - locSplit = locationHref.split('/')[3] || '' - } while (locSplit !== 'posts'); - //若所有文章都如 https://…….com/posts/2022/07/…… 格式,主域名后字符是 posts,则循环条件改为: - //while (locSplit !== 'posts'); - location.href = locationHref - }) -} \ No newline at end of file diff --git a/themes/butterfly/source/js/search/algolia.js b/themes/butterfly/source/js/search/algolia.js deleted file mode 100644 index ed69d2e..0000000 --- a/themes/butterfly/source/js/search/algolia.js +++ /dev/null @@ -1,562 +0,0 @@ -window.addEventListener('load', () => { - const { algolia } = GLOBAL_CONFIG - const { appId, apiKey, indexName, hitsPerPage = 5, languages } = algolia - - if (!appId || !apiKey || !indexName) { - return console.error('Algolia setting is invalid!') - } - - const $searchMask = document.getElementById('search-mask') - const $searchDialog = document.querySelector('#algolia-search .search-dialog') - - const animateElements = show => { - const action = show ? 'animateIn' : 'animateOut' - const maskAnimation = show ? 'to_show 0.5s' : 'to_hide 0.5s' - const dialogAnimation = show ? 'titleScale 0.5s' : 'search_close .5s' - btf[action]($searchMask, maskAnimation) - btf[action]($searchDialog, dialogAnimation) - } - - const fixSafariHeight = () => { - if (window.innerWidth < 768) { - $searchDialog.style.setProperty('--search-height', `${window.innerHeight}px`) - } - } - - const openSearch = () => { - btf.overflowPaddingR.add() - animateElements(true) - showLoading(false) - - setTimeout(() => { - const searchInput = document.querySelector('#algolia-search-input .ais-SearchBox-input') - if (searchInput) searchInput.focus() - }, 100) - - const handleEscape = event => { - if (event.code === 'Escape') { - closeSearch() - document.removeEventListener('keydown', handleEscape) - } - } - - document.addEventListener('keydown', handleEscape) - fixSafariHeight() - window.addEventListener('resize', fixSafariHeight) - } - - const closeSearch = () => { - btf.overflowPaddingR.remove() - animateElements(false) - window.removeEventListener('resize', fixSafariHeight) - } - - const searchClickFn = () => { - btf.addEventListenerPjax(document.querySelector('#search-button > .search'), 'click', openSearch) - } - - const searchFnOnce = () => { - $searchMask.addEventListener('click', closeSearch) - document.querySelector('#algolia-search .search-close-button').addEventListener('click', closeSearch) - } - - const cutContent = content => { - if (!content) return '' - - let contentStr = '' - if (typeof content === 'string') { - contentStr = content.trim() - } else if (typeof content === 'object') { - if (content.value !== undefined) { - contentStr = String(content.value).trim() - if (!contentStr) return '' - } else if (content.matchedWords || content.matchLevel || content.fullyHighlighted !== undefined) { - return '' - } else { - try { - contentStr = JSON.stringify(content).trim() - if (contentStr === '{}' || contentStr === '[]' || contentStr === '""') { - return '' - } - } catch (e) { - return '' - } - } - } else if (content.toString && typeof content.toString === 'function') { - contentStr = content.toString().trim() - if (contentStr === '[object Object]' || contentStr === '[object Array]') { - return '' - } - } else { - return '' - } - - const firstOccur = contentStr.indexOf('') - let start = firstOccur - 30 - let end = firstOccur + 120 - let pre = '' - let post = '' - - if (start <= 0) { - start = 0 - end = 140 - } else { - pre = '...' - } - - if (end > contentStr.length) { - end = contentStr.length - } else { - post = '...' - } - - // Ensure we don't cut off HTML tags in the middle - let substr = contentStr.substring(start, end) - - // Handle tag completeness - // Check for incomplete opening tags at the beginning - const firstCloseBracket = substr.indexOf('>') - const firstOpenBracket = substr.indexOf('<') - - // If there's a closing bracket but no opening bracket before it, we've cut a tag - if (firstCloseBracket !== -1 && (firstOpenBracket === -1 || firstCloseBracket < firstOpenBracket)) { - substr = substr.substring(firstCloseBracket + 1) - } - - // Check for incomplete closing tags at the end - const lastOpenBracket = substr.lastIndexOf('<') - const lastCloseBracket = substr.lastIndexOf('>') - - // If there's an opening bracket after the last closing bracket, we've cut a tag - if (lastOpenBracket !== -1 && lastOpenBracket > lastCloseBracket) { - substr = substr.substring(0, lastOpenBracket) - } - - // Balance tags in the substring - const tagStack = [] - let balancedStr = '' - let i = 0 - - while (i < substr.length) { - if (substr[i] === '<') { - // Check if it's a closing tag - if (substr[i + 1] === '/') { - const closeTagEnd = substr.indexOf('>', i) - if (closeTagEnd !== -1) { - const closeTagName = substr.substring(i + 2, closeTagEnd) - // Remove matching opening tag from stack - for (let j = tagStack.length - 1; j >= 0; j--) { - if (tagStack[j] === closeTagName) { - tagStack.splice(j, 1) - break - } - } - balancedStr += substr.substring(i, closeTagEnd + 1) - i = closeTagEnd + 1 - continue - } - } else if (substr.substr(i, 2) === '', i) !== -1 && substr.indexOf('/>', i) < substr.indexOf('>', i))) { - const tagEnd = substr.indexOf('>', i) - if (tagEnd !== -1) { - balancedStr += substr.substring(i, tagEnd + 1) - i = tagEnd + 1 - continue - } - } else { - const tagEnd = substr.indexOf('>', i) - if (tagEnd !== -1) { - const tagName = substr.substring(i + 1, (substr.indexOf(' ', i) > -1 && substr.indexOf(' ', i) < tagEnd) - ? substr.indexOf(' ', i) - : tagEnd).split(/\s/)[0] - tagStack.push(tagName) - balancedStr += substr.substring(i, tagEnd + 1) - i = tagEnd + 1 - continue - } - } - } - balancedStr += substr[i] - i++ - } - - // Close any unclosed tags - while (tagStack.length > 0) { - const tagName = tagStack.pop() - balancedStr += `` - } - - // If we removed content from the beginning, add prefix - if (start > 0 || pre) { - const actualFirstOpenBracket = contentStr.indexOf('<', start > 0 ? start - 30 : 0) - const actualFirstMark = contentStr.indexOf('', start > 0 ? start - 30 : 0) - - if (actualFirstOpenBracket !== -1 && - (actualFirstMark === -1 || actualFirstOpenBracket < actualFirstMark)) { - pre = '...' - } - } - - substr = balancedStr - return `${pre}${substr}${post}` - } - - // Helper function to handle Algolia highlight results - const extractHighlightValue = highlightObj => { - if (!highlightObj) return '' - - if (typeof highlightObj === 'string') { - return highlightObj.trim() - } - - if (typeof highlightObj === 'object' && highlightObj.value !== undefined) { - return String(highlightObj.value).trim() - } - - return '' - } - - // Initialize Algolia client - let searchClient - - if (window['algoliasearch/lite'] && typeof window['algoliasearch/lite'].liteClient === 'function') { - searchClient = window['algoliasearch/lite'].liteClient(appId, apiKey) - } else if (typeof window.algoliasearch === 'function') { - searchClient = window.algoliasearch(appId, apiKey) - } else { - return console.error('Algolia search client not found!') - } - - if (!searchClient) { - return console.error('Failed to initialize Algolia search client') - } - - // Search state - let currentQuery = '' - - // Show loading state - const showLoading = show => { - const loadingIndicator = document.getElementById('loading-status') - if (loadingIndicator) { - loadingIndicator.hidden = !show - } - } - - // Cache frequently used elements - const elements = { - get searchInput () { return document.querySelector('#algolia-search-input .ais-SearchBox-input') }, - get hits () { return document.getElementById('algolia-hits') }, - get hitsEmpty () { return document.getElementById('algolia-hits-empty') }, - get hitsList () { return document.querySelector('#algolia-hits .ais-Hits-list') }, - get hitsWrapper () { return document.querySelector('#algolia-hits .ais-Hits') }, - get pagination () { return document.getElementById('algolia-pagination') }, - get paginationList () { return document.querySelector('#algolia-pagination .ais-Pagination-list') }, - get stats () { return document.querySelector('#algolia-info .ais-Stats-text') }, - } - - // Show/hide search results area - const toggleResultsVisibility = hasResults => { - elements.pagination.style.display = hasResults ? '' : 'none' - elements.stats.style.display = hasResults ? '' : 'none' - } - - // Render search results - const renderHits = (hits, query, page = 0) => { - if (hits.length === 0 && query) { - elements.hitsEmpty.textContent = languages.hits_empty.replace(/\$\{query}/, query) - elements.hitsEmpty.style.display = '' - elements.hitsWrapper.style.display = 'none' - elements.stats.style.display = 'none' - return - } - - elements.hitsEmpty.style.display = 'none' - - const hitsHTML = hits.map((hit, index) => { - const itemNumber = page * hitsPerPage + index + 1 - const link = hit.permalink || (GLOBAL_CONFIG.root + hit.path) - const result = hit._highlightResult || hit - - // Content extraction - let content = '' - try { - if (result.contentStripTruncate) { - content = cutContent(result.contentStripTruncate) - } else if (result.contentStrip) { - content = cutContent(result.contentStrip) - } else if (result.content) { - content = cutContent(result.content) - } else if (hit.contentStripTruncate) { - content = cutContent(hit.contentStripTruncate) - } else if (hit.contentStrip) { - content = cutContent(hit.contentStrip) - } else if (hit.content) { - content = cutContent(hit.content) - } - } catch (error) { - content = '' - } - - // Title handling - let title = 'no-title' - try { - if (result.title) { - title = extractHighlightValue(result.title) || 'no-title' - } else if (hit.title) { - title = extractHighlightValue(hit.title) || 'no-title' - } - - if (!title || title === 'no-title') { - if (typeof hit.title === 'string' && hit.title.trim()) { - title = hit.title.trim() - } else if (hit.title && typeof hit.title === 'object' && hit.title.value) { - title = String(hit.title.value).trim() || 'no-title' - } else { - title = 'no-title' - } - } - } catch (error) { - title = 'no-title' - } - - return ` -
  • - - ${title} - ${content ? `
    ${content}
    ` : ''} -
    -
  • ` - }).join('') - - elements.hitsList.innerHTML = hitsHTML - elements.hitsWrapper.style.display = query ? '' : 'none' - - if (hits.length > 0) { - elements.stats.style.display = '' - } - } - - // Render pagination - const renderPagination = (page, nbPages) => { - if (nbPages <= 1) { - elements.pagination.style.display = 'none' - elements.paginationList.innerHTML = '' - return - } - - elements.pagination.style.display = 'block' - - const isFirstPage = page === 0 - const isLastPage = page === nbPages - 1 - - // Responsive page display - const isMobile = window.innerWidth < 768 - const maxVisiblePages = isMobile ? 3 : 5 - let startPage = Math.max(0, page - Math.floor(maxVisiblePages / 2)) - const endPage = Math.min(nbPages - 1, startPage + maxVisiblePages - 1) - - // Adjust starting page to maintain max visible pages - if (endPage - startPage + 1 < maxVisiblePages) { - startPage = Math.max(0, endPage - maxVisiblePages + 1) - } - - let pagesHTML = '' - - // Only add ellipsis and first page when there are many pages - if (nbPages > maxVisiblePages && startPage > 0) { - pagesHTML += ` -
  • - 1 -
  • ` - if (startPage > 1) { - pagesHTML += ` -
  • - ... -
  • ` - } - } - - // Add middle page numbers - for (let i = startPage; i <= endPage; i++) { - const isSelected = i === page - if (isSelected) { - pagesHTML += ` -
  • - ${i + 1} -
  • ` - } else { - pagesHTML += ` -
  • - ${i + 1} -
  • ` - } - } - - // Only add ellipsis and last page when there are many pages - if (nbPages > maxVisiblePages && endPage < nbPages - 1) { - if (endPage < nbPages - 2) { - pagesHTML += ` -
  • - ... -
  • ` - } - pagesHTML += ` -
  • - ${nbPages} -
  • ` - } - - if (nbPages > 1) { - elements.paginationList.innerHTML = ` -
  • - ${isFirstPage - ? '' - : `` - } -
  • - ${pagesHTML} -
  • - ${isLastPage - ? '' - : `` - } -
  • ` - elements.pagination.style.display = currentQuery ? '' : 'none' - } else { - elements.pagination.style.display = 'none' - } - } - - // Render statistics - const renderStats = (nbHits, processingTimeMS, query) => { - if (query) { - const stats = languages.hits_stats - .replace(/\$\{hits}/, nbHits) - .replace(/\$\{time}/, processingTimeMS) - elements.stats.innerHTML = `
    ${stats}` - elements.stats.style.display = '' - } else { - elements.stats.style.display = 'none' - } - } - - // Perform search - const performSearch = async (query, page = 0) => { - if (!query.trim()) { - currentQuery = '' - renderHits([], '', 0) - renderPagination(0, 0) - renderStats(0, 0, '') - toggleResultsVisibility(false) - return - } - - showLoading(true) - currentQuery = query - - try { - let result - - if (searchClient && typeof searchClient.search === 'function') { - // v5 multi-index search - const searchResult = await searchClient.search([{ - indexName, - query, - params: { - page, - hitsPerPage, - highlightPreTag: '', - highlightPostTag: '', - attributesToHighlight: ['title', 'content', 'contentStrip', 'contentStripTruncate'] - } - }]) - result = searchResult.results[0] - } else if (searchClient && typeof searchClient.initIndex === 'function') { - // v4 single-index search - const index = searchClient.initIndex(indexName) - result = await index.search(query, { - page, - hitsPerPage, - highlightPreTag: '', - highlightPostTag: '', - attributesToHighlight: ['title', 'content', 'contentStrip', 'contentStripTruncate'] - }) - } else { - throw new Error('Algolia: No compatible search method available') - } - - renderHits(result.hits || [], query, page) - - const actualNbPages = result.nbHits <= hitsPerPage ? 1 : (result.nbPages || 0) - renderPagination(page, actualNbPages) - renderStats(result.nbHits || 0, result.processingTimeMS || 0, query) - - const hasResults = result.hits && result.hits.length > 0 - toggleResultsVisibility(hasResults) - - // Refresh Pjax links - if (window.pjax) { - window.pjax.refresh(document.getElementById('algolia-hits')) - } - } catch (error) { - console.error('Algolia search error:', error) - renderHits([], query, page) - renderPagination(0, 0) - renderStats(0, 0, query) - } finally { - showLoading(false) - } - } - - // Debounced search - let searchTimeout - const debouncedSearch = (query, delay = 300) => { - clearTimeout(searchTimeout) - searchTimeout = setTimeout(() => performSearch(query), delay) - } - - // Initialize search box and events - const initializeSearch = () => { - showLoading(false) - - if (elements.searchInput) { - elements.searchInput.addEventListener('input', e => { - const query = e.target.value - debouncedSearch(query) - }) - } - - const searchForm = document.querySelector('#algolia-search-input .ais-SearchBox-form') - if (searchForm) { - searchForm.addEventListener('submit', e => { - e.preventDefault() - const query = elements.searchInput.value - performSearch(query) - }) - } - - // Pagination event delegation - elements.pagination.addEventListener('click', e => { - e.preventDefault() - const link = e.target.closest('a[data-page]') - if (link) { - const page = parseInt(link.dataset.page, 10) - if (!isNaN(page) && currentQuery) { - performSearch(currentQuery, page) - } - } - }) - - // Initial state - toggleResultsVisibility(false) - } - - // Initialize - initializeSearch() - searchClickFn() - searchFnOnce() - - window.addEventListener('pjax:complete', () => { - if (!btf.isHidden($searchMask)) closeSearch() - searchClickFn() - }) -}) diff --git a/themes/butterfly/source/js/search/local-search.js b/themes/butterfly/source/js/search/local-search.js deleted file mode 100644 index f2ccb06..0000000 --- a/themes/butterfly/source/js/search/local-search.js +++ /dev/null @@ -1,567 +0,0 @@ -/** - * Refer to hexo-generator-searchdb - * https://github.com/next-theme/hexo-generator-searchdb/blob/main/dist/search.js - * Modified by hexo-theme-butterfly - */ - -class LocalSearch { - constructor ({ - path = '', - unescape = false, - top_n_per_article = 1 - }) { - this.path = path - this.unescape = unescape - this.top_n_per_article = top_n_per_article - this.isfetched = false - this.datas = null - } - - getIndexByWord (words, text, caseSensitive = false) { - const index = [] - const included = new Set() - - if (!caseSensitive) { - text = text.toLowerCase() - } - words.forEach(word => { - if (this.unescape) { - const div = document.createElement('div') - div.innerText = word - word = div.innerHTML - } - const wordLen = word.length - if (wordLen === 0) return - let startPosition = 0 - let position = -1 - if (!caseSensitive) { - word = word.toLowerCase() - } - while ((position = text.indexOf(word, startPosition)) > -1) { - index.push({ position, word }) - included.add(word) - startPosition = position + wordLen - } - }) - // Sort index by position of keyword - index.sort((left, right) => { - if (left.position !== right.position) { - return left.position - right.position - } - return right.word.length - left.word.length - }) - return [index, included] - } - - // Merge hits into slices - mergeIntoSlice (start, end, index) { - let item = index[0] - let { position, word } = item - const hits = [] - const count = new Set() - while (position + word.length <= end && index.length !== 0) { - count.add(word) - hits.push({ - position, - length: word.length - }) - const wordEnd = position + word.length - - // Move to next position of hit - index.shift() - while (index.length !== 0) { - item = index[0] - position = item.position - word = item.word - if (wordEnd > position) { - index.shift() - } else { - break - } - } - } - return { - hits, - start, - end, - count: count.size - } - } - - // Highlight title and content - highlightKeyword (val, slice) { - let result = '' - let index = slice.start - for (const { position, length } of slice.hits) { - result += val.substring(index, position) - index = position + length - result += `${val.substr(position, length)}` - } - result += val.substring(index, slice.end) - return result - } - - getResultItems (keywords) { - const resultItems = [] - this.datas.forEach(({ title, content, url }) => { - // The number of different keywords included in the article. - const [indexOfTitle, keysOfTitle] = this.getIndexByWord(keywords, title) - const [indexOfContent, keysOfContent] = this.getIndexByWord(keywords, content) - const includedCount = new Set([...keysOfTitle, ...keysOfContent]).size - - // Show search results - const hitCount = indexOfTitle.length + indexOfContent.length - if (hitCount === 0) return - - const slicesOfTitle = [] - if (indexOfTitle.length !== 0) { - slicesOfTitle.push(this.mergeIntoSlice(0, title.length, indexOfTitle)) - } - - let slicesOfContent = [] - while (indexOfContent.length !== 0) { - const item = indexOfContent[0] - const { position } = item - // Cut out 120 characters. The maxlength of .search-input is 80. - const start = Math.max(0, position - 20) - const end = Math.min(content.length, position + 100) - slicesOfContent.push(this.mergeIntoSlice(start, end, indexOfContent)) - } - - // Sort slices in content by included keywords' count and hits' count - slicesOfContent.sort((left, right) => { - if (left.count !== right.count) { - return right.count - left.count - } else if (left.hits.length !== right.hits.length) { - return right.hits.length - left.hits.length - } - return left.start - right.start - }) - - // Select top N slices in content - const upperBound = parseInt(this.top_n_per_article, 10) - if (upperBound >= 0) { - slicesOfContent = slicesOfContent.slice(0, upperBound) - } - - let resultItem = '' - - url = new URL(url, location.origin) - url.searchParams.append('highlight', keywords.join(' ')) - - if (slicesOfTitle.length !== 0) { - resultItem += `
  • ${this.highlightKeyword(title, slicesOfTitle[0])}` - } else { - resultItem += `
  • ${title}` - } - - slicesOfContent.forEach(slice => { - resultItem += `

    ${this.highlightKeyword(content, slice)}...

    ` - }) - - resultItem += '
  • ' - resultItems.push({ - item: resultItem, - id: resultItems.length, - hitCount, - includedCount - }) - }) - return resultItems - } - - fetchData () { - const isXml = !this.path.endsWith('json') - fetch(this.path) - .then(response => response.text()) - .then(res => { - // Get the contents from search data - this.isfetched = true - this.datas = isXml - ? [...new DOMParser().parseFromString(res, 'text/xml').querySelectorAll('entry')].map(element => ({ - title: element.querySelector('title').textContent, - content: element.querySelector('content').textContent, - url: element.querySelector('url').textContent - })) - : JSON.parse(res) - // Only match articles with non-empty titles - this.datas = this.datas.filter(data => data.title).map(data => { - data.title = data.title.trim() - data.content = data.content ? data.content.trim().replace(/<[^>]+>/g, '') : '' - data.url = decodeURIComponent(data.url).replace(/\/{2,}/g, '/') - return data - }) - // Remove loading animation - window.dispatchEvent(new Event('search:loaded')) - }) - } - - // Highlight by wrapping node in mark elements with the given class name - highlightText (node, slice, className) { - const val = node.nodeValue - let index = slice.start - const children = [] - for (const { position, length } of slice.hits) { - const text = document.createTextNode(val.substring(index, position)) - index = position + length - const mark = document.createElement('mark') - mark.className = className - mark.appendChild(document.createTextNode(val.substr(position, length))) - children.push(text, mark) - } - node.nodeValue = val.substring(index, slice.end) - children.forEach(element => { - node.parentNode.insertBefore(element, node) - }) - } - - // Highlight the search words provided in the url in the text - highlightSearchWords (body) { - const params = new URL(location.href).searchParams.get('highlight') - const keywords = params ? params.split(' ') : [] - if (!keywords.length || !body) return - const walk = document.createTreeWalker(body, NodeFilter.SHOW_TEXT, null) - const allNodes = [] - while (walk.nextNode()) { - if (!walk.currentNode.parentNode.matches('button, select, textarea, .mermaid')) allNodes.push(walk.currentNode) - } - allNodes.forEach(node => { - const [indexOfNode] = this.getIndexByWord(keywords, node.nodeValue) - if (!indexOfNode.length) return - const slice = this.mergeIntoSlice(0, node.nodeValue.length, indexOfNode) - this.highlightText(node, slice, 'search-keyword') - }) - } -} - -window.addEventListener('load', () => { -// Search - const { path, top_n_per_article, unescape, languages, pagination } = GLOBAL_CONFIG.localSearch - const enablePagination = pagination && pagination.enable - const localSearch = new LocalSearch({ - path, - top_n_per_article, - unescape - }) - - const input = document.querySelector('.local-search-input input') - const statsItem = document.getElementById('local-search-stats') - const $loadingStatus = document.getElementById('loading-status') - const isXml = !path.endsWith('json') - - // Pagination variables (only initialize if pagination is enabled) - let currentPage = 0 - const hitsPerPage = pagination.hitsPerPage || 10 - - let currentResultItems = [] - - if (!enablePagination) { - // If pagination is disabled, we don't need these variables - currentPage = undefined - currentResultItems = undefined - } - - // Cache frequently used elements - const elements = { - get pagination () { return document.getElementById('local-search-pagination') }, - get paginationList () { return document.querySelector('#local-search-pagination .ais-Pagination-list') } - } - - // Show/hide search results area - const toggleResultsVisibility = hasResults => { - if (enablePagination) { - elements.pagination.style.display = hasResults ? '' : 'none' - } else { - elements.pagination.style.display = 'none' - } - } - - // Render search results for current page - const renderResults = (searchText, resultItems) => { - const container = document.getElementById('local-search-results') - - // Determine items to display based on pagination mode - const itemsToDisplay = enablePagination - ? currentResultItems.slice(currentPage * hitsPerPage, (currentPage + 1) * hitsPerPage) - : resultItems - - // Handle empty page in pagination mode - if (enablePagination && itemsToDisplay.length === 0 && currentResultItems.length > 0) { - currentPage = 0 - renderResults(searchText, resultItems) - return - } - - // Add numbering to items - const numberedItems = itemsToDisplay.map((result, index) => { - const itemNumber = enablePagination - ? currentPage * hitsPerPage + index + 1 - : index + 1 - return result.item.replace( - '
  • ', - `
  • ` - ) - }) - - container.innerHTML = `
      ${numberedItems.join('')}
    ` - - // Update stats - const displayCount = enablePagination ? currentResultItems.length : resultItems.length - const stats = languages.hits_stats.replace(/\$\{hits}/, displayCount) - statsItem.innerHTML = `
    ${stats}
    ` - - // Handle pagination - if (enablePagination) { - const nbPages = Math.ceil(currentResultItems.length / hitsPerPage) - renderPagination(currentPage, nbPages, searchText) - } - - const hasResults = resultItems.length > 0 - toggleResultsVisibility(hasResults) - - window.pjax && window.pjax.refresh(container) - } - - // Render pagination - const renderPagination = (page, nbPages, query) => { - if (nbPages <= 1) { - elements.pagination.style.display = 'none' - elements.paginationList.innerHTML = '' - return - } - - elements.pagination.style.display = 'block' - - const isFirstPage = page === 0 - const isLastPage = page === nbPages - 1 - - // Responsive page display - const isMobile = window.innerWidth < 768 - const maxVisiblePages = isMobile ? 3 : 5 - let startPage = Math.max(0, page - Math.floor(maxVisiblePages / 2)) - const endPage = Math.min(nbPages - 1, startPage + maxVisiblePages - 1) - - // Adjust starting page to maintain max visible pages - if (endPage - startPage + 1 < maxVisiblePages) { - startPage = Math.max(0, endPage - maxVisiblePages + 1) - } - - let pagesHTML = '' - - // Only add ellipsis and first page when there are many pages - if (nbPages > maxVisiblePages && startPage > 0) { - pagesHTML += ` -
  • - 1 -
  • ` - if (startPage > 1) { - pagesHTML += ` -
  • - ... -
  • ` - } - } - - // Add middle page numbers - for (let i = startPage; i <= endPage; i++) { - const isSelected = i === page - if (isSelected) { - pagesHTML += ` -
  • - ${i + 1} -
  • ` - } else { - pagesHTML += ` -
  • - ${i + 1} -
  • ` - } - } - - // Only add ellipsis and last page when there are many pages - if (nbPages > maxVisiblePages && endPage < nbPages - 1) { - if (endPage < nbPages - 2) { - pagesHTML += ` -
  • - ... -
  • ` - } - pagesHTML += ` -
  • - ${nbPages} -
  • ` - } - - if (nbPages > 1) { - elements.paginationList.innerHTML = ` -
  • - ${isFirstPage - ? '' - : `` - } -
  • - ${pagesHTML} -
  • - ${isLastPage - ? '' - : `` - } -
  • ` - } else { - elements.pagination.style.display = 'none' - } - } - - // Clear search results and stats - const clearSearchResults = () => { - const container = document.getElementById('local-search-results') - container.textContent = '' - statsItem.textContent = '' - toggleResultsVisibility(false) - if (enablePagination) { - currentResultItems = [] - currentPage = 0 - } - } - - // Show no results message - const showNoResults = searchText => { - const container = document.getElementById('local-search-results') - container.textContent = '' - const statsDiv = document.createElement('div') - statsDiv.className = 'search-result-stats' - statsDiv.textContent = languages.hits_empty.replace(/\$\{query}/, searchText) - statsItem.innerHTML = statsDiv.outerHTML - toggleResultsVisibility(false) - if (enablePagination) { - currentResultItems = [] - currentPage = 0 - } - } - - const inputEventFunction = () => { - if (!localSearch.isfetched) return - let searchText = input.value.trim().toLowerCase() - isXml && (searchText = searchText.replace(//g, '>')) - - if (searchText !== '') $loadingStatus.hidden = false - - const keywords = searchText.split(/[-\s]+/) - let resultItems = [] - - if (searchText.length > 0) { - resultItems = localSearch.getResultItems(keywords) - } - - if (keywords.length === 1 && keywords[0] === '') { - clearSearchResults() - } else if (resultItems.length === 0) { - showNoResults(searchText) - } else { - // Sort results by relevance - resultItems.sort((left, right) => { - if (left.includedCount !== right.includedCount) { - return right.includedCount - left.includedCount - } else if (left.hitCount !== right.hitCount) { - return right.hitCount - left.hitCount - } - return right.id - left.id - }) - - if (enablePagination) { - currentResultItems = resultItems - currentPage = 0 - } - renderResults(searchText, resultItems) - } - - $loadingStatus.hidden = true - } - - let loadFlag = false - const $searchMask = document.getElementById('search-mask') - const $searchDialog = document.querySelector('#local-search .search-dialog') - - // fix safari - const fixSafariHeight = () => { - if (window.innerWidth < 768) { - $searchDialog.style.setProperty('--search-height', window.innerHeight + 'px') - } - } - - const openSearch = () => { - btf.overflowPaddingR.add() - btf.animateIn($searchMask, 'to_show 0.5s') - btf.animateIn($searchDialog, 'titleScale 0.5s') - setTimeout(() => { input.focus() }, 300) - if (!loadFlag) { - !localSearch.isfetched && localSearch.fetchData() - input.addEventListener('input', inputEventFunction) - loadFlag = true - } - // shortcut: ESC - document.addEventListener('keydown', function f (event) { - if (event.code === 'Escape') { - closeSearch() - document.removeEventListener('keydown', f) - } - }) - - fixSafariHeight() - window.addEventListener('resize', fixSafariHeight) - } - - const closeSearch = () => { - btf.overflowPaddingR.remove() - btf.animateOut($searchDialog, 'search_close .5s') - btf.animateOut($searchMask, 'to_hide 0.5s') - window.removeEventListener('resize', fixSafariHeight) - } - - const searchClickFn = () => { - btf.addEventListenerPjax(document.querySelector('#search-button > .search'), 'click', openSearch) - } - - const searchFnOnce = () => { - document.querySelector('#local-search .search-close-button').addEventListener('click', closeSearch) - $searchMask.addEventListener('click', closeSearch) - if (GLOBAL_CONFIG.localSearch.preload) { - localSearch.fetchData() - } - localSearch.highlightSearchWords(document.getElementById('article-container')) - - // Pagination event delegation - only add if pagination is enabled - if (enablePagination) { - elements.pagination.addEventListener('click', e => { - e.preventDefault() - const link = e.target.closest('a[data-page]') - if (link) { - const page = parseInt(link.dataset.page, 10) - if (!isNaN(page) && currentResultItems.length > 0) { - currentPage = page - renderResults(input.value.trim().toLowerCase(), currentResultItems) - } - } - }) - } - - // Initial state - toggleResultsVisibility(false) - } - - window.addEventListener('search:loaded', () => { - const $loadDataItem = document.getElementById('loading-database') - $loadDataItem.nextElementSibling.style.visibility = 'visible' - $loadDataItem.remove() - }) - - searchClickFn() - searchFnOnce() - - // pjax - window.addEventListener('pjax:complete', () => { - !btf.isHidden($searchMask) && closeSearch() - localSearch.highlightSearchWords(document.getElementById('article-container')) - searchClickFn() - }) -}) diff --git a/themes/butterfly/source/js/search/typesense-search.js b/themes/butterfly/source/js/search/typesense-search.js deleted file mode 100644 index 58c896c..0000000 --- a/themes/butterfly/source/js/search/typesense-search.js +++ /dev/null @@ -1,545 +0,0 @@ -(function () { - 'use strict'; - - // ============================================================================ - // 配置区域 - 请根据实际情况修改 - // ============================================================================ - const CONFIG = { - apiKey: "2uxQzk7eD2GBCljdSmIiuU3ause7UK9n", // ⚠️ 建议使用 Search-Only API Key - server: { - host: "typesense.biss.click", - port: "443", - protocol: "https" - }, - indexName: "blogs", - searchParams: { - query_by: "title,content", - highlight_full_fields: "title,content", - per_page: 8, - num_typos: 1, - typo_tokens_threshold: 1, - prefix: true - }, - ui: { - maxRetries: 10, - retryDelay: 100, - animationDuration: 300 - } - }; - - // ============================================================================ - // 状态管理 - // ============================================================================ - let searchInstance = null; - let isInitialized = false; - let isSearchOpen = false; - let initRetryCount = 0; - const MAX_INIT_RETRIES = 30; // 最多重试30次 (3秒) - - // ============================================================================ - // 错误提示函数 - // ============================================================================ - function showErrorMessage() { - const hitsContainer = document.getElementById('hits'); - if (!hitsContainer) return; - - hitsContainer.innerHTML = - '
    ' + - '
    ' + - '
    搜索服务加载失败
    ' + - '
    ' + - '

    依赖库未能正确加载,请检查以下配置:

    ' + - '
      ' + - '
    1. 确认已在 _config.butterfly.yml 中正确引入依赖
    2. ' + - '
    3. 检查 JS 文件加载顺序(先 instantsearch.js,再 adapter)
    4. ' + - '
    5. 尝试更换 CDN 或使用本地文件
    6. ' + - '
    7. 打开浏览器控制台查看详细错误信息
    8. ' + - '
    ' + - '
    ' + - '
    ' + - '' + - '
    ' + - '
    '; - } - - // ============================================================================ - // 1. 动态插入 HTML 结构 - // ============================================================================ - const searchHTML = ` - - - - `; - - document.body.insertAdjacentHTML('beforeend', searchHTML); - - const mask = document.getElementById('typesense-search-mask'); - const closeBtn = document.getElementById('close-typesense'); - const container = document.getElementById('typesense-search-container'); - - // ============================================================================ - // 搜索控制 - // ============================================================================ - function openSearch() { - if (isSearchOpen) return; - isSearchOpen = true; - mask.style.display = 'block'; - void mask.offsetWidth; - mask.classList.add('active'); - document.body.style.overflow = 'hidden'; - - if (!isInitialized) { - initTypesense(); - } - focusSearchInput(); - } - - function closeSearch() { - if (!isSearchOpen) return; - isSearchOpen = false; - mask.classList.remove('active'); - setTimeout(function() { - mask.style.display = 'none'; - document.body.style.overflow = ''; - }, CONFIG.ui.animationDuration); - } - - function focusSearchInput(retryCount) { - retryCount = retryCount || 0; - const input = document.querySelector('.ais-SearchBox-input'); - if (input) { - input.focus(); - input.select(); - } else if (retryCount < CONFIG.ui.maxRetries) { - setTimeout(function() { - focusSearchInput(retryCount + 1); - }, CONFIG.ui.retryDelay); - } - } - - // ============================================================================ - // 事件监听 - // ============================================================================ - document.addEventListener('click', function(e) { - if (e.target.closest('.search-typesense-trigger')) { - e.preventDefault(); - openSearch(); - } - }); - - closeBtn.addEventListener('click', closeSearch); - mask.addEventListener('click', function(e) { - if (e.target === mask) closeSearch(); - }); - container.addEventListener('click', function(e) { - e.stopPropagation(); - }); - window.addEventListener('keydown', function(e) { - if (e.key === 'Escape' && isSearchOpen) { - closeSearch(); - } - if ((e.ctrlKey || e.metaKey) && e.key === 'k') { - e.preventDefault(); - openSearch(); - } - }); - - // ============================================================================ - // Typesense 初始化(带重试限制) - // ============================================================================ - function initTypesense() { - if (isInitialized || searchInstance) { - console.warn('Typesense 搜索已初始化'); - return; - } - - var instantsearchLoaded = typeof instantsearch !== 'undefined'; - var adapterLoaded = typeof TypesenseInstantSearchAdapter !== 'undefined' || - typeof window.TypesenseInstantSearchAdapter !== 'undefined'; - - console.log('📦 依赖库检查 (' + (initRetryCount + 1) + '/' + MAX_INIT_RETRIES + '):'); - console.log(' instantsearch.js:', instantsearchLoaded ? '✅ 已加载' : '❌ 未加载'); - console.log(' TypesenseAdapter:', adapterLoaded ? '✅ 已加载' : '❌ 未加载'); - - if (!instantsearchLoaded || !adapterLoaded) { - initRetryCount++; - - if (initRetryCount >= MAX_INIT_RETRIES) { - console.error(''); - console.error('❌ Typesense 依赖库加载失败!'); - console.error('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'); - console.error(''); - console.error('🔧 请检查 _config.butterfly.yml 配置:'); - console.error(''); - console.error('inject:'); - console.error(' bottom: # ⚠️ 使用 bottom 而不是 head'); - console.error(' - '); - console.error(' - '); - console.error(' - '); - console.error(''); - console.error('💡 或在控制台手动检查:'); - console.error(' typeof instantsearch'); - console.error(' typeof TypesenseInstantSearchAdapter'); - console.error(''); - console.error('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'); - - showErrorMessage(); - return; - } - - console.warn('⏳ 100ms 后重试...'); - setTimeout(initTypesense, 100); - return; - } - - initRetryCount = 0; - console.log('🚀 开始初始化 Typesense...'); - - try { - const typesenseAdapter = new TypesenseInstantSearchAdapter({ - server: { - apiKey: CONFIG.apiKey, - nodes: [{ - host: CONFIG.server.host, - port: CONFIG.server.port, - protocol: CONFIG.server.protocol - }], - cacheSearchResultsForSeconds: 120 - }, - additionalSearchParameters: CONFIG.searchParams - }); - - searchInstance = instantsearch({ - searchClient: typesenseAdapter.searchClient, - indexName: CONFIG.indexName, - routing: false - }); - - searchInstance.addWidgets([ - instantsearch.widgets.searchBox({ - container: '#searchbox', - placeholder: '输入关键词寻找故事...', - autofocus: true, - showReset: true, - showSubmit: false, - showLoadingIndicator: true - }), - instantsearch.widgets.stats({ - container: '#stats', - templates: { - text: function(data) { - if (!data.query) return ''; - return '找到 ' + data.nbHits + ' 条结果 (' + data.processingTimeMS + 'ms)'; - } - } - }), - instantsearch.widgets.hits({ - container: '#hits', - templates: { - empty: function(results) { - return '
    ' + - '
    ' + - '
    找不到与 "' + results.query + '" 相关的内容
    ' + - '
    试试其他关键词吧 (´·ω·`)
    ' + - '
    '; - }, - item: function(hit) { - // 使用 _highlightResult 获取高亮文本 - var titleHighlight = hit._highlightResult && hit._highlightResult.title - ? hit._highlightResult.title.value - : (hit.title || ''); - - var contentHighlight = hit._highlightResult && hit._highlightResult.content - ? hit._highlightResult.content.value - : (hit.content || ''); - - // 截取内容长度 - if (contentHighlight.length > 200) { - contentHighlight = contentHighlight.substring(0, 200) + '...'; - } - - return '' + - '
    ' + titleHighlight + '
    ' + - '
    ' + contentHighlight + '
    ' + - '
    '; - } - } - }), - instantsearch.widgets.pagination({ - container: '#pagination', - padding: 2, - showFirst: false, - showLast: false - }) - ]); - - searchInstance.start(); - isInitialized = true; - - console.log('✅ Typesense 初始化成功!'); - - searchInstance.on('render', function() { - if (isSearchOpen) { - const input = document.querySelector('.ais-SearchBox-input'); - if (input && document.activeElement !== input) { - input.focus(); - } - } - }); - - } catch (error) { - console.error('❌ 初始化失败:', error); - showErrorMessage(); - } - } - - // ============================================================================ - // 初始化 - // ============================================================================ - function init() { - console.log('🔍 Typesense 搜索已准备就绪'); - console.log('💡 快捷键: Ctrl/Cmd + K'); - } - - if (document.readyState === 'loading') { - document.addEventListener('DOMContentLoaded', init); - } else { - init(); - } - - // ============================================================================ - // 全局接口 - // ============================================================================ - window.TypesenseSearch = { - open: openSearch, - close: closeSearch, - isOpen: function() { return isSearchOpen; }, - getInstance: function() { return searchInstance; } - }; - -})(); \ No newline at end of file diff --git a/themes/butterfly/source/js/shuoshuo.js b/themes/butterfly/source/js/shuoshuo.js deleted file mode 100644 index 5858ebf..0000000 --- a/themes/butterfly/source/js/shuoshuo.js +++ /dev/null @@ -1,321 +0,0 @@ -function renderTalks() { - const talkContainer = document.querySelector('#talk'); - if (!talkContainer) return; - talkContainer.innerHTML = ''; - const generateIconSVG = () => { - return ``; - } - const waterfall = (a) => { - function b(a, b) { - var c = window.getComputedStyle(b); - return parseFloat(c["margin" + a]) || 0 - } - - function c(a) { - return a + "px" - } - - function d(a) { - return parseFloat(a.style.top) - } - - function e(a) { - return parseFloat(a.style.left) - } - - function f(a) { - return a.clientWidth - } - - function g(a) { - return a.clientHeight - } - - function h(a) { - return d(a) + g(a) + b("Bottom", a) - } - - function i(a) { - return e(a) + f(a) + b("Right", a) - } - - function j(a) { - a = a.sort(function (a, b) { - return h(a) === h(b) ? e(b) - e(a) : h(b) - h(a) - }) - } - - function k(b) { - f(a) != t && (b.target.removeEventListener(b.type, arguments.callee), waterfall(a)) - } - "string" == typeof a && (a = document.querySelector(a)); - var l = [].map.call(a.children, function (a) { - return a.style.position = "absolute", a - }); - a.style.position = "relative"; - var m = []; - l.length && (l[0].style.top = "0px", l[0].style.left = c(b("Left", l[0])), m.push(l[0])); - for (var n = 1; n < l.length; n++) { - var o = l[n - 1], - p = l[n], - q = i(o) + f(p) <= f(a); - if (!q) break; - p.style.top = o.style.top, p.style.left = c(i(o) + b("Left", p)), m.push(p) - } - for (; n < l.length; n++) { - j(m); - var p = l[n], - r = m.pop(); - p.style.top = c(h(r) + b("Top", p)), p.style.left = c(e(r)), m.push(p) - } - j(m); - var s = m[0]; - a.style.height = c(h(s) + b("Bottom", s)); - var t = f(a); - window.addEventListener ? window.addEventListener("resize", k) : document.body.onresize = k - }; - - const fetchAndRenderTalks = () => { - const url = 'https://mm.biss.click/api/echo/page'; - const cacheKey = 'talksCache'; - const cacheTimeKey = 'talksCacheTime'; - const cacheDuration = 30 * 60 * 1000; - const cachedData = localStorage.getItem(cacheKey); - const cachedTime = localStorage.getItem(cacheTimeKey); - const now = Date.now(); - - if (cachedData && cachedTime && (now - cachedTime < cacheDuration)) { - renderTalksList(JSON.parse(cachedData)); - } else { - fetch(url, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ page: 1, pageSize: 30 }) - }) - .then(res => res.json()) - .then(data => { - if (data.code === 1 && data.data && Array.isArray(data.data.items)) { - localStorage.setItem(cacheKey, JSON.stringify(data.data.items)); - localStorage.setItem(cacheTimeKey, now.toString()); - renderTalksList(data.data.items); - } - }) - .catch(err => console.error('Error fetching:', err)); - } - }; - - const renderTalksList = (list) => { - list.map(formatTalk).forEach(item => talkContainer.appendChild(generateTalkElement(item))); - waterfall('#talk'); - }; - - const formatTalk = (item) => { - const date = formatTime(item.created_at); - let content = item.content || ''; - content = content.replace(/\[(.*?)\]\((.*?)\)/g, `@$1`) - .replace(/- \[ \]/g, '⚪') - .replace(/- \[x\]/g, '⚫') - .replace(/\n/g, '
    '); - content = `
    ${content}
    `; - - // 图片 - if (Array.isArray(item.images) && item.images.length > 0) { - const imgDiv = document.createElement('div'); - imgDiv.className = 'zone_imgbox'; - item.images.forEach(img => { - const link = document.createElement('a'); - link.href = img.image_url + "?fmt=webp&q=75"; - link.setAttribute('data-fancybox', 'gallery'); - link.className = 'fancybox'; - const imgTag = document.createElement('img'); - imgTag.src = img.image_url + "?fmt=webp&q=75"; - link.appendChild(imgTag); - imgDiv.appendChild(link); - }); - content += imgDiv.outerHTML; - } - - // 外链 / GitHub 项目 - // 外链 / GitHub 项目 - if (['WEBSITE', 'GITHUBPROJ'].includes(item.extension_type)) { - let siteUrl = '', title = ''; - let extensionBack = "https://pic.biss.click/image/1971bdc1-4349-4bb9-b683-20404f5da7d7.webp"; - - // 解析 extension 字段 - try { - const extObj = typeof item.extension === 'string' ? JSON.parse(item.extension) : item.extension; - siteUrl = extObj.site || extObj.url || item.extension; - title = extObj.title || siteUrl; - } catch { - siteUrl = item.extension; - title = siteUrl; - } - - // 特殊处理 GitHub 项目 - if (item.extension_type === 'GITHUBPROJ') { - extensionBack = "https://pic.biss.click/image/ed410d4e-d3f8-4b26-8840-50dd58f7dc4e.webp"; - - // 提取 GitHub 项目名 - const match = siteUrl.match(/^https?:\/\/github\.com\/[^/]+\/([^/?#]+)/i); - if (match) { - title = match[1]; // 获取仓库名 - } else { - // fallback:从最后一个路径段提取 - try { - const parts = new URL(siteUrl).pathname.split('/').filter(Boolean); - title = parts.pop() || siteUrl; - } catch { - // 如果 URL 无效则保留原始 - } - } - } - - // 输出 HTML 结构 - content += ` - `; - } - - - // 音乐 - if (item.extension_type === 'MUSIC' && item.extension) { - const link = item.extension; - let server = ''; - if (link.includes('music.163.com')) server = 'netease'; - else if (link.includes('y.qq.com')) server = 'tencent'; - const idMatch = link.match(/id=(\d+)/); - const id = idMatch ? idMatch[1] : ''; - if (server && id) { - content += ``; - } - } - - // 视频 - if (item.extension_type === 'VIDEO' && item.extension) { - const video = item.extension; - if (video.startsWith('BV')) { - const bilibiliUrl = `https://www.bilibili.com/blackboard/html5mobileplayer.html?bvid=${video}&as_wide=1&high_quality=1&danmaku=0`; - content += ` -
    - -
    `; - } else { - const youtubeUrl = `https://www.youtube.com/embed/${video}`; - content += ` -
    - -
    `; - } - } - - return { - content, - user: item.username || '匿名', - avatar: 'https://free.picui.cn/free/2025/08/10/689845496a283.png', - date, - location: '', - tags: Array.isArray(item.tags) && item.tags.length ? item.tags.map(t => t.name) : ['无标签'], - text: content.replace(/\[(.*?)\]\((.*?)\)/g, '[链接]') - }; - }; - - const generateTalkElement = (item) => { - const talkItem = document.createElement('div'); - talkItem.className = 'talk_item'; - - const talkMeta = document.createElement('div'); - talkMeta.className = 'talk_meta'; - const avatar = document.createElement('img'); - avatar.className = 'no-lightbox avatar'; - avatar.src = item.avatar; - - const info = document.createElement('div'); - info.className = 'info'; - const nick = document.createElement('span'); - nick.className = 'talk_nick'; - nick.innerHTML = `${item.user} ${generateIconSVG()}`; - const date = document.createElement('span'); - date.className = 'talk_date'; - date.textContent = item.date; - info.appendChild(nick); - info.appendChild(date); - talkMeta.appendChild(avatar); - talkMeta.appendChild(info); - - const talkContent = document.createElement('div'); - talkContent.className = 'talk_content'; - talkContent.innerHTML = item.content; - - const talkBottom = document.createElement('div'); - talkBottom.className = 'talk_bottom'; - const tags = document.createElement('div'); - const tag = document.createElement('span'); - tag.className = 'talk_tag'; - tag.textContent = `🏷️${item.tags}`; - //const loc = document.createElement('span'); - //loc.className = 'location_tag'; - //loc.textContent = `🌍${item.location}`; - tags.appendChild(tag); - //tags.appendChild(loc); - - const commentLink = document.createElement('a'); - commentLink.href = 'javascript:;'; - commentLink.onclick = () => goComment(item.text); - const icon = document.createElement('span'); - icon.className = 'icon'; - icon.innerHTML = ''; - commentLink.appendChild(icon); - - talkBottom.appendChild(tags); - talkBottom.appendChild(commentLink); - - talkItem.appendChild(talkMeta); - talkItem.appendChild(talkContent); - talkItem.appendChild(talkBottom); - - return talkItem; - }; - - const goComment = (e) => { - const match = e.match(/
    ([\s\S]*?)<\/div>/); - const textContent = match ? match[1] : ""; - const textarea = document.querySelector("#twikoo .el-textarea__inner"); - textarea.value = `> ${textContent}\n\n`; - textarea.focus(); - btf.snackbarShow("已为您引用该说说,不删除空格效果更佳"); - }; - - const formatTime = (time) => { - const d = new Date(time); - const pad = (n) => n.toString().padStart(2, '0'); - return `${d.getFullYear()}-${pad(d.getMonth()+1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}`; - }; - - fetchAndRenderTalks(); -} - -renderTalks(); - -// function whenDOMReady() { -// const talkContainer = document.querySelector('#talk'); -// talkContainer.innerHTML = ''; -// fetchAndRenderTalks(); -// } -// whenDOMReady(); -// document.addEventListener("pjax:complete", whenDOMReady); diff --git a/themes/butterfly/source/js/shuoshuoshouye.js b/themes/butterfly/source/js/shuoshuoshouye.js deleted file mode 100644 index f5a2594..0000000 --- a/themes/butterfly/source/js/shuoshuoshouye.js +++ /dev/null @@ -1,97 +0,0 @@ -let talkTimer = null; - -const cacheKey = 'talksCache'; -const cacheTimeKey = 'talksCacheTime'; -const cacheDuration = 30 * 60 * 1000; // 缓存有效期 30分钟 - -function indexTalk() { - if (talkTimer) { - clearInterval(talkTimer); - talkTimer = null; - } - - if (!document.getElementById('bber-talk')) return; - - function toText(ls) { - return ls.map(item => { - let c = item.content || ''; - - const hasImg = /\!\[.*?\]\(.*?\)/.test(c); - const hasLink = /\[.*?\]\(.*?\)/.test(c); - - c = c - .replace(/#(.*?)\s/g, '') - .replace(/\{.*?\}/g, '') - .replace(/\!\[.*?\]\(.*?\)/g, '') - .replace(/\[.*?\]\(.*?\)/g, ''); - - const icons = []; - - if (item.images?.length && !hasImg) icons.push('fa-solid fa-image'); - if (item.extension_type === 'VIDEO') icons.push('fa-solid fa-video'); - if (item.extension_type === 'MUSIC') icons.push('fa-solid fa-music'); - if (item.extension_type === 'WEBSITE' && !hasLink) icons.push('fa-solid fa-link'); - if (item.extension_type === 'GITHUBPROJ' && !hasLink) icons.push('fab fa-github'); - - if (icons.length) c += ' ' + icons.map(i => ``).join(' '); - return c; - }); - } - - // 渲染与轮播 - function talk(ls) { - let html = ''; - ls.forEach((item, i) => { - html += `
  • ${item}
  • `; - }); - - let box = document.querySelector("#bber-talk .talk-list"); - if (!box) return; - - box.innerHTML = html; - - talkTimer = setInterval(() => { - if (box.children.length > 0) { - box.appendChild(box.children[0]); - } - }, 3000); - } - - const cachedData = localStorage.getItem(cacheKey); - const cachedTime = localStorage.getItem(cacheTimeKey); - const currentTime = new Date().getTime(); - - // 判断缓存是否有效 - if (cachedData && cachedTime && (currentTime - cachedTime < cacheDuration)) { - const data = toText(JSON.parse(cachedData)); - talk(data.slice(0, 6)); // 使用缓存渲染数据 - } else { - fetch('https://mm.biss.click/api/echo/page', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ page: 1, pageSize: 30 }) - }) - .then(res => res.json()) - .then(data => { - // 适配新版结构:code=1 且 data.items 存在 - if (data.code === 1 && data.data && Array.isArray(data.data.items)) { - localStorage.setItem(cacheKey, JSON.stringify(data.data.items)); - localStorage.setItem(cacheTimeKey, currentTime.toString()); - - const formattedData = toText(data.data.items); - talk(formattedData.slice(0, 6)); - } else { - console.warn('Unexpected API response format:', data); - } - }) - .catch(error => console.error('Error fetching data:', error)); - } -} - -// pjax 支持 -function whenDOMReady() { - indexTalk(); -} - -whenDOMReady(); -document.addEventListener("pjax:complete", whenDOMReady); diff --git a/themes/butterfly/source/js/tw_cn.js b/themes/butterfly/source/js/tw_cn.js deleted file mode 100644 index 43228dd..0000000 --- a/themes/butterfly/source/js/tw_cn.js +++ /dev/null @@ -1,117 +0,0 @@ -document.addEventListener('DOMContentLoaded', () => { - const { defaultEncoding, translateDelay, msgToTraditionalChinese, msgToSimplifiedChinese } = GLOBAL_CONFIG.translate - const snackbarData = GLOBAL_CONFIG.Snackbar - const targetEncodingCookie = 'translate-chn-cht' - - let currentEncoding = defaultEncoding - let targetEncoding = Number(btf.saveToLocal.get(targetEncodingCookie)) || defaultEncoding - const translateButtonObject = document.getElementById('translateLink') - const isSnackbar = snackbarData !== undefined - - const setLang = () => { - document.documentElement.lang = targetEncoding === 1 ? 'zh-TW' : 'zh-CN' - } - - const translateText = txt => { - if (!txt) return '' - if (currentEncoding === 1 && targetEncoding === 2) return Simplized(txt) - if (currentEncoding === 2 && targetEncoding === 1) return Traditionalized(txt) - return txt - } - - const translateBody = fobj => { - const nodes = typeof fobj === 'object' ? fobj.childNodes : document.body.childNodes - - for (const node of nodes) { - // Skip BR, HR tags, or the translate button object - if (['BR', 'HR'].includes(node.tagName) || node === translateButtonObject) continue - - if (node.nodeType === Node.ELEMENT_NODE) { - const { tagName, title, alt, placeholder, value, type } = node - - // Translate title, alt, placeholder - if (title) node.title = translateText(title) - if (alt) node.alt = translateText(alt) - if (placeholder) node.placeholder = translateText(placeholder) - - // Translate input value except text and hidden types - if (tagName === 'INPUT' && value && type !== 'text' && type !== 'hidden') { - node.value = translateText(value) - } - - // Recursively translate child nodes - translateBody(node) - } else if (node.nodeType === Node.TEXT_NODE) { - // Translate text node data - node.data = translateText(node.data) - } - } - } - - const translatePage = () => { - if (targetEncoding === 1) { - currentEncoding = 1 - targetEncoding = 2 - translateButtonObject.textContent = msgToTraditionalChinese - isSnackbar && btf.snackbarShow(snackbarData.cht_to_chs) - } else if (targetEncoding === 2) { - currentEncoding = 2 - targetEncoding = 1 - translateButtonObject.textContent = msgToSimplifiedChinese - isSnackbar && btf.snackbarShow(snackbarData.chs_to_cht) - } - btf.saveToLocal.set(targetEncodingCookie, targetEncoding, 2) - setLang() - translateBody() - } - - const JTPYStr = () => '万与丑专业丛东丝丢两严丧个丬丰临为丽举么义乌乐乔习乡书买乱争于亏云亘亚产亩亲亵亸亿仅从仑仓仪们价众优伙会伛伞伟传伤伥伦伧伪伫体余佣佥侠侣侥侦侧侨侩侪侬俣俦俨俩俪俭债倾偬偻偾偿傥傧储傩儿兑兖党兰关兴兹养兽冁内冈册写军农冢冯冲决况冻净凄凉凌减凑凛几凤凫凭凯击凼凿刍划刘则刚创删别刬刭刽刿剀剂剐剑剥剧劝办务劢动励劲劳势勋勐勚匀匦匮区医华协单卖卢卤卧卫却卺厂厅历厉压厌厍厕厢厣厦厨厩厮县参叆叇双发变叙叠叶号叹叽吁后吓吕吗吣吨听启吴呒呓呕呖呗员呙呛呜咏咔咙咛咝咤咴咸哌响哑哒哓哔哕哗哙哜哝哟唛唝唠唡唢唣唤唿啧啬啭啮啰啴啸喷喽喾嗫呵嗳嘘嘤嘱噜噼嚣嚯团园囱围囵国图圆圣圹场坂坏块坚坛坜坝坞坟坠垄垅垆垒垦垧垩垫垭垯垱垲垴埘埙埚埝埯堑堕塆墙壮声壳壶壸处备复够头夸夹夺奁奂奋奖奥妆妇妈妩妪妫姗姜娄娅娆娇娈娱娲娴婳婴婵婶媪嫒嫔嫱嬷孙学孪宁宝实宠审宪宫宽宾寝对寻导寿将尔尘尧尴尸尽层屃屉届属屡屦屿岁岂岖岗岘岙岚岛岭岳岽岿峃峄峡峣峤峥峦崂崃崄崭嵘嵚嵛嵝嵴巅巩巯币帅师帏帐帘帜带帧帮帱帻帼幂幞干并广庄庆庐庑库应庙庞废庼廪开异弃张弥弪弯弹强归当录彟彦彻径徕御忆忏忧忾怀态怂怃怄怅怆怜总怼怿恋恳恶恸恹恺恻恼恽悦悫悬悭悯惊惧惨惩惫惬惭惮惯愍愠愤愦愿慑慭憷懑懒懔戆戋戏戗战戬户扎扑扦执扩扪扫扬扰抚抛抟抠抡抢护报担拟拢拣拥拦拧拨择挂挚挛挜挝挞挟挠挡挢挣挤挥挦捞损捡换捣据捻掳掴掷掸掺掼揸揽揿搀搁搂搅携摄摅摆摇摈摊撄撑撵撷撸撺擞攒敌敛数斋斓斗斩断无旧时旷旸昙昼昽显晋晒晓晔晕晖暂暧札术朴机杀杂权条来杨杩杰极构枞枢枣枥枧枨枪枫枭柜柠柽栀栅标栈栉栊栋栌栎栏树栖样栾桊桠桡桢档桤桥桦桧桨桩梦梼梾检棂椁椟椠椤椭楼榄榇榈榉槚槛槟槠横樯樱橥橱橹橼檐檩欢欤欧歼殁殇残殒殓殚殡殴毁毂毕毙毡毵氇气氢氩氲汇汉污汤汹沓沟没沣沤沥沦沧沨沩沪沵泞泪泶泷泸泺泻泼泽泾洁洒洼浃浅浆浇浈浉浊测浍济浏浐浑浒浓浔浕涂涌涛涝涞涟涠涡涢涣涤润涧涨涩淀渊渌渍渎渐渑渔渖渗温游湾湿溃溅溆溇滗滚滞滟滠满滢滤滥滦滨滩滪漤潆潇潋潍潜潴澜濑濒灏灭灯灵灾灿炀炉炖炜炝点炼炽烁烂烃烛烟烦烧烨烩烫烬热焕焖焘煅煳熘爱爷牍牦牵牺犊犟状犷犸犹狈狍狝狞独狭狮狯狰狱狲猃猎猕猡猪猫猬献獭玑玙玚玛玮环现玱玺珉珏珐珑珰珲琎琏琐琼瑶瑷璇璎瓒瓮瓯电画畅畲畴疖疗疟疠疡疬疮疯疱疴痈痉痒痖痨痪痫痴瘅瘆瘗瘘瘪瘫瘾瘿癞癣癫癯皑皱皲盏盐监盖盗盘眍眦眬着睁睐睑瞒瞩矫矶矾矿砀码砖砗砚砜砺砻砾础硁硅硕硖硗硙硚确硷碍碛碜碱碹磙礼祎祢祯祷祸禀禄禅离秃秆种积称秽秾稆税稣稳穑穷窃窍窑窜窝窥窦窭竖竞笃笋笔笕笺笼笾筑筚筛筜筝筹签简箓箦箧箨箩箪箫篑篓篮篱簖籁籴类籼粜粝粤粪粮糁糇紧絷纟纠纡红纣纤纥约级纨纩纪纫纬纭纮纯纰纱纲纳纴纵纶纷纸纹纺纻纼纽纾线绀绁绂练组绅细织终绉绊绋绌绍绎经绐绑绒结绔绕绖绗绘给绚绛络绝绞统绠绡绢绣绤绥绦继绨绩绪绫绬续绮绯绰绱绲绳维绵绶绷绸绹绺绻综绽绾绿缀缁缂缃缄缅缆缇缈缉缊缋缌缍缎缏缐缑缒缓缔缕编缗缘缙缚缛缜缝缞缟缠缡缢缣缤缥缦缧缨缩缪缫缬缭缮缯缰缱缲缳缴缵罂网罗罚罢罴羁羟羡翘翙翚耢耧耸耻聂聋职聍联聩聪肃肠肤肷肾肿胀胁胆胜胧胨胪胫胶脉脍脏脐脑脓脔脚脱脶脸腊腌腘腭腻腼腽腾膑臜舆舣舰舱舻艰艳艹艺节芈芗芜芦苁苇苈苋苌苍苎苏苘苹茎茏茑茔茕茧荆荐荙荚荛荜荞荟荠荡荣荤荥荦荧荨荩荪荫荬荭荮药莅莜莱莲莳莴莶获莸莹莺莼萚萝萤营萦萧萨葱蒇蒉蒋蒌蓝蓟蓠蓣蓥蓦蔷蔹蔺蔼蕲蕴薮藁藓虏虑虚虫虬虮虽虾虿蚀蚁蚂蚕蚝蚬蛊蛎蛏蛮蛰蛱蛲蛳蛴蜕蜗蜡蝇蝈蝉蝎蝼蝾螀螨蟏衅衔补衬衮袄袅袆袜袭袯装裆裈裢裣裤裥褛褴襁襕见观觃规觅视觇览觉觊觋觌觍觎觏觐觑觞触觯詟誉誊讠计订讣认讥讦讧讨让讪讫训议讯记讱讲讳讴讵讶讷许讹论讻讼讽设访诀证诂诃评诅识诇诈诉诊诋诌词诎诏诐译诒诓诔试诖诗诘诙诚诛诜话诞诟诠诡询诣诤该详诧诨诩诪诫诬语诮误诰诱诲诳说诵诶请诸诹诺读诼诽课诿谀谁谂调谄谅谆谇谈谊谋谌谍谎谏谐谑谒谓谔谕谖谗谘谙谚谛谜谝谞谟谠谡谢谣谤谥谦谧谨谩谪谫谬谭谮谯谰谱谲谳谴谵谶谷豮贝贞负贠贡财责贤败账货质贩贪贫贬购贮贯贰贱贲贳贴贵贶贷贸费贺贻贼贽贾贿赀赁赂赃资赅赆赇赈赉赊赋赌赍赎赏赐赑赒赓赔赕赖赗赘赙赚赛赜赝赞赟赠赡赢赣赪赵赶趋趱趸跃跄跖跞践跶跷跸跹跻踊踌踪踬踯蹑蹒蹰蹿躏躜躯车轧轨轩轪轫转轭轮软轰轱轲轳轴轵轶轷轸轹轺轻轼载轾轿辀辁辂较辄辅辆辇辈辉辊辋辌辍辎辏辐辑辒输辔辕辖辗辘辙辚辞辩辫边辽达迁过迈运还这进远违连迟迩迳迹适选逊递逦逻遗遥邓邝邬邮邹邺邻郁郄郏郐郑郓郦郧郸酝酦酱酽酾酿释里鉅鉴銮錾钆钇针钉钊钋钌钍钎钏钐钑钒钓钔钕钖钗钘钙钚钛钝钞钟钠钡钢钣钤钥钦钧钨钩钪钫钬钭钮钯钰钱钲钳钴钵钶钷钸钹钺钻钼钽钾钿铀铁铂铃铄铅铆铈铉铊铋铍铎铏铐铑铒铕铗铘铙铚铛铜铝铞铟铠铡铢铣铤铥铦铧铨铪铫铬铭铮铯铰铱铲铳铴铵银铷铸铹铺铻铼铽链铿销锁锂锃锄锅锆锇锈锉锊锋锌锍锎锏锐锑锒锓锔锕锖锗错锚锜锞锟锠锡锢锣锤锥锦锨锩锫锬锭键锯锰锱锲锳锴锵锶锷锸锹锺锻锼锽锾锿镀镁镂镃镆镇镈镉镊镌镍镎镏镐镑镒镕镖镗镙镚镛镜镝镞镟镠镡镢镣镤镥镦镧镨镩镪镫镬镭镮镯镰镱镲镳镴镶长门闩闪闫闬闭问闯闰闱闲闳间闵闶闷闸闹闺闻闼闽闾闿阀阁阂阃阄阅阆阇阈阉阊阋阌阍阎阏阐阑阒阓阔阕阖阗阘阙阚阛队阳阴阵阶际陆陇陈陉陕陧陨险随隐隶隽难雏雠雳雾霁霉霭靓静靥鞑鞒鞯鞴韦韧韨韩韪韫韬韵页顶顷顸项顺须顼顽顾顿颀颁颂颃预颅领颇颈颉颊颋颌颍颎颏颐频颒颓颔颕颖颗题颙颚颛颜额颞颟颠颡颢颣颤颥颦颧风飏飐飑飒飓飔飕飖飗飘飙飚飞飨餍饤饥饦饧饨饩饪饫饬饭饮饯饰饱饲饳饴饵饶饷饸饹饺饻饼饽饾饿馀馁馂馃馄馅馆馇馈馉馊馋馌馍馎馏馐馑馒馓馔馕马驭驮驯驰驱驲驳驴驵驶驷驸驹驺驻驼驽驾驿骀骁骂骃骄骅骆骇骈骉骊骋验骍骎骏骐骑骒骓骔骕骖骗骘骙骚骛骜骝骞骟骠骡骢骣骤骥骦骧髅髋髌鬓魇魉鱼鱽鱾鱿鲀鲁鲂鲄鲅鲆鲇鲈鲉鲊鲋鲌鲍鲎鲏鲐鲑鲒鲓鲔鲕鲖鲗鲘鲙鲚鲛鲜鲝鲞鲟鲠鲡鲢鲣鲤鲥鲦鲧鲨鲩鲪鲫鲬鲭鲮鲯鲰鲱鲲鲳鲴鲵鲶鲷鲸鲹鲺鲻鲼鲽鲾鲿鳀鳁鳂鳃鳄鳅鳆鳇鳈鳉鳊鳋鳌鳍鳎鳏鳐鳑鳒鳓鳔鳕鳖鳗鳘鳙鳛鳜鳝鳞鳟鳠鳡鳢鳣鸟鸠鸡鸢鸣鸤鸥鸦鸧鸨鸩鸪鸫鸬鸭鸮鸯鸰鸱鸲鸳鸴鸵鸶鸷鸸鸹鸺鸻鸼鸽鸾鸿鹀鹁鹂鹃鹄鹅鹆鹇鹈鹉鹊鹋鹌鹍鹎鹏鹐鹑鹒鹓鹔鹕鹖鹗鹘鹚鹛鹜鹝鹞鹟鹠鹡鹢鹣鹤鹥鹦鹧鹨鹩鹪鹫鹬鹭鹯鹰鹱鹲鹳鹴鹾麦麸黄黉黡黩黪黾龙历志制一台皋准复猛钟注范签' - const FTPYStr = () => '萬與醜專業叢東絲丟兩嚴喪個爿豐臨為麗舉麼義烏樂喬習鄉書買亂爭於虧雲亙亞產畝親褻嚲億僅從侖倉儀們價眾優夥會傴傘偉傳傷倀倫傖偽佇體餘傭僉俠侶僥偵側僑儈儕儂俁儔儼倆儷儉債傾傯僂僨償儻儐儲儺兒兌兗黨蘭關興茲養獸囅內岡冊寫軍農塚馮衝決況凍淨淒涼淩減湊凜幾鳳鳧憑凱擊氹鑿芻劃劉則剛創刪別剗剄劊劌剴劑剮劍剝劇勸辦務勱動勵勁勞勢勳猛勩勻匭匱區醫華協單賣盧鹵臥衛卻巹廠廳曆厲壓厭厙廁廂厴廈廚廄廝縣參靉靆雙發變敘疊葉號歎嘰籲後嚇呂嗎唚噸聽啟吳嘸囈嘔嚦唄員咼嗆嗚詠哢嚨嚀噝吒噅鹹呱響啞噠嘵嗶噦嘩噲嚌噥喲嘜嗊嘮啢嗩唕喚呼嘖嗇囀齧囉嘽嘯噴嘍嚳囁嗬噯噓嚶囑嚕劈囂謔團園囪圍圇國圖圓聖壙場阪壞塊堅壇壢壩塢墳墜壟壟壚壘墾坰堊墊埡墶壋塏堖塒塤堝墊垵塹墮壪牆壯聲殼壺壼處備複夠頭誇夾奪奩奐奮獎奧妝婦媽嫵嫗媯姍薑婁婭嬈嬌孌娛媧嫻嫿嬰嬋嬸媼嬡嬪嬙嬤孫學孿寧寶實寵審憲宮寬賓寢對尋導壽將爾塵堯尷屍盡層屭屜屆屬屢屨嶼歲豈嶇崗峴嶴嵐島嶺嶽崠巋嶨嶧峽嶢嶠崢巒嶗崍嶮嶄嶸嶔崳嶁脊巔鞏巰幣帥師幃帳簾幟帶幀幫幬幘幗冪襆幹並廣莊慶廬廡庫應廟龐廢廎廩開異棄張彌弳彎彈強歸當錄彠彥徹徑徠禦憶懺憂愾懷態慫憮慪悵愴憐總懟懌戀懇惡慟懨愷惻惱惲悅愨懸慳憫驚懼慘懲憊愜慚憚慣湣慍憤憒願懾憖怵懣懶懍戇戔戲戧戰戩戶紮撲扡執擴捫掃揚擾撫拋摶摳掄搶護報擔擬攏揀擁攔擰撥擇掛摯攣掗撾撻挾撓擋撟掙擠揮撏撈損撿換搗據撚擄摑擲撣摻摜摣攬撳攙擱摟攪攜攝攄擺搖擯攤攖撐攆擷擼攛擻攢敵斂數齋斕鬥斬斷無舊時曠暘曇晝曨顯晉曬曉曄暈暉暫曖劄術樸機殺雜權條來楊榪傑極構樅樞棗櫪梘棖槍楓梟櫃檸檉梔柵標棧櫛櫳棟櫨櫟欄樹棲樣欒棬椏橈楨檔榿橋樺檜槳樁夢檮棶檢欞槨櫝槧欏橢樓欖櫬櫚櫸檟檻檳櫧橫檣櫻櫫櫥櫓櫞簷檁歡歟歐殲歿殤殘殞殮殫殯毆毀轂畢斃氈毿氌氣氫氬氳彙漢汙湯洶遝溝沒灃漚瀝淪滄渢溈滬濔濘淚澩瀧瀘濼瀉潑澤涇潔灑窪浹淺漿澆湞溮濁測澮濟瀏滻渾滸濃潯濜塗湧濤澇淶漣潿渦溳渙滌潤澗漲澀澱淵淥漬瀆漸澠漁瀋滲溫遊灣濕潰濺漵漊潷滾滯灩灄滿瀅濾濫灤濱灘澦濫瀠瀟瀲濰潛瀦瀾瀨瀕灝滅燈靈災燦煬爐燉煒熗點煉熾爍爛烴燭煙煩燒燁燴燙燼熱煥燜燾煆糊溜愛爺牘犛牽犧犢強狀獷獁猶狽麅獮獰獨狹獅獪猙獄猻獫獵獼玀豬貓蝟獻獺璣璵瑒瑪瑋環現瑲璽瑉玨琺瓏璫琿璡璉瑣瓊瑤璦璿瓔瓚甕甌電畫暢佘疇癤療瘧癘瘍鬁瘡瘋皰屙癰痙癢瘂癆瘓癇癡癉瘮瘞瘺癟癱癮癭癩癬癲臒皚皺皸盞鹽監蓋盜盤瞘眥矓著睜睞瞼瞞矚矯磯礬礦碭碼磚硨硯碸礪礱礫礎硜矽碩硤磽磑礄確鹼礙磧磣堿镟滾禮禕禰禎禱禍稟祿禪離禿稈種積稱穢穠穭稅穌穩穡窮竊竅窯竄窩窺竇窶豎競篤筍筆筧箋籠籩築篳篩簹箏籌簽簡籙簀篋籜籮簞簫簣簍籃籬籪籟糴類秈糶糲粵糞糧糝餱緊縶糸糾紆紅紂纖紇約級紈纊紀紉緯紜紘純紕紗綱納紝縱綸紛紙紋紡紵紖紐紓線紺絏紱練組紳細織終縐絆紼絀紹繹經紿綁絨結絝繞絰絎繪給絢絳絡絕絞統綆綃絹繡綌綏絛繼綈績緒綾緓續綺緋綽緔緄繩維綿綬繃綢綯綹綣綜綻綰綠綴緇緙緗緘緬纜緹緲緝縕繢緦綞緞緶線緱縋緩締縷編緡緣縉縛縟縝縫縗縞纏縭縊縑繽縹縵縲纓縮繆繅纈繚繕繒韁繾繰繯繳纘罌網羅罰罷羆羈羥羨翹翽翬耮耬聳恥聶聾職聹聯聵聰肅腸膚膁腎腫脹脅膽勝朧腖臚脛膠脈膾髒臍腦膿臠腳脫腡臉臘醃膕齶膩靦膃騰臏臢輿艤艦艙艫艱豔艸藝節羋薌蕪蘆蓯葦藶莧萇蒼苧蘇檾蘋莖蘢蔦塋煢繭荊薦薘莢蕘蓽蕎薈薺蕩榮葷滎犖熒蕁藎蓀蔭蕒葒葤藥蒞蓧萊蓮蒔萵薟獲蕕瑩鶯蓴蘀蘿螢營縈蕭薩蔥蕆蕢蔣蔞藍薊蘺蕷鎣驀薔蘞藺藹蘄蘊藪槁蘚虜慮虛蟲虯蟣雖蝦蠆蝕蟻螞蠶蠔蜆蠱蠣蟶蠻蟄蛺蟯螄蠐蛻蝸蠟蠅蟈蟬蠍螻蠑螿蟎蠨釁銜補襯袞襖嫋褘襪襲襏裝襠褌褳襝褲襇褸襤繈襴見觀覎規覓視覘覽覺覬覡覿覥覦覯覲覷觴觸觶讋譽謄訁計訂訃認譏訐訌討讓訕訖訓議訊記訒講諱謳詎訝訥許訛論訩訟諷設訪訣證詁訶評詛識詗詐訴診詆謅詞詘詔詖譯詒誆誄試詿詩詰詼誠誅詵話誕詬詮詭詢詣諍該詳詫諢詡譸誡誣語誚誤誥誘誨誑說誦誒請諸諏諾讀諑誹課諉諛誰諗調諂諒諄誶談誼謀諶諜謊諫諧謔謁謂諤諭諼讒諮諳諺諦謎諞諝謨讜謖謝謠謗諡謙謐謹謾謫譾謬譚譖譙讕譜譎讞譴譫讖穀豶貝貞負貟貢財責賢敗賬貨質販貪貧貶購貯貫貳賤賁貰貼貴貺貸貿費賀貽賊贄賈賄貲賃賂贓資賅贐賕賑賚賒賦賭齎贖賞賜贔賙賡賠賧賴賵贅賻賺賽賾贗讚贇贈贍贏贛赬趙趕趨趲躉躍蹌蹠躒踐躂蹺蹕躚躋踴躊蹤躓躑躡蹣躕躥躪躦軀車軋軌軒軑軔轉軛輪軟轟軲軻轤軸軹軼軤軫轢軺輕軾載輊轎輈輇輅較輒輔輛輦輩輝輥輞輬輟輜輳輻輯轀輸轡轅轄輾轆轍轔辭辯辮邊遼達遷過邁運還這進遠違連遲邇逕跡適選遜遞邐邏遺遙鄧鄺鄔郵鄒鄴鄰鬱郤郟鄶鄭鄆酈鄖鄲醞醱醬釅釃釀釋裏钜鑒鑾鏨釓釔針釘釗釙釕釷釺釧釤鈒釩釣鍆釹鍚釵鈃鈣鈈鈦鈍鈔鍾鈉鋇鋼鈑鈐鑰欽鈞鎢鉤鈧鈁鈥鈄鈕鈀鈺錢鉦鉗鈷缽鈳鉕鈽鈸鉞鑽鉬鉭鉀鈿鈾鐵鉑鈴鑠鉛鉚鈰鉉鉈鉍鈹鐸鉶銬銠鉺銪鋏鋣鐃銍鐺銅鋁銱銦鎧鍘銖銑鋌銩銛鏵銓鉿銚鉻銘錚銫鉸銥鏟銃鐋銨銀銣鑄鐒鋪鋙錸鋱鏈鏗銷鎖鋰鋥鋤鍋鋯鋨鏽銼鋝鋒鋅鋶鐦鐧銳銻鋃鋟鋦錒錆鍺錯錨錡錁錕錩錫錮鑼錘錐錦鍁錈錇錟錠鍵鋸錳錙鍥鍈鍇鏘鍶鍔鍤鍬鍾鍛鎪鍠鍰鎄鍍鎂鏤鎡鏌鎮鎛鎘鑷鐫鎳鎿鎦鎬鎊鎰鎔鏢鏜鏍鏰鏞鏡鏑鏃鏇鏐鐔钁鐐鏷鑥鐓鑭鐠鑹鏹鐙鑊鐳鐶鐲鐮鐿鑔鑣鑞鑲長門閂閃閆閈閉問闖閏闈閑閎間閔閌悶閘鬧閨聞闥閩閭闓閥閣閡閫鬮閱閬闍閾閹閶鬩閿閽閻閼闡闌闃闠闊闋闔闐闒闕闞闤隊陽陰陣階際陸隴陳陘陝隉隕險隨隱隸雋難雛讎靂霧霽黴靄靚靜靨韃鞽韉韝韋韌韍韓韙韞韜韻頁頂頃頇項順須頊頑顧頓頎頒頌頏預顱領頗頸頡頰頲頜潁熲頦頤頻頮頹頷頴穎顆題顒顎顓顏額顳顢顛顙顥纇顫顬顰顴風颺颭颮颯颶颸颼颻飀飄飆飆飛饗饜飣饑飥餳飩餼飪飫飭飯飲餞飾飽飼飿飴餌饒餉餄餎餃餏餅餑餖餓餘餒餕餜餛餡館餷饋餶餿饞饁饃餺餾饈饉饅饊饌饢馬馭馱馴馳驅馹駁驢駔駛駟駙駒騶駐駝駑駕驛駘驍罵駰驕驊駱駭駢驫驪騁驗騂駸駿騏騎騍騅騌驌驂騙騭騤騷騖驁騮騫騸驃騾驄驏驟驥驦驤髏髖髕鬢魘魎魚魛魢魷魨魯魴魺鮁鮃鯰鱸鮋鮓鮒鮊鮑鱟鮍鮐鮭鮚鮳鮪鮞鮦鰂鮜鱠鱭鮫鮮鮺鯗鱘鯁鱺鰱鰹鯉鰣鰷鯀鯊鯇鮶鯽鯒鯖鯪鯕鯫鯡鯤鯧鯝鯢鯰鯛鯨鯵鯴鯔鱝鰈鰏鱨鯷鰮鰃鰓鱷鰍鰒鰉鰁鱂鯿鰠鼇鰭鰨鰥鰩鰟鰜鰳鰾鱈鱉鰻鰵鱅鰼鱖鱔鱗鱒鱯鱤鱧鱣鳥鳩雞鳶鳴鳲鷗鴉鶬鴇鴆鴣鶇鸕鴨鴞鴦鴒鴟鴝鴛鴬鴕鷥鷙鴯鴰鵂鴴鵃鴿鸞鴻鵐鵓鸝鵑鵠鵝鵒鷳鵜鵡鵲鶓鵪鶤鵯鵬鵮鶉鶊鵷鷫鶘鶡鶚鶻鶿鶥鶩鷊鷂鶲鶹鶺鷁鶼鶴鷖鸚鷓鷚鷯鷦鷲鷸鷺鸇鷹鸌鸏鸛鸘鹺麥麩黃黌黶黷黲黽龍歷誌製壹臺臯準復勐鐘註範籤' - - const Traditionalized = cc => { - let str = '' - const ss = JTPYStr() - const tt = FTPYStr() - for (let i = 0; i < cc.length; i++) { - if (cc.charCodeAt(i) > 10000 && ss.indexOf(cc.charAt(i)) !== -1) { - str += tt.charAt(ss.indexOf(cc.charAt(i))) - } else str += cc.charAt(i) - } - return str - } - - const Simplized = cc => { - let str = '' - const ss = JTPYStr() - const tt = FTPYStr() - for (let i = 0; i < cc.length; i++) { - if (cc.charCodeAt(i) > 10000 && tt.indexOf(cc.charAt(i)) !== -1) { - str += ss.charAt(tt.indexOf(cc.charAt(i))) - } else str += cc.charAt(i) - } - return str - } - - const translateInitialization = () => { - if (translateButtonObject) { - if (currentEncoding !== targetEncoding) { - translateButtonObject.textContent = - targetEncoding === 1 - ? msgToSimplifiedChinese - : msgToTraditionalChinese - setLang() - setTimeout(translateBody, translateDelay) - } - } - } - - window.translateFn = { - translatePage, - Traditionalized, - Simplized, - translateInitialization - } - - translateInitialization() - btf.addGlobalFn('pjaxComplete', translateInitialization, 'translateInitialization') -}) diff --git a/themes/butterfly/source/js/utils.js b/themes/butterfly/source/js/utils.js deleted file mode 100644 index 1dd5386..0000000 --- a/themes/butterfly/source/js/utils.js +++ /dev/null @@ -1,338 +0,0 @@ -(() => { - const btfFn = { - debounce: (func, wait = 0, immediate = false) => { - let timeout - return (...args) => { - const later = () => { - timeout = null - if (!immediate) func(...args) - } - const callNow = immediate && !timeout - clearTimeout(timeout) - timeout = setTimeout(later, wait) - if (callNow) func(...args) - } - }, - - throttle: (func, wait, options = {}) => { - let timeout, args - let previous = 0 - - const later = () => { - previous = options.leading === false ? 0 : new Date().getTime() - timeout = null - func(...args) - if (!timeout) args = null - } - - return (...params) => { - const now = new Date().getTime() - if (!previous && options.leading === false) previous = now - const remaining = wait - (now - previous) - args = params - - if (remaining <= 0 || remaining > wait) { - if (timeout) { - clearTimeout(timeout) - timeout = null - } - previous = now - func(...args) - if (!timeout) args = null - } else if (!timeout && options.trailing !== false) { - timeout = setTimeout(later, remaining) - } - } - }, - - overflowPaddingR: { - add: () => { - const paddingRight = window.innerWidth - document.body.clientWidth - - if (paddingRight > 0) { - document.body.style.paddingRight = `${paddingRight}px` - document.body.style.overflow = 'hidden' - const menuElement = document.querySelector('#page-header.nav-fixed #menus') - if (menuElement) { - menuElement.style.paddingRight = `${paddingRight}px` - } - } - }, - remove: () => { - document.body.style.paddingRight = '' - document.body.style.overflow = '' - const menuElement = document.querySelector('#page-header.nav-fixed #menus') - if (menuElement) { - menuElement.style.paddingRight = '' - } - } - }, - - snackbarShow: (text, showAction = false, duration = 2000) => { - const { position, bgLight, bgDark } = GLOBAL_CONFIG.Snackbar - const bg = document.documentElement.getAttribute('data-theme') === 'light' ? bgLight : bgDark - Snackbar.show({ - text, - backgroundColor: bg, - showAction, - duration, - pos: position, - customClass: 'snackbar-css' - }) - }, - - diffDate: (inputDate, more = false) => { - const dateNow = new Date() - const datePost = new Date(inputDate) - const diffMs = dateNow - datePost - const diffSec = diffMs / 1000 - const diffMin = diffSec / 60 - const diffHour = diffMin / 60 - const diffDay = diffHour / 24 - const diffMonth = diffDay / 30 - const { dateSuffix } = GLOBAL_CONFIG - - if (!more) return Math.floor(diffDay) - - if (diffMonth > 12) return datePost.toISOString().slice(0, 10) - if (diffMonth >= 1) return `${Math.floor(diffMonth)} ${dateSuffix.month}` - if (diffDay >= 1) return `${Math.floor(diffDay)} ${dateSuffix.day}` - if (diffHour >= 1) return `${Math.floor(diffHour)} ${dateSuffix.hour}` - if (diffMin >= 1) return `${Math.floor(diffMin)} ${dateSuffix.min}` - return dateSuffix.just - }, - - loadComment: (dom, callback) => { - if ('IntersectionObserver' in window) { - const observerItem = new IntersectionObserver(entries => { - if (entries[0].isIntersecting) { - callback() - observerItem.disconnect() - } - }, { threshold: [0] }) - observerItem.observe(dom) - } else { - callback() - } - }, - - scrollToDest: (pos, time = 500) => { - const currentPos = window.scrollY - const isNavFixed = document.getElementById('page-header').classList.contains('fixed') - if (currentPos > pos || isNavFixed) pos = pos - 70 - - if ('scrollBehavior' in document.documentElement.style) { - window.scrollTo({ - top: pos, - behavior: 'smooth' - }) - return - } - - const startTime = performance.now() - const animate = currentTime => { - const timeElapsed = currentTime - startTime - const progress = Math.min(timeElapsed / time, 1) - window.scrollTo(0, currentPos + (pos - currentPos) * progress) - if (progress < 1) { - requestAnimationFrame(animate) - } - } - requestAnimationFrame(animate) - }, - - animateIn: (ele, animation) => { - ele.style.display = 'block' - ele.style.animation = animation - }, - - animateOut: (ele, animation) => { - const handleAnimationEnd = () => { - ele.style.display = '' - ele.style.animation = '' - ele.removeEventListener('animationend', handleAnimationEnd) - } - ele.addEventListener('animationend', handleAnimationEnd) - ele.style.animation = animation - }, - - wrap: (selector, eleType, options) => { - const createEle = document.createElement(eleType) - for (const [key, value] of Object.entries(options)) { - createEle.setAttribute(key, value) - } - selector.parentNode.insertBefore(createEle, selector) - createEle.appendChild(selector) - }, - - isHidden: ele => ele.offsetHeight === 0 && ele.offsetWidth === 0, - - getEleTop: ele => ele.getBoundingClientRect().top + window.scrollY, - - loadLightbox: ele => { - const service = GLOBAL_CONFIG.lightbox - - if (service === 'medium_zoom') { - mediumZoom(ele, { background: 'var(--zoom-bg)' }) - return - } - - if (service === 'fancybox') { - ele.forEach(i => { - if (i.parentNode.tagName !== 'A') { - const dataSrc = i.dataset.lazySrc || i.src - const dataCaption = i.title || i.alt || '' - btf.wrap(i, 'a', { href: dataSrc, 'data-fancybox': 'gallery', 'data-caption': dataCaption, 'data-thumb': dataSrc }) - } - }) - - if (!window.fancyboxRun) { - let options = '' - if (Fancybox.version < '6') { - options = { - Hash: false, - Thumbs: { - showOnStart: false - }, - Images: { - Panzoom: { - maxScale: 4 - } - }, - Carousel: { - transition: 'slide' - }, - Toolbar: { - display: { - left: ['infobar'], - middle: [ - 'zoomIn', - 'zoomOut', - 'toggle1to1', - 'rotateCCW', - 'rotateCW', - 'flipX', - 'flipY' - ], - right: ['slideshow', 'thumbs', 'close'] - } - } - } - } else { - options = { - Hash: false, - Carousel: { - transition: 'slide', - Thumbs: { - showOnStart: false - }, - Toolbar: { - display: { - left: ['counter'], - middle: [ - 'zoomIn', - 'zoomOut', - 'toggle1to1', - 'rotateCCW', - 'rotateCW', - 'flipX', - 'flipY', - 'reset' - ], - right: ['autoplay', 'thumbs', 'close'] - } - }, - Zoomable: { - Panzoom: { - maxScale: 4 - } - } - } - } - } - - Fancybox.bind('[data-fancybox]', options) - window.fancyboxRun = true - } - } - }, - - setLoading: { - add: ele => { - const html = ` -
    -
    -
    -
    -
    - ` - ele.insertAdjacentHTML('afterend', html) - }, - remove: ele => { - ele.nextElementSibling.remove() - } - }, - - updateAnchor: anchor => { - if (anchor !== window.location.hash) { - if (!anchor) anchor = location.pathname - const title = GLOBAL_CONFIG_SITE.title - window.history.replaceState({ - url: location.href, - title - }, title, anchor) - } - }, - - getScrollPercent: (() => { - let docHeight, winHeight, headerHeight, contentMath - - return (currentTop, ele) => { - if (!docHeight || ele.clientHeight !== docHeight) { - docHeight = ele.clientHeight - winHeight = window.innerHeight - headerHeight = ele.offsetTop - contentMath = Math.max(docHeight - winHeight, document.documentElement.scrollHeight - winHeight) - } - - const scrollPercent = (currentTop - headerHeight) / contentMath - return Math.max(0, Math.min(100, Math.round(scrollPercent * 100))) - } - })(), - - addEventListenerPjax: (ele, event, fn, option = false) => { - ele.addEventListener(event, fn, option) - btf.addGlobalFn('pjaxSendOnce', () => { - ele.removeEventListener(event, fn, option) - }) - }, - - removeGlobalFnEvent: (key, parent = window) => { - const globalFn = parent.globalFn || {} - const keyObj = globalFn[key] - if (!keyObj) return - - Object.keys(keyObj).forEach(i => keyObj[i]()) - - delete globalFn[key] - }, - - switchComments: (el = document, path) => { - const switchBtn = el.querySelector('#switch-btn') - if (!switchBtn) return - - let switchDone = false - const postComment = el.querySelector('#post-comment') - const handleSwitchBtn = () => { - postComment.classList.toggle('move') - if (!switchDone && typeof loadOtherComment === 'function') { - switchDone = true - loadOtherComment(el, path) - } - } - btf.addEventListenerPjax(switchBtn, 'click', handleSwitchBtn) - } - } - - window.btf = { ...window.btf, ...btfFn } -})() diff --git a/themes/butterfly/source/js/weather.js b/themes/butterfly/source/js/weather.js deleted file mode 100644 index c2876c6..0000000 --- a/themes/butterfly/source/js/weather.js +++ /dev/null @@ -1,204 +0,0 @@ -// weather.js - -(function () { - - const WEATHER_ID = "aside-weather"; - const LOCATION_WAIT_MS = 3000; - const LOCATION_POLL_INTERVAL_MS = 100; - const WEATHER_KEY = "70201debab4245379e376d116b05f8a2"; - const WEATHER_HOST = "https://ny5vxmbbkd.re.qweatherapi.com"; - const WEATHER_LANG = "zh"; - - function debugLog(label, payload) { - console.log(`[weather.js] ${label}`, payload); - } - - function maskSecret(secret) { - if (!secret || secret.length < 10) return secret; - return `${secret.slice(0, 4)}***${secret.slice(-4)}`; - } - - function readExistingLocation() { - return window.ipLocation?.result?.location || null; - } - - function waitForExistingLocation(timeout = LOCATION_WAIT_MS) { - return new Promise((resolve, reject) => { - const existingLocation = readExistingLocation(); - if (existingLocation) { - resolve(existingLocation); - return; - } - - const startTime = Date.now(); - const timer = window.setInterval(() => { - const location = readExistingLocation(); - if (location) { - window.clearInterval(timer); - resolve(location); - return; - } - - if (Date.now() - startTime >= timeout) { - window.clearInterval(timer); - reject(new Error("等待 ipLocation 超时")); - } - }, LOCATION_POLL_INTERVAL_MS); - }); - } - - function requestLocationFallback() { - return new Promise((resolve, reject) => { - if (!window.$?.ajax) { - reject(new Error("jQuery 未加载,无法执行定位兜底请求")); - return; - } - - $.ajax({ - type: 'get', - url: 'https://apis.map.qq.com/ws/location/v1/ip', - data: { - key: '你的key', - output: 'jsonp', - callback: '?', - }, - dataType: 'jsonp', - success: function (res) { - debugLog("定位兜底返回", res); - if (res && res.status === 0 && res.result?.location) { - window.ipLocation = res; - resolve(res.result.location); - } else { - reject(new Error("定位失败")); - } - }, - error: function () { - reject(new Error("定位请求失败")); - } - }); - }); - } - - function getLocation() { - return waitForExistingLocation() - .then(location => { - debugLog("使用 txmap.js 定位", location); - return location; - }) - .catch(error => { - debugLog("等待 txmap.js 定位失败,转兜底请求", error); - return requestLocationFallback(); - }); - } - - - function getWeather(lng, lat) { - const location = `${lng.toFixed(2)},${lat.toFixed(2)}`; - const params = new URLSearchParams({ - location, - lang: WEATHER_LANG, - key: WEATHER_KEY - }); - const requestUrl = `${WEATHER_HOST}/v7/weather/now?${params.toString()}`; - - debugLog("天气请求参数", { - host: WEATHER_HOST, - location, - lang: WEATHER_LANG, - key: maskSecret(WEATHER_KEY) - }); - - return fetch(requestUrl, { - headers: { - Accept: "application/json" - } - }).then(async res => { - const responseText = await res.text(); - let data; - - try { - data = JSON.parse(responseText); - } catch (parseError) { - debugLog("天气接口返回非 JSON", { - status: res.status, - statusText: res.statusText, - body: responseText - }); - throw new Error(`天气接口返回非 JSON,HTTP ${res.status}`); - } - - debugLog("天气接口响应", { - status: res.status, - statusText: res.statusText, - body: data - }); - - if (!res.ok) { - const detail = data?.error?.detail || data?.msg || `HTTP ${res.status}`; - throw new Error(`天气接口请求失败:${detail}`); - } - - return data; - }); - } - - - function renderWeather(data) { - const el = document.getElementById(WEATHER_ID); - if (!el) return; - - if (data.code !== "200") { - debugLog("天气接口业务错误", data); - el.innerHTML = `天气获取失败:${data.code || "未知错误"}`; - return; - } - - const now = data.now; - - const emojiMap = { - "晴": "☀️", - "多云": "⛅", - "阴": "☁️", - "雨": "🌧️", - "雪": "❄️", - "雷": "⛈️" - }; - - const emoji = emojiMap[now.text] || "🌡️"; - const weatherSentence = `当前天气${now.text},气温${now.temp}℃,体感${now.feelsLike}℃,${now.windDir}${now.windScale}级,湿度${now.humidity}%,能见度${now.vis}km。`; - - el.innerHTML = ` -
    -
    -
    -
    - ${weatherSentence} -
    -
    - `; - } - - - function initWeather() { - const el = document.getElementById(WEATHER_ID); - if (!el) return; - - el.innerHTML = "🌥️ 获取天气中..."; - - getLocation() - .then(loc => getWeather(loc.lng, loc.lat)) - .then(renderWeather) - .catch(err => { - console.error("[weather.js] 天气加载失败", err); - el.innerHTML = `🌧️ 天气加载失败:${err.message || "未知错误"}`; - }); - } - - - // 🚀 初次加载 - window.addEventListener("load", initWeather); - - // 🔄 pjax - document.addEventListener("pjax:complete", initWeather); - -})();