(function($) { var BUE = window.BUE = window.BUE || {preset: {}, templates: {}, instances: [], preprocess: {}, postprocess: {}}; // Get editor settings from Drupal.settings and process preset textareas. BUE.behavior = function(context) { var set = Drupal.settings.BUE || null, tpls = BUE.templates, pset = BUE.preset; if (set) { $.each(set.templates, function (id, tpl) { tpls[id] = tpls[id] || $.extend({}, tpl); }); $.extend(pset, set.preset); set.templates = {}; set.preset = {}; } $.each(pset, function (tid, tplid) { BUE.processTextarea($('#'+ tid, context).get(0), tplid); }); // Fix enter key on textfields triggering button click. $('input:text', context).bind('keydown.bue', BUE.eFixEnter); }; // Integrate editor template into textarea T BUE.processTextarea = function (T, tplid) { if (!T || !BUE.templates[tplid] || !(T = $(T).filter('textarea')[0])) return false; // Check visibility on the element-level only. if (T.style.display == 'none' || T.style.visibility == 'hidden') return false; if (T.bue) return T.bue; var E = new BUE.instance(T, tplid); !BUE.active || BUE.active.textArea.disabled ? E.activate() : E.accesskeys(false); // Pre&post process. for (var i in BUE.preprocess) BUE.preprocess[i](E, $); for (var i in BUE.postprocess) BUE.postprocess[i](E, $); return E; }; // Create an editor instance BUE.instance = function (T, tplid) { var i = BUE.instances.length, E = T.bue = BUE.instances[i] = this; E.index = i; E.textArea = T; E.tplid = tplid; E.tpl = BUE.templates[tplid]; E.bindex = null; E.safeToPreview = T.value.indexOf('<') == -1; E.UI = BUE.$html(BUE.theme(tplid).replace(/\%n/g, i)).insertBefore(T).bind('keydown.bue', BUE.eUIKeydown); E.buttons = $('.bue-button', E.UI).each(function(i, B) { var arr = B.id.split('-'); $($.extend(B, {eindex: arr[1], bid: arr[3], bindex: i})).bind('click.bue', BUE.eButtonClick); }).get(); $(T).bind('focus.bue', BUE.eTextareaFocus); }; // Execute button's click event BUE.buttonClick = function (eindex, bindex) { try { var E = BUE.instances[eindex].activate(); var domB = E.buttons[bindex]; var tplB = E.tpl.buttons[domB.bid]; var content = tplB[1]; E.bindex = bindex; E.dialog.close(); if (tplB[4]) { tplB[4](E, $); } else if (content) { var arr = content.split('%TEXT%'); if (arr.length == 2) E.tagSelection(arr[0], arr[1]); else E.replaceSelection(arr.length == 1 ? content : arr.join(E.getSelection()), 'end'); } !(domB.pops || domB.stayClicked) && E.focus(); } catch (e) {alert(e.name +': '+ e.message);} return false; }; // Return html for editor templates. BUE.theme = function (tplid) { var tpl = BUE.templates[tplid] || {html: ''}, html = '', sprite; if (typeof tpl.html == 'string') return tpl.html; // Load sprite if (sprite = tpl.sprite) { var surl = (new Image()).src = sprite.url, sunit = sprite.unit, sx1 = sprite.x1; $(document.body).append(''); } var access = $.browser.mozilla && 'Shift + Alt' || ($.browser.msie || window.chrome) && 'Alt', title, content, icon, key, func; // Create html for buttons. B(0-title, 1-content, 2-icon or caption, 3-accesskey) and 4-function for js buttons for (var B, isimg, src, type, btype, attr, alt, i = 0, s = 0; B = tpl.buttons[i]; i++) { // Empty button. if (B.length == 0) { s++; continue; } title = B[0], content = B[1], icon = B[2], key = B[3], func = null; // Set button function if (content.substr(0, 3) == 'js:') { func = B[4] = new Function('E', '$', content.substr(3)); } isimg = (/\.(png|gif|jpg)$/i).test(icon); // Theme button. if (title.substr(0, 4) == 'tpl:') { html += func ? (func(null, $) || '') : content; html += icon ? (''+ (isimg ? '' : icon) +'') : ''; continue; } // Text button if (!isimg) { type = 'button', btype = 'text', attr = 'value="'+ icon +'"'; } else { type = 'image'; // Sprite button if (sprite) { btype = 'sprite', attr = 'src="'+ sx1 +'" style="background-position: -'+ (s * sunit) +'px 0;"'; s++; } // Image button else { btype = 'image', attr = 'src="'+ tpl.iconpath +'/'+ icon +'"'; } } alt = title + (key ? '('+ key +')' : ''); title += access && key ? ' ('+ access +' + '+ key +')' : ''; html += ''; } return tpl.html = ''; }; // Cross browser selection handling. 0-1=All, 2=IE, 3=Opera BUE.mode = (window.getSelection || document.getSelection) ? ($.browser.opera ? 3 : 1) : (document.selection && document.selection.createRange ? 2 : 0 ); // New line standardization. At least make them represented by a single char. BUE.text = BUE.processText = BUE.mode < 2 ? function (s) {return s.toString()} : function (s) {return s.toString().replace(/\r\n/g, '\n')}; // Create selection in a textarea BUE.selMake = BUE.mode == 2 ? function (T, start, end) { range = T.createTextRange(); range.collapse(); range.moveEnd('character', end); range.moveStart('character', start); range.select(); } : BUE.mode == 3 ? function (T, start, end) { var text = BUE.text(T.value), i = text.substring(0, start).split('\n').length, j = text.substring(start, end).split('\n').length; T.setSelectionRange(start + i -1 , end + i + j - 2); } : function (T, start, end) { T.setSelectionRange(start, end); }; // Return the selection coordinates in a textarea BUE.selPos = BUE.mode == 2 ? function (T) { T.focus(); var orange = document.selection.createRange(), range = orange.duplicate(); range.moveToElementText(T); range.setEndPoint('EndToEnd', orange); var otext = orange.text, olen = otext.length, prelen = range.text.length - olen; var start = prelen - (T.value.substr(0, prelen).split('\r\n').length - 1); start && range.moveStart('character', start); for (; range.compareEndPoints('StartToStart', orange) < 0; start++) { range.moveStart('character', 1); } var end = start + olen - (otext.split('\r\n').length - 1); for (; range.compareEndPoints('EndToStart', orange) > 0; end++) { range.moveEnd('character', -1); if (range.text.length != olen) break; } return {start: start, end: end}; } : BUE.mode == 3 ? function (T) { var start = T.selectionStart || 0, end = T.selectionEnd || 0, val = T.value; var i = val.substring(0, start).split('\r\n').length, j = val.substring(start, end).split('\r\n').length; return {start: start - i + 1, end: end - i - j + 2}; } : function (T) { return {start: T.selectionStart || 0, end: T.selectionEnd || 0} }; // Enter key fixer for text fields BUE.eFixEnter = function(e) { e.keyCode == 13 && (BUE.enterKeyTime = new Date()); }; // Button click handler BUE.eButtonClick = function(e) { return !(BUE.enterKeyTime && new Date() - BUE.enterKeyTime < 500) && BUE.buttonClick(this.eindex, this.bindex); }; // Textarea focus handler BUE.eTextareaFocus = function(e) { this.bue && !this.bue.dialog.esp && this.bue.activate(); }; // UI keydown handler BUE.eUIKeydown = function(e) { if (e.keyCode != 37 && e.keyCode != 39) return; var len, E = BUE.instances[this.id.split('-').pop()]; if (E && (len = E.buttons.length)) { var A = document.activeElement, i = Math.max(-1, (A && A.eindex == E.index ? A.bindex : -1) + e.keyCode - 38) + len; E.buttons[i % len].focus(); } }; // Html 2 jquery. Faster than $(html) BUE.$html = function(s){ var div = document.createElement('div'); div.innerHTML = s; return $(div.childNodes); }; // Backward compatibility. window.editor = window.editor || BUE; // Initiate bueditor $(document).ready(function () { (Drupal.behaviors.BUE = BUE.behavior)(document); }); })(jQuery); // Bueditor instance methods (function(E) { // Focus on editor textarea. E.focus = function () { this.textArea.focus(); return this; }; // Return textarea content E.getContent = function () { return BUE.text(this.textArea.value); }; // Set textarea content E.setContent = function (content) { var T = this.textArea, st = T.scrollTop; T.value = content; T.scrollTop = st; return this; }; // Return selected text E.getSelection = function () { var pos = this.posSelection(); return this.getContent().substring(pos.start, pos.end); }; // Replace selected text E.replaceSelection = function (txt, cursor) { var E = this, pos = E.posSelection(), content = E.getContent(), txt = BUE.text(txt); var end = cursor == 'start' ? pos.start : pos.start+txt.length, start = cursor == 'end' ? end : pos.start; E.setContent(content.substr(0, pos.start) + txt + content.substr(pos.end)); return E.makeSelection(start, end); }; // Wrap selected text. E.tagSelection = function (left, right, cursor) { var E = this, pos = E.posSelection(), content = E.getContent(); var left = BUE.text(left), right = BUE.text(right), llen = left.length; var end = cursor == 'start' ? pos.start+llen : pos.end+llen, start = cursor == 'end' ? end : pos.start+llen; E.setContent(content.substr(0, pos.start) + left + content.substring(pos.start, pos.end) + right + content.substr(pos.end)); return E.makeSelection(start, end); }; // Make a new selection E.makeSelection = function (start, end) { var E = this; if (end === undefined || end < start) end = start; BUE.selMake(E.textArea, start, end); E.dialog.esp && (E.dialog.esp = {start: start, end: end}) || E.focus(); return E; }; // Return selection coordinates. E.posSelection = function () { return this.dialog.esp || BUE.selPos(this.textArea); }; // Enable/disable editor buttons E.buttonsDisabled = function (state, bindex) { for (var B, i=0; B = this.buttons[i]; i++) { B.disabled = i == bindex ? !state : state; } return this; }; // Make active/custom button stay clicked E.stayClicked = function (state, bindex) { var B = this.buttons[bindex === undefined ? this.bindex : bindex]; B && jQuery(B)[state ? 'addClass' : 'removeClass']('stay-clicked') && (B.stayClicked = state || false); return this; }; // Enable/disable button accesskeys E.accesskeys = function (state) { for (var B, i=0; B = this.buttons[i]; i++) { B.accessKey = state ? this.tpl.buttons[B.bid][3] : ''; } return this; }; // Activate editor and make it BUE.active E.activate = function() { var E = this, A = BUE.active || null; if (E == A) return E; A && A.accesskeys(false) && E.accesskeys(true); return BUE.active = E; }; // Reserve dialog and quickPop var pop = E.dialog = E.quickPop = BUE.dialog = BUE.quickPop = {}; pop.open = pop.close = function(){}; })(BUE.instance.prototype);