Compare commits

..

4 Commits

Author SHA1 Message Date
c27f9628f4 Fine-tune markup drawing
This adjusts markup color slightly for the dark display, makes it fully
opaque, alternates the squiggly line between grammar and spelling
mistakes to visualize one-word-two-mistake-types, paints most important
mistakes last/on top etc.

All to make the markup as visible as possible.
2025-03-04 11:09:57 +01:00
ef0d35ccee Cleanup 2025-03-04 10:49:12 +01:00
2b54735175 Reformat code 2025-03-04 09:21:51 +01:00
9c2151f182 Add clickable tolerance around grammar mistakes
Users are complaining it is hard to make a click on a mistake when
mistake is covering relative small portion of the text.
2025-03-04 09:21:35 +01:00
7 changed files with 90 additions and 48 deletions

View File

@ -91,8 +91,8 @@ Kategorije pravopisnih pravil so:
Privzeto servis uporablja podčrtovanje pravopisnih napak (nastavitev `'underline'`). Videz lahko spreminjamo. Privzeto servis uporablja podčrtovanje pravopisnih napak (nastavitev `'underline'`). Videz lahko spreminjamo.
<img src="samples/markup_underline.png" alt="underline" width="448"/> <img src="samples/images/markup_underline.png" alt="underline" width="448"/>
<img src="samples/markup_lector.png" alt="lector" width="448"/> <img src="samples/images/markup_lector.png" alt="lector" width="448"/>
Levo `'underline'`, desno `'lector'`. Levo `'underline'`, desno `'lector'`.

Binary file not shown.

After

Width:  |  Height:  |  Size: 201 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 179 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 174 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 151 KiB

View File

@ -371,13 +371,19 @@ class BesService {
if (match.highlights.length === 0) return if (match.highlights.length === 0) return
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
let amplitude = 0
const ruleId = match.match.rule.id const ruleId = match.match.rule.id
this.ctx.strokeStyle = ruleId.startsWith('MORFOLOGIK_RULE') if (ruleId.startsWith('MORFOLOGIK_RULE')) {
? 'rgba(0, 123, 255, 0.8)' const styles = window.getComputedStyle(this.highlightSpelling)
: 'rgba(255, 115, 0, 0.8)' this.ctx.strokeStyle = styles.color
this.ctx.fillStyle = ruleId.startsWith('MORFOLOGIK_RULE') this.ctx.fillStyle = styles.color
? 'rgba(0, 123, 255, 0.8)' amplitude = -1
: 'rgba(255, 115, 0, 0.8)' } else {
const styles = window.getComputedStyle(this.highlightGrammar)
this.ctx.strokeStyle = styles.color
this.ctx.fillStyle = styles.color
amplitude = 1
}
let markerY1, markerY2 let markerY1, markerY2
switch (this.markupStyle) { switch (this.markupStyle) {
case 'lector': case 'lector':
@ -681,7 +687,7 @@ 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, x2, y, amplitude, scale)
} }
markerY1 = Math.min(...match.highlights.map(rect => rect.top)) markerY1 = Math.min(...match.highlights.map(rect => rect.top))
@ -865,17 +871,18 @@ class BesService {
* @param {Number} x1 Sign left [px] * @param {Number} x1 Sign left [px]
* @param {Number} x2 Sign right [px] * @param {Number} x2 Sign right [px]
* @param {Number} y Sign baseline [px] * @param {Number} y Sign baseline [px]
* @param {Number} amplitude Sign amplitude [px]
* @param {Number} scale Sign scale * @param {Number} scale Sign scale
*/ */
drawAttentionRequired(x1, x2, y, scale) { drawAttentionRequired(x1, x2, y, amplitude, scale) {
const dpr = window.devicePixelRatio const dpr = window.devicePixelRatio
this.ctx.beginPath() this.ctx.beginPath()
this.ctx.moveTo(x1 * dpr, (y - scale) * dpr) this.ctx.moveTo(x1 * dpr, (y - amplitude * scale) * dpr)
for (let x = x1; ; ) { for (let x = x1; ; ) {
if (x >= x2) break if (x >= x2) break
this.ctx.lineTo((x += 2 * scale) * dpr, (y + scale) * dpr) this.ctx.lineTo((x += 2 * scale) * dpr, (y + amplitude * scale) * dpr)
if (x >= x2) break if (x >= x2) break
this.ctx.lineTo((x += 2 * scale) * dpr, (y - scale) * dpr) this.ctx.lineTo((x += 2 * scale) * dpr, (y - amplitude * scale) * dpr)
} }
this.ctx.stroke() this.ctx.stroke()
} }
@ -886,10 +893,11 @@ class BesService {
* @param {Number} scale Sign scale * @param {Number} scale Sign scale
* @param {Number} dpr Device pixel ratio * @param {Number} dpr Device pixel ratio
*/ */
setCtxFont(scale, dpr) setCtxFont(scale, dpr) {
{
const styles = window.getComputedStyle(this.canvasPanel) const styles = window.getComputedStyle(this.canvasPanel)
this.ctx.font = `${styles.fontStyle} ${styles.fontWeight} ${14 * scale * dpr}px ${styles.fontFamily}` this.ctx.font = `${styles.fontStyle} ${styles.fontWeight} ${
14 * scale * dpr
}px ${styles.fontFamily}`
} }
/** /**
@ -944,10 +952,16 @@ class BesService {
* @param {Number} x X coordinate * @param {Number} x X coordinate
* @param {Number} y Y coordinate * @param {Number} y Y coordinate
* @param {DOMRect} rect Rectangle * @param {DOMRect} rect Rectangle
* @param {Number} tolerance Extra margin around the rectangle treated as "inside"
* @returns * @returns
*/ */
static isPointInRect(x, y, rect) { static isPointInRect(x, y, rect, tolerance) {
return rect.left <= x && x < rect.right && rect.top <= y && y < rect.bottom return (
rect.left - tolerance <= x &&
x < rect.right + tolerance &&
rect.top - tolerance <= y &&
y < rect.bottom + tolerance
)
} }
/** /**
@ -965,7 +979,16 @@ class BesService {
this.ctx = this.canvasPanel.getContext('2d') this.ctx = this.canvasPanel.getContext('2d')
this.ctx.scale(1, 1) this.ctx.scale(1, 1)
this.highlightSpelling = document.createElement('div')
this.highlightSpelling.classList.add('bes-highlight-placeholder')
this.highlightSpelling.classList.add('bes-highlight-spelling')
this.highlightGrammar = document.createElement('div')
this.highlightGrammar.classList.add('bes-highlight-placeholder')
this.highlightGrammar.classList.add('bes-highlight-grammar')
this.correctionPanel.appendChild(this.scrollPanel) this.correctionPanel.appendChild(this.scrollPanel)
this.correctionPanel.appendChild(this.highlightSpelling)
this.correctionPanel.appendChild(this.highlightGrammar)
this.scrollPanel.appendChild(this.canvasPanel) this.scrollPanel.appendChild(this.canvasPanel)
this.textElement.parentElement.insertBefore( this.textElement.parentElement.insertBefore(
this.correctionPanel, this.correctionPanel,
@ -1033,14 +1056,14 @@ class BesService {
/** /**
* Prepares and displays popup. * Prepares and displays popup.
* *
* @param {*} elMatch Array containing block element/paragraph containing grammar checking rule match and a match * @param {*} hits Array containing block element/paragraph containing grammar checking rule match and a match
* @param {PointerEvent} source Click event source * @param {PointerEvent} source Click event source
*/ */
preparePopup(elMatch, source) { preparePopup(hits, source) {
this.dismissPopup() this.dismissPopup()
const popup = document.querySelector('bes-popup-el') const popup = document.querySelector('bes-popup-el')
BesPopup.clearReplacements() BesPopup.clearReplacements()
elMatch.forEach(({ el, match }) => { hits.forEach(({ el, match }) => {
popup.setContent(el, match, this, this.isContentEditable()) popup.setContent(el, match, this, this.isContentEditable())
this.highlightMistake(match) this.highlightMistake(match)
}) })
@ -1059,8 +1082,8 @@ class BesService {
el.classList.add('bes-highlight-rect') el.classList.add('bes-highlight-rect')
el.classList.add( el.classList.add(
match.match.rule.id.startsWith('MORFOLOGIK_RULE') match.match.rule.id.startsWith('MORFOLOGIK_RULE')
? 'bes-highlight-spelling-rect' ? 'bes-highlight-spelling'
: 'bes-highlight-grammar-rect' : 'bes-highlight-grammar'
) )
el.style.left = `${rect.x + canvasPanelRect.x + window.scrollX}px` el.style.left = `${rect.x + canvasPanelRect.x + window.scrollX}px`
el.style.top = `${rect.y + canvasPanelRect.y + window.scrollY}px` el.style.top = `${rect.y + canvasPanelRect.y + window.scrollY}px`
@ -1100,7 +1123,9 @@ class BesService {
redrawAllMistakeMarkup() { redrawAllMistakeMarkup() {
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)) // Most important matches are first, we want to draw them last => iterate in reverse.
for (let i = result.matches.length; i-- > 0; )
this.drawMistakeMarkup(result.matches[i])
}) })
} }
@ -1256,9 +1281,11 @@ class BesTreeService extends BesService {
), ),
match: match match: match
} }
this.drawMistakeMarkup(m)
matches.push(m) matches.push(m)
}) })
// Most important matches are first, we want to draw them last => iterate in reverse.
for (let i = matches.length; i-- > 0; )
this.drawMistakeMarkup(matches[i])
this.markProofed(node, matches) this.markProofed(node, matches)
this.onProofingProgress(matches.length) this.onProofingProgress(matches.length)
}) })
@ -1514,19 +1541,19 @@ class BesTreeService extends BesService {
const canvasPanelRect = this.canvasPanel.getBoundingClientRect() const canvasPanelRect = this.canvasPanel.getBoundingClientRect()
let x = source.clientX - canvasPanelRect.x let x = source.clientX - canvasPanelRect.x
let y = source.clientY - canvasPanelRect.y let y = source.clientY - canvasPanelRect.y
const pointsInRect = [] const hits = []
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(x, y, rect, 5)) {
pointsInRect.push({ el, match: m }) hits.push({ el, match: m })
break break
} }
} }
} }
} }
this.dismissPopup() this.dismissPopup()
if (pointsInRect.length) this.preparePopup(pointsInRect, source) if (hits.length) this.preparePopup(hits, source)
} }
} }
@ -2045,9 +2072,11 @@ class BesPlainTextService extends BesService {
), ),
match: match match: match
} }
this.drawMistakeMarkup(m)
matches.push(m) matches.push(m)
}) })
// Most important matches are first, we want to draw them last => iterate in reverse.
for (let i = matches.length; i-- > 0; )
this.drawMistakeMarkup(matches[i])
this.markProofed(paragraphRange, matches) this.markProofed(paragraphRange, matches)
this.onProofingProgress(matches.length) this.onProofingProgress(matches.length)
}) })
@ -2182,19 +2211,19 @@ class BesPlainTextService extends BesService {
const canvasPanelRect = this.canvasPanel.getBoundingClientRect() const canvasPanelRect = this.canvasPanel.getBoundingClientRect()
let x = source.clientX - canvasPanelRect.x let x = source.clientX - canvasPanelRect.x
let y = source.clientY - canvasPanelRect.y let y = source.clientY - canvasPanelRect.y
const pointsInRect = [] const hits = []
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(x, y, rect, 5)) {
pointsInRect.push({ el: result.range, match: m }) hits.push({ el: result.range, match: m })
break break
} }
} }
} }
} }
this.dismissPopup() this.dismissPopup()
if (pointsInRect.length) this.preparePopup(pointsInRect, source) if (hits.length) this.preparePopup(hits, source)
} }
/** /**
@ -2467,11 +2496,13 @@ class BesTAService extends BesPlainTextService {
parseFloat(styles.paddingRight) - parseFloat(styles.paddingRight) -
parseFloat(styles.borderRightWidth) parseFloat(styles.borderRightWidth)
}px` }px`
textElement.style.height = `${rect.height - textElement.style.height = `${
rect.height -
parseFloat(styles.borderTopWidth) - parseFloat(styles.borderTopWidth) -
parseFloat(styles.paddingTop) - parseFloat(styles.paddingTop) -
parseFloat(styles.paddingBottom) - parseFloat(styles.paddingBottom) -
parseFloat(styles.borderBottomWidth)}px` parseFloat(styles.borderBottomWidth)
}px`
} }
/** /**

View File

@ -1,23 +1,29 @@
/* Mistake types styles */ /* Mistake types styles */
.bes-spelling-mistake {
border-bottom: 2px solid #ff7300;
position: absolute;
z-index: 3;
cursor: text;
}
.bes-highlight-rect { .bes-highlight-rect {
position: absolute; position: absolute;
opacity: 0.3;
cursor: text; cursor: text;
} }
.bes-highlight-spelling-rect { .bes-highlight-spelling {
background: rgb(0, 123, 255); color: hsl(211, 100%, 60%);
background: hsla(211, 100%, 60%, 0.3);
} }
.bes-highlight-grammar-rect { .bes-highlight-grammar {
background: rgb(255, 115, 0); color: hsl(27, 100%, 45%);
background: hsla(27, 100%, 45%, 0.3);
}
@media (prefers-color-scheme: dark) {
.bes-highlight-spelling {
color: hsl(211, 100%, 55%);
background: hsla(211, 100%, 55%, 0.3);
}
.bes-highlight-grammar {
color: hsl(27, 100%, 65%);
background: hsla(27, 100%, 65%, 0.3);
}
} }
/* Styles required to ensure full functionality and optimal user experience. */ /* Styles required to ensure full functionality and optimal user experience. */
@ -54,3 +60,8 @@
background: none; background: none;
z-index: -1; z-index: -1;
} }
.bes-highlight-placeholder {
display: none;
visibility: hidden;
}