Skrivning af apps (plugins)

Apps tilføjer funktioner til Antimatter indefra: en serverdel i Go, der krog ind i serveren, og en web app-del i React, der tilføjer menuer, paneler, beskedtyper og sider. Denne side viser, hvordan en app er opbygget, ved at bruge den medfølgende Polls-app som eksempel, og hvad der ændres i Antimatters Fusion-grænseflade.

Sådan fungerer en app

  • Serverdelen er et Go-program. Serveren starter det som en separat proces og kommunikerer med det via RPC: den kalder appens hooks (en besked er ved at blive sendt, en kommando blev kørt…), og appen kalder serverens plugin API (opret en besked, læs en bruger, gem data…). Den kan også servere HTTP-ruter under /plugins/<app id>/.
  • Web app-delen er en JavaScript-bundel, der indlæses i browseren sammen med web appen. Den registrerer React-komponenter og callbacks med registret: en knap i en menu, et panel, en måde at tegne en beskedtype på.
  • Hver del er valgfri. En app installeres som en .tar.gz-bundel, og systemadministratorer aktiverer, deaktiverer og konfigurerer den.
Serverdelen kører med fuld adgang til serverens data. Behandle en app som kode, du udruller til serveren.

Antimatter kører apps skrevet til Mattermost, og omvendt, så længe en app kun bruger hooks, som begge har. Se Hooks, som kun Antimatter har.

Bundlen layout

Der er ingen separat starter-skabelon: start med en lille medfølgende app som antimatter-plugin-polls eller antimatter-plugin-todo, og ændr dens ID og kode. Polls er opbygget på denne måde:

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

Valgfrit: en public/-mappe, der serveres som den er under /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 domænenavn er almindeligt. Det navngiver ruterne, de gemte indstillinger og WebSocket-hændelserne.
  • version: semantisk version. De medfølgende apps udelader den fra plugin.json; deres byggereværk henter den fra den seneste v* git-tag.
  • min_server_version: Antimatter rapporterer 12.0.0.
  • settings_schema: appens indstillings-side i Systemkonsolen (se Indstillinger).

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

Serverdelen

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

package main

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

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

Plugin-typen indlejrer plugin.MattermostPlugin, som giver den p.API, og implementerer de hooks, den har brug for:

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

Almindelige hooks

HookNår den køres
OnActivate, OnDeactivateNår appen aktiveres eller deaktiveres, og når serveren startes og stoppes. Registrer kommandoer og bots i OnActivate.
OnConfigurationChangeNår dens indstillinger ændres; indlæs dem med p.API.LoadPluginConfiguration(&cfg).
ServeHTTPAnmodninger til /plugins/<app id>/…. Serveren sætter Mattermost-User-Id-headeren, når anmodningen kommer fra en indlogget bruger; afvis anmodninger uden den, medmindre de er beregnet til at være offentlige.
ExecuteCommandEn kommando, som appen har registreret, blev kørt.
MessageWillBePosted, MessageWillBeUpdatedFør en besked gemmes: returner en ændret besked eller en grund til at afvise den.
MessageHasBeenPosted, MessageHasBeenUpdated, MessageHasBeenDeletedEfter det faktiske, for sideeffekter.
ReactionHasBeenAdded, ReactionHasBeenRemovedReaktioner.
UserHasJoinedChannel, UserHasLeftChannel, UserHasJoinedTeam, UserHasLeftTeam, ChannelHasBeenCreatedMedlemskab og kanalændringer. Et team er et fællesskab.
FileWillBeUploadedFør en fil gemmes: ændr den eller afvis den.
OnPluginClusterEventEn besked fra samme app på en anden server i en kluster.

Den fulde liste, med de præcise signaturer, findes i server/public/plugin/hooks.go; API'et findes i server/public/plugin/api.go. pluginapi-pakken (server/public/pluginapi) omslutter API'et i mere brugervenlige hjælpere.

Nyttige API-opkald

  • CreatePost, UpdatePost, SendEphemeralPost: opret en besked som appens bot eller som en bruger.
  • EnsureBotUser(&model.Bot{Username, DisplayName}): opret appens bot én gang og få dens ID.
  • PublishWebSocketEvent("poll_updated", payload, &model.WebsocketBroadcast{ChannelId: id}): send en hændelse til webappen. Den ankommer som custom_<app id>_poll_updated.
  • OpenInteractiveDialog: åbn en dialog.
  • LogInfo, LogError og andre: skriv til serverloggen, mærket med appens ID.
  • GetUser, GetChannel, HasPermissionToChannel: tjek, hvem der må gøre hvad, før du handler for en bruger.

En app kører på hver server i en kluster. Gem tilstand i nøgle-værdi-lageret eller i databasen, ikke kun i hukommelsen.

AI-tjenesten

Antimatters server kalder AI-modeller for apps via de modelforbindelser, som en administrator har opsat (se AI i administratørvejledningen). En app indeholder aldrig en udbyders nøgle: den angiver den person, den handler på vegne af, og hvor, og serveren tjekker, at denne person må bruge forbindelsen der, anvender sine begrænsninger og budgetter, tæller tokens mod personen og appen, og reviderer opkaldet uden dets indhold.

MetodeHvad den gør
AIChat(actingUserID, channelID, teamID, *ai.ChatRequest)En chatfuldførelse: beskeder, værktøjer, struktureret output (JSONSchema).
AIChatStream(…)Det samme, strømmet: læs strømningens Events, indtil kanalen lukker, eller Close den for at stoppe opkaldet.
AIEmbed(…, *ai.EmbedRequest)Indlejring af tekster.
AITranscribe(…, *ai.TranscribeRequest)Tale til tekst.
AIDecide(…, *ai.DecideRequest)Typeindtastede spørgsmål til en beslutningsmodel (ja eller nej, et valg, en score) om en tekst.
AIConnectionsForUser(userID, channelID, teamID)De forbindelser, en person må bruge der, med deres modeller og funktioner, men aldrig deres nøgler.

Hver anmodnings ConnectionID vælger forbindelsen, og Model vælger en af dens modeller (tom for forbindelsens standard). Operation er en kort etiket til brugs- og revisionsposter, aldrig indhold. Deres mindste serverversion er 11.11.

Tag kun actingUserID fra en kilde, som serveren har verificeret: Mattermost-User-Id-headeren i en anmodning til din ServeHTTP, eller brugeren af en hooks kontekst (en beskedforfatters, brugeren, der udførte en kommando). Tag den aldrig fra en besked, en webhook-payload, en URL eller en anmodning fra en anden app: din app ville bruge en andens adgang.

Disse metoder og ai-pakken (server/public/ai) findes kun i Antimatters kopi af modulet, ikke i den version, der er udgivet af Mattermost. For at bruge dem skal du bygge mod Antimatters server/public med en replace-linje i din go.mod, for eksempel replace github.com/mattermost/mattermost/server/public => ../antimatter/server/public. En sådan app kører kun på Antimatter.

At give agenter værktøjer

En app stiller sine egne værktøjer til rådighed for agenter med RegisterAITools([]*model.AIToolDefinition): hvert værktøjs navn, titel, beskrivelse, input-JSON-skema og type (read, write eller external_write), hvilket afgør, om en agents politik kan køre det selvstændigt (se Værktøjer i administratorens guide). Tilladelserne kontrolleres ud fra et kanal- eller community-argument i opkaldet eller på serveren, afhængigt af hvad værktøjet kræver. Når en agent bruger værktøjet, kalder serveren ExecuteAITool(c, *model.AIToolExecution) *model.AIToolResult-hooket med den verificerede person, der anmodede, angivet i UserId: handle kun med deres tilladelser, aldrig agentens. Værktøjer navngives <app id>.<name>; minimum serverversion 11.11; disse metoder og hooket findes kun i Antimatters server/public, ligesom AI-tjenesten ovenfor. En app, hvis ID er antimatter, eller som starter med mcp., kan ikke registrere værktøjer.

Lagring af data

Hver app har sit eget nøgle-værdi-lager i serverens database:

  • KVSet(key, value), KVGet(key), KVDelete(key): værdierne er bytes, typisk JSON. Nøglerne må højst være 150 tegn lange.
  • KVSetWithExpiry(key, value, seconds): værdien forsvinder efter den angivne tid.
  • KVCompareAndSet(key, old, new) og KVSetWithOptions med Atomic: sikre opdateringer, når flere anmodninger eller servere skriver til samme nøgle. Polls gemmer hver stemme på denne måde og prøver igen ved konflikt.
  • KVList(page, perPage): nøglerne, side for side.

Webapp-delen

Bundlen registrerer en klasse med window.registerPlugin. Dens initialize modtager 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());
  • Delte biblioteker. Webappen stiller React, ReactDOM, Redux, React Redux og React Intl til rådighed: oplist dem som webpack externals (React, ReactDOM, Redux, ReactRedux, ReactIntl) i stedet for at bundling dine egne kopier. Se Polls' webpack.config.js.
  • Kald til din serverdel. Hent ${window.basename}/plugins/<app id>/… med indstillingerne fra Client4.getOptions() (fra mattermost-redux/client): de bærer de headers, serveren har brug for for at acceptere en anmodning fra browseren.
  • Din egen tilstand. registerReducer tilføjer en reducer, der gemmes under state['plugins-<app id>'].
  • Typetyper. Antimatters ekstra registreringsmetoder er ikke med i Mattermosts udgivne typetyper: Polls erklærer det register, det bruger, i webapp/src/types/host.ts.

Registrerets metoder og deres parametre er dokumenteret i koden, i webapp/fusion/src/plugins/registry.ts (Fusion) og webapp/channels/src/plugins/registry.ts (klassisk).

Understøttelse af Fusion-grænsefladen

Antimatter har to webgrænseflader: Fusion, der er standarden, og den klassiske. Begge indlæser de samme app-bundles og har det samme register, men Fusion tegner mange ting selv og placerer komponenter fra plugins på sin egen måde. Test din app i begge.

Afkend grænsefladen

Før nogen app indlæses, sætter begge grænseflader window.antimatterWebUI til 'fusion' eller 'classic', og den samme værdi på sideens rod-element som data-am-web-ui. På Mattermost er ingen af dem sat.

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

De medfølgende apps bruger dette til at tegne forskelligt. Afstemninger tegner sine afstemningskort i Fusion-stil, og i Fusion skrives afstemningen i kompositionsfeltet (en kompositionstilstand) i stedet for i en dialog. Opkald og lydkanaler udelader sidebjælke- og toast-komponenterne, som Fusion erstatter med sin egen opkaldsgrænseflade. AI-chat-appen placerer sine AI-handlinger i beskeds menuen, som en gruppe under Flere handlinger, i stedet for i værktøjslinjen ved mouseover. Fusions egne CSS-klasser (med præfikset am-) og dets dialoglag (elementet am-layer) er det, disse apps bruger til at matche grænsefladen; de er ikke en dokumenteret, stabil API.

Hvor Fusion viser plugin-komponenter

RegistreringsmetodeHvor det vises i Fusion
registerAppBarComponent, registerChannelHeaderButtonActionApp-spor, kolonnen med ikoner på højre kant, mellem din avatar og Om Antimatter-knappen. Kanaloverskriftsknapperne findes også her, ikke i kanaloverskriften. Skjules, når en systemadministrator aktiverer Deaktiver Apps Bar (ExperimentalSettings.DisableAppBar, standard er deaktiveret).
registerRightHandSidebarComponentPanelet til højre. Dets overskrift er Fusions, den samme for alle apps: appens ikon og navn (den registrerede titel, når den kun tegner tekst, ellers app-skinnets etiket eller manifestets navn), derefter Gendan panelets størrelse (når det er ændret), Åbn i et nyt vindue og Luk. En titel, der tegner noget andet (en kontrol, et link, et billede), ignoreres, så tegn din apps egne kontroller i selve panelet, for eksempel i en smal række øverst. Brugere kan ændre panelets størrelse ved at trække i dets kant. Se Panelet i et nyt vindue.
registerChannelHeaderIconIkoner i kanalens overskrift, efter opkaldsknapperne; på telefoner i overskriftens Mere-menu.
registerCallButtonActionKanalens overskrift opkaldsknap; flere leverandører deler en Andre måder at ringe på-menu.
registerPostDropdownMenuActionMenuen Beskedhandlinger (⋯) for beskeden, i undermenuen Flere handlinger; også i menuen Trådhandlinger i trådpanelet, under Flere handlinger. Fusion tegner appens ikon før hver handling i grå farve: sit eget ikon for de integrerede apps, ellers det ikon, appen viser i appskinnen, og hvis ikke, et neutralt ikon. Et element uden tekst i starten af handlingens tekst (et ikon, som appen selv tegner) skjules.
registerPostDropdownSubMenuActionEn gruppe af indgange i den samme undermenu Flere handlinger, efter de enkelte handlinger, adskilt af en linje, uden overskrift. Ingen app får sin egen indgang i beskedsmenuen.
registerPostActionComponentBeskedens hover-værktøjslinje, før ⋯-knappen.
registerPostTypeComponent, registerPostMessageAttachmentComponent, registerPostHeaderComponent, registerPostWillRenderEmbedComponentSelve beskeden: dens krop, under dens tekst, efter dens tidspunkt, og i stedet for link-forhåndsvisninger.
registerChannelHeaderMenuActionKanalens Kanalvalgmuligheder menu (⋯ på en kanal i sidebjælken, eller højreklik), som elementer med et stik-ikon.
registerMainMenuActionFællesskabets menu (fællesskabets navn øverst i kanallisten), under Flere handlinger, grupperet efter app.
registerSidebarBrowseOrAddChannelMenuActionFællesskabets menu, efter Opret kanal og Opret kategori.
registerFileUploadMethodSkrivefeltets + menu (Tilføj til besked), efter Upload en fil.
registerPostEditorActionComponentSkrivefeltets formateringslinje, når formatering vises.
registerAIActionMenuItemComponentSkrivefeltets AI menu.
registerFileDropdownMenuAction, registerFilePreviewComponentEn filens optionsmenu og fil-forhåndsvisning.
registerUserSettingsProfil & indstillinger, en fane pr. app under Plugins overskriften.
registerChannelSettingsTabKanalens indstillingsdialog, under Apps overskriften.
registerPopoverUserAttributesComponent, registerPopoverUserActionsComponentProfilkortet, der vises, når du klikker på et navn eller en avatar.
registerLeftSidebarHeaderComponent, registerSidebarChannelLinkLabelComponent, registerChannelIconOverride, registerBottomTeamSidebarComponentToppen af en fællesskabs kanalliste, ved siden af kanalens navn, kanalens ikon og bunden af fællesskabsbjælken til venstre.
registerChannelIntro, registerChannelIntroButtonAction, registerChannelToastComponent, registerNewMessagesSeparatorActionComponent, registerChannelComposerBannerComponent, registerComposerPlaceholderStarten på en kanals historik, toppen af beskedlisten, den ulæste linje, over skrivefeltet og skrivefeltets pladsholder.
registerChannelTypeOptionKanaltyperne i Opret kanal-dialogen.
registerSearchComponentsSøgefeltet i kanalens overskrift: app-områder vises, når det er tomt.
registerUserGuideDropdownMenuActionOm Antimatter (bund af app-bjælken), under Hjælp.
registerProductFulde sider på produktets URL, i den klassiske sideramme, uden en produktmenu: Fusion har ingen og viser ikke nogen liste over produkter, så du skal selv oprette linket til siden. Ingen integreret app registrerer et produkt: Kanban, Playbooks og AI-chat åbnes i deres sidepaneler.
registerRootComponent, registerGlobalComponent, hooks såsom registerMessageWillBePostedHook, registerSlashCommandWillBePostedHook, registerMessageWillFormatHook, registerFilesWillUploadHook, registerDesktopNotificationHook, registerWebSocketEventHandler, registerReducer, registerTranslationsVirker som i det klassiske grænseflade.
registerAdminConsolePlugin, registerAdminConsoleCustomSettingSystemkonsolen, som er den klassiske i begge grænseflader.

Det Fusion ikke viser eller kører

  • registerPostDropdownMenuComponent (en komponent i beskedsmenuen) og registerPostCardTypeComponent (et opslags kort i højre panel) vises ikke i Fusions hovedvisninger. registerProductSwitcherMenuItem gør intet i Fusion, som ikke har nogen produktmenu (den logger en advarsel). Brug registerPostDropdownMenuAction i stedet for den første.
  • registerMessageWillBeUpdatedHook kører ikke, når en besked redigeres i Fusion. Tjek redigeringer på serveren med MessageWillBeUpdated-hooket i stedet.
  • registerActionAfterChannelCreation kører kun fra den klassiske kanalopretning-dialog, som Fusion åbner fra Flere muligheder… i sin Opret kanal-dialog.
  • Komponenter i app-bjælken vises uanset deres supportedProductIds.
  • Ruter fra registerNeedsTeamRoute og registerCustomRoute beholder den klassiske side-ramme, ligesom produkter.

Hooks, som kun Antimatter har

Disse registry-metoder findes ikke på Mattermost. Tjek, at en metode findes, før du kalder den, så appen også virker 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');
}
MetodeHvad den tilføjer
registerComposerMenuItem({text, icon, fusionIcon, action, shouldRender})Et element i beskedfeltets +-menu, i begge grænseflader. action modtager {channelId, rootId}.
registerEmojiPickerTab({id, label, icon, fusionIcon, order, component, shouldRender, replacesGifPicker})En fane i beskedfeltets emojivælger, f.eks. GIF'er eller stickers. I Fusion kommer faner med en order under 10 før fanen Emoji. Komponenten modtager channelId, rootId, filter, onFilterChange, onSelectDone, insertText, isFusion og, i Fusion, pickerKit (se nedenfor).
registerChannelViewPanel({matcher, component})Et panel mellem kanalens overskrift og beskederne, for de kanaler, som matcher(state, channel) vælger. Det kan skjule beskederne og beskedfeltet (setMessagesVisible), som lydkanaler gør.
registerSidebarChannelFooterComponent(component)En linje under en kanal i sidebjælken, f.eks. de personer, der er i dens opkald. Den modtager channel.
registerIntegrationsOption({title, description, fusionIcon, action, shouldRender})Kun Fusion: et element på Integrationer-siderne, ved siden af brugerdefinerede emoji. GIF-appen tilføjer sine brugerdefinerede GIF'er og klistermærker der.

Tilføj og administrer visninger i emojivælgeren

I Fusion får et emoji-vælgerfaneblad et pickerKit-prop: de komponenter, som Emoji-fanebladet bruger til sine egne visninger for tilføjelse og administrering, så fanebladets visninger ser ens ud og bliver inde i vælgeren. Appsene GIFs og Memes bruger det til at tilføje og administrere GIFs, stickers og meme-skabeloner. Det indeholder version (1), hooksene usePickerView og useOnEscape samt komponenterne PickerViewButtons (knapperne + og administrér ved siden af søgefeltet), PickerViewFrame (en visning med sin pil tilbage), PickerNotice, PickerField, PickerSearch, PickerDropZone og PickerManageList. Escape går tilbage fra en visning til fanebladets gitter. Tjek, at propet er til stede: andre værter udelader det, og fanebladet beholder så sine egne dialogbokse. Typerne findes i webapp/fusion/src/types/store/plugins.ts (EmojiPickerTabProps) og komponenterne i webapp/fusion/src/fusion/popovers/picker_views.tsx.

Kompositionstilstande

I Fusion tilføjer registerComposerMode({id, label, fusionIcon, placeholder, sendLabel, component, shouldRender}) en anden måde at skrive en besked på i kompositionsfeltet, ligesom Polls gør for en afstemning. Tilstanden tilbydes i kompositionsfeltets +-menu under dens label. Når den er valgt, beholder kompositionsfeltet sin tekstboks (med tilstandens placeholder: afstemningens spørgsmål), viser dit component under den og en bjælke, der navngiver tilstanden med en Annullér-knap; Send bruger sendLabel og udfører din indsendelse i stedet for at sende teksten.

  • Komponenten modtager channelId, rootId (tråden, i et svarfelt), message (kompositionsfeltets tekst), setSubmit og onCancel (tilbage til en almindelig besked, hvor teksten bevares).
  • Giv setSubmit en funktion, der kaldes med kompositionsfeltets tekst og løser til intet (eller en tom streng), når den er sendt: kompositionsfeltet tømmes derefter og vender tilbage til en almindelig besked. Løs til en streng for at vise den som en fejl på stedet.
  • placeholder og sendLabel er strenge eller funktioner af react-intls intl. shouldRender(state, {channelId, rootId}) afgør, om et kompositionsfelt tilbyder tilstanden.
  • For at åbne tilstanden fra et andet sted, f.eks. via en skråstregskommando uden argumenter: window.dispatchEvent(new CustomEvent('am-composer-mode', {detail: {id, channelId, rootId}})).

Kun Fusion understøtter kompositionstilstande: Tjek, om registreringen indeholder funktionen, og behold ellers din egen dialog. Metoden returnerer tilstandens id, og tilstanden forsvinder, når appen fjernes.

Panelet i et nyt vindue

Fusion giver komponenten fra registerRightHandSidebarComponent to ekstra props, så Åbn i et nyt vindue åbner panelet, hvor det var, i stedet for ved appens startside:

  • setPanelState(state): Kald den, når panelet skifter placering, med en kort streng, som din app definerer, f.eks. 'note/<id>' eller 'board/<id>/card/<id>', eller null ved startside. Tilstanden føjes til vinduets adresse, så anbring aldrig fortrolige oplysninger i den. En tilstand, der er længere end 512 tegn eller indeholder kontroltegn, ignoreres.
  • initialPanelState: I det nye vindue er dette den tilstand, panelet havde, eller undefined. Behandl det som utroværdig input: Hent elementet igen som den loggede ind person, og vis appens startside eller en "ikke fundet"-visning, hvis de ikke kan åbne det.

Tilstanden glemmes, når panelet lukkes, eller når et panel fra en anden app erstatter det. For selv at åbne et panels vindue, tager popoutRhsPlugin (også tilgængelig via window.WebappUtils.popouts) tilstanden som et valgfrit femte argument. Notes, Whiteboard, Todo, Kanban og Playbooks bruger denne metode.

fusionIcon angiver et ikon fra Fusions ikonsæt (f.eks. poll, sticker, calendar eller image; se webapp/fusion/src/fusion/components/icon). Fusion viser det i stedet for icon, så elementet matcher de andre.

Indstillinger

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

Indstillingstyper: bool, text, longtext, number, dropdown, radio, generated (en tilfældig hemmelig kode), username og custom (tegnet af en komponent, som webapp-delen registrerer med registerAdminConsoleCustomSetting). Indstillinger kan grupperes i sections.

Serverdelen læser dem ind i en struktur, hvis felter matcher nøglerne:

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å sætte dem i konfigurationen under PluginSettings.Plugins.<app id>, med nøgler i små bogstaver (se administrationsguiden).

Oversættelser

Antimatter er oversat til 37 sprog, og de medfølgende apps er det også. Webapp-delen gør det sådan:

  1. Skriv hver streng med React Intl, med et ID og dens engelske tekst: <FormattedMessage id='polls.create' defaultMessage='Create a poll'/> eller defineMessages. Præfiksér ID'erne med appens navn.
  2. Udtræk de engelske strenger til webapp/i18n/en.json (de medfølgende apps bruger formatjs extract).
  3. Tilføj én fil per sprog ved siden af, med de samme ID'er: fr.json, de.json, pt-BR.json, zh-CN.json…
  4. Giv dem til webappen, som beder om brugerens sprog:
    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 falder tilbage til sin engelske defaultMessage. For strenger, som serverdelen sender (kommandosvar, botbeskeder), indlæser pluginapi/i18n-pakken oversættelsesfiler leveret i bundlen (for eksempel assets/i18n) og giver en lokaliserer i hver brugers sprog.

Byg, installer og fejlfind

Du har brug for Go og Node.js (versionen i appens .nvmrc). I appens mappe:

CommandHvad den gør
make distBygger serverdelen til alle platforme og webapp-delen, og skriver bundlen til dist/<app id>-<version>.tar.gz.
make testKører Go- og JavaScript-testene.
make check-styleLinter koden (golangci-lint, ESLint) og tjekker TypeScript-typene.
make deployBygger, installerer og aktiverer derefter bundlen på en server.
make watch, make deploy-from-watchDen første bygger webapp-delen hver gang du gemmer; den anden pakker det, den byggede, og installerer det på serveren.
make enable, make disable, make resetAktiver, deaktiver eller genstart appen på serveren.
make logs, make logs-watchVis appens linjer i serverloggen.
make attachVedhæfter Delve-fejlfinderen til den kørende serverdel (byg først med AM_DEBUG=1).

make deploy og de andre serverkommandoer bruger en lokal modesocket, når de finder én (/var/tmp/antimatter_local.socket, eller AM_LOCALSOCKETPATH): kør dem på serverens maskine med lokal mode til. Ellers logger de ind på serveren angivet af MM_SERVICESETTINGS_SITEURL med AM_ADMIN_TOKEN, eller AM_ADMIN_USERNAME og AM_ADMIN_PASSWORD. Bygværktøjerne læser endnu ikke et AM_-navn for site-URL'en.

Installer en bundle manuelt

  • En systemadministrator tillader uploads med AM_PLUGINSETTINGS_ENABLEUPLOADS=true (fra som standard; der er ingen System Console-afbryder).
  • Så Systemkonsol › Plugins › Plugin-håndtering › Upload plugin, eller amctl plugin add weather.tar.gz efterfulgt af amctl plugin enable com.example.weather.
  • Hvis serveren har Kræv pluginsignatur slået til, accepterer den kun pakker signeret med en nøgle, den stoler på; se Installation af andre apps.
For hurtige iterationer på en udviklingsserver, sæt AM_SERVICESETTINGS_ENABLEDEVELOPER=true på serveren: fejl bærer derefter deres detaljer. De bundlende apps' Makefiles bygger serverdelen kun til din platform, når MM_SERVICESETTINGS_ENABLEDEVELOPER er sat i din shell.