diff --git a/editors/js/jwysiwyg.js b/editors/js/jwysiwyg.js index d3e7490..2841d46 100644 --- a/editors/js/jwysiwyg.js +++ b/editors/js/jwysiwyg.js @@ -2,10 +2,126 @@ /** * Attach this editor to a target element. + * + * See Drupal.wysiwyg.editor.attach.none() for a full desciption of this hook. */ Drupal.wysiwyg.editor.attach.jwysiwyg = function(context, params, settings) { + var $field = $('#' + params.field); + var css = []; + + // Setup editor buttons for Drupal plugins. + var buttonSettings = {}; + for (var buttonName in settings.buttons) { + buttonSettings[buttonName] = { + visible: settings.buttons[buttonName] + } + + if (Drupal.settings.wysiwyg.plugins.drupal) { + if ((settings.buttons[buttonName]) && (Drupal.settings.wysiwyg.plugins.drupal[buttonName])) { + // This is a Drupal plugin and needs a custom function. + var pluginSettings = Drupal.settings.wysiwyg.plugins[params.format].drupal[buttonName]; + if (pluginSettings.css) { + css.push(pluginSettings.css); + } + buttonSettings[buttonName].custom = true; + buttonSettings[buttonName].tooltip = pluginSettings.title; + buttonSettings[buttonName].icon = pluginSettings.icon; + buttonSettings[buttonName].exec = function(fieldName, buttonName) { + // Use a closure to store the value of fieldName and buttonName. + return function() { + var $field = $('#' + fieldName); + if (typeof Drupal.wysiwyg.plugins[buttonName].invoke == 'function') { + var element = $field.data('wysiwyg').editor[0]; + var selectedRange = Drupal.wysiwyg.editor.instance.jwysiwyg.getSelectedRange(element.contentWindow); + var selectedNode = (selectedRange.commonAncestorContainer ? selectedRange.commonAncestorContainer : selectedRange.parentElement()); + var data = { format: 'html', node: selectedNode, content : $field.data('wysiwyg').getContent() }; + Drupal.wysiwyg.plugins[buttonName].invoke(data, Drupal.settings.wysiwyg.plugins.drupal[buttonName], params.field); + } + }; + }(params.field, buttonName); + } + } + } + delete settings.buttons; + settings.controls = buttonSettings; + + if (settings.content_css) { + for (var file in settings.content_css) { + css.push(settings.content_css[file]); + } + } + delete settings.content_css; + + // jWysiwyg only supports a single css file, so we'll change the template. + var htmlTemplate = '<' + '?xml version="1.0" encoding="UTF-8"?' + '>' + + '' + + '' + + '' + + '' + + 'INITIAL_CONTENT'; + settings.html = htmlTemplate; + + // Attach editor. + $field.wysiwyg(settings); + + var editor = $field.data('wysiwyg'); + + // Inject proxy methods to handle plugins attaching/detaching plugins and + // activating their buttons. + var pluginSettings = Drupal.settings.wysiwyg.plugins; + var pluginInstances = Drupal.wysiwyg.plugins; + + // Use the jWysiwyg editor area document as key for the field name. + $(editor.editorDoc).data('wysiwyg_field', params.field); + $(editor.editorDoc).focus(function (){ + Drupal.wysiwyg.activeId = $(this).data('wysiwyg_field'); + }); + + var oldGetContent = editor.getContent; + editor.getContent = function() { + var content = oldGetContent.call(this); + for (var plugin in pluginSettings[params.format].drupal) if (typeof pluginInstances[plugin].detach == 'function') { + content = pluginInstances[plugin].detach(content, pluginSettings[plugin], params.field); + } + return content; + }; + + var oldSetContent = editor.setContent; + editor.setContent = function(newContent) { + for (var plugin in Drupal.settings.wysiwyg.plugins[params.format].drupal) if (typeof pluginInstances[plugin].attach == 'function') { + newContent = pluginInstances[plugin].attach(newContent, pluginSettings.drupal[plugin], params.field); + } + oldSetContent.call(this, newContent); + }; + + var oldCheckTargets = editor.checkTargets; + editor.checkTargets = function(element) { + var foundTarget = false; + for (var plugin in pluginSettings[params.format].drupal) { + if (typeof pluginInstances[plugin].isNode == 'function') { + if (pluginInstances[plugin].isNode(element)) { + foundTarget = true; + break; + } + } + } + + if (!foundTarget) { + oldCheckTargets.call(this, element); + } + else { + // Deactivate all buttons by passing the body element. + oldCheckTargets.call(this, element.ownerDocument.body); + // Activate the Drupal plugin button. + $('.' + plugin, this.panel).addClass('active'); + } + }; + // Because we could not proxy the setContent method before it was defined + // we need to run it again to allow Drupal plugins to attach themselves. + //editor.setContent(editor.getContent()); + // Attach editor. - $('#' + params.field).wysiwyg(); + //$('#' + params.field).wysiwyg(); }; /** @@ -27,16 +143,58 @@ Drupal.wysiwyg.editor.detach.jwysiwyg = function (context, params, trigger) { }; Drupal.wysiwyg.editor.instance.jwysiwyg = { - insert: function (content) { - $('#' + this.field).wysiwyg('insertHtml', content); + openDialog: function(dialog, params) { + alert("Drupal plugin dialogs are not yet supported by jWysiwyg."); + }, + + closeDialog: function(dialog) { + }, + + getSelection: function(targetWindow) { + return targetWindow.getSelection ? targetWindow.getSelection() : targetWindow.document.selection.createRange(); }, - setContent: function (content) { - $('#' + this.field).wysiwyg('setContent', content); + getSelectedRange: function(targetWindow) { + var selection = this.getSelection(targetWindow); + if (selection.getRangeAt) { + return selection.getRangeAt(0); + } + // Safari. + else { + var range = document.createRange(); + range.setStart(selection.anchorNode,selection.anchorOffset); + range.setEnd(selection.focusNode,selection.focusOffset); + return range; + } }, - getContent: function () { - return $('#' + this.field).wysiwyg('getContent'); + /** + * Insert content at the current caret position. + */ + insert: function(content) { + var $field = $('#' + this.field); + var editor = $field.data('wysiwyg'); + var editorWindow = editor.editor[0].contentWindow; + + // jWysiwyg does not provide an insert method. + // Using simplified code from QuirksMode.org + var selectionRange = this.getSelectedRange(editorWindow); + if (selectionRange.insertNode) { + // Delete and insert new node. + selectionRange.deleteContents(); + selectionRange.insertNode(selectionRange.createContextualFragment(content)); + } + else if (editor.selectionStart || editor.selectionStart == '0') { + if (selectionRange.item) { + // Delete content and get caret text selection. + editor.editorDoc.execCommand('Delete', false, null); + selectionRange = this.getSelectedRange(editorWindow); + } + selectionRange.pasteHTML(content); + } + else { + editor.setContent(editor.getContent() + content); + } } }; diff --git a/editors/jwysiwyg.inc b/editors/jwysiwyg.inc index 0f842db..008b584 100644 --- a/editors/jwysiwyg.inc +++ b/editors/jwysiwyg.inc @@ -16,10 +16,23 @@ function wysiwyg_jwysiwyg_editor() { 'libraries' => array( '' => array( 'title' => 'Source', - 'files' => array('jquery.wysiwyg.js'), + 'files' => array( + 'jquery.wysiwyg.js', + ), ), ), 'version callback' => 'wysiwyg_jwysiwyg_version', + 'settings callback' => 'wysiwyg_jwysiwyg_settings', + 'settings form callback' => 'wysiwyg_jwysiwyg_settings_form', + 'plugin callback' => 'wysiwyg_jwysiwyg_plugins', + 'plugin settings callback' => 'wysiwyg_jwysiwyg_plugin_settings', + 'proxy plugin' => array( + 'drupal' => array( + 'load' => TRUE, + 'proxy' => TRUE, + ), + ), + 'proxy plugin settings callback' => 'wysiwyg_jwysiwyg_proxy_plugin_settings', // @todo Wrong property; add separate properties for editor requisites. 'css path' => wysiwyg_get_path('jwysiwyg'), 'versions' => array( @@ -56,3 +69,198 @@ function wysiwyg_jwysiwyg_version($editor) { fclose($script); } +/** + * Implements wysiwyg_EDITOR_settings_form(). + */ +function wysiwyg_jwysiwyg_settings_form(&$form, &$form_state) { + $profile = $form_state['wysiwyg_profile']; + $form['jwysiwyg'] = array( + '#type' => 'fieldset', + '#title' => 'Editor specific', + '#group' => 'advanced', + ); + $form['jwysiwyg']['jqueryui_dialog'] = array( + '#type' => 'checkbox', + '#title' => 'Jquery UI dialogs', + '#default_value' => isset($profile->settings['jqueryui_dialog'])?$profile->settings['jqueryui_dialog']:0, + '#description' => t("Use Jquery UI dialogs instead of jWYSIWYG's native ones."), + ); +} + +/** + * Return runtime editor settings for a given wysiwyg profile. + * + * @param $editor + * A processed hook_editor() array of editor properties. + * @param $config + * An array containing wysiwyg editor profile settings. + * @param $theme + * The name of a theme/GUI/skin to use. + * + * @return + * A settings array to be populated in + * Drupal.settings.wysiwyg.configs.{editor} + * + * @see wysiwyg_jwysiwyg_settings_form(). + */ +function wysiwyg_jwysiwyg_settings($editor, $config, $theme) { + // Global settings, overridable by plugins. + + // @todo identify more editor settings and expose them in the UI. + $settings = array( + 'debug' => FALSE, + 'rmUnwantedBr' => TRUE, + 'brIE' => TRUE, + 'messages' => array( + 'nonSelection' => FAlSE, + ), + 'initialContent' => '', + ); + + if ($config['resizing']) { + $settings['resizeOptions'] = array(); + drupal_add_library('system', 'ui.resizable'); + } + if ($config['jqueryui_dialog']) { + $settings['dialog'] = 'jqueryui'; + drupal_add_library('system', 'ui.dialog'); + }; + + // Reset editor default buttons and enable selected ones. + $settings['buttons'] = array(); + $plugins = wysiwyg_get_plugins($editor['name']); + foreach ($plugins as $plugin => $meta) { + if (isset($config['buttons'][$plugin])) { + $settings['buttons'] += $config['buttons'][$plugin]; + } + $settings['buttons'] += array_fill_keys(array_keys($meta['buttons']), FALSE); + + // Load js files needed by plugins if any of its buttons is enabled. + // @todo those files should be loaded with ajax from jwysiwyg.js. Alternatively wysiwyg.module should provide a mechanism similar to the one used for Drupal plugins? + if (!empty($config['buttons'][$plugin]) && empty($meta['internal'])) { + drupal_add_js($meta['path'] . '/' . $meta['filename']); + } + + // Allow plugins to add or override global configuration settings. + if (!empty($meta['options'])) { + $settings = array_merge($settings, $meta['options']); + } + } + + // Add editor content stylesheet. + if (isset($config['css_setting'])) { + if ($config['css_setting'] == 'theme') { + $settings['content_css'] = wysiwyg_get_css(); + } + else if ($config['css_setting'] == 'self' && isset($config['css_path'])) { + $settings['content_css'] = strtr($config['css_path'], array('%b' => base_path(), '%t' => path_to_theme())); + } + } + + return $settings; +} + +/** + * Build a JS settings array of native external plugins that need to be loaded separately. + */ +function wysiwyg_jwysiwyg_plugin_settings($editor, $profile, $plugins) { + $settings = array(); + return $settings; +} + +/** + * Build a JS settings array for Drupal plugins loaded via the proxy plugin. + */ +function wysiwyg_jwysiwyg_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; +} + +/** + * Return internal plugins for jWYSIWYG; semi-implementation of hook_wysiwyg_plugin(). + */ +function wysiwyg_jwysiwyg_plugins($editor) { + return array( + 'default' => array( + 'buttons' => array( + 'bold' => t('Bold'), + 'italic' => t('Italic'), + 'underline' => t('Underline'), + 'strikeThrough' => t('Strikethrough'), + 'justifyLeft' => t('Align left'), + 'justifyCenter' => t('Align center'), + 'justifyRight' => t('Align right'), + 'justifyFull' => t('Justify'), + 'indent' => t('Indent'), + 'outdent' => t('Outdent'), + 'subscript' => t('Subscript'), + 'superscript' => t('Superscript'), + 'undo' => t('Undo'), + 'redo' => t('Redo'), + 'insertUnorderedList' => t('Bullet list'), + 'insertOrderedList' => t('Numbered list'), + 'insertHorizontalRule' => t('Horizontal rule'), + 'unLink' => t('Remove link'), + 'h1' => t('Header 1'), + 'h2' => t('Header 2'), + 'h3' => t('Header 3'), + 'paragraph' => t('Paragraph'), + 'cut' => t('Cut'), + 'copy' => t('Copy'), + 'paste' => t('Paste'), + 'increaseFontSize' => t('Increase font size'), + 'decreaseFontSize' => t('Decrease font size'), + 'removeFormat' => t('Remove formatting'), + 'rtl' => t('Right to left'), + 'ltr' => t('Left to right'), + 'html' => t('Source code'), + 'highlight' => t('Highlight'), + 'code' => t('Code snippet'), + ), + 'internal' => TRUE, + ), + 'cssWrap' => array( + 'path' => $editor['library path'] . '/controls', + 'filename' => 'wysiwyg.cssWrap.js', + 'buttons' => array( + 'cssWrap' => t('CSS Wrapper'), + ), + 'load' => TRUE, + ), + 'createLink' => array( + 'path' => $editor['library path'] . '/controls', + 'filename' => 'wysiwyg.link.js', + 'buttons' => array( + 'createLink' => t('Link'), + ), + 'load' => TRUE, + ), + 'insertImage' => array( + 'path' => $editor['library path'] . '/controls', + 'filename' => 'wysiwyg.image.js', + 'buttons' => array( + 'insertImage' => t('Insert image'), + ), + 'load' => TRUE, + ), + 'insertTable' => array( + 'path' => $editor['library path'] . '/controls', + 'filename' => 'wysiwyg.table.js', + 'buttons' => array( + 'insertTable' => t('Insert table'), + ), + 'load' => TRUE, + ), + ); +}