Add some more documentation, upgrade addMistakeMarkup for multiline
This commit is contained in:
parent
bc0a3f7905
commit
259fc25f6f
190
online-editor.js
190
online-editor.js
@ -158,13 +158,11 @@ class BesEditor {
|
||||
}
|
||||
}
|
||||
|
||||
const { clientRects, highlight } = this.addMistakeMarkup(
|
||||
range,
|
||||
this.scrollPanel
|
||||
)
|
||||
const { clientRects, highlights } =
|
||||
this.addMistakeMarkup(range)
|
||||
matches.push({
|
||||
rects: clientRects,
|
||||
highlight: highlight,
|
||||
highlights: highlights,
|
||||
range: range,
|
||||
match: match
|
||||
})
|
||||
@ -238,9 +236,8 @@ class BesEditor {
|
||||
)
|
||||
})
|
||||
blockElements.forEach(block => {
|
||||
editor.clearProofed(block)
|
||||
editor.clearMistakeMarkup(block)
|
||||
editor.clearChildren(block)
|
||||
editor.removeChild(block)
|
||||
})
|
||||
// Not a nice way to do it, but it works for now the repositionMistakes function is called before the DOM updates are finished.
|
||||
setTimeout(() => {
|
||||
@ -251,78 +248,84 @@ class BesEditor {
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
// Test if given block element has already been grammar-proofed.
|
||||
/**
|
||||
* Tests if given block element has already been grammar-proofed.
|
||||
*
|
||||
* @param {Element} el DOM element to check
|
||||
* @returns {Boolean} true if the element has already been grammar-proofed; false otherwise.
|
||||
*/
|
||||
isProofed(el) {
|
||||
let filteredChildren = this.children.filter(child => child.elements === el)
|
||||
return filteredChildren[0]?.isProofed
|
||||
return this.children.find(child => child.elements === el)?.isProofed
|
||||
}
|
||||
|
||||
// Mark given block element as grammar-proofed.
|
||||
/**
|
||||
* Marks given block element as grammar-proofed.
|
||||
*
|
||||
* @param {Element} el DOM element that was checked
|
||||
* @param {Array} matches Grammar mistakes
|
||||
*/
|
||||
markProofed(el, matches) {
|
||||
let newChild = {
|
||||
this.removeChild(el)
|
||||
this.children.push({
|
||||
isProofed: true,
|
||||
elements: el, // TODO: Rename "elements" to "el" - 1. It contains only single element (plural elements is misleading), 2. BesEditor also uses "el" named field for DOM matching.
|
||||
matches: matches
|
||||
}
|
||||
|
||||
this.children = this.children.map(child =>
|
||||
child.elements === newChild.elements ? newChild : child
|
||||
)
|
||||
if (!this.children.some(child => child.elements === newChild.elements)) {
|
||||
this.children.push(newChild)
|
||||
}
|
||||
}
|
||||
|
||||
// Mark given block element as not grammar-proofed.
|
||||
clearProofed(el) {
|
||||
this.children
|
||||
.filter(child => child.elements === el)
|
||||
.forEach(child => {
|
||||
child.isProofed = false
|
||||
})
|
||||
}
|
||||
|
||||
// Remove all grammar mistakes markup for given block element.
|
||||
/**
|
||||
* Clears given block element as not grammar-proofed and removes all its grammar mistakes.
|
||||
*
|
||||
* @param {Element} el DOM element that we should re-grammar-proof
|
||||
*/
|
||||
clearMistakeMarkup(el) {
|
||||
this.children
|
||||
.filter(child => child.elements === el)
|
||||
.forEach(child => {
|
||||
let child = this.children.find(child => child.elements === el)
|
||||
if (!child) return
|
||||
child.isProofed = false
|
||||
child.matches.forEach(match => {
|
||||
match.highlight.remove()
|
||||
delete match.highlight
|
||||
})
|
||||
match.highlights.forEach(h => h.remove())
|
||||
delete match.highlights
|
||||
})
|
||||
}
|
||||
|
||||
// Remove all children from this.children array
|
||||
clearChildren(el) {
|
||||
if (el?.classList.contains('bes-online-editor')) return
|
||||
else this.children = this.children.filter(child => child.elements !== el)
|
||||
/**
|
||||
* Removes given block element from this.children array
|
||||
*
|
||||
* @param {Element} el DOM element for removal
|
||||
*/
|
||||
removeChild(el) {
|
||||
this.children = this.children.filter(child => child.elements !== el)
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates grammar mistake markup positions.
|
||||
*/
|
||||
repositionMistakes() {
|
||||
this.children.forEach(child => {
|
||||
this.clearMistakeMarkup(child.elements)
|
||||
child.matches.forEach(match => {
|
||||
const { clientRects, highlight } = this.addMistakeMarkup(
|
||||
match.range,
|
||||
this.scrollPanel
|
||||
)
|
||||
const { clientRects, highlights } = this.addMistakeMarkup(match.range)
|
||||
match.rects = clientRects
|
||||
match.highlight = highlight
|
||||
match.highlights = highlights
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// Adds grammar mistake markup
|
||||
addMistakeMarkup(range, scrollPanel) {
|
||||
/**
|
||||
* Adds grammar mistake markup.
|
||||
*
|
||||
* @param {Range} range Grammar mistake range
|
||||
* @returns {Object} Client rectangles and grammar mistake highlight elements
|
||||
*/
|
||||
addMistakeMarkup(range) {
|
||||
// TODO: Consider using range.getClientRects() instead of range.getBoundingClientRect()
|
||||
// In CKEditor case, the highlight element is not shown for some reason. But after resizing the window it is shown.
|
||||
const clientRects = range.getClientRects()
|
||||
const scrollPanelRect = scrollPanel.getBoundingClientRect()
|
||||
const highlight = document.createElement('div')
|
||||
const scrollPanelRect = this.scrollPanel.getBoundingClientRect()
|
||||
let highlights = []
|
||||
for (let i = 0, n = clientRects.length; i < n; ++i) {
|
||||
const rect = clientRects[i]
|
||||
const highlight = document.createElement('div')
|
||||
highlight.classList.add('bes-typo-mistake')
|
||||
const topPosition = rect.top - scrollPanelRect.top
|
||||
const leftPosition = rect.left - scrollPanelRect.left
|
||||
@ -331,11 +334,17 @@ class BesEditor {
|
||||
highlight.style.width = `${rect.width}px`
|
||||
highlight.style.height = `${rect.height}px`
|
||||
this.scrollPanel.appendChild(highlight)
|
||||
highlights.push(highlight)
|
||||
}
|
||||
return { clientRects, highlight }
|
||||
return { clientRects, highlights }
|
||||
}
|
||||
|
||||
// Tests if given element is block element.
|
||||
/**
|
||||
* Tests if given element is block element.
|
||||
*
|
||||
* @param {Element} el DOM element
|
||||
* @returns false if CSS display property is inline or inline-block; true otherwise.
|
||||
*/
|
||||
static isBlockElement(el) {
|
||||
switch (
|
||||
document.defaultView
|
||||
@ -351,7 +360,12 @@ class BesEditor {
|
||||
}
|
||||
}
|
||||
|
||||
// Returns first block parent element
|
||||
/**
|
||||
* Returns first block parent element of a node.
|
||||
*
|
||||
* @param {Node} el DOM node
|
||||
* @returns {Element} Innermost block element containing given node
|
||||
*/
|
||||
getBlockParent(el) {
|
||||
for (; el && el !== this.el; el = el.parentNode) {
|
||||
if (el.nodeType === Node.ELEMENT_NODE && BesEditor.isBlockElement(el))
|
||||
@ -360,6 +374,12 @@ class BesEditor {
|
||||
return el
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns next node in the DOM text flow.
|
||||
*
|
||||
* @param {Node} node DOM node
|
||||
* @returns {Node} Next node
|
||||
*/
|
||||
static getNextNode(node) {
|
||||
if (node.firstChild) return node.firstChild
|
||||
while (node) {
|
||||
@ -368,6 +388,12 @@ class BesEditor {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all ancestors of a node.
|
||||
*
|
||||
* @param {Node} node DOM node
|
||||
* @returns {Array} Array of all ancestors in reverse order: node, immediate parent, ..., document
|
||||
*/
|
||||
static getParents(node) {
|
||||
let parents = []
|
||||
do {
|
||||
@ -377,6 +403,12 @@ class BesEditor {
|
||||
return parents.reverse()
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all nodes marked by a range.
|
||||
*
|
||||
* @param {Range} range DOM range
|
||||
* @returns {Array} Array of nodes
|
||||
*/
|
||||
static getNodesInRange(range) {
|
||||
var start = range.startContainer
|
||||
var end = range.endContainer
|
||||
@ -425,15 +457,7 @@ class BesEditor {
|
||||
let editor = besEditors.find(e => e.el === edit)
|
||||
if (!editor) return
|
||||
const target = editor.getBlockParent(event.target)
|
||||
const popup = document.querySelector('bes-popup-el')
|
||||
const matches = editor.children.find(
|
||||
child => child.elements === target
|
||||
)?.matches
|
||||
if (
|
||||
!matches ||
|
||||
!editor.renderPopup(target, matches, popup, event.clientX, event.clientY)
|
||||
)
|
||||
popup.hide()
|
||||
editor.renderPopup(target, event.clientX, event.clientY)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -456,13 +480,13 @@ class BesEditor {
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the editor with grammar checking service the given DOM node is a child of.
|
||||
* Finds the editor with grammar checking service a DOM node is child of.
|
||||
*
|
||||
* @param {Node} target DOM node
|
||||
* @param {Node} el DOM node
|
||||
* @returns {Element} Editor DOM element; null if DOM node is not a descendant of an editor.
|
||||
*/
|
||||
static findParent(target) {
|
||||
for (let el = target; el; el = el.parentNode) {
|
||||
static findParent(el) {
|
||||
for (; el; el = el.parentNode) {
|
||||
if (el.classList?.contains('bes-online-editor')) {
|
||||
return el
|
||||
}
|
||||
@ -470,25 +494,42 @@ class BesEditor {
|
||||
return null
|
||||
}
|
||||
|
||||
renderPopup(el, matches, popup, clientX, clientY) {
|
||||
/**
|
||||
* Displays grammar mistake explanation popup.
|
||||
*
|
||||
* @param {*} el DOM element we have grammar proofing available for
|
||||
* @param {*} clientX Client X coordinate of the pointer event
|
||||
* @param {*} clientY Client Y coordinate of the pointer event
|
||||
*/
|
||||
renderPopup(el, clientX, clientY) {
|
||||
const popup = document.querySelector('bes-popup-el')
|
||||
const matches = this.children.find(
|
||||
child => child.elements === el
|
||||
)?.matches
|
||||
if (matches) {
|
||||
for (let m of matches) {
|
||||
if (m.rects) {
|
||||
for (let r of m.rects) {
|
||||
if (BesEditor.isPointInRect(clientX, clientY, r)) {
|
||||
popup.changeText(m.match.message)
|
||||
m.match.replacements.forEach(replacement => {
|
||||
popup.appendReplacements(el, r, m.match, replacement.value, this)
|
||||
popup.appendReplacements(
|
||||
el,
|
||||
r,
|
||||
m.match,
|
||||
replacement.value,
|
||||
this
|
||||
)
|
||||
})
|
||||
popup.show(clientX, clientY)
|
||||
return true
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
popup.hide()
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// TODO: In rich HTML texts, match.offset has different value than in plain text.
|
||||
// This function should be able to handle both cases or find a way that works for both.
|
||||
@ -592,12 +633,9 @@ window.onresize = () => {
|
||||
editor.children.forEach(child => {
|
||||
editor.clearMistakeMarkup(child.elements)
|
||||
child.matches.forEach(match => {
|
||||
const { clientRects, highlight } = editor.addMistakeMarkup(
|
||||
match.range,
|
||||
editor.scrollPanel
|
||||
)
|
||||
const { clientRects, highlights } = editor.addMistakeMarkup(match.range)
|
||||
match.rects = clientRects
|
||||
match.highlight = highlight
|
||||
match.highlights = highlights
|
||||
})
|
||||
})
|
||||
})
|
||||
|
Loading…
x
Reference in New Issue
Block a user