3 Copyright 2012 Yahoo! Inc. All rights reserved.
4 Licensed under the BSD License.
5 http://yuilibrary.com/license/
7 YUI.add('editor-para', function(Y) {
11 * Plugin for Editor to paragraph auto wrapping and correction.
12 * @class Plugin.EditorPara
13 * @extends Plugin.EditorParaBase
16 * @submodule editor-para
20 var EditorPara = function() {
21 EditorPara.superclass.constructor.apply(this, arguments);
22 }, HOST = 'host', BODY = 'body', NODE_CHANGE = 'nodeChange', PARENT_NODE = 'parentNode',
23 FIRST_P = BODY + ' > p', P = 'p', BR = '<br>', FC = 'firstChild', LI = 'li';
26 Y.extend(EditorPara, Y.Plugin.EditorParaBase, {
28 * nodeChange handler to handle fixing an empty document.
30 * @method _onNodeChange
32 _onNodeChange: function(e) {
33 var host = this.get(HOST), inst = host.getInstance(),
34 html, txt, par , d, sel, btag = inst.EditorSelection.DEFAULT_BLOCK_TAG,
35 inHTML, txt2, childs, aNode, index, node2, top, n, sib,
36 ps, br, item, p, imgs, t, LAST_CHILD = ':last-child';
38 switch (e.changedType) {
40 var para = ((this._lastPara) ? this._lastPara : e.changedNode),
41 b = para.one('br.yui-cursor');
44 delete this._lastPara;
48 if (b.previous() || b.next()) {
54 if (!para.test(btag)) {
55 var para2 = para.ancestor(btag);
61 if (para.test(btag)) {
62 var prev = para.previous(), lc, lc2, found = false;
64 lc = prev.one(LAST_CHILD);
67 lc2 = lc.one(LAST_CHILD);
78 host.copyStyles(lc, para);
85 //Webkit doesn't support shift+enter as a BR, this fixes that.
86 if (e.changedEvent.shiftKey) {
87 host.execCommand('insertbr');
88 e.changedEvent.preventDefault();
91 if (e.changedNode.test('li') && !Y.UA.ie) {
92 html = inst.EditorSelection.getText(e.changedNode);
94 par = e.changedNode.ancestor('ol,ul');
95 var dir = par.getAttribute('dir');
97 dir = ' dir = "' + dir + '"';
99 par = e.changedNode.ancestor(inst.EditorSelection.BLOCKS);
100 d = inst.Node.create('<p' + dir + '>' + inst.EditorSelection.CURSOR + '</p>');
101 par.insert(d, 'after');
102 e.changedNode.remove();
103 e.changedEvent.halt();
105 sel = new inst.EditorSelection();
106 sel.selectNode(d, true, false);
109 //TODO Move this to a GECKO MODULE - Can't for the moment, requires no change to metadata (YMAIL)
110 if (Y.UA.gecko && host.get('defaultblock') !== 'p') {
113 if (!par.test(LI) && !par.ancestor(LI)) {
114 if (!par.test(btag)) {
115 par = par.ancestor(btag);
117 d = inst.Node.create('<' + btag + '></' + btag + '>');
118 par.insert(d, 'after');
119 sel = new inst.EditorSelection();
120 if (sel.anchorOffset) {
121 inHTML = sel.anchorNode.get('textContent');
123 txt = inst.one(inst.config.doc.createTextNode(inHTML.substr(0, sel.anchorOffset)));
124 txt2 = inst.one(inst.config.doc.createTextNode(inHTML.substr(sel.anchorOffset)));
126 aNode = sel.anchorNode;
127 aNode.setContent(''); //I
128 node2 = aNode.cloneNode(); //I
129 node2.append(txt2); //text
133 sib = sib.get(PARENT_NODE); //B
134 if (sib && !sib.test(btag)) {
136 n.set('innerHTML', '');
140 childs = sib.get('childNodes');
142 childs.each(function(c) {
151 aNode = sib; //Top sibling
158 sel.anchorNode.append(txt);
167 d.prepend(inst.EditorSelection.CURSOR);
168 sel.focusCursor(true, true);
169 html = inst.EditorSelection.getText(d);
171 inst.EditorSelection.cleanCursor();
173 e.changedEvent.preventDefault();
179 if (inst.config.doc && inst.config.doc.body && inst.config.doc.body.innerHTML.length < 20) {
180 if (!inst.one(FIRST_P)) {
181 this._fixFirstPara();
187 case 'backspace-down':
190 ps = inst.all(FIRST_P);
191 item = inst.one(BODY);
197 br.removeAttribute('id');
198 br.removeAttribute('class');
201 txt = inst.EditorSelection.getText(item);
202 txt = txt.replace(/ /g, '').replace(/\n/g, '');
203 imgs = item.all('img');
205 if (txt.length === 0 && !imgs.size()) {
206 //God this is horrible..
208 this._fixFirstPara();
211 if (e.changedNode && e.changedNode.test(P)) {
214 if (!p && host._lastPara && host._lastPara.inDoc()) {
217 if (p && !p.test(P)) {
221 if (!p.previous() && p.get(PARENT_NODE) && p.get(PARENT_NODE).test(BODY)) {
222 Y.log('Stopping the backspace event', 'warn', 'editor-para');
223 e.changedEvent.frameEvent.halt();
230 //All backspace calls in Webkit need a preventDefault to
231 //stop history navigation #2531299
233 item = e.changedNode;
234 if (item.test('li') && (!item.previous() && !item.next())) {
235 html = item.get('innerHTML').replace(BR, '');
237 if (item.get(PARENT_NODE)) {
238 item.get(PARENT_NODE).replace(inst.Node.create(BR));
239 e.changedEvent.frameEvent.halt();
240 inst.EditorSelection.filterBlocks();
249 * This forced FF to redraw the content on backspace.
250 * On some occasions FF will leave a cursor residue after content has been deleted.
251 * Dropping in the empty textnode and then removing it causes FF to redraw and
252 * remove the "ghost cursors"
255 t = inst.config.doc.createTextNode(' ');
262 if (e.changedNode && !e.changedNode.test(btag)) {
263 p = e.changedNode.ancestor(btag);
271 initializer: function() {
272 var host = this.get(HOST);
274 Y.error('Can not plug EditorPara and EditorBR at the same time.');
278 host.on(NODE_CHANGE, Y.bind(this._onNodeChange, this));
300 Y.namespace('Plugin');
302 Y.Plugin.EditorPara = EditorPara;
306 }, '3.5.1' ,{skinnable:false, requires:['editor-base']});