You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

11 KiB

Vditor Unification Implementation Plan

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal: 将所有 Markdown 可编辑输入场景统一到 Vditor,并满足 PC 端完整编辑体验与移动端编辑优先体验。

Architecture: 保留 PostBodyMarkdownEditor 作为唯一入口组件,对外接口继续使用 v-model,内部替换为 Vditor 实例并集中处理生命周期、上传回调与端侧差异化配置。页面层尽量不改动业务逻辑,只做必要的样式/交互对齐;通过组件级测试与关键路径手测保障保存、回填、上传与移动端可用性。

Tech Stack: Nuxt 4、Vue 3、TypeScript、Vditor、Bun


File Structure

Create

  • app/components/PostBodyMarkdownEditor.vditor.test.ts:编辑器组件行为测试(同步、上传、移动端配置切换)

Modify

  • package.json:新增 vditor 依赖,移除 md-editor-v3(如无其他引用)
  • app/components/PostBodyMarkdownEditor.vue:内部从 md-editor-v3 替换为 Vditor
  • app/pages/me/posts/new.vue:仅在必要时调整容器样式(若编辑器高度或移动端布局需要)
  • app/pages/me/posts/[id].vue:仅在必要时调整容器样式(与新建页一致)

Test

  • app/components/PostBodyMarkdownEditor.vditor.test.ts
  • 手工验证页面:app/pages/me/posts/new.vueapp/pages/me/posts/[id].vue

Task 1: 依赖切换与基线校验

Files:

  • Modify: package.json

  • Step 1: 写失败检查(确认旧编辑器仍被引用)

rg "md-editor-v3" app package.json

Expected: 至少包含 PostBodyMarkdownEditor.vuepackage.json 命中,表示替换前基线成立。

  • Step 2: 运行检查确认基线

Run: rg "md-editor-v3" app package.json
Expected: 有匹配结果(旧实现仍在)

  • Step 3: 更新依赖为 Vditor
{
  "dependencies": {
    "vditor": "^3.10.0"
  }
}

并移除:

{
  "dependencies": {
    "md-editor-v3": "..."
  }
}
  • Step 4: 安装依赖并校验 lockfile 变更

Run: bun install
Expected: vditor 安装成功,依赖树无冲突错误

  • Step 5: 提交
git add package.json bun.lock
git commit -m "chore(markdown): replace md-editor-v3 dependency with vditor"

Task 2: 重写 PostBodyMarkdownEditor 组件为 Vditor 实现

Files:

  • Modify: app/components/PostBodyMarkdownEditor.vue

  • Test: app/components/PostBodyMarkdownEditor.vditor.test.ts

  • Step 1: 写失败测试(v-model 双向同步)

import { describe, test, expect } from "bun:test";

describe("PostBodyMarkdownEditor v-model sync", () => {
  test("emits update when editor content changes", async () => {
    // mount component with modelValue = "a"
    // simulate editor input -> "b"
    // expect update:modelValue emitted with "b"
    expect(true).toBe(false);
  });
});
  • Step 2: 运行测试确认失败

Run: bun test app/components/PostBodyMarkdownEditor.vditor.test.ts
Expected: FAIL(测试占位断言失败或组件尚未适配 Vditor)

  • Step 3: 写最小实现(客户端初始化 + 销毁 + 双向同步)
import Vditor from "vditor";
import "vditor/dist/index.css";

const editor = shallowRef<Vditor | null>(null);
const hostEl = ref<HTMLElement | null>(null);

onMounted(() => {
  if (!hostEl.value) return;
  editor.value = new Vditor(hostEl.value, {
    value: props.modelValue,
    input(value) {
      emit("update:modelValue", value);
    },
  });
});

watch(
  () => props.modelValue,
  (next) => {
    const inst = editor.value;
    if (!inst) return;
    if (inst.getValue() !== next) {
      inst.setValue(next, true);
    }
  },
);

onBeforeUnmount(() => {
  editor.value?.destroy();
  editor.value = null;
});
  • Step 4: 运行测试确认通过

Run: bun test app/components/PostBodyMarkdownEditor.vditor.test.ts
Expected: PASS(双向同步、实例销毁断言通过)

  • Step 5: 提交
git add app/components/PostBodyMarkdownEditor.vue app/components/PostBodyMarkdownEditor.vditor.test.ts
git commit -m "refactor(editor): migrate PostBodyMarkdownEditor to vditor core"

Task 3: 实现 PC/移动端差异化配置(A/B 策略)

Files:

  • Modify: app/components/PostBodyMarkdownEditor.vue

  • Test: app/components/PostBodyMarkdownEditor.vditor.test.ts

  • Step 1: 写失败测试(按断点应用不同 toolbar/preview 策略)

describe("PostBodyMarkdownEditor responsive config", () => {
  test("uses full toolbar on desktop and compact toolbar on mobile", async () => {
    // mock matchMedia for desktop/mobile
    // assert Vditor config differs by mode
    expect(true).toBe(false);
  });
});
  • Step 2: 运行测试确认失败

Run: bun test app/components/PostBodyMarkdownEditor.vditor.test.ts -t "responsive config"
Expected: FAIL(断点策略尚未实现)

  • Step 3: 增加断点配置逻辑
const isMobile = useMediaQuery("(max-width: 768px)");

const desktopToolbar = [
  "headings", "bold", "italic", "strike", "|",
  "list", "ordered-list", "check", "|",
  "quote", "line", "code", "inline-code", "|",
  "link", "upload", "table", "|", "preview", "fullscreen"
];

const mobileToolbar = ["bold", "italic", "headings", "list", "link", "upload", "code"];

const options = {
  toolbar: isMobile.value ? mobileToolbar : desktopToolbar,
  mode: isMobile.value ? "sv" : "wysiwyg",
  preview: {
    mode: isMobile.value ? "editor" : "both",
  },
};

并确保移动端存在可进入预览的入口(按钮或工具栏项)。

  • Step 4: 运行测试确认通过

Run: bun test app/components/PostBodyMarkdownEditor.vditor.test.ts -t "responsive config"
Expected: PASS

  • Step 5: 提交
git add app/components/PostBodyMarkdownEditor.vue app/components/PostBodyMarkdownEditor.vditor.test.ts
git commit -m "feat(editor): add desktop/mobile tailored vditor modes"

Task 4: 接入现有图片上传链路并对齐错误提示

Files:

  • Modify: app/components/PostBodyMarkdownEditor.vue

  • Test: app/components/PostBodyMarkdownEditor.vditor.test.ts

  • Step 1: 写失败测试(上传成功插入 URL,失败提示)

describe("PostBodyMarkdownEditor image upload", () => {
  test("inserts uploaded urls into markdown", async () => {
    // mock fetchData('/api/file/upload') -> { files: [{ url: "https://x/a.png" }] }
    // trigger upload hook
    // expect markdown contains image syntax with returned url
    expect(true).toBe(false);
  });
});
  • Step 2: 运行测试确认失败

Run: bun test app/components/PostBodyMarkdownEditor.vditor.test.ts -t "image upload"
Expected: FAIL(上传钩子尚未对接)

  • Step 3: 实现上传适配
upload: {
  accept: "image/*",
  handler: async (files) => {
    const form = new FormData();
    files.forEach((f) => form.append("file", f));
    try {
      const { files: uploaded } = await fetchData("/api/file/upload", {
        method: "POST",
        body: form,
      });
      toast.add({ title: "图片已上传", color: "success" });
      return JSON.stringify({
        msg: "",
        code: 0,
        data: { errFiles: [], succMap: Object.fromEntries(uploaded.map((x) => [x.url, x.url])) },
      });
    } catch {
      toast.add({ title: "图片上传失败", color: "warning" });
      return JSON.stringify({ msg: "upload failed", code: 1, data: { errFiles: [], succMap: {} } });
    }
  },
},
  • Step 4: 运行测试确认通过

Run: bun test app/components/PostBodyMarkdownEditor.vditor.test.ts -t "image upload"
Expected: PASS

  • Step 5: 提交
git add app/components/PostBodyMarkdownEditor.vue app/components/PostBodyMarkdownEditor.vditor.test.ts
git commit -m "feat(editor): wire vditor image upload to existing api"

Task 5: 页面回归与移动端可用性验证

Files:

  • Modify: app/pages/me/posts/new.vue(如需最小样式调整)

  • Modify: app/pages/me/posts/[id].vue(如需最小样式调整)

  • Step 1: 写失败检查(页面仍依赖统一编辑器组件)

rg "<PostBodyMarkdownEditor" app/pages/me/posts/new.vue app/pages/me/posts/[id].vue

Expected: 两个页面都命中,确认页面接入点统一。

  • Step 2: 启动开发环境并验证基础流程

Run: bun run dev
Expected:

  • 新建文章页可输入 Markdown、可保存

  • 编辑文章页可回填并保存

  • Step 3: 移动端手测(浏览器设备模拟)

Run: bun run dev + 浏览器 DevTools 移动设备模式
Expected:

  • 默认编辑优先

  • 工具栏为精简模式

  • 可进入预览视图

  • 软键盘/视口变化下输入区可持续编辑

  • Step 4: 执行相关自动化测试

Run: bun test app/components/PostBodyMarkdownEditor.vditor.test.ts
Expected: PASS

  • Step 5: 提交
git add app/pages/me/posts/new.vue app/pages/me/posts/[id].vue app/components/PostBodyMarkdownEditor.vditor.test.ts
git commit -m "test(editor): verify vditor behavior on post create/edit flows"

Task 6: 清理旧编辑器引用并最终验收

Files:

  • Modify: app/components/PostBodyMarkdownEditor.vue

  • Modify: package.json

  • Step 1: 写失败检查(仍存在旧编辑器运行时引用)

rg "md-editor-v3|MdEditor" app package.json

Expected: 若仍有命中则说明清理未完成。

  • Step 2: 清理所有旧编辑器运行时引用

Run: rg "md-editor-v3|MdEditor" app package.json
Expected: 无运行时代码命中(仅历史文档可保留)

  • Step 3: 执行最小验收命令

Run: bun test app/components/PostBodyMarkdownEditor.vditor.test.ts
Expected: PASS

  • Step 4: 手工闭环验收

Run: bun run dev
Expected:

  • PC 端:完整工具栏 + 可预览

  • 移动端:编辑优先 + 精简工具栏 + 次级预览

  • 图片上传成功插入,失败有提示

  • 保存/回填链路正常

  • Step 5: 提交

git add app/components/PostBodyMarkdownEditor.vue package.json bun.lock app/components/PostBodyMarkdownEditor.vditor.test.ts
git commit -m "refactor(markdown): fully unify editable markdown inputs on vditor"

Self-Review

  1. Spec coverage: 已覆盖范围边界(仅可编辑输入)、PC=A 与移动端=B 策略、上传链路、双向同步、生命周期、验收标准。
  2. Placeholder scan: 无 TBD/TODO/“后续补充”占位词;每个任务均给出命令与预期结果。
  3. Type consistency: 全程统一使用 PostBodyMarkdownEditormodelValue/update:modelValue/api/file/upload 命名,前后一致。