class RichTextEditor extends HTMLElement {
constructor() {
super();
this.loadExternalScripts();
this.attachShadow({ mode: 'open' });
this.shadowRoot.innerHTML = `
${RichTextEditor.header()}
${RichTextEditor.template()}
`;
}
connectedCallback() {
this.myQuill = this.mountQuill();
}
loadExternalScripts() {
const links = ["https://cdn.quilljs.com/1.3.6/quill.snow.css", "https://cdn.jsdelivr.net/npm/bulma@0.9.4/css/bulma.min.css", "https://fonts.googleapis.com/css?family=Mirza|Roboto|Slabo+27px|Sofia|Inconsolata|Ubuntu|Akronim|Monoton&display=swap"]
links.forEach(link => {
const css = document.createElement("link");
css.href = link;
css.rel = "stylesheet"
document.head.appendChild(css);
})
}
static template() {
return `
`;
}
static header() {
return `
`;
}
async mountQuill() {
// Register the customs format with Quill
const lib = await import("https://cdn.jsdelivr.net/npm/shadow-selection-polyfill");
const getRange = lib.getRange;
const Font = Quill.import('formats/font');
Font.whitelist = ['mirza', 'roboto', 'sofia', 'slabo', 'inconsolata', 'ubuntu', 'cursive', 'Akronim', 'Monoton'];
const Link = Quill.import('formats/link');
Link.sanitize = function (url) {
// modify url if desired
return url;
}
const SizeStyle = Quill.import('attributors/style/size');
SizeStyle.whitelist = ['10px', '18px', '32px', '50px', '64px'];
Quill.register(SizeStyle, true);
Quill.register(Link, true);
Quill.register(Font, true);
const icons = Quill.import('ui/icons');
const icon = ``
icons['link'] = icon;
const editorContainer = this.shadowRoot.querySelector('#editor-container')
const toolbarContainer = this.shadowRoot.querySelector('#toolbar-container')
const myQuill = new Quill(editorContainer, {
modules: {
toolbar: {
container: toolbarContainer,
},
},
theme: 'snow'
});
const normalizeNative = (nativeRange) => {
if (nativeRange) {
const range = nativeRange;
if (range.baseNode) {
range.startContainer = nativeRange.baseNode;
range.endContainer = nativeRange.focusNode;
range.startOffset = nativeRange.baseOffset;
range.endOffset = nativeRange.focusOffset;
if (range.endOffset < range.startOffset) {
range.startContainer = nativeRange.focusNode;
range.endContainer = nativeRange.baseNode;
range.startOffset = nativeRange.focusOffset;
range.endOffset = nativeRange.baseOffset;
}
}
if (range.startContainer) {
return {
start: { node: range.startContainer, offset: range.startOffset },
end: { node: range.endContainer, offset: range.endOffset },
native: range
};
}
}
return null
};
myQuill.selection.getNativeRange = () => {
const dom = myQuill.root.getRootNode();
const selection = getRange(dom);
const range = normalizeNative(selection);
return range;
};
let fromEditor = false;
editorContainer.addEventListener("pointerup", (e) => {
fromEditor = false;
});
editorContainer.addEventListener("pointerout", (e) => {
fromEditor = false;
});
editorContainer.addEventListener("pointerdown", (e) => {
fromEditor = true;
});
document.addEventListener("selectionchange", () => {
if (fromEditor) {
myQuill.selection.update()
}
});
myQuill.on('text-change', () => {
// keep qull data inside _data to communicate with Gradio
document.querySelector("#rich-text-root")._data = myQuill.getContents()
})
return myQuill
}
}
customElements.define('rich-text-editor', RichTextEditor);