import { Marked } from "marked" // 示例 Markdown 文本 const markdown = ` # 2333 - [ ] 1111 - [ ] 1111 - [ ] 1111 - [x] 2222 - [ ] 233323 - [ ] 233323 - [ ] 233323 这是一段测试文本 ` const marked = new Marked() const tokens = marked.lexer(markdown) // 光标 class Cursor { constructor(targetNode = null, insertIndex = null) { this.targetNode = targetNode // 初始时可以直接指定目标文本节点 this.insertIndex = insertIndex // 初始时可以直接指定插入位置索引 this.range = document.createRange() this.cursorElement = this.createCursorElement() document.body.appendChild(this.cursorElement) } // 创建光标元素 createCursorElement() { const cursorElement = document.createElement("div") Object.assign(cursorElement.style, { position: "absolute", width: "2px", height: "1em", backgroundColor: "black", zIndex: "9999", pointerEvents: "none", display: "none", // 初始隐藏 }) return cursorElement } // 设置目标文本节点和插入位置 setTarget(node, index) { this.targetNode = node this.insertIndex = index this.updateRange() } // 更新插入位置索引 updateInsertIndex(offset) { this.insertIndex = Math.max(0, Math.min(this.targetNode.textContent.length, this.insertIndex + offset)) this.updateRange() } // 更新范围 updateRange() { if (this.targetNode && this.insertIndex !== null) { this.range.setStart(this.targetNode, this.insertIndex) this.range.setEnd(this.targetNode, this.insertIndex) } } // 插入文本 insertText(text) { const insertionNode = document.createTextNode(text) this.updateRange() this.range.insertNode(insertionNode) // 更新插入位置 this.insertIndex += text.length this.updateRange() // 更新光标范围 return insertionNode } // 获取当前光标的矩形位置 getBoundingClientRect() { this.updateRange() return this.range.getBoundingClientRect() } // 更新光标位置 updatePosition(rect) { if (!rect) { this.hide() return } this.cursorElement.style.left = `${rect.left}px` this.cursorElement.style.top = `${rect.top}px` this.cursorElement.style.display = "block" } // 隐藏光标 hide() { this.cursorElement.style.display = "none" } // 移除光标元素 remove() { this.cursorElement.remove() } // 获取目标节点的上下兄弟节点 getSiblingNodes() { const parent = this.targetNode.parentNode const siblings = Array.from(parent.childNodes).filter( (node) => node !== this.targetNode && node.nodeType === Node.TEXT_NODE ) return siblings } // 根据当前光标位置移动到上下兄弟节点 moveUp() { console.log('根据当前光标位置移动到上下兄弟节点') const siblings = this.getSiblingNodes() const currentIndex = siblings.indexOf(this.targetNode) if (currentIndex > 0) { const prevSibling = siblings[currentIndex - 1] this.setTarget(prevSibling, prevSibling.textContent.length) } } moveDown() { const siblings = this.getSiblingNodes() const currentIndex = siblings.indexOf(this.targetNode) if (currentIndex < siblings.length - 1) { const nextSibling = siblings[currentIndex + 1] this.setTarget(nextSibling, 0) } } } // 光标集合,存储所有光标 const cursors = [] let defaultCursor = new Cursor() // 初始创建默认光标 cursors.push(defaultCursor) // 将默认光标添加到光标集合 // 创建 textarea 接受输入 const textarea = document.createElement("textarea") Object.assign(textarea.style, { position: "fixed", bottom: "10px", left: "10px", width: "300px", height: "100px", zIndex: "1000", placeholder: "在这里输入文本或使用方向键调整位置", }) document.body.appendChild(textarea) // 处理输入事件 textarea.oninput = () => { const inputText = textarea.value // 获取用户输入的文本 if (!inputText) return cursors.forEach(cursor => { if (!cursor.targetNode || cursor.insertIndex === null) return // 更新文本节点内容 cursor.targetNode.textContent = cursor.targetNode.textContent.slice(0, cursor.insertIndex) + inputText + cursor.targetNode.textContent.slice(cursor.insertIndex) // 更新插入位置 cursor.insertIndex += inputText.length }) // 清空输入框 textarea.value = "" updateCursors() } // 处理方向键移动插入点 textarea.onkeydown = (event) => { console.log(event.key) if (!cursors.length) return cursors.forEach(cursor => { if (!cursor.targetNode || cursor.insertIndex === null) return if (event.key === "ArrowLeft") { cursor.updateInsertIndex(-1) } else if (event.key === "ArrowRight") { cursor.updateInsertIndex(1) } else if (event.key === "ArrowUp") { cursor.moveUp() } else if (event.key === "ArrowDown") { cursor.moveDown() } }) // 更新光标位置 updateCursors() } // 更新所有光标的位置 const updateCursors = () => { cursors.forEach(cursor => { if (!cursor.targetNode || cursor.insertIndex === null) { cursor.hide() return } // 获取插入点位置 const rect = cursor.getBoundingClientRect() // 更新光标位置 cursor.updatePosition(rect) }) } // 渲染 Markdown 并监听点击事件 const element = document.createElement("div") element.innerHTML = marked.parser(tokens) document.body.appendChild(element) // 点击事件:记录插入位置 element.onclick = (event) => { if (event.target.tagName !== "LI") return const { clientX: x, clientY: y } = event // 查找点击的文本节点 const textNodes = Array.from(event.target.childNodes).filter( (node) => node.nodeType === Node.TEXT_NODE ) const targetNode = textNodes.find((node) => { const range = document.createRange() range.selectNodeContents(node) const rect = range.getBoundingClientRect() return x >= rect.left && x <= rect.right && y >= rect.top && y <= rect.bottom }) if (!targetNode) return // 计算插入位置索引 const positions = [...targetNode.textContent].map((_, i) => { const range = document.createRange() range.setStart(targetNode, i) range.setEnd(targetNode, i + 1) return { index: i, rect: range.getBoundingClientRect() } }) const closest = positions.reduce( (closest, pos) => { const dist = Math.abs(x - pos.rect.left) return dist < closest.distance ? { ...pos, distance: dist } : closest }, { index: -1, distance: Infinity } ) const rect = closest.rect const insertBefore = x < rect.left + rect.width / 2 const insertIndex = closest.index + (insertBefore ? 0 : 1) // 设置光标的目标节点和插入位置 cursors.forEach(cursor => cursor.setTarget(targetNode, insertIndex)) // 更新光标位置 updateCursors() // 聚焦输入框 textarea.value = "" textarea.focus() // 如果是点击多个光标的情况,增加新光标 if (event.ctrlKey) { const newCursor = new Cursor(targetNode, insertIndex) cursors.push(newCursor) console.log("新增光标") } } // 按删除键移除光标 document.addEventListener('keydown', (event) => { if (event.key === 'Delete' && cursors.length > 0) { const cursorToRemove = cursors.pop() // 移除最后一个光标 cursorToRemove.remove() // 移除对应的光标元素 console.log("移除光标") } })