You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
364 lines
14 KiB
364 lines
14 KiB
2 years ago
|
/*
|
||
|
YUI 3.17.2 (build 9c3c78e)
|
||
|
Copyright 2014 Yahoo! Inc. All rights reserved.
|
||
|
Licensed under the BSD License.
|
||
|
http://yuilibrary.com/license/
|
||
|
*/
|
||
|
|
||
|
YUI.add('editor-para', function (Y, NAME) {
|
||
|
|
||
|
|
||
|
/**
|
||
|
* Plugin for Editor to paragraph auto wrapping and correction.
|
||
|
* @class Plugin.EditorPara
|
||
|
* @extends Plugin.EditorParaBase
|
||
|
* @constructor
|
||
|
* @module editor
|
||
|
* @submodule editor-para
|
||
|
*/
|
||
|
|
||
|
|
||
|
var EditorPara = function() {
|
||
|
EditorPara.superclass.constructor.apply(this, arguments);
|
||
|
}, HOST = 'host', NODE_CHANGE = 'nodeChange', PARENT_NODE = 'parentNode',
|
||
|
FIRST_P = '> p', P = 'p', BR = '<br>', FC = 'firstChild', LI = 'li';
|
||
|
|
||
|
|
||
|
Y.extend(EditorPara, Y.Plugin.EditorParaBase, {
|
||
|
/**
|
||
|
* Resolves the ROOT editor element.
|
||
|
* @method _getRoot
|
||
|
* @private
|
||
|
*/
|
||
|
_getRoot: function() {
|
||
|
return this.get(HOST).getInstance().EditorSelection.ROOT;
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* nodeChange handler to handle fixing an empty document.
|
||
|
* @private
|
||
|
* @method _onNodeChange
|
||
|
*/
|
||
|
_onNodeChange: function(e) {
|
||
|
var host = this.get(HOST), inst = host.getInstance(),
|
||
|
html, txt, par , d, sel, btag = inst.EditorSelection.DEFAULT_BLOCK_TAG,
|
||
|
inHTML, txt2, childs, aNode, node2, top, n, sib, para2, prev,
|
||
|
ps, br, item, p, imgs, t, LAST_CHILD = ':last-child', para, b, dir,
|
||
|
lc, lc2, found = false, root = this._getRoot(), start;
|
||
|
|
||
|
switch (e.changedType) {
|
||
|
case 'enter-up':
|
||
|
para = ((this._lastPara) ? this._lastPara : e.changedNode);
|
||
|
b = para.one('br.yui-cursor');
|
||
|
|
||
|
if (this._lastPara) {
|
||
|
delete this._lastPara;
|
||
|
}
|
||
|
|
||
|
if (b) {
|
||
|
if (b.previous() || b.next()) {
|
||
|
if (b.ancestor(P)) {
|
||
|
b.remove();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
if (!para.test(btag)) {
|
||
|
para2 = para.ancestor(btag);
|
||
|
if (para2) {
|
||
|
para = para2;
|
||
|
para2 = null;
|
||
|
}
|
||
|
}
|
||
|
if (para.test(btag)) {
|
||
|
prev = para.previous();
|
||
|
if (prev) {
|
||
|
lc = prev.one(LAST_CHILD);
|
||
|
while (!found) {
|
||
|
if (lc) {
|
||
|
lc2 = lc.one(LAST_CHILD);
|
||
|
if (lc2) {
|
||
|
lc = lc2;
|
||
|
} else {
|
||
|
found = true;
|
||
|
}
|
||
|
} else {
|
||
|
found = true;
|
||
|
}
|
||
|
}
|
||
|
if (lc) {
|
||
|
host.copyStyles(lc, para);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
break;
|
||
|
case 'enter':
|
||
|
if (Y.UA.webkit) {
|
||
|
//Webkit doesn't support shift+enter as a BR, this fixes that.
|
||
|
if (e.changedEvent.shiftKey) {
|
||
|
host.execCommand('insertbr');
|
||
|
e.changedEvent.preventDefault();
|
||
|
}
|
||
|
}
|
||
|
if (e.changedNode.test('li') && !Y.UA.ie) {
|
||
|
html = inst.EditorSelection.getText(e.changedNode);
|
||
|
if (html === '') {
|
||
|
par = e.changedNode.ancestor('ol,ul');
|
||
|
dir = par.getAttribute('dir');
|
||
|
if (dir !== '') {
|
||
|
dir = ' dir = "' + dir + '"';
|
||
|
}
|
||
|
par = e.changedNode.ancestor(inst.EditorSelection.BLOCKS);
|
||
|
d = inst.Node.create('<p' + dir + '>' + inst.EditorSelection.CURSOR + '</p>');
|
||
|
par.insert(d, 'after');
|
||
|
e.changedNode.remove();
|
||
|
e.changedEvent.halt();
|
||
|
|
||
|
sel = new inst.EditorSelection();
|
||
|
sel.selectNode(d, true, false);
|
||
|
}
|
||
|
}
|
||
|
//TODO Move this to a GECKO MODULE - Can't for the moment, requires no change to metadata (YMAIL)
|
||
|
if (Y.UA.gecko && host.get('defaultblock') !== 'p') {
|
||
|
par = e.changedNode;
|
||
|
|
||
|
if (!par.test(LI) && !par.ancestor(LI)) {
|
||
|
if (!par.test(btag)) {
|
||
|
par = par.ancestor(btag);
|
||
|
}
|
||
|
d = inst.Node.create('<' + btag + '></' + btag + '>');
|
||
|
par.insert(d, 'after');
|
||
|
sel = new inst.EditorSelection();
|
||
|
if (sel.anchorOffset) {
|
||
|
inHTML = sel.anchorNode.get('textContent');
|
||
|
|
||
|
txt = inst.one(inst.config.doc.createTextNode(inHTML.substr(0, sel.anchorOffset)));
|
||
|
txt2 = inst.one(inst.config.doc.createTextNode(inHTML.substr(sel.anchorOffset)));
|
||
|
|
||
|
aNode = sel.anchorNode;
|
||
|
aNode.setContent(''); //I
|
||
|
node2 = aNode.cloneNode(); //I
|
||
|
node2.append(txt2); //text
|
||
|
top = false;
|
||
|
sib = aNode; //I
|
||
|
while (!top) {
|
||
|
sib = sib.get(PARENT_NODE); //B
|
||
|
if (sib && !sib.test(btag)) {
|
||
|
n = sib.cloneNode();
|
||
|
n.set('innerHTML', '');
|
||
|
n.append(node2);
|
||
|
|
||
|
//Get children..
|
||
|
childs = sib.get('childNodes');
|
||
|
start = false;
|
||
|
/*jshint loopfunc: true */
|
||
|
childs.each(function(c) {
|
||
|
if (start) {
|
||
|
n.append(c);
|
||
|
}
|
||
|
if (c === aNode) {
|
||
|
start = true;
|
||
|
}
|
||
|
});
|
||
|
|
||
|
aNode = sib; //Top sibling
|
||
|
node2 = n;
|
||
|
} else {
|
||
|
top = true;
|
||
|
}
|
||
|
}
|
||
|
txt2 = node2;
|
||
|
sel.anchorNode.append(txt);
|
||
|
|
||
|
if (txt2) {
|
||
|
d.append(txt2);
|
||
|
}
|
||
|
}
|
||
|
if (d.get(FC)) {
|
||
|
d = d.get(FC);
|
||
|
}
|
||
|
d.prepend(inst.EditorSelection.CURSOR);
|
||
|
sel.focusCursor(true, true);
|
||
|
html = inst.EditorSelection.getText(d);
|
||
|
if (html !== '') {
|
||
|
inst.EditorSelection.cleanCursor();
|
||
|
}
|
||
|
e.changedEvent.preventDefault();
|
||
|
}
|
||
|
}
|
||
|
break;
|
||
|
case 'keyup':
|
||
|
if (Y.UA.gecko) {
|
||
|
if (root && root.getHTML().length < 20) {
|
||
|
if (!root.one(FIRST_P)) {
|
||
|
this._fixFirstPara();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
break;
|
||
|
case 'backspace-up':
|
||
|
case 'backspace-down':
|
||
|
case 'delete-up':
|
||
|
if (!Y.UA.ie) {
|
||
|
ps = root.all(FIRST_P);
|
||
|
item = root;
|
||
|
if (ps.item(0)) {
|
||
|
item = ps.item(0);
|
||
|
}
|
||
|
br = item.one('br');
|
||
|
if (br) {
|
||
|
br.removeAttribute('id');
|
||
|
br.removeAttribute('class');
|
||
|
}
|
||
|
|
||
|
txt = inst.EditorSelection.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).compareTo(root)) {
|
||
|
Y.log('Stopping the backspace event', 'warn', 'editor-para');
|
||
|
e.changedEvent.frameEvent.halt();
|
||
|
e.preventDefault();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
if (Y.UA.webkit) {
|
||
|
if (e.changedNode) {
|
||
|
//All backspace calls in Webkit need a preventDefault to
|
||
|
//stop history navigation #2531299
|
||
|
e.preventDefault();
|
||
|
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();
|
||
|
inst.EditorSelection.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);
|
||
|
|
||
|
this._fixGeckoOnBackspace(inst);
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
if (Y.UA.gecko) {
|
||
|
if (e.changedNode && !e.changedNode.test(btag)) {
|
||
|
p = e.changedNode.ancestor(btag);
|
||
|
if (p) {
|
||
|
this._lastPara = p;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
},
|
||
|
|
||
|
//If we just backspaced into a P on FF, we have to put the cursor
|
||
|
//before the BR that FF (usually) had injected when we used <ENTER> to
|
||
|
//leave the P.
|
||
|
_fixGeckoOnBackspace: function (inst) {
|
||
|
var sel = new inst.EditorSelection(),
|
||
|
node,
|
||
|
childNodes;
|
||
|
|
||
|
//not a cursor, not in a paragraph, or anchored at paragraph start.
|
||
|
if (!sel.isCollapsed || sel.anchorNode.get('nodeName') !== 'P' ||
|
||
|
sel.anchorOffset === 0) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
//cursor not on the injected final BR
|
||
|
childNodes = sel.anchorNode.get('childNodes');
|
||
|
node = sel.anchorNode.get('lastChild');
|
||
|
if (sel.anchorOffset !== childNodes.size() || node.get('nodeName') !== 'BR') {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
//empty P (only contains BR)
|
||
|
if (sel.anchorOffset === 1) {
|
||
|
sel.selectNode(sel.anchorNode, true);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
//We only expect injected BR behavior when last Node is text
|
||
|
node = node.get('previousSibling');
|
||
|
if (node.get('nodeType') !== Node.TEXT_NODE) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
offset = node.get('length');
|
||
|
|
||
|
// the cursor's position is strictly
|
||
|
// at the offset when this bug occurs
|
||
|
if (sel.getEditorOffset() === offset) {
|
||
|
sel.selectNode(node, true, offset);
|
||
|
}
|
||
|
},
|
||
|
|
||
|
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));
|
||
|
}
|
||
|
}, {
|
||
|
/**
|
||
|
* editorPara
|
||
|
* @static
|
||
|
* @property NAME
|
||
|
*/
|
||
|
NAME: 'editorPara',
|
||
|
/**
|
||
|
* editorPara
|
||
|
* @static
|
||
|
* @property NS
|
||
|
*/
|
||
|
NS: 'editorPara',
|
||
|
ATTRS: {
|
||
|
host: {
|
||
|
value: false
|
||
|
}
|
||
|
}
|
||
|
});
|
||
|
|
||
|
Y.namespace('Plugin');
|
||
|
|
||
|
Y.Plugin.EditorPara = EditorPara;
|
||
|
|
||
|
|
||
|
}, '3.17.2', {"requires": ["editor-para-base"]});
|