├── .gitignore ├── LICENSE.md ├── codebox.d.ts ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md ├── PULL_REQUEST_TEMPLATE.md └── CONTRIBUTING.md ├── tsconfig.json ├── .eslintrc.js ├── webpack.config.js ├── package.json ├── src ├── style.css └── index.ts ├── CODE_OF_CONDUCT.md ├── readme.md └── dist └── index.min.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | test.js 3 | *~ 4 | .env* 5 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dev-juju/codebox/HEAD/LICENSE.md -------------------------------------------------------------------------------- /codebox.d.ts: -------------------------------------------------------------------------------- 1 | declare global { 2 | interface Window { 3 | hljs: any 4 | } 5 | } 6 | 7 | export {}; 8 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: dev-juju 4 | patreon: bomdisoft 5 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "strict": false, 12 | "forceConsistentCasingInFileNames": true, 13 | "noEmit": true, 14 | "esModuleInterop": true, 15 | "module": "esnext", 16 | "moduleResolution": "node", 17 | "resolveJsonModule": true, 18 | "isolatedModules": true, 19 | "jsx": "preserve", 20 | "incremental": true, 21 | 22 | "outDir": "./dist/" 23 | }, 24 | "include": [ 25 | "codebox.d.ts", 26 | "**/*.ts" 27 | ], 28 | "exclude": [ 29 | "node_modules" 30 | ] 31 | } 32 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: 'babel-eslint', 3 | env: { 4 | browser: true, 5 | commonjs: true, 6 | es6: true, 7 | node: true, 8 | jest: true, 9 | mocha: true, 10 | }, 11 | globals: { 12 | React: false, 13 | shallow: false, 14 | store: false, 15 | workbox: false, 16 | }, 17 | extends: ['eslint:recommended', 'plugin:react/recommended'], 18 | parserOptions: { 19 | ecmaVersion: 8, 20 | sourceType: 'module' 21 | }, 22 | plugins: ['react'], 23 | rules: { 24 | indent: ['error', 2, { SwitchCase: 1 }], 25 | 'linebreak-style': ['error', 'windows'], 26 | quotes: ['error', 'single'], 27 | semi: ['error', 'always'], 28 | 'no-console': 'off', 29 | 'no-unused-vars': ['error', { args: 'none' }] 30 | } 31 | }; 32 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | module.exports = { 4 | mode: 'production', 5 | entry: path.normalize(`${__dirname}/src/index.ts`), 6 | output: { 7 | path: path.normalize(`${__dirname}/dist`), 8 | publicPath: '/', 9 | filename: 'index.min.js', 10 | library: 'CodeBox', 11 | libraryTarget: 'umd' 12 | }, 13 | module: { 14 | rules: [ 15 | { 16 | test: /\.(js|ts)$/, 17 | use: [ 18 | { 19 | loader: 'babel-loader', 20 | options: { 21 | presets: ['@babel/preset-env'], 22 | }, 23 | }, 24 | { 25 | loader: 'ts-loader', 26 | options: { 27 | compilerOptions: { 28 | noEmit: false, 29 | }, 30 | }, 31 | } 32 | ], 33 | exclude: /node_modules/, 34 | }, 35 | { 36 | test: /\.css$/, 37 | use: [ 38 | 'style-loader', 39 | 'css-loader' 40 | ] 41 | } 42 | ] 43 | }, 44 | resolve: { 45 | extensions: ['.ts', '.js'], 46 | }, 47 | }; 48 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@bomdi/codebox", 3 | "version": "2.0.0", 4 | "license": "ISC", 5 | "description": "Code syntax highlighting tool for Editor.js", 6 | "keywords": [ 7 | "Editor.js", 8 | "Code", 9 | "Code Syntax", 10 | "Code highlight", 11 | "Syntax highlight" 12 | ], 13 | "author": "Adombang Munang Mbomndih (https://bomdisoft.com)", 14 | "contributors": [], 15 | "repository": { 16 | "type": "git", 17 | "url": "git+https://github.com/dev-juju/codebox.git" 18 | }, 19 | "scripts": { 20 | "build": "webpack --progress", 21 | "build:dev": "webpack --mode development --watch", 22 | "start": "node test.js" 23 | }, 24 | "devDependencies": { 25 | "@babel/core": "7.19.0", 26 | "@babel/node": "^7.18.10", 27 | "@babel/preset-env": "7.19.0", 28 | "@types/node": "^18.7.15", 29 | "babel-eslint": "^10.0.3", 30 | "babel-loader": "8.2.5", 31 | "css-loader": "^6.7.1", 32 | "eslint": "^8.23.0", 33 | "style-loader": "^3.3.1", 34 | "ts-loader": "^9.3.1", 35 | "typescript": "^4.8.2", 36 | "webpack": "^5.74.0", 37 | "webpack-cli": "^4.10.0" 38 | }, 39 | "files": [ 40 | "dist" 41 | ], 42 | "main": "dist/index.min.js" 43 | } 44 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 10 | 11 | ### 🤔 This is a ... 12 | 13 | - [ ] New feature 14 | - [ ] Bug fix 15 | - [ ] Site / document update 16 | - [ ] Component style update 17 | - [ ] Refactoring 18 | - [ ] Code style optimization 19 | - [ ] Test Case 20 | - [ ] Branch merge 21 | - [ ] Other (what?) 22 | 23 | ### 🔗 Related issue link 24 | 25 | 28 | 29 | ### 💡 Background and solution 30 | 31 | 36 | 37 | ### 📝 Changelog 38 | 39 | 42 | 43 | | Language | Changelog | 44 | | ---------- | --------- | 45 | | 🇺🇸 English | | 46 | 47 | ### ☑️ Self Check before Merge 48 | 49 | - [ ] Doc is updated/provided or not needed 50 | - [ ] Demo/Example is updated/provided or not needed 51 | - [ ] Changelog is provided or not needed 52 | -------------------------------------------------------------------------------- /src/style.css: -------------------------------------------------------------------------------- 1 | .codeBoxHolder{ 2 | display: flex; 3 | flex-direction: column; 4 | justify-content: flex-start; 5 | align-items: flex-start; 6 | } 7 | 8 | .codeBoxTextArea{ 9 | width: 100%; 10 | min-height: 30px; 11 | padding: 10px; 12 | border-radius: 2px 2px 2px 0; 13 | border: none !important; 14 | outline: none !important; 15 | font: 14px monospace; 16 | } 17 | 18 | .codeBoxSelectDiv{ 19 | display: flex; 20 | flex-direction: column; 21 | justify-content: flex-start; 22 | align-items: flex-start; 23 | position: relative; 24 | } 25 | 26 | .codeBoxSelectInput{ 27 | border-radius: 0 0 20px 2px; 28 | padding: 2px 26px; 29 | padding-top: 0; 30 | padding-right: 0; 31 | text-align: left; 32 | cursor: pointer; 33 | border: none !important; 34 | outline: none !important; 35 | } 36 | 37 | .codeBoxSelectDropIcon{ 38 | position: absolute !important; 39 | left: 10px !important; 40 | bottom: 0 !important; 41 | width: unset !important; 42 | height: unset !important; 43 | font-size: 16px !important; 44 | } 45 | 46 | .codeBoxSelectPreview{ 47 | display: none; 48 | flex-direction: column; 49 | justify-content: flex-start; 50 | align-items: flex-start; 51 | border-radius: 2px; 52 | box-shadow: 0 3px 15px -3px rgba(13,20,33,.13); 53 | position: absolute; 54 | top: 100%; 55 | margin: 5px 0; 56 | max-height: 30vh; 57 | overflow-x: hidden; 58 | overflow-y: auto; 59 | z-index: 10000; 60 | } 61 | 62 | .codeBoxSelectItem{ 63 | width: 100%; 64 | padding: 5px 20px; 65 | margin: 0; 66 | cursor: pointer; 67 | } 68 | 69 | .codeBoxSelectItem:hover{ 70 | opacity: 0.7; 71 | } 72 | 73 | .codeBoxSelectedItem{ 74 | background-color: lightblue !important; 75 | } 76 | 77 | .codeBoxShow{ 78 | display: flex !important; 79 | } 80 | 81 | .dark{ 82 | color: #abb2bf; 83 | background-color: #282c34; 84 | } 85 | 86 | .light{ 87 | color: #383a42; 88 | background-color: #fafafa; 89 | } 90 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to CodeBox 2 | 3 | The following is a set of guidelines for contributing to CodeBox. Please spend several minutes reading these guidelines before you create an issue or pull request. 4 | 5 | ## Code of Conduct 6 | We have adopted a Code of Conduct that we expect project participants to adhere to. Please read the full text so that you can understand what actions will and will not be tolerated. 7 | 8 | ## Open Development 9 | All work on CodeBox happens directly on GitHub. Both core team members and external contributors send pull requests which go through the same review process. 10 | 11 | ## Branch Organization 12 | According to our release schedule, we maintain two branches, master and feature. If you send a bugfix pull request, please do it against the master branch, if it's a feature pull request, please do it against the feature branch. 13 | 14 | ## Bugs 15 | We are using GitHub Issues for bug tracking. The best way to get your bug fixed is using our issue helper and provide reproduction steps with this template. 16 | 17 | Before you report a bug, please make sure you've searched exists issues, and read our FAQ. 18 | 19 | ## Proposing a Change 20 | If you intend to change the public API or introduce new feature, we also recommend you use our issue helper to create a feature request issue. 21 | 22 | If you want to help on new API, please follow our API Naming Rules. 23 | 24 | ## Your First Pull Request 25 | Working on your first Pull Request? You can learn how from this free video series: 26 | 27 | [How to Contribute to an Open Source Project on GitHub](https://egghead.io/courses/how-to-contribute-to-an-open-source-project-on-github) 28 | 29 | 30 | ## Sending a Pull Request 31 | The core team is monitoring for pull requests. We will review your pull request and either merge it, request changes to it, or close it with an explanation. 32 | 33 | **Before submitting a pull request**, please make sure the following is done: 34 | 35 | 1) Fork the repository and create your branch from the correct branch. 36 | 37 | 2) Run npm install in the repository root. 38 | 39 | 3) If you've fixed a bug or added code that should be tested, add tests! 40 | 41 | 4) Ensure the test suite passes (npm run test). Tip: npm test -- --watch TestName is helpful in development. 42 | 43 | 5) Run npm test -- -u to update the jest snapshots and commit these changes as well (if there are any updates). 44 | 45 | 6) Make sure you follow our style guide (take a look at .eslint.js in the root directory). 46 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at support@bomdisoft.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # CodeBox 2 | 3 | [![](https://img.shields.io/npm/v/@bomdi/codebox)](https://www.npmjs.com/package/@bomdi/codebox) 4 | [![](https://img.shields.io/npm/dw/@bomdi/codebox)](https://www.npmjs.com/package/@bomdi/codebox) 5 | [![](https://flat.badgen.net/npm/license/@bomdi/codebox)](https://www.npmjs.com/package/@bomdi/codebox) 6 | [![](https://flat.badgen.net/badge/icon/typescript?icon=typescript&label)](https://www.npmjs.com/package/@bomdi/codebox) 7 | 8 | Code syntax highlighting tool for [Editor.js](https://editorjs.io/) 9 | 10 | ## Setup 11 | 12 | Install the package via NPM 13 | 14 | ```shell 15 | npm i @bomdi/codebox 16 | ``` 17 | 18 | Add to your module/application 19 | 20 | ```javascript 21 | import CodeBox from '@bomdi/codebox'; 22 | OR 23 | const CodeBox = require('@bomdi/codebox'); 24 | ``` 25 | 26 | 27 | ## Usage 28 | 29 | Add CodeBox `tools` property of the CodeX Editor initial config. 30 | 31 | ```javascript 32 | const CodexEditor = require('@editorjs/editorjs'); 33 | 34 | let editor = new CodexEditor({ 35 | ... 36 | 37 | tools: { 38 | ... 39 | codeBox: { 40 | class: CodeBox, 41 | config: { 42 | themeURL: 'https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@9.18.1/build/styles/dracula.min.css', // Optional 43 | themeName: 'atom-one-dark', // Optional 44 | useDefaultTheme: 'light' // Optional. This also determines the background color of the language select drop-down 45 | } 46 | }, 47 | } 48 | 49 | ... 50 | }); 51 | ``` 52 | 53 | ## Config Params 54 | 55 | **All parameters are optional** 56 | 57 | | Field | Type | Description | 58 | | ------------------ | -------- | ------------------------------| 59 | | themeURL | `string` | URL pointing to CSS file that can be used by [highlight.js](https://highlightjs.org/). This could also point o your own custom CSS syntax file. If **themeURL** is provided, **themeName** and **useDefaultTheme** will be ignored | 60 | | themeName | `string` | Any one of the [accepted theme names](https://github.com/highlightjs/highlight.js/tree/master/src/styles) used by **highlight.js**. Only the name is required, not the full URL (example "a11y-dark"). If **themeName** is provided, **useDefaultTheme** will be ignored | 61 | | useDefaultTheme | `string` | **CodeBox** has 2 default themes - 1 light and 1 dark. You can specify what default should be applied by passing the string **'light'** or **'dark'** to this parameter. Note that setting **themeURL** or **themeName** overrides this setting for the main code block, however, the background of the language select element/dropdown menu is set by this value | 62 | 63 | If no parameters are specified, CodeBox defaults to a dark theme. 64 | 65 | ## Output data 66 | 67 | CodeBox returns the following data 68 | 69 | ```json 70 | { 71 | "type": "codeBox", 72 | "data": { 73 | "code": "consttest = newTest();.codeBoxTextArea{\n width: 100%;\n min-height: 30px;\n padding: 10px;\n border-radius: 2px 2px 2px 0;\n border: none !important;\n outline: none !important;\n font: 14px monospace;\n}\n\n.codeBoxSelectDiv{\n display: flex;\n flex-direction: column;\n justify-content: flex-start;\n align-items: flex-start;\n position: relative;\n}", 74 | "language": "css", 75 | "theme": "https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@9.18.1/build/styles/atom-one-dark.min.css" 76 | } 77 | } 78 | ``` 79 | 80 | ## Render saved data as component 81 | 82 | If you use react, you can pass your saved data to the library [editorjs-react-renderer](https://www.npmjs.com/package/editorjs-react-renderer) to render a code block component 83 | 84 | ```javascript 85 | import { CodeBoxOutput } from 'editorjs-react-renderer'; 86 | 87 | const data = { 88 | "type": "codeBox", 89 | "data": { 90 | "code": ".codeBoxTextArea{\n width: 100%;\n min-height: 30px;\n padding: 10px;\n border-radius: 2px 2px 2px 0;\n border: none !important;\n outline: none !important;\n font: 14px monospace;\n}\n\n.codeBoxSelectDiv{\n display: flex;\n flex-direction: column;\n justify-content: flex-start;\n align-items: flex-start;\n position: relative;\n}", 91 | "language": "css", 92 | "theme": "https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@9.18.1/build/styles/atom-one-dark.min.css" 93 | } 94 | }; 95 | 96 | const CodeBlock = () => CodeBoxOutput(data); 97 | 98 | export default CodeBlock; 99 | ``` 100 | 101 | 102 | ## Author 103 | 104 | Dev Juju 105 | 106 | [Contact Us](https://bomdisoft.com) 107 | 108 | [Learn](https://devjuju.com) 109 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | /** CodeBox 2 | * 3 | * Code syntax highlighting tool for Editor.js 4 | * 5 | * @version 1.0.0 6 | * @created - 2020.02.12 7 | * @author - Adombang Munang Mbomndih (Bomdi) (https://bomdisoft.com) 8 | * 9 | * Version History 10 | * --------------- 11 | * @version 2.0.0 - 2022.09.06 - Use TypeScript - Adombang Munang Mbomndih 12 | */ 13 | 14 | 15 | //#region imports 16 | require('./style.css').toString(); 17 | //#endregion 18 | 19 | const DEFAULT_THEMES = ['light', 'dark']; 20 | const COMMON_LANGUAGES = { 21 | none: 'Auto-detect', apache: 'Apache', bash: 'Bash', cs: 'C#', cpp: 'C++', css: 'CSS', coffeescript: 'CoffeeScript', diff: 'Diff', 22 | go: 'Go', html: 'HTML, XML', http: 'HTTP', json: 'JSON', java: 'Java', javascript: 'JavaScript', kotlin: 'Kotlin', 23 | less: 'Less', lua: 'Lua', makefile: 'Makefile', markdown: 'Markdown', nginx: 'Nginx', objectivec: 'Objective-C', 24 | php: 'PHP', perl: 'Perl', properties: 'Properties', python: 'Python', ruby: 'Ruby', rust: 'Rust', scss: 'SCSS', 25 | sql: 'SQL', shell: 'Shell Session', swift: 'Swift', toml: 'TOML, also INI', typescript: 'TypeScript', yaml: 'YAML', 26 | plaintext: 'Plaintext' 27 | }; 28 | 29 | class CodeBox { 30 | api: any; 31 | config: { themeName: any; themeURL: any; useDefaultTheme: any; }; 32 | data: { code: any; language: any; theme: any; }; 33 | highlightScriptID: string; 34 | highlightCSSID: string; 35 | codeArea: HTMLDivElement; 36 | selectInput: HTMLInputElement; 37 | selectDropIcon: HTMLElement; 38 | 39 | constructor({ data, api, config }){ 40 | this.api = api; 41 | this.config = { 42 | themeName: config.themeName && typeof config.themeName === 'string' ? config.themeName : '', 43 | themeURL: config.themeURL && typeof config.themeURL === 'string' ? config.themeURL : '', 44 | useDefaultTheme: (config.useDefaultTheme && typeof config.useDefaultTheme === 'string' 45 | && DEFAULT_THEMES.includes(config.useDefaultTheme.toLowerCase())) ? config.useDefaultTheme : 'dark', 46 | }; 47 | this.data = { 48 | code: data.code && typeof data.code === 'string' ? data.code : '', 49 | language: data.language && typeof data.language === 'string' ? data.language : 'Auto-detect', 50 | theme: data.theme && typeof data.theme === 'string' ? data.theme : this._getThemeURLFromConfig(), 51 | }; 52 | this.highlightScriptID = 'highlightJSScriptElement'; 53 | this.highlightCSSID = 'highlightJSCSSElement'; 54 | this.codeArea = document.createElement('div'); 55 | this.selectInput = document.createElement('input'); 56 | this.selectDropIcon = document.createElement('i'); 57 | 58 | this._injectHighlightJSScriptElement(); 59 | this._injectHighlightJSCSSElement(); 60 | 61 | this.api.listeners.on(window, 'click', this._closeAllLanguageSelects, true); 62 | } 63 | 64 | static get sanitize(){ 65 | return { 66 | code: true, 67 | language: false, 68 | theme: false, 69 | } 70 | } 71 | 72 | static get toolbox() { 73 | return { 74 | title: 'CodeBox', 75 | icon: '' 76 | }; 77 | } 78 | 79 | static get displayInToolbox() { 80 | return true; 81 | } 82 | 83 | static get enableLineBreaks() { 84 | return true; 85 | } 86 | 87 | render(){ 88 | const codeAreaHolder = document.createElement('pre'); 89 | const languageSelect = this._createLanguageSelectElement(); 90 | 91 | codeAreaHolder.setAttribute('class', 'codeBoxHolder'); 92 | this.codeArea.setAttribute('class', `codeBoxTextArea ${ this.config.useDefaultTheme } ${ this.data.language }`); 93 | this.codeArea.setAttribute('contenteditable', 'true'); 94 | this.codeArea.innerHTML = this.data.code; 95 | this.api.listeners.on(this.codeArea, 'blur', event => this._highlightCodeArea(event), false); 96 | this.api.listeners.on(this.codeArea, 'paste', event => this._handleCodeAreaPaste(event), false); 97 | 98 | codeAreaHolder.appendChild(this.codeArea); 99 | codeAreaHolder.appendChild(languageSelect); 100 | 101 | return codeAreaHolder; 102 | } 103 | 104 | save(blockContent){ 105 | return Object.assign(this.data, { code: this.codeArea.innerHTML, theme: this._getThemeURLFromConfig() }); 106 | } 107 | 108 | validate(savedData){ 109 | if (!savedData.code.trim()) return false; 110 | return true; 111 | } 112 | 113 | destroy(){ 114 | this.api.listeners.off(window, 'click', this._closeAllLanguageSelects, true); 115 | this.api.listeners.off(this.codeArea, 'blur', event => this._highlightCodeArea(event), false); 116 | this.api.listeners.off(this.codeArea, 'paste', event => this._handleCodeAreaPaste(event), false); 117 | this.api.listeners.off(this.selectInput, 'click', event => this._handleSelectInputClick(event), false); 118 | } 119 | 120 | _createLanguageSelectElement(){ 121 | const selectHolder = document.createElement('div'); 122 | const selectPreview = document.createElement('div'); 123 | const languages = Object.entries(COMMON_LANGUAGES); 124 | 125 | selectHolder.setAttribute('class', 'codeBoxSelectDiv'); 126 | 127 | this.selectDropIcon.setAttribute('class', `codeBoxSelectDropIcon ${ this.config.useDefaultTheme }`); 128 | this.selectDropIcon.innerHTML = '↓'; 129 | this.selectInput.setAttribute('class', `codeBoxSelectInput ${ this.config.useDefaultTheme }`); 130 | this.selectInput.setAttribute('type', 'text'); 131 | this.selectInput.setAttribute('readonly', 'true'); 132 | this.selectInput.value = this.data.language; 133 | this.api.listeners.on(this.selectInput, 'click', event => this._handleSelectInputClick(event), false); 134 | 135 | selectPreview.setAttribute('class', 'codeBoxSelectPreview'); 136 | 137 | languages.forEach(language => { 138 | const selectItem = document.createElement('p'); 139 | selectItem.setAttribute('class', `codeBoxSelectItem ${ this.config.useDefaultTheme }`); 140 | selectItem.setAttribute('data-key', language[0]); 141 | selectItem.textContent = language[1]; 142 | this.api.listeners.on(selectItem, 'click', event => this._handleSelectItemClick(event, language), false); 143 | 144 | selectPreview.appendChild(selectItem); 145 | }); 146 | 147 | selectHolder.appendChild(this.selectDropIcon); 148 | selectHolder.appendChild(this.selectInput); 149 | selectHolder.appendChild(selectPreview); 150 | 151 | return selectHolder; 152 | } 153 | 154 | _highlightCodeArea(event){ 155 | window.hljs.highlightBlock(this.codeArea); 156 | } 157 | 158 | _handleCodeAreaPaste(event){ 159 | event.stopPropagation(); 160 | } 161 | 162 | _handleSelectInputClick(event){ 163 | event.target.nextSibling.classList.toggle('codeBoxShow'); 164 | } 165 | 166 | _handleSelectItemClick(event, language){ 167 | event.target.parentNode.parentNode.querySelector('.codeBoxSelectInput').value = language[1]; 168 | event.target.parentNode.classList.remove('codeBoxShow'); 169 | this.codeArea.removeAttribute('class'); 170 | this.data.language = language[0]; 171 | this.codeArea.setAttribute('class', `codeBoxTextArea ${ this.config.useDefaultTheme } ${ this.data.language }`); 172 | window.hljs.highlightBlock(this.codeArea); 173 | } 174 | 175 | _closeAllLanguageSelects(){ 176 | const selectPreviews = document.querySelectorAll('.codeBoxSelectPreview'); 177 | for (let i = 0, len = selectPreviews.length; i < len; i++) selectPreviews[i].classList.remove('codeBoxShow'); 178 | } 179 | 180 | _injectHighlightJSScriptElement(){ 181 | const highlightJSScriptElement = document.querySelector(`#${ this.highlightScriptID }`); 182 | const highlightJSScriptURL = 'https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@9.18.1/build/highlight.min.js'; 183 | if (!highlightJSScriptElement) { 184 | const script = document.createElement('script'); 185 | const head = document.querySelector('head'); 186 | script.setAttribute('src', highlightJSScriptURL); 187 | script.setAttribute('id', this.highlightScriptID); 188 | 189 | if (head) head.appendChild(script); 190 | } 191 | else highlightJSScriptElement.setAttribute('src', highlightJSScriptURL); 192 | } 193 | 194 | _injectHighlightJSCSSElement(){ 195 | const highlightJSCSSElement = document.querySelector(`#${ this.highlightCSSID }`); 196 | let highlightJSCSSURL = this._getThemeURLFromConfig(); 197 | if (!highlightJSCSSElement) { 198 | const link = document.createElement('link'); 199 | const head = document.querySelector('head'); 200 | link.setAttribute('rel', 'stylesheet'); 201 | link.setAttribute('href', highlightJSCSSURL); 202 | link.setAttribute('id', this.highlightCSSID); 203 | 204 | if (head) head.appendChild(link); 205 | } 206 | else highlightJSCSSElement.setAttribute('href', highlightJSCSSURL); 207 | } 208 | 209 | _getThemeURLFromConfig(){ 210 | let themeURL = `https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@9.18.1/build/styles/atom-one-${ this.config.useDefaultTheme }.min.css`; 211 | 212 | if (this.config.themeName) themeURL = `https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@9.18.1/build/styles/${ this.config.themeName }.min.css`; 213 | if (this.config.themeURL) themeURL = this.config.themeURL; 214 | 215 | return themeURL; 216 | } 217 | } 218 | 219 | 220 | export default CodeBox; 221 | -------------------------------------------------------------------------------- /dist/index.min.js: -------------------------------------------------------------------------------- 1 | !function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.CodeBox=t():e.CodeBox=t()}(self,(()=>(()=>{"use strict";var e={426:(e,t,n)=>{n.d(t,{Z:()=>c});var o=n(81),i=n.n(o),r=n(645),a=n.n(r)()(i());a.push([e.id,".codeBoxHolder{\n display: flex;\n flex-direction: column;\n justify-content: flex-start;\n align-items: flex-start;\n}\n\n.codeBoxTextArea{\n width: 100%;\n min-height: 30px;\n padding: 10px;\n border-radius: 2px 2px 2px 0;\n border: none !important;\n outline: none !important;\n font: 14px monospace;\n}\n\n.codeBoxSelectDiv{\n display: flex;\n flex-direction: column;\n justify-content: flex-start;\n align-items: flex-start;\n position: relative;\n}\n\n.codeBoxSelectInput{\n border-radius: 0 0 20px 2px;\n padding: 2px 26px;\n padding-top: 0;\n padding-right: 0;\n text-align: left;\n cursor: pointer;\n border: none !important;\n outline: none !important;\n}\n\n.codeBoxSelectDropIcon{\n position: absolute !important;\n left: 10px !important;\n bottom: 0 !important;\n width: unset !important;\n height: unset !important;\n font-size: 16px !important;\n}\n\n.codeBoxSelectPreview{\n display: none;\n flex-direction: column;\n justify-content: flex-start;\n align-items: flex-start;\n border-radius: 2px;\n box-shadow: 0 3px 15px -3px rgba(13,20,33,.13);\n position: absolute;\n top: 100%;\n margin: 5px 0;\n max-height: 30vh;\n overflow-x: hidden;\n overflow-y: auto;\n z-index: 10000;\n}\n\n.codeBoxSelectItem{\n width: 100%;\n padding: 5px 20px;\n margin: 0;\n cursor: pointer;\n}\n\n.codeBoxSelectItem:hover{\n opacity: 0.7;\n}\n\n.codeBoxSelectedItem{\n background-color: lightblue !important;\n}\n\n.codeBoxShow{\n display: flex !important;\n}\n\n.dark{\n color: #abb2bf;\n background-color: #282c34;\n}\n\n.light{\n color: #383a42;\n background-color: #fafafa;\n}\n",""]);const c=a},645:e=>{e.exports=function(e){var t=[];return t.toString=function(){return this.map((function(t){var n="",o=void 0!==t[5];return t[4]&&(n+="@supports (".concat(t[4],") {")),t[2]&&(n+="@media ".concat(t[2]," {")),o&&(n+="@layer".concat(t[5].length>0?" ".concat(t[5]):""," {")),n+=e(t),o&&(n+="}"),t[2]&&(n+="}"),t[4]&&(n+="}"),n})).join("")},t.i=function(e,n,o,i,r){"string"==typeof e&&(e=[[null,e,void 0]]);var a={};if(o)for(var c=0;c0?" ".concat(u[5]):""," {").concat(u[1],"}")),u[5]=r),n&&(u[2]?(u[1]="@media ".concat(u[2]," {").concat(u[1],"}"),u[2]=n):u[2]=n),i&&(u[4]?(u[1]="@supports (".concat(u[4],") {").concat(u[1],"}"),u[4]=i):u[4]="".concat(i)),t.push(u))}},t}},81:e=>{e.exports=function(e){return e[1]}},654:(e,t,n)=>{var o=n(379),i=n.n(o),r=n(795),a=n.n(r),c=n(569),s=n.n(c),l=n(565),u=n.n(l),d=n(216),h=n.n(d),p=n(589),f=n.n(p),g=n(426),m={};m.styleTagTransform=f(),m.setAttributes=u(),m.insert=s().bind(null,"head"),m.domAPI=a(),m.insertStyleElement=h(),i()(g.Z,m),g.Z&&g.Z.locals&&g.Z.locals},379:e=>{var t=[];function n(e){for(var n=-1,o=0;o{var t={};e.exports=function(e,n){var o=function(e){if(void 0===t[e]){var n=document.querySelector(e);if(window.HTMLIFrameElement&&n instanceof window.HTMLIFrameElement)try{n=n.contentDocument.head}catch(e){n=null}t[e]=n}return t[e]}(e);if(!o)throw new Error("Couldn't find a style target. This probably means that the value for the 'insert' parameter is invalid.");o.appendChild(n)}},216:e=>{e.exports=function(e){var t=document.createElement("style");return e.setAttributes(t,e.attributes),e.insert(t,e.options),t}},565:(e,t,n)=>{e.exports=function(e){var t=n.nc;t&&e.setAttribute("nonce",t)}},795:e=>{e.exports=function(e){var t=e.insertStyleElement(e);return{update:function(n){!function(e,t,n){var o="";n.supports&&(o+="@supports (".concat(n.supports,") {")),n.media&&(o+="@media ".concat(n.media," {"));var i=void 0!==n.layer;i&&(o+="@layer".concat(n.layer.length>0?" ".concat(n.layer):""," {")),o+=n.css,i&&(o+="}"),n.media&&(o+="}"),n.supports&&(o+="}");var r=n.sourceMap;r&&"undefined"!=typeof btoa&&(o+="\n/*# sourceMappingURL=data:application/json;base64,".concat(btoa(unescape(encodeURIComponent(JSON.stringify(r))))," */")),t.styleTagTransform(o,e,t.options)}(t,e,n)},remove:function(){!function(e){if(null===e.parentNode)return!1;e.parentNode.removeChild(e)}(t)}}}},589:e=>{e.exports=function(e,t){if(t.styleSheet)t.styleSheet.cssText=e;else{for(;t.firstChild;)t.removeChild(t.firstChild);t.appendChild(document.createTextNode(e))}}}},t={};function n(o){var i=t[o];if(void 0!==i)return i.exports;var r=t[o]={id:o,exports:{}};return e[o](r,r.exports,n),r.exports}n.n=e=>{var t=e&&e.__esModule?()=>e.default:()=>e;return n.d(t,{a:t}),t},n.d=(e,t)=>{for(var o in t)n.o(t,o)&&!n.o(e,o)&&Object.defineProperty(e,o,{enumerable:!0,get:t[o]})},n.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),n.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.nc=void 0;var o={};return(()=>{n.r(o),n.d(o,{default:()=>i}),n(654).toString();var e=["light","dark"],t={none:"Auto-detect",apache:"Apache",bash:"Bash",cs:"C#",cpp:"C++",css:"CSS",coffeescript:"CoffeeScript",diff:"Diff",go:"Go",html:"HTML, XML",http:"HTTP",json:"JSON",java:"Java",javascript:"JavaScript",kotlin:"Kotlin",less:"Less",lua:"Lua",makefile:"Makefile",markdown:"Markdown",nginx:"Nginx",objectivec:"Objective-C",php:"PHP",perl:"Perl",properties:"Properties",python:"Python",ruby:"Ruby",rust:"Rust",scss:"SCSS",sql:"SQL",shell:"Shell Session",swift:"Swift",toml:"TOML, also INI",typescript:"TypeScript",yaml:"YAML",plaintext:"Plaintext"};const i=function(){function n(t){var n=t.data,o=t.api,i=t.config;this.api=o,this.config={themeName:i.themeName&&"string"==typeof i.themeName?i.themeName:"",themeURL:i.themeURL&&"string"==typeof i.themeURL?i.themeURL:"",useDefaultTheme:i.useDefaultTheme&&"string"==typeof i.useDefaultTheme&&e.includes(i.useDefaultTheme.toLowerCase())?i.useDefaultTheme:"dark"},this.data={code:n.code&&"string"==typeof n.code?n.code:"",language:n.language&&"string"==typeof n.language?n.language:"Auto-detect",theme:n.theme&&"string"==typeof n.theme?n.theme:this._getThemeURLFromConfig()},this.highlightScriptID="highlightJSScriptElement",this.highlightCSSID="highlightJSCSSElement",this.codeArea=document.createElement("div"),this.selectInput=document.createElement("input"),this.selectDropIcon=document.createElement("i"),this._injectHighlightJSScriptElement(),this._injectHighlightJSCSSElement(),this.api.listeners.on(window,"click",this._closeAllLanguageSelects,!0)}return Object.defineProperty(n,"sanitize",{get:function(){return{code:!0,language:!1,theme:!1}},enumerable:!1,configurable:!0}),Object.defineProperty(n,"toolbox",{get:function(){return{title:"CodeBox",icon:''}},enumerable:!1,configurable:!0}),Object.defineProperty(n,"displayInToolbox",{get:function(){return!0},enumerable:!1,configurable:!0}),Object.defineProperty(n,"enableLineBreaks",{get:function(){return!0},enumerable:!1,configurable:!0}),n.prototype.render=function(){var e=this,t=document.createElement("pre"),n=this._createLanguageSelectElement();return t.setAttribute("class","codeBoxHolder"),this.codeArea.setAttribute("class","codeBoxTextArea ".concat(this.config.useDefaultTheme," ").concat(this.data.language)),this.codeArea.setAttribute("contenteditable","true"),this.codeArea.innerHTML=this.data.code,this.api.listeners.on(this.codeArea,"blur",(function(t){return e._highlightCodeArea(t)}),!1),this.api.listeners.on(this.codeArea,"paste",(function(t){return e._handleCodeAreaPaste(t)}),!1),t.appendChild(this.codeArea),t.appendChild(n),t},n.prototype.save=function(e){return Object.assign(this.data,{code:this.codeArea.innerHTML,theme:this._getThemeURLFromConfig()})},n.prototype.validate=function(e){return!!e.code.trim()},n.prototype.destroy=function(){var e=this;this.api.listeners.off(window,"click",this._closeAllLanguageSelects,!0),this.api.listeners.off(this.codeArea,"blur",(function(t){return e._highlightCodeArea(t)}),!1),this.api.listeners.off(this.codeArea,"paste",(function(t){return e._handleCodeAreaPaste(t)}),!1),this.api.listeners.off(this.selectInput,"click",(function(t){return e._handleSelectInputClick(t)}),!1)},n.prototype._createLanguageSelectElement=function(){var e=this,n=document.createElement("div"),o=document.createElement("div"),i=Object.entries(t);return n.setAttribute("class","codeBoxSelectDiv"),this.selectDropIcon.setAttribute("class","codeBoxSelectDropIcon ".concat(this.config.useDefaultTheme)),this.selectDropIcon.innerHTML="↓",this.selectInput.setAttribute("class","codeBoxSelectInput ".concat(this.config.useDefaultTheme)),this.selectInput.setAttribute("type","text"),this.selectInput.setAttribute("readonly","true"),this.selectInput.value=this.data.language,this.api.listeners.on(this.selectInput,"click",(function(t){return e._handleSelectInputClick(t)}),!1),o.setAttribute("class","codeBoxSelectPreview"),i.forEach((function(t){var n=document.createElement("p");n.setAttribute("class","codeBoxSelectItem ".concat(e.config.useDefaultTheme)),n.setAttribute("data-key",t[0]),n.textContent=t[1],e.api.listeners.on(n,"click",(function(n){return e._handleSelectItemClick(n,t)}),!1),o.appendChild(n)})),n.appendChild(this.selectDropIcon),n.appendChild(this.selectInput),n.appendChild(o),n},n.prototype._highlightCodeArea=function(e){window.hljs.highlightBlock(this.codeArea)},n.prototype._handleCodeAreaPaste=function(e){e.stopPropagation()},n.prototype._handleSelectInputClick=function(e){e.target.nextSibling.classList.toggle("codeBoxShow")},n.prototype._handleSelectItemClick=function(e,t){e.target.parentNode.parentNode.querySelector(".codeBoxSelectInput").value=t[1],e.target.parentNode.classList.remove("codeBoxShow"),this.codeArea.removeAttribute("class"),this.data.language=t[0],this.codeArea.setAttribute("class","codeBoxTextArea ".concat(this.config.useDefaultTheme," ").concat(this.data.language)),window.hljs.highlightBlock(this.codeArea)},n.prototype._closeAllLanguageSelects=function(){for(var e=document.querySelectorAll(".codeBoxSelectPreview"),t=0,n=e.length;t