├── .codecov.yml ├── .editorconfig ├── .eslintrc.json ├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .nvmrc ├── .prettierrc ├── LICENSE ├── README.md ├── angular.json ├── karma.conf.js ├── package-lock.json ├── package.json ├── src ├── app │ ├── app.component.html │ ├── app.component.spec.ts │ ├── app.component.ts │ ├── app.module.ts │ └── footer.component.ts ├── assets │ └── .gitkeep ├── environments │ ├── environment.prod.ts │ └── environment.ts ├── favicon.ico ├── index.html ├── lib │ ├── codemirror.component.ts │ ├── codemirror.module.ts │ ├── ng-package.json │ ├── package.json │ └── public_api.ts ├── main.ts ├── polyfills.ts ├── styles.css └── test.ts ├── tailwind.config.js ├── tsconfig.app.json ├── tsconfig.json ├── tsconfig.spec.json └── vercel.json /.codecov.yml: -------------------------------------------------------------------------------- 1 | coverage: 2 | range: "50..100" 3 | status: 4 | project: false 5 | patch: false 6 | comment: 7 | require_changes: true 8 | behavior: once 9 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see https://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.ts] 12 | quote_type = single 13 | 14 | [*.md] 15 | max_line_length = off 16 | trim_trailing_whitespace = false 17 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "ignorePatterns": [ 4 | "projects/**/*" 5 | ], 6 | "overrides": [ 7 | { 8 | "files": [ 9 | "*.ts" 10 | ], 11 | "parserOptions": { 12 | "project": [ 13 | "tsconfig.json", 14 | "e2e/tsconfig.json" 15 | ], 16 | "createDefaultProgram": true 17 | }, 18 | "extends": [ 19 | "plugin:@angular-eslint/recommended", 20 | "plugin:@angular-eslint/template/process-inline-templates" 21 | ], 22 | "rules": {} 23 | }, 24 | { 25 | "files": [ 26 | "*.html" 27 | ], 28 | "extends": [ 29 | "plugin:@angular-eslint/template/recommended" 30 | ], 31 | "rules": {} 32 | } 33 | ] 34 | } 35 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v3 14 | - uses: actions/setup-node@v3 15 | with: 16 | node-version: 20 17 | cache: 'npm' 18 | - run: npm ci 19 | - name: lint 20 | run: npm run lint 21 | - run: npm run build 22 | - name: test 23 | run: npm run test:ci 24 | - name: coverage 25 | uses: codecov/codecov-action@v3 26 | with: 27 | token: ${{ secrets.CODECOV_TOKEN }} 28 | 29 | publish: 30 | needs: build 31 | runs-on: ubuntu-latest 32 | if: github.ref_name == 'master' 33 | permissions: 34 | contents: write # to be able to publish a GitHub release 35 | issues: write # to be able to comment on released issues 36 | pull-requests: write # to be able to comment on released pull requests 37 | id-token: write # to enable use of OIDC for npm provenance 38 | steps: 39 | - uses: actions/checkout@v3 40 | - uses: actions/setup-node@v3 41 | with: 42 | node-version: 20 43 | cache: 'npm' 44 | - run: npm ci 45 | - run: npm run build 46 | - name: release 47 | run: cd dist && npx semantic-release 48 | env: 49 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 50 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 51 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /tmp 6 | /out-tsc 7 | # Only exists if Bazel was run 8 | /bazel-out 9 | 10 | # dependencies 11 | /node_modules 12 | 13 | # profiling files 14 | chrome-profiler-events*.json 15 | speed-measure-plugin*.json 16 | 17 | # IDEs and editors 18 | /.idea 19 | .project 20 | .classpath 21 | .c9/ 22 | *.launch 23 | .settings/ 24 | *.sublime-workspace 25 | 26 | # IDE - VSCode 27 | .vscode/* 28 | !.vscode/settings.json 29 | !.vscode/tasks.json 30 | !.vscode/launch.json 31 | !.vscode/extensions.json 32 | .history/* 33 | 34 | # misc 35 | /.angular/cache 36 | /.sass-cache 37 | /connect.lock 38 | /coverage 39 | /libpeerconnection.log 40 | npm-debug.log 41 | yarn-error.log 42 | testem.log 43 | /typings 44 | 45 | # System Files 46 | .DS_Store 47 | Thumbs.db 48 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 20 2 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": true, 3 | "singleQuote": true, 4 | "trailingComma": "all", 5 | "bracketSpacing": true, 6 | "printWidth": 100, 7 | "arrowParens": "avoid" 8 | } 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Scott Cooper 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 | # DEPRECATED Use https://github.com/acrodata/code-editor 2 | 3 | # ngx-codemirror [![npm](https://badgen.net/npm/v/@ctrl/ngx-codemirror)](https://www.npmjs.com/package/@ctrl/ngx-codemirror) [![coverage](https://badgen.net/codecov/c/github/scttcper/ngx-codemirror)](https://codecov.io/gh/scttcper/ngx-codemirror) 4 | 5 | DEMO: https://ngx-codemirror.vercel.app 6 | 7 | ## Dependencies 8 | 9 | Latest version available for each version of Angular 10 | 11 | | @ctrl/ngx-codemirror | Angular | Codemirror | 12 | | -------------------- | --------- | ---------- | 13 | | 1.3.10 | 6.x 7.x | 5 | 14 | | 2.2.1 | 8.x | 5 | 15 | | 3.1.3 | 9.x | 5 | 16 | | 4.1.1 | 10.x 11.x | 5 | 17 | | 5.1.1 | 12.x-14x | 5 | 18 | | 6.1.0 | 15.x | 5 | 19 | | current | >= 16.x | 5 | 20 | 21 | An Angular component wrapper for [CodeMirror](https://codemirror.net/) that extends ngModel. Based on [JedWatson/react-codemirror](https://github.com/JedWatson/react-codemirror) 22 | 23 | ##### Used in: 24 | 25 | tsquery playground: https://tsquery-playground.firebaseapp.com/ 26 | 27 | ## Install 28 | 29 | `codemirror` is a peer dependency and must also be installed 30 | 31 | ```sh 32 | npm install @ctrl/ngx-codemirror codemirror@5 33 | ``` 34 | 35 | ## Use 36 | 37 | Import `CodemirrorModule` and `FormsModule` and bring in the [codemirror files for parsing the language](https://codemirror.net/mode/index.html) you wish to use. 38 | 39 | In your `NgModule`: 40 | 41 | ```ts 42 | import { FormsModule } from '@angular/forms'; 43 | import { NgModule } from '@angular/core'; 44 | import { CodemirrorModule } from '@ctrl/ngx-codemirror'; 45 | 46 | // add to imports: 47 | imports: [ 48 | BrowserModule, 49 | FormsModule, 50 | CodemirrorModule, 51 | ... 52 | ] 53 | ``` 54 | 55 | In your `main.ts` or at the root of your application, see [documentation](https://codemirror.net/mode/index.html): 56 | 57 | ```ts 58 | import 'codemirror/mode/javascript/javascript'; 59 | import 'codemirror/mode/markdown/markdown'; 60 | ``` 61 | 62 | Import the base css file and your [theme](https://codemirror.net/demo/theme.html) 63 | 64 | ```css 65 | @import '~codemirror/lib/codemirror'; 66 | @import '~codemirror/theme/material'; 67 | ``` 68 | 69 | Use The Component 70 | 71 | ```html 72 | 80 | ``` 81 | 82 | ## Inputs 83 | 84 | All Inputs of [ngModel](https://angular.io/api/forms/NgModel#inputs) and 85 | 86 | - `options` - options passed to the CodeMirror instance see http://codemirror.net/doc/manual.html#config 87 | - `name` - name applied to the created textarea 88 | - `autoFocus` - setting applied to the created textarea 89 | - `preserveScrollPosition` - preserve previous scroll position after updating value 90 | 91 | ## Outputs 92 | 93 | All outputs of [ngModel](https://angular.io/api/forms/NgModel#outputs) and 94 | 95 | - `codeMirrorLoaded` - called when codeMirror instance is initiated 96 | - `focusChange` - called when the editor is focused or loses focus 97 | - `scroll` - called when the editor is scrolled (not wrapped inside angular change detection must manually trigger change detection or run inside ngzone) 98 | - `cursorActivity` - called when the text cursor is moved 99 | - `drop` - called when file(s) are dropped 100 | 101 | ## License 102 | 103 | MIT 104 | -------------------------------------------------------------------------------- /angular.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json", 3 | "version": 1, 4 | "newProjectRoot": "projects", 5 | "projects": { 6 | "ngx-codemirror": { 7 | "projectType": "application", 8 | "schematics": { 9 | "@schematics/angular:component": { 10 | "style": "css" 11 | }, 12 | "@schematics/angular:application": { 13 | "strict": true 14 | } 15 | }, 16 | "root": "", 17 | "sourceRoot": "src", 18 | "prefix": "app", 19 | "architect": { 20 | "build": { 21 | "builder": "@angular-devkit/build-angular:browser", 22 | "options": { 23 | "allowedCommonJsDependencies": [ 24 | "codemirror" 25 | ], 26 | "outputPath": "dist", 27 | "index": "src/index.html", 28 | "main": "src/main.ts", 29 | "polyfills": "src/polyfills.ts", 30 | "tsConfig": "tsconfig.app.json", 31 | "assets": [ 32 | "src/favicon.ico", 33 | "src/assets" 34 | ], 35 | "styles": [ 36 | "src/styles.css" 37 | ], 38 | "scripts": [], 39 | "vendorChunk": true, 40 | "extractLicenses": false, 41 | "buildOptimizer": false, 42 | "sourceMap": true, 43 | "optimization": false, 44 | "namedChunks": true 45 | }, 46 | "configurations": { 47 | "production": { 48 | "fileReplacements": [ 49 | { 50 | "replace": "src/environments/environment.ts", 51 | "with": "src/environments/environment.prod.ts" 52 | } 53 | ], 54 | "optimization": { 55 | "scripts": true, 56 | "fonts": { 57 | "inline": true 58 | }, 59 | "styles": { 60 | "minify": true, 61 | "inlineCritical": false 62 | } 63 | }, 64 | "outputHashing": "all", 65 | "sourceMap": false, 66 | "namedChunks": false, 67 | "extractLicenses": true, 68 | "vendorChunk": false, 69 | "buildOptimizer": true, 70 | "budgets": [ 71 | { 72 | "type": "initial", 73 | "maximumWarning": "500kb", 74 | "maximumError": "1mb" 75 | }, 76 | { 77 | "type": "anyComponentStyle", 78 | "maximumWarning": "2kb", 79 | "maximumError": "4kb" 80 | } 81 | ] 82 | } 83 | } 84 | }, 85 | "serve": { 86 | "builder": "@angular-devkit/build-angular:dev-server", 87 | "options": { 88 | "browserTarget": "ngx-codemirror:build" 89 | }, 90 | "configurations": { 91 | "production": { 92 | "browserTarget": "ngx-codemirror:build:production" 93 | } 94 | } 95 | }, 96 | "extract-i18n": { 97 | "builder": "@angular-devkit/build-angular:extract-i18n", 98 | "options": { 99 | "browserTarget": "ngx-codemirror:build" 100 | } 101 | }, 102 | "test": { 103 | "builder": "@angular-devkit/build-angular:karma", 104 | "options": { 105 | "main": "src/test.ts", 106 | "polyfills": "src/polyfills.ts", 107 | "tsConfig": "tsconfig.spec.json", 108 | "karmaConfig": "karma.conf.js", 109 | "assets": [ 110 | "src/favicon.ico", 111 | "src/assets" 112 | ], 113 | "styles": [ 114 | "src/styles.css" 115 | ], 116 | "scripts": [] 117 | } 118 | }, 119 | "lint": { 120 | "builder": "@angular-eslint/builder:lint", 121 | "options": { 122 | "lintFilePatterns": [ 123 | "src/**/*.ts", 124 | "src/**/*.html" 125 | ] 126 | } 127 | } 128 | } 129 | } 130 | }, 131 | "cli": { 132 | "schematicCollections": [ 133 | "@angular-eslint/schematics" 134 | ], 135 | "analytics": false 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/1.0/config/configuration-file.html 3 | 4 | module.exports = function (config) { 5 | config.set({ 6 | basePath: '', 7 | frameworks: ['jasmine', '@angular-devkit/build-angular'], 8 | plugins: [ 9 | require('karma-jasmine'), 10 | require('karma-chrome-launcher'), 11 | require('karma-jasmine-html-reporter'), 12 | require('karma-coverage-istanbul-reporter'), 13 | require('@angular-devkit/build-angular/plugins/karma'), 14 | ], 15 | client: { 16 | clearContext: false, // leave Jasmine Spec Runner output visible in browser 17 | }, 18 | coverageIstanbulReporter: { 19 | dir: require('path').join(__dirname, './coverage/zzz'), 20 | reports: ['html', 'lcovonly', 'text-summary'], 21 | fixWebpackSourcePaths: true, 22 | }, 23 | reporters: ['coverage-istanbul', 'progress', 'kjhtml'], 24 | port: 9876, 25 | colors: true, 26 | logLevel: config.LOG_INFO, 27 | autoWatch: true, 28 | browsers: ['Chrome'], 29 | customLaunchers: { 30 | ChromeHeadlessCustom: { 31 | base: 'ChromeHeadless', 32 | flags: ['--no-sandbox', '--disable-gpu'], 33 | }, 34 | }, 35 | singleRun: false, 36 | restartOnFileChange: true, 37 | }); 38 | }; 39 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@ctrl/ngx-codemirror", 3 | "version": "0.0.0-placeholder", 4 | "license": "MIT", 5 | "repository": "scttcper/ngx-codemirror", 6 | "scripts": { 7 | "ng": "ng", 8 | "start": "ng serve", 9 | "build": "ng-packagr -p src/lib/ng-package.json", 10 | "postbuild": "cpy README.md LICENSE dist", 11 | "publish": "cd dist && npm publish --access public", 12 | "test": "ng test --watch=false", 13 | "test:ci": "ng test --watch=false --code-coverage --no-progress --browsers=ChromeHeadlessCustom", 14 | "lint": "ng lint", 15 | "lint:fix": "ng lint --fix", 16 | "ghpages": "ng build --configuration production --no-progress" 17 | }, 18 | "private": true, 19 | "dependencies": { 20 | "@types/codemirror": "5.60.7", 21 | "codemirror": "5.64.0" 22 | }, 23 | "devDependencies": { 24 | "@angular-devkit/build-angular": "16.0.1", 25 | "@angular-eslint/builder": "16.0.1", 26 | "@angular-eslint/eslint-plugin": "16.0.1", 27 | "@angular-eslint/eslint-plugin-template": "16.0.1", 28 | "@angular-eslint/schematics": "16.0.1", 29 | "@angular-eslint/template-parser": "16.0.1", 30 | "@angular/cli": "16.0.1", 31 | "@angular/common": "16.0.1", 32 | "@angular/compiler": "16.0.1", 33 | "@angular/compiler-cli": "16.0.1", 34 | "@angular/core": "16.0.1", 35 | "@angular/forms": "16.0.1", 36 | "@angular/language-service": "16.0.1", 37 | "@angular/platform-browser": "16.0.1", 38 | "@angular/platform-browser-dynamic": "16.0.1", 39 | "@ctrl/ngx-github-buttons": "9.0.0", 40 | "@ngneat/tailwind": "7.0.3", 41 | "@tailwindcss/forms": "0.5.3", 42 | "@tailwindcss/typography": "0.5.9", 43 | "@types/jasmine": "4.3.1", 44 | "@types/node": "20.1.4", 45 | "@typescript-eslint/eslint-plugin": "5.59.5", 46 | "@typescript-eslint/parser": "5.59.5", 47 | "core-js": "3.30.2", 48 | "cpy-cli": "4.2.0", 49 | "eslint": "8.40.0", 50 | "jasmine-core": "4.6.0", 51 | "karma": "6.4.2", 52 | "karma-chrome-launcher": "3.2.0", 53 | "karma-cli": "2.0.0", 54 | "karma-coverage-istanbul-reporter": "3.0.3", 55 | "karma-jasmine": "5.1.0", 56 | "karma-jasmine-html-reporter": "2.0.0", 57 | "karma-mocha-reporter": "2.2.5", 58 | "ng-packagr": "16.0.1", 59 | "puppeteer": "20.2.0", 60 | "rxjs": "7.8.1", 61 | "tailwindcss": "3.3.2", 62 | "ts-node": "10.9.1", 63 | "tslib": "2.5.0", 64 | "typescript": "5.0.4", 65 | "zone.js": "0.13.0" 66 | }, 67 | "release": { 68 | "branches": [ 69 | "master" 70 | ] 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/app/app.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

4 | Angular CodeMirror 5 |

6 |
7 | 8 |
9 |
10 |
11 | 12 |
13 |
14 | 21 |
22 | 23 |
24 | 25 | 47 |
48 | 49 |
50 |
51 |
52 | 59 |
60 |
61 | 62 |

Disable input (currently {{ readOnly ? 'on' : 'off' }})

63 |
64 |
65 |
66 |
67 | 88 |
89 | 90 | 91 |
92 | -------------------------------------------------------------------------------- /src/app/app.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed, waitForAsync } from '@angular/core/testing'; 2 | import { FormsModule } from '@angular/forms'; 3 | import { BrowserModule } from '@angular/platform-browser'; 4 | 5 | import { GhButtonModule } from '@ctrl/ngx-github-buttons'; 6 | 7 | import { CodemirrorModule } from '../lib/public_api'; 8 | import { AppComponent } from './app.component'; 9 | import { FooterComponent } from './footer.component'; 10 | 11 | describe('AppComponent', () => { 12 | beforeEach(waitForAsync(() => { 13 | TestBed.configureTestingModule({ 14 | declarations: [AppComponent, FooterComponent], 15 | imports: [BrowserModule, FormsModule, CodemirrorModule, GhButtonModule], 16 | }).compileComponents(); 17 | })); 18 | it('should create the app', waitForAsync(() => { 19 | const fixture = TestBed.createComponent(AppComponent); 20 | const app = fixture.debugElement.componentInstance; 21 | expect(app).toBeTruthy(); 22 | })); 23 | }); 24 | -------------------------------------------------------------------------------- /src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | const defaults = { 4 | markdown: 5 | '# Heading\n\nSome **bold** and _italic_ text\nBy [Scott Cooper](https://github.com/scttcper)', 6 | 'text/typescript': `const component = { 7 | name: "@ctrl/ngx-codemirror", 8 | author: "Scott Cooper", 9 | repo: "https://github.com/scttcper/ngx-codemirror" 10 | }; 11 | const hello: string = 'world';`, 12 | }; 13 | 14 | @Component({ 15 | selector: 'app-root', 16 | templateUrl: './app.component.html', 17 | }) 18 | export class AppComponent { 19 | readOnly = false; 20 | mode: keyof typeof defaults = 'markdown'; 21 | options = { 22 | lineNumbers: true, 23 | mode: this.mode, 24 | }; 25 | defaults = defaults; 26 | 27 | changeMode(): void { 28 | this.options = { 29 | ...this.options, 30 | mode: this.mode, 31 | }; 32 | } 33 | 34 | handleChange($event: Event): void { 35 | console.log('ngModelChange', $event); 36 | } 37 | 38 | clear(): void { 39 | this.defaults[this.mode] = ''; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { FormsModule } from '@angular/forms'; 3 | import { BrowserModule } from '@angular/platform-browser'; 4 | 5 | import { GhButtonModule } from '@ctrl/ngx-github-buttons'; 6 | 7 | import { CodemirrorModule } from '../lib/public_api'; 8 | import { AppComponent } from './app.component'; 9 | import { FooterComponent } from './footer.component'; 10 | 11 | @NgModule({ 12 | declarations: [AppComponent, FooterComponent], 13 | imports: [BrowserModule, FormsModule, CodemirrorModule, GhButtonModule], 14 | bootstrap: [AppComponent], 15 | }) 16 | export class AppModule {} 17 | -------------------------------------------------------------------------------- /src/app/footer.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, VERSION } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-footer', 5 | template: ` 6 | 13 | `, 14 | styles: [ 15 | ` 16 | .footer { 17 | line-height: 2; 18 | text-align: center; 19 | font-size: 69%; 20 | color: #999; 21 | font-family: var(--font-family-monospace); 22 | } 23 | `, 24 | ], 25 | }) 26 | export class FooterComponent { 27 | version = VERSION.full; 28 | } 29 | -------------------------------------------------------------------------------- /src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scttcper/ngx-codemirror/78deb0677b36c0082b025a6fb48a1fe08446e2ff/src/assets/.gitkeep -------------------------------------------------------------------------------- /src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true 3 | }; 4 | -------------------------------------------------------------------------------- /src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // The file contents for the current environment will overwrite these during build. 2 | // The build system defaults to the dev environment which uses `environment.ts`, but if you do 3 | // `ng build --env=prod` then `environment.prod.ts` will be used instead. 4 | // The list of which env maps to which file can be found in `.angular-cli.json`. 5 | 6 | export const environment = { 7 | production: false 8 | }; 9 | -------------------------------------------------------------------------------- /src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scttcper/ngx-codemirror/78deb0677b36c0082b025a6fb48a1fe08446e2ff/src/favicon.ico -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ngx-codemirror 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/lib/codemirror.component.ts: -------------------------------------------------------------------------------- 1 | import { 2 | AfterViewInit, 3 | ChangeDetectionStrategy, 4 | Component, 5 | DoCheck, 6 | ElementRef, 7 | EventEmitter, 8 | forwardRef, 9 | Input, 10 | KeyValueDiffer, 11 | KeyValueDiffers, 12 | NgZone, 13 | OnDestroy, 14 | Output, 15 | ViewChild, 16 | } from '@angular/core'; 17 | import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; 18 | import { Editor, EditorChange, EditorFromTextArea, ScrollInfo } from 'codemirror'; 19 | 20 | function normalizeLineEndings(str: string): string { 21 | if (!str) { 22 | return str; 23 | } 24 | return str.replace(/\r\n|\r/g, '\n'); 25 | } 26 | 27 | declare var require: any; 28 | declare var CodeMirror: any; 29 | 30 | @Component({ 31 | selector: 'ngx-codemirror', 32 | template: ` 33 | 42 | `, 43 | providers: [ 44 | { 45 | provide: NG_VALUE_ACCESSOR, 46 | useExisting: forwardRef(() => CodemirrorComponent), 47 | multi: true, 48 | }, 49 | ], 50 | preserveWhitespaces: false, 51 | changeDetection: ChangeDetectionStrategy.OnPush, 52 | }) 53 | export class CodemirrorComponent 54 | implements AfterViewInit, OnDestroy, ControlValueAccessor, DoCheck 55 | { 56 | /* class applied to the created textarea */ 57 | @Input() className = ''; 58 | /* name applied to the created textarea */ 59 | @Input() name = 'codemirror'; 60 | /* autofocus setting applied to the created textarea */ 61 | @Input() autoFocus = false; 62 | /** 63 | * set options for codemirror 64 | * @link http://codemirror.net/doc/manual.html#config 65 | */ 66 | @Input() 67 | set options(value: { [key: string]: any }) { 68 | this._options = value; 69 | if (!this._differ && value) { 70 | this._differ = this._differs.find(value).create(); 71 | } 72 | } 73 | /* preserve previous scroll position after updating value */ 74 | @Input() preserveScrollPosition = false; 75 | /* called when the text cursor is moved */ 76 | @Output() cursorActivity = new EventEmitter(); 77 | /* called when the editor is focused or loses focus */ 78 | @Output() focusChange = new EventEmitter(); 79 | /* called when the editor is scrolled */ 80 | // eslint-disable-next-line @angular-eslint/no-output-native 81 | @Output() scroll = new EventEmitter(); 82 | /* called when file(s) are dropped */ 83 | // eslint-disable-next-line @angular-eslint/no-output-native 84 | @Output() drop = new EventEmitter<[Editor, DragEvent]>(); 85 | /* called when codeMirror instance is initiated on the component */ 86 | @Output() codeMirrorLoaded = new EventEmitter(); 87 | @ViewChild('ref') ref!: ElementRef; 88 | value = ''; 89 | disabled = false; 90 | isFocused = false; 91 | codeMirror?: EditorFromTextArea; 92 | /** 93 | * either global variable or required library 94 | */ 95 | private _codeMirror: any; 96 | 97 | private _differ?: KeyValueDiffer; 98 | private _options: any; 99 | 100 | constructor(private _differs: KeyValueDiffers, private _ngZone: NgZone) {} 101 | 102 | get codeMirrorGlobal(): any { 103 | if (this._codeMirror) { 104 | return this._codeMirror; 105 | } 106 | 107 | // in order to allow for universal rendering, we import Codemirror runtime with `require` to prevent node errors 108 | this._codeMirror = typeof CodeMirror !== 'undefined' ? CodeMirror : import('codemirror'); 109 | return this._codeMirror; 110 | } 111 | 112 | ngAfterViewInit() { 113 | this._ngZone.runOutsideAngular(async () => { 114 | const codeMirrorObj = await this.codeMirrorGlobal; 115 | const codeMirror = codeMirrorObj?.default ? codeMirrorObj.default : codeMirrorObj; 116 | this.codeMirror = codeMirror.fromTextArea( 117 | this.ref.nativeElement, 118 | this._options, 119 | ) as EditorFromTextArea; 120 | this.codeMirror.on('cursorActivity', cm => this._ngZone.run(() => this.cursorActive(cm))); 121 | this.codeMirror.on('scroll', this.scrollChanged.bind(this)); 122 | this.codeMirror.on('blur', () => this._ngZone.run(() => this.focusChanged(false))); 123 | this.codeMirror.on('focus', () => this._ngZone.run(() => this.focusChanged(true))); 124 | this.codeMirror.on('change', (cm, change) => 125 | this._ngZone.run(() => this.codemirrorValueChanged(cm, change)), 126 | ); 127 | this.codeMirror.on('drop', (cm, e) => { 128 | this._ngZone.run(() => this.dropFiles(cm, e)); 129 | }); 130 | this.codeMirror.setValue(this.value); 131 | this.codeMirrorLoaded.emit(this); 132 | }); 133 | } 134 | ngDoCheck() { 135 | if (!this._differ) { 136 | return; 137 | } 138 | // check options have not changed 139 | const changes = this._differ.diff(this._options); 140 | if (changes) { 141 | changes.forEachChangedItem(option => 142 | this.setOptionIfChanged(option.key, option.currentValue), 143 | ); 144 | changes.forEachAddedItem(option => this.setOptionIfChanged(option.key, option.currentValue)); 145 | changes.forEachRemovedItem(option => 146 | this.setOptionIfChanged(option.key, option.currentValue), 147 | ); 148 | } 149 | } 150 | ngOnDestroy() { 151 | // is there a lighter-weight way to remove the cm instance? 152 | if (this.codeMirror) { 153 | this.codeMirror.toTextArea(); 154 | } 155 | } 156 | codemirrorValueChanged(cm: Editor, change: EditorChange) { 157 | const cmVal = cm.getValue(); 158 | if (this.value !== cmVal) { 159 | this.value = cmVal; 160 | this.onChange(this.value); 161 | } 162 | } 163 | setOptionIfChanged(optionName: string, newValue: any) { 164 | if (!this.codeMirror) { 165 | return; 166 | } 167 | 168 | // cast to any to handle strictly typed option names 169 | // could possibly import settings strings available in the future 170 | this.codeMirror.setOption(optionName as any, newValue); 171 | } 172 | focusChanged(focused: boolean) { 173 | this.onTouched(); 174 | this.isFocused = focused; 175 | this.focusChange.emit(focused); 176 | } 177 | scrollChanged(cm: Editor) { 178 | this.scroll.emit(cm.getScrollInfo()); 179 | } 180 | cursorActive(cm: Editor) { 181 | this.cursorActivity.emit(cm); 182 | } 183 | dropFiles(cm: Editor, e: DragEvent) { 184 | this.drop.emit([cm, e]); 185 | } 186 | /** Implemented as part of ControlValueAccessor. */ 187 | writeValue(value: string) { 188 | if (value === null || value === undefined) { 189 | return; 190 | } 191 | if (!this.codeMirror) { 192 | this.value = value; 193 | return; 194 | } 195 | const cur = this.codeMirror.getValue(); 196 | if (value !== cur && normalizeLineEndings(cur) !== normalizeLineEndings(value)) { 197 | this.value = value; 198 | if (this.preserveScrollPosition) { 199 | const prevScrollPosition = this.codeMirror.getScrollInfo(); 200 | this.codeMirror.setValue(this.value); 201 | this.codeMirror.scrollTo(prevScrollPosition.left, prevScrollPosition.top); 202 | } else { 203 | this.codeMirror.setValue(this.value); 204 | } 205 | } 206 | } 207 | 208 | /** Implemented as part of ControlValueAccessor. */ 209 | registerOnChange(fn: (value: string) => void) { 210 | this.onChange = fn; 211 | } 212 | /** Implemented as part of ControlValueAccessor. */ 213 | registerOnTouched(fn: () => void) { 214 | this.onTouched = fn; 215 | } 216 | /** Implemented as part of ControlValueAccessor. */ 217 | setDisabledState(isDisabled: boolean) { 218 | this.disabled = isDisabled; 219 | this.setOptionIfChanged('readOnly', this.disabled); 220 | } 221 | /** Implemented as part of ControlValueAccessor. */ 222 | private onChange = (_: any) => {}; 223 | /** Implemented as part of ControlValueAccessor. */ 224 | private onTouched = () => {}; 225 | } 226 | -------------------------------------------------------------------------------- /src/lib/codemirror.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | 3 | import { CodemirrorComponent } from './codemirror.component'; 4 | 5 | @NgModule({ 6 | exports: [CodemirrorComponent], 7 | declarations: [CodemirrorComponent], 8 | }) 9 | export class CodemirrorModule {} 10 | -------------------------------------------------------------------------------- /src/lib/ng-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../node_modules/ng-packagr/ng-package.schema.json", 3 | "allowedNonPeerDependencies": [ 4 | "@types/codemirror" 5 | ], 6 | "lib": { 7 | "entryFile": "public_api.ts" 8 | }, 9 | "dest": "../../dist" 10 | } 11 | -------------------------------------------------------------------------------- /src/lib/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@ctrl/ngx-codemirror", 3 | "version": "0.0.0-placeholder", 4 | "license": "MIT", 5 | "description": "CodeMirror wrapper for Angular", 6 | "dependencies": { 7 | "@types/codemirror": "^5.60.7" 8 | }, 9 | "peerDependencies": { 10 | "@angular/core": ">=16.0.0-0", 11 | "@angular/forms": ">=16.0.0-0", 12 | "codemirror": "^5.65.9" 13 | }, 14 | "repository": "scttcper/ngx-codemirror", 15 | "homepage": "https://github.com/scttcper/ngx-codemirror", 16 | "keywords": ["ngx", "angular", "codemirror"], 17 | "release": { 18 | "branches": [ 19 | "master" 20 | ] 21 | }, 22 | "publishConfig": { 23 | "access": "public", 24 | "provenance": true 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/lib/public_api.ts: -------------------------------------------------------------------------------- 1 | export * from './codemirror.component'; 2 | export * from './codemirror.module'; 3 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import 'codemirror/mode/javascript/javascript'; 2 | import 'codemirror/mode/markdown/markdown'; 3 | 4 | import { enableProdMode } from '@angular/core'; 5 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 6 | 7 | import { AppModule } from './app/app.module'; 8 | import { environment } from './environments/environment'; 9 | 10 | if (environment.production) { 11 | enableProdMode(); 12 | } 13 | 14 | platformBrowserDynamic() 15 | .bootstrapModule(AppModule) 16 | .catch(err => console.error(err)); 17 | -------------------------------------------------------------------------------- /src/polyfills.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file includes polyfills needed by Angular and is loaded before the app. 3 | * You can add your own extra polyfills to this file. 4 | * 5 | * This file is divided into 2 sections: 6 | * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. 7 | * 2. Application imports. Files imported after ZoneJS that should be loaded before your main 8 | * file. 9 | * 10 | * The current setup is for so-called "evergreen" browsers; the last versions of browsers that 11 | * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), 12 | * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. 13 | * 14 | * Learn more in https://angular.io/guide/browser-support 15 | */ 16 | 17 | /*************************************************************************************************** 18 | * BROWSER POLYFILLS 19 | */ 20 | 21 | /** 22 | * By default, zone.js will patch all possible macroTask and DomEvents 23 | * user can disable parts of macroTask/DomEvents patch by setting following flags 24 | * because those flags need to be set before `zone.js` being loaded, and webpack 25 | * will put import in the top of bundle, so user need to create a separate file 26 | * in this directory (for example: zone-flags.ts), and put the following flags 27 | * into that file, and then add the following code before importing zone.js. 28 | * import './zone-flags'; 29 | * 30 | * The flags allowed in zone-flags.ts are listed here. 31 | * 32 | * The following flags will work for all browsers. 33 | * 34 | * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame 35 | * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick 36 | * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames 37 | * 38 | * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js 39 | * with the following flag, it will bypass `zone.js` patch for IE/Edge 40 | * 41 | * (window as any).__Zone_enable_cross_context_check = true; 42 | * 43 | */ 44 | 45 | /*************************************************************************************************** 46 | * Zone JS is required by default for Angular itself. 47 | */ 48 | import 'zone.js'; // Included with Angular CLI. 49 | 50 | 51 | /*************************************************************************************************** 52 | * APPLICATION IMPORTS 53 | */ 54 | -------------------------------------------------------------------------------- /src/styles.css: -------------------------------------------------------------------------------- 1 | @import 'tailwindcss/base'; 2 | @import 'tailwindcss/components'; 3 | @import 'tailwindcss/utilities'; 4 | 5 | @import 'codemirror/lib/codemirror'; 6 | 7 | .ngx-codemirror { 8 | border: 1px solid #ccc; 9 | } 10 | -------------------------------------------------------------------------------- /src/test.ts: -------------------------------------------------------------------------------- 1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files 2 | import 'zone.js/testing'; 3 | import { getTestBed } from '@angular/core/testing'; 4 | import { 5 | BrowserDynamicTestingModule, 6 | platformBrowserDynamicTesting, 7 | } from '@angular/platform-browser-dynamic/testing'; 8 | 9 | // First, initialize the Angular testing environment. 10 | getTestBed().initTestEnvironment( 11 | BrowserDynamicTestingModule, 12 | platformBrowserDynamicTesting(), { 13 | teardown: { destroyAfterEach: false } 14 | }, 15 | ); 16 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | const { guessProductionMode } = require('@ngneat/tailwind'); 2 | 3 | process.env.TAILWIND_MODE = guessProductionMode() ? 'build' : 'watch'; 4 | 5 | module.exports = { 6 | prefix: '', 7 | mode: 'jit', 8 | content: ['./src/**/*.{html,ts,css,scss,sass,less,styl}'], 9 | darkMode: 'media', 10 | theme: { 11 | extend: {}, 12 | }, 13 | variants: { 14 | extend: {}, 15 | }, 16 | plugins: [require('@tailwindcss/forms'), require('@tailwindcss/typography')], 17 | }; 18 | -------------------------------------------------------------------------------- /tsconfig.app.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "./tsconfig.json", 4 | "compilerOptions": { 5 | "outDir": "./out-tsc/app", 6 | "types": [] 7 | }, 8 | "files": [ 9 | "src/main.ts", 10 | "src/polyfills.ts" 11 | ], 12 | "include": [ 13 | "src/**/*.d.ts" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "compileOnSave": false, 4 | "compilerOptions": { 5 | "baseUrl": "./", 6 | "outDir": "./dist/out-tsc", 7 | "forceConsistentCasingInFileNames": true, 8 | "strict": true, 9 | "noImplicitAny": false, 10 | "noImplicitReturns": true, 11 | "noFallthroughCasesInSwitch": true, 12 | "sourceMap": true, 13 | "declaration": false, 14 | "downlevelIteration": true, 15 | "experimentalDecorators": true, 16 | "moduleResolution": "node", 17 | "importHelpers": true, 18 | "target": "ES2022", 19 | "module": "es2020", 20 | "lib": [ 21 | "ES2022", 22 | "DOM" 23 | ], 24 | "useDefineForClassFields": false 25 | }, 26 | "angularCompilerOptions": { 27 | "strictInjectionParameters": true, 28 | "strictTemplates": true 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "./tsconfig.json", 4 | "compilerOptions": { 5 | "outDir": "./out-tsc/spec", 6 | "types": [ 7 | "jasmine" 8 | ] 9 | }, 10 | "files": [ 11 | "src/test.ts", 12 | "src/polyfills.ts" 13 | ], 14 | "include": [ 15 | "src/**/*.spec.ts", 16 | "src/**/*.d.ts" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /vercel.json: -------------------------------------------------------------------------------- 1 | { 2 | "github": { 3 | "silent": true 4 | } 5 | } 6 | --------------------------------------------------------------------------------