├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .gitattributes ├── .github ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── FUNDING.yml ├── ISSUE_TEMPLATE │ └── BUG_REPORT.yaml └── workflows │ └── main.yml ├── .gitignore ├── .husky └── pre-commit ├── .npmrc ├── .nvmrc ├── .prettierrc.js ├── .release-it.json ├── .vscode └── extensions.json ├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── dist ├── plugin │ ├── VCodeBlock.vue.d.ts │ ├── components │ │ └── StatusIcons.vue.d.ts │ ├── composables │ │ ├── classes.d.ts │ │ ├── helpers.d.ts │ │ └── styles.d.ts │ ├── index.d.ts │ ├── themes │ │ └── index.d.ts │ ├── types │ │ └── index.d.ts │ └── utils │ │ ├── globals.d.ts │ │ └── props.d.ts ├── scss │ ├── README.md │ ├── cssVariables.css │ ├── main.scss │ ├── themeStyles.scss │ └── utilities.scss ├── vue-code-block.cjs.js └── vue-code-block.es.js ├── index.html ├── package.json ├── playground └── index.html ├── pnpm-lock.yaml ├── public ├── assets │ ├── brown-papersq.png │ ├── pojoaque.jpg │ └── vue.svg ├── brown-papersq.png ├── pojoaque.jpg └── vue.svg ├── src ├── App.vue ├── assets │ ├── brown-papersq.png │ ├── pojoaque.jpg │ └── vue.svg ├── documentation │ ├── DocsPage.vue │ ├── components │ │ ├── MenuComponent.vue │ │ ├── PropsTable.vue │ │ ├── ThemeSelectComponent.vue │ │ ├── examples │ │ │ ├── AdditionalLangExamples.vue │ │ │ ├── BrowserWindowExamples.vue │ │ │ ├── ButtonExamples.vue │ │ │ ├── LangExamples.vue │ │ │ ├── OtherExamples.vue │ │ │ ├── PluginExamples.vue │ │ │ ├── TabExamples.vue │ │ │ └── index.ts │ │ └── index.js │ ├── layout │ │ └── AppBar.vue │ └── sections │ │ ├── DependenciesSection.vue │ │ ├── DescriptionSection.vue │ │ ├── EventsSection.vue │ │ ├── ExampleSection.vue │ │ ├── LegalSection.vue │ │ ├── LicenseSection.vue │ │ ├── PlaygroundSection.vue │ │ ├── PropsSection.vue │ │ ├── SlotsSection.vue │ │ ├── UsageSection.vue │ │ └── index.js ├── libraries │ └── fontawesome.ts ├── main.ts ├── playground │ ├── .gitignore │ └── configs │ │ ├── PlaygroundApp.vue │ │ ├── build.sh │ │ ├── database.json │ │ ├── playground.ts │ │ └── templates │ │ ├── PlaygroundPage.ts.vue │ │ └── PlaygroundPage.vue ├── plugin │ ├── VCodeBlock.vue │ ├── __tests__ │ │ ├── VCodeBlock.test.ts │ │ ├── __snapshots__ │ │ │ └── VCodeBlock.test.ts.snap │ │ └── index.test.ts │ ├── components │ │ ├── StatusIcons.vue │ │ └── __tests__ │ │ │ ├── StatusIcons.test.ts │ │ │ └── __snapshots__ │ │ │ └── StatusIcons.test.ts.snap │ ├── composables │ │ ├── __tests__ │ │ │ ├── __snapshots__ │ │ │ │ ├── classes.test.ts.snap │ │ │ │ └── styles.test.ts.snap │ │ │ ├── classes.test.ts │ │ │ ├── helpers.test.ts │ │ │ └── styles.test.ts │ │ ├── classes.ts │ │ ├── helpers.ts │ │ └── styles.ts │ ├── index.ts │ ├── styles │ │ ├── README.md │ │ ├── cssVariables.css │ │ ├── main.scss │ │ ├── themeStyles.scss │ │ └── utilities.scss │ ├── themes │ │ ├── css │ │ │ ├── highlight-template.css │ │ │ ├── min │ │ │ │ ├── highlight-template.css │ │ │ │ ├── neon-bunny-carrot-highlight.css │ │ │ │ ├── neon-bunny-carrot-prism.css │ │ │ │ ├── neon-bunny-highlight.css │ │ │ │ └── neon-bunny-prism.css │ │ │ ├── neon-bunny-carrot-highlight.css │ │ │ ├── neon-bunny-carrot-prism.css │ │ │ ├── neon-bunny-highlight.css │ │ │ └── neon-bunny-prism.css │ │ ├── index.ts │ │ └── scss │ │ │ ├── highlight-template.scss │ │ │ ├── neon-bunny-carrot-highlight.scss │ │ │ ├── neon-bunny-carrot-prism.scss │ │ │ ├── neon-bunny-highlight.scss │ │ │ └── neon-bunny-prism.scss │ ├── types │ │ ├── auto-imports.d.ts │ │ ├── index.ts │ │ ├── vite-env.d.ts │ │ └── vue-shim.d.ts │ └── utils │ │ ├── globals.ts │ │ └── props.ts ├── plugins │ ├── index.ts │ ├── theme.ts │ ├── vuetify.ts │ └── webfontloader.ts ├── stores │ ├── index.ts │ ├── menu.ts │ └── props.ts └── vite-env.d.ts ├── stylelint.config.js ├── tsconfig.dist.json ├── tsconfig.json ├── tsconfig.node.json ├── vite.build.config.mts ├── vite.config.mts └── vitest.config.mts /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | indent_size = 4 7 | indent_style = tab 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | 14 | [*.{yml,yaml}] 15 | indent_size = 2 16 | 17 | [*.{js,ts,mts,vue}] 18 | indent_size = 2 19 | indent_style = tab 20 | 21 | [*.{scss,css}] 22 | indent_size = 2 23 | indent_style = space 24 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | # don't ever lint node_modules 2 | node_modules 3 | # don't lint build output (make sure it's set to your correct build folder name) 4 | dist 5 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | browser: true, 4 | node: true, 5 | }, 6 | extends: [ 7 | 'eslint:recommended', 8 | 'plugin:vue/essential', 9 | "@vue/typescript/recommended", 10 | "prettier", 11 | ], 12 | ignorePatterns: [ 13 | '.eslintrc.js', 14 | 'stylelint.config.js', 15 | 'vite.build.config.mts', 16 | 'vite.config.mts', 17 | '*.bk.vue', 18 | ], 19 | overrides: [ 20 | { 21 | files: [ 22 | '**/*.spec.{j,t}s?(x)', 23 | ], 24 | env: { 25 | jest: true, 26 | }, 27 | }, 28 | ], 29 | globals: { 30 | Entry: true, 31 | loadLanguages: true, 32 | Prism: true, 33 | }, 34 | parserOptions: { 35 | parser: '@typescript-eslint/parser', 36 | }, 37 | plugins: [ 38 | '@typescript-eslint', 39 | 'import', 40 | 'prettier', 41 | 'vue', 42 | ], 43 | root: true, 44 | settings: { 45 | 'import/resolver': { 46 | 'babel-module': {}, 47 | }, 48 | }, 49 | rules: { 50 | "@typescript-eslint/ban-types": [ 51 | "error", 52 | { 53 | "extendDefaults": true, 54 | "types": { 55 | "{}": false 56 | } 57 | } 58 | ], 59 | '@typescript-eslint/no-empty-function': 0, 60 | 'brace-style': ['error', 'stroustrup'], 61 | 'default-case': [ 62 | 'error', { 63 | commentPattern: '^skip\\sdefault', 64 | }, 65 | ], 66 | 'func-names': ['error', 'never'], 67 | 'function-paren-newline': 0, 68 | 'import/no-self-import': 0, 69 | 'import/no-extraneous-dependencies': 0, 70 | 'implicit-arrow-linebreak': ['warn', 'beside'], 71 | indent: [2, 'tab', { SwitchCase: 1 }], 72 | 'no-tabs': [0, { allowIndentationTabs: true }], 73 | 'linebreak-style': 0, 74 | 'max-len': 0, 75 | 'no-else-return': ['error', { allowElseIf: true }], 76 | 'no-console': ['warn', { allow: ['warn', 'error', 'info'] }], 77 | 'no-const-assign': 'error', 78 | 'no-debugger': 0, 79 | 'no-new': 0, 80 | 'no-unused-vars': 1, 81 | 'no-use-before-define': 0, 82 | 'no-useless-escape': 0, 83 | 'no-param-reassign': [ 84 | 'error', { 85 | props: true, 86 | ignorePropertyModificationsFor: ['field', 'model', 'el', 'item', 'state', 'Vue', 'vue'], 87 | }, 88 | ], 89 | 'no-underscore-dangle': [ 90 | 'error', { 91 | allow: ['_data'], 92 | allowAfterThis: true, 93 | }, 94 | ], 95 | 'no-plusplus': [ 96 | 'error', { allowForLoopAfterthoughts: true }, 97 | ], 98 | 'object-curly-newline': ['error', { 99 | ObjectPattern: { multiline: false }, 100 | }], 101 | 'operator-linebreak': ['error', 'after'], 102 | 'prefer-destructuring': [ 103 | 'error', { 104 | array: false, 105 | object: false, 106 | }, 107 | { 108 | enforceForRenamedProperties: false, 109 | }, 110 | ], 111 | 'space-before-function-paren': ['error', { 112 | anonymous: 'never', 113 | named: 'never', 114 | asyncArrow: 'never', 115 | }], 116 | 'vue/attributes-order': ['error', { 117 | 'alphabetical': true, 118 | 'order': [ 119 | 'DEFINITION', 120 | 'LIST_RENDERING', 121 | 'CONDITIONALS', 122 | 'RENDER_MODIFIERS', 123 | 'GLOBAL', 124 | ['UNIQUE', 'SLOT'], 125 | 'TWO_WAY_BINDING', 126 | 'OTHER_DIRECTIVES', 127 | 'OTHER_ATTR', 128 | 'EVENTS', 129 | 'CONTENT', 130 | ], 131 | }], 132 | 'vue/html-closing-bracket-newline': 0, 133 | 'vue/html-indent': 0, 134 | 'vue/html-self-closing': 0, 135 | 'vue/max-attributes-per-line': 0, 136 | 'vue/no-multiple-template-root': 0, 137 | 'vue/no-v-for-template-key': 0, 138 | 'vue/no-template-shadow': 0, 139 | 'vue/no-v-html': 0, 140 | 'vue/singleline-html-element-content-newline': 0, 141 | 'vue/valid-template-root': 0, 142 | }, 143 | }; 144 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | -------------------------------------------------------------------------------- /.github/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | webdevnerdstuff@gmail.com. 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | https://www.contributor-covenant.org/faq. Translations are available at 128 | https://www.contributor-covenant.org/translations. 129 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to this repository 2 | 3 | ## Getting started 4 | 5 | Before you begin: 6 | - Have you read the [code of conduct](CODE_OF_CONDUCT.md)? 7 | - Check out the [existing issues](https://github.com/webdevnerdstuff/vue-code-block/issues). 8 | 9 | ### Don't see your issue? Open one 10 | 11 | If you spot something new, open an issue using a [template](https://github.com/webdevnerdstuff/vue-code-block/issues/new/choose). We'll use the issue to have a conversation about the problem you want to fix. 12 | 13 | ### Ready to make a change? Fork the repo 14 | 15 | Fork using the command line: 16 | 17 | - [Fork the repo](https://docs.github.com/en/github/getting-started-with-github/fork-a-repo#fork-an-example-repository) so that you can make your changes without affecting the original project until you're ready to merge them. 18 | 19 | ### Make your update: 20 | 21 | Make your changes to the file(s) you'd like to update. 22 | Update the CHANGELOG.md with the updates you made, please include the date and Github username. 23 | 24 | ### Open a pull request 25 | When you're done making changes and you'd like to propose them for review, open your PR (pull request). 26 | 27 | ### Submit your PR & get it reviewed 28 | - Once you submit your PR, others from the Vue3 CodeBlock community will review it with you. 29 | - After that, we may have questions, check back on your PR to keep up with the conversation. 30 | 31 | ### Your PR is merged! 32 | Congratulations! The whole GitHub community thanks you. :sparkles: 33 | 34 | Once your PR is merged, you will be proudly listed as a contributor in the [contributor chart](https://github.com/webdevnerdstuff/vue-code-block/graphs/contributors). 35 | 36 | ### Keep contributing as you use Vue3 CodeBlock 37 | 38 | Now that you're a part of the Vue3 CodeBlock community, you can keep participating in many ways. 39 | 40 | ## Types of contributions 41 | You can contribute to the Vue3 CodeBlock content and site in several ways. This repo is a place to discuss and collaborate on Vue3 CodeBlock! Our small, but mighty team is maintaining this repo, to preserve our bandwidth, off topic conversations will be closed. 42 | 43 | ### :mega: Discussions 44 | Discussions are where we have conversations. 45 | 46 | If you'd like help troubleshooting a Vue3 CodeBlock PR you're working on, have a great new idea, or want to share something amazing you've learned, join us in [discussions](https://github.com/webdevnerdstuff/vue-code-block/discussions). 47 | 48 | ### :beetle: Issues 49 | [Issues](https://docs.github.com/en/github/managing-your-work-on-github/about-issues) are used to track tasks that contributors can help with. 50 | 51 | If you've found something in the content or the website that should be updated, search open issues to see if someone else has reported the same thing. If it's something new, open an issue using a [template](https://github.com/webdevnerdstuff/vue-code-block/issues/new/choose). We'll use the issue to have a conversation about the problem you want to fix. 52 | 53 | ### :hammer_and_wrench: Pull requests 54 | A [pull request](https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/about-pull-requests) is a way to suggest changes in our repository. 55 | 56 | To learn more about opening a pull request in this repo, see [Opening a pull request](#opening-a-pull-request) below. 57 | 58 | ### :question: Support 59 | We are a small team working hard to keep up with the documentation demands of a continuously changing product. Unfortunately, we just can't help with support questions in this repository. If you are experiencing a problem with GitHub, unrelated to our documentation, please [contact GitHub Support directly](https://support.github.com/contact). Any issues, discussions, or pull requests opened here requesting support will be given information about how to contact GitHub Support, then closed and locked. 60 | 61 | If you're having trouble with your GitHub account, contact [Support](https://support.github.com/contact). 62 | 63 | ## Starting with an issue 64 | You can browse existing issues to find something that needs help! 65 | 66 | ### Labels 67 | Labels can help you find an issue you'd like to help with. 68 | 69 | - The [`help wanted` label](https://github.com/webdevnerdstuff/vue-code-block/issues?q=is%3Aopen+is%3Aissue+label%3Aenhancement) is for problems or updates that anyone in the community can start working on. 70 | - The [`documentation` label](https://github.com/webdevnerdstuff/vue-code-block/issues?q=is%3Aopen+is%3Aissue+label%3Adocumentation) is for problems or updates relating to the README.MD documentation. 71 | - The [`bug` label](https://github.com/webdevnerdstuff/vue-code-block/issues?q=is%3Aopen+is%3Aissue+label%3Abug) is for problems with the code and bugs. 72 | - The [`enhancement` label](https://github.com/webdevnerdstuff/vue-code-block/issues?q=is%3Aopen+is%3Aissue+label%3Aenhancement) is for 73 | suggestions to improve the code or adding of additional features. 74 | 75 | ## Opening a pull request 76 | You can use the GitHub user interface for some small changes, like fixing a typo or updating a readme. You can also fork the repo and then clone it locally, to view changes and run your tests on your machine. 77 | 78 | ## Working in the Vue3 CodeBlock repository 79 | Here's some information that might be helpful while working on a Vue3 CodeBlock PR: 80 | 81 | 82 | 83 | ## Reviewing 84 | We (usually the Vue3 CodeBlock team) review every single PR. The purpose of reviews is to create the best content we can for people who use GitHub. 85 | 86 | - Reviews are always respectful, acknowledging that everyone did the best possible job with the knowledge they had at the time. 87 | - Reviews discuss content, not the person who created it. 88 | - Reviews are constructive and start conversation around feedback. 89 | 90 | ### Self review 91 | You should always review your own PR first. 92 | 93 | 95 | 96 | ### Suggested changes 97 | We may ask for changes to be made before a PR can be merged, either using [suggested changes](https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/incorporating-feedback-in-your-pull-request) or pull request comments. You can apply suggested changes directly through the UI. You can make any other changes in your fork, then commit them to your branch. 98 | 99 | As you update your PR and apply changes, mark each conversation as [resolved](https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/commenting-on-a-pull-request#resolving-conversations). 100 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | buy_me_a_coffee: webdevnerdstuff 2 | custom: ["paypal.me/webdevnerdstuff"] 3 | patreon: webdevnerdstuff 4 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/BUG_REPORT.yaml: -------------------------------------------------------------------------------- 1 | name: Bug Report 2 | description: File a bug report 3 | title: "[Bug Report]: " 4 | labels: ["bug", "triage"] 5 | assignees: 6 | - webdevnerdstuff 7 | body: 8 | - type: markdown 9 | attributes: 10 | value: | 11 | Thanks for taking the time to fill out this bug report! 12 | - type: input 13 | id: vue-codeblock-version 14 | attributes: 15 | label: Vue Code Block Version 16 | validations: 17 | required: true 18 | - type: input 19 | id: vue-version 20 | attributes: 21 | label: Vue Version 22 | validations: 23 | required: true 24 | - type: textarea 25 | id: bug-description 26 | attributes: 27 | label: Bug description 28 | description: What happened? 29 | validations: 30 | required: true 31 | - type: textarea 32 | id: steps 33 | attributes: 34 | label: Steps to reproduce 35 | description: Which steps do we need to take to reproduce this error? 36 | placeholder: "Steps to reproduce the behavior:\n1. Go to '...'\n2. Click on '....'\n3. Scroll down to '....'" 37 | validations: 38 | required: true 39 | - type: textarea 40 | id: logs 41 | attributes: 42 | label: Relevant log output 43 | description: If applicable, please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks. 44 | render: shell 45 | - type: textarea 46 | id: additional-context 47 | attributes: 48 | label: Additional context 49 | description: Add any other context about the problem here. 50 | - type: checkboxes 51 | id: terms 52 | attributes: 53 | label: Code of Conduct 54 | description: By submitting this issue, you agree to follow our [Code of Conduct](https://github.com/webdevnerdstuff/vue-code-block/blob/main/.github/CODE_OF_CONDUCT.md) 55 | options: 56 | - label: I agree to follow this project's Code of Conduct 57 | required: true 58 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | # Simple workflow for deploying static content to GitHub Pages 2 | name: Deploy to GitHub Pages 3 | 4 | on: 5 | push: 6 | branches: [ main ] 7 | 8 | permissions: 9 | contents: write 10 | pages: write 11 | id-token: write 12 | pull-requests: write 13 | repository-projects: write 14 | 15 | jobs: 16 | build: 17 | runs-on: ubuntu-latest 18 | 19 | strategy: 20 | matrix: 21 | node-version: [20] 22 | 23 | steps: 24 | - uses: actions/checkout@v3 25 | 26 | - name: Use Node.js ${{ matrix.node-version }} 27 | uses: actions/setup-node@v3 28 | with: 29 | node-version: ${{ matrix.node-version }} 30 | 31 | - name: Build 32 | run: | 33 | git config --global user.email "webdevnerdstuff@gmail.com" 34 | git config --global user.name "webdevnerdstuff" 35 | mkdir docs 36 | npm install --g gh-pages 37 | npm i 38 | npm run build:docs 39 | git remote set-url origin https://git:${GITHUB_TOKEN}@github.com/${GITHUB_REPOSITORY}.git 40 | npm run deploy -u "github-actions-bot " 41 | env: 42 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 43 | -------------------------------------------------------------------------------- /.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 | docs 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | 26 | .history 27 | 28 | .stylelintcache 29 | src/plugin/themes/scss/highlight-template.scss 30 | dist/themes/scss/highlight-template.scss 31 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | npx lint-staged && npm run test:commit 5 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | node-version=20.10.0 2 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 20.10.0 2 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | semi: true, 3 | singleQuote: true, 4 | trailingComma: 'all', 5 | }; 6 | -------------------------------------------------------------------------------- /.release-it.json: -------------------------------------------------------------------------------- 1 | { 2 | "git": { 3 | "commitMessage": "chore: release ${version}", 4 | "tagName": "v${version}" 5 | }, 6 | "npm": { 7 | "publish": true 8 | }, 9 | "github": { 10 | "release": true, 11 | "releaseName": "v${version}" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["Vue.volar", "Vue.vscode-typescript-vue-plugin"] 3 | } 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | All notable changes to the "vue-code-block" plugin will be documented in this file. 3 | 4 | ## v2.3.3 5 | 03-13-2024 6 | [main] (@kymtwyf) 7 | * Fix cssPath prop not being reactive. Fixes [#49](https://github.com/webdevnerdstuff/vue-code-block/issues/49) 8 | 9 | [main] (@webdevnerdstuff) 10 | * Update packages. 11 | 12 | ## v2.3.2 13 | 03-13-2024 14 | [main] (@webdevnerdstuff) 15 | * Change component to use `defineAsyncComponent` 16 | 17 | ## v2.3.1 18 | 12-02-2023 19 | [main] (@webdevnerdstuff) 20 | * Add default export 21 | 22 | ## v2.3.0 23 | 12-02-2023 24 | [main] (@webdevnerdstuff) 25 | * Improve/Fix TypeScript support 26 | 27 | ## v2.2.15 28 | 11-13-2023 29 | [main] (@webdevnerdstuff) 30 | * Add binding props to slots 31 | * Update docs 32 | 33 | ## v2.2.13 34 | 11-09-2023 35 | [main] (@webdevnerdstuff) 36 | * Add `cssPath` prop to component to allow for custom css file path to be used. 37 | * Update packages. 38 | 39 | ## v2.2.11 40 | 06-11-2023 41 | [main] (@webdevnerdstuff) 42 | * Changing Highlight.js to include all languages by default. 43 | * Unfortunately, adding additional languages was not working as expected, so this is the best solution I can think of for now. 44 | * Updated typings for Props as well as composables. 45 | * Changing compiling to use a Vite config instead of rollup so d.ts file aliases resolve correctly. 46 | 47 | ## v2.1.1 48 | 04-16-2023 49 | [main] (@webdevnerdstuff) 50 | * Changing importing of css/scss/sass to use @use instead of @import. 51 | 52 | ## v2.1.0 53 | 04-16-2023 54 | [main] (@webdevnerdstuff) 55 | * Changing component to use the min version of the default themes. 56 | * Reorganizing default themes to new location. 57 | * This is a breaking change if you are importing the default themes from the `node_modules` folder. You will need to update your import paths to the new location. 58 | * If you are using the default themes without using `import` then you will not be affected by this change. 59 | * Fixing HighlightJS default theme style causing icon to appear over a scrollbar when present. 60 | * Adding sass scripts to package.json to compile scss files to css. 61 | 62 | ## v2.0.1 - v2.0.6 63 | 04-15-2023 64 | [main] (@webdevnerdstuff) 65 | * Minor changes to fix issues after testing npm packaging. 66 | 67 | ## v2.0.0 68 | 04-15-2023 69 | [main] (@webdevnerdstuff) 70 | * Major release update. Added in support to use either PrismJS or HighlightJS. 71 | 72 | ## v1.0.15 73 | 04-02-2023 74 | [main] (@webdevnerdstuff) 75 | * Minor changes to fix issues after testing npm packaging. 76 | 77 | ## v1.0.0 78 | 03-16-2023 79 | [main] (@webdevnerdstuff) 80 | * Initial release 81 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 WebDevNerdStuff 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 |

2 | Vue 3 |

4 | 5 |

6 |

Vue 3 CodeBlock

7 |

8 | 9 |

10 | 11 | NPM Package 12 | 13 |   14 | 15 | @WebDevNerdStuff 16 | 17 |

18 | 19 | 20 | ## Description 21 | 22 | The Vue 3 CodeBlock component leverages the power of [PrismJS](https://prismjs.com/) or [Highlight.js](https://highlightjs.org/) to provide syntax highlighting for code blocks within your application. The component takes a prop, which is the code to be highlighted, and uses PrismJS or Highlight.js to render the code with syntax highlighting. The component supports a variety of programming languages and can be customized with different themes to match your application's design. With this component, your users can display their code snippets with ease and clarity, making it easier to share and collaborate on code within your application. 23 | 24 | 25 | ## Installation 26 | 27 | Using [pnpm](https://pnpm.io/): 28 | ``` 29 | pnpm add @wdns/vue-code-block 30 | ``` 31 | 32 | Using npm: 33 | ``` 34 | npm i @wdns/vue-code-block 35 | ``` 36 | 37 | ## Documentation 38 | 39 | [Documentation & Demo](https://webdevnerdstuff.github.io/vue-code-block/) 40 | 41 | ## Dependencies 42 | 43 | [PrismJS](https://prismjs.com/) 44 | [Highlight.js](https://highlightjs.org/) 45 | [Vue 3](https://vuejs.org/) 46 | [UAParser.js](https://www.npmjs.com/package/ua-parser-js) 47 | 48 | 49 | ## Change Log 50 | 51 | [CHANGELOG](https://github.com/webdevnerdstuff/vue-code-block/blob/main/CHANGELOG.md) 52 | 53 | 54 | ## License 55 | 56 | Copyright (c) 2023 WebDevNerdStuff 57 | Licensed under the [MIT license](https://github.com/webdevnerdstuff/vue-code-block/blob/main/LICENSE.md). 58 | -------------------------------------------------------------------------------- /dist/plugin/VCodeBlock.vue.d.ts: -------------------------------------------------------------------------------- 1 | import { Props } from './types'; 2 | 3 | declare function copyCode(): void; 4 | declare function runCode(): void; 5 | declare function __VLS_template(): { 6 | label?(_: { 7 | copyCode: typeof copyCode; 8 | copyStatus: "copy" | "success" | "failed"; 9 | runCode: typeof runCode; 10 | }): any; 11 | tabs?(_: { 12 | copyCode: typeof copyCode; 13 | copyStatus: "copy" | "success" | "failed"; 14 | runCode: typeof runCode; 15 | }): any; 16 | copyButton?(_: { 17 | copyStatus: "copy" | "success" | "failed"; 18 | }): any; 19 | }; 20 | declare const __VLS_component: import('vue').DefineComponent<__VLS_WithDefaults<__VLS_TypePropsToRuntimeProps, { 21 | browserWindow: boolean; 22 | cssPath: undefined; 23 | code: string; 24 | codeBlockRadius: string; 25 | copyButton: boolean; 26 | copyIcons: boolean; 27 | copyTab: boolean; 28 | copyFailedText: string; 29 | copyText: string; 30 | copySuccessText: string; 31 | floatingTabs: boolean; 32 | height: string; 33 | highlightjs: boolean; 34 | indent: number; 35 | label: string; 36 | lang: string; 37 | maxHeight: string; 38 | persistentCopyButton: boolean; 39 | prismjs: boolean; 40 | prismPlugin: boolean; 41 | runTab: boolean; 42 | runText: string; 43 | tabGap: string; 44 | tabs: boolean; 45 | theme: string; 46 | }>, {}, unknown, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, { 47 | run: (...args: any[]) => void; 48 | "update:copy-status": (...args: any[]) => void; 49 | }, string, import('vue').PublicProps, Readonly, { 50 | browserWindow: boolean; 51 | cssPath: undefined; 52 | code: string; 53 | codeBlockRadius: string; 54 | copyButton: boolean; 55 | copyIcons: boolean; 56 | copyTab: boolean; 57 | copyFailedText: string; 58 | copyText: string; 59 | copySuccessText: string; 60 | floatingTabs: boolean; 61 | height: string; 62 | highlightjs: boolean; 63 | indent: number; 64 | label: string; 65 | lang: string; 66 | maxHeight: string; 67 | persistentCopyButton: boolean; 68 | prismjs: boolean; 69 | prismPlugin: boolean; 70 | runTab: boolean; 71 | runText: string; 72 | tabGap: string; 73 | tabs: boolean; 74 | theme: string; 75 | }>>> & { 76 | onRun?: ((...args: any[]) => any) | undefined; 77 | "onUpdate:copy-status"?: ((...args: any[]) => any) | undefined; 78 | }, { 79 | highlightjs: boolean; 80 | prismjs: boolean; 81 | persistentCopyButton: boolean; 82 | floatingTabs: boolean; 83 | tabGap: string | number; 84 | copyTab: boolean; 85 | height: string | number; 86 | maxHeight: string | number; 87 | codeBlockRadius: string; 88 | runTab: boolean; 89 | tabs: boolean; 90 | browserWindow: boolean; 91 | cssPath: string; 92 | code: object | [] | string | number; 93 | copyButton: boolean; 94 | copyIcons: boolean; 95 | copyFailedText: string; 96 | copyText: string; 97 | copySuccessText: string; 98 | indent: number; 99 | label: string; 100 | lang: string; 101 | prismPlugin: boolean; 102 | runText: string; 103 | theme: string | boolean; 104 | }, {}>; 105 | declare const _default: __VLS_WithTemplateSlots>; 106 | export default _default; 107 | type __VLS_NonUndefinedable = T extends undefined ? never : T; 108 | type __VLS_TypePropsToRuntimeProps = { 109 | [K in keyof T]-?: {} extends Pick ? { 110 | type: import('vue').PropType<__VLS_NonUndefinedable>; 111 | } : { 112 | type: import('vue').PropType; 113 | required: true; 114 | }; 115 | }; 116 | type __VLS_WithDefaults = { 117 | [K in keyof Pick]: K extends keyof D ? __VLS_Prettify : P[K]; 120 | }; 121 | type __VLS_Prettify = { 122 | [K in keyof T]: T[K]; 123 | } & {}; 124 | type __VLS_WithTemplateSlots = T & { 125 | new (): { 126 | $slots: S; 127 | }; 128 | }; 129 | -------------------------------------------------------------------------------- /dist/plugin/components/StatusIcons.vue.d.ts: -------------------------------------------------------------------------------- 1 | declare const _default: import('vue').DefineComponent<{ 2 | icon: { 3 | type: StringConstructor; 4 | required: true; 5 | }; 6 | }, {}, unknown, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly>, {}, {}>; 12 | export default _default; 13 | -------------------------------------------------------------------------------- /dist/plugin/composables/classes.d.ts: -------------------------------------------------------------------------------- 1 | import { UseCodeBlockClasses, UseCopyButtonClasses, UseIconClasses, UseLabelClasses, UseTabClasses } from '../types'; 2 | 3 | export declare const useCodeBlockClasses: UseCodeBlockClasses; 4 | export declare const useCopyButtonClasses: UseCopyButtonClasses; 5 | export declare const useIconClasses: UseIconClasses; 6 | export declare const useLabelClasses: UseLabelClasses; 7 | export declare const useTabClasses: UseTabClasses; 8 | -------------------------------------------------------------------------------- /dist/plugin/composables/helpers.d.ts: -------------------------------------------------------------------------------- 1 | import { UseConvertToUnit } from '../types'; 2 | 3 | /** 4 | * Converts a string to a number with a unit. 5 | */ 6 | export declare const useConvertToUnit: UseConvertToUnit; 7 | -------------------------------------------------------------------------------- /dist/plugin/composables/styles.d.ts: -------------------------------------------------------------------------------- 1 | import { UseCodeTagStyles, UseHeaderStyles, UsePreTagStyles, UseTabGroupStyles } from '../types'; 2 | 3 | export declare const useCodeTagStyles: UseCodeTagStyles; 4 | export declare const useHeaderStyles: UseHeaderStyles; 5 | export declare const usePreTagStyles: UsePreTagStyles; 6 | export declare const useTabGroupStyles: UseTabGroupStyles; 7 | -------------------------------------------------------------------------------- /dist/plugin/index.d.ts: -------------------------------------------------------------------------------- 1 | import { App } from 'vue'; 2 | import { Props } from './types'; 3 | import { default as VCodeBlock } from './VCodeBlock.vue'; 4 | 5 | export declare const codeBlockOptions: unique symbol; 6 | export declare function createVCodeBlock(options?: Props): { 7 | install: (app: App) => void; 8 | }; 9 | export default VCodeBlock; 10 | export { VCodeBlock, }; 11 | -------------------------------------------------------------------------------- /dist/plugin/types/index.d.ts: -------------------------------------------------------------------------------- 1 | import { CSSProperties, MaybeRef } from 'vue'; 2 | import { default as VCodeBlock } from '../VCodeBlock.vue'; 3 | 4 | export * from '../index'; 5 | export type UseTheme = MaybeRef; 6 | export type CopyStatus = MaybeRef<'copy' | 'success' | 'failed'>; 7 | export interface Props { 8 | browserWindow?: boolean; 9 | cssPath?: string | undefined; 10 | code?: object | [] | string | number; 11 | codeBlockRadius?: string; 12 | copyButton?: boolean; 13 | copyIcons?: boolean; 14 | copyTab?: boolean; 15 | copyFailedText?: string; 16 | copyText?: string; 17 | copySuccessText?: string; 18 | floatingTabs?: boolean; 19 | globalOptions?: boolean; 20 | height?: string | number; 21 | highlightjs?: boolean; 22 | indent?: number; 23 | label?: string; 24 | lang?: string; 25 | languages?: string[]; 26 | maxHeight?: string | number; 27 | persistentCopyButton?: boolean; 28 | prismjs?: boolean; 29 | prismPlugin?: boolean; 30 | runTab?: boolean; 31 | runText?: string; 32 | tabGap?: string | number; 33 | tabs?: boolean; 34 | theme?: string | boolean; 35 | } 36 | export interface UseConvertToUnit { 37 | (options: { 38 | str: string | number | undefined | null; 39 | unit?: string; 40 | }): string | void; 41 | } 42 | export interface UseCodeBlockClasses { 43 | (options: { 44 | isMobile: MaybeRef; 45 | isPrism: MaybeRef; 46 | }): object; 47 | } 48 | export interface UseCopyButtonClasses { 49 | (options: { 50 | copyStatus: CopyStatus; 51 | isMobile: MaybeRef; 52 | persistentCopyButton: MaybeRef; 53 | }): object; 54 | } 55 | export interface UseIconClasses { 56 | (options: { 57 | copyStatus: CopyStatus; 58 | highlightjs: MaybeRef; 59 | useTheme: UseTheme; 60 | }): object; 61 | } 62 | export interface UseLabelClasses { 63 | (options: { 64 | isMobile: MaybeRef; 65 | }): object; 66 | } 67 | export interface UseTabClasses { 68 | (options: { 69 | highlightjs: MaybeRef; 70 | useTheme: UseTheme; 71 | }): object; 72 | } 73 | export interface UseCodeTagStyles { 74 | (options: { 75 | isLoading: MaybeRef; 76 | useTheme: UseTheme; 77 | }): CSSProperties; 78 | } 79 | export interface UseHeaderStyles { 80 | (options: { 81 | floatingTabs: MaybeRef; 82 | tabGap: MaybeRef; 83 | }): CSSProperties; 84 | } 85 | export interface UsePreTagStyles { 86 | (options: { 87 | copyTab: MaybeRef; 88 | height: MaybeRef; 89 | maxHeight: MaybeRef; 90 | radius: MaybeRef; 91 | runTab: MaybeRef; 92 | tabs: MaybeRef; 93 | useTheme: UseTheme; 94 | }): CSSProperties; 95 | } 96 | export interface UseTabGroupStyles { 97 | (options: { 98 | tabGap: MaybeRef; 99 | }): CSSProperties; 100 | } 101 | declare module "vue" { 102 | interface ComponentCustomProperties { 103 | } 104 | interface GlobalComponents { 105 | VCodeBlock: typeof VCodeBlock; 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /dist/plugin/utils/globals.d.ts: -------------------------------------------------------------------------------- 1 | export declare const pluginName = "v-code-block"; 2 | -------------------------------------------------------------------------------- /dist/plugin/utils/props.d.ts: -------------------------------------------------------------------------------- 1 | export declare const AllProps: { 2 | browserWindow: boolean; 3 | cssPath: undefined; 4 | code: string; 5 | codeBlockRadius: string; 6 | copyButton: boolean; 7 | copyIcons: boolean; 8 | copyTab: boolean; 9 | copyFailedText: string; 10 | copyText: string; 11 | copySuccessText: string; 12 | floatingTabs: boolean; 13 | height: string; 14 | highlightjs: boolean; 15 | indent: number; 16 | label: string; 17 | lang: string; 18 | maxHeight: string; 19 | persistentCopyButton: boolean; 20 | prismjs: boolean; 21 | prismPlugin: boolean; 22 | runTab: boolean; 23 | runText: string; 24 | tabGap: string; 25 | tabs: boolean; 26 | theme: string; 27 | }; 28 | -------------------------------------------------------------------------------- /dist/scss/README.md: -------------------------------------------------------------------------------- 1 | # 🎨 Vue3 CodeBlock Themed Tab Styles 2 | 3 | If you would like to contribute any missing theme tab styles please follow the instructions below. 4 | 5 | _I am not currently accepting any custom themes to be integrated into the plugin at this time._ 6 | 7 | ## 🚀 Getting Started 8 | Before you get started, please read the [Contributing](https://github.com/webdevnerdstuff/vue-code-block/blob/main/.github/CONTRIBUTING.md) and [Contributor Covenant Code of Conduct](https://github.com/webdevnerdstuff/vue-code-block/blob/main/.github/CODE_OF_CONDUCT.md) pages. 9 | 10 | 1. Fork the repository. 11 | 2. Clone the forked repository to your local machine. 12 | 3. Create a new branch for your changes. 13 | 4. Install the node packages `pnpm i`. Please do not use `npm` or `yarn`. 14 | 5. Run the development server with the developer playground `pnpm play`. 15 | 6. You can view the demo site at the link provided by vite that will display in the console. Ex. `http://localhost:5173/vue-code-block/` 16 | 7. The playground page will open automatically or can be viewed by adding `playground/` to the demo site link provided by vite that will display in the console. Ex. `http://localhost:5173/vue-code-block/playground/`. The base site link will also be working. 17 | 18 | ## 📋 Additional notes 19 | Make sure you are not adding a theme that already exists. 20 | 21 | When selecting colors for the tabs, use colors from the themes stylesheet. Generally a green color for `success`, red color for `failed`. If the theme does not have a color for `success` or `failed`, use the default variables `--v-cb-success` and `--v-cb-failed`. 22 | 23 | Do not change other variable colors. Please stick to the theme you are adding. If you have a suggestion for changing a variable color already defined, please open a [discussion](https://github.com/webdevnerdstuff/vue-code-block/discussions). The reason we have the variables is to allow users to customize the colors to their liking if they do not like the defaults already created. 24 | 25 | Make sure your `css` and `scss` files are using tabbed spacing. When adding themes, do not change or alter any other files than the ones mentioned below. If you have a bug fix please make your changes in a separate branch and create a separate pull request. 26 | 27 | Do not commit the `PlaygroundPage.vue` file. This is only for testing purposes. 28 | 29 | ## ⌨ Add new theme variables 30 | Update `cssVariables.css` by including the new theme variables. All values must use the [hsl](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/hsl) color format. 31 | 32 | #### libraryName: 33 | Available options include: `prism` or `highlightjs`. Exclude the square brackets. 34 | 35 | #### themeName: 36 | The name of the theme. This must match the themes css file name (excluding the `min` and `.css` extension) Exclude the square brackets. 37 | 38 | If you are adding themes from the [Prism Themes Repo](https://github.com/PrismJS/prism-themes), prefix the theme name with `themes-`. Ex. `themes-night-owl`. Do no include the `prism-` prefix into the theme name. 39 | 40 | You can use this as a template for the variables: 41 | 42 | ```css 43 | --v-cb-tab-[libraryName]-[themeName]-bkg: 0 0% 0%; 44 | --v-cb-tab-[libraryName]-[themeName]-text: 0 0% 0%; 45 | --v-cb-tab-[libraryName]-[themeName]-icon: 0 0% 0%; 46 | --v-cb-tab-[libraryName]-[themeName]-icon-success: 0 0% 0%; 47 | --v-cb-tab-[libraryName]-[themeName]-icon-failed: 0 0% 0%; 48 | ``` 49 | 50 | ## 📝 Update themes to be compiled 51 | Update `themeStyles.scss` by including the new theme name. 52 | 53 | #### [Highlight.js](https://highlightjs.org/) themes 54 | Add the theme name into the `$hljs-themes` variable. For base16 themes, use a dash to separate base16 from the theme name. Ex. `base16-onedark`. The list should be alphabetically organized. No other changes are needed as the themes are built within a loop. If it requires additional styles, it should be added under the `Themes with additional styles` section. 55 | 56 | #### [Prism](https://prismjs.com/) themes 57 | Not all themes from the [Prism Themes Repo](https://github.com/PrismJS/prism-themes) have been added. 58 | Add the theme using the following as a template under the section `Add new Prism Themes here`. Themes are alphabetically organized. 59 | 60 | ``` 61 | &-[themeName] { 62 | background-color: hsl(var(--v-cb-tab-prism-themes-[themeName]-bkg) / 100%) !important; 63 | 64 | &:hover { 65 | background-color: hsl(var(--v-cb-tab-prism-themes-[themeName]-bkg) / 50%) !important; 66 | } 67 | 68 | &-icon { 69 | color: hsl(var(--v-cb-tab-prism-themes-[themeName]-icon)) !important; 70 | fill: hsl(var(--v-cb-tab-prism-themes-[themeName]-icon)) !important; 71 | 72 | &-status { 73 | &-success { 74 | color: hsl(var(--v-cb-tab-prism-themes-[themeName]-icon-success)) !important; 75 | fill: hsl(var(--v-cb-tab-prism-themes-[themeName]-icon-success)) !important; 76 | } 77 | 78 | &-failed { 79 | color: hsl(var(--v-cb-tab-prism-themes-[themeName]-icon-failed)) !important; 80 | fill: hsl(var(--v-cb-tab-prism-themes-[themeName]-icon-failed)) !important; 81 | } 82 | } 83 | } 84 | 85 | > div { 86 | color: hsl(var(--v-cb-tab-prism-themes-[themeName]-text)); 87 | } 88 | } 89 | ``` 90 | 91 | ## ⌨ Add theme to the demo site 92 | Update `src/stores/index.ts` by including the new theme name in either `prismThemes` or `highlightThemes` array depending on the theme added. Themes are alphabetically organized. 93 | 94 | ## 🔬 Test your changes 95 | There are plenty of examples in the demo site, so please make sure to test your changes before submitting a pull request. 96 | 97 | ## 🏆 Thank you! 98 | Thank you for your contribution! I appreciate your time and effort to help make this plugin better for everyone. 99 | 100 | ## 💵 Donations 101 | If you want to show your appreciation for all the work that has been done, I always appreciate [donations](paypal.me/webdevnerdstuff)! 102 | ``` 103 | -------------------------------------------------------------------------------- /dist/scss/main.scss: -------------------------------------------------------------------------------- 1 | .v-code-block { 2 | display: block; 3 | max-width: 100%; 4 | 5 | &--header { 6 | align-items: end; 7 | display: flex; 8 | justify-content: space-between; 9 | overflow: visible; 10 | position: relative; 11 | width: 100%; 12 | } 13 | 14 | &--label { 15 | overflow: auto; 16 | } 17 | 18 | &--tabs { 19 | align-items: end; 20 | display: flex; 21 | justify-content: flex-end; 22 | } 23 | 24 | &--tab { 25 | align-items: center; 26 | border-radius: 5px 5px 0 0; 27 | cursor: pointer; 28 | display: flex; 29 | flex-direction: row; 30 | justify-content: flex-start; 31 | padding: 5px 15px; 32 | text-align: center; 33 | transition: background-color 0.35s ease; 34 | white-space: nowrap; 35 | width: fit-content; 36 | 37 | svg { 38 | height: 0.85rem; 39 | width: 0.85rem; 40 | } 41 | } 42 | 43 | &--code { 44 | position: relative; 45 | z-index: 1; 46 | 47 | pre { 48 | margin-top: 0; 49 | 50 | &[class*='language-'] { 51 | margin-top: 0; 52 | 53 | &::before, 54 | &::after { 55 | bottom: 0.95em; 56 | } 57 | } 58 | 59 | code { 60 | width: 100%; 61 | } 62 | } 63 | 64 | // Browser Window // 65 | &-browser { 66 | &::before { 67 | background-image: url('data:image/svg+xml;utf8, '); 68 | background-position: 0.5em 0.5em; 69 | background-repeat: no-repeat; 70 | content: ''; 71 | display: block; 72 | padding-right: 10em; 73 | padding-top: 3rem; 74 | width: 100%; 75 | } 76 | } 77 | 78 | // Copy Button // 79 | &:hover { 80 | .v-code-block--code-copy-button { 81 | opacity: 1; 82 | } 83 | } 84 | 85 | &-copy { 86 | &-button { 87 | align-items: center; 88 | color: #ccc; 89 | cursor: pointer; 90 | display: flex; 91 | fill: #ccc; 92 | height: 1.5em; 93 | justify-content: center; 94 | opacity: 0; 95 | position: absolute; 96 | right: 0.9rem; 97 | top: 0.7rem; 98 | transition: opacity 0.2s ease-in-out; 99 | width: auto; 100 | z-index: 2; 101 | 102 | &:hover { 103 | opacity: 1; 104 | } 105 | 106 | &-persist { 107 | opacity: .5; 108 | } 109 | 110 | svg { 111 | height: 1rem; 112 | width: 1rem; 113 | } 114 | } 115 | } 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /dist/scss/utilities.scss: -------------------------------------------------------------------------------- 1 | // Utilities // 2 | .v-code-block { 3 | @for $i from 1 through 5 { 4 | 5 | // ----------------------------- MARGIN // 6 | &--mt-#{$i} { 7 | margin-top: $i * 0.25rem !important; 8 | } 9 | 10 | &--me-#{$i} { 11 | margin-right: $i * 0.25rem !important; 12 | } 13 | 14 | &--mb-#{$i} { 15 | margin-bottom: $i * 0.25rem !important; 16 | } 17 | 18 | &--ms-#{$i} { 19 | margin-left: $i * 0.25rem !important; 20 | } 21 | 22 | // ----------------------------- PADDING // 23 | &--pt-#{$i} { 24 | padding-top: $i * 0.25rem !important; 25 | } 26 | 27 | &--pe-#{$i} { 28 | padding-right: $i * 0.25rem !important; 29 | } 30 | 31 | &--pb-#{$i} { 32 | padding-bottom: $i * 0.25rem !important; 33 | } 34 | 35 | &--ps-#{$i} { 36 | padding-left: $i * 0.25rem !important; 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 13 | 20 | 21 | 22 | 26 | Vue 3 CodeBlock 27 | 28 | 32 | 36 | 40 | 44 | 48 | 52 | 53 | 57 | 61 | 65 | 69 | 73 | 77 | 81 | 82 | 86 | 91 | 95 | 96 | 101 | 102 | 103 | 104 |
105 | 109 | 110 | 122 | 123 | 124 | 125 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@wdns/vue-code-block", 3 | "version": "2.3.5", 4 | "description": "Vue 3 CodeBlock - Highlight your code with ease using this syntax highlighting component powered by PrismJS or Highlight.js.", 5 | "private": false, 6 | "publishConfig": { 7 | "access": "public" 8 | }, 9 | "main": "dist/vue-code-block.cjs.js", 10 | "module": "dist/vue-code-block.es.js", 11 | "types": "dist/plugin/types/index.d.ts", 12 | "scripts": { 13 | "dev": "NODE_OPTIONS='--no-warnings' vite", 14 | "watch": "pnpm dev", 15 | "play": "sh src/playground/configs/build.sh && NODE_ENV=playground pnpm dev", 16 | "sass": "sass --watch --no-source-map src/plugin/themes/scss/:src/plugin/themes/css", 17 | "sass:min": "sass --style compressed --watch --no-source-map src/plugin/themes/scss/:src/plugin/themes/css/min", 18 | "build": "vue-tsc -p tsconfig.dist.json && npm run test:build && vite build --config vite.build.config.mts", 19 | "build:docs": "vite build", 20 | "predeploy": "npm run build", 21 | "deploy": "gh-pages -d docs", 22 | "prepublishOnly": "npm run build", 23 | "lint": "eslint src/**/*.{ts,vue} --max-warnings 4", 24 | "prepare": "husky", 25 | "test:dev": "NODE_OPTIONS='--no-warnings' vitest", 26 | "test:commit": "vitest --run --bail 1", 27 | "test:build": "vitest --run --bail 1 --reporter dot" 28 | }, 29 | "lint-staged": { 30 | "src/**/*.{js,ts,vue}": [ 31 | "npm run lint" 32 | ] 33 | }, 34 | "author": "WebDevNerdStuff & Bunnies... lots and lots of bunnies! (https://webdevnerdstuff.com)", 35 | "funding": [ 36 | { 37 | "type": "paypal", 38 | "url": "https://paypal.me/webdevnerdstuff" 39 | }, 40 | { 41 | "type": "patreon", 42 | "url": "https://www.patreon.com/WebDevNerdStuff" 43 | } 44 | ], 45 | "license": "MIT", 46 | "files": [ 47 | "dist/*", 48 | "LICENSE.md", 49 | "README.md" 50 | ], 51 | "repository": "https://github.com/webdevnerdstuff/vue-code-block", 52 | "bugs": { 53 | "url": "https://github.com/webdevnerdstuff/vue-code-block/issues" 54 | }, 55 | "homepage": "https://webdevnerdstuff.github.io/vue-code-block/", 56 | "keywords": [ 57 | "vue-code-block", 58 | "code", 59 | "pre", 60 | "highlight", 61 | "syntax", 62 | "vue", 63 | "vue3", 64 | "prism", 65 | "prismjs", 66 | "highlightjs", 67 | "highlight.js", 68 | "component", 69 | "javascript", 70 | "typescript", 71 | "neon bunny", 72 | "webdevnerdstuff", 73 | "wdns" 74 | ], 75 | "dependencies": { 76 | "highlight.js": "^11.8.0", 77 | "prismjs": "^1.29.0", 78 | "ua-parser-js": "^1.0.38", 79 | "vue": "^3.4.31" 80 | }, 81 | "devDependencies": { 82 | "@fortawesome/fontawesome-svg-core": "^6.5.2", 83 | "@fortawesome/free-brands-svg-icons": "^6.5.2", 84 | "@fortawesome/free-regular-svg-icons": "^6.5.2", 85 | "@fortawesome/free-solid-svg-icons": "^6.5.2", 86 | "@fortawesome/vue-fontawesome": "^3.0.8", 87 | "@mdi/font": "^7.4.47", 88 | "@rollup/plugin-commonjs": "^26.0.1", 89 | "@rollup/plugin-node-resolve": "^15.2.3", 90 | "@rollup/plugin-terser": "^0.4.4", 91 | "@stylistic/stylelint-plugin": "^2.1.2", 92 | "@types/node": "^22.10.2", 93 | "@types/prismjs": "^1.26.5", 94 | "@typescript-eslint/eslint-plugin": "^6.18.1", 95 | "@typescript-eslint/parser": "^6.18.1", 96 | "@vitejs/plugin-vue": "^5.0.5", 97 | "@vue/cli-plugin-eslint": "^5.0.8", 98 | "@vue/cli-service": "^5.0.8", 99 | "@vue/compiler-sfc": "^3.4.31", 100 | "@vue/eslint-config-typescript": "^12.0.0", 101 | "@vue/test-utils": "^2.4.6", 102 | "autoprefixer": "^10.4.19", 103 | "eslint": "^8.56.0", 104 | "eslint-config-prettier": "^9.1.0", 105 | "eslint-plugin-import": "^2.29.1", 106 | "eslint-plugin-prettier": "^5.1.3", 107 | "eslint-plugin-vue": "^9.20.0", 108 | "gh-pages": "^6.1.1", 109 | "husky": "^9.0.11", 110 | "jsdom": "^24.1.0", 111 | "lint-staged": "^15.2.7", 112 | "pinia": "^2.1.7", 113 | "postcss": "^8.4.39", 114 | "postcss-html": "^1.7.0", 115 | "postcss-scss": "^4.0.9", 116 | "prettier": "^3.3.2", 117 | "rollup": "^4.18.0", 118 | "rollup-plugin-postcss": "^4.0.2", 119 | "rollup-plugin-scss": "^4.0.0", 120 | "rollup-plugin-typescript2": "^0.36.0", 121 | "sass": "^1.77.6", 122 | "stylelint": "^16.6.1", 123 | "stylelint-config-standard": "^36.0.1", 124 | "stylelint-order": "^6.0.4", 125 | "stylelint-scss": "^6.3.2", 126 | "typescript": "~5.6.2", 127 | "unplugin-auto-import": "^0.17.6", 128 | "vite": "^5.3.3", 129 | "vite-plugin-css-injected-by-js": "^3.5.1", 130 | "vite-plugin-dts": "^3.9.1", 131 | "vite-plugin-eslint": "^1.8.1", 132 | "vite-plugin-static-copy": "^1.0.6", 133 | "vite-plugin-stylelint": "^5.3.1", 134 | "vite-plugin-vuetify": "^2.0.3", 135 | "vitest": "^1.6.0", 136 | "vue-tsc": "^2.1.10", 137 | "vuetify": "^3.4.10", 138 | "webfontloader": "^1.6.28" 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /playground/index.html: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 13 | Vue 3 CodeBlock Playground 14 | 15 | 19 | 20 | 24 | 29 | 33 | 34 | 39 | 40 | 41 | 42 |
43 | 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /public/assets/brown-papersq.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/webdevnerdstuff/vue-code-block/950e212a98f00df6a9ef6762577fba20f25765e5/public/assets/brown-papersq.png -------------------------------------------------------------------------------- /public/assets/pojoaque.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/webdevnerdstuff/vue-code-block/950e212a98f00df6a9ef6762577fba20f25765e5/public/assets/pojoaque.jpg -------------------------------------------------------------------------------- /public/assets/vue.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/brown-papersq.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/webdevnerdstuff/vue-code-block/950e212a98f00df6a9ef6762577fba20f25765e5/public/brown-papersq.png -------------------------------------------------------------------------------- /public/pojoaque.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/webdevnerdstuff/vue-code-block/950e212a98f00df6a9ef6762577fba20f25765e5/public/pojoaque.jpg -------------------------------------------------------------------------------- /public/vue.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 33 | 34 | 76 | 77 | 78 | 168 | 169 | 170 | -------------------------------------------------------------------------------- /src/assets/brown-papersq.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/webdevnerdstuff/vue-code-block/950e212a98f00df6a9ef6762577fba20f25765e5/src/assets/brown-papersq.png -------------------------------------------------------------------------------- /src/assets/pojoaque.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/webdevnerdstuff/vue-code-block/950e212a98f00df6a9ef6762577fba20f25765e5/src/assets/pojoaque.jpg -------------------------------------------------------------------------------- /src/assets/vue.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/documentation/DocsPage.vue: -------------------------------------------------------------------------------- 1 | 105 | 106 | 154 | 155 | 167 | 168 | 178 | -------------------------------------------------------------------------------- /src/documentation/components/MenuComponent.vue: -------------------------------------------------------------------------------- 1 | 53 | 54 | 90 | 91 | 108 | -------------------------------------------------------------------------------- /src/documentation/components/PropsTable.vue: -------------------------------------------------------------------------------- 1 | 79 | 80 | 111 | 112 | 118 | -------------------------------------------------------------------------------- /src/documentation/components/ThemeSelectComponent.vue: -------------------------------------------------------------------------------- 1 | 63 | 64 | 65 | 117 | -------------------------------------------------------------------------------- /src/documentation/components/examples/BrowserWindowExamples.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 44 | -------------------------------------------------------------------------------- /src/documentation/components/examples/ButtonExamples.vue: -------------------------------------------------------------------------------- 1 | 37 | 38 | 77 | -------------------------------------------------------------------------------- /src/documentation/components/examples/LangExamples.vue: -------------------------------------------------------------------------------- 1 | 82 | 83 | 191 | -------------------------------------------------------------------------------- /src/documentation/components/examples/PluginExamples.vue: -------------------------------------------------------------------------------- 1 | 54 | 55 | 105 | -------------------------------------------------------------------------------- /src/documentation/components/examples/TabExamples.vue: -------------------------------------------------------------------------------- 1 | 128 | 129 | 245 | -------------------------------------------------------------------------------- /src/documentation/components/examples/index.ts: -------------------------------------------------------------------------------- 1 | import AdditionalLangExamples from './AdditionalLangExamples.vue'; 2 | import BrowserWindowExamples from './BrowserWindowExamples.vue'; 3 | import ButtonExamples from './ButtonExamples.vue'; 4 | import LangExamples from './LangExamples.vue'; 5 | import OtherExamples from './OtherExamples.vue'; 6 | import PluginExamples from './PluginExamples.vue'; 7 | import TabExamples from './TabExamples.vue'; 8 | 9 | export { 10 | AdditionalLangExamples, 11 | BrowserWindowExamples, 12 | ButtonExamples, 13 | LangExamples, 14 | OtherExamples, 15 | PluginExamples, 16 | TabExamples, 17 | }; 18 | -------------------------------------------------------------------------------- /src/documentation/components/index.js: -------------------------------------------------------------------------------- 1 | import PropsTable from './PropsTable'; 2 | 3 | 4 | export { 5 | PropsTable, 6 | }; 7 | -------------------------------------------------------------------------------- /src/documentation/layout/AppBar.vue: -------------------------------------------------------------------------------- 1 | 122 | 123 | 187 | 188 | 219 | -------------------------------------------------------------------------------- /src/documentation/sections/DependenciesSection.vue: -------------------------------------------------------------------------------- 1 | 47 | 48 | 56 | -------------------------------------------------------------------------------- /src/documentation/sections/DescriptionSection.vue: -------------------------------------------------------------------------------- 1 | 39 | 40 | 44 | -------------------------------------------------------------------------------- /src/documentation/sections/EventsSection.vue: -------------------------------------------------------------------------------- 1 | 53 | 54 | 55 | 90 | -------------------------------------------------------------------------------- /src/documentation/sections/ExampleSection.vue: -------------------------------------------------------------------------------- 1 | 27 | 28 | 42 | -------------------------------------------------------------------------------- /src/documentation/sections/LegalSection.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 30 | -------------------------------------------------------------------------------- /src/documentation/sections/LicenseSection.vue: -------------------------------------------------------------------------------- 1 | 33 | 34 | 40 | -------------------------------------------------------------------------------- /src/documentation/sections/PlaygroundSection.vue: -------------------------------------------------------------------------------- 1 | 53 | 54 | 63 | -------------------------------------------------------------------------------- /src/documentation/sections/PropsSection.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 34 | -------------------------------------------------------------------------------- /src/documentation/sections/SlotsSection.vue: -------------------------------------------------------------------------------- 1 | 127 | 128 | 165 | 166 | 177 | -------------------------------------------------------------------------------- /src/documentation/sections/UsageSection.vue: -------------------------------------------------------------------------------- 1 | 59 | 60 | 105 | -------------------------------------------------------------------------------- /src/documentation/sections/index.js: -------------------------------------------------------------------------------- 1 | import DependenciesSection from './DependenciesSection.vue'; 2 | import DescriptionSection from './DescriptionSection.vue'; 3 | import EventsSection from './EventsSection.vue'; 4 | import ExampleSection from './ExampleSection.vue'; 5 | import LicenseSection from './LicenseSection.vue'; 6 | import PlaygroundSection from './PlaygroundSection.vue'; 7 | import PropsSection from './PropsSection.vue'; 8 | import SlotsSection from './SlotsSection.vue'; 9 | import UsageSection from './UsageSection.vue'; 10 | 11 | export { 12 | DependenciesSection, 13 | DescriptionSection, 14 | EventsSection, 15 | ExampleSection, 16 | LicenseSection, 17 | PlaygroundSection, 18 | PropsSection, 19 | SlotsSection, 20 | UsageSection, 21 | }; 22 | -------------------------------------------------------------------------------- /src/libraries/fontawesome.ts: -------------------------------------------------------------------------------- 1 | import { library } from '@fortawesome/fontawesome-svg-core'; 2 | import { fab } from '@fortawesome/free-brands-svg-icons'; 3 | import { fas } from '@fortawesome/free-solid-svg-icons'; 4 | import { far } from '@fortawesome/free-regular-svg-icons'; 5 | 6 | library.add( 7 | fab, 8 | fas, 9 | far, 10 | ); 11 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import '@/libraries/fontawesome'; 2 | import App from './App.vue'; 3 | import { createApp } from 'vue'; 4 | import { createPinia } from 'pinia'; 5 | import { registerPlugins } from './plugins'; 6 | import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'; 7 | import { createVCodeBlock } from './plugin'; 8 | 9 | 10 | const app = createApp(App); 11 | 12 | app.use(createVCodeBlock()); 13 | app.use(createPinia()); 14 | app.component('font-awesome-icon', FontAwesomeIcon); 15 | app.component('FaIcon', FontAwesomeIcon); 16 | 17 | registerPlugins(app); 18 | 19 | app.mount('#app'); 20 | -------------------------------------------------------------------------------- /src/playground/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | 3 | !.gitignore 4 | 5 | !configs/ 6 | !configs/**/* 7 | -------------------------------------------------------------------------------- /src/playground/configs/PlaygroundApp.vue: -------------------------------------------------------------------------------- 1 | 38 | 39 | 40 | 55 | 56 | 57 | 59 | -------------------------------------------------------------------------------- /src/playground/configs/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | export WHITE="$(printf '\033[0;37m')" 4 | export BOLD_WHITE="$(printf '\033[1;37m')" 5 | export BOLD_GREEN="$(printf '\033[1;32m')" 6 | export CHECKMARK="$(printf '\e[1;32m\xE2\x9C\x94\e[0m')" 7 | 8 | # Playground path and template # 9 | PLAYGROUND_VUE_DIR=src/playground 10 | 11 | PLAYGROUND_VUE_FILE=PlaygroundPage.vue 12 | PLAYGROUND_TS_VUE_FILE=PlaygroundPage.ts.vue 13 | 14 | 15 | # Check if Playground.vue file exists before trying to create it # 16 | if [ ! -f "$PLAYGROUND_VUE_DIR/$PLAYGROUND_VUE_FILE" ]; then 17 | cp "$PLAYGROUND_VUE_DIR/configs/templates/$PLAYGROUND_VUE_FILE" "$PLAYGROUND_VUE_DIR/$PLAYGROUND_VUE_FILE" 18 | 19 | echo "" 20 | echo " ${BOLD_GREEN}${CHECKMARK}${BOLD_WHITE} $PLAYGROUND_VUE_FILE file has been created.${WHITE}" 21 | echo "" 22 | fi 23 | 24 | # Check if PlaygroundPage.ts.vue file exists before trying to create it # 25 | if [ ! -f "$PLAYGROUND_VUE_DIR/$PLAYGROUND_TS_VUE_FILE" ]; then 26 | cp "$PLAYGROUND_VUE_DIR/configs/templates/$PLAYGROUND_TS_VUE_FILE" "$PLAYGROUND_VUE_DIR/$PLAYGROUND_TS_VUE_FILE" 27 | 28 | echo "" 29 | echo " ${BOLD_GREEN}${CHECKMARK}${BOLD_WHITE} $PLAYGROUND_TS_VUE_FILE file has been created.${WHITE}" 30 | echo "" 31 | fi 32 | 33 | -------------------------------------------------------------------------------- /src/playground/configs/playground.ts: -------------------------------------------------------------------------------- 1 | import '@/libraries/fontawesome'; 2 | import PlaygroundApp from './PlaygroundApp.vue'; 3 | import { createApp } from 'vue'; 4 | import { createPinia } from 'pinia'; 5 | import { registerPlugins } from '../../plugins'; 6 | import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'; 7 | import { createVCodeBlock } from '../../plugin/index'; 8 | 9 | 10 | const app = createApp(PlaygroundApp); 11 | 12 | app.use(createVCodeBlock()); 13 | app.use(createPinia()); 14 | app.component('font-awesome-icon', FontAwesomeIcon); 15 | app.component('FaIcon', FontAwesomeIcon); 16 | 17 | registerPlugins(app); 18 | 19 | app.mount('#app'); 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/playground/configs/templates/PlaygroundPage.ts.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 47 | -------------------------------------------------------------------------------- /src/playground/configs/templates/PlaygroundPage.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 47 | -------------------------------------------------------------------------------- /src/plugin/__tests__/VCodeBlock.test.ts: -------------------------------------------------------------------------------- 1 | import VCodeBlock from '../VCodeBlock.vue'; 2 | import { h } from 'vue'; 3 | import { describe, it, expect } from 'vitest'; 4 | import { mount } from '@vue/test-utils'; 5 | import { pluginName } from '../utils/globals'; 6 | import { codeBlockOptions } from '../'; 7 | 8 | describe('VCodeBlock Component', () => { 9 | 10 | // -------------------------------------------------- Component // 11 | describe('Component', () => { 12 | it('should mount the component', () => { 13 | const wrapper = mount(VCodeBlock, { 14 | props: { highlightjs: true } 15 | }); 16 | 17 | const returnedProps = wrapper.getComponent(VCodeBlock).props(); 18 | 19 | expect(returnedProps).toMatchSnapshot(); 20 | }); 21 | 22 | it('should mount the component using global options', () => { 23 | const wrapper = mount(VCodeBlock, { 24 | global: { 25 | provide: { 26 | [codeBlockOptions]: { 27 | browserWindow: true, 28 | }, 29 | } 30 | }, 31 | props: { highlightjs: true } 32 | }); 33 | 34 | expect(wrapper.html()).toContain('v-code-block--code-browser'); 35 | }); 36 | }); 37 | 38 | 39 | // -------------------------------------------------- Slots // 40 | describe('Slots', () => { 41 | it('should use label slot', () => { 42 | const wrapper = mount(VCodeBlock, { 43 | props: { highlightjs: true }, 44 | slots: { 45 | label: h('div', { class: 'using-label-slot' }, 'Hello World'), 46 | } 47 | }); 48 | 49 | const labelHtml = wrapper.find('.using-label-slot').html(); 50 | 51 | expect(labelHtml).toMatchSnapshot(); 52 | }); 53 | 54 | it('should use tabs slot', () => { 55 | const wrapper = mount(VCodeBlock, { 56 | props: { highlightjs: true }, 57 | slots: { 58 | tabs: h('div', { class: 'using-tabs-slot' }, 'This is the tabs slot'), 59 | } 60 | }); 61 | 62 | const tabsHtml = wrapper.find('.using-tabs-slot').html(); 63 | 64 | expect(tabsHtml).toMatchSnapshot(); 65 | }); 66 | 67 | it('should use copyButton slot', () => { 68 | const wrapper = mount(VCodeBlock, { 69 | props: { highlightjs: true }, 70 | slots: { 71 | copyButton: h('div', { class: 'using-copy-button-slot' }, 'This is the copy button slot'), 72 | } 73 | }); 74 | 75 | const copyButtonSlotHtml = wrapper.find('.using-copy-button-slot').html(); 76 | 77 | expect(copyButtonSlotHtml).toMatchSnapshot(); 78 | }); 79 | }); 80 | 81 | 82 | // -------------------------------------------------- Events // 83 | describe('Events', () => { 84 | it('should emit run event', () => { 85 | const wrapper = mount(VCodeBlock, { 86 | props: { 87 | prismjs: true, 88 | runTab: true, 89 | tabs: true, 90 | } 91 | }); 92 | 93 | wrapper.find(`.${pluginName}--tab-run`).trigger('click'); 94 | 95 | expect(wrapper.emitted()).toHaveProperty('run'); 96 | }); 97 | }); 98 | 99 | 100 | // -------------------------------------------------- Attributes // 101 | describe('Attributes', () => { 102 | it('should add class hljs class to code tag when highlightjs prop is true', () => { 103 | const wrapper = mount(VCodeBlock, { 104 | props: { highlightjs: true } 105 | }); 106 | 107 | const codeTag = wrapper.getComponent(VCodeBlock).find('code').attributes(); 108 | 109 | expect(codeTag.class).toContain('hljs'); 110 | }); 111 | 112 | it('should add class attribute line-numbers to pre tag', () => { 113 | const wrapper = mount(VCodeBlock, { 114 | attrs: { class: 'line-numbers' }, 115 | props: { highlightjs: true } 116 | }); 117 | 118 | const preTag = wrapper.getComponent(VCodeBlock).find('pre').attributes(); 119 | 120 | expect(preTag.class).toContain('line-numbers'); 121 | }); 122 | }); 123 | 124 | 125 | // -------------------------------------------------- Errors // 126 | describe('Errors', () => { 127 | const throwErrors = { 128 | bothSet: '[vue-code-block]: You cannot have both prismjs and highlightjs props set at the same time.', 129 | neitherSet: '[vue-code-block]: You must set either the prismjs or highlightjs props.', 130 | }; 131 | 132 | it('should throw error if both prismjs and highlightjs props are true', () => { 133 | expect(() => { 134 | mount(VCodeBlock, { 135 | props: { 136 | prismjs: true, 137 | highlightjs: true, 138 | } 139 | }); 140 | }).toThrowError(throwErrors.bothSet); 141 | }); 142 | 143 | it('should throw error if both prismjs and highlightjs props are not set', () => { 144 | expect(() => { 145 | mount(VCodeBlock); 146 | }).toThrowError(throwErrors.neitherSet); 147 | }); 148 | 149 | it('should throw error if neither prismjs or highlightjs props are true', () => { 150 | expect(() => { 151 | mount(VCodeBlock, { 152 | props: { 153 | prismjs: false, 154 | highlightjs: false, 155 | } 156 | }); 157 | }).toThrowError(throwErrors.neitherSet); 158 | }); 159 | 160 | it('should throw error if highlightjs used with prismPlugin prop', () => { 161 | expect(() => { 162 | mount(VCodeBlock, { 163 | props: { 164 | highlightjs: true, 165 | prismPlugin: true, 166 | } 167 | }); 168 | }).toThrowError('[vue-code-block]: Highlight.js does not support PrismJS plugins. Unexpected results may occur. Remove the `prism-plugin` prop from the vue-code-block component.'); 169 | }); 170 | }); 171 | }); 172 | -------------------------------------------------------------------------------- /src/plugin/__tests__/__snapshots__/VCodeBlock.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 | 3 | exports[`VCodeBlock Component > Component > should mount the component 1`] = ` 4 | { 5 | "browserWindow": false, 6 | "code": "", 7 | "codeBlockRadius": "0.5rem", 8 | "copyButton": true, 9 | "copyFailedText": "Copy failed!", 10 | "copyIcons": true, 11 | "copySuccessText": "Copied!", 12 | "copyTab": true, 13 | "copyText": "Copy Code", 14 | "cssPath": undefined, 15 | "floatingTabs": true, 16 | "globalOptions": false, 17 | "height": "auto", 18 | "highlightjs": true, 19 | "indent": 2, 20 | "label": "", 21 | "lang": "javascript", 22 | "languages": undefined, 23 | "maxHeight": "auto", 24 | "persistentCopyButton": false, 25 | "prismPlugin": false, 26 | "prismjs": false, 27 | "runTab": false, 28 | "runText": "Run", 29 | "tabGap": "0.25rem", 30 | "tabs": false, 31 | "theme": "neon-bunny", 32 | } 33 | `; 34 | 35 | exports[`VCodeBlock Component > Slots > should use copyButton slot 1`] = `"
This is the copy button slot
"`; 36 | 37 | exports[`VCodeBlock Component > Slots > should use label slot 1`] = `"
Hello World
"`; 38 | 39 | exports[`VCodeBlock Component > Slots > should use tabs slot 1`] = `"
This is the tabs slot
"`; 40 | -------------------------------------------------------------------------------- /src/plugin/__tests__/index.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, expect } from 'vitest'; 2 | import { createVCodeBlock } from '../'; 3 | 4 | 5 | describe('Plugin Index', () => { 6 | it('should return install function', () => { 7 | const VCodeBlock = createVCodeBlock(); 8 | 9 | expect('install' in VCodeBlock).toBe(true); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /src/plugin/components/StatusIcons.vue: -------------------------------------------------------------------------------- 1 | 41 | 42 | 43 | 52 | 53 | 56 | -------------------------------------------------------------------------------- /src/plugin/components/__tests__/StatusIcons.test.ts: -------------------------------------------------------------------------------- 1 | import StatusIcons from '../StatusIcons.vue'; 2 | import { describe, it, expect } from 'vitest'; 3 | import { mount } from '@vue/test-utils'; 4 | 5 | 6 | describe('StatusIcons Component', () => { 7 | 8 | it('should mount the copy icon', () => { 9 | const wrapper = mount(StatusIcons, { 10 | props: { icon: 'copy' } 11 | }); 12 | 13 | expect(wrapper.html()).toMatchSnapshot(); 14 | }); 15 | 16 | it('should mount the success icon', () => { 17 | const wrapper = mount(StatusIcons, { 18 | props: { icon: 'success' } 19 | }); 20 | 21 | expect(wrapper.html()).toMatchSnapshot(); 22 | }); 23 | 24 | it('should mount the failed icon', () => { 25 | const wrapper = mount(StatusIcons, { 26 | props: { icon: 'failed' } 27 | }); 28 | 29 | expect(wrapper.html()).toMatchSnapshot(); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /src/plugin/components/__tests__/__snapshots__/StatusIcons.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 | 3 | exports[`StatusIcons Component > should mount the copy icon 1`] = ` 4 | " 5 | 6 | 7 | 8 | " 9 | `; 10 | 11 | exports[`StatusIcons Component > should mount the failed icon 1`] = ` 12 | " 13 | 14 | 15 | 16 | 17 | " 18 | `; 19 | 20 | exports[`StatusIcons Component > should mount the success icon 1`] = ` 21 | " 22 | 23 | 24 | 25 | 26 | " 27 | `; 28 | -------------------------------------------------------------------------------- /src/plugin/composables/__tests__/__snapshots__/classes.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 | 3 | exports[`Classes Composable > useCodeBlockClasses > should return the codeblock class defaults 1`] = ` 4 | { 5 | "v-code-block": true, 6 | "v-code-block--highlightjs": true, 7 | "v-code-block--mobile": false, 8 | "v-code-block--prismjs": false, 9 | "v-code-block-mb-5": true, 10 | } 11 | `; 12 | 13 | exports[`Classes Composable > useCopyButtonClasses > should return copy button class defaults 1`] = ` 14 | { 15 | "v-code-block--code-copy-button": true, 16 | "v-code-block--code-copy-button-mobile": false, 17 | "v-code-block--code-copy-button-persist": false, 18 | "v-code-block--code-copy-button-status-copy": true, 19 | } 20 | `; 21 | 22 | exports[`Classes Composable > useIconClasses > should return icon class defaults 1`] = ` 23 | { 24 | "v-code-block--button-copy-icon-status-copy": true, 25 | "v-code-block--me-1": true, 26 | "v-code-block--tab-prism-neon-bunny-icon": true, 27 | "v-code-block--tab-prism-neon-bunny-icon-status-copy": true, 28 | } 29 | `; 30 | 31 | exports[`Classes Composable > useLabelClasses > should return icon class defaults 1`] = ` 32 | { 33 | "v-code-block--label": true, 34 | "v-code-block--label-mobile": false, 35 | } 36 | `; 37 | 38 | exports[`Classes Composable > useTabClasses > should return icon class defaults 1`] = ` 39 | { 40 | "v-code-block--tab-neon-bunny": true, 41 | "v-code-block--tab-prism-neon-bunny": true, 42 | } 43 | `; 44 | -------------------------------------------------------------------------------- /src/plugin/composables/__tests__/__snapshots__/styles.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 | 3 | exports[`Styles Composable > useCodeTagStyles > should return the code tag style defaults 1`] = ` 4 | { 5 | "padding": "1em", 6 | "width": "100%", 7 | } 8 | `; 9 | 10 | exports[`Styles Composable > useCodeTagStyles > should return the code tag style defaults if loading 1`] = ` 11 | { 12 | "padding": "1em", 13 | "width": "", 14 | } 15 | `; 16 | 17 | exports[`Styles Composable > useHeaderStyles > should return gap as undefined 1`] = ` 18 | { 19 | "bottom": "0", 20 | "gap": "0px", 21 | } 22 | `; 23 | 24 | exports[`Styles Composable > useHeaderStyles > should return tabGap as 0px 1`] = ` 25 | { 26 | "bottom": "0", 27 | "gap": "0px", 28 | } 29 | `; 30 | 31 | exports[`Styles Composable > useHeaderStyles > should return tabGap as 0px 2`] = ` 32 | { 33 | "bottom": "0", 34 | "gap": "0px", 35 | } 36 | `; 37 | 38 | exports[`Styles Composable > useHeaderStyles > should return the header style defaults 1`] = ` 39 | { 40 | "bottom": "1px", 41 | "gap": "0.25rem", 42 | } 43 | `; 44 | 45 | exports[`Styles Composable > useHeaderStyles > should return the header style with floatingTabs as false 1`] = ` 46 | { 47 | "bottom": "0", 48 | "gap": "0.25rem", 49 | } 50 | `; 51 | 52 | exports[`Styles Composable > usePreTagStyles > should return the pre tag style defaults 1`] = ` 53 | { 54 | "borderRadius": "0.5rem", 55 | "display": "flex", 56 | "height": "auto", 57 | "maxHeight": "auto", 58 | "overflow": "auto", 59 | } 60 | `; 61 | 62 | exports[`Styles Composable > usePreTagStyles > should return the set radius if no tabs 1`] = ` 63 | { 64 | "borderRadius": "0.5rem", 65 | "display": "flex", 66 | "height": "auto", 67 | "maxHeight": "auto", 68 | "overflow": "auto", 69 | } 70 | `; 71 | 72 | exports[`Styles Composable > usePreTagStyles > should return the set radius with tabs 1`] = ` 73 | { 74 | "borderRadius": "0.5rem 0 0.5rem 0.5rem !important", 75 | "display": "flex", 76 | "height": "auto", 77 | "maxHeight": "auto", 78 | "overflow": "auto", 79 | } 80 | `; 81 | 82 | exports[`Styles Composable > useTabGroupStyles > should return tabGap as 0px 1`] = ` 83 | { 84 | "gap": "0px", 85 | } 86 | `; 87 | 88 | exports[`Styles Composable > useTabGroupStyles > should return tabGap as 0px 2`] = ` 89 | { 90 | "gap": "0px", 91 | } 92 | `; 93 | 94 | exports[`Styles Composable > useTabGroupStyles > should return the tab group style defaults 1`] = ` 95 | { 96 | "gap": "0.25rem", 97 | } 98 | `; 99 | -------------------------------------------------------------------------------- /src/plugin/composables/__tests__/classes.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, expect, test } from 'vitest'; 2 | import { ref } from 'vue'; 3 | import { 4 | useCodeBlockClasses, 5 | useCopyButtonClasses, 6 | useIconClasses, 7 | useLabelClasses, 8 | useTabClasses, 9 | } from '../classes'; 10 | import { 11 | CopyStatus, 12 | Props, 13 | UseTheme, 14 | } from '../../types'; 15 | import { AllProps } from '../../utils/props'; 16 | import { pluginName } from '../../utils/globals'; 17 | 18 | 19 | const defaultProps: Props = { ...AllProps }; 20 | const defaultTheme = defaultProps.theme as UseTheme; 21 | 22 | 23 | describe('Classes Composable', () => { 24 | describe('useCodeBlockClasses', () => { 25 | const isMobile = ref(false); 26 | const isMobileClass = `${pluginName}--mobile`; 27 | const isPrism = ref(false); 28 | const isPrismClass = `${pluginName}--prismjs`; 29 | let classes = useCodeBlockClasses({ isMobile, isPrism }); 30 | 31 | it('should return the codeblock class defaults', () => { 32 | expect(classes).toMatchSnapshot(); 33 | }); 34 | 35 | test('mobile classes', () => { 36 | isMobile.value = true; 37 | 38 | classes = useCodeBlockClasses({ isMobile, isPrism }); 39 | expect(classes[isMobileClass]).toBeTruthy(); 40 | 41 | isMobile.value = false; 42 | classes = useCodeBlockClasses({ isMobile, isPrism }); 43 | expect(classes[isMobileClass]).toBeFalsy(); 44 | }); 45 | 46 | test('prismjs classes', () => { 47 | isPrism.value = true; 48 | 49 | classes = useCodeBlockClasses({ isMobile, isPrism }); 50 | expect(classes[isPrismClass]).toBeTruthy(); 51 | 52 | isPrism.value = false; 53 | classes = useCodeBlockClasses({ isMobile, isPrism }); 54 | expect(classes[isPrismClass]).toBeFalsy(); 55 | }); 56 | }); 57 | 58 | describe('useCopyButtonClasses', () => { 59 | const copyStatus = ref('copy'); 60 | const copyStatusClassPrefix = `${pluginName}--code-copy-button-status`; 61 | const isMobile = ref(false); 62 | const isMobileClass = `${pluginName}--code-copy-button-mobile`; 63 | const persistentCopyButton = ref(defaultProps.persistentCopyButton); 64 | const persistentCopyButtonCLass = `${pluginName}--code-copy-button-persist`; 65 | 66 | let classes = useCopyButtonClasses({ copyStatus, isMobile, persistentCopyButton }); 67 | 68 | it('should return copy button class defaults', () => { 69 | expect(classes).toMatchSnapshot(); 70 | }); 71 | 72 | test('copy status classes', () => { 73 | classes = useCopyButtonClasses({ copyStatus, isMobile, persistentCopyButton }); 74 | expect(classes[`${copyStatusClassPrefix}-copy`]).toBeTruthy(); 75 | 76 | copyStatus.value = 'success'; 77 | classes = useCopyButtonClasses({ copyStatus, isMobile, persistentCopyButton }); 78 | expect(classes[`${copyStatusClassPrefix}-success`]).toBeTruthy(); 79 | 80 | copyStatus.value = 'failed'; 81 | classes = useCopyButtonClasses({ copyStatus, isMobile, persistentCopyButton }); 82 | expect(classes[`${copyStatusClassPrefix}-failed`]).toMatchInlineSnapshot(`true`); 83 | }); 84 | 85 | test('mobile classes', () => { 86 | isMobile.value = true; 87 | classes = useCopyButtonClasses({ copyStatus, isMobile, persistentCopyButton }); 88 | expect(classes[isMobileClass]).toBeTruthy(); 89 | 90 | isMobile.value = false; 91 | classes = useCopyButtonClasses({ copyStatus, isMobile, persistentCopyButton }); 92 | expect(classes[isMobileClass]).toBeFalsy(); 93 | }); 94 | 95 | test('persistent copy button classes', () => { 96 | persistentCopyButton.value = true; 97 | classes = useCopyButtonClasses({ copyStatus, isMobile, persistentCopyButton }); 98 | expect(classes[persistentCopyButtonCLass]).toBeTruthy(); 99 | 100 | persistentCopyButton.value = false; 101 | classes = useCopyButtonClasses({ copyStatus, isMobile, persistentCopyButton }); 102 | expect(classes[persistentCopyButtonCLass]).toBeFalsy(); 103 | }); 104 | }); 105 | 106 | describe('useIconClasses', () => { 107 | const copyStatus = ref('copy'); 108 | const highlightjs = ref(defaultProps.highlightjs); 109 | const highlightjsStatusClassPrefix = `${pluginName}--tab-highlightjs-${defaultTheme}-icon-status`; 110 | const prismCopyStatusClassPrefix = `${pluginName}--tab-prism-${defaultTheme}-icon-status`; 111 | const prismjsClass = `${pluginName}--tab-prism-neon-bunny-icon`; 112 | const highlightjsClass = `${pluginName}--tab-highlightjs-neon-bunny-icon`; 113 | 114 | let classes = useIconClasses({ copyStatus, highlightjs, useTheme: defaultTheme }); 115 | 116 | it('should return icon class defaults', () => { 117 | expect(classes).toMatchSnapshot(); 118 | }); 119 | 120 | test('highlightjs class', () => { 121 | highlightjs.value = true; 122 | classes = useIconClasses({ copyStatus, highlightjs, useTheme: defaultTheme }); 123 | expect(classes[highlightjsClass]).toBeTruthy(); 124 | 125 | highlightjs.value = false; 126 | classes = useIconClasses({ copyStatus, highlightjs, useTheme: defaultTheme }); 127 | expect(classes[highlightjsClass]).toBeFalsy(); 128 | }); 129 | 130 | test('prismjs class', () => { 131 | classes = useIconClasses({ copyStatus, highlightjs, useTheme: defaultTheme }); 132 | expect(classes[prismjsClass]).toBeTruthy(); 133 | 134 | highlightjs.value = true; 135 | classes = useIconClasses({ copyStatus, highlightjs, useTheme: defaultTheme }); 136 | expect(classes[prismjsClass]).toBeFalsy(); 137 | }); 138 | 139 | test('copy status classes for prismjs', () => { 140 | highlightjs.value = false; 141 | classes = useIconClasses({ copyStatus, highlightjs, useTheme: defaultTheme }); 142 | expect(classes[`${prismCopyStatusClassPrefix}-copy`]).toBeTruthy(); 143 | 144 | copyStatus.value = 'success'; 145 | classes = useIconClasses({ copyStatus, highlightjs, useTheme: defaultTheme }); 146 | expect(classes[`${prismCopyStatusClassPrefix}-success`]).toBeTruthy(); 147 | 148 | copyStatus.value = 'failed'; 149 | classes = useIconClasses({ copyStatus, highlightjs, useTheme: defaultTheme }); 150 | expect(classes[`${prismCopyStatusClassPrefix}-failed`]).toBeTruthy(); 151 | }); 152 | 153 | test('copy status classes for highlightjs', () => { 154 | highlightjs.value = true; 155 | copyStatus.value = 'copy'; 156 | classes = useIconClasses({ copyStatus, highlightjs, useTheme: defaultTheme }); 157 | expect(classes[`${highlightjsStatusClassPrefix}-copy`]).toBeTruthy(); 158 | 159 | copyStatus.value = 'success'; 160 | classes = useIconClasses({ copyStatus, highlightjs, useTheme: defaultTheme }); 161 | expect(classes[`${highlightjsStatusClassPrefix}-success`]).toBeTruthy(); 162 | 163 | copyStatus.value = 'failed'; 164 | classes = useIconClasses({ copyStatus, highlightjs, useTheme: defaultTheme }); 165 | expect(classes[`${highlightjsStatusClassPrefix}-failed`]).toBeTruthy(); 166 | }); 167 | }); 168 | 169 | describe('useLabelClasses', () => { 170 | const isMobile = ref(false); 171 | const mobileClass = `${pluginName}--label-mobile`; 172 | 173 | let classes = useLabelClasses({ isMobile }); 174 | 175 | it('should return icon class defaults', () => { 176 | expect(classes).toMatchSnapshot(); 177 | }); 178 | 179 | test('mobile classes', () => { 180 | classes = useLabelClasses({ isMobile }); 181 | expect(classes[mobileClass]).toBeFalsy(); 182 | 183 | isMobile.value = true; 184 | classes = useLabelClasses({ isMobile }); 185 | expect(classes[mobileClass]).toBeTruthy(); 186 | }); 187 | }); 188 | 189 | describe('useTabClasses', () => { 190 | const highlightjs = ref(defaultProps.highlightjs); 191 | const tabClassPrefix = `${pluginName}--tab`; 192 | 193 | let classes = useTabClasses({ highlightjs, useTheme: defaultTheme }); 194 | 195 | it('should return icon class defaults', () => { 196 | expect(classes).toMatchSnapshot(); 197 | }); 198 | 199 | test('highlightjs classes', () => { 200 | classes = useTabClasses({ highlightjs, useTheme: defaultTheme }); 201 | expect(classes[`${tabClassPrefix}-highlightjs-${defaultTheme}`]).toBeFalsy(); 202 | 203 | highlightjs.value = true; 204 | classes = useTabClasses({ highlightjs, useTheme: defaultTheme }); 205 | expect(classes[`${tabClassPrefix}-highlightjs-${defaultTheme}`]).toBeTruthy(); 206 | }); 207 | 208 | test('prismjs classes', () => { 209 | highlightjs.value = false; 210 | classes = useTabClasses({ highlightjs, useTheme: defaultTheme }); 211 | expect(classes[`${tabClassPrefix}-prism-${defaultTheme}`]).toBeTruthy(); 212 | 213 | highlightjs.value = true; 214 | classes = useTabClasses({ highlightjs, useTheme: defaultTheme }); 215 | expect(classes[`${tabClassPrefix}-prism-${defaultTheme}`]).toBeFalsy(); 216 | }); 217 | }); 218 | }); 219 | -------------------------------------------------------------------------------- /src/plugin/composables/__tests__/helpers.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, expect } from 'vitest'; 2 | import { useConvertToUnit } from '../helpers'; 3 | 4 | describe('Helpers Composable', () => { 5 | describe('useConvertToUnit', () => { 6 | const testValue = 10; 7 | 8 | it('should return string with a default px unit', () => { 9 | const unit = useConvertToUnit({ str: String(testValue) }); 10 | expect(unit).toBe(`${testValue}px`); 11 | }); 12 | 13 | it('should return number with a default px unit', () => { 14 | const unit = useConvertToUnit({ str: testValue }); 15 | expect(unit).toBe(`${testValue}px`); 16 | }); 17 | 18 | it('should return string with a supplied unit', () => { 19 | const unit = useConvertToUnit({ str: String(testValue), unit: 'em' }); 20 | expect(unit).toBe(`${testValue}em`); 21 | }); 22 | 23 | it('should return number with a supplied unit', () => { 24 | const unit = useConvertToUnit({ str: testValue, unit: 'em' }); 25 | expect(unit).toBe(`${testValue}em`); 26 | }); 27 | 28 | it('should return undefined if str is null', () => { 29 | const unit = useConvertToUnit({ str: null }); 30 | expect(unit).toBe(undefined); 31 | }); 32 | 33 | it('should return undefined if str is undefined', () => { 34 | const unit = useConvertToUnit({ str: undefined }); 35 | expect(unit).toBe(undefined); 36 | }); 37 | 38 | it('should return undefined if str is empty', () => { 39 | const unit = useConvertToUnit({ str: '' }); 40 | expect(unit).toBe(undefined); 41 | }); 42 | }); 43 | }); 44 | -------------------------------------------------------------------------------- /src/plugin/composables/__tests__/styles.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, expect } from 'vitest'; 2 | import { ref } from 'vue'; 3 | import { 4 | useCodeTagStyles, 5 | useHeaderStyles, 6 | usePreTagStyles, 7 | useTabGroupStyles, 8 | } from '../styles'; 9 | import { 10 | Props, 11 | UseTheme, 12 | } from '../../types'; 13 | import { AllProps } from '../../utils/props'; 14 | 15 | 16 | const defaultProps: Props = { ...AllProps }; 17 | const defaultTheme = defaultProps.theme as UseTheme; 18 | 19 | 20 | describe('Styles Composable', () => { 21 | describe('useCodeTagStyles', () => { 22 | const theme = ref('coy'); 23 | 24 | it('should return the code tag style defaults', () => { 25 | expect( 26 | useCodeTagStyles({ isLoading: false, useTheme: theme }) 27 | ).toMatchSnapshot(); 28 | }); 29 | 30 | it('should return the code tag style defaults if loading', () => { 31 | expect( 32 | useCodeTagStyles({ isLoading: true, useTheme: theme }) 33 | ).toMatchSnapshot(); 34 | }); 35 | }); 36 | 37 | describe('useHeaderStyles', () => { 38 | // ? Default floatingTabs value is true 39 | const floatingTabs = ref(defaultProps.floatingTabs); 40 | const tabGap = ref(defaultProps.tabGap); 41 | 42 | it('should return the header style defaults', () => { 43 | expect( 44 | useHeaderStyles({ floatingTabs, tabGap }) 45 | ).toMatchSnapshot(); 46 | }); 47 | 48 | it('should return the header style with floatingTabs as false', () => { 49 | expect( 50 | useHeaderStyles({ floatingTabs: false, tabGap }) 51 | ).toMatchSnapshot(); 52 | }); 53 | 54 | it('should return gap as undefined', () => { 55 | expect( 56 | useHeaderStyles({ floatingTabs: false, tabGap: '' }) 57 | ).toMatchSnapshot(); 58 | }); 59 | 60 | it('should return tabGap as 0px', () => { 61 | expect( 62 | useHeaderStyles({ floatingTabs: false, tabGap: undefined }) 63 | ).toMatchSnapshot(); 64 | 65 | expect( 66 | useHeaderStyles({ floatingTabs: false, tabGap: '' }) 67 | ).toMatchSnapshot(); 68 | }); 69 | }); 70 | 71 | describe('usePreTagStyles', () => { 72 | // ? Default floatingTabs value is true 73 | const copyTab = ref(defaultProps.copyTab); 74 | const height = ref(defaultProps.height); 75 | const maxHeight = ref(defaultProps.maxHeight); 76 | const radius = ref(defaultProps.codeBlockRadius); 77 | const runTab = ref(defaultProps.runTab); 78 | const tabs = ref(defaultProps.tabs); 79 | 80 | it('should return the pre tag style defaults', () => { 81 | expect( 82 | usePreTagStyles({ copyTab, height, maxHeight, radius, runTab, tabs, useTheme: defaultTheme }) 83 | ).toMatchSnapshot(); 84 | }); 85 | 86 | it('should return the set radius if no tabs', () => { 87 | expect( 88 | usePreTagStyles({ copyTab, height, maxHeight, radius, runTab, tabs, useTheme: defaultTheme }) 89 | ).toMatchSnapshot(); 90 | }); 91 | 92 | it('should return the set radius with tabs', () => { 93 | expect( 94 | usePreTagStyles({ copyTab, height, maxHeight, radius, runTab, tabs: true, useTheme: defaultTheme }) 95 | ).toMatchSnapshot(); 96 | }); 97 | 98 | // TODO (fix): The border radius is not set correctly if using multiple units with tabs // 99 | it('should return the set radius', () => { 100 | expect( 101 | usePreTagStyles({ copyTab, height, maxHeight, radius: '0 1em', runTab, tabs: true, useTheme: defaultTheme }) 102 | ).toMatchInlineSnapshot(` 103 | { 104 | "borderRadius": "0 1em 0 0 1em 0 1em !important", 105 | "display": "flex", 106 | "height": "auto", 107 | "maxHeight": "auto", 108 | "overflow": "auto", 109 | } 110 | `); 111 | }); 112 | 113 | }); 114 | 115 | describe('useTabGroupStyles', () => { 116 | const tabGap = ref(defaultProps.tabGap); 117 | 118 | it('should return the tab group style defaults', () => { 119 | expect( 120 | useTabGroupStyles({ tabGap }) 121 | ).toMatchSnapshot(); 122 | }); 123 | 124 | it('should return tabGap as 0px', () => { 125 | expect( 126 | useTabGroupStyles({ tabGap: undefined }) 127 | ).toMatchSnapshot(); 128 | 129 | expect( 130 | useTabGroupStyles({ tabGap: '' }) 131 | ).toMatchSnapshot(); 132 | }); 133 | }); 134 | 135 | }); 136 | -------------------------------------------------------------------------------- /src/plugin/composables/classes.ts: -------------------------------------------------------------------------------- 1 | import { pluginName } from '../utils/globals'; 2 | import { 3 | UseCodeBlockClasses, 4 | UseCopyButtonClasses, 5 | UseIconClasses, 6 | UseLabelClasses, 7 | UseTabClasses, 8 | } from '@/plugin/types'; 9 | 10 | 11 | 12 | function getTheme(useTheme: string | boolean): string | boolean { 13 | return useTheme === '' || useTheme === 'prism' ? 'default' : useTheme; 14 | } 15 | 16 | function getActiveLibrary(highlightjs?: boolean): string { 17 | if (highlightjs) { 18 | return 'highlightjs'; 19 | } 20 | 21 | return 'prism'; 22 | } 23 | 24 | 25 | // -------------------------------------------------- Foo // 26 | export const useCodeBlockClasses: UseCodeBlockClasses = (options) => { 27 | const { isMobile, isPrism } = options; 28 | 29 | return { 30 | [`${pluginName}`]: true, 31 | [`${pluginName}-mb-5`]: true, 32 | [`${pluginName}--mobile`]: unref(isMobile), 33 | [`${pluginName}--prismjs`]: unref(isPrism), 34 | [`${pluginName}--highlightjs`]: !unref(isPrism), 35 | 36 | }; 37 | }; 38 | 39 | export const useCopyButtonClasses: UseCopyButtonClasses = (options) => { 40 | const { copyStatus, isMobile, persistentCopyButton } = options; 41 | 42 | return { 43 | [`${pluginName}--code-copy-button`]: true, 44 | [`${pluginName}--code-copy-button-mobile`]: unref(isMobile), 45 | [`${pluginName}--code-copy-button-persist`]: unref(persistentCopyButton), 46 | [`${pluginName}--code-copy-button-status-${unref(copyStatus)}`]: true, 47 | }; 48 | }; 49 | 50 | export const useIconClasses: UseIconClasses = (options) => { 51 | const { copyStatus, highlightjs, useTheme } = options; 52 | 53 | const activeLibrary = getActiveLibrary(unref(highlightjs)); 54 | const theme = getTheme(unref(useTheme)); 55 | 56 | return { 57 | [`${pluginName}--button-copy-icon-status-${unref(copyStatus)}`]: true, 58 | [`${pluginName}--me-1`]: true, 59 | [`${pluginName}--tab-${activeLibrary}-${theme}-icon-status-${unref(copyStatus)}`]: true, 60 | [`${pluginName}--tab-${activeLibrary}-${theme}-icon`]: true, 61 | }; 62 | }; 63 | 64 | export const useLabelClasses: UseLabelClasses = (options) => { 65 | const { isMobile } = options; 66 | 67 | return { 68 | [`${pluginName}--label`]: true, 69 | [`${pluginName}--label-mobile`]: unref(isMobile), 70 | }; 71 | }; 72 | 73 | export const useTabClasses: UseTabClasses = (options) => { 74 | const { highlightjs, useTheme } = options; 75 | 76 | const activeLibrary = getActiveLibrary(unref(highlightjs)); 77 | const theme = getTheme(unref(useTheme)); 78 | 79 | return { 80 | [`${pluginName}--tab-${theme}`]: true, 81 | [`${pluginName}--tab-${activeLibrary}-${theme}`]: true, 82 | }; 83 | }; 84 | -------------------------------------------------------------------------------- /src/plugin/composables/helpers.ts: -------------------------------------------------------------------------------- 1 | import { UseConvertToUnit } from '@/plugin/types'; 2 | 3 | 4 | /** 5 | * Converts a string to a number with a unit. 6 | */ 7 | export const useConvertToUnit: UseConvertToUnit = (options) => { 8 | const { str, unit = 'px' } = options; 9 | 10 | if (str == null || str === '' || typeof str === 'undefined') { 11 | return undefined; 12 | } 13 | 14 | if (!+str) { 15 | return String(str); 16 | } 17 | 18 | return `${Number(str)}${unit}`; 19 | }; 20 | -------------------------------------------------------------------------------- /src/plugin/composables/styles.ts: -------------------------------------------------------------------------------- 1 | import { CSSProperties } from 'vue'; 2 | import { useConvertToUnit } from './helpers'; 3 | import { 4 | UseCodeTagStyles, 5 | UseHeaderStyles, 6 | UsePreTagStyles, 7 | UseTabGroupStyles, 8 | } from '@/plugin/types'; 9 | 10 | 11 | export const useCodeTagStyles: UseCodeTagStyles = (options) => { 12 | const { isLoading, useTheme } = options; 13 | const width = unref(useTheme) === 'coy' && unref(isLoading) === false ? '100%' : ''; 14 | 15 | const styles: { width: string, padding?: string; } = { 16 | width, 17 | }; 18 | 19 | if (unref(useTheme) === 'coy') { 20 | styles.padding = '1em'; 21 | } 22 | 23 | return styles as CSSProperties; 24 | }; 25 | 26 | export const useHeaderStyles: UseHeaderStyles = (options) => { 27 | const { floatingTabs, tabGap } = options; 28 | 29 | const styles = { 30 | bottom: floatingTabs ? '1px' : '0', 31 | gap: useConvertToUnit({ str: unref(tabGap) }) || '0px', 32 | }; 33 | 34 | return styles as CSSProperties; 35 | }; 36 | 37 | // TODO (fix): The border radius is not set correctly if using multiple units with tabs // 38 | export const usePreTagStyles: UsePreTagStyles = (options) => { 39 | const { copyTab, height, maxHeight, radius, runTab, tabs, useTheme } = options; 40 | const unrefRadius = unref(radius); 41 | let borderRadius = `${unrefRadius} 0 ${unrefRadius} ${unrefRadius} !important`; 42 | 43 | if (!unref(tabs) || (!unref(copyTab) && !unref(runTab))) { 44 | borderRadius = unrefRadius as string; 45 | } 46 | 47 | const display = unref(useTheme) !== 'funky' ? 'flex' : 'block'; 48 | 49 | const styles = { 50 | borderRadius, 51 | display, 52 | height: useConvertToUnit({ str: unref(height) }), 53 | maxHeight: useConvertToUnit({ str: unref(maxHeight) }), 54 | overflow: 'auto', 55 | }; 56 | 57 | return styles as CSSProperties; 58 | }; 59 | 60 | export const useTabGroupStyles: UseTabGroupStyles = (options) => { 61 | const { tabGap } = options; 62 | 63 | const styles = { 64 | gap: useConvertToUnit({ str: unref(tabGap) }) || '0px', 65 | }; 66 | 67 | return styles as CSSProperties; 68 | }; 69 | -------------------------------------------------------------------------------- /src/plugin/index.ts: -------------------------------------------------------------------------------- 1 | import { defineAsyncComponent } from 'vue'; 2 | import type { App } from 'vue'; 3 | import type { Props } from './types'; 4 | import './styles/cssVariables.css'; 5 | import './styles/utilities.scss'; 6 | import './styles/main.scss'; 7 | import './styles/themeStyles.scss'; 8 | import VCodeBlock from './VCodeBlock.vue'; 9 | 10 | 11 | export const codeBlockOptions = Symbol(); 12 | 13 | export function createVCodeBlock(options: Props = {}) { 14 | const install = (app: App) => { 15 | app.provide(codeBlockOptions, options); 16 | 17 | app.component('CodeBlock', defineAsyncComponent(() => import('./VCodeBlock.vue'))); 18 | app.component('VCodeBlock', defineAsyncComponent(() => import('./VCodeBlock.vue'))); 19 | }; 20 | 21 | return { 22 | install, 23 | }; 24 | } 25 | 26 | export default VCodeBlock; 27 | 28 | export { 29 | VCodeBlock, 30 | }; 31 | -------------------------------------------------------------------------------- /src/plugin/styles/README.md: -------------------------------------------------------------------------------- 1 | # 🎨 Vue3 CodeBlock Themed Tab Styles 2 | 3 | If you would like to contribute any missing theme tab styles please follow the instructions below. 4 | 5 | _I am not currently accepting any custom themes to be integrated into the plugin at this time._ 6 | 7 | ## 🚀 Getting Started 8 | Before you get started, please read the [Contributing](https://github.com/webdevnerdstuff/vue-code-block/blob/main/.github/CONTRIBUTING.md) and [Contributor Covenant Code of Conduct](https://github.com/webdevnerdstuff/vue-code-block/blob/main/.github/CODE_OF_CONDUCT.md) pages. 9 | 10 | 1. Fork the repository. 11 | 2. Clone the forked repository to your local machine. 12 | 3. Create a new branch for your changes. 13 | 4. Install the node packages `pnpm i`. Please do not use `npm` or `yarn`. 14 | 5. Run the development server with the developer playground `pnpm play`. 15 | 6. You can view the demo site at the link provided by vite that will display in the console. Ex. `http://localhost:5173/vue-code-block/` 16 | 7. The playground page will open automatically or can be viewed by adding `playground/` to the demo site link provided by vite that will display in the console. Ex. `http://localhost:5173/vue-code-block/playground/`. The base site link will also be working. 17 | 18 | ## 📋 Additional notes 19 | Make sure you are not adding a theme that already exists. 20 | 21 | When selecting colors for the tabs, use colors from the themes stylesheet. Generally a green color for `success`, red color for `failed`. If the theme does not have a color for `success` or `failed`, use the default variables `--v-cb-success` and `--v-cb-failed`. 22 | 23 | Do not change other variable colors. Please stick to the theme you are adding. If you have a suggestion for changing a variable color already defined, please open a [discussion](https://github.com/webdevnerdstuff/vue-code-block/discussions). The reason we have the variables is to allow users to customize the colors to their liking if they do not like the defaults already created. 24 | 25 | Make sure your `css` and `scss` files are using tabbed spacing. When adding themes, do not change or alter any other files than the ones mentioned below. If you have a bug fix please make your changes in a separate branch and create a separate pull request. 26 | 27 | Do not commit the `PlaygroundPage.vue` file. This is only for testing purposes. 28 | 29 | ## ⌨ Add new theme variables 30 | Update `cssVariables.css` by including the new theme variables. All values must use the [hsl](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/hsl) color format. 31 | 32 | #### libraryName: 33 | Available options include: `prism` or `highlightjs`. Exclude the square brackets. 34 | 35 | #### themeName: 36 | The name of the theme. This must match the themes css file name (excluding the `min` and `.css` extension) Exclude the square brackets. 37 | 38 | If you are adding themes from the [Prism Themes Repo](https://github.com/PrismJS/prism-themes), prefix the theme name with `themes-`. Ex. `themes-night-owl`. Do no include the `prism-` prefix into the theme name. 39 | 40 | You can use this as a template for the variables: 41 | 42 | ```css 43 | --v-cb-tab-[libraryName]-[themeName]-bkg: 0 0% 0%; 44 | --v-cb-tab-[libraryName]-[themeName]-text: 0 0% 0%; 45 | --v-cb-tab-[libraryName]-[themeName]-icon: 0 0% 0%; 46 | --v-cb-tab-[libraryName]-[themeName]-icon-success: 0 0% 0%; 47 | --v-cb-tab-[libraryName]-[themeName]-icon-failed: 0 0% 0%; 48 | ``` 49 | 50 | ## 📝 Update themes to be compiled 51 | Update `themeStyles.scss` by including the new theme name. 52 | 53 | #### [Highlight.js](https://highlightjs.org/) themes 54 | Add the theme name into the `$hljs-themes` variable. For base16 themes, use a dash to separate base16 from the theme name. Ex. `base16-onedark`. The list should be alphabetically organized. No other changes are needed as the themes are built within a loop. If it requires additional styles, it should be added under the `Themes with additional styles` section. 55 | 56 | #### [Prism](https://prismjs.com/) themes 57 | Not all themes from the [Prism Themes Repo](https://github.com/PrismJS/prism-themes) have been added. 58 | Add the theme using the following as a template under the section `Add new Prism Themes here`. Themes are alphabetically organized. 59 | 60 | ``` 61 | &-[themeName] { 62 | background-color: hsl(var(--v-cb-tab-prism-themes-[themeName]-bkg) / 100%) !important; 63 | 64 | &:hover { 65 | background-color: hsl(var(--v-cb-tab-prism-themes-[themeName]-bkg) / 50%) !important; 66 | } 67 | 68 | &-icon { 69 | color: hsl(var(--v-cb-tab-prism-themes-[themeName]-icon)) !important; 70 | fill: hsl(var(--v-cb-tab-prism-themes-[themeName]-icon)) !important; 71 | 72 | &-status { 73 | &-success { 74 | color: hsl(var(--v-cb-tab-prism-themes-[themeName]-icon-success)) !important; 75 | fill: hsl(var(--v-cb-tab-prism-themes-[themeName]-icon-success)) !important; 76 | } 77 | 78 | &-failed { 79 | color: hsl(var(--v-cb-tab-prism-themes-[themeName]-icon-failed)) !important; 80 | fill: hsl(var(--v-cb-tab-prism-themes-[themeName]-icon-failed)) !important; 81 | } 82 | } 83 | } 84 | 85 | > div { 86 | color: hsl(var(--v-cb-tab-prism-themes-[themeName]-text)); 87 | } 88 | } 89 | ``` 90 | 91 | ## ⌨ Add theme to the demo site 92 | Update `src/stores/index.ts` by including the new theme name in either `prismThemes` or `highlightThemes` array depending on the theme added. Themes are alphabetically organized. 93 | 94 | ## 🔬 Test your changes 95 | There are plenty of examples in the demo site, so please make sure to test your changes before submitting a pull request. 96 | 97 | ## 🏆 Thank you! 98 | Thank you for your contribution! I appreciate your time and effort to help make this plugin better for everyone. 99 | 100 | ## 💵 Donations 101 | If you want to show your appreciation for all the work that has been done, I always appreciate [donations](paypal.me/webdevnerdstuff)! 102 | ``` 103 | -------------------------------------------------------------------------------- /src/plugin/styles/main.scss: -------------------------------------------------------------------------------- 1 | .v-code-block { 2 | display: block; 3 | max-width: 100%; 4 | 5 | &--header { 6 | align-items: end; 7 | display: flex; 8 | justify-content: space-between; 9 | overflow: visible; 10 | position: relative; 11 | width: 100%; 12 | } 13 | 14 | &--label { 15 | overflow: auto; 16 | } 17 | 18 | &--tabs { 19 | align-items: end; 20 | display: flex; 21 | justify-content: flex-end; 22 | } 23 | 24 | &--tab { 25 | align-items: center; 26 | border-radius: 5px 5px 0 0; 27 | cursor: pointer; 28 | display: flex; 29 | flex-direction: row; 30 | justify-content: flex-start; 31 | padding: 5px 15px; 32 | text-align: center; 33 | transition: background-color 0.35s ease; 34 | white-space: nowrap; 35 | width: fit-content; 36 | 37 | svg { 38 | height: 0.85rem; 39 | width: 0.85rem; 40 | } 41 | } 42 | 43 | &--code { 44 | position: relative; 45 | z-index: 1; 46 | 47 | pre { 48 | margin-top: 0; 49 | 50 | &[class*='language-'] { 51 | margin-top: 0; 52 | 53 | &::before, 54 | &::after { 55 | bottom: 0.95em; 56 | } 57 | } 58 | 59 | code { 60 | width: 100%; 61 | } 62 | } 63 | 64 | // Browser Window // 65 | &-browser { 66 | &::before { 67 | background-image: url('data:image/svg+xml;utf8, '); 68 | background-position: 0.5em 0.5em; 69 | background-repeat: no-repeat; 70 | content: ''; 71 | display: block; 72 | padding-right: 10em; 73 | padding-top: 3rem; 74 | width: 100%; 75 | } 76 | } 77 | 78 | // Copy Button // 79 | &:hover { 80 | .v-code-block--code-copy-button { 81 | opacity: 1; 82 | } 83 | } 84 | 85 | &-copy { 86 | &-button { 87 | align-items: center; 88 | color: #ccc; 89 | cursor: pointer; 90 | display: flex; 91 | fill: #ccc; 92 | height: 1.5em; 93 | justify-content: center; 94 | opacity: 0; 95 | position: absolute; 96 | right: 0.9rem; 97 | top: 0.7rem; 98 | transition: opacity 0.2s ease-in-out; 99 | width: auto; 100 | z-index: 2; 101 | 102 | &:hover { 103 | opacity: 1; 104 | } 105 | 106 | &-persist { 107 | opacity: .5; 108 | } 109 | 110 | svg { 111 | height: 1rem; 112 | width: 1rem; 113 | } 114 | } 115 | } 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/plugin/styles/utilities.scss: -------------------------------------------------------------------------------- 1 | // Utilities // 2 | .v-code-block { 3 | @for $i from 1 through 5 { 4 | 5 | // ----------------------------- MARGIN // 6 | &--mt-#{$i} { 7 | margin-top: $i * 0.25rem !important; 8 | } 9 | 10 | &--me-#{$i} { 11 | margin-right: $i * 0.25rem !important; 12 | } 13 | 14 | &--mb-#{$i} { 15 | margin-bottom: $i * 0.25rem !important; 16 | } 17 | 18 | &--ms-#{$i} { 19 | margin-left: $i * 0.25rem !important; 20 | } 21 | 22 | // ----------------------------- PADDING // 23 | &--pt-#{$i} { 24 | padding-top: $i * 0.25rem !important; 25 | } 26 | 27 | &--pe-#{$i} { 28 | padding-right: $i * 0.25rem !important; 29 | } 30 | 31 | &--pb-#{$i} { 32 | padding-bottom: $i * 0.25rem !important; 33 | } 34 | 35 | &--ps-#{$i} { 36 | padding-left: $i * 0.25rem !important; 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/plugin/themes/css/min/neon-bunny-carrot-prism.css: -------------------------------------------------------------------------------- 1 | :root{--neon-bunny-blue-light: #2492ff;--neon-bunny-blue: #0b93ff;--neon-bunny-gray: #7f817e;--neon-bunny-green-darker-darker: #008b05;--neon-bunny-green-darker: #2bb71d;--neon-bunny-green-light: #7cd47d;--neon-bunny-green: #00d205;--neon-bunny-lime: #c3e88d;--neon-bunny-magenta: #dd00ff;--neon-bunny-orange-darker: #e58100;--neon-bunny-orange-light: #ffc266;--neon-bunny-orange: #ff9900;--neon-bunny-peach-light: #ffe4a6;--neon-bunny-peach: #ffcb6b;--neon-bunny-pink: #ff1190;--neon-bunny-purple-light-dim: #c792ea;--neon-bunny-purple-light: #da96df;--neon-bunny-purple: #ea03ff;--neon-bunny-red: #ff5370;--neon-bunny-teal: #00EEFF;--neon-bunny-white: #fff;--neon-bunny-yellow-light: #ffffa2;--neon-bunny-yellow: #ffff00;--neon-bunny-atrule: var(--neon-bunny-peach);--neon-bunny-attr-name: var(--neon-bunny-purple-light-dim);--neon-bunny-attr-value: var(--neon-bunny-peach);--neon-bunny-boolean: var(--neon-bunny-purple-light);--neon-bunny-builtin: var(--neon-bunny-purple);--neon-bunny-cdata: var(--neon-bunny-gray);--neon-bunny-char: var(--neon-bunny-pink);--neon-bunny-class-name: var(--neon-bunny-peach);--neon-bunny-comment: var(--neon-bunny-gray);--neon-bunny-constant: var(--neon-bunny-green-darker);--neon-bunny-deleted: var(--neon-bunny-red);--neon-bunny-entity: var(--neon-bunny-peach);--neon-bunny-function: var(--neon-bunny-orange-light);--neon-bunny-important: var(--neon-bunny-pink);--neon-bunny-inserted: var(--neon-bunny-lime);--neon-bunny-keyword: var(--neon-bunny-purple-light);--neon-bunny-namespace: var(--neon-bunny-peach);--neon-bunny-number: var(--neon-bunny-white);--neon-bunny-operator: var(--neon-bunny-orange);--neon-bunny-prolog: var(--neon-bunny-gray);--neon-bunny-property: var(--neon-bunny-orange-light);--neon-bunny-punctuation: var(--neon-bunny-orange);--neon-bunny-regex: var(--neon-bunny-peach-light);--neon-bunny-string: var(--neon-bunny-green-light);--neon-bunny-symbol: var(--neon-bunny-orange-light);--neon-bunny-tag: var(--neon-bunny-orange);--neon-bunny-url: var(--neon-bunny-orange-darker);--neon-bunny-variable: var(--neon-bunny-orange);--neon-bunny-html-attr-name: var(--neon-bunny-orange-light);--neon-bunny-html-attr-value-punctuation: var(--neon-bunny-green);--neon-bunny-html-attr-value: var(--neon-bunny-green);--neon-bunny-css-atrule-rule: var(--neon-bunny-blue);--neon-bunny-css-atrule: var(--neon-bunny-white);--neon-bunny-css-function: var(--neon-bunny-orange-darker);--neon-bunny-css-property: var(--neon-bunny-pink);--neon-bunny-css-punctuation: var(--neon-bunny-white);--neon-bunny-css-selector: var(--neon-bunny-yellow);--neon-bunny-css-string: var(--neon-bunny-green-light);--neon-bunny-css: var(--neon-bunny-teal);--neon-bunny-js-keyword: var(--neon-bunny-magenta);--neon-bunny-js-literal-property: var(--neon-bunny-orange);--neon-bunny-js-operator: var(--neon-bunny-blue-light);--neon-bunny-js-punctuation: var(--neon-bunny-white);--neon-bunny-js-string: var(--neon-bunny-green-darker-darker);--neon-bunny-js-template-string-string: var(--neon-bunny-green-darker-darker);--neon-bunny-js: var(--neon-bunny-orange);--neon-bunny-php-boolean: var(--neon-bunny-purple-light);--neon-bunny-php-class-name-return-type: var(--neon-bunny-yellow);--neon-bunny-php-class-name: var(--neon-bunny-yellow);--neon-bunny-php-double-quote-string: var(--neon-bunny-green);--neon-bunny-php-function: var(--neon-bunny-orange-light);--neon-bunny-php-keyword: var(--neon-bunny-magenta);--neon-bunny-php-keyword-type-hint: var(--neon-bunny-magenta);--neon-bunny-php-class-name-definition-class-name: var(--neon-bunny-yellow);--neon-bunny-php-operator: var(--neon-bunny-yellow-light);--neon-bunny-php-package: var(--neon-bunny-yellow);--neon-bunny-php-property: var(--neon-bunny-orange);--neon-bunny-php-punctuation: var(--neon-bunny-white);--neon-bunny-php-single-quote-string: var(--neon-bunny-green-light);--neon-bunny-php-variable: var(--neon-bunny-orange)}code[class*=language-],pre[class*=language-]{-moz-hyphens:none;-moz-tab-size:4;-ms-hyphens:none;-o-tab-size:4;-webkit-hyphens:none;color:var(--neon-bunny-white);font-family:Consolas,Monaco,"Andale Mono","Ubuntu Mono",monospace;font-size:1em;hyphens:none;line-height:1.5;tab-size:4;text-align:left;white-space:pre;word-break:normal;word-spacing:normal;word-wrap:normal}code[class*=language-]::-moz-selection,code[class*=language-] ::-moz-selection,pre[class*=language-]::-moz-selection,pre[class*=language-] ::-moz-selection{background:rgba(29,59,83,.99);text-shadow:none}code[class*=language-]::selection,code[class*=language-] ::selection,pre[class*=language-]::selection,pre[class*=language-] ::selection{background:rgba(255,255,255,.0823529412);text-shadow:none}@media print{code[class*=language-],pre[class*=language-]{text-shadow:none}}pre[class*=language-]{background:#000;color:#fff;margin:.5em 0;overflow:auto;padding:1em}:not(pre)>code[class*=language-]{background:#000;color:#fff;border-radius:.3em;padding:.1em;white-space:normal}.token.atrule{color:var(--neon-bunny-atrule)}.token.attr-name{color:var(--neon-bunny-attr-name)}.token.attr-value{color:var(--neon-bunny-attr-value)}.token.bold{font-weight:bold}.token.boolean{color:var(--neon-bunny-boolean)}.token.builtin{color:var(--neon-bunny-builtin)}.token.cdata{color:var(--neon-bunny-cdata);font-style:italic}.token.char{color:var(--neon-bunny-char)}.token.class-name{color:var(--neon-bunny-class-name)}.token.constant{color:var(--neon-bunny-constant)}.token.comment{color:var(--neon-bunny-comment);font-style:italic}.token.deleted{color:var(--neon-bunny-deleted)}.token.entity{color:var(--neon-bunny-entity)}.token.function{color:var(--neon-bunny-function)}.token.important{color:var(--neon-bunny-important);font-style:italic}.token.inserted{color:var(--neon-bunny-inserted)}.token.italic{font-style:italic}.token.keyword{color:var(--neon-bunny-keyword)}.token.number{color:var(--neon-bunny-number)}.token.operator{color:var(--neon-bunny-operator)}.token.prolog{color:var(--neon-bunny-prolog);font-style:italic}.token.property{color:var(--neon-bunny-property)}.token.punctuation{color:var(--neon-bunny-punctuation)}.token.regex{color:var(--neon-bunny-regex)}.token.string{color:var(--neon-bunny-string)}.token.symbol{color:var(--neon-bunny-symbol)}.token.tag{color:var(--neon-bunny-tag)}.token.url{color:var(--neon-bunny-url);text-decoration:underline}.token.variable{color:var(--neon-bunny-variable)}.namespace{color:var(--neon-bunny-namespace)}.language-html .token .attr-name{color:var(--neon-bunny-html-attr-name)}.language-html .token.attr-value{color:var(--neon-bunny-html-attr-value)}.language-html .token.attr-value.punctuation{color:var(--neon-bunny-html-attr-value-punctuation)}.language-css{color:var(--neon-bunny-css) !important}.language-css .token.atrule{color:var(--neon-bunny-css-atrule)}.language-css .token.atrule .token.rule{color:var(--neon-bunny-css-atrule-rule)}.language-css .token.function{color:var(--neon-bunny-css-function)}.language-css .token.property{color:var(--neon-bunny-css-property)}.language-css .token.punctuation{color:var(--neon-bunny-css-punctuation)}.language-css .token.selector{color:var(--neon-bunny-css-selector)}.language-css .token.string{color:var(--neon-bunny-css-string);font-style:italic}.style .token.string{color:var(--neon-bunny-css-string);font-style:italic}.language-javascript,.language-js{color:var(--neon-bunny-js) !important;font-style:italic}.language-javascript span,.language-js span{font-style:normal}.language-javascript .token.keyword,.language-js .token.keyword{color:var(--neon-bunny-js-keyword)}.language-javascript .token.literal-property.property,.language-js .token.literal-property.property{color:var(--neon-bunny-js-literal-property);font-style:italic}.language-javascript .token.operator,.language-js .token.operator{color:var(--neon-bunny-js-operator);font-style:italic}.language-javascript .token.punctuation,.language-js .token.punctuation{color:var(--neon-bunny-js-punctuation)}.language-javascript .token.template-string.string,.language-js .token.template-string.string{color:var(--neon-bunny-js-template-string-string)}.language-php .token.boolean{color:var(--neon-bunny-boolean)}.language-php .token.class-name{color:var(--neon-bunny-php-class-name)}.language-php .token.class-name-definition.class-name{color:var(--neon-bunny-php-class-name-definition-class-name)}.language-php .token.class-name .return-type{color:var(--neon-bunny-php-class-name-return-type)}.language-php .token.function{color:var(--neon-bunny-php-function);text-decoration:underline}.language-php .token.keyword{color:var(--neon-bunny-php-keyword)}.language-php .token.keyword.type-hint{color:var(--neon-bunny-php-keyword-type-hint)}.language-php .token.operator{color:var(--neon-bunny-php-operator)}.language-php .token.package{color:var(--neon-bunny-php-package)}.language-php .token.property{color:var(--neon-bunny-php-property)}.language-php .token.punctuation{color:var(--neon-bunny-php-punctuation)}.language-php .token.string.double-quoted-string{color:var(--neon-bunny-php-double-quote-string)}.language-php .token.string.single-quoted-string{color:var(--neon-bunny-php-single-quote-string)}.language-php .token.variable{color:var(--neon-bunny-php-variable)} 2 | -------------------------------------------------------------------------------- /src/plugin/themes/css/min/neon-bunny-prism.css: -------------------------------------------------------------------------------- 1 | :root{--neon-bunny-blue-light: #2492ff;--neon-bunny-blue: #0b93ff;--neon-bunny-gray-lighter: #aaa;--neon-bunny-gray: #7f817e;--neon-bunny-green-darker-darker: #008b05;--neon-bunny-green-darker: #2bb71d;--neon-bunny-green-light: #2bb71d;--neon-bunny-green-neon: #0aff04;--neon-bunny-green: #00d205;--neon-bunny-lime-lighter: #c3e88d;--neon-bunny-lime: #b2ff02;--neon-bunny-magenta: #df00df;--neon-bunny-neon-green: #00ff00;--neon-bunny-orange: #e58100;--neon-bunny-peach-darker: #ffb46a;--neon-bunny-peach-light: #ffe4a6;--neon-bunny-peach: #ffcb6b;--neon-bunny-pink: #ff1190;--neon-bunny-purple-light-dim: #c792ea;--neon-bunny-purple-light: #d285cc;--neon-bunny-purple: #ea03ff;--neon-bunny-red: #ff3229;--neon-bunny-salmon: #ff6f5b;--neon-bunny-teal: #80fcff;--neon-bunny-white: #fff;--neon-bunny-yellow: #fef611;--neon-bunny-atrule: var(--neon-bunny-peach);--neon-bunny-attr-name: var(--neon-bunny-purple-light-dim);--neon-bunny-attr-value: var(--neon-bunny-peach);--neon-bunny-boolean: var(--neon-bunny-blue-light);--neon-bunny-builtin: var(--neon-bunny-purple);--neon-bunny-cdata: var(--neon-bunny-gray);--neon-bunny-char: var(--neon-bunny-pink);--neon-bunny-class-name: var(--neon-bunny-peach);--neon-bunny-comment: var(--neon-bunny-gray);--neon-bunny-constant: var(--neon-bunny-green-darker);--neon-bunny-deleted: var(--neon-bunny-red);--neon-bunny-entity: var(--neon-bunny-peach);--neon-bunny-function: var(--neon-bunny-green-neon);--neon-bunny-important: var(--neon-bunny-red);--neon-bunny-inserted: var(--neon-bunny-lime-lighter);--neon-bunny-keyword: var(--neon-bunny-blue);--neon-bunny-namespace: var(--neon-bunny-peach);--neon-bunny-number: var(--neon-bunny-white);--neon-bunny-operator: var(--neon-bunny-blue-light);--neon-bunny-prolog: var(--neon-bunny-gray);--neon-bunny-property: var(--neon-bunny-pink);--neon-bunny-punctuation: var(--neon-bunny-blue);--neon-bunny-regex: var(--neon-bunny-peach-light);--neon-bunny-string: var(--neon-bunny-green-darker);--neon-bunny-symbol: var(--neon-bunny-pink);--neon-bunny-tag: var(--neon-bunny-blue-light);--neon-bunny-url: var(--neon-bunny-orange);--neon-bunny-variable: var(--neon-bunny-green-darker);--neon-bunny-html-attr-name: var(--neon-bunny-green);--neon-bunny-html-attr-value-punctuation: var(--neon-bunny-peach-darker);--neon-bunny-html-attr-value: var(--neon-bunny-peach-darker);--neon-bunny-css-atrule-rule: var(--neon-bunny-blue);--neon-bunny-css-atrule: var(--neon-bunny-white);--neon-bunny-css-function: var(--neon-bunny-orange);--neon-bunny-css-property: var(--neon-bunny-pink);--neon-bunny-css-punctuation: var(--neon-bunny-white);--neon-bunny-css-selector: var(--neon-bunny-lime);--neon-bunny-css-string: var(--neon-bunny-green-light);--neon-bunny-css: var(--neon-bunny-purple-light-dim);--neon-bunny-js-keyword: var(--neon-bunny-blue);--neon-bunny-js-literal-property: var(--neon-bunny-neon-green);--neon-bunny-js-operator: var(--neon-bunny-blue-light);--neon-bunny-js-punctuation: var(--neon-bunny-white);--neon-bunny-js-string: var(--neon-bunny-green-darker-darker);--neon-bunny-js-template-string-string: var(--neon-bunny-green-darker-darker);--neon-bunny-js: var(--neon-bunny-green-darker);--neon-bunny-php-boolean: var(--neon-bunny-blue);--neon-bunny-php-class-name-definition-class-name: var(--neon-bunny-magenta);--neon-bunny-php-class-name-return-type: var(--neon-bunny-gray-lighter);--neon-bunny-php-class-name: var(--neon-bunny-teal);--neon-bunny-php-double-quote-string: var(--neon-bunny-salmon);--neon-bunny-php-function: var(--neon-bunny-green-neon);--neon-bunny-php-keyword-type-hint: var(--neon-bunny-gray-lighter);--neon-bunny-php-keyword: var(--neon-bunny-blue);--neon-bunny-php-operator: var(--neon-bunny-purple);--neon-bunny-php-package: var(--neon-bunny-peach);--neon-bunny-php-property: var(--neon-bunny-purple-light);--neon-bunny-php-punctuation: var(--neon-bunny-white);--neon-bunny-php-single-quote-string: var(--neon-bunny-yellow);--neon-bunny-php-variable: var(--neon-bunny-purple-light)}code[class*=language-],pre[class*=language-]{-moz-hyphens:none;-moz-tab-size:4;-ms-hyphens:none;-o-tab-size:4;-webkit-hyphens:none;color:var(--neon-bunny-white);font-family:Consolas,Monaco,"Andale Mono","Ubuntu Mono",monospace;font-size:1em;hyphens:none;line-height:1.5;tab-size:4;text-align:left;white-space:pre;word-break:normal;word-spacing:normal;word-wrap:normal}code[class*=language-]::-moz-selection,code[class*=language-] ::-moz-selection,pre[class*=language-]::-moz-selection,pre[class*=language-] ::-moz-selection{background:rgba(29,59,83,.99);text-shadow:none}code[class*=language-]::selection,code[class*=language-] ::selection,pre[class*=language-]::selection,pre[class*=language-] ::selection{background:rgba(255,255,255,.0823529412);text-shadow:none}@media print{code[class*=language-],pre[class*=language-]{text-shadow:none}}pre[class*=language-]{background:#000;color:#fff;margin:.5em 0;overflow:auto;padding:1em}:not(pre)>code[class*=language-]{background:#000;color:#fff;border-radius:.3em;padding:.1em;white-space:normal}.token.atrule{color:var(--neon-bunny-atrule)}.token.attr-name{color:var(--neon-bunny-attr-name)}.token.attr-value{color:var(--neon-bunny-attr-value)}.token.bold{font-weight:bold}.token.boolean{color:var(--neon-bunny-boolean)}.token.builtin{color:var(--neon-bunny-builtin)}.token.cdata{color:var(--neon-bunny-cdata);font-style:italic}.token.char{color:var(--neon-bunny-char)}.token.class-name{color:var(--neon-bunny-class-name)}.token.comment{color:var(--neon-bunny-comment);font-style:italic}.token.constant{color:var(--neon-bunny-constant)}.token.deleted{color:var(--neon-bunny-deleted)}.token.entity{color:var(--neon-bunny-entity)}.token.function{color:var(--neon-bunny-function)}.token.important{color:var(--neon-bunny-important);font-style:italic}.token.inserted{color:var(--neon-bunny-inserted)}.token.italic{font-style:italic}.token.keyword{color:var(--neon-bunny-keyword)}.token.number{color:var(--neon-bunny-number)}.token.operator{color:var(--neon-bunny-operator)}.token.prolog{color:var(--neon-bunny-prolog);font-style:italic}.token.property{color:var(--neon-bunny-property)}.token.punctuation{color:var(--neon-bunny-punctuation)}.token.regex{color:var(--neon-bunny-regex)}.token.string{color:var(--neon-bunny-string)}.token.symbol{color:var(--neon-bunny-symbol)}.token.tag{color:var(--neon-bunny-tag)}.token.url{color:var(--neon-bunny-url);text-decoration:underline}.token.variable{color:var(--neon-bunny-variable)}.namespace{color:var(--neon-bunny-namespace)}.language-html .token.attr-name{color:var(--neon-bunny-html-attr-name)}.language-html .token.attr-value{color:var(--neon-bunny-html-attr-value)}.language-html .token.attr-value .token.punctuation{color:var(--neon-bunny-html-attr-value-punctuation)}.language-css{color:var(--neon-bunny-css) !important}.language-css .token.atrule{color:var(--neon-bunny-css-atrule)}.language-css .token.atrule .token.rule{color:var(--neon-bunny-css-atrule-rule)}.language-css .token.function{color:var(--neon-bunny-css-function)}.language-css .token.property{color:var(--neon-bunny-css-property)}.language-css .token.punctuation{color:var(--neon-bunny-css-punctuation)}.language-css .token.selector{color:var(--neon-bunny-css-selector)}.language-css .token.string{color:var(--neon-bunny-css-string);font-style:italic}.style .token.string{color:var(--neon-bunny-css-string);font-style:italic}.language-javascript,.language-js{color:var(--neon-bunny-js) !important;font-style:italic}.language-javascript span,.language-js span{font-style:normal}.language-javascript .token.keyword,.language-js .token.keyword{color:var(--neon-bunny-js-keyword)}.language-javascript .token.literal-property.property,.language-js .token.literal-property.property{color:var(--neon-bunny-js-literal-property);font-style:italic}.language-javascript .token.operator,.language-js .token.operator{color:var(--neon-bunny-js-operator);font-style:italic}.language-javascript .token.punctuation,.language-js .token.punctuation{color:var(--neon-bunny-js-punctuation)}.language-javascript .token.template-string .token.string,.language-js .token.template-string .token.string{color:var(--neon-bunny-js-template-string-string)}.language-php .token.boolean{color:var(--neon-bunny-boolean)}.language-php .token.class-name{color:var(--neon-bunny-php-class-name)}.language-php .token.class-name-definition.class-name{color:var(--neon-bunny-php-class-name-definition-class-name)}.language-php .token.class-name.return-type{color:var(--neon-bunny-php-class-name-return-type);font-style:italic}.language-php .token.function{color:var(--neon-bunny-php-function)}.language-php .token.keyword{color:var(--neon-bunny-php-keyword)}.language-php .token.keyword.type-hint{color:var(--neon-bunny-php-keyword-type-hint);font-style:italic}.language-php .token.operator{color:var(--neon-bunny-php-operator)}.language-php .token.package{color:var(--neon-bunny-php-package)}.language-php .token.property{color:var(--neon-bunny-php-property)}.language-php .token.punctuation{color:var(--neon-bunny-php-punctuation)}.language-php .token.string.double-quoted-string{color:var(--neon-bunny-php-double-quote-string)}.language-php .token.string.single-quoted-string{color:var(--neon-bunny-php-single-quote-string)}.language-php .token.variable{color:var(--neon-bunny-php-variable)} 2 | -------------------------------------------------------------------------------- /src/plugin/types/index.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-explicit-any */ 2 | /* eslint-disable no-unused-vars */ 3 | import type{ 4 | CSSProperties, 5 | MaybeRef, 6 | } from 'vue'; 7 | import VCodeBlock from '../VCodeBlock.vue'; 8 | 9 | export * from '../index'; 10 | 11 | 12 | // -------------------------------------------------- Types // 13 | export type UseTheme = MaybeRef; 14 | export type CopyStatus = MaybeRef<'copy' | 'success' | 'failed'>; 15 | 16 | 17 | // -------------------------------------------------- Props // 18 | export interface Props { 19 | browserWindow?: boolean; 20 | cssPath?: string | undefined; 21 | code?: object | [] | string | number; 22 | codeBlockRadius?: string; 23 | copyButton?: boolean; 24 | copyIcons?: boolean; 25 | copyTab?: boolean; 26 | copyFailedText?: string; 27 | copyText?: string; 28 | copySuccessText?: string; 29 | floatingTabs?: boolean; 30 | globalOptions?: boolean; 31 | height?: string | number; 32 | highlightjs?: boolean; 33 | indent?: number; 34 | label?: string; 35 | lang?: string; 36 | languages?: string[]; 37 | maxHeight?: string | number; 38 | persistentCopyButton?: boolean; 39 | prismjs?: boolean; 40 | prismPlugin?: boolean; 41 | runTab?: boolean; 42 | runText?: string; 43 | tabGap?: string | number; 44 | tabs?: boolean; 45 | theme?: string | boolean; 46 | }; 47 | 48 | 49 | // -------------------------------------------------- Helpers // 50 | export interface UseConvertToUnit { 51 | ( 52 | options: { 53 | str: string | number | undefined | null, 54 | unit?: string, 55 | } 56 | ): string | void; 57 | } 58 | 59 | 60 | // -------------------------------------------------- Classes // 61 | export interface UseCodeBlockClasses { 62 | ( 63 | options: { 64 | isMobile: MaybeRef; 65 | isPrism: MaybeRef; 66 | } 67 | ): object; 68 | } 69 | 70 | export interface UseCopyButtonClasses { 71 | ( 72 | options: { 73 | copyStatus: CopyStatus; 74 | isMobile: MaybeRef; 75 | persistentCopyButton: MaybeRef; 76 | } 77 | ): object; 78 | } 79 | 80 | export interface UseIconClasses { 81 | ( 82 | options: { 83 | copyStatus: CopyStatus; 84 | highlightjs: MaybeRef; 85 | useTheme: UseTheme; 86 | } 87 | ): object; 88 | } 89 | 90 | export interface UseLabelClasses { 91 | ( 92 | options: { 93 | isMobile: MaybeRef; 94 | } 95 | ): object; 96 | } 97 | 98 | export interface UseTabClasses { 99 | ( 100 | options: { 101 | highlightjs: MaybeRef; 102 | useTheme: UseTheme; 103 | } 104 | ): object; 105 | } 106 | 107 | 108 | // -------------------------------------------------- Styles // 109 | export interface UseCodeTagStyles { 110 | ( 111 | options: { 112 | isLoading: MaybeRef; 113 | useTheme: UseTheme; 114 | } 115 | ): CSSProperties; 116 | } 117 | 118 | export interface UseHeaderStyles { 119 | ( 120 | options: { 121 | floatingTabs: MaybeRef; 122 | tabGap: MaybeRef; 123 | } 124 | ): CSSProperties; 125 | } 126 | 127 | export interface UsePreTagStyles { 128 | ( 129 | options: { 130 | copyTab: MaybeRef; 131 | height: MaybeRef; 132 | maxHeight: MaybeRef; 133 | radius: MaybeRef; 134 | runTab: MaybeRef; 135 | tabs: MaybeRef; 136 | useTheme: UseTheme; 137 | } 138 | ): CSSProperties; 139 | } 140 | 141 | export interface UseTabGroupStyles { 142 | ( 143 | options: { 144 | tabGap: MaybeRef; 145 | } 146 | ): CSSProperties; 147 | } 148 | 149 | 150 | declare module "vue" { 151 | interface ComponentCustomProperties { 152 | 153 | } 154 | 155 | interface GlobalComponents { 156 | VCodeBlock: typeof VCodeBlock; 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /src/plugin/types/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /src/plugin/types/vue-shim.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-explicit-any */ 2 | declare module '*.vue' { 3 | import type { DefineComponent } from 'vue'; 4 | const component: DefineComponent<{}, {}, any>; 5 | export default component; 6 | } 7 | -------------------------------------------------------------------------------- /src/plugin/utils/globals.ts: -------------------------------------------------------------------------------- 1 | export const pluginName = 'v-code-block'; 2 | -------------------------------------------------------------------------------- /src/plugin/utils/props.ts: -------------------------------------------------------------------------------- 1 | 2 | export const AllProps = { 3 | browserWindow: false, 4 | cssPath: undefined, 5 | code: '', 6 | codeBlockRadius: '0.5rem', 7 | copyButton: true, 8 | copyIcons: true, 9 | copyTab: true, 10 | copyFailedText: 'Copy failed!', 11 | copyText: 'Copy Code', 12 | copySuccessText: 'Copied!', 13 | floatingTabs: true, 14 | height: 'auto', 15 | highlightjs: false, 16 | indent: 2, 17 | label: '', 18 | lang: 'javascript', 19 | maxHeight: 'auto', 20 | persistentCopyButton: false, 21 | prismjs: false, 22 | prismPlugin: false, 23 | runTab: false, 24 | runText: 'Run', 25 | tabGap: '0.25rem', 26 | tabs: false, 27 | theme: 'neon-bunny', 28 | }; 29 | -------------------------------------------------------------------------------- /src/plugins/index.ts: -------------------------------------------------------------------------------- 1 | import { loadFonts } from '@/plugins/webfontloader'; 2 | import vuetify from '@/plugins/vuetify'; 3 | 4 | import type { App } from 'vue'; 5 | 6 | export function registerPlugins(app: App) { 7 | loadFonts(); 8 | app.use(vuetify); 9 | } 10 | -------------------------------------------------------------------------------- /src/plugins/theme.ts: -------------------------------------------------------------------------------- 1 | import colors from 'vuetify/lib/util/colors.mjs'; 2 | 3 | export const dark = { 4 | colors: { 5 | accent: '#d00274', 6 | danger: colors.red.base, 7 | error: colors.red.base, 8 | highlightjs: '#600', 9 | info: colors.teal.base, 10 | primary: colors.blue.darken2, 11 | prismjs: '#39a1cf', 12 | secondary: colors.purple.base, 13 | success: colors.green.base, 14 | warning: colors.orange.darken3, 15 | }, 16 | dark: true, 17 | }; 18 | 19 | export const light = { 20 | colors: { 21 | accent: '#905', 22 | danger: colors.red.base, 23 | error: colors.red.base, 24 | highlightjs: '#600', 25 | info: colors.teal.darken1, 26 | primary: colors.blue.base, 27 | prismjs: '#39a1cf', 28 | secondary: colors.purple.lighten1, 29 | success: colors.green.base, 30 | warning: colors.orange.base, 31 | }, 32 | dark: false, 33 | }; 34 | 35 | 36 | export default { 37 | dark, 38 | light, 39 | }; 40 | -------------------------------------------------------------------------------- /src/plugins/vuetify.ts: -------------------------------------------------------------------------------- 1 | import 'vuetify/styles'; 2 | import '@mdi/font/css/materialdesignicons.css'; 3 | import * as components from 'vuetify/components'; 4 | import * as directives from 'vuetify/directives'; 5 | import defaultThemes from './theme'; 6 | import { createVuetify } from 'vuetify'; 7 | import { aliases, mdi } from 'vuetify/iconsets/mdi'; 8 | import { fa } from 'vuetify/iconsets/fa-svg'; 9 | 10 | 11 | export default createVuetify({ 12 | components: { 13 | components, 14 | }, 15 | directives, 16 | icons: { 17 | aliases, 18 | defaultSet: 'mdi', 19 | sets: { 20 | fa, 21 | mdi, 22 | }, 23 | }, 24 | theme: { 25 | defaultTheme: 'dark', 26 | themes: { 27 | ...defaultThemes, 28 | }, 29 | }, 30 | }); 31 | -------------------------------------------------------------------------------- /src/plugins/webfontloader.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * plugins/webfontloader.ts 3 | * 4 | * webfontloader documentation: https://github.com/typekit/webfontloader 5 | */ 6 | 7 | export async function loadFonts() { 8 | const webFontLoader = await import(/* webpackChunkName: "webfontloader" */'webfontloader'); 9 | 10 | webFontLoader.load({ 11 | google: { 12 | families: ['Roboto:100,300,400,500,700,900&display=swap'], 13 | }, 14 | }); 15 | } 16 | -------------------------------------------------------------------------------- /src/stores/menu.ts: -------------------------------------------------------------------------------- 1 | import { defineStore } from 'pinia'; 2 | import { useCoreStore } from './index'; 3 | 4 | 5 | export const useMenuStore = defineStore('menu', () => { 6 | const coreStore = useCoreStore(); 7 | const links = coreStore.links; 8 | 9 | const eventItems = [ 10 | { 11 | href: '#examples', 12 | icon: 'mdi:mdi-code-json', 13 | key: 'languages', 14 | title: 'Languages', 15 | }, 16 | { 17 | href: '#examples-additional-languages', 18 | icon: 'mdi:mdi-code-json', 19 | key: 'add-languages', 20 | title: 'Additional languages', 21 | }, 22 | { 23 | href: '#examples-plugins', 24 | icon: 'mdi:mdi-code-json', 25 | key: 'plugins', 26 | title: 'Plugins', 27 | }, 28 | { 29 | href: '#examples-tabs', 30 | icon: 'mdi:mdi-code-json', 31 | key: 'tabs', 32 | title: 'Tabs', 33 | }, 34 | { 35 | href: '#examples-copy-button', 36 | icon: 'mdi:mdi-code-json', 37 | key: 'copy-button', 38 | title: 'Copy Button', 39 | }, 40 | { 41 | href: '#examples-browser-window', 42 | icon: 'mdi:mdi-code-json', 43 | key: 'browser-window', 44 | title: 'Browser Window', 45 | }, 46 | { 47 | href: '#examples-other-props', 48 | icon: 'mdi:mdi-code-json', 49 | key: 'other-props', 50 | title: 'Other Props', 51 | }, 52 | ]; 53 | 54 | const libraryItems = [ 55 | { 56 | key: 'highlightjs', 57 | link: links.highlightjs, 58 | src: 'https://www.google.com/s2/favicons?sz=64&domain=highlightjs.org', 59 | title: 'Highlight.js', 60 | }, 61 | { 62 | icon: 'mdi:mdi-triangle', 63 | key: 'prismjs', 64 | link: links.prismjs, 65 | title: 'PrismJS', 66 | }, 67 | ]; 68 | 69 | const menuItems = [ 70 | { 71 | href: '#home', 72 | icon: 'mdi:mdi-home', 73 | title: 'Home', 74 | }, 75 | { 76 | href: '#installation', 77 | icon: 'mdi:mdi-plus-thick', 78 | title: 'Installation', 79 | }, 80 | { 81 | href: '#description', 82 | icon: 'mdi:mdi-information-outline', 83 | title: 'Description', 84 | }, 85 | { 86 | href: '#usage', 87 | icon: 'mdi:mdi-power-plug-outline', 88 | title: 'Usage', 89 | }, 90 | { 91 | href: '#props', 92 | icon: 'mdi:mdi-cog', 93 | title: 'Props', 94 | }, 95 | { 96 | href: '#events', 97 | icon: 'mdi:mdi-calendar-star', 98 | title: 'Events', 99 | }, 100 | { 101 | href: '#slots', 102 | icon: 'mdi:mdi-slot-machine', 103 | title: 'Slots', 104 | }, 105 | { 106 | href: '#examples', 107 | icon: 'mdi:mdi-code-json', 108 | items: [...eventItems], 109 | key: 'examples', 110 | title: 'Examples', 111 | }, 112 | // { 113 | // href: '#examples', 114 | // icon: 'mdi:mdi-code-json', 115 | // title: 'Examples', 116 | // }, 117 | { 118 | href: '#playground', 119 | icon: 'mdi:mdi-seesaw', 120 | title: 'Playground', 121 | }, 122 | { 123 | href: '#dependencies', 124 | icon: 'mdi:mdi-asterisk-circle-outline', 125 | title: 'Dependencies', 126 | }, 127 | { 128 | href: '#license', 129 | icon: 'mdi:mdi-card-account-details-outline', 130 | title: 'License', 131 | }, 132 | ]; 133 | 134 | return { 135 | libraryItems, 136 | menuItems, 137 | }; 138 | }); 139 | -------------------------------------------------------------------------------- /src/stores/props.ts: -------------------------------------------------------------------------------- 1 | import { defineStore } from 'pinia'; 2 | // @ts-expect-error @ts-ignore 3 | import packageJson from '@root/package.json'; 4 | 5 | const dependencies = packageJson.dependencies; 6 | const prismVersion = dependencies.prismjs.replace('^', ''); 7 | const highlightJsVersion = dependencies['highlight.js'].replace('^', ''); 8 | 9 | const highlightJsLinks = { 10 | cdn: `https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@${highlightJsVersion}/build/styles/`, 11 | homepage: 'https://highlightjs.org/', 12 | themes: `https://github.com/highlightjs/highlight.js/tree/${highlightJsVersion}/src/styles`, 13 | }; 14 | const prismLinks = { 15 | cdn: `https://cdn.jsdelivr.net/gh/PrismJS/prism@${prismVersion}/themes/`, 16 | homepage: 'https://prismjs.com/', 17 | prismThemes: `https://github.com/PrismJS/prism/tree/v${prismVersion}/themes`, 18 | themes: 'https://github.com/PrismJS/prism-themes', 19 | }; 20 | 21 | 22 | const propsHeaders = [ 23 | { 24 | align: 'start', 25 | filterable: true, 26 | key: 'name', 27 | sortable: true, 28 | title: 'Name', 29 | width: '20%', 30 | }, 31 | { 32 | align: 'start', 33 | filterable: false, 34 | key: 'type', 35 | sortable: false, 36 | title: 'Type', 37 | width: '15%', 38 | }, 39 | { 40 | align: 'start', 41 | filterable: false, 42 | key: 'default', 43 | sortable: false, 44 | title: 'Default', 45 | width: '10%', 46 | }, 47 | { 48 | align: 'start', 49 | filterable: false, 50 | key: 'desc', 51 | sortable: false, 52 | title: 'Description', 53 | }, 54 | ]; 55 | 56 | const componentProps = [ 57 | { 58 | default: 'false', 59 | desc: 'To give the code block a browser window look.', 60 | name: 'browserWindow', 61 | required: false, 62 | type: 'boolean', 63 | }, 64 | { 65 | default: 'undefined', 66 | desc: 'Used to load the css from a specified location instead of the component using the CDN location. If you want to dynamically change the theme, you will also need to change the theme prop.', 67 | name: 'cssPath', 68 | required: false, 69 | type: 'string | undefined', 70 | }, 71 | { 72 | default: '', 73 | desc: 'The code to be displayed in the code block.', 74 | name: 'code', 75 | required: true, 76 | type: 'object | string | number', 77 | }, 78 | { 79 | default: '0.5rem', 80 | desc: 'The border radius of the code block.', 81 | name: 'codeBlockRadius', 82 | required: false, 83 | type: 'string', 84 | }, 85 | { 86 | default: 'true', 87 | desc: 'To show a copy button within the code block.', 88 | name: 'copyButton', 89 | required: false, 90 | type: 'boolean', 91 | }, 92 | { 93 | default: 'true', 94 | desc: 'To show the copy icons on the copy code tab.', 95 | name: 'copyIcons', 96 | required: false, 97 | type: 'boolean', 98 | }, 99 | { 100 | default: 'true?', 101 | desc: 'To show the copy code tab.', 102 | name: 'copyTab', 103 | required: false, 104 | type: 'boolean', 105 | }, 106 | { 107 | default: 'Copy failed!', 108 | desc: 'The text to be displayed when the code copy failed.', 109 | name: 'copyFailedText', 110 | required: false, 111 | type: 'string', 112 | }, 113 | { 114 | default: 'Copy Code', 115 | desc: 'The text to be displayed on the copy tab.', 116 | name: 'copyText', 117 | required: false, 118 | type: 'string', 119 | }, 120 | { 121 | default: 'Copied!', 122 | desc: 'The text to be displayed when the code copy was successful.', 123 | name: 'copySuccessText', 124 | required: false, 125 | type: 'string', 126 | }, 127 | { 128 | default: 'true', 129 | desc: 'To make the tabs float on top of the code block.', 130 | name: 'floatingTabs', 131 | required: false, 132 | type: 'boolean', 133 | }, 134 | { 135 | default: 'auto', 136 | desc: 'The height of the code block.', 137 | name: 'height', 138 | required: false, 139 | type: 'string | number', 140 | }, 141 | { 142 | default: 'false', 143 | desc: 'Specifies that the Highlight.js library should be used. * Required if prismjs prop is not set.', 144 | name: 'highlightjs', 145 | required: false, 146 | type: 'boolean', 147 | }, 148 | { 149 | default: '4', 150 | desc: 'The number of spaces to indent the code for json.', 151 | name: 'indent', 152 | required: false, 153 | type: 'number', 154 | }, 155 | { 156 | default: '', 157 | desc: 'The label to be displayed on the code block.', 158 | name: 'label', 159 | required: false, 160 | type: 'string', 161 | }, 162 | { 163 | default: 'javascript', 164 | desc: 'The language of the code.', 165 | name: 'lang', 166 | required: false, 167 | type: 'string', 168 | }, 169 | { 170 | default: 'auto', 171 | desc: 'The max height of the code block.', 172 | name: 'maxHeight', 173 | required: false, 174 | type: 'string | number', 175 | }, 176 | { 177 | default: 'false', 178 | desc: 'To show a persistent copy button within the code block.', 179 | name: 'persistentCopyButton', 180 | required: false, 181 | type: 'boolean', 182 | }, 183 | { 184 | default: 'true', 185 | desc: 'Specifies that the PrismJS library should be used. * Required if highlightjs prop is not set.', 186 | name: 'prismjs', 187 | required: false, 188 | type: 'boolean', 189 | }, 190 | { 191 | default: 'false', 192 | desc: 'Specifies that a PrismJS plugin is being used. This is needed for the plugin to work properly.', 193 | name: 'prismPlugin', 194 | required: false, 195 | type: 'boolean', 196 | }, 197 | { 198 | default: 'false', 199 | desc: 'To show the run tab.', 200 | name: 'runTab', 201 | required: false, 202 | type: 'boolean', 203 | }, 204 | { 205 | default: 'Run', 206 | desc: 'The text to be displayed on the run tab.', 207 | name: 'runText', 208 | required: false, 209 | type: 'string', 210 | }, 211 | { 212 | default: '0.25rem', 213 | desc: 'The gap between the tabs.', 214 | name: 'tabGap', 215 | required: false, 216 | type: 'string', 217 | }, 218 | { 219 | default: 'false', 220 | desc: 'To show the tabs. This will show/hide all tabs.', 221 | name: 'tabs', 222 | required: false, 223 | type: 'boolean', 224 | }, 225 | { 226 | default: 'neon-bunny', 227 | desc: `

The theme to be used for the code block. Available options include:

228 |

229 | false - When you are loading the theme's styles yourself. 230 |

231 |

232 | Neon Bunny Themes 233 |
234 | neon-bunny 235 |
236 | neon-bunny-carrot 237 |

238 |

239 | PrismJS Themes 240 |

241 |

242 | Highlight.js Themes 243 |

244 | `, 245 | name: 'theme', 246 | required: false, 247 | type: 'string | boolean', 248 | }, 249 | ]; 250 | 251 | // eslint-disable-next-line no-unused-vars, @typescript-eslint/no-unused-vars 252 | const componentProps2 = { 253 | // theme: { 254 | // type: 'string | boolean', 255 | // required: false, 256 | // id: 'props-theme-option', 257 | // default: 'neon-bunny', 258 | // description: `

The theme to be used for the code block. Available options include:

259 | //

260 | // false - When you are loading the theme's styles yourself. 261 | //

262 | //

263 | // Neon Bunny Themes 264 | //
265 | // neon-bunny 266 | //
267 | // neon-bunny-carrot 268 | //

269 | //

270 | // PrismJS Themes 271 | //

272 | //

273 | // Highlight.js Themes 274 | //

275 | // `, 276 | // } 277 | }; 278 | 279 | 280 | export const usePropsStore = defineStore('props', { 281 | state: () => { 282 | return { 283 | componentProps, 284 | highlightJsLinks, 285 | prismLinks, 286 | propsHeaders, 287 | }; 288 | }, 289 | }); 290 | -------------------------------------------------------------------------------- /src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /tsconfig.dist.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "include": [ 4 | "src/plugin/**/*.ts", 5 | "src/plugin/**/*.vue" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowSyntheticDefaultImports": true, 4 | "baseUrl": "./", 5 | "declaration": true, 6 | "declarationDir": "./dist", 7 | "esModuleInterop": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "importHelpers": true, 10 | "jsx": "preserve", 11 | "lib": [ 12 | "ESNext", 13 | "DOM" 14 | ], 15 | "module": "ESNext", 16 | "moduleResolution": "Node", 17 | "noEmit": true, 18 | "noFallthroughCasesInSwitch": true, 19 | "noImplicitAny": false, 20 | "noUncheckedIndexedAccess": true, 21 | "noUnusedLocals": true, 22 | "noUnusedParameters": true, 23 | "outDir": "./dist", 24 | "paths": { 25 | "@/*": [ 26 | "./src/*" 27 | ] 28 | }, 29 | "resolveJsonModule": true, 30 | "rootDir": "src", 31 | "skipLibCheck": true, 32 | "strict": true, 33 | "target": "ESNext", 34 | "typeRoots": [ 35 | "./src/plugin/types", 36 | "./node_modules/@types" 37 | ], 38 | "types": [ 39 | "node" 40 | ], 41 | "useDefineForClassFields": true 42 | }, 43 | "exclude": [ 44 | "src/**/*.spec.ts", 45 | "src/**/*.test.ts", 46 | "src/**/*.test.ts.snap", 47 | "node_modules" 48 | ], 49 | "include": [ 50 | "src/playground/configs/templates/PlaygroundPage.ts.vue", 51 | "src/playground/PlaygroundPage.ts.vue", 52 | "src/plugin/**/*.ts", 53 | "src/plugin/**/*.tsx", 54 | "src/plugin/**/*.vue" 55 | ], 56 | "references": [ 57 | { 58 | "path": "./tsconfig.node.json" 59 | } 60 | ] 61 | } 62 | -------------------------------------------------------------------------------- /tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "module": "ESNext", 5 | "moduleResolution": "Node", 6 | "allowSyntheticDefaultImports": true 7 | }, 8 | "include": ["vite.config.mts"] 9 | } 10 | -------------------------------------------------------------------------------- /vite.build.config.mts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | import * as path from 'path'; 3 | import AutoImport from 'unplugin-auto-import/vite'; 4 | import commonjs from '@rollup/plugin-commonjs'; 5 | import cssInjectedByJsPlugin from 'vite-plugin-css-injected-by-js'; 6 | import dts from 'vite-plugin-dts'; 7 | import pkg from './package.json'; 8 | import terser from '@rollup/plugin-terser'; 9 | import typescript from 'rollup-plugin-typescript2'; 10 | import vue from '@vitejs/plugin-vue'; 11 | import { viteStaticCopy } from 'vite-plugin-static-copy'; 12 | 13 | const scopedPackageName = pkg.name; 14 | const packageName = scopedPackageName.split('/')[1]; 15 | 16 | const banner = `/** 17 | * @name ${scopedPackageName} 18 | * @version ${pkg.version} 19 | * @description ${pkg.description} 20 | * @author ${pkg.author} 21 | * @copyright Copyright ${new Date().getFullYear()}, WebDevNerdStuff 22 | * @homepage ${pkg.homepage} 23 | * @repository ${pkg.repository} 24 | * @license ${pkg.license} License 25 | */ 26 | `; 27 | 28 | export default defineConfig({ 29 | publicDir: false, 30 | build: { 31 | lib: { 32 | entry: './src/plugin/index.ts', 33 | name: packageName, 34 | formats: ['es', 'cjs'], 35 | fileName: format => `${packageName}.${format}.js`, 36 | }, 37 | rollupOptions: { 38 | input: { 39 | main: path.resolve(__dirname, './src/plugin/index.ts') 40 | }, 41 | external: [ 42 | ...Object.keys(pkg.dependencies || {}), 43 | ], 44 | output: { 45 | banner, 46 | exports: 'named', 47 | }, 48 | }, 49 | }, 50 | plugins: [ 51 | commonjs(), 52 | AutoImport({ 53 | dts: false, 54 | imports: [ 55 | 'vue', 56 | { 57 | vue: ['CSSProperties'] 58 | } 59 | ], 60 | vueTemplate: true, 61 | }), 62 | vue({ 63 | script: { 64 | defineModel: true, 65 | }, 66 | }), 67 | dts({ 68 | insertTypesEntry: true, 69 | }), 70 | typescript({ 71 | check: true, 72 | include: ['./src/plugin/**/*.vue'], 73 | }), 74 | cssInjectedByJsPlugin({ topExecutionPriority: false }), 75 | viteStaticCopy({ 76 | targets: [ 77 | { 78 | src: 'src/plugin/styles/*', 79 | dest: 'scss', 80 | }, 81 | ] 82 | }), 83 | terser(), 84 | ], 85 | resolve: { 86 | alias: { 87 | '@': path.resolve(__dirname, './src'), 88 | '@root': path.resolve(__dirname, './') 89 | }, 90 | extensions: [ 91 | '.js', 92 | '.json', 93 | '.jsx', 94 | '.mjs', 95 | '.ts', 96 | '.tsx', 97 | '.vue', 98 | ], 99 | }, 100 | }); 101 | -------------------------------------------------------------------------------- /vite.config.mts: -------------------------------------------------------------------------------- 1 | import vue from '@vitejs/plugin-vue'; 2 | import vuetify, { transformAssetUrls } from 'vite-plugin-vuetify'; 3 | import eslint from 'vite-plugin-eslint'; 4 | import stylelint from 'vite-plugin-stylelint'; 5 | import { defineConfig } from 'vite'; 6 | import { fileURLToPath, URL } from 'node:url'; 7 | import AutoImport from 'unplugin-auto-import/vite'; 8 | 9 | const baseUrl = '/vue-code-block/'; 10 | const playgroundUrl = baseUrl + 'playground/'; 11 | 12 | 13 | export default defineConfig({ 14 | base: baseUrl, 15 | build: { 16 | outDir: 'docs', 17 | }, 18 | plugins: [ 19 | eslint({ 20 | fix: true, 21 | }), 22 | stylelint({ 23 | cache: false, 24 | fix: true, 25 | include: [ 26 | 'src/**/*.{css,scss,sass,vue}', 27 | './src/components/**/*.{css,scss,sass,vue}', 28 | './src/plugin/styles/*.{css,scss,sass}' 29 | ], 30 | }), 31 | AutoImport({ 32 | dts: false, 33 | imports: [ 34 | 'vue', 35 | { 36 | vue: ['CSSProperties'], 37 | } 38 | ], 39 | vueTemplate: true, 40 | }), 41 | vue({ 42 | template: { transformAssetUrls }, 43 | }), 44 | vuetify({ 45 | autoImport: true, 46 | }), 47 | ], 48 | resolve: { 49 | alias: { 50 | '@': fileURLToPath(new URL('./src', import.meta.url)), 51 | '@root': fileURLToPath(new URL('.', import.meta.url)), 52 | }, 53 | extensions: [ 54 | '.js', 55 | '.json', 56 | '.jsx', 57 | '.mjs', 58 | '.mts', 59 | '.ts', 60 | '.tsx', 61 | '.vue', 62 | ], 63 | }, 64 | server: { 65 | hmr: { 66 | protocol: 'ws', 67 | }, 68 | open: process?.env?.NODE_ENV === 'playground' ? playgroundUrl : false, 69 | }, 70 | }); 71 | 72 | export const assetAttrsConfig: Record = { 73 | link: ['href'], 74 | video: ['src', 'poster'], 75 | source: ['src', 'srcset'], 76 | img: ['src', 'srcset'], 77 | image: ['xlink:href', 'href'], 78 | use: ['xlink:href', 'href'] 79 | }; 80 | -------------------------------------------------------------------------------- /vitest.config.mts: -------------------------------------------------------------------------------- 1 | import { fileURLToPath } from 'node:url'; 2 | import { mergeConfig, defineConfig, configDefaults } from 'vitest/config'; 3 | import viteConfig from './vite.config.mts'; 4 | 5 | export default mergeConfig( 6 | viteConfig, 7 | defineConfig({ 8 | test: { 9 | environment: 'jsdom', 10 | exclude: [ 11 | ...configDefaults.exclude, 12 | ], 13 | root: fileURLToPath(new URL('./', import.meta.url)), 14 | server: { 15 | deps: { 16 | inline: ['element-plus'] 17 | } 18 | }, 19 | } 20 | }) 21 | ); 22 | --------------------------------------------------------------------------------