service2.js: Port popup from service.js
This commit is contained in:
parent
c54366e95f
commit
ff54607e7e
@ -17,5 +17,6 @@
|
|||||||
<p>To velja tudi v Bledu. To se je zgodilo na velikemu vrtu. Prišel je na Kamnik. On je včeraj prišel z svojo torbo. Dve žemlje prosim. Pogosto brskam po temu forumu. Prišel je včeraj in sicer s otroci. To ne vem. Pogleda vse kar daš v odložišče. Nisem jo videl. Ona izgleda dobro. Pri zanikanju ne smete uporabljati tožilnik. Vlak gre v Ljubljano čez Zidani Most. Skočil je čez okno. Slovenija meji na avstrijo. Jaz pišem v Slovenščini vsak Torek. Novica, da je skupina 25 planincev hodila pod vodstvom gorskega vodnika je napačna in zavajujoča. Želim da poješ kosmizailo. Jaz pogosto brskam po temu forumu. Med tem ko je iskal ključe, so se odprla vrata. V takoimenovanem skladišču je bilo veliko ljudi. V sobi sta dve mize. Stekel je h mami. Videl sem Jurčič Micko. To je bil njegov življenski cilj. Po vrsti popravite vse kar želite. Preden zaspiva mi prebere pravljico. Prišel je s stricom. Oni zadanejo tarčo. Mi gremo teči po polju. Mi gremo peči kruh. Usedel se je k miza. Postreži kosilo! Skul je veslanje z dvemi vesli.</p>
|
<p>To velja tudi v Bledu. To se je zgodilo na velikemu vrtu. Prišel je na Kamnik. On je včeraj prišel z svojo torbo. Dve žemlje prosim. Pogosto brskam po temu forumu. Prišel je včeraj in sicer s otroci. To ne vem. Pogleda vse kar daš v odložišče. Nisem jo videl. Ona izgleda dobro. Pri zanikanju ne smete uporabljati tožilnik. Vlak gre v Ljubljano čez Zidani Most. Skočil je čez okno. Slovenija meji na avstrijo. Jaz pišem v Slovenščini vsak Torek. Novica, da je skupina 25 planincev hodila pod vodstvom gorskega vodnika je napačna in zavajujoča. Želim da poješ kosmizailo. Jaz pogosto brskam po temu forumu. Med tem ko je iskal ključe, so se odprla vrata. V takoimenovanem skladišču je bilo veliko ljudi. V sobi sta dve mize. Stekel je h mami. Videl sem Jurčič Micko. To je bil njegov življenski cilj. Po vrsti popravite vse kar želite. Preden zaspiva mi prebere pravljico. Prišel je s stricom. Oni zadanejo tarčo. Mi gremo teči po polju. Mi gremo peči kruh. Usedel se je k miza. Postreži kosilo! Skul je veslanje z dvemi vesli.</p>
|
||||||
<p>Na mizo nisem položil knjigo.</p>
|
<p>Na mizo nisem položil knjigo.</p>
|
||||||
</div>
|
</div>
|
||||||
|
<bes-popup-el/>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@ -17,5 +17,6 @@
|
|||||||
<p>To velja tudi v Bledu. To se je zgodilo na velikemu vrtu. Prišel je na Kamnik. On je včeraj prišel z svojo torbo. Dve žemlje prosim. Pogosto brskam po temu forumu. Prišel je včeraj in sicer s otroci. To ne vem. Pogleda vse kar daš v odložišče. Nisem jo videl. Ona izgleda dobro. Pri zanikanju ne smete uporabljati tožilnik. Vlak gre v Ljubljano čez Zidani Most. Skočil je čez okno. Slovenija meji na avstrijo. Jaz pišem v Slovenščini vsak Torek. Novica, da je skupina 25 planincev hodila pod vodstvom gorskega vodnika je napačna in zavajujoča. Želim da poješ kosmizailo. Jaz pogosto brskam po temu forumu. Med tem ko je iskal ključe, so se odprla vrata. V takoimenovanem skladišču je bilo veliko ljudi. V sobi sta dve mize. Stekel je h mami. Videl sem Jurčič Micko. To je bil njegov življenski cilj. Po vrsti popravite vse kar želite. Preden zaspiva mi prebere pravljico. Prišel je s stricom. Oni zadanejo tarčo. Mi gremo teči po polju. Mi gremo peči kruh. Usedel se je k miza. Postreži kosilo! Skul je veslanje z dvemi vesli.</p>
|
<p>To velja tudi v Bledu. To se je zgodilo na velikemu vrtu. Prišel je na Kamnik. On je včeraj prišel z svojo torbo. Dve žemlje prosim. Pogosto brskam po temu forumu. Prišel je včeraj in sicer s otroci. To ne vem. Pogleda vse kar daš v odložišče. Nisem jo videl. Ona izgleda dobro. Pri zanikanju ne smete uporabljati tožilnik. Vlak gre v Ljubljano čez Zidani Most. Skočil je čez okno. Slovenija meji na avstrijo. Jaz pišem v Slovenščini vsak Torek. Novica, da je skupina 25 planincev hodila pod vodstvom gorskega vodnika je napačna in zavajujoča. Želim da poješ kosmizailo. Jaz pogosto brskam po temu forumu. Med tem ko je iskal ključe, so se odprla vrata. V takoimenovanem skladišču je bilo veliko ljudi. V sobi sta dve mize. Stekel je h mami. Videl sem Jurčič Micko. To je bil njegov življenski cilj. Po vrsti popravite vse kar želite. Preden zaspiva mi prebere pravljico. Prišel je s stricom. Oni zadanejo tarčo. Mi gremo teči po polju. Mi gremo peči kruh. Usedel se je k miza. Postreži kosilo! Skul je veslanje z dvemi vesli.</p>
|
||||||
<p>Na mizo nisem položil knjigo.</p>
|
<p>Na mizo nisem položil knjigo.</p>
|
||||||
</div>
|
</div>
|
||||||
|
<bes-popup-el/>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
328
service2.js
328
service2.js
@ -1,4 +1,3 @@
|
|||||||
// TODO: Port popup dialog from service.js
|
|
||||||
// TODO: Test with contenteditable="plaintext-only"
|
// TODO: Test with contenteditable="plaintext-only"
|
||||||
// TODO: Implement <textarea> class
|
// TODO: Implement <textarea> class
|
||||||
// TODO: Port CKEditor class from service.js
|
// TODO: Port CKEditor class from service.js
|
||||||
@ -32,8 +31,8 @@ class BesService {
|
|||||||
this.originalSpellcheck = this.hostElement.spellcheck
|
this.originalSpellcheck = this.hostElement.spellcheck
|
||||||
this.hostElement.spellcheck = false
|
this.hostElement.spellcheck = false
|
||||||
|
|
||||||
this.handleScroll = () => this.onScroll()
|
this.onScroll = this.onScroll.bind(this)
|
||||||
this.hostElement.addEventListener('scroll', this.handleScroll)
|
this.hostElement.addEventListener('scroll', this.onScroll)
|
||||||
|
|
||||||
besServices.push(this)
|
besServices.push(this)
|
||||||
}
|
}
|
||||||
@ -43,7 +42,7 @@ class BesService {
|
|||||||
*/
|
*/
|
||||||
unregister() {
|
unregister() {
|
||||||
besServices = besServices.filter(item => item !== this)
|
besServices = besServices.filter(item => item !== this)
|
||||||
this.hostElement.removeEventListener('scroll', this.handleScroll)
|
this.hostElement.removeEventListener('scroll', this.onScroll)
|
||||||
this.hostElement.spellcheck = this.originalSpellcheck
|
this.hostElement.spellcheck = this.originalSpellcheck
|
||||||
this.clearCorrectionPanel()
|
this.clearCorrectionPanel()
|
||||||
}
|
}
|
||||||
@ -237,10 +236,12 @@ class BesDOMService extends BesService {
|
|||||||
constructor(hostElement) {
|
constructor(hostElement) {
|
||||||
super(hostElement)
|
super(hostElement)
|
||||||
this.results = [] // Results of grammar-checking, one per each block of text
|
this.results = [] // Results of grammar-checking, one per each block of text
|
||||||
this.handleBeforeInput = event => this.onBeforeInput(event)
|
this.onBeforeInput = this.onBeforeInput.bind(this)
|
||||||
this.hostElement.addEventListener('beforeinput', this.handleBeforeInput)
|
this.hostElement.addEventListener('beforeinput', this.onBeforeInput)
|
||||||
this.handleInput = () => this.onInput()
|
this.onInput = this.onInput.bind(this)
|
||||||
this.hostElement.addEventListener('input', this.handleInput)
|
this.hostElement.addEventListener('input', this.onInput)
|
||||||
|
this.onClick = this.onClick.bind(this)
|
||||||
|
this.hostElement.addEventListener('click', this.onClick)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -259,8 +260,9 @@ class BesDOMService extends BesService {
|
|||||||
* Unregisters grammar checking service.
|
* Unregisters grammar checking service.
|
||||||
*/
|
*/
|
||||||
unregister() {
|
unregister() {
|
||||||
this.hostElement.removeEventListener('input', this.handleInput)
|
this.hostElement.removeEventListener('click', this.onClick)
|
||||||
this.hostElement.removeEventListener('beforeinput', this.handleBeforeInput)
|
this.hostElement.removeEventListener('input', this.onInput)
|
||||||
|
this.hostElement.removeEventListener('beforeinput', this.onBeforeInput)
|
||||||
if (this.timer) clearTimeout(this.timer)
|
if (this.timer) clearTimeout(this.timer)
|
||||||
if (this.abortController) this.abortController.abort()
|
if (this.abortController) this.abortController.abort()
|
||||||
super.unregister()
|
super.unregister()
|
||||||
@ -727,7 +729,313 @@ class BesDOMService extends BesService {
|
|||||||
|
|
||||||
return nodes
|
return nodes
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called to report mouse click
|
||||||
|
*
|
||||||
|
* Displays or hides grammar mistake popup.
|
||||||
|
*
|
||||||
|
* @param {PointerEvent} event The event produced by a pointer such as the geometry of the contact point, the device type that generated the event, the amount of pressure that was applied on the contact surface, etc.
|
||||||
|
*/
|
||||||
|
onClick(event) {
|
||||||
|
const source = event?.detail !== 1 ? event?.detail : event
|
||||||
|
const target = this.getBlockParent(source.targetElement || source.target)
|
||||||
|
if (!target) return
|
||||||
|
|
||||||
|
const matches = this.results?.find(
|
||||||
|
child => child.element === target
|
||||||
|
)?.matches
|
||||||
|
if (matches) {
|
||||||
|
const popup = document.querySelector('bes-popup-el')
|
||||||
|
for (let m of matches) {
|
||||||
|
if (m.rects) {
|
||||||
|
for (let r of m.rects) {
|
||||||
|
if (
|
||||||
|
BesDOMService.isPointInRect(source.clientX, source.clientY, r)
|
||||||
|
) {
|
||||||
|
popup.changeMessage(m.match.message)
|
||||||
|
popup.appendReplacements(
|
||||||
|
target,
|
||||||
|
m,
|
||||||
|
this,
|
||||||
|
this.hostElement.contentEditable !== 'false'
|
||||||
|
)
|
||||||
|
popup.show(source.clientX, source.clientY)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
BesPopupEl.hide()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests if given coordinate is inside of a rectangle.
|
||||||
|
*
|
||||||
|
* @param {Number} x X coordinate
|
||||||
|
* @param {Number} y Y coordinate
|
||||||
|
* @param {DOMRect} rect Rectangle
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
static isPointInRect(x, y, rect) {
|
||||||
|
return rect.left <= x && x < rect.right && rect.top <= y && y < rect.bottom
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replaces grammar checking match with a suggestion provided by grammar checking service.
|
||||||
|
*
|
||||||
|
* @param {Element} el Block element containing grammar checking rule match
|
||||||
|
* @param {*} match Grammar checking rule match
|
||||||
|
* @param {String} replacement Text to replace grammar checking match with
|
||||||
|
*/
|
||||||
|
replaceText(el, match, replacement) {
|
||||||
|
if (this.timer) clearTimeout(this.timer)
|
||||||
|
if (this.abortController) this.abortController.abort()
|
||||||
|
this.clearProofing(el)
|
||||||
|
match.range.deleteContents()
|
||||||
|
match.range.insertNode(document.createTextNode(replacement))
|
||||||
|
this.onStartProofing()
|
||||||
|
this.proofNode(el, this.abortController)
|
||||||
|
if (this.proofingCount == 0) {
|
||||||
|
// No text blocks were discovered for proofing. onProofingProgress() will not be called
|
||||||
|
// and we need to notify manually.
|
||||||
|
this.onEndProofing()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*************************************************************************
|
||||||
|
*
|
||||||
|
* Grammar mistake popup dialog
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
class BesPopupEl extends HTMLElement {
|
||||||
|
constructor() {
|
||||||
|
super()
|
||||||
|
this.attachShadow({ mode: 'open' })
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
this.shadowRoot.innerHTML = `
|
||||||
|
<style>
|
||||||
|
:host {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
:host(.show){
|
||||||
|
z-index: 10;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
.popup-text {
|
||||||
|
max-width: 160px;
|
||||||
|
color: black;
|
||||||
|
text-align: center;
|
||||||
|
padding: 8px 0;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
.bes-popup-container {
|
||||||
|
visibility: hidden;
|
||||||
|
min-width: 200px;
|
||||||
|
max-width: 300px;
|
||||||
|
background-color: rgb(241, 243, 249);
|
||||||
|
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;
|
||||||
|
cursor: grab;
|
||||||
|
}
|
||||||
|
.bes-text-div{
|
||||||
|
background-color: white;
|
||||||
|
padding: 10px;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
.bes-replacement-btn{
|
||||||
|
margin: 4px 1px;
|
||||||
|
padding: 4px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 5px;
|
||||||
|
background-color: #239aff;
|
||||||
|
color: white;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.bes-replacement-btn:hover{
|
||||||
|
background-color: #1976f0;
|
||||||
|
}
|
||||||
|
: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="BesPopupEl.hide()">X</button>
|
||||||
|
</div>
|
||||||
|
<div class="bes-text-div">
|
||||||
|
<span class="popup-text">
|
||||||
|
</span>
|
||||||
|
<div class="bes-replacement-div">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shows popup window.
|
||||||
|
*
|
||||||
|
* @param {*} x X location hint
|
||||||
|
* @param {*} y Y location hint
|
||||||
|
*/
|
||||||
|
show(x, y) {
|
||||||
|
this.style.position = 'fixed'
|
||||||
|
|
||||||
|
// 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`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears all grammar mistake suggestions.
|
||||||
|
*/
|
||||||
|
clearReplacements() {
|
||||||
|
Array.from(
|
||||||
|
this.shadowRoot.querySelector('.bes-replacement-div')?.children
|
||||||
|
).forEach(replacement => replacement.remove())
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a grammar mistake suggestion.
|
||||||
|
*
|
||||||
|
* @param {Element} el Block element containing the grammar mistake
|
||||||
|
* @param {*} match Grammar checking rule match
|
||||||
|
* @param {BesService} service Grammar checking service
|
||||||
|
* @param {Boolean} allowReplacements Host element is mutable and grammar mistake may be replaced by suggestion
|
||||||
|
*/
|
||||||
|
appendReplacements(el, match, service, allowReplacements) {
|
||||||
|
const replacementDiv = this.shadowRoot.querySelector('.bes-replacement-div')
|
||||||
|
match.match.replacements.forEach(replacement => {
|
||||||
|
const replacementBtn = document.createElement('button')
|
||||||
|
replacementBtn.classList.add('bes-replacement-btn')
|
||||||
|
replacementBtn.textContent = replacement.value
|
||||||
|
replacementBtn.addEventListener('click', () => {
|
||||||
|
if (allowReplacements) {
|
||||||
|
service.replaceText(el, match, replacement.value)
|
||||||
|
BesPopupEl.hide()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
replacementDiv.appendChild(replacementBtn)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets grammar mistake description
|
||||||
|
*
|
||||||
|
* @param {String} text
|
||||||
|
*/
|
||||||
|
changeMessage(text) {
|
||||||
|
this.clearReplacements()
|
||||||
|
this.shadowRoot.querySelector('.popup-text').textContent = text
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the mousedown event.
|
||||||
|
*
|
||||||
|
* @param {MouseEvent} e Event
|
||||||
|
*/
|
||||||
|
onMouseDown(e) {
|
||||||
|
e.preventDefault()
|
||||||
|
this.initialMouseX = e.clientX
|
||||||
|
this.initialMouseY = e.clientY
|
||||||
|
this.handleMouseMove = this.onMouseMove.bind(this)
|
||||||
|
document.addEventListener('mousemove', this.handleMouseMove)
|
||||||
|
this.handleMouseUp = this.onMouseUp.bind(this)
|
||||||
|
document.addEventListener('mouseup', this.handleMouseUp)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the mousemove event.
|
||||||
|
*
|
||||||
|
* @param {MouseEvent} e Event
|
||||||
|
*/
|
||||||
|
onMouseMove(e) {
|
||||||
|
e.preventDefault()
|
||||||
|
|
||||||
|
let diffX = this.initialMouseX - e.clientX
|
||||||
|
this.initialMouseX = e.clientX
|
||||||
|
let left = this.offsetLeft - diffX
|
||||||
|
left = Math.max(0, Math.min(left, window.innerWidth - this.offsetWidth))
|
||||||
|
this.style.left = `${left}px`
|
||||||
|
|
||||||
|
let diffY = this.initialMouseY - e.clientY
|
||||||
|
this.initialMouseY = e.clientY
|
||||||
|
let top = this.offsetTop - diffY
|
||||||
|
top = Math.max(0, Math.min(top, window.innerHeight - this.offsetHeight))
|
||||||
|
this.style.top = `${top}px`
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the mouseup event.
|
||||||
|
*
|
||||||
|
* @param {MouseEvent} e Event
|
||||||
|
*/
|
||||||
|
onMouseUp(e) {
|
||||||
|
document.removeEventListener('mouseup', this.handleMouseUp)
|
||||||
|
document.removeEventListener('mousemove', this.handleMouseMove)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called each time the element is added to the document
|
||||||
|
*/
|
||||||
|
connectedCallback() {
|
||||||
|
this.render()
|
||||||
|
this.addEventListener('mousedown', this.onMouseDown)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dismisses all the popups.
|
||||||
|
*/
|
||||||
|
static hide() {
|
||||||
|
document
|
||||||
|
.querySelectorAll('bes-popup-el')
|
||||||
|
.forEach(popup => popup.classList.remove('show'))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
customElements.define('bes-popup-el', BesPopupEl)
|
||||||
|
|
||||||
// Auto-register all elements with bes-service class.
|
// Auto-register all elements with bes-service class.
|
||||||
window.addEventListener('load', () => {
|
window.addEventListener('load', () => {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user