Слияние ​

Если во время вашего редактирования сохранились другие изменения, объедините обе стороны относительно начальной версии. Всё, что можно объединить автоматически, применяется сразу; конфликтами остаются лишь пересечения. Это отдельное окно nabi-note/merge, а не wing.

СлеваВ центреСправа
Версия начала редактированияВаш текущий документ после автоматического слиянияВерсия, сохранённая во время редактирования
Только чтениеМожно редактироватьТолько чтение

В центре можно менять текст, форматирование, таблицы, списки и изображения как в обычном редакторе. Доступные функции определяют переданные wings. Метки и состояния конфликтов не попадают в сохранённый текст.

Полностью автоматическое слияние ​

Вы изменили время встречи на 11:00, другой человек — место на переговорную на 3-м этаже. Изменены разные абзацы, поэтому центральный результат содержит обе правки.

Без оставшихся конфликтов можно сразу нажать Подтвердить или сначала доработать текст в центре. Итоговый документ ниже — фактический результат, полученный callback подтверждения.

Загрузка...

Частично автоматическое слияние ​

Теперь вы изменили время на 11:00, другой человек — на 14:00. Разные правки одного абзаца оставляют время конфликтным. Ваше изменение ведущих на Чимина и Соён и новое место переговорная на 3-м этаже применяются автоматически.

Разрешите конфликт одним из способов:

  • Слева >>: принять соответствующий фрагмент начальной версии.
  • Справа <<: принять фрагмент новой сохранённой версии.
  • Разрешить с текущим содержимым: оставить или изменить центр и отметить конфликт решённым.

Измените предложение со временем в центре и нажмите Разрешить с текущим содержимым. Подтверждение доступно только после разрешения всех конфликтов.

Загрузка...

Повторное редактирование решённого участка сохраняет его статус. Отмена разрешения отменяет и статус, повтор восстанавливает его. Одно удаление или редактирование текста не разрешает открытый конфликт автоматически.

Правила автоматического слияния и конфликтов ​

Сравниваются изменения от начального документа к вашему и от той же начальной версии к новой. Односторонние и одинаковые с обеих сторон изменения применяются автоматически, как и правки разных абзацев или строк.

Как в текстовом слиянии Git, пересечения определяются по абзацам и строкам. Даже разные слова, изменённые в одной строке, остаются конфликтом. Разные вставки в одной позиции и редактирование удалённого другой стороной содержимого также требуют решения.

Если обе стороны по-разному изменили структурный блок — таблицу, список или плавающую область, — конфликт охватывает всю структуру. Ячейки и пункты не смешиваются произвольно: выберите содержимое или исправьте центр. Пересекающиеся перемещения и правки с ненадёжно определяемой позицией тоже не применяются автоматически.

Подключение отдельного окна слияния ​

Передайте сохранённый NABI TREE JSON или HTML-строки в base, local, incoming. Используйте wings и параметры исходного редактора. mountMerge() создаёт центральный редактор и панель, обе исходные версии, выбор конфликтов и кнопку подтверждения.

ts
import { wings } from 'nabi-note'
import { mountMerge } from 'nabi-note/merge'
import 'nabi-note/nabi.css'

const merge = mountMerge({
  root: document.querySelector('#merge')!,
  base: editingStartedDocument,
  local: myEditedDocument,
  incoming: latestDocument,
  wings: wings().allBasic().build(),
  locale: 'ru',
  incomingVersion: latestVersion,
  async onConfirm({ json, html, incomingVersion, signal }) {
    return saveDocument({ json, html, expectedVersion: incomingVersion, signal })
  },
  onError(error) {
    showError(error)
  },
})

onConfirm получает JSON, HTML для публикации, ревизию редактирования и incomingVersion полностью разрешённого текущего результата. Возврат false не завершает подтверждение. Пока ожидается Promise, редактирование, выбор конфликтов и повторное подтверждение блокируются. Демонтаж отменяет signal результата. При корейском IME-вводе подтверждение ждёт окончания композиции.

Свои действия, например выбор изображений или загрузки, подключайте как в обычном редакторе. toolbarOptions принимает панели и действия хоста, surfaceOptions — параметры поверхности редактирования.

incomingVersion возвращает переданное сервисом значение без изменений. При сохранении сервис сравнивает его с последней версией сервера. Если за время слияния сохранилась новая версия, загрузите свежий документ и начните новое слияние, не перезаписывая его устаревшим результатом.

Состояние можно читать, а подтверждение вызывать и вне окна.

ts
const stop = merge.session.onChange((state) => {
  updateRemainingCount(state.unresolvedCount)
})

const canConfirm = merge.session.getState().canConfirm
const confirmed = await merge.confirm()

stop()
merge.unmount()

Читайте результат через merge.nabi.getJson() и merge.nabi.getHtml(). Не сохраняйте DOM редактора или getEditorHtml(). Для переноса лишь выбранных прежних фрагментов между двумя версиями используйте слияние по клику в Сравнении.

Подтверждение после загрузки файлов ​

Подключите wing upload и mountUpload() для кнопки файла, вставки и перетаскивания. sendUpload отправляет файлы серверу и возвращает { uri }. Полный пример подключения и прогресса — в Загрузке файлов.

С помощью merge.session.hold() отложите подтверждение и выбор конфликтов на время асинхронной работы, например загрузки, ещё не отражённой в документе. Вызовите возвращённую функцию при завершении, отмене или демонтаже. Снимайте задержку после вставки в документ, не сразу после отправки серверу, чтобы файл попал в итог.

ts
import { mountUpload, wings, type UploadMount } from 'nabi-note'
import { mountMerge } from 'nabi-note/merge'

let upload: UploadMount | undefined
let releaseUpload: (() => void) | undefined

const merge = mountMerge({
  root,
  base,
  local,
  incoming,
  wings: wings().allBasic().use('upload').build(),
  locale: 'ru',
  incomingVersion: latestVersion,
  toolbarOptions: { onFiles: (files) => upload?.take(files) },
  surfaceOptions: { fileSink: (files) => upload?.take(files) },
  onConfirm: ({ json, html, incomingVersion, signal }) =>
    saveDocument({ json, html, expectedVersion: incomingVersion, signal }),
})

upload = mountUpload({
  nabi: merge.nabi,
  root: merge.content,
  locale: 'ru',
  uploader: sendUpload,
  onStart() {
    releaseUpload = merge.session.hold()
  },
  onDone() {
    releaseUpload?.()
    releaseUpload = undefined
  },
})

function dispose() {
  upload?.unmount()
  releaseUpload?.()
  merge.unmount()
}

merge.content — центральный DOM редактирования; merge.surface и merge.toolbar уже подключены. Не монтируйте там вторую поверхность или панель. Сначала демонтируйте дополнительные загрузки и индикаторы, затем вызовите merge.unmount().