Библиотеки
Monaco-Editor

Monaco-Editor

С чего начать

Установите monaco-editor следующей командой:

npm install monaco-editor @diasoft/qpalette-monaco-editor --save
💡

Пакет называется @diasoft/qpalette-monaco-editor с версии 7.4.11 (и во всей линии 8.x); прежнее имя @diasoft/ngx-monaco-editor осталось на версии 17.0.0 и больше не обновляется. Если в вашем package.json ещё старое имя — замените его: при переходе на Q.Palette 8 это делает мигратор @diasoft/qpalette-cli, на 7.x — вручную, вместе с импортами в коде. Примеры ниже — с новым именем.

Скопируйте monaco-editor в ассеты своей сборки и укажите каталог в baseUrl MonacoEditorModule.forRoot(): файлов редактора в рутовом приложении с 8.2.0 нет. Два варианта — по версии @diasoft/qpalette-monaco-editor:

Путь без ведущего assets/ — каталог внутри ассетов вашей сборки, адрес считается сам (Адреса собственных ассетов). baseUrl — сам каталог monaco-editor.

angular.json, раздел projects.<project>.architect.build.options.assets:

{
  "glob": "**/*",
  "input": "node_modules/monaco-editor",
  "output": "assets/monaco-editor"
}

app.module.ts — forRoot() только в главном модуле, в функциональных — MonacoEditorModule:

import { MonacoEditorModule } from '@diasoft/qpalette-monaco-editor';
 
@NgModule({
  imports: [
    BrowserModule,
    FormsModule,
    MonacoEditorModule.forRoot({ baseUrl: 'monaco-editor' })   // → /api/<сервис>/<компонент>/assets/monaco-editor/min/vs
  ],
  bootstrap: [AppComponent]
})
export class AppModule {}

Создайте опции и привязки ngModel редактора в компоненте:

import { Component } from '@angular/core';
 
@Component({
  selector: 'app-root',
  templateUrl: './app.component.html'
})
export class AppComponent {
  editorOptions = {theme: 'vs-dark', language: 'javascript'};
  code: '''YOUR CODE''';
}

Включите редактор в html с опциями и привязками ngModel:

<ngx-monaco-editor [options]="editorOptions" [(ngModel)]="code"></ngx-monaco-editor>

Diff-editor

Включите редактор различий в html с опциями и необходимыми привязками:

<ngx-monaco-diff-editor
  [options]="options"
  [originalModel]="originalModel"
  [modifiedModel]="modifiedModel"

Стилизация

Чтобы редактор соответствовал высоте контейнера, добавьте height: 100% и заверните редактор в контейнер:

<div style="height: 400px">
  <ngx-monaco-editor style="height: 100%" [options]="editorOptions" [(ngModel)]="code"></ngx-monaco-editor>
</div>

Добавьте редактору класс, напримерmy-code-editorи добавьте необходимые стили в css/scss файл:

<ngx-monaco-editor
  class="my-code-editor"
  [options]="editorOptions"
  [(ngModel)]="code"
></ngx-monaco-editor>

События

<ngx-monaco-editor
  [options]="editorOptions"
  [(ngModel)]="code"
  (onInit)="onInit($event)"
></ngx-monaco-editor>

Откуда берётся редактор

Файлы редактора отдаёт та сборка, в ассеты которой их скопировали (запись в angular.json из С чего начать); каталог задаёт baseUrl, файлы ищутся в <baseUrl>/min/vs, если baseUrl — сам каталог monaco-editor, иначе — в <baseUrl>/monaco-editor/min/vs. По умолчанию редактор берётся из рутового приложения, а там его с 8.2.0 нет, — задайте baseUrl одним из двух способов:

  • Путь без ведущего assets/ — каталог внутри ассетов вашей сборки: копия в assets/monaco-editor и baseUrl: 'monaco-editor' (с 8.2.1; на 8.2.0 baseUrl — каталог с monaco-editor, поэтому там копию кладут в подкаталог: assets/vendor/monaco-editor и baseUrl: 'vendor'); адрес считается сам, имя сервиса в коде не нужно. Это общее правило Адреса собственных ассетов: assets/…, путь от корня и полный URL используются как есть.
  • Адрес своей сборки от корня сайта — '/api/<сервис>/<компонент>/assets' при копии в assets/monaco-editor. Работает на любой версии обёртки; как собрать адрес для веб-компонента и библиотечного проекта — Адрес вручную: версии до 8.2.0.

Куда ведёт каждый вариант — на примере веб-компонента myproduct/catalog:

baseUrlКаталог vs редактораКогда
'monaco-editor', './monaco-editor'/api/myproduct/catalog/assets/monaco-editor/min/vsс 8.2.1; копия в assets/monaco-editor
'vendor'/api/myproduct/catalog/assets/vendor/monaco-editor/min/vsлюбая версия; копия в assets/vendor/monaco-editor
'/api/myproduct/catalog/assets'/api/myproduct/catalog/assets/monaco-editor/min/vsлюбая версия; копия в assets/monaco-editor
'https://cdn.example.com/editor'https://cdn.example.com/editor/monaco-editor/min/vsлюбая версия; редактор с внешнего адреса
не задан, 'assets', './assets'/assets/monaco-editor/min/vs — рутовое приложениес 8.2.1 — 404, файлов там нет; см. ниже про переходный период. На 8.2.0 без baseUrl редактор ищется в ассетах вашей сборки
💡

Переходный период. На крупных стендах может стоять UI-микросервис qpassetsui, и тогда файлы Monaco по адресу рутового /assets/monaco-editor/… отдаёт он (рутовое приложение перенаправляет к нему с 8.2.1): nginx рутового перенаправляет к нему запросы, и продукт, который берёт редактор из рутового (без baseUrl или с 'assets' / './assets' / '/assets'), продолжает работать, ничего не меняя. Это отсрочка на время миграции, а не замена копии: задайте baseUrl и возите monaco-editor у себя — на стендах без qpassetsui редактор без этого не откроется.

Первые два варианта в коде:

// angular.json → projects.<project>.architect.build.options.assets
{ "glob": "**/*", "input": "node_modules/monaco-editor", "output": "assets/monaco-editor" }
MonacoEditorModule.forRoot({ baseUrl: 'monaco-editor' })

Проверьте, откуда ваш редактор берёт файлы

Продукт с baseUrl: 'assets' / './assets' из старых примеров — и, начиная с 8.2.1, без baseUrl — берёт файлы из рутового приложения, даже если возит свою копию monaco-editor. Файлов там нет: такой продукт увидит вместо редактора ошибку загрузки loader.js (404) — кроме стендов, где /assets/monaco-editor/… отдаёт qpassetsui (см. выше). Признак в коде — MonacoEditorModule.forRoot() пустой либо с baseUrl: 'assets' / './assets'.

  1. Убедитесь, что в angular.json есть запись копирования node_modules/monaco-editor в ассеты (С чего начать) — без копии в своей сборке не поможет ничего.
  2. Задайте baseUrl по вкладке своей версии из «С чего начать». Адрес от корня сайта не требует обновления пакета — подходит и продукту, который остаётся на прежней версии обёртки.
💡

Монако на странице один: библиотека держит его в window.monaco и, если он уже загружен, повторно не грузит. Два UI-микросервиса с редактором на одной странице делят копию того, кто открыл редактор первым, — вместе с его baseUrl и набором языков. Рутовое приложение с 8.2.0 Monaco не использует и в это не вмешивается; на более ранних версиях его окно проксирования могло загрузить редактор раньше вашего.

Чтобы не сломать редактор соседнему продукту, выкладывайте полную копию monaco-editor — как в С чего начать, не вырезая языки и воркеры: урезанная копия, загруженная первой, достанется всем. Если редактор нужен для одного-двух форматов, рассмотрите более лёгкий CodeMirror — на нём с 8.2.0 работает окно проксирования рутового приложения; механизм изоляции экземпляров Monaco в Q.Palette пока не реализован.

Конфигурация

МетодforRoot()принимает конфигурацию типаNgxMonacoEditorConfig.

import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { BrowserModule } from '@angular/platform-browser';
 
import { MonacoEditorModule, NgxMonacoEditorConfig } from '@diasoft/qpalette-monaco-editor';
import { AppComponent } from './app.component';
 
const monacoConfig: NgxMonacoEditorConfig = {
  baseUrl: 'monaco-editor', // копия в assets/monaco-editor вашей сборки (с 8.2.1); по умолчанию — рутовое
  defaultOptions: { scrollBeyondLastLine: false }, // pass default options to be used
  onMonacoLoad: () => { console.log((<any>window).monaco); } // here monaco object will be available as window.monaco use this function to extend monaco editor functionalities.
};
 
@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    FormsModule,
    MonacoEditorModule.forRoot(monacoConfig)
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule {
}

Настройка дефолтного JSON

СвойствоonMonacoLoadможет быть использовано для конфигурации дефолтного JSON.

import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { BrowserModule } from '@angular/platform-browser';
 
import { MonacoEditorModule, NgxMonacoEditorConfig } from '@diasoft/qpalette-monaco-editor';
import { AppComponent } from './app.component';
 
export function onMonacoLoad() {
 
  console.log((window as any).monaco);
 
  const uri = monaco.Uri.parse('a://b/foo.json');
  monaco.languages.json.jsonDefaults.setDiagnosticsOptions({
    validate: true,
    schemas: [{
      uri: 'http://myserver/foo-schema.json',
      fileMatch: [uri.toString()],
      schema: {
        type: 'object',
        properties: {
          p1: {
            enum: ['v1', 'v2']
          },
          p2: {
            $ref: 'http://myserver/bar-schema.json'
          }
        }
      }
    }, {
      uri: 'http://myserver/bar-schema.json',
      fileMatch: [uri.toString()],
      schema: {
        type: 'object',
        properties: {
          q1: {
            enum: ['x1', 'x2']
          }
        }
      }
    }]
  });
}
 
const monacoConfig: NgxMonacoEditorConfig = {
  baseUrl: 'monaco-editor',   // копия в assets/monaco-editor вашей сборки; по умолчанию — рутовое
  defaultOptions: { scrollBeyondLastLine: false },
  onMonacoLoad
};
 
@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    FormsModule,
    MonacoEditorModule.forRoot(monacoConfig)
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule {
}

Теперь передайте конфигурацию модели типаNgxEditorModelкомпоненту редактора:

@Component({
  selector: 'app-root',
  template: `<ngx-monaco-editor [options]="options" [model]="model"></ngx-monaco-editor>`,
  styles: []
})
export class AppComponent {
  options = {
    theme: 'vs-dark'
  };
 
  jsonCode = [
    '{',
    '    "p1": "v3",',
    '    "p2": false',
    '}'
  ].join('\n');
 
  model: NgxEditorModel = {
    value: this.jsonCode,
    language: 'json',
    uri: monaco.Uri.parse('a://b/foo.json')
  };
}