Add your own content type
Build a note group by understanding nodes, commands, display and selection step by step.
Suppose your knowledge base needs a note group containing editable paragraphs. Users should select, copy and save it with the document. This is a new document structure, beyond changing a paragraph's color.
We call it docsNote. First define its content and insertion command, then add Canvas/SVG display and selection behavior.
1. Define what it stores
In feature/node.ts, Node.create() defines the structure:
const DocsNote = Node.create({
name: 'docsNote',
group: 'block',
content: 'block+',
isolating: true,
})
name is stored in JSON. group: 'block' lets the group appear alongside paragraphs. content: 'block+' requires at least one block child. isolating makes general editing operations respect the container boundary.
The complete file also maps HTML input and output to aside[data-docs-note], preserving children when copying HTML. This excerpt explains the structure fields; inspect the complete definition at the end of this page.
2. Provide an insertion command
The interface should call “insert note” without constructing JSON itself. feature/extension.ts provides insertDocsNote(text). Its command body is:
insertDocsNote: (text: string) => ({ commands }) =>
commands.insertContent({
type: 'docsNote',
content: [{
type: 'paragraph',
content: [{ type: 'text', text }],
}],
})
Using insertContent() preserves Core transactions and undo. The complete file adds TypeScript declarations and installs the node through addExtensions(). After installing DocsNoteFeature, a button can call:
editor.chain().focus().insertDocsNote('Note content').run()
Use can().insertDocsNote(...) to determine whether that button is enabled.
3. Arrange children in Canvas/SVG
DOM can display HTML elements directly. Canvas/SVG uses QuireKit layout results, so renderHTML() alone is insufficient. The note also needs a layout provider.
feature/layout.ts uses existing engine services to lay out paragraphs within the available width and place them in the group. It does not measure browser DOM or reimplement text layout. addLayoutProviders() registers this behavior.
The group now has a display in both Canvas and SVG. Detailed size, document-range and pagination parameters are in the extension API; start by reusing this ordinary container layout.
4. Explain container boundaries to selection
Dragging inside the group should select ordinary text. Dragging across its boundary also requires a definition of which children belong to the selection.
feature/selection.ts delegates internal selection to text and uses child units when crossing the group. addSelectionAdapters() registers these rules, allowing copy and replacement to use consistent content ranges.
Try it
Click “Insert note group”, type inside it, select across its boundary and undo. Switch to SVG and confirm that content remains. Download JSON to see docsNote and its paragraphs.
Inspect complete files by purpose
| File | When to read it | Responsibility |
|---|---|---|
feature/node.ts | Change attributes or allowed children | Structure and HTML parsing/output |
feature/extension.ts | Install the feature or change insertion | Command types, implementation and registration |
feature/layout.ts | Change container arrangement | Canvas/SVG child sizes and positions |
feature/selection.ts | Change cross-container selection | Internal and boundary selection rules |
Choose one file below. Read the node and extension first; inspect the other two when adjusting layout or selection.
import { Extension } from "@quirekit/core";
import { DocsNote } from "./node";
import { noteLayout } from "./layout";
import { noteSelection } from "./selection";
declare module "@quirekit/core" {
/** The same typed command registry used by direct, chain and can modes. */
interface Commands<ReturnType> {
/** Feature-owned commands. */
docsNote: {
/** @param text - Paragraph text inside a new note. @returns Whether insertion can run or was handled. */
insertDocsNote: (text: string) => ReturnType;
};
}
}
/** One installable feature owns schema, command, retained layout and structured selection. */
export const DocsNoteFeature = Extension.create({
name: "docsNoteFeature",
/** @returns Schema owner; no second document is maintained. */
addExtensions() { return [DocsNote]; },
/** @returns Typed raw command factories, also safe in can() dry runs. */
addCommands() {
return {
insertDocsNote: (text: string) => ({ commands }) => commands.insertContent({ type: "docsNote",
content: [{ type: "paragraph", content: [{ type: "text", text }] }] }),
};
},
/** @returns Renderer-neutral geometry owner. */
addLayoutProviders() { return [noteLayout]; },
/** @returns Structured crossing semantics, independently of hit testing. */
addSelectionAdapters() { return [noteSelection]; },
});
Saving and other formats
The node is saved with document JSON; install the extension again on restore. This example implements neither DOCX conversion nor AI integration. Add the respective rules through addDocx() and addAiCapabilities() when needed. JSON persistence alone does not provide another format's support.
For a toolbar or sidebar rather than new content, continue with product plugins.