Add some more documentation, upgrade addMistakeMarkup for multiline
This commit is contained in:
parent
bc0a3f7905
commit
259fc25f6f
210
online-editor.js
210
online-editor.js
@ -158,13 +158,11 @@ class BesEditor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const { clientRects, highlight } = this.addMistakeMarkup(
|
const { clientRects, highlights } =
|
||||||
range,
|
this.addMistakeMarkup(range)
|
||||||
this.scrollPanel
|
|
||||||
)
|
|
||||||
matches.push({
|
matches.push({
|
||||||
rects: clientRects,
|
rects: clientRects,
|
||||||
highlight: highlight,
|
highlights: highlights,
|
||||||
range: range,
|
range: range,
|
||||||
match: match
|
match: match
|
||||||
})
|
})
|
||||||
@ -238,9 +236,8 @@ class BesEditor {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
blockElements.forEach(block => {
|
blockElements.forEach(block => {
|
||||||
editor.clearProofed(block)
|
|
||||||
editor.clearMistakeMarkup(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.
|
// Not a nice way to do it, but it works for now the repositionMistakes function is called before the DOM updates are finished.
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
@ -251,78 +248,84 @@ class BesEditor {
|
|||||||
}, 1000)
|
}, 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) {
|
isProofed(el) {
|
||||||
let filteredChildren = this.children.filter(child => child.elements === el)
|
return this.children.find(child => child.elements === el)?.isProofed
|
||||||
return filteredChildren[0]?.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) {
|
markProofed(el, matches) {
|
||||||
let newChild = {
|
this.removeChild(el)
|
||||||
|
this.children.push({
|
||||||
isProofed: true,
|
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.
|
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
|
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) {
|
* Clears given block element as not grammar-proofed and removes all its grammar mistakes.
|
||||||
this.children
|
*
|
||||||
.filter(child => child.elements === el)
|
* @param {Element} el DOM element that we should re-grammar-proof
|
||||||
.forEach(child => {
|
*/
|
||||||
child.isProofed = false
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove all grammar mistakes markup for given block element.
|
|
||||||
clearMistakeMarkup(el) {
|
clearMistakeMarkup(el) {
|
||||||
this.children
|
let child = this.children.find(child => child.elements === el)
|
||||||
.filter(child => child.elements === el)
|
if (!child) return
|
||||||
.forEach(child => {
|
child.isProofed = false
|
||||||
child.matches.forEach(match => {
|
child.matches.forEach(match => {
|
||||||
match.highlight.remove()
|
match.highlights.forEach(h => h.remove())
|
||||||
delete match.highlight
|
delete match.highlights
|
||||||
})
|
})
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove all children from this.children array
|
/**
|
||||||
clearChildren(el) {
|
* Removes given block element from this.children array
|
||||||
if (el?.classList.contains('bes-online-editor')) return
|
*
|
||||||
else this.children = this.children.filter(child => child.elements !== el)
|
* @param {Element} el DOM element for removal
|
||||||
|
*/
|
||||||
|
removeChild(el) {
|
||||||
|
this.children = this.children.filter(child => child.elements !== el)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates grammar mistake markup positions.
|
||||||
|
*/
|
||||||
repositionMistakes() {
|
repositionMistakes() {
|
||||||
this.children.forEach(child => {
|
this.children.forEach(child => {
|
||||||
this.clearMistakeMarkup(child.elements)
|
this.clearMistakeMarkup(child.elements)
|
||||||
child.matches.forEach(match => {
|
child.matches.forEach(match => {
|
||||||
const { clientRects, highlight } = this.addMistakeMarkup(
|
const { clientRects, highlights } = this.addMistakeMarkup(match.range)
|
||||||
match.range,
|
|
||||||
this.scrollPanel
|
|
||||||
)
|
|
||||||
match.rects = clientRects
|
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()
|
// 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.
|
// 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 clientRects = range.getClientRects()
|
||||||
const scrollPanelRect = scrollPanel.getBoundingClientRect()
|
const scrollPanelRect = this.scrollPanel.getBoundingClientRect()
|
||||||
const highlight = document.createElement('div')
|
let highlights = []
|
||||||
for (let i = 0, n = clientRects.length; i < n; ++i) {
|
for (let i = 0, n = clientRects.length; i < n; ++i) {
|
||||||
const rect = clientRects[i]
|
const rect = clientRects[i]
|
||||||
|
const highlight = document.createElement('div')
|
||||||
highlight.classList.add('bes-typo-mistake')
|
highlight.classList.add('bes-typo-mistake')
|
||||||
const topPosition = rect.top - scrollPanelRect.top
|
const topPosition = rect.top - scrollPanelRect.top
|
||||||
const leftPosition = rect.left - scrollPanelRect.left
|
const leftPosition = rect.left - scrollPanelRect.left
|
||||||
@ -331,11 +334,17 @@ class BesEditor {
|
|||||||
highlight.style.width = `${rect.width}px`
|
highlight.style.width = `${rect.width}px`
|
||||||
highlight.style.height = `${rect.height}px`
|
highlight.style.height = `${rect.height}px`
|
||||||
this.scrollPanel.appendChild(highlight)
|
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) {
|
static isBlockElement(el) {
|
||||||
switch (
|
switch (
|
||||||
document.defaultView
|
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) {
|
getBlockParent(el) {
|
||||||
for (; el && el !== this.el; el = el.parentNode) {
|
for (; el && el !== this.el; el = el.parentNode) {
|
||||||
if (el.nodeType === Node.ELEMENT_NODE && BesEditor.isBlockElement(el))
|
if (el.nodeType === Node.ELEMENT_NODE && BesEditor.isBlockElement(el))
|
||||||
@ -360,6 +374,12 @@ class BesEditor {
|
|||||||
return el
|
return el
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns next node in the DOM text flow.
|
||||||
|
*
|
||||||
|
* @param {Node} node DOM node
|
||||||
|
* @returns {Node} Next node
|
||||||
|
*/
|
||||||
static getNextNode(node) {
|
static getNextNode(node) {
|
||||||
if (node.firstChild) return node.firstChild
|
if (node.firstChild) return node.firstChild
|
||||||
while (node) {
|
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) {
|
static getParents(node) {
|
||||||
let parents = []
|
let parents = []
|
||||||
do {
|
do {
|
||||||
@ -377,6 +403,12 @@ class BesEditor {
|
|||||||
return parents.reverse()
|
return parents.reverse()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns all nodes marked by a range.
|
||||||
|
*
|
||||||
|
* @param {Range} range DOM range
|
||||||
|
* @returns {Array} Array of nodes
|
||||||
|
*/
|
||||||
static getNodesInRange(range) {
|
static getNodesInRange(range) {
|
||||||
var start = range.startContainer
|
var start = range.startContainer
|
||||||
var end = range.endContainer
|
var end = range.endContainer
|
||||||
@ -425,15 +457,7 @@ class BesEditor {
|
|||||||
let editor = besEditors.find(e => e.el === edit)
|
let editor = besEditors.find(e => e.el === edit)
|
||||||
if (!editor) return
|
if (!editor) return
|
||||||
const target = editor.getBlockParent(event.target)
|
const target = editor.getBlockParent(event.target)
|
||||||
const popup = document.querySelector('bes-popup-el')
|
editor.renderPopup(target, event.clientX, event.clientY)
|
||||||
const matches = editor.children.find(
|
|
||||||
child => child.elements === target
|
|
||||||
)?.matches
|
|
||||||
if (
|
|
||||||
!matches ||
|
|
||||||
!editor.renderPopup(target, matches, popup, event.clientX, event.clientY)
|
|
||||||
)
|
|
||||||
popup.hide()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -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.
|
* @returns {Element} Editor DOM element; null if DOM node is not a descendant of an editor.
|
||||||
*/
|
*/
|
||||||
static findParent(target) {
|
static findParent(el) {
|
||||||
for (let el = target; el; el = el.parentNode) {
|
for (; el; el = el.parentNode) {
|
||||||
if (el.classList?.contains('bes-online-editor')) {
|
if (el.classList?.contains('bes-online-editor')) {
|
||||||
return el
|
return el
|
||||||
}
|
}
|
||||||
@ -470,24 +494,41 @@ class BesEditor {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
renderPopup(el, matches, popup, clientX, clientY) {
|
/**
|
||||||
for (let m of matches) {
|
* Displays grammar mistake explanation popup.
|
||||||
if (m.rects) {
|
*
|
||||||
for (let r of m.rects) {
|
* @param {*} el DOM element we have grammar proofing available for
|
||||||
if (BesEditor.isPointInRect(clientX, clientY, r)) {
|
* @param {*} clientX Client X coordinate of the pointer event
|
||||||
popup.changeText(m.match.message)
|
* @param {*} clientY Client Y coordinate of the pointer event
|
||||||
m.match.replacements.forEach(replacement => {
|
*/
|
||||||
popup.appendReplacements(el, r, m.match, replacement.value, this)
|
renderPopup(el, clientX, clientY) {
|
||||||
})
|
const popup = document.querySelector('bes-popup-el')
|
||||||
popup.show(clientX, clientY)
|
const matches = this.children.find(
|
||||||
return true
|
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.show(clientX, clientY)
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
popup.hide()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false
|
popup.hide()
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: In rich HTML texts, match.offset has different value than in plain text.
|
// TODO: In rich HTML texts, match.offset has different value than in plain text.
|
||||||
@ -592,12 +633,9 @@ window.onresize = () => {
|
|||||||
editor.children.forEach(child => {
|
editor.children.forEach(child => {
|
||||||
editor.clearMistakeMarkup(child.elements)
|
editor.clearMistakeMarkup(child.elements)
|
||||||
child.matches.forEach(match => {
|
child.matches.forEach(match => {
|
||||||
const { clientRects, highlight } = editor.addMistakeMarkup(
|
const { clientRects, highlights } = editor.addMistakeMarkup(match.range)
|
||||||
match.range,
|
|
||||||
editor.scrollPanel
|
|
||||||
)
|
|
||||||
match.rects = clientRects
|
match.rects = clientRects
|
||||||
match.highlight = highlight
|
match.highlights = highlights
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
Loading…
x
Reference in New Issue
Block a user