├── src ├── public_api.ts ├── environments │ ├── environment.prod.ts │ └── environment.ts ├── favicon.ico ├── lib │ └── angular-markdown-editor │ │ ├── models │ │ ├── icon.interface.ts │ │ ├── dictionary.interface.ts │ │ ├── iconSet.interface.ts │ │ ├── editorLocale.interface.ts │ │ ├── index.ts │ │ ├── editorInstance.interface.ts │ │ ├── editorMethod.interface.ts │ │ └── editorOption.interface.ts │ │ ├── markdown-editor-config.ts │ │ ├── index.ts │ │ ├── angular-markdown-editor.module.ts │ │ ├── global-editor-options.ts │ │ └── angular-markdown-editor.component.ts ├── typings.d.ts ├── app │ ├── app.component.ts │ ├── reactive │ │ ├── reactive.component.scss │ │ ├── reactive.component.html │ │ └── reactive.component.ts │ ├── template │ │ ├── template.component.scss │ │ ├── template.component.html │ │ └── template.component.ts │ ├── app.component.scss │ ├── app-routing.module.ts │ ├── app.component.html │ └── app.module.ts ├── styles.scss ├── index.html ├── main.ts └── polyfills.ts ├── .vscode ├── settings.json ├── extensions.json ├── tasks.json └── launch.json ├── ngcc.config.js ├── ng-package.json ├── .github ├── ISSUE_TEMPLATE │ ├── config.yml │ ├── feature_request.yml │ └── bug_report.yml ├── FUNDING.yml ├── workflows │ └── main.yml └── CODE_OF_CONDUCT.md ├── .editorconfig ├── tsconfig.app.json ├── .browserslistrc ├── tsconfig.spec.json ├── .npmignore ├── .release-it.json ├── tsconfig.json ├── .gitignore ├── LICENSE ├── .eslintrc.json ├── CHANGELOG.md ├── package.json ├── angular.json └── readme.md /src/public_api.ts: -------------------------------------------------------------------------------- 1 | export * from './lib/angular-markdown-editor/index'; 2 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "typescript.tsdk": "node_modules\\typescript\\lib" 3 | } -------------------------------------------------------------------------------- /src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true 3 | }; 4 | -------------------------------------------------------------------------------- /src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ghiscoding/angular-markdown-editor/HEAD/src/favicon.ico -------------------------------------------------------------------------------- /src/lib/angular-markdown-editor/models/icon.interface.ts: -------------------------------------------------------------------------------- 1 | export interface Icon { 2 | [libraryName: string]: string; 3 | } 4 | -------------------------------------------------------------------------------- /src/lib/angular-markdown-editor/models/dictionary.interface.ts: -------------------------------------------------------------------------------- 1 | export interface Dictionary { 2 | [word: string]: string; 3 | } 4 | -------------------------------------------------------------------------------- /ngcc.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | packages: { 3 | 'angular-markdown-editor': { 4 | ignorableDeepImportMatchers: [] 5 | }, 6 | } 7 | }; 8 | -------------------------------------------------------------------------------- /src/typings.d.ts: -------------------------------------------------------------------------------- 1 | /* SystemJS module definition */ 2 | declare var module: NodeModule; 3 | 4 | interface NodeModule { 5 | id: string; 6 | } 7 | interface JQuery { 8 | markdown: (options: any) => any; 9 | } 10 | -------------------------------------------------------------------------------- /src/lib/angular-markdown-editor/models/iconSet.interface.ts: -------------------------------------------------------------------------------- 1 | import { Icon } from './icon.interface'; 2 | 3 | export interface IconSet { 4 | [iconName: string]: { 5 | name: string, 6 | icon: Icon; 7 | }; 8 | } 9 | -------------------------------------------------------------------------------- /src/lib/angular-markdown-editor/models/editorLocale.interface.ts: -------------------------------------------------------------------------------- 1 | import { Dictionary } from './dictionary.interface'; 2 | 3 | export interface EditorLocale { 4 | language: string; 5 | dictionary: Dictionary | Dictionary[]; 6 | } 7 | -------------------------------------------------------------------------------- /ng-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/ng-packagr/ng-package.schema.json", 3 | "allowedNonPeerDependencies": [ 4 | "." 5 | ], 6 | "dest": "dist", 7 | "lib": { 8 | "entryFile": "src/public_api.ts" 9 | } 10 | } -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "andys8.jest-snippets", 4 | "angular.ng-template", 5 | "editorconfig.editorconfig", 6 | "msjsdiag.debugger-for-chrome", 7 | "dbaeumer.vscode-eslint" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Questions & Discussions 4 | url: https://github.com/ghiscoding/angular-markdown-editor/discussions 5 | about: Use GitHub discussions for message-board style questions and discussions. -------------------------------------------------------------------------------- /src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-root', 5 | templateUrl: './app.component.html', 6 | styleUrls: ['./app.component.scss'] 7 | }) 8 | export class AppComponent { 9 | title = 'Angular-Markdown-Editor'; 10 | } 11 | -------------------------------------------------------------------------------- /src/lib/angular-markdown-editor/markdown-editor-config.ts: -------------------------------------------------------------------------------- 1 | import { GlobalEditorOptions } from './global-editor-options'; 2 | import { EditorOption } from './models'; 3 | 4 | export class MarkdownEditorConfig { 5 | options: Partial; 6 | 7 | constructor() { 8 | this.options = GlobalEditorOptions; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/lib/angular-markdown-editor/models/index.ts: -------------------------------------------------------------------------------- 1 | export * from './dictionary.interface'; 2 | export * from './editorInstance.interface'; 3 | export * from './editorLocale.interface'; 4 | export * from './editorMethod.interface'; 5 | export * from './editorOption.interface'; 6 | export * from './icon.interface'; 7 | export * from './iconSet.interface'; 8 | -------------------------------------------------------------------------------- /src/lib/angular-markdown-editor/index.ts: -------------------------------------------------------------------------------- 1 | // Public classes. 2 | export * from './models/index'; 3 | 4 | // components & module 5 | export { MarkdownEditorConfig } from './markdown-editor-config'; 6 | export { AngularMarkdownEditorComponent } from './angular-markdown-editor.component'; 7 | export { AngularMarkdownEditorModule } from './angular-markdown-editor.module'; 8 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | end_of_line = lf 7 | indent_style = space 8 | indent_size = 4 9 | insert_final_newline = false 10 | trim_trailing_whitespace = true 11 | 12 | [*] 13 | indent_style = space 14 | indent_size = 2 15 | 16 | [*.md] 17 | max_line_length = 0 18 | trim_trailing_whitespace = false 19 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: ghiscoding 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: ghiscoding 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | custom: # Replace with a single custom sponsorship URL 9 | -------------------------------------------------------------------------------- /src/app/reactive/reactive.component.scss: -------------------------------------------------------------------------------- 1 | .action-buttons { 2 | padding-top: 10px; 3 | } 4 | 5 | .bold { 6 | font-weight: bold; 7 | } 8 | 9 | textarea.md-input { 10 | padding: 8px; 11 | } 12 | 13 | .outline { 14 | border: 1px solid #c0c0c0; 15 | border-radius: 4px; 16 | } 17 | 18 | .result-preview { 19 | padding: 10px; 20 | max-height: 350px; 21 | overflow: auto; 22 | } 23 | -------------------------------------------------------------------------------- /src/app/template/template.component.scss: -------------------------------------------------------------------------------- 1 | .action-buttons { 2 | padding-top: 10px; 3 | } 4 | 5 | .bold { 6 | font-weight: bold; 7 | } 8 | 9 | textarea.md-input { 10 | padding: 8px; 11 | } 12 | 13 | .outline { 14 | border: 1px solid #c0c0c0; 15 | border-radius: 4px; 16 | } 17 | 18 | .result-preview { 19 | padding: 10px; 20 | max-height: 350px; 21 | overflow: auto; 22 | } 23 | -------------------------------------------------------------------------------- /src/styles.scss: -------------------------------------------------------------------------------- 1 | /* You can add global styles to this file, and also import other style files */ 2 | .italic { 3 | font-style: italic; 4 | } 5 | .subtitle { 6 | font-size: 20px; 7 | font-style: italic; 8 | color: grey; 9 | margin-bottom: 10px; 10 | } 11 | .red { 12 | color: red; 13 | } 14 | .faded { 15 | opacity: 0.2; 16 | } 17 | .faded:hover { 18 | opacity: 0.5; 19 | } 20 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "allowSyntheticDefaultImports": true, 5 | "outDir": "../out-tsc/app", 6 | "baseUrl": "./", 7 | "types": [ 8 | "jquery" 9 | ], 10 | "paths": { 11 | "stream": [ "./node_modules/stream-browserify" ] 12 | } 13 | }, 14 | "files": [ 15 | "src/main.ts", 16 | "src/polyfills.ts" 17 | ], 18 | "include": [ 19 | "src/**/*.d.ts" 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /.browserslistrc: -------------------------------------------------------------------------------- 1 | # This file is used by the build system to adjust CSS and JS output to support the specified browsers below. 2 | # For additional information regarding the format and rule options, please see: 3 | # https://github.com/browserslist/browserslist#queries 4 | 5 | # You can see what browsers were selected by your queries by running: 6 | # npx browserslist 7 | 8 | > 0.5% 9 | last 2 versions 10 | Firefox ESR 11 | not dead 12 | not IE 9-11 # For IE 9-11 support, remove 'not'. -------------------------------------------------------------------------------- /src/app/app.component.scss: -------------------------------------------------------------------------------- 1 | $navbar-height: 56px; 2 | 3 | .body-content { 4 | margin-top: $navbar-height; 5 | } 6 | .lightblue { 7 | color: lightblue; 8 | } 9 | .red { 10 | color: red; 11 | } 12 | .faded { 13 | opacity: 0.2; 14 | } 15 | .faded:hover { 16 | opacity: 0.5; 17 | } 18 | 19 | section { 20 | margin: 0; 21 | } 22 | 23 | .demo-content { 24 | padding-top: $navbar-height; 25 | } 26 | 27 | .github-button-container { 28 | display: flex; 29 | align-items: center; 30 | } -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Angular-Markdown-Editor 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { enableProdMode } from '@angular/core'; 2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 3 | 4 | import { AppModule } from './app/app.module'; 5 | import { environment } from './environments/environment'; 6 | 7 | if (environment.production) { 8 | enableProdMode(); 9 | } 10 | 11 | platformBrowserDynamic() 12 | .bootstrapModule(AppModule, { preserveWhitespaces: true }) // preserveWhitespaces is now default to False since Angular 6 13 | .catch((err: any) => console.log(err)); 14 | -------------------------------------------------------------------------------- /src/lib/angular-markdown-editor/models/editorInstance.interface.ts: -------------------------------------------------------------------------------- 1 | import { EditorMethod } from './editorMethod.interface'; 2 | import { EditorOption } from './editorOption.interface'; 3 | 4 | export interface EditorInstance extends EditorMethod { 5 | $callback: any[]; 6 | $editable: any; 7 | $editor: any; 8 | $element: any; 9 | $handler: string[]; 10 | $isFullscreen: boolean; 11 | $isPreview: boolean; 12 | $nextTab: any[]; 13 | $ns: string; 14 | $oldContent: string; 15 | $options: EditorOption; 16 | $textarea: any; 17 | } 18 | -------------------------------------------------------------------------------- /tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "./tsconfig.json", 4 | "compilerOptions": { 5 | "allowJs": true, 6 | "allowSyntheticDefaultImports": true, 7 | "outDir": "./out-tsc/spec", 8 | "module": "commonjs", 9 | "types": [ 10 | "jest", 11 | "jest-extended", 12 | "jquery", 13 | "node" 14 | ] 15 | }, 16 | "files": [ 17 | "src/polyfills.ts" 18 | ], 19 | "include": [ 20 | "src/**/*.spec.ts", 21 | "src/**/*.d.ts" 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /tmp 5 | /out-tsc 6 | /.ng_build 7 | .ng_build 8 | .angular 9 | 10 | # dependencies 11 | /node_modules 12 | 13 | # IDE - VSCode 14 | .vscode/* 15 | !.vscode/settings.json 16 | !.vscode/tasks.json 17 | !.vscode/launch.json 18 | !.vscode/extensions.json 19 | .chrome 20 | 21 | # package manager files 22 | package-lock.json 23 | yarn.lock 24 | yarn-error.log 25 | 26 | # Tests, Report & Coverage 27 | *.spec.ts 28 | /test 29 | **/docs 30 | **/node_modules 31 | **/coverage 32 | **/jest-coverage 33 | **/*.tgz 34 | **/junit.xml 35 | **/test-report.xml 36 | **/testresult.xml -------------------------------------------------------------------------------- /src/app/app-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { Routes, RouterModule } from '@angular/router'; 3 | import { ReactiveComponent } from './reactive/reactive.component'; 4 | import { TemplateComponent } from './template/template.component'; 5 | 6 | const routes: Routes = [ 7 | { path: 'reactive-editor', component: ReactiveComponent }, 8 | { path: 'template-editor', component: TemplateComponent }, 9 | { path: '', redirectTo: '/reactive-editor', pathMatch: 'full' }, 10 | { path: '**', redirectTo: '/reactive-editor', pathMatch: 'full' } 11 | ]; 12 | 13 | @NgModule({ 14 | imports: [RouterModule.forRoot(routes, { useHash: true, relativeLinkResolution: 'legacy' })], 15 | exports: [RouterModule], 16 | }) 17 | export class AppRoutingRoutingModule { } 18 | -------------------------------------------------------------------------------- /src/lib/angular-markdown-editor/angular-markdown-editor.module.ts: -------------------------------------------------------------------------------- 1 | import { CommonModule } from '@angular/common'; 2 | import { ModuleWithProviders, NgModule } from '@angular/core'; 3 | import { AngularMarkdownEditorComponent } from './angular-markdown-editor.component'; 4 | import { EditorOption } from './models'; 5 | 6 | 7 | @NgModule({ 8 | imports: [CommonModule], 9 | declarations: [AngularMarkdownEditorComponent], 10 | exports: [AngularMarkdownEditorComponent], 11 | }) 12 | export class AngularMarkdownEditorModule { 13 | static forRoot(config: EditorOption = {}): ModuleWithProviders { 14 | return { 15 | ngModule: AngularMarkdownEditorModule, 16 | providers: [ 17 | { provide: 'config', useValue: config } 18 | ] 19 | }; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/app/reactive/reactive.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

Reactive Form - Demo

4 |
5 |
6 | 9 |
10 |
11 |
12 | 13 |
14 |
15 | 16 | 17 | 18 |
19 |
20 | 21 |
22 |

Preview

23 |
24 | 25 |
26 | -------------------------------------------------------------------------------- /.release-it.json: -------------------------------------------------------------------------------- 1 | { 2 | "git": { 3 | "commitMessage": "chore: release v${version}", 4 | "tagName": "v${version}" 5 | }, 6 | "github": { 7 | "release": true, 8 | "web": true, 9 | "releaseName": "v${version}" 10 | }, 11 | "npm": { 12 | "publishPath": "dist/" 13 | }, 14 | "hooks": { 15 | "before:init": [ 16 | "npm run lint" 17 | ], 18 | "after:bump": "npm run build", 19 | "after:git:release": "echo After git push, before GitHub Release", 20 | "after:release": "echo Successfully released ${name} v${version} to ${repo.repository}." 21 | }, 22 | "plugins": { 23 | "@release-it/conventional-changelog": { 24 | "header": "# Change Log\nAll notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.", 25 | "preset": "angular", 26 | "ignoreRecommendedBump": true, 27 | "infile": "CHANGELOG.md" 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /src/app/template/template.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

Template Form [(ngModel)] - Demo

4 |
5 |
6 | 9 |
10 |
11 |
12 | 13 |
14 |
15 | 16 | 18 | 19 |
20 |
21 | 22 |
23 |

Preview

24 |
25 | 26 |
27 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "allowSyntheticDefaultImports": true, 5 | "downlevelIteration": true, 6 | "importHelpers": true, 7 | "outDir": "./dist/out-tsc", 8 | "sourceMap": true, 9 | "declaration": false, 10 | "moduleResolution": "node", 11 | "allowUnreachableCode": false, 12 | "noUnusedParameters": false, 13 | "noUnusedLocals": false, 14 | "noImplicitReturns": true, 15 | "emitDecoratorMetadata": true, 16 | "experimentalDecorators": true, 17 | "target": "es2020", 18 | "typeRoots": [ 19 | "node_modules/@types", 20 | "typings" 21 | ], 22 | "lib": [ 23 | "es2017", 24 | "dom" 25 | ], 26 | "module": "es2020", 27 | "baseUrl": "./", 28 | "paths": { 29 | "stream": [ "./node_modules/stream-browserify" ] 30 | }, 31 | "strict": true 32 | }, 33 | "angularCompilerOptions": { 34 | "preserveWhitespaces": true, 35 | "strictMetadataEmit": true 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /tmp 5 | /out-tsc 6 | /.ng_build 7 | .ng_build 8 | 9 | # dependencies 10 | **/node_modules 11 | 12 | # IDEs and editors 13 | /.idea 14 | .project 15 | .classpath 16 | .c9/ 17 | *.launch 18 | .settings/ 19 | *.sublime-workspace 20 | 21 | # IDE - VSCode 22 | .vscode/* 23 | !.vscode/settings.json 24 | !.vscode/tasks.json 25 | !.vscode/launch.json 26 | !.vscode/extensions.json 27 | .chrome 28 | 29 | # misc 30 | /.angular/cache 31 | /.sass-cache 32 | /connect.lock 33 | /coverage 34 | /libpeerconnection.log 35 | npm-debug.log 36 | testem.log 37 | /typings 38 | yarn-error.log 39 | 40 | # System Files 41 | .DS_Store 42 | Thumbs.db 43 | 44 | # Distribution/Demo folders 45 | dist/ 46 | docs/ 47 | 48 | # Tests Report & Coverage 49 | **/node_modules 50 | **/coverage 51 | **/jest-coverage 52 | **/dist 53 | **/*.tgz 54 | **/junit.xml 55 | **/test-report.xml 56 | **/testresult.xml 57 | 58 | # Node Version Manager files 59 | .naverc -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017-present, Angular-Markdown-Editor - Ghislain B. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /.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 | ], 15 | "createDefaultProgram": true 16 | }, 17 | "extends": [ 18 | "plugin:@angular-eslint/recommended", 19 | "plugin:@angular-eslint/template/process-inline-templates" 20 | ], 21 | "rules": { 22 | "@angular-eslint/directive-selector": [ 23 | "error", 24 | { 25 | "type": "attribute", 26 | "prefix": "app", 27 | "style": "camelCase" 28 | } 29 | ], 30 | "@angular-eslint/component-selector": [ 31 | "error", 32 | { 33 | "type": "element", 34 | "style": "kebab-case" 35 | } 36 | ], 37 | "@angular-eslint/no-output-on-prefix": 0, 38 | "@angular-eslint/no-output-rename": 0 39 | } 40 | }, 41 | { 42 | "files": [ 43 | "*.html" 44 | ], 45 | "extends": [ 46 | "plugin:@angular-eslint/template/recommended" 47 | ], 48 | "rules": {} 49 | } 50 | ] 51 | } 52 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "Build GitHub Prod Demo", 6 | "type": "shell", 7 | "command": "yarn run build:demo", 8 | "problemMatcher": [] 9 | }, 10 | { 11 | "label": "Build Library", 12 | "type": "shell", 13 | "command": "yarn run build", 14 | "problemMatcher": [] 15 | }, 16 | { 17 | "label": "Publish Library to NPM", 18 | "type": "shell", 19 | "command": "npm publish dist", 20 | "problemMatcher": [] 21 | }, 22 | { 23 | "label": "Publish Library with (Beta Tag) to NPM", 24 | "type": "shell", 25 | "command": "npm publish dist --tag beta", 26 | "problemMatcher": [] 27 | }, 28 | { 29 | "label": "Start Library Development", 30 | "type": "shell", 31 | "command": "yarn start", 32 | "problemMatcher": [] 33 | }, 34 | { 35 | "label": "Prepare Release as Minor (0.x.0) -- Make sure to NPM Publish DIST folder", 36 | "type": "shell", 37 | "command": "yarn run release -- --release-as minor --infile ./CHANGELOG.md", 38 | "problemMatcher": [] 39 | }, 40 | { 41 | "label": "Prepare Release as Patch (0.0.x) -- Make sure to NPM Publish DIST folder", 42 | "type": "shell", 43 | "command": "yarn run release -- --release-as patch --infile ./CHANGELOG.md", 44 | "problemMatcher": [] 45 | } 46 | ] 47 | } -------------------------------------------------------------------------------- /src/app/app.component.html: -------------------------------------------------------------------------------- 1 | 33 | 34 |
35 |
36 | 37 |
38 |
-------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: GitHub Actions 2 | on: 3 | # Trigger the workflow on push or pull request, 4 | # but only for the master branch on Push and any branches on PR 5 | push: 6 | branches: 7 | - master 8 | pull_request: 9 | branches: 10 | - '**' 11 | env: 12 | CI: true 13 | PREFERRED_WORKSPACE_MANAGER: yarn 14 | 15 | jobs: 16 | run: 17 | name: Node ${{ matrix.node }} 18 | runs-on: ubuntu-latest 19 | 20 | strategy: 21 | fail-fast: false 22 | matrix: 23 | node: [20] 24 | 25 | steps: 26 | - name: Clone repository 27 | uses: actions/checkout@v4 28 | with: 29 | fetch-depth: 3 30 | 31 | - uses: actions/cache@v4 32 | with: 33 | path: ~/.cache/yarn 34 | key: ${{ runner.os }}-node-${{ hashFiles('**/yarn.lock') }} 35 | restore-keys: | 36 | ${{ runner.os }}-node- 37 | - name: Set Node.js version 38 | uses: actions/setup-node@v4 39 | with: 40 | node-version: ${{ matrix.node }} 41 | 42 | - run: node --version 43 | - run: npm --version 44 | 45 | - name: Install npm/yarn dependencies 46 | run: yarn install 47 | 48 | - name: Website Prod Build (GitHub demo site) 49 | run: yarn build:demo 50 | 51 | # always deploy (re-publish) GitHub demo page with a Prod build 52 | - name: Deploy to gh-pages 53 | uses: peaceiris/actions-gh-pages@v4 54 | if: github.ref == 'refs/heads/master' 55 | with: 56 | github_token: ${{ secrets.GITHUB_TOKEN }} 57 | publish_dir: ./docs 58 | -------------------------------------------------------------------------------- /src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { AppRoutingRoutingModule } from './app-routing.module'; 2 | import { BrowserModule } from '@angular/platform-browser'; 3 | import { NgModule } from '@angular/core'; 4 | import { FormsModule, ReactiveFormsModule } from '@angular/forms'; 5 | import { MarkdownModule, MarkedOptions } from 'ngx-markdown'; 6 | 7 | import { AppComponent } from './app.component'; 8 | 9 | // import our custom module, library created using this article 10 | // https://medium.com/@ngl817/building-an-angular-4-component-library-with-the-angular-cli-and-ng-packagr-53b2ade0701e 11 | import { AngularMarkdownEditorModule } from '../lib/angular-markdown-editor/angular-markdown-editor.module'; 12 | import { ReactiveComponent } from './reactive/reactive.component'; 13 | import { TemplateComponent } from './template/template.component'; 14 | 15 | // @dynamic 16 | @NgModule({ 17 | declarations: [ 18 | AppComponent, 19 | ReactiveComponent, 20 | TemplateComponent, 21 | ], 22 | imports: [ 23 | AppRoutingRoutingModule, 24 | BrowserModule, 25 | FormsModule, 26 | MarkdownModule.forRoot({ 27 | // loader: HttpClient, // optional, only if you use [src] attribute 28 | markedOptions: { 29 | provide: MarkedOptions, 30 | useValue: { 31 | gfm: true, 32 | breaks: false, 33 | pedantic: false, 34 | smartLists: true, 35 | smartypants: false, 36 | }, 37 | }, 38 | }), 39 | ReactiveFormsModule, 40 | AngularMarkdownEditorModule.forRoot({ 41 | // add any Global Options/Config you might want 42 | // to avoid passing the same options over and over in each components of your App 43 | iconlibrary: 'fa' 44 | }) 45 | ], 46 | bootstrap: [AppComponent] 47 | }) 48 | export class AppModule { } 49 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. 3 | 4 | ## [3.1.1](https://github.com/ghiscoding/angular-markdown-editor/compare/v3.1.0...v3.1.1) (2024-09-21) 5 | 6 | 7 | ### Bug Fixes 8 | 9 | * update github link of bootstrap-markdown dep, fixes [#64](https://github.com/ghiscoding/angular-markdown-editor/issues/64) ([40da5ef](https://github.com/ghiscoding/angular-markdown-editor/commit/40da5ef47fd009f8c68357851bcf4f6ea27a979f)) 10 | 11 | # [3.1.0](https://github.com/ghiscoding/angular-markdown-editor/compare/v3.0.2...v3.1.0) (2023-04-07) 12 | 13 | # Change Log 14 | All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. 15 | 16 | ## [3.0.2](https://github.com/ghiscoding/angular-markdown-editor/compare/v3.0.1...v3.0.2) (2022-12-12) 17 | 18 | ### Bug Fixes 19 | 20 | fix: forRoot now requires explicit type ModuleWithProviders, fixes #39 ([973b72](https://github.com/ghiscoding/angular-markdown-editor/commit/973b72f75350fffc36d8d8ddcac769edf327fbfa)) 21 | 22 | ## [3.0.0](https://github.com/ghiscoding/angular-markdown-editor/compare/v2.0.2...v3.0.0) (2022-09-12) 23 | 24 | 25 | ### Features 26 | 27 | * **core:** upgrade to Angular 14 and Bootstrap 4-5, closes [#33](https://github.com/ghiscoding/angular-markdown-editor/issues/33) ([dd0f3df](https://github.com/ghiscoding/angular-markdown-editor/commit/dd0f3dfe591e764855767622aea909973964a177)) 28 | 29 | ### [2.0.2](https://github.com/ghiscoding/angular-markdown-editor/compare/v1.1.5...v2.0.2) (2018-09-24) 30 | 31 | ### Features 32 | `Angular-Markdown-Editor` code base was totally rewritten to support Angular 4+. The demo uses [ngx-markdown](https://github.com/jfcere/ngx-markdown), however it is not a deep dependency, you can use any Markdown Parser you wish to use. -------------------------------------------------------------------------------- /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 | /** IE11 was throwing console errors without these (cannot find "includes") */ 23 | // import 'core-js/es7/array'; 24 | 25 | /** Evergreen browsers require these. **/ 26 | // import 'core-js/es6/reflect'; 27 | 28 | /** Support Custom Event */ 29 | import 'custom-event-polyfill'; // npm install custom-event-polyfill 30 | 31 | 32 | 33 | /*************************************************************************************************** 34 | * Zone JS is required by Angular itself. 35 | */ 36 | import 'zone.js'; // Included with Angular CLI. 37 | 38 | 39 | 40 | /*************************************************************************************************** 41 | * APPLICATION IMPORTS 42 | */ 43 | 44 | /** 45 | * Date, currency, decimal and percent pipes. 46 | * Needed for: All but Chrome, Firefox, Edge, IE11 and Safari 10 47 | */ 48 | // import 'intl'; // Run `npm install --save intl`. 49 | /** 50 | * Need to import at least one locale-data with intl. 51 | */ 52 | // import 'intl/locale-data/jsonp/en'; 53 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yml: -------------------------------------------------------------------------------- 1 | name: 🚀 New feature proposal 2 | description: Propose a new feature to be added to Angular-Markdown-Editor 3 | labels: ['enhancement: pending triage'] 4 | body: 5 | - type: markdown 6 | attributes: 7 | value: | 8 | Thanks for your interest in the project and taking the time to fill out this feature report! 9 | - type: textarea 10 | id: feature-description 11 | attributes: 12 | label: Clear and concise description of the problem 13 | description: 'As a developer using Angular-Markdown-Editor I want [goal / wish] so that [benefit]. If you intend to submit a PR for this issue, tell us in the description. Thanks!' 14 | validations: 15 | required: true 16 | - type: textarea 17 | id: suggested-solution 18 | attributes: 19 | label: Suggested solution 20 | description: We could provide following implementation... 21 | validations: 22 | required: true 23 | - type: textarea 24 | id: alternative 25 | attributes: 26 | label: Alternative 27 | description: Clear and concise description of any alternative solutions or features you've considered. 28 | - type: textarea 29 | id: additional-context 30 | attributes: 31 | label: Additional context 32 | description: Any other context or screenshots about the feature request here. 33 | - type: checkboxes 34 | id: checkboxes 35 | attributes: 36 | label: Validations 37 | description: Before submitting the issue, please make sure you do the following 38 | options: 39 | - label: Follow our [Code of Conduct](https://github.com/ghiscoding/angular-markdown-editor/blob/master/.github/CODE_OF_CONDUCT.md) 40 | required: true 41 | - label: Read the [HOWTO - Step by Step](https://github.com/ghiscoding/angular-markdown-editor/wiki/HOWTO---Step-by-Step). 42 | required: true 43 | - label: Check that there isn't already an issue that request the same feature to avoid creating a duplicate. 44 | required: true -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "type": "chrome", 6 | "request": "launch", 7 | "name": "Chrome Debugger", 8 | "url": "http://localhost:4300", 9 | "breakOnLoad": false, 10 | "webRoot": "${workspaceFolder}" 11 | }, 12 | { 13 | "type": "chrome", 14 | "request": "attach", 15 | "name": "Attach to Chrome", 16 | "port": 9222, 17 | "webRoot": "${workspaceFolder}" 18 | }, 19 | { 20 | "type": "node", 21 | "request": "launch", 22 | "name": "Jest All Tests", 23 | "program": "${workspaceFolder}/node_modules/.bin/jest", 24 | "args": [ 25 | "--runInBand", 26 | "--config", 27 | "${workspaceFolder}/test/jest.config.js" 28 | ], 29 | "console": "internalConsole", 30 | "internalConsoleOptions": "neverOpen", 31 | "windows": { 32 | "program": "${workspaceFolder}/node_modules/jest/bin/jest", 33 | } 34 | }, 35 | { 36 | "type": "node", 37 | "request": "launch", 38 | "name": "Jest Current Spec File", 39 | "program": "${workspaceFolder}/node_modules/.bin/jest", 40 | "args": [ 41 | "--runInBand", 42 | "${fileBasename}", 43 | "--config", 44 | "${workspaceFolder}/test/jest.config.js" 45 | ], 46 | "console": "internalConsole", 47 | "internalConsoleOptions": "neverOpen", 48 | "disableOptimisticBPs": true, 49 | "windows": { 50 | "program": "${workspaceFolder}/node_modules/jest/bin/jest", 51 | } 52 | }, 53 | { 54 | "type": "node", 55 | "request": "launch", 56 | "name": "Jest Selected Test Name", 57 | "program": "${workspaceFolder}/node_modules/.bin/jest", 58 | "args": [ 59 | "${fileBasename}", 60 | "--config", 61 | "${workspaceFolder}/test/jest.config.js", 62 | "-t=${selectedText}$", 63 | "--watch" 64 | ], 65 | "console": "internalConsole", 66 | "internalConsoleOptions": "neverOpen", 67 | "windows": { 68 | "program": "${workspaceFolder}/node_modules/jest/bin/jest", 69 | } 70 | } 71 | ] 72 | } 73 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: 🐞 Bug report 2 | description: Report an issue with Angular-Markdown-Editor 3 | labels: [pending triage] 4 | body: 5 | - type: markdown 6 | attributes: 7 | value: | 8 | Thanks for taking the time to fill out this bug report! 9 | - type: textarea 10 | id: bug-description 11 | attributes: 12 | label: Describe the bug 13 | description: A clear and concise description of what the bug is. If you intend to submit a PR for this issue, tell us in the description. Thanks! 14 | placeholder: Bug description 15 | validations: 16 | required: true 17 | - type: textarea 18 | id: reproduction 19 | attributes: 20 | label: Reproduction 21 | description: Please provide a way to reproduce the problem you ran into. A [minimal reproduction](https://stackoverflow.com/help/minimal-reproducible-example) would be nice unless you are absolutely sure that the issue is obvious and the provided information is enough to understand the problem. 22 | placeholder: Reproduction 23 | validations: 24 | required: true 25 | - type: textarea 26 | id: system-info 27 | attributes: 28 | label: Environment Info 29 | description: versions number of each systems and libs (Angular, Angular-Markdown-Editor, TypeScript) 30 | render: shell 31 | placeholder: | 32 | Angular (x.y) 33 | Angular-Markdown-Editor (x.y) 34 | TypeScript (x.y) 35 | Browser(s) 36 | System OS 37 | validations: 38 | required: true 39 | - type: checkboxes 40 | id: checkboxes 41 | attributes: 42 | label: Validations 43 | description: Before submitting the issue, please make sure you do the following 44 | options: 45 | - label: Follow our [Code of Conduct](https://github.com/ghiscoding/angular-markdown-editor/blob/master/.github/CODE_OF_CONDUCT.md) 46 | required: true 47 | - label: Read the [HOWTO - Step by Step](https://github.com/ghiscoding/angular-markdown-editor/wiki/HOWTO---Step-by-Step). 48 | required: true 49 | - label: Check that there isn't [already an issue](https://github.com/ghiscoding/angular-markdown-editor/issues) that reports the same bug to avoid creating a duplicate. 50 | required: true 51 | - label: Check that this is a concrete bug. For Q&A open a [GitHub Discussion](https://github.com/ghiscoding/angular-markdown-editor/discussions). 52 | required: true 53 | - label: The provided reproduction is a [minimal reproducible example](https://stackoverflow.com/help/minimal-reproducible-example) of the bug. 54 | required: true -------------------------------------------------------------------------------- /src/lib/angular-markdown-editor/global-editor-options.ts: -------------------------------------------------------------------------------- 1 | import { EditorOption } from './models/editorOption.interface'; 2 | 3 | export const GlobalEditorOptions: EditorOption = { 4 | autofocus: false, 5 | disabledButtons: [], 6 | dropZoneOptions: null, 7 | enableDropDataUri: false, 8 | footer: '', 9 | height: 'inherit', 10 | hiddenButtons: [], 11 | hideable: false, 12 | iconlibrary: 'fa', 13 | initialstate: 'editor', 14 | language: 'fr', 15 | additionalButtons: [ 16 | [{ 17 | name: 'groupFont', 18 | data: [{ 19 | name: 'cmdStrikethrough', 20 | toggle: false, 21 | title: 'Strikethrough', 22 | icon: { 23 | fa: 'fa fa-strikethrough' 24 | }, 25 | callback: (e: any) => { 26 | // Give/remove ~~ surround the selection 27 | let chunk; 28 | let cursor; 29 | const selected = e.getSelection(); 30 | const content = e.getContent(); 31 | 32 | if (selected.length === 0) { 33 | // Give extra word 34 | chunk = e.__localize('strikethrough'); 35 | } else { 36 | chunk = selected.text; 37 | } 38 | 39 | // transform selection and set the cursor into chunked text 40 | if (content.substr(selected.start - 2, 2) === '~~' && 41 | content.substr(selected.end, 2) === '~~') { 42 | e.setSelection(selected.start - 2, selected.end + 2); 43 | e.replaceSelection(chunk); 44 | cursor = selected.start - 2; 45 | } else { 46 | e.replaceSelection('~~' + chunk + '~~'); 47 | cursor = selected.start + 2; 48 | } 49 | 50 | // Set the cursor 51 | e.setSelection(cursor, cursor + chunk.length); 52 | } 53 | }] 54 | }, 55 | { 56 | name: 'groupMisc', 57 | data: [{ 58 | name: 'cmdTable', 59 | toggle: false, 60 | title: 'Table', 61 | icon: { 62 | fa: 'fa fa-table' 63 | }, 64 | callback: (e: any) => { 65 | // Replace selection with some drinks 66 | let chunk; 67 | let cursor; 68 | const selected = e.getSelection(); 69 | 70 | chunk = '\n| Tables | Are | Cool | \n' 71 | + '| ------------- |:-------------:| -----:| \n' 72 | + '| col 3 is | right-aligned | $1600 | \n' 73 | + '| col 2 is | centered | $12 | \n' 74 | + '| zebra stripes | are neat | $1 |'; 75 | 76 | // transform selection and set the cursor into chunked text 77 | e.replaceSelection(chunk); 78 | cursor = selected.start; 79 | 80 | // Set the cursor 81 | e.setSelection(cursor, cursor + chunk.length); 82 | } 83 | }] 84 | }] 85 | ] 86 | }; 87 | -------------------------------------------------------------------------------- /src/app/reactive/reactive.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, ViewEncapsulation } from '@angular/core'; 2 | import { FormBuilder, FormGroup } from '@angular/forms'; 3 | import { MarkdownService } from 'ngx-markdown'; 4 | import { EditorInstance, EditorOption } from '../../lib/angular-markdown-editor'; 5 | 6 | @Component({ 7 | templateUrl: './reactive.component.html', 8 | encapsulation: ViewEncapsulation.None, 9 | styleUrls: ['./reactive.component.scss'] 10 | }) 11 | export class ReactiveComponent implements OnInit { 12 | bsEditorInstance!: EditorInstance; 13 | markdownText = ''; 14 | showEditor = true; 15 | templateForm!: FormGroup; 16 | editorOptions!: EditorOption; 17 | 18 | constructor( 19 | private fb: FormBuilder, 20 | private markdownService: MarkdownService 21 | ) { } 22 | 23 | ngOnInit() { 24 | this.editorOptions = { 25 | autofocus: false, 26 | iconlibrary: 'fa', 27 | savable: false, 28 | onFullscreenExit: (e) => this.hidePreview(), 29 | onShow: (e) => this.bsEditorInstance = e, 30 | parser: (val) => this.parse(val) 31 | }; 32 | 33 | // put the text completely on the left to avoid extra white spaces 34 | this.markdownText = 35 | `### Markdown example 36 | --- 37 | This is an **example** where we bind a variable to the \`markdown\` component that is also bind to the editor. 38 | #### example.component.ts 39 | \`\`\`javascript 40 | function hello() { 41 | alert('Hello World'); 42 | } 43 | \`\`\` 44 | #### example.component.html 45 | \`\`\`html 46 | 47 | 48 | \`\`\``; 49 | 50 | this.buildForm(this.markdownText); 51 | this.onFormChanges(); 52 | } 53 | 54 | 55 | buildForm(markdownText: string) { 56 | this.templateForm = this.fb.group({ 57 | body: [markdownText], 58 | isPreview: [true] 59 | }); 60 | } 61 | 62 | /** highlight all code found, needs to be wrapped in timer to work properly */ 63 | highlight() { 64 | setTimeout(() => { 65 | this.markdownService.highlight(); 66 | }); 67 | } 68 | 69 | hidePreview() { 70 | if (this.bsEditorInstance && this.bsEditorInstance.hidePreview) { 71 | this.bsEditorInstance.hidePreview(); 72 | } 73 | } 74 | 75 | showFullScreen(isFullScreen: boolean) { 76 | if (this.bsEditorInstance && this.bsEditorInstance.setFullscreen) { 77 | this.bsEditorInstance.showPreview(); 78 | this.bsEditorInstance.setFullscreen(isFullScreen); 79 | } 80 | } 81 | 82 | parse(inputValue: string) { 83 | const markedOutput = this.markdownService.parse(inputValue.trim()); 84 | this.highlight(); 85 | 86 | return markedOutput; 87 | } 88 | 89 | onFormChanges(): void { 90 | this.templateForm.valueChanges.subscribe(formData => { 91 | if (formData) { 92 | this.markdownText = formData.body; 93 | } 94 | }); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-markdown-editor", 3 | "version": "3.1.1", 4 | "description": "Bootstrap Markdown components made available in Angular", 5 | "keywords": [ 6 | "angular", 7 | "plugin", 8 | "bootstrap-markdown" 9 | ], 10 | "license": "MIT", 11 | "author": "Ghislain B.", 12 | "homepage": "https://ghiscoding.github.io/angular-markdown-editor/", 13 | "bugs": { 14 | "url": "https://github.com/ghiscoding/angular-markdown-editor/issues" 15 | }, 16 | "scripts": { 17 | "ng": "ng", 18 | "start": "ng serve --port 4300", 19 | "postinstall": "ngcc", 20 | "prebuild": "npm-run-all delete:dist lint", 21 | "build": "ng-packagr -p ng-package.json", 22 | "delete:dist": "cross-env rimraf dist", 23 | "lint": "ng lint", 24 | "packagr": "ng-packagr -p ng-package.json", 25 | "build:demo": "ng build --configuration=production --base-href=\"\"", 26 | "pack-lib": "npm pack ./dist", 27 | "release": "release-it --only-version" 28 | }, 29 | "repository": { 30 | "type": "git", 31 | "url": "http://github.com/ghiscoding/angular-markdown-editor" 32 | }, 33 | "main": "src/lib/angular-markdown-editor/index", 34 | "private": false, 35 | "funding": { 36 | "type": "ko_fi", 37 | "url": "https://ko-fi.com/ghiscoding" 38 | }, 39 | "dependencies": { 40 | "bootstrap": ">=4.6.2", 41 | "bootstrap-markdown": "refactory-id/bootstrap-markdown", 42 | "font-awesome": "^4.7.0", 43 | "jquery": "^3.7.1" 44 | }, 45 | "devDependencies": { 46 | "@angular-devkit/build-angular": "^14.2.13", 47 | "@angular-eslint/builder": "^14.4.0", 48 | "@angular-eslint/eslint-plugin": "^14.4.0", 49 | "@angular-eslint/eslint-plugin-template": "^14.4.0", 50 | "@angular-eslint/schematics": "^14.4.0", 51 | "@angular-eslint/template-parser": "^14.4.0", 52 | "@angular/animations": "^14.3.0", 53 | "@angular/cli": "^14.2.13", 54 | "@angular/common": "^14.3.0", 55 | "@angular/compiler": "^14.3.0", 56 | "@angular/compiler-cli": "^14.3.0", 57 | "@angular/core": "^14.3.0", 58 | "@angular/forms": "^14.3.0", 59 | "@angular/language-service": "^14.3.0", 60 | "@angular/platform-browser": "^14.3.0", 61 | "@angular/platform-browser-dynamic": "^14.3.0", 62 | "@angular/router": "^14.3.0", 63 | "@release-it/conventional-changelog": "^7.0.2", 64 | "@types/jquery": "^3.5.30", 65 | "@types/node": "^18.19.50", 66 | "@typescript-eslint/eslint-plugin": "^6.21.0", 67 | "@typescript-eslint/parser": "^6.1.0", 68 | "autoprefixer": "^10.4.20", 69 | "cross-env": "^7.0.3", 70 | "custom-event-polyfill": "^1.0.7", 71 | "eslint": "^8.57.1", 72 | "ng-packagr": "^14.3.0", 73 | "ngx-markdown": "^14.0.1", 74 | "npm-run-all2": "^6.2.3", 75 | "release-it": "^16.3.0", 76 | "rimraf": "^5.0.10", 77 | "rxjs": "^6.6.7", 78 | "sass": "^1.79.3", 79 | "ts-node": "^10.9.2", 80 | "tslib": "^2.7.0", 81 | "typescript": "~4.7.4", 82 | "zone.js": "~0.11.8" 83 | }, 84 | "engines": { 85 | "node": ">=14.17.0", 86 | "npm": ">=6.14.13" 87 | }, 88 | "resolutions": { 89 | "caniuse-lite": "1.0.30001517", 90 | "semver": "^7.5.4" 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/lib/angular-markdown-editor/models/editorMethod.interface.ts: -------------------------------------------------------------------------------- 1 | import { EditorOption } from './editorOption.interface'; 2 | 3 | export interface EditorMethod { 4 | /** add a list bullet "-" at a given index position */ 5 | addBullet: (index: number) => void; 6 | 7 | /** add a list number nullet "1." at a given index position */ 8 | addNumberedBullet: (index: number, num: number) => void; 9 | 10 | /** Trigger a blur event */ 11 | blur: (event: any) => EditorOption; 12 | 13 | /** Trigger a change event */ 14 | change: (event: any) => EditorOption; 15 | 16 | /** Disabled a button by name that described in buttons or additionalButtons arrays. Passing all will disabled all buttons. */ 17 | disableButtons: (name: string) => EditorOption; 18 | 19 | /** Enabled a button by name that described in buttons or additionalButtons arrays. Passing all will enabled all buttons. */ 20 | enableButtons: (name: string) => EditorOption; 21 | 22 | /** Check if the Event Name is supported by the Editor */ 23 | eventSupported: (eventName: string) => boolean; 24 | 25 | /** Trigger a focus event */ 26 | focus: (event: any) => EditorOption; 27 | 28 | /** Find some words/sentence within the editor and returned selection object (containing word position and other useful information). */ 29 | findSelection: (words: string) => string; 30 | 31 | /** Get the list bullet "-" at a given start index position */ 32 | getBulletNumber: (startIndex: number) => number | string; 33 | 34 | /** Get the editor content */ 35 | getContent: () => string; 36 | 37 | /** Get the next tab memory. Returned selection object (containing word position and other useful information) */ 38 | getNextTab: () => any; 39 | 40 | /** Get the current selected chunk of words within the editor. */ 41 | getSelection: () => string; 42 | 43 | /** Hide a button by name that described in buttons or additionalButtons arrays. */ 44 | hideButtons: (name: string) => EditorOption; 45 | 46 | /** Toggle off the editor visibility */ 47 | hidePreview: () => EditorOption; 48 | 49 | /** Insert text content to an index position in the textarea element */ 50 | insertContent: (index: number, content: string) => void; 51 | 52 | /** Check the editor content state, return true if the original content was changed */ 53 | isDirty: () => boolean; 54 | 55 | /** Event triggered by the keyboard up (when releasing the key) */ 56 | keyup: (e: any) => void; 57 | 58 | /** Get the parsed editor content */ 59 | parseContent: (val: string) => string; 60 | 61 | /** Replace the current selected chunk of words within the editor with any content. */ 62 | replaceSelection: (content: string) => EditorOption; 63 | 64 | /** Trigger a select event */ 65 | select: (event: any) => EditorOption; 66 | 67 | /** Set the editor content */ 68 | setContent: (content: string) => EditorOption; 69 | 70 | /** Set the editor to full screen mode */ 71 | setFullscreen: (mode: boolean) => void; 72 | 73 | /** Tell the editor to select a span of words from start to end at next tab keypress event. */ 74 | setNextTab: (start: number, end: number) => any; 75 | 76 | /** Tell the editor to select a span of words from start to end. */ 77 | setSelection: (start: number, end: number) => string; 78 | 79 | /** Show a button by name that described in buttons or additionalButtons arrays. */ 80 | showButtons: (name: string) => EditorOption; 81 | 82 | /** Toggle on the editor visibility */ 83 | showEditor: () => void; 84 | 85 | /** Toggle on the preview visibility */ 86 | showPreview: () => EditorOption; 87 | } 88 | -------------------------------------------------------------------------------- /.github/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Angular-Markdown-Editor 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 within all project spaces, and it also applies when 49 | an individual is representing the project or its community in public spaces. 50 | Examples of representing a project or community include using an official 51 | project e-mail address, posting via an official social media account, or acting 52 | as an appointed representative at an online or offline event. Representation of 53 | a project may be 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. 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 | -------------------------------------------------------------------------------- /src/lib/angular-markdown-editor/models/editorOption.interface.ts: -------------------------------------------------------------------------------- 1 | import { IconSet } from './iconSet.interface'; 2 | 3 | export interface EditorOption { 4 | /** Indicates that editor will focused after instantiated. Default to false */ 5 | autofocus?: boolean; 6 | 7 | /** Array or additional icon set that can be added to the editor as new icon set family. Default is empty string */ 8 | customIcons?: any; 9 | 10 | /** Enabling the use of DropZone data URI, Defaults to false */ 11 | enableDropDataUri?: boolean; 12 | 13 | /** What is the initial state of the editor ('preview', 'editor', 'fullscreen') */ 14 | initialstate?: 'preview' | 'editor' | 'fullscreen'; 15 | 16 | /** Indicates that editor will have save button and action. Default to false */ 17 | savable?: boolean; 18 | 19 | /** If set to true then the editor will be hidden on blur event. Default to false */ 20 | hideable?: boolean; 21 | 22 | /** The editor width. Default to inherit. You could supply any numerical value (that will be set as css), or supply valid Bootstrap class (something like span2) */ 23 | width?: number | string; 24 | 25 | /** The editor height. Default to inherit */ 26 | height?: number | string; 27 | 28 | /** 29 | * Option to disable or change the resize property, possible values none,both,horizontal,vertical. Default none 30 | * if this option is enabled, the user will be able to resize the editor and preview screen. 31 | */ 32 | resize?: string; 33 | 34 | /** The icon library to use. The only supported library at this point is Font Awesome v4.7 (fa). Default to "fa" */ 35 | iconlibrary?: string; 36 | 37 | /** Localization setting. Default to en */ 38 | language?: string; 39 | 40 | /** Footer dom. Can be string or callback. Default is empty string */ 41 | footer?: any; 42 | 43 | /** Contains enable (bool) and icons (object) keys. */ 44 | fullscreen?: { 45 | enable: boolean; 46 | icons: IconSet; 47 | }; 48 | 49 | /** Array or additional buttons that can be added to the editor. Default is empty string */ 50 | additionalButtons?: any[]; 51 | 52 | /** Array or string of button names to be hidden. Default is empty string */ 53 | hiddenButtons?: any[]; 54 | 55 | /** Array or string of button names to be disabled. Default is empty string */ 56 | disabledButtons?: any[]; 57 | 58 | /** Enables integration with DropZone for allowing file upload/linking via drag&drop. The options are directly passed to the DropZone library. Valid options are described here */ 59 | dropZoneOptions?: any; 60 | 61 | // methods 62 | parser?: (val: string) => void; 63 | 64 | // -- 65 | // Event Callbacks 66 | 67 | /** Triggered when the editor shows up */ 68 | onShow?: (e: any) => void; 69 | 70 | /** Triggered when the "Preview" button or "showPreview" method are called */ 71 | onPreview?: (e: any) => void; 72 | 73 | /** Triggered when the editor goes out of Preview mode */ 74 | onPreviewEnd?: (e: any) => void; 75 | 76 | /** Triggered when the "Save" button is clicked (requires the flag "savable: true") */ 77 | onSave?: (e: any) => void; 78 | 79 | /** Triggered after the editor window (textarea) loses the focus */ 80 | onBlur?: (e: any) => void; 81 | 82 | /** Triggered after the editor window (textarea) gains the focus */ 83 | onFocus?: (e: any) => void; 84 | 85 | /** Triggered after any characters are typed in th editor or any buttons are clicked */ 86 | onChange?: (e: any) => void; 87 | 88 | /** Triggered when the "Full Screen" icon or "setFullscreen" method are called */ 89 | onFullscreen?: (e: any) => void; 90 | 91 | /** Triggered when the editor goes out of Full Screen mode */ 92 | onFullscreenExit?: (e: any) => void; 93 | 94 | /** Triggered when a selection (word select) is made in the editor. It can also be triggered by a button which changes text */ 95 | onSelect?: (e: any) => void; 96 | } 97 | -------------------------------------------------------------------------------- /src/app/template/template.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, ViewEncapsulation } from '@angular/core'; 2 | import { FormBuilder, FormGroup } from '@angular/forms'; 3 | import { MarkdownService } from 'ngx-markdown'; 4 | import { EditorInstance, EditorLocale, EditorOption } from '../../lib/angular-markdown-editor'; 5 | 6 | @Component({ 7 | templateUrl: './template.component.html', 8 | encapsulation: ViewEncapsulation.None, 9 | styleUrls: ['./template.component.scss'] 10 | }) 11 | export class TemplateComponent implements OnInit { 12 | bsEditorInstance!: EditorInstance; 13 | markdownText = ''; 14 | showEditor = true; 15 | templateForm!: FormGroup; 16 | editorOptions!: EditorOption; 17 | locale: EditorLocale = { 18 | language: 'fr', 19 | dictionary: { 20 | 'Bold': 'Gras', 21 | 'Italic': 'Italique', 22 | 'Heading': 'Titre', 23 | 'URL/Link': 'Insérer un lien HTTP', 24 | 'Image': 'Insérer une image', 25 | 'List': 'Liste à puces', 26 | 'Ordered List': 'Liste ordonnée', 27 | 'Unordered List': 'Liste non-ordonnée', 28 | 'Code': 'Code', 29 | 'Quote': 'Citation', 30 | 'Preview': 'Prévisualiser', 31 | 'Strikethrough': 'Caractères barrés', 32 | 'Table': 'Table', 33 | 'strong text': 'texte important', 34 | 'emphasized text': 'texte souligné', 35 | 'heading text': 'texte d\'entête', 36 | 'enter link description here': 'entrez la description du lien ici', 37 | 'Insert Hyperlink': 'Insérez le lien hypertexte', 38 | 'enter image description here': 'entrez la description de l\'image ici', 39 | 'Insert Image Hyperlink': 'Insérez le lien hypertexte de l\'image', 40 | 'enter image title here': 'entrez le titre de l\'image ici', 41 | 'list text here': 'texte à puce ici' 42 | } 43 | }; 44 | 45 | constructor( 46 | private fb: FormBuilder, 47 | private markdownService: MarkdownService 48 | ) { } 49 | 50 | ngOnInit() { 51 | this.editorOptions = { 52 | autofocus: false, 53 | iconlibrary: 'fa', 54 | savable: false, 55 | onShow: (e) => this.bsEditorInstance = e, 56 | parser: (val) => this.parse(val) 57 | }; 58 | 59 | // put the text completely on the left to avoid extra white spaces 60 | this.markdownText = 61 | `### Markdown example 62 | --- 63 | This is an **example** where we bind a variable to the \`markdown\` component that is also bind to a textarea. 64 | #### example.component.ts 65 | \`\`\`javascript 66 | function hello() { 67 | alert('Hello World'); 68 | } 69 | \`\`\` 70 | #### example.component.css 71 | \`\`\`css 72 | .bold { 73 | font-weight: bold; 74 | } 75 | \`\`\``; 76 | 77 | this.buildForm(this.markdownText); 78 | } 79 | 80 | onChange(e: any) { 81 | // for example show textarea new body content 82 | // console.log(e.getContent()); 83 | } 84 | 85 | buildForm(markdownText: string) { 86 | this.templateForm = this.fb.group({ 87 | body: [markdownText], 88 | isPreview: [true] 89 | }); 90 | } 91 | 92 | /** highlight all code found, needs to be wrapped in timer to work properly */ 93 | highlight() { 94 | setTimeout(() => { 95 | this.markdownService.highlight(); 96 | }); 97 | } 98 | 99 | hidePreview() { 100 | if (this.bsEditorInstance && this.bsEditorInstance.hidePreview) { 101 | this.bsEditorInstance.hidePreview(); 102 | } 103 | } 104 | 105 | showFullScreen(isFullScreen: boolean) { 106 | if (this.bsEditorInstance && this.bsEditorInstance.setFullscreen) { 107 | this.bsEditorInstance.showPreview(); 108 | this.bsEditorInstance.setFullscreen(isFullScreen); 109 | } 110 | } 111 | 112 | parse(inputValue: string) { 113 | const markedOutput = this.markdownService.parse(inputValue.trim()); 114 | this.highlight(); 115 | 116 | return markedOutput; 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /angular.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json", 3 | "version": 1, 4 | "newProjectRoot": "projects", 5 | "projects": { 6 | "angular-markdown-editor": { 7 | "root": "", 8 | "sourceRoot": "src", 9 | "projectType": "library", 10 | "architect": { 11 | "build": { 12 | "builder": "@angular-devkit/build-angular:browser", 13 | "options": { 14 | "outputPath": "docs", 15 | "index": "src/index.html", 16 | "main": "src/main.ts", 17 | "polyfills": "src/polyfills.ts", 18 | "tsConfig": "tsconfig.app.json", 19 | "allowedCommonJsDependencies": [ 20 | "assign-deep", 21 | "excel-builder-webpacker", 22 | "stream" 23 | ], 24 | "assets": [ 25 | "src/assets", 26 | "src/favicon.ico", 27 | { 28 | "glob": "**/*", 29 | "input": "src/assets/i18n", 30 | "output": "/assets/i18n" 31 | }, 32 | { 33 | "glob": "*", 34 | "input": "src/assets/i18n", 35 | "output": "/assets/i18n" 36 | }, 37 | { 38 | "glob": "*/", 39 | "input": "src/assets/i18n", 40 | "output": "/assets/i18n" 41 | }, 42 | { 43 | "glob": "**/*", 44 | "input": "src/assets/i18n", 45 | "output": "/assets/i18n" 46 | } 47 | ], 48 | "styles": [ 49 | "node_modules/bootstrap/dist/css/bootstrap.css", 50 | "node_modules/font-awesome/css/font-awesome.css", 51 | "node_modules/bootstrap-markdown/css/bootstrap-markdown.min.css", 52 | "node_modules/prismjs/themes/prism.css", 53 | "src/styles.scss" 54 | ], 55 | "scripts": [ 56 | "node_modules/jquery/dist/jquery.js", 57 | "node_modules/bootstrap-markdown/js/bootstrap-markdown.js", 58 | "node_modules/prismjs/prism.js", 59 | "node_modules/prismjs/components/prism-css.min.js", 60 | "node_modules/prismjs/components/prism-javascript.min.js", 61 | "node_modules/prismjs/components/prism-typescript.min.js" 62 | ], 63 | "vendorChunk": true, 64 | "extractLicenses": false, 65 | "buildOptimizer": false, 66 | "sourceMap": true, 67 | "optimization": false, 68 | "namedChunks": true 69 | }, 70 | "configurations": { 71 | "development": { 72 | "budgets": [ 73 | { 74 | "type": "anyComponentStyle", 75 | "maximumWarning": "150kb" 76 | } 77 | ], 78 | "outputHashing": "all", 79 | "extractLicenses": true, 80 | "fileReplacements": [ 81 | { 82 | "replace": "src/environments/environment.ts", 83 | "with": "src/environments/environment.dev.ts" 84 | } 85 | ] 86 | }, 87 | "production": { 88 | "budgets": [ 89 | { 90 | "type": "anyComponentStyle", 91 | "maximumWarning": "50kb" 92 | } 93 | ], 94 | "optimization": true, 95 | "outputHashing": "all", 96 | "sourceMap": false, 97 | "namedChunks": false, 98 | "aot": false, 99 | "extractLicenses": true, 100 | "vendorChunk": false, 101 | "fileReplacements": [ 102 | { 103 | "replace": "src/environments/environment.ts", 104 | "with": "src/environments/environment.prod.ts" 105 | } 106 | ] 107 | } 108 | } 109 | }, 110 | "serve": { 111 | "builder": "@angular-devkit/build-angular:dev-server", 112 | "options": { 113 | "browserTarget": "angular-markdown-editor:build" 114 | }, 115 | "configurations": { 116 | "production": { 117 | "browserTarget": "angular-markdown-editor:build:production" 118 | } 119 | } 120 | }, 121 | "lint": { 122 | "builder": "@angular-eslint/builder:lint", 123 | "options": { 124 | "lintFilePatterns": [ 125 | "src/**/*.ts", 126 | "src/**/*.html" 127 | ] 128 | } 129 | } 130 | } 131 | } 132 | }, 133 | "schematics": { 134 | "@schematics/angular:component": { 135 | "prefix": "app", 136 | "style": "scss" 137 | }, 138 | "@schematics/angular:directive": { 139 | "prefix": "app" 140 | } 141 | }, 142 | "cli": { 143 | "packageManager": "yarn", 144 | "schematicCollections": [ 145 | "@angular-eslint/schematics" 146 | ] 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /src/lib/angular-markdown-editor/angular-markdown-editor.component.ts: -------------------------------------------------------------------------------- 1 | import {AfterViewInit, Component, ElementRef, EventEmitter, forwardRef, Inject, Input, Output} from '@angular/core'; 2 | import { NG_VALUE_ACCESSOR } from '@angular/forms'; 3 | import { EditorLocale, EditorOption } from './models'; 4 | import { GlobalEditorOptions } from './global-editor-options'; 5 | 6 | declare var $: any; 7 | 8 | export const MARKDOWN_EDITOR_VALUE_ACCESSOR: any = { 9 | provide: NG_VALUE_ACCESSOR, 10 | useExisting: forwardRef(() => AngularMarkdownEditorComponent), 11 | multi: true 12 | }; 13 | 14 | @Component({ 15 | moduleId: 'angulaMarkdownEditor', 16 | selector: 'angular-markdown-editor', 17 | template: '', 18 | providers: [MARKDOWN_EDITOR_VALUE_ACCESSOR] 19 | }) 20 | export class AngularMarkdownEditorComponent implements AfterViewInit { 21 | /** Locale set that has a language and dictionary that can be added as an alternative language. Can be 1 or more dictionaries */ 22 | @Input() 23 | set locale(locale: EditorLocale | EditorLocale[]) { 24 | this.addLocaleSet(locale); 25 | } 26 | 27 | /** Id of the textarea DOM element used by the lib */ 28 | @Input() textareaId = ''; 29 | 30 | /** Markdown Editor Options to pass to the element */ 31 | @Input() options?: EditorOption; 32 | 33 | /** Number of rows for the textarea */ 34 | @Input() rows = 10; 35 | 36 | /** These do not actually ever emit, since bootstrap-markdown already emits the event. This is simply just for typings **/ 37 | @Output('onShow') private readonly _onShow: EventEmitter = new EventEmitter(); 38 | @Output('onPreview') private readonly _onPreview: EventEmitter = new EventEmitter(); 39 | @Output('onPreviewEnd') private readonly _onPreviewEnd: EventEmitter = new EventEmitter(); 40 | @Output('onSave') private readonly _onSave: EventEmitter = new EventEmitter(); 41 | @Output('onBlur') private readonly _onBlur: EventEmitter = new EventEmitter(); 42 | @Output('onFocus') private readonly _onFocus: EventEmitter = new EventEmitter(); 43 | @Output('onChange') private readonly _onChange: EventEmitter = new EventEmitter(); 44 | @Output('onFullscreen') private readonly _onFullscreen: EventEmitter = new EventEmitter(); 45 | @Output('onFullscreenExit') private readonly _onFullscreenExit: EventEmitter = new EventEmitter(); 46 | @Output('onSelect') private readonly _onSelect: EventEmitter = new EventEmitter(); 47 | 48 | public value: any | any[]; 49 | public onModelChange: Function = () => { }; 50 | public onModelTouched: Function = () => { }; 51 | 52 | constructor(private readonly elm: ElementRef, @Inject('config') private forRootConfig: EditorOption) { } 53 | 54 | ngAfterViewInit() { 55 | this.initialization(); 56 | } 57 | 58 | addLocaleSet(editorLocale: EditorLocale | EditorLocale[]) { 59 | if (!editorLocale) { 60 | return; 61 | } 62 | if (Array.isArray(editorLocale)) { 63 | editorLocale.forEach((locale: EditorLocale) => $.fn.markdown.messages[locale.language] = locale.dictionary); 64 | } else { 65 | $.fn.markdown.messages[editorLocale.language] = editorLocale.dictionary; 66 | } 67 | } 68 | 69 | initialization() { 70 | // get all default options to get the entire list of onEvent so that we can attach Dispatch Custom Event to them 71 | // we also merge these with the options, and pass these merged options to the hookToEditorEvents() method to cover all onEvent callbacks 72 | const markdownDefaultOptions = $.fn.markdown.defaults; 73 | 74 | // re-hook new events that were optionally defined in the options 75 | // merge the options, the order matters (last options on the right have higher priorities) 76 | const options = { ...markdownDefaultOptions, ...GlobalEditorOptions, ...this.forRootConfig, ...this.options }; 77 | 78 | // hook all events to respective callbacks 79 | // 1- could be coming from a Dispatched Event in the View:: (onX)="do()" 80 | // 2- or from editor option callback in the Component:: const options = { onX: () => do() } 81 | this.hookToEditorEvents(options); 82 | 83 | // hook to the onChange event to update our model 84 | // however we don't want to override the previous callback, so we will run that one to if exists 85 | const previousOnChange = options.onChange; 86 | options.onChange = (e: any) => { 87 | this.onModelChange(e?.getContent()); 88 | if (typeof previousOnChange === 'function') { 89 | previousOnChange(e); 90 | } 91 | }; 92 | 93 | // finally create the editor 94 | $(`#${this.textareaId}`).markdown(options); 95 | } 96 | 97 | /** 98 | * Hook any of the editor event(s) to Dispatch Custom Event so that we can use them in Angular with (onX)="doSomething($event.detail.eventData)" 99 | * @param editor options 100 | */ 101 | hookToEditorEvents(options: any) { 102 | for (const prop in options) { 103 | if (options.hasOwnProperty(prop) && prop.startsWith('on')) { 104 | const previousEvent = options[prop]; 105 | 106 | // on Callback triggered 107 | options[prop] = (e: any) => { 108 | // Dispatch a Custom Event, so that the (onX)="do()" from the View works 109 | this.dispatchCustomEvent(prop, { eventData: e }); 110 | 111 | // if an event was passed through the options (instead of dispatch), and is not empty function, then we need to run it as well 112 | // basically we don't want the Dispatch Custom Event (onX)="do()" to override the ones passed directly in the editor option callbacks 113 | if (typeof previousEvent === 'function') { 114 | previousEvent(e); 115 | } 116 | }; 117 | } 118 | } 119 | } 120 | 121 | /** 122 | * Write value to the native element 123 | * @param value string 124 | */ 125 | writeValue(value: string): void { 126 | this.value = value; 127 | 128 | // preset values in the DOM element 129 | if (this.value) { 130 | this.elm.nativeElement.querySelector('textarea').value = this.value; 131 | } 132 | } 133 | 134 | registerOnChange(fn: Function): void { 135 | this.onModelChange = fn; 136 | } 137 | 138 | registerOnTouched(fn: Function): void { 139 | this.onModelTouched = fn; 140 | } 141 | 142 | /** Dispatch of Custom Event, which by default will bubble & is cancelable */ 143 | private dispatchCustomEvent(eventName: string, data?: any, isBubbling: boolean = true, isCancelable: boolean = true) { 144 | const eventInit: CustomEventInit = { bubbles: isBubbling, cancelable: isCancelable }; 145 | if (data) { 146 | eventInit.detail = data; 147 | } 148 | return this.elm.nativeElement.dispatchEvent(new CustomEvent(eventName, eventInit)); 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Angular-Markdown-Editor 2 | [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) 3 | [![TypeScript](https://img.shields.io/badge/%3C%2F%3E-TypeScript-%230074c1.svg)](http://www.typescriptlang.org/) 4 | [![npm version](https://badge.fury.io/js/angular-markdown-editor.svg)](//npmjs.com/package/angular-markdown-editor) 5 | [![NPM downloads](https://img.shields.io/npm/dy/angular-markdown-editor.svg)](https://npmjs.org/package/angular-markdown-editor) 6 | 7 | This package includes a few libraries and tools to make a more convenient "all in one" WYSIWYG Markdown Editor with preview. All of that bundled into a simple Angular Component. This package can be useful for online documentation and/or anything similar (docs, blog, ...). 8 | 9 | ## License 10 | [MIT License](LICENSE) 11 | 12 | ### Like it? :star: it 13 | You like to use **Angular-Markdown-Editor**? Be sure to upvote :star: the, also maybe support me with cafeine :coffee: and feel free to contribute. 👷👷‍♀️ 14 | 15 | Buy Me a Coffee at ko-fi.com 16 | 17 | ## Angular Compatibility 18 | - version `1.x` for AngularJS 1.x (EOL) 19 | - version `2.x` for Angular >= 4.x 20 | - requires Bootstrap 3.x 21 | - version `3.x` for Angular >= 14.x 22 | - Ivy only 23 | - this version works with Bootstrap 4 and 5 24 | 25 | ### Dependencies 26 | Here is the list of required dependencies 27 | - [Bootstrap-Markdown](https://github.com/refactory-id/bootstrap-markdown) (editor) itself 28 | - [jQuery](http://jquery.com/) _(required for Bootstrap-Markdown editor)_ 29 | - [Bootstrap >=4.x](https://getbootstrap.com/) 30 | 31 | ### Nice to have Dependency 32 | It's a "nice to have" extra, but not a hard dependency, which is to include the [ngx-markdown](https://github.com/jfcere/ngx-markdown) library. It is used in the demo of this package, but technically you could plug any other library that you wish for dealing with the markdown preview. 33 | 34 | ### Demo page 35 | Standalone demo project, with Angular 14 & Bootstrap 5, that you can clone to easily get started 36 | - [Bootstrap 5 demo](https://ghiscoding.github.io/angular-markdown-editor) / [examples repo](https://github.com/ghiscoding/angular-markdown-editor-demo) 37 | 38 | ## Installation 39 | 40 | ### NPM Package 41 | [Angular-Markdown-Editor on NPM](https://www.npmjs.com/package/angular-markdown-editor) 42 | 43 | Install through `NPM` or `Yarn` 44 | ```bash 45 | npm install angular-markdown-editor 46 | 47 | # or with Yarn 48 | yarn add angular-markdown-editor 49 | ``` 50 | 51 | ### Modify your `angular.json` config 52 | You need to add the necessary CSS Styles for Bootstrap, Markdown Editor and Font-Awesome v4.7. 53 | Also make sure to include the proper 3rd party javascript libraries in your `scripts` as shown below. 54 | 55 | ```json 56 | "styles": [ 57 | "node_modules/bootstrap/dist/css/bootstrap.css", 58 | "node_modules/bootstrap-markdown/css/bootstrap-markdown.min.css", 59 | "node_modules/font-awesome/css/font-awesome.css" 60 | ], 61 | "scripts": [ 62 | "node_modules/jquery/dist/jquery.js", 63 | "node_modules/bootstrap-markdown/js/bootstrap-markdown.js" 64 | ], 65 | ``` 66 | 67 | #### When using `ngx-markdown` and/or `Prism.js` 68 | `Prism.js` uses separate javascript file for each language, so you'll need to add them yourself. 69 | For example, we are showing below the highlight of `CSS`, `JavaScript` and `TypeScript` language: 70 | ```json 71 | "styles": [ 72 | "node_modules/prismjs/themes/prism.css" 73 | ], 74 | "scripts": [ 75 | "node_modules/prismjs/prism.js", 76 | "node_modules/prismjs/components/prism-css.min.js", 77 | "node_modules/prismjs/components/prism-javascript.min.js", 78 | "node_modules/prismjs/components/prism-typescript.min.js" 79 | ], 80 | ``` 81 | 82 | ### Import Module 83 | ```typescript 84 | import { AngularMarkdownEditorModule } from 'angular-markdown-editor'; 85 | 86 | @NgModule({ 87 | declarations: [], 88 | imports: [AngularMarkdownEditorModule] 89 | 90 | // or pre-define global configuration using the forRoot 91 | // imports: [AngularMarkdownEditorModule.forRoot({ iconlibrary: 'fa' })] 92 | }); 93 | ``` 94 | 95 | ### Input attributes 96 | You can pass the following input attribute: 97 | 98 | | attribute | type | required | comments | 99 | | ------------- | ------------- | -------- | ------- | 100 | | **textareaId** | string | yes | id of the textarea DOM element used by the lib | 101 | | **rows** | number | no | number of rows for the textarea, defaults to 10 | 102 | | **options** | mixed | no | markdown Editor Options to pass to the element | 103 | | **locale** | EditorLocale | no | locale set that has a language and dictionary that can be added as an alternative language. Can be 1 or more dictionaries | 104 | 105 | ### Global Options 106 | The library comes with it's own Global Editor Options, these propertoes can be overriden at any by the `options` attribute. Click to see the [Global Options](https://github.com/ghiscoding/angular-markdown-editor/blob/master/src/lib/angular-markdown-editor/global-editor-options.ts) defined. 107 | 108 | ### Event Hooks 109 | You can hook to any of the [Bootstrap Markdown Editor Events](http://www.codingdrama.com/bootstrap-markdown/) through 2 ways, just choose the one you prefer: 110 | 111 | #### 1. Dispatch of Custom Event 112 | Each of the events are available in the View from a Custom Event as `(onX)="doSomething()"`, for example: 113 | ```html 114 | 118 | 119 | ``` 120 | ```typescript 121 | export class MyComponent { 122 | hidePreview() { console.log(e.getContent()); } 123 | } 124 | ``` 125 | 126 | You can also pass the Event returned by the Editor via `$event.detail.eventData` 127 | 128 | ```html 129 | 133 | 134 | ``` 135 | ```typescript 136 | export class MyComponent { 137 | onChange(e) { 138 | console.log(e.getContent()); 139 | } 140 | } 141 | ``` 142 | 143 | #### 2. Editor Option Callbacks 144 | The second way is to use the callback directly when defining the Editor Options. 145 | ```html 146 | 150 | 151 | ``` 152 | ```typescript 153 | import { EditorOption } from 'angular-markdown-editor'; 154 | export class MyComponent { 155 | ngOnInit() { 156 | this.editorOptions: EditorOption = { 157 | iconlibrary: 'fa', 158 | onChange: (e) => console.log(e.getContent()), 159 | onFullscreenExit: () => this.hidePreview() 160 | }; 161 | } 162 | } 163 | ``` 164 | 165 | #### List of Event Hooks 166 | - `onPreview` 167 | - `onPreviewEnd` 168 | - `onSave` 169 | - `onBlur` 170 | - `onFocus` 171 | - `onFullscreen` 172 | - `onFullscreenExit` 173 | - `onChange` 174 | - `onSelect` 175 | - `onShow` 176 | 177 | ### API - Editor Methods 178 | The editor API is quite dense and I will not list the entire set of methods, but you can see the entire list from the [Editor Method Interface](https://github.com/ghiscoding/angular-markdown-editor/blob/master/src/lib/angular-markdown-editor/models/editorMethod.interface.ts). 179 | To call any of the Editor Methods, you will have to first get a reference to the Editor's instance which you can get from the `onShow` callback. 180 | 181 | Get the Editor's instance through the `onShow`, via the Custom Event (from the View) or Editor Option callback (just choose the one you prefer). Below shows how to get it through the latter option. 182 | 183 | ###### View 184 | ```html 185 | 186 | 190 | 191 | ``` 192 | 193 | ###### Component 194 | ```typescript 195 | import { EditorInstance, EditorOption } from 'angular-markdown-editor'; 196 | export class MyComponent { 197 | bsEditorInstance: EditorInstance; 198 | 199 | ngOnInit() { 200 | this.editorOptions = { 201 | iconlibrary: 'fa', 202 | onShow: (e) => this.bsEditorInstance = e 203 | }; 204 | } 205 | 206 | showFullScreen() { 207 | this.bsEditorInstance.setFullscreen(true); 208 | } 209 | } 210 | ``` 211 | 212 | ### Preview Button (need a Parser) 213 | For the "Preview" button to work, you will need to provide a `parser` to the Editor Options. This lib has no deep dependencies to any Markdown Parser (you could use `marked.js` or any other parser). But assuming we are using `ngx-markdown`, we can add the parser this way: 214 | 215 | ###### Component 216 | ```typescript 217 | import { MarkdownService } from 'ngx-markdown'; 218 | 219 | export class TestComponent implements OnInit { 220 | constructor(private markdownService: MarkdownService) {} 221 | 222 | ngOnInit() { 223 | this.editorOptions = { 224 | parser: (val) => this.markdownService.parse(val.trim()) 225 | }; 226 | } 227 | } 228 | ``` 229 | 230 | ### Security - Dealing with Cross Site Scripting (XSS) 231 | If you want to use this package for any type of users, you should consider sanatizing your data for Cross Site Scripting (XSS) attack. A good package to use for sanitizing is [DOMPurify](https://www.npmjs.com/package/dompurify) and you should sanitize your data when calling the `parser` as shown below. Also if you have any Markdown Preview, remember to sanitize them as well probably via the form input or control. 232 | 233 | ```ts 234 | this.editorOptions = { 235 | parser: (val: string) => { 236 | const sanitizedText = DOMPurify.sanitize(val.trim()); 237 | this.markdownService.parse(sanitizedText); 238 | } 239 | }; 240 | ``` 241 | 242 | ### Additional Editor Buttons 243 | I really thought that some buttons were missing to go a great job (~~Strikethrough~~ & **Table**). So I added them directly in the [Global Options](https://github.com/ghiscoding/angular-markdown-editor/blob/master/src/lib/angular-markdown-editor/global-editor-options.ts). If you want to add your own, then just look at how it was done in the [Global Options](https://github.com/ghiscoding/angular-markdown-editor/blob/master/src/lib/angular-markdown-editor/global-editor-options.ts) and read the section `additionalButtons` of [Bootstrap Markdown](https://github.com/refactory-id/bootstrap-markdown) website. 244 | 245 | ### Adding Locale 246 | You can add a locale to the editor but passing a `locale` object (and bind it in the View) which contain a `language` and the dictionary of words used by the editor. The entire list of words can be seen in the example below. So for example, if we want to add French locale, we will do the following (you can see [demo code](https://github.com/ghiscoding/angular-markdown-editor/blob/master/src/app/template/template.component.ts)): 247 | 248 | ###### View 249 | ```html 250 | 251 | 256 | 257 | ``` 258 | 259 | ###### Component 260 | ```typescript 261 | import { EditorInstance, EditorLocale, EditorOption } from 'angular-markdown-editor'; 262 | export class MyComponent { 263 | locale: EditorLocale = { 264 | language: 'fr', 265 | dictionary: { 266 | 'Bold': 'Gras', 267 | 'Italic': 'Italique', 268 | 'Heading': 'Titre', 269 | 'URL/Link': 'Insérer un lien HTTP', 270 | 'Image': 'Insérer une image', 271 | 'List': 'Liste à puces', 272 | 'Ordered List': 'Liste ordonnée', 273 | 'Unordered List': 'Liste non-ordonnée', 274 | 'Code': 'Code', 275 | 'Quote': 'Citation', 276 | 'Preview': 'Prévisualiser', 277 | 'Strikethrough': 'Caractères barrés', 278 | 'Table': 'Table', 279 | 'strong text': 'texte important', 280 | 'emphasized text': 'texte souligné', 281 | 'heading text': 'texte d\'entête', 282 | 'enter link description here': 'entrez la description du lien ici', 283 | 'Insert Hyperlink': 'Insérez le lien hypertexte', 284 | 'enter image description here': 'entrez la description de l\'image ici', 285 | 'Insert Image Hyperlink': 'Insérez le lien hypertexte de l\'image', 286 | 'enter image title here': 'entrez le titre de l\'image ici', 287 | 'list text here': 'texte à puce ici' 288 | } 289 | }; 290 | 291 | // if you want to pass multiple locales, just pass it as an array 292 | /* 293 | locale: EditorLocale[] = [ 294 | { language: 'fr', dictionary: { 'Bold': 'Gras', ... 295 | { language: 'en', dictionary: { 'Bold': 'Bold', ... 296 | ]; 297 | */ 298 | 299 | ngOnInit() { 300 | this.editorOptions = { 301 | language: 'fr', // also set the language option to French 302 | onShow: (e) => this.bsEditorInstance = e 303 | }; 304 | } 305 | } 306 | ``` 307 | 308 | **Note** I could not find a way to change the language dynamically, so it seems that we would have to destroy the component and re-create it for switching the language/locale. 309 | --------------------------------------------------------------------------------