Changeset 66df084 in github


Ignore:
Timestamp:
Sep 6, 2011 9:39:45 AM (21 months ago)
Author:
alecpl <alec@…>
Branches:
master, HEAD, courier-fix, dev-browser-capabilities, pdo, release-0.7, release-0.8
Children:
f8e48df
Parents:
eb2365c
Message:
  • Merge devel-spellcheck branch:
    • Added spellchecker exceptions dictionary (shared or per-user)
    • Added possibility to ignore words containing caps, numbers, symbols (spellcheck_ignore_* options)
Files:
20 edited

Legend:

Unmodified
Added
Removed
  • CHANGELOG

    reb2365c r66df084  
    11CHANGELOG Roundcube Webmail 
    22=========================== 
     3 
     4- Added spellchecker exceptions dictionary (shared or per-user) 
     5- Added possibility to ignore words containing caps, numbers, symbols (spellcheck_ignore_* options) 
    36 
    47RELEASE 0.6-rc 
  • SQL/mssql.initial.sql

    r3a5476d1 r66df084  
    9494GO 
    9595 
     96CREATE TABLE [dbo].[dictionary] ( 
     97        [user_id] [int] , 
     98        [language] [varchar] (5) COLLATE Latin1_General_CI_AI NOT NULL , 
     99        [data] [text] COLLATE Latin1_General_CI_AI NOT NULL  
     100) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY] 
     101GO 
     102 
    96103ALTER TABLE [dbo].[cache] WITH NOCHECK ADD  
    97104         PRIMARY KEY  CLUSTERED  
     
    265272GO 
    266273 
     274CREATE  UNIQUE INDEX [IX_dictionary_user_language] ON [dbo].[dictionary]([user_id],[language]) ON [PRIMARY] 
     275GO 
     276 
    267277ALTER TABLE [dbo].[identities] ADD CONSTRAINT [FK_identities_user_id]  
    268278    FOREIGN KEY ([user_id]) REFERENCES [dbo].[users] ([user_id]) 
  • SQL/mssql.upgrade.sql

    rfaf10e8 r66df084  
    111111DELETE FROM [dbo].[cache] 
    112112GO 
     113 
     114-- Updates from version 0.6-stable 
     115 
     116CREATE TABLE [dbo].[dictionary] ( 
     117    [user_id] [int] , 
     118    [language] [varchar] (5) COLLATE Latin1_General_CI_AI NOT NULL , 
     119    [data] [text] COLLATE Latin1_General_CI_AI NOT NULL  
     120) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY] 
     121GO 
     122CREATE  UNIQUE INDEX [IX_dictionary_user_language] ON [dbo].[dictionary]([user_id],[language]) ON [PRIMARY] 
     123GO 
  • SQL/mysql.initial.sql

    r09b0e36 r66df084  
    145145 
    146146 
     147-- Table structure for table `dictionary` 
     148 
     149CREATE TABLE `dictionary` ( 
     150  `user_id` int(10) UNSIGNED DEFAULT NULL, 
     151  `language` varchar(5) NOT NULL, 
     152  `data` longtext NOT NULL, 
     153  CONSTRAINT `user_id_fk_dictionary` FOREIGN KEY (`user_id`) 
     154    REFERENCES `users`(`user_id`) ON DELETE CASCADE ON UPDATE CASCADE, 
     155  UNIQUE `uniqueness` (`user_id`, `language`) 
     156) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */; 
     157 
    147158/*!40014 SET FOREIGN_KEY_CHECKS=1 */; 
  • SQL/mysql.update.sql

    rfaf10e8 r66df084  
    145145TRUNCATE TABLE `messages`; 
    146146TRUNCATE TABLE `cache`; 
     147 
     148-- Updates from version 0.6-stable 
     149 
     150CREATE TABLE `dictionary` ( 
     151  `user_id` int(10) UNSIGNED DEFAULT NULL, 
     152  `language` varchar(5) NOT NULL, 
     153  `data` longtext NOT NULL, 
     154  CONSTRAINT `user_id_fk_dictionary` FOREIGN KEY (`user_id`) 
     155    REFERENCES `users`(`user_id`) ON DELETE CASCADE ON UPDATE CASCADE, 
     156  UNIQUE `uniqueness` (`user_id`, `language`) 
     157) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */; 
  • SQL/postgres.initial.sql

    r8381ec1 r66df084  
    226226CREATE INDEX messages_index_idx ON messages (user_id, cache_key, idx); 
    227227CREATE INDEX messages_created_idx ON messages (created); 
     228 
     229-- 
     230-- Table "dictionary" 
     231-- Name: dictionary; Type: TABLE; Schema: public; Owner: postgres 
     232-- 
     233 
     234CREATE TABLE dictionary ( 
     235    user_id integer DEFAULT NULL 
     236        REFERENCES users (user_id) ON DELETE CASCADE ON UPDATE CASCADE, 
     237   "language" varchar(5) NOT NULL, 
     238    data text NOT NULL, 
     239    CONSTRAINT dictionary_user_id_language_key UNIQUE (user_id, "language") 
     240); 
  • SQL/postgres.update.sql

    rfaf10e8 r66df084  
    101101TRUNCATE messages; 
    102102TRUNCATE cache; 
     103 
     104-- Updates from version 0.6-stable 
     105 
     106CREATE TABLE dictionary ( 
     107    user_id integer DEFAULT NULL 
     108        REFERENCES users (user_id) ON DELETE CASCADE ON UPDATE CASCADE, 
     109   "language" varchar(5) NOT NULL, 
     110    data text NOT NULL, 
     111    CONSTRAINT dictionary_user_id_language_key UNIQUE (user_id, "language") 
     112); 
  • SQL/sqlite.initial.sql

    r3a5476d1 r66df084  
    147147CREATE INDEX ix_messages_index ON messages (user_id,cache_key,idx); 
    148148CREATE INDEX ix_messages_created ON messages (created); 
     149 
     150-- -------------------------------------------------------- 
     151 
     152-- 
     153-- Table structure for table dictionary 
     154-- 
     155 
     156CREATE TABLE dictionary ( 
     157    user_id integer DEFAULT NULL, 
     158   "language" varchar(5) NOT NULL, 
     159    data text NOT NULL 
     160); 
     161 
     162CREATE UNIQUE INDEX ix_dictionary_user_language ON dictionary (user_id, "language"); 
     163 
  • SQL/sqlite.update.sql

    rfaf10e8 r66df084  
    227227DELETE FROM cache; 
    228228CREATE INDEX ix_contactgroupmembers_contact_id ON contactgroupmembers (contact_id); 
     229 
     230-- Updates from version 0.6-stable 
     231 
     232CREATE TABLE dictionary ( 
     233    user_id integer DEFAULT NULL, 
     234   "language" varchar(5) NOT NULL, 
     235    data text NOT NULL 
     236); 
     237 
     238CREATE UNIQUE INDEX ix_dictionary_user_language ON dictionary (user_id, "language"); 
  • config/main.inc.php.dist

    r67eecde r66df084  
    428428$rcmail_config['enable_spellcheck'] = true; 
    429429 
     430// Enables spellchecker exceptions dictionary. 
     431// Setting it to 'shared' will make the dictionary shared by all users. 
     432$rcmail_config['spellcheck_dictionary'] = false; 
     433 
    430434// Set the spell checking engine. 'googie' is the default. 'pspell' is also available, 
    431435// but requires the Pspell extensions. When using Nox Spell Server, also set 'googie' here. 
     
    442446// Leave empty for default set of available language. 
    443447$rcmail_config['spellcheck_languages'] = NULL; 
     448 
     449// Makes that words with all letters capitalized will be ignored (e.g. GOOGLE) 
     450$rcmail_config['spellcheck_ignore_caps'] = false; 
     451 
     452// Makes that words with numbers will be ignored (e.g. g00gle) 
     453$rcmail_config['spellcheck_ignore_nums'] = false; 
     454 
     455// Makes that words with symbols will be ignored (e.g. g@@gle) 
     456$rcmail_config['spellcheck_ignore_syms'] = false; 
    444457 
    445458// don't let users set pagesize to more than this value if set 
  • program/include/main.inc

    r427e3a4 r66df084  
    15961596 
    15971597  if ($hook['abort']) 
    1598     return;   
     1598    return; 
    15991599 
    16001600  $lang = strtolower($_SESSION['language']); 
     
    16081608  $RCMAIL->output->include_script('tiny_mce/tiny_mce.js'); 
    16091609  $RCMAIL->output->include_script('editor.js'); 
    1610   $RCMAIL->output->add_script(sprintf("rcmail_editor_init('\$__skin_path', '%s', %d, '%s');", 
    1611     JQ($lang), intval($CONFIG['enable_spellcheck']), $mode), 
    1612     'foot'); 
     1610  $RCMAIL->output->add_script(sprintf("rcmail_editor_init(%s)", 
     1611    json_encode(array( 
     1612        'mode'       => $mode, 
     1613        'skin_path'  => '$__skin_path', 
     1614        'lang'       => $lang, 
     1615        'spellcheck' => intval($CONFIG['enable_spellcheck']), 
     1616        'spelldict'  => intval($CONFIG['spellcheck_dictionary']), 
     1617    ))), 'foot'); 
    16131618} 
    16141619 
  • program/include/rcube_spellchecker.php

    r644e3ad9 r66df084  
    3535    private $rc; 
    3636    private $error; 
    37     private $separator = '/[ !"#$%&()*+\\,\/\n:;<=>?@\[\]^_{|}-]+|\.[^\w]/'; 
    38      
     37    private $separator = '/[\s\r\n\t\(\)\/\[\]{}<>\\"]+|[:;?!,\.]([^\w]|$)/'; 
     38    private $options = array(); 
     39    private $dict; 
     40    private $have_dict; 
     41 
    3942 
    4043    // default settings 
     
    5154    function __construct($lang = 'en') 
    5255    { 
    53         $this->rc = rcmail::get_instance(); 
     56        $this->rc     = rcmail::get_instance(); 
    5457        $this->engine = $this->rc->config->get('spellcheck_engine', 'googie'); 
    55         $this->lang = $lang ? $lang : 'en'; 
     58        $this->lang   = $lang ? $lang : 'en'; 
    5659 
    5760        if ($this->engine == 'pspell' && !extension_loaded('pspell')) { 
     
    6164                'message' => "Pspell extension not available"), true, true); 
    6265        } 
     66 
     67        $this->options = array( 
     68            'ignore_syms' => $this->rc->config->get('spellcheck_ignore_syms'), 
     69            'ignore_nums' => $this->rc->config->get('spellcheck_ignore_nums'), 
     70            'ignore_caps' => $this->rc->config->get('spellcheck_ignore_caps'), 
     71            'dictionary'  => $this->rc->config->get('spellcheck_dictionary'), 
     72        ); 
    6373    } 
    6474 
     
    7282     * @return bool True when no mispelling found, otherwise false 
    7383     */ 
    74     function check($text, $is_html=false) 
     84    function check($text, $is_html = false) 
    7585    { 
    7686        // convert to plain text 
     
    117127        } 
    118128 
    119         return $this->_googie_suggestions($word);     
    120     } 
    121      
     129        return $this->_googie_suggestions($word); 
     130    } 
     131 
    122132 
    123133    /** 
     
    180190        } 
    181191 
    182         return $out; 
     192        return $result; 
    183193    } 
    184194 
     
    212222        $text = preg_split($this->separator, $text, NULL, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_OFFSET_CAPTURE); 
    213223 
    214         $diff = 0; 
    215         $matches = array(); 
     224        $diff       = 0; 
     225        $matches    = array(); 
    216226 
    217227        foreach ($text as $w) { 
     
    220230            $len  = mb_strlen($word); 
    221231 
    222             if ($word && preg_match('/[^0-9\.]/', $word) && !pspell_check($this->plink, $word)) { 
     232            // skip exceptions 
     233            if ($this->is_exception($word)) { 
     234            } 
     235            else if (!pspell_check($this->plink, $word)) { 
    223236                $suggestions = pspell_suggest($this->plink, $word); 
    224237 
     
    241254    private function _pspell_words($text = null, $is_html=false) 
    242255    { 
     256        $result = array(); 
     257 
    243258        if ($text) { 
    244259            // init spellchecker 
     
    258273            foreach ($text as $w) { 
    259274                $word = trim($w[0]); 
    260                 if ($word && preg_match('/[^0-9\.]/', $word) && !pspell_check($this->plink, $word)) { 
     275 
     276                // skip exceptions 
     277                if ($this->is_exception($word)) { 
     278                    continue; 
     279                } 
     280 
     281                if (!pspell_check($this->plink, $word)) { 
    261282                    $result[] = $word; 
    262283                } 
     
    265286            return $result; 
    266287        } 
    267  
    268         $result = array(); 
    269288 
    270289        foreach ($this->matches as $m) { 
     
    331350 
    332351        // Google has some problem with spaces, use \n instead 
    333         $text = str_replace(' ', "\n", $text); 
    334  
    335         $text = '<?xml version="1.0" encoding="utf-8" ?>' 
     352        $gtext = str_replace(' ', "\n", $text); 
     353 
     354        $gtext = '<?xml version="1.0" encoding="utf-8" ?>' 
    336355            .'<spellrequest textalreadyclipped="0" ignoredups="0" ignoredigits="1" ignoreallcaps="1">' 
    337             .'<text>' . $text . '</text>' 
     356            .'<text>' . $gtext . '</text>' 
    338357            .'</spellrequest>'; 
    339358 
     
    342361            $out = "POST $path HTTP/1.0\r\n"; 
    343362            $out .= "Host: " . str_replace('ssl://', '', $host) . "\r\n"; 
    344             $out .= "Content-Length: " . strlen($text) . "\r\n"; 
     363            $out .= "Content-Length: " . strlen($gtext) . "\r\n"; 
    345364            $out .= "Content-Type: application/x-www-form-urlencoded\r\n"; 
    346365            $out .= "Connection: Close\r\n\r\n"; 
    347             $out .= $text; 
     366            $out .= $gtext; 
    348367            fwrite($fp, $out); 
    349368 
     
    358377 
    359378        preg_match_all('/<c o="([^"]*)" l="([^"]*)" s="([^"]*)">([^<]*)<\/c>/', $store, $matches, PREG_SET_ORDER); 
     379 
     380        // skip exceptions (if appropriate options are enabled) 
     381        if (!empty($this->options['ignore_syms']) || !empty($this->options['ignore_nums']) 
     382            || !empty($this->options['ignore_caps']) || !empty($this->options['dictionary']) 
     383        ) { 
     384            foreach ($matches as $idx => $m) { 
     385                $word = mb_substr($text, $m[1], $m[2], RCMAIL_CHARSET); 
     386                // skip  exceptions 
     387                if ($this->is_exception($word)) { 
     388                    unset($matches[$idx]); 
     389                } 
     390            } 
     391        } 
    360392 
    361393        return $matches; 
     
    414446        return $h2t->get_text(); 
    415447    } 
     448 
     449 
     450    /** 
     451     * Check if the specified word is an exception accoring to  
     452     * spellcheck options. 
     453     * 
     454     * @param string  $word  The word 
     455     * 
     456     * @return bool True if the word is an exception, False otherwise 
     457     */ 
     458    public function is_exception($word) 
     459    { 
     460        // Contain only symbols (e.g. "+9,0", "2:2") 
     461        if (!$word || preg_match('/^[0-9@#$%^&_+~*=:;?!,.-]+$/', $word)) 
     462            return true; 
     463 
     464        // Contain symbols (e.g. "g@@gle"), all symbols excluding separators 
     465        if (!empty($this->options['ignore_syms']) && preg_match('/[@#$%^&_+~*=-]/', $word)) 
     466            return true; 
     467 
     468        // Contain numbers (e.g. "g00g13") 
     469        if (!empty($this->options['ignore_nums']) && preg_match('/[0-9]/', $word)) 
     470            return true; 
     471 
     472        // Blocked caps (e.g. "GOOGLE") 
     473        if (!empty($this->options['ignore_caps']) && $word == mb_strtoupper($word)) 
     474            return true; 
     475 
     476        // Use exceptions from dictionary 
     477        if (!empty($this->options['dictionary'])) { 
     478            $this->load_dict(); 
     479 
     480            // @TODO: should dictionary be case-insensitive? 
     481            if (!empty($this->dict) && in_array($word, $this->dict)) 
     482                return true; 
     483        } 
     484 
     485        return false; 
     486    } 
     487 
     488 
     489    /** 
     490     * Add a word to dictionary 
     491     * 
     492     * @param string  $word  The word to add 
     493     */ 
     494    public function add_word($word) 
     495    { 
     496        $this->load_dict(); 
     497 
     498        foreach (explode(' ', $word) as $word) { 
     499            // sanity check 
     500            if (strlen($word) < 512) { 
     501                $this->dict[] = $word; 
     502                $valid = true; 
     503            } 
     504        } 
     505 
     506        if ($valid) { 
     507            $this->dict = array_unique($this->dict); 
     508            $this->update_dict(); 
     509        } 
     510    } 
     511 
     512 
     513    /** 
     514     * Remove a word from dictionary 
     515     * 
     516     * @param string  $word  The word to remove 
     517     */ 
     518    public function remove_word($word) 
     519    { 
     520        $this->load_dict(); 
     521 
     522        if (($key = array_search($word, $this->dict)) !== false) { 
     523            unset($this->dict[$key]); 
     524            $this->update_dict(); 
     525        } 
     526    } 
     527 
     528 
     529    /** 
     530     * Update dictionary row in DB 
     531     */ 
     532    private function update_dict() 
     533    { 
     534        if (strcasecmp($this->options['dictionary'], 'shared') != 0) { 
     535            $userid = (int) $this->rc->user->ID; 
     536        } 
     537 
     538        $plugin = $this->rc->plugins->exec_hook('spell_dictionary_save', array( 
     539            'userid' => $userid, 'language' => $this->lang, 'dictionary' => $this->dict)); 
     540 
     541        if (!empty($plugin['abort'])) { 
     542            return; 
     543        } 
     544 
     545        if ($this->have_dict) { 
     546            if (!empty($this->dict)) { 
     547                $this->rc->db->query( 
     548                    "UPDATE ".get_table_name('dictionary') 
     549                    ." SET data = ?" 
     550                    ." WHERE user_id " . ($plugin['userid'] ? "= ".$plugin['userid'] : "IS NULL") 
     551                        ." AND " . $this->rc->db->quoteIdentifier('language') . " = ?", 
     552                    implode(' ', $plugin['dictionary']), $plugin['language']); 
     553            } 
     554            // don't store empty dict 
     555            else { 
     556                $this->rc->db->query( 
     557                    "DELETE FROM " . get_table_name('dictionary') 
     558                    ." WHERE user_id " . ($plugin['userid'] ? "= ".$plugin['userid'] : "IS NULL") 
     559                        ." AND " . $this->rc->db->quoteIdentifier('language') . " = ?", 
     560                    $plugin['language']); 
     561            } 
     562        } 
     563        else if (!empty($this->dict)) { 
     564            $this->rc->db->query( 
     565                "INSERT INTO " .get_table_name('dictionary') 
     566                ." (user_id, " . $this->rc->db->quoteIdentifier('language') . ", data) VALUES (?, ?, ?)", 
     567                $plugin['userid'], $plugin['language'], implode(' ', $plugin['dictionary'])); 
     568        } 
     569    } 
     570 
     571 
     572    /** 
     573     * Get dictionary from DB 
     574     */ 
     575    private function load_dict() 
     576    { 
     577        if (is_array($this->dict)) { 
     578            return $this->dict; 
     579        } 
     580 
     581        if (strcasecmp($this->options['dictionary'], 'shared') != 0) { 
     582            $userid = (int) $this->rc->user->ID; 
     583        } 
     584 
     585        $plugin = $this->rc->plugins->exec_hook('spell_dictionary_get', array( 
     586            'userid' => $userid, 'language' => $this->lang, 'dictionary' => array())); 
     587 
     588        if (empty($plugin['abort'])) { 
     589            $dict = array(); 
     590            $this->rc->db->query( 
     591                "SELECT data FROM ".get_table_name('dictionary') 
     592                ." WHERE user_id ". ($plugin['userid'] ? "= ".$plugin['userid'] : "IS NULL") 
     593                    ." AND " . $this->rc->db->quoteIdentifier('language') . " = ?", 
     594                $plugin['language']); 
     595 
     596            if ($sql_arr = $this->rc->db->fetch_assoc($sql_result)) { 
     597                $this->have_dict = true; 
     598                if (!empty($sql_arr['data'])) { 
     599                    $dict = explode(' ', $sql_arr['data']); 
     600                } 
     601            } 
     602 
     603            $plugin['dictionary'] = array_merge((array)$plugin['dictionary'], $dict); 
     604        } 
     605 
     606        if (!empty($plugin['dictionary']) && is_array($plugin['dictionary'])) { 
     607            $this->dict = $plugin['dictionary']; 
     608        } 
     609        else { 
     610            $this->dict = array(); 
     611        } 
     612 
     613        return $this->dict; 
     614    } 
     615 
    416616} 
  • program/js/editor.js

    rb4edf78 r66df084  
    1515 
    1616// Initialize HTML editor 
    17 function rcmail_editor_init(skin_path, editor_lang, spellcheck, mode) 
     17function rcmail_editor_init(config) 
    1818{ 
    1919  var ret, conf = { 
     
    2222      apply_source_formatting: true, 
    2323      theme: 'advanced', 
    24       language: editor_lang, 
    25       content_css: skin_path + '/editor_content.css', 
     24      language: config.lang, 
     25      content_css: config.skin_path + '/editor_content.css', 
    2626      theme_advanced_toolbar_location: 'top', 
    2727      theme_advanced_toolbar_align: 'left', 
     
    3636    }; 
    3737 
    38   if (mode == 'identity') 
     38  if (config.mode == 'identity') 
    3939    $.extend(conf, { 
    4040      plugins: 'paste,tabfocus', 
     
    4444  else // mail compose 
    4545    $.extend(conf, { 
    46       plugins: 'paste,emotions,media,nonbreaking,table,searchreplace,visualchars,directionality,tabfocus' + (spellcheck ? ',spellchecker' : ''), 
     46      plugins: 'paste,emotions,media,nonbreaking,table,searchreplace,visualchars,directionality,tabfocus' + (config.spellcheck ? ',spellchecker' : ''), 
    4747      theme_advanced_buttons1: 'bold,italic,underline,|,justifyleft,justifycenter,justifyright,justifyfull,|,bullist,numlist,outdent,indent,ltr,rtl,blockquote,|,forecolor,backcolor,fontselect,fontsizeselect', 
    48       theme_advanced_buttons2: 'link,unlink,table,|,emotions,charmap,image,media,|,code,search' + (spellcheck ? ',spellchecker' : '') + ',undo,redo', 
     48      theme_advanced_buttons2: 'link,unlink,table,|,emotions,charmap,image,media,|,code,search' + (config.spellcheck ? ',spellchecker' : '') + ',undo,redo', 
    4949      spellchecker_languages: (rcmail.env.spellcheck_langs ? rcmail.env.spellcheck_langs : 'Dansk=da,Deutsch=de,+English=en,Espanol=es,Francais=fr,Italiano=it,Nederlands=nl,Polski=pl,Portugues=pt,Suomi=fi,Svenska=sv'), 
    5050      spellchecker_rpc_url: '?_task=utils&_action=spell_html', 
     51      spellchecker_enable_learn_rpc: config.spelldict, 
    5152      accessibility_focus: false, 
    5253      oninit: 'rcmail_editor_callback' 
  • program/js/googiespell.js

    r340546c r66df084  
    22 SpellCheck 
    33    jQuery'fied spell checker based on GoogieSpell 4.0 
    4  Copyright Amir Salihefendic 2006 
    5  Copyright Aleksander Machniak 2009 
     4 Copyright (C) 2006 Amir Salihefendic 
     5 Copyright (C) 2009 Aleksander Machniak 
     6 Copyright (C) 2011 Kolab Systems AG 
    67     LICENSE 
    78         GPL 
     
    1415    GOOGIE_DEFAULT_LANG = 'en'; 
    1516 
    16 function GoogieSpell(img_dir, server_url) { 
     17function GoogieSpell(img_dir, server_url, has_dict) 
     18{ 
    1719    var ref = this, 
    1820        cookie_value = getCookie('language'); 
     
    5052    this.lang_no_error_found = "No spelling errors found"; 
    5153    this.lang_no_suggestions = "No suggestions"; 
     54    this.lang_learn_word = "Add to dictionary"; 
    5255 
    5356    this.show_spell_img = false; // roundcube mod. 
     
    6568    this.custom_spellcheck_starter = null; 
    6669    this.main_controller = true; 
     70    this.has_dictionary = has_dict; 
    6771 
    6872    // Observers 
     
    9195 
    9296 
    93 this.decorateTextarea = function(id) { 
     97this.decorateTextarea = function(id) 
     98{ 
    9499    this.text_area = typeof id === 'string' ? document.getElementById(id) : id; 
    95100 
     
    120125// API Functions (the ones that you can call) 
    121126///// 
    122 this.setSpellContainer = function(id) { 
     127this.setSpellContainer = function(id) 
     128{ 
    123129    this.spell_container = typeof id === 'string' ? document.getElementById(id) : id; 
    124130}; 
    125131 
    126 this.setLanguages = function(lang_dict) { 
     132this.setLanguages = function(lang_dict) 
     133{ 
    127134    this.lang_to_word = lang_dict; 
    128135    this.langlist_codes = this.array_keys(lang_dict); 
    129136}; 
    130137 
    131 this.setCurrentLanguage = function(lan_code) { 
     138this.setCurrentLanguage = function(lan_code) 
     139{ 
    132140    GOOGIE_CUR_LANG = lan_code; 
    133141 
     
    138146}; 
    139147 
    140 this.setForceWidthHeight = function(width, height) { 
     148this.setForceWidthHeight = function(width, height) 
     149{ 
    141150    // Set to null if you want to use one of them 
    142151    this.force_width = width; 
     
    144153}; 
    145154 
    146 this.setDecoration = function(bool) { 
     155this.setDecoration = function(bool) 
     156{ 
    147157    this.decoration = bool; 
    148158}; 
    149159 
    150 this.dontUseCloseButtons = function() { 
     160this.dontUseCloseButtons = function() 
     161{ 
    151162    this.use_close_btn = false; 
    152163}; 
    153164 
    154 this.appendNewMenuItem = function(name, call_back_fn, checker) { 
     165this.appendNewMenuItem = function(name, call_back_fn, checker) 
     166{ 
    155167    this.extra_menu_items.push([name, call_back_fn, checker]); 
    156168}; 
    157169 
    158 this.appendCustomMenuBuilder = function(eval, builder) { 
     170this.appendCustomMenuBuilder = function(eval, builder) 
     171{ 
    159172    this.custom_menu_builder.push([eval, builder]); 
    160173}; 
    161174 
    162 this.setFocus = function() { 
     175this.setFocus = function() 
     176{ 
    163177    try { 
    164178        this.focus_link_b.focus(); 
     
    175189// Set functions (internal) 
    176190///// 
    177 this.setStateChanged = function(current_state) { 
     191this.setStateChanged = function(current_state) 
     192{ 
    178193    this.state = current_state; 
    179194    if (this.spelling_state_observer != null && this.report_state_change) 
     
    181196}; 
    182197 
    183 this.setReportStateChange = function(bool) { 
     198this.setReportStateChange = function(bool) 
     199{ 
    184200    this.report_state_change = bool; 
    185201}; 
     
    189205// Request functions 
    190206///// 
    191 this.getUrl = function() { 
     207this.getUrl = function() 
     208{ 
    192209    return this.server_url + GOOGIE_CUR_LANG; 
    193210}; 
    194211 
    195 this.escapeSpecial = function(val) { 
     212this.escapeSpecial = function(val) 
     213{ 
    196214    return val.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;"); 
    197215}; 
    198216 
    199 this.createXMLReq = function (text) { 
     217this.createXMLReq = function (text) 
     218{ 
    200219    return '<?xml version="1.0" encoding="utf-8" ?>' 
    201220        + '<spellrequest textalreadyclipped="0" ignoredups="0" ignoredigits="1" ignoreallcaps="1">' 
     
    203222}; 
    204223 
    205 this.spellCheck = function(ignore) { 
     224this.spellCheck = function(ignore) 
     225{ 
    206226    this.prepare(ignore); 
    207227 
     
    209229        ref = this; 
    210230 
    211     $.ajax({ type: 'POST', url: this.getUrl(), 
    212         data: this.createXMLReq(req_text), dataType: 'text', 
     231    $.ajax({ type: 'POST', url: this.getUrl(), data: this.createXMLReq(req_text), dataType: 'text', 
    213232            error: function(o) { 
    214233            if (ref.custom_ajax_error) 
     
    235254}; 
    236255 
     256this.learnWord = function(word, id) 
     257{ 
     258    word = this.escapeSpecial(word.innerHTML); 
     259 
     260    var ref = this, 
     261        req_text = '<?xml version="1.0" encoding="utf-8" ?><learnword><text>' + word + '</text></learnword>'; 
     262 
     263    $.ajax({ type: 'POST', url: this.getUrl(), data: req_text, dataType: 'text', 
     264            error: function(o) { 
     265            if (ref.custom_ajax_error) 
     266                    ref.custom_ajax_error(ref); 
     267            else 
     268                    alert('An error was encountered on the server. Please try again later.'); 
     269            }, 
     270        success: function(data) { 
     271            } 
     272    }); 
     273}; 
     274 
    237275 
    238276////// 
     
    275313}; 
    276314 
    277 this.parseResult = function(r_text) { 
     315this.parseResult = function(r_text) 
     316{ 
    278317    // Returns an array: result[item] -> ['attrs'], ['suggestions'] 
    279318    var re_split_attr_c = /\w+="(\d+|true)"/g, 
     
    325364// Error menu functions 
    326365///// 
    327 this.createErrorWindow = function() { 
     366this.createErrorWindow = function() 
     367{ 
    328368    this.error_window = document.createElement('div'); 
    329369    $(this.error_window).addClass('googie_window popupmenu').attr('googie_action_btn', '1'); 
    330370}; 
    331371 
    332 this.isErrorWindowShown = function() { 
     372this.isErrorWindowShown = function() 
     373{ 
    333374    return $(this.error_window).is(':visible'); 
    334375}; 
    335376 
    336 this.hideErrorWindow = function() { 
     377this.hideErrorWindow = function() 
     378{ 
    337379    $(this.error_window).hide(); 
    338380    $(this.error_window_iframe).hide(); 
    339381}; 
    340382 
    341 this.updateOrginalText = function(offset, old_value, new_value, id) { 
     383this.updateOrginalText = function(offset, old_value, new_value, id) 
     384{ 
    342385    var part_1 = this.orginal_text.substring(0, offset), 
    343386        part_2 = this.orginal_text.substring(offset+old_value.length), 
     
    358401}; 
    359402 
    360 this.createListSeparator = function() { 
     403this.createListSeparator = function() 
     404{ 
    361405    var td = document.createElement('td'), 
    362406        tr = document.createElement('tr'); 
    363407 
    364408    $(td).html(' ').attr('googie_action_btn', '1') 
    365         .css({'cursor': 'default', 'font-size': '3px', 'border-top': '1px solid #ccc', 'padding-top': '3px'}); 
     409            .css({'cursor': 'default', 'font-size': '3px', 'border-top': '1px solid #ccc', 'padding-top': '3px'}); 
    366410    tr.appendChild(td); 
    367411 
     
    369413}; 
    370414 
    371 this.correctError = function(id, elm, l_elm, rm_pre_space) { 
     415this.correctError = function(id, elm, l_elm, rm_pre_space) 
     416{ 
    372417    var old_value = elm.innerHTML, 
    373418        new_value = l_elm.nodeType == 3 ? l_elm.nodeValue : l_elm.innerHTML, 
     
    394439}; 
    395440 
    396 this.showErrorWindow = function(elm, id) { 
     441this.ignoreError = function(elm, id) 
     442{ 
     443    // @TODO: ignore all same words 
     444    $(elm).removeAttr('class').css('color', '').unbind(); 
     445    this.hideErrorWindow(); 
     446}; 
     447 
     448this.showErrorWindow = function(elm, id) 
     449{ 
    397450    if (this.show_menu_observer) 
    398451        this.show_menu_observer(this); 
     
    415468        } 
    416469    } 
     470 
    417471    if (!changed) { 
    418472        // Build up the result list 
     
    422476            row, item, dummy; 
    423477 
     478        // [Add to dictionary] button 
     479        if (this.has_dictionary && !$(elm).attr('is_corrected')) { 
     480            row = document.createElement('tr'), 
     481            item = document.createElement('td'), 
     482            dummy = document.createElement('span'); 
     483 
     484            $(dummy).text(this.lang_learn_word); 
     485            $(item).attr('googie_action_btn', '1').css('cursor', 'default') 
     486                .mouseover(ref.item_onmouseover) 
     487                .mouseout(ref.item_onmouseout) 
     488                            .click(function(e) { 
     489                                ref.learnWord(elm, id); 
     490                                ref.ignoreError(elm, id); 
     491                            }); 
     492 
     493            item.appendChild(dummy); 
     494            row.appendChild(item); 
     495            list.appendChild(row); 
     496        } 
     497/* 
    424498        if (suggestions.length == 0) { 
    425499            row = document.createElement('tr'), 
     
    434508            list.appendChild(row); 
    435509        } 
    436  
     510*/ 
    437511        for (var i=0, len=suggestions.length; i < len; i++) { 
    438512            row = document.createElement('tr'), 
     
    442516            $(dummy).html(suggestions[i]); 
    443517 
    444             $(item).bind('mouseover', this.item_onmouseover) 
    445                     .bind('mouseout', this.item_onmouseout) 
    446                     .bind('click', function(e) { ref.correctError(id, elm, e.target.firstChild) }); 
     518            $(item).mouseover(this.item_onmouseover).mouseout(this.item_onmouseout) 
     519                    .click(function(e) { ref.correctError(id, elm, e.target.firstChild) }); 
    447520 
    448521            item.appendChild(dummy); 
     
    451524        } 
    452525 
    453         //The element is changed, append the revert 
     526        // The element is changed, append the revert 
    454527        if (elm.is_changed && elm.innerHTML != elm.old_value) { 
    455528            var old_value = elm.old_value, 
     
    460533                $(rev_span).addClass('googie_list_revert').html(this.lang_revert + ' ' + old_value); 
    461534 
    462             $(revert).bind('mouseover', this.item_onmouseover) 
    463                     .bind('mouseout', this.item_onmouseout) 
    464                     .bind('click', function(e) { 
     535            $(revert).mouseover(this.item_onmouseover).mouseout(this.item_onmouseout) 
     536                    .click(function(e) { 
    465537                    ref.updateOrginalText(offset, elm.innerHTML, old_value, id); 
    466                     $(elm).attr('is_corrected', true).css('color', '#b91414').html(old_value); 
     538                    $(elm).removeAttr('is_corrected').css('color', '#b91414').html(old_value); 
    467539                    ref.hideErrorWindow(); 
    468540                    }); 
     
    499571                .width(32).height(16) 
    500572            .css({'cursor': 'pointer', 'margin-left': '2px', 'margin-right': '2px'}) 
    501                 .bind('click', onsub); 
     573                .click(onsub); 
    502574 
    503575        $(edit_form).attr('googie_action_btn', '1') 
    504576                .css({'margin': 0, 'padding': 0, 'cursor': 'default', 'white-space': 'nowrap'}) 
    505                 .bind('submit', onsub); 
     577                .submit(onsub); 
    506578 
    507579            edit_form.appendChild(edit_input); 
     
    524596 
    525597                                $(e_col).html(e_elm[0]) 
    526                         .bind('mouseover', ref.item_onmouseover) 
    527                         .bind('mouseout', ref.item_onmouseout) 
    528                                     .bind('click', function() { return e_elm[1](elm, ref) }); 
     598                        .mouseover(ref.item_onmouseover) 
     599                        .mouseout(ref.item_onmouseout) 
     600                                    .click(function() { return e_elm[1](elm, ref) }); 
    529601 
    530602                                e_row.appendChild(e_col); 
     
    576648// Edit layer (the layer where the suggestions are stored) 
    577649////// 
    578 this.createEditLayer = function(width, height) { 
     650this.createEditLayer = function(width, height) 
     651{ 
    579652    this.edit_layer = document.createElement('div'); 
    580653    $(this.edit_layer).addClass('googie_edit_layer').attr('id', 'googie_edit_layer') 
     
    604677}; 
    605678 
    606 this.resumeEditing = function() { 
     679this.resumeEditing = function() 
     680{ 
    607681    this.setStateChanged('ready'); 
    608682 
     
    630704}; 
    631705 
    632 this.createErrorLink = function(text, id) { 
     706this.createErrorLink = function(text, id) 
     707{ 
    633708    var elm = document.createElement('span'), 
    634709        ref = this, 
     
    639714        }; 
    640715 
    641     $(elm).html(text).addClass('googie_link').bind('click', d) 
    642             .attr({'googie_action_btn' : '1', 'g_id' : id, 'is_corrected' : false}); 
     716    $(elm).html(text).addClass('googie_link').click(d).removeAttr('is_corrected') 
     717            .attr({'googie_action_btn' : '1', 'g_id' : id}); 
    643718 
    644719    return elm; 
    645720}; 
    646721 
    647 this.createPart = function(txt_part) { 
     722this.createPart = function(txt_part) 
     723{ 
    648724    if (txt_part == " ") 
    649725        return document.createTextNode(" "); 
     
    660736}; 
    661737 
    662 this.showErrorsInIframe = function() { 
     738this.showErrorsInIframe = function() 
     739{ 
    663740    var output = document.createElement('div'), 
    664741        pointer = 0, 
     
    718795// Choose language menu 
    719796////// 
    720 this.createLangWindow = function() { 
     797this.createLangWindow = function() 
     798{ 
    721799    this.language_window = document.createElement('div'); 
    722800    $(this.language_window).addClass('googie_window popupmenu') 
     
    777855}; 
    778856 
    779 this.isLangWindowShown = function() { 
     857this.isLangWindowShown = function() 
     858{ 
    780859    return $(this.language_window).is(':visible'); 
    781860}; 
    782861 
    783 this.hideLangWindow = function() { 
     862this.hideLangWindow = function() 
     863{ 
    784864    $(this.language_window).hide(); 
    785865    $(this.switch_lan_pic).removeClass().addClass('googie_lang_3d_on'); 
    786866}; 
    787867 
    788 this.showLangWindow = function(elm) { 
     868this.showLangWindow = function(elm) 
     869{ 
    789870    if (this.show_menu_observer) 
    790871        this.show_menu_observer(this); 
     
    807888}; 
    808889 
    809 this.deHighlightCurSel = function() { 
     890this.deHighlightCurSel = function() 
     891{ 
    810892    $(this.lang_cur_elm).removeClass().addClass('googie_list_onout'); 
    811893}; 
    812894 
    813 this.highlightCurSel = function() { 
     895this.highlightCurSel = function() 
     896{ 
    814897    if (GOOGIE_CUR_LANG == null) 
    815898        GOOGIE_CUR_LANG = GOOGIE_DEFAULT_LANG; 
     
    825908}; 
    826909 
    827 this.createChangeLangPic = function() { 
     910this.createChangeLangPic = function() 
     911{ 
    828912    var img = $('<img>') 
    829913            .attr({src: this.img_dir + 'change_lang.gif', 'alt': 'Change language', 'googie_action_btn': '1'}), 
     
    848932}; 
    849933 
    850 this.createSpellDiv = function() { 
     934this.createSpellDiv = function() 
     935{ 
    851936    var span = document.createElement('span'); 
    852937 
     
    863948// State functions 
    864949///// 
    865 this.flashNoSpellingErrorState = function(on_finish) { 
     950this.flashNoSpellingErrorState = function(on_finish) 
     951{ 
    866952    this.setStateChanged('no_error_found'); 
    867953 
     
    889975}; 
    890976 
    891 this.resumeEditingState = function() { 
     977this.resumeEditingState = function() 
     978{ 
    892979    this.setStateChanged('resume_editing'); 
    893980 
     
    907994}; 
    908995 
    909 this.checkSpellingState = function(fire) { 
     996this.checkSpellingState = function(fire) 
     997{ 
    910998    if (fire) 
    911999        this.setStateChanged('ready'); 
     
    9401028// Misc. functions 
    9411029///// 
    942 this.isDefined = function(o) { 
     1030this.isDefined = function(o) 
     1031{ 
    9431032    return (o !== undefined && o !== null) 
    9441033}; 
    9451034 
    946 this.errorFixed = function() {  
    947     this.cnt_errors_fixed++;  
     1035this.errorFixed = function() 
     1036{ 
     1037    this.cnt_errors_fixed++; 
    9481038    if (this.all_errors_fixed_observer) 
    9491039        if (this.cnt_errors_fixed == this.cnt_errors) { 
     
    9531043}; 
    9541044 
    955 this.errorFound = function() { 
     1045this.errorFound = function() 
     1046{ 
    9561047    this.cnt_errors++; 
    9571048}; 
    9581049 
    959 this.createCloseButton = function(c_fn) { 
     1050this.createCloseButton = function(c_fn) 
     1051{ 
    9601052    return this.createButton(this.lang_close, 'googie_list_close', c_fn); 
    9611053}; 
    9621054 
    963 this.createButton = function(name, css_class, c_fn) { 
     1055this.createButton = function(name, css_class, c_fn) 
     1056{ 
    9641057    var btn_row = document.createElement('tr'), 
    9651058        btn = document.createElement('td'), 
     
    9831076}; 
    9841077 
    985 this.removeIndicator = function(elm) { 
     1078this.removeIndicator = function(elm) 
     1079{ 
    9861080    //$(this.indicator).remove(); 
    9871081    // roundcube mod. 
     
    9901084}; 
    9911085 
    992 this.appendIndicator = function(elm) { 
     1086this.appendIndicator = function(elm) 
     1087{ 
    9931088    // modified by roundcube 
    9941089    if (window.rcmail) 
     
    10061101} 
    10071102 
    1008 this.createFocusLink = function(name) { 
     1103this.createFocusLink = function(name) 
     1104{ 
    10091105    var link = document.createElement('a'); 
    10101106    $(link).attr({'href': 'javascript:;', 'name': name}); 
     
    10121108}; 
    10131109 
    1014 this.item_onmouseover = function(e) { 
     1110this.item_onmouseover = function(e) 
     1111{ 
    10151112    if (this.className != 'googie_list_revert' && this.className != 'googie_list_close') 
    10161113        this.className = 'googie_list_onhover'; 
     
    10181115        this.parentNode.className = 'googie_list_onhover'; 
    10191116}; 
    1020 this.item_onmouseout = function(e) { 
     1117 
     1118this.item_onmouseout = function(e) 
     1119{ 
    10211120    if (this.className != 'googie_list_revert' && this.className != 'googie_list_close') 
    10221121        this.className = 'googie_list_onout'; 
  • program/localization/en_US/labels.inc

    rdbb0c2a r66df084  
    428428$labels['defaultaddressbook'] = 'Add new contacts to the selected addressbook'; 
    429429$labels['spellcheckbeforesend'] = 'Check spelling before sending a message'; 
     430$labels['spellcheckoptions'] = 'Spellcheck Options'; 
     431$labels['spellcheckignoresyms'] = 'Ignore words with symbols'; 
     432$labels['spellcheckignorenums'] = 'Ignore words with numbers'; 
     433$labels['spellcheckignorecaps'] = 'Ignore words with all letters capitalized'; 
     434$labels['addtodict'] = 'Add to dictionary'; 
    430435 
    431436$labels['folder']  = 'Folder'; 
  • program/steps/mail/compose.inc

    r13d45df r66df084  
    698698  // include GoogieSpell 
    699699  if (!empty($CONFIG['enable_spellcheck'])) { 
    700  
    701     $engine = $RCMAIL->config->get('spellcheck_engine','googie'); 
     700    $engine           = $RCMAIL->config->get('spellcheck_engine','googie'); 
     701    $dictionary       = (bool) $RCMAIL->config->get('spellcheck_dictionary'); 
    702702    $spellcheck_langs = (array) $RCMAIL->config->get('spellcheck_languages', 
    703703      array('da'=>'Dansk', 'de'=>'Deutsch', 'en' => 'English', 'es'=>'Español', 
     
    729729      $editor_lang_set[] = ($key == $lang ? '+' : '') . JQ($name).'='.JQ($key); 
    730730    } 
    731      
     731 
    732732    $OUTPUT->include_script('googiespell.js'); 
    733733    $OUTPUT->add_script(sprintf( 
    734       "var googie = new GoogieSpell('\$__skin_path/images/googiespell/','?_task=utils&_action=spell&lang=');\n". 
     734      "var googie = new GoogieSpell('\$__skin_path/images/googiespell/','?_task=utils&_action=spell&lang=', %s);\n". 
    735735      "googie.lang_chck_spell = \"%s\";\n". 
    736736      "googie.lang_rsm_edt = \"%s\";\n". 
     
    738738      "googie.lang_revert = \"%s\";\n". 
    739739      "googie.lang_no_error_found = \"%s\";\n". 
     740      "googie.lang_learn_word = \"%s\";\n". 
    740741      "googie.setLanguages(%s);\n". 
    741742      "googie.setCurrentLanguage('%s');\n". 
     
    743744      "googie.decorateTextarea('%s');\n". 
    744745      "%s.set_env('spellcheck', googie);", 
     746      !empty($dictionary) ? 'true' : 'false', 
    745747      JQ(Q(rcube_label('checkspelling'))), 
    746748      JQ(Q(rcube_label('resumeediting'))), 
     
    748750      JQ(Q(rcube_label('revertto'))), 
    749751      JQ(Q(rcube_label('nospellerrors'))), 
     752      JQ(Q(rcube_label('addtodict'))), 
    750753      json_serialize($spellcheck_langs), 
    751754      $lang, 
  • program/steps/settings/func.inc

    r25e6a0f r66df084  
    449449 
    450450    $blocks = array( 
    451       'main' => array('name' => Q(rcube_label('mainoptions'))), 
    452       'sig' => array('name' => Q(rcube_label('signatureoptions'))), 
     451      'main'       => array('name' => Q(rcube_label('mainoptions'))), 
     452      'spellcheck' => array('name' => Q(rcube_label('spellcheckoptions'))), 
     453      'sig'        => array('name' => Q(rcube_label('signatureoptions'))), 
    453454    ); 
    454455 
     
    550551      $input_spellcheck = new html_checkbox(array('name' => '_spellcheck_before_send', 'id' => $field_id, 'value' => 1)); 
    551552 
    552       $blocks['main']['options']['spellcheck_before_send'] = array( 
     553      $blocks['spellcheck']['options']['spellcheck_before_send'] = array( 
    553554        'title' => html::label($field_id, Q(rcube_label('spellcheckbeforesend'))), 
    554555        'content' => $input_spellcheck->show($config['spellcheck_before_send']?1:0), 
    555556      ); 
     557    } 
     558 
     559    if ($config['enable_spellcheck']) { 
     560      foreach (array('syms', 'nums', 'caps') as $key) { 
     561        $key = 'spellcheck_ignore_'.$key; 
     562        if (!isset($no_override[$key])) { 
     563          $input_spellcheck = new html_checkbox(array('name' => '_'.$key, 'id' => 'rcmfd_'.$key, 'value' => 1)); 
     564 
     565          $blocks['spellcheck']['options'][$key] = array( 
     566            'title' => html::label($field_id, Q(rcube_label(str_replace('_', '', $key)))), 
     567            'content' => $input_spellcheck->show($config[$key]?1:0), 
     568          ); 
     569        } 
     570      } 
    556571    } 
    557572 
  • program/steps/settings/save_prefs.inc

    rda71783 r66df084  
    7272      'reply_same_folder'  => isset($_POST['_reply_same_folder']) ? TRUE : FALSE, 
    7373      'spellcheck_before_send' => isset($_POST['_spellcheck_before_send']) ? TRUE : FALSE, 
     74      'spellcheck_ignore_syms' => isset($_POST['_spellcheck_ignore_syms']) ? TRUE : FALSE, 
     75      'spellcheck_ignore_nums' => isset($_POST['_spellcheck_ignore_nums']) ? TRUE : FALSE, 
     76      'spellcheck_ignore_caps' => isset($_POST['_spellcheck_ignore_caps']) ? TRUE : FALSE, 
    7477      'show_sig'           => isset($_POST['_show_sig']) ? intval($_POST['_show_sig']) : 1, 
    7578      'top_posting'        => !empty($_POST['_top_posting']), 
     
    168171      } 
    169172    } 
    170    
     173 
    171174  break; 
    172175} 
  • program/steps/utils/spell.inc

    rb4edf78 r66df084  
    2424$data = file_get_contents('php://input'); 
    2525 
     26$learn_word = strpos($data, '<learnword>'); 
     27 
    2628// Get data string 
    2729$left = strpos($data, '<text>'); 
     
    3133 
    3234$spellchecker = new rcube_spellchecker($lang); 
    33 $spellchecker->check($data); 
    34 $result = $spellchecker->get_xml(); 
     35 
     36if ($learn_word) { 
     37    $spellchecker->add_word($data); 
     38    $result = '<?xml version="1.0" encoding="'.RCMAIL_CHARSET.'"?><learnwordresult></learnwordresult>'; 
     39} 
     40else { 
     41    $spellchecker->check($data); 
     42    $result = $spellchecker->get_xml(); 
     43} 
    3544 
    3645// set response length 
  • program/steps/utils/spell_html.inc

    rb4edf78 r66df084  
    4141    $result['result'] = $spellchecker->get_suggestions($data); 
    4242} 
     43else if ($request['method'] == 'learnWord') { 
     44    $spellchecker->add_word($data); 
     45    $result['result'] = true; 
     46} 
    4347 
    4448if ($error = $spellchecker->error()) { 
Note: See TracChangeset for help on using the changeset viewer.