Extend grammar markup style
This commit is contained in:
404
service.js
404
service.js
@@ -43,6 +43,7 @@ class BesService {
|
||||
this.results = [] // Results of grammar-checking, one per each block/paragraph of text
|
||||
this.highlightElements = []
|
||||
this.createCorrectionPanel()
|
||||
this.markupStyle = 'underline'
|
||||
|
||||
// Disable browser built-in spell-checker to prevent collision with our grammar markup.
|
||||
this.originalSpellcheck = this.hostElement.spellcheck
|
||||
@@ -188,6 +189,18 @@ class BesService {
|
||||
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.
|
||||
*
|
||||
@@ -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 {String} ruleId Grammar mistake rule ID as reported by BesStr
|
||||
* @returns {Array} Grammar mistake highlight elements
|
||||
* @param {*} match Grammar checking rule match
|
||||
*/
|
||||
addMistakeMarkup(range, ruleId) {
|
||||
addMistakeMarkup(match) {
|
||||
const range = match.range
|
||||
const ruleId = match.match.rule.id
|
||||
const scrollPanelRect = this.scrollPanel.getBoundingClientRect()
|
||||
let highlights = []
|
||||
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.strokeStyle = ruleId.startsWith('MORFOLOGIK_RULE')
|
||||
? 'rgba(255, 115, 0, 0.5)'
|
||||
: 'rgba(0, 123, 255, 0.5)'
|
||||
for (let rect of range.getClientRects()) {
|
||||
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)
|
||||
|
||||
// Draw the underline
|
||||
const drawSideMarker = (y1, y2) => {
|
||||
this.ctx.beginPath()
|
||||
this.ctx.moveTo(x, y + height)
|
||||
this.ctx.lineTo(x + width, y + height)
|
||||
this.ctx.moveTo(markerX, y1 * dpr)
|
||||
this.ctx.lineTo(markerX, y2 * dpr)
|
||||
this.ctx.stroke()
|
||||
highlights.push(rect)
|
||||
}
|
||||
return highlights
|
||||
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 y = (rect.top - scrollPanelRect.top) * dpr
|
||||
const width = rect.width * dpr
|
||||
const height = rect.height * dpr
|
||||
|
||||
// Draw the underline.
|
||||
this.ctx.beginPath()
|
||||
this.ctx.moveTo(x, y + height)
|
||||
this.ctx.lineTo(x + width, y + height)
|
||||
this.ctx.stroke()
|
||||
}
|
||||
|
||||
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.height !== canvasPanelRect.height * dpr
|
||||
) {
|
||||
this.canvasPanel.width = canvasPanelRect.width * dpr
|
||||
this.canvasPanel.height = canvasPanelRect.height * dpr
|
||||
this.canvasPanel.width = Math.round(canvasPanelRect.width * dpr)
|
||||
this.canvasPanel.height = Math.round(canvasPanelRect.height * dpr)
|
||||
this.repositionAllMarkup()
|
||||
}
|
||||
if (this.isHostElementInline()) {
|
||||
@@ -539,21 +744,9 @@ class BesService {
|
||||
* Updates all grammar mistake markup positions.
|
||||
*/
|
||||
repositionAllMarkup() {
|
||||
const dpr = window.devicePixelRatio
|
||||
this.ctx.clearRect(
|
||||
0,
|
||||
0,
|
||||
this.canvasPanel.width * dpr,
|
||||
this.canvasPanel.height * dpr
|
||||
)
|
||||
this.ctx.clearRect(0, 0, this.canvasPanel.width, this.canvasPanel.height)
|
||||
this.results.forEach(result => {
|
||||
result.matches.forEach(match => {
|
||||
if (match.highlights) delete match.highlights
|
||||
match.highlights = this.addMistakeMarkup(
|
||||
match.range,
|
||||
match.match.rule.id
|
||||
)
|
||||
})
|
||||
result.matches.forEach(match => this.addMistakeMarkup(match))
|
||||
})
|
||||
}
|
||||
|
||||
@@ -700,49 +893,17 @@ class BesTreeService extends BesService {
|
||||
.then(responseData => {
|
||||
let matches = []
|
||||
responseData.matches.forEach(match => {
|
||||
let range = document.createRange()
|
||||
|
||||
// Locate start of the grammar mistake.
|
||||
for (
|
||||
let idx = 0, startingOffset = 0;
|
||||
;
|
||||
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,
|
||||
let m = {
|
||||
data: data,
|
||||
range: this.makeRange(
|
||||
data,
|
||||
match.offset,
|
||||
match.offset + match.length
|
||||
),
|
||||
match: match
|
||||
})
|
||||
}
|
||||
this.addMistakeMarkup(m)
|
||||
matches.push(m)
|
||||
})
|
||||
this.markProofed(node, matches)
|
||||
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.
|
||||
*
|
||||
@@ -1454,27 +1643,18 @@ class BesPlainTextService extends BesService {
|
||||
.then(responseData => {
|
||||
let matches = []
|
||||
responseData.matches.forEach(match => {
|
||||
let matchRange = document.createRange()
|
||||
let nodeIdx = 0,
|
||||
matchStart = start + match.offset
|
||||
while (nodeIdx < nodes.length && nodes[nodeIdx].end < matchStart)
|
||||
nodeIdx++
|
||||
matchRange.setStart(
|
||||
nodes[nodeIdx].node,
|
||||
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,
|
||||
const d = { nodes, start, end }
|
||||
let m = {
|
||||
data: d,
|
||||
range: this.makeRange(
|
||||
d,
|
||||
match.offset,
|
||||
match.offset + match.length
|
||||
),
|
||||
match: match
|
||||
})
|
||||
}
|
||||
this.addMistakeMarkup(m)
|
||||
matches.push(m)
|
||||
})
|
||||
this.markProofed(paragraphRange, matches)
|
||||
this.onProofingProgress(matches.length)
|
||||
@@ -1486,6 +1666,26 @@ class BesPlainTextService extends BesService {
|
||||
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
|
||||
*
|
||||
|
Reference in New Issue
Block a user