Skip to content

Commit a14f427

Browse files
feat: markdown editor
1 parent 393e8df commit a14f427

36 files changed

Lines changed: 595 additions & 370 deletions

File tree

‎ui/src/components/COMPONENT_README.md‎

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ src/components/
5252
│ │ ├── MdEditor.vue # 支持多语言和自定义页脚的 Markdown 编辑器
5353
│ │ ├── MdEditorMagnify.vue # 支持弹窗放大编辑的 Markdown 编辑器
5454
│ │ ├── MdPreview.vue # 禁用代码折叠的 Markdown 预览器
55-
│ │ ├── sup-popover.scss # Markdown 上标说明悬浮层样式
55+
│ │ ├── md-editor.scss # Markdown 编辑器、预览区和上标说明浮层的共享样式
5656
│ │ └── sup-popover.ts # Markdown 上标说明悬浮层交互
5757
│ ├── mk-complex-search/
5858
│ │ └── index.vue # 字段选择与输入或枚举条件组合搜索框
@@ -115,7 +115,8 @@ src/components/
115115
│ └── LogoIcon.vue # 不带产品名称的图形 Logo,手动导入
116116
├── codemirror-editor/
117117
│ ├── python.vue # 内置 pylint 诊断和全屏编辑的 Python 编辑器
118-
│ └── Json.vue # 支持格式化、语法诊断和全屏编辑的 JSON 输入框
118+
│ ├── Json.vue # 支持格式化、语法诊断和全屏编辑的 JSON 输入框
119+
│ └── style.scss # Python 与 JSON 编辑器共享的 scoped 样式
119120
└── mk-source-card/
120121
│ ├── index.vue # 来源资源的统一卡片结构,手动导入
121122
│ ├── mk-source-card-action.vue # 卡片悬浮操作容器
@@ -198,6 +199,18 @@ Element Plus 使用 `ElOnlyChild` 处理浮层触发器。`el-tooltip`、`el-pop
198199
配置为本地实例,避免运行时加载 CDN 资源;`md-editor-v3` v6 的图标已随依赖打包,不再维护或加载
199200
旧版 `markdown-iconfont.js`。
200201

202+
编辑器、预览区和上标说明浮层的共享样式统一放在组件目录的 `md-editor.scss`,由 `config.ts`
203+
引入。`MdEditor` 与 `MdPreview` 在底层组件根节点添加 `mk-markdown-editor`,编辑器内部样式
204+
仅在该范围生效;`MdEditorMagnify` 通过复用 `MdEditor` 继承样式。上标浮层由 `sup-popover.ts`
205+
挂载到 `body`,使用独立的 `markdown-sup-popover` 类名,不依赖编辑器祖先或 Vue scoped 属性。
206+
组件专属样式不放入全局 `src/styles`;第三方基础 CSS 仍由全局 Sass 入口加载。
207+
208+
可编辑的 Markdown 输入框悬浮边框使用 `--el-input-hover-border-color`,编辑区获得焦点时
209+
使用 `--el-input-focus-border-color`,未提供这两个变量时分别回退到 Element Plus 的悬浮边框色
210+
和主题色。只读、禁用和独立预览不触发编辑边框状态。编辑器自带的滚动条统一使用
211+
`el-scrollbar` 的颜色变量、6px 宽度和 0.3/0.5 透明度,内容溢出时由编辑器控制显示,悬浮
212+
滑块时加深颜色;不再额外通过轨道透明度隐藏滚动条,保留原有滚动节点和拖动逻辑。
213+
201214
```vue
202215
<MdEditor v-model="markdownContent">
203216
<template #defFooters>自定义页脚</template>
@@ -700,6 +713,11 @@ import MkDragUpload from '@/components/mk-drag-upload/index.vue'
700713

701714
### PythonCodeEditor
702715

716+
Python 与 JSON 编辑器通过 `<style lang="scss" scoped src="./style.scss">` 共享组件目录内的
717+
样式,使用 `mk-codemirror` 容器和 `:deep()` 限定 CodeMirror 内部节点的覆盖范围。普通编辑器
718+
与全屏弹窗内容分别提供该容器,默认高度为 `210px`,调用方可通过透传的 `style` 覆盖;全屏
719+
编辑器单独设置高度。不在全局 `app.scss` 中覆盖 `.cm-editor`。
720+
703721
基于 CodeMirror 6 的 Python 代码编辑器,通过 `v-model` 管理代码,并在组件内部调用工具 pylint
704722
接口生成诊断。组件最多展示 50 条诊断,并在代码停止输入 500ms 后检查。编辑器提供内置全屏
705723
入口;全屏确认时更新 `v-model` 并触发 `submit-dialog`,`header-extra` 插槽用于添加全屏标题栏操作。

‎ui/src/components/codemirror-editor/Json.vue‎

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -91,13 +91,13 @@ defineExpose({ format, validateRules })
9191
</script>
9292

9393
<template>
94-
<div class="relative w-full">
94+
<div class="mk-codemirror relative w-full">
9595
<Codemirror
9696
:model-value="editorContent"
9797
:extensions="extensions"
9898
:tab-size="4"
9999
autofocus
100-
style="height: 210px; width: 100%"
100+
:style="{ height: '210px' }"
101101
v-bind="$attrs"
102102
@update:model-value="handleContentChange"
103103
/>
@@ -110,11 +110,17 @@ defineExpose({ format, validateRules })
110110
</div>
111111

112112
<MkDialog v-model="dialogVisible" :title="props.title" append-to-body fullscreen>
113-
<Codemirror v-model="dialogContent" :extensions="extensions" :tab-size="4" autofocus style="height: calc(100vh - 160px)" />
113+
<div class="mk-codemirror">
114+
<Codemirror v-model="dialogContent" :extensions="extensions" :tab-size="4" autofocus :style="{ height: 'calc(100dvh - 160px)' }" />
115+
</div>
114116

115117
<template #footer>
116118
<el-button @click="closeEditorDialog">取消</el-button>
117119
<el-button type="primary" @click="submitEditorDialog">确定</el-button>
118120
</template>
119121
</MkDialog>
120122
</template>
123+
124+
<style lang="scss" scoped>
125+
@use './style.scss';
126+
</style>

‎ui/src/components/codemirror-editor/python.vue‎

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -68,8 +68,8 @@ function submitEditorDialog() {
6868
</script>
6969

7070
<template>
71-
<div class="codemirror-editor w-full relative">
72-
<Codemirror v-model="code" :extensions="extensions" :tab-size="4" autofocus v-bind="$attrs" />
71+
<div class="mk-codemirror w-full relative">
72+
<Codemirror v-model="code" :extensions="extensions" :tab-size="4" autofocus v-bind="$attrs" :style="{ height: '210px' }" />
7373
<el-button class="absolute right-2 bottom-2" text type="info" @click="openEditorDialog">
7474
<MkIcon name="icon_magnify_outlined" />
7575
</el-button>
@@ -83,12 +83,16 @@ function submitEditorDialog() {
8383
</div>
8484
</template>
8585

86-
<Codemirror v-model="dialogCode" autofocus :extensions="extensions" style="height: calc(100vh - 160px) !important" :tab-size="4" />
86+
<div class="mk-codemirror">
87+
<Codemirror v-model="dialogCode" autofocus :extensions="extensions" :tab-size="4" :style="{ height: 'calc(100dvh - 160px)' }" />
88+
</div>
8789

8890
<template #footer>
8991
<el-button type="primary" @click="submitEditorDialog">确定</el-button>
9092
</template>
9193
</MkDialog>
9294
</template>
9395

94-
<style lang="scss" scoped></style>
96+
<style lang="scss" scoped>
97+
@use './style.scss';
98+
</style>
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/* CodeMirror 编辑器 */
2+
.mk-codemirror {
3+
:deep(.cm-editor) {
4+
--el-scrollbar-bg-color: var(--el-text-color-secondary);
5+
--el-scrollbar-hover-bg-color: var(--el-text-color-secondary);
6+
--el-scrollbar-hover-opacity: 0.5;
7+
--el-scrollbar-opacity: 0.3;
8+
border: var(--el-border);
9+
border-radius: var(--el-border-radius-base);
10+
overflow: hidden;
11+
scrollbar-color: transparent transparent;
12+
scrollbar-width: thin;
13+
}
14+
15+
:deep(.cm-editor .cm-gutters.cm-gutters-before) {
16+
border: none;
17+
}
18+
19+
:deep(.cm-editor.cm-focused) {
20+
outline: none !important;
21+
}
22+
23+
:deep(.cm-editor::-webkit-scrollbar) {
24+
background-color: transparent;
25+
height: 6px;
26+
width: 6px;
27+
}
28+
29+
:deep(.cm-editor::-webkit-scrollbar-corner) {
30+
background-color: transparent;
31+
}
32+
33+
:deep(.cm-editor::-webkit-scrollbar-thumb) {
34+
background-color: transparent;
35+
border-radius: var(--el-border-radius-base);
36+
transition: background-color var(--el-transition-duration);
37+
}
38+
39+
:deep(.cm-editor:hover) {
40+
scrollbar-color: color-mix(in srgb, var(--el-scrollbar-bg-color) calc(var(--el-scrollbar-opacity) * 100%), transparent) transparent;
41+
}
42+
43+
:deep(.cm-editor:hover::-webkit-scrollbar-thumb) {
44+
background-color: var(--el-scrollbar-bg-color);
45+
opacity: var(--el-scrollbar-opacity);
46+
}
47+
48+
// 滑块悬浮状态覆盖编辑器悬浮状态。
49+
:deep(.cm-editor::-webkit-scrollbar-thumb:hover) {
50+
background-color: var(--el-scrollbar-hover-bg-color);
51+
opacity: var(--el-scrollbar-hover-opacity);
52+
}
53+
}

‎ui/src/components/global/markdown-editor/MdEditor.vue‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ const language = computed(() => {
1818
</script>
1919

2020
<template>
21-
<MdEditorV3 :language="language" no-prettier v-bind="$attrs">
21+
<MdEditorV3 class="mk-markdown-editor" :language="language" no-prettier v-bind="$attrs">
2222
<template #defFooters>
2323
<slot name="defFooters" />
2424
</template>

‎ui/src/components/global/markdown-editor/MdEditorMagnify.vue‎

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ function submitEditorDialog() {
3232
<template>
3333
<MdEditor v-model="modelValue" :preview="false" :toolbars="[]" :footers="footers" class="magnify-md-editor" v-bind="$attrs">
3434
<template #defFooters>
35-
<el-button text @click="openEditorDialog">
35+
<el-button text @click="openEditorDialog" class="mr-1 mb-1">
3636
<MkIcon name="icon_magnify_outlined" />
3737
</el-button>
3838
</template>
@@ -42,18 +42,9 @@ function submitEditorDialog() {
4242
<MdEditor v-model="dialogContent" :preview="false" :toolbars="[]" :footers="[]" class="magnify-dialog-editor" />
4343

4444
<template #footer>
45-
<el-button @click="closeEditorDialog">取消</el-button>
4645
<el-button type="primary" @click="submitEditorDialog">确定</el-button>
4746
</template>
4847
</MkDialog>
4948
</template>
5049

51-
<style scoped>
52-
.magnify-md-editor :deep(.md-editor-footer) {
53-
border: 0;
54-
}
55-
56-
.magnify-dialog-editor {
57-
height: min(60vh, 600px);
58-
}
59-
</style>
50+
<style scoped></style>

‎ui/src/components/global/markdown-editor/MdPreview.vue‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ const language = computed(() => {
1414
</script>
1515

1616
<template>
17-
<MdPreviewV3 :language="language" :code-foldable="false" v-bind="$attrs" />
17+
<MdPreviewV3 class="mk-markdown-editor" :language="language" :code-foldable="false" v-bind="$attrs" />
1818
</template>
1919

2020
<style scoped>

‎ui/src/components/global/markdown-editor/config.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import { supPopover } from './sup-popover'
1414
import 'cropperjs/dist/cropper.css'
1515
import 'highlight.js/styles/atom-one-dark.css'
1616
import 'katex/dist/katex.min.css'
17-
import './sup-popover.scss'
17+
import './md-editor.scss'
1818

1919
let configured = false
2020

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
/* Markdown 编辑器 */
2+
.mk-markdown-editor.md-editor {
3+
--md-color: var(--mk-N900);
4+
--md-scrollbar-bg-color: transparent;
5+
--md-scrollbar-thumb-active-color: var(--el-scrollbar-hover-bg-color, var(--el-text-color-secondary));
6+
--md-scrollbar-thumb-color: var(--el-scrollbar-bg-color, var(--el-text-color-secondary));
7+
--md-scrollbar-thumb-hover-color: var(--el-scrollbar-hover-bg-color, var(--el-text-color-secondary));
8+
border-radius: var(--el-border-radius-base);
9+
font-weight: 400;
10+
line-height: 22px;
11+
font-family: var(--mk-font-family) !important;
12+
13+
.cm-content {
14+
color: var(--mk-N900);
15+
font-weight: 400;
16+
margin-block: 0 !important;
17+
margin-inline: 11px !important;
18+
padding: 5px 0 !important;
19+
}
20+
.cm-line {
21+
padding: 0 !important;
22+
}
23+
24+
.cm-placeholder {
25+
color: var(--mk-N500);
26+
font-size: var(--mk-font-size-base);
27+
font-weight: 400;
28+
}
29+
30+
.md-editor-footer {
31+
height: auto !important;
32+
border: none !important;
33+
}
34+
/* Markdown 输入框边框 */
35+
&:not(.md-editor-previewOnly) {
36+
border-color: var(--el-input-border-color, var(--el-border-color));
37+
transition: border-color var(--el-transition-duration);
38+
39+
&:not(.md-editor-disabled):has(.cm-content[contenteditable='true']:not([aria-readonly='true'])) {
40+
&:hover {
41+
border-color: var(--el-input-hover-border-color, var(--el-border-color-hover));
42+
}
43+
44+
// 编辑焦点优先于鼠标悬浮状态。
45+
&:has(.cm-focused) {
46+
border-color: var(--el-input-focus-border-color, var(--el-color-primary));
47+
}
48+
}
49+
}
50+
}
51+
52+
/* Markdown 自带滚动条沿用 el-scrollbar 的颜色、尺寸和悬浮透明度。 */
53+
.mk-markdown-editor {
54+
.md-editor-custom-scrollbar__thumb {
55+
border-radius: inherit;
56+
opacity: var(--el-scrollbar-opacity, 0.3);
57+
transition: background-color var(--el-transition-duration);
58+
width: 100%;
59+
60+
&:active,
61+
&:hover {
62+
opacity: var(--el-scrollbar-hover-opacity, 0.5);
63+
}
64+
}
65+
66+
.md-editor-custom-scrollbar__track {
67+
border-radius: 4px;
68+
inset-inline-end: 2px;
69+
width: 6px;
70+
z-index: 1;
71+
}
72+
}
73+
74+
/* Markdown 预览 */
75+
.mk-markdown-editor {
76+
.md-editor-preview {
77+
font-size: inherit;
78+
margin: 0;
79+
padding: 0;
80+
word-break: break-word;
81+
82+
.md-editor-admonition {
83+
margin: 0;
84+
padding: 0;
85+
}
86+
87+
.md-editor-code .md-editor-code-head {
88+
z-index: inherit !important;
89+
}
90+
91+
img {
92+
border: 0 !important;
93+
max-width: calc(var(--spacing) * 90) !important;
94+
}
95+
96+
p {
97+
padding: 0 !important;
98+
}
99+
100+
sup[data-title] {
101+
align-items: center;
102+
background-color: var(--el-color-primary-light-9);
103+
border: 1px solid var(--el-color-primary-light-7);
104+
border-radius: 4px;
105+
color: var(--mk-primary);
106+
cursor: pointer;
107+
display: inline-flex;
108+
font-size: 10px;
109+
font-weight: 500;
110+
letter-spacing: 0.02em;
111+
line-height: 1;
112+
padding: 2px var(--spacing);
113+
transition:
114+
background-color 0.15s,
115+
color 0.15s;
116+
vertical-align: super;
117+
white-space: nowrap;
118+
}
119+
120+
table {
121+
display: block;
122+
}
123+
124+
ul {
125+
list-style: circle;
126+
}
127+
128+
video {
129+
max-width: calc(var(--spacing) * 90) !important;
130+
width: 100%;
131+
}
132+
}
133+
134+
.md-editor-preview-wrapper {
135+
padding: 0;
136+
}
137+
}
138+
139+
@media only screen and (max-width: 768px) {
140+
.mk-markdown-editor .md-editor-preview img {
141+
max-width: 100% !important;
142+
}
143+
}
144+
145+
/* 上标说明浮层由 sup-popover.ts 挂载到 body,使用独立类名限定范围。 */
146+
.markdown-sup-popover {
147+
background: var(--el-bg-color-overlay);
148+
border-radius: 4px;
149+
box-shadow: var(--el-box-shadow-light);
150+
color: var(--mk-N900);
151+
display: none;
152+
font-size: 13px;
153+
line-height: 1.5;
154+
padding: 10px calc(var(--spacing) * 3);
155+
position: absolute;
156+
width: calc(var(--spacing) * 60);
157+
word-break: break-word;
158+
z-index: 20001;
159+
}
160+
161+
.markdown-sup-popover__arrow {
162+
background: var(--el-bg-color-overlay);
163+
box-shadow: 1px 1px 4px rgb(0 0 0 / 8%);
164+
height: calc(var(--spacing) * 2);
165+
position: absolute;
166+
transform: rotate(45deg);
167+
width: calc(var(--spacing) * 2);
168+
}

0 commit comments

Comments
 (0)