Blocknote Headless

Last modified by Manuel Leduc on 2025/07/11 15:08

This module provides a Vue component that renders a BlockNote editor with Cristal-specific features, such as the handling of internal links, attachments, macros, etc.

User

See Blocknote.

Admin

See Blocknote.

Developer

Concept and limitations

The rendered portion of BlockNote is in React, and a wrapper is designed around it to make its integration seamless in Vue. In other words, no React integration is required on the developer's part ; simply using the component 'as-is' will work. No configuration is required in the bundler (Vite, Rollup, ...) and React does not need to be imported.

Some limitations of that approach are:

  • A React app is created for each instance of the BlockNote editor
  • Communication with the editor is handled using events, as React and Vue events aren't compatible
  • React versions need to be aligned ; if an application is using a given version and the BlockNote Headless module is using another, some problems may arise

Explanations

CBlockNoteView

type Props = {
  editorProps: Omit<
    BlockNoteViewWrapperProps,
    "content" | "linkEditionCtx"
  >;
  editorContent: UniAst | Error;
  collaborationProvider?: () => CollaborationInitializer;
  container: Container;
}

type BlockNoteViewWrapperProps = {
  /**
   * Options to forward to the BlockNote editor
   */
  blockNoteOptions?: Partial<
    Omit<DefaultEditorOptionsType, "schema" | "collaboration">
  >;

  /**
   * The display theme to use
   */
  theme?: "light" | "dark";

  /**
   * The editor's language
   */
  lang: EditorLanguage;

  /**
   * The editor's initial content
   * If realtime is enabled, this content may be replaced by the other users' own editor content
   */
  content: BlockType[];

  /**
   * Realtime options
   */
  realtime?: {
    hocusPocus: HocuspocusProviderConfiguration;
    user: { name: string; color: string };
  };

  /**
   * Run a function when the document's content change
   * WARN: this function may be fired at a rapid rate if the user types rapidly. Debouncing may be required on your end.
   */
  onChange?: (editor: EditorType) => void;

  /**
   * Link edition utilities
   */
  linkEditionCtx: LinkEditionContext;

  /**
   * Make the wrapper forward some data through references
   */
  refs?: {
    setEditor?: (editor: EditorType) => void;
    setProvider?: (provider: HocuspocusProvider) => void;
  };
};

Vue Events

{
  // Emitted as soon as a user-triggered change happens into the editor
  // The event won't be triggered when the editor is filled with its initial content,
  // or when the editor's content changes due to modifications made by other players in the realtime session
  "instant-change": [];

  // Emitted in the same context as "instant-change", but debounced
  "debounced-change": [content: UniAst];

  // Emitted when the realtime provider is set up in the editor
  "setup-provider": [provider: HocuspocusProvider];
}

Vue Expose

{
  // Get the editor's content
  getContent(): UniAst | Error
}

Example

Here we are importing the CBlockNoteView component from blocknote-headless and using it inside our own.

This will render a complete BlockNote editor, along with the customizations that are proper to Cristal (handling of internal links, custom blocks, etc).

Many properties are available to customize the editor further, adding realtime capabilities or being notified when the editor's content changes.

<script setup lang="ts">
import { collaborationManagerProviderName } from "@xwiki/cristal-collaboration-api";
import type {
  CollaborationInitializer,
  CollaborationManagerProvider,
  User,
} from "@xwiki/cristal-collaboration-api";
const collaborationManager = container
    .get<CollaborationManagerProvider>(collaborationManagerProviderName)
    .get();
const collaborationProvider = await collaborationManager.get();
</script>
<template>
<CBlockNoteView
  :editor-props="{
    lang: 'en',
    // Optional
    blockNoteOptions: {
      // ...
    },
    // Optional
    onChange(editor) {
      // Get the entire document from the editor
      console.log(editor.document)
    },
    // Optional
    theme: 'dark'
  }"
  // Optional
  :collaboration-provider="collaborationProvider"
  @setup-provider="
    (provider) =>
      console.log('The realtime provider was set up', provider)
  "
  :editor-content="{
    blocks: [
      {
        type: 'paragraph',
        content: [
          {
            type: 'text',
            content: 'Hello world!',
            styles: { bold: true },
          },
        ],
        styles: {
          textColor: '#123456',
        },
      },
    ],
  }"
  @instant-change="
    console.log('Something changed inside the editor!')
  "
  @debounced-change="
    console.log('Something changed inside the editor (only triggered once a few delay passed after the user stopped typing)')
  "
/>
</template>
France 2030 Logo

This project is being financed by the French State as part of the France 2030 program

Ce projet est financé par l’État Français dans le cadre de France 2030

  • Powered by XWiki 16.10.6-node2. Hosted and managed by XWiki SAS

Get Connected