feat(documents): use toast-ui markdown rich editor
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { onBeforeUnmount, onMounted, ref } from "vue";
|
||||
import ContactCollaborativeEditor from "~~/app/components/ContactCollaborativeEditor.client.vue";
|
||||
import MarkdownRichEditor from "~~/app/components/workspace/documents/MarkdownRichEditor.client.vue";
|
||||
|
||||
type DocumentSortOption = {
|
||||
value: string;
|
||||
@@ -179,11 +179,9 @@ onBeforeUnmount(() => {
|
||||
</div>
|
||||
|
||||
<div class="mt-3 min-h-0 flex-1 overflow-y-auto">
|
||||
<ContactCollaborativeEditor
|
||||
<MarkdownRichEditor
|
||||
:key="`doc-editor-${props.selectedDocument.id}`"
|
||||
:model-value="props.selectedDocument.body"
|
||||
:markdown="true"
|
||||
:room="`crm-doc-${props.selectedDocument.id}`"
|
||||
placeholder="Describe policy, steps, rules, and exceptions..."
|
||||
@update:model-value="emit('update-selected-document-body', $event)"
|
||||
/>
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
<script setup lang="ts">
|
||||
import { onBeforeUnmount, onMounted, ref, watch } from "vue";
|
||||
|
||||
type ToastUiEditor = {
|
||||
destroy: () => void;
|
||||
getMarkdown: () => string;
|
||||
getHTML: () => string;
|
||||
setMarkdown: (markdown: string, cursorToEnd?: boolean) => void;
|
||||
setHTML: (html: string, cursorToEnd?: boolean) => void;
|
||||
};
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue: string;
|
||||
placeholder?: string;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(event: "update:modelValue", value: string): void;
|
||||
}>();
|
||||
|
||||
const mountEl = ref<HTMLDivElement | null>(null);
|
||||
const editor = ref<ToastUiEditor | null>(null);
|
||||
const isSyncing = ref(false);
|
||||
|
||||
function looksLikeHtml(value: string) {
|
||||
return /<([a-z][\w-]*)\b[^>]*>/i.test(value);
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
if (!mountEl.value) return;
|
||||
const [{ default: Editor }] = await Promise.all([
|
||||
import("@toast-ui/editor"),
|
||||
import("@toast-ui/editor/dist/toastui-editor.css"),
|
||||
]);
|
||||
|
||||
const initialValue = String(props.modelValue ?? "");
|
||||
const instance = new Editor({
|
||||
el: mountEl.value,
|
||||
initialEditType: "markdown",
|
||||
previewStyle: "vertical",
|
||||
initialValue: looksLikeHtml(initialValue) ? "" : initialValue,
|
||||
placeholder: props.placeholder ?? "Write with Markdown...",
|
||||
height: "520px",
|
||||
usageStatistics: false,
|
||||
events: {
|
||||
change: () => {
|
||||
if (isSyncing.value) return;
|
||||
emit("update:modelValue", instance.getMarkdown());
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (looksLikeHtml(initialValue)) {
|
||||
isSyncing.value = true;
|
||||
instance.setHTML(initialValue, false);
|
||||
emit("update:modelValue", instance.getMarkdown());
|
||||
isSyncing.value = false;
|
||||
}
|
||||
|
||||
editor.value = instance as unknown as ToastUiEditor;
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
(incoming) => {
|
||||
const instance = editor.value;
|
||||
if (!instance || isSyncing.value) return;
|
||||
const next = String(incoming ?? "");
|
||||
const currentMarkdown = instance.getMarkdown();
|
||||
const currentHtml = instance.getHTML();
|
||||
if (next === currentMarkdown || next === currentHtml) return;
|
||||
|
||||
isSyncing.value = true;
|
||||
if (looksLikeHtml(next)) {
|
||||
instance.setHTML(next, false);
|
||||
emit("update:modelValue", instance.getMarkdown());
|
||||
} else {
|
||||
instance.setMarkdown(next, false);
|
||||
}
|
||||
isSyncing.value = false;
|
||||
},
|
||||
);
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
editor.value?.destroy();
|
||||
editor.value = null;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="markdown-rich-editor min-h-[420px]">
|
||||
<div ref="mountEl" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.markdown-rich-editor :deep(.toastui-editor-defaultUI) {
|
||||
border-radius: 0.75rem;
|
||||
border: 1px solid color-mix(in oklab, var(--color-base-content) 14%, transparent);
|
||||
}
|
||||
|
||||
.markdown-rich-editor :deep(.toastui-editor-mode-switch) {
|
||||
border-top: 1px solid color-mix(in oklab, var(--color-base-content) 12%, transparent);
|
||||
}
|
||||
|
||||
.markdown-rich-editor :deep(.toastui-editor-main-container) {
|
||||
min-height: 360px;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user