3
Copyright 2011 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
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.Base, {
28
* Utility method to create an empty paragraph when the document is empty.
30
* @method _fixFirstPara
32
_fixFirstPara: function() {
33
Y.log('Fix First Paragraph', 'info', 'editor-para');
34
var host = this.get(HOST), inst = host.getInstance(), sel, n,
35
body = inst.config.doc.body,
36
html = body.innerHTML,
37
col = ((html.length) ? true : false);
44
body.innerHTML = '<' + P + '>' + html + inst.Selection.CURSOR + '</' + P + '>';
46
n = inst.one(FIRST_P);
47
sel = new inst.Selection();
49
sel.selectNode(n, true, col);
52
* nodeChange handler to handle fixing an empty document.
54
* @method _onNodeChange
56
_onNodeChange: function(e) {
57
var host = this.get(HOST), inst = host.getInstance(),
58
html, txt, par , d, sel, btag = inst.Selection.DEFAULT_BLOCK_TAG,
59
inHTML, txt2, childs, aNode, index, node2, top, n, sib,
60
ps, br, item, p, imgs, t, LAST_CHILD = ':last-child';
62
switch (e.changedType) {
64
var para = ((this._lastPara) ? this._lastPara : e.changedNode),
65
b = para.one('br.yui-cursor');
68
delete this._lastPara;
72
if (b.previous() || b.next()) {
78
if (!para.test(btag)) {
79
var para2 = para.ancestor(btag);
85
if (para.test(btag)) {
86
var prev = para.previous(), lc, lc2, found = false;
88
lc = prev.one(LAST_CHILD);
91
lc2 = lc.one(LAST_CHILD);
102
host.copyStyles(lc, para);
109
if (e.changedNode.test('br')) {
110
e.changedNode.remove();
111
} else if (e.changedNode.test('p, span')) {
112
var b = e.changedNode.one('br.yui-cursor');
119
//Webkit doesn't support shift+enter as a BR, this fixes that.
120
if (e.changedEvent.shiftKey) {
121
host.execCommand('insertbr');
122
e.changedEvent.preventDefault();
125
if (e.changedNode.test('li') && !Y.UA.ie) {
126
html = inst.Selection.getText(e.changedNode);
128
par = e.changedNode.ancestor('ol,ul');
129
var dir = par.getAttribute('dir');
131
dir = ' dir = "' + dir + '"';
133
par = e.changedNode.ancestor(inst.Selection.BLOCKS);
134
d = inst.Node.create('<p' + dir + '>' + inst.Selection.CURSOR + '</p>');
135
par.insert(d, 'after');
136
e.changedNode.remove();
137
e.changedEvent.halt();
139
sel = new inst.Selection();
140
sel.selectNode(d, true, false);
143
//TODO Move this to a GECKO MODULE - Can't for the moment, requires no change to metadata (YMAIL)
144
if (Y.UA.gecko && host.get('defaultblock') !== 'p') {
147
if (!par.test(LI) && !par.ancestor(LI)) {
148
if (!par.test(btag)) {
149
par = par.ancestor(btag);
151
d = inst.Node.create('<' + btag + '></' + btag + '>');
152
par.insert(d, 'after');
153
sel = new inst.Selection();
154
if (sel.anchorOffset) {
155
inHTML = sel.anchorNode.get('textContent');
157
txt = inst.one(inst.config.doc.createTextNode(inHTML.substr(0, sel.anchorOffset)));
158
txt2 = inst.one(inst.config.doc.createTextNode(inHTML.substr(sel.anchorOffset)));
160
aNode = sel.anchorNode;
161
aNode.setContent(''); //I
162
node2 = aNode.cloneNode(); //I
163
node2.append(txt2); //text
167
sib = sib.get(PARENT_NODE); //B
168
if (sib && !sib.test(btag)) {
170
n.set('innerHTML', '');
174
childs = sib.get('childNodes');
176
childs.each(function(c) {
185
aNode = sib; //Top sibling
192
sel.anchorNode.append(txt);
201
d.prepend(inst.Selection.CURSOR);
202
sel.focusCursor(true, true);
203
html = inst.Selection.getText(d);
205
inst.Selection.cleanCursor();
207
e.changedEvent.preventDefault();
213
if (inst.config.doc && inst.config.doc.body && inst.config.doc.body.innerHTML.length < 20) {
214
if (!inst.one(FIRST_P)) {
215
this._fixFirstPara();
221
case 'backspace-down':
224
ps = inst.all(FIRST_P);
225
item = inst.one(BODY);
231
br.removeAttribute('id');
232
br.removeAttribute('class');
235
txt = inst.Selection.getText(item);
236
txt = txt.replace(/ /g, '').replace(/\n/g, '');
237
imgs = item.all('img');
239
if (txt.length === 0 && !imgs.size()) {
240
//God this is horrible..
242
this._fixFirstPara();
245
if (e.changedNode && e.changedNode.test(P)) {
248
if (!p && host._lastPara && host._lastPara.inDoc()) {
251
if (p && !p.test(P)) {
255
if (!p.previous() && p.get(PARENT_NODE) && p.get(PARENT_NODE).test(BODY)) {
256
Y.log('Stopping the backspace event', 'warn', 'editor-para');
257
e.changedEvent.frameEvent.halt();
263
item = e.changedNode;
264
if (item.test('li') && (!item.previous() && !item.next())) {
265
html = item.get('innerHTML').replace(BR, '');
267
if (item.get(PARENT_NODE)) {
268
item.get(PARENT_NODE).replace(inst.Node.create(BR));
269
e.changedEvent.frameEvent.halt();
271
inst.Selection.filterBlocks();
280
* This forced FF to redraw the content on backspace.
281
* On some occasions FF will leave a cursor residue after content has been deleted.
282
* Dropping in the empty textnode and then removing it causes FF to redraw and
283
* remove the "ghost cursors"
286
t = inst.config.doc.createTextNode(' ');
293
if (e.changedNode && !e.changedNode.test(btag)) {
294
p = e.changedNode.ancestor(btag);
303
* Performs a block element filter when the Editor is first ready
305
* @method _afterEditorReady
307
_afterEditorReady: function() {
308
var host = this.get(HOST), inst = host.getInstance(), btag;
310
inst.Selection.filterBlocks();
311
btag = inst.Selection.DEFAULT_BLOCK_TAG;
312
FIRST_P = BODY + ' > ' + btag;
317
* Performs a block element filter when the Editor after an content change
319
* @method _afterContentChange
321
_afterContentChange: function() {
322
var host = this.get(HOST), inst = host.getInstance();
323
if (inst && inst.Selection) {
324
inst.Selection.filterBlocks();
328
* Performs block/paste filtering after paste.
330
* @method _afterPaste
332
_afterPaste: function() {
333
var host = this.get(HOST), inst = host.getInstance(),
334
sel = new inst.Selection();
336
Y.later(50, host, function() {
337
inst.Selection.filterBlocks();
341
initializer: function() {
342
var host = this.get(HOST);
344
Y.error('Can not plug EditorPara and EditorBR at the same time.');
348
host.on(NODE_CHANGE, Y.bind(this._onNodeChange, this));
349
host.after('ready', Y.bind(this._afterEditorReady, this));
350
host.after('contentChange', Y.bind(this._afterContentChange, this));
352
host.after('dom:paste', Y.bind(this._afterPaste, this));
375
Y.namespace('Plugin');
377
Y.Plugin.EditorPara = EditorPara;
381
}, '3.4.1' ,{skinnable:false, requires:['editor-base']});