├── .github ├── dependabot.yml └── workflows │ ├── publish.yml │ └── release.yml ├── .gitignore ├── package.json ├── LICENSE ├── lib ├── themes.js ├── codeblock.js └── codeblock.css ├── pnpm-lock.yaml ├── CHANGELOG.md ├── README.md └── index.js /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: npm 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | open-pull-requests-limit: 20 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | .DS_Store 12 | dist 13 | dist-ssr 14 | coverage 15 | *.local 16 | 17 | /cypress/videos/ 18 | /cypress/screenshots/ 19 | 20 | # Editor directories and files 21 | .vscode/* 22 | !.vscode/extensions.json 23 | .idea 24 | *.suo 25 | *.ntvs* 26 | *.njsproj 27 | *.sln 28 | *.sw? -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: "Publish" 2 | 3 | on: 4 | push: 5 | tags: 6 | - "v*" 7 | 8 | jobs: 9 | npmjs_publish: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout 13 | uses: actions/checkout@v3 14 | - name: Install Node.js 15 | uses: actions/setup-node@v3 16 | with: 17 | registry-url: "https://registry.npmjs.org" 18 | node-version: 16 19 | - name: Publish to npmjs 20 | run: npm publish 21 | env: 22 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 23 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hexo-shiki-plugin", 3 | "version": "1.0.27", 4 | "description": "A beautiful hexo code block highlight plugin based on shiki", 5 | "author": "nova1751", 6 | "license": "MIT", 7 | "main": "index.js", 8 | "scripts": { 9 | "test": "echo \"Error: no test specified\" && exit 1" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "https://github.com/nova1751/hexo-shiki-plugin.git" 14 | }, 15 | "bugs": { 16 | "url": "https://github.com/nova1751/hexo-shiki-plugin/issues" 17 | }, 18 | "homepage": "https://github.com/nova1751/hexo-shiki-plugin", 19 | "keywords": [ 20 | "hexo", 21 | "shiki", 22 | "code", 23 | "highlight", 24 | "butterfly", 25 | "hexo-theme-butterfly" 26 | ], 27 | "dependencies": { 28 | "shiki-nova1751": "^0.0.0", 29 | "strip-indent": "^3.0.0" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: "Release" 2 | 3 | on: 4 | push: 5 | tags: 6 | - "v*" 7 | 8 | jobs: 9 | tagged-release: 10 | runs-on: "ubuntu-latest" 11 | steps: 12 | - name: Checkout 13 | uses: actions/checkout@v3 14 | - name: Get current package version 15 | id: package_version 16 | uses: martinbeentjes/npm-get-version-action@v1.1.0 17 | - name: Get Changelog Entry 18 | id: changelog_reader 19 | uses: mindsers/changelog-reader-action@v2.0.0 20 | with: 21 | validation_level: warn 22 | version: ${{ steps.package_version.outputs.current-version }} 23 | path: "CHANGELOG.md" 24 | - name: Create a Release 25 | id: create_release 26 | uses: actions/create-release@v1 27 | env: 28 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 29 | with: 30 | tag_name: ${{ steps.package_version.outputs.current-version}} 31 | release_name: ${{ steps.package_version.outputs.current-version}} 32 | body: ${{ steps.changelog_reader.outputs.changes }} 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 nova1751 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 | -------------------------------------------------------------------------------- /lib/themes.js: -------------------------------------------------------------------------------- 1 | const themes = new Map(); 2 | themes.set( 3 | "one-dark-pro", 4 | `` 5 | ); 6 | themes.set( 7 | "material-theme-palenight", 8 | `` 9 | ); 10 | themes.set( 11 | "github-light", 12 | `` 13 | ); 14 | themes.set( 15 | "github-dark", 16 | `` 17 | ); 18 | module.exports = themes; 19 | -------------------------------------------------------------------------------- /pnpm-lock.yaml: -------------------------------------------------------------------------------- 1 | lockfileVersion: '6.0' 2 | 3 | settings: 4 | autoInstallPeers: true 5 | excludeLinksFromLockfile: false 6 | 7 | dependencies: 8 | shiki-nova1751: 9 | specifier: ^0.0.0 10 | version: 0.0.0 11 | strip-indent: 12 | specifier: ^3.0.0 13 | version: 3.0.0 14 | 15 | packages: 16 | 17 | /ansi-sequence-parser@1.1.1: 18 | resolution: {integrity: sha512-vJXt3yiaUL4UU546s3rPXlsry/RnM730G1+HkpKE012AN0sx1eOrxSu95oKDIonskeLTijMgqWZ3uDEe3NFvyg==} 19 | dev: false 20 | 21 | /jsonc-parser@3.2.0: 22 | resolution: {integrity: sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==} 23 | dev: false 24 | 25 | /min-indent@1.0.1: 26 | resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} 27 | engines: {node: '>=4'} 28 | dev: false 29 | 30 | /shiki-nova1751@0.0.0: 31 | resolution: {integrity: sha512-6RK+IVNm+j5vPYsRP8Yv9M7dETcmQGO2T6sR2TWF64LgqiIc/zAt+yX+5lhTWegQ36nh1/t+YViX8ypZJGqdJA==} 32 | dependencies: 33 | ansi-sequence-parser: 1.1.1 34 | jsonc-parser: 3.2.0 35 | vscode-oniguruma: 1.7.0 36 | vscode-textmate: 8.0.0 37 | dev: false 38 | 39 | /strip-indent@3.0.0: 40 | resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==} 41 | engines: {node: '>=8'} 42 | dependencies: 43 | min-indent: 1.0.1 44 | dev: false 45 | 46 | /vscode-oniguruma@1.7.0: 47 | resolution: {integrity: sha512-L9WMGRfrjOhgHSdOYgCt/yRMsXzLDJSL7BPrOZt73gU0iWO4mpqzqQzOz5srxqTvMBaR0XZTSrVWo4j55Rc6cA==} 48 | dev: false 49 | 50 | /vscode-textmate@8.0.0: 51 | resolution: {integrity: sha512-AFbieoL7a5LMqcnOF04ji+rpXadgOXnZsxQr//r83kLPr7biP7am3g9zbaZIaBGwBRWeSvoMD4mgPdX3e4NWBg==} 52 | dev: false 53 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [1.0.27] - 2024-6-15 4 | 5 | ### Style Update 6 | 7 | - Ajust the text style. @noraj 8 | 9 | ## [1.0.26] - 2024-6-15 10 | 11 | ### Style Update 12 | 13 | - Ajust the font style. 14 | 15 | ## [1.0.25] - 2024-1-25 16 | 17 | ### Bug Fixes 18 | 19 | - Add cutom CDN url. 20 | 21 | ## [1.0.24] - 2023-12-14 22 | 23 | ### Bug Fixes 24 | 25 | - Ajust the code font to adapt to the PC without consolas font. 26 | 27 | ## [1.0.23] - 2023-11-30 28 | 29 | ### Bug Fixes 30 | 31 | - Remove the tabindex attribute in codeblock. 32 | - Change dependency shiki to shiki-nova1751 to ajust the `vue` code highlight. 33 | 34 | ## [1.0.23] - 2023-11-30 35 | 36 | ### Bug Fixes 37 | 38 | - Remove the tabindex attribute in codeblock. 39 | - Change dependency shiki to shiki-nova1751 to ajust the `vue` code highlight. 40 | 41 | ## [1.0.22] - 2023-11-18 42 | 43 | ### Bug Fixes 44 | 45 | - Ajust the github-light theme style. 46 | 47 | ## [1.0.21] - 2023-11-18 48 | 49 | ### Bug Fixes 50 | 51 | - Fix the code block display problem in the blockquote. 52 | 53 | ## [1.0.20] - 2023-11-17 54 | 55 | ### New Features 56 | 57 | - For the languages not supported by shiki,`hexo-shiki-plugin` will display it as plain text and print the `error` message in the console. 58 | - Add `Clipboard` API to the copy function for better compatibility. 59 | 60 | ## [1.0.19] - 2023-11-16 61 | 62 | ### Chore 63 | 64 | - Remove the box-shadow style to adapt the dark-mode 65 | 66 | ## [1.0.18] - 2023-11-15 67 | 68 | ### New Features 69 | 70 | - Add line_number option to show or hide line_number. 71 | - Add the code highlight theme,for themes not built in,load the theme code in the `one-dark-pro` codeblock style. 72 | 73 | ### Bug Fixes 74 | 75 | - Change the table style to flex to avoid table wrap problems. 76 | - Use `em` unit to unify the code font size. 77 | 78 | ## [1.0.17] - 2023-11-14 79 | 80 | ### Chore 81 | 82 | - Simplify the code 83 | 84 | ## [1.0.16] - 2023-11-14 85 | 86 | ### Bug fixes 87 | 88 | - Remove the css class conflicts with theme butterfly 89 | - fix some style conflicts 90 | - update github workflow 91 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Hexo Shiki Plugin 2 | 3 |
27 | 28 | > A code highlight plugin based on shiki,built for hexo.You can go to my [blog](https://blog.refrain.site) for preview. 29 | 30 | ## Installation 31 | 32 | 1. Intall the plugin. 33 | ```bash 34 | yarn add hexo-shiki-plugin 35 | ``` 36 | 2. Setup config. 37 | ```yml 38 | shiki: 39 | theme: one-dark-pro 40 | ``` 41 | 42 | > [!WARNING] 43 | > To avoid conflicts with the native code highlight plugin,please disable the native plugins. 44 | > 45 | > ```yml 46 | > highlight: 47 | > enable: false 48 | > prismjs: 49 | > enable: false 50 | > ``` 51 | > 52 | > for `hexo>=7.0.0` versions,please add a additional line,leave `syntax_highlighter` to empty,just like below. 53 | > 54 | > ```yml 55 | > syntax_highlighter: 56 | > ``` 57 | 58 | ## Usage 59 | 60 | > this plugin has four themes built in,you can choose one of theme to display: 61 | > 62 | > - `one-dark-pro` 63 | > - `material-theme-palenight` 64 | > - `github-light` 65 | > - `github-dark` 66 | 67 | If you choose a theme other than one of the built-in themes,the plugin will use the `one-dark-pro` codeblock style,and load the specific theme code.You can load more code highlight themes in [Themes](https://github.com/shikijs/shiki/blob/main/docs/themes.md). 68 | 69 | There are some other features ported from [hexo-theme-butterfly](https://github.com/jerryc127/hexo-theme-butterfly.git).The available settings are below: 70 | 71 | > [!NOTE] 72 | > If you want to enable the code block beautify config, please make sure your website has introduced the font-awesome icon set. 73 | 74 | ```yml 75 | shiki: 76 | theme: github-light # highlight-theme 77 | line_number: true # whether to show the line_number 78 | beautify: false # whether to add highlight tool true or false 79 | highlight_copy: true # copy button 80 | highlight_lang: false # show the code language 81 | highlight_height_limit: 360 # code-block max height,unit: px 82 | is_highlight_shrink: false # true: shrink the code blocks / false: expand the code blocks | none: expand code blocks and hide the button 83 | copy: # copy message 84 | success: 'Copy Success' 85 | error: 'Copy Error' 86 | no_support: 'Browser Not Support' 87 | ``` 88 | 89 | > [!NOTE] 90 | > Since shiki support a lot of beautiful themes,you can add your own cutom css files to cusomize your codeblock style,here is an example: 91 | 92 | ```css 93 | :root { 94 | --hl-color: #e1e4e8; 95 | --hl-bg: #24292e; 96 | --hltools-bg: #1f2428; 97 | --hltools-color: #c5c5c5; 98 | --hlnumber-bg: #24292e; 99 | --hlnumber-color: #444d56; 100 | --hlscrollbar-bg: #32383e; 101 | --hlexpand-bg: linear-gradient( 102 | 180deg, 103 | rgba(36, 41, 46, 0.6), 104 | rgba(36, 41, 46, 0.9) 105 | ); 106 | } 107 | ``` 108 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const shiki = require("shiki-nova1751"); 2 | const stripIndent = require("strip-indent"); 3 | const themes = require("./lib/themes"); 4 | const { version } = require("./package.json"); 5 | const codeMatch = 6 | /(?[> ]*)(?(-|\d+\.)?)(?
\s*)(? ~{3,}|`{3,}) *(? .*)\n(? [\s\S]*?)\k\s*\k(? \s*)$/gm; 7 | const config = hexo.config.shiki; 8 | if (!config) return; 9 | const { 10 | theme, 11 | line_number, 12 | beautify, 13 | highlight_copy, 14 | highlight_lang, 15 | highlight_height_limit, 16 | is_highlight_shrink, 17 | css_cdn, 18 | js_cdn, 19 | copy: { success, error, no_support } = {}, 20 | } = config; 21 | const codeblockTheme = themes.has(theme) ? theme : "one-dark-pro"; 22 | const css = hexo.extend.helper.get("css").bind(hexo); 23 | const js = hexo.extend.helper.get("js").bind(hexo); 24 | hexo.extend.injector.register("head_end", () => { 25 | return css( 26 | css_cdn || 27 | "https://cdn.jsdelivr.net/npm/hexo-shiki-plugin@latest/lib/codeblock.css" 28 | ); 29 | }); 30 | hexo.extend.injector.register("head_end", () => { 31 | return themes.get(codeblockTheme); 32 | }); 33 | if (config.highlight_height_limit) { 34 | hexo.extend.injector.register("head_end", () => { 35 | return ` 36 | 43 | `; 44 | }); 45 | } 46 | 47 | if (beautify) { 48 | hexo.extend.injector.register("body_end", () => { 49 | return js( 50 | js_cdn || 51 | "https://cdn.jsdelivr.net/npm/hexo-shiki-plugin@latest/lib/codeblock.js" 52 | ); 53 | }); 54 | } 55 | hexo.extend.injector.register("body_end", () => { 56 | return ` 57 | 79 | `; 80 | }); 81 | return shiki 82 | .getHighlighter({ 83 | theme, 84 | }) 85 | .then((hl) => { 86 | hexo.extend.filter.register("before_post_render", (post) => { 87 | post.content = post.content.replace(codeMatch, (...argv) => { 88 | let { quote, ul, start, end, args, code } = argv.pop(); 89 | let result; 90 | const match = new RegExp(`^${quote.trimEnd()}`, "gm"); 91 | code = code.replace(match, ""); 92 | code = stripIndent(code.trimEnd()); 93 | const arr = code.split("\n"); 94 | let numbers = ""; 95 | let pre = ""; 96 | try { 97 | pre = hl.codeToHtml(code, { lang: args }); 98 | pre = pre.replace(/ ]*>/, (match) => { 99 | return match.replace(/\s*style\s*=\s*"[^"]*"\s*tabindex="0"/, ""); 100 | }); 101 | } catch (error) { 102 | console.warn(error); 103 | pre = ``; 104 | } 105 | result = `${code}`; 106 | result += " "; 115 | return `${ 116 | quote + ul + start 117 | }"; 107 | if (line_number) { 108 | for (let i = 0, len = arr.length; i < len; i++) { 109 | numbers += `${1 + i}
`; 110 | } 111 | result += ``; 112 | } 113 | result += `${numbers}${pre}`; 114 | result += "${result} ${end}`; 118 | }); 119 | }); 120 | }); 121 | -------------------------------------------------------------------------------- /lib/codeblock.js: -------------------------------------------------------------------------------- 1 | const addHighlightTool = function () { 2 | const isHidden = (ele) => ele.offsetHeight === 0 && ele.offsetWidth === 0; 3 | if (!CODE_CONFIG || !CODE_CONFIG.beautify) return; 4 | 5 | const { highlightCopy, highlightLang, highlightHeightLimit } = CODE_CONFIG; 6 | const isHighlightShrink = CODE_CONFIG.isHighlightShrink; 7 | const isShowTool = 8 | highlightCopy || highlightLang || isHighlightShrink !== undefined; 9 | const $figureHighlight = document.querySelectorAll("figure.shiki"); 10 | 11 | if (!((isShowTool || highlightHeightLimit) && $figureHighlight.length)) 12 | return; 13 | 14 | const highlightShrinkClass = isHighlightShrink === true ? "closed" : ""; 15 | const highlightShrinkEle = 16 | isHighlightShrink !== undefined 17 | ? `` 18 | : ""; 19 | const highlightCopyEle = highlightCopy 20 | ? '' 21 | : ""; 22 | 23 | const copy = async (text, ctx) => { 24 | const showMsg = (msg) => { 25 | const prevEle = ctx.previousElementSibling; 26 | prevEle.textContent = msg; 27 | prevEle.style.opacity = 1; 28 | setTimeout(() => { 29 | prevEle.style.opacity = 0; 30 | }, 700); 31 | }; 32 | if ( 33 | document.queryCommandSupported && 34 | document.queryCommandSupported("copy") 35 | ) { 36 | document.execCommand("copy"); 37 | showMsg(CODE_CONFIG.copy.success); 38 | } else if (navigator.clipboard) { 39 | try { 40 | await navigator.clipboard.writeText(text); 41 | showMsg(CODE_CONFIG.copy.success); 42 | } catch { 43 | showMsg(CODE_CONFIG.copy.error); 44 | } 45 | } else { 46 | showMsg(CODE_CONFIG.copy.noSupport); 47 | } 48 | }; 49 | 50 | // click events 51 | const highlightCopyFn = (ele) => { 52 | const $buttonParent = ele.parentNode; 53 | $buttonParent.classList.add("copy-true"); 54 | const selection = window.getSelection(); 55 | const range = document.createRange(); 56 | const preCodeSelector = "div.codeblock .code pre"; 57 | range.selectNodeContents( 58 | $buttonParent.querySelectorAll(`${preCodeSelector}`)[0] 59 | ); 60 | selection.removeAllRanges(); 61 | selection.addRange(range); 62 | const text = selection.toString(); 63 | copy(text, ele.lastChild); 64 | selection.removeAllRanges(); 65 | $buttonParent.classList.remove("copy-true"); 66 | }; 67 | 68 | const highlightShrinkFn = (ele) => { 69 | const $nextEle = [...ele.parentNode.children].slice(1); 70 | ele.firstChild.classList.toggle("closed"); 71 | if (isHidden($nextEle[$nextEle.length - 1])) { 72 | $nextEle.forEach((e) => { 73 | e.style.display = "flex"; 74 | }); 75 | } else { 76 | $nextEle.forEach((e) => { 77 | e.style.display = "none"; 78 | }); 79 | } 80 | }; 81 | 82 | const highlightToolsFn = function (e) { 83 | const $target = e.target.classList; 84 | if ($target.contains("expand")) highlightShrinkFn(this); 85 | else if ($target.contains("copy-button")) highlightCopyFn(this); 86 | }; 87 | 88 | const expandCode = function () { 89 | this.classList.toggle("expand-done"); 90 | }; 91 | 92 | function createEle(lang, item, service) { 93 | const fragment = document.createDocumentFragment(); 94 | 95 | if (isShowTool) { 96 | const hlTools = document.createElement("div"); 97 | hlTools.className = `shiki-tools ${highlightShrinkClass}`; 98 | hlTools.innerHTML = highlightShrinkEle + lang + highlightCopyEle; 99 | hlTools.addEventListener("click", highlightToolsFn); 100 | fragment.appendChild(hlTools); 101 | } 102 | 103 | if (highlightHeightLimit && item.offsetHeight > highlightHeightLimit + 30) { 104 | const ele = document.createElement("div"); 105 | ele.className = "code-expand-btn"; 106 | ele.innerHTML = ''; 107 | ele.addEventListener("click", expandCode); 108 | fragment.appendChild(ele); 109 | } 110 | 111 | if (service === "hl") { 112 | item.insertBefore(fragment, item.firstChild); 113 | } else { 114 | item.parentNode.insertBefore(fragment, item); 115 | } 116 | } 117 | 118 | $figureHighlight.forEach(function (item) { 119 | if (highlightLang) { 120 | let langName = item.getAttribute("class").split(" ")[1]; 121 | if (langName === "plain" || langName === undefined) 122 | langName = "PlainText"; 123 | const highlightLangEle = `${langName}`; 124 | createEle(highlightLangEle, item, "hl"); 125 | } else { 126 | createEle("", item, "hl"); 127 | } 128 | }); 129 | }; 130 | 131 | document.addEventListener("pjax:success", addHighlightTool); 132 | document.addEventListener("DOMContentLoaded", addHighlightTool); 133 | -------------------------------------------------------------------------------- /lib/codeblock.css: -------------------------------------------------------------------------------- 1 | /* scrollbar */ 2 | figure.shiki div.codeblock::-webkit-scrollbar-thumb { 3 | background: var(--hlscrollbar-bg); 4 | border-radius: 2.5px; 5 | } 6 | figure.shiki div.codeblock::-webkit-scrollbar { 7 | width: 5px; 8 | height: 5px; 9 | } 10 | /* code block figure */ 11 | figure.shiki .shiki-tools.closed ~ * { 12 | display: none; 13 | } 14 | figure.shiki div.codeblock { 15 | margin: 0; 16 | } 17 | figure.shiki { 18 | margin: 0 0 24px; 19 | border-radius: 7px; 20 | -webkit-transform: translateZ(0); 21 | transform: translateZ(0); 22 | position: relative; 23 | overflow: auto; 24 | padding: 0; 25 | background: var(--hl-bg); 26 | color: var(--hl-color); 27 | line-height: 1.6; 28 | } 29 | /* highlighttool bar */ 30 | .shiki-tools { 31 | position: relative; 32 | display: -webkit-box; 33 | display: -moz-box; 34 | display: -webkit-flex; 35 | display: -ms-flexbox; 36 | display: box; 37 | display: flex; 38 | -webkit-box-align: center; 39 | -moz-box-align: center; 40 | -o-box-align: center; 41 | -ms-flex-align: center; 42 | -webkit-align-items: center; 43 | align-items: center; 44 | overflow: hidden; 45 | min-height: 24px; 46 | height: 2.15em; 47 | background: var(--hltools-bg); 48 | color: var(--hltools-color); 49 | font-size: 1em; 50 | user-select: none; 51 | } 52 | figure.shiki .shiki-tools .expand { 53 | right: 0; 54 | position: absolute; 55 | padding: 0.57em 0.7em; 56 | cursor: pointer; 57 | -webkit-transition: -webkit-transform 0.3s; 58 | -moz-transition: -moz-transform 0.3s; 59 | -o-transition: -o-transform 0.3s; 60 | -ms-transition: -ms-transform 0.3s; 61 | transition: transform 0.3s; 62 | } 63 | 64 | figure.shiki .shiki-tools .expand.closed { 65 | -webkit-transition: all 0.3s; 66 | -moz-transition: all 0.3s; 67 | -o-transition: all 0.3s; 68 | -ms-transition: all 0.3s; 69 | transition: all 0.3s; 70 | -webkit-transform: rotate(90deg); 71 | -moz-transform: rotate(90deg); 72 | -o-transform: rotate(90deg); 73 | -ms-transform: rotate(90deg); 74 | transform: rotate(90deg); 75 | } 76 | figure.shiki .shiki-tools .code-lang { 77 | left: 75px; 78 | position: absolute; 79 | text-transform: uppercase; 80 | font-weight: 700; 81 | font-size: 1.15em; 82 | -webkit-user-select: none; 83 | -moz-user-select: none; 84 | -ms-user-select: none; 85 | user-select: none; 86 | } 87 | 88 | figure.shiki .shiki-tools .expand ~ .copy-notice { 89 | right: 3.45em; 90 | position: absolute; 91 | opacity: 0; 92 | -webkit-transition: opacity 0.4s; 93 | -moz-transition: opacity 0.4s; 94 | -o-transition: opacity 0.4s; 95 | -ms-transition: opacity 0.4s; 96 | transition: opacity 0.4s; 97 | } 98 | 99 | figure.shiki .shiki-tools .expand ~ .copy-button { 100 | right: 2.1em; 101 | position: absolute; 102 | cursor: pointer; 103 | -webkit-transition: color 0.2s; 104 | -moz-transition: color 0.2s; 105 | -o-transition: color 0.2s; 106 | -ms-transition: color 0.2s; 107 | transition: color 0.2s; 108 | } 109 | .shiki-tools .copy-button:hover { 110 | color: #49b1f5; 111 | } 112 | figure.shiki .shiki-tools:after { 113 | position: absolute; 114 | left: 14px; 115 | width: 12px; 116 | height: 12px; 117 | border-radius: 50%; 118 | background: #fc625d; 119 | -webkit-box-shadow: 20px 0 #fdbc40, 40px 0 #35cd4b; 120 | box-shadow: 20px 0 #fdbc40, 40px 0 #35cd4b; 121 | content: " "; 122 | } 123 | /* bottom toolbar */ 124 | .code-expand-btn { 125 | position: absolute; 126 | display: flex; 127 | bottom: 0; 128 | z-index: 10; 129 | width: 100%; 130 | background: var(--hlexpand-bg); 131 | justify-content: center; 132 | align-items: center; 133 | font-size: 1em; 134 | cursor: pointer; 135 | } 136 | @-moz-keyframes code-expand-key { 137 | 0% { 138 | opacity: 0.6; 139 | } 140 | 141 | 50% { 142 | opacity: 0.1; 143 | } 144 | 145 | 100% { 146 | opacity: 0.6; 147 | } 148 | } 149 | 150 | @-webkit-keyframes code-expand-key { 151 | 0% { 152 | opacity: 0.6; 153 | } 154 | 155 | 50% { 156 | opacity: 0.1; 157 | } 158 | 159 | 100% { 160 | opacity: 0.6; 161 | } 162 | } 163 | 164 | @-o-keyframes code-expand-key { 165 | 0% { 166 | opacity: 0.6; 167 | } 168 | 169 | 50% { 170 | opacity: 0.1; 171 | } 172 | 173 | 100% { 174 | opacity: 0.6; 175 | } 176 | } 177 | 178 | @keyframes code-expand-key { 179 | 0% { 180 | opacity: 0.6; 181 | } 182 | 183 | 50% { 184 | opacity: 0.1; 185 | } 186 | 187 | 100% { 188 | opacity: 0.6; 189 | } 190 | } 191 | .code-expand-btn i { 192 | padding: 6px 0; 193 | color: var(--hlnumber-color); 194 | -webkit-animation: code-expand-key 1.2s infinite; 195 | -moz-animation: code-expand-key 1.2s infinite; 196 | -o-animation: code-expand-key 1.2s infinite; 197 | -ms-animation: code-expand-key 1.2s infinite; 198 | animation: code-expand-key 1.2s infinite; 199 | } 200 | .code-expand-btn.expand-done > i { 201 | -webkit-transform: rotate(180deg); 202 | -moz-transform: rotate(180deg); 203 | -o-transform: rotate(180deg); 204 | -ms-transform: rotate(180deg); 205 | transform: rotate(180deg); 206 | } 207 | /* codeblock */ 208 | figure.shiki div.codeblock { 209 | display: block; 210 | overflow: auto; 211 | border: none; 212 | display: flex; 213 | } 214 | figure.shiki div.codeblock div { 215 | padding: 0; 216 | border: none; 217 | } 218 | figure.shiki .gutter pre { 219 | padding-right: 10px !important; 220 | padding-left: 10px !important; 221 | background-color: var(--hlnumber-bg) !important; 222 | color: var(--hlnumber-color) !important; 223 | text-align: right !important; 224 | user-select: none !important; 225 | } 226 | figure.shiki pre { 227 | margin: 0 !important; 228 | padding: 8px 0 !important; 229 | border: none !important; 230 | } 231 | figure.shiki pre code { 232 | background: none !important; 233 | } 234 | figure.shiki .codeblock pre * { 235 | font-size: 1em; 236 | font-family: Consolas, "Fira Code", "Fira Mono", Menlo, "DejaVu Sans Mono", 237 | monospace, 宋体; 238 | overflow: auto !important; 239 | line-height: 1.6; 240 | } 241 | figure.shiki .code pre { 242 | padding-right: 10px !important; 243 | padding-left: 10px !important; 244 | width: 100% !important; 245 | background: none !important; 246 | } 247 | .code-expand-btn:not(.expand-done) ~ pre, 248 | .code-expand-btn:not(.expand-done) ~ *pre { 249 | overflow: hidden; 250 | } 251 | .code-expand-btn.expand-done + pre, 252 | .code-expand-btn.expand-done + * pre, 253 | .code-expand-btn.expand-done + div.codeblock, 254 | .code-expand-btn.expand-done + * div.codeblock { 255 | margin-bottom: 1.8em; 256 | } 257 | --------------------------------------------------------------------------------