An Angular Markdown Editor in WYSIWYG style with extensive functionality, high customizability and an integrated material theme.
npm install @mdefy/ngx-markdown-editor_Ngx Markdown Editor_ is a _Angular_ library providing a WYSIWYG markdown editor, which is especially intended for users unfamiliar with the _Markdown_ syntax. However, it is also well-suited for advanced users as it provides efficient ways to write _Markdown_, e.g. by using shortcuts or utilizing a preview-like markup theme to get immediate visual response of how the result will look like.
In addition, this markdown editor provides high extensibility and customizability as well as broad and simple options for internationalization.
Last but not least, by containing an opt-in material theme, this component will perfectly fit into your _Angular Material_ application. If you do not use _Angular Material_ you can easily integrate your own theme.
- Ngx Markdown Editor
- Table of contents
- Dependencies
- Installation
- Getting started
- Module configuration
- Component bindings
- Inputs
- Outputs
- Toolbar
- 1. Construct a toolbar from existing items
- 2. Configure an existing item
- 3. Create your own item
- Shortcuts
- Disabling shortcuts
- Icons
- Statusbar
- 1. Construct a statusbar from existing items
- 2. Configure an existing item
- 3. Create your own item
- Internationalization
- Theming
- Editor styling
- Markup styling
- FAQs
- How to set the editor's content programmatically
- How to access the _CodeMirror_ editor instance
- How to listen to an _CodeMirror_ event which is not emitted by MarkdownEditorComponent
- How to contribute
- Writing issues
- Making pull requests
- Project setup
- Package manager
- Commit rules
- Coding style guidelines
This library depends on _Markdown Editor Core_ and _Ngx Markdown_.
_Markdown Editor Core_ is a JS library based on CodeMirror and was developed together with _Ngx Markdown Editor_. It provides the text editor and an extensive API for markdown-related actions and everything required to interact conveniently with the editor.
_Ngx Markdown_ is used to provide a preview feature, that renders the _Markdown_ text written in the editor.
Run
```
npm i ngx-markdown-editor
or
``
yarn add ngx-markdown-editor
Include MarkdownEditorModule into your Angular module and include into your HTML template.
Make sure to load _Material Font_, e.g. the header of your index.html file:
`html`
In order to use the material theme of _Ngx Markdown Editor_ in combination with your global material theme (especially _Angular Material_), import the theme file into your styles.scss and include the material mixin, where you should pass your app's primary color.MatFormField
You can select from different material styles like in _Angular Material's_ using the materialStyle input property.
`scss
@import '~ngx-markdown-editor/themes/material';
@include mde-material(mat-color($your-primary-color));
`
In order to set specific dimension for _Ngx Markdown Editor_, simply apply any of the dimensional CSS properties to the component. Example:
`scss`
ngx-markdown-editor {
min-height: 100px;
max-height: 500px;
}
In general, all configuration of the editor can be done dynamically through input bindings. However, as _Ngx Markdown Editor_ utilizes the _Ngx Markdown_ library for its preview feature and as the latter can be configured statically via NgModule import, MarkdownEditorModule also implements Angular's forRoot() and forChild() paradigma to make _Ngx Markdown_'s configuration options available. For this, import MarkdownEditorModule as follows:
`typescript`
@NgModule({
imports: [
NgxMarkdownEditorModule.forRoot({
previewConfig: {
sanitize: ...
markedOptions: ...
}
})
]
})
For detailed instructions how to configure _Ngx Markdown_, visit its dedicated documentation section on GitHub.
Note, that we do not forward the loader option of MarkdownModuleConfig, as the [src] property of is irrelevant for us.
If you like to use the same configuration for other MarkdownEditorComponent instances in sub modules, import the module with
`typescript`
@NgModule({
imports: [ NgxMarkdownEditorModule.forChild() ]
})
> Due to the fact that the MarkdownService _Ngx Markdown_ is a singleton object, we cannot provide different preview configurations within one application so far. The most possible is to inject MarkdownService an make adjustments at run-time (see _ngx-markdown#177_). Apart from that, however, there are no dependencies between different _Ngx Markdown Editor_ instances.
| Input | Description | Default value |
|---|---|---|
data: string | Data string to set as content of the editor. | '' |
options: Options | Mainly options from Markdown Editor Core, including some adjustments. To update options at runtime, merge the old options object with the new options before applying the changes: this.options = { ...this.options, optionToUpdate: updateValue }. | {} |
toolbar: ToolbarItemDef[] | Toolbar configuration. Can contain names of predefined items or objects of custom items. | See toolbar section. |
statusItems: StatusbarItemDef[] | Statusbar configuration. Can contain names of predefined items or objects of custom items. | See statusbar section. |
showTooltips: boolean | Specifies whether tooltips are shown for toolbar items. | true |
shortcutsInTooltips: boolean | Specifies whether the applied keyboard shortcuts are included in the tooltips of toolbar items. | true |
materialStyle: boolean | 'standard' | 'fill' | 'legacy' | Specifies whether or which Angular Material style is applied. | false; for true, the default style is standard |
label: string | undefined | The label text for the editor component. The label area is hidden, if no label is specified. | undefined |
disabled: boolean | Specifies whether the editor is disabled. In the disabled mode, the preview with rendered markdown is shown instead of the editor area. | false |
showToolbar: boolean | Specifies whether the toolbar is rendered. | true |
showStatusbar: boolean | Specifies whether the statusbar is rendered. | true |
required: boolean | Specifies whether the editor component is a required field. If true, an asterisk (*) is added to the label. (Apart from that, this has no other effect.) | false |
language: LanguageTag | Specifies the current language applied to all internationalizable properties. | en |
The most important _Codemirror_ events are transformed to _Angular_ outputs to facilitate event binding.
| Output | Description |
|---|---|
contentChange: ObservableEmitter<{ instance: Editor; changes: EditorChangeLinkedList[] }> | Emits when the editor's content changes. |
curserActivity: ObservableEmitter<{ instance: Editor }> | Emits when the cursor is moved. |
editorFocus: ObservableEmitter<{ instance: Editor; event: FocusEvent }> | Emits when the editor receives focus. |
editorBlur: ObservableEmitter<{ instance: Editor; event: FocusEvent }> | Emits when the editor loses focus. |
The toolbar is highly configurable and comes with many built-in items. You can simply
- pick the ones you need,
- define a custom order,
- reconfigure existing items,
- and even define whole new items (currently only buttons supported).
If you do not specify anything at all for the toolbar input property, the default toolbar setup is applied.
The toolbar input property in an array of type
`typescript`
type ToolbarItemDef = ToolbarItemName | ToolbarItem;
ToolbarItemName is a union type of all built-in item names. The interface ToolbarItem represents a full toolbar item.
`typescript`
interface ToolbarItem {
name: string;
action?: (...args: any[]) => void;
shortcut?: string;
isActive?: (...args: any[]) => boolean | number;
tooltip?: OptionalI18n
icon?: OptionalI18n
disableOnPreview?: boolean;
}
> For details about the OptionalI18n type, see Internationalization section.
In the following, we always apply a JavaScript variable to the toolbar input property:
`html`
To build a toolbar from existing items, simply create an array of type ToolbarItemName[] (or ToolbarItemDef[]) and specify the items by name.'|'
Additionally, there is a separator element, which you can insert at any position with .
`typescript`
public toolbar: ToolbarItemName[] = ['toggleBold', 'toggleItalic', '|', 'insertLink', '|', 'openMarkdownGuide'];
The naming convention for items is to use the name of the function that is triggered by the item.
You can adjust built-in items for you needs by defining an item with a name included in ToolbarItemName. You do not have to specify
all item properties, but can simply adjust only a subset of them, the rest will keep their default values.
For example, if you want to give the toggleBold item a new shortcut (default: Ctrl-B) as well as change the tooltip, then proceed as following:
`typescript`
const newToggleBoldItem: ToolbarItem = {
name: 'toggleBold',
shortcut: 'Alt-B',
tooltip: 'Bold you shall be',
};
Then include this object into the toolbar item array (may be alongside ToolbarItemNames):
`typescript`
public toolbar: ToolbarItemDef[] = [newToggleBoldItem, 'toggleItalic', ...];
This is very similar to configuring an existing item. Example:
`typescriptshortcutsInTooltips
const myItem: ToolbarItem = {
name: 'myCustomAction',
action: () => myCustomAction()
shortcut: 'Alt-B',
tooltip: 'Hint: No need to hard code the shortcut here, see input ',`
icon: {
format: 'material'
iconName: 'star'
}
};
Again you only have to include the properties you want to explicitly specify (except the obligatory name property), all other item properties will be "as empty as possible" per default:
`typescript`
const defaultItem: ToolbarItem = {
name: '',
action: () => {},
shortcut: undefined,
isActive: undefined,
tooltip: '',
icon: { format: 'material', iconName: '' },
disableOnPreview: false,
};
> Note, that although there is the built-in setHeadingLevel dropdown item, so far only custom button items can be constructed in the described way (setHeadingLevel is implemented as a special case). This should satisfy most cases. If you require other items as well, you are welcome to fork this repo and/or make a pull request.
The default keymap is as follows (on Mac "Ctrl" is replaced with "Cmd"):
| Action | Shortcut |
| ------------------------- | -------------------- |
| setHeadingLevel | Shift-Ctrl-Alt-H |toggleHeadingLevel
| | Alt-H |increaseHeadingLevel
| | Alt-H |decreaseHeadingLevel
| | Shift-Alt-H |toggleBold
| | Ctrl-B |toggleItalic
| | Ctrl-I |toggleStrikethrough
| | Ctrl-K |toggleUnorderedList
| | Ctrl-L |toggleOrderedList
| | Shift-Ctrl-L |toggleCheckList
| | Shift-Ctrl-Alt-L |toggleQuote
| | Ctrl-Q |toggleInlineCode
| | Ctrl-7 |insertCodeBlock
| | Shift-Ctrl-7 |insertLink
| | Ctrl-M |insertImageLink
| | Shift-Ctrl-M |insertTable
| | Ctrl-Alt-T |insertHorizontalRule
| | Shift-Ctrl-- |toggleRichTextMode
| | Alt-R |formatContent
| | Alt-F |downloadAsFile
| | Shift-Ctrl-S |importFromFile
| | Ctrl-Alt-I |togglePreview
| | Alt-p |toggleSideBySidePreview
| | Shift-Alt-P |undo
| | Ctrl-Z |redo
| | Ctrl-Y, Shift-Ctrl-Z |openMarkdownGuide
| | F1 |
For shortcuts that come built-in with _CodeMirror_, see _CodeMirror_ documentation.
The primary to configure single shortcuts alongside with other item properties is to use the toolbar configuration as described in the toolbar section.
However, if you want to customize keyboard shortcuts of a lot of (built-in) items you may also do this inside the options: Options input property with options.shortcuts = {...}. This is a decent alternative as you can specify many keybindings in a single object. Attention: Shortcuts defined in options.shortcuts will override shortcuts specified in toolbar.
When specifying custom shortcuts, mind the correct order of special keys: Shift-Cmd-Ctrl-Alt (see here).
#### Disabling shortcuts
As per default, keyboard shortcuts are always functioning for all built-in toolbar items, even when they are not included into the visible toolbar, in order to enable users to efficiently write _Markdown_.
However, you can configure this behavior inside the options: Options input property with options.shortcutsEnabled.shortcutsEnabled: 'none'
You can either disable shortcuts completely () or only enable them for items included in the toolbar or specified in options.shortcuts (shortcutsEnabled: 'customOnly').
Icons can be specified in multiple ways. The easiest is to use an icon included in the _Material icon_ font.
`typescript`
const item: Icon = {
format: 'material',
iconName: 'thumb_up',
};
But you can also use your own SVG icons by either specifying the icon's file path (location at runtime) or by including an SVG string into your TypeScript file:
`typescript`
const item: Icon = {
format: 'svgFile',
iconName: 'my_icon',
runTimePath: './path/to/icon.svg',
};
or
`typescript`
const item: Icon = {
format: 'svgFile',
iconName: 'my_icon',
svgHtmlString: '',
};
Depending on the format of your icon you might need to adjust the icon via CSS, e.g. similar to
`scss`
.mat-button .mat-icon[data-mat-icon-name='my_icon'] {
height: 16px;
...;
}
Configuring the statusbar is very similar to configuring the toolbar, only simpler as there are only two properties for an item.
The statusbar input property is an array of type
`typescript`
type StatusbarItemDef = StatusbarItemName | StatusbarItem;
StatusbarItemName is a union type of all built-in item names. The interface StatusbarItem represents a full statusbar item.
`typescript`
interface StatusbarItem {
name: string;
value: OptionalI18n
}
The value of an item is a observable (or an internationalized version of it), which will be observed by the statusbar.
> For details about the OptionalI18n type, see Internationalization section.
In the following, we always apply a JavaScript variable to the toolbar input property:
`html`
To build a statusbar from existing items, simply create an array of type StatusbarItemName[] (or StatusbarItemDef[]) and specify the items by name.'|'
Additionally, there is a separator element, which you can insert at any position with .
`typescript`
public statusbar: ToolbarItemName[] = ['wordCount', 'characterCount', '|', 'cursorPosition'];
The naming convention for items is to use the name of the subject / value that is displayed.
You can adjust built-in items for you needs by defining an item with a name included in ToolbarItemName. However, this might only make sense if you want to keep an existing item name and implement internationalization for it, as this is almost the same as creating a new item. For this reason, we omit an example here and refer to the section below.
To create a custom statusbar item, simply define a new object of type StatusbarItem:
`typescript`
const myItem: ToolbarItem = {
name: 'myValue',
value: of('static string'),
};
Then include this object into the statusbar item array (maybe alongside StatusbarItemNames):
`typescript`
public toolbar: ToolbarItemDef[] = [newToggleBoldItem, 'toggleItalic', ...];
_Ngx Markdown Editor_ provides opt-in internationalization for many objects like tooltips and even icons.
To realize this, a generic type named OptionalI18n was implemented:
`typescript`
type OptionalI18n
This enables you to specify either the same object for all languages (not using internationalization for this specific object)
or apply an i18n object for only those language you need. If you use the internationalized version, you are required
to define a default value, that is applied for all languages you do not specify explicitly.
To summarize: you can go exactly as far with internationalization as you want to with this component.
Theming is an important issue when using third party components to integrate them smoothly into your application.
Therefore, _Ngx Markdown Editor_ provides default themes as well as an easy way to apply your own theme.
You can style every element inside with CSS as the component does not useViewEncapsulation.None
view encapsulation (). You can also predefine different themes and apply them dynamically.
The default theme is named default. Alternatively you can use the predefined material theme and choose from different styles as described in the _Getting started_ section.
To customize the editor's appearance for your needs, use options.editorThemes To apply a customized theme with the name "example" - specify If you only want to extend the default theme, you can either define new stylings for the classes Again, the default theme is named Additionally there is a predefined theme To customize the markup styling, use You can either set the content using the input property Or you can do it using the The _CodeMirror_ instance is accessible through the _Markdown Editor Core_ instance, which is in turn publicly accessible in With the utility function eventObs.subscribe((instance, ...) => myEventHandler()); First of all, contributions in any way are very welcome! And a big thank you to all who decide to so!! :) The code is neither perfect nor complete. If you have any suggestions, requirements or even just Before you open an issue, please have one closer look if this is really an issue of _Ngx Markdown Editor_ When writing issues, please give a clear description of the current state and what you are unhappy Recipe for making a pull request: 1. Fork and checkout repo locally. This project uses _Yarn_ as package manager. So you must use this one to install dependencies when contributing code. The scripts in _package.json_ still work with We use _Commitlint_ to guarantee structured commit messages. This means There are not many strict guidelines to keep in mind, but please adapt to the project's code style We use _Prettier_ to ensure consistent formatting. Therefore, you should install a _Prettier_ plugin for your IDE. Further it is highly recommended to enable "Format on save", which is also set as the project's default for VSCode. There is a pre-commit git hook for _Prettier_, which checks the formatting of all files. Occasionally.
The theme name specified here, will be applied as is to as well as be applied with a cm-s- prefix to the element.{ editorThemes: ['example']}
For further details on CodeMirror's theming, visit the dedicated section on _CodeMirror_. in the options input property,ngx-markdown-editor.example
- define the CSS selector in a CSS file,.cm-s-example
- define the CSS class to style the _CodeMirror_ element, andngx-markdown-editor.default
- make sure to load the CSS file with your app. and .cm-s-default and make sure that the "default" theme is applied or you can create your own additional theme and specify two themes in the options: { editorThemes: ['default', 'additional-theme'] }.default$3
here, which applies the default markup styling from the gfm _CodeMirror_ mode.preview-like-markup which imitates the styling of _Ngx Markdown's_ default styling.options.markupThemes
This makes the markup look as similar to the preview as possible..
The theme specified here is only applied to the _CodeMirror_ element .data
For detailed instructions how to define your own markup styling, visit the section on _Markdown Editor Core_.FAQs
$3
(one-way binding, not like ngModel):`html`MarkdownEditorComponent instance:`typescript`
@ViewChild(MarkdownEditorComponent) ngxMde: MarkdownEditorComponent;`typescript`
this.ngxMde.mde.setContent('Any _Markdown_ string, where lines are separated with a new line character "\n".');MarkdownEditorComponent$3
.`typescript`
@ViewChild(MarkdownEditorComponent) ngxMde: MarkdownEditorComponent;`typescript`
const cm: CodeMirror.Editor = this.ngxMde.mde.cm;fromCmEvent$3
, _Ngx Markdown Editor_ provides a convenient way to convert a _CodeMirror_ event to an RxJS Observable.`typescript`
@ViewChild(MarkdownEditorComponent) ngxMde: MarkdownEditorComponent;`typescript`
const eventObs: Observable<{...}> = fromCmEvent(this.ngxMde.mde.cm, 'mousedown');npm i yarn -gHow to contribute
comments, please let me know and I will do my best do incorporate them! The even better (and probably faster) way for requesting code modifications, however, are pull requests. I am very happy about all code
contributions as time is often rare around here... :)$3
or if it rather belongs to _Markdown Editor Core_.
about. Then, if possible, propose your solution or at least leave a short statement of your thoughts
about it.$3
2. Install _Yarn_, if you do not have it yet. For example via .yarn
3. Open a command line, move to the project directory and run to install all dependencies.ng build ngx-markdown-editor --watch
4. Make your code changes. (Please mind the style guidelines.)
5. Run to build the library in watch mode.ng serve
6. Run to test your changes in the demo app.npm
7. Check the docs whether they need to be changed.
8. Push the changes to your fork.
9. Make a pull request to the _master_ branch of this repo. Please provide a meaningful title for
the PR and give a concise description.Project setup
$3
, although it is recommended to always use yarn throughout the project.yarn run commit$3
you must write commit messages that meet the rules of _Commitlint_. If you are not familiar with
_Commitlint_, you can use the CLI tool _Commitizen_ by running , which assists you tocz
write conventional messages. You can also install _Commitizen_ globally on your system, if you want to use the shorter cli commands or git cz.yarn add ...$3
when contributing. Only one more thing shall be mentioned here:
it might happen that this hook fails although you have "Format on save" enabled. This is usually
due to wrong line endings, e.g. caused by or some other file-writing script or tool.yarn run format:write
In this case, run to let _Prettier_ correct the wrong formatting and then try to commit again. Unfortunately,format:write` command cannot be set as a pre-commit hook as it is not known in general, which
the
files need to be staged afterwards.