Skip to content

Commit

Permalink
refactor: decouple editor logic from angular
Browse files Browse the repository at this point in the history
  • Loading branch information
sibiraj-s committed Jan 3, 2021
1 parent e14c3bc commit a5763a3
Show file tree
Hide file tree
Showing 29 changed files with 456 additions and 368 deletions.
6 changes: 4 additions & 2 deletions demo/src/app/app.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@
<form [formGroup]="form">

<div class="editor">
<ngx-editor formControlName="editorContent" (init)="init($event)" [customMenuRef]="customMenu">
<ngx-editor-menu [editor]="editor" [toolbar]="toolbar" *ngIf="toolbar" [customMenuRef]="customMenu">
</ngx-editor-menu>
<ngx-editor [editor]="editor" formControlName="editorContent">
</ngx-editor>
</div>

Expand All @@ -27,7 +29,7 @@

<!-- custom menu -->
<ng-template #customMenu>
<app-custom-menu [editorView]="editorView"></app-custom-menu>
<app-custom-menu [editor]="editor"></app-custom-menu>
</ng-template>
</div>

Expand Down
29 changes: 23 additions & 6 deletions demo/src/app/app.component.scss
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,29 @@
}
}

.CodeMirror {
border: 1px solid #eee;
height: auto;
margin-bottom: 0.7rem;
.editor {
border: 2px solid rgba(0, 0, 0, 0.2);
border-radius: 4px;

pre {
white-space: pre !important;
.NgxEditor__MenuBar {
border-top-left-radius: 4px;
border-top-right-radius: 4px;
border-bottom: 1px solid rgba(0, 0, 0, 0.2);
}

.NgxEditor {
border-top-left-radius: 0;
border-top-right-radius: 0;
border: none;
}

.CodeMirror {
border: 1px solid #eee;
height: auto;
margin-bottom: 0.7rem;

pre {
white-space: pre !important;
}
}
}
31 changes: 28 additions & 3 deletions demo/src/app/app.component.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import { Component, ViewEncapsulation } from '@angular/core';
import { Component, OnInit, ViewEncapsulation } from '@angular/core';
import { AbstractControl, FormControl, FormGroup } from '@angular/forms';
import { EditorView } from 'prosemirror-view';
import { environment } from '../environments/environment';
import { Validators } from 'ngx-editor';

import { Validators, Editor, Toolbar } from 'ngx-editor';

import jsonDoc from './doc';
import schema from './schema';
import plugins from './plugins';
import nodeViews from './nodeviews';

@Component({
selector: 'app-root',
Expand All @@ -13,11 +17,24 @@ import jsonDoc from './doc';
encapsulation: ViewEncapsulation.None
})

export class AppComponent {
export class AppComponent implements OnInit {
isProdMode = environment.production;

editorView: EditorView;
editordoc = jsonDoc;

editor: Editor;
toolbar: Toolbar = [
['bold', 'italic'],
['underline', 'strike'],
['code', 'blockquote'],
['ordered_list', 'bullet_list'],
[{ heading: ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'] }],
['link', 'image'],
['text_color', 'background_color'],
['align_left', 'align_center', 'align_right', 'align_justify'],
];

form = new FormGroup({
editorContent: new FormControl(jsonDoc, Validators.required())
});
Expand All @@ -29,4 +46,12 @@ export class AppComponent {
init(view: EditorView): void {
this.editorView = view;
}

ngOnInit(): void {
this.editor = new Editor({
schema,
plugins,
nodeViews
});
}
}
24 changes: 3 additions & 21 deletions demo/src/app/app.module.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
import { CommonModule } from '@angular/common';
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';

import { AppComponent } from './app.component';
import { NgxEditorModule } from 'ngx-editor';

import schema from './schema';
import plugins from './plugins';
import nodeViews from './nodeviews';
import { CustomMenuComponent } from './components/custom-menu/custom-menu.component';

@NgModule({
Expand All @@ -17,28 +14,13 @@ import { CustomMenuComponent } from './components/custom-menu/custom-menu.compon
BrowserModule,
FormsModule,
ReactiveFormsModule,
NgxEditorModule.forRoot({
schema,
plugins,
nodeViews,
menu: {
toolbar: [
['bold', 'italic'],
['underline', 'strike'],
['code', 'blockquote'],
['ordered_list', 'bullet_list'],
[{ heading: ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'] }],
['link', 'image'],
['text_color', 'background_color'],
['align_left', 'align_center', 'align_right', 'align_justify'],
]
}
}),
NgxEditorModule
],
declarations: [
AppComponent,
CustomMenuComponent,
],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
bootstrap: [AppComponent]
})

Expand Down
13 changes: 8 additions & 5 deletions demo/src/app/components/custom-menu/custom-menu.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { setBlockType } from 'prosemirror-commands';
import { EditorState, Plugin, PluginKey, Transaction } from 'prosemirror-state';
import { EditorView } from 'prosemirror-view';

import { Editor } from 'ngx-editor';
import { isNodeActive } from 'ngx-editor/helpers';

@Component({
Expand All @@ -13,13 +14,13 @@ import { isNodeActive } from 'ngx-editor/helpers';
export class CustomMenuComponent implements OnInit {
constructor() { }

@Input() editorView: EditorView;
@Input() editor: Editor;
isActive = false;
isDisabled = false;

onClick(e: MouseEvent): void {
e.preventDefault();
const { state, dispatch } = this.editorView;
const { state, dispatch } = this.editor.view;
this.execute(state, dispatch);
}

Expand All @@ -41,6 +42,8 @@ export class CustomMenuComponent implements OnInit {
}

ngOnInit(): void {
const { view } = this.editor;

const plugin = new Plugin({
key: new PluginKey(`custom-menu-codemirror`),
view: () => {
Expand All @@ -50,10 +53,10 @@ export class CustomMenuComponent implements OnInit {
}
});

const newState = this.editorView.state.reconfigure({
plugins: this.editorView.state.plugins.concat([plugin])
const newState = view.state.reconfigure({
plugins: view.state.plugins.concat([plugin])
});

this.editorView.updateState(newState);
view.updateState(newState);
}
}
160 changes: 160 additions & 0 deletions src/lib/Editor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
import { Schema, Node as ProsemirrorNode } from 'prosemirror-model';
import { EditorState, Plugin, Transaction } from 'prosemirror-state';
import { Decoration, EditorView, NodeView } from 'prosemirror-view';
import { Subject } from 'rxjs';

import {
editable as editablePlugin,
placeholder as placeholderPlugin
} from 'ngx-editor/plugins';

import defautlSchema from './schema';
import { parseContent } from './parsers';
import isNil from './utils/isNil';

type Content = string | Record<string, any> | null;
type JSONDoc = Record<string, null> | null;

interface NodeViews {
[name: string]: (
node: ProsemirrorNode,
view: EditorView,
getPos: () => number,
decorations: Decoration[]
) => NodeView;
}

interface Options {
content?: Content;
enabled?: boolean;
placeholder?: string;
schema?: Schema;
plugins?: Plugin[];
nodeViews?: NodeViews;
}

class Editor {
view: EditorView;
options: Options;
el: DocumentFragment;

onContentChange = new Subject<JSONDoc>();
onFocus = new Subject<void>();
onBlur = new Subject<void>();
onUpdate = new Subject();

constructor(options: Options) {
this.options = options;
this.createEditor(options);
}

get schema(): Schema {
return this.options.schema || defautlSchema;
}

setContent(content: Content): void {
if (isNil(content)) {
return;
}

const { state } = this.view;
const { tr, doc } = state;

const newDoc = parseContent(content, this.schema);

tr.replaceWith(0, state.doc.content.size, newDoc);

// don't emit if both content is same
if (doc.eq(tr.doc)) {
return;
}

if (!tr.docChanged) {
return;
}

this.view.dispatch(tr);
}

private handleTransactions(tr: Transaction): void {
const { state } = this.view.state.applyTransaction(tr);
this.view.updateState(state);

this.onUpdate.next();

if (!tr.docChanged) {
return;
}

const json = state.doc.toJSON();
this.onContentChange.next(json);
}

private createEditor(options: Options): void {
const { content, plugins, nodeViews, enabled } = options;
const schema = this.schema;

const editable = enabled ?? true;
const placeholder = options.placeholder ?? '';

const doc = parseContent(content, schema);
this.el = document.createDocumentFragment();

this.view = new EditorView(this.el, {
editable: () => editable,
state: EditorState.create({
doc,
schema,
plugins: [
editablePlugin(),
placeholderPlugin(placeholder),
...plugins
],
}),
nodeViews,
dispatchTransaction: this.handleTransactions.bind(this),
handleDOMEvents: {
focus: () => {
this.onFocus.next();
return false;
},
blur: () => {
this.onBlur.next();
return false;
}
},
attributes: {
class: 'NgxEditor__Content'
},
});
}

registerPlugin(plugin: Plugin): void {
const { state } = this.view;
const plugins = [...state.plugins, plugin];

const newState = state.reconfigure({ plugins });
this.view.updateState(newState);
}

enable(): void {
const { dispatch, state: { tr } } = this.view;
dispatch(tr.setMeta('UPDATE_EDITABLE', true));
}

disable(): void {
const { dispatch, state: { tr } } = this.view;
dispatch(tr.setMeta('UPDATE_EDITABLE', false));
}

setPlaceholder(placeholder: string): void {
const { dispatch, state: { tr } } = this.view;
dispatch(tr.setMeta('UPDATE_PLACEHOLDER', placeholder));
}

destroy(): void {
this.view.destroy();
}
}

export default Editor;
2 changes: 1 addition & 1 deletion src/lib/components/bubble/bubble.component.scss
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ $light-gray: #f1f1f1;
padding: 0.3rem;
margin-bottom: 0.3rem;
transform: translateX(-50%);
display: flex;
display: none;
align-items: center;

&::before,
Expand Down
Loading

0 comments on commit a5763a3

Please sign in to comment.