|
|
|
|
<!doctype html>
|
|
|
|
|
<html>
|
|
|
|
|
|
|
|
|
|
<head>
|
|
|
|
|
<title>可编辑 Div 示例</title>
|
|
|
|
|
<meta charset="UTF-8">
|
|
|
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
|
|
|
<meta name="description" content="Editable Div with Suggestions">
|
|
|
|
|
</head>
|
|
|
|
|
<style>
|
|
|
|
|
body {
|
|
|
|
|
background-color: var(--bg-color-primary);
|
|
|
|
|
color: var(--text-color-primary);
|
|
|
|
|
padding: var(--spacing-lg);
|
|
|
|
|
}
|
|
|
|
|
.editable-div {
|
|
|
|
|
border: 1px solid var(--border-color);
|
|
|
|
|
padding: var(--spacing-md);
|
|
|
|
|
min-height: 100px;
|
|
|
|
|
outline: none;
|
|
|
|
|
white-space: pre-wrap;
|
|
|
|
|
background-color: var(--bg-color-secondary);
|
|
|
|
|
border-radius: var(--radius-md);
|
|
|
|
|
transition: var(--transition-base);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.editable-div:focus {
|
|
|
|
|
border-color: var(--color-primary);
|
|
|
|
|
box-shadow: 0 0 0 3px color-mix(in srgb, var(--color-primary), transparent 80%);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.suggestions {
|
|
|
|
|
border: 1px solid var(--border-color);
|
|
|
|
|
background: var(--bg-color-primary);
|
|
|
|
|
max-height: 150px;
|
|
|
|
|
overflow-y: auto;
|
|
|
|
|
position: absolute;
|
|
|
|
|
z-index: 1000;
|
|
|
|
|
box-shadow: var(--shadow-lg);
|
|
|
|
|
border-radius: var(--radius-md);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.suggestion-item {
|
|
|
|
|
padding: var(--spacing-sm) var(--spacing-md);
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
color: var(--text-color-primary);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.suggestion-item:hover {
|
|
|
|
|
background-color: var(--bg-color-tertiary);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.selected-tag {
|
|
|
|
|
color: var(--color-success);
|
|
|
|
|
font-weight: bold;
|
|
|
|
|
}
|
|
|
|
|
</style>
|
|
|
|
|
<style unscoped>
|
|
|
|
|
.test {
|
|
|
|
|
position: relative;
|
|
|
|
|
color: var(--color-danger);
|
|
|
|
|
display: inline-block;
|
|
|
|
|
min-width: 10px;
|
|
|
|
|
margin-left: 50px;
|
|
|
|
|
margin-right: 50px;
|
|
|
|
|
}
|
|
|
|
|
</style>
|
|
|
|
|
|
|
|
|
|
<body>
|
|
|
|
|
<div class="editable-div" contenteditable="true" @input="handleInput" @keydown="handleKeydown" ref="editableDiv">
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="suggestions" v-if="showSuggestions">
|
|
|
|
|
<div class="suggestion-item" v-for="(item, index) in filteredSuggestions" :key="index"
|
|
|
|
|
@mousedown="selectSuggestion(item)">
|
|
|
|
|
{{ item }}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</body>
|
|
|
|
|
<script setup>
|
|
|
|
|
// 初始化数据
|
|
|
|
|
textContent = "";
|
|
|
|
|
suggestions = ["Alice", "Bob", "Charlie", "David", "Eve"];
|
|
|
|
|
filteredSuggestions = [];
|
|
|
|
|
showSuggestions = false;
|
|
|
|
|
currentSelectionIndex = -1;
|
|
|
|
|
|
|
|
|
|
// 处理输入事件
|
|
|
|
|
handleInput = (e) => {
|
|
|
|
|
// const div = $node.querySelector(".editable-div");
|
|
|
|
|
// textContent = div.innerText;
|
|
|
|
|
// ... (logic commented out in original)
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 处理键盘事件(上下箭头、回车)
|
|
|
|
|
handleKeydown = (e) => {
|
|
|
|
|
const selection = window.getSelection();
|
|
|
|
|
|
|
|
|
|
const range = selection.getRangeAt(0);
|
|
|
|
|
let dom = selection.anchorNode;
|
|
|
|
|
if (dom.nodeType === 3) {
|
|
|
|
|
dom = dom.parentNode;
|
|
|
|
|
}
|
|
|
|
|
if (dom && dom.hasAttribute && dom.hasAttribute('test')) {
|
|
|
|
|
showSuggestions = true
|
|
|
|
|
let sub = dom.innerText.slice(1)
|
|
|
|
|
console.log(sub)
|
|
|
|
|
filteredSuggestions = suggestions.filter((item) =>
|
|
|
|
|
item.toLowerCase().includes(sub)
|
|
|
|
|
);
|
|
|
|
|
} else {
|
|
|
|
|
showSuggestions = false
|
|
|
|
|
}
|
|
|
|
|
if (!showSuggestions) {
|
|
|
|
|
if (e.key === '@') {
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
const div = document.createElement('div');
|
|
|
|
|
div.setAttribute('test', '')
|
|
|
|
|
div.innerText = '@'
|
|
|
|
|
div.classList.add('test')
|
|
|
|
|
|
|
|
|
|
// 插入换行符到当前光标位置
|
|
|
|
|
range.deleteContents(); // 清除选中的内容(如果有)
|
|
|
|
|
// range.insertNode(document.createElement('div'))
|
|
|
|
|
range.insertNode(div);
|
|
|
|
|
|
|
|
|
|
// 确保光标移动到换行后的位置
|
|
|
|
|
// const nextNode = br.nextSibling || br.parentNode.appendChild(document.createTextNode(''));
|
|
|
|
|
const newRange = document.createRange();
|
|
|
|
|
newRange.setStartAfter(div.childNodes[0]);
|
|
|
|
|
newRange.collapse(true);
|
|
|
|
|
|
|
|
|
|
// 更新选区
|
|
|
|
|
selection.removeAllRanges();
|
|
|
|
|
selection.addRange(newRange);
|
|
|
|
|
}
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (e.key === "ArrowDown") {
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
currentSelectionIndex =
|
|
|
|
|
(currentSelectionIndex + 1) % filteredSuggestions.length;
|
|
|
|
|
if (isNaN(currentSelectionIndex)) {
|
|
|
|
|
currentSelectionIndex = 0
|
|
|
|
|
}
|
|
|
|
|
highlightSuggestion();
|
|
|
|
|
} else if (e.key === "ArrowUp") {
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
currentSelectionIndex =
|
|
|
|
|
(currentSelectionIndex - 1 + filteredSuggestions.length) %
|
|
|
|
|
filteredSuggestions.length;
|
|
|
|
|
if (isNaN(currentSelectionIndex)) {
|
|
|
|
|
currentSelectionIndex = 0
|
|
|
|
|
}
|
|
|
|
|
highlightSuggestion();
|
|
|
|
|
} else if (e.key === "Enter") {
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
if (currentSelectionIndex !== -1) {
|
|
|
|
|
let item = filteredSuggestions[currentSelectionIndex]
|
|
|
|
|
console.log(dom, item)
|
|
|
|
|
dom.innerText = '@' + item
|
|
|
|
|
const newRange = document.createRange();
|
|
|
|
|
if (!dom.nextSibling) {
|
|
|
|
|
dom.parentNode.appendChild(document.createTextNode(''))
|
|
|
|
|
}
|
|
|
|
|
if (dom.nextSibling.nodeType === 3) {
|
|
|
|
|
let span = document.createElement('span')
|
|
|
|
|
span.innerText = dom.nextSibling.textContent || ' '
|
|
|
|
|
span.style.minWidth = '10px'
|
|
|
|
|
dom.nextSibling.replaceWith(span)
|
|
|
|
|
}
|
|
|
|
|
let next = dom.nextSibling
|
|
|
|
|
if (next.nodeType === 3) {
|
|
|
|
|
next = next.nextSibling
|
|
|
|
|
}
|
|
|
|
|
console.log([next])
|
|
|
|
|
newRange.setStartAfter(next);
|
|
|
|
|
newRange.collapse(true);
|
|
|
|
|
selection.removeAllRanges(); // 清除当前的选择
|
|
|
|
|
selection.addRange(newRange);
|
|
|
|
|
dom.setAttribute('contenteditable', 'false')
|
|
|
|
|
dom.style.contenteditable = false
|
|
|
|
|
// selectSuggestion(filteredSuggestions[currentSelectionIndex]);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
highlightSuggestion = () => {
|
|
|
|
|
// Logic to highlight suggestion in the list
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
selectSuggestion = (item) => {
|
|
|
|
|
// Logic to select suggestion
|
|
|
|
|
}
|
|
|
|
|
</script>
|
|
|
|
|
</html>
|