[{"data":1,"prerenderedAt":1376},["ShallowReactive",2],{"navigation_docs":3,"-tutorial-react-surface":412,"-tutorial-react-surface-surround":1371},[4,38,84,326,386],{"title":5,"icon":6,"path":7,"stem":8,"children":9,"page":37},"Getting started","i-lucide-rocket","\u002Fgetting-started","1.getting-started",[10,15,20,24,28,32],{"title":11,"path":12,"stem":13,"icon":14},"Introduction","\u002Fgetting-started\u002Fintroduction","1.getting-started\u002F1.introduction","i-lucide-book-open",{"title":16,"path":17,"stem":18,"icon":19},"Installation","\u002Fgetting-started\u002Finstallation","1.getting-started\u002F2.installation","i-lucide-download",{"title":21,"path":22,"stem":23,"icon":6},"Quick start: Nuxt","\u002Fgetting-started\u002Fquick-start-nuxt","1.getting-started\u002F3.quick-start-nuxt",{"title":25,"path":26,"stem":27,"icon":6},"Quick start: plain Vue","\u002Fgetting-started\u002Fquick-start-vue","1.getting-started\u002F4.quick-start-vue",{"title":29,"path":30,"stem":31,"icon":6},"Quick start: React","\u002Fgetting-started\u002Fquick-start-react","1.getting-started\u002F5.quick-start-react",{"title":33,"path":34,"stem":35,"icon":36},"Glossary","\u002Fgetting-started\u002Fglossary","1.getting-started\u002F6.glossary","i-lucide-book-a",false,{"title":39,"icon":40,"path":41,"stem":42,"children":43,"page":37},"Tutorial","i-lucide-graduation-cap","\u002Ftutorial","2.tutorial",[44,49,54,59,64,69,74,79],{"title":45,"path":46,"stem":47,"icon":48},"Tutorial: overview","\u002Ftutorial\u002Foverview","2.tutorial\u002F1.overview","i-lucide-map",{"title":50,"path":51,"stem":52,"icon":53},"Chapter 2: A simple editor","\u002Ftutorial\u002Fsimple-editor","2.tutorial\u002F2.simple-editor","i-lucide-pencil",{"title":55,"path":56,"stem":57,"icon":58},"Chapter 3: Compose features","\u002Ftutorial\u002Fcompose-features","2.tutorial\u002F3.compose-features","i-lucide-blocks",{"title":60,"path":61,"stem":62,"icon":63},"Chapter 4: Your own block, path A","\u002Ftutorial\u002Fown-block-path-a","2.tutorial\u002F4.own-block-path-a","i-lucide-square-plus",{"title":65,"path":66,"stem":67,"icon":68},"Chapter 5: Your own feature, path B","\u002Ftutorial\u002Fown-feature-path-b","2.tutorial\u002F5.own-feature-path-b","i-lucide-puzzle",{"title":70,"path":71,"stem":72,"icon":73},"Chapter 6: Backend and collaboration","\u002Ftutorial\u002Fbackend-and-collaboration","2.tutorial\u002F6.backend-and-collaboration","i-lucide-users",{"title":75,"path":76,"stem":77,"icon":78},"Chapter 7: AI","\u002Ftutorial\u002Fai","2.tutorial\u002F7.ai","i-lucide-sparkles",{"title":80,"path":81,"stem":82,"icon":83},"Chapter 8: The React surface","\u002Ftutorial\u002Freact-surface","2.tutorial\u002F8.react-surface","i-lucide-atom",{"title":85,"icon":14,"path":86,"stem":87,"children":88,"page":37},"Guides","\u002Fguides","3.guides",[89,93,98,103,108,112,116,121,126,130,134,139,144,149,154,159,164,169,174,179,184,189,194,199,204,209,213,218,223,228,233,237,242,247,251,256,261,266,271,276,281,286,291,295,302,307,311,316,321],{"title":90,"path":91,"stem":92,"icon":58},"Feature composition and entries","\u002Fguides\u002Fcomposition","3.guides\u002F1.composition",{"title":94,"path":95,"stem":96,"icon":97},"Media, mentions and transcript features","\u002Fguides\u002Fmedia-mentions-transcript","3.guides\u002F10.media-mentions-transcript","i-lucide-image",{"title":99,"path":100,"stem":101,"icon":102},"Authors and comments","\u002Fguides\u002Fauthors-and-comments","3.guides\u002F11.authors-and-comments","i-lucide-message-square-text",{"title":104,"path":105,"stem":106,"icon":107},"Themes (Obsidian)","\u002Fguides\u002Fthemes","3.guides\u002F12.themes","i-lucide-palette",{"title":109,"path":110,"stem":111,"icon":58},"Composites and the settings UI","\u002Fguides\u002Fcomposites","3.guides\u002F13.composites",{"title":113,"path":114,"stem":115,"icon":78},"AI","\u002Fguides\u002Fai","3.guides\u002F14.ai",{"title":117,"path":118,"stem":119,"icon":120},"Document tree","\u002Fguides\u002Fdocument-tree","3.guides\u002F15.document-tree","i-lucide-folder-tree",{"title":122,"path":123,"stem":124,"icon":125},"Markdown and code","\u002Fguides\u002Fmarkdown-and-code","3.guides\u002F16.markdown-and-code","i-lucide-file-code",{"title":127,"path":128,"stem":129,"icon":83},"Advanced editor in React","\u002Fguides\u002Fadvanced-react","3.guides\u002F17.advanced-react",{"title":131,"path":132,"stem":133,"icon":6},"Kitchen sink: learning path","\u002Fguides\u002Fadvanced-nuxt-example","3.guides\u002F18.advanced-nuxt-example",{"title":135,"path":136,"stem":137,"icon":138},"Production","\u002Fguides\u002Fproduction","3.guides\u002F19.production","i-lucide-server",{"title":140,"path":141,"stem":142,"icon":143},"Register the Nuxt module","\u002Fguides\u002Fnuxt-module","3.guides\u002F2.nuxt-module","i-lucide-package",{"title":145,"path":146,"stem":147,"icon":148},"Storage adapters, comment stores, Nitro host","\u002Fguides\u002Fstorage-adapters","3.guides\u002F20.storage-adapters","i-lucide-database",{"title":150,"path":151,"stem":152,"icon":153},"Migrating from #tiptap-editor","\u002Fguides\u002Fmigration","3.guides\u002F21.migration","i-lucide-arrow-right-left",{"title":155,"path":156,"stem":157,"icon":158},"Content tree mechanics","\u002Fguides\u002Fcontent-tree","3.guides\u002F22.content-tree","i-lucide-git-fork",{"title":160,"path":161,"stem":162,"icon":163},"Block capabilities, abilities and actions","\u002Fguides\u002Fblock-capabilities","3.guides\u002F23.block-capabilities","i-lucide-shield-check",{"title":165,"path":166,"stem":167,"icon":168},"Block types in code and JSON","\u002Fguides\u002Fblock-types","3.guides\u002F24.block-types","i-lucide-shapes",{"title":170,"path":171,"stem":172,"icon":173},"Derive methods and the Derivation index","\u002Fguides\u002Fderive-methods","3.guides\u002F25.derive-methods","i-lucide-git-branch-plus",{"title":175,"path":176,"stem":177,"icon":178},"Block properties and Property definitions","\u002Fguides\u002Fblock-properties","3.guides\u002F26.block-properties","i-lucide-sliders-horizontal",{"title":180,"path":181,"stem":182,"icon":183},"Image studio","\u002Fguides\u002Fimage-studio","3.guides\u002F27.image-studio","i-lucide-image-plus",{"title":185,"path":186,"stem":187,"icon":188},"Prüfung and Freigabestatus","\u002Fguides\u002Fpruefung","3.guides\u002F28.pruefung","i-lucide-stamp",{"title":190,"path":191,"stem":192,"icon":193},"Workspace: find, read, reference and embed Blocks across Documents","\u002Fguides\u002Fworkspace","3.guides\u002F29.workspace","i-lucide-network",{"title":195,"path":196,"stem":197,"icon":198},"EditorPorts: auth and storage","\u002Fguides\u002Feditor-ports","3.guides\u002F3.editor-ports","i-lucide-plug",{"title":200,"path":201,"stem":202,"icon":203},"Collections: an ordered list of Blocks","\u002Fguides\u002Fcollections","3.guides\u002F30.collections","i-lucide-list-checks",{"title":205,"path":206,"stem":207,"icon":208},"Data views: a query shown as table, list or board","\u002Fguides\u002Fdata-views","3.guides\u002F31.data-views","i-lucide-table-properties",{"title":210,"path":211,"stem":212,"icon":193},"Explore: backlinks, related Blocks and the trail","\u002Fguides\u002Fexplore","3.guides\u002F32.explore",{"title":214,"path":215,"stem":216,"icon":217},"Render: export a Block or a Document to a file","\u002Fguides\u002Frender","3.guides\u002F33.render","i-lucide-file-output",{"title":219,"path":220,"stem":221,"icon":222},"Importers: what a dropped file becomes","\u002Fguides\u002Fimporters","3.guides\u002F34.importers","i-lucide-file-input",{"title":224,"path":225,"stem":226,"icon":227},"Templates: start a Document from a Dokumentvorlage","\u002Fguides\u002Ftemplates","3.guides\u002F35.templates","i-lucide-file-stack",{"title":229,"path":230,"stem":231,"icon":232},"Citations","\u002Fguides\u002Fcitations","3.guides\u002F36.citations","i-lucide-quote",{"title":234,"path":235,"stem":236,"icon":163},"AI proposals and the export check","\u002Fguides\u002Fexport-check","3.guides\u002F37.export-check",{"title":238,"path":239,"stem":240,"icon":241},"Query: filter, sort, group and total Blocks","\u002Fguides\u002Fquery","3.guides\u002F38.query","i-lucide-list-filter",{"title":243,"path":244,"stem":245,"icon":246},"Edges: backlinks and authored connections","\u002Fguides\u002Fedges","3.guides\u002F39.edges","i-lucide-link-2",{"title":248,"path":249,"stem":250,"icon":163},"Server routes and authentication","\u002Fguides\u002Froutes-and-auth","3.guides\u002F4.routes-and-auth",{"title":252,"path":253,"stem":254,"icon":255},"Speech: text to audio, and subtitle files","\u002Fguides\u002Fspeech","3.guides\u002F41.speech","i-lucide-audio-lines",{"title":257,"path":258,"stem":259,"icon":260},"Graph: the Edges around a Block","\u002Fguides\u002Fgraph","3.guides\u002F42.graph","i-lucide-waypoints",{"title":262,"path":263,"stem":264,"icon":265},"PDF viewer: passages, anchors and quotes","\u002Fguides\u002Fpdf-viewer","3.guides\u002F43.pdf-viewer","i-lucide-file-search",{"title":267,"path":268,"stem":269,"icon":270},"Obsidian: import and export a vault","\u002Fguides\u002Fobsidian","3.guides\u002F44.obsidian","i-lucide-folder-input",{"title":272,"path":273,"stem":274,"icon":275},"Typst: render a Document as a PDF with citations","\u002Fguides\u002Ftypst","3.guides\u002F45.typst","i-lucide-file-text",{"title":277,"path":278,"stem":279,"icon":280},"Audio: read aloud, mixdown and ElevenLabs","\u002Fguides\u002Faudio","3.guides\u002F46.audio","i-lucide-audio-waveform",{"title":282,"path":283,"stem":284,"icon":285},"Slides: decks, slide layouts and exports","\u002Fguides\u002Fslides","3.guides\u002F47.slides","i-lucide-presentation",{"title":287,"path":288,"stem":289,"icon":290},"Knowledge graph: sources and concepts beside the Documents","\u002Fguides\u002Fknowledge-graph","3.guides\u002F49.knowledge-graph","i-lucide-share-2",{"title":292,"path":293,"stem":294,"icon":78},"AI configuration and secrets","\u002Fguides\u002Fai-configuration","3.guides\u002F5.ai-configuration",{"title":296,"path":297,"stem":298,"children":299,"icon":301},"Index on Postgres: typos, meaning and SQL pushdown","\u002Fguides\u002Findex-pg","3.guides\u002F50.index-pg",[300],{"title":296,"path":297,"stem":298,"icon":301},"i-lucide-database-zap",{"title":303,"path":304,"stem":305,"icon":306},"Charts: bar, line, pie and scatter over a query","\u002Fguides\u002Fcharts","3.guides\u002F51.charts","i-lucide-chart-column",{"title":308,"path":309,"stem":310,"icon":73},"Collaboration on the client","\u002Fguides\u002Fcollaboration","3.guides\u002F6.collaboration",{"title":312,"path":313,"stem":314,"icon":315},"Block UI (drag handle, hover actions)","\u002Fguides\u002Fblock-ui","3.guides\u002F7.block-ui","i-lucide-grip-vertical",{"title":317,"path":318,"stem":319,"icon":320},"Settings and app lifetime","\u002Fguides\u002Fsettings","3.guides\u002F8.settings","i-lucide-settings",{"title":322,"path":323,"stem":324,"icon":325},"Language","\u002Fguides\u002Flanguage","3.guides\u002F9.language","i-lucide-languages",{"title":327,"icon":14,"path":328,"stem":329,"children":330,"page":37},"Reference","\u002Freference","4.reference",[331,335,339,344,348,352,356,360,364,368,373,378,382],{"title":332,"path":333,"stem":334,"icon":143},"Public exports","\u002Freference\u002Fexports","4.reference\u002F1.exports",{"title":336,"path":337,"stem":338,"icon":173},"Derive method contract","\u002Freference\u002Fderive-methods","4.reference\u002F10.derive-methods",{"title":340,"path":341,"stem":342,"icon":343},"Changelog","\u002Freference\u002Fchangelog","4.reference\u002F11.changelog","i-lucide-history",{"title":345,"path":346,"stem":347,"icon":217},"Renderer port","\u002Freference\u002Frenderer-port","4.reference\u002F12.renderer-port",{"title":349,"path":350,"stem":351,"icon":255},"Speech port","\u002Freference\u002Fspeech-port","4.reference\u002F13.speech-port",{"title":353,"path":354,"stem":355,"icon":58},"Presets and features","\u002Freference\u002Fpresets-and-features","4.reference\u002F2.presets-and-features",{"title":357,"path":358,"stem":359,"icon":68},"Feature contract","\u002Freference\u002Ffeature-contract","4.reference\u002F3.feature-contract",{"title":361,"path":362,"stem":363,"icon":107},"CSS and UI-provider requirements","\u002Freference\u002Fcss-and-ui","4.reference\u002F4.css-and-ui",{"title":365,"path":366,"stem":367,"icon":148},"Vue-free advanced schema","\u002Freference\u002Fadvanced-schema","4.reference\u002F5.advanced-schema",{"title":369,"path":370,"stem":371,"icon":372},"Dependency rules","\u002Freference\u002Fdependency-rules","4.reference\u002F6.dependency-rules","i-lucide-git-branch",{"title":374,"path":375,"stem":376,"icon":377},"Commands","\u002Freference\u002Fcommands","4.reference\u002F7.commands","i-lucide-terminal",{"title":379,"path":380,"stem":381,"icon":163},"Block capability contract","\u002Freference\u002Fblock-capabilities","4.reference\u002F8.block-capabilities",{"title":383,"path":384,"stem":385,"icon":168},"Block type contract","\u002Freference\u002Fblock-types","4.reference\u002F9.block-types",{"title":387,"path":388,"stem":389,"children":390,"icon":393},"AI agents","\u002Fai-agents","5.ai-agents\u002F1.index",[391,394,398,402,407],{"title":392,"path":388,"stem":389,"icon":393},"Overview","i-lucide-bot",{"title":395,"path":396,"stem":397,"icon":198},"Integrate the editor","\u002Fai-agents\u002Fintegrate","5.ai-agents\u002F2.integrate",{"title":399,"path":400,"stem":401,"icon":68},"Add a block or feature","\u002Fai-agents\u002Fextend","5.ai-agents\u002F3.extend",{"title":403,"path":404,"stem":405,"icon":406},"Pitfalls and invariants","\u002Fai-agents\u002Finvariants","5.ai-agents\u002F4.invariants","i-lucide-shield-alert",{"title":408,"path":409,"stem":410,"icon":411},"Verify","\u002Fai-agents\u002Fverify","5.ai-agents\u002F5.verify","i-lucide-check-check",{"id":413,"title":80,"body":414,"description":1364,"extension":1365,"links":1366,"meta":1367,"navigation":1368,"path":81,"seo":1369,"stem":82,"__hash__":1370},"docs\u002F2.tutorial\u002F8.react-surface.md",{"type":415,"value":416,"toc":1352},"minimark",[417,442,456,460,478,486,531,734,758,766,779,825,864,883,938,1095,1102,1129,1202,1206,1234,1238,1241,1311,1315,1324,1328,1348],[418,419,420,421,425,426,429,430,433,434,437,438,441],"p",{},"Everything in chapters 2 to 7 has a React twin. The React layer (",[422,423,424],"code",{},"@skaile\u002Fforge-editor\u002Freact",", shadcn\u002Fui on Radix and Tailwind 4) renders the same framework-free core as the Vue layer: the simple editor, ",[422,427,428],{},"EditorToolbar",", the settings panel and modal, and the ",[422,431,432],{},"@",", ",[422,435,436],{},"#"," and ",[422,439,440],{},"\u002F"," suggestion popups. It uses no Vue, Nuxt UI or Nuxt module.",[418,443,444,445,448,449,452,453,455],{},"The runnable reference is ",[422,446,447],{},"examples\u002Freact-simple"," (port 3041); it is built into this page. It has the same sample picker, read-only switch, host ",[422,450,451],{},"\u002Fdatum"," command and JSON and Markdown panes as the Vue example from chapter 2, plus ",[422,454,432],{}," mentions:",[457,458],"demo-frame",{"name":459},"react-simple",[418,461,462,463,466,467,470,471,437,474,477],{},"Start it yourself with ",[422,464,465],{},"bun run --cwd examples\u002Freact-simple dev",". ",[468,469,29],"a",{"href":30}," lists the dependencies, ",[422,472,473],{},"vite.config.ts",[422,475,476],{},"index.css"," an app needs; this chapter shows how the pieces map to what you already know.",[479,480,482,483],"h2",{"id":481},"the-context-editorcontextprovider","The context: ",[422,484,485],{},"EditorContextProvider",[418,487,488,489,492,493,497,498,501,502,505,506,433,509,433,512,515,516,518,519,522,523,526,527,530],{},"Vue has ",[422,490,491],{},"app.use(createEditor())",". React has a context object that ",[494,495,496],"strong",{},"you own",": ",[422,499,500],{},"createEditorContext()"," from the framework-free ",[422,503,504],{},"@skaile\u002Fforge-editor\u002Fcore"," takes the same options as the Vue plugin (",[422,507,508],{},"backend",[422,510,511],{},"locale",[422,513,514],{},"ai",", …), and ",[422,517,485],{}," hands it to the tree. The provider never disposes the context; call ",[422,520,521],{},"context.dispose()"," yourself when the context outlives your app. The toolbar and menus render ",[422,524,525],{},"i-lucide-*"," names through ",[422,528,529],{},"@iconify\u002Freact",", so the example preloads the icon collection and the page makes no request to the Iconify API:",[532,533,539],"pre",{"className":534,"code":535,"filename":536,"language":537,"meta":538,"style":538},"language-tsx shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","import lucide from '@iconify-json\u002Flucide\u002Ficons.json'\nimport { addCollection } from '@iconify\u002Freact'\nimport { createMemoryBackend } from '@skaile\u002Fforge-editor\u002Fadapters\u002Fmemory'\nimport { createEditorContext } from '@skaile\u002Fforge-editor\u002Fcore'\nimport { EditorContextProvider } from '@skaile\u002Fforge-editor\u002Freact'\nimport { StrictMode } from 'react'\nimport { createRoot } from 'react-dom\u002Fclient'\nimport { App } from '.\u002FApp'\nimport '.\u002Findex.css'\n\n\u002F\u002F Preload the icon set: the page makes no request to the Iconify API.\naddCollection(lucide)\n\n\u002F\u002F The host owns the context; the provider never disposes it. No data layer: documents, dropped files (as blob: URLs),\n\u002F\u002F mention users and tags, and settings live in the page.\nconst context = createEditorContext({\n  backend: createMemoryBackend({\n    users: [\n      { id: 'u1', username: 'Ada Lovelace', color: '#7c3aed' },\n      { id: 'u2', username: 'Alan Turing', color: '#0ea5e9' },\n    ],\n    tags: ['idee', 'entwurf', 'wichtig'],\n  }),\n})\n\ncreateRoot(document.getElementById('app')!).render(\n  \u003CStrictMode>\n    \u003CEditorContextProvider context={context}>\n      \u003CApp \u002F>\n    \u003C\u002FEditorContextProvider>\n  \u003C\u002FStrictMode>,\n)\n","examples\u002Freact-simple\u002Fsrc\u002Fmain.tsx","tsx","",[422,540,541,549,555,561,567,573,579,585,591,597,604,610,616,621,627,633,639,645,651,657,663,669,675,681,687,692,698,704,710,716,722,728],{"__ignoreMap":538},[542,543,546],"span",{"class":544,"line":545},"line",1,[542,547,548],{},"import lucide from '@iconify-json\u002Flucide\u002Ficons.json'\n",[542,550,552],{"class":544,"line":551},2,[542,553,554],{},"import { addCollection } from '@iconify\u002Freact'\n",[542,556,558],{"class":544,"line":557},3,[542,559,560],{},"import { createMemoryBackend } from '@skaile\u002Fforge-editor\u002Fadapters\u002Fmemory'\n",[542,562,564],{"class":544,"line":563},4,[542,565,566],{},"import { createEditorContext } from '@skaile\u002Fforge-editor\u002Fcore'\n",[542,568,570],{"class":544,"line":569},5,[542,571,572],{},"import { EditorContextProvider } from '@skaile\u002Fforge-editor\u002Freact'\n",[542,574,576],{"class":544,"line":575},6,[542,577,578],{},"import { StrictMode } from 'react'\n",[542,580,582],{"class":544,"line":581},7,[542,583,584],{},"import { createRoot } from 'react-dom\u002Fclient'\n",[542,586,588],{"class":544,"line":587},8,[542,589,590],{},"import { App } from '.\u002FApp'\n",[542,592,594],{"class":544,"line":593},9,[542,595,596],{},"import '.\u002Findex.css'\n",[542,598,600],{"class":544,"line":599},10,[542,601,603],{"emptyLinePlaceholder":602},true,"\n",[542,605,607],{"class":544,"line":606},11,[542,608,609],{},"\u002F\u002F Preload the icon set: the page makes no request to the Iconify API.\n",[542,611,613],{"class":544,"line":612},12,[542,614,615],{},"addCollection(lucide)\n",[542,617,619],{"class":544,"line":618},13,[542,620,603],{"emptyLinePlaceholder":602},[542,622,624],{"class":544,"line":623},14,[542,625,626],{},"\u002F\u002F The host owns the context; the provider never disposes it. No data layer: documents, dropped files (as blob: URLs),\n",[542,628,630],{"class":544,"line":629},15,[542,631,632],{},"\u002F\u002F mention users and tags, and settings live in the page.\n",[542,634,636],{"class":544,"line":635},16,[542,637,638],{},"const context = createEditorContext({\n",[542,640,642],{"class":544,"line":641},17,[542,643,644],{},"  backend: createMemoryBackend({\n",[542,646,648],{"class":544,"line":647},18,[542,649,650],{},"    users: [\n",[542,652,654],{"class":544,"line":653},19,[542,655,656],{},"      { id: 'u1', username: 'Ada Lovelace', color: '#7c3aed' },\n",[542,658,660],{"class":544,"line":659},20,[542,661,662],{},"      { id: 'u2', username: 'Alan Turing', color: '#0ea5e9' },\n",[542,664,666],{"class":544,"line":665},21,[542,667,668],{},"    ],\n",[542,670,672],{"class":544,"line":671},22,[542,673,674],{},"    tags: ['idee', 'entwurf', 'wichtig'],\n",[542,676,678],{"class":544,"line":677},23,[542,679,680],{},"  }),\n",[542,682,684],{"class":544,"line":683},24,[542,685,686],{},"})\n",[542,688,690],{"class":544,"line":689},25,[542,691,603],{"emptyLinePlaceholder":602},[542,693,695],{"class":544,"line":694},26,[542,696,697],{},"createRoot(document.getElementById('app')!).render(\n",[542,699,701],{"class":544,"line":700},27,[542,702,703],{},"  \u003CStrictMode>\n",[542,705,707],{"class":544,"line":706},28,[542,708,709],{},"    \u003CEditorContextProvider context={context}>\n",[542,711,713],{"class":544,"line":712},29,[542,714,715],{},"      \u003CApp \u002F>\n",[542,717,719],{"class":544,"line":718},30,[542,720,721],{},"    \u003C\u002FEditorContextProvider>\n",[542,723,725],{"class":544,"line":724},31,[542,726,727],{},"  \u003C\u002FStrictMode>,\n",[542,729,731],{"class":544,"line":730},32,[542,732,733],{},")\n",[418,735,736,737,739,740,743,744,747,748,750,751,754,755,757],{},"Notices of app-level models, such as the AI model profiles, go through the context's notifier. The simple editor raises none, so ",[422,738,500],{}," is enough here. A host of features passes ",[422,741,742],{},"createEditorContext({ notify: reactNotifier })"," and mounts sonner's ",[422,745,746],{},"Toaster"," once (both from ",[422,749,424],{},"); components such as ",[422,752,753],{},"DocumentTree"," need the ",[422,756,746],{}," to be mounted.",[479,759,761,437,764],{"id":760},"editorsurface-and-editortoolbar",[422,762,763],{},"EditorSurface",[422,765,428],{},[418,767,768,769,771,772,774,775,778],{},"The React ",[422,770,763],{}," takes the same kind of preset as the Vue one. ",[422,773,428],{}," is again a sibling that works on the editor you pass it (from ",[422,776,777],{},"onReady","), never on a global active editor, and it renders no groups without an editor. The preset follows chapter 3, with the same feature factories:",[532,780,783],{"className":534,"code":781,"filename":782,"language":537,"meta":538,"style":538},"\u002F\u002F Preset and metadata are read once at mount: module constants.\nconst preset = defineEditor({ features: [coreContentFeature(), coreUiFeature\u003CComponentType\u003C{ editor: Editor }>>({ palette: EditorCommandPalette }), mentionsFeature()] })\nconst metadata: EditorMetadata = {\n  mentions: [\n    { id: 'u1', label: 'Ada Lovelace', color: '#7c3aed' },\n    { id: 'u2', label: 'Alan Turing' },\n  ],\n}\n","examples\u002Freact-simple\u002Fsrc\u002FApp.tsx",[422,784,785,790,795,800,805,810,815,820],{"__ignoreMap":538},[542,786,787],{"class":544,"line":545},[542,788,789],{},"\u002F\u002F Preset and metadata are read once at mount: module constants.\n",[542,791,792],{"class":544,"line":551},[542,793,794],{},"const preset = defineEditor({ features: [coreContentFeature(), coreUiFeature\u003CComponentType\u003C{ editor: Editor }>>({ palette: EditorCommandPalette }), mentionsFeature()] })\n",[542,796,797],{"class":544,"line":557},[542,798,799],{},"const metadata: EditorMetadata = {\n",[542,801,802],{"class":544,"line":563},[542,803,804],{},"  mentions: [\n",[542,806,807],{"class":544,"line":569},[542,808,809],{},"    { id: 'u1', label: 'Ada Lovelace', color: '#7c3aed' },\n",[542,811,812],{"class":544,"line":575},[542,813,814],{},"    { id: 'u2', label: 'Alan Turing' },\n",[542,816,817],{"class":544,"line":581},[542,818,819],{},"  ],\n",[542,821,822],{"class":544,"line":587},[542,823,824],{},"}\n",[418,826,827,433,830,433,833,836,837,840,841,433,844,840,847,850,851,433,854,433,857,437,860,863],{},[422,828,829],{},"coreContentFeature()",[422,831,832],{},"coreUiFeature()",[422,834,835],{},"mentionsFeature()"," and the three integrations have no components and work in both layers. A feature with components has a React entry: ",[422,838,839],{},"mediaFeature"," from ",[422,842,843],{},"@skaile\u002Fforge-editor\u002Fmedia\u002Freact",[422,845,846],{},"blockUiFeature",[422,848,849],{},"@skaile\u002Fforge-editor\u002Fblock-ui\u002Freact",", and so on. Mixing a Vue feature into a React preset is a type error. Module constants matter more than in Vue: the preset, ",[422,852,853],{},"metadata",[422,855,856],{},"abilities",[422,858,859],{},"extensions",[422,861,862],{},"commands"," are read once at mount.",[418,865,866,867,437,870,873,874,433,877,873,879,882],{},"The surface itself differs from Vue in the prop names: ",[422,868,869],{},"value",[422,871,872],{},"onChange"," instead of ",[422,875,876],{},"v-model",[422,878,777],{},[422,880,881],{},"@ready",", and so on:",[532,884,886],{"className":534,"code":885,"filename":782,"language":537,"meta":538,"style":538},"            \u003CEditorSurface\n              preset={preset}\n              metadata={metadata}\n              commands={hostCommands}\n              value={content}\n              editable={editable}\n              onReady={setEditor}\n              onChange={onChange}\n              onContentError={onContentError}\n            \u002F>\n",[422,887,888,893,898,903,908,913,918,923,928,933],{"__ignoreMap":538},[542,889,890],{"class":544,"line":545},[542,891,892],{},"            \u003CEditorSurface\n",[542,894,895],{"class":544,"line":551},[542,896,897],{},"              preset={preset}\n",[542,899,900],{"class":544,"line":557},[542,901,902],{},"              metadata={metadata}\n",[542,904,905],{"class":544,"line":563},[542,906,907],{},"              commands={hostCommands}\n",[542,909,910],{"class":544,"line":569},[542,911,912],{},"              value={content}\n",[542,914,915],{"class":544,"line":575},[542,916,917],{},"              editable={editable}\n",[542,919,920],{"class":544,"line":581},[542,921,922],{},"              onReady={setEditor}\n",[542,924,925],{"class":544,"line":587},[542,926,927],{},"              onChange={onChange}\n",[542,929,930],{"class":544,"line":593},[542,931,932],{},"              onContentError={onContentError}\n",[542,934,935],{"class":544,"line":599},[542,936,937],{},"            \u002F>\n",[939,940,941,954],"table",{},[942,943,944],"thead",{},[945,946,947,951],"tr",{},[948,949,950],"th",{},"Name",[948,952,953],{},"Meaning",[955,956,957,982,997,1015,1028,1041,1066],"tbody",{},[945,958,959,965],{},[960,961,962],"td",{},[422,963,964],{},"preset",[960,966,967,969,970,973,974,977,978,981],{},[422,968,763],{}," only: ",[422,971,972],{},"defineEditor({ features })"," (a ",[422,975,976],{},"ReactPreset","); fixed at mount, remount with ",[422,979,980],{},"key"," to change it.",[945,983,984,991],{},[960,985,986,988,989],{},[422,987,869],{}," \u002F ",[422,990,872],{},[960,992,993,994,996],{},"TipTap JSON document and its edits. A new object replaces the document without an ",[422,995,872],{},"; JSON the editor emitted itself and the host hands back unchanged is ignored, so the round trip keeps caret and undo history.",[945,998,999,1004],{},[960,1000,1001],{},[422,1002,1003],{},"editable",[960,1005,1006,1007,1010,1011,1014],{},"Read-only when ",[422,1008,1009],{},"false"," (default ",[422,1012,1013],{},"true","); reactive.",[945,1016,1017,1021],{},[960,1018,1019],{},[422,1020,853],{},[960,1022,1023,1024,1027],{},"Host lists (",[422,1025,1026],{},"EditorMetadata","); reactive, pass a constant.",[945,1029,1030,1038],{},[960,1031,1032,433,1034,433,1036],{},[422,1033,856],{},[422,1035,859],{},[422,1037,862],{},[960,1039,1040],{},"As in Vue; read once at mount.",[945,1042,1043,1053],{},[960,1044,1045,433,1047,433,1050],{},[422,1046,777],{},[422,1048,1049],{},"onMetadataCreate",[422,1051,1052],{},"onContentError",[960,1054,1055,1056,433,1059,437,1062,1065],{},"The Vue events ",[422,1057,1058],{},"ready",[422,1060,1061],{},"metadataCreate",[422,1063,1064],{},"contentError"," as callbacks.",[945,1067,1068,1073],{},[960,1069,1070],{},[422,1071,1072],{},"settingsUi",[960,1074,1075,1078,1079,1082,1083,1086,1087,1090,1091,1094],{},[422,1076,1077],{},"'internal'"," (default) mounts the settings modal inside the surface; ",[422,1080,1081],{},"'external'"," calls ",[422,1084,1085],{},"onSettingsOpen(tab?)"," and the host renders ",[422,1088,1089],{},"EditorSettingsPanel"," or ",[422,1092,1093],{},"EditorSettingsModal",".",[479,1096,1098,1099],{"id":1097},"the-one-liner-simpleeditor","The one-liner: ",[422,1100,1101],{},"SimpleEditor",[418,1103,1104,1105,840,1107,1110,1111,437,1114,1117,1118,1121,1122,1124,1125,1128],{},"Without own features, ",[422,1106,1101],{},[422,1108,1109],{},"@skaile\u002Fforge-editor\u002Freact\u002Fsimple"," builds the preset itself (",[422,1112,1113],{},"core-content",[422,1115,1116],{},"core-ui",", plus the features in its ",[422,1119,1120],{},"add"," prop) on an ",[422,1123,763],{},". The ref exposes ",[422,1126,1127],{},"{ editor, openSettings, preset }",":",[532,1130,1132],{"className":534,"code":1131,"language":537,"meta":538,"style":538},"import { EditorToolbar } from '@skaile\u002Fforge-editor\u002Freact'\nimport { SimpleEditor } from '@skaile\u002Fforge-editor\u002Freact\u002Fsimple'\nimport type { Editor } from '@tiptap\u002Fcore'\nimport { useState } from 'react'\n\nexport function App() {\n  const [editor, setEditor] = useState\u003CEditor | null>(null)\n  return (\n    \u003C>\n      \u003CEditorToolbar editor={editor ?? undefined} \u002F>\n      \u003CSimpleEditor onReady={setEditor} \u002F>\n    \u003C\u002F>\n  )\n}\n",[422,1133,1134,1139,1144,1149,1154,1158,1163,1168,1173,1178,1183,1188,1193,1198],{"__ignoreMap":538},[542,1135,1136],{"class":544,"line":545},[542,1137,1138],{},"import { EditorToolbar } from '@skaile\u002Fforge-editor\u002Freact'\n",[542,1140,1141],{"class":544,"line":551},[542,1142,1143],{},"import { SimpleEditor } from '@skaile\u002Fforge-editor\u002Freact\u002Fsimple'\n",[542,1145,1146],{"class":544,"line":557},[542,1147,1148],{},"import type { Editor } from '@tiptap\u002Fcore'\n",[542,1150,1151],{"class":544,"line":563},[542,1152,1153],{},"import { useState } from 'react'\n",[542,1155,1156],{"class":544,"line":569},[542,1157,603],{"emptyLinePlaceholder":602},[542,1159,1160],{"class":544,"line":575},[542,1161,1162],{},"export function App() {\n",[542,1164,1165],{"class":544,"line":581},[542,1166,1167],{},"  const [editor, setEditor] = useState\u003CEditor | null>(null)\n",[542,1169,1170],{"class":544,"line":587},[542,1171,1172],{},"  return (\n",[542,1174,1175],{"class":544,"line":593},[542,1176,1177],{},"    \u003C>\n",[542,1179,1180],{"class":544,"line":599},[542,1181,1182],{},"      \u003CEditorToolbar editor={editor ?? undefined} \u002F>\n",[542,1184,1185],{"class":544,"line":606},[542,1186,1187],{},"      \u003CSimpleEditor onReady={setEditor} \u002F>\n",[542,1189,1190],{"class":544,"line":612},[542,1191,1192],{},"    \u003C\u002F>\n",[542,1194,1195],{"class":544,"line":618},[542,1196,1197],{},"  )\n",[542,1199,1200],{"class":544,"line":623},[542,1201,824],{},[479,1203,1205],{"id":1204},"settings-in-react","Settings in React",[418,1207,1208,840,1211,1213,1214,1217,1218,1221,1222,1225,1226,1229,1230,1233],{},[422,1209,1210],{},"useEditorSettings()",[422,1212,424],{}," reads the app's settings from the context: ",[422,1215,1216],{},"{ settings, update, set, ready, synced }",", where ",[422,1219,1220],{},"update(draft => { … })"," mutates a copy and stores it and ",[422,1223,1224],{},"set(value)"," replaces it. Persistence is the same as in Vue: the ",[422,1227,1228],{},"localStorage"," key ",[422,1231,1232],{},"tiptap-editor:settings",", optionally synced per account.",[479,1235,1237],{"id":1236},"where-the-callout-would-go","Where the callout would go",[418,1239,1240],{},"There is no React callout in the repository: the Hinweis block of the examples is a Vue example, and the React sample says so („Hinweis-Blöcke gehören nicht zur Bibliothek; das Vue-Beispiel bringt einen eigenen mit.“). Porting it is small, because the node is already framework-free:",[1242,1243,1244,1257,1281,1294],"ul",{},[1245,1246,1247,1250,1251,437,1254,1094],"li",{},[422,1248,1249],{},"node.ts"," from chapter 4 stays unchanged. It imports only ",[422,1252,1253],{},"@tiptap\u002Fcore",[422,1255,1256],{},"@skaile\u002Fforge-editor\u002Fcore\u002Fschema",[1245,1258,1259,1260,1263,1264,840,1267,1270,1271,1274,1275,437,1278,1094],{},"The second file attaches the view: ",[422,1261,1262],{},"Callout.extend({ addNodeView: () => ReactNodeViewRenderer(CalloutView) })"," with ",[422,1265,1266],{},"ReactNodeViewRenderer",[422,1268,1269],{},"@tiptap\u002Freact"," and a React ",[422,1272,1273],{},"CalloutView"," built from ",[422,1276,1277],{},"NodeViewWrapper",[422,1279,1280],{},"NodeViewContent",[1245,1282,1283,1284,1286,1287,1289,1290,1090,1292,1094],{},"Path A is the same: pass ",[422,1285,859],{}," (a module constant) and ",[422,1288,862],{}," (a module function) to ",[422,1291,763],{},[422,1293,1101],{},[1245,1295,1296,1297,1300,1301,1304,1305,840,1308,1310],{},"Path B is the same: ",[422,1298,1299],{},"calloutFeature()"," fills no component slot, so it is an ",[422,1302,1303],{},"EditorFeature\u003Cnever>"," and fits a React list as it is. Add ",[422,1306,1307],{},"blockUiFeature()",[422,1309,849],{}," for the hover actions.",[479,1312,1314],{"id":1313},"what-you-have-now","What you have now",[418,1316,1317,1318,1320,1321,1323],{},"The same editor on React: a context you own, an ",[422,1319,763],{}," over a preset of the features from chapter 3, a toolbar, the ",[422,1322,1101],{}," shortcut and the settings hook. You have finished the tutorial: from a simple editor to composed features, an own block, a server with collaboration and AI, on both surfaces.",[479,1325,1327],{"id":1326},"next","Next",[1242,1329,1330,1342],{},[1245,1331,1332,1334,1335,433,1338,1341],{},[468,1333,127],{"href":128}," for the full catalog on React (",[422,1336,1337],{},"AdvancedEditor",[422,1339,1340],{},"composeEditor",", collaboration).",[1245,1343,1344,1347],{},[468,1345,1346],{"href":333},"Exports"," for the inventory of every entry of the package.",[1349,1350,1351],"style",{},"html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":538,"searchDepth":551,"depth":551,"links":1353},[1354,1356,1358,1360,1361,1362,1363],{"id":481,"depth":551,"text":1355},"The context: EditorContextProvider",{"id":760,"depth":551,"text":1357},"EditorSurface and EditorToolbar",{"id":1097,"depth":551,"text":1359},"The one-liner: SimpleEditor",{"id":1204,"depth":551,"text":1205},{"id":1236,"depth":551,"text":1237},{"id":1313,"depth":551,"text":1314},{"id":1326,"depth":551,"text":1327},"Run the meeting-notes editor on React: EditorContextProvider, EditorSurface, EditorToolbar, SimpleEditor and the Toaster, and where the callout would go.","md",null,{},{"icon":83},{"title":80,"description":1364},"BKGmtueWj1R9zc7yhcbnCuiEbnzmb4kp-NurOo3yku0",[1372,1374],{"title":75,"path":76,"stem":77,"description":1373,"icon":78,"children":-1},"Add aiFeature() to the meeting-notes editor, provide the \u002Fapi\u002Fai endpoints and keys, and describe the callout to the model with aiContext.",{"title":90,"path":91,"stem":92,"description":1375,"icon":58,"children":-1},"Compose an editor from features and presets, pick the right entries, and choose the integration depth from no data layer to a Nuxt server.",1793178230474]