From dcbb5fa3f4434be266793f6a610071a14c777414 Mon Sep 17 00:00:00 2001 From: Dries Buytaert Date: Mon, 9 Apr 2007 13:58:03 +0000 Subject: [PATCH] - Patch #107061 by Steven et al: add jQuery teaser splitter. --- CHANGELOG.txt | 1 + includes/form.inc | 12 ++++++ misc/drupal.js | 20 +++++++++ misc/teaser.js | 80 ++++++++++++++++++++++++++++++++++++ misc/textarea.js | 6 +++ modules/block/block.module | 6 +-- modules/blog/blog.module | 3 +- modules/book/book.module | 9 +--- modules/forum/forum.module | 3 +- modules/node/node.module | 84 ++++++++++++++++++++++++++++++++++---- modules/system/system.css | 25 ++++++++++++ 11 files changed, 226 insertions(+), 23 deletions(-) create mode 100644 misc/teaser.js diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 2d4fe6bdb54..45c10ae96d1 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -8,6 +8,7 @@ Drupal 6.0, xxxx-xx-xx (development version) - Drupal works with error reporting set to E_ALL. - Added scripts/drupal.sh to execute Drupal code from the command line. Useful to use Drupal as a framework to build command-line tools. - Used the Garland theme for the installation and maintenance pages. +- Improved handling of teasers in posts. - Added generic language management functionality. * Support for right to left scripts. * Language detection based on parts of the URL. diff --git a/includes/form.inc b/includes/form.inc index 0ce8b3d3af1..1845f4d9a19 100644 --- a/includes/form.inc +++ b/includes/form.inc @@ -1452,6 +1452,18 @@ function theme_form($element) { */ function theme_textarea($element) { $class = array('form-textarea'); + + // Add teaser behaviour (must come before resizable) + if (!empty($element['#teaser'])) { + drupal_add_js('misc/teaser.js'); + // Note: arrays are merged in drupal_get_js(). + drupal_add_js(array('teaserButton' => array(t('Join summary'), t('Split summary at cursor'))), 'setting'); + drupal_add_js(array('teaserCheckbox' => array($element['#id'] => $element['#teaser_checkbox'])), 'setting'); + drupal_add_js(array('teaser' => array($element['#id'] => $element['#teaser'])), 'setting'); + $class[] = 'teaser'; + } + + // Add resizable behaviour if ($element['#resizable'] !== FALSE) { drupal_add_js('misc/textarea.js'); $class[] = 'resizable'; diff --git a/misc/drupal.js b/misc/drupal.js index b4130e4ade9..04176089dd8 100644 --- a/misc/drupal.js +++ b/misc/drupal.js @@ -200,6 +200,26 @@ Drupal.encodeURIComponent = function (item, uri) { return uri.indexOf('?q=') ? item : item.replace('%26', '%2526').replace('%23', '%2523'); }; +/** + * Get the text selection in a textarea. + */ +Drupal.getSelection = function (element) { + if (typeof(element.selectionStart) != 'number' && document.selection) { + // The current selection + var range1 = document.selection.createRange(); + var range2 = range1.duplicate(); + // Select all text. + range2.moveToElementText(element); + // Now move 'dummy' end point to end point of original range. + range2.setEndPoint('EndToEnd', range1); + // Now we can calculate start and end points. + var start = range2.text.length - range1.text.length; + var end = start + range1.text.length; + return { 'start': start, 'end': end }; + } + return { 'start': element.selectionStart, 'end': element.selectionEnd }; +} + // Global Killswitch on the element if (Drupal.jsEnabled) { document.documentElement.className = 'js'; diff --git a/misc/teaser.js b/misc/teaser.js new file mode 100644 index 00000000000..12275fc397e --- /dev/null +++ b/misc/teaser.js @@ -0,0 +1,80 @@ +// $Id$ + +/** + * Auto-attach for teaser behaviour. + * + * Note: depends on resizable textareas. + */ +Drupal.teaserAttach = function() { + $('textarea.teaser:not(.joined)').each(function() { + var teaser = $(this).addClass('joined'); + + // Move teaser textarea before body, and remove its form-item wrapper. + var body = $('#'+ Drupal.settings.teaser[this.id]); + var checkbox = $('#'+ Drupal.settings.teaserCheckbox[this.id]).parent(); + var parent = teaser[0].parentNode; + $(body).before(teaser); + $(parent).remove(); + + function trim(text) { + return text.replace(/^\s+/g, '').replace(/\s+$/g, ''); + } + + // Join the teaser back to the body. + function join_teaser() { + if (teaser.val()) { + body.val(trim(teaser.val()) +'\r\n\r\n'+ trim(body.val())); + } + // Hide and disable teaser + $(teaser).attr('disabled', 'disabled'); + $(teaser).parent().slideUp('fast'); + // Change label + $(this).val(Drupal.settings.teaserButton[1]); + // Show separate teaser checkbox + $(checkbox).hide(); + } + + // Split the teaser from the body. + function split_teaser() { + body[0].focus(); + var selection = Drupal.getSelection(body[0]); + var split = selection.start; + var text = body.val(); + + // Note: using val() fails sometimes. jQuery bug? + teaser[0].value = trim(text.slice(0, split)); + body[0].value = trim(text.slice(split)); + // Reveal and enable teaser + $(teaser).attr('disabled', ''); + $(teaser).parent().slideDown('fast'); + // Change label + $(this).val(Drupal.settings.teaserButton[0]); + // Show separate teaser checkbox + $(checkbox).show(); + } + + // Add split/join button. + var button = $('
'); + var include = $('#'+ this.id.substring(0, this.id.length - 2) +'include'); + $(include).parent().parent().before(button); + + // Extract the teaser from the body, if set. Otherwise, stay in joined mode. + var text = body.val().split('', 2); + if (text.length == 2) { + teaser[0].value = trim(text[0]); + body[0].value = trim(text[1]); + $(teaser).attr('disabled', ''); + $('input', button).val(Drupal.settings.teaserButton[0]).toggle(join_teaser, split_teaser); + } + else { + $(teaser).hide(); + $('input', button).val(Drupal.settings.teaserButton[1]).toggle(split_teaser, join_teaser); + $(checkbox).hide(); + } + + }); +} + +if (Drupal.jsEnabled) { + $(document).ready(Drupal.teaserAttach); +} diff --git a/misc/textarea.js b/misc/textarea.js index 6fe92234d94..34217c7e596 100644 --- a/misc/textarea.js +++ b/misc/textarea.js @@ -7,6 +7,12 @@ Drupal.textareaAttach = function() { $(this).wrap('
') .parent().append($('
').mousedown(startDrag)); + // Inherit visibility + if ($(this).is(':hidden')) { + $(this).parent().hide(); + $(this).show(); + } + var grippie = $('div.grippie', $(this).parent())[0]; grippie.style.marginRight = (grippie.offsetWidth - $(this)[0].offsetWidth) +'px'; diff --git a/modules/block/block.module b/modules/block/block.module index 916a8dc6d80..a61f21f359a 100644 --- a/modules/block/block.module +++ b/modules/block/block.module @@ -595,8 +595,8 @@ function block_box_form($edit = array()) { '#required' => TRUE, '#weight' => -19, ); - $form['body_filter']['#weight'] = -17; - $form['body_filter']['body'] = array( + $form['body_field']['#weight'] = -17; + $form['body_field']['body'] = array( '#type' => 'textarea', '#title' => t('Block body'), '#default_value' => $edit['body'], @@ -607,7 +607,7 @@ function block_box_form($edit = array()) { if (!isset($edit['format'])) { $edit['format'] = FILTER_FORMAT_DEFAULT; } - $form['body_filter']['format'] = filter_form($edit['format'], -16); + $form['body_field']['format'] = filter_form($edit['format'], -16); return $form; } diff --git a/modules/blog/blog.module b/modules/blog/blog.module index 4c9fa1d881d..ff61ba9985c 100644 --- a/modules/blog/blog.module +++ b/modules/blog/blog.module @@ -211,8 +211,7 @@ function blog_form(&$node) { } $form['title'] = array('#type' => 'textfield', '#title' => check_plain($type->title_label), '#required' => TRUE, '#default_value' => !empty($node->title) ? $node->title : NULL, '#weight' => -5); - $form['body_filter']['body'] = array('#type' => 'textarea', '#title' => check_plain($type->body_label), '#default_value' => !empty($node->body) ? $node->title : NULL, '#rows' => 20, '#required' => TRUE); - $form['body_filter']['filter'] = filter_form($node->format); + $form['body_field'] = node_body_field($node, $type->body_label, $type->min_word_count); return $form; } diff --git a/modules/book/book.module b/modules/book/book.module index 5437ee5a98e..2c4a4f31fed 100644 --- a/modules/book/book.module +++ b/modules/book/book.module @@ -232,13 +232,8 @@ function book_form(&$node) { '#default_value' => $node->title, '#weight' => -5, ); - $form['body_filter']['body'] = array('#type' => 'textarea', - '#title' => check_plain($type->body_label), - '#default_value' => $node->body, - '#rows' => 20, - '#required' => TRUE, - ); - $form['body_filter']['format'] = filter_form($node->format); + + $form['body_field'] = node_body_field($node, $type->body_label, 1); if (user_access('administer nodes')) { $form['weight'] = array('#type' => 'weight', diff --git a/modules/forum/forum.module b/modules/forum/forum.module index 10d69901a24..b5895d40fe8 100644 --- a/modules/forum/forum.module +++ b/modules/forum/forum.module @@ -423,8 +423,7 @@ function forum_form(&$node) { $form['shadow'] = array('#type' => 'checkbox', '#title' => t('Leave shadow copy'), '#default_value' => $shadow, '#description' => t('If you move this topic, you can leave a link in the old forum to the new forum.')); } - $form['body_filter']['body'] = array('#type' => 'textarea', '#title' => check_plain($type->body_label), '#default_value' => !empty($node->body) ? $node->body : '', '#rows' => 20, '#required' => TRUE); - $form['body_filter']['format'] = filter_form($node->format); + $form['body_field'] = node_body_field($node, $type->body_label, 1); return $form; } diff --git a/modules/node/node.module b/modules/node/node.module index 92443f60545..c79aeeea017 100644 --- a/modules/node/node.module +++ b/modules/node/node.module @@ -170,6 +170,28 @@ function node_mark($nid, $timestamp) { return MARK_READ; } +/** + * See if the user used JS to submit a teaser. + */ +function node_teaser_js(&$form, $form_values) { + // Glue the teaser to the body. + if (isset($form['#post']['teaser_js'])) { + if (trim($form_values['teaser_js'])) { + // Space the teaser from the body + $body = trim($form_values['teaser_js']) ."\r\n\r\n". trim($form_values['body']); + } + else { + // Empty teaser, no spaces. + $body = ''. $form_values['body']; + } + // Pass value onto preview/submit + form_set_value($form['body'], $body); + // Pass value back onto form + $form['body']['#value'] = $body; + } + return $form; +} + /** * Automatically generate a teaser for a node body in a given format. */ @@ -1876,7 +1898,16 @@ function node_submit($node) { // Auto-generate the teaser, but only if it hasn't been set (e.g. by a // module-provided 'teaser' form item). if (!isset($node->teaser)) { - $node->teaser = isset($node->body) ? node_teaser($node->body, isset($node->format) ? $node->format : NULL) : ''; + if (isset($node->body)) { + $node->teaser = node_teaser($node->body, isset($node->format) ? $node->format : NULL); + // Chop off the teaser from the body if needed. + if (!$node->teaser_include && $node->teaser == substr($node->body, 0, strlen($node->teaser))) { + $node->body = substr($node->body, strlen($node->teaser)); + } + } + else { + $node->teaser = ''; + } } if (user_access('administer nodes')) { @@ -2216,6 +2247,10 @@ function node_preview($node) { // 'teaser' form item). if (!isset($node->teaser)) { $node->teaser = empty($node->body) ? '' : node_teaser($node->body, $node->format); + // Chop off the teaser from the body if needed. + if (!$node->teaser_include && $node->teaser == substr($node->body, 0, strlen($node->teaser))) { + $node->body = substr($node->body, strlen($node->teaser)); + } } // Display a preview of the node: @@ -2241,7 +2276,7 @@ function node_preview($node) { function theme_node_preview($node) { $output = '
'; if ($node->teaser && $node->teaser != $node->body) { - drupal_set_message(t('The trimmed version of your post shows what your post looks like when promoted to the main page or when exported for syndication. You can insert the delimiter "<!--break-->" (without the quotes) to fine-tune where your post gets split.')); + drupal_set_message(t('The trimmed version of your post shows what your post looks like when promoted to the main page or when exported for syndication. You can insert the delimiter "<!--break-->" (without the quotes) to fine-tune where your post gets split.')); $output .= '

'. t('Preview trimmed version') .'

'; $output .= node_view(drupal_clone($node), 1, FALSE, 0); $output .= '

'. t('Preview full version') .'

'; @@ -2957,6 +2992,43 @@ function node_content_access($op, $node) { } } +/** + * Return a node body field, with format and teaser. + */ +function node_body_field(&$node, $label, $word_count) { + + // Check if we need to restore the teaser at the beginning of the body. + $include = !isset($node->teaser) || ($node->teaser == substr($node->body, 0, strlen($node->teaser))); + + $form = array( + '#after_build' => array('node_teaser_js')); + + $form['teaser_js'] = array( + '#type' => 'textarea', + '#rows' => 10, + '#teaser' => 'edit-body', + '#teaser_checkbox' => 'edit-teaser-include', + '#disabled' => TRUE); + + $form['teaser_include'] = array( + '#type' => 'checkbox', + '#title' => t('Show summary in full view'), + '#default_value' => $include, + '#prefix' => '
', + '#suffix' => '
', + ); + + $form['body'] = array( + '#type' => 'textarea', + '#title' => check_plain($label), + '#default_value' => $include ? $node->body : ($node->teaser . $node->body), + '#rows' => 20, + '#required' => ($word_count > 0)); + + $form['format'] = filter_form($node->format); + + return $form; +} /** * Implementation of hook_form(). */ @@ -2975,13 +3047,7 @@ function node_content_form($node) { } if ($type->has_body) { - $form['body_filter']['body'] = array( - '#type' => 'textarea', - '#title' => check_plain($type->body_label), - '#default_value' => $node->body, - '#rows' => 20, - '#required' => ($type->min_word_count > 0)); - $form['body_filter']['format'] = filter_form($node->format); + $form['body_field'] = node_body_field($node, $type->body_label, $type->min_word_count); } return $form; diff --git a/modules/system/system.css b/modules/system/system.css index c78d5212771..fa03200e3c0 100644 --- a/modules/system/system.css +++ b/modules/system/system.css @@ -357,6 +357,31 @@ html.js .resizable-textarea textarea { display: block; } +/* +** Teaser splitter +*/ +.joined + .grippie { + height: 5px; + background-position: center 1px; + margin-bottom: -2px; +} +div.teaser-button-wrapper { + float: right; + padding-right: 5%; + margin: 0; +} +.teaser-checkbox div.form-item { + float: right; + margin: 0 5% 0 0; + padding: 0; +} +textarea.teaser { + display: none; +} +html.js .no-js { + display: none; +} + /* ** Progressbar styles */