Extend grammar markup style

This commit is contained in:
Simon Rozman 2025-02-26 15:51:15 +01:00
parent ad256cabef
commit ad13e9849f
2 changed files with 388 additions and 102 deletions

86
samples/markup-style.html Normal file
View File

@ -0,0 +1,86 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>BesService Markup Styles Example</title>
<link rel="stylesheet" href="../styles.css" />
<link rel="stylesheet" href="styles.css" />
<script src="https://cdn.ckeditor.com/ckeditor5/41.1.0/classic/ckeditor.js"></script>
<script>const besUrl = 'http://localhost:225/api/v2';</script>
<script src="../service.js"></script>
<script src="common.js"></script>
</head>
<body>
<p class="my-block">This is an example how to switch markup style.</p>
<p class="my-block">
<input type="radio" id="markup_style_underline" name="markup_style" onchange="set_markup_style('underline')" checked="true"/> <label for="markup_style_underline">Underline parts of sentences where grammar mistake is detected (default)</label><br>
<input type="radio" id="markup_style_lector" name="markup_style" onchange="set_markup_style('lector')"/> <label for="markup_style_lector">Use lector signs to markup grammar mistakes</label>
</p>
</p>
<script>
function set_markup_style(style) {
BesService.getServiceByElement(document.getElementById('textarea-control'))?.setMarkupStyle(style)
BesService.getServiceByElement(document.getElementById('contenteditable-control'))?.setMarkupStyle(style)
BesService.getServiceByElement(document.getElementById('readonly-control'))?.setMarkupStyle(style)
BesService.getServiceByElement(my_ckeditor)?.setMarkupStyle(style)
}
</script>
<h1 class="my-block">&lt;textarea&gt; Control</h1>
<div class="my-block">
<textarea id="textarea-control" class="my-control">Tukaj vpišite besedilo ki ga želite popraviti.
Prišla je njena lepa hčera. Smatram da tega nebi bilo potrebno storiti. Predavanje je trajalo dve ure. S njim grem v Kamnik. Janez jutri nebo prišel. Prišel je z 100 idejami.
To velja tudi v Bledu. To se je zgodilo na velikemu vrtu. Prišel je na Kamnik. On je včeraj prišel z svojo torbo. Dve žemlje prosim. Pogosto brskam po temu forumu. Prišel je včeraj in sicer s otroci. To ne vem. Pogleda vse kar daš v odložišče. Nisem jo videl. Ona izgleda dobro. Pri zanikanju ne smete uporabljati tožilnik. Vlak gre v Ljubljano čez Zidani Most. Skočil je čez okno. Slovenija meji na avstrijo. Jaz pišem v Slovenščini vsak Torek. Novica, da je skupina 25 planincev hodila pod vodstvom gorskega vodnika je napačna in zavajujoča. Želim da poješ kosmizailo. Jaz pogosto brskam po temu forumu. Med tem ko je iskal ključe, so se odprla vrata. V takoimenovanem skladišču je bilo veliko ljudi. V sobi sta dve mize. Stekel je h mami. Videl sem Jurčič Micko. To je bil njegov življenski cilj. Po vrsti popravite vse kar želite. Preden zaspiva mi prebere pravljico. Prišel je s stricom. Oni zadanejo tarčo. Mi gremo teči po polju. Mi gremo peči kruh. Usedel se je k miza. Postreži kosilo! Skul je veslanje z dvemi vesli.
Na mizo nisem položil knjigo.
Kvazimodo ji je ponavadi prinesel hrano in pijačo, medtem ko je spala, da ne bi videla njegov iznakažen in grd obraz. Poleg tega ji je pustil tudi piščalko, da bi ga lahko priklicala, če bi bilo to potrebno. Kvazimodo se je odločil, da razveseli Esmeraldo in ji obljubi, da ji bo pripeljal Febusa. Toda Febus ni želel priti. Kvazimodo ji je raje lagal, da ni mogel najti Febusa, kot da Esmeraldi pove resnico, ker bi ona trpela.</textarea>
</div>
<h1 class="my-block">&lt;div contenteditable="true"&gt; Control</h1>
<div id="contenteditable-control" class="my-block my-control" contenteditable="true">
<p style="font-size: 30px;">Tukaj vpišite besedilo ki ga želite popraviti.</p>
<p>Prišla je njena lepa hčera. Smatram da tega nebi bilo potrebno storiti. Predavanje je trajalo dve ure. S njim grem v Kamnik. Janez jutri nebo prišel. Prišel je z 100 idejami.</p>
<p>To velja tudi v Bledu. To se je zgodilo na velikemu vrtu. Prišel je na Kamnik. On je včeraj prišel z svojo torbo. Dve žemlje prosim. Pogosto brskam po temu forumu. Prišel je včeraj in sicer s otroci. To ne vem. Pogleda vse kar daš v odložišče. Nisem jo videl. Ona izgleda dobro. Pri zanikanju ne smete uporabljati tožilnik. Vlak gre v Ljubljano čez Zidani Most. Skočil je čez okno. Slovenija meji na avstrijo. Jaz pišem v Slovenščini vsak Torek. Novica, da je skupina 25 planincev hodila pod vodstvom gorskega vodnika je napačna in zavajujoča. Želim da poješ kosmizailo. Jaz pogosto brskam po temu forumu. Med tem ko je iskal ključe, so se odprla vrata. V takoimenovanem skladišču je bilo veliko ljudi. V sobi sta dve mize. Stekel je h mami. Videl sem Jurčič Micko. To je bil njegov življenski cilj. Po vrsti popravite vse kar želite. Preden zaspiva mi prebere pravljico. Prišel je s stricom. Oni zadanejo tarčo. Mi gremo teči po polju. Mi gremo peči kruh. Usedel se je k miza. Postreži kosilo! Skul je veslanje z dvemi vesli.</p>
<p>Na mizo nisem položil knjigo.</p>
<p>Kvazimodo ji je ponavadi prinesel hrano in pijačo, medtem ko je spala, da ne bi videla njegov iznakažen in grd obraz. Poleg tega ji je pustil tudi piščalko, da bi ga lahko priklicala, če bi bilo to potrebno. Kvazimodo se je odločil, da razveseli Esmeraldo in ji obljubi, da ji bo pripeljal Febusa. Toda Febus ni želel priti. Kvazimodo ji je raje lagal, da ni mogel najti Febusa, kot da Esmeraldi pove resnico, ker bi ona trpela.</p>
</div>
<h1 class="my-block">Static Content</h1>
<div id="readonly-control" class="my-block my-control">
<p>Tukaj vpišite besedilo ki ga želite popraviti.</p>
<p>Prišla je njena lepa hčera. Smatram da tega nebi bilo potrebno storiti. Predavanje je trajalo dve ure. S njim grem v Kamnik. Janez jutri nebo prišel. Prišel je z 100 idejami.</p>
<p>To velja tudi v Bledu. To se je zgodilo na velikemu vrtu. Prišel je na Kamnik. On je včeraj prišel z svojo torbo. Dve žemlje prosim. Pogosto brskam po temu forumu. Prišel je včeraj in sicer s otroci. To ne vem. Pogleda vse kar daš v odložišče. Nisem jo videl. Ona izgleda dobro. Pri zanikanju ne smete uporabljati tožilnik. Vlak gre v Ljubljano čez Zidani Most. Skočil je čez okno. Slovenija meji na avstrijo. Jaz pišem v Slovenščini vsak Torek. Novica, da je skupina 25 planincev hodila pod vodstvom gorskega vodnika je napačna in zavajujoča. Želim da poješ kosmizailo. Jaz pogosto brskam po temu forumu. Med tem ko je iskal ključe, so se odprla vrata. V takoimenovanem skladišču je bilo veliko ljudi. V sobi sta dve mize. Stekel je h mami. Videl sem Jurčič Micko. To je bil njegov življenski cilj. Po vrsti popravite vse kar želite. Preden zaspiva mi prebere pravljico. Prišel je s stricom. Oni zadanejo tarčo. Mi gremo teči po polju. Mi gremo peči kruh. Usedel se je k miza. Postreži kosilo! Skul je veslanje z dvemi vesli.</p>
<p>Na mizo nisem položil knjigo.</p>
<p>Kvazimodo ji je ponavadi prinesel hrano in pijačo, medtem ko je spala, da ne bi videla njegov iznakažen in grd obraz. Poleg tega ji je pustil tudi piščalko, da bi ga lahko priklicala, če bi bilo to potrebno. Kvazimodo se je odločil, da razveseli Esmeraldo in ji obljubi, da ji bo pripeljal Febusa. Toda Febus ni želel priti. Kvazimodo ji je raje lagal, da ni mogel najti Febusa, kot da Esmeraldi pove resnico, ker bi ona trpela.</p>
</div>
<h1 class="my-block">CKEditor Control</h1>
<div class="my-block">
<div id="ckeditor-control">
<p>Tukaj vpišite besedilo ki ga želite popraviti.</p>
<p>Prišla je njena lepa hčera. Smatram da tega nebi bilo potrebno storiti. Predavanje je trajalo dve ure. S njim grem v Kamnik. Janez jutri nebo prišel. Prišel je z 100 idejami.</p>
<p>To velja tudi v Bledu. To se je zgodilo na velikemu vrtu. Prišel je na Kamnik. On je včeraj prišel z svojo torbo. Dve žemlje prosim. Pogosto brskam po temu forumu. Prišel je včeraj in sicer s otroci. To ne vem. Pogleda vse kar daš v odložišče. Nisem jo videl. Ona izgleda dobro. Pri zanikanju ne smete uporabljati tožilnik. Vlak gre v Ljubljano čez Zidani Most. Skočil je čez okno. Slovenija meji na avstrijo. Jaz pišem v Slovenščini vsak Torek. Novica, da je skupina 25 planincev hodila pod vodstvom gorskega vodnika je napačna in zavajujoča. Želim da poješ kosmizailo. Jaz pogosto brskam po temu forumu. Med tem ko je iskal ključe, so se odprla vrata. V takoimenovanem skladišču je bilo veliko ljudi. V sobi sta dve mize. Stekel je h mami. Videl sem Jurčič Micko. To je bil njegov življenski cilj. Po vrsti popravite vse kar želite. Preden zaspiva mi prebere pravljico. Prišel je s stricom. Oni zadanejo tarčo. Mi gremo teči po polju. Mi gremo peči kruh. Usedel se je k miza. Postreži kosilo! Skul je veslanje z dvemi vesli.</p>
<p>Na mizo nisem položil knjigo.</p>
<p>Kvazimodo ji je ponavadi prinesel hrano in pijačo, medtem ko je spala, da ne bi videla njegov iznakažen in grd obraz. Poleg tega ji je pustil tudi piščalko, da bi ga lahko priklicala, če bi bilo to potrebno. Kvazimodo se je odločil, da razveseli Esmeraldo in ji obljubi, da ji bo pripeljal Febusa. Toda Febus ni želel priti. Kvazimodo ji je raje lagal, da ni mogel najti Febusa, kot da Esmeraldi pove resnico, ker bi ona trpela.</p>
</div>
</div>
<script>
BesService.registerByElement(document.getElementById('textarea-control'), new BesStatusIconEventSink())
BesService.registerByElement(document.getElementById('contenteditable-control'), new BesStatusIconEventSink())
BesService.registerByElement(document.getElementById('readonly-control'), new BesStatusIconEventSink())
let my_ckeditor = null
ClassicEditor.create(document.querySelector('#ckeditor-control'))
.then(newEditor => {
my_ckeditor = newEditor
BesCKService.register(newEditor.ui.view.editable.element, newEditor, new BesCKStatusIconEventSink())
})
.catch(error => console.error(error))
</script>
<bes-popup-el/>
</body>
</html>

View File

@ -43,6 +43,7 @@ class BesService {
this.results = [] // Results of grammar-checking, one per each block/paragraph of text this.results = [] // Results of grammar-checking, one per each block/paragraph of text
this.highlightElements = [] this.highlightElements = []
this.createCorrectionPanel() this.createCorrectionPanel()
this.markupStyle = 'underline'
// Disable browser built-in spell-checker to prevent collision with our grammar markup. // Disable browser built-in spell-checker to prevent collision with our grammar markup.
this.originalSpellcheck = this.hostElement.spellcheck this.originalSpellcheck = this.hostElement.spellcheck
@ -188,6 +189,18 @@ class BesService {
return this return this
} }
/**
* Sets markup style.
*
* @param {String} style Can be one of the following values:
* 'underline' Underline parts of sentences where grammar mistake is detected (default)
* 'lector' Use lector signs to markup grammar mistakes
*/
setMarkupStyle(style) {
this.markupStyle = style
this.repositionAllMarkup()
}
/** /**
* Schedules proofing after given number of milliseconds. * Schedules proofing after given number of milliseconds.
* *
@ -343,41 +356,233 @@ class BesService {
} }
/** /**
* Creates grammar mistake markup in DOM. * Creates grammar mistake markup in DOM and populates collection of highlight rectangles.
* *
* @param {Range} range Grammar mistake range * @param {*} match Grammar checking rule match
* @param {String} ruleId Grammar mistake rule ID as reported by BesStr
* @returns {Array} Grammar mistake highlight elements
*/ */
addMistakeMarkup(range, ruleId) { addMistakeMarkup(match) {
const range = match.range
const ruleId = match.match.rule.id
const scrollPanelRect = this.scrollPanel.getBoundingClientRect() const scrollPanelRect = this.scrollPanel.getBoundingClientRect()
let highlights = []
const dpr = window.devicePixelRatio const dpr = window.devicePixelRatio
const markerX = this.canvasPanel.width - 30 * dpr
match.highlights = Array.from(range.getClientRects())
this.ctx.lineWidth = 2 * dpr // Use 2 for clearer visibility this.ctx.lineWidth = 2 * dpr // Use 2 for clearer visibility
this.ctx.strokeStyle = ruleId.startsWith('MORFOLOGIK_RULE') this.ctx.strokeStyle = ruleId.startsWith('MORFOLOGIK_RULE')
? 'rgba(255, 115, 0, 0.5)' ? 'rgba(255, 115, 0, 0.5)'
: 'rgba(0, 123, 255, 0.5)' : 'rgba(0, 123, 255, 0.5)'
for (let rect of range.getClientRects()) { const drawSideMarker = (y1, y2) => {
this.ctx.beginPath()
this.ctx.moveTo(markerX, y1 * dpr)
this.ctx.lineTo(markerX, y2 * dpr)
this.ctx.stroke()
}
switch (this.markupStyle) {
case 'lector':
if (match.match.replacements && match.match.replacements.length) {
const drawMissingComma = (x, y) => {
this.ctx.beginPath()
this.ctx.moveTo((x - 2) * dpr, y * dpr)
this.ctx.lineTo((x + 2) * dpr, y * dpr)
this.ctx.lineTo((x + 2) * dpr, (y - 4) * dpr)
this.ctx.stroke()
}
const drawWrongSpacing = (x, y1, y2) => {
this.ctx.beginPath()
this.ctx.moveTo((x - 4) * dpr, (y1 + 4) * dpr)
this.ctx.lineTo(x * dpr, y1 * dpr)
this.ctx.lineTo((x + 4) * dpr, (y1 + 4) * dpr)
this.ctx.moveTo(x * dpr, y1 * dpr)
this.ctx.lineTo(x * dpr, y2 * dpr)
this.ctx.moveTo((x - 4) * dpr, (y2 - 4) * dpr)
this.ctx.lineTo(x * dpr, y2 * dpr)
this.ctx.lineTo((x + 4) * dpr, (y2 - 4) * dpr)
this.ctx.stroke()
}
const drawExcessiveText = (x1, y1, x2, y2) => {
this.ctx.beginPath()
this.ctx.moveTo(x1 * dpr, y1 * dpr)
this.ctx.lineTo(x2 * dpr, y2 * dpr)
this.ctx.stroke()
}
const context = match.match.context.text.substr(
match.match.context.offset,
match.match.context.length
)
const replacement = match.match.replacements[0].value
const lengthDiff = replacement.length - context.length
if (
lengthDiff > 0 &&
replacement.substr(-context.length) === context
) {
// Something to insert before
const toInsert = replacement.substr(0, lengthDiff)
if (toInsert === ',') {
const x = match.highlights[0].left - scrollPanelRect.left
const y = match.highlights[0].bottom - scrollPanelRect.top
drawMissingComma(x, y)
} else if (/^\s+$/.test(toInsert)) {
const x = match.highlights[0].left - scrollPanelRect.left
const y1 = match.highlights[0].bottom - scrollPanelRect.top - 2
const y2 = match.highlights[0].top - scrollPanelRect.top + 2
drawWrongSpacing(x, y1, y2)
} else {
// TODO
}
drawSideMarker(
match.highlights[0].top - scrollPanelRect.top,
match.highlights[0].bottom - scrollPanelRect.top
)
} else if (replacement.substr(0, context.length) === context) {
// Something to insert after
const toInsert = replacement.substr(-lengthDiff)
if (toInsert === ',') {
const x = match.highlights.at(-1).right - scrollPanelRect.left
const y = match.highlights.at(-1).bottom - scrollPanelRect.top
drawMissingComma(x, y)
} else if (/^\s+$/.test(toInsert)) {
const x = match.highlights.at(-1).right - scrollPanelRect.left
const y1 =
match.highlights.at(-1).bottom - scrollPanelRect.top - 2
const y2 = match.highlights.at(-1).top - scrollPanelRect.top + 2
drawWrongSpacing(x, y1, y2)
} else {
// TODO
}
drawSideMarker(
match.highlights.at(-1).top - scrollPanelRect.top,
match.highlights.at(-1).bottom - scrollPanelRect.top
)
} else if (
lengthDiff < 0 &&
context.substr(-replacement.length) === replacement
) {
// Something to remove before
const toRemove = context.substr(0, -lengthDiff)
if (/^\s+$/.test(toRemove)) {
const rect = this.makeRange(
match.data,
match.match.offset,
match.match.offset - lengthDiff
)?.getClientRects()[0]
const x = (rect.left + rect.right) / 2 - scrollPanelRect.left
const y1 = rect.top - scrollPanelRect.top
const y2 = rect.bottom - scrollPanelRect.top
drawWrongSpacing(x, y1, y2)
} else {
for (let rect of this.makeRange(
match.data,
match.match.offset,
match.match.offset - lengthDiff
)?.getClientRects())
drawExcessiveText(
rect.left - scrollPanelRect.left,
rect.bottom - scrollPanelRect.top,
rect.right - scrollPanelRect.left,
rect.top - scrollPanelRect.top
)
}
drawSideMarker(
match.highlights[0].top - scrollPanelRect.top,
match.highlights[0].bottom - scrollPanelRect.top
)
} else if (context.substr(0, replacement.length) === replacement) {
// Something to remove after
const toRemove = context.substr(lengthDiff)
if (/^\s+$/.test(toRemove)) {
const rect = this.makeRange(
match.data,
match.match.offset + match.match.length + lengthDiff,
match.match.offset + match.match.length
)?.getClientRects()[0]
const x = (rect.left + rect.right) / 2 - scrollPanelRect.left
const y1 = rect.top - scrollPanelRect.top
const y2 = rect.bottom - scrollPanelRect.top
drawWrongSpacing(x, y1, y2)
} else {
for (let rect of this.makeRange(
match.data,
match.match.offset + match.match.length + lengthDiff,
match.match.offset + match.match.length
)?.getClientRects())
drawExcessiveText(
rect.left - scrollPanelRect.left,
rect.bottom - scrollPanelRect.top,
rect.right - scrollPanelRect.left,
rect.top - scrollPanelRect.top
)
}
drawSideMarker(
match.highlights.at(-1).top - scrollPanelRect.top,
match.highlights.at(-1).bottom - scrollPanelRect.top
)
} else {
// Sugesstion and context are different.
// TODO
drawSideMarker(
Math.min(...match.highlights.map(rect => rect.top)) -
scrollPanelRect.top,
Math.max(...match.highlights.map(rect => rect.bottom)) -
scrollPanelRect.top
)
}
} else {
// TODO
drawSideMarker(
Math.min(...match.highlights.map(rect => rect.top)) -
scrollPanelRect.top,
Math.max(...match.highlights.map(rect => rect.bottom)) -
scrollPanelRect.top
)
}
// for (let rect of match.highlights) {
// const x = (rect.left - scrollPanelRect.left) * dpr
// const y = (rect.top - scrollPanelRect.top) * dpr
// const width = rect.width * dpr
// const height = rect.height * dpr
// // MOCKUP text drawing
// this.ctx.font = `5px ${this.textFont}` // Font se lahko doda na sledeč način: `25px 'Times New Roman', serif`
// const text = 'To je izmišljeno besedilo'
// const textLength = this.ctx.measureText(text)
// console.log(`Dolžina texta: ${textLength.width}px`) // Dolžina texta
// this.ctx.fillText(text, x, y)
// }
break
default:
for (let rect of match.highlights) {
const x = (rect.left - scrollPanelRect.left) * dpr const x = (rect.left - scrollPanelRect.left) * dpr
const y = (rect.top - scrollPanelRect.top) * dpr const y = (rect.top - scrollPanelRect.top) * dpr
const width = rect.width * dpr const width = rect.width * dpr
const height = rect.height * dpr const height = rect.height * dpr
// MOCKUP text drawing // Draw the underline.
this.ctx.font = `5px ${this.textFont}` // Font se lahko doda na sledeč način: `25px 'Times New Roman', serif`
const text = 'To je izmišljeno besedilo'
const textLength = this.ctx.measureText(text)
console.log(`Dolžina texta: ${textLength.width}px`) // Dolžina texta
this.ctx.fillText(text, x, y)
// Draw the underline
this.ctx.beginPath() this.ctx.beginPath()
this.ctx.moveTo(x, y + height) this.ctx.moveTo(x, y + height)
this.ctx.lineTo(x + width, y + height) this.ctx.lineTo(x + width, y + height)
this.ctx.stroke() this.ctx.stroke()
highlights.push(rect)
} }
return highlights
drawSideMarker(
Math.min(...match.highlights.map(rect => rect.top)) -
scrollPanelRect.top,
Math.max(...match.highlights.map(rect => rect.bottom)) -
scrollPanelRect.top
)
}
} }
/** /**
@ -450,8 +655,8 @@ class BesService {
this.canvasPanel.width !== canvasPanelRect.width * dpr || this.canvasPanel.width !== canvasPanelRect.width * dpr ||
this.canvasPanel.height !== canvasPanelRect.height * dpr this.canvasPanel.height !== canvasPanelRect.height * dpr
) { ) {
this.canvasPanel.width = canvasPanelRect.width * dpr this.canvasPanel.width = Math.round(canvasPanelRect.width * dpr)
this.canvasPanel.height = canvasPanelRect.height * dpr this.canvasPanel.height = Math.round(canvasPanelRect.height * dpr)
this.repositionAllMarkup() this.repositionAllMarkup()
} }
if (this.isHostElementInline()) { if (this.isHostElementInline()) {
@ -539,21 +744,9 @@ class BesService {
* Updates all grammar mistake markup positions. * Updates all grammar mistake markup positions.
*/ */
repositionAllMarkup() { repositionAllMarkup() {
const dpr = window.devicePixelRatio this.ctx.clearRect(0, 0, this.canvasPanel.width, this.canvasPanel.height)
this.ctx.clearRect(
0,
0,
this.canvasPanel.width * dpr,
this.canvasPanel.height * dpr
)
this.results.forEach(result => { this.results.forEach(result => {
result.matches.forEach(match => { result.matches.forEach(match => this.addMistakeMarkup(match))
if (match.highlights) delete match.highlights
match.highlights = this.addMistakeMarkup(
match.range,
match.match.rule.id
)
})
}) })
} }
@ -700,49 +893,17 @@ class BesTreeService extends BesService {
.then(responseData => { .then(responseData => {
let matches = [] let matches = []
responseData.matches.forEach(match => { responseData.matches.forEach(match => {
let range = document.createRange() let m = {
data: data,
// Locate start of the grammar mistake. range: this.makeRange(
for ( data,
let idx = 0, startingOffset = 0; match.offset,
; match.offset + match.length
startingOffset += data[idx++].text.length ),
) {
if (
!data[idx].markup &&
/*startingOffset <= match.offset &&*/ match.offset <
startingOffset + data[idx].text.length
) {
range.setStart(
data[idx].node,
match.offset - startingOffset
)
break
}
}
// Locate end of the grammar mistake.
let endOffset = match.offset + match.length
for (
let idx = 0, startingOffset = 0;
;
startingOffset += data[idx++].text.length
) {
if (
!data[idx].markup &&
/*startingOffset <= endOffset &&*/ endOffset <=
startingOffset + data[idx].text.length
) {
range.setEnd(data[idx].node, endOffset - startingOffset)
break
}
}
matches.push({
highlights: this.addMistakeMarkup(range, match.rule.id),
range: range,
match: match match: match
}) }
this.addMistakeMarkup(m)
matches.push(m)
}) })
this.markProofed(node, matches) this.markProofed(node, matches)
this.onProofingProgress(matches.length) this.onProofingProgress(matches.length)
@ -763,6 +924,34 @@ class BesTreeService extends BesService {
} }
} }
makeRange(data, start, end) {
let range = document.createRange()
// Locate start of the grammar mistake.
for (let idx = 0, offset = 0; ; offset += data[idx++].text.length) {
if (
!data[idx].markup &&
/*offset <= start &&*/ start < offset + data[idx].text.length
) {
range.setStart(data[idx].node, start - offset)
break
}
}
// Locate end of the grammar mistake.
for (let idx = 0, offset = 0; ; offset += data[idx++].text.length) {
if (
!data[idx].markup &&
/*offset <= end &&*/ end <= offset + data[idx].text.length
) {
range.setEnd(data[idx].node, end - offset)
break
}
}
return range
}
/** /**
* Tests if given block element has already been grammar-checked. * Tests if given block element has already been grammar-checked.
* *
@ -1454,27 +1643,18 @@ class BesPlainTextService extends BesService {
.then(responseData => { .then(responseData => {
let matches = [] let matches = []
responseData.matches.forEach(match => { responseData.matches.forEach(match => {
let matchRange = document.createRange() const d = { nodes, start, end }
let nodeIdx = 0, let m = {
matchStart = start + match.offset data: d,
while (nodeIdx < nodes.length && nodes[nodeIdx].end < matchStart) range: this.makeRange(
nodeIdx++ d,
matchRange.setStart( match.offset,
nodes[nodeIdx].node, match.offset + match.length
matchStart - nodes[nodeIdx].start ),
)
let matchEnd = matchStart + match.length
while (nodeIdx < nodes.length && nodes[nodeIdx].end < matchEnd)
nodeIdx++
matchRange.setEnd(
nodes[nodeIdx].node,
matchEnd - nodes[nodeIdx].start
)
matches.push({
highlights: this.addMistakeMarkup(matchRange, match.rule.id),
range: matchRange,
match: match match: match
}) }
this.addMistakeMarkup(m)
matches.push(m)
}) })
this.markProofed(paragraphRange, matches) this.markProofed(paragraphRange, matches)
this.onProofingProgress(matches.length) this.onProofingProgress(matches.length)
@ -1486,6 +1666,26 @@ class BesPlainTextService extends BesService {
this.onProofingProgress(0) this.onProofingProgress(0)
} }
makeRange(data, start, end) {
let matchRange = document.createRange()
let nodeIdx = 0,
matchStart = data.start + start
while (nodeIdx < data.nodes.length && data.nodes[nodeIdx].end < matchStart)
nodeIdx++
matchRange.setStart(
data.nodes[nodeIdx].node,
matchStart - data.nodes[nodeIdx].start
)
let matchEnd = data.start + end
while (nodeIdx < data.nodes.length && data.nodes[nodeIdx].end < matchEnd)
nodeIdx++
matchRange.setEnd(
data.nodes[nodeIdx].node,
matchEnd - data.nodes[nodeIdx].start
)
return matchRange
}
/** /**
* Concatenates child text nodes * Concatenates child text nodes
* *