Skrive apper (plugins)

Apper legger til funksjonalitet i Antimatter fra innsiden: en serverdel i Go som henger seg inn i serveren, og en webapp-del i React som legger til menyer, paneler, meldingstyper og sider. Denne siden viser hvordan en app er bygd, ved å bruke den medfølgende Avstemning-appen som eksempel, og hva som endres i Antimatters Fusion-grensesnitt.

Hvordan en app fungerer

  • Serverdelen er et Go-program. Serveren starter det som en separat prosess og kommuniserer med det via RPC: den ruter appens hooks (en melding skal publiseres, en kommando ble kjørt…), og appen ruter serverens plugin API (lage en posting, lese en bruker, lagre data…). Den kan også levere HTTP-ruter under /plugins/<app id>/.
  • Webapp-delen er en JavaScript-bunt som lastes inn i nettleseren sammen med webappen. Den registrerer React-komponenter og tilbakemeldinger i registret: en knapp i en meny, et panel, en måte å tegne en meldingstype på.
  • Hver del er valgfri. En app installeres som en .tar.gz-bunt, og systemadministratorer aktiverer, deaktiverer og konfigurerer den.
Serverdelen kjører med full tilgang til serverens data. Behandle en app som kode du distribuerer til serveren.

Antimatter kjører apper skrevet for Mattermost, og omvendt, så lenge en app bare bruker hooks begge har. Se Hooks som bare Antimatter har.

Buntlegget

Det finnes ingen separat startmal: start med en liten medfølgende app som antimatter-plugin-polls eller antimatter-plugin-todo, og endre ID-en og koden. Avstemning er bygd slik:

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

Valgfritt: en public/-mappe, levert som den er på /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: unik og permanent; et omvendt domenenavn er vanlig. Den navngir rutene, de lagrede innstillingene og WebSocket-hendelsene.
  • version: semantisk versjon. De medfølgende appene utelater den fra plugin.json; verktøyet deres for bygging henter den fra den nyeste v*-git-etiketten.
  • min_server_version: Antimatter rapporterer 12.0.0.
  • settings_schema: appens innstillingsside i Systemkonsollen (se Innstillinger).

make apply kopierer manifestet til server/manifest.go og webapp/src/manifest.ts; make manifest-check validerer det.

Serverdelen

Serverdelen importerer samme Go-modul som Mattermost-apps, github.com/mattermost/mattermost/server/public, fra Go-modul-proxyen. Dens main starter bare appen:

package main

import "github.com/mattermost/mattermost/server/public/plugin"

func main() {
	plugin.ClientMain(&Plugin{})
}

Plugin-typen inneholder plugin.MattermostPlugin, som gir den p.API, og implementerer de hookene den trenger:

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)
}

Vanlige hooks

HookNår den kjører
OnActivate, OnDeactivateNår appen aktiveres eller deaktiveres, og når serveren starter og stopper. Registrer kommandoer og bots i OnActivate.
OnConfigurationChangeNår innstillingene endres; last dem inn med p.API.LoadPluginConfiguration(&cfg).
ServeHTTPForespørsler til /plugins/<app id>/…. Serveren setter Mattermost-User-Id-headeren når forespørselen kommer fra en innlogget bruker; avvise forespørsler uten den med mindre de er ment å være offentlige.
ExecuteCommandEn kommando appen registrerte ble kjørt.
MessageWillBePosted, MessageWillBeUpdatedFør en melding lagres: returner en endret post, eller en grunn til å avvise den.
MessageHasBeenPosted, MessageHasBeenUpdated, MessageHasBeenDeletedEtter at det er skjedd, for bivirkninger.
ReactionHasBeenAdded, ReactionHasBeenRemovedReaksjoner.
UserHasJoinedChannel, UserHasLeftChannel, UserHasJoinedTeam, UserHasLeftTeam, ChannelHasBeenCreatedEndringer i medlemskap og kanaler. Et team er et samfunn.
FileWillBeUploadedFør en fil lagres: endre den eller avvise den.
OnPluginClusterEventEn melding fra samme app på en annen server i en kluster.

Den fullstendige listen, med nøyaktige signaturer, finnes i server/public/plugin/hooks.go; API-et er i server/public/plugin/api.go. pluginapi-pakken (server/public/pluginapi) pakker API-et i vennligere hjelpere.

Nyttige API-oppkall

  • CreatePost, UpdatePost, SendEphemeralPost: publiser som appens bot eller som en bruker.
  • EnsureBotUser(&model.Bot{Username, DisplayName}): opprett appens bot én gang og få dens ID.
  • PublishWebSocketEvent("poll_updated", payload, &model.WebsocketBroadcast{ChannelId: id}): send en hendelse til webappen. Den ankommer som custom_<app id>_poll_updated.
  • OpenInteractiveDialog: åpne en dialog.
  • LogInfo, LogError og andre: skriv til serverloggen, merket med appens ID.
  • GetUser, GetChannel, HasPermissionToChannel: sjekk hvem som kan gjøre hva før du handler for en bruker.

En app kjører på hver server i en kluster. Oppbevar tilstand i nøkkel-verdi-lageret eller i databasen, ikke bare i minnet.

AI-tjenesten

Antimatters server kaller AI-modeller for apper, gjennom modelltilkoblingene en administrator har satt opp (se AI i administratørguiden). En app oppbeholder aldri en leverandørs nøkkel: den angir hvem den handler for og hvor, og serveren sjekker at denne personen kan bruke tilkoblingen der, anvender dens grenser og budsjett, teller tokenene mot personen og appen, og reviderer oppropet uten innholdet.

MetodeHva det gjør
AIChat(actingUserID, channelID, teamID, *ai.ChatRequest)En chatfullføring: meldinger, verktøy, strukturert utdata (JSONSchema).
AIChatStream(…)Det samme, streames: les strømme Events til kanalen lukkes, eller Close den for å stoppe oppropet.
AIEmbed(…, *ai.EmbedRequest)Innleiringer (embeddings) av tekster.
AITranscribe(…, *ai.TranscribeRequest)Tale til tekst.
AIDecide(…, *ai.DecideRequest)Typede spørsmål til en beslutningsmodell (ja eller nei, et valg, en score) om en tekst.
AIConnectionsForUser(userID, channelID, teamID)Tilkoblingene en person kan bruke der, med deres modeller og evner, aldri deres nøkler.

Hver forespørslers ConnectionID velger tilkoblingen, og Model en av dens modeller (tom for tilkoblingens standard). Operation er et kort merke for bruks- og revideringssposter, aldri innhold. Deres minimumsserversversjon er 11.11.

Ta actingUserID kun fra en kilde serveren har verifisert: Mattermost-User-Id-hodet i en forespørsel til din ServeHTTP, eller brukeren i en hakers kontekst (en posts forfatter, brukeren som kjørte en kommando). Ta den aldri fra en melding, en webhook-payload, en URL eller en annen apps forespørsel: din app ville bruke noen annens tilgang.

Disse metodene og ai-pakken (server/public/ai) finnes bare i Antimatters kopi av modulen, ikke i versjonen utgitt av Mattermost. For å bruke dem, bygg mot Antimatters server/public med en replace-linje i din go.mod, for eksempel replace github.com/mattermost/mattermost/server/public => ../antimatter/server/public. En slik app kjører bare på Antimatter.

Lagring av data

Hver app har sitt eget nøkkel-verdi-lager i serverens database:

  • KVSet(key, value), KVGet(key), KVDelete(key): verdiene er byte, vanligvis JSON. Nøklene kan være opptil 150 tegn.
  • KVSetWithExpiry(key, value, seconds): verdien forsvinner etter denne tiden.
  • KVCompareAndSet(key, old, new) og KVSetWithOptions med Atomic: sikre oppdateringer når flere forespørsler eller servere skriver til samme nøkkel. Avstemning lagrer hver stemme på denne måten og prøver på nytt ved konflikt.
  • KVList(page, perPage): nøklene, side for side.

Webapp-delen

Bunken registrerer en klasse med window.registerPlugin. Dens initialize mottar registret og Redux-lageret:

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());
  • Felles biblioteker. Webappen tilbyr React, ReactDOM, Redux, React Redux og React Intl: oppfør dem som webpack externals (React, ReactDOM, Redux, ReactRedux, ReactIntl) i stedet for å binde dine egne kopier. Se Avstemnings webpack.config.js.
  • Kalling av serverdelen din. Hent ${window.basename}/plugins/<app id>/… med alternativene fra Client4.getOptions() (fra mattermost-redux/client): de bærer med seg overskriftene serveren trenger for å akseptere en forespørsel fra nettleseren.
  • Din egen tilstand. registerReducer legger til en reducer, lagret under state['plugins-<app id>'].
  • Typer. Antimatters ekstra registreringsmetoder finnes ikke i Mattermosts utgitte typer: Avstemning deklarerer registret den bruker i webapp/src/types/host.ts.

Metodene i registret og deres parametere er dokumentert i koden, i webapp/fusion/src/plugins/registry.ts (Fusion) og webapp/channels/src/plugins/registry.ts (klassisk).

Støtte for Fusion-grensesnittet

Antimatter har to webgrensesnitt: Fusion, som er standard, og det klassiske. Begge laster de samme app-bunkene og har det samme registret, men Fusion tegner mange ting selv og plasserer komponenter fra plugins på sin egen måte. Test appen din i begge.

Oppdag grensesnittet

Før noen app lastes, setter begge grensesnittene window.antimatterWebUI til 'fusion' eller 'classic', og samme verdi på sidens rot-element som data-am-web-ui. I Mattermost er verken av dem satt.

export function isFusionUI(): boolean {
    return window.antimatterWebUI === 'fusion' || document.documentElement.dataset.amWebUi === 'fusion';
}

De medfølgende appene bruker dette for å tegnes på en annen måte. Avstemning tegner sine avstemningskort og sin opprettingsdialog i Fusions stil, og åpner sin egen dialog for /poll i Fusion. Samtaler og talekanaler utelater sidefeltet og toast-komponentene som Fusion erstatter med sitt eget samtalegrensesnitt. Agent-appen plasserer sine AI-handlinger i meldingsmenyen, som en gruppe under Flere handlinger, i stedet for forverktøyet. Fusions egne CSS-klasser (med prefikset am-) og dets dialoglag (elementet am-layer) er det disse appene bruker for å matche grensesnittet; de er ikke en dokumentert, stabil API.

Hvor Fusion viser plugin-komponenter

RegistreringsmetodeHvor det vises i Fusion
registerAppBarComponent, registerChannelHeaderButtonActionApp-skinnet, kolonnen med ikoner på høyre kant, mellom avataren din og Om Antimatter-knappen. Kanalknappene i kanalkladden plasseres også der, ikke i kanalkladden. Skjult når en systemadministrator aktiverer Deaktiver Apps Bar (ExperimentalSettings.DisableAppBar, av som standard).
registerRightHandSidebarComponentPanellet på høyre side. Folk kan endre størrelsen ved å dra i kanten, og åpne det i et nytt vindu. Hvis appen også er et produkt, tilbyr panelet Åpne som en full side. Se Panelet i et nytt vindu.
registerChannelHeaderIconIkoner i kanalkanalen, etter samtaleknappene; på telefoner, i kanalkanalets Mere-meny.
registerCallButtonActionKanalkanalets samtaleknapp; flere leverandører deler en Andre måter å ringe på-meny.
registerPostDropdownMenuActionMeldingens Meldingshandlinger (⋯) -meny, i dens Flere handlinger -undermeny; også i trådpallets Trådhandlinger -meny, under Flere handlinger. Fusion tegner appens ikon før hver handling, i grått: sitt eget ikon for de medfølgende appene, ellers ikonet appen viser i app-skinnet, ellers et nøytralt. Et element uten tekst i starten av handlingens tekst (et ikon appen tegner selv) er skjult.
registerPostDropdownSubMenuActionEn gruppe med en overskrift i samme Flere handlinger -undermeny, etter de enkelte handlingene. Ingen app får en egen post i meldingsmenyen.
registerPostActionComponentMeldingens verktøylinje ved peker, før ⋯-knappen.
registerPostTypeComponent, registerPostMessageAttachmentComponent, registerPostHeaderComponent, registerPostWillRenderEmbedComponentMeldingen selv: dens brødtekst, under teksten, etter tidspunktet, og i stedet for lenkeforhåndsvisninger.
registerChannelHeaderMenuActionKanalens Kanalvalg -meny (⋯ på en kanal i sidefeltet, eller høyreklikk), som elementer med et stikkontakt-ikon.
registerMainMenuActionSamfunnets meny (samfunnets navn øverst i kanallisten), under Flere handlinger, gruppert per app.
registerSidebarBrowseOrAddChannelMenuActionSamfunnsmenyen, etter Opprett kanal og Opprett kategori.
registerFileUploadMethodSkrivefeltets +-meny (Legg til i melding), etter Last opp en fil.
registerPostEditorActionComponentSkrivefeltets formateringslinje, når formatering vises.
registerAIActionMenuItemComponentSkrivefeltets AI-meny.
registerFileDropdownMenuAction, registerFilePreviewComponentEn fils opsjonsmeny, og filforhåndsvisningen.
registerUserSettingsProfil & innstillinger, én fane per app under Plugins-overskriften.
registerChannelSettingsTabKanalinstillingsdialogen, under Apper -overskriften.
registerPopoverUserAttributesComponent, registerPopoverUserActionsComponentProfilkortet som vises når du klikker på et navn eller en avatar.
registerLeftSidebarHeaderComponent, registerSidebarChannelLinkLabelComponent, registerChannelIconOverride, registerBottomTeamSidebarComponentØverst i kanallisten for et samfunn, ved siden av kanalens navn, kanalens ikon og nederst i samfunnsfeltet til venstre.
registerChannelIntro, registerChannelIntroButtonAction, registerChannelToastComponent, registerNewMessagesSeparatorActionComponent, registerChannelComposerBannerComponent, registerComposerPlaceholderStarten på kanalens historikk, øverst i meldingslisten, ulest-linjen, over skrivefeltet og skrivefeltets plassholder.
registerChannelTypeOptionKanaltyper i dialogen Opprett kanal.
registerSearchComponentsSøkeboksen i kanalens overskrift: app-områder vises når den er tom.
registerUserGuideDropdownMenuActionOm Antimatter (nederst i appfeltet), under Hjelp.
registerProductFullstendige sider på produktets URL, listet under Produkter i Om Antimatter. Produktsider beholder den klassiske sidebunken, hvis produktmeny bare vises når en app registrerer et produkt eller legger til en post i det. De medfølgende Kanban- og Playbook-appene registrerer lenger ikke produkter: deres sider åpnes i deres sidepaneler.
registerRootComponent, registerGlobalComponent, krokker som registerMessageWillBePostedHook, registerSlashCommandWillBePostedHook, registerMessageWillFormatHook, registerFilesWillUploadHook, registerDesktopNotificationHook, registerWebSocketEventHandler, registerReducer, registerTranslationsVirker som i den klassiske grensesnittet.
registerAdminConsolePlugin, registerAdminConsoleCustomSettingSystemkonsollen, som er den klassiske i begge grensesnitt.

Det Fusion ikke viser eller kjører

  • registerPostDropdownMenuComponent (en komponent i meldingsmenyen), registerPostCardTypeComponent (en meldings kort i panelet til høyre) og registerProductSwitcherMenuItem vises ikke i Fusions hovedvisninger. Bruk registerPostDropdownMenuAction i stedet for den første.
  • registerMessageWillBeUpdatedHook kjører ikke når en melding redigeres i Fusion. Kontroller endringer på serveren med MessageWillBeUpdated-kroken i stedet.
  • registerActionAfterChannelCreation kjører bare fra den klassiske dialogen for å opprette kanal, som Fusion åpner fra Flere alternativer… i dialogen Opprett kanal.
  • Komponenter i appfeltet vises uansett supportedProductIds.
  • Ruter fra registerNeedsTeamRoute og registerCustomRoute beholder den klassiske sidestrukturen, som produkter.

Krokker som bare Antimatter har

Disse registreringsmetodene finnes ikke i Mattermost. Kontroller at en metode finnes før du kaller den, slik at appen også fungerer der:

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');
}
MetodeHva den legger til
registerComposerMenuItem({text, icon, fusionIcon, action, shouldRender})Et element i +-menyen i meldingsboksen, i begge grensesnitt. action mottar {channelId, rootId}.
registerEmojiPickerTab({id, label, icon, fusionIcon, order, component, shouldRender, replacesGifPicker})En fane i meldingsboksens emojivalg, for eksempel GIF-er eller klistermerker. I Fusion kommer faner med en order under 10 før Emojifanen. Komponenten mottar channelId, rootId, filter, onFilterChange, onSelectDone, insertText, isFusion og, i Fusion, pickerKit (se nedenfor).
registerChannelViewPanel({matcher, component})En panel mellom kanalens overskrift og meldingene, for de kanalene som matcher(state, channel) velger. Den kan skjule meldingene og skrivefeltet (setMessagesVisible), slik talekanaler gjør.
registerSidebarChannelFooterComponent(component)En linje under en kanal i sidefeltet, for eksempel personene i kanalens samtale. Den mottar channel.
registerIntegrationsOption({title, description, fusionIcon, action, shouldRender})Kun Fusion: en oppføring på Integrasjoner-siden, ved siden av egne emojier. GIF-appen legger sine egne GIF-er og klistermerker der.

Legg til og administrer visninger i emojivalget

I Fusion får en emojivalg-fane en pickerKit-prop: komponentene Emojifanen bruker for sine egne «legg til»- og «administrer»-visninger, slik at fanens visninger ser like ut og holdes inne i valget. GIF- og Memes-appene bruker det for å legge til og administrere GIF-er, klistermerker og memesjmaloner. Det inneholder version (1), hookene usePickerView og useOnEscape, og komponentene PickerViewButtons (knappene + og administrer ved siden av søkeboksen), PickerViewFrame (en visning med sin bak-pil), PickerNotice, PickerField, PickerSearch, PickerDropZone og PickerManageList. Escape-tasten går tilbake fra en visning til fanens rutenett. Kontroller at propen er der: andre vertsmiljøer utelater den, og da beholder fanen sine egne dialoger. Typene finnes i webapp/fusion/src/types/store/plugins.ts (EmojiPickerTabProps) og komponentene i webapp/fusion/src/fusion/popovers/picker_views.tsx.

Panelet i et nytt vindu

Fusion gir komponenten for registerRightHandSidebarComponent to flere props, slik at Åpne i nytt vindu åpner panelet der det var, i stedet for på appens startside:

  • setPanelState(state): kall dette når panelet bytter plass, med en kort streng appen din definerer, for eksempel 'note/<id>' eller 'board/<id>/card/<id>', eller null på startsiden. Det går inn i vinduets adresse, så legg aldri inn en hemmelighet der. En tilstand på over 512 tegn, eller med kontrolltegn, forkastes.
  • initialPanelState: i det nye vinduet, tilstanden panelet hadde, eller undefined. Behandle det som upålitelig inndata: hent elementet på nytt som den påloggete personen, og vis appens startside eller en «ikke funnet»-visning hvis de ikke kan åpne det.

Tilstanden glemmes når panelet lukkes eller når et annet apps panel tar over. For å åpne et panels vindu selv, tar popoutRhsPlugin (også i window.WebappUtils.popouts) tilstanden som et valgfritt femte argument. Notat, Tavle, Gjøremål, Kanban og Playbooks bruker dette.

fusionIcon navngir et ikon fra Fusion-ikonsettet (for eksempel poll, sticker, calendar eller image; se webapp/fusion/src/fusion/components/icon). Fusion viser det i stedet for icon, slik at elementet passer med de andre.

Innstillinger

settings_schema i plugin.json beskriver appens side i Systemkonsoll › 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."}
  ]
}

Innstillingstyper: bool, text, longtext, number, dropdown, radio, generated (en tilfeldig hemmelighet), username og custom (tegnet av en komponent som webapp-delen registrerer med registerAdminConsoleCustomSetting). Innstillinger kan grupperes i sections.

Serverdelen leser dem inn i en struct hvis felter samsvarer med nøklene:

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
}

Administrators kan også sette dem i konfigurasjonen, under PluginSettings.Plugins.<app id>, med nøkler i små bokstaver (se administratørguiden).

Oversettelser

Antimatter er oversatt til 37 språk, og de medfølgende appene er det også. Webapp-delen gjør det slik:

  1. Skriv alle strenger med React Intl, med en ID og engelsk tekst: <FormattedMessage id='polls.create' defaultMessage='Create a poll'/> eller defineMessages. Prefiks ID-ene med appens navn.
  2. Ekstraher de engelske strengene til webapp/i18n/en.json (de medfølgende appene bruker formatjs extract).
  3. Legg til én fil per språk ved siden av den, med de samme ID-ene: fr.json, de.json, pt-BR.json, zh-CN.json…
  4. Gi dem til nettappen, som ber om brukerens språk:
    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);

En manglende streng faller tilbake til sin engelske defaultMessage. For strenger som serverdelen sender (kommandosvar, botmeldinger), laster pluginapi/i18n-pakkene oversettelsesfiler som leveres i bunken (for eksempel assets/i18n) og gir en lokalisator på hver brukers språk.

Bygg, installer og feilsøk

Du trenger Go og Node.js (versjonen i appens .nvmrc). I appens mappe:

KommandoHva det gjør
make distBygger serverdelen for alle plattformer og nettappdelen, og skriver bunken til dist/<app id>-<version>.tar.gz.
make testKjører Go- og JavaScript-testene.
make check-styleLinter koden (golangci-lint, ESLint) og sjekker TypeScript-typene.
make deployBygger, installerer deretter og aktiverer bunken på en server.
make watch, make deploy-from-watchDen første bygger nettappdelen hver gang du lagrer; den andre pakker inn det den bygget og installerer det på serveren.
make enable, make disable, make resetAktiver, deaktiver eller omstart appen på serveren.
make logs, make logs-watchViser appens linjer i serverloggen.
make attachFester Delve-feilsøkeren til den kjørende serverdelen (bygg med AM_DEBUG=1 først).

make deploy og de andre serverkommandoene bruker en lokal modus sokkel når de finner én (/var/tmp/antimatter_local.socket, eller AM_LOCALSOCKETPATH): kjør dem på serverens maskin med lokal modus på. Ellers logger de seg inn på serveren gitt av MM_SERVICESETTINGS_SITEURL med AM_ADMIN_TOKEN, eller AM_ADMIN_USERNAME og AM_ADMIN_PASSWORD. Byggevårene leser ennå ikke et AM_-navn for nettsted-URL-en.

Installer en bunke manuelt

  • En systemadministrator tillater opplastinger med AM_PLUGINSETTINGS_ENABLEUPLOADS=true (av som standard; det finnes ingen Systemkonsoll-bryter).
  • Deretter Systemkonsoll › Plugins › Pluginhåndtering › Last opp plugin, eller amctl plugin add weather.tar.gz etterfulgt av amctl plugin enable com.example.weather.
  • Hvis serveren har Krev pluginsignatur slått på, godtar den bare pakker som er signert med en nøkkel den stoler på; se Installerer andre apps.
For raske iterasjoner på en utviklingsserver, sett AM_SERVICESETTINGS_ENABLEDEVELOPER=true på serveren: feil bærer da sine detaljer. De medfølgende appenes Makefiler bygger serverdelen kun for din plattform når MM_SERVICESETTINGS_ENABLEDEVELOPER er satt i din shell.