כתיבת אפליקציות (תוספים)
אפליקציות מוסיפות יכולות ל-Antimatter מבפנים: חלק שרת בשפת Go שמתחבר לשרת, וחלק אפליקציית Web ב-React שמוסיף תפריטים, פאנלים, סוגי הודעות ועמודים. עמוד זה מציג כיצד אפליקציה מונחת, תוך שימוש באפליקציית סקרים (Polls) המצורפת כדוגמה, ומה משתנה בממשק Fusion של Antimatter.
כיצד אפליקציה עובדת
- חלק השרת הוא תוכנית בשפת Go. השרת מריץ אותו כתהליך נפרד ומתקשר איתו באמצעות RPC: הוא קורא להוקים של האפליקציה (הודעה עומדת להתפרסם, פקודה הורצה…), והאפליקציה קוראת לplugin API של השרת (יצירת פוסט, קריאת משתמש, אחסון נתונים…). היא יכולה גם לשרת נתיבי HTTP תחת
/plugins/<app id>/. - חלק אפליקציית ה-Web הוא חבילת JavaScript שנטענת בדפדפן יחד עם אפליקציית ה-Web. היא רשומה בregistry: רכיבי React וקריאות חזרה (callbacks) – כפתור בתפריט, פאנל, דרך לצייר סוג הודעה.
- כל חלק מהחלקים הוא אופציונלי. אפליקציה מותקנת כחבילת
.tar.gz, ומנהלי מערכת מאפשרים, מעכבים ומגדירים אותה.
Antimatter מריץ אפליקציות שכתובות עבור Mattermost, ולהפך, כל עוד האפליקציה משתמשת רק בהוקים שיש לשני הצדדים. ראו הוקים שיש רק ל-Antimatter.
מבנה החבילה
אין תבנית התחלה נפרדת: יש להתחיל מאפליקציה מצורפת קטנה כמו 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: ייחודי וקבוע; שם דומיין הפוך הוא נפוץ. הוא קובע את שם הנתיבים, ההגדרות המאוחסנות ואירועי WebSocket.version: גרסה סמנטית. האפליקציות המצורפות משמיטות את השדה הזה מ-plugin.json; כלי הבנייה שלהן לוקח אותו מהתגיתv*האחרונה ב-git.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, ומממש את ה- 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 | כשהאפליקציה מופעלת או נכבית, וכשהשרת נדלק ונכבה. יש להרשיל פקודות ובוטים ב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: לבדוק מי יכול לעשות מה לפני שפועלים בשם משתמש.
האפליקציה רצה על כל שרת בקלאסטר. יש לשמור את המצב במאגר מפתח־ערך או במסד הנתונים, ולא רק בזיכרון.
שירות ה-AI
שרת Antimatter פונה לדגמי AI עבור אפליקציות, באמצעות חיבורי הדגמים שהוגדרו על ידי מנהל (ראו AI במדריך למנהלים). אפליקציה לעולם אינה מחזיקה מפתח של ספק: היא מציינת את האדם שבו היא פועלת בשמו ואת המיקום, והשרת בודק שהאדם רשאי להשתמש בחיבור שם, מיישם את הגבלותיו ותקציביו, מונה את הטוקנים מול האדם והאפליקציה, ומבצע רישום ביקורת (audit) של השיחה ללא תוכנה.
| שיטה | מה היא עושה |
|---|---|
AIChat(actingUserID, channelID, teamID, *ai.ChatRequest) | השלמת שיחה (chat completion): הודעות, כלים, פלט מבני (JSONSchema). |
AIChatStream(…) | אותו דבר, בזרימה (streaming): קרא את Events של הזרימה עד שהערוץ נסגר, או Close אותו כדי לעצור את השיחה. |
AIEmbed(…, *ai.EmbedRequest) | Embeddings של טקסטים. |
AITranscribe(…, *ai.TranscribeRequest) | המרת דיבור לטקסט. |
AIDecide(…, *ai.DecideRequest) | שאלות טיפוסיות (typed) לדגם קבלת החלטות (כן או לא, בחירה, ציון) לגבי טקסט. |
AIConnectionsForUser(userID, channelID, teamID) | החיבורים שבהם אדם רשאי להשתמש, כולל הדגמים והיכולות שלהם, מעולם לא המפתחות. |
ConnectionID של כל בקשה בוחר את החיבור, ו-Model בוחר אחד מהדגמים שלו (ריק עבור הדגם המוגדר כברירת מחדל בחיבור). Operation הוא תווית קצרה לשימוש ולרישומי הביקורת, מעולם לא תוכן. גרסת השרת המינימלית היא 11.11.
actingUserID רק ממקור שהשרת אימת: כותרת Mattermost-User-Id של בקשה ל-ServeHTTP שלך, או המשתמש מההקשר של הוק (כותב ההודעה, המשתמש שריץ פקודה). מעולם אל תקח אותו מהודעה, מטענת webhook, מ-URL או מבקשה של אפליקציה אחרת: אפליקציה כזו תבזבז גישה של אדם אחר.שיטות אלו ו-ai package (server/public/ai) קיימות רק בעותק של Antimatter של המודול, ולא בגרסה שפורסמה על ידי Mattermost. כדי להשתמש בהן, בצע build מול server/public של Antimatter עם שורת replace ב-go.mod שלך, למשל replace github.com/mattermost/mattermost/server/public => ../antimatter/server/public. אפליקציה כזו תרוץ רק על Antimatter.
שמירת נתונים
לכל אפליקציה יש מאגר מפתח־ערך משלה במסד הנתונים של השרת:
KVSet(key, value),KVGet(key),KVDelete(key): הערכים הם בתים, בדרך כלל JSON. המפתחות הם עד 150 תווים.KVSetWithExpiry(key, value, seconds): הערך נעלם לאחר הזמן שצוין.KVCompareAndSet(key, old, new)ו־KVSetWithOptionsעםAtomic: עדכונים בטוחים כאשר מספר בקשות או שרתים כותבים את אותו מפתח. Polls שומרת כל קול כך ומנסה שוב במקרה של סתירה.KVList(page, perPage): המפתחות, עמוד אחרי עמוד.
חלק האפליקציה הוובית
הבאנדל מרשם מחלקה באמצעות 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של Polls. - קריאה לחלק השרת שלכם. יש לבצע Fetch ל־
${window.basename}/plugins/<app id>/…עם האפשרויות מ־Client4.getOptions()(מ־mattermost-redux/client): הן נושאות את הכותרות (headers) שהשרת זקוק לקבלת בקשה מהדפדפן. - המצב שלכם.
registerReducerמוסיף reducer, הנשמר תחתstate['plugins-<app id>']. - טיפוסים. שיטות הרשומה הנוספות של Antimatter אינן נכללות בטיפוסים שפורסמו עבור Mattermost: Polls מרשמה את הרשומה שהיא משתמשת בה ב־
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, ופותחים דיאלוג עצמאי עבור /poll בתוך Fusion. שיחות וערוצי קול מושמטים את רכיבי סרגל הצד וההודעות הזמניות (toasts) ש-Fusion מחליף בממשק השיחה שלו. אפליקציית הסוכנים מציבה את פעולות ה-AI שלה בתפריט ההודעה, כקבוצה מתחת לפעולות נוספות, במקום בסרגל הכלים המופיע ב-hover. כמדי ה-CSS של Fusion עצמה (המתחילות ב-am-) ושלבת הדיאלוג שלה (אלמנט am-layer) הן מה שהאפליקציות האלו משתמשות בו כדי להתאים את הממשק; הן אינן API מתועד ויציב.
המקומות שבהם Fusion מציגה רכיבי תוספים
| שיטת הרשמה | המקום שבו זה מופיע ב-Fusion |
|---|---|
registerAppBarComponent, registerChannelHeaderButtonAction | סרגל האפליקציות, עמודת האייקונים בקצה הימני, בין האווטאר שלך לכפתור אודות 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 | עמודים מלאים בכתובת המוצר, המפורטים תחת מוצרים באודות Antimatter. עמודי מוצר שומרים על מסגרת העמוד הקלאסית, שבה תפריט המוצר מופיע רק כאשר אפליקציה רשמה מוצר או הוסיפה פריט אליו. אפליקציות Kanban ו-Playbooks המצורפות אינן רשמות מוצרים anymore: העמודים שלהם נפתחים בפאנלים הצדדיים שלהם. |
registerRootComponent, registerGlobalComponent, הוקים כמו registerMessageWillBePostedHook, registerSlashCommandWillBePostedHook, registerMessageWillFormatHook, registerFilesWillUploadHook, registerDesktopNotificationHook, registerWebSocketEventHandler, registerReducer, registerTranslations | עובדים כפי שעושים בממשק הקלאסי. |
registerAdminConsolePlugin, registerAdminConsoleCustomSetting | קונסולת מערכת, שהיא הקלאסית בשני הממשקים. |
מה ש-Fusion לא מציג או לא מריץ
registerPostDropdownMenuComponent(רכיב בתפריט ההודעה),registerPostCardTypeComponent(כרטיס של פוסט בפאנל הימני) ו-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}) | Fusion בלבד: פריט בדפי אינטגרציות, ליד האמוג'י המותאמים אישית. אפליקציית GIFs מוסיפה שם את ה-GIFs והמדבקות המותאמים אישית. |
הוספה וניהול תצוגות בבורר האמוג'י
ב-Fusion, לשונית בוחר האמוג'י מקבלת תכונה בשם pickerKit: רכיבים שהשובר האמוג'י משמש לצפייה בהוספה ובניהול, כך שהתצוגות של השוברים נראות באופן אחיד ונשארות בתוך הבוחר. אפליקציות ה-GIF וה-Memes משתמשות בו כדי להוסיף ולנהל GIF, מדבקות וקבצי ממים. התכונה מכילה את 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 מעבירה לרכיב של registerRightHandSidebarComponent שתי תכונות נוספות, כך שפתיחה בחלון חדש פותחת את הפאנל במקום שבו הוא נמצא, ולא בדף הבית של האפליקציה:
setPanelState(state): יש לקרוא לפונקציה זו כאשר הפאנל משנה מקום, עם מחרוזת קצרה שהאפליקציה מגדירה, כמו'note/<id>'או'board/<id>/card/<id>', אוnullכאשר הוא בדף הבית. המחרוזת מופיעה בכתובת החלון, ולכן אין להכניס אליה סודות. מחרוזת ארוכה מ-512 תווים, או כזו המכילה תווים של בקרה, תימחק.initialPanelState: בחלון החדש, המצב שהפאנל היה בו, אוundefined. יש להתייחס למחרוזת זו כאל קלט לא אמין: יש להביא את הפריט שוב כמשתמש המחובר, ולהציג את דף הבית של האפליקציה או תצוגת "לא נמצא" אם אין אפשרות לפתוח אותו.
המצב נשכח כאשר הפאנל נסגר או שפאנל של אפליקציה אחרת תופס את מקומו. כדי לפתוח את חלון הפאנל בעצמכם, popoutRhsPlugin (גם ב-window.WebappUtils.popouts) מקבל את המצב כטיעון חמישי אופציונלי. פתקים, לוח לבן, משימות, 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>, עם מפתחות באותיות קטנות (ראו מדריך המנהלים).
תרגומים
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 מוגדר ב־shell שלך.