npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@nova_voyager/article-editor-vue3

v0.5.0

Published

Protocol-driven Vue 3 article editor based on Tiptap

Readme

Vue 3 Article Editor

一个基于 Vue 3、TypeScript、Tiptap 3 和 ProseMirror 的协议驱动文章编辑器。组件只接收和输出 ProseMirror 风格 JSON。

内置 Article Content Protocol v1,并提供协议注册、版本切换、显式迁移、Schema 校验和自定义工具栏能力。

安装与运行

npm install
npm run dev

验证组件库:

npm run check

构建产物位于 dist/

  • article-editor.js:ESM。
  • article-editor.umd.cjs:UMD/CommonJS。
  • article-editor.css:独立样式。
  • index.d.ts:TypeScript 声明入口。

基础用法

<script setup lang="ts">
import { ref } from 'vue'
import {
  ArticleEditor,
  ARTICLE_PROTOCOL_V1_ID,
  type JSONContent,
} from '@nova_voyager/article-editor-vue3'
import '@nova_voyager/article-editor-vue3/style.css'

const content = ref<JSONContent>({ type: 'doc', content: [] })

function save(payload: { content: JSONContent; protocol: string; valid: boolean }) {
  // 保存 payload.content
}
</script>

<template>
  <ArticleEditor
    v-model:content="content"
    :protocol="ARTICLE_PROTOCOL_V1_ID"
    :editable="true"
    @change="save"
  />
</template>

update:content 在有效编辑 transaction 后同步触发;change 默认防抖 200ms,适合连接保存逻辑。

配置

<ArticleEditor
  v-model:content="content"
  protocol="article-content@1"
  :config="{
    validationMode: 'strict',
    changeDebounce: 300,
    layout: {
      contentHeight: 480,
    },
    link: {
      defaultTarget: '_blank',
      allowedProtocols: ['http', 'https'],
    },
    image: {
      allowedProtocols: ['https'],
      accept: 'image/png,image/jpeg,image/webp',
      maxFileSize: 10 * 1024 * 1024,
    },
    table: {
      defaultRows: 3,
      defaultColumns: 3,
      maxRows: 20,
      maxColumns: 20,
      resizable: false,
    },
    codeBlock: {
      languages: [
        { value: '', label: '纯文本' },
        { value: 'typescript', label: 'TypeScript' },
      ],
    },
    toolbar: {
      image: false,
      textAlign: true,
      articleButton: true,
    },
  }"
/>

config.layout.contentHeight 设置正文滚动区域的高度,数字按像素处理,也可以传入 "60vh""calc(100vh - 240px)" 等 CSS 长度。默认值为 480px。工具栏位于滚动区域之外,正文滚动时会固定在编辑器顶部。

配置只能隐藏或收紧协议已有能力。若需要增加 Node、Mark 或属性,应注册新的协议版本。

普通链接与自定义链接

V1 协议的 link mark 支持 hrefcustom 两种类型。工具栏的“链接类型”可以在两者之间切换。

href 链接

type: "href" 要求提供 hreftarget,不能包含 idtitle。为了兼容旧文章,省略 type 时也按 href 链接处理:

{
  "type": "link",
  "attrs": {
    "type": "href",
    "href": "#section",
    "target": "_self"
  }
}

href 支持不带协议的 URI 引用、配置中允许的绝对 URL 协议,以及 fragment-only 页面锚点。以下值都可以通过严格模式校验:

#
#section
example.com/article
https://example.com/article#section

普通文本链接和 articleButton 链接都会保留输入的 href,不会为不带协议的地址自动补充 https://。例如输入 example.com/article,协议 JSON 中仍保存为 example.com/article。 如果输入中显式包含协议,则该协议仍必须位于 config.link.allowedProtocols 白名单中。

fragment-only 例外只适用于 link.href,不适用于图片 srcjavascript:data: 等未配置的协议仍会被严格模式拒绝。

custom 链接

type: "custom" 要求提供非空的 idtitletarget,并且不能包含 href

{
  "type": "link",
  "attrs": {
    "type": "custom",
    "id": "article-42",
    "title": "查看相关文章",
    "target": "_blank"
  }
}

custom 链接渲染为没有 href<a>,对应属性为 data-link-type="custom"data-link-idtitletarget。宿主展示文章时可根据 data-link-id 或协议 JSON 中的 id 实现自己的跳转逻辑。

协议注册与切换

协议结构:

interface ArticleProtocolDefinition {
  id: string
  version: string
  label: string
  capabilities: ProtocolCapabilities
  createExtensions(config): Extensions
  validate(content, config): ValidationResult
  toEditorJSON(content): JSONContent
  fromEditorJSON(content): JSONContent
  migrations?: ProtocolMigration[]
}

注册并切换到新协议:

import {
  articleProtocolV1,
  cloneJSON,
  type ArticleProtocolDefinition,
} from '@nova_voyager/article-editor-vue3'

const articleProtocolV2: ArticleProtocolDefinition = {
  ...articleProtocolV1,
  id: 'article-content@2',
  version: '2.0.0',
  label: 'Article Content Protocol v2',
  // 实际项目应替换为 v2 Extension、校验器和 JSON 适配器。
  migrations: [
    {
      from: articleProtocolV1.id,
      migrate: cloneJSON,
    },
  ],
}
<ArticleEditor
  v-model:content="content"
  :protocol="currentProtocol"
  :protocols="[articleProtocolV2]"
  @protocol-changed="handleProtocolChanged"
  @protocol-change-error="handleProtocolError"
/>

协议切换会先迁移并校验目标 JSON,再创建目标 Schema。校验或创建失败时,原编辑器实例和内容保持不变。Tiptap Schema 不支持安全的原地替换,因此成功切换时会原子化重建编辑器。

图片接入

点击默认工具栏的图片按钮后,可选择“上传图片”或“网络图片”:“网络图片”会打开 URL 表单;“上传图片”会调用宿主项目传入的 imageUploader(file)。编辑器不绑定具体上传服务,只把上传成功后的 URL 写入协议 JSON。

图片插入后,点击正文中的图片即可在工具栏看到“图片居左”“图片居中”“图片居右”三个按钮。对齐结果保存到图片节点的 attrs.imageAlign,允许值为 leftcenterright;旧数据没有该属性时按居中显示。

上传图片

下面是一个完整的 Vue 3 接入示例:

<script setup lang="ts">
import { ref } from 'vue'
import {
  ArticleEditor,
  type ArticleEditorConfig,
  type ImageUploader,
  type JSONContent,
} from '@nova_voyager/article-editor-vue3'
import '@nova_voyager/article-editor-vue3/style.css'

const content = ref<JSONContent>({ type: 'doc', content: [] })

const editorConfig: ArticleEditorConfig = {
  image: {
    allowedProtocols: ['https'],
    accept: 'image/png,image/jpeg,image/webp',
    maxFileSize: 10 * 1024 * 1024,
  },
}

const uploadImage: ImageUploader = async (file) => {
  const body = new FormData()
  body.append('file', file)

  const response = await fetch('/api/article-images', {
    method: 'POST',
    body,
  })

  if (!response.ok) {
    throw new Error('图片上传失败')
  }

  const result = await response.json() as {
    url: string
    width?: number
    height?: number
  }

  return {
    src: result.url,
    alt: file.name,
    width: result.width,
    height: result.height,
    imageAlign: 'center',
  }
}

function handleEditorError(error: Error) {
  console.error(error.message)
}
</script>

<template>
  <ArticleEditor
    v-model:content="content"
    :config="editorConfig"
    :image-uploader="uploadImage"
    @error="handleEditorError"
  />
</template>

imageUploader 可以返回以下结果:

  • URL 字符串,例如 https://cdn.example.com/image.png
  • 图片属性对象 { src, alt?, title?, width?, height?, imageAlign? }
  • null,表示用户取消,不插入图片。

传入 imageUploader 后,也可以直接把一张或多张本地图片拖入可编辑的正文区域。编辑器会:

  • 使用同一个 imageUploader(file) 逐张上传图片,并保持文件原有顺序。
  • 使用鼠标松开时对应的正文位置插入上传结果并立即回显。
  • 在上传前执行 config.image.acceptconfig.image.maxFileSize 校验。
  • 对上传结果执行 URL 协议、宽高和 imageAlign 校验。

拖拽过程中正文区域会显示上传提示。只读模式、未提供 imageUploader 或当前协议不支持 image 节点时,组件不会接管图片拖放。单张图片上传失败不会阻止同一批次中其他有效图片 插入,错误仍通过组件的 error 事件返回。

回调抛出的错误会通过组件的 error 事件传出。config.image.accept 控制文件选择器接受的类型,config.image.maxFileSize 控制上传前的文件大小校验;默认分别为 image/* 和 10 MB。

上传服务返回的 src 必须符合 config.image.allowedProtocols。v1 协议保存的是最终图片 URL,不会把 File、Base64 或临时对象 URL 写入文章 JSON。

网络图片与素材库

“网络图片”不需要配置上传器,用户可以直接填写图片 URL、替代文本、标题和尺寸。

已有素材库可以继续使用 imageResolver。当没有配置 imageUploader、但配置了 imageResolver 时,“上传图片”入口会调用素材选择器:

async function selectImage() {
  const asset = await openAssetPicker()
  return asset ? { src: asset.url, alt: asset.alt } : null
}

文章按钮

v1 协议支持独立的 articleButton 块节点。点击工具栏的“文章按钮”后,可以选择“手动配置”或“从外部选择”。textbutton 样式需要填写文章 ID;link 样式的文章 ID 为选填。显示文字和样式始终必填,提示文字为选填。

生成的协议 JSON:

{
  "type": "articleButton",
  "attrs": {
    "id": "article-123",
    "text": "查看文章",
    "title": "打开相关文章",
    "style": "button"
  }
}

style 支持 buttontextlinktext 是文字操作样式;link 会渲染为 <a>,并允许携带 href;其他样式继续渲染为 <button>title 对应 HTML 的悬停提示,未填写时不会输出该字段。

链接样式示例:

{
  "type": "articleButton",
  "attrs": {
    "text": "查看完整文章",
    "style": "link",
    "href": "https://example.com/articles/456"
  }
}

协议规定 href 只能在 style: "link" 时出现。默认手动配置表单选择“链接”后会把文章 ID 标记为选填,同时显示链接地址并要求填写;支持不带协议的地址、配置允许的绝对 URL,以及 ##section 等 fragment-only 地址,输入值不会被自动补充 https://。外部选择器返回 link 样式时也应提供 href,可以省略 idtextbutton 样式仍必须提供非空 id

接入外部文章选择器

宿主项目通过 articleButtonResolver 接入自己的弹窗、列表或素材库。resolver 可以同步返回,也可以返回 Promise;返回 null 表示用户取消。

<script setup lang="ts">
import { ref } from 'vue'
import {
  ArticleEditor,
  type ArticleButtonResolver,
  type JSONContent,
} from '@nova_voyager/article-editor-vue3'
import '@nova_voyager/article-editor-vue3/style.css'

const content = ref<JSONContent>({ type: 'doc', content: [] })

const selectArticle: ArticleButtonResolver = async ({ mode, current }) => {
  // openArticlePicker 由宿主项目实现,可以打开任意 Vue 弹窗或选择组件。
  const selected = await openArticlePicker({ mode, currentId: current?.id })
  if (!selected) return null

  return {
    id: selected.id,
    text: selected.title,
    title: `打开文章:${selected.title}`,
    style: 'link',
    href: selected.url,
  }
}
</script>

<template>
  <ArticleEditor
    v-model:content="content"
    :article-button-resolver="selectArticle"
    @error="console.error($event)"
  />
</template>

如果当前选中了已有的文章按钮,resolver 会收到 { mode: 'update', current };否则收到 { mode: 'insert', current: undefined }。返回结果只会保留协议允许的 idtexttitlestylehref 字段,字符串会去除首尾空格。href 仅在 style: 'link' 时保留;link 可省略 id,其他样式缺少 id 时会拒绝插入。

编辑器内点击文章按钮只会选中节点,便于再次编辑,不会执行业务跳转。最终展示页面可读取 data-article-button-id 或协议 JSON 中的 attrs.id,自行实现打开文章等行为。

仓库内置了一个可运行的外部选择器 Demo。执行 npm run dev 后,点击“文章按钮 → 从外部选择”,可以体验文章搜索、列表选择、样式选择、取消,以及编辑已有文章按钮时的数据回填。Promise 桥接代码位于 src/App.vue,独立选择弹窗位于 src/components/DemoArticlePicker.vue

自定义工具栏

可以替换默认工具栏。工具栏应只调用 Tiptap command,不能直接修改 JSON:

<ArticleEditor v-model:content="content">
  <template #toolbar="{ editor, protocol, config, revision }">
    <MyToolbar
      :editor="editor"
      :protocol="protocol"
      :config="config"
      :revision="revision"
    />
  </template>
</ArticleEditor>

组件也导出 useArticleEditor(),可用于构建完全无默认 UI 的编辑器封装。

Props

| 名称 | 类型 | 默认值 | 说明 | |---|---|---|---| | content | JSONContent | 空文档 | 协议 JSON | | protocol | string \| ArticleProtocolDefinition | article-content@1 | 当前协议 | | protocols | ArticleProtocolDefinition[] | [] | 附加协议注册表 | | config | ArticleEditorConfig | {} | 交互与限制配置 | | editable | boolean | true | 是否允许编辑 | | showToolbar | boolean | true | 是否渲染工具栏 slot/default toolbar | | imageResolver | ImageResolver | undefined | 外部图片选择器 | | imageUploader | ImageUploader | undefined | 本地图片上传回调,返回 URL 或图片属性 | | articleButtonResolver | ArticleButtonResolver | undefined | 外部文章选择器,返回 articleButton 属性或 null |

Events

| 事件 | 说明 | |---|---| | update:content | 同步有效协议 JSON | | change | 防抖后的 { content, protocol, valid } | | ready | 编辑器实例创建完成 | | validation-error | 输入、输出或切换校验失败 | | protocol-changed | 协议切换成功 | | protocol-change-error | 协议切换失败 | | error | 编辑器或外部解析器运行错误 |

暴露方法

  • editor:当前 Tiptap Vue Editor。
  • activeProtocol:当前协议定义。
  • setContent(content):校验并静默替换内容。
  • getJSON():取得协议 JSON。
  • validate(content?):执行当前协议校验。
  • focus(position?):聚焦编辑器。

v1 范围

支持 paragraph、H1–H6、blockquote、bullet/ordered list、code block、horizontal rule、URL image、articleButton、table,以及 bold、italic、strike、underline、inline code 和 link。paragraph 与 heading 额外支持 attrs.textAlign,允许 leftcenterrightjustify;image 支持 attrs.imageAlign,允许 leftcenterright;link 支持 hrefcustom 两种类型;articleButton 支持 textbutton 和带 hreflink 样式。

不支持 Highlight、上下标、Video、Mention、Emoji、Embed、AI Block 或协作评论。完整示例见 examples/article-demo.json