hi-mention
Version:
纯JS聊天框,兼容原生HTML/React/Vue等各种框架;支持@提及功能、插入富文本等多功能编辑器;内置H5和PC的交互样式
599 lines (598 loc) • 23.9 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getSelection = getSelection;
exports.getRangeAt = getRangeAt;
exports.moveRangeAtEditorEnd = moveRangeAtEditorEnd;
exports.moveRangeAtRowEnd = moveRangeAtRowEnd;
exports.moveRangeAtRowStart = moveRangeAtRowStart;
exports.rangeEls = rangeEls;
exports.isRangeAtRowEnd = isRangeAtRowEnd;
exports.isRangeAtTextEnd = isRangeAtTextEnd;
exports.isRangeAtRowStart = isRangeAtRowStart;
exports.isRangeAtTextStart = isRangeAtTextStart;
exports.fixTextContent = fixTextContent;
exports.removeRangeContent = removeRangeContent;
exports.insertText = insertText;
exports.insertElement = insertElement;
var _1 = require(".");
var const_1 = require("../const");
function getSelection() {
if (window.getSelection) {
return window.getSelection();
}
return null;
}
/**
* 获取光标位置
* @param index
* @returns
*/
function getRangeAt(index, selection) {
if (index === void 0) { index = 0; }
if (selection === void 0) { selection = getSelection(); }
try {
var range = selection === null || selection === void 0 ? void 0 : selection.getRangeAt(index);
if (!range) {
range = document.createRange();
range.selectNodeContents(document.body);
range.collapse(false);
selection === null || selection === void 0 ? void 0 : selection.removeAllRanges();
selection === null || selection === void 0 ? void 0 : selection.addRange(range);
}
// 如果光标没有选中内容,修正光标位置
if (range.collapsed) {
rangeEls(range);
}
return range;
}
catch (error) {
console.error("编辑器未获取焦点");
return null;
}
}
/**
* 将光标移动到编辑器末尾
*/
function moveRangeAtEditorEnd(range, editorEl) {
// 获取最后一行
var lastLine = editorEl.children[editorEl.children.length - 1];
// 如果不是行元素
if (!lastLine || lastLine.className !== const_1.ROW_TAG_CLASS) {
// 修正标签
lastLine = (0, _1.createElement)("p", { className: const_1.ROW_TAG_CLASS, content: (0, _1.createElement)("span", { className: const_1.TEXT_TAG_CLASS, content: const_1.NEW_LINE }) });
// 添加到编辑器末尾
editorEl.appendChild(lastLine);
}
moveRangeAtRowEnd(range, lastLine);
}
/**
* 将光标位置移动到当前行的末尾
*/
function moveRangeAtRowEnd(range, rowEl, merge) {
var _a;
if (merge === void 0) { merge = true; }
// 获取最后一个文本标签
var lastText = rowEl.children[rowEl.children.length - 1];
if (!lastText || lastText.className !== const_1.TEXT_TAG_CLASS) {
// 修正标签
lastText = (0, _1.createElement)("span", { className: const_1.TEXT_TAG_CLASS, content: rowEl.children.length > 0 ? const_1.PLACEHOLDER_TEXT : const_1.NEW_LINE });
// 添加到行末尾
rowEl.appendChild(lastText);
}
// 获取最后一个文本标签的长度
var textNode = lastText.lastChild;
var lastTextLength = (textNode ? (_a = textNode.textContent) === null || _a === void 0 ? void 0 : _a.length : lastText.childNodes.length) || 0;
// 设置光标位置
range.setEnd(textNode ? textNode : lastText, lastTextLength);
if (merge)
range.collapse();
}
/**
* 将光标位置移动到当前行的开头
*/
function moveRangeAtRowStart(range, rowEl, merge) {
if (merge === void 0) { merge = true; }
// 获取第一个文本标签
var firstText = rowEl.children[0];
if (!firstText || firstText.className !== const_1.TEXT_TAG_CLASS) {
// 修正标签
firstText = (0, _1.createTextTag)(rowEl.children.length > 0 ? const_1.PLACEHOLDER_TEXT : const_1.NEW_LINE);
// 添加到行开头
rowEl.prepend(firstText);
}
// 设置光标位置
var textNode = firstText.firstChild;
range.setStart(textNode ? textNode : firstText, 0);
if (merge)
range.collapse(true);
}
/**
* 修正光标位置,并获取当前光标所在的编辑器/行/文本
*/
function rangeEls(range, which) {
var _a, _b, _c;
if (which === void 0) { which = "common"; }
// 光标所在的编辑器
var editorEl = null;
// 光标所在的行
var rowEl = null;
// 光标所在的文本
var textEl = null;
var rangeEl;
var rangeIndex = which === "start" ? range.startOffset : range.endOffset;
if (which === "end") {
rangeEl = range.endContainer;
}
else if (which === "start") {
rangeEl = range.startContainer;
}
else {
rangeEl = range.commonAncestorContainer;
}
if (!rangeEl)
return null;
// 向上找节点,最多找10层
for (var i = 0, el = rangeEl; i < 10 && !editorEl; i++) {
if (!el)
break;
if (el.className === const_1.TEXT_TAG_CLASS || ((_a = el.parentElement) === null || _a === void 0 ? void 0 : _a.className) === const_1.ROW_TAG_CLASS) {
textEl = el;
}
else if (el.className === const_1.ROW_TAG_CLASS) {
rowEl = el;
}
else if (el.className === const_1.EDITOR_CLASS) {
editorEl = el;
break;
}
el = el.parentElement;
}
if (!editorEl)
return null;
if (!rowEl || rowEl.className !== const_1.ROW_TAG_CLASS) {
// 如果不在行内,则将当前光标所在的内容全部转移到新的一行
var textContent = "";
if (rangeEl.className !== const_1.EDITOR_CLASS && rangeEl.className !== const_1.ROW_TAG_CLASS) {
textContent = rangeEl.textContent || "";
rangeEl.remove();
}
var index_1 = rangeIndex > editorEl.childNodes.length - 1 ? editorEl.childNodes.length - 1 : rangeIndex;
rowEl = editorEl.childNodes[index_1];
if (!rowEl || rowEl.className !== const_1.ROW_TAG_CLASS) {
textEl = (0, _1.createTextTag)(textContent ? textContent : const_1.NEW_LINE);
rowEl = (0, _1.createElement)("p", { className: const_1.ROW_TAG_CLASS });
rowEl.appendChild(textEl);
editorEl.appendChild(rowEl);
}
if (!textEl) {
textEl = rowEl.children[rowEl.children.length - 1];
if (textEl.className !== const_1.TEXT_TAG_CLASS) {
textEl = (0, _1.createTextTag)(textContent ? textContent : const_1.NEW_LINE);
rowEl.appendChild(textEl);
}
}
if (which === "start") {
rangeIndex = 0;
moveRangeAtRowStart(range, rowEl, false);
}
else {
rangeIndex = rowEl.childNodes.length;
moveRangeAtRowEnd(range, rowEl, which === "common");
}
}
if (!textEl || textEl.nodeName === "#text") {
// 如果不在行内,则将当前光标所在的内容全部转移到新的一行
var textContent = "";
if (rangeEl.className !== const_1.EDITOR_CLASS && rangeEl.className !== const_1.ROW_TAG_CLASS) {
textContent = rangeEl.textContent || "";
rangeEl.remove();
}
textEl = (0, _1.createTextTag)(textContent ? textContent : rowEl.children.length > 0 ? const_1.PLACEHOLDER_TEXT : const_1.NEW_LINE);
rowEl.appendChild(textEl);
if (which === "start") {
rangeIndex = 0;
moveRangeAtRowStart(range, rowEl, false);
}
else {
rangeIndex = rowEl.childNodes.length;
moveRangeAtRowEnd(range, rowEl, which === "common");
}
}
var _while = 0; // 防止死循环
while (textEl.className !== const_1.TEXT_TAG_CLASS && _while < 10) {
if (which === "start") {
textEl = (textEl.previousElementSibling ? textEl.previousElementSibling : textEl);
var textNode_1 = textEl.lastChild;
range.setStart(textNode_1 ? textNode_1 : textEl, (textNode_1 ? (_b = textNode_1.textContent) === null || _b === void 0 ? void 0 : _b.length : textEl.childNodes.length) || 0);
}
else {
textEl = (textEl.nextElementSibling ? textEl.nextElementSibling : textEl);
var textNode_2 = textEl.firstChild;
range.setEnd(textNode_2 ? textNode_2 : textEl, 0);
}
_while++;
}
if (_while) {
rangeIndex = which === "start" ? 0 : rowEl.childNodes.length;
}
// 查找当前光标,如果光标不在文本标签中,修正光标位置
var textNode = null;
var index = 0;
if (rangeEl === rowEl) {
var _textEl = (which === "start" ? rowEl.childNodes[0] : rowEl.childNodes[rangeIndex - 1]);
if ((_textEl === null || _textEl === void 0 ? void 0 : _textEl.className) !== const_1.TEXT_TAG_CLASS) {
_textEl = (which === "start" ? rowEl.firstElementChild : rowEl.lastElementChild);
}
textNode = (which === "start" ? _textEl.firstElementChild : _textEl.lastElementChild);
}
else if (rangeEl === textEl) {
textNode = (which === "start" ? textEl.firstElementChild : textEl.lastElementChild);
}
if (textNode) {
index = which === "start" ? 0 : ((_c = textNode.textContent) === null || _c === void 0 ? void 0 : _c.length) || 0;
if (which === "start") {
range.setStart(textNode, index);
}
else {
range.setEnd(textNode, index);
}
if (which === "common")
range.collapse(false);
}
return { editorEl: editorEl, rowEl: rowEl, textEl: textEl };
}
/**
* 判断光标是否在当前行的末尾
*/
function isRangeAtRowEnd(range, rowEl) {
if (range.startContainer === rowEl && range.startOffset === rowEl.childNodes.length)
return true;
var els = rangeEls(range);
if (!els)
return false;
if (els.rowEl !== rowEl)
return false;
if (els.textEl !== rowEl.children[rowEl.children.length - 1])
return false;
return isRangeAtTextEnd(range);
}
/**
* 判断光标是否在当前文本元素末尾
*/
function isRangeAtTextEnd(range) {
var _a, _b, _c, _d, _e;
var rangeEl = range.endContainer;
var textEl = rangeEl;
if (textEl.className !== const_1.TEXT_TAG_CLASS && textEl.parentElement) {
textEl = textEl.parentElement;
}
if (rangeEl.className === const_1.TEXT_TAG_CLASS && range.endOffset === rangeEl.childNodes.length)
return true;
if (Number((_a = textEl.textContent) === null || _a === void 0 ? void 0 : _a.length) === 0)
return true;
if (Number((_b = textEl.textContent) === null || _b === void 0 ? void 0 : _b.length) === 1) {
if (textEl.textContent === const_1.PLACEHOLDER_TEXT)
return true;
if (textEl.textContent === "\n")
return true;
if (rangeEl.nodeName === "BR")
return true;
}
if (rangeEl.nodeName === "#text") {
if (range.endOffset < Number((_c = rangeEl.textContent) === null || _c === void 0 ? void 0 : _c.length))
return false;
var nodeIndex = Array.from(((_d = rangeEl.parentElement) === null || _d === void 0 ? void 0 : _d.childNodes) || []).indexOf(rangeEl);
for (var i = nodeIndex + 1; i < textEl.childNodes.length; i++) {
if ((_e = textEl.childNodes[i].textContent) === null || _e === void 0 ? void 0 : _e.trim())
return false;
}
return true;
}
return false;
}
/**
* 判断光标是否在当前行的开头
*/
function isRangeAtRowStart(range, rowEl) {
// 如果不是0宽占位符就必须是0
if (range.startContainer === rowEl && range.startOffset === 0)
return true;
if (range.commonAncestorContainer.textContent !== const_1.PLACEHOLDER_TEXT && range.startOffset !== 0)
return false;
var els = rangeEls(range);
if (!els)
return false;
if (els.rowEl !== rowEl)
return false;
if (els.textEl !== rowEl.children[0])
return false;
return isRangeAtTextStart(range);
}
/**
* 判断光标是否在当前文本元素开头
*/
function isRangeAtTextStart(range) {
var _a, _b, _c, _d;
var rangeEl = range.startContainer;
var textEl = rangeEl;
if (textEl.className !== const_1.TEXT_TAG_CLASS && textEl.parentElement) {
textEl = textEl.parentElement;
}
if (rangeEl.className === const_1.TEXT_TAG_CLASS && range.endOffset === 0)
return true;
if (Number((_a = textEl.textContent) === null || _a === void 0 ? void 0 : _a.length) === 0)
return true;
if (Number((_b = textEl.textContent) === null || _b === void 0 ? void 0 : _b.length) === 1) {
if (textEl.textContent === const_1.PLACEHOLDER_TEXT)
return true;
if (textEl.textContent === "\n")
return true;
if (rangeEl.nodeName === "BR")
return true;
}
if (range.startOffset > 0)
return false;
if (rangeEl.nodeName === "#text") {
var nodeIndex = Array.from(((_c = rangeEl.parentElement) === null || _c === void 0 ? void 0 : _c.childNodes) || []).indexOf(rangeEl);
for (var i = 0; i < nodeIndex; i++) {
if ((_d = textEl.childNodes[i].textContent) === null || _d === void 0 ? void 0 : _d.trim())
return false;
}
return true;
}
return false;
}
/**
* 修正文本标签内容
*/
function fixTextContent(range, els) {
var _a, _b, _c, _d;
var _els = els || rangeEls(range);
if (!_els)
return;
var rowEl = _els.rowEl, textEl = _els.textEl;
if (range.startContainer.nodeName === "BR") {
var br_1 = range.startContainer;
var text = (0, _1.createTextNode)();
// 将text内容插入到range.startContainer标签之前
(_a = br_1.parentElement) === null || _a === void 0 ? void 0 : _a.insertBefore(text, br_1);
range.setStart(text, 0);
range.collapse(true);
var content = textEl.textContent || "";
if (content && content !== "\n") {
(_b = br_1.parentElement) === null || _b === void 0 ? void 0 : _b.removeChild(br_1);
}
return;
}
var br = rowEl.querySelector("br");
while (Number((_c = rowEl.textContent) === null || _c === void 0 ? void 0 : _c.trim().length) > 1 && br) {
br.remove();
br = rowEl.querySelector("br");
}
if (!(0, _1.isNeedFix)(textEl))
return;
if (textEl.className !== const_1.TEXT_TAG_CLASS)
return;
if (Number((_d = textEl.textContent) === null || _d === void 0 ? void 0 : _d.length) <= 1)
return;
var rangeEl = range.startContainer;
var rangeIndex = range.startOffset;
var nodeIndex = 0;
if (rangeEl === textEl) {
nodeIndex = rangeIndex - 1;
rangeIndex = 0;
}
else if (rangeEl.nodeName === "#text") {
nodeIndex = Array.from(textEl.childNodes).indexOf(rangeEl);
}
// 获取光标两边的内容
var prevText = "";
var nextText = "";
textEl.childNodes.forEach(function (node, index) {
var _a, _b;
if (index < nodeIndex) {
prevText += node.textContent || "";
}
else if (index > nodeIndex) {
nextText += node.textContent || "";
}
else {
prevText += ((_a = node.textContent) === null || _a === void 0 ? void 0 : _a.slice(0, rangeIndex)) || "";
nextText += ((_b = node.textContent) === null || _b === void 0 ? void 0 : _b.slice(rangeIndex)) || "";
}
});
prevText = (0, _1.formatString)(prevText);
nextText = (0, _1.formatString)(nextText);
var textNode = (0, _1.createTextNode)(prevText + nextText);
textEl.innerHTML = "";
textEl.appendChild(textNode);
range.setStart(textNode, prevText.length);
range.collapse(true);
}
/**
* 删除选中区域内的所有元素
*/
function removeRangeContent(range, _a) {
var _b, _c, _d, _e;
var _f = _a === void 0 ? {} : _a, startEls = _f.startEls, endEls = _f.endEls, mergeRow = _f.mergeRow;
if (range.collapsed)
return ""; // 没有选中内容
var sEls = startEls ? startEls : rangeEls(range, "start");
var eEls = endEls ? endEls : rangeEls(range, "end");
if (!sEls || !eEls)
return "";
// 不在同一个编辑器内
if (sEls.editorEl !== eEls.editorEl)
return "";
// 如果是同一个文本标签
if (sEls.textEl === eEls.textEl) {
// 是标准文本标签
if (sEls.textEl.className === const_1.TEXT_TAG_CLASS) {
var rangeEl = range.startContainer;
var rangeIndex = range.startOffset;
// 获取选中内容
var content_1 = range.extractContents().textContent || "";
// 删除选中内容
range.deleteContents();
range.setStart(rangeEl, rangeIndex);
range.collapse(true);
return content_1;
}
var content = sEls.textEl.textContent || "";
// 删除标签
sEls.textEl.remove();
return content;
}
var startContent = ""; // 前面删除的内容
// 获取开始标签下标
var sIndex = Array.from(sEls.rowEl.children).indexOf(sEls.textEl);
// 如果开始标签是标准文本标签
if (sEls.textEl.className === const_1.TEXT_TAG_CLASS) {
startContent += ((_b = sEls.textEl.textContent) === null || _b === void 0 ? void 0 : _b.slice(range.startOffset)) || "";
// 保留光标之前的内容
var context = (_c = sEls.textEl.textContent) === null || _c === void 0 ? void 0 : _c.slice(0, range.startOffset);
context = (0, _1.formatString)(context);
if (context) {
sIndex += 1; // 如果还有内容,要删除的开始标签往后移一位
sEls.textEl.textContent = context;
var textNode = sEls.textEl.lastChild;
range.setStart(textNode ? textNode : sEls.textEl, textNode ? context.length : sEls.textEl.childNodes.length);
}
}
// 获取结束标签下标
var eIndex = Array.from(eEls.rowEl.children).indexOf(eEls.textEl);
// 如果结束标签是标准文本标签
var endContent = ""; // 后面的内容
if (eEls.textEl.className === const_1.TEXT_TAG_CLASS) {
endContent += ((_d = eEls.textEl.textContent) === null || _d === void 0 ? void 0 : _d.slice(0, range.endOffset)) || "";
// 保留光标之后的内容
var context = (_e = eEls.textEl.textContent) === null || _e === void 0 ? void 0 : _e.slice(range.endOffset);
context = (0, _1.formatString)(context);
if (context) {
eIndex -= 1; // 如果还有内容,要删除的结束标签往前移一位
eEls.textEl.textContent = context;
range.setEnd(eEls.textEl, 0);
}
}
// 如果是同一行
if (sEls.rowEl === eEls.rowEl) {
// 删除包括开始和结束的所有内容
for (var i = eIndex; i >= sIndex; i--) {
endContent = (sEls.rowEl.children[i].textContent || "") + endContent;
sEls.rowEl.children[i].remove();
}
range.collapse(true);
return startContent + endContent;
}
// 删除开始行和结束行之间的所有行
var swIndex = Array.from(sEls.editorEl.children).indexOf(sEls.rowEl);
var ewIndex = Array.from(eEls.editorEl.children).indexOf(eEls.rowEl);
var centerContent = ""; // 中间的内容
for (var i = ewIndex - 1; i > swIndex; i--) {
centerContent = (sEls.editorEl.children[i].textContent || "") + centerContent;
sEls.editorEl.children[i].remove();
}
// 从开始文本标签开始删除开始行的所有内容
for (var i = sEls.rowEl.children.length - 1; i >= sIndex; i--) {
centerContent = (sEls.rowEl.children[i].textContent || "") + centerContent;
sEls.rowEl.children[i].remove();
}
// 删除结束行的从头到结束标签之前的内容
for (var i = eIndex; i >= 0; i--) {
endContent = (eEls.rowEl.children[i].textContent || "") + endContent;
eEls.rowEl.children[i].remove();
}
if (mergeRow) {
// 将光标移动到开始标签的末尾
moveRangeAtRowEnd(range, sEls.rowEl);
// 将结束行的所有内容添加到开始行的末尾
(0, _1.transferElement)(eEls.rowEl, sEls.rowEl);
// 删除结束标签
eEls.rowEl.remove();
}
range.collapse(true);
return startContent + centerContent + endContent;
}
/**
* 在光标位置插入文本
* @param text 文本内容
* @param range 光标位置
* @returns
*/
function insertText(text, range) {
var _a;
var commonEl = range.commonAncestorContainer;
if (!["BR", "#text"].includes(commonEl === null || commonEl === void 0 ? void 0 : commonEl.nodeName) && commonEl.className !== const_1.TEXT_TAG_CLASS)
return false;
if (commonEl.nodeName === "BR") {
var els = rangeEls(range);
if (!els)
return false;
(_a = commonEl.parentElement) === null || _a === void 0 ? void 0 : _a.removeChild(commonEl);
els.textEl.innerHTML = text;
fixTextContent(range, els);
return true;
}
range.insertNode((0, _1.createTextNode)(text));
return true;
}
/**
* 在文本内容中插入元素
* @param el 需要插入的元素
* @param range 光标位置
* @returns
*/
function insertElement(el, range) {
var _a, _b;
var commonEl = range.commonAncestorContainer;
if (!["BR", "#text"].includes(commonEl === null || commonEl === void 0 ? void 0 : commonEl.nodeName) && commonEl.className !== const_1.TEXT_TAG_CLASS)
return false;
var els = rangeEls(range);
if (!els)
return false;
fixTextContent(range, els);
// 如果当前文本元素是个空元素
if (!els.textEl.textContent) {
var t = (0, _1.createTextNode)(const_1.PLACEHOLDER_TEXT);
els.textEl.innerHTML = "";
els.textEl.appendChild(t);
// 修正光标位置
range.setStart(t, 0);
range.collapse(true);
}
el.setAttribute("contenteditable", "false");
// 如果当前光标在文本节点开头,则将元素插入到文本节点之前
if (isRangeAtTextStart(range)) {
// 获取当前textEL的前一个兄弟节点
var prevEl = els.textEl.previousElementSibling;
if (!prevEl || prevEl.className !== const_1.TEXT_TAG_CLASS) {
els.textEl.insertAdjacentElement("beforebegin", (0, _1.createTextTag)());
}
els.textEl.insertAdjacentElement("beforebegin", el);
return true;
}
else if (isRangeAtTextEnd(range)) {
// 获取当前textEL的下一个兄弟节点
var nextEl = els.textEl.nextElementSibling;
if (!nextEl || nextEl.className !== const_1.TEXT_TAG_CLASS) {
nextEl = (0, _1.createTextTag)();
els.textEl.insertAdjacentElement("afterend", nextEl);
}
els.textEl.insertAdjacentElement("afterend", el);
// 更新光标位置
range.setStart(nextEl, 0);
range.collapse(true);
return true;
}
// 如果当前光标在文本节点中间,则将文本节点分割为两个,并将元素插入到中间
var text1 = ((_a = range.endContainer.textContent) === null || _a === void 0 ? void 0 : _a.slice(0, range.endOffset)) || const_1.PLACEHOLDER_TEXT;
var text2 = ((_b = range.endContainer.textContent) === null || _b === void 0 ? void 0 : _b.slice(range.endOffset)) || const_1.PLACEHOLDER_TEXT;
range.endContainer.textContent = text1;
var textEl2 = (0, _1.createTextTag)(text2);
els.textEl.insertAdjacentElement("afterend", textEl2);
els.textEl.insertAdjacentElement("afterend", el);
// 更新光标位置
range.setStart(textEl2.childNodes[0], 0);
range.collapse(true);
return true;
}