Fix <textarea>, cleanup...

This commit is contained in:
Simon Rozman 2025-02-25 12:56:09 +01:00
parent 00cb5aecd4
commit 0855c13384
2 changed files with 66 additions and 263 deletions

View File

@ -41,7 +41,7 @@ class BesService {
this.enabledCategories = []
this.disabledCategories = []
this.results = [] // Results of grammar-checking, one per each block/paragraph of text
this.highlightedRects = []
this.highlightElements = []
this.createCorrectionPanel()
// Disable browser built-in spell-checker to prevent collision with our grammar markup.
@ -58,7 +58,7 @@ class BesService {
this.hostElement.setAttribute('data-gramm', 'false')
this.hostElement.setAttribute('data-gramm_editor', 'false')
this.hostElement.setAttribute('data-enable-grammarly', 'false')
this.textFont = window.getComputedStyle(this.textElement).fontFamily
this.textFont = window.getComputedStyle(this.hostElement).fontFamily
this.onScroll = this.onScroll.bind(this)
this.hostElement.addEventListener('scroll', this.onScroll)
@ -334,8 +334,8 @@ class BesService {
const dpr = window.devicePixelRatio
this.ctx.lineWidth = 2 * dpr // Use 2 for clearer visibility
this.ctx.strokeStyle = ruleId.startsWith('MORFOLOGIK_RULE')
? '#ff7300'
: '#007bff'
? 'rgba(255, 115, 0, 0.5)'
: 'rgba(0, 123, 255, 0.5)'
for (let rect of range.getClientRects()) {
const x = (rect.left - scrollPanelRect.left) * dpr
const y = (rect.top - scrollPanelRect.top) * dpr
@ -455,7 +455,9 @@ class BesService {
* @param {PointerEvent} source Click event source
*/
popupCorrectionPanel(el, match, source) {
this.dismissPopup()
const popup = document.querySelector('bes-popup-el')
// TODO: popup.setContent(elements, matches, this, this.isContentEditable())
popup.changeMessage(match.match.message)
popup.appendReplacements(el, match, this, this.isContentEditable())
this.highlightMistake(match)
@ -469,27 +471,32 @@ class BesService {
*/
highlightMistake(match) {
// TODO: Če sta 2 napaki v istem rectu, se prikaže samo zgornja/zadnja (oz. tista, ki jo uporabnik klikne). To pa pomeni, da se lahko podčrtana barva razlikuje od "highlight" barve.
const highlightColor = match.match.rule.id.startsWith('MORFOLOGIK_RULE')
? '#ff7300'
: '#007bff'
match.highlights.forEach(highlight => {
const rect = highlight
const highlightElement = document.createElement('div')
highlightElement.classList.add('highlight-rect')
highlightElement.style.position = 'absolute'
highlightElement.style.left = `${rect.x}px`
highlightElement.style.top = `${rect.y}px`
highlightElement.style.width = `${rect.width}px`
highlightElement.style.height = `${rect.height}px`
highlightElement.style.backgroundColor = highlightColor
highlightElement.style.opacity = '0.5'
highlightElement.style.cursor = 'text'
document.body.appendChild(highlightElement)
this.highlightedRects.push(highlightElement)
match.highlights.forEach(rect => {
const el = document.createElement('div')
el.classList.add('bes-highlight-rect')
el.classList.add(
match.match.rule.id.startsWith('MORFOLOGIK_RULE')
? 'bes-highlight-spelling-rect'
: 'bes-highlight-grammar-rect'
)
el.style.left = `${rect.x}px`
el.style.top = `${rect.y}px`
el.style.width = `${rect.width}px`
el.style.height = `${rect.height}px`
document.body.appendChild(el)
this.highlightElements.push(el)
})
}
/**
* Clears highlight and hides popup
*/
dismissPopup() {
BesPopup.hide()
this.highlightElements.forEach(el => el.remove())
this.highlightElements = []
}
/**
* Checks if host element content is editable.
*
@ -764,34 +771,10 @@ class BesTreeService extends BesService {
* @param {Element} el DOM element for removal
*/
clearProofing(el) {
this.clearMarkup(el)
this.results = this.results.filter(
result => !BesTreeService.isSameParagraph(result.element, el)
)
}
/**
* Clears given block element grammar mistake markup.
*
* @param {Element} el DOM element we want to clean markup for
*/
clearMarkup(el) {
const dpr = window.devicePixelRatio
this.results
.filter(result => BesTreeService.isSameParagraph(result.element, el))
.forEach(result => {
let sourceRect = result.element.getBoundingClientRect()
let targetRect = this.canvasPanel.getBoundingClientRect()
let newX = sourceRect.left - targetRect.left
let newY = sourceRect.top - targetRect.top
this.ctx.clearRect(
newX * dpr,
newY * dpr,
sourceRect.width * dpr,
sourceRect.height * dpr
)
delete result.matches
})
this.repositionAllMarkup()
}
/**
@ -930,16 +913,15 @@ class BesTreeService extends BesService {
for (let result of this.results) {
for (let m of result.matches) {
for (let h of m.highlights) {
if (BesService.isPointInRect(source.clientX, source.clientY, h)) {
for (let rect of m.highlights) {
if (BesService.isPointInRect(source.clientX, source.clientY, rect)) {
this.popupCorrectionPanel(el, m, source)
return
}
}
}
}
BesPopup.hide()
this.highlightedRects.forEach(h => h.remove())
this.dismissPopup()
}
}
@ -1024,8 +1006,7 @@ class BesDOMService extends BesTreeService {
onInput() {
// Now that the text is done changing, we can correctly calculate markup position.
this.repositionAllMarkup()
BesPopup.hide()
this.highlightedRects.forEach(h => h.remove())
this.dismissPopup()
// Defer grammar-checking to reduce stress on grammar-checking server.
this.scheduleProofing(1000)
}
@ -1536,30 +1517,10 @@ class BesPlainTextService extends BesService {
* @param {Range} range Paragraph range
*/
clearProofing(range) {
this.clearMarkup(range)
this.results = this.results.filter(
result => !BesPlainTextService.isSameParagraph(result.range, range)
)
}
/**
* Clears given paragraph grammar mistake markup.
*
* @param {Range} range Paragraph range
*/
clearMarkup(range) {
this.results
.filter(result =>
BesPlainTextService.isSameParagraph(result.range, range)
)
.forEach(result =>
result.matches.forEach(match => {
if (match.highlights) {
match.highlights.forEach(h => h.remove())
delete match.highlights
}
})
)
this.repositionAllMarkup()
}
/**
@ -1605,16 +1566,15 @@ class BesPlainTextService extends BesService {
for (let result of this.results) {
for (let m of result.matches) {
for (let h of m.highlights) {
if (BesService.isPointInRect(source.clientX, source.clientY, h)) {
for (let rect of m.highlights) {
if (BesService.isPointInRect(source.clientX, source.clientY, rect)) {
this.popupCorrectionPanel(result.range, m, source)
return
}
}
}
}
BesPopup.hide()
this.highlightedRects.forEach(h => h.remove())
this.dismissPopup()
}
/**
@ -1729,8 +1689,7 @@ class BesDOMPlainTextService extends BesPlainTextService {
// Now that the text is done changing, we can correctly calculate markup position.
this.repositionAllMarkup()
BesPopup.hide()
this.highlightedRects.forEach(h => h.remove())
this.dismissPopup()
// Defer grammar-checking to reduce stress on grammar-checking server.
this.scheduleProofing(1000)
}
@ -1862,28 +1821,21 @@ class BesTAService extends BesPlainTextService {
const styles = window.getComputedStyle(hostElement)
textElement.style.zIndex = hostElement.style.zIndex - 1
textElement.style.fontSize = styles.fontSize
textElement.style.fontFamily = styles.fontFamily
textElement.style.font = styles.font
textElement.style.lineHeight = styles.lineHeight
textElement.style.whiteSpace = styles.whiteSpace
textElement.style.whiteSpaceCollapse = styles.whiteSpaceCollapse
textElement.style.hyphens = styles.hyphens
textElement.style.boxSizing = styles.boxSizing
textElement.style.scrollBehavior = styles.scrollBehavior
textElement.style.overflow = 'hidden'
textElement.style.border = styles.border
textElement.style.borderRadius = styles.borderRadius
textElement.style.paddingLeft = styles.paddingLeft
textElement.style.paddingTop = styles.paddingTop
textElement.style.paddingRight = styles.paddingRight
textElement.style.paddingBottom = styles.paddingBottom
textElement.style.height = styles.height
textElement.style.minHeight = styles.minHeight
textElement.style.maxHeight = styles.maxHeight
textElement.style.overflow = styles.overflow
textElement.style.overflowWrap = styles.overflowWrap
textElement.style.top = `${rect.top + scrollTop}px`
textElement.style.padding = styles.padding
textElement.style.left = `${rect.left + scrollLeft}px`
textElement.style.top = `${rect.top + scrollTop}px`
textElement.style.width = styles.width
textElement.style.minWidth = styles.minWidth
textElement.style.maxWidth = styles.maxWidth
textElement.style.height = styles.height
}
/**
@ -2137,7 +2089,7 @@ class BesPopup extends HTMLElement {
<div class="bes-popup-container">
<div class="bes-toolbar">
<div class="bes-popup-title">Besana</div>
<button class="bes-close-btn" onclick="BesPopup.hide()">X</button>
<button class="bes-close-btn" onclick="BesPopup.dismiss()">X</button>
</div>
<div class="bes-text-div">
<span class="popup-text">
@ -2210,7 +2162,7 @@ class BesPopup extends HTMLElement {
replacementBtn.addEventListener('click', () => {
if (allowReplacements) {
service.replaceText(el, match, replacement.value)
BesPopup.hide()
service.dismissPopup()
}
})
replacementDiv.appendChild(replacementBtn)
@ -2274,167 +2226,24 @@ class BesPopup extends HTMLElement {
}
/**
* Dismisses all the popups.
* Hides all the popups.
*/
static hide() {
document.querySelectorAll('bes-popup-el').forEach(popup => {
popup.classList.remove('show')
})
document.querySelectorAll('.bes-mistake-highlight-selected').forEach(el => {
el.classList.remove('bes-mistake-highlight-selected')
})
}
/**
* Dismisses all the popups.
*/
static dismiss() {
besServices.forEach(service => service.dismissPopup())
}
}
customElements.define('bes-popup-el', BesPopup)
// /*************************************************************************
// *
// * Status pop-up
// *
// *************************************************************************/
// class BesStatusPopup extends HTMLElement {
// constructor() {
// super()
// this.attachShadow({ mode: 'open' })
// }
// /**
// * Called each time the element is added to the document
// */
// connectedCallback() {
// this.shadowRoot.innerHTML = `
// <style>
// :host {
// display: none;
// }
// :host(.show){
// z-index: 10;
// }
// .popup-text {
// max-width: 160px;
// color: #333;
// text-align: center;
// padding: 8px 0;
// z-index: 1;
// }
// .bes-popup-container {
// visibility: hidden;
// max-width: 300px;
// background-color: #f1f3f9;
// box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
// border-radius: 5px;
// padding: 8px;
// z-index: 1;
// }
// .bes-toolbar {
// display: flex;
// justify-content: end;
// padding: 5px 2px;
// }
// .bes-toolbar button {
// margin-right: 2px;
// }
// .bes-popup-title {
// flex-grow: 1;
// }
// .bes-text-div{
// background-color: #eee;
// padding: 10px;
// border-radius: 5px;
// }
// .bes-service-btn{
// background-color: none;
// width: 30px;
// height: 30px;
// display: flex;
// justify-content: center;
// align-items: center;
// }
// .bes-turn-off{
// width: 20px;
// height: 20px;
// }
// :host(.show) .bes-popup-container {
// visibility: visible;
// animation: fadeIn 1s;
// }
// @keyframes fadeIn {
// from {opacity: 0;}
// to {opacity:1 ;}
// }
// </style>
// <div class="bes-popup-container">
// <div class="bes-toolbar">
// <div class="bes-popup-title">Besana</div>
// <button class="bes-close-btn" onclick="BesStatusPopup.hide()">X</button>
// </div>
// <div class="bes-text-div">
// <span class="popup-text">
// Če želite izključiti preverjanje pravopisa, kliknite na gumb.
// </span>
// <button class="bes-service-btn">
// <img class="bes-turn-off" src="/images/turn-off-svgrepo-com.svg" alt="Izključi preverjanje pravopisa">
// </button>
// </div>
// </div>
// `
// }
// /**
// * Shows popup window.
// *
// * @param {Number} x X location hint
// * @param {Number} y Y location hint
// * @param {BesService} service Grammar checking service
// */
// show(x, y, service) {
// this.style.position = 'fixed'
// this.style.display = 'block'
// // Element needs some initial placement for the browser to provide this.offsetWidth and this.offsetHeight measurements.
// // The fade-in effect on the popup window should prevent flicker.
// this.style.left = `0px`
// this.style.top = `0px`
// this.classList.add('show')
// if (x + this.offsetWidth <= window.innerWidth) {
// this.style.left = `${x}px`
// } else if (this.offsetWidth <= x) {
// this.style.left = `${x - this.offsetWidth}px`
// } else {
// this.style.left = `${x - this.offsetWidth / 2}px`
// }
// if (y + 20 + this.offsetHeight <= window.innerHeight) {
// this.style.top = `${y + 20}px`
// } else if (this.offsetHeight <= y) {
// this.style.top = `${y - this.offsetHeight}px`
// } else {
// this.style.top = `${y - this.offsetHeight / 2}px`
// }
// if (service) {
// const disableButton = this.shadowRoot.querySelector('.bes-service-btn')
// disableButton.onclick = () => {
// service.unregister()
// BesStatusPopup.hide()
// }
// }
// this.classList.add('show')
// }
// /**
// * Dismisses all the popups.
// */
// static hide() {
// const popup = document.querySelector('bes-popup-status.show')
// popup?.classList?.remove('show')
// }
// }
// customElements.define('bes-popup-status', BesStatusPopup)
// Auto-register all elements with bes-service class.
window.addEventListener('load', () => {
document

View File

@ -8,32 +8,26 @@
cursor: text;
}
/* TODO: Cleanup */
.bes-grammar-mistake {
border-bottom: 2px solid #007bff;
.bes-highlight-rect {
position: absolute;
z-index: 3;
opacity: 0.3;
cursor: text;
}
.bes-highlight-spelling-rect {
background: rgb(255, 115, 0);
}
.bes-highlight-grammar-rect {
background: rgb(0, 123, 255);
}
.bes-canvas {
position: relative;
z-index: 3;
cursor: text;
}
/* TODO: Cleanup */
.bes-spelling-mistake.bes-mistake-highlight-selected {
background: #cc7024;
opacity: 0.5;
}
/* TODO: Cleanup */
.bes-grammar-mistake.bes-mistake-highlight-selected {
background: #3691f3;
opacity: 0.5;
}
/* Styles required to ensure full functionality and optimal user experience. */
.bes-correction-panel-parent {
position: relative;