У редакторі вже може існувати один список доступних типів блоків: текст, зображення, код, цитата, кнопка та інші.
Спочатку цей список використовує одна кнопка "Додати блок". Потім з’являється нова зручність: вставити блок між двома вже існуючими блоками.
Найпростіша реалізація здається очевидною - створити для кнопки вставки між блоками ще один масив доступних типів.
Саме тут починається проблема.
Два однакові списки майже гарантовано розійдуться з часом
const toolbarBlocks = [ 'text', 'image', 'code', 'quote',]; const insertBetweenBlocks = [ 'text', 'image', 'code',];На старті обидва масиви можуть бути однаковими.
Через місяць хтось додасть новий блок у панель інструментів і забуде додати його в меню вставки між блоками. Ще пізніше один список отримає новий порядок, іконку або обмеження доступності.
UI починає виглядати так, ніби продукт підтримує різні можливості в різних місцях, хоча бізнесова дія одна й та сама.
Реєстр можливостей повинен описувати саму дію, а не конкретну кнопку
Краще мати один реєстр, який відповідає на питання: "які блоки редактор уміє створювати?".
Панель інструментів, кнопка між блоками, палітра команд або контекстне меню лише читають цей реєстр і вирішують, як показати ті самі можливості у своєму інтерфейсі.
export const blockRegistry = [ { type: 'text', labelKey: 'blocks.text', icon: TextIcon, create: () => ({ type: 'text', data: {}, }), }, { type: 'image', labelKey: 'blocks.image', icon: ImageIcon, create: () => ({ type: 'image',Нова точка входу повинна перевикористовувати реєстр, а не копіювати його
const availableBlocks = computed(() => blockRegistry); const insertBetween = ( index, definition,) => { const block = definition.create(); blocks.value.splice( index + 1, 0, block, );};Тут нова кнопка знає лише позицію вставки.
Вона не вирішує, які типи існують, як створюються початкові дані або яка іконка належить блоку. Це вже відповідальність реєстру.
Реєстр можливостей ширший за звичайний список типів
У міру розвитку редактора одному type стає недостатньо.
Реєстр може містити:
- назву й іконку;
- фабрику початкових даних;
- групу в меню;
- умову доступності;
- чи можна вставляти блок між іншими блоками;
- чи дозволений він у конкретному режимі редактора.
Якщо нова точка входу має обмеження, фільтруйте реєстр, а не створюйте новий
const insertBetweenBlocks = computed(() => blockRegistry.filter( (block) => block.canInsertBetween !== false, ) );Це важлива різниця.
Окрема точка входу може показувати не всі можливості, але вона все одно повинна починати з того самого канонічного набору.
Тобто ми додаємо правило відбору, а не друге джерело правди.
Однакова дія повинна використовувати не тільки один список, а й один шлях створення
Навіть якщо дві кнопки читають один масив типів, дублювання може залишитися в іншому місці.
Наприклад, toolbar викликає фабрику з реєстру, а insert-between вручну створює об’єкт блока.
Тоді при додаванні нового поля в початкові дані одна точка входу отримає правильний блок, а інша - застарілий.
Спільний реєстр не означає однаковий інтерфейс
Панель інструментів може показувати іконки без тексту. Меню вставки між блоками може показувати іконку, назву й короткий опис. Палітра команд може додавати пошук.
Це нормально.
Спільним повинне бути знання про можливість, а не спосіб її візуального представлення.
Контекстні правила теж краще зберігати поруч із capability
{ type: 'image', canUse: ({ settings, readonly, }) => settings.imagesEnabled && ! readonly,}Якщо правило "зображення вимкнені" перевіряється окремо в панелі інструментів і окремо в меню вставки між блоками, воно теж з часом може розійтися.
Краще, щоб обидва елементи інтерфейсу запитували одне й те саме визначення можливості.
Що варто перевірити тестами
1. Toolbar and insert-between use the same registry.2. Adding a new block type makes it appear in both entry points.3. A capability disabled by settings disappears from both.4. Entry-point-specific filters do not mutate the registry.5. Both entry points use the same block factory.6. Initial block data is identical regardless of where creation started.7. Reordering registry items changes both menus consistently.8. One entry point can render different UI without duplicating capability data.Цей принцип працює далеко за межами редактора блоків
Той самий принцип корисний для:
- команд у панелі інструментів і палітрі команд;
- дій у рядку таблиці та масових дій;
- створення сутності з кнопки в заголовку та порожнього стану;
- контекстного меню й основної панелі;
- гарячих клавіш і звичайних кнопок.
Якщо всі ці точки запускають ту саму можливість, вони повинні перевикористовувати спільне визначення цієї можливості.
Нова точка входу інтерфейсу не повинна створювати нове джерело правди
Коли додається нова кнопка або меню, головне питання не "який список показати?", а "де вже описана ця можливість?".
Якщо дія та сама, її реєстр можливостей, фабрика створення і правила доступності теж повинні залишатися спільними.
Другий спосіб зробити ту саму дію має бути новим входом у стару можливість, а не другою реалізацією цієї можливості.
Нова точка входу інтерфейсу повинна додавати новий спосіб доступу, а не новий список правди.



