نوشتن برنامه‌ها (افزونه‌ها)

برنامه‌ها از درون به Antimatter قابلیت می‌افزایند: یک بخش سرور به زبان Go که به سرور متصل می‌شود و یک بخش وب‌اپ به زبان React که منوها، پنل‌ها، انواع پیام و صفحات را اضافه می‌کند. این صفحه چیدمان یک برنامه را با استفاده از برنامه Polls که همراه با Antimatter ارائه می‌شود به‌عنوان مثال نشان می‌دهد و توضیح می‌دهد چه تغییراتی در رابط Fusion آنتی‌ماتر رخ می‌دهد.

نحوه عملکرد یک برنامه

  • بخش سرور یک برنامه Go است. سرور آن را به‌عنوان یک فرآیند جداگانه اجرا می‌کند و از طریق RPC با آن صحبت می‌کند: سرور هوک‌های برنامه را فراخوانی می‌کند (مثلاً یک پیام در آستانه ارسال است، یک دستور اجرا شده است…) و برنامه API افزونه سرور را فراخوانی می‌کند (ایجاد یک پست، خواندن یک کاربر، ذخیره داده…). همچنین می‌تواند مسیرهای HTTP را زیر /plugins/<app id>/ ارائه دهد.
  • بخش وب‌اپ یک باندل جاوااسکریپت است که همراه با وب‌اپ در مرورگر بارگذاری می‌شود. این بخش اجزای React و کال‌بک‌ها را در registry ثبت می‌کند: یک دکمه در منو، یک پنل، یا روشی برای رسم یک نوع پیام.
  • هر یک از این بخش‌ها اختیاری است. یک برنامه به‌صورت یک بسته .tar.gz نصب می‌شود و مدیران سیستم آن را فعال، غیرفعال و پیکربندی می‌کنند.
بخش سرور با دسترسی کامل به داده‌های سرور اجرا می‌شود. یک برنامه را مانند کدی که روی سرور استقرار می‌دهید در نظر بگیرید.

Antimatter برنامه‌هایی که برای Mattermost نوشته شده‌اند را اجرا می‌کند و برعکس، به شرطی که برنامه فقط از هوک‌هایی استفاده کند که هر دو دارند. به هوک‌هایی که فقط Antimatter دارد مراجعه کنید.

چیدمان بسته

هیچ قالب شروع جداگانه‌ای وجود ندارد: از یک برنامه کوچک همراه با Antimatter مانند antimatter-plugin-polls یا antimatter-plugin-todo شروع کنید و شناسه و کد آن را تغییر دهید. 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: منحصربه‌فرد و دائمی؛ معمولاً یک نام دامنه معکوس است. این شناسه نام مسیرها، تنظیمات ذخیره‌شده و رویدادهای WebSocket را تعیین می‌کند.
  • version: نسخه معنایی. برنامه‌های همراه با Antimatter آن را از plugin.json حذف می‌کنند؛ ابزار ساخت آن را از آخرین تگ git با پیشوند v* می‌گیرد.
  • min_server_version: Antimatter نسخه 12.0.0 را گزارش می‌دهد.
  • settings_schema: صفحه تنظیمات برنامه در کنسول سیستم.

make apply مانیفست را در server/manifest.go و webapp/src/manifest.ts کپی می‌کند؛ make manifest-check آن را اعتبارسنجی می‌کند.

بخش سرور

بخش سرور همان ماژول Go را که برنامه‌های Mattermost از آن استفاده می‌کنند، یعنی github.com/mattermost/mattermost/server/public، از پروکسی ماژول‌های Go وارد می‌کند. main آن فقط برنامه را راه‌اندازی می‌کند:

package main

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

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

نوع Plugin نوع plugin.MattermostPlugin را در خود جای می‌دهد که به آن p.API را می‌دهد، و هوک‌های مورد نیازش را پیاده‌سازی می‌کند:

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

هوک‌های رایج

هوکزمان اجرا
OnActivate, OnDeactivateهنگامی که برنامه فعال یا غیرفعال می‌شود، و هنگام راه‌اندازی و توقف سرور. دستورات و ربات‌ها را در OnActivate ثبت کنید.
OnConfigurationChangeهنگام تغییر تنظیمات؛ آن‌ها را با p.API.LoadPluginConfiguration(&cfg) بارگذاری کنید.
ServeHTTPدرخواست‌ها به /plugins/<app id>/…. سرور هنگامی که درخواست از یک کاربر واردشده می‌آید، هدر Mattermost-User-Id را تنظیم می‌کند؛ درخواست‌هایی که فاقد آن هستند را رد کنید، مگر اینکه برای دسترسی عمومی طراحی شده باشند.
ExecuteCommandهنگام اجرای دستوری که برنامه ثبت کرده است.
MessageWillBePosted, MessageWillBeUpdatedقبل از ذخیره پیام: یک پست تغییر‌یافته را برگردانید، یا دلیلی برای رد آن ارائه دهید.
MessageHasBeenPosted, MessageHasBeenUpdated, MessageHasBeenDeletedبعد از عمل، برای اثرات جانبی.
ReactionHasBeenAdded, ReactionHasBeenRemovedواکنش‌ها.
UserHasJoinedChannel, UserHasLeftChannel, UserHasJoinedTeam, UserHasLeftTeam, ChannelHasBeenCreatedتغییرات عضویت و کانال. یک تیم یک جامعه است.
FileWillBeUploadedقبل از ذخیره فایل: آن را تغییر دهید یا رد کنید.
OnPluginClusterEventپیامی از همان برنامه روی سرور دیگری از یک خوشه.

لیست کامل، همراه با امضاهای دقیق، در server/public/plugin/hooks.go است؛ API در server/public/plugin/api.go قرار دارد. بسته pluginapi (server/public/pluginapi) API را با کمکی‌های دوستانه‌تر پوشش می‌دهد.

فراخوان‌های مفید API

  • CreatePost، UpdatePost، SendEphemeralPost: ارسال پست به‌عنوان ربات برنامه یا به‌عنوان یک کاربر.
  • EnsureBotUser(&model.Bot{Username, DisplayName}): یک‌بار ربات برنامه را ایجاد کنید و شناسه آن را دریافت کنید.
  • PublishWebSocketEvent("poll_updated", payload, &model.WebsocketBroadcast{ChannelId: id}): ارسال یک رویداد به برنامه وب. آن به‌صورت custom_<app id>_poll_updated دریافت می‌شود.
  • OpenInteractiveDialog: باز کردن یک گفتگو.
  • LogInfo، LogError و هم‌خانواده‌های آن‌ها: نوشتن در لاگ سرور، با برچسب‌گذاری به‌صورت شناسه برنامه.
  • GetUser، GetChannel، HasPermissionToChannel: بررسی اینکه چه کسی اجازه انجام چه کاری را دارد، قبل از عمل به‌جای یک کاربر.

هر اپلیکیشن روی هر سروری از خوشه اجرا می‌شود. وضعیت را در مخزن کلید-مقدار یا پایگاه داده ذخیره کنید، نه فقط در حافظه.

سرویس هوش مصنوعی

سرور Antimatter برای اپلیکیشن‌ها با مدل‌های هوش مصنوعی ارتباط برقرار می‌کند، از طریق اتصالاتی که یک مدیر پیکربندی کرده است (به بخش هوش مصنوعی در راهنمای مدیر مراجعه کنید). یک اپلیکیشن هرگز کلید ارائه‌دهنده را در اختیار ندارد: نام شخص و مکانی که به نمایندگی از او عمل می‌کند را مشخص می‌کند و سرور بررسی می‌کند که آیا این شخص مجاز به استفاده از اتصال در آن مکان است یا خیر، محدودیت‌ها و بودجه‌های آن را اعمال می‌کند، توکن‌ها را نسبت به شخص و اپلیکیشن محاسبه می‌کند و فراخوان را بدون محتوای آن ممیزی می‌کند.

روشعملکرد
AIChat(actingUserID, channelID, teamID, *ai.ChatRequest)تکمیل گفتگو: پیام‌ها، ابزارها، خروجی ساختاریافته (JSONSchema).
AIChatStream(…)همانند قبل، اما به صورت پخش زنده (streamed): رویدادهای Events جریان را بخوانید تا زمانی که کانال بسته شود، یا آن را با Close ببندید تا فراخوان متوقف شود.
AIEmbed(…, *ai.EmbedRequest)جایگذاری (Embeddings) متن‌ها.
AITranscribe(…, *ai.TranscribeRequest)تبدیل گفتار به متن.
AIDecide(…, *ai.DecideRequest)پرسش‌های تایپ‌شده به یک مدل تصمیم‌گیر (بله یا خیر، یک انتخاب، یک امتیاز) درباره یک متن.
AIConnectionsForUser(userID, channelID, teamID)اتصالاتی که یک شخص می‌تواند در آنجا از آن‌ها استفاده کند، همراه با مدل‌ها و قابلیت‌هایشان، هرگز کلیدهایشان.

ConnectionID هر درخواست اتصال را انتخاب می‌کند و Model یکی از مدل‌های آن را (خالی برای مدل پیش‌فرض اتصال). Operation یک برچسب کوتاه برای رکوردهای استفاده و ممیزی است، هرگز محتوا. حداقل نسخه سرور برای این موارد 11.11 است.

actingUserID را فقط از منبعی دریافت کنید که سرور آن را تأیید کرده است: هدر Mattermost-User-Id یک درخواست به ServeHTTP شما، یا کاربر در زمینه (context) یک هوک (نویسنده یک پست، یا کاربری که یک دستور را اجرا کرده است). هرگز آن را از یک پیام، بار (payload) یک وب‌هوک، URL یا درخواست اپلیکیشن دیگری دریافت نکنید: در غیر این صورت اپلیکیشن شما به دسترسی شخص دیگری دست می‌زند.

این روش‌ها و بسته ai (server/public/ai) فقط در نسخه Antimatter از ماژول وجود دارند، نه در نسخه‌ای که توسط Mattermost منتشر شده است. برای استفاده از آن‌ها، با 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) که تعیین می‌کند آیا سیاست یک عامل اجازهٔ اجرای مستقل آن را دارد یا خیر (بخش ابزارها در راهنمای مدیر را ببینید). مجوزها بر اساس آرگومان کانال یا جامعه در فراخوانی، یا بر اساس سرور بررسی می‌شوند، بسته به نیاز ابزار. وقتی یک عامل از ابزار استفاده می‌کند، سرور هوک ExecuteAITool(c, *model.AIToolExecution) *model.AIToolResult را با فرد تأیید شده‌ای که درخواست را انجام داده در UserId فراخوانی می‌کند: فقط با مجوزهای آن فرد عمل کنید، هرگز با مجوزهای عامل. ابزارها با نام <app id>.<name> نام‌گذاری می‌شوند؛ حداقل نسخهٔ سرور ۱۱.۱۱ است؛ این متدها و هوک فقط در server/public آنتی‌متر وجود دارند، مانند سرویس هوش مصنوعی در بالا. برنامه‌ای که شناسهٔ آن antimatter است یا با mcp. شروع می‌شود، نمی‌تواند ابزار ثبت کند.

ذخیره‌سازی داده‌ها

هر اپلیکیشن مخزن کلید-مقدار اختصاصی خود را در پایگاه داده سرور دارد:

  • KVSet(key, value), KVGet(key), KVDelete(key): مقادیر بایت هستند، معمولاً به صورت JSON. کلیدها حداکثر ۱۵۰ کاراکتر طول دارند.
  • KVSetWithExpiry(key, value, seconds): مقدار پس از گذشت این مدت حذف می‌شود.
  • KVCompareAndSet(key, old, new) و KVSetWithOptions با Atomic: به‌روزرسانی ایمن زمانی که چند درخواست یا سرور کلید یکسانی را می‌نویسند. نظرسنجی هر رأی را به این صورت ذخیره می‌کند و در صورت تعارض تلاش مجدد می‌کند.
  • KVList(page, perPage): کلیدها، صفحه به صفحه.

بخش وب اپلیکیشن

باندل یک کلاس را با window.registerPlugin ثبت می‌کند. متد initialize آن، رجیستری و مخزن 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());
  • کتابخانه‌های مشترک. وب اپلیکیشن React، ReactDOM، Redux، React Redux و React Intl را فراهم می‌کند: به جای باندل کردن نسخه‌های خودتان، آن‌ها را به عنوان externals در webpack فهرست کنید (React، ReactDOM، Redux، ReactRedux، ReactIntl). به webpack.config.js نظرسنجی مراجعه کنید.
  • فراخوانی بخش سرور. با استفاده از گزینه‌های Client4.getOptions() (از mattermost-redux/client)، آدرس ${window.basename}/plugins/<app id>/… را دریافت کنید: این گزینه‌ها هدرهایی را حمل می‌کنند که سرور برای پذیرش درخواست از مرورگر به آن‌ها نیاز دارد.
  • وضعیت اختصاصی شما. 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 دو رابط وب دارد: Fusion که پیش‌فرض است، و رابط کلاسیک. هر دو باندل‌های یکسان اپلیکیشن را بارگذاری می‌کنند و رجیستری یکسانی دارند، اما Fusion بسیاری از موارد را خودش رسم می‌کند و اجزای افزونه‌ها را به شیوه‌ای خاص جایگذاری می‌کند. اپلیکیشن خود را در هر دو حالت آزمایش کنید.

تشخیص رابط

قبل از بارگذاری هر اپلیکیشن، هر دو رابط window.antimatterWebUI را به 'fusion' یا 'classic' تنظیم می‌کنند، و همان مقدار را روی عنصر ریشه صفحه به صورت data-am-web-ui قرار می‌دهند. در Mattermost، هیچ‌کدام از این‌ها تنظیم نمی‌شود.

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

برنامه‌های همراه از این قابلیت برای نمایش متفاوت استفاده می‌کنند. نظرسنجی‌ها کارت‌های نظرسنجی خود را به سبک فیوژن ترسیم می‌کنند و در فیوژن، نظرسنجی در نوار نوشتن (یک حالت نوار نوشتن) نوشته می‌شود، نه در یک پنجرهٔ گفتگو. مکالمه‌ها و کانال‌های صوتی اجزای نوار کناری و اعلان‌های موقت (toast) را حذف می‌کنند که فیوژن آن‌ها را با رابط کاربری مکالمهٔ خود جایگزین می‌کند. برنامهٔ گفتگوی هوش مصنوعی، اقدامات هوش مصنوعی خود را در منوی پیام، به صورت یک گروه زیر اقدامات بیشتر قرار می‌دهد، نه در نوار ابزار شناور. کلاس‌های CSS اختصاصی فیوژن (با پیشوند am-) و لایهٔ پنجره‌های گفتگوی آن (عنصر am-layer) همان چیزی هستند که این برنامه‌ها برای هم‌خوانی با رابط کاربری استفاده می‌کنند؛ این‌ها یک API مستند و پایدار نیستند.

جایی که Fusion اجزای پلاگین را نمایش می‌دهد

روش ثبت (Registry)محل نمایش در Fusion
registerAppBarComponent, registerChannelHeaderButtonActionنوار برنامه‌ها (App rail)، ستون آیکون‌ها در لبه راست، بین آواتار شما و دکمه درباره Antimatter. دکمه‌های هدر کانال نیز در این بخش قرار می‌گیرند، نه در هدر کانال. این نوار پنهان می‌شود اگر مدیر سیستم گزینه غیرفعال کردن نوار برنامه‌ها (ExperimentalSettings.DisableAppBar، به‌طور پیش‌فرض خاموش) را فعال کند.
registerRightHandSidebarComponentپنل سمت راست. سربرگ آن متعلق به فیوژن است و برای همهٔ برنامه‌ها یکسان است: نماد و نام برنامه (عنوان ثبت شده وقتی فقط متن ترسیم می‌شود، در غیر این صورت برچسب ریل برنامه یا نام در مانیفست)، سپس بازگرداندن اندازهٔ پنل (پس از تغییر اندازه)، باز کردن در پنجرهٔ جدید و نزدیک. عنوانی که هر چیز دیگری (یک کنترل، یک پیوند، یک تصویر) ترسیم کند، نادیده گرفته می‌شود، بنابراین کنترل‌های اختصاصی برنامهٔ خود را در خودِ پنل ترسیم کنید، برای مثال در یک ردیف باریک در بالای آن. کاربران می‌توانند با کشیدن لبهٔ پنل، اندازهٔ آن را تغییر دهند. بخش پنل در پنجرهٔ جدید را ببینید.
registerChannelHeaderIconآیکون‌ها در هدر کانال، پس از دکمه‌های مکالمه؛ در تلفن‌های همراه، در منوی بیشتر هدر.
registerCallButtonActionدکمه مکالمه در هدر کانال؛ چندین ارائه‌دهنده از منوی روش‌های دیگر برای مکالمه استفاده می‌کنند.
registerPostDropdownMenuActionمنوی عملیات پیام (⋯) پیام، در زیرمنوی اقدامات بیشتر آن؛ همچنین در منوی عملیات رشته پنل رشته، زیر اقدامات بیشتر. Fusion آیکون اپ را قبل از هر عمل ترسیم می‌کند، به رنگ خاکستری: آیکون اختصاصی خود برای اپ‌های همراه، در غیر این صورت آیکونی که اپ در نوار اپ‌ها نمایش می‌دهد، و در غیر این صورت یک آیکون خنثی. عنصری بدون متن در ابتدای متن عمل (یک آیکون که خود اپ ترسیم می‌کند) پنهان می‌شود.
registerPostDropdownSubMenuActionیک گروه از ورودی‌ها در همان زیرمنوی اقدامات بیشتر، پس از اقدامات تکی، که با یک خط از هم جدا شده‌اند و عنوانی ندارند. هیچ برنامه‌ای ورودی اختصاصی خود را در منوی پیام دریافت نمی‌کند.
registerPostActionComponentنوار ابزار هاور پیام، قبل از دکمه ⋯.
registerPostTypeComponent, registerPostMessageAttachmentComponent, registerPostHeaderComponent, registerPostWillRenderEmbedComponentخود پیام: بدنه آن، زیر متن، پس از زمان و به‌جای پیش‌نمایش پیوندها.
registerChannelHeaderMenuActionمنوی گزینه های کانال کانال (⋯ روی یک کانال در نوار کناری، یا کلیک راست)، به‌صورت آیتم‌هایی با آیکون پلاگ.
registerMainMenuActionمنوی جامعه (نام جامعه در بالای فهرست کانال‌ها)، زیر اقدامات بیشتر، گروه‌بندی‌شده بر اساس برنامه.
registerSidebarBrowseOrAddChannelMenuActionمنوی جامعه، پس از ایجاد کانال و ایجاد دسته.
registerFileUploadMethodمنوی + ویرایشگر (افزودن به پیام)، پس از بارگذاری فایل.
registerPostEditorActionComponentنوار قالب‌بندی ویرایشگر، زمانی که قالب‌بندی نمایش داده شود.
registerAIActionMenuItemComponentمنوی AI ویرایشگر.
registerFileDropdownMenuAction, registerFilePreviewComponentمنوی گزینه‌های فایل و پیش‌نمایش فایل.
registerUserSettingsمشخصات و تنظیمات، یک تب برای هر برنامه زیر عنوان پلاگین ها.
registerChannelSettingsTabدیالوگ تنظیمات کانال، زیر عنوان برنامه‌ها.
registerPopoverUserAttributesComponent, registerPopoverUserActionsComponentکارت مشخصات که هنگام کلیک روی نام یا آواتار نمایش داده می‌شود.
registerLeftSidebarHeaderComponent, registerSidebarChannelLinkLabelComponent, registerChannelIconOverride, registerBottomTeamSidebarComponentبالای فهرست کانال‌های یک جامعه، کنار نام کانال، آیکون کانال و پایین نوار کناری جامعه در سمت چپ.
registerChannelIntro, registerChannelIntroButtonAction, registerChannelToastComponent, registerNewMessagesSeparatorActionComponent, registerChannelComposerBannerComponent, registerComposerPlaceholderآغاز تاریخچه کانال، بالای فهرست پیام‌ها، خط خوانده‌نشده، بالای کادر نوشتن و جای‌گیر کادر نوشتن.
registerChannelTypeOptionانواع کانال در پنجره‌ی ایجاد کانال.
registerSearchComponentsجعبه‌ی جستجو در سربرگ کانال: دامنه‌های اپلیکیشن زمانی ظاهر می‌شوند که خالی باشد.
registerUserGuideDropdownMenuActionدرباره Antimatter (پایین نوار اپلیکیشن)، زیر کمک.
registerProductصفحات کامل در آدرس URL محصول، در قاب کلاسیک صفحه و بدون منوی محصول: Fusion منویی ندارد و هیچ‌جا لیستی از محصولات ارائه نمی‌دهد، بنابراین خودتان باید لینک را به صفحه اضافه کنید. هیچ اپلیکیشن داخلی‌ای محصولی را ثبت نمی‌کند: Kanban، Playbooks و چت هوش مصنوعی در پنل‌های کناری خود باز می‌شوند.
registerRootComponent, registerGlobalComponent, هوک‌هایی مانند registerMessageWillBePostedHook, registerSlashCommandWillBePostedHook, registerMessageWillFormatHook, registerFilesWillUploadHook, registerDesktopNotificationHook, registerWebSocketEventHandler, registerReducer, registerTranslationsهمان‌طور که در رابط کلاسیک کار می‌کنند.
registerAdminConsolePlugin, registerAdminConsoleCustomSettingکنسول سیستم، که در هر دو رابط همان کلاسیک است.

آنچه Fusion نمایش نمی‌دهد یا اجرا نمی‌کند

  • عناصر registerPostDropdownMenuComponent (یک کامپوننت در منوی پیام) و registerPostCardTypeComponent (کارت یک پیام در پنل سمت راست) در نماهای اصلی Fusion نمایش داده نمی‌شوند. registerProductSwitcherMenuItem در Fusion هیچ کاری انجام نمی‌دهد، زیرا Fusion منوی محصولی ندارد (هشدار لاگ می‌کند). به جای مورد اول، از registerPostDropdownMenuAction استفاده کنید.
  • registerMessageWillBeUpdatedHook هنگام ویرایش پیام در Fusion اجرا نمی‌شود. به‌جای آن، ویرایش‌ها را روی سرور با هوک MessageWillBeUpdated بررسی کنید.
  • registerActionAfterChannelCreation فقط از پنجره‌ی ایجاد کانال کلاسیک اجرا می‌شود، که Fusion آن را از گزینه‌های بیشتر… در پنجره‌ی ایجاد کانال خود باز می‌کند.
  • جزء‌های نوار اپلیکیشن صرف‌نظر از supportedProductIds نمایش داده می‌شوند.
  • مسیرهای registerNeedsTeamRoute و registerCustomRoute قاب کلاسیک صفحه را حفظ می‌کنند، مانند محصولات.

هوک‌هایی که فقط 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})زبانی از انتخابگر ایموجی جعبه پیام، مانند GIF یا استیکر. در Fusion، زبانی که order کمتر از 10 دارد، قبل از زبانی ایموجی قرار می‌گیرد. این کامپوننت 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: یک ورودی در صفحات ادغام ها، در کنار ایموجی سفارشی. اپلیکیشن GIF استیکرهای سفارشی خود را در آنجا اضافه می‌کند.

افزودن و مدیریت نماها در انتخابگر ایموجی

در Fusion، یک تب در انتخابگر ایموجی یک خاصیت به نام pickerKit دریافت می‌کند: این شامل اجزایی است که تب ایموجی برای نمای افزودن و مدیریت خود استفاده می‌کند تا نمای تب‌ها یکسان باشد و درون انتخابگر باقی بماند. اپلیکیشن‌های GIFs و Memes از آن برای افزودن و مدیریت GIFها، استیکرها و قالب‌های میم استفاده می‌کنند. این خاصیت شامل version (1)، هوک‌های usePickerView و useOnEscape، و اجزای PickerViewButtons (دکمه‌های + و مدیریت در کنار جعبه جستجو)، PickerViewFrame (نمایی با فلش بازگشت)، PickerNotice، PickerField، PickerSearch، PickerDropZone و PickerManageList است. کلید Escape از یک نمای به شبکه (grid) تب برمی‌گردد. بررسی کنید که این خاصیت وجود دارد؛ میزبان‌های دیگر آن را حذف می‌کنند و در آن صورت، تب دیالوگ‌های خود را نگه می‌دارد. تایپ‌ها در 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 را می‌گیرد و به جای ارسال متن، تابع ارسال شما را اجرا می‌کند.

  • کامپوننت مقادیر channelId، rootId (رشته، در جعبه پاسخ)، message (متن ویرایشگر)، setSubmit و onCancel (بازگشت به پیام ساده، با حفظ متن) را دریافت می‌کند.
  • به setSubmit تابعی بدهید که با متن ویرایشگر فراخوانی می‌شود و پس از ارسال به هیچ مقدار (یا رشته خالی) resolve می‌شود: سپس ویرایشگر خالی شده و به حالت پیام ساده بازمی‌گردد. اگر به یک رشته resolve کنید، آن را به عنوان خطا در همان مکان نمایش می‌دهد.
  • placeholder و sendLabel رشته هستند یا توابعی از intl کتابخانه react-intl. shouldRender(state, {channelId, rootId}) تعیین می‌کند که آیا یک ویرایشگر این حالت را ارائه دهد یا خیر.
  • برای باز کردن حالت از مکان‌های دیگر، مانند یک دستور اسلش بدون آرگومان: window.dispatchEvent(new CustomEvent('am-composer-mode', {detail: {id, channelId, rootId}})).

فقط Fusion حالت‌های ویرایشگر را دارد: بررسی کنید که رجیستری این تابع را دارد و در غیر این صورت دیالوگ خودتان را نگه دارید. این متد شناسه (id) حالت را برمی‌گرداند و حالت زمانی از بین می‌رود که اپلیکیشن حذف شود.

پنل در یک پنجره جدید

Fusion به کامپوننت registerRightHandSidebarComponent دو پراپ دیگر می‌دهد تا باز کردن در پنجرهٔ جدید پنل را در همان مکانی که بود باز کند، نه در صفحه اصلی اپلیکیشن:

  • 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 (که توسط کامپوننتی که بخش وب اپلیکیشن با registerAdminConsoleCustomSetting ثبت می‌کند، ترسیم می‌شود). تنظیمات می‌توانند در sections گروه‌بندی شوند.

بخش سرور آن‌ها را در یک ساختار (struct) می‌خواند که فیلدهای آن با کلیدها مطابقت دارند:

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> با کلیدهای کوچک (lower case) تنظیم کنند (به راهنمای مدیران مراجعه کنید).

ترجمه‌ها

Antimatter به 37 زبان ترجمه شده است و اپلیکیشن‌های همراه نیز همین‌طور. بخش وب اپلیکیشن این کار را به این صورت انجام می‌دهد:

  1. هر رشته را با React Intl بنویسید، همراه با یک شناسه (ID) و متن انگلیسی آن: <FormattedMessage id='polls.create' defaultMessage='Create a poll'/> یا defineMessages. شناسه‌ها را با نام اپلیکیشن خود پیشوند بزنید.
  2. رشته‌های انگلیسی را در webapp/i18n/en.json استخراج کنید (اپلیکیشن‌های همراه از formatjs extract استفاده می‌کنند).
  3. یک فایل برای هر زبان در کنار آن اضافه کنید، با همان شناسه‌ها: fr.json، de.json، pt-BR.json، zh-CN.json…
  4. این رشته‌ها را به برنامه وب بدهید که زبان کاربر را می‌پرسد:
    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);

اگر رشته‌ای یافت نشود، به defaultMessage انگلیسی برمی‌گردد. برای رشته‌هایی که بخش سرور ارسال می‌کند (پاسخ دستورات، پیام‌های ربات)، بسته pluginapi/i18n فایل‌های ترجمه‌ای که در باندل قرار دارند (مثلاً assets/i18n) را بارگذاری کرده و یک ابزار ترجمه به زبان هر کاربر ارائه می‌دهد.

ساخت، نصب و عیب‌یابی

به Go و Node.js (نسخه‌ای که در .nvmrc برنامه آمده) نیاز دارید. در پوشه برنامه:

دستورعملکرد
make distبخش سرور را برای همه پلتفرم‌ها و بخش برنامه وب را می‌سازد و باندل را در dist/<app id>-<version>.tar.gz می‌نویسد.
make testتست‌های Go و JavaScript را اجرا می‌کند.
make check-styleکد را با golangci-lint و ESLint بررسی (lint) می‌کند و تایپ‌های TypeScript را کنترل می‌کند.
make deployمی‌سازد، سپس باندل را روی سرور نصب و فعال می‌کند.
make watch, make deploy-from-watchاولی هر بار که ذخیره می‌کنید، بخش برنامه وب را از نو می‌سازد؛ دومی آنچه ساخته را باندل کرده و روی سرور نصب می‌کند.
make enable, make disable, make resetبرنامه را روی سرور فعال، غیرفعال یا از نو راه‌اندازی می‌کند.
make logs, make logs-watchسطرهای مربوط به برنامه را از لاگ سرور نمایش می‌دهد.
make attachدیباگر Delve را به بخش سرور در حال اجرا متصل می‌کند (قبل از آن با AM_DEBUG=1 بسازید).

make deploy و سایر دستورات سرور، اگر یک سوکت حالت محلی پیدا کنند (/var/tmp/antimatter_local.socket یا AM_LOCALSOCKETPATH) از آن استفاده می‌کنند: در این صورت باید روی دستگاه سرور و با حالت محلی روشن اجرا شوند. در غیر این صورت با AM_ADMIN_TOKEN یا AM_ADMIN_USERNAME و AM_ADMIN_PASSWORD به سروری که در MM_SERVICESETTINGS_SITEURL آمده وارد می‌شوند. ابزارهای ساخت هنوز نام AM_ را برای آدرس سایت نمی‌خوانند.

نصب دستی یک باندل

  • یک مدیر سیستم با AM_PLUGINSETTINGS_ENABLEUPLOADS=true بارگذاری را مجاز می‌کند (به‌طور پیش‌فرض خاموش است و کلیدی در کنسول سیستم ندارد).
  • سپس کنسول سیستم › پلاگین ها › مدیریت پلاگین › افزونه آپلود، یا amctl plugin add weather.tar.gz و سپس amctl plugin enable com.example.weather.
  • اگر روی سرور نیاز به امضای افزونه روشن باشد، فقط باندل‌هایی را می‌پذیرد که با کلیدی امضا شده باشند که به آن اعتماد دارد؛ ببینید نصب سایر برنامه‌ها.
برای تکرار سریع روی سرور توسعه، روی سرور AM_SERVICESETTINGS_ENABLEDEVELOPER=true را تنظیم کنید: در این صورت خطاها جزئیاتشان را همراه دارند. Makefile برنامه‌های باندل‌شده فقط وقتی که MM_SERVICESETTINGS_ENABLEDEVELOPER در shell شما تنظیم شده باشد، بخش سرور را فقط برای پلتفرم شما می‌سازند.