Pisanie aplikacji (wtyczek)
Aplikacje dodają funkcje do Antimatter od wewnątrz: część serwerowa w Go, która podpinana jest do serwera, oraz część aplikacji webowej w React, która dodaje menu, panele, typy wiadomości i strony. Ta strona pokazuje, jak wygląda struktura aplikacji, używając jako przykładu dołączonej aplikacji Ankiety, oraz jakie zmiany wprowadza interfejs Fusion w Antimatter.
Jak działa aplikacja
- Część serwerowa to program w Go. Serwer uruchamia go jako osobny proces i komunikuje się z nim przez RPC: wywołuje haki aplikacji (np. wiadomość ma zostać opublikowana, wykonano polecenie…), a aplikacja wywołuje API wtyczki serwera (np. tworzenie posta, odczyt użytkownika, przechowywanie danych…). Może również obsługiwać trasy HTTP pod
/plugins/<app id>/. - Część aplikacji webowej to pakiet JavaScript ładowany do przeglądarki wraz z aplikacją webową. Rejestruje komponenty React i wywołania zwrotne w rejestrze: przycisk w menu, panel, sposób rysowania typu wiadomości.
- Każda z części jest opcjonalna. Aplikacja jest instalowana jako pakiet
.tar.gz, a administratorzy systemu mogą ją włączać, wyłączać i konfigurować.
Antimatter uruchamia aplikacje napisane dla Mattermost i odwrotnie, o ile aplikacja używa wyłącznie haków wspólnych dla obu systemów. Zobacz Haki dostępne tylko w Antimatter.
Struktura pakietu
Nie ma osobnej szablony startowej: zacznij od małej dołączonej aplikacji, takiej jak antimatter-plugin-polls lub antimatter-plugin-todo, i zmień jej identyfikator oraz kod. Aplikacja Ankiety ma następującą strukturę:
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
Opcjonalnie: folder public/, serwowany bez zmian pod /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: unikalny i stały; zwykle ma postać odwróconej nazwy domeny. Określa nazwy tras, przechowywanych ustawień oraz zdarzeń WebSocket.version: wersja semantyczna. Dołączone aplikacje pomijają ją w plikuplugin.json; ich narzędzie budowania pobiera ją z najnowszej etykiety gitv*.min_server_version: Antimatter zgłasza12.0.0.settings_schema: strona ustawień aplikacji w Konsoli systemu (patrz Ustawienia).
make apply kopiuje manifest do server/manifest.go i webapp/src/manifest.ts; make manifest-check go waliduje.
Część serwerowa
Część serwerowa importuje ten sam moduł Go co aplikacje Mattermost, github.com/mattermost/mattermost/server/public, z proxy modułów Go. Jej main tylko uruchamia aplikację:
package main
import "github.com/mattermost/mattermost/server/public/plugin"
func main() {
plugin.ClientMain(&Plugin{})
}
Typ Plugin osadza plugin.MattermostPlugin, co daje mu p.API, i implementuje potrzebne mu haki:
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)
}
Wspólne haki
| Hak | Kiedy się uruchamia |
|---|---|
OnActivate, OnDeactivate | Gdy aplikacja jest włączana lub wyłączana, a także gdy serwer się uruchamia i zatrzymuje. Rejestruj polecenia i boty w OnActivate. |
OnConfigurationChange | Gdy zmieniają się jej ustawienia; wczytaj je za pomocą p.API.LoadPluginConfiguration(&cfg). |
ServeHTTP | Żądania do /plugins/<app id>/…. Serwer ustawia nagłówek Mattermost-User-Id, gdy żądanie pochodzi od zalogowanego użytkownika; odrzucaj żądania bez niego, chyba że mają być publiczne. |
ExecuteCommand | Gdy wykonano polecenie zarejestrowane przez aplikację. |
MessageWillBePosted, MessageWillBeUpdated | Zanim wiadomość zostanie zapisana: zwróć zmienioną wiadomość lub powód jej odrzucenia. |
MessageHasBeenPosted, MessageHasBeenUpdated, MessageHasBeenDeleted | Po fakcie, w celu wykonania działań ubocznych. |
ReactionHasBeenAdded, ReactionHasBeenRemoved | Reakcje. |
UserHasJoinedChannel, UserHasLeftChannel, UserHasJoinedTeam, UserHasLeftTeam, ChannelHasBeenCreated | Zmiany członkostwa i kanałów. Zespół to społeczność. |
FileWillBeUploaded | Zanim plik zostanie zapisany: zmień go lub go odrzuć. |
OnPluginClusterEvent | Wiadomość z tej samej aplikacji z innego serwera klastra. |
Pełna lista, z dokładnymi sygnaturami, znajduje się w server/public/plugin/hooks.go; API jest w server/public/plugin/api.go. Pakiet pluginapi (server/public/pluginapi) opakowuje API w wygodniejsze pomocnicze funkcje.
Przydatne wywołania API
CreatePost,UpdatePost,SendEphemeralPost: publikuj jako bot aplikacji lub jako użytkownik.EnsureBotUser(&model.Bot{Username, DisplayName}): utwórz bota aplikacji raz i uzyskaj jego identyfikator.PublishWebSocketEvent("poll_updated", payload, &model.WebsocketBroadcast{ChannelId: id}): wyślij zdarzenie do aplikacji webowej. Dociera jakocustom_<app id>_poll_updated.OpenInteractiveDialog: otwiera okno dialogowe.LogInfo,LogErrori podobne: zapisuj do dziennika serwera, z oznaczeniem identyfikatorem aplikacji.GetUser,GetChannel,HasPermissionToChannel: sprawdzaj, kto może co zrobić, zanim wykonasz akcję w imieniu użytkownika.
Aplikacja działa na każdym serwerze klastra. Przechowuj stan w magazynie kluczo-wartości lub w bazie danych, a nie tylko w pamięci.
Usługa AI
Serwer Antimatter wywołuje modele AI dla aplikacji za pośrednictwem połączeń z modelami skonfigurowanych przez administratora (patrz AI w przewodniku dla administratorów). Aplikacja nigdy nie przechowuje klucza dostawcy: podaje osobę, w imieniu której działa, oraz miejsce, a serwer sprawdza, czy ta osoba może użyć połączenia w tym miejscu, stosuje jego limity i budżety, rozlicza tokeny na osobę i aplikację oraz audytuje wywołanie bez jego zawartości.
| Metoda | Opis |
|---|---|
AIChat(actingUserID, channelID, teamID, *ai.ChatRequest) | Ukończenie czatu: wiadomości, narzędzia, strukturalna odpowiedź (JSONSchema). |
AIChatStream(…) | To samo, ale w strumieniu: odczytuj Events strumienia, aż kanał się zamknie, albo zamknij go za pomocą Close, aby zatrzymać wywołanie. |
AIEmbed(…, *ai.EmbedRequest) | Wektoryzacja (embeddings) tekstów. |
AITranscribe(…, *ai.TranscribeRequest) | Rozpoznawanie mowy (speech to text). |
AIDecide(…, *ai.DecideRequest) | Zadawanie typowanych pytań do modelu decyzyjnego (tak lub nie, wybór, ocena) dotyczących tekstu. |
AIConnectionsForUser(userID, channelID, teamID) | Połączenia, których dana osoba może tam użyć, wraz z ich modelami i możliwościami, nigdy z kluczami. |
ConnectionID w każdym żądaniu wybiera połączenie, a Model jeden z jego modeli (puste dla modelu domyślnego połączenia). Operation to krótka etykieta dla rekordów użycia i audytu, nigdy zawartość. Minimalna wersja serwera to 11.11.
actingUserID wyłącznie ze źródła zweryfikowanego przez serwer: nagłówka Mattermost-User-Id żądania do Twojego ServeHTTP lub użytkownika z kontekstu hooka (autora wiadomości, użytkownika, który wykonał polecenie). Nigdy nie pobieraj go z wiadomości, obciążnika webhooka, adresu URL ani żądania innej aplikacji: Twoja aplikacja zużyłaby dostęp kogoś innego.Te metody i pakiet ai (server/public/ai) istnieją wyłącznie w kopii modułu Antimatter, a nie w wersji opublikowanej przez Mattermost. Aby ich użyć, kompiluj w oparciu o server/public Antimatter, dodając linię replace w swoim pliku go.mod, na przykład replace github.com/mattermost/mattermost/server/public => ../antimatter/server/public. Taka aplikacja działa wyłącznie na Antimatter.
Przechowywanie danych
Każda aplikacja ma własny magazyn kluczo-wartości w bazie danych serwera:
KVSet(key, value),KVGet(key),KVDelete(key): wartości to bajty, zwykle JSON. Klucze mają maksymalnie 150 znaków.KVSetWithExpiry(key, value, seconds): wartość znika po upływie tego czasu.KVCompareAndSet(key, old, new)orazKVSetWithOptionsz opcjąAtomic: bezpieczne aktualizacje, gdy wiele żądań lub serwerów zapisuje ten sam klucz. Ankieta zapisuje w ten sposób każdy głos i ponawia próbę w przypadku konfliktu.KVList(page, perPage): klucze, strona po stronie.
Część aplikacji webowej
Pakiet rejestruje klasę za pomocą window.registerPlugin. Jej metoda initialize otrzymuje rejestr oraz magazyn 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());
- Biblioteki współdzielone. Aplikacja webowa udostępnia React, ReactDOM, Redux, React Redux i React Intl: wymień je jako
externalsw webpack (React,ReactDOM,Redux,ReactRedux,ReactIntl), zamiast pakować własne kopie. Zobaczwebpack.config.jsw Ankiecie. - Wywoływanie części serwerowej. Używaj
${window.basename}/plugins/<app id>/…z opcjami zClient4.getOptions()(zmattermost-redux/client): zawierają one nagłówki, których serwer potrzebuje, aby zaakceptować żądanie z przeglądarki. - Własny stan.
registerReducerdodaje reducer, przechowywany podstate['plugins-<app id>']. - Typy. Dodatkowe metody rejestru Antimatter nie są zawarte w opublikowanych typach Mattermost: Ankieta deklaruje używany rejestr w
webapp/src/types/host.ts.
Metody rejestru i ich parametry są udokumentowane w kodzie, w webapp/fusion/src/plugins/registry.ts (Fusion) oraz webapp/channels/src/plugins/registry.ts (klasyczny).
Obsługa interfejsu Fusion
Antimatter ma dwa interfejsy webowe: Fusion (domyślny) i klasyczny. Oba ładują te same pakiety aplikacji i mają ten sam rejestr, ale Fusion rysuje wiele elementów samodzielnie i umieszcza komponenty pluginów na własny sposób. Przetestuj aplikację w obu interfejsach.
Wykrywanie interfejsu
Zanim załaduje się jakakolwiek aplikacja, oba interfejsy ustawiają window.antimatterWebUI na 'fusion' lub 'classic', a tę samą wartość na elemencie głównym strony jako data-am-web-ui. W Mattermost żadna z nich nie jest ustawiona.
export function isFusionUI(): boolean {
return window.antimatterWebUI === 'fusion' || document.documentElement.dataset.amWebUi === 'fusion';
}
Zainstalowane aplikacje używają tego mechanizmu, aby renderować interfejs w inny sposób. Ankieta rysuje swoje karty ankiety oraz okno dialogowe tworzenia w stylu Fusion i otwiera własne okno dialogowe dla polecenia /poll w Fusion. Rozmowy i kanały głosowe pomijają pasek boczny oraz komponenty toast, które Fusion zastępuje własnym interfejsem rozmów. Aplikacja Agent umieszcza swoje akcje AI w menu wiadomości, jako grupę pod Więcej Akcji, zamiast na pasku narzędzi pojawiającym się po najechaniu. Własne klasy CSS Fusion (z prefiksem am-) oraz jej warstwa okien dialogowych (element am-layer) to elementy, których te aplikacje używają, aby dopasować się do interfejsu; nie stanowią one udokumentowanego, stabilnego API.
Gdzie Fusion wyświetla komponenty wtyczek
| Metoda rejestru | Gdzie pojawia się w Fusion |
|---|---|
registerAppBarComponent, registerChannelHeaderButtonAction | Pasek aplikacji, kolumna ikon na prawej krawędzi, między Twoim awatarem a przyciskiem O Antimatter. Przyciski nagłówka kanału również umieszczane są w tym miejscu, a nie w nagłówku kanału. Ukryty, gdy administrator systemu włączy Wyłącz pasek aplikacji (ExperimentalSettings.DisableAppBar, domyślnie wyłączony). |
registerRightHandSidebarComponent | Prawy panel. Użytkownicy mogą zmieniać jego rozmiar, przeciągając jego krawędź, oraz otworzyć go w nowym oknie. Jeśli aplikacja jest również produktem, panel oferuje opcję Otwórz na całej stronie. Zobacz Panel w nowym oknie. |
registerChannelHeaderIcon | Ikony w nagłówku kanału, po przyciskach rozmów; na telefonach w menu Więcej nagłówka. |
registerCallButtonAction | Przycisk rozmowy w nagłówku kanału; kilku dostawców udostępnia menu Inne opcje rozmowy. |
registerPostDropdownMenuAction | Menu Wiadomości akcji (⋯) wiadomości, w podmenu Więcej Akcji; również w menu Akcje wątku panelu wątku, pod Więcej Akcji. Fusion rysuje ikonę aplikacji przed każdą akcją, w kolorze szarym: własną ikonę dla zainstalowanych aplikacji, w przeciwnym razie ikonę, którą aplikacja wyświetla na pasku aplikacji, a jeśli nie ma takiej, to ikonę neutralną. Element bez tekstu na początku opisu akcji (ikona rysowana samodzielnie przez aplikację) jest ukrywany. |
registerPostDropdownSubMenuAction | Grupa z nagłówkiem w tym samym podmenu Więcej Akcji, po pojedynczych akcjach. Żadna aplikacja nie otrzymuje własnego wpisu w menu wiadomości. |
registerPostActionComponent | Pasek narzędzi pojawiający się po najechaniu na wiadomość, przed przyciskiem ⋯. |
registerPostTypeComponent, registerPostMessageAttachmentComponent, registerPostHeaderComponent, registerPostWillRenderEmbedComponent | Sama wiadomość: jej treść, pod jej tekstem, po czasie, oraz w miejscu podglądów linków. |
registerChannelHeaderMenuAction | Menu Opcje kanału (⋯ na kanale w pasku bocznym lub kliknięcie prawym przyciskiem myszy), jako elementy z ikoną wtyczki. |
registerMainMenuAction | Menu społeczności (nazwa społeczności u góry listy kanałów), pod Więcej akcji, pogrupowane według aplikacji. |
registerSidebarBrowseOrAddChannelMenuAction | Menu społeczności, po Utwórz kanał i Utwórz kategorię. |
registerFileUploadMethod | Menu + kompozytora (Dodaj do wiadomości), po Prześlij plik. |
registerPostEditorActionComponent | Pasek formatowania kompozytora, gdy formatowanie jest wyświetlane. |
registerAIActionMenuItemComponent | Menu AI kompozytora. |
registerFileDropdownMenuAction, registerFilePreviewComponent | Menu opcji pliku oraz podgląd pliku. |
registerUserSettings | Profil i ustawienia, jedna zakładka na aplikację pod nagłówkiem Wtyczki. |
registerChannelSettingsTab | Okno dialogowe ustawień kanału, pod nagłówkiem Aplikacje. |
registerPopoverUserAttributesComponent, registerPopoverUserActionsComponent | Karta profilu wyświetlana po kliknięciu nazwy lub awatara. |
registerLeftSidebarHeaderComponent, registerSidebarChannelLinkLabelComponent, registerChannelIconOverride, registerBottomTeamSidebarComponent | Górna część listy kanałów przestrzeni roboczej, obok nazwy kanału, ikony kanału oraz dolna część paska bocznego po lewej stronie. |
registerChannelIntro, registerChannelIntroButtonAction, registerChannelToastComponent, registerNewMessagesSeparatorActionComponent, registerChannelComposerBannerComponent, registerComposerPlaceholder | Początek historii kanału, górna część listy wiadomości, linia nieprzeczytanych, nad polem tworzenia wiadomości oraz placeholder w tym polu. |
registerChannelTypeOption | Typy kanałów w oknie dialogowym Utwórz kanał. |
registerSearchComponents | Pole wyszukiwania w nagłówku kanału: zakresy aplikacji pojawiają się, gdy jest puste. |
registerUserGuideDropdownMenuAction | O Antimatter (na dole paska aplikacji), pod Pomoc. |
registerProduct | Pełne strony pod adresem URL produktu, wymienione pod Produkty w sekcji O Antimatter. Strony produktów zachowują klasyczny szablon strony, którego menu produktów pojawia się tylko wtedy, gdy aplikacja zarejestruje produkt lub doda do niego wpis. Zainstalowane aplikacje Kanban i Playbooks nie rejestrują już produktów: ich strony otwierają się w panelach bocznych. |
registerRootComponent, registerGlobalComponent, haki takie jak registerMessageWillBePostedHook, registerSlashCommandWillBePostedHook, registerMessageWillFormatHook, registerFilesWillUploadHook, registerDesktopNotificationHook, registerWebSocketEventHandler, registerReducer, registerTranslations | Działają tak jak w klasycznym interfejsie. |
registerAdminConsolePlugin, registerAdminConsoleCustomSetting | Konsola systemu, która jest klasyczna w obu interfejsach. |
Czego Fusion nie wyświetla ani nie uruchamia
registerPostDropdownMenuComponent(komponent w menu wiadomości),registerPostCardTypeComponent(karta wiadomości w panelu po prawej) orazregisterProductSwitcherMenuItemnie są wyświetlane w głównych widokach Fusion. Zamiast pierwszego użyjregisterPostDropdownMenuAction.registerMessageWillBeUpdatedHooknie uruchamia się, gdy wiadomość jest edytowana w Fusion. Sprawdź edycje na serwerze za pomocą hakaMessageWillBeUpdated.registerActionAfterChannelCreationuruchamia się tylko z klasycznego okna dialogowego tworzenia kanału, które Fusion otwiera z opcji Więcej opcji… w swoim oknie dialogowym Utwórz kanał.- Komponenty paska aplikacji są wyświetlane niezależnie od wartości
supportedProductIds. - Trasy z
registerNeedsTeamRouteiregisterCustomRoutezachowują klasyczny układ strony, podobnie jak produkty.
Haki dostępne tylko w Antimatter
Te metody rejestru nie istnieją w Mattermost. Sprawdź, czy metoda istnieje, przed jej wywołaniem, aby aplikacja działała również tam:
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');
}
| Metoda | Co dodaje |
|---|---|
registerComposerMenuItem({text, icon, fusionIcon, action, shouldRender}) | Pozycja w menu + w polu wiadomości, w obu interfejsach. action otrzymuje {channelId, rootId}. |
registerEmojiPickerTab({id, label, icon, fusionIcon, order, component, shouldRender, replacesGifPicker}) | Zakładka w oknie wyboru emoji w polu wiadomości, na przykład GIF-y lub naklejki. W Fusion zakładki z wartością order poniżej 10 pojawiają się przed zakładką Emoji. Komponent otrzymuje channelId, rootId, filter, onFilterChange, onSelectDone, insertText, isFusion oraz, w Fusion, pickerKit (patrz niżej). |
registerChannelViewPanel({matcher, component}) | Panel umieszczony między nagłówkiem kanału a wiadomościami, dla kanałów wybranych przez matcher(state, channel). Może ukryć wiadomości i pole kompozycji (setMessagesVisible), tak jak to robią kanały głosowe. |
registerSidebarChannelFooterComponent(component) | Wiersz pod kanałem w pasku bocznym, np. osoby uczestniczące w jego rozmowie. Otrzymuje channel. |
registerIntegrationsOption({title, description, fusionIcon, action, shouldRender}) | Tylko Fusion: wpis na stronach Integracje, obok niestandardowych emoji. Aplikacja GIF dodaje tam swoje niestandardowe GIF-y i naklejki. |
Dodawanie i zarządzanie widokami w oknie wyboru emoji
W Fusion tab wyboru emoji otrzymuje prop pickerKit: komponenty, których tab Emoji używa do własnych widoków dodawania i zarządzania, dzięki czemu widoki danej karty wyglądają tak samo i pozostają wewnątrz okna wyboru. Aplikacje GIF i Memes używają go do dodawania i zarządzania GIF-ami, naklejkami i szablonami memów. Zawiera version (1), haki usePickerView i useOnEscape oraz komponenty PickerViewButtons (przyciski + i zarządzania obok pola wyszukiwania), PickerViewFrame (widok ze strzałką wsteczną), PickerNotice, PickerField, PickerSearch, PickerDropZone i PickerManageList. Klawisz Escape cofa z widoku do siatki karty. Sprawdź, czy prop jest obecny: inne hosty go pomijają, a wtedy karta używa własnych okien dialogowych. Typy znajdują się w webapp/fusion/src/types/store/plugins.ts (EmojiPickerTabProps), a komponenty w webapp/fusion/src/fusion/popovers/picker_views.tsx.
Panel w nowym oknie
Fusion przekazuje komponentowi z registerRightHandSidebarComponent dwa dodatkowe propy, dzięki czemu opcja Otwórz w nowym oknie otwiera panel w miejscu, w którym się znajdował, a nie na stronie głównej aplikacji:
setPanelState(state): wywołaj tę funkcję, gdy panel zmienia miejsce, przekazując krótki ciąg znaków zdefiniowany przez aplikację, np.'note/<id>'lub'board/<id>/card/<id>', albonullw przypadku strony głównej. Stan trafia do adresu okna, więc nigdy nie umieszczaj w nim danych poufnych. Stany dłuższe niż 512 znaków lub zawierające znaki sterujące są odrzucane.initialPanelState: w nowym oknie jest to stan, który miał panel, lubundefined. Traktuj go jako niezaufane dane wejściowe: pobierz element ponownie jako zalogowana osoba i wyświetl stronę główną aplikacji lub widok „nie znaleziono”, jeśli nie ma do niego dostępu.
Stan jest zapomniany, gdy panel się zamyka lub panel innej aplikacji zajmuje jego miejsce. Aby samodzielnie otworzyć okno panelu, użyj popoutRhsPlugin (dostępnego również w window.WebappUtils.popouts), przekazując stan jako opcjonalny piąty argument. Z tej funkcji korzystają Notatki, Biała tablica, Do zrobienia, Kanban i Playbooki.
fusionIcon określa nazwę ikony z zestawu ikon Fusion (np. poll, sticker, calendar lub image; patrz webapp/fusion/src/fusion/components/icon). Fusion wyświetla ją zamiast icon, aby element był spójny z pozostałymi.
Ustawienia
settings_schema w plugin.json opisuje stronę aplikacji w Konsola systemu › Wtyczki:
"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."}
]
}
Typy ustawień: bool, text, longtext, number, dropdown, radio, generated (losowa wartość tajna), username oraz custom (rysowane przez komponent zarejestrowany przez część aplikacji webowej za pomocą registerAdminConsoleCustomSetting). Ustawienia można grupować w sections.
Część serwerowa odczytuje je do struktury, której pola odpowiadają kluczom:
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
}
Administratorzy mogą je również ustawić w konfiguracji, pod PluginSettings.Plugins.<app id>, z kluczami zapisanymi małymi literami (patrz przewodnik administratora).
Tłumaczenia
Antimatter jest tłumaczony na 37 języków, a także na języki używane przez dołączone aplikacje. Część aplikacji webowej robi to w następujący sposób:
- Zapisz każdy ciąg za pomocą React Intl, z identyfikatorem i jego tekstem po angielsku:
<FormattedMessage id='polls.create' defaultMessage='Create a poll'/>lubdefineMessages. Prefiksuj identyfikatory nazwą swojej aplikacji. - Wyodrębnij angielskie ciągi do
webapp/i18n/en.json(dołączone aplikacje używająformatjs extract). - Dodaj jeden plik dla każdego języka obok niego, z tymi samymi identyfikatorami:
fr.json,de.json,pt-BR.json,zh-CN.json… - Przekaż je aplikacji webowej, która zapyta o język użytkownika:
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);
Brakujący ciąg jest zwracany jako jego angielski defaultMessage. W przypadku ciągów wysyłanych przez część serwerową (odpowiedzi na polecenia, wiadomości botów), pakiet pluginapi/i18n ładuje pliki tłumaczeń dołączone do pakietu (na przykład assets/i18n) i udostępnia lokalizator w języku każdego użytkownika.
Kompilacja, instalacja i debugowanie
Wymagane są Go i Node.js (wersja z pliku .nvmrc aplikacji). W folderze aplikacji:
| Polecenie | Opis |
|---|---|
make dist | Kompiluje część serwerową dla każdej platformy oraz część aplikacji webowej i zapisuje pakiet do dist/<app id>-<version>.tar.gz. |
make test | Uruchamia testy Go i JavaScript. |
make check-style | Sprawdza kod (golangci-lint, ESLint) i typy TypeScript. |
make deploy | Kompiluje, a następnie instaluje i włącza pakiet na serwerze. |
make watch, make deploy-from-watch | Pierwsze polecenie kompiluje część aplikacji webowej za każdym razem, gdy zapiszesz zmiany; drugie tworzy pakiet z wygenerowanych plików i instaluje go na serwerze. |
make enable, make disable, make reset | Włącza, wyłącza lub ponownie uruchamia aplikację na serwerze. |
make logs, make logs-watch | Wyświetla wpisy aplikacji w dzienniku serwera. |
make attach | Dołącza debugger Delve do działającej części serwerowej (najpierw skompiluj z AM_DEBUG=1). |
make deploy i inne polecenia serwerowe używają lokalnego gniazda trybu lokalnego, jeśli je znajdą (/var/tmp/antimatter_local.socket lub AM_LOCALSOCKETPATH): uruchom je na maszynie serwera z włączonym trybem lokalnym. W przeciwnym razie logują się na serwerze określonym przez MM_SERVICESETTINGS_SITEURL za pomocą AM_ADMIN_TOKEN lub AM_ADMIN_USERNAME i AM_ADMIN_PASSWORD. Narzędzia do kompilacji nie odczytują jeszcze nazwy AM_ dla adresu URL witryny.
Ręczna instalacja pakietu
- Administrator systemu umożliwia przesyłanie plików za pomocą
AM_PLUGINSETTINGS_ENABLEUPLOADS=true(domyślnie wyłączona; nie ma przełącznika w Konsoli systemu). - Następnie Konsola systemu › Wtyczki › Zarządzanie wtyczkami › Wyślij Wtyczkę, albo
amctl plugin add weather.tar.gz, a następnieamctl plugin enable com.example.weather. - Jeśli na serwerze włączona jest opcja Wymagaj sygnatury wtyczki, akceptowane są tylko pakiety podpisane kluczem, któremu serwer ufa; zob. Instalowanie innych aplikacji.
AM_SERVICESETTINGS_ENABLEDEVELOPER=true na serwerze: błędy będą wtedy zawierać szczegóły. Makefile'y dołączonych aplikacji kompilują część serwerową tylko dla Twojej platformy, gdy w powłoce ustawione jest MM_SERVICESETTINGS_ENABLEDEVELOPER.