Commands: Sections & Track Changes
Page layout commands (sections, page breaks, line numbers, headers and footers) from core, and the track-changes commands from @stesura/track-changes.
// section, page-break and header/footer commands ship in core
import { addSection, removeSectionBreak, setSectionFormat } from "@stesura/core/commands";
// track-changes commands ship in the track-changes feature pack
import {
enableTrackChanges,
disableTrackChanges,
toggleTrackChanges,
toggleTrackChangesVisibility,
resolveTrackChanges,
resolveAllTrackChanges,
selectTrackChanges,
} from "@stesura/track-changes";Track changes also requires transactionModifier={trackChangesTransactionModifier} on StesuraEditor so transactions flow through the diff layer:
import { trackChangesTransactionModifier } from "@stesura/track-changes";
<StesuraEditor /* ... */ transactionModifier={trackChangesTransactionModifier} />Sections
addSection
Split the current section after the top-level block holding the cursor. The blocks after it move into a new section that inherits the current one’s page setup and header/footer, and the cursor moves into the new section.
addSection(): CommandremoveSectionBreak / removeSectionBreakAt
Remove a section break as Word does: the section before the break moves its body to the start of the section after it, which keeps its page setup, header, footer and id. Blocks on either side of the break stay separate. removeSectionBreak removes the break that ends the section holding the selection (in the last section, the break above it); removeSectionBreakAt takes the index of the section before the break. Refused with a toast when the doc has one section, or while track changes is suggesting. Backspace at the start of a section’s body and Delete at the end of the previous one do the same.
removeSectionBreak: Command
removeSectionBreakAt(index: number): CommanddeleteAcrossSections
Delete (or type over, cut, paste over) a selection whose ends lie in different sections. Such a selection always covers whole blocks; the covered blocks go with every break inside the range, so the last section touched survives. While suggesting, the blocks are tracked as deletions and every break stays. Backspace, Delete, typing, cut and paste already route through it.
deleteAcrossSections(replacement?: { text: string } | { slice: Slice }): CommandsetSectionFormat
Set the current section’s orientation, page size and/or margins in one step. A page size whose aspect contradicts the orientation is transposed to match it, and an orientation change without a size swaps the page’s width and height. Refused when nothing changes.
setSectionFormat(options: SectionOptions): Command
type SectionOptions = {
orientation?: "portrait" | "landscape";
pageSize?: { width: number; height: number };
margin?: { // merged over the current margins
top?: number;
bottom?: number;
left?: number;
right?: number;
layout?: "narrow" | "moderate" | "wide" | "custom";
};
};There is no section background colour. The canvas behind the pages is set with setDocBgColor(color) (the docBgColor doc attr).
setPageBorders
Set the current section’s page borders, Word’s Page Border tab; null removes them. scope: "document" sets every section. Refused when nothing changes. Borders are a per-page overlay: they never move text, and print with the page.
setPageBorders(pageBorders: PageBorders | null, options?: SetPageBordersOptions): Command
type PageBorders = {
top: PageBorderSide | null; // { style, size (px), color, space (pt) }
right: PageBorderSide | null; // left/right are inline start/end, like margins
bottom: PageBorderSide | null;
left: PageBorderSide | null;
offsetFrom: "page" | "text"; // what `space` is measured from
display: "allPages" | "firstPage" | "notFirstPage";
zOrder: "front" | "back"; // round-tripped only; always painted in front
};
type SetPageBordersOptions = {
scope?: "section" | "document";
sectionId?: string; // target when a header/footer sub-editor has focus
};DEFAULT_PAGE_BORDERS (from @stesura/core/constants) is Word’s “Box”: ½pt single black, 24pt in from the page edge.
setPageBordersSurround
Whether text-measured page borders enclose the header and footer. Document-wide (pageBordersSurroundHeader / pageBordersSurroundFooter doc attrs), like Word’s w:bordersDoNotSurroundHeader/Footer settings. Refused when nothing changes.
setPageBordersSurround(surround: { header?: boolean; footer?: boolean }): CommandsetWatermark
Set the current section’s watermark, Word’s “Printed Watermark”; null removes it. scope: "document" sets every section. Refused when nothing changes. The watermark is painted behind the text of every page of the section (not when pagination is off), is not document content (search, selection, copy and markdown export never see it), and round-trips through DOCX as Word’s VML watermark.
setWatermark(watermark: Watermark | null, options?: SetWatermarkOptions): Command
type Watermark =
| {
kind: "text";
text: string; // trimmed, at most 200 characters
fontFamily: string | null; // null: the document's Normal font
fontSize: "auto" | number; // pt, 8–400; "auto" fills the text area
color: string;
semitransparent: boolean;
layout: "diagonal" | "horizontal";
}
| {
kind: "picture";
src: string; // an image src, like an image node's
scale: "auto" | number; // percent of the picture's own size, 10–500
washout: boolean;
};
type SetWatermarkOptions = {
scope?: "section" | "document";
sectionId?: string; // target when a header/footer sub-editor has focus
};DEFAULT_TEXT_WATERMARK (from @stesura/core/constants) is Word’s default: “CONFIDENTIAL”, diagonal, semitransparent silver, auto size, the document’s font.
setOrientation
Switch the current section between portrait and landscape, swapping page width and height. Refused when already in that orientation.
setOrientation(orientation: "portrait" | "landscape"): CommandinsertPageBreak
Insert a hard page break at the cursor (Word’s Ctrl+Enter). The paragraph is split around the break and the cursor lands after it; at the end of a block, the split leaves an empty paragraph for the cursor. Refused inside tables and outside section content.
insertPageBreak: CommandinsertPageNumber
Insert a page-number field at the cursor. Refused inside code blocks.
insertPageNumber({ pageType: "currentPage" | "totalPages" }): CommandinsertPageOfPage
Insert a “1 of 12” pair of page-number fields. middleText is the bare separator word (default "of"); a space is added on each side.
insertPageOfPage(middleText?: string): CommandLine Numbers
setLineNumbers
Merge attributes into the section’s line numbering (starting from the defaults when it is off), clamped to bounds.
setLineNumbers(attrs: Partial<{
countBy: number; // show only every Nth number (default 1)
distance: number; // gutter gap in px (default 10)
restart: "continuous" | "newPage" | "newSection"; // default "continuous"
start: number; // first line's number, 1-based (default 1)
}>): CommandtoggleLineNumbers
Turn the section’s line numbering on (with the defaults) or off.
toggleLineNumbers(): CommandtoggleSuppressLineNumbers
Toggle suppressLineNumbers on every suppressible block in the selection. Suppressed lines are skipped by the counter (Word semantics), not merely hidden. Pass suppress to force a value.
toggleSuppressLineNumbers(suppress?: boolean): CommandlineNumbersSuppressed
State reader, not a command: true when every suppressible block in the selection is suppressed.
lineNumbersSuppressed(state: EditorState): booleanHeaders & Footers
setHeaderFooterDistance
Set the section’s headerFromTop / footerFromBottom (px). Pass sectionId when dispatching on the main editor while a header/footer sub-editor holds focus. Refused when nothing changes.
setHeaderFooterDistance(options: {
headerFromTop?: number;
footerFromBottom?: number;
}, sectionId?: string): CommandinsertHeaderFooterContent / deleteHeaderFooterContent
Add or remove a header_footer_content variant on both the section’s header and footer. Inserting adds one empty paragraph wherever the variant is missing. Deleting the "default" variant is refused. insertHeaderFooterContentTr(tr, state, sectionId, variant) in @stesura/core/commands/internal is the composable form.
type HeaderFooterVariant = "default" | "first" | "even";
insertHeaderFooterContent(sectionId: string, variant: HeaderFooterVariant): Command
deleteHeaderFooterContent(sectionId: string, variant: HeaderFooterVariant): CommandinsertPageNumberPreset
Insert a page number into a section’s header (top) or footer (bottom) as a Word-style preset: a borderless 1×3 table with left-, centre- and right-aligned columns. Creates the variant if needed, all in one transaction. Dispatch it on the main editor, not a header/footer sub-editor.
insertPageNumberPreset({
sectionId: string,
variant?: HeaderFooterVariant, // default "default"
position: { vertical: "top" | "bottom"; horizontal: "left" | "center" | "right" },
pageType?: "currentPage" | "totalPages" // default "currentPage"
}): CommandfixHeadersFooters
Repair header/footer structure: resolve null and duplicate variants, mirror each variant on both header and footer (always including "default"), and give every content node at least one paragraph. Refused when nothing needs fixing.
fixHeadersFooters: CommandWhich variants a section displays (different first page, odd/even) follows from which header_footer_content nodes exist, so insertHeaderFooterContent / deleteHeaderFooterContent toggle them — see Header & Footer.
Borders
Border commands (setBorders, toggleOutline, insertHorizontalRule) are documented on the tables page. They also apply to styled blocks and horizontal rules.
Track Changes
enableTrackChanges / disableTrackChanges / toggleTrackChanges
Turn track-changes recording on or off. The optional show argument also sets visibility at the same time. Omitted, turning tracking on also turns visibility on, and turning it off leaves visibility as it is. In review mode, disableTrackChanges and toggleTrackChanges return false.
enableTrackChanges(show?: boolean): Command
disableTrackChanges(show?: boolean): Command
toggleTrackChanges(show?: boolean): CommandtoggleTrackChangesVisibility
Flip whether pending suggestions are displayed. Tracking itself is untouched — edits keep being recorded while the markup is hidden.
toggleTrackChangesVisibility: CommandselectTrackChanges
Move the selection to cover the tracked change with the given id — inline marks and block-level suggestions (a deleted row, a pending attribute change) alike.
selectTrackChanges(selectionId: string): CommandresolveTrackChanges
Accept or reject the suggestion under the cursor (or covering the selection). With an empty selection it resolves the whole same-user, same-burst change; a drawn selection resolves every suggestion it touches, in full. moveToNext jumps to the next suggestion afterwards.
Dry runs (toolbar probes) answer false on a document with no pending changes, so Accept/Reject buttons disable themselves when there is nothing to review — the same applies to resolveAllTrackChanges and disableTrackChanges, which return false rather than dispatching when they would change nothing.
resolveTrackChanges(action: "approve" | "reject", moveToNext?: boolean): CommandresolveAllTrackChanges
Accept or reject every suggestion in the document at once.
resolveAllTrackChanges(action: "approve" | "reject"): CommandTrack Changes Marks
Tracked inline edits are stored as marks from @stesura/core/schema:
insertion: pending inserted text, rendered as<ins>.deletion: pending deleted text, kept in the document and rendered as<del>.modification: a pending inline mark change.typeis"addMark"(newValueholds the added mark’s JSON) or"removeMark"(previousValueholds the removed one).
Each mark carries id, date, userId and userName. Block-level changes live on node attributes instead: trackChanges (block insertion or deletion) and trackChangesModification (attribute changes).
Next Steps
- Commands: Specialized — Footnotes, numbering, code blocks, images, and more
- Schema — Section node attributes reference
- Track Changes — Full track-changes reference