react-highlight-selector
Version:
Text highlighter with customizable palette and text copier
258 lines (257 loc) • 14.7 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.Highlighter = void 0;
var tslib_1 = require("tslib");
var jsx_runtime_1 = require("react/jsx-runtime");
var react_1 = require("react");
var client_1 = tslib_1.__importDefault(require("react-dom/client"));
var serialize_1 = require("../../libs/serialize");
var uid_1 = require("../../libs/uid");
var wrapperElements_1 = require("../../libs/wrapperElements");
var DeafultPopover_1 = tslib_1.__importDefault(require("../DeafultPopover"));
var UseSelection_1 = require("../../hooks/UseSelection");
var constants_1 = require("../../constants/constants");
var dom_1 = require("../../libs/dom");
var createRange_1 = require("../../libs/createRange");
var sort_1 = require("../../libs/sort");
var extractHighlightsFromHtml = function (htmlString, identifier) {
var preprocessedHtml = htmlString
.replace(/<div([^>]*)(\sid="selection-[^"]+")([^>]*)>/gi, '<span$1$2$3>')
.replace(/<\/div>/gi, '</span>');
var parser = new DOMParser();
var doc = parser.parseFromString(preprocessedHtml, 'text/html');
var initialSelections = {};
var highlightElements = doc.querySelectorAll('span[id^="selection-"]');
var data = [];
highlightElements.forEach(function (el) {
var _a, _b, _c, _d, _e, _f;
var id = el.getAttribute('id') || "selection-".concat((0, uid_1.generateId)());
var className = el.className || constants_1.defaultSelectionWrapperClassName;
var text = el.textContent || '';
var parent = el.parentElement;
if (parent && text) {
var prevSibling = el.previousSibling;
var nextSibling = el.nextSibling;
var leadingSpace = '';
var trailingSpace = '';
if ((prevSibling === null || prevSibling === void 0 ? void 0 : prevSibling.nodeType) === Node.TEXT_NODE) {
leadingSpace = ((_b = (_a = prevSibling.textContent) === null || _a === void 0 ? void 0 : _a.match(/\s+$/)) === null || _b === void 0 ? void 0 : _b.pop()) || '';
prevSibling.textContent = ((_c = prevSibling.textContent) === null || _c === void 0 ? void 0 : _c.replace(/\s+$/, '')) || '';
}
if ((nextSibling === null || nextSibling === void 0 ? void 0 : nextSibling.nodeType) === Node.TEXT_NODE) {
trailingSpace = ((_e = (_d = nextSibling.textContent) === null || _d === void 0 ? void 0 : _d.match(/^\s+/)) === null || _e === void 0 ? void 0 : _e.pop()) || '';
nextSibling.textContent = ((_f = nextSibling.textContent) === null || _f === void 0 ? void 0 : _f.replace(/^\s+/, '')) || '';
}
var textNode = doc.createTextNode("".concat(leadingSpace).concat(text).concat(trailingSpace));
parent.replaceChild(textNode, el);
var range = doc.createRange();
range.selectNodeContents(textNode);
// const meta = serializeRange(range, parent);
var meta = el.getAttribute('data-meta') || (0, serialize_1.serializeRange)(range, parent);
data.push({
id: id,
text: text,
className: className,
meta: meta,
startContainerText: text,
endContainerText: text,
});
}
});
initialSelections[identifier] = data;
var cleanedHtml = doc.body.innerHTML.trim();
// const cleanedHtml = `<div>` +doc.body.innerHTML.trim() + `</div>`;
console.log(doc.body.innerHTML, 'doc.body.innerHTML cleanedHtml', identifier, initialSelections);
// const cleanedHtml = `<div><p>hhhhh</p><p>question 101</p> question</div>`
return { cleanedHtml: cleanedHtml, initialSelections: initialSelections };
};
var Highlighter = function (_a) {
var htmlString = _a.htmlString, onClickHighlight = _a.onClickHighlight, disablePopover = _a.disablePopover, maxSelectionLength = _a.maxSelectionLength, minSelectionLength = _a.minSelectionLength, className = _a.className, PopoverChildren = _a.PopoverChildren, PopoverClassName = _a.PopoverClassName, selectionWrapperClassName = _a.selectionWrapperClassName, onSelection = _a.onSelection, onClick = _a.onClick, onCopy = _a.onCopy, disableMultiColorHighlight = _a.disableMultiColorHighlight, identifier = _a.identifier;
console.log(htmlString, 'removal string cleandHtml');
var _b = (0, react_1.useMemo)(function () { return extractHighlightsFromHtml(htmlString, identifier); }, [htmlString]), cleanedHtml = _b.cleanedHtml, initialSelections = _b.initialSelections;
var _c = (0, UseSelection_1.useSelections)(), selections = _c.selections, setSelections = _c.setSelections, addSelection = _c.addSelection, removeSelection = _c.removeSelection, updateSelection = _c.updateSelection;
// const rootRef = useRef<HTMLDivElement | null>(null)
// const tempRef = useRef<HTMLDivElement | null>(null)
// const div = document.createElement('div')
// tempRef.current = div
// tempRef.current.innerHTML = htmlString
(0, react_1.useEffect)(function () {
var _a, _b;
console.log('cleanedHtml comming here0', initialSelections, selections, cleanedHtml);
if ((!selections[identifier] || ((_a = selections[identifier]) === null || _a === void 0 ? void 0 : _a.length) === 0) &&
((_b = initialSelections[identifier]) === null || _b === void 0 ? void 0 : _b.length) > 0) {
console.log('cleanedHtml comming here1', initialSelections);
setSelections(initialSelections);
}
}, [initialSelections]);
var rootRef = (0, react_1.useRef)(null);
var tempRef = (0, react_1.useRef)(null);
var div = document.createElement('div');
tempRef.current = div;
tempRef.current.innerHTML = cleanedHtml;
console.log(cleanedHtml, 'cleanedHtml', selections, 'initialSelections', initialSelections);
var handleHoverAndClickEffects = function () {
if (!rootRef.current)
return;
rootRef.current.querySelectorAll('.hover-content-mark').forEach(function (mark) {
var _a;
var uniqueId = mark.getAttribute('data-hover-id');
var shortHtml = mark.getAttribute('data-short-html');
var longHtml = mark.getAttribute('data-long-html');
if (!uniqueId || (!shortHtml && !longHtml))
return;
var markElement = mark;
if (shortHtml && shortHtml.trim() !== '') {
var popup_1 = document.createElement('div');
popup_1.className = 'hover-content-popup';
popup_1.innerHTML = shortHtml + (longHtml ? "<button class=\"view-more\">View More</button>" : '');
markElement.appendChild(popup_1);
var timeout_1;
var showPopup = function () {
clearTimeout(timeout_1);
popup_1.style.display = 'block';
};
var hidePopup = function () {
timeout_1 = setTimeout(function () {
popup_1.style.display = 'none';
}, 100);
};
markElement.addEventListener('mouseenter', showPopup);
markElement.addEventListener('mouseleave', hidePopup);
popup_1.addEventListener('mouseenter', showPopup);
popup_1.addEventListener('mouseleave', hidePopup);
(_a = popup_1.querySelector('.view-more')) === null || _a === void 0 ? void 0 : _a.addEventListener('click', function () {
var _a;
var modal = document.createElement('div');
modal.className = 'modal';
modal.innerHTML = "\n <div class=\"modal-content\">\n <button class=\"close-button\">close</button>\n ".concat(longHtml, "\n </div>\n ");
document.body.appendChild(modal);
(_a = modal.querySelector('.close-button')) === null || _a === void 0 ? void 0 : _a.addEventListener('click', function () {
document.body.removeChild(modal);
});
});
}
else {
markElement.addEventListener('click', function (event) {
var _a;
event.preventDefault();
var modal = document.createElement('div');
modal.className = 'modal';
modal.innerHTML = "\n <div class=\"modal-content\">\n <button class=\"close-button\">close</button>\n ".concat(longHtml, "\n </div>\n ");
document.body.appendChild(modal);
(_a = modal.querySelector('.close-button')) === null || _a === void 0 ? void 0 : _a.addEventListener('click', function () {
document.body.removeChild(modal);
});
});
}
});
};
var getWrapper = (0, react_1.useCallback)(function (selection) {
var span = (0, wrapperElements_1.getSpanElement)({
className: (selection === null || selection === void 0 ? void 0 : selection.className) || constants_1.defaultSelectionWrapperClassName,
meta: (selection === null || selection === void 0 ? void 0 : selection.meta) || '',
});
if (!disablePopover) {
var popover_1 = (0, wrapperElements_1.getPopoverElement)({ className: PopoverClassName });
if (!PopoverClassName) {
span.onmouseover = function () {
popover_1.style.visibility = 'visible';
popover_1.style.opacity = '1';
};
span.onmouseout = function () {
popover_1.style.visibility = 'hidden';
popover_1.style.opacity = '0';
};
}
popover_1.id = "pop-".concat(selection.id);
span.appendChild(popover_1);
}
if (onClickHighlight) {
span.onclick = function (e) { return onClickHighlight(selection, e); };
}
span.id = selection.id;
return span;
}, [PopoverClassName, disablePopover, onClickHighlight]);
var handleMouseUp = function () {
// e.stopPropagation()
var selection = window.getSelection();
if (!selection)
return;
if (!minSelectionLength) {
minSelectionLength = constants_1.defaultMinSelectionLength;
}
if (minSelectionLength && (selection === null || selection === void 0 ? void 0 : selection.toString().length) < minSelectionLength)
return;
if (maxSelectionLength && (selection === null || selection === void 0 ? void 0 : selection.toString().length) > maxSelectionLength)
return;
var range = selection.getRangeAt(0);
if (!(0, dom_1.isHighlightable)(range))
return;
var expRange = (0, createRange_1.getOriginalRange)(range, tempRef.current);
if (!expRange)
return;
var _a = (0, createRange_1.getRangeStartEndContainerText)(range), startContainerText = _a.startContainerText, endContainerText = _a.endContainerText;
var newSelection = {
meta: (0, serialize_1.serializeRange)(expRange, tempRef.current),
text: range === null || range === void 0 ? void 0 : range.toString(),
id: "selection-".concat((0, uid_1.generateId)()),
className: selectionWrapperClassName || constants_1.defaultSelectionWrapperClassName,
startContainerText: startContainerText,
endContainerText: endContainerText,
};
addSelection(newSelection, identifier);
onSelection && onSelection(newSelection);
console.log('cleanedHtml selection --?/p', selections);
};
function manageCopy(selection) {
// const span = getSpanElement({
// className: selection.className || defaultSelectionWrapperClassName,
// })
console.log(selection, 'Coping');
onCopy && onCopy(selection);
}
(0, react_1.useEffect)(function () {
var sortedSelections = (0, sort_1.sortByPositionAndOffset)(selections[identifier]);
if (!rootRef.current)
return;
rootRef.current.innerHTML = '';
// rootRef.current.innerHTML = htmlString
rootRef.current.innerHTML = cleanedHtml;
handleHoverAndClickEffects();
if (sortedSelections && (sortedSelections === null || sortedSelections === void 0 ? void 0 : sortedSelections.length)) {
for (var i = 0; i < sortedSelections.length; i++) {
var item = sortedSelections[i];
var range = (0, serialize_1.deserializeRange)(item.meta, rootRef.current);
if (range) {
(0, dom_1.addHighlight)(range, getWrapper(item));
// onHiglightChange && onHiglightChange('')
}
var popoverRoot = document.getElementById("pop-".concat(item.id));
if (!popoverRoot)
return;
var root = client_1.default.createRoot(popoverRoot);
if (PopoverChildren) {
root.render((0, jsx_runtime_1.jsx)(PopoverChildren, { selection: item, removeSelection: removeSelection, updateSelection: updateSelection, handleCopy: function (selection) { return manageCopy(selection); }, disableMultiColorHighlight: disableMultiColorHighlight, identifier: identifier }));
}
else {
root.render((0, jsx_runtime_1.jsx)(DeafultPopover_1.default, { removeSelection: removeSelection, selection: item, updateSelection: updateSelection, handleCopy: function (selection) { return manageCopy(selection); }, disableMultiColorHighlight: disableMultiColorHighlight, identifier: identifier }));
}
}
}
}, [
selections,
getWrapper,
PopoverChildren,
// htmlString,
cleanedHtml,
removeSelection,
updateSelection,
disableMultiColorHighlight,
identifier,
]);
var memoizedChildren = (0, react_1.useMemo)(function () {
return ((0, jsx_runtime_1.jsx)("div", { ref: rootRef, id: identifier ? "highlighter-root" + identifier : "highlighter-root", onClick: onClick, onMouseUp: handleMouseUp, className: className }));
}, [onClick, handleMouseUp, className, identifier]);
return memoizedChildren;
};
exports.Highlighter = Highlighter;