905 lines
34 KiB
JavaScript
905 lines
34 KiB
JavaScript
/*
|
|
* =================================================================
|
|
* HTML Editor - A WYSIWYG web based editor for IE5.5+ and Mozilla v1.4+
|
|
*
|
|
* Website : http://gossamer-threads.com/
|
|
* Support : http://gossamer-threads.com/scripts/support/
|
|
* Revision : $Id: editor.js,v 1.12 2009/05/09 05:52:50 brewt Exp $
|
|
*
|
|
* Copyright (c) 2005 Gossamer Threads Inc. All Rights Reserved.
|
|
* Redistribution in part or in whole strictly prohibited. Please
|
|
* see LICENSE file for full details.
|
|
* =================================================================
|
|
*/
|
|
|
|
var dialogWindow = {};
|
|
|
|
function advancedEditor() {
|
|
this.config = {
|
|
imageURL : 'images',
|
|
imageDialogURL : '',
|
|
defaultFont : 'Arial',
|
|
defaultFontSize : '3',
|
|
defaultForeground : '#000000',
|
|
defaultBackground : '#FFFFFF',
|
|
focusOnload : false
|
|
};
|
|
|
|
this.id = {
|
|
editorFrame : 'editor_iframe',
|
|
toolbarArea : 'toolbar_area',
|
|
editableFrame : 'editable_iframe',
|
|
editableContent : 'editable_content',
|
|
editableSource : 'editable_source',
|
|
form : '',
|
|
content : ''
|
|
};
|
|
|
|
// command, image, title, updateType(0 = don't update, 1 = queryCommandState, 2 = queryCommandEnabled, 3 = customQueryCommandState, 4 = customQueryCommandEnabled), customCommand, customStateCommand, useCSS (mozilla)
|
|
this.toolbar = [
|
|
{
|
|
buttons : {
|
|
copy : ['Copy', 'copy.gif', 'Copy', 2],
|
|
cut : ['Cut', 'cut.gif', 'Cut', 2],
|
|
paste : ['Paste', 'paste.gif', 'Paste', 2]
|
|
},
|
|
num_buttons : 3,
|
|
hidden : isIE ? 0 : 1
|
|
},
|
|
// The undo/redo buttons don't work too reliably and the buffer is lost after
|
|
// doing things like resizing (changing any properties of the contenteditable
|
|
// area will wipe it), or custom commands.
|
|
/*
|
|
{
|
|
buttons : {
|
|
undo : ['Undo', 'undo.gif', 'Undo', 2],
|
|
redo : ['Redo', 'redo.gif', 'Redo', 2]
|
|
},
|
|
num_buttons : 2
|
|
},
|
|
*/
|
|
{
|
|
buttons : {
|
|
bold : ['Bold', 'bold.gif', 'Bold', 1],
|
|
italic : ['Italic', 'italic.gif', 'Italic', 1],
|
|
underline : ['Underline', 'underline.gif', 'Underline', 1]
|
|
},
|
|
num_buttons : 3
|
|
},
|
|
{
|
|
buttons : {
|
|
quote : ['', 'quote.gif', 'Quote', 0, 'this.insertQuote()']
|
|
/*
|
|
reply : ['', 'reply.gif', 'Reply', 0, 'this.insertReply()'],
|
|
code : ['', 'code.gif', 'Code', 0, 'this.insertCode()']
|
|
*/
|
|
},
|
|
num_buttons : 3
|
|
},
|
|
{
|
|
buttons : {
|
|
alignleft : ['JustifyLeft', 'alignleft.gif', 'Align Left', 1],
|
|
center : ['JustifyCenter', 'center.gif', 'Center', 1],
|
|
alignright : ['JustifyRight', 'alignright.gif', 'Align Right', 1]
|
|
},
|
|
num_buttons : 3
|
|
},
|
|
{
|
|
buttons : {
|
|
numlist : ['InsertOrderedList', 'numlist.gif', 'Numbering', 1],
|
|
bullist : ['InsertUnorderedList', 'bullist.gif', 'Bullets', 1],
|
|
outdent : ['Outdent', 'outdent.gif', 'Decrease Indent', 0],
|
|
indent : ['Indent', 'indent.gif', 'Increase Indent', 0]
|
|
},
|
|
num_buttons : 4
|
|
},
|
|
{
|
|
buttons : {
|
|
font : ['FontName', 'font.gif', 'Font', 0, 'this.fontDialog()'],
|
|
color : ['', 'color.gif', 'Color', 0, 'this.colorDialog("foreground")']
|
|
},
|
|
num_buttons : 2
|
|
},
|
|
{
|
|
buttons : {
|
|
hr : ['InsertHorizontalRule', 'hr.gif', 'Horizontal Rule', 0],
|
|
image : ['', 'image.gif', 'Insert Image', 0, 'this.imageDialog()'],
|
|
link : ['', 'link.gif', 'Create Link', 4, 'this.linkDialog()', 'this.selectionMade()']
|
|
},
|
|
num_buttons : 3
|
|
},
|
|
{
|
|
buttons : {
|
|
source : ['', 'source.gif', 'Toggle HTML Source', 3, 'this.toggleSource()', 'this.sourceMode']
|
|
},
|
|
num_buttons : 1
|
|
}
|
|
];
|
|
this.toolbarMapping = {};
|
|
this.toolbarQueryCommandState = {};
|
|
this.toolbarQueryCommandEnabled = {};
|
|
|
|
this.objects = {};
|
|
this.loadingInterval = 0;
|
|
this.initCount = 0;
|
|
this.toolbarInterval = 0;
|
|
this.selection = null;
|
|
this.loaded = false;
|
|
this.sourceMode = false;
|
|
|
|
// TODO remove this and get values from currentStyle
|
|
// Dimensions of various objects
|
|
// *Extra are padding/border/margin widths that need to be added to properly
|
|
// calculate object total widths
|
|
this.editorFrameExtra = 2;
|
|
this.editableContentExtra = 10;
|
|
this.toolbarExtra = 2;
|
|
this.toolbarHeight = 26;
|
|
this.buttonWidth = 25;
|
|
this.separatorWidth = 8;
|
|
}
|
|
|
|
advancedEditor.prototype.load = function (form, content) {
|
|
var self = this;
|
|
if (form)
|
|
this.id.form = form;
|
|
if (content)
|
|
this.id.content = content;
|
|
this.loadingInterval = setInterval(function () { self.init() }, 100);
|
|
}
|
|
|
|
advancedEditor.prototype.init = function () {
|
|
if (this.initCount++ > 100) {
|
|
clearInterval(this.loadingInterval);
|
|
return;
|
|
}
|
|
|
|
var self = this;
|
|
this.objects.editorFrame = document.getElementById(this.id.editorFrame);
|
|
if (!this.objects.editorFrame)
|
|
return;
|
|
|
|
this.objects.editorFrameDoc = this.objects.editorFrame.contentWindow.document;
|
|
this.objects.editableSource = this.objects.editorFrameDoc.getElementById(this.id.editableSource);
|
|
this.objects.editableFrame = this.objects.editorFrameDoc.getElementById(this.id.editableFrame);
|
|
if (!this.objects.editableFrame)
|
|
return;
|
|
if (!this.objects.editableSource)
|
|
this.deleteButton('source');
|
|
|
|
this.objects.editableFrameDoc = this.objects.editableFrame.contentWindow.document;
|
|
this.objects.editableContent = this.objects.editableFrameDoc.getElementById(this.id.editableContent);
|
|
if (!this.objects.editableContent)
|
|
return;
|
|
|
|
this.objects.toolbarArea = this.objects.editorFrameDoc.getElementById(this.id.toolbarArea);
|
|
if (!this.objects.toolbarArea)
|
|
return;
|
|
|
|
clearInterval(this.loadingInterval);
|
|
|
|
this.objects.form = document.getElementById(this.id.form);
|
|
this.objects.content = document.getElementById(this.id.content);
|
|
this.objects.editableContent.innerHTML = this.objects.content.value;
|
|
registerEvent(this.objects.form, 'submit', function () {
|
|
self.objects.content.value = self.getContent();
|
|
});
|
|
// Before leaving the page, make sure the content is put back into the input so that the content isn't lost if the user goes back to the page
|
|
registerEvent(window, 'beforeunload', function () {
|
|
self.objects.content.value = self.getContent();
|
|
});
|
|
|
|
// Tabbing from the subject takes you to the iframe in IE, but the document in Mozilla
|
|
registerEvent(this.objects.editorFrame, 'focus', function () { self.focus() });
|
|
registerEvent(this.objects.editorFrameDoc, 'focus', function () { self.focus() });
|
|
|
|
var width = this.objects.editorFrame.offsetWidth - this.editorFrameExtra;
|
|
var toolbar;
|
|
for (var i = 0; i < this.toolbar.length; i++) {
|
|
if (this.toolbar[i].hidden || this.toolbar[i].num_buttons <= 0)
|
|
continue;
|
|
|
|
var group = this.objects.editorFrameDoc.createElement('div');
|
|
group.aewidth = this.buttonWidth * this.toolbar[i].num_buttons;
|
|
|
|
if (!toolbar || toolbar.aewidth + this.toolbarExtra + this.separatorWidth + group.aewidth > width)
|
|
toolbar = this.addToolbar();
|
|
else
|
|
this.addSeparator(toolbar);
|
|
|
|
this.addButtonGroup(toolbar, group);
|
|
|
|
for (var item in this.toolbar[i].buttons) {
|
|
var button = this.objects.editorFrameDoc.createElement('div');
|
|
group.appendChild(button);
|
|
|
|
button.id = item;
|
|
button.enabled = true;
|
|
button.className = 'button';
|
|
button.style.backgroundImage = 'url(' + this.config.imageURL + '/' + this.toolbar[i].buttons[item][1] + ')';
|
|
button.title = this.toolbar[i].buttons[item][2];
|
|
button.setAttribute('unselectable', 'on');
|
|
registerEvent(button, 'mouseover', function (e) { self.mouseHandler(e) });
|
|
registerEvent(button, 'mouseout', function (e) { self.mouseHandler(e) });
|
|
registerEvent(button, 'mousedown', function (e) { self.mouseHandler(e) });
|
|
registerEvent(button, 'click', function (e) { self.mouseHandler(e) });
|
|
|
|
this.toolbarMapping[item] = i;
|
|
if (this.toolbar[i].buttons[item][3] == 1 || this.toolbar[i].buttons[item][3] == 3)
|
|
this.toolbarQueryCommandState[item] = i;
|
|
else if (this.toolbar[i].buttons[item][3] == 2 || this.toolbar[i].buttons[item][3] == 4)
|
|
this.toolbarQueryCommandEnabled[item] = i;
|
|
}
|
|
}
|
|
|
|
this.objects.editableContent.style.fontFamily = this.config.defaultFont;
|
|
var fontsize = {
|
|
1 : 'xx-small',
|
|
2 : 'x-small',
|
|
3 : 'small',
|
|
4 : 'medium',
|
|
5 : 'large',
|
|
6 : 'x-large',
|
|
7 : 'xx-large'
|
|
};
|
|
this.objects.editableContent.style.fontSize = fontsize[parseInt(this.config.defaultFontSize) + (isIE ? 0 : 1)];
|
|
this.objects.editableContent.style.backgroundColor = this.config.defaultBackground;
|
|
|
|
this.objects.editableFrameDoc.body.contentEditable = true;
|
|
if (!isIE)
|
|
this.objects.editableFrameDoc.designMode = 'on';
|
|
|
|
// This actually turns *off* the usage of css
|
|
if (isMozilla)
|
|
this.execCommand("useCSS", true);
|
|
|
|
this.toolbarInterval = setInterval(function () { self.updateButtonStatus(); }, 250);
|
|
|
|
registerEvent(this.objects.editableContent, 'dblclick', function (e) {
|
|
if (!e) e = self.objects.editableFrame.contentWindow.event;
|
|
var target = e.target ? e.target : e.srcElement;
|
|
|
|
if (target.tagName == 'IMG')
|
|
self.imageDialog();
|
|
});
|
|
|
|
registerEvent(self.objects.editorFrame.contentWindow, 'resize', function () { self.resize(); });
|
|
|
|
if (this.config.focusOnload)
|
|
this.focus();
|
|
|
|
this.loaded = true;
|
|
}
|
|
|
|
advancedEditor.prototype.getContent = function (bodyOnly) {
|
|
var content = this.objects.editableContent.innerHTML;
|
|
if (content) {
|
|
// Perform some clean up on the editor generated HTML
|
|
if (isIE)
|
|
content = content.replace(/<P>/g, '').replace(/<\/P>/g, '<BR>').replace(/\r?\n/g, '');
|
|
else
|
|
content = content.replace(/<br><(ol|ul)>/g, "<$1>");
|
|
if (content.match(/^(?: )?<br>\n*$/i))
|
|
content = '';
|
|
if (bodyOnly)
|
|
return content;
|
|
else {
|
|
var bgcolor = advancedEditor.getHexColor(this.objects.editableContent.style.backgroundColor);
|
|
var html = '<html><body';
|
|
if (bgcolor)
|
|
html += ' bgcolor="' + bgcolor + '"';
|
|
html += '>' + content + '</body></html>';
|
|
return html;
|
|
}
|
|
}
|
|
return '';
|
|
}
|
|
|
|
advancedEditor.prototype.setContent = function (html) {
|
|
this.objects.editableContent.innerHTML = html;
|
|
}
|
|
|
|
advancedEditor.prototype.enable = function () {
|
|
this.changeButtonState(true);
|
|
}
|
|
|
|
advancedEditor.prototype.disable = function () {
|
|
this.changeButtonState(false);
|
|
}
|
|
|
|
advancedEditor.prototype.deleteButton = function () {
|
|
var args = advancedEditor.prototype.deleteButton.arguments;
|
|
if (this.loaded || !args.length) {
|
|
alert("Can't call deleteButton() after the editor has loaded!");
|
|
return;
|
|
}
|
|
|
|
var del = {};
|
|
for (var i = 0; i < args.length; i++)
|
|
del[args[i]] = 1;
|
|
|
|
for (var i = 0; i < this.toolbar.length; i++) {
|
|
for (var button in this.toolbar[i].buttons) {
|
|
if (del[button]) {
|
|
delete this.toolbar[i].buttons[button];
|
|
delete del[button];
|
|
this.toolbar[i].num_buttons--;
|
|
if (!del) break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
advancedEditor.prototype.changeButtonState = function (enabled) {
|
|
var args = advancedEditor.prototype.changeButtonState.arguments;
|
|
|
|
var leave = {};
|
|
for (var i = 0; i < args.length; i++)
|
|
leave[args[i]] = 1;
|
|
|
|
for (var id in this.toolbarMapping) {
|
|
if (leave[id])
|
|
continue;
|
|
|
|
var button = this.objects.editorFrameDoc.getElementById(id);
|
|
if (enabled) {
|
|
button.enabled = button.previousState;
|
|
button.className = button.previousClass;
|
|
button.noUpdates = false;
|
|
}
|
|
else {
|
|
button.noUpdates = true;
|
|
button.previousState = button.enabled;
|
|
button.enabled = false;
|
|
button.previousClass = button.className;
|
|
button.className = 'button button-disabled';
|
|
}
|
|
}
|
|
}
|
|
|
|
advancedEditor.prototype.addToolbar = function () {
|
|
var toolbar = this.objects.editorFrameDoc.createElement('div');
|
|
this.objects.toolbarArea.appendChild(toolbar);
|
|
toolbar.aewidth = 0;
|
|
toolbar.className = 'toolbar';
|
|
toolbar.setAttribute('unselectable', 'on');
|
|
|
|
this.objects.editableFrame.style.height = this.objects.editorFrame.offsetHeight - this.editorFrameExtra - this.toolbarHeight * this.objects.toolbarArea.childNodes.length + 'px';
|
|
|
|
return toolbar;
|
|
}
|
|
|
|
advancedEditor.prototype.addButtonGroup = function (toolbar, group, beforeElement) {
|
|
if (beforeElement)
|
|
toolbar.insertBefore(group, beforeElement);
|
|
else
|
|
toolbar.appendChild(group);
|
|
toolbar.aewidth += group.aewidth;
|
|
}
|
|
|
|
advancedEditor.prototype.addSeparator = function (toolbar, beforeElement) {
|
|
var separator = this.objects.editorFrameDoc.createElement('div');
|
|
separator.className = 'separator';
|
|
separator.setAttribute('unselectable', 'on');
|
|
|
|
if (beforeElement)
|
|
toolbar.insertBefore(separator, beforeElement);
|
|
else
|
|
toolbar.appendChild(separator);
|
|
toolbar.aewidth += this.separatorWidth;
|
|
}
|
|
|
|
advancedEditor.prototype.updateButtonStatus = function () {
|
|
for (var item in this.toolbarQueryCommandState) {
|
|
var buttonConfig = this.toolbar[this.toolbarQueryCommandState[item]].buttons[item];
|
|
var command = buttonConfig[0];
|
|
var button = this.objects.editorFrameDoc.getElementById(item);
|
|
|
|
if (button.mousedown || button.noUpdates)
|
|
continue;
|
|
var previousState = button.pressed;
|
|
if (buttonConfig[3] == 3)
|
|
button.pressed = eval(buttonConfig[5]);
|
|
else
|
|
button.pressed = this.objects.editableFrameDoc.queryCommandState(command);
|
|
|
|
if (previousState == button.pressed)
|
|
continue;
|
|
|
|
if (button.pressed)
|
|
button.className = 'button button-pressed' + (button.mouseover ? ' button-pressed-mouse-over' : '');
|
|
else
|
|
button.className = 'button' + (button.mouseover ? ' button-mouse-over' : '');
|
|
}
|
|
|
|
for (var item in this.toolbarQueryCommandEnabled) {
|
|
var buttonConfig = this.toolbar[this.toolbarQueryCommandEnabled[item]].buttons[item];
|
|
var command = buttonConfig[0];
|
|
var button = this.objects.editorFrameDoc.getElementById(item);
|
|
|
|
if (button.noUpdates)
|
|
continue;
|
|
var enable;
|
|
if (buttonConfig[3] == 4)
|
|
enable = eval(buttonConfig[5]);
|
|
else
|
|
enable = this.objects.editableFrameDoc.queryCommandEnabled(command);
|
|
|
|
if (button.enabled == enable)
|
|
continue;
|
|
|
|
if (enable) {
|
|
button.enabled = true;
|
|
// mozilla bug loses mouseout event after changing applying the alpha transparency
|
|
button.className = 'button' + (button.mouseover && !isMozilla ? ' button-mouse-over' : '');
|
|
}
|
|
else {
|
|
button.enabled = false;
|
|
button.className = 'button button-disabled';
|
|
}
|
|
}
|
|
}
|
|
|
|
advancedEditor.prototype.mouseHandler = function (e) {
|
|
if (!e) e = this.objects.editorFrame.contentWindow.event;
|
|
var target = e.target ? e.target : e.srcElement;
|
|
|
|
switch (e.type) {
|
|
case 'mouseover':
|
|
target.mouseover = true;
|
|
break;
|
|
case 'mouseout':
|
|
target.mouseover = false;
|
|
break;
|
|
case 'mousedown':
|
|
target.mousedown = true;
|
|
break;
|
|
case 'click':
|
|
target.mousedown = false;
|
|
break;
|
|
}
|
|
|
|
if (!target.enabled)
|
|
return;
|
|
|
|
switch (e.type) {
|
|
case 'mouseover':
|
|
if (target.pressed)
|
|
target.className = 'button button-pressed button-pressed-mouse-over';
|
|
else
|
|
target.className = 'button button-mouse-over';
|
|
break;
|
|
case 'mouseout':
|
|
if (target.pressed)
|
|
target.className = 'button button-pressed';
|
|
else
|
|
target.className = 'button';
|
|
break;
|
|
case 'mousedown':
|
|
if (!target.pressed)
|
|
target.className = 'button button-pressed button-pressed-mouse-over';
|
|
break;
|
|
case 'click':
|
|
// Only do something on a left click
|
|
if ((e.which && e.which != 1) || (e.button && e.button != 1))
|
|
return;
|
|
|
|
var button = this.toolbar[this.toolbarMapping[target.id]].buttons[target.id];
|
|
if (button[4])
|
|
eval(button[4]);
|
|
else
|
|
this.execCommand(button[0], null, button[6]);
|
|
|
|
if (button[3] == 1)
|
|
target.pressed = this.objects.editableFrameDoc.queryCommandState(button[0]);
|
|
else if (button[3] == 3)
|
|
target.pressed = eval(button[5]);
|
|
|
|
if (target.pressed)
|
|
target.className = 'button button-pressed' + (target.mouseover ? ' button-pressed-mouse-over' : '');
|
|
else
|
|
target.className = 'button' + (target.mouseover ? ' button-mouse-over' : '');
|
|
break;
|
|
|
|
}
|
|
}
|
|
|
|
advancedEditor.prototype.resize = function () {
|
|
var width = this.objects.editorFrame.offsetWidth - this.editorFrameExtra;
|
|
var height = this.objects.editorFrame.offsetHeight - this.editorFrameExtra;
|
|
var toolbarCount = this.objects.toolbarArea.childNodes.length;
|
|
|
|
for (var i = 0; i < this.objects.toolbarArea.childNodes.length; i++) {
|
|
var toolbar = this.objects.toolbarArea.childNodes[i];
|
|
// Window has shrunk, move overflowing buttons down to next toolbar
|
|
while (toolbar.aewidth + this.toolbarExtra > width && toolbar.childNodes.length > 2) {
|
|
var group = toolbar.removeChild(toolbar.lastChild);
|
|
toolbar.removeChild(toolbar.lastChild);
|
|
toolbar.aewidth -= group.aewidth + this.separatorWidth;
|
|
var nextToolbar;
|
|
if (nextToolbar = toolbar.nextSibling) {
|
|
this.addSeparator(nextToolbar, nextToolbar.firstChild);
|
|
this.addButtonGroup(nextToolbar, group, nextToolbar.firstChild);
|
|
}
|
|
else
|
|
this.addButtonGroup(this.addToolbar(), group);
|
|
}
|
|
|
|
// Window has grown, fill up empty space
|
|
var nextToolbar;
|
|
if (nextToolbar = toolbar.nextSibling) {
|
|
var group = nextToolbar.firstChild;
|
|
while (nextToolbar && group && toolbar.aewidth + this.toolbarExtra + this.separatorWidth + group.aewidth <= width) {
|
|
nextToolbar.removeChild(group);
|
|
nextToolbar.aewidth -= group.aewidth;
|
|
if (nextToolbar.hasChildNodes()) {
|
|
nextToolbar.removeChild(nextToolbar.firstChild);
|
|
nextToolbar.aewidth -= this.separatorWidth;
|
|
}
|
|
else {
|
|
this.objects.toolbarArea.removeChild(nextToolbar);
|
|
nextToolbar = toolbar.nextSibling;
|
|
}
|
|
this.addSeparator(toolbar);
|
|
this.addButtonGroup(toolbar, group);
|
|
group = nextToolbar ? nextToolbar.firstChild : null;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (toolbarCount != this.objects.toolbarArea.childNodes.length) {
|
|
var contentHeight = height - this.toolbarHeight * this.objects.toolbarArea.childNodes.length;
|
|
if (this.sourceMode) {
|
|
this.objects.editableSource.style.top = this.toolbarHeight * this.objects.toolbarArea.childNodes.length + 'px';
|
|
this.objects.editableSource.style.height = contentHeight + 'px';
|
|
// Reset height back to 0px, since addToolbar() sets it
|
|
this.objects.editableFrame.style.height = '0px';
|
|
}
|
|
else
|
|
this.objects.editableFrame.style.height = contentHeight + 'px';
|
|
}
|
|
}
|
|
|
|
// Set focus on the editor content area
|
|
advancedEditor.prototype.focus = function (focusWindow) {
|
|
if (focusWindow)
|
|
window.focus();
|
|
this.objects.editableFrame.contentWindow.focus();
|
|
}
|
|
|
|
advancedEditor.prototype.execCommand = function (cmd, value, useCSS) {
|
|
if (isMozilla && useCSS)
|
|
this.objects.editableFrameDoc.execCommand("useCSS", false, false);
|
|
|
|
// For some commands (eg. indent, lists), without focusing it ends up running the
|
|
// command in the toolbar area. If you comment out the focus code, then in IE
|
|
// click in the editor, click outside the editor, then click indent, you'll see
|
|
// the problem.
|
|
if (isIE || isWebkit)
|
|
this.focus();
|
|
|
|
var res;
|
|
try {
|
|
res = this.objects.editableFrameDoc.execCommand(cmd, false, value);
|
|
}
|
|
catch (err) {
|
|
// debug("execCommand failed (" + cmd + " => " + value + ")" + err);
|
|
}
|
|
// debug("execCommand(" + cmd + ", " + value + ") => " + res + (useCSS ? ' (css)' : ''));
|
|
|
|
if (isMozilla && useCSS)
|
|
this.objects.editableFrameDoc.execCommand("useCSS", false, true);
|
|
}
|
|
|
|
advancedEditor.prototype.queryCommandValue = function (cmd, useCSS) {
|
|
if (isMozilla && useCSS)
|
|
this.objects.editableFrameDoc.execCommand("useCSS", false, false);
|
|
|
|
var value = this.objects.editableFrameDoc.queryCommandValue(cmd);
|
|
|
|
if (isMozilla && useCSS)
|
|
this.objects.editableFrameDoc.execCommand("useCSS", false, true);
|
|
// debug("queryCommandValue(" + cmd + ") => " + value + (useCSS ? ' (css)' : ''));
|
|
|
|
cmd = cmd.toLowerCase();
|
|
if (cmd == 'forecolor' || cmd == 'backcolor' || cmd == 'hilitecolor')
|
|
return advancedEditor.getHexColor(value);
|
|
return value;
|
|
}
|
|
|
|
advancedEditor.prototype.setColor = function () {
|
|
this.focus(1);
|
|
if (dialogWindow.values._type == 'foreground')
|
|
this.execCommand('ForeColor', dialogWindow.values.hexcolor);
|
|
else if (dialogWindow.values._type == 'background')
|
|
this.objects.editableContent.style.backgroundColor = dialogWindow.values.hexcolor;
|
|
else if (dialogWindow.values._type == 'highlight')
|
|
this.execCommand(isIE ? 'BackColor' : 'HiliteColor', dialogWindow.values.hexcolor, true);
|
|
}
|
|
|
|
advancedEditor.prototype.setFont = function () {
|
|
this.focus(1);
|
|
if (dialogWindow.values.size)
|
|
this.execCommand('FontSize', dialogWindow.values.size);
|
|
if (dialogWindow.values.font)
|
|
this.execCommand('FontName', dialogWindow.values.font);
|
|
if (dialogWindow.values.hexcolor)
|
|
this.execCommand('ForeColor', dialogWindow.values.hexcolor);
|
|
if ((dialogWindow.values.style.indexOf('b') != -1) != dialogWindow.values._bold)
|
|
this.execCommand('Bold');
|
|
if ((dialogWindow.values.style.indexOf('i') != -1) != dialogWindow.values._italic)
|
|
this.execCommand('Italic');
|
|
if ((dialogWindow.values.underline == 'on') != dialogWindow.values._underline)
|
|
this.execCommand('Underline');
|
|
}
|
|
|
|
advancedEditor.prototype.insertImage = function () {
|
|
this.focus(1);
|
|
var img = this.objects.editableFrameDoc.createElement('img');
|
|
|
|
var values = dialogWindow.values;
|
|
img.src = values['src-url-input'];
|
|
|
|
if (values['src-inline'])
|
|
img.setAttribute('gforuminline', values['src-inline-input']);
|
|
|
|
var currentSelection = new advancedEditorSelection(isIE ? this.objects.editableFrameDoc : this.objects.editableFrame.contentWindow);
|
|
currentSelection.replaceSelection(img);
|
|
}
|
|
|
|
advancedEditor.prototype.insertLink = function () {
|
|
this.focus(1);
|
|
if (dialogWindow.values.url)
|
|
this.execCommand('CreateLink', dialogWindow.values.url);
|
|
}
|
|
|
|
advancedEditor.prototype.colorDialog = function (type) {
|
|
var values = {
|
|
hexcolor : null,
|
|
_type : type,
|
|
_foreground : null,
|
|
_background : null,
|
|
_highlight : null
|
|
};
|
|
|
|
values._selection = new advancedEditorSelection(isIE ? this.objects.editableFrameDoc : this.objects.editableFrame.contentWindow);
|
|
var selectionObjects = values._selection.getSelection();
|
|
|
|
values._foreground = this.queryCommandValue('ForeColor') || this.config.defaultForeground;
|
|
values._background = advancedEditor.getHexColor(this.objects.editableContent.style.backgroundColor) || this.config.defaultBackground;
|
|
values._highlight = this.queryCommandValue(isIE ? 'BackColor' : 'HiliteColor', true);
|
|
|
|
if (type == 'foreground')
|
|
values.hexcolor = values._foreground;
|
|
else if (type == 'background')
|
|
values.hexcolor = values._background;
|
|
else if (type == 'highlight')
|
|
values.hexcolor = values._highlight;
|
|
|
|
this.showDialog('editor_color.html', isSafari ? 375 : 358, 200, values, 'opener.dialogWindow.editor.setColor()');
|
|
}
|
|
|
|
advancedEditor.prototype.fontDialog = function () {
|
|
var values = {
|
|
font : null,
|
|
style : null,
|
|
size : null,
|
|
underline : null,
|
|
hexcolor : null,
|
|
_type : 'foreground',
|
|
_foreground : null,
|
|
_background : null,
|
|
_highlight : null
|
|
};
|
|
|
|
values._selection = new advancedEditorSelection(isIE ? this.objects.editableFrameDoc : this.objects.editableFrame.contentWindow);
|
|
var selectionObjects = values._selection.getSelection();
|
|
|
|
values.font = this.queryCommandValue('FontName') || this.config.defaultFont;
|
|
values.size = this.queryCommandValue('FontSize') || this.config.defaultFontSize;
|
|
values.underline = this.objects.editableFrameDoc.queryCommandState('Underline');
|
|
values._underline = values.underline;
|
|
values._bold = this.objects.editableFrameDoc.queryCommandState('Bold');
|
|
values._italic = this.objects.editableFrameDoc.queryCommandState('Italic');
|
|
values.style = (values._bold && values._italic ? 'bi' : values._bold ? 'b' : values._italic ? 'i' : 'r');
|
|
|
|
values._foreground = this.queryCommandValue('ForeColor') || this.config.defaultForeground;
|
|
values._background = advancedEditor.getHexColor(this.objects.editableContent.style.backgroundColor) || this.config.defaultBackground;
|
|
values._highlight = this.queryCommandValue(isIE ? 'BackColor' : 'HiliteColor', true);
|
|
values.hexcolor = values._foreground;
|
|
|
|
this.showDialog('editor_font.html', 447, 250, values, 'opener.dialogWindow.editor.setFont()');
|
|
}
|
|
|
|
advancedEditor.prototype.imageDialog = function () {
|
|
var values = {
|
|
'src-inline' : null,
|
|
'src-inline-input' : null,
|
|
'src-url-input' : null
|
|
};
|
|
var url = 'editor_image.html';
|
|
if (this.config.imageDialogURL && this.config.imageDialogURL.match(/^https?:/i))
|
|
url = this.config.imageDialogURL;
|
|
else
|
|
url = parent.location.protocol + '//' + parent.location.host +
|
|
parent.location.pathname.substr(0, parent.location.pathname.lastIndexOf('/') + 1) +
|
|
this.config.imageDialogURL;
|
|
this.showDialog(url, 430, 500, values, 'opener.dialogWindow.editor.insertImage()');
|
|
}
|
|
|
|
advancedEditor.prototype.linkDialog = function () {
|
|
var values = {
|
|
protocol : null,
|
|
url : null,
|
|
title : null
|
|
};
|
|
|
|
values._selection = new advancedEditorSelection(isIE ? this.objects.editableFrameDoc : this.objects.editableFrame.contentWindow);
|
|
var selectionObjects = values._selection.getSelection();
|
|
if (selectionObjects.length == 1 && selectionObjects[0].tagName == 'A') {
|
|
values.url = selectionObjects[0].href;
|
|
values.protocol = values.url.replace(/^(\w+:(?:\/\/)?).*$/, "$1");
|
|
values.title = selectionObjects[0].title;
|
|
}
|
|
|
|
this.showDialog('editor_link.html', 390, 100, values, 'opener.dialogWindow.editor.insertLink()');
|
|
}
|
|
|
|
advancedEditor.prototype.showDialog = function (url, width, height, values, returnCall) {
|
|
if (dialogWindow.win && !dialogWindow.win.closed)
|
|
dialogWindow.win.close();
|
|
|
|
if (url.match(/^https?:/i))
|
|
dialogWindow.url = url;
|
|
else
|
|
dialogWindow.url = this.objects.editorFrame.contentWindow.location.href.replace(/editor_iframe\.html$/, '') + url;
|
|
dialogWindow.width = width;
|
|
dialogWindow.height = height;
|
|
dialogWindow.name = Math.random().toString().replace(/\./, "");
|
|
|
|
dialogWindow.left = (screen.width - width) / 2;
|
|
dialogWindow.top = (screen.height - height) / 2;
|
|
dialogWindow.attribs = 'left=' + dialogWindow.left + ',top=' + dialogWindow.top +
|
|
'location=0,menubar=0,resizable=1,scrollbars=0,status=0,toolbar=0,width=' + dialogWindow.width +
|
|
',height=' + dialogWindow.height;
|
|
|
|
dialogWindow.editor = this;
|
|
dialogWindow.values = values;
|
|
dialogWindow.returnCall = returnCall;
|
|
try {
|
|
dialogWindow.win = window.open(dialogWindow.url, dialogWindow.name, dialogWindow.attribs);
|
|
dialogWindow.win.focus();
|
|
}
|
|
catch (e) {
|
|
// FIXME should we alert?
|
|
}
|
|
}
|
|
|
|
advancedEditor.prototype.toggleSource = function () {
|
|
if (!this.objects.editableSource)
|
|
return;
|
|
|
|
this.sourceMode = !this.sourceMode;
|
|
if (this.sourceMode) {
|
|
this.changeButtonState(false, 'source');
|
|
this.objects.editableSource.value = this.getContent();
|
|
this.objects.editableSource.style.top = this.toolbarHeight * this.objects.toolbarArea.childNodes.length + 'px';
|
|
this.objects.editableSource.style.height = this.objects.editableFrame.offsetHeight + 'px';
|
|
this.objects.editableSource.style.visibility = 'visible';
|
|
this.objects.editableFrame.style.visibility = 'hidden';
|
|
// Without setting this to 0px, mozilla loses the cursor in the source textarea
|
|
this.objects.editableFrame.style.height = '0px';
|
|
this.objects.editableSource.focus();
|
|
}
|
|
else {
|
|
this.changeButtonState(true, 'source');
|
|
this.objects.editableContent.innerHTML = this.objects.editableSource.value;
|
|
this.objects.editableSource.style.visibility = 'hidden';
|
|
this.objects.editableFrame.style.height = this.objects.editableSource.style.height;
|
|
this.objects.editableFrame.style.visibility = 'visible';
|
|
this.focus();
|
|
}
|
|
}
|
|
|
|
advancedEditor.prototype.selectionMade = function () {
|
|
if (this.objects.editableFrameDoc.selection)
|
|
return this.objects.editableFrameDoc.selection.type != 'None';
|
|
// Don't use advancedEditorSelection because in Gecko, range.cloneContents()
|
|
// tries to re-fetch broken images, and since this gets called on a timer,
|
|
// floods the server with requests.
|
|
if (this.objects.editableFrame.contentWindow.getSelection)
|
|
return this.objects.editableFrame.contentWindow.getSelection().toString().length > 0;
|
|
}
|
|
|
|
advancedEditor.getHexColor = function (color) {
|
|
function intToHex(i) {
|
|
i = parseInt(i);
|
|
return (i < 16 ? '0' : '') + i.toString(16).toUpperCase();
|
|
}
|
|
|
|
if (typeof(color) == 'number')
|
|
return '#' + intToHex(color & 0xff) + intToHex((color >> 8) & 0xff) + intToHex((color >> 16) & 0xff);
|
|
else if (typeof(color) != 'string')
|
|
return;
|
|
|
|
if (color.match(/rgb\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)/i))
|
|
return '#' + intToHex(RegExp.$1) + intToHex(RegExp.$2) + intToHex(RegExp.$3);
|
|
|
|
if (color == 'transparent')
|
|
return;
|
|
|
|
return color.toUpperCase();
|
|
}
|
|
|
|
function advancedEditorSelection(documentWindow) {
|
|
if (documentWindow.selection)
|
|
this.document = documentWindow;
|
|
else
|
|
this.window = documentWindow;
|
|
}
|
|
|
|
advancedEditorSelection.prototype.getSelection = function () {
|
|
if (this.document) {
|
|
this.document.parentWindow.focus();
|
|
this.selection = this.document.selection;
|
|
this.range = this.selection.createRange();
|
|
this.nodes = [];
|
|
if (this.selection.type == 'Control') {
|
|
for (var i = 0; i < this.range.length; i++)
|
|
this.nodes[i] = this.range(i);
|
|
}
|
|
else if (this.selection.type == 'Text') {
|
|
this.nodesContainer = this.document.createElement('span');
|
|
this.nodesContainer.innerHTML = this.range.htmlText;
|
|
this.nodes = this.nodesContainer.childNodes;
|
|
}
|
|
}
|
|
else {
|
|
this.selection = this.window.getSelection();
|
|
this.nodes = [];
|
|
if (this.selection && this.selection.getRangeAt) {
|
|
// Safari triggers an exception when there's nothing selected
|
|
try {
|
|
this.range = this.selection.getRangeAt(0);
|
|
this.documentFragment = this.range.cloneContents();
|
|
this.nodes = this.documentFragment.childNodes;
|
|
if (this.nodes.length == 1 && this.nodes[0].nodeType == 3 && this.nodes[0].nodeValue == '')
|
|
this.nodes = [];
|
|
}
|
|
catch (e) {}
|
|
}
|
|
}
|
|
|
|
return this.nodes;
|
|
}
|
|
|
|
advancedEditorSelection.prototype.getSelectionHTML = function () {
|
|
if (!this.selection)
|
|
this.getSelection();
|
|
|
|
if (this.document)
|
|
return this.range.htmlText;
|
|
else {
|
|
var div = document.createElement('div');
|
|
div.appendChild(this.documentFragment);
|
|
return div.innerHTML;
|
|
}
|
|
}
|
|
|
|
advancedEditorSelection.prototype.replaceSelection = function (node) {
|
|
if (!this.selection)
|
|
this.getSelection();
|
|
|
|
if (this.document) {
|
|
if (this.selection.type == 'Control') {
|
|
this.selection.clear();
|
|
this.range = this.selection.createRange();
|
|
}
|
|
this.range.pasteHTML(node.outerHTML);
|
|
}
|
|
else {
|
|
this.range.deleteContents();
|
|
this.range.insertNode(node);
|
|
}
|
|
}
|