256
266
inst._use = inst.use;
257
267
inst.use = Y.bind(this.use, this);
259
268
this._iframe.setStyles({
260
269
visibility: 'inherit'
262
271
inst.one('body').setStyle('display', 'block');
273
this._fixIECursors();
277
* It appears that having a BR tag anywhere in the source "below" a table with a percentage width (in IE 7 & 8)
278
* if there is any TEXTINPUT's outside the iframe, the cursor will rapidly flickr and the CPU would occasionally
279
* spike. This method finds all <BR>'s below the sourceIndex of the first table. Does some checks to see if they
280
* can be modified and replaces then with a <WBR> so the layout will remain in tact, but the flickering will
282
* @method _fixIECursors
285
_fixIECursors: function() {
286
var inst = this.getInstance(),
287
tables = inst.all('table'),
288
brs = inst.all('br'), si;
290
if (tables.size() && brs.size()) {
292
si = tables.item(0).get('sourceIndex');
293
brs.each(function(n) {
294
var p = n.get('parentNode'),
295
c = p.get('children'), b = p.all('>br');
299
n.replace(inst.Node.create('<wbr>'));
301
if (n.get('sourceIndex') > si) {
303
n.replace(inst.Node.create('<wbr>'));
307
n.replace(inst.Node.create('<wbr>'));
565
690
* @description The default css used when creating the document.
568
DEFAULT_CSS: 'html { height: 95%; } body { padding: 7px; background-color: #fff; font: 13px/1.22 arial,helvetica,clean,sans-serif;*font-size:small;*font:x-small; } a, a:visited, a:hover { color: blue !important; text-decoration: underline !important; cursor: text !important; } img { cursor: pointer !important; border: none; }',
570
//DEFAULT_CSS: 'html { } body { margin: -15px 0 0 -15px; padding: 7px 0 0 15px; display: block; background-color: #fff; font: 13px/1.22 arial,helvetica,clean,sans-serif;*font-size:small;*font:x-small; }',
571
//DEFAULT_CSS: 'html { height: 95%; } body { height: 100%; padding: 7px; margin: 0 0 0 -7px; postion: relative; background-color: #fff; font: 13px/1.22 arial,helvetica,clean,sans-serif;*font-size:small;*font:x-small; } a, a:visited, a:hover { color: blue !important; text-decoration: underline !important; cursor: text !important; } img { cursor: pointer !important; border: none; }',
572
//DEFAULT_CSS: 'html { margin: 0; padding: 0; border: none; border-size: 0; } body { height: 97%; margin: 0; padding: 0; display: block; background-color: gray; font: 13px/1.22 arial,helvetica,clean,sans-serif;*font-size:small;*font:x-small; }',
693
//DEFAULT_CSS: 'html { height: 95%; } body { padding: 7px; background-color: #fff; font: 13px/1.22 arial,helvetica,clean,sans-serif;*font-size:small;*font:x-small; } a, a:visited, a:hover { color: blue !important; text-decoration: underline !important; cursor: text !important; } img { cursor: pointer !important; border: none; }',
694
DEFAULT_CSS: 'body { background-color: #fff; font: 13px/1.22 arial,helvetica,clean,sans-serif;*font-size:small;*font:x-small; } a, a:visited, a:hover { color: blue !important; text-decoration: underline !important; cursor: text !important; } img { cursor: pointer !important; border: none; }',
576
698
* @description The template string used to create the iframe
701
//HTML: '<iframe border="0" frameBorder="0" marginWidth="0" marginHeight="0" leftMargin="0" topMargin="0" allowTransparency="true" width="100%" height="99%"></iframe>',
579
702
HTML: '<iframe border="0" frameBorder="0" marginWidth="0" marginHeight="0" leftMargin="0" topMargin="0" allowTransparency="true" width="100%" height="99%"></iframe>',
580
//HTML: '<iframe border="0" frameBorder="0" width="100%" height="99%"></iframe>',
583
705
* @property PAGE_HTML
584
706
* @description The template used to create the page when created dynamically.
587
PAGE_HTML: '<html dir="{DIR}" lang="{LANG}"><head><title>{TITLE}</title>{META}<base href="{BASE_HREF}"/><style id="editor_css">{DEFAULT_CSS}</style>{EXTRA_CSS}</head><body>{CONTENT}</body></html>',
709
PAGE_HTML: '<html dir="{DIR}" lang="{LANG}"><head><title>{TITLE}</title>{META}<base href="{BASE_HREF}"/>{LINKED_CSS}<style id="editor_css">{DEFAULT_CSS}</style>{EXTRA_CSS}</head><body>{CONTENT}</body></html>',
714
* @description Parses document.doctype and generates a DocType to match the parent page, if supported.
715
* For IE8, it grabs document.all[0].nodeValue and uses that. For IE < 8, it falls back to Frame.DOC_TYPE.
716
* @returns {String} The normalized DocType to apply to the iframe
718
getDocType: function() {
719
var dt = Y.config.doc.doctype,
720
str = Frame.DOC_TYPE;
723
str = '<!DOCTYPE ' + dt.name + ((dt.publicId) ? ' ' + dt.publicId : '') + ((dt.systemId) ? ' ' + dt.systemId : '') + '>';
725
if (Y.config.doc.all) {
726
dt = Y.config.doc.all[0];
728
if (dt.nodeType === 8) {
730
if (dt.nodeValue.toLowerCase().indexOf('doctype') !== -1) {
731
str = '<!' + dt.nodeValue + '>';
590
742
* @property DOC_TYPE
884
1084
var endTime1 = (new Date()).getTime();
1086
Y.all('.hr').addClass('yui-skip').addClass('yui-non');
886
1088
Y.each(hrs, function(hr) {
887
1089
var el = doc.createElement('div');
888
el.className = 'hr yui-non';
889
el.setAttribute('style', 'border: 1px solid #ccc; line-height: 0; font-size: 0;margin-top: 5px; margin-bottom: 5px;');
1090
el.className = 'hr yui-non yui-skip';
890
1092
el.setAttribute('readonly', true);
891
1093
el.setAttribute('contenteditable', false); //Keep it from being Edited
892
1094
if (hr.parentNode) {
893
1095
hr.parentNode.replaceChild(el, hr);
1097
//Had to move to inline style. writes for ie's < 8. They don't render el.setAttribute('style');
1099
s.border = '1px solid #ccc';
1102
s.marginTop = '5px';
1103
s.marginBottom = '5px';
1104
s.marginLeft = '0px';
1105
s.marginRight = '0px';
898
1110
Y.each(classNames, function(v, k) {
899
1111
cssString += k + ' { font-family: ' + v.replace(/"/gi, '') + '; }';
1119
1357
* @return {String} The string of text
1121
1359
Y.Selection.getText = function(node) {
1122
var t = node.get('innerHTML').replace(Y.Selection.STRIP_HTML, ''),
1123
c = t.match(Y.Selection.REG_CHAR),
1124
s = t.match(Y.Selection.REG_NON);
1125
if (c === null && s) {
1360
var txt = node.get('innerHTML').replace(Y.Selection.REG_NOHTML, '');
1361
//Clean out the cursor subs to see if the Node is empty
1362
txt = txt.replace('<span><br></span>', '').replace('<br>', '');
1366
//Y.Selection.DEFAULT_BLOCK_TAG = 'div';
1367
Y.Selection.DEFAULT_BLOCK_TAG = 'p';
1132
1370
* The selector to use when looking for Nodes to cache the value of: [style],font[face]
1744
2026
insertandfocus: function(cmd, html) {
1745
2027
var inst = this.getInstance(), out, sel;
1746
html += inst.Selection.CURSOR;
1747
out = this.command('inserthtml', html);
1748
sel = new inst.Selection();
1749
sel.focusCursor(true, true);
2028
if (inst.Selection.hasCursor()) {
2029
html += inst.Selection.CURSOR;
2030
out = this.command('inserthtml', html);
2031
sel = new inst.Selection();
2032
sel.focusCursor(true, true);
2034
this.command('inserthtml', html);
1801
2087
return (new inst.Selection()).getSelected().removeClass(cls);
2090
* Adds a forecolor to the current selection, or creates a new element and applies it
2091
* @method COMMANDS.forecolor
2093
* @param {String} cmd The command executed: forecolor
2094
* @param {String} val The color value to apply
2095
* @return {NodeList} NodeList of the items touched by this command.
2097
forecolor: function(cmd, val) {
2098
var inst = this.getInstance(),
2099
sel = new inst.Selection(), n;
2102
this._command('useCSS', false);
2104
if (inst.Selection.hasCursor()) {
2105
if (sel.isCollapsed) {
2106
if (sel.anchorNode && (sel.anchorNode.get('innerHTML') === ' ')) {
2107
sel.anchorNode.setStyle('color', val);
2110
n = this.command('inserthtml', '<span style="color: ' + val + '">' + inst.Selection.CURSOR + '</span>');
2111
sel.focusCursor(true, true);
2115
return this._command(cmd, val);
2118
this._command(cmd, val);
1804
2122
* Adds a background color to the current selection, or creates a new element and applies it
1805
2123
* @method COMMANDS.backcolor
1808
2126
* @param {String} val The color value to apply
1809
2127
* @return {NodeList} NodeList of the items touched by this command.
1811
forecolor: function(cmd, val) {
1812
var inst = this.getInstance(),
1813
sel = new inst.Selection(), n;
1816
this._command('styleWithCSS', 'true');
1818
if (sel.isCollapsed) {
1819
if (sel.anchorNode && (sel.anchorNode.get('innerHTML') === ' ')) {
1820
sel.anchorNode.setStyle('color', val);
1823
n = this.command('inserthtml', '<span style="color: ' + val + '">' + inst.Selection.CURSOR + '</span>');
1824
sel.focusCursor(true, true);
1828
return this._command(cmd, val);
1831
this._command('styleWithCSS', false);
1834
2129
backcolor: function(cmd, val) {
1835
2130
var inst = this.getInstance(),
1836
2131
sel = new inst.Selection(), n;
1838
2133
if (Y.UA.gecko || Y.UA.opera) {
1839
2134
cmd = 'hilitecolor';
1841
2136
if (!Y.UA.ie) {
1842
this._command('styleWithCSS', 'true');
2137
this._command('useCSS', false);
1844
if (sel.isCollapsed) {
1845
if (sel.anchorNode && (sel.anchorNode.get('innerHTML') === ' ')) {
1846
sel.anchorNode.setStyle('backgroundColor', val);
2139
if (inst.Selection.hasCursor()) {
2140
if (sel.isCollapsed) {
2141
if (sel.anchorNode && (sel.anchorNode.get('innerHTML') === ' ')) {
2142
sel.anchorNode.setStyle('backgroundColor', val);
2145
n = this.command('inserthtml', '<span style="background-color: ' + val + '">' + inst.Selection.CURSOR + '</span>');
2146
sel.focusCursor(true, true);
1849
n = this.command('inserthtml', '<span style="background-color: ' + val + '">' + inst.Selection.CURSOR + '</span>');
1850
sel.focusCursor(true, true);
2150
return this._command(cmd, val);
1854
return this._command(cmd, val);
1857
this._command('styleWithCSS', false);
2153
this._command(cmd, val);
1877
2173
* @return {NodeList} NodeList of the items touched by this command.
1879
2175
fontname: function(cmd, val) {
2176
this._command('fontname', val);
1880
2177
var inst = this.getInstance(),
1881
sel = new inst.Selection(), n;
1883
if (sel.isCollapsed) {
1884
if (sel.anchorNode && (sel.anchorNode.get('innerHTML') === ' ')) {
1885
sel.anchorNode.setStyle('fontFamily', val);
1888
n = this.command('inserthtml', '<span style="font-family: ' + val + '">' + inst.Selection.CURSOR + '</span>');
1889
sel.focusCursor(true, true);
2178
sel = new inst.Selection();
2180
if (sel.isCollapsed && (this._lastKey != 32)) {
2181
if (sel.anchorNode.test('font')) {
2182
sel.anchorNode.set('face', val);
1893
return this._command('fontname', val);
1902
2192
* @return {NodeList} NodeList of the items touched by this command.
1904
2194
fontsize: function(cmd, val) {
2195
this._command('fontsize', val);
1905
2197
var inst = this.getInstance(),
1906
sel = new inst.Selection(), n, prev;
1908
if (sel.isCollapsed) {
1909
n = this.command('inserthtml', '<font size="' + val + '"> </font>');
1910
prev = n.get('previousSibling');
1911
if (prev && prev.get('nodeType') === 3) {
1912
if (prev.get('length') < 2) {
1916
sel.selectNode(n.get('firstChild'), true, false);
1919
return this._command('fontsize', val);
2198
sel = new inst.Selection();
2200
if (sel.isCollapsed && sel.anchorNode && (this._lastKey != 32)) {
2202
if (sel.anchorNode.getStyle('lineHeight')) {
2203
sel.anchorNode.setStyle('lineHeight', '');
2206
if (sel.anchorNode.test('font')) {
2207
sel.anchorNode.set('size', val);
2208
} else if (Y.UA.gecko) {
2209
var p = sel.anchorNode.ancestor(inst.Selection.DEFAULT_BLOCK_TAG);
2211
p.setStyle('fontSize', '');
2220
* This method is meant to normalize IE's in ability to exec the proper command on elements with CSS styling.
2223
* @param {String} cmd The command to execute
2224
* @param {String} tag The tag to create
2225
* @param {String} rule The rule that we are looking for.
2227
var fixIETags = function(cmd, tag, rule) {
2228
var inst = this.getInstance(),
2229
doc = inst.config.doc,
2230
sel = doc.selection.createRange(),
2231
o = doc.queryCommandValue(cmd),
2232
html, reg, m, p, d, s, c;
2235
html = sel.htmlText;
2236
reg = new RegExp(rule, 'g');
2237
m = html.match(reg);
2240
html = html.replace(rule + ';', '').replace(rule, '');
2242
sel.pasteHTML('<var id="yui-ie-bs">');
2244
p = doc.getElementById('yui-ie-bs');
2245
d = doc.createElement('div');
2246
s = doc.createElement(tag);
2249
if (p.parentNode !== inst.config.doc.body) {
2255
p.parentNode.replaceChild(s, p);
2257
Y.each(c, function(f) {
2261
sel.moveToElementText(s);
2269
ExecCommand.COMMANDS.bold = function() {
2270
fixIETags.call(this, 'bold', 'b', 'FONT-WEIGHT: bold');
2272
ExecCommand.COMMANDS.italic = function() {
2273
fixIETags.call(this, 'italic', 'i', 'FONT-STYLE: italic');
2275
ExecCommand.COMMANDS.underline = function() {
2276
fixIETags.call(this, 'underline', 'u', 'TEXT-DECORATION: underline');
1925
2280
Y.namespace('Plugin');
1926
2281
Y.Plugin.ExecCommand = ExecCommand;
1930
}, '3.2.0' ,{skinnable:false, requires:['frame']});
2285
}, '3.3.0' ,{requires:['frame'], skinnable:false});
1931
2286
YUI.add('editor-tab', function(Y) {
2151
2530
_lastBookmark: null,
2532
* Resolves the e.changedNode in the nodeChange event if it comes from the document. If
2533
* the event came from the document, it will get the last child of the last child of the document
2534
* and return that instead.
2535
* @method _resolveChangedNode
2536
* @param {Node} n The node to resolve
2539
_resolveChangedNode: function(n) {
2540
var inst = this.getInstance(), lc, lc2, found;
2541
if (inst && n && n.test('html')) {
2542
lc = inst.one(BODY).one(LAST_CHILD);
2545
lc2 = lc.one(LAST_CHILD);
2556
if (lc.test('br')) {
2557
if (lc.previous()) {
2560
lc = lc.get('parentNode');
2153
2572
* The default handler for the nodeChange event.
2154
2573
* @method _defNodeChangeFn
2155
2574
* @param {Event} e The event
2174
2600
switch (e.changedType) {
2175
2601
case 'keydown':
2176
inst.Selection.cleanCursor();
2180
//Webkit doesn't support shift+enter as a BR, this fixes that.
2181
if (e.changedEvent.shiftKey) {
2182
this.execCommand('insertbr');
2183
e.changedEvent.preventDefault();
2603
if (!EditorBase.NC_KEYS[e.changedEvent.keyCode] && !e.changedEvent.shiftKey && !e.changedEvent.ctrlKey && (e.changedEvent.keyCode !== 13)) {
2604
//inst.later(100, inst, inst.Selection.cleanCursor);
2188
2609
if (!e.changedNode.test('li, li *') && !e.changedEvent.shiftKey) {
2189
e.changedEvent.preventDefault();
2191
var sel = new inst.Selection();
2193
var cur = sel.getCursor();
2194
cur.insert(EditorBase.TABKEY, 'before');
2199
if (e.changedNode.test('p')) {
2200
var prev = e.changedNode.previous(), lc, lc2, found = false;
2202
lc = prev.one(':last-child');
2205
lc2 = lc.one(':last-child');
2216
this.copyStyles(lc, e.changedNode);
2610
e.changedEvent.frameEvent.preventDefault();
2612
this.execCommand('inserttext', '\t');
2613
} else if (Y.UA.gecko) {
2614
this.frame.exec._command('inserthtml', '<span> </span>');
2615
} else if (Y.UA.ie) {
2616
sel = new inst.Selection();
2617
sel._selection.pasteHTML(EditorBase.TABKEY);
2622
if (Y.UA.webkit && e.commands && (e.commands.indent || e.commands.outdent)) {
2624
* When executing execCommand 'indent or 'outdent' Webkit applies
2625
* a class to the BLOCKQUOTE that adds left/right margin to it
2626
* This strips that style so it is just a normal BLOCKQUOTE
2628
var bq = inst.all('.webkit-indent-blockquote');
2630
bq.setStyle('margin', '');
2223
2634
var changed = this.getDomPath(e.changedNode, false),
2224
2635
cmds = {}, family, fsize, classes = [],
2384
2802
this.frame.on('dom:mouseup', Y.bind(this._onFrameMouseUp, this));
2385
2803
this.frame.on('dom:mousedown', Y.bind(this._onFrameMouseDown, this));
2804
this.frame.on('dom:keydown', Y.bind(this._onFrameKeyDown, this));
2807
this.frame.on('dom:activate', Y.bind(this._onFrameActivate, this));
2808
this.frame.on('dom:beforedeactivate', Y.bind(this._beforeFrameDeactivate, this));
2387
2810
this.frame.on('dom:keyup', Y.bind(this._onFrameKeyUp, this));
2388
this.frame.on('dom:keydown', Y.bind(this._onFrameKeyDown, this));
2389
2811
this.frame.on('dom:keypress', Y.bind(this._onFrameKeyPress, this));
2391
//this.frame.on('dom:keydown', Y.throttle(Y.bind(this._onFrameKeyDown, this), 500));
2394
this.frame.on('dom:keydown', Y.bind(this._onFrameKeyDown, this));
2397
this.frame.on('dom:activate', Y.bind(this._onFrameActivate, this));
2398
this.frame.on('dom:keyup', Y.throttle(Y.bind(this._onFrameKeyUp, this), 800));
2399
this.frame.on('dom:keypress', Y.throttle(Y.bind(this._onFrameKeyPress, this), 800));
2401
this.frame.on('dom:keyup', Y.bind(this._onFrameKeyUp, this));
2402
this.frame.on('dom:keypress', Y.bind(this._onFrameKeyPress, this));
2405
2813
inst.Selection.filter();
2406
2814
this.fire('ready');
2817
* Caches the current cursor position in IE.
2818
* @method _beforeFrameDeactivate
2821
_beforeFrameDeactivate: function() {
2822
var inst = this.getInstance(),
2823
sel = inst.config.doc.selection.createRange();
2825
if ((!sel.compareEndPoints('StartToEnd', sel))) {
2826
sel.pasteHTML('<var id="yui-ie-cursor">');
2409
2830
* Moves the cached selection bookmark back so IE can place the cursor in the right place.
2410
2831
* @method _onFrameActivate
2413
2834
_onFrameActivate: function() {
2414
if (this._lastBookmark) {
2415
var inst = this.getInstance(),
2416
sel = inst.config.doc.selection.createRange(),
2417
bk = sel.moveToBookmark(this._lastBookmark);
2835
var inst = this.getInstance(),
2836
sel = new inst.Selection(),
2837
range = sel.createRange(),
2838
cur = inst.all('#yui-ie-cursor');
2421
this._lastBookmark = null;
2841
cur.each(function(n) {
2843
range.moveToElementText(n._node);
2844
range.move('character', -1);
2845
range.move('character', 1);
2458
2897
this._currentSelectionTimer = Y.later(850, this, function() {
2459
2898
this._currentSelectionClear = true;
2461
var inst = this.frame.getInstance(),
2462
sel = new inst.Selection(e);
2901
inst = this.frame.getInstance();
2902
sel = new inst.Selection(e);
2464
2904
this._currentSelection = sel;
2466
var sel = this._currentSelection;
2906
sel = this._currentSelection;
2468
var inst = this.frame.getInstance(),
2469
sel = new inst.Selection();
2471
this._currentSelection = sel;
2909
inst = this.frame.getInstance();
2910
sel = new inst.Selection();
2912
this._currentSelection = sel;
2473
2914
if (sel && sel.anchorNode) {
2474
2915
this.fire('nodeChange', { changedNode: sel.anchorNode, changedType: 'keydown', changedEvent: e.frameEvent });
2475
2916
if (EditorBase.NC_KEYS[e.keyCode]) {
3202
3708
var inst = this.getInstance(),
3203
3709
sel = new inst.Selection(),
3204
3710
returnValue, block,
3205
selected, selectedBlocks;
3711
selected, selectedBlocks, dir;
3207
3713
inst.Selection.filterBlocks();
3208
3714
if (sel.isCollapsed) { // No selection
3209
3715
block = EditorBidi.blockParent(sel.anchorNode);
3717
//If no direction is set, auto-detect the proper setting to make it "toggle"
3718
dir = block.getAttribute(DIR);
3719
if (!dir || dir == 'ltr') {
3210
3725
block.setAttribute(DIR, direction);
3211
3726
returnValue = block;
3212
3727
} else { // some text is selected
3213
3728
selected = sel.getSelected();
3214
3729
selectedBlocks = [];
3215
3730
selected.each(function(node) {
3216
if (!node.test(BODY)) { // workaround for a YUI bug
3732
* Temporarily removed this check, should already be fixed
3733
* in Y.Selection.getSelected()
3735
//if (!node.test(BODY)) { // workaround for a YUI bug
3217
3736
selectedBlocks.push(EditorBidi.blockParent(node));
3220
3739
selectedBlocks = inst.all(EditorBidi.addParents(selectedBlocks));
3221
3740
selectedBlocks.setAttribute(DIR, direction);
3222
3741
returnValue = selectedBlocks;
3225
this.get(HOST).get(HOST).editorBidi.checkForChange();
3744
this.get(HOST).get(HOST).editorBidi._checkForChange();
3226
3745
return returnValue;
3232
}, '3.2.0' ,{skinnable:false, requires:['editor-base', 'selection']});
3751
}, '3.3.0' ,{requires:['editor-base'], skinnable:false});
3233
3752
YUI.add('editor-para', function(Y) {
3270
3792
* @method _onNodeChange
3272
3794
_onNodeChange: function(e) {
3273
var host = this.get(HOST), inst = host.getInstance();
3795
var host = this.get(HOST), inst = host.getInstance(),
3796
html, txt, par , d, sel, btag = inst.Selection.DEFAULT_BLOCK_TAG,
3797
inHTML, txt2, childs, aNode, index, node2, top, n, sib,
3798
ps, br, item, p, imgs, t, LAST_CHILD = ':last-child';
3275
3800
switch (e.changedType) {
3802
var para = ((this._lastPara) ? this._lastPara : e.changedNode),
3803
b = para.one('br.yui-cursor');
3805
if (this._lastPara) {
3806
delete this._lastPara;
3810
if (b.previous() || b.next()) {
3814
if (!para.test(btag)) {
3815
var para2 = para.ancestor(btag);
3821
if (para.test(btag)) {
3822
var prev = para.previous(), lc, lc2, found = false;
3824
lc = prev.one(LAST_CHILD);
3827
lc2 = lc.one(LAST_CHILD);
3838
host.copyStyles(lc, para);
3845
//Webkit doesn't support shift+enter as a BR, this fixes that.
3846
if (e.changedEvent.shiftKey) {
3847
host.execCommand('insertbr');
3848
e.changedEvent.preventDefault();
3851
//TODO Move this to a GECKO MODULE - Can't for the moment, requires no change to metadata (YMAIL)
3852
if (Y.UA.gecko && host.get('defaultblock') !== 'p') {
3853
par = e.changedNode;
3855
if (!par.test(LI) && !par.ancestor(LI)) {
3856
if (!par.test(btag)) {
3857
par = par.ancestor(btag);
3859
d = inst.Node.create('<' + btag + '></' + btag + '>');
3860
par.insert(d, 'after');
3861
sel = new inst.Selection();
3862
if (sel.anchorOffset) {
3863
inHTML = sel.anchorNode.get('textContent');
3865
txt = inst.one(inst.config.doc.createTextNode(inHTML.substr(0, sel.anchorOffset)));
3866
txt2 = inst.one(inst.config.doc.createTextNode(inHTML.substr(sel.anchorOffset)));
3868
aNode = sel.anchorNode;
3869
aNode.setContent(''); //I
3870
node2 = aNode.cloneNode(); //I
3871
node2.append(txt2); //text
3875
sib = sib.get(PARENT_NODE); //B
3876
if (sib && !sib.test(btag)) {
3877
n = sib.cloneNode();
3878
n.set('innerHTML', '');
3882
childs = sib.get('childNodes');
3884
childs.each(function(c) {
3893
aNode = sib; //Top sibling
3900
sel.anchorNode.append(txt);
3909
d.prepend(inst.Selection.CURSOR);
3910
sel.focusCursor(true, true);
3911
html = inst.Selection.getText(d);
3913
inst.Selection.cleanCursor();
3915
e.changedEvent.preventDefault();
3276
3919
case 'keydown':
3277
3920
if (inst.config.doc.childNodes.length < 2) {
3278
3921
var cont = inst.config.doc.body.innerHTML;
3279
if (cont && cont.length < 5 && cont.toLowerCase() == '<br>') {
3922
if (cont && cont.length < 5 && cont.toLowerCase() == BR) {
3280
3923
this._fixFirstPara();
3284
3927
case 'backspace-up':
3928
case 'backspace-down':
3285
3929
case 'delete-up':
3286
var ps = inst.all(FIRST_P), br, item;
3287
if (ps.size() < 2) {
3931
ps = inst.all(FIRST_P);
3288
3932
item = inst.one(BODY);
3289
3933
if (ps.item(0)) {
3290
3934
item = ps.item(0);
3292
if (inst.Selection.getText(item) === '' && !item.test('p')) {
3293
this._fixFirstPara();
3294
} else if (item.test('p') && item.get('innerHTML').length === 0) {
3295
e.changedEvent.halt();
3936
br = item.one('br');
3938
br.removeAttribute('id');
3939
br.removeAttribute('class');
3942
txt = inst.Selection.getText(item);
3943
txt = txt.replace(/ /g, '').replace(/\n/g, '');
3944
imgs = item.all('img');
3946
if (txt.length === 0 && !imgs.size()) {
3947
//God this is horrible..
3948
if (!item.test(P)) {
3949
this._fixFirstPara();
3952
if (e.changedNode && e.changedNode.test(P)) {
3955
if (!p && host._lastPara && host._lastPara.inDoc()) {
3958
if (p && !p.test(P)) {
3962
if (!p.previous() && p.get(PARENT_NODE) && p.get(PARENT_NODE).test(BODY)) {
3963
e.changedEvent.frameEvent.halt();
3968
if (e.changedNode) {
3969
item = e.changedNode;
3970
if (item.test('li') && (!item.previous() && !item.next())) {
3971
html = item.get('innerHTML').replace(BR, '');
3973
if (item.get(PARENT_NODE)) {
3974
item.get(PARENT_NODE).replace(inst.Node.create(BR));
3975
e.changedEvent.frameEvent.halt();
3977
inst.Selection.filterBlocks();
3986
* This forced FF to redraw the content on backspace.
3987
* On some occasions FF will leave a cursor residue after content has been deleted.
3988
* Dropping in the empty textnode and then removing it causes FF to redraw and
3989
* remove the "ghost cursors"
3992
t = inst.config.doc.createTextNode(' ');
3999
if (e.changedNode && !e.changedNode.test(btag)) {
4000
var p = e.changedNode.ancestor(btag);
3330
4039
var host = this.get(HOST), inst = host.getInstance(),
3331
4040
sel = new inst.Selection();
3335
4042
Y.later(50, host, function() {
3336
4043
inst.Selection.filterBlocks();
3337
sel.focusCursor(true, true);
3341
4047
initializer: function() {
3342
4048
var host = this.get(HOST);
4049
if (host.editorBR) {
4050
Y.error('Can not plug EditorPara and EditorBR at the same time.');
3344
4054
host.on(NODE_CHANGE, Y.bind(this._onNodeChange, this));
3345
4055
host.after('ready', Y.bind(this._afterEditorReady, this));
3346
4056
host.after('contentChange', Y.bind(this._afterContentChange, this));
3347
host.after('dom:paste', Y.bind(this._afterPaste, this));
4058
host.after('dom:paste', Y.bind(this._afterPaste, this));
3353
4065
* @property NAME
3355
4067
NAME: 'editorPara',
3375
}, '3.2.0' ,{skinnable:false, requires:['editor-base', 'selection']});
3378
YUI.add('editor', function(Y){}, '3.2.0' ,{use:['frame', 'selection', 'exec-command', 'editor-base'], skinnable:false});
4087
}, '3.3.0' ,{requires:['node'], skinnable:false});
4088
YUI.add('editor-br', function(Y) {
4093
* Plugin for Editor to normalize BR's.
4095
* @submodule editor-br
4098
* Plugin for Editor to normalize BR's.
4099
* @class Plugin.EditorBR
4105
var EditorBR = function() {
4106
EditorBR.superclass.constructor.apply(this, arguments);
4107
}, HOST = 'host', LI = 'li';
4110
Y.extend(EditorBR, Y.Base, {
4112
* Frame keyDown handler that normalizes BR's when pressing ENTER.
4114
* @method _onKeyDown
4116
_onKeyDown: function(e) {
4121
if (e.keyCode == 13) {
4122
var host = this.get(HOST), inst = host.getInstance(),
4123
sel = new inst.Selection(),
4128
if (!sel.anchorNode || (!sel.anchorNode.test(LI) && !sel.anchorNode.ancestor(LI))) {
4129
sel._selection.pasteHTML('<br>');
4130
sel._selection.collapse(false);
4131
sel._selection.select();
4136
if (!sel.anchorNode.test(LI) && !sel.anchorNode.ancestor(LI)) {
4137
host.frame._execCommand('insertlinebreak', null);
4145
* Adds listeners for keydown in IE and Webkit. Also fires insertbeonreturn for supporting browsers.
4147
* @method _afterEditorReady
4149
_afterEditorReady: function() {
4150
var inst = this.get(HOST).getInstance();
4152
inst.config.doc.execCommand('insertbronreturn', null, true);
4155
if (Y.UA.ie || Y.UA.webkit) {
4156
inst.on('keydown', Y.bind(this._onKeyDown, this), inst.config.doc);
4160
* Adds a nodeChange listener only for FF, in the event of a backspace or delete, it creates an empy textNode
4161
* inserts it into the DOM after the e.changedNode, then removes it. Causing FF to redraw the content.
4163
* @method _onNodeChange
4164
* @param {Event} e The nodeChange event.
4166
_onNodeChange: function(e) {
4167
switch (e.changedType) {
4168
case 'backspace-up':
4169
case 'backspace-down':
4172
* This forced FF to redraw the content on backspace.
4173
* On some occasions FF will leave a cursor residue after content has been deleted.
4174
* Dropping in the empty textnode and then removing it causes FF to redraw and
4175
* remove the "ghost cursors"
4177
var inst = this.get(HOST).getInstance();
4178
var d = e.changedNode;
4179
var t = inst.config.doc.createTextNode(' ');
4185
initializer: function() {
4186
var host = this.get(HOST);
4187
if (host.editorPara) {
4188
Y.error('Can not plug EditorBR and EditorPara at the same time.');
4191
host.after('ready', Y.bind(this._afterEditorReady, this));
4193
host.on('nodeChange', Y.bind(this._onNodeChange, this));
4216
Y.namespace('Plugin');
4218
Y.Plugin.EditorBR = EditorBR;
4222
}, '3.3.0' ,{requires:['node'], skinnable:false});
4225
YUI.add('editor', function(Y){}, '3.3.0' ,{skinnable:false, use:['frame', 'selection', 'exec-command', 'editor-base', 'editor-para', 'editor-br', 'editor-bidi', 'createlink-base']});