Escriure aplicacions (plugins)
Les aplicacions afegeixen funcions a Antimatter des de dins: una part de servidor en Go que s'acobla al servidor, i una part d'aplicació web en React que afegeix menús, panells, tipus de missatges i pàgines. Aquesta pàgina mostra com s'estructura una aplicació, utilitzant l'aplicació Enquestes integrada com a exemple, i què canvia a la interfície Fusion d'Antimatter.
Com funciona una aplicació
- La part de servidor és un programa en Go. El servidor l'executa com a procés independent i hi comunica mitjançant RPC: crida els hooks de l'aplicació (s'apunta a publicar un missatge, s'ha executat una comanda...) i l'aplicació crida l'API de plugins del servidor (crear una publicació, llegir un usuari, emmagatzemar dades...). També pot servir rutes HTTP sota
/plugins/<app id>/. - La part d'aplicació web és un paquet JavaScript que es carrega al navegador amb l'aplicació web. Registra components i callbacks de React al registre: un botó en un menú, un panell, una manera de dibuixar un tipus de missatge.
- Cada part és opcional. Una aplicació s'instal·la com a paquet
.tar.gz, i els administradors del sistema l'activen, la desactiven i la configuren.
Antimatter executa aplicacions escrites per a Mattermost, i a l'inrevs, sempre que l'aplicació només utilitzi hooks que tinguin tots dos. Vegeu Hooks que només té Antimatter.
L'estructura del paquet
No hi ha cap plantilla inicial separada: comenceu amb una petita aplicació integrada com antimatter-plugin-polls o antimatter-plugin-todo, i canvieu-ne l'ID i el codi. Enquestes s'estructura així:
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
Opcional: una carpeta public/, servida tal qual a /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: únic i permanent; sol ser un nom de domini invers. Nomena les rutes, la configuració emmagatzemada i els esdeveniments WebSocket.version: versió semàntica. Les aplicacions integrades l'ometen deplugin.json; la seva eina de construcció la pren de l'etiqueta gitv*més recent.min_server_version: Antimatter informa12.0.0.settings_schema: la pàgina de configuració de l'aplicació al Consola del sistema (vegeu Configuració).
make apply copia el manifest a server/manifest.go i webapp/src/manifest.ts; make manifest-check el valida.
La part del servidor
La part del servidor importa el mateix mòdul Go que les aplicacions de Mattermost, github.com/mattermost/mattermost/server/public, des del proxy de mòduls Go. El seu main només inicia l'aplicació:
package main
import "github.com/mattermost/mattermost/server/public/plugin"
func main() {
plugin.ClientMain(&Plugin{})
}
El tipus Plugin incorpora plugin.MattermostPlugin, que li proporciona p.API, i implementa els ganxos que necessita:
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)
}
Ganxos comuns
| Ganxo | Quan s'executa |
|---|---|
OnActivate, OnDeactivate | Quan l'aplicació s'activa o es desactiva, i quan el servidor s'inicia i s'atura. Registra les comandes i els bots a OnActivate. |
OnConfigurationChange | Quan canvien les seves configuracions; carrega-les amb p.API.LoadPluginConfiguration(&cfg). |
ServeHTTP | Sol·licituds a /plugins/<app id>/…. El servidor estableix la capçalera Mattermost-User-Id quan la sol·licitud prové d'un usuari iniciat; rebutja les sol·licituds que no la tinguin, llevat que estiguin destinades a ser públiques. |
ExecuteCommand | S'ha executat una comanda registrada per l'aplicació. |
MessageWillBePosted, MessageWillBeUpdated | Abans que un missatge es guardi: retorna un missatge modificat o un motiu per rebutjar-lo. |
MessageHasBeenPosted, MessageHasBeenUpdated, MessageHasBeenDeleted | Després dels fets, per a efectes secundaris. |
ReactionHasBeenAdded, ReactionHasBeenRemoved | Reaccions. |
UserHasJoinedChannel, UserHasLeftChannel, UserHasJoinedTeam, UserHasLeftTeam, ChannelHasBeenCreated | Canvis de membres i canals. Un equip és una comunitat. |
FileWillBeUploaded | Abans que un fitxer es guardi: modifica'l o rebutja'l. |
OnPluginClusterEvent | Un missatge de la mateixa aplicació en un altre servidor d'un clúster. |
La llista completa, amb les signatures exactes, es troba a server/public/plugin/hooks.go; l'API és a server/public/plugin/api.go. El paquet pluginapi (server/public/pluginapi) envolta l'API amb ajudants més amigables.
Crides a l'API útils
CreatePost,UpdatePost,SendEphemeralPost: publica com a bot de l'aplicació o com a usuari.EnsureBotUser(&model.Bot{Username, DisplayName}): crea el bot de l'aplicació una sola vegada i obté el seu ID.PublishWebSocketEvent("poll_updated", payload, &model.WebsocketBroadcast{ChannelId: id}): envia un esdeveniment a l'aplicació web. Arriba comcustom_<app id>_poll_updated.OpenInteractiveDialog: obre un diàleg.LogInfo,LogErrori altres: escriu al registre del servidor, etiquetat amb l'ID de l'aplicació.GetUser,GetChannel,HasPermissionToChannel: comprova qui pot fer què abans d'actuar en nom d'un usuari.
Una aplicació s'executa en cada servidor d'un clúster. Mantén l'estat a l'emmagatzematge de claus i valors o a la base de dades, no només a la memòria.
El servei d'IA
El servidor d'Antimatter crida models d'IA per a les aplicacions, a través de les connexions de models configurades per un administrador (vegeu IA a la guia d'administració). Una aplicació mai no guarda la clau d'un proveïdor: nomena la persona per la qual actua i on, i el servidor comprova que aquesta persona pot utilitzar la connexió allà, aplica els seus límits i pressupostos, compta els tokens en nom de la persona i de l'aplicació, i audita la crida sense el seu contingut.
| Mètode | Què fa |
|---|---|
AIChat(actingUserID, channelID, teamID, *ai.ChatRequest) | Una completació de xat: missatges, eines, sortida estructurada (JSONSchema). |
AIChatStream(…) | El mateix, en flux: llegeix els Events del flux fins que el canal es tanca, o Close per aturar la crida. |
AIEmbed(…, *ai.EmbedRequest) | Incorporacions (embeddings) de textos. |
AITranscribe(…, *ai.TranscribeRequest) | Veu a text. |
AIDecide(…, *ai.DecideRequest) | Preguntes tipificades a un model de decisió (sí o no, una opció, una puntuació) sobre un text. |
AIConnectionsForUser(userID, channelID, teamID) | Les connexions que una persona pot utilitzar allà, amb els seus models i capacitats, mai les seves claus. |
El ConnectionID de cada sol·licitud tria la connexió, i Model un dels seus models (buit per al model predeterminat de la connexió). Operation és una etiqueta curta per als registres d'ús i d'auditoria, mai el contingut. La seva versió mínima de servidor és 11.11.
actingUserID només d'una font verificada pel servidor: la capçalera Mattermost-User-Id d'una sol·licitud al teu ServeHTTP, o l'usuari del context d'un ganxo (l'autor d'una publicació, l'usuari que va executar una comanda). Mai no l'obtinguis d'un missatge, d'una càrrega de webhook, d'una URL o d'una sol·licitud d'una altra aplicació: la teva aplicació gastaria l'accés d'una altra persona.Aquests mètodes i el paquet ai (server/public/ai) existeixen només a la còpia del mòdul d'Antimatter, no a la versió publicada per Mattermost. Per utilitzar-los, compila contra server/public d'Antimatter amb una línia replace al teu go.mod, per exemple replace github.com/mattermost/mattermost/server/public => ../antimatter/server/public. Una aplicació d'aquest tipus només funciona a Antimatter.
Donar eines als agents
Una aplicació ofereix les seves pròpies eines als agents amb RegisterAITools([]*model.AIToolDefinition): el nom, el títol, la descripció, l'esquema JSON d'entrada i el tipus de cada eina (read, write o external_write), que determina si la política d'un agent pot executar-la per si sola (vegeu Eines a la guia d'administració). Els permisos es comproven en un argument de canal o comunitat de la crida, o al servidor, segons el que necessiti l'eina. Quan un agent utilitza l'eina, el servidor crida el ganxo ExecuteAITool(c, *model.AIToolExecution) *model.AIToolResult amb la persona verificada que ho ha sol·licitat a UserId: actua només amb els seus permisos, mai amb els de l'agent. Les eines s'anomenen <app id>.<name>; versió mínima del servidor 11.11; aquests mètodes i el ganxo només existeixen a server/public d'Antimatter, com el servei d'IA anterior. Una aplicació amb ID antimatter, o que comenci per mcp., no pot registrar eines.
Emmagatzemar dades
Cada aplicació té el seu propi emmagatzematge de claus i valors a la base de dades del servidor:
KVSet(key, value),KVGet(key),KVDelete(key): els valors són bytes, normalment JSON. Les claus tenen com a màxim 150 caràcters.KVSetWithExpiry(key, value, seconds): el valor desapareix després d'aquest temps.KVCompareAndSet(key, old, new)iKVSetWithOptionsambAtomic: actualitzacions segures quan diverses sol·licituds o servidors escriuen la mateixa clau. Enquesta desa cada vot d'aquesta manera i torna a intentar-ho en cas de conflicte.KVList(page, perPage): les claus, pàgina per pàgina.
La part de l'aplicació web
El paquet registra una classe amb window.registerPlugin. El seu initialize rep el registre i l'emmagatzematge de Redux:
import manifest from './manifest';
class Plugin {
initialize(registry, store) {
registry.registerTranslations((locale) => translationsFor(locale));
registry.registerPostTypeComponent('custom_poll', PollPost);
registry.registerWebSocketEventHandler(`custom_${manifest.id}_poll_updated`, (msg) => {
store.dispatch(pollUpdated(JSON.parse(msg.data.poll)));
});
registry.registerReconnectHandler(() => store.dispatch(reloadPolls()));
}
}
window.registerPlugin(manifest.id, new Plugin());
- Biblioteques compartides. L'aplicació web proporciona React, ReactDOM, Redux, React Redux i React Intl: llista-les com a
externalsde webpack (React,ReactDOM,Redux,ReactRedux,ReactIntl) en lloc d'empaquetar còpies pròpies. Consultawebpack.config.jsd'Enquesta. - Crèdit a la part del servidor. Fes una sol·licitud a
${window.basename}/plugins/<app id>/…amb les opcions deClient4.getOptions()(demattermost-redux/client): porten les capçaleres que el servidor necessita per acceptar una sol·licitud del navegador. - El teu propi estat.
registerReducerafegeix un reductor, emmagatzemat sotastate['plugins-<app id>']. - Tipus. Els mètodes addicionals del registre d'Antimatter no estan als tipus publicats de Mattermost: Enquesta declara el registre que utilitza a
webapp/src/types/host.ts.
Els mètodes del registre i els seus paràmetres estan documentats al codi, a webapp/fusion/src/plugins/registry.ts (Fusion) i webapp/channels/src/plugins/registry.ts (clàssic).
Donar suport a la interfície Fusion
Antimatter té dues interfícies web: Fusion, la predeterminada, i la clàssica. Ambdues carreguen els mateixos paquets d'aplicacions i tenen el mateix registre, però Fusion dibuixa moltes coses per si mateixa i col·loca els components de les aplicacions a la seva manera. Prova la teva aplicació en ambdues.
Detectar la interfície
Abans que qualsevol aplicació es carregui, ambdues interfícies estableixen window.antimatterWebUI a 'fusion' o 'classic', i el mateix valor a l'element arrel de la pàgina com a data-am-web-ui. A Mattermost, cap dels dos s'estableix.
export function isFusionUI(): boolean {
return window.antimatterWebUI === 'fusion' || document.documentElement.dataset.amWebUi === 'fusion';
}
Les aplicacions integrades l'utilitzen per dibuixar de manera diferent. Les enquestes dibuixen les seves targetes amb l'estil de Fusion, i a Fusion l'enquesta s'escriu al compositor (un mode de compositor) en lloc d'un diàleg. Les trucades i els canals de veu ometen la barra lateral i els components de notificació emergent que Fusion substitueix per la seva pròpia interfície de trucada. L'aplicació de xat d'IA posa les seves accions d'IA al menú del missatge, com un grup sota Més accions, en lloc de la barra d'eines en passar el ratolí. Les classes CSS pròpies de Fusion (amb el prefix am-) i la seva capa de diàlegs (l'element am-layer) són el que aquestes aplicacions utilitzen per adaptar-se a la interfície; no són una API documentada ni estable.
On Fusion mostra els components dels plugins
| Mètode del registre | On apareix a Fusion |
|---|---|
registerAppBarComponent, registerChannelHeaderButtonAction | La barra d'aplicacions, la columna d'icones del costat dret, entre el teu avatar i el botó Quant a Antimatter. Els botons de la capçalera del canal també hi van, no a la capçalera del canal. S'amaga quan un administrador del sistema activa Inhabilitar la barra d'aplicacions (ExperimentalSettings.DisableAppBar, desactivat per defecte). |
registerRightHandSidebarComponent | El panell de la dreta. La seva capçalera és la de Fusion, la mateixa per a totes les aplicacions: la icona i el nom de l'aplicació (el títol registrat quan només dibuixa text, si no, l'etiqueta de la barra d'aplicacions o el nom del manifest), després Restaura la mida del panell (un cop redimensionat), Obre en una finestra nova i Tanca. Un títol que dibuixi qualsevol altra cosa (un control, un enllaç, una imatge) es descarta, així que dibuixeu els controls propis de la vostra aplicació al panell mateix, per exemple en una fila prima a la part superior. Les persones poden canviar la mida del panell arrossegant-ne la vora. Vegeu El panell en una finestra nova. |
registerChannelHeaderIcon | Icones a la capçalera del canal, després dels botons de trucada; als telèfons, al menú Més de la capçalera. |
registerCallButtonAction | El botó de trucada de la capçalera del canal; diversos proveïdors comparteixen un menú Altres maneres de trucar. |
registerPostDropdownMenuAction | El menú Accions del missatge (⋯) del missatge, al seu submenú Més accions; també al menú Accions del fil del panell del fil, sota Més accions. Fusion dibuixa la icona de l'aplicació abans de cada acció, en gris: la seva pròpia icona per a les aplicacions integrades, en cas contrari la icona que l'aplicació mostra al carril d'aplicacions, i en cas contrari una icona neutra. Un element sense text al començament del text de l'acció (una icona que l'aplicació dibuixa ella mateixa) s'amaga. |
registerPostDropdownSubMenuAction | Un grup d'entrades al mateix submenú Més accions, després de les accions individuals, separades per una línia, sense cap títol. Cap aplicació rep una entrada pròpia al menú del missatge. |
registerPostActionComponent | La barra d'eines de pas del cursor del missatge, abans del botó ⋯. |
registerPostTypeComponent, registerPostMessageAttachmentComponent, registerPostHeaderComponent, registerPostWillRenderEmbedComponent | El propi missatge: el seu cos, sota el seu text, després de la seva hora, i en lloc de les previsualitzacions d'enllaços. |
registerChannelHeaderMenuAction | El menú Opcions del canal del canal (⋯ en un canal de la barra lateral, o clic dret), com a elements amb una icona de connector. |
registerMainMenuAction | El menú de la comunitat (el nom de la comunitat a la part superior de la llista de canals), sota Més accions, agrupat per aplicació. |
registerSidebarBrowseOrAddChannelMenuAction | El menú de la comunitat, després de Crea un canal i Crea una categoria. |
registerFileUploadMethod | El menú + del compositor (Afegeix al missatge), després de Penja un fitxer. |
registerPostEditorActionComponent | La barra de format del compositor, quan es mostra el format. |
registerAIActionMenuItemComponent | El menú IA del compositor. |
registerFileDropdownMenuAction, registerFilePreviewComponent | El menú d'opcions d'un fitxer, i la previsualització del fitxer. |
registerUserSettings | Perfil i configuració, una pestanya per aplicació sota el títol Connectors. |
registerChannelSettingsTab | El diàleg de configuració del canal, sota el títol Aplicacions. |
registerPopoverUserAttributesComponent, registerPopoverUserActionsComponent | La targeta de perfil que es mostra quan fas clic en un nom o un avatar. |
registerLeftSidebarHeaderComponent, registerSidebarChannelLinkLabelComponent, registerChannelIconOverride, registerBottomTeamSidebarComponent | La part superior de la llista de canals d'una comunitat, al costat del nom d'un canal, de la icona d'un canal i la part inferior de la barra lateral de la comunitat a l'esquerra. |
registerChannelIntro, registerChannelIntroButtonAction, registerChannelToastComponent, registerNewMessagesSeparatorActionComponent, registerChannelComposerBannerComponent, registerComposerPlaceholder | L'inici de l'historial d'un canal, la part superior de la llista de missatges, la línia de no llegits, per sobre de l'editor i el text de lloc de l'editor. |
registerChannelTypeOption | Els tipus de canal del diàleg Crea un canal. |
registerSearchComponents | La caixa de cerca de la capçalera del canal: els àmbits de l'aplicació apareixen quan està buida. |
registerUserGuideDropdownMenuAction | Quant a Antimatter (part inferior de la barra lateral de l'aplicació), sota Ajuda. |
registerProduct | Pàgines completes a l'URL del producte, al marc de pàgina clàssic, sense menú de producte: Fusion no en té cap, i no llista productes enlloc, així que enllaceu-hi vosaltres mateixos. Cap aplicació integrada registra un producte: Kanban, les Guies d'actuació i el xat d'IA s'obren als seus panells laterals. |
registerRootComponent, registerGlobalComponent, hooks com registerMessageWillBePostedHook, registerSlashCommandWillBePostedHook, registerMessageWillFormatHook, registerFilesWillUploadHook, registerDesktopNotificationHook, registerWebSocketEventHandler, registerReducer, registerTranslations | Funciona com a la interfície clàssica. |
registerAdminConsolePlugin, registerAdminConsoleCustomSetting | La Consola del sistema, que és la clàssica en ambdues interfícies. |
El que Fusion no mostra ni executa
registerPostDropdownMenuComponent(un component al menú del missatge) iregisterPostCardTypeComponent(la targeta d'un missatge al panell de la dreta) no es mostren a les vistes principals de Fusion.registerProductSwitcherMenuItemno fa res a Fusion, que no té menú de producte (registra un avís). UtilitzeuregisterPostDropdownMenuActionen lloc del primer.registerMessageWillBeUpdatedHookno s'executa quan un missatge s'edita a Fusion. Comprova les edicions al servidor amb el hookMessageWillBeUpdateden lloc d'això.registerActionAfterChannelCreationnomés s'executa des del diàleg de creació de canal clàssic, que Fusion obre des de Més opcions… al seu diàleg Crea un canal.- Els components de la barra d'aplicacions es mostren independentment del seu
supportedProductIds. - Les rutes de
registerNeedsTeamRouteiregisterCustomRoutemantenen el marc de pàgina clàssic, com els productes.
Hooks que només té Antimatter
Aquests mètodes de registre no existeixen a Mattermost. Comprova que un mètode existeix abans de cridar-lo, perquè l'aplicació també funcioni allà:
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');
}
| Mètode | Què afegeix |
|---|---|
registerComposerMenuItem({text, icon, fusionIcon, action, shouldRender}) | Un element al menú + de la caixa de missatges, en ambdues interfícies. action rep {channelId, rootId}. |
registerEmojiPickerTab({id, label, icon, fusionIcon, order, component, shouldRender, replacesGifPicker}) | Una pestanya del selector d'emoji de la caixa de missatges, com ara GIF o adhesius. A Fusion, les pestanyes amb un order inferior a 10 apareixen abans de la pestanya Emoji. El component rep channelId, rootId, filter, onFilterChange, onSelectDone, insertText, isFusion i, a Fusion, pickerKit (vegeu més avall). |
registerChannelViewPanel({matcher, component}) | Un panell entre la capçalera del canal i els missatges, per als canals que selecciona matcher(state, channel). Pot amagar els missatges i el compositor (setMessagesVisible), com fan els canals de veu. |
registerSidebarChannelFooterComponent(component) | Una línia sota un canal a la barra lateral, com ara les persones de la seva trucada. Rep el channel. |
registerIntegrationsOption({title, description, fusionIcon, action, shouldRender}) | Només Fusion: una entrada a les pàgines Integracions, al costat dels emoji personalitzats. L'aplicació GIF hi afegeix els seus GIF i adhesius personalitzats. |
Afegeix i gestiona vistes al selector d'emoji
A Fusion, la pestanya del selector d'emoji rep una propietat pickerKit: els components que la pestanya Emoji utilitza per a les seves pròpies vistes d'addició i gestió, de manera que les vistes d'una pestanya tinguin l'aparença i es mantinguin dins del selector. Les aplicacions GIF i Memes l'utilitzen per afegir i gestionar GIF, adhesius i plantilles de memes. Conté version (1), els hooks usePickerView i useOnEscape, i els components PickerViewButtons (els botons + i de gestió al costat de la caixa de cerca), PickerViewFrame (una vista amb la seva fletxa enrere), PickerNotice, PickerField, PickerSearch, PickerDropZone i PickerManageList. La tecla Escape torna d'una vista a la graella de la pestanya. Assegureu-vos que la propietat hi és: altres amfitrions l'ometen, i en aquest cas la pestanya manté els seus propis diàlegs. Els tipus es troben a webapp/fusion/src/types/store/plugins.ts (EmojiPickerTabProps) i els components a webapp/fusion/src/fusion/popovers/picker_views.tsx.
Modes de compositor
A Fusion, registerComposerMode({id, label, fusionIcon, placeholder, sendLabel, component, shouldRender}) afegeix una altra manera d'escriure un missatge al redactor, com fa Polls per a una enquesta. El mode s'ofereix al menú + del redactor amb el seu label. Un cop seleccionat, el redactor manté la seva caixa de text (amb el placeholder del mode: la pregunta de l'enquesta), mostra el teu component a sota i una barra amb el nom del mode i un botó Cancel·la; Envia utilitza el sendLabel i executa el teu enviament en lloc de publicar el text.
- El component rep
channelId,rootId(el fil, en una caixa de resposta),message(el text del redactor),setSubmitionCancel(torna a un missatge normal, mantenint el text). - Passa a
setSubmituna funció que es crida amb el text del redactor i es resol a res (o una cadena buida) un cop enviat: el redactor s'esbuirra i torna a un missatge normal. Resol a una cadena per mostrar-la com un error al lloc. placeholderisendLabelsón cadenes o funcions de l'intlde react-intl.shouldRender(state, {channelId, rootId})decideix si un redactor ofereix el mode.- Per obrir el mode des d'un altre lloc, com ara una comanda amb barra sense arguments:
window.dispatchEvent(new CustomEvent('am-composer-mode', {detail: {id, channelId, rootId}})).
Només Fusion té modes de redactor: comprova que el registre tingui la funció i, si no, mantingues el teu propi diàleg. El mètode retorna l'id del mode, i el mode desapareix quan es desinstal·la l'aplicació.
El panell en una finestra nova
Fusion passa al component registerRightHandSidebarComponent dues propietats més, de manera que Obre en una finestra nova obre el panell on era, en lloc de fer-ho a la pàgina inicial de l'aplicació:
setPanelState(state): crideu-lo quan el panell canviï de lloc, amb una cadena curta que la vostra aplicació defineixi, com ara'note/<id>'o'board/<id>/card/<id>', onulla la seva pàgina d'inici. Aquesta informació s'incorpora a la direcció de la finestra, per tant, no hi introduïu mai cap secret. Un estat més llarg de 512 caràcters, o amb caràcters de control, es descarta.initialPanelState: a la finestra nova, l'estat que tenia el panell, oundefined. Tracteu-lo com a entrada no fiable: recupereu de nou l'element com a la persona iniciada sessió, i mostreu la pàgina d'inici de l'aplicació o una vista de no trobat si no pot obrir-lo.
L'estat s'oblida quan el panell es tanca o el panell d'una altra aplicació pren el seu lloc. Per obrir la finestra d'un panell per compte propi, popoutRhsPlugin (també a window.WebappUtils.popouts) pren l'estat com a cinquè argument opcional. Notes, Pissarra, Tasques pendents, Kanban i Guies d'actuació utilitzen això.
fusionIcon nomena un icona del conjunt d'icones de Fusion (per exemple poll, sticker, calendar o image; vegeu webapp/fusion/src/fusion/components/icon). Fusion la mostra en lloc de icon, de manera que l'element coincideix amb els altres.
Configuració
settings_schema a plugin.json descriu la pàgina de l'aplicació a Consola del sistema › Connectors:
"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."}
]
}
Tipus de configuració: bool, text, longtext, number, dropdown, radio, generated (un secret aleatori), username i custom (dibuixat per un component que la part de l'aplicació web registra amb registerAdminConsoleCustomSetting). Les configuracions es poden agrupar en sections.
La part del servidor les llegeix en una estructura de dades (struct) els camps de la qual coincideixen amb les claus:
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
}
Els administradors també poden establir-les a la configuració, sota PluginSettings.Plugins.<app id>, amb claus en minúscules (vegeu la guia d'administració).
Traduccions
Antimatter està traduït a 37 idiomes, i les aplicacions integrades també. La part de l'aplicació web ho fa d'aquesta manera:
- Escriviu cada cadena amb React Intl, amb un ID i el seu text en anglès:
<FormattedMessage id='polls.create' defaultMessage='Create a poll'/>odefineMessages. Prefixeu els ID amb el nom de la vostra aplicació. - Extraieu les cadenes en anglès a
webapp/i18n/en.json(les aplicacions integrades utilitzenformatjs extract). - Afegiu un fitxer per idioma al costat, amb els mateixos ID:
fr.json,de.json,pt-BR.json,zh-CN.json… - Passa-les a l'aplicació web, que demana l'idioma de l'usuari:
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);
Si falta una cadena, s'utilitza el defaultMessage en anglès. Per a les cadenes que envia la part del servidor (respostes de comandes, missatges de bots), el paquet pluginapi/i18n carrega els fitxers de traducció inclosos al paquet (per exemple assets/i18n) i proporciona un localitzador en l'idioma de cada usuari.
Compila, instal·la i depura
Necessites Go i Node.js (la versió indicada al .nvmrc de l'aplicació). A la carpeta de l'aplicació:
| Ordre | Què fa |
|---|---|
make dist | Compila la part del servidor per a totes les plataformes i la part de l'aplicació web, i escriu el paquet a dist/<app id>-<version>.tar.gz. |
make test | Executa les proves de Go i JavaScript. |
make check-style | Analitza el codi (golangci-lint, ESLint) i comprova els tipus de TypeScript. |
make deploy | Compila, després instal·la i habilita el paquet en un servidor. |
make watch, make deploy-from-watch | El primer recompila la part de l'aplicació web cada vegada que deses; el segon empaqueta el que ha compilat i l'instal·la al servidor. |
make enable, make disable, make reset | Habilita, deshabilita o reinicia l'aplicació al servidor. |
make logs, make logs-watch | Mostra les línies de l'aplicació al registre del servidor. |
make attach | Adjunta el depurador Delve a la part del servidor en execució (compila primer amb AM_DEBUG=1). |
make deploy i les altres comandes del servidor utilitzen un socket de mode local si n'hi ha un (/var/tmp/antimatter_local.socket, o AM_LOCALSOCKETPATH): executa-les a la màquina del servidor amb el mode local activat. En cas contrari, s'hi autenticen al servidor indicat per MM_SERVICESETTINGS_SITEURL amb AM_ADMIN_TOKEN, o amb AM_ADMIN_USERNAME i AM_ADMIN_PASSWORD. Les eines de compilació encara no llegeixen un nom AM_ per a l'URL del lloc.
Instal·la un paquet manualment
- Un administrador del sistema permet les pujades amb
AM_PLUGINSETTINGS_ENABLEUPLOADS=true(desactivat per defecte; no hi ha un interruptor a la Consola del sistema). - Després, Consola del sistema › Connectors › Gestió de plugins › Carrega un plugin, o
amctl plugin add weather.tar.gzseguit deamctl plugin enable com.example.weather. - Si el servidor té activada l'opció Requerix la signatura del complement, només accepta paquets signats amb una clau de confiança; consulta Instal·lació d'altres aplicacions.
AM_SERVICESETTINGS_ENABLEDEVELOPER=true al servidor: els errors llavors inclouen els seus detalls. Els fitxers Makefile de les aplicacions empaquetades compilen la part del servidor només per a la teva plataforma quan MM_SERVICESETTINGS_ENABLEDEVELOPER està establert al teu shell.