2260 lines
81 KiB
JavaScript
2260 lines
81 KiB
JavaScript
|
/*****************************************************************************
|
||
|
*
|
||
|
* Copyright (c) 2003-2005 Kupu Contributors. All rights reserved.
|
||
|
*
|
||
|
* This software is distributed under the terms of the Kupu
|
||
|
* License. See LICENSE.txt for license text. For a list of Kupu
|
||
|
* Contributors see CREDITS.txt.
|
||
|
*
|
||
|
*****************************************************************************/
|
||
|
|
||
|
// $Id: kupubasetools.js 14505 2005-07-11 15:54:18Z duncan $
|
||
|
|
||
|
|
||
|
//----------------------------------------------------------------------------
|
||
|
//
|
||
|
// Toolboxes
|
||
|
//
|
||
|
// These are addons for Kupu, simple plugins that implement a certain
|
||
|
// interface to provide functionality and control view aspects.
|
||
|
//
|
||
|
//----------------------------------------------------------------------------
|
||
|
|
||
|
//----------------------------------------------------------------------------
|
||
|
// Superclasses
|
||
|
//----------------------------------------------------------------------------
|
||
|
|
||
|
function KupuTool() {
|
||
|
/* Superclass (or actually more of an interface) for tools
|
||
|
|
||
|
Tools must implement at least an initialize method and an
|
||
|
updateState method, and can implement other methods to add
|
||
|
certain extra functionality (e.g. createContextMenuElements).
|
||
|
*/
|
||
|
|
||
|
this.toolboxes = {};
|
||
|
|
||
|
// methods
|
||
|
this.initialize = function(editor) {
|
||
|
/* Initialize the tool.
|
||
|
|
||
|
Obviously this can be overriden but it will do
|
||
|
for the most simple cases
|
||
|
*/
|
||
|
this.editor = editor;
|
||
|
};
|
||
|
|
||
|
this.registerToolBox = function(id, toolbox) {
|
||
|
/* register a ui box
|
||
|
|
||
|
Note that this needs to be called *after* the tool has been
|
||
|
registered to the KupuEditor
|
||
|
*/
|
||
|
this.toolboxes[id] = toolbox;
|
||
|
toolbox.initialize(this, this.editor);
|
||
|
};
|
||
|
|
||
|
this.updateState = function(selNode, event) {
|
||
|
/* Is called when user moves cursor to other element
|
||
|
|
||
|
Calls the updateState for all toolboxes and may want perform
|
||
|
some actions itself
|
||
|
*/
|
||
|
for (id in this.toolboxes) {
|
||
|
this.toolboxes[id].updateState(selNode, event);
|
||
|
};
|
||
|
};
|
||
|
|
||
|
this.enable = function() {
|
||
|
// Called when the tool is enabled after a form is dismissed.
|
||
|
}
|
||
|
|
||
|
this.disable = function() {
|
||
|
// Called when the tool is disabled (e.g. for a modal form)
|
||
|
}
|
||
|
// private methods
|
||
|
addEventHandler = addEventHandler;
|
||
|
|
||
|
this._selectSelectItem = function(select, item) {
|
||
|
this.editor.logMessage(_('Deprecation warning: KupuTool._selectSelectItem'));
|
||
|
};
|
||
|
this._fixTabIndex = function(element) {
|
||
|
var tabIndex = this.editor.getDocument().getEditable().tabIndex-1;
|
||
|
if (tabIndex && !element.tabIndex) {
|
||
|
element.tabIndex = tabIndex;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function KupuToolBox() {
|
||
|
/* Superclass for a user-interface object that controls a tool */
|
||
|
|
||
|
this.initialize = function(tool, editor) {
|
||
|
/* store a reference to the tool and the editor */
|
||
|
this.tool = tool;
|
||
|
this.editor = editor;
|
||
|
};
|
||
|
|
||
|
this.updateState = function(selNode, event) {
|
||
|
/* update the toolbox according to the current iframe's situation */
|
||
|
};
|
||
|
|
||
|
this._selectSelectItem = function(select, item) {
|
||
|
this.editor.logMessage(_('Deprecation warning: KupuToolBox._selectSelectItem'));
|
||
|
};
|
||
|
};
|
||
|
|
||
|
function NoContextMenu(object) {
|
||
|
/* Decorator for a tool to suppress the context menu */
|
||
|
object.createContextMenuElements = function(selNode, event) {
|
||
|
return [];
|
||
|
}
|
||
|
return object;
|
||
|
}
|
||
|
|
||
|
// Helper function for enabling/disabling tools
|
||
|
function KupuButtonDisable(button) {
|
||
|
button = button || this.button;
|
||
|
button.disabled = "disabled";
|
||
|
button.className += ' disabled';
|
||
|
}
|
||
|
function KupuButtonEnable(button) {
|
||
|
button = button || this.button;
|
||
|
button.disabled = "";
|
||
|
button.className = button.className.replace(/ *\bdisabled\b/g, '');
|
||
|
}
|
||
|
|
||
|
|
||
|
//----------------------------------------------------------------------------
|
||
|
// Implementations
|
||
|
//----------------------------------------------------------------------------
|
||
|
|
||
|
function KupuButton(buttonid, commandfunc, tool) {
|
||
|
/* Base prototype for kupu button tools */
|
||
|
this.buttonid = buttonid;
|
||
|
this.button = getFromSelector(buttonid);
|
||
|
this.commandfunc = commandfunc;
|
||
|
this.tool = tool;
|
||
|
|
||
|
this.initialize = function(editor) {
|
||
|
this.editor = editor;
|
||
|
this._fixTabIndex(this.button);
|
||
|
addEventHandler(this.button, 'click', this.execCommand, this);
|
||
|
};
|
||
|
|
||
|
this.execCommand = function() {
|
||
|
/* exec this button's command */
|
||
|
this.commandfunc(this, this.editor, this.tool);
|
||
|
};
|
||
|
|
||
|
this.updateState = function(selNode, event) {
|
||
|
/* override this in subclasses to determine whether a button should
|
||
|
look 'pressed in' or not
|
||
|
*/
|
||
|
};
|
||
|
this.disable = KupuButtonDisable;
|
||
|
this.enable = KupuButtonEnable;
|
||
|
};
|
||
|
|
||
|
KupuButton.prototype = new KupuTool;
|
||
|
function KupuStateButton(buttonid, commandfunc, checkfunc, offclass, onclass) {
|
||
|
/* A button that can have two states (e.g. pressed and
|
||
|
not-pressed) based on CSS classes */
|
||
|
this.buttonid = buttonid;
|
||
|
this.button = getFromSelector(buttonid);
|
||
|
this.commandfunc = commandfunc;
|
||
|
this.checkfunc = checkfunc;
|
||
|
this.offclass = offclass;
|
||
|
this.onclass = onclass;
|
||
|
this.pressed = false;
|
||
|
|
||
|
this.execCommand = function() {
|
||
|
/* exec this button's command */
|
||
|
this.button.className = (this.pressed ? this.offclass : this.onclass);
|
||
|
this.pressed = !this.pressed;
|
||
|
this.editor.focusDocument();
|
||
|
this.commandfunc(this, this.editor);
|
||
|
};
|
||
|
|
||
|
this.updateState = function(selNode, event) {
|
||
|
/* check if we need to be clicked or unclicked, and update accordingly
|
||
|
|
||
|
if the state of the button should be changed, we set the class
|
||
|
*/
|
||
|
var currclass = this.button.className;
|
||
|
var newclass = null;
|
||
|
if (this.checkfunc(selNode, this, this.editor, event)) {
|
||
|
newclass = this.onclass;
|
||
|
this.pressed = true;
|
||
|
} else {
|
||
|
newclass = this.offclass;
|
||
|
this.pressed = false;
|
||
|
};
|
||
|
if (currclass != newclass) {
|
||
|
this.button.className = newclass;
|
||
|
};
|
||
|
};
|
||
|
};
|
||
|
|
||
|
KupuStateButton.prototype = new KupuButton;
|
||
|
|
||
|
/* Same as the state button, but the focusDocument call is delayed.
|
||
|
* Mozilla&Firefox have a bug on windows which can cause a crash if you
|
||
|
* change CSS positioning styles on an element which has focus.
|
||
|
*/
|
||
|
function KupuLateFocusStateButton(buttonid, commandfunc, checkfunc, offclass, onclass) {
|
||
|
KupuStateButton.apply(this, [buttonid, commandfunc, checkfunc, offclass, onclass]);
|
||
|
this.execCommand = function() {
|
||
|
/* exec this button's command */
|
||
|
this.button.className = (this.pressed ? this.offclass : this.onclass);
|
||
|
this.pressed = !this.pressed;
|
||
|
this.commandfunc(this, this.editor);
|
||
|
this.editor.focusDocument();
|
||
|
};
|
||
|
}
|
||
|
KupuLateFocusStateButton.prototype = new KupuStateButton;
|
||
|
|
||
|
function KupuRemoveElementButton(buttonid, element_name, cssclass) {
|
||
|
/* A button specialized in removing elements in the current node
|
||
|
context. Typical usages include removing links, images, etc. */
|
||
|
this.button = getFromSelector(buttonid);
|
||
|
this.onclass = 'invisible';
|
||
|
this.offclass = cssclass;
|
||
|
this.pressed = false;
|
||
|
|
||
|
this.commandfunc = function(button, editor) {
|
||
|
editor.removeNearestParentOfType(editor.getSelectedNode(), element_name);
|
||
|
};
|
||
|
|
||
|
this.checkfunc = function(currnode, button, editor, event) {
|
||
|
var element = editor.getNearestParentOfType(currnode, element_name);
|
||
|
return (element ? false : true);
|
||
|
};
|
||
|
};
|
||
|
|
||
|
KupuRemoveElementButton.prototype = new KupuStateButton;
|
||
|
|
||
|
function KupuUI(textstyleselectid) {
|
||
|
/* View
|
||
|
|
||
|
This is the main view, which controls most of the toolbar buttons.
|
||
|
Even though this is probably never going to be removed from the view,
|
||
|
it was easier to implement this as a plain tool (plugin) as well.
|
||
|
*/
|
||
|
|
||
|
// attributes
|
||
|
this.tsselect = getFromSelector(textstyleselectid);
|
||
|
|
||
|
this.initialize = function(editor) {
|
||
|
/* initialize the ui like tools */
|
||
|
this.editor = editor;
|
||
|
this._fixTabIndex(this.tsselect);
|
||
|
this._selectevent = addEventHandler(this.tsselect, 'change', this.setTextStyleHandler, this);
|
||
|
};
|
||
|
|
||
|
this.setTextStyleHandler = function(event) {
|
||
|
this.setTextStyle(this.tsselect.options[this.tsselect.selectedIndex].value);
|
||
|
};
|
||
|
|
||
|
// event handlers
|
||
|
this.basicButtonHandler = function(action) {
|
||
|
/* event handler for basic actions (toolbar buttons) */
|
||
|
this.editor.execCommand(action);
|
||
|
this.editor.updateState();
|
||
|
};
|
||
|
|
||
|
this.saveButtonHandler = function() {
|
||
|
/* handler for the save button */
|
||
|
this.editor.saveDocument();
|
||
|
};
|
||
|
|
||
|
this.saveAndExitButtonHandler = function(redirect_url) {
|
||
|
/* save the document and, if successful, redirect */
|
||
|
this.editor.saveDocument(redirect_url);
|
||
|
};
|
||
|
|
||
|
this.cutButtonHandler = function() {
|
||
|
try {
|
||
|
this.editor.execCommand('Cut');
|
||
|
} catch (e) {
|
||
|
if (this.editor.getBrowserName() == 'Mozilla') {
|
||
|
alert(_('Cutting from JavaScript is disabled on your Mozilla due to security settings. For more information, read http://www.mozilla.org/editor/midasdemo/securityprefs.html'));
|
||
|
} else {
|
||
|
throw e;
|
||
|
};
|
||
|
};
|
||
|
this.editor.updateState();
|
||
|
};
|
||
|
|
||
|
this.copyButtonHandler = function() {
|
||
|
try {
|
||
|
this.editor.execCommand('Copy');
|
||
|
} catch (e) {
|
||
|
if (this.editor.getBrowserName() == 'Mozilla') {
|
||
|
alert(_('Copying from JavaScript is disabled on your Mozilla due to security settings. For more information, read http://www.mozilla.org/editor/midasdemo/securityprefs.html'));
|
||
|
} else {
|
||
|
throw e;
|
||
|
};
|
||
|
};
|
||
|
this.editor.updateState();
|
||
|
};
|
||
|
|
||
|
this.pasteButtonHandler = function() {
|
||
|
try {
|
||
|
this.editor.execCommand('Paste');
|
||
|
} catch (e) {
|
||
|
if (this.editor.getBrowserName() == 'Mozilla') {
|
||
|
alert(_('Pasting from JavaScript is disabled on your Mozilla due to security settings. For more information, read http://www.mozilla.org/editor/midasdemo/securityprefs.html'));
|
||
|
} else {
|
||
|
throw e;
|
||
|
};
|
||
|
};
|
||
|
this.editor.updateState();
|
||
|
};
|
||
|
|
||
|
this.setTextStyle = function(style) {
|
||
|
/* method for the text style pulldown
|
||
|
|
||
|
parse the argument into a type and classname part if it contains
|
||
|
a pipe symbol (|), generate a block element
|
||
|
*/
|
||
|
var classname = "";
|
||
|
var eltype = style;
|
||
|
if (style.indexOf('|') > -1) {
|
||
|
style = style.split('|');
|
||
|
eltype = style[0];
|
||
|
classname = style[1];
|
||
|
};
|
||
|
|
||
|
var command = eltype;
|
||
|
// first create the element, then find it and set the classname
|
||
|
if (this.editor.getBrowserName() == 'IE') {
|
||
|
command = '<' + eltype + '>';
|
||
|
};
|
||
|
this.editor.getDocument().execCommand('formatblock', command);
|
||
|
|
||
|
// now get a reference to the element just added
|
||
|
var selNode = this.editor.getSelectedNode();
|
||
|
var el = this.editor.getNearestParentOfType(selNode, eltype);
|
||
|
|
||
|
// now set the classname
|
||
|
if (classname) {
|
||
|
el.className = classname;
|
||
|
};
|
||
|
this.editor.updateState();
|
||
|
};
|
||
|
|
||
|
this.updateState = function(selNode) {
|
||
|
/* set the text-style pulldown */
|
||
|
|
||
|
// first get the nearest style
|
||
|
var styles = {}; // use an object here so we can use the 'in' operator later on
|
||
|
for (var i=0; i < this.tsselect.options.length; i++) {
|
||
|
// XXX we should cache this
|
||
|
styles[this.tsselect.options[i].value.toUpperCase()] = i;
|
||
|
}
|
||
|
|
||
|
var currnode = selNode;
|
||
|
var index = 0;
|
||
|
while (currnode) {
|
||
|
if (currnode.nodeName.toUpperCase() in styles) {
|
||
|
index = styles[currnode.nodeName.toUpperCase()];
|
||
|
break
|
||
|
}
|
||
|
currnode = currnode.parentNode;
|
||
|
}
|
||
|
|
||
|
this.tsselect.selectedIndex = index;
|
||
|
};
|
||
|
|
||
|
this.createContextMenuElements = function(selNode, event) {
|
||
|
var ret = new Array();
|
||
|
ret.push(new ContextMenuElement(_('Cut'),
|
||
|
this.cutButtonHandler, this));
|
||
|
ret.push(new ContextMenuElement(_('Copy'),
|
||
|
this.copyButtonHandler, this));
|
||
|
ret.push(new ContextMenuElement(_('Paste'),
|
||
|
this.pasteButtonHandler, this));
|
||
|
return ret;
|
||
|
};
|
||
|
this.disable = function() {
|
||
|
this.tsselect.disabled = "disabled";
|
||
|
}
|
||
|
this.enable = function() {
|
||
|
this.tsselect.disabled = "";
|
||
|
}
|
||
|
}
|
||
|
|
||
|
KupuUI.prototype = new KupuTool;
|
||
|
|
||
|
function ColorchooserTool(fgcolorbuttonid, hlcolorbuttonid, colorchooserid) {
|
||
|
/* the colorchooser */
|
||
|
|
||
|
this.fgcolorbutton = getFromSelector(fgcolorbuttonid);
|
||
|
this.hlcolorbutton = getFromSelector(hlcolorbuttonid);
|
||
|
this.ccwindow = getFromSelector(colorchooserid);
|
||
|
this.command = null;
|
||
|
|
||
|
this.initialize = function(editor) {
|
||
|
/* attach the event handlers */
|
||
|
this.editor = editor;
|
||
|
|
||
|
this.createColorchooser(this.ccwindow);
|
||
|
|
||
|
addEventHandler(this.fgcolorbutton, "click", this.openFgColorChooser, this);
|
||
|
addEventHandler(this.hlcolorbutton, "click", this.openHlColorChooser, this);
|
||
|
addEventHandler(this.ccwindow, "click", this.chooseColor, this);
|
||
|
|
||
|
this.hide();
|
||
|
|
||
|
this.editor.logMessage(_('Colorchooser tool initialized'));
|
||
|
};
|
||
|
|
||
|
this.updateState = function(selNode) {
|
||
|
/* update state of the colorchooser */
|
||
|
this.hide();
|
||
|
};
|
||
|
|
||
|
this.openFgColorChooser = function() {
|
||
|
/* event handler for opening the colorchooser */
|
||
|
this.command = "forecolor";
|
||
|
this.show();
|
||
|
};
|
||
|
|
||
|
this.openHlColorChooser = function() {
|
||
|
/* event handler for closing the colorchooser */
|
||
|
if (this.editor.getBrowserName() == "IE") {
|
||
|
this.command = "backcolor";
|
||
|
} else {
|
||
|
this.command = "hilitecolor";
|
||
|
}
|
||
|
this.show();
|
||
|
};
|
||
|
|
||
|
this.chooseColor = function(event) {
|
||
|
/* event handler for choosing the color */
|
||
|
var target = _SARISSA_IS_MOZ ? event.target : event.srcElement;
|
||
|
var cell = this.editor.getNearestParentOfType(target, 'td');
|
||
|
this.editor.execCommand(this.command, cell.getAttribute('bgColor'));
|
||
|
this.hide();
|
||
|
|
||
|
this.editor.logMessage(_('Color chosen'));
|
||
|
};
|
||
|
|
||
|
this.show = function(command) {
|
||
|
/* show the colorchooser */
|
||
|
this.ccwindow.style.display = "block";
|
||
|
};
|
||
|
|
||
|
this.hide = function() {
|
||
|
/* hide the colorchooser */
|
||
|
this.command = null;
|
||
|
this.ccwindow.style.display = "none";
|
||
|
};
|
||
|
|
||
|
this.createColorchooser = function(table) {
|
||
|
/* create the colorchooser table */
|
||
|
|
||
|
var chunks = new Array('00', '33', '66', '99', 'CC', 'FF');
|
||
|
table.setAttribute('id', 'kupu-colorchooser-table');
|
||
|
table.style.borderWidth = '2px';
|
||
|
table.style.borderStyle = 'solid';
|
||
|
table.style.position = 'absolute';
|
||
|
table.style.cursor = 'default';
|
||
|
table.style.display = 'none';
|
||
|
|
||
|
var tbody = document.createElement('tbody');
|
||
|
|
||
|
for (var i=0; i < 6; i++) {
|
||
|
var tr = document.createElement('tr');
|
||
|
var r = chunks[i];
|
||
|
for (var j=0; j < 6; j++) {
|
||
|
var g = chunks[j];
|
||
|
for (var k=0; k < 6; k++) {
|
||
|
var b = chunks[k];
|
||
|
var color = '#' + r + g + b;
|
||
|
var td = document.createElement('td');
|
||
|
td.setAttribute('bgColor', color);
|
||
|
td.style.backgroundColor = color;
|
||
|
td.style.borderWidth = '1px';
|
||
|
td.style.borderStyle = 'solid';
|
||
|
td.style.fontSize = '1px';
|
||
|
td.style.width = '10px';
|
||
|
td.style.height = '10px';
|
||
|
var text = document.createTextNode('\u00a0');
|
||
|
td.appendChild(text);
|
||
|
tr.appendChild(td);
|
||
|
}
|
||
|
}
|
||
|
tbody.appendChild(tr);
|
||
|
}
|
||
|
table.appendChild(tbody);
|
||
|
|
||
|
return table;
|
||
|
};
|
||
|
this.enable = function() {
|
||
|
KupuButtonEnable(this.fgcolorbutton);
|
||
|
KupuButtonEnable(this.hlcolorbutton);
|
||
|
}
|
||
|
this.disable = function() {
|
||
|
KupuButtonDisable(this.fgcolorbutton);
|
||
|
KupuButtonDisable(this.hlcolorbutton);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
ColorchooserTool.prototype = new KupuTool;
|
||
|
|
||
|
function PropertyTool(titlefieldid, descfieldid) {
|
||
|
/* The property tool */
|
||
|
|
||
|
this.titlefield = getFromSelector(titlefieldid);
|
||
|
this.descfield = getFromSelector(descfieldid);
|
||
|
|
||
|
this.initialize = function(editor) {
|
||
|
/* attach the event handlers and set the initial values */
|
||
|
this.editor = editor;
|
||
|
addEventHandler(this.titlefield, "change", this.updateProperties, this);
|
||
|
addEventHandler(this.descfield, "change", this.updateProperties, this);
|
||
|
|
||
|
// set the fields
|
||
|
var heads = this.editor.getInnerDocument().getElementsByTagName('head');
|
||
|
if (!heads[0]) {
|
||
|
this.editor.logMessage(_('No head in document!'), 1);
|
||
|
} else {
|
||
|
var head = heads[0];
|
||
|
var titles = head.getElementsByTagName('title');
|
||
|
if (titles.length) {
|
||
|
this.titlefield.value = titles[0].text;
|
||
|
}
|
||
|
var metas = head.getElementsByTagName('meta');
|
||
|
if (metas.length) {
|
||
|
for (var i=0; i < metas.length; i++) {
|
||
|
var meta = metas[i];
|
||
|
if (meta.getAttribute('name') &&
|
||
|
meta.getAttribute('name').toLowerCase() ==
|
||
|
'description') {
|
||
|
this.descfield.value = meta.getAttribute('content');
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
this.editor.logMessage(_('Property tool initialized'));
|
||
|
};
|
||
|
|
||
|
this.updateProperties = function() {
|
||
|
/* event handler for updating the properties form */
|
||
|
var doc = this.editor.getInnerDocument();
|
||
|
var heads = doc.getElementsByTagName('HEAD');
|
||
|
if (!heads) {
|
||
|
this.editor.logMessage(_('No head in document!'), 1);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
var head = heads[0];
|
||
|
|
||
|
// set the title
|
||
|
var titles = head.getElementsByTagName('title');
|
||
|
if (!titles) {
|
||
|
var title = doc.createElement('title');
|
||
|
var text = doc.createTextNode(this.titlefield.value);
|
||
|
title.appendChild(text);
|
||
|
head.appendChild(title);
|
||
|
} else {
|
||
|
var title = titles[0];
|
||
|
// IE6 title has no children, and refuses appendChild.
|
||
|
// Delete and recreate the title.
|
||
|
if (title.childNodes.length == 0) {
|
||
|
title.removeNode(true);
|
||
|
title = doc.createElement('title');
|
||
|
title.innerText = this.titlefield.value;
|
||
|
head.appendChild(title);
|
||
|
} else {
|
||
|
title.childNodes[0].nodeValue = this.titlefield.value;
|
||
|
}
|
||
|
}
|
||
|
document.title = this.titlefield.value;
|
||
|
|
||
|
// let's just fulfill the usecase, not think about more properties
|
||
|
// set the description
|
||
|
var metas = doc.getElementsByTagName('meta');
|
||
|
var descset = 0;
|
||
|
for (var i=0; i < metas.length; i++) {
|
||
|
var meta = metas[i];
|
||
|
if (meta.getAttribute('name') &&
|
||
|
meta.getAttribute('name').toLowerCase() == 'description') {
|
||
|
meta.setAttribute('content', this.descfield.value);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (!descset) {
|
||
|
var meta = doc.createElement('meta');
|
||
|
meta.setAttribute('name', 'description');
|
||
|
meta.setAttribute('content', this.descfield.value);
|
||
|
head.appendChild(meta);
|
||
|
}
|
||
|
|
||
|
this.editor.logMessage(_('Properties modified'));
|
||
|
};
|
||
|
}
|
||
|
|
||
|
PropertyTool.prototype = new KupuTool;
|
||
|
|
||
|
function LinkTool() {
|
||
|
/* Add and update hyperlinks */
|
||
|
|
||
|
this.initialize = function(editor) {
|
||
|
this.editor = editor;
|
||
|
this.editor.logMessage(_('Link tool initialized'));
|
||
|
};
|
||
|
|
||
|
this.createLinkHandler = function(event) {
|
||
|
/* create a link according to a url entered in a popup */
|
||
|
var linkWindow = openPopup('kupupopups/link.html', 300, 200);
|
||
|
linkWindow.linktool = this;
|
||
|
linkWindow.focus();
|
||
|
};
|
||
|
|
||
|
this.updateLink = function (linkel, url, type, name, target, title) {
|
||
|
if (type && type == 'anchor') {
|
||
|
linkel.removeAttribute('href');
|
||
|
linkel.setAttribute('name', name);
|
||
|
} else {
|
||
|
linkel.href = url;
|
||
|
if (linkel.innerHTML == "") {
|
||
|
var doc = this.editor.getInnerDocument();
|
||
|
linkel.appendChild(doc.createTextNode(title || url));
|
||
|
}
|
||
|
if (title) {
|
||
|
linkel.title = title;
|
||
|
} else {
|
||
|
linkel.removeAttribute('title');
|
||
|
}
|
||
|
if (target && target != '') {
|
||
|
linkel.setAttribute('target', target);
|
||
|
}
|
||
|
else {
|
||
|
linkel.removeAttribute('target');
|
||
|
};
|
||
|
linkel.style.color = this.linkcolor;
|
||
|
};
|
||
|
};
|
||
|
|
||
|
this.formatSelectedLink = function(url, type, name, target, title) {
|
||
|
var currnode = this.editor.getSelectedNode();
|
||
|
|
||
|
// selection inside link
|
||
|
var linkel = this.editor.getNearestParentOfType(currnode, 'A');
|
||
|
if (linkel) {
|
||
|
this.updateLink(linkel, url, type, name, target, title);
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
if (currnode.nodeType!=1) return false;
|
||
|
|
||
|
// selection contains links
|
||
|
var linkelements = currnode.getElementsByTagName('A');
|
||
|
var selection = this.editor.getSelection();
|
||
|
var containsLink = false;
|
||
|
for (var i = 0; i < linkelements.length; i++) {
|
||
|
linkel = linkelements[i];
|
||
|
if (selection.containsNode(linkel)) {
|
||
|
this.updateLink(linkel, url, type, name, target, title);
|
||
|
containsLink = true;
|
||
|
}
|
||
|
};
|
||
|
return containsLink;
|
||
|
}
|
||
|
|
||
|
// Can create a link in the following circumstances:
|
||
|
// The selection is inside a link:
|
||
|
// just update the link attributes.
|
||
|
// The selection contains links:
|
||
|
// update the attributes of the contained links
|
||
|
// No links inside or outside the selection:
|
||
|
// create a link around the selection
|
||
|
// No selection:
|
||
|
// insert a link containing the title
|
||
|
//
|
||
|
// the order of the arguments is a bit odd here because of backward
|
||
|
// compatibility
|
||
|
this.createLink = function(url, type, name, target, title) {
|
||
|
if (!this.formatSelectedLink(url, type, name, target, title)) {
|
||
|
// No links inside or outside.
|
||
|
this.editor.execCommand("CreateLink", url);
|
||
|
if (!this.formatSelectedLink(url, type, name, target, title)) {
|
||
|
// Insert link with no text selected, insert the title
|
||
|
// or URI instead.
|
||
|
var doc = this.editor.getInnerDocument();
|
||
|
linkel = doc.createElement("a");
|
||
|
linkel.setAttribute('href', url);
|
||
|
linkel.setAttribute('class', 'generated');
|
||
|
this.editor.getSelection().replaceWithNode(linkel, true);
|
||
|
this.updateLink(linkel, url, type, name, target, title);
|
||
|
};
|
||
|
}
|
||
|
this.editor.logMessage(_('Link added'));
|
||
|
this.editor.updateState();
|
||
|
};
|
||
|
|
||
|
this.deleteLink = function() {
|
||
|
/* delete the current link */
|
||
|
var currnode = this.editor.getSelectedNode();
|
||
|
var linkel = this.editor.getNearestParentOfType(currnode, 'a');
|
||
|
if (!linkel) {
|
||
|
this.editor.logMessage(_('Not inside link'));
|
||
|
return;
|
||
|
};
|
||
|
while (linkel.childNodes.length) {
|
||
|
linkel.parentNode.insertBefore(linkel.childNodes[0], linkel);
|
||
|
};
|
||
|
linkel.parentNode.removeChild(linkel);
|
||
|
|
||
|
this.editor.logMessage(_('Link removed'));
|
||
|
this.editor.updateState();
|
||
|
};
|
||
|
|
||
|
this.createContextMenuElements = function(selNode, event) {
|
||
|
/* create the 'Create link' or 'Remove link' menu elements */
|
||
|
var ret = new Array();
|
||
|
var link = this.editor.getNearestParentOfType(selNode, 'a');
|
||
|
if (link) {
|
||
|
ret.push(new ContextMenuElement(_('Delete link'), this.deleteLink, this));
|
||
|
} else {
|
||
|
ret.push(new ContextMenuElement(_('Create link'), this.createLinkHandler, this));
|
||
|
};
|
||
|
return ret;
|
||
|
};
|
||
|
}
|
||
|
|
||
|
LinkTool.prototype = new KupuTool;
|
||
|
|
||
|
function LinkToolBox(inputid, buttonid, toolboxid, plainclass, activeclass) {
|
||
|
/* create and edit links */
|
||
|
|
||
|
this.input = getFromSelector(inputid);
|
||
|
this.button = getFromSelector(buttonid);
|
||
|
this.toolboxel = getFromSelector(toolboxid);
|
||
|
this.plainclass = plainclass;
|
||
|
this.activeclass = activeclass;
|
||
|
|
||
|
this.initialize = function(tool, editor) {
|
||
|
/* attach the event handlers */
|
||
|
this.tool = tool;
|
||
|
this.editor = editor;
|
||
|
addEventHandler(this.input, "blur", this.updateLink, this);
|
||
|
addEventHandler(this.button, "click", this.addLink, this);
|
||
|
};
|
||
|
|
||
|
this.updateState = function(selNode) {
|
||
|
/* if we're inside a link, update the input, else empty it */
|
||
|
var linkel = this.editor.getNearestParentOfType(selNode, 'a');
|
||
|
if (linkel) {
|
||
|
// check first before setting a class for backward compatibility
|
||
|
if (this.toolboxel) {
|
||
|
this.toolboxel.className = this.activeclass;
|
||
|
};
|
||
|
this.input.value = linkel.getAttribute('href');
|
||
|
} else {
|
||
|
// check first before setting a class for backward compatibility
|
||
|
if (this.toolboxel) {
|
||
|
this.toolboxel.className = this.plainclass;
|
||
|
};
|
||
|
this.input.value = '';
|
||
|
}
|
||
|
};
|
||
|
|
||
|
this.addLink = function(event) {
|
||
|
/* add a link */
|
||
|
var url = this.input.value;
|
||
|
this.tool.createLink(url);
|
||
|
};
|
||
|
|
||
|
this.updateLink = function() {
|
||
|
/* update the current link */
|
||
|
var currnode = this.editor.getSelectedNode();
|
||
|
var linkel = this.editor.getNearestParentOfType(currnode, 'A');
|
||
|
if (!linkel) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
var url = this.input.value;
|
||
|
linkel.setAttribute('href', url);
|
||
|
|
||
|
this.editor.logMessage(_('Link modified'));
|
||
|
};
|
||
|
};
|
||
|
|
||
|
LinkToolBox.prototype = new LinkToolBox;
|
||
|
|
||
|
function ImageTool() {
|
||
|
/* Image tool to add images */
|
||
|
|
||
|
this.initialize = function(editor) {
|
||
|
/* attach the event handlers */
|
||
|
this.editor = editor;
|
||
|
this.editor.logMessage(_('Image tool initialized'));
|
||
|
};
|
||
|
|
||
|
this.createImageHandler = function(event) {
|
||
|
/* create an image according to a url entered in a popup */
|
||
|
var imageWindow = openPopup('kupupopups/image.html', 300, 200);
|
||
|
imageWindow.imagetool = this;
|
||
|
imageWindow.focus();
|
||
|
};
|
||
|
|
||
|
this.createImage = function(url, alttext, imgclass) {
|
||
|
/* create an image */
|
||
|
var img = this.editor.getInnerDocument().createElement('img');
|
||
|
img.src = url;
|
||
|
img.removeAttribute('height');
|
||
|
img.removeAttribute('width');
|
||
|
if (alttext) {
|
||
|
img.alt = alttext;
|
||
|
};
|
||
|
if (imgclass) {
|
||
|
img.className = imgclass;
|
||
|
};
|
||
|
img = this.editor.insertNodeAtSelection(img, 1);
|
||
|
this.editor.logMessage(_('Image inserted'));
|
||
|
this.editor.updateState();
|
||
|
return img;
|
||
|
};
|
||
|
|
||
|
this.setImageClass = function(imgclass) {
|
||
|
/* set the class of the selected image */
|
||
|
var currnode = this.editor.getSelectedNode();
|
||
|
var currimg = this.editor.getNearestParentOfType(currnode, 'IMG');
|
||
|
if (currimg) {
|
||
|
currimg.className = imgclass;
|
||
|
};
|
||
|
};
|
||
|
|
||
|
this.createContextMenuElements = function(selNode, event) {
|
||
|
return new Array(new ContextMenuElement(_('Create image'), this.createImageHandler, this));
|
||
|
};
|
||
|
}
|
||
|
|
||
|
ImageTool.prototype = new KupuTool;
|
||
|
|
||
|
function ImageToolBox(inputfieldid, insertbuttonid, classselectid, toolboxid, plainclass, activeclass) {
|
||
|
/* toolbox for adding images */
|
||
|
|
||
|
this.inputfield = getFromSelector(inputfieldid);
|
||
|
this.insertbutton = getFromSelector(insertbuttonid);
|
||
|
this.classselect = getFromSelector(classselectid);
|
||
|
this.toolboxel = getFromSelector(toolboxid);
|
||
|
this.plainclass = plainclass;
|
||
|
this.activeclass = activeclass;
|
||
|
|
||
|
this.initialize = function(tool, editor) {
|
||
|
this.tool = tool;
|
||
|
this.editor = editor;
|
||
|
addEventHandler(this.classselect, "change", this.setImageClass, this);
|
||
|
addEventHandler(this.insertbutton, "click", this.addImage, this);
|
||
|
};
|
||
|
|
||
|
this.updateState = function(selNode, event) {
|
||
|
/* update the state of the toolbox element */
|
||
|
var imageel = this.editor.getNearestParentOfType(selNode, 'img');
|
||
|
if (imageel) {
|
||
|
// check first before setting a class for backward compatibility
|
||
|
if (this.toolboxel) {
|
||
|
this.toolboxel.className = this.activeclass;
|
||
|
this.inputfield.value = imageel.getAttribute('src');
|
||
|
var imgclass = imageel.className ? imageel.className : 'image-inline';
|
||
|
selectSelectItem(this.classselect, imgclass);
|
||
|
};
|
||
|
} else {
|
||
|
if (this.toolboxel) {
|
||
|
this.toolboxel.className = this.plainclass;
|
||
|
};
|
||
|
};
|
||
|
};
|
||
|
|
||
|
this.addImage = function() {
|
||
|
/* add an image */
|
||
|
var url = this.inputfield.value;
|
||
|
var sel_class = this.classselect.options[this.classselect.selectedIndex].value;
|
||
|
this.tool.createImage(url, null, sel_class);
|
||
|
this.editor.focusDocument();
|
||
|
};
|
||
|
|
||
|
this.setImageClass = function() {
|
||
|
/* set the class for the current image */
|
||
|
var sel_class = this.classselect.options[this.classselect.selectedIndex].value;
|
||
|
this.tool.setImageClass(sel_class);
|
||
|
this.editor.focusDocument();
|
||
|
};
|
||
|
};
|
||
|
|
||
|
ImageToolBox.prototype = new KupuToolBox;
|
||
|
|
||
|
function TableTool() {
|
||
|
/* The table tool */
|
||
|
|
||
|
// XXX There are some awfully long methods in here!!
|
||
|
this.createContextMenuElements = function(selNode, event) {
|
||
|
var table = this.editor.getNearestParentOfType(selNode, 'table');
|
||
|
if (!table) {
|
||
|
ret = new Array();
|
||
|
var el = new ContextMenuElement(_('Add table'), this.addPlainTable, this);
|
||
|
ret.push(el);
|
||
|
return ret;
|
||
|
} else {
|
||
|
var ret = new Array();
|
||
|
ret.push(new ContextMenuElement(_('Add row'), this.addTableRow, this));
|
||
|
ret.push(new ContextMenuElement(_('Delete row'), this.delTableRow, this));
|
||
|
ret.push(new ContextMenuElement(_('Add column'), this.addTableColumn, this));
|
||
|
ret.push(new ContextMenuElement(_('Delete column'), this.delTableColumn, this));
|
||
|
ret.push(new ContextMenuElement(_('Delete Table'), this.delTable, this));
|
||
|
return ret;
|
||
|
};
|
||
|
};
|
||
|
|
||
|
this.addPlainTable = function() {
|
||
|
/* event handler for the context menu */
|
||
|
this.createTable(2, 3, 1, 'plain');
|
||
|
};
|
||
|
|
||
|
this.createTable = function(rows, cols, makeHeader, tableclass) {
|
||
|
/* add a table */
|
||
|
if (rows < 1 || rows > 99 || cols < 1 || cols > 99) {
|
||
|
this.editor.logMessage(_('Invalid table size'), 1);
|
||
|
return;
|
||
|
};
|
||
|
|
||
|
var doc = this.editor.getInnerDocument();
|
||
|
|
||
|
table = doc.createElement("table");
|
||
|
table.className = tableclass;
|
||
|
|
||
|
// If the user wants a row of headings, make them
|
||
|
if (makeHeader) {
|
||
|
var tr = doc.createElement("tr");
|
||
|
var thead = doc.createElement("thead");
|
||
|
for (i=0; i < cols; i++) {
|
||
|
var th = doc.createElement("th");
|
||
|
th.appendChild(doc.createTextNode("Col " + i+1));
|
||
|
tr.appendChild(th);
|
||
|
}
|
||
|
thead.appendChild(tr);
|
||
|
table.appendChild(thead);
|
||
|
}
|
||
|
|
||
|
tbody = doc.createElement("tbody");
|
||
|
for (var i=0; i < rows; i++) {
|
||
|
var tr = doc.createElement("tr");
|
||
|
for (var j=0; j < cols; j++) {
|
||
|
var td = doc.createElement("td");
|
||
|
var content = doc.createTextNode('\u00a0');
|
||
|
td.appendChild(content);
|
||
|
tr.appendChild(td);
|
||
|
}
|
||
|
tbody.appendChild(tr);
|
||
|
}
|
||
|
table.appendChild(tbody);
|
||
|
this.editor.insertNodeAtSelection(table);
|
||
|
|
||
|
this._setTableCellHandlers(table);
|
||
|
|
||
|
this.editor.logMessage(_('Table added'));
|
||
|
this.editor.updateState();
|
||
|
return table;
|
||
|
};
|
||
|
|
||
|
this._setTableCellHandlers = function(table) {
|
||
|
// make each cell select its full contents if it's clicked
|
||
|
addEventHandler(table, 'click', this._selectContentIfEmpty, this);
|
||
|
|
||
|
var cells = table.getElementsByTagName('td');
|
||
|
for (var i=0; i < cells.length; i++) {
|
||
|
addEventHandler(cells[i], 'click', this._selectContentIfEmpty, this);
|
||
|
};
|
||
|
|
||
|
// select the nbsp in the first cell
|
||
|
var firstcell = cells[0];
|
||
|
if (firstcell) {
|
||
|
var children = firstcell.childNodes;
|
||
|
if (children.length == 1 && children[0].nodeType == 3 &&
|
||
|
children[0].nodeValue == '\xa0') {
|
||
|
var selection = this.editor.getSelection();
|
||
|
selection.selectNodeContents(firstcell);
|
||
|
};
|
||
|
};
|
||
|
};
|
||
|
|
||
|
this._selectContentIfEmpty = function() {
|
||
|
var selNode = this.editor.getSelectedNode();
|
||
|
var cell = this.editor.getNearestParentOfType(selNode, 'td');
|
||
|
if (!cell) {
|
||
|
return;
|
||
|
};
|
||
|
var children = cell.childNodes;
|
||
|
if (children.length == 1 && children[0].nodeType == 3 &&
|
||
|
children[0].nodeValue == '\xa0') {
|
||
|
var selection = this.editor.getSelection();
|
||
|
selection.selectNodeContents(cell);
|
||
|
};
|
||
|
};
|
||
|
|
||
|
this.addTableRow = function() {
|
||
|
/* Find the current row and add a row after it */
|
||
|
var currnode = this.editor.getSelectedNode();
|
||
|
var currtbody = this.editor.getNearestParentOfType(currnode, "TBODY");
|
||
|
var bodytype = "tbody";
|
||
|
if (!currtbody) {
|
||
|
currtbody = this.editor.getNearestParentOfType(currnode, "THEAD");
|
||
|
bodytype = "thead";
|
||
|
}
|
||
|
var parentrow = this.editor.getNearestParentOfType(currnode, "TR");
|
||
|
var nextrow = parentrow.nextSibling;
|
||
|
|
||
|
// get the number of cells we should place
|
||
|
var colcount = 0;
|
||
|
for (var i=0; i < currtbody.childNodes.length; i++) {
|
||
|
var el = currtbody.childNodes[i];
|
||
|
if (el.nodeType != 1) {
|
||
|
continue;
|
||
|
}
|
||
|
if (el.nodeName.toLowerCase() == 'tr') {
|
||
|
var cols = 0;
|
||
|
for (var j=0; j < el.childNodes.length; j++) {
|
||
|
if (el.childNodes[j].nodeType == 1) {
|
||
|
cols++;
|
||
|
}
|
||
|
}
|
||
|
if (cols > colcount) {
|
||
|
colcount = cols;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
var newrow = this.editor.getInnerDocument().createElement("TR");
|
||
|
|
||
|
for (var i = 0; i < colcount; i++) {
|
||
|
var newcell;
|
||
|
if (bodytype == 'tbody') {
|
||
|
newcell = this.editor.getInnerDocument().createElement("TD");
|
||
|
} else {
|
||
|
newcell = this.editor.getInnerDocument().createElement("TH");
|
||
|
}
|
||
|
var newcellvalue = this.editor.getInnerDocument().createTextNode("\u00a0");
|
||
|
newcell.appendChild(newcellvalue);
|
||
|
newrow.appendChild(newcell);
|
||
|
}
|
||
|
|
||
|
if (!nextrow) {
|
||
|
currtbody.appendChild(newrow);
|
||
|
} else {
|
||
|
currtbody.insertBefore(newrow, nextrow);
|
||
|
}
|
||
|
|
||
|
this.editor.focusDocument();
|
||
|
this.editor.logMessage(_('Table row added'));
|
||
|
};
|
||
|
|
||
|
this.delTableRow = function() {
|
||
|
/* Find the current row and delete it */
|
||
|
var currnode = this.editor.getSelectedNode();
|
||
|
var parentrow = this.editor.getNearestParentOfType(currnode, "TR");
|
||
|
if (!parentrow) {
|
||
|
this.editor.logMessage(_('No row to delete'), 1);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// move selection aside
|
||
|
// XXX: doesn't work if parentrow is the only row of thead/tbody/tfoot
|
||
|
// XXX: doesn't preserve the colindex
|
||
|
var selection = this.editor.getSelection();
|
||
|
if (parentrow.nextSibling) {
|
||
|
selection.selectNodeContents(parentrow.nextSibling.firstChild);
|
||
|
} else if (parentrow.previousSibling) {
|
||
|
selection.selectNodeContents(parentrow.previousSibling.firstChild);
|
||
|
};
|
||
|
|
||
|
// remove the row
|
||
|
parentrow.parentNode.removeChild(parentrow);
|
||
|
|
||
|
this.editor.focusDocument();
|
||
|
this.editor.logMessage(_('Table row removed'));
|
||
|
};
|
||
|
|
||
|
this.addTableColumn = function() {
|
||
|
/* Add a new column after the current column */
|
||
|
var currnode = this.editor.getSelectedNode();
|
||
|
var currtd = this.editor.getNearestParentOfType(currnode, 'TD');
|
||
|
if (!currtd) {
|
||
|
currtd = this.editor.getNearestParentOfType(currnode, 'TH');
|
||
|
}
|
||
|
if (!currtd) {
|
||
|
this.editor.logMessage(_('No parentcolumn found!'), 1);
|
||
|
return;
|
||
|
}
|
||
|
var currtr = this.editor.getNearestParentOfType(currnode, 'TR');
|
||
|
var currtable = this.editor.getNearestParentOfType(currnode, 'TABLE');
|
||
|
|
||
|
// get the current index
|
||
|
var tdindex = this._getColIndex(currtd);
|
||
|
// XXX this looks like a debug message, remove
|
||
|
this.editor.logMessage(_('tdindex: ${tdindex}'));
|
||
|
|
||
|
// now add a column to all rows
|
||
|
// first the thead
|
||
|
var theads = currtable.getElementsByTagName('THEAD');
|
||
|
if (theads) {
|
||
|
for (var i=0; i < theads.length; i++) {
|
||
|
// let's assume table heads only have ths
|
||
|
var currthead = theads[i];
|
||
|
for (var j=0; j < currthead.childNodes.length; j++) {
|
||
|
var tr = currthead.childNodes[j];
|
||
|
if (tr.nodeType != 1) {
|
||
|
continue;
|
||
|
}
|
||
|
var currindex = 0;
|
||
|
for (var k=0; k < tr.childNodes.length; k++) {
|
||
|
var th = tr.childNodes[k];
|
||
|
if (th.nodeType != 1) {
|
||
|
continue;
|
||
|
}
|
||
|
if (currindex == tdindex) {
|
||
|
var doc = this.editor.getInnerDocument();
|
||
|
var newth = doc.createElement('th');
|
||
|
var text = doc.createTextNode('\u00a0');
|
||
|
newth.appendChild(text);
|
||
|
if (tr.childNodes.length == k+1) {
|
||
|
// the column will be on the end of the row
|
||
|
tr.appendChild(newth);
|
||
|
} else {
|
||
|
tr.insertBefore(newth, tr.childNodes[k + 1]);
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
currindex++;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// then the tbody
|
||
|
var tbodies = currtable.getElementsByTagName('TBODY');
|
||
|
if (tbodies) {
|
||
|
for (var i=0; i < tbodies.length; i++) {
|
||
|
// let's assume table heads only have ths
|
||
|
var currtbody = tbodies[i];
|
||
|
for (var j=0; j < currtbody.childNodes.length; j++) {
|
||
|
var tr = currtbody.childNodes[j];
|
||
|
if (tr.nodeType != 1) {
|
||
|
continue;
|
||
|
}
|
||
|
var currindex = 0;
|
||
|
for (var k=0; k < tr.childNodes.length; k++) {
|
||
|
var td = tr.childNodes[k];
|
||
|
if (td.nodeType != 1) {
|
||
|
continue;
|
||
|
}
|
||
|
if (currindex == tdindex) {
|
||
|
var doc = this.editor.getInnerDocument();
|
||
|
var newtd = doc.createElement('td');
|
||
|
var text = doc.createTextNode('\u00a0');
|
||
|
newtd.appendChild(text);
|
||
|
if (tr.childNodes.length == k+1) {
|
||
|
// the column will be on the end of the row
|
||
|
tr.appendChild(newtd);
|
||
|
} else {
|
||
|
tr.insertBefore(newtd, tr.childNodes[k + 1]);
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
currindex++;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
this.editor.focusDocument();
|
||
|
this.editor.logMessage(_('Table column added'));
|
||
|
};
|
||
|
|
||
|
this.delTableColumn = function() {
|
||
|
/* remove a column */
|
||
|
var currnode = this.editor.getSelectedNode();
|
||
|
var currtd = this.editor.getNearestParentOfType(currnode, 'TD');
|
||
|
if (!currtd) {
|
||
|
currtd = this.editor.getNearestParentOfType(currnode, 'TH');
|
||
|
}
|
||
|
var currcolindex = this._getColIndex(currtd);
|
||
|
var currtable = this.editor.getNearestParentOfType(currnode, 'TABLE');
|
||
|
|
||
|
// move selection aside
|
||
|
var selection = this.editor.getSelection();
|
||
|
if (currtd.nextSibling) {
|
||
|
selection.selectNodeContents(currtd.nextSibling);
|
||
|
} else if (currtd.previousSibling) {
|
||
|
selection.selectNodeContents(currtd.previousSibling);
|
||
|
};
|
||
|
|
||
|
// remove the theaders
|
||
|
var heads = currtable.getElementsByTagName('THEAD');
|
||
|
if (heads.length) {
|
||
|
for (var i=0; i < heads.length; i++) {
|
||
|
var thead = heads[i];
|
||
|
for (var j=0; j < thead.childNodes.length; j++) {
|
||
|
var tr = thead.childNodes[j];
|
||
|
if (tr.nodeType != 1) {
|
||
|
continue;
|
||
|
}
|
||
|
var currindex = 0;
|
||
|
for (var k=0; k < tr.childNodes.length; k++) {
|
||
|
var th = tr.childNodes[k];
|
||
|
if (th.nodeType != 1) {
|
||
|
continue;
|
||
|
}
|
||
|
if (currindex == currcolindex) {
|
||
|
tr.removeChild(th);
|
||
|
break;
|
||
|
}
|
||
|
currindex++;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// now we remove the column field, a bit harder since we need to take
|
||
|
// colspan and rowspan into account XXX Not right, fix theads as well
|
||
|
var bodies = currtable.getElementsByTagName('TBODY');
|
||
|
for (var i=0; i < bodies.length; i++) {
|
||
|
var currtbody = bodies[i];
|
||
|
var relevant_rowspan = 0;
|
||
|
for (var j=0; j < currtbody.childNodes.length; j++) {
|
||
|
var tr = currtbody.childNodes[j];
|
||
|
if (tr.nodeType != 1) {
|
||
|
continue;
|
||
|
}
|
||
|
var currindex = 0
|
||
|
for (var k=0; k < tr.childNodes.length; k++) {
|
||
|
var cell = tr.childNodes[k];
|
||
|
if (cell.nodeType != 1) {
|
||
|
continue;
|
||
|
}
|
||
|
var colspan = cell.colSpan;
|
||
|
if (currindex == currcolindex) {
|
||
|
tr.removeChild(cell);
|
||
|
break;
|
||
|
}
|
||
|
currindex++;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
this.editor.focusDocument();
|
||
|
this.editor.logMessage(_('Table column deleted'));
|
||
|
};
|
||
|
|
||
|
this.delTable = function() {
|
||
|
/* delete the current table */
|
||
|
var currnode = this.editor.getSelectedNode();
|
||
|
var table = this.editor.getNearestParentOfType(currnode, 'table');
|
||
|
if (!table) {
|
||
|
this.editor.logMessage(_('Not inside a table!'));
|
||
|
return;
|
||
|
};
|
||
|
table.parentNode.removeChild(table);
|
||
|
this.editor.logMessage(_('Table removed'));
|
||
|
};
|
||
|
|
||
|
this.setColumnAlign = function(newalign) {
|
||
|
/* change the alignment of a full column */
|
||
|
var currnode = this.editor.getSelectedNode();
|
||
|
var currtd = this.editor.getNearestParentOfType(currnode, "TD");
|
||
|
var bodytype = 'tbody';
|
||
|
if (!currtd) {
|
||
|
currtd = this.editor.getNearestParentOfType(currnode, "TH");
|
||
|
bodytype = 'thead';
|
||
|
}
|
||
|
var currcolindex = this._getColIndex(currtd);
|
||
|
var currtable = this.editor.getNearestParentOfType(currnode, "TABLE");
|
||
|
|
||
|
// unfortunately this is not enough to make the browsers display
|
||
|
// the align, we need to set it on individual cells as well and
|
||
|
// mind the rowspan...
|
||
|
for (var i=0; i < currtable.childNodes.length; i++) {
|
||
|
var currtbody = currtable.childNodes[i];
|
||
|
if (currtbody.nodeType != 1 ||
|
||
|
(currtbody.nodeName.toUpperCase() != "THEAD" &&
|
||
|
currtbody.nodeName.toUpperCase() != "TBODY")) {
|
||
|
continue;
|
||
|
}
|
||
|
for (var j=0; j < currtbody.childNodes.length; j++) {
|
||
|
var row = currtbody.childNodes[j];
|
||
|
if (row.nodeType != 1) {
|
||
|
continue;
|
||
|
}
|
||
|
var index = 0;
|
||
|
for (var k=0; k < row.childNodes.length; k++) {
|
||
|
var cell = row.childNodes[k];
|
||
|
if (cell.nodeType != 1) {
|
||
|
continue;
|
||
|
}
|
||
|
if (index == currcolindex) {
|
||
|
if (this.editor.config.use_css) {
|
||
|
cell.style.textAlign = newalign;
|
||
|
} else {
|
||
|
cell.setAttribute('align', newalign);
|
||
|
}
|
||
|
cell.className = 'align-' + newalign;
|
||
|
}
|
||
|
index++;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
|
||
|
this.setTableClass = function(sel_class) {
|
||
|
/* set the class for the table */
|
||
|
var currnode = this.editor.getSelectedNode();
|
||
|
var currtable = this.editor.getNearestParentOfType(currnode, 'TABLE');
|
||
|
|
||
|
if (currtable) {
|
||
|
currtable.className = sel_class;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
this._getColIndex = function(currcell) {
|
||
|
/* Given a node, return an integer for which column it is */
|
||
|
var prevsib = currcell.previousSibling;
|
||
|
var currcolindex = 0;
|
||
|
while (prevsib) {
|
||
|
if (prevsib.nodeType == 1 &&
|
||
|
(prevsib.tagName.toUpperCase() == "TD" ||
|
||
|
prevsib.tagName.toUpperCase() == "TH")) {
|
||
|
var colspan = prevsib.colSpan;
|
||
|
if (colspan) {
|
||
|
currcolindex += parseInt(colspan);
|
||
|
} else {
|
||
|
currcolindex++;
|
||
|
}
|
||
|
}
|
||
|
prevsib = prevsib.previousSibling;
|
||
|
if (currcolindex > 30) {
|
||
|
alert(_("Recursion detected when counting column position"));
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return currcolindex;
|
||
|
};
|
||
|
|
||
|
this._getColumnAlign = function(selNode) {
|
||
|
/* return the alignment setting of the current column */
|
||
|
var align;
|
||
|
var td = this.editor.getNearestParentOfType(selNode, 'td');
|
||
|
if (!td) {
|
||
|
td = this.editor.getNearestParentOfType(selNode, 'th');
|
||
|
};
|
||
|
if (td) {
|
||
|
align = td.getAttribute('align');
|
||
|
if (this.editor.config.use_css) {
|
||
|
align = td.style.textAlign;
|
||
|
};
|
||
|
};
|
||
|
return align;
|
||
|
};
|
||
|
|
||
|
this.fixTable = function(event) {
|
||
|
/* fix the table so it can be processed by Kupu */
|
||
|
// since this can be quite a nasty creature we can't just use the
|
||
|
// helper methods
|
||
|
|
||
|
// first we create a new tbody element
|
||
|
var currnode = this.editor.getSelectedNode();
|
||
|
var table = this.editor.getNearestParentOfType(currnode, 'TABLE');
|
||
|
if (!table) {
|
||
|
this.editor.logMessage(_('Not inside a table!'));
|
||
|
return;
|
||
|
};
|
||
|
this._fixTableHelper(table);
|
||
|
};
|
||
|
|
||
|
this._fixTableHelper = function(table) {
|
||
|
/* the code to actually fix tables */
|
||
|
var doc = this.editor.getInnerDocument();
|
||
|
var tbody = doc.createElement('tbody');
|
||
|
|
||
|
if (this.editor.config.table_classes) {
|
||
|
var allowed_classes = this.editor.config.table_classes['class'];
|
||
|
if (!allowed_classes.contains(table.className)) {
|
||
|
table.className = allowed_classes[0];
|
||
|
};
|
||
|
} else {
|
||
|
table.removeAttribute('class');
|
||
|
table.removeAttribute('className');
|
||
|
};
|
||
|
table.removeAttribute('border');
|
||
|
table.removeAttribute('cellpadding');
|
||
|
table.removeAttribute('cellPadding');
|
||
|
table.removeAttribute('cellspacing');
|
||
|
table.removeAttribute('cellSpacing');
|
||
|
|
||
|
// now get all the rows of the table, the rows can either be
|
||
|
// direct descendants of the table or inside a 'tbody', 'thead'
|
||
|
// or 'tfoot' element
|
||
|
var rows = new Array();
|
||
|
var parents = new Array('thead', 'tbody', 'tfoot');
|
||
|
for (var i=0; i < table.childNodes.length; i++) {
|
||
|
var node = table.childNodes[i];
|
||
|
if (node.nodeName.toLowerCase() == 'tr') {
|
||
|
rows.push(node);
|
||
|
} else if (parents.contains(node.nodeName.toLowerCase())) {
|
||
|
for (var j=0; j < node.childNodes.length; j++) {
|
||
|
var inode = node.childNodes[j];
|
||
|
if (inode.nodeName.toLowerCase() == 'tr') {
|
||
|
rows.push(inode);
|
||
|
};
|
||
|
};
|
||
|
};
|
||
|
};
|
||
|
|
||
|
// now find out how many cells our rows should have
|
||
|
var numcols = 0;
|
||
|
for (var i=0; i < rows.length; i++) {
|
||
|
var row = rows[i];
|
||
|
var currnumcols = 0;
|
||
|
for (var j=0; j < row.childNodes.length; j++) {
|
||
|
var node = row.childNodes[j];
|
||
|
if (node.nodeName.toLowerCase() == 'td' ||
|
||
|
node.nodeName.toLowerCase() == 'th') {
|
||
|
var colspan = 1;
|
||
|
if (node.getAttribute('colSpan')) {
|
||
|
colspan = parseInt(node.getAttribute('colSpan'));
|
||
|
};
|
||
|
currnumcols += colspan;
|
||
|
};
|
||
|
};
|
||
|
if (currnumcols > numcols) {
|
||
|
numcols = currnumcols;
|
||
|
};
|
||
|
};
|
||
|
|
||
|
// now walk through all rows to clean them up
|
||
|
for (var i=0; i < rows.length; i++) {
|
||
|
var row = rows[i];
|
||
|
var newrow = doc.createElement('tr');
|
||
|
var currcolnum = 0;
|
||
|
while (row.childNodes.length > 0) {
|
||
|
var node = row.childNodes[0];
|
||
|
if (node.nodeName.toLowerCase() != 'td' && node.nodeName.toLowerCase() != 'th') {
|
||
|
row.removeChild(node);
|
||
|
continue;
|
||
|
};
|
||
|
node.removeAttribute('colSpan');
|
||
|
node.removeAttribute('rowSpan');
|
||
|
newrow.appendChild(node);
|
||
|
};
|
||
|
if (newrow.childNodes.length) {
|
||
|
tbody.appendChild(newrow);
|
||
|
};
|
||
|
};
|
||
|
|
||
|
// now make sure all rows have the correct length
|
||
|
for (var i=0; i < tbody.childNodes.length; i++) {
|
||
|
var row = tbody.childNodes[i];
|
||
|
var cellname = row.childNodes[0].nodeName;
|
||
|
while (row.childNodes.length < numcols) {
|
||
|
var cell = doc.createElement(cellname);
|
||
|
var nbsp = doc.createTextNode('\u00a0');
|
||
|
cell.appendChild(nbsp);
|
||
|
row.appendChild(cell);
|
||
|
};
|
||
|
};
|
||
|
|
||
|
// now remove all the old stuff from the table and add the new tbody
|
||
|
var tlength = table.childNodes.length;
|
||
|
for (var i=0; i < tlength; i++) {
|
||
|
table.removeChild(table.childNodes[0]);
|
||
|
};
|
||
|
table.appendChild(tbody);
|
||
|
|
||
|
this.editor.focusDocument();
|
||
|
this.editor.logMessage(_('Table cleaned up'));
|
||
|
};
|
||
|
|
||
|
this.fixAllTables = function() {
|
||
|
/* fix all the tables in the document at once */
|
||
|
var tables = this.editor.getInnerDocument().getElementsByTagName('table');
|
||
|
for (var i=0; i < tables.length; i++) {
|
||
|
this._fixTableHelper(tables[i]);
|
||
|
};
|
||
|
};
|
||
|
};
|
||
|
|
||
|
TableTool.prototype = new KupuTool;
|
||
|
|
||
|
function TableToolBox(addtabledivid, edittabledivid, newrowsinputid,
|
||
|
newcolsinputid, makeheaderinputid, classselectid, alignselectid, addtablebuttonid,
|
||
|
addrowbuttonid, delrowbuttonid, addcolbuttonid, delcolbuttonid, fixbuttonid,
|
||
|
fixallbuttonid, toolboxid, plainclass, activeclass) {
|
||
|
/* The table tool */
|
||
|
|
||
|
// XXX There are some awfully long methods in here!!
|
||
|
|
||
|
|
||
|
// a lot of dependencies on html elements here, but most implementations
|
||
|
// will use them all I guess
|
||
|
this.addtablediv = getFromSelector(addtabledivid);
|
||
|
this.edittablediv = getFromSelector(edittabledivid);
|
||
|
this.newrowsinput = getFromSelector(newrowsinputid);
|
||
|
this.newcolsinput = getFromSelector(newcolsinputid);
|
||
|
this.makeheaderinput = getFromSelector(makeheaderinputid);
|
||
|
this.classselect = getFromSelector(classselectid);
|
||
|
this.alignselect = getFromSelector(alignselectid);
|
||
|
this.addtablebutton = getFromSelector(addtablebuttonid);
|
||
|
this.addrowbutton = getFromSelector(addrowbuttonid);
|
||
|
this.delrowbutton = getFromSelector(delrowbuttonid);
|
||
|
this.addcolbutton = getFromSelector(addcolbuttonid);
|
||
|
this.delcolbutton = getFromSelector(delcolbuttonid);
|
||
|
this.fixbutton = getFromSelector(fixbuttonid);
|
||
|
this.fixallbutton = getFromSelector(fixallbuttonid);
|
||
|
this.toolboxel = getFromSelector(toolboxid);
|
||
|
this.plainclass = plainclass;
|
||
|
this.activeclass = activeclass;
|
||
|
|
||
|
// register event handlers
|
||
|
this.initialize = function(tool, editor) {
|
||
|
/* attach the event handlers */
|
||
|
this.tool = tool;
|
||
|
this.editor = editor;
|
||
|
// build the select list of table classes if configured
|
||
|
if (this.editor.config.table_classes) {
|
||
|
var classes = this.editor.config.table_classes['class'];
|
||
|
while (this.classselect.hasChildNodes()) {
|
||
|
this.classselect.removeChild(this.classselect.firstChild);
|
||
|
};
|
||
|
for (var i=0; i < classes.length; i++) {
|
||
|
var classname = classes[i];
|
||
|
var option = document.createElement('option');
|
||
|
var content = document.createTextNode(classname);
|
||
|
option.appendChild(content);
|
||
|
option.setAttribute('value', classname);
|
||
|
this.classselect.appendChild(option);
|
||
|
};
|
||
|
};
|
||
|
addEventHandler(this.addtablebutton, "click", this.addTable, this);
|
||
|
addEventHandler(this.addrowbutton, "click", this.tool.addTableRow, this.tool);
|
||
|
addEventHandler(this.delrowbutton, "click", this.tool.delTableRow, this.tool);
|
||
|
addEventHandler(this.addcolbutton, "click", this.tool.addTableColumn, this.tool);
|
||
|
addEventHandler(this.delcolbutton, "click", this.tool.delTableColumn, this.tool);
|
||
|
addEventHandler(this.alignselect, "change", this.setColumnAlign, this);
|
||
|
addEventHandler(this.classselect, "change", this.setTableClass, this);
|
||
|
addEventHandler(this.fixbutton, "click", this.tool.fixTable, this.tool);
|
||
|
addEventHandler(this.fixallbutton, "click", this.tool.fixAllTables, this.tool);
|
||
|
this.addtablediv.style.display = "block";
|
||
|
this.edittablediv.style.display = "none";
|
||
|
this.editor.logMessage(_('Table tool initialized'));
|
||
|
};
|
||
|
|
||
|
this.updateState = function(selNode) {
|
||
|
/* update the state (add/edit) and update the pulldowns (if required) */
|
||
|
var table = this.editor.getNearestParentOfType(selNode, 'table');
|
||
|
if (table) {
|
||
|
this.addtablediv.style.display = "none";
|
||
|
this.edittablediv.style.display = "block";
|
||
|
|
||
|
var align = this.tool._getColumnAlign(selNode);
|
||
|
selectSelectItem(this.alignselect, align);
|
||
|
selectSelectItem(this.classselect, table.className);
|
||
|
if (this.toolboxel) {
|
||
|
this.toolboxel.className = this.activeclass;
|
||
|
};
|
||
|
} else {
|
||
|
this.edittablediv.style.display = "none";
|
||
|
this.addtablediv.style.display = "block";
|
||
|
this.alignselect.selectedIndex = 0;
|
||
|
this.classselect.selectedIndex = 0;
|
||
|
if (this.toolboxel) {
|
||
|
this.toolboxel.className = this.plainclass;
|
||
|
};
|
||
|
};
|
||
|
};
|
||
|
|
||
|
this.addTable = function() {
|
||
|
/* add a table */
|
||
|
var rows = this.newrowsinput.value;
|
||
|
var cols = this.newcolsinput.value;
|
||
|
var makeHeader = this.makeheaderinput.checked;
|
||
|
// XXX getFromSelector
|
||
|
var classchooser = getFromSelector("kupu-table-classchooser-add");
|
||
|
var tableclass = this.classselect.options[this.classselect.selectedIndex].value;
|
||
|
|
||
|
this.tool.createTable(rows, cols, makeHeader, tableclass);
|
||
|
};
|
||
|
|
||
|
this.setColumnAlign = function() {
|
||
|
/* set the alignment of the current column */
|
||
|
var newalign = this.alignselect.options[this.alignselect.selectedIndex].value;
|
||
|
this.tool.setColumnAlign(newalign);
|
||
|
};
|
||
|
|
||
|
this.setTableClass = function() {
|
||
|
/* set the class for the current table */
|
||
|
var sel_class = this.classselect.options[this.classselect.selectedIndex].value;
|
||
|
if (sel_class) {
|
||
|
this.tool.setTableClass(sel_class);
|
||
|
};
|
||
|
};
|
||
|
};
|
||
|
|
||
|
TableToolBox.prototype = new KupuToolBox;
|
||
|
|
||
|
function ListTool(addulbuttonid, addolbuttonid, ulstyleselectid, olstyleselectid) {
|
||
|
/* tool to set list styles */
|
||
|
|
||
|
this.addulbutton = getFromSelector(addulbuttonid);
|
||
|
this.addolbutton = getFromSelector(addolbuttonid);
|
||
|
this.ulselect = getFromSelector(ulstyleselectid);
|
||
|
this.olselect = getFromSelector(olstyleselectid);
|
||
|
|
||
|
this.style_to_type = {'decimal': '1',
|
||
|
'lower-alpha': 'a',
|
||
|
'upper-alpha': 'A',
|
||
|
'lower-roman': 'i',
|
||
|
'upper-roman': 'I',
|
||
|
'disc': 'disc',
|
||
|
'square': 'square',
|
||
|
'circle': 'circle',
|
||
|
'none': 'none'
|
||
|
};
|
||
|
this.type_to_style = {'1': 'decimal',
|
||
|
'a': 'lower-alpha',
|
||
|
'A': 'upper-alpha',
|
||
|
'i': 'lower-roman',
|
||
|
'I': 'upper-roman',
|
||
|
'disc': 'disc',
|
||
|
'square': 'square',
|
||
|
'circle': 'circle',
|
||
|
'none': 'none'
|
||
|
};
|
||
|
|
||
|
this.initialize = function(editor) {
|
||
|
/* attach event handlers */
|
||
|
this.editor = editor;
|
||
|
this._fixTabIndex(this.addulbutton);
|
||
|
this._fixTabIndex(this.addolbutton);
|
||
|
this._fixTabIndex(this.ulselect);
|
||
|
this._fixTabIndex(this.olselect);
|
||
|
|
||
|
addEventHandler(this.addulbutton, "click", this.addUnorderedList, this);
|
||
|
addEventHandler(this.addolbutton, "click", this.addOrderedList, this);
|
||
|
addEventHandler(this.ulselect, "change", this.setUnorderedListStyle, this);
|
||
|
addEventHandler(this.olselect, "change", this.setOrderedListStyle, this);
|
||
|
this.ulselect.style.display = "none";
|
||
|
this.olselect.style.display = "none";
|
||
|
|
||
|
this.editor.logMessage(_('List style tool initialized'));
|
||
|
};
|
||
|
|
||
|
this._handleStyles = function(currnode, onselect, offselect) {
|
||
|
if (this.editor.config.use_css) {
|
||
|
var currstyle = currnode.style.listStyleType;
|
||
|
} else {
|
||
|
var currstyle = this.type_to_style[currnode.getAttribute('type')];
|
||
|
}
|
||
|
selectSelectItem(onselect, currstyle);
|
||
|
offselect.style.display = "none";
|
||
|
onselect.style.display = "inline";
|
||
|
offselect.selectedIndex = 0;
|
||
|
};
|
||
|
|
||
|
this.updateState = function(selNode) {
|
||
|
/* update the visibility and selection of the list type pulldowns */
|
||
|
// we're going to walk through the tree manually since we want to
|
||
|
// check on 2 items at the same time
|
||
|
for (var currnode=selNode; currnode; currnode=currnode.parentNode) {
|
||
|
var tag = currnode.nodeName.toLowerCase();
|
||
|
if (tag == 'ul') {
|
||
|
this._handleStyles(currnode, this.ulselect, this.olselect);
|
||
|
return;
|
||
|
} else if (tag == 'ol') {
|
||
|
this._handleStyles(currnode, this.olselect, this.ulselect);
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
with(this.ulselect) {
|
||
|
selectedIndex = 0;
|
||
|
style.display = "none";
|
||
|
};
|
||
|
with(this.olselect) {
|
||
|
selectedIndex = 0;
|
||
|
style.display = "none";
|
||
|
};
|
||
|
};
|
||
|
|
||
|
this.addList = function(command) {
|
||
|
this.ulselect.style.display = "inline";
|
||
|
this.olselect.style.display = "none";
|
||
|
this.editor.execCommand(command);
|
||
|
this.editor.focusDocument();
|
||
|
};
|
||
|
this.addUnorderedList = function() {
|
||
|
/* add an unordered list */
|
||
|
this.addList("insertunorderedlist");
|
||
|
};
|
||
|
|
||
|
this.addOrderedList = function() {
|
||
|
/* add an ordered list */
|
||
|
this.addList("insertorderedlist");
|
||
|
};
|
||
|
|
||
|
this.setListStyle = function(tag, select) {
|
||
|
/* set the type of an ul */
|
||
|
var currnode = this.editor.getSelectedNode();
|
||
|
var l = this.editor.getNearestParentOfType(currnode, tag);
|
||
|
var style = select.options[select.selectedIndex].value;
|
||
|
if (this.editor.config.use_css) {
|
||
|
l.style.listStyleType = style;
|
||
|
} else {
|
||
|
l.setAttribute('type', this.style_to_type[style]);
|
||
|
}
|
||
|
this.editor.focusDocument();
|
||
|
this.editor.logMessage(_('List style changed'));
|
||
|
};
|
||
|
|
||
|
this.setUnorderedListStyle = function() {
|
||
|
/* set the type of an ul */
|
||
|
this.setListStyle('ul', this.ulselect);
|
||
|
};
|
||
|
|
||
|
this.setOrderedListStyle = function() {
|
||
|
/* set the type of an ol */
|
||
|
this.setListStyle('ol', this.olselect);
|
||
|
};
|
||
|
|
||
|
this.enable = function() {
|
||
|
KupuButtonEnable(this.addulbutton);
|
||
|
KupuButtonEnable(this.addolbutton);
|
||
|
this.ulselect.disabled = "";
|
||
|
this.olselect.disabled = "";
|
||
|
}
|
||
|
this.disable = function() {
|
||
|
KupuButtonDisable(this.addulbutton);
|
||
|
KupuButtonDisable(this.addolbutton);
|
||
|
this.ulselect.disabled = "disabled";
|
||
|
this.olselect.disabled = "disabled";
|
||
|
}
|
||
|
};
|
||
|
|
||
|
ListTool.prototype = new KupuTool;
|
||
|
|
||
|
function ShowPathTool() {
|
||
|
/* shows the path to the current element in the status bar */
|
||
|
|
||
|
this.updateState = function(selNode) {
|
||
|
/* calculate and display the path */
|
||
|
var path = '';
|
||
|
var url = null; // for links we want to display the url too
|
||
|
var currnode = selNode;
|
||
|
while (currnode != null && currnode.nodeName != '#document') {
|
||
|
if (currnode.nodeName.toLowerCase() == 'a') {
|
||
|
url = currnode.getAttribute('href');
|
||
|
};
|
||
|
path = '/' + currnode.nodeName.toLowerCase() + path;
|
||
|
currnode = currnode.parentNode;
|
||
|
}
|
||
|
|
||
|
try {
|
||
|
window.status = url ?
|
||
|
(path.toString() + ' - contains link to \'' +
|
||
|
url.toString() + '\'') :
|
||
|
path;
|
||
|
} catch (e) {
|
||
|
this.editor.logMessage(_('Could not set status bar message, ' +
|
||
|
'check your browser\'s security settings.'
|
||
|
), 1);
|
||
|
};
|
||
|
};
|
||
|
};
|
||
|
|
||
|
ShowPathTool.prototype = new KupuTool;
|
||
|
|
||
|
function ViewSourceTool() {
|
||
|
/* tool to provide a 'show source' context menu option */
|
||
|
this.sourceWindow = null;
|
||
|
|
||
|
this.viewSource = function() {
|
||
|
/* open a window and write the current contents of the iframe to it */
|
||
|
if (this.sourceWindow) {
|
||
|
this.sourceWindow.close();
|
||
|
};
|
||
|
this.sourceWindow = window.open('#', 'sourceWindow');
|
||
|
|
||
|
//var transform = this.editor._filterContent(this.editor.getInnerDocument().documentElement);
|
||
|
//var contents = transform.xml;
|
||
|
var contents = '<html>\n' + this.editor.getInnerDocument().documentElement.innerHTML + '\n</html>';
|
||
|
|
||
|
var doc = this.sourceWindow.document;
|
||
|
doc.write('\xa0');
|
||
|
doc.close();
|
||
|
var body = doc.getElementsByTagName("body")[0];
|
||
|
while (body.hasChildNodes()) {
|
||
|
body.removeChild(body.firstChild);
|
||
|
};
|
||
|
var pre = doc.createElement('pre');
|
||
|
var textNode = doc.createTextNode(contents);
|
||
|
body.appendChild(pre);
|
||
|
pre.appendChild(textNode);
|
||
|
};
|
||
|
|
||
|
this.createContextMenuElements = function(selNode, event) {
|
||
|
/* create the context menu element */
|
||
|
return new Array(new ContextMenuElement(_('View source'), this.viewSource, this));
|
||
|
};
|
||
|
};
|
||
|
|
||
|
ViewSourceTool.prototype = new KupuTool;
|
||
|
|
||
|
function DefinitionListTool(dlbuttonid) {
|
||
|
/* a tool for managing definition lists
|
||
|
|
||
|
the dl elements should behave much like plain lists, and the keypress
|
||
|
behaviour should be similar
|
||
|
*/
|
||
|
|
||
|
this.dlbutton = getFromSelector(dlbuttonid);
|
||
|
|
||
|
this.initialize = function(editor) {
|
||
|
/* initialize the tool */
|
||
|
this.editor = editor;
|
||
|
this._fixTabIndex(this.dlbutton);
|
||
|
addEventHandler(this.dlbutton, 'click', this.createDefinitionList, this);
|
||
|
addEventHandler(editor.getInnerDocument(), 'keyup', this._keyDownHandler, this);
|
||
|
addEventHandler(editor.getInnerDocument(), 'keypress', this._keyPressHandler, this);
|
||
|
};
|
||
|
|
||
|
// even though the following methods may seem view related, they belong
|
||
|
// here, since they describe core functionality rather then view-specific
|
||
|
// stuff
|
||
|
this.handleEnterPress = function(selNode) {
|
||
|
var dl = this.editor.getNearestParentOfType(selNode, 'dl');
|
||
|
if (dl) {
|
||
|
var dt = this.editor.getNearestParentOfType(selNode, 'dt');
|
||
|
if (dt) {
|
||
|
if (dt.childNodes.length == 1 && dt.childNodes[0].nodeValue == '\xa0') {
|
||
|
this.escapeFromDefinitionList(dl, dt, selNode);
|
||
|
return;
|
||
|
};
|
||
|
|
||
|
var selection = this.editor.getSelection();
|
||
|
var startoffset = selection.startOffset();
|
||
|
var endoffset = selection.endOffset();
|
||
|
if (endoffset > startoffset) {
|
||
|
// throw away any selected stuff
|
||
|
selection.cutChunk(startoffset, endoffset);
|
||
|
selection = this.editor.getSelection();
|
||
|
startoffset = selection.startOffset();
|
||
|
};
|
||
|
|
||
|
var ellength = selection.getElementLength(selection.parentElement());
|
||
|
if (startoffset >= ellength - 1) {
|
||
|
// create a new element
|
||
|
this.createDefinition(dl, dt);
|
||
|
} else {
|
||
|
var doc = this.editor.getInnerDocument();
|
||
|
var newdt = selection.splitNodeAtSelection(dt);
|
||
|
var newdd = doc.createElement('dd');
|
||
|
while (newdt.hasChildNodes()) {
|
||
|
if (newdt.firstChild != newdt.lastChild || newdt.firstChild.nodeName.toLowerCase() != 'br') {
|
||
|
newdd.appendChild(newdt.firstChild);
|
||
|
};
|
||
|
};
|
||
|
newdt.parentNode.replaceChild(newdd, newdt);
|
||
|
selection.selectNodeContents(newdd);
|
||
|
selection.collapse();
|
||
|
};
|
||
|
} else {
|
||
|
var dd = this.editor.getNearestParentOfType(selNode, 'dd');
|
||
|
if (!dd) {
|
||
|
this.editor.logMessage(_('Not inside a definition list element!'));
|
||
|
return;
|
||
|
};
|
||
|
if (dd.childNodes.length == 1 && dd.childNodes[0].nodeValue == '\xa0') {
|
||
|
this.escapeFromDefinitionList(dl, dd, selNode);
|
||
|
return;
|
||
|
};
|
||
|
var selection = this.editor.getSelection();
|
||
|
var startoffset = selection.startOffset();
|
||
|
var endoffset = selection.endOffset();
|
||
|
if (endoffset > startoffset) {
|
||
|
// throw away any selected stuff
|
||
|
selection.cutChunk(startoffset, endoffset);
|
||
|
selection = this.editor.getSelection();
|
||
|
startoffset = selection.startOffset();
|
||
|
};
|
||
|
var ellength = selection.getElementLength(selection.parentElement());
|
||
|
if (startoffset >= ellength - 1) {
|
||
|
// create a new element
|
||
|
this.createDefinitionTerm(dl, dd);
|
||
|
} else {
|
||
|
// add a break and continue in this element
|
||
|
var br = this.editor.getInnerDocument().createElement('br');
|
||
|
this.editor.insertNodeAtSelection(br, 1);
|
||
|
//var selection = this.editor.getSelection();
|
||
|
//selection.moveStart(1);
|
||
|
selection.collapse(true);
|
||
|
};
|
||
|
};
|
||
|
};
|
||
|
};
|
||
|
|
||
|
this.handleTabPress = function(selNode) {
|
||
|
};
|
||
|
|
||
|
this._keyDownHandler = function(event) {
|
||
|
var selNode = this.editor.getSelectedNode();
|
||
|
var dl = this.editor.getNearestParentOfType(selNode, 'dl');
|
||
|
if (!dl) {
|
||
|
return;
|
||
|
};
|
||
|
switch (event.keyCode) {
|
||
|
case 13:
|
||
|
if (event.preventDefault) {
|
||
|
event.preventDefault();
|
||
|
} else {
|
||
|
event.returnValue = false;
|
||
|
};
|
||
|
break;
|
||
|
};
|
||
|
};
|
||
|
|
||
|
this._keyPressHandler = function(event) {
|
||
|
var selNode = this.editor.getSelectedNode();
|
||
|
var dl = this.editor.getNearestParentOfType(selNode, 'dl');
|
||
|
if (!dl) {
|
||
|
return;
|
||
|
};
|
||
|
switch (event.keyCode) {
|
||
|
case 13:
|
||
|
this.handleEnterPress(selNode);
|
||
|
if (event.preventDefault) {
|
||
|
event.preventDefault();
|
||
|
} else {
|
||
|
event.returnValue = false;
|
||
|
};
|
||
|
break;
|
||
|
case 9:
|
||
|
if (event.preventDefault) {
|
||
|
event.preventDefault();
|
||
|
} else {
|
||
|
event.returnValue = false;
|
||
|
};
|
||
|
this.handleTabPress(selNode);
|
||
|
};
|
||
|
};
|
||
|
|
||
|
this.createDefinitionList = function() {
|
||
|
/* create a new definition list (dl) */
|
||
|
var selection = this.editor.getSelection();
|
||
|
var doc = this.editor.getInnerDocument();
|
||
|
|
||
|
var selection = this.editor.getSelection();
|
||
|
var cloned = selection.cloneContents();
|
||
|
// first get the 'first line' (until the first break) and use it
|
||
|
// as the dt's content
|
||
|
var iterator = new NodeIterator(cloned);
|
||
|
var currnode = null;
|
||
|
var remove = false;
|
||
|
while (currnode = iterator.next()) {
|
||
|
if (currnode.nodeName.toLowerCase() == 'br') {
|
||
|
remove = true;
|
||
|
};
|
||
|
if (remove) {
|
||
|
var next = currnode;
|
||
|
while (!next.nextSibling) {
|
||
|
next = next.parentNode;
|
||
|
};
|
||
|
next = next.nextSibling;
|
||
|
iterator.setCurrent(next);
|
||
|
currnode.parentNode.removeChild(currnode);
|
||
|
};
|
||
|
};
|
||
|
|
||
|
var dtcontentcontainer = cloned;
|
||
|
var collapsetoend = false;
|
||
|
|
||
|
var dl = doc.createElement('dl');
|
||
|
this.editor.insertNodeAtSelection(dl);
|
||
|
var dt = this.createDefinitionTerm(dl);
|
||
|
if (dtcontentcontainer.hasChildNodes()) {
|
||
|
collapsetoend = true;
|
||
|
while (dt.hasChildNodes()) {
|
||
|
dt.removeChild(dt.firstChild);
|
||
|
};
|
||
|
while (dtcontentcontainer.hasChildNodes()) {
|
||
|
dt.appendChild(dtcontentcontainer.firstChild);
|
||
|
};
|
||
|
};
|
||
|
|
||
|
var selection = this.editor.getSelection();
|
||
|
selection.selectNodeContents(dt);
|
||
|
selection.collapse(collapsetoend);
|
||
|
};
|
||
|
|
||
|
this.createDefinitionTerm = function(dl, dd) {
|
||
|
/* create a new definition term inside the current dl */
|
||
|
var doc = this.editor.getInnerDocument();
|
||
|
var dt = doc.createElement('dt');
|
||
|
// somehow Mozilla seems to add breaks to all elements...
|
||
|
if (dd) {
|
||
|
if (dd.lastChild.nodeName.toLowerCase() == 'br') {
|
||
|
dd.removeChild(dd.lastChild);
|
||
|
};
|
||
|
};
|
||
|
// dd may be null here, if so we assume this is the first element in
|
||
|
// the dl
|
||
|
if (!dd || dl == dd.lastChild) {
|
||
|
dl.appendChild(dt);
|
||
|
} else {
|
||
|
var nextsibling = dd.nextSibling;
|
||
|
if (nextsibling) {
|
||
|
dl.insertBefore(dt, nextsibling);
|
||
|
} else {
|
||
|
dl.appendChild(dt);
|
||
|
};
|
||
|
};
|
||
|
var nbsp = doc.createTextNode('\xa0');
|
||
|
dt.appendChild(nbsp);
|
||
|
var selection = this.editor.getSelection();
|
||
|
selection.selectNodeContents(dt);
|
||
|
selection.collapse();
|
||
|
|
||
|
this.editor.focusDocument();
|
||
|
return dt;
|
||
|
};
|
||
|
|
||
|
this.createDefinition = function(dl, dt, initial_content) {
|
||
|
var doc = this.editor.getInnerDocument();
|
||
|
var dd = doc.createElement('dd');
|
||
|
var nextsibling = dt.nextSibling;
|
||
|
// somehow Mozilla seems to add breaks to all elements...
|
||
|
if (dt) {
|
||
|
if (dt.lastChild.nodeName.toLowerCase() == 'br') {
|
||
|
dt.removeChild(dt.lastChild);
|
||
|
};
|
||
|
};
|
||
|
while (nextsibling) {
|
||
|
var name = nextsibling.nodeName.toLowerCase();
|
||
|
if (name == 'dd' || name == 'dt') {
|
||
|
break;
|
||
|
} else {
|
||
|
nextsibling = nextsibling.nextSibling;
|
||
|
};
|
||
|
};
|
||
|
if (nextsibling) {
|
||
|
dl.insertBefore(dd, nextsibling);
|
||
|
//this._fixStructure(doc, dl, nextsibling);
|
||
|
} else {
|
||
|
dl.appendChild(dd);
|
||
|
};
|
||
|
if (initial_content) {
|
||
|
for (var i=0; i < initial_content.length; i++) {
|
||
|
dd.appendChild(initial_content[i]);
|
||
|
};
|
||
|
};
|
||
|
var nbsp = doc.createTextNode('\xa0');
|
||
|
dd.appendChild(nbsp);
|
||
|
var selection = this.editor.getSelection();
|
||
|
selection.selectNodeContents(dd);
|
||
|
selection.collapse();
|
||
|
};
|
||
|
|
||
|
this.escapeFromDefinitionList = function(dl, currel, selNode) {
|
||
|
var doc = this.editor.getInnerDocument();
|
||
|
var p = doc.createElement('p');
|
||
|
var nbsp = doc.createTextNode('\xa0');
|
||
|
p.appendChild(nbsp);
|
||
|
|
||
|
if (dl.lastChild == currel) {
|
||
|
dl.parentNode.insertBefore(p, dl.nextSibling);
|
||
|
} else {
|
||
|
for (var i=0; i < dl.childNodes.length; i++) {
|
||
|
var child = dl.childNodes[i];
|
||
|
if (child == currel) {
|
||
|
var newdl = this.editor.getInnerDocument().createElement('dl');
|
||
|
while (currel.nextSibling) {
|
||
|
newdl.appendChild(currel.nextSibling);
|
||
|
};
|
||
|
dl.parentNode.insertBefore(newdl, dl.nextSibling);
|
||
|
dl.parentNode.insertBefore(p, dl.nextSibling);
|
||
|
};
|
||
|
};
|
||
|
};
|
||
|
currel.parentNode.removeChild(currel);
|
||
|
var selection = this.editor.getSelection();
|
||
|
selection.selectNodeContents(p);
|
||
|
selection.collapse();
|
||
|
this.editor.focusDocument();
|
||
|
};
|
||
|
|
||
|
this._fixStructure = function(doc, dl, offsetnode) {
|
||
|
/* makes sure the order of the elements is correct */
|
||
|
var currname = offsetnode.nodeName.toLowerCase();
|
||
|
var currnode = offsetnode.nextSibling;
|
||
|
while (currnode) {
|
||
|
if (currnode.nodeType == 1) {
|
||
|
var nodename = currnode.nodeName.toLowerCase();
|
||
|
if (currname == 'dt' && nodename == 'dt') {
|
||
|
var dd = doc.createElement('dd');
|
||
|
while (currnode.hasChildNodes()) {
|
||
|
dd.appendChild(currnode.childNodes[0]);
|
||
|
};
|
||
|
currnode.parentNode.replaceChild(dd, currnode);
|
||
|
} else if (currname == 'dd' && nodename == 'dd') {
|
||
|
var dt = doc.createElement('dt');
|
||
|
while (currnode.hasChildNodes()) {
|
||
|
dt.appendChild(currnode.childNodes[0]);
|
||
|
};
|
||
|
currnode.parentNode.replaceChild(dt, currnode);
|
||
|
};
|
||
|
};
|
||
|
currnode = currnode.nextSibling;
|
||
|
};
|
||
|
};
|
||
|
};
|
||
|
|
||
|
DefinitionListTool.prototype = new KupuTool;
|
||
|
|
||
|
function KupuZoomTool(buttonid, firsttab, lasttab) {
|
||
|
this.button = getFromSelector(buttonid);
|
||
|
firsttab = firsttab || 'kupu-tb-styles';
|
||
|
lasttab = lasttab || 'kupu-logo-button';
|
||
|
|
||
|
this.initialize = function(editor) {
|
||
|
this.offclass = 'kupu-zoom';
|
||
|
this.onclass = 'kupu-zoom-pressed';
|
||
|
this.pressed = false;
|
||
|
|
||
|
this.baseinitialize(editor);
|
||
|
this.button.tabIndex = this.editor.document.editable.tabIndex;
|
||
|
addEventHandler(window, "resize", this.onresize, this);
|
||
|
addEventHandler(window, "scroll", this.onscroll, this);
|
||
|
|
||
|
/* Toolbar tabbing */
|
||
|
var lastbutton = getFromSelector(lasttab);
|
||
|
var firstbutton = getFromSelector(firsttab);
|
||
|
var iframe = editor.getInnerDocument();
|
||
|
this.setTabbing(iframe, firstbutton, lastbutton);
|
||
|
this.setTabbing(firstbutton, null, editor.getDocument().getWindow());
|
||
|
|
||
|
this.editor.logMessage(_('Zoom tool initialized'));
|
||
|
};
|
||
|
};
|
||
|
|
||
|
KupuZoomTool.prototype = new KupuLateFocusStateButton;
|
||
|
KupuZoomTool.prototype.baseinitialize = KupuZoomTool.prototype.initialize;
|
||
|
|
||
|
KupuZoomTool.prototype.onscroll = function() {
|
||
|
if (!this.zoomed) return;
|
||
|
/* XXX Problem here: Mozilla doesn't generate onscroll when window is
|
||
|
* scrolled by focus move or selection. */
|
||
|
var top = window.pageYOffset!=undefined ? window.pageYOffset : document.documentElement.scrollTop;
|
||
|
var left = window.pageXOffset!=undefined ? window.pageXOffset : document.documentElement.scrollLeft;
|
||
|
if (top || left) window.scrollTo(0, 0);
|
||
|
}
|
||
|
|
||
|
// Handle tab pressed from a control.
|
||
|
KupuZoomTool.prototype.setTabbing = function(control, forward, backward) {
|
||
|
function TabDown(event) {
|
||
|
if (event.keyCode != 9 || !this.zoomed) return;
|
||
|
|
||
|
var target = event.shiftKey ? backward : forward;
|
||
|
if (!target) return;
|
||
|
|
||
|
if (event.stopPropogation) event.stopPropogation();
|
||
|
event.cancelBubble = true;
|
||
|
event.returnValue = false;
|
||
|
|
||
|
target.focus();
|
||
|
return false;
|
||
|
}
|
||
|
addEventHandler(control, "keydown", TabDown, this);
|
||
|
}
|
||
|
|
||
|
KupuZoomTool.prototype.onresize = function() {
|
||
|
if (!this.zoomed) return;
|
||
|
|
||
|
var editor = this.editor;
|
||
|
var iframe = editor.getDocument().editable;
|
||
|
var sourcetool = editor.getTool('sourceedittool');
|
||
|
var sourceArea = sourcetool?sourcetool.getSourceArea():null;
|
||
|
|
||
|
var fulleditor = iframe.parentNode;
|
||
|
var body = document.body;
|
||
|
|
||
|
if (window.innerWidth) {
|
||
|
var width = window.innerWidth;
|
||
|
var height = window.innerHeight;
|
||
|
} else if (document.documentElement) {
|
||
|
var width = document.documentElement.offsetWidth-5;
|
||
|
var height = document.documentElement.offsetHeight-5;
|
||
|
} else {
|
||
|
var width = document.body.offsetWidth-5;
|
||
|
var height = document.body.offsetHeight-5;
|
||
|
}
|
||
|
width = width + 'px';
|
||
|
var offset = iframe.offsetTop;
|
||
|
if (sourceArea) offset = sourceArea.offsetTop-1;
|
||
|
// XXX: TODO: Using wrong values here, figure out why.
|
||
|
var nheight = Math.max(height - offset -1/*top border*/, 10);
|
||
|
nheight = nheight + 'px';
|
||
|
fulleditor.style.width = width; /*IE needs this*/
|
||
|
iframe.style.width = width;
|
||
|
iframe.style.height = nheight;
|
||
|
if (sourceArea) {
|
||
|
sourceArea.style.width = width;
|
||
|
sourceArea.style.height = height;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
KupuZoomTool.prototype.checkfunc = function(selNode, button, editor, event) {
|
||
|
return this.zoomed;
|
||
|
}
|
||
|
|
||
|
KupuZoomTool.prototype.commandfunc = function(button, editor) {
|
||
|
/* Toggle zoom state */
|
||
|
var zoom = button.pressed;
|
||
|
this.zoomed = zoom;
|
||
|
|
||
|
var zoomClass = 'kupu-fulleditor-zoomed';
|
||
|
var iframe = editor.getDocument().getEditable();
|
||
|
|
||
|
var body = document.body;
|
||
|
var html = document.getElementsByTagName('html')[0];
|
||
|
if (zoom) {
|
||
|
html.style.overflow = 'hidden';
|
||
|
window.scrollTo(0, 0);
|
||
|
editor.setClass(zoomClass);
|
||
|
body.className += ' '+zoomClass;
|
||
|
this.onresize();
|
||
|
} else {
|
||
|
html.style.overflow = '';
|
||
|
var fulleditor = iframe.parentNode;
|
||
|
fulleditor.style.width = '';
|
||
|
body.className = body.className.replace(' '+zoomClass, '');
|
||
|
editor.clearClass(zoomClass);
|
||
|
|
||
|
iframe.style.width = '';
|
||
|
iframe.style.height = '';
|
||
|
|
||
|
var sourcetool = editor.getTool('sourceedittool');
|
||
|
var sourceArea = sourcetool?sourcetool.getSourceArea():null;
|
||
|
if (sourceArea) {
|
||
|
sourceArea.style.width = '';
|
||
|
sourceArea.style.height = '';
|
||
|
};
|
||
|
}
|
||
|
var doc = editor.getInnerDocument();
|
||
|
// Mozilla needs this. Yes, really!
|
||
|
doc.designMode=doc.designMode;
|
||
|
|
||
|
window.scrollTo(0, iframe.offsetTop);
|
||
|
editor.focusDocument();
|
||
|
}
|