├── .editorconfig ├── .gitattributes ├── .gitignore ├── LICENSE ├── README.md ├── circle.yml ├── example └── index.js ├── package.json ├── src ├── CodeMirror.js └── index.js └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist/ 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) egoist <0x142857@gmail.com> (https://egoist.moe) 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 13 | all 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 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vue-cm 2 | 3 | [![NPM version](https://img.shields.io/npm/v/vue-cm.svg?style=flat)](https://npmjs.com/package/vue-cm) [![NPM downloads](https://img.shields.io/npm/dm/vue-cm.svg?style=flat)](https://npmjs.com/package/vue-cm) [![CircleCI](https://circleci.com/gh/egoist/vue-cm/tree/master.svg?style=shield)](https://circleci.com/gh/egoist/vue-cm/tree/master) [![donate](https://img.shields.io/badge/$-donate-ff69b4.svg?maxAge=2592000&style=flat)](https://github.com/egoist/donate) 4 | 5 | *[CodeMirror](https://codemirror.net/) is a versatile text editor implemented in JavaScript for the browser.* 6 | 7 | **NOTE:** I'm aware of the existence of [vue-codemirror](https://github.com/surmon-china/vue-codemirror), but I built this one for good reasons: 8 | 9 | - Smaller. 10 | - No unnecessary abstractions, which means simpler API. 11 | - Prebuilt bundle, you can use it with or without a bundler. 12 | - More modern-ish. 13 | - Simply better. 14 | 15 | ## Install 16 | 17 | ```bash 18 | yarn add codemirror vue-cm 19 | ``` 20 | 21 | CDN: [UNPKG](https://unpkg.com/vue-cm/dist/) | [jsDelivr](https://cdn.jsdelivr.net/npm/vue-cm/dist/) 22 | 23 | > **NOTE**: You need to include CodeMirror as well if you're using the CDN version, basically we access it via `window.CodeMirror` in the CDN version. 24 | 25 | ## Usage 26 | 27 | ```vue 28 | 34 | 35 | 56 | 57 | 58 | ``` 59 | 60 | `v-model` for components is just a syntax sugar of: 61 | 62 | ```diff 63 | 68 | 69 | ``` 70 | 71 | ### Props 72 | 73 | - `value`: `string` Editor value 74 | - `options`: `object` CodeMirror instance options 75 | - `preserveScrollPosition` `default: false`: Preserve previous scroll position after updating value. 76 | 77 | ### Events 78 | 79 | - `change`: Emitted when a change is made, args: 80 | - `newValue`: New editor value 81 | - `focus`: Emitted when the editor is focused 82 | - `blur`: Emitted when the editor loses focus 83 | - `scroll`: Emitted when the editor is scrolled, args: 84 | - `scrollInfo` 85 | - `cursorActivity`: Emitted when cursor is moved, args: 86 | - `codemirror`: CodeMirror instance 87 | 88 | ### Methods 89 | 90 | - `focus()`: focuses the CodeMirror instance 91 | - `getCodeMirror()`: get the CodeMirror instance 92 | 93 | You can interact with the `CodeMirror` component by using a `ref` attribute, eg: ``, then you can call `this.$refs.editor.getCodeMirror()` in parent component's `mounted` hook to get the CodeMirror instance we use in the child component. 94 | 95 | ## Contributing 96 | 97 | 1. Fork it! 98 | 2. Create your feature branch: `git checkout -b my-new-feature` 99 | 3. Commit your changes: `git commit -am 'Add some feature'` 100 | 4. Push to the branch: `git push origin my-new-feature` 101 | 5. Submit a pull request :D 102 | 103 | 104 | ## Author 105 | 106 | **vue-cm** © [egoist](https://github.com/egoist), Released under the [MIT](./LICENSE) License.
107 | Authored and maintained by egoist with help from contributors ([list](https://github.com/egoist/vue-cm/contributors)). 108 | 109 | > [egoist.moe](https://egoist.moe) · GitHub [@egoist](https://github.com/egoist) · Twitter [@_egoistlily](https://twitter.com/_egoistlily) 110 | -------------------------------------------------------------------------------- /circle.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | jobs: 3 | build: 4 | working_directory: ~/repo 5 | docker: 6 | - image: circleci/node:latest 7 | branches: 8 | ignore: 9 | - gh-pages # list of branches to ignore 10 | - /release\/.*/ # or ignore regexes 11 | steps: 12 | - checkout 13 | - restore_cache: 14 | key: dependency-cache-{{ checksum "yarn.lock" }} 15 | - run: 16 | name: install dependences 17 | command: yarn 18 | - save_cache: 19 | key: dependency-cache-{{ checksum "yarn.lock" }} 20 | paths: 21 | - ./node_modules 22 | - run: 23 | name: test 24 | command: yarn test 25 | -------------------------------------------------------------------------------- /example/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import CodeMirror from '../src' 3 | import 'codemirror/mode/javascript/javascript' 4 | import 'codemirror/mode/gfm/gfm' 5 | 6 | import 'codemirror/lib/codemirror.css' 7 | 8 | const code = ` 9 | function foo() { 10 | return 'foo' 11 | } 12 | `.trim() 13 | 14 | const markdownCode = ` 15 | # hello 16 | 17 | **this is bold** 18 | 19 | \`\`\`js 20 | function foo() { 21 | return 'foo' 22 | } 23 | \`\`\` 24 | `.trim() 25 | 26 | new Vue({ 27 | el: '#app', 28 | 29 | data: { 30 | code, 31 | options: { 32 | mode: 'javascript' 33 | } 34 | }, 35 | 36 | mounted() { 37 | console.log(this.$refs.editor) 38 | }, 39 | 40 | methods: { 41 | updateEditor() { 42 | this.code = markdownCode 43 | this.options.mode = 'gfm' 44 | this.$refs.editor.focus() 45 | } 46 | }, 47 | 48 | render() { 49 | return ( 50 |
51 | 52 | this.code = code} 58 | onFocus={() => console.log('focused')} 59 | onScroll={info => console.log(info)} 60 | onCursorActivity={cm => console.log(cm)} 61 | options={this.options} 62 | /> 63 |
64 | ) 65 | } 66 | }) 67 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-cm", 3 | "version": "1.1.0", 4 | "description": "CodeMirror component for Vue.js", 5 | "repository": { 6 | "url": "egoist/vue-cm", 7 | "type": "git" 8 | }, 9 | "main": "dist/cm.common.js", 10 | "cdn": "dist/cm.min.js", 11 | "unpkg": "dist/cm.min.js", 12 | "files": [ 13 | "dist" 14 | ], 15 | "keywords": [ 16 | "codemirror", 17 | "code", 18 | "editor", 19 | "vue" 20 | ], 21 | "scripts": { 22 | "test": "npm run lint", 23 | "lint": "xo", 24 | "prepublish": "npm run build", 25 | "build": "npm run build:cjs && npm run build:umd", 26 | "build:cjs": "bili --filename cm", 27 | "build:umd": "bili --filename cm --format umd,umdCompress --external codemirror --module-name VueCodeMirror", 28 | "example": "poi", 29 | "build:example": "poi build" 30 | }, 31 | "author": "egoist <0x142857@gmail.com>", 32 | "license": "MIT", 33 | "poi": { 34 | "entry": "example/index.js", 35 | "dist": "example/dist", 36 | "homepage": "/" 37 | }, 38 | "dependencies": { 39 | "fast-deep-equal": "^1.0.0" 40 | }, 41 | "devDependencies": { 42 | "bili": "^0.17.0", 43 | "codemirror": "^5.28.0", 44 | "eslint-config-rem": "^3.0.0", 45 | "poi": "^9.1.4", 46 | "xo": "^0.18.0" 47 | }, 48 | "xo": { 49 | "extends": "rem/prettier", 50 | "rules": { 51 | "unicorn/filename-case": 0 52 | }, 53 | "ignores": [ 54 | "example/**" 55 | ], 56 | "envs": [ 57 | "browser" 58 | ] 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/CodeMirror.js: -------------------------------------------------------------------------------- 1 | import CodeMirror from 'codemirror' 2 | import deepEqual from 'fast-deep-equal' 3 | 4 | export default { 5 | name: 'CodeMirror', 6 | 7 | model: { 8 | event: 'change' 9 | }, 10 | 11 | props: { 12 | value: String, 13 | options: Object, 14 | preserveScrollPosition: Boolean 15 | }, 16 | 17 | methods: { 18 | focus() { 19 | this.codemirror && this.codemirror.focus() 20 | }, 21 | 22 | getCodeMirror() { 23 | return this.codemirror 24 | }, 25 | 26 | setOptionIfChanged(name, newValue) { 27 | const oldValue = this.codemirror.getOption(name) 28 | if (!deepEqual(oldValue, newValue)) { 29 | this.codemirror.setOption(name, newValue) 30 | } 31 | } 32 | }, 33 | 34 | watch: { 35 | value(newValue) { 36 | if (this.codemirror && newValue !== this.codemirror.getValue()) { 37 | if (this.preserveScrollPosition) { 38 | const prevScrollPosition = this.codemirror.getScrollInfo() 39 | this.codemirror.setValue(newValue) 40 | this.codemirror.scrollTo( 41 | prevScrollPosition.left, 42 | prevScrollPosition.top 43 | ) 44 | } else { 45 | this.codemirror.setValue(newValue) 46 | } 47 | } 48 | }, 49 | 50 | options: { 51 | deep: true, 52 | handler(newValue) { 53 | if (this.codemirror) { 54 | // eslint-disable-next-line guard-for-in 55 | for (const name in newValue) { 56 | this.setOptionIfChanged(name, newValue[name]) 57 | } 58 | } 59 | } 60 | } 61 | }, 62 | 63 | mounted() { 64 | this.codemirror = CodeMirror.fromTextArea(this.$el, this.options) 65 | 66 | this.codemirror.on('change', e => this.$emit('change', e.getValue())) 67 | this.codemirror.on('focus', () => this.$emit('focus')) 68 | this.codemirror.on('blur', () => this.$emit('blur')) 69 | this.codemirror.on('scroll', cm => this.$emit('scroll', cm.getScrollInfo())) 70 | this.codemirror.on('cursorActivity', cm => this.$emit('cursorActivity', cm)) 71 | }, 72 | 73 | beforeDestroy() { 74 | this.codemirror && this.codemirror.toTextArea() 75 | }, 76 | 77 | render(h) { 78 | return h('textarea', null, [this.value]) 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import CodeMirror from './CodeMirror' 2 | 3 | export default CodeMirror 4 | 5 | if (typeof window !== 'undefined' && window.Vue) { 6 | window.Vue.component(CodeMirror.name, CodeMirror) 7 | } 8 | --------------------------------------------------------------------------------