كتابة التطبيقات (الإضافات)
تضيف التطبيقات ميزات إلى Antimatter من الداخل: جزء خادم بلغة Go يتصل بالخادم، وجزء تطبيق ويب بلغة React يضيف قوائم ولوحات وأنواع رسائل وصفحات. تُظهر هذه الصفحة كيفية تنظيم التطبيق، باستخدام تطبيق Polls المدمج كمثال، وما الذي يتغير في واجهة Fusion الخاصة بـ Antimatter.
كيف يعمل التطبيق
- جزء الخادم هو برنامج بلغة Go. يبدأه الخادم كعملية مستقلة ويتواصل معه عبر RPC: يستدعي الخطافات الخاصة بالتطبيق (رسالة على وشك النشر، تم تنفيذ أمر…)، ويستدعي التطبيق واجهة برمجة التطبيقات الخاصة بالخادم (إنشاء منشور، قراءة مستخدم، تخزين بيانات…). كما يمكنه تقديم مسارات HTTP تحت
/plugins/<app id>/. - جزء تطبيق الويب هو حزمة JavaScript تُحمَّل في المتصفح مع تطبيق الويب. يسجّل مكوّنات React واستدعاءات مع السجل: زر في قائمة، لوحة، طريقة لرسم نوع رسالة.
- أي جزء منهما اختياري. يُثبَّت التطبيق كحزمة
.tar.gz، ويُمكّن مسؤولو النظام التطبيق ويُعطّلونه ويضبطون إعداداته.
تشغّل Antimatter التطبيقات المكتوبة لـ Mattermost، والعكس صحيح، طالما أن التطبيق يستخدم فقط الخطافات التي يمتلكها كلاهما. راجع الخطافات التي تمتلكها 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: إصدار دلالي. تُترك التطبيقات المدمجة هذا الحقل خارجplugin.json؛ حيث يأخذ أداة البناء قيمته من أحدث وسم gitv*.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؛ والواجهة البرمجية موجودة في server/public/plugin/api.go. أما حزمة pluginapi (server/public/pluginapi) فتغلّف الواجهة البرمجية بمساعدات أسهل في الاستخدام.
نداءات الواجهة البرمجية المفيدة
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(…) | نفس الشيء، ولكن بتدفق مستمر: اقرأ 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 الخاص بك، أو مستخدم سياق الخطاف (مؤلف المنشور، المستخدم الذي نفذ أمرًا). لا تأخذه أبدًا من رسالة، أو حمولة خطاف ويب، أو عنوان 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>؛ وتتطلب إصدار خادم 11.11 على الأقل؛ هذه الطرق والخطاف موجودان فقط في server/public الخاص بـ Antimatter، مثل خدمة الذكاء الاصطناعي المذكورة أعلاه. لا يمكن للتطبيقات التي يكون معرّفها antimatter أو التي تبدأ بـ mcp. تسجيل الأدوات.
تخزين البيانات
يحتوي كل تطبيق على مخزن مفاتيح وقيم خاص به في قاعدة بيانات الخادم:
KVSet(key, value)،KVGet(key)،KVDelete(key): تكون القيم بايتات، وعادةً ما تكون بصيغة JSON. لا تتجاوز المفاتيح 150 حرفًا.KVSetWithExpiry(key, value, seconds): تختفي القيمة بعد انقضاء هذه المدة.KVCompareAndSet(key, old, new)وKVSetWithOptionsمعAtomic: تحديثات آمنة عندما تكتب عدة طلبات أو خوادم إلى المفتاح نفسه. يحفظ استطلاع كل صوت بهذه الطريقة ويعيد المحاولة عند التعارض.KVList(page, perPage): المفاتيح، صفحةً بصفحة.
جزء تطبيق الويب
يسجّل الحزمة (bundle) فئةً باستخدام window.registerPlugin. تستقبل initialize السجل (registry) ومخزن 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الخاص باستطلاع. - الاتصال بجزء الخادم الخاص بك. استخدم Fetch لجلب
${window.basename}/plugins/<app id>/…مع الخيارات منClient4.getOptions()(منmattermost-redux/client): فهي تحمل الترويسات التي يحتاجها الخادم لقبول طلب من المتصفح. - الحالة الخاصة بك. يضيف
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';
}
تستخدم التطبيقات المدمجة هذا لعرض واجهات مختلفة. تعرض تطبيق الاستطلاعات بطاقات الاستطلاع بأسلوب Fusion، وفي Fusion يتم كتابة الاستطلاع في محرر الرسائل (وضع من أوضاع المحرر) بدلاً من نافذة حوارية. تتجاهل تطبيقات المكالمات وقنوات الصوت مكونات الشريط الجانبي والإشعارات المنبثقة التي تستبدلها Fusion بواجهة المكالمات الخاصة بها. يضع تطبيق محادثة الذكاء الاصطناعي إجراءات الذكاء الاصطناعي في قائمة الرسائل، كمجموعة تحت إجراءات أخرى، بدلاً من شريط الأدوات عند التمرير. تستخدم هذه التطبيقات فئات CSS الخاصة بـ Fusion (التي تبدأ بـ am-) وطبقة النوافذ الحوارية الخاصة بها (العنصر am-layer) لمطابقة الواجهة؛ وهي ليست واجهة برمجة تطبيقات موثقة ومستقرة.
أين تعرض Fusion مكوّنات الإضافات
| طريقة التسجيل | أين يظهر في Fusion |
|---|---|
registerAppBarComponent, registerChannelHeaderButtonAction | سكة التطبيقات، وهي عمود الأيقونات على الحافة اليمنى، بين صورتك الرمزية وزر حول Antimatter. تذهب أزرار ترويسة القناة هناك أيضاً، وليس في ترويسة القناة. يُخفى عندما يفعّل مدير النظام تعطيل شريط التطبيقات (ExperimentalSettings.DisableAppBar، معطّل افتراضياً). |
registerRightHandSidebarComponent | اللوحة الجانبية اليمنى. رأسها خاص بـ Fusion، وهو نفسه لجميع التطبيقات: أيقونة التطبيق واسمه (العنوان المسجل عندما يعرض نصًا فقط، وإلا يكون تسمية شريط التطبيقات أو الاسم في البيان التعريفي)، ثم استعادة حجم اللوحة (بعد تغيير الحجم)، فتح في نافذة جديدة وإغلاق. يتم تجاهل أي عنوان يعرض عناصر أخرى (عنصر تحكم، رابط، صورة)، لذا يجب عليك عرض عناصر التحكم الخاصة بتطبيقك داخل اللوحة نفسها، على سبيل المثال في شريط رفيع في أعلى اللوحة. يمكن للمستخدمين تغيير حجم اللوحة عن طريق سحب حافتها. راجع اللوحة في نافذة جديدة. |
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 أي قائمة، ولا تعرض قائمة بالمنتجات في أي مكان، لذا قم بربط الصفحة بنفسك. لا يسجل أي تطبيق مدمج كمنتج: لوحات كانبان، وأدلة التشغيل، ودردشة الذكاء الاصطناعي تفتح في لوحاتها الجانبية. |
registerRootComponent، registerGlobalComponent، خطافات مثل registerMessageWillBePostedHook، registerSlashCommandWillBePostedHook، registerMessageWillFormatHook، registerFilesWillUploadHook، registerDesktopNotificationHook، registerWebSocketEventHandler، registerReducer، registerTranslations | تعمل كما في الواجهة الكلاسيكية. |
registerAdminConsolePlugin, registerAdminConsoleCustomSetting | وحدة التحكم بالنظام، وهي الكلاسيكية في كلا الواجهتين. |
ما لا يعرضه Fusion أو يشغّله
- لا يتم عرض
registerPostDropdownMenuComponent(مكوّن في قائمة الرسالة) وregisterPostCardTypeComponent(بطاقة المنشور في اللوحة اليمنى) في العروض الرئيسية لـ Fusion. لا يفعلregisterProductSwitcherMenuItemشيئًا في 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}) | خاص بفوزيون فقط: عنصر في صفحات التكاملات، بجوار الرموز التعبيرية المخصصة. تضيف تطبيق GIF رموزه التعبيرية وملصقاته المخصصة هناك. |
إضافة وإدارة العروض في منتقي الرموز التعبيرية
في Fusion، تحصل علامة تبويب منتقي الرموز التعبيرية على الخاصية pickerKit: وهي المكوّنات التي يستخدمها تبويب الرموز التعبيرية لعرضي الإضافة والإدارة الخاصين به، بحيث تبدو عروض التبويب متطابقة وتبقى داخل المنتقي. تستخدم تطبيقي GIFs وMemes هذه الخاصية لإدارة وإضافة ملفات GIF والملصقات وقوالب الصور الساخرة (Memes). تحتوي على version (1)، والخطافات 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 ويشغّل دالة الإرسال الخاصة بك بدلاً من نشر النص.
- يتلقى المكوّن
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 فقط أوضاع المحرر: تحقق من وجود الدالة في السجل، وإلا احتفظ بنافذتك الحوارية الخاصة بك. تُرجع الطريقة معرّف الوضع، ويُزال الوضع عند إزالة التطبيق.
اللوحة في نافذة جديدة
يمنح Fusion المكوّن registerRightHandSidebarComponent خاصيتين إضافيتين، بحيث يفتح فتح في نافذة جديدة اللوحة في مكانها الحالي بدلاً من الصفحة الرئيسية للتطبيق:
setPanelState(state): استدعِها عند تغيير موضع اللوحة، مع نص قصير يعرّفه تطبيقك، مثل'note/<id>'أو'board/<id>/card/<id>'، أوnullعند الصفحة الرئيسية. يُدرج هذا النص في عنوان النافذة، لذا لا تضع فيه أبداً بيانات سرية. تُهمل الحالات التي يتجاوز طولها 512 حرفاً أو تحتوي على أحرف تحكم.initialPanelState: في النافذة الجديدة، يمثل حالة اللوحة السابقة، أوundefined. تعامل معه كمدخل غير موثوق به: أعد جلب العنصر بصفتك المستخدم المسجّل دخوله، واعرض الصفحة الرئيسية للتطبيق أو عرض «لم يتم العثور عليه» إذا لم يتمكن من فتحه.
تُنسى الحالة عند إغلاق اللوحة أو عند استبدالها بلوحة تطبيق آخر. لفتح نافذة لوحة بنفسك، تقبل popoutRhsPlugin (الموجودة أيضاً في window.WebappUtils.popouts) الحالة كوسيط اختياري خامس. تستخدم هذا الميزات التالية: الملاحظات، والسبورة البيضاء، وقائمة المهام، ولوحات كانبان، وأدلة التشغيل.
يُسمّي 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>، بمفاتيح بحروف صغيرة (راجع دليل المديرين).
الترجمات
يُترجم Antimatter إلى 37 لغة، وكذلك التطبيقات المضمّنة. يقوم جزء تطبيق الويب بذلك على النحو التالي:
- اكتب كل نص باستخدام React Intl، مع معرّف ونصه الإنجليزي:
<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) ويتحقق من أنواع 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): شغّلها على جهاز الخادم مع تفعيل الوضع المحلي. وإلا، تسجّل الدخول إلى الخادم المحدد بـ MM_SERVICESETTINGS_SITEURL باستخدام AM_ADMIN_TOKEN، أو AM_ADMIN_USERNAME وAM_ADMIN_PASSWORD. أدوات البناء لا تقرأ بعد اسم 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 مضبوطًا في شلّك.