From 753f40ab290cb8e34d590d09724991f5d19fda83 Mon Sep 17 00:00:00 2001 From: satori Date: Wed, 15 Jan 2025 15:10:30 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B8=B8=E6=A0=87=20markdown?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- index.html | 2 +- markdown.js | 285 +++++++++++++++++++++++++++++++++++++++++++++++++++ package.json | 1 + 4 files changed, 288 insertions(+), 2 deletions(-) create mode 100644 markdown.js diff --git a/README.md b/README.md index 374b83c..ff551ce 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # cooperation -协作工具 +协作工具, 把事情变简单 ```bash # 安装依赖(使用镜像站) diff --git a/index.html b/index.html index 3766ac1..a9a4c91 100644 --- a/index.html +++ b/index.html @@ -10,7 +10,7 @@

HHH

- + \ No newline at end of file diff --git a/markdown.js b/markdown.js new file mode 100644 index 0000000..e8b31dc --- /dev/null +++ b/markdown.js @@ -0,0 +1,285 @@ +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("移除光标") + } +}) diff --git a/package.json b/package.json index bcc4a8b..a210c15 100755 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "dependencies": { "@laniakeasupercluster/widgets": "^1.1.8", "core-js": "^3.39.0", + "marked": "^15.0.6", "primeicons": "^7.0.0", "pug": "^3.0.3", "pug-plain-loader": "^1.1.0",