Skip to content
ФреймворкJavaScript

OutputData: формат сохранённого JSON

Структура данных, возвращаемая методом save(). Во входных позициях — опция конфигурации `data`, `render()`, `blocks.render()` и `blocks.insertMany()` — также принимаются нестрогие варианты `LooseOutputData` / `LooseOutputBlockData`, где `data`, `id` и `time` блока могут быть `null`: `null` в `data` становится `{}`, `null`/пустой `id` заменяется сгенерированным. Сохранённый вывод всегда имеет строгую форму.

Как получить экземпляр редактора

Методы ниже вызываются на редакторе, созданном через new Blok(). Они доступны после того, как разрешится editor.isReady.

TypeScript
// You already hold the instance returned by the constructor.
const editor = new Blok({ holder: 'editor' });
await editor.isReady;

// Call any API method on it.
editor.caret.setToLastBlock('end');

Методы

equalsOutputData(a, b)

boolean

Структурное равенство сохранённых документов, экспортируется из главной точки входа. Глубоко сравнивает массивы `blocks`; изменчивые поля-конверты `time` и `version` игнорируются, поэтому документ, прошедший через save(), равен своему эху. Принимаются nullish-документы и нестрогие форматы — `null`/`undefined` равны `{ blocks: [] }`.

Когда использовать

Используйте, чтобы сохранять или обновлять состояние только при реальных изменениях — time и version различаются при каждом save(), поэтому наивное глубокое сравнение всегда сообщает об изменении.

TypeScript
import { equalsOutputData } from '@bloklabs/core';

const saved = await editor.save();
if (!equalsOutputData(saved, previousData)) {
  await persist(saved); // only hit the backend on real changes
}

isEmptyOutputData(data)

boolean

True, когда документ не содержит пользовательского контента, экспортируется из главной точки входа: он nullish, не имеет блоков, либо данные каждого блока содержат только пустые значения (пустые строки и строки из пробелов, пустые массивы/объекты). Числа и булевы значения (`level`, `checked`, стили) — презентационные метаданные и сами по себе контентом не считаются.

Когда использовать

Визуальные блоки без контента (например, разделитель с data: {}) считаются пустыми — проверяйте blocks.length, когда важно само наличие блоков.

TypeScript
import { isEmptyOutputData } from '@bloklabs/core';

const data = await editor.save();
submitButton.disabled = isEmptyOutputData(data);
// → true for a fresh editor holding one blank paragraph

normalizeOutputData(data)

OutputData

Normalizes a whole loose backend DTO into the strict saved OutputData shape, exported from the main entry. A nullish document becomes `{ blocks: [] }`; `null` envelope fields (`time`/`version`) are dropped; each block is normalized so `null`/missing `data` becomes `{}` and `null`/empty ids are dropped for regeneration. Unlike a hand-written `blocks.map(...)` mapper it preserves every passthrough field — `tunes`, `parent`, `content`, `indent`, edit metadata — so hierarchy and tunes are never silently lost. Idempotent: a strict document passes through unchanged.

TypeScript
import { normalizeOutputData } from '@bloklabs/core';

// A loose Editor.js-era DTO (data: null, id: null) from your backend
const strict = normalizeOutputData(dtoFromApi);
// → strict OutputData, safe to persist or diff — no blind `as OutputData` cast

normalizeOutputBlocks(blocks)

OutputBlockData[]

Block-level counterpart of normalizeOutputData, exported from the main entry: normalizes an array of loose wire blocks into the strict saved shape (`null`/missing `data` → `{}`, `null`/empty `id` dropped for regeneration) while passing every other field through untouched. Use normalizeOutputData when you hold the whole document envelope.

TypeScript
import { normalizeOutputBlocks } from '@bloklabs/core';

const blocks = normalizeOutputBlocks(looseBlocksFromApi);
// → OutputBlockData[] with tunes/parent/content/indent intact

EMPTY_OUTPUT_DATA

OutputData

A shared, deeply frozen empty document (`{ blocks: [] }`), exported from the main entry. Use it in place of a hand-written `{ blocks: [] }` literal for cleared/pristine baselines. Frozen (blocks array included) so a shared reference can never be mutated into a stale non-empty baseline.

TypeScript
import { EMPTY_OUTPUT_DATA, equalsOutputData } from '@bloklabs/core';

const saved = await editor.save();
const isPristine = equalsOutputData(saved, EMPTY_OUTPUT_DATA, {
  ignoreEmptyDefaultBlocks: true,
});

toRenderableData(data)

OutputData | LooseOutputData

Maps a controlled `data` value to something render()/blocks.render() accepts, exported from the main entry: a whole-document `null` (a controlled "clear to empty") becomes `{ blocks: [] }`; any real document passes through untouched. render()'s strict guard reads `data.blocks` and would throw on `null`, so route a nullable controlled value through this first.

TypeScript
import { toRenderableData } from '@bloklabs/core';

// `draft` may be null when the host clears the document
await editor.blocks.render(toRenderableData(draft));

createEmittedEchoWindow(capacity?)

{ record; matches; clear }

Creates a bounded window of recently emitted onSave payloads for recognizing controlled-`data` echoes, exported from the main entry. Deduping against only the LAST emitted payload is not enough: a host that persists on save and refetches can hand back a STALE echo (an earlier save arriving after a newer one already replaced the baseline), and re-rendering it would clobber the caret and any content typed since. Matching is structural (equalsOutputData), so envelopes reshaped in transit (fresh `time`, stripped ids) still count as echoes.

TypeScript
import { createEmittedEchoWindow } from '@bloklabs/core';

const echoes = createEmittedEchoWindow();
// in onSave: echoes.record(data)
// before re-rendering incoming props: if (echoes.matches(next)) return;

migrateLegacyBlocks(blocks)

OutputBlockData[]

Migrate legacy / Editor.js-style blocks into Blok's hierarchical flat-with-references format, exported from the `@bloklabs/core/migrate` subpath — the same transform the renderer runs automatically at load. Legacy nested shapes (list items, toggle/callout children) explode into separate blocks linked by `parentId`/`content`, and id-less blocks are stamped with an id. Already-hierarchical blocks pass through unchanged, so it is safe to run on current data and idempotent across repeated runs. Use it to migrate a stored document explicitly — a one-off batch upgrade of persisted records, or a pre-load normalization step — instead of hand-rolling per-tool shape conversion. `migrateLegacyOutputData(data)` is the envelope-preserving variant, and `needsLegacyMigration(blocks)` reports whether a migration would change anything (skip the id-minting pass when the document is already current).

Когда использовать

For a data shape only a specific tool understands (a columns layout, a custom media envelope) that core's built-in migration can't read, give that tool a static upgradeData(data) — a pure function returning the tool's current data shape. Blok runs it at load, while composing each stored block, before the tool is constructed; a hook that throws is caught and the block loads with its stored data.

TypeScript
import {
  migrateLegacyBlocks,
  migrateLegacyOutputData,
  needsLegacyMigration,
} from '@bloklabs/core/migrate';

// Batch-upgrade persisted Editor.js documents
const upgraded = migrateLegacyOutputData(storedDocument);

// Or migrate just the blocks, skipping the pass when already current
const blocks = needsLegacyMigration(stored.blocks)
  ? migrateLegacyBlocks(stored.blocks)
  : stored.blocks;
TypeScript
// Save editor content
const data = await editor.save();

// Result structure:
interface OutputData {
  version?: string;    // Editor version
  time?: number;       // Save timestamp
  blocks: OutputBlockData[]; // Array of block data
}

// Example output:
{
  "version": "1.4.2",
  "time": 1704067200000,
  "blocks": [
    {
      "id": "p6QK0Xz1Ab",
      "type": "paragraph",
      "data": { "text": "Hello, world!" }
    },
    {
      "id": "hM3lTn9RdC",
      "type": "header",
      "data": { "text": "Title", "level": 2 }
    }
  ]
}

OutputData

СвойствоОписание
versionstring (optional)Структура данных, возвращаемая методом save(). Во входных позициях — опция конфигурации `data`, `render()`, `blocks.render()` и `blocks.insertMany()` — также принимаются нестрогие варианты `LooseOutputData` / `LooseOutputBlockData`, где `data`, `id` и `time` блока могут быть `null`: `null` в `data` становится `{}`, `null`/пустой `id` заменяется сгенерированным. Сохранённый вывод всегда имеет строгую форму.
timenumber (optional)Структура данных, возвращаемая методом save(). Во входных позициях — опция конфигурации `data`, `render()`, `blocks.render()` и `blocks.insertMany()` — также принимаются нестрогие варианты `LooseOutputData` / `LooseOutputBlockData`, где `data`, `id` и `time` блока могут быть `null`: `null` в `data` становится `{}`, `null`/пустой `id` заменяется сгенерированным. Сохранённый вывод всегда имеет строгую форму.
blocksOutputBlockData[]Структура данных, возвращаемая методом save(). Во входных позициях — опция конфигурации `data`, `render()`, `blocks.render()` и `blocks.insertMany()` — также принимаются нестрогие варианты `LooseOutputData` / `LooseOutputBlockData`, где `data`, `id` и `time` блока могут быть `null`: `null` в `data` становится `{}`, `null`/пустой `id` заменяется сгенерированным. Сохранённый вывод всегда имеет строгую форму.