Compare commits

...

20 Commits

Author SHA1 Message Date
d3deb4cb11 Improve popup positioning based on rect's postion and not on click's X,Y coordiantes.
Closes #3
2025-06-11 10:01:40 +02:00
3cd86bf4c3 Fix scrollIntoView behavior and improve popup positioning #4 2025-06-09 11:48:15 +02:00
a04ffb3e70 Improve navigation and fix focus-visible colors in the popup #4 2025-06-05 09:44:52 +02:00
e4ba4dd3f1 Refactor findNextMistake method to be static and its calls #4 2025-06-05 08:54:56 +02:00
b99d233abc Prevent ctrl + enter shortuct when no grammar highlights are present #4 2025-06-05 08:17:19 +02:00
04cd5f2e7d Use scrollintoView after using shortcut navigation for finding a next/previous mistake #4 2025-06-05 08:11:42 +02:00
1163b3c47e Enhance shortcut navigation and add replacement acceptance logic for mistakes #4 2025-06-04 15:03:10 +02:00
e903917179 Add navigation buttons in popup #4 2025-06-04 13:18:30 +02:00
0f2fa218f3 Update popup positioning after using keyboard shortcut #4 2025-06-04 11:50:41 +02:00
99db143007 Add keyboard shortcut navigation for grammar mistakes
- Implements Ctrl+Š/Đ keyboard shortcuts to navigate between grammar mistakes/highlights. #4
2025-06-04 10:26:56 +02:00
c7c90101a2 Implement tab navigation for grammar mistakes
- The feature is still not fully finished yet.
- Navigation does not work in Quill, textarea and static-content examples.
- But it works well in CKEditor and contenteditable examples.

#4
2025-06-03 14:59:25 +02:00
Aljaž Grilc
b9ab9b6a64 Add double underline drawing for specific grammar rules 2025-05-28 21:01:43 +02:00
Aljaž Grilc
c67adcdc99 Adjust highlight color as requested by the company 2025-05-28 19:46:49 +02:00
be08136a31 Improve color of the popup close button #6 2025-05-21 09:14:06 +02:00
9bc8dfbdfc Display AI suggestions using different color 2025-04-24 14:46:23 +02:00
5cbac62de3 Fix BesQuillService when pasting a simple block of text 2025-04-16 09:27:20 +02:00
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 374 additions and 65 deletions

View File

@@ -91,8 +91,8 @@ Kategorije pravopisnih pravil so:
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/markup_lector.png" alt="lector" width="448"/>
<img src="samples/images/markup_underline.png" alt="underline" width="448"/>
<img src="samples/images/markup_lector.png" alt="lector" width="448"/>
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

@@ -63,6 +63,8 @@ class BesService {
this.onScroll = this.onScroll.bind(this)
this.hostElement.addEventListener('scroll', this.onScroll)
this.onShortcutNavigation = this.onShortcutNavigation.bind(this)
this.hostElement.addEventListener('keydown', this.onShortcutNavigation)
this.hostBoundingClientRect = this.hostElement.getBoundingClientRect()
this.mutationObserver = new MutationObserver(this.onBodyMutate.bind(this))
@@ -109,6 +111,7 @@ class BesService {
if (this.abortController) this.abortController.abort()
besServices = besServices.filter(item => item !== this)
this.mutationObserver.disconnect()
this.hostElement.removeEventListener('keydown', this.onShortcutNavigation)
this.hostElement.removeEventListener('scroll', this.onScroll)
this.hostElement.setAttribute('spellcheck', this.originalSpellcheck)
this.hostElement.setAttribute('data-gramm', this.originalDataGramm)
@@ -276,6 +279,9 @@ class BesService {
*/
onProofingProgress(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)
this.eventSink.proofingProgress(this)
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
*/
@@ -371,13 +417,24 @@ class BesService {
if (match.highlights.length === 0) return
const dpr = window.devicePixelRatio
this.ctx.lineWidth = 2 * dpr // Use 2 for clearer visibility
let amplitude = 0
const ruleId = match.match.rule.id
this.ctx.strokeStyle = ruleId.startsWith('MORFOLOGIK_RULE')
? 'rgba(0, 123, 255, 0.8)'
: 'rgba(255, 115, 0, 0.8)'
this.ctx.fillStyle = ruleId.startsWith('MORFOLOGIK_RULE')
? 'rgba(0, 123, 255, 0.8)'
: 'rgba(255, 115, 0, 0.8)'
if (ruleId.startsWith('MORFOLOGIK_RULE')) {
const styles = window.getComputedStyle(this.highlightSpelling)
this.ctx.strokeStyle = styles.color
this.ctx.fillStyle = styles.color
amplitude = -1
} 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
switch (this.markupStyle) {
case 'lector':
@@ -681,7 +738,9 @@ class BesService {
const x2 = rect.right
const y = rect.bottom
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))
@@ -865,31 +924,50 @@ class BesService {
* @param {Number} x1 Sign left [px]
* @param {Number} x2 Sign right [px]
* @param {Number} y Sign baseline [px]
* @param {Number} amplitude Sign amplitude [px]
* @param {Number} scale Sign scale
*/
drawAttentionRequired(x1, x2, y, scale) {
drawAttentionRequired(x1, x2, y, amplitude, scale) {
const dpr = window.devicePixelRatio
this.ctx.beginPath()
this.ctx.moveTo(x1 * dpr, (y - scale) * dpr)
this.ctx.moveTo(x1 * dpr, (y - amplitude * scale) * dpr)
for (let x = x1; ; ) {
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
this.ctx.lineTo((x += 2 * scale) * dpr, (y - scale) * dpr)
this.ctx.lineTo((x += 2 * scale) * dpr, (y - amplitude * scale) * dpr)
}
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)
{
setCtxFont(scale, dpr) {
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 +1022,21 @@ class BesService {
* @param {Number} x X coordinate
* @param {Number} y Y coordinate
* @param {DOMRect} rect Rectangle
* @param {Number} tolerance Extra margin around the rectangle treated as "inside"
* @returns
*/
static isPointInRect(x, y, rect) {
return rect.left <= x && x < rect.right && rect.top <= y && y < rect.bottom
static isPointInRect(x, y, rect, tolerance) {
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)
}
/**
@@ -965,7 +1054,20 @@ class BesService {
this.ctx = this.canvasPanel.getContext('2d')
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.highlightSpelling)
this.correctionPanel.appendChild(this.highlightAI)
this.correctionPanel.appendChild(this.highlightGrammar)
this.scrollPanel.appendChild(this.canvasPanel)
this.textElement.parentElement.insertBefore(
this.correctionPanel,
@@ -1033,18 +1135,24 @@ class BesService {
/**
* 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
*/
preparePopup(elMatch, source) {
preparePopup(hits, source) {
this.dismissPopup()
const popup = document.querySelector('bes-popup-el')
BesPopup.clearReplacements()
elMatch.forEach(({ el, match }) => {
hits.forEach(({ el, match }) => {
popup.setContent(el, match, this, this.isContentEditable())
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)
}
/**
@@ -1059,27 +1167,123 @@ class BesService {
el.classList.add('bes-highlight-rect')
el.classList.add(
match.match.rule.id.startsWith('MORFOLOGIK_RULE')
? 'bes-highlight-spelling-rect'
: 'bes-highlight-grammar-rect'
? 'bes-highlight-spelling'
: 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.top = `${rect.y + canvasPanelRect.y + window.scrollY}px`
el.style.width = `${rect.width}px`
el.style.height = `${rect.height}px`
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
*/
dismissPopup() {
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 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.
*
@@ -1100,7 +1304,9 @@ class BesService {
redrawAllMistakeMarkup() {
this.ctx.clearRect(0, 0, this.canvasPanel.width, this.canvasPanel.height)
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 +1462,11 @@ class BesTreeService extends BesService {
),
match: match
}
this.drawMistakeMarkup(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.onProofingProgress(matches.length)
})
@@ -1514,19 +1722,19 @@ class BesTreeService extends BesService {
const canvasPanelRect = this.canvasPanel.getBoundingClientRect()
let x = source.clientX - canvasPanelRect.x
let y = source.clientY - canvasPanelRect.y
const pointsInRect = []
const hits = []
for (let result of this.results) {
for (let m of result.matches) {
for (let rect of m.highlights) {
if (BesService.isPointInRect(x, y, rect)) {
pointsInRect.push({ el, match: m })
if (BesService.isPointInRect(x, y, rect, 5)) {
hits.push({ el, match: m })
break
}
}
}
}
this.dismissPopup()
if (pointsInRect.length) this.preparePopup(pointsInRect, source)
if (hits.length) this.preparePopup(hits, source)
}
}
@@ -1830,6 +2038,7 @@ class BesQuillService extends BesTreeService {
onChangeData(delta) {
let index = 0
let reproofNeeded = false
const affectedBlocks = new Set()
delta.ops.forEach(op => {
if (op.retain) {
@@ -1839,11 +2048,12 @@ class BesQuillService extends BesTreeService {
}
} else if (op.insert) {
reproofNeeded = true
index += op.insert.length
index += typeof op.insert === 'string' ? op.insert.length : 1 // Handle string or embed
} else if (op.delete) {
reproofNeeded = true
}
})
if (reproofNeeded) {
const editorLength = this.quillInstance.getLength()
const clampedIndex = Math.min(index, editorLength - 1)
@@ -1852,22 +2062,55 @@ class BesQuillService extends BesTreeService {
if (leaf) {
let domElement = leaf.domNode
// Traverse up to find the block element
while (domElement && !this.isBlockElement(domElement)) {
domElement = domElement.parentNode
}
if (domElement) {
this.clearProofing(domElement)
setTimeout(() => {
this.redrawAllMistakeMarkup()
this.scheduleProofing(1000)
}, 0)
}
if (domElement) affectedBlocks.add(domElement)
} else {
console.warn(
'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)
}
}
@@ -2045,9 +2288,11 @@ class BesPlainTextService extends BesService {
),
match: match
}
this.drawMistakeMarkup(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.onProofingProgress(matches.length)
})
@@ -2182,19 +2427,19 @@ class BesPlainTextService extends BesService {
const canvasPanelRect = this.canvasPanel.getBoundingClientRect()
let x = source.clientX - canvasPanelRect.x
let y = source.clientY - canvasPanelRect.y
const pointsInRect = []
const hits = []
for (let result of this.results) {
for (let m of result.matches) {
for (let rect of m.highlights) {
if (BesService.isPointInRect(x, y, rect)) {
pointsInRect.push({ el: result.range, match: m })
if (BesService.isPointInRect(x, y, rect, 5)) {
hits.push({ el: result.range, match: m })
break
}
}
}
}
this.dismissPopup()
if (pointsInRect.length) this.preparePopup(pointsInRect, source)
if (hits.length) this.preparePopup(hits, source)
}
/**
@@ -2467,11 +2712,13 @@ class BesTAService extends BesPlainTextService {
parseFloat(styles.paddingRight) -
parseFloat(styles.borderRightWidth)
}px`
textElement.style.height = `${rect.height -
textElement.style.height = `${
rect.height -
parseFloat(styles.borderTopWidth) -
parseFloat(styles.paddingTop) -
parseFloat(styles.paddingBottom) -
parseFloat(styles.borderBottomWidth)}px`
parseFloat(styles.borderBottomWidth)
}px`
}
/**
@@ -2669,7 +2916,7 @@ class BesPopup extends HTMLElement {
padding: 3px 2px;
}
.bes-toolbar button {
margin-right: 2px;
margin-right: 0px;
}
.bes-popup-title {
color: #333;
@@ -2694,6 +2941,12 @@ class BesPopup extends HTMLElement {
}
.bes-replacement-btn:hover{
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{
margin-top: 4px;
@@ -2709,11 +2962,17 @@ class BesPopup extends HTMLElement {
.bes-close-btn svg {
width: 100%;
height: 100%;
fill: #333;
}
.bes-mistake-nav{
margin-right: 10px;
}
:host{
--bes-close-icon: #485362;
--hover-bg-clr: #dee3ed;
}
.bes-close-btn:hover {
background: #dee3ed;
border-radius: 8px
background: var(--hover-bg-clr);
border-radius: 4px
}
:host(.show) .bes-popup-container {
visibility: visible;
@@ -2742,17 +3001,45 @@ class BesPopup extends HTMLElement {
background-color: #111213;
border: 1px solid #2e3036;
}
:host{
--bes-close-icon: #a4b5c7;
--hover-bg-clr:rgba(189, 189, 189, 0.28);
}
}
</style>
<div class="bes-popup-container">
<div class="bes-toolbar">
<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()">
<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>
</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)
}
@@ -2762,8 +3049,9 @@ class BesPopup extends HTMLElement {
* @param {Number} x X location hint
* @param {Number} y Y location hint
*/
show(x, y) {
show(x, y, service) {
this.style.position = 'fixed'
this.hostElService = service
// Element needs some initial placement for the browser to provide this.offsetWidth and this.
// offsetHeight measurements.

View File

@@ -1,23 +1,39 @@
/* Mistake types styles */
.bes-spelling-mistake {
border-bottom: 2px solid #ff7300;
position: absolute;
z-index: 3;
cursor: text;
}
.bes-highlight-rect {
position: absolute;
opacity: 0.3;
cursor: text;
}
.bes-highlight-spelling-rect {
background: rgb(0, 123, 255);
.bes-highlight-spelling {
color: rgb(242, 90, 90);
background: hsla(0, 100%, 67%, 0.18);
}
.bes-highlight-grammar-rect {
background: rgb(255, 115, 0);
.bes-highlight-ai {
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. */
@@ -54,3 +70,8 @@
background: none;
z-index: -1;
}
.bes-highlight-placeholder {
display: none;
visibility: hidden;
}