Написання додатків (плагінів)
Додатки додають функції до Antimatter зсередини: серверна частина на Go, яка підключається до сервера, і веб-додаток на React, який додає меню, панелі, типи повідомлень та сторінки. На цій сторінці показано, як організовано додаток, на прикладі вбудованого додатка «Опитування», і які зміни відбуваються в інтерфейсі Fusion Antimatter.
Як працює додаток
- Серверна частина — це програма на Go. Сервер запускає її як окремий процес і спілкується з нею через RPC: він викликає хуки додатка (повідомлення збирається опублікувати, виконано команду…) і додаток викликає API плагінів сервера (створити запис, прочитати користувача, зберегти дані…). Вона також може надавати HTTP-маршрути під
/plugins/<app id>/. - Веб-додаток — це JavaScript-пакет, який завантажується в браузер разом із веб-додатком. Він реєструє компоненти React та колбэки в реєстрі: кнопку в меню, панель, спосіб відображення типу повідомлення.
- Обидві частини є необов’язковими. Додаток встановлюється як пакет
.tar.gz, а системні адміністратори вмикають, вимикають та налаштовують його.
Antimatter запускає додатки, написані для Mattermost, і навпаки, за умови, що додаток використовує лише хуки, які є в обох. Див. Хуки, які є лише в Antimatter.
Структура пакета
Окремого стартового шаблона немає: почніть із невеликого вбудованого додатка, наприклад antimatter-plugin-polls або antimatter-plugin-todo, і змініть його ID та код. Додаток «Опитування» організовано так:
antimatter-plugin-polls/
├── plugin.json the manifest
├── Makefile build, test, bundle, deploy
├── build/ the manifest and pluginctl helper tools (Go), setup.mk
├── assets/icon.svg files shipped with the bundle
├── go.mod
├── server/
│ ├── main.go plugin.ClientMain(&Plugin{})
│ ├── plugin.go the Plugin type, OnActivate, ServeHTTP
│ ├── manifest.go generated from plugin.json
│ ├── api.go, command.go, hooks.go, store.go …
│ └── dist/ compiled binaries, one per platform
└── webapp/
├── package.json, webpack.config.js, tsconfig.json
├── i18n/ en.json and one file per language
├── src/index.tsx registers the app with the web app
├── src/manifest.ts generated from plugin.json
├── src/translations.ts
└── dist/main.js the bundle
Необов’язково: папка public/, яка надається як є під /plugins/<app id>/public/.
plugin.json
{
"id": "com.example.weather",
"name": "Weather",
"description": "Forecasts in the chat.",
"homepage_url": "https://github.com/example/weather",
"support_url": "https://github.com/example/weather/issues",
"icon_path": "assets/icon.svg",
"version": "1.0.0",
"min_server_version": "12.0.0",
"server": {
"executables": {
"linux-amd64": "server/dist/plugin-linux-amd64",
"linux-arm64": "server/dist/plugin-linux-arm64",
"darwin-amd64": "server/dist/plugin-darwin-amd64",
"darwin-arm64": "server/dist/plugin-darwin-arm64",
"windows-amd64": "server/dist/plugin-windows-amd64.exe"
}
},
"webapp": {"bundle_path": "webapp/dist/main.js"},
"settings_schema": {"settings": []}
}
id: унікальний і незмінний; зазвичай використовується ім’я домену у зворотному порядку. Він визначає маршрути, збережені налаштування та події WebSocket.version: семантична версія. Вбудовані додатки не вказують її вplugin.json; їхній інструмент збірки бере її з останнього git-тегуv*.min_server_version: Antimatter повідомляє12.0.0.settings_schema: сторінка налаштувань додатка в Системній консолі (див. Налаштування).
make apply копіює маніфест у server/manifest.go та webapp/src/manifest.ts; make manifest-check перевіряє його.
Серверна частина
Серверна частина імпортує той самий Go-модуль, що й додатки Mattermost, github.com/mattermost/mattermost/server/public, з проксі Go-модулів. Її main лише запускає додаток:
package main
import "github.com/mattermost/mattermost/server/public/plugin"
func main() {
plugin.ClientMain(&Plugin{})
}
Тип Plugin вбудовує plugin.MattermostPlugin, що надає йому p.API, та реалізує потрібні хуки:
type Plugin struct {
plugin.MattermostPlugin
router *mux.Router
}
func (p *Plugin) OnActivate() error {
p.router = p.newRouter()
return p.API.RegisterCommand(&model.Command{
Trigger: "weather",
AutoComplete: true,
AutoCompleteDesc: "Show the forecast",
AutoCompleteHint: "[city]",
})
}
func (p *Plugin) ExecuteCommand(c *plugin.Context, args *model.CommandArgs) (*model.CommandResponse, *model.AppError) {
city := strings.TrimSpace(strings.TrimPrefix(args.Command, "/weather"))
return &model.CommandResponse{
ResponseType: model.CommandResponseTypeEphemeral,
Text: "Forecast for " + city + ": sunny",
}, nil
}
func (p *Plugin) ServeHTTP(c *plugin.Context, w http.ResponseWriter, r *http.Request) {
p.router.ServeHTTP(w, r)
}
Загальні хуки
| Хук | Коли виконується |
|---|---|
OnActivate, OnDeactivate | Коли додаток вмикається або вимикається, а також при запуску та зупинці сервера. Реєструйте команди та ботів у OnActivate. |
OnConfigurationChange | Коли змінюються його налаштування; завантажуйте їх за допомогою p.API.LoadPluginConfiguration(&cfg). |
ServeHTTP | Запити до /plugins/<app id>/…. Сервер встановлює заголовок Mattermost-User-Id, коли запит надходить від користувача, який увійшов в систему; відхиляйте запити без нього, якщо вони не призначені для публічного доступу. |
ExecuteCommand | Коли виконується команда, зареєстрована додатком. |
MessageWillBePosted, MessageWillBeUpdated | Перед збереженням повідомлення: поверніть змінений пост або причину для його відхилення. |
MessageHasBeenPosted, MessageHasBeenUpdated, MessageHasBeenDeleted | Після факту, для побічних ефектів. |
ReactionHasBeenAdded, ReactionHasBeenRemoved | Реакції. |
UserHasJoinedChannel, UserHasLeftChannel, UserHasJoinedTeam, UserHasLeftTeam, ChannelHasBeenCreated | Зміни в учасниках та каналах. Команда — це спільнота. |
FileWillBeUploaded | Перед збереженням файлу: змініть його або відхиліть. |
OnPluginClusterEvent | Повідомлення від того самого додатка на іншому сервері кластера. |
Повний список з точними сигнатурами наведено в server/public/plugin/hooks.go; API — в server/public/plugin/api.go. Пакет pluginapi (server/public/pluginapi) обгортає API у зручніші допоміжні функції.
Корисні виклики API
CreatePost,UpdatePost,SendEphemeralPost: публікуйте від імені бота додатка або від імені користувача.EnsureBotUser(&model.Bot{Username, DisplayName}): створюйте бота додатка один раз і отримуйте його ID.PublishWebSocketEvent("poll_updated", payload, &model.WebsocketBroadcast{ChannelId: id}): надсилайте подію веб-додатку. Вона надходить якcustom_<app id>_poll_updated.OpenInteractiveDialog: відкриває діалог.LogInfo,LogErrorта інші: записує в журнал сервера, позначаючи запис ID додатка.GetUser,GetChannel,HasPermissionToChannel: перевіряйте, хто має право робити що, перед діями від імені користувача.
Додаток працює на кожному сервері кластера. Зберігайте стан у сховищі ключ-значення або в базі даних, а не лише в пам’яті.
Служба ШІ
Сервер Antimatter викликає моделі ШІ для додатків через з’єднання з моделями, які налаштував адміністратор (див. ШІ у посібнику адміністратора). Додаток ніколи не зберігає ключ провайдера: він вказує, від імені якої особи та де діє, а сервер перевіряє, чи має ця особа доступ до з’єднання в цьому місці, застосовує його обмеження та бюджети, рахує токени за особою та додатком і аудитує виклик без його вмісту.
| Метод | Призначення |
|---|---|
AIChat(actingUserID, channelID, teamID, *ai.ChatRequest) | Завершення чату: повідомлення, інструменти, структурований вихід (JSONSchema). |
AIChatStream(…) | Те саме, але потоково: читайте Events потоку, доки канал не закриється, або викличте Close, щоб зупинити виклик. |
AIEmbed(…, *ai.EmbedRequest) | Вбудовування (ембедінги) текстів. |
AITranscribe(…, *ai.TranscribeRequest) | Розпізнавання мовлення (перетворення мовлення на текст). |
AIDecide(…, *ai.DecideRequest) | Типізовані запити до моделі прийняття рішень (так або ні, вибір, оцінка) щодо тексту. |
AIConnectionsForUser(userID, channelID, teamID) | З’єднання, які особа може використовувати там, з їхніми моделями та можливостями, але ніколи з їхніми ключами. |
ConnectionID у кожному запиті вибирає з’єднання, а Model — одну з його моделей (порожнє значення для моделі за замовчуванням з’єднання). Operation — це коротка мітка для записів використання та аудиту, ніколи не вміст. Мінімальна версія сервера — 11.11.
actingUserID лише з джерела, яке сервер перевірив: заголовок Mattermost-User-Id запиту до вашого ServeHTTP або користувача з контексту гачка (автор публікації, користувач, який виконав команду). Ніколи не отримуйте його з повідомлення, навантаження вебхука, URL чи запиту іншого додатка: ваш додаток витрачатиме чужий доступ.Ці методи та пакет ai (server/public/ai) існують лише в копії модуля Antimatter, а не в версії, опублікованій Mattermost. Щоб ними скористатися, збирайте код проти server/public Antimatter, додавши рядок replace у ваш go.mod, наприклад replace github.com/mattermost/mattermost/server/public => ../antimatter/server/public. Такий додаток працюватиме лише на Antimatter.
Зберігання даних
Кожен додаток має власне сховище ключ-значення в базі даних сервера:
KVSet(key, value),KVGet(key),KVDelete(key): значення — це байти, зазвичай JSON. Ключі — не довше 150 символів.KVSetWithExpiry(key, value, seconds): значення зникає після зазначеного часу.KVCompareAndSet(key, old, new)таKVSetWithOptionsзAtomic: безпечні оновлення, коли кілька запитів або сервери записують один і той самий ключ. Опитування зберігає кожен голос саме так і повторює спробу в разі конфлікту.KVList(page, perPage): ключі, сторінка за сторінкою.
Веб-додаткова частина
Бандл реєструє клас за допомогою window.registerPlugin. Його initialize отримує реєстр та сховище Redux:
import manifest from './manifest';
class Plugin {
initialize(registry, store) {
registry.registerTranslations((locale) => translationsFor(locale));
registry.registerPostTypeComponent('custom_poll', PollPost);
registry.registerWebSocketEventHandler(`custom_${manifest.id}_poll_updated`, (msg) => {
store.dispatch(pollUpdated(JSON.parse(msg.data.poll)));
});
registry.registerReconnectHandler(() => store.dispatch(reloadPolls()));
}
}
window.registerPlugin(manifest.id, new Plugin());
- Спільні бібліотеки. Веб-додаток надає React, ReactDOM, Redux, React Redux та React Intl: вказуйте їх як
externalsу webpack (React,ReactDOM,Redux,ReactRedux,ReactIntl), замість того щоб включати власні копії. Дивітьсяwebpack.config.jsв Опитуваннях. - Виклики вашої серверної частини. Використовуйте
${window.basename}/plugins/<app id>/…з параметрами зClient4.getOptions()(зmattermost-redux/client): вони містять заголовки, які серверу потрібні, щоб прийняти запит від браузера. - Ваш власний стан.
registerReducerдодає редьюсер, який зберігається підstate['plugins-<app id>']. - Типи. Додаткові методи реєстру Antimatter відсутні в опублікованих типах Mattermost: Опитування декларує реєстр, який використовує, у
webapp/src/types/host.ts.
Методи реєстру та їхні параметри задокументовано в коді, у webapp/fusion/src/plugins/registry.ts (Fusion) та webapp/channels/src/plugins/registry.ts (класичний).
Підтримка інтерфейсу Fusion
Antimatter має два веб-інтерфейси: Fusion (типовий) та класичний. Обидва завантажують ті самі бандли додатків і мають той самий реєстр, але Fusion малює багато елементів сам і розміщує компоненти додатків по-своєму. Перевірте ваш додаток в обох.
Визначення інтерфейсу
Перед завантаженням будь-якого додатка обидва інтерфейси встановлюють window.antimatterWebUI у 'fusion' або 'classic', а також те саме значення на кореневому елементі сторінки як data-am-web-ui. У Mattermost жодне з них не встановлюється.
export function isFusionUI(): boolean {
return window.antimatterWebUI === 'fusion' || document.documentElement.dataset.amWebUi === 'fusion';
}
Вбудовані застосунки використовують це для відмінного відображення. Опитування малює свої картки та діалогове вікно створення у стилі Fusion і відкриває власне діалогове вікно для /poll у Fusion. Дзвінки та Голосові канали не відображають бічну панель та сповіщення-тости, які Fusion замінює власним інтерфейсом дзвінка. Застосунок Агентів розміщує свої дії ШІ в меню повідомлень як групу під Інші дії, замість панелі інструментів при наведенні. Власні класи CSS Fusion (з префіксом am-) та його шар діалогових вікон (елемент am-layer) — це те, що використовують ці застосунки для відповідності інтерфейсу; це не задокументований стабільний API.
Де Fusion відображає компоненти плагінів
| Метод реєстру | Де з’являється у Fusion |
|---|---|
registerAppBarComponent, registerChannelHeaderButtonAction | Панель додатків, стовпчик іконок на правому краю, між вашим аватаром і кнопкою Про Antimatter. Кнопки заголовка каналу також розміщуються там, а не у заголовку каналу. Приховується, коли системний адміністратор вмикає Вимкнути панель програм (ExperimentalSettings.DisableAppBar, вимкнено за замовчуванням). |
registerRightHandSidebarComponent | Панель праворуч. Користувачі можуть змінювати її розмір, перетягуючи край, і відкривати її в новому вікні. Якщо застосунок також є продуктом, панель пропонує Відкрити як повну сторінку. Див. Панель у новому вікні. |
registerChannelHeaderIcon | Іконки у заголовку каналу, після кнопок дзвінка; на телефонах — у меню Більше заголовка. |
registerCallButtonAction | Кнопка дзвінка у заголовку каналу; кілька провайдерів спільно використовують меню Інші способи здійснити дзвінок. |
registerPostDropdownMenuAction | Меню Дії з повідомленнями (⋯) повідомлення, у його підменю Інші дії; також у меню Дії з обговоренням панелі обговорення, під Інші дії. Fusion малює іконку застосунка перед кожною дією, сірою: власну іконку для вбудованих застосунків, інакше іконку, яку застосунок показує на рейці застосунків, інакше нейтральну. Елемент без тексту на початку тексту дії (іконку, яку застосунок малює сам) приховується. |
registerPostDropdownSubMenuAction | Група з заголовком у тому самому підменю Інші дії, після окремих дій. Жоден застосунок не отримує власного пункту в меню повідомлення. |
registerPostActionComponent | Панель інструментів повідомлення, що з’являється при наведенні, перед кнопкою ⋯. |
registerPostTypeComponent, registerPostMessageAttachmentComponent, registerPostHeaderComponent, registerPostWillRenderEmbedComponent | Саме повідомлення: його тіло, під текстом, після часу, та замість попереднього перегляду посилань. |
registerChannelHeaderMenuAction | Меню Параметри каналу (⋯ на каналі у бічній панелі або правий клік), як пункти з іконкою роз’єму. |
registerMainMenuAction | Меню спільноти (назва спільноти вгорі списку каналів), під Інші дії, згруповане за додатками. |
registerSidebarBrowseOrAddChannelMenuAction | Меню спільноти, після Створити канал та Створити категорію. |
registerFileUploadMethod | Меню + редактора (Додати до повідомлення), після Завантажити файл. |
registerPostEditorActionComponent | Панель форматування редактора, коли форматування відображається. |
registerAIActionMenuItemComponent | Меню ШІ редактора. |
registerFileDropdownMenuAction, registerFilePreviewComponent | Меню параметрів файлу та попередній перегляд файлу. |
registerUserSettings | Профіль і налаштування, одна вкладка на додаток під заголовком Плагіни. |
registerChannelSettingsTab | Діалогове вікно налаштувань каналу, під заголовком Додатки. |
registerPopoverUserAttributesComponent, registerPopoverUserActionsComponent | Картка профілю, що відображається, коли ви натискаєте на ім’я або аватар. |
registerLeftSidebarHeaderComponent, registerSidebarChannelLinkLabelComponent, registerChannelIconOverride, registerBottomTeamSidebarComponent | Верхня частина списку каналів спільноти, поруч із назвою каналу, іконкою каналу та нижня частина бічної панелі спільноти зліва. |
registerChannelIntro, registerChannelIntroButtonAction, registerChannelToastComponent, registerNewMessagesSeparatorActionComponent, registerChannelComposerBannerComponent, registerComposerPlaceholder | Початок історії каналу, верхня частина списку повідомлень, лінія непрочитаних, над полем введення та заповнювач поля введення. |
registerChannelTypeOption | Типи каналів у діалоговому вікні Створити канал. |
registerSearchComponents | Поле пошуку в заголовку каналу: сфери застосунків з’являються, коли воно порожнє. |
registerUserGuideDropdownMenuAction | Про Antimatter (внизу бічної панелі застосунку), під Допомога. |
registerProduct | Повні сторінки за URL продукту, перелічені під Продукти у Про Antimatter. Сторінки продуктів зберігають класичну рамку сторінки, меню продуктів якої відображається лише тоді, коли застосунок реєструє продукт або додає до нього пункт. Вбудовані застосунки Kanban і Playbooks більше не реєструють продукти: їхні сторінки відкриваються в їхніх бічних панелях. |
registerRootComponent, registerGlobalComponent, хуки, такі як registerMessageWillBePostedHook, registerSlashCommandWillBePostedHook, registerMessageWillFormatHook, registerFilesWillUploadHook, registerDesktopNotificationHook, registerWebSocketEventHandler, registerReducer, registerTranslations | Працюють так, як у класичному інтерфейсі. |
registerAdminConsolePlugin, registerAdminConsoleCustomSetting | Системна консоль, яка є класичною в обох інтерфейсах. |
Що Fusion не показує або не запускає
registerPostDropdownMenuComponent(компонент у меню повідомлення),registerPostCardTypeComponent(картка повідомлення в правій панелі) таregisterProductSwitcherMenuItemне показуються в основних видах Fusion. Замість першого використовуйтеregisterPostDropdownMenuAction.registerMessageWillBeUpdatedHookне запускається, коли повідомлення редагується в Fusion. Перевіряйте редагування на сервері за допомогою хукаMessageWillBeUpdated.registerActionAfterChannelCreationзапускається лише з класичного діалогового вікна створення каналу, яке Fusion відкриває з Інші опції… у своєму діалоговому вікні Створити канал.- Компоненти панелі застосунків показуються незалежно від їхніх
supportedProductIds. - Маршрути з
registerNeedsTeamRouteтаregisterCustomRouteзберігають класичну рамку сторінки, як і продукти.
Хуки, які є лише в Antimatter
Ці методи реєстрації не існують у Mattermost. Перевіряйте, що метод існує, перед викликом, щоб застосунок також працював там:
if (typeof registry.registerComposerMenuItem === 'function') {
registry.registerComposerMenuItem({
text: 'Create a poll',
icon: <i className='icon icon-chart-bar'/>,
fusionIcon: 'poll',
action: ({channelId, rootId}) => openPollCreation(channelId, rootId),
});
} else {
registry.registerChannelHeaderButtonAction(icon, (channel) => openPollCreation(channel.id), 'Create a poll', 'Create a poll');
}
| Метод | Що він додає |
|---|---|
registerComposerMenuItem({text, icon, fusionIcon, action, shouldRender}) | Елемент у меню + поля повідомлення, в обох інтерфейсах. action отримує {channelId, rootId}. |
registerEmojiPickerTab({id, label, icon, fusionIcon, order, component, shouldRender, replacesGifPicker}) | Вкладка вибірника емодзі поля повідомлення, наприклад GIF або наклейки. У Fusion вкладки з order нижче 10 з’являються перед вкладкою Емодзі. Компонент отримує channelId, rootId, filter, onFilterChange, onSelectDone, insertText, isFusion та, у Fusion, pickerKit (див. нижче). |
registerChannelViewPanel({matcher, component}) | Панель між заголовком каналу та повідомленнями для каналів, які обирає matcher(state, channel). Вона може приховувати повідомлення та поле введення (setMessagesVisible), як це роблять голосові канали. |
registerSidebarChannelFooterComponent(component) | Рядок під каналом у бічній панелі, наприклад учасники його дзвінка. Він отримує channel. |
registerIntegrationsOption({title, description, fusionIcon, action, shouldRender}) | Лише Fusion: запис на сторінках Інтеграції, поруч із власними емодзі. Додаток GIF додає туди свої власні GIF і наклейки. |
Додавати та керувати видами у виборнику емодзі
У Fusion вкладка вибору емодзі отримує проп pickerKit: компоненти, які вкладка Emoji використовує для власних переглядів додавання та керування, щоб перегляди вкладки виглядали однаково та залишалися всередині вибірника. Додатки GIFs і Memes використовують його для додавання та керування GIF, наклейками та шаблонами мемів. Він містить version (1), хуки usePickerView та useOnEscape, а також компоненти PickerViewButtons (кнопки + та керування поруч із полем пошуку), PickerViewFrame (перегляд зі стрілкою назад), PickerNotice, PickerField, PickerSearch, PickerDropZone та PickerManageList. Клавіша Escape повертає з перегляду до сітки вкладки. Перевірте, що проп присутній: інші хости його не включають, і тоді вкладка використовує власні діалоги. Типи описано у webapp/fusion/src/types/store/plugins.ts (EmojiPickerTabProps), а компоненти — у webapp/fusion/src/fusion/popovers/picker_views.tsx.
Панель у новому вікні
Fusion передає компоненту registerRightHandSidebarComponent два додаткові пропи, щоб Відкрити в новому вікні відкривало панель там, де вона була, а не на домашній сторінці додатка:
setPanelState(state): викликайте, коли панель змінює місце, передаючи короткий рядок, який визначає ваш додаток, наприклад'note/<id>'або'board/<id>/card/<id>', абоnullдля домашнього екрана. Це значення потрапляє до адреси вікна, тому ніколи не зберігайте в ньому конфіденційні дані. Стан довжиною понад 512 символів або з керувальними символами буде відкинуто.initialPanelState: у новому вікні — стан, який мала панель, абоundefined. Вважайте його ненадійним вхідним даним: отримуйте елемент повторно від імені ввійшеної особи, а якщо вона не може його відкрити, показуйте домашній екран додатка або перегляд «не знайдено».
Стан забувається, коли панель закривається або на її місце приходить панель іншого додатка. Щоб відкрити вікно панелі самостійно, використовуйте popoutRhsPlugin (також доступний у window.WebappUtils.popouts), передаючи стан як необов’язковий п’ятий аргумент. Нотатки, Дошка для малювання, Список справ, Kanban та Плейбуки використовують саме це.
fusionIcon вказує на іконку з набору іконок Fusion (наприклад poll, sticker, calendar або image; див. webapp/fusion/src/fusion/components/icon). Fusion показує її замість icon, щоб елемент гармонійно вписувався в загальний стиль.
Налаштування
settings_schema у plugin.json описує сторінку додатка в Системна консоль › Плагіни:
"settings_schema": {
"header": "Forecasts come from the weather service below.",
"settings": [
{"key": "ServiceURL", "display_name": "Service URL", "type": "text", "default": "https://api.example.com"},
{"key": "Units", "display_name": "Units", "type": "dropdown", "default": "metric",
"options": [{"display_name": "Celsius", "value": "metric"}, {"display_name": "Fahrenheit", "value": "imperial"}]},
{"key": "EnableAlerts", "display_name": "Severe weather alerts", "type": "bool", "default": false,
"help_text": "Post alerts in the channels that follow a city."}
]
}
Типи налаштувань: bool, text, longtext, number, dropdown, radio, generated (випадковий секрет), username та custom (відображається компонентом, який веб-додаток реєструє за допомогою registerAdminConsoleCustomSetting). Налаштування можна групувати в sections.
Серверна частина читає їх у структуру, поля якої відповідають ключам:
type configuration struct {
ServiceURL string
Units string
EnableAlerts bool
}
func (p *Plugin) OnConfigurationChange() error {
var cfg configuration
if err := p.API.LoadPluginConfiguration(&cfg); err != nil {
return err
}
p.setConfiguration(&cfg)
return nil
}
Адміністратори також можуть встановити їх у конфігурації, у розділі PluginSettings.Plugins.<app id>, з ключами в нижньому регістрі (див. посібник адміністратора).
Переклади
Antimatter перекладено 37 мовами, а вбудовані додатки — теж. Веб-додаток робить це так:
- Написуйте кожний рядок за допомогою React Intl, з ID та англійським текстом:
<FormattedMessage id='polls.create' defaultMessage='Create a poll'/>абоdefineMessages. Префіксуйте ID назвою вашого додатка. - Виділіть англійські рядки у
webapp/i18n/en.json(вбудовані додатки використовуютьformatjs extract). - Додайте по одному файлу для кожної мови поруч із ним, з тими самими ID:
fr.json,de.json,pt-BR.json,zh-CN.json… - Передайте їх веб-додатку, який запитує мову користувача:
import fr from '../i18n/fr.json'; import ptBR from '../i18n/pt-BR.json'; const catalogs = {fr, 'pt-BR': ptBR}; export function translationsFor(locale) { return catalogs[locale] || catalogs[locale.split('-')[0]] || {}; } registry.registerTranslations(translationsFor);
Якщо рядок відсутній, використовується його англійський defaultMessage. Для рядків, які надсилає серверна частина (відповіді на команди, повідомлення ботів), пакет pluginapi/i18n завантажує файли перекладів, включені в пакет (наприклад, assets/i18n), і надає локалізатор мовою кожного користувача.
Збирання, встановлення та налагодження
Потрібні Go та Node.js (версія з .nvmrc додатка). У папці додатка:
| Команда | Призначення |
|---|---|
make dist | Збирає серверну частину для всіх платформ та веб-додаток, а записує пакет у dist/<app id>-<version>.tar.gz. |
make test | Запускає тести Go та JavaScript. |
make check-style | Перевіряє код (golangci-lint, ESLint) та типи TypeScript. |
make deploy | Збирає, потім встановлює та вмикає пакет на сервері. |
make watch, make deploy-from-watch | Перший перебудовує веб-додаток щоразу, коли ви зберігаєте зміни; другий пакує те, що зібрав, і встановлює його на сервері. |
make enable, make disable, make reset | Вмикає, вимикає або перезапускає додаток на сервері. |
make logs, make logs-watch | Показує рядки журналу сервера, що стосуються додатка. |
make attach | Приєднує налагоджувач Delve до запущеної серверної частини (спочатку зберіть із AM_DEBUG=1). |
make deploy та інші серверні команди використовують локальний сокет, якщо його знаходять (/var/tmp/antimatter_local.socket або AM_LOCALSOCKETPATH): запускайте їх на машині сервера з увімкненим локальним режимом. Інакше вони вхідять на сервер, заданий MM_SERVICESETTINGS_SITEURL, за допомогою AM_ADMIN_TOKEN або AM_ADMIN_USERNAME та AM_ADMIN_PASSWORD. Інструменти збирання ще не читають ім’я AM_ для URL сайту.
Ручне встановлення пакета
- Системний адміністратор дозволяє завантаження за допомогою
AM_PLUGINSETTINGS_ENABLEUPLOADS=true(вимкнено за замовчуванням; у системній консолі немає перемикача). - Потім Системна консоль › Плагіни › Керування плагінами › Завантажити плагін, або
amctl plugin add weather.tar.gz, а потімamctl plugin enable com.example.weather. - Якщо на сервері увімкнено Вимагати підпис плагіна, він приймає лише пакети, підписані ключем, якому довіряє; див. Встановлення інших додатків.
AM_SERVICESETTINGS_ENABLEDEVELOPER=true на сервері: тоді помилки міститимуть деталі. Makefile вбудованих додатків збирають серверну частину лише для вашої платформи, якщо MM_SERVICESETTINGS_ENABLEDEVELOPER встановлено у вашій оболонці.