WordPress-like summaries and Teasers in Drupal 7 and CKEditor

Over the past few versions of Drupal, there have been quite a few attempts to create a good way of creating page summaries/teasers/excerpts/extracts that you can use on the front page to encourage people to read the article.

The version that comes out of the box with Drupal 7 works quite nicely if no text editors are added, but our editors have found it rather confusing when used in combination with an editor like CKEditor. When a summary is included and selected, 2 text editors suddenly pop up on the page!

So we have been looking for ways to make the experience a little easier for people.

A good template to follow in this regard is WordPress. They have 2 different ways of creating summaries:

1) A ‘post excerpt’ field, which is good for 1-line summaries to be used in sidebar blocks and so forth. We will call this the short summary.

2) A ‘break’ button which you can press to separate the first couple of paragraphs from the rest (we will call this the long summary). This is good for a main blog page, to let people read the first couple of paragraphs, and then press ‘read more’ to read the rest.

We will try to emulate that here.

Out of the Box steps

So these are steps you can take without having to resort to code. They’ll take you 90% of the way there.

1. Disable the ‘Add/edit summary’ link

Go to Structure » Content types and click the ‘Manage fields‘ link for the content type. Edit the Body field and uncheck “Summary input”.

2. Add the Drupal break button in CKEditor

Go to Configuration » Content authoring » CKEditor. In the ‘Editor Appearance’ section, enable the ‘Drupal Break’ plugin, and add the button (a red dashed line) to the sidebar. This will enable ‘long summaries’ to be created.

3. Create a separate ‘Long text’ field for your ‘short summaries’

You can make this field ‘plain text’ input and limit it to 2 or 3 rows. You can then use this field in shorter Views listings to be placed in sidebars or on the front page.

After that, what’s left?

1. Views doesn’t recognise the <!–break–> tag. The CKEditor button inserts a <!–break–> tag to distinguish between the summary and the rest of the page. However, neither Views ‘trimmed’ or ‘summary or trimmed’ display modes recognise it, which is odd as Drupal’s default trimming does. So the trick here is to populate the node summary on node save:

1
2
3
4
5
6
7
8
9
10
11
/* Implements hook_form_node_form_alter(). */
function mymodule_form_node_form_alter(&amp;$form, $form_state) {
// Not sure if we need this, if we have input disabled
$form['body']['und'][0]['summary']['#access'] = FALSE;
 
$form['#submit'][] = 'mymodule_node_form_submit';
}
 
function mymodule_node_form_submit(&amp;$form, &amp;$form_state) {
$form_state['values']['body']['und'][0]['summary'] = text_summary($form_state['values']['body']['und'][0]['value'], 3, 600);
}

The last 2 values of the text_summary function are the filter format ID (which should probably be the same as the ID of the body itself. ) and the trim length, which won’t matter in our case as text_summary first looks for the <!–break–> before doing any trims. With this addition, we can then use the long summary in our views, as the Body field in ‘Summary or trimmed’ mode.

2. The page break button is not as precise as we would like. The break button is quite careful not to break into things like divs (for fear of leaving unopened tags), but if youre using it on an old site with lots of legacy code (like we often are), this means that often when you press the break button, the <!–break–> tag ends up at the bottom of the page! Views has an option to close unopened tags, so we no longer need to worry about them in the plugin itself. So here’s a version of the CKEditor plugin that is less choosy about positioning, just replace the /plugins/drupalbreaks/plugin.js in the ckeditor module. Just make sure to close all tags in your views. Also try not to use it inside things like tables :)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
/*
Copyright (c) 2003-2011, CKSource - Frederico Knabben. All rights reserved.
For licensing, see LICENSE.html or http://ckeditor.com/license
*/
 
/**
 * @file Plugin for inserting Drupal teaser and page breaks.
 */
( function() {
    CKEDITOR.plugins.add( 'drupalbreaks',
    {
        requires  : [ 'fakeobjects', 'htmldataprocessor' ],
 
        init : function( editor )
        {
 
            // Add the styles that renders our fake objects.
            editor.addCss(
                'img.cke_drupal_pagebreak,img.cke_drupal_break' +
                '{' +
                'background-image: url(' + CKEDITOR.getUrl( this.path + 'images/pagebreak.gif' ) + ');' +
                'background-position: center center;' +
                'background-repeat: no-repeat;' +
                'clear: both;' +
                'display: block;' +
                'float: none;' +
                'width: 100%;' +
                'border-top: #999999 1px dotted;' +
                'border-bottom: #999999 1px dotted;' +
                'height: 5px;' +
                '}' +
                'img.cke_drupal_break' +
                '{' +
                'border-top: #FF0000 1px dotted;' +
                'border-bottom: #FF0000 1px dotted;' +
                '}'
                );
 
            // Register the toolbar buttons.
            editor.ui.addButton( 'DrupalBreak',
            {
                label : Drupal.t('Insert Teaser Break'),
                icon : this.path + 'images/drupalbreak.png',
                command : 'drupalbreak'
            });
 
            editor.addCommand( 'drupalbreak',
            {
                exec : function()
                {
                    // There should be only one <!--break--> in document. So, look
                    // for an image with class "cke_drupal_break" (the fake element).
                    var images = editor.document.getElementsByTag( 'img' );
                    for ( var i = 0, len = images.count() ; i < len ; i++ )
                    {
                        var img = images.getItem( i );
                        if ( img.hasClass( 'cke_drupal_break' ) )
                        {
                            if ( confirm( Drupal.t( 'The document already contains a teaser break. Do you want to proceed by removing it first?' ) ) )
                            {
                                img.remove();
                                break;
                            }
                            else
                                return;
                        }
                    }
 
                    insertComment( 'break' );
                }
            } );
 
            editor.ui.addButton( 'DrupalPageBreak',
            {
                label : Drupal.t( 'Insert Page Break' ),
                icon : this.path + 'images/drupalpagebreak.png',
                command : 'drupalpagebreak'
            });
 
            editor.addCommand( 'drupalpagebreak',
            {
                exec : function()
                {
 
                    var hr = editor.document.createElement( '!--break--' ),
				range = new CKEDITOR.dom.range( editor.document );
 
			editor.insertElement( hr );
 
			// If there's nothing or a non-editable block followed by, establish a new paragraph
			// to make sure cursor is not trapped.
			range.moveToPosition( hr, CKEDITOR.POSITION_AFTER_END );
			var next = hr.getNext();
			if ( !next || next.type == CKEDITOR.NODE_ELEMENT && !next.isEditable() )
				range.fixBlock( true, editor.config.enterMode == CKEDITOR.ENTER_DIV ? 'div' : 'p'  );
 
			range.select();
 
                  /*   insertComment( 'pagebreak' );*/
                }
            } );
 
            // This function effectively inserts the comment into the editor.
            function insertComment( text )
            {
                // Create the fake element that will be inserted into the document.
                // The trick is declaring it as an <hr>, so it will behave like a
                // block element (and in effect it behaves much like an <hr>).
                if ( !CKEDITOR.dom.comment.prototype.getAttribute ) {
                    CKEDITOR.dom.comment.prototype.getAttribute = function() {
                        return '';
                    };
                    CKEDITOR.dom.comment.prototype.attributes = {
                        align : ''
                    };
                }
                var fakeElement = editor.createFakeElement( new CKEDITOR.dom.comment( text ), 'cke_drupal_' + text, 'hr' );
 
                // This is the trick part. We can't use editor.insertElement()
                // because we need to put the comment directly at <body> level.
                // We need to do range manipulation for that.
 
                // Get a DOM range from the current selection.
                var range = editor.getSelection().getRanges()[0],
                elementsPath = new CKEDITOR.dom.elementPath( range.getCommonAncestor( true ) ),
                element = ( elementsPath.block && elementsPath.block.getParent() ) || elementsPath.blockLimit,
                hasMoved;
 
                // If we're not in <body> go moving the position to after the
                // elements until reaching it. This may happen when inside tables,
                // lists, blockquotes, etc.
                /*while ( element && element.getName() != 'body' )
                {
                    range.moveToPosition( element, CKEDITOR.POSITION_AFTER_END );
                    hasMoved = 1;
                    element = element.getParent();
                }*/
 
                // Split the current block.
                if ( !hasMoved )
                    range.splitBlock( 'p' );
 
                // Insert the fake element into the document.
                range.insertNode( fakeElement );
 
                // Now, we move the selection to the best possible place following
                // our fake element.
                var next = fakeElement;
                //while ( ( next = next.getNext() ) && !range.moveToElementEditStart( next ) )
                //{}
 
                range.select();
            }
        },
 
        afterInit : function( editor )
        {
            // Adds the comment processing rules to the data filter, so comments
            // are replaced by fake elements.
            editor.dataProcessor.dataFilter.addRules(
            {
                comment : function( value )
                {
                    if ( !CKEDITOR.htmlParser.comment.prototype.getAttribute ) {
                        CKEDITOR.htmlParser.comment.prototype.getAttribute = function() {
                            return '';
                        };
                        CKEDITOR.htmlParser.comment.prototype.attributes = {
                            align : ''
                        };
                    }
 
                    if ( value == 'break' || value == 'pagebreak' )
                        return editor.createFakeParserElement( new CKEDITOR.htmlParser.comment( value ), 'cke_drupal_' + value, 'hr' );
 
                    return value;
                }
            });
        }
    });
} )();

About Nirbhasa Magee

Nirbhasa lives in Dublin, Ireland and has been developing Wordpress and Drupal based sites since 2006.

Subscribe

Subscribe to our e-mail newsletter to receive updates.

No comments yet.

Leave a Reply

* Copy this password:

* Type or paste password here:

411 Spam Comments Blocked so far by Spam Free Wordpress