typograph-editor-documentation
    Preparing search index...

    Typograph — Find and replace from host code

    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.


    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.

    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 });
    • One undo step for the whole operation, however many frames or pages it touched. Ctrl/⌘-Z puts all of it back.
    • A replacement containing \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.
    • The inserted text takes the character formatting of the run the match starts in, so replacing a phrase that spans a bold word gives you one uniformly formatted replacement rather than a guess at which part should stay bold.
    • Matching nothing changes nothing: it returns 0, records no undo step and fires no event.
    • When it does replace, the affected flows are recomposed and the canvas redrawn before it returns, and a 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.
    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.
    • Matches go stale as soon as you edit. They are offsets into the text as it was; replacing one occurrence moves every offset after it. Re-run find() after any change rather than reusing an earlier result.
    • The needle cannot contain a newline. A paragraph break is a boundary between paragraphs, not a character inside one, so a needle containing \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.
    • TextModel.md — the same job from backend code, without the library
    • DocumentEvents.mdtypograph_document_changed, and is_changed()
    • Embedding.md — loading the editor or the viewer in the first place