Two calls on window.typograph search and rewrite the text of the open document:
const matches = window.typograph.find('NAME');
const count = window.typograph.replace_all('NAME', 'Jane Doe');
Available in the editor and the viewer — they are part of the canvas library, not of the
editor's chrome, so an embed that never shows the Find and Replace dialog can still drive them.
They act on the document that is currently open, so wait until it is
(typograph_document_opened).
In a viewer embed this API is the only route: the viewer has no menu bar and therefore no Find and Replace dialog, and it does not gain one from a template. Whether your users get a search box at all, and what it looks like, is your host's decision — these two calls are what you build it on.
Editing the JSON on a server instead, without the library running? That is a different job with different rules — see TextModel.md.
find(needle, options)Returns every occurrence, in document order:
window.typograph.find('NAME')
window.typograph.find('name', { case_sensitive:true, whole_word:true })
window.typograph.find('NAME', { page_index:2 })
Each match is:
{
element_uuid: string, // the frame that owns the text
page_index: number, // the page it was found on
paragraph_index: number, // which paragraph within that frame's story
char_offset: number, // where in the paragraph's text the match starts
length: number // how many characters it spans
}
char_offset and length address the paragraph's flattened text — the paragraph as the reader
sees it, with its formatting runs concatenated. That is deliberate: a phrase whose formatting
changes part way through is one match, so find('Dear NAME') matches even when the name is bold.
Read-only. Calling it records nothing on the undo stack and fires no events.
replace_all(needle, replacement, options)Returns how many occurrences it replaced:
const n = window.typograph.replace_all('NAME', 'Jane Doe');
const n = window.typograph.replace_all('NAME', 'Jane\nDoe', { page_index:0 });
\n splits the paragraph, exactly as pressing Enter in the frame
would: the new paragraphs carry the original's paragraph style, its overrides and its tab stops.
Text before the match stays with the first of them, text after it moves to the last.0, records no undo step and fires no event.typograph_document_changed event fires with reason: 'edit' — so an auto-save
wired to that event (see DocumentEvents.md) picks this up like any other
edit.options| Option | Type | Meaning |
|---|---|---|
case_sensitive |
boolean | Default false: name matches NAME. |
whole_word |
boolean | Default false. A word character is a letter, a digit or _, Unicode-aware — so whole_word stops cat matching inside concatenate, and does not break on accented letters. |
page_index |
number | Search only this page (0-based). |
element_uuid |
string | Search only this text frame. |
Both calls take the same options, and both default to searching the whole document.
Text that overflows from one frame into the next is one story with several frames on screen. Only the first frame owns the text; the others are windows onto it.
Both calls follow that. A flow is searched once rather than once per frame, and the
element_uuid in a match is always the flow's first frame — the one that owns the text — even
when the words appear in a later one. Pass a later frame's uuid as element_uuid and you get the
same thing: the whole flow is searched, and matches come back named after its first frame.
This is also why replace_all cannot double-apply. A three-frame chain is de-duplicated before
anything is written, so replace_all('cat', 'concatenate') runs once over that story instead of
three times over its own output.
Text inside a table cell is invisible to both calls. find() will not return it and
replace_all() will not rewrite it.
A count of 0 therefore does not prove the document is free of the needle. If your documents
use tables and the distinction matters — a mail-merge that must not ship an unfilled placeholder,
for instance — verify the result rather than trusting the count.
page_index and flows that span pages. Narrowing to a page still searches any flow that has
a frame on it, including one whose first frame lives on an earlier page — it is a single story
and cannot be searched by halves. The page_index on those matches is the page you filtered to.find() after any change rather than reusing
an earlier result.\n matches nothing. Search within a paragraph,
or match one paragraph at a time. Replacements have no such limit — that is what the \n split
above is for.typograph_document_changed, and is_changed()