yuque-rich-text

Yuque (语雀) Lake rich-text editor & viewer for Vue 3 and React — extract from yuque-chrome-extension.
333 49
Install
npm install yuque-rich-text
Latest Version:1.1.0
Vue:3
License:MIT
Last Updated:Jul 13, 2026
Links: GitHub  ·  npm
Maintainer: entity-now

yuque-rich-text

npm version license

Yuque (语雀) Lake rich-text editor and viewer for Vue 3 and React.

Extracted from the official yuque-chrome-extension so you can embed the same Lake editing experience in your own apps.

Unofficial third-party package. Not affiliated with, maintained by, or endorsed by Yuque / 语雀.


Table of Contents


Features

Feature Description
Vue 3 & React First-class adapters with a shared core
Editor + Viewer Full WYSIWYG editing or read-only preview
Configurable upload Custom upload URLs and/or upload callbacks for image & video
Lake format Native Yuque Lake document format + HTML interop
Imperative API setContent / getContent / appendContent / wordCount / …
Iframe isolation Editor runs in a sandboxed iframe with Lake runtime

Screenshots

Component demo


Installation

# npm
npm install yuque-rich-text

# pnpm
pnpm add yuque-rich-text

# yarn
yarn add yuque-rich-text

Peer dependencies (install what you use):

# Vue 3
npm install vue@^3

# React
npm install react react-dom

Prerequisites (CDN assets)

Lake depends on global scripts/styles. Include them in your host HTML (or inject equivalently at runtime).

Styles (<head>)

<link
  rel="stylesheet"
  type="text/css"
  href="https://gw.alipayobjects.com/render/p/yuyan_npm/@alipay_lakex-doc/1.71.0/umd/doc.css"
/>
<link
  rel="stylesheet"
  type="text/css"
  href="https://gw.alipayobjects.com/os/lib/antd/4.24.13/dist/antd.css"
/>

Scripts (before app bootstrap, typically end of <body>)

<script crossorigin src="https://unpkg.com/react@18.2.0/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@18.2.0/umd/react-dom.production.min.js"></script>
<script src="https://gw.alipayobjects.com/render/p/yuyan_v/180020010000005484/7.1.4/CodeMirror.js"></script>
<script src="https://ur.alipay.com/tracert_a385.js"></script>
<script src="https://mdn.alipayobjects.com/design_kitchencore/afts/file/ANSZQ7GHQPMAAAAAAAAAAAAADhulAQBr"></script>
<script src="https://gw.alipayobjects.com/render/p/yuyan_npm/@alipay_lakex-doc/1.71.0/umd/doc.umd.js"></script>

The editor iframe also loads these assets via its internal template. Host-page inclusion is still recommended for consistent global Doc availability in some environments.


Quick Start

Vue 3

<template>
  <div class="editor-wrap">
    <YuqueRichText
      ref="editorRef"
      :value="content"
      :image-upload-u-r-l="imageUploadURL"
      :video-upload-u-r-l="videoUploadURL"
      :upload-image="uploadImage"
      @on-change="onChange"
      @on-load="onLoad"
      @on-save="onSave"
    />
  </div>
</template>

<script setup lang="ts">
import { ref } from "vue";
import { YuqueRichText } from "yuque-rich-text";
// or: import { YuqueRichText } from 'yuque-rich-text/vue'
import type { IEditorRef } from "yuque-rich-text";

const editorRef = ref<IEditorRef>();
const content = ref("<p>Hello Lake</p>");

/** Prefer kebab for multi-cap props, or use camelCase in script-bound objects */
const imageUploadURL = "/api/v1/upload/image";
const videoUploadURL = "/api/v1/upload/video";

const uploadImage = async (params: { data: string | File }) => {
  const form = new FormData();
  form.append("file", params.data);
  const res = await fetch(imageUploadURL, { method: "POST", body: form });
  const json = await res.json();
  return {
    url: json.url,
    size: json.size ?? 0,
    filename: json.filename ?? "image.png",
  };
};

const onChange = (value: string) => {
  // Do NOT write back into `content` from onChange with the same binding
  // if you also pass `:value="content"` without guards — that can loop.
  console.log("change", value);
};

const onLoad = () => {
  editorRef.value?.focusToStart();
};

const onSave = () => {
  // Ctrl/Cmd + Enter
  console.log(editorRef.value?.getContent("lake"));
};
</script>

<style scoped>
.editor-wrap {
  height: 480px;
  border: 1px solid #e5e7eb;
  border-radius: 8px;
  overflow: hidden;
}
</style>

Tip: Vue prop names like imageUploadURL are exposed as image-upload-u-r-l in templates. You can also pass a nested upload object to avoid awkward kebab names (see Upload configuration).

Using nested upload (recommended in Vue templates):

<YuqueRichText
  ref="editorRef"
  :value="content"
  :upload="{
    imageUploadURL: '/api/v1/upload/image',
    videoUploadURL: '/api/v1/upload/video',
    uploadImage,
  }"
  @on-change="onChange"
/>

React

import { useRef, useState } from "react";
import { LakeRich, type IEditorRef } from "yuque-rich-text/react";

export function DocEditor() {
  const editorRef = useRef<IEditorRef>(null);
  const [value, setValue] = useState("<p>Hello Lake</p>");

  return (
    <div style={{ height: 480 }}>
      <LakeRich
        ref={editorRef}
        value={value}
        imageUploadURL="/api/v1/upload/image"
        videoUploadURL="/api/v1/upload/video"
        uploadImage={async ({ data }) => {
          const form = new FormData();
          form.append("file", data);
          const res = await fetch("/api/v1/upload/image", {
            method: "POST",
            body: form,
          });
          const json = await res.json();
          return {
            url: json.url,
            size: json.size ?? 0,
            filename: json.filename ?? "image.png",
          };
        }}
        onChange={setValue}
        onLoad={() => editorRef.current?.focusToStart()}
        onSave={() => {
          console.log(editorRef.current?.getContent("lake"));
        }}
      />
    </div>
  );
}

Upload configuration

Previously the editor hard-coded /api/upload/image and /api/upload/video. You can now configure URLs and/or custom upload functions.

Option A — Upload URLs only

Lake will POST to your endpoints using its built-in uploader:

{
  imageUploadURL: "https://your.cdn.example/upload/image",
  imageCrawlURL: "https://your.cdn.example/crawl/image", // optional; defaults to imageUploadURL
  videoUploadURL: "https://your.cdn.example/upload/video",
}

Option B — Custom upload callbacks

Full control over auth headers, form fields, response mapping:

async function uploadImage(params: { data: string | File }) {
  // params.data is File | base64 string depending on Lake path
  const res = await yourUploader(params.data);
  return {
    url: res.publicUrl,
    size: res.bytes,
    filename: res.name,
  };
}

When uploadImage / uploadVideo is provided, it is passed to Lake as createUploadPromise and takes precedence for the upload pipeline.

Option C — Nested upload object

const upload = {
  imageUploadURL: "/api/upload/image",
  imageCrawlURL: "/api/upload/crawl",
  videoUploadURL: "/api/upload/video",
  uploadImage: async ({ data }) => { /* ... */ },
  uploadVideo: async ({ data }) => { /* ... */ },
};

Precedence: flat props (imageUploadURL, uploadImage, …) override the same keys inside upload.

Field Type Default Description
imageUploadURL string /api/upload/image Image upload endpoint
imageCrawlURL string same as imageUploadURL Remote image crawl / proxy
videoUploadURL string /api/upload/video Video upload endpoint
uploadImage (params) => Promise<UploadResult> — Custom image uploader
uploadVideo (params) => Promise<UploadResult> — Custom video uploader
interface UploadResult {
  url: string;
  size: number;
  filename: string;
}

API Reference

Props

Prop Type Default Description
value string '' Document content (Lake / HTML as accepted by Lake)
isview boolean false true → read-only viewer
isView boolean — React-only alias of isview
placeholder string '输入内容...' Empty-editor placeholder
defaultFontsize number 14 Default font size (px)
upload UploadConfig — Nested upload config
imageUploadURL string see above Image upload URL
imageCrawlURL string see above Image crawl URL
videoUploadURL string see above Video upload URL
uploadImage function — Custom image upload
uploadVideo function — Custom video upload

Events / Callbacks

Name Payload Description
onChange (value: string) Content changed (Lake string with meta)
onLoad () Editor / viewer ready
onSave () Ctrl+Enter (Windows/Linux) or Cmd+Enter (macOS*)

*Save chord follows the original extension behavior.

Vue also emits update:value for optional v-model:value usage. Avoid uncontrolled two-way binding loops: if you bind both :value and @onChange that immediately writes the same source, guard with equality checks.

Imperative API (ref)

interface IEditorRef {
  appendContent: (html: string, breakLine?: boolean) => void;
  setContent: (content: string, type?: "text/lake" | "text/html") => void;
  getContent: (type: "lake" | "text/html" | "description") => string;
  isEmpty: () => boolean;
  getSummaryContent: () => string;
  wordCount: () => number;
  focusToStart: (offset?: number) => void;
  insertBreakLine: () => void;
}
Method Description
appendContent(html, breakLine?) Append HTML; optional leading break
setContent(content, type?) Replace document (text/html default)
getContent(type) Read content as Lake / HTML / description
isEmpty() Whether document is empty
getSummaryContent() Summary in Lake format
wordCount() Word count
focusToStart(offset?) Focus start (optional paragraph offset)
insertBreakLine() Insert line break at selection

Call imperative methods after onLoad.


Viewer mode

Vue

<script setup lang="ts">
import { YuqueRichText, YuqueRichTextView } from "yuque-rich-text";
</script>

<template>
  <!-- Option 1 -->
  <YuqueRichText :isview="true" :value="htmlOrLake" />

  <!-- Option 2: dedicated viewer component -->
  <YuqueRichTextView :value="htmlOrLake" />
</template>

React

import { LakeRich, LakeRichView } from "yuque-rich-text/react";

<LakeRich isView value={content} />
// or
<LakeRichView value={content} />

Package exports

Subpath Framework Primary symbols
yuque-rich-text Vue 3 (default, backward compatible) YuqueRichText, YuqueRichTextView
yuque-rich-text/vue Vue 3 same as default
yuque-rich-text/react React LakeRich, LakeRichView

Shared types (IEditorRef, UploadConfig, …) are re-exported from each entry.


Development

# install
pnpm install   # or npm install

# local demo
pnpm dev

# library build → ./lib
pnpm build:lib

# static demo build
pnpm build:demo

Project layout

src/
  core/           # Framework-agnostic Lake mount + types
  vue/            # Vue 3 adapter
  react/          # React adapter
  components/     # Legacy paths (re-exports)
demo/             # Vite playground

Disclaimer

This is an unofficial third-party integration for Yuque. It is not affiliated with, maintained by, or endorsed by Yuque.

  • Use at your own risk. Authors are not responsible for ToS violations or damages.
  • Do not use if Yuque prohibits this usage in your context.
  • This project does not redistribute Yuque’s proprietary application source; it loads publicly referenced Lake runtime assets in the same manner as the open-source browser extension.

Please review Yuque’s Terms of Service before production use.


License

MIT


Credits

  • yuque-chrome-extension — original editor integration
  • Lake / lakex-doc runtime — Alipay / Yuque ecosystem

Related Packages

eddy-editor

A lightweight WYSIWYG text editor for Vue and React

3,184 3
@hebamuh28/mini-editor

A lightweight rich text editor component for Vue 3

1,383
@abhinavakhil/penna-vue

Vue 3 bindings for @abhinavakhil/penna-core

307 0
@tessera-editor/vue

Tessera Vue 3 binding with the full default UI

235 0
@oh-new-editor/vue

Vue adapter for Oh New Editor

165 0

Version History

Version Released Vue License
1.1.0 ^3.0.0 MIT
1.0.2 ^3.5.13
1.0.1 ^3.5.13
1.0.0 ^3.5.13