
Как мы сделали свой плагин таблицы для Strapi
Когда готовых инструментов не хватает, остаётся два пути: страдать или делать своё. Мы выбрали второе и собрали плагин с таблицей, изображениями, форматированным текстом и нормальной работой с большими объёмами данных.
Во многих проектах Mish мы используем Strapi — гибкую систему управления контентом, которую можно адаптировать под конкретный продукт. После запуска редакторы и менеджеры могут самостоятельно работать с текстами, визуалом и другими материалами.
В одном из проектов стандартных возможностей Strapi оказалось недостаточно. Нам понадобилась таблица, где можно добавлять строки и столбцы, редактировать текст и прикреплять изображения без работы с JSON. Готового решения мы не нашли, поэтому сделали собственный плагин.
В этой статье мы расскажем, как его разработали, подключили библиотеку файлов Strapi, выбрали текстовый редактор и адаптировали таблицу под большой объём данных. Если вашему продукту тоже не хватает готовых решений, приходите в Mish. Поможем найти и реализовать своё.
С чего всё началось
Первая задача была довольно конкретной: найти табличное поле для Strapi.
Мы посмотрели существующие решения, в том числе sensinum-strapi-table-field. Но в этом случае данные приходилось бы редактировались как JSON. Для разработчика такой формат ещё может быть приемлемым, но для контент-менеджера это уже лишний уровень сложности.
Кроме того, нам требовалось хранить в ячейках не только текст, но и изображения. Расширять существующее решение под такой сценарий оказалось бы не намного проще, чем написать собственное.
С точки зрения продукта здесь тоже есть важный момент. Система управления контентом обычно внедряется именно для того, чтобы бизнес мог менять наполнение сайта без участия разработчиков. Если для редактирования обычной таблицы пользователю приходится разбираться в структуре JSON, часть ценности CMS просто исчезает.
Поэтому решили сделать своё расширение.
Создаём структуру плагина
Для начала нужно было разобраться, как вообще разрабатываются расширения для Strapi.
Официальная документация дала базовое понимание механики пользовательских полей, но для практической реализации нам больше помогли примеры с готовым кодом и структурой проекта.
Создаём расширение:
npx @strapi/sdk-plugin init src/plugins/strapi-table
После генерации получаем примерно такую структуру:
src/plugins/strapi-table/
├── admin/
│ ├── src/
│ │ ├── components/
│ │ ├── pages/
│ │ │ └── App.tsx
│ │ ├── translations/
│ │ │ └── en.json
│ │ ├── index.ts
│ │ └── pluginId.ts
│ ├── tsconfig.json
│ ├── tsconfig.build.json
│ └── custom.d.ts
├── server/
│ ├── src/
│ │ ├── config/
│ │ ├── content-types/
│ │ ├── controllers/
│ │ ├── middlewares/
│ │ ├── policies/
│ │ ├── routes/
│ │ ├── services/
│ │ ├── bootstrap.ts
│ │ ├── destroy.ts
│ │ ├── index.ts
│ │ └── register.ts
│ ├── tsconfig.json
│ └── tsconfig.build.json
├── package.json
├── README.md
├── tsconfig.json
├── .editorconfig
├── eslint.config.mjs
└── .prettierrc
Здесь всё довольно привычно.
admin отвечает за интерфейс плагина и его работу внутри панели Strapi. В admin/src/index.ts регистрируется клиентская часть, а в components лежат React-компоненты интерфейса.
server отвечает за серверную часть. Здесь находятся контроллеры, службы, маршруты, правила доступа, промежуточные обработчики и обработчики жизненного цикла вроде register, bootstrap и destroy
После создания расширение нужно подключить к основному приложению Strapi.
В config/plugins добавляем:
table: {
enabled: true,
resolve: './src/plugins/strapi-table'
}
Во время разработки у нас постоянно открыты два терминала:
- приложение Strapi;
- само расширение в режиме отслеживания изменений.
Для второго используем:
npm run watch
Если всё подключено правильно, в боковой панели Strapi появляется новое расширение.
На этом окружение готово, и можно переходить к интерфейсу.
Первый прототип
Для быстрого прототипа мы использовали компоненты системы оформления Strapi.
Главная задача первой версии была не в том, чтобы сделать идеальный интерфейс. Нужно было проверить саму идею: можем ли мы представить данные как нормальную таблицу и редактировать их визуально, без JSON.
Получилось.
Но быстро проявилась следующая проблема.
Небольшая таблица выглядела нормально. А если представить сотни строк, длинный текст и изображения внутри ячеек, интерфейс начинал сильно разрастаться. Таблица становилась визуально тяжёлой и неудобной в работе.
Поэтому после проверки концепции мы решили не развивать прототип дальше, а переработать интерфейс и заодно привести код в порядок.
Как получить библиотеку файлов внутри своего расширения
Одна из важных функций таблицы — возможность добавлять изображения прямо в ячейки.
В обычном Strapi пользователь уже умеет это делать через стандартное окно библиотеки файлов. Делать ещё один собственный инструмент загрузки означало бы дублировать знакомый интерфейс и усложнять поддержку.
Логичнее было использовать уже существующий компонент Strapi.
Только напрямую импортировать это окно из системы оформления нельзя. Оно находится внутри самого Strapi.
Для доступа к внутренним компонентам можно использовать общий хук:
import { useStrapiApp } from '@strapi/strapi/admin';
Получаем компоненты:
const components = useStrapiApp(
'MediaPicker',
(state) => state.components
);
Дальше достаём библиотеку файлов:
const MediaLibrary = components['media-library'];
И здесь начинается более интересная часть. TypeScript сообщает нам примерно следующее:
string |
number |
boolean |
ReactElement> |
Iterable |
ReactPortal |
null |
undefined
То есть компонент мы получили, но из типов непонятно, какие именно свойства он ожидает.
Можно идти в исходный код Strapi и искать реализацию компонента. А можно воспользоваться особенностями JavaScript:
console.log(String(MediaLibrary));
Так мы получим строковое представление функции и сможем увидеть её аргументы:
({
onClose,
onSelectAssets,
allowedTypes = [
"files",
"images",
"videos",
"audios"
],
multiple = true,
initiallySelectedAssets = []
}) => {
...
}
Для нашей задачи важны:
- onClose
- onSelectAssets
- allowedTypes
- multiple
- initiallySelectedAssets
После этого компонент уже можно использовать внутри расширения.
Например:
setOpenModal(false)}
onSelectAssets={(image: Image['image'][]) => {
setOpenModal(false);
return handleAddImage(image);
}}
allowedTypes={['images']}
multiple={false}
/>
Для прототипа можно привести компонент к any:
const MediaLibrary = components['media-library'] as any;
Это не самый строгий вариант с точки зрения TypeScript, зато в нашей ситуации он позволил работать с внутренним компонентом Strapi, для которого не хватало открытого описания типов.
Типизируем изображения
Для данных, которые возвращаются из библиотеки файлов, используем типы Strapi:
import { Modules } from '@strapi/strapi';
type Image = Modules.Documents.Result<'plugin::upload.file'> & {
width: number;
height: number;
url: string;
};
Фактически нас прежде всего интересуют:
type Image = {
[key: string]: any;
documentId: string;
id: string | number;
width: number;
height: number;
url: string;
};
Для нашей логики критичны width, height и url.
После этого стандартный сценарий загрузки изображений Strapi заработал и внутри нашей таблицы. Пользователь при этом работает с той же библиотеке файлов, которая используется в других частях системы.
Перерабатываем дизайн таблицы
После технического прототипа вернулись к внешнему виду.
Таблица из системы оформления Strapi подходила для проверки гипотезы, но не для нашего конечного сценария. Поэтому в качестве визуального ориентира взяли Google Sheets и адаптировали его подход под Strapi.
Все основные цвета оставили из тем Strapi, чтобы расширение не выглядело чужеродным внутри панели управления.
При этом часть управляющих элементов решили скрыть по умолчанию. Кнопки редактирования и добавления изображения появляются только при наведении.
Так в обычном состоянии пользователь видит прежде всего контент, а не набор элементов управления.
Особенно это важно для больших таблиц. Чем больше строк и колонок появляется в интерфейсе, тем сильнее постоянно отображаемые кнопки увеличивают визуальный шум.
Добавляем текстовый редактор
С изображениями разобрались. Следующая задача — текст.
Здесь было два основных варианта:
- Использовать richtext из Strapi.
- Подключить отдельный редактор.
Мы выбрали второй путь.
На этапе поиска рассматривали:
- CKEditor;
- React Quill New;
- Editor.js;
- TinyMCE.
В итоге остановились на React Quill.
Нам не нужен был сложный инструмент для подготовки публикаций с десятками типов блоков. Требовался компактный редактор с базовыми возможностями форматирования, который можно встроить непосредственно внутрь ячейки таблицы.
React Quill хорошо подошёл под этот сценарий.
Его также стилизовали под цвета Strapi, поэтому редактор визуально продолжает интерфейс системы, а не выглядит отдельным сторонним приложением.
В итоге пользователь может редактировать форматированный текст прямо внутри конкретной ячейки.
Удаление и очистка строк
В первом прототипе строка удалялась через отдельный крестик.
После переработки механику поменяли.
Теперь пользователь нажимает на ячейку с порядковым номером, после чего вся строка выделяется.
Дальше доступны два действия с клавиатуры:
- Delete — удалить строку
- Backspace — очистить строку
Так работа с таблицей становится ближе к привычным электронным таблицам и настольным программам.
Сам по себе такой сценарий кажется мелочью, но именно из подобных деталей складывается скорость работы с системой. Если сотрудник регулярно заполняет десятки или сотни строк, даже одно лишнее действие начинает повторяться сотни раз.
Работа с заголовками колонок
Шапка таблицы тоже редактируемая.
Пользователь может:
- изменить название колонки;
- удалить колонку;
- добавить иконку;
- расположить иконку слева или справа от текста;
- удалить уже добавленную иконку.
После добавления изображения соответствующее действие автоматически появляется в выпадающем меню заголовка.
То есть структура таблицы не зафиксирована разработчиком раз и навсегда. Часть её можно менять непосредственно через интерфейс.
Для бизнеса это означает чуть меньшую зависимость от команды разработки при обычных изменениях контента. Если нужно поменять название колонки или визуально обозначить категорию, для этого не требуется ставить новую задачу разработчику.
Вставка данных из Google Sheets и Google Docs
Ещё один сценарий появился из реального способа работы с контентом.
Данные далеко не всегда появляются сразу внутри системы управления контентом. Часто команды сначала собирают их в Google Sheets или Google Docs, а уже потом переносят в систему.
Поэтому мы добавили поддержку вставки таблиц из Google Sheets и Google Docs.
Но обычного копирования и вставки было недостаточно.
Например, пользователь может скопировать диапазон на 1000 строк, хотя фактически данные находятся только в нескольких из них.
Если перенести диапазон как есть, в Strapi появятся сотни пустых ячеек.
Поэтому при вставке автоматически удаляем:
- пустые строки между заполненными данными и все пустые строки после них;
- пустые столбцы и ненужные пустые колонки после заполненной области.
Вставка начинается из верхней левой ячейки таблицы.
При этом заголовок исходной таблицы не переносится напрямую. Для новых колонок используется стандартное название «Новая колонка».
Таким образом, можно быстро перенести массив данных из рабочей электронной таблицы и не тянуть вместе с ним лишнюю структуру.
Для разработчика это сравнительно небольшая функция. Для пользователя системы — возможность не переносить контент по одной ячейке вручную.
Что получилось
В итоге мы сделали минимальную рабочую версию расширения с таблицей для Strapi, которая позволяет:
- редактировать данные визуально вместо работы с JSON;
- добавлять изображения через стандартную библиотеку файлов Strapi;
- работать с форматированным текстом;
- управлять строками и колонками;
- добавлять изображения в заголовки;
- вставлять данные из Google Sheets и Google Docs;
- автоматически очищать вставляемый диапазон от лишних пустых строк и столбцов.
Но важнее здесь не количество функций.
Основная задача была в том, чтобы технически сложную структуру данных превратить в понятный интерфейс для человека, который работает с контентом каждый день.
Для нас это хороший пример того, зачем иногда имеет смысл расширять систему управления контентом, а не пытаться любой ценой уложить рабочий процесс в стандартный набор полей.
Если ограничение интерфейса заставляет сотрудников постоянно обращаться к разработчикам, редактировать JSON или выполнять однотипную ручную работу, технически более сложное собственное решение может в итоге сделать продукт проще.
Расширение уже доступно в открытом доступе
Мы выложили наше решение в npm:@mish.dev/strapi-table
И на GitHub: github.com/mish-design/strapi-table
Текущая версия — не финальная точка. В планах добавить перетаскивание элементов, очистку всей таблицы, удаление всей таблицы и доработать действия с клавиатуры для строк и колонок.
Но базовую задачу расширение уже решает: вместо JSON внутри Strapi появляется полноценный визуальный инструмент для работы с табличным контентом.
Если вы дочитали до этого места и всё ещё не уверены, нужен ли вам свой плагин, другая система или вообще что-то третье, приходите в Mish. У нас много сильных разработчиков и экспертизы, чтобы разобраться в задаче, не напланировать лишнего и собрать решение, которое будет работать.
Ещё статьи
Предыдущая статья
Как подобрать респондента для диджитал-исследования
Исследования, аналитика данных, обработка результатов и интеграция полученной информации в процессы — все это может оказаться бесполезной тратой ресурсов, если на начальном этапе были выбраны неправильные респонденты. Как не прогадать с выбором? Кого и когда опрашивать? Как наладить контакт? Разберемся вместе с экспертами команды Mish.

Следующая статья
С мыслями о бесконечном и вечном: как сделать проект, который переживет все тренды
Все хотят быть в тренде — люди, бизнесы, крупные компании. Хочется быть одновременно на хайпе, а еще на чилле и расслабоне. Но вот это пребывание в моменте может очень лихо сыграть с бизнесом и привести к потере себя. Чтобы этого избежать, мы в своей работе используем форкастинг (future-casting) — не просто прогнозирование, а целостный подход к проектированию, который позволяет создавать решения, актуальные сегодня, завтра и в обозримом будущем. Рассказываем, как форкастинг помогает нам создавать по-настоящему устойчивые и эффективные проекты.


