rendered paste body/**
* Editor
* WYSIWYG Editor
* ---
* Written by José Carlos Nieto Jarquín <xiam@astrata.com.mx>
*
* Copyright (c) 2007 Astrata Software S.A. de C.V.
*
* Licensed under The MIT License
* Redistributions of files must retain the above copyright notice.
*
* @author José Carlos Nieto Jarquín
* @copyright Copyright (c) 2007, Astrata Software S.A. de C.V.
* @link http://astrata.com.mx Astrata Open Source Projects
* @version $Revision: $
* @modifiedby $LastChangedBy: $
* @lastmodified $Date: $
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
*
*/
// http://www.mozilla.org/editor/midas-spec.html
var Editor = Control.extend(
{
__imageExtension: '.png',
__colors: [
'#000', '#333', '#666', '#888', '#999', '#aaa',
'#ddd', '#008', '#00f', '#080', '#088', '#08f',
'#0f0', '#0f8', '#0ff', '#f00', '#f08', '#f0f',
'#f80', '#f88', '#f8f', '#fff'
],
'__selectNode': function(el) {
this.focusVisual();
var sel = this.__getSelection();
var range = this.__createRange(sel);
if (window.ie) {
range.moveToElementText(el);
range.select();
} else {
sel.removeAllRanges();
sel.selectAllChildren(range);
sel.addRange(range);
}
},
'focusVisual': function() {
this.components.iframe.contentWindow.focus();
},
setBookmark: function() {
this.focusVisual();
var range = this.__createRange();
if (window.ie) {
this.__bm = range.getBookmark();
} else {
this.__bm = {
'startContainer': range.startContainer,
'startOffset': range.startOffset,
'endContainer': range.endContainer,
'endOffset': range.endOffset
}
}
},
loadBookmark: function() {
if (this.__bm) {
this.focusVisual();
var range = this.__createRange();
if (window.ie) {
range.moveToBookmark(this.__bm);
range.select();
} else {
var sel = this.__getSelection();
range.setStart(this.__bm.startContainer, this.__bm.startOffset);
range.setEnd(this.__bm.endContainer, this.__bm.endOffset);
sel.removeAllRanges();
sel.addRange(range);
}
}
},
getIcon: function(icon) {
return $mediaDir+'/editor/'+icon+this.__imageExtension;
return Widget.img({src: $mediaDir+'/editor/'+icon+this.__imageExtension});
},
focus: function() {
this.iframeDocument().focus();
},
buttons: {},
reset: function() {
this.components.textarea.value = '';
this._updateVisual();
},
updateSource: function() {
if (this.__updateSourceTimer) {
window.clearTimeout(this.__updateSourceTimer);
}
this.__updateSourceTimer = window.setTimeout(function() { this._updateSource() }.bind(this), 500);
},
updateVisual: function() {
if (this.__updateVisualTimer) {
window.clearTimeout(this.__updateVisualTimer);
}
this.__updateVisualTimer = window.setTimeout(function() { this._updateVisual() }.bind(this), 500);
},
findParent: function(callback) {
this.focusVisual();
var range = this.__createRange();
var parent = this.getRangeParent(range);
while (parent) {
if (window.ie) {
if (typeof callback == 'function') {
if (callback(parent)) {
return parent;
}
} else if (parent.nodeName.toLowerCase().match(callback)) {
return parent;
}
} else {
if ($type(callback) == 'regexp') {
if (parent.nodeName.toLowerCase().match(callback)) {
return parent;
}
} else if (callback(parent)) {
return parent;
}
}
parent = parent.parentNode;
}
return null;
},
'removeAttribute': function (node, style) {
if ($type(node) == 'element') {
node.style[style] = '';
for (var i = 0; i < node.childNodes.length; i++) {
this.removeAttribute(node.childNodes[i], style);
}
}
},
'getRangeParent': function(range) {
if (window.ie) {
var html = range.htmlText;
this.execCommand('delete');
this.execCommand('inserthtml', '<span id="__temp_node__">'+html+'</span>');
var node = this.iframeDocument().getElementById('__temp_node__');
node.removeAttribute('id');
range.moveToElementText(node);
range.select();
return node;
} else {
// removing selected contents
var contents = range.extractContents();
// opera hack
if (window.opera) {
var hackNode = range.commonAncestorContainer.childNodes[range.startOffset-1];
this.trimTextNodes(hackNode);
/*
for (var i in hackNode) {
log(i+': '+hackNode[i]);
}
*/
if (!hackNode.textContent) {
range.commonAncestorContainer.removeChild(hackNode);
}
}
this.trimTextNodes(contents);
if (contents.childNodes.length == 1 && $type(contents.childNodes[0]) == 'element') {
// the user selected only one node
var el = contents.childNodes[0];
} else {
// a wrapper is needed
var el = this.iframeDocument().createElement('span');
el.appendChild(contents);
}
// reselecting
range.insertNode(el);
range.selectNode(el);
}
this.__selectNode(el);
return el;
},
'trimTextNodes': function(contents) {
// garbage hack to delete childNodes
var garbage = document.createDocumentFragment();
// emptying nodes from the end
var node = contents.childNodes[contents.childNodes.length - 1];
while (node && node.nodeType == 3 && node.nodeValue == '') {
garbage.appendChild(node);
node = node.previousSibling;
}
// emptying nodes from the start
var node = contents.childNodes[0];
while (node && node.nodeType == 3 && node.nodeValue == '') {
garbage.appendChild(node);
node = node.nextSibling;
}
},
'execAction': function(params) {
// This is the tricky part so it needs to be documented
this.focusVisual();
var sel = this.__getSelection();
var range = this.__createRange(sel);
var parent = this.getRangeParent(range);
var formatted = false;
log('while');
while
(
!formatted && parent
&& !parent.nodeName.toLowerCase().match(/^(body|html|head)$/)
) {
var match = false;
var name = parent.nodeName.toLowerCase();
log(name);
if
(
params.replaceTag
&& name.match(params.replaceTag)
) {
log('replaceTag');
if (window.ie) {
range.moveToElementText(parent);
var html = new String(parent.innerHTML);
range.select();
var bm = range.getBookmark();
this.execCommand('delete');
range.collapse();
range.select();
range.pasteHTML(html);
range.moveToBookmark(bm);
range.select();
log('replace');
} else {
var sel = this.__getSelection();
sel.removeAllRanges();
var range = this.__createRange();
var start = false;
while (parent.firstChild) {
var node = parent.firstChild;
parent.parentNode.insertBefore(node, parent);
if (!start) {
range.setStart(node, 0);
}
}
range.setEndBefore(node.nextSibling);
sel.addRange(range);
}
break;
} else if
(
params.matchTag
&& name.match(params.matchTag)
) {
// Parent tag matches, replacing whole tag with its
// contents.
log('matchTag');
if (window.ie) {
range.moveToElementText(parent);
var html = new String(parent.innerHTML);
range.select();
var bm = range.getBookmark();
this.execCommand('delete');
range.collapse();
range.select();
range.pasteHTML(html);
range.moveToBookmark(bm);
range.select();
} else {
var sel = this.__getSelection();
sel.removeAllRanges();
var range = this.__createRange();
var start = false;
while (parent.firstChild) {
var node = parent.firstChild;
parent.parentNode.insertBefore(node, parent);
if (!start) {
range.setStart(node, 0);
}
}
range.setEndBefore(node.nextSibling);
sel.addRange(range);
$(parent).dump();
}
formatted = true;
} else if
(
params.matchStyle
&& (parent.style && typeof parent.style[params.matchStyle[0]] != 'undefined' && parent.style[params.matchStyle[0]])
) {
log('matchStyle');
if (parent.style[params.matchStyle[0]].match(params.matchStyle[1])) {
log('style match, removing....');
// Style matches, removing style
if (window.ie) {
range.moveToElementText(parent);
parent.style[params.matchStyle[0]] = parent.style[params.matchStyle[0]].replace(params.matchStyle[1], '');
range.select();
} else {
parent.style[params.matchStyle[0]] = parent.style[params.matchStyle[0]].replace(params.matchStyle[1], '');
}
} else {
log('style does not match');
// Style does not match, changing
if (!params.replaceStyle && typeof parent.style[params.matchStyle[0]] != 'undefined') {
try {
parent.style[params.matchStyle[0]] = parent.style[params.matchStyle[0]]+' '+params.matchStyle[1];
} catch(e) {
parent.style[params.matchStyle[0]] = params.matchStyle[1];
}
} else {
parent.style[params.matchStyle[0]] = params.matchStyle[1];
}
}
this.__selectNode(parent);
formatted = true;
} else if
(
params.matchStyleTag
&& name.match(params.matchStyleTag)
) {
// Special case when a whole with matching style is selected
log('matchStyleTag');
if
(
(typeof parent.style[params.matchStyle[0]] != 'undefined')
&& parent.style[params.matchStyle[0]].match(params.matchStyle[1])
) {
log('style matches');
// Style matches, we need to remove it
parent.style[params.matchStyle[0]] = parent.style[params.matchStyle[0]].replace(params.matchStyle[1], '');
} else {
// Style does not match
if (parent.style[params.matchStyle[0]]) {
// Appeding style
log('appending style');
parent.style[params.matchStyle[0]] += ' '+params.matchStyle[1];
} else {
// Creating style
log('inserting style');
log(parent.style[params.matchStyle[0]]);
parent.style[params.matchStyle[0]] = params.matchStyle[1];
}
for (var i = 0; i < parent.childNodes.length; i++) {
this.removeAttribute(parent.childNodes[i], params.matchStyle[0]);
}
}
this.__selectNode(parent);
formatted = true;
}
parent = parent.parentNode;
}
if (formatted == false && !params.matchForRemove) {
log('non formatted');
// Patterns were not found, so the text needs to
// be formatted.
if (window.ie) {
var selected = this.getSelectedHTML();
} else {
var selected = this.__domToHTML(range.cloneContents());
}
var node = this.iframeDocument().createElement(params.wrapWithTag ? params.wrapWithTag : 'span');
node.setAttribute('id', '__temp_node__');
if (params.matchStyle && (params.wrapWithStyle || !params.wrapWithTag)) {
node.style[params.matchStyle[0]] = params.matchStyle[1];
}
if (params.wrapWithAttr) {
for (var i in params.wrapWithAttr) {
node.setAttribute(i, params.wrapWithAttr[i]);
}
}
if (params.stripTags) {
selected = selected.replace(new RegExp('<\\/?('+params.stripTags+')(\\s[^>]*)?>', 'ig'));
}
try {
if (params.insertValue) {
node.innerHTML = params.insertValue;
} else {
node.innerHTML = selected ? selected : ' ';
}
} catch(e) {
log('possible bug? do you need to strip some tags?');
log(node.nodeName);
log(selected);
}
if (window.ie) {
this.execCommand('inserthtml', node.outerHTML);
var node = this.iframeDocument().getElementById('__temp_node__');
node.removeAttribute('id');
range.moveToElementText(node);
range.select();
if (!selected && params.collapseIfNull) {
range.collapse(true);
range.select();
}
} else {
range.deleteContents();
range.insertNode(node);
var sel = this.__getSelection();
sel.removeAllRanges();
range.selectNodeContents(node);
sel.addRange(range);
node.removeAttribute('id');
if (params.collapseIfNull) {
range.collapse(true);
}
}
}
this.focusVisual();
},
__createButtons: function() {
this.__buttons = $extend({
bold: {
tooltip: 'Bold',
onAction: function() {
this.execAction({
'matchTag': /^(b|strong)$/,
'matchStyleTag': /^(span)$/,
'matchStyle': [ 'fontWeight', 'bold' ],
'wrapWithTag': 'b'
});
},
nodeStyle: 'fontWeight',
nodeStyleValue: 'bold',
changeElementPattern: /^(b|strong)$/
},
italic: {
tooltip: 'Italics',
onAction: function() {
this.execAction({
'matchTag': /^(i)$/,
'matchStyleTag': /^(span)$/,
'matchStyle': [ 'fontStyle', 'italic' ],
'wrapWithTag': 'i'
});
},
nodeStyle: 'fontStyle',
nodeStyleValue: 'italic',
changeElementPattern: /^i$/
},
underline: {
tooltip: 'Underline',
onAction: function() {
this.execAction({
'matchTag': /^(u)$/,
'matchStyleTag': /^(span)$/,
'matchStyle': [ 'textDecoration', 'underline' ]
});
},
nodeStyle: 'textDecoration',
nodeStyleValue: 'underline',
changeElementPattern: /^u$/
},
strikethrough: {
tooltip: 'Strikethrough',
onAction: function() {
this.execAction({
'matchTag': /^(s)$/,
'matchStyleTag': /^(span)$/,
'matchStyle': [ 'textDecoration', 'line-through' ]
});
},
nodeStyle: 'textDecoration',
nodeStyleValue: 'line-through',
changeElementPattern: /^s$/
},
subscript: {
tooltip: 'Subscript',
onAction: function() {
this.unselectButton('superscript');
this.execAction({
'matchTag': /^(sub)$/,
'replaceTag': /^(sup)$/,
'wrapWithTag': 'sub'
});
},
changeElementPattern: /^sub$/
},
superscript: {
tooltip: 'Superscript',
onAction: function() {
this.unselectButton('subscript');
this.execAction({
'matchTag': /^(sup)$/,
'replaceTag': /^(sub)$/,
'wrapWithTag': 'sup'
});
},
changeElementPattern: /^sup$/
},
justifyleft: {
tooltip: 'Align left',
onAction: function() {
this.unselectButton('justifycenter');
this.unselectButton('justifyfull');
this.unselectButton('justifyright');
this.execAction({
'matchStyleTag': /^(div|p)$/,
'matchStyle': ['textAlign', 'left'],
'collapseIfNull': true,
'wrapWithTag': 'p',
'replaceStyle': true,
'stripTags': 'p',
'wrapWithStyle': true
});
},
nodeStyle: 'textAlign',
nodeStyleValue: 'left'
},
justifycenter: {
tooltip: 'Align center',
onAction: function() {
this.unselectButton('justifyleft');
this.unselectButton('justifyfull');
this.unselectButton('justifyright');
this.execAction({
'matchStyleTag': /^(div|p)$/,
'matchStyle': ['textAlign', 'center'],
'collapseIfNull': true,
'wrapWithTag': 'p',
'replaceStyle': true,
'stripTags': 'p',
'wrapWithStyle': true
});
},
nodeStyle: 'textAlign',
nodeStyleValue: 'center'
},
justifyfull: {
tooltip: 'Justify',
onAction: function() {
this.unselectButton('justifycenter');
this.unselectButton('justifyleft');
this.unselectButton('justifyright');
this.execAction({
'matchStyleTag': /^(div|p)$/,
'matchStyle': ['textAlign', 'justify'],
'collapseIfNull': true,
'wrapWithTag': 'p',
'replaceStyle': true,
'stripTags': 'p',
'wrapWithStyle': true
});
},
nodeStyle: 'textAlign',
nodeStyleValue: 'justify'
},
justifyright: {
tooltip: 'Align right',
onAction: function() {
this.unselectButton('justifycenter');
this.unselectButton('justifyfull');
this.unselectButton('justifyleft');
this.execAction({
'matchStyleTag': /^(div|p)$/,
'matchStyle': ['textAlign', 'right'],
'collapseIfNull': true,
'wrapWithTag': 'p',
'replaceStyle': true,
'stripTags': 'p',
'wrapWithStyle': true
});
},
nodeStyle: 'textAlign',
nodeStyleValue: 'right'
},
indent: {
tooltip: 'Indent',
onAction: function() {
this.execCommand('indent', null);
}
},
outdent: {
tooltip: 'Outdent',
onAction: function() {
this.execCommand('outdent', null);
}
},
insertorderedlist: {
tooltip: 'Ordered List',
onAction: function() {
this.execCommand('insertorderedlist');
}
},
insertunorderedlist: {
tooltip: 'Unordered List',
onAction: function() {
this.execCommand('insertunorderedlist');
}
},
inserthorizontalrule: {
tooltip: 'Insert Horizontal Rule',
onAction: function() {
this.execCommand('inserthorizontalrule');
}
},
removeformat: {
tooltip: 'Remove format',
onAction: function() {
this.execCommand('removeformat');
}
},
insertimage: {
tooltip: 'Insert image',
onAction: function() {
var value = Dialog.prompt('Image URL', {
'value': 'http://',
'onOk': function (dialog) {
var value = dialog.input.value;
if (value && value != 'http://') {
this.execAction({
'matchTag': /^(img)$/,
'wrapWithTag': 'img',
'wrapWithAttr': {
'src': value
}
});
}
dialog.close();
}.bind(this)
});
}
},
createlink: {
tooltip: 'Create a link',
onAction: function() {
var def = 'http://';
if (this.isPressed('createlink')) {
var a = this.findParent(/^a$/);
if (a) {
def = a.href;
this.selectNode(a);
}
}
this.setBookmark();
Dialog.prompt(__('Web address'),
{
'value': def,
'onOk': function(dialog) {
this.loadBookmark();
var a = this.findParent(/^a$/);
if (dialog.input.value && dialog.input.value != 'http://') {
if (a) {
// updating
a.href = dialog.input.value;
} else {
log('new link');
// new link
this.execAction({
'matchTag': /^(a)$/,
'wrapWithTag': 'a',
'stripTags': 'a',
'wrapWithAttr': {
'href': dialog.input.value
}
});
}
} else {
if (a) {
this.execAction({
'matchTag': /^(a)$/,
'matchForRemove': true
});
}
}
dialog.close();
this._updateSource();
}.bind(this)
});
},
changeElementPattern: /^a$/
},
fullscreen: {
tooltip: 'Fullscreen editor',
onAction: function() {
if (this.isPressed('fullscreen')) {
this.components.iframe.style.height = this.components.iframe.origHeight+'px';
this.unselectButton('fullscreen');
if (this.__notebook) {
this.__notebook.toggleControlFullScreen();
} else {
this.toggleControlFullScreen();
}
this.components.iframe.setStyle({
'width': this.__iframeDimensions.width+'px',
'height': this.__iframeDimensions.height+'px'
});
window.removeEvent(
'resize',
this.__fullScreenResizeIframe
);
this.__isFullScreen = false;
} else {
this.selectButton('fullscreen');
this.__iframeDimensions = $(this.components.iframe).getDimensions();
this.__iframeOffset = {
'x': this.element.offsetWidth - this.components.iframe.offsetWidth,
'y': this.element.offsetHeight - this.components.iframe.offsetHeight
}
if (this.__notebook) {
this.__notebook.toggleControlFullScreen();
} else {
this.toggleControlFullScreen();
}
this.__fullScreenResizeIframe = function() {
this.components.iframe.style.height = (Browser.clientHeight() - this.__iframeOffset.y) + 'px';
this.components.iframe.style.width = (Browser.clientWidth() - this.__iframeOffset.x) + 'px';
}.bindAsEventListener(this)
this.__fullScreenResizeIframe();
window.addEvent(
'resize',
this.__fullScreenResizeIframe
);
this.__isFullScreen = true;
}
}.bindAsEventListener(this)
},
// HEADING
heading: {
tooltip: 'Paragraph type',
element: Widget.span({'class': 'm-select'},
[
Widget.img({'src': this.getIcon('heading')}),
Widget.select({
'class': 'm-form-input',
'onchange': function(e) {
this.__buttons['heading'].onAction.bind(this)(e);
this.updateSource();
}.bind(this)
},
[
Widget.option({value: ''}, 'Text format'),
Widget.option({value: 'h2'}, 'Main title'),
Widget.option({value: 'h3'}, 'Subtitle 1'),
Widget.option({value: 'h4'}, 'Subtitle 2'),
Widget.option({value: 'code'}, 'Code'),
Widget.option({value: 'blockquote'}, 'Quote')
])
]),
onAction: function(e) {
e = new Event(e);
if (e.target.value) {
this.execAction({
'matchTag': new RegExp('/^('+e.target.value+')$/', 'i'),
'replaceTag': /^(h\d+|code|pre)$/,
'wrapWithTag': e.target.value
});
} else {
this.execAction({
'matchTag': /^(h\d+|code|pre)$/,
'matchForRemove': true
});
}
},
onNodeChange: function(node) {
if (!node.__status.heading) {
var select = this.element.getElementsByTagName('select')[0];
select.value = node.nodeName.toLowerCase();
node.__status.heading = true;
}
},
listenEvent: 'auto',
changeElementPattern: /^h\d$/
},
// FONT FACE
fontname: {
tooltip: 'Font face',
element: Widget.span({'class': 'm-select'}, [
Widget.img({'src': this.getIcon('fontface')}),
Widget.select({
'class': 'm-form-input',
'onchange': function(e) {
this.__buttons['fontname'].onAction.bind(this)(e);
this.updateSource();
}.bind(this)
}, Widget.option({'value': ''}, 'Font name'))
]),
onStart: function(parent, el) {
var fonts = {
'Verdana': 'Verdana',
'Arial': 'Arial',
'Trebuchet MS': 'Trebuchet MS',
'Times New Roman': 'Times New Roman',
'monotype': 'Monotype',
'sans-serif': 'Sans Serif'
};
var select = el.getElementsByTagName('select')[0];
for (var font in fonts) {
select.appendChild(Widget.option({'value': font, 'style': {'fontFamily': font}}, fonts[font]));
}
},
onNodeChange: function(node) {
if (!node.__status.fontname) {
var select = this.element.getElementsByTagName('select')[0];
select.value = node.style.fontFamily;
node.__status.fontname = true;
}
},
onAction: function(e) {
var e = new Event(e);
this.execAction({
'matchTag': /^(font)$/,
'matchStyleTag': /^(span)$/,
'wrapWithTag': 'span',
'wrapWithStyle': true,
'replaceStyle': true,
'matchStyle': [ 'fontFamily', e.target.value ]
});
},
nodeStyle: 'fontFamily',
nodeStyleValue: /.*/,
listenEvent: 'auto'
},
// FONT SIZE
fontsize: {
tooltip: 'Font size',
element: Widget.span({'class': 'm-select'}, [
Widget.img({'src': this.getIcon('fontsize')}),
Widget.select({
'class': 'm-form-input',
'onchange': function(e) {
this.__buttons['fontsize'].onAction.bind(this)(e);
this.updateSource();
}.bind(this)
}, Widget.option({'value': ''}, 'Font size'))
]),
onStart: function(parent, el) {
var sizes = {
'xx-small': 'Micro',
'x-small': 'Extra small',
'small': 'Small',
'medium': 'Medium',
'large': 'Large',
'x-large': 'Extra large',
'xx-large': 'Huge'
};
var select = el.getElementsByTagName('select')[0];
for (var i in sizes) {
select.appendChild(Widget.option({'value': i}, sizes[i]));
}
},
onNodeChange: function(node) {
if (!node.__status.fontsize) {
var select = this.element.getElementsByTagName('select')[0];
select.value = node.style.fontSize;
node.__status.fontsize = true;
}
},
onAction: function(e) {
var e = new Event(e);
this.execAction({
'matchTag': /^(font)$/,
'matchStyleTag': /^(span)$/,
'wrapWithTag': 'span',
'wrapWithStyle': true,
'replaceStyle': true,
'matchStyle': [ 'fontSize', e.target.value ]
});
},
nodeStyle: 'fontSize',
nodeStyleValue: /.*/,
listenEvent: 'auto'
},
// FORECOLOR
forecolor: {
tooltip: 'Font color',
element: Widget.span({'class': 'm-select'}, [
Widget.img({'src': this.getIcon('forecolor') }),
Widget.select({
'class': 'm-form-input',
'onchange': function(e) {
this.__buttons['forecolor'].onAction.bind(this)(e);
this.updateSource();
}.bind(this)
}, Widget.option({'value': ''}, 'Font color'))
]),
onStart: function(parent, el) {
for (var i = 0; i < el.childNodes.length; i++) {
var node = el.childNodes[i];
if ($type(node) == 'element') {
if (node.nodeName.toLowerCase() == 'select') {
var colors = parent.__colors;
for (var i = 0; i < colors.length; i++) {
node.appendChild(Widget.option({'value': colors[i], 'style': { 'backgroundColor': colors[i] } }));
}
return;
}
}
}
},
onNodeChange: function(node) {
if (!node.__status.forecolor) {
var select = this.element.getElementsByTagName('select')[0];
select.value = node.style.color;
select.style.backgroundColor = node.style.color;
node.__status.forecolor = true;
}
},
onAction: function(e) {
var e = new Event(e);
var color = e.target.value;
e.target.style.backgroundColor = color;
if (color.length == '4') {
color = '#'+color.charAt(1)+color.charAt(1)+color.charAt(2)+color.charAt(2)+color.charAt(3)+color.charAt(3);
}
this.execAction({
'matchTag': /^(font)$/,
'matchStyleTag': /^(span)$/,
'wrapWithTag': 'span',
'wrapWithStyle': true,
'replaceStyle': true,
'matchStyle': [ 'color', color ]
});
},
nodeStyle: 'color',
nodeStyleValue: /.*/,
listenEvent: 'auto'
},
// BACKGROUND COLOR
hilitecolor: {
tooltip: 'Background color',
element: Widget.span({'class': 'm-select'}, [
Widget.img({'src': this.getIcon('hilitecolor') }),
Widget.select({
'class': 'm-form-input',
'onchange': function(e) {
this.__buttons['hilitecolor'].onAction.bind(this)(e);
this.updateSource();
}.bind(this)
}, Widget.option({value: ''}, 'Background'))
]),
onStart: function(parent, el) {
for (var i = 0; i < el.childNodes.length; i++) {
var node = el.childNodes[i];
if ($type(node) == 'element') {
if (node.nodeName.toLowerCase() == 'select') {
var colors = parent.__colors;
for (var i = 0; i < colors.length; i++) {
node.appendChild(Widget.option({'value': colors[i], 'style': {'backgroundColor': colors[i]}}));
}
return;
}
}
}
},
onAction: function(e) {
var e = new Event(e);
var color = e.target.value;
e.target.style.backgroundColor = color;
if (color.length == '4') {
color = '#'+color.charAt(1)+color.charAt(1)+color.charAt(2)+color.charAt(2)+color.charAt(3)+color.charAt(3);
}
this.execAction({
'matchTag': /^(font)$/,
'matchStyleTag': /^(span)$/,
'wrapWithTag': 'span',
'wrapWithStyle': true,
'replaceStyle': true,
'matchStyle': [ 'backgroundColor', color ]
});
},
listenEvent: 'auto',
nodeStyle: 'backgroundColor',
nodeStyleValue: /.*/,
onNodeChange: function(node) {
if (!node.__status.hilite) {
var select = this.element.getElementsByTagName('select')[0];
select.value = node.style.backgroundColor;
select.style.backgroundColor = node.style.backgroundColor;
node.__status.hilitecolor = true;
}
}
}
}, this.buttons);
if (!this.options.buttons) {
this.options.buttons = [];
for (var i in this.__buttons) {
this.options.buttons.push(i);
}
}
for (var i = 0; i < this.options.buttons.length; i++) {
var name = this.options.buttons[i];
if ($type(this.__buttons[name])) {
var button = this.__buttons[name];
button.name = name;
button.parent = this;
// creating an element if the button doesn't has one
if (!button.element) {
button.element = Widget.img({ 'class': 'm-button', 'src': this.getIcon(name) });
}
if (button.onStart) {
button.onStart(this, button.element);
}
if (!button.listenEvent) {
button.listenEvent = 'onclick';
}
button.element[button.listenEvent] = function () {
// calling the button's action
this.onAction.bind(this.parent).call();
this.parent.updateSource();
// shading and unshading the button
if (this.element.nodeName.toLowerCase() == 'img' && (this.changeElementPattern || this.nodeStyle)) {
this.parent.toggleStatus(this.element);
}
}.bind(button);
if (button.tooltip) {
button.element.title = button.tooltip;
}
if (button.element.nodeName.toLowerCase() == 'img') {
this.components.buttonsUpper.appendChild(button.element);
} else {
this.components.buttonsLower.appendChild(button.element);
}
} else {
log('The button '+name+' does not exist.');
}
}
},
toggleStatus: function(obj) {
if (obj.src.match(/\/active-([^\/]+)/)) {
this.__unselectButton(obj);
} else {
this.__selectButton(obj);
}
},
__execCommand: function(command, value) {
try {
this.iframeDocument().execCommand(command, false, value);
} catch(e) {
log('Unsupported command: '+command);
}
},
selectNode: function(el) {
if (window.ie) {
var range = this.__createRange();
range.moveToElementText(el);
range.select();
}
},
execCommand: function(command, value) {
switch (command) {
case 'inserthtml':
if (window.ie) {
var sel = this.__getSelection();
var range = this.__createRange(sel);
range.pasteHTML(value);
} else {
var range = this.__createRange();
range.deleteContents();
this.__execCommand('inserthtml', value);
}
break;
default:
this.__execCommand(command, value);
break;
}
if (this.__started) {
this.updateSource();
}
},
resizeTo: function(width, height) {
this.element.style.width = width+'px';
this.components.iframe.style.height = height+'px';
},
__buildComponents: function() {
this.element = Widget.div({'class': 'm-editor'});
this.components = {
buttonsUpper: Widget.div(),
buttonsLower: Widget.div(),
buttons: Widget.div({'class': 'm-editor-buttons'}),
visualarea: Widget.div({'class': 'm-visual-area'}),
iframe: Widget.iframe({
'src': this.options.blank,
'border': '0',
'frameborder': '0',
'style': {
'display': 'block'
}
})
}
this.components.buttons.appendChildren([
this.components.buttonsUpper,
this.components.buttonsLower
]);
this.components.textarea = this.__textarea;
//this.components.textarea.setAttribute('wrap', 'off');
if (this.components.textarea.offsetWidth && this.components.textarea.offsetHeight) {
this.resizeTo(this.components.textarea.offsetWidth, this.components.textarea.offsetHeight);
} else {
if (this.components.textarea.style.width && this.components.textarea.style.height) {
if (this.components.textarea.style.width.indexOf('%') == -1 && this.components.textarea.style.height.indexOf('%') == -1) {
this.resizeTo(parseInt(this.components.textarea.style.width), parseInt(this.components.textarea.style.height));
}
} else {
this.element.style.width = '100%';
}
}
this.__createButtons();
if (!this.options.buttons.length) {
this.components.buttons.hide();
}
this.components.textarea.parentNode.insertBefore(this.element, this.components.textarea);
switch (this.options.variant) {
case 1:
this.components.visualarea.appendChildren([
this.components.iframe,
this.components.buttons
]);
break;
default:
this.components.visualarea.appendChildren([
this.components.buttons,
this.components.iframe
]);
break;
}
if (this.options.showSource) {
nb = new Notebook(this.element, { allowBookmark: false } );
nb.addPage({id: 'visual', title: 'Visual mode'}, this.components.visualarea);
nb.addPage({id: 'source', title: 'Source mode'}, this.components.textarea);
this.__notebook = nb;
} else {
this.element.appendChild(this.components.visualarea);
this.components.textarea.parentNode.insertBefore(this.element, this.components.textarea);
this.components.textarea.hide();
}
var form = this.components.textarea;
while (form) {
if (form.nodeName.toLowerCase() == 'form') {
$(form).addEvent(
'reset', function() {
this.reset();
}.bind(this)
);
$(form).addEvent(
'submit', function(e) {
if (this.options.autoSave) {
this.__autoSave(true);
}
}.bind(this)
);
break;
}
form = form.parentNode;
}
},
__bindEvents: function() {
$(this.components.iframe).addEvent(
'load',
function() {
this.__started = false;
this.__start();
}.bind(this)
);
},
__createRange: function(sel) {
if (!sel) {
sel = this.__getSelection();
}
if (window.ie) {
return sel.createRange();
} else {
try {
return sel.getRangeAt(0);
} catch(e) {
return this.iframeDocument().createRange();
}
}
},
__domToHTML: function(content) {
var buff = new String("");
var isTag = (content.nodeName.substr(0, 1) != '#');
if (!isTag) {
if (content.nodeName == '#text') {
buff += content.nodeValue;
}
}
if (isTag) {
buff += '<'+content.nodeName.toLowerCase();
if (content.attributes.length) {
var attr = new Array();
for (var i = 0; i < content.attributes.length; i++) {
var name = content.attributes[i].nodeName;
var value = content.attributes[i].nodeValue;
value = value.replace(/-moz-.*;/, '');
if (name.substr(0, 1) != '-')
attr.push(name+'="'+value+'"');
}
buff += ' '+attr.join(' ');
}
if (this.__isSingleTag(content.nodeName))
buff += ' /';
buff += '>';
}
for (var i = 0; i < content.childNodes.length; i++) {
buff += this.__domToHTML(content.childNodes[i]);
}
if (isTag && !this.__isSingleTag(content.nodeName)) {
buff += '</'+content.nodeName.toLowerCase()+'>';
}
return buff;
},
__isSingleTag: function (tag) {
tag = tag.toLowerCase();
if (tag == 'br' || tag == 'img' || tag == 'input' || tag == 'hr') {
return true;
}
return false;
},
'getSelectedHTML': function(sel) {
if (!sel) {
var sel = this.__getSelection();
}
var range = this.__createRange(sel);
if ($type(range.htmlText)) {
return range.htmlText;
} else {
var content = range.cloneContents();
return this.__domToHTML(content);
}
},
__unselectButtons: function() {
for (i in this.__buttons) {
this.__unselectButton(this.__buttons[i].element);
}
},
isPressed: function(buttonName) {
return (this.__buttons[buttonName].element.src.match(/.*active-[\.a-z0-9]+\.png$/i));
},
selectButton: function(buttonName) {
if (this.__buttons[buttonName]) {
this.__selectButton(this.__buttons[buttonName].element);
}
},
unselectButton: function(buttonName) {
if (this.__buttons[buttonName]) {
this.__unselectButton(this.__buttons[buttonName].element);
}
},
__selectButton: function(button) {
if (button) {
this.__unselectButton(button);
var match = button.src.match(/(.+)\/([^\/]+)$/);
button.src = match[1]+'/'+'active-'+match[2];
}
},
__unselectButton: function(button) {
if (button) {
button.src = button.src.replace(/active-/, '');
}
},
'__handleNodeChange': function() {
var sel = this.__getSelection();
var range = this.__createRange(sel);
if (window.ie) {
try {
var parent = range.parentElement();
} catch(e) {
return;
}
} else {
var parent = range.commonAncestorContainer;
// read comments from getRangeParent
var contents = range.cloneContents();
var garbage = document.createDocumentFragment();
// emptying nodes from the end
var node = contents.childNodes[contents.childNodes.length - 1];
while (node && node.nodeType == 3 && node.nodeValue == '') {
garbage.appendChild(node);
node = node.previousSibling;
}
// emptying nodes from the start
var node = contents.childNodes[0];
while (node && node.nodeType == 3 && node.nodeValue == '') {
garbage.appendChild(node);
node = node.nextSibling;
}
if (contents.childNodes.length == 1 && $type(contents.childNodes[0]) == 'element') {
// the user selected only one node
var parent = contents.childNodes[0];
} else {
// a wrapper is needed
while ($type(parent) != 'element') {
parent = parent.parentNode;
}
}
}
// special cases
if (parent.nodeName.toLowerCase() == 'a') {
this.__selectNode(parent);
}
if (parent) {
parent.__status = {};
var button = this.components.buttons.getElementsByTagName('img');
for (var i = 0; i < button.length; i++) {
this.__unselectButton(button[i]);
}
if (this.__isFullScreen) {
this.selectButton('fullscreen');
}
var select = this.components.buttons.getElementsByTagName('select');
for (var i = 0; i < select.length; i++) {
select[i].value = '';
select[i].style.backgroundColor = '';
}
while (parent) {
var name = parent.nodeName.toLowerCase();
if (name == 'body') {
break;
}
for (var i in this.__buttons) {
var button = this.__buttons[i];
if (button.changeElementPattern) {
if (name.match(button.changeElementPattern)) {
if (typeof button.onNodeChange != 'undefined') {
button.onNodeChange(parent);
} else {
this.selectButton(button.name);
}
}
}
if (parent.style && button.nodeStyle && button.nodeStyleValue) {
if (parent.style[button.nodeStyle]) {
if (parent.style[button.nodeStyle].match(button.nodeStyleValue)) {
if (button.onNodeChange) {
button.onNodeChange(parent);
} else {
this.selectButton(button.name);
}
}
}
}
}
if (parent.parentNode) {
parent.parentNode.__status = parent.__status;
}
parent = parent.parentNode;
}
}
},
addEvent: function(name, callback) {
if (window.ie) {
$(this.components.iframe.contentWindow.document).addEvent(name, callback);
} else {
$(this.components.iframe.contentWindow).addEvent(name, callback);
}
},
removeEvent: function(name, callback) {
if (window.ie) {
$(this.components.iframe.contentWindow.document).removeEvent(name, callback);
} else {
$(this.components.iframe.contentWindow).removeEvent(name, callback);
}
},
__getSelection: function() {
if (window.ie) {
var sel = this.iframeDocument().selection;
} else {
var sel = this.components.iframe.contentWindow.getSelection();
}
return sel;
},
__start: function() {
if (!this.__started) {
var parent = this.components.iframe;
try {
if (this.components.iframe.contentWindow) {
this.components.iframe.contentWindow.document.body.innerHTML = this.components.textarea.value ? this.components.textarea.value : ' ';
}
this.designMode(true);
if (this.__startAgain) {
window.clearTimeout(this.__startAgain);
}
$(this.iframeDocument()).addEvent(
'mouseup',
function() {
this.__handleNodeChange();
}.bind(this)
);
$(this.components.textarea).addEvent(
'keydown',
function() {
this.updateVisual();
}.bind(this)
);
if (this.options.autoSave) {
this._autoSave();
}
var events = ['keydown', 'mouseout', 'mouseover'];
for (var i = 0; i < events.length; i++) {
this.addEvent(
events[i],
function(e) {
e = new Event(e);
if (e.type == 'keydown' && !this.options.tabIndents) {
if (e.key == 'tab') {
var form = this.components.textarea;
var change = false;
while (form) {
if (form.nodeName.toLowerCase() == 'form') {
for (var i = 0; i < form.elements.length; i++) {
var el = form.elements[i];
if (change == true) {
el.focus();
return e.stop();
}
if (el == this.components.textarea) {
var change = true;
}
}
}
form = form.parentNode;
}
}
}
this.updateSource();
}.bind(this)
);
}
this.__started = true;
} catch(e) {
if (!this.__startAgain) {
this.__startAgain = window.setInterval(this.__start.bind(this), 500);
}
}
}
},
__autoSave: function(clear) {
if (!this.__autoSaveForm) {
this.__autoSaveForm = Widget.form({
'action': this.options.autoSave,
'method': 'post',
'style': {'display': 'none'}
});
this.__autoSaveForm.data = Widget.textarea({'name': 'data'});
this.__autoSaveForm.ident = Widget.input({
'name': 'ident',
'value': this.__ident
});
this.__autoSaveForm.appendChild(this.__autoSaveForm.data);
this.__autoSaveForm.appendChild(this.__autoSaveForm.ident);
document.body.appendChild(this.__autoSaveForm);
}
if (clear == true) {
window.clearTimeout(this.__autoSaveTimer);
this.__autoSaveForm.data.value = '';
} else {
this.__autoSaveForm.data.value = this.components.textarea.value;
}
if (!this.components.textarea.parentNode) {
return window.clearTimeout(this.__autoSaveTimer);
}
// saving...
return formSubmit(this.__autoSaveForm);
},
_autoSave: function() {
var parent = this.components.textarea;
while (parent && parent.nodeName.toLowerCase() != 'form') {
parent = parent.parentNode;
}
this.__ident = location.href+'|'+this.components.textarea.name+(parent ? '|'+parent.action+'|'+parent.method : null);
if (parent) {
parent.appendChild(Widget.input({
'name': '__ident',
'type': 'hidden',
'value': this.__ident
}));
}
this.__autoSaveForm = null;
new Ajax(
this.options.autoSave,
{
'method': 'get',
'data': 'ident='+encodeURIComponent(this.__ident),
'onComplete': function(t) {
var json = eval('('+t+')');
if (json) {
new JsonRpc(json);
this.components.textarea.value = json.data;
this._updateVisual();
}
}.bind(this)
}
).request();
this.__autoSaveTimer = window.setInterval(this.__autoSave.bind(this), parseInt(this.options.autoSaveInterval) * 1000);
},
_updateSource: function() {
if (this.__started && this.components.iframe.contentWindow) {
log('source up');
this.components.textarea.value = this.components.iframe.contentWindow.document.body.innerHTML;
}
},
_updateVisual: function() {
if (this.components.iframe.contentWindow) {
log('visual up');
this.components.iframe.contentWindow.document.body.innerHTML = this.components.textarea.value ? this.components.textarea.value : ' ';
}
},
iframeDocument: function() {
if (window.ie) {
return this.components.iframe.contentWindow.document;
} else {
return this.components.iframe.contentDocument;
}
},
designMode: function(status) {
this.components.iframe.style.display = 'block';
if (window.ie) {
this.iframeDocument().body.contentEditable = true;
} else {
this.iframeDocument().designMode = status ? 'On' : 'Off';
this.execCommand('styleWithCSS', true);
}
},
initialize: function(textarea, options) {
// default options
this.options = {
'buttons': null,
'mode': 'complete',
'blank': $mediaDir+'/blank.html',
'showSource': true,
'tabIndents': true,
'autoSave': false,
'autoSaveInterval': 30,
'variant': 0
}
this.setOptions(options);
if (!this.options.buttons) {
switch (this.options.mode) {
case 'nano':
this.options.showSource = false;
this.options.buttons = [
'bold',
'italic',
'underline',
'strikethrough',
'createlink',
'removeformat'
];
break;
case 'mini':
this.options.showSource = false;
this.options.buttons = [
'bold',
'italic',
'underline',
'strikethrough',
'createlink',
'insertorderedlist',
'insertunorderedlist',
'removeformat',
'fullscreen'
];
break;
case 'basic':
this.options.showSource = false;
this.options.buttons = [
'bold',
'italic',
'underline',
'strikethrough',
'createlink',
'insertorderedlist',
'insertunorderedlist',
'justifyleft',
'justifyright',
'justifyfull',
'justifycenter',
'indent',
'outdent',
'removeformat',
'fullscreen'
];
break;
case 'advanced':
this.options.showSource = true;
this.options.buttons = [
'bold',
'italic',
'underline',
'strikethrough',
'createlink',
'insertorderedlist',
'insertunorderedlist',
'justifyleft',
'justifyright',
'justifyfull',
'justifycenter',
'indent',
'outdent',
'inserthorizontalrule',
'insertimage',
'removeformat',
'fullscreen'
];
break;
case 'complete':
// all buttons
this.options.showSource = true;
break;
}
}
// explicit rewrite
if ($type(options.showSource)) {
this.options.showSource = options.showSource;
}
this.__textarea = $(textarea);
this.__buildComponents();
this.__bindEvents();
//this.components.iframe.contentWindow.focus();
}
}
);
Meteora.onStart(
function() {
var textareas = document.getElementsByTagName('textarea');
for (var i = 0; i < textareas.length; i++) {
var t = textareas[i];
if (t.className == 'meteoraEditor') {
new Editor(t);
}
}
}
);