ngx-editor
Version:
Rich Text Editor for angular using ProseMirror
1,079 lines (1,036 loc) • 123 kB
JavaScript
import * as i0 from '@angular/core';
import { EventEmitter, Component, Input, Output, ViewChild, ComponentFactoryResolver, ApplicationRef, forwardRef, ViewEncapsulation, Injectable, Optional, Pipe, HostBinding, HostListener, NgModule, InjectionToken } from '@angular/core';
import * as i4 from '@angular/forms';
import { NG_VALUE_ACCESSOR, FormGroup, FormControl, Validators as Validators$1, ReactiveFormsModule } from '@angular/forms';
import { Plugin, PluginKey, NodeSelection, EditorState } from 'prosemirror-state';
import { DecorationSet, Decoration, EditorView } from 'prosemirror-view';
import * as i3 from '@angular/common';
import { CommonModule } from '@angular/common';
import { Fragment, Slice, DOMSerializer, DOMParser } from 'prosemirror-model';
import { schema } from 'ngx-editor/schema';
export { marks, nodes, schema } from 'ngx-editor/schema';
import { Subject, fromEvent, asyncScheduler } from 'rxjs';
import { toggleMark, lift, wrapIn, setBlockType, newlineInCode, createParagraphNear, liftEmptyBlock, splitBlock, chainCommands, exitCode, baseKeymap } from 'prosemirror-commands';
import { applyMark, removeLink, removeMark } from 'ngx-editor/commands';
import { isMarkActive, isNodeActive, getSelectionNodes, getSelectionMarks, markInputRule } from 'ngx-editor/helpers';
import { liftListItem, wrapInList, splitListItem, sinkListItem } from 'prosemirror-schema-list';
import * as i1 from '@angular/platform-browser';
import { throttleTime } from 'rxjs/operators';
import { isNil } from 'ngx-editor/utils';
import { keymap } from 'prosemirror-keymap';
import { undo, redo, history } from 'prosemirror-history';
import { wrappingInputRule, textblockTypeInputRule, smartQuotes, ellipsis, emDash, inputRules } from 'prosemirror-inputrules';
const editablePlugin = (editable = true) => {
return new Plugin({
key: new PluginKey('editable'),
state: {
init() {
return editable;
},
apply(tr, previousVal) {
return tr.getMeta('UPDATE_EDITABLE') ?? previousVal;
}
},
props: {
editable(state) {
return this.getState(state);
},
attributes(state) {
const isEnabled = this.getState(state);
if (isEnabled) {
return null;
}
return {
class: 'NgxEditor__Content--Disabled'
};
}
}
});
};
const PLACEHOLDER_CLASSNAME = 'NgxEditor__Placeholder';
const placeholderPlugin = (text) => {
return new Plugin({
key: new PluginKey('placeholder'),
state: {
init() {
return text ?? '';
},
apply(tr, previousVal) {
const placeholder = tr.getMeta('UPDATE_PLACEHOLDER') ?? previousVal;
return placeholder;
}
},
props: {
decorations(state) {
const { doc } = state;
const { textContent, childCount } = doc;
const placeholder = this.getState(state);
if (!placeholder || childCount > 1) {
return DecorationSet.empty;
}
const decorations = [];
const decorate = (node, pos) => {
if (node.type.isBlock && node.childCount === 0 && textContent.length === 0) {
const placeholderNode = Decoration.node(pos, (pos + node.nodeSize), {
class: PLACEHOLDER_CLASSNAME,
'data-placeholder': placeholder,
'data-align': node.attrs['align'] ?? null
});
decorations.push(placeholderNode);
}
return false;
};
doc.descendants(decorate);
return DecorationSet.create(doc, decorations);
}
}
});
};
const attributesPlugin = (attributes = {}) => {
return new Plugin({
key: new PluginKey('attributes'),
props: {
attributes
}
});
};
const focusPlugin = (cb) => {
return new Plugin({
key: new PluginKey('focus'),
props: {
handleDOMEvents: {
focus: () => {
cb();
return false;
}
}
}
});
};
const blurPlugin = (cb) => {
return new Plugin({
key: new PluginKey('blur'),
props: {
handleDOMEvents: {
blur: () => {
cb();
return false;
}
}
}
});
};
class ImageViewComponent {
constructor() {
this.alt = '';
this.title = '';
this.outerWidth = '';
this.selected = false;
this.imageResize = new EventEmitter();
}
startResizing(e, direction) {
e.preventDefault();
this.resizeImage(e, direction);
}
resizeImage(evt, direction) {
const startX = evt.pageX;
const startWidth = this.imgEl.nativeElement.clientWidth;
const isLeftResize = direction === 'left';
const { width } = window.getComputedStyle(this.view.dom);
const editorWidth = parseInt(width, 10);
const onMouseMove = (e) => {
const currentX = e.pageX;
const diffInPx = currentX - startX;
const computedWidth = isLeftResize ? startWidth - diffInPx : startWidth + diffInPx;
// prevent image overflow the editor
// prevent resizng below 20px
if (computedWidth > editorWidth || computedWidth < 20) {
return;
}
this.outerWidth = `${computedWidth}px`;
};
const onMouseUp = (e) => {
e.preventDefault();
document.removeEventListener('mousemove', onMouseMove);
document.removeEventListener('mouseup', onMouseUp);
this.imageResize.emit();
};
document.addEventListener('mousemove', onMouseMove);
document.addEventListener('mouseup', onMouseUp);
}
}
ImageViewComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.2.0", ngImport: i0, type: ImageViewComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
ImageViewComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "13.2.0", type: ImageViewComponent, selector: "ngx-image-view", inputs: { src: "src", alt: "alt", title: "title", outerWidth: "outerWidth", selected: "selected", view: "view" }, outputs: { imageResize: "imageResize" }, viewQueries: [{ propertyName: "imgEl", first: true, predicate: ["imgEl"], descendants: true, static: true }], ngImport: i0, template: "<span class=\"NgxEditor__ImageWrapper\" [ngClass]=\"{'NgxEditor__Resizer--Active': selected}\" [style.width]=\"outerWidth\">\n <span class=\"NgxEditor__ResizeHandle\" *ngIf=\"selected\">\n <span class=\"NgxEditor__ResizeHandle--TL\" (mousedown)=\"startResizing($event, 'left')\"></span>\n <span class=\"NgxEditor__ResizeHandle--TR\" (mousedown)=\"startResizing($event, 'right')\"></span>\n <span class=\"NgxEditor__ResizeHandle--BL\" (mousedown)=\"startResizing($event, 'left')\"></span>\n <span class=\"NgxEditor__ResizeHandle--BR\" (mousedown)=\"startResizing($event, 'right')\"></span>\n </span>\n <img [src]=\"src\" [alt]=\"alt\" [title]=\"title\" #imgEl />\n</span>\n", styles: ["*,*:before,*:after{box-sizing:border-box}img{width:100%;height:100%}.NgxEditor__ImageWrapper{position:relative;display:inline-block;line-height:0;padding:2px}.NgxEditor__ImageWrapper.NgxEditor__Resizer--Active{padding:1px;border:1px solid #1a73e8}.NgxEditor__ImageWrapper .NgxEditor__ResizeHandle{position:absolute;height:100%;width:100%}.NgxEditor__ImageWrapper .NgxEditor__ResizeHandle .NgxEditor__ResizeHandle--TL,.NgxEditor__ImageWrapper .NgxEditor__ResizeHandle .NgxEditor__ResizeHandle--BL,.NgxEditor__ImageWrapper .NgxEditor__ResizeHandle .NgxEditor__ResizeHandle--TR,.NgxEditor__ImageWrapper .NgxEditor__ResizeHandle .NgxEditor__ResizeHandle--BR{position:absolute;width:7px;height:7px;background-color:#1a73e8;border:1px solid white}.NgxEditor__ImageWrapper .NgxEditor__ResizeHandle .NgxEditor__ResizeHandle--BR{bottom:-5px;right:-5px;cursor:se-resize}.NgxEditor__ImageWrapper .NgxEditor__ResizeHandle .NgxEditor__ResizeHandle--TR{top:-5px;right:-5px;cursor:ne-resize}.NgxEditor__ImageWrapper .NgxEditor__ResizeHandle .NgxEditor__ResizeHandle--TL{top:-5px;left:-5px;cursor:nw-resize}.NgxEditor__ImageWrapper .NgxEditor__ResizeHandle .NgxEditor__ResizeHandle--BL{bottom:-5px;left:-5px;cursor:sw-resize}\n"], directives: [{ type: i3.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { type: i3.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }] });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.2.0", ngImport: i0, type: ImageViewComponent, decorators: [{
type: Component,
args: [{ selector: 'ngx-image-view', template: "<span class=\"NgxEditor__ImageWrapper\" [ngClass]=\"{'NgxEditor__Resizer--Active': selected}\" [style.width]=\"outerWidth\">\n <span class=\"NgxEditor__ResizeHandle\" *ngIf=\"selected\">\n <span class=\"NgxEditor__ResizeHandle--TL\" (mousedown)=\"startResizing($event, 'left')\"></span>\n <span class=\"NgxEditor__ResizeHandle--TR\" (mousedown)=\"startResizing($event, 'right')\"></span>\n <span class=\"NgxEditor__ResizeHandle--BL\" (mousedown)=\"startResizing($event, 'left')\"></span>\n <span class=\"NgxEditor__ResizeHandle--BR\" (mousedown)=\"startResizing($event, 'right')\"></span>\n </span>\n <img [src]=\"src\" [alt]=\"alt\" [title]=\"title\" #imgEl />\n</span>\n", styles: ["*,*:before,*:after{box-sizing:border-box}img{width:100%;height:100%}.NgxEditor__ImageWrapper{position:relative;display:inline-block;line-height:0;padding:2px}.NgxEditor__ImageWrapper.NgxEditor__Resizer--Active{padding:1px;border:1px solid #1a73e8}.NgxEditor__ImageWrapper .NgxEditor__ResizeHandle{position:absolute;height:100%;width:100%}.NgxEditor__ImageWrapper .NgxEditor__ResizeHandle .NgxEditor__ResizeHandle--TL,.NgxEditor__ImageWrapper .NgxEditor__ResizeHandle .NgxEditor__ResizeHandle--BL,.NgxEditor__ImageWrapper .NgxEditor__ResizeHandle .NgxEditor__ResizeHandle--TR,.NgxEditor__ImageWrapper .NgxEditor__ResizeHandle .NgxEditor__ResizeHandle--BR{position:absolute;width:7px;height:7px;background-color:#1a73e8;border:1px solid white}.NgxEditor__ImageWrapper .NgxEditor__ResizeHandle .NgxEditor__ResizeHandle--BR{bottom:-5px;right:-5px;cursor:se-resize}.NgxEditor__ImageWrapper .NgxEditor__ResizeHandle .NgxEditor__ResizeHandle--TR{top:-5px;right:-5px;cursor:ne-resize}.NgxEditor__ImageWrapper .NgxEditor__ResizeHandle .NgxEditor__ResizeHandle--TL{top:-5px;left:-5px;cursor:nw-resize}.NgxEditor__ImageWrapper .NgxEditor__ResizeHandle .NgxEditor__ResizeHandle--BL{bottom:-5px;left:-5px;cursor:sw-resize}\n"] }]
}], propDecorators: { src: [{
type: Input
}], alt: [{
type: Input
}], title: [{
type: Input
}], outerWidth: [{
type: Input
}], selected: [{
type: Input
}], view: [{
type: Input
}], imageResize: [{
type: Output
}], imgEl: [{
type: ViewChild,
args: ['imgEl', { static: true }]
}] } });
class ImageRezieView {
constructor(node, view, getPos, injector) {
this.updating = false;
this.handleResize = () => {
if (this.updating) {
return;
}
const { state, dispatch } = this.view;
const { tr } = state;
const transaction = tr.setNodeMarkup(this.getPos(), undefined, {
...this.node.attrs,
width: this.imageComponentRef.instance.outerWidth,
});
const resolvedPos = transaction.doc.resolve(this.getPos());
const newSelection = new NodeSelection(resolvedPos);
transaction.setSelection(newSelection);
dispatch(transaction);
};
const dom = document.createElement('image-view');
const componentFactoryResolver = injector.get(ComponentFactoryResolver);
this.applicationRef = injector.get(ApplicationRef);
// Create the component and wire it up with the element
const factory = componentFactoryResolver.resolveComponentFactory(ImageViewComponent);
this.imageComponentRef = factory.create(injector, [], dom);
// Attach to the view so that the change detector knows to run
this.applicationRef.attachView(this.imageComponentRef.hostView);
this.setNodeAttributes(node.attrs);
this.imageComponentRef.instance.view = view;
this.dom = dom;
this.view = view;
this.node = node;
this.getPos = getPos;
this.resizeSubscription = this.imageComponentRef.instance.imageResize.subscribe(() => {
this.handleResize();
});
}
computeChanges(prevAttrs, newAttrs) {
return JSON.stringify(prevAttrs) === JSON.stringify(newAttrs);
}
setNodeAttributes(attrs) {
this.imageComponentRef.instance.src = attrs['src'];
this.imageComponentRef.instance.alt = attrs['alt'];
this.imageComponentRef.instance.title = attrs['title'];
this.imageComponentRef.instance.outerWidth = attrs['width'];
}
update(node) {
if (node.type !== this.node.type) {
return false;
}
this.node = node;
const changed = this.computeChanges(this.node.attrs, node.attrs);
if (changed) {
this.updating = true;
this.setNodeAttributes(node.attrs);
this.updating = false;
}
return true;
}
ignoreMutation() {
return true;
}
selectNode() {
this.imageComponentRef.instance.selected = true;
}
deselectNode() {
this.imageComponentRef.instance.selected = false;
}
destroy() {
this.resizeSubscription.unsubscribe();
this.applicationRef.detachView(this.imageComponentRef.hostView);
}
}
const imageResizePlugin = (injector) => {
return new Plugin({
key: new PluginKey('image-resize'),
props: {
nodeViews: {
image: (node, view, getPos) => {
return new ImageRezieView(node, view, getPos, injector);
},
}
}
});
};
const HTTP_LINK_REGEX = /((https?:\/\/)?[\w-]+(\.[\w-]+)+\.?(:\d+)?(\/\S*)?)$/;
const linkify = (fragment) => {
const linkified = [];
fragment.forEach((child) => {
if (child.isText) {
const text = child.text;
let pos = 0;
const match = HTTP_LINK_REGEX.exec(text);
if (match) {
const start = match.index;
const end = start + match[0].length;
const link = child.type.schema.marks.link;
if (start > 0) {
linkified.push(child.cut(pos, start));
}
const urlText = text.slice(start, end);
linkified.push(child.cut(start, end).mark(link.create({ href: urlText }).addToSet(child.marks)));
pos = end;
}
if (pos < text.length) {
linkified.push(child.cut(pos));
}
}
else {
linkified.push(child.copy(linkify(child.content)));
}
});
return Fragment.fromArray(linkified);
};
const linkifyPlugin = () => {
return new Plugin({
key: new PluginKey('linkify'),
props: {
transformPasted: (slice) => {
return new Slice(linkify(slice.content), slice.openStart, slice.openEnd);
}
}
});
};
const emptyDoc = {
type: 'doc',
content: [
{
type: 'paragraph',
}
],
};
// https://developer.mozilla.org/en-US/docs/Web/API/DocumentFragment
const toHTML = (json, inputSchema) => {
const schema$1 = inputSchema ?? schema;
const contentNode = schema$1.nodeFromJSON(json);
const html = DOMSerializer.fromSchema(schema$1).serializeFragment(contentNode.content);
const div = document.createElement('div');
div.appendChild(html);
return div.innerHTML;
};
const toDoc = (html, inputSchema) => {
const schema$1 = inputSchema ?? schema;
const el = document.createElement('div');
el.innerHTML = html;
return DOMParser.fromSchema(schema$1).parse(el).toJSON();
};
const parseContent = (value, schema) => {
if (!value) {
return schema.nodeFromJSON(emptyDoc);
}
if (typeof value !== 'string') {
return schema.nodeFromJSON(value);
}
const docJson = toDoc(value, schema);
return schema.nodeFromJSON(docJson);
};
class NgxEditorComponent {
constructor(_renderer, _injector, _elementRef) {
this._renderer = _renderer;
this._injector = _injector;
this._elementRef = _elementRef;
this.placeholder = 'Type Here...';
this.focusOut = new EventEmitter();
this.focusIn = new EventEmitter();
this.subscriptions = [];
this.onChange = () => { };
this.onTouched = () => { };
}
writeValue(value) {
if (!this.outputFormat && typeof value === 'string') {
this.outputFormat = 'html';
}
this.editor.setContent(value ?? '');
}
registerOnChange(fn) {
this.onChange = fn;
}
registerOnTouched(fn) {
this.onTouched = fn;
}
setDisabledState(isDisabled) {
this.setMeta('UPDATE_EDITABLE', !isDisabled);
this._renderer.setProperty(this._elementRef.nativeElement, 'disabled', isDisabled);
}
handleChange(jsonDoc) {
if (this.outputFormat === 'html') {
const html = toHTML(jsonDoc, this.editor.schema);
this.onChange(html);
return;
}
this.onChange(jsonDoc);
}
setMeta(key, value) {
const { dispatch, state: { tr } } = this.editor.view;
dispatch(tr.setMeta(key, value));
}
setPlaceholder(placeholder) {
this.setMeta('UPDATE_PLACEHOLDER', placeholder);
}
registerPlugins() {
this.editor.registerPlugin(editablePlugin());
this.editor.registerPlugin(placeholderPlugin(this.placeholder));
this.editor.registerPlugin(attributesPlugin({
class: 'NgxEditor__Content'
}));
this.editor.registerPlugin(focusPlugin(() => {
this.focusIn.emit();
}));
this.editor.registerPlugin(focusPlugin(() => {
this.focusIn.emit();
}));
this.editor.registerPlugin(blurPlugin(() => {
this.focusOut.emit();
this.onTouched();
}));
if (this.editor.features.resizeImage) {
this.editor.registerPlugin(imageResizePlugin(this._injector));
}
if (this.editor.features.linkOnPaste) {
this.editor.registerPlugin(linkifyPlugin());
}
}
ngOnInit() {
if (!this.editor) {
throw new Error('NgxEditor: Required editor instance');
}
this.registerPlugins();
this._renderer.appendChild(this.ngxEditor.nativeElement, this.editor.view.dom);
const contentChangeSubscription = this.editor.valueChanges.subscribe(jsonDoc => {
this.handleChange(jsonDoc);
});
this.subscriptions.push(contentChangeSubscription);
}
ngOnChanges(changes) {
if (changes?.['placeholder'] && !changes['placeholder'].isFirstChange()) {
this.setPlaceholder(changes['placeholder'].currentValue);
}
}
ngOnDestroy() {
this.subscriptions.forEach(subscription => {
subscription.unsubscribe();
});
}
}
NgxEditorComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.2.0", ngImport: i0, type: NgxEditorComponent, deps: [{ token: i0.Renderer2 }, { token: i0.Injector }, { token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Component });
NgxEditorComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "13.2.0", type: NgxEditorComponent, selector: "ngx-editor", inputs: { editor: "editor", outputFormat: "outputFormat", placeholder: "placeholder" }, outputs: { focusOut: "focusOut", focusIn: "focusIn" }, providers: [{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => NgxEditorComponent),
multi: true
}], viewQueries: [{ propertyName: "ngxEditor", first: true, predicate: ["ngxEditor"], descendants: true, static: true }], usesOnChanges: true, ngImport: i0, template: "<div class=\"NgxEditor\" #ngxEditor>\n <ng-content></ng-content>\n</div>\n", styles: [".NgxEditor{background:white;color:#000;background-clip:padding-box;border-radius:4px;border:1px solid rgba(0,0,0,.2);position:relative}.NgxEditor--Disabled{opacity:.5;pointer-events:none}.NgxEditor__Placeholder:before{color:#6c757d;opacity:1;-webkit-user-select:none;user-select:none;position:absolute;cursor:text;content:attr(data-placeholder)}.NgxEditor__Placeholder[data-align=right]:before{position:relative}.NgxEditor__Content{padding:8px;white-space:pre-wrap;outline:none;font-variant-ligatures:none;font-feature-settings:\"liga\" 0,none}.NgxEditor__Content p{margin:0 0 10px}.NgxEditor__Content blockquote{padding-left:16px;border-left:3px solid #ddd;margin-left:0;margin-right:0}.NgxEditor__Content--Disabled{-webkit-user-select:none;user-select:none;pointer-events:none}.NgxEditor__Wrapper{border:1px solid rgba(0,0,0,.4);border-radius:4px}.NgxEditor__Wrapper .NgxEditor__MenuBar{border-top-left-radius:4px;border-top-right-radius:4px;border-bottom:1px solid rgba(0,0,0,.2)}.NgxEditor__Wrapper .NgxEditor{border-top-left-radius:0;border-top-right-radius:0;border:none}.NgxEditor__MenuBar{display:flex;padding:3px;cursor:default;background-color:#fff}.NgxEditor__MenuItem{border-radius:2px;display:flex;align-items:center;justify-content:center;position:relative;flex-shrink:0}.NgxEditor__MenuItem:hover{background-color:#f1f1f1}.NgxEditor__MenuItem.NgxEditor__MenuItem--Icon{height:30px;width:30px;transition:.3s ease-in-out}.NgxEditor__MenuItem.NgxEditor__MenuItem--Icon+.NgxEditor__MenuItem--Icon{margin-left:2px}.NgxEditor__MenuItem .NgxEditor__MenuItem--IconContainer{display:flex;height:100%;width:100%;align-items:center;justify-content:center}.NgxEditor__MenuItem.NgxEditor__MenuItem--Text{padding:0 5px}.NgxEditor__MenuItem.NgxEditor__MenuItem--Active{background-color:#e8f0fe;color:#1a73e8}.NgxEditor__Dropdown{min-width:64px;position:relative;display:flex;align-items:center;flex-shrink:0}.NgxEditor__Dropdown:hover{background-color:#f1f1f1}.NgxEditor__Dropdown .NgxEditor__Dropdown--Text{display:flex;align-items:center;justify-content:center;padding:0 5px;height:100%;width:100%}.NgxEditor__Dropdown .NgxEditor__Dropdown--Text:after{display:inline-block;content:\"\";margin-left:24px;vertical-align:4px;border-top:4px solid;border-right:4px solid transparent;border-bottom:0;border-left:4px solid transparent}.NgxEditor__Dropdown .NgxEditor__Dropdown--DropdownMenu{position:absolute;left:0;box-shadow:#3c404326 0 2px 6px 2px;border-radius:4px;background-color:#fff;z-index:10;width:100%;top:32px}.NgxEditor__Dropdown .NgxEditor__Dropdown--Item{padding:8px;white-space:nowrap;color:inherit}.NgxEditor__Dropdown .NgxEditor__Dropdown--Item:hover{background-color:#ececec}.NgxEditor__Dropdown.NgxEditor__Dropdown--Selected,.NgxEditor__Dropdown.NgxEditor__Dropdown--Open{background-color:#e8f0fe}.NgxEditor__Dropdown.NgxEditor__Dropdown--Selected .NgxEditor__Dropdown--Text,.NgxEditor__Dropdown.NgxEditor__Dropdown--Open .NgxEditor__Dropdown--Text{color:#1a73e8}.NgxEditor__Dropdown .NgxEditor__Dropdown--Active{background-color:#f1f1f1}.NgxEditor__Dropdown .NgxEditor__Dropdown--Active:hover{background-color:#e7e7e7}.NgxEditor__MenuBar--Reverse .NgxEditor__Dropdown--DropdownMenu{top:unset;bottom:32px}.NgxEditor__MenuBar--Reverse .NgxEditor__Dropdown--Text:after{transform:rotate(180deg)}.NgxEditor__MenuBar--Reverse .NgxEditor__Popup{top:unset;bottom:32px}.NgxEditor__Popup{position:absolute;top:32px;box-shadow:#3c404326 0 2px 6px 2px;border-radius:4px;background-color:#fff;z-index:10;min-width:192px;padding:8px}.NgxEditor__Popup .NgxEditor__Popup--FormGroup{margin-bottom:8px}.NgxEditor__Popup .NgxEditor__Popup--FormGroup label{margin-bottom:3px}.NgxEditor__Popup .NgxEditor__Popup--FormGroup input[type=text],.NgxEditor__Popup .NgxEditor__Popup--FormGroup input[type=url]{padding:2px 4px}.NgxEditor__Popup .NgxEditor__Popup--Col{display:flex;flex-direction:column;position:relative}.NgxEditor__Popup .NgxEditor__Popup--Label{font-size:85%}.NgxEditor__Seperator{border-left:1px solid #ccc;margin:0 5px}.NgxEditor__HelpText{font-size:80%}.NgxEditor__HelpText.NgxEditor__HelpText--Error{color:red}\n"], encapsulation: i0.ViewEncapsulation.None });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.2.0", ngImport: i0, type: NgxEditorComponent, decorators: [{
type: Component,
args: [{ selector: 'ngx-editor', providers: [{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => NgxEditorComponent),
multi: true
}], encapsulation: ViewEncapsulation.None, template: "<div class=\"NgxEditor\" #ngxEditor>\n <ng-content></ng-content>\n</div>\n", styles: [".NgxEditor{background:white;color:#000;background-clip:padding-box;border-radius:4px;border:1px solid rgba(0,0,0,.2);position:relative}.NgxEditor--Disabled{opacity:.5;pointer-events:none}.NgxEditor__Placeholder:before{color:#6c757d;opacity:1;-webkit-user-select:none;user-select:none;position:absolute;cursor:text;content:attr(data-placeholder)}.NgxEditor__Placeholder[data-align=right]:before{position:relative}.NgxEditor__Content{padding:8px;white-space:pre-wrap;outline:none;font-variant-ligatures:none;font-feature-settings:\"liga\" 0,none}.NgxEditor__Content p{margin:0 0 10px}.NgxEditor__Content blockquote{padding-left:16px;border-left:3px solid #ddd;margin-left:0;margin-right:0}.NgxEditor__Content--Disabled{-webkit-user-select:none;user-select:none;pointer-events:none}.NgxEditor__Wrapper{border:1px solid rgba(0,0,0,.4);border-radius:4px}.NgxEditor__Wrapper .NgxEditor__MenuBar{border-top-left-radius:4px;border-top-right-radius:4px;border-bottom:1px solid rgba(0,0,0,.2)}.NgxEditor__Wrapper .NgxEditor{border-top-left-radius:0;border-top-right-radius:0;border:none}.NgxEditor__MenuBar{display:flex;padding:3px;cursor:default;background-color:#fff}.NgxEditor__MenuItem{border-radius:2px;display:flex;align-items:center;justify-content:center;position:relative;flex-shrink:0}.NgxEditor__MenuItem:hover{background-color:#f1f1f1}.NgxEditor__MenuItem.NgxEditor__MenuItem--Icon{height:30px;width:30px;transition:.3s ease-in-out}.NgxEditor__MenuItem.NgxEditor__MenuItem--Icon+.NgxEditor__MenuItem--Icon{margin-left:2px}.NgxEditor__MenuItem .NgxEditor__MenuItem--IconContainer{display:flex;height:100%;width:100%;align-items:center;justify-content:center}.NgxEditor__MenuItem.NgxEditor__MenuItem--Text{padding:0 5px}.NgxEditor__MenuItem.NgxEditor__MenuItem--Active{background-color:#e8f0fe;color:#1a73e8}.NgxEditor__Dropdown{min-width:64px;position:relative;display:flex;align-items:center;flex-shrink:0}.NgxEditor__Dropdown:hover{background-color:#f1f1f1}.NgxEditor__Dropdown .NgxEditor__Dropdown--Text{display:flex;align-items:center;justify-content:center;padding:0 5px;height:100%;width:100%}.NgxEditor__Dropdown .NgxEditor__Dropdown--Text:after{display:inline-block;content:\"\";margin-left:24px;vertical-align:4px;border-top:4px solid;border-right:4px solid transparent;border-bottom:0;border-left:4px solid transparent}.NgxEditor__Dropdown .NgxEditor__Dropdown--DropdownMenu{position:absolute;left:0;box-shadow:#3c404326 0 2px 6px 2px;border-radius:4px;background-color:#fff;z-index:10;width:100%;top:32px}.NgxEditor__Dropdown .NgxEditor__Dropdown--Item{padding:8px;white-space:nowrap;color:inherit}.NgxEditor__Dropdown .NgxEditor__Dropdown--Item:hover{background-color:#ececec}.NgxEditor__Dropdown.NgxEditor__Dropdown--Selected,.NgxEditor__Dropdown.NgxEditor__Dropdown--Open{background-color:#e8f0fe}.NgxEditor__Dropdown.NgxEditor__Dropdown--Selected .NgxEditor__Dropdown--Text,.NgxEditor__Dropdown.NgxEditor__Dropdown--Open .NgxEditor__Dropdown--Text{color:#1a73e8}.NgxEditor__Dropdown .NgxEditor__Dropdown--Active{background-color:#f1f1f1}.NgxEditor__Dropdown .NgxEditor__Dropdown--Active:hover{background-color:#e7e7e7}.NgxEditor__MenuBar--Reverse .NgxEditor__Dropdown--DropdownMenu{top:unset;bottom:32px}.NgxEditor__MenuBar--Reverse .NgxEditor__Dropdown--Text:after{transform:rotate(180deg)}.NgxEditor__MenuBar--Reverse .NgxEditor__Popup{top:unset;bottom:32px}.NgxEditor__Popup{position:absolute;top:32px;box-shadow:#3c404326 0 2px 6px 2px;border-radius:4px;background-color:#fff;z-index:10;min-width:192px;padding:8px}.NgxEditor__Popup .NgxEditor__Popup--FormGroup{margin-bottom:8px}.NgxEditor__Popup .NgxEditor__Popup--FormGroup label{margin-bottom:3px}.NgxEditor__Popup .NgxEditor__Popup--FormGroup input[type=text],.NgxEditor__Popup .NgxEditor__Popup--FormGroup input[type=url]{padding:2px 4px}.NgxEditor__Popup .NgxEditor__Popup--Col{display:flex;flex-direction:column;position:relative}.NgxEditor__Popup .NgxEditor__Popup--Label{font-size:85%}.NgxEditor__Seperator{border-left:1px solid #ccc;margin:0 5px}.NgxEditor__HelpText{font-size:80%}.NgxEditor__HelpText.NgxEditor__HelpText--Error{color:red}\n"] }]
}], ctorParameters: function () { return [{ type: i0.Renderer2 }, { type: i0.Injector }, { type: i0.ElementRef }]; }, propDecorators: { ngxEditor: [{
type: ViewChild,
args: ['ngxEditor', { static: true }]
}], editor: [{
type: Input
}], outputFormat: [{
type: Input
}], placeholder: [{
type: Input
}], focusOut: [{
type: Output
}], focusIn: [{
type: Output
}] } });
class MenuService {
constructor() {
this.customMenuRefChange = new Subject();
}
setCustomMenuRef(c) {
this.customMenuRefChange.next(c);
}
}
MenuService.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.2.0", ngImport: i0, type: MenuService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
MenuService.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "13.2.0", ngImport: i0, type: MenuService, providedIn: 'root' });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.2.0", ngImport: i0, type: MenuService, decorators: [{
type: Injectable,
args: [{
providedIn: 'root'
}]
}] });
var bold = `
<path d="M15.6 10.79c.97-.67 1.65-1.77 1.65-2.79 0-2.26-1.75-4-4-4H7v14h7.04c2.09 0 3.71-1.7 3.71-3.79 0-1.52-.86-2.82-2.15-3.42zM10 6.5h3c.83 0 1.5.67 1.5 1.5s-.67 1.5-1.5 1.5h-3v-3zm3.5 9H10v-3h3.5c.83 0 1.5.67 1.5 1.5s-.67 1.5-1.5 1.5z" />
`;
var italic = `
<path d="M10 4v3h2.21l-3.42 8H6v3h8v-3h-2.21l3.42-8H18V4z" />
`;
var code = `
<path d="M9.4 16.6L4.8 12l4.6-4.6L8 6l-6 6 6 6 1.4-1.4zm5.2 0l4.6-4.6-4.6-4.6L16 6l6 6-6 6-1.4-1.4z"/>
`;
var underline = `
<path d="M12 17c3.31 0 6-2.69 6-6V3h-2.5v8c0 1.93-1.57 3.5-3.5 3.5S8.5 12.93 8.5 11V3H6v8c0 3.31 2.69 6 6 6zm-7 2v2h14v-2H5z"/>
`;
var strike = `
<path d="M6.85,7.08C6.85,4.37,9.45,3,12.24,3c1.64,0,3,0.49,3.9,1.28c0.77,0.65,1.46,1.73,1.46,3.24h-3.01 c0-0.31-0.05-0.59-0.15-0.85c-0.29-0.86-1.2-1.28-2.25-1.28c-1.86,0-2.34,1.02-2.34,1.7c0,0.48,0.25,0.88,0.74,1.21 C10.97,8.55,11.36,8.78,12,9H7.39C7.18,8.66,6.85,8.11,6.85,7.08z M21,12v-2H3v2h9.62c1.15,0.45,1.96,0.75,1.96,1.97 c0,1-0.81,1.67-2.28,1.67c-1.54,0-2.93-0.54-2.93-2.51H6.4c0,0.55,0.08,1.13,0.24,1.58c0.81,2.29,3.29,3.3,5.67,3.3 c2.27,0,5.3-0.89,5.3-4.05c0-0.3-0.01-1.16-0.48-1.94H21V12z"/>
`;
var orderedList = `
<path d="M2 17h2v.5H3v1h1v.5H2v1h3v-4H2v1zm1-9h1V4H2v1h1v3zm-1 3h1.8L2 13.1v.9h3v-1H3.2L5 10.9V10H2v1zm5-6v2h14V5H7zm0 14h14v-2H7v2zm0-6h14v-2H7v2z"/>
`;
var bulletList = `
<path d="M4 10.5c-.83 0-1.5.67-1.5 1.5s.67 1.5 1.5 1.5 1.5-.67 1.5-1.5-.67-1.5-1.5-1.5zm0-6c-.83 0-1.5.67-1.5 1.5S3.17 7.5 4 7.5 5.5 6.83 5.5 6 4.83 4.5 4 4.5zm0 12c-.83 0-1.5.68-1.5 1.5s.68 1.5 1.5 1.5 1.5-.68 1.5-1.5-.67-1.5-1.5-1.5zM7 19h14v-2H7v2zm0-6h14v-2H7v2zm0-8v2h14V5H7z"/>
`;
var quote = `
<path d="M0 0h24v24H0z" fill="none"/><path d="M6 17h3l2-4V7H5v6h3zm8 0h3l2-4V7h-6v6h3z"/>
`;
var link = `
<path d="M0 0h24v24H0z" fill="none"/><path d="M3.9 12c0-1.71 1.39-3.1 3.1-3.1h4V7H7c-2.76 0-5 2.24-5 5s2.24 5 5 5h4v-1.9H7c-1.71 0-3.1-1.39-3.1-3.1zM8 13h8v-2H8v2zm9-6h-4v1.9h4c1.71 0 3.1 1.39 3.1 3.1s-1.39 3.1-3.1 3.1h-4V17h4c2.76 0 5-2.24 5-5s-2.24-5-5-5z"/>
`;
var unlink = `
<path d="M17 7h-4v1.9h4c1.71 0 3.1 1.39 3.1 3.1 0 1.43-.98 2.63-2.31 2.98l1.46 1.46C20.88 15.61 22 13.95 22 12c0-2.76-2.24-5-5-5zm-1 4h-2.19l2 2H16zM2 4.27l3.11 3.11C3.29 8.12 2 9.91 2 12c0 2.76 2.24 5 5 5h4v-1.9H7c-1.71 0-3.1-1.39-3.1-3.1 0-1.59 1.21-2.9 2.76-3.07L8.73 11H8v2h2.73L13 15.27V17h1.73l4.01 4L20 19.74 3.27 3 2 4.27z"/>
`;
var image = `
<path d="M21 19V5c0-1.1-.9-2-2-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2zM8.5 13.5l2.5 3.01L14.5 12l4.5 6H5l3.5-4.5z"/>
`;
var alignLeft = `
<path d="M15 15H3v2h12v-2zm0-8H3v2h12V7zM3 13h18v-2H3v2zm0 8h18v-2H3v2zM3 3v2h18V3H3z"/>
`;
var alignCenter = `
<path d="M7 15v2h10v-2H7zm-4 6h18v-2H3v2zm0-8h18v-2H3v2zm4-6v2h10V7H7zM3 3v2h18V3H3z"/>
`;
var alignRight = `
<path d="M3 21h18v-2H3v2zm6-4h12v-2H9v2zm-6-4h18v-2H3v2zm6-4h12V7H9v2zM3 3v2h18V3H3z"/>
`;
var alignJustify = `
<path d="M3 21h18v-2H3v2zm0-4h18v-2H3v2zm0-4h18v-2H3v2zm0-4h18V7H3v2zm0-6v2h18V3H3z"/>
`;
var textColor = `
<path d="M2,20h20v4H2V20z M5.49,17h2.42l1.27-3.58h5.65L16.09,17h2.42L13.25,3h-2.5L5.49,17z M9.91,11.39l2.03-5.79h0.12l2.03,5.79 H9.91z"/>
`;
var colorFill = `
<path d="M16.56,8.94L7.62,0L6.21,1.41l2.38,2.38L3.44,8.94c-0.59,0.59-0.59,1.54,0,2.12l5.5,5.5C9.23,16.85,9.62,17,10,17 s0.77-0.15,1.06-0.44l5.5-5.5C17.15,10.48,17.15,9.53,16.56,8.94z M5.21,10L10,5.21L14.79,10H5.21z M19,11.5c0,0-2,2.17-2,3.5 c0,1.1,0.9,2,2,2s2-0.9,2-2C21,13.67,19,11.5,19,11.5z M2,20h20v4H2V20z"/>
`;
// Icons source: https://material.io/
const DEFAULT_ICON_HEIGHT = 20;
const DEFAULT_ICON_WIDTH = 20;
const DEFAULT_ICON_FILL = 'currentColor';
const icons = {
bold,
italic,
code,
underline,
strike,
ordered_list: orderedList,
bullet_list: bulletList,
blockquote: quote,
link,
unlink,
image,
align_left: alignLeft,
align_center: alignCenter,
align_right: alignRight,
align_justify: alignJustify,
text_color: textColor,
color_fill: colorFill
};
class Icon {
static get(name, fill = DEFAULT_ICON_FILL) {
const path = icons[name] || '<path></path>';
return `
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill=${fill}
height=${DEFAULT_ICON_HEIGHT}
width=${DEFAULT_ICON_WIDTH}
>
${path}
</svg>
`;
}
}
class Mark {
constructor(name) {
this.name = name;
}
apply() {
return (state, dispatch) => {
const { schema } = state;
const type = schema.marks[this.name];
if (!type) {
return false;
}
return applyMark(type)(state, dispatch);
};
}
toggle() {
return (state, dispatch) => {
const { schema } = state;
const type = schema.marks[this.name];
if (!type) {
return false;
}
return toggleMark(type)(state, dispatch);
};
}
isActive(state) {
const { schema } = state;
const type = schema.marks[this.name];
if (!type) {
return false;
}
return isMarkActive(state, type);
}
canExecute(state) {
return this.toggle()(state);
}
}
class Blockqote {
toggle() {
return (state, dispatch) => {
const { schema } = state;
const type = schema.nodes.blockquote;
if (!type) {
return false;
}
if (this.isActive(state)) {
return lift(state, dispatch);
}
return wrapIn(type)(state, dispatch);
};
}
isActive(state) {
const { schema } = state;
const type = schema.nodes.blockquote;
if (!type) {
return false;
}
return isNodeActive(state, type);
}
canExecute(state) {
return this.toggle()(state);
}
}
class ListItem {
constructor(isBulletList = false) {
this.isBulletList = false;
this.isBulletList = isBulletList;
}
getType(schema) {
return this.isBulletList ? schema.nodes['bullet_list'] : schema.nodes['ordered_list'];
}
toggle() {
return (state, dispatch) => {
const { schema } = state;
const type = this.getType(schema);
if (!type) {
return false;
}
if (this.isActive(state)) {
return liftListItem(schema.nodes.list_item)(state, dispatch);
}
return wrapInList(type)(state, dispatch);
};
}
isActive(state) {
const { schema } = state;
const type = this.getType(schema);
if (!type) {
return false;
}
return isNodeActive(state, type);
}
canExecute(state) {
return this.toggle()(state);
}
}
class Heading {
constructor(level) {
this.level = level;
}
apply() {
return (state, dispatch) => {
const { schema } = state;
const type = schema.nodes.heading;
if (!type) {
return false;
}
return setBlockType(type)(state, dispatch);
};
}
toggle() {
return (state, dispatch) => {
const { schema, selection, doc } = state;
const type = schema.nodes.heading;
if (!type) {
return false;
}
const nodePos = selection.$from.before(1);
const node = doc.nodeAt(nodePos);
const attrs = node?.attrs ?? {};
if (this.isActive(state)) {
return setBlockType(schema.nodes.paragraph, attrs)(state, dispatch);
}
return setBlockType(type, { ...attrs, level: this.level })(state, dispatch);
};
}
isActive(state) {
const { schema } = state;
const nodesInSelection = getSelectionNodes(state);
const type = schema.nodes.heading;
if (!type) {
return false;
}
const supportedNodes = [
type,
schema.nodes.text,
schema.nodes.blockquote
];
// heading is a text node
// don't mark as active when it has more nodes
const nodes = nodesInSelection.filter(node => {
return supportedNodes.includes(node.type);
});
const acitveNode = nodes.find((node) => {
return node.attrs['level'] === this.level;
});
return Boolean(acitveNode);
}
canExecute(state) {
return this.toggle()(state);
}
}
class TextAlign {
constructor(align) {
this.align = align;
}
toggle() {
return (state, dispatch) => {
const { doc, selection, tr, schema } = state;
const { from, to } = selection;
let applicable = false;
doc.nodesBetween(from, to, (node, pos) => {
const nodeType = node.type;
if ([schema.nodes.paragraph, schema.nodes.heading].includes(nodeType)) {
applicable = true;
tr.setNodeMarkup(pos, nodeType, { ...node.attrs, align: this.align });
}
return true;
});
if (!applicable) {
return false;
}
if (tr.docChanged) {
dispatch?.(tr);
}
return true;
};
}
isActive(state) {
const nodes = getSelectionNodes(state);
const active = nodes.find((node) => {
return node.attrs['align'] === this.align;
});
return Boolean(active);
}
canExecute(state) {
return this.toggle()(state);
}
}
const defaultOptions = {
strict: true
};
class Link$1 {
update(attrs) {
return (state, dispatch) => {
const { schema, selection } = state;
const type = schema.marks.link;
if (!type) {
return false;
}
if (selection.empty) {
return false;
}
return toggleMark(type, attrs)(state, dispatch);
};
}
insert(text, attrs) {
return (state, dispatch) => {
const { schema, tr } = state;
const type = schema.marks.link;
if (!type) {
return false;
}
const linkAttrs = {
href: attrs.href,
title: attrs.title ?? text,
target: attrs.target ?? '_blank'
};
const node = schema.text(text, [schema.marks.link.create(linkAttrs)]);
tr.replaceSelectionWith(node, false)
.scrollIntoView();
if (tr.docChanged) {
dispatch?.(tr);
return true;
}
return false;
};
}
isActive(state, options = defaultOptions) {
if (options.strict) {
return true;
}
const { schema } = state;
const type = schema.marks.link;
if (!type) {
return false;
}
return isMarkActive(state, type);
}
remove(state, dispatch) {
return removeLink()(state, dispatch);
}
canExecute(state) {
const testAttrs = {
href: ''
};
return this.insert('Exec', testAttrs)(state) || this.update(testAttrs)(state);
}
}
class Image$1 {
insert(src, attrs) {
return (state, dispatch) => {
const { schema, tr, selection } = state;
const type = schema.nodes.image;
if (!type) {
return false;
}
const imageAttrs = {
width: null,
src,
...attrs
};
if (!imageAttrs.width && selection instanceof NodeSelection && selection.node.type === type) {
imageAttrs.width = selection.node.attrs['width'];
}
tr.replaceSelectionWith(type.createAndFill(imageAttrs));
const resolvedPos = tr.doc.resolve(tr.selection.anchor - tr.selection.$anchor.nodeBefore.nodeSize);
tr
.setSelection(new NodeSelection(resolvedPos))
.scrollIntoView();
if (tr.docChanged) {
dispatch?.(tr);
return true;
}
return false;
};
}
isActive(state) {
const { selection } = state;
if (selection instanceof NodeSelection) {
return selection.node.type.name === 'image';
}
return false;
}
}
class TextColor$1 {
constructor(name, attrName = 'color') {
this.name = name;
this.attrName = attrName;
}
apply(attrs) {
return (state, dispatch) => {
const { schema, selection, doc } = state;
const type = schema.marks[this.name];
if (!type) {
return false;
}
const { from, to, empty } = selection;
if (!empty && (from + 1 === to)) {
const node = doc.nodeAt(from);
if (node?.isAtom && !node.isText && node.isLeaf) {
// An atomic node (e.g. Image) is selected.
return false;
}
}
return applyMark(type, attrs)(state, dispatch);
};
}
isActive(state) {
const { schema } = state;
const type = schema.marks[this.name];
if (!type) {
return false;
}
return isMarkActive(state, type);
}
getActiveColors(state) {
if (!this.isActive(state)) {
return [];
}
const { schema } = state;
const marks = getSelectionMarks(state);
const colors = marks
.filter(mark => mark.type === schema.marks[this.name])
.map(mark => {
return mark.attrs[this.attrName];
})
.filter(Boolean);
return colors;
}
remove() {
return (state, dispatch) => {
const { schema } = state;
const type = schema.marks[this.name];
if (!type) {
return false;
}
return removeMark(type)(state, dispatch);
};
}
canExecute(state) {
const attrs = this.name === 'text_color' ? { color: '' } : { backgroundColor: '' };
return this.apply(attrs)(state);
}
}
const STRONG = new Mark('strong');
const EM = new Mark('em');
const CODE = new Mark('code');
const UNDERLINE = new Mark('u');
const STRIKE = new Mark('s');
const BLOCKQUOTE = new Blockqote();
const UL = new ListItem(true);
const OL = new ListItem(false);
const H1 = new Heading(1);
const H2 = new Heading(2);
const H3 = new Heading(3);
const H4 = new Heading(4);
const H5 = new Heading(5);
const H6 = new Heading(6);
const ALIGN_LEFT = new TextAlign('left');
const ALIGN_CENTER = new TextAlign('center');
const ALIGN_RIGHT = new TextAlign('right');
const ALIGN_JUSTIFY = new TextAlign('justify');
const LINK = new Link$1();
const IMAGE = new Image$1();
const TEXT_COLOR = new TextColor$1('text_color', 'color');
const TEXT_BACKGROUND_COLOR = new TextColor$1('text_background_color', 'backgroundColor');
const ToggleCommands = {
bold: STRONG,
italic: EM,
code: CODE,
underline: UNDERLINE,
strike: STRIKE,
blockquote: BLOCKQUOTE,
bullet_list: UL,
ordered_list: OL,
h1: H1,
h2: H2,
h3: H3,
h4: H4,
h5: H5,
h6: H6,
align_left: ALIGN_LEFT,
align_center: ALIGN_CENTER,
align_right: ALIGN_RIGHT,
align_justify: ALIGN_JUSTIFY
};
const Link = LINK;
const Image = IMAGE;
const TextColor = TEXT_COLOR;
const TextBackgroundColor = TEXT_BACKGROUND_COLOR;
const defaults = {
// menu
bold: 'Bold',
italic: 'Italic',
code: 'Code',
underline: 'Underline',
strike: 'Strike',
blockquote: 'Blockquote',
bullet_list: 'Bullet List',
ordered_list: 'Ordered List',
heading: 'Heading',
h1: 'Header 1',
h2: 'Header 2',
h3: 'Header 3',
h4: 'Header 4',
h5: 'Header 5',
h6: 'Header 6',
align_left: 'Left Align',
align_center: 'Center Align',
align_right: 'Right Align',
align_justify: 'Justify',
text_color: 'Text Color',
background_color: 'Background Color',
insertLink: 'Insert Link',
removeLink: 'Remove Link',
insertImage: 'Insert Image',
// pupups, forms, others...
url: 'URL',
text: 'Text',
openInNewTab: 'Open in new tab',
insert: 'Insert',
altText: 'Alt Text',
title: 'Title',
remove: 'Remove',
};
class Locals {
constructor(newLocals = {}) {
this.locals = defaults;
this.get = (key) => {
return this.locals[key] ?? '';
};
this.locals = Object.assign({}, defaults, newLocals);
}
}
class NgxEditorServiceConfig {
constructor() {
this.locals = {};
}
}
NgxEditorServiceConfig.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.2.0", ngImport: i0, type: NgxEditorServiceConfig, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
NgxEditorServiceConfig.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "13.2.0", ngImport: i0, type: NgxEditorServiceConfig, providedIn: 'root' });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.2.0", ngImport: i0, type: NgxEditorServiceConfig, decorators: [{
type: Injectable,
args: [{
providedIn: 'root'
}]
}] });
class NgxEditorService {
constructor(config) {
this.config = config;
}
get locals() {
return new Locals(this.config.locals);
}
}
NgxEditorService.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.2.0", ngImport: i0, type: NgxEditorService, deps: [{ token: NgxEditorServiceConfig, optional: true }], target: i0.ɵɵFactoryTarget.Injectable });
NgxEditorService.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "13.2.0", ngImport: i0, type: NgxEditorService, providedIn: 'root' });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.2.0", ngImport: i0, type: NgxEditorService, decorators: [{
type: Injectable,
args: [{
providedIn: 'root'
}]
}], ctorParameters: function () { return [{ type: NgxEditorServiceConfig, decorators: [{
type: Optional
}] }]; } });
const provideMyServiceOptions = (config) => {
return {
locals: config.locals ?? {}
};
};
class SanitizeHtmlPipe {
constructor(sanitizer) {
this.sanitizer = sanitizer;
}
transform(value) {