diff --git a/samples/quill.html b/samples/quill.html index d2eb1f9..32ac967 100644 --- a/samples/quill.html +++ b/samples/quill.html @@ -42,7 +42,7 @@ const quill = new Quill('#editor', { theme: 'snow', }); - BesService.registerByElement(quill.root, new BesStatusIconEventSink()) + BesQuillService.register(quill.root, quill, new BesStatusIconEventSink()) diff --git a/service.js b/service.js index 552e2d9..c9328ac 100644 --- a/service.js +++ b/service.js @@ -1136,6 +1136,110 @@ class BesCKService extends BesTreeService { } } +/************************************************************************************************** + * + * Quill editor grammar-checking service + * + *************************************************************************************************/ +class BesQuillService extends BesTreeService { + /** + * Constructs class. + * + * @param {Element} hostElement The element in DOM tree we are providing grammar-checking service + * for + * @param {*} quillInstance Quill instance + * @param {*} eventSink Event sink for notifications + */ + constructor(hostElement, quillInstance, eventSink) { + super(hostElement, hostElement, eventSink) + this.quillInstance = quillInstance + this.onChangeData = this.onChangeData.bind(this) + this.quillInstance.on('text-change', delta => { + this.onChangeData(delta) + }) + } + + /** + * Registers grammar checking service. + * + * @param {Element} hostElement DOM element to register grammar checking service for + * @param {Quill} quillInstance Enable Quill tweaks + * @param {*} eventSink Event sink for notifications + * @returns {BesQuillService} Grammar checking service instance + */ + static register(hostElement, quillInstance, eventSink) { + let service = BesService.getServiceByElement(hostElement) + if (service) return service + service = new BesQuillService(hostElement, quillInstance, eventSink) + if (service.eventSink && 'register' in service.eventSink) + service.eventSink.register(service) + // Defer proofing giving user a chance to configure the service. + service.scheduleProofing(10) + return service + } + + /** + * Unregisters grammar checking service. + */ + unregister() { + this.quillInstance.off('change:data', this.onChangeData) + if (this.timer) clearTimeout(this.timer) + super.unregister() + } + + /** + * Called to report the text has changed + */ + onChangeData(delta) { + let index = 0 + let reproofNeeded = false + + delta.ops.forEach(op => { + if (op.retain) { + index += op.retain + if (op.attributes) { + reproofNeeded = true + } + } else if (op.insert) { + reproofNeeded = true + index += op.insert.length + } else if (op.delete) { + reproofNeeded = true + } + }) + + if (reproofNeeded) { + const [leaf, offset] = this.quillInstance.getLeaf(index) + let domElement = leaf.domNode + while (domElement && domElement.tagName !== 'P') { + domElement = domElement.parentNode + } + this.clearProofing(domElement) + + setTimeout(() => { + this.repositionAllMarkup() + this.scheduleProofing(1000) + }, 0) + } + } + + /** + * Replaces grammar checking match with a suggestion provided by grammar checking service. + * + * @param {*} el Block element/paragraph containing grammar checking rule match + * @param {*} match Grammar checking rule match + * @param {String} replacement Text to replace grammar checking match with + */ + replaceText(el, match, replacement) { + if (this.timer) clearTimeout(this.timer) + if (this.abortController) this.abortController.abort() + this.clearProofing(el) + match.range.deleteContents() + match.range.insertNode(document.createTextNode(replacement)) + this.scheduleProofing(20) + } +} + /************************************************************************************************** * * Plain-text grammar-checking service @@ -2260,3 +2364,4 @@ window.BesCKService = BesCKService window.BesDOMPlainTextService = BesDOMPlainTextService window.BesTAService = BesTAService window.BesPopup = BesPopup +window.BesQuillService = BesQuillService