Index: wysiwyg-dialog-page.tpl.php =================================================================== RCS file: wysiwyg-dialog-page.tpl.php diff -N wysiwyg-dialog-page.tpl.php --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ wysiwyg-dialog-page.tpl.php 1 Feb 2009 06:56:30 -0000 @@ -0,0 +1,85 @@ +language contains its textual representation. + * $language->dir contains the language direction. It will either be 'ltr' or 'rtl'. + * - $head_title: A modified version of the page title, for use in the TITLE tag. + * - $head: Markup for the HEAD section (including meta tags, keyword tags, and + * so on). + * - $styles: Style tags necessary to import all CSS files for the page. + * - $scripts: Script tags necessary to load the JavaScript files and settings + * for the page. + * + * Site identity: + * - $site_name: The name of the site, empty when display has been disabled + * in theme settings. + * + * Page content (in order of occurrance in the default page.tpl.php): + * - $breadcrumb: The breadcrumb trail for the current page. + * - $title: The page title, for use in the actual HTML content. + * - $help: Dynamic help text, mostly for admin pages. + * - $messages: HTML for status and error messages. Should be displayed prominently. + * - $tabs: Tabs linking to any sub-pages beneath the current page (e.g., the view + * and edit tabs when displaying a node). + * + * - $content: The main content of the current Drupal page. + * + * Footer/closing data: + * - $footer : The footer region. + * - $closure: Final closing markup from any modules that have altered the page. + * This variable should always be output last, after all other dynamic content. + * + * @see template_preprocess() + * @see template_preprocess_wysiwyg_dialog_page() + */ +?> + + + + + <?php print $head_title; ?> + + + + + + +
+
+
+ + +
+

+
+ + +
+ +
+
+ +
+
+
+ + + Index: wysiwyg.dialog.inc =================================================================== RCS file: wysiwyg.dialog.inc diff -N wysiwyg.dialog.inc --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ wysiwyg.dialog.inc 1 Feb 2009 06:56:23 -0000 @@ -0,0 +1,60 @@ + $instance), 'setting'); + + echo theme('wysiwyg_dialog_page', $callback($instance)); +} + +/** + * Template preprocess function for theme_wysiwyg_dialog_page(). + * + * @see wysiwyg_dialog() + * @see wysiwyg-dialog-page.tpl.php + * @see template_preprocess() + */ +function template_preprocess_wysiwyg_dialog_page(&$variables) { + // Construct page title + $head_title = array(strip_tags(drupal_get_title()), variable_get('site_name', 'Drupal')); + + $variables['head_title'] = implode(' | ', $head_title); + $variables['base_path'] = base_path(); + $variables['front_page'] = url(); + // @todo Would a breadcrumb make sense / possible at all? + // $variables['breadcrumb'] = theme('breadcrumb', drupal_get_breadcrumb()); + $variables['head'] = drupal_get_html_head(); + $variables['help'] = theme('help'); + $variables['language'] = $GLOBALS['language']; + $variables['language']->dir = $GLOBALS['language']->direction ? 'rtl' : 'ltr'; + $variables['messages'] = $variables['show_messages'] ? theme('status_messages') : ''; + $variables['site_name'] = (theme_get_setting('toggle_name') ? variable_get('site_name', 'Drupal') : ''); + $variables['css'] = drupal_add_css(); + $variables['styles'] = drupal_get_css(); + $variables['scripts'] = drupal_get_js(); + $variables['tabs'] = theme('menu_local_tasks'); + $variables['title'] = drupal_get_title(); + // Closure should be filled last. + $variables['closure'] = theme('closure'); +} + Index: wysiwyg.init.js =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/wysiwyg/wysiwyg.init.js,v retrieving revision 1.2 diff -u -p -r1.2 wysiwyg.init.js --- wysiwyg.init.js 30 Nov 2008 17:16:27 -0000 1.2 +++ wysiwyg.init.js 1 Dec 2008 15:17:34 -0000 @@ -2,7 +2,7 @@ Drupal.wysiwyg = Drupal.wysiwyg || { 'instances': {} }; -Drupal.wysiwyg.editor = Drupal.wysiwyg.editor || { 'init': {}, 'attach': {}, 'detach': {} }; +Drupal.wysiwyg.editor = Drupal.wysiwyg.editor || { 'init': {}, 'attach': {}, 'detach': {}, 'instance': {} }; Drupal.wysiwyg.plugins = Drupal.wysiwyg.plugins || {}; Index: wysiwyg.js =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/wysiwyg/wysiwyg.js,v retrieving revision 1.7 diff -u -p -r1.7 wysiwyg.js --- wysiwyg.js 18 Jan 2009 22:04:13 -0000 1.7 +++ wysiwyg.js 1 Feb 2009 09:06:39 -0000 @@ -76,8 +76,16 @@ Drupal.wysiwygAttach = function(context, if (typeof Drupal.wysiwyg.editor.attach[params.editor] == 'function') { // (Re-)initialize field instance. Drupal.wysiwyg.instances[params.field] = {}; - // Store new editor name and status for this field. - Drupal.wysiwyg.instances[params.field].editor = params.editor; + // Provide all input format parameters to editor instance. + jQuery.extend(Drupal.wysiwyg.instances[params.field], params); + // Provide editor callbacks for plugins, if available. + if (typeof Drupal.wysiwyg.editor.instance[params.editor] == 'object') { + jQuery.extend(Drupal.wysiwyg.instances[params.field], Drupal.wysiwyg.editor.instance[params.editor]); + } + // Store this field id, so (external) plugins can use it. + // @todo Wrong point in time. Probably can only supported by editors which + // support a onFocus() or similar event. + Drupal.wysiwyg.activeId = params.field; // Attach or update toggle link. Drupal.wysiwygAttachToggleLink(context, params); // Attach editor, if enabled by default or last state was enabled. @@ -131,6 +139,7 @@ Drupal.wysiwygAttachToggleLink = functio Drupal.wysiwygDetach(context, params); // After disabling the editor, re-attach default behaviors. Drupal.wysiwyg.editor.attach.none(context, params); + Drupal.wysiwyg.instances[params.field] = Drupal.wysiwyg.editor.instance.none; Drupal.wysiwyg.instances[params.field].editor = 'none'; $(this).html(Drupal.settings.wysiwyg.enable).blur(); } Index: wysiwyg.module =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/wysiwyg/wysiwyg.module,v retrieving revision 1.21 diff -u -p -r1.21 wysiwyg.module --- wysiwyg.module 1 Feb 2009 05:58:10 -0000 1.21 +++ wysiwyg.module 1 Feb 2009 07:10:38 -0000 @@ -18,16 +18,31 @@ function wysiwyg_menu() { 'access arguments' => array('administer filters'), 'file' => 'wysiwyg.admin.inc', ); + $items['wysiwyg/%'] = array( + 'page callback' => 'wysiwyg_dialog', + 'page arguments' => array(1), + 'access arguments' => array('access content'), + 'type' => MENU_CALLBACK, + 'file' => 'wysiwyg.dialog.inc', + ); return $items; } /** * Implementation of hook_theme(). + * + * @see drupal_common_theme(), common.inc + * @see template_preprocess_page(), theme.inc */ function wysiwyg_theme() { return array( 'wysiwyg_profile_overview' => array('arguments' => array('form' => NULL)), 'wysiwyg_admin_button_table' => array('arguments' => array('form' => NULL)), + 'wysiwyg_dialog_page' => array( + 'arguments' => array('content' => NULL, 'show_messages' => TRUE), + 'file' => 'wysiwyg.dialog.inc', + 'template' => 'wysiwyg-dialog-page', + ), ); } @@ -124,10 +139,10 @@ function wysiwyg_process_form(&$form) { // Check editor theme (and reset it if not/no longer available). $theme = wysiwyg_get_editor_themes($profile, (isset($profile->settings['theme']) ? $profile->settings['theme'] : '')); + // Add plugin settings (first) for this input format. + wysiwyg_add_plugin_settings($profile); // Add profile settings for this input format. wysiwyg_add_editor_settings($profile, $theme); - // Add plugin settings for this input format. - wysiwyg_add_plugin_settings($profile); } // Use a prefix/suffix for a single input format, or attach to input @@ -320,26 +335,62 @@ function wysiwyg_add_editor_settings($pr function wysiwyg_add_plugin_settings($profile) { static $plugins_added = array(); - if (!isset($plugins_added[$profile->editor])) { - $plugins = array(); - $editor = wysiwyg_get_editor($profile->editor); - // Collect editor plugins provided via hook_wysiwyg_plugin(). - $info = module_invoke_all('wysiwyg_plugin', $editor['name'], $editor['installed version']); - // Only keep enabled plugins in this profile. - foreach ($info as $plugin => $meta) { - if (!isset($profile->settings['buttons'][$plugin])) { - unset($info[$plugin]); - } - } + // External plugins must only be loaded once. + // @todo Actually, each native plugin must not be added twice, but different + // profiles can have different plugin-sets for the same editor. We should + // check each plugin for each editor. + if (isset($plugins_added[$profile->editor])) { + return; + } + $plugins_added[$profile->editor] = TRUE; + + $editor = wysiwyg_get_editor($profile->editor); + // Assume that this editor does not support neither native external plugins + // nor Drupal plugins if it does not provide a callback. + if (!(isset($editor['plugin settings callback']) && function_exists($editor['plugin settings callback']))) { + return; + } - if (isset($editor['plugin settings callback']) && function_exists($editor['plugin settings callback'])) { - $plugins = $editor['plugin settings callback']($editor, $profile, $info); + // Collect native editor plugins provided via hook_wysiwyg_plugin(). + $plugins = module_invoke_all('wysiwyg_plugin', $editor['name'], $editor['installed version']); + // Only keep enabled native plugins in this profile. + foreach ($plugins as $plugin => $meta) { + if (!isset($profile->settings['buttons'][$plugin])) { + unset($plugins[$plugin]); } + } + // Invoke the editor's plugin settings callback, so it can populate the + // settings for external plugins with custom, required values. + $plugins = $editor['plugin settings callback']($editor, $profile, $plugins); - drupal_add_js(array('wysiwyg' => array('plugins' => array($profile->editor => $plugins))), 'setting'); + drupal_add_js(array('wysiwyg' => array('plugins' => array($profile->editor => array('native' => $plugins)))), 'setting'); - $plugins_added[$profile->editor] = TRUE; + // If this editor does not define a proxy plugin and a proxy plugin settings + // callback, it does not support "Drupal plugins". + if (!(isset($editor['proxy plugin']) && isset($editor['proxy plugin settings callback']) && function_exists($editor['proxy plugin settings callback']))) { + return; } + + // Collect, load, and add API plugins provided by Drupal modules. + $plugins += $editor['proxy plugin']; + $proxy = key($editor['proxy plugin']); + $proxy_plugins = array(); + foreach (wysiwyg_get_all_plugins() as $plugin_name => $meta) { + if (isset($profile->settings['buttons'][$proxy][$plugin_name])) { + $proxy_plugins[$plugin_name] = $meta; + // Load the Drupal plugin's JavaScript. + drupal_add_js($meta['js path'] .'/'. $meta['js file']); + // Add plugin specific settings. + if (isset($meta['settings'])) { + drupal_add_js(array('wysiwyg' => array('plugins' => array('drupal' => array($plugin_name => $meta['settings'])))), 'setting'); + } + } + } + // Invoke the editor's proxy plugin settings callback, so it can populate + // the settings for Drupal plugins with custom, required values. + $proxy_plugins = $editor['proxy plugin settings callback']($editor, $profile, $proxy_plugins); + + drupal_add_js(array('wysiwyg' => array('plugins' => array($profile->editor => array('drupal' => $proxy_plugins)))), 'setting'); } /** @@ -398,11 +449,17 @@ function wysiwyg_get_plugins($editor_nam if (isset($editor['plugin callback']) && function_exists($editor['plugin callback'])) { $plugins = $editor['plugin callback']($editor); } - // Load our own plugins. - include_once drupal_get_path('module', 'wysiwyg') .'/wysiwyg.plugins.inc'; - // Add editor plugins provided via hook_wysiwyg_plugin(). $plugins = array_merge($plugins, module_invoke_all('wysiwyg_plugin', $editor['name'], $editor['installed version'])); + // Add API plugins provided by Drupal modules. + // @todo We need to pass the filepath to the plugin icon for Drupal plugins. + if (isset($editor['proxy plugin'])) { + $plugins += $editor['proxy plugin']; + $proxy = key($editor['proxy plugin']); + foreach (wysiwyg_get_all_plugins() as $plugin_name => $info) { + $plugins[$proxy]['buttons'][$plugin_name] = $info['title']; + } + } } return $plugins; } @@ -592,6 +649,46 @@ function wysiwyg_get_all_editors() { } /** + * Invoke hook_wysiwyg_plugin() in all modules. + */ +function wysiwyg_get_all_plugins() { + static $plugins; + + if (isset($plugins)) { + return $plugins; + } + + $plugins = wysiwyg_load_includes('plugins', 'plugin'); + foreach ($plugins as $name => $properties) { + $plugin = &$plugins[$name]; + // Fill in required/default properties. + $plugin += array( + 'title' => $plugin['name'], + 'vendor url' => '', + 'js path' => $plugin['path'] . '/' . $plugin['name'], + 'js file' => $plugin['name'] . '.js', + 'css path' => $plugin['path'] . '/' . $plugin['name'], + 'css file' => $plugin['name'] . '.css', + 'icon path' => $plugin['path'] . '/' . $plugin['name'] . '/images', + 'icon file' => $plugin['name'] . '.png', + 'dialog path' => $plugin['name'], + 'dialog settings' => array(), + 'settings callback' => NULL, + 'settings form callback' => NULL, + ); + // Fill in default settings. + $plugin['settings'] += array( + 'path' => base_path() . $plugin['path'] . '/' . $plugin['name'], + ); + // Check whether library is present. + if (!($plugin['installed'] = file_exists($plugin['js path'] . '/' . $plugin['js file']))) { + continue; + } + } + return $plugins; +} + +/** * Load include files for wysiwyg implemented by all modules. * * @param $type Index: wysiwyg.plugins.inc =================================================================== RCS file: wysiwyg.plugins.inc diff -N wysiwyg.plugins.inc --- wysiwyg.plugins.inc 14 Oct 2008 21:45:07 -0000 1.1 +++ /dev/null 1 Jan 1970 00:00:00 -0000 @@ -1,23 +0,0 @@ - array( - 'path' => drupal_get_path('module', 'wysiwyg') .'/plugins/break/editor_plugin.js', - 'buttons' => array('break' => t('Teaser break')), - 'url' => 'http://drupal.org/project/wysiwyg', - ), - ); - } - break; - } -} - Index: editors/tinymce.inc =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/wysiwyg/editors/tinymce.inc,v retrieving revision 1.23 diff -u -p -r1.23 tinymce.inc --- editors/tinymce.inc 1 Feb 2009 05:58:10 -0000 1.23 +++ editors/tinymce.inc 1 Feb 2009 07:20:54 -0000 @@ -33,6 +33,13 @@ function wysiwyg_tinymce_editor() { 'settings callback' => 'wysiwyg_tinymce_settings', 'plugin callback' => 'wysiwyg_tinymce_plugins', 'plugin settings callback' => 'wysiwyg_tinymce_plugin_settings', + 'proxy plugin' => array( + 'drupal' => array( + 'load' => TRUE, + 'proxy' => TRUE, + ), + ), + 'proxy plugin settings callback' => 'wysiwyg_tinymce_proxy_plugin_settings', 'versions' => array( // Each version can override global editor properties. '2.1' => array( // 'include files' => array('tinymce-2.inc'), @@ -183,8 +190,12 @@ function wysiwyg_tinymce_settings($edito if ($type == 'buttons') { $init['buttons'][] = $button; } + // Add external Drupal plugins to the list of extensions. + if ($type == 'buttons' && !empty($plugins[$plugin]['proxy'])) { + $init['extensions'][_wysiwyg_tinymce_plugin_name('add', $button)] = 1; + } // Add external plugins to the list of extensions. - if ($type == 'buttons' && empty($plugins[$plugin]['internal'])) { + else if ($type == 'buttons' && empty($plugins[$plugin]['internal'])) { $init['extensions'][_wysiwyg_tinymce_plugin_name('add', $plugin)] = 1; } // Add internal buttons that also need to be loaded as extension. @@ -291,10 +302,10 @@ function wysiwyg_tinymce_themes($editor, } /** - * Build a JS settings array of external plugins that need to be loaded separately. + * Build a JS settings array of native external plugins that need to be loaded separately. * * TinyMCE requires that external plugins (i.e. not residing in the editor's - * directory) are loaded (once) after the editor has been initialized. + * directory) are loaded (once) upon initializing the editor. */ function wysiwyg_tinymce_plugin_settings($editor, $profile, $plugins) { $settings = array(); @@ -307,6 +318,24 @@ function wysiwyg_tinymce_plugin_settings } /** + * Build a JS settings array for Drupal plugins loaded via the proxy plugin. + */ +function wysiwyg_tinymce_proxy_plugin_settings($editor, $profile, $plugins) { + $settings = array(); + foreach ($plugins as $name => $plugin) { + // Populate required plugin settings. + $settings[$name] = $plugin['dialog settings'] + array( + 'title' => $plugin['title'], + 'icon' => base_path() . $plugin['icon path'] .'/'. $plugin['icon file'], + 'iconTitle' => $plugin['icon title'], + // @todo These should only be set if the plugin defined them. + 'css' => base_path() . $plugin['css path'] .'/'. $plugin['css file'], + ); + } + return $settings; +} + +/** * Add or remove leading hiven to/of external plugin names. * * TinyMCE requires that external plugins, which should not be loaded from Index: editors/js/none.js =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/wysiwyg/editors/js/none.js,v retrieving revision 1.3 diff -u -p -r1.3 none.js --- editors/js/none.js 28 Oct 2008 22:46:05 -0000 1.3 +++ editors/js/none.js 26 Jan 2009 21:09:29 -0000 @@ -43,3 +43,28 @@ Drupal.wysiwyg.editor.detach.none = func } }; +/** + * Instance methods for plain text areas. + */ +Drupal.wysiwyg.editor.instance.none = { + insert: function(content) { + var editor = document.getElementById(this.field); + + // IE support. + if (document.selection) { + editor.focus(); + sel = document.selection.createRange(); + sel.text = content; + } + // Mozilla/Firefox/Netscape 7+ support. + else if (editor.selectionStart || editor.selectionStart == '0') { + var startPos = editor.selectionStart; + var endPos = editor.selectionEnd; + editor.value = editor.value.substring(0, startPos) + content + editor.value.substring(endPos, editor.value.length); + } + // Fallback, just add to the end of the content. + else { + editor.value += content; + } + } +}; Index: editors/js/tinymce-2.js =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/wysiwyg/editors/js/tinymce-2.js,v retrieving revision 1.7 diff -u -p -r1.7 tinymce-2.js --- editors/js/tinymce-2.js 1 Dec 2008 14:14:41 -0000 1.7 +++ editors/js/tinymce-2.js 1 Feb 2009 08:28:57 -0000 @@ -21,8 +21,13 @@ Drupal.wysiwyg.editor.init.tinymce = fun for (var format in settings) { tinyMCE.init(settings[format]); } - for (var plugin in Drupal.settings.wysiwyg.plugins.tinymce) { - tinyMCE.loadPlugin(plugin, Drupal.settings.wysiwyg.plugins.tinymce[plugin]); + // Load native external plugins. + for (var plugin in Drupal.settings.wysiwyg.plugins.tinymce.native) { + tinyMCE.loadPlugin(plugin, Drupal.settings.wysiwyg.plugins.tinymce.native[plugin]); + } + // Load Drupal plugins. + for (var plugin in Drupal.settings.wysiwyg.plugins.tinymce.drupal) { + Drupal.wysiwyg.editor.instance.tinymce.addPlugin(plugin, Drupal.settings.wysiwyg.plugins.tinymce.drupal[plugin], Drupal.settings.wysiwyg.plugins.drupal[plugin]); } }; @@ -56,3 +61,135 @@ Drupal.wysiwyg.editor.detach.tinymce = f // } }; +Drupal.wysiwyg.editor.instance.tinymce = { + addPlugin: function(plugin, settings, pluginSettings) { + if (typeof Drupal.wysiwyg.plugins[plugin] != 'object') { + return; + } + tinyMCE.addPlugin(plugin, { + + // Register an editor command for this plugin, invoked by the plugin's button. + execCommand: function(editor_id, element, command, user_interface, value) { + switch (command) { + case plugin: + if (typeof Drupal.wysiwyg.plugins[plugin].invoke == 'function') { + var ed = tinyMCE.getInstanceById(editor_id); + var data = { format: 'html', node: ed.getFocusElement(), content: ed.getFocusElement() }; + Drupal.wysiwyg.plugins[plugin].invoke(data, pluginSettings, ed.formTargetElementId); + return true; + } + } + // Pass to next handler in chain. + return false; + }, + + // Register the plugin button. + getControlHTML: function(control_name) { + switch (control_name) { + case plugin: + return tinyMCE.getButtonHTML(control_name, settings.iconTitle, settings.icon, plugin); + } + return ''; + }, + + // Load custom CSS for editor contents on startup. + initInstance: function(ed) { + if (settings.css) { + tinyMCE.importCSS(ed.getDoc(), settings.css); + } + }, + + cleanup: function(type, content) { + switch (type) { + case 'insert_to_editor': + // Attach: Replace plain text with HTML representations. + if (typeof Drupal.wysiwyg.plugins[plugin].attach == 'function') { + content = Drupal.wysiwyg.plugins[plugin].attach(content, pluginSettings, tinyMCE.selectedInstance.editorId); + content = Drupal.wysiwyg.editor.instance.tinymce.prepareContent(content); + } + break; + + case 'get_from_editor': + // Detach: Replace HTML representations with plain text. + if (typeof Drupal.wysiwyg.plugins[plugin].detach == 'function') { + content = Drupal.wysiwyg.plugins[plugin].detach(content, pluginSettings, tinyMCE.selectedInstance.editorId); + } + break; + } + // Pass through to next handler in chain + return content; + }, + + // isNode: Return whether the plugin button should be enabled for the + // current selection. + handleNodeChange: function(editor_id, node, undo_index, undo_levels, visual_aid, any_selection) { + if (node == null) { + return; + } + if (typeof Drupal.wysiwyg.plugins[plugin].isNode == 'function') { + if (Drupal.wysiwyg.plugins[plugin].isNode(node)) { + tinyMCE.switchClass(editor_id + '_' + plugin, 'mceButtonSelected'); + return true; + } + } + tinyMCE.switchClass(editor_id + '_' + plugin, 'mceButtonNormal'); + return true; + }, + + /** + * Return information about the plugin as a name/value array. + */ + getInfo: function() { + return { + longname: settings.title + }; + } + }); + }, + + openDialog: function(dialog, params) { + var editor = tinyMCE.getInstanceById(this.field); + tinyMCE.openWindow({ + file: dialog.url + '/' + this.field, + width: dialog.width, + height: dialog.height, + inline: 1 + }, params); + }, + + closeDialog: function(dialog) { + var editor = tinyMCE.getInstanceById(this.field); + tinyMCEPopup.close(); + }, + + prepareContent: function(content) { + // Certain content elements need to have additional DOM properties applied + // to prevent this editor from highlighting an internal button in addition + // to the button of a Drupal plugin. + var specialProperties = { + img: { name: 'mce_drupal' } + }; + $content = $('
' + content + '
'); // No .outerHTML() in jQuery :( + jQuery.each(specialProperties, function(element, properties) { + $content.find(element).each(function() { + for (var property in properties) { + if (property == 'class') { + $(this).addClass(properties[property]); + } + else { + $(this).attr(property, properties[property]); + } + } + }); + }); + return $content.html(); + }, + + insert: function(content) { + content = this.prepareContent(content); + var editor = tinyMCE.getInstanceById(this.field); + editor.execCommand('mceInsertContent', false, content); + editor.repaint(); + } +}; + Index: editors/js/tinymce-3.js =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/wysiwyg/editors/js/tinymce-3.js,v retrieving revision 1.9 diff -u -p -r1.9 tinymce-3.js --- editors/js/tinymce-3.js 1 Dec 2008 14:14:41 -0000 1.9 +++ editors/js/tinymce-3.js 1 Feb 2009 09:02:37 -0000 @@ -22,8 +22,13 @@ Drupal.wysiwyg.editor.init.tinymce = fun for (var format in settings) { tinyMCE.init(settings[format]); } - for (var plugin in Drupal.settings.wysiwyg.plugins.tinymce) { - tinymce.PluginManager.load(plugin, Drupal.settings.wysiwyg.plugins.tinymce[plugin]); + // Load native external plugins. + for (var plugin in Drupal.settings.wysiwyg.plugins.tinymce.native) { + tinymce.PluginManager.load(plugin, Drupal.settings.wysiwyg.plugins.tinymce.native[plugin]); + } + // Load Drupal plugins. + for (var plugin in Drupal.settings.wysiwyg.plugins.tinymce.drupal) { + Drupal.wysiwyg.editor.instance.tinymce.addPlugin(plugin, Drupal.settings.wysiwyg.plugins.tinymce.drupal[plugin], Drupal.settings.wysiwyg.plugins.drupal[plugin]); } }; @@ -34,11 +39,13 @@ Drupal.wysiwyg.editor.init.tinymce = fun */ Drupal.wysiwyg.editor.attach.tinymce = function(context, params, settings) { // Configure editor settings for this input format. - for (var setting in settings) { - tinyMCE.settings[setting] = settings[setting]; - } + var ed = new tinymce.Editor(params.field, settings); + // Reset active instance id on any event. + ed.onEvent.add(function(ed, e) { + Drupal.wysiwyg.activeId = ed.id; + }); // Attach editor. - tinyMCE.execCommand('mceAddControl', true, params.field); + ed.render(); }; /** @@ -64,3 +71,122 @@ Drupal.wysiwyg.editor.detach.tinymce = f } }; +Drupal.wysiwyg.editor.instance.tinymce = { + addPlugin: function(plugin, settings, pluginSettings) { + if (typeof Drupal.wysiwyg.plugins[plugin] != 'object') { + return; + } + tinymce.create('tinymce.plugins.' + plugin, { + /** + * Initialize the plugin, executed after the plugin has been created. + * + * @param ed + * The tinymce.Editor instance the plugin is initialized in. + * @param url + * The absolute URL of the plugin location. + */ + init: function(ed, url) { + // Register an editor command for this plugin, invoked by the plugin's button. + ed.addCommand(plugin, function() { + if (typeof Drupal.wysiwyg.plugins[plugin].invoke == 'function') { + var data = { format: 'html', node: ed.selection.getNode(), content: ed.selection.getContent() }; + Drupal.wysiwyg.plugins[plugin].invoke(data, pluginSettings, ed.id); + } + }); + + // Register the plugin button. + ed.addButton(plugin, { + title : settings.iconTitle, + cmd : plugin, + image : settings.icon + }); + + // Load custom CSS for editor contents on startup. + ed.onInit.add(function() { + if (settings.css) { + ed.dom.loadCSS(settings.css); + } + }); + + // Attach: Replace plain text with HTML representations. + ed.onBeforeSetContent.add(function(ed, data) { + if (typeof Drupal.wysiwyg.plugins[plugin].attach == 'function') { + data.content = Drupal.wysiwyg.plugins[plugin].attach(data.content, pluginSettings, ed.id); + data.content = Drupal.wysiwyg.editor.instance.tinymce.prepareContent(data.content); + } + }); + + // Detach: Replace HTML representations with plain text. + ed.onGetContent.add(function(ed, data) { + if (typeof Drupal.wysiwyg.plugins[plugin].detach == 'function') { + data.content = Drupal.wysiwyg.plugins[plugin].detach(data.content, pluginSettings, ed.id); + } + }); + + // isNode: Return whether the plugin button should be enabled for the + // current selection. + ed.onNodeChange.add(function(ed, command, node) { + if (typeof Drupal.wysiwyg.plugins[plugin].isNode == 'function') { + command.setActive(plugin, Drupal.wysiwyg.plugins[plugin].isNode(node)); + } + }); + }, + + /** + * Return information about the plugin as a name/value array. + */ + getInfo: function() { + return { + longname: settings.title + }; + } + }); + + // Register plugin. + tinymce.PluginManager.add(plugin, tinymce.plugins[plugin]); + }, + + openDialog: function(dialog, params) { + var editor = tinyMCE.get(this.field); + editor.windowManager.open({ + file: dialog.url + '/' + this.field, + width: dialog.width, + height: dialog.height, + inline: 1 + }, params); + }, + + closeDialog: function(dialog) { + var editor = tinyMCE.get(this.field); + editor.windowManager.close(dialog); + }, + + prepareContent: function(content) { + // Certain content elements need to have additional DOM properties applied + // to prevent this editor from highlighting an internal button in addition + // to the button of a Drupal plugin. + var specialProperties = { + img: { class: 'mceItem' } + }; + $content = $('
' + content + '
'); // No .outerHTML() in jQuery :( + jQuery.each(specialProperties, function(element, properties) { + $content.find(element).each(function() { + for (var property in properties) { + if (property == 'class') { + $(this).addClass(properties[property]); + } + else { + $(this).attr(property, properties[property]); + } + } + }); + }); + return $content.html(); + }, + + insert: function(content) { + content = this.prepareContent(content); + tinyMCE.execInstanceCommand(this.field, 'mceInsertContent', false, content); + } +}; + Index: plugins/break.inc =================================================================== RCS file: plugins/break.inc diff -N plugins/break.inc --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ plugins/break.inc 25 Jan 2009 03:22:35 -0000 @@ -0,0 +1,22 @@ + t('Teaser break'), + 'vendor url' => 'http://drupal.org/project/wysiwyg', + 'icon file' => 'break.gif', + 'icon title' => t('Separate the teaser and body of this content'), + 'settings' => array( + // Path is set by default. + // 'path' => wysiwyg_get_path('plugins/break', TRUE), + ), + ); + return $plugins; +} + Index: plugins/break/break.js =================================================================== RCS file: plugins/break/break.js diff -N plugins/break/break.js --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ plugins/break/break.js 1 Feb 2009 06:43:58 -0000 @@ -0,0 +1,59 @@ +// $Id: editor_plugin.js 201 2008-02-12 15:56:56Z sun $ + +Drupal.wysiwyg.plugins.break = { + + /** + * Return whether the passed node belongs to this plugin. + */ + isNode: function(node) { + return ($(node).is('img.wysiwyg-break')); + }, + + /** + * Execute the button. + */ + invoke: function(data, settings, instanceId) { + if (data.format == 'html') { + // Prevent duplicating a teaser break. + if ($(data.node).is('img.wysiwyg-break')) { + return; + } + var content = this._getPlaceholder(settings); + } + else { + // Prevent duplicating a teaser break. + // @todo data.content is the selection only; needs access to complete content. + if (data.content.match(//)) { + return; + } + var content = ''; + } + if (typeof content != 'undefined') { + Drupal.wysiwyg.instances[instanceId].insert(content); + } + }, + + /** + * Replace all tags with images. + */ + attach: function(content, settings, instanceId) { + content = content.replace(//g, this._getPlaceholder(settings)); + return content; + }, + + /** + * Replace images with tags in content upon detaching editor. + */ + detach: function(content, settings, instanceId) { + $content = $('
' + content + '
'); // No .outerHTML() in jQuery :( + $('img.wysiwyg-break', $content).replaceWith(''); + return $content.html(); + }, + + /** + * Helper function to return a HTML placeholder. + */ + _getPlaceholder: function (settings) { + return '<--break->'; + } +}; Index: plugins/break/editor_plugin.js =================================================================== RCS file: plugins/break/editor_plugin.js diff -N plugins/break/editor_plugin.js --- plugins/break/editor_plugin.js 10 Jun 2008 18:20:14 -0000 1.1 +++ /dev/null 1 Jan 1970 00:00:00 -0000 @@ -1,148 +0,0 @@ -// $Id: editor_plugin.js,v 1.1 2008/06/10 18:20:14 sun Exp $ - -// Import plugin language. -tinyMCE.importPluginLanguagePack('wysiwyg', 'en'); - -var TinyMCE_wysiwygBreakPlugin = { - getInfo: function() { - return { - longname: 'Teaser break', - author: 'Nathan Haug', - authorurl: 'http://www.quicksketch.org', - infourl: 'http://drupal.org/project/wysiwyg' - }; - }, - - initInstance: function(inst) { - tinyMCE.importCSS(inst.getDoc(), this.baseURL + '/break.css'); - }, - - getControlHTML: function (control_name) { - switch (control_name) { - case 'break': - return tinyMCE.getButtonHTML(control_name, 'lang_break_desc', '{$pluginurl}/images/break.gif', 'break', false, 'null'); - } - return ''; - }, - - execCommand: function(editor_id, element, command, user_interface, value) { - switch (command) { - case 'break': - var classValue = ''; - var template = new Array(); - var inst = tinyMCE.getInstanceById(editor_id); - var focusElm = inst.getFocusElement(); - - // Check whether selection is an image and belongs to this plugin. - if (focusElm != null && focusElm.nodeName.toLowerCase() == 'img') { - classValue = this.getAttrib(focusElm, 'class'); - if (classValue != 'wysiwyg-break') { - return true; - } - } - - html = '<--break->'; - tinyMCE.execInstanceCommand(editor_id, 'mceInsertContent', false, html); - return true; - } - // Pass to next handler in chain. - return false; - }, - - cleanup: function(type, content) { - switch (type) { - case 'insert_to_editor': - // Parse all tags and replace them with images. - var startPos = 0; - while ((startPos = content.indexOf('', startPos)) != -1) { - // Insert image. - var contentAfter = content.substring(startPos + 12); - content = content.substring(0, startPos); - content += '<--break->'; - content += contentAfter; - startPos++; - } - break; - - case 'get_from_editor': - // Parse all img tags and replace them with . - var startPos = -1; - while ((startPos = content.indexOf('', startPos); - var attribs = parseAttributes(content.substring(startPos + 4, endPos)); - if (attribs['class'] == 'wysiwyg-break') { - endPos += 2; - chunkBefore = content.substring(0, startPos); - chunkAfter = content.substring(endPos); - content = chunkBefore + '' + chunkAfter; - } - } - break; - } - // Pass through to next handler in chain - return content; - - /** - * Local function that parses the break image in and out. - */ - function parseAttributes (attribute_string) { - var attributeName = '', attributeValue = '', withInName, withInValue; - var attributes = new Array(); - var whiteSpaceRegExp = new RegExp('^[ \n\r\t]+', 'g'); - - if (attribute_string == null || attribute_string.length < 2) { - return null; - } - withInName = withInValue = false; - for (var i = 0; i < attribute_string.length; i++) { - var chr = attribute_string.charAt(i); - if ((chr == '"' || chr == "'") && !withInValue) { - withInValue = true; - } - else if ((chr == '"' || chr == "'") && withInValue) { - withInValue = false; - var pos = attributeName.lastIndexOf(' '); - if (pos != -1) { - attributeName = attributeName.substring(pos+1); - } - attributes[attributeName.toLowerCase()] = attributeValue.substring(1).toLowerCase(); - attributeName = ''; - attributeValue = ''; - } - else if (!whiteSpaceRegExp.test(chr) && !withInName && !withInValue) { - withInName = true; - } - if (chr == '=' && withInName) { - withInName = false; - } - if (withInName) { - attributeName += chr; - } - if (withInValue) { - attributeValue += chr; - } - } - return attributes; - } - }, - - handleNodeChange: function(editor_id, node, undo_index, undo_levels, visual_aid, any_selection) { - tinyMCE.switchClass(editor_id + '_wysiwyg_break', 'mceButtonNormal'); - if (node == null) { - return; - } - do { - if (node.nodeName.toLowerCase() == 'img' && this.getAttrib(node, 'class').indexOf('wysiwyg-break') == 0) { - tinyMCE.switchClass(editor_id + '_wysiwyg_break', 'mceButtonSelected'); - } - } while ((node = node.parentNode)); - return true; - }, - - getAttrib: function(elm, name) { - return elm.getAttribute(name) ? elm.getAttribute(name) : ''; - } -}; - -tinyMCE.addPlugin('wysiwyg', TinyMCE_wysiwygBreakPlugin); - Index: plugins/break/editor_plugin_src.js =================================================================== RCS file: plugins/break/editor_plugin_src.js diff -N plugins/break/editor_plugin_src.js --- plugins/break/editor_plugin_src.js 10 Jun 2008 18:20:14 -0000 1.1 +++ /dev/null 1 Jan 1970 00:00:00 -0000 @@ -1,148 +0,0 @@ -// $Id: editor_plugin_src.js,v 1.1 2008/06/10 18:20:14 sun Exp $ - -// Import plugin language. -tinyMCE.importPluginLanguagePack('wysiwyg', 'en'); - -var TinyMCE_wysiwygBreakPlugin = { - getInfo: function() { - return { - longname: 'Teaser break', - author: 'Nathan Haug', - authorurl: 'http://www.quicksketch.org', - infourl: 'http://drupal.org/project/wysiwyg' - }; - }, - - initInstance: function(inst) { - tinyMCE.importCSS(inst.getDoc(), this.baseURL + '/break.css'); - }, - - getControlHTML: function (control_name) { - switch (control_name) { - case 'break': - return tinyMCE.getButtonHTML(control_name, 'lang_break_desc', '{$pluginurl}/images/break.gif', 'break', false, 'null'); - } - return ''; - }, - - execCommand: function(editor_id, element, command, user_interface, value) { - switch (command) { - case 'break': - var classValue = ''; - var template = new Array(); - var inst = tinyMCE.getInstanceById(editor_id); - var focusElm = inst.getFocusElement(); - - // Check whether selection is an image and belongs to this plugin. - if (focusElm != null && focusElm.nodeName.toLowerCase() == 'img') { - classValue = this.getAttrib(focusElm, 'class'); - if (classValue != 'wysiwyg-break') { - return true; - } - } - - html = '<--break->'; - tinyMCE.execInstanceCommand(editor_id, 'mceInsertContent', false, html); - return true; - } - // Pass to next handler in chain. - return false; - }, - - cleanup: function(type, content) { - switch (type) { - case 'insert_to_editor': - // Parse all tags and replace them with images. - var startPos = 0; - while ((startPos = content.indexOf('', startPos)) != -1) { - // Insert image. - var contentAfter = content.substring(startPos + 12); - content = content.substring(0, startPos); - content += '<--break->'; - content += contentAfter; - startPos++; - } - break; - - case 'get_from_editor': - // Parse all img tags and replace them with . - var startPos = -1; - while ((startPos = content.indexOf('', startPos); - var attribs = parseAttributes(content.substring(startPos + 4, endPos)); - if (attribs['class'] == 'wysiwyg-break') { - endPos += 2; - chunkBefore = content.substring(0, startPos); - chunkAfter = content.substring(endPos); - content = chunkBefore + '' + chunkAfter; - } - } - break; - } - // Pass through to next handler in chain - return content; - - /** - * Local function that parses the break image in and out. - */ - function parseAttributes (attribute_string) { - var attributeName = '', attributeValue = '', withInName, withInValue; - var attributes = new Array(); - var whiteSpaceRegExp = new RegExp('^[ \n\r\t]+', 'g'); - - if (attribute_string == null || attribute_string.length < 2) { - return null; - } - withInName = withInValue = false; - for (var i = 0; i < attribute_string.length; i++) { - var chr = attribute_string.charAt(i); - if ((chr == '"' || chr == "'") && !withInValue) { - withInValue = true; - } - else if ((chr == '"' || chr == "'") && withInValue) { - withInValue = false; - var pos = attributeName.lastIndexOf(' '); - if (pos != -1) { - attributeName = attributeName.substring(pos+1); - } - attributes[attributeName.toLowerCase()] = attributeValue.substring(1).toLowerCase(); - attributeName = ''; - attributeValue = ''; - } - else if (!whiteSpaceRegExp.test(chr) && !withInName && !withInValue) { - withInName = true; - } - if (chr == '=' && withInName) { - withInName = false; - } - if (withInName) { - attributeName += chr; - } - if (withInValue) { - attributeValue += chr; - } - } - return attributes; - } - }, - - handleNodeChange: function(editor_id, node, undo_index, undo_levels, visual_aid, any_selection) { - tinyMCE.switchClass(editor_id + '_wysiwyg_break', 'mceButtonNormal'); - if (node == null) { - return; - } - do { - if (node.nodeName.toLowerCase() == 'img' && this.getAttrib(node, 'class').indexOf('wysiwyg-break') == 0) { - tinyMCE.switchClass(editor_id + '_wysiwyg_break', 'mceButtonSelected'); - } - } while ((node = node.parentNode)); - return true; - }, - - getAttrib: function(elm, name) { - return elm.getAttribute(name) ? elm.getAttribute(name) : ''; - } -}; - -tinyMCE.addPlugin('wysiwyg', TinyMCE_wysiwygBreakPlugin); -