/** * $Id: ForceBlocks.js 1137 2009-05-22 15:13:40Z spocke $ * * @author Moxiecode * @copyright Copyright © 2004-2008, Moxiecode Systems AB, All rights reserved. */ (function(tinymce) { // Shorten names var Event, isIE, isGecko, isOpera, each, extend; Event = tinymce.dom.Event; isIE = tinymce.isIE; isGecko = tinymce.isGecko; isOpera = tinymce.isOpera; each = tinymce.each; extend = tinymce.extend; function isEmpty(n) { n = n.innerHTML; n = n.replace(/<(img|hr|table|input|select|textarea)[ \>]/gi, '-'); // Keep these convert them to - chars n = n.replace(/<[^>]+>/g, ''); // Remove all tags return n.replace(/[ \t\r\n]+/g, '') == ''; }; /** * This is a internal class and no method in this class should be called directly form the out side. */ tinymce.create('tinymce.ForceBlocks', { ForceBlocks : function(ed) { var t = this, s = ed.settings, elm; t.editor = ed; t.dom = ed.dom; elm = (s.forced_root_block || 'p').toLowerCase(); s.element = elm.toUpperCase(); ed.onPreInit.add(t.setup, t); t.reOpera = new RegExp('(\\u00a0| | )<\/' + elm + '>', 'gi'); t.rePadd = new RegExp('
]+)><\\\/p>|
]+)\\\/>|
]+)>\\s+<\\\/p>|
<\\\/p>|
|\\s+<\\\/p>'.replace(/p/g, elm), 'gi'); t.reNbsp2BR1 = new RegExp('
]+)>[\\s\\u00a0]+<\\\/p>|
[\\s\\u00a0]+<\\\/p>'.replace(/p/g, elm), 'gi'); t.reNbsp2BR2 = new RegExp('<%p()([^>]+)>( | )<\\\/%p>|<%p>( | )<\\\/%p>'.replace(/%p/g, elm), 'gi'); t.reBR2Nbsp = new RegExp('
]+)>\\s*
\\s*<\\\/p>|
\\s*
\\s*<\\\/p>'.replace(/p/g, elm), 'gi');
function padd(ed, o) {
if (isOpera)
o.content = o.content.replace(t.reOpera, '' + elm + '>');
o.content = o.content.replace(t.rePadd, '<' + elm + '$1$2$3$4$5$6>\u00a0' + elm + '>');
if (!isIE && !isOpera && o.set) {
// Use instead of BR in padded paragraphs
o.content = o.content.replace(t.reNbsp2BR1, '<' + elm + '$1$2>
' + elm + '>');
o.content = o.content.replace(t.reNbsp2BR2, '<' + elm + '$1$2>
' + elm + '>');
} else
o.content = o.content.replace(t.reBR2Nbsp, '<' + elm + '$1$2>\u00a0' + elm + '>');
};
ed.onBeforeSetContent.add(padd);
ed.onPostProcess.add(padd);
if (s.forced_root_block) {
ed.onInit.add(t.forceRoots, t);
ed.onSetContent.add(t.forceRoots, t);
ed.onBeforeGetContent.add(t.forceRoots, t);
}
},
setup : function() {
var t = this, ed = t.editor, s = ed.settings;
// Force root blocks when typing and when getting output
if (s.forced_root_block) {
ed.onKeyUp.add(t.forceRoots, t);
ed.onPreProcess.add(t.forceRoots, t);
}
if (s.force_br_newlines) {
// Force IE to produce BRs on enter
if (isIE) {
ed.onKeyPress.add(function(ed, e) {
var n, s = ed.selection;
if (e.keyCode == 13 && s.getNode().nodeName != 'LI') {
s.setContent('
', {format : 'raw'});
n = ed.dom.get('__');
n.removeAttribute('id');
s.select(n);
s.collapse();
return Event.cancel(e);
}
});
}
return;
}
if (!isIE && s.force_p_newlines) {
/* ed.onPreProcess.add(function(ed, o) {
each(ed.dom.select('br', o.node), function(n) {
var p = n.parentNode;
// Replace
if (p && p.nodeName == 'p' && (p.childNodes.length == 1 || p.lastChild == n)) { p.replaceChild(ed.getDoc().createTextNode('\u00a0'), n); } }); });*/ ed.onKeyPress.add(function(ed, e) { if (e.keyCode == 13 && !e.shiftKey) { if (!t.insertPara(e)) Event.cancel(e); } }); if (isGecko) { ed.onKeyDown.add(function(ed, e) { if ((e.keyCode == 8 || e.keyCode == 46) && !e.shiftKey) t.backspaceDelete(e, e.keyCode == 8); }); } } function ren(rn, na) { var ne = ed.dom.create(na); each(rn.attributes, function(a) { if (a.specified && a.nodeValue) ne.setAttribute(a.nodeName.toLowerCase(), a.nodeValue); }); each(rn.childNodes, function(n) { ne.appendChild(n.cloneNode(true)); }); rn.parentNode.replaceChild(ne, rn); return ne; }; // Padd empty inline elements within block elements // For example:
becomes
ed.onPreProcess.add(function(ed, o) { each(ed.dom.select('p,h1,h2,h3,h4,h5,h6,div', o.node), function(p) { if (isEmpty(p)) { each(ed.dom.select('span,em,strong,b,i', o.node), function(n) { if (!n.hasChildNodes()) { n.appendChild(ed.getDoc().createTextNode('\u00a0')); return false; // Break the loop one padding is enough } }); } }); }); // IE specific fixes if (isIE) { // Replaces IE:s auto generated paragraphs with the specified element name if (s.element != 'P') { ed.onKeyPress.add(function(ed, e) { t.lastElm = ed.selection.getNode().nodeName; }); ed.onKeyUp.add(function(ed, e) { var bl, sel = ed.selection, n = sel.getNode(), b = ed.getBody(); if (b.childNodes.length === 1 && n.nodeName == 'P') { n = ren(n, s.element); sel.select(n); sel.collapse(); ed.nodeChanged(); } else if (e.keyCode == 13 && !e.shiftKey && t.lastElm != 'P') { bl = ed.dom.getParent(n, 'p'); if (bl) { ren(bl, s.element); ed.nodeChanged(); } } }); } } }, find : function(n, t, s) { var ed = this.editor, w = ed.getDoc().createTreeWalker(n, 4, null, false), c = -1; while (n = w.nextNode()) { c++; // Index by node if (t == 0 && n == s) return c; // Node by index if (t == 1 && c == s) return n; } return -1; }, forceRoots : function(ed, e) { var t = this, ed = t.editor, b = ed.getBody(), d = ed.getDoc(), se = ed.selection, s = se.getSel(), r = se.getRng(), si = -2, ei, so, eo, tr, c = -0xFFFFFF; var nx, bl, bp, sp, le, nl = b.childNodes, i, n, eid; // Fix for bug #1863847 //if (e && e.keyCode == 13) // return true; // Wrap non blocks into blocks for (i = nl.length - 1; i >= 0; i--) { nx = nl[i]; // Is text or non block element if (nx.nodeType == 3 || (!t.dom.isBlock(nx) && nx.nodeType != 8)) { if (!bl) { // Create new block but ignore whitespace if (nx.nodeType != 3 || /[^\s]/g.test(nx.nodeValue)) { // Store selection if (si == -2 && r) { if (!isIE) { // If selection is element then mark it if (r.startContainer.nodeType == 1 && (n = r.startContainer.childNodes[r.startOffset]) && n.nodeType == 1) { // Save the id of the selected element eid = n.getAttribute("id"); n.setAttribute("id", "__mce"); } else { // If element is inside body, might not be the case in contentEdiable mode if (ed.dom.getParent(r.startContainer, function(e) {return e === b;})) { so = r.startOffset; eo = r.endOffset; si = t.find(b, 0, r.startContainer); ei = t.find(b, 0, r.endContainer); } } } else { tr = d.body.createTextRange(); tr.moveToElementText(b); tr.collapse(1); bp = tr.move('character', c) * -1; tr = r.duplicate(); tr.collapse(1); sp = tr.move('character', c) * -1; tr = r.duplicate(); tr.collapse(0); le = (tr.move('character', c) * -1) - sp; si = sp - bp; ei = le; } } bl = ed.dom.create(ed.settings.forced_root_block); bl.appendChild(nx.cloneNode(1)); nx.parentNode.replaceChild(bl, nx); } } else { if (bl.hasChildNodes()) bl.insertBefore(nx, bl.firstChild); else bl.appendChild(nx); } } else bl = null; // Time to create new block } // Restore selection if (si != -2) { if (!isIE) { bl = b.getElementsByTagName(ed.settings.element)[0]; r = d.createRange(); // Select last location or generated block if (si != -1) r.setStart(t.find(b, 1, si), so); else r.setStart(bl, 0); // Select last location or generated block if (ei != -1) r.setEnd(t.find(b, 1, ei), eo); else r.setEnd(bl, 0); if (s) { s.removeAllRanges(); s.addRange(r); } } else { try { r = s.createRange(); r.moveToElementText(b); r.collapse(1); r.moveStart('character', si); r.moveEnd('character', ei); r.select(); } catch (ex) { // Ignore } } } else if (!isIE && (n = ed.dom.get('__mce'))) { // Restore the id of the selected element if (eid) n.setAttribute('id', eid); else n.removeAttribute('id'); // Move caret before selected element r = d.createRange(); r.setStartBefore(n); r.setEndBefore(n); se.setRng(r); } }, getParentBlock : function(n) { var d = this.dom; return d.getParent(n, d.isBlock); }, insertPara : function(e) { var t = this, ed = t.editor, dom = ed.dom, d = ed.getDoc(), se = ed.settings, s = ed.selection.getSel(), r = s.getRangeAt(0), b = d.body; var rb, ra, dir, sn, so, en, eo, sb, eb, bn, bef, aft, sc, ec, n, vp = dom.getViewPort(ed.getWin()), y, ch, car; // If root blocks are forced then use Operas default behavior since it's really good // Removed due to bug: #1853816 // if (se.forced_root_block && isOpera) // return true; // Setup before range rb = d.createRange(); // If is before the first block element and in body, then move it into first block element rb.setStart(s.anchorNode, s.anchorOffset); rb.collapse(true); // Setup after range ra = d.createRange(); // If is before the first block element and in body, then move it into first block element ra.setStart(s.focusNode, s.focusOffset); ra.collapse(true); // Setup start/end points dir = rb.compareBoundaryPoints(rb.START_TO_END, ra) < 0; sn = dir ? s.anchorNode : s.focusNode; so = dir ? s.anchorOffset : s.focusOffset; en = dir ? s.focusNode : s.anchorNode; eo = dir ? s.focusOffset : s.anchorOffset; // If selection is in empty table cell if (sn === en && /^(TD|TH)$/.test(sn.nodeName)) { if (sn.firstChild.nodeName == 'BR') dom.remove(sn.firstChild); // Remove BR // Create two new block elements if (sn.childNodes.length == 0) { ed.dom.add(sn, se.element, null, '