การเขียนแอป (ปลั๊กอิน)
แอปเพิ่มฟีเจอร์ให้กับ Antimatter จากภายใน: ส่วนเซิร์ฟเวอร์ใน Go ที่เชื่อมต่อเข้ากับเซิร์ฟเวอร์ และส่วน web app ใน React ที่เพิ่มเมนู, แผง, ประเภทข้อความ และหน้าต่างๆ หน้านี้แสดงว่าแอปมีโครงสร้างอย่างไร โดยใช้แอป Polls ที่มากับระบบเป็นตัวอย่าง และสิ่งที่จะเปลี่ยนแปลงในอินเทอร์เฟซ Fusion ของ Antimatter
การทำงานของแอป
- ส่วนเซิร์ฟเวอร์ เป็นโปรแกรม Go เซิร์ฟเวอร์จะรันมันเป็นกระบวนการแยกต่างหากและสื่อสารกันผ่าน RPC: เซิร์ฟเวอร์เรียก hooks ของแอป (เช่น กำลังจะโพสต์ข้อความ, รันคำสั่งแล้ว…), และแอปเรียก plugin API ของเซิร์ฟเวอร์ (สร้างโพสต์, อ่านผู้ใช้, บันทึกละดับข้อมูล…) นอกจากนี้ยังสามารถให้บริการ HTTP routes ภายใต้
/plugins/<app id>/ได้ด้วย - ส่วน web app เป็น JavaScript bundle ที่โหลดเข้าเบราว์เซอร์พร้อมกับ web app มันจะลงทะเบียน React components และ callbacks กับ registry: ปุ่มในเมนู, แผง, หรือวิธีวาดประเภทข้อความ
- แต่ละส่วนเป็นตัวเลือก (optional) แอปจะถูกติดตั้งเป็น bundle รูปแบบ
.tar.gzและผู้ดูแลระบบจะเป็นผู้เปิดใช้งาน, ปิดใช้งาน และตั้งค่า
Antimatter รันแอปที่เขียนสำหรับ Mattermost และในทางกลับกันได้ ตราบใดที่แอปใช้เฉพาะ hooks ที่ทั้งสองระบบมี ดู Hooks ที่มีเฉพาะใน Antimatter
โครงสร้างของ bundle
ไม่มี starter template แยกต่างหาก: เริ่มต้นจากแอปขนาดเล็กที่มากับระบบ เช่น antimatter-plugin-polls หรือ antimatter-plugin-todo จากนั้นเปลี่ยน ID และโค้ดของมัน Polls มีโครงสร้างดังนี้:
antimatter-plugin-polls/
├── plugin.json the manifest
├── Makefile build, test, bundle, deploy
├── build/ the manifest and pluginctl helper tools (Go), setup.mk
├── assets/icon.svg files shipped with the bundle
├── go.mod
├── server/
│ ├── main.go plugin.ClientMain(&Plugin{})
│ ├── plugin.go the Plugin type, OnActivate, ServeHTTP
│ ├── manifest.go generated from plugin.json
│ ├── api.go, command.go, hooks.go, store.go …
│ └── dist/ compiled binaries, one per platform
└── webapp/
├── package.json, webpack.config.js, tsconfig.json
├── i18n/ en.json and one file per language
├── src/index.tsx registers the app with the web app
├── src/manifest.ts generated from plugin.json
├── src/translations.ts
└── dist/main.js the bundle
ตัวเลือก (Optional): โฟลเดอร์ public/ ซึ่งจะถูกให้บริการตามนั้นที่ /plugins/<app id>/public/
plugin.json
{
"id": "com.example.weather",
"name": "Weather",
"description": "Forecasts in the chat.",
"homepage_url": "https://github.com/example/weather",
"support_url": "https://github.com/example/weather/issues",
"icon_path": "assets/icon.svg",
"version": "1.0.0",
"min_server_version": "12.0.0",
"server": {
"executables": {
"linux-amd64": "server/dist/plugin-linux-amd64",
"linux-arm64": "server/dist/plugin-linux-arm64",
"darwin-amd64": "server/dist/plugin-darwin-amd64",
"darwin-arm64": "server/dist/plugin-darwin-arm64",
"windows-amd64": "server/dist/plugin-windows-amd64.exe"
}
},
"webapp": {"bundle_path": "webapp/dist/main.js"},
"settings_schema": {"settings": []}
}
id: ต้องไม่ซ้ำกันและคงที่; โดยทั่วไปจะใช้ชื่อโดเมนแบบกลับ (reverse domain name) มันจะเป็นชื่อของ routes, การตั้งค่าที่บันทึกไว้ และ WebSocket eventsversion: semantic version แอปที่มากับระบบจะไม่ระบุในplugin.json; เครื่องมือ build ของพวกมันจะดึงค่าจาก git tagv*ล่าสุดmin_server_version: Antimatter รายงานค่าเป็น12.0.0settings_schema: หน้่าการตั้่งค่าของแอปในคอนโซลระบบ (ดู การตั้่งค่า).
make apply จะคัดลอก manifest ไปยัง server/manifest.go และ webapp/src/manifest.ts; make manifest-check ใช้เพื่อยืนยันความถูกต้องของ manifest
ส่วนเซิร์ฟเวอร์
ส่วนเซิร์ฟเวอร์จะนำโมดูล Go เดียวกันกับแอป Mattermost คือ github.com/mattermost/mattermost/server/public มาจาก Go module proxy โดย main ของมันจะทำหน้าที่เริ่มแอปเท่านั้น:
package main
import "github.com/mattermost/mattermost/server/public/plugin"
func main() {
plugin.ClientMain(&Plugin{})
}
ประเภท Plugin ฝัง plugin.MattermostPlugin ไว้ ซึ่งทำให้มี p.API พร้อมใช้งาน และอิมพลีเมนต์ฮุคท่ีตองการ:
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}): สร้างบอทของแอปเพียงครั้งเดียวและรับ ID ของมันPublishWebSocketEvent("poll_updated", payload, &model.WebsocketBroadcast{ChannelId: id}): ส่งเหตุการณ์ไปยังแอปบนเว็บ ซึ่งจะได้รับเป็นcustom_<app id>_poll_updatedOpenInteractiveDialog: เปิดไดอะล็อก.LogInfo,LogErrorและฟังก์ชันอื่น ๆ: เขียนลงล็อกของเซิร์ฟเวอร์ พร้อมติดแท็กด้วย ID ของแอปGetUser,GetChannel,HasPermissionToChannel: ตรวจสอบสิทธิ์ว่าผู้ใช้สามารถทำอะไรได้บ้างก่อนดำเนินการแทนผู้ใช้
แอปจะทำงานบนเซิร์ฟเวอร์ทุกตัวในคลัสเตอร์ เก็บสถานะไว้ใน key-value store หรือฐานข้อมูลเท่านั้น ไม่ใช่เก็บไว้ในหน่วยความจำอย่างเดียว
บริการ AI
เซิร์ฟเวอร์ของ Antimatter จะเรียกใช้โมเดล AI สำหรับแอป ผ่านการเชื่อมต่อโมเดลที่ผู้ดูแลระบบตั้งค่าไว้ (ดู AI ในคู่มือผู้ดูแลระบบ) แอปจะไม่เก็บคีย์ของผู้ให้บริการไว้เอง: แอปจะระบุชื่อผู้ใช้ที่ดำเนินการแทนและตำแหน่งที่ใช้งาน จากนั้นเซิร์ฟเวอร์จะตรวจสอบว่าผู้ใช้คนนั้นมีสิทธิ์使用该การเชื่อมต่อในตำแหน่งนั้น, ใช้ขีดจำกัดและงบประมาณ, นับจำนวนโทเคนโดยอ้างอิงกับผู้ใช้และแอป, และตรวจสอบบันทึกการใช้งานโดยไม่ตรวจสอบเนื้อหา
| เมธอด | สิ่งที่ทำ |
|---|---|
AIChat(actingUserID, channelID, teamID, *ai.ChatRequest) | การเติมเต็มบทสนทนา (chat completion): ข้อความ, เครื่องมือ, ผลลัพธ์ที่มีโครงสร้าง (JSONSchema) |
AIChatStream(…) | ทำนองเดียวกัน แต่เป็นแบบสตรีม: อ่าน Events ของสตรีมจนกว่าช่องจะปิด หรือใช้ Close เพื่อหยุดการเรียกใช้ |
AIEmbed(…, *ai.EmbedRequest) | Embeddings ของข้อความ |
AITranscribe(…, *ai.TranscribeRequest) | การแปลงเสียงเป็นข้อความ |
AIDecide(…, *ai.DecideRequest) | คำถามที่มีรูปแบบ (typed questions) ไปยังโมเดลการตัดสินใจ (ใช่หรือไม่, การเลือก, คะแนน) เกี่ยวกับข้อความ |
AIConnectionsForUser(userID, channelID, teamID) | การเชื่อมต่อที่ผู้ใช้สามารถใช้ได้ที่นั่น พร้อมโมเดลและขีดความสามารถ โดยไม่รวมคีย์ |
ConnectionID ของแต่ละคำขอจะใช้สำหรับเลือกการเชื่อมต่อ และ Model จะเลือกหนึ่งในโมเดลของมัน (เว้นว่างเพื่อใช้ค่าเริ่มต้นของการเชื่อมต่อ) Operation เป็นป้ายกำกับสั้นๆ สำหรับบันทึกการใช้งานและการตรวจสอบ (audit) โดยไม่ใช่เนื้อหา เวอร์ชันขั้นต่ำของเซิร์ฟเวอร์คือ 11.11
actingUserID เฉพาะจากแหล่งข้อมูลที่เซิร์ฟเวอร์ตรวจสอบแล้วเท่านั้น: หัวข้อ Mattermost-User-Id ของคำขอไปยัง ServeHTTP ของคุณ หรือผู้ใช้ในบริบทของ hook (ผู้เขียนโพสต์, ผู้ที่รันคำสั่ง) ห้ามรับจากข้อความ, payload ของ webhook, URL หรือคำขอจากแอปอื่น: แอปของคุณจะสิ้นเปลืองสิทธิ์การเข้าถึงของผู้อื่นเมธอดเหล่านี้และแพ็กเกจ ai (server/public/ai) มีอยู่ในสำเนาของ Antimatter เท่านั้น ไม่ใช่ในเวอร์ชันที่เผยแพร่โดย Mattermost เพื่อใช้งานสิ่งเหล่านี้ ให้ build โดยอ้างอิง server/public ของ Antimatter โดยใช้บรรทัด replace ใน go.mod ของคุณ เช่น replace github.com/mattermost/mattermost/server/public => ../antimatter/server/public แอปเช่นนี้จะทำงานบน Antimatter เท่านั้น
การเก็บข้อมูล
แต่ละแอปมี key-value store เป็นของตัวเองในฐานข้อมูลของเซิร์ฟเวอร์:
KVSet(key, value),KVGet(key),KVDelete(key): ค่าเป็น bytes โดยปกติจะเป็น JSON คีย์ยาวได้ไม่เกิน 150 ตัวอักษรKVSetWithExpiry(key, value, seconds): ค่าจะหายไปหลังจากเวลาผ่านไปเท่านั้นKVCompareAndSet(key, old, new)และKVSetWithOptionsที่ใช้Atomic: การอัปเดตที่ปลอดภัยเมื่อมีคำขอหรือเซิร์ฟเวอร์หลายตัวเขียนคีย์เดียวกัน Polls ใช้วิธีนี้บันทึกคะแนนโหวตทุกครั้ง และลองใหม่เมื่อเกิดการชนกันKVList(page, perPage): คีย์ทีละหน้า
ส่วนแอปเว็บ
bundle จะลงทะเบียนคลาสกับ window.registerPlugin โดย initialize จะได้รับ registry และ Redux store:
import manifest from './manifest';
class Plugin {
initialize(registry, store) {
registry.registerTranslations((locale) => translationsFor(locale));
registry.registerPostTypeComponent('custom_poll', PollPost);
registry.registerWebSocketEventHandler(`custom_${manifest.id}_poll_updated`, (msg) => {
store.dispatch(pollUpdated(JSON.parse(msg.data.poll)));
});
registry.registerReconnectHandler(() => store.dispatch(reloadPolls()));
}
}
window.registerPlugin(manifest.id, new Plugin());
- ไลบรารีที่ใช้ร่วมกัน. แอปเว็บจัดเตรียม React, ReactDOM, Redux, React Redux และ React Intl ให้: ระบุรายการเหล่านี้เป็น webpack
externals(React,ReactDOM,Redux,ReactRedux,ReactIntl) แทนที่จะรวมสำเนาของตัวเองใน bundle ดูwebpack.config.jsของ Polls - การเรียกส่วนเซิร์ฟเวอร์ของคุณ. ใช้ Fetch กับ
${window.basename}/plugins/<app id>/…พร้อมตัวเลือกจากClient4.getOptions()(จากmattermost-redux/client): ตัวเลือกเหล่านี้จะนำ header ที่เซิร์ฟเวอร์ต้องการเพื่อรับคำขอจากเบราว์เซอร์ไปด้วย - สถานะของคุณเอง.
registerReducerเพิ่ม reducer ซึ่งจะถูกเก็บไว้ภายใต้state['plugins-<app id>'] - ประเภท. วิธีการของ registry เพิ่มเติมของ Antimatter ไม่มีอยู่ใน types ที่ Mattermost เผยแพร่: Polls ประกาศ registry ที่ใช้ไว้ใน
webapp/src/types/host.ts
วิธีการของ registry และพารามิเตอร์ของมันได้รับการบันทึกไว้ในโค้ด ที่ webapp/fusion/src/plugins/registry.ts (Fusion) และ webapp/channels/src/plugins/registry.ts (classic)
การรองรับอินเทอร์เฟซ Fusion
Antimatter มีอินเทอร์เฟซเว็บสองแบบ: Fusion ซึ่งเป็นค่าเริ่มต้น และแบบ classic ทั้งสองแบบโหลด bundle ของแอปเดียวกันและมี registry เดียวกัน แต่ 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 การโทรและช่องเสียงจะละเว้นแถบด้านข้างและองค์ประกอบ toast ที่ Fusion แทนที่ด้วยอินเทอร์เฟซการโทรของตัวเอง แอปเอเจนต์จะวางการกระทำของ AI ไว้ในเมนูข้อความ เป็นกลุ่มภายใต้ การดำเนินการเพิ่มเติม แทนที่จะเป็นแถบเครื่องมือเมื่อชี้เมาส์ คลาส CSS ของ Fusion เอง (ที่มีคำนำหน้า am-) และเลเยอร์กล่องโต้ตอบ (องค์ประกอบ am-layer) คือสิ่งที่แอปเหล่านี้ใช้เพื่อให้อินเทอร์เฟซเข้ากัน; พวกมันไม่ใช่ API ที่ได้รับการบันทึกไว้และมีความเสถียร
ตำแหน่งที่ Fusion แสดงองค์ประกอบของปลั๊กอิน
| วิธีการลงทะเบียน | ตำแหน่งที่ปรากฏใน 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 | กล่องค้นหาในหัวของช่อง: ขอบเขตของแอปจะปรากฏเมื่อบoxยังไม่มีข้อมู |
registerUserGuideDropdownMenuAction | เกี่ยวกับ Antimatter (ส่วนล่างของแถบด้านข้างของแอป) ใต้ ความช่่วยเหลื่อ |
registerProduct | หน้าเต็มที่ URL ของผลิตภัณฑ์ ซึ่งแสดงรายการภายใต้ ผลิตภัณฑ์ ใน เกี่ยวกับ Antimatter หน้าผลิตภัณฑ์ยังคงใช้กรอบหน้าแบบคลาสสิก ซึ่งเมนูผลิตภัณฑ์จะแสดงเฉพาะเมื่อแอปลงทะเบียนผลิตภัณฑ์หรือเพิ่มรายการเข้าไป แอป Kanban และ Playbooks ที่รวมมาไม่ได้อีกต่อไปลงทะเบียนผลิตภัณฑ์: หน้าของพวกมันเปิดในแผงด้านข้าง |
registerRootComponent, registerGlobalComponent, hooks เชน registerMessageWillBePostedHook, registerSlashCommandWillBePostedHook, registerMessageWillFormatHook, registerFilesWillUploadHook, registerDesktopNotificationHook, registerWebSocketEventHandler, registerReducer, registerTranslations | ทำงานเหมือนในอินเทอร์เฟซแบบคลาสสิก |
registerAdminConsolePlugin, registerAdminConsoleCustomSetting | คอนโซลระบบ ท่ีเป็นแบบคลาสสิกในอินเทอร์เฟซทั้งสอง |
สิ่่งท่ี Fusion ไม่แสดงหรือไม่รัน
registerPostDropdownMenuComponent(คอมโพเนนต์ในเมนูของข้อความ),registerPostCardTypeComponent(การด์ของข้อความในแผงด้านขวา) และregisterProductSwitcherMenuItemไม่แสดงในมุมมองหลัของ Fusion ใช้registerPostDropdownMenuActionแทนตัวแรกregisterMessageWillBeUpdatedHookไม่รันเมื่อข้อความถูกแก้ไขใน Fusion ตรวจสอบการแก้ไขบนเซิร์ฟเวอร์ด้วย hookMessageWillBeUpdatedแทนregisterActionAfterChannelCreationรันเฉพาะจากไดอะล็อกสร้างช่องแบบคลาสสิก ท่ี Fusion เปิดจาก ตัวเลือกเพิ่มเติม… ในไดอะล็อก สร้างช่อง ของมัน- คอมโพเนนต์ของแถบแอปจะแสดงไม่ว่า
supportedProductIdsจะเป็นอะไร - เส้นทางจาก
registerNeedsTeamRouteและregisterCustomRouteคงกรอบหน้าแบบคลาสสิกไว้ เชนผลิตภัณฑ์
Hooks ท่ี Antimatter มีเพีง
เมธอดใน registry เหล่านีไ่ม่มีใน 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 จะมาก่อนแท็บ Emoji องค์ประกอบนี้จะรับ channelId, rootId, filter, onFilterChange, onSelectDone, insertText, isFusion และใน Fusion pickerKit (ดูด้านล่าง) |
registerChannelViewPanel({matcher, component}) | แผงที่อยู่ระหว่างส่วนหัวของช่องกับข้อความ สำหรับช่องที่ matcher(state, channel) เลือกไว้ แผงนี้สามารถซ่อนข้อความและช่องพิมพ์ข้อความ (setMessagesVisible) ได้ เช่นเดียวกับที่ช่องเสียงทำ |
registerSidebarChannelFooterComponent(component) | บรรทัดใต้ช่องในแถบด้านข้าง เช่น ผู้คนในสายการโทรของช่องนั้น รับ channel |
registerIntegrationsOption({title, description, fusionIcon, action, shouldRender}) | เฉพาะ Fusion: รายการในหน้า อินทิเกรชัน อยู่ถัดจากอิโมจิแบบกำหนดเอง แอป GIF จะเพิ่ม GIF และสติกเกอร์แบบกำหนดเองของมันไว้ที่นั่น |
เพิ่มและจัดการมุมมองในตัวเลือกอิโมจิ
ใน Fusion แท็บตัวเลือกอิโมจิจะรับ prop ชื่อ pickerKit: ซึ่งประกอบด้วยคอมโพเนนต์ที่แท็บอิโมจิใช้สำหรับมุมมองการเพิ่มและจัดการ เพื่อให้มุมมองของแท็บมีลักษณะเหมือนกันและยังคงอยู่ภายในตัวเลือก GIFs และ Memes apps ใช้สิ่งนี้เพื่อเพิ่มและจัดการ GIF, สติกเกอร์ และเทมเพลตมีม ซึ่งประกอบด้วย version (1), hooks usePickerView และ useOnEscape, และคอมโพเนนต์ PickerViewButtons (ปุ่ม + และปุ่มจัดการข้างกล่องค้นหา), PickerViewFrame (มุมมองพร้อมลูกศรย้อนกลับ), PickerNotice, PickerField, PickerSearch, PickerDropZone และ PickerManageList การกดปุ่ม Escape จะย้อนกลับจากมุมมองไปยังกริดของแท็บ ตรวจสอบว่า prop นี้มีอยู่: host อื่น ๆ อาจไม่รวมไว้ และแท็บจะยังคงใช้ไดอะล็อกของตัวเองในกรณีนั้น ประเภท (types) อยู่ใน webapp/fusion/src/types/store/plugins.ts (EmojiPickerTabProps) และคอมโพเนนต์อยู่ใน webapp/fusion/src/fusion/popovers/picker_views.tsx.
แผงในหน้าต่างใหม่
Fusion ให้คอมโพเนนต์ของ registerRightHandSidebarComponent รับ props เพิ่มอีก 2 ตัว เพื่อให้ เปิดในหน้าต่างใหม่ เปิดแผงในตำแหน่งที่มันอยู่แทนที่จะอยู่ที่หน้าหลักของแอป:
setPanelState(state): เรียกใช้เมื่อแผงเปลี่ยนตำแหน่ง โดยส่งสตริงสั้น ๆ ที่แอปของคุณกำหนด เช่น'note/<id>'หรือ'board/<id>/card/<id>'หรือnullเมื่ออยู่ที่หน้าหลัก ข้อมูลนี้จะถูกใส่ในที่อยู่ของหน้าต่าง ดังนั้นอย่าใส่ข้อมูลลับไว้ในนั้น สถานะ (state) ที่ยาวเกิน 512 ตัวอักษร หรือมี control characters จะถูกตัดทิ้งinitialPanelState: ในหน้าต่างใหม่ จะเป็นสถานะที่แผงเคยมี หรือundefinedให้ถือว่าเป็นข้อมูลที่ไม่สามารถเชื่อถือได้: ดึงข้อมูลรายการใหม่อีกครั้งในฐานะผู้ใช้ที่เข้าสู่ระบบ และแสดงหน้าหลักของแอปหรือมุมมองไม่พบรายการ (not-found view) หากผู้ใช้ไม่สามารถเปิดรายการนั้นได้
สถานะ (state) จะถูกลืมเมื่อแผงปิดลง หรือแผงของแอปอื่นเข้ามาแทนที่ หากต้องการเปิดหน้าต่างของแผงด้วยตนเอง popoutRhsPlugin (ซึ่งอยู่ใน window.WebappUtils.popouts ด้วย) จะรับ state เป็นอาร์กิวเมนต์ที่ 5 (ไม่บังคับ) 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> โดยใช้คีย์ตัวพิมพ์เล็ก (ดู คู่มือสำหรับผู้ดูแลระบบ)
การแปลภาษา
Antimatter ถูกแปลเป็น 37 ภาษา และแอปที่รวมมาด้วยก็เช่นกัน ส่วนเว็บแอปทำดังนี้:
- เขียนทุกข้อความด้วย React Intl พร้อม ID และข้อความภาษาอังกฤษ:
<FormattedMessage id='polls.create' defaultMessage='Create a poll'/>หรือdefineMessagesเพิ่มคำนำหน้า ID ด้วยชื่อแอปของคุณ - ดึงข้อความภาษาอังกฤษออกมาไว้ใน
webapp/i18n/en.json(แอปที่รวมมาจะใช้formatjs extract) - เพิ่มไฟล์หนึ่งไฟล์ต่อภาษาไว้ข้างเคียง โดยใช้ ID เดียวกัน:
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) และให้ localizer ในภาษาของผู้ใช้แต่ละคน
สร้าง ติดตั้ง และดีบัก
คุณต้องมี 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 debugger เข้ากับส่วนเซิร์ฟเวอร์ที่กำลังทำงาน (สร้างด้วย AM_DEBUG=1 ก่อน) |
make deploy และคำสั่งเซิร์ฟเวอร์อื่น ๆ ใช้ local mode socket เมื่อพบ (/var/tmp/antimatter_local.socket หรือ AM_LOCALSOCKETPATH): รันบนเครื่องของเซิร์ฟเวอร์โดยเปิด local mode ไว้ มิฉะนั้นจะเข้าสู่ระบบไปยังเซิร์ฟเวอร์ที่ระบุโดย MM_SERVICESETTINGS_SITEURL ด้วย AM_ADMIN_TOKEN หรือ AM_ADMIN_USERNAME และ AM_ADMIN_PASSWORD เครื่องมือสร้างยังไม่อ่านชื่อ AM_ สำหรับ URL ของไซต์
ติดตั้งบันเดิลด้วยตนเอง
- ผู้ดูแลระบบอนุญาตการอัปโหลดด้วย
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 ของคุณ