অ্যাপ লেখা (প্লাগইন)

অ্যাপসমূহ Antimatter-এর ভেতর থেকে ফিচার যোগ করে: একটি Go-তে লেখা সার্ভার অংশ যা সার্ভারে হুক করে, এবং একটি React-তে লেখা ওয়েব অ্যাপ অংশ যা মেনু, প্যানেল, ম্যাসেজ টাইপ এবং পেজ যোগ করে। এই পেজে একটি অ্যাপ কীভাবে সাজানো থাকে তা দেখানো হয়েছে, যেখানে বান্ডেল করা Polls অ্যাপটি উদাহরণ হিসেবে ব্যবহার করা হয়েছে, এবং Antimatter-এর Fusion ইন্টারফেসে কী পরিবর্তন আসে তাও দেখানো হয়েছে।

একটি অ্যাপ কীভাবে কাজ করে

  • সার্ভার অংশটি একটি Go প্রোগ্রাম। সার্ভার এটি একটি আলাদা প্রসেস হিসেবে চালু করে এবং RPC-এর মাধ্যমে এর সাথে যোগাযোগ করে: এটি অ্যাপের হুকস (একটি ম্যাসেজ পোস্ট করার আগে, একটি কম্যান্ড চালানো হয়েছে…) কল করে, এবং অ্যাপটি সার্ভারের প্লাগইন API (একটি পোস্ট তৈরি করা, একটি ইউজার পড়া, ডেটা সংরক্ষণ করা…) কল করে। এটি /plugins/<app id>/ এর নিচে HTTP রুটও সার্ভ করতে পারে।
  • ওয়েব অ্যাপ অংশটি একটি JavaScript বান্ডেল যা ওয়েব অ্যাপের সাথে ব্রাউজারে লোড হয়। এটি রেজিস্ট্রি-তে React কম্পোনেন্ট এবং কলব্যাক রেজিস্টার করে: একটি মেনুতে বাটন, একটি প্যানেল, একটি ম্যাসেজ টাইপ আঁকার উপায়।
  • যেকোনো একটি অংশই ঐচ্ছিক হতে পারে। একটি অ্যাপ .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 এটি যাচাই করে।

সার্ভার অংশ

সার্ভার অংশ Mattermost অ্যাপগুলোর মতোই github.com/mattermost/mattermost/server/public Go মডিউলটি Go মডিউল প্রক্সি থেকে ইমপোর্ট করে। এর main শুধুমাত্র অ্যাপটি চালু করে:

package main

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

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

Plugin টাইপটি plugin.MattermostPlugin এমবেড করে, যা এতে p.API প্রদান করে, এবং এটি যেসব হুক প্রয়োজন তা ইমপ্লিমেন্ট করে:

type Plugin struct {
	plugin.MattermostPlugin
	router *mux.Router
}

func (p *Plugin) OnActivate() error {
	p.router = p.newRouter()
	return p.API.RegisterCommand(&model.Command{
		Trigger:          "weather",
		AutoComplete:     true,
		AutoCompleteDesc: "Show the forecast",
		AutoCompleteHint: "[city]",
	})
}

func (p *Plugin) ExecuteCommand(c *plugin.Context, args *model.CommandArgs) (*model.CommandResponse, *model.AppError) {
	city := strings.TrimSpace(strings.TrimPrefix(args.Command, "/weather"))
	return &model.CommandResponse{
		ResponseType: model.CommandResponseTypeEphemeral,
		Text:         "Forecast for " + city + ": sunny",
	}, nil
}

func (p *Plugin) ServeHTTP(c *plugin.Context, w http.ResponseWriter, r *http.Request) {
	p.router.ServeHTTP(w, r)
}

সাধারণ হুকসমূহ

হুককখন চলে
OnActivate, OnDeactivateযখন অ্যাপটি চালু বা বন্ধ করা হয়, এবং যখন সার্ভার শুরু ও থামে। OnActivate-এ কমান্ড এবং বট নিবন্ধিত করুন।
OnConfigurationChangeযখন এর সেটিংস পরিবর্তিত হয়; p.API.LoadPluginConfiguration(&cfg) দিয়ে সেগুলো লোড করুন।
ServeHTTP/plugins/<app id>/… প্রতিরোধ। যখন অনুরোধটি সাইন-ইন করা ব্যবহারকারী থেকে আসে, তখন সার্ভার Mattermost-User-Id হেডার সেট করে; যদি এটি পাবলিক না হয়, তবে এই হেডার ছাড়া অনুরোধ প্রত্যাখ্যান করুন।
ExecuteCommandঅ্যাপটি নিবন্ধিত করা একটি কমান্ড চালু হয়েছে।
MessageWillBePosted, MessageWillBeUpdatedএকটি ম্যাসেজ সংরক্ষণের আগে: পরিবর্তিত পোস্টটি ফিরিয়ে দিন, অথবা এটি প্রত্যাখ্যান করার কারণটি ফিরিয়ে দিন।
MessageHasBeenPosted, MessageHasBeenUpdated, MessageHasBeenDeletedপরে, পার্শ্বপ্রতিক্রিয়ার জন্য।
ReactionHasBeenAdded, ReactionHasBeenRemovedপ্রতিক্রিয়াসমূহ।
UserHasJoinedChannel, UserHasLeftChannel, UserHasJoinedTeam, UserHasLeftTeam, ChannelHasBeenCreatedসদস্যপদ এবং চ্যানেল পরিবর্তন। একটি টিম হলো একটি কমিউনিটি।
FileWillBeUploadedএকটি ফাইল সংরক্ষণের আগে: এটি পরিবর্তন করুন বা প্রত্যাখ্যান করুন।
OnPluginClusterEventক্লাস্টারের অন্য সার্ভারে একই অ্যাপ থেকে আসা একটি ম্যাসেজ।

সম্পূর্ণ তালিকা, সঠিক সিগনেচারসহ, server/public/plugin/hooks.go-তে রয়েছে; API-টি server/public/plugin/api.go-তে রয়েছে। pluginapi প্যাকেজ (server/public/pluginapi) API-টিকে আরও বান্ধব হেল্পার দিয়ে র‍্যাপ করে।

উপযোগী API কলসমূহ

  • CreatePost, UpdatePost, SendEphemeralPost: অ্যাপের বট বা কোনো ব্যবহারকারীর হয়ে পোস্ট করুন।
  • EnsureBotUser(&model.Bot{Username, DisplayName}): অ্যাপের বটটি একবার তৈরি করুন এবং এর ID পান।
  • PublishWebSocketEvent("poll_updated", payload, &model.WebsocketBroadcast{ChannelId: id}): ওয়েব অ্যাপে একটি ইভেন্ট পাঠান। এটি custom_<app id>_poll_updated হিসেবে পৌঁছায়।
  • OpenInteractiveDialog: একটি ডায়ালগ খুলুন।
  • LogInfo, LogError এবং অন্যান্য: সার্ভার লগে লিখুন, অ্যাপের ID দিয়ে ট্যাগ করা।
  • GetUser, GetChannel, HasPermissionToChannel: কোনো ব্যবহারকারীর হয়ে কাজ করার আগে যাচাই করুন কে কী করতে পারেন।

একটি অ্যাপ ক্লাস্টারের প্রতিটি সার্ভারে চলে। স্টেট কেভি স্টোরে বা ডাটাবেসে রাখুন, শুধুমাত্র মেমরিতে নয়।

AI সার্ভিস

Antimatter-এর সার্ভার অ্যাপসের জন্য AI মডেলগুলোকে কল করে, যেগুলো অ্যাডমিন সেটআপ করা মডেল কানেকশনগুলোর মাধ্যমে কাজ করে (অ্যাডমিন গাইডে AI দেখুন)। কোনো অ্যাপ কখনো প্রোভাইডারের কী (key) ধরে রাখে না: এটি শুধুমাত্র সেই ব্যক্তির নাম এবং অবস্থান উল্লেখ করে যার হয়ে অ্যাপটি কাজ করছে, এবং সার্ভার নিশ্চিত করে যে সেই ব্যক্তি সেখানে কানেকশনটি ব্যবহার করতে পারেন কিনা। এরপর সার্ভার সীমাবদ্ধতা এবং বাজেট প্রয়োগ করে, ব্যক্তি এবং অ্যাপের বিপরীতে টোকেন গণনা করে, এবং কলটির বিষয়বস্তু না দেখেই অডিট রেকর্ড তৈরি করে।

মেথডএটি কী করে
AIChat(actingUserID, channelID, teamID, *ai.ChatRequest)একটি চ্যাট কমপ্লিশন: মেসেজ, টুলস, এবং স্ট্রাকচার্ড আউটপুট (JSONSchema)।
AIChatStream(…)একই প্রক্রিয়া, স্ট্রিমড মোডে: চ্যানেল বন্ধ না হওয়া পর্যন্ত স্ট্রিমের Events পড়ুন, অথবা কলটি থামাতে Close করুন।
AIEmbed(…, *ai.EmbedRequest)টেক্সটের এমবেডিং (Embeddings)।
AITranscribe(…, *ai.TranscribeRequest)স্পিচ টু টেক্সট (Speech to text)।
AIDecide(…, *ai.DecideRequest)কোনো টেক্সট নিয়ে একটি ডিসিশন মডেলের কাছে টাইপড প্রশ্ন (হ্যাঁ বা না, একটি পছন্দ, বা একটি স্কোর)।
AIConnectionsForUser(userID, channelID, teamID)সেই কানেকশনসমূহ যা একজন ব্যক্তি সেখানে ব্যবহার করতে পারেন, তাদের মডেল এবং ক্ষমতা সহ, কিন্তু কী (keys) ছাড়া।

প্রতিটি রিকোয়েস্টের ConnectionID কানেকশনটি নির্বাচন করে, এবং Model তার মডেলগুলোর মধ্যে একটি নির্বাচন করে (কানেকশনের ডিফল্ট মডেলের জন্য খালি রাখুন)। Operation হলো ব্যবহার এবং অডিট রেকর্ডের জন্য একটি সংক্ষিপ্ত লেবেল, যা কখনোই বিষয়বস্তু হয় না। এগুলোর ন্যূনতম সার্ভার সংস্করণ হলো 11.11।

actingUserID শুধুমাত্র সেই উৎস থেকে নিন যা সার্ভার যাচাই করেছে: আপনার ServeHTTP-এ আসা রিকোয়েস্টের Mattermost-User-Id হেডার, অথবা একটি হুকের কনটেক্সটের ব্যবহারকারী (একটি পোস্টের লেখক, বা যে কমান্ড চালিয়েছেন সেই ব্যবহারকারী)। কখনোই কোনো মেসেজ, ওয়েবহুক পেলোয়েড, URL বা অন্য অ্যাপের রিকোয়েস্ট থেকে এটি নিন: তাহলে আপনার অ্যাপ অন্য কারো অ্যাক্সেস ব্যবহার করবে।

এই মেথডগুলো এবং ai প্যাকেজ (server/public/ai) শুধুমাত্র Antimatter-এর মডিউলের কপিতে রয়েছে, Mattermost দ্বারা প্রকাশিত সংস্করণে নয়। এগুলো ব্যবহার করতে, আপনার go.mod-এ একটি replace লাইন দিয়ে Antimatter-এর server/public-এর বিপরীতে বিল্ড করুন, উদাহরণস্বরূপ replace github.com/mattermost/mattermost/server/public => ../antimatter/server/public। এমন একটি অ্যাপ শুধুমাত্র Antimatter-এ চলবে।

এজেন্টদের টুল প্রদান করা

একটি অ্যাপ RegisterAITools([]*model.AIToolDefinition) ব্যবহার করে এজেন্টদের জন্য নিজস্ব টুল অফার করে: প্রতিটি টুলের নাম, শিরোনাম, বিবরণ, ইনপুট JSON স্কিমা এবং ধরন (read, write অথবা external_write), যা নির্ধারণ করে যে একটি এজেন্টের নীতিমালার আওতায় এটি স্বয়ংক্রিয়ভাবে চালানো যাবে কিনা (অ্যাডমিন গাইডের টুলস অংশ দেখুন)। কলের চ্যানেল বা কমিউনিটি আর্গুমেন্টের ওপর, অথবা সার্ভারের ওপর, টুলের প্রয়োজন অনুযায়ী অনুমতিগুলো যাচাই করা হয়। যখন কোনো এজেন্ট টুলটি ব্যবহার করে, তখন সার্ভার UserId-এ অনুরোধকারী ব্যক্তির সত্যাপিত পরিচয়সহ ExecuteAITool(c, *model.AIToolExecution) *model.AIToolResult হুকটি কল করে: কেবল তাদের অনুমতি অনুযায়ী কাজ করুন, এজেন্টের অনুমতি নয়। টুলগুলোর নামকরণ করা হয় <app id>.<name> ফরম্যাটে; সার্ভারের ন্যূনতম সংস্করণ 11.11; এই মেথডগুলো এবং হুকটি কেবল Antimatter-এর server/public-এ বিদ্যমান, যেমন উপরের AI সার্ভিসটি। যেসব অ্যাপের আইডি antimatter, অথবা যেগুলোর নাম mcp. দিয়ে শুরু হয়, তারা টুল রেজিস্টার করতে পারে না।

ডেটা সংরক্ষণ

প্রতিটি অ্যাপের সার্ভারের ডাটাবেসে নিজস্ব একটি কেভি স্টোর থাকে:

  • KVSet(key, value), KVGet(key), KVDelete(key): ভ্যালুগুলো বাইট, সাধারণত JSON। কীসমূহ সর্বোচ্চ ১৫০ অক্ষরের।
  • KVSetWithExpiry(key, value, seconds): সেই সময় পরে ভ্যালুটি মুছে যায়।
  • KVCompareAndSet(key, old, new) এবং KVSetWithOptions Atomic সহ: একাধিক রিকোয়েস্ট বা সার্ভার একই কীতে লেখার সময় নিরাপদ আপডেট। Polls এই পদ্ধতিতে প্রতিটি ভোট সংরক্ষণ করে এবং সংঘাতে রিট্রাই করে।
  • KVList(page, perPage): পেজ ধরে কীসমূহ।

ওয়েব অ্যাপ অংশ

বান্ডেলটি window.registerPlugin দিয়ে একটি ক্লাস নিবন্ধিত করে। এর initialize রেজিস্ট্রি এবং Redux স্টোর পায়:

import manifest from './manifest';

class Plugin {
    initialize(registry, store) {
        registry.registerTranslations((locale) => translationsFor(locale));
        registry.registerPostTypeComponent('custom_poll', PollPost);
        registry.registerWebSocketEventHandler(`custom_${manifest.id}_poll_updated`, (msg) => {
            store.dispatch(pollUpdated(JSON.parse(msg.data.poll)));
        });
        registry.registerReconnectHandler(() => store.dispatch(reloadPolls()));
    }
}

window.registerPlugin(manifest.id, new Plugin());
  • শেয়ার্ড লাইব্রেরি। ওয়েব অ্যাপ React, ReactDOM, Redux, React Redux এবং React Intl প্রদান করে: নিজস্ব কপি বান্ডেল করার পরিবর্তে এগুলোকে webpack externals (React, ReactDOM, Redux, ReactRedux, ReactIntl) হিসেবে তালিকাভুক্ত করুন। Polls-এর webpack.config.js দেখুন।
  • আপনার সার্ভার অংশকে কল করা। Client4.getOptions() (from mattermost-redux/client) থেকে পাওয়া অপশনসহ ${window.basename}/plugins/<app id>/… ফেচ করুন: এগুলো ব্রাউজার থেকে আসা রিকোয়েস্ট গ্রহণ করতে সার্ভারের প্রয়োজনীয় হেডার বহন করে।
  • আপনার নিজস্ব স্টেট। registerReducer একটি রিডিউসার যোগ করে, যা 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 (classic) -এ।

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 তার নিজস্ব কল ইন্টারফেস দিয়ে প্রতিস্থাপন করে। AI চ্যাট অ্যাপটি তার AI অ্যাকশনগুলো হোভার টুলবারের পরিবর্তে মেসেজ মেনুতে আরও অপশন-এর অধীনে একটি গ্রুপ হিসেবে রাখে। Fusion-এর নিজস্ব CSS ক্লাস (am- প্রিফিক্সযুক্ত) এবং তার ডায়লগ লেয়ার (am-layer এলিমেন্ট) হলো সেগুলো যা এই অ্যাপগুলো ইন্টারফেসের সাথে মেলাতে ব্যবহার করে; এগুলো কোনো নথিভুক্ত বা স্থিতিশীল API নয়।

ফিউশনে প্লাগইন উপাদানগুলো কোথায় দেখা যায়

রেজিস্ট্রি মেথডফিউশনে এটি কোথায় দেখা যায়
registerAppBarComponent, registerChannelHeaderButtonActionঅ্যাপ রেল, ডানদিকের কিনারায় অবস্থিত আইকনের স্তম্ভ, আপনার অ্যাভাটার এবং Antimatter সম্পর্কে বাটনের মাঝে। চ্যানেল হেডারের বাটনগুলোও সেখানে থাকে, চ্যানেল হেডারে নয়। সিস্টেম অ্যাডমিন অ্যাপস বার নিষ্ক্রিয় করুন (ExperimentalSettings.DisableAppBar, ডিফল্টে বন্ধ) চালু করলে এটি লুকানো থাকে।
registerRightHandSidebarComponentডান-পার্শ্বের প্যানেল। এর হেডারটি Fusion-এর, প্রতিটি অ্যাপের জন্য একই: অ্যাপের আইকন এবং নাম (যখন এটি শুধুমাত্র টেক্সট আঁকে তখন নিবন্ধিত শিরোনাম, অন্যথায় অ্যাপ রেইলের লেবেল বা ম্যানিফেস্টের নাম), তারপর প্যানেলের আকার পুনরুদ্ধার করুন (একবার আকার পরিবর্তন করার পর), নতুন উইন্ডোতে খুলুন এবং বন্ধ করুন। যে শিরোনাম অন্য কিছু (একটি কন্ট্রোল, একটি লিংক, একটি ছবি) আঁকে, তা বাদ দেওয়া হয়, তাই প্যানেলের মধ্যেই আপনার অ্যাপের নিজস্ব কন্ট্রোলগুলো আঁকুন, উদাহরণস্বরূপ এর শীর্ষে একটি সরু সারিতে। ব্যবহারকারীরা প্যানেলের প্রান্ত টেনে এটি আকার পরিবর্তন করতে পারেন। আরও দেখুন নতুন উইন্ডোতে প্যানেল।
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চ্যানেল হেডারের সার্চ বক্স: এটি খালি থাকলে অ্যাপ স্কোপসমূহ দেখা যায়।
registerUserGuideDropdownMenuActionAntimatter সম্পর্কে (অ্যাপ রেলের নিচের অংশ), সহায়তা এর নিচে।
registerProductপ্রোডাক্টের URL-এ পূর্ণ পৃষ্ঠা, ক্লাসিক পেজ ফ্রেমে, কোনো প্রোডাক্ট মেনু ছাড়াই: Fusion-এর কোনো প্রোডাক্ট মেনু নেই এবং কোথাও প্রোডাক্টের তালিকা দেখায় না, তাই পৃষ্ঠাটিতে আপনি নিজেই লিংক করুন। কোনো বান্ডল করা অ্যাপ কোনো প্রোডাক্ট নিবন্ধন করে না: Kanban, Playbooks এবং AI চ্যাট তাদের সাইড প্যানেলে খোলে।
registerRootComponent, registerGlobalComponent, registerMessageWillBePostedHook, registerSlashCommandWillBePostedHook, registerMessageWillFormatHook, registerFilesWillUploadHook, registerDesktopNotificationHook, registerWebSocketEventHandler, registerReducer, registerTranslations এর মতো হুকসমূহক্লাসিক ইন্টারফেসের মতো কাজ করে।
registerAdminConsolePlugin, registerAdminConsoleCustomSettingসিস্টেম কনসোল, যা উভয় ইন্টারফেসেই ক্লাসিক।

যা Fusion দেখায় না বা চালায় না

  • registerPostDropdownMenuComponent (মেসেজ মেনুতে একটি কম্পোনেন্ট), registerPostCardTypeComponent (ডান-পার্শ্বের প্যানেলে একটি পোস্টের কার্ড) Fusion-এর মূল ভিউতে দেখানো হয় না। registerProductSwitcherMenuItem Fusion-এ কিছু করে না, যার কোনো প্রোডাক্ট মেনু নেই (এটি একটি সতর্কতা লগ করে)। প্রথমটির পরিবর্তে registerPostDropdownMenuAction ব্যবহার করুন।
  • Fusion-এ একটি ম্যাসেজ সম্পাদনা করা হলে registerMessageWillBeUpdatedHook চলে না। পরিবর্তে 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})মেসেজ বক্সের ইমোজি পিকারের একটি ট্যাব, যেমন GIFs বা স্টিকার। 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-এ, একটি ইমোজি পিকার ট্যাব একটি pickerKit প্রপ পায়: Emoji ট্যাব তার নিজস্ব যোগ এবং পরিচালনা ভিউয়ের জন্য যে কম্পোনেন্টসমূহ ব্যবহার করে, যাতে একটি ট্যাবের ভিউসমূহ একই রকম দেখায় এবং পিকারের ভেতরেই থাকে। GIFs এবং Memes অ্যাপসমূহ এটি ব্যবহার করে GIFs, স্টিকার এবং মেম টেমপ্লেট যোগ ও পরিচালনা করতে। এতে 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 হলো স্ট্রিং, অথবা react-intl-এর 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। এটি উইন্ডোর ঠিকানায় চলে যায়, তাই এতে কখনো কোনো গোপন তথ্য রাখবেন না। ৫১২ অক্ষরের বেশি লম্বা স্টেট বা কন্ট্রোল ক্যারেক্টারযুক্ত স্টেট উপেক্ষা করা হয়।
  • initialPanelState: নতুন উইন্ডোতে, প্যানেলের আগের স্টেট, অথবা undefined। এটি অবিশ্বস্ত ইনপুট হিসেবে গণ্য করুন: সাইন-ইন করা ব্যক্তি হিসেবে আইটেমটি আবার ফেচ করুন, এবং যদি তারা এটি খুলতে না পারেন তবে অ্যাপের হোম বা একটি 'না-পাওয়া' ভিউ দেখান।

প্যানেল বন্ধ হলে বা অন্য অ্যাপের প্যানেল তার জায়গা নিলে স্টেট ভুলে যাওয়া হয়। আপনি নিজে প্যানেলের উইন্ডো খুলতে চাইলে, popoutRhsPlugin (যা window.WebappUtils.popouts-এও আছে) স্টেটকে একটি ঐচ্ছিক পঞ্চম আর্গুমেন্ট হিসেবে নেয়। Notes, Whiteboard, Todo, Kanban এবং Playbooks এটি ব্যবহার করে।

fusionIcon Fusion-এর আইকন সেটের একটি আইকন নির্দেশ করে (যেমন poll, sticker, calendar বা image; দেখুন webapp/fusion/src/fusion/components/icon)। Fusion এটি icon-এর পরিবর্তে দেখায়, যাতে আইটেমটি অন্যগুলোর সাথে মিলে যায়।

সেটিংস

plugin.json-এ settings_schema সিস্টেম কনসোল › প্লাগইনসমূহ-এ অ্যাপের পেজ বর্ণনা করে:

"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-এ গ্রুপ করা যেতে পারে।

সার্ভার অংশ এগুলো এমন একটি স্ট্রাক্টে পড়ে, যার ফিল্ডগুলো কীগুলোর সাথে মিলে যায়:

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-কে ৩৭টি ভাষায় অনুবাদ করা হয়েছে, এবং বান্ডল করা অ্যাপগুলোও। ওয়েব অ্যাপ অংশ এটি এভাবে করে:

  1. প্রতিটি স্ট্রিং React Intl দিয়ে লিখুন, একটি ID এবং তার ইংরেজি টেক্সট সহ: <FormattedMessage id='polls.create' defaultMessage='Create a poll'/> বা defineMessages। ID-গুলোর আগে আপনার অ্যাপের নাম প্রিফিক্স দিন।
  2. ইংরেজি স্ট্রিংগুলো webapp/i18n/en.json-এ এক্সট্রাক্ট করুন (বান্ডল করা অ্যাপগুলো formatjs extract ব্যবহার করে)।
  3. এর পাশে প্রতিটি ভাষার জন্য একটি করে ফাইল যোগ করুন, একই ID সহ: fr.json, de.json, pt-BR.json, zh-CN.json…
  4. এগুলো ওয়েব অ্যাপে পাঠান, যা ব্যবহারকারীর ভাষা চায়:
    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-এ ফিরে যায়। সার্ভার অংশ যে স্ট্রিং পাঠায় (কমান্ডের উত্তর, বটের মESSAGES), সেই pluginapi/i18n প্যাকেজ বান্ডলে শিপ করা ট্রান্সলেশন ফাইল (যেমন assets/i18n) লোড করে এবং প্রতিটি ব্যবহারকারীর ভাষায় একটি লোকালাইজার দেয়।

বিল্ড, ইনস্টল এবং ডিবাগ

আপনার প্রয়োজন Go এবং Node.js (অ্যাপের .nvmrc-এ উল্লেখিত ভার্সন)। অ্যাপের ফোল্ডারে:

কমান্ডএটি কী করে
make distপ্রতিটি প্ল্যাটফর্মের জন্য সার্ভার অংশ এবং ওয়েব অ্যাপ অংশ বিল্ড করে, এবং বান্ডলটি dist/<app id>-<version>.tar.gz-এ লেখে।
make testGo এবং 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 দিয়ে সাইন ইন করে। বিল্ড টুলস এখনও সাইট URL-এর জন্য AM_ নামটি পড়ে না।

হাতে বান্ডল ইনস্টল করা

  • একজন সিস্টেম অ্যাডমিন AM_PLUGINSETTINGS_ENABLEUPLOADS=true দিয়ে আপলোড অনুমোদন করেন (ডিফল্টে বন্ধ; কোনো System Console সুইচ নেই)।
  • তারপর সিস্টেম কনসোল › প্লাগইনসমূহ › প্লাগইন ম্যানেজমেন্ট › প্লাগইন আপলোড, অথবা amctl plugin add weather.tar.gz এর পরে amctl plugin enable com.example.weather।
  • যদি সার্ভারে প্লাগইন সই আবশ্যক চালু থাকে, তবে এটি শুধুমাত্র এমন বান্ডেল গ্রহণ করে যা তার বিশ্বাসী একটি কী দিয়ে সই করা হয়েছে; অন্যান্য অ্যাপ ইনস্টল করা দেখুন।
ডেভেলপমেন্ট সার্ভারে দ্রুত ইটারেশনের জন্য, সার্ভারে AM_SERVICESETTINGS_ENABLEDEVELOPER=true সেট করুন: তখন এররগুলো তাদের বিস্তারিত বহন করে। বান্ডল করা অ্যাপগুলোর Makefile-এ আপনার শেল-এ MM_SERVICESETTINGS_ENABLEDEVELOPER সেট থাকলে শুধুমাত্র আপনার প্ল্যাটফর্মের জন্য সার্ভার অংশটি বিল্ড করে।