--- /dev/null
+/**
+ * Provides the javascript for managing addressbooks.
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
+ *
+ * @category Horde
+ * @package Core
+ */
+
+var HordeAddressbooksPrefs = {
+
+ // Variables set by other code: fields, nonetext
+
+ updateSearchFields: function()
+ {
+ var tmp,
+ sv = $F('selected_sources'),
+ sf = $('search_fields_select');
+
+ sf.childElements().invoke('remove');
+
+ if (sv.size() == 1) {
+ tmp = this.fields.get(sv.first());
+ tmp.entries.each(function(o) {
+ var opt = new Option(o.label, o.name);
+ if (tmp.selected.include(o.name)) {
+ opt.selected = true;
+ }
+ sf.insert(opt);
+ });
+ } else {
+ tmp = new Option(this.nonetext, '');
+ tmp.disabled = true;
+ sf.insert(tmp);
+ }
+ },
+
+ changeSearchFields: function()
+ {
+ var tmp,
+ out = $H(),
+ sv = $F('selected_sources');
+
+ if (sv.size() == 1) {
+ tmp = this.fields.get(sv.first());
+ tmp.selected = $F('search_fields_select');
+ this.fields.set(sv.first(), tmp);
+
+ this.fields.each(function(f) {
+ out.set(f.key, f.value.selected);
+ });
+
+ $('search_fields').setValue(Object.toJSON(out));
+ }
+ },
+
+ onDomLoad: function()
+ {
+ this.fields = $H(this.fields);
+
+ this.updateSearchFields();
+
+ $('search_fields_select').observe('change', this.changeSearchFields.bind(this));
+ $('selected_sources').observe('change', this.updateSearchFields.bind(this));
+ $('selected_sources').observe('HordeSourceSelectPrefs:remove', this.updateSearchFields.bind(this));
+ }
+
+};
+
+document.observe('dom:loaded', HordeAddressbooksPrefs.onDomLoad.bind(HordeAddressbooksPrefs));
--- /dev/null
+/**
+ * Provides the javascript for managing alarms.
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
+ *
+ * @category Horde
+ * @package Core
+ */
+
+var HordeAlarmPrefs = {
+
+ // Variables defaulting to null: pref
+
+ updateParams: function()
+ {
+ [ 'notify', 'mail', 'sms' ].each(function(method) {
+ var p = $(method + 'Params');
+ if (p) {
+ if ($(this.pref).getValue().include(method)) {
+ p.show();
+ } else {
+ p.hide();
+ }
+ }
+ }, this);
+ },
+
+ onDomLoad: function()
+ {
+ $(this.pref).observe('change', this.updateParams.bind(this));
+ this.updateParams();
+ }
+
+};
+
+document.observe('dom:load', HordeAlarmPrefs.onDomLoad.bind(HordeAlarmPrefs));
--- /dev/null
+/**
+ * autocomplete.js - A javascript library which implements autocomplete.
+ * Requires prototype.js v1.6.0.2+, scriptaculous v1.8.0+ (effects.js),
+ * and keynavlist.js.
+ *
+ * Adapted from script.aculo.us controls.js v1.8.0
+ * (c) 2005-2007 Thomas Fuchs, Ivan Krstic, and Jon Tirsen
+ * Contributors: Richard Livsey, Rahul Bhargava, Rob Wills
+ * http://script.aculo.us/
+ *
+ * The original script was freely distributable under the terms of an
+ * MIT-style license.
+ *
+ * Usage:
+ * ------
+ * TODO: options = autoSelect, frequency, minChars, onSelect, onShow, onType,
+ * paramName, tokens
+ *
+ * Copyright 2007-2010 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
+ *
+ * @category Horde
+ * @package Core
+ */
+
+var Autocompleter = {};
+
+Autocompleter.Base = Class.create({
+ baseInitialize: function(elt, opts)
+ {
+ this.elt = elt = $(elt);
+ this.changed = false;
+ this.observer = null;
+ this.oldval = $F(elt);
+ this.opts = Object.extend({
+ frequency: 0.4,
+ indicator: null,
+ minChars: 1,
+ onSelect: Prototype.K,
+ onShow: Prototype.K,
+ onType: Prototype.K,
+ filterCallback: Prototype.K,
+ paramName: elt.readAttribute('name'),
+ tokens: [],
+ keydownObserver: this.elt
+ }, (this._setOptions ? this._setOptions(opts) : (opts || {})));
+
+ // Force carriage returns as token delimiters anyway
+ if (!this.opts.tokens.include('\n')) {
+ this.opts.tokens.push('\n');
+ }
+
+ elt.writeAttribute('autocomplete', 'off');
+ elt.observe("keydown", this._onKeyDown.bindAsEventListener(this));
+ },
+
+ _onKeyDown: function(e)
+ {
+ var a = document.activeElement;
+
+ if (Object.isUndefined(a) || a == this.elt) {
+ switch (e.keyCode) {
+ case 0:
+ if (!Prototype.Browser.WebKit) {
+ break;
+ }
+ // Fall-through
+
+ // Ignore events caught by KevNavList
+ case Event.KEY_DOWN:
+ case Event.KEY_ESC:
+ case Event.KEY_RETURN:
+ case Event.KEY_TAB:
+ case Event.KEY_UP:
+ return;
+ }
+
+ this.changed = true;
+
+ if (this.observer) {
+ clearTimeout(this.observer);
+ }
+
+ this.observer = this.onObserverEvent.bind(this).delay(this.opts.frequency);
+ }
+ },
+
+ updateChoices: function(choices)
+ {
+ var a = document.activeElement, c = [], re;
+
+ if (this.changed ||
+ (Object.isUndefined(a) || a != this.elt)) {
+ return;
+ }
+
+ if (this.opts.indicator) {
+ $(this.opts.indicator).hide();
+ }
+
+ choices = this.opts.filterCallback(choices);
+ if (!choices.size()) {
+ if (this.knl) {
+ this.knl.hide();
+ }
+ this.getNewVal(this.lastentry);
+ } else if (choices.size() == 1 && this.opts.autoSelect) {
+ this.onSelect(choices.first());
+ if (this.knl) {
+ this.knl.hide();
+ }
+ } else {
+ re = new RegExp("(" + this.getToken() + ")", "i");
+
+ choices.each(function(n) {
+ c.push({
+ l: n.escapeHTML().gsub(re, '<strong>#{1}</strong>'),
+ v: n
+ });
+ });
+
+ if (!this.knl) {
+ this.knl = new KeyNavList(this.elt, { onChoose: this.onSelect.bind(this),
+ onShow: this.opts.onShow.bind(this),
+ domParent: this.opts.domParent,
+ keydownObserver: this.opts.keydownObserver});
+ }
+
+ this.knl.show(c);
+ }
+ },
+
+ onObserverEvent: function()
+ {
+ this.changed = false;
+
+ var entry = this.getToken();
+
+ if (entry.length >= this.opts.minChars) {
+ entry = this.opts.onType(entry);
+ }
+
+ if (entry.length) {
+ if (this.opts.indicator) {
+ $(this.opts.indicator).show();
+ }
+ this.lastentry = entry;
+ this.getUpdatedChoices(entry);
+ } else if (this.knl) {
+ this.knl.hide();
+ }
+ },
+
+ getToken: function()
+ {
+ var bounds = this.getTokenBounds();
+ return $F(this.elt).substring(bounds[0], bounds[1]).strip();
+ },
+
+ getTokenBounds: function()
+ {
+ var diff, i, index, l, offset, tp,
+ t = this.opts.tokens,
+ value = $F(this.elt),
+ nextTokenPos = value.length,
+ prevTokenPos = -1,
+ boundary = Math.min(nextTokenPos, this.oldval.length);
+
+ if (value.strip().empty()) {
+ return [ -1, 0 ];
+ }
+
+ diff = boundary;
+ for (i = 0; i < boundary; ++i) {
+ if (value[i] != this.oldval[i]) {
+ diff = i;
+ break;
+ }
+ }
+
+ offset = (diff == this.oldval.length ? 1 : 0);
+
+ for (index = 0, l = t.length; index < l; ++index) {
+ tp = value.lastIndexOf(t[index], diff + offset - 1);
+ if (tp > prevTokenPos) {
+ prevTokenPos = tp;
+ }
+ tp = value.indexOf(t[index], diff + offset);
+ if (tp != -1 && tp < nextTokenPos) {
+ nextTokenPos = tp;
+ }
+ }
+ return [ prevTokenPos + 1, nextTokenPos ];
+ },
+
+ onSelect: function(entry)
+ {
+ if (entry) {
+ this.elt.setValue(this.opts.onSelect(this.getNewVal(entry))).focus();
+ if (this.knl) {
+ this.knl.markSelected();
+ }
+ }
+ },
+
+ getNewVal: function(entry)
+ {
+ var bounds = this.getTokenBounds(), newval, v, ws;
+
+ if (bounds[0] == -1) {
+ newval = entry;
+ } else {
+ v = $F(this.elt);
+ newval = v.substr(0, bounds[0]);
+ ws = v.substr(bounds[0]).match(/^\s+/);
+ if (ws) {
+ newval += ws[0];
+ }
+ newval += entry + v.substr(bounds[1]);
+ }
+
+ this.oldval = newval;
+
+ return newval;
+ }
+
+});
+
+Ajax.Autocompleter = Class.create(Autocompleter.Base, {
+
+ initialize: function(element, url, opts)
+ {
+ this.baseInitialize(element, opts);
+ this.opts = Object.extend(this.opts, {
+ asynchronous: true,
+ onComplete: this._onComplete.bind(this),
+ defaultParams: $H(this.opts.parameters)
+ });
+ this.url = url;
+ this.cache = $H();
+ },
+
+ getUpdatedChoices: function(t)
+ {
+ var p,
+ o = this.opts,
+ c = this.cache.get(t);
+
+ if (c) {
+ this.updateChoices(c);
+ } else {
+ p = Object.clone(o.defaultParams);
+ p.set(o.paramName, t);
+ o.parameters = p.toQueryString();
+ new Ajax.Request(this.url, o);
+ }
+ },
+
+ _onComplete: function(request)
+ {
+ this.updateChoices(this.cache.set(this.getToken(), request.responseJSON));
+ }
+});
+
+Autocompleter.Local = Class.create(Autocompleter.Base, {
+
+ initialize: function(element, arr, opts)
+ {
+ this.baseInitialize(element, opts);
+ this.opts.arr = arr;
+ },
+
+ getUpdatedChoices: function(entry)
+ {
+ var choices,
+ csort = [],
+ entry_len = entry.length,
+ i = 0,
+ o = this.opts;
+
+ if (o.ignoreCase) {
+ entry = entry.toLowerCase();
+ }
+
+ choices = o.arr.findAll(function(t) {
+ if (i == o.choices) {
+ throw $break;
+ }
+
+ if (o.ignoreCase) {
+ t = t.toLowerCase();
+ }
+ t = t.unescapeHTML();
+
+ var pos = t.indexOf(entry);
+ if (pos != -1 &&
+ ((pos == 0 && t.length != entry_len) ||
+ (entry_len >= o.partialChars &&
+ o.partialSearch &&
+ (o.fullSearch || /\s/.test(t.substr(pos - 1, 1)))))) {
+ ++i;
+ return true;
+ }
+ return false;
+ }, this);
+
+ if (o.score) {
+ choices.each(function(term) {
+ csort.push({ s: LiquidMetal.score(term, entry), t: term });
+ }.bind(this));
+ // Sort the terms
+ csort.sort(function(a, b) { return b.s - a.s; });
+ choices = csort.pluck('t');
+ }
+
+ this.updateChoices(choices);
+ },
+
+ _setOptions: function(opts)
+ {
+ return Object.extend({
+ choices: 10,
+ fullSearch: false,
+ ignoreCase: true,
+ partialChars: 2,
+ partialSearch: true,
+ score: false
+ }, opts || {});
+ }
+
+});
--- /dev/null
+/**
+ * Horde javascript calendar widget.
+ *
+ * Custom Events:
+ * --------------
+ * Horde_Calendar:select
+ * params: Date object
+ * Fired when a date is selected.
+ *
+ * Horde_Calendar:selectMonth
+ * params: Date object
+ * Fired when a month is selected.
+ *
+ * Horde_Calendar:selectWeek
+ * params: Date object
+ * Fired when a week is selected.
+ *
+ * Horde_Calendar:selectYear
+ * params: Date object
+ * Fired when a year is selected.
+ *
+ * @category Horde
+ * @package Core
+ */
+
+var Horde_Calendar =
+{
+ // Variables set externally: click_month, click_week, click_year,
+ // firstDayOfWeek, fullweekdays, months,
+ // weekdays
+ // Variables defaulting to null: date, month, openDate, trigger, year
+
+ open: function(trigger, data)
+ {
+ var date = data ? data : new Date();
+
+ this.openDate = date.getTime();
+ this.trigger = $(trigger);
+ this.draw(this.openDate, true);
+ },
+
+ /**
+ * Days in the month (month is a zero-indexed javascript month).
+ */
+ daysInMonth: function(month, year)
+ {
+ switch (month) {
+ case 3:
+ case 5:
+ case 8:
+ case 10:
+ return 30;
+
+ case 1:
+ return (year % 4 == 0 && (year % 100 != 0 || year % 400 == 0))
+ ? 29
+ : 28;
+
+ default:
+ return 31;
+ }
+ },
+
+ weeksInMonth: function(month, year)
+ {
+ var firstWeekDays, weeks,
+ firstOfMonth = (new Date(year, month, 1)).getDay();
+
+ if ((this.firstDayOfWeek == 1 && firstOfMonth == 0) ||
+ (this.firstDayOfWeek == 0 && firstOfMonth == 6)) {
+ firstWeekDays = 7 - firstOfMonth + this.firstDayOfWeek;
+ weeks = 1;
+ } else {
+ firstWeekDays = this.firstDayOfWeek - firstOfMonth;
+ weeks = 0;
+ }
+
+ firstWeekDays %= 7;
+
+ return Math.ceil((this.daysInMonth(month, year) - firstWeekDays) / 7) + weeks;
+ },
+
+ // http://javascript.about.com/library/blstdweek.htm
+ weekOfYear: function(d)
+ {
+ var newYear = new Date(d.getFullYear(), 0, 1),
+ day = newYear.getDay();
+ if (this.firstDayOfWeek != 0) {
+ day = ((day + (7 - this.firstDayOfWeek)) % 7);
+ }
+ return Math.ceil((((d - newYear) / 86400000) + day + 1) / 7);
+ },
+
+ draw: function(timestamp, init)
+ {
+ this.date = new Date(timestamp);
+ this.month = this.date.getMonth();
+ this.year = this.date.getFullYear();
+
+ var cell, i, i_max, p, row, startOfView, vp,
+ count = 1,
+ div = $('hordeCalendar'),
+ tbody = div.down('TBODY'),
+
+ // Requires init above
+ daysInMonth = this.daysInMonth(this.month, this.year),
+ daysInView = this.weeksInMonth(this.month, this.year) * 7,
+ firstOfMonth = (new Date(this.year, this.month, 1)).getDay(),
+
+ // Cache today and open date.
+ today = new Date(),
+ today_year = today.getFullYear(),
+ today_month = today.getMonth(),
+ today_day = today.getDate(),
+ open = new Date(this.openDate),
+ open_year = open.getFullYear(),
+ open_month = open.getMonth(),
+ open_day = open.getDate();
+
+ if (this.firstDayOfWeek == 0) {
+ startOfView = 1 - firstOfMonth;
+ } else {
+ // @TODO Adjust this for days other than Monday.
+ startOfView = (firstOfMonth == 0)
+ ? -5
+ : 2 - firstOfMonth;
+ }
+
+ div.down('.hordeCalendarYear').update(this.year);
+ div.down('.hordeCalendarMonth').update(this.months[this.month]);
+ tbody.update('');
+
+ for (i = startOfView, i_max = startOfView + daysInView; i < i_max; ++i) {
+ if (count == 1) {
+ row = new Element('TR');
+ if (this.click_week) {
+ row.insert(new Element('TD').insert(new Element('A', { className: 'hordeCalendarWeek' }).insert(this.weekOfYear(new Date(this.year, this.month, (i < 1) ? 1 : i)))));
+ }
+ }
+
+ cell = new Element('TD');
+
+ if (i < 1 || i > daysInMonth) {
+ cell.addClassName('hordeCalendarEmpty');
+ row.insert(cell);
+ } else {
+ if (today_year == this.year &&
+ today_month == this.month &&
+ today_day == i) {
+ cell.writeAttribute({ className: 'hordeCalendarToday' });
+ }
+
+ if (open_year == this.year &&
+ open_month == this.month &&
+ open_day == i) {
+ cell.addClassName('hordeCalendarCurrent');
+ }
+
+ row.insert(cell.insert(new Element('A', { className: 'hordeCalendarDay', href: '#' }).insert(i)));
+ }
+
+ if (count == 7) {
+ tbody.insert(row);
+ count = 0;
+ }
+ ++count;
+ }
+
+ if (count > 1) {
+ tbody.insert(row);
+ }
+
+ div.show();
+
+ // Position the popup every time in case of a different input,
+ // window sizing changes, etc.
+ if (init) {
+ p = this.trigger.cumulativeOffset();
+ vp = document.viewport.getDimensions();
+
+ if (p.left + div.offsetWidth > vp.width) {
+ div.setStyle({ left: (vp.width - 10 - div.offsetWidth) + 'px' });
+ } else {
+ div.setStyle({ left: p.left + 'px' });
+ }
+
+ if (p.top + div.offsetHeight > vp.height) {
+ div.setStyle({ top: (vp.height - 10 - div.offsetHeight) + 'px' });
+ } else {
+ div.setStyle({ top: p.top + 'px' });
+ }
+ }
+
+ // IE 6 only.
+ if (Prototype.Browser.IE && !window.XMLHttpRequest) {
+ iframe = $('hordeCalendarIframe');
+ if (!iframe) {
+ iframe = new Element('IFRAME', { name: 'hordeCalendarIframe', id: 'hordeCalendarIframe', src: 'javascript:false;', scrolling: 'no', frameborder: 0 }).hide();
+ $(document.body).insert(iframe);
+ }
+ iframe.clonePosition(div).setStyle({
+ position: 'absolute',
+ display: 'block',
+ zIndex: 1
+ });
+ }
+
+ div.setStyle({ zIndex: 999 });
+ },
+
+ hideCal: function()
+ {
+ var iefix = $('hordeCalendarIframe');
+
+ $('hordeCalendar').hide();
+
+ if (iefix) {
+ iefix.hide();
+ }
+ },
+
+ changeYear: function(by)
+ {
+ this.draw((new Date(this.date.getFullYear() + by, this.date.getMonth(), 1)).getTime());
+ },
+
+ changeMonth: function(by)
+ {
+ var newMonth = this.date.getMonth() + by,
+ newYear = this.date.getFullYear();
+
+ if (newMonth == -1) {
+ newMonth = 11;
+ newYear -= 1;
+ }
+
+ this.draw((new Date(newYear, newMonth, 1)).getTime());
+ },
+
+ init: function()
+ {
+ var i, link, row,
+ offset = this.click_week ? 1 : 0,
+ thead = new Element('THEAD'),
+ table = new Element('TABLE', { className: 'hordeCalendarPopup', cellSpacing: 0 }).insert(thead).insert(new Element('TBODY'));
+
+ // Title bar.
+ link = new Element('A', { href: '#', className: 'hordeCalendarClose rightAlign' }).insert('x');
+ thead.insert(new Element('TR').insert(new Element('TD', { colspan: 6 + offset })).insert(new Element('TD').insert(link)));
+
+ // Year.
+ row = new Element('TR');
+ link = new Element('A', { className: 'hordeCalendarPrevYear', href: '#' }).insert('«');
+ row.insert(new Element('TD').insert(link));
+
+ tmp = new Element('TD', { align: 'center', colspan: 5 + offset });
+ if (this.click_year) {
+ tmp.insert(new Element('A', { className: 'hordeCalendarYear' }));
+ } else {
+ tmp.addClassName('hordeCalendarYear');
+ }
+ row.insert(tmp);
+
+ link = new Element('A', { className: 'hordeCalendarNextYear', href: '#' }).insert('»');
+ row.insert(new Element('TD', { className: 'rightAlign' }).insert(link));
+
+ thead.insert(row);
+
+ // Month name.
+ row = new Element('TR');
+ link = new Element('A', { className: 'hordeCalendarPrevMonth', href: '#' }).insert('«');
+ row.insert(new Element('TD').insert(link));
+
+ tmp = new Element('TD', { align: 'center', colspan: 5 + offset });
+ if (this.click_year) {
+ tmp.insert(new Element('A', { className: 'hordeCalendarMonth' }));
+ } else {
+ tmp.addClassName('hordeCalendarMonth');
+ }
+ row.insert(tmp);
+
+ link = new Element('A', { className: 'hordeCalendarNextMonth', href: '#' }).insert('»');
+ row.insert(new Element('TD', { className: 'rightAlign' }).insert(link));
+
+ thead.insert(row);
+
+ // Weekdays.
+ row = new Element('TR');
+ if (this.click_week) {
+ row.insert(new Element('TH'));
+ }
+ for (i = 0; i < 7; ++i) {
+ row.insert(new Element('TH').insert(this.weekdays[(i + this.firstDayOfWeek) % 7]));
+ }
+ thead.insert(row);
+
+ $(document.body).insert({ bottom: new Element('DIV', { id: 'hordeCalendar' }).setStyle({ position: 'absolute', 'z-index': 999 }).hide().insert(table) });
+
+ $('hordeCalendar').observe('click', this.clickHandler.bindAsEventListener(this));
+ },
+
+ clickHandler: function(e)
+ {
+ var elt = e.element();
+
+ if (elt.hasClassName('hordeCalendarDay')) {
+ this.hideCal();
+ this.trigger.fire('Horde_Calendar:select', new Date(this.year, this.month, parseInt(e.element().textContent, 10)));
+ } else if (elt.hasClassName('hordeCalendarClose')) {
+ this.hideCal();
+ } else if (elt.hasClassName('hordeCalendarPrevYear')) {
+ this.changeYear(-1);
+ } else if (elt.hasClassName('hordeCalendarNextYear')) {
+ this.changeYear(1);
+ } else if (elt.hasClassName('hordeCalendarPrevMonth')) {
+ this.changeMonth(-1);
+ } else if (elt.hasClassName('hordeCalendarNextMonth')) {
+ this.changeMonth(1);
+ } else if (this.click_year && elt.hasClassName('hordeCalendarYear')) {
+ this.trigger.fire('Horde_Calendar:selectYear', new Date(this.year, this.month, 1));
+ this.hideCal();
+ } else if (this.click_month && elt.hasClassName('hordeCalendarMonth')) {
+ this.trigger.fire('Horde_Calendar:selectMonth', new Date(this.year, this.month, 1));
+ this.hideCal();
+ } else if (this.click_week && elt.hasClassName('hordeCalendarWeek')) {
+ this.trigger.fire('Horde_Calendar:selectWeek', new Date(this.year, this.month, elt.up('TR').down('A.hordeCalendarDay').textContent));
+ this.hideCal();
+ }
+
+ e.stop();
+ }
+
+};
+
+document.observe('dom:loaded', Horde_Calendar.init.bind(Horde_Calendar));
--- /dev/null
+/**
+ * Provides the javascript class to create dynamic trees.
+ *
+ * Optionally uses the Horde_Tooltip class (tooltips.js).
+ *
+ * Custom Events
+ * -------------
+ * The 'memo' property of the Event object contains the original event object.
+ *
+ * 'Horde_Tree:expand'
+ * Fired when a tree element is expanded.
+ *
+ * 'Horde_Tree:collapse'
+ * Fired when a tree element is collapsed.
+ *
+ * Copyright 2003-2010 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
+ *
+ * @author Marko Djukic <marko@oblo.com>
+ * @author Michael Slusarz <slusarz@horde.org>
+ * @category Horde
+ * @package Core
+ */
+
+var Horde_Tree = Class.create({
+
+ initialize: function(opts)
+ {
+ this.opts = opts;
+
+ if (this.opts.initTree) {
+ this.renderTree(this.opts.initTree.nodes, this.opts.initTree.root_nodes, this.opts.initTree.is_static);
+ this.opts.initTree = null;
+ }
+
+ $(this.opts.target).observe('click', this._onClick.bindAsEventListener(this));
+
+ this.opts.ie6 = (navigator.userAgent.toLowerCase().substr(25,6)=="msie 6");
+ },
+
+ renderTree: function(nodes, rootNodes, renderStatic)
+ {
+ this.nodes = nodes;
+ this.rootNodes = rootNodes;
+ this.renderStatic = renderStatic;
+ this.dropline = [];
+ this.output = document.createDocumentFragment();
+
+ this._buildHeader();
+
+ this.rootNodes.each(function(r) {
+ this.buildTree(r, this.output);
+ }, this);
+
+ $(this.opts.target).update('');
+ $(this.opts.target).appendChild(this.output);
+
+ if (this.opts.ie6) {
+ /* Correct for frame scrollbar in IE6 by determining if a scrollbar
+ * is present, and if not readjusting the marginRight property to
+ * 0. See http://www.xs4all.nl/~ppk/js/doctypes.html for why this
+ * works */
+ if (document.documentElement.clientHeight == document.documentElement.offsetHeight) {
+ // no scrollbar present, take away extra margin
+ $(document.body).setStyle({ marginRight: 0 });
+ } else {
+ $(document.body).setStyle({ marginRight: '15px' });
+ }
+ }
+
+ // If using alternating row shading, work out correct shade.
+ if (this.opts.options.alternate) {
+ this.stripe();
+ }
+
+ if (window.Horde_Tooltips) {
+ window.Horde_ToolTips.attachBehavior();
+ }
+ },
+
+ _buildHeader: function()
+ {
+ if (this.opts.options.hideHeaders ||
+ !this.opts.header.size()) {
+ return;
+ }
+
+ var div = new Element('DIV');
+
+ this.opts.header.each(function(h) {
+ var tmp = new Element('SPAN').insert(h.html ? h.html : ' ');
+
+ if (h['class']) {
+ tmp.addClassName(h['class']);
+ }
+
+ div.appendChild(tmp);
+ }, this);
+
+ this.output.appendChild(div);
+ },
+
+ // Recursive function to walk through the tree array and build
+ // the output.
+ buildTree: function(nodeId, p)
+ {
+ var last_subnode, tmp,
+ node = this.nodes[nodeId];
+
+ this.buildLine(nodeId, p);
+
+ if (!Object.isUndefined(node.children)) {
+ last_subnode = node.children.last();
+ tmp = new Element('DIV', { id: 'nodeChildren_' + nodeId });
+ [ tmp ].invoke(node.expanded ? 'show' : 'hide');
+
+ node.children.each(function(c) {
+ if (c == last_subnode) {
+ this.nodes[c].node_last = true;
+ }
+ this.buildTree(c, tmp);
+ }, this);
+
+ p.appendChild(tmp);
+ }
+ },
+
+ buildLine: function(nodeId, p)
+ {
+ var div, label, tmp,
+ column = 0,
+ node = this.nodes[nodeId];
+
+ div = new Element('DIV', { className: 'treeRow' });
+ if (node['class']) {
+ div.addClassName(node['class']);
+ }
+
+ // If we have headers, track which logical "column" we're in for
+ // any given cell of content.
+ if (node.extra && node.extra[0]) {
+ node.extra[0].each(function(n) {
+ div.insert(this._divClass(new Element('SPAN').update(n), column++));
+ }, this);
+ }
+
+ for (; column < this.opts.extraColsLeft; ++column) {
+ div.insert(this._divClass(new Element('SPAN').update(' '), column));
+ }
+
+ div.insert(this._divClass(new Element('SPAN'), column));
+
+ tmp = document.createDocumentFragment();
+ for (i = Number(this.renderStatic); i < node.indent; ++i) {
+ tmp.appendChild(new Element('SPAN').addClassName('treeImg').addClassName(
+ 'treeImg' + ((this.dropline[i] && this.opts.options.lines)
+ ? this.opts.imgLine
+ : this.opts.imgBlank)
+ ));
+ }
+
+ tmp.appendChild(this._setNodeToggle(nodeId));
+
+ if (node.url) {
+ label = new Element('A', { href: node.url }).insert(
+ this._setNodeIcon(nodeId)
+ ).insert(
+ node.label
+ );
+
+ if (node.urlclass) {
+ label.addClassName(node.urlclass);
+ } else if (this.opts.options.urlclass) {
+ label.addClassName(this.opts.options.urlclass);
+ }
+
+ if (node.title) {
+ label.writeAttribute('title', node.title);
+ }
+
+ if (node.target) {
+ label.writeAttribute('target', node.target);
+ } else if (this.opts.options.target) {
+ label.writeAttribute('target', this.opts.options.target);
+ }
+
+ //if (node.onclick) {
+ // label.push(' onclick="' + node.onclick + '"');
+ //}
+
+ label = label.wrap('SPAN');
+ } else {
+ label = new Element('SPAN').addClassName('toggle').insert(
+ this._setNodeIcon(nodeId)
+ ).insert(
+ node.label
+ );
+ }
+
+ if (this.opts.options.multiline) {
+ div.insert(new Element('TABLE').insert(
+ new Element('TR').insert(
+ new Element('TD').appendChild(tmp)
+ ).insert(
+ new Element('TD').insert(label)
+ )
+ ));
+ } else {
+ div.appendChild(tmp);
+ div.insert(label)
+ }
+
+ ++column;
+
+ if (node.extra && node.extra[1]) {
+ node.extra[1].each(function(n) {
+ div.insert(this._divClass(new Element('SPAN').update(n), column++));
+ }, this);
+ }
+
+ for (; column < this.opts.extraColsRight; ++column) {
+ div.insert(this._divClass(new Element('SPAN').update(' '), column));
+ }
+
+ p.appendChild(div);
+ },
+
+ _divClass: function(div, c)
+ {
+ if (this.opts.header[c] && this.opts.header[c]['class']) {
+ div.addClassName(this.opts.header[c]['class']);
+ }
+
+ return div;
+ },
+
+ _setNodeToggle: function(nodeId)
+ {
+ var node = this.nodes[nodeId];
+
+ if (node.indent == '0') {
+ // Top level with children.
+ this.dropline[0] = false;
+
+ if (this.renderStatic) {
+ return '';
+ }
+
+ switch (this._getLineType(nodeId)) {
+ case 1:
+ case 2:
+ this.dropline[0] = true;
+ break;
+ }
+ } else {
+ this.dropline[node.indent] = !node.node_last;
+ }
+
+ return new Element('SPAN', { id: "nodeToggle_" + nodeId }).addClassName('treeToggle').addClassName('treeImg').addClassName('treeImg' + this._getNodeToggle(nodeId));
+ },
+
+ _getNodeToggle: function(nodeId)
+ {
+ var type,
+ node = this.nodes[nodeId];
+
+ if (node.indent == '0') {
+ if (this.renderStatic) {
+ return '';
+ }
+
+ if (!this.opts.options.lines) {
+ return this.opts.imgBlank;
+ }
+
+ type = this._getLineType(nodeId);
+
+ if (node.children) {
+ // Top level with children.
+ if (node.expanded) {
+ return type
+ ? ((type == 2) ? this.opts.imgMinus : this.opts.imgMinusBottom)
+ : this.opts.imgMinusOnly;
+ }
+
+ return type
+ ? ((type == 2) ? this.opts.imgPlus : this.opts.imgPlusBottom)
+ : this.opts.imgPlusOnly;
+ }
+
+ switch (type) {
+ case 0:
+ return this.opts.imgNullOnly;
+
+ case 1:
+ return this.opts.imgJoinTop;
+
+ case 2:
+ return this.opts.imgJoin;
+
+ case 3:
+ return this.opts.imgJoinBottom;
+ }
+ }
+
+ if (node.children) {
+ // Node with children.
+ if (!node.node_last) {
+ // Not last node.
+ if (!this.opts.options.lines) {
+ return this.opts.imgBlank;
+ } else if (this.renderStatic) {
+ return this.opts.imgJoin;
+ } else if (node.expanded) {
+ return this.opts.imgMinus;
+ }
+
+ return this.opts.imgPlus;
+ }
+
+ // Last node.
+ if (!this.opts.options.lines) {
+ return this.opts.imgBlank;
+ } else if (this.renderStatic) {
+ return this.opts.imgJoinBottom;
+ } else if (node.expanded) {
+ return this.opts.imgMinusBottom;
+ }
+
+ return this.opts.imgPlusBottom;
+ }
+
+ // Node no children.
+ if (!node.node_last) {
+ // Not last node.
+ return this.opts.options.lines
+ ? this.opts.imgJoin
+ : this.opts.imgBlank;
+ }
+
+ // Last node.
+ return this.opts.options.lines
+ ? this.opts.imgJoinBottom
+ : this.opts.imgBlank;
+ },
+
+ _getLineType: function(nodeId)
+ {
+ if (this.opts.options.lines_base &&
+ this.rootNodes.size() > 1) {
+ switch (this.rootNodes.indexOf(nodeId)) {
+ case 0:
+ return 1;
+
+ case (this.rootNodes.size() - 1):
+ return 3;
+
+ default:
+ return 2;
+ }
+ }
+
+ return 0;
+ },
+
+ _setNodeIcon: function(nodeId)
+ {
+ var img,
+ node = this.nodes[nodeId];
+
+ // Image.
+ if (node.icon) {
+ // Node has a user defined icon.
+ img = new Element('IMG', { id: "nodeIcon_" + nodeId, src: (node.iconopen && node.expanded ? node.iconopen : node.icon) }).addClassName('treeIcon')
+ } else {
+ img = new Element('SPAN', { id: "nodeIcon_" + nodeId }).addClassName('treeIcon');
+ if (node.children) {
+ // Standard icon: node with children.
+ img.addClassName('treeImg' + (node.expanded ? this.opts.imgFolderOpen : this.opts.imgFolder));
+ } else {
+ // Standard icon: node, no children.
+ img.addClassName('treeImg' + this.opts.imgLeaf);
+ }
+ }
+
+ if (node.iconalt) {
+ img.writeAttribute('alt', node.iconalt);
+ }
+
+ return img;
+ },
+
+ toggle: function(nodeId)
+ {
+ var icon, nodeToggle, toggle, children,
+ node = this.nodes[nodeId];
+
+ node.expanded = !node.expanded;
+ if (children = $('nodeChildren_' + nodeId)) {
+ children.setStyle({ display: node.expanded ? 'block' : 'none' });
+ }
+
+ // Toggle the node's icon if it has separate open and closed
+ // icons.
+ if (icon = $('nodeIcon_' + nodeId)) {
+ // Image.
+ if (node.icon) {
+ icon.writeAttribute('src', (node.expanded && node.iconopen) ? node.iconopen : node.icon);
+ } else {
+ // Use standard icon set.
+ icon.writeAttribute('src', node.expanded ? this.opts.imgFolderOpen : this.opts.imgFolder);
+ }
+ }
+
+ // If using alternating row shading, work out correct shade.
+ if (this.opts.options.alternate) {
+ this.stripe();
+ }
+
+ if (toggle = $('nodeToggle_' + nodeId)) {
+ toggle.writeAttribute('class', 'treeToggle treeImg').addClassName('treeImg' + this._getNodeToggle(nodeId));
+ }
+
+ $(this.opts.target).fire(node.expanded ? 'Horde_Tree:expand' : 'Horde_Tree:collapse', nodeId);
+
+ this.saveState(nodeId, node.expanded)
+ },
+
+ stripe: function()
+ {
+ var classes = [ 'rowEven', 'rowOdd' ],
+ i = 0;
+
+ $(this.opts.target).select('DIV.treeRow').each(function(r) {
+ classes.each(r.removeClassName.bind(r));
+ if (r.clientHeight) {
+ r.addClassName(classes[++i % 2]);
+ }
+ });
+ },
+
+ saveState: function(nodeId, expanded)
+ {
+ if (this.opts.nocookie) {
+ return;
+ }
+
+ var newCookie = '',
+ newNodes = [],
+ oldCookie = this._getCookie(this.opts.target + '_expanded');
+
+ if (expanded) {
+ // Expand requested so add to cookie.
+ newCookie = (oldCookie ? oldCookie + ',' : '') + nodeId;
+ } else {
+ // Collapse requested so remove from cookie.
+ oldCookie.split(',').each(function(n) {
+ if (n != nodeId) {
+ newNodes[newNodes.length] = n
+ }
+ });
+ newCookie = newNodes.join(',');
+ }
+
+ document.cookie = this.opts.target + '_expanded=exp' + escape(newCookie) + ';DOMAIN=' + this.opts.cookieDomain + ';PATH=' + this.opts.cookiePath + ';';
+ },
+
+ _getCookie: function(name)
+ {
+ var end,
+ dc = document.cookie,
+ prefix = name + '=exp',
+ begin = dc.indexOf('; ' + prefix);
+
+ if (begin == -1) {
+ begin = dc.indexOf(prefix);
+ if (begin != 0) {
+ return '';
+ }
+ } else {
+ begin += 2;
+ }
+
+ end = document.cookie.indexOf(';', begin);
+ if (end == -1) {
+ end = dc.length;
+ }
+
+ return unescape(dc.substring(begin + prefix.length, end));
+ },
+
+ _onClick: function(e)
+ {
+ var elt = e.element(),
+ id = elt.readAttribute('id');
+
+ if (elt.hasClassName('treeIcon')) {
+ elt = elt.up().previous();
+ } else if (elt.hasClassName('toggle')) {
+ elt = elt.previous();
+ }
+
+ id = elt.readAttribute('id');
+ if (id && id.startsWith('nodeToggle_')) {
+ this.toggle(id.substr(11));
+ e.stop();
+ }
+ }
+
+});
--- /dev/null
+/**
+ * Horde identity selection javascript.
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
+ *
+ * @category Horde
+ * @package Core
+ */
+
+var HordeIdentitySelect = {
+
+ newChoice: function()
+ {
+ var identity = $('identity'),
+ id = Number($F(identity));
+
+ if (id < 0) {
+ identity.up('FORM').reset();
+ identity.setValue(id);
+ return;
+ }
+
+ this.identities[id].each(function(a) {
+ var field = $(a[0]);
+
+ switch (a[1]) {
+ case "special":
+ identity.fire('HordeIdentitySelect:change', {
+ i: id,
+ pref: a[0]
+ });
+ break;
+
+ default:
+ field.setValue(a[2]);
+ break;
+ }
+ });
+ },
+
+ onDomLoad: function()
+ {
+ $('identity').observe('change', this.newChoice.bind(this));
+ }
+
+};
+
+document.observe('dom:loaded', HordeIdentitySelect.onDomLoad.bind(HordeIdentitySelect));
--- /dev/null
+/*
+ * LiquidMetal, version: 0.1 (2009-02-05)
+ *
+ * A mimetic poly-alloy of Quicksilver's scoring algorithm, essentially
+ * LiquidMetal.
+ *
+ * For usage and examples, visit:
+ * http://github.com/rmm5t/liquidmetal
+ *
+ * Licensed under the MIT:
+ * http://www.opensource.org/licenses/mit-license.php
+ *
+ * Copyright (c) 2009, Ryan McGeary (ryanonjavascript -[at]- mcgeary [*dot*] org)
+ * Modified by the Horde Project to use compressibility.
+ *
+ * @category Horde
+ * @package Core
+ */
+var LiquidMetal = {
+
+ SCORE_NO_MATCH: 0.0,
+ SCORE_MATCH: 1.0,
+ SCORE_TRAILING: 0.8,
+ SCORE_TRAILING_BUT_STARTED: 0.9,
+ SCORE_BUFFER: 0.85,
+
+ score: function(str, abbr)
+ {
+ // Short circuits
+ if (abbr.length == 0) {
+ return this.SCORE_TRAILING;
+ }
+ if (abbr.length > str.length) {
+ return this.SCORE_NO_MATCH;
+ }
+
+ var scores = this.buildScoreArray(str, abbr),
+ sum = 0.0;
+
+ scores.each(function(i) {
+ sum += i;
+ });
+
+ return (sum / scores.size());
+ },
+
+ buildScoreArray: function(str, abbr)
+ {
+ var lastIndex = -1,
+ lower = str.toLowerCase(),
+ scores = new Array(str.length),
+ started = false;
+
+ abbr.toLowerCase().split("").each(function(c) {
+ var index = to = lower.indexOf(c, lastIndex + 1),
+ val = this.SCORE_BUFFER;
+
+ if (index < 0) {
+ return this.fillArray(scores, this.SCORE_NO_MATCH);
+ }
+
+ if (index == 0) {
+ started = true;
+ }
+
+ if (this.isNewWord(str, index)) {
+ scores[index - 1] = 1;
+ to = index - 1;
+ } else if (!this.isUpperCase(str, index)) {
+ val = this.SCORE_NO_MATCH;
+ }
+
+ this.fillArray(scores, val, lastIndex + 1, val);
+
+ scores[index] = this.SCORE_MATCH;
+ lastIndex = index;
+ }.bind(this));
+
+ this.fillArray(scores, started ? this.SCORE_TRAILING_BUT_STARTED : this.SCORE_TRAILING, lastIndex + 1);
+
+ return scores;
+ },
+
+ isUpperCase: function(str, index)
+ {
+ var c = str.charAt(index);
+ return ("A" <= c && c <= "Z");
+ },
+
+ isNewWord: function(str, index)
+ {
+ var c = str.charAt(index - 1);
+ return (c == " " || c == "\t");
+ },
+
+ fillArray: function(arr, value, from, to)
+ {
+ from = Math.max(from || 0, 0);
+ to = Math.min(to || arr.length, arr.length);
+ for (var i = from; i < to; ++i) {
+ arr[i] = value;
+ }
+ return arr;
+ }
+};
--- /dev/null
+/**
+ * Horde Html Helper Javascript Class
+ *
+ * Provides the javascript class insert html tags by clicking on icons.
+ *
+ * The helpers available:
+ * emoticons - for inserting emoticons strings
+ *
+ * Copyright 2003-2010 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
+ *
+ * @author Marko Djukic <marko@oblo.com>
+ * @category Horde
+ * @package Core
+ * @todo Add handling for font tags, tables, etc.
+ */
+
+var Horde_Html_Helper = {
+
+ iconlist: [],
+ targetElement: null,
+
+ open: function(type, target)
+ {
+ var cell, row, table, tbody,
+ lay = $('htmlhelper_' + target);
+ this.targetElement = $(target);
+
+ if (lay.getStyle('display') == 'block') {
+ lay.hide();
+ return false;
+ }
+
+ if (lay.firstChild) {
+ lay.removeChild(lay.firstChild);
+ }
+
+ tbody = new Element('TBODY');
+ table = new Element('TABLE', { border: 0, cellSpacing: 0 }).insert(tbody);
+
+ if (type == 'emoticons') {
+ row = new Element('TR');
+ cell = new Element('TD');
+
+ iconlist.each(function(i) {
+ var link =
+ new Element('A', { href: '#' }).insert(
+ new Element('IMG', { align: 'middle', border: 0, src: i[0] })
+ );
+ cell.appendChild(link);
+
+ link.observe('click', function(e) {
+ this.targetElement.setValue($F(this.targetElement) + i[1] + ' ');
+ e.stop();
+ }.bindAsEventListener(this));
+ });
+
+ row.insert(cell);
+ tbody.insert(row);
+ table.insert(tbody);
+ }
+
+ lay.insert(table).setStyle({ display: 'block' });
+ }
+};
--- /dev/null
+/**
+ * Provides the javascript for the prefs page.
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
+ *
+ * @category Horde
+ * @package Core
+ */
+
+document.observe('dom:loaded', function() {
+ $('appsubmit').hide();
+
+ $('app').observe('change', function() {
+ $('appswitch').submit();
+ });
+});
--- /dev/null
+/**
+ * @category Horde
+ * @package Core
+ */
+var PrettyAutocompleter = Class.create({
+
+ initialize: function(element, params)
+ {
+ this.p = Object.extend({
+ // Outer div/fake input box and CSS class
+ box: 'HordeACBox',
+ boxClass: 'hordeACBox',
+ // <ul> CSS class
+ listClass: 'hordeACList',
+ // CSS class for real input field
+ growingInputClass: 'hordeACTrigger',
+ // Dom id for <li> that holds the input field.
+ triggerContainer: 'hordeACTriggerContainer',
+ // Min pixel width of input field
+ minTriggerWidth: 100,
+ // Allow for a function that filters the display value
+ // This function should *always* return escaped HTML
+ displayFilter: function(t) { return t.escapeHTML() },
+ filterCallback: this._filterChoices.bind(this),
+ onAdd: Prototype.K,
+ onRemove: Prototype.K
+ }, params || {});
+
+ // Array to hold the currently selected items to ease with removing
+ // them, assuring no duplicates etc..
+ this.selectedItems = [];
+
+ // The original input element is transformed into the hidden input
+ // field that hold the text values (p.items), while p.trigger is
+ // the borderless input field located in p.box
+ this.p.items = element;
+ this.p.trigger = element + 'real';
+ this.initialized = false;
+ this._enabled = true;
+ },
+
+ /**
+ * Initializes the autocompleter, builds the dom structure, registers
+ * events, etc...
+ */
+ init: function()
+ {
+ if (this.initialized) {
+ return;
+ }
+
+ // Build the DOM structure
+ this.buildStructure();
+
+ // Remember the bound method to unregister later.
+ this._boundProcessValue = this._processValue.bind(this);
+ var trigger = $(this.p.trigger);
+ trigger.observe('keydown', this._onKeyDown.bindAsEventListener(this));
+ trigger.observe('blur', this._boundProcessValue);
+
+ // Make sure the p.items element is hidden
+ if (!this.p.debug) {
+ $(this.p.items).hide();
+ }
+
+ // Set the updateElement callback
+ this.p.onSelect = this._updateElement.bind(this);
+
+ // Look for clicks on the box to simulate clicking in an input box
+ $(this.p.box).observe('click', function() { trigger.focus() });
+ trigger.observe('blur', this._resize.bind(this));
+ trigger.observe('keydown', this._resize.bind(this));
+ trigger.observe('keypress', this._resize.bind(this));
+ trigger.observe('keyup', this._resize.bind(this));
+
+ // Create the underlaying Autocompleter
+ this.p.uri += '/input=' + this.p.trigger;
+
+ this.p.onShow = this._knvShow.bind(this);
+ this.p.onHide = this._knvHide.bind(this);
+
+ // Make sure the knl is contained in the overlay
+ this.p.domParent = this.p.box;
+ new Ajax.Autocompleter(this.p.trigger, this.p.uri, this.p);
+
+ // Prepopulate the items and the container elements?
+ if (typeof this.p.existing != 'undefined') {
+ this.init(this.p.existing);
+ }
+
+ this.initialized = true;
+ },
+
+ /**
+ * Resets the autocompleter's state.
+ */
+ reset: function(existing)
+ {
+ if (!this.initialized) {
+ this.init();
+ }
+
+ // TODO: Resize the trigger field to fill the current line?
+ // Clear any existing values
+ if (this.selectedItems.length) {
+ $(this.p.box).select('li.' + this.p.listClass + 'Item').each(function(item) {
+ this.removeItemNode(item);
+ }.bind(this));
+ }
+
+ // Clear the hidden items field
+ $(this.p.items).value = '';
+
+ // Add any initial values
+ if (typeof existing != 'undefined' && existing.length) {
+ for (var i = 0, l = existing.length; i < l; i++) {
+ this.addNewItemNode(existing[i]);
+ }
+ }
+ this._enabled = true;
+ },
+
+ buildStructure: function()
+ {
+ // Build the outter box
+ var box = new Element('div', { id: this.p.box, className: this.p.boxClass }).setStyle({ position: 'relative' });
+
+ // The list - where the choosen items are placed as <li> nodes
+ var list = new Element('ul', { className: this.p.listClass });
+
+ // The input element and the <li> wraper
+ var inputListItem = new Element('li', {
+ className: this.p.listClass + 'Member',
+ id: this.p.triggerContainer }),
+ growingInput = new Element('input', {
+ className: this.p.growingInputClass,
+ id: this.p.trigger,
+ name: this.p.trigger,
+ autocomplete: 'off' });
+
+ // Create a hidden span node to help calculate the needed size
+ // of the input field.
+ this.sizer = new Element('span').setStyle({ float: 'left', display: 'inline-block', position: 'absolute', left: '-1000px' });
+
+ inputListItem.update(growingInput);
+ list.update(inputListItem);
+ box.update(list);
+ box.insert(this.sizer);
+
+ // Replace the single input element with the new structure and
+ // move the old element into the structure while making sure it's
+ // hidden. (Use the long form to play nice with Opera)
+ box.insert(Element.replace($(this.p.items), box));
+ },
+
+ shutdown: function()
+ {
+ this._processValue();
+ },
+
+ _onKeyDown: function(e)
+ {
+ // Check for a comma
+ if (e.keyCode == 188) {
+ this._processValue();
+ e.stop();
+ }
+ },
+
+ _processValue: function()
+ {
+ var value = $F(this.p.trigger).replace(/^,/, '').strip();
+ if (value.length) {
+ this.addNewItemNode(value);
+ this.p.onAdd(value);
+ }
+ },
+
+ _resize: function()
+ {
+ this.sizer.update($(this.p.trigger).value);
+ newSize = Math.min(this.sizer.getWidth(), $(this.p.box).getWidth());
+ newSize = Math.max(newSize, this.p.minTriggerWidth);
+ $(this.p.trigger).setStyle({ width: newSize + 'px' });
+ },
+
+ // Used as the updateElement callback.
+ _updateElement: function(item)
+ {
+ this.addNewItemNode(item);
+ this.p.onAdd(item);
+ },
+
+ addNewItemNode: function(value)
+ {
+ // Don't add if it's already present.
+ for (var x = 0, len = this.selectedItems.length; x < len; x++) {
+ if (this.selectedItems[x].rawValue == value) {
+ $(this.p.trigger).value = '';
+ return;
+ }
+ }
+
+ var displayValue = this.p.displayFilter(value),
+ newItem = new Element('li', { className: this.p.listClass + 'Member ' + this.p.listClass + 'Item' }).update(displayValue),
+ x = new Element('img', { className: 'hordeACItemRemove', src: this.p.deleteIcon });
+ x.observe('click', this._removeItemHandler.bindAsEventListener(this));
+ newItem.insert(x);
+ $(this.p.triggerContainer).insert({ before: newItem });
+ $(this.p.trigger).value = '';
+
+ // Add to hidden input field.
+ if ($(this.p.items).value) {
+ $(this.p.items).value = $(this.p.items).value + ', ' + value;
+ } else {
+ $(this.p.items).value = value;
+ }
+
+ // ...and keep the selectedItems array up to date.
+ this.selectedItems.push({ rawValue: value, displayValue: displayValue });
+ },
+
+ removeItemNode: function(item)
+ {
+ var value = item.collectTextNodesIgnoreClass('informal');
+ for (var x = 0, len = this.selectedItems.length; x < len; x++) {
+ if (this.selectedItems[x].displayValue.unescapeHTML() == value) {
+ this.selectedItems.splice(x, 1);
+ break;
+ }
+ }
+ item.remove();
+ this.p.onRemove(value);
+ },
+
+ disable: function()
+ {
+ if (!this._enabled || !this.initialized) {
+ return;
+ }
+
+ this._enabled = false;
+ $(this.p.box).select('.hordeACItemRemove').invoke('toggle');
+ $(this.p.trigger).disable();
+ },
+
+ enable: function()
+ {
+ if (this._enabled) {
+ return;
+ }
+ this._enabled = true;
+ $(this.p.box).select('.hordeACItemRemove').invoke('toggle');
+ $(this.p.trigger).enable();
+ },
+
+ _removeItemHandler: function(e)
+ {
+ var realValues = [], x, len;
+ this.removeItemNode(e.element().up());
+ for (x = 0, len = this.selectedItems.length; x < len; x++) {
+ realValues.push(this.selectedItems[x].rawValue);
+ }
+ $(this.p.items).value = realValues.join(',');
+ },
+
+ _filterChoices: function(c)
+ {
+ this.selectedItems.each(function(item) {
+ c = c.without(item.rawValue);
+ });
+ return c;
+ },
+
+ _knvShow: function(l)
+ {
+ $(this.p.trigger).stopObserving('blur', this._boundProcessValue);
+ },
+
+ _knvHide: function(l)
+ {
+ $(this.p.trigger).observe('blur', this._boundProcessValue);
+ }
+});
--- /dev/null
+/**
+ * Provides the javascript for managing the source selection widget.
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
+ *
+ * @category Horde
+ * @package Core
+ */
+
+var HordeSourceSelectPrefs = {
+
+ // Vars defaulting to null: source_list
+
+ setSourcesHidden: function()
+ {
+ var out = [], ss;
+
+ if (this.source_list) {
+ ss = $F('source_select');
+ if (ss) {
+ this.source_list.each(function(s) {
+ if (s.source == ss) {
+ s.selected = $F('selected_sources');
+ }
+ out.push([ s.source, s.selected ]);
+ });
+ }
+ } else {
+ $A($('selected_sources').options).slice(1).each(function(s) {
+ out.push(s.value);
+ });
+ }
+
+ $('sources').setValue(Object.toJSON(out));
+ },
+
+ moveAction: function(from, to)
+ {
+ var moved = false;
+
+ $(from).childElements().each(function(c) {
+ if (c.selected) {
+ c.remove();
+ c.selected = false;
+ $(to).insert(c);
+ moved = true;
+ }
+ });
+
+
+ if (moved) {
+ $(to).fire('HordeSourceSelectPrefs:add');
+ $(from).fire('HordeSourceSelectPrefs:remove');
+ this.setSourcesHidden();
+ }
+ },
+
+ moveSource: function(e, mode)
+ {
+ var sa = $('selected_sources'), sel, tmp;
+
+ if (sa.selectedIndex < 1 || sa.length < 3) {
+ return;
+ }
+
+ // Deselect everything but the first selected item
+ sa.childElements().each(function(s) {
+ if (sel) {
+ s.selected = false;
+ } else if (s.selected) {
+ sel = s;
+ }
+ });
+
+ switch (mode) {
+ case 'down':
+ tmp = sel.next();
+ if (tmp) {
+ sel.remove();
+ tmp.insert({ after: sel });
+ }
+ break;
+
+ case 'up':
+ tmp = sel.previous();
+ if (tmp && tmp.value) {
+ sel.remove();
+ tmp.insert({ before: sel });
+ }
+ break;
+ }
+
+ this.setSourcesHidden();
+ e.stop();
+ },
+
+ changeSource: function()
+ {
+ var source,
+ sel = $('selected_sources'),
+ ss = $('source_select'),
+ unsel = $('unselected_sources'),
+ val = $F(ss);
+
+ sel.down().siblings().invoke('remove');
+ unsel.down().siblings().invoke('remove');
+
+ if (val) {
+ source = this.source_list.find(function(s) {
+ return val == s.source;
+ });
+ source.selected.each(function(s) {
+ sel.insert(new Option(s.v, s.l));
+ });
+ source.unselected.each(function(u) {
+ unsel.insert(new Option(s.v, s.l));
+ });
+ }
+ },
+
+ onDomLoad: function()
+ {
+ if (this.source_list) {
+ $('source_select').observe('change', this.changeSource.bind(this));
+ }
+
+ this.setSourcesHidden();
+
+ $('addsource').observe('click', this.moveAction.bind(this, 'unselected_sources', 'selected_sources'));
+ $('removesource').observe('click', this.moveAction.bind(this, 'selected_sources', 'unselected_sources'));
+ $('moveup').observe('click', this.moveSource.bindAsEventListener(this, 'up'));
+ $('movedown').observe('click', this.moveSource.bindAsEventListener(this, 'down'));
+ }
+
+};
+
+document.observe('dom:loaded', HordeSourceSelectPrefs.onDomLoad.bind(HordeSourceSelectPrefs));
--- /dev/null
+/**
+ * This spell checker was inspired by work done by Garrison Locke, but
+ * was rewritten almost completely by Chuck Hagenbuch to use
+ * Prototype/Scriptaculous.
+ *
+ * Requires: prototype.js (v1.6.1+), KeyNavList.js
+ *
+ * Copyright 2005-2010 The Horde Project (http://www.horde.org/)
+ *
+ * Custom Events:
+ * --------------
+ * Custom events are triggered on the target element.
+ *
+ * 'SpellChecker:after'
+ * Fired when the spellcheck processing ends.
+ *
+ * 'SpellChecker:before'
+ * Fired before the spellcheck is performed.
+ *
+ * 'SpellChecker:noerror'
+ * Fired when no spellcheck errors are found.
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
+ *
+ * @category Horde
+ * @package Core
+ */
+
+var SpellChecker = Class.create({
+
+ // Vars used and defaulting to null:
+ // bad, choices, disabled, htmlAreaParent, lc, locale, reviewDiv,
+ // statusButton, statusClass, suggestions, target, url
+ options: {},
+ resumeOnDblClick: true,
+ state: 'CheckSpelling',
+
+ // Options:
+ // bs = (array) Button states
+ // locales = (array) List of locales. See KeyNavList for structure.
+ // sc = (string) Status class
+ // statusButton = (string/element) DOM ID or element of the status
+ // button
+ // target = (string|Element) DOM element containing data
+ // url = (string) URL of specllchecker handler
+ initialize: function(opts)
+ {
+ var d, lc, tmp, ul;
+
+ this.url = opts.url;
+ this.target = $(opts.target);
+ this.statusButton = $(opts.statusButton);
+ this.buttonStates = opts.bs;
+ this.statusClass = opts.sc || '';
+ this.disabled = false;
+
+ this.options.onComplete = this.onComplete.bind(this);
+
+ document.observe('click', this.onClick.bindAsEventListener(this));
+
+ if (opts.locales) {
+ this.lc = new KeyNavList(this.statusButton, {
+ list: opts.locales,
+ onChoose: this.setLocale.bindAsEventListener(this)
+ });
+
+ this.statusButton.insert({ after: new Element('SPAN', { className: 'spellcheckPopdownImg' }) });
+ }
+
+ this.setStatus('CheckSpelling');
+ },
+
+ setLocale: function(locale)
+ {
+ this.locale = locale;
+ },
+
+ targetValue: function()
+ {
+ return Object.isUndefined(this.target.value)
+ ? this.target.innerHTML
+ : this.target.value;
+ },
+
+ spellCheck: function()
+ {
+ this.target.fire('SpellChecker:before');
+
+ var opts = Object.clone(this.options),
+ p = $H(),
+ url = this.url;
+
+ this.setStatus('Checking');
+
+ p.set(this.target.identify(), this.targetValue());
+ opts.parameters = p.toQueryString();
+
+ if (this.locale) {
+ url += '/locale=' + this.locale;
+ }
+ if (this.htmlAreaParent) {
+ url += '/html=1';
+ }
+
+ new Ajax.Request(url, opts);
+ },
+
+ onComplete: function(request)
+ {
+ var bad, content, washidden,
+ i = 0,
+ result = request.responseJSON;
+
+ if (Object.isUndefined(result)) {
+ this.setStatus('Error');
+ return;
+ }
+
+ this.suggestions = result.suggestions || [];
+
+ if (!this.suggestions.size()) {
+ this.setStatus('CheckSpelling');
+ this.target.fire('SpellChecker:noerror');
+ return;
+ }
+
+ bad = result.bad || [];
+
+ content = this.targetValue();
+ content = this.htmlAreaParent
+ ? content.replace(/\r?\n/g, '')
+ : content.replace(/\r?\n/g, '~~~').escapeHTML();
+
+ $A(bad).each(function(node) {
+ var re_text = '<span index="' + (i++) + '" class="spellcheckIncorrect">' + node + '</span>';
+ content = content.replace(new RegExp("(?:^|\\b)" + RegExp.escape(node) + "(?:\\b|$)", 'g'), re_text);
+
+ // Go through and see if we matched anything inside a tag (i.e.
+ // class/spellcheckIncorrect is often matched if using a
+ // non-English lang).
+ content = content.replace(new RegExp("(<[^>]*)" + RegExp.escape(re_text) + "([^>]*>)", 'g'), '\$1' + node + '\$2');
+ }, this);
+
+ if (!this.reviewDiv) {
+ this.reviewDiv = new Element('DIV', { className: this.target.readAttribute('class') }).addClassName('spellcheck').setStyle({ overflow: 'auto' });
+ if (this.resumeOnDblClick) {
+ this.reviewDiv.observe('dblclick', this.resume.bind(this));
+ }
+ }
+
+ if (!this.target.visible()) {
+ this.target.show();
+ washidden = true;
+ }
+ this.reviewDiv.setStyle({ width: this.target.clientWidth + 'px', height: this.target.clientHeight + 'px'});
+ if (washidden) {
+ this.target.hide();
+ }
+
+ if (!this.htmlAreaParent) {
+ content = content.replace(/~~~/g, '<br />');
+ }
+ this.reviewDiv.update(content);
+
+ if (this.htmlAreaParent) {
+ $(this.htmlAreaParent).insert({ bottom: this.reviewDiv });
+ } else {
+ this.target.hide().insert({ before: this.reviewDiv });
+ }
+
+ this.setStatus('ResumeEdit');
+ },
+
+ onClick: function(e)
+ {
+ var data = [], index, elt = e.element();
+
+ if (this.disabled) {
+ return;
+ }
+
+ if (elt == this.statusButton) {
+ switch (this.state) {
+ case 'CheckSpelling':
+ this.spellCheck();
+ break;
+
+ case 'ResumeEdit':
+ this.resume();
+ break;
+ }
+
+ e.stop();
+ } else if (elt.hasClassName('spellcheckPopdownImg')) {
+ this.lc.show();
+ this.lc.ignoreClick(e);
+ e.stop();
+ } else if (elt.hasClassName('spellcheckIncorrect')) {
+ index = e.element().readAttribute('index');
+
+ $A(this.suggestions[index]).each(function(node) {
+ data.push({ l: node, v: node });
+ });
+
+ if (this.choices) {
+ this.choices.updateBase(elt);
+ this.choices.opts.onChoose = function(val) {elt.update(val).writeAttribute({ className: 'spellcheckCorrected' });};
+ } else {
+ this.choices = new KeyNavList(elt, {
+ esc: true,
+ onChoose: function(val) {
+ elt.update(val).writeAttribute({ className: 'spellcheckCorrected' });
+ }
+ });
+ }
+
+ this.choices.show(data);
+ this.choices.ignoreClick(e);
+ e.stop();
+ }
+ },
+
+ resume: function()
+ {
+ if (!this.reviewDiv) {
+ return;
+ }
+
+ var t;
+
+ this.reviewDiv.select('span.spellcheckIncorrect').each(function(n) {
+ n.replace(n.innerHTML);
+ });
+
+ t = this.reviewDiv.innerHTML;
+ if (!this.htmlAreaParent) {
+ t = t.replace(/<br *\/?>/gi, '~~~').unescapeHTML().replace(/~~~/g, "\n");
+ }
+ this.target.setValue(t);
+ this.target.enable();
+
+ if (this.resumeOnDblClick) {
+ this.reviewDiv.stopObserving('dblclick');
+ }
+ this.reviewDiv.remove();
+ this.reviewDiv = null;
+
+ this.setStatus('CheckSpelling');
+
+ if (!this.htmlAreaParent) {
+ this.target.show();
+ }
+
+ this.target.fire('SpellChecker:after');
+ },
+
+ setStatus: function(state)
+ {
+ if (!this.statusButton) {
+ return;
+ }
+
+ this.state = state;
+ switch (this.statusButton.tagName) {
+ case 'INPUT':
+ this.statusButton.setValue(this.buttonStates[state]);
+ break;
+
+ case 'A':
+ this.statusButton.update(this.buttonStates[state]);
+ break;
+ }
+ this.statusButton.className = this.statusClass + ' spellcheck' + state;
+ },
+
+ isActive: function()
+ {
+ return this.reviewDiv;
+ },
+
+ disable: function(disable)
+ {
+ this.disabled = disable;
+ }
+
+});
</notes>
<contents>
<dir baseinstalldir="/" name="/">
+ <dir name="js">
+ <file name="addressbooksprefs.js" role="horde" />
+ <file name="alarmprefs.js" role="horde" />
+ <file name="autocomplete.js" role="horde" />
+ <file name="calendar.js" role="horde" />
+ <file name="hordetree.js" role="horde" />
+ <file name="identityselect.js" role="horde" />
+ <file name="liquidmetal.js" role="horde" />
+ <file name="open_html_helper.js" role="horde" />
+ <file name="prefs.js" role="horde" />
+ <file name="prettyautocomplete.js" role="horde" />
+ <file name="spellchecker.js" role="horde" />
+ <file name="sourceselect.js" role="horde" />
+ </dir> <!-- /js -->
<dir name="lib">
<dir name="Horde">
<dir name="Config">
</package>
</optional>
</dependencies>
+ <usesrole>
+ <role>horde</role>
+ <package>Role</package>
+ <channel>pear.horde.org</channel>
+ </usesrole>
<phprelease>
<filelist>
+ <install as="js/addressbooksprefs.js" name="js/addressbooksprefs.js" />
+ <install as="js/alarmprefs.js" name="js/alarmprefs.js" />
+ <install as="js/autocomplete.js" name="js/autocomplete.js" />
+ <install as="js/calendar.js" name="js/calendar.js" />
+ <install as="js/hordetree.js" name="js/hordetree.js" />
+ <install as="js/identityselect.js" name="js/identityselect.js" />
+ <install as="js/liquidmetal.js" name="js/liquidmetal.js" />
+ <install as="js/open_html_helper.js" name="js/open_html_helper.js" />
+ <install as="js/prefs.js" name="js/prefs.js" />
+ <install as="js/prettyautocomplete.js" name="js/prettyautocomplete.js" />
+ <install as="js/spellchecker.js" name="js/spellchecker.js" />
+ <install as="js/sourceselect.js" name="js/sourceselect.js" />
<install as="Horde.php" name="lib/Horde.php" />
<install as="Horde/Config.php" name="lib/Horde/Config.php" />
<install as="Horde/ErrorHandler.php" name="lib/Horde/ErrorHandler.php" />
+++ /dev/null
-/**
- * Provides the javascript for managing addressbooks.
- *
- * See the enclosed file COPYING for license information (LGPL). If you
- * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
- */
-
-var HordeAddressbooksPrefs = {
-
- // Variables set by other code: fields, nonetext
-
- updateSearchFields: function()
- {
- var tmp,
- sv = $F('selected_sources'),
- sf = $('search_fields_select');
-
- sf.childElements().invoke('remove');
-
- if (sv.size() == 1) {
- tmp = this.fields.get(sv.first());
- tmp.entries.each(function(o) {
- var opt = new Option(o.label, o.name);
- if (tmp.selected.include(o.name)) {
- opt.selected = true;
- }
- sf.insert(opt);
- });
- } else {
- tmp = new Option(this.nonetext, '');
- tmp.disabled = true;
- sf.insert(tmp);
- }
- },
-
- changeSearchFields: function()
- {
- var tmp,
- out = $H(),
- sv = $F('selected_sources');
-
- if (sv.size() == 1) {
- tmp = this.fields.get(sv.first());
- tmp.selected = $F('search_fields_select');
- this.fields.set(sv.first(), tmp);
-
- this.fields.each(function(f) {
- out.set(f.key, f.value.selected);
- });
-
- $('search_fields').setValue(Object.toJSON(out));
- }
- },
-
- onDomLoad: function()
- {
- this.fields = $H(this.fields);
-
- this.updateSearchFields();
-
- $('search_fields_select').observe('change', this.changeSearchFields.bind(this));
- $('selected_sources').observe('change', this.updateSearchFields.bind(this));
- $('selected_sources').observe('HordeSourceSelectPrefs:remove', this.updateSearchFields.bind(this));
- }
-
-};
-
-document.observe('dom:loaded', HordeAddressbooksPrefs.onDomLoad.bind(HordeAddressbooksPrefs));
+++ /dev/null
-/**
- * Provides the javascript for managing alarms.
- *
- * See the enclosed file COPYING for license information (LGPL). If you
- * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
- */
-
-var HordeAlarmPrefs = {
-
- // Variables defaulting to null: pref
-
- updateParams: function()
- {
- [ 'notify', 'mail', 'sms' ].each(function(method) {
- var p = $(method + 'Params');
- if (p) {
- if ($(this.pref).getValue().include(method)) {
- p.show();
- } else {
- p.hide();
- }
- }
- }, this);
- },
-
- onDomLoad: function()
- {
- $(this.pref).observe('change', this.updateParams.bind(this));
- this.updateParams();
- }
-
-};
-
-document.observe('dom:load', HordeAlarmPrefs.onDomLoad.bind(HordeAlarmPrefs));
+++ /dev/null
-/**
- * autocomplete.js - A javascript library which implements autocomplete.
- * Requires prototype.js v1.6.0.2+, scriptaculous v1.8.0+ (effects.js),
- * and keynavlist.js.
- *
- * Adapted from script.aculo.us controls.js v1.8.0
- * (c) 2005-2007 Thomas Fuchs, Ivan Krstic, and Jon Tirsen
- * Contributors: Richard Livsey, Rahul Bhargava, Rob Wills
- * http://script.aculo.us/
- *
- * The original script was freely distributable under the terms of an
- * MIT-style license.
- *
- * Usage:
- * ------
- * TODO: options = autoSelect, frequency, minChars, onSelect, onShow, onType,
- * paramName, tokens
- *
- * Copyright 2007-2010 The Horde Project (http://www.horde.org/)
- *
- * See the enclosed file COPYING for license information (GPL). If you
- * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
- */
-
-var Autocompleter = {};
-
-Autocompleter.Base = Class.create({
- baseInitialize: function(elt, opts)
- {
- this.elt = elt = $(elt);
- this.changed = false;
- this.observer = null;
- this.oldval = $F(elt);
- this.opts = Object.extend({
- frequency: 0.4,
- indicator: null,
- minChars: 1,
- onSelect: Prototype.K,
- onShow: Prototype.K,
- onType: Prototype.K,
- filterCallback: Prototype.K,
- paramName: elt.readAttribute('name'),
- tokens: [],
- keydownObserver: this.elt
- }, (this._setOptions ? this._setOptions(opts) : (opts || {})));
-
- // Force carriage returns as token delimiters anyway
- if (!this.opts.tokens.include('\n')) {
- this.opts.tokens.push('\n');
- }
-
- elt.writeAttribute('autocomplete', 'off');
- elt.observe("keydown", this._onKeyDown.bindAsEventListener(this));
- },
-
- _onKeyDown: function(e)
- {
- var a = document.activeElement;
-
- if (Object.isUndefined(a) || a == this.elt) {
- switch (e.keyCode) {
- case 0:
- if (!Prototype.Browser.WebKit) {
- break;
- }
- // Fall-through
-
- // Ignore events caught by KevNavList
- case Event.KEY_DOWN:
- case Event.KEY_ESC:
- case Event.KEY_RETURN:
- case Event.KEY_TAB:
- case Event.KEY_UP:
- return;
- }
-
- this.changed = true;
-
- if (this.observer) {
- clearTimeout(this.observer);
- }
-
- this.observer = this.onObserverEvent.bind(this).delay(this.opts.frequency);
- }
- },
-
- updateChoices: function(choices)
- {
- var a = document.activeElement, c = [], re;
-
- if (this.changed ||
- (Object.isUndefined(a) || a != this.elt)) {
- return;
- }
-
- if (this.opts.indicator) {
- $(this.opts.indicator).hide();
- }
-
- choices = this.opts.filterCallback(choices);
- if (!choices.size()) {
- if (this.knl) {
- this.knl.hide();
- }
- this.getNewVal(this.lastentry);
- } else if (choices.size() == 1 && this.opts.autoSelect) {
- this.onSelect(choices.first());
- if (this.knl) {
- this.knl.hide();
- }
- } else {
- re = new RegExp("(" + this.getToken() + ")", "i");
-
- choices.each(function(n) {
- c.push({
- l: n.escapeHTML().gsub(re, '<strong>#{1}</strong>'),
- v: n
- });
- });
-
- if (!this.knl) {
- this.knl = new KeyNavList(this.elt, { onChoose: this.onSelect.bind(this),
- onShow: this.opts.onShow.bind(this),
- domParent: this.opts.domParent,
- keydownObserver: this.opts.keydownObserver});
- }
-
- this.knl.show(c);
- }
- },
-
- onObserverEvent: function()
- {
- this.changed = false;
-
- var entry = this.getToken();
-
- if (entry.length >= this.opts.minChars) {
- entry = this.opts.onType(entry);
- }
-
- if (entry.length) {
- if (this.opts.indicator) {
- $(this.opts.indicator).show();
- }
- this.lastentry = entry;
- this.getUpdatedChoices(entry);
- } else if (this.knl) {
- this.knl.hide();
- }
- },
-
- getToken: function()
- {
- var bounds = this.getTokenBounds();
- return $F(this.elt).substring(bounds[0], bounds[1]).strip();
- },
-
- getTokenBounds: function()
- {
- var diff, i, index, l, offset, tp,
- t = this.opts.tokens,
- value = $F(this.elt),
- nextTokenPos = value.length,
- prevTokenPos = -1,
- boundary = Math.min(nextTokenPos, this.oldval.length);
-
- if (value.strip().empty()) {
- return [ -1, 0 ];
- }
-
- diff = boundary;
- for (i = 0; i < boundary; ++i) {
- if (value[i] != this.oldval[i]) {
- diff = i;
- break;
- }
- }
-
- offset = (diff == this.oldval.length ? 1 : 0);
-
- for (index = 0, l = t.length; index < l; ++index) {
- tp = value.lastIndexOf(t[index], diff + offset - 1);
- if (tp > prevTokenPos) {
- prevTokenPos = tp;
- }
- tp = value.indexOf(t[index], diff + offset);
- if (tp != -1 && tp < nextTokenPos) {
- nextTokenPos = tp;
- }
- }
- return [ prevTokenPos + 1, nextTokenPos ];
- },
-
- onSelect: function(entry)
- {
- if (entry) {
- this.elt.setValue(this.opts.onSelect(this.getNewVal(entry))).focus();
- if (this.knl) {
- this.knl.markSelected();
- }
- }
- },
-
- getNewVal: function(entry)
- {
- var bounds = this.getTokenBounds(), newval, v, ws;
-
- if (bounds[0] == -1) {
- newval = entry;
- } else {
- v = $F(this.elt);
- newval = v.substr(0, bounds[0]);
- ws = v.substr(bounds[0]).match(/^\s+/);
- if (ws) {
- newval += ws[0];
- }
- newval += entry + v.substr(bounds[1]);
- }
-
- this.oldval = newval;
-
- return newval;
- }
-
-});
-
-Ajax.Autocompleter = Class.create(Autocompleter.Base, {
-
- initialize: function(element, url, opts)
- {
- this.baseInitialize(element, opts);
- this.opts = Object.extend(this.opts, {
- asynchronous: true,
- onComplete: this._onComplete.bind(this),
- defaultParams: $H(this.opts.parameters)
- });
- this.url = url;
- this.cache = $H();
- },
-
- getUpdatedChoices: function(t)
- {
- var p,
- o = this.opts,
- c = this.cache.get(t);
-
- if (c) {
- this.updateChoices(c);
- } else {
- p = Object.clone(o.defaultParams);
- p.set(o.paramName, t);
- o.parameters = p.toQueryString();
- new Ajax.Request(this.url, o);
- }
- },
-
- _onComplete: function(request)
- {
- this.updateChoices(this.cache.set(this.getToken(), request.responseJSON));
- }
-});
-
-Autocompleter.Local = Class.create(Autocompleter.Base, {
-
- initialize: function(element, arr, opts)
- {
- this.baseInitialize(element, opts);
- this.opts.arr = arr;
- },
-
- getUpdatedChoices: function(entry)
- {
- var choices,
- csort = [],
- entry_len = entry.length,
- i = 0,
- o = this.opts;
-
- if (o.ignoreCase) {
- entry = entry.toLowerCase();
- }
-
- choices = o.arr.findAll(function(t) {
- if (i == o.choices) {
- throw $break;
- }
-
- if (o.ignoreCase) {
- t = t.toLowerCase();
- }
- t = t.unescapeHTML();
-
- var pos = t.indexOf(entry);
- if (pos != -1 &&
- ((pos == 0 && t.length != entry_len) ||
- (entry_len >= o.partialChars &&
- o.partialSearch &&
- (o.fullSearch || /\s/.test(t.substr(pos - 1, 1)))))) {
- ++i;
- return true;
- }
- return false;
- }, this);
-
- if (o.score) {
- choices.each(function(term) {
- csort.push({ s: LiquidMetal.score(term, entry), t: term });
- }.bind(this));
- // Sort the terms
- csort.sort(function(a, b) { return b.s - a.s; });
- choices = csort.pluck('t');
- }
-
- this.updateChoices(choices);
- },
-
- _setOptions: function(opts)
- {
- return Object.extend({
- choices: 10,
- fullSearch: false,
- ignoreCase: true,
- partialChars: 2,
- partialSearch: true,
- score: false
- }, opts || {});
- }
-
-});
+++ /dev/null
-/**
- * Horde javascript calendar widget.
- *
- * Custom Events:
- * --------------
- * Horde_Calendar:select
- * params: Date object
- * Fired when a date is selected.
- *
- * Horde_Calendar:selectMonth
- * params: Date object
- * Fired when a month is selected.
- *
- * Horde_Calendar:selectWeek
- * params: Date object
- * Fired when a week is selected.
- *
- * Horde_Calendar:selectYear
- * params: Date object
- * Fired when a year is selected.
- */
-
-var Horde_Calendar =
-{
- // Variables set externally: click_month, click_week, click_year,
- // firstDayOfWeek, fullweekdays, months,
- // weekdays
- // Variables defaulting to null: date, month, openDate, trigger, year
-
- open: function(trigger, data)
- {
- var date = data ? data : new Date();
-
- this.openDate = date.getTime();
- this.trigger = $(trigger);
- this.draw(this.openDate, true);
- },
-
- /**
- * Days in the month (month is a zero-indexed javascript month).
- */
- daysInMonth: function(month, year)
- {
- switch (month) {
- case 3:
- case 5:
- case 8:
- case 10:
- return 30;
-
- case 1:
- return (year % 4 == 0 && (year % 100 != 0 || year % 400 == 0))
- ? 29
- : 28;
-
- default:
- return 31;
- }
- },
-
- weeksInMonth: function(month, year)
- {
- var firstWeekDays, weeks,
- firstOfMonth = (new Date(year, month, 1)).getDay();
-
- if ((this.firstDayOfWeek == 1 && firstOfMonth == 0) ||
- (this.firstDayOfWeek == 0 && firstOfMonth == 6)) {
- firstWeekDays = 7 - firstOfMonth + this.firstDayOfWeek;
- weeks = 1;
- } else {
- firstWeekDays = this.firstDayOfWeek - firstOfMonth;
- weeks = 0;
- }
-
- firstWeekDays %= 7;
-
- return Math.ceil((this.daysInMonth(month, year) - firstWeekDays) / 7) + weeks;
- },
-
- // http://javascript.about.com/library/blstdweek.htm
- weekOfYear: function(d)
- {
- var newYear = new Date(d.getFullYear(), 0, 1),
- day = newYear.getDay();
- if (this.firstDayOfWeek != 0) {
- day = ((day + (7 - this.firstDayOfWeek)) % 7);
- }
- return Math.ceil((((d - newYear) / 86400000) + day + 1) / 7);
- },
-
- draw: function(timestamp, init)
- {
- this.date = new Date(timestamp);
- this.month = this.date.getMonth();
- this.year = this.date.getFullYear();
-
- var cell, i, i_max, p, row, startOfView, vp,
- count = 1,
- div = $('hordeCalendar'),
- tbody = div.down('TBODY'),
-
- // Requires init above
- daysInMonth = this.daysInMonth(this.month, this.year),
- daysInView = this.weeksInMonth(this.month, this.year) * 7,
- firstOfMonth = (new Date(this.year, this.month, 1)).getDay(),
-
- // Cache today and open date.
- today = new Date(),
- today_year = today.getFullYear(),
- today_month = today.getMonth(),
- today_day = today.getDate(),
- open = new Date(this.openDate),
- open_year = open.getFullYear(),
- open_month = open.getMonth(),
- open_day = open.getDate();
-
- if (this.firstDayOfWeek == 0) {
- startOfView = 1 - firstOfMonth;
- } else {
- // @TODO Adjust this for days other than Monday.
- startOfView = (firstOfMonth == 0)
- ? -5
- : 2 - firstOfMonth;
- }
-
- div.down('.hordeCalendarYear').update(this.year);
- div.down('.hordeCalendarMonth').update(this.months[this.month]);
- tbody.update('');
-
- for (i = startOfView, i_max = startOfView + daysInView; i < i_max; ++i) {
- if (count == 1) {
- row = new Element('TR');
- if (this.click_week) {
- row.insert(new Element('TD').insert(new Element('A', { className: 'hordeCalendarWeek' }).insert(this.weekOfYear(new Date(this.year, this.month, (i < 1) ? 1 : i)))));
- }
- }
-
- cell = new Element('TD');
-
- if (i < 1 || i > daysInMonth) {
- cell.addClassName('hordeCalendarEmpty');
- row.insert(cell);
- } else {
- if (today_year == this.year &&
- today_month == this.month &&
- today_day == i) {
- cell.writeAttribute({ className: 'hordeCalendarToday' });
- }
-
- if (open_year == this.year &&
- open_month == this.month &&
- open_day == i) {
- cell.addClassName('hordeCalendarCurrent');
- }
-
- row.insert(cell.insert(new Element('A', { className: 'hordeCalendarDay', href: '#' }).insert(i)));
- }
-
- if (count == 7) {
- tbody.insert(row);
- count = 0;
- }
- ++count;
- }
-
- if (count > 1) {
- tbody.insert(row);
- }
-
- div.show();
-
- // Position the popup every time in case of a different input,
- // window sizing changes, etc.
- if (init) {
- p = this.trigger.cumulativeOffset();
- vp = document.viewport.getDimensions();
-
- if (p.left + div.offsetWidth > vp.width) {
- div.setStyle({ left: (vp.width - 10 - div.offsetWidth) + 'px' });
- } else {
- div.setStyle({ left: p.left + 'px' });
- }
-
- if (p.top + div.offsetHeight > vp.height) {
- div.setStyle({ top: (vp.height - 10 - div.offsetHeight) + 'px' });
- } else {
- div.setStyle({ top: p.top + 'px' });
- }
- }
-
- // IE 6 only.
- if (Prototype.Browser.IE && !window.XMLHttpRequest) {
- iframe = $('hordeCalendarIframe');
- if (!iframe) {
- iframe = new Element('IFRAME', { name: 'hordeCalendarIframe', id: 'hordeCalendarIframe', src: 'javascript:false;', scrolling: 'no', frameborder: 0 }).hide();
- $(document.body).insert(iframe);
- }
- iframe.clonePosition(div).setStyle({
- position: 'absolute',
- display: 'block',
- zIndex: 1
- });
- }
-
- div.setStyle({ zIndex: 999 });
- },
-
- hideCal: function()
- {
- var iefix = $('hordeCalendarIframe');
-
- $('hordeCalendar').hide();
-
- if (iefix) {
- iefix.hide();
- }
- },
-
- changeYear: function(by)
- {
- this.draw((new Date(this.date.getFullYear() + by, this.date.getMonth(), 1)).getTime());
- },
-
- changeMonth: function(by)
- {
- var newMonth = this.date.getMonth() + by,
- newYear = this.date.getFullYear();
-
- if (newMonth == -1) {
- newMonth = 11;
- newYear -= 1;
- }
-
- this.draw((new Date(newYear, newMonth, 1)).getTime());
- },
-
- init: function()
- {
- var i, link, row,
- offset = this.click_week ? 1 : 0,
- thead = new Element('THEAD'),
- table = new Element('TABLE', { className: 'hordeCalendarPopup', cellSpacing: 0 }).insert(thead).insert(new Element('TBODY'));
-
- // Title bar.
- link = new Element('A', { href: '#', className: 'hordeCalendarClose rightAlign' }).insert('x');
- thead.insert(new Element('TR').insert(new Element('TD', { colspan: 6 + offset })).insert(new Element('TD').insert(link)));
-
- // Year.
- row = new Element('TR');
- link = new Element('A', { className: 'hordeCalendarPrevYear', href: '#' }).insert('«');
- row.insert(new Element('TD').insert(link));
-
- tmp = new Element('TD', { align: 'center', colspan: 5 + offset });
- if (this.click_year) {
- tmp.insert(new Element('A', { className: 'hordeCalendarYear' }));
- } else {
- tmp.addClassName('hordeCalendarYear');
- }
- row.insert(tmp);
-
- link = new Element('A', { className: 'hordeCalendarNextYear', href: '#' }).insert('»');
- row.insert(new Element('TD', { className: 'rightAlign' }).insert(link));
-
- thead.insert(row);
-
- // Month name.
- row = new Element('TR');
- link = new Element('A', { className: 'hordeCalendarPrevMonth', href: '#' }).insert('«');
- row.insert(new Element('TD').insert(link));
-
- tmp = new Element('TD', { align: 'center', colspan: 5 + offset });
- if (this.click_year) {
- tmp.insert(new Element('A', { className: 'hordeCalendarMonth' }));
- } else {
- tmp.addClassName('hordeCalendarMonth');
- }
- row.insert(tmp);
-
- link = new Element('A', { className: 'hordeCalendarNextMonth', href: '#' }).insert('»');
- row.insert(new Element('TD', { className: 'rightAlign' }).insert(link));
-
- thead.insert(row);
-
- // Weekdays.
- row = new Element('TR');
- if (this.click_week) {
- row.insert(new Element('TH'));
- }
- for (i = 0; i < 7; ++i) {
- row.insert(new Element('TH').insert(this.weekdays[(i + this.firstDayOfWeek) % 7]));
- }
- thead.insert(row);
-
- $(document.body).insert({ bottom: new Element('DIV', { id: 'hordeCalendar' }).setStyle({ position: 'absolute', 'z-index': 999 }).hide().insert(table) });
-
- $('hordeCalendar').observe('click', this.clickHandler.bindAsEventListener(this));
- },
-
- clickHandler: function(e)
- {
- var elt = e.element();
-
- if (elt.hasClassName('hordeCalendarDay')) {
- this.hideCal();
- this.trigger.fire('Horde_Calendar:select', new Date(this.year, this.month, parseInt(e.element().textContent, 10)));
- } else if (elt.hasClassName('hordeCalendarClose')) {
- this.hideCal();
- } else if (elt.hasClassName('hordeCalendarPrevYear')) {
- this.changeYear(-1);
- } else if (elt.hasClassName('hordeCalendarNextYear')) {
- this.changeYear(1);
- } else if (elt.hasClassName('hordeCalendarPrevMonth')) {
- this.changeMonth(-1);
- } else if (elt.hasClassName('hordeCalendarNextMonth')) {
- this.changeMonth(1);
- } else if (this.click_year && elt.hasClassName('hordeCalendarYear')) {
- this.trigger.fire('Horde_Calendar:selectYear', new Date(this.year, this.month, 1));
- this.hideCal();
- } else if (this.click_month && elt.hasClassName('hordeCalendarMonth')) {
- this.trigger.fire('Horde_Calendar:selectMonth', new Date(this.year, this.month, 1));
- this.hideCal();
- } else if (this.click_week && elt.hasClassName('hordeCalendarWeek')) {
- this.trigger.fire('Horde_Calendar:selectWeek', new Date(this.year, this.month, elt.up('TR').down('A.hordeCalendarDay').textContent));
- this.hideCal();
- }
-
- e.stop();
- }
-
-};
-
-document.observe('dom:loaded', Horde_Calendar.init.bind(Horde_Calendar));
+++ /dev/null
-/**
- * Provides the javascript class to create dynamic trees.
- *
- * Optionally uses the Horde_Tooltip class (tooltips.js).
- *
- * Custom Events
- * -------------
- * The 'memo' property of the Event object contains the original event object.
- *
- * 'Horde_Tree:expand'
- * Fired when a tree element is expanded.
- *
- * 'Horde_Tree:collapse'
- * Fired when a tree element is collapsed.
- *
- * Copyright 2003-2010 The Horde Project (http://www.horde.org/)
- *
- * See the enclosed file COPYING for license information (GPL). If you
- * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
- *
- * @author Marko Djukic <marko@oblo.com>
- * @author Michael Slusarz <slusarz@horde.org>
- * @category Horde
- */
-
-var Horde_Tree = Class.create({
-
- initialize: function(opts)
- {
- this.opts = opts;
-
- if (this.opts.initTree) {
- this.renderTree(this.opts.initTree.nodes, this.opts.initTree.root_nodes, this.opts.initTree.is_static);
- this.opts.initTree = null;
- }
-
- $(this.opts.target).observe('click', this._onClick.bindAsEventListener(this));
-
- this.opts.ie6 = (navigator.userAgent.toLowerCase().substr(25,6)=="msie 6");
- },
-
- renderTree: function(nodes, rootNodes, renderStatic)
- {
- this.nodes = nodes;
- this.rootNodes = rootNodes;
- this.renderStatic = renderStatic;
- this.dropline = [];
- this.output = document.createDocumentFragment();
-
- this._buildHeader();
-
- this.rootNodes.each(function(r) {
- this.buildTree(r, this.output);
- }, this);
-
- $(this.opts.target).update('');
- $(this.opts.target).appendChild(this.output);
-
- if (this.opts.ie6) {
- /* Correct for frame scrollbar in IE6 by determining if a scrollbar
- * is present, and if not readjusting the marginRight property to
- * 0. See http://www.xs4all.nl/~ppk/js/doctypes.html for why this
- * works */
- if (document.documentElement.clientHeight == document.documentElement.offsetHeight) {
- // no scrollbar present, take away extra margin
- $(document.body).setStyle({ marginRight: 0 });
- } else {
- $(document.body).setStyle({ marginRight: '15px' });
- }
- }
-
- // If using alternating row shading, work out correct shade.
- if (this.opts.options.alternate) {
- this.stripe();
- }
-
- if (window.Horde_Tooltips) {
- window.Horde_ToolTips.attachBehavior();
- }
- },
-
- _buildHeader: function()
- {
- if (this.opts.options.hideHeaders ||
- !this.opts.header.size()) {
- return;
- }
-
- var div = new Element('DIV');
-
- this.opts.header.each(function(h) {
- var tmp = new Element('SPAN').insert(h.html ? h.html : ' ');
-
- if (h['class']) {
- tmp.addClassName(h['class']);
- }
-
- div.appendChild(tmp);
- }, this);
-
- this.output.appendChild(div);
- },
-
- // Recursive function to walk through the tree array and build
- // the output.
- buildTree: function(nodeId, p)
- {
- var last_subnode, tmp,
- node = this.nodes[nodeId];
-
- this.buildLine(nodeId, p);
-
- if (!Object.isUndefined(node.children)) {
- last_subnode = node.children.last();
- tmp = new Element('DIV', { id: 'nodeChildren_' + nodeId });
- [ tmp ].invoke(node.expanded ? 'show' : 'hide');
-
- node.children.each(function(c) {
- if (c == last_subnode) {
- this.nodes[c].node_last = true;
- }
- this.buildTree(c, tmp);
- }, this);
-
- p.appendChild(tmp);
- }
- },
-
- buildLine: function(nodeId, p)
- {
- var div, label, tmp,
- column = 0,
- node = this.nodes[nodeId];
-
- div = new Element('DIV', { className: 'treeRow' });
- if (node['class']) {
- div.addClassName(node['class']);
- }
-
- // If we have headers, track which logical "column" we're in for
- // any given cell of content.
- if (node.extra && node.extra[0]) {
- node.extra[0].each(function(n) {
- div.insert(this._divClass(new Element('SPAN').update(n), column++));
- }, this);
- }
-
- for (; column < this.opts.extraColsLeft; ++column) {
- div.insert(this._divClass(new Element('SPAN').update(' '), column));
- }
-
- div.insert(this._divClass(new Element('SPAN'), column));
-
- tmp = document.createDocumentFragment();
- for (i = Number(this.renderStatic); i < node.indent; ++i) {
- tmp.appendChild(new Element('SPAN').addClassName('treeImg').addClassName(
- 'treeImg' + ((this.dropline[i] && this.opts.options.lines)
- ? this.opts.imgLine
- : this.opts.imgBlank)
- ));
- }
-
- tmp.appendChild(this._setNodeToggle(nodeId));
-
- if (node.url) {
- label = new Element('A', { href: node.url }).insert(
- this._setNodeIcon(nodeId)
- ).insert(
- node.label
- );
-
- if (node.urlclass) {
- label.addClassName(node.urlclass);
- } else if (this.opts.options.urlclass) {
- label.addClassName(this.opts.options.urlclass);
- }
-
- if (node.title) {
- label.writeAttribute('title', node.title);
- }
-
- if (node.target) {
- label.writeAttribute('target', node.target);
- } else if (this.opts.options.target) {
- label.writeAttribute('target', this.opts.options.target);
- }
-
- //if (node.onclick) {
- // label.push(' onclick="' + node.onclick + '"');
- //}
-
- label = label.wrap('SPAN');
- } else {
- label = new Element('SPAN').addClassName('toggle').insert(
- this._setNodeIcon(nodeId)
- ).insert(
- node.label
- );
- }
-
- if (this.opts.options.multiline) {
- div.insert(new Element('TABLE').insert(
- new Element('TR').insert(
- new Element('TD').appendChild(tmp)
- ).insert(
- new Element('TD').insert(label)
- )
- ));
- } else {
- div.appendChild(tmp);
- div.insert(label)
- }
-
- ++column;
-
- if (node.extra && node.extra[1]) {
- node.extra[1].each(function(n) {
- div.insert(this._divClass(new Element('SPAN').update(n), column++));
- }, this);
- }
-
- for (; column < this.opts.extraColsRight; ++column) {
- div.insert(this._divClass(new Element('SPAN').update(' '), column));
- }
-
- p.appendChild(div);
- },
-
- _divClass: function(div, c)
- {
- if (this.opts.header[c] && this.opts.header[c]['class']) {
- div.addClassName(this.opts.header[c]['class']);
- }
-
- return div;
- },
-
- _setNodeToggle: function(nodeId)
- {
- var node = this.nodes[nodeId];
-
- if (node.indent == '0') {
- // Top level with children.
- this.dropline[0] = false;
-
- if (this.renderStatic) {
- return '';
- }
-
- switch (this._getLineType(nodeId)) {
- case 1:
- case 2:
- this.dropline[0] = true;
- break;
- }
- } else {
- this.dropline[node.indent] = !node.node_last;
- }
-
- return new Element('SPAN', { id: "nodeToggle_" + nodeId }).addClassName('treeToggle').addClassName('treeImg').addClassName('treeImg' + this._getNodeToggle(nodeId));
- },
-
- _getNodeToggle: function(nodeId)
- {
- var type,
- node = this.nodes[nodeId];
-
- if (node.indent == '0') {
- if (this.renderStatic) {
- return '';
- }
-
- if (!this.opts.options.lines) {
- return this.opts.imgBlank;
- }
-
- type = this._getLineType(nodeId);
-
- if (node.children) {
- // Top level with children.
- if (node.expanded) {
- return type
- ? ((type == 2) ? this.opts.imgMinus : this.opts.imgMinusBottom)
- : this.opts.imgMinusOnly;
- }
-
- return type
- ? ((type == 2) ? this.opts.imgPlus : this.opts.imgPlusBottom)
- : this.opts.imgPlusOnly;
- }
-
- switch (type) {
- case 0:
- return this.opts.imgNullOnly;
-
- case 1:
- return this.opts.imgJoinTop;
-
- case 2:
- return this.opts.imgJoin;
-
- case 3:
- return this.opts.imgJoinBottom;
- }
- }
-
- if (node.children) {
- // Node with children.
- if (!node.node_last) {
- // Not last node.
- if (!this.opts.options.lines) {
- return this.opts.imgBlank;
- } else if (this.renderStatic) {
- return this.opts.imgJoin;
- } else if (node.expanded) {
- return this.opts.imgMinus;
- }
-
- return this.opts.imgPlus;
- }
-
- // Last node.
- if (!this.opts.options.lines) {
- return this.opts.imgBlank;
- } else if (this.renderStatic) {
- return this.opts.imgJoinBottom;
- } else if (node.expanded) {
- return this.opts.imgMinusBottom;
- }
-
- return this.opts.imgPlusBottom;
- }
-
- // Node no children.
- if (!node.node_last) {
- // Not last node.
- return this.opts.options.lines
- ? this.opts.imgJoin
- : this.opts.imgBlank;
- }
-
- // Last node.
- return this.opts.options.lines
- ? this.opts.imgJoinBottom
- : this.opts.imgBlank;
- },
-
- _getLineType: function(nodeId)
- {
- if (this.opts.options.lines_base &&
- this.rootNodes.size() > 1) {
- switch (this.rootNodes.indexOf(nodeId)) {
- case 0:
- return 1;
-
- case (this.rootNodes.size() - 1):
- return 3;
-
- default:
- return 2;
- }
- }
-
- return 0;
- },
-
- _setNodeIcon: function(nodeId)
- {
- var img,
- node = this.nodes[nodeId];
-
- // Image.
- if (node.icon) {
- // Node has a user defined icon.
- img = new Element('IMG', { id: "nodeIcon_" + nodeId, src: (node.iconopen && node.expanded ? node.iconopen : node.icon) }).addClassName('treeIcon')
- } else {
- img = new Element('SPAN', { id: "nodeIcon_" + nodeId }).addClassName('treeIcon');
- if (node.children) {
- // Standard icon: node with children.
- img.addClassName('treeImg' + (node.expanded ? this.opts.imgFolderOpen : this.opts.imgFolder));
- } else {
- // Standard icon: node, no children.
- img.addClassName('treeImg' + this.opts.imgLeaf);
- }
- }
-
- if (node.iconalt) {
- img.writeAttribute('alt', node.iconalt);
- }
-
- return img;
- },
-
- toggle: function(nodeId)
- {
- var icon, nodeToggle, toggle, children,
- node = this.nodes[nodeId];
-
- node.expanded = !node.expanded;
- if (children = $('nodeChildren_' + nodeId)) {
- children.setStyle({ display: node.expanded ? 'block' : 'none' });
- }
-
- // Toggle the node's icon if it has separate open and closed
- // icons.
- if (icon = $('nodeIcon_' + nodeId)) {
- // Image.
- if (node.icon) {
- icon.writeAttribute('src', (node.expanded && node.iconopen) ? node.iconopen : node.icon);
- } else {
- // Use standard icon set.
- icon.writeAttribute('src', node.expanded ? this.opts.imgFolderOpen : this.opts.imgFolder);
- }
- }
-
- // If using alternating row shading, work out correct shade.
- if (this.opts.options.alternate) {
- this.stripe();
- }
-
- if (toggle = $('nodeToggle_' + nodeId)) {
- toggle.writeAttribute('class', 'treeToggle treeImg').addClassName('treeImg' + this._getNodeToggle(nodeId));
- }
-
- $(this.opts.target).fire(node.expanded ? 'Horde_Tree:expand' : 'Horde_Tree:collapse', nodeId);
-
- this.saveState(nodeId, node.expanded)
- },
-
- stripe: function()
- {
- var classes = [ 'rowEven', 'rowOdd' ],
- i = 0;
-
- $(this.opts.target).select('DIV.treeRow').each(function(r) {
- classes.each(r.removeClassName.bind(r));
- if (r.clientHeight) {
- r.addClassName(classes[++i % 2]);
- }
- });
- },
-
- saveState: function(nodeId, expanded)
- {
- if (this.opts.nocookie) {
- return;
- }
-
- var newCookie = '',
- newNodes = [],
- oldCookie = this._getCookie(this.opts.target + '_expanded');
-
- if (expanded) {
- // Expand requested so add to cookie.
- newCookie = (oldCookie ? oldCookie + ',' : '') + nodeId;
- } else {
- // Collapse requested so remove from cookie.
- oldCookie.split(',').each(function(n) {
- if (n != nodeId) {
- newNodes[newNodes.length] = n
- }
- });
- newCookie = newNodes.join(',');
- }
-
- document.cookie = this.opts.target + '_expanded=exp' + escape(newCookie) + ';DOMAIN=' + this.opts.cookieDomain + ';PATH=' + this.opts.cookiePath + ';';
- },
-
- _getCookie: function(name)
- {
- var end,
- dc = document.cookie,
- prefix = name + '=exp',
- begin = dc.indexOf('; ' + prefix);
-
- if (begin == -1) {
- begin = dc.indexOf(prefix);
- if (begin != 0) {
- return '';
- }
- } else {
- begin += 2;
- }
-
- end = document.cookie.indexOf(';', begin);
- if (end == -1) {
- end = dc.length;
- }
-
- return unescape(dc.substring(begin + prefix.length, end));
- },
-
- _onClick: function(e)
- {
- var elt = e.element(),
- id = elt.readAttribute('id');
-
- if (elt.hasClassName('treeIcon')) {
- elt = elt.up().previous();
- } else if (elt.hasClassName('toggle')) {
- elt = elt.previous();
- }
-
- id = elt.readAttribute('id');
- if (id && id.startsWith('nodeToggle_')) {
- this.toggle(id.substr(11));
- e.stop();
- }
- }
-
-});
+++ /dev/null
-/**
- * Horde identity selection javascript.
- *
- * See the enclosed file COPYING for license information (LGPL). If you
- * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
- */
-
-var HordeIdentitySelect = {
-
- newChoice: function()
- {
- var identity = $('identity'),
- id = Number($F(identity));
-
- if (id < 0) {
- identity.up('FORM').reset();
- identity.setValue(id);
- return;
- }
-
- this.identities[id].each(function(a) {
- var field = $(a[0]);
-
- switch (a[1]) {
- case "special":
- identity.fire('HordeIdentitySelect:change', {
- i: id,
- pref: a[0]
- });
- break;
-
- default:
- field.setValue(a[2]);
- break;
- }
- });
- },
-
- onDomLoad: function()
- {
- $('identity').observe('change', this.newChoice.bind(this));
- }
-
-};
-
-document.observe('dom:loaded', HordeIdentitySelect.onDomLoad.bind(HordeIdentitySelect));
+++ /dev/null
-/*
- * LiquidMetal, version: 0.1 (2009-02-05)
- *
- * A mimetic poly-alloy of Quicksilver's scoring algorithm, essentially
- * LiquidMetal.
- *
- * For usage and examples, visit:
- * http://github.com/rmm5t/liquidmetal
- *
- * Licensed under the MIT:
- * http://www.opensource.org/licenses/mit-license.php
- *
- * Copyright (c) 2009, Ryan McGeary (ryanonjavascript -[at]- mcgeary [*dot*] org)
- * Modified by the Horde Project to use compressibility.
- */
-var LiquidMetal = {
-
- SCORE_NO_MATCH: 0.0,
- SCORE_MATCH: 1.0,
- SCORE_TRAILING: 0.8,
- SCORE_TRAILING_BUT_STARTED: 0.9,
- SCORE_BUFFER: 0.85,
-
- score: function(str, abbr)
- {
- // Short circuits
- if (abbr.length == 0) {
- return this.SCORE_TRAILING;
- }
- if (abbr.length > str.length) {
- return this.SCORE_NO_MATCH;
- }
-
- var scores = this.buildScoreArray(str, abbr),
- sum = 0.0;
-
- scores.each(function(i) {
- sum += i;
- });
-
- return (sum / scores.size());
- },
-
- buildScoreArray: function(str, abbr)
- {
- var lastIndex = -1,
- lower = str.toLowerCase(),
- scores = new Array(str.length),
- started = false;
-
- abbr.toLowerCase().split("").each(function(c) {
- var index = to = lower.indexOf(c, lastIndex + 1),
- val = this.SCORE_BUFFER;
-
- if (index < 0) {
- return this.fillArray(scores, this.SCORE_NO_MATCH);
- }
-
- if (index == 0) {
- started = true;
- }
-
- if (this.isNewWord(str, index)) {
- scores[index - 1] = 1;
- to = index - 1;
- } else if (!this.isUpperCase(str, index)) {
- val = this.SCORE_NO_MATCH;
- }
-
- this.fillArray(scores, val, lastIndex + 1, val);
-
- scores[index] = this.SCORE_MATCH;
- lastIndex = index;
- }.bind(this));
-
- this.fillArray(scores, started ? this.SCORE_TRAILING_BUT_STARTED : this.SCORE_TRAILING, lastIndex + 1);
-
- return scores;
- },
-
- isUpperCase: function(str, index)
- {
- var c = str.charAt(index);
- return ("A" <= c && c <= "Z");
- },
-
- isNewWord: function(str, index)
- {
- var c = str.charAt(index - 1);
- return (c == " " || c == "\t");
- },
-
- fillArray: function(arr, value, from, to)
- {
- from = Math.max(from || 0, 0);
- to = Math.min(to || arr.length, arr.length);
- for (var i = from; i < to; ++i) {
- arr[i] = value;
- }
- return arr;
- }
-};
+++ /dev/null
-/**
- * Horde Html Helper Javascript Class
- *
- * Provides the javascript class insert html tags by clicking on icons.
- *
- * The helpers available:
- * emoticons - for inserting emoticons strings
- *
- * Copyright 2003-2010 The Horde Project (http://www.horde.org/)
- *
- * See the enclosed file COPYING for license information (GPL). If you
- * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
- *
- * @author Marko Djukic <marko@oblo.com>
- * @package Horde
- * @todo Add handling for font tags, tables, etc.
- */
-
-var Horde_Html_Helper = {
-
- iconlist: [],
- targetElement: null,
-
- open: function(type, target)
- {
- var cell, row, table, tbody,
- lay = $('htmlhelper_' + target);
- this.targetElement = $(target);
-
- if (lay.getStyle('display') == 'block') {
- lay.hide();
- return false;
- }
-
- if (lay.firstChild) {
- lay.removeChild(lay.firstChild);
- }
-
- tbody = new Element('TBODY');
- table = new Element('TABLE', { border: 0, cellSpacing: 0 }).insert(tbody);
-
- if (type == 'emoticons') {
- row = new Element('TR');
- cell = new Element('TD');
-
- iconlist.each(function(i) {
- var link =
- new Element('A', { href: '#' }).insert(
- new Element('IMG', { align: 'middle', border: 0, src: i[0] })
- );
- cell.appendChild(link);
-
- link.observe('click', function(e) {
- this.targetElement.setValue($F(this.targetElement) + i[1] + ' ');
- e.stop();
- }.bindAsEventListener(this));
- });
-
- row.insert(cell);
- tbody.insert(row);
- table.insert(tbody);
- }
-
- lay.insert(table).setStyle({ display: 'block' });
- }
-};
+++ /dev/null
-/**
- * Provides the javascript for the prefs page.
- *
- * See the enclosed file COPYING for license information (LGPL). If you
- * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
- */
-
-document.observe('dom:loaded', function() {
- $('appsubmit').hide();
-
- $('app').observe('change', function() {
- $('appswitch').submit();
- });
-});
+++ /dev/null
-var PrettyAutocompleter = Class.create({
-
- initialize: function(element, params)
- {
- this.p = Object.extend({
- // Outer div/fake input box and CSS class
- box: 'HordeACBox',
- boxClass: 'hordeACBox',
- // <ul> CSS class
- listClass: 'hordeACList',
- // CSS class for real input field
- growingInputClass: 'hordeACTrigger',
- // Dom id for <li> that holds the input field.
- triggerContainer: 'hordeACTriggerContainer',
- // Min pixel width of input field
- minTriggerWidth: 100,
- // Allow for a function that filters the display value
- // This function should *always* return escaped HTML
- displayFilter: function(t) { return t.escapeHTML() },
- filterCallback: this._filterChoices.bind(this),
- onAdd: Prototype.K,
- onRemove: Prototype.K
- }, params || {});
-
- // Array to hold the currently selected items to ease with removing
- // them, assuring no duplicates etc..
- this.selectedItems = [];
-
- // The original input element is transformed into the hidden input
- // field that hold the text values (p.items), while p.trigger is
- // the borderless input field located in p.box
- this.p.items = element;
- this.p.trigger = element + 'real';
- this.initialized = false;
- this._enabled = true;
- },
-
- /**
- * Initializes the autocompleter, builds the dom structure, registers
- * events, etc...
- */
- init: function()
- {
- if (this.initialized) {
- return;
- }
-
- // Build the DOM structure
- this.buildStructure();
-
- // Remember the bound method to unregister later.
- this._boundProcessValue = this._processValue.bind(this);
- var trigger = $(this.p.trigger);
- trigger.observe('keydown', this._onKeyDown.bindAsEventListener(this));
- trigger.observe('blur', this._boundProcessValue);
-
- // Make sure the p.items element is hidden
- if (!this.p.debug) {
- $(this.p.items).hide();
- }
-
- // Set the updateElement callback
- this.p.onSelect = this._updateElement.bind(this);
-
- // Look for clicks on the box to simulate clicking in an input box
- $(this.p.box).observe('click', function() { trigger.focus() });
- trigger.observe('blur', this._resize.bind(this));
- trigger.observe('keydown', this._resize.bind(this));
- trigger.observe('keypress', this._resize.bind(this));
- trigger.observe('keyup', this._resize.bind(this));
-
- // Create the underlaying Autocompleter
- this.p.uri += '/input=' + this.p.trigger;
-
- this.p.onShow = this._knvShow.bind(this);
- this.p.onHide = this._knvHide.bind(this);
-
- // Make sure the knl is contained in the overlay
- this.p.domParent = this.p.box;
- new Ajax.Autocompleter(this.p.trigger, this.p.uri, this.p);
-
- // Prepopulate the items and the container elements?
- if (typeof this.p.existing != 'undefined') {
- this.init(this.p.existing);
- }
-
- this.initialized = true;
- },
-
- /**
- * Resets the autocompleter's state.
- */
- reset: function(existing)
- {
- if (!this.initialized) {
- this.init();
- }
-
- // TODO: Resize the trigger field to fill the current line?
- // Clear any existing values
- if (this.selectedItems.length) {
- $(this.p.box).select('li.' + this.p.listClass + 'Item').each(function(item) {
- this.removeItemNode(item);
- }.bind(this));
- }
-
- // Clear the hidden items field
- $(this.p.items).value = '';
-
- // Add any initial values
- if (typeof existing != 'undefined' && existing.length) {
- for (var i = 0, l = existing.length; i < l; i++) {
- this.addNewItemNode(existing[i]);
- }
- }
- this._enabled = true;
- },
-
- buildStructure: function()
- {
- // Build the outter box
- var box = new Element('div', { id: this.p.box, className: this.p.boxClass }).setStyle({ position: 'relative' });
-
- // The list - where the choosen items are placed as <li> nodes
- var list = new Element('ul', { className: this.p.listClass });
-
- // The input element and the <li> wraper
- var inputListItem = new Element('li', {
- className: this.p.listClass + 'Member',
- id: this.p.triggerContainer }),
- growingInput = new Element('input', {
- className: this.p.growingInputClass,
- id: this.p.trigger,
- name: this.p.trigger,
- autocomplete: 'off' });
-
- // Create a hidden span node to help calculate the needed size
- // of the input field.
- this.sizer = new Element('span').setStyle({ float: 'left', display: 'inline-block', position: 'absolute', left: '-1000px' });
-
- inputListItem.update(growingInput);
- list.update(inputListItem);
- box.update(list);
- box.insert(this.sizer);
-
- // Replace the single input element with the new structure and
- // move the old element into the structure while making sure it's
- // hidden. (Use the long form to play nice with Opera)
- box.insert(Element.replace($(this.p.items), box));
- },
-
- shutdown: function()
- {
- this._processValue();
- },
-
- _onKeyDown: function(e)
- {
- // Check for a comma
- if (e.keyCode == 188) {
- this._processValue();
- e.stop();
- }
- },
-
- _processValue: function()
- {
- var value = $F(this.p.trigger).replace(/^,/, '').strip();
- if (value.length) {
- this.addNewItemNode(value);
- this.p.onAdd(value);
- }
- },
-
- _resize: function()
- {
- this.sizer.update($(this.p.trigger).value);
- newSize = Math.min(this.sizer.getWidth(), $(this.p.box).getWidth());
- newSize = Math.max(newSize, this.p.minTriggerWidth);
- $(this.p.trigger).setStyle({ width: newSize + 'px' });
- },
-
- // Used as the updateElement callback.
- _updateElement: function(item)
- {
- this.addNewItemNode(item);
- this.p.onAdd(item);
- },
-
- addNewItemNode: function(value)
- {
- // Don't add if it's already present.
- for (var x = 0, len = this.selectedItems.length; x < len; x++) {
- if (this.selectedItems[x].rawValue == value) {
- $(this.p.trigger).value = '';
- return;
- }
- }
-
- var displayValue = this.p.displayFilter(value),
- newItem = new Element('li', { className: this.p.listClass + 'Member ' + this.p.listClass + 'Item' }).update(displayValue),
- x = new Element('img', { className: 'hordeACItemRemove', src: this.p.deleteIcon });
- x.observe('click', this._removeItemHandler.bindAsEventListener(this));
- newItem.insert(x);
- $(this.p.triggerContainer).insert({ before: newItem });
- $(this.p.trigger).value = '';
-
- // Add to hidden input field.
- if ($(this.p.items).value) {
- $(this.p.items).value = $(this.p.items).value + ', ' + value;
- } else {
- $(this.p.items).value = value;
- }
-
- // ...and keep the selectedItems array up to date.
- this.selectedItems.push({ rawValue: value, displayValue: displayValue });
- },
-
- removeItemNode: function(item)
- {
- var value = item.collectTextNodesIgnoreClass('informal');
- for (var x = 0, len = this.selectedItems.length; x < len; x++) {
- if (this.selectedItems[x].displayValue.unescapeHTML() == value) {
- this.selectedItems.splice(x, 1);
- break;
- }
- }
- item.remove();
- this.p.onRemove(value);
- },
-
- disable: function()
- {
- if (!this._enabled || !this.initialized) {
- return;
- }
-
- this._enabled = false;
- $(this.p.box).select('.hordeACItemRemove').invoke('toggle');
- $(this.p.trigger).disable();
- },
-
- enable: function()
- {
- if (this._enabled) {
- return;
- }
- this._enabled = true;
- $(this.p.box).select('.hordeACItemRemove').invoke('toggle');
- $(this.p.trigger).enable();
- },
-
- _removeItemHandler: function(e)
- {
- var realValues = [], x, len;
- this.removeItemNode(e.element().up());
- for (x = 0, len = this.selectedItems.length; x < len; x++) {
- realValues.push(this.selectedItems[x].rawValue);
- }
- $(this.p.items).value = realValues.join(',');
- },
-
- _filterChoices: function(c)
- {
- this.selectedItems.each(function(item) {
- c = c.without(item.rawValue);
- });
- return c;
- },
-
- _knvShow: function(l)
- {
- $(this.p.trigger).stopObserving('blur', this._boundProcessValue);
- },
-
- _knvHide: function(l)
- {
- $(this.p.trigger).observe('blur', this._boundProcessValue);
- }
-});
+++ /dev/null
-/**
- * Provides the javascript for managing the source selection widget.
- *
- * See the enclosed file COPYING for license information (LGPL). If you
- * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
- */
-
-var HordeSourceSelectPrefs = {
-
- // Vars defaulting to null: source_list
-
- setSourcesHidden: function()
- {
- var out = [], ss;
-
- if (this.source_list) {
- ss = $F('source_select');
- if (ss) {
- this.source_list.each(function(s) {
- if (s.source == ss) {
- s.selected = $F('selected_sources');
- }
- out.push([ s.source, s.selected ]);
- });
- }
- } else {
- $A($('selected_sources').options).slice(1).each(function(s) {
- out.push(s.value);
- });
- }
-
- $('sources').setValue(Object.toJSON(out));
- },
-
- moveAction: function(from, to)
- {
- var moved = false;
-
- $(from).childElements().each(function(c) {
- if (c.selected) {
- c.remove();
- c.selected = false;
- $(to).insert(c);
- moved = true;
- }
- });
-
-
- if (moved) {
- $(to).fire('HordeSourceSelectPrefs:add');
- $(from).fire('HordeSourceSelectPrefs:remove');
- this.setSourcesHidden();
- }
- },
-
- moveSource: function(e, mode)
- {
- var sa = $('selected_sources'), sel, tmp;
-
- if (sa.selectedIndex < 1 || sa.length < 3) {
- return;
- }
-
- // Deselect everything but the first selected item
- sa.childElements().each(function(s) {
- if (sel) {
- s.selected = false;
- } else if (s.selected) {
- sel = s;
- }
- });
-
- switch (mode) {
- case 'down':
- tmp = sel.next();
- if (tmp) {
- sel.remove();
- tmp.insert({ after: sel });
- }
- break;
-
- case 'up':
- tmp = sel.previous();
- if (tmp && tmp.value) {
- sel.remove();
- tmp.insert({ before: sel });
- }
- break;
- }
-
- this.setSourcesHidden();
- e.stop();
- },
-
- changeSource: function()
- {
- var source,
- sel = $('selected_sources'),
- ss = $('source_select'),
- unsel = $('unselected_sources'),
- val = $F(ss);
-
- sel.down().siblings().invoke('remove');
- unsel.down().siblings().invoke('remove');
-
- if (val) {
- source = this.source_list.find(function(s) {
- return val == s.source;
- });
- source.selected.each(function(s) {
- sel.insert(new Option(s.v, s.l));
- });
- source.unselected.each(function(u) {
- unsel.insert(new Option(s.v, s.l));
- });
- }
- },
-
- onDomLoad: function()
- {
- if (this.source_list) {
- $('source_select').observe('change', this.changeSource.bind(this));
- }
-
- this.setSourcesHidden();
-
- $('addsource').observe('click', this.moveAction.bind(this, 'unselected_sources', 'selected_sources'));
- $('removesource').observe('click', this.moveAction.bind(this, 'selected_sources', 'unselected_sources'));
- $('moveup').observe('click', this.moveSource.bindAsEventListener(this, 'up'));
- $('movedown').observe('click', this.moveSource.bindAsEventListener(this, 'down'));
- }
-
-};
-
-document.observe('dom:loaded', HordeSourceSelectPrefs.onDomLoad.bind(HordeSourceSelectPrefs));
+++ /dev/null
-/**
- * This spell checker was inspired by work done by Garrison Locke, but
- * was rewritten almost completely by Chuck Hagenbuch to use
- * Prototype/Scriptaculous.
- *
- * Requires: prototype.js (v1.6.1+), KeyNavList.js
- *
- * Copyright 2005-2010 The Horde Project (http://www.horde.org/)
- *
- * Custom Events:
- * --------------
- * Custom events are triggered on the target element.
- *
- * 'SpellChecker:after'
- * Fired when the spellcheck processing ends.
- *
- * 'SpellChecker:before'
- * Fired before the spellcheck is performed.
- *
- * 'SpellChecker:noerror'
- * Fired when no spellcheck errors are found.
- *
- * See the enclosed file COPYING for license information (GPL). If you
- * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
- */
-
-var SpellChecker = Class.create({
-
- // Vars used and defaulting to null:
- // bad, choices, disabled, htmlAreaParent, lc, locale, reviewDiv,
- // statusButton, statusClass, suggestions, target, url
- options: {},
- resumeOnDblClick: true,
- state: 'CheckSpelling',
-
- // Options:
- // bs = (array) Button states
- // locales = (array) List of locales. See KeyNavList for structure.
- // sc = (string) Status class
- // statusButton = (string/element) DOM ID or element of the status
- // button
- // target = (string|Element) DOM element containing data
- // url = (string) URL of specllchecker handler
- initialize: function(opts)
- {
- var d, lc, tmp, ul;
-
- this.url = opts.url;
- this.target = $(opts.target);
- this.statusButton = $(opts.statusButton);
- this.buttonStates = opts.bs;
- this.statusClass = opts.sc || '';
- this.disabled = false;
-
- this.options.onComplete = this.onComplete.bind(this);
-
- document.observe('click', this.onClick.bindAsEventListener(this));
-
- if (opts.locales) {
- this.lc = new KeyNavList(this.statusButton, {
- list: opts.locales,
- onChoose: this.setLocale.bindAsEventListener(this)
- });
-
- this.statusButton.insert({ after: new Element('SPAN', { className: 'spellcheckPopdownImg' }) });
- }
-
- this.setStatus('CheckSpelling');
- },
-
- setLocale: function(locale)
- {
- this.locale = locale;
- },
-
- targetValue: function()
- {
- return Object.isUndefined(this.target.value)
- ? this.target.innerHTML
- : this.target.value;
- },
-
- spellCheck: function()
- {
- this.target.fire('SpellChecker:before');
-
- var opts = Object.clone(this.options),
- p = $H(),
- url = this.url;
-
- this.setStatus('Checking');
-
- p.set(this.target.identify(), this.targetValue());
- opts.parameters = p.toQueryString();
-
- if (this.locale) {
- url += '/locale=' + this.locale;
- }
- if (this.htmlAreaParent) {
- url += '/html=1';
- }
-
- new Ajax.Request(url, opts);
- },
-
- onComplete: function(request)
- {
- var bad, content, washidden,
- i = 0,
- result = request.responseJSON;
-
- if (Object.isUndefined(result)) {
- this.setStatus('Error');
- return;
- }
-
- this.suggestions = result.suggestions || [];
-
- if (!this.suggestions.size()) {
- this.setStatus('CheckSpelling');
- this.target.fire('SpellChecker:noerror');
- return;
- }
-
- bad = result.bad || [];
-
- content = this.targetValue();
- content = this.htmlAreaParent
- ? content.replace(/\r?\n/g, '')
- : content.replace(/\r?\n/g, '~~~').escapeHTML();
-
- $A(bad).each(function(node) {
- var re_text = '<span index="' + (i++) + '" class="spellcheckIncorrect">' + node + '</span>';
- content = content.replace(new RegExp("(?:^|\\b)" + RegExp.escape(node) + "(?:\\b|$)", 'g'), re_text);
-
- // Go through and see if we matched anything inside a tag (i.e.
- // class/spellcheckIncorrect is often matched if using a
- // non-English lang).
- content = content.replace(new RegExp("(<[^>]*)" + RegExp.escape(re_text) + "([^>]*>)", 'g'), '\$1' + node + '\$2');
- }, this);
-
- if (!this.reviewDiv) {
- this.reviewDiv = new Element('DIV', { className: this.target.readAttribute('class') }).addClassName('spellcheck').setStyle({ overflow: 'auto' });
- if (this.resumeOnDblClick) {
- this.reviewDiv.observe('dblclick', this.resume.bind(this));
- }
- }
-
- if (!this.target.visible()) {
- this.target.show();
- washidden = true;
- }
- this.reviewDiv.setStyle({ width: this.target.clientWidth + 'px', height: this.target.clientHeight + 'px'});
- if (washidden) {
- this.target.hide();
- }
-
- if (!this.htmlAreaParent) {
- content = content.replace(/~~~/g, '<br />');
- }
- this.reviewDiv.update(content);
-
- if (this.htmlAreaParent) {
- $(this.htmlAreaParent).insert({ bottom: this.reviewDiv });
- } else {
- this.target.hide().insert({ before: this.reviewDiv });
- }
-
- this.setStatus('ResumeEdit');
- },
-
- onClick: function(e)
- {
- var data = [], index, elt = e.element();
-
- if (this.disabled) {
- return;
- }
-
- if (elt == this.statusButton) {
- switch (this.state) {
- case 'CheckSpelling':
- this.spellCheck();
- break;
-
- case 'ResumeEdit':
- this.resume();
- break;
- }
-
- e.stop();
- } else if (elt.hasClassName('spellcheckPopdownImg')) {
- this.lc.show();
- this.lc.ignoreClick(e);
- e.stop();
- } else if (elt.hasClassName('spellcheckIncorrect')) {
- index = e.element().readAttribute('index');
-
- $A(this.suggestions[index]).each(function(node) {
- data.push({ l: node, v: node });
- });
-
- if (this.choices) {
- this.choices.updateBase(elt);
- this.choices.opts.onChoose = function(val) {elt.update(val).writeAttribute({ className: 'spellcheckCorrected' });};
- } else {
- this.choices = new KeyNavList(elt, {
- esc: true,
- onChoose: function(val) {
- elt.update(val).writeAttribute({ className: 'spellcheckCorrected' });
- }
- });
- }
-
- this.choices.show(data);
- this.choices.ignoreClick(e);
- e.stop();
- }
- },
-
- resume: function()
- {
- if (!this.reviewDiv) {
- return;
- }
-
- var t;
-
- this.reviewDiv.select('span.spellcheckIncorrect').each(function(n) {
- n.replace(n.innerHTML);
- });
-
- t = this.reviewDiv.innerHTML;
- if (!this.htmlAreaParent) {
- t = t.replace(/<br *\/?>/gi, '~~~').unescapeHTML().replace(/~~~/g, "\n");
- }
- this.target.setValue(t);
- this.target.enable();
-
- if (this.resumeOnDblClick) {
- this.reviewDiv.stopObserving('dblclick');
- }
- this.reviewDiv.remove();
- this.reviewDiv = null;
-
- this.setStatus('CheckSpelling');
-
- if (!this.htmlAreaParent) {
- this.target.show();
- }
-
- this.target.fire('SpellChecker:after');
- },
-
- setStatus: function(state)
- {
- if (!this.statusButton) {
- return;
- }
-
- this.state = state;
- switch (this.statusButton.tagName) {
- case 'INPUT':
- this.statusButton.setValue(this.buttonStates[state]);
- break;
-
- case 'A':
- this.statusButton.update(this.buttonStates[state]);
- break;
- }
- this.statusButton.className = this.statusClass + ' spellcheck' + state;
- },
-
- isActive: function()
- {
- return this.reviewDiv;
- },
-
- disable: function(disable)
- {
- this.disabled = disable;
- }
-
-});