summaryrefslogtreecommitdiffstats
path: root/webapp/plugins
diff options
context:
space:
mode:
authorJoram Wilander <jwawilander@gmail.com>2017-08-29 09:54:02 -0400
committerGitHub <noreply@github.com>2017-08-29 09:54:02 -0400
commit257edc9ea3b25328aa44098e963815c3c3d25312 (patch)
treeed72b2f646ea9287fdccb5076b99b01bc8585a1d /webapp/plugins
parent82a8bd99cc5fe59fe4577c9b0d2c06a82c89e628 (diff)
downloadchat-257edc9ea3b25328aa44098e963815c3c3d25312.tar.gz
chat-257edc9ea3b25328aa44098e963815c3c3d25312.tar.bz2
chat-257edc9ea3b25328aa44098e963815c3c3d25312.zip
Experimental implementation for webapp plugins (#7185)
* Start of experimental implementation for webapp plugins * Updates to webapp plugin architecture * Update pluggable test * Remove debug code
Diffstat (limited to 'webapp/plugins')
-rw-r--r--webapp/plugins/index.js51
-rw-r--r--webapp/plugins/pluggable/index.js17
-rw-r--r--webapp/plugins/pluggable/pluggable.jsx55
3 files changed, 123 insertions, 0 deletions
diff --git a/webapp/plugins/index.js b/webapp/plugins/index.js
new file mode 100644
index 000000000..2e8240cec
--- /dev/null
+++ b/webapp/plugins/index.js
@@ -0,0 +1,51 @@
+// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+// EXPERIMENTAL - SUBJECT TO CHANGE
+
+import store from 'stores/redux_store.jsx';
+import {ActionTypes} from 'utils/constants.jsx';
+import {getSiteURL} from 'utils/url.jsx';
+
+window.plugins = {};
+
+export function registerComponents(components) {
+ store.dispatch({
+ type: ActionTypes.RECEIVED_PLUGIN_COMPONENTS,
+ data: components || {}
+ });
+}
+
+export function initializePlugins() {
+ const pluginJson = window.mm_config.Plugins || '[]';
+
+ let pluginManifests;
+ try {
+ pluginManifests = JSON.parse(pluginJson);
+ } catch (error) {
+ console.error('Invalid plugins JSON: ' + error); //eslint-disable-line no-console
+ return;
+ }
+
+ pluginManifests.forEach((m) => {
+ function onLoad() {
+ // Add the plugin's js to the page
+ const script = document.createElement('script');
+ script.type = 'text/javascript';
+ script.text = this.responseText;
+ document.getElementsByTagName('head')[0].appendChild(script);
+
+ // Initialize the plugin
+ console.log('Registering ' + m.id + ' plugin...'); //eslint-disable-line no-console
+ const plugin = window.plugins[m.id];
+ plugin.initialize(registerComponents, store);
+ console.log('...done'); //eslint-disable-line no-console
+ }
+
+ // Fetch the plugin's bundled js
+ const xhrObj = new XMLHttpRequest();
+ xhrObj.open('GET', getSiteURL() + m.bundle_path, true);
+ xhrObj.addEventListener('load', onLoad);
+ xhrObj.send('');
+ });
+}
diff --git a/webapp/plugins/pluggable/index.js b/webapp/plugins/pluggable/index.js
new file mode 100644
index 000000000..d00f18a5d
--- /dev/null
+++ b/webapp/plugins/pluggable/index.js
@@ -0,0 +1,17 @@
+// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import {connect} from 'react-redux';
+import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
+
+import Pluggable from './pluggable.jsx';
+
+function mapStateToProps(state, ownProps) {
+ return {
+ ...ownProps,
+ components: state.plugins.components,
+ theme: getTheme(state)
+ };
+}
+
+export default connect(mapStateToProps)(Pluggable);
diff --git a/webapp/plugins/pluggable/pluggable.jsx b/webapp/plugins/pluggable/pluggable.jsx
new file mode 100644
index 000000000..566e024e5
--- /dev/null
+++ b/webapp/plugins/pluggable/pluggable.jsx
@@ -0,0 +1,55 @@
+// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+// EXPERIMENTAL - SUBJECT TO CHANGE
+
+import React from 'react';
+import PropTypes from 'prop-types';
+
+export default class Pluggable extends React.PureComponent {
+ static propTypes = {
+
+ /*
+ * Should be a single overridable React component
+ */
+ children: PropTypes.element.isRequired,
+
+ /*
+ * Components for overriding provided by plugins
+ */
+ components: PropTypes.object.isRequired,
+
+ /*
+ * Logged in user's theme
+ */
+ theme: PropTypes.object.isRequired
+ }
+
+ render() {
+ const child = React.Children.only(this.props.children).type;
+ const components = this.props.components;
+
+ if (child == null) {
+ return null;
+ }
+
+ // Include any props passed to this component or to the child component
+ let props = {...this.props};
+ Reflect.deleteProperty(props, 'children');
+ Reflect.deleteProperty(props, 'components');
+ props = {...props, ...this.props.children.props};
+
+ // Override the default component with any registered plugin's component
+ if (components.hasOwnProperty(child.name)) {
+ const PluginComponent = components[child.name];
+ return (
+ <PluginComponent
+ {...props}
+ theme={this.props.theme}
+ />
+ );
+ }
+
+ return React.cloneElement(this.props.children, {...props});
+ }
+}