/* WysiHat - WYSIWYG JavaScript framework, version 0.2 * (c) 2008-2009 Joshua Peek * * WysiHat is freely distributable under the terms of an MIT-style license. *--------------------------------------------------------------------------*/ var WysiHat = {}; WysiHat.Editor = { attach: function(textarea, options, block) { options = $H(options); textarea = $(textarea); textarea.hide(); var model = options.get('model') || WysiHat.iFrame; var initializer = block; return model.create(textarea, function(editArea) { var document = editArea.getDocument(); var window = editArea.getWindow(); editArea.load(); Event.observe(window, 'focus', function(event) { editArea.focus(); }); Event.observe(window, 'blur', function(event) { editArea.blur(); }); editArea._observeEvents(); if (Prototype.Browser.Gecko) { editArea.execCommand('undo', false, null); } if (initializer) initializer(editArea); editArea.focus(); }); }, include: function(module) { this.includedModules = this.includedModules || $A([]); this.includedModules.push(module); }, extend: function(object) { var modules = this.includedModules || $A([]); modules.each(function(module) { Object.extend(object, module); }); } }; WysiHat.Commands = (function() { function boldSelection() { this.execCommand('bold', false, null); } function boldSelected() { return this.queryCommandState('bold'); } function underlineSelection() { this.execCommand('underline', false, null); } function underlineSelected() { return this.queryCommandState('underline'); } function italicSelection() { this.execCommand('italic', false, null); } function italicSelected() { return this.queryCommandState('italic'); } function strikethroughSelection() { this.execCommand('strikethrough', false, null); } function blockquoteSelection() { this.execCommand('blockquote', false, null); } function fontSelection(font) { this.execCommand('fontname', false, font); } function fontSelected() { var node = this.selection.getNode(); return Element.getStyle(node, 'fontFamily'); } function fontSizeSelection(fontSize) { this.execCommand('fontsize', false, fontSize); } function fontSizeSelected() { var node = this.selection.getNode(); return standardizeFontSize(Element.getStyle(node, 'fontSize')); } function colorSelection(color) { this.execCommand('forecolor', false, color); } function colorSelected() { var node = this.selection.getNode(); return standardizeColor(Element.getStyle(node, 'color')); } function backgroundColorSelection(color) { if(Prototype.Browser.Gecko) { this.execCommand('hilitecolor', false, color); } else { this.execCommand('backcolor', false, color); } } function backgroundColorSelected() { var node = this.selection.getNode(); return standardizeColor(Element.getStyle(node, 'backgroundColor')); } function alignSelection(alignment) { this.execCommand('justify' + alignment); } function alignSelected() { var node = this.selection.getNode(); return Element.getStyle(node, 'textAlign'); } function linkSelection(url) { this.execCommand('createLink', false, url); } function unlinkSelection() { var node = this.selection.getNode(); if (this.linkSelected()) this.selection.selectNode(node); this.execCommand('unlink', false, null); } function linkSelected() { var node = this.selection.getNode(); return node ? node.tagName.toUpperCase() == 'A' : false; } function insertOrderedList() { this.execCommand('insertorderedlist', false, null); } function insertUnorderedList() { this.execCommand('insertunorderedlist', false, null); } function insertImage(url) { this.execCommand('insertImage', false, url); } function insertHTML(html) { if (Prototype.Browser.IE) { var range = this._selection.getRange(); range.pasteHTML(html); range.collapse(false); range.select(); } else { this.execCommand('insertHTML', false, html); } } function execCommand(command, ui, value) { var document = this.getDocument(); if (Prototype.Browser.IE) this.selection.restore(); var handler = this.commands.get(command) if (handler) handler.bind(this)(value); else document.execCommand(command, ui, value); } function queryCommandState(state) { var document = this.getDocument(); var handler = this.queryCommands.get(state) if (handler) return handler.bind(this)(); else return document.queryCommandState(state); } var fontSizeNames = $w('xxx-small xx-small x-small small medium large x-large xx-large'); var fontSizePixels = $w('9px 10px 13px 16px 18px 24px 32px 48px'); if (Prototype.Browser.WebKit) { fontSizeNames.shift(); fontSizeNames.push('-webkit-xxx-large'); } function standardizeFontSize(fontSize) { var newSize = fontSizeNames.indexOf(fontSize); if (newSize >= 0) return newSize; newSize = fontSizePixels.indexOf(fontSize); if (newSize >= 0) return newSize; return parseInt(fontSize); } function standardizeColor(color) { if (!color || color.match(/[0-9a-f]{6}/i)) return color; var m = color.toLowerCase().match(/^(rgba?|hsla?)\(([\s\.\-,%0-9]+)\)/); if(m){ var c = m[2].split(/\s*,\s*/), l = c.length, t = m[1]; if((t == "rgb" && l == 3) || (t == "rgba" && l == 4)){ var r = c[0]; if(r.charAt(r.length - 1) == "%"){ var a = c.map(function(x){ return parseFloat(x) * 2.56; }); if(l == 4){ a[3] = c[3]; } return _colorFromArray(a); } return _colorFromArray(c); } if((t == "hsl" && l == 3) || (t == "hsla" && l == 4)){ var H = ((parseFloat(c[0]) % 360) + 360) % 360 / 360, S = parseFloat(c[1]) / 100, L = parseFloat(c[2]) / 100, m2 = L <= 0.5 ? L * (S + 1) : L + S - L * S, m1 = 2 * L - m2, a = [_hue2rgb(m1, m2, H + 1 / 3) * 256, _hue2rgb(m1, m2, H) * 256, _hue2rgb(m1, m2, H - 1 / 3) * 256, 1]; if(l == 4){ a[3] = c[3]; } return _colorFromArray(a); } } return null; // dojo.Color } function _colorFromArray(a) { var arr = a.slice(0, 3).map(function(x){ var s = parseInt(x).toString(16); return s.length < 2 ? "0" + s : s; }); return "#" + arr.join(""); // String } function _hue2rgb(m1, m2, h){ if(h < 0){ ++h; } if(h > 1){ --h; } var h6 = 6 * h; if(h6 < 1){ return m1 + (m2 - m1) * h6; } if(2 * h < 1){ return m2; } if(3 * h < 2){ return m1 + (m2 - m1) * (2 / 3 - h) * 6; } return m1; } return { boldSelection: boldSelection, boldSelected: boldSelected, underlineSelection: underlineSelection, underlineSelected: underlineSelected, italicSelection: italicSelection, italicSelected: italicSelected, strikethroughSelection: strikethroughSelection, blockquoteSelection: blockquoteSelection, fontSelection: fontSelection, fontSelected: fontSelected, fontSizeSelection: fontSizeSelection, fontSizeSelected: fontSizeSelected, colorSelection: colorSelection, colorSelected: colorSelected, backgroundColorSelection: backgroundColorSelection, backgroundColorSelected: backgroundColorSelected, alignSelection: alignSelection, alignSelected: alignSelected, linkSelection: linkSelection, unlinkSelection: unlinkSelection, linkSelected: linkSelected, insertOrderedList: insertOrderedList, insertUnorderedList: insertUnorderedList, insertImage: insertImage, insertHTML: insertHTML, execCommand: execCommand, queryCommandState: queryCommandState, commands: $H({}), queryCommands: $H({ link: linkSelected }) }; })(); WysiHat.Editor.include(WysiHat.Commands); WysiHat.Events = (function() { var eventsToFoward = [ 'click', 'dblclick', 'mousedown', 'mouseup', 'mouseover', 'mousemove', 'mouseout', 'keypress', 'keydown', 'keyup' ]; function forwardEvents(document, editor) { eventsToFoward.each(function(event) { Event.observe(document, event, function(e) { editor.fire('wysihat:' + event); }); }); } function observePasteEvent(window, document, editor) { Event.observe(document, 'keydown', function(event) { if (event.keyCode == 86) editor.fire("wysihat:paste"); }); Event.observe(window, 'paste', function(event) { editor.fire("wysihat:paste"); }); } function observeFocus(window, editor) { Event.observe(window, 'focus', function(event) { editor.fire("wysihat:focus"); }); Event.observe(window, 'blur', function(event) { editor.fire("wysihat:blur"); }); } function observeSelections(document, editor) { Event.observe(document, 'mouseup', function(event) { var range = editor.selection.getRange(); if (!range.collapsed) editor.fire("wysihat:select"); }); } function observeChanges(document, editor) { var previousContents = editor.rawContent(); Event.observe(document, 'keyup', function(event) { var contents = editor.rawContent(); if (previousContents != contents) { editor.fire("wysihat:change"); previousContents = contents; } }); } function observeCursorMovements(document, editor) { var previousRange = editor.selection.getRange(); var handler = function(event) { var range = editor.selection.getRange(); if (previousRange != range) { editor.fire("wysihat:cursormove"); previousRange = range; } }; Event.observe(document, 'keyup', handler); Event.observe(document, 'mouseup', handler); } function observeEvents() { if (this._observers_setup) return; var document = this.getDocument(); var window = this.getWindow(); forwardEvents(document, this); observePasteEvent(window, document, this); observeFocus(window, this); observeSelections(document, this); observeChanges(document, this); observeCursorMovements(document, this); this._observers_setup = true; } return { _observeEvents: observeEvents }; })(); WysiHat.Editor.include(WysiHat.Events); WysiHat.Persistence = (function() { function outputFilter(text) { return text.formatHTMLOutput(); } function inputFilter(text) { return text.formatHTMLInput(); } function content() { return this.outputFilter(this.rawContent()); } function setContent(text) { this.setRawContent(this.inputFilter(text)); } function save() { this.textarea.value = this.content(); } function load() { this.setContent(this.textarea.value); } function reload() { this.selection.setBookmark(); this.save(); this.load(); this.selection.moveToBookmark(); } return { outputFilter: outputFilter, inputFilter: inputFilter, content: content, setContent: setContent, save: save, load: load, reload: reload }; })(); WysiHat.Editor.include(WysiHat.Persistence); WysiHat.Window = (function() { function getDocument() { return this.contentDocument || this.contentWindow.document; } function getWindow() { if (this.contentDocument) return this.contentDocument.defaultView; else if (this.contentWindow.document) return this.contentWindow; else return null; } function focus() { this.getWindow().focus(); if (this.hasFocus) return; this.hasFocus = true; } function blur() { this.hasFocus = false; } return { getDocument: getDocument, getWindow: getWindow, focus: focus, blur: blur }; })(); WysiHat.Editor.include(WysiHat.Window); WysiHat.iFrame = { create: function(textarea, callback) { var editArea = new Element('iframe', { 'id': textarea.id + '_editor', 'class': 'editor' }); Object.extend(editArea, WysiHat.iFrame.Methods); WysiHat.Editor.extend(editArea); editArea.attach(textarea, callback); textarea.insert({before: editArea}); return editArea; } }; WysiHat.iFrame.Methods = { attach: function(element, callback) { this.textarea = element; this.observe('load', function() { try { var document = this.getDocument(); } catch(e) { return; } // No iframe, just stop this.selection = new WysiHat.Selection(this); if (this.ready && document.designMode == 'on') return; this.setStyle({}); document.designMode = 'on'; callback(this); this.ready = true; this.fire('wysihat:ready'); }); }, unattach: function() { this.remove(); }, whenReady: function(callback) { if (this.ready) { callback(this); } else { var editor = this; editor.observe('wysihat:ready', function() { callback(editor); }); } return this; }, setStyle: function(styles) { var document = this.getDocument(); var element = this; if (!this.ready) return setTimeout(function() { element.setStyle(styles); }, 1); if (Prototype.Browser.IE) { var style = document.createStyleSheet(); style.addRule("body", "border: 0"); style.addRule("p", "margin: 0"); $H(styles).each(function(pair) { var value = pair.first().underscore().dasherize() + ": " + pair.last(); style.addRule("body", value); }); } else if (Prototype.Browser.Opera) { var style = Element('style').update("p { margin: 0; }"); var head = document.getElementsByTagName('head')[0]; head.appendChild(style); } else { Element.setStyle(document.body, styles); } return this; }, linkStyleSheet: function(href) { this.whenReady(function(editor){ var document = editor.getDocument(); if(document.createStyleSheet) { // IE document.createStyleSheet(css); } else { var head = document.documentElement.getElementsByTagName('head')[0]; if (!head) { head=document.createElement('head'); document.documentElement.insertBefore(head,document.getElementsByTagName('body')[0]); } var link=''; head=$(head); if (head.insert) { // Safari $(head).insert(link); } else { // everyone else head.innerHTML=head.innerHTML+link; } } }); }, /** * WysiHat.iFrame.Methods#getStyle(style) -> string * - style specificication (i.e. backgroundColor) * * Returns the style from the element based on the given style */ getStyle: function(style) { var document = this.getDocument(); return Element.getStyle(document.body, style); }, rawContent: function() { var document = this.getDocument(); if (document.body) return document.body.innerHTML; else return ""; }, setRawContent: function(text) { var document = this.getDocument(); if (document.body) document.body.innerHTML = text; } }; WysiHat.Editable = { create: function(textarea, callback) { var editArea = new Element('div', { 'id': textarea.id + '_editor', 'class': 'editor', 'contenteditable': 'true' }); editArea.textarea = textarea; WysiHat.Editor.extend(editArea); Object.extend(editArea, WysiHat.Editable.Methods); callback(editArea); textarea.insert({before: editArea}); return editArea; } }; WysiHat.Editable.Methods = { getDocument: function() { return document; }, getWindow: function() { return window; }, rawContent: function() { return this.innerHTML; }, setRawContent: function(text) { this.innerHTML = text; } }; Object.extend(String.prototype, (function() { function formatHTMLOutput() { var text = String(this); text = text.tidyXHTML(); if (Prototype.Browser.WebKit) { text = text.replace(/(
\s*<\/p>/g, "");
text = text.replace(/
(\n)*/g, "\n");
} else if (Prototype.Browser.Gecko) {
text = text.replace(/
/g, "");
text = text.replace(/<\/p>(\n)?/g, "\n");
text = text.replace(/
(\n)*/g, "\n");
} else if (Prototype.Browser.IE || Prototype.Browser.Opera) {
text = text.replace(/
( | |\s)<\/p>/g, "
"); text = text.replace(//g, ''); text = text.replace(/ /g, ''); text = text.replace(/<\/p>(\n)?/g, "\n"); text = text.gsub(/^
/, ''); text = text.gsub(/<\/p>$/, ''); } text = text.gsub(//, ""); text = text.gsub(/<\/b>/, ""); text = text.gsub(//, ""); text = text.gsub(/<\/i>/, ""); text = text.replace(/\n\n+/g, "
\n\n");
text = text.gsub(/(([^\n])(\n))(?=([^\n]))/, "#{2}
\n");
text = '
' + text + '
'; text = text.replace(/\s*/g, "
"); text = text.replace(/\s*<\/p>/g, "
"); var element = Element("body"); element.innerHTML = text; if (Prototype.Browser.WebKit || Prototype.Browser.Gecko) { var replaced; do { replaced = false; element.select('span').each(function(span) { if (span.hasClassName('Apple-style-span')) { span.removeClassName('Apple-style-span'); if (span.className == '') span.removeAttribute('class'); replaced = true; } else if (span.getStyle('fontWeight') == 'bold') { span.setStyle({fontWeight: ''}); if (span.style.length == 0) span.removeAttribute('style'); span.update('' + span.innerHTML + ''); replaced = true; } else if (span.getStyle('fontStyle') == 'italic') { span.setStyle({fontStyle: ''}); if (span.style.length == 0) span.removeAttribute('style'); span.update('' + span.innerHTML + ''); replaced = true; } else if (span.getStyle('textDecoration') == 'underline') { span.setStyle({textDecoration: ''}); if (span.style.length == 0) span.removeAttribute('style'); span.update('' + span.innerHTML + ''); replaced = true; } else if (span.attributes.length == 0) { span.replace(span.innerHTML); replaced = true; } }); } while (replaced); } var acceptableBlankTags = $A(['BR', 'IMG']); for (var i = 0; i < element.descendants().length; i++) { var node = element.descendants()[i]; if (node.innerHTML.blank() && !acceptableBlankTags.include(node.nodeName) && node.id != 'bookmark') node.remove(); } text = element.innerHTML; text = text.tidyXHTML(); text = text.replace(//g, "
\n\n"); text = text.replace(/
\s*<\/p>/g, ""); text = text.replace(/\s*$/g, ""); return text; } function formatHTMLInput() { var text = String(this); var element = Element("body"); element.innerHTML = text; if (Prototype.Browser.Gecko || Prototype.Browser.WebKit) { element.select('strong').each(function(element) { element.replace('' + element.innerHTML + ''); }); element.select('em').each(function(element) { element.replace('' + element.innerHTML + ''); }); element.select('u').each(function(element) { element.replace('' + element.innerHTML + ''); }); } if (Prototype.Browser.WebKit) element.select('span').each(function(span) { if (span.getStyle('fontWeight') == 'bold') span.addClassName('Apple-style-span'); if (span.getStyle('fontStyle') == 'italic') span.addClassName('Apple-style-span'); if (span.getStyle('textDecoration') == 'underline') span.addClassName('Apple-style-span'); }); text = element.innerHTML; text = text.tidyXHTML(); text = text.replace(/<\/p>(\n)*
/g, "\n\n");
text = text.replace(/(\n)?
(\n)?/g, "\n");
text = text.replace(/^
/g, '');
text = text.replace(/<\/p>$/g, '');
if (Prototype.Browser.Gecko) {
text = text.replace(/\n/g, "
");
text = text + '
';
} else if (Prototype.Browser.WebKit) {
text = text.replace(/\n/g, "
"); text = '
' + text + '
'; text = text.replace(/<\/p>/g, "
"); text = text.replace(/(
<\/p>)+$/g, "");
}
return text;
}
function tidyXHTML() {
var text = String(this);
text = text.gsub(/\r\n?/, "\n");
text = text.gsub(/<([A-Z]+)([^>]*)>/, function(match) {
return '<' + match[1].toLowerCase() + match[2] + '>';
});
text = text.gsub(/<\/([A-Z]+)>/, function(match) {
return '' + match[1].toLowerCase() + '>';
});
text = text.replace(/
/g, "
");
return text;
}
return {
formatHTMLOutput: formatHTMLOutput,
formatHTMLInput: formatHTMLInput,
tidyXHTML: tidyXHTML
};
})());
Object.extend(String.prototype, {
sanitize: function(options) {
return Element("div").update(this).sanitize(options).innerHTML.tidyXHTML();
}
});
Element.addMethods({
sanitize: function(element, options) {
element = $(element);
options = $H(options);
var allowed_tags = $A(options.get('tags') || []);
var allowed_attributes = $A(options.get('attributes') || []);
var sanitized = Element(element.nodeName);
$A(element.childNodes).each(function(child) {
if (child.nodeType == 1) {
var children = $(child).sanitize(options).childNodes;
if (allowed_tags.include(child.nodeName.toLowerCase())) {
var new_child = Element(child.nodeName);
allowed_attributes.each(function(attribute) {
if ((value = child.readAttribute(attribute)))
new_child.writeAttribute(attribute, value);
});
sanitized.appendChild(new_child);
$A(children).each(function(grandchild) { new_child.appendChild(grandchild); });
} else {
$A(children).each(function(grandchild) { sanitized.appendChild(grandchild); });
}
} else if (child.nodeType == 3) {
sanitized.appendChild(child);
}
});
return sanitized;
}
});
if (typeof Range == 'undefined') {
Range = function(ownerDocument) {
this.ownerDocument = ownerDocument;
this.startContainer = this.ownerDocument.documentElement;
this.startOffset = 0;
this.endContainer = this.ownerDocument.documentElement;
this.endOffset = 0;
this.collapsed = true;
this.commonAncestorContainer = this._commonAncestorContainer(this.startContainer, this.endContainer);
this.detached = false;
this.START_TO_START = 0;
this.START_TO_END = 1;
this.END_TO_END = 2;
this.END_TO_START = 3;
}
Range.CLONE_CONTENTS = 0;
Range.DELETE_CONTENTS = 1;
Range.EXTRACT_CONTENTS = 2;
if (!document.createRange) {
document.createRange = function() {
return new Range(this);
};
}
Object.extend(Range.prototype, (function() {
function cloneContents() {
return _processContents(this, Range.CLONE_CONTENTS);
}
function cloneRange() {
try {
var clone = new Range(this.ownerDocument);
clone.startContainer = this.startContainer;
clone.startOffset = this.startOffset;
clone.endContainer = this.endContainer;
clone.endOffset = this.endOffset;
clone.collapsed = this.collapsed;
clone.commonAncestorContainer = this.commonAncestorContainer;
clone.detached = this.detached;
return clone;
} catch (e) {
return null;
};
}
function collapse(toStart) {
if (toStart) {
this.endContainer = this.startContainer;
this.endOffset = this.startOffset;
this.collapsed = true;
} else {
this.startContainer = this.endContainer;
this.startOffset = this.endOffset;
this.collapsed = true;
}
}
function compareBoundaryPoints(compareHow, sourceRange) {
try {
var cmnSelf, cmnSource, rootSelf, rootSource;
cmnSelf = this.commonAncestorContainer;
cmnSource = sourceRange.commonAncestorContainer;
rootSelf = cmnSelf;
while (rootSelf.parentNode) {
rootSelf = rootSelf.parentNode;
}
rootSource = cmnSource;
while (rootSource.parentNode) {
rootSource = rootSource.parentNode;
}
switch (compareHow) {
case this.START_TO_START:
return _compareBoundaryPoints(this, this.startContainer, this.startOffset, sourceRange.startContainer, sourceRange.startOffset);
break;
case this.START_TO_END:
return _compareBoundaryPoints(this, this.startContainer, this.startOffset, sourceRange.endContainer, sourceRange.endOffset);
break;
case this.END_TO_END:
return _compareBoundaryPoints(this, this.endContainer, this.endOffset, sourceRange.endContainer, sourceRange.endOffset);
break;
case this.END_TO_START:
return _compareBoundaryPoints(this, this.endContainer, this.endOffset, sourceRange.startContainer, sourceRange.startOffset);
break;
}
} catch (e) {};
return null;
}
function deleteContents() {
try {
_processContents(this, Range.DELETE_CONTENTS);
} catch (e) {}
}
function detach() {
this.detached = true;
}
function extractContents() {
try {
return _processContents(this, Range.EXTRACT_CONTENTS);
} catch (e) {
return null;
};
}
function insertNode(newNode) {
try {
var n, newText, offset;
switch (this.startContainer.nodeType) {
case Node.CDATA_SECTION_NODE:
case Node.TEXT_NODE:
newText = this.startContainer.splitText(this.startOffset);
this.startContainer.parentNode.insertBefore(newNode, newText);
break;
default:
if (this.startContainer.childNodes.length == 0) {
offset = null;
} else {
offset = this.startContainer.childNodes(this.startOffset);
}
this.startContainer.insertBefore(newNode, offset);
}
} catch (e) {}
}
function selectNode(refNode) {
this.setStartBefore(refNode);
this.setEndAfter(refNode);
}
function selectNodeContents(refNode) {
this.setStart(refNode, 0);
this.setEnd(refNode, refNode.childNodes.length);
}
function setStart(refNode, offset) {
try {
var endRootContainer, startRootContainer;
this.startContainer = refNode;
this.startOffset = offset;
endRootContainer = this.endContainer;
while (endRootContainer.parentNode) {
endRootContainer = endRootContainer.parentNode;
}
startRootContainer = this.startContainer;
while (startRootContainer.parentNode) {
startRootContainer = startRootContainer.parentNode;
}
if (startRootContainer != endRootContainer) {
this.collapse(true);
} else {
if (_compareBoundaryPoints(this, this.startContainer, this.startOffset, this.endContainer, this.endOffset) > 0) {
this.collapse(true);
}
}
this.collapsed = _isCollapsed(this);
this.commonAncestorContainer = _commonAncestorContainer(this.startContainer, this.endContainer);
} catch (e) {}
}
function setStartAfter(refNode) {
this.setStart(refNode.parentNode, _nodeIndex(refNode) + 1);
}
function setStartBefore(refNode) {
this.setStart(refNode.parentNode, _nodeIndex(refNode));
}
function setEnd(refNode, offset) {
try {
this.endContainer = refNode;
this.endOffset = offset;
endRootContainer = this.endContainer;
while (endRootContainer.parentNode) {
endRootContainer = endRootContainer.parentNode;
}
startRootContainer = this.startContainer;
while (startRootContainer.parentNode) {
startRootContainer = startRootContainer.parentNode;
}
if (startRootContainer != endRootContainer) {
this.collapse(false);
} else {
if (_compareBoundaryPoints(this, this.startContainer, this.startOffset, this.endContainer, this.endOffset) > 0) {
this.collapse(false);
}
}
this.collapsed = _isCollapsed(this);
this.commonAncestorContainer = _commonAncestorContainer(this.startContainer, this.endContainer);
} catch (e) {}
}
function setEndAfter(refNode) {
this.setEnd(refNode.parentNode, _nodeIndex(refNode) + 1);
}
function setEndBefore(refNode) {
this.setEnd(refNode.parentNode, _nodeIndex(refNode));
}
function surroundContents(newParent) {
try {
var n, fragment;
while (newParent.firstChild) {
newParent.removeChild(newParent.firstChild);
}
fragment = this.extractContents();
this.insertNode(newParent);
newParent.appendChild(fragment);
this.selectNode(newParent);
} catch (e) {}
}
function _compareBoundaryPoints(range, containerA, offsetA, containerB, offsetB) {
var c, offsetC, n, cmnRoot, childA;
if (containerA == containerB) {
if (offsetA == offsetB) {
return 0; // equal
} else if (offsetA < offsetB) {
return -1; // before
} else {
return 1; // after
}
}
c = containerB;
while (c && c.parentNode != containerA) {
c = c.parentNode;
}
if (c) {
offsetC = 0;
n = containerA.firstChild;
while (n != c && offsetC < offsetA) {
offsetC++;
n = n.nextSibling;
}
if (offsetA <= offsetC) {
return -1; // before
} else {
return 1; // after
}
}
c = containerA;
while (c && c.parentNode != containerB) {
c = c.parentNode;
}
if (c) {
offsetC = 0;
n = containerB.firstChild;
while (n != c && offsetC < offsetB) {
offsetC++;
n = n.nextSibling;
}
if (offsetC < offsetB) {
return -1; // before
} else {
return 1; // after
}
}
cmnRoot = range._commonAncestorContainer(containerA, containerB);
childA = containerA;
while (childA && childA.parentNode != cmnRoot) {
childA = childA.parentNode;
}
if (!childA) {
childA = cmnRoot;
}
childB = containerB;
while (childB && childB.parentNode != cmnRoot) {
childB = childB.parentNode;
}
if (!childB) {
childB = cmnRoot;
}
if (childA == childB) {
return 0; // equal
}
n = cmnRoot.firstChild;
while (n) {
if (n == childA) {
return -1; // before
}
if (n == childB) {
return 1; // after
}
n = n.nextSibling;
}
return null;
}
function _commonAncestorContainer(containerA, containerB) {
var parentStart = containerA, parentEnd;
while (parentStart) {
parentEnd = containerB;
while (parentEnd && parentStart != parentEnd) {
parentEnd = parentEnd.parentNode;
}
if (parentStart == parentEnd) {
break;
}
parentStart = parentStart.parentNode;
}
if (!parentStart && containerA.ownerDocument) {
return containerA.ownerDocument.documentElement;
}
return parentStart;
}
function _isCollapsed(range) {
return (range.startContainer == range.endContainer && range.startOffset == range.endOffset);
}
function _offsetInCharacters(node) {
switch (node.nodeType) {
case Node.CDATA_SECTION_NODE:
case Node.COMMENT_NODE:
case Node.ELEMENT_NODE:
case Node.PROCESSING_INSTRUCTION_NODE:
return true;
default:
return false;
}
}
function _processContents(range, action) {
try {
var cmnRoot, partialStart = null, partialEnd = null, fragment, n, c, i;
var leftContents, leftParent, leftContentsParent;
var rightContents, rightParent, rightContentsParent;
var next, prev;
var processStart, processEnd;
if (range.collapsed) {
return null;
}
cmnRoot = range.commonAncestorContainer;
if (range.startContainer != cmnRoot) {
partialStart = range.startContainer;
while (partialStart.parentNode != cmnRoot) {
partialStart = partialStart.parentNode;
}
}
if (range.endContainer != cmnRoot) {
partialEnd = range.endContainer;
while (partialEnd.parentNode != cmnRoot) {
partialEnd = partialEnd.parentNode;
}
}
if (action == Range.EXTRACT_CONTENTS || action == Range.CLONE_CONTENTS) {
fragment = range.ownerDocument.createDocumentFragment();
}
if (range.startContainer == range.endContainer) {
switch (range.startContainer.nodeType) {
case Node.CDATA_SECTION_NODE:
case Node.COMMENT_NODE:
case Node.TEXT_NODE:
if (action == Range.EXTRACT_CONTENTS || action == Range.CLONE_CONTENTS) {
c = range.startContainer.cloneNode();
c.deleteData(range.endOffset, range.startContainer.data.length - range.endOffset);
c.deleteData(0, range.startOffset);
fragment.appendChild(c);
}
if (action == Range.EXTRACT_CONTENTS || action == Range.DELETE_CONTENTS) {
range.startContainer.deleteData(range.startOffset, range.endOffset - range.startOffset);
}
break;
case Node.PROCESSING_INSTRUCTION_NODE:
break;
default:
n = range.startContainer.firstChild;
for (i = 0; i < range.startOffset; i++) {
n = n.nextSibling;
}
while (n && i < range.endOffset) {
next = n.nextSibling;
if (action == Range.EXTRACT_CONTENTS) {
fragment.appendChild(n);
} else if (action == Range.CLONE_CONTENTS) {
fragment.appendChild(n.cloneNode());
} else {
range.startContainer.removeChild(n);
}
n = next;
i++;
}
}
range.collapse(true);
return fragment;
}
if (range.startContainer != cmnRoot) {
switch (range.startContainer.nodeType) {
case Node.CDATA_SECTION_NODE:
case Node.COMMENT_NODE:
case Node.TEXT_NODE:
if (action == Range.EXTRACT_CONTENTS || action == Range.CLONE_CONTENTS) {
c = range.startContainer.cloneNode(true);
c.deleteData(0, range.startOffset);
leftContents = c;
}
if (action == Range.EXTRACT_CONTENTS || action == Range.DELETE_CONTENTS) {
range.startContainer.deleteData(range.startOffset, range.startContainer.data.length - range.startOffset);
}
break;
case Node.PROCESSING_INSTRUCTION_NODE:
break;
default:
if (action == Range.EXTRACT_CONTENTS || action == Range.CLONE_CONTENTS) {
leftContents = range.startContainer.cloneNode(false);
}
n = range.startContainer.firstChild;
for (i = 0; i < range.startOffset; i++) {
n = n.nextSibling;
}
while (n && i < range.endOffset) {
next = n.nextSibling;
if (action == Range.EXTRACT_CONTENTS) {
fragment.appendChild(n);
} else if (action == Range.CLONE_CONTENTS) {
fragment.appendChild(n.cloneNode());
} else {
range.startContainer.removeChild(n);
}
n = next;
i++;
}
}
leftParent = range.startContainer.parentNode;
n = range.startContainer.nextSibling;
for(; leftParent != cmnRoot; leftParent = leftParent.parentNode) {
if (action == Range.EXTRACT_CONTENTS || action == Range.CLONE_CONTENTS) {
leftContentsParent = leftParent.cloneNode(false);
leftContentsParent.appendChild(leftContents);
leftContents = leftContentsParent;
}
for (; n; n = next) {
next = n.nextSibling;
if (action == Range.EXTRACT_CONTENTS) {
leftContents.appendChild(n);
} else if (action == Range.CLONE_CONTENTS) {
leftContents.appendChild(n.cloneNode(true));
} else {
leftParent.removeChild(n);
}
}
n = leftParent.nextSibling;
}
}
if (range.endContainer != cmnRoot) {
switch (range.endContainer.nodeType) {
case Node.CDATA_SECTION_NODE:
case Node.COMMENT_NODE:
case Node.TEXT_NODE:
if (action == Range.EXTRACT_CONTENTS || action == Range.CLONE_CONTENTS) {
c = range.endContainer.cloneNode(true);
c.deleteData(range.endOffset, range.endContainer.data.length - range.endOffset);
rightContents = c;
}
if (action == Range.EXTRACT_CONTENTS || action == Range.DELETE_CONTENTS) {
range.endContainer.deleteData(0, range.endOffset);
}
break;
case Node.PROCESSING_INSTRUCTION_NODE:
break;
default:
if (action == Range.EXTRACT_CONTENTS || action == Range.CLONE_CONTENTS) {
rightContents = range.endContainer.cloneNode(false);
}
n = range.endContainer.firstChild;
if (n && range.endOffset) {
for (i = 0; i+1 < range.endOffset; i++) {
next = n.nextSibling;
if (!next) {
break;
}
n = next;
}
for (; n; n = prev) {
prev = n.previousSibling;
if (action == Range.EXTRACT_CONTENTS) {
rightContents.insertBefore(n, rightContents.firstChild);
} else if (action == Range.CLONE_CONTENTS) {
rightContents.insertBefore(n.cloneNode(True), rightContents.firstChild);
} else {
range.endContainer.removeChild(n);
}
}
}
}
rightParent = range.endContainer.parentNode;
n = range.endContainer.previousSibling;
for(; rightParent != cmnRoot; rightParent = rightParent.parentNode) {
if (action == Range.EXTRACT_CONTENTS || action == Range.CLONE_CONTENTS) {
rightContentsParent = rightContents.cloneNode(false);
rightContentsParent.appendChild(rightContents);
rightContents = rightContentsParent;
}
for (; n; n = prev) {
prev = n.previousSibling;
if (action == Range.EXTRACT_CONTENTS) {
rightContents.insertBefore(n, rightContents.firstChild);
} else if (action == Range.CLONE_CONTENTS) {
rightContents.appendChild(n.cloneNode(true), rightContents.firstChild);
} else {
rightParent.removeChild(n);
}
}
n = rightParent.previousSibling;
}
}
if (range.startContainer == cmnRoot) {
processStart = range.startContainer.firstChild;
for (i = 0; i < range.startOffset; i++) {
processStart = processStart.nextSibling;
}
} else {
processStart = range.startContainer;
while (processStart.parentNode != cmnRoot) {
processStart = processStart.parentNode;
}
processStart = processStart.nextSibling;
}
if (range.endContainer == cmnRoot) {
processEnd = range.endContainer.firstChild;
for (i = 0; i < range.endOffset; i++) {
processEnd = processEnd.nextSibling;
}
} else {
processEnd = range.endContainer;
while (processEnd.parentNode != cmnRoot) {
processEnd = processEnd.parentNode;
}
}
if ((action == Range.EXTRACT_CONTENTS || action == Range.CLONE_CONTENTS) && leftContents) {
fragment.appendChild(leftContents);
}
if (processStart) {
for (n = processStart; n && n != processEnd; n = next) {
next = n.nextSibling;
if (action == Range.EXTRACT_CONTENTS) {
fragment.appendChild(n);
} else if (action == Range.CLONE_CONTENTS) {
fragment.appendChild(n.cloneNode(true));
} else {
cmnRoot.removeChild(n);
}
}
}
if ((action == Range.EXTRACT_CONTENTS || action == Range.CLONE_CONTENTS) && rightContents) {
fragment.appendChild(rightContents);
}
if (action == Range.EXTRACT_CONTENTS || action == Range.DELETE_CONTENTS) {
if (!partialStart && !partialEnd) {
range.collapse(true);
} else if (partialStart) {
range.startContainer = partialStart.parentNode;
range.endContainer = partialStart.parentNode;
range.startOffset = range.endOffset = range._nodeIndex(partialStart) + 1;
} else if (partialEnd) {
range.startContainer = partialEnd.parentNode;
range.endContainer = partialEnd.parentNode;
range.startOffset = range.endOffset = range._nodeIndex(partialEnd);
}
}
return fragment;
} catch (e) {
return null;
};
}
function _nodeIndex(refNode) {
var nodeIndex = 0;
while (refNode.previousSibling) {
nodeIndex++;
refNode = refNode.previousSibling;
}
return nodeIndex;
}
return {
setStart: setStart,
setEnd: setEnd,
setStartBefore: setStartBefore,
setStartAfter: setStartAfter,
setEndBefore: setEndBefore,
setEndAfter: setEndAfter,
collapse: collapse,
selectNode: selectNode,
selectNodeContents: selectNodeContents,
compareBoundaryPoints: compareBoundaryPoints,
deleteContents: deleteContents,
extractContents: extractContents,
cloneContents: cloneContents,
insertNode: insertNode,
surroundContents: surroundContents,
cloneRange: cloneRange,
toString: toString,
detach: detach,
_commonAncestorContainer: _commonAncestorContainer
};
})());
}
if (!window.getSelection) {
window.getSelection = function() {
return Selection.getInstance();
};
SelectionImpl = function() {
this.anchorNode = null;
this.anchorOffset = 0;
this.focusNode = null;
this.focusOffset = 0;
this.isCollapsed = true;
this.rangeCount = 0;
this.ranges = [];
}
Object.extend(SelectionImpl.prototype, (function() {
function addRange(r) {
return true;
}
function collapse() {
return true;
}
function collapseToStart() {
return true;
}
function collapseToEnd() {
return true;
}
function getRangeAt() {
return true;
}
function removeAllRanges() {
this.anchorNode = null;
this.anchorOffset = 0;
this.focusNode = null;
this.focusOffset = 0;
this.isCollapsed = true;
this.rangeCount = 0;
this.ranges = [];
}
function _addRange(r) {
if (r.startContainer.nodeType != Node.TEXT_NODE) {
var start = this._getRightStart(r.startContainer);
var startOffset = 0;
} else {
var start = r.startContainer;
var startOffset = r.startOffset;
}
if (r.endContainer.nodeType != Node.TEXT_NODE) {
var end = this._getRightEnd(r.endContainer);
var endOffset = end.data.length;
} else {
var end = r.endContainer;
var endOffset = r.endOffset;
}
var rStart = this._selectStart(start, startOffset);
var rEnd = this._selectEnd(end,endOffset);
rStart.setEndPoint('EndToStart', rEnd);
rStart.select();
document.selection._selectedRange = r;
}
function _getRightStart(start, offset) {
if (start.nodeType != Node.TEXT_NODE) {
if (start.nodeType == Node.ELEMENT_NODE) {
start = start.childNodes(offset);
}
return getNextTextNode(start);
} else {
return null;
}
}
function _getRightEnd(end, offset) {
if (end.nodeType != Node.TEXT_NODE) {
if (end.nodeType == Node.ELEMENT_NODE) {
end = end.childNodes(offset);
}
return getPreviousTextNode(end);
} else {
return null;
}
}
function _selectStart(start, offset) {
var r = document.body.createTextRange();
if (start.nodeType == Node.TEXT_NODE) {
var moveCharacters = offset, node = start;
var moveToNode = null, collapse = true;
while (node.previousSibling) {
switch (node.previousSibling.nodeType) {
case Node.ELEMENT_NODE:
moveToNode = node.previousSibling;
collapse = false;
break;
case Node.TEXT_NODE:
moveCharacters += node.previousSibling.data.length;
}
if (moveToNode != null) {
break;
}
node = node.previousSibling;
}
if (moveToNode == null) {
moveToNode = start.parentNode;
}
r.moveToElementText(moveToNode);
r.collapse(collapse);
r.move('Character', moveCharacters);
return r;
} else {
return null;
}
}
function _selectEnd(end, offset) {
var r = document.body.createTextRange(), node = end;
if (end.nodeType == 3) {
var moveCharacters = end.data.length - offset;
var moveToNode = null, collapse = false;
while (node.nextSibling) {
switch (node.nextSibling.nodeType) {
case Node.ELEMENT_NODE:
moveToNode = node.nextSibling;
collapse = true;
break;
case Node.TEXT_NODE:
moveCharacters += node.nextSibling.data.length;
break;
}
if (moveToNode != null) {
break;
}
node = node.nextSibling;
}
if (moveToNode == null) {
moveToNode = end.parentNode;
collapse = false;
}
switch (moveToNode.nodeName.toLowerCase()) {
case 'p':
case 'div':
case 'h1':
case 'h2':
case 'h3':
case 'h4':
case 'h5':
case 'h6':
moveCharacters++;
}
r.moveToElementText(moveToNode);
r.collapse(collapse);
r.move('Character', -moveCharacters);
return r;
}
return null;
}
function getPreviousTextNode(node) {
var stack = [];
var current = null;
while (node) {
stack = [];
current = node;
while (current) {
while (current) {
if (current.nodeType == 3 && current.data.replace(/^\s+|\s+$/, '').length) {
return current;
}
if (current.previousSibling) {
stack.push (current.previousSibling);
}
current = current.lastChild;
}
current = stack.pop();
}
node = node.previousSibling;
}
return null;
}
function getNextTextNode(node) {
var stack = [];
var current = null;
while (node) {
stack = [];
current = node;
while (current) {
while (current) {
if (current.nodeType == 3 && current.data.replace(/^\s+|\s+$/, '').length) {
return current;
}
if (current.nextSibling) {
stack.push (current.nextSibling);
}
current = current.firstChild;
}
current = stack.pop();
}
node = node.nextSibling;
}
return null;
}
return {
removeAllRanges: removeAllRanges,
_addRange: _addRange,
_getRightStart: _getRightStart,
_getRightEnd: _getRightEnd,
_selectStart: _selectStart,
_selectEnd: _selectEnd
};
})());
Selection = new function() {
var instance = null;
this.getInstance = function() {
if (instance == null) {
return (instance = new SelectionImpl());
} else {
return instance;
}
};
};
}
Object.extend(Range.prototype, (function() {
function getNode() {
var node = this.commonAncestorContainer;
if (this.startContainer == this.endContainer)
if (this.startOffset - this.endOffset < 2)
node = this.startContainer.childNodes[this.startOffset];
while (node.nodeType == Node.TEXT_NODE)
node = node.parentNode;
return node;
}
return {
getNode: getNode
};
})());
WysiHat.Selection = Class.create((function() {
function initialize(editor) {
this.window = editor.getWindow();
this.document = editor.getDocument();
if (Prototype.Browser.IE) {
editor.observe('wysihat:cursormove', saveRange.bind(this));
editor.observe('wysihat:focus', restoreRange);
}
}
function getSelection() {
return this.window.getSelection ? this.window.getSelection() : this.document.selection;
}
function getRange() {
var range = null, selection = this.getSelection();
try {
if (selection.getRangeAt)
range = selection.getRangeAt(0);
else
range = selection.createRange();
} catch(e) { return null; }
if (Prototype.Browser.WebKit) {
range.setStart(selection.baseNode, selection.baseOffset);
range.setEnd(selection.extentNode, selection.extentOffset);
}
return range;
}
function selectNode(node) {
var selection = this.getSelection();
if (Prototype.Browser.IE) {
var range = createRangeFromElement(this.document, node);
range.select();
} else if (Prototype.Browser.WebKit) {
selection.setBaseAndExtent(node, 0, node, node.innerText.length);
} else if (Prototype.Browser.Opera) {
range = this.document.createRange();
range.selectNode(node);
selection.removeAllRanges();
selection.addRange(range);
} else {
var range = createRangeFromElement(this.document, node);
selection.removeAllRanges();
selection.addRange(range);
}
}
function getNode() {
var nodes = null, candidates = [], children, el;
var range = this.getRange();
if (!range)
return null;
var parent;
if (range.parentElement)
parent = range.parentElement();
else
parent = range.commonAncestorContainer;
if (parent) {
while (parent.nodeType != 1) parent = parent.parentNode;
if (parent.nodeName.toLowerCase() != "body") {
el = parent;
do {
el = el.parentNode;
candidates[candidates.length] = el;
} while (el.nodeName.toLowerCase() != "body");
}
children = parent.all || parent.getElementsByTagName("*");
for (var j = 0; j < children.length; j++)
candidates[candidates.length] = children[j];
nodes = [parent];
for (var ii = 0, r2; ii < candidates.length; ii++) {
r2 = createRangeFromElement(this.document, candidates[ii]);
if (r2 && compareRanges(range, r2))
nodes[nodes.length] = candidates[ii];
}
}
return nodes.first();
}
function createRangeFromElement(document, node) {
if (document.body.createTextRange) {
var range = document.body.createTextRange();
range.moveToElementText(node);
} else if (document.createRange) {
var range = document.createRange();
range.selectNodeContents(node);
}
return range;
}
function compareRanges(r1, r2) {
if (r1.compareEndPoints) {
return !(
r2.compareEndPoints('StartToStart', r1) == 1 &&
r2.compareEndPoints('EndToEnd', r1) == 1 &&
r2.compareEndPoints('StartToEnd', r1) == 1 &&
r2.compareEndPoints('EndToStart', r1) == 1
||
r2.compareEndPoints('StartToStart', r1) == -1 &&
r2.compareEndPoints('EndToEnd', r1) == -1 &&
r2.compareEndPoints('StartToEnd', r1) == -1 &&
r2.compareEndPoints('EndToStart', r1) == -1
);
} else if (r1.compareBoundaryPoints) {
return !(
r2.compareBoundaryPoints(0, r1) == 1 &&
r2.compareBoundaryPoints(2, r1) == 1 &&
r2.compareBoundaryPoints(1, r1) == 1 &&
r2.compareBoundaryPoints(3, r1) == 1
||
r2.compareBoundaryPoints(0, r1) == -1 &&
r2.compareBoundaryPoints(2, r1) == -1 &&
r2.compareBoundaryPoints(1, r1) == -1 &&
r2.compareBoundaryPoints(3, r1) == -1
);
}
return null;
};
function setBookmark() {
var bookmark = this.document.getElementById('bookmark');
if (bookmark)
bookmark.parentNode.removeChild(bookmark);
bookmark = this.document.createElement('span');
bookmark.id = 'bookmark';
bookmark.innerHTML = ' ';
if (Prototype.Browser.IE) {
var range = this.document.selection.createRange();
var parent = this.document.createElement('div');
parent.appendChild(bookmark);
range.collapse();
range.pasteHTML(parent.innerHTML);
}
else {
var range = this.getRange();
range.insertNode(bookmark);
}
}
function moveToBookmark() {
var bookmark = this.document.getElementById('bookmark');
if (!bookmark)
return;
if (Prototype.Browser.IE) {
var range = this.getRange();
range.moveToElementText(bookmark);
range.collapse();
range.select();
} else if (Prototype.Browser.WebKit) {
var selection = this.getSelection();
selection.setBaseAndExtent(bookmark, 0, bookmark, 0);
} else {
var range = this.getRange();
range.setStartBefore(bookmark);
}
bookmark.parentNode.removeChild(bookmark);
}
var savedRange = null;
function saveRange() {
savedRange = this.getRange();
}
function restoreRange() {
if (savedRange) savedRange.select();
}
return {
initialize: initialize,
getSelection: getSelection,
getRange: getRange,
getNode: getNode,
selectNode: selectNode,
setBookmark: setBookmark,
moveToBookmark: moveToBookmark,
restore: restoreRange
};
})());
WysiHat.Toolbar = Class.create((function() {
function initialize(editor) {
this.editor = editor;
this.element = this.createToolbarElement();
}
function createToolbarElement() {
var toolbar = new Element('div', { 'class': 'editor_toolbar' });
this.editor.insert({before: toolbar});
return toolbar;
}
function addButtonSet(set) {
var toolbar = this;
$A(set).each(function(button){
toolbar.addButton(button);
});
}
function addButton(options, handler) {
options = $H(options);
if (!options.get('name'))
options.set('name', options.get('label').toLowerCase());
var name = options.get('name');
var button = this.createButtonElement(this.element, options);
var handler = this.buttonHandler(name, options);
this.observeButtonClick(button, handler);
var handler = this.buttonStateHandler(name, options);
this.observeStateChanges(button, name, handler)
}
function createButtonElement(toolbar, options) {
var button = Element('a', {
'class': 'button', 'href': '#'
});
button.update('' + options.get('label') + '');
button.addClassName(options.get('name'));
toolbar.appendChild(button);
return button;
}
function buttonHandler(name, options) {
if (options.handler)
return options.handler;
else if (options.get('handler'))
return options.get('handler');
else
return function(editor) { editor.execCommand(name); };
}
function observeButtonClick(element, handler) {
var toolbar = this;
element.observe('click', function(event) {
handler(toolbar.editor);
toolbar.editor.fire("wysihat:change");
toolbar.editor.fire("wysihat:cursormove");
Event.stop(event);
});
}
function buttonStateHandler(name, options) {
if (options.query)
return options.query;
else if (options.get('query'))
return options.get('query');
else
return function(editor) { return editor.queryCommandState(name); };
}
function observeStateChanges(element, name, handler) {
var toolbar = this;
var previousState = false;
toolbar.editor.observe("wysihat:cursormove", function(event) {
var state = handler(toolbar.editor);
if (state != previousState) {
previousState = state;
toolbar.updateButtonState(element, name, state);
}
});
}
function updateButtonState(element, name, state) {
if (state)
element.addClassName('selected');
else
element.removeClassName('selected');
}
return {
initialize: initialize,
createToolbarElement: createToolbarElement,
addButtonSet: addButtonSet,
addButton: addButton,
createButtonElement: createButtonElement,
buttonHandler: buttonHandler,
observeButtonClick: observeButtonClick,
buttonStateHandler: buttonStateHandler,
observeStateChanges: observeStateChanges,
updateButtonState: updateButtonState
};
})());
WysiHat.Toolbar.ButtonSets = {};
WysiHat.Toolbar.ButtonSets.Basic = $A([
{ label: "Bold" },
{ label: "Underline" },
{ label: "Italic" }
]);