Pagsulat ng mga app (plugin)

Ang mga app ay nagdaragdag ng mga feature sa Antimatter mula sa loob: may server part na Go na nakahook sa server, at may web app part na React na nagdaragdag ng mga menu, panel, uri ng mensahe, at mga pahina. Ipinapakita ng pahinang ito kung paano nakalagay ang isang app, gamit ang bundled na Polls app bilang halimbawa, at ano ang nagbabago sa Fusion interface ng Antimatter.

Paano gumagana ang isang app

  • Ang server part ay isang Go program. Ipinapalaganap nito ng server bilang isang magkakaibang process at nakikipag-usap dito sa pamamagitan ng RPC: tinatawag nito ang mga hooks ng app (may mensahe na magpupost, may binibigay na command…), at tinatawag ng app ang plugin API ng server (gumawa ng post, basahin ang user, i-store ang data…). Maaari rin nitong mag-serve ng mga HTTP routes sa ilalim ng /plugins/<app id>/.
  • Ang web app part ay isang JavaScript bundle na nilalagay sa browser kasama ang web app. Ipinapahanda nito ang mga React components at callbacks sa registry: isang button sa menu, isang panel, o paraan ng pagguhit ng uri ng mensahe.
  • Ang alinman sa bahagi ay opsyonal. Ang isang app ay inii-install bilang .tar.gz bundle, at pinapagana, pinipigil, at pinapaandar nito ng mga system admin.
Ang server part ay tumatatakbo na may buong access sa data ng server. Ituring ang isang app na parang code na idedeploy mo sa server.

Pinapalaganap ng Antimatter ang mga app na nakasulat para sa Mattermost, at sa kabaligtaran, hangga't gumagamit lamang ng mga hooks na mayroon silang pareho. Tingnan ang Mga hooks na Antimatter lang ang mayroon.

Ang layout ng bundle

Wala nang hiwalay na starter template: magsimula mula sa isang maliit na bundled app tulad ng antimatter-plugin-polls o antimatter-plugin-todo, at baguhin ang ID at code nito. Nakalagay ang Polls na ganito:

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

Opsyonal: isang public/ folder, na isinaserve nang ganito sa /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: natatangi at permanente; karaniwang reverse domain name. Ito ang nagpapaalam sa mga routes, mga tinatag na setting, at mga WebSocket events.
  • version: semantic version. Ang mga bundled app ay hindi ito ilalagay sa plugin.json; kinukuha nito ng kanilang build tool mula sa pinakabagong v* git tag.
  • min_server_version: Inuulat ng Antimatter ang 12.0.0.
  • settings_schema: ang pahina ng mga setting ng app sa System Console (tingnan ang Mga Setting).

Ang make apply ay kopyahin ang manifest sa server/manifest.go at webapp/src/manifest.ts; ang make manifest-check ay i-verify ito.

Ang bahagi ng server

Ang bahagi ng server ay nag-i-import ng parehong Go module na ginagamit ng mga Mattermost apps, ang github.com/mattermost/mattermost/server/public, mula sa Go module proxy. Ang main nito ay nagpapaanod lamang ng app:

package main

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

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

Ang Plugin type ay nag-embed ng plugin.MattermostPlugin, na nagbibigay nito ng p.API, at nag-i-implement ng mga hooks na kailangan nito:

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

Mga karaniwang hooks

HookKapag tumatakbo
OnActivate, OnDeactivateKapag na-enable o na-disable ang app, at kapag nagpapaanod at nagpapatigil ang server. I-register ang mga command at bot sa OnActivate.
OnConfigurationChangeKapag nagbago ang mga setting nito; i-load ang mga ito gamit ang p.API.LoadPluginConfiguration(&cfg).
ServeHTTPMga request sa /plugins/<app id>/…. I-set ng server ang Mattermost-User-Id header kapag mula sa naka-sign-in na user ang request; tanggihan ang mga request na wala ito maliban kung pampubliko ang layunin nito.
ExecuteCommandIsang command na naka-register ng app ay tinakbo.
MessageWillBePosted, MessageWillBeUpdatedBago ma-save ang mensahe: ibalik ang binagong post, o isang dahilan para itanggihan.
MessageHasBeenPosted, MessageHasBeenUpdated, MessageHasBeenDeletedPagkatapos ng gawa, para sa mga side effects.
ReactionHasBeenAdded, ReactionHasBeenRemovedMga Reaksyon.
UserHasJoinedChannel, UserHasLeftChannel, UserHasJoinedTeam, UserHasLeftTeam, ChannelHasBeenCreatedMga pagbabago sa membership at channel. Ang team ay isang komunidad.
FileWillBeUploadedBago ma-store ang file: baguhin o tanggihan ito.
OnPluginClusterEventMensahe mula sa parehong app sa ibang server ng cluster.

Ang buong listahan, kasama ang eksaktong signatures, ay nasa server/public/plugin/hooks.go; ang API ay nasa server/public/plugin/api.go. Ang pluginapi package (server/public/pluginapi) ay nag-wrap ng API sa mas magaan na mga helper.

Mga kapaki-pakinabang na tawag sa API

  • CreatePost, UpdatePost, SendEphemeralPost: mag-post bilang bot ng app o bilang user.
  • EnsureBotUser(&model.Bot{Username, DisplayName}): gumawa ng bot ng app isang beses at kunin ang ID nito.
  • PublishWebSocketEvent("poll_updated", payload, &model.WebsocketBroadcast{ChannelId: id}): magpadala ng event sa web app. Darating ito bilang custom_<app id>_poll_updated.
  • OpenInteractiveDialog: buksan ang dialog.
  • LogInfo, LogError at mga katulad nito: mag-write sa server log, may tag ng ID ng app.
  • GetUser, GetChannel, HasPermissionToChannel: suriin kung sino ang maaaring gawin bago kumilos para sa isang user.

Ang app ay tumatakbo sa bawat server ng cluster. Iimbak ang state sa key-value store o database, hindi lamang sa memory.

Ang AI service

Tinatawag ng server ng Antimatter ang mga AI model para sa mga app, gamit ang mga model connection na itinatag ng admin (tingnan ang AI sa admin guide). Hindi hawak ng app ang key ng provider: tinatawag niya ang tao na kung saan siya ay kumikilos at saan, at tinutukoy ng server na maaari bang gamitin ng tao ang connection sa lugar na iyon, inilalapat ang mga limitasyon at budget nito, binibilang ang mga token laban sa tao at sa app, at sinasagawa ang audit ng tawag nang walang nilalaman nito.

MethodGinagawa nito
AIChat(actingUserID, channelID, teamID, *ai.ChatRequest)Isang chat completion: mga mensahe, mga tool, structured output (JSONSchema).
AIChatStream(…)Ito rin, na streamed: basahin ang Events ng stream hanggang sa mag-close ang channel, o Close ito upang itigil ang tawag.
AIEmbed(…, *ai.EmbedRequest)Mga embedding ng mga teksto.
AITranscribe(…, *ai.TranscribeRequest)Speech to text.
AIDecide(…, *ai.DecideRequest)Mga tinipang tanong sa isang decision model (oo o hindi, isang pagpili, isang score) tungkol sa isang teksto.
AIConnectionsForUser(userID, channelID, teamID)Ang mga connection na maaaring gamitin ng tao sa lugar na iyon, kasama ang mga model at capabilities nito, ngunit hindi ang mga key nito.

Ang ConnectionID ng bawat request ay pumipili ng connection, at ang Model ay isa sa mga model nito (walang laman para sa default ng connection). Ang Operation ay maikling label para sa mga record ng paggamit at audit, hindi ang nilalaman. Ang minimum server version nito ay 11.11.

Kunin ang actingUserID lamang mula sa pinagmulan na binigyan ng tawag ng server: ang Mattermost-User-Id header ng isang request sa iyong ServeHTTP, o ang user ng context ng isang hook (ang may-akda ng isang post, ang user na nagpatakbo ng isang command). Huwag kunin mula sa isang mensahe, isang webhook payload, isang URL, o request ng ibang app: gagastusin ng iyong app ang access ng ibang tao.

Ang mga method na ito at ang ai package (server/public/ai) ay umiiral lamang sa kopya ng Antimatter ng module, hindi sa bersyon na ipinubliko ng Mattermost. Upang gamitin ang mga ito, i-build laban sa server/public ng Antimatter gamit ang isang replace line sa iyong go.mod, halimbawa replace github.com/mattermost/mattermost/server/public => ../antimatter/server/public. Ang ganitong app ay tumatakbo lamang sa Antimatter.

Pagbibigay ng mga tool sa mga agent

Ang isang app ay nag-aalok ng sarili nitong mga tool sa mga agent gamit ang RegisterAITools([]*model.AIToolDefinition): ang pangalan, pamagat, paglalarawan, input JSON schema, at uri (read, write o external_write) ng bawat tool, na nagtatakda kung maaaring patakbuhin ito ng agent nang kusa ayon sa kanyang patakaran (tingnan ang Mga Tool sa admin guide). Ang mga pahintulot ay sinusuri sa isang argumento ng channel o community ng tawag, o sa server, depende sa kailangan ng tool. Kapag ginamit ng agent ang tool, tinatawag ng server ang ExecuteAITool(c, *model.AIToolExecution) *model.AIToolResult hook kasama ang pinatunayang taong humingi nito sa UserId: kumilos lamang gamit ang kanilang mga pahintulot, hindi ang mga pahintulot ng agent. Ang mga tool ay pinangalanan bilang <app id>.<name>; minimum na bersyon ng server ay 11.11; ang mga method na ito at ang hook ay umiiral lamang sa server/public ng Antimatter, tulad ng AI service sa itaas. Ang isang app na ang ID ay antimatter, o nagsisimula sa mcp., ay hindi maaaring magrehistro ng mga tool.

Pag-iimbak ng data

May sariling key-value store ang bawat app sa database ng server:

  • KVSet(key, value), KVGet(key), KVDelete(key): ang mga value ay bytes, karaniwang JSON. Ang mga key ay hindi higit sa 150 character.
  • KVSetWithExpiry(key, value, seconds): nawawala ang value pagkatapos ng oras na iyon.
  • KVCompareAndSet(key, old, new) at KVSetWithOptions na may Atomic: ligtas na pag-update kapag maraming request o server ang sumusulat sa parehong key. Ginagawa ng Polls ang pag-save ng bawat boto gamit ang paraan na ito, at nagre-retry sa pagkakaroon ng conflict.
  • KVList(page, perPage): ang mga key, pahina-pahina.

Ang bahagi ng web app

Nagre-register ang bundle ng isang class gamit ang window.registerPlugin. Ang kanyang initialize ay tumatanggap ng registry at 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());
  • Mga shared library. Nagbibigay ang web app ng React, ReactDOM, Redux, React Redux, at React Intl: ilista ang mga ito bilang webpack externals (React, ReactDOM, Redux, ReactRedux, ReactIntl) sa halip na mag-bundle ng sariling kopya. Tingnan ang webpack.config.js ng Polls.
  • Pag-tawag sa iyong bahagi ng server. Gamitin ang ${window.basename}/plugins/<app id>/… kasama ang mga opsyon mula sa Client4.getOptions() (mula sa mattermost-redux/client): dala nila ang mga header na kailangan ng server upang tanggapin ang request mula sa browser.
  • Iyong sariling state. Ang registerReducer ay nagdadagdag ng reducer, na nakaimbak sa ilalim ng state['plugins-<app id>'].
  • Mga type. Ang mga karagdagang registry method ng Antimatter ay wala sa mga published type ng Mattermost: inideklara ng Polls ang registry na ginagamit nito sa webapp/src/types/host.ts.

Ang mga method ng registry at ang kanilang mga parameter ay nakadokumento sa code, sa webapp/fusion/src/plugins/registry.ts (Fusion) at webapp/channels/src/plugins/registry.ts (classic).

Suportahan ang interface ng Fusion

May dalawang web interface ang Antimatter: ang Fusion, na default, at ang classic. Parehong naglo-load ng parehong app bundles at may parehong registry, ngunit ang Fusion ay nagdudraw ng maraming bagay sa sarili at nagpupunta ng plugin components sa kanyang paraan. Subukan ang iyong app sa parehong interface.

Pagtuklas ng interface

Bago mag-load ang anumang app, inilalagay ng parehong interface ang window.antimatterWebUI sa 'fusion' o 'classic', at ang parehong value sa root element ng page bilang data-am-web-ui. Sa Mattermost, wala sa dalawa ang inilalagay.

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

Ginagamit ng mga bundled app ang feature na ito upang mag-render nang iba-iba. Ang Polls ay nag-drawing ng mga poll card nito sa istilo ng Fusion, at sa Fusion ay ang poll ay nakasulat sa composer (isang composer mode) sa halip na sa isang dialog. Ang Calls at Voice channels ay hindi kasama ang mga component ng sidebar at toast na pinalitan ng Fusion ng sarili nitong interface para sa tawag. Ang AI chat app ay inilalagay ang mga AI action nito sa menu ng mensahe, bilang isang grupo sa ilalim ng Mga Karagdagang Aksyon, sa halip na sa hover toolbar. Ang mga CSS class ng Fusion (na may prefix na am-) at ang layer ng dialog nito (ang am-layer element) ang ginagamit ng mga app na ito upang tugma ang interface; hindi ito isang dokumentado at stable na API.

Saan ipinapakita ng Fusion ang mga plugin component

Registry methodSaan lumalabas ito sa loob ng Fusion
registerAppBarComponent, registerChannelHeaderButtonActionAng app rail, ang hanay ng mga icon sa kanang gilid, sa pagitan ng iyong avatar at ng Tungkol sa Antimatter button. Dito rin matatagpuan ang mga button ng channel header, hindi sa channel header mismo. Ito ay natatago kapag pinag-on ng system admin ang I-disable ang Apps Bar (ExperimentalSettings.DisableAppBar, off by default).
registerRightHandSidebarComponentAng panel sa kanang bahagi. Ang header nito ay sa Fusion, pareho para sa lahat ng app: ang icon at pangalan ng app (ang rehistradong pamagat kapag nag-drawing lamang ng teksto, kung hindi ay ang label ng app rail o ang pangalan ng manifest), pagkatapos ay Ibalik ang laki ng panel (kapag na-resize na), Buksan sa bagong window at Isara. Ang isang pamagat na nag-drawing ng anumang iba pa (isang control, link, o larawan) ay binabalewala, kaya iguhit ang mga sariling control ng app mo sa mismong panel, halimbawa sa isang makitid na row sa itaas nito. Maaaring i-resize ng mga tao ang panel sa pamamagitan ng pag-drag ng gilid nito. Tingnan ang Ang panel sa isang bagong window.
registerChannelHeaderIconMga icon sa channel header, pagkatapos ng mga call button; sa mga phone, nasa Pang-iba pa menu ng header.
registerCallButtonActionAng call button ng channel header; maraming provider ang nagbabahagi ng Iba pang paraan ng pagtawag menu.
registerPostDropdownMenuActionAng Mga aksyon sa mensahe (⋯) menu ng mensahe, sa Mga Karagdagang Aksyon submenu nito; pati na rin sa Mga aksyon sa thread menu ng thread panel, sa ilalim ng Mga Karagdagang Aksyon. Ang Fusion ay nagguhit ng icon ng app bago bawat aksyon, sa kulay abo: ang sariling icon para sa mga kasamang app, kung hindi naman ang icon na ipinapakita ng app sa app rail, kung hindi naman ang isang neutral na icon. Ang element na walang teksto sa simula ng teksto ng aksyon (isang icon na nagguhit ng sarili ang app) ay itinatago.
registerPostDropdownSubMenuActionIsang grupo ng mga entry sa parehong submenu ng Mga Karagdagang Aksyon, pagkatapos ng mga single action, hiwalay sa pamamagitan ng isang linya, walang heading. Walang app na makakakuha ng sariling entry sa menu ng mensahe.
registerPostActionComponentAng hover toolbar ng mensahe, bago ang ⋯ button.
registerPostTypeComponent, registerPostMessageAttachmentComponent, registerPostHeaderComponent, registerPostWillRenderEmbedComponentAng mensahe mismo: ang katawan nito, sa ilalim ng teksto nito, pagkatapos ng oras nito, at sa lugar ng mga link preview.
registerChannelHeaderMenuActionAng Mga opsyon ng channel menu ng channel (⋯ sa isang channel sa sidebar, o right-click), bilang mga item na may plug icon.
registerMainMenuActionAng community menu (pangalan ng community sa itaas ng listahan ng mga channel), sa ilalim ng Mga Karagdagang Aksyon, naka-group ayon sa app.
registerSidebarBrowseOrAddChannelMenuActionAng community menu, pagkatapos ng Gumawa ng channel at Gumawa ng kategorya.
registerFileUploadMethodAng + menu ng composer (Magdagdag sa mensahe), pagkatapos ng Mag-upload ng file.
registerPostEditorActionComponentAng formatting bar ng composer, kapang ipinapakita ang formatting.
registerAIActionMenuItemComponentAng AI menu ng composer.
registerFileDropdownMenuAction, registerFilePreviewComponentAng options menu ng isang file, at ang file preview.
registerUserSettingsProfile & settings, isang tab bawat app sa ilalim ng Mga Plugin heading.
registerChannelSettingsTabAng channel settings dialog, sa ilalim ng Mga app heading.
registerPopoverUserAttributesComponent, registerPopoverUserActionsComponentAng profile card na ipinapakita kapag pinindot mo ang isang pangalan o avatar.
registerLeftSidebarHeaderComponent, registerSidebarChannelLinkLabelComponent, registerChannelIconOverride, registerBottomTeamSidebarComponentAng itaas ng listahan ng mga channel ng isang community, tabi ng pangalan ng channel, ang icon ng channel, at ang ibaba ng community rail sa kaliwa.
registerChannelIntro, registerChannelIntroButtonAction, registerChannelToastComponent, registerNewMessagesSeparatorActionComponent, registerChannelComposerBannerComponent, registerComposerPlaceholderAng simula ng history ng isang channel, ang itaas ng listahan ng mga mensahe, ang linya ng hindi nabasa, sa itaas ng composer, at ang placeholder ng composer.
registerChannelTypeOptionMga uri ng channel ng Gumawa ng channel dialog.
registerSearchComponentsAng search box sa header ng channel: lumalabas ang mga app scopes kapang walang laman ito.
registerUserGuideDropdownMenuActionTungkol sa Antimatter (sa ibaba ng app rail), sa ilalim ng Tulong.
registerProductMga buong pahina sa URL ng produkto, sa klasikong frame ng pahina, nang walang menu ng produkto: Walang ganito ang Fusion, at hindi binibilang ang mga produkto sa kahit saan, kaya ikaw mismo ang gagawa ng link sa pahina. Walang kasamang app na nagre-register ng produkto: Ang Kanban, Playbooks, at AI chat ay bumubukas sa kanilang mga side panel.
registerRootComponent, registerGlobalComponent, mga hook tulad ng registerMessageWillBePostedHook, registerSlashCommandWillBePostedHook, registerMessageWillFormatHook, registerFilesWillUploadHook, registerDesktopNotificationHook, registerWebSocketEventHandler, registerReducer, registerTranslationsGumagana tulad ng sa klasikong interface.
registerAdminConsolePlugin, registerAdminConsoleCustomSettingAng System Console, na ang klasikong isa sa parehong interface.

Ang hindi ipinapakita o binubuhos ng Fusion

  • Ang registerPostDropdownMenuComponent (isang component sa menu ng mensahe) at registerPostCardTypeComponent (ang card ng isang post sa kanang panel) ay hindi ipinapakita sa mga pangunahing view ng Fusion. Ang registerProductSwitcherMenuItem ay walang ginagawa sa Fusion, dahil wala itong menu ng produkto (nag-log lamang ito ng babala). Gamitin ang registerPostDropdownMenuAction bilang kapalit ng una.
  • Ang registerMessageWillBeUpdatedHook ay hindi gumagana kapag na-edit ang mensahe sa Fusion. Suriin ang mga edit sa server gamit ang MessageWillBeUpdated hook sa halip.
  • Ang registerActionAfterChannelCreation ay tumatakbo lamang mula sa klasikong channel creation dialog, na binubuksan ng Fusion mula sa Iba pang opsyon… sa kanyang Gumawa ng channel dialog.
  • Ipinapakita ang mga component ng app bar kahit anong supportedProductIds nila.
  • Ang mga route mula sa registerNeedsTeamRoute at registerCustomRoute ay pananatili ang klasikong frame ng pahina, tulad ng mga produkto.

Mga hook na Antimatter lamang ang mayroon

Ang mga registry method na ito ay wala sa Mattermost. Siguraduhing mayroon ang method bago ito tumawag, upang gumana rin ang app doon:

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');
}
MethodAng idinagdag nito
registerComposerMenuItem({text, icon, fusionIcon, action, shouldRender})Isang item sa menu ng + sa kahon ng mensahe, sa parehong interface. Ang action ay tumatanggap ng {channelId, rootId}.
registerEmojiPickerTab({id, label, icon, fusionIcon, order, component, shouldRender, replacesGifPicker})Isang tab ng emoji picker ng message box, tulad ng GIFs o stickers. Sa Fusion, ang mga tab na may order na mas mababa sa 10 ay nasa unahan ng Emoji tab. Ang component ay tumatanggap ng channelId, rootId, filter, onFilterChange, onSelectDone, insertText, isFusion at, sa Fusion, pickerKit (tingnan sa ibaba).
registerChannelViewPanel({matcher, component})Isang panel sa pagitan ng channel header at mga mensahe, para sa mga channel na pinili ng matcher(state, channel). Maaari nitong itago ang mga mensahe at composer (setMessagesVisible), gaya ng ginagawa ng mga voice channel.
registerSidebarChannelFooterComponent(component)Isang linya sa ilalim ng channel sa sidebar, tulad ng mga tao sa kanyang tawag. Tinatanggap nito ang channel.
registerIntegrationsOption({title, description, fusionIcon, action, shouldRender})Fusion lamang: isang entry sa mga Mga Integration pages, tabi ng custom emoji. Ang GIFs app ay nagdadagdag ng kanyang mga custom GIF at stickers doon.

Magdagdag at pamahalaan ng mga view sa emoji picker

Sa Fusion, ang emoji picker tab ay may pickerKit prop: ang mga component na ginagamit ng Emoji tab para sa sariling add at manage views nito, upang magkasingkat ang mga view ng tab at manatili sa loob ng picker. Ginagamit ito ng mga GIFs at Memes apps para magdagdag at pamahalaan ng mga GIF, sticker, at meme template. Ito ay naglalaman ng version (1), ang mga hook na usePickerView at useOnEscape, at ang mga component na PickerViewButtons (ang + at manage buttons sa tabi ng search box), PickerViewFrame (isang view na may back arrow), PickerNotice, PickerField, PickerSearch, PickerDropZone, at PickerManageList. Ang Escape ay bumabalik mula sa view patungo sa grid ng tab. Siguraduhing nandoon ang prop: ang ibang host ay hindi ito isasama, at ang tab ay gagamit ng sariling dialogs sa ganun. Ang mga type ay nasa webapp/fusion/src/types/store/plugins.ts (EmojiPickerTabProps) at ang mga component ay nasa webapp/fusion/src/fusion/popovers/picker_views.tsx.

Mga mode ng composer

Sa Fusion, ang registerComposerMode({id, label, fusionIcon, placeholder, sendLabel, component, shouldRender}) ay nagdadagdag ng ibang paraan ng pagsulat ng mensahe sa composer, tulad ng ginagawa ng Polls para sa isang poll. Ang mode ay inaalok sa + menu ng composer sa ilalim ng kanyang label. Kapag napili, pinapanatili ng composer ang text box nito (na may placeholder ng mode: ang tanong ng poll), ipinapakita ang iyong component sa ilalim nito, at isang bar na naglalarawan ng mode na may Kanselahin button; ang Ipadala ay kinukuha ang sendLabel at pinapatakbo ang iyong submit sa halip na i-post ang text.

  • Ang component ay tumatanggap ng channelId, rootId (ang thread, sa isang reply box), message (ang text ng composer), setSubmit, at onCancel (pabalik sa isang simpleng mensahe, habang pinapanatili ang text).
  • Bigyan ang setSubmit ng function na tatawagin gamit ang text ng composer at magreresolve sa walang anumang bagay (o isang empty string) kapag naipadala na: pagkatapos ay wawalin ng composer ang text at babalik sa isang simpleng mensahe. Mag-resolve sa isang string upang ipakita ito bilang error sa lugar.
  • Ang placeholder at sendLabel ay mga string, o mga function ng intl ng react-intl. Ang shouldRender(state, {channelId, rootId}) ang nagdedesisyon kung ang isang composer ay mag-aalok ng mode.
  • Upang buksan ang mode mula sa ibang lugar, tulad ng isang slash command na walang arguments: window.dispatchEvent(new CustomEvent('am-composer-mode', {detail: {id, channelId, rootId}})).

Ang Fusion lamang ang may mga mode ng composer: suriin kung ang registry ay may function, at kung hindi, panatilihin ang iyong sariling dialog. Ang method ay bumabalik sa id ng mode, at ang mode ay aalis kapag tinanggal ang app.

Ang panel sa bagong window

Binibigyan ng Fusion ang component ng registerRightHandSidebarComponent ng dalawa pang props, upang ang Buksan sa bagong window ay buksan ang panel kung saan ito nanggaling sa halip na sa home ng app:

  • setPanelState(state): tawagin ito kapag nagbago ang posisyon ng panel, gamit ang maikling string na iyong tinutukoy, tulad ng 'note/<id>' o 'board/<id>/card/<id>', o null sa home nito. Dito ito ilalagay sa address ng window, kaya huwag ilagay ang anumang sekreto rito. Ang state na mas mahaba kaysa 512 character, o may control characters, ay tatanggalin.
  • initialPanelState: sa bagong window, ang state ng panel, o undefined. Ituring itong hindi mapagkakatiwalaang input: i-fetch ulit ang item bilang ang nakasign-in na tao, at ipakita ang home ng app o isang not-found view kung hindi ito mabubuksan.

Kalimutan ang state kapag sarado ang panel o may ibang app na panel ang tumakas sa lugar nito. Para buksan ang window ng panel sa sarili, ang popoutRhsPlugin (nasa window.WebappUtils.popouts din) ay tumatanggap ng state bilang opsyonal na ikalimang argumento. Ginagamit ito ng Notes, Whiteboard, Todo, Kanban, at Playbooks.

Ang fusionIcon ay nagpapangalan ng isang icon mula sa icon set ng Fusion (halimbawa, poll, sticker, calendar, o image; tingnan ang webapp/fusion/src/fusion/components/icon). Ipinapakita nito ng Fusion sa halip ng icon, upang tumugma ang item sa iba.

Mga Setting

Ang settings_schema sa plugin.json ay naglalarawan ng pahina ng app sa System Console › Mga Plugin:

"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."}
  ]
}

Mga uri ng setting: bool, text, longtext, number, dropdown, radio, generated (isang random secret), username, at custom (ginagawa ng isang component na narehistro ng web app part gamit ang registerAdminConsoleCustomSetting). Maaaring i-group ang mga setting sa mga sections.

Binabasa ng server part ang mga ito sa isang struct na ang mga field ay tumutugma sa mga key:

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
}

Maaari ring itakda ng mga Admin ang mga ito sa configuration, sa ilalim ng PluginSettings.Plugins.<app id>, na may mga key na nasa lower case (tingnan ang admin guide).

Mga Pagsasalin

Isinalin ang Antimatter sa 37 wika, pati na rin ang mga bundled apps. Ginagawa ito ng web app part sa ganitong paraan:

  1. Isulat ang bawat string gamit ang React Intl, na may ID at kanyang English text: <FormattedMessage id='polls.create' defaultMessage='Create a poll'/> o defineMessages. Ilagay ang pangalan ng app sa simula ng mga ID.
  2. I-extract ang mga English string sa webapp/i18n/en.json (gamit ng mga bundled apps ang formatjs extract).
  3. Magdagdag ng isang file bawat wika sa tabi nito, na may parehong mga ID: fr.json, de.json, pt-BR.json, zh-CN.json…
  4. Ibigay ito sa web app, na humihingi ng wika ng user:
    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);

Ang nawawalang string ay bumabalik sa English defaultMessage. Para sa mga string na ipinapadala ng server part (mga sagot ng command, mga mensahe ng bot), ang pluginapi/i18n package ay naglo-load ng mga translation file na kasama sa bundle (halimbawa, assets/i18n) at nagbibigay ng localizer sa wika ng bawat user.

Pagbuo, pag-install, at pag-debug

Kailangan mo ng Go at Node.js (ang bersyon sa .nvmrc ng app). Sa folder ng app:

CommandGinagawa nito
make distNagbu-build ng server part para sa bawat platform at ng web app part, at isinusulat ang bundle sa dist/<app id>-<version>.tar.gz.
make testNagpapatakbo ng mga Go at JavaScript tests.
make check-styleNag-lint ng code (golangci-lint, ESLint) at tinutukoy ang mga TypeScript types.
make deployNagbu-build, tapos nag-install at nag-activate ng bundle sa server.
make watch, make deploy-from-watchAng unang nagbu-build ng web app part bawat pag-save; ang pangalawa ay nag-bundle ng nabuo at nag-install nito sa server.
make enable, make disable, make resetI-activate, i-disable, o i-restart ang app sa server.
make logs, make logs-watchIpakita ang mga linya ng app sa server log.
make attachNakakabit ng Delve debugger sa tumatakbo na server part (buuin muna gamit ang AM_DEBUG=1).

Ang make deploy at ang ibang server commands ay gumagamit ng local mode socket kapag may natagpuan (/var/tmp/antimatter_local.socket, o AM_LOCALSOCKETPATH): itakbo ito sa makina ng server na may local mode. Kung wala, mag-sign-in sila sa server na ibinigay ng MM_SERVICESETTINGS_SITEURL gamit ang AM_ADMIN_TOKEN, o AM_ADMIN_USERNAME at AM_ADMIN_PASSWORD. Hindi pa binabasa ng mga build tools ang AM_ name para sa site URL.

Mag-install ng bundle sa pamamagitan ng kamay

  • Pinapayagan ng system admin ang mga upload gamit ang AM_PLUGINSETTINGS_ENABLEUPLOADS=true (naka-off sa default; wala itong System Console switch).
  • Pagkatapos ay System Console › Mga Plugin › Pamamahala ng Plugin › Mag-upload ng Plugin, o amctl plugin add weather.tar.gz na sinundan ng amctl plugin enable com.example.weather.
  • Kung naka-on ang Kinakailangan ng Plugin Signature sa server, tinatanggap nito lamang ang mga bundle na firmahan ng isang key na pinagkakatiwalaan nito; tingnan ang Pag-install ng ibang mga app.
Para sa mabilis na pag-iterate sa development server, itakda ang AM_SERVICESETTINGS_ENABLEDEVELOPER=true sa server: ang mga error ay may kasamang detalye. Ang mga Makefile ng mga bundled apps ay nagbu-build ng server part para sa iyong platform lamang kapag naka-set ang MM_SERVICESETTINGS_ENABLEDEVELOPER sa iyong shell.