Editor

Базовый сценарий

Тип блока

Абзацев: 0

Готовое значение

Тип блока
Первый абзац набирается как в обычном поле.
Тип блока
Enter делит блок, Backspace в начале склеивает с предыдущим.

Только для чтения и отключение

Тип блока
Текст доступен для чтения и выделения, но не правится.
Тип блока
Поле отключено.

Ошибка

Тип блока
Поле с ошибкой.

Размеры

Тип блока
Тип блока
Тип блока

Минимальная высота

Тип блока
Тип блока

Плоский текст значения:

Внутри C3FormField

Тип блока

Плоский текст: —

События

Тип блока

focus: false · blur: false

Тулбокс и типы блоков

Наведите на блок или нажмите Tab в пустом — откроется тулбокс. Стрелки и Enter выбирают тип, Escape закрывает.

Тип блока
Абзац, который станет заголовком

Типы: paragraph

Список

Тип блока

Enter добавляет пункт, Backspace в начале пустого пункта заканчивает список.

Смена типа блока

Нажмите Tab в начале абзаца — откроется тулбокс. Текст переезжает вместе с блоком: абзац становится списком с пунктами, список — обратно абзацем с переводами строк, начертания и настройки сохраняются. В середине текста Tab ведёт себя как в обычном поле.

Тип блока
Строка один Строка два

Блок: {"id":"c1","type":"paragraph","data":{"text":"Строка один\nСтрока два"}}

Настройки типа блока

Наведите на блок с настройками: справа появится кнопка с шестерёнкой. У заголовка это уровень, у списка — нумерация, у кода — язык. Абзац и разделитель настроек не имеют.

Тип блока
Заголовок с настройками
Тип блока
Тип блока
const a = 1
Тип блока
У абзаца настроек нет, поэтому кнопки у него нет.

Значение: [{"type":"header","data":{"text":"Заголовок с настройками","level":2}},{"type":"list","data":{"text":"","items":["Первый пункт","Второй пункт"],"style":"unordered"}},{"type":"raw","data":{"text":"const a = 1","language":"ts"}},{"type":"paragraph","data":{"text":"У абзаца настроек нет, поэтому кнопки у него нет."}}]

В режиме readOnly и disabled кнопки настроек нет: документ не должен меняться.

Начертания внутри текста

Выделите текст в абзаце, заголовке или пункте списка — над выделением появится панель. Ctrl+B — полужирный, Ctrl+I — курсив, Ctrl+E — моноширинный, Ctrl+K — ссылка. Повторное нажатие снимает начертание, ссылка открывает диалог адреса. В блоке кода форматирования нет.

Тип блока
Выделите слово и сделайте его жирным или курсивом.
Тип блока
Жирный заголовок
Тип блока
Тип блока
const a = 1

Начертания в значении: [{"type":"header","marks":[{"type":"bold","from":0,"to":6}]},{"type":"list","items":[[{"type":"italic","from":0,"to":5}]]}]

Ссылка

Выделите слово и нажмите Ctrl+K (или кнопку Ссылка в панели) — откроется диалог адреса с текущим адресом выделения. Пустое поле снимает ссылку, нерабочая схема (javascript:, data:) не применится. Ссылка хранится интервалом с адресом: { type: 'link', from, to, href }.

Тип блока
Ссылка пришла из данных: сайт.
Тип блока
Слово для смены адреса.

Начертания: [ [ { "type": "link", "from": 25, "to": 29, "href": "https://example.com" } ] ]

Вложенный список

Tab в начале пункта вкладывает его на уровень глубже, Shift+Tab выносит на уровень выше. В середине текста Tab ведёт себя как в обычном поле — уводит фокус. Глубина ограничена пятью уровнями.

Тип блока

Уровни: 0, 1, 1, 0, 1, 2, 0

Пункты в значении: [{"text":"Планирование","level":0},{"text":"Собрать требования","level":1},{"text":"Согласовать сроки","level":1},{"text":"Разработка","level":0},{"text":"Ядро редактора","level":1},{"text":"Тесты в браузере","level":2},{"text":"Релиз","level":0}]

Входные параметры

ИмяТипПо умолчаниюОписание
v-modelC3EditorValueundefinedДокумент редактора: { version, time, blocks: [{ id, type, data }] }. version — версия схемы для миграций, time — время последней правки. Пустое значение показывается как один пустой абзац.
placeholderstringundefinedПодсказка, пока документ пуст.
size'sm' | 'md' | 'lg'undefinedРазмер поля: отступы и размер шрифта. Если не задан, берётся из C3FormField.
minHeightnumber | stringundefinedМинимальная высота области текста. Число трактуется как пиксели, строка передаётся в CSS как есть.
readOnlybooleanfalseЗапрещает правку, оставляет текст доступным для чтения, выделения и навигации по блокам.
disabledbooleanfalseОтключает поле.
autofocusbooleanfalseСтавит фокус в первый блок после монтирования. Не работает вместе с disabled.
requiredbooleanfalseМаркер обязательности, добавляет aria-required. Учитывается и значение из C3FormField.
errorbooleanfalseПризнак ошибки: красная рамка и aria-invalid. Ошибка из C3FormField имеет приоритет.
namestringundefinedИмя поля для нативной формы: документ уходит в скрытом input строкой JSON.
toolsC3BlockTool[][paragraphTool]Инструменты типов блоков: по ним строится реестр, порядок определяет порядок в тулбоксе. Контракт и правила — в src/runtime/utils/_editor_tools.ts, инструменты — в src/runtime/editor/tools/. По умолчанию: paragraph, header, quote, list, raw, delimiter. Инструменты без правки текста (разделитель, а позже картинка и таблица) объявляют editable: false.
ariaLabelstringundefinedДоступное имя группы. Если не задано, берётся подпись из C3FormField.

Настройки типа блока (Tunes)

Набор полей объявляет инструмент в поле tunes, а рисует их редактор готовыми компонентами. Набор фиксированный: select, switch, number, text. Произвольный интерфейс настройки не рисуется.

  • Кнопка настроек появляется у блока, если у его типа есть непустой tunes, и скрыта в режиме readOnly и disabled.
  • Диалог один на редактор и правит копию данных блока: пока Отмена не нажата, документ не меняется и история не пополняется.
  • Значения приводятся к типам полей: числовой уровень заголовка остаётся числом, переключатель — булевым, select берёт значение из своих вариантов.
  • Значение вне списка вариантов и поля вне набора инструмента в данные не попадают.
  • Применение попадает в историю: Ctrl+Z возвращает прежние значения.
  • У полей number границы min/max удерживаются при записи.

События

  • update:modelValue — payload C3EditorValue. Документ меняется на каждый ввод и на каждую структурную правку.
  • focus — без payload. Блок получил фокус.
  • blur — без payload. Редактор потерял фокус.

Описание

C3Editor — поле формы для структурированного текста: документ состоит из блоков, каждый блок — абзац. Подключается как любое другое поле: те же size, error, label, required, name и валидация, что у C3Input и C3Textarea.

Ядро ввода собственное, без внешних редакторов. Браузер не вставляет собственную разметку: Enter, Backspace на границе блока и вставка перехватываются, а правки идут через состояние документа. Каретка хранится как смещение в тексте блока, поэтому отмена возвращает ровно то состояние, которое было до правки.

План развития и набор инструментов — в docs/editor.md. Сейчас реализован первый этап: абзацы, ввод с клавиатуры, вставка из буфера обмена, история правок и работа в форме.

Дополнительная информация

Клавиатура

  • Enter — разделить блок по каретке. В пустом блоке блок удаляется, а каретка уходит в конец предыдущего. У списка Enter добавляет пункт, а не создаёт блок.
  • Backspace в начале блока — слить с предыдущим; в середине — обычное удаление символа.
  • Delete в конце блока — забрать следующий блок.
  • Ctrl+Z и Ctrl+Shift+Z либо Ctrl+Y — отмена и повтор. Правки набора одного блока склеиваются в один шаг.
  • ↑ и ↓ — переход между блоками; Home и End — края блока; с Ctrl — края документа.
  • Tab — увести фокус из редактора; в начале блока открывает тулбокс с типами; в начале пункта списка вкладывает его на уровень глубже. Между блоками перемещение только стрелками.
  • Shift+Tab — уйти из редактора назад, в том числе из пустого блока с открытым тулбоксом; в начале пункта списка выносит его на уровень выше.

Начертания

  • Хранятся интервалами в data.marks: { type: 'bold', from: 7, to: 10 }. Не HTML и не деревом токенов — каретка остаётся смещением в строке.
  • Формат версии 2. Документы версии 1 открываются как есть: отсутствие поля означает «без начертаний».
  • У списка начертания лежат в пунктах, потому что текст разложен по ним; блок спрашивает инструмент через readMarks и applyMark.
  • Пересечение с выделением разрезает интервал, повторное применение снимает, а набор текста сдвигает интервалы вместе с текстом.
  • Деление блока и склейка режут и сдвигают интервалы, поэтому форматирование переживает Enter и Backspace.
  • Набор начертаний объявляет инструмент в поле marks: у кода он пустой, и панель к нему не рисуется.
  • Ссылка хранит адрес в самом интервале: { type: 'link', from: 0, to: 5, href: 'https://…' }. Пустой адрес или нерабочая схема (javascript:, data:) снимают ссылку, а не открывают код.
  • Ссылку рисуют кнопкой панели или Ctrl+K: открывается диалог ввода адреса. Повторное применение меняет адрес, пустое поле снимает ссылку, а края частично перекрытой ссылки сохраняют свой адрес.

Список и вложенность

  • Пункт — это { text, level }, а данные хранятся плоско: level у каждого пункта, дерево собирается при отрисовке.
  • Старый формат items: string[] читается как пункты верхнего уровня, поэтому документы, сохранённые до появления вложенности, открываются как есть.
  • Enter добавляет пункт на уровне текущего, а не верхнего. Backspace в начале пустого пункта удаляет его.
  • Уровень не перескакивает глубже открытого: пункту без родителя не на чем висеть, поэтому он остаётся там, где был.
  • Потолок вложенности — MAX_LEVEL (уровень 4, то есть пять уровней). Значение за границей приводится к нулю.
  • Отступ вложенного списка задаёт его уровень, а не глубина DOM: списки вложены друг в друга, и отступ от предка накапливался бы.

Вставка и IME

  • Одиночный абзац вставляется в текущий блок по каретке, краевые пробелы сохраняются.
  • Несколько абзацев вставляются отдельными блоками, текст после каретки становится последним из них.
  • Форматирование из буфера обмена не переносится: используется только текст.
  • Во время ввода через IME (китайский, японский, корейский) блоки не делятся и история не пополняется — правка фиксируется по окончании композиции.

Доступность

  • Корень — группа с подписью из ariaLabel или из C3FormField.
  • Каждый блок — role="textbox" с названием типа блока для скринридера.
  • Активный блок доступен с клавиатуры (tabindex="0"), остальные — -1: Tab переходит между полями формы, стрелки — между блоками.
  • При ошибке добавляется aria-invalid, при обязательности — aria-required, в режиме чтения — aria-readonly.

Прочее

  • Слотов нет: содержимое формируют инструменты блоков.
  • На сервере блоки отдаются только для чтения, contenteditable появляется после монтирования — разметка детерминирована и совпадает при гидрации.
  • Чужая разметка, если её всё же вставил браузер, вычищается при потере фокуса: состояние документа остаётся источником правды.
  • Стили только через --c3-*, поэтому тёмная тема и RTL работают без отдельной поддержки.
  • Размер меняет пропорционально отступы и кегль, а высота следует за содержимым: фиксированной высоты у поля нет, нижнюю границу задаёт minHeight.
  • Разметку блока рисует инструмент типа (render), а данные читает через save при потере фокуса — попутно вычищая разметку, вставленную браузером. Невалидные данные помечаются, но не выбрасываются.
×