VVibeFlui

Editors Plugin

@vibeflui/editors provides rich text, code, markdown, and Editor.js field adapters.

Install

Install the package and the peer editor libraries used by the adapters you enable.

BASH
npm install @vibeflui/core @vibeflui/editors

For rich text:

BASH
npm install @tiptap/react @tiptap/starter-kit @tiptap/pm

For code:

BASH
npm install @uiw/react-codemirror

For markdown:

BASH
npm install @uiw/react-md-editor

For Editor.js:

BASH
npm install @editorjs/editorjs

Public exports

ExportPurpose
richtextPluginRegisters the richtext field adapter.
codePluginRegisters the code field adapter.
markdownPluginRegisters the markdown field adapter.
editorJsPluginRegisters the editorjs field adapter. Accepts { tools } for Editor.js tool packages (Header, List, etc.).
createEditorsPluginRegisters richtext, code, markdown, and editorjs together.
editorsPluginPrebuilt plugin from createEditorsPlugin().
RichTextFieldTipTap field adapter component.
CodeFieldCodeMirror field adapter component.
MarkdownFieldreact-md-editor field adapter component.
EditorJsFieldEditor.js field adapter component.
createEditorJsFieldFactory returning an EditorJsField with specific tools baked in.
EditorJsEditorStandalone Editor.js component — usable outside <FluiKit>/<FluiKitForm> as a plain controlled component.
buildEditorJsToolsWires app-imported @editorjs/* tool classes (Header, List, Image, ...) into a ready-to-use tools config, applying sensible per-tool defaults and image/link endpoint wiring.
applyEditorJsFieldAdapterPropsApplies a field's adapterProps (tool allowlist, endpoint overrides) on top of an already-built tools object. Used internally by EditorJsField.
EDITORJS_DEFAULT_TOOL_NAMESThe list of tool names buildEditorJsTools understands (header, list, checklist, quote, warning, delimiter, table, code, embed, image, linkTool, raw, marker, inlineCode, underline).

Registered slots

PluginField adapter keys
richtextPlugin()richtext
codePlugin()code
markdownPlugin()markdown
editorJsPlugin()editorjs
createEditorsPlugin()richtext, code, markdown, editorjs

Minimal setup

Prefer per-editor plugins when the app only needs one editor.

TSX
import { FluiKit, FluiKitProvider } from "@vibeflui/core";
import { markdownPlugin } from "@vibeflui/editors";

const editorPlugin = markdownPlugin();

export function ArticlePage() {
  return (
    <FluiKitProvider plugins={[editorPlugin]}>
      <FluiKit schema={articleSchema} />
    </FluiKitProvider>
  );
}

Schema usage

TS
export const articleSchema = {
  resource: "articles",
  form: {
    fields: [
      { name: "body", label: "Body", type: "richtext", adapter: "richtext" },
      { name: "config", label: "Config", type: "code", adapter: "code" },
      { name: "notes", label: "Notes", type: "markdown", adapter: "markdown" },
      { name: "content", label: "Content", type: "json", adapter: "editorjs" }
    ]
  },
  registry: {
    fieldAdapters: ["richtext", "code", "markdown", "editorjs"]
  }
} as const;

Editor.js

Editor.js is a block editor. Its field value is Editor.js's own OutputData shape ({ time, blocks, version }), not a string — that is why it pairs with type: "json" instead of a dedicated field type, matching the json field type's existing "textarea unless an adapter overrides it" contract.

TSX
import { FluiKitProvider } from "@vibeflui/core";
import { editorJsPlugin } from "@vibeflui/editors";

<FluiKitProvider plugins={[editorJsPlugin()]}>
  <FluiKit schema={articleSchema} />
</FluiKitProvider>

With no tools configured, this only enables Editor.js's built-in tools: paragraph blocks plus inline Bold/Italic/Link. Every other block — Header, List, Checklist, Quote, Warning, Table, Code, Embed, Image, Link preview, Raw HTML — and the Marker/Inline Code/Underline inline tools are each a separate @editorjs/* npm package that must be installed and wired explicitly.

Enabling the full tool set

@vibeflui/editors never imports these tool packages itself — install the ones you want, import their classes in your own registry file, and wire them with buildEditorJsTools:

BASH
npm install @editorjs/header @editorjs/list @editorjs/checklist @editorjs/quote \
  @editorjs/warning @editorjs/delimiter @editorjs/table @editorjs/code \
  @editorjs/embed @editorjs/image @editorjs/link @editorjs/raw \
  @editorjs/marker @editorjs/inline-code @editorjs/underline
TSX
import Header from "@editorjs/header";
import EditorjsList from "@editorjs/list";
import Checklist from "@editorjs/checklist";
import Quote from "@editorjs/quote";
import Warning from "@editorjs/warning";
import Delimiter from "@editorjs/delimiter";
import EditorjsTable from "@editorjs/table";
import CodeTool from "@editorjs/code";
import Embed from "@editorjs/embed";
import ImageTool from "@editorjs/image";
import LinkTool from "@editorjs/link";
import RawTool from "@editorjs/raw";
import Marker from "@editorjs/marker";
import InlineCode from "@editorjs/inline-code";
import Underline from "@editorjs/underline";
import { buildEditorJsTools, editorJsPlugin } from "@vibeflui/editors";

const editorJsTools = buildEditorJsTools(
  {
    header: Header,
    list: EditorjsList,
    checklist: Checklist,
    quote: Quote,
    warning: Warning,
    delimiter: Delimiter,
    table: EditorjsTable,
    code: CodeTool,
    embed: Embed,
    image: ImageTool,
    linkTool: LinkTool,
    raw: RawTool,
    marker: Marker,
    inlineCode: InlineCode,
    underline: Underline
  },
  {
    image: { endpointByFile: "/api/editorjs/upload-image" },
    link: { endpoint: "/api/editorjs/link-preview" }
  }
);

const plugin = editorJsPlugin({ tools: editorJsTools });

buildEditorJsTools only registers a tool whose class was actually passed in — leave one out to exclude it. It applies sensible per-tool defaults (inline toolbar where it fits, Header's levels, Embed's common service list) so callers don't need to know each tool's raw config shape. Tool classes still cannot live in a JSON-friendly schema, so this wiring happens at plugin registration.

Narrowing tools per schema field

field.adapterProps is JSON-friendly (plain strings/URLs, no classes), so it can safely narrow the enabled tool set or override the image/link endpoint for one specific field, without a second plugin registration:

TS
{
  name: "body",
  label: "Body",
  type: "json",
  adapter: "editorjs",
  adapterProps: {
    tools: ["header", "list", "quote", "linkTool"]
  }
}

The Image and Link preview tools call a server endpoint — VibeFlui only owns the FE/UI layer and does not provide one, but the request/response contract is fixed by Editor.js:

  • Image (endpointByFile): receives a multipart/form-data upload under the field name ("file" by default). Must respond { "success": 1, "file": { "url": "https://..." } }.
  • Link preview (endpoint): receives a GET request with the target URL as ?url=. Must respond { "success": 1, "link": "https://...", "meta": { "title": "...", "description": "...", "image": { "url": "https://..." } } } — the server fetches the URL and parses its Open Graph tags to build meta.

buildEditorJsTools skips registering image/linkTool entirely when no endpoint is configured, instead of shipping a tool that would fail on first use.

Using Editor.js outside a form

EditorJsEditor is a plain controlled component. It does not need <FluiKit>, <FluiKitForm>, or a registry — use it directly anywhere in a React tree:

TSX
"use client";

import { useState } from "react";
import { EditorJsEditor, type EditorJsOutputData } from "@vibeflui/editors";
import { editorJsTools } from "@/lib/vibeflui/editorjs-tools";

export function ArticleBodyEditor() {
  const [value, setValue] = useState<EditorJsOutputData>();

  return <EditorJsEditor value={value} onChange={setValue} tools={editorJsTools} />;
}

Omit tools and the standalone editor also falls back to paragraph plus inline Bold/Italic/Link only.

Markdown stylesheet

The markdown adapter source notes that the editor stylesheet should be imported once.

TS
import "@uiw/react-md-editor/markdown-editor.css";

Common mistakes

  • Use type: "richtext" and adapter: "richtext" for the current rich text adapter API.
  • Do not use createEditorsPlugin() unless all peer editor libraries are installed.
  • Do not try to pass Editor.js tool classes through the schema — schemas must stay JSON-friendly; import @editorjs/* packages and build tools with buildEditorJsTools at plugin registration instead. field.adapterProps.tools (a string-name allowlist) and its .image/.link endpoint overrides are schema-safe.
  • type: "json" alone renders a plain textarea; adapter: "editorjs" is what actually swaps in the Editor.js block editor.
  • Do not assume Header/List/Image/etc. render with zero setup — without tools, Editor.js only renders paragraph blocks plus inline Bold/Italic/Link.