Mattermost記事まとめ: https://blog.kaakaa.dev/tags/mattermost/

本記事について

Mattermostの統合機能アドベントカレンダーの第20日目の記事です。

本記事では、Mattermost上の様々な操作に対応した処理を追加できるMattermost PluginのWebappサイドの機能を紹介していきます。

記事が長いので2日に分けて紹介していきます。それでも長いです。

Mattermost Pluginについての公式ドキュメントは下記になります。 https://developers.mattermost.com/extend/plugins/overview/

サンプルコードは下記リポジトリにコミットしています。 https://github.com/kaakaa/mattermost-plugin-api-sample

Mattermost Plugin (Webapp) について

Mattermost Plugin (Webapp) の本体について紹介します。

MattermostのWebappサイドのPluginを実装する場合、プラグイン本体はregistrystoreを引数にとるinitializeメソッドを持つクラスです。registryはMattermost Plugin (Webapp)の機能を呼び出すためのメソッドを持つオブジェクトで、storeはMattermostのデータにアクセスするためのインターフェースです。

公式ドキュメントでは、以下のようなPluginClassが紹介されています。 https://developers.mattermost.com/extend/plugins/webapp/reference/

class PluginClass {
    /**
    * initialize is called by the webapp when the plugin is first loaded.
    * Receives the following:
    * - registry - an instance of the registry tied to your plugin id
    * - store - the Redux store of the web app.
    */
    initialize(registry, store)

    /**
    * uninitialize is called by the webapp if your plugin is uninstalled
    */
    uninitialize()
}

このPluginClassのインスタンスをwindowオブジェクトが持つregisterPlugin関数にPlugin IDと共に与えることで、Pluginが有効になります。

window.registerPlugin('myplugin', new PluginClass());

Webapp Plugin API

registerRootComponent

registerRootComponentは、画面全体に表示されるモーダルのReact Componentを登録するAPIです。Componentが登録されると、Componentを一意に識別するためのIDが返却されます。

ここで登録したRoot Componentは、登録するだけで表示されるわけではなく、別のアクションから起動されることで表示されます。

登録されたComponentで使用できるpropsは特にありません。channel_controller.jsx

今回は、チャンネルのヘッダー部分にボタンを追加するregisterChannelHeaderButtonAction APIを使ってRoot Componentを表示する例を以下に示します。全てのプログラムを記述すると非常に長くなってしまうため、Reduxによるデータのやりとりなど部分については本記事では割愛します。プログラム全体について知りたい場合は、kaakaa/mattermost-plugin-api-sample また、Mattermost公式チームにより作成されているデモ用プラグインmattermost-plugin-demoの実装も参考になります。

...

import {openRootModal} from './actions'
import reducer from './reducer';

import Root from './components/root';

...

export default class Plugin {
    initialize(registry, store) {
        // (1) Root Componentの登録
        registry.registerRootComponent(Root)

        // (2) Root Componentを呼び出すアクションの登録
        registry.registerChannelHeaderButtonAction(
            channelHeaderButtonIcon,
            () => store.dispatch(openRootModal()),
            "Open Root modal","Open Root modal"
        );

        registry.registerReducer(reducer);
    }
}

上記の例では、(1)でRoot ComponentとなるReact Componentを登録し、(2)でRoot Componentを表示するアクションをチャンネルヘッダーボタンに与えています。(2)store.dispatch(openRootModal())がRoot Componentを表示する処理ですが、これはRoot Componentを表示するstateを流す処理になります。

Root Componentは以下のようなコードであり、visible = trueとなることで画面に表示されます。

import React from 'react';

const Root = ({visible, close}) => {
    if (!visible) {
        return null;
    }

    const style = getStyle();

    return (
        <div
            style={style.backdrop}
            onClick={close}
        >
            <div style={style.modal}>
                <p>Root modal</p>
            </div>
        </div>
    );
};

Root.propTypes = {
    visible: PropTypes.bool.isRequired,
    close: PropTypes.func.isRequired,
};

const getStyle = () => ({
    backdrop: {
        position: 'absolute',
        display: 'flex',
        top: 0,
        left: 0,
        right: 0,
        bottom: 0,
        backgroundColor: 'rgba(0, 0, 0, 0.50)',
        zIndex: 2000,
        alignItems: 'center',
        justifyContent: 'center',
    },
    modal: {
        height: '300px',
        width: '500px',
        padding: '1em',
        color: 'black',
        backgroundColor: 'white',
    },
});

export default Root;

Root ComponentはReact Componentのため、様々な要素を表示することができますが、基本的には通知のために利用し、ユーザーに入力を促すようなユースケースの場合はInteractive Dialogを使用すべきだと思います。

registerPopoverUserAttributesComponent

registerPopoverUserAttributesComponentは、ユーザーアイコンをクリックすると表示されるpopover内に表示されるComponentを登録します。

Mattermost外からユーザーの情報を取得して表示する際に使用できます。所属の名称がコロコロ変わる会社などでLDAPから所属を引いて表示しておくと便利です。

登録したComponentは以下のpropsを受け取れます。profile_popover.jsx

ここでは、固定文字列を表示する例を示します。

...

import UserAttributes from './components/user_attributes'

...

export default class Plugin {
    // eslint-disable-next-line no-unused-vars
    initialize(registry, store) {
		...
        // (1) User Popoverに説明を追加するComponentの登録
		registry.registerPopoverUserAttributesComponent(UserAttributes)
		...
    }
}
import React from 'react';
import PropTypes from 'prop-types';

const UserAttributes = ({user, hide, status}) => {
    console.log("user", user.id);
    return (
        <div>
            <p>{'UserId: ' + user.id}</p>
        </div>
    );
}

UserAttributes.propTypes = {
    user: PropTypes.object.isRequired,
    hide: PropTypes.func.isRequired,
    status: PropTypes.object.isRequired,
};

export default UserAttributes;

registerPopoverUserActionsComponent

registerPopoverUserActionsComponentは、ユーザーアイコンをクリックすると表示されるpopover内に表示されるComponentを登録します。registerPopoverUserAttributesComponentと似ていますが、表示される位置が異なるだけの違いだと思います。

登録したComponentは、registerPopoverUserAttributesComponentと同じく以下のpropsを受け取れます。profile_popover.jsx

registerPopoverUserActionsComponentで、RootComponentを表示する例は以下のようになります。(処理を一部割愛しているため、プログラム全体は https://github.com/kaakaa/mattermost-plugin-api-sample から確認してください)

...
import UserAction from './components/user_action';
...
export default class Plugin {
    // eslint-disable-next-line no-unused-vars
    initialize(registry, store) {
		...
        // User Popoverにアクションを追加するComponentの登録
		registry.registerPopoverUserActionsComponent(UserAction)
		...
	}
}
import React from 'react';
import PropTypes from 'prop-types';

const UserActionComponent = ({openRootModal, user, hide, status}) => {
	const onClick = () => {
		openRootModal();
		hide();
	};
    return (
        <div>
            <p>User Action <button onClick={onClick}>Action</button></p>
        </div>
    )
};

UserActionComponent.PropTypes = {
	openRootModal: PropTypes.func.isRequired,
    user: PropTypes.object.isRequired,
    hide: PropTypes.func.isRequired,
    status: PropTypes.object.isRequired,
};

export default UserActionComponent;

registerLeftSidebarHeaderComponent

registerLeftSidebarHeaderComponentは、Mattermost画面の左サイドバーに表示するComponentを登録します。

登録されたComponentで使用できるpropsは特にありません。sidebar.tsx

常に表示されている部分のため、職場の室温やCO2濃度などを表示するなどの利用方法があるかと思います。

...
import LeftSidebarHeader from './components/left_sidebar_header';
...
export default class Plugin {
    // eslint-disable-next-line no-unused-vars
    initialize(registry, store) {
		...
        // User Popoverにアクションを追加するComponentの登録
		registry.registerLeftSidebarHeaderComponent(LeftSidebarHeader)
		...
	}
}
import React from 'react';

const LeftSidebarHeaderComponent = () => {
	const style = {
		color: 'white',
	};
    return (
        <div style={style}>
            <p>left sidear header</p>
        </div>
    );
}

export default LeftSidebarHeader;

registerBottomTeamSidebarComponent

registerBottomTeamSidebarComponentは、チーム選択サイドバーの下部に表示されるComponentを登録します。1つのチームにしか参加していない場合、チーム選択サイドバー自体が表示されないため、ここで登録したComponentも表示されません。

登録されたComponentで使用できるpropsは特にありません。legacy_team_sidebar_controller.tsx

...
import BottomTeamSidebar from './components/bottom_team_sidebar';
...
export default class Plugin {
    // eslint-disable-next-line no-unused-vars
    initialize(registry, store) {
		...
        // User Popoverにアクションを追加するComponentの登録
		registry.registerLeftSidebarHeaderComponent(BottomTeamSidebar)
		...
	}
}
import React from 'react';

const BottomTeamSidebarComponent = () => (
    <>
        <a href="https://github.com/">
            <i
                className='icon fa fa-github'
                style={{color: 'white'}}
            />
        </a>
    </>
);

export default BottomTeamSidebarComponent;

registerLinkTooltipComponent

registerLinkTooltipComponentは、リンクをhoverした時に表示されるtooltipのComponentを登録します。

登録したComponentは以下のpropsを受け取れます。link_tooltip.tsx

以下の例では単にhoverしているリンクのURLを表示しているだけですが、リンク先から情報を取得して表示するようなこともできます。

...
import LinkTooltip from './components/link_tooltip';
...
export default class Plugin {
    // eslint-disable-next-line no-unused-vars
    initialize(registry, store) {
		...
        // User Popoverにアクションを追加するComponentの登録
		registry.registerLinkTooltipComponent(LinkTooltip)
		...
	}
}
import React from 'react';
import PropTypes from 'prop-types';

const LinkTooltipComponent = ({href}) => {
    return (
        <>
            <p style={{
                backgroundColor: 'white',
                color: 'black',
                padding: '25px',
                border: '1px solid red',
            }}>
                {href}
            </p>
        </>
    )
}

LinkTooltipComponent.propTypes = {
    href: PropTypes.string.isRequired,
};

export default LinkTooltipComponent;

registerChannelHeaderButtonAction

registerChannelHeaderButtonActionは、registerRootComponentのところでも使用しましたが、チャンネルのヘッダ部分にアクション付きのボタンを登録します。

registerChannelHeaderButtonActionは、(icon, action, dropdownText, tooltipText)の4つの引数を取ります。

ボタン形式

ドロップダウン形式

ここでは、先ほどのRoot Comopnentを開くButtonと、クリックすると投稿を作成するButtonの2つのButtonを表示する例を示します。投稿の作成はmattermost-reduxを使って投稿を作成するactionsを作成し、Channel Header Buttonのactionから呼び出しています。

movie

import {openRootModal, createPluginPost} from './actions';
import reducer from './reducer';
import Root from './components/root';

...

export default class Plugin {
    // eslint-disable-next-line no-unused-vars
    initialize(registry, store) {
        // Root Componentの登録
        const rootComponentId = registry.registerRootComponent(Root)

        // Root Componentを呼び出すアクションの登録
        registry.registerChannelHeaderButtonAction(
            () => (<i className='icon fa fa-commenting-o' style={{fontSize: '15px', position: 'relative', top: '-1px'}}/>),
            () => store.dispatch(openRootModal()),
            "Open Root modal","Open Root modal"
		);
		
		...

        // 投稿を作成するチャンネルヘッダボタンを登録
        registry.registerChannelHeaderButtonAction(
            () => (<i className='icon fa fa-commenting-o' style={{fontSize: '15px', position: 'relative', top: '-1px'}}/>),
            (channel, channelMembers) => store.dispatch(createPluginPost(channel.id)),
            "Create Sample Post", "Create Sample Post"
        );

        registry.registerReducer(reducer);
    }
}
import {createPost} from 'mattermost-redux/actions/posts';
import {getCurrentUserId} from 'mattermost-redux/selectors/entities/users';

...

export function createPluginPost(channelId) {
    return async (dispatch, getState) => {
        const state = getState();
        const userId = getCurrentUserId(state)
        const post = {
            channel_id: channelId,
            user_id: userId,
            message: "Post from webapp plugin",
        }
        return await dispatch(createPost(post));
    }
}

registerPostTypeComponent

registerPostTypeComponentは、特定のtypeを持つ投稿をレンダリングする時に使用されるComponentを登録します。投稿(Post)に独自のtypeを指定する場合、そのtypeはcustom_で始まる必要があります。

登録したComponentは以下のpropsを受け取れます。

typeにcustom_sample_postを持つ投稿が作成された場合、背景色が赤の投稿をレンダリングする例を以下に示します。今回は、Incoming Webhookで投稿を作成する際のパラメータとしてcustom_sample_postの独自typeを指定しています。

curl \
  -H "Content-Type: application/json" \
  -d '{"type": "custom_sample_post", "text": "Sample Message", "props": {"card": "## Sample\n* foo\n* bar"}}'  \
  http://localhost:8065/hooks/ucw5qjw86jgeum77o1uw8197jr

...
import CustomPost from './components/custom_post';

export default class Plugin {
    // eslint-disable-next-line no-unused-vars
    initialize(registry, store) {
		...
        // type: custom_sample_post を持つ投稿をレンダリングするComponentの登録
		registry.registerPostTypeComponent('custom_sample_post', CustomPost);
	}
}
...
import React from 'react';
import PropTypes from 'prop-types';

const {formatText, messageHtmlToComponent} = window.PostUtils;

const CustomPostComponent = ({post, theme}) => {
    const formattedText = messageHtmlToComponent(formatText(post.message));

    console.log('theme', theme);
    return (
        <div style={{backgroundColor: '#ffcccc'}}>
            {formattedText}
            <pre>
                {JSON.stringify(post.props, null, 4)}
            </pre>
        </div>
    )
}

CustomPostComponent.propTypes = {
    post: PropTypes.object.isRequired,
    theme: PropTypes.object.isRequired,
};

export default CustomPostComponent;

独自のtypeを指定した投稿を作成した後にPluginを無効にするなど、typeに対応したComponentが登録されていない場合は、通常の投稿と同じようにレンダリングされます。

registerPostCardTypeComponent

registerPostCardTypeComponentは、特定のtypeを持つ投稿のcard要素をレンダリングする時に使用されるComponentを登録します。投稿(Post)に独自のtypeを指定する場合、そのtypeはcustom_で始まる必要があります。

登録したComponentは以下のpropsを受け取れます。

typeにcustom_sample_cardを持つ投稿が作成された場合、背景色が青のカード要素を持つ投稿をレンダリングする例を以下に示します。custom_sample_cardという独自typeはIncoming Webhookのパラメータとして指定しています。

curl \
  -H "Content-Type: application/json" \
  -d '{"type": "custom_sample_card", "text": "Sample Message", "props": {"card": "## Sample\n* foo\n* bar"}}'  \
  http://localhost:8065/hooks/ucw5qjw86jgeum77o1uw8197jr

作成する投稿にprops.cardプロパティがない場合、card要素を表示するためのiボタンが表示されないためカード要素を表示することができません。必ずprops.card要素を指定する必要があります。

...
import CustomCard from './components/custom_card';

export default class Plugin {
    // eslint-disable-next-line no-unused-vars
    initialize(registry, store) {
		...
        // type: custom_sample_card を持つ投稿をレンダリングするComponentの登録
        registry.registerPostCardTypeComponent('custom_sample_card', CustomCard);
	}
}
...
import React from 'react';
import PropTypes from 'prop-types';

const {formatText, messageHtmlToComponent} = window.PostUtils;

const CustomCardComponent = ({post, theme}) => {
    const formattedText = messageHtmlToComponent(formatText(post.message));

    console.log('theme', theme);
    return (
        <div style={{backgroundColor: '#ccccff'}}>
            {formattedText}
            <pre>
                {JSON.stringify(post.props, null, 4)}
            </pre>
        </div>
    )
}

CustomCardComponent.propTypes = {
    post: PropTypes.object.isRequired,
    theme: PropTypes.object.isRequired,
};

export default CustomCardComponent;

独自のtypeを指定した投稿を作成した後にPluginを無効にするなど、typeに対応したComponentが登録されていない場合は、通常の投稿と同じようにレンダリングされます。

registerPostWillRenderEmbedComponent

registerPostWillRenderEmbedComponentは、投稿内で最初に出現するURLの内容をプレビュー表示する部分のコンポーネントを登録します。OpenGraphの情報を表示するアレです。

registerPostWillRenderEmbedComponentは3つの引数を取ります。

また、登録したComponentは以下のpropsを受け取れます。

https://github.com/mattermostで始まるURLが指定された場合に、青い背景色のプレビューをレンダリングする例を以下に示します。

...
import CustomEmbed from './components/custom_embed';

export default class Plugin {
    // eslint-disable-next-line no-unused-vars
    initialize(registry, store) {
		...
        // 投稿に含まれるURLのプレビューをレンダリングするComponentの登録
        registry.registerPostWillRenderEmbedComponent(
            (embed) => embed.url && embed.url.startsWith(`https://github.com/mattermost/`),
            CustomEmbed,
            true
        );
    }
}
...
import React from 'react';
import PropTypes from 'prop-types';

const CustomEmbedComponent = ({embed}) => {
    const {type, url, data} = embed;
    return (
        <div style={{backgroundColor: '#ccffcc'}}>
            <p>{type}</p>
            <p>{url}</p>
            <pre>
                {JSON.stringify(data, null, 4)}
            </pre>
        </div>
    )
}

CustomEmbedComponent.propTypes = {
    embed: PropTypes.shape ({
        type: PropTypes.string.isRequired,
        url: PropTypes.string.isRequired,
        data: PropTypes.object.isRequired,
    })
};

export default CustomEmbedComponent;

以下のリポジトリのoEmbed-pluginブランチに、Mattermost公式チームによるregisterPostWillRenderEmbedComponentを使用したプラグインの実装が残っています。(作りかけで止まっているようですが) https://github.com/mattermost/mattermost-plugin-oembed/tree/oEmbed-plugin

registerMainMenuAction

registerMainMenuActionは、Mattermostのメインメニュー部分に独自のメニューを追加します。

registerPostWillRenderEmbedComponentは3つの引数を取ります。

モーダルを開くアクションが登録されたメインメニューを追加する例を以下に示します。

...
export default class Plugin {
    // eslint-disable-next-line no-unused-vars
    initialize(registry, store) {
		...
        // メインメニューを追加する
        registry.registerMainMenuAction(
            'Sample Main Menu',
            () => store.dispatch(openRootModal()),
            () => (<i className='icon fa fa-plug' style={{fontSize: '15px', position: 'relative', top: '-1px'}}/>)
        );
    }
}
...

registerChannelHeaderMenuAction

registerChannelHeaderMenuActionは、チャンネル名の部分をクリックした時に表示されるメニューに独自のメニューを追加します。

registerPostWillRenderEmbedComponentは2つの引数を取ります。

モーダルを開くアクションが登録されたチャンネルヘッダーメニューを追加する例を以下に示します。

...
export default class Plugin {
    // eslint-disable-next-line no-unused-vars
    initialize(registry, store) {
		...
        // チャンネル名をクリックした際に表示されるメニューに独自メニューを追加する
        registry.registerChannelHeaderMenuAction(
            <i className='icon fa fa-plug' style={{fontSize: '15px', position: 'relative', top: '-1px'}}>{'Sample Menu'}</i>,
            (chnnelId) => store.dispatch(openRootModal())
        );
    }
}
...

registerPostDropdownMenuAction

registerPostDropdownMenuActionは、投稿に対するドロップダウンメニューに独自のメニューを追加します。

registerPostDropdownMenuActionは3つの引数を取ります。

モーダルを開くアクションが登録されたドロップダウンメニューを追加する例を以下に示します。

...
export default class Plugin {
    // eslint-disable-next-line no-unused-vars
    initialize(registry, store) {
		...
        // 投稿に対するドロップダウンメニューに独自メニューを追加する
        registry.registerPostDropdownMenuAction(
            <i className='icon fa fa-plug' style={{fontSize: '15px'}}>{'Sample Post Menu'}</i>,
            (postId) => store.dispatch(openRootModal()),
            (postId) => { return true; }
        );
    }
}
...

registerPostDropdownSubMenuAction

registerPostDropdownSubMenuActionは、投稿に対するドロップダウンメニューにサブメニュー付きの独自のメニューを追加します。

まず、registerPostDropdownSubMenuActionは3つの引数を取ります。

registerPostDropdownSubMenuActionは、返り値として登録したアクションのIDと、サブメニューを登録するための関数を返します。ここで返される関数を使ってサブメニューを登録していきますが、この関数は2つの引数を取ります。

(サブメニューを登録する関数の第3引数としてfilterとなる関数を渡しても、特に効果はないようです)

モーダルを開くアクションが登録されたドロップダウンサブメニューを追加する例を以下に示します。

...
export default class Plugin {
    // eslint-disable-next-line no-unused-vars
    initialize(registry, store) {
		...
        // 投稿に対するサブメニュー付きのドロップダウンメニューを追加する
        const {id, rootRegisterMenuItem} = registry.registerPostDropdownSubMenuAction(
            <i className='icon fa fa-plug' style={{fontSize: '15px'}}>{'Sample SubMenu'}</i>,
            (postId) => store.dispatch(openRootModal()),  // 実行されない
            (postId) => { return true; }
        );
        rootRegisterMenuItem(
            <i className='icon fa fa-plug' style={{fontSize: '15px'}}>{'SubMenu 1'}</i>,
            (postId) => store.dispatch(openRootModal()),
        );
        rootRegisterMenuItem(
            <i className='icon fa fa-plug' style={{fontSize: '15px'}}>{'SubMenu 2'}</i>,
            (postId) => store.dispatch(openRootModal())
        );
    }
}
...

registerPostDropdownMenuComponent

registerPostDropdownMenuComponentは、投稿に対するドロップダウンメニューに独自のコンポーネントを追加します。

登録したComponentは以下のpropsを受け取れます。

モーダルを開くアクションが登録されたドロップダウンメニューを追加する例を以下に示します。

...
import CustomPostDropdown from './components/custom_post_dropdown';
...
export default class Plugin {
    // eslint-disable-next-line no-unused-vars
    initialize(registry, store) {
		...
        // 投稿に対するドロップダウンメニューにコンポーネントを登録する
        registry.registerPostDropdownMenuComponent(CustomPostDropdown);
		...
	}
}
import React from 'react';
import PropTypes from 'prop-types';

import {openRootModal} from 'actions';

const CustomPostDropdownComponent = ({postId, theme, dispatch}) => {
    return (
        <div
            style={{backgroundColor: '#ffcccc'}}
            onClick={() => dispatch(openRootModal())}
        >
            {postId}
        </div>
    )
}

CustomPostDropdownComponent.propTypes = {
    postId: PropTypes.string.isRequired,
    theme: PropTypes.object.isRequired,
    dispatch: PropTypes.func.isRequired,
};

export default CustomPostDropdownComponent;

registerFileUploadMethod

registerFileUploadMethodは、ファイルアップロードメニューに独自のメニューを追加します。

registerFileUploadMethodは3つの引数を取ります。(公式ドキュメントは引数の順序が違っているので注意)

メニューが選択されると、テキストファイルが2つアップロードされる例を以下に示します。

...
export default class Plugin {
    // eslint-disable-next-line no-unused-vars
    initialize(registry, store) {
		...
        // ファイルアップロードメニューを追加する
        registry.registerFileUploadMethod(
            <i className='icon fa fa-pencil-square-o' style={{fontSize: '15px'}}/>,
            (upload) => upload([
                new File(["test1"], "sample1.txt"),
                new File(["test2"], 'sample2.txt')
            ]),
            'Sample File Upload'
        );
    }
}
...

registerFilesWillUploadHook

registerFilesWillUploadHookは、ファイルアップロード時に実行される関数を登録できます。

registerFilesWillUploadHookは2つの引数を取る関数を引数に取ります。

返り値として以下のフィールドを持つオブジェクトを返却する必要があります。

2つ以上のファイルを同時にアップロードしようとするとアップロードをrejectする例を以下に示します。

...
export default class Plugin {
    // eslint-disable-next-line no-unused-vars
    initialize(registry, store) {
		...

        // ファイルアップロード時の処理を登録する
        registry.registerFilesWillUploadHook((files, upload) => {
            let msg = '';
            if (files.length >= 2 ) {
                files = null;
                msg = 'Must upload one by one.';
            }
            return {
                message: msg,
                files: files,
            };
        });
    }
}
...

unregisterComponent

unregisterComponentは、プラグインとして登録したComponentやHookを登録から除外します。

registerLeftSidebarHeaderComponentによって登録したコンポーネントを登録から除外するメインメニューの例を以下に示します。

...
export default class Plugin {
    // eslint-disable-next-line no-unused-vars
    initialize(registry, store) {
		...
        // 左サイドバーの上部に表示されるComponentの登録
        const leftSidebarHeaderComponentId = registry.registerLeftSidebarHeaderComponent(LeftSidebarHeader);
        ...
        // プラグインによって登録されたコンポーネントを登録から除外する
        registry.registerMainMenuAction(
            'Unregister LeftSideberHeader',
            () => registry.unregisterComponent(leftSidebarHeaderComponentId),
            () => (<i className='icon fa fa-plug' style={{fontSize: '15px', position: 'relative', top: '-1px'}}/>)
        );
    }
}
...

unregisterPostTypeComponent

unregisterPostTypeComponentは、unregisterComponentと同様、独自のPostTypeに対して設定したComponentを登録から除外します。

例は省略します。

registerReducer

registerReducerは、プラグイン内で使用するReducerを登録します。

ここまでに紹介してきた例では、モーダルを開くアクションが実行された場合などにregisterReducerによって登録されたreducerを利用しています。

...
import reducer from './reducer';
...
export default class Plugin {
    // eslint-disable-next-line no-unused-vars
    initialize(registry, store) {
		...
        // Reducerを登録する
        registry.registerReducer(reducer);
    }
}
import {combineReducers} from 'redux';

import {OPEN_ROOT_MODAL, CLOSE_ROOT_MODAL} from './action_types';

const rootModalVisible = (state = false, action) => {
    switch (action.type) {
    case OPEN_ROOT_MODAL:
        return true;
    case CLOSE_ROOT_MODAL:
        return false;
    default:
        return state;
    }
};

export default combineReducers({
    rootModalVisible,
});
import {id as pluginId} from './manifest';

export const OPEN_ROOT_MODAL = pluginId + '_open_root_modal';
export const CLOSE_ROOT_MODAL = pluginId + '_close_root_modal';

さいごに

本日は、Mattermost PluginのWebappサイドの実装について紹介しました。 明日は、残りのWebappサイドのAPIを紹介していきます。

comments powered by Disqus