let besEditors = {} // Collection of all editors on page window.onload = () => { // Search and prepare all our editors found in the document. document.querySelectorAll('.online-editor').forEach(ed => { let editor = { ignoreInput: false, timer: null } besEditors[ed.id] = editor besCheckText(ed.id) ed.addEventListener('beforeinput', e => besBeforeInput(ed.id, e), false) ed.addEventListener('click', e => { besHandleClick(e) }) }) } function besHandleClick(e) { switch (e.target) { case e.target.closest('span'): const clicked = e.target.closest('span') const infoText = clicked?.dataset.info const myComponent = document.querySelector('my-component') myComponent.setAttribute('my-attribute', infoText) console.log(clicked) break } } async function besCheckText(editorId) { let text = '' let editor = besEditors[editorId] let ed = document.getElementById(editorId) let paragraphs = [] let divElements = ed.getElementsByTagName('div') if (divElements.length === 0) { // Editor is empty or has plain text only text = ed.textContent } else { // Editor contains
paragraphs for (const para of divElements) { if (para.getAttribute('data-info') === 'checked') continue let p = { start: text.length } text += para.textContent.replace(/\r?\n/g, ' ') text += '\n\n' p.end = text.length paragraphs.push(p) } } let result = await ajaxCheck(text) result.matches.sort((a, b) => a.offset < b.offset ? -1 : a.offset > b.offset ? +1 : 0) if (divElements.length === 0) { // Editor is empty or has plain text only let textOut = '' let offset = 0 for (var mistakeIdx = 0; mistakeIdx < result.matches.length; ++mistakeIdx) { let mistakeStart = result.matches[mistakeIdx].offset let mistakeEnd = mistakeStart + result.matches[mistakeIdx].length textOut += text.substring(offset, mistakeStart) textOut += '' + text.substring(mistakeStart, mistakeEnd) + '' offset = mistakeEnd } textOut += text.substring(offset) editor.ignoreInput = true ed.innerHTML = textOut editor.ignoreInput = false } else { // Editor contains
paragraphs let idx = 0 let mistakeIdx = 0 for (const para of divElements) { if (para.getAttribute('data-info') === 'checked') continue let p = paragraphs[idx++] let textOut = '' let offset = p.start for (; mistakeIdx < result.matches.length && result.matches[mistakeIdx].offset < p.end; ++mistakeIdx) { let mistakeStart = result.matches[mistakeIdx].offset let mistakeEnd = mistakeStart + result.matches[mistakeIdx].length textOut += text.substring(offset, mistakeStart) textOut += '' + text.substring(mistakeStart, mistakeEnd) + '' offset = mistakeEnd } textOut += text.substring(offset, p.end - 2 /* Compensate for '\n\n' we appended to each paragraph. */) editor.ignoreInput = true para.innerHTML = textOut editor.ignoreInput = false para.setAttribute('data-info', 'checked') } } } function besBeforeInput(editorId, event) { let editor = besEditors[editorId] if (editor.ignoreInput) return if (editor.timer) clearTimeout(editor.timer) editor.timer = setTimeout(function(){ besCheckText(editorId) }, 1000) let ed = document.getElementById(editorId) event.getTargetRanges().forEach(range => { if (range.startContainer === ed) return for (var el = range.startContainer; el ; el = el.nextSibling) { for (var el2 = el; el2 && el2 !== ed; el2 = el2.parentElement) { if (!(el2 instanceof HTMLElement) || el2.tagName !== 'DIV') continue el2.removeAttribute('data-info') } if (el === range.endContainer) break } }) } async function ajaxCheck(paragraph) { const url = 'http://localhost:225/api/v2/check' const data = { format: 'plain', text: paragraph, language: 'sl', level: 'picky' } const formData = new URLSearchParams(data) const request = new Request(url, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: formData }) return fetch(request) .then(response => { if (!response.ok) { throw new Error('Backend server response was not OK') } return response.json() }) .then(data => { return data }) .catch(error => { throw new Error('Request to backend server failed: ' + error) }) }