Compare commits
No commits in common. "bf7b844e1cf34548b45a85a90c8bad847e70cd03" and "0ed93fb9b29664ca759567b97724ea3797bd7fd7" have entirely different histories.
bf7b844e1c
...
0ed93fb9b2
@ -8,33 +8,9 @@
|
|||||||
<link rel="stylesheet" href="styles.css" />
|
<link rel="stylesheet" href="styles.css" />
|
||||||
<script>const besUrl = 'http://localhost:225/api/v2';</script>
|
<script>const besUrl = 'http://localhost:225/api/v2';</script>
|
||||||
<script src="../service.js"></script>
|
<script src="../service.js"></script>
|
||||||
<style>
|
|
||||||
.mock-resizable {
|
|
||||||
overflow-x: scroll;
|
|
||||||
position: relative;
|
|
||||||
display: inline-block;
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
width: 300px;
|
|
||||||
height: 400px;
|
|
||||||
border-radius: 3px;
|
|
||||||
background-color: #f9f9f9;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mock-content {
|
|
||||||
width: 1000px;
|
|
||||||
height: 600px;
|
|
||||||
background-color: #f9f9f9;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mock-flex {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="mock-flex">
|
<div class="flex">
|
||||||
<div class="mock-content">
|
<div class="mock-content">
|
||||||
<p>Element to delete</p>
|
<p>Element to delete</p>
|
||||||
<p>Element to delete</p>
|
<p>Element to delete</p>
|
||||||
@ -42,7 +18,7 @@
|
|||||||
<p>Element to delete</p>
|
<p>Element to delete</p>
|
||||||
<p>Element to delete</p>
|
<p>Element to delete</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="mock-resizable">
|
<div class="resizable">
|
||||||
<div class="my-block my-control bes-service" contenteditable="true">
|
<div class="my-block my-control bes-service" contenteditable="true">
|
||||||
<p>Tukaj vpišite besedilo ki ga želite popraviti.</p>
|
<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>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>
|
||||||
|
344
service.js
344
service.js
@ -73,9 +73,6 @@ class BesService {
|
|||||||
})
|
})
|
||||||
|
|
||||||
besServices.push(this)
|
besServices.push(this)
|
||||||
|
|
||||||
// Initial sync the scroll as hostElement may be scrolled by non-(0, 0) at the time of BesService registration.
|
|
||||||
this.onScroll()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -201,7 +198,7 @@ class BesService {
|
|||||||
*/
|
*/
|
||||||
setMarkupStyle(style) {
|
setMarkupStyle(style) {
|
||||||
this.markupStyle = style
|
this.markupStyle = style
|
||||||
this.redrawAllMistakeMarkup()
|
this.repositionAllMarkup()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -357,19 +354,17 @@ class BesService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Draws grammar mistake markup on canvas and populates collection of highlight rectangles.
|
* Creates grammar mistake markup in DOM and populates collection of highlight rectangles.
|
||||||
*
|
*
|
||||||
* @param {*} match Grammar checking rule match
|
* @param {*} match Grammar checking rule match
|
||||||
*/
|
*/
|
||||||
drawMistakeMarkup(match) {
|
addMistakeMarkup(match) {
|
||||||
const range = match.range
|
const range = match.range
|
||||||
|
const canvasPanelRect = this.canvasPanel.getBoundingClientRect()
|
||||||
|
const scrollX = canvasPanelRect.left
|
||||||
|
const scrollY = canvasPanelRect.top
|
||||||
match.highlights = Array.from(range.getClientRects())
|
match.highlights = Array.from(range.getClientRects())
|
||||||
if (match.highlights.length === 0) return
|
if (match.highlights.length === 0) return
|
||||||
const canvasPanelRect = this.canvasPanel.getBoundingClientRect()
|
|
||||||
for (let rect of match.highlights) {
|
|
||||||
rect.x -= canvasPanelRect.x
|
|
||||||
rect.y -= canvasPanelRect.y
|
|
||||||
}
|
|
||||||
const dpr = window.devicePixelRatio
|
const dpr = window.devicePixelRatio
|
||||||
this.ctx.lineWidth = 2 * dpr // Use 2 for clearer visibility
|
this.ctx.lineWidth = 2 * dpr // Use 2 for clearer visibility
|
||||||
const ruleId = match.match.rule.id
|
const ruleId = match.match.rule.id
|
||||||
@ -391,7 +386,7 @@ class BesService {
|
|||||||
const scale = (markerY2 - markerY1) / 18
|
const scale = (markerY2 - markerY1) / 18
|
||||||
const x = match.highlights[0].left
|
const x = match.highlights[0].left
|
||||||
const y = match.highlights[0].bottom
|
const y = match.highlights[0].bottom
|
||||||
this.drawMissingComma(x, y, scale, '?')
|
this.drawMissingComma(x - scrollX, y - scrollY, scale, '?')
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -418,20 +413,25 @@ class BesService {
|
|||||||
// in another line, making a confusing UX.
|
// in another line, making a confusing UX.
|
||||||
const x = match.highlights[0].left
|
const x = match.highlights[0].left
|
||||||
const y = match.highlights[0].bottom
|
const y = match.highlights[0].bottom
|
||||||
this.drawMissingComma(x, y, scale)
|
this.drawMissingComma(x - scrollX, y - scrollY, scale)
|
||||||
} else if (/^\s+$/.test(toInsert)) {
|
} else if (/^\s+$/.test(toInsert)) {
|
||||||
const x = match.highlights[0].left
|
const x = match.highlights[0].left
|
||||||
const y1 = match.highlights[0].bottom - 2 * scale
|
const y1 = match.highlights[0].bottom - 2 * scale
|
||||||
const y2 = match.highlights[0].top + 2 * scale
|
const y2 = match.highlights[0].top + 2 * scale
|
||||||
this.drawWrongSpacing(x, y1, y2, scale)
|
this.drawWrongSpacing(
|
||||||
|
x - scrollX,
|
||||||
|
y1 - scrollY,
|
||||||
|
y2 - scrollY,
|
||||||
|
scale
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
const x = match.highlights[0].left - 1 * scale
|
const x = match.highlights[0].left - 1 * scale
|
||||||
const y1 = match.highlights[0].bottom
|
const y1 = match.highlights[0].bottom
|
||||||
const y2 = match.highlights[0].top
|
const y2 = match.highlights[0].top
|
||||||
this.drawMissingText(
|
this.drawMissingText(
|
||||||
x,
|
x - scrollX,
|
||||||
y1,
|
y1 - scrollY,
|
||||||
y2,
|
y2 - scrollY,
|
||||||
scale,
|
scale,
|
||||||
replacement.substr(lengthDiff).trim()
|
replacement.substr(lengthDiff).trim()
|
||||||
)
|
)
|
||||||
@ -446,20 +446,25 @@ class BesService {
|
|||||||
if (toInsert === ',') {
|
if (toInsert === ',') {
|
||||||
const x = match.highlights.at(-1).right
|
const x = match.highlights.at(-1).right
|
||||||
const y = match.highlights.at(-1).bottom
|
const y = match.highlights.at(-1).bottom
|
||||||
this.drawMissingComma(x, y, scale)
|
this.drawMissingComma(x - scrollX, y - scrollY, scale)
|
||||||
} else if (/^\s+$/.test(toInsert)) {
|
} else if (/^\s+$/.test(toInsert)) {
|
||||||
const x = match.highlights.at(-1).right
|
const x = match.highlights.at(-1).right
|
||||||
const y1 = match.highlights.at(-1).bottom - 2 * scale
|
const y1 = match.highlights.at(-1).bottom - 2 * scale
|
||||||
const y2 = match.highlights.at(-1).top + 2 * scale
|
const y2 = match.highlights.at(-1).top + 2 * scale
|
||||||
this.drawWrongSpacing(x, y1, y2, scale)
|
this.drawWrongSpacing(
|
||||||
|
x - scrollX,
|
||||||
|
y1 - scrollY,
|
||||||
|
y2 - scrollY,
|
||||||
|
scale
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
const x = match.highlights.at(-1).right + 1 * scale
|
const x = match.highlights.at(-1).right + 1 * scale
|
||||||
const y1 = match.highlights.at(-1).bottom
|
const y1 = match.highlights.at(-1).bottom
|
||||||
const y2 = match.highlights.at(-1).top
|
const y2 = match.highlights.at(-1).top
|
||||||
this.drawMissingText(
|
this.drawMissingText(
|
||||||
x,
|
x - scrollX,
|
||||||
y1,
|
y1 - scrollY,
|
||||||
y2,
|
y2 - scrollY,
|
||||||
scale,
|
scale,
|
||||||
replacement.substr(-lengthDiff).trim()
|
replacement.substr(-lengthDiff).trim()
|
||||||
)
|
)
|
||||||
@ -483,7 +488,12 @@ class BesService {
|
|||||||
const x = (rect.left + rect.right) / 2
|
const x = (rect.left + rect.right) / 2
|
||||||
const y1 = rect.top
|
const y1 = rect.top
|
||||||
const y2 = rect.bottom
|
const y2 = rect.bottom
|
||||||
this.drawWrongSpacing(x, y1, y2, scale)
|
this.drawWrongSpacing(
|
||||||
|
x - scrollX,
|
||||||
|
y1 - scrollY,
|
||||||
|
y2 - scrollY,
|
||||||
|
scale
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
for (let rect of this.makeRange(
|
for (let rect of this.makeRange(
|
||||||
match.data,
|
match.data,
|
||||||
@ -491,10 +501,10 @@ class BesService {
|
|||||||
match.match.offset - lengthDiff
|
match.match.offset - lengthDiff
|
||||||
)?.getClientRects())
|
)?.getClientRects())
|
||||||
this.drawExcessiveText(
|
this.drawExcessiveText(
|
||||||
rect.left,
|
rect.left - scrollX,
|
||||||
rect.bottom,
|
rect.bottom - scrollY,
|
||||||
rect.right,
|
rect.right - scrollX,
|
||||||
rect.top
|
rect.top - scrollY
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else if (context.substr(0, replacement.length) === replacement) {
|
} else if (context.substr(0, replacement.length) === replacement) {
|
||||||
@ -513,7 +523,12 @@ class BesService {
|
|||||||
const x = (rect.left + rect.right) / 2
|
const x = (rect.left + rect.right) / 2
|
||||||
const y1 = rect.top
|
const y1 = rect.top
|
||||||
const y2 = rect.bottom
|
const y2 = rect.bottom
|
||||||
this.drawWrongSpacing(x, y1, y2, scale)
|
this.drawWrongSpacing(
|
||||||
|
x - scrollX,
|
||||||
|
y1 - scrollY,
|
||||||
|
y2 - scrollY,
|
||||||
|
scale
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
for (let rect of this.makeRange(
|
for (let rect of this.makeRange(
|
||||||
match.data,
|
match.data,
|
||||||
@ -521,10 +536,10 @@ class BesService {
|
|||||||
match.match.offset + match.match.length
|
match.match.offset + match.match.length
|
||||||
)?.getClientRects())
|
)?.getClientRects())
|
||||||
this.drawExcessiveText(
|
this.drawExcessiveText(
|
||||||
rect.left,
|
rect.left - scrollX,
|
||||||
rect.bottom,
|
rect.bottom - scrollY,
|
||||||
rect.right,
|
rect.right - scrollX,
|
||||||
rect.top
|
rect.top - scrollY
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -544,20 +559,20 @@ class BesService {
|
|||||||
for (let rect of match.highlights) {
|
for (let rect of match.highlights) {
|
||||||
if (first) {
|
if (first) {
|
||||||
this.drawWrongText(
|
this.drawWrongText(
|
||||||
rect.left,
|
rect.left - scrollX,
|
||||||
rect.bottom,
|
rect.bottom - scrollY,
|
||||||
rect.right,
|
rect.right - scrollX,
|
||||||
rect.top,
|
rect.top - scrollY,
|
||||||
scale,
|
scale,
|
||||||
replacement
|
replacement
|
||||||
)
|
)
|
||||||
first = false
|
first = false
|
||||||
} else {
|
} else {
|
||||||
this.drawExcessiveText(
|
this.drawExcessiveText(
|
||||||
rect.left,
|
rect.left - scrollX,
|
||||||
rect.bottom,
|
rect.bottom - scrollY,
|
||||||
rect.right,
|
rect.right - scrollX,
|
||||||
rect.top
|
rect.top - scrollY
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -585,11 +600,22 @@ class BesService {
|
|||||||
if (/^\s+$/.test(toInsert)) {
|
if (/^\s+$/.test(toInsert)) {
|
||||||
const y1 = rects[0].bottom - 2 * scale
|
const y1 = rects[0].bottom - 2 * scale
|
||||||
const y2 = rects[0].top + 2 * scale
|
const y2 = rects[0].top + 2 * scale
|
||||||
this.drawWrongSpacing(x, y1, y2, scale)
|
this.drawWrongSpacing(
|
||||||
|
x - scrollX,
|
||||||
|
y1 - scrollY,
|
||||||
|
y2 - scrollY,
|
||||||
|
scale
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
const y1 = rects[0].bottom
|
const y1 = rects[0].bottom
|
||||||
const y2 = rects[0].top
|
const y2 = rects[0].top
|
||||||
this.drawMissingText(x, y1, y2, scale, toInsert.trim())
|
this.drawMissingText(
|
||||||
|
x - scrollX,
|
||||||
|
y1 - scrollY,
|
||||||
|
y2 - scrollY,
|
||||||
|
scale,
|
||||||
|
toInsert.trim()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
} else if (lengthL + lengthR === replacement.length) {
|
} else if (lengthL + lengthR === replacement.length) {
|
||||||
// Something to remove
|
// Something to remove
|
||||||
@ -603,14 +629,19 @@ class BesService {
|
|||||||
const x = (rects[0].left + rects[0].right) / 2
|
const x = (rects[0].left + rects[0].right) / 2
|
||||||
const y1 = rects[0].top
|
const y1 = rects[0].top
|
||||||
const y2 = rects[0].bottom
|
const y2 = rects[0].bottom
|
||||||
this.drawWrongSpacing(x, y1, y2, scale)
|
this.drawWrongSpacing(
|
||||||
|
x - scrollX,
|
||||||
|
y1 - scrollY,
|
||||||
|
y2 - scrollY,
|
||||||
|
scale
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
for (let rect of rects)
|
for (let rect of rects)
|
||||||
this.drawExcessiveText(
|
this.drawExcessiveText(
|
||||||
rect.left,
|
rect.left - scrollX,
|
||||||
rect.bottom,
|
rect.bottom - scrollY,
|
||||||
rect.right,
|
rect.right - scrollX,
|
||||||
rect.top
|
rect.top - scrollY
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -624,20 +655,20 @@ class BesService {
|
|||||||
for (let rect of rects) {
|
for (let rect of rects) {
|
||||||
if (first) {
|
if (first) {
|
||||||
this.drawWrongText(
|
this.drawWrongText(
|
||||||
rect.left,
|
rect.left - scrollX,
|
||||||
rect.bottom,
|
rect.bottom - scrollY,
|
||||||
rect.right,
|
rect.right - scrollX,
|
||||||
rect.top,
|
rect.top - scrollY,
|
||||||
scale,
|
scale,
|
||||||
toReplace
|
toReplace
|
||||||
)
|
)
|
||||||
first = false
|
first = false
|
||||||
} else {
|
} else {
|
||||||
this.drawExcessiveText(
|
this.drawExcessiveText(
|
||||||
rect.left,
|
rect.left - scrollX,
|
||||||
rect.bottom,
|
rect.bottom - scrollY,
|
||||||
rect.right,
|
rect.right - scrollX,
|
||||||
rect.top
|
rect.top - scrollY
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -653,22 +684,36 @@ class BesService {
|
|||||||
const x2 = rect.right
|
const x2 = rect.right
|
||||||
const y = rect.bottom
|
const y = rect.bottom
|
||||||
const scale = (rect.bottom - rect.top) / 18
|
const scale = (rect.bottom - rect.top) / 18
|
||||||
this.drawAttentionRequired(x1, x2, y, scale)
|
this.drawAttentionRequired(
|
||||||
|
x1 - scrollX,
|
||||||
|
x2 - scrollX,
|
||||||
|
y - scrollY,
|
||||||
|
scale
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
markerY1 = Math.min(...match.highlights.map(rect => rect.top))
|
markerY1 = Math.min(...match.highlights.map(rect => rect.top))
|
||||||
markerY2 = Math.max(...match.highlights.map(rect => rect.bottom))
|
markerY2 = Math.max(...match.highlights.map(rect => rect.bottom))
|
||||||
}
|
}
|
||||||
|
|
||||||
this.drawSideMarker(markerY1, markerY2)
|
this.drawSideMarker(markerY1 - scrollY, markerY2 - scrollY)
|
||||||
|
}
|
||||||
|
|
||||||
|
static commonPrefixLength(s1, s2) {
|
||||||
|
let i = 0
|
||||||
|
let len = Math.min(s1.length, s2.length)
|
||||||
|
while (i < len && s1[i] === s2[i]) i++
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
|
||||||
|
static commonSuffixLength(s1, s2) {
|
||||||
|
let i = 0
|
||||||
|
let i1 = s1.length
|
||||||
|
let i2 = s2.length
|
||||||
|
while (0 < i1-- && 0 < i2-- && s1[i1] === s2[i2]) i++
|
||||||
|
return i
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Draws the marker that helps visualize lines of text where grammar mistakes were detected
|
|
||||||
*
|
|
||||||
* @param {Number} y1 Marker top [px]
|
|
||||||
* @param {Number} y2 Marker bottom [px]
|
|
||||||
*/
|
|
||||||
drawSideMarker(y1, y2) {
|
drawSideMarker(y1, y2) {
|
||||||
const dpr = window.devicePixelRatio
|
const dpr = window.devicePixelRatio
|
||||||
const markerX = this.canvasPanel.width - 5 * dpr
|
const markerX = this.canvasPanel.width - 5 * dpr
|
||||||
@ -678,14 +723,6 @@ class BesService {
|
|||||||
this.ctx.stroke()
|
this.ctx.stroke()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Draws the missing comma sign
|
|
||||||
*
|
|
||||||
* @param {Number} x Sign center [px]
|
|
||||||
* @param {Number} y Sign bottom [px]
|
|
||||||
* @param {Number} scale Sign scale
|
|
||||||
* @param {String} comment Text to display above the marker
|
|
||||||
*/
|
|
||||||
drawMissingComma(x, y, scale, comment) {
|
drawMissingComma(x, y, scale, comment) {
|
||||||
const dpr = window.devicePixelRatio
|
const dpr = window.devicePixelRatio
|
||||||
this.ctx.beginPath()
|
this.ctx.beginPath()
|
||||||
@ -702,14 +739,6 @@ class BesService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Draws the wrong spacing sign. Control direction of chevrons by reversing y1 and y2.
|
|
||||||
*
|
|
||||||
* @param {Number} x Sign center [px]
|
|
||||||
* @param {Number} y1 Sign top/bottom [px]
|
|
||||||
* @param {Number} y2 Sign bottom/top [px]
|
|
||||||
* @param {Number} scale Sign scale
|
|
||||||
*/
|
|
||||||
drawWrongSpacing(x, y1, y2, scale) {
|
drawWrongSpacing(x, y1, y2, scale) {
|
||||||
const dpr = window.devicePixelRatio
|
const dpr = window.devicePixelRatio
|
||||||
this.ctx.beginPath()
|
this.ctx.beginPath()
|
||||||
@ -724,14 +753,6 @@ class BesService {
|
|||||||
this.ctx.stroke()
|
this.ctx.stroke()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Strikes out the excessive text
|
|
||||||
*
|
|
||||||
* @param {Number} x1 Strike line start X [px]
|
|
||||||
* @param {Number} y1 Strike line start Y [px]
|
|
||||||
* @param {Number} x2 Strike line end X [px]
|
|
||||||
* @param {Number} y2 Strike line end Y [px]
|
|
||||||
*/
|
|
||||||
drawExcessiveText(x1, y1, x2, y2) {
|
drawExcessiveText(x1, y1, x2, y2) {
|
||||||
const dpr = window.devicePixelRatio
|
const dpr = window.devicePixelRatio
|
||||||
this.ctx.beginPath()
|
this.ctx.beginPath()
|
||||||
@ -740,16 +761,6 @@ class BesService {
|
|||||||
this.ctx.stroke()
|
this.ctx.stroke()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Strikes out the text and draws the replacement text above
|
|
||||||
*
|
|
||||||
* @param {Number} x1 Strike line start X [px]
|
|
||||||
* @param {Number} y1 Strike line start Y [px]
|
|
||||||
* @param {Number} x2 Strike line end X [px]
|
|
||||||
* @param {Number} y2 Strike line end Y [px]
|
|
||||||
* @param {Number} scale Sign scale
|
|
||||||
* @param {String} text Text to display above
|
|
||||||
*/
|
|
||||||
drawWrongText(x1, y1, x2, y2, scale, text) {
|
drawWrongText(x1, y1, x2, y2, scale, text) {
|
||||||
const dpr = window.devicePixelRatio
|
const dpr = window.devicePixelRatio
|
||||||
this.ctx.beginPath()
|
this.ctx.beginPath()
|
||||||
@ -774,15 +785,6 @@ class BesService {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Draws the sign some text is missing
|
|
||||||
*
|
|
||||||
* @param {Number} x Sign center [px]
|
|
||||||
* @param {Number} y1 Sign bottom [px]
|
|
||||||
* @param {Number} y2 Sign top [px]
|
|
||||||
* @param {Number} scale Sign scale
|
|
||||||
* @param {String} text Text to display above
|
|
||||||
*/
|
|
||||||
drawMissingText(x, y1, y2, scale, text) {
|
drawMissingText(x, y1, y2, scale, text) {
|
||||||
const dpr = window.devicePixelRatio
|
const dpr = window.devicePixelRatio
|
||||||
this.ctx.beginPath()
|
this.ctx.beginPath()
|
||||||
@ -809,14 +811,6 @@ class BesService {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Draws zig-zag line
|
|
||||||
*
|
|
||||||
* @param {Number} x1 Sign left [px]
|
|
||||||
* @param {Number} x2 Sign right [px]
|
|
||||||
* @param {Number} y Sign baseline [px]
|
|
||||||
* @param {Number} scale Sign scale
|
|
||||||
*/
|
|
||||||
drawAttentionRequired(x1, x2, y, scale) {
|
drawAttentionRequired(x1, x2, y, scale) {
|
||||||
const dpr = window.devicePixelRatio
|
const dpr = window.devicePixelRatio
|
||||||
this.ctx.beginPath()
|
this.ctx.beginPath()
|
||||||
@ -830,35 +824,6 @@ class BesService {
|
|||||||
this.ctx.stroke()
|
this.ctx.stroke()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Calculates common string prefix length
|
|
||||||
*
|
|
||||||
* @param {String} s1 First string
|
|
||||||
* @param {String} s2 Second string
|
|
||||||
* @returns Number of characters the beginnings of the strings are equal
|
|
||||||
*/
|
|
||||||
static commonPrefixLength(s1, s2) {
|
|
||||||
let i = 0
|
|
||||||
let len = Math.min(s1.length, s2.length)
|
|
||||||
while (i < len && s1[i] === s2[i]) i++
|
|
||||||
return i
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Calculates common string suffix length
|
|
||||||
*
|
|
||||||
* @param {String} s1 First string
|
|
||||||
* @param {String} s2 Second string
|
|
||||||
* @returns Number of characters the endings of the strings are equal
|
|
||||||
*/
|
|
||||||
static commonSuffixLength(s1, s2) {
|
|
||||||
let i = 0
|
|
||||||
let i1 = s1.length
|
|
||||||
let i2 = s2.length
|
|
||||||
while (0 < i1-- && 0 < i2-- && s1[i1] === s2[i2]) i++
|
|
||||||
return i
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests if given coordinate is inside of a rectangle.
|
* Tests if given coordinate is inside of a rectangle.
|
||||||
*
|
*
|
||||||
@ -875,21 +840,21 @@ class BesService {
|
|||||||
* Creates auxiliary DOM elements for text adornments.
|
* Creates auxiliary DOM elements for text adornments.
|
||||||
*/
|
*/
|
||||||
createCorrectionPanel() {
|
createCorrectionPanel() {
|
||||||
|
this.panelParent = document.createElement('div')
|
||||||
|
this.panelParent.classList.add('bes-correction-panel-parent')
|
||||||
|
|
||||||
this.correctionPanel = document.createElement('div')
|
this.correctionPanel = document.createElement('div')
|
||||||
this.correctionPanel.classList.add('bes-correction-panel')
|
this.correctionPanel.classList.add('bes-correction-panel')
|
||||||
|
|
||||||
this.scrollPanel = document.createElement('div')
|
|
||||||
this.scrollPanel.classList.add('bes-scroll-panel')
|
|
||||||
|
|
||||||
this.canvasPanel = document.createElement('canvas')
|
this.canvasPanel = document.createElement('canvas')
|
||||||
this.canvasPanel.classList.add('bes-canvas')
|
this.canvasPanel.classList.add('bes-canvas')
|
||||||
this.ctx = this.canvasPanel.getContext('2d')
|
this.ctx = this.canvasPanel.getContext('2d')
|
||||||
this.ctx.scale(1, 1)
|
this.ctx.scale(1, 1)
|
||||||
|
|
||||||
this.correctionPanel.appendChild(this.scrollPanel)
|
this.panelParent.appendChild(this.correctionPanel)
|
||||||
this.scrollPanel.appendChild(this.canvasPanel)
|
this.correctionPanel.appendChild(this.canvasPanel)
|
||||||
this.textElement.parentElement.insertBefore(
|
this.textElement.parentElement.insertBefore(
|
||||||
this.correctionPanel,
|
this.panelParent,
|
||||||
this.textElement
|
this.textElement
|
||||||
)
|
)
|
||||||
this.setCorrectionPanelSize()
|
this.setCorrectionPanelSize()
|
||||||
@ -899,7 +864,7 @@ class BesService {
|
|||||||
* Clears auxiliary DOM elements for text adornments.
|
* Clears auxiliary DOM elements for text adornments.
|
||||||
*/
|
*/
|
||||||
clearCorrectionPanel() {
|
clearCorrectionPanel() {
|
||||||
this.correctionPanel.remove()
|
this.panelParent.remove()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -907,11 +872,15 @@ class BesService {
|
|||||||
*/
|
*/
|
||||||
setCorrectionPanelSize() {
|
setCorrectionPanelSize() {
|
||||||
this.disableMutationObserver()
|
this.disableMutationObserver()
|
||||||
|
|
||||||
const styles = window.getComputedStyle(this.hostElement)
|
const styles = window.getComputedStyle(this.hostElement)
|
||||||
this.textFont = styles.fontFamily
|
const hostRect = this.hostElement.getBoundingClientRect()
|
||||||
|
// Sync margins one by one. Firefox is not happy when syncing all at once.
|
||||||
// Resize canvas if needed.
|
this.correctionPanel.style.marginLeft = styles.marginLeft
|
||||||
|
this.correctionPanel.style.marginTop = styles.marginTop
|
||||||
|
this.correctionPanel.style.marginRight = styles.marginRight
|
||||||
|
this.correctionPanel.style.marginBottom = styles.marginBottom
|
||||||
|
this.correctionPanel.style.boxSizing = styles.boxSizing
|
||||||
|
this.correctionPanel.style.scrollBehavior = styles.scrollBehavior
|
||||||
this.canvasPanel.style.width = `${this.hostElement.scrollWidth}px`
|
this.canvasPanel.style.width = `${this.hostElement.scrollWidth}px`
|
||||||
this.canvasPanel.style.height = `${this.hostElement.scrollHeight}px`
|
this.canvasPanel.style.height = `${this.hostElement.scrollHeight}px`
|
||||||
const dpr = window.devicePixelRatio
|
const dpr = window.devicePixelRatio
|
||||||
@ -924,16 +893,8 @@ class BesService {
|
|||||||
) {
|
) {
|
||||||
this.canvasPanel.width = newCanvasWidth
|
this.canvasPanel.width = newCanvasWidth
|
||||||
this.canvasPanel.height = newCanvasHeight
|
this.canvasPanel.height = newCanvasHeight
|
||||||
this.redrawAllMistakeMarkup()
|
this.repositionAllMarkup()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note: Firefox is not happy when syncing all margins at once.
|
|
||||||
this.scrollPanel.style.marginLeft = styles.marginLeft
|
|
||||||
this.scrollPanel.style.marginTop = styles.marginTop
|
|
||||||
this.scrollPanel.style.marginRight = styles.marginRight
|
|
||||||
this.scrollPanel.style.marginBottom = styles.marginBottom
|
|
||||||
this.scrollPanel.style.boxSizing = styles.boxSizing
|
|
||||||
this.scrollPanel.style.scrollBehavior = styles.scrollBehavior
|
|
||||||
if (this.isHostElementInline()) {
|
if (this.isHostElementInline()) {
|
||||||
const totalWidth =
|
const totalWidth =
|
||||||
parseFloat(styles.paddingLeft) +
|
parseFloat(styles.paddingLeft) +
|
||||||
@ -941,14 +902,12 @@ class BesService {
|
|||||||
parseFloat(styles.width) +
|
parseFloat(styles.width) +
|
||||||
parseFloat(styles.marginRight) +
|
parseFloat(styles.marginRight) +
|
||||||
parseFloat(styles.paddingRight)
|
parseFloat(styles.paddingRight)
|
||||||
this.scrollPanel.style.width = `${totalWidth}px`
|
this.correctionPanel.style.width = `${totalWidth}px`
|
||||||
this.scrollPanel.style.height = styles.height
|
this.correctionPanel.style.height = styles.height
|
||||||
} else {
|
} else {
|
||||||
const hostRect = this.hostElement.getBoundingClientRect()
|
this.correctionPanel.style.width = `${hostRect.width}px`
|
||||||
this.scrollPanel.style.width = `${hostRect.width}px`
|
this.correctionPanel.style.height = `${hostRect.height}px`
|
||||||
this.scrollPanel.style.height = `${hostRect.height}px`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.enableMutationObserver()
|
this.enableMutationObserver()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -970,12 +929,11 @@ class BesService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Highlights given grammar mistake.
|
* Removes previously highlighted grammar mistake and highlights new one.
|
||||||
*
|
*
|
||||||
* @param {*} match Grammar checking rule match
|
* @param {*} match Grammar checking rule match
|
||||||
*/
|
*/
|
||||||
highlightMistake(match) {
|
highlightMistake(match) {
|
||||||
const canvasPanelRect = this.canvasPanel.getBoundingClientRect()
|
|
||||||
match.highlights.forEach(rect => {
|
match.highlights.forEach(rect => {
|
||||||
const el = document.createElement('div')
|
const el = document.createElement('div')
|
||||||
el.classList.add('bes-highlight-rect')
|
el.classList.add('bes-highlight-rect')
|
||||||
@ -984,8 +942,8 @@ class BesService {
|
|||||||
? 'bes-highlight-spelling-rect'
|
? 'bes-highlight-spelling-rect'
|
||||||
: 'bes-highlight-grammar-rect'
|
: 'bes-highlight-grammar-rect'
|
||||||
)
|
)
|
||||||
el.style.left = `${rect.x + canvasPanelRect.x + window.scrollX}px`
|
el.style.left = `${rect.x + window.scrollX}px`
|
||||||
el.style.top = `${rect.y + canvasPanelRect.y + window.scrollY}px`
|
el.style.top = `${rect.y + window.scrollY}px`
|
||||||
el.style.width = `${rect.width}px`
|
el.style.width = `${rect.width}px`
|
||||||
el.style.height = `${rect.height}px`
|
el.style.height = `${rect.height}px`
|
||||||
document.body.appendChild(el)
|
document.body.appendChild(el)
|
||||||
@ -1017,12 +975,12 @@ class BesService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Redraws all grammar mistake markup.
|
* Updates all grammar mistake markup positions.
|
||||||
*/
|
*/
|
||||||
redrawAllMistakeMarkup() {
|
repositionAllMarkup() {
|
||||||
this.ctx.clearRect(0, 0, this.canvasPanel.width, this.canvasPanel.height)
|
this.ctx.clearRect(0, 0, this.canvasPanel.width, this.canvasPanel.height)
|
||||||
this.results.forEach(result => {
|
this.results.forEach(result => {
|
||||||
result.matches.forEach(match => this.drawMistakeMarkup(match))
|
result.matches.forEach(match => this.addMistakeMarkup(match))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1178,7 +1136,7 @@ class BesTreeService extends BesService {
|
|||||||
),
|
),
|
||||||
match: match
|
match: match
|
||||||
}
|
}
|
||||||
this.drawMistakeMarkup(m)
|
this.addMistakeMarkup(m)
|
||||||
matches.push(m)
|
matches.push(m)
|
||||||
})
|
})
|
||||||
this.markProofed(node, matches)
|
this.markProofed(node, matches)
|
||||||
@ -1263,7 +1221,7 @@ class BesTreeService extends BesService {
|
|||||||
this.results = this.results.filter(
|
this.results = this.results.filter(
|
||||||
result => !BesTreeService.isSameParagraph(result.element, el)
|
result => !BesTreeService.isSameParagraph(result.element, el)
|
||||||
)
|
)
|
||||||
this.redrawAllMistakeMarkup()
|
this.repositionAllMarkup()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1399,16 +1357,12 @@ class BesTreeService extends BesService {
|
|||||||
const source = event?.detail !== 1 ? event?.detail : event
|
const source = event?.detail !== 1 ? event?.detail : event
|
||||||
const el = this.getBlockParent(source.targetElement || source.target)
|
const el = this.getBlockParent(source.targetElement || source.target)
|
||||||
if (!el) return
|
if (!el) return
|
||||||
const canvasPanelRect = this.canvasPanel.getBoundingClientRect()
|
|
||||||
let x = source.clientX - canvasPanelRect.x
|
|
||||||
let y = source.clientY - canvasPanelRect.y
|
|
||||||
const pointsInRect = []
|
const pointsInRect = []
|
||||||
for (let result of this.results) {
|
for (let result of this.results) {
|
||||||
for (let m of result.matches) {
|
for (let m of result.matches) {
|
||||||
for (let rect of m.highlights) {
|
for (let rect of m.highlights) {
|
||||||
if (BesService.isPointInRect(x, y, rect)) {
|
if (BesService.isPointInRect(source.clientX, source.clientY, rect)) {
|
||||||
pointsInRect.push({ el, match: m })
|
pointsInRect.push({ el, match: m })
|
||||||
break
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1498,7 +1452,7 @@ class BesDOMService extends BesTreeService {
|
|||||||
*/
|
*/
|
||||||
onInput() {
|
onInput() {
|
||||||
// Now that the text is done changing, we can correctly calculate markup position.
|
// Now that the text is done changing, we can correctly calculate markup position.
|
||||||
this.redrawAllMistakeMarkup()
|
this.repositionAllMarkup()
|
||||||
this.dismissPopup()
|
this.dismissPopup()
|
||||||
// Defer grammar-checking to reduce stress on grammar-checking server.
|
// Defer grammar-checking to reduce stress on grammar-checking server.
|
||||||
this.scheduleProofing(1000)
|
this.scheduleProofing(1000)
|
||||||
@ -1580,7 +1534,7 @@ class BesCKService extends BesTreeService {
|
|||||||
// element, it will not be updated immediately.
|
// element, it will not be updated immediately.
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
// Now that the text is done changing, we can correctly calculate markup position.
|
// Now that the text is done changing, we can correctly calculate markup position.
|
||||||
this.redrawAllMistakeMarkup()
|
this.repositionAllMarkup()
|
||||||
|
|
||||||
// Defer grammar-checking to reduce stress on grammar-checking server.
|
// Defer grammar-checking to reduce stress on grammar-checking server.
|
||||||
this.scheduleProofing(1000)
|
this.scheduleProofing(1000)
|
||||||
@ -1747,7 +1701,7 @@ class BesQuillService extends BesTreeService {
|
|||||||
this.clearProofing(domElement)
|
this.clearProofing(domElement)
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.redrawAllMistakeMarkup()
|
this.repositionAllMarkup()
|
||||||
this.scheduleProofing(1000)
|
this.scheduleProofing(1000)
|
||||||
}, 0)
|
}, 0)
|
||||||
}
|
}
|
||||||
@ -1933,7 +1887,7 @@ class BesPlainTextService extends BesService {
|
|||||||
),
|
),
|
||||||
match: match
|
match: match
|
||||||
}
|
}
|
||||||
this.drawMistakeMarkup(m)
|
this.addMistakeMarkup(m)
|
||||||
matches.push(m)
|
matches.push(m)
|
||||||
})
|
})
|
||||||
this.markProofed(paragraphRange, matches)
|
this.markProofed(paragraphRange, matches)
|
||||||
@ -2024,7 +1978,7 @@ class BesPlainTextService extends BesService {
|
|||||||
this.results = this.results.filter(
|
this.results = this.results.filter(
|
||||||
result => !BesPlainTextService.isSameParagraph(result.range, range)
|
result => !BesPlainTextService.isSameParagraph(result.range, range)
|
||||||
)
|
)
|
||||||
this.redrawAllMistakeMarkup()
|
this.repositionAllMarkup()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -2067,16 +2021,12 @@ class BesPlainTextService extends BesService {
|
|||||||
const source = event?.detail !== 1 ? event?.detail : event
|
const source = event?.detail !== 1 ? event?.detail : event
|
||||||
const el = source.targetElement || source.target || this.hostElement
|
const el = source.targetElement || source.target || this.hostElement
|
||||||
if (!el) return
|
if (!el) return
|
||||||
const canvasPanelRect = this.canvasPanel.getBoundingClientRect()
|
|
||||||
let x = source.clientX - canvasPanelRect.x
|
|
||||||
let y = source.clientY - canvasPanelRect.y
|
|
||||||
const pointsInRect = []
|
const pointsInRect = []
|
||||||
for (let result of this.results) {
|
for (let result of this.results) {
|
||||||
for (let m of result.matches) {
|
for (let m of result.matches) {
|
||||||
for (let rect of m.highlights) {
|
for (let rect of m.highlights) {
|
||||||
if (BesService.isPointInRect(x, y, rect)) {
|
if (BesService.isPointInRect(source.clientX, source.clientY, rect)) {
|
||||||
pointsInRect.push({ el: result.range, match: m })
|
pointsInRect.push({ el: result.range, match: m })
|
||||||
break
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2196,7 +2146,7 @@ class BesDOMPlainTextService extends BesPlainTextService {
|
|||||||
delete this.textBeforeChange
|
delete this.textBeforeChange
|
||||||
|
|
||||||
// Now that the text is done changing, we can correctly calculate markup position.
|
// Now that the text is done changing, we can correctly calculate markup position.
|
||||||
this.redrawAllMistakeMarkup()
|
this.repositionAllMarkup()
|
||||||
this.dismissPopup()
|
this.dismissPopup()
|
||||||
// Defer grammar-checking to reduce stress on grammar-checking server.
|
// Defer grammar-checking to reduce stress on grammar-checking server.
|
||||||
this.scheduleProofing(1000)
|
this.scheduleProofing(1000)
|
||||||
@ -2274,6 +2224,9 @@ class BesTAService extends BesPlainTextService {
|
|||||||
*/
|
*/
|
||||||
constructor(hostElement, eventSink) {
|
constructor(hostElement, eventSink) {
|
||||||
super(hostElement, BesTAService.createTextElement(hostElement), eventSink)
|
super(hostElement, BesTAService.createTextElement(hostElement), eventSink)
|
||||||
|
this.textElement.replaceChildren(
|
||||||
|
document.createTextNode(this.hostElement.value)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -2313,7 +2266,6 @@ class BesTAService extends BesPlainTextService {
|
|||||||
static createTextElement(hostElement) {
|
static createTextElement(hostElement) {
|
||||||
const textElement = document.createElement('div')
|
const textElement = document.createElement('div')
|
||||||
textElement.classList.add('bes-text-panel')
|
textElement.classList.add('bes-text-panel')
|
||||||
textElement.replaceChildren(document.createTextNode(hostElement.value))
|
|
||||||
BesTAService.setTextElementSize(hostElement, textElement)
|
BesTAService.setTextElementSize(hostElement, textElement)
|
||||||
hostElement.parentNode.insertBefore(textElement, hostElement)
|
hostElement.parentNode.insertBefore(textElement, hostElement)
|
||||||
return textElement
|
return textElement
|
||||||
@ -2463,7 +2415,7 @@ class BesTAService extends BesPlainTextService {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// Now that the text is done changing, we can correctly calculate markup position.
|
// Now that the text is done changing, we can correctly calculate markup position.
|
||||||
this.redrawAllMistakeMarkup()
|
this.repositionAllMarkup()
|
||||||
|
|
||||||
// Defer grammar-checking to reduce stress on grammar-checking server.
|
// Defer grammar-checking to reduce stress on grammar-checking server.
|
||||||
this.scheduleProofing(1000)
|
this.scheduleProofing(1000)
|
||||||
|
40
styles.css
40
styles.css
@ -22,8 +22,14 @@
|
|||||||
background: rgb(255, 115, 0);
|
background: rgb(255, 115, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.bes-canvas {
|
||||||
|
position: relative;
|
||||||
|
z-index: 3;
|
||||||
|
cursor: text;
|
||||||
|
}
|
||||||
|
|
||||||
/* Styles required to ensure full functionality and optimal user experience. */
|
/* Styles required to ensure full functionality and optimal user experience. */
|
||||||
.bes-correction-panel {
|
.bes-correction-panel-parent {
|
||||||
position: relative;
|
position: relative;
|
||||||
overflow: visible;
|
overflow: visible;
|
||||||
float: left;
|
float: left;
|
||||||
@ -34,7 +40,7 @@
|
|||||||
z-index: 1;
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bes-scroll-panel {
|
.bes-correction-panel {
|
||||||
position: relative;
|
position: relative;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
border-color: transparent;
|
border-color: transparent;
|
||||||
@ -42,12 +48,6 @@
|
|||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bes-canvas {
|
|
||||||
position: relative;
|
|
||||||
z-index: 3;
|
|
||||||
cursor: text;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bes-text-panel {
|
.bes-text-panel {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
@ -56,3 +56,27 @@
|
|||||||
border-color: transparent;
|
border-color: transparent;
|
||||||
background: none;
|
background: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* TODO: Styles below should be removed after testing period is over */
|
||||||
|
.resizable {
|
||||||
|
overflow-x: scroll;
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
width: 300px;
|
||||||
|
height: 400px;
|
||||||
|
border-radius: 3px;
|
||||||
|
background-color: #f9f9f9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mock-content {
|
||||||
|
width: 1000px;
|
||||||
|
height: 600px;
|
||||||
|
background-color: #f9f9f9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flex {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user