Templates
Two kinds — a document template seeds a whole new editor, a block template is a saved snippet insertable anywhere via "/". Both are developer-definable in code and end-user-creatable/persisted (IndexedDB, alongside usePersistedDocument's own storage, but a separate object store — a template isn't tied to any one document).
Block templates — reusable snippets, insertable via "/"
import { EditorStore, captureBlockTemplate, registerBlockTemplates, registerBuiltInBlocks } from 'noteloom';
// Build once (a throwaway store is fine — only its content is captured):
const draftStore = new EditorStore({
rootId: 'root',
blocks: [
{ id: 'root', type: 'page', parentId: null, contentIds: ['h1', 'li1'], props: {} },
{ id: 'h1', type: 'heading', parentId: 'root', contentIds: ['r1'], props: { level: 2 } },
{ id: 'li1', type: 'listItem', parentId: 'root', contentIds: [], props: { ordered: true, titleRunIds: ['r2'] } },
],
runs: [
{ id: 'r1', type: 'text', value: 'Meeting agenda', marks: {} },
{ id: 'r2', type: 'text', value: 'Review previous action items', marks: {} },
],
});
const agendaSnippet = captureBlockTemplate(draftStore, ['h1', 'li1']);
const editor = useEditor({
registerBlocks: (registry) => {
registerBuiltInBlocks(registry);
registerBlockTemplates(registry, [{ id: 'agenda', label: 'Meeting agenda', keywords: ['agenda'], roots: agendaSnippet.roots }]);
},
});Typing "/agenda" now shows "Meeting agenda" in the slash menu, same as any built-in block — registerBlockTemplates registers under the hood exactly the way a real block type does (just one that's never actually rendered — only its captured content, which already has real block types, gets inserted). insertBlockTemplate(store, template, { parentId, index }) does the same insertion directly, if you want a button instead of/alongside "/".
Document templates — starter documents
No new primitives needed — a document template is a document JSON, so useEditor({ doc: someTemplate.doc }) already covers "start a new editor from it." To apply one to an already-mounted editor instead, use applyDocumentTemplate(store, doc) — the same function version history restore uses.
Saving/browsing a library of templates
Either kind, persisted so it survives reload:
import { useEditor, NoteloomEditor, useTemplates, TemplatePicker, saveTemplate, exportDocumentJSON } from 'noteloom';
function NewDocumentScreen({ onPick }) {
const { templates, isLoaded } = useTemplates({ scope: 'document' }); // or 'block', or omit for both
if (!isLoaded) return <p>Loading…</p>;
return <TemplatePicker templates={templates} onSelect={(template) => onPick(template.doc)} />;
}
// Saving the current document as a reusable template:
async function saveCurrentAsTemplate(store, name) {
await saveTemplate({
id: crypto.randomUUID(),
scope: 'document',
name,
doc: JSON.parse(exportDocumentJSON(store)), // exportDocumentJSON returns a JSON string — parse it first
});
}TemplatePicker is deliberately just a plain list (name + description + a "Use" button) — wrap it in the exported Modal component yourself, or render it inline, whichever fits. What onSelect actually does (apply it, insert it, just read .doc) is up to you, since that differs by scope. saveTemplate/loadTemplate/deleteTemplate/listTemplates are the raw storage operations useTemplates is built on, for anywhere the hook's all-in-one behavior doesn't fit.
Importing a template from a file
Since a stored template is already plain JSON, this needs no new format or function — just saveTemplate(JSON.parse(fileText)):
async function handleImport(event) {
const template = JSON.parse(await event.target.files[0].text());
await saveTemplate(template);
}Exporting one for sharing is the mirror image — JSON.stringify(template), downloaded as a .json file — ordinary front-end code, not something the package needs to provide.