游标 markdown
This commit is contained in:
parent
896a50fcad
commit
753f40ab29
@ -1,6 +1,6 @@
|
|||||||
# cooperation
|
# cooperation
|
||||||
|
|
||||||
协作工具
|
协作工具, 把事情变简单
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 安装依赖(使用镜像站)
|
# 安装依赖(使用镜像站)
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
|
|
||||||
<body>
|
<body>
|
||||||
<p>HHH</p>
|
<p>HHH</p>
|
||||||
<script type="module" src="main.js"></script>
|
<script type="module" src="markdown.js"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
285
markdown.js
Normal file
285
markdown.js
Normal file
@ -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("移除光标")
|
||||||
|
}
|
||||||
|
})
|
@ -13,6 +13,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@laniakeasupercluster/widgets": "^1.1.8",
|
"@laniakeasupercluster/widgets": "^1.1.8",
|
||||||
"core-js": "^3.39.0",
|
"core-js": "^3.39.0",
|
||||||
|
"marked": "^15.0.6",
|
||||||
"primeicons": "^7.0.0",
|
"primeicons": "^7.0.0",
|
||||||
"pug": "^3.0.3",
|
"pug": "^3.0.3",
|
||||||
"pug-plain-loader": "^1.1.0",
|
"pug-plain-loader": "^1.1.0",
|
||||||
|
Loading…
Reference in New Issue
Block a user