starwait/components/Ni/Markdown.vue
2025-04-29 17:01:17 +08:00

223 lines
5.6 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<script setup lang="ts">
import 'highlight.js/styles/atom-one-light.css';
import DOMPurify from "dompurify";
const props = defineProps({
content: {
type: String,
required: true,
},
contentId: {
type: String,
required: true,
}
})
// 原始内容
const originContent = ref(props.content);
// 编辑器内容
const editorContent = ref('');
// 显示器内容
const viewerContent = ref('');
// 编辑器元素对象
const editorRef =ref<null | HTMLElement>(null)
// 编译器
const markedWorker = ref<null | Worker>(null)
// 初始化Markdown编译线程
function initMarked(){
const worker = new Worker(new URL("./Markdown.worker.js", import.meta.url), {type: 'module' });
worker.postMessage({
type: 'init',
})
worker.onmessage = (e) => {
switch (e.data.type) {
case 'render': {
// 加强安全防止xss攻击
console.log(e.data.content.editContent)
// editorContent.value = DOMPurify.sanitize(e.data.content.editContent);
viewerContent.value = DOMPurify.sanitize(e.data.content.viewContent);
break;
}
default: {
console.log(e)
}
}
// worker.terminate();
};
markedWorker.value = worker
}
let isUpdating = false;
const getCursorPos = (event) => {
const content = event.target.innerText;
const selection = window.getSelection();
const range = selection.rangeCount > 0 ? selection.getRangeAt(0) : null;
if (range) {
// 获取光标位置(相对于整个内容的偏移量)
const cursorPosition = range.startOffset;
// 将内容按换行符分割成数组
const lines = content.split(/\n/i);
// 累计每行的长度,找到光标所在的行
let lineIndex = 0;
let cumulativeLength = 0;
for (let i = 0; i < lines.length; i++) {
cumulativeLength += lines[i].length;
// 如果光标位置小于当前累计长度,说明光标在这一行
if (cursorPosition <= cumulativeLength) {
lineIndex = i + 1; // 行号从1开始
break;
}
// 考虑换行符的长度HTML 中的 `<br>`
cumulativeLength += 4; // `<br>` 的长度
}
console.log("光标所在的行:", lineIndex, range.startOffset);
}
}
// 实时监听编辑器输入事件
const handleEditorInput = async (event) => {
getCursorPos(event)
const editorContent = event.target.innerText;
markedWorker.value?.postMessage({
type: 'render',
content: editorContent,
})
// restoreCaretPosition( event.target, caretPos);
isUpdating = false;
};
// 手动获取内容(通过按钮触发)
const getEditorContent = () => {
if (editorRef.value) {
const text = editorRef.value.innerText.trim();
console.log("手动获取:", text);
}
};
// 处理粘贴内容(自动去除粘贴的 HTML 标签)
const handleEditorPaste = (event) => {
event.preventDefault(); // 阻止默认粘贴行为
const text = event.clipboardData.getData("text/plain"); // 获取纯文本
document.execCommand("insertText", false, text); // 插入纯文本
};
onMounted(() => {
initMarked()
})
</script>
<template>
<div class="NiMarkdown">
<header></header>
<main>
<div class="markdownEditor"
ref="editorRef"
contenteditable="true"
@input="handleEditorInput"
v-html="editorContent"
/>
<div class="markdownViewer"
v-html="viewerContent"
/>
</main>
<footer></footer>
</div>
</template>
<style scoped lang="scss">
.NiMarkdown{
position: relative;
height: 100%;
width: 100%;
overflow: hidden;
display: flex;
flex-direction: column;
& > header{
position: relative;
width: 100%;
height: 20px;
display: flex;
flex-shrink: 0;
}
& > footer{
position: relative;
width: 100%;
height: 60px;
display: flex;
flex-shrink: 0;
}
& > main{
position: relative;
width: 100%;
flex: 1;
overflow: hidden;
display: flex;
& > div{
position: relative;
width: 50%;
border: 1px solid var(--Ni-theme-border-color);
overflowY: auto;
padding: 1rem;
}
}
}
.markdownEditor{
outline: none;
}
.markdownViewer{
/* 代码块样式 */
:deep(pre) {
white-space: pre-wrap;
background: #cdcdcd;
padding: 15px;
border-radius: 5px;
code {
font-family: 'Consolas', 'Fira Code', monospace;
font-size: 14px;
line-height: 1.5;
white-space: pre;
}
/* 确保代码块正确换行 */
code {
display: block;
overflow-x: auto;
padding: 1em;
}
.code-line {
display: flex;
min-height: 1em; /* 防止空行高度塌陷 */
}
.line-number {
width: 40px;
padding-right: 12px;
color: #666;
text-align: right;
user-select: none;
flex-shrink: 0;
}
.line-content {
flex-grow: 1;
white-space: pre-wrap; /* 允许代码换行 */
}
}
}
</style>