Support HTML editing with inline <span> grammar markup
This commit is contained in:
parent
0d8d6e165a
commit
aa3f025c7d
@ -11,8 +11,10 @@
|
|||||||
<body>
|
<body>
|
||||||
<!--<div id="ed1" class="online-editor" contenteditable="true">Tukaj vpišite besedilo ki ga želite popraviti.</div>
|
<!--<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="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="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>
|
<my-component></my-component>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@ -6,13 +6,12 @@ window.onload = () => {
|
|||||||
// Search and prepare all our editors found in the document.
|
// Search and prepare all our editors found in the document.
|
||||||
document.querySelectorAll('.online-editor').forEach(edit => {
|
document.querySelectorAll('.online-editor').forEach(edit => {
|
||||||
let editor = {
|
let editor = {
|
||||||
ignoreInput: false,
|
|
||||||
timer: null
|
timer: null
|
||||||
}
|
}
|
||||||
besEditors[edit.id] = editor
|
besEditors[edit.id] = editor
|
||||||
besCheckText(edit)
|
besCheckText(edit)
|
||||||
|
|
||||||
//edit.addEventListener('beforeinput', e => besBeforeInput(edit.id, e), false)
|
edit.addEventListener('beforeinput', e => besBeforeInput(edit.id, e), false)
|
||||||
|
|
||||||
edit.addEventListener('click', e => {
|
edit.addEventListener('click', e => {
|
||||||
besHandleClick(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)
|
async function besCheckText(el)
|
||||||
{
|
{
|
||||||
switch (el.nodeType) {
|
switch (el.nodeType) {
|
||||||
@ -27,12 +33,21 @@ async function besCheckText(el)
|
|||||||
return [{ text: el.textContent, el: el, markup: false }]
|
return [{ text: el.textContent, el: el, markup: false }]
|
||||||
|
|
||||||
case Node.ELEMENT_NODE:
|
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 = []
|
let data = []
|
||||||
for (const el2 of el.childNodes) {
|
for (const el2 of el.childNodes) {
|
||||||
data = data.concat(await besCheckText(el2))
|
data = data.concat(await besCheckText(el2))
|
||||||
}
|
}
|
||||||
let defaultView = document.defaultView;
|
let defaultView = document.defaultView;
|
||||||
switch (defaultView.getComputedStyle(el, null).getPropertyValue('display'))
|
switch (defaultView.getComputedStyle(el, null).getPropertyValue('display').toLowerCase())
|
||||||
{
|
{
|
||||||
case 'inline':
|
case 'inline':
|
||||||
case 'inline-block':
|
case 'inline-block':
|
||||||
@ -62,6 +77,20 @@ async function besCheckText(el)
|
|||||||
return response.json()
|
return response.json()
|
||||||
})
|
})
|
||||||
.then(responseData => {
|
.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.
|
// Reverse sort grammar mistakes for easier markup insertion later.
|
||||||
// When we start inserting grammar mistakes at the back, indexes before that remain valid.
|
// 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);
|
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.
|
// but it doesnt work when the range has partially selected a non-Text node.
|
||||||
// range.surroundContents(span)
|
// range.surroundContents(span)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
el.setAttribute('besChecked', 'true')
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
// TODO: Make parsing issues non-fatal.
|
// 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)
|
function besBeforeInput(editorId, event)
|
||||||
{
|
{
|
||||||
let editor = besEditors[editorId]
|
let editor = besEditors[editorId]
|
||||||
if (editor.ignoreInput) return
|
|
||||||
|
|
||||||
if (editor.timer) clearTimeout(editor.timer)
|
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)
|
let edit = document.getElementById(editorId)
|
||||||
event.getTargetRanges().forEach(range => {
|
event.getTargetRanges().forEach(range => besGetBlockParent(range.startContainer, edit)?.removeAttribute('besChecked'))
|
||||||
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
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function besHandleClick(e) {
|
function besHandleClick(e) {
|
||||||
switch (e.target) {
|
switch (e.target) {
|
||||||
case e.target.closest('span'):
|
case e.target.closest('span'):
|
||||||
|
Loading…
x
Reference in New Issue
Block a user