import dayjs from "dayjs"; import Store from "../store.js"; class Suggestion { constructor( author, context, target, suggestion, fieldName, id = Date.now(), date = dayjs().format("DD/MM/YY"), time = dayjs().format("HH:mm") ) { this.author = author; this.context = context; this.target = target; this.suggestion = suggestion; this.fieldName = fieldName; this.id = id; this.date = date; this.time = time; this.node; this.highlightTargetInField(); } highlightTargetInField() { const highlight = document.querySelector(".fc__suggestion--edit"); if (highlight) { this.node = highlight; highlight.classList.remove("fc__suggestion--edit"); highlight.classList.add("fc__suggestion"); } else { const { target, fieldName } = this; const fieldElement = this.getFieldElement(fieldName); const textNodes = this.getTextNodes(fieldElement); this.highlightTarget(textNodes, target); } if (this.node) { this.node.addEventListener("mouseenter", () => { this._bubble = this.createBubble(); this.node.appendChild(this._bubble); }); this.node.addEventListener("mouseleave", () => { this.node.removeChild(this._bubble); }); } else { this.remove(); } } getFieldElement(fieldName) { return document.querySelector(`[data-field-name="${fieldName}"]`); } getTextNodes(fieldElement) { const textNodes = []; const childNodes = Array.from(fieldElement.childNodes); childNodes.forEach((node) => { if (node.nodeType === Node.TEXT_NODE && node.textContent.trim() !== "") { textNodes.push(node); } else if (node.hasChildNodes()) { textNodes.push(...this.getChildTextNodes(node)); } }); return textNodes; } highlightTarget(textNodes, target) { textNodes.forEach((textNode) => { const targetIndex = textNode.textContent.indexOf(target); if ( targetIndex >= 0 && this.matchContext(textNode, target, targetIndex) ) { this.node = this.insertHighlightedTarget(textNode, target, targetIndex); } else { // this.remove(); } }); } matchContext(textNode, target, targetIndex) { const beforeMatch = textNode.textContent.slice( Math.max(0, targetIndex - 30), targetIndex ); const afterMatch = textNode.textContent.slice( targetIndex + target.length, targetIndex + target.length + 30 ); return ( beforeMatch === this.context.before && afterMatch === this.context.after ); } insertHighlightedTarget(textNode, target, targetIndex) { const highlightedTarget = this.createHighlightedTarget(target); const beforeTarget = this.createTextNode( textNode.textContent.slice(0, targetIndex) ); const afterTarget = this.createTextNode( textNode.textContent.slice(targetIndex + target.length) ); textNode.parentNode.insertBefore(beforeTarget, textNode); textNode.parentNode.insertBefore(highlightedTarget, textNode); textNode.parentNode.insertBefore(afterTarget, textNode); textNode.remove(); return highlightedTarget; } createHighlightedTarget(target) { const highlightedTarget = document.createElement("span"); highlightedTarget.classList.add("fc__suggestion"); highlightedTarget.textContent = target; return highlightedTarget; } createTextNode(text) { return document.createTextNode(text); } getChildTextNodes(node) { const textNodes = []; const childNodes = Array.from(node.childNodes); childNodes.forEach((childNode) => { if ( childNode.nodeType === Node.TEXT_NODE && childNode.textContent.trim() !== "" ) { textNodes.push(childNode); } else if (childNode.hasChildNodes()) { textNodes.push(...this.getChildTextNodes(childNode)); } }); return textNodes; } createBubble() { const bubble = document.createElement("div"); bubble.className = "fc__suggestion-bubble"; bubble.innerHTML = `

${this.suggestion}

`; bubble.appendChild(this.createBtns()); return bubble; } createBtns() { const btns = document.createElement("div"); btns.classList.add("fc__suggestion-bubble__btns"); const decline = document.createElement("button"); decline.classList.add("fc__btn"); decline.innerHTML = ``; const accept = document.createElement("button"); accept.classList.add("fc__btn"); accept.innerHTML = ``; decline.addEventListener("click", () => { this.remove(); }); accept.addEventListener("click", () => { this.push(); }); btns.appendChild(decline); btns.appendChild(accept); return btns; } remove() { Store.suggestions = Store.suggestions.filter( (suggestion) => suggestion.id != this.id ); if (this.node !== undefined) { this.node.parentNode.removeChild(this.node); } Store.save("suggestions"); } push() { const field = this.getFieldElement(this.fieldName); const data = {}; data[this.fieldName] = field.dataset.content.replace( this.target, this.suggestion ); const init = { method: "PATCH", headers: { "X-CSRF": Store.csrf, }, body: JSON.stringify(data).replaceAll("_", ""), }; fetch("/api/pages/" + Store.page, init) .then((response) => response.json()) .then((response) => { console.log(response); console.log("Page comments successfully updated."); }) .catch((error) => { console.log(error); }); Store.suggestions = Store.suggestions.filter( (suggestion) => suggestion.id != this.id ); Store.save("suggestions"); this.node.outerHTML = this.suggestion; } } export default Suggestion;