Apps schrijven (plugins)
Apps voegen functionaliteit toe aan Antimatter van binnenuit: een servergedeelte in Go dat zich aan de server koopt, en een webappgedeelte in React dat menu’s, panelen, berichttypes en pagina’s toevoegt. Deze pagina toont hoe een app is opgebouwd, met de meegeleverde Peilingen-app als voorbeeld, en wat er verandert in de Fusion-interface van Antimatter.
Hoe een app werkt
- Het servergedeelte is een Go-programma. De server start dit als een apart proces en communiceert ermee via RPC: het roept de hooks van de app aan (een bericht staat op het punt te worden gepost, een opdracht is uitgevoerd…), en de app roept de plugin-API van de server aan (een bericht aanmaken, een gebruiker lezen, gegevens opslaan…). Het kan ook HTTP-routes aanbieden onder
/plugins/<app id>/. - Het webappgedeelte is een JavaScript-bundel die in de browser wordt geladen samen met de webapp. Het registreert React-componenten en callbacks bij het register: een knop in een menu, een paneel, een manier om een berichttype weer te geven.
- Elk van beide onderdelen is optioneel. Een app wordt geïnstalleerd als een
.tar.gz-bundel, en systeembeheerders schakelen deze in, uit en configureren deze.
Antimatter voert apps uit die voor Mattermost zijn geschreven, en omgekeerd, zolang een app alleen hooks gebruikt die beide hebben. Zie Alleen door Antimatter beschikbare hooks.
De bundelstructuur
Er is geen apart starttemplate: begin bij een kleine meegeleverde app zoals antimatter-plugin-polls of antimatter-plugin-todo, en wijzig de ID en de code. De Peilingen-app is als volgt opgebouwd:
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
Optioneel: een public/-map, die ongewijzigd wordt aangeboden op /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: uniek en permanent; een omgekeerd domeinnaam is gebruikelijk. Het noemt de routes, de opgeslagen instellingen en de WebSocket-gebeurtenissen.version: semantische versie. De meegeleverde apps laten dit weg uitplugin.json; hun bouwwerkzeug haalt het uit de nieuwstev*-git-tag.min_server_version: Antimatter meldt12.0.0.settings_schema: de instellingenpagina van de app in de Systeemconsole (zie Instellingen).
make apply kopieert het manifest naar server/manifest.go en webapp/src/manifest.ts; make manifest-check valideert het.
Het servergedeelte
Het servergedeelte importeert dezelfde Go-module als Mattermost-apps, github.com/mattermost/mattermost/server/public, via de Go-module-proxy. De main start alleen de app:
package main
import "github.com/mattermost/mattermost/server/public/plugin"
func main() {
plugin.ClientMain(&Plugin{})
}
Het type Plugin bevat plugin.MattermostPlugin, wat p.API biedt, en implementeert de benodigde hooks:
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)
}
Veelvoorkomende hooks
| Hook | Wanneer het wordt uitgevoerd |
|---|---|
OnActivate, OnDeactivate | Wanneer de app wordt ingeschakeld of uitgeschakeld, en wanneer de server start en stopt. Registreer slash-opdrachten en bots in OnActivate. |
OnConfigurationChange | Wanneer de instellingen veranderen; laad ze in met p.API.LoadPluginConfiguration(&cfg). |
ServeHTTP | Verzoeken naar /plugins/<app id>/…. De server stelt de Mattermost-User-Id-header in wanneer het verzoek van een ingelogde gebruiker komt; weiger verzoeken zonder deze header, tenzij ze publiek bedoeld zijn. |
ExecuteCommand | Een door de app geregistreerde slash-opdracht is uitgevoerd. |
MessageWillBePosted, MessageWillBeUpdated | Voordat een bericht wordt opgeslagen: geef een gewijzigd bericht terug, of een reden om het te weigeren. |
MessageHasBeenPosted, MessageHasBeenUpdated, MessageHasBeenDeleted | Naderhand, voor neveneffecten. |
ReactionHasBeenAdded, ReactionHasBeenRemoved | Reacties. |
UserHasJoinedChannel, UserHasLeftChannel, UserHasJoinedTeam, UserHasLeftTeam, ChannelHasBeenCreated | Lidmaatschap en wijzigingen in kanalen. Een team is een community. |
FileWillBeUploaded | Voordat een bestand wordt opgeslagen: wijzig het of weiger het. |
OnPluginClusterEvent | Een bericht van dezelfde app op een andere server van een cluster. |
De volledige lijst, met de exacte signaturen, staat in server/public/plugin/hooks.go; de API staat in server/public/plugin/api.go. Het pluginapi-pakket (server/public/pluginapi) verpakt de API in vriendelijkere helpers.
Handige API-aanroepen
CreatePost,UpdatePost,SendEphemeralPost: post als de bot van de app of als een gebruiker.EnsureBotUser(&model.Bot{Username, DisplayName}): maak de bot van de app een keer aan en verkrijg de ID.PublishWebSocketEvent("poll_updated", payload, &model.WebsocketBroadcast{ChannelId: id}): stuur een evenement naar de webapp. Het komt aan alscustom_<app id>_poll_updated.OpenInteractiveDialog: open een dialoog.LogInfo,LogErroren dergelijke: schrijf naar de serverlog, gemarkeerd met de ID van de app.GetUser,GetChannel,HasPermissionToChannel: controleer wie wat mag doen voordat u handelt namens een gebruiker.
Een app wordt op elke server van een cluster uitgevoerd. Bewaar de status in de sleutel-waarde-opslag of de database, niet alleen in het geheugen.
De AI-service
De server van Antimatter roept AI-modellen aan voor apps via de modelverbindingen die een beheerder heeft ingesteld (zie AI in de beheerdersgids). Een app bewaart nooit een sleutel van een provider: de app geeft aan voor welke persoon en op welke locatie hij handelt, en de server controleert of die persoon de verbinding daar mag gebruiken, past de limieten en budgetten toe, rekent de tokens af op de persoon en de app en controleert de aanroep zonder de inhoud.
| Methode | Wat het doet |
|---|---|
AIChat(actingUserID, channelID, teamID, *ai.ChatRequest) | Een chatvoltooiing: berichten, tools, gestructureerde output (JSONSchema). |
AIChatStream(…) | Hetzelfde, gestreamd: lees de Events van de stream totdat het kanaal wordt gesloten, of sluit het met Close om de aanroep te stoppen. |
AIEmbed(…, *ai.EmbedRequest) | Embeddings van teksten. |
AITranscribe(…, *ai.TranscribeRequest) | Spraak naar tekst. |
AIDecide(…, *ai.DecideRequest) | Gestuurde vragen aan een beslissingsmodel (ja of nee, een keuze, een score) over een tekst. |
AIConnectionsForUser(userID, channelID, teamID) | De verbindingen die een persoon daar mag gebruiken, met hun modellen en mogelijkheden, nooit hun sleutels. |
De ConnectionID van elke aanvraag kiest de verbinding, en Model kiest een van de modellen (leeg voor het standaardmodel van de verbinding). Operation is een korte label voor de gebruiks- en controlelogboeken, nooit de inhoud. De minimale serverversie is 11.11.
actingUserID alleen uit een bron die de server heeft geverifieerd: de Mattermost-User-Id-header van een aanvraag naar uw ServeHTTP, of de gebruiker in de context van een hook (de auteur van een bericht, de gebruiker die een opdracht heeft uitgevoerd). Neem het nooit uit een bericht, een webhook-payload, een URL of een aanvraag van een andere app: uw app zou dan de toegang van iemand anders gebruiken.Deze methoden en het ai-pakket (server/public/ai) bestaan alleen in de kopie van het module van Antimatter, niet in de versie die door Mattermost is gepubliceerd. Om ze te gebruiken, compileer tegen server/public van Antimatter met een replace-regel in uw go.mod, bijvoorbeeld replace github.com/mattermost/mattermost/server/public => ../antimatter/server/public. Een dergelijke app werkt alleen op Antimatter.
Data opslaan
Elke app heeft zijn eigen sleutel-waarde-opslag in de database van de server:
KVSet(key, value),KVGet(key),KVDelete(key): waarden zijn bytes, meestal JSON. Sleutels zijn maximaal 150 tekens lang.KVSetWithExpiry(key, value, seconds): de waarde verdwijnt na die tijd.KVCompareAndSet(key, old, new)enKVSetWithOptionsmetAtomic: veilige updates wanneer meerdere verzoeken of servers dezelfde sleutel schrijven. Peilingen slaat elke stem op deze manier op en probeert opnieuw bij een conflict.KVList(page, perPage): de sleutels, pagina voor pagina.
Het webapp-deel
Het bundle registreert een klasse met window.registerPlugin. De initialize-methode ontvangt het register en de Redux store:
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());
- Gedeelde bibliotheken. De webapp biedt React, ReactDOM, Redux, React Redux en React Intl: vermeld ze als webpack
externals(React,ReactDOM,Redux,ReactRedux,ReactIntl) in plaats van uw eigen kopieën te bundelen. Ziewebpack.config.jsvan Polls. - Uw server-deel aanroepen. Haal
${window.basename}/plugins/<app id>/…op met de opties uitClient4.getOptions()(uitmattermost-redux/client): ze bevatten de headers die de server nodig heeft om een verzoek van de browser te accepteren. - Uw eigen status.
registerReducervoegt een reducer toe, opgeslagen onderstate['plugins-<app id>']. - Types. De extra registermethoden van Antimatter staan niet in de gepubliceerde types van Mattermost: Polls declareert het register dat het gebruikt in
webapp/src/types/host.ts.
De methoden van het register en hun parameters zijn gedocumenteerd in de code, in webapp/fusion/src/plugins/registry.ts (Fusion) en webapp/channels/src/plugins/registry.ts (klassiek).
Ondersteuning voor de Fusion-interface
Antimatter heeft twee webinterfaces: Fusion, de standaard, en de klassieke. Beide laden dezelfde app-bundles en hebben hetzelfde register, maar Fusion tekent veel dingen zelf en plaatst plugincomponenten op zijn eigen manier. Test uw app in beide.
De interface detecteren
Vóórdat een app wordt geladen, stellen beide interfaces window.antimatterWebUI in op 'fusion' of 'classic', en dezelfde waarde op het wortelelement van de pagina als data-am-web-ui. Op Mattermost is er geen van beide ingesteld.
export function isFusionUI(): boolean {
return window.antimatterWebUI === 'fusion' || document.documentElement.dataset.amWebUi === 'fusion';
}
De ingebouwde apps gebruiken dit om anders te worden weergegeven. Peilingen toont zijn peilingskaarten en zijn aanmaakscherm in de stijl van Fusion en opent een eigen dialoogvenster voor /poll in Fusion. Gesprekken en spraakkanaal laten de zijbalk en toast-componenten weg die Fusion vervangt door zijn eigen gespreksinterface. De Agents-app plaatst zijn AI-acties in het berichtmenu, als een groep onder Meer acties, in plaats van in de hover-balk. De eigen CSS-classes van Fusion (met het voorvoegsel am-) en de dialooglaag (het am-layer-element) zijn wat deze apps gebruiken om de interface te matchen; het is geen gedocumenteerde, stabiele API.
Waar Fusion plugin-componenten toont
| Registratiemethode | Waar het in Fusion verschijnt |
|---|---|
registerAppBarComponent, registerChannelHeaderButtonAction | De app-rail, de kolom met pictogrammen aan de rechterkant, tussen je avatar en de knop Over Antimatter. Knoppen in de kanaalkop gaan daar ook naartoe, niet in de kanaalkop zelf. Verborgen wanneer een systeembeheerder Apps-balk uitschakelen inschakelt (ExperimentalSettings.DisableAppBar, standaard uit). |
registerRightHandSidebarComponent | Het paneel aan de rechterkant. Gebruikers kunnen het verkleinen door de rand te verslepen en het in een nieuw venster te openen. Als de app ook een product is, biedt het paneel Openen als volledige pagina. Zie Het paneel in een nieuw venster. |
registerChannelHeaderIcon | Pictogrammen in de kanaalkop, na de gespreksknoppen; op telefoons in het menu Meer van de kop. |
registerCallButtonAction | De gespreksknop in de kanaalkop; meerdere providers delen een menu Andere manieren om te bellen. |
registerPostDropdownMenuAction | Het menu Acties voor berichten (⋯) van het bericht, in het submenu Meer acties; ook in het menu Acties voor draadje van het draadpaneel, onder Meer acties. Fusion toont het pictogram van de app vóór elke actie, in grijs: het eigen pictogram voor de ingebouwde apps, anders het pictogram dat de app toont in de app-rail, anders een neutraal pictogram. Een element zonder tekst aan het begin van de actietekst (een pictogram dat de app zelf tekent) wordt verborgen. |
registerPostDropdownSubMenuAction | Een groep met een kop in hetzelfde submenu Meer acties, na de individuele acties. Geen app krijgt een eigen item in het berichtmenu. |
registerPostActionComponent | De hover-balk van het bericht, vóór de ⋯-knop. |
registerPostTypeComponent, registerPostMessageAttachmentComponent, registerPostHeaderComponent, registerPostWillRenderEmbedComponent | Het bericht zelf: de inhoud, onder de tekst, na de tijd, en ter vervanging van linkvoorbeelden. |
registerChannelHeaderMenuAction | Het menu Kanaalopties van het kanaal (⋯ op een kanaal in de zijbalk, of rechtermuisklik), als items met een stekkerpictogram. |
registerMainMenuAction | Het gemeenschapsmenu (de naam van de gemeenschap bovenaan de kanaallijst), onder Meer acties, gegroepeerd per app. |
registerSidebarBrowseOrAddChannelMenuAction | Het gemeenschapsmenu, na Kanaal aanmaken en Categorie aanmaken. |
registerFileUploadMethod | Het +-menu van de invoerbalk (Toevoegen aan bericht), na Bestand uploaden. |
registerPostEditorActionComponent | De opmaakbalk van de invoerbalk, wanneer de opmaak wordt weergegeven. |
registerAIActionMenuItemComponent | Het AI-menu van de invoerbalk. |
registerFileDropdownMenuAction, registerFilePreviewComponent | Het optiemenu van een bestand en de voorvertoning van het bestand. |
registerUserSettings | Profiel & instellingen, met één tab per app onder de kop Plugins. |
registerChannelSettingsTab | Het dialoogvenster voor kanaalinstellingen, onder de kop Apps. |
registerPopoverUserAttributesComponent, registerPopoverUserActionsComponent | De profielkaart die wordt weergegeven wanneer u op een naam of een avatar klikt. |
registerLeftSidebarHeaderComponent, registerSidebarChannelLinkLabelComponent, registerChannelIconOverride, registerBottomTeamSidebarComponent | De bovenkant van de kanaallijst van een community, naast de naam van een kanaal, het pictogram van een kanaal en de onderkant van de community-zijbalk aan de linkerkant. |
registerChannelIntro, registerChannelIntroButtonAction, registerChannelToastComponent, registerNewMessagesSeparatorActionComponent, registerChannelComposerBannerComponent, registerComposerPlaceholder | Het begin van de geschiedenis van een kanaal, de bovenkant van de berichtenlijst, de onlezenlijn, boven de invoerbalk en de placeholder van de invoerbalk. |
registerChannelTypeOption | De kanaaltypen in het dialoogvenster Kanaal aanmaken. |
registerSearchComponents | Het zoekveld in de kanaalkop: appbereiken verschijnen wanneer het leeg is. |
registerUserGuideDropdownMenuAction | Over Antimatter (onderaan de app-zijbalk), onder Help. |
registerProduct | Volledige pagina’s op het product-URL, vermeld onder Producten in Over Antimatter. Productpagina’s behouden het klassieke paginaframe, waarvan het productmenu alleen wordt weergegeven wanneer een app een product registreert of een item toevoegt. De ingebouwde Kanban- en Playbooks-apps registreren geen producten meer: hun pagina’s openen in hun zijpanelen. |
registerRootComponent, registerGlobalComponent, hooks zoals registerMessageWillBePostedHook, registerSlashCommandWillBePostedHook, registerMessageWillFormatHook, registerFilesWillUploadHook, registerDesktopNotificationHook, registerWebSocketEventHandler, registerReducer, registerTranslations | Werk zoals in de klassieke interface. |
registerAdminConsolePlugin, registerAdminConsoleCustomSetting | De Systeemconsole, die in beide interfaces de klassieke is. |
Wat Fusion niet weergeeft of uitvoert
registerPostDropdownMenuComponent(een component in het berichtmenu),registerPostCardTypeComponent(een kaart van een bericht in het paneel aan de rechterkant) enregisterProductSwitcherMenuItemworden niet weergegeven in de hoofdweergaven van Fusion. Gebruik in plaats van de eersteregisterPostDropdownMenuAction.registerMessageWillBeUpdatedHookwordt niet uitgevoerd wanneer een bericht in Fusion wordt bewerkt. Controleer bewerkingen op de server met deMessageWillBeUpdated-hook.registerActionAfterChannelCreationwordt alleen uitgevoerd vanuit het klassieke dialoogvenster voor kanaalaanmaak, dat Fusion opent via Meer opties… in het dialoogvenster Kanaal aanmaken.- Componenten in de appbalk worden weergegeven ongeacht hun
supportedProductIds. - Routes van
registerNeedsTeamRouteenregisterCustomRoutebehouden het klassieke paginakraam, zoals producten.
Hooks die alleen Antimatter heeft
Deze registratiemethoden bestaan niet in Mattermost. Controleer of een methode bestaat voordat je hem aanroept, zodat de app daar ook werkt:
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');
}
| Methode | Wat het toevoegt |
|---|---|
registerComposerMenuItem({text, icon, fusionIcon, action, shouldRender}) | Een item in het +-menu van het berichtvak, in beide interfaces. action ontvangt {channelId, rootId}. |
registerEmojiPickerTab({id, label, icon, fusionIcon, order, component, shouldRender, replacesGifPicker}) | Een tab van de emoji-kiezer in het berichtvak, zoals GIFs of stickers. In Fusion komen tabs met een order onder de 10 vóór de Emoji-tab. De component ontvangt channelId, rootId, filter, onFilterChange, onSelectDone, insertText, isFusion en, in Fusion, pickerKit (zie hieronder). |
registerChannelViewPanel({matcher, component}) | Een paneel tussen de kanaalkop en de berichten, voor de kanalen die matcher(state, channel) selecteert. Het kan de berichten en de invoerbalk verbergen (setMessagesVisible), zoals spraakkanaal dat doet. |
registerSidebarChannelFooterComponent(component) | Een regel onder een kanaal in de zijbalk, zoals de personen in het gesprek. Het ontvangt het channel. |
registerIntegrationsOption({title, description, fusionIcon, action, shouldRender}) | Alleen Fusion: een item op de pagina's Integraties, naast aangepaste emoji's. De GIFs-app voegt daar zijn aangepaste GIFs en stickers toe. |
Weergaven toevoegen en beheren in de emoji-kiezer
In Fusion krijgt een emoji-kiezer tab een pickerKit-prop: de componenten die de Emoji-tab gebruikt voor zijn eigen toevoegen- en beheren-weergaven, zodat de weergaven van een tab er hetzelfde uitzien en binnen de kiezer blijven. De GIFs- en Memes-apps gebruiken dit om GIFs, stickers en memetemplates toe te voegen en te beheren. Het bevat version (1), de hooks usePickerView en useOnEscape, en de componenten PickerViewButtons (de +- en beheren-knoppen naast het zoekvak), PickerViewFrame (een weergave met een terugpijl), PickerNotice, PickerField, PickerSearch, PickerDropZone en PickerManageList. Escape gaat terug van een weergave naar het raster van de tab. Controleer of de prop aanwezig is: andere hosts laten hem weg, en de tab gebruikt dan zijn eigen dialoogvensters. De types staan in webapp/fusion/src/types/store/plugins.ts (EmojiPickerTabProps) en de componenten in webapp/fusion/src/fusion/popovers/picker_views.tsx.
Het paneel in een nieuw venster
Fusion geeft het component van registerRightHandSidebarComponent twee extra props mee, zodat Openen in nieuw venster het paneel opent op de plek waar het zich bevindt, in plaats van op het startscherm van de app:
setPanelState(state): roep dit aan wanneer het paneel van plaats verandert, met een korte tekenreeks die uw app definieert, zoals'note/<id>'of'board/<id>/card/<id>', ofnullop het startscherm. Deze waarde komt in het adres van het venster, dus plaats er nooit een geheim in. Een staat van meer dan 512 tekens, of met controletekens, wordt genegeerd.initialPanelState: in het nieuwe venster is dit de staat die het paneel had, ofundefined. Behandel dit als onbetrouwbare invoer: haal het item opnieuw op als de aangemelde gebruiker, en toon het startscherm van de app of een 'niet gevonden'-weergave als ze het niet kunnen openen.
De staat wordt vergeten wanneer het paneel wordt gesloten of wanneer het paneel van een andere app de plek inneemt. Om zelf het venster van een paneel te openen, gebruikt u popoutRhsPlugin (ook te vinden in window.WebappUtils.popouts), waarbij de staat als optioneel vijfde argument wordt doorgegeven. Notities, Whiteboard, Te doen, Kanban en Playbooks maken gebruik van deze functionaliteit.
fusionIcon noemt een pictogram uit de pictogramset van Fusion (bijvoorbeeld poll, sticker, calendar of image; zie webapp/fusion/src/fusion/components/icon). Fusion toont dit in plaats van icon, zodat het item overeenkomt met de andere.
Instellingen
settings_schema in plugin.json beschrijft de pagina van de app in Systeemconsole › Plugins:
"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."}
]
}
Instellingstypen: bool, text, longtext, number, dropdown, radio, generated (een willekeurig geheim), username en custom (getekend door een component dat het webapp-deel registreert met registerAdminConsoleCustomSetting). Instellingen kunnen worden gegroepeerd in sections.
Het serverdeel leest ze in naar een structuur waarvan de velden overeenkomen met de sleutels:
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
}
Beheerders kunnen ze ook instellen in de configuratie, onder PluginSettings.Plugins.<app id>, met sleutels in kleine letters (zie de beheerdersgids).
Vertalingen
Antimatter is vertaald naar 37 talen, en ook de meegeleverde apps. Het webapp-deel doet dit als volgt:
- Schrijf elke tekst met React Intl, met een ID en de Engelse tekst:
<FormattedMessage id='polls.create' defaultMessage='Create a poll'/>ofdefineMessages. Voeg de naam van uw app als prefix toe aan de IDs. - Extraheer de Engelse teksten naar
webapp/i18n/en.json(de gebundelde apps gebruikenformatjs extract). - Voeg er één bestand per taal naast toe, met dezelfde IDs:
fr.json,de.json,pt-BR.json,zh-CN.json… - Geef ze door aan de webapp, die de taal van de gebruiker vraagt:
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);
Een ontbrekende tekst valt terug op de Engelse defaultMessage. Voor teksten die het servergedeelte verstuurt (antwoorden op opdrachten, botberichten), laadt het pakket pluginapi/i18n de vertaalbestanden die in de bundle zijn opgenomen (bijvoorbeeld assets/i18n) en geeft een localizer in de taal van elke gebruiker.
Bouwen, installeren en debuggen
U heeft Go en Node.js nodig (de versie in het .nvmrc-bestand van de app). In de map van de app:
| Opdracht | Wat het doet |
|---|---|
make dist | Bouwt het servergedeelte voor elk platform en het webappgedeelte, en schrijft de bundle naar dist/<app id>-<version>.tar.gz. |
make test | Voert de Go- en JavaScript-tests uit. |
make check-style | Controleert de code (golangci-lint, ESLint) en de TypeScript-types. |
make deploy | Bouwt, installeert en activeert de bundle op een server. |
make watch, make deploy-from-watch | De eerste bouwt het webappgedeelte elke keer opnieuw wanneer u opslaat; de tweede bundelt wat is gebouwd en installeert het op de server. |
make enable, make disable, make reset | Activeert, deactiveert of herstart de app op de server. |
make logs, make logs-watch | Toont de regels van de app in het serverlog. |
make attach | Koppelt de Delve-debugger aan het draaiende servergedeelte (bouw eerst met AM_DEBUG=1). |
make deploy en de andere serveropdrachten gebruiken een lokale modus socket als ze er een vinden (/var/tmp/antimatter_local.socket, of AM_LOCALSOCKETPATH): voer ze uit op de machine van de server met de lokale modus ingeschakeld. Anders loggen ze in op de server die is opgegeven door MM_SERVICESETTINGS_SITEURL met AM_ADMIN_TOKEN, of AM_ADMIN_USERNAME en AM_ADMIN_PASSWORD. De bouwt hulpmiddelen lezen nog geen AM_-naam voor de site-URL.
Een bundle handmatig installeren
- Een systeembeheerder staat uploads toe met
AM_PLUGINSETTINGS_ENABLEUPLOADS=true(standaard uit; er is geen schakelaar in de Systeemconsole). - Vervolgens Systeemconsole › Plugins › Plugin Beheer › Plugin Uploaden, of
amctl plugin add weather.tar.gzgevolgd dooramctl plugin enable com.example.weather. - Als de server Plugin-handtekening vereist is ingeschakeld, accepteert het alleen bundels die zijn ondertekend met een sleutel waaraan het vertrouwen hecht. Zie Andere apps installeren.
AM_SERVICESETTINGS_ENABLEDEVELOPER=true in op de server: fouten tonen dan hun details. De Makefiles van de gebundelde apps bouwen het servergedeelte alleen voor uw platform als MM_SERVICESETTINGS_ENABLEDEVELOPER is ingesteld in uw shell.