Σύνταξη εφαρμογών (πρόσθετα)

Οι εφαρμογές προσθέτουν λειτουργικότητα στο Antimatter από μέσα: ένα server μέρος σε Go που συνδέεται με τον server, και ένα web app μέρος σε React που προσθέτει μενού, πάνελ, τύπους μηνυμάτων και σελίδες. Αυτή η σελίδα δείχνει πώς δομείται μια εφαρμογή, χρησιμοποιώντας τη συσκευασμένη εφαρμογή Polls ως παράδειγμα, και τι αλλάζει στο Fusion interface του Antimatter.

Πώς λειτουργεί μια εφαρμογή

  • Το server μέρος είναι ένα πρόγραμμα Go. Ο server το εκτελεί ως ξεχωριστή διεργασία και επικοινωνεί με αυτό μέσω RPC: καλεί τα hooks της εφαρμογής (ένα μήνυμα πρόκειται να δημοσιευτεί, εκτελέστηκε μια εντολή…) και η εφαρμογή καλεί το plugin API του server (δημιουργία μηνύματος, ανάγνωση χρήστη, αποθήκευση δεδομένων…). Μπορεί επίσης να εξυπηρετεί HTTP routes κάτω από /plugins/<app id>/.
  • Το web app μέρος είναι ένα JavaScript bundle που φορτώνεται στον browser μαζί με το web app. Εγγραφεί React components και callbacks στο μητρώο: ένα κουμπί σε ένα μενού, ένα πάνελ, ένας τρόπος απόδοσης ενός τύπου μηνύματος.
  • Κάθε μέρος είναι προαιρετικό. Μια εφαρμογή εγκαθίσταται ως δέσμευμα .tar.gz, και οι διαχειριστές συστήματος την ενεργοποιούν, απενεργοποιούν και ρυθμίζουν.
Το server μέρος εκτελείται με πλήρη πρόσβαση στα δεδομένα του server. Χρησιμοποιήστε μια εφαρμογή όπως κώδικα που αναπτύσσετε στον server.

Το Antimatter εκτελεί εφαρμογές γραμμένες για Mattermost, και το αντίστροφο, εφόσον μια εφαρμογή χρησιμοποιεί μόνο hooks που και τα δύο έχουν. Δείτε Hooks που έχει μόνο το Antimatter.

Η δομή του δέσμευματος

Δεν υπάρχει ξεχωριστό starter template: ξεκινήστε από μια μικρή συσκευασμένη εφαρμογή όπως το antimatter-plugin-polls ή το antimatter-plugin-todo, και αλλάξτε το ID και τον κώδικά της. Το Polls έχει τη δομή:

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

Προαιρετικά: ένα φάκελο public/, που εξυπηρετείται ως έχει στο /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: μοναδικό και μόνιμο· συνήθως χρησιμοποιείται reverse domain name. Ονοματίζει τις routes, τις αποθηκευμένες ρυθμίσεις και τα WebSocket events.
  • version: semantic version. Οι συσκευασμένες εφαρμογές το αφήνουν εκτός του plugin.json· το build tool το παίρνει από το τελευταίο v* git tag.
  • min_server_version: το Antimatter αναφέρει 12.0.0.
  • settings_schema: η σελίδα ρυθμίσεων της εφαρμογής στην Κονσόλα συστήματος (βλ. Ρυθμίσεις).

Η εντολή make apply αντιγράφει το manifest στα αρχεία server/manifest.go και webapp/src/manifest.ts· η εντολή make manifest-check το ελέγχει.

Το μέρος του server

Το μέρος του server εισάγει το ίδιο Go module με τις εφαρμογές Mattermost, github.com/mattermost/mattermost/server/public, από το Go module proxy. Το main του απλώς εκκινεί την εφαρμογή:

package main

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

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

Ο τύπος Plugin ενσωματώνει τον τύπο plugin.MattermostPlugin, ο οποίος του παρέχει το p.API, και υλοποιεί τα hooks που χρειάζεται:

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

Κοινά hooks

HookΠότε εκτελείται
OnActivate, OnDeactivateΌταν η εφαρμογή ενεργοποιείται ή απενεργοποιείται, καθώς και κατά την εκκίνηση και τον τερματισμό του server. Εγγραφείτε εντολές και bots στο OnActivate.
OnConfigurationChangeΌταν αλλάζουν οι ρυθμίσεις της. Φορτώστε τις με p.API.LoadPluginConfiguration(&cfg).
ServeHTTPΑιτήματα προς /plugins/<app id>/…. Το server ορίζει το header Mattermost-User-Id όταν το αίτημα προέρχεται από συνδεδεμένο χρήστη· απορρίψτε αιτήματα χωρίς αυτό, εκτός αν προορίζονται να είναι δημόσια.
ExecuteCommandΌταν εκτελείται μια εντολή που εγγραφήθηκε από την εφαρμογή.
MessageWillBePosted, MessageWillBeUpdatedΠριν αποθηκευτεί ένα μήνυμα: επιστρέψτε ένα τροποποιημένο post ή έναν λόγο απόρριψής του.
MessageHasBeenPosted, MessageHasBeenUpdated, MessageHasBeenDeletedΜετά τη δράση, για δευτερεύουσες επιπτώσεις.
ReactionHasBeenAdded, ReactionHasBeenRemovedΑντιδράσεις.
UserHasJoinedChannel, UserHasLeftChannel, UserHasJoinedTeam, UserHasLeftTeam, ChannelHasBeenCreatedΑλλαγές στη συμμετοχή και στα κανάλια. Ένα team είναι μια κοινότητα.
FileWillBeUploadedΠριν αποθηκευτεί ένα αρχείο: τροποποιήστε το ή απορρίψτε το.
OnPluginClusterEventΈνα μήνυμα από την ίδια εφαρμογή σε άλλο server ενός cluster.

Η πλήρης λίστα, με τις ακριβείς υπογραφές, βρίσκεται στο server/public/plugin/hooks.go· το API βρίσκεται στο server/public/plugin/api.go. Το πακέτο pluginapi (server/public/pluginapi) περιτυλίγει το API σε πιο φιλικές βοηθητικές συναρτήσεις.

Χρήσιμες κλήσεις API

  • CreatePost, UpdatePost, SendEphemeralPost: δημοσίευση ως το bot της εφαρμογής ή ως χρήστης.
  • EnsureBotUser(&model.Bot{Username, DisplayName}): δημιουργία του bot της εφαρμογής μία φορά και λήψη του ID του.
  • PublishWebSocketEvent("poll_updated", payload, &model.WebsocketBroadcast{ChannelId: id}): αποστολή συμβάντος στο web app. Φτάνει ως custom_<app id>_poll_updated.
  • OpenInteractiveDialog: άνοιγμα διαλόγου.
  • LogInfo, LogError και συναφή: εγγραφή στο αρχείο καταγραφής του server, με ετικέτα το ID της εφαρμογής.
  • GetUser, GetChannel, HasPermissionToChannel: έλεγχος ποιος μπορεί να κάνει τι, πριν ενεργήσετε για λογαριασμό χρήστη.

Μια εφαρμογή εκτελείται σε κάθε διακομιστή ενός κλάστου. Κρατήστε την κατάσταση (state) στο αποθετήριο κλειδιών-τιμών ή στη βάση δεδομένων, όχι μόνο στη μνήμη.

Η υπηρεσία AI

Ο server του Antimatter καλεί μοντέλα AI για τις εφαρμογές, μέσω των συνδέσεων μοντέλων που έχει ορίσει ένας διαχειριστής (βλ. AI στον οδηγό διαχείρισης). Μια εφαρμογή δεν κατέχει ποτέ το κλειδί ενός προμηθευτή: ονοματίζει το πρόσωπο για το οποίο ενεργεί και το πού, και ο server ελέγχει ότι αυτό το πρόσωπο μπορεί να χρησιμοποιήσει τη σύνδεση εκεί, εφαρμόζει τα όρια και τα προϋπολογισμούς της, μετράει τα tokens έναντι του προσώπου και της εφαρμογής, και ελέγχει την κλήση χωρίς το περιεχόμενό της.

ΜέθοδοςΛειτουργία
AIChat(actingUserID, channelID, teamID, *ai.ChatRequest)Μια ολοκλήρωση συνομιλίας: μηνύματα, εργαλεία, δομημένη έξοδος (JSONSchema).
AIChatStream(…)Το ίδιο, με streaming: διαβάστε τα Events του stream μέχρι να κλείσει το κανάλι, ή Close το για να σταματήσετε την κλήση.
AIEmbed(…, *ai.EmbedRequest)Embeddings κειμένων.
AITranscribe(…, *ai.TranscribeRequest)Μετατροπή ομιλίας σε κείμενο.
AIDecide(…, *ai.DecideRequest)Τυποποιημένες ερωτήσεις σε ένα μοντέλο λήψης αποφάσεων (ναι ή όχι, μια επιλογή, μια βαθμολογία) σχετικά με ένα κείμενο.
AIConnectionsForUser(userID, channelID, teamID)Οι συνδέσεις που μπορεί να χρησιμοποιήσει ένα πρόσωπο εκεί, με τα μοντέλα και τις ικανότητές τους, ποτέ τα κλειδιά τους.

Το ConnectionID κάθε αιτήματος επιλέγει τη σύνδεση, και το Model ένα από τα μοντέλα της (κενό για το προεπιλεγμένο της σύνδεσης). Το Operation είναι μια σύντομη ετικέτα για τα αρχεία χρήσης και ελέγχου, ποτέ περιεχόμενο. Η ελάχιστη έκδοση server είναι 11.11.

Πάρτε το actingUserID μόνο από μια πηγή που έχει επαληθεύσει ο server: το header Mattermost-User-Id ενός αιτήματος προς το ServeHTTP σας, ή το χρήστη του context ενός hook (ο συγγραφέας μιας αναρτησης, το χρήστης που εκτέλεσε μια εντολή). Ποτέ μην το παίρνετε από ένα μήνυμα, ένα webhook payload, ένα URL ή ένα αίτημα από άλλη εφαρμογή: η εφαρμογή σας θα δαπανούσε την πρόσβαση κάποιου άλλου.

Αυτές οι μέθοδοι και το πακέτο ai (server/public/ai) υπάρχουν μόνο στην αντίγραφο του Antimatter του module, όχι στην έκδοση που δημοσιεύεται από το Mattermost. Για να τις χρησιμοποιήσετε, συγκεντρώστε (build) εναντίον του server/public του Antimatter με μια γραμμή replace στο go.mod σας, για παράδειγμα replace github.com/mattermost/mattermost/server/public => ../antimatter/server/public. Μια τέτοια εφαρμογή εκτελείται μόνο στο Antimatter.

Παροχή εργαλείων σε πράκτορες

Μια εφαρμογή προσφέρει τα δικά της εργαλεία σε πράκτορες μέσω της RegisterAITools([]*model.AIToolDefinition): για κάθε εργαλείο ορίζονται το όνομα, ο τίτλος, η περιγραφή, το σχήμα JSON εισόδου και ο τύπος (read, write ή external_write), ο οποίος καθορίζει αν η πολιτική ενός πράκτορα επιτρέπει την αυτόματη εκτέλεσή του (βλ. Εργαλεία στον οδηγό διαχειριστή). Οι δικαιώσεις ελέγχονται σε ένα όρισμα καναλιού ή κοινότητας της κλήσης, ή στον διακομιστή, ανάλογα με τις ανάγκες του εργαλείου. Όταν ένας πράκτορας χρησιμοποιεί το εργαλείο, ο διακομιστής καλεί το hook ExecuteAITool(c, *model.AIToolExecution) *model.AIToolResult με το αναγνωρισμένο πρόσωπο που πραγματοποίησε το αίτημα στο πεδίο UserId: ενεργείτε αποκλειστικά με τα δικαιώματά του και ποτέ με αυτά του πράκτορα. Τα εργαλεία ονομάζονται <app id>.<name>. Η ελάχιστη έκδοση διακομιστή είναι η 11.11. Αυτές οι μέθοδοι και το hook υπάρχουν μόνο στο server/public του Antimatter, όπως και η υπηρεσία AI παραπάνω. Μια εφαρμογή με αναγνωριστικό antimatter ή που ξεκινά με mcp. δεν μπορεί να καταχωρήσει εργαλεία.

Αποθήκευση δεδομένων

Κάθε εφαρμογή έχει το δικό της αποθετήριο κλειδιών-τιμών στη βάση δεδομένων του διακομιστή:

  • KVSet(key, value), KVGet(key), KVDelete(key): οι τιμές είναι bytes, συνήθως JSON. Τα κλειδιά είναι έως και 150 χαρακτήρες.
  • KVSetWithExpiry(key, value, seconds): η τιμή εξαφανίζεται μετά από αυτό το χρονικό διάστημα.
  • KVCompareAndSet(key, old, new) και KVSetWithOptions με Atomic: ασφαλείς ενημερώσεις όταν πολλά αιτήματα ή διακομιστές γράφουν στο ίδιο κλειδί. Η εφαρμογή Ψηφοφοριών αποθηκεύει έτσι κάθε ψήφο και επαναλαμβάνει τη διαδικασία σε περίπτωση σύγκρουσης.
  • KVList(page, perPage): τα κλειδιά, σελίδα προς σελίδα.

Το μέρος της web εφαρμογής

Το bundle καταγράφει μια κλάση με window.registerPlugin. Η μέθοδος initialize της λαμβάνει το μητρώο (registry) και το 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());
  • Κοινές βιβλιοθήκες. Η web εφαρμογή παρέχει React, ReactDOM, Redux, React Redux και React Intl: αναφέρετε τις ως webpack externals (React, ReactDOM, Redux, ReactRedux, ReactIntl) αντί να συμπεριλάβετε δικές σας αντιγραφές. Δείτε το webpack.config.js της Ψηφοφορίας.
  • Κλήσεις στο μέρος του διακομιστή σας. Κάντε Fetch στο ${window.basename}/plugins/<app id>/… με τις επιλογές από Client4.getOptions() (από mattermost-redux/client): αυτές μεταφέρουν τα headers που χρειάζεται ο διακομιστής για να δεχτεί ένα αίτημα από το πρόγραμμα περιήγησης.
  • Η δική σας κατάσταση. Η registerReducer προσθέτει έναν reducer, αποθηκευμένο κάτω από state['plugins-<app id>'].
  • Τύποι. Οι επιπλέον μέθοδοι μητρώου του Antimatter δεν υπάρχουν στους δημοσιευμένους τύπους του Mattermost: η Ψηφοφορία δηλώνει το μητρώο που χρησιμοποιεί στο webapp/src/types/host.ts.

Οι μέθοδοι του μητρώου και οι παράμετροί τους τεκμηριώνονται στον κώδικα, στο webapp/fusion/src/plugins/registry.ts (Fusion) και στο webapp/channels/src/plugins/registry.ts (κλασική).

Υποστήριξη της διεπαφής Fusion

Το Antimatter έχει δύο web διεπαφές: την Fusion, η οποία είναι η προεπιλογή, και την κλασική. Και οι δύο φορτώνουν τα ίδια bundles εφαρμογών και έχουν το ίδιο μητρώο, αλλά η Fusion σχεδιάζει πολλά στοιχεία από μόνη της και τοποθετεί τα συστατικά των εφαρμογών με τον δικό της τρόπο. Δοκιμάστε την εφαρμογή σας και στις δύο.

Ανίχνευση της διεπαφής

Πριν φορτωθεί οποιαδήποτε εφαρμογή, και οι δύο διεπαφές ορίζουν το window.antimatterWebUI σε 'fusion' ή 'classic', και την ίδια τιμή στο ριζικό στοιχείο της σελίδας ως data-am-web-ui. Στο Mattermost, κανένα από τα δύο δεν ορίζεται.

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

Οι ενσωματωμένες εφαρμογές χρησιμοποιούν αυτό το μηχανισμό για να εμφανίζονται διαφορετικά. Η εφαρμογή Ψηφοφορίες σχεδιάζει τις κάρτες ψηφοφορίας στο στυλ του Fusion, και στο Fusion η ψηφοφορία γράφεται στη γραμμή σύνταξης (μια λειτουργία γραμμής σύνταξης) αντί σε ένα παράθυρο διαλόγου. Οι Κλήσεις και τα Φωνητικά κανάλια παραλείπουν τα στοιχεία της πλευρικής στήλης και των ειδοποιήσεων (toast) που το Fusion αντικαθιστά με τη δική του διεπαφή κλήσεων. Η εφαρμογή συνομιλίας AI τοποθετεί τις ενέργειες AI στο μενού μηνύματος, ως ομάδα κάτω από το Περισσότερες ενέργειες, αντί στη γραμμή εργαλείων που εμφανίζεται κατά την τοποθέτηση του δείκτη. Οι δικές της κλάσεις CSS του Fusion (με πρόθεμα am-) και το επίπεδο διαλόγου του (το στοιχείο am-layer) είναι αυτά που χρησιμοποιούν αυτές οι εφαρμογές για να ταιριάζουν με τη διεπαφή. Δεν αποτελούν τεκμηριωμένο, σταθερό API.

Πού εμφανίζει το Fusion τα στοιχεία των προσθηκών

Μέθοδος μητρώουΠού εμφανίζεται στο Fusion
registerAppBarComponent, registerChannelHeaderButtonActionΗ ράβα εφαρμογών, η στήλη εικονιδίων στην ακροδεξιά άκρη, ανάμεσα στο avatar σας και το κουμπί Σχετικά με το Antimatter. Εκεί τοποθετούνται και τα κουμπία κεφαλίδας καναλιού, όχι στην κεφαλίδα του καναλιού. Κρύβεται όταν ένας διαχειριστής συστήματος ενεργοποιήσει την Απενεργοποίηση Λωρίδας εφαρμογών (ExperimentalSettings.DisableAppBar, απενεργοποιημένη από προεπιλογή).
registerRightHandSidebarComponentΤο δεξί πάνελ. Η κεφαλίδα του είναι του Fusion και είναι ίδια για όλες τις εφαρμογές: το εικονίδιο και το όνομα της εφαρμογής (ο καταχωρημένος τίτλος όταν εμφανίζει μόνο κείμενο, αλλιώς η ετικέτα της ράγας εφαρμογών ή το όνομα από το manifest), στη συνέχεια Επαναφορά μεγέθους πάνελ (μόλις αλλάξει μέγεθος), Άνοιγμα σε νέο παράθυρο και Κλείσιμο. Ένας τίτλος που εμφανίζει οποιοδήποτε άλλο στοιχείο (μια χειριστήρια, έναν σύνδεσμο, μια εικόνα) αγνοείται, οπότε σχεδιάστε τα δικά σας χειριστήρια μέσα στο πάνελ, για παράδειγμα σε μια λεπτή γραμμή στην κορυφή του. Οι χρήστες μπορούν να αλλάξουν το μέγεθος του πάνελ σύροντας την άκρη του. Βλ. Το πάνελ σε νέο παράθυρο.
registerChannelHeaderIconΕικονίδια στο κεφάλαιο του καναλιού, μετά τα κουμπία κλήσης· σε κινητά, στο μενού Περισσότερα του κεφαλαίου.
registerCallButtonActionΤο κουμπί κλήσης στο κεφάλαιο του καναλιού· πολλοί πάροχοι μοιράζονται ένα μενού Άλλοι τρόποι κλήσης.
registerPostDropdownMenuActionΤο μενού Ενέργειες μηνύματος (⋯) του μηνύματος, στο υπομενού Περισσότερες ενέργειες· επίσης στο μενού Ενέργειες νήματος του πίνακα νήματος, κάτω από Περισσότερες ενέργειες. Το Fusion αποδίδει το εικονίδιο της εφαρμογής πριν από κάθε ενέργεια, σε γκρι: το δικό του εικονίδιο για τις ενσωματωμένες εφαρμογές, αλλιώς το εικονίδιο που εμφανίζει η εφαρμογή στη ράγα εφαρμογών, αλλιώς ένα ουδέτερο. Ένα στοιχείο χωρίς κείμενο στην αρχή του κειμένου της ενέργειας (ένα εικονίδιο που αποδίδει η ίδια η εφαρμογή) κρύβεται.
registerPostDropdownSubMenuActionΜια ομάδα καταχωρίσεων στο ίδιο υπομενού Περισσότερες ενέργειες, μετά τις μεμονωμένες ενέργειες, διαχωρισμένη με μια γραμμή και χωρίς επικεφαλίδα. Καμία εφαρμογή δεν λαμβάνει δική της καταχώρηση στο μενού μηνύματος.
registerPostActionComponentΗ εργαλειοθήκη που εμφανίζεται με το πέρασμα του δείκτη στο μήνυμα, πριν το κουμπί ⋯.
registerPostTypeComponent, registerPostMessageAttachmentComponent, registerPostHeaderComponent, registerPostWillRenderEmbedComponentΤο ίδιο το μήνυμα: το σώμα του, κάτω από το κείμενό του, μετά την ώρα του, και στη θέση των προεπισκοπήσεων συνδέσμων.
registerChannelHeaderMenuActionΤο μενού Επιλογές καναλιού (⋯ σε ένα κανάλι της πλευρικής στήλης, ή δεξί κλικ), ως στοιχεία με εικονίδιο πρίζας.
registerMainMenuActionΤο μενού της κοινότητας (το όνομα της κοινότητας στην κορυφή της λίστας καναλιών), κάτω από Περισσότερες ενέργειες, ομαδοποιημένο ανά εφαρμογή.
registerSidebarBrowseOrAddChannelMenuActionΤο μενού της κοινότητας, μετά τα Δημιουργία καναλιού και Δημιουργία κατηγορίας.
registerFileUploadMethodΤο μενού + του πεδίου σύνταξης (Προσθήκη στο μήνυμα), μετά το Ανέβασμα αρχείου.
registerPostEditorActionComponentΗ γραμμή μορφοποίησης του πεδίου σύνταξης, όταν εμφανίζεται η μορφοποίηση.
registerAIActionMenuItemComponentΤο μενού Τ.Π. του πεδίου σύνταξης.
registerFileDropdownMenuAction, registerFilePreviewComponentΤο μενού επιλογών ενός αρχείου, και η προεπισκόπησή του.
registerUserSettingsΠροφίλ & ρυθμίσεις, με μια καρτέλα ανά εφαρμογή κάτω από την κεφαλίδα Προσθήκες.
registerChannelSettingsTabΟ διάλογος ρυθμίσεων του καναλιού, κάτω από την κεφαλίδα Εφαρμογές.
registerPopoverUserAttributesComponent, registerPopoverUserActionsComponentΗ κάρτα προφίλ που εμφανίζεται όταν κάνετε κλικ σε ένα όνομα ή ένα εικονίδιο.
registerLeftSidebarHeaderComponent, registerSidebarChannelLinkLabelComponent, registerChannelIconOverride, registerBottomTeamSidebarComponentΗ κορυφή της λίστας καναλιών μιας κοινότητας, δίπλα από το όνομα και το εικονίδιο ενός καναλιού, και το κάτω μέρος του αριστερού πλαισίου της κοινότητας.
registerChannelIntro, registerChannelIntroButtonAction, registerChannelToastComponent, registerNewMessagesSeparatorActionComponent, registerChannelComposerBannerComponent, registerComposerPlaceholderΗ αρχή του ιστορικού ενός καναλιού, η κορυφή της λίστας μηνυμάτων, η γραμμή μη αναγνωσμένων, πάνω από το πεδίο σύνταξης και το κείμενο-καταληπτή του πεδίου σύνταξης.
registerChannelTypeOptionΟι τύποι καναλιών του διαλόγου Δημιουργία καναλιού.
registerSearchComponentsΤο πεδίο αναζήτησης στην κεφαλίδα του καναλιού: τα πεδία εφαρμογών εμφανίζονται όταν είναι άδειο.
registerUserGuideDropdownMenuActionΣχετικά με το Antimatter (κάτω μέρος του πλαισίου της εφαρμογής), κάτω από Βοήθεια.
registerProductΠλήρεις σελίδες στη διεύθυνση URL του προϊόντος, στο κλασικό πλαίσιο σελίδας, χωρίς μενού προϊόντος: Το Fusion δεν διαθέτει τέτοιο μενού και δεν εμφανίζει λίστα με προϊόντα πουθενά, οπότε θα πρέπει να δημιουργήσετε εσείς τον σύνδεσμο προς τη σελίδα. Κανένα ενσωματωμένο app δεν καταχωρεί προϊόν: Το Kanban, τα Playbooks και η συνομιλία AI ανοίγουν στα πλευρικά τους πάνελ.
registerRootComponent, registerGlobalComponent, hooks όπως registerMessageWillBePostedHook, registerSlashCommandWillBePostedHook, registerMessageWillFormatHook, registerFilesWillUploadHook, registerDesktopNotificationHook, registerWebSocketEventHandler, registerReducer, registerTranslationsΛειτουργούν όπως στο κλασικό περιβάλλον.
registerAdminConsolePlugin, registerAdminConsoleCustomSettingΗ Κονσόλα συστήματος, που είναι η κλασική και στα δύο περιβάλλοντα.

Τι δεν εμφανίζει ή εκτελεί το Fusion

  • Το registerPostDropdownMenuComponent (ένα component στο μενού μηνύματος) και το registerPostCardTypeComponent (η κάρτα μιας ανάρτησης στο δεξί πάνελ) δεν εμφανίζονται στις κύριες προβολές του Fusion. Το registerProductSwitcherMenuItem δεν κάνει τίποτα στο Fusion, καθώς δεν υπάρχει μενού προϊόντων (καταγράφει μια προειδοποίηση). Χρησιμοποιήστε το registerPostDropdownMenuAction αντί για το πρώτο.
  • Το registerMessageWillBeUpdatedHook δεν εκτελείται όταν ένα μήνυμα επεξεργάζεται στο Fusion. Ελέγξτε τις επεξεργασίες στον διακομιστή με το hook MessageWillBeUpdated αντί για αυτό.
  • Το registerActionAfterChannelCreation εκτελείται μόνο από τον κλασικό διάλογο δημιουργίας καναλιού, τον οποίο το Fusion ανοίγει από Περισσότερες επιλογές… στον διάλογο Δημιουργία καναλιού.
  • Τα στοιχεία της γραμμής εργαλείων της εφαρμογής εμφανίζονται ανεξάρτητα από το supportedProductIds.
  • Οι διαδρομές από registerNeedsTeamRoute και registerCustomRoute διατηρούν το κλασικό πλαίσιο σελίδας, όπως και τα προϊόντα.

Hooks που υπάρχουν μόνο στο Antimatter

Αυτές οι μέθοδοι μητρώου δεν υπάρχουν στο Mattermost. Ελέγξτε ότι μια μέθοδος υπάρχει πριν την καλέσετε, ώστε η εφαρμογή να λειτουργεί και εκεί:

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');
}
ΜέθοδοςΤι προσθέτει
registerComposerMenuItem({text, icon, fusionIcon, action, shouldRender})Ένα στοιχείο στο μενού + του πεδίου μηνύματος, και στα δύο περιβάλλοντα. Το action λαμβάνει {channelId, rootId}.
registerEmojiPickerTab({id, label, icon, fusionIcon, order, component, shouldRender, replacesGifPicker})Μια καρτέλα του επιλογέα emoji του πεδίου μηνύματος, όπως GIFs ή αυτοκόλλητα. Στο Fusion, οι καρτέλες με order κάτω από 10 εμφανίζονται πριν από την καρτέλα Emoji. Το στοιχείο λαμβάνει channelId, rootId, filter, onFilterChange, onSelectDone, insertText, isFusion και, στο Fusion, pickerKit (βλ. παρακάτω).
registerChannelViewPanel({matcher, component})Ένα πάνελ ανάμεσα στην κεφαλίδα του καναλιού και τα μηνύματα, για τα κανάλια που επιλέγει ο matcher(state, channel). Μπορεί να κρύψει τα μηνύματα και το πεδίο σύνταξης (setMessagesVisible), όπως κάνουν τα φωνητικά κανάλια.
registerSidebarChannelFooterComponent(component)Μια γραμμή κάτω από ένα κανάλι στην πλευρική στήλη, όπως τα άτομα που συμμετέχουν στην κλήση του. Λαμβάνει το channel.
registerIntegrationsOption({title, description, fusionIcon, action, shouldRender})Μόνο Fusion: μια εγγραφή στις σελίδες Ολοκληρώσεις, δίπλα στα προσαρμοσμένα emoji. Η εφαρμογή GIFs προσθέτει εκεί τα προσαρμοσμένα GIFs και τα αυτοκόλλητα της.

Προσθήκη και διαχείριση προβολών στον επιλογέα emoji

Στο Fusion, η καρτέλα του επιλογέα emoji λαμβάνει την ιδιότητα pickerKit: τα συστατικά που χρησιμοποιεί η καρτέλα Emoji για τις δικές της προβολές προσθήκης και διαχείρισης, ώστε οι προβολές μιας καρτέλας να έχουν την ίδια εμφάνιση και να παραμένουν εντός του επιλογέα. Οι εφαρμογές GIFs και Memes το χρησιμοποιούν για να προσθέτουν και να διαχειρίζονται GIF, αυτοκόλλητα και προτυπώσεις meme. Περιέχει το version (1), τα hooks usePickerView και useOnEscape, καθώς και τα συστατικά PickerViewButtons (τα κουμπιά + και διαχείρισης δίπλα στο πεδίο αναζήτησης), PickerViewFrame (μια προβολή με το βέλος επιστροφής), PickerNotice, PickerField, PickerSearch, PickerDropZone και PickerManageList. Το Escape επιστρέφει από μια προβολή στο πλέγμα της καρτέλας. Βεβαιωθείτε ότι η ιδιότητα υπάρχει: άλλοι ξενιστές δεν την παρέχουν, οπότε η καρτέλα διατηρεί τότε τα δικά της διαλόγα. Οι τύποι βρίσκονται στο webapp/fusion/src/types/store/plugins.ts (EmojiPickerTabProps) και τα συστατικά στο webapp/fusion/src/fusion/popovers/picker_views.tsx.

Λειτουργίες συντάκτη

Στο Fusion, η registerComposerMode({id, label, fusionIcon, placeholder, sendLabel, component, shouldRender}) προσθέτει έναν νέο τρόπο σύνταξης μηνύματος στον συντάκτη, όπως κάνει το Polls για μια ψηφοφορία. Η λειτουργία προτείνεται στο μενού + του συντάκτη κάτω από την ετικέτα label. Μόλις επιλεγεί, ο συντάκτης διατηρεί το πεδίο κειμένου (με το placeholder της λειτουργίας: την ερώτηση της ψηφοφορίας), εμφανίζει το component σας από κάτω και μια μπάρα που ονομάζει τη λειτουργία με ένα κουμπί Ακύρωση. Το Αποστολή λαμβάνει το sendLabel και εκτελεί τη δική σας υποβολή αντί να αναρτά το κείμενο.

  • Το component λαμβάνει τα channelId, rootId (το νήμα, σε ένα πεδίο απάντησης), message (το κείμενο του συντάκτη), setSubmit και onCancel (επιστροφή σε ένα απλό μήνυμα, διατηρώντας το κείμενο).
  • Δώστε στο setSubmit μια συνάρτηση που καλείται με το κείμενο του συντάκτη και επιλύεται σε τίποτα (ή σε μια κενή συμβολοσειρά) μόλις σταλεί: στη συνέχεια, ο συντάκτης αδειάζει και επιστρέφει σε ένα απλό μήνυμα. Επιλύστε σε μια συμβολοσειρά για να εμφανιστεί ως σφάλμα στη θέση του.
  • Τα placeholder και sendLabel είναι συμβολοσειρές ή συναρτήσεις του intl του react-intl. Η shouldRender(state, {channelId, rootId}) αποφασίζει εάν ένας συντάκτης προσφέρει τη λειτουργία.
  • Για να ανοίξετε τη λειτουργία από αλλού, όπως από μια εντολή καθέτου χωρίς ορίσματα: window.dispatchEvent(new CustomEvent('am-composer-mode', {detail: {id, channelId, rootId}})).

Μόνο το Fusion έχει λειτουργίες συντάκτη: ελέγξτε ότι το registry διαθέτει τη συνάρτηση και κρατήστε το δικό σας διάλογο διαφορετικά. Η μέθοδος επιστρέφει το id της λειτουργίας και η λειτουργία αφαιρείται όταν αφαιρεθεί το app.

Το πάνελ σε νέο παράθυρο

Το Fusion παρέχει στο component του registerRightHandSidebarComponent δύο επιπλέον props, ώστε το Άνοιγμα σε νέο παράθυρο να ανοίγει το πάνελ εκεί όπου βρισκόταν και όχι στην αρχική σελίδα του app:

  • setPanelState(state): καλέστε το όταν το πάνελ αλλάζει θέση, με μια σύντομη συμβολοσειρά που ορίζει η εφαρμογή σας, όπως 'note/<id>' ή 'board/<id>/card/<id>', ή null στην αρχική του σελίδα. Εμφανίζεται στη διεύθυνση του παραθύρου, οπότε μην τοποθετείτε ποτέ απόρρητες πληροφορίες σε αυτό. Μια κατάσταση μεγαλύτερη από 512 χαρακτήρες, ή που περιέχει χαρακτήρες ελέγχου, απορρίπτεται.
  • initialPanelState: στο νέο παράθυρο, η κατάσταση που είχε το πάνελ, ή undefined. Θεωρήστε την ως μη αξιόπιστη είσοδο: ανακτήστε ξανά το στοιχείο ως ο συνδεδεμένος χρήστης και εμφανίστε την αρχική σελίδα της εφαρμογής ή μια προβολή «δεν βρέθηκε» αν δεν μπορούν να το ανοίξουν.

Η κατάσταση ξεχνάται όταν το πάνελ κλείνει ή όταν το πάνελ μιας άλλης εφαρμογής καταλαμβάνει τη θέση του. Για να ανοίξετε μόνοι σας το παράθυρο ενός πάνελ, το popoutRhsPlugin (επίσης στο window.WebappUtils.popouts) δέχεται την κατάσταση ως προαιρετικό πέμπτο όρισμα. Οι εφαρμογές Notes, Whiteboard, Todo, Kanban και Playbooks χρησιμοποιούν αυτό.

Το fusionIcon ονομάζει ένα εικονίδιο του σετ εικονιδίων του Fusion (για παράδειγμα poll, sticker, calendar ή image; δείτε webapp/fusion/src/fusion/components/icon). Το Fusion το εμφανίζει αντί για το icon, ώστε το στοιχείο να ταιριάζει με τα υπόλοιπα.

Ρυθμίσεις

Το settings_schema στο plugin.json περιγράφει τη σελίδα της εφαρμογής στο Κονσόλα συστήματος › Προσθήκες:

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

Τύποι ρυθμίσεων: bool, text, longtext, number, dropdown, radio, generated (μια τυχαία μυστική τιμή), username και custom (αποδίδεται από ένα στοιχείο που εγγράφει το μέρος της web εφαρμογής με registerAdminConsoleCustomSetting). Οι ρυθμίσεις μπορούν να ομαδοποιηθούν σε sections.

Το μέρος του server τις διαβάζει σε μια δομή (struct) cuyos campos coinciden con las claves:

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
}

Οι διαχειριστές μπορούν επίσης να τις ορίσουν στη διαμόρφωση, κάτω από PluginSettings.Plugins.<app id>, με κλειδιά σε πεζά (βλ. οδηγός διαχείρισης).

Μεταφράσεις

Το Antimatter μεταφράζεται σε 37 γλώσσες, και οι ενσωματωμένες εφαρμογές επίσης. Το μέρος της web εφαρμογής το κάνει ως εξής:

  1. Γράψτε κάθε συμβολοσειρά με React Intl, με ένα ID και το αγγλικό κείμενο: <FormattedMessage id='polls.create' defaultMessage='Create a poll'/> ή defineMessages. Προθέστε τα ID με το όνομα της εφαρμογής σας.
  2. Εκτελέστε τις αγγλικές συμβολοσειρές στο webapp/i18n/en.json (οι ενσωματωμένες εφαρμογές χρησιμοποιούν formatjs extract).
  3. Προσθέστε ένα αρχείο ανά γλώσσα δίπλα του, με τα ίδια ID: fr.json, de.json, pt-BR.json, zh-CN.json…
  4. Δώστε τα στην web εφαρμογή, η οποία ζητά τη γλώσσα του χρήστη:
    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 επιστρέφει στο αγγλικό defaultMessage. Για τα strings που στέλνει το server (απαντήσεις εντολών, μηνύματα bot), το πακέτο pluginapi/i18n φορτώνει τα αρχεία μετάφρασης που περιλαμβάνονται στο bundle (για παράδειγμα assets/i18n) και παρέχει έναν localizer στη γλώσσα κάθε χρήστη.

Συγκόλληση, εγκατάσταση και αποσφαλμάτωση

Χρειάζεστε Go και Node.js (η έκδοση στο .nvmrc της εφαρμογής). Στον φάκελο της εφαρμογής:

ΕντολήΛειτουργία
make distΣυγκολλεί το μέρος του server για κάθε πλατφόρμα και το μέρος της web εφαρμογής, και γράφει το bundle στο dist/<app id>-<version>.tar.gz.
make testΕκτελεί τις δοκιμές Go και JavaScript.
make check-styleΕλέγχει τον κώδικα (golangci-lint, ESLint) και ελέγχει τους τύπους TypeScript.
make deployΣυγκολλεί, στη συνέχεια εγκαθιστά και ενεργοποιεί το bundle σε έναν server.
make watch, make deploy-from-watchΤο πρώτο συγκεολλεί ξανά το μέρος της web εφαρμογής κάθε φορά που αποθηκεύετε. Το δεύτερο συσκευάζει ό,τι συγκεολλήθηκε και το εγκαθιστά στον server.
make enable, make disable, make resetΕνεργοποιεί, απενεργοποιεί ή επανεκκινεί την εφαρμογή στον server.
make logs, make logs-watchΕμφανίζει τις γραμμές της εφαρμογής στο αρχείο καταγραφής (log) του server.
make attachΣυνδέει τον αποσφαλματωτή Delve στο τρέχον μέρος του server (συγκεολλήστε πρώτα με AM_DEBUG=1).

Η make deploy και οι άλλες εντολές του server χρησιμοποιούν μια τοπική socket όταν βρουν μία (/var/tmp/antimatter_local.socket, ή AM_LOCALSOCKETPATH): εκτελέστε τις στον υπολογιστή του server με τοπική λειτουργία ενεργοποιημένη. Αλλιώς, συνδέονται στον server που δίνεται από το MM_SERVICESETTINGS_SITEURL με το AM_ADMIN_TOKEN, ή με το AM_ADMIN_USERNAME και το AM_ADMIN_PASSWORD. Τα εργαλεία συγκεολλησης δεν διαβάζουν ακόμα ένα όνομα AM_ για το site URL.

Εγκατάσταση bundle με το χέρι

  • Ένας διαχειριστής συστήματος επιτρέπει ανεβάσματα με AM_PLUGINSETTINGS_ENABLEUPLOADS=true (απενεργοποιημένο από προεπιλογή. Δεν υπάρχει διακόπτης στο Κονσόλα συστήματος).
  • Στη συνέχεια Κονσόλα συστήματος › Προσθήκες › Διαχείριση Plugins › Ανέβασμα Plugin, ή amctl plugin add weather.tar.gz ακολουθούμενο από amctl plugin enable com.example.weather.
  • Αν ο διακομιστής έχει ενεργοποιημένη την Απαιτείται υπογραφή plugin, αποδέχεται μόνο δέσμευες υπογεγραμμένες με κλειδί που εμπιστεύεται· δείτε Εγκατάσταση άλλων εφαρμογών.
Για γρήγορες επαναλήψεις σε έναν server ανάπτυξης, ορίστε AM_SERVICESETTINGS_ENABLEDEVELOPER=true στον server: τα σφάλματα θα φέρουν τότε τις λεπτομέρειές τους. Τα Makefiles των συσκευασμένων εφαρμογών συγκεολλούν το μέρος του server μόνο για την πλατφόρμα σας όταν το MM_SERVICESETTINGS_ENABLEDEVELOPER είναι ορισμένο στο shell σας.