Skip to content

Commit

Permalink
feat(editor): move ace only related logic to separate module
Browse files Browse the repository at this point in the history
  • Loading branch information
Sisha0 committed Dec 20, 2022
1 parent 14ab990 commit 13c44b3
Show file tree
Hide file tree
Showing 5 changed files with 130 additions and 87 deletions.
9 changes: 0 additions & 9 deletions src/plugins/async-plugins.ts

This file was deleted.

65 changes: 65 additions & 0 deletions src/plugins/editor/ace/ace-editor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import js_beautify from 'js-beautify';
import {Ace, edit} from 'ace-builds';
import 'ace-builds/src-min-noconflict/mode-html';
import 'ace-builds/src-min-noconflict/theme-chrome';
import 'ace-builds/src-min-noconflict/theme-tomorrow_night';

import {EditorConfig, Theme} from './utils';

/** {@link https://ace.c9.io/ Ace} editor wrapper. */
class AceEditor {
/** Inner {@link https://ace.c9.io/ Ace} instance. */
private editor: Ace.Editor;
/** Callback to run on editor's content changes. */
private changeCallback: Function;
/** Default ace editor's options. */
private static defaultConfig: EditorConfig = {
theme: Theme.Light,
printMarginColumn: -1,
wrap: true,
};

/**
* @param {HTMLElement} element - element to place editor inside.
* @param {Function=} changeCallback - callback to run on editor's content changes.
*/
constructor(element: HTMLElement, changeCallback: Function = () => {}) {
this.editor = edit(element);
this.changeCallback = changeCallback;
this.editor.addEventListener('change', changeCallback);
this.editor.setOption('useWorker', false);
this.editor.setOption('mode', 'ace/mode/html');
}

/** Update editor's options.
* @param {Partial<EditorConfig>} config - new options to set.
*/
public setConfig(config: Partial<EditorConfig>): void {
this.editor.setOptions({
...AceEditor.defaultConfig,
...config
});
}

/** Set editor's text content.
* @param {string} value - text content to set.
*/
public setValue(value: string): void {
this.editor.removeEventListener('change', this.changeCallback);
this.editor.setValue(js_beautify.html(value), -1);
this.editor.addEventListener('change', this.changeCallback);
}

/** @returns Editor's text content. */
public getValue(): string {
return this.editor.getValue();
}

/** Manually cleanup internal event listeners. */
public destroy(): void {
this.editor.removeEventListener('change', this.changeCallback);
}
}

export type {AceEditor};
export const Editor = AceEditor;
17 changes: 17 additions & 0 deletions src/plugins/editor/ace/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/** Config interface to define inner ACE editor settings. */
export interface EditorConfig {
/** Editor's appearance theme. */
theme: string;
/** Position of the vertical line for wrapping. */
printMarginColumn: number;
/** Limit of characters before wrapping. */
wrap: number | boolean;
}

/** Enum to match themes to {@link https://github.com/ajaxorg/ace/tree/master/src/theme Ace styles}. */
export enum Theme {
/** Light editor theme. */
Light = 'ace/theme/chrome',
/** Dark editor theme. */
Dark = 'ace/theme/tomorrow_night',
};
120 changes: 45 additions & 75 deletions src/plugins/editor/editor.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,11 @@
import js_beautify from 'js-beautify';
import {Ace, edit} from 'ace-builds';
import 'ace-builds/src-min-noconflict/mode-html';
import 'ace-builds/src-min-noconflict/theme-chrome';
import 'ace-builds/src-min-noconflict/theme-tomorrow_night';

import {bind} from '@exadel/esl/modules/esl-utils/decorators/bind';
import {debounce} from '@exadel/esl/modules/esl-utils/async/debounce';
import {jsonAttr} from '@exadel/esl/modules/esl-base-element/core';

import {UIPPlugin} from '../../core/registration';
import {listen} from '@exadel/esl/modules/esl-utils/decorators/listen';

/** Config interface to define inner ACE editor settings. */
interface EditorConfig {
/** Editor's appearance theme. */
theme?: string;
/** Position of the vertical line for wrapping. */
printMarginColumn?: number;
/** Limit of characters before wrapping. */
wrap?: number | boolean;
}

/** Interface to represent {@link UIPEditor's} theme. */
interface Theme {
[index: string]: string;
}
import {UIPPlugin} from '../../core/registration';
import {EditorConfig, Theme} from './ace/utils';
import type {AceEditor} from './ace/ace-editor';

/**
* Editor UIPPlugin custom element definition.
Expand All @@ -33,55 +14,43 @@ interface Theme {
*/
export class UIPEditor extends UIPPlugin {
public static is = 'uip-editor';
/** Default [config]{@link EditorConfig} instance. */
public static defaultOptions: EditorConfig = {
theme: 'ace/theme/chrome',
printMarginColumn: -1,
wrap: true,
};

/** Object to map dark/light themes to [Ace]{@link https://ace.c9.io/} themes. */
static themesMapping: Theme = {
'uip-light': 'ace/theme/chrome',
'uip-dark': 'ace/theme/tomorrow_night'
};
private static collapsedAttribute = 'editor-collapsed';

/** Editor's [config]{@link EditorConfig} passed through attribute. */
@jsonAttr()
public editorConfig: EditorConfig;
/** Wrapped [Ace]{@link https://ace.c9.io/} editor instance. */
protected editor: Ace.Editor;

/** {@link editorConfig} merged with {@link defaultOptions}. */
protected get mergedEditorConfig(): EditorConfig {
const type = (this.constructor as typeof UIPEditor);
return Object.assign({}, type.defaultOptions, this.editorConfig || {});
}
/** Editor's {@link EditorConfig config} passed through attribute. */
@jsonAttr({defaultValue: {}})
public editorConfig: Partial<EditorConfig>;
/** Wrapped {@link https://ace.c9.io/ Ace} editor instance. */
protected editor: AceEditor;

protected connectedCallback() {
super.connectedCallback();
this.initEditor();
this.innerHTML = '';
this.appendChild(this.$inner);

if (this.root && !this.root.hasAttribute(UIPEditor.collapsedAttribute)) {
this.initEditor();
}
}

protected disconnectedCallback() {
protected disconnectedCallback(): void {
this.editor.destroy();
super.disconnectedCallback();
}

/** Initialize [Ace]{@link https://ace.c9.io/} editor. */
protected initEditor() {
this.innerHTML = '';
this.appendChild(this.$inner);

this.editor = edit(this.$inner);
this.editor.setOption('useWorker', false);
this.editor.setOption('mode', 'ace/mode/html');
this.initEditorOptions();
}
/** Initialize inner {@link https://ace.c9.io/ Ace} editor. */
protected initEditor(): Promise<void> {
if (this.editor) {
return Promise.resolve();
}

protected initEditorOptions(): void {
this.editor?.setOptions(this.mergedEditorConfig);
return import(/* webpackChunkName: "ace-editor" */ './ace/ace-editor').then((Ace) => {
this.editor = new Ace.Editor(this.$inner, this.onChange);
this._onRootStateChange();
this.editor.setConfig(this.editorConfig);
});
}

/** Callback to call on editor's content changes. */
protected onChange = debounce(() => {
this.model!.setHtml(this.editor.getValue(), this);
}, 1000);
Expand All @@ -91,33 +60,34 @@ export class UIPEditor extends UIPPlugin {
if (this.model!.lastModifier === this) return;

const markup = this.model!.html;
setTimeout(() => this.editor && this.setEditorValue(markup));
setTimeout(() => this.editor && this.editor.setValue(markup));
}

protected setEditorValue(value: string): void {
this.editor.removeEventListener('change', this.onChange);
this.editor.setValue(js_beautify.html(value), -1);
this.editor.addEventListener('change', this.onChange);
/**
* Merges passed editorConfig with current editorConfig.
* @param {Partial<EditorConfig>} editorConfig - config to merge.
*/
public updateEditorConfig(editorConfig: Partial<EditorConfig>): void {
this.editorConfig = {
...this.editorConfig,
...editorConfig,
};

this.editor?.setConfig(this.editorConfig);
}

public setEditorConfig(editorConfig: EditorConfig): void {
this.editorConfig = editorConfig;
this.initEditorOptions();
}

/** Callback to catch theme changes from {@link UIPRoot}. */
/** Callback to catch theme and collapse state changes from {@link UIPRoot}. */
@listen({event: 'uip:configchange', target: '::parent(.uip-root)'})
protected _onRootConfigChange(e: CustomEvent) {
const attr = e.detail.attribute;
const value = e.detail.value;

if (attr === 'dark-theme') {
return this.setEditorConfig({
theme: value === null ?
UIPEditor.defaultOptions.theme : UIPEditor.themesMapping['uip-dark']
this.updateEditorConfig({
theme: value === null ? Theme.Light : Theme.Dark
});
} else if (attr === UIPEditor.collapsedAttribute && value === null) {
this.initEditor();
}
}
}

UIPEditor.register();
6 changes: 3 additions & 3 deletions src/plugins/registration.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import {UIPHeader} from './header/header';
import {UIPOptions} from './header/options/options';
import {UIPSnippets} from './header/snippets/snippets';
import {UIPEditor} from './editor/editor';
import {UIPSettings} from './settings/settings';
import {registeredSettings} from '../registration';
import {registerAsyncPlugins} from './async-plugins';

export {UIPOptions, UIPSettings, UIPSnippets, UIPHeader};
export {UIPOptions, UIPEditor, UIPSettings, UIPSnippets, UIPHeader};

export const registerPlugins = () => {
registerAsyncPlugins();
UIPHeader.register();
UIPOptions.register();
UIPSnippets.register();
UIPEditor.register();
registeredSettings().then(() => UIPSettings.register());
};

0 comments on commit 13c44b3

Please sign in to comment.