support code for markdown-editor
This commit is contained in:
@@ -2,11 +2,13 @@
|
||||
<div
|
||||
ref="root"
|
||||
class="tui-markdown-editor diselect"
|
||||
:class="{ 'fold': isFold }"
|
||||
:class="{
|
||||
'fold': isFold,
|
||||
'code': code
|
||||
}"
|
||||
>
|
||||
<div class="editor">
|
||||
<div class="header">
|
||||
<icon class="icon" name="WRITING" />
|
||||
<slot name="editorHeader">
|
||||
<h4 class="title">
|
||||
<span>源码</span>
|
||||
@@ -15,30 +17,31 @@
|
||||
</slot>
|
||||
</div>
|
||||
<slot name="editor">
|
||||
<textarea ref="textArea" class="text-area" v-model="_data"></textarea>
|
||||
<textarea ref="textArea" class="text-area" v-model="_data" @keydown.tab.prevent="onTab"></textarea>
|
||||
</slot>
|
||||
</div>
|
||||
<div class="preview" :class="{ 'showing': showingPreview }">
|
||||
<div class="header">
|
||||
<icon
|
||||
<div
|
||||
v-if="isFold"
|
||||
class="icon cur-pointer"
|
||||
:name="showingPreview ? 'ARROW_1_E' : 'ARROW_1_W'"
|
||||
v-text="showingPreview ? '>' : '<'"
|
||||
@click="showingPreview = !showingPreview"
|
||||
/>
|
||||
>
|
||||
</div>
|
||||
<slot name="previewHeader">
|
||||
<h4 class="title">预览</h4>
|
||||
</slot>
|
||||
</div>
|
||||
<div ref="previewContent" class="content">
|
||||
<markdown-view :content="_data" />
|
||||
<markdown-view v-if="code" :code="`${code}:${_data}`" />
|
||||
<markdown-view v-else :content="_data" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { Icon } from "../icon";
|
||||
import { MarkdownView } from "../markdown-view";
|
||||
import calcHeight from "./CalcTextareaHeight";
|
||||
|
||||
@@ -47,6 +50,7 @@ defineOptions({
|
||||
});
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
code?: string;
|
||||
data?: string,
|
||||
minRows?: number,
|
||||
maxRows?: number
|
||||
@@ -100,7 +104,7 @@ onMounted(() => {
|
||||
const textareaHeightObserver = new ResizeObserver((entries: ResizeObserverEntry[]) => {
|
||||
if (entries && 0 < entries.length) {
|
||||
if (previewContent.value) {
|
||||
previewContent.value.style.height = entries[0].contentRect.height + "px";
|
||||
previewContent.value.style.height = `calc(${entries[0].contentRect.height}px + 16px)`;
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -108,6 +112,56 @@ onMounted(() => {
|
||||
}
|
||||
});
|
||||
|
||||
// ---------- Tab 缩进 ----------
|
||||
|
||||
const TAB = "\t";
|
||||
|
||||
/** 处理 Tab / Shift+Tab 缩进 */
|
||||
const onTab = (e: KeyboardEvent) => {
|
||||
const el = textArea.value!;
|
||||
const { selectionStart: start, selectionEnd: end, value } = el;
|
||||
const lines = value.split("\n");
|
||||
|
||||
// 计算选区覆盖的行范围
|
||||
let charCount = 0;
|
||||
let startLine = 0;
|
||||
let endLine = 0;
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const lineEnd = charCount + lines[i].length;
|
||||
if (charCount <= start && start <= lineEnd) startLine = i;
|
||||
if (charCount <= end && end <= lineEnd) endLine = i;
|
||||
charCount += lines[i].length + 1;
|
||||
}
|
||||
|
||||
// 单行且无选区:直接插入 Tab
|
||||
if (!e.shiftKey && startLine === endLine && start === end) {
|
||||
_data.value = value.slice(0, start) + TAB + value.slice(end);
|
||||
nextTick(() => {
|
||||
el.selectionStart = el.selectionEnd = start + 1;
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 多行或有选区:整行缩进 / 反缩进
|
||||
const newLines = lines.map((line, i) => {
|
||||
if (i < startLine || i > endLine) return line;
|
||||
if (e.shiftKey) return line.startsWith(TAB) ? line.slice(1) : line;
|
||||
return TAB + line;
|
||||
});
|
||||
|
||||
// 计算新选区位置
|
||||
const delta = e.shiftKey ? -1 : 1;
|
||||
const affectedCount = endLine - startLine + 1;
|
||||
const newStart = Math.max(0, start + delta);
|
||||
const newEnd = Math.max(newStart, end + delta * affectedCount);
|
||||
|
||||
_data.value = newLines.join("\n");
|
||||
nextTick(() => {
|
||||
el.selectionStart = newStart;
|
||||
el.selectionEnd = newEnd;
|
||||
});
|
||||
};
|
||||
|
||||
defineExpose({
|
||||
textArea
|
||||
});
|
||||
@@ -189,5 +243,21 @@ defineExpose({
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.code {
|
||||
|
||||
.preview {
|
||||
|
||||
.content {
|
||||
padding: 0;
|
||||
|
||||
:deep(pre[class*="language-"]) {
|
||||
margin: 0;
|
||||
border: none;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user