diff --git a/README.txt b/README.txt index fc963d7..302d7da 100644 --- a/README.txt +++ b/README.txt @@ -23,7 +23,7 @@ While entering content, choose an input format that utilizes the caption filter. In a plain textarea, the entered code should look like this: A simple caption: -[caption]This is an image caption[/caption] +[caption caption="This is an image caption"][/caption] Aligned Caption (can be left, center or right): -[caption align=right]This is a right aligned caption[/caption] +[caption caption="This is a right aligned caption" align="right"][/caption] diff --git a/caption-filter.css b/caption-filter.css index 4fc991a..0b4a9ca 100644 --- a/caption-filter.css +++ b/caption-filter.css @@ -18,7 +18,7 @@ div.caption-inner { width: auto; } -div.caption p { +div.caption p.caption-text { margin: .25em 0; } diff --git a/caption_filter.module b/caption_filter.module index 783bf8e..45c984e 100644 --- a/caption_filter.module +++ b/caption_filter.module @@ -40,12 +40,12 @@ function caption_filter_tips($filter, $format, $long = FALSE) {

You may wrap images or embeds with a caption using the code [caption]IMAGE caption[/caption].

Examples:

'); } else { - return check_plain(t('Captions may be specified with [caption]Image caption[/caption]. Items can be aligned with [caption align=left].')); + return check_plain(t('Captions may be specified with [caption caption="Image caption"][/caption]. Items can be aligned with [caption align="left"].')); } } @@ -68,12 +68,33 @@ function caption_filter_process_filter($text, $filter) { * HTML output. */ function _caption_filter_replace($matches) { - $caption_attributes = _caption_filter_tag_attributes($matches[2]); + $caption_attributes_filtered = str_replace('\"', '"', $matches[2]); + $caption_attributes = _caption_filter_tag_attributes($caption_attributes_filtered); $item = $matches[3]; $width = _caption_filter_get_width($item); $align = isset($caption_attributes['align']) ? $caption_attributes['align'] : 'center'; - $caption = isset($caption_attributes['caption']) ? $caption_attributes['caption'] : ''; + + // Determine the caption value from the formal attributes of [caption] + $caption = (isset($caption_attributes['caption'])) ? $caption_attributes['caption'] : ''; + + // Legacy fallback - If there is no formal attribute, try to infer from the HTML + // and then assign the caption to $caption and the $item to the remainder + if (empty($caption)) { + if (preg_match('/(.*)/i', $item, $legacy_img) && !empty($legacy_img[1])) { + $caption = $legacy_img[1]; + } else if (preg_match('/(.*)/i', $item, $legacy_iframe) && !empty($legacy_iframe[1])) { + $caption = $legacy_iframe[1]; + } else if (preg_match('/(.*)/i', $item, $legacy_embed) && !empty($legacy_embed[1])) { + $caption = $legacy_embed[1]; + } else if (preg_match('/(.*)/i', $item, $legacy_obejct) && !empty($legacy_obejct[1])) { + $caption = $legacy_obejct[1]; + } + } + + // Determine the content value. This is any HTML content that is inside the [caption] that isn't + // part of the actual caption text + $content = str_replace($caption, '', $item); // Remove "align" from the start of the alignment if needed. WordPress // commonly uses align="alignright" for example. @@ -81,7 +102,13 @@ function _caption_filter_replace($matches) { $align = substr($align, 5); } - return '
'. $item . $caption .'
'; + // If we have a caption, output markup to wrap the caption in a specific tag and class + if (!empty($caption)) { + return '
'. $content . '

' . $caption .'

'; + } + else { + return '
'. $content . '
'; + } } /** @@ -338,3 +365,88 @@ function caption_filter_field_widget_caption_validate($element, &$form_state) { form_error($element, t('The Title field must be enabled to use it as a caption.')); } } + +/** + * Implements hook_menu(). + */ +function caption_filter_menu() { + // Dialog callback for the TinyMCE button. + $items['caption_filter/tinymce'] = array( + 'title' => 'Image caption', + 'page callback' => 'caption_filter_tinymce_button', + 'access arguments' => array('access content'), + 'type' => MENU_CALLBACK, + ); + return $items; +} + +/** + * Menu page callback; the TinyMCE button dialog. + */ +function caption_filter_tinymce_button() { + // Suppress the admin menu in the popup. + module_invoke('admin_menu', 'suppress'); + if (module_exists('wysiwyg') && $editor = wysiwyg_get_editor('tinymce')) { + drupal_add_js($editor['library path'] . '/tiny_mce_popup.js'); + } + drupal_add_js(drupal_get_path('module', 'caption_filter') . '/js/caption-filter-tinymce-button.js'); + drupal_add_js(drupal_get_path('module', 'caption_filter') . '/js/caption-filter.js'); + + $form = drupal_get_form('caption_filter_tinymce_button_form'); + $output = theme('caption_filter_tinymce_button', array('form' => $form)); + + // Write directly to the window and quit rather than returning so the modal + // doesn't get themed as a Drupal page. + echo $output; + exit; +} + +/** + * Form builder; the TinyMCE button dialog. + */ +function caption_filter_tinymce_button_form() { + $form['#action'] = '#'; + $form['#attributes'] = array('onsubmit' => 'CaptionFilterButton.insert(); return false;'); + $form['caption'] = array( + '#title' => t('Caption'), + '#type' => 'textarea', + '#rows' => 4, + '#default_value' => '', + '#attributes' => array('class' => array('field', 'mceFocus')), + ); + $form['align'] = array( + '#title' => t('Float'), + '#type' => 'select', + '#options' => array( + 0 => t('None'), + 'left' => t('Left'), + 'right' => t('Right'), + ), + '#default_value' => 0, + '#attributes' => array('class' => array('field')), + ); + $form['actions'] = array( + '#type' => 'container', + '#attributes' => array('class' => array('mceActionPanel')), + ); + $form['actions']['insert'] = array( + '#markup' => '', + ); + $form['actions']['cancel'] = array( + '#markup' => '', + ); + return $form; +} + +/** + * Implements hook_theme(). + */ +function caption_filter_theme($existing, $type, $theme, $path) { + return array( + 'caption_filter_tinymce_button' => array( + 'variables' => array('form' => array()), + 'path' => drupal_get_path('module', 'caption_filter') . '/js', + 'template' => 'caption-filter-tinymce-button', + ), + ); +} diff --git a/js/caption-filter-tinymce-button.js b/js/caption-filter-tinymce-button.js new file mode 100644 index 0000000..2e32490 --- /dev/null +++ b/js/caption-filter-tinymce-button.js @@ -0,0 +1,95 @@ +/** + * @file + * Functionality for implementing the caption filter button in tinyMCE. + */ +var CaptionFilterButton = { + init : function() { + var ed = tinyMCEPopup.editor; + var node = ed.selection.getNode(); + var p = ed.dom.getParents(node, 'DIV'); + var f = document.forms[0]; + + // Only pre-populate values if we're inside an existing caption. + if (p[0] && ed.dom.hasClass(p[0], 'caption-inner') + && p[2] && ed.dom.hasClass(p[2], 'caption')) { + // Parse the entire caption block to get the original [caption] tag. + var tag = Drupal.captionFilter.toTag(p[2].outerHTML); + + // Filter the tag elements to have " elements + tag = tag.replace(/\\"/g, '"'); + + // Extract the caption from the [caption] attribute first, caption-text class second, raw text third. + // fails default to the HTML. + var caption_attribute = tag.match(/^\[caption.*caption=\"([^"]*)\".*\].*\[\/caption\]$/); + var caption_wrapper = tag.match(/^\[caption.*\]\

(.*)<\/p>\[\/caption\]$/); + var caption_html = tag.match(/^\[caption.*\]\(.*)\[\/caption\]$/); + if (caption_attribute && caption_attribute[1]) { + f.caption.value = caption_attribute[1]; + } + else if (caption_wrapper && caption_wrapper[1]) { + f.caption.value = caption_wrapper[1]; + } + else if (caption_html && caption_html[1]) { + f.caption.value = caption_html[1]; + } + + // Filter the caption value to replace double quotes + f.caption.value = f.caption.value.replace(/\"/g, '"'); + + // Extract the alignment. + var align = tag.match(/^\[caption.*align=\"([^"]*)\".*\].*\[\/caption\]$/); + if (align && align[1]) { + f.align.value = align[1]; + } + } + }, + + insert : function() { + var ed = tinyMCEPopup.editor; + var node = ed.selection.getNode(); + var p = ed.dom.getParents(node, 'DIV'); + var align = document.forms[0].align.value; + var caption = document.forms[0].caption.value; + var image; + var tag; + var replace = false; + // If we're inside an existing caption... + if (p[0] && ed.dom.hasClass(p[0], 'caption-inner') + && p[2] && ed.dom.hasClass(p[2], 'caption')) { + replace = true; + // Recall the original [caption] tag. + tag = Drupal.captionFilter.toTag(p[2].outerHTML); + // Select the outer DIV so we can replace the entire thing. + ed.selection.select(p[2]); + // If we're in an existing caption, parse it from the [caption] tag. + var parse = tag.match(/^\[caption.*\](\)(.*)\[\/caption\]$/); + if (parse[1]) { + image = parse[1]; + } + } + // Get the image HTML. + else if (node.nodeName === 'IMG') { + // If we're on the image, just use it. + image = node.outerHTML; + } + var newtag = '[caption'; + if (align == 'right' || align == 'left') { + newtag += ' align="' + align + '"'; + } + if (caption) { + newtag += ' caption="' + caption.replace(/"/g, '\\"') + '"'; + } + newtag += ']' + image + '[/caption]'; + + // Create the new [caption] tag. + if (replace === true){ + ed.dom.remove(p[2], false); + ed.execCommand('mceReplaceContent', false, newtag); + } else { + ed.execCommand('mceReplaceContent', false, newtag); + } + tinyMCEPopup.close(); + } +}; + +tinyMCEPopup.onInit.add(CaptionFilterButton.init, CaptionFilterButton); diff --git a/js/caption-filter-tinymce-button.tpl.php b/js/caption-filter-tinymce-button.tpl.php new file mode 100644 index 0000000..7241414 --- /dev/null +++ b/js/caption-filter-tinymce-button.tpl.php @@ -0,0 +1,20 @@ + + + + +<?php print drupal_get_title(); ?> + + + + + + diff --git a/js/caption-filter-tinymce.js b/js/caption-filter-tinymce.js index 8e26f7b..d266c09 100644 --- a/js/caption-filter-tinymce.js +++ b/js/caption-filter-tinymce.js @@ -9,12 +9,48 @@ * @see http://core.svn.wordpress.org/branches/3.2/wp-admin/js/editor.dev.js */ (function() { + // Load the plugin-specific language pack. + tinymce.PluginManager.requireLangPack('captionfilter'); + tinymce.create('tinymce.plugins.CaptionFilter', { init : function(ed, url) { var t = this; t.url = url; + // Register the command. + ed.addCommand('CaptionFilter', function() { + ed.windowManager.open({ + file : Drupal.settings.basePath + 'index.php?q=caption_filter/tinymce', + width : 400 + parseInt(ed.getLang('captionfilter.delta_width', 0)), + height : 200 + parseInt(ed.getLang('captionfilter.delta_height', 0)), + inline : 1 + }, { + plugin_url : url + }); + }); + + // Register the button. + ed.addButton('captionfilter', { + title : 'captionfilter.desc', + cmd : 'CaptionFilter', + image : url + '/caption-filter.gif' + }); + + // Add a handler to activate/deactivate the button. + ed.onNodeChange.add(function(ed, command, node) { + var p = ed.dom.getParent(node, 'DIV'); + var selection = ed.selection.getContent(); + + // Enable if an image is selected, or if inside an existing caption. + command.setDisabled('captionfilter', + !(node.nodeName === 'IMG' && selection) && + !(p && ed.dom.hasClass(p, 'caption-inner')) + ); + // Light up the button if inside an existing caption. + command.setActive('captionfilter', p && ed.dom.hasClass(p, 'caption-inner')); + }); + function _do_filter(ed, o) { o.content = Drupal.captionFilter.toHTML(o.content, 'tinymce'); }; @@ -30,10 +66,6 @@ // Resize the caption wrapper when the image is soft-resized by the user. ed.onMouseUp.add(function(ed, e) { - // Webkit (Safari/Chrome) and Opera currently don't have resize handles. - if (tinymce.isWebKit || tinymce.isOpera) - return; - var img = ed.selection.getNode(); if (img.nodeName == 'IMG') { window.setTimeout(function(){ @@ -107,7 +139,7 @@ align = cmd.substr(7).toLowerCase(); wrapperClass = 'caption-' + align; ed.dom.addClass(captionWrapper, wrapperClass); - + if (align == 'center') ed.dom.addClass(captionWrapper, 'mceIEcenter'); else diff --git a/js/caption-filter.gif b/js/caption-filter.gif new file mode 100644 index 0000000000000000000000000000000000000000..9bcffe66b0a0907efebcc88618b487bb8ec8a63e GIT binary patch literal 122 zcmZ?wbhEHb6k!lyIK;*P1Pu)h2M!!Ke*E~shu=Sa`t%

' + c + '
'; + if (ct) { + return '
' + c + '

' + ct + '

'; + } + else { + return '
' + c + '
'; + } }); }; @@ -44,8 +51,12 @@ Drupal.captionFilter.toTag = function(co) { var align; align = captionWrapper.match(/class=.*?caption-(left|center|right)/i); align = (align && align[1]) ? align[1] : ''; + caption = contents.match(/\

(.*)\<\/p\>/); + caption_html = (caption && caption[0]) ? caption[0] : ''; + caption = (caption && caption[1]) ? caption[1].replace(/"/g, '\\"') : ''; + contents = contents.replace(caption_html, ''); - return '[caption' + (align ? (' align="' + align + '"') : '') + ']' + contents + '[/caption]'; + return '[caption' + (caption ? (' caption="' + caption + '"') : '') + (align ? (' align="' + align + '"') : '') + ']' + contents + '[/caption]'; }); }; diff --git a/js/langs/en.js b/js/langs/en.js new file mode 100644 index 0000000..f686d2c --- /dev/null +++ b/js/langs/en.js @@ -0,0 +1,3 @@ +tinyMCE.addI18n('en.captionfilter', { + desc : 'Add a caption to an image', +}); diff --git a/js/langs/fr.js b/js/langs/fr.js new file mode 100644 index 0000000..39090d6 --- /dev/null +++ b/js/langs/fr.js @@ -0,0 +1,3 @@ +tinyMCE.addI18n('fr.captionfilter', { + desc : 'Ajouter une légende à une image', +});