+++ /dev/null
-/**
- * Reuseable keyboard or mouse driven list component. Based on
- * Scriptaculous' AutoCompleter.
- *
- * Copyright 2005-2009 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 KeyNavList = Class.create({
- // Vars used and defaulting to empty:
- // active, entryCount
-
- initialize: function(element, options)
- {
- var clickfunc = this.onClick.bindAsEventListener(this),
- entry,
- i = 0,
- overfunc = this.onHover.bindAsEventListener(this);
-
- this.element = $(element);
- this.options = options || {};
- this.index = -1;
-
- this.entryCount = this.element.firstDescendant().childElements().size();
- for (; i < this.entryCount; i++) {
- entry = this.getEntry(i);
- entry.writeAttribute('autocompleteIndex', i);
- entry.observe('click', clickfunc);
- entry.observe('mouseover', overfunc);
- }
-
- this.options.onShow = this.options.onShow ||
- function(elt) { new Effect.Appear(elt, { duration: 0.15 }); };
- this.options.onHide = this.options.onHide ||
- function(elt) { new Effect.Fade(elt, { duration: 0.15 }); };
-
- this.element.observe('blur', this.onBlur.bind(this));
- document.observe('keypress', this.onKeyPress.bindAsEventListener(this));
- },
-
- show: function()
- {
- this.active = true;
- if (!this.element.visible()) {
- this.options.onShow(this.element);
- }
- if (!this.iefix &&
- (navigator.appVersion.indexOf('MSIE') > 0) &&
- (this.element.getStyle('position') == 'absolute')) {
- this.element.insert({ after:
- '<iframe id="' + this.element.id + '_iefix" '
- + 'style="display:none;position:absolute;filter:progid:DXImageTransform.Microsoft.Alpha(opacity=0);" '
- + 'src="javascript:false;" frameborder="0" scrolling="no"></iframe>'});
- this.iefix = $(this.element.id + '_iefix');
- }
- if (this.iefix) {
- setTimeout(this.fixIEOverlapping.bind(this), 50);
- }
- },
-
- fixIEOverlapping: function()
- {
- this.iefix.clonePosition(this.element).setStyle({ zIndex: 1 }).show();
- this.element.setStyle({ zIndex: 2 });
- },
-
- hide: function()
- {
- this.active = false;
- this.stopIndicator();
- if (this.element.visible()) {
- this.options.onHide(this.element);
- }
- if (this.iefix) {
- this.iefix.hide();
- }
- },
-
- startIndicator: function()
- {
- if (this.options.indicator) {
- $(this.options.indicator).show();
- }
- },
-
- stopIndicator: function()
- {
- if (this.options.indicator) {
- $(this.options.indicator).hide();
- }
- },
-
- onKeyPress: function(e)
- {
- if (!this.active) {
- return;
- }
-
- switch (e.keyCode) {
- case Event.KEY_TAB:
- case Event.KEY_RETURN:
- this.selectEntry();
- e.stop();
-
- case Event.KEY_ESC:
- this.hide();
- this.active = false;
- e.stop();
- return;
-
- case Event.KEY_LEFT:
- case Event.KEY_RIGHT:
- return;
-
- case Event.KEY_UP:
- this.markPrevious();
- this.render();
- e.stop();
- return;
-
- case Event.KEY_DOWN:
- this.markNext();
- this.render();
- e.stop();
- return;
- }
- },
-
- onHover: function(e)
- {
- var element = e.findElement('LI'),
- index = parseInt(element.readAttribute('autocompleteIndex'));
- if (this.index != index) {
- this.index = index;
- this.render();
- }
- e.stop();
- },
-
- onClick: function(e)
- {
- var element = e.findElement('LI');
- this.index = parseInt(element.readAttribute('autocompleteIndex'));
- this.selectEntry();
- this.hide();
- e.stop();
- },
-
- onBlur: function()
- {
- setTimeout(this.hide.bind(this), 250);
- this.active = false;
- },
-
- render: function()
- {
- if (this.entryCount > 0) {
- for (var i = 0; i < this.entryCount; i++) {
- [ this.getEntry(i) ].invoke(this.index == i ? 'addClassName' : 'removeClassName', 'selected');
- }
- this.show();
- this.active = true;
- } else {
- this.hide();
- this.active = false;
- }
- },
-
- markPrevious: function()
- {
- if (this.index > 0) {
- this.index--;
- } else {
- this.index = this.entryCount - 1;
- }
- },
-
- markNext: function()
- {
- if (this.index < this.entryCount - 1) {
- this.index++;
- } else {
- this.index = 0;
- }
- },
-
- getEntry: function(index)
- {
- return this.element.down().down(index);
- },
-
- getCurrentEntry: function()
- {
- return this.getEntry(this.index);
- },
-
- selectEntry: function()
- {
- this.active = false;
- if (typeof this.options.onChoose == 'function') {
- this.options.onChoose(this.getCurrentEntry());
- }
- }
-
-});
+++ /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.
- *
- * Copyright 2005-2009 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 SpellChecker = Class.create({
-
- // Vars used and defaulting to null:
- // bad, choices, choicesDiv, curWord, htmlArea, htmlAreaParent, locale,
- // localeChoices, onAfterSpellCheck, onBeforeSpellCheck, onNoError,
- // reviewDiv, statusButton, statusClass, suggestions, target, url
- options: {},
- resumeOnDblClick: true,
- state: 'CheckSpelling',
-
- // url = (string) URL of specllchecker handler
- // target = (string) DOM ID of message data
- // statusButton = (string/element) DOM ID or element of the status button
- // bs = (array) Button states
- // locales = (array) List of locales
- // sc = (string) Status class
- initialize: function(url, target, statusButton, bs, locales, sc)
- {
- var d, popdown, ul;
-
- this.url = url;
- this.target = target;
- this.statusButton = $(statusButton);
- this.buttonStates = bs;
- this.statusClass = sc || '';
-
- this.statusButton.observe('click', this.process.bindAsEventListener(this));
- this.options.onComplete = this.onComplete.bind(this);
-
- if (locales) {
- d = new Element('DIV', { className: 'autocomplete', id: 'spellcheckpopdown' }).setStyle({ position: 'absolute' }).hide();
- ul = new Element('UL');
- $H(locales).each(function(pair) {
- ul.insert({ bottom: new Element('LI', { lc: pair.key }).update(pair.value) });
- });
- d.insert({ bottom: ul });
-
- this.localeChoices = new KeyNavList(d, { onChoose: this.setLocale.bindAsEventListener(this) });
-
- popdown = new Element('SPAN', { className: 'spellcheckPopdownImg' }).observe('click', function() {
- $('spellcheckpopdown').clonePosition(this.statusButton, {
- setHeight: false,
- setWidth: false,
- offsetTop: this.statusButton.getHeight()
- });
- this.localeChoices.show();
- }.bind(this));
-
- this.statusButton.insert({ after: popdown });
-
- $(document.body).insert(d);
- }
-
- // We need to monitor clicks to know when to go out of
- // showSuggestions mode.
- document.observe('click', this.onClick.bindAsEventListener(this));
-
- this.setStatus('CheckSpelling');
- },
-
- setLocale: function(e)
- {
- this.locale = e.readAttribute('lc');
- },
-
- targetValue: function()
- {
- var input = $(this.target);
- return (Object.isUndefined(input.value)) ? input.innerHTML : input.value;
- },
-
- process: function(e)
- {
- switch (this.state) {
- case 'CheckSpelling':
- this.spellCheck();
- break;
-
- case 'ResumeEdit':
- this.resume();
- break;
- }
-
- e.stop();
- },
-
- // noerror - (function) A callback function to run if no errors are
- // identified. If not specified, will remain in spell check
- // mode even if no errors are present.
- spellCheck: function(noerror)
- {
- if (this.onBeforeSpellCheck) {
- this.onBeforeSpellCheck();
- }
-
- var opts = Object.clone(this.options),
- p = $H(),
- url = this.url;
-
- this.setStatus('Checking');
- this.removeChoices();
-
- this.onNoError = noerror;
-
- p.set(this.target, this.targetValue());
- opts.parameters = p.toQueryString();
-
- if (this.locale) {
- url += '/locale=' + this.locale;
- }
- if (this.htmlAreaParent) {
- url += '/html=1';
- }
-
- new Ajax.Request(url, opts);
- },
-
- onClick: function(e)
- {
- // If the language dropdown is currently active, and the click
- // got here, then we should hide the language dropdown.
- if (this.localeChoices) {
- this.localeChoices.hide();
- }
-
- // If we're not currently showing any choices, then we return
- // and let the click go.
- if (!this.choicesDiv) {
- return true;
- }
-
- // If the click was on the word that we're currently showing
- // results for, then we do nothing and let the list be
- // redisplayed by the link's own onclick handler.
- var link = e.findElement('SPAN');
- if (link && link == this.curWord) {
- return true;
- }
-
- // The KeyNavList will have already handled any valid clicks,
- // so that just leaves the rest of the window - in this case
- // we want to hide the KeyNavList and reset curWord without
- // changing anything.
- this.removeChoices();
- this.curWord = null;
- },
-
- onComplete: function(request)
- {
- var bad, content, d,
- i = 0,
- input = $(this.target),
- result = request.responseJSON;
-
- this.removeChoices();
-
- if (Object.isUndefined(result)) {
- this.setStatus('Error');
- return;
- }
-
- this.suggestions = result.suggestions || [];
-
- if (this.onNoError && !this.suggestions.size()) {
- this.setStatus('CheckSpelling');
- this.onNoError();
- 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: input.readAttribute('className') + ' spellcheck' }).setStyle({ overflow: 'auto' });
- if (this.resumeOnDblClick) {
- this.reviewDiv.observe('dblclick', this.resume.bind(this));
- }
- }
-
- d = input.getDimensions();
- this.reviewDiv.setStyle({ width: d.width + 'px', height: d.height + 'px'});
-
- if (!this.htmlAreaParent) {
- content = content.replace(/~~~/g, '<br />');
- }
- this.reviewDiv.update(content);
-
- // Now attach results behavior to each link.
- this.reviewDiv.select('span.spellcheckIncorrect').invoke('observe', 'click', this.showSuggestions.bindAsEventListener(this));
-
- // Falsify links
- this.reviewDiv.select('A').invoke('observe', 'click', Event.stop);
-
- if (this.htmlAreaParent) {
- // Fix for Safari 3/fckeditor - Ticket #6909
- Element.hide(this.htmlArea);
- $(this.htmlAreaParent).insert({ bottom: this.reviewDiv });
- } else {
- input.hide().insert({ before: this.reviewDiv });
- }
-
- this.setStatus('ResumeEdit');
- },
-
- showSuggestions: function(e)
- {
- var pos,
- elt = e.element(),
- ul = new Element('UL');
-
- try {
- pos = elt.viewportOffset();
- } catch (err) {
- // Fix for Safari 3/fckeditor - Ticket #6909
- pos = [ e.pointerX(), e.pointerY() ];
- }
-
- this.removeChoices();
- this.choicesDiv = new Element('DIV', { className: 'autocomplete' }).setStyle({ left: pos[0] + 'px', top: (pos[1] + 16) + 'px' }).hide();
- this.curWord = elt;
-
- $A(this.suggestions[this.curWord.readAttribute('index')]).each(function(node) {
- ul.insert({ bottom: new Element('LI').update(node) });
- });
- this.choicesDiv.insert({ bottom: ul });
-
- this.choices = new KeyNavList(this.choicesDiv, { onChoose: function(elt) { this.replaceWord(elt, this.curWord); }.bind(this) }).show();
- $(document.body).insert(this.choicesDiv);
- },
-
- replaceWord: function(li, word)
- {
- word.update(li.innerHTML).writeAttribute({ className: 'spellcheckCorrected' });
- this.removeChoices();
- },
-
- removeChoices: function()
- {
- if (this.choicesDiv) {
- this.choicesDiv.remove();
- this.choicesDiv = null;
- }
- },
-
- resume: function()
- {
- this.removeChoices();
-
- if (!this.reviewDiv) {
- return;
- }
-
- var input = $(this.target),
- t;
-
- this.reviewDiv.select('span.spellcheckIncorrect').each(function(n) {
- n.replace(n.innerHTML);
- });
-
- // Unfalsify links
- this.reviewDiv.select('A').invoke('stopObserving', 'click');
-
- t = this.reviewDiv.innerHTML;
- if (!this.htmlAreaParent) {
- t = t.replace(/<br *\/?>/gi, '~~~').unescapeHTML().replace(/~~~/g, "\n");
- }
- input.value = t;
- input.disabled = false;
-
- if (this.resumeOnDblClick) {
- this.reviewDiv.stopObserving('dblclick');
- }
- this.reviewDiv.remove();
- this.reviewDiv = null;
-
- this.setStatus('CheckSpelling');
-
- if (this.htmlAreaParent) {
- // Fix for Safari 3/fckeditor - Ticket #6909
- Element.show(this.htmlArea);
- } else {
- input.show();
- }
-
- if (this.onAfterSpellCheck) {
- this.onAfterSpellCheck();
- }
- },
-
- setStatus: function(state)
- {
- if (!this.statusButton) {
- return;
- }
-
- this.state = state;
- switch (this.statusButton.tagName) {
- case 'INPUT':
- this.statusButton.value = 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);
- }
-
-});
+++ /dev/null
-<?php
-/**
- * Attach the spellchecker to a javascript element.
- *
- * Copyright 2005-2009 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 Michael Slusarz <slusarz@horde.org>
- * @package IMP
- */
-class IMP_Ajax_Imple_SpellChecker extends Horde_Ajax_Imple_Base
-{
- /**
- * Constructor.
- *
- * @param array $params Configuration parameters.
- * <pre>
- * 'id' => TODO (optional)
- * 'locales' => TODO (optional)
- * 'states' => TODO (optional)
- * 'targetId' => TODO (optional)
- * 'triggerId' => TODO (optional)
- * </pre>
- */
- public function __construct($params = array())
- {
- if (empty($params['id'])) {
- $params['id'] = $this->_randomid();
- }
- if (empty($params['targetId'])) {
- $params['targetId'] = $this->_randomid();
- }
- if (empty($params['triggerId'])) {
- $params['triggerId'] = $params['targetId'] . '_trigger';
- }
- if (empty($params['states'])) {
- $params['states'] = '""';
- } else {
- $params['states'] = Horde_Serialize::serialize($params['states'], Horde_Serialize::JSON, Horde_Nls::getCharset());
- }
- if (empty($params['locales'])) {
- $params['locales'] = array();
- foreach (array_keys(Horde_Nls::$config['spelling']) as $lcode) {
- $params['locales'][$lcode] = Horde_Nls::$config['languages'][$lcode];
- }
- }
- asort($params['locales'], SORT_LOCALE_STRING);
- $params['locales'] = Horde_Serialize::serialize($params['locales'], Horde_Serialize::JSON, Horde_Nls::getCharset());
-
- parent::__construct($params);
- }
-
- /**
- */
- public function attach()
- {
- Horde::addScriptFile('prototype.js', 'horde', true);
- Horde::addScriptFile('effects.js', 'horde', true);
- Horde::addScriptFile('KeyNavList.js', 'imp', true);
- Horde::addScriptFile('SpellChecker.js', 'imp', true);
-
- $url = $this->_getUrl('SpellChecker', 'imp', array('input' => $this->_params['targetId']));
-
- IMP::addInlineScript($this->_params['id'] . ' = new SpellChecker("' . $url . '", "' . $this->_params['targetId'] . '", "' . $this->_params['triggerId'] . '", ' . $this->_params['states'] . ', ' . $this->_params['locales'] . ', \'widget\');', 'dom');
- }
-
- /**
- */
- public function handle($args)
- {
- $spellArgs = array();
-
- if (!empty($GLOBALS['conf']['spell']['params'])) {
- $spellArgs = $GLOBALS['conf']['spell']['params'];
- }
-
- if (isset($args['locale'])) {
- $spellArgs['locale'] = $args['locale'];
- } elseif (isset($GLOBALS['language'])) {
- $spellArgs['locale'] = $GLOBALS['language'];
- }
-
- /* Add local dictionary words. */
- try {
- $result = Horde::loadConfiguration('spelling.php', 'ignore_list', 'horde');
- $spellArgs['localDict'] = $result;
- } catch (Horde_Exception $e) {}
-
- if (!empty($args['html'])) {
- $spellArgs['html'] = true;
- }
-
- try {
- $speller = Horde_SpellChecker::getInstance($GLOBALS['conf']['spell']['driver'], $spellArgs);
- } catch (Horde_Exception $e) {
- Horde::logMessage($e, __FILE__, __LINE__, PEAR_LOG_ERR);
- return array();
- }
-
- try {
- return $speller->spellCheck(Horde_Util::getPost($args['input']));
- } catch (Horde_Exception $e) {
- Horde::logMessage($e, __FILE__, __LINE__, PEAR_LOG_ERR);
- return array('bad' => array(), 'suggestions' => array());
- }
- }
-
-}