Compare commits
24 Commits
24216a4dff
...
master
Author | SHA1 | Date | |
---|---|---|---|
d3deb4cb11 | |||
3cd86bf4c3 | |||
a04ffb3e70 | |||
e4ba4dd3f1 | |||
b99d233abc | |||
04cd5f2e7d | |||
1163b3c47e | |||
e903917179 | |||
0f2fa218f3 | |||
99db143007 | |||
c7c90101a2 | |||
|
b9ab9b6a64 | ||
|
c67adcdc99 | ||
be08136a31 | |||
9bc8dfbdfc | |||
5cbac62de3 | |||
c27f9628f4 | |||
ef0d35ccee | |||
2b54735175 | |||
9c2151f182 | |||
a507f24326 | |||
20713b8b5d | |||
72b6fb2d91 | |||
9815ddfed0 |
@@ -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'`.
|
||||||
|
|
||||||
|
BIN
samples/images/markup_lector.png
Normal file
BIN
samples/images/markup_lector.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 201 KiB |
BIN
samples/images/markup_underline.png
Normal file
BIN
samples/images/markup_underline.png
Normal file
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 |
@@ -1,3 +1,14 @@
|
|||||||
|
body {
|
||||||
|
color: #000;
|
||||||
|
background-color: #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
input, textarea {
|
||||||
|
color: #000;
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
.my-block {
|
.my-block {
|
||||||
max-width: 500px;
|
max-width: 500px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
@@ -13,7 +24,6 @@
|
|||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
background-color: #f5f5f5;
|
background-color: #f5f5f5;
|
||||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
||||||
line-height: 20px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.ck-editor__editable {
|
.ck-editor__editable {
|
||||||
@@ -52,3 +62,22 @@
|
|||||||
.bes-status-icon.bes-status-mistakes {
|
.bes-status-icon.bes-status-mistakes {
|
||||||
background-image: url('images/mistake-svgrepo-com.svg');
|
background-image: url('images/mistake-svgrepo-com.svg');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
body {
|
||||||
|
color: #eee;
|
||||||
|
background-color: #444;
|
||||||
|
font-weight: bolder;
|
||||||
|
}
|
||||||
|
|
||||||
|
input, textarea {
|
||||||
|
color: #eee;
|
||||||
|
background-color: #222;
|
||||||
|
box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.my-control {
|
||||||
|
background-color: #222;
|
||||||
|
box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
406
service.js
406
service.js
@@ -1,4 +1,5 @@
|
|||||||
// TODO: Research if there is a way to disable languageTool & Grammarly extensions in CKEditor
|
// TODO: Research if there is a way to disable languageTool & Grammarly extensions in CKEditor
|
||||||
|
// TODO: Add mutation observer should any style of hostElement/textElement change and repaint markup (e.g. notice font-weight difference when toggling light/dark color-scheme)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Collection of all grammar checking services in the document
|
* Collection of all grammar checking services in the document
|
||||||
@@ -59,10 +60,11 @@ class BesService {
|
|||||||
this.hostElement.setAttribute('data-gramm', 'false')
|
this.hostElement.setAttribute('data-gramm', 'false')
|
||||||
this.hostElement.setAttribute('data-gramm_editor', 'false')
|
this.hostElement.setAttribute('data-gramm_editor', 'false')
|
||||||
this.hostElement.setAttribute('data-enable-grammarly', 'false')
|
this.hostElement.setAttribute('data-enable-grammarly', 'false')
|
||||||
this.textFont = window.getComputedStyle(this.hostElement).fontFamily
|
|
||||||
|
|
||||||
this.onScroll = this.onScroll.bind(this)
|
this.onScroll = this.onScroll.bind(this)
|
||||||
this.hostElement.addEventListener('scroll', this.onScroll)
|
this.hostElement.addEventListener('scroll', this.onScroll)
|
||||||
|
this.onShortcutNavigation = this.onShortcutNavigation.bind(this)
|
||||||
|
this.hostElement.addEventListener('keydown', this.onShortcutNavigation)
|
||||||
|
|
||||||
this.hostBoundingClientRect = this.hostElement.getBoundingClientRect()
|
this.hostBoundingClientRect = this.hostElement.getBoundingClientRect()
|
||||||
this.mutationObserver = new MutationObserver(this.onBodyMutate.bind(this))
|
this.mutationObserver = new MutationObserver(this.onBodyMutate.bind(this))
|
||||||
@@ -109,6 +111,7 @@ class BesService {
|
|||||||
if (this.abortController) this.abortController.abort()
|
if (this.abortController) this.abortController.abort()
|
||||||
besServices = besServices.filter(item => item !== this)
|
besServices = besServices.filter(item => item !== this)
|
||||||
this.mutationObserver.disconnect()
|
this.mutationObserver.disconnect()
|
||||||
|
this.hostElement.removeEventListener('keydown', this.onShortcutNavigation)
|
||||||
this.hostElement.removeEventListener('scroll', this.onScroll)
|
this.hostElement.removeEventListener('scroll', this.onScroll)
|
||||||
this.hostElement.setAttribute('spellcheck', this.originalSpellcheck)
|
this.hostElement.setAttribute('spellcheck', this.originalSpellcheck)
|
||||||
this.hostElement.setAttribute('data-gramm', this.originalDataGramm)
|
this.hostElement.setAttribute('data-gramm', this.originalDataGramm)
|
||||||
@@ -276,6 +279,9 @@ class BesService {
|
|||||||
*/
|
*/
|
||||||
onProofingProgress(numberOfMatches) {
|
onProofingProgress(numberOfMatches) {
|
||||||
this.proofingMatches += numberOfMatches
|
this.proofingMatches += numberOfMatches
|
||||||
|
// Sorting the array here is preferable to sorting only in onEndProofing.This way it allows users to interact
|
||||||
|
// with and navigate newly detected mistakes as soon as they appear.
|
||||||
|
this.sortMatchesArray()
|
||||||
if (this.eventSink && 'proofingProgress' in this.eventSink)
|
if (this.eventSink && 'proofingProgress' in this.eventSink)
|
||||||
this.eventSink.proofingProgress(this)
|
this.eventSink.proofingProgress(this)
|
||||||
if (--this.proofingCount <= 0) this.onEndProofing()
|
if (--this.proofingCount <= 0) this.onEndProofing()
|
||||||
@@ -324,6 +330,46 @@ class BesService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called to report keydown event
|
||||||
|
*
|
||||||
|
* @param {Event} e
|
||||||
|
*/
|
||||||
|
onShortcutNavigation(e) {
|
||||||
|
switch (e.code) {
|
||||||
|
case 'BracketLeft':
|
||||||
|
if (e.ctrlKey) {
|
||||||
|
// Handle Ctrl + [ OR Ctrl + Š
|
||||||
|
e.preventDefault()
|
||||||
|
e.stopPropagation()
|
||||||
|
BesService.findNextMistake(this, -1)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case 'BracketRight':
|
||||||
|
if (e.ctrlKey) {
|
||||||
|
// Handle Ctrl + ] OR Ctrl + Đ
|
||||||
|
e.preventDefault()
|
||||||
|
e.stopPropagation()
|
||||||
|
BesService.findNextMistake(this, 1)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case 'Enter':
|
||||||
|
// Handle Ctrl + Enter
|
||||||
|
if (e.ctrlKey) {
|
||||||
|
if (!this.highlightElements.length) return
|
||||||
|
e.preventDefault()
|
||||||
|
e.stopPropagation()
|
||||||
|
this.acceptReplacement()
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case 'Escape':
|
||||||
|
this.dismissPopup()
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called to report repositioning
|
* Called to report repositioning
|
||||||
*/
|
*/
|
||||||
@@ -371,13 +417,24 @@ 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 if (ruleId === 'BESANA_178' /*PR_VNAP_POPRAVEK_UI*/) {
|
||||||
|
const styles = window.getComputedStyle(this.highlightAI)
|
||||||
|
this.ctx.strokeStyle = styles.color
|
||||||
|
this.ctx.fillStyle = styles.color
|
||||||
|
amplitude = 1
|
||||||
|
} 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 +738,9 @@ 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)
|
if (ruleId !== 'MORFOLOGIK_RULE') {
|
||||||
|
this.drawDoubleUnderline(x1, x2, y, scale)
|
||||||
|
} else 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))
|
||||||
@@ -723,7 +782,7 @@ class BesService {
|
|||||||
this.ctx.stroke()
|
this.ctx.stroke()
|
||||||
|
|
||||||
if (comment) {
|
if (comment) {
|
||||||
this.ctx.font = `${12 * scale * dpr}px ${this.textFont}`
|
this.setCtxFont(scale, dpr)
|
||||||
this.ctx.textAlign = 'center'
|
this.ctx.textAlign = 'center'
|
||||||
this.ctx.textBaseline = 'bottom'
|
this.ctx.textBaseline = 'bottom'
|
||||||
this.ctx.fillText('?', (x + 2 * scale) * dpr, (y - 6 * scale) * dpr)
|
this.ctx.fillText('?', (x + 2 * scale) * dpr, (y - 6 * scale) * dpr)
|
||||||
@@ -807,7 +866,7 @@ class BesService {
|
|||||||
this.ctx.lineTo(x2 * dpr, (y2 + 6 * scale) * dpr)
|
this.ctx.lineTo(x2 * dpr, (y2 + 6 * scale) * dpr)
|
||||||
this.ctx.stroke()
|
this.ctx.stroke()
|
||||||
|
|
||||||
this.ctx.font = `${12 * scale * dpr}px ${this.textFont}`
|
this.setCtxFont(scale, dpr)
|
||||||
this.ctx.textAlign = 'left' // Thou we want the text to be centered, we align it manually to prevent it getting off canvas.
|
this.ctx.textAlign = 'left' // Thou we want the text to be centered, we align it manually to prevent it getting off canvas.
|
||||||
this.ctx.textBaseline = 'bottom'
|
this.ctx.textBaseline = 'bottom'
|
||||||
const textMetrics = this.ctx.measureText(text)
|
const textMetrics = this.ctx.measureText(text)
|
||||||
@@ -842,7 +901,7 @@ class BesService {
|
|||||||
this.ctx.lineTo(x * dpr, (y2 + 6 * scale) * dpr)
|
this.ctx.lineTo(x * dpr, (y2 + 6 * scale) * dpr)
|
||||||
this.ctx.stroke()
|
this.ctx.stroke()
|
||||||
|
|
||||||
this.ctx.font = `${12 * scale * dpr}px ${this.textFont}`
|
this.setCtxFont(scale, dpr)
|
||||||
this.ctx.textAlign = 'left' // Thou we want the text to be centered, we align it manually to prevent it getting off canvas.
|
this.ctx.textAlign = 'left' // Thou we want the text to be centered, we align it manually to prevent it getting off canvas.
|
||||||
this.ctx.textBaseline = 'bottom'
|
this.ctx.textBaseline = 'bottom'
|
||||||
const textMetrics = this.ctx.measureText(text)
|
const textMetrics = this.ctx.measureText(text)
|
||||||
@@ -865,21 +924,52 @@ 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()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {Number} x1 Sign left [px]
|
||||||
|
* @param {Number} x2 Sign right [px]
|
||||||
|
* @param {Number} y Sign baseline [px]
|
||||||
|
* @param {Number} scale Sign scale
|
||||||
|
*/
|
||||||
|
drawDoubleUnderline(x1, x2, y, scale) {
|
||||||
|
const dpr = window.devicePixelRatio
|
||||||
|
this.ctx.beginPath()
|
||||||
|
this.ctx.moveTo(x1 * dpr, (y - 2 * scale) * dpr)
|
||||||
|
this.ctx.lineTo(x2 * dpr, (y - 2 * scale) * dpr)
|
||||||
|
this.ctx.moveTo(x1 * dpr, (y + 1 * scale) * dpr)
|
||||||
|
this.ctx.lineTo(x2 * dpr, (y + 1 * scale) * dpr)
|
||||||
|
this.ctx.stroke()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets markup font
|
||||||
|
*
|
||||||
|
* @param {Number} scale Sign scale
|
||||||
|
* @param {Number} dpr Device pixel ratio
|
||||||
|
*/
|
||||||
|
setCtxFont(scale, dpr) {
|
||||||
|
const styles = window.getComputedStyle(this.canvasPanel)
|
||||||
|
this.ctx.font = `${styles.fontStyle} ${styles.fontWeight} ${
|
||||||
|
14 * scale * dpr
|
||||||
|
}px ${styles.fontFamily}`
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calculates rectangles covering a given range and compensates for scroll offset
|
* Calculates rectangles covering a given range and compensates for scroll offset
|
||||||
*
|
*
|
||||||
@@ -932,10 +1022,21 @@ 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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
static arrowBtnNavigation(value, service) {
|
||||||
|
const direction = value === 'forward' ? 1 : value === 'back' ? -1 : 0
|
||||||
|
BesService.findNextMistake(service, direction)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -953,7 +1054,20 @@ 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.highlightAI = document.createElement('div')
|
||||||
|
this.highlightAI.classList.add('bes-highlight-placeholder')
|
||||||
|
this.highlightAI.classList.add('bes-highlight-ai')
|
||||||
|
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.highlightAI)
|
||||||
|
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,
|
||||||
@@ -999,8 +1113,6 @@ class BesService {
|
|||||||
this.scrollPanel.style.height = `${hostRect.height}px`
|
this.scrollPanel.style.height = `${hostRect.height}px`
|
||||||
}
|
}
|
||||||
|
|
||||||
this.textFont = styles.fontFamily
|
|
||||||
|
|
||||||
// Resize canvas if needed.
|
// Resize canvas if needed.
|
||||||
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`
|
||||||
@@ -1023,18 +1135,24 @@ 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)
|
||||||
|
const containerRect = this.hostElement.getBoundingClientRect()
|
||||||
|
match.highlights.forEach(rect => {
|
||||||
|
const clientX = rect.x + containerRect.left
|
||||||
|
const clientY =
|
||||||
|
rect.y + containerRect.top + rect.height - this.hostElement.scrollTop
|
||||||
|
popup.show(clientX, clientY, this)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
popup.show(source.clientX, source.clientY)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1049,27 +1167,123 @@ 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'
|
: match.match.rule.id === 'BESANA_178' /*PR_VNAP_POPRAVEK_UI*/
|
||||||
|
? 'bes-highlight-ai'
|
||||||
|
: '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`
|
||||||
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)
|
||||||
this.highlightElements.push(el)
|
const matchSorted =
|
||||||
|
this.sortedMatches.find(entry => entry.match === match) || null
|
||||||
|
this.highlightElements.push({ el, matchSorted })
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function finds the next/previous mistake.
|
||||||
|
* @param {Number} direction Navigation direction: 1 for next, -1 for previous
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
static findNextMistake(service, direction = 1) {
|
||||||
|
if (!service || !service.sortedMatches || !service.sortedMatches.length)
|
||||||
|
return
|
||||||
|
const active = service.highlightElements.find(
|
||||||
|
({ matchSorted }) => matchSorted
|
||||||
|
)
|
||||||
|
let current = -1
|
||||||
|
if (active && active.matchSorted) {
|
||||||
|
current = service.sortedMatches.findIndex(
|
||||||
|
entry => entry.match === active.matchSorted.match
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const len = service.sortedMatches.length
|
||||||
|
const next = (current + direction + len) % len
|
||||||
|
service.activeMatchIndex = next
|
||||||
|
const { el, match } = service.sortedMatches[next]
|
||||||
|
|
||||||
|
if (el && typeof el.scrollIntoView === 'function') {
|
||||||
|
el.scrollIntoView({ behavior: 'instant', block: 'center' })
|
||||||
|
}
|
||||||
|
// Not the cleanest solution to setTimeout()
|
||||||
|
setTimeout(() => {
|
||||||
|
service.dismissPopup()
|
||||||
|
const popup = document.querySelector('bes-popup-el')
|
||||||
|
BesPopup.clearReplacements()
|
||||||
|
popup.setContent(el, match, service, service.isContentEditable())
|
||||||
|
service.highlightMistake(match)
|
||||||
|
const containerRect = service.hostElement.getBoundingClientRect()
|
||||||
|
match.highlights.forEach(rect => {
|
||||||
|
const clientX = rect.x + containerRect.left
|
||||||
|
const clientY =
|
||||||
|
rect.y +
|
||||||
|
containerRect.top +
|
||||||
|
rect.height -
|
||||||
|
service.hostElement.scrollTop
|
||||||
|
popup.show(clientX, clientY, service)
|
||||||
|
})
|
||||||
|
}, 150)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Accepts the replacement for the current grammar mistake.
|
||||||
|
*/
|
||||||
|
acceptReplacement() {
|
||||||
|
const popup = document.querySelector('bes-popup-el')
|
||||||
|
const replacementDiv = popup.shadowRoot.querySelector(
|
||||||
|
'.bes-replacement-div'
|
||||||
|
)
|
||||||
|
const firstReplacement = replacementDiv?.firstChild
|
||||||
|
if (replacementDiv.childElementCount === 1) {
|
||||||
|
// Is this an ugly solution?
|
||||||
|
firstReplacement.click()
|
||||||
|
} else if (replacementDiv.childElementCount > 1) {
|
||||||
|
firstReplacement.focus()
|
||||||
|
} else BesService.findNextMistake(this, 1)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clears highlight and hides popup
|
* Clears highlight and hides popup
|
||||||
*/
|
*/
|
||||||
dismissPopup() {
|
dismissPopup() {
|
||||||
BesPopup.hide()
|
BesPopup.hide()
|
||||||
this.highlightElements.forEach(el => el.remove())
|
this.highlightElements.forEach(obj => {
|
||||||
|
if (obj.el && typeof obj.el.remove === 'function') {
|
||||||
|
obj.el.remove()
|
||||||
|
}
|
||||||
|
})
|
||||||
this.highlightElements = []
|
this.highlightElements = []
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function collects all matches from the results array, flattens them into a single array,
|
||||||
|
* and sorts them in order: first by their Y axis, then by X axis.
|
||||||
|
*/
|
||||||
|
sortMatchesArray() {
|
||||||
|
this.sortedMatches = []
|
||||||
|
this.results.forEach(element => {
|
||||||
|
element.matches.forEach(match => {
|
||||||
|
if (!match.highlights || !match.highlights.length) return
|
||||||
|
this.sortedMatches.push({
|
||||||
|
el: element.element,
|
||||||
|
match,
|
||||||
|
top: match.highlights[0].top
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
this.sortedMatches.sort((a, b) => {
|
||||||
|
if (a.top !== b.top) return a.top - b.top
|
||||||
|
|
||||||
|
const aLeft = a.match.highlights[0].left
|
||||||
|
const bLeft = b.match.highlights[0].left
|
||||||
|
return aLeft - bLeft
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if host element content is editable.
|
* Checks if host element content is editable.
|
||||||
*
|
*
|
||||||
@@ -1090,7 +1304,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])
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1246,9 +1462,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)
|
||||||
})
|
})
|
||||||
@@ -1504,19 +1722,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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1820,6 +2038,7 @@ class BesQuillService extends BesTreeService {
|
|||||||
onChangeData(delta) {
|
onChangeData(delta) {
|
||||||
let index = 0
|
let index = 0
|
||||||
let reproofNeeded = false
|
let reproofNeeded = false
|
||||||
|
const affectedBlocks = new Set()
|
||||||
|
|
||||||
delta.ops.forEach(op => {
|
delta.ops.forEach(op => {
|
||||||
if (op.retain) {
|
if (op.retain) {
|
||||||
@@ -1829,11 +2048,12 @@ class BesQuillService extends BesTreeService {
|
|||||||
}
|
}
|
||||||
} else if (op.insert) {
|
} else if (op.insert) {
|
||||||
reproofNeeded = true
|
reproofNeeded = true
|
||||||
index += op.insert.length
|
index += typeof op.insert === 'string' ? op.insert.length : 1 // Handle string or embed
|
||||||
} else if (op.delete) {
|
} else if (op.delete) {
|
||||||
reproofNeeded = true
|
reproofNeeded = true
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
if (reproofNeeded) {
|
if (reproofNeeded) {
|
||||||
const editorLength = this.quillInstance.getLength()
|
const editorLength = this.quillInstance.getLength()
|
||||||
const clampedIndex = Math.min(index, editorLength - 1)
|
const clampedIndex = Math.min(index, editorLength - 1)
|
||||||
@@ -1842,22 +2062,55 @@ class BesQuillService extends BesTreeService {
|
|||||||
if (leaf) {
|
if (leaf) {
|
||||||
let domElement = leaf.domNode
|
let domElement = leaf.domNode
|
||||||
|
|
||||||
|
// Traverse up to find the block element
|
||||||
while (domElement && !this.isBlockElement(domElement)) {
|
while (domElement && !this.isBlockElement(domElement)) {
|
||||||
domElement = domElement.parentNode
|
domElement = domElement.parentNode
|
||||||
}
|
}
|
||||||
if (domElement) {
|
|
||||||
this.clearProofing(domElement)
|
|
||||||
|
|
||||||
setTimeout(() => {
|
if (domElement) affectedBlocks.add(domElement)
|
||||||
this.redrawAllMistakeMarkup()
|
|
||||||
this.scheduleProofing(1000)
|
|
||||||
}, 0)
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
console.warn(
|
console.warn(
|
||||||
'Leaf is null. The index might be out of bounds or the editor content is empty.'
|
'Leaf is null. The index might be out of bounds or the editor content is empty.'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle pasted content spanning multiple blocks
|
||||||
|
const selection = this.quillInstance.getSelection()
|
||||||
|
if (selection) {
|
||||||
|
const [startLeaf] = this.quillInstance.getLeaf(selection.index)
|
||||||
|
const [endLeaf] = this.quillInstance.getLeaf(
|
||||||
|
selection.index + selection.length
|
||||||
|
)
|
||||||
|
|
||||||
|
if (startLeaf && endLeaf) {
|
||||||
|
let startElement = startLeaf.domNode
|
||||||
|
let endElement = endLeaf.domNode
|
||||||
|
|
||||||
|
while (startElement && !this.isBlockElement(startElement)) {
|
||||||
|
startElement = startElement.parentNode
|
||||||
|
}
|
||||||
|
while (endElement && !this.isBlockElement(endElement)) {
|
||||||
|
endElement = endElement.parentNode
|
||||||
|
}
|
||||||
|
|
||||||
|
if (startElement && endElement) {
|
||||||
|
let currentElement = startElement
|
||||||
|
while (currentElement) {
|
||||||
|
affectedBlocks.add(currentElement)
|
||||||
|
if (currentElement === endElement) break
|
||||||
|
currentElement = currentElement.nextElementSibling
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear proofing for all affected blocks
|
||||||
|
affectedBlocks.forEach(block => this.clearProofing(block))
|
||||||
|
|
||||||
|
// Schedule proofing for all affected blocks
|
||||||
|
setTimeout(() => {
|
||||||
|
this.scheduleProofing(1000)
|
||||||
|
}, 0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2035,9 +2288,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)
|
||||||
})
|
})
|
||||||
@@ -2172,19 +2427,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)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -2457,11 +2712,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`
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -2659,7 +2916,7 @@ class BesPopup extends HTMLElement {
|
|||||||
padding: 3px 2px;
|
padding: 3px 2px;
|
||||||
}
|
}
|
||||||
.bes-toolbar button {
|
.bes-toolbar button {
|
||||||
margin-right: 2px;
|
margin-right: 0px;
|
||||||
}
|
}
|
||||||
.bes-popup-title {
|
.bes-popup-title {
|
||||||
color: #333;
|
color: #333;
|
||||||
@@ -2684,6 +2941,12 @@ class BesPopup extends HTMLElement {
|
|||||||
}
|
}
|
||||||
.bes-replacement-btn:hover{
|
.bes-replacement-btn:hover{
|
||||||
background-color: #1976f0;
|
background-color: #1976f0;
|
||||||
|
}
|
||||||
|
.bes-replacement-btn:focus{
|
||||||
|
outline: -webkit-focus-ring-color auto 1px;
|
||||||
|
}
|
||||||
|
.bes-replacement-btn:focus-visible{
|
||||||
|
outline: -webkit-focus-ring-color auto 1px;
|
||||||
}
|
}
|
||||||
.bes-replacement-div{
|
.bes-replacement-div{
|
||||||
margin-top: 4px;
|
margin-top: 4px;
|
||||||
@@ -2699,11 +2962,17 @@ class BesPopup extends HTMLElement {
|
|||||||
.bes-close-btn svg {
|
.bes-close-btn svg {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
fill: #333;
|
}
|
||||||
|
.bes-mistake-nav{
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
:host{
|
||||||
|
--bes-close-icon: #485362;
|
||||||
|
--hover-bg-clr: #dee3ed;
|
||||||
}
|
}
|
||||||
.bes-close-btn:hover {
|
.bes-close-btn:hover {
|
||||||
background: #dee3ed;
|
background: var(--hover-bg-clr);
|
||||||
border-radius: 8px
|
border-radius: 4px
|
||||||
}
|
}
|
||||||
:host(.show) .bes-popup-container {
|
:host(.show) .bes-popup-container {
|
||||||
visibility: visible;
|
visibility: visible;
|
||||||
@@ -2732,17 +3001,45 @@ class BesPopup extends HTMLElement {
|
|||||||
background-color: #111213;
|
background-color: #111213;
|
||||||
border: 1px solid #2e3036;
|
border: 1px solid #2e3036;
|
||||||
}
|
}
|
||||||
|
:host{
|
||||||
|
--bes-close-icon: #a4b5c7;
|
||||||
|
--hover-bg-clr:rgba(189, 189, 189, 0.28);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<div class="bes-popup-container">
|
<div class="bes-popup-container">
|
||||||
<div class="bes-toolbar">
|
<div class="bes-toolbar">
|
||||||
<div class="bes-popup-title">Besana</div>
|
<div class="bes-popup-title">Besana</div>
|
||||||
|
<div class="bes-mistake-nav">
|
||||||
|
<button class="bes-close-btn" title="Prejšnja napaka">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><!-- Icon from Material Symbols by Google - https://github.com/google/material-design-icons/blob/master/LICENSE --><path fill="var(--bes-close-icon)" d="M11 20V7.825l-5.6 5.6L4 12l8-8l8 8l-1.4 1.425l-5.6-5.6V20z"/></svg>
|
||||||
|
</button>
|
||||||
|
<button class="bes-close-btn" title="Naslednja napaka">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><!-- Icon from Material Symbols by Google - https://github.com/google/material-design-icons/blob/master/LICENSE --><path fill="var(--bes-close-icon)" d="M11 4v12.175l-5.6-5.6L4 12l8 8l8-8l-1.4-1.425l-5.6 5.6V4z"/></svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
<button class="bes-close-btn" onclick="BesPopup.dismiss()">
|
<button class="bes-close-btn" onclick="BesPopup.dismiss()">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><path fill="currentColor" d="M13.46 12L19 17.54V19h-1.46L12 13.46L6.46 19H5v-1.46L10.54 12L5 6.46V5h1.46L12 10.54L17.54 5H19v1.46z"/></svg>
|
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><path fill="var(--bes-close-icon)" d="M13.46 12L19 17.54V19h-1.46L12 13.46L6.46 19H5v-1.46L10.54 12L5 6.46V5h1.46L12 10.54L17.54 5H19v1.46z"/></svg>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`
|
`
|
||||||
|
const prevBtn = this.shadowRoot.querySelector(
|
||||||
|
'.bes-mistake-nav .bes-close-btn[title="Prejšnja napaka"]'
|
||||||
|
)
|
||||||
|
const nextBtn = this.shadowRoot.querySelector(
|
||||||
|
'.bes-mistake-nav .bes-close-btn[title="Naslednja napaka"]'
|
||||||
|
)
|
||||||
|
|
||||||
|
prevBtn.addEventListener('click', () => {
|
||||||
|
if (this.hostElService)
|
||||||
|
BesService.arrowBtnNavigation('back', this.hostElService)
|
||||||
|
})
|
||||||
|
nextBtn.addEventListener('click', () => {
|
||||||
|
if (this.hostElService)
|
||||||
|
BesService.arrowBtnNavigation('forward', this.hostElService)
|
||||||
|
})
|
||||||
|
|
||||||
this.addEventListener('mousedown', this.onMouseDown)
|
this.addEventListener('mousedown', this.onMouseDown)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2752,8 +3049,9 @@ class BesPopup extends HTMLElement {
|
|||||||
* @param {Number} x X location hint
|
* @param {Number} x X location hint
|
||||||
* @param {Number} y Y location hint
|
* @param {Number} y Y location hint
|
||||||
*/
|
*/
|
||||||
show(x, y) {
|
show(x, y, service) {
|
||||||
this.style.position = 'fixed'
|
this.style.position = 'fixed'
|
||||||
|
this.hostElService = service
|
||||||
|
|
||||||
// Element needs some initial placement for the browser to provide this.offsetWidth and this.
|
// Element needs some initial placement for the browser to provide this.offsetWidth and this.
|
||||||
// offsetHeight measurements.
|
// offsetHeight measurements.
|
||||||
|
47
styles.css
47
styles.css
@@ -1,25 +1,39 @@
|
|||||||
/* TODO: Dark mode theme */
|
|
||||||
|
|
||||||
/* 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: rgb(242, 90, 90);
|
||||||
|
background: hsla(0, 100%, 67%, 0.18);
|
||||||
}
|
}
|
||||||
|
|
||||||
.bes-highlight-grammar-rect {
|
.bes-highlight-ai {
|
||||||
background: rgb(255, 115, 0);
|
color: rgb(139, 62, 223);
|
||||||
|
background: hsla(262, 70%, 56%, 0.18);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bes-highlight-grammar {
|
||||||
|
color: rgb(60, 120, 220);
|
||||||
|
background: hsla(220, 80%, 56%, 0.18);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
.bes-highlight-spelling {
|
||||||
|
color: rgb(255, 120, 120);
|
||||||
|
background: hsla(0, 100%, 67%, 0.32);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bes-highlight-ai {
|
||||||
|
color: rgb(180, 120, 255);
|
||||||
|
background: hsla(262, 70%, 56%, 0.32);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bes-highlight-grammar {
|
||||||
|
color: rgb(100, 164, 243);
|
||||||
|
background: hsla(220, 80%, 56%, 0.32);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Styles required to ensure full functionality and optimal user experience. */
|
/* Styles required to ensure full functionality and optimal user experience. */
|
||||||
@@ -56,3 +70,8 @@
|
|||||||
background: none;
|
background: none;
|
||||||
z-index: -1;
|
z-index: -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.bes-highlight-placeholder {
|
||||||
|
display: none;
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
Reference in New Issue
Block a user