├── .eslintrc.js ├── .github └── workflows │ └── codeql-analysis.yml ├── .gitignore ├── .nvmrc ├── .prettierignore ├── .prettierrc.json ├── .vscode └── settings.json ├── CHANGELOG.md ├── LICENSE ├── README.md ├── demo ├── de-de │ └── test.md ├── es │ └── test.md ├── index.html ├── navbar.md ├── ru │ └── test.md └── zh-cn │ └── test.md ├── index.js ├── package-lock.json ├── package.json ├── rollup.config.mjs ├── server.js └── src ├── index.js └── styles.css /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | browser: true, 4 | commonjs: true, 5 | es6: true, 6 | node: true, 7 | }, 8 | extends: ['eslint:recommended', 'prettier'], 9 | parserOptions: { 10 | ecmaVersion: 6, 11 | sourceType: 'module', 12 | }, 13 | plugins: [], 14 | rules: { 15 | 'array-bracket-spacing': ['error', 'never'], 16 | 'array-callback-return': ['error'], 17 | 'block-scoped-var': ['error'], 18 | 'block-spacing': ['error', 'always'], 19 | curly: ['error'], 20 | 'dot-notation': ['error'], 21 | eqeqeq: ['error'], 22 | indent: ['error', 2], 23 | 'linebreak-style': ['error', 'unix'], 24 | 'no-console': ['warn'], 25 | 'no-floating-decimal': ['error'], 26 | 'no-implicit-coercion': ['error'], 27 | 'no-implicit-globals': ['error'], 28 | 'no-loop-func': ['error'], 29 | 'no-return-assign': ['error'], 30 | 'no-template-curly-in-string': ['error'], 31 | 'no-unneeded-ternary': ['error'], 32 | 'no-unused-vars': ['error', { args: 'none' }], 33 | 'no-useless-computed-key': ['error'], 34 | 'no-useless-return': ['error'], 35 | 'no-var': ['error'], 36 | 'prefer-const': ['error'], 37 | quotes: ['error', 'single'], 38 | semi: ['error', 'always'], 39 | }, 40 | }; 41 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ "master" ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ "master" ] 20 | schedule: 21 | - cron: '16 9 * * 2' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | permissions: 28 | actions: read 29 | contents: read 30 | security-events: write 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | language: [ 'javascript' ] 36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] 37 | # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support 38 | 39 | steps: 40 | - name: Checkout repository 41 | uses: actions/checkout@v3 42 | 43 | # Initializes the CodeQL tools for scanning. 44 | - name: Initialize CodeQL 45 | uses: github/codeql-action/init@v2 46 | with: 47 | languages: ${{ matrix.language }} 48 | # If you wish to specify custom queries, you can do so here or in a config file. 49 | # By default, queries listed here will override any specified in a config file. 50 | # Prefix the list here with "+" to use these queries and those in the config file. 51 | 52 | # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs 53 | # queries: security-extended,security-and-quality 54 | 55 | 56 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 57 | # If this step fails, then you should remove it and run the build manually (see below) 58 | - name: Autobuild 59 | uses: github/codeql-action/autobuild@v2 60 | 61 | # ℹ️ Command-line programs to run using the OS shell. 62 | # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun 63 | 64 | # If the Autobuild fails above, remove it and uncomment the following three lines. 65 | # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. 66 | 67 | # - run: | 68 | # echo "Run, Build Application using script" 69 | # ./location_of_script_within_repo/buildscript.sh 70 | 71 | - name: Perform CodeQL Analysis 72 | uses: github/codeql-action/analyze@v2 73 | with: 74 | category: "/language:${{matrix.language}}" 75 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Directories 2 | dist 3 | 4 | # Logs 5 | logs 6 | *.log 7 | npm-debug.log* 8 | yarn-debug.log* 9 | yarn-error.log* 10 | 11 | # OS files 12 | .DS_Store 13 | Thumbs.db 14 | 15 | # Runtime data 16 | pids 17 | *.pid 18 | *.seed 19 | *.pid.lock 20 | 21 | # Directory for instrumented libs generated by jscoverage/JSCover 22 | lib-cov 23 | 24 | # Coverage directory used by tools like istanbul 25 | coverage 26 | 27 | # nyc test coverage 28 | .nyc_output 29 | 30 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 31 | .grunt 32 | 33 | # Bower dependency directory (https://bower.io/) 34 | bower_components 35 | 36 | # node-waf configuration 37 | .lock-wscript 38 | 39 | # Compiled binary addons (http://nodejs.org/api/addons.html) 40 | build/Release 41 | 42 | # Dependency directories 43 | node_modules/ 44 | jspm_packages/ 45 | 46 | # Typescript v1 declaration files 47 | typings/ 48 | 49 | # Optional npm cache directory 50 | .npm 51 | 52 | # Optional eslint cache 53 | .eslintcache 54 | 55 | # Optional REPL history 56 | .node_repl_history 57 | 58 | # Output of 'npm pack' 59 | *.tgz 60 | 61 | # Yarn Integrity file 62 | .yarn-integrity 63 | .yarn.lock 64 | 65 | # dotenv environment variables file 66 | .env 67 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 18 -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # Ignore artifacts 2 | dist 3 | node_modules -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "semi": true, 3 | "singleQuote": true, 4 | "trailingComma": "es5" 5 | } 6 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cSpell.words": ["docsify", "iife"] 3 | } 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | ## 3.0.0 4 | 5 | _2023-08-18_ 6 | 7 | - Upgrade all dependencies & overhaul build tools 8 | - Drop support for Node.js < v14 9 | - Ensure screen readers correctly announces copied text 10 | 11 | ## 2.1.0 12 | 13 | _2019-01-23_ 14 | 15 | - Added localization (l10n) support via `copyCode` options 16 | - Added error feedback (UI & console) 17 | - Added demo site 18 | - Fixed success feedback in IE 19 | - Updated README 20 | - Updated dependencies 21 | 22 | ## 2.0.2 23 | 24 | _2018-04-16_ 25 | 26 | - Updated plugin bundling configuration 27 | - Removed necessity for `init()` method 28 | - Removed external stylesheet 29 | 30 | ## 1.0.0 31 | 32 | _2017-09-28_ 33 | 34 | - Initial release 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 JP Erasmus 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 | # docsify-copy-code 2 | 3 | [![NPM](https://img.shields.io/npm/v/docsify-copy-code.svg?style=flat-square)](https://www.npmjs.com/package/docsify-copy-code) 4 | [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg?style=flat-square)](https://github.com/jhildenbiddle/docsify-copy-code/blob/master/LICENSE) 5 | 6 | A [docsify](https://docsify.js.org) plugin that adds a button to easily copy code blocks to your clipboard. 7 | 8 | ## Installation 9 | 10 | ### Production 11 | 12 | Add following script tag to your `index.html` after docsify. Specifying the `@[version]` in the URL ensures that the release of a major update (v3.x) will not break your production site: 13 | 14 | ```html 15 | 16 | 17 | ``` 18 | 19 | ### Development 20 | 21 | If you prefer to load the latest version of the library, you may do so by omitting the `@[version]` from the above URL. 22 | 23 | ```html 24 | 25 | 26 | ``` 27 | 28 | ## Usage 29 | 30 | Create a markdown code block with help of triple backticks at the beginning and end of your code. This block will have a copy button on the top right when hovering over it. 31 | 32 | ## Options 33 | 34 | ### Button text 35 | 36 | Button text can be customized as follows: 37 | 38 | ```javascript 39 | window.$docsify = { 40 | // docsify-copy-code (defaults) 41 | copyCode: { 42 | buttonText: 'Copy to clipboard', 43 | errorText: 'Error', 44 | successText: 'Copied', 45 | }, 46 | }; 47 | ``` 48 | 49 | ### Localization (l10n) 50 | 51 | Button text can also be customized based on the current URL. Object key/value pairs are processed in the order provided. 52 | 53 | ```javascript 54 | window.$docsify = { 55 | copyCode: { 56 | buttonText: { 57 | '/zh-cn/': '点击复制', 58 | '/ru/': 'Скопировать в буфер обмена', 59 | '/de-de/': 'Klicken Sie zum Kopieren', 60 | '/es/': 'Haga clic para copiar', 61 | '/': 'Copy to clipboard', 62 | }, 63 | errorText: { 64 | '/zh-cn/': '错误', 65 | '/ru/': 'ошибка', 66 | '/': 'Error', 67 | }, 68 | successText: { 69 | '/zh-cn/': '复制', 70 | '/ru/': 'Скопировано', 71 | '/de-de/': 'Kopiert', 72 | '/es/': 'Copiado', 73 | '/': 'Copied', 74 | }, 75 | }, 76 | }; 77 | ``` 78 | 79 | **Note:** Docsify's [alias](https://docsify.js.org/#/configuration?id=alias) option makes it easy to manage local content using separate directories. See the [`/demo/`](https://github.com/jperasmus/docsify-copy-code/tree/master/demo) content in this repo for an example. 80 | 81 | ## License 82 | 83 | This project is licensed under the MIT License. See the [LICENSE](https://github.com/jperasmus/docsify-copy-code/blob/master/LICENSE) for details. 84 | -------------------------------------------------------------------------------- /demo/de-de/test.md: -------------------------------------------------------------------------------- 1 | # docsify-copy-code 2 | 3 | :de: Ein Docsify-Plugin, das eine Schaltfläche zum einfachen Kopieren von Codeblöcken in die Zwischenablage hinzufügt. 4 | 5 | ## CSS 6 | 7 | ```css 8 | body: { 9 | background: white; 10 | color: black; 11 | } 12 | ``` 13 | 14 | ## HTML 15 | 16 | ```html 17 |

Foo

18 |

Bar

19 |

Baz

20 | ``` 21 | 22 | ## JavaScript 23 | 24 | ```javascript 25 | const a = 1; 26 | const b = 2; 27 | const c = 3; 28 | ``` 29 | -------------------------------------------------------------------------------- /demo/es/test.md: -------------------------------------------------------------------------------- 1 | # docsify-copy-code 2 | 3 | :es: Un complemento de docsify que agrega un botón para copiar fácilmente bloques de código a tu portapapeles. 4 | 5 | ## CSS 6 | 7 | ```css 8 | body: { 9 | background: white; 10 | color: black; 11 | } 12 | ``` 13 | 14 | ## HTML 15 | 16 | ```html 17 |

Foo

18 |

Bar

19 |

Baz

20 | ``` 21 | 22 | ## JavaScript 23 | 24 | ```javascript 25 | const a = 1; 26 | const b = 2; 27 | const c = 3; 28 | ``` 29 | -------------------------------------------------------------------------------- /demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 | 64 | 65 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /demo/navbar.md: -------------------------------------------------------------------------------- 1 | - Translations 2 | - [:us: English](/) 3 | - [:cn: 中文](/zh-cn/test.md) 4 | - [:de: Deutsch](/de-de/test.md) 5 | - [:es: Spanish](/es/test.md) 6 | - [:ru: Russian](/ru/test.md) 7 | -------------------------------------------------------------------------------- /demo/ru/test.md: -------------------------------------------------------------------------------- 1 | # docsify-copy-code 2 | 3 | :ru: Плагин docsify, который добавляет кнопку для простого копирования блоков кода в буфер обмена. 4 | 5 | ## CSS 6 | 7 | ```css 8 | body: { 9 | background: white; 10 | color: black; 11 | } 12 | ``` 13 | 14 | ## HTML 15 | 16 | ```html 17 |

Foo

18 |

Bar

19 |

Baz

20 | ``` 21 | 22 | ## JavaScript 23 | 24 | ```javascript 25 | const a = 1; 26 | const b = 2; 27 | const c = 3; 28 | ``` 29 | -------------------------------------------------------------------------------- /demo/zh-cn/test.md: -------------------------------------------------------------------------------- 1 | # docsify-copy-code 2 | 3 | :cn: 一个docsify插件,它添加了一个按钮,可以轻松地将代码块复制到剪贴板。 4 | 5 | ## CSS 6 | 7 | ```css 8 | body: { 9 | background: white; 10 | color: black; 11 | } 12 | ``` 13 | 14 | ## HTML 15 | 16 | ```html 17 |

Foo

18 |

Bar

19 |

Baz

20 | ``` 21 | 22 | ## JavaScript 23 | 24 | ```javascript 25 | const a = 1; 26 | const b = 2; 27 | const c = 3; 28 | ``` 29 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | // Fix for v1.x installations 2 | (function () { 3 | // Deprecation warning for v1.x: init() 4 | window.DocsifyCopyCodePlugin = { 5 | init: function () { 6 | return function (hook, vm) { 7 | hook.ready(function () { 8 | // eslint-disable-next-line no-console 9 | console.warn( 10 | '[Update] docsify-copy-code has been updated. Please see new installation instructions at https://github.com/jperasmus/docsify-copy-code.' 11 | ); 12 | }); 13 | }; 14 | }, 15 | }; 16 | })(); 17 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "docsify-copy-code", 3 | "version": "3.0.0", 4 | "description": "A docsify plugin that copies a markdown code block to your clipboard", 5 | "author": "JP Erasmus ", 6 | "contributors": [ 7 | "John Hildenbiddle " 8 | ], 9 | "license": "MIT", 10 | "homepage": "https://github.com/jperasmus/docsify-copy-code", 11 | "repository": "git@github.com:jperasmus/docsify-copy-code.git", 12 | "files": [ 13 | "dist" 14 | ], 15 | "main": "dist/docsify-copy-code.js", 16 | "unpkg": "dist/docsify-copy-code.min.js", 17 | "scripts": { 18 | "build": "rollup -c", 19 | "clean": "rimraf dist/*", 20 | "dev": "node server.js & npm run start", 21 | "start": "npm run build -- -w" 22 | }, 23 | "devDependencies": { 24 | "@babel/core": "^7.22.10", 25 | "@babel/preset-env": "^7.22.10", 26 | "@rollup/plugin-babel": "^6.0.3", 27 | "@rollup/plugin-commonjs": "^25.0.4", 28 | "@rollup/plugin-eslint": "^9.0.4", 29 | "@rollup/plugin-json": "^6.0.0", 30 | "@rollup/plugin-node-resolve": "^15.2.0", 31 | "@rollup/plugin-terser": "^0.4.3", 32 | "autoprefixer": "^10.4.14", 33 | "browser-sync": "^2.29.3", 34 | "compression": "^1.7.4", 35 | "eslint": "^8.47.0", 36 | "eslint-config-prettier": "^9.0.0", 37 | "lodash.merge": "^4.6.2", 38 | "prettier": "3.0.2", 39 | "rimraf": "^5.0.1", 40 | "rollup": "^3.28.0", 41 | "rollup-plugin-postcss": "^4.0.2" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /rollup.config.mjs: -------------------------------------------------------------------------------- 1 | import { createRequire } from 'node:module'; 2 | import { fileURLToPath } from 'node:url'; 3 | import autoprefixer from 'autoprefixer'; 4 | import merge from 'lodash.merge'; 5 | import babel from '@rollup/plugin-babel'; 6 | import commonjs from '@rollup/plugin-commonjs'; 7 | import eslint from '@rollup/plugin-eslint'; 8 | import json from '@rollup/plugin-json'; 9 | import resolve from '@rollup/plugin-node-resolve'; 10 | import terser from '@rollup/plugin-terser'; 11 | import postcss from 'rollup-plugin-postcss'; 12 | 13 | const require = createRequire(import.meta.url); 14 | const pkg = require('./package.json'); 15 | 16 | const entryFile = fileURLToPath(new URL('src/index.js', import.meta.url)); 17 | const outputFile = fileURLToPath( 18 | new URL(`dist/${pkg.name}.js`, import.meta.url) 19 | ); 20 | 21 | const currentYear = new Date().getFullYear(); 22 | const releaseYear = 2017; 23 | const bannerData = [ 24 | `${pkg.name}`, 25 | `v${pkg.version}`, 26 | `${pkg.homepage}`, 27 | `(c) ${releaseYear}${currentYear === releaseYear ? '' : '-' + currentYear} ${ 28 | pkg.author 29 | }`, 30 | `${pkg.license} license`, 31 | ]; 32 | 33 | // Plugins 34 | const pluginSettings = { 35 | eslint: { 36 | exclude: ['node_modules/**', './package.json', '**.css'], 37 | throwOnWarning: false, 38 | throwOnError: true, 39 | }, 40 | babel: { 41 | babelHelpers: 'bundled', 42 | exclude: ['node_modules/**'], 43 | presets: [ 44 | [ 45 | '@babel/env', 46 | { 47 | modules: false, 48 | targets: 'last 2 versions, ie > 10', 49 | }, 50 | ], 51 | ], 52 | }, 53 | commonjs: { 54 | exclude: ['**.css'], 55 | }, 56 | json: {}, 57 | postcss: { 58 | inject: true, 59 | minimize: true, 60 | plugins: [autoprefixer()], 61 | }, 62 | resolve: { 63 | moduleDirectories: ['node_modules'], 64 | }, 65 | terser: { 66 | beautify: { 67 | compress: false, 68 | mangle: false, 69 | output: { 70 | beautify: true, 71 | comments: 'some', 72 | }, 73 | }, 74 | minify: { 75 | compress: true, 76 | mangle: true, 77 | output: { 78 | comments: new RegExp(pkg.name), 79 | }, 80 | }, 81 | }, 82 | }; 83 | 84 | /** 85 | * @type {import('rollup').RollupOptions} 86 | */ 87 | const config = { 88 | strictDeprecations: true, 89 | input: entryFile, 90 | output: { 91 | file: outputFile, 92 | banner: `/*!\n * ${bannerData.join('\n * ')}\n */`, 93 | sourcemap: true, 94 | }, 95 | plugins: [ 96 | postcss(pluginSettings.postcss), 97 | resolve(pluginSettings.resolve), 98 | commonjs(pluginSettings.commonjs), 99 | json(pluginSettings.json), 100 | eslint(pluginSettings.eslint), 101 | babel(pluginSettings.babel), 102 | ], 103 | watch: { 104 | clearScreen: false, 105 | }, 106 | }; 107 | 108 | // IIFE 109 | const iife = merge({}, config, { 110 | output: { 111 | format: 'iife', 112 | }, 113 | plugins: config.plugins.concat([terser(pluginSettings.terser.beautify)]), 114 | }); 115 | 116 | // IIFE (Minified) 117 | const iifeMinified = merge({}, config, { 118 | output: { 119 | file: iife.output.file.replace(/\.js$/, '.min.js'), 120 | format: iife.output.format, 121 | }, 122 | plugins: config.plugins.concat([terser(pluginSettings.terser.minify)]), 123 | }); 124 | 125 | export default [iife, iifeMinified]; 126 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | // Dependencies 2 | // ============================================================================= 3 | const browserSync = require('browser-sync').create(); 4 | const compression = require('compression'); 5 | 6 | browserSync.init({ 7 | files: ['./demo/', './dist/', './README.md'], 8 | ghostMode: { 9 | clicks: false, 10 | forms: false, 11 | scroll: false, 12 | }, 13 | open: false, 14 | notify: false, 15 | server: { 16 | baseDir: ['./demo/'], 17 | middleware: [compression()], 18 | routes: { 19 | '/dist/': './dist', 20 | '/README.md': './README.md', 21 | }, 22 | }, 23 | }); 24 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import './styles.css'; 2 | 3 | function docsifyCopyCode(hook, vm) { 4 | const i18n = { 5 | buttonText: 'Copy to clipboard', 6 | errorText: 'Error', 7 | successText: 'Copied', 8 | }; 9 | 10 | hook.doneEach(function () { 11 | const targetElms = Array.from(document.querySelectorAll('pre[data-lang]')); 12 | 13 | // Update i18n strings based on options and location.href 14 | if (vm.config.copyCode) { 15 | Object.keys(i18n).forEach((key) => { 16 | const textValue = vm.config.copyCode[key]; 17 | 18 | if (typeof textValue === 'string') { 19 | i18n[key] = textValue; 20 | } else if (typeof textValue === 'object') { 21 | Object.keys(textValue).some((match) => { 22 | const isMatch = location.href.indexOf(match) > -1; 23 | 24 | i18n[key] = isMatch ? textValue[match] : i18n[key]; 25 | 26 | return isMatch; 27 | }); 28 | } 29 | }); 30 | } 31 | 32 | const template = [ 33 | '', 39 | ].join(''); 40 | 41 | targetElms.forEach((elm) => { 42 | elm.insertAdjacentHTML('beforeend', template); 43 | }); 44 | }); 45 | 46 | hook.mounted(function () { 47 | const listenerHost = document.querySelector('.content'); 48 | 49 | if (listenerHost) { 50 | listenerHost.addEventListener('click', function (evt) { 51 | const isCopyCodeButton = evt.target.classList.contains( 52 | 'docsify-copy-code-button' 53 | ); 54 | 55 | if (isCopyCodeButton) { 56 | const buttonElm = 57 | evt.target.tagName === 'BUTTON' 58 | ? evt.target 59 | : evt.target.parentNode; 60 | const range = document.createRange(); 61 | const preElm = buttonElm.parentNode; 62 | const codeElm = preElm.querySelector('code'); 63 | const liveRegionElm = buttonElm.querySelector('[aria-live]'); 64 | 65 | let selection = window.getSelection(); 66 | 67 | range.selectNode(codeElm); 68 | 69 | if (selection) { 70 | selection.removeAllRanges(); 71 | selection.addRange(range); 72 | } 73 | 74 | try { 75 | // Copy selected text 76 | const successful = document.execCommand('copy'); 77 | 78 | if (successful) { 79 | buttonElm.classList.add('success'); 80 | liveRegionElm.innerText = i18n.successText; 81 | 82 | setTimeout(function () { 83 | buttonElm.classList.remove('success'); 84 | liveRegionElm.innerText = ''; 85 | }, 1000); 86 | } 87 | } catch (err) { 88 | // eslint-disable-next-line no-console 89 | console.error(`docsify-copy-code: ${err}`); 90 | 91 | buttonElm.classList.add('error'); 92 | liveRegionElm.innerText = i18n.errorText; 93 | 94 | setTimeout(function () { 95 | buttonElm.classList.remove('error'); 96 | liveRegionElm.innerText = ''; 97 | }, 1000); 98 | } 99 | 100 | selection = window.getSelection(); 101 | 102 | if (selection) { 103 | if (typeof selection.removeRange === 'function') { 104 | selection.removeRange(range); 105 | } else if (typeof selection.removeAllRanges === 'function') { 106 | selection.removeAllRanges(); 107 | } 108 | } 109 | } 110 | }); 111 | } 112 | }); 113 | } 114 | 115 | // Deprecation warning for v1.x: stylesheet 116 | if (document.querySelector('link[href*="docsify-copy-code"]')) { 117 | // eslint-disable-next-line no-console 118 | console.warn( 119 | '[Deprecation] Link to external docsify-copy-code stylesheet is no longer necessary.' 120 | ); 121 | } 122 | 123 | // Deprecation warning for v1.x: init() 124 | window.DocsifyCopyCodePlugin = { 125 | init: function () { 126 | return function (hook, vm) { 127 | hook.ready(function () { 128 | // eslint-disable-next-line no-console 129 | console.warn( 130 | '[Deprecation] Manually initializing docsify-copy-code using window.DocsifyCopyCodePlugin.init() is no longer necessary.' 131 | ); 132 | }); 133 | }; 134 | }, 135 | }; 136 | 137 | window.$docsify = window.$docsify || {}; 138 | window.$docsify.plugins = [docsifyCopyCode].concat( 139 | window.$docsify.plugins || [] 140 | ); 141 | -------------------------------------------------------------------------------- /src/styles.css: -------------------------------------------------------------------------------- 1 | .docsify-copy-code-button, 2 | .docsify-copy-code-button > span { 3 | cursor: pointer; 4 | transition: all 0.25s ease; 5 | } 6 | 7 | .docsify-copy-code-button { 8 | position: absolute; 9 | z-index: 1; 10 | top: 0; 11 | right: 0; 12 | overflow: visible; 13 | padding: 0.65em 0.8em; 14 | border: 0; 15 | border-radius: 0; 16 | outline: 0; 17 | font-size: 1em; 18 | background: #808080; 19 | background: var(--theme-color, #808080); 20 | color: #fff; 21 | opacity: 0; 22 | } 23 | 24 | .docsify-copy-code-button > span { 25 | border-radius: 3px; 26 | background: inherit; 27 | pointer-events: none; 28 | } 29 | 30 | .docsify-copy-code-button > .error, 31 | .docsify-copy-code-button > .success { 32 | position: absolute; 33 | z-index: -100; 34 | top: 50%; 35 | right: 0; 36 | padding: 0.5em 0.65em; 37 | font-size: 0.825em; 38 | opacity: 0; 39 | transform: translate(0, -50%); 40 | } 41 | 42 | .docsify-copy-code-button.error > .error, 43 | .docsify-copy-code-button.success > .success { 44 | right: 100%; 45 | opacity: 1; 46 | transform: translate(-25%, -50%); 47 | } 48 | 49 | .docsify-copy-code-button:focus, 50 | pre:hover .docsify-copy-code-button { 51 | opacity: 1; 52 | } 53 | 54 | .docsify-copy-code-button > [aria-live] { 55 | position: absolute; 56 | left: -10000px; 57 | top: auto; 58 | width: 1px; 59 | height: 1px; 60 | overflow: hidden; 61 | } 62 | --------------------------------------------------------------------------------