editor-para.js revision 7a67e82a93bc7dc5bf9a62a3ad321c2424da37f7
752e31b910dbf30e2b803437da522585eca28528Matt Sweeney
752e31b910dbf30e2b803437da522585eca28528Matt Sweeney
752e31b910dbf30e2b803437da522585eca28528Matt Sweeney /**
31acd99497fcc9fc72f15c5553fe356d64fb9d03Matt Sweeney * Plugin for Editor to paragraph auto wrapping and correction.
752e31b910dbf30e2b803437da522585eca28528Matt Sweeney * @module editor
752e31b910dbf30e2b803437da522585eca28528Matt Sweeney * @submodule editor-para
752e31b910dbf30e2b803437da522585eca28528Matt Sweeney */
752e31b910dbf30e2b803437da522585eca28528Matt Sweeney /**
eb2ea4a236bb9b9bc726a4ba7ed9fdef81246381Matt Sweeney * Plugin for Editor to paragraph auto wrapping and correction.
752e31b910dbf30e2b803437da522585eca28528Matt Sweeney * @class Plugin.EditorPara
752e31b910dbf30e2b803437da522585eca28528Matt Sweeney * @extends Base
752e31b910dbf30e2b803437da522585eca28528Matt Sweeney * @constructor
752e31b910dbf30e2b803437da522585eca28528Matt Sweeney */
752e31b910dbf30e2b803437da522585eca28528Matt Sweeney
752e31b910dbf30e2b803437da522585eca28528Matt Sweeney
752e31b910dbf30e2b803437da522585eca28528Matt Sweeney var EditorPara = function() {
752e31b910dbf30e2b803437da522585eca28528Matt Sweeney EditorPara.superclass.constructor.apply(this, arguments);
b0614bd702ed299874c87fd5d4d48bccda498c73Matt Sweeney }, HOST = 'host', BODY = 'body', NODE_CHANGE = 'nodeChange', PARENT_NODE = 'parentNode',
eb2ea4a236bb9b9bc726a4ba7ed9fdef81246381Matt Sweeney FIRST_P = BODY + ' > p', P = 'p', BR = '<br>', FC = 'firstChild', LI = 'li';
eb2ea4a236bb9b9bc726a4ba7ed9fdef81246381Matt Sweeney
eb2ea4a236bb9b9bc726a4ba7ed9fdef81246381Matt Sweeney
eb2ea4a236bb9b9bc726a4ba7ed9fdef81246381Matt Sweeney Y.extend(EditorPara, Y.Base, {
752e31b910dbf30e2b803437da522585eca28528Matt Sweeney /**
752e31b910dbf30e2b803437da522585eca28528Matt Sweeney * Utility method to create an empty paragraph when the document is empty.
752e31b910dbf30e2b803437da522585eca28528Matt Sweeney * @private
752e31b910dbf30e2b803437da522585eca28528Matt Sweeney * @method _fixFirstPara
752e31b910dbf30e2b803437da522585eca28528Matt Sweeney */
752e31b910dbf30e2b803437da522585eca28528Matt Sweeney _fixFirstPara: function() {
752e31b910dbf30e2b803437da522585eca28528Matt Sweeney var host = this.get(HOST), inst = host.getInstance(), sel;
752e31b910dbf30e2b803437da522585eca28528Matt Sweeney inst.one('body').set('innerHTML', '<' + P + '>' + inst.Selection.CURSOR + '</' + P + '>');
752e31b910dbf30e2b803437da522585eca28528Matt Sweeney var n = inst.one(FIRST_P);
752e31b910dbf30e2b803437da522585eca28528Matt Sweeney sel = new inst.Selection();
752e31b910dbf30e2b803437da522585eca28528Matt Sweeney sel.selectNode(n, true, false);
752e31b910dbf30e2b803437da522585eca28528Matt Sweeney
752e31b910dbf30e2b803437da522585eca28528Matt Sweeney },
752e31b910dbf30e2b803437da522585eca28528Matt Sweeney /**
752e31b910dbf30e2b803437da522585eca28528Matt Sweeney * nodeChange handler to handle fixing an empty document.
752e31b910dbf30e2b803437da522585eca28528Matt Sweeney * @private
752e31b910dbf30e2b803437da522585eca28528Matt Sweeney * @method _onNodeChange
752e31b910dbf30e2b803437da522585eca28528Matt Sweeney */
752e31b910dbf30e2b803437da522585eca28528Matt Sweeney _onNodeChange: function(e) {
752e31b910dbf30e2b803437da522585eca28528Matt Sweeney var host = this.get(HOST), inst = host.getInstance(),
752e31b910dbf30e2b803437da522585eca28528Matt Sweeney html, txt, par , d, sel, btag = inst.Selection.DEFAULT_BLOCK_TAG,
752e31b910dbf30e2b803437da522585eca28528Matt Sweeney inHTML, txt2, childs, aNode, index, node2, top, n, sib,
eb2ea4a236bb9b9bc726a4ba7ed9fdef81246381Matt Sweeney ps, br, item, p, imgs, t, LAST_CHILD = ':last-child';
eb2ea4a236bb9b9bc726a4ba7ed9fdef81246381Matt Sweeney
752e31b910dbf30e2b803437da522585eca28528Matt Sweeney switch (e.changedType) {
752e31b910dbf30e2b803437da522585eca28528Matt Sweeney case 'enter-up':
ac9a31a10e6b74ccde5140bc7696b4364724dedcLuke Smith var para = ((this._lastPara) ? this._lastPara : e.changedNode),
752e31b910dbf30e2b803437da522585eca28528Matt Sweeney b = para.one('br.yui-cursor');
ac9a31a10e6b74ccde5140bc7696b4364724dedcLuke Smith
ac9a31a10e6b74ccde5140bc7696b4364724dedcLuke Smith if (this._lastPara) {
ac9a31a10e6b74ccde5140bc7696b4364724dedcLuke Smith delete this._lastPara;
752e31b910dbf30e2b803437da522585eca28528Matt Sweeney }
752e31b910dbf30e2b803437da522585eca28528Matt Sweeney
eb2ea4a236bb9b9bc726a4ba7ed9fdef81246381Matt Sweeney if (b) {
b0614bd702ed299874c87fd5d4d48bccda498c73Matt Sweeney if (b.previous() || b.next()) {
b0614bd702ed299874c87fd5d4d48bccda498c73Matt Sweeney b.remove();
752e31b910dbf30e2b803437da522585eca28528Matt Sweeney }
eb2ea4a236bb9b9bc726a4ba7ed9fdef81246381Matt Sweeney }
eb2ea4a236bb9b9bc726a4ba7ed9fdef81246381Matt Sweeney if (!para.test(btag)) {
752e31b910dbf30e2b803437da522585eca28528Matt Sweeney var para2 = para.ancestor(btag);
752e31b910dbf30e2b803437da522585eca28528Matt Sweeney if (para2) {
eb2ea4a236bb9b9bc726a4ba7ed9fdef81246381Matt Sweeney para = para2;
eb2ea4a236bb9b9bc726a4ba7ed9fdef81246381Matt Sweeney para2 = null;
eb2ea4a236bb9b9bc726a4ba7ed9fdef81246381Matt Sweeney }
eb2ea4a236bb9b9bc726a4ba7ed9fdef81246381Matt Sweeney }
eb2ea4a236bb9b9bc726a4ba7ed9fdef81246381Matt Sweeney if (para.test(btag)) {
eb2ea4a236bb9b9bc726a4ba7ed9fdef81246381Matt Sweeney var prev = para.previous(), lc, lc2, found = false;
eb2ea4a236bb9b9bc726a4ba7ed9fdef81246381Matt Sweeney if (prev) {
eb2ea4a236bb9b9bc726a4ba7ed9fdef81246381Matt Sweeney lc = prev.one(LAST_CHILD);
eb2ea4a236bb9b9bc726a4ba7ed9fdef81246381Matt Sweeney while (!found) {
eb2ea4a236bb9b9bc726a4ba7ed9fdef81246381Matt Sweeney if (lc) {
eb2ea4a236bb9b9bc726a4ba7ed9fdef81246381Matt Sweeney lc2 = lc.one(LAST_CHILD);
eb2ea4a236bb9b9bc726a4ba7ed9fdef81246381Matt Sweeney if (lc2) {
eb2ea4a236bb9b9bc726a4ba7ed9fdef81246381Matt Sweeney lc = lc2;
eb2ea4a236bb9b9bc726a4ba7ed9fdef81246381Matt Sweeney } else {
eb2ea4a236bb9b9bc726a4ba7ed9fdef81246381Matt Sweeney found = true;
eb2ea4a236bb9b9bc726a4ba7ed9fdef81246381Matt Sweeney }
eb2ea4a236bb9b9bc726a4ba7ed9fdef81246381Matt Sweeney } else {
eb2ea4a236bb9b9bc726a4ba7ed9fdef81246381Matt Sweeney found = true;
eb2ea4a236bb9b9bc726a4ba7ed9fdef81246381Matt Sweeney }
752e31b910dbf30e2b803437da522585eca28528Matt Sweeney }
eb2ea4a236bb9b9bc726a4ba7ed9fdef81246381Matt Sweeney if (lc) {
eb2ea4a236bb9b9bc726a4ba7ed9fdef81246381Matt Sweeney host.copyStyles(lc, para);
eb2ea4a236bb9b9bc726a4ba7ed9fdef81246381Matt Sweeney }
eb2ea4a236bb9b9bc726a4ba7ed9fdef81246381Matt Sweeney }
eb2ea4a236bb9b9bc726a4ba7ed9fdef81246381Matt Sweeney }
eb2ea4a236bb9b9bc726a4ba7ed9fdef81246381Matt Sweeney break;
752e31b910dbf30e2b803437da522585eca28528Matt Sweeney case 'enter':
eb2ea4a236bb9b9bc726a4ba7ed9fdef81246381Matt Sweeney if (Y.UA.webkit) {
eb2ea4a236bb9b9bc726a4ba7ed9fdef81246381Matt Sweeney //Webkit doesn't support shift+enter as a BR, this fixes that.
eb2ea4a236bb9b9bc726a4ba7ed9fdef81246381Matt Sweeney if (e.changedEvent.shiftKey) {
eb2ea4a236bb9b9bc726a4ba7ed9fdef81246381Matt Sweeney host.execCommand('insertbr');
eb2ea4a236bb9b9bc726a4ba7ed9fdef81246381Matt Sweeney e.changedEvent.preventDefault();
752e31b910dbf30e2b803437da522585eca28528Matt Sweeney }
752e31b910dbf30e2b803437da522585eca28528Matt Sweeney }
eb2ea4a236bb9b9bc726a4ba7ed9fdef81246381Matt Sweeney //TODO Move this to a GECKO MODULE - Can't for the moment, requires no change to metadata (YMAIL)
752e31b910dbf30e2b803437da522585eca28528Matt Sweeney if (Y.UA.gecko && host.get('defaultblock') !== 'p') {
752e31b910dbf30e2b803437da522585eca28528Matt Sweeney par = e.changedNode;
b0614bd702ed299874c87fd5d4d48bccda498c73Matt Sweeney
b0614bd702ed299874c87fd5d4d48bccda498c73Matt Sweeney if (!par.test(LI) && !par.ancestor(LI)) {
eb2ea4a236bb9b9bc726a4ba7ed9fdef81246381Matt Sweeney if (!par.test(btag)) {
b0614bd702ed299874c87fd5d4d48bccda498c73Matt Sweeney par = par.ancestor(btag);
752e31b910dbf30e2b803437da522585eca28528Matt Sweeney }
eb2ea4a236bb9b9bc726a4ba7ed9fdef81246381Matt Sweeney d = inst.Node.create('<' + btag + '></' + btag + '>');
eb2ea4a236bb9b9bc726a4ba7ed9fdef81246381Matt Sweeney par.insert(d, 'after');
eb2ea4a236bb9b9bc726a4ba7ed9fdef81246381Matt Sweeney sel = new inst.Selection();
eb2ea4a236bb9b9bc726a4ba7ed9fdef81246381Matt Sweeney if (sel.anchorOffset) {
eb2ea4a236bb9b9bc726a4ba7ed9fdef81246381Matt Sweeney inHTML = sel.anchorNode.get('textContent');
eb2ea4a236bb9b9bc726a4ba7ed9fdef81246381Matt Sweeney
eb2ea4a236bb9b9bc726a4ba7ed9fdef81246381Matt Sweeney txt = inst.one(inst.config.doc.createTextNode(inHTML.substr(0, sel.anchorOffset)));
eb2ea4a236bb9b9bc726a4ba7ed9fdef81246381Matt Sweeney txt2 = inst.one(inst.config.doc.createTextNode(inHTML.substr(sel.anchorOffset)));
b0614bd702ed299874c87fd5d4d48bccda498c73Matt Sweeney
b0614bd702ed299874c87fd5d4d48bccda498c73Matt Sweeney aNode = sel.anchorNode;
b0614bd702ed299874c87fd5d4d48bccda498c73Matt Sweeney aNode.setContent(''); //I
eb2ea4a236bb9b9bc726a4ba7ed9fdef81246381Matt Sweeney node2 = aNode.cloneNode(); //I
31acd99497fcc9fc72f15c5553fe356d64fb9d03Matt Sweeney node2.append(txt2); //text
eb2ea4a236bb9b9bc726a4ba7ed9fdef81246381Matt Sweeney top = false;
eb2ea4a236bb9b9bc726a4ba7ed9fdef81246381Matt Sweeney sib = aNode; //I
b0614bd702ed299874c87fd5d4d48bccda498c73Matt Sweeney while (!top) {
b0614bd702ed299874c87fd5d4d48bccda498c73Matt Sweeney sib = sib.get(PARENT_NODE); //B
752e31b910dbf30e2b803437da522585eca28528Matt Sweeney if (sib && !sib.test(btag)) {
752e31b910dbf30e2b803437da522585eca28528Matt Sweeney n = sib.cloneNode();
752e31b910dbf30e2b803437da522585eca28528Matt Sweeney n.set('innerHTML', '');
ac9a31a10e6b74ccde5140bc7696b4364724dedcLuke Smith n.append(node2);
752e31b910dbf30e2b803437da522585eca28528Matt Sweeney
752e31b910dbf30e2b803437da522585eca28528Matt Sweeney //Get children..
752e31b910dbf30e2b803437da522585eca28528Matt Sweeney childs = sib.get('childNodes');
752e31b910dbf30e2b803437da522585eca28528Matt Sweeney var start = false;
eb2ea4a236bb9b9bc726a4ba7ed9fdef81246381Matt Sweeney childs.each(function(c) {
eb2ea4a236bb9b9bc726a4ba7ed9fdef81246381Matt Sweeney if (start) {
eb2ea4a236bb9b9bc726a4ba7ed9fdef81246381Matt Sweeney n.append(c);
752e31b910dbf30e2b803437da522585eca28528Matt Sweeney }
752e31b910dbf30e2b803437da522585eca28528Matt Sweeney if (c === aNode) {
752e31b910dbf30e2b803437da522585eca28528Matt Sweeney start = true;
752e31b910dbf30e2b803437da522585eca28528Matt Sweeney }
752e31b910dbf30e2b803437da522585eca28528Matt Sweeney });
752e31b910dbf30e2b803437da522585eca28528Matt Sweeney
752e31b910dbf30e2b803437da522585eca28528Matt Sweeney aNode = sib; //Top sibling
752e31b910dbf30e2b803437da522585eca28528Matt Sweeney node2 = n;
eb2ea4a236bb9b9bc726a4ba7ed9fdef81246381Matt Sweeney } else {
eb2ea4a236bb9b9bc726a4ba7ed9fdef81246381Matt Sweeney top = true;
eb2ea4a236bb9b9bc726a4ba7ed9fdef81246381Matt Sweeney }
eb2ea4a236bb9b9bc726a4ba7ed9fdef81246381Matt Sweeney }
752e31b910dbf30e2b803437da522585eca28528Matt Sweeney txt2 = node2;
752e31b910dbf30e2b803437da522585eca28528Matt Sweeney sel.anchorNode.append(txt);
752e31b910dbf30e2b803437da522585eca28528Matt Sweeney
eb2ea4a236bb9b9bc726a4ba7ed9fdef81246381Matt Sweeney if (txt2) {
eb2ea4a236bb9b9bc726a4ba7ed9fdef81246381Matt Sweeney d.append(txt2);
eb2ea4a236bb9b9bc726a4ba7ed9fdef81246381Matt Sweeney }
eb2ea4a236bb9b9bc726a4ba7ed9fdef81246381Matt Sweeney }
eb2ea4a236bb9b9bc726a4ba7ed9fdef81246381Matt Sweeney if (d.get(FC)) {
eb2ea4a236bb9b9bc726a4ba7ed9fdef81246381Matt Sweeney d = d.get(FC);
eb2ea4a236bb9b9bc726a4ba7ed9fdef81246381Matt Sweeney }
eb2ea4a236bb9b9bc726a4ba7ed9fdef81246381Matt Sweeney d.prepend(inst.Selection.CURSOR);
eb2ea4a236bb9b9bc726a4ba7ed9fdef81246381Matt Sweeney sel.focusCursor(true, true);
eb2ea4a236bb9b9bc726a4ba7ed9fdef81246381Matt Sweeney html = inst.Selection.getText(d);
eb2ea4a236bb9b9bc726a4ba7ed9fdef81246381Matt Sweeney if (html !== '') {
eb2ea4a236bb9b9bc726a4ba7ed9fdef81246381Matt Sweeney inst.Selection.cleanCursor();
eb2ea4a236bb9b9bc726a4ba7ed9fdef81246381Matt Sweeney }
eb2ea4a236bb9b9bc726a4ba7ed9fdef81246381Matt Sweeney e.changedEvent.preventDefault();
eb2ea4a236bb9b9bc726a4ba7ed9fdef81246381Matt Sweeney }
eb2ea4a236bb9b9bc726a4ba7ed9fdef81246381Matt Sweeney }
eb2ea4a236bb9b9bc726a4ba7ed9fdef81246381Matt Sweeney break;
eb2ea4a236bb9b9bc726a4ba7ed9fdef81246381Matt Sweeney case 'keydown':
eb2ea4a236bb9b9bc726a4ba7ed9fdef81246381Matt Sweeney if (inst.config.doc.childNodes.length < 2) {
eb2ea4a236bb9b9bc726a4ba7ed9fdef81246381Matt Sweeney var cont = inst.config.doc.body.innerHTML;
eb2ea4a236bb9b9bc726a4ba7ed9fdef81246381Matt Sweeney if (cont && cont.length < 5 && cont.toLowerCase() == BR) {
eb2ea4a236bb9b9bc726a4ba7ed9fdef81246381Matt Sweeney this._fixFirstPara();
eb2ea4a236bb9b9bc726a4ba7ed9fdef81246381Matt Sweeney }
eb2ea4a236bb9b9bc726a4ba7ed9fdef81246381Matt Sweeney }
eb2ea4a236bb9b9bc726a4ba7ed9fdef81246381Matt Sweeney break;
eb2ea4a236bb9b9bc726a4ba7ed9fdef81246381Matt Sweeney case 'backspace-up':
eb2ea4a236bb9b9bc726a4ba7ed9fdef81246381Matt Sweeney case 'backspace-down':
eb2ea4a236bb9b9bc726a4ba7ed9fdef81246381Matt Sweeney case 'delete-up':
eb2ea4a236bb9b9bc726a4ba7ed9fdef81246381Matt Sweeney if (!Y.UA.ie) {
eb2ea4a236bb9b9bc726a4ba7ed9fdef81246381Matt Sweeney ps = inst.all(FIRST_P);
eb2ea4a236bb9b9bc726a4ba7ed9fdef81246381Matt Sweeney item = inst.one(BODY);
752e31b910dbf30e2b803437da522585eca28528Matt Sweeney if (ps.item(0)) {
752e31b910dbf30e2b803437da522585eca28528Matt Sweeney item = ps.item(0);
752e31b910dbf30e2b803437da522585eca28528Matt Sweeney }
752e31b910dbf30e2b803437da522585eca28528Matt Sweeney br = item.one('br');
eb2ea4a236bb9b9bc726a4ba7ed9fdef81246381Matt Sweeney if (br) {
b0614bd702ed299874c87fd5d4d48bccda498c73Matt Sweeney br.removeAttribute('id');
752e31b910dbf30e2b803437da522585eca28528Matt Sweeney br.removeAttribute('class');
752e31b910dbf30e2b803437da522585eca28528Matt Sweeney }
752e31b910dbf30e2b803437da522585eca28528Matt Sweeney
txt = inst.Selection.getText(item);
txt = txt.replace(/ /g, '').replace(/\n/g, '');
imgs = item.all('img');
if (txt.length === 0 && !imgs.size()) {
//God this is horrible..
if (!item.test(P)) {
this._fixFirstPara();
}
p = null;
if (e.changedNode && e.changedNode.test(P)) {
p = e.changedNode;
}
if (!p && host._lastPara && host._lastPara.inDoc()) {
p = host._lastPara;
}
if (p && !p.test(P)) {
p = p.ancestor(P);
}
if (p) {
if (!p.previous() && p.get(PARENT_NODE) && p.get(PARENT_NODE).test(BODY)) {
Y.log('Stopping the backspace event', 'warn', 'editor-para');
e.changedEvent.frameEvent.halt();
}
}
}
if (Y.UA.webkit) {
if (e.changedNode) {
item = e.changedNode;
if (item.test('li') && (!item.previous() && !item.next())) {
html = item.get('innerHTML').replace(BR, '');
if (html === '') {
if (item.get(PARENT_NODE)) {
item.get(PARENT_NODE).replace(inst.Node.create(BR));
e.changedEvent.frameEvent.halt();
e.preventDefault();
inst.Selection.filterBlocks();
}
}
}
}
}
}
if (Y.UA.gecko) {
/**
* This forced FF to redraw the content on backspace.
* On some occasions FF will leave a cursor residue after content has been deleted.
* Dropping in the empty textnode and then removing it causes FF to redraw and
* remove the "ghost cursors"
*/
d = e.changedNode;
t = inst.config.doc.createTextNode(' ');
d.appendChild(t);
d.removeChild(t);
}
break;
}
if (Y.UA.gecko) {
if (e.changedNode && !e.changedNode.test(btag)) {
p = e.changedNode.ancestor(btag);
if (p) {
this._lastPara = p;
}
}
}
},
/**
* Performs a block element filter when the Editor is first ready
* @private
* @method _afterEditorReady
*/
_afterEditorReady: function() {
var host = this.get(HOST), inst = host.getInstance(), btag;
if (inst) {
inst.Selection.filterBlocks();
btag = inst.Selection.DEFAULT_BLOCK_TAG;
FIRST_P = BODY + ' > ' + btag;
P = btag;
}
},
/**
* Performs a block element filter when the Editor after an content change
* @private
* @method _afterContentChange
*/
_afterContentChange: function() {
var host = this.get(HOST), inst = host.getInstance();
if (inst && inst.Selection) {
inst.Selection.filterBlocks();
}
},
/**
* Performs block/paste filtering after paste.
* @private
* @method _afterPaste
*/
_afterPaste: function() {
var host = this.get(HOST), inst = host.getInstance(),
sel = new inst.Selection();
Y.later(50, host, function() {
inst.Selection.filterBlocks();
});
},
initializer: function() {
var host = this.get(HOST);
if (host.editorBR) {
Y.error('Can not plug EditorPara and EditorBR at the same time.');
return;
}
host.on(NODE_CHANGE, Y.bind(this._onNodeChange, this));
host.after('ready', Y.bind(this._afterEditorReady, this));
host.after('contentChange', Y.bind(this._afterContentChange, this));
if (Y.Env.webkit) {
host.after('dom:paste', Y.bind(this._afterPaste, this));
}
}
}, {
/**
* editorPara
* @static
* @property NAME
*/
NAME: 'editorPara',
/**
* editorPara
* @static
* @property NS
*/
NS: 'editorPara',
ATTRS: {
host: {
value: false
}
}
});
Y.namespace('Plugin');
Y.Plugin.EditorPara = EditorPara;