5bac5f7ccd
This plugin can automatically format text patterns as you type. It includes two patterns: unordered (`* ` and `- `) and ordered list (`1. ` and `1) `). If the transformation in unwanted, the user can undo the change by pressing backspace, using the undo shortcut, or the undo button in the toolbar. This is the first TinyMCE plugin that has unit tests and there's some good groundwork for adding tests to existing plugins in the future. First run. See #31441. git-svn-id: https://develop.svn.wordpress.org/trunk@32699 602fd350-edb4-49c9-b593-d223f7449a82
426 lines
10 KiB
JavaScript
426 lines
10 KiB
JavaScript
(function() {
|
|
function fontFace(face) {
|
|
if (tinymce.isOpera) {
|
|
return "'" + face + "'";
|
|
} else {
|
|
return face;
|
|
}
|
|
}
|
|
|
|
function findContainer(selector) {
|
|
var container;
|
|
if (tinymce.is(selector, 'string')) {
|
|
container = editor.dom.select(selector)[0];
|
|
} else {
|
|
container = selector;
|
|
}
|
|
if (container.firstChild) {
|
|
container = container.firstChild;
|
|
}
|
|
return container;
|
|
}
|
|
|
|
function setSelection(startSelector, startOffset, endSelector, endOffset) {
|
|
if (!endSelector) {
|
|
endSelector = startSelector;
|
|
endOffset = startOffset;
|
|
}
|
|
var startContainer = findContainer(startSelector);
|
|
var endContainer = findContainer(endSelector);
|
|
var rng = editor.dom.createRng();
|
|
|
|
function setRange(container, offset, start) {
|
|
offset = offset || 0;
|
|
|
|
if (offset === 'after') {
|
|
if (start) {
|
|
rng.setStartAfter(container);
|
|
} else {
|
|
rng.setEndAfter(container);
|
|
}
|
|
return;
|
|
} else if (offset === 'afterNextCharacter') {
|
|
container = container.nextSibling;
|
|
offset = 1;
|
|
}
|
|
if (start) {
|
|
rng.setStart(container, offset);
|
|
} else {
|
|
rng.setEnd(container, offset);
|
|
}
|
|
}
|
|
|
|
setRange(startContainer, startOffset, true);
|
|
setRange(endContainer, endOffset, false);
|
|
editor.selection.setRng(rng);
|
|
}
|
|
|
|
function trimContent(content) {
|
|
return content.replace(/^<p> <\/p>\n?/, '').replace(/\n?<p> <\/p>$/, '');
|
|
}
|
|
|
|
/**
|
|
* Fakes a key event.
|
|
*
|
|
* @param {Element/String} e DOM element object or element id to send fake event to.
|
|
* @param {String} na Event name to fake like "keydown".
|
|
* @param {Object} o Optional object with data to send with the event like keyCode and charCode.
|
|
*/
|
|
function fakeKeyEvent(e, na, o) {
|
|
var ev;
|
|
|
|
o = tinymce.extend({
|
|
keyCode : 13,
|
|
charCode : 0
|
|
}, o);
|
|
|
|
e = tinymce.DOM.get(e);
|
|
|
|
if (e.fireEvent) {
|
|
ev = document.createEventObject();
|
|
tinymce.extend(ev, o);
|
|
e.fireEvent('on' + na, ev);
|
|
return;
|
|
}
|
|
|
|
if (document.createEvent) {
|
|
try {
|
|
// Fails in Safari
|
|
ev = document.createEvent('KeyEvents');
|
|
ev.initKeyEvent(na, true, true, window, false, false, false, false, o.keyCode, o.charCode);
|
|
} catch (ex) {
|
|
ev = document.createEvent('Events');
|
|
ev.initEvent(na, true, true);
|
|
|
|
ev.keyCode = o.keyCode;
|
|
ev.charCode = o.charCode;
|
|
}
|
|
} else {
|
|
ev = document.createEvent('UIEvents');
|
|
|
|
if (ev.initUIEvent) {
|
|
ev.initUIEvent(na, true, true, window, 1);
|
|
}
|
|
|
|
ev.keyCode = o.keyCode;
|
|
ev.charCode = o.charCode;
|
|
}
|
|
|
|
e.dispatchEvent(ev);
|
|
}
|
|
|
|
function normalizeRng(rng) {
|
|
if (rng.startContainer.nodeType == 3) {
|
|
if (rng.startOffset === 0) {
|
|
rng.setStartBefore(rng.startContainer);
|
|
} else if (rng.startOffset >= rng.startContainer.nodeValue.length - 1) {
|
|
rng.setStartAfter(rng.startContainer);
|
|
}
|
|
}
|
|
|
|
if (rng.endContainer.nodeType == 3) {
|
|
if (rng.endOffset === 0) {
|
|
rng.setEndBefore(rng.endContainer);
|
|
} else if (rng.endOffset >= rng.endContainer.nodeValue.length - 1) {
|
|
rng.setEndAfter(rng.endContainer);
|
|
}
|
|
}
|
|
|
|
return rng;
|
|
}
|
|
|
|
// TODO: Replace this with the new event logic in 3.5
|
|
function type(chr) {
|
|
var editor = tinymce.activeEditor, keyCode, charCode, evt, startElm, rng, startContainer, startOffset, textNode;
|
|
|
|
function charCodeToKeyCode(charCode) {
|
|
var lookup = {
|
|
'0': 48, '1': 49, '2': 50, '3': 51, '4': 52, '5': 53, '6': 54, '7': 55, '8': 56, '9': 57,'a': 65, 'b': 66, 'c': 67,
|
|
'd': 68, 'e': 69, 'f': 70, 'g': 71, 'h': 72, 'i': 73, 'j': 74, 'k': 75, 'l': 76, 'm': 77, 'n': 78, 'o': 79, 'p': 80, 'q': 81,
|
|
'r': 82, 's': 83, 't': 84, 'u': 85, 'v': 86, 'w': 87, 'x': 88, 'y': 89, ' ': 32, ',': 188, '-': 189, '.': 190, '/': 191, '\\': 220,
|
|
'[': 219, ']': 221, '\'': 222, ';': 186, '=': 187, ')': 41
|
|
};
|
|
|
|
return lookup[String.fromCharCode(charCode)];
|
|
}
|
|
|
|
function fakeEvent(target, type, evt) {
|
|
editor.dom.fire(target, type, evt);
|
|
}
|
|
|
|
// Numeric keyCode
|
|
if (typeof(chr) == "number") {
|
|
charCode = chr;
|
|
keyCode = charCodeToKeyCode(charCode);
|
|
} else if (typeof(chr) == "string") {
|
|
// String value
|
|
if (chr == '\b') {
|
|
keyCode = 8;
|
|
charCode = chr.charCodeAt(0);
|
|
} else if (chr == '\n') {
|
|
keyCode = 13;
|
|
charCode = chr.charCodeAt(0);
|
|
} else {
|
|
charCode = chr.charCodeAt(0);
|
|
keyCode = charCodeToKeyCode(charCode);
|
|
}
|
|
} else {
|
|
evt = chr;
|
|
|
|
if (evt.charCode) {
|
|
chr = String.fromCharCode(evt.charCode);
|
|
}
|
|
|
|
if (evt.keyCode) {
|
|
keyCode = evt.keyCode;
|
|
}
|
|
}
|
|
|
|
evt = evt || {keyCode: keyCode, charCode: charCode};
|
|
|
|
startElm = editor.selection.getStart();
|
|
fakeEvent(startElm, 'keydown', evt);
|
|
fakeEvent(startElm, 'keypress', evt);
|
|
|
|
if (!evt.isDefaultPrevented()) {
|
|
if (keyCode == 8) {
|
|
if (editor.getDoc().selection) {
|
|
rng = editor.getDoc().selection.createRange();
|
|
|
|
if (rng.text.length === 0) {
|
|
rng.moveStart('character', -1);
|
|
rng.select();
|
|
}
|
|
|
|
rng.execCommand('Delete', false, null);
|
|
} else {
|
|
rng = editor.selection.getRng();
|
|
startContainer = rng.startContainer;
|
|
|
|
if (startContainer.nodeType == 1 && rng.collapsed) {
|
|
var nodes = rng.startContainer.childNodes;
|
|
startContainer = nodes[nodes.length - 1];
|
|
}
|
|
|
|
// If caret is at <p>abc|</p> and after the abc text node then move it to the end of the text node
|
|
// Expand the range to include the last char <p>ab[c]</p> since IE 11 doesn't delete otherwise
|
|
if ( rng.collapsed && startContainer && startContainer.nodeType == 3 && startContainer.data.length > 0) {
|
|
rng.setStart(startContainer, startContainer.data.length - 1);
|
|
rng.setEnd(startContainer, startContainer.data.length);
|
|
editor.selection.setRng(rng);
|
|
}
|
|
|
|
editor.getDoc().execCommand('Delete', false, null);
|
|
}
|
|
} else if (typeof(chr) == 'string') {
|
|
rng = editor.selection.getRng(true);
|
|
|
|
if (rng.startContainer.nodeType == 3 && rng.collapsed) {
|
|
// `insertData` may alter the range.
|
|
startContainer = rng.startContainer;
|
|
startOffset = rng.startOffset;
|
|
rng.startContainer.insertData( rng.startOffset, chr );
|
|
rng.setStart( startContainer, startOffset + 1 );
|
|
} else {
|
|
textNode = editor.getDoc().createTextNode(chr);
|
|
rng.insertNode(textNode);
|
|
rng.setStart(textNode, 1);
|
|
}
|
|
|
|
rng.collapse(true);
|
|
editor.selection.setRng(rng);
|
|
}
|
|
}
|
|
|
|
fakeEvent(startElm, 'keyup', evt);
|
|
}
|
|
|
|
function cleanHtml(html) {
|
|
html = html.toLowerCase().replace(/[\r\n]+/gi, '');
|
|
html = html.replace(/ (sizcache[0-9]+|sizcache|nodeindex|sizset[0-9]+|sizset|data\-mce\-expando|data\-mce\-selected)="[^"]*"/gi, '');
|
|
html = html.replace(/<span[^>]+data-mce-bogus[^>]+>[\u200B\uFEFF]+<\/span>|<div[^>]+data-mce-bogus[^>]+><\/div>/gi, '');
|
|
html = html.replace(/ style="([^"]+)"/gi, function(val1, val2) {
|
|
val2 = val2.replace(/;$/, '');
|
|
return ' style="' + val2.replace(/\:([^ ])/g, ': $1') + ';"';
|
|
});
|
|
|
|
return html;
|
|
}
|
|
|
|
function normalizeHtml(html) {
|
|
var writer = new tinymce.html.Writer();
|
|
|
|
new tinymce.html.SaxParser({
|
|
validate: false,
|
|
comment: writer.comment,
|
|
cdata: writer.cdata,
|
|
text: writer.text,
|
|
end: writer.end,
|
|
pi: writer.pi,
|
|
doctype: writer.doctype,
|
|
|
|
start: function(name, attrs, empty) {
|
|
attrs.sort(function(a, b) {
|
|
if (a.name === b.name) {
|
|
return 0;
|
|
}
|
|
|
|
return a.name > b.name ? 1 : -1;
|
|
});
|
|
|
|
writer.start(name, attrs, empty);
|
|
}
|
|
}).parse(html);
|
|
|
|
return writer.getContent();
|
|
}
|
|
|
|
/**
|
|
* Measures the x, y, w, h of the specified element/control relative to the view element.
|
|
*/
|
|
function rect(ctrl) {
|
|
var outerRect, innerRect;
|
|
|
|
if (ctrl.nodeType) {
|
|
innerRect = ctrl.getBoundingClientRect();
|
|
} else {
|
|
innerRect = ctrl.getEl().getBoundingClientRect();
|
|
}
|
|
|
|
outerRect = document.getElementById('view').getBoundingClientRect();
|
|
|
|
return [
|
|
Math.round(innerRect.left - outerRect.left),
|
|
Math.round(innerRect.top - outerRect.top),
|
|
Math.round(innerRect.right - innerRect.left),
|
|
Math.round(innerRect.bottom - innerRect.top)
|
|
];
|
|
}
|
|
|
|
function size(ctrl) {
|
|
return rect(ctrl).slice(2);
|
|
}
|
|
|
|
function resetScroll(elm) {
|
|
elm.scrollTop = 0;
|
|
elm.scrollLeft = 0;
|
|
}
|
|
|
|
// Needed since fonts render differently on different platforms
|
|
function nearlyEqualRects(rect1, rect2, diff) {
|
|
diff = diff || 1;
|
|
|
|
for (var i = 0; i < 4; i++) {
|
|
if (Math.abs(rect1[i] - rect2[i]) > diff) {
|
|
deepEqual(rect1, rect2);
|
|
return;
|
|
}
|
|
}
|
|
|
|
ok(true);
|
|
}
|
|
|
|
function getFontmostWindow() {
|
|
return editor.windowManager.windows[editor.windowManager.windows.length - 1];
|
|
}
|
|
|
|
function pressArrowKey(evt) {
|
|
var dom = editor.dom, target = editor.selection.getNode();
|
|
|
|
evt = tinymce.extend({keyCode: 37}, evt);
|
|
|
|
dom.fire(target, 'keydown', evt);
|
|
dom.fire(target, 'keypress', evt);
|
|
dom.fire(target, 'keyup', evt);
|
|
}
|
|
|
|
function pressEnter(evt) {
|
|
var dom = editor.dom, target = editor.selection.getNode();
|
|
|
|
evt = tinymce.extend({keyCode: 13}, evt);
|
|
|
|
dom.fire(target, 'keydown', evt);
|
|
dom.fire(target, 'keypress', evt);
|
|
dom.fire(target, 'keyup', evt);
|
|
}
|
|
|
|
function trimBrsOnIE(html) {
|
|
return html.replace(/<br[^>]*>/gi, '');
|
|
}
|
|
|
|
function patch(proto, name, patchFunc) {
|
|
var originalFunc = proto[name];
|
|
var originalFuncs = proto.__originalFuncs;
|
|
|
|
if (!originalFuncs) {
|
|
proto.__originalFuncs = originalFuncs = {};
|
|
}
|
|
|
|
if (!originalFuncs[name]) {
|
|
originalFuncs[name] = originalFunc;
|
|
} else {
|
|
originalFunc = originalFuncs[name];
|
|
}
|
|
|
|
proto[name] = function() {
|
|
var args = Array.prototype.slice.call(arguments);
|
|
args.unshift(originalFunc);
|
|
return patchFunc.apply(this, args);
|
|
};
|
|
}
|
|
|
|
function unpatch(proto, name) {
|
|
var originalFuncs = proto.__originalFuncs;
|
|
|
|
if (!originalFuncs) {
|
|
return;
|
|
}
|
|
|
|
if (name) {
|
|
proto[name] = originalFuncs[name];
|
|
delete originalFuncs[name];
|
|
} else {
|
|
for (var key in originalFuncs) {
|
|
proto[key] = originalFuncs[key];
|
|
}
|
|
|
|
delete proto.__originalFuncs;
|
|
}
|
|
}
|
|
|
|
function triggerElementChange(element){
|
|
var evt;
|
|
|
|
if ("createEvent" in document) {
|
|
evt = document.createEvent("HTMLEvents");
|
|
evt.initEvent("change", false, true);
|
|
element.dispatchEvent(evt);
|
|
} else {
|
|
element.fireEvent("onchange");
|
|
}
|
|
}
|
|
|
|
window.Utils = {
|
|
fontFace: fontFace,
|
|
findContainer: findContainer,
|
|
setSelection: setSelection,
|
|
trimContent: trimContent,
|
|
fakeKeyEvent: fakeKeyEvent,
|
|
normalizeRng: normalizeRng,
|
|
type: type,
|
|
cleanHtml: cleanHtml,
|
|
normalizeHtml: normalizeHtml,
|
|
rect: rect,
|
|
size: size,
|
|
resetScroll: resetScroll,
|
|
nearlyEqualRects: nearlyEqualRects,
|
|
getFontmostWindow: getFontmostWindow,
|
|
pressArrowKey: pressArrowKey,
|
|
pressEnter: pressEnter,
|
|
trimBrsOnIE: trimBrsOnIE,
|
|
patch: patch,
|
|
unpatch: unpatch,
|
|
triggerElementChange: triggerElementChange
|
|
};
|
|
})();
|