diff --git a/cms/envs/common.py b/cms/envs/common.py index 3e57f1b064fa..f9bc75a82cf3 100644 --- a/cms/envs/common.py +++ b/cms/envs/common.py @@ -1000,6 +1000,11 @@ ##### EMBARGO ##### EMBARGO_SITE_REDIRECT_URL = None +##### custom vendor plugin variables ##### +# JavaScript code can access this data using `process.env.JS_ENV_EXTRA_CONFIG` +# One of the current use cases for this is enabling custom TinyMCE plugins +JS_ENV_EXTRA_CONFIG = {} + ############################### PIPELINE ####################################### PIPELINE = { diff --git a/common/lib/xmodule/xmodule/js/src/html/edit.js b/common/lib/xmodule/xmodule/js/src/html/edit.js index a86cf21cf811..33accebc07c3 100644 --- a/common/lib/xmodule/xmodule/js/src/html/edit.js +++ b/common/lib/xmodule/xmodule/js/src/html/edit.js @@ -95,7 +95,8 @@ tinyMCE incorrectly decides that the suffix should be "", which means it fails to load files. */ tinyMCE.suffix = ".min"; - this.tiny_mce_textarea = $(".tiny-mce", this.element).tinymce({ + + var tinyMceConfig = { script_url: baseUrl + "js/vendor/tinymce/js/tinymce/tinymce.full.min.js", font_formats: _getFonts(), theme: "modern", @@ -171,7 +172,41 @@ */ init_instance_callback: this.initInstanceCallback, browser_spellcheck: true - }); + }; + + if (typeof process != "undefined" && process.env.JS_ENV_EXTRA_CONFIG) { + var tinyMceAdditionalPlugins = process.env.JS_ENV_EXTRA_CONFIG.TINYMCE_ADDITIONAL_PLUGINS; + // check if we have any additional plugins passed + if (tinyMceAdditionalPlugins) { + // go over each plugin + tinyMceAdditionalPlugins.forEach(function (tinyMcePlugin) { + // check if plugins is not empty (ie there are existing plugins) + if (tinyMceConfig.plugins.trim()) { + tinyMceConfig.plugins += ', '; + } + + // add the plugin to the list of plugins + tinyMceConfig.plugins += tinyMcePlugin.name; + + // check if the plugin should be included in the toolbar + if (tinyMcePlugin.toolbar) { + // check if toolbar is not empty (ie there are already items in the toolbar) + if (tinyMceConfig.toolbar.trim()) { + tinyMceConfig.toolbar += ' | '; + } + + tinyMceConfig.toolbar += tinyMcePlugin.name; + } + + // add the additional settings for each plugin (if there is any) + if (tinyMcePlugin.extra_settings) { + tinyMceConfig[tinyMcePlugin.name] = tinyMcePlugin.extra_settings; + } + }); + } + } + + this.tiny_mce_textarea = $(".tiny-mce", this.element).tinymce(tinyMceConfig); tinymce.addI18n('en', { /* diff --git a/pavelib/assets.py b/pavelib/assets.py index 1fe27b3a10ef..cdde0d7e3a96 100644 --- a/pavelib/assets.py +++ b/pavelib/assets.py @@ -5,7 +5,9 @@ import argparse import glob +import json import os +import re import traceback from datetime import datetime from functools import wraps @@ -763,14 +765,35 @@ def webpack(options): """ Run a Webpack build. """ + def json_format_setting(setting_value): + """ + Replaces capitalized booleans with booleans that + are compatible with parsed JSON in javascript. + """ + if isinstance(setting_value, str): + # replace python bools with json valid bools + setting_value = re.sub(r'(\:\s?)True', r'\1true', setting_value) + setting_value = re.sub(r'(\:\s?)False', r'\1false', setting_value) + + setting_value = setting_value.replace("'", '"') + + return setting_value + settings = getattr(options, 'settings', Env.DEVSTACK_SETTINGS) static_root_lms = Env.get_django_setting("STATIC_ROOT", "lms", settings=settings) static_root_cms = Env.get_django_setting("STATIC_ROOT", "cms", settings=settings) config_path = Env.get_django_setting("WEBPACK_CONFIG_PATH", "lms", settings=settings) - environment = u'NODE_ENV={node_env} STATIC_ROOT_LMS={static_root_lms} STATIC_ROOT_CMS={static_root_cms}'.format( + js_env_extra_config_setting = Env.get_django_setting("JS_ENV_EXTRA_CONFIG", "cms", settings=settings) + js_env_extra_config_json_setting = json.loads(json_format_setting(js_env_extra_config_setting) or '{}') + js_env_extra_config = json.dumps(json.dumps(js_env_extra_config_json_setting, sort_keys=True)) + environment = ( + u'NODE_ENV={node_env} STATIC_ROOT_LMS={static_root_lms} STATIC_ROOT_CMS={static_root_cms} ' + u'JS_ENV_EXTRA_CONFIG={js_env_extra_config}' + ).format( node_env="development" if config_path == 'webpack.dev.config.js' else "production", static_root_lms=static_root_lms, - static_root_cms=static_root_cms + static_root_cms=static_root_cms, + js_env_extra_config=js_env_extra_config, ) sh( cmd( diff --git a/pavelib/paver_tests/test_servers.py b/pavelib/paver_tests/test_servers.py index be69d8d5f319..fa90849fdc66 100644 --- a/pavelib/paver_tests/test_servers.py +++ b/pavelib/paver_tests/test_servers.py @@ -1,6 +1,8 @@ """Unit tests for the Paver server tasks.""" +import json + import ddt from paver.easy import call_task @@ -46,10 +48,12 @@ EXPECTED_PRINT_SETTINGS_COMMAND = [ u"python manage.py lms --settings={settings} print_setting STATIC_ROOT 2>{log_file}", u"python manage.py cms --settings={settings} print_setting STATIC_ROOT 2>{log_file}", - u"python manage.py lms --settings={settings} print_setting WEBPACK_CONFIG_PATH 2>{log_file}" + u"python manage.py lms --settings={settings} print_setting WEBPACK_CONFIG_PATH 2>{log_file}", + u"python manage.py cms --settings={settings} print_setting JS_ENV_EXTRA_CONFIG 2>{log_file}", ] EXPECTED_WEBPACK_COMMAND = ( u"NODE_ENV={node_env} STATIC_ROOT_LMS={static_root_lms} STATIC_ROOT_CMS={static_root_cms} " + u"JS_ENV_EXTRA_CONFIG={js_env_extra_config} " u"$(npm bin)/webpack --config={webpack_config_path}" ) @@ -252,6 +256,7 @@ def verify_server_task(self, task_name, options, contracts_default=False): node_env="production", static_root_lms=None, static_root_cms=None, + js_env_extra_config=json.dumps({}), webpack_config_path=None )) expected_messages.extend(self.expected_sass_commands(system=system, asset_settings=expected_asset_settings)) @@ -298,6 +303,7 @@ def verify_run_all_servers_task(self, options): node_env="production", static_root_lms=None, static_root_cms=None, + js_env_extra_config=json.dumps({}), webpack_config_path=None )) expected_messages.extend(self.expected_sass_commands(asset_settings=expected_asset_settings)) diff --git a/webpack.dev.config.js b/webpack.dev.config.js index 913d773caca3..05f75385c8ca 100644 --- a/webpack.dev.config.js +++ b/webpack.dev.config.js @@ -20,7 +20,8 @@ module.exports = _.values(Merge.smart(commonConfig, { debug: true }), new webpack.DefinePlugin({ - 'process.env.NODE_ENV': JSON.stringify('development') + 'process.env.NODE_ENV': JSON.stringify('development'), + 'process.env.JS_ENV_EXTRA_CONFIG': process.env.JS_ENV_EXTRA_CONFIG }) ], module: { diff --git a/webpack.prod.config.js b/webpack.prod.config.js index 360ab56d4d01..dc85e4efae3b 100644 --- a/webpack.prod.config.js +++ b/webpack.prod.config.js @@ -17,7 +17,8 @@ var optimizedConfig = Merge.smart(commonConfig, { devtool: false, plugins: [ new webpack.DefinePlugin({ - 'process.env.NODE_ENV': JSON.stringify('production') + 'process.env.NODE_ENV': JSON.stringify('production'), + 'process.env.JS_ENV_EXTRA_CONFIG': process.env.JS_ENV_EXTRA_CONFIG }), new webpack.LoaderOptionsPlugin({ // This may not be needed; legacy option for loaders written for webpack 1 minimize: true