diff --git a/docma-config.json b/docma-config.json
index 855584b94b..848891deb7 100644
--- a/docma-config.json
+++ b/docma-config.json
@@ -111,6 +111,7 @@
"web/client/components/buttons/GoFullButton.jsx",
"web/client/components/mapcontrols/search/SearchBar.jsx",
"web/client/components/buttons/ToggleButton.jsx",
+ "web/client/components/plugins/PluginsContainer.jsx",
"web/client/actions/index.jsdoc",
"web/client/actions/controls.js",
@@ -132,6 +133,7 @@
"plugins": [
"web/client/plugins/index.jsdoc",
"web/client/plugins/BackgroundSwitcher.jsx",
+ "web/client/plugins/DrawerMenu.jsx",
"web/client/plugins/GoFull.jsx",
"web/client/plugins/Map.jsx",
"web/client/plugins/FullScreen.jsx",
diff --git a/web/client/components/plugins/PluginsContainer.jsx b/web/client/components/plugins/PluginsContainer.jsx
index c4d3a54139..67de15e09f 100644
--- a/web/client/components/plugins/PluginsContainer.jsx
+++ b/web/client/components/plugins/PluginsContainer.jsx
@@ -12,7 +12,20 @@ const PluginsUtils = require('../../utils/PluginsUtils');
const assign = require('object-assign');
const {get} = require('lodash');
-
+/**
+ * Container for plugins. Get's the plugin definitions (`plugins`) and configuration (`pluginsConfig`)
+ * to render the configured plugins.
+ * The plugins to render will come from the `mode` entry of the `pluginsConfig`
+ * @class
+ * @memberof components.plugins
+ * @prop {string} mode key of the pluginsConfig object to get the plugins to render
+ * @prop {string} defaultMode mode to use if mode is not defined
+ * @prop {object} params params of the current page, usually from react router. Used as state to get plugins descriptor if monitored state is not present.
+ * @prop {object} plugins the Plugins definitions
+ * @prop {object} pluginsConfig the configuration for the plugins. a map of [mode]: [{pluginCfg1}...]
+ * @prop {object} pluginsState a piece of state to use. usually controls.
+ * @prop {object} monitoredState the piece of state to monitor Used as state to get plugins descriptor.
+ */
const PluginsContainer = React.createClass({
propTypes: {
mode: React.PropTypes.string,
@@ -60,7 +73,7 @@ const PluginsContainer = React.createClass({
},
renderPlugins(plugins) {
return plugins
- .filter((Plugin) => !Plugin.hide)
+ .filter((Plugin) => !PluginsUtils.handleExpression(this.props.pluginsState, this.props.plugins && this.props.plugins.requires, Plugin.hide))
.map(this.getPluginDescriptor)
.filter((Plugin) => Plugin && !Plugin.impl.loadPlugin)
.filter(this.filterPlugins)
diff --git a/web/client/components/share/ShareEmbed.jsx b/web/client/components/share/ShareEmbed.jsx
index 0df7bc398c..5ee585fb4a 100644
--- a/web/client/components/share/ShareEmbed.jsx
+++ b/web/client/components/share/ShareEmbed.jsx
@@ -15,10 +15,10 @@
const React = require('react');
const CopyToClipboard = require('react-copy-to-clipboard');
const Message = require('../../components/I18N/Message');
-const {Glyphicon, Col, Grid, Row, Tooltip, Button} = require('react-bootstrap');
+const {Glyphicon, Col, Grid, Row, Tooltip, Button, Checkbox} = require('react-bootstrap');
const OverlayTrigger = require('../misc/OverlayTrigger');
-
+const url = require('url');
// css required
require('./share.css');
@@ -27,11 +27,11 @@ const ShareEmbed = React.createClass({
shareUrl: React.PropTypes.string
},
getInitialState() {
- return {copied: false};
+ return {copied: false, forceDrawer: false};
},
render() {
- const codeEmbedded = "";
+ const codeEmbedded = "";
const tooltip = (
{this.state.copied ? : }
);
@@ -44,11 +44,16 @@ const ShareEmbed = React.createClass({
);
return (
+
+
+ this.setState({forceDrawer: !this.state.forceDrawer})}>
+
+
@@ -59,6 +64,14 @@ const ShareEmbed = React.createClass({
);
+ },
+ generateUrl() {
+ const parsed = url.parse(this.props.shareUrl, true);
+ if (this.state.forceDrawer) {
+ parsed.query.forceDrawer = true;
+ }
+ return url.format(parsed);
+
}
});
diff --git a/web/client/localConfig.json b/web/client/localConfig.json
index 0e25a81f2e..373e598c21 100644
--- a/web/client/localConfig.json
+++ b/web/client/localConfig.json
@@ -209,7 +209,10 @@
"cfg": {
"tools": ["locate"]
}
- }, "DrawerMenu", {
+ }, {
+ "name": "DrawerMenu",
+ "hide": "{!(request.query && request.query.forceDrawer)}"
+ }, {
"name": "Identify",
"cfg": {
"style": {
diff --git a/web/client/plugins/DrawerMenu.jsx b/web/client/plugins/DrawerMenu.jsx
index 914fa25105..3ac9828ea9 100644
--- a/web/client/plugins/DrawerMenu.jsx
+++ b/web/client/plugins/DrawerMenu.jsx
@@ -21,6 +21,7 @@ const Section = require('./drawer/Section');
const {partialRight} = require('lodash');
+
const Menu = connect((state) => ({
show: state.controls.drawer && state.controls.drawer.enabled,
activeKey: state.controls.drawer && state.controls.drawer.menu || "1",
@@ -33,6 +34,21 @@ const Menu = connect((state) => ({
require('./drawer/drawer.css');
+/**
+ * DrawerMenu plugin. Shows a left menu with some pluins rendered inside it (typically the TOC).
+ * @prop {string} cfg.glyph glyph icon to use for the button
+ * @prop {object} cfg.menuButtonStyle Css inline style for the button. Display property will be overridden by the hideButton/forceDrawer options.
+ * @prop {string} cfg.buttonClassName class for the toggle button
+ * @prop {object} cfg.menuOptions options for the drawer menu. They can be `docked`, `width.
+ * @memberof plugins
+ * @class
+ * @example
+ * {
+ * "name": "DrawerMenu",
+ * "cfg": {
+ * "hideButton": true
+ * }
+ */
const DrawerMenu = React.createClass({
propTypes: {
items: React.PropTypes.array,
diff --git a/web/client/plugins/containers/ToolsContainer.jsx b/web/client/plugins/containers/ToolsContainer.jsx
index be4a2985b6..bfcfe24f1d 100644
--- a/web/client/plugins/containers/ToolsContainer.jsx
+++ b/web/client/plugins/containers/ToolsContainer.jsx
@@ -28,7 +28,22 @@ const {setControlProperty, toggleControl} = require('../../actions/controls');
const {partial} = require('lodash');
const assign = require('object-assign');
-
+/**
+ * A container for tools.
+ * @memberof plugins.containers.ToolsContainer
+ * @class ToolsContainer
+ * @static
+ * @prop {object[]} tools An array of tools. Each tool have this shape. the first in order wins:
+ * ```
+ * {
+ * tool: {boolean|node} if boolean and true, renders the plugins itself, if object, renders this object as a react component,
+ * exclusive: if true, gets a selector to make it active or not, setting active property of the tool. tool.toggleControl | tool.name is used from controls state to retrieve the status of the tool
+ * toggle: same as above, but sets also bsStyle
+ * action: if present, this action will be binded to the context and associated to the tool as eventSelector (default onClick)
+ * }
+ * ```
+ *
+ */
const ToolsContainer = React.createClass({
propTypes: {
id: React.PropTypes.string.isRequired,
diff --git a/web/client/translations/data.de-DE b/web/client/translations/data.de-DE
index 37f0f6b423..66efff8466 100644
--- a/web/client/translations/data.de-DE
+++ b/web/client/translations/data.de-DE
@@ -522,6 +522,7 @@
"socialIntro": "In deinem bevorzugten sozialen Netzwerk",
"directLinkTitle": "Über einen direkten Link",
"embeddedLinkTitle": "Über einen eingebetteten Code",
+ "forceDrawer": "Inhaltsverzeichnis anzeigen",
"apiLinkTitle": "API verwenden",
"social": "Social",
"direct": "Link",
diff --git a/web/client/translations/data.en-US b/web/client/translations/data.en-US
index 2a857a540f..5b6b3ef87a 100644
--- a/web/client/translations/data.en-US
+++ b/web/client/translations/data.en-US
@@ -522,6 +522,7 @@
"socialIntro": "In your favourite social network",
"directLinkTitle": "Via a direct link",
"embeddedLinkTitle": "Via the embedded code",
+ "forceDrawer": "Show TOC",
"apiLinkTitle": "Using APIs",
"social": "Social",
"direct": "Link",
diff --git a/web/client/translations/data.fr-FR b/web/client/translations/data.fr-FR
index 5b5537f053..9717a4f45f 100644
--- a/web/client/translations/data.fr-FR
+++ b/web/client/translations/data.fr-FR
@@ -524,6 +524,7 @@
"socialIntro": "via votre réseau social favori",
"directLinkTitle": "Via un lien direct",
"embeddedLinkTitle": "Via le code intégré",
+ "forceDrawer": "Afficher la table des matières",
"apiLinkTitle": "Via le APIs",
"social": "Social",
"direct": "Link",
diff --git a/web/client/translations/data.it-IT b/web/client/translations/data.it-IT
index a4ed55acd4..cf2759b756 100644
--- a/web/client/translations/data.it-IT
+++ b/web/client/translations/data.it-IT
@@ -525,6 +525,7 @@
"direct": "Link",
"code": "Embed",
"embeddedLinkTitle": "Con il codice embedded",
+ "forceDrawer": "Mostra l'indice dei livelli",
"apiLinkTitle": "Usando le API",
"QRCodeLinkTitle": "Usando il QR Code",
"msgCopiedUrl":"Copiato",
diff --git a/web/client/utils/PluginsUtils.js b/web/client/utils/PluginsUtils.js
index 3f0d56db01..8276d49922 100644
--- a/web/client/utils/PluginsUtils.js
+++ b/web/client/utils/PluginsUtils.js
@@ -10,6 +10,7 @@ const assign = require('object-assign');
const {omit, isObject, head, isArray, isString} = require('lodash');
const {combineReducers} = require('redux');
const {connect} = require('react-redux');
+const url = require('url');
const {combineEpics} = require('redux-observable');
@@ -34,20 +35,30 @@ const isPluginConfigured = (pluginsConfig, plugin) => {
};
/*eslint-disable */
-const parseExpression = (state, requires, value) => {
+const parseExpression = (state = {}, context = {}, value) => {
const searchExpression = /^\{(.*?)\}$/;
- const context = requires || {};
const expression = searchExpression.exec(value);
+ const request = url.parse(location.href, true);
if (expression !== null) {
return eval(expression[1]);
}
return value;
};
/*eslint-enable */
-
-const handleExpression = (state, requires, expression) => {
+/**
+ * Parses a expression string "{some javascript}" and evaluate it.
+ * The expression will be evalueted getting as parameters the state and the context and the request.
+ * @memberof utils.PluginsUtils
+ * @param {object} state the state context
+ * @param {object} context the context element
+ * @param {string} expression the expression to parse, it's a string
+ * @return {object} the result of the expression
+ * @example "{1===0 && request.query.queryParam1=paramValue1}"
+ * @example "{1===0 && context.el1 === 'checked'}"
+ */
+const handleExpression = (state, context, expression) => {
if (isString(expression) && expression.indexOf('{') === 0) {
- return parseExpression(state, requires, expression);
+ return parseExpression(state, context, expression);
}
return expression;
};
@@ -213,6 +224,7 @@ const PluginsUtils = {
connect: (mapStateToProps, mapDispatchToProps, mergeProps, options) => {
return connect(mapStateToProps, mapDispatchToProps, mergeProps || pluginsMergeProps, options);
},
+ handleExpression,
getMorePrioritizedContainer
};
module.exports = PluginsUtils;
diff --git a/web/client/utils/__tests__/PluginUtils-test.js b/web/client/utils/__tests__/PluginUtils-test.js
index 29acafe467..d4e098d492 100644
--- a/web/client/utils/__tests__/PluginUtils-test.js
+++ b/web/client/utils/__tests__/PluginUtils-test.js
@@ -98,4 +98,7 @@ describe('PluginsUtils', () => {
const domElement = ReactDOM.findDOMNode(app);
expect(domElement.innerText).toBe("plugintest");
});
+ it('handleExpression', () => {
+ expect(PluginsUtils.handleExpression({state1: "test1"}, {context1: "test2"}, "{state.state1 + ' ' + context.context1}")).toBe("test1 test2");
+ });
});