Jelajahi Sumber

feat(编辑器): 新增分类选择组件并优化编辑器功能

- 添加分类选择组件(CateSelect.vue)支持带图标的分类选择
- 扩展PostBody接口增加分类和描述字段
- 实现编辑器页面根据URL参数预设分类
- 优化浮动按钮功能,支持带分类的跳转
- 添加新的图标资源
- 改进编辑器表单验证和提交逻辑
Sakulin 2 bulan lalu
induk
melakukan
df315b0000

+ 25 - 1
src/assets/icons.ts

@@ -1,5 +1,5 @@
 
-interface Icon {
+export interface Icon {
     template: string
 }
 
@@ -61,3 +61,27 @@ export const closeIcon: Icon = {
   </svg>
   `
 }
+
+export const codeIcon: Icon = {
+  template: `
+  <svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg">
+    <path d="M277.432637 293.177226L77.243245 482.660788c-15.280008 14.521738-16.044418 38.968523-1.521656 54.249555l187.953719 200.187345c6.877641 8.402367 17.575284 12.227486 27.506471 12.227486 9.17394 0 19.10615-3.060709 26.743084-9.93835 15.286148-14.516622 16.043395-38.962384 1.529843-54.242392L157.470451 512.462534l172.680874-163.514097c15.281031-14.514575 16.046464-38.969547 1.530866-54.249555-14.514575-15.278985-38.969547-16.045441-54.249554-1.521656zM946.753685 481.896378l-200.187345-189.485609c-15.278985-14.522762-39.726793-13.757329-54.241368 1.523703-14.521738 15.287171-13.757329 39.73498 1.521656 54.249555l172.681898 163.514098-162.750711 172.679851c-14.514575 15.281031-13.750165 39.727817 1.530866 54.249555 6.877641 6.872524 16.808828 10.698666 25.977651 10.698666 9.931187 0 20.627806-3.826142 28.271904-12.991896l188.726315-200.187345c14.514575-15.283078 13.750165-39.72884-1.530866-54.250578zM573.893652 199.192691c-20.62883-4.583388-41.265846 8.40953-45.849234 29.037337L403.499867 779.119757c-4.583388 20.629853 8.40953 41.258683 29.037337 45.842071 3.053546 0.76441 5.348821 0.76441 8.401344 0.76441 17.575284 0 33.621748-12.220323 37.437657-29.795607l124.545574-550.890752c4.584412-20.635993-8.401344-41.263799-29.028127-45.847188z"/>
+  </svg>
+  `
+}
+
+export const diaryIcon: Icon = {
+  template: `
+  <svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg">
+    <path d="M269.92364 141.786154c-15.698467 0-28.497217 12.79875-28.497217 28.497217s12.79875 28.497217 28.497217 28.497217h569.544381c15.698467 0 28.497217-12.79875 28.497217-28.497217s-12.79875-28.497217-28.497217-28.497217H269.92364z m652.736256 162.784103c-0.999902-2.299775-2.599746-4.19959-4.099599-6.199395-0.699932-0.899912-1.099893-2.099795-1.899815-2.899717-1.599844-1.599844-3.599648-2.599746-5.499463-3.799629-1.199883-0.699932-2.199785-1.799824-3.499658-2.399765-1.699834-0.699932-3.599648-0.699932-5.399473-1.099893-1.899814-0.399961-3.599648-1.099893-5.599453-1.099892l-626.538814-2.399766c-62.793868 0-113.888878-51.09501-113.888878-113.888878S207.129772 56.994434 269.92364 56.994434h626.438825c15.698467 0 28.497217-12.79875 28.497217-28.497217S912.160922 0 896.462455 0H269.92364C175.732839 0 99.140318 76.59252 99.140318 170.883312c0 2.199785 0.299971 4.39957 0.299971 6.599356 0 0.599941-0.299971 1.099893-0.299971 1.699834v673.934186c0 94.190802 76.59252 170.883312 170.883313 170.883312h498.351332c15.698467 0 28.497217-12.69876 28.497217-28.497217 0-15.698467-12.69876-28.497217-28.497217-28.497217H269.92364C207.129772 966.905576 156.034762 915.910556 156.034762 853.016698V297.870911c30.297041 27.197344 70.093155 43.895713 113.788888 43.895713L867.965238 343.96641v535.247729c0 15.698467 12.69876 28.497217 28.497217 28.497217s28.497217-12.69876 28.497217-28.497217V315.569183c0-2.199785-0.799922-4.19959-1.299873-6.299385-0.399961-1.599844-0.399961-3.299678-0.999903-4.699541z"/>
+  </svg>
+  `
+}
+
+export const othersIcon: Icon = {
+  template: `
+  <svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg">
+    <path d="M64 512c0-31.44 10.92-58.07 32.75-79.91 21.83-21.83 48.47-32.75 79.9-32.75 31.44 0 58.08 10.92 79.91 32.75s32.75 48.47 32.75 79.91-10.92 58.07-32.75 79.91c-21.83 21.83-48.47 32.75-79.91 32.75-31.44 0-58.07-10.91-79.9-32.75C74.92 570.07 64 543.44 64 512z m335.35 0c0-31.44 10.91-58.07 32.75-79.91 21.83-21.83 48.47-32.75 79.91-32.75 31.44 0 58.07 10.92 79.91 32.75 21.83 21.83 32.75 48.47 32.75 79.91s-10.92 58.07-32.75 79.91c-21.83 21.83-48.47 32.75-79.91 32.75-31.44 0-58.07-10.91-79.91-32.75-21.84-21.84-32.75-48.47-32.75-79.91z m335.34 0c0-31.44 10.92-58.07 32.75-79.91 21.83-21.83 48.47-32.75 79.91-32.75s58.07 10.92 79.91 32.75C949.09 453.93 960 480.56 960 512s-10.91 58.07-32.75 79.91-48.47 32.75-79.91 32.75-58.07-10.91-79.91-32.75c-21.82-21.84-32.74-48.47-32.74-79.91z"/>
+  </svg>
+  `
+}

+ 11 - 0
src/assets/main.css

@@ -343,3 +343,14 @@ a {
   background-size: 100% 100%;
   color: var(--text-color);
 }
+
+.p-button-disabled {
+  padding: 8px 12px;
+  background-size: 100% 2px;
+  font-weight: bold;
+  transition: all .3s;
+  cursor: not-allowed;
+  color: var(--muted-text-color);
+  background: linear-gradient(to right, var(--muted-text-color), var(--muted-text-color)) no-repeat left bottom;
+  background-size: 100% 2px;
+}

+ 73 - 17
src/components/BetterSelect.vue → src/components/CateSelect.vue

@@ -1,11 +1,11 @@
 <script setup lang="ts">
 import { ref, computed, onMounted, ref as elementRef, onUnmounted, watch } from 'vue';
+import type { Icon } from '@/assets/icons.ts'
 
 const props = defineProps<{
-  options: { value: string; label: string }[];
+  options: { value: string; label: string; svg: Icon }[];
   modelValue: string;
   placeholder: string;
-  width: string;
 }>();
 
 const emit = defineEmits(['update:modelValue']);
@@ -43,7 +43,6 @@ const updateOptionsListPosition = () => {
     if (selectedValueRef.value && optionsListRef.value && showOptions.value) {
       optionsListRef.value.style.top = selectedValueRef.value.getBoundingClientRect().bottom + 'px';
       optionsListRef.value.style.left = selectedValueRef.value.getBoundingClientRect().left + 'px';
-      optionsListRef.value.style.width = selectedValueRef.value.offsetWidth + 'px';
     }
     updateTimer = null;
   }, 300);
@@ -70,7 +69,10 @@ watch(showOptions, (newValue) => {
 <template>
   <div class="better-select" :class="showOptions ? ['showing-select'] : []">
     <div ref="selectedValueRef" :class="selectedOption ? [] : ['placeholder-showing']" class="selected-value" @click="showOptions = !showOptions">
-      {{ selectedOption?.label || props.placeholder }}
+      <div class="prefix-icon" v-html="selectedOption?.svg?.template ?? ''"/>
+      <div>
+        {{ selectedOption?.label || props.placeholder }}
+      </div>
     </div>
     <ul
       ref="optionsListRef"
@@ -79,8 +81,7 @@ watch(showOptions, (newValue) => {
       :style="{
         position: 'fixed',
         top: selectedValueRef?.getBoundingClientRect().bottom + 'px',
-        left: selectedValueRef?.getBoundingClientRect().left + 'px',
-        width: selectedValueRef?.offsetWidth + 'px'
+        left: selectedValueRef?.getBoundingClientRect().left + 'px'
       }"
     >
       <li
@@ -89,36 +90,85 @@ watch(showOptions, (newValue) => {
         :class="option.value == selectedOption?.value ? ['active'] : []"
         @click="handleSelect(option)"
       >
-        {{ option.label }}
+        <div class="prefix-icon" v-html="option.svg.template ?? ''"/>
+        <div>{{ option.label }}</div>
       </li>
     </ul>
   </div>
 </template>
 
 <style scoped>
+
+.prefix-icon {
+  fill: var(--text-color);
+  width: 24px;
+  height: 24px;
+  transform: translateY(2px);
+  transition: transform .3s;
+}
+
 .better-select {
   position: relative;
   text-align: center;
-  width: v-bind("props.width");
+  width: 48px;
+  overflow: hidden;
+  transition: all .3s;
 }
 
-.selected-value:hover {
-  background-size: 100% 2px;
+.better-select:hover {
+  width: 96px;
+}
+
+.showing-select {
+  width: 96px;
+}
+
+.better-select:hover .selected-value {
+  gap: 12px;
+  color: var(--text-color);
+  font-size: medium;
 }
 
 .showing-select .selected-value {
-  background-size: 100% 2px;
+  gap: 12px;
+  color: var(--text-color);
+  font-size: medium;
+}
+
+.better-select:hover .selected-value .prefix-icon {
+  transform: translateY(0);
+}
+
+.showing-select .selected-value .prefix-icon {
+  transform: translateY(0);
 }
 
+
+
 .selected-value {
-  padding: 2px;
+  width: 96px;
+  height: 38px;
+  padding: 2px 2px 2px 8px;
   cursor: pointer;
-  background: linear-gradient(to right, var(--text-color), var(--text-color)) no-repeat left bottom;
-  color: var(--text-color);
+  background: linear-gradient(to right, var(--text-color), var(--text-color)) no-repeat right bottom;
+  color: transparent;
+  font-size: xx-small;
+  font-weight: bold;
   background-size: 0 2px;
+  display: flex;
+  align-items: center;
+  gap: 24px;
   transition: all .3s;
 }
 
+.selected-value:hover {
+  background-size: 100% 2px;
+}
+
+.showing-select .selected-value {
+  background-size: 100% 2px;
+}
+
 .placeholder-showing {
   color: var(--secondary-text-color);
 }
@@ -127,11 +177,12 @@ watch(showOptions, (newValue) => {
   margin: 0;
   padding: 0;
   opacity: 0;
+  width: 96px;
   list-style-type: none;
   border-top: none;
   border-radius: 0 0 4px 4px;
   max-height: 200px;
-  overflow-y: hidden;
+  overflow-y: auto;
   background-color: var(--background-color);
   z-index: 1000;
   transform-origin: top;
@@ -142,18 +193,23 @@ watch(showOptions, (newValue) => {
 .showing-options-list {
   opacity: 100%;
   transform: scaleY(100%);
-  box-shadow: var(--muted-text-color) 0 0 4px;
+  box-shadow: rgba(0, 0, 0, 0.5) 0 0 12px;
 }
 
 .options-list li {
-  padding: 4px 0;
+  padding: 2px 2px 2px 8px;
+  height: 40px;
   cursor: pointer;
   text-align: center;
   transition: all .3s;
+  display: flex;
+  align-items: center;
+  gap: 12px;
 }
 
 .options-list .active {
   background-color: var(--secondary-background-color);
+  font-weight: bold;
 }
 
 .options-list li:hover {

+ 1 - 1
src/components/PostCard.vue

@@ -35,7 +35,7 @@ function handleRouteToDetail() {
 </template>
 
 <style scoped>
-.title span{
+.title span {
   cursor: pointer;
   background: linear-gradient(to right, var(--text-color), var(--text-color)) no-repeat left bottom;
   background-size: 0 2px;

+ 9 - 3
src/components/PostDetailContent.vue

@@ -9,16 +9,22 @@ const props = defineProps<{
 
 <template>
   <div>
-    <div class="post-item">
+    <div class="post-item preview-box">
       <h1 class="title">{{ props.target.title }}</h1>
       <div class="meta">
         <span class="date">{{ formateDateAccurateToDay(props.target.createdAt) }}</span>
         <span v-show="props.target.updatedAt !== props.target.createdAt" class="date">已更新于 {{ formateDateAccurateToDay(props.target.updatedAt) }}</span>
         <span class="cate">{{ props.target.cate }}</span>
       </div>
-      <p>{{ props.target.content }}</p>
+      <v-md-preview style="padding: 0 !important;" :text="props.target.content"/>
     </div>
   </div>
 </template>
 
-<style scoped></style>
+<style>
+
+.preview-box .vuepress-markdown-body {
+  padding: 0 !important;
+}
+
+</style>

+ 5 - 2
src/main.ts

@@ -7,9 +7,10 @@ import App from './App.vue'
 import { router } from './router'
 
 import VMdEditor from '@kangc/v-md-editor'
+import VMdPreview from '@kangc/v-md-editor/lib/preview'
 import '@kangc/v-md-editor/lib/style/base-editor.css'
 
-import githubTheme from '@kangc/v-md-editor/lib/theme/vuepress.js'
+import vuepressTheme from '@kangc/v-md-editor/lib/theme/vuepress.js'
 import '@kangc/v-md-editor/lib/theme/style/vuepress.css'
 
 import './assets/editordark.css'
@@ -20,12 +21,14 @@ import Prism from 'prismjs'
 import piniaPluginPersistence from "pinia-plugin-persistedstate"
 import { useTokenStore } from '@/stores/auth.ts'
 
-VMdEditor.use(githubTheme, { Prism })
+VMdEditor.use(vuepressTheme, { Prism });
+VMdPreview.use(vuepressTheme, { Prism });
 
 const app = createApp(App);
 app.use(router);
 
 app.use(VMdEditor);
+app.use(VMdPreview);
 
 const pinia = createPinia();
 

+ 3 - 1
src/models/index.ts

@@ -64,6 +64,8 @@ export interface PostBody {
   title: string
   content: string
   tags: string[]
+  cate: string
+  description?: string
 }
 
 export interface PushSuccess {
@@ -75,6 +77,6 @@ export interface PushSuccess {
 }
 
 export interface UpdateSuccess {
-  code: 200,
+  code: 200
   msg: string
 }

+ 78 - 43
src/views/PostEditView.vue

@@ -1,48 +1,51 @@
 <script setup lang="ts">
 import { computed, onMounted, ref } from 'vue'
-import BetterSelect from '@/components/BetterSelect.vue'
+import CateSelect from '@/components/CateSelect.vue'
 import TagCloud from '@/components/TagCloud.vue'
-import type { Tag } from '@/models'
+import type { PostBody, Tag } from '@/models'
 import { api } from '@/utils/axios.ts'
+import { codeIcon, diaryIcon, othersIcon, writeIcon } from '@/assets/icons.ts'
+import { router } from '@/router'
 
 const editorConfig = {
   leftToolbar: 'undo redo | image',
   rightToolbar: 'preview fullscreen',
 }
 
-const tags = ref<Tag[] | null>(null);
+const tags = ref<Tag[] | null>(null)
 
 function freshData() {
-  api.tagList().then(res => {
-    tags.value = res.data;
-  });
+  api.tagList().then((res) => {
+    tags.value = res.data
+  })
 }
 
-const activeTags = ref<string[]>([]);
+const activeTags = ref<string[]>([])
 
 const tagsView = computed<Tag[] | null>(() => {
-  if (!tags.value) return null;
-  return tags.value.map(e => {
-    if (activeTags.value.includes(e.name)) return e;
-    else return {
-      color: "#888888",
-      id: e.id,
-      name: e.name
-    }
+  if (!tags.value) return null
+  return tags.value.map((e) => {
+    if (activeTags.value.includes(e.name)) return e
+    else
+      return {
+        color: '#888888',
+        id: e.id,
+        name: e.name,
+      }
   })
-});
+})
 
 const handleSelectTag = (tag: Tag) => {
   if (activeTags.value.includes(tag.name)) {
-    activeTags.value = activeTags.value.filter(e => e != tag.name)
+    activeTags.value = activeTags.value.filter((e) => e != tag.name)
   } else {
     activeTags.value.push(tag.name)
   }
 }
 
 onMounted(() => {
-  freshData();
-});
+  freshData()
+})
 
 const title = ref('')
 
@@ -57,27 +60,61 @@ print('Hello, World!')
 
 `)
 
-const selected = ref('')
+const selectedCate = ref('其他')
+
+onMounted(() => {
+  let defaultCate = router.currentRoute.value.query['cate'];
+  if (Array.isArray(defaultCate) && defaultCate.length) {
+    defaultCate = defaultCate[0]
+  }
+  if (typeof defaultCate == "string" && cateOptions.map(e => e.value).includes(defaultCate)) {
+    selectedCate.value = defaultCate;
+  }
+})
 
-const options = [
-  {label: "技术", value: "技术"},
-  {label: "日志", value: "日志"},
-  {label: "随笔", value: "随笔"},
-  {label: "其他", value: "其他"},
+const cateOptions = [
+  { label: '技术', value: '技术', svg: codeIcon },
+  { label: '日志', value: '日志', svg: diaryIcon },
+  { label: '随笔', value: '随笔', svg: writeIcon },
+  { label: '其他', value: '其他', svg: othersIcon },
 ]
 
-const newTags = ref<string[]>([]);
+const newTags = ref<string[]>([])
+
+const validContent = computed(() => {
+  return text.value.length > 0 && title.value.length > 0 && selectedCate.value.length > 0
+})
+
+function buildPost(): PostBody | undefined {
+  if (!validContent.value) {
+    return undefined;
+  }
+  return {
+    content: text.value,
+    tags: [...activeTags.value, ...newTags.value],
+    title: title.value,
+    cate: selectedCate.value,
+  }
+}
 
+function handleSubmit() {
+  const postBody = buildPost();
+  if (postBody) {
+    api.postPush(postBody)
+  }
+}
 </script>
 
 <template>
   <div>
     <div class="post-item" style="padding: 12px 30px; overflow: hidden">
-      <div>
-        <input class="p-input" type="text" placeholder="博文标题" v-model="title" />
-      </div>
-      <div style="display: flex; align-items: center; gap: 12px">
-        <better-select :options="options" v-model="selected" placeholder="类型" width="64px"/>
+      <div style="display: flex; align-items: end; padding-bottom: 12px">
+        <cate-select
+          :options="cateOptions"
+          v-model="selectedCate"
+          placeholder="类型"
+        />
+        <input style="margin-bottom: 0" class="p-input" type="text" placeholder="博文标题" v-model="title" />
       </div>
     </div>
     <div class="post-item" style="padding: 0; overflow: hidden">
@@ -90,28 +127,26 @@ const newTags = ref<string[]>([]);
     </div>
 
     <div class="post-item" style="padding: 12px 30px 24px 30px">
-      <h1>
-        这篇文章是关于什么的?做个收纳吧~
-      </h1>
-      <TagCloud v-if="tagsView" :tags="tagsView" :on-click="handleSelectTag" v-model:new-tags="newTags" :addable="true"/>
+      <h1>这篇文章是关于什么的?做个收纳吧~</h1>
+      <TagCloud
+        v-if="tagsView"
+        :tags="tagsView"
+        :on-click="handleSelectTag"
+        v-model:new-tags="newTags"
+        :addable="true"
+      />
     </div>
 
     <div class="post-item footer" style="padding: 0 30px">
-      <div class="p-button">
-        保存草稿
-      </div>
-      <div class="p-button-success">
-        发布博文
-      </div>
+      <div class="p-button">保存草稿</div>
+      <div :class="validContent ? ['p-button-success'] : ['p-button-disabled']" @click="handleSubmit">发布博文</div>
     </div>
   </div>
 </template>
 
 <style scoped>
-
 .footer {
   display: flex;
   justify-content: end;
 }
-
 </style>

+ 50 - 46
src/views/PostView.vue

@@ -12,7 +12,7 @@ const data = ref<PostSketch[]>([])
 const pageSize = 10
 const isEnd = ref(false)
 
-const tokenStore = useTokenStore();
+const tokenStore = useTokenStore()
 
 function nextDatum() {
   if (isEnd.value) {
@@ -33,55 +33,66 @@ function nextDatum() {
 
 onMounted(() => {
   nextDatum()
-});
+})
 
-const tokenAvailable = ref(false);
+const tokenAvailable = ref(false)
 
 const floatBtnElementAnimationStyle = ref({
-  display: "none",
-  transform: "scale(0)",
-  transition: "all .3s"
-});
-
-watch(() => tokenStore.available, async (newValue) => {
-  if (!tokenAvailable.value && newValue) {
-    tokenAvailable.value = true;
-    floatBtnElementAnimationStyle.value.display = "block";
-    floatBtnElementAnimationStyle.value.transform = "scale(0)";
-    await delay(20);
-    floatBtnElementAnimationStyle.value.transform = "scale(100%)";
-  } else if (tokenAvailable.value && !newValue) {
-    tokenAvailable.value = false;
-    floatBtnElementAnimationStyle.value.transform = "scale(0)";
-    await delay(320);
-    floatBtnElementAnimationStyle.value.display = "none";
-  }
-}, {immediate: true});
-
-const tips = [
-  "写点东西?", "有新鲜事?", "有新技术?", "有新发现?", "有新想法?"
-];
-
-const tipIndex = ref(0);
-
-const currentTip = computed(() => tips[tipIndex.value]);
+  display: 'none',
+  transform: 'scale(0)',
+  transition: 'all .3s',
+})
+
+watch(
+  () => tokenStore.available,
+  async (newValue) => {
+    if (!tokenAvailable.value && newValue) {
+      tokenAvailable.value = true
+      floatBtnElementAnimationStyle.value.display = 'block'
+      floatBtnElementAnimationStyle.value.transform = 'scale(0)'
+      await delay(20)
+      floatBtnElementAnimationStyle.value.transform = 'scale(100%)'
+    } else if (tokenAvailable.value && !newValue) {
+      tokenAvailable.value = false
+      floatBtnElementAnimationStyle.value.transform = 'scale(0)'
+      await delay(320)
+      floatBtnElementAnimationStyle.value.display = 'none'
+    }
+  },
+  { immediate: true },
+)
+
+const tips: { content: string, value: string }[] = [
+  { content: '有新技术?', value: '技术' },
+  { content: '有新鲜事?', value: '日志' },
+  { content: '有新想法?', value: '随笔' },
+  { content: '写点东西?', value: '其他' },
+]
+
+const tipIndex = ref(0)
+
+const currentTip = computed(() => tips[tipIndex.value])
 
 const changeTip = () => {
-  tipIndex.value = (tipIndex.value + 1) % tips.length;
+  tipIndex.value = (tipIndex.value + 1) % tips.length
 }
 
 function handleWriteClick() {
-  router.push("/editor")
+  router.push(`/editor?cate=${currentTip.value.value}`)
 }
-
 </script>
 
 <template>
-  <div style="position: relative;">
-    <div class="float-btn" @click.stop="handleWriteClick" :style="floatBtnElementAnimationStyle" @mouseenter="changeTip">
+  <div style="position: relative">
+    <div
+      class="float-btn"
+      @click.stop="handleWriteClick"
+      :style="floatBtnElementAnimationStyle"
+      @mouseenter="changeTip"
+    >
       <div class="float-btn-content">
-        <div class="float-btn-ico" v-html="writeIcon.template"/>
-        <div>{{ currentTip }}</div>
+        <div class="float-btn-ico" v-html="writeIcon.template" />
+        <div>{{ currentTip.content }}</div>
       </div>
     </div>
     <PostCard v-for="item of data" :key="item.id" :target="item" />
@@ -92,13 +103,7 @@ function handleWriteClick() {
     >
       <div>没有更多了😭</div>
     </div>
-    <div
-      class="post-item show-more"
-      v-else
-      @click="nextDatum"
-    >
-      显示更多
-    </div>
+    <div class="post-item show-more" v-else @click="nextDatum">显示更多</div>
   </div>
 </template>
 
@@ -155,12 +160,11 @@ function handleWriteClick() {
 .float-btn-ico {
   width: 32px;
   height: 32px;
-  transition: all .3s;
+  transition: all 0.3s;
 }
 
 .float-btn:hover .float-btn-content .float-btn-ico {
   width: 48px;
   height: 48px;
 }
-
 </style>

+ 1 - 0
src/vme.d.ts

@@ -1,3 +1,4 @@
 declare module '@kangc/v-md-editor/lib/theme/vuepress.js';
 declare module '@kangc/v-md-editor/lib/theme/github.js';
 declare module '@kangc/v-md-editor';
+declare module '@kangc/v-md-editor/lib/preview';