From: Michael M Slusarz Date: Mon, 24 May 2010 19:25:08 +0000 (-0600) Subject: This file seems to have disappeared at some point X-Git-Url: https://git.internetallee.de/?a=commitdiff_plain;h=d8074ffea038de1ab97c135c73cea6a513c8e783;p=horde.git This file seems to have disappeared at some point --- diff --git a/horde/js/spellchecker.js b/horde/js/spellchecker.js new file mode 100644 index 000000000..2b8a8c67b --- /dev/null +++ b/horde/js/spellchecker.js @@ -0,0 +1,284 @@ +/** + * 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 = '' + node + ''; + 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, '
'); + } + 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(/
/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; + } + +});