Support HTML editing with inline <span> grammar markup
This commit is contained in:
parent
0d8d6e165a
commit
aa3f025c7d
@ -11,8 +11,10 @@
|
||||
<body>
|
||||
<!--<div id="ed1" class="online-editor" contenteditable="true">Tukaj vpišite besedilo ki ga želite popraviti.</div>
|
||||
<div id="ed2" class="online-editor" contenteditable="true"></div>
|
||||
<div id="ed3" class="online-editor" contenteditable="true"><div>Popravite kar želite.</div></div>-->
|
||||
<div id="ed3" class="online-editor" contenteditable="true"><div>Popravite kar želite.</div></div>
|
||||
<div id="ed4" class="online-editor" contenteditable="true"><div>Popravite <a href=".">kar želite</a>.</div><div>Na mizo nisem položil knjigo. Popravite kar želite.</div></div>
|
||||
<div id="ed5" class="online-editor" contenteditable="true">To je preiskus.</div>-->
|
||||
<div id="ed6" class="online-editor" contenteditable="true"><div class="contextual"><p beschecked="true">Madžarski premier Orban je tako očitno vendarle <span class="typo-mistake">pristal na nadaljnjo makrofinančno pomoč Ukrajini</span> v okviru revizije dolgoročnega proračuna unije 2021-2027<span class="typo-mistake">.</span> Ta vključuje 50 milijard evrov za Ukrajino za prihodnja štiri leta, od tega 33 milijard evrov posojil in 17 milijard evrov nepovratnih sredstev.</p></div></div>
|
||||
<my-component></my-component>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -6,13 +6,12 @@ window.onload = () => {
|
||||
// Search and prepare all our editors found in the document.
|
||||
document.querySelectorAll('.online-editor').forEach(edit => {
|
||||
let editor = {
|
||||
ignoreInput: false,
|
||||
timer: null
|
||||
}
|
||||
besEditors[edit.id] = editor
|
||||
besCheckText(edit)
|
||||
|
||||
//edit.addEventListener('beforeinput', e => besBeforeInput(edit.id, e), false)
|
||||
edit.addEventListener('beforeinput', e => besBeforeInput(edit.id, e), false)
|
||||
|
||||
edit.addEventListener('click', e => {
|
||||
besHandleClick(e)
|
||||
@ -20,6 +19,13 @@ window.onload = () => {
|
||||
})
|
||||
}
|
||||
|
||||
function besIntervalsOverlap(start1, end1, start2, end2)
|
||||
{
|
||||
return (
|
||||
start2 <= start1 && start1 < end2 ||
|
||||
start1 <= start2 && start2 < end1)
|
||||
}
|
||||
|
||||
async function besCheckText(el)
|
||||
{
|
||||
switch (el.nodeType) {
|
||||
@ -27,12 +33,21 @@ async function besCheckText(el)
|
||||
return [{ text: el.textContent, el: el, markup: false }]
|
||||
|
||||
case Node.ELEMENT_NODE:
|
||||
if (el.getAttribute('besChecked') === 'true') {
|
||||
return [{ text: '<'+el.tagName+'/>', el: el, markup: true }]
|
||||
}
|
||||
for (const el2 of el.childNodes) {
|
||||
if (el2.tagName === 'SPAN' && el2.classList.contains('typo-mistake')) {
|
||||
el2.replaceWith(...el2.childNodes)
|
||||
el2.remove()
|
||||
}
|
||||
}
|
||||
let data = []
|
||||
for (const el2 of el.childNodes) {
|
||||
data = data.concat(await besCheckText(el2))
|
||||
}
|
||||
let defaultView = document.defaultView;
|
||||
switch (defaultView.getComputedStyle(el, null).getPropertyValue('display'))
|
||||
switch (defaultView.getComputedStyle(el, null).getPropertyValue('display').toLowerCase())
|
||||
{
|
||||
case 'inline':
|
||||
case 'inline-block':
|
||||
@ -62,6 +77,20 @@ async function besCheckText(el)
|
||||
return response.json()
|
||||
})
|
||||
.then(responseData => {
|
||||
if (!responseData.matches.length) return
|
||||
|
||||
// Remove overlapping grammar mistakes for simplicity.
|
||||
for (let idx1 = 0; idx1 < responseData.matches.length - 1; ++idx1) {
|
||||
for (let idx2 = idx1 + 1; idx2 < responseData.matches.length;) {
|
||||
if (besIntervalsOverlap(
|
||||
responseData.matches[idx1].offset, responseData.matches[idx1].offset + responseData.matches[idx1].length,
|
||||
responseData.matches[idx2].offset, responseData.matches[idx2].offset + responseData.matches[idx2].length)) {
|
||||
responseData.matches.splice(idx2, 1)
|
||||
}
|
||||
else idx2++
|
||||
}
|
||||
}
|
||||
|
||||
// Reverse sort grammar mistakes for easier markup insertion later.
|
||||
// When we start inserting grammar mistakes at the back, indexes before that remain valid.
|
||||
responseData.matches.sort((a, b) => a.offset < b.offset ? +1 : a.offset > b.offset ? -1 : 0);
|
||||
@ -96,6 +125,8 @@ async function besCheckText(el)
|
||||
// but it doesnt work when the range has partially selected a non-Text node.
|
||||
// range.surroundContents(span)
|
||||
})
|
||||
|
||||
el.setAttribute('besChecked', 'true')
|
||||
})
|
||||
.catch(error => {
|
||||
// TODO: Make parsing issues non-fatal.
|
||||
@ -110,29 +141,42 @@ async function besCheckText(el)
|
||||
}
|
||||
}
|
||||
|
||||
function besGetBlockParent(el, edit)
|
||||
{
|
||||
const defaultView = document.defaultView
|
||||
while (el && el !== edit) {
|
||||
switch (el.nodeType) {
|
||||
case Node.TEXT_NODE:
|
||||
el = el.parentNode
|
||||
continue
|
||||
|
||||
case Node.ELEMENT_NODE:
|
||||
switch (defaultView.getComputedStyle(el, null).getPropertyValue('display').toLowerCase())
|
||||
{
|
||||
case 'inline':
|
||||
case 'inline-block':
|
||||
el = el.parentNode
|
||||
continue
|
||||
default:
|
||||
return el
|
||||
}
|
||||
}
|
||||
}
|
||||
return el
|
||||
}
|
||||
|
||||
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)
|
||||
editor.timer = setTimeout(function(){ besCheckText(edit) }, 1000)
|
||||
|
||||
// No need to invalidate elements after range.startContainer since they will
|
||||
// get either deleted or replaced.
|
||||
let edit = document.getElementById(editorId)
|
||||
event.getTargetRanges().forEach(range => {
|
||||
if (range.startContainer === edit)
|
||||
return
|
||||
for (var el = range.startContainer; el ; el = el.nextSibling) {
|
||||
for (var el2 = el; el2 && el2 !== edit; el2 = el2.parentElement) {
|
||||
if (!(el2 instanceof HTMLElement) || el2.tagName !== 'DIV') continue
|
||||
el2.removeAttribute('data-info')
|
||||
}
|
||||
if (el === range.endContainer) break
|
||||
}
|
||||
})
|
||||
event.getTargetRanges().forEach(range => besGetBlockParent(range.startContainer, edit)?.removeAttribute('besChecked'))
|
||||
}
|
||||
|
||||
|
||||
function besHandleClick(e) {
|
||||
switch (e.target) {
|
||||
case e.target.closest('span'):
|
||||
|
Loading…
x
Reference in New Issue
Block a user