let editors = {} // 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 } editors[ed.id] = editor checkText(ed.id) ed.addEventListener('beforeinput', e => beforeTextChange(ed.id, e), false) ed.addEventListener('click', e => { handleClick(e) }) }) } function handleClick(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 checkText(editorId) { let text = '' let editor = editors[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 beforeTextChange(editorId, event) { let editor = editors[editorId] if (editor.ignoreInput) return if (editor.timer) clearTimeout(editor.timer) editor.timer = setTimeout(function(){ checkText(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 } }) } function createFirstParagraph(text) { const divRegex = /]*>/i const firstDiv = text.match(divRegex) if (!firstDiv) return `
${text}
` const slicedText = text?.slice(0, firstDiv.index) const firstParagraph = `
${slicedText}
` const newText = firstParagraph + text.slice(firstDiv.index) return newText } function separateParagraphs(text) { let paragraphs = text.match(/
.*?<\/div>/g) return paragraphs } 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) }) } function renderMistakes(matches, paragraph) {} // //TODO: Popravi dodajanje presledkov // function mockSpellChecker(text) { // const specificWords = ['Tole', 'nov', 'test'] // const words = text.split(/(?=<)|(?<=\>)|\s/g).filter(word => word !== '') // const modifiedWords = words.map(word => { // const wordWithoutTags = word.replace(/<[^>]*>/g, '') // if (specificWords.includes(wordWithoutTags)) { // return `${word}` // } // return word // }) // // This is necessary to remove spaces between opening and closing tags // // TODO: improve this or find a better way to do it // return modifiedWords // .map((word, index) => { // if (index === 0 || index === 1 || index === modifiedWords.length - 1) { // return word // } // return ' ' + word // }) // .join('') // }