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

yjweb-editor

v1.0.5

Published

A configurable Vue 3 rich text editor workbench for article editing and publishing.

Readme

yjweb-editor

一个基于 Vue 3 + TipTap 的文章编辑组件,支持:

  • 自动识别新增 / 编辑
  • 文章详情加载
  • 保存、发布、上传接口完全自定义
  • 自动携带 Authorization: Bearer ${token}
  • 图片插入后拖拽手柄缩放
  • 图片插入后会保留前后可输入区域
  • 封面上传后支持预览与自定义裁剪
  • 表格插入、行列增删、表头切换、单元格合并/拆分
  • docx 导入支持内嵌图片显示
  • 底部保存 / 发布按钮栏支持 footer slot 自定义
  • 通过组件事件接管错误和成功反馈,不再使用 alert

安装

npm install yjweb-editor

基础使用

<template>
  <YjEditor
    :config="config"
    @error="handleEditorError"
    @save="handleEditorSave"
    @publish="handleEditorPublish"
  />
</template>

<script setup lang="ts">
import { useRoute } from 'vue-router'
import YjEditor from 'yjweb-editor'
import 'yjweb-editor/style.css'

const route = useRoute()
const token = 'your-token'

const config = {
  initialData: {
    route: {
      params: route.params,
      query: route.query,
      keys: ['id', 'articleId'],
    },
  },
  api: {
    enabled: true,
    baseUrl: 'https://api.example.com',
    token,
    endpoints: {
      articleCreate: '/cms/article/create',
      articleUpdate: '/cms/article/update/:id',
      articleDetail: '/cms/article/detail/:id',
      publish: '/cms/article/publish',
      upload: '/cms/file/upload',
    },
  },
}

const handleEditorError = (payload: unknown) => {
  console.log('editor error', payload)
}

const handleEditorSave = (payload: unknown) => {
  console.log('editor saved', payload)
}

const handleEditorPublish = (payload: unknown) => {
  console.log('editor published', payload)
}
</script>

新增与编辑逻辑

  • 如果能从 initialData.articleId 获取到 id,则自动走详情接口并进入编辑模式。
  • 如果没有 articleId,则自动进入新增模式。
  • save
    • 新增模式调用新增接口
    • 编辑模式调用编辑接口
  • publish 始终调用发布接口

从 params / query 取 id

initialData: {
  route: {
    params: route.params,
    query: route.query,
    keys: ['id', 'articleId'],
  },
}

也可以直接传:

initialData: {
  articleId: 'article_001',
}

API 配置

默认端点配置

api: {
  enabled: true,
  token: 'your-token',
  endpoints: {
    articleCreate: '/cms/article/create',
    articleUpdate: '/cms/article/update/:id',
    articleDetail: '/cms/article/detail/:id',
    publish: '/cms/article/publish',
    upload: '/cms/file/upload',
  },
}

组件请求会自动追加:

headers['Authorization'] = `Bearer ${token}`

完全自定义请求结构

你可以对每个动作单独配置:

  • url
  • method
  • headers
  • params
  • query
  • body
api: {
  enabled: true,
  token: 'your-token',
  requests: {
    detail: {
      url: '/cms/article/detail/:id',
      method: 'GET',
      query: ({ articleId }) => ({
        articleId,
      }),
    },
    create: {
      url: '/cms/article/save',
      method: 'POST',
      body: ({ payload }) => ({
        title: payload?.meta.title,
        author: payload?.meta.author,
        html: payload?.content,
        coverUrl: payload?.meta.cover,
        sourceUrl: payload?.originLink,
      }),
    },
    update: {
      url: '/cms/article/save/:id',
      method: 'PUT',
      params: ({ articleId }) => ({
        id: articleId,
      }),
      body: ({ articleId, payload }) => ({
        articleId,
        title: payload?.meta.title,
        author: payload?.meta.author,
        html: payload?.content,
      }),
    },
    publish: {
      url: '/cms/article/publish',
      method: 'POST',
      body: ({ payload }) => ({
        articleId: payload?.id,
        title: payload?.meta.title,
        html: payload?.content,
      }),
    },
    upload: {
      url: '/cms/file/upload',
      method: 'POST',
    },
  },
}

字段映射

如果你不想完全自定义 body,也可以只配映射:

api: {
  mapping: {
    article: {
      id: 'data.articleId',
      title: 'data.articleTitle',
      author: 'data.writerName',
      cover: 'data.coverUrl',
      content: 'data.html',
      updatedAt: 'data.updateTime',
      originLink: 'data.sourceUrl',
    },
    publish: {
      id: 'data.articleId',
      publishedAt: 'data.publishTime',
      message: 'data.msg',
      title: 'articleTitle',
      author: 'writerName',
      cover: 'coverUrl',
      content: 'html',
      originLink: 'sourceUrl',
    },
    upload: {
      fileFieldName: 'uploadFile',
      typeFieldName: 'bizType',
      id: 'data.fileId',
      url: 'data.fileUrl',
      name: 'data.fileName',
      type: 'data.mediaType',
    },
  },
}

事件

组件不再使用 alert,统一通过事件抛出:

<YjEditor
  @error="handleEditorError"
  @save="handleEditorSave"
  @publish="handleEditorPublish"
/>

error 事件结构:

{
  action: 'detail' | 'create' | 'update' | 'publish' | 'upload' | 'import',
  message: string,
  error: unknown,
}

自定义底部按钮栏

默认底部会显示保存、发布按钮。你也可以通过 footer slot 接管按钮栏,slot 会把当前富文本值、元信息和默认保存 / 发布方法传出来:

<YjEditor :config="config">
  <template #footer="{ content, payload, getPayload, save, publish }">
    <div class="my-editor-footer">
      <span>当前正文长度:{{ content.length }}</span>
      <button @click="console.log(getPayload())">查看提交值</button>
      <button @click="save">保存草稿</button>
      <button @click="publish">发布文章</button>
    </div>
  </template>
</YjEditor>

slot 可用参数包括:

  • content:当前富文本 HTML
  • payload:当前文章提交结构
  • getPayload():先同步编辑器,再返回最新提交结构
  • metaoriginLinkisDirtyisSavingisPublishing
  • save()publish():组件默认保存 / 发布方法

图片缩放

上传图片后,图片会作为编辑器图片节点插入正文区域。
图片右下角有拖拽手柄,可以直接拖动调整宽度。

图片插入时会自动补充可输入段落,方便继续在图片前后编辑正文。

封面裁剪

上传封面后会先显示裁剪弹窗,可以直接拖动裁剪框,也可以拖拽四边和四个角调整裁剪范围。确认后组件会把裁剪后的封面图片上传,并在封面上传区域和左侧预览中显示。

文档导入

支持 .docx.txt.md.html.htm。其中 .docx 里的内嵌图片会转换为可显示的图片内容。

注意

  • 保存和发布前,组件会强制从编辑器实例同步最新 HTML,避免提交时正文仍然是 <p></p>
  • 如果你使用 vue-router,建议把 route.paramsroute.query 传给组件,这样 id 判定最稳定。