Using Monaco Editor
To make it easier to integrate Monaco in your editor, you can use this thin wrapper.
Setup
npm i @cisstech/nge
.- add
importProvidersFrom(NgeMonacoModule.forRoot({})),
in the providers ofappConfig
.
If using CTE plugins, add to your app.config
the required plugins, e.g.:
import {
CADMUS_TEXT_ED_SERVICE_OPTIONS_TOKEN,
CADMUS_TEXT_ED_BINDINGS_TOKEN,
} from '@myrmidon/cadmus-text-ed';
import {
MdBoldCtePlugin,
MdItalicCtePlugin,
MdLinkCtePlugin,
} from '@myrmidon/cadmus-text-ed-md';
import { TxtEmojiCtePlugin } from '@myrmidon/cadmus-text-ed-txt';
export const appConfig: ApplicationConfig = {
providers: [
//...
// text editor plugins
// https://github.com/vedph/cadmus-bricks-shell-v3/blob/master/projects/myrmidon/cadmus-text-ed/README.md
MdBoldCtePlugin,
MdItalicCtePlugin,
TxtEmojiCtePlugin,
MdLinkCtePlugin,
{
provide: CADMUS_TEXT_ED_SERVICE_OPTIONS_TOKEN,
useFactory: (
mdBoldCtePlugin: MdBoldCtePlugin,
mdItalicCtePlugin: MdItalicCtePlugin,
txtEmojiCtePlugin: TxtEmojiCtePlugin,
mdLinkCtePlugin: MdLinkCtePlugin
) => {
return {
plugins: [
mdBoldCtePlugin,
mdItalicCtePlugin,
txtEmojiCtePlugin,
mdLinkCtePlugin,
],
};
},
deps: [
MdBoldCtePlugin,
MdItalicCtePlugin,
TxtEmojiCtePlugin,
MdLinkCtePlugin,
],
},
// monaco bindings for plugins
// 2080 = monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyB;
// 2087 = monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyI;
// 2083 = monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyE;
// 2090 = monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyL;
{
provide: CADMUS_TEXT_ED_BINDINGS_TOKEN,
useValue: {
2080: 'md.bold', // Ctrl+B
2087: 'md.italic', // Ctrl+I
2083: 'txt.emoji', // Ctrl+E
2090: 'md.link', // Ctrl+L
},
},
]
};
Usage
-
in your component add these fields:
import { NgeMonacoModule } from '@cisstech/nge/monaco'; // ... @Component({ selector: '...', imports: [ NgeMonacoModule // ... ] }) export class MyComponent implements OnDestroy { private _editorModel?: monaco.editor.ITextModel; private _editor?: monaco.editor.IStandaloneCodeEditor; // ... }
-
if using CTE plugins, inject in the component constructor:
constructor( private _editService: CadmusTextEdService, @Inject(CADMUS_TEXT_ED_BINDINGS_TOKEN) @Optional() private _editorBindings?: CadmusTextEdBindings )
-
add to your component’s form a
FormControl<string|null>
control to hold the text to edit, just like any other form element. This will be kept in synch with the Monaco editor. For instance:this.description = formBuilder.control(null, { validators: Validators.maxLength(10000), }); this.form = formBuilder.group({ description: this.description, // ... });
-
add code for initializing the editor:
private updateEditorContent(description: string | null) { if (this._editorModel) { this._editorModel.setValue(description || ''); } } public onEditorInit(editor: monaco.editor.IEditor) { editor.updateOptions({ minimap: { side: 'right', }, wordWrap: 'on', automaticLayout: true, }); this._editorModel = this._editorModel || monaco.editor.createModel('', 'markdown'); editor.setModel(this._editorModel); this._editor = editor as monaco.editor.IStandaloneCodeEditor; this._disposables.push( this._editorModel.onDidChangeContent((e) => { this.description.setValue(this._editorModel!.getValue()); this.description.markAsDirty(); this.description.updateValueAndValidity(); }) ); // plugins if (this._editorBindings) { Object.keys(this._editorBindings).forEach((key) => { const n = parseInt(key, 10); console.log( 'Binding ' + n + ' to ' + this._editorBindings![key as any] ); this._editor!.addCommand(n, () => { this.applyEdit(this._editorBindings![key as any]); }); }); } // update the editor content if the description is already available this.updateEditorContent(this.description.value); }
-
in the form update code, called when data is bound to your component, update both the control and Monaco editor:
private updateForm(sign: EpiSign | undefined | null): void { if (!sign) { this.form.reset(); return; } // ... this.description.setValue(sign.description || null); this.form.markAsPristine(); this.updateEditorContent(sign.description || null); }
-
when getting your text back, just read it from your control’s value (here
this.description.value
).👉 Note that we are using
updateEditorContent
both inupdateForm
and in editor init. This is because typically the editor’s text is set when data is bound to an input endpoint of the component, which typically is a signal got viamodel
orinput
, so thatupdateForm
is called in an effect like:effect(() => { this.updateForm(this.sign()); });
Thus, it might happen that when
updateForm
gets called in the effect, the Monaco editor is not yet initialized. Setting the content in both these places ensures that we set it later if Monaco wasn’t ready yet. -
in your template, add the editor and bind its
ready
event:<div id="editor"> <nge-monaco-editor style="--editor-height: 100%" (ready)="onEditorInit($event)" /> </div>
-
remember to destroy disposables:
public ngOnDestroy() {
this._disposables.forEach((d) => d.dispose());
}