Menulis aplikasi (plugin)

Aplikasi menambahkan fitur ke Antimatter dari dalam: bagian server dalam Go yang terhubung ke server, dan bagian aplikasi web dalam React yang menambahkan menu, panel, jenis pesan, dan halaman. Halaman ini menunjukkan tata letak aplikasi, menggunakan aplikasi Polls bawaan sebagai contoh, dan apa yang berubah di antarmuka Fusion Antimatter.

Cara kerja aplikasi

  • Bagian server adalah program Go. Server menjalankannya sebagai proses terpisah dan berkomunikasi dengannya melalui RPC: server memanggil hook aplikasi (pesan akan diposting, perintah dijalankan…), dan aplikasi memanggil API plugin server (membuat posting, membaca pengguna, menyimpan data…). Aplikasi juga dapat menyajikan rute HTTP di bawah /plugins/<app id>/.
  • Bagian aplikasi web adalah bundle JavaScript yang dimuat ke browser bersama aplikasi web. Ini mendaftarkan komponen React dan callback ke registri: tombol di menu, panel, atau cara menggambar jenis pesan.
  • Salah satu bagian bersifat opsional. Aplikasi dipasang sebagai bundle .tar.gz, dan admin sistem mengaktifkan, menonaktifkan, dan mengonfigurasikannya.
Bagian server berjalan dengan akses penuh ke data server. Perlakukan aplikasi seperti kode yang Anda deploy ke server.

Antimatter menjalankan aplikasi yang ditulis untuk Mattermost, dan sebaliknya, selama aplikasi hanya menggunakan hook yang dimiliki keduanya. Lihat Hook yang hanya dimiliki Antimatter.

Tata letak bundle

Tidak ada template starter terpisah: mulai dari aplikasi bawaan kecil seperti antimatter-plugin-polls atau antimatter-plugin-todo, lalu ubah ID dan kodenya. Polls ditata seperti ini:

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

Opsional: folder public/, disajikan apa adanya di /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 dan permanen; nama domain terbalik adalah hal yang umum. Ini menamai rute, pengaturan yang disimpan, dan peristiwa WebSocket.
  • version: versi semantik. Aplikasi bawaan tidak menyertakannya di plugin.json; alat pembangunnya mengambilnya dari tag git v* terbaru.
  • min_server_version: Antimatter melaporkan 12.0.0.
  • settings_schema: halaman pengaturan aplikasi di Konsol Sistem (lihat Pengaturan).

make apply menyalin manifest ke server/manifest.go dan webapp/src/manifest.ts; make manifest-check memvalidasinya.

Bagian server

Bagian server mengimpor modul Go yang sama dengan aplikasi Mattermost, github.com/mattermost/mattermost/server/public, dari proxy modul Go. main hanya memulai aplikasi:

package main

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

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

Jenis Plugin menyematkan plugin.MattermostPlugin, yang memberikannya p.API, dan mengimplementasikan hook yang dibutuhkan:

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

Hook umum

HookKapan dijalankan
OnActivate, OnDeactivateKetika aplikasi diaktifkan atau dinonaktifkan, dan ketika server dimulai dan dihentikan. Daftarkan perintah dan bot di OnActivate.
OnConfigurationChangeKetika pengaturannya berubah; muat dengan p.API.LoadPluginConfiguration(&cfg).
ServeHTTPPermintaan ke /plugins/<app id>/…. Server mengatur header Mattermost-User-Id ketika permintaan berasal dari pengguna yang masuk; tolak permintaan tanpa header tersebut kecuali memang dimaksudkan untuk publik.
ExecuteCommandPerintah yang didaftarkan oleh aplikasi dijalankan.
MessageWillBePosted, MessageWillBeUpdatedSebelum pesan disimpan: kembalikan pesan yang diubah, atau alasan untuk menolaknya.
MessageHasBeenPosted, MessageHasBeenUpdated, MessageHasBeenDeletedSetelah kejadian, untuk efek samping.
ReactionHasBeenAdded, ReactionHasBeenRemovedReaksi.
UserHasJoinedChannel, UserHasLeftChannel, UserHasJoinedTeam, UserHasLeftTeam, ChannelHasBeenCreatedPerubahan keanggotaan dan kanal. Tim adalah komunitas.
FileWillBeUploadedSebelum file disimpan: ubah atau tolak.
OnPluginClusterEventPesan dari aplikasi yang sama di server lain dalam klaster.

Daftar lengkap, dengan tanda tangan yang tepat, ada di server/public/plugin/hooks.go; API ada di server/public/plugin/api.go. Paket pluginapi (server/public/pluginapi) membungkus API dengan helper yang lebih ramah.

Panggilan API yang berguna

  • CreatePost, UpdatePost, SendEphemeralPost: kirim sebagai bot aplikasi atau sebagai pengguna.
  • EnsureBotUser(&model.Bot{Username, DisplayName}): buat bot aplikasi sekali dan dapatkan ID-nya.
  • PublishWebSocketEvent("poll_updated", payload, &model.WebsocketBroadcast{ChannelId: id}): kirim peristiwa ke aplikasi web. Datang sebagai custom_<app id>_poll_updated.
  • OpenInteractiveDialog: buka dialog.
  • LogInfo, LogError dan sejenisnya: tulis ke log server, diberi tag dengan ID aplikasi.
  • GetUser, GetChannel, HasPermissionToChannel: periksa siapa yang boleh melakukan apa sebelum bertindak untuk pengguna.

Aplikasi berjalan di setiap server dalam klaster. Simpan status di penyimpanan kunci-nilai atau database, bukan hanya di memori.

Layanan AI

Server Antimatter memanggil model AI untuk aplikasi, melalui koneksi model yang disetel oleh admin (lihat AI di panduan admin). Aplikasi tidak pernah menyimpan kunci penyedia: aplikasi hanya menyebutkan orang yang diwakilinya dan lokasinya, lalu server memeriksa apakah orang tersebut dapat menggunakan koneksi di lokasi itu, menerapkan batas dan anggaran, menghitung token atas nama orang dan aplikasi tersebut, serta mengaudit panggilan tanpa isinya.

MetodeFungsinya
AIChat(actingUserID, channelID, teamID, *ai.ChatRequest)Penyelesaian percakapan: pesan, alat, keluaran terstruktur (JSONSchema).
AIChatStream(…)Sama, tetapi dialirkan: baca Events dari aliran hingga kanal ditutup, atau Close untuk menghentikan panggilan.
AIEmbed(…, *ai.EmbedRequest)Embedding teks.
AITranscribe(…, *ai.TranscribeRequest)Ucapan ke teks.
AIDecide(…, *ai.DecideRequest)Pertanyaan bertipe ke model keputusan (ya atau tidak, pilihan, skor) tentang sebuah teks.
AIConnectionsForUser(userID, channelID, teamID)Koneksi yang dapat digunakan seseorang di sana, beserta model dan kemampuannya, bukan kuncinya.

ConnectionID pada setiap permintaan memilih koneksi, dan Model memilih salah satu modelnya (kosong untuk model bawaan koneksi). Operation adalah label singkat untuk catatan penggunaan dan audit, bukan isinya. Versi server minimumnya adalah 11.11.

Ambil actingUserID hanya dari sumber yang telah diverifikasi server: header Mattermost-User-Id pada permintaan ke ServeHTTP Anda, atau pengguna dari konteks hook (pengarang kiriman, pengguna yang menjalankan perintah). Jangan pernah mengambilnya dari pesan, muatan webhook, URL, atau permintaan aplikasi lain: aplikasi Anda akan menghabiskan akses orang lain.

Metode-metode ini dan paket ai (server/public/ai) hanya ada pada salinan modul milik Antimatter, bukan pada versi yang diterbitkan oleh Mattermost. Untuk menggunakannya, bangun modul terhadap server/public Antimatter dengan baris replace di go.mod Anda, misalnya replace github.com/mattermost/mattermost/server/public => ../antimatter/server/public. Aplikasi semacam ini hanya berjalan di Antimatter.

Menyimpan data

Setiap aplikasi memiliki penyimpanan kunci-nilai sendiri di database server:

  • KVSet(key, value), KVGet(key), KVDelete(key): nilai berupa byte, biasanya JSON. Kunci maksimal 150 karakter.
  • KVSetWithExpiry(key, value, seconds): nilai akan hilang setelah waktu tersebut.
  • KVCompareAndSet(key, old, new) dan KVSetWithOptions dengan Atomic: pembaruan aman ketika beberapa permintaan atau server menulis kunci yang sama. Polls menyimpan setiap suara dengan cara ini dan mengulang saat terjadi konflik.
  • KVList(page, perPage): kunci, per halaman.

Bagian aplikasi web

Bundel mendaftarkan kelas dengan window.registerPlugin. Metode initialize menerima registri dan store 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());
  • Perpustakaan bersama. Aplikasi web menyediakan React, ReactDOM, Redux, React Redux, dan React Intl: cantumkan sebagai externals webpack (React, ReactDOM, Redux, ReactRedux, ReactIntl) alih-alih membundel salinan Anda sendiri. Lihat webpack.config.js Polls.
  • Memanggil bagian server Anda. Ambil ${window.basename}/plugins/<app id>/… dengan opsi dari Client4.getOptions() (dari mattermost-redux/client): opsi ini membawa header yang diperlukan server untuk menerima permintaan dari browser.
  • Status Anda sendiri. registerReducer menambahkan reducer, yang disimpan di bawah state['plugins-<app id>'].
  • Tipe. Metode registri tambahan Antimatter tidak ada dalam tipe yang dipublikasikan Mattermost: Polls mendeklarasikan registri yang digunakan di webapp/src/types/host.ts.

Metode registri dan parameternya didokumentasikan dalam kode, di webapp/fusion/src/plugins/registry.ts (Fusion) dan webapp/channels/src/plugins/registry.ts (klasik).

Mendukung antarmuka Fusion

Antimatter memiliki dua antarmuka web: Fusion (standar) dan klasik. Keduanya memuat bundel aplikasi yang sama dan memiliki registri yang sama, tetapi Fusion menggambar banyak hal sendiri dan menempatkan komponen plugin dengan caranya sendiri. Uji aplikasi Anda di keduanya.

Mendeteksi antarmuka

Sebelum aplikasi dimuat, kedua antarmuka mengatur window.antimatterWebUI menjadi 'fusion' atau 'classic', dan nilai yang sama pada elemen akar halaman sebagai data-am-web-ui. Di Mattermost, keduanya tidak diatur.

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

Aplikasi bawaan menggunakan ini untuk menggambar antarmuka dengan cara yang berbeda. Poll menggambar kartu jajak pendapat dan dialog pembuatannya dengan gaya Fusion, dan membuka dialognya sendiri untuk /poll di Fusion. Calls dan Kanal Suara tidak menyertakan komponen bilah samping dan toast yang diganti Fusion dengan antarmuka panggilannya sendiri. Aplikasi Agen menempatkan aksi AI-nya di menu pesan, sebagai grup di bawah Lainnya, bukan di bilah alat hover. Kelas CSS Fusion (dengan awalan am-) dan lapisan dialognya (elemen am-layer) adalah yang digunakan aplikasi-aplikasi ini untuk menyelaraskan antarmuka; ini bukan API yang didokumentasikan dan stabil.

Di mana Fusion menampilkan komponen plugin

Metode registriDi mana muncul di Fusion
registerAppBarComponent, registerChannelHeaderButtonActionRel aplikasi, kolom ikon di tepi kanan, antara avatar Anda dan tombol Tentang Antimatter. Tombol header kanal juga berada di sana, bukan di header kanal. Disembunyikan ketika admin sistem mengaktifkan Nonaktifkan Bilah Aplikasi (ExperimentalSettings.DisableAppBar, nonaktif secara default).
registerRightHandSidebarComponentPanel di sisi kanan. Orang dapat mengubah ukurannya dengan menyeret tepinya, dan membukanya di jendela baru. Jika aplikasi tersebut juga merupakan produk, panel menawarkan Buka sebagai halaman penuh. Lihat Panel di jendela baru.
registerChannelHeaderIconIkon di header kanal, setelah tombol panggilan; di ponsel, di menu Lainnya header.
registerCallButtonActionTombol panggilan di header kanal; beberapa penyedia berbagi menu Cara lain untuk melakukan panggilan.
registerPostDropdownMenuActionMenu Aksi pesan (⋯) pesan, di submenu Lainnya; juga di menu Tindakan utas panel utas, di bawah Lainnya. Fusion menggambar ikon aplikasi di depan setiap aksi, berwarna abu-abu: ikonnya sendiri untuk aplikasi bawaan, jika tidak, ikon yang ditampilkan aplikasi di rel aplikasi, jika tidak, ikon netral. Elemen tanpa teks di awal teks aksi (ikon yang digambar oleh aplikasi itu sendiri) disembunyikan.
registerPostDropdownSubMenuActionGrup dengan judul di submenu Lainnya yang sama, setelah aksi tunggal. Tidak ada aplikasi yang mendapatkan entri miliknya sendiri di menu pesan.
registerPostActionComponentBilah alat hover pesan, sebelum tombol ⋯.
registerPostTypeComponent, registerPostMessageAttachmentComponent, registerPostHeaderComponent, registerPostWillRenderEmbedComponentPesan itu sendiri: isinya, di bawah teksnya, setelah waktunya, dan menggantikan pratinjau tautan.
registerChannelHeaderMenuActionMenu Opsi kanal kanal (⋯ pada kanal di bilah samping, atau klik kanan), sebagai item dengan ikon colokan.
registerMainMenuActionMenu komunitas (nama komunitas di bagian atas daftar kanal), di bawah Lainnya, dikelompokkan per aplikasi.
registerSidebarBrowseOrAddChannelMenuActionMenu komunitas, setelah Buat kanal dan Buat kategori.
registerFileUploadMethodMenu + komposer (Tambahkan ke pesan), setelah Unggah file.
registerPostEditorActionComponentBilah format komposer, ketika format ditampilkan.
registerAIActionMenuItemComponentMenu AI komposer.
registerFileDropdownMenuAction, registerFilePreviewComponentMenu opsi file, dan pratinjau file.
registerUserSettingsProfil & pengaturan, satu tab per aplikasi di bawah judul Plugin.
registerChannelSettingsTabDialog pengaturan kanal, di bawah judul Aplikasi.
registerPopoverUserAttributesComponent, registerPopoverUserActionsComponentKartu profil yang ditampilkan ketika Anda mengklik nama atau avatar.
registerLeftSidebarHeaderComponent, registerSidebarChannelLinkLabelComponent, registerChannelIconOverride, registerBottomTeamSidebarComponentBagian atas daftar kanal komunitas, di samping nama kanal, ikon kanal, dan bagian bawah bilah komunitas di sisi kiri.
registerChannelIntro, registerChannelIntroButtonAction, registerChannelToastComponent, registerNewMessagesSeparatorActionComponent, registerChannelComposerBannerComponent, registerComposerPlaceholderAwal riwayat kanal, bagian atas daftar pesan, garis belum dibaca, di atas komposer, dan placeholder komposer.
registerChannelTypeOptionJenis kanal dalam dialog Buat kanal.
registerSearchComponentsKotak cari di header kanal: cakupan aplikasi muncul saat kotak kosong.
registerUserGuideDropdownMenuActionTentang Antimatter (bagian bawah bilah aplikasi), di bawah Bantuan.
registerProductHalaman penuh di URL produk, yang terdaftar di bawah Produk di Tentang Antimatter. Halaman produk mempertahankan bingkai halaman klasik, di mana menu produk hanya muncul ketika aplikasi mendaftarkan produk atau menambahkan entri ke dalamnya. Aplikasi Kanban dan Playbooks bawaan tidak lagi mendaftarkan produk: halaman mereka terbuka di panel samping mereka.
registerRootComponent, registerGlobalComponent, hook seperti registerMessageWillBePostedHook, registerSlashCommandWillBePostedHook, registerMessageWillFormatHook, registerFilesWillUploadHook, registerDesktopNotificationHook, registerWebSocketEventHandler, registerReducer, registerTranslationsBerfungsi seperti pada antarmuka klasik.
registerAdminConsolePlugin, registerAdminConsoleCustomSettingKonsol Sistem, yang merupakan versi klasik di kedua antarmuka.

Yang tidak ditampilkan atau dijalankan oleh Fusion

  • registerPostDropdownMenuComponent (komponen di menu pesan), registerPostCardTypeComponent (kartu pesan di panel kanan) dan registerProductSwitcherMenuItem tidak ditampilkan di tampilan utama Fusion. Gunakan registerPostDropdownMenuAction sebagai pengganti yang pertama.
  • registerMessageWillBeUpdatedHook tidak dijalankan saat pesan diedit di Fusion. Periksa pengeditan di server dengan hook MessageWillBeUpdated sebagai gantinya.
  • registerActionAfterChannelCreation hanya dijalankan dari dialog pembuatan kanal klasik, yang Fusion buka dari Opsi lainnya… di dialog Buat kanal-nya.
  • Komponen bilah aplikasi ditampilkan apa pun nilai supportedProductIds-nya.
  • Rute dari registerNeedsTeamRoute dan registerCustomRoute mempertahankan bingkai halaman klasik, seperti produk.

Hook yang hanya dimiliki Antimatter

Metode registry ini tidak ada di Mattermost. Periksa apakah metode tersebut ada sebelum memanggilnya, agar aplikasi juga berfungsi di sana:

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');
}
MetodeYang ditambahkan
registerComposerMenuItem({text, icon, fusionIcon, action, shouldRender})Item di menu + kotak pesan, di kedua antarmuka. action menerima {channelId, rootId}.
registerEmojiPickerTab({id, label, icon, fusionIcon, order, component, shouldRender, replacesGifPicker})Tab dari pemilih emoji kotak pesan, seperti GIF atau stiker. Di Fusion, tab dengan order di bawah 10 muncul sebelum tab Emoji. Komponen menerima channelId, rootId, filter, onFilterChange, onSelectDone, insertText, isFusion dan, di Fusion, pickerKit (lihat di bawah).
registerChannelViewPanel({matcher, component})Panel di antara header kanal dan pesan, untuk kanal yang dipilih oleh matcher(state, channel). Panel ini dapat menyembunyikan pesan dan komposer (setMessagesVisible), seperti yang dilakukan oleh kanal suara.
registerSidebarChannelFooterComponent(component)Baris di bawah sebuah kanal di bilah samping, seperti orang-orang yang sedang dalam panggilannya. Baris ini menerima channel.
registerIntegrationsOption({title, description, fusionIcon, action, shouldRender})Khusus Fusion: entri di halaman Integrasi, di sebelah emoji kustom. Aplikasi GIF menambahkan GIF dan stiker kustomnya di sana.

Menambahkan dan mengelola tampilan di pemilih emoji

Di Fusion, tab pemilih emoji memiliki prop pickerKit: komponen yang digunakan tab Emoji untuk tampilan tambah dan kelolanya sendiri, sehingga tampilan tab terlihat seragam dan tetap berada di dalam pemilih. Aplikasi GIF dan Meme menggunakannya untuk menambah dan mengelola GIF, stiker, dan template meme. Prop ini berisi version (1), hook usePickerView dan useOnEscape, serta komponen PickerViewButtons (tombol + dan kelola di samping kotak pencarian), PickerViewFrame (tampilan dengan panah kembali), PickerNotice, PickerField, PickerSearch, PickerDropZone, dan PickerManageList. Tekan Escape untuk kembali dari tampilan ke grid tab. Pastikan prop ini ada: host lain tidak menyediakannya, dan tab akan menggunakan dialognya sendiri. Tipe data ada di webapp/fusion/src/types/store/plugins.ts (EmojiPickerTabProps) dan komponen ada di webapp/fusion/src/fusion/popovers/picker_views.tsx.

Panel di jendela baru

Fusion memberikan dua prop tambahan ke komponen registerRightHandSidebarComponent, sehingga Buka di jendela baru membuka panel di tempat asalnya, bukan di beranda aplikasi:

  • setPanelState(state): panggil ini ketika panel berpindah tempat, dengan string pendek yang didefinisikan oleh aplikasi Anda, seperti 'note/<id>' atau 'board/<id>/card/<id>', atau null di beranda. Nilai ini masuk ke alamat jendela, jadi jangan pernah menyimpan rahasia di dalamnya. State yang lebih dari 512 karakter, atau mengandung karakter kontrol, akan diabaikan.
  • initialPanelState: di jendela baru, ini adalah state yang dimiliki panel, atau undefined. Perlakukan sebagai input yang tidak dapat dipercaya: ambil ulang item sebagai pengguna yang sedang masuk, dan tampilkan beranda aplikasi atau tampilan tidak ditemukan jika mereka tidak dapat membukanya.

State dilupakan ketika panel ditutup atau panel aplikasi lain mengambil tempatnya. Untuk membuka jendela panel secara mandiri, popoutRhsPlugin (juga ada di window.WebappUtils.popouts) menerima state sebagai argumen kelima opsional. Catatan, Papan Putih, To-do, Kanban, dan Buku Taktik menggunakan ini.

fusionIcon menentukan nama ikon dari set ikon Fusion (misalnya poll, sticker, calendar, atau image; lihat webapp/fusion/src/fusion/components/icon). Fusion menampilkan ikon ini alih-alih icon, sehingga item tersebut sesuai dengan item lainnya.

Pengaturan

settings_schema di plugin.json mendeskripsikan halaman aplikasi di Konsol Sistem › 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."}
  ]
}

Jenis pengaturan: bool, text, longtext, number, dropdown, radio, generated (rahasia acak), username, dan custom (digambar oleh komponen yang didaftarkan oleh bagian web app dengan registerAdminConsoleCustomSetting). Pengaturan dapat dikelompokkan dalam sections.

Bagian server membacanya ke dalam struktur data (struct) yang field-nya sesuai dengan kuncinya:

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
}

Admin juga dapat menyetelnya dalam konfigurasi, di bawah PluginSettings.Plugins.<app id>, dengan kunci dalam huruf kecil (lihat panduan admin).

Terjemahan

Antimatter diterjemahkan ke dalam 37 bahasa, dan aplikasi bawaan juga demikian. Bagian web app melakukannya seperti ini:

  1. Tulis setiap string dengan React Intl, dengan ID dan teks bahasa Inggrisnya: <FormattedMessage id='polls.create' defaultMessage='Create a poll'/> atau defineMessages. Beri awalan ID dengan nama aplikasi Anda.
  2. Ekstrak string bahasa Inggris ke webapp/i18n/en.json (aplikasi bawaan menggunakan formatjs extract).
  3. Tambahkan satu file per bahasa di sebelahnya, dengan ID yang sama: fr.json, de.json, pt-BR.json, zh-CN.json…
  4. Berikan kepada aplikasi web, yang meminta bahasa pengguna:
    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);

String yang hilang akan kembali ke defaultMessage berbahasa Inggris. Untuk string yang dikirim oleh bagian server (jawaban perintah, pesan bot), paket pluginapi/i18n memuat file terjemahan yang disertakan dalam bundle (misalnya assets/i18n) dan memberikan lokalizer dalam bahasa masing-masing pengguna.

Membangun, menginstal, dan men-debug

Anda membutuhkan Go dan Node.js (versi yang ada di .nvmrc aplikasi). Di folder aplikasi:

PerintahFungsinya
make distMembangun bagian server untuk semua platform dan bagian aplikasi web, lalu menulis bundle ke dist/<app id>-<version>.tar.gz.
make testMenjalankan tes Go dan JavaScript.
make check-styleMelint kode (golangci-lint, ESLint) dan memeriksa tipe TypeScript.
make deployMembangun, lalu menginstal dan mengaktifkan bundle di server.
make watch, make deploy-from-watchYang pertama membangun ulang bagian aplikasi web setiap kali Anda menyimpan; yang kedua membundel apa yang dibangun dan menginstalnya di server.
make enable, make disable, make resetMengaktifkan, menonaktifkan, atau memulai ulang aplikasi di server.
make logs, make logs-watchMenampilkan baris aplikasi di log server.
make attachMenempelkan debugger Delve ke bagian server yang sedang berjalan (bangun dengan AM_DEBUG=1 terlebih dahulu).

make deploy dan perintah server lainnya menggunakan soket mode lokal jika menemukannya (/var/tmp/antimatter_local.socket, atau AM_LOCALSOCKETPATH): jalankan di mesin server dengan mode lokal aktif. Jika tidak, mereka masuk ke server yang diberikan oleh MM_SERVICESETTINGS_SITEURL dengan AM_ADMIN_TOKEN, atau AM_ADMIN_USERNAME dan AM_ADMIN_PASSWORD. Alat build belum membaca nama AM_ untuk URL situs.

Menginstal bundle secara manual

  • Admin sistem mengizinkan unggahan dengan AM_PLUGINSETTINGS_ENABLEUPLOADS=true (nonaktif secara default; tidak ada sakelar di Konsol Sistem).
  • Lalu Konsol Sistem › Plugin › Manajemen Plugin › Unggah Plugin, atau amctl plugin add weather.tar.gz diikuti dengan amctl plugin enable com.example.weather.
  • Jika server memiliki Wajibkan Tanda Tangan Plugin aktif, ia hanya menerima bundle yang ditandatangani dengan kunci yang dipercaya; lihat Menginstal aplikasi lain.
Untuk iterasi cepat di server pengembangan, setel AM_SERVICESETTINGS_ENABLEDEVELOPER=true di server: kesalahan kemudian membawa detailnya. Makefile aplikasi terbundle membangun bagian server hanya untuk platform Anda ketika MM_SERVICESETTINGS_ENABLEDEVELOPER disetel di shell Anda.