UNPKG

@plait/angular-text

Version:

Text processing implementation, supports commonly used rich text formats, such as font size, color, bold, italics, etc.

356 lines (344 loc) 17 kB
import * as i0 from '@angular/core'; import { ElementRef, ViewChild, Component, ChangeDetectionStrategy, Input, HostBinding } from '@angular/core'; import { isKeyHotkey } from 'is-hotkey'; import { Range, Editor, Transforms, Element, createEditor } from 'slate'; import { BaseElementComponent, hotkeys, BaseTextComponent, withAngular, SlateEditable } from 'slate-angular'; import { withHistory } from 'slate-history'; import { markShortcuts, withMark, CLIPBOARD_FORMAT_KEY, MarkTypes, getTextFromClipboard, isUrl, LinkEditor, withLink } from '@plait/text-plugins'; import * as i1 from '@angular/forms'; import { FormsModule } from '@angular/forms'; import * as i2 from '@angular/common'; import { CommonModule } from '@angular/common'; class PlaitLinkNodeComponent extends BaseElementComponent { constructor() { super(...arguments); // Put this at the start and end of an inline component to work around this Chromium bug: // https://bugs.chromium.org/p/chromium/issues/detail?id=1249405 this.inlineChromiumBugfix = String.fromCodePoint(160); this.getOutletParent = () => { return this.outletParent.nativeElement; }; } ngOnInit() { super.ngOnInit(); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: PlaitLinkNodeComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.14", type: PlaitLinkNodeComponent, isStandalone: true, selector: "a[plaitLink]", host: { attributes: { "target": "_blank" }, properties: { "attr.href": "element.url" }, classAttribute: "plait-link-node" }, viewQueries: [{ propertyName: "outletParent", first: true, predicate: ["outletParent"], descendants: true, read: ElementRef, static: true }], usesInheritance: true, ngImport: i0, template: ` <span contenteditable="false" class="link-break-char">{{ inlineChromiumBugfix }}</span> <span #outletParent></span> <span contenteditable="false" class="link-break-char">{{ inlineChromiumBugfix }}</span> `, isInline: true }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: PlaitLinkNodeComponent, decorators: [{ type: Component, args: [{ selector: 'a[plaitLink]', template: ` <span contenteditable="false" class="link-break-char">{{ inlineChromiumBugfix }}</span> <span #outletParent></span> <span contenteditable="false" class="link-break-char">{{ inlineChromiumBugfix }}</span> `, host: { '[attr.href]': 'element.url', target: '_blank', class: 'plait-link-node' }, standalone: true }] }], propDecorators: { outletParent: [{ type: ViewChild, args: ['outletParent', { read: ElementRef, static: true }] }] } }); const withMarkHotkey = (editor) => { const e = editor; const { onKeydown } = e; e.onKeydown = (event) => { markShortcuts(editor, event); onKeydown(event); }; return withMark(e); }; class ParagraphElementComponent extends BaseElementComponent { ngOnInit() { super.ngOnInit(); this.applyAlign(); } onContextChange() { super.onContextChange(); if (this.initialized) { this.applyAlign(); } } applyAlign() { if (this.element.align) { if (this.nativeElement.style.textAlign !== this.element.align) { this.nativeElement.style.textAlign = this.element.align; } } else if (this.nativeElement.style.textAlign) { this.nativeElement.style.removeProperty('text-align'); } } ngOnDestroy() { super.ngOnDestroy(); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: ParagraphElementComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.14", type: ParagraphElementComponent, isStandalone: true, selector: "div[plaitTextParagraphElement]", host: { classAttribute: "plait-text-paragraph" }, usesInheritance: true, ngImport: i0, template: ``, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: ParagraphElementComponent, decorators: [{ type: Component, args: [{ selector: 'div[plaitTextParagraphElement]', template: ``, changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, host: { class: 'plait-text-paragraph' } }] }] }); const withInlineMove = (editor) => { const { onKeydown } = editor; editor.onKeydown = (event) => { const { selection } = editor; if (!selection || !selection.anchor || !selection.focus) { onKeydown(event); return; } const isMoveBackward = hotkeys.isMoveBackward(event); const isMoveForward = hotkeys.isMoveForward(event); const isCollapsed = selection && Range.isCollapsed(selection); const isInlineNode = isInline(editor); if (isCollapsed && isMoveForward) { let isInlineCodeBefore = false; if (!isInlineNode) { try { const { path } = Editor.after(editor, selection); if (path) { isInlineCodeBefore = isInline(editor, path); } } catch (error) { } } if (isInlineNode || isInlineCodeBefore) { event.preventDefault(); Transforms.move(editor, { unit: 'offset' }); return; } } if (isCollapsed && isMoveBackward) { let isInlineCodeAfter = false; if (!isInlineNode) { try { const { path } = Editor.before(editor, selection); isInlineCodeAfter = isInline(editor, path); } catch (error) { } } if (isInlineNode || isInlineCodeAfter) { event.preventDefault(); Transforms.move(editor, { unit: 'offset', reverse: true }); return; } } onKeydown(event); }; return editor; }; const isInline = (editor, path) => { const [inlineNode] = Editor.nodes(editor, { at: path ? path : editor.selection?.anchor.path, match: n => Element.isElement(n) && Editor.isInline(editor, n) && !Editor.isVoid(editor, n) }); return !!inlineNode; }; const withText = (editor) => { const e = editor; const { insertData } = e; e.insertBreak = () => { editor.insertText('\n'); }; e.insertData = (data) => { let text = data.getData('text/plain'); let plaitData = data.getData(`application/${CLIPBOARD_FORMAT_KEY}`); if (!plaitData && text) { if (text.endsWith('\n')) { text = text.substring(0, text.length - 1); } text = text.trim().replace(/\t+/g, ' '); e.insertText(text); return; } insertData(data); }; return e; }; class PlaitTextNodeComponent extends BaseTextComponent { constructor(renderer2) { super(); this.renderer2 = renderer2; this.excludes = ['color', 'font-size', 'text']; this.attributes = []; } applyTextMark() { this.attributes.forEach(attr => { this.renderer2.removeAttribute(this.elementRef.nativeElement, attr); }); this.attributes = []; for (const key in this.text) { if (Object.prototype.hasOwnProperty.call(this.text, key) && !this.excludes.includes(key)) { const attr = `the-${key}`; this.renderer2.setAttribute(this.elementRef.nativeElement, attr, 'true'); this.attributes.push(attr); } } const fontSize = this.text[MarkTypes.fontSize]; if (fontSize) { this.renderer2.setAttribute(this.elementRef.nativeElement, `plait-${MarkTypes.fontSize}`, fontSize); } else { this.renderer2.removeAttribute(this.elementRef.nativeElement, `plait-${MarkTypes.fontSize}`); } if (this.text[MarkTypes.color]) { this.renderer2.setStyle(this.elementRef.nativeElement, 'color', this.text[MarkTypes.color]); } else { this.renderer2.removeStyle(this.elementRef.nativeElement, 'color'); } } onContextChange() { super.onContextChange(); this.applyTextMark(); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: PlaitTextNodeComponent, deps: [{ token: i0.Renderer2 }], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.14", type: PlaitTextNodeComponent, isStandalone: true, selector: "span[plaitText]", host: { attributes: { "data-slate-node": "text" } }, usesInheritance: true, ngImport: i0, template: ``, isInline: true }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: PlaitTextNodeComponent, decorators: [{ type: Component, args: [{ selector: 'span[plaitText]', template: ``, host: { 'data-slate-node': 'text' }, standalone: true }] }], ctorParameters: () => [{ type: i0.Renderer2 }] }); const withPasteLink = (editor) => { const { insertData } = editor; editor.insertData = data => { const text = getTextFromClipboard(data); if (typeof text === 'string' && text && isUrl(text)) { LinkEditor.wrapLink(editor, text, text); Transforms.move(editor, { distance: 1, unit: 'offset' }); } else { insertData(data); } }; return withLink(editor); }; class PlaitTextComponent { set text(text) { this.children = [text]; this.cdr.markForCheck(); } nativeElement() { return this.elementRef.nativeElement; } constructor(renderer2, cdr, elementRef) { this.renderer2 = renderer2; this.cdr = cdr; this.elementRef = elementRef; this.hostClass = 'plait-text-container'; this.children = []; this.readonly = true; this.editor = withInlineMove(withPasteLink(withMarkHotkey(withText(withHistory(withAngular(createEditor(), CLIPBOARD_FORMAT_KEY)))))); this.renderElement = (element) => { const render = this.editor.renderElement; if (render && render(element)) { return render(element); } if (element.type === 'link') { return PlaitLinkNodeComponent; } return ParagraphElementComponent; }; this.renderText = (text) => { for (const key in MarkTypes) { if (text[MarkTypes[key]]) { return PlaitTextNodeComponent; } } return null; }; this.compositionStart = (event) => { this.onComposition(event); }; this.compositionUpdate = (event) => { this.onComposition(event); }; this.compositionEnd = (event) => { this.onComposition(event); }; this.onKeydown = (event) => { if (isKeyHotkey('mod+a', event)) { Transforms.select(this.editor, [0]); event.preventDefault(); } this.editor.onKeydown(event); }; this.scrollSelectionIntoView = () => { // prevent auto scroll }; } valueChange() { this.onChange({ newText: this.editor.children[0], operations: this.editor.operations }); } ngOnChanges(changes) { } ngOnInit() { if (this.textPlugins) { this.textPlugins.forEach(plugin => { plugin(this.editor); }); } this.editor.board = this.board; } ngAfterViewInit() { this.afterInit && this.afterInit(this.editor); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: PlaitTextComponent, deps: [{ token: i0.Renderer2 }, { token: i0.ChangeDetectorRef }, { token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.14", type: PlaitTextComponent, isStandalone: true, selector: "plait-text", inputs: { textPlugins: "textPlugins", text: "text", readonly: "readonly", onChange: "onChange", afterInit: "afterInit", onComposition: "onComposition", board: "board" }, host: { properties: { "class": "this.hostClass" } }, viewQueries: [{ propertyName: "slateEditable", first: true, predicate: ["slateEditable"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<slate-editable\n #slateEditable\n [editor]=\"editor\"\n [readonly]=\"readonly\"\n [(ngModel)]=\"children\"\n (ngModelChange)=\"valueChange()\"\n [keydown]=\"onKeydown\"\n [renderElement]=\"renderElement\"\n [compositionStart]=\"compositionStart\"\n [compositionUpdate]=\"compositionUpdate\"\n [compositionEnd]=\"compositionEnd\"\n [renderText]=\"renderText\"\n [ngClass]=\"{'editing': !readonly}\"\n [scrollSelectionIntoView]=\"scrollSelectionIntoView\"\n></slate-editable>\n", dependencies: [{ kind: "component", type: SlateEditable, selector: "slate-editable", inputs: ["editor", "renderElement", "renderLeaf", "renderText", "decorate", "placeholderDecorate", "scrollSelectionIntoView", "isStrictDecorate", "trackBy", "readonly", "placeholder", "beforeInput", "blur", "click", "compositionEnd", "compositionUpdate", "compositionStart", "copy", "cut", "dragOver", "dragStart", "dragEnd", "drop", "focus", "keydown", "paste", "spellCheck", "autoCorrect", "autoCapitalize"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }] }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: PlaitTextComponent, decorators: [{ type: Component, args: [{ selector: 'plait-text', imports: [SlateEditable, FormsModule, CommonModule], template: "<slate-editable\n #slateEditable\n [editor]=\"editor\"\n [readonly]=\"readonly\"\n [(ngModel)]=\"children\"\n (ngModelChange)=\"valueChange()\"\n [keydown]=\"onKeydown\"\n [renderElement]=\"renderElement\"\n [compositionStart]=\"compositionStart\"\n [compositionUpdate]=\"compositionUpdate\"\n [compositionEnd]=\"compositionEnd\"\n [renderText]=\"renderText\"\n [ngClass]=\"{'editing': !readonly}\"\n [scrollSelectionIntoView]=\"scrollSelectionIntoView\"\n></slate-editable>\n" }] }], ctorParameters: () => [{ type: i0.Renderer2 }, { type: i0.ChangeDetectorRef }, { type: i0.ElementRef }], propDecorators: { hostClass: [{ type: HostBinding, args: ['class'] }], textPlugins: [{ type: Input }], text: [{ type: Input }], readonly: [{ type: Input }], slateEditable: [{ type: ViewChild, args: ['slateEditable'] }], onChange: [{ type: Input }], afterInit: [{ type: Input }], onComposition: [{ type: Input }], board: [{ type: Input }] } }); /* * Public API Surface of richtext */ /** * Generated bundle index. Do not edit. */ export { PlaitTextComponent }; //# sourceMappingURL=plait-angular-text.mjs.map