@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
JavaScript
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