Viết ứng dụng (plugin)

Ứng dụng bổ sung tính năng cho Antimatter từ bên trong: một phần máy chủ bằng Go gắn vào máy chủ, và một phần ứng dụng web bằng React thêm các menu, bảng, kiểu tin nhắn và trang. Trang này cho thấy cách bố trí một ứng dụng, sử dụng ứng dụng Bình chọn có sẵn làm ví dụ, và những thay đổi trong giao diện Fusion của Antimatter.

Cách một ứng dụng hoạt động

  • Phần máy chủ là một chương trình Go. Máy chủ khởi chạy nó như một tiến trình riêng và giao tiếp với nó qua RPC: nó gọi các hook của ứng dụng (một tin nhắn sắp được đăng, một lệnh đã được chạy…), và ứng dụng gọi API plugin của máy chủ (tạo một bài đăng, đọc một người dùng, lưu trữ dữ liệu…). Nó cũng có thể phục vụ các tuyến HTTP dưới /plugins/<app id>/.
  • Phần ứng dụng web là một gói JavaScript được tải vào trình duyệt cùng với ứng dụng web. Nó đăng ký các thành phần React và callback với registry: một nút trong menu, một bảng, một cách để vẽ một kiểu tin nhắn.
  • Mỗi phần đều là tùy chọn. Một ứng dụng được cài đặt dưới dạng gói .tar.gz, và quản trị viên hệ thống sẽ bật, tắt và cấu hình nó.
Phần máy chủ chạy với quyền truy cập đầy đủ vào dữ liệu của máy chủ. Hãy đối xử với một ứng dụng như mã nguồn mà bạn triển khai lên máy chủ.

Antimatter chạy các ứng dụng được viết cho Mattermost, và ngược lại, miễn là ứng dụng chỉ sử dụng các hook mà cả hai đều có. Xem Các hook chỉ có trong Antimatter.

Cấu trúc gói

Không có mẫu khởi tạo riêng biệt: hãy bắt đầu từ một ứng dụng có sẵn nhỏ như antimatter-plugin-polls hoặc antimatter-plugin-todo, và thay đổi ID và mã của nó. Bình chọn được bố trí như sau:

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

Tùy chọn: một thư mục public/, được phục vụ nguyên vẹn tại /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: duy nhất và vĩnh viễn; tên miền đảo là thông thường. Nó đặt tên cho các tuyến, cài đặt đã lưu và các sự kiện WebSocket.
  • version: phiên bản ngữ nghĩa. Các ứng dụng có sẵn bỏ nó khỏi plugin.json; công cụ xây dựng của chúng lấy nó từ thẻ git v* mới nhất.
  • min_server_version: Antimatter báo cáo 12.0.0.
  • settings_schema: trang cài đặt của ứng dụng trong Bảng điều khiển Hệ thống (xem Cài đặt).

make apply sao chép manifest vào server/manifest.go và webapp/src/manifest.ts; make manifest-check xác thực nó.

Phần máy chủ

Phần máy chủ nhập cùng module Go với các ứng dụng Mattermost, github.com/mattermost/mattermost/server/public, từ Go module proxy. Phần main của nó chỉ khởi động ứng dụng:

package main

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

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

Kiểu Plugin nhúng plugin.MattermostPlugin, cho phép nó có p.API, và triển khai các hook cần thiết:

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

Các hook phổ biến

HookThời điểm thực thi
OnActivate, OnDeactivateKhi ứng dụng được bật hoặc tắt, và khi máy chủ khởi động và dừng. Đăng ký các lệnh và bot trong OnActivate.
OnConfigurationChangeKhi cài đặt của nó thay đổi; tải chúng bằng p.API.LoadPluginConfiguration(&cfg).
ServeHTTPCác yêu cầu đến /plugins/<app id>/…. Máy chủ đặt tiêu đề Mattermost-User-Id khi yêu cầu đến từ người dùng đã đăng nhập; từ chối các yêu cầu không có tiêu đề này trừ khi chúng được thiết kế để công khai.
ExecuteCommandMột lệnh do ứng dụng đăng ký đã được thực thi.
MessageWillBePosted, MessageWillBeUpdatedTrước khi một tin nhắn được lưu: trả về một bài đăng đã thay đổi, hoặc một lý do để từ chối nó.
MessageHasBeenPosted, MessageHasBeenUpdated, MessageHasBeenDeletedSau khi sự việc xảy ra, cho các tác dụng phụ.
ReactionHasBeenAdded, ReactionHasBeenRemovedPhản ứng.
UserHasJoinedChannel, UserHasLeftChannel, UserHasJoinedTeam, UserHasLeftTeam, ChannelHasBeenCreatedCác thay đổi về thành viên và kênh. Một nhóm là một cộng đồng.
FileWillBeUploadedTrước khi một tệp được lưu trữ: thay đổi hoặc từ chối nó.
OnPluginClusterEventMột tin nhắn từ cùng ứng dụng trên một máy chủ khác của cụm.

Danh sách đầy đủ, với các chữ ký chính xác, nằm trong server/public/plugin/hooks.go; API nằm trong server/public/plugin/api.go. Gói pluginapi (server/public/pluginapi) bọc API trong các trình hỗ trợ thân thiện hơn.

Các cuộc gọi API hữu ích

  • CreatePost, UpdatePost, SendEphemeralPost: đăng bài dưới danh nghĩa bot của ứng dụng hoặc dưới danh nghĩa người dùng.
  • EnsureBotUser(&model.Bot{Username, DisplayName}): tạo bot của ứng dụng một lần và lấy ID của nó.
  • PublishWebSocketEvent("poll_updated", payload, &model.WebsocketBroadcast{ChannelId: id}): gửi một sự kiện đến ứng dụng web. Nó đến dưới dạng custom_<app id>_poll_updated.
  • OpenInteractiveDialog: mở một hộp thoại.
  • LogInfo, LogError và các hàm tương tự: ghi vào nhật ký máy chủ, được gắn thẻ với ID của ứng dụng.
  • GetUser, GetChannel, HasPermissionToChannel: kiểm tra ai có thể làm gì trước khi hành động thay mặt cho một người dùng.

Ứng dụng chạy trên mọi máy chủ của cụm. Hãy lưu trữ trạng thái trong kho lưu trữ khóa-giá trị hoặc cơ sở dữ liệu, không chỉ trong bộ nhớ.

Dịch vụ AI

Máy chủ Antimatter gọi các mô hình AI cho ứng dụng, thông qua các kết nối mô hình do quản trị viên thiết lập (xem AI trong hướng dẫn dành cho quản trị viên). Ứng dụng không bao giờ giữ khóa của nhà cung cấp: nó chỉ nêu rõ người dùng mà nó hành động thay mặt và vị trí, sau đó máy chủ kiểm tra xem người này có quyền sử dụng kết nối đó ở đó hay không, áp dụng các giới hạn và ngân sách, tính số token cho người dùng và ứng dụng, và kiểm toán cuộc gọi mà không lưu nội dung.

Phương thứcChức năng
AIChat(actingUserID, channelID, teamID, *ai.ChatRequest)Hoàn thành trò chuyện: tin nhắn, công cụ, đầu ra có cấu trúc (JSONSchema).
AIChatStream(…)Tương tự nhưng dạng luồng: đọc các Events của luồng cho đến khi kênh đóng, hoặc gọi Close để dừng cuộc gọi.
AIEmbed(…, *ai.EmbedRequest)Vector nhúng (embeddings) của văn bản.
AITranscribe(…, *ai.TranscribeRequest)Chuyển giọng nói thành văn bản.
AIDecide(…, *ai.DecideRequest)Các câu hỏi có kiểu dữ liệu cho mô hình ra quyết định (có hoặc không, một lựa chọn, một điểm số) về một đoạn văn bản.
AIConnectionsForUser(userID, channelID, teamID)Các kết nối mà một người có thể sử dụng ở đó, cùng với các mô hình và khả năng của chúng, nhưng không bao giờ bao gồm khóa.

ConnectionID của mỗi yêu cầu chọn kết nối, và Model chọn một trong các mô hình của nó (trống nếu dùng mô hình mặc định của kết nối). Operation là một nhãn ngắn cho bản ghi sử dụng và kiểm toán, không bao giờ chứa nội dung. Phiên bản máy chủ tối thiểu là 11.11.

Chỉ lấy actingUserID từ nguồn đã được máy chủ xác minh: tiêu đề Mattermost-User-Id của một yêu cầu đến ServeHTTP của bạn, hoặc người dùng trong ngữ cảnh của hook (tác giả của bài đăng, người dùng đã chạy lệnh). Không bao giờ lấy từ tin nhắn, payload webhook, URL hoặc yêu cầu từ ứng dụng khác: ứng dụng của bạn sẽ tiêu hao quyền truy cập của người khác.

Các phương thức này và gói ai (server/public/ai) chỉ tồn tại trong bản sao của Antimatter cho module, không có trong phiên bản do Mattermost phát hành. Để sử dụng chúng, hãy build dựa trên server/public của Antimatter với dòng replace trong go.mod của bạn, ví dụ replace github.com/mattermost/mattermost/server/public => ../antimatter/server/public. Ứng dụng như vậy chỉ chạy trên Antimatter.

Lưu trữ dữ liệu

Mỗi ứng dụng có kho lưu trữ khóa-giá trị riêng trong cơ sở dữ liệu của máy chủ:

  • KVSet(key, value), KVGet(key), KVDelete(key): giá trị là các byte, thường là JSON. Khóa tối đa 150 ký tự.
  • KVSetWithExpiry(key, value, seconds): giá trị sẽ biến mất sau khoảng thời gian đó.
  • KVCompareAndSet(key, old, new) và KVSetWithOptions với Atomic: cập nhật an toàn khi nhiều yêu cầu hoặc máy chủ ghi cùng một khóa. Polls lưu mỗi phiếu bầu theo cách này và thử lại khi xảy ra xung đột.
  • KVList(page, perPage): các khóa, theo từng trang.

Phần ứng dụng web

Gói (bundle) đăng ký một lớp với window.registerPlugin. Phương thức initialize của nó nhận registry và 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());
  • Thư viện dùng chung. Ứng dụng web cung cấp React, ReactDOM, Redux, React Redux và React Intl: hãy liệt kê chúng như externals của webpack (React, ReactDOM, Redux, ReactRedux, ReactIntl) thay vì đóng gói các bản sao của riêng bạn. Xem webpack.config.js của Polls.
  • Gọi phần máy chủ của bạn. Fetch ${window.basename}/plugins/<app id>/… với các tùy chọn từ Client4.getOptions() (từ mattermost-redux/client): chúng mang theo các header mà máy chủ cần để chấp nhận yêu cầu từ trình duyệt.
  • Trạng thái của riêng bạn. registerReducer thêm một reducer, được lưu dưới state['plugins-<app id>'].
  • Loại dữ liệu. Các phương thức registry bổ sung của Antimatter không có trong các loại dữ liệu đã công bố của Mattermost: Polls khai báo registry mà nó sử dụng trong webapp/src/types/host.ts.

Các phương thức của registry và tham số của chúng được tài liệu hóa trong mã nguồn, tại webapp/fusion/src/plugins/registry.ts (Fusion) và webapp/channels/src/plugins/registry.ts (giao diện cổ điển).

Hỗ trợ giao diện Fusion

Antimatter có hai giao diện web: Fusion (mặc định) và giao diện cổ điển. Cả hai đều tải các gói ứng dụng giống nhau và có cùng registry, nhưng Fusion tự vẽ nhiều thành phần và đặt các thành phần plugin theo cách riêng của nó. Hãy kiểm tra ứng dụng của bạn trên cả hai giao diện.

Phát hiện giao diện

Trước khi bất kỳ ứng dụng nào được tải, cả hai giao diện đều đặt window.antimatterWebUI thành 'fusion' hoặc 'classic', và cùng giá trị đó trên phần tử gốc của trang dưới dạng data-am-web-ui. Trên Mattermost, cả hai đều không được đặt.

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

Các ứng dụng tích hợp sẵn sử dụng điều này để hiển thị khác biệt. Ứng dụng Bình chọn vẽ thẻ bình chọn và hộp thoại tạo mới theo phong cách của Fusion, và mở hộp thoại riêng cho /poll trong Fusion. Cuộc gọi và Kênh thoại loại bỏ các thành phần thanh bên và thông báo nhanh mà Fusion thay thế bằng giao diện cuộc gọi của riêng nó. Ứng dụng Trợ lý đặt các hành động AI của mình vào menu tin nhắn, dưới dạng một nhóm trong Hành động khác, thay vì thanh công cụ khi di chuột. Các lớp CSS riêng của Fusion (có tiền tố am-) và lớp hộp thoại của nó (phần tử am-layer) là những gì các ứng dụng này sử dụng để khớp với giao diện; chúng không phải là một API được tài liệu hóa và ổn định.

Nơi Fusion hiển thị các thành phần plugin

Phương thức RegistryNơi nó xuất hiện trong Fusion
registerAppBarComponent, registerChannelHeaderButtonActionThanh ứng dụng (app rail), cột biểu tượng ở cạnh phải, nằm giữa ảnh đại diện của bạn và nút Giới thiệu về Antimatter. Các nút trong tiêu đề kênh cũng được đặt ở đây, không phải trong tiêu đề kênh. Thanh này sẽ bị ẩn khi quản trị viên hệ thống bật Tắt Thanh ứng dụng (Apps Bar) (ExperimentalSettings.DisableAppBar, mặc định là tắt).
registerRightHandSidebarComponentBảng bên phải. Người dùng có thể thay đổi kích thước bằng cách kéo cạnh của nó và mở nó trong một cửa sổ mới. Nếu ứng dụng cũng là một sản phẩm, bảng sẽ cung cấp tùy chọn Mở dưới dạng trang đầy đủ. Xem Bảng trong cửa sổ mới.
registerChannelHeaderIconCác biểu tượng trong tiêu đề kênh, sau các nút cuộc gọi; trên điện thoại, trong menu Thêm của tiêu đề.
registerCallButtonActionNút cuộc gọi trong tiêu đề kênh; một số nhà cung cấp chia sẻ menu Các cách gọi khác.
registerPostDropdownMenuActionMenu Hành động cho tin nhắn (⋯) của tin nhắn, trong mục con Hành động khác; cũng có trong menu Thao tác chủ đề của bảng chủ đề, dưới Hành động khác. Fusion vẽ biểu tượng của ứng dụng trước mỗi hành động, màu xám: biểu tượng riêng của nó cho các ứng dụng tích hợp sẵn, nếu không thì biểu tượng mà ứng dụng hiển thị trong thanh ứng dụng, nếu không thì một biểu tượng trung tính. Một phần tử không có văn bản ở đầu văn bản của hành động (biểu tượng mà ứng dụng tự vẽ) sẽ bị ẩn.
registerPostDropdownSubMenuActionMột nhóm có tiêu đề trong cùng một mục con Hành động khác, sau các hành động đơn lẻ. Không có ứng dụng nào có mục riêng trong menu tin nhắn.
registerPostActionComponentThanh công cụ khi di chuột trên tin nhắn, trước nút ⋯.
registerPostTypeComponent, registerPostMessageAttachmentComponent, registerPostHeaderComponent, registerPostWillRenderEmbedComponentChính tin nhắn: phần thân, dưới văn bản, sau thời gian, và thay thế cho các bản xem trước liên kết.
registerChannelHeaderMenuActionMenu Tùy chọn kênh của kênh (⋯ trên một kênh trong thanh bên, hoặc nhấp chuột phải), dưới dạng các mục có biểu tượng phích cắm.
registerMainMenuActionMenu cộng đồng (tên cộng đồng ở đầu danh sách kênh), dưới mục Hành động khác, được nhóm theo ứng dụng.
registerSidebarBrowseOrAddChannelMenuActionMenu cộng đồng, sau Tạo kênh và Tạo danh mục.
registerFileUploadMethodMenu + của trình soạn thảo (Thêm vào tin nhắn), sau Tải lên tệp.
registerPostEditorActionComponentThanh định dạng của trình soạn thảo, khi định dạng được hiển thị.
registerAIActionMenuItemComponentMenu AI của trình soạn thảo.
registerFileDropdownMenuAction, registerFilePreviewComponentMenu tùy chọn của tệp, và bản xem trước tệp.
registerUserSettingsHồ sơ & cài đặt, mỗi ứng dụng có một tab riêng dưới tiêu đề Các plugin.
registerChannelSettingsTabHộp thoại cài đặt kênh, dưới tiêu đề Ứng dụng.
registerPopoverUserAttributesComponent, registerPopoverUserActionsComponentThẻ hồ sơ được hiển thị khi bạn nhấp vào một tên hoặc một ảnh đại diện.
registerLeftSidebarHeaderComponent, registerSidebarChannelLinkLabelComponent, registerChannelIconOverride, registerBottomTeamSidebarComponentĐầu danh sách kênh của cộng đồng, cạnh tên kênh, biểu tượng kênh và phần dưới cùng của thanh bên trái của cộng đồng.
registerChannelIntro, registerChannelIntroButtonAction, registerChannelToastComponent, registerNewMessagesSeparatorActionComponent, registerChannelComposerBannerComponent, registerComposerPlaceholderĐầu lịch sử của kênh, đầu danh sách tin nhắn, dòng chưa đọc, phía trên khung soạn tin và phần văn bản gợi ý của khung soạn tin.
registerChannelTypeOptionCác loại kênh trong hộp thoại Tạo kênh.
registerSearchComponentsHộp tìm kiếm trong tiêu đề kênh: các phạm vi ứng dụng sẽ hiển thị khi hộp này trống.
registerUserGuideDropdownMenuActionGiới thiệu về Antimatter (phần dưới cùng của thanh ứng dụng), dưới mục Trợ giúp.
registerProductCác trang đầy đủ tại URL của sản phẩm, được liệt kê dưới Sản phẩm trong Giới thiệu về Antimatter. Các trang sản phẩm giữ lại khung trang cổ điển, trong đó menu sản phẩm chỉ hiển thị khi một ứng dụng đăng ký một sản phẩm hoặc thêm một mục vào đó. Các ứng dụng Kanban và Playbook tích hợp sẵn không còn đăng ký sản phẩm nữa: trang của chúng mở trong bảng bên của chúng.
registerRootComponent, registerGlobalComponent, các hook như registerMessageWillBePostedHook, registerSlashCommandWillBePostedHook, registerMessageWillFormatHook, registerFilesWillUploadHook, registerDesktopNotificationHook, registerWebSocketEventHandler, registerReducer, registerTranslationsHoạt động như trong giao diện cổ điển.
registerAdminConsolePlugin, registerAdminConsoleCustomSettingBảng điều khiển Hệ thống, là bảng điều khiển cổ điển trong cả hai giao diện.

Những gì Fusion không hiển thị hoặc không thực thi

  • registerPostDropdownMenuComponent (một thành phần trong menu tin nhắn), registerPostCardTypeComponent (thẻ của bài đăng trong bảng bên phải) và registerProductSwitcherMenuItem không được hiển thị trong các chế độ xem chính của Fusion. Hãy sử dụng registerPostDropdownMenuAction thay cho thành phần đầu tiên.
  • registerMessageWillBeUpdatedHook không được thực thi khi một tin nhắn được chỉnh sửa trong Fusion. Hãy kiểm tra các chỉnh sửa trên máy chủ bằng hook MessageWillBeUpdated thay thế.
  • registerActionAfterChannelCreation chỉ chạy từ hộp thoại tạo kênh cổ điển, mà Fusion mở từ Tùy chọn khác… trong hộp thoại Tạo kênh của nó.
  • Các thành phần thanh ứng dụng được hiển thị bất kể giá trị supportedProductIds của chúng.
  • Các tuyến đường từ registerNeedsTeamRoute và registerCustomRoute giữ nguyên khung trang cổ điển, giống như các sản phẩm.

Các hook chỉ có trong Antimatter

Các phương thức đăng ký này không tồn tại trong Mattermost. Hãy kiểm tra xem một phương thức có tồn tại trước khi gọi nó, để ứng dụng cũng hoạt động ở đó:

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');
}
Phương thứcNó bổ sung gì
registerComposerMenuItem({text, icon, fusionIcon, action, shouldRender})Một mục trong menu + của hộp tin nhắn, trong cả hai giao diện. action nhận {channelId, rootId}.
registerEmojiPickerTab({id, label, icon, fusionIcon, order, component, shouldRender, replacesGifPicker})Một tab của trình chọn biểu tượng cảm xúc trong hộp tin nhắn, chẳng hạn như GIF hoặc sticker. Trong Fusion, các tab có order nhỏ hơn 10 sẽ đứng trước tab Biểu tượng cảm xúc. Thành phần nhận channelId, rootId, filter, onFilterChange, onSelectDone, insertText, isFusion và, trong Fusion, pickerKit (xem bên dưới).
registerChannelViewPanel({matcher, component})Một panel nằm giữa tiêu đề kênh và các tin nhắn, dành cho các kênh mà matcher(state, channel) chọn. Nó có thể ẩn các tin nhắn và trình soạn thảo (setMessagesVisible), giống như cách các kênh thoại làm.
registerSidebarChannelFooterComponent(component)Một dòng dưới một kênh trong thanh bên, chẳng hạn những người đang trong cuộc gọi của kênh đó. Nó nhận channel.
registerIntegrationsOption({title, description, fusionIcon, action, shouldRender})Chỉ dành cho Fusion: một mục trên các trang Tích Hợp, cạnh biểu tượng cảm xúc tùy chỉnh. Ứng dụng GIF sẽ thêm các GIF và sticker tùy chỉnh của nó vào đó.

Thêm và quản lý các chế độ xem trong trình chọn biểu tượng cảm xúc

Trong Fusion, một tab trình chọn emoji nhận prop pickerKit: các thành phần mà tab Emoji sử dụng cho các chế độ xem thêm và quản lý của riêng nó, để các chế độ xem của tab trông giống nhau và nằm gọn trong trình chọn. Các ứng dụng GIF và Memes sử dụng nó để thêm và quản lý GIF, sticker và mẫu meme. Nó chứa version (1), các hook usePickerView và useOnEscape, cùng các thành phần PickerViewButtons (nút + và nút quản lý bên cạnh ô tìm kiếm), PickerViewFrame (một chế độ xem với mũi tên quay lại), PickerNotice, PickerField, PickerSearch, PickerDropZone và PickerManageList. Nhấn Escape để quay lại từ một chế độ xem về lưới của tab. Hãy kiểm tra xem prop này có tồn tại không: các host khác có thể không bao gồm nó, và khi đó tab sẽ giữ lại các hộp thoại của riêng mình. Các kiểu dữ liệu nằm trong webapp/fusion/src/types/store/plugins.ts (EmojiPickerTabProps) và các thành phần nằm trong webapp/fusion/src/fusion/popovers/picker_views.tsx.

Bảng trong cửa sổ mới

Fusion cung cấp cho thành phần của registerRightHandSidebarComponent hai prop bổ sung, để Mở trong cửa sổ mới mở bảng tại vị trí nó đang ở thay vì ở trang chủ của ứng dụng:

  • setPanelState(state): gọi hàm này khi bảng thay đổi vị trí, với một chuỗi ngắn do ứng dụng của bạn định nghĩa, chẳng hạn như 'note/<id>' hoặc 'board/<id>/card/<id>', hoặc null khi ở trang chủ. Giá trị này sẽ được đưa vào địa chỉ của cửa sổ, vì vậy đừng bao giờ đặt thông tin bí mật vào đó. Trạng thái dài hơn 512 ký tự, hoặc chứa các ký tự điều khiển, sẽ bị loại bỏ.
  • initialPanelState: trong cửa sổ mới, đây là trạng thái mà bảng đã có, hoặc undefined. Hãy coi đây là đầu vào không đáng tin cậy: lấy lại mục một lần nữa với tư cách là người dùng đã đăng nhập, và hiển thị trang chủ của ứng dụng hoặc chế độ xem không tìm thấy nếu họ không thể mở nó.

Trạng thái sẽ bị quên đi khi bảng đóng hoặc bảng của ứng dụng khác thay thế vị trí của nó. Để tự mở cửa sổ của một bảng, popoutRhsPlugin (cũng nằm trong window.WebappUtils.popouts) nhận trạng thái như một đối số tùy chọn thứ năm. Ghi chú, Bảng trắng, Việc cần làm, Kanban và Playbook sử dụng tính năng này.

fusionIcon đặt tên cho một icon trong bộ icon của Fusion (ví dụ poll, sticker, calendar hoặc image; xem webapp/fusion/src/fusion/components/icon). Fusion hiển thị nó thay cho icon, để mục này khớp với các mục khác.

Cài đặt

settings_schema trong plugin.json mô tả trang của ứng dụng trong Bảng điều khiển Hệ thống › Các plugin:

"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."}
  ]
}

Các loại cài đặt: bool, text, longtext, number, dropdown, radio, generated (một bí mật ngẫu nhiên), username, và custom (được vẽ bởi một component mà phần web app đăng ký bằng registerAdminConsoleCustomSetting). Các cài đặt có thể được nhóm trong sections.

Phần server đọc chúng vào một struct có các trường khớp với các key:

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
}

Quản trị viên cũng có thể đặt chúng trong cấu hình, dưới PluginSettings.Plugins.<app id>, với các key viết thường (xem hướng dẫn dành cho quản trị viên).

Bản dịch

Antimatter được dịch sang 37 ngôn ngữ, và các ứng dụng tích hợp sẵn cũng vậy. Phần web app thực hiện điều này như sau:

  1. Viết mọi chuỗi với React Intl, kèm một ID và văn bản tiếng Anh của nó: <FormattedMessage id='polls.create' defaultMessage='Create a poll'/> hoặc defineMessages. Đặt tên ID với tiền tố là tên ứng dụng của bạn.
  2. Trích xuất các chuỗi tiếng Anh vào webapp/i18n/en.json (các ứng dụng tích hợp sẵn sử dụng formatjs extract).
  3. Thêm một tệp cho mỗi ngôn ngữ cạnh tệp đó, với cùng các ID: fr.json, de.json, pt-BR.json, zh-CN.json…
  4. Giao cho ứng dụng web, nơi sẽ hỏi ngôn ngữ của người dùng:
    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);

Một chuỗi bị thiếu sẽ quay về defaultMessage tiếng Anh của nó. Đối với các chuỗi do phần máy chủ gửi (câu trả lời lệnh, tin nhắn bot), gói pluginapi/i18n sẽ tải các tệp dịch được đóng gói (ví dụ assets/i18n) và cung cấp một bộ định vị ngôn ngữ cho từng người dùng.

Xây dựng, cài đặt và gỡ lỗi

Bạn cần Go và Node.js (phiên bản trong tệp .nvmrc của ứng dụng). Trong thư mục của ứng dụng:

LệnhChức năng
make distXây dựng phần máy chủ cho mọi nền tảng và phần ứng dụng web, sau đó ghi gói vào dist/<app id>-<version>.tar.gz.
make testChạy các bài kiểm tra Go và JavaScript.
make check-styleKiểm tra mã nguồn (golangci-lint, ESLint) và kiểm tra các kiểu TypeScript.
make deployXây dựng, sau đó cài đặt và bật gói trên máy chủ.
make watch, make deploy-from-watchLệnh đầu tiên sẽ xây dựng lại phần ứng dụng web mỗi khi bạn lưu; lệnh thứ hai sẽ đóng gói kết quả đã xây dựng và cài đặt nó trên máy chủ.
make enable, make disable, make resetBật, tắt hoặc khởi động lại ứng dụng trên máy chủ.
make logs, make logs-watchHiển thị các dòng nhật ký máy chủ của ứng dụng.
make attachGắn trình gỡ lỗi Delve vào phần máy chủ đang chạy (xây dựng với AM_DEBUG=1 trước).

make deploy và các lệnh máy chủ khác sẽ sử dụng socket chế độ cục bộ nếu tìm thấy (/var/tmp/antimatter_local.socket, hoặc AM_LOCALSOCKETPATH): hãy chạy chúng trên máy của máy chủ với chế độ cục bộ bật. Nếu không, chúng sẽ đăng nhập vào máy chủ được chỉ định bởi MM_SERVICESETTINGS_SITEURL bằng AM_ADMIN_TOKEN, hoặc AM_ADMIN_USERNAME và AM_ADMIN_PASSWORD. Các công cụ xây dựng hiện chưa đọc tên AM_ cho URL của trang web.

Cài đặt gói thủ công

  • Quản trị viên hệ thống cho phép tải lên bằng AM_PLUGINSETTINGS_ENABLEUPLOADS=true (mặc định là tắt; không có công tắc trong Bảng điều khiển Hệ thống).
  • Sau đó, vào Bảng điều khiển Hệ thống › Các plugin › Quản lý Plugin › Tải lên Plugin, hoặc chạy amctl plugin add weather.tar.gz rồi chạy amctl plugin enable com.example.weather.
  • Nếu máy chủ có bật Yêu cầu Chữ ký của Plugin, nó chỉ chấp nhận các gói được ký bằng một khóa mà nó tin tưởng; xem Cài đặt các ứng dụng khác.
Để lặp lại nhanh trên máy chủ phát triển, hãy đặt AM_SERVICESETTINGS_ENABLEDEVELOPER=true trên máy chủ: khi đó các lỗi sẽ kèm theo chi tiết. Makefile của các ứng dụng được đóng gói sẽ chỉ xây dựng phần máy chủ cho nền tảng của bạn khi MM_SERVICESETTINGS_ENABLEDEVELOPER được đặt trong shell của bạn.