Since this component is about to be included in other solutions, chance of name collisions with others should be minimized.
146 lines
4.5 KiB
JavaScript
146 lines
4.5 KiB
JavaScript
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 <div> 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 += '<span class="typo-mistake">' + text.substring(mistakeStart, mistakeEnd) + '</span>'
|
|
offset = mistakeEnd
|
|
}
|
|
textOut += text.substring(offset)
|
|
editor.ignoreInput = true
|
|
ed.innerHTML = textOut
|
|
editor.ignoreInput = false
|
|
} else {
|
|
// Editor contains <div> 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 += '<span class="typo-mistake">' + text.substring(mistakeStart, mistakeEnd) + '</span>'
|
|
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)
|
|
})
|
|
}
|