├── .eslintignore ├── .eslintrc.json ├── .github ├── ISSUE_TEMPLATE │ ├── Bug反馈.md │ ├── bug_report.md │ ├── config.yml │ ├── feature_request.md │ └── 功能建议.md ├── PULL_REQUEST_TEMPLATE │ └── pull_request_template.md ├── dependabot.yml ├── stale.yml └── workflows │ ├── github-release.yml │ ├── lint.yml │ ├── npm-publish.yml │ └── test.yml ├── .gitignore ├── .npmignore ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── include ├── config.js ├── dependency.js ├── migration │ ├── head.js │ ├── v2_v3.js │ ├── v3_v4.js │ ├── v4_v5.js │ └── v5_v5.1.js ├── register.js ├── schema │ ├── comment │ │ └── .gitkeep │ ├── common │ │ ├── article.json │ │ ├── comment.json │ │ ├── donates.json │ │ ├── footer.json │ │ ├── head.json │ │ ├── navbar.json │ │ ├── plugins.json │ │ ├── providers.json │ │ ├── search.json │ │ ├── share.json │ │ ├── sidebar.json │ │ └── widgets.json │ ├── config.json │ ├── donate │ │ └── .gitkeep │ ├── misc │ │ └── .gitkeep │ ├── plugin │ │ ├── animejs.json │ │ ├── back_to_top.json │ │ └── pjax.json │ ├── search │ │ └── .gitkeep │ ├── share │ │ └── .gitkeep │ └── widget │ │ └── profile.json ├── style │ ├── article.styl │ ├── base.styl │ ├── button.styl │ ├── card.styl │ ├── codeblock.styl │ ├── donate.styl │ ├── footer.styl │ ├── helper.styl │ ├── navbar.styl │ ├── pagination.styl │ ├── plugin.styl │ ├── responsive.styl │ ├── search.styl │ ├── timeline.styl │ └── widget.styl └── util │ └── console.js ├── languages ├── de.yml ├── en.yml ├── es.yml ├── fr.yml ├── id.yml ├── it.yml ├── ja.yml ├── ko.yml ├── pl.yml ├── pt-BR.yml ├── ru.yml ├── sv.yml ├── tk.yml ├── tr.yml ├── vn.yml ├── zh-CN.yml └── zh-TW.yml ├── layout ├── archive.jsx ├── categories.jsx ├── category.jsx ├── comment │ └── .gitkeep ├── common │ ├── article.jsx │ ├── comment.jsx │ ├── donates.jsx │ ├── footer.jsx │ ├── head.jsx │ ├── navbar.jsx │ ├── plugins.jsx │ ├── scripts.jsx │ ├── search.jsx │ ├── share.jsx │ └── widgets.jsx ├── donate │ └── .gitkeep ├── index.jsx ├── layout.jsx ├── misc │ └── .gitkeep ├── page.jsx ├── plugin │ ├── animejs.jsx │ ├── back_to_top.jsx │ └── pjax.jsx ├── post.jsx ├── search │ └── .gitkeep ├── share │ └── .gitkeep ├── tag.jsx ├── tags.jsx └── widget │ └── profile.jsx ├── package.json ├── scripts └── index.js └── source ├── css ├── cyberpunk.styl ├── default.styl └── style.styl ├── img ├── avatar.png ├── favicon.svg ├── logo.svg ├── og_image.png ├── razor-bottom-black.svg └── razor-top-black.svg └── js ├── .eslintrc.json ├── animation.js ├── back_to_top.js ├── column.js ├── main.js └── pjax.js /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules/ -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "hexo", 4 | "plugin:react/recommended", 5 | "plugin:json/recommended" 6 | ], 7 | "settings": { 8 | "node": { 9 | "tryExtensions": [ 10 | ".js", 11 | ".jsx", 12 | ".json" 13 | ] 14 | }, 15 | "react": { 16 | "version": "16.0" 17 | } 18 | }, 19 | "parserOptions": { 20 | "ecmaFeatures": { 21 | "jsx": true 22 | }, 23 | "sourceType": "module", 24 | "ecmaVersion": "latest" 25 | }, 26 | "plugins": [ 27 | "react" 28 | ], 29 | "rules": { 30 | "react/jsx-uses-vars": "error", 31 | "indent": [ 32 | "error", 33 | 4, 34 | { 35 | "SwitchCase": 1 36 | } 37 | ], 38 | "react/no-unknown-property": [ 39 | "error", 40 | { 41 | "ignore": [ 42 | "class", 43 | "onclick", 44 | "onload", 45 | "onsubmit", 46 | "crossorigin" 47 | ] 48 | } 49 | ], 50 | "react/react-in-jsx-scope": [ 51 | "off" 52 | ], 53 | "react/prop-types": [ 54 | "off" 55 | ], 56 | "react/display-name": [ 57 | "off" 58 | ], 59 | "react/jsx-key": [ 60 | "off" 61 | ], 62 | "react/jsx-no-target-blank": [ 63 | "error", 64 | { 65 | "allowReferrer": true 66 | } 67 | ] 68 | } 69 | } -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/Bug反馈.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug反馈 3 | about: 请按照模板填写Bug反馈,否则你的Issue可能会被直接关闭。 4 | title: [Bug] 问题概述 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | > 确保你在提交Bug反馈之前仔细阅读了[Hexo文档](https://hexo.io/zh-cn/),[Icarus用户指南](https://ppoffice.github.io/hexo-theme-icarus/tags/Icarus%E7%94%A8%E6%88%B7%E6%8C%87%E5%8D%97/),和[GitHub issues](https://github.com/ppoffice/hexo-theme-icarus/issues)来了解你的问题是否已经被他人提出过。 11 | 12 | **Bug描述** 13 | 简洁清晰地描述你遇到的Bug是什么。 14 | 15 | **系统与环境** 16 | 列出你的Hexo和Icarus的版本和配置。 17 | 18 | - Hexo,操作系统,和Node.js的版本(使用`hexo version`命令来查看) 19 | - 站点配置文件`_config.yml` 20 | - 主题配置文件`_config.icarus.yml`或`themes/icarus/_config.yml` 21 | - 其他额外的配置文件(文章front-matter,`_config.post.yml`,或`_config.page.yml`) 22 | - 浏览器版本(如Firefox 70.0,Chrome Android 80.0) 23 | 24 | **复现方式** 25 | 列出复现这个Bug的步骤,如: 26 | 27 | 1. 访问‘...’ 28 | 2. 点击’...‘ 29 | 3. 下拉到‘...’ 30 | 4. 出现‘...’的错误 31 | 32 | **期望行为** 33 | 简洁清晰地描述没有这个情况下你期望得到的结果。 34 | 35 | **截图** 36 | 如果可以的话,请附上几张截图来帮助说明你遇到的问题。 37 | 38 | **额外上下文** 39 | 附上与问题有关的其他上下文信息。 40 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug Report 3 | about: Please follow this template if you are reporting a bug, or your issue may be closed without further notice. 4 | title: [Bug] Bug summary 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | > Make sure you go through the [Hexo docs](https://hexo.io), [Icarus user manual](https://ppoffice.github.io/hexo-theme-icarus/tags/Icarus-User-Guide/), and [GitHub issues](https://github.com/ppoffice/hexo-theme-icarus/issues) to see if the bug you are reporting has been already addressed by others. 11 | 12 | **Describe the bug** 13 | A clear and concise description of what the bug is. 14 | 15 | **System and Environment** 16 | The version and configuration of Hexo and Icarus. 17 | 18 | - Hexo, OS, and node version (use `hexo version` command to view these information) 19 | - Site configuration file `_config.yml` 20 | - Theme configuration file `_config.icarus.yml` or `themes/icarus/_config.yml` 21 | - Any additional theme configuration files (post front-matter, `_config.post.yml`, or `_config.page.yml`) 22 | - Browser and version (e.g., Firefox 70.0, Chrome Android 80.0) 23 | 24 | **To Reproduce** 25 | Steps to reproduce the behavior, such as: 26 | 27 | 1. Go to '...' 28 | 2. Click on '...' 29 | 3. Scroll down to '...' 30 | 4. '...' error appears 31 | 32 | **Expected behavior** 33 | A clear and concise description of what you expected to happen. 34 | 35 | **Screenshots** 36 | If applicable, add screenshots to help explain your problem. 37 | 38 | **Additional context** 39 | Add any other context about the problem here. 40 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: GitHub Discussions 4 | url: https://github.com/ppoffice/hexo-theme-icarus/discussions 5 | about: Redirect your Icarus usage questions to here. 6 | - name: GitHub讨论组 7 | url: https://github.com/ppoffice/hexo-theme-icarus/discussions 8 | about: 与Icarus使用相关的问题请转至这里。 9 | - name: Bug Report 10 | url: https://github.com/ppoffice/hexo-theme-icarus/issues/new?template=bug_report.md 11 | about: Please follow this template if you are reporting a bug, or your issue may be closed without further notice. 12 | - name: Bug反馈 13 | url: https://github.com/ppoffice/hexo-theme-icarus/issues/new?template=Bug反馈.md 14 | about: 请按照模板填写Bug反馈,否则你的Issue可能会被直接关闭。 15 | - name: Feature Request 16 | url: https://github.com/ppoffice/hexo-theme-icarus/issues/new?template=feature_request.md 17 | about: Please follow this template if you are requesting a new feature, or your issue may be closed without further notice. 18 | - name: 功能建议 19 | url: https://github.com/ppoffice/hexo-theme-icarus/issues/new?template=功能建议.md 20 | about: 请按照模板填写功能建议,否则你的Issue可能会被直接关闭。 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature Request 3 | about: Please follow this template if you are requesting a new feature, or your issue may be closed without further notice. 4 | title: [FEAT] Feature request summary 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | > Make sure you go through the [Hexo docs](https://hexo.io), [Icarus user manual](https://ppoffice.github.io/hexo-theme-icarus/tags/Icarus-User-Guide/), and [GitHub issues](https://github.com/ppoffice/hexo-theme-icarus/issues) to see if the feature you are requesting has been already addressed by others. 11 | 12 | **Is your feature request related to a problem? Please describe.** 13 | 14 | A clear and concise description of what the problem is (e.g., I'm always frustrated when [...]). 15 | 16 | **Describe the solution you'd like** 17 | 18 | A clear and concise description of what you want to happen. 19 | 20 | **Describe alternatives you've considered** 21 | 22 | A clear and concise description of any alternative solutions or features you've considered. 23 | 24 | **Additional context** 25 | 26 | Add any other context or screenshots about the feature request here. 27 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/功能建议.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 功能建议 3 | about: 请按照模板填写功能建议,否则你的Issue可能会被直接关闭。 4 | title: [FEAT] 功能建议概述 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | > 确保你在提交功能建议之前仔细阅读了[Hexo文档](https://hexo.io/zh-cn/),[Icarus用户指南](https://ppoffice.github.io/hexo-theme-icarus/tags/Icarus%E7%94%A8%E6%88%B7%E6%8C%87%E5%8D%97/),和[GitHub issues](https://github.com/ppoffice/hexo-theme-icarus/issues)来了解你的建议是否已经被他人提出过。 11 | 12 | **你的功能建议与某个使用问题相关么?请详述。** 13 | 14 | 简洁清晰地描述你遇到的问题是什么(如:我在使用...的时候遇到了...)。 15 | 16 | **描述你想要的解决方案** 17 | 18 | 简洁清晰地描述你想要的解决方案可以达到的效果。 19 | 20 | **描述你考虑过的替代办法** 21 | 22 | 简洁清晰地描述你考虑过的替代解决方案或是新功能。 23 | 24 | **额外上下文** 25 | 26 | 附上与功能请求有关的其他上下文信息或者截图。 27 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE/pull_request_template.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Pull Request 3 | about: Suggest a code change to this project. 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Note 0** 11 | Please review [the contributing guide](https://github.com/ppoffice/hexo-theme-icarus/blob/master/CONTRIBUTING.md) before making this pull request. 12 | 13 | **Note 1** 14 | 15 | Please break up your pull request into multiple smaller requests if it contains multiple bug fixes 16 | or new features. 17 | Each pull request should have one bug fix or new feature only for better code maintainability. 18 | 19 | **Note 2** 20 | 21 | Many components of this theme, including core functions, some Hexo extensions, widgets and plugins, 22 | have been moved to [ppoffice/hexo-component-inferno](https://github.com/ppoffice/hexo-component-inferno). 23 | Please make a pull request to that repository instead of this one if your changes are related to 24 | the above components. 25 | 26 | **Detailed description** 27 | 28 | > Please use the [Icarus issue template](https://github.com/ppoffice/hexo-theme-icarus/blob/master/.github/ISSUE_TEMPLATE/bug_report.md) if it is a bug fix. 29 | 30 | > Please use the [Icarus feature request template](https://github.com/ppoffice/hexo-theme-icarus/blob/master/.github/ISSUE_TEMPLATE/feature_request.md) if it is a bug fix. 31 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: 'npm' 4 | directory: '/' 5 | schedule: 6 | interval: 'daily' 7 | -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | # Number of days of inactivity before an issue becomes stale 2 | daysUntilStale: 14 3 | # Number of days of inactivity before a stale issue is closed 4 | daysUntilClose: 7 5 | # Issues with these labels will never be considered stale 6 | exemptLabels: 7 | - bug:core 8 | - bug:extension 9 | - bug:general 10 | - feature:core 11 | - feature:extension 12 | - feature:general 13 | - tutorial:v1 14 | - tutorial:v2 15 | - tutorial:v3 16 | - tutorial:v4 17 | - tutorial:v5 18 | - tutorial:v6 19 | - tutorial:v7 20 | - tutorial:v8 21 | - tutorial:v9 22 | - Gitalk 23 | - utterances 24 | # Label to use when marking an issue as stale 25 | staleLabel: wontfix 26 | # Comment to post when marking an issue as stale. Set to `false` to disable 27 | markComment: > 28 | This issue has been automatically marked as stale because it has not had 29 | recent activity. It will be closed if no further activity occurs. Thank you 30 | for your contributions. 31 | # Comment to post when closing a stale issue. Set to `false` to disable 32 | closeComment: false -------------------------------------------------------------------------------- /.github/workflows/github-release.yml: -------------------------------------------------------------------------------- 1 | name: GitHub Release 2 | 3 | on: 4 | push: 5 | tags: 6 | - '*' 7 | 8 | jobs: 9 | publish: 10 | runs-on: ubuntu-latest 11 | permissions: 12 | contents: write 13 | steps: 14 | - uses: actions/checkout@v3 15 | - uses: ncipollo/release-action@v1 16 | with: 17 | tag: ${{ github.ref }} 18 | name: ${{ github.ref }} 19 | draft: true 20 | prerelease: false 21 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: Code Linting 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | lint: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v3 10 | - uses: actions/setup-node@v3 11 | with: 12 | node-version: latest 13 | - run: npm install 14 | - run: npm run lint 15 | -------------------------------------------------------------------------------- /.github/workflows/npm-publish.yml: -------------------------------------------------------------------------------- 1 | name: Node.js Package 2 | 3 | on: 4 | release: 5 | types: [published] 6 | 7 | jobs: 8 | publish: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v3 12 | - uses: actions/setup-node@v3 13 | with: 14 | node-version: 14 15 | registry-url: https://registry.npmjs.org/ 16 | - run: npm publish 17 | env: 18 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 19 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | test: 7 | runs-on: ubuntu-latest 8 | strategy: 9 | matrix: 10 | node-version: [14, latest] 11 | fail-fast: false 12 | steps: 13 | - uses: actions/checkout@v3 14 | with: 15 | repository: hexojs/hexo-starter 16 | ref: dcd18588de8f5c1bcc689c101e2f21726c11c4d6 # pin blog dependencies to support node.js 14 17 | - uses: actions/checkout@v3 18 | with: 19 | path: themes/icarus 20 | - uses: actions/checkout@v3 21 | with: 22 | repository: SukkaLab/hexo-many-posts 23 | path: source/_posts/hexo-many-posts 24 | - uses: actions/setup-node@v3 25 | with: 26 | node-version: ${{ matrix.node-version }} 27 | - uses: actions/cache@v3 28 | with: 29 | path: node_modules 30 | key: npm-cache 31 | restore-keys: npm-cache 32 | - run: npm install 33 | - run: > 34 | npm install $(node -e "const deps=require('./themes/icarus/package.json').dependencies; 35 | console.log(Object.keys(deps).map(key=>key+'@'+deps[key]).join(' '));") 36 | - run: npm install hexo-tag-embed 37 | - run: npx hexo config theme icarus 38 | - run: time NODE_ENV=production npx hexo g -b 39 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.test 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | 78 | # Next.js build output 79 | .next 80 | 81 | # Nuxt.js build / generate output 82 | .nuxt 83 | dist 84 | 85 | # Gatsby files 86 | .cache/ 87 | # Comment in the public line in if your project uses Gatsby and not Next.js 88 | # https://nextjs.org/blog/next-9-1#public-directory-support 89 | # public 90 | 91 | # vuepress build output 92 | .vuepress/dist 93 | 94 | # Serverless directories 95 | .serverless/ 96 | 97 | # FuseBox cache 98 | .fusebox/ 99 | 100 | # DynamoDB Local files 101 | .dynamodb/ 102 | 103 | # TernJS port file 104 | .tern-port 105 | 106 | # Stores VSCode versions used for testing VSCode extensions 107 | .vscode-test 108 | 109 | _config*.yml* 110 | yarn.lock 111 | 112 | # Intellij Idea config file 113 | .idea 114 | *.ipr 115 | *.iws 116 | *.iml -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .github/ 2 | .eslintignore 3 | .eslintrc.json 4 | .travis.yml 5 | yarn.lock 6 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | ## Code styles 4 | 5 | Please refer to the [.eslintrc.json](https://github.com/ppoffice/hexo-theme-icarus/blob/master/.eslintrc.json). 6 | You can also use `npm run lint` or `yarn lint` to fix code style issues. 7 | 8 | ## Project versioning 9 | 10 | We use [SemVer](http://semver.org/) for versioning. 11 | Any changes to the code base should not be released using an existing version. 12 | 13 | ## Commit message format 14 | 15 | The commit message should follow the [Bluejava commit message format](https://github.com/bluejava/git-commit-guide). 16 | The supported scopes are: 17 | 18 | - **core** for changes related to Hexo extensions and theme-specific functions 19 | - **comment** for comment plugin layout, schema, style, or script changes 20 | - **share** for share plugin layout, schema, style, or script changes 21 | - **donate** for donation plugin layout, schema, style, or script changes 22 | - **search** for search plugin layout, schema, style, or script changes 23 | - **widget** for widget layout, schema, style, or script changes 24 | - **plugin** for other plugin layout, schema, style, or script changes 25 | - **i18n** for adding or updating translations 26 | - **test** for testing or linting-related commits 27 | - **build** for build scripts, CI, other development or deployment related commits 28 | - use __\*__ or leave empty to refer to commits that do not have a clear scope 29 | 30 | ## Submit changes 31 | 32 | 1. Fork this repository, make changes to it, and run it against some actual Hexo sites to see if 33 | anything is broken. 34 | You should also run `npm run lint` or `yarn lint` to find and fix any code formatting issue. 35 | 2. Submit a pull request to our repository. Please make sure you followed the instructions 36 | above. 37 | 3. We will review the pull request regularly and inform you of our questions and any changes 38 | that need to be made before we can merge your pull request. 39 | 4. We expect your response within two weeks, after which your pull request may be closed if 40 | no activity is shown. 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2020 PPOffice 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | <p align="center" class="mb-2"> 2 | <img class="not-gallery-item" height="48" src="https://ppoffice.github.io/hexo-theme-icarus/img/logo.svg"> 3 | <br> A simple, delicate, and modern theme for the static site generator Hexo. 4 | <br> 5 | <a href="https://ppoffice.github.io/hexo-theme-icarus/">Preview</a> | 6 | <a href="https://ppoffice.github.io/hexo-theme-icarus/categories/">Documentation</a> | 7 | <a href="https://github.com/ppoffice/hexo-theme-icarus/discussions">Discuss on GitHub</a> 8 | <br> 9 | </p> 10 | 11 |  12 | 13 | ## :cd: Installation 14 | 15 | ```shell 16 | $ npm install hexo-theme-icarus 17 | $ hexo config theme icarus 18 | ``` 19 | 20 | Please refer to [Getting Started with Icarus](https://ppoffice.github.io/hexo-theme-icarus/uncategorized/getting-started-with-icarus/) 21 | for more details. 22 | 23 | ## :gift: Features 24 | 25 | ### Cyberpunk Theme Variant 26 | 27 | Tap into the future cyber world with the newly added Cyberpunk theme variant. 28 | Inspired by [Cyberpunk 2077](https://www.cyberpunk.net). 29 | 30 |  31 | 32 | ### Extensive Plugin Support 33 | 34 | Icarus includes plentiful search, comment, sharing and other plugins out of the box that makes your 35 | blog feature-rich and powerful. 36 | 37 | **[Comment](https://ppoffice.github.io/hexo-theme-icarus/categories/Plugins/Comment/)** 38 | 39 | Changyan · Disqus · DisqusJS · Facebook · Gitalk · Gitment · 40 | Isso · LiveRe · Utterance · Valine 41 | 42 | **[Donate Button](https://ppoffice.github.io/hexo-theme-icarus/categories/Plugins/Donation/)** 43 | 44 | Afdian.net · Alipay · Buy me a coffee · Patreon · Paypal · Wecat 45 | 46 | **[Search](https://ppoffice.github.io/hexo-theme-icarus/categories/Plugins/Search/)** 47 | 48 | Algolia · Baidu · Google CSE · Insight 49 | 50 | **[Share](https://ppoffice.github.io/hexo-theme-icarus/categories/Plugins/Share/)** 51 | 52 | AddThis · AddToAny · Baidu Share · Share.js · ShareThis 53 | 54 | **[Widgets](https://ppoffice.github.io/hexo-theme-icarus/categories/Widgets/)** 55 | 56 | Google Adsense · Archives · Categories · External Site Links · 57 | Recent Posts · Google Feedburner · Tags · Table of Contents 58 | 59 | **[Analytics](https://ppoffice.github.io/hexo-theme-icarus/Plugins/Analytics/icarus-user-guide-web-analytics-plugins/)** 60 | 61 | Baidu Statistics · Bing Webmaster · BuSuanZi Web Counter · CNZZ Statistics · 62 | Google Analytics · Hotjar · StatCounter · Twitter Conversion Tracking 63 | 64 | **[Other Plugins](https://ppoffice.github.io/hexo-theme-icarus/categories/Plugins/)** 65 | 66 | Cookie Consent · LightGallery · Justified Gallery · KaTeX · MathJax · 67 | Oudated Browser · Page Loading Animations 68 | 69 | ### Colorful Code Highlight 70 | 71 | Icarus directly import stylesheets from the [highlight.js](https://highlightjs.org/) package and makes more than 72 | 90 code highlight themes available to you. 73 | 74 | <table> 75 | <tr> 76 | <th>Atom One Light</th> 77 | <th>Monokai</th> 78 | <th>Kimbie Dark</th> 79 | </tr> 80 | <tr> 81 | <td><img width="266" src="https://ppoffice.github.io/hexo-theme-icarus/gallery/code-highlight/atom-one-light.png?2"></td> 82 | <td><img width="266" src="https://ppoffice.github.io/hexo-theme-icarus/gallery/code-highlight/monokai.png?2"></td> 83 | <td><img width="266" src="https://ppoffice.github.io/hexo-theme-icarus/gallery/code-highlight/kimbie-dark.png?2"></td> 84 | </tr> 85 | </table> 86 | 87 | ### Flexible Theme Configuration 88 | 89 | Icarus allows you to configure your site on a per-page or per-layout basis. 90 | 91 | <div> 92 | <table> 93 | <tr> 94 | <th>_config.yml</th> 95 | <th>post.md</th> 96 | <th>_config.page.yml</th> 97 | </tr> 98 | <tr> 99 | <td> 100 | <pre>widgets: 101 | - type: profile 102 | position: left 103 | - type: recent_posts 104 | position: right</pre> 105 | </td> 106 | <td> 107 | <pre>widgets: 108 | - type: profile 109 | position: left 110 | - type: recent_posts 111 | position: left</pre> 112 | </td> 113 | <td> 114 | <pre>widgets: null 115 | 116 | 117 | 118 | </pre> 119 | </td> 120 | </tr> 121 | <tr> 122 | <td><img width="266" src="https://ppoffice.github.io/hexo-theme-icarus/gallery/screenshots/default-config.png"></td> 123 | <td><img width="266" src="https://ppoffice.github.io/hexo-theme-icarus/gallery/screenshots/post-config.png"></td> 124 | <td><img width="266" src="https://ppoffice.github.io/hexo-theme-icarus/gallery/screenshots/layout-config.png"></td> 125 | </tr> 126 | </table> 127 | </div> 128 | 129 | ### Responsive Layout 130 | 131 | Give your audiences best viewing experience with Icarus's mobile-friendly responsive layout. 132 | 133 |  134 | 135 | ## :hammer: Development 136 | 137 | This project is built with 138 | 139 | - [Hexo](https://hexo.io/) 140 | - [Inferno.js](https://infernojs.org/) 141 | - [Stylus](https://stylus-lang.com/) 142 | - [Bulma](https://bulma.io/) 143 | 144 | Please refer to the [documentation](https://ppoffice.github.io/hexo-theme-icarus/categories/) and 145 | [contributing guide](https://github.com/ppoffice/hexo-theme-icarus/blob/master/CONTRIBUTING.md) for implementation details. 146 | 147 | ## :tada: Contribute 148 | 149 | If you feel like to help us build a better Icarus, you can 150 | 151 | :black_nib: [Submit a tutorial](https://github.com/ppoffice/hexo-theme-icarus/new/site/source/_posts) | 152 | :earth_asia: [Add a translation](https://github.com/ppoffice/hexo-theme-icarus/tree/master/languages) | 153 | :triangular_flag_on_post: [Report a bug](https://github.com/ppoffice/hexo-theme-icarus/issues) | 154 | :electric_plug: [Suggest a new feature](https://github.com/ppoffice/hexo-theme-icarus/pulls) 155 | 156 | ## :memo: License 157 | 158 | This project is licensed under the MIT License - see the [LICENSE](https://github.com/ppoffice/hexo-theme-icarus/blob/master/LICENSE) file for details. 159 | -------------------------------------------------------------------------------- /include/config.js: -------------------------------------------------------------------------------- 1 | /* eslint no-process-exit: "off" */ 2 | const fs = require('fs'); 3 | const path = require('path'); 4 | const util = require('util'); 5 | const crypto = require('crypto'); 6 | const createLogger = require('hexo-log'); 7 | const yaml = require('hexo-component-inferno/lib/util/yaml'); 8 | const { Migrator } = require('hexo-component-inferno/lib/core/migrate'); 9 | const { SchemaLoader } = require('hexo-component-inferno/lib/core/schema'); 10 | const { yellow } = require('./util/console'); 11 | 12 | const logger = createLogger.default(); 13 | 14 | function loadThemeConfig(hexo, cfgPaths) { 15 | const configs = cfgPaths.map(cfgPath => fs.readFileSync(cfgPath)) 16 | .map(cfgPath => yaml.parse(cfgPath)); 17 | return Object.assign({}, ...configs, hexo.config.theme_config); 18 | } 19 | 20 | function generateThemeConfigFile(schema, cfgPath) { 21 | const defaultValue = schema.getDefaultValue(); 22 | fs.writeFileSync(cfgPath, defaultValue.toYaml()); 23 | } 24 | 25 | function hashConfigFile(cfgPath) { 26 | const content = fs.readFileSync(cfgPath); 27 | return crypto.createHash('md5').update(content).digest('hex'); 28 | } 29 | 30 | function checkConfig(hexo) { 31 | if (!process.argv.includes('--icarus-dont-check-config')) { 32 | logger.info('=== Checking theme configurations ==='); 33 | 34 | const siteCfgFile = path.join(hexo.base_dir, '_config.yml'); 35 | const themeSiteCfg = path.join(hexo.base_dir, '_config.icarus.yml'); 36 | const themeDirCfg = path.join(hexo.theme_dir, '_config.yml'); 37 | const themeCfgPaths = [themeDirCfg, themeSiteCfg].filter(cfgPath => fs.existsSync(cfgPath)); 38 | const themeSiteCfgExample = themeSiteCfg + '.example'; 39 | 40 | const schemaDir = path.join(hexo.theme_dir, 'include/schema/'); 41 | const loader = SchemaLoader.load(require(path.join(schemaDir, 'config.json')), schemaDir); 42 | const schema = loader.getSchema('/config.json'); 43 | 44 | if (!process.argv.includes('--icarus-dont-generate-config')) { 45 | if (!themeCfgPaths.length) { 46 | logger.warn('None of the following configuration files is found:'); 47 | logger.warn(`- ${yellow(themeSiteCfg)}`); 48 | logger.warn(`- ${yellow(themeDirCfg)}`); 49 | logger.info('Generating theme configuration file...'); 50 | generateThemeConfigFile(schema, themeSiteCfg); 51 | themeCfgPaths.push(themeSiteCfg); 52 | logger.info(`${yellow(themeSiteCfg)} created successfully.`); 53 | logger.info('To skip configuration generation, use "--icarus-dont-generate-config".'); 54 | } 55 | } 56 | 57 | let cfg = loadThemeConfig(hexo, themeCfgPaths); 58 | 59 | if (!process.argv.includes('--icarus-dont-upgrade-config')) { 60 | const migrator = new Migrator(require(path.join(hexo.theme_dir, 'include/migration/head'))); 61 | if (cfg.version && migrator.isOudated(cfg.version)) { 62 | logger.warn(`Your theme configuration is outdated (${cfg.version} < ${migrator.getLatestVersion()}).`); 63 | logger.info('To skip the configuration upgrade, use "--icarus-dont-upgrade-config".'); 64 | 65 | logger.info('Backing up theme configuration files...'); 66 | for (const cfgPath of themeCfgPaths) { 67 | const backupPath = cfgPath + '.' + hashConfigFile(cfgPath); 68 | const relCfgPath = path.relative(hexo.base_dir, cfgPath); 69 | const relBackupPath = path.relative(hexo.base_dir, backupPath); 70 | fs.renameSync(cfgPath, backupPath); 71 | logger.info(`${yellow(relCfgPath)} => ${yellow(relBackupPath)}`); 72 | } 73 | 74 | logger.info('Upgrading theme configurations...'); 75 | cfg = migrator.migrate(cfg); 76 | fs.writeFileSync(themeSiteCfg, yaml.stringify(cfg)); 77 | logger.info(`Theme configurations are written to ${yellow(themeSiteCfg)}.`); 78 | 79 | generateThemeConfigFile(schema, themeSiteCfgExample); 80 | logger.info(`Example configurations is at ${yellow(themeSiteCfgExample)}.`); 81 | } 82 | } 83 | 84 | const validation = schema.validate(cfg); 85 | if (validation !== true) { 86 | logger.warn('Theme configurations failed one or more checks.'); 87 | logger.warn('Icarus may still run, but you will encounter unexcepted results.'); 88 | logger.warn('Here is some information for you to correct the configuration file.'); 89 | logger.warn(util.inspect(validation)); 90 | } 91 | 92 | const rootCfg = yaml.parse(fs.readFileSync(siteCfgFile)); 93 | if (rootCfg.theme_config) { 94 | logger.warn(`"theme_config" found in ${yellow(siteCfgFile)}.`); 95 | logger.warn(`Please remove theme configurations from ${yellow(siteCfgFile)}.`); 96 | } 97 | } 98 | } 99 | 100 | module.exports = hexo => { 101 | try { 102 | checkConfig(hexo); 103 | } catch (e) { 104 | logger.error(e); 105 | logger.error('Theme configuration checking failed.'); 106 | logger.info('You may use \'--icarus-dont-check-config\' to skip configuration checking.'); 107 | process.exit(-1); 108 | } 109 | }; 110 | -------------------------------------------------------------------------------- /include/dependency.js: -------------------------------------------------------------------------------- 1 | /* eslint no-process-exit: "off" */ 2 | const semver = require('semver'); 3 | const createLogger = require('hexo-log'); 4 | const packageInfo = require('../package.json'); 5 | const { yellow, red, green } = require('./util/console'); 6 | 7 | const logger = createLogger.default(); 8 | 9 | module.exports = hexo => { 10 | function checkDependency(name, reqVer) { 11 | try { 12 | require.resolve(name); 13 | const version = require(name + '/package.json').version; 14 | if (!semver.satisfies(version, reqVer)) { 15 | logger.error(`Package ${yellow(name)}'s version (${yellow(version)}) does not satisfy the required version (${red(reqVer)}).`); 16 | return false; 17 | } 18 | return true; 19 | } catch (e) { 20 | logger.error(`Package ${yellow(name)} is not installed.`); 21 | } 22 | return false; 23 | } 24 | 25 | logger.info('=== Checking package dependencies ==='); 26 | const dependencies = Object.assign({}, packageInfo.dependencies); 27 | const missingDeps = Object.keys(dependencies) 28 | .filter(name => !checkDependency(name, dependencies[name])); 29 | if (missingDeps && missingDeps.length) { 30 | logger.error('Please install the missing dependencies your Hexo site root directory:'); 31 | logger.error(green('npm install --save ' + missingDeps.map(name => `${name}@${dependencies[name]}`).join(' '))); 32 | logger.error('or:'); 33 | logger.error(green('yarn add ' + missingDeps.map(name => `${name}@${dependencies[name]}`).join(' '))); 34 | process.exit(-1); 35 | } 36 | }; 37 | -------------------------------------------------------------------------------- /include/migration/head.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./v5_v5.1'); 2 | -------------------------------------------------------------------------------- /include/migration/v2_v3.js: -------------------------------------------------------------------------------- 1 | const createLogger = require('hexo-log'); 2 | const deepmerge = require('deepmerge'); 3 | const Migration = require('hexo-component-inferno/lib/core/migrate').Migration; 4 | 5 | const logger = createLogger.default(); 6 | 7 | module.exports = class extends Migration { 8 | constructor() { 9 | super('3.0.0', null); 10 | } 11 | 12 | upgrade(config) { 13 | const result = deepmerge({}, config); 14 | result.head = { 15 | favicon: config.favicon || null, 16 | canonical_url: config.canonical_url || null, 17 | open_graph: config.open_graph || null, 18 | meta: config.meta || null, 19 | rss: config.rss || null 20 | }; 21 | delete result.favicon; 22 | delete result.canonical_url; 23 | delete result.open_graph; 24 | delete result.meta; 25 | delete result.rss; 26 | 27 | if (result.logo === '/images/logo.svg') { 28 | result.logo = result.logo.replace(/^\/images/, '/img'); 29 | } 30 | 31 | if (result.head.favicon === '/img/favicon.svg') { 32 | result.head.favicon = result.head.favicon.replace(/^\/images/, '/img'); 33 | } 34 | 35 | if (result.search && Object.prototype.hasOwnProperty.call(result.search, 'type')) { 36 | switch (result.search.type) { 37 | case 'google-cse': 38 | result.search.type = 'google_cse'; 39 | break; 40 | } 41 | } 42 | 43 | if (result.comment && Object.prototype.hasOwnProperty.call(result.comment, 'type')) { 44 | switch (result.comment.type) { 45 | case 'changyan': 46 | result.comment.app_id = config.comment.appid; 47 | delete result.comment.appid; 48 | break; 49 | } 50 | } 51 | 52 | if (Array.isArray(result.donate) && result.donate.length) { 53 | result.donates = result.donate; 54 | delete result.donate; 55 | } 56 | 57 | if (Array.isArray(result.widgets) && result.widgets.length) { 58 | for (const widget of result.widgets) { 59 | if (Object.prototype.hasOwnProperty.call(widget, 'type')) { 60 | switch (widget.type) { 61 | case 'archive': 62 | widget.type = 'archives'; 63 | break; 64 | case 'category': 65 | widget.type = 'categories'; 66 | break; 67 | case 'tag': 68 | widget.type = 'tags'; 69 | break; 70 | case 'tagcloud': 71 | logger.warn('The tagcloud widget has been removed from Icarus in version 3.0.0.'); 72 | logger.warn('Please remove it from your configuration file.'); 73 | break; 74 | } 75 | } 76 | } 77 | } 78 | 79 | if (result.plugins && typeof result.plugins === 'object') { 80 | for (const name in result.plugins) { 81 | switch (name) { 82 | case 'outdated-browser': 83 | result.plugins.outdated_browser = result.plugins[name]; 84 | delete result.plugins[name]; 85 | break; 86 | case 'back-to-top': 87 | result.plugins.back_to_top = result.plugins[name]; 88 | delete result.plugins[name]; 89 | break; 90 | case 'baidu-analytics': 91 | result.plugins.baidu_analytics = result.plugins[name]; 92 | delete result.plugins[name]; 93 | break; 94 | case 'google-analytics': 95 | result.plugins.google_analytics = result.plugins[name]; 96 | delete result.plugins[name]; 97 | break; 98 | } 99 | } 100 | } 101 | 102 | return result; 103 | } 104 | }; 105 | -------------------------------------------------------------------------------- /include/migration/v3_v4.js: -------------------------------------------------------------------------------- 1 | const Migration = require('hexo-component-inferno/lib/core/migrate').Migration; 2 | 3 | module.exports = class extends Migration { 4 | constructor() { 5 | super('4.0.0', null); 6 | } 7 | 8 | upgrade(config) { 9 | if (typeof config.article === 'object') { 10 | delete config.article.thumbnail; 11 | } 12 | return config; 13 | } 14 | }; 15 | -------------------------------------------------------------------------------- /include/migration/v4_v5.js: -------------------------------------------------------------------------------- 1 | const Migration = require('hexo-component-inferno/lib/core/migrate').Migration; 2 | 3 | module.exports = class extends Migration { 4 | constructor() { 5 | super('5.0.0', null); 6 | } 7 | 8 | upgrade(config) { 9 | return config; 10 | } 11 | }; 12 | -------------------------------------------------------------------------------- /include/migration/v5_v5.1.js: -------------------------------------------------------------------------------- 1 | const Migration = require('hexo-component-inferno/lib/core/migrate').Migration; 2 | 3 | module.exports = class extends Migration { 4 | constructor() { 5 | super('5.1.0', null); 6 | } 7 | 8 | upgrade(config) { 9 | // Upgrade Waline configurations from v1 to v2. 10 | const comment = config.comment || {}; 11 | const renamedOptions = { 12 | 'visitor': 'pageview', 13 | 'uploadImage': 'image_uploader', 14 | 'highlight': 'highlighter', 15 | 'math': 'tex_renderer' 16 | }; 17 | if (comment.type === 'waline') { 18 | for (const option in renamedOptions) { 19 | if (typeof comment[option] !== 'undefined') { 20 | if (typeof comment[renamedOptions[option]] === 'undefined') { 21 | comment[renamedOptions[option]] = comment[option]; 22 | } 23 | delete comment[option]; 24 | } 25 | } 26 | } 27 | return config; 28 | } 29 | }; 30 | -------------------------------------------------------------------------------- /include/register.js: -------------------------------------------------------------------------------- 1 | const createLogger = require('hexo-log'); 2 | 3 | const logger = createLogger.default(); 4 | 5 | module.exports = hexo => { 6 | logger.info('=== Registering Hexo extensions ==='); 7 | require('hexo-component-inferno/lib/hexo/filter/locals')(hexo); 8 | require('hexo-component-inferno/lib/hexo/generator/assets')(hexo); 9 | require('hexo-component-inferno/lib/hexo/generator/insight')(hexo); 10 | require('hexo-component-inferno/lib/hexo/generator/categories')(hexo); 11 | require('hexo-component-inferno/lib/hexo/generator/category')(hexo); 12 | require('hexo-component-inferno/lib/hexo/generator/manifest')(hexo); 13 | require('hexo-component-inferno/lib/hexo/generator/tags')(hexo); 14 | require('hexo-component-inferno/lib/hexo/helper/cdn')(hexo); 15 | require('hexo-component-inferno/lib/hexo/helper/page')(hexo); 16 | require('hexo-component-inferno/lib/hexo/tag/message')(hexo); 17 | require('hexo-component-inferno/lib/hexo/tag/tabs')(hexo); 18 | require('hexo-component-inferno/lib/core/view').init(hexo); 19 | }; 20 | -------------------------------------------------------------------------------- /include/schema/comment/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ppoffice/hexo-theme-icarus/76d8cd0197003fc1ad673741d66a803ebb116f81/include/schema/comment/.gitkeep -------------------------------------------------------------------------------- /include/schema/common/article.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-07/schema#", 3 | "$id": "/common/article.json", 4 | "description": "Article related configurations", 5 | "type": "object", 6 | "properties": { 7 | "highlight": { 8 | "type": "object", 9 | "description": "Code highlight settings", 10 | "properties": { 11 | "theme": { 12 | "type": "string", 13 | "description": "Code highlight themes\nhttps://github.com/highlightjs/highlight.js/tree/master/src/styles", 14 | "default": "atom-one-light", 15 | "nullable": true 16 | }, 17 | "clipboard": { 18 | "type": "boolean", 19 | "description": "Show copy code button", 20 | "default": true, 21 | "nullable": true 22 | }, 23 | "fold": { 24 | "type": "string", 25 | "description": "Default folding status of the code blocks. Can be \"\", \"folded\", \"unfolded\"", 26 | "enum": [ 27 | "", 28 | "folded", 29 | "unfolded" 30 | ], 31 | "default": "unfolded", 32 | "nullable": true 33 | } 34 | }, 35 | "nullable": true 36 | }, 37 | "readtime": { 38 | "type": "boolean", 39 | "description": "Whether to show estimated article reading time", 40 | "default": true, 41 | "nullable": true 42 | }, 43 | "update_time": { 44 | "type": ["boolean", "string"], 45 | "description": "Whether to show updated time. For \"auto\", shows article update time only when page.updated is set and it is different from page.date", 46 | "default": true, 47 | "enum": [true, false, "auto"], 48 | "nullable": true 49 | }, 50 | "licenses": { 51 | "$ref": "/misc/poly_links.json", 52 | "description": "Article licensing block", 53 | "examples": [ 54 | { 55 | "Creative Commons": { 56 | "icon": "fab fa-creative-commons", 57 | "url": "https://creativecommons.org/" 58 | }, 59 | "Attribution": { 60 | "icon": "fab fa-creative-commons-by", 61 | "url": "https://creativecommons.org/licenses/by/4.0/" 62 | }, 63 | "Noncommercial": { 64 | "icon": "fab fa-creative-commons-nc", 65 | "url": "https://creativecommons.org/licenses/by-nc/4.0/" 66 | } 67 | } 68 | ] 69 | } 70 | } 71 | } -------------------------------------------------------------------------------- /include/schema/common/comment.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-07/schema#", 3 | "$id": "/common/comment.json", 4 | "description": "Comment plugin configurations\nhttps://ppoffice.github.io/hexo-theme-icarus/categories/Plugins/Comment/", 5 | "type": "object", 6 | "oneOf": [ 7 | { 8 | "$ref": "/comment/disqus.json" 9 | }, 10 | { 11 | "$ref": "/comment/changyan.json" 12 | }, 13 | { 14 | "$ref": "/comment/disqusjs.json" 15 | }, 16 | { 17 | "$ref": "/comment/facebook.json" 18 | }, 19 | { 20 | "$ref": "/comment/giscus.json" 21 | }, 22 | { 23 | "$ref": "/comment/gitalk.json" 24 | }, 25 | { 26 | "$ref": "/comment/gitment.json" 27 | }, 28 | { 29 | "$ref": "/comment/isso.json" 30 | }, 31 | { 32 | "$ref": "/comment/livere.json" 33 | }, 34 | { 35 | "$ref": "/comment/utterances.json" 36 | }, 37 | { 38 | "$ref": "/comment/valine.json" 39 | }, 40 | { 41 | "$ref": "/comment/waline.json" 42 | }, 43 | { 44 | "$ref": "/comment/twikoo.json" 45 | } 46 | ] 47 | } 48 | -------------------------------------------------------------------------------- /include/schema/common/donates.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-07/schema#", 3 | "$id": "/common/donates.json", 4 | "description": "Donate plugin configurations\nhttps://ppoffice.github.io/hexo-theme-icarus/categories/Plugins/Donation/", 5 | "type": "array", 6 | "items": { 7 | "type": "object", 8 | "oneOf": [ 9 | { 10 | "$ref": "/donate/afdian.json" 11 | }, 12 | { 13 | "$ref": "/donate/alipay.json" 14 | }, 15 | { 16 | "$ref": "/donate/buymeacoffee.json" 17 | }, 18 | { 19 | "$ref": "/donate/patreon.json" 20 | }, 21 | { 22 | "$ref": "/donate/paypal.json" 23 | }, 24 | { 25 | "$ref": "/donate/wechat.json" 26 | } 27 | ] 28 | } 29 | } -------------------------------------------------------------------------------- /include/schema/common/footer.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-07/schema#", 3 | "$id": "/common/footer.json", 4 | "description": "Page footer configurations", 5 | "type": "object", 6 | "properties": { 7 | "copyright": { 8 | "description": "Copyright text", 9 | "type": "string", 10 | "examples": [ 11 | "© 2019", 12 | "© 2019 - 2020", 13 | "© 2019 - 2020, Company Name", 14 | "© 2019 - 2020, Company Name. All rights reserved.", 15 | "粤ICP备12345678号" 16 | ] 17 | }, 18 | "links": { 19 | "$ref": "/misc/poly_links.json", 20 | "description": "Links to be shown on the right of the footer section", 21 | "examples": [ 22 | { 23 | "Creative Commons": { 24 | "icon": "fab fa-creative-commons", 25 | "url": "https://creativecommons.org/" 26 | }, 27 | "Attribution 4.0 International": { 28 | "icon": "fab fa-creative-commons-by", 29 | "url": "https://creativecommons.org/licenses/by/4.0/" 30 | }, 31 | "Download on GitHub": { 32 | "icon": "fab fa-github", 33 | "url": "https://github.com/ppoffice/hexo-theme-icarus" 34 | } 35 | } 36 | ] 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /include/schema/common/head.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-07/schema#", 3 | "$id": "/common/head.json", 4 | "description": "Page metadata configurations", 5 | "type": "object", 6 | "properties": { 7 | "favicon": { 8 | "type": "string", 9 | "description": "URL or path to the website's icon", 10 | "default": "/img/favicon.svg", 11 | "nullable": true 12 | }, 13 | "manifest": { 14 | "$ref": "/misc/manifest.json" 15 | }, 16 | "open_graph": { 17 | "$ref": "/misc/open_graph.json" 18 | }, 19 | "structured_data": { 20 | "$ref": "/misc/structured_data.json" 21 | }, 22 | "meta": { 23 | "$ref": "/misc/meta.json" 24 | }, 25 | "rss": { 26 | "type": "string", 27 | "description": "URL or path to the website's RSS atom.xml", 28 | "nullable": true 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /include/schema/common/navbar.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-07/schema#", 3 | "$id": "/common/navbar.json", 4 | "description": "Page top navigation bar configurations", 5 | "type": "object", 6 | "properties": { 7 | "menu": { 8 | "type": "object", 9 | "description": "Navigation menu items", 10 | "patternProperties": { 11 | ".+": { 12 | "type": "string", 13 | "description": "URL or path of the menu link" 14 | } 15 | }, 16 | "examples": [ 17 | { 18 | "Home": "/", 19 | "Archives": "/archives", 20 | "Categories": "/categories", 21 | "Tags": "/tags", 22 | "About": "/about" 23 | } 24 | ], 25 | "nullable": true 26 | }, 27 | "links": { 28 | "$ref": "/misc/poly_links.json", 29 | "description": "Links to be shown on the right of the navigation bar", 30 | "examples": [ 31 | { 32 | "Download on GitHub": { 33 | "icon": "fab fa-github", 34 | "url": "https://github.com/ppoffice/hexo-theme-icarus" 35 | } 36 | } 37 | ] 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /include/schema/common/plugins.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-07/schema#", 3 | "$id": "/common/plugins.json", 4 | "description": "Plugin configurations\nhttps://ppoffice.github.io/hexo-theme-icarus/categories/Plugins/", 5 | "type": "object", 6 | "properties": { 7 | "animejs": { 8 | "$ref": "/plugin/animejs.json" 9 | }, 10 | "back_to_top": { 11 | "$ref": "/plugin/back_to_top.json" 12 | }, 13 | "baidu_analytics": { 14 | "$ref": "/plugin/baidu_analytics.json" 15 | }, 16 | "bing_webmaster": { 17 | "$ref": "/plugin/bing_webmaster.json" 18 | }, 19 | "busuanzi": { 20 | "$ref": "/plugin/busuanzi.json" 21 | }, 22 | "cnzz": { 23 | "$ref": "/plugin/cnzz.json" 24 | }, 25 | "cookie_consent": { 26 | "$ref": "/plugin/cookie_consent.json" 27 | }, 28 | "gallery": { 29 | "$ref": "/plugin/gallery.json" 30 | }, 31 | "google_analytics": { 32 | "$ref": "/plugin/google_analytics.json" 33 | }, 34 | "hotjar": { 35 | "$ref": "/plugin/hotjar.json" 36 | }, 37 | "katex": { 38 | "$ref": "/plugin/katex.json" 39 | }, 40 | "mathjax": { 41 | "$ref": "/plugin/mathjax.json" 42 | }, 43 | "outdated_browser": { 44 | "$ref": "/plugin/outdated_browser.json" 45 | }, 46 | "pjax": { 47 | "$ref": "/plugin/pjax.json" 48 | }, 49 | "progressbar": { 50 | "$ref": "/plugin/progressbar.json" 51 | }, 52 | "statcounter": { 53 | "$ref": "/plugin/statcounter.json" 54 | }, 55 | "twitter_conversion_tracking": { 56 | "$ref": "/plugin/twitter_conversion_tracking.json" 57 | } 58 | } 59 | } -------------------------------------------------------------------------------- /include/schema/common/providers.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-07/schema#", 3 | "$id": "/common/providers.json", 4 | "description": "CDN provider settings\nhttps://ppoffice.github.io/hexo-theme-icarus/Configuration/Theme/speed-up-your-site-with-custom-cdn/", 5 | "type": "object", 6 | "properties": { 7 | "cdn": { 8 | "type": "string", 9 | "description": "Name or URL template of the JavaScript and/or stylesheet CDN provider", 10 | "default": "jsdelivr", 11 | "nullable": true 12 | }, 13 | "fontcdn": { 14 | "type": "string", 15 | "description": "Name or URL template of the webfont CDN provider", 16 | "default": "google", 17 | "nullable": true 18 | }, 19 | "iconcdn": { 20 | "type": "string", 21 | "description": "Name or URL of the fontawesome icon font CDN provider", 22 | "default": "fontawesome", 23 | "nullable": true 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /include/schema/common/search.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-07/schema#", 3 | "$id": "/common/search.json", 4 | "description": "Search plugin configurations\nhttps://ppoffice.github.io/hexo-theme-icarus/categories/Plugins/Search/", 5 | "type": "object", 6 | "oneOf": [ 7 | { 8 | "$ref": "/search/insight.json" 9 | }, 10 | { 11 | "$ref": "/search/baidu.json" 12 | }, 13 | { 14 | "$ref": "/search/google_cse.json" 15 | }, 16 | { 17 | "$ref": "/search/algolia.json" 18 | } 19 | ] 20 | } -------------------------------------------------------------------------------- /include/schema/common/share.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-07/schema#", 3 | "$id": "/common/share.json", 4 | "description": "Share plugin configurations\nhttps://ppoffice.github.io/hexo-theme-icarus/categories/Plugins/Share/", 5 | "type": "object", 6 | "oneOf": [ 7 | { 8 | "$ref": "/share/sharethis.json" 9 | }, 10 | { 11 | "$ref": "/share/addthis.json" 12 | }, 13 | { 14 | "$ref": "/share/addtoany.json" 15 | }, 16 | { 17 | "$ref": "/share/bdshare.json" 18 | }, 19 | { 20 | "$ref": "/share/sharejs.json" 21 | } 22 | ] 23 | } -------------------------------------------------------------------------------- /include/schema/common/sidebar.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-07/schema#", 3 | "$id": "/common/sidebar.json", 4 | "description": "Sidebar configurations.\nPlease be noted that a sidebar is only visible when it has at least one widget", 5 | "type": "object", 6 | "properties": { 7 | "left": { 8 | "type": "object", 9 | "description": "Left sidebar configurations", 10 | "properties": { 11 | "sticky": { 12 | "type": "boolean", 13 | "description": "Whether the sidebar sticks to the top when page scrolls", 14 | "default": false 15 | } 16 | } 17 | }, 18 | "right": { 19 | "type": "object", 20 | "description": "Right sidebar configurations", 21 | "properties": { 22 | "sticky": { 23 | "type": "boolean", 24 | "description": "Whether the sidebar sticks to the top when page scrolls", 25 | "default": false 26 | } 27 | } 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /include/schema/common/widgets.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-07/schema#", 3 | "$id": "/common/widgets.json", 4 | "description": "Sidebar widget configurations\nhttp://ppoffice.github.io/hexo-theme-icarus/categories/Widgets/", 5 | "type": "array", 6 | "items": { 7 | "type": "object", 8 | "properties": { 9 | "position": { 10 | "type": "string", 11 | "description": "Where should the widget be placed, left sidebar or right sidebar", 12 | "default": "left" 13 | } 14 | }, 15 | "oneOf": [ 16 | { 17 | "$ref": "/widget/profile.json" 18 | }, 19 | { 20 | "$ref": "/widget/toc.json" 21 | }, 22 | { 23 | "$ref": "/widget/links.json" 24 | }, 25 | { 26 | "$ref": "/widget/categories.json" 27 | }, 28 | { 29 | "$ref": "/widget/recent_posts.json" 30 | }, 31 | { 32 | "$ref": "/widget/archives.json" 33 | }, 34 | { 35 | "$ref": "/widget/tags.json" 36 | }, 37 | { 38 | "$ref": "/widget/subscribe_email.json" 39 | }, 40 | { 41 | "$ref": "/widget/adsense.json" 42 | }, 43 | { 44 | "$ref": "/widget/followit.json" 45 | } 46 | ], 47 | "required": [ 48 | "position" 49 | ] 50 | } 51 | } -------------------------------------------------------------------------------- /include/schema/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-07/schema#", 3 | "$id": "/config.json", 4 | "description": "The configuration file definition", 5 | "type": "object", 6 | "properties": { 7 | "version": { 8 | "type": "string", 9 | "description": "Version of the configuration file", 10 | "default": "5.1.0" 11 | }, 12 | "variant": { 13 | "type": "string", 14 | "description": "Icarus theme variant, can be \"default\" or \"cyberpunk\"", 15 | "enum": [ 16 | "default", 17 | "cyberpunk" 18 | ], 19 | "default": "default" 20 | }, 21 | "logo": { 22 | "type": [ 23 | "string", 24 | "object" 25 | ], 26 | "description": "Path or URL to the website's logo", 27 | "default": "/img/logo.svg", 28 | "properties": { 29 | "text": { 30 | "type": "string", 31 | "description": "Text to be shown in place of the logo image" 32 | } 33 | }, 34 | "required": [ 35 | "text" 36 | ] 37 | }, 38 | "head": { 39 | "$ref": "/common/head.json" 40 | }, 41 | "navbar": { 42 | "$ref": "/common/navbar.json" 43 | }, 44 | "footer": { 45 | "$ref": "/common/footer.json" 46 | }, 47 | "article": { 48 | "$ref": "/common/article.json" 49 | }, 50 | "search": { 51 | "$ref": "/common/search.json" 52 | }, 53 | "comment": { 54 | "$ref": "/common/comment.json" 55 | }, 56 | "donates": { 57 | "$ref": "/common/donates.json" 58 | }, 59 | "share": { 60 | "$ref": "/common/share.json" 61 | }, 62 | "sidebar": { 63 | "$ref": "/common/sidebar.json" 64 | }, 65 | "widgets": { 66 | "$ref": "/common/widgets.json" 67 | }, 68 | "plugins": { 69 | "$ref": "/common/plugins.json" 70 | }, 71 | "providers": { 72 | "$ref": "/common/providers.json" 73 | } 74 | }, 75 | "required": [ 76 | "version" 77 | ] 78 | } -------------------------------------------------------------------------------- /include/schema/donate/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ppoffice/hexo-theme-icarus/76d8cd0197003fc1ad673741d66a803ebb116f81/include/schema/donate/.gitkeep -------------------------------------------------------------------------------- /include/schema/misc/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ppoffice/hexo-theme-icarus/76d8cd0197003fc1ad673741d66a803ebb116f81/include/schema/misc/.gitkeep -------------------------------------------------------------------------------- /include/schema/plugin/animejs.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-07/schema#", 3 | "$id": "/plugin/animejs.json", 4 | "description": "Enable page startup animations", 5 | "type": "boolean", 6 | "default": true 7 | } -------------------------------------------------------------------------------- /include/schema/plugin/back_to_top.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-07/schema#", 3 | "$id": "/plugin/back_to_top.json", 4 | "description": "Show the \"back to top\" button", 5 | "type": "boolean", 6 | "default": true 7 | } -------------------------------------------------------------------------------- /include/schema/plugin/pjax.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-07/schema#", 3 | "$id": "/plugin/pjax.json", 4 | "description": "Enable PJAX", 5 | "type": "boolean", 6 | "default": true 7 | } -------------------------------------------------------------------------------- /include/schema/search/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ppoffice/hexo-theme-icarus/76d8cd0197003fc1ad673741d66a803ebb116f81/include/schema/search/.gitkeep -------------------------------------------------------------------------------- /include/schema/share/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ppoffice/hexo-theme-icarus/76d8cd0197003fc1ad673741d66a803ebb116f81/include/schema/share/.gitkeep -------------------------------------------------------------------------------- /include/schema/widget/profile.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-07/schema#", 3 | "$id": "/widget/profile.json", 4 | "description": "Profile widget configurations", 5 | "type": "object", 6 | "properties": { 7 | "type": { 8 | "type": "string", 9 | "const": "profile", 10 | "nullable": true 11 | }, 12 | "author": { 13 | "type": "string", 14 | "description": "Author name", 15 | "examples": [ 16 | "Your name" 17 | ], 18 | "nullable": true 19 | }, 20 | "author_title": { 21 | "type": "string", 22 | "description": "Author title", 23 | "examples": [ 24 | "Your title" 25 | ], 26 | "nullable": true 27 | }, 28 | "location": { 29 | "type": "string", 30 | "description": "Author's current location", 31 | "examples": [ 32 | "Your location" 33 | ], 34 | "nullable": true 35 | }, 36 | "avatar": { 37 | "type": "string", 38 | "description": "URL or path to the avatar image", 39 | "nullable": true 40 | }, 41 | "avatar_rounded": { 42 | "type": "boolean", 43 | "description": "Whether show the rounded avatar image", 44 | "default": false, 45 | "nullable": true 46 | }, 47 | "gravatar": { 48 | "type": "string", 49 | "description": "Email address for the Gravatar", 50 | "nullable": true 51 | }, 52 | "follow_link": { 53 | "type": "string", 54 | "description": "URL or path for the follow button", 55 | "examples": [ 56 | "https://github.com/ppoffice" 57 | ], 58 | "nullable": true 59 | }, 60 | "social_links": { 61 | "$ref": "/misc/poly_links.json", 62 | "description": "Links to be shown on the bottom of the profile widget", 63 | "examples": [ 64 | { 65 | "Github": { 66 | "icon": "fab fa-github", 67 | "url": "https://github.com/ppoffice" 68 | }, 69 | "Facebook": { 70 | "icon": "fab fa-facebook", 71 | "url": "https://facebook.com" 72 | }, 73 | "Twitter": { 74 | "icon": "fab fa-twitter", 75 | "url": "https://twitter.com" 76 | }, 77 | "Dribbble": { 78 | "icon": "fab fa-dribbble", 79 | "url": "https://dribbble.com" 80 | }, 81 | "RSS": { 82 | "icon": "fas fa-rss", 83 | "url": "/" 84 | } 85 | } 86 | ] 87 | } 88 | }, 89 | "required": [ 90 | "type" 91 | ] 92 | } -------------------------------------------------------------------------------- /include/style/article.styl: -------------------------------------------------------------------------------- 1 | /* --------------------------------- 2 | * Article Summary and Content 3 | * --------------------------------- */ 4 | $article-font-size ?= 1.1rem 5 | 6 | article 7 | &.media 8 | color: $text-light 9 | 10 | a 11 | color: inherit 12 | 13 | &:hover 14 | color: $primary 15 | 16 | .image 17 | width: 64px 18 | height: 64px 19 | 20 | img 21 | object-fit: cover 22 | width: 100% 23 | height: 100% 24 | 25 | .title 26 | @extend .is-size-6 27 | margin-bottom: .25em 28 | 29 | .date, .categories 30 | @extend .is-size-7 31 | 32 | .categories 33 | @extend .is-uppercase 34 | 35 | .media-content 36 | color: $text-light 37 | 38 | .title 39 | margin: 0 40 | line-height: inherit 41 | 42 | &.article 43 | .article-meta, .article-tags 44 | color: $text-light 45 | 46 | .article-meta 47 | overflow-x: auto 48 | margin-bottom: .5rem 49 | 50 | .article-more 51 | @extend .button.is-light 52 | 53 | .content 54 | word-wrap: break-word 55 | font-size: $article-font-size 56 | 57 | h1 58 | font-size: 1.75em 59 | 60 | h2 61 | font-size: 1.5em 62 | 63 | h3 64 | font-size: 1.25em 65 | 66 | h4 67 | font-size: 1.125em 68 | 69 | h5 70 | font-size: 1em 71 | 72 | pre 73 | font-size: .85em 74 | 75 | code 76 | padding: 0 77 | background: transparent 78 | overflow-wrap: break-word 79 | 80 | blockquote 81 | &.pullquote 82 | float: right 83 | max-width: 50% 84 | font-size: 1.15rem 85 | position: relative 86 | 87 | footer 88 | strong + cite 89 | margin-left: .5em 90 | 91 | .message.message-immersive 92 | border-radius: 0 93 | margin: 0 0 - $card-content-padding $card-content-padding 0 - $card-content-padding 94 | 95 | .message-body 96 | border: none 97 | 98 | .article-tags 99 | display: flex 100 | flex-wrap: wrap 101 | 102 | .rtl 103 | direction: rtl 104 | 105 | .level 106 | &, &.is-mobile 107 | .level-item:not(:last-child) 108 | margin-left: .75rem 109 | margin-right: 0 110 | 111 | // Overflow table 112 | .table-overflow 113 | overflow-x: auto 114 | 115 | table 116 | width: auto !important 117 | 118 | th 119 | word-break: keep-all 120 | 121 | // Video container 122 | .video-container 123 | position: relative 124 | padding-bottom: 56.25% 125 | padding-top: 25px 126 | height: 0 127 | 128 | iframe 129 | position: absolute 130 | top: 0 131 | left: 0 132 | width: 100% 133 | height: 100% 134 | 135 | .article-licensing 136 | position: relative 137 | z-index: 1 138 | box-shadow: none 139 | background: $white-ter 140 | border-radius: $radius 141 | overflow: hidden 142 | 143 | &:after 144 | position: absolute 145 | z-index: -1 146 | right: -50px 147 | top: -87.87px 148 | content: '\f25e' 149 | font-size: 200px 150 | font-family: 'Font Awesome 5 Brands' 151 | opacity: .1 152 | 153 | .level-left 154 | flex-wrap: wrap 155 | max-width: 100% 156 | 157 | .licensing-title 158 | @extend .mb-3 159 | line-height: 1.2 160 | 161 | p:not(:last-child) 162 | @extend .mb-1 163 | 164 | a 165 | @extend .is-size-7, .has-text-grey 166 | 167 | .licensing-meta 168 | .level-item 169 | @extend .mr-4 170 | 171 | .icons 172 | .icon 173 | @extend .ml-1 174 | width: 1.2em 175 | height: 1.2em 176 | font-size: 1.2em 177 | vertical-align: bottom 178 | 179 | h6 180 | @extend .is-size-7 181 | 182 | a 183 | color: inherit 184 | 185 | a 186 | &.article-nav-prev 187 | span 188 | text-align: left 189 | flex-shrink: 1 190 | word-wrap: break-word 191 | white-space: normal 192 | 193 | &.article-nav-next 194 | span 195 | text-align: right 196 | flex-shrink: 1 197 | word-wrap: break-word 198 | white-space: normal -------------------------------------------------------------------------------- /include/style/base.styl: -------------------------------------------------------------------------------- 1 | bulma-stylus-root = '../../../../node_modules/bulma-stylus/stylus' 2 | 3 | /* --------------------------------- 4 | * Override Bulma CSS Framework 5 | * --------------------------------- */ 6 | $body-size ?= 14px 7 | $body-background-color ?= #f7f7f7 8 | 9 | $family-sans-serif ?= Ubuntu, Roboto, 'Open Sans', 'Microsoft YaHei', sans-serif 10 | $family-code ?= 'Source Code Pro', monospace, 'Microsoft YaHei' 11 | 12 | $size-7 ?= .85rem 13 | $size-small ?= .75rem 14 | 15 | $primary ?= $blue 16 | $custom-colors ?= { 17 | grey-lightest: { 18 | '1': $grey-lightest 19 | '2': $grey-darker 20 | } 21 | } 22 | 23 | $navbar-item-active-color ?= $primary 24 | $footer-background-color ?= $scheme-main 25 | 26 | $gap ?= 64px 27 | $tablet ?= 769px 28 | $desktop ?= 1088px 29 | $widescreen ?= 1280px 30 | $fullhd ?= 1472px 31 | 32 | $shadow ?= 0 4px 10px rgba(0, 0, 0, 0.05) 33 | 34 | $title-weight ?= $weight-normal 35 | 36 | $control-height ?= 2.25em 37 | $button-padding-vertical ?= calc(0.375em - 1px) 38 | 39 | $card-radius ?= $radius 40 | $card-media-margin ?= 0.75rem 41 | $card-shadow ?= $shadow, 0 0 1px rgba(0, 0, 0, 0.1) 42 | 43 | $menu-item-active-color ?= $link 44 | $menu-item-active-background-color ?= hsl(219, 70%, 96%) 45 | 46 | $content-heading-weight ?= $weight-normal 47 | 48 | 49 | $logo-height ?= 1.75rem 50 | 51 | // FIXME: https://github.com/groenroos/bulma-stylus/issues/11 52 | @import bulma-stylus-root + '/utilities/initial-variables' 53 | @import bulma-stylus-root + '/utilities/functions' 54 | @import bulma-stylus-root + '/utilities/derived-variables' 55 | 56 | $colors = merge($colors, $custom-colors) 57 | 58 | @import bulma-stylus-root + '/utilities/animations' 59 | @import bulma-stylus-root + '/utilities/mixins' 60 | @import bulma-stylus-root + '/utilities/controls' 61 | @import bulma-stylus-root + '/base/_all' 62 | @import bulma-stylus-root + '/components/_all' 63 | @import bulma-stylus-root + '/elements/_all' 64 | @import bulma-stylus-root + '/form/_all' 65 | @import bulma-stylus-root + '/grid/_all' 66 | @import bulma-stylus-root + '/layout/_all' 67 | 68 | html 69 | height: 100% 70 | -webkit-text-size-adjust: 100% 71 | -moz-text-size-adjust: 100% 72 | -ms-text-size-adjust: 100% 73 | text-size-adjust: 100% 74 | 75 | body 76 | min-height: 100% 77 | display: flex 78 | flex-direction: column 79 | 80 | body > .section 81 | flex-grow: 1 82 | 83 | +desktop() 84 | ::-webkit-scrollbar 85 | width: 8px 86 | height: 8px 87 | 88 | ::-webkit-scrollbar-track 89 | border-radius: 3px 90 | background: rgba(0,0,0,0.06) 91 | box-shadow: inset 0 0 5px rgba(0,0,0,0.1) 92 | 93 | ::-webkit-scrollbar-thumb 94 | border-radius: 3px 95 | background: rgba(0,0,0,0.12) 96 | box-shadow: inset 0 0 10px rgba(0,0,0,0.2) 97 | 98 | ::-webkit-scrollbar-thumb:hover 99 | background: rgba(0,0,0,0.24) 100 | -------------------------------------------------------------------------------- /include/style/button.styl: -------------------------------------------------------------------------------- 1 | /* --------------------------------- 2 | * Buttons 3 | * --------------------------------- */ 4 | .button 5 | &.is-transparent 6 | color: inherit 7 | background: transparent 8 | border-color: transparent 9 | -------------------------------------------------------------------------------- /include/style/card.styl: -------------------------------------------------------------------------------- 1 | /* --------------------------------- 2 | * Card 3 | * --------------------------------- */ 4 | .card 5 | overflow: visible 6 | border-radius: $card-radius 7 | 8 | & + .card, & + .column-right-shadow 9 | margin-top: 1.5rem 10 | 11 | .card-image 12 | overflow: hidden 13 | border-top-left-radius: $card-radius 14 | border-top-right-radius: $card-radius 15 | 16 | .media + .media 17 | border: none 18 | margin-top: 0 19 | -------------------------------------------------------------------------------- /include/style/codeblock.styl: -------------------------------------------------------------------------------- 1 | /* --------------------------------- 2 | * Code Highlight 3 | * --------------------------------- */ 4 | $codeblock-caption-bg ?= rgba(200, 200, 200, .15) 5 | 6 | figure.highlight 7 | padding: 0 8 | width: 100% 9 | position: relative 10 | margin: 1em 0 1em !important 11 | border-radius: $radius 12 | 13 | &.folded 14 | .highlight-body 15 | height: 0 16 | 17 | .copy 18 | opacity: .7 19 | 20 | pre, table tr:hover 21 | color: inherit 22 | background: transparent 23 | 24 | table 25 | width: auto 26 | 27 | tr td 28 | border: none 29 | 30 | tr:not(:first-child) td 31 | padding-top: 0 32 | 33 | tr:not(:last-child) td 34 | padding-bottom: 0 35 | 36 | pre 37 | padding: 0 38 | overflow: visible 39 | 40 | .line, code .hljs 41 | line-height: 1.5rem 42 | 43 | figcaption, .gutter 44 | background: $codeblock-caption-bg 45 | 46 | figcaption 47 | margin: 0 !important 48 | padding: .3em 0em .3em .75em 49 | font-style: normal 50 | font-size: .8em 51 | 52 | * 53 | color: inherit 54 | 55 | span 56 | font-weight: 500 57 | font-family: $family-code 58 | 59 | .level-left *:not(:last-child) 60 | margin-right: .5em 61 | 62 | .level-right *:not(:first-child) 63 | margin-left: .5em 64 | 65 | .fold 66 | cursor: pointer 67 | 68 | &.level 69 | overflow: auto 70 | 71 | .level-right 72 | a 73 | padding: 0em .75em 74 | 75 | .highlight-body 76 | overflow: auto 77 | 78 | .gutter 79 | text-align: right 80 | 81 | .tag, .title, .number, .section 82 | display: inherit 83 | font: inherit 84 | margin: inherit 85 | padding: inherit 86 | background: inherit 87 | height: inherit 88 | text-align: inherit 89 | vertical-align: inherit 90 | min-width: inherit 91 | border-radius: inherit 92 | 93 | figure.highlight.foldable 94 | div.level-left 95 | cursor: pointer // clicking the codeblock filename can toggle folding 96 | 97 | /* --------------------------------- 98 | * Fix Gist Snippet 99 | * --------------------------------- */ 100 | .gist 101 | table 102 | tr:hover 103 | background: transparent 104 | 105 | td 106 | border: none 107 | 108 | .file 109 | all: initial 110 | -------------------------------------------------------------------------------- /include/style/donate.styl: -------------------------------------------------------------------------------- 1 | /* --------------------------------- 2 | * Donate Buttons 3 | * --------------------------------- */ 4 | $donate-qrcode-max-width ?= 280px 5 | $donate-qrcode-shadow ?= $card-shadow 6 | $donate-qrcode-box-radius ?= $card-radius 7 | 8 | $donate-button-colors ?= { 9 | 'afdian': rgb(136, 95, 217), 10 | 'alipay': rgb(0, 160, 232), 11 | 'buymeacoffee': rgb(255, 221, 0), 12 | 'paypal': rgb(254, 183, 0), 13 | 'patreon': rgb(255, 66, 77), 14 | 'wechat': rgb(26, 173, 25), 15 | } 16 | 17 | .donate 18 | position: relative 19 | 20 | .qrcode 21 | display: none 22 | position: absolute 23 | z-index: 99 24 | bottom: 2.5em 25 | line-height: 0 26 | overflow: hidden 27 | box-shadow: $donate-qrcode-shadow 28 | border-radius: $donate-qrcode-box-radius 29 | 30 | img 31 | max-width: $donate-qrcode-max-width 32 | 33 | &:hover 34 | .qrcode 35 | display: block 36 | 37 | &:first-child:not(:last-child) 38 | .qrcode 39 | left: -.75rem 40 | 41 | &:last-child:not(:first-child) 42 | .qrcode 43 | right: -.75rem 44 | 45 | for $name, $color in $donate-button-colors 46 | &[data-type={'"' + $name + '"'}] 47 | color: findColorInvert($color) 48 | background-color: $color 49 | border-color: transparent 50 | 51 | &:active 52 | background-color: darken($color, 5%) 53 | 54 | &:hover 55 | background-color: darken($color, 2.5%) 56 | 57 | &:focus:not(:active) 58 | box-shadow: 0 0 0 .125em rgba($color, .25) 59 | -------------------------------------------------------------------------------- /include/style/footer.styl: -------------------------------------------------------------------------------- 1 | /* --------------------------------- 2 | * Page Footer 3 | * --------------------------------- */ 4 | footer.footer 5 | .level-start 6 | +mobile() 7 | text-align: center 8 | 9 | .level-end 10 | .field 11 | flex-wrap: wrap 12 | align-items: center 13 | 14 | +mobile() 15 | justify-content: center 16 | margin-top: 1rem 17 | 18 | .footer-logo 19 | img 20 | max-height: $logo-height 21 | -------------------------------------------------------------------------------- /include/style/helper.styl: -------------------------------------------------------------------------------- 1 | /* --------------------------------- 2 | * Spacing helpers 3 | * --------------------------------- */ 4 | $spacer ?= 1rem 5 | $spacers ?= 0, $spacer * .25, $spacer * .5, $spacer, $spacer * 1.5, $spacer * 3 6 | 7 | for n in (0 .. 5) 8 | .ml-{n} 9 | margin-left: $spacers[n] !important 10 | 11 | .mr-{n} 12 | margin-right: $spacers[n] !important 13 | 14 | .mx-{n} 15 | @extend .ml-{n}, .mr-{n} 16 | 17 | .ml-n{n} 18 | margin-left: - $spacers[n] !important 19 | 20 | .mr-n{n} 21 | margin-right: - $spacers[n] !important 22 | 23 | .mx-n{n} 24 | @extend .ml-n{n}, .mr-n{n} 25 | 26 | .mt-{n} 27 | margin-top: $spacers[n] !important 28 | 29 | .mb-{n} 30 | margin-bottom: $spacers[n] !important 31 | 32 | .my-{n} 33 | @extend .mt-{n}, .mb-{n} 34 | 35 | .mt-n{n} 36 | margin-top: - $spacers[n] !important 37 | 38 | .mb-n{n} 39 | margin-bottom: - $spacers[n] !important 40 | 41 | .my-n{n} 42 | @extend .mt-n{n}, .mb-n{n} 43 | 44 | .pl-{n} 45 | padding-left: $spacers[n] !important 46 | 47 | .pr-{n} 48 | padding-right: $spacers[n] !important 49 | 50 | .px-{n} 51 | @extend .pl-{n}, .pr-{n} 52 | 53 | .pl-n{n} 54 | padding-left: - $spacers[n] !important 55 | 56 | .pr-n{n} 57 | padding-right: - $spacers[n] !important 58 | 59 | .px-n{n} 60 | @extend .pl-n{n}, .pr-n{n} 61 | 62 | .pt-{n} 63 | padding-top: $spacers[n] !important 64 | 65 | .pb-{n} 66 | padding-bottom: $spacers[n] !important 67 | 68 | .py-{n} 69 | @extend .pt-{n}, .pb-{n} 70 | 71 | .pt-n{n} 72 | padding-top: - $spacers[n] !important 73 | 74 | .pb-n{n} 75 | padding-bottom: - $spacers[n] !important 76 | 77 | .py-n{n} 78 | @extend .pt-n{n}, .pb-n{n} 79 | 80 | .ml-auto 81 | margin-left: auto !important 82 | 83 | .mr-auto 84 | margin-right: auto !important 85 | 86 | .mx-auto 87 | @extend .ml-auto, .mr-auto 88 | 89 | .mt-auto 90 | margin-top: auto !important 91 | 92 | .mb-auto 93 | margin-bottom: auto !important 94 | 95 | .my-auto 96 | @extend .mt-auto, .mb-auto 97 | 98 | .pl-auto 99 | margin-left: auto !important 100 | 101 | .pr-auto 102 | margin-right: auto !important 103 | 104 | .px-auto 105 | @extend .pl-auto, .pr-auto 106 | 107 | .pt-auto 108 | margin-top: auto !important 109 | 110 | .pb-auto 111 | margin-bottom: auto !important 112 | 113 | .py-auto 114 | @extend .pt-auto, .pb-auto 115 | 116 | /* --------------------------------- 117 | * Flex helpers 118 | * --------------------------------- */ 119 | for n in (0 .. 5) 120 | .order-{n} 121 | order: n !important 122 | 123 | .justify-content-start 124 | justify-content: start !important 125 | 126 | .justify-content-center 127 | justify-content: center !important 128 | 129 | .flex-shrink-1 130 | flex-shrink: 1 !important 131 | 132 | /* --------------------------------- 133 | * Color helpers 134 | * --------------------------------- */ 135 | .link-muted 136 | color: inherit 137 | 138 | &:hover 139 | color: $primary !important 140 | 141 | /* --------------------------------- 142 | * Image helpers 143 | * --------------------------------- */ 144 | .image 145 | &.is-7by3 146 | padding-top: 42.8% 147 | 148 | img 149 | bottom: 0 150 | left: 0 151 | position: absolute 152 | right: 0 153 | top: 0 154 | 155 | .avatar 156 | height: 100% 157 | object-fit: cover 158 | 159 | .fill 160 | object-fit: cover 161 | width: 100% !important 162 | height: 100% !important -------------------------------------------------------------------------------- /include/style/navbar.styl: -------------------------------------------------------------------------------- 1 | /* --------------------------------- 2 | * Top Navigation 3 | * --------------------------------- */ 4 | $navbar-item-padding-v ?= 1.25rem 5 | $navbar-item-padding-h ?= .75rem 6 | $navbar-item-margin-v ?= 0 7 | $navbar-item-margin-h ?= 0 8 | 9 | .navbar-main 10 | box-shadow: $shadow 11 | 12 | .navbar-container 13 | overflow-x: auto 14 | 15 | .navbar-menu, .navbar-start, .navbar-end 16 | align-items: stretch 17 | display: flex 18 | padding: 0 19 | flex-shrink: 0 20 | 21 | .navbar-menu 22 | flex-grow: 1 23 | flex-shrink: 0 24 | overflow-x: auto 25 | 26 | .navbar-start 27 | justify-content: flex-start 28 | margin-right: auto 29 | 30 | .navbar-end 31 | justify-content: flex-end 32 | margin-left: auto 33 | 34 | .navbar-item 35 | display: flex 36 | align-items: center 37 | padding: $navbar-item-padding-v $navbar-item-padding-h 38 | margin: $navbar-item-margin-v $navbar-item-margin-h 39 | 40 | &.is-active 41 | background-color: transparent 42 | 43 | +until($navbar-breakpoint) 44 | .navbar-menu 45 | justify-content: center 46 | box-shadow: none 47 | 48 | .navbar-start 49 | margin-right: 0 50 | 51 | .navbar-end 52 | margin-left: 0 53 | 54 | .navbar-logo 55 | img 56 | max-height: $logo-height 57 | 58 | @media screen and (min-width: $desktop) 59 | .navbar > .container .navbar-menu, .container > .navbar .navbar-menu 60 | margin-right: 0rem 61 | -------------------------------------------------------------------------------- /include/style/pagination.styl: -------------------------------------------------------------------------------- 1 | /* --------------------------------- 2 | * Pagination and Post Navigation 3 | * --------------------------------- */ 4 | $pagination-box-shadow ?= $card-shadow 5 | $pagination-background-color ?= $button-background-color 6 | $post-navigation-fg ?= $grey 7 | 8 | .pagination 9 | @extend .pagination.is-centered 10 | margin-top: 1.5rem 11 | 12 | .pagination-link, 13 | .pagination-ellipsis, 14 | .pagination-previous, 15 | .pagination-next 16 | a 17 | color: $pagination-color 18 | .pagination-link, 19 | .pagination-previous, 20 | .pagination-next 21 | border: none 22 | background: $pagination-background-color 23 | box-shadow: $pagination-box-shadow 24 | .pagination-link.is-current 25 | background: $pagination-current-background-color 26 | 27 | .post-navigation 28 | color: $post-navigation-fg 29 | flex-wrap: wrap 30 | justify-content: space-around 31 | .level-item 32 | margin-bottom: 0 33 | -------------------------------------------------------------------------------- /include/style/plugin.styl: -------------------------------------------------------------------------------- 1 | /* --------------------------------- 2 | * Back to Top Button 3 | * --------------------------------- */ 4 | #back-to-top 5 | position: fixed 6 | opacity: 0 7 | outline: none 8 | padding: 8px 0 9 | line-height: 24px 10 | border-radius: $card-radius 11 | transform: translateY(120px) 12 | transition: .4s ease opacity, .4s ease width, .4s ease transform, .4s ease border-radius 13 | 14 | &.is-rounded 15 | border-radius: 50% 16 | 17 | &.fade-in 18 | opacity: 1 19 | 20 | &.rise-up 21 | transform: translateY(0) 22 | 23 | /* --------------------------------- 24 | * Gallery Plugin 25 | * --------------------------------- */ 26 | .gallery-item 27 | .caption 28 | color: $grey 29 | 30 | /* --------------------------------- 31 | * Page Loading Progressbar 32 | * --------------------------------- */ 33 | .pace 34 | user-select: none 35 | pointer-events: none 36 | 37 | .pace-progress 38 | top: 0 39 | right: 100% 40 | width: 100% 41 | height: 2px 42 | z-index: 2000 43 | position: fixed 44 | background: $primary 45 | 46 | .pace-inactive 47 | display: none 48 | 49 | /* --------------------------------- 50 | * Fix FontAwesome Icons 51 | * --------------------------------- */ 52 | .fa, .fab, .fal, .far, .fas 53 | line-height: inherit 54 | 55 | /* --------------------------------- 56 | * MathJax and KaTeX 57 | * --------------------------------- */ 58 | .MathJax, .MathJax_Display, .MJXc-display, .MathJax_SVG_Display, .katex-display 59 | overflow-x: auto 60 | overflow-y: hidden 61 | 62 | .katex 63 | white-space: nowrap 64 | 65 | .katex-display 66 | margin-top: -1em !important 67 | 68 | .katex-html 69 | padding-top: 1em 70 | 71 | .tag 72 | align-items: unset 73 | background-color: unset 74 | border-radius: unset 75 | color: unset 76 | display: unset 77 | font-size: unset 78 | height: unset 79 | justify-content: unset 80 | line-height: unset 81 | padding-left: unset 82 | padding-right: unset 83 | white-space: unset 84 | 85 | /* --------------------------------- 86 | * Cookie Consent 87 | * --------------------------------- */ 88 | .cc-window, .cc-revoke 89 | font-size: 1.1rem !important 90 | font-family: $family-sans-serif !important 91 | 92 | .cc-window 93 | color: $text !important 94 | background-color: $scheme-main !important 95 | 96 | &.cc-floating 97 | border-radius: $card-radius 98 | box-shadow: $card-shadow 99 | 100 | &.cc-banner 101 | background-color: darken($scheme-main, 2.5%) !important 102 | 103 | &.cc-theme-block, &.cc-theme-classic 104 | .cc-compliance > .cc-btn 105 | border-radius: $radius-rounded 106 | 107 | .cc-compliance > .cc-btn 108 | font-weight: 400 109 | border: none 110 | color: $primary-invert 111 | background-color: $primary 112 | 113 | &:hover, &:focus 114 | background-color: darken($primary, 2.5%) 115 | 116 | &.cc-deny 117 | &:hover 118 | color: $primary 119 | text-decoration: none 120 | 121 | .cc-revoke 122 | padding: .5rem 1rem !important 123 | color: $primary-invert !important 124 | background-color: $primary !important 125 | 126 | &:hover 127 | text-decoration: none !important 128 | background-color: darken($primary, 2.5%) 129 | -------------------------------------------------------------------------------- /include/style/responsive.styl: -------------------------------------------------------------------------------- 1 | /* --------------------------------- 2 | * Responsive Layout 3 | * --------------------------------- */ 4 | +widescreen() 5 | .is-1-column .container, .is-2-column .container 6 | max-width: $desktop - 2 * $gap 7 | width: $desktop - 2 * $gap 8 | 9 | +fullhd() 10 | .is-2-column .container 11 | max-width: $widescreen - 2 * $gap 12 | width: $widescreen - 2 * $gap 13 | 14 | .is-1-column .container 15 | max-width: $desktop - 2 * $gap 16 | width: $desktop - 2 * $gap 17 | 18 | +tablet() 19 | .is-sticky 20 | position: -webkit-sticky 21 | position: sticky 22 | top: 1.5rem 23 | z-index: 99 24 | 25 | .column-main, .column-left, .column-right, .column-right-shadow 26 | &.is-sticky 27 | top: .75rem 28 | align-self: flex-start 29 | 30 | +mobile() 31 | .section 32 | padding: 1.5rem 1rem 33 | -------------------------------------------------------------------------------- /include/style/search.styl: -------------------------------------------------------------------------------- 1 | /* --------------------------------- 2 | * Search Box 3 | * --------------------------------- */ 4 | // container sizes 5 | $searchbox-container-width ?= 540px 6 | $searchbox-container-margin ?= 100px 7 | $searchbox-breakpoint-width ?= 559px 8 | $searchbox-breakpoint-height ?= 479px 9 | // overlay and container styles 10 | $searchbox-box-shadow ?= $card-shadow 11 | $searchbox-border-radius ?= $radius 12 | $searchbox-bg-overlay ?= $modal-background-background-color 13 | $searchbox-bg-container ?= $white-ter 14 | $searchbox-border ?= $border 15 | // header styles 16 | $searchbox-bg-input ?= $white 17 | $searchbox-bg-close-hover ?= $searchbox-bg-container 18 | $searchbox-bg-close-active ?= $grey-lighter 19 | // body styles 20 | $searchbox-fg-result-header ?= $grey-light 21 | $searchbox-fg-result-item-secondary ?= $grey-light 22 | $searchbox-bg-result-item-hover ?= $searchbox-bg-input 23 | $searchbox-fg-result-item-active ?= findColorInvert($primary) 24 | $searchbox-bg-result-item-active ?= $primary 25 | $searchbox-bg-result-item-highlight ?= $yellow 26 | // footer styles 27 | $searchbox-bg-pagination-item ?= $searchbox-bg-input 28 | $searchbox-bg-pagination-item-hover ?= $searchbox-bg-container 29 | $searchbox-fg-pagination-item-active ?= findColorInvert($primary) 30 | $searchbox-bg-pagination-item-active ?= $primary 31 | $searchbox-bg-pagination-item-disabled ?= $searchbox-bg-container 32 | 33 | .searchbox 34 | display: none 35 | top: 0 36 | left: 0 37 | width: 100% 38 | height: 100% 39 | z-index: 100 40 | font-size: 1rem 41 | line-height: 0 42 | background: $searchbox-bg-overlay 43 | 44 | &.show 45 | display: flex 46 | 47 | a, a:hover 48 | color: inherit 49 | text-decoration: none 50 | 51 | input 52 | font-size: 1rem 53 | border: none 54 | outline: none 55 | box-shadow: none 56 | border-radius: 0 57 | 58 | &, .searchbox-container 59 | position: fixed 60 | align-items: center 61 | flex-direction: column 62 | line-height: 1.25em 63 | 64 | .searchbox-container 65 | z-index: 101 66 | display: flex 67 | overflow: hidden 68 | box-shadow: $searchbox-box-shadow 69 | border-radius: $searchbox-border-radius 70 | background-color: $searchbox-bg-container 71 | width: $searchbox-container-width 72 | top: $searchbox-container-margin 73 | bottom: $searchbox-container-margin 74 | 75 | .searchbox-header, .searchbox-body, .searchbox-footer 76 | width: 100% 77 | 78 | .searchbox-header 79 | display: flex 80 | flex-direction: row 81 | line-height: 1.5em 82 | font-weight: normal 83 | background-color: $searchbox-bg-input 84 | // fix Chrome 71 height issue 85 | // https://github.com/ppoffice/hexo-theme-icarus/issues/719 86 | min-height: 3rem 87 | 88 | .searchbox-input-container 89 | display: flex 90 | flex-grow: 1 91 | 92 | .searchbox-input 93 | flex-grow: 1 94 | color: inherit 95 | box-sizing: border-box 96 | padding: .75em 0 .75em 1.25em 97 | background: $searchbox-bg-input 98 | 99 | .searchbox-close 100 | display: inline-block 101 | font-size: 1.5em 102 | padding: .5em .75em 103 | cursor: pointer 104 | 105 | &:hover 106 | background: $searchbox-bg-close-hover 107 | 108 | &:active 109 | background: $searchbox-bg-close-active 110 | 111 | .searchbox-body 112 | flex-grow: 1 113 | overflow-y: auto 114 | border-top: 1px solid $searchbox-border 115 | 116 | .searchbox-result-section header, .searchbox-result-item 117 | padding: .75em 1em 118 | 119 | .searchbox-result-section 120 | border-bottom: 1px solid $searchbox-border 121 | 122 | header 123 | color: $searchbox-fg-result-header 124 | 125 | .searchbox-result-item 126 | display: flex 127 | flex-direction: row 128 | 129 | &:not(.disabled):not(.active):not(:active):hover 130 | background-color: $searchbox-bg-result-item-hover 131 | 132 | &:active, &.active 133 | color: $searchbox-fg-result-item-active 134 | background-color: $searchbox-bg-result-item-active 135 | 136 | em 137 | font-style: normal 138 | background: $searchbox-bg-result-item-highlight 139 | 140 | .searchbox-result-icon 141 | margin-right: 1em 142 | 143 | .searchbox-result-content 144 | overflow: hidden 145 | 146 | .searchbox-result-title, .searchbox-result-preview 147 | display: block 148 | overflow: hidden 149 | white-space: nowrap 150 | text-overflow: ellipsis 151 | 152 | .searchbox-result-title-secondary 153 | color: $searchbox-fg-result-item-secondary 154 | 155 | .searchbox-result-preview 156 | margin-top: .25em 157 | 158 | .searchbox-result-item:not(:active):not(.active) 159 | .searchbox-result-preview 160 | color: $searchbox-fg-result-item-secondary 161 | 162 | .searchbox-footer 163 | padding: .5em 1em 164 | 165 | .searchbox-pagination 166 | margin: 0 167 | padding: 0 168 | list-style: none 169 | text-align: center 170 | 171 | .searchbox-pagination-item 172 | margin: 0 .25rem 173 | 174 | .searchbox-pagination-item, .searchbox-pagination-link 175 | display: inline-block 176 | 177 | .searchbox-pagination-link 178 | overflow: hidden 179 | padding: .5em .8em 180 | box-shadow: $searchbox-box-shadow 181 | border-radius: $searchbox-border-radius 182 | background-color: $searchbox-bg-pagination-item 183 | 184 | .searchbox-pagination-item.active 185 | .searchbox-pagination-link 186 | color: $searchbox-fg-pagination-item-active 187 | background-color: $searchbox-bg-pagination-item-active 188 | 189 | .searchbox-pagination-item.disabled 190 | .searchbox-pagination-link 191 | cursor: not-allowed 192 | background-color: $searchbox-bg-pagination-item-disabled 193 | 194 | .searchbox-pagination-item:not(.active):not(.disabled) 195 | .searchbox-pagination-link:hover 196 | background-color: $searchbox-bg-pagination-item-hover 197 | 198 | @media screen and (max-width: $searchbox-breakpoint-width), screen and (max-height: $searchbox-breakpoint-height) 199 | .searchbox .searchbox-container 200 | top: 0 201 | left: 0 202 | width: 100% 203 | height: 100% 204 | border-radius: 0 205 | -------------------------------------------------------------------------------- /include/style/timeline.styl: -------------------------------------------------------------------------------- 1 | /* --------------------------------- 2 | * Archive Timeline 3 | * --------------------------------- */ 4 | $timeline-fg-line ?= $grey-lighter 5 | $timeline-bg-line ?= $card-background-color 6 | 7 | .timeline 8 | margin-left: 1rem 9 | padding: 1rem 0 0 1.5rem 10 | border-left: 1px solid $timeline-fg-line 11 | 12 | .media 13 | position: relative 14 | 15 | &:before, &:last-child:after 16 | content: '' 17 | display: block 18 | position: absolute 19 | left: calc(-.375rem - 1.5rem - .25px) 20 | 21 | &:before 22 | width: .75rem 23 | height: .75rem 24 | top: calc(1rem + 1.5 * .85rem / 2 - .75rem / 2) 25 | background: $timeline-fg-line 26 | border-radius: 50% 27 | 28 | &:first-child:before 29 | top: calc(1.5 * .85rem / 2 - .75rem / 2) 30 | 31 | &:last-child:after 32 | width: .75rem 33 | top: calc(1rem + 1.5 * .85rem / 2 + .75rem / 2) 34 | bottom: 0 35 | background: $timeline-bg-line 36 | 37 | &:first-child:last-child:after 38 | top: calc(1.5 * .85rem / 2 + .75rem / 2) 39 | -------------------------------------------------------------------------------- /include/style/widget.styl: -------------------------------------------------------------------------------- 1 | .widget 2 | .menu-list 3 | li 4 | ul 5 | margin-right: 0 6 | 7 | .level 8 | margin-bottom: 0 9 | 10 | .level-left, .level-right, .level-item 11 | flex-shrink: 1 12 | 13 | .level-left, .level-right 14 | align-items: flex-start 15 | 16 | .tag 17 | background: $light-grey 18 | color: $white-invert 19 | 20 | .tags 21 | .tag:first-child 22 | background: $primary 23 | color: $primary-invert 24 | 25 | .tag:last-child 26 | background: $light-grey 27 | color: $white-invert 28 | 29 | .level.is-multiline 30 | flex-wrap: wrap 31 | 32 | /* --------------------------------- 33 | * Table of Content Widget 34 | * --------------------------------- */ 35 | +mobile() 36 | .widget.card#toc 37 | display: none 38 | position: fixed 39 | margin: 1rem 40 | left: 0 41 | right: 0 42 | bottom: 0 43 | z-index: 100 44 | 45 | .card-content 46 | padding: 0 47 | 48 | .menu 49 | padding: 1.5rem 50 | max-height: calc(100vh - 2rem) 51 | overflow-y: auto 52 | 53 | #toc-mask 54 | display: none 55 | position: fixed 56 | top: 0 57 | left: 0 58 | right: 0 59 | bottom: 0 60 | z-index: 99 61 | background: rgba(0, 0, 0, .7) 62 | 63 | .widget.card#toc, #toc-mask 64 | &.is-active 65 | display: block -------------------------------------------------------------------------------- /include/util/console.js: -------------------------------------------------------------------------------- 1 | let chalk; 2 | try { 3 | chalk = require('chalk'); // eslint-disable-line node/no-extraneous-require 4 | } catch (e) { } 5 | 6 | module.exports = new Proxy({}, { 7 | get(obj, prop) { 8 | if (chalk) { 9 | return chalk[prop]; 10 | } 11 | return function() { 12 | return arguments.length === 1 ? arguments[0] : arguments; 13 | }; 14 | } 15 | }); 16 | -------------------------------------------------------------------------------- /languages/de.yml: -------------------------------------------------------------------------------- 1 | common: 2 | archive: 3 | one: 'Archiv' 4 | other: 'Archive' 5 | category: 6 | one: 'Kategorie' 7 | other: 'Kategorien' 8 | tag: 9 | one: 'Tag' 10 | other: 'Tags' 11 | post: 12 | one: 'Seite' 13 | other: 'Seiten' 14 | page: 15 | one: 'Page' 16 | other: 'Pages' 17 | prev: 'Zurück' 18 | next: 'Weiter' 19 | widget: 20 | follow: 'Folgen' 21 | recents: 'Letzte Einträge' 22 | links: 'Links' 23 | catalogue: 'Katalog' 24 | subscribe_email: 'Abonnieren Sie Updates' 25 | subscribe: 'Abonnieren' 26 | adsense: 'Werbung' 27 | followit: 'follow.it' 28 | article: 29 | created_at: 'Gepostet vor %s' 30 | updated_at: 'Aktualisiert vor %s' 31 | more: 'Mehr lesen' 32 | comments: 'Kommentare' 33 | read_time: '%s lesen' 34 | word_count: 35 | one: 'Über %d Wort' 36 | other: 'Über %d Wörter' 37 | licensing: 38 | author: 'Author' 39 | created_at: 'Posted on' 40 | updated_at: 'Updated on' 41 | licensed_under: 'Licensed under' 42 | donate: 43 | title: 'Gefällt Ihnen der Artikel? Unterstützen Sie den Autor mit' 44 | afdian: 'Afdian.net' 45 | alipay: 'Alipay' 46 | wechat: 'Wechat' 47 | paypal: 'Paypal' 48 | patreon: 'Patreon' 49 | buymeacoffee: 'Kauf mir einen Kaffee' 50 | plugin: 51 | backtotop: 'Zurück nach oben' 52 | visit_count: '%s Besuche' 53 | visitor_count: 'Von %s Nutzern besucht' 54 | cookie_consent: 55 | message: Diese Website verwendet Cookies, um Ihre Erfahrung zu verbessern. 56 | dismiss: Verstanden! 57 | allow: Cookies zulassen 58 | deny: Ablehnen 59 | link: Mehr erfahren 60 | policy: Cookie-Richtlinie 61 | search: 62 | search: 'Suche' 63 | hint: 'Tippen Sie etwas...' 64 | no_result: 'Keine Ergebnisse für' 65 | untitled: '(Ohne Titel)' 66 | empty_preview: '(Keine Vorschau)' 67 | -------------------------------------------------------------------------------- /languages/en.yml: -------------------------------------------------------------------------------- 1 | common: 2 | archive: 3 | one: 'Archive' 4 | other: 'Archives' 5 | category: 6 | one: 'Category' 7 | other: 'Categories' 8 | tag: 9 | one: 'Tag' 10 | other: 'Tags' 11 | post: 12 | one: 'Post' 13 | other: 'Posts' 14 | page: 15 | one: 'Page' 16 | other: 'Pages' 17 | prev: 'Previous' 18 | next: 'Next' 19 | widget: 20 | follow: 'Follow' 21 | recents: 'Recents' 22 | links: 'Links' 23 | catalogue: 'Catalogue' 24 | subscribe_email: 'Subscribe for updates' 25 | subscribe: 'Subscribe' 26 | adsense: 'Advertisement' 27 | followit: 'follow.it' 28 | article: 29 | created_at: 'Posted %s' 30 | updated_at: 'Updated %s' 31 | more: 'Read more' 32 | comments: 'Comments' 33 | read_time: '%s read' 34 | word_count: 35 | one: 'About %d word' 36 | other: 'About %d words' 37 | licensing: 38 | author: 'Author' 39 | created_at: 'Posted on' 40 | updated_at: 'Updated on' 41 | licensed_under: 'Licensed under' 42 | donate: 43 | title: 'Like this article? Support the author with' 44 | afdian: 'Afdian.net' 45 | alipay: 'Alipay' 46 | wechat: 'Wechat' 47 | paypal: 'Paypal' 48 | patreon: 'Patreon' 49 | buymeacoffee: 'Buy me a coffee' 50 | plugin: 51 | backtotop: 'Back to top' 52 | visit_count: '%s visits' 53 | visitor_count: 'Visited by %s users' 54 | cookie_consent: 55 | message: This website uses cookies to improve your experience. 56 | dismiss: Got it! 57 | allow: Allow cookies 58 | deny: Decline 59 | link: Learn more 60 | policy: Cookie Policy 61 | search: 62 | search: 'Search' 63 | hint: 'Type something...' 64 | no_result: 'No results for' 65 | untitled: '(Untitled)' 66 | empty_preview: '(No preview)' 67 | -------------------------------------------------------------------------------- /languages/es.yml: -------------------------------------------------------------------------------- 1 | #By SrWoOoW 2 | common: 3 | archive: 4 | one: 'Archivo' 5 | other: 'Archivos' 6 | category: 7 | one: 'Categoría' 8 | other: 'Categorías' 9 | tag: 10 | one: 'Etiqueta' 11 | other: 'Etiquetas' 12 | post: 13 | one: 'Entrada' 14 | other: 'Entradas' 15 | page: 16 | one: 'Página' 17 | other: 'Páginas' 18 | prev: 'Anterior' 19 | next: 'Siguiente' 20 | widget: 21 | follow: 'SEGUIR' 22 | recents: 'Recientes' 23 | links: 'Enlaces' 24 | catalogue: 'Catálogo' 25 | subscribe_email: 'Suscríbete para recibir actualizaciones' 26 | subscribe: 'Suscribir' 27 | adsense: 'Anuncio' 28 | followit: 'follow.it' 29 | article: 30 | created_at: 'Publicado hace %s' 31 | updated_at: 'Actualizado hace %s' 32 | more: 'Leer más' 33 | comments: 'Comentarios' 34 | read_time: '%s de lectura' 35 | word_count: 36 | one: 'Aproximadamente %d palabra' 37 | other: 'Aproximadamente %d palabras' 38 | licensing: 39 | author: 'Author' 40 | created_at: 'Posted on' 41 | updated_at: 'Updated on' 42 | licensed_under: 'Licensed under' 43 | donate: 44 | title: '¿Te gusta este artículo? Apoya al autor con' 45 | afdian: 'Afdian.net' 46 | alipay: 'Alipay' 47 | wechat: 'Wechat' 48 | paypal: 'Paypal' 49 | patreon: 'Patreon' 50 | buymeacoffee: 'Cómprame un café' 51 | plugin: 52 | backtotop: 'Volver arriba' 53 | visit_count: '%s visitas' 54 | visitor_count: 'Visitado por %s usuarios' 55 | cookie_consent: 56 | message: Este sitio web utiliza cookies para mejorar su experiencia. 57 | dismiss: ¡Entendido! 58 | allow: Permitir cookies 59 | deny: Descenso 60 | link: Aprende más 61 | policy: Política de cookies 62 | search: 63 | search: 'Buscar' 64 | hint: 'Teclea algo...' 65 | no_result: 'No hay resultados para' 66 | untitled: '(Sin título)' 67 | empty_preview: '(Sin vista previa)' 68 | -------------------------------------------------------------------------------- /languages/fr.yml: -------------------------------------------------------------------------------- 1 | common: 2 | archive: 3 | one: 'Archive' 4 | other: 'Archives' 5 | category: 6 | one: 'Catégorie' 7 | other: 'Catégories' 8 | tag: 9 | one: 'Tag' 10 | other: 'Tags' 11 | post: 12 | one: 'Article' 13 | other: 'Articles' 14 | page: 15 | one: 'Page' 16 | other: 'Pages' 17 | prev: 'Préc' 18 | next: 'Suiv' 19 | widget: 20 | follow: 'Suivre' 21 | recents: 'Récents' 22 | links: 'Liens' 23 | catalogue: 'Catalogue' 24 | subscribe_email: 'Abonnez-vous aux mises à jour' 25 | subscribe: 'Abonnez-vous' 26 | adsense: 'Publicités' 27 | followit: 'follow.it' 28 | article: 29 | created_at: 'Publié il y a %s' 30 | updated_at: 'Mis à jour il y a %s' 31 | more: 'Lire la suite' 32 | comments: 'Commentaires' 33 | read_time: '%s de lecture' 34 | word_count: 35 | one: 'Environ %d mot' 36 | other: 'Environ %d mots' 37 | licensing: 38 | author: 'Auteur' 39 | created_at: 'Posté le' 40 | updated_at: 'Mis à jour le' 41 | licensed_under: 'Licencié sous' 42 | donate: 43 | title: "Vous aimez cet article? Soutenez l'auteur avec" 44 | afdian: 'Afdian.net' 45 | alipay: 'Alipay' 46 | wechat: 'Wechat' 47 | paypal: 'Paypal' 48 | patreon: 'Patreon' 49 | buymeacoffee: 'Achetez-moi un café' 50 | plugin: 51 | backtotop: 'Retour en haut' 52 | visit_count: '%s visites' 53 | visitor_count: 'Visité par %s utilisateurs' 54 | cookie_consent: 55 | message: 'Ce site Web utilise des cookies pour améliorer votre expérience.' 56 | dismiss: "C'est bon" 57 | allow: 'Autoriser les cookies' 58 | deny: 'Refuser' 59 | link: 'En savoir plus' 60 | policy: 'Politique relative aux cookies' 61 | search: 62 | search: 'Rechercher' 63 | hint: 'Ecrivez quelque chose...' 64 | no_result: 'Aucun résultat pour' 65 | untitled: '(Sans titre)' 66 | empty_preview: '(Pas de prévisualisation)' 67 | -------------------------------------------------------------------------------- /languages/id.yml: -------------------------------------------------------------------------------- 1 | common: 2 | archive: 3 | one: 'Arsip' 4 | other: 'Arsip' 5 | category: 6 | one: 'Kategori' 7 | other: 'Kategori' 8 | tag: 9 | one: 'Tag' 10 | other: 'Tag' 11 | post: 12 | one: 'Artikel' 13 | other: 'Artikel' 14 | page: 15 | one: 'Halaman' 16 | other: 'Halaman' 17 | prev: 'Sebelumnya' 18 | next: 'Berikutnya' 19 | widget: 20 | follow: 'IKUTI' 21 | recents: 'Terbaru' 22 | links: 'Tautan' 23 | catalogue: 'Katalog' 24 | subscribe_email: 'Berlangganan untuk pembaruan' 25 | subscribe: 'Berlangganan' 26 | adsense: 'Iklan' 27 | followit: 'follow.it' 28 | article: 29 | created_at: 'Diposting %s' 30 | updated_at: 'Diperbarui %s' 31 | more: 'Selengkapnya' 32 | comments: 'Komentar' 33 | read_time: '%s membaca' 34 | word_count: 35 | one: 'Sekitar %d kata' 36 | other: 'Sekitar %d kata' 37 | licensing: 38 | author: 'Author' 39 | created_at: 'Posted on' 40 | updated_at: 'Updated on' 41 | licensed_under: 'Licensed under' 42 | donate: 43 | title: 'Suka dengan artikel ini? Bantu penulis dengan donasi melalui' 44 | afdian: 'Afdian.net' 45 | alipay: 'Alipay' 46 | wechat: 'Wechat' 47 | paypal: 'Paypal' 48 | patreon: 'Patreon' 49 | buymeacoffee: 'Belikan aku kopi' 50 | plugin: 51 | backtotop: 'Kembali ke atas' 52 | visit_count: '%s kunjungan' 53 | visitor_count: 'Dikunjungi oleh %s pengguna' 54 | cookie_consent: 55 | message: Situs web ini menggunakan cookie untuk meningkatkan pengalaman Anda. 56 | dismiss: Mengerti! 57 | allow: Izinkan cookie 58 | deny: Menolak 59 | link: Belajarlah lagi 60 | policy: Kebijakan Cookie 61 | search: 62 | search: 'Pencarian' 63 | hint: 'Tulis Sesuatu..' 64 | no_result: 'Tidak ada hasil untuk' 65 | untitled: '(Tanpa judul)' 66 | empty_preview: '(Tidak ada preview)' 67 | -------------------------------------------------------------------------------- /languages/it.yml: -------------------------------------------------------------------------------- 1 | common: 2 | archive: 3 | one: 'Archivio' 4 | other: 'Archivi' 5 | category: 6 | one: 'Categoria' 7 | other: 'Categorie' 8 | tag: 9 | one: 'Tag' 10 | other: 'Tag' 11 | post: 12 | one: 'Articolo' 13 | other: 'Articoli' 14 | page: 15 | one: 'Pagina' 16 | other: 'Pagine' 17 | prev: 'Precedente' 18 | next: 'Successivo' 19 | widget: 20 | follow: 'Segui' 21 | recents: 'Recenti' 22 | links: 'Collegamenti' 23 | catalogue: 'Catalogo' 24 | subscribe_email: 'Iscriviti per aggiornamenti' 25 | subscribe: 'Iscriviti' 26 | adsense: 'Pubblicità' 27 | followit: 'follow.it' 28 | article: 29 | created_at: 'Pubblicato il %s' 30 | updated_at: 'Aggiornato il %s' 31 | more: 'Di più' 32 | comments: 'Commenti' 33 | read_time: '%s di lettura' 34 | word_count: 35 | one: 'Circa %d parola' 36 | other: 'Circa %d parole' 37 | licensing: 38 | author: 'Autore' 39 | created_at: 'Pubblicato il' 40 | updated_at: 'Aggiornato il' 41 | licensed_under: 'Licenza' 42 | donate: 43 | title: "Ti è piaciuto questo articolo? Supporta l'autore con" 44 | afdian: 'Afdian.net' 45 | alipay: 'Alipay' 46 | wechat: 'Wechat' 47 | paypal: 'Paypal' 48 | patreon: 'Patreon' 49 | buymeacoffee: 'Buy me a coffee' 50 | plugin: 51 | backtotop: 'Torna su' 52 | visit_count: '%s visite' 53 | visitor_count: 'Visto da %s usenti' 54 | cookie_consent: 55 | message: Questo sito usa i cookie per migliorare la tua esperienza. 56 | dismiss: "D'accordo!" 57 | allow: Accetta i cookie 58 | deny: Rifiuta 59 | link: Più informazioni 60 | policy: Politica dei cookie 61 | search: 62 | search: 'Cerca' 63 | hint: 'Digita qualcosa...' 64 | no_result: 'Nessun risultato per' 65 | untitled: '(Senza titolo)' 66 | empty_preview: '(Senza anteprima)' 67 | -------------------------------------------------------------------------------- /languages/ja.yml: -------------------------------------------------------------------------------- 1 | common: 2 | archive: 3 | one: 'アーカイブ' 4 | other: 'アーカイブ' 5 | category: 6 | one: 'カテゴリ' 7 | other: 'カテゴリ' 8 | tag: 9 | one: 'タグ' 10 | other: 'タグ' 11 | post: 12 | one: '投稿' 13 | other: '投稿' 14 | page: 15 | one: 'ページ' 16 | other: 'ページ' 17 | prev: '前' 18 | next: '次' 19 | widget: 20 | follow: 'フォローする' 21 | recents: '最近の記事' 22 | links: 'リンク' 23 | catalogue: 'カタログ' 24 | subscribe_email: '更新を購読する' 25 | subscribe: '購読する' 26 | adsense: '広告' 27 | followit: 'follow.it' 28 | article: 29 | created_at: '%sに投稿' 30 | updated_at: '%sに更新' 31 | more: '続きを読む' 32 | comments: 'コメント' 33 | read_time: '%sで読む' 34 | word_count: 35 | one: '約%d語' 36 | other: '約%d語' 37 | licensing: 38 | author: 'Author' 39 | created_at: 'Posted on' 40 | updated_at: 'Updated on' 41 | licensed_under: 'Licensed under' 42 | donate: 43 | title: 'この記事は気に入りましたか? 著者をサポートする' 44 | afdian: 'Afdian.net' 45 | alipay: 'Alipay' 46 | wechat: 'Wechat' 47 | paypal: 'Paypal' 48 | patreon: 'Patreon' 49 | buymeacoffee: 'コーヒーを買って' 50 | plugin: 51 | backtotop: 'トップに戻る' 52 | visit_count: '%s回の訪問' 53 | visitor_count: '%s人のユーザーがアクセス' 54 | cookie_consent: 55 | message: このウェブサイトはあなたの経験を改善するためにCookieを使用しています。 56 | dismiss: 了解しました 57 | allow: Cookiesを許可する 58 | deny: 拒否する 59 | link: もっと詳しく知る 60 | policy: Cookieポリシー 61 | search: 62 | search: '検索' 63 | hint: '何かを入力してください...' 64 | no_result: 'の結果はありません' 65 | untitled: '(無題)' 66 | empty_preview: '(プレビューなし)' 67 | -------------------------------------------------------------------------------- /languages/ko.yml: -------------------------------------------------------------------------------- 1 | common: 2 | archive: 3 | one: '아카이브' 4 | other: '아카이브' 5 | category: 6 | one: '카테고리' 7 | other: '카테고리' 8 | tag: 9 | one: '태그' 10 | other: '태그' 11 | post: 12 | one: '포스트' 13 | other: '포스트' 14 | page: 15 | one: '페이지' 16 | other: '페이지' 17 | prev: '이전' 18 | next: '다음' 19 | widget: 20 | follow: '팔로우' 21 | recents: '최근 글' 22 | links: '링크' 23 | catalogue: '카탈로그' 24 | subscribe_email: '업데이트 소식 받기' 25 | subscribe: '구독' 26 | adsense: '광고' 27 | followit: 'follow.it' 28 | article: 29 | created_at: '%s 게시 됨' 30 | updated_at: '%s 업데이트 됨' 31 | more: '자세히 보기' 32 | comments: '댓글' 33 | read_time: '%s안에 읽기' 34 | word_count: 35 | one: '약 %d 단어' 36 | other: '약 %d 단어' 37 | licensing: 38 | author: 'Author' 39 | created_at: 'Posted on' 40 | updated_at: 'Updated on' 41 | licensed_under: 'Licensed under' 42 | donate: 43 | title: '이 글이 마음에 드시나요? 다음을 통해 후원하실 수 있습니다: ' 44 | afdian: 'Afdian.net' 45 | alipay: 'Alipay' 46 | wechat: 'Wechat' 47 | paypal: 'Paypal' 48 | patreon: 'Patreon' 49 | buymeacoffee: '커피 한 잔 사주기' 50 | plugin: 51 | backtotop: '맨 위로' 52 | visit_count: '%s회 방문' 53 | visitor_count: '%s명의 사용자가 방문 함' 54 | cookie_consent: 55 | message: 이 웹 사이트는 귀하의 경험을 향상시키기 위해 Cookie를 사용합니다. 56 | dismiss: 무시 57 | allow: 허용 58 | deny: 거부 59 | link: 더 알아보기 60 | policy: Cookie 정책 61 | search: 62 | search: '검색' 63 | hint: '입력 하세요...' 64 | no_result: '에 대한 결과 없음' 65 | untitled: '(제목 없음)' 66 | empty_preview: '(미리보기 없음)' 67 | -------------------------------------------------------------------------------- /languages/pl.yml: -------------------------------------------------------------------------------- 1 | common: 2 | archive: 3 | one: 'Archiwum' 4 | other: 'Archiwum' 5 | category: 6 | one: 'Kategoria' 7 | other: 'Kategorie' 8 | tag: 9 | one: 'Tag' 10 | other: 'Tagi' 11 | post: 12 | one: 'Artykuł' 13 | other: 'Artykuły' 14 | page: 15 | one: 'Strona' 16 | other: 'Strony' 17 | prev: 'Poprzedni' 18 | next: 'Następny' 19 | widget: 20 | follow: 'SUBSKRYBUJ' 21 | recents: 'Najnowsze wpisy' 22 | links: 'Linki' 23 | catalogue: 'Spis treści' 24 | subscribe_email: 'Zapisz się, aby otrzymywać aktualizacje' 25 | subscribe: 'Subskrybuj' 26 | adsense: 'Reklama' 27 | followit: 'follow.it' 28 | article: 29 | created_at: 'Opublikowano %s' 30 | updated_at: 'Zaktualizowano %s' 31 | more: 'Czytaj dalej' 32 | comments: 'Komentarze' 33 | read_time: '%s czytania' 34 | word_count: 35 | one: 'Około %d słowa' 36 | other: 'Około %d słów' 37 | licensing: 38 | author: 'Author' 39 | created_at: 'Posted on' 40 | updated_at: 'Updated on' 41 | licensed_under: 'Licensed under' 42 | donate: 43 | title: 'Podoba Ci się ten artykuł? Wesprzyj autora za pomocą' 44 | afdian: 'Afdian.net' 45 | alipay: 'Alipay' 46 | wechat: 'Wechat' 47 | paypal: 'Paypal' 48 | patreon: 'Patreon' 49 | buymeacoffee: 'Kup mi kawę' 50 | plugin: 51 | backtotop: 'Powrót do góry' 52 | visit_count: '%s wizyty' 53 | visitor_count: 'Odwiedzone przez %s użytkowników' 54 | cookie_consent: 55 | message: Ta strona korzysta z plików cookie, aby poprawić Twoje doświadczenia. 56 | dismiss: Rozumiem! 57 | allow: Zezwól na pliki cookie 58 | deny: Odrzucać 59 | link: Ucz się więcej 60 | policy: Polityka Cookie 61 | search: 62 | search: 'szukaj' 63 | hint: 'Wpisz coś...' 64 | no_result: 'Brak wyników dla' 65 | untitled: '(Bez tytułu)' 66 | empty_preview: '(Brak podglądu)' 67 | -------------------------------------------------------------------------------- /languages/pt-BR.yml: -------------------------------------------------------------------------------- 1 | common: 2 | archive: 3 | one: 'Arquivo' 4 | other: 'Arquivos' 5 | category: 6 | one: 'Categoria' 7 | other: 'Categorias' 8 | tag: 9 | one: 'Tag' 10 | other: 'Tags' 11 | post: 12 | one: 'Artigo' 13 | other: 'Artigos' 14 | page: 15 | one: 'Página' 16 | other: 'Páginas' 17 | prev: 'Anterior' 18 | next: 'Próximo' 19 | widget: 20 | follow: 'Seguir' 21 | recents: 'Recentes' 22 | links: 'Links' 23 | catalogue: 'Catálogo' 24 | subscribe_email: 'Subscrição de atualizações' 25 | subscribe: 'Se inscrever' 26 | adsense: 'Anúncio' 27 | followit: 'follow.it' 28 | article: 29 | created_at: 'Postado %s' 30 | updated_at: 'Atualizado %s' 31 | more: 'Ler Mais' 32 | comments: 'Comentarios' 33 | read_time: '%s lidos' 34 | word_count: 35 | one: 'Cerca de %d palavra' 36 | other: 'Cerca de %d palavras' 37 | licensing: 38 | author: 'Author' 39 | created_at: 'Posted on' 40 | updated_at: 'Updated on' 41 | licensed_under: 'Licensed under' 42 | donate: 43 | title: 'Gostou deste artigo? Apoie o autor com' 44 | afdian: 'Afdian.net' 45 | alipay: 'Alipay' 46 | wechat: 'Wechat' 47 | paypal: 'Paypal' 48 | patreon: 'Patreon' 49 | buymeacoffee: 'Me compra um café' 50 | plugin: 51 | backtotop: 'De volta ao topo' 52 | visit_count: '%s visitas' 53 | visitor_count: 'Visitado por %s usuários' 54 | cookie_consent: 55 | message: Este site usa cookies para melhorar sua experiência. 56 | dismiss: Entendi! 57 | allow: Permitir cookies 58 | deny: Declínio 59 | link: Saber mais 60 | policy: Política de Cookies 61 | search: 62 | search: 'Procurar' 63 | hint: 'Digite alguma coisa...' 64 | no_result: 'Sem resultados para' 65 | untitled: '(Sem título)' 66 | empty_preview: '(Não há visualização)' 67 | -------------------------------------------------------------------------------- /languages/ru.yml: -------------------------------------------------------------------------------- 1 | common: 2 | archive: 3 | one: 'архив' 4 | other: 'архивы' 5 | category: 6 | one: 'категории' 7 | other: 'категории' 8 | tag: 9 | one: 'тег' 10 | other: 'теги' 11 | post: 12 | one: 'пост' 13 | other: 'посты' 14 | page: 15 | one: 'страница' 16 | other: 'страницы' 17 | prev: 'Назад' 18 | next: 'Далее' 19 | widget: 20 | follow: 'Подписаться' 21 | recents: 'недавние' 22 | links: 'ссылки' 23 | catalogue: 'Каталог' 24 | subscribe_email: 'Подпишитесь на обновления' 25 | subscribe: 'Подписывайся' 26 | adsense: 'Рекламное объявление' 27 | followit: 'follow.it' 28 | article: 29 | created_at: 'Опубликовано %s' 30 | updated_at: 'Обновлено %s' 31 | more: 'Читать дальше' 32 | comments: 'Комментарии' 33 | read_time: '%s на чтение' 34 | word_count: 35 | one: 'Около %d слова' 36 | other: 'Примерно %d слова' 37 | licensing: 38 | author: 'Author' 39 | created_at: 'Posted on' 40 | updated_at: 'Updated on' 41 | licensed_under: 'Licensed under' 42 | donate: 43 | title: 'Понравилась эта статья? Поддержите автора' 44 | afdian: 'Afdian.net' 45 | alipay: 'Alipay' 46 | wechat: 'Wechat' 47 | paypal: 'Paypal' 48 | patreon: 'Patreon' 49 | buymeacoffee: 'Купи мне кофе' 50 | plugin: 51 | backtotop: 'Вернуться наверх' 52 | visit_count: '%s посещения' 53 | visitor_count: 'Посетили %s пользователя' 54 | cookie_consent: 55 | message: Этот веб-сайт использует файлы cookie для улучшения вашего опыта. 56 | dismiss: Понял! 57 | allow: Разрешить cookies 58 | deny: Отказаться 59 | link: Учить больше 60 | policy: Политика Cookie 61 | search: 62 | search: 'Поиск' 63 | hint: 'Введите что-нибудь...' 64 | no_result: 'Нет результатов по запросу' 65 | untitled: '(Без названия)' 66 | empty_preview: '(Нет предварительного просмотра)' 67 | -------------------------------------------------------------------------------- /languages/sv.yml: -------------------------------------------------------------------------------- 1 | common: 2 | archive: 3 | one: 'Arkiv' 4 | other: 'Arkiv' 5 | category: 6 | one: 'Kategori' 7 | other: 'Kategorier' 8 | tag: 9 | one: 'Etikett' 10 | other: 'Etiketter' 11 | post: 12 | one: 'Inlägg' 13 | other: 'Inlägg' 14 | page: 15 | one: 'Sida' 16 | other: 'Sidor' 17 | prev: 'Föregående' 18 | next: 'Nästa' 19 | widget: 20 | follow: 'Följ' 21 | recents: 'Senaste' 22 | links: 'Länkar' 23 | catalogue: 'Katalog' 24 | subscribe_email: 'Prenumerera för uppdateringar' 25 | subscribe: 'Prenumerera' 26 | adsense: 'Marknadsföring' 27 | followit: 'follow.it' 28 | article: 29 | created_at: 'Publicerad %s' 30 | updated_at: 'Uppdaterad %s' 31 | more: 'Läs mer' 32 | comments: 'Kommentarer' 33 | read_time: '%s lästid' 34 | word_count: 35 | one: 'Cirka %d ord' 36 | other: 'Cirka %d ord' 37 | licensing: 38 | author: 'Författare' 39 | created_at: 'Publicerad' 40 | updated_at: 'Uppdaterad' 41 | licensed_under: 'Licensierad under' 42 | donate: 43 | title: 'Tycker du om den här artikeln? Stöd författaren genom' 44 | afdian: 'Afdian.net' 45 | alipay: 'Alipay' 46 | wechat: 'Wechat' 47 | paypal: 'Paypal' 48 | patreon: 'Patreon' 49 | buymeacoffee: 'Köp mig en kaffe' 50 | plugin: 51 | backtotop: 'Tillbaka till början' 52 | visit_count: '%s besök' 53 | visitor_count: 'Besökt av %s användare' 54 | cookie_consent: 55 | message: Den här hemsidan använder kakor för att förbättra funktionen. 56 | dismiss: Jag förstår! 57 | allow: Tillåt kakor 58 | deny: Avböj kakor 59 | link: Lär mer 60 | policy: Kakpolicy 61 | search: 62 | search: 'Sök' 63 | hint: 'Skriv någonting...' 64 | no_result: 'Inga sökresultat för' 65 | untitled: '(Utan titel)' 66 | empty_preview: '(Ingen förhandsvisning)' 67 | -------------------------------------------------------------------------------- /languages/tk.yml: -------------------------------------------------------------------------------- 1 | common: 2 | archive: 3 | one: 'Arhiw' 4 | other: 'Arhiwler' 5 | category: 6 | one: 'Bölüm' 7 | other: 'Bölümler' 8 | tag: 9 | one: 'Teg' 10 | other: 'Tegler' 11 | post: 12 | one: 'Post' 13 | other: 'Postlar' 14 | page: 15 | one: 'Sahypa' 16 | other: 'Sahypalar' 17 | prev: 'Öňki' 18 | next: 'Indiki' 19 | widget: 20 | follow: 'Abuna bol' 21 | recents: 'Täze habarlar' 22 | links: 'Linkler' 23 | catalogue: 'Katalog' 24 | subscribe_email: 'Täzelikler üçin ýazyl' 25 | subscribe: 'Ýazyl' 26 | adsense: 'Mahabat' 27 | followit: 'follow.it' 28 | article: 29 | created_at: 'Paýlaşyldy %s' 30 | updated_at: 'Üýtgedildi %s' 31 | more: 'Dowamy...' 32 | comments: 'Kommentariýa' 33 | read_time: '%s okaldy' 34 | word_count: 35 | one: 'Ortaça %d söz' 36 | other: 'Ortaça %d söz' 37 | licensing: 38 | author: 'Awtor' 39 | created_at: 'Paýlaşdy' 40 | updated_at: 'Üýtgetdi' 41 | licensed_under: 'Resmileşdirilen' 42 | donate: 43 | title: 'Haladynmy? Awtory gollaň' 44 | afdian: 'Afdian.net' 45 | alipay: 'Alipay' 46 | wechat: 'Wechat' 47 | paypal: 'Paypal' 48 | patreon: 'Patreon' 49 | buymeacoffee: 'Buy me a coffee' 50 | plugin: 51 | backtotop: 'Ýokaryk' 52 | visit_count: '%s görüldi' 53 | visitor_count: '%s adam gördi' 54 | cookie_consent: 55 | message: Bu web saýt siziň üçin kuki ulanýar. 56 | dismiss: Düşündim! 57 | allow: Kukini kabul et! 58 | deny: Närazylyk bildir 59 | link: Dowamy... 60 | policy: Kuki syýasaty 61 | search: 62 | search: 'Gözle' 63 | hint: 'Birzatlar ýazyň...' 64 | no_result: 'Tapylmady' 65 | untitled: 'Atlandyrylmadyk' 66 | empty_preview: 'Boş' 67 | -------------------------------------------------------------------------------- /languages/tr.yml: -------------------------------------------------------------------------------- 1 | common: 2 | archive: 3 | one: 'Arşiv' 4 | other: 'Arşivler' 5 | category: 6 | one: 'Kategori' 7 | other: 'Kategoriler' 8 | tag: 9 | one: 'Etiket' 10 | other: 'Etiketler' 11 | post: 12 | one: 'Gönderi' 13 | other: 'Gönderiler' 14 | page: 15 | one: 'Sayfa' 16 | other: 'Sayfalar' 17 | prev: 'Önceki' 18 | next: 'Sonraki' 19 | widget: 20 | follow: 'TAKİP ET' 21 | recents: 'Son' 22 | links: 'Linkler' 23 | catalogue: 'Katalog' 24 | subscribe_email: 'Güncellemeler için abone olun' 25 | subscribe: 'Abone ol' 26 | adsense: 'İlan' 27 | followit: 'follow.it' 28 | article: 29 | created_at: '%s yayınlandı' 30 | updated_at: '%s güncellendi' 31 | more: 'Daha fazla oku' 32 | comments: 'Yorumlar' 33 | read_time: '%s okuma süresi' 34 | word_count: 35 | one: 'Yaklaşık %d kelime' 36 | other: 'Yaklaşık %d kelime' 37 | licensing: 38 | author: 'Author' 39 | created_at: 'Posted on' 40 | updated_at: 'Updated on' 41 | licensed_under: 'Licensed under' 42 | donate: 43 | title: 'Bu makaleyi beğendiniz mi? Yazarı şununla destekleyin' 44 | afdian: 'Afdian.net' 45 | alipay: 'Alipay' 46 | wechat: 'Wechat' 47 | paypal: 'Paypal' 48 | patreon: 'Patreon' 49 | buymeacoffee: 'Bana bir kahve al' 50 | plugin: 51 | backtotop: 'Başa dönüş' 52 | visit_count: '%s ziyaret' 53 | visitor_count: '%s kullanıcı tarafından ziyaret edildi' 54 | cookie_consent: 55 | message: Bu web sitesi, deneyiminizi geliştirmek için çerezler kullanır. 56 | dismiss: Anladım! 57 | allow: Çerezlere izin ver 58 | deny: Reddet 59 | link: Daha fazla bilgi edin 60 | policy: Çerez politikası 61 | search: 62 | search: 'Ara' 63 | hint: 'Bir şeyler yaz...' 64 | no_result: 'İçin sonuç yok' 65 | untitled: '(Başlıksız)' 66 | empty_preview: '(Önizleme yok)' 67 | -------------------------------------------------------------------------------- /languages/vn.yml: -------------------------------------------------------------------------------- 1 | common: 2 | archive: 3 | one: 'Lưu trữ' 4 | other: 'Lưu trữ' 5 | category: 6 | one: 'Thể loại' 7 | other: 'Thể loại' 8 | tag: 9 | one: 'Nhãn' 10 | other: 'Nhãn' 11 | post: 12 | one: 'Bài viết' 13 | other: 'Bài viết' 14 | page: 15 | one: 'Trang' 16 | other: 'Trang' 17 | prev: 'Trước' 18 | next: 'Sau' 19 | widget: 20 | follow: 'Theo dõi' 21 | recents: 'Gần đây' 22 | links: 'Link' 23 | catalogue: 'Mục lục' 24 | subscribe_email: 'Theo dõi các bản cập nhật' 25 | subscribe: 'Theo dõi' 26 | adsense: 'Quảng cáo' 27 | followit: 'follow.it' 28 | article: 29 | created_at: 'Đã đăng %s' 30 | updated_at: 'Đã cập nhật %s' 31 | more: 'Đọc thêm' 32 | comments: 'Bình luận' 33 | read_time: '%s đọc' 34 | word_count: 35 | one: 'Khoảng %d từ' 36 | other: 'Khoảng %d từ' 37 | licensing: 38 | author: 'Author' 39 | created_at: 'Posted on' 40 | updated_at: 'Updated on' 41 | licensed_under: 'Licensed under' 42 | donate: 43 | title: 'Bạn đọc có thể ủng hộ blog qua' 44 | afdian: 'Afdian.net' 45 | alipay: 'Alipay' 46 | wechat: 'Wechat' 47 | paypal: 'Paypal' 48 | patreon: 'Patreon' 49 | buymeacoffee: 'Mua cho tôi một ly cà phê' 50 | plugin: 51 | backtotop: 'Trở lai đầu trang' 52 | visit_count: '%s Bạn đọc' 53 | visitor_count: 'Thăm bởi %s bạn đọc' 54 | cookie_consent: 55 | message: Trang web này sử dụng cookie để cải thiện trải nghiệm của bạn. 56 | dismiss: Hiểu rồi! 57 | allow: Cho phép cookie 58 | deny: Từ chối 59 | link: Tìm hiểu thêm 60 | policy: Chính sách Cookie 61 | search: 62 | search: 'Tìm kiếm' 63 | hint: 'Gõ gì đó...' 64 | no_result: 'Không có kết quả cho' 65 | untitled: '(Không có tiêu đề)' 66 | empty_preview: '(Không có xem trước)' 67 | -------------------------------------------------------------------------------- /languages/zh-CN.yml: -------------------------------------------------------------------------------- 1 | common: 2 | archive: 3 | one: '归档' 4 | other: '归档' 5 | category: 6 | one: '分类' 7 | other: '分类' 8 | tag: 9 | one: '标签' 10 | other: '标签' 11 | post: 12 | one: '文章' 13 | other: '文章' 14 | page: 15 | one: '页面' 16 | other: '页面' 17 | prev: '上一页' 18 | next: '下一页' 19 | widget: 20 | follow: '关注我' 21 | recents: '最新文章' 22 | links: '链接' 23 | catalogue: '目录' 24 | subscribe_email: '订阅更新' 25 | subscribe: '订阅' 26 | adsense: '广告' 27 | followit: 'follow.it' 28 | article: 29 | created_at: '%s发表' 30 | updated_at: '%s更新' 31 | more: '阅读更多' 32 | comments: '评论' 33 | read_time: '%s读完' 34 | word_count: 35 | one: '大约%d个字' 36 | other: '大约%d个字' 37 | licensing: 38 | author: '作者' 39 | created_at: '发布于' 40 | updated_at: '更新于' 41 | licensed_under: '许可协议' 42 | donate: 43 | title: '喜欢这篇文章?打赏一下作者吧' 44 | afdian: '爱发电' 45 | alipay: '支付宝' 46 | wechat: '微信' 47 | paypal: 'Paypal' 48 | patreon: 'Patreon' 49 | buymeacoffee: '送我杯咖啡' 50 | plugin: 51 | backtotop: '回到顶端' 52 | visit_count: '%s次访问' 53 | visitor_count: '共%s个访客' 54 | cookie_consent: 55 | message: 此网站使用Cookie来改善您的体验。 56 | dismiss: 知道了! 57 | allow: 允许使用Cookie 58 | deny: 拒绝 59 | link: 了解更多 60 | policy: Cookie政策 61 | search: 62 | search: '搜索' 63 | hint: '想要查找什么...' 64 | no_result: '未找到搜索结果' 65 | untitled: '(无标题)' 66 | empty_preview: '(无内容预览)' 67 | -------------------------------------------------------------------------------- /languages/zh-TW.yml: -------------------------------------------------------------------------------- 1 | common: 2 | archive: 3 | one: '彙整' 4 | other: '彙整' 5 | category: 6 | one: '分類' 7 | other: '分類' 8 | tag: 9 | one: '標籤' 10 | other: '標籤' 11 | post: 12 | one: '文章' 13 | other: '文章' 14 | page: 15 | one: '頁面' 16 | other: '頁面' 17 | prev: '上一頁' 18 | next: '下一頁' 19 | widget: 20 | follow: '追蹤' 21 | recents: '最新文章' 22 | links: '連結' 23 | catalogue: '文章目錄' 24 | subscribe_email: '訂閱 Email' 25 | subscribe: '訂閱' 26 | adsense: '廣告' 27 | followit: 'follow.it' 28 | article: 29 | created_at: '%s發表' 30 | updated_at: '%s更新' 31 | more: '繼續閱讀' 32 | comments: '評論' 33 | read_time: '%s讀完' 34 | word_count: 35 | one: '大約%d個字' 36 | other: '大約%d個字' 37 | licensing: 38 | author: '作者' 39 | created_at: '發表於' 40 | updated_at: '更新於' 41 | licensed_under: '許可協議' 42 | donate: 43 | title: '喜歡這篇文章嗎? 贊助一下作者吧!' 44 | afdian: '愛發電' 45 | alipay: '支付寶' 46 | wechat: 'WeChat' 47 | paypal: 'PayPal' 48 | patreon: 'Patreon' 49 | buymeacoffee: '送我杯咖啡' 50 | plugin: 51 | backtotop: '回到頁首' 52 | visit_count: '%s次訪問' 53 | visitor_count: '共%s個訪客' 54 | cookie_consent: 55 | message: 此網站使用Cookie來改善您的體驗。 56 | dismiss: 知道了! 57 | allow: 允許使用Cookie 58 | deny: 拒絕 59 | link: 了解更多 60 | policy: Cookie政策 61 | search: 62 | search: '搜尋' 63 | hint: '請輸入關鍵字...' 64 | no_result: '未找到搜索結果' 65 | untitled: '(無標題)' 66 | empty_preview: '(無內容預覽)' 67 | -------------------------------------------------------------------------------- /layout/archive.jsx: -------------------------------------------------------------------------------- 1 | const moment = require('moment'); 2 | const { Component, Fragment } = require('inferno'); 3 | const { toMomentLocale } = require('hexo/dist/plugins/helper/date'); 4 | const Paginator = require('hexo-component-inferno/lib/view/misc/paginator'); 5 | const ArticleMedia = require('hexo-component-inferno/lib/view/common/article_media'); 6 | 7 | module.exports = class extends Component { 8 | render() { 9 | const { config, page, helper } = this.props; 10 | const { url_for, __, date_xml, date } = helper; 11 | 12 | const language = toMomentLocale(page.lang || page.language || config.language); 13 | 14 | function renderArticleList(posts, year, month = null) { 15 | const time = moment([page.year, page.month ? page.month - 1 : null].filter(i => i !== null)); 16 | 17 | return <div class="card"> 18 | <div class="card-content"> 19 | <h3 class="tag is-primary">{month === null ? year : time.locale(language).format('MMMM YYYY')}</h3> 20 | <div class="timeline"> 21 | {posts.map(post => { 22 | const categories = post.categories.map(category => ({ 23 | url: url_for(category.path), 24 | name: category.name 25 | })); 26 | return <ArticleMedia 27 | url={url_for(post.link || post.path)} 28 | title={post.title} 29 | date={date(post.date)} 30 | dateXml={date_xml(post.date)} 31 | categories={categories} 32 | thumbnail={post.thumbnail ? url_for(post.thumbnail) : null} />; 33 | })} 34 | </div> 35 | </div> 36 | </div>; 37 | } 38 | 39 | let articleList; 40 | if (!page.year) { 41 | const years = {}; 42 | page.posts.each(p => { years[p.date.year()] = null; }); 43 | articleList = Object.keys(years).sort((a, b) => b - a).map(year => { 44 | const posts = page.posts.filter(p => p.date.year() === parseInt(year, 10)); 45 | return renderArticleList(posts, year, null); 46 | }); 47 | } else { 48 | articleList = renderArticleList(page.posts, page.year, page.month); 49 | } 50 | 51 | return <Fragment> 52 | {articleList} 53 | {page.total > 1 ? <Paginator 54 | current={page.current} 55 | total={page.total} 56 | baseUrl={page.base} 57 | path={config.pagination_dir} 58 | urlFor={url_for} 59 | prevTitle={__('common.prev')} 60 | nextTitle={__('common.next')} /> : null} 61 | </Fragment>; 62 | } 63 | }; 64 | -------------------------------------------------------------------------------- /layout/categories.jsx: -------------------------------------------------------------------------------- 1 | const { Component } = require('inferno'); 2 | const Categories = require('hexo-component-inferno/lib/view/widget/categories'); 3 | 4 | module.exports = class extends Component { 5 | render() { 6 | const { site, page, helper } = this.props; 7 | 8 | return <Categories.Cacheable site={site} page={page} helper={helper} />; 9 | } 10 | }; 11 | -------------------------------------------------------------------------------- /layout/category.jsx: -------------------------------------------------------------------------------- 1 | const { Component, Fragment } = require('inferno'); 2 | const Index = require('./index'); 3 | 4 | module.exports = class extends Component { 5 | render() { 6 | const { config, page, helper } = this.props; 7 | const { url_for, _p } = helper; 8 | 9 | return <Fragment> 10 | <div class="card"> 11 | <div class="card-content"> 12 | <nav class="breadcrumb" aria-label="breadcrumbs"> 13 | <ul> 14 | <li><a href={url_for('/categories/')}>{_p('common.category', Infinity)}</a></li> 15 | {page.parents.map(category => { 16 | return <li><a href={url_for(category.path)}>{category.name}</a></li>; 17 | })} 18 | <li class="is-active"><a href="#" aria-current="page">{page.category}</a></li> 19 | </ul> 20 | </nav> 21 | </div> 22 | </div> 23 | <Index config={config} page={page} helper={helper} /> 24 | </Fragment>; 25 | } 26 | }; 27 | -------------------------------------------------------------------------------- /layout/comment/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ppoffice/hexo-theme-icarus/76d8cd0197003fc1ad673741d66a803ebb116f81/layout/comment/.gitkeep -------------------------------------------------------------------------------- /layout/common/article.jsx: -------------------------------------------------------------------------------- 1 | const moment = require('moment'); 2 | const { Component, Fragment } = require('inferno'); 3 | const { toMomentLocale } = require('hexo/dist/plugins/helper/date'); 4 | const Share = require('./share'); 5 | const Donates = require('./donates'); 6 | const Comment = require('./comment'); 7 | const ArticleLicensing = require('hexo-component-inferno/lib/view/misc/article_licensing'); 8 | 9 | /** 10 | * Get the word count of text. 11 | */ 12 | function getWordCount(content) { 13 | if (typeof content === 'undefined') { 14 | return 0; 15 | } 16 | content = content.replace(/<\/?[a-z][^>]*>/gi, ''); 17 | content = content.trim(); 18 | return content ? (content.match(/[\u00ff-\uffff]|[a-zA-Z]+/g) || []).length : 0; 19 | } 20 | 21 | module.exports = class extends Component { 22 | render() { 23 | const { config, helper, page, index } = this.props; 24 | const { article, plugins } = config; 25 | const { url_for, date, date_xml, __, _p } = helper; 26 | 27 | const defaultLanguage = Array.isArray(config.language) && config.language.length ? config.language[0] : config.language; 28 | 29 | const indexLanguage = toMomentLocale(defaultLanguage || 'en'); 30 | const language = toMomentLocale(page.lang || page.language || defaultLanguage || 'en'); 31 | const cover = page.cover ? url_for(page.cover) : null; 32 | const updateTime = article && article.update_time !== undefined ? article.update_time : true; 33 | const isUpdated = page.updated && !moment(page.date).isSame(moment(page.updated)); 34 | const shouldShowUpdated = page.updated && ((updateTime === 'auto' && isUpdated) || updateTime === true); 35 | 36 | return <Fragment> 37 | {/* Main content */} 38 | <div class="card"> 39 | {/* Thumbnail */} 40 | {cover ? <div class="card-image"> 41 | {index ? <a href={url_for(page.link || page.path)} class="image is-7by3"> 42 | <img class="fill" src={cover} alt={page.title || cover} /> 43 | </a> : <span class="image is-7by3"> 44 | <img class="fill" src={cover} alt={page.title || cover} /> 45 | </span>} 46 | </div> : null} 47 | <article class={`card-content article${'direction' in page ? ' ' + page.direction : ''}`} role="article"> 48 | {/* Metadata */} 49 | {page.layout !== 'page' ? <div class="article-meta is-size-7 is-uppercase level is-mobile"> 50 | <div class="level-left"> 51 | {/* PIN Icon */} 52 | {page.top ? <i class="fas fa-thumbtack level-item" title="Pinned"></i> : null} 53 | {/* Creation Date */} 54 | {page.date && <span class="level-item" dangerouslySetInnerHTML={{ 55 | __html: _p('article.created_at', `<time dateTime="${date_xml(page.date)}" title="${new Date(page.date).toLocaleString()}">${date(page.date)}</time>`) 56 | }}></span>} 57 | {/* Last Update Date */} 58 | {shouldShowUpdated && <span class="level-item" dangerouslySetInnerHTML={{ 59 | __html: _p('article.updated_at', `<time dateTime="${date_xml(page.updated)}" title="${new Date(page.updated).toLocaleString()}">${date(page.updated)}</time>`) 60 | }}></span>} 61 | {/* author */} 62 | {page.author ? <span class="level-item"> {page.author} </span> : null} 63 | {/* Categories */} 64 | {page.categories && page.categories.length ? <span class="level-item"> 65 | {(() => { 66 | const categories = []; 67 | page.categories.forEach((category, i) => { 68 | categories.push(<a class="link-muted" href={url_for(category.path)}>{category.name}</a>); 69 | if (i < page.categories.length - 1) { 70 | categories.push(<span> / </span>); 71 | } 72 | }); 73 | return categories; 74 | })()} 75 | </span> : null} 76 | {/* Read time */} 77 | {article && article.readtime && article.readtime === true ? <span class="level-item"> 78 | {(() => { 79 | const words = getWordCount(page._content); 80 | const time = moment.duration((words / 150.0) * 60, 'seconds'); 81 | return `${_p('article.read_time', time.locale(index ? indexLanguage : language).humanize())} (${_p('article.word_count', words)})`; 82 | })()} 83 | </span> : null} 84 | {/* Visitor counter */} 85 | {!index && plugins && plugins.busuanzi === true ? <span class="level-item" id="busuanzi_container_page_pv" dangerouslySetInnerHTML={{ 86 | __html: _p('plugin.visit_count', '<span id="busuanzi_value_page_pv">0</span>') 87 | }}></span> : null} 88 | </div> 89 | </div> : null} 90 | {/* Title */} 91 | {page.title !== '' && index ? <p class="title is-3 is-size-4-mobile"><a class="link-muted" href={url_for(page.link || page.path)}>{page.title}</a></p> : null} 92 | {page.title !== '' && !index ? <h1 class="title is-3 is-size-4-mobile">{page.title}</h1> : null} 93 | {/* Content/Excerpt */} 94 | <div class="content" dangerouslySetInnerHTML={{ __html: index && page.excerpt ? page.excerpt : page.content }}></div> 95 | {/* Licensing block */} 96 | {!index && article && article.licenses && Object.keys(article.licenses) 97 | ? <ArticleLicensing.Cacheable page={page} config={config} helper={helper} /> : null} 98 | {/* Tags */} 99 | {!index && page.tags && page.tags.length ? <div class="article-tags is-size-7 mb-4"> 100 | <span class="mr-2">#</span> 101 | {page.tags.map(tag => { 102 | return <a class="link-muted mr-2" rel="tag" href={url_for(tag.path)}>{tag.name}</a>; 103 | })} 104 | </div> : null} 105 | {/* "Read more" button */} 106 | {index && page.excerpt ? <a class="article-more button is-small is-size-7" href={`${url_for(page.link || page.path)}#more`}>{__('article.more')}</a> : null} 107 | {/* Share button */} 108 | {!index ? <Share config={config} page={page} helper={helper} /> : null} 109 | </article> 110 | </div> 111 | {/* Donate button */} 112 | {!index ? <Donates config={config} helper={helper} /> : null} 113 | {/* Post navigation */} 114 | {!index && (page.prev || page.next) ? <nav class="post-navigation mt-4 level is-mobile"> 115 | {page.prev ? <div class="level-start"> 116 | <a class={`article-nav-prev level level-item${!page.prev ? ' is-hidden-mobile' : ''} link-muted`} href={url_for(page.prev.path)}> 117 | <i class="level-item fas fa-chevron-left"></i> 118 | <span class="level-item">{page.prev.title}</span> 119 | </a> 120 | </div> : null} 121 | {page.next ? <div class="level-end"> 122 | <a class={`article-nav-next level level-item${!page.next ? ' is-hidden-mobile' : ''} link-muted`} href={url_for(page.next.path)}> 123 | <span class="level-item">{page.next.title}</span> 124 | <i class="level-item fas fa-chevron-right"></i> 125 | </a> 126 | </div> : null} 127 | </nav> : null} 128 | {/* Comment */} 129 | {!index ? <Comment config={config} page={page} helper={helper} /> : null} 130 | </Fragment>; 131 | } 132 | }; 133 | -------------------------------------------------------------------------------- /layout/common/comment.jsx: -------------------------------------------------------------------------------- 1 | const createLogger = require('hexo-log'); 2 | const { Component } = require('inferno'); 3 | const view = require('hexo-component-inferno/lib/core/view'); 4 | 5 | const logger = createLogger.default(); 6 | 7 | module.exports = class extends Component { 8 | render() { 9 | const { config, page, helper } = this.props; 10 | const { __ } = helper; 11 | const { comment } = config; 12 | if (!comment || typeof comment.type !== 'string') { 13 | return null; 14 | } 15 | 16 | return <div class="card" id="comments"> 17 | <div class="card-content"> 18 | <h3 class="title is-5">{__('article.comments')}</h3> 19 | {(() => { 20 | try { 21 | let Comment = view.require('comment/' + comment.type); 22 | Comment = Comment.Cacheable ? Comment.Cacheable : Comment; 23 | return <Comment config={config} page={page} helper={helper} comment={comment} />; 24 | } catch (e) { 25 | logger.w(`Icarus cannot load comment "${comment.type}"`); 26 | return null; 27 | } 28 | })()} 29 | </div> 30 | </div>; 31 | } 32 | }; 33 | -------------------------------------------------------------------------------- /layout/common/donates.jsx: -------------------------------------------------------------------------------- 1 | const createLogger = require('hexo-log'); 2 | const { Component } = require('inferno'); 3 | const view = require('hexo-component-inferno/lib/core/view'); 4 | 5 | const logger = createLogger.default(); 6 | 7 | module.exports = class extends Component { 8 | render() { 9 | const { config, helper } = this.props; 10 | const { __ } = helper; 11 | const { donates = [] } = config; 12 | if (!Array.isArray(donates) || !donates.length) { 13 | return null; 14 | } 15 | return <div class="card"> 16 | <div class="card-content"> 17 | <h3 class="menu-label has-text-centered">{__('donate.title')}</h3> 18 | <div class="buttons is-centered"> 19 | {donates.map(service => { 20 | const type = service.type; 21 | if (typeof type === 'string') { 22 | try { 23 | let Donate = view.require('donate/' + type); 24 | Donate = Donate.Cacheable ? Donate.Cacheable : Donate; 25 | return <Donate helper={helper} donate={service} />; 26 | } catch (e) { 27 | logger.w(`Icarus cannot load donate button "${type}"`); 28 | } 29 | } 30 | return null; 31 | })} 32 | </div> 33 | </div> 34 | </div>; 35 | } 36 | }; 37 | -------------------------------------------------------------------------------- /layout/common/footer.jsx: -------------------------------------------------------------------------------- 1 | const { Component } = require('inferno'); 2 | const { cacheComponent } = require('hexo-component-inferno/lib/util/cache'); 3 | 4 | class Footer extends Component { 5 | render() { 6 | const { 7 | logo, 8 | logoUrl, 9 | siteUrl, 10 | siteTitle, 11 | siteYear, 12 | author, 13 | links, 14 | copyright, 15 | showVisitorCounter, 16 | visitorCounterTitle 17 | } = this.props; 18 | 19 | let footerLogo = ''; 20 | if (logo) { 21 | if (logo.text) { 22 | footerLogo = logo.text; 23 | } else { 24 | footerLogo = <img src={logoUrl} alt={siteTitle} height="28" />; 25 | } 26 | } else { 27 | footerLogo = siteTitle; 28 | } 29 | 30 | return <footer class="footer"> 31 | <div class="container"> 32 | <div class="level"> 33 | <div class="level-start"> 34 | <a class="footer-logo is-block mb-2" href={siteUrl}> 35 | {footerLogo} 36 | </a> 37 | <p class="is-size-7"> 38 | <span dangerouslySetInnerHTML={{ __html: `© ${siteYear} ${author || siteTitle}` }}></span> 39 | Powered by <a href="https://hexo.io/" target="_blank" rel="noopener">Hexo</a> & 40 | <a href="https://github.com/ppoffice/hexo-theme-icarus" target="_blank" rel="noopener">Icarus</a> 41 | {showVisitorCounter ? <br /> : null} 42 | {showVisitorCounter ? <span id="busuanzi_container_site_uv" 43 | dangerouslySetInnerHTML={{ __html: visitorCounterTitle }}></span> : null} 44 | </p> 45 | {copyright ? <p class="is-size-7" dangerouslySetInnerHTML={{ __html: copyright }}></p> : null} 46 | </div> 47 | <div class="level-end"> 48 | {Object.keys(links).length ? <div class="field has-addons"> 49 | {Object.keys(links).map(name => { 50 | const link = links[name]; 51 | return <p class="control"> 52 | <a class={`button is-transparent ${link.icon ? 'is-large' : ''}`} target="_blank" rel="noopener" title={name} href={link.url}> 53 | {link.icon ? <i class={link.icon}></i> : name} 54 | </a> 55 | </p>; 56 | })} 57 | </div> : null} 58 | </div> 59 | </div> 60 | </div> 61 | </footer>; 62 | } 63 | } 64 | 65 | module.exports = cacheComponent(Footer, 'common.footer', props => { 66 | const { config, helper } = props; 67 | const { url_for, _p, date } = helper; 68 | const { logo, title, author, footer, plugins } = config; 69 | 70 | const links = {}; 71 | if (footer && footer.links) { 72 | Object.keys(footer.links).forEach(name => { 73 | const link = footer.links[name]; 74 | links[name] = { 75 | url: url_for(typeof link === 'string' ? link : link.url), 76 | icon: link.icon 77 | }; 78 | }); 79 | } 80 | 81 | return { 82 | logo, 83 | logoUrl: url_for(logo), 84 | siteUrl: url_for('/'), 85 | siteTitle: title, 86 | siteYear: date(new Date(), 'YYYY'), 87 | author, 88 | links, 89 | copyright: footer?.copyright ?? '', 90 | showVisitorCounter: plugins && plugins.busuanzi === true, 91 | visitorCounterTitle: _p('plugin.visitor_count', '<span id="busuanzi_value_site_uv">0</span>') 92 | }; 93 | }); 94 | -------------------------------------------------------------------------------- /layout/common/head.jsx: -------------------------------------------------------------------------------- 1 | const { Component } = require('inferno'); 2 | const MetaTags = require('hexo-component-inferno/lib/view/misc/meta'); 3 | const WebApp = require('hexo-component-inferno/lib/view/misc/web_app'); 4 | const OpenGraph = require('hexo-component-inferno/lib/view/misc/open_graph'); 5 | const StructuredData = require('hexo-component-inferno/lib/view/misc/structured_data'); 6 | const Plugins = require('./plugins'); 7 | 8 | function getPageTitle(page, siteTitle, helper) { 9 | let title = page.title; 10 | 11 | if (helper.is_archive()) { 12 | title = helper._p('common.archive', Infinity); 13 | if (helper.is_month()) { 14 | title += ': ' + page.year + '/' + page.month; 15 | } else if (helper.is_year()) { 16 | title += ': ' + page.year; 17 | } 18 | } else if (helper.is_category()) { 19 | title = helper._p('common.category', 1) + ': ' + page.category; 20 | } else if (helper.is_tag()) { 21 | title = helper._p('common.tag', 1) + ': ' + page.tag; 22 | } else if (helper.is_categories()) { 23 | title = helper._p('common.category', Infinity); 24 | } else if (helper.is_tags()) { 25 | title = helper._p('common.tag', Infinity); 26 | } 27 | 28 | return [title, siteTitle].filter(str => typeof str !== 'undefined' && str.trim() !== '').join(' - '); 29 | } 30 | 31 | module.exports = class extends Component { 32 | render() { 33 | const { site, config, helper, page } = this.props; 34 | const { url_for, cdn, fontcdn, iconcdn, is_post } = helper; 35 | const { 36 | url, 37 | head = {}, 38 | article, 39 | highlight, 40 | variant = 'default' 41 | } = config; 42 | const { 43 | meta = [], 44 | manifest = {}, 45 | open_graph = {}, 46 | structured_data = {}, 47 | canonical_url = page.permalink, 48 | rss, 49 | favicon 50 | } = head; 51 | 52 | const noIndex = helper.is_archive() || helper.is_category() || helper.is_tag(); 53 | 54 | const language = page.lang || page.language || config.language; 55 | const fontCssUrl = { 56 | default: fontcdn('Ubuntu:wght@400;600&family=Source+Code+Pro', 'css2'), 57 | cyberpunk: fontcdn('Oxanium:wght@300;400;600&family=Roboto+Mono', 'css2') 58 | }; 59 | 60 | let hlTheme, images; 61 | if (highlight && highlight.enable === false) { 62 | hlTheme = null; 63 | } else if (article && article.highlight && article.highlight.theme) { 64 | hlTheme = article.highlight.theme; 65 | } else { 66 | hlTheme = 'atom-one-light'; 67 | } 68 | 69 | if (typeof page.og_image === 'string') { 70 | images = [page.og_image]; 71 | } else if (typeof page.cover === 'string') { 72 | images = [url_for(page.cover)]; 73 | } else if (typeof page.thumbnail === 'string') { 74 | images = [url_for(page.thumbnail)]; 75 | } else if (article && typeof article.og_image === 'string') { 76 | images = [article.og_image]; 77 | } else if (page.content && page.content.includes('<img')) { 78 | let img; 79 | images = []; 80 | const imgPattern = /<img [^>]*src=['"]([^'"]+)([^>]*>)/gi; 81 | while ((img = imgPattern.exec(page.content)) !== null) { 82 | images.push(img[1]); 83 | } 84 | } else { 85 | images = [url_for('/img/og_image.png')]; 86 | } 87 | 88 | let adsenseClientId = null; 89 | if (Array.isArray(config.widgets)) { 90 | const widget = config.widgets.find(widget => widget.type === 'adsense'); 91 | if (widget) { 92 | adsenseClientId = widget.client_id; 93 | } 94 | } 95 | 96 | let openGraphImages = images; 97 | if ((typeof open_graph === 'object' && open_graph !== null) 98 | && ((Array.isArray(open_graph.image) && open_graph.image.length > 0) || typeof open_graph.image === 'string')) { 99 | openGraphImages = open_graph.image; 100 | } else if ((Array.isArray(page.photos) && page.photos.length > 0) || typeof page.photos === 'string') { 101 | openGraphImages = page.photos; 102 | } 103 | 104 | let structuredImages = images; 105 | if ((typeof structured_data === 'object' && structured_data !== null) 106 | && ((Array.isArray(structured_data.image) && structured_data.image.length > 0) || typeof structured_data.image === 'string')) { 107 | structuredImages = structured_data.image; 108 | } else if ((Array.isArray(page.photos) && page.photos.length > 0) || typeof page.photos === 'string') { 109 | structuredImages = page.photos; 110 | } 111 | 112 | let followItVerificationCode = null; 113 | if (Array.isArray(config.widgets)) { 114 | const widget = config.widgets.find(widget => widget.type === 'followit'); 115 | if (widget) { 116 | followItVerificationCode = widget.verification_code; 117 | } 118 | } 119 | 120 | return <head> 121 | <meta charset="utf-8" /> 122 | <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" /> 123 | {noIndex ? <meta name="robots" content="noindex" /> : null} 124 | {meta && meta.length ? <MetaTags meta={meta} /> : null} 125 | 126 | <title>{getPageTitle(page, config.title, helper)}</title> 127 | 128 | <WebApp.Cacheable 129 | helper={helper} 130 | favicon={favicon} 131 | icons={manifest.icons} 132 | themeColor={manifest.theme_color} 133 | name={manifest.name || config.title} /> 134 | 135 | {typeof open_graph === 'object' && open_graph !== null ? <OpenGraph 136 | type={open_graph.type || (is_post(page) ? 'article' : 'website')} 137 | title={open_graph.title || page.title || config.title} 138 | date={page.date} 139 | updated={page.updated} 140 | author={open_graph.author || config.author} 141 | description={open_graph.description || page.description || page.excerpt || page.content || config.description} 142 | keywords={(page.tags && page.tags.length ? page.tags : undefined) || config.keywords} 143 | url={open_graph.url || page.permalink || url} 144 | images={openGraphImages} 145 | siteName={open_graph.site_name || config.title} 146 | language={language} 147 | twitterId={open_graph.twitter_id} 148 | twitterCard={open_graph.twitter_card} 149 | twitterSite={open_graph.twitter_site} 150 | googlePlus={open_graph.google_plus} 151 | facebookAdmins={open_graph.fb_admins} 152 | facebookAppId={open_graph.fb_app_id} /> : null} 153 | 154 | {typeof structured_data === 'object' && structured_data !== null ? <StructuredData 155 | title={structured_data.title || page.title || config.title} 156 | description={structured_data.description || page.description || page.excerpt || page.content || config.description} 157 | url={structured_data.url || page.permalink || url} 158 | author={structured_data.author || config.author} 159 | publisher={structured_data.publisher || config.title} 160 | publisherLogo={structured_data.publisher_logo || config.logo} 161 | date={page.date} 162 | updated={page.updated} 163 | images={structuredImages} /> : null} 164 | 165 | {canonical_url ? <link rel="canonical" href={canonical_url} /> : null} 166 | {rss ? <link rel="alternate" href={url_for(rss)} title={config.title} type="application/atom+xml" /> : null} 167 | {favicon ? <link rel="icon" href={url_for(favicon)} /> : null} 168 | <link rel="stylesheet" href={iconcdn()} /> 169 | {hlTheme ? <link data-pjax rel="stylesheet" href={cdn('highlight.js', '11.7.0', 'styles/' + hlTheme + '.css')} /> : null} 170 | <link rel="stylesheet" href={fontCssUrl[variant]} /> 171 | <link data-pjax rel="stylesheet" href={url_for('/css/' + variant + '.css')} /> 172 | <Plugins site={site} config={config} helper={helper} page={page} head={true} /> 173 | 174 | {adsenseClientId ? <script data-ad-client={adsenseClientId} 175 | src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js" async></script> : null} 176 | 177 | {followItVerificationCode ? <meta name="follow.it-verification-code" content={followItVerificationCode} /> : null} 178 | </head>; 179 | } 180 | }; 181 | -------------------------------------------------------------------------------- /layout/common/navbar.jsx: -------------------------------------------------------------------------------- 1 | const { Component, Fragment } = require('inferno'); 2 | const { cacheComponent } = require('hexo-component-inferno/lib/util/cache'); 3 | const classname = require('hexo-component-inferno/lib/util/classname'); 4 | 5 | function isSameLink(a, b) { 6 | function santize(url) { 7 | let paths = url.replace(/(^\w+:|^)\/\//, '').split('#')[0].split('/').filter(p => p.trim() !== ''); 8 | if (paths.length > 0 && paths[paths.length - 1].trim() === 'index.html') { 9 | paths = paths.slice(0, paths.length - 1); 10 | } 11 | return paths.join('/'); 12 | } 13 | return santize(a) === santize(b); 14 | } 15 | 16 | class Navbar extends Component { 17 | render() { 18 | const { 19 | logo, 20 | logoUrl, 21 | siteUrl, 22 | siteTitle, 23 | menu, 24 | links, 25 | showToc, 26 | tocTitle, 27 | showSearch, 28 | searchTitle 29 | } = this.props; 30 | 31 | let navbarLogo = ''; 32 | if (logo) { 33 | if (logo.text) { 34 | navbarLogo = logo.text; 35 | } else { 36 | navbarLogo = <img src={logoUrl} alt={siteTitle} height="28" />; 37 | } 38 | } else { 39 | navbarLogo = siteTitle; 40 | } 41 | 42 | return <nav class="navbar navbar-main"> 43 | <div class="container navbar-container"> 44 | <div class="navbar-brand justify-content-center"> 45 | <a class="navbar-item navbar-logo" href={siteUrl}> 46 | {navbarLogo} 47 | </a> 48 | </div> 49 | <div class="navbar-menu"> 50 | {Object.keys(menu).length ? <div class="navbar-start"> 51 | {Object.keys(menu).map(name => { 52 | const item = menu[name]; 53 | return <a class={classname({ 'navbar-item': true, 'is-active': item.active })} href={item.url}>{name}</a>; 54 | })} 55 | </div> : null} 56 | <div class="navbar-end"> 57 | {Object.keys(links).length ? <Fragment> 58 | {Object.keys(links).map(name => { 59 | const link = links[name]; 60 | return <a class="navbar-item" target="_blank" rel="noopener" title={name} href={link.url}> 61 | {link.icon ? <i class={link.icon}></i> : name} 62 | </a>; 63 | })} 64 | </Fragment> : null} 65 | {showToc ? <a class="navbar-item is-hidden-tablet catalogue" title={tocTitle} href="javascript:;"> 66 | <i class="fas fa-list-ul"></i> 67 | </a> : null} 68 | {showSearch ? <a class="navbar-item search" title={searchTitle} href="javascript:;"> 69 | <i class="fas fa-search"></i> 70 | </a> : null} 71 | </div> 72 | </div> 73 | </div> 74 | </nav>; 75 | } 76 | } 77 | 78 | module.exports = cacheComponent(Navbar, 'common.navbar', props => { 79 | const { config, helper, page } = props; 80 | const { url_for, _p, __ } = helper; 81 | const { logo, title, navbar, widgets, search } = config; 82 | 83 | const hasTocWidget = Array.isArray(widgets) && widgets.find(widget => widget.type === 'toc'); 84 | const showToc = (config.toc === true || page.toc) && hasTocWidget && ['page', 'post'].includes(page.layout); 85 | 86 | const menu = {}; 87 | if (navbar && navbar.menu) { 88 | const pageUrl = typeof page.path !== 'undefined' ? url_for(page.path) : ''; 89 | Object.keys(navbar.menu).forEach(name => { 90 | const url = url_for(navbar.menu[name]); 91 | const active = isSameLink(url, pageUrl); 92 | menu[name] = { url, active }; 93 | }); 94 | } 95 | 96 | const links = {}; 97 | if (navbar && navbar.links) { 98 | Object.keys(navbar.links).forEach(name => { 99 | const link = navbar.links[name]; 100 | links[name] = { 101 | url: url_for(typeof link === 'string' ? link : link.url), 102 | icon: link.icon 103 | }; 104 | }); 105 | } 106 | 107 | return { 108 | logo, 109 | logoUrl: url_for(logo), 110 | siteUrl: url_for('/'), 111 | siteTitle: title, 112 | menu, 113 | links, 114 | showToc, 115 | tocTitle: _p('widget.catalogue', Infinity), 116 | showSearch: search && search.type, 117 | searchTitle: __('search.search') 118 | }; 119 | }); 120 | -------------------------------------------------------------------------------- /layout/common/plugins.jsx: -------------------------------------------------------------------------------- 1 | const createLogger = require('hexo-log'); 2 | const { Component, Fragment } = require('inferno'); 3 | const view = require('hexo-component-inferno/lib/core/view'); 4 | 5 | const logger = createLogger.default(); 6 | 7 | module.exports = class extends Component { 8 | render() { 9 | const { site, config, page, helper, head } = this.props; 10 | const { plugins = [] } = config; 11 | 12 | return <Fragment> 13 | {Object.keys(plugins).map(name => { 14 | // plugin is not enabled 15 | if (!plugins[name]) { 16 | return null; 17 | } 18 | try { 19 | let Plugin = view.require('plugin/' + name); 20 | Plugin = Plugin.Cacheable ? Plugin.Cacheable : Plugin; 21 | return <Plugin site={site} config={config} page={page} helper={helper} plugin={plugins[name]} head={head} />; 22 | } catch (e) { 23 | logger.w(`Icarus cannot load plugin "${name}"`); 24 | return null; 25 | } 26 | })} 27 | </Fragment>; 28 | } 29 | }; 30 | -------------------------------------------------------------------------------- /layout/common/scripts.jsx: -------------------------------------------------------------------------------- 1 | const { Component, Fragment } = require('inferno'); 2 | const { toMomentLocale } = require('hexo/dist/plugins/helper/date'); 3 | const Plugins = require('./plugins'); 4 | 5 | module.exports = class extends Component { 6 | render() { 7 | const { site, config, helper, page } = this.props; 8 | const { url_for, cdn } = helper; 9 | const { article } = config; 10 | const language = toMomentLocale(page.lang || page.language || config.language || 'en'); 11 | 12 | let fold = 'unfolded'; 13 | let clipboard = true; 14 | if (article && article.highlight) { 15 | if (typeof article.highlight.clipboard !== 'undefined') { 16 | clipboard = !!article.highlight.clipboard; 17 | } 18 | if (typeof article.highlight.fold === 'string') { 19 | fold = article.highlight.fold; 20 | } 21 | } 22 | 23 | const embeddedConfig = `var IcarusThemeSettings = { 24 | article: { 25 | highlight: { 26 | clipboard: ${clipboard}, 27 | fold: '${fold}' 28 | } 29 | } 30 | };`; 31 | 32 | return <Fragment> 33 | <script src={cdn('jquery', '3.3.1', 'dist/jquery.min.js')}></script> 34 | <script src={cdn('moment', '2.22.2', 'min/moment-with-locales.min.js')}></script> 35 | {clipboard && <script src={cdn('clipboard', '2.0.4', 'dist/clipboard.min.js')} defer></script>} 36 | <script dangerouslySetInnerHTML={{ __html: `moment.locale("${language}");` }}></script> 37 | <script dangerouslySetInnerHTML={{ __html: embeddedConfig }}></script> 38 | <script data-pjax src={url_for('/js/column.js')}></script> 39 | <Plugins site={site} config={config} page={page} helper={helper} head={false} /> 40 | <script data-pjax src={url_for('/js/main.js')} defer></script> 41 | </Fragment>; 42 | } 43 | }; 44 | -------------------------------------------------------------------------------- /layout/common/search.jsx: -------------------------------------------------------------------------------- 1 | const createLogger = require('hexo-log'); 2 | const { Component } = require('inferno'); 3 | const view = require('hexo-component-inferno/lib/core/view'); 4 | 5 | const logger = createLogger.default(); 6 | 7 | module.exports = class extends Component { 8 | render() { 9 | const { config, helper } = this.props; 10 | const { search } = config; 11 | if (!search || typeof search.type !== 'string') { 12 | return null; 13 | } 14 | 15 | try { 16 | let Search = view.require('search/' + search.type); 17 | Search = Search.Cacheable ? Search.Cacheable : Search; 18 | return <Search config={config} helper={helper} search={search} />; 19 | } catch (e) { 20 | logger.w(`Icarus cannot load search "${search.type}"`); 21 | return null; 22 | } 23 | } 24 | }; 25 | -------------------------------------------------------------------------------- /layout/common/share.jsx: -------------------------------------------------------------------------------- 1 | const createLogger = require('hexo-log'); 2 | const { Component } = require('inferno'); 3 | const view = require('hexo-component-inferno/lib/core/view'); 4 | 5 | const logger = createLogger.default(); 6 | 7 | module.exports = class extends Component { 8 | render() { 9 | const { config, page, helper } = this.props; 10 | const { share } = config; 11 | if (!share || typeof share.type !== 'string') { 12 | return null; 13 | } 14 | 15 | try { 16 | let Share = view.require('share/' + share.type); 17 | Share = Share.Cacheable ? Share.Cacheable : Share; 18 | return <Share config={config} page={page} helper={helper} share={share} />; 19 | } catch (e) { 20 | logger.w(`Icarus cannot load share button "${share.type}"`); 21 | return null; 22 | } 23 | } 24 | }; 25 | -------------------------------------------------------------------------------- /layout/common/widgets.jsx: -------------------------------------------------------------------------------- 1 | const createLogger = require('hexo-log'); 2 | const { Component } = require('inferno'); 3 | const view = require('hexo-component-inferno/lib/core/view'); 4 | const classname = require('hexo-component-inferno/lib/util/classname'); 5 | 6 | const logger = createLogger.default(); 7 | 8 | function formatWidgets(widgets) { 9 | const result = {}; 10 | if (Array.isArray(widgets)) { 11 | widgets.filter(widget => typeof widget === 'object').forEach(widget => { 12 | if ('position' in widget && (widget.position === 'left' || widget.position === 'right')) { 13 | if (!(widget.position in result)) { 14 | result[widget.position] = [widget]; 15 | } else { 16 | result[widget.position].push(widget); 17 | } 18 | } 19 | }); 20 | } 21 | return result; 22 | } 23 | 24 | function hasColumn(widgets, position, config, page) { 25 | const showToc = (config.toc === true) && ['page', 'post'].includes(page.layout); 26 | if (Array.isArray(widgets)) { 27 | return typeof widgets.find(widget => { 28 | if (widget.type === 'toc' && !showToc) { 29 | return false; 30 | } 31 | return widget.position === position; 32 | }) !== 'undefined'; 33 | } 34 | return false; 35 | } 36 | 37 | function getColumnCount(widgets, config, page) { 38 | return [hasColumn(widgets, 'left', config, page), hasColumn(widgets, 'right', config, page)].filter(v => !!v).length + 1; 39 | } 40 | 41 | function getColumnSizeClass(columnCount) { 42 | switch (columnCount) { 43 | case 2: 44 | return 'is-4-tablet is-4-desktop is-4-widescreen'; 45 | case 3: 46 | return 'is-4-tablet is-4-desktop is-3-widescreen'; 47 | } 48 | return ''; 49 | } 50 | 51 | function getColumnVisibilityClass(columnCount, position) { 52 | if (columnCount === 3 && position === 'right') { 53 | return 'is-hidden-touch is-hidden-desktop-only'; 54 | } 55 | return ''; 56 | } 57 | 58 | function getColumnOrderClass(position) { 59 | return position === 'left' ? 'order-1' : 'order-3'; 60 | } 61 | 62 | function isColumnSticky(config, position) { 63 | return typeof config.sidebar === 'object' 64 | && position in config.sidebar 65 | && config.sidebar[position].sticky === true; 66 | } 67 | 68 | class Widgets extends Component { 69 | render() { 70 | const { site, config, helper, page, position } = this.props; 71 | const widgets = formatWidgets(config.widgets)[position] || []; 72 | const columnCount = getColumnCount(config.widgets, config, page); 73 | 74 | if (!widgets.length) { 75 | return null; 76 | } 77 | 78 | return <div class={classname({ 79 | 'column': true, 80 | ['column-' + position]: true, 81 | [getColumnSizeClass(columnCount)]: true, 82 | [getColumnVisibilityClass(columnCount, position)]: true, 83 | [getColumnOrderClass(position)]: true, 84 | 'is-sticky': isColumnSticky(config, position) 85 | })}> 86 | {widgets.map(widget => { 87 | // widget type is not defined 88 | if (!widget.type) { 89 | return null; 90 | } 91 | try { 92 | let Widget = view.require('widget/' + widget.type); 93 | Widget = Widget.Cacheable ? Widget.Cacheable : Widget; 94 | return <Widget site={site} helper={helper} config={config} page={page} widget={widget} />; 95 | } catch (e) { 96 | logger.w(`Icarus cannot load widget "${widget.type}"`); 97 | } 98 | return null; 99 | })} 100 | {position === 'left' && hasColumn(config.widgets, 'right', config, page) ? <div class={classname({ 101 | 'column-right-shadow': true, 102 | 'is-hidden-widescreen': true, 103 | 'is-sticky': isColumnSticky(config, 'right') 104 | })}></div> : null} 105 | </div>; 106 | } 107 | } 108 | 109 | Widgets.getColumnCount = getColumnCount; 110 | 111 | module.exports = Widgets; 112 | -------------------------------------------------------------------------------- /layout/donate/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ppoffice/hexo-theme-icarus/76d8cd0197003fc1ad673741d66a803ebb116f81/layout/donate/.gitkeep -------------------------------------------------------------------------------- /layout/index.jsx: -------------------------------------------------------------------------------- 1 | const { Component, Fragment } = require('inferno'); 2 | const Paginator = require('hexo-component-inferno/lib/view/misc/paginator'); 3 | const Article = require('./common/article'); 4 | 5 | module.exports = class extends Component { 6 | render() { 7 | const { config, page, helper } = this.props; 8 | const { __, url_for } = helper; 9 | 10 | return <Fragment> 11 | {page.posts.map(post => <Article config={config} page={post} helper={helper} index={true} />)} 12 | {page.total > 1 ? <Paginator 13 | current={page.current} 14 | total={page.total} 15 | baseUrl={page.base} 16 | path={config.pagination_dir} 17 | urlFor={url_for} 18 | prevTitle={__('common.prev')} 19 | nextTitle={__('common.next')} /> : null} 20 | </Fragment>; 21 | } 22 | }; 23 | -------------------------------------------------------------------------------- /layout/layout.jsx: -------------------------------------------------------------------------------- 1 | const { Component } = require('inferno'); 2 | const classname = require('hexo-component-inferno/lib/util/classname'); 3 | const Head = require('./common/head'); 4 | const Navbar = require('./common/navbar'); 5 | const Widgets = require('./common/widgets'); 6 | const Footer = require('./common/footer'); 7 | const Scripts = require('./common/scripts'); 8 | const Search = require('./common/search'); 9 | 10 | module.exports = class extends Component { 11 | render() { 12 | const { site, config, page, helper, body } = this.props; 13 | 14 | const language = page.lang || page.language || config.language; 15 | const columnCount = Widgets.getColumnCount(config.widgets, config, page); 16 | 17 | return <html lang={language ? language.substr(0, 2) : ''}> 18 | <Head site={site} config={config} helper={helper} page={page} /> 19 | <body class={`is-${columnCount}-column`}> 20 | <Navbar config={config} helper={helper} page={page} /> 21 | <section class="section"> 22 | <div class="container"> 23 | <div class="columns"> 24 | <div class={classname({ 25 | column: true, 26 | 'order-2': true, 27 | 'column-main': true, 28 | 'is-12': columnCount === 1, 29 | 'is-8-tablet is-8-desktop is-8-widescreen': columnCount === 2, 30 | 'is-8-tablet is-8-desktop is-6-widescreen': columnCount === 3 31 | })} dangerouslySetInnerHTML={{ __html: body }}></div> 32 | <Widgets site={site} config={config} helper={helper} page={page} position={'left'} /> 33 | <Widgets site={site} config={config} helper={helper} page={page} position={'right'} /> 34 | </div> 35 | </div> 36 | </section> 37 | <Footer config={config} helper={helper} /> 38 | <Scripts site={site} config={config} helper={helper} page={page} /> 39 | <Search config={config} helper={helper} /> 40 | </body> 41 | </html>; 42 | } 43 | }; 44 | -------------------------------------------------------------------------------- /layout/misc/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ppoffice/hexo-theme-icarus/76d8cd0197003fc1ad673741d66a803ebb116f81/layout/misc/.gitkeep -------------------------------------------------------------------------------- /layout/page.jsx: -------------------------------------------------------------------------------- 1 | const { Component } = require('inferno'); 2 | const Article = require('./common/article'); 3 | 4 | module.exports = class extends Component { 5 | render() { 6 | const { config, page, helper } = this.props; 7 | 8 | return <Article config={config} page={page} helper={helper} index={false} />; 9 | } 10 | }; 11 | -------------------------------------------------------------------------------- /layout/plugin/animejs.jsx: -------------------------------------------------------------------------------- 1 | const { Component } = require('inferno'); 2 | const { cacheComponent } = require('hexo-component-inferno/lib/util/cache'); 3 | 4 | class AnimeJs extends Component { 5 | render() { 6 | if (this.props.head) { 7 | return <style dangerouslySetInnerHTML={{ __html: 'body>.footer,body>.navbar,body>.section{opacity:0}' }}></style>; 8 | } 9 | return <script src={this.props.jsUrl}></script>; 10 | 11 | } 12 | } 13 | 14 | AnimeJs.Cacheable = cacheComponent(AnimeJs, 'plugin.animejs', props => { 15 | const { helper, head } = props; 16 | return { 17 | head, 18 | jsUrl: helper.url_for('/js/animation.js') 19 | }; 20 | }); 21 | 22 | module.exports = AnimeJs; 23 | -------------------------------------------------------------------------------- /layout/plugin/back_to_top.jsx: -------------------------------------------------------------------------------- 1 | const { Component, Fragment } = require('inferno'); 2 | const { cacheComponent } = require('hexo-component-inferno/lib/util/cache'); 3 | 4 | class BackToTop extends Component { 5 | render() { 6 | const { title, jsUrl } = this.props; 7 | 8 | return <Fragment> 9 | <a id="back-to-top" title={title} href="javascript:;"> 10 | <i class="fas fa-chevron-up"></i> 11 | </a> 12 | <script data-pjax src={jsUrl} defer></script> 13 | </Fragment>; 14 | 15 | } 16 | } 17 | 18 | BackToTop.Cacheable = cacheComponent(BackToTop, 'plugin.backtotop', props => { 19 | const { helper, head } = props; 20 | if (head) { 21 | return null; 22 | } 23 | return { 24 | title: helper.__('plugin.backtotop'), 25 | jsUrl: helper.url_for('/js/back_to_top.js') 26 | }; 27 | }); 28 | 29 | module.exports = BackToTop; 30 | -------------------------------------------------------------------------------- /layout/plugin/pjax.jsx: -------------------------------------------------------------------------------- 1 | const { Component, Fragment } = require('inferno'); 2 | 3 | class Pjax extends Component { 4 | render() { 5 | if (this.props.head) { 6 | return null; 7 | } 8 | const { helper } = this.props; 9 | const { url_for, cdn } = helper; 10 | 11 | return <Fragment> 12 | <script src={cdn('pjax', '0.2.8', 'pjax.min.js')}></script> 13 | <script src={url_for('/js/pjax.js')}></script> 14 | </Fragment>; 15 | } 16 | } 17 | 18 | module.exports = Pjax; 19 | -------------------------------------------------------------------------------- /layout/post.jsx: -------------------------------------------------------------------------------- 1 | const { Component } = require('inferno'); 2 | const Article = require('./common/article'); 3 | 4 | module.exports = class extends Component { 5 | render() { 6 | const { config, page, helper } = this.props; 7 | 8 | return <Article config={config} page={page} helper={helper} index={false} />; 9 | } 10 | }; 11 | -------------------------------------------------------------------------------- /layout/search/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ppoffice/hexo-theme-icarus/76d8cd0197003fc1ad673741d66a803ebb116f81/layout/search/.gitkeep -------------------------------------------------------------------------------- /layout/share/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ppoffice/hexo-theme-icarus/76d8cd0197003fc1ad673741d66a803ebb116f81/layout/share/.gitkeep -------------------------------------------------------------------------------- /layout/tag.jsx: -------------------------------------------------------------------------------- 1 | const { Component, Fragment } = require('inferno'); 2 | const Index = require('./index'); 3 | 4 | module.exports = class extends Component { 5 | render() { 6 | const { config, page, helper } = this.props; 7 | const { url_for, _p } = helper; 8 | 9 | return <Fragment> 10 | <div class="card"> 11 | <div class="card-content"> 12 | <nav class="breadcrumb" aria-label="breadcrumbs"> 13 | <ul> 14 | <li><a href={url_for('/tags/')}>{_p('common.tag', Infinity)}</a></li> 15 | <li class="is-active"><a href="#" aria-current="page">{page.tag}</a></li> 16 | </ul> 17 | </nav> 18 | </div> 19 | </div> 20 | <Index config={config} page={page} helper={helper} /> 21 | </Fragment>; 22 | } 23 | }; 24 | -------------------------------------------------------------------------------- /layout/tags.jsx: -------------------------------------------------------------------------------- 1 | const { Component } = require('inferno'); 2 | const Tags = require('hexo-component-inferno/lib/view/widget/tags'); 3 | 4 | module.exports = class extends Component { 5 | render() { 6 | const { site, helper } = this.props; 7 | 8 | return <Tags.Cacheable site={site} helper={helper} />; 9 | } 10 | }; 11 | -------------------------------------------------------------------------------- /layout/widget/profile.jsx: -------------------------------------------------------------------------------- 1 | const { Component } = require('inferno'); 2 | const gravatrHelper = require('hexo-util').gravatar; 3 | const { cacheComponent } = require('hexo-component-inferno/lib/util/cache'); 4 | 5 | class Profile extends Component { 6 | renderSocialLinks(links) { 7 | if (!links.length) { 8 | return null; 9 | } 10 | return <div class="level is-mobile is-multiline"> 11 | {links.filter(link => typeof link === 'object').map(link => { 12 | return <a class="level-item button is-transparent is-marginless" 13 | target="_blank" rel="me noopener" title={link.name} href={link.url}> 14 | {'icon' in link ? <i class={link.icon}></i> : link.name} 15 | </a>; 16 | })} 17 | </div>; 18 | } 19 | 20 | render() { 21 | const { 22 | avatar, 23 | avatarRounded, 24 | author, 25 | authorTitle, 26 | location, 27 | counter, 28 | followLink, 29 | followTitle, 30 | socialLinks 31 | } = this.props; 32 | return <div class="card widget" data-type="profile"> 33 | <div class="card-content"> 34 | <nav class="level"> 35 | <div class="level-item has-text-centered flex-shrink-1"> 36 | <div> 37 | <figure class="image is-128x128 mx-auto mb-2"> 38 | <img class={'avatar' + (avatarRounded ? ' is-rounded' : '')} src={avatar} alt={author} /> 39 | </figure> 40 | {author ? <p class="title is-size-4 is-block" style={{'line-height': 'inherit'}}>{author}</p> : null} 41 | {authorTitle ? <p class="is-size-6 is-block">{authorTitle}</p> : null} 42 | {location ? <p class="is-size-6 is-flex justify-content-center"> 43 | <i class="fas fa-map-marker-alt mr-1"></i> 44 | <span>{location}</span> 45 | </p> : null} 46 | </div> 47 | </div> 48 | </nav> 49 | <nav class="level is-mobile"> 50 | <div class="level-item has-text-centered is-marginless"> 51 | <div> 52 | <p class="heading">{counter.post.title}</p> 53 | <a href={counter.post.url}> 54 | <p class="title">{counter.post.count}</p> 55 | </a> 56 | </div> 57 | </div> 58 | <div class="level-item has-text-centered is-marginless"> 59 | <div> 60 | <p class="heading">{counter.category.title}</p> 61 | <a href={counter.category.url}> 62 | <p class="title">{counter.category.count}</p> 63 | </a> 64 | </div> 65 | </div> 66 | <div class="level-item has-text-centered is-marginless"> 67 | <div> 68 | <p class="heading">{counter.tag.title}</p> 69 | <a href={counter.tag.url}> 70 | <p class="title">{counter.tag.count}</p> 71 | </a> 72 | </div> 73 | </div> 74 | </nav> 75 | {followLink ? <div class="level"> 76 | <a class="level-item button is-primary is-rounded" href={followLink} target="_blank" rel="me noopener">{followTitle}</a> 77 | </div> : null} 78 | {socialLinks ? this.renderSocialLinks(socialLinks) : null} 79 | </div> 80 | </div>; 81 | } 82 | } 83 | 84 | Profile.Cacheable = cacheComponent(Profile, 'widget.profile', props => { 85 | const { site, helper, widget } = props; 86 | const { 87 | avatar, 88 | gravatar, 89 | avatar_rounded = false, 90 | author = props.config.author, 91 | author_title, 92 | location, 93 | follow_link, 94 | social_links 95 | } = widget; 96 | const { url_for, _p, __ } = helper; 97 | 98 | function getAvatar() { 99 | if (gravatar) { 100 | return gravatrHelper(gravatar, 128); 101 | } 102 | if (avatar) { 103 | return url_for(avatar); 104 | } 105 | return url_for('/img/avatar.png'); 106 | } 107 | 108 | const postCount = site.posts.length; 109 | const categoryCount = site.categories.filter(category => category.length).length; 110 | const tagCount = site.tags.filter(tag => tag.length).length; 111 | 112 | const socialLinks = social_links ? Object.keys(social_links).map(name => { 113 | const link = social_links[name]; 114 | if (typeof link === 'string') { 115 | return { 116 | name, 117 | url: url_for(link) 118 | }; 119 | } 120 | return { 121 | name, 122 | url: url_for(link.url), 123 | icon: link.icon 124 | }; 125 | }) : null; 126 | 127 | return { 128 | avatar: getAvatar(), 129 | avatarRounded: avatar_rounded, 130 | author, 131 | authorTitle: author_title, 132 | location, 133 | counter: { 134 | post: { 135 | count: postCount, 136 | title: _p('common.post', postCount), 137 | url: url_for('/archives/') 138 | }, 139 | category: { 140 | count: categoryCount, 141 | title: _p('common.category', categoryCount), 142 | url: url_for('/categories/') 143 | }, 144 | tag: { 145 | count: tagCount, 146 | title: _p('common.tag', tagCount), 147 | url: url_for('/tags/') 148 | } 149 | }, 150 | followLink: follow_link ? url_for(follow_link) : undefined, 151 | followTitle: __('widget.follow'), 152 | socialLinks 153 | }; 154 | }); 155 | 156 | module.exports = Profile; 157 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hexo-theme-icarus", 3 | "version": "6.1.1", 4 | "author": "ppoffice <ppoffice@users.noreply.github.com>", 5 | "license": "MIT", 6 | "description": "A simple, delicate, and modern theme for Hexo", 7 | "keywords": [ 8 | "hexo", 9 | "theme", 10 | "icarus" 11 | ], 12 | "homepage": "https://github.com/ppoffice/hexo-theme-icarus", 13 | "repository": "https://github.com/ppoffice/hexo-theme-icarus.git", 14 | "bugs": { 15 | "url": "https://github.com/ppoffice/hexo-theme-icarus/issues" 16 | }, 17 | "engines": { 18 | "node": ">=14" 19 | }, 20 | "scripts": { 21 | "lint": "eslint --ext .js --ext .jsx --ext .json ." 22 | }, 23 | "devDependencies": { 24 | "eslint": "^8.56.0", 25 | "eslint-config-hexo": "^5.0.0", 26 | "eslint-plugin-json": "^3.1.0", 27 | "eslint-plugin-react": "^7.33.2" 28 | }, 29 | "dependencies": { 30 | "bulma-stylus": "0.8.0", 31 | "deepmerge": "^4.3.1", 32 | "hexo": "^7.1.1", 33 | "hexo-component-inferno": "^3.1.2", 34 | "hexo-log": "^4.1.0", 35 | "hexo-pagination": "^3.0.0", 36 | "hexo-renderer-inferno": "^1.0.2", 37 | "hexo-renderer-stylus": "^3.0.1", 38 | "hexo-util": "^3.2.0", 39 | "inferno": "^8.2.3", 40 | "inferno-create-element": "^8.2.3", 41 | "moment": "^2.30.1", 42 | "semver": "^7.5.4" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /scripts/index.js: -------------------------------------------------------------------------------- 1 | /* global hexo */ 2 | const createLogger = require('hexo-log'); 3 | 4 | const logger = createLogger.default(); 5 | 6 | /** 7 | * Print welcome message 8 | */ 9 | logger.info(`======================================= 10 | ██╗ ██████╗ █████╗ ██████╗ ██╗ ██╗███████╗ 11 | ██║██╔════╝██╔══██╗██╔══██╗██║ ██║██╔════╝ 12 | ██║██║ ███████║██████╔╝██║ ██║███████╗ 13 | ██║██║ ██╔══██║██╔══██╗██║ ██║╚════██║ 14 | ██║╚██████╗██║ ██║██║ ██║╚██████╔╝███████║ 15 | ╚═╝ ╚═════╝╚═╝ ╚═╝╚═╝ ╚═╝ ╚═════╝ ╚══════╝ 16 | =============================================`); 17 | 18 | /** 19 | * Check if all dependencies are installed 20 | */ 21 | require('../include/dependency')(hexo); 22 | 23 | /** 24 | * Configuration file checking and migration 25 | */ 26 | require('../include/config')(hexo); 27 | 28 | /** 29 | * Register Hexo extensions and remove Hexo filters that could cause OOM 30 | */ 31 | require('../include/register')(hexo); 32 | -------------------------------------------------------------------------------- /source/css/default.styl: -------------------------------------------------------------------------------- 1 | @import 'style' 2 | -------------------------------------------------------------------------------- /source/css/style.styl: -------------------------------------------------------------------------------- 1 | // Base CSS framework 2 | @import '../../include/style/base' 3 | // Helper classes & mixins 4 | @import '../../include/style/helper' 5 | // Icarus components 6 | @import '../../include/style/button' 7 | @import '../../include/style/card' 8 | @import '../../include/style/article' 9 | @import '../../include/style/navbar' 10 | @import '../../include/style/footer' 11 | @import '../../include/style/pagination' 12 | @import '../../include/style/timeline' 13 | @import '../../include/style/search' 14 | @import '../../include/style/codeblock' 15 | @import '../../include/style/widget' 16 | @import '../../include/style/donate' 17 | @import '../../include/style/plugin' 18 | @import '../../include/style/responsive' 19 | -------------------------------------------------------------------------------- /source/img/avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ppoffice/hexo-theme-icarus/76d8cd0197003fc1ad673741d66a803ebb116f81/source/img/avatar.png -------------------------------------------------------------------------------- /source/img/favicon.svg: -------------------------------------------------------------------------------- 1 | <svg 2 | xmlns="http://www.w3.org/2000/svg" version="1.1" width="256" height="256" viewbox="0 0 949 256"> 3 | <path fill="#2366d1" d="M110.85125168440814 128L221.70250336881628 192L110.85125168440814 256L0 192Z"/> 4 | <path fill="#609dff" d="M110.85125168440814 64L221.70250336881628 128L110.85125168440814 192L0 128Z"/> 5 | <path fill="#a4c7ff" d="M110.85125168440814 0L221.70250336881628 64L110.85125168440814 128L0 64Z"/> 6 | </svg> -------------------------------------------------------------------------------- /source/img/og_image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ppoffice/hexo-theme-icarus/76d8cd0197003fc1ad673741d66a803ebb116f81/source/img/og_image.png -------------------------------------------------------------------------------- /source/img/razor-bottom-black.svg: -------------------------------------------------------------------------------- 1 | <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="1920px" height="39px"> 2 | <path fill-rule="evenodd" fill="rgb(0, 0, 0)" d="M1877.759,23.791 L1874.461,20.504 L1879.969,15.027 L1868.965,15.027 L1874.461,20.504 L1871.156,23.791 L1789.442,23.791 L1784.749,28.484 L1775.502,37.704 L1769.337,31.559 L1738.089,31.695 L1733.080,31.695 L1720.422,19.079 L1686.790,19.079 L1679.552,26.293 L1386.290,26.255 L1380.520,20.508 L1371.871,29.132 L1368.980,26.255 L1203.819,26.293 L1191.058,38.996 L1167.007,15.027 L1167.000,15.038 L1166.989,15.027 L1157.936,24.049 L1154.094,24.049 L1151.994,21.956 L1155.330,18.643 L1148.674,18.643 L1151.994,21.956 L1149.933,24.007 L1072.000,24.040 L1072.000,24.049 L1061.000,24.044 L1050.000,24.049 L1050.000,24.040 L972.067,24.007 L970.005,21.956 L973.326,18.643 L966.670,18.643 L970.005,21.956 L967.906,24.049 L964.064,24.049 L955.012,15.027 L955.000,15.038 L954.993,15.027 L930.942,38.996 L918.181,26.293 L818.020,26.255 L815.129,29.132 L806.480,20.508 L800.710,26.255 L661.448,26.293 L654.209,19.079 L620.578,19.079 L607.919,31.695 L602.910,31.695 L571.663,31.559 L565.497,37.704 L556.251,28.484 L551.557,23.791 L469.844,23.791 L466.539,20.504 L472.035,15.027 L461.031,15.027 L466.539,20.504 L463.241,23.791 L204.638,23.791 L195.467,15.027 L195.449,15.049 L195.426,15.027 L179.458,31.695 L166.537,31.695 L158.884,24.049 L0.001,24.049 L0.001,-0.012 L1919.998,-0.012 L1919.998,24.049 L1877.759,23.791 ZM228.624,15.027 L226.889,15.027 L226.889,20.205 L228.624,20.205 L228.624,15.027 ZM231.248,15.027 L229.518,15.027 L229.518,20.205 L231.248,20.205 L231.248,15.027 ZM243.370,15.027 L241.640,15.027 L241.640,20.205 L243.370,20.205 L243.370,15.027 ZM264.168,15.027 L258.973,15.027 L258.973,20.205 L264.168,20.205 L264.168,15.027 ZM584.675,24.989 L582.941,24.989 L582.941,26.714 L584.675,26.714 L584.675,24.989 ZM591.316,21.536 L589.582,21.536 L589.582,26.714 L591.316,26.714 L591.316,21.536 ZM604.751,21.536 L603.017,21.536 L603.017,23.260 L604.751,23.260 L604.751,21.536 ZM604.751,24.989 L603.017,24.989 L603.017,26.714 L604.751,26.714 L604.751,24.989 ZM1737.983,21.536 L1736.249,21.536 L1736.249,23.260 L1737.983,23.260 L1737.983,21.536 ZM1737.983,24.989 L1736.249,24.989 L1736.249,26.714 L1737.983,26.714 L1737.983,24.989 ZM1751.418,21.536 L1749.683,21.536 L1749.683,26.714 L1751.418,26.714 L1751.418,21.536 ZM1758.059,24.989 L1756.324,24.989 L1756.324,26.714 L1758.059,26.714 L1758.059,24.989 ZM1877.785,23.818 L1871.129,23.818 L1871.156,23.791 L1877.759,23.791 L1877.785,23.818 ZM1367.778,33.211 L1371.871,29.132 L1375.975,33.211 L1367.778,33.211 ZM469.871,23.818 L463.214,23.818 L463.241,23.791 L469.844,23.791 L469.871,23.818 Z"/> 3 | </svg> -------------------------------------------------------------------------------- /source/img/razor-top-black.svg: -------------------------------------------------------------------------------- 1 | <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="1920px" height="40px"> 2 | <path fill-rule="evenodd" fill="rgb(0, 0, 0)" d="M1827.156,15.021 L1827.129,14.994 L1833.785,14.994 L1833.759,15.021 L1827.156,15.021 ZM1824.965,24.036 L1835.969,24.036 L1830.461,18.558 L1833.759,15.021 L1920.000,15.021 L1920.000,39.075 L0.001,39.075 L0.001,15.013 L271.884,15.013 L279.537,6.930 L292.458,6.930 L308.426,24.036 L308.449,24.013 L308.467,24.036 L317.638,15.021 L463.241,15.021 L466.539,18.558 L461.031,24.036 L472.035,24.036 L466.539,18.558 L469.844,15.021 L551.557,15.021 L556.251,10.578 L565.497,1.358 L571.663,7.066 L602.910,7.055 L607.919,7.055 L620.578,19.983 L654.209,19.983 L661.448,12.957 L735.709,12.995 L741.480,18.554 L750.129,9.930 L753.020,12.995 L918.181,12.957 L930.942,0.066 L954.993,24.036 L955.000,24.024 L955.012,24.036 L964.064,15.013 L967.906,15.013 L970.005,17.106 L966.670,20.419 L973.326,20.419 L970.005,17.106 L972.067,15.055 L1050.000,15.023 L1050.000,15.013 L1064.030,15.017 L1072.000,15.013 L1072.000,15.019 L1225.933,15.055 L1227.994,17.106 L1224.674,20.419 L1231.331,20.419 L1227.994,17.106 L1230.094,15.013 L1233.936,15.013 L1242.989,24.036 L1243.000,24.024 L1243.007,24.036 L1267.058,0.066 L1279.819,12.957 L1368.980,12.995 L1371.871,9.930 L1380.520,18.554 L1386.290,13.057 L1635.552,13.019 L1642.790,19.983 L1676.422,19.983 L1689.080,6.992 L1725.337,7.003 L1731.502,1.358 L1740.749,10.578 L1745.443,15.021 L1827.156,15.021 L1830.461,18.558 L1824.965,24.036 ZM341.624,18.857 L339.889,18.857 L339.889,24.036 L341.624,24.036 L341.624,18.857 ZM344.248,18.857 L342.518,18.857 L342.518,24.036 L344.248,24.036 L344.248,18.857 ZM356.370,18.857 L354.640,18.857 L354.640,24.036 L356.370,24.036 L356.370,18.857 ZM377.168,18.857 L371.973,18.857 L371.973,24.036 L377.168,24.036 L377.168,18.857 ZM584.675,12.348 L582.941,12.348 L582.941,14.073 L584.675,14.073 L584.675,12.348 ZM591.316,12.348 L589.582,12.348 L589.582,17.526 L591.316,17.526 L591.316,12.348 ZM604.751,12.348 L603.017,12.348 L603.017,14.073 L604.751,14.073 L604.751,12.348 ZM604.751,15.802 L603.017,15.802 L603.017,17.526 L604.751,17.526 L604.751,15.802 ZM1693.983,12.348 L1692.249,12.348 L1692.249,14.073 L1693.983,14.073 L1693.983,12.348 ZM1693.983,15.802 L1692.249,15.802 L1692.249,17.526 L1693.983,17.526 L1693.983,15.802 ZM1707.418,12.348 L1705.683,12.348 L1705.683,17.526 L1707.418,17.526 L1707.418,12.348 ZM1714.059,12.348 L1712.324,12.348 L1712.324,14.073 L1714.059,14.073 L1714.059,12.348 ZM463.214,14.994 L469.871,14.994 L469.844,15.021 L463.241,15.021 L463.214,14.994 ZM754.222,5.976 L750.129,9.930 L746.025,5.976 L754.222,5.976 ZM1375.975,5.976 L1371.871,9.930 L1367.778,5.976 L1375.975,5.976 Z"/> 3 | </svg> -------------------------------------------------------------------------------- /source/js/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../.eslintrc.json", 3 | "env": { 4 | "browser": true, 5 | "jquery": true, 6 | "node": false 7 | } 8 | } -------------------------------------------------------------------------------- /source/js/animation.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | function $() { 3 | return Array.prototype.slice.call(document.querySelectorAll.apply(document, arguments)); 4 | } 5 | 6 | $('body > .navbar, body > .section, body > .footer').forEach(element => { 7 | element.style.transition = '0s'; 8 | element.style.opacity = '0'; 9 | }); 10 | document.querySelector('body > .navbar').style.transform = 'translateY(-100px)'; 11 | [ 12 | '.column-main > .card, .column-main > .pagination, .column-main > .post-navigation', 13 | '.column-left > .card, .column-right-shadow > .card', 14 | '.column-right > .card' 15 | ].forEach(selector => { 16 | $(selector).forEach(element => { 17 | element.style.transition = '0s'; 18 | element.style.opacity = '0'; 19 | element.style.transform = 'scale(0.8)'; 20 | element.style.transformOrigin = 'center top'; 21 | }); 22 | }); 23 | // disable jump to location.hash 24 | if (window.location.hash) { 25 | window.scrollTo(0, 0); 26 | setTimeout(() => window.scrollTo(0, 0)); 27 | } 28 | 29 | setTimeout(() => { 30 | $('body > .navbar, body > .section, body > .footer').forEach(element => { 31 | element.style.opacity = '1'; 32 | element.style.transition = 'opacity 0.3s ease-out, transform 0.3s ease-out'; 33 | }); 34 | document.querySelector('body > .navbar').style.transform = 'translateY(0)'; 35 | 36 | let i = 1; 37 | [ 38 | '.column-main > .card, .column-main > .pagination, .column-main > .post-navigation', 39 | '.column-left > .card, .column-right-shadow > .card', 40 | '.column-right > .card' 41 | ].forEach(selector => { 42 | $(selector).forEach(element => { 43 | setTimeout(() => { 44 | element.style.opacity = '1'; 45 | element.style.transform = ''; 46 | element.style.transition = 'opacity 0.3s ease-out, transform 0.3s ease-out'; 47 | }, i * 100); 48 | i++; 49 | }); 50 | }); 51 | 52 | // jump to location.hash 53 | if (window.location.hash) { 54 | setTimeout(() => { 55 | const id = '#' + CSS.escape(window.location.hash.substring(1)); 56 | const target = document.querySelector(id); 57 | if (target) { 58 | target.scrollIntoView({ behavior: 'smooth' }); 59 | } 60 | }, i * 100); 61 | } 62 | }); 63 | }()); 64 | -------------------------------------------------------------------------------- /source/js/back_to_top.js: -------------------------------------------------------------------------------- 1 | $(document).ready(() => { 2 | const $button = $('#back-to-top'); 3 | const $footer = $('footer.footer'); 4 | const $mainColumn = $('.column-main'); 5 | const $leftSidebar = $('.column-left'); 6 | const $rightSidebar = $('.column-right'); 7 | let lastScrollTop = 0; 8 | const rightMargin = 20; 9 | const bottomMargin = 20; 10 | let lastState = null; 11 | const state = { 12 | base: { 13 | classname: 'card has-text-centered', 14 | left: '', 15 | width: 64, 16 | bottom: bottomMargin 17 | } 18 | }; 19 | state['desktop-hidden'] = Object.assign({}, state.base, { 20 | classname: state.base.classname + ' rise-up' 21 | }); 22 | state['desktop-visible'] = Object.assign({}, state['desktop-hidden'], { 23 | classname: state['desktop-hidden'].classname + ' fade-in' 24 | }); 25 | state['desktop-dock'] = Object.assign({}, state['desktop-visible'], { 26 | classname: state['desktop-visible'].classname + ' fade-in is-rounded', 27 | width: 40 28 | }); 29 | state['mobile-hidden'] = Object.assign({}, state.base, { 30 | classname: state.base.classname + ' fade-in', 31 | right: rightMargin 32 | }); 33 | state['mobile-visible'] = Object.assign({}, state['mobile-hidden'], { 34 | classname: state['mobile-hidden'].classname + ' rise-up' 35 | }); 36 | 37 | function isStateEquals(prev, next) { 38 | return ![].concat(Object.keys(prev), Object.keys(next)).some(key => { 39 | return !Object.prototype.hasOwnProperty.call(prev, key) 40 | || !Object.prototype.hasOwnProperty.call(next, key) 41 | || next[key] !== prev[key]; 42 | }); 43 | } 44 | 45 | function applyState(state) { 46 | if (lastState !== null && isStateEquals(lastState, state)) { 47 | return; 48 | } 49 | $button.attr('class', state.classname); 50 | for (const prop in state) { 51 | if (prop === 'classname') { 52 | continue; 53 | } 54 | $button.css(prop, state[prop]); 55 | } 56 | lastState = state; 57 | } 58 | 59 | function isDesktop() { 60 | return window.innerWidth >= 1078; 61 | } 62 | 63 | function isTablet() { 64 | return window.innerWidth >= 768 && !isDesktop(); 65 | } 66 | 67 | function isScrollUp() { 68 | return $(window).scrollTop() < lastScrollTop && $(window).scrollTop() > 0; 69 | } 70 | 71 | function hasLeftSidebar() { 72 | return $leftSidebar.length > 0; 73 | } 74 | 75 | function hasRightSidebar() { 76 | return $rightSidebar.length > 0; 77 | } 78 | 79 | function getRightSidebarBottom() { 80 | if (!hasRightSidebar()) { 81 | return 0; 82 | } 83 | return Math.max.apply(null, $rightSidebar.find('.widget').map(function() { 84 | return $(this).offset().top + $(this).outerHeight(true); 85 | })); 86 | } 87 | 88 | function getScrollTop() { 89 | return $(window).scrollTop(); 90 | } 91 | 92 | function getScrollBottom() { 93 | return $(window).scrollTop() + $(window).height(); 94 | } 95 | 96 | function getButtonWidth() { 97 | return $button.outerWidth(true); 98 | } 99 | 100 | function getButtonHeight() { 101 | return $button.outerHeight(true); 102 | } 103 | 104 | function updateScrollTop() { 105 | lastScrollTop = $(window).scrollTop(); 106 | } 107 | 108 | function update() { 109 | // desktop mode or tablet mode with only right sidebar enabled 110 | if (isDesktop() || (isTablet() && !hasLeftSidebar() && hasRightSidebar())) { 111 | let nextState; 112 | const padding = ($mainColumn.outerWidth() - $mainColumn.width()) / 2; 113 | const maxLeft = $(window).width() - getButtonWidth() - rightMargin; 114 | const maxBottom = $footer.offset().top + (getButtonHeight() / 2) + bottomMargin; 115 | if (getScrollTop() === 0 || getScrollBottom() < getRightSidebarBottom() + padding + getButtonHeight()) { 116 | nextState = state['desktop-hidden']; 117 | } else if (getScrollBottom() < maxBottom) { 118 | nextState = state['desktop-visible']; 119 | } else { 120 | nextState = Object.assign({}, state['desktop-dock'], { 121 | bottom: getScrollBottom() - maxBottom + bottomMargin 122 | }); 123 | } 124 | 125 | const left = $mainColumn.offset().left + $mainColumn.outerWidth() + padding; 126 | nextState = Object.assign({}, nextState, { 127 | left: Math.min(left, maxLeft) 128 | }); 129 | applyState(nextState); 130 | } else { 131 | // mobile and tablet mode 132 | if (!isScrollUp()) { 133 | applyState(state['mobile-hidden']); 134 | } else { 135 | applyState(state['mobile-visible']); 136 | } 137 | updateScrollTop(); 138 | } 139 | } 140 | 141 | update(); 142 | $(window).resize(update); 143 | $(window).scroll(update); 144 | 145 | $('#back-to-top').on('click', () => { 146 | if (CSS && CSS.supports && CSS.supports('(scroll-behavior: smooth)')) { 147 | window.scroll({ top: 0, behavior: 'smooth' }); 148 | } else { 149 | $('body, html').animate({ scrollTop: 0 }, 400); 150 | } 151 | }); 152 | }); 153 | -------------------------------------------------------------------------------- /source/js/column.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | function $() { 3 | return Array.prototype.slice.call(document.querySelectorAll.apply(document, arguments)); 4 | } 5 | 6 | // copy widgets in the right column, when exist, to the bottom of the left column 7 | if ($('.columns .column-right').length && $('.columns .column-right-shadow').length && !$('.columns .column-right-shadow')[0].children.length) { 8 | for (const child of $('.columns .column-right')[0].children) { 9 | $('.columns .column-right-shadow')[0].append(child.cloneNode(true)); 10 | } 11 | } 12 | }()); 13 | -------------------------------------------------------------------------------- /source/js/main.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable node/no-unsupported-features/node-builtins */ 2 | (function($, moment, ClipboardJS, config) { 3 | $('.article img:not(".not-gallery-item")').each(function() { 4 | // wrap images with link and add caption if possible 5 | if ($(this).parent('a').length === 0) { 6 | $(this).wrap('<a class="gallery-item" href="' + $(this).attr('src') + '"></a>'); 7 | if (this.alt) { 8 | $(this).after('<p class="has-text-centered is-size-6 caption">' + this.alt + '</p>'); 9 | } 10 | } 11 | }); 12 | 13 | if (typeof $.fn.lightGallery === 'function') { 14 | $('.article').lightGallery({ selector: '.gallery-item' }); 15 | } 16 | if (typeof $.fn.justifiedGallery === 'function') { 17 | if ($('.justified-gallery > p > .gallery-item').length) { 18 | $('.justified-gallery > p > .gallery-item').unwrap(); 19 | } 20 | $('.justified-gallery').justifiedGallery(); 21 | } 22 | 23 | if (typeof moment === 'function') { 24 | $('.article-meta time').each(function() { 25 | $(this).text(moment($(this).attr('datetime')).fromNow()); 26 | }); 27 | } 28 | 29 | $('.article > .content > table').each(function() { 30 | if ($(this).width() > $(this).parent().width()) { 31 | $(this).wrap('<div class="table-overflow"></div>'); 32 | } 33 | }); 34 | 35 | function adjustNavbar() { 36 | const navbarWidth = $('.navbar-main .navbar-start').outerWidth() + $('.navbar-main .navbar-end').outerWidth(); 37 | if ($(document).outerWidth() < navbarWidth) { 38 | $('.navbar-main .navbar-menu').addClass('justify-content-start'); 39 | } else { 40 | $('.navbar-main .navbar-menu').removeClass('justify-content-start'); 41 | } 42 | } 43 | adjustNavbar(); 44 | $(window).resize(adjustNavbar); 45 | 46 | function toggleFold(codeBlock, isFolded) { 47 | const $toggle = $(codeBlock).find('.fold i'); 48 | !isFolded ? $(codeBlock).removeClass('folded') : $(codeBlock).addClass('folded'); 49 | !isFolded ? $toggle.removeClass('fa-angle-right') : $toggle.removeClass('fa-angle-down'); 50 | !isFolded ? $toggle.addClass('fa-angle-down') : $toggle.addClass('fa-angle-right'); 51 | } 52 | 53 | function createFoldButton(fold) { 54 | return '<span class="fold">' + (fold === 'unfolded' ? '<i class="fas fa-angle-down"></i>' : '<i class="fas fa-angle-right"></i>') + '</span>'; 55 | } 56 | 57 | $('figure.highlight table').wrap('<div class="highlight-body">'); 58 | if (typeof config !== 'undefined' 59 | && typeof config.article !== 'undefined' 60 | && typeof config.article.highlight !== 'undefined') { 61 | 62 | $('figure.highlight').addClass('hljs'); 63 | $('figure.highlight .code .line span').each(function() { 64 | const classes = $(this).attr('class').split(/\s+/); 65 | for (const cls of classes) { 66 | $(this).addClass('hljs-' + cls); 67 | $(this).removeClass(cls); 68 | } 69 | }); 70 | 71 | 72 | const clipboard = config.article.highlight.clipboard; 73 | const fold = config.article.highlight.fold.trim(); 74 | 75 | $('figure.highlight').each(function() { 76 | if ($(this).find('figcaption').length) { 77 | $(this).find('figcaption').addClass('level is-mobile'); 78 | $(this).find('figcaption').append('<div class="level-left">'); 79 | $(this).find('figcaption').append('<div class="level-right">'); 80 | $(this).find('figcaption div.level-left').append($(this).find('figcaption').find('span')); 81 | $(this).find('figcaption div.level-right').append($(this).find('figcaption').find('a')); 82 | } else { 83 | if (clipboard || fold) { 84 | $(this).prepend('<figcaption class="level is-mobile"><div class="level-left"></div><div class="level-right"></div></figcaption>'); 85 | } 86 | } 87 | }); 88 | 89 | if (typeof ClipboardJS !== 'undefined' && clipboard) { 90 | $('figure.highlight').each(function() { 91 | const id = 'code-' + Date.now() + (Math.random() * 1000 | 0); 92 | const button = '<a href="javascript:;" class="copy" title="Copy" data-clipboard-target="#' + id + ' .code"><i class="fas fa-copy"></i></a>'; 93 | $(this).attr('id', id); 94 | $(this).find('figcaption div.level-right').append(button); 95 | }); 96 | new ClipboardJS('.highlight .copy'); // eslint-disable-line no-new 97 | } 98 | 99 | if (fold) { 100 | $('figure.highlight').each(function() { 101 | $(this).addClass('foldable'); // add 'foldable' class as long as fold is enabled 102 | 103 | if ($(this).find('figcaption').find('span').length > 0) { 104 | const span = $(this).find('figcaption').find('span'); 105 | if (span[0].innerText.indexOf('>folded') > -1) { 106 | span[0].innerText = span[0].innerText.replace('>folded', ''); 107 | $(this).find('figcaption div.level-left').prepend(createFoldButton('folded')); 108 | toggleFold(this, true); 109 | return; 110 | } 111 | } 112 | $(this).find('figcaption div.level-left').prepend(createFoldButton(fold)); 113 | toggleFold(this, fold === 'folded'); 114 | }); 115 | 116 | $('figure.highlight figcaption .level-left').click(function() { 117 | const $code = $(this).closest('figure.highlight'); 118 | toggleFold($code.eq(0), !$code.hasClass('folded')); 119 | }); 120 | } 121 | } 122 | 123 | const $toc = $('#toc'); 124 | if ($toc.length > 0) { 125 | const $mask = $('<div>'); 126 | $mask.attr('id', 'toc-mask'); 127 | 128 | $('body').append($mask); 129 | 130 | function toggleToc() { // eslint-disable-line no-inner-declarations 131 | $toc.toggleClass('is-active'); 132 | $mask.toggleClass('is-active'); 133 | } 134 | 135 | $toc.on('click', toggleToc); 136 | $mask.on('click', toggleToc); 137 | $('.navbar-main .catalogue').on('click', toggleToc); 138 | } 139 | }(jQuery, window.moment, window.ClipboardJS, window.IcarusThemeSettings)); 140 | -------------------------------------------------------------------------------- /source/js/pjax.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | // eslint-disable-next-line no-unused-vars 3 | let pjax; 4 | 5 | function initPjax() { 6 | try { 7 | const Pjax = window.Pjax || function() {}; 8 | pjax = new Pjax({ 9 | selectors: [ 10 | '[data-pjax]', 11 | '.pjax-reload', 12 | 'head title', 13 | '.columns', 14 | '.navbar-start', 15 | '.navbar-end', 16 | '.searchbox link', 17 | '.searchbox script', 18 | '#back-to-top', 19 | '#comments link', 20 | '#comments script' 21 | ], 22 | cacheBust: false 23 | }); 24 | } catch (e) { 25 | console.warn('PJAX error: ' + e); 26 | } 27 | } 28 | 29 | // // Listen for start of Pjax 30 | // document.addEventListener('pjax:send', function() { 31 | // return; 32 | // // TODO pace start loading animation 33 | // }) 34 | 35 | // Listen for completion of Pjax 36 | document.addEventListener('pjax:complete', () => { 37 | // Plugin [MathJax] reload logic 38 | if (window.MathJax) { 39 | try { 40 | window.MathJax.typesetPromise && window.MathJax.typesetPromise(); 41 | } catch (e) { 42 | console.error('MathJax reload error:', e); 43 | } 44 | } 45 | // Plugin [Busuanzi] reload logic 46 | if (window.bszCaller && window.bszTag) { 47 | window.bszCaller.fetch('//busuanzi.ibruce.info/busuanzi?jsonpCallback=BusuanziCallback', a => { 48 | window.bszTag.texts(a); 49 | window.bszTag.shows(); 50 | }); 51 | } 52 | 53 | // TODO pace stop loading animation 54 | }); 55 | 56 | document.addEventListener('DOMContentLoaded', () => initPjax()); 57 | }()); 58 | --------------------------------------------------------------------------------