Създаване на приложения (плъгини)

Приложенията добавят функционалности в Antimatter от вътре: сървърна част на Go, която се хука в сървъра, и уеб част на React, която добавя менюта, панели, типове съобщения и страници. Тази страница показва как е структурирано приложение, като пример се използва вграденото приложение Polls, и какви промени има в интерфейса Fusion на Antimatter.

Как работи едно приложение

  • Сървърната част е програма на Go. Сървърът я стартира като отделен процес и комуникира с нея чрез RPC: той извиква хуковете на приложението (съобщение е на път да бъде публикувано, команда е била изпълнена…), а приложението извиква API-то на плъгините на сървъра (създаване на публикация, четене на потребител, съхранение на данни…). То може също да обслужва HTTP маршрути под /plugins/<app id>/.
  • Уеб частта е JavaScript пакет, зареден в браузъра заедно с уеб приложението. То регистрира React компоненти и обратни извиквания в регистъра: бутон в меню, панел, начин за изобразяване на тип съобщение.
  • И двете части са опционални. Приложението се инсталира като .tar.gz пакет, а системните администратори го активират, деактивират и конфигурират.
Сървърната част се изпълнява с пълен достъп до данните на сървъра. Отнасяйте се към приложението като към код, който разполагате на сървъра.

Antimatter изпълнява приложения, написани за Mattermost, и обратно, стига приложението да използва само хукове, които и двете имат. Вижте Хукове, които има само Antimatter.

Структура на пакета

Няма отделен стартов шаблон: започнете от малко вградено приложение като antimatter-plugin-polls или antimatter-plugin-todo и променете ID-то и кода му. Polls е структуриран по следния начин:

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; инструментът за изграждане го взима от последния v* git таг.
  • 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: проверява кой може да направи какво, преди да действате от името на потребител.

Приложението се изпълнява на всеки сървър в кластера. Запазвайте състоянието в хранилището за ключ-стойност или в базата данни, а не само в паметта.

AI услугата

Сървърът на Antimatter извиква AI модели за приложенията чрез моделните връзки, които администраторът е конфигурирал (вижте AI в ръководството за администратори). Приложението никога не съхранява ключ на доставчик: то посочва за кого и къде действа, а сървърът проверява дали този човек може да използва връзката там, прилага лимитите и бюджетите ѝ, отчита токените спрямо човека и приложението и води одит на извикването без неговото съдържание.

МетодКакво прави
AIChat(actingUserID, channelID, teamID, *ai.ChatRequest)Chat завършване: съобщения, инструменти, структуриран изход (JSONSchema).
AIChatStream(…)Същото, но в поток: четете Events от потока, докато каналът се затвори, или го Close-нете, за да спрете извикването.
AIEmbed(…, *ai.EmbedRequest)Embeddings на текстове.
AITranscribe(…, *ai.TranscribeRequest)Говор в текст.
AIDecide(…, *ai.DecideRequest)Типизирани въпроси към модел за вземане на решения (да или не, избор, оценка) относно текст.
AIConnectionsForUser(userID, channelID, teamID)Връзките, които човек може да използва там, с техните модели и възможности, но никога с техните ключове.

ConnectionID на всяко запитване избира връзката, а Model – един от моделите ѝ (празно за модел по подразбиране на връзката). Operation е кратка етикета за записите за използване и одит, никога не е съдържание. Минималната версия на сървъра е 11.11.

Вземайте actingUserID само от източник, който сървърът е верифицирал: заглавието Mattermost-User-Id на запитване към вашия ServeHTTP, или потребителя от контекста на хук (автор на публикация, потребителят, който е изпълнил команда). Никога не го вземайте от съобщение, webhook payload, URL или запитване на друго приложение: приложението ви би разхарчило достъпа на някой друг.

Тези методи и пакетът ai (server/public/ai) съществуват само в копото на Antimatter от модула, а не в версията, публикувана от Mattermost. За да ги използвате, компилирайте спрямо server/public на Antimatter с ред replace във вашия go.mod, например replace github.com/mattermost/mattermost/server/public => ../antimatter/server/public. Такова приложение работи само на Antimatter.

Осигуряване на инструменти за агенти

Приложението предлага собствените си инструменти на агентите чрез RegisterAITools([]*model.AIToolDefinition): за всеки инструмент се задават име, заглавие, описание, входна JSON схема и тип (read, write или external_write), който определя дали политиката на агента може да го изпълни самостоятелно (вижте Инструменти в ръководството за администратори). Правата се проверяват спрямо аргумента за канал или общност при извикването, или спрямо сървъра, в зависимост от нуждите на инструмента. Когато агент използва инструмента, сървърът извиква хука ExecuteAITool(c, *model.AIToolExecution) *model.AIToolResult, като в UserId е посочена проверената личност, която е отправила заявката: действайте само с правата на тази личност, никога с правата на агента. Инструментите се наименуват като <app id>.<name>; минималната версия на сървъра е 11.11; тези методи и хукът съществуват само в server/public на Antimatter, подобно на описаната по-горе AI услуга. Приложение с ID antimatter или такова, което започва с mcp., не може да регистрира инструменти.

Запазване на данни

Всяко приложение има собствено хранилище за ключ-стойност в базата данни на сървъра:

  • KVSet(key, value), KVGet(key), KVDelete(key): стойностите са байтове, обикновено JSON. Ключовете са най-много 150 символа.
  • KVSetWithExpiry(key, value, seconds): стойността изчезва след това време.
  • KVCompareAndSet(key, old, new) и KVSetWithOptions с Atomic: безопасни актуализации, когато няколко заявки или сървъра записват един и същ ключ. Polls запазва всеки глас по този начин и повтаря при конфликт.
  • 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: изреждайте ги като webpack externals (React, ReactDOM, Redux, ReactRedux, ReactIntl), вместо да опаковате собствени копия. Вижте webpack.config.js на Polls.
  • Извикване на сървърната ви част. Използвайте Fetch за ${window.basename}/plugins/<app id>/… с опциите от Client4.getOptions() (от mattermost-redux/client): те носят заглавията, от които сървърът се нуждае, за да приеме заявка от браузъра.
  • Собствено състояние. registerReducer добавя редюсер, запазен под state['plugins-<app id>'].
  • Типове. Допълнителните методи на регистъра на Antimatter не са в публикуваните типове на Mattermost: Polls декларира регистъра, който използва, в 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, като в Fusion анкетата се пише в полето за въвеждане (един от режимите на полето за въвеждане), вместо в диалогов прозорец. Обажданията и гласовите канали пропускат страничната лента и компонентите за известия (toast), които Fusion замества със собствен интерфейс за обаждания. Приложението за чат с AI поставя своите AI действия в менюто на съобщението, като група под Още действия, вместо в лентата с инструменти при посочване с мишката. Собствените CSS класове на Fusion (с префикс am-) и неговият слой за диалогови прозорци (елементът am-layer) са това, което тези приложения използват, за да съответстват на интерфейса; те не са документиран, стабилен API.

Къде Fusion показва компонентите на плъгините

Метод на регистъраКъде се появява в Fusion
registerAppBarComponent, registerChannelHeaderButtonActionЛентата с приложения, колоната с икони на дясната страна, между вашия аватар и бутона За Antimatter. Там се поставят и бутоните в заглавието на канала, а не в самото заглавие. Скрива се, когато системният администратор активира Забрани лентата с приложения (ExperimentalSettings.DisableAppBar, изключено по подразбиране).
registerRightHandSidebarComponentДесният панел. Неговата заглавка е на Fusion и е еднаква за всяко приложение: иконата и името на приложението (регистрираното заглавие, когато се изобразява само текст, в противен случай етикетът от релсата на приложението или името от манифеста), след това Възстановяване на размера на панела (след промяна на размера), Отваряне в нов прозорец и Затвори. Заглавие, което изобразява нещо друго (контрола, връзка, изображение), се игнорира, затова изрисувате собствените си контроли в самия панел, например в тънка редица в горната му част. Потребителите могат да променят размера на панела, като влачат края му. Вижте Панелът в нов прозорец.
registerChannelHeaderIconИкони в заглавието на канала, след бутоните за обаждане; на телефони, в менюто Още на заглавието.
registerCallButtonActionБутонът за обаждане в заглавието на канала; няколко доставчика споделят меню Други начини за обаждане.
registerPostDropdownMenuActionМенюто Действия върху съобщение (⋯) на съобщението, в подменюто му Още действия; също и в менюто Действия с нишката на панела за нишки, под Още действия. Fusion изобразява иконата на приложението преди всяко действие, в сиво: собствената икона за вградените приложения, в противен случай иконата, която приложението показва в лентата за приложения, в противен случай неутрална. Елемент без текст в началото на текста на действието (икона, която приложението изобразява само) се скрива.
registerPostDropdownSubMenuActionГрупа от елементи в едно и също подменю Още действия, след единичните действия, отделени с линия, без заглавие. Никое приложение няма собствен елемент в менюто на съобщението.
registerPostActionComponentЛентата за действия при посочване на съобщението, преди бутона ⋯.
registerPostTypeComponent, registerPostMessageAttachmentComponent, registerPostHeaderComponent, registerPostWillRenderEmbedComponentСамото съобщение: неговото тяло, под текста му, след часа му и на мястото на превюто на линкове.
registerChannelHeaderMenuActionМенюто Опции за канала (⋯ върху канал в страничната лента, или десен клик), като елементи с икона на щепсел.
registerMainMenuActionМенюто на общността (името на общността в горната част на списъка с канали), под Още действия, групирани по приложение.
registerSidebarBrowseOrAddChannelMenuActionМенюто за общността, след Създаване на канал и Създаване на категория.
registerFileUploadMethodМенюто + на редактора (Добавяне към съобщението), след Качване на файл.
registerPostEditorActionComponentЛентата за форматиране на редактора, когато форматирането е показано.
registerAIActionMenuItemComponentМенюто AI на редактора.
registerFileDropdownMenuAction, registerFilePreviewComponentМенюто с опции за файл и превюто на файла.
registerUserSettingsПрофил и настройки, с по един раздел за всяко приложение под заглавието Приставка.
registerChannelSettingsTabДиалоговият прозорец за настройки на канала, под заглавието Приложения.
registerPopoverUserAttributesComponent, registerPopoverUserActionsComponentКарта за профил, показана, когато щракнете върху име или аватар.
registerLeftSidebarHeaderComponent, registerSidebarChannelLinkLabelComponent, registerChannelIconOverride, registerBottomTeamSidebarComponentГорната част на списъка с канали на общността, до името на канала, иконата на канала и долната част на лентата на общността вляво.
registerChannelIntro, registerChannelIntroButtonAction, registerChannelToastComponent, registerNewMessagesSeparatorActionComponent, registerChannelComposerBannerComponent, registerComposerPlaceholderНачалото на историята на канала, горната част на списъка със съобщения, линията за непрочетено, над редактора и заглавният текст на редактора.
registerChannelTypeOptionТипове канали в диалоговия прозорец Създаване на канал.
registerSearchComponentsПоле за търсене в заглавието на канала: обхватите на приложенията се показват, когато е празно.
registerUserGuideDropdownMenuActionЗа Antimatter (в долната част на лентата на приложението), под Помощ.
registerProductПълни страници на URL адреса на продукта, в класическата рамка на страницата, без меню на продукта: Fusion няма такова и не изброява продукти никъде, затова създайте връзка към страницата сами. Никое вградено приложение не регистрира продукт: Kanban, Наръчници и чатът с AI се отварят в страничните си панели.
registerRootComponent, registerGlobalComponent, хукове като registerMessageWillBePostedHook, registerSlashCommandWillBePostedHook, registerMessageWillFormatHook, registerFilesWillUploadHook, registerDesktopNotificationHook, registerWebSocketEventHandler, registerReducer, registerTranslationsРаботят както в класическия интерфейс.
registerAdminConsolePlugin, registerAdminConsoleCustomSettingСистемната конзола, която е класическа и в двата интерфейса.

Какво Fusion не показва или не изпълнява

  • registerPostDropdownMenuComponent (компонент в менюто на съобщението) и registerPostCardTypeComponent (картата на публикация в десния панел) не се показват в основните изгледи на Fusion. 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: компонентите, които таблоидът Емоджи използва за собствените си изгледи за добавяне и управление, така че изгледите на таблоида да изглеждат еднакво и да остават в рамките на изборника. Приложенията 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 registerComposerMode({id, label, fusionIcon, placeholder, sendLabel, component, shouldRender}) добавя нов начин за изписване на съобщение в полето за въвеждане, подобно на това как приложението Анкети добавя възможност за създаване на анкета. Режимът се предлага в менюто + на полето за въвеждане под неговия label. След като бъде избран, полето за въвеждане запазва текстовото си поле (с placeholder на режима: въпроса на анкетата), показва вашия component под него и лента с името на режима и бутон Откажи; бутонът Изпращане приема sendLabel и изпълнява вашата функция за изпращане вместо да публикува текста.

  • Компонентът получава channelId, rootId (нишката, в полето за отговор), message (текста в полето за въвеждане), setSubmit и onCancel (връща се към обикновено съобщение, запазвайки текста).
  • Передайте на setSubmit функция, която се извиква с текста от полето за въвеждане и връща нищо (или празен низ) след изпращане: тогава полето за въвеждане се изчиства и се връща към обикновено съобщение. Върнете низ, за да го покажете като грешка на място.
  • placeholder и sendLabel са низове или функции на intl от react-intl. shouldRender(state, {channelId, rootId}) определя дали дадено поле за въвеждане предлага режима.
  • За да отворите режима от друго място, например чрез команда с наклонена черта без аргументи: window.dispatchEvent(new CustomEvent('am-composer-mode', {detail: {id, channelId, rootId}})).

Само Fusion поддържа режими за полето за въвеждане: проверете дали регистърът съдържа функцията и в противен случай използвайте собствен диалог. Методът връща идентификатора на режима, а режимът се премахва, когато приложението бъде премахнато.

Панелът в нов прозорец

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 езика, както и вградените приложения. Веб частта на приложението го прави по следния начин:

  1. Записвайте всяка низ с React Intl, с ID и английския ѝ текст: <FormattedMessage id='polls.create' defaultMessage='Create a poll'/> или defineMessages. Предоставете ID-тата с името на вашето приложение.
  2. Извличайте английските низове в webapp/i18n/en.json (вградените приложения използват formatjs extract).
  3. Добавете по един файл за всеки език до него, със същите ID-та: fr.json, de.json, pt-BR.json, zh-CN.json…
  4. Предоставяте ги на уеб приложението, което пита за езика на потребителя:
    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_ име за адреса на сайта.

Ръчно инсталиране на пакет

  • Системен администратор разрешава качвания с AM_PLUGINSETTINGS_ENABLEUPLOADS=true (изключено по подразбиране; няма превключвател в Системната конзола).
  • След това Системна конзола › Приставка › Управление на приставките › Качване на приставка, или amctl plugin add weather.tar.gz, последвано от amctl plugin enable com.example.weather.
  • Ако на сървъра е активирано Изисква се подпис на приставката, той приема само пакети, подписани с ключ, на който има доверие; вижте Инсталиране на други приложения.
За бързи итерации на сървър за разработка, задайте AM_SERVICESETTINGS_ENABLEDEVELOPER=true на сървъра: тогава грешките ще носят своите детайли. Makefile-овете на приложенията в пакета създават сървърната част само за вашата платформа, когато MM_SERVICESETTINGS_ENABLEDEVELOPER е зададен в вашата shell.