import { css, html, LitElement, unsafeCSS } from "lit";
import { property } from "lit/decorators.js";
import Quill from "uxl-quill/dist/quill";
import styles from "./styles.css?inline";
import { template } from "./template";
import { fixSpellCheckerIssue } from "./utils";
fixSpellCheckerIssue(Quill);
export class PrimariaRichTextEditor extends LitElement {
private textChangeTimeout: number | null = null;
render() {
return html` ${template(this)} `;
}
static styles = css`
${unsafeCSS(styles)}
`;
firstUpdated() {
// const rte: PrimariaRichTextEditor = this;
this.quill = new Quill(this.shadowRoot?.querySelector("#rte"), this._getOptions());
this.quill.on("text-change", () => this._debouncedEmitTextChangeEvent());
this.quill.root.addEventListener("paste", () => {
this._debouncedEmitTextChangeEvent();
});
this.quill.on("selection-change", (range, oldRange, source) => {
if (range) this.range = range;
});
}
_debouncedEmitTextChangeEvent() {
if (this.textChangeTimeout) {
clearTimeout(this.textChangeTimeout);
}
this.textChangeTimeout = window.setTimeout(() => {
this._emitTextChangeEvent();
}, 200);
}
_emitTextChangeEvent() {
const plainValue = this.quill.getText();
const values = {
html: this.shadowRoot?.querySelector(".ql-editor")?.innerHTML,
plain: plainValue,
};
const textChanged = new CustomEvent("text-changed", {
composed: true,
detail: values,
});
this.dispatchEvent(textChanged);
this.range = this.getCursorPosition();
}
@property()
quill: Quill | null;
@property()
options: string;
@property({ type: Object })
range: { index: number; length: number };
@property({ type: Array })
availableOptions: string[] = [
"bold",
"italic",
"underline",
"strike",
"blockquote",
"code-block",
"image",
"video",
"link",
"color",
"background",
"ol",
"ul",
"subindex",
"superindex",
// 'outdent',
// 'indent',
"size",
"header",
"font",
"align",
// "clear",
"undo",
"redo",
];
@property({ type: Array })
availableFormats: string[] = [
"background",
"bold",
"color",
"font",
"code",
"italic",
"link",
"size",
"strike",
"script",
"underline",
"blockquote",
"header",
// 'indent',
"list",
"align",
"direction",
"code-block",
"formula",
"image",
"video",
];
@property()
formats: string;
_getOptions() {
let toolbarOptions = [];
const availableOpts = this.availableOptions;
if (this.options === undefined || this.options.length === 0) {
toolbarOptions = [
["bold", "italic", "underline", "strike"],
["blockquote", "code-block"],
[{ header: 1 }, { header: 2 }],
[{ list: "ordered" }, { list: "bullet" }],
[{ script: "sub" }, { script: "super" }],
// [{indent: '-1'}, {indent: '+1'}],
[{ direction: "rtl" }],
[{ size: ["small", false, "large", "huge", "20px"] }],
[{ header: [1, 2, 3, 4, 5, 6, false] }],
[{ color: [] }, { background: [] }],
[{ font: [] }],
[{ align: [] }],
["image"],
["video"],
// ["clean"],
];
} else {
toolbarOptions = this.options.split(",");
toolbarOptions.forEach((option) => {
if (availableOpts.indexOf(option) <= -1) toolbarOptions.splice(toolbarOptions.indexOf(option), 1);
});
toolbarOptions = toolbarOptions.map(function (option) {
if (option == "color") return { color: [] };
else if (option == "ol") return { list: "ordered" };
else if (option == "ul") return { list: "bullet" };
else if (option == "subindex") return { script: "sub" };
else if (option == "superindex") return { script: "super" };
// else if (option == 'outdent') return {indent: '-1'};
// else if (option == 'indent') return {indent: '+1'};
else if (option == "size") return { size: ["small", false, "large", "huge"] };
else if (option == "header") return { header: [1, 2, 3, 4, 5, 6, false] };
else if (option == "background") return { background: [] };
else if (option == "font") return { font: [] };
else if (option == "align") return { align: [] };
/* else if (option == 'undo') return {undo: undo_icon};
else if (option == 'redo') return {redo: redo_icon}; */ else return option;
});
// if (toolbarOptions.indexOf("clean") < 0) {
// toolbarOptions.push("clean");
// }
}
/* const icons = Quill.import('ui/icons');
icons['undo'] = undo_icon;
icons['redo'] = redo_icon; */
let formats = this.availableFormats;
if (this.formats) {
formats = this.formats.split(",");
}
return {
modules: {
toolbar: {
container: toolbarOptions,
handlers: {
redo() {
this.quill.history.redo();
},
undo() {
this.quill.history.undo();
},
},
},
history: {
delay: 1000,
maxStack: 50,
userOnly: false,
},
keyboard: {
bindings: {
indent: {
// highlight tab or tab at beginning of list, indent or blockquote
key: "Tab",
format: ["blockquote", "indent", "list"],
handler(range, context) {
if (
(context.collapsed && context.offset !== 0) ||
context.format?.list === "ordered" // disable ordered list auto-numbering on indentation
)
return true;
this.quill.format("indent", "+1", Quill.sources.USER);
return false;
},
},
"list autofill": {
key: " ",
shiftKey: null,
collapsed: true,
format: {
"code-block": false,
blockquote: false,
table: false,
},
prefix: /^\s*?(\d+\.|-|\*)$/,
handler(range, context) {
if (this.quill.scroll.query("list") == null) return true;
const { length } = context.prefix;
const line = this.quill.getLine(range.index);
const offset = line[1];
if (offset > length) return true;
this.quill.insertText(range.index, " ", Quill.sources.USER);
this.quill.setSelection(range.index - length, Quill.sources.SILENT);
return false;
},
},
},
},
},
theme: "snow",
formats: formats,
};
}
public getCursorPosition() {
return this.quill.getSelection();
}
}