UNPKG

@ryhrm-gz/xincodo-lib

Version:

Utilities for working with Xincodo body documents.

252 lines (189 loc) 6.25 kB
--- name: editing-preview-workflow description: > Load when implementing management-screen editing and preview updates with walkBody, findBlockPathById, TraversalPath, getBlockAtPath, insertBlockAtRoot, insertBlockAtPath, updateBlockAtPath, replaceBlockAtPath, removeBlockAtPath, moveBlock, updateRichTextAtPath, mapBody, or BodyEditError. type: lifecycle library: "@ryhrm-gz/xincodo-lib" library_version: "0.1.0" requires: - building-and-parsing-body - validating-body sources: - "ryhrm-gz/xincodo-lib:README.md" - "ryhrm-gz/xincodo-lib:src/editing.ts" - "ryhrm-gz/xincodo-lib:src/traversal.ts" - "ryhrm-gz/xincodo-lib:tests/editing.test.ts" - "ryhrm-gz/xincodo-lib:tests/traversal.test.ts" --- # Editing Preview Workflow This skill builds on `building-and-parsing-body` and `validating-body`. Use stable ids to locate blocks, resolve a fresh `TraversalPath` immediately before editing, and use immutable helpers to produce the next `Body`. ## Setup ```ts import { findBlockPathById, insertBlockAtPath, paragraph, type Body } from "@ryhrm-gz/xincodo-lib"; export function insertNoteAfterIntro(body: Body): Body { const introPath = findBlockPathById(body, "intro"); if (!introPath) { return body; } return insertBlockAtPath(body, introPath, paragraph("Preview note"), { position: "after", }); } ``` ## Core Patterns ### Resolve a fresh path from a stable id ```ts import { findBlockPathById, getBlockAtPath } from "@ryhrm-gz/xincodo-lib"; const path = findBlockPathById(body, "summary"); const block = path ? getBlockAtPath(body, path) : undefined; ``` Paths are position-based. Recompute them from stable ids after edits that may move or insert blocks. ### Replace or update a block immutably ```ts import { heading, replaceBlockAtPath, type Body, type TraversalPath } from "@ryhrm-gz/xincodo-lib"; export function promoteIntro(body: Body, path: TraversalPath) { return replaceBlockAtPath(body, path, heading(2, "Introduction", { id: "intro" })); } ``` The returned `Body` is the value to store in editor state. ### Update rich text from traversal context ```ts import { richText, updateRichTextAtPath, walkBody, type Body, type TraversalPath, } from "@ryhrm-gz/xincodo-lib"; export function renameIntro(body: Body): Body { let richTextPath: TraversalPath | undefined; walkBody(body, { richText(_, context) { if ( "type" in context.parent && context.parent.type === "text" && context.parent.id === "intro" ) { richTextPath = context.path; } }, }); return richTextPath ? updateRichTextAtPath(body, richTextPath, () => richText("Updated intro")) : body; } ``` `updateRichTextAtPath` needs a path ending at `richText`, `caption`, or `title`, not just the block path. ### Move blocks with descendant safety ```ts import { findBlockPathById, moveBlock, type Body } from "@ryhrm-gz/xincodo-lib"; export function moveIntroAfterSummary(body: Body): Body { const introPath = findBlockPathById(body, "intro"); const summaryPath = findBlockPathById(body, "summary"); if (!introPath || !summaryPath) { return body; } return moveBlock(body, introPath, summaryPath, { position: "after" }); } ``` `moveBlock` throws `BodyEditError` if the target is inside the moved block. ## Common Mistakes ### HIGH Mutating nested content directly Wrong: ```ts import { paragraph, type Body } from "@ryhrm-gz/xincodo-lib"; function addNote(body: Body): Body { const first = body.content[0]; if (first?.type === "text") { first.children?.push(paragraph("Inserted")); } return body; } ``` Correct: ```ts import { findBlockPathById, insertBlockAtPath, paragraph, type Body } from "@ryhrm-gz/xincodo-lib"; function addNote(body: Body): Body { const path = findBlockPathById(body, "intro"); return path ? insertBlockAtPath(body, path, paragraph("Inserted")) : body; } ``` Editing helpers return new `Body` values so preview state can update predictably. Source: `README.md`; `tests/editing.test.ts`; `tests/traversal.test.ts` ### HIGH Passing a block path to rich text updater Wrong: ```ts import { findBlockPathById, richText, updateRichTextAtPath, type Body, } from "@ryhrm-gz/xincodo-lib"; function updateIntro(body: Body): Body { const path = findBlockPathById(body, "intro"); return path ? updateRichTextAtPath(body, path, () => richText("Updated")) : body; } ``` Correct: ```ts import { richText, updateRichTextAtPath, walkBody, type Body, type TraversalPath, } from "@ryhrm-gz/xincodo-lib"; function updateIntro(body: Body): Body { let richTextPath: TraversalPath | undefined; walkBody(body, { richText(_, context) { if ( "type" in context.parent && context.parent.type === "text" && context.parent.id === "intro" ) { richTextPath = context.path; } }, }); return richTextPath ? updateRichTextAtPath(body, richTextPath, () => richText("Updated")) : body; } ``` Rich text updates require a field path, usually captured from `walkBody`. Source: `src/editing.ts`; `tests/editing.test.ts` ### MEDIUM Moving a block into its descendant Wrong: ```ts import { findBlockPathById, moveBlock, type Body } from "@ryhrm-gz/xincodo-lib"; function moveParentIntoChild(body: Body): Body { return moveBlock(body, findBlockPathById(body, "parent")!, findBlockPathById(body, "child")!); } ``` Correct: ```ts import { findBlockPathById, moveBlock, type Body } from "@ryhrm-gz/xincodo-lib"; function moveParentAfterSibling(body: Body): Body { const parentPath = findBlockPathById(body, "parent"); const siblingPath = findBlockPathById(body, "sibling"); return parentPath && siblingPath ? moveBlock(body, parentPath, siblingPath, { position: "after" }) : body; } ``` `moveBlock` prevents moving a block relative to its own descendant because that would create an invalid nesting operation. Source: `src/editing.ts`; `tests/editing.test.ts` ## References - [Traversal paths](references/traversal-paths.md) - [Rich text field paths](references/rich-text-field-paths.md) See also: `validating-body/SKILL.md` — edited preview state should be linted before save or publish.