CzRger před 3 měsíci
rodič
revize
e54d5a88de

+ 1 - 0
package.json

@@ -25,6 +25,7 @@
     "axios": "^1.9.0",
     "dagre": "^0.8.5",
     "default-passive-events": "^4.0.0",
+    "dompurify": "^3.2.6",
     "echarts": "^5.6.0",
     "element-plus": "^2.10.1",
     "fast-glob": "^3.3.3",

+ 124 - 0
src/components/czr-ui/CzrMarkdown/CzrMarkdown.vue

@@ -0,0 +1,124 @@
+<template>
+  <div class="markdown-container">
+    <div class="toolbar">
+      <button @click="insertText('**', '**')">B</button>
+      <button @click="insertText('*', '*')">I</button>
+      <button @click="insertText('# ', '')">H1</button>
+      <button @click="insertText('## ', '')">H2</button>
+      <button @click="insertText('[', '](url)')">Link</button>
+      <button @click="insertText('![', '](image.png)')">Image</button>
+    </div>
+    <div class="editor-wrapper">
+      <textarea
+        ref="textarea"
+        v-model="markdownText"
+        class="editor"
+        @input="updateMarkdown"
+      ></textarea>
+      <div class="preview" v-html="compiledMarkdown"></div>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+defineOptions({
+  name: 'CzrMarkdown',
+})
+import { ref, computed, onMounted } from 'vue'
+import { marked } from 'marked'
+import DOMPurify from 'dompurify'
+
+const emit = defineEmits(['update:modelValue'])
+const props = defineProps({
+  modelValue: {
+    type: String,
+    default: '',
+  },
+})
+const textarea = ref(null)
+const markdownText = ref(props.modelValue)
+
+const compiledMarkdown = computed(() => {
+  return DOMPurify.sanitize(marked(markdownText.value))
+})
+
+function updateMarkdown() {
+  emit('update:modelValue', markdownText.value)
+}
+
+function insertText(before, after) {
+  const textareaEl = textarea.value
+  const start = textareaEl.selectionStart
+  const end = textareaEl.selectionEnd
+  const selectedText = markdownText.value.substring(start, end)
+
+  markdownText.value =
+    markdownText.value.substring(0, start) +
+    before +
+    selectedText +
+    after +
+    markdownText.value.substring(end)
+
+  // 移动光标位置
+  setTimeout(() => {
+    textareaEl.selectionStart = start + before.length
+    textareaEl.selectionEnd = end + before.length
+    textareaEl.focus()
+  }, 0)
+
+  emit('update:modelValue', markdownText.value)
+}
+
+onMounted(() => {
+  marked.setOptions({
+    breaks: true,
+    gfm: true,
+  })
+})
+</script>
+
+<style>
+.markdown-container {
+  display: flex;
+  flex-direction: column;
+  height: 100%;
+}
+
+.toolbar {
+  padding: 8px;
+  background: #f5f5f5;
+  border-bottom: 1px solid #ddd;
+}
+
+.toolbar button {
+  margin-right: 8px;
+  padding: 4px 8px;
+  background: white;
+  border: 1px solid #ddd;
+  border-radius: 4px;
+  cursor: pointer;
+}
+
+.editor-wrapper {
+  display: flex;
+  flex: 1;
+  overflow: hidden;
+}
+
+.editor {
+  flex: 1;
+  padding: 10px;
+  border: none;
+  border-right: 1px solid #ddd;
+  resize: none;
+  font-family: monospace;
+  outline: none;
+}
+
+.preview {
+  flex: 1;
+  padding: 10px;
+  overflow-y: auto;
+  background: white;
+}
+</style>

+ 7 - 1
src/views/manage/app/index.vue

@@ -1,6 +1,11 @@
 <template>
   <div>
-    <CzrRich />
+    <el-card>
+      <!--      <CzrRich v-model="" />-->
+    </el-card>
+    <el-card>
+      <CzrMarkdown />
+    </el-card>
     <div class="editor-container">
       <div
         ref="editorRef"
@@ -32,6 +37,7 @@
 
 <script setup>
 import { ref, computed, onMounted } from 'vue'
+import CzrMarkdown from '@/components/czr-ui/CzrMarkdown/CzrMarkdown.vue'
 
 const editorRef = ref(null)
 const showSuggestions = ref(false)

+ 12 - 0
yarn.lock

@@ -1160,6 +1160,11 @@
   dependencies:
     "@types/node" "*"
 
+"@types/trusted-types@^2.0.7":
+  version "2.0.7"
+  resolved "https://registry.npmmirror.com/@types/trusted-types/-/trusted-types-2.0.7.tgz#baccb07a970b91707df3a3e8ba6896c57ead2d11"
+  integrity sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==
+
 "@types/web-bluetooth@^0.0.16":
   version "0.0.16"
   resolved "https://registry.npmmirror.com/@types/web-bluetooth/-/web-bluetooth-0.0.16.tgz#1d12873a8e49567371f2a75fe3e7f7edca6662d8"
@@ -2204,6 +2209,13 @@ domhandler@^4.2.0, domhandler@^4.3.1:
   dependencies:
     domelementtype "^2.2.0"
 
+dompurify@^3.2.6:
+  version "3.2.6"
+  resolved "https://registry.npmmirror.com/dompurify/-/dompurify-3.2.6.tgz#ca040a6ad2b88e2a92dc45f38c79f84a714a1cad"
+  integrity sha512-/2GogDQlohXPZe6D6NOgQvXLPSYBqIWMnZ8zzOhn09REE4eyAzb+Hed3jhoM9OkuaJ8P6ZGTTVWQKAi8ieIzfQ==
+  optionalDependencies:
+    "@types/trusted-types" "^2.0.7"
+
 domutils@^1.5.1:
   version "1.7.0"
   resolved "https://registry.npmmirror.com/domutils/-/domutils-1.7.0.tgz#56ea341e834e06e6748af7a1cb25da67ea9f8c2a"