This file seems to have disappeared at some point
authorMichael M Slusarz <slusarz@curecanti.org>
Mon, 24 May 2010 19:25:08 +0000 (13:25 -0600)
committerMichael M Slusarz <slusarz@curecanti.org>
Mon, 24 May 2010 19:25:08 +0000 (13:25 -0600)
horde/js/spellchecker.js [new file with mode: 0644]

diff --git a/horde/js/spellchecker.js b/horde/js/spellchecker.js
new file mode 100644 (file)
index 0000000..2b8a8c6
--- /dev/null
@@ -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 = '<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;
+    }
+
+});