ngx-editor
Version:
Rich Text Editor for angular using ProseMirror
1 lines • 194 kB
Source Map (JSON)
{"version":3,"file":"ngx-editor.mjs","sources":["../../../projects/ngx-editor/src/lib/plugins/editable.ts","../../../projects/ngx-editor/src/lib/plugins/placeholder.ts","../../../projects/ngx-editor/src/lib/plugins/attributes.ts","../../../projects/ngx-editor/src/lib/plugins/focus.ts","../../../projects/ngx-editor/src/lib/plugins/blur.ts","../../../projects/ngx-editor/src/lib/components/image-view/image-view.component.ts","../../../projects/ngx-editor/src/lib/components/image-view/image-view.component.html","../../../projects/ngx-editor/src/lib/plugins/image-resize.ts","../../../projects/ngx-editor/src/lib/plugins/link.ts","../../../projects/ngx-editor/src/lib/parsers.ts","../../../projects/ngx-editor/src/lib/editor.component.ts","../../../projects/ngx-editor/src/lib/editor.component.html","../../../projects/ngx-editor/src/lib/modules/menu/menu.service.ts","../../../projects/ngx-editor/src/lib/icons/bold.ts","../../../projects/ngx-editor/src/lib/icons/italic.ts","../../../projects/ngx-editor/src/lib/icons/code.ts","../../../projects/ngx-editor/src/lib/icons/underline.ts","../../../projects/ngx-editor/src/lib/icons/strike.ts","../../../projects/ngx-editor/src/lib/icons/ordered_list.ts","../../../projects/ngx-editor/src/lib/icons/bullet_list.ts","../../../projects/ngx-editor/src/lib/icons/quote.ts","../../../projects/ngx-editor/src/lib/icons/link.ts","../../../projects/ngx-editor/src/lib/icons/unlink.ts","../../../projects/ngx-editor/src/lib/icons/image.ts","../../../projects/ngx-editor/src/lib/icons/align_left.ts","../../../projects/ngx-editor/src/lib/icons/align_center.ts","../../../projects/ngx-editor/src/lib/icons/align_right.ts","../../../projects/ngx-editor/src/lib/icons/align_justify.ts","../../../projects/ngx-editor/src/lib/icons/text_color.ts","../../../projects/ngx-editor/src/lib/icons/color_fill.ts","../../../projects/ngx-editor/src/lib/icons/horizontal_rule.ts","../../../projects/ngx-editor/src/lib/icons/index.ts","../../../projects/ngx-editor/src/lib/commands/Mark.ts","../../../projects/ngx-editor/src/lib/commands/Blockquote.ts","../../../projects/ngx-editor/src/lib/commands/HorizontalRule.ts","../../../projects/ngx-editor/src/lib/commands/ListItem.ts","../../../projects/ngx-editor/src/lib/commands/Heading.ts","../../../projects/ngx-editor/src/lib/commands/TextAlign.ts","../../../projects/ngx-editor/src/lib/commands/Link.ts","../../../projects/ngx-editor/src/lib/commands/Image.ts","../../../projects/ngx-editor/src/lib/commands/TextColor.ts","../../../projects/ngx-editor/src/lib/commands/index.ts","../../../projects/ngx-editor/src/lib/modules/menu/MenuCommands.ts","../../../projects/ngx-editor/src/lib/Locals.ts","../../../projects/ngx-editor/src/lib/editor-config.service.ts","../../../projects/ngx-editor/src/lib/editor.service.ts","../../../projects/ngx-editor/src/lib/pipes/sanitize/sanitize-html.pipe.ts","../../../projects/ngx-editor/src/lib/modules/menu/toggle-command/toggle-command.component.ts","../../../projects/ngx-editor/src/lib/modules/menu/toggle-command/toggle-command.component.html","../../../projects/ngx-editor/src/lib/modules/menu/insert-command/insert-command.component.ts","../../../projects/ngx-editor/src/lib/modules/menu/insert-command/insert-command.component.html","../../../projects/ngx-editor/src/lib/modules/menu/link/link.component.ts","../../../projects/ngx-editor/src/lib/modules/menu/link/link.component.html","../../../projects/ngx-editor/src/lib/modules/menu/image/image.component.ts","../../../projects/ngx-editor/src/lib/modules/menu/image/image.component.html","../../../projects/ngx-editor/src/lib/modules/menu/dropdown/dropdown.component.ts","../../../projects/ngx-editor/src/lib/modules/menu/dropdown/dropdown.component.html","../../../projects/ngx-editor/src/lib/modules/menu/color-picker/color-picker.component.ts","../../../projects/ngx-editor/src/lib/modules/menu/color-picker/color-picker.component.html","../../../projects/ngx-editor/src/lib/modules/menu/menu.component.ts","../../../projects/ngx-editor/src/lib/modules/menu/menu.component.html","../../../projects/ngx-editor/src/lib/modules/menu/bubble/bubble.component.ts","../../../projects/ngx-editor/src/lib/modules/menu/bubble/bubble.component.html","../../../projects/ngx-editor/src/lib/modules/menu/floating-menu/floating-menu.component.ts","../../../projects/ngx-editor/src/lib/modules/menu/floating-menu/floating-menu.component.html","../../../projects/ngx-editor/src/lib/modules/menu/menu.module.ts","../../../projects/ngx-editor/src/lib/editor.module.ts","../../../projects/ngx-editor/src/lib/validators.ts","../../../projects/ngx-editor/src/lib/EditorCommands.ts","../../../projects/ngx-editor/src/lib/defaultPlugins.ts","../../../projects/ngx-editor/src/lib/Editor.ts","../../../projects/ngx-editor/src/public_api.ts","../../../projects/ngx-editor/src/ngx-editor.ts"],"sourcesContent":["import { EditorState, Plugin, PluginKey, Transaction } from 'prosemirror-state';\n\nconst editablePlugin = (editable = true): Plugin => {\n return new Plugin({\n key: new PluginKey('editable'),\n state: {\n init(): boolean {\n return editable;\n },\n apply(tr: Transaction, previousVal: boolean): string {\n return tr.getMeta('UPDATE_EDITABLE') ?? previousVal;\n },\n },\n props: {\n editable(state: EditorState): boolean {\n return this.getState(state);\n },\n attributes(state: EditorState): Record<string, string> | null {\n const isEnabled = this.getState(state);\n\n if (isEnabled) {\n return null;\n }\n\n return {\n class: 'NgxEditor__Content--Disabled',\n };\n },\n },\n });\n};\n\nexport default editablePlugin;\n","import { Plugin, EditorState, PluginKey, Transaction } from 'prosemirror-state';\nimport { DecorationSet, Decoration } from 'prosemirror-view';\nimport { Node as ProseMirrorNode } from 'prosemirror-model';\n\nconst PLACEHOLDER_CLASSNAME = 'NgxEditor__Placeholder';\n\nconst placeholderPlugin = (text?: string): Plugin => {\n return new Plugin({\n key: new PluginKey('placeholder'),\n state: {\n init(): string {\n return text ?? '';\n },\n apply(tr: Transaction, previousVal: string): string {\n const placeholder = tr.getMeta('UPDATE_PLACEHOLDER') ?? previousVal;\n return placeholder;\n },\n },\n props: {\n decorations(state: EditorState): DecorationSet {\n const { doc } = state;\n const { textContent, childCount } = doc;\n\n const placeholder = this.getState(state);\n\n if (!placeholder || childCount > 1) {\n return DecorationSet.empty;\n }\n\n const decorations: Decoration[] = [];\n\n const decorate = (node: ProseMirrorNode, pos: number) => {\n if (node.type.isBlock && node.childCount === 0 && textContent.length === 0) {\n const from = pos;\n const to = pos + node.nodeSize;\n\n const placeholderNode = Decoration.node(from, to, {\n 'class': PLACEHOLDER_CLASSNAME,\n 'data-placeholder': placeholder,\n 'data-align': node.attrs['align'] ?? null,\n });\n\n decorations.push(placeholderNode);\n }\n\n return false;\n };\n\n doc.descendants(decorate);\n return DecorationSet.create(doc, decorations);\n },\n },\n });\n};\n\nexport default placeholderPlugin;\n","import { Plugin, PluginKey } from 'prosemirror-state';\n\nconst attributesPlugin = (attributes = {}): Plugin => {\n return new Plugin({\n key: new PluginKey('attributes'),\n props: {\n attributes,\n },\n });\n};\n\nexport default attributesPlugin;\n","import { Plugin, PluginKey } from 'prosemirror-state';\n\nconst focusPlugin = (cb: () => void): Plugin => {\n return new Plugin({\n key: new PluginKey('focus'),\n props: {\n handleDOMEvents: {\n focus: () => {\n cb();\n return false;\n },\n },\n },\n });\n};\n\nexport default focusPlugin;\n","import { Plugin, PluginKey } from 'prosemirror-state';\n\nconst blurPlugin = (cb: () => void):Plugin => {\n return new Plugin({\n key: new PluginKey('blur'),\n props: {\n handleDOMEvents: {\n blur: () => {\n cb();\n return false;\n },\n },\n },\n });\n};\n\nexport default blurPlugin;\n","import {\n Component, ElementRef, EventEmitter,\n Input, Output, ViewChild,\n} from '@angular/core';\nimport { EditorView } from 'prosemirror-view';\n\n@Component({\n selector: 'ngx-image-view',\n templateUrl: './image-view.component.html',\n styleUrls: ['./image-view.component.scss'],\n})\n\nexport class ImageViewComponent {\n @Input() src: string;\n @Input() alt = '';\n @Input() title = '';\n @Input() outerWidth = '';\n @Input() selected = false;\n @Input() view: EditorView;\n\n @Output() imageResize = new EventEmitter();\n\n @ViewChild('imgEl', { static: true }) imgEl: ElementRef;\n\n startResizing(e: MouseEvent, direction: string): void {\n e.preventDefault();\n this.resizeImage(e, direction);\n }\n\n resizeImage(evt: MouseEvent, direction: string): void {\n const startX = evt.pageX;\n const startWidth = this.imgEl.nativeElement.clientWidth;\n\n const isLeftResize = direction === 'left';\n\n const { width } = window.getComputedStyle(this.view.dom);\n const editorWidth = parseInt(width, 10);\n\n const onMouseMove = (e: MouseEvent) => {\n const currentX = e.pageX;\n const diffInPx = currentX - startX;\n const computedWidth = isLeftResize ? startWidth - diffInPx : startWidth + diffInPx;\n\n // prevent image overflow the editor\n // prevent resizng below 20px\n if (computedWidth > editorWidth || computedWidth < 20) {\n return;\n }\n\n this.outerWidth = `${computedWidth}px`;\n };\n\n const onMouseUp = (e: MouseEvent) => {\n e.preventDefault();\n\n document.removeEventListener('mousemove', onMouseMove);\n document.removeEventListener('mouseup', onMouseUp);\n\n this.imageResize.emit();\n };\n\n document.addEventListener('mousemove', onMouseMove);\n document.addEventListener('mouseup', onMouseUp);\n }\n}\n","<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","import { ApplicationRef, ComponentFactoryResolver, ComponentRef, Injector } from '@angular/core';\nimport { Node as ProseMirrorNode } from 'prosemirror-model';\nimport { NodeSelection, Plugin, PluginKey } from 'prosemirror-state';\nimport { EditorView, NodeView } from 'prosemirror-view';\nimport { Subscription } from 'rxjs';\n\nimport { ImageViewComponent } from '../components/image-view/image-view.component';\n\nclass ImageRezieView implements NodeView {\n dom: HTMLElement;\n view: EditorView;\n getPos: () => number;\n\n applicationRef: ApplicationRef;\n imageComponentRef: ComponentRef<ImageViewComponent>;\n resizeSubscription: Subscription;\n\n node: ProseMirrorNode;\n updating = false;\n\n constructor(node: ProseMirrorNode, view: EditorView, getPos: () => number, injector: Injector) {\n const componentFactoryResolver = injector.get(ComponentFactoryResolver);\n this.applicationRef = injector.get(ApplicationRef);\n\n // Create the component and wire it up with the element\n const factory = componentFactoryResolver.resolveComponentFactory(ImageViewComponent);\n\n this.imageComponentRef = factory.create(injector, []);\n // Attach to the view so that the change detector knows to run\n this.applicationRef.attachView(this.imageComponentRef.hostView);\n\n // Possible alternate for deprecated ComponentFactoryResolver\n // const viewContainerRef = injector.get(ViewContainerRef);\n // this.imageComponentRef = viewContainerRef.createComponent(ImageViewComponent, { injector });\n\n this.setNodeAttributes(node.attrs);\n this.imageComponentRef.instance.view = view;\n\n this.dom = this.imageComponentRef.location.nativeElement;\n this.view = view;\n this.node = node;\n this.getPos = getPos;\n\n this.resizeSubscription = this.imageComponentRef.instance.imageResize.subscribe(() => {\n this.handleResize();\n });\n }\n\n private computeChanges(prevAttrs: Record<string, any>, newAttrs: Record<string, any>): boolean {\n return JSON.stringify(prevAttrs) === JSON.stringify(newAttrs);\n }\n\n private setNodeAttributes(attrs: Record<string, any>): void {\n this.imageComponentRef.instance.src = attrs['src'];\n this.imageComponentRef.instance.alt = attrs['alt'];\n this.imageComponentRef.instance.title = attrs['title'];\n this.imageComponentRef.instance.outerWidth = attrs['width'];\n }\n\n handleResize = (): void => {\n if (this.updating) {\n return;\n }\n\n const { state, dispatch } = this.view;\n const { tr } = state;\n\n const transaction = tr.setNodeMarkup(this.getPos(), undefined, {\n ...this.node.attrs,\n width: this.imageComponentRef.instance.outerWidth,\n });\n\n const resolvedPos = transaction.doc.resolve(this.getPos());\n const newSelection = new NodeSelection(resolvedPos);\n\n transaction.setSelection(newSelection);\n dispatch(transaction);\n };\n\n update(node: ProseMirrorNode): boolean {\n if (node.type !== this.node.type) {\n return false;\n }\n\n this.node = node;\n\n const changed = this.computeChanges(this.node.attrs, node.attrs);\n if (changed) {\n this.updating = true;\n this.setNodeAttributes(node.attrs);\n this.updating = false;\n }\n return true;\n }\n\n ignoreMutation(): boolean {\n return true;\n }\n\n selectNode(): void {\n this.imageComponentRef.instance.selected = true;\n }\n\n deselectNode(): void {\n this.imageComponentRef.instance.selected = false;\n }\n\n destroy(): void {\n this.resizeSubscription.unsubscribe();\n this.applicationRef.detachView(this.imageComponentRef.hostView);\n }\n}\n\nconst imageResizePlugin = (injector: Injector): Plugin => {\n return new Plugin({\n key: new PluginKey('image-resize'),\n props: {\n nodeViews: {\n image: (node: ProseMirrorNode, view: EditorView, getPos: () => number) => {\n return new ImageRezieView(node, view, getPos, injector);\n },\n },\n },\n });\n};\n\nexport default imageResizePlugin;\n","import { Fragment, Slice, Node as ProseMirrorNode } from 'prosemirror-model';\nimport { Plugin, PluginKey } from 'prosemirror-state';\n\nconst HTTP_LINK_REGEX = /(?:https?:\\/\\/)?[\\w-]+(?:\\.[\\w-]+)+\\.?(?:\\d+)?(?:\\/\\S*)?$/;\n\nconst linkify = (fragment: Fragment): Fragment => {\n const linkified: ProseMirrorNode[] = [];\n\n fragment.forEach((child: ProseMirrorNode) => {\n if (child.isText) {\n const text = child.text as string;\n let pos = 0;\n\n const match: RegExpMatchArray | null = HTTP_LINK_REGEX.exec(text);\n\n if (match) {\n const start = match.index;\n const end = start + match[0].length;\n const { link } = child.type.schema.marks;\n\n if (start > 0) {\n linkified.push(child.cut(pos, start));\n }\n\n const urlText = text.slice(start, end);\n linkified.push(\n child.cut(start, end).mark(link.create({ href: urlText }).addToSet(child.marks)),\n );\n pos = end;\n }\n\n if (pos < text.length) {\n linkified.push(child.cut(pos));\n }\n } else {\n linkified.push(child.copy(linkify(child.content)));\n }\n });\n\n return Fragment.fromArray(linkified);\n};\n\nconst linkifyPlugin = ():Plugin => {\n return new Plugin({\n key: new PluginKey('linkify'),\n props: {\n transformPasted: (slice: Slice) => {\n return new Slice(linkify(slice.content), slice.openStart, slice.openEnd);\n },\n },\n });\n};\n\nexport default linkifyPlugin;\n","import { DOMSerializer, Schema, DOMParser, Node as ProseMirrorNode } from 'prosemirror-model';\n\nimport defaultSchema from './schema';\n\nexport const emptyDoc = {\n type: 'doc',\n content: [\n {\n type: 'paragraph',\n },\n ],\n};\n\n// https://developer.mozilla.org/en-US/docs/Web/API/DocumentFragment\nexport const toHTML = (json: Record<string, any>, inputSchema?: Schema): string => {\n const schema = inputSchema ?? defaultSchema;\n\n const contentNode = schema.nodeFromJSON(json);\n const html = DOMSerializer.fromSchema(schema).serializeFragment(contentNode.content);\n\n const div = document.createElement('div');\n div.appendChild(html);\n return div.innerHTML;\n};\n\nexport const toDoc = (html: string, inputSchema?: Schema): Record<string, any> => {\n const schema = inputSchema ?? defaultSchema;\n\n const el = document.createElement('div');\n el.innerHTML = html;\n\n return DOMParser.fromSchema(schema).parse(el).toJSON();\n};\n\nexport const parseContent = (value: string | Record<string, any> | null, schema: Schema): ProseMirrorNode => {\n if (!value) {\n return schema.nodeFromJSON(emptyDoc);\n }\n\n if (typeof value !== 'string') {\n return schema.nodeFromJSON(value);\n }\n\n const docJson = toDoc(value, schema);\n return schema.nodeFromJSON(docJson);\n};\n","import {\n Component, ViewChild, ElementRef, forwardRef,\n OnDestroy, ViewEncapsulation, OnInit, Output,\n EventEmitter, Input, Renderer2, SimpleChanges,\n OnChanges, Injector,\n} from '@angular/core';\nimport { NG_VALUE_ACCESSOR, ControlValueAccessor } from '@angular/forms';\nimport { Subject, takeUntil } from 'rxjs';\n\nimport { NgxEditorError } from 'ngx-editor/utils';\nimport * as plugins from './plugins';\nimport { toHTML } from './parsers';\nimport Editor from './Editor';\n\n@Component({\n selector: 'ngx-editor',\n templateUrl: './editor.component.html',\n styleUrls: ['./editor.component.scss'],\n providers: [{\n provide: NG_VALUE_ACCESSOR,\n useExisting: forwardRef(() => NgxEditorComponent),\n multi: true,\n }],\n encapsulation: ViewEncapsulation.None,\n})\nexport class NgxEditorComponent implements ControlValueAccessor, OnInit, OnChanges, OnDestroy {\n constructor(\n private renderer: Renderer2,\n private injector: Injector,\n private elementRef: ElementRef<HTMLElement>,\n ) { }\n\n @ViewChild('ngxEditor', { static: true }) private ngxEditor: ElementRef;\n\n @Input() editor: Editor;\n @Input() outputFormat: 'doc' | 'html';\n @Input() placeholder = 'Type Here...';\n\n @Output() focusOut = new EventEmitter<void>();\n @Output() focusIn = new EventEmitter<void>();\n\n private unsubscribe: Subject<void> = new Subject();\n private onChange: (value: Record<string, any> | string) => void = () => { /** */ };\n private onTouched: () => void = () => { /** */ };\n\n writeValue(value: Record<string, any> | string | null): void {\n if (!this.outputFormat && typeof value === 'string') {\n this.outputFormat = 'html';\n }\n\n this.editor.setContent(value ?? '');\n }\n\n registerOnChange(fn: () => void): void {\n this.onChange = fn;\n }\n\n registerOnTouched(fn: () => void): void {\n this.onTouched = fn;\n }\n\n setDisabledState(isDisabled: boolean): void {\n this.setMeta('UPDATE_EDITABLE', !isDisabled);\n this.renderer.setProperty(this.elementRef.nativeElement, 'disabled', isDisabled);\n }\n\n private handleChange(jsonDoc: Record<string, any>): void {\n if (this.outputFormat === 'html') {\n const html = toHTML(jsonDoc, this.editor.schema);\n this.onChange(html);\n return;\n }\n\n this.onChange(jsonDoc);\n }\n\n private setMeta(key: string, value: any): void {\n const { dispatch, state: { tr } } = this.editor.view;\n dispatch(tr.setMeta(key, value));\n }\n\n private setPlaceholder(placeholder: string): void {\n this.setMeta('UPDATE_PLACEHOLDER', placeholder);\n }\n\n private registerPlugins(): void {\n this.editor.registerPlugin(plugins.editable());\n this.editor.registerPlugin(plugins.placeholder(this.placeholder));\n\n this.editor.registerPlugin(plugins.attributes({\n class: 'NgxEditor__Content',\n }));\n\n this.editor.registerPlugin(plugins.focus(() => {\n this.focusIn.emit();\n }));\n\n this.editor.registerPlugin(plugins.blur(() => {\n this.focusOut.emit();\n this.onTouched();\n }));\n\n if (this.editor.features.resizeImage) {\n this.editor.registerPlugin(plugins.imageResize(this.injector));\n }\n\n if (this.editor.features.linkOnPaste) {\n this.editor.registerPlugin(plugins.linkify());\n }\n }\n\n ngOnInit(): void {\n if (!this.editor) {\n throw new NgxEditorError('Required editor instance for initializing editor component');\n }\n\n this.registerPlugins();\n\n this.renderer.appendChild(this.ngxEditor.nativeElement, this.editor.view.dom);\n\n this.editor.valueChanges\n .pipe(takeUntil(this.unsubscribe))\n .subscribe((jsonDoc) => {\n this.handleChange(jsonDoc);\n });\n }\n\n ngOnChanges(changes: SimpleChanges): void {\n if (changes['placeholder'] && !changes['placeholder'].isFirstChange()) {\n this.setPlaceholder(changes['placeholder'].currentValue);\n }\n }\n\n ngOnDestroy(): void {\n this.unsubscribe.next();\n this.unsubscribe.complete();\n }\n}\n","<div class=\"NgxEditor\" #ngxEditor>\n <ng-content></ng-content>\n</div>\n","import { Injectable, TemplateRef } from '@angular/core';\nimport { Subject } from 'rxjs';\n\nimport Editor from '../../Editor';\n\n@Injectable({\n providedIn: 'root',\n})\nexport class MenuService {\n editor: Editor;\n customMenuRefChange: Subject<TemplateRef<any>> = new Subject<TemplateRef<any>>();\n\n setCustomMenuRef(c: TemplateRef<any>): void {\n this.customMenuRefChange.next(c);\n }\n}\n","export default `\n <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\" />\n`;\n","export default `\n <path d=\"M10 4v3h2.21l-3.42 8H6v3h8v-3h-2.21l3.42-8H18V4z\" />\n`;\n","export default `\n<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\"/>\n`;\n","export default `\n<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\"/>\n`;\n","export default `\n<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\"/>\n`;\n","export default `\n<path d=\"M2 17h2v.5H3v1h1v.5H2v1h3v-4H2v1zm1-9h1V4H2v1h1v3zm-1 3h1.8L2 13.1v.9h3v-1H3.2L5 10.9V10H2v1zm5-6v2h14V5H7zm0 14h14v-2H7v2zm0-6h14v-2H7v2z\"/>\n`;\n","export default `\n<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\"/>\n`;\n","export default `\n<path d=\"M0 0h24v24H0z\" fill=\"none\"/><path d=\"M6 17h3l2-4V7H5v6h3zm8 0h3l2-4V7h-6v6h3z\"/>\n`;\n","export default `\n<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\"/>\n`;\n","export default `\n<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\"/>\n`;\n","export default `\n<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\"/>\n`;\n","export default `\n<path d=\"M15 15H3v2h12v-2zm0-8H3v2h12V7zM3 13h18v-2H3v2zm0 8h18v-2H3v2zM3 3v2h18V3H3z\"/>\n`;\n","export default `\n<path d=\"M7 15v2h10v-2H7zm-4 6h18v-2H3v2zm0-8h18v-2H3v2zm4-6v2h10V7H7zM3 3v2h18V3H3z\"/>\n`;\n","export default `\n<path d=\"M3 21h18v-2H3v2zm6-4h12v-2H9v2zm-6-4h18v-2H3v2zm6-4h12V7H9v2zM3 3v2h18V3H3z\"/>\n`;\n","export default `\n<path d=\"M3 21h18v-2H3v2zm0-4h18v-2H3v2zm0-4h18v-2H3v2zm0-4h18V7H3v2zm0-6v2h18V3H3z\"/>\n`;\n","export default `\n<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\"/>\n`;\n","export default `\n<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\"/>\n`;\n","export default `\n <g>\n <rect fill=\"none\" fill-rule=\"evenodd\" height=\"24\" width=\"24\"/>\n <rect fill-rule=\"evenodd\" height=\"2\" width=\"16\" x=\"4\" y=\"11\"/>\n </g>\n`;\n","// Icons source: https://material.io/\n\nimport bold from './bold';\nimport italic from './italic';\nimport code from './code';\nimport underline from './underline';\nimport strike from './strike';\nimport orderedList from './ordered_list';\nimport bulletList from './bullet_list';\nimport quote from './quote';\nimport link from './link';\nimport unlink from './unlink';\nimport image from './image';\nimport alignLeft from './align_left';\nimport alignCenter from './align_center';\nimport alignRight from './align_right';\nimport alignJustify from './align_justify';\nimport textColor from './text_color';\nimport colorFill from './color_fill';\nimport horizontalRule from './horizontal_rule';\n\nconst DEFAULT_ICON_HEIGHT = 20;\nconst DEFAULT_ICON_WIDTH = 20;\nconst DEFAULT_ICON_FILL = 'currentColor';\n\nconst icons: Record<string, any> = {\n bold,\n italic,\n code,\n underline,\n strike,\n ordered_list: orderedList,\n bullet_list: bulletList,\n blockquote: quote,\n link,\n unlink,\n image,\n align_left: alignLeft,\n align_center: alignCenter,\n align_right: alignRight,\n align_justify: alignJustify,\n text_color: textColor,\n color_fill: colorFill,\n horizontal_rule: horizontalRule,\n};\n\nclass Icon {\n static get(name: keyof typeof icons, fill = DEFAULT_ICON_FILL): string {\n const path = icons[name] || '<path></path>';\n return `\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=${fill}\n height=${DEFAULT_ICON_HEIGHT}\n width=${DEFAULT_ICON_WIDTH}\n >\n ${path}\n </svg>\n `;\n }\n}\n\nexport default Icon;\n","import type { MarkType } from 'prosemirror-model';\nimport type { EditorState, Transaction } from 'prosemirror-state';\nimport { type Command, toggleMark } from 'prosemirror-commands';\n\nimport { applyMark } from 'ngx-editor/commands';\nimport { isMarkActive } from 'ngx-editor/helpers';\n\nclass Mark {\n name: string;\n\n constructor(name: string) {\n this.name = name;\n }\n\n apply(): Command {\n return (state: EditorState, dispatch?: (tr: Transaction) => void): boolean => {\n const { schema } = state;\n\n const type: MarkType = schema.marks[this.name];\n if (!type) {\n return false;\n }\n\n return applyMark(type)(state, dispatch);\n };\n }\n\n toggle(): Command {\n return (state: EditorState, dispatch?: (tr: Transaction) => void): boolean => {\n const { schema } = state;\n\n const type: MarkType = schema.marks[this.name];\n if (!type) {\n return false;\n }\n\n return toggleMark(type)(state, dispatch);\n };\n }\n\n isActive(state: EditorState): boolean {\n const { schema } = state;\n\n const type: MarkType = schema.marks[this.name];\n\n if (!type) {\n return false;\n }\n\n return isMarkActive(state, type);\n }\n\n canExecute(state: EditorState): boolean {\n return this.toggle()(state);\n }\n}\n\nexport default Mark;\n","import type { NodeType } from 'prosemirror-model';\nimport type { EditorState, Transaction } from 'prosemirror-state';\nimport { type Command, lift, wrapIn } from 'prosemirror-commands';\n\nimport { isNodeActive } from 'ngx-editor/helpers';\n\nclass Blockqote {\n toggle(): Command {\n return (state: EditorState, dispatch?: (tr: Transaction) => void): boolean => {\n const { schema } = state;\n\n const type: NodeType = schema.nodes.blockquote;\n if (!type) {\n return false;\n }\n\n if (this.isActive(state)) {\n return lift(state, dispatch);\n }\n\n return wrapIn(type)(state, dispatch);\n };\n }\n\n isActive(state: EditorState): boolean {\n const { schema } = state;\n\n const type: NodeType = schema.nodes.blockquote;\n if (!type) {\n return false;\n }\n\n return isNodeActive(state, type);\n }\n\n canExecute(state: EditorState): boolean {\n return this.toggle()(state);\n }\n}\n\nexport default Blockqote;\n","import type { NodeType } from 'prosemirror-model';\nimport type { EditorState, Transaction } from 'prosemirror-state';\nimport type { Command } from 'prosemirror-commands';\n\nimport { canInsert } from 'ngx-editor/helpers';\n\nclass HorizontalRule {\n insert(): Command {\n return (state: EditorState, dispatch?: (tr: Transaction) => void): boolean => {\n const { schema, tr } = state;\n\n const type: NodeType = schema.nodes.horizontal_rule;\n\n if (!type) {\n return false;\n }\n\n dispatch(tr.replaceSelectionWith(type.create()).scrollIntoView());\n return true;\n };\n }\n\n canExecute(state: EditorState): boolean {\n return canInsert(state, state.schema.nodes.horizontal_rule);\n }\n}\n\nexport default HorizontalRule;\n","import type { NodeType, Schema } from 'prosemirror-model';\nimport type { EditorState, Transaction } from 'prosemirror-state';\nimport { liftListItem, wrapInList } from 'prosemirror-schema-list';\nimport type { Command } from 'prosemirror-commands';\n\nimport { isNodeActive } from 'ngx-editor/helpers';\n\nclass ListItem {\n isBulletList = false;\n\n constructor(isBulletList = false) {\n this.isBulletList = isBulletList;\n }\n\n getType(schema: Schema): NodeType {\n return this.isBulletList ? schema.nodes['bullet_list'] : schema.nodes['ordered_list'];\n }\n\n toggle(): Command {\n return (state: EditorState, dispatch?: (tr: Transaction) => void): boolean => {\n const { schema } = state;\n\n const type = this.getType(schema);\n if (!type) {\n return false;\n }\n\n if (this.isActive(state)) {\n return liftListItem(schema.nodes.list_item)(state, dispatch);\n }\n\n return wrapInList(type)(state, dispatch);\n };\n }\n\n isActive(state: EditorState): boolean {\n const { schema } = state;\n\n const type = this.getType(schema);\n if (!type) {\n return false;\n }\n\n return isNodeActive(state, type);\n }\n\n canExecute(state: EditorState): boolean {\n return this.toggle()(state);\n }\n}\n\nexport default ListItem;\n","import type { NodeType, Node as ProseMirrorNode } from 'prosemirror-model';\nimport type { EditorState, Transaction } from 'prosemirror-state';\nimport { type Command, setBlockType } from 'prosemirror-commands';\n\nimport { getSelectionNodes } from 'ngx-editor/helpers';\n\nexport type HeadingLevels = 1 | 2 | 3 | 4 | 5 | 6;\n\nclass Heading {\n level: number;\n\n constructor(level: HeadingLevels) {\n this.level = level;\n }\n\n apply(): Command {\n return (state: EditorState, dispatch?: (tr: Transaction) => void): boolean => {\n const { schema } = state;\n\n const type: NodeType = schema.nodes.heading;\n if (!type) {\n return false;\n }\n\n return setBlockType(type)(state, dispatch);\n };\n }\n\n toggle(): Command {\n return (state: EditorState, dispatch?: (tr: Transaction) => void): boolean => {\n const { schema, selection, doc } = state;\n\n const type: NodeType = schema.nodes.heading;\n if (!type) {\n return false;\n }\n\n const nodePos = selection.$from.before(1);\n const node = doc.nodeAt(nodePos);\n\n const attrs = node?.attrs ?? {};\n\n if (this.isActive(state)) {\n return setBlockType(schema.nodes.paragraph, attrs)(state, dispatch);\n }\n\n return setBlockType(type, { ...attrs, level: this.level })(state, dispatch);\n };\n }\n\n isActive(state: EditorState): boolean {\n const { schema } = state;\n const nodesInSelection = getSelectionNodes(state);\n\n const type: NodeType = schema.nodes.heading;\n if (!type) {\n return false;\n }\n\n const supportedNodes = [\n type,\n schema.nodes.text,\n schema.nodes.blockquote,\n ];\n\n // heading is a text node\n // don't mark as active when it has more nodes\n const nodes = nodesInSelection.filter((node) => {\n return supportedNodes.includes(node.type);\n });\n\n const acitveNode = nodes.find((node: ProseMirrorNode) => {\n return node.attrs['level'] === this.level;\n });\n\n return Boolean(acitveNode);\n }\n\n canExecute(state: EditorState): boolean {\n return this.toggle()(state);\n }\n}\n\nexport default Heading;\n","import type { EditorState, Transaction } from 'prosemirror-state';\nimport type { Node } from 'prosemirror-model';\nimport type { Command } from 'prosemirror-commands';\n\nimport { getSelectionNodes } from 'ngx-editor/helpers';\n\nexport type Align = 'left' | 'center' | 'right' | 'justify';\n\nclass TextAlign {\n align: string;\n\n constructor(align: Align) {\n this.align = align;\n }\n\n toggle(): Command {\n return (state: EditorState, dispatch?: (tr: Transaction) => void): boolean => {\n const { doc, selection, tr, schema } = state;\n const { from, to } = selection;\n\n let applicable = false;\n\n doc.nodesBetween(from, to, (node, pos) => {\n const nodeType = node.type;\n if ([schema.nodes.paragraph, schema.nodes.heading].includes(nodeType)) {\n applicable = true;\n tr.setNodeMarkup(pos, nodeType, { ...node.attrs, align: this.align });\n }\n return true;\n });\n\n if (!applicable) {\n return false;\n }\n\n if (tr.docChanged) {\n dispatch?.(tr);\n }\n\n return true;\n };\n }\n\n isActive(state: EditorState): boolean {\n const nodes = getSelectionNodes(state);\n\n const active = nodes.find((node: Node) => {\n return node.attrs['align'] === this.align;\n });\n\n return Boolean(active);\n }\n\n canExecute(state: EditorState): boolean {\n return this.toggle()(state);\n }\n}\n\nexport default TextAlign;\n","import type { MarkType } from 'prosemirror-model';\nimport type { EditorState } from 'prosemirror-state';\nimport { type Command, toggleMark } from 'prosemirror-commands';\n\nimport { isMarkActive } from 'ngx-editor/helpers';\nimport { removeLink } from 'ngx-editor/commands';\nimport type { Dispatch } from './types';\n\nconst defaultOptions = {\n strict: true,\n};\n\nexport interface LinkAttrs {\n href: string;\n title?: string;\n target?: string;\n}\n\nclass Link {\n update(attrs: LinkAttrs): Command {\n return (state: EditorState, dispatch?: Dispatch): boolean => {\n const { schema, selection } = state;\n\n const type: MarkType = schema.marks.link;\n if (!type) {\n return false;\n }\n\n if (selection.empty) {\n return false;\n }\n\n return toggleMark(type, attrs)(state, dispatch);\n };\n }\n\n insert(text: string, attrs: LinkAttrs): Command {\n return (state: EditorState, dispatch?: Dispatch): boolean => {\n const { schema, tr } = state;\n\n const type: MarkType = schema.marks.link;\n if (!type) {\n return false;\n }\n\n const linkAttrs: LinkAttrs = {\n href: attrs.href,\n title: attrs.title ?? text,\n target: attrs.target ?? '_blank',\n };\n\n const node = schema.text(text, [schema.marks.link.create(linkAttrs)]);\n\n tr.replaceSelectionWith(node, false)\n .scrollIntoView();\n\n if (tr.docChanged) {\n dispatch?.(tr);\n return true;\n }\n\n return false;\n };\n }\n\n isActive(state: EditorState, options = defaultOptions): boolean {\n if (options.strict) {\n return true;\n }\n\n const { schema } = state;\n const type = schema.marks.link;\n\n if (!type) {\n return false;\n }\n\n return isMarkActive(state, type);\n }\n\n remove(state: EditorState, dispatch?: Dispatch): boolean {\n return removeLink()(state, dispatch);\n }\n\n canExecute(state: EditorState): boolean {\n const testAttrs: LinkAttrs = {\n href: '',\n };\n\n return this.insert('Exec', testAttrs)(state) || this.update(testAttrs)(state);\n }\n}\n\nexport default Link;\n","import { type EditorState, NodeSelection } from 'prosemirror-state';\nimport type { Command } from 'prosemirror-commands';\n\nimport { Dispatch } from './types';\n\nexport interface ImageAttrs {\n alt?: string;\n title?: string;\n width?: string;\n}\n\nclass Image {\n insert(src: string, attrs: ImageAttrs): Command {\n return (state: EditorState, dispatch?: Dispatch): boolean => {\n const { schema, tr, selection } = state;\n\n const type = schema.nodes.image;\n if (!type) {\n return false;\n }\n\n const imageAttrs = {\n width: null,\n src,\n ...attrs,\n };\n\n if (!imageAttrs.width && selection instanceof NodeSelection && selection.node.type === type) {\n imageAttrs.width = selection.node.attrs['width'];\n }\n\n tr.replaceSelectionWith(type.createAndFill(imageAttrs));\n\n const resolvedPos = tr.doc.resolve(\n tr.selection.anchor - tr.selection.$anchor.nodeBefore.nodeSize,\n );\n\n tr\n .setSelection(new NodeSelection(resolvedPos))\n .scrollIntoView();\n\n if (tr.docChanged) {\n dispatch?.(tr);\n return true;\n }\n\n return false;\n };\n }\n\n isActive(state: EditorState): boolean {\n const { selection } = state;\n if (selection instanceof NodeSelection) {\n return selection.node.type.name === 'image';\n }\n\n return false;\n }\n}\n\nexport default Image;\n","import type { MarkType } from 'prosemirror-model';\nimport type { EditorState } from 'prosemirror-state';\nimport type { Command } from 'prosemirror-commands';\n\nimport { getSelectionMarks, isMarkActive } from 'ngx-editor/helpers';\nimport { applyMark, removeMark } from 'ngx-editor/commands';\n\nimport type { Dispatch } from './types';\n\ntype Name = 'text_color' | 'text_background_color';\ntype AttrName = 'color' | 'backgroundColor';\n\ninterface ColorAttrs {\n color: string;\n}\n\ninterface BackgroundColorAttrs {\n backgroundColor: string;\n}\n\nclass TextColor {\n name: Name;\n attrName: AttrName;\n\n constructor(name: Name, attrName: AttrName = 'color') {\n this.name = name;\n this.attrName = attrName;\n }\n\n apply(attrs: ColorAttrs | BackgroundColorAttrs): Command {\n return (state: EditorState, dispatch?: Dispatch): boolean => {\n const { schema, selection, doc } = state;\n\n const type: MarkType = schema.marks[this.name];\n if (!type) {\n return false;\n }\n\n const { from, to, empty } = selection;\n\n if (!empty && (from + 1 === to)) {\n const node = doc.nodeAt(from);\n if (node?.isAtom && !node.isText && node.isLeaf) {\n // An atomic node (e.g. Image) is selected.\n return false;\n }\n }\n\n return applyMark(type, attrs)(state, dispatch);\n };\n }\n\n isActive(state: EditorState): boolean {\n const { schema } = state;\n const type: MarkType = schema.marks[this.name];\n\n if (!type) {\n return false;\n }\n\n return isMarkActive(state, type);\n }\n\n getActiveColors(state: EditorState): string[] {\n if (!this.isActive(state)) {\n return [];\n }\n\n const { schema } = state;\n const marks = getSelectionMarks(state);\n\n const colors = marks\n .filter((mark) => mark.type === schema.marks[this.name])\n .map((mark) => {\n return mark.attrs[this.attrName];\n })\n .filter(Boolean);\n\n return colors;\n }\n\n remove(): Command {\n return (state: EditorState, dispatch?: Dispatch): boolean => {\n const { schema } = state;\n\n const type = schema.marks[this.name];\n if (!type) {\n return false;\n }\n\n return removeMark(type)(state, dispatch);\n };\n }\n\n canExecute(state: EditorState): boolean {\n const attrs = this.name === 'text_color' ? { color: '' } : { backgroundColor: '' };\n return this.apply(attrs)(state);\n }\n}\n\nexport default TextColor;\n","import Mark from './Mark';\nimport Blockquote from './Blockquote';\nimport HorizontalRule from './HorizontalRule';\nimport ListItem from './ListItem';\nimport Heading from './Heading';\nimport TextAlign from './TextAlign';\nimport Link from './Link';\nimport Image from './Image';\nimport TextColor from './TextColor';\n\nexport const STRONG = new Mark('strong');\nexport const EM = new Mark('em');\nexport const CODE = new Mark('code');\nexport const UNDERLINE = new Mark('u');\nexport const STRIKE = new Mark('s');\nexport const BLOCKQUOTE = new Blockquote();\nexport const HORIZONTAL_RULE = new HorizontalRule();\nexport const UL = new ListItem(true);\nexport const OL = new ListItem(false);\nexport const H1 = new Heading(1);\nexport const H2 = new Heading(2);\nexport const H3 = new Heading(3);\nexport const H4 = new Heading(4);\nexport const H5 = new Heading(5);\nexport const H6 = new Heading(6);\nexport const ALIGN_LEFT = new TextAlign('left');\nexport const ALIGN_CENTER = new TextAlign('center');\nexport const ALIGN_RIGHT = new TextAlign('right');\nexport const ALIGN_JUSTIFY = new TextAlign('justify');\nexport const LINK = new Link();\nexport const IMAGE = new Image();\nexport const TEXT_COLOR = new TextColor('text_color', 'color');\nexport const TEXT_BACKGROUND_COLOR = new TextColor('text_background_color', 'backgroundColor');\n","import * as Commands from '../../commands';\n\nimport { InsertCommand, ToggleCommand } from '../../commands/types';\n\nexport const ToggleCommands: Record<string, ToggleCommand> = {\n bold: Commands.STRONG,\n italic: Commands.EM,\n code: Commands.CODE,\n underline: Commands.UNDERLINE,\n strike: Commands.STRIKE,\n blockquote: Commands.BLOCKQUOTE,\n bullet_list: Commands.UL,\n ordered_list: Commands.OL,\n h1: Commands.H1,\n h2: Commands.H2,\n h3: Commands.H3,\n h4: Commands.H4,\n h5: Commands.H5,\n h6: Commands.H6,\n align_left: Commands.ALIGN_LEFT,\n align_center: Commands.ALIGN_CENTER,\n align_right: Commands.ALIGN_RIGHT,\n align_justify: Commands.ALIGN_JUSTIFY,\n};\n\nexport const InsertCommands: Record<string, InsertCommand> = {\n horizontal_rule: Commands.HORIZONTAL_RULE,\n};\n\nexport const Link = Commands.LINK;\nexport const Image = Commands.IMAGE;\nexport const TextColor = Commands.TEXT_COLOR;\nexport const TextBackgroundColor = Commands.TEXT_BACKGROUND_COLOR;\n","export const defaults: Record<string, string> = {\n // menu\n bold: 'Bold',\n italic: 'Italic',\n code: 'Code',\n underline: 'Underline',\n strike: 'Strike',\n blockquote: 'Blockquote',\n bullet_list: 'Bullet List',\n ordered_list: 'Ordered List',\n heading: 'Heading',\n h1: 'Header 1',\n h2: 'Header 2',\n h3: 'Header 3',\n h4: 'Header 4',\n h5: 'Header 5',\n h6: 'Header 6',\n align_left: 'Left Align',\n align_center: 'Center Align',\n align_right: 'Right Align',\n align_justify: 'Justify',\n text_color: 'Text Color',\n background_color: 'Background Color',\n insertLink: 'Insert Link',\n removeLink: 'Remove Link',\n insertImage: 'Insert Image',\n\n // pupups, forms, others...\n url: 'URL',\n text: 'Text',\n openInNewTab: 'Open in new tab',\n insert: 'Insert',\n altText: 'Alt Text',\n title: 'Title',\n remove: 'Remove',\n};\n\nexport type LocalsKeys = keyof typeof defaults;\n\nclass Locals {\n locals = defaults;\n\n constructor(newLocals: Partial<Record<LocalsKeys, string>> = {}) {\n this.locals = { ...defaults, ...newLocals };\n }\n\n get = (key: string):string => {\n return this.locals[key] ?? '';\n };\n}\n\nexport default Locals;\n","import { Injectable } from '@angular/core';\n\n@Injectable({\n providedIn: 'root',\n})\nexport class NgxEditorServiceConfig {\n public locals = {};\n}\n","import { Injectable, Optional } from '@angular/core';\n\nimport { NgxEditorConfig } from './types';\nimport Locals from './Locals';\nimport { NgxEditorServiceConfig } from './editor-config.service';\n\n@Injectable({\n providedIn: 'root',\n})\nexport class NgxEditorService {\n config: NgxEditorServiceConfig;\n\n constructor(@Optional() config?: NgxEditorServiceConfig) {\n this.config = config;\n }\n\n get locals(): Locals {\n return new Locals(this.config.locals);\n }\n}\n\nexport const provideMyServiceOptions = (config?: NgxEditorConfig): NgxEditorServiceConfig => {\n return {\n locals: config.locals ?? {},\n };\n};\n","import { Pipe, PipeTransform } from '@angular/core';\nimport { DomSanitizer, SafeHtml } from '@angular/platform-browser';\n\n@Pipe({\n name: 'sanitizeHtml',\n})\n\nexport class SanitizeHtmlPipe implements PipeTransform {\n constructor(private sanitizer: DomSanitizer) { }\n\n transform(value: string): SafeHtml {\n return this.sanitizer.bypassSecurityTrustHtml(value);\n }\n}\n","import { Component, Input, OnDestroy, OnInit } from '@angular/core';\nimport { EditorView } from 'prosemirror-view';\nimport { Subscription } from 'rxjs';\n\nimport Icon from '../../../icons';\nimport { ToggleCommands } from '../MenuCommands';\nimport { NgxEditorService } from '../../../editor.service';\nimport { MenuService } from '../menu.service';\nimport { TBItems, ToolbarItem } from '../../../types';\n\n@Component({\n selector: 'ngx-toggle-command',\n templateUrl: './toggle-command.component.html',\n styleUrls: ['./toggle-command.component.scss'],\n})\n\nexport class ToggleCommandComponent implements OnInit, OnDestroy {\n @Input() toolbarItem: ToolbarItem;\n\n get name(): TBItems {\n return this.toolbarItem as TBItems;\n }\n\n html: string;\n editorView: EditorView;\n isActive = false;\n disabled = false;\n private updateSubscription: Subscription;\n\n constructor(\n private ngxeService: NgxEditorService,\n private menuService: MenuService,\n ) { }\n\n toggle(e: MouseEvent): void {\n e.preventDefault();\n\n if (e.button !== 0) {\n return;\n }\n\n const { state, dispatch } = this.editorView;\n const command = ToggleCommands[this.name];\n command.toggle()(state, dispatch);\n }\n\n update = (view: EditorView): void => {\n const { state } = view;\n const command = ToggleCommands[this.name];\n this.isActive = command.isActive(state);\n this.disabled = !command.canExecute(state);\n };\n\n getTitle(name: string): string {\n return this.ngxeService.locals.get(name);\n }\n\n ngOnInit(): void {\n this.html = Icon.get(this.name);\n\n this.editorView = this.menuService.editor.view;\n\n this.updateSubscription = this.menuService.editor.update.subscribe((view: EditorView) => {\n this.update(view);\n });\n }\n\n ngOnDestroy(): void {\n this.updateSubscription.unsubscribe();\n }\n}\n","<div class=\"NgxEditor__MenuItem--IconContainer\" [class.NgxEditor__MenuItem--Active]=\"isActive\"\n [class.NgxEditor--Disabled]=\"disabled\" [innerHTML]=\"html | sanitizeHtml\" (mousedown)=\"toggle($event)\"\n [title]=\"getTitle(name)\">\n</div>\n","import { Component, Input, OnDestroy, OnInit } from '@angular/core';\nimport { EditorView } from 'prosemirror-view';\nimport { Subscription } from 'rxjs';\n\nimport Icon from '../../../icons';\nimport { InsertCommands } from '../MenuCommands';\nimport { NgxEditorService } from '../../../editor.service';\nimport { MenuService } from '../menu.service';\nimport { TBItems, ToolbarItem } from '../../../types';\n\n@Component({\n selector: 'ngx-insert-command',\n templateUrl: './insert-command.component.html',\n styleUrls: ['./insert-command.component.scss'],\n})\n\nexport class InsertCommandComponent implements OnInit, OnDestroy {\n @Input() toolbarItem: ToolbarItem;\n\n get name(): TBItems {\n return this.toolbarItem as TBItems;\n }\n\n html: string;\n editorView: EditorView;\n disabled = false;\n private updateSubscription: Subscription;\n\n constructor(\n private ngxeService: NgxEditorService,\n private menuService: MenuService,\n ) { }\n\n insert(e: MouseEven