diff --git a/cursor.js b/cursor.js new file mode 100644 index 0000000..84eaf5e --- /dev/null +++ b/cursor.js @@ -0,0 +1,121 @@ +export const cursors = [] + +export 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", // 初始隐藏 + transition: "all 0.1s ease" + }) + return cursorElement + } + + // 设置目标文本节点和插入位置 + setTarget(node, index) { + this.targetNode = node + this.insertIndex = index + 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() + } + + move(key) { + // 获取目标节点的上下兄弟节点(li元素) + const children = Array.from(this.targetNode.parentNode.parentNode.children) + const siblings = children.filter(node => { + return node !== this.targetNode && node.nodeType === Node.ELEMENT_NODE + }).map(item => { + return Array.from(item.childNodes).find(node => node.nodeType === Node.TEXT_NODE) + }) + const currentIndex = siblings.indexOf(this.targetNode) + + if (key === "ArrowUp" && currentIndex > 0) { + const prevSibling = siblings[currentIndex - 1] + const length = prevSibling.textContent.length + const index = length < this.insertIndex ? length : this.insertIndex + this.setTarget(prevSibling, index) // 光标在上一个兄弟元素的起始位置 + return + } + + if (key === "ArrowDown" && currentIndex >= 0 && currentIndex < siblings.length - 1) { + const nextSibling = siblings[currentIndex + 1] + const length = nextSibling.textContent.length + const index = length < this.insertIndex ? length : this.insertIndex + this.setTarget(nextSibling, index) // 光标在下一个兄弟元素的起始位置 + return + } + + if (key === "ArrowLeft" && this.insertIndex > 0) { + this.insertIndex = Math.max(0, Math.min(this.targetNode.textContent.length, this.insertIndex - 1)) + this.updateRange() + return + } + + if (key === "ArrowRight" && this.insertIndex < this.targetNode.textContent.length) { + this.insertIndex = Math.max(0, Math.min(this.targetNode.textContent.length, this.insertIndex + 1)) + this.updateRange() + return + } + } +} + +export default { cursors, Cursor } diff --git a/markdown.js b/markdown.js index e8b31dc..49bdc0c 100644 --- a/markdown.js +++ b/markdown.js @@ -1,4 +1,5 @@ import { Marked } from "marked" +import { cursors, Cursor } from "./cursor" // 示例 Markdown 文本 const markdown = ` @@ -17,125 +18,6 @@ const markdown = ` 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, { @@ -174,26 +56,10 @@ textarea.oninput = () => { // 处理方向键移动插入点 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() - } + cursors.filter(cursor => cursor.targetNode && cursor.insertIndex !== null).forEach(cursor => { + cursor.move(event.key) + updateCursors() }) - - // 更新光标位置 - updateCursors() } // 更新所有光标的位置 @@ -260,19 +126,19 @@ element.onclick = (event) => { // 设置光标的目标节点和插入位置 cursors.forEach(cursor => cursor.setTarget(targetNode, insertIndex)) + // 如果是点击多个光标的情况,增加新光标 + if (event.ctrlKey || !cursors.length) { + const newCursor = new Cursor(targetNode, insertIndex) + cursors.push(newCursor) + console.log("新增光标") + } + // 更新光标位置 updateCursors() // 聚焦输入框 textarea.value = "" textarea.focus() - - // 如果是点击多个光标的情况,增加新光标 - if (event.ctrlKey) { - const newCursor = new Cursor(targetNode, insertIndex) - cursors.push(newCursor) - console.log("新增光标") - } } // 按删除键移除光标