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