Plugin Directory

Changeset 1795661


Ignore:
Timestamp:
01/02/2018 12:05:31 PM (8 years ago)
Author:
priberam
Message:

Version 1.5.9

  • Fix collapsing spaces between errors.
Location:
flip
Files:
23 added
3 edited

Legend:

Unmodified
Added
Removed
  • flip/trunk/engine.js

    r1665320 r1795661  
    11(function (DOM) {
    2     if (DOM.FLiPEngine) return;
    3     DOM.FLiPEngine = function (textContainer) {
    4         var self = this;
    5 
    6         /* configs */
    7         var tagsBlock = {
    8             "address": true,
    9             "area": true,
    10             "blockquote": true,
    11             "br": true,
    12             "center": true,
    13             "dir": true,
    14             "div": true,
    15             "dl": true,
    16             "fieldset": true,
    17             "form": true,
    18             "h1": true,
    19             "h2": true,
    20             "h3": true,
    21             "h4": true,
    22             "h5": true,
    23             "h6": true,
    24             "hr": true,
    25             "isindex": true,
    26             "menu": true,
    27             "noframes": true,
    28             "noscript": true,
    29             "ol": true,
    30             "p": true,
    31             "pre": true,
    32             "script": true,
    33             "td": true,
    34             "li": true,
    35             "layer": true,
    36             "article": true,
    37             "aside": true,
    38             "details": true,
    39             "figcaption": true,
    40             "figure": true,
    41             "footer": true,
    42             "header": true,
    43             "hgroup": true,
    44             "nav": true,
    45             "section": true,
    46             "summary": true
     2  if (DOM.FLiPEngine) return;
     3  DOM.FLiPEngine = function (textContainer) {
     4    var self = this;
     5
     6    /* configs */
     7    var tagsBlock = {
     8      "address": true,
     9      "area": true,
     10      "blockquote": true,
     11      "br": true,
     12      "center": true,
     13      "dir": true,
     14      "div": true,
     15      "dl": true,
     16      "fieldset": true,
     17      "form": true,
     18      "h1": true,
     19      "h2": true,
     20      "h3": true,
     21      "h4": true,
     22      "h5": true,
     23      "h6": true,
     24      "hr": true,
     25      "isindex": true,
     26      "menu": true,
     27      "noframes": true,
     28      "noscript": true,
     29      "ol": true,
     30      "p": true,
     31      "pre": true,
     32      "script": true,
     33      "td": true,
     34      "li": true,
     35      "layer": true,
     36      "article": true,
     37      "aside": true,
     38      "details": true,
     39      "figcaption": true,
     40      "figure": true,
     41      "footer": true,
     42      "header": true,
     43      "hgroup": true,
     44      "nav": true,
     45      "section": true,
     46      "summary": true
     47    };
     48
     49    /* Options */
     50
     51    this.defaultSettings = {
     52      NumMaxSugest: 4,
     53      ScaytEnable: true,
     54      AutoStart: true,
     55      Language: 'pt-pt', //'pt-br', 'es'
     56      Acordo: true,
     57      Webservice: '',
     58      GrammarSet: 'Common',
     59      CanAddWords: false,
     60      Key: '',
     61      MaxErrors: 200,
     62      IgnoreTags: "",
     63      /* Events */
     64      onRequest: null,
     65      onSuccess: null,
     66      onError: null,
     67      onTimeout: null
     68    };
     69
     70    function extendOptions(d, n) {
     71      var r = {};
     72      Object.keys(d).forEach(function (f) {
     73        r[f] = n.hasOwnProperty(f) ? n[f] : d[f];
     74      });
     75      return r;
     76    }
     77
     78    var _settings = extendOptions(this.defaultSettings, {});
     79
     80    var scaytImediateSpellChars = ' .,;:!?\xA0',
     81      _flipRunning = false,
     82      _flipStarting = false,
     83      _pba_ort_error = 'pba_ort_error',
     84      _pba_gram_error = 'pba_gram_error',
     85      _pba_hide_error = 'pba_hide_error',
     86      _ignoredWords = [],
     87      _minVersion = "1.5.0",
     88      _wsVersion = "0.0.0",
     89      _versionValid = false,
     90      _hasInit = false,
     91      _ignoretags = {};
     92
     93    function calcIgnoreTags() {
     94      _ignoretags = {};
     95      _settings.IgnoreTags && _settings.IgnoreTags.constructor === String && _settings.IgnoreTags.split(' ').forEach(function (f) {
     96        var split = f.split(/([\.@#][a-z:=\d]+)/gi).filter(function (i) { return i; });
     97        if (split.length === 0) return;
     98        var value = {};
     99        if (split.length > 1) {
     100          var selector = split[1].substr(1);
     101          switch (split[1][0]) {
     102            case '.':
     103              value.attr = 'class';
     104              value.value = selector;
     105              break;
     106            case '#':
     107              value.attr = 'id';
     108              value.value = selector;
     109              break;
     110            default:
     111              var sa = selector.split('=');
     112              value.attr = sa[0];
     113              value.value = sa[1];
     114              break;
     115          }
     116        }
     117        _ignoretags[split[0]] = value;
     118      });
     119    }
     120
     121    calcIgnoreTags();
     122
     123    function trim(str) {
     124      var trimRegex = /(?:^[\s]+)|(?:[\s]+$)/g;
     125      return str.replace(trimRegex, '');
     126    }
     127
     128    var events = {};
     129    function doEvent(name, obj, args) {
     130      for (var i = 0; i < (events[name] && events[name].length || 0); i++) {
     131        events[name][i].apply(obj, args);
     132      }
     133    }
     134
     135
     136    /* métodos publicos */
     137
     138    this.setTextContainer = function (tc) {
     139      textContainer = tc;
     140      spellcheckQueue = [];
     141    };
     142
     143    this.on = function (event, callback) {
     144      if (event && typeof (event) === 'string' &&
     145        callback && typeof (callback) === 'function') {
     146        if (!events[event])
     147          events[event] = [];
     148        var e = events[event];
     149        var idx = e.indexOf(callback);
     150        if (idx < 0)
     151          e.push(callback);
     152      }
     153    };
     154
     155    this.isError = function (element) {
     156      return isError(element) && element.className.indexOf(_pba_hide_error) < 0;
     157    };
     158
     159    this.Settings = function (settings, value) {
     160      if (typeof (settings) === 'undefined')
     161        return _settings;
     162      else if (typeof (settings) === 'object') {
     163        Object.keys(settings).forEach(function (f) {
     164          self.Settings(f, settings[f]);
     165        });
     166      }
     167      else if (typeof (settings) === 'string') {
     168        if (typeof (value) !== 'undefined') {
     169          if (settings.substr(0, 2) === 'on')
     170            this.on(settings.substr(2), value);
     171          else
     172            _settings[settings] = value;
     173
     174          if (settings === 'IgnoreTags')
     175            calcIgnoreTags();
     176        }
     177        else
     178          return _settings[settings];
     179      }
     180    };
     181
     182    this.isRunning = function () {
     183      return _flipRunning;
     184    };
     185
     186    this.status = function () {
     187      if (_versionValid)
     188        return spellcheckQueue.length > 0 || waitingForResponseForBlock ? "checking" : "idle";
     189      else
     190        return "verErr";
     191    };
     192
     193    this.hasErrors = function () {
     194      if (!(textContainer && textContainer.ownerDocument))
     195        return false;
     196      var e = textContainer.ownerDocument.getElementsByTagName('span');
     197      for (var i = 0; i < e.length; i++) {
     198        if (isError(e[i]))
     199          return true;
     200      }
     201      return false;
     202    };
     203
     204    this.textContainer = function (textcontainer) {
     205      if (typeof (textcontainer) === 'undefined')
     206        return textContainer;
     207      else
     208        textContainer = textcontainer;
     209    };
     210
     211    this.startEngine = function (coldStart /* false */) {
     212      if (_flipRunning || _flipStarting) return;
     213      _flipStarting = true;
     214      textContainer.setAttribute("spellcheck", false);
     215
     216      initEngine(function () {
     217        _flipRunning = true;
     218        _flipStarting = false;
     219        if (_versionValid) {
     220          removeErrors(textContainer); // para ter a certeza que não fica nada sublinhado
     221          doSpellCheck(coldStart);
     222        }
     223        else
     224          console.log("FLiP Api version is " + _wsVersion + " must be atleast " + _minVersion);
     225      });
     226    };
     227
     228    this.stopEngine = function () {
     229      if (!_flipRunning) return;
     230      stopSpellCheck();
     231      _flipRunning = false;
     232      _flipStarting = false;
     233    };
     234
     235    this.restartEngine = function (coldStart /* true */) {
     236      this.stopEngine();
     237      this.startEngine(coldStart !== false);
     238    };
     239
     240    this.shutdown = function () {
     241      this.stopEngine();
     242      _hasInit = false;
     243    };
     244
     245    this.spellCheck = function () {
     246      if (_flipRunning && _versionValid)
     247        doSpellCheck();
     248
     249    };
     250
     251    this.removeMarkupFromHtml = function (html) {
     252      if (!(textContainer && textContainer.ownerDocument && html))
     253        return;
     254      var escaped = html.replace(/\&nbsp;/g, "___NBSP___");
     255      var div = textContainer.ownerDocument.createElement('div');
     256      div.innerHTML = escaped;
     257      removeErrors(div, true);
     258
     259      return div.innerHTML.replace(/\&nbsp;/g, " ").replace(/___NBSP___/g, "&nbsp;");
     260    };
     261
     262    this.replaceWord = function (node, word) {
     263      if (!(node && node.error)) return;
     264
     265      node.textContent = word;
     266      addToQueue({ block: node.error.block });
     267      removeNode(node);
     268    };
     269
     270    this.addWord = function (node) {
     271      if (!(node && node.error && trim(node.textContent).length > 0 && _settings.CanAddWords)) return;
     272
     273      doJSONRequest({
     274        Url: geturl(),
     275        Action: ['userdict', _settings.Language, _settings.Acordo ? 'true' : 'false', encodeURIComponent(node.textContent || "")].join("/"),
     276        key: _settings.Key,
     277        Method: "PUT"
     278      });
     279
     280      this.ignoreAllWords(node);
     281    };
     282
     283    this.ignoreWord = function (node) {
     284      if (!(node && node.error)) return;
     285      _ignoredWords.push(node.textContent);
     286      removeNode(node);
     287    };
     288
     289    this.ignoreAllWords = function (node) {
     290      if (!(node && node.error)) return;
     291      var word = node.textContent;
     292      _ignoredWords.push(word);
     293      var p = [].concat.apply([], node.ownerDocument.getElementsByTagName('span'));
     294      for (var i = 0; i < p.length; i++) {
     295        var el = p[i];
     296        if (isError(el) && el.textContent === word)
     297          removeNode(el);
     298      }
     299    };
     300
     301    var onChangeTimer = 0;
     302    var lastElement = null; // usado para saber o elemento anterior a uma newline
     303    this.GetKeyEventHandler = function () {
     304      return function (evt) {
     305        var sel = getCaretPosition();
     306        if (_flipRunning && _versionValid && sel) {
     307          var key = evt.which || evt.charCode || evt.keyCode || 0;
     308          var element = null;
     309          if (lastElement && (key === 13 || key === 8 || key === 46)) {
     310            element = lastElement;
     311          }
     312          else {
     313            var node = sel.node;
     314            while (node && node.parentNode && node !== textContainer) {
     315              if (isError(node))
     316                removeNode(node);
     317              if (!element && tagsBlock[node.nodeName.toLowerCase()])
     318                element = node;
     319              node = node.parentNode;
     320            }
     321            lastElement = element;
     322          }
     323
     324          if (_settings.ScaytEnable) {
     325            clearTimeout(onChangeTimer);
     326
     327            if (checkErrorCount() && scaytImediateSpellChars.indexOf(String.fromCharCode(key)) >= 0
     328              || key === 13 || key === 8 || key === 46) {
     329
     330              if (ignoreTag(element))
     331                return;
     332              var blocks = markBlocks(element);
     333              blocks.forEach(function (v) {
     334                if (key === 13 || v.checked === 0) {
     335                  addToQueue({ block: v, options: { newline: key === 13 } });
     336                }
     337              });
     338            }
     339          }
     340        }
     341      };
     342    };
     343
     344    /* Selection METHODS */
     345    function hasSelection() {
     346      var sel = (textContainer.ownerDocument.getSelection ? textContainer.ownerDocument.getSelection() : textContainer.ownerDocument.selection) || null;
     347      return sel && !sel.isCollapsed;
     348    }
     349
     350    function getCaretPosition() {
     351      var sel = (textContainer.ownerDocument.getSelection ? textContainer.ownerDocument.getSelection() : textContainer.ownerDocument.selection) || null;
     352      if (sel && sel.isCollapsed) {
     353        var range = sel && sel.type !== 'None' && sel.getRangeAt && sel.rangeCount ? sel.getRangeAt(0) : null;
     354        if (range) {
     355          range.collapse(true);
     356          return {
     357            node: range.startContainer,
     358            offset: range.startOffset
     359          };
     360        }
     361      }
     362      return null;
     363    }
     364
     365    function setCaretPosition(p) {
     366      var sel = (textContainer.ownerDocument.getSelection ? textContainer.ownerDocument.getSelection() : textContainer.ownerDocument.selection) || null;
     367      if (sel.isCollapsed) {
     368        var range = sel && sel.type !== 'None' && sel.getRangeAt && sel.rangeCount ? sel.getRangeAt(0) : textContainer.ownerDocument.createRange();
     369        if (range && sel && p && p.node && p.node.textContent && p.node.textContent.length >= p.offset) {
     370          range.setStart(p.node, p.offset);
     371          range.collapse(true);
     372          sel.removeAllRanges();
     373          sel.addRange(range);
     374        }
     375      }
     376    }
     377
     378    /* Selection METHODS END */
     379
     380
     381    /* Funções internas */
     382
     383    function initDone(callback) {
     384      return function (result) {
     385        _hasInit = true;
     386        if (result && !(result.ErrorCode || result.code)) {
     387          _wsVersion = result.Application;
     388          _versionValid = compareVersions(_wsVersion, _minVersion) >= 0;
     389        }
     390        callback();
     391      };
     392    }
     393
     394    function initEngine(callback) {
     395      if (_hasInit)
     396        callback();
     397      else {
     398        var initdone = initDone(callback);
     399        doJSONRequest({
     400          Url: geturl(),
     401          Action: 'version',
     402          Method: 'GET',
     403          onRequest: function () { },
     404          onSuccess: initdone,
     405          onError: initdone,
     406          onTimeout: initdone
     407        });
     408      }
     409    }
     410
     411    function versionFromString(str) {
     412      var values = [];
     413      if (str) {
     414        var arr = null;
     415        var re = /([0-9]+)\.{0,1}/g;
     416        while ((arr = re.exec(str)) !== null)
     417          values.push(+arr[1]);
     418      }
     419      return values;
     420    }
     421
     422    function compareVersions(v1, v2) {
     423      var mv1 = versionFromString(v1);
     424      var mv2 = versionFromString(v2);
     425      var cmp = 0;
     426      for (var i = 0; i < Math.min(mv1.length, mv2.length) && cmp === 0; i++) {
     427        var r = mv1[i] - mv2[i];
     428        cmp = r !== 0 ? r / Math.abs(r) : 0;
     429      }
     430
     431      return cmp;
     432    }
     433
     434    function ignoreTag(element) {
     435      if (_settings.IgnoreTags && element && element.nodeName) {
     436        var itag = _ignoretags[element.nodeName.toLowerCase()];
     437        if (itag) {
     438          if (itag.attr) {
     439            var attrvalue = element.getAttribute(itag.attr);
     440            return attrvalue ? attrvalue.indexOf(itag.value) >= 0 : false;
     441          }
     442          else
     443            return true;
     444        }
     445      }
     446      return false;
     447    }
     448
     449    function geturl() {
     450      var url = _settings.Webservice;
     451      if (url.length > 0 && url[url.length - 1] !== '/')
     452        url += '/';
     453      return url;
     454    }
     455
     456    function isError(element) {
     457      return (element && element.className &&
     458        (element.className.indexOf(_pba_ort_error) >= 0 ||
     459          element.className.indexOf(_pba_gram_error) >= 0)) === true;
     460    }
     461
     462    function stopSpellCheck() {
     463      removeErrors(textContainer);
     464    }
     465
     466    var _doingCheck = false;
     467    function doSpellCheck(coldStart) {
     468      if (_doingCheck) return;
     469      _doingCheck = true;
     470
     471      var p = [];
     472      if (_settings.IgnoreTags) {
     473        traverseDom(textContainer, function (node) {
     474          if (node && node.nodeName) {
     475            if (ignoreTag(node))
     476              return false;
     477            if (tagsBlock[node.nodeName.toLowerCase()])
     478              p.push(node);
     479            return true;
     480          }
     481          return false;
     482        });
     483      }
     484      else {
     485        Object.keys(tagsBlock).forEach(function (f) {
     486          var elements = textContainer.getElementsByTagName(f);
     487          if (elements.length)
     488            p.push.apply(p, elements);
     489        });
     490      }
     491
     492      p.forEach(function (n) {
     493        if (n.nodeType !== 1)
     494          return;
     495
     496        if (coldStart)
     497          n.blocks = null;
     498
     499        if (checkErrorCount()) {
     500          var blocks = markBlocks(n);
     501          blocks.forEach(function (v) {
     502            if (v.checked === 1) {
     503              if (!areErrorsMarked(v.erros))
     504                markErrors(v, v.erros);
     505            }
     506            else if (v.checked === 0) {
     507              addToQueue({ block: v });
     508            }
     509          });
     510        }
     511      });
     512      _doingCheck = false;
     513    }
     514
     515    var spellcheckQueue = [];
     516    var waitingForResponseForBlock = null;
     517    var _processing = false;
     518
     519    function addToQueue(b) {
     520      if (b && b.block) {
     521        if (spellcheckQueue.filter(function (f) { return f.block.hash === b.block.hash; }).length === 0) {
     522          spellcheckQueue.push(b);
     523        }
     524      }
     525    }
     526
     527    var _nextAutoCheck = 0;
     528    function processQueue() {
     529      if (_flipRunning && _versionValid && waitingForResponseForBlock === null) {
     530        var time = (new Date()).getTime();
     531        if (spellcheckQueue.length > 0) {
     532          if (_processing)
     533            return;
     534          _processing = true;
     535
     536          var element = spellcheckQueue.shift();
     537
     538          if (!hasSelection() && checkErrorCount(true) && element && element.block && element.block.element && element.block.element.parentNode && element.block.element.offsetParent) {
     539            var block = element.block;
     540
     541            var hash = getBlockHash(block);
     542            if (hash !== block.hash) {
     543              block.hash = null;
     544              block.checked = 0;
     545            }
     546            else if (block.checked === 0) {
     547              var texto = block.text;
     548              if (trim(texto).length > 0) {
     549                block.checked = 2; // a correr
     550                block.request = {
     551                  id: doJSONRequest({
     552                    Url: geturl(),
     553                    Action: ['check', _settings.Language, _settings.GrammarSet, _settings.Acordo ? 'true' : 'false'].join("/"),
     554                    key: _settings.Key,
     555                    onSuccess: responseReceived,
     556                    onTimeout: responseTimeout,
     557                    onError: responseError,
     558                    Params: {
     559                      nline: element.options && element.options.newline ? 'true' : 'false'
     560                    },
     561                    Data: texto
     562                  }),
     563                  hash: hash
     564                };
     565                waitingForResponseForBlock = block;
     566              }
     567              else
     568                block.checked = 1;
     569            }
     570          }
     571          else {
     572            setTimeout(processQueue, 0);
     573          }
     574          _processing = false;
     575          _nextAutoCheck = time + 15000;
     576        }
     577        else if (time > _nextAutoCheck) {
     578          doSpellCheck(false);
     579          _nextAutoCheck = time + 15000;
     580        }
     581      }
     582    }
     583    setInterval(processQueue, 100);
     584
     585    function doJSONRequest(options) {
     586      var url = options.Url || null,
     587        action = options.Action || null,
     588        params = options.Params || null,
     589        data = options.Data || null,
     590        method = options.Method || "POST",
     591        contenttype = options.contentType || "text/plain",
     592        key = options.key || "",
     593        // Callbacks - por defeito chama os eventos associados
     594        on_r = options.onRequest || function (action, params, data) { doEvent("Request", this, [action, params, data]); },
     595        on_s = options.onSuccess || function (result) { doEvent("Success", this, [result]); },
     596        on_t = options.onTimeout || function () { doEvent("Timeout", this, null); },
     597        on_e = options.onError || function (response) { doEvent("Error", this, [response]); };
     598
     599      var xmlhttprequest = new XMLHttpRequest();
     600      if (xmlhttprequest) {
     601        var sparams = (params && ("?" + serialize(params))) || "";
     602        xmlhttprequest.open(method, url + action + sparams, true);
     603        xmlhttprequest.setRequestHeader("Content-type", contenttype);
     604        xmlhttprequest.setRequestHeader("X-Priberam-auth-key", key)
     605        xmlhttprequest.responseType = "json";
     606        xmlhttprequest.async = true;
     607        xmlhttprequest.ontimeout = on_t;
     608        xmlhttprequest.onreadystatechange = function () {
     609          if (xmlhttprequest.readyState === 4) {
     610            var response;
     611            try {
     612              response = JSON.parse(xmlhttprequest.responseText);
     613            }
     614            catch (err) {
     615              response = xmlhttprequest.response;
     616            }
     617
     618            if (xmlhttprequest.status === 200) {
     619              on_s(response);
     620            }
     621            else {
     622              on_e({
     623                status: xmlhttprequest.status,
     624                reason: xmlhttprequest.statusText
     625              });
     626              spellcheckQueue = [];
     627            }
     628          }
     629
    47630        };
    48 
    49         /* Options */
    50 
    51         this.defaultSettings = {
    52             NumMaxSugest: 4,
    53             ScaytEnable: true,
    54             AutoStart: true,
    55             Language: 'pt-pt', //'pt-br', 'es'
    56             Acordo: true,
    57             Webservice: '',
    58             GrammarSet: 'Common',
    59             CanAddWords: false,
    60             Key: '',
    61             MaxErrors: 200,
    62             IgnoreTags: "",
    63             /* Events */
    64             onRequest: null,
    65             onSuccess: null,
    66             onError: null,
    67             onTimeout: null
     631        xmlhttprequest.timeout = 10000;
     632
     633        on_r(action, params, data);
     634        xmlhttprequest.send(data);
     635
     636      }
     637
     638    }
     639
     640    function serialize(obj, prefix) {
     641      if (obj) {
     642        var pre = (prefix && encodeURIComponent(prefix)) || "";
     643        return Object.keys(obj).map(function (k) {
     644          return pre + encodeURIComponent(k) + "=" + encodeURIComponent(obj[k] === undefined ? "" : obj[k].toString());
     645        }).join("&");
     646      }
     647      else
     648        return "";
     649    }
     650
     651    function responseReceived(result) {
     652      if (!result || (result && (result.ErrorCode || result.code))) {
     653        doEvent("Error", this, [result]);
     654        spellcheckQueue = [];
     655      }
     656      else if (waitingForResponseForBlock && !hasSelection()) {
     657        doEvent("Success", this, [result]);
     658
     659        var element = waitingForResponseForBlock;
     660        var newhash = getBlockHash(element);
     661        // Verifica se a texto não mudou entretanto
     662        if (newhash === element.request.hash) {
     663          var offset = 0;
     664          for (var j = 0; j < result.length; j++) {
     665            var json = result[j];
     666            if (json.errors && json.errors.length > 0) {
     667              for (var i = 0; i < json.errors.length; i++) {
     668                json.errors[i].spos += offset;
     669                json.errors[i].block = element;
     670                var e1 = json.errors[i].spos;
     671                var e2 = e1 + json.errors[i].len;
     672                // ignora erros sobrepostos
     673                var a = element.erros.filter(function (f) {
     674                  var f1 = f.spos;
     675                  var f2 = f.spos + f.len;
     676                  return f2 >= e1 && f1 <= e2;
     677                });
     678
     679                if (a.length == 0)
     680                  element.erros.push(json.errors[i]);
     681              }
     682            }
     683            offset += json.len;
     684            markErrors(element, element.erros);
     685            element.checked = 1;
     686
     687          }
     688        }
     689      }
     690      waitingForResponseForBlock = null;
     691    }
     692
     693    function responseTimeout() {
     694      doEvent("Timeout", this, null);
     695      spellcheckQueue = [];
     696
     697      if (waitingForResponseForBlock)
     698        waitingForResponseForBlock.hash = null;
     699      waitingForResponseForBlock = null;
     700    }
     701
     702    function responseError(err) {
     703      doEvent("Error", this, [err]);
     704      spellcheckQueue = [];
     705
     706      if (waitingForResponseForBlock)
     707        waitingForResponseForBlock.hash = null;
     708      waitingForResponseForBlock = null;
     709    }
     710
     711    function checkErrorCount(update) {
     712      var spans = [].concat.apply([], textContainer.ownerDocument.getElementsByClassName(_pba_gram_error));
     713      spans = [].concat.apply(spans, textContainer.ownerDocument.getElementsByClassName(_pba_ort_error));
     714      if (update) {
     715        spans.forEach(function (v) {
     716          v.setAttribute("class", v.getAttribute("class").replace(_pba_hide_error, ""));
     717        });
     718        if (spans.length >= _settings.MaxErrors) {
     719          spans.forEach(function (v) {
     720            v.setAttribute("class", v.getAttribute("class").trim() + " " + _pba_hide_error);
     721          });
     722        }
     723      }
     724      return spans.length < _settings.MaxErrors;
     725    }
     726
     727    function getHash(text) {
     728      return murmurhash3_32_gc(text || '', 25);
     729    }
     730
     731    function doRangeIntersect(r1, r2, touch) {
     732      if (!r1 || !r2)
     733        return false;
     734      touch = touch === false ? false : true;
     735      try {
     736        return r1.compareBoundaryPoints(Range.END_TO_START, r2) * r2.compareBoundaryPoints(Range.END_TO_START, r1) >= (touch ? 0 : 1);
     737      }
     738      catch (err) {
     739        return false;
     740      }
     741    }
     742
     743    function traverseDom(node, callback) {
     744      if (callback(node)) {
     745        node = node.firstChild;
     746        while (node) {
     747          traverseDom(node, callback);
     748          node = node.nextSibling;
     749        }
     750      }
     751    }
     752
     753    function getBlockTextNodes(block) {
     754
     755      if (!block || !block.element) return [];
     756
     757      var textNodes = getAllTextNodes(block.element);
     758      if (textNodes.length <= block.index)
     759        return [];
     760      return textNodes[block.index];
     761    }
     762
     763    function getAllTextNodes(root) {
     764
     765      var descendants = [];
     766      var current = [];
     767      traverseDom(root, function (node) {
     768        if (node && node.nodeName && tagsBlock[node.nodeName.toLowerCase()]) {
     769          if (current.length > 0)
     770            descendants.push(current);
     771          current = [];
     772          if (node !== root)
     773            return false;
     774        }
     775        else if (node.nodeType === 3)
     776          current.push(node);
     777        return true;
     778      });
     779      if (current.length > 0)
     780        descendants.push(current);
     781      return descendants;
     782    }
     783
     784    function createTag(tipo) {
     785
     786      var tag = textContainer.ownerDocument.createElement('span');
     787      tag.className = (tipo === 'spell' ? _pba_ort_error : _pba_gram_error);
     788      if (tipo === 1) {
     789        tag.onmouseover = function () {
     790          //showToolTip(tag);
    68791        };
    69 
    70         function extendOptions(d, n) {
    71             var r = {};
    72             Object.keys(d).forEach(function (f) {
    73                 r[f] = n.hasOwnProperty(f) ? n[f] : d[f];
     792        tag.onmouseout = function () {
     793          //hideToolTip(tag);
     794        };
     795      }
     796
     797      return tag;
     798    }
     799
     800    function surroundNodeTextWithTag(block, offset, length, tag) {
     801
     802      var textNodes = getBlockTextNodes(block);
     803
     804      var startNode = null, endNode = null;
     805      var start = offset;
     806      var i = 0;
     807      for (; i < textNodes.length; i++) {
     808        if (textNodes[i].length > start) {
     809          startNode = textNodes[i];
     810          break;
     811        }
     812        start -= textNodes[i].length;
     813      }
     814
     815      if (!startNode)
     816        return false;
     817
     818      var end = length + start;
     819      for (; i < textNodes.length; i++) {
     820        if (textNodes[i].length >= end) {
     821          endNode = textNodes[i];
     822          break;
     823        }
     824        end -= textNodes[i].length;
     825      }
     826      if (!endNode)
     827        return false;
     828
     829      try {
     830        var range = textContainer.ownerDocument.createRange();
     831        range.setStart(startNode, start);
     832        range.setEnd(endNode, end);
     833
     834        var pos = getCaretPosition();
     835        if (pos && pos.node) {
     836
     837          var caretRange = textContainer.ownerDocument.createRange();
     838          caretRange.setStart(pos.node, pos.offset);
     839          caretRange.collapse(true);
     840
     841          if (range.compareBoundaryPoints(Range.START_TO_END, caretRange) < 0) {
     842            if (range.startContainer === pos.node) {
     843              tag.appendChild(range.extractContents());
     844              range.insertNode(tag);
     845              //setCaretPosition({
     846              //    node: tag.nextSibling,
     847              //    offset: pos.offset - range.startOffset - tag.textContent.length
     848              //});
     849            }
     850            else {
     851              tag.appendChild(range.extractContents());
     852              range.insertNode(tag);
     853            }
     854          }
     855          else if (range.compareBoundaryPoints(Range.START_TO_START, caretRange) > 0) {
     856            tag.appendChild(range.extractContents());
     857            range.insertNode(tag);
     858          }
     859          else if (range.commonAncestorContainer == caretRange.commonAncestorContainer) {
     860            var dif = pos.offset - range.startOffset;
     861            if (range.startContainer === pos.node)
     862              dif = pos.offset - range.startOffset;
     863            else {
     864              for (i = textNodes.indexOf(range.startContainer); i < textNodes.length && textNodes[i] !== pos.node; i++) {
     865                dif += textNodes[i].textContent.length;
     866              }
     867            }
     868            tag.appendChild(range.extractContents());
     869            range.insertNode(tag);
     870            var textnode = null, lastnode = null;
     871            traverseDom(tag, function (node) {
     872              if (!textnode && node.nodeType === 3) {
     873                if (node.textContent.length > dif)
     874                  textnode = node;
     875                else
     876                  dif -= node.textContent.length;
     877                lastnode = node;
     878              }
     879              return true;
    74880            });
    75             return r;
    76         }
    77 
    78         var _settings = extendOptions(this.defaultSettings, {});
    79 
    80         var scaytImediateSpellChars = ' .,;:!?\xA0',
    81             _flipRunning = false,
    82             _flipStarting = false,
    83             _pba_ort_error = 'pba_ort_error',
    84             _pba_gram_error = 'pba_gram_error',
    85             _pba_hide_error = 'pba_hide_error',
    86             _ignoredWords = [],
    87             _minVersion = "1.5.0",
    88             _wsVersion = "0.0.0",
    89             _versionValid = false,
    90             _hasInit = false,
    91             _ignoretags = {};
    92 
    93         function calcIgnoreTags() {
    94             _ignoretags = {};
    95             _settings.IgnoreTags && _settings.IgnoreTags.constructor === String && _settings.IgnoreTags.split(' ').forEach(function (f) {
    96                 var split = f.split(/([\.@#][a-z:=\d]+)/gi).filter(function (i) { return i; });
    97                 if (split.length === 0) return;
    98                 var value = {};
    99                 if (split.length > 1) {
    100                     var selector = split[1].substr(1);
    101                     switch (split[1][0]) {
    102                         case '.':
    103                             value.attr = 'class';
    104                             value.value = selector;
    105                             break;
    106                         case '#':
    107                             value.attr = 'id';
    108                             value.value = selector;
    109                             break;
    110                         default:
    111                             var sa = selector.split('=');
    112                             value.attr = sa[0];
    113                             value.value = sa[1];
    114                             break;
    115                     }
    116                 }
    117                 _ignoretags[split[0]] = value;
     881
     882            setCaretPosition({
     883              node: textnode ? textnode : lastnode,
     884              offset: textnode ? dif : lastnode.length
    118885            });
    119         }
    120 
    121         calcIgnoreTags();
    122 
    123         function trim(str) {
    124             var trimRegex = /(?:^[\s]+)|(?:[\s]+$)/g;
    125             return str.replace(trimRegex, '');
    126         }
    127 
    128         var events = {};
    129         function doEvent(name, obj, args) {
    130             for (var i = 0; i < (events[name] && events[name].length || 0) ; i++) {
    131                 events[name][i].apply(obj, args);
    132             }
    133         }
    134 
    135 
    136         /* métodos publicos */
    137 
    138         this.setTextContainer = function (tc) {
    139             textContainer = tc;
    140             spellcheckQueue = [];
    141         };
    142 
    143         this.on = function (event, callback) {
    144             if (event && typeof (event) === 'string' &&
    145                 callback && typeof (callback) === 'function') {
    146                 if (!events[event])
    147                     events[event] = [];
    148                 var e = events[event];
    149                 var idx = e.indexOf(callback);
    150                 if (idx < 0)
    151                     e.push(callback);
    152             }
    153         };
    154 
    155         this.isError = function (element) {
    156             return isError(element) && element.className.indexOf(_pba_hide_error) < 0;
    157         };
    158 
    159         this.Settings = function (settings, value) {
    160             if (typeof (settings) === 'undefined')
    161                 return _settings;
    162             else if (typeof (settings) === 'object') {
    163                 Object.keys(settings).forEach(function (f) {
    164                     self.Settings(f, settings[f]);
    165                 });
    166             }
    167             else if (typeof (settings) === 'string') {
    168                 if (typeof (value) !== 'undefined') {
    169                     if (settings.substr(0, 2) === 'on')
    170                         this.on(settings.substr(2), value);
    171                     else
    172                         _settings[settings] = value;
    173 
    174                     if (settings === 'IgnoreTags')
    175                         calcIgnoreTags();
    176                 }
    177                 else
    178                     return _settings[settings];
    179             }
    180         };
    181 
    182         this.isRunning = function () {
    183             return _flipRunning;
    184         };
    185 
    186         this.status = function () {
    187             if (_versionValid)
    188                 return spellcheckQueue.length > 0 || waitingForResponseForBlock ? "checking" : "idle";
    189             else
    190                 return "verErr";
    191         };
    192 
    193         this.hasErrors = function () {
    194             if (!(textContainer && textContainer.ownerDocument))
    195                 return false;
    196             var e = textContainer.ownerDocument.getElementsByTagName('span');
    197             for (var i = 0; i < e.length; i++) {
    198                 if (isError(e[i]))
    199                     return true;
    200             }
    201             return false;
    202         };
    203 
    204         this.textContainer = function (textcontainer) {
    205             if (typeof (textcontainer) === 'undefined')
    206                 return textContainer;
    207             else
    208                 textContainer = textcontainer;
    209         };
    210 
    211         this.startEngine = function (coldStart /* false */) {
    212             if (_flipRunning || _flipStarting) return;
    213             _flipStarting = true;
    214             textContainer.setAttribute("spellcheck", false);
    215 
    216             initEngine(function () {
    217                 _flipRunning = true;
    218                 _flipStarting = false;
    219                 if (_versionValid) {
    220                     removeErrors(textContainer); // para ter a certeza que não fica nada sublinhado
    221                     doSpellCheck(coldStart);
    222                 }
    223                 else
    224                     console.log("FLiP Api version is " + _wsVersion + " must be atleast " + _minVersion);
    225             });
    226         };
    227 
    228         this.stopEngine = function () {
    229             if (!_flipRunning) return;
    230             stopSpellCheck();
    231             _flipRunning = false;
    232             _flipStarting = false;
    233         };
    234 
    235         this.restartEngine = function (coldStart /* true */) {
    236             this.stopEngine();
    237             this.startEngine(coldStart !== false);
    238         };
    239 
    240         this.shutdown = function() {
    241             this.stopEngine();
    242             _hasInit = false;
    243         };
    244 
    245         this.spellCheck = function () {
    246             if (_flipRunning && _versionValid)
    247                 doSpellCheck();
    248 
    249         };
    250 
    251         this.removeMarkupFromHtml = function (html) {
    252             if (!(textContainer && textContainer.ownerDocument && html))
    253                 return;
    254             var escaped = html.replace(/\&nbsp;/g, "___NBSP___");
    255             var div = textContainer.ownerDocument.createElement('div');
    256             div.innerHTML = escaped;
    257             removeErrors(div, true);
    258 
    259             return div.innerHTML.replace(/\&nbsp;/g, " ").replace(/___NBSP___/g, "&nbsp;");
    260         };
    261 
    262         this.replaceWord = function (node, word) {
    263             if (!(node && node.error)) return;
    264 
    265             node.textContent = word;
    266             addToQueue({ block: node.error.block });
    267             removeNode(node);
    268         };
    269 
    270         this.addWord = function (node) {
    271             if (!(node && node.error && trim(node.textContent).length > 0 && _settings.CanAddWords)) return;
    272 
    273             doJSONRequest({
    274                 Url: geturl(),
    275                 Action: ['userdict', _settings.Language, _settings.Acordo ? 'true' : 'false', encodeURIComponent(node.textContent || "")].join("/"),
    276                 key:  _settings.Key,
    277                 Method: "PUT"
    278             });
    279 
    280             this.ignoreAllWords(node);
    281         };
    282 
    283         this.ignoreWord = function (node) {
    284             if (!(node && node.error)) return;
    285             _ignoredWords.push(node.textContent);
    286             removeNode(node);
    287         };
    288 
    289         this.ignoreAllWords = function (node) {
    290             if (!(node && node.error)) return;
    291             var word = node.textContent;
    292             _ignoredWords.push(word);
    293             var p = [].concat.apply([], node.ownerDocument.getElementsByTagName('span'));
    294             for (var i = 0; i < p.length; i++) {
    295                 var el = p[i];
    296                 if (isError(el) && el.textContent === word)
    297                     removeNode(el);
    298             }
    299         };
    300 
    301         var onChangeTimer = 0;
    302         var lastElement = null; // usado para saber o elemento anterior a uma newline
    303         this.GetKeyEventHandler = function () {
    304             return function (evt) {
    305                 var sel = getCaretPosition();
    306                 if (_flipRunning && _versionValid && sel) {
    307                     var key = evt.which || evt.charCode || evt.keyCode || 0;
    308                     var element = null;
    309                     if (lastElement && (key === 13 || key === 8 || key === 46)) {
    310                         element = lastElement;
    311                     }
    312                     else {
    313                         var node = sel.node;
    314                         while (node && node.parentNode && node !== textContainer) {
    315                             if (isError(node))
    316                                 removeNode(node);
    317                             if (!element && tagsBlock[node.nodeName.toLowerCase()])
    318                                 element = node;
    319                             node = node.parentNode;
    320                         }
    321                         lastElement = element;
    322                     }
    323 
    324                     if (_settings.ScaytEnable) {
    325                         clearTimeout(onChangeTimer);
    326 
    327                         if (checkErrorCount() && scaytImediateSpellChars.indexOf(String.fromCharCode(key)) >= 0
    328                             || key === 13 || key === 8 || key === 46) {
    329 
    330                             if (ignoreTag(element))
    331                                 return;
    332                             var blocks = markBlocks(element);
    333                             blocks.forEach(function (v) {
    334                                 if (key === 13 || v.checked === 0) {
    335                                     addToQueue({ block: v, options: { newline: key === 13 } });
    336                                 }
    337                             });
    338                         }
    339                     }
    340                 }
    341             };
    342         };
    343 
    344         /* Selection METHODS */
    345         function hasSelection() {
    346             var sel = (textContainer.ownerDocument.getSelection ? textContainer.ownerDocument.getSelection() : textContainer.ownerDocument.selection) || null;
    347             return sel && !sel.isCollapsed;
    348         }
    349 
    350         function getCaretPosition() {
    351             var sel = (textContainer.ownerDocument.getSelection ? textContainer.ownerDocument.getSelection() : textContainer.ownerDocument.selection) || null;
    352             if (sel && sel.isCollapsed) {
    353                 var range = sel && sel.type !== 'None' && sel.getRangeAt && sel.rangeCount ? sel.getRangeAt(0) : null;
    354                 if (range) {
    355                     range.collapse(true);
    356                     return {
    357                         node: range.startContainer,
    358                         offset: range.startOffset
    359                     };
    360                 }
    361             }
    362             return null;
    363         }
    364 
    365         function setCaretPosition(p) {
    366             var sel = (textContainer.ownerDocument.getSelection ? textContainer.ownerDocument.getSelection() : textContainer.ownerDocument.selection) || null;
    367             if (sel.isCollapsed) {
    368                 var range = sel && sel.type !== 'None' && sel.getRangeAt && sel.rangeCount ? sel.getRangeAt(0) : textContainer.ownerDocument.createRange();
    369                 if (range && sel && p && p.node && p.node.textContent && p.node.textContent.length >= p.offset) {
    370                     range.setStart(p.node, p.offset);
    371                     range.collapse(true);
    372                     sel.removeAllRanges();
    373                     sel.addRange(range);
    374                 }
    375             }
    376         }
    377 
    378         /* Selection METHODS END */
    379 
    380 
    381         /* Funções internas */
    382 
    383         function initDone(callback) {
    384             return function (result) {
    385                 _hasInit = true;
    386                 if (result && !(result.ErrorCode || result.code)) {
    387                     _wsVersion = result.Application;
    388                     _versionValid = compareVersions(_wsVersion, _minVersion) >= 0;
    389                 }
    390                 callback();
    391             };
    392         }
    393 
    394         function initEngine(callback) {
    395             if (_hasInit)
    396                 callback();
    397             else {
    398                 var initdone = initDone(callback);
    399                 doJSONRequest({
    400                     Url: geturl(),
    401                     Action: 'version',
    402                     Method: 'GET',
    403                     onRequest: function () { },
    404                     onSuccess: initdone,
    405                     onError: initdone,
    406                     onTimeout: initdone
    407                 });
    408             }
    409         }
    410 
    411         function versionFromString(str) {
    412             var values = [];
    413             if (str) {
    414                 var arr = null;
    415                 var re = /([0-9]+)\.{0,1}/g;
    416                 while ((arr = re.exec(str)) !== null)
    417                     values.push(+arr[1]);
    418             }
    419             return values;
    420         }
    421 
    422         function compareVersions(v1, v2) {
    423             var mv1 = versionFromString(v1);
    424             var mv2 = versionFromString(v2);
    425             var cmp = 0;
    426             for (var i = 0; i < Math.min(mv1.length, mv2.length) && cmp === 0; i++) {
    427                 var r = mv1[i] - mv2[i];
    428                 cmp = r !== 0 ? r / Math.abs(r) : 0;
    429             }
    430 
    431             return cmp;
    432         }
    433 
    434         function ignoreTag(element) {
    435             if (_settings.IgnoreTags && element && element.nodeName) {
    436                 var itag = _ignoretags[element.nodeName.toLowerCase()];
    437                 if (itag)
    438                 {
    439                     if (itag.attr) {
    440                         var attrvalue = element.getAttribute(itag.attr);
    441                         return attrvalue ? attrvalue.indexOf(itag.value) >= 0 : false;
    442                     }
    443                     else
    444                         return true;
    445                 }
    446             }
    447             return false;
    448         }
    449 
    450         function geturl() {
    451             var url = _settings.Webservice;
    452             if (url.length > 0 && url[url.length - 1] !== '/')
    453                 url += '/';
    454             return url;
    455         }
    456 
    457         function isError(element) {
    458             return (element && element.className &&
    459                 (element.className.indexOf(_pba_ort_error) >= 0 ||
    460                 element.className.indexOf(_pba_gram_error) >= 0)) === true;
    461         }
    462 
    463         function stopSpellCheck() {
    464             removeErrors(textContainer);
    465         }
    466 
    467         var _doingCheck = false;
    468         function doSpellCheck(coldStart) {
    469             if (_doingCheck) return;
    470             _doingCheck = true;
    471 
    472             var p = [];
    473             if (_settings.IgnoreTags) {
    474                 traverseDom(textContainer, function (node) {
    475                     if (node && node.nodeName) {
    476                         if (ignoreTag(node))
    477                             return false;
    478                         if (tagsBlock[node.nodeName.toLowerCase()])
    479                             p.push(node);
    480                         return true;
    481                     }
    482                     return false;
    483                 });
    484             }
    485             else {
    486                 Object.keys(tagsBlock).forEach(function (f) {
    487                     var elements = textContainer.getElementsByTagName(f);
    488                     if (elements.length)
    489                         p.push.apply(p, elements);
    490                 });
    491             }
    492 
    493             p.forEach(function (n) {
    494                 if (n.nodeType !== 1)
    495                     return;
    496 
    497                 if (coldStart)
    498                     n.blocks = null;
    499 
    500                 if (checkErrorCount()) {
    501                     var blocks = markBlocks(n);
    502                     blocks.forEach(function (v) {
    503                         if (v.checked === 1) {
    504                             if (!areErrorsMarked(v.erros))
    505                                 markErrors(v, v.erros);
    506                         }
    507                         else if (v.checked === 0) {
    508                             addToQueue({ block: v });
    509                         }
    510                     });
    511                 }
    512             });
    513             _doingCheck = false;
    514         }
    515 
    516         var spellcheckQueue = [];
    517         var waitingForResponseForBlock = null;
    518         var _processing = false;
    519 
    520         function addToQueue(b) {
    521             if (b && b.block) {
    522                 if (spellcheckQueue.filter(function (f) { return f.block.hash === b.block.hash; }).length === 0) {
    523                     spellcheckQueue.push(b);
    524                 }
    525             }
    526         }
    527 
    528         var _nextAutoCheck = 0;
    529         function processQueue() {
    530             if (_flipRunning && _versionValid && waitingForResponseForBlock === null) {
    531                 var time = (new Date()).getTime();
    532                 if (spellcheckQueue.length > 0) {
    533                     if (_processing)
    534                         return;
    535                     _processing = true;
    536 
    537                     var element = spellcheckQueue.shift();
    538 
    539                     if (!hasSelection() && checkErrorCount(true) && element && element.block && element.block.element && element.block.element.parentNode && element.block.element.offsetParent) {
    540                         var block = element.block;
    541 
    542                         var hash = getBlockHash(block);
    543                         if (hash !== block.hash) {
    544                             block.hash = null;
    545                             block.checked = 0;
    546                         }
    547                         else if (block.checked === 0) {
    548                             var texto = block.text;
    549                             if (trim(texto).length > 0) {
    550                                 block.checked = 2; // a correr
    551                                 block.request = {
    552                                     id: doJSONRequest({
    553                                         Url: geturl(),
    554                                         Action: ['check', _settings.Language, _settings.GrammarSet, _settings.Acordo ? 'true' : 'false'].join("/"),
    555                                         key: _settings.Key,
    556                                         onSuccess: responseReceived,
    557                                         onTimeout: responseTimeout,
    558                                         onError: responseError,
    559                                         Params: {
    560                                             nline: element.options && element.options.newline ? 'true' : 'false'
    561                                         },
    562                                         Data: texto
    563                                     }),
    564                                     hash: hash
    565                                 };
    566                                 waitingForResponseForBlock = block;
    567                             }
    568                             else
    569                                 block.checked = 1;
    570                         }
    571                     }
    572                     else {
    573                         setTimeout(processQueue, 0);
    574                     }
    575                     _processing = false;
    576                     _nextAutoCheck = time + 15000;
    577                 }
    578                 else if (time > _nextAutoCheck) {
    579                     doSpellCheck(false);
    580                     _nextAutoCheck = time + 15000;
    581                 }
    582             }
    583         }
    584         setInterval(processQueue, 100);
    585 
    586         function doJSONRequest(options) {
    587             var url = options.Url || null,
    588             action = options.Action || null,
    589             params = options.Params || null,
    590             data = options.Data || null,
    591             method = options.Method || "POST",
    592             contenttype = options.contentType || "text/plain",
    593             key = options.key || "",
    594             // Callbacks - por defeito chama os eventos associados
    595             on_r = options.onRequest || function (action, params, data) { doEvent("Request", this, [action, params, data]); },
    596             on_s = options.onSuccess || function (result) { doEvent("Success", this, [result]); },
    597             on_t = options.onTimeout || function () { doEvent("Timeout", this, null); },
    598             on_e = options.onError || function (response) { doEvent("Error", this, [response]); };
    599 
    600             var xmlhttprequest = new XMLHttpRequest();
    601             if (xmlhttprequest) {
    602                 var sparams = (params && ("?" + serialize(params))) || "";
    603                 xmlhttprequest.open(method, url + action + sparams, true);
    604                 xmlhttprequest.setRequestHeader("Content-type", contenttype);
    605                 xmlhttprequest.setRequestHeader("X-Priberam-auth-key", key)
    606                 xmlhttprequest.responseType = "json";
    607                 xmlhttprequest.async = true;
    608                 xmlhttprequest.ontimeout = on_t;
    609                 xmlhttprequest.onreadystatechange = function () {
    610                     if (xmlhttprequest.readyState === 4) {
    611                         var response;
    612                         try {
    613                             response = JSON.parse(xmlhttprequest.responseText);
    614                         }
    615                         catch (err) {
    616                             response = xmlhttprequest.response;
    617                         }
    618 
    619                         if (xmlhttprequest.status === 200) {
    620                             on_s(response);
    621                         }
    622                         else {
    623                             on_e({
    624                                 status: xmlhttprequest.status,
    625                                 reason: xmlhttprequest.statusText
    626                             });
    627                             spellcheckQueue = [];
    628                         }
    629                     }
    630 
    631                 };
    632                 xmlhttprequest.timeout = 10000;
    633 
    634                 on_r(action, params, data);
    635                 xmlhttprequest.send(data);
    636 
    637             }
    638 
    639         }
    640 
    641         function serialize(obj, prefix) {
    642             if (obj) {
    643                 var pre = (prefix && encodeURIComponent(prefix)) || "";
    644                 return Object.keys(obj).map(function (k) {
    645                     return pre + encodeURIComponent(k) + "=" + encodeURIComponent(obj[k] === undefined ? "" : obj[k].toString());
    646                 }).join("&");
    647             }
    648             else
    649                 return "";
    650         }
    651 
    652         function responseReceived(result) {
    653             if (!result || (result && (result.ErrorCode || result.code))) {
    654                 doEvent("Error", this, [result]);
    655                 spellcheckQueue = [];
    656             }
    657             else if (waitingForResponseForBlock && !hasSelection()) {
    658                 doEvent("Success", this, [result]);
    659 
    660                 var element = waitingForResponseForBlock;
    661                 var newhash = getBlockHash(element);
    662                 // Verifica se a texto não mudou entretanto
    663                 if (newhash === element.request.hash) {
    664                     var offset = 0;
    665                     for (var j = 0; j < result.length; j++) {
    666                         var json = result[j];
    667                         if (json.errors && json.errors.length > 0) {
    668                             for (var i = 0; i < json.errors.length; i++) {
    669                                 json.errors[i].spos += offset;
    670                                 json.errors[i].block = element;
    671                                 var e1 = json.errors[i].spos;
    672                                 var e2 = e1 + json.errors[i].len;
    673                                 // ignora erros sobrepostos
    674                                 var a = element.erros.filter(function (f) {
    675                                     var f1 = f.spos;
    676                                     var f2 = f.spos + f.len;
    677                                     return f2 >= e1 && f1 <= e2;
    678                                 });
    679 
    680                                 if (a.length == 0)
    681                                     element.erros.push(json.errors[i]);
    682                             }
    683                         }
    684                         offset += json.len;
    685                         markErrors(element, element.erros);
    686                         element.checked = 1;
    687 
    688                     }
    689                 }
    690             }
    691             waitingForResponseForBlock = null;
    692         }
    693 
    694         function responseTimeout() {
    695             doEvent("Timeout", this, null);
    696             spellcheckQueue = [];
    697 
    698             if (waitingForResponseForBlock)
    699                 waitingForResponseForBlock.hash = null;
    700             waitingForResponseForBlock = null;
    701         }
    702 
    703         function responseError(err) {
    704             doEvent("Error", this, [err]);
    705             spellcheckQueue = [];
    706 
    707             if (waitingForResponseForBlock)
    708                 waitingForResponseForBlock.hash = null;
    709             waitingForResponseForBlock = null;
    710         }
    711 
    712         function checkErrorCount(update) {
    713             var spans = [].concat.apply([], textContainer.ownerDocument.getElementsByClassName(_pba_gram_error));
    714             spans = [].concat.apply(spans, textContainer.ownerDocument.getElementsByClassName(_pba_ort_error));
    715             if (update) {
    716                 spans.forEach(function (v) {
    717                     v.setAttribute("class", v.getAttribute("class").replace(_pba_hide_error, ""));
    718                 });
    719                 if (spans.length >= _settings.MaxErrors) {
    720                     spans.forEach(function (v) {
    721                         v.setAttribute("class", v.getAttribute("class").trim() + " " + _pba_hide_error);
    722                     });
    723                 }
    724             }
    725             return spans.length < _settings.MaxErrors;
    726         }
    727 
    728         function getHash(text) {
    729             return murmurhash3_32_gc(text || '', 25);
    730         }
    731 
    732         function doRangeIntersect(r1, r2, touch) {
    733             if (!r1 || !r2)
    734                 return false;
    735             touch = touch === false ? false : true;
    736             try {
    737                 return r1.compareBoundaryPoints(Range.END_TO_START, r2) * r2.compareBoundaryPoints(Range.END_TO_START, r1) >= (touch ? 0 : 1);
    738             }
    739             catch (err) {
    740                 return false;
    741             }
    742         }
    743 
    744         function traverseDom(node, callback) {
    745             if (callback(node)) {
    746                 node = node.firstChild;
    747                 while (node) {
    748                     traverseDom(node, callback);
    749                     node = node.nextSibling;
    750                 }
    751             }
    752         }
    753 
    754         function getBlockTextNodes(block) {
    755 
    756             if (!block || !block.element) return [];
    757 
    758             var textNodes = getAllTextNodes(block.element);
    759             if (textNodes.length <= block.index)
    760                 return [];
    761             return textNodes[block.index];
    762         }
    763 
    764         function getAllTextNodes(root) {
    765 
    766             var descendants = [];
    767             var current = [];
    768             traverseDom(root, function (node) {
    769                 if (node && node.nodeName && tagsBlock[node.nodeName.toLowerCase()]) {
    770                     if (current.length > 0)
    771                         descendants.push(current);
    772                     current = [];
    773                     if (node !== root)
    774                         return false;
    775                 }
    776                 else if (node.nodeType === 3)
    777                     current.push(node);
    778                 return true;
    779             });
    780             if (current.length > 0)
    781                 descendants.push(current);
    782             return descendants;
    783         }
    784 
    785         function createTag(tipo) {
    786 
    787             var tag = textContainer.ownerDocument.createElement('span');
    788             tag.className = (tipo === 'spell' ? _pba_ort_error : _pba_gram_error);
    789             if (tipo === 1) {
    790                 tag.onmouseover = function () {
    791                     //showToolTip(tag);
    792                 };
    793                 tag.onmouseout = function () {
    794                     //hideToolTip(tag);
    795                 };
    796             }
    797 
    798             return tag;
    799         }
    800 
    801         function surroundNodeTextWithTag(block, offset, length, tag) {
    802 
    803             var textNodes = getBlockTextNodes(block);
    804 
    805             var startNode = null, endNode = null;
    806             var start = offset;
    807             var i = 0;
    808             for (; i < textNodes.length; i++) {
    809                 if (textNodes[i].length > start) {
    810                     startNode = textNodes[i];
    811                     break;
    812                 }
    813                 start -= textNodes[i].length;
    814             }
    815 
    816             if (!startNode)
    817                 return false;
    818 
    819             var end = length + start;
    820             for (; i < textNodes.length; i++) {
    821                 if (textNodes[i].length >= end) {
    822                     endNode = textNodes[i];
    823                     break;
    824                 }
    825                 end -= textNodes[i].length;
    826             }
    827             if (!endNode)
    828                 return false;
    829 
    830             try {
    831                 var range = textContainer.ownerDocument.createRange();
    832                 range.setStart(startNode, start);
    833                 range.setEnd(endNode, end);
    834 
    835                 var pos = getCaretPosition();
    836                 if (pos && pos.node) {
    837 
    838                     var caretRange = textContainer.ownerDocument.createRange();
    839                     caretRange.setStart(pos.node, pos.offset);
    840                     caretRange.collapse(true);
    841 
    842                     if (range.compareBoundaryPoints(Range.START_TO_END, caretRange) < 0) {
    843                         if (range.startContainer === pos.node) {
    844                             tag.appendChild(range.extractContents());
    845                             range.insertNode(tag);
    846                             //setCaretPosition({
    847                             //    node: tag.nextSibling,
    848                             //    offset: pos.offset - range.startOffset - tag.textContent.length
    849                             //});
    850                         }
    851                         else {
    852                             tag.appendChild(range.extractContents());
    853                             range.insertNode(tag);
    854                         }
    855                     }
    856                     else if (range.compareBoundaryPoints(Range.START_TO_START, caretRange) > 0) {
    857                         tag.appendChild(range.extractContents());
    858                         range.insertNode(tag);
    859                     }
    860                     else if (range.commonAncestorContainer == caretRange.commonAncestorContainer) {
    861                         var dif = pos.offset - range.startOffset;
    862                         if (range.startContainer === pos.node)
    863                             dif = pos.offset - range.startOffset;
    864                         else {
    865                             for (i = textNodes.indexOf(range.startContainer) ; i < textNodes.length && textNodes[i] !== pos.node; i++) {
    866                                 dif += textNodes[i].textContent.length;
    867                             }
    868                         }
    869                         tag.appendChild(range.extractContents());
    870                         range.insertNode(tag);
    871                         var textnode = null, lastnode = null;
    872                         traverseDom(tag, function (node) {
    873                             if (!textnode && node.nodeType === 3) {
    874                                 if (node.textContent.length > dif)
    875                                     textnode = node;
    876                                 else
    877                                     dif -= node.textContent.length;
    878                                 lastnode = node;
    879                             }
    880                             return true;
    881                         });
    882 
    883                         setCaretPosition({
    884                             node: textnode ? textnode : lastnode,
    885                             offset: textnode ? dif : lastnode.length
    886                         });
    887                     }
    888                 }
    889                 else {
    890 
    891                     tag.appendChild(range.extractContents());
    892                     range.insertNode(tag);
    893                 }
    894                 //removeErrors(tag);
    895 
    896 
    897                 return tag;
    898             }
    899             catch (err) {
    900                 return null;
    901             }
    902         }
    903 
    904         function removeNode(node, ignorecursor) {
    905             if (!node || !node.parentNode)
    906                 return;
    907             var pos = null;
    908             if (ignorecursor !== true)
    909                 pos = getCaretPosition();
    910             while (node.firstChild) {
    911                 node.parentNode.insertBefore(node.firstChild, node);
    912             }
     886          }
     887        }
     888        else {
     889
     890          tag.appendChild(range.extractContents());
     891          range.insertNode(tag);
     892        }
     893        //removeErrors(tag);
     894
     895
     896        return tag;
     897      }
     898      catch (err) {
     899        return null;
     900      }
     901    }
     902
     903    function removeNode(node, ignorecursor) {
     904      if (!node || !node.parentNode)
     905        return;
     906      var pos = null;
     907      if (ignorecursor !== true)
     908        pos = getCaretPosition();
     909      while (node.firstChild) {
     910        node.parentNode.insertBefore(node.firstChild, node);
     911      }
     912      node.parentNode.removeChild(node);
     913      if (pos)
     914        setCaretPosition(pos);
     915    }
     916
     917    function removeErrors(element, ignorecursor) {
     918      if (!element) return;
     919
     920      var spans = [].concat.apply([], element.getElementsByTagName('span'));
     921      for (var i = 0; i < spans.length; i++) {
     922        if (isError(spans[i]))
     923          removeNode(spans[i], ignorecursor);
     924      }
     925
     926      cleanTextNodes(element);
     927    }
     928
     929
     930    function getBlockErrors(block) {
     931      if (!block || !block.element)
     932        return;
     933      var erros = [];
     934      var r1 = textContainer.ownerDocument.createRange();
     935      var r2 = textContainer.ownerDocument.createRange();
     936
     937      var tn = getBlockTextNodes(block);
     938      r1.setStartBefore(tn[0]);
     939      r1.setEndAfter(tn[tn.length - 1]);
     940
     941      var spans = [].concat.apply([], block.element.getElementsByTagName('span'));
     942      for (var i = 0; i < spans.length; i++) {
     943        if (isError(spans[i])) {
     944          r2.selectNode(spans[i]);
     945          if (doRangeIntersect(r1, r2, false))
     946            erros.push(spans[i]);
     947        }
     948      }
     949      return erros;
     950    }
     951
     952    function removeBlockErrors(block) {
     953      if (!block || !block.element)
     954        return;
     955      var erros = getBlockErrors(block);
     956      for (var i = 0; i < erros.length; i++) {
     957        removeNode(erros[i]);
     958      }
     959
     960      cleanTextNodes(block.element);
     961    }
     962
     963    function cleanTextNodes(element) {
     964      var pos = getCaretPosition();
     965
     966      var node = element.firstChild;
     967      var ftext = null;
     968      while (node != element.lastChild) {
     969        if (node && node.nodeType === 3) {
     970          if (ftext === null)
     971            ftext = node;
     972          else {
     973            ftext.nodeValue += node.nodeValue;
    913974            node.parentNode.removeChild(node);
    914             if (pos)
    915                 setCaretPosition(pos);
    916         }
    917 
    918         function removeErrors(element, ignorecursor) {
    919             if (!element) return;
    920 
    921             var spans = [].concat.apply([], element.getElementsByTagName('span'));
    922             for (var i = 0; i < spans.length; i++) {
    923                 if (isError(spans[i]))
    924                     removeNode(spans[i], ignorecursor);
    925             }
    926         }
    927 
    928 
    929         function getBlockErrors(block) {
    930             if (!block || !block.element)
    931                 return;
    932             var erros = [];
    933             var r1 = textContainer.ownerDocument.createRange();
    934             var r2 = textContainer.ownerDocument.createRange();
    935 
    936             var tn = getBlockTextNodes(block);
    937             r1.setStartBefore(tn[0]);
    938             r1.setEndAfter(tn[tn.length - 1]);
    939 
    940             var spans = [].concat.apply([], block.element.getElementsByTagName('span'));
    941             for (var i = 0; i < spans.length; i++) {
    942                 if (isError(spans[i])) {
    943                     r2.selectNode(spans[i]);
    944                     if (doRangeIntersect(r1, r2, false))
    945                         erros.push(spans[i]);
    946                 }
    947             }
    948             return erros;
    949         }
    950 
    951         function removeBlockErrors(block) {
    952             if (!block || !block.element)
    953                 return;
    954             var erros = getBlockErrors(block);
    955             for (var i = 0; i < erros.length; i++) {
    956                 removeNode(erros[i]);
    957             }
    958 
    959         }
    960 
    961         function areErrorsMarked(errors) {
    962             for (var i = 0; i < (errors && errors.length || 0) ; i++) {
    963                 if (!errors[i].tag || !errors[i].tag.parentNode)
    964                     return false;
    965             }
    966             return true;
    967         }
    968 
    969         function markErrors(block, erros) {
    970             if (!block || !block.element) return;
    971 
    972             removeBlockErrors(block);
    973 
    974             if (!erros || erros.length === 0) return;
    975 
    976             for (var i = 0; i < erros.length; i++) {
    977                 var erro = erros[i];
    978 
    979                 if (_ignoredWords.indexOf(block.text.substr(erro.spos, erro.len)) >= 0)
    980                     continue;
    981 
    982                 var tag = createTag(erro.type);
    983                 surroundNodeTextWithTag(block, erro.spos, erro.len, tag);
    984                 tag.error = erro;
    985                 erro.tag = tag;
    986             }
    987         }
    988 
    989         function getBlockHash(block) {
    990             if (!(block && block.element && block.element.parentNode))
    991                 return '';
    992 
    993             var r = textContainer.ownerDocument.createRange();
    994             r.selectNode(block.element);
    995             return getHash(r.toString().substr(block.offset, block.size));
    996         }
    997 
    998         function markBlocks(element) {
    999             if (!element)
    1000                 return [];
    1001 
    1002             var tn = getAllTextNodes(element);
    1003             if (tn.length === 0)
    1004                 return [];
    1005 
    1006             var range = textContainer.ownerDocument.createRange();
    1007             if (!element.blocks)
    1008                 element.blocks = [];
    1009 
    1010             var blocks = [];
    1011             var offset = 0;
    1012             for (var i = 0; i < tn.length; i++) {
    1013                 var n = tn[i];
    1014                 range.setStartBefore(n[0]);
    1015                 range.setEndAfter(n[n.length - 1]);
    1016                 var str = range.toString();
    1017                 var hash = getHash(str);
    1018 
    1019                 if (i < element.blocks.length && hash === element.blocks[i].hash) {
    1020                     blocks.push(element.blocks[i]);
    1021                 }
    1022                 else {
    1023                     var block = {
    1024                         element: element,
    1025                         index: i,
    1026                         offset: offset,
    1027                         size: str.length,
    1028                         hash: hash,
    1029                         text: str,
    1030                         erros: [],
    1031                         checked: 0
    1032                     };
    1033                     blocks.push(block);
    1034                 }
    1035                 offset += str.length;
    1036             }
    1037 
    1038             element.blocks = blocks;
    1039             return blocks;
    1040         }
    1041     };
     975            node = ftext;
     976          }
     977        }
     978        else {
     979          ftext = null;
     980        }
     981        node = node.nextSibling;
     982      }
     983
     984      if (pos)
     985        setCaretPosition(pos);
     986    }
     987
     988    function areErrorsMarked(errors) {
     989      for (var i = 0; i < (errors && errors.length || 0); i++) {
     990        if (!errors[i].tag || !errors[i].tag.parentNode)
     991          return false;
     992      }
     993      return true;
     994    }
     995
     996    function markErrors(block, erros) {
     997      if (!block || !block.element) return;
     998
     999      removeBlockErrors(block);
     1000
     1001      if (!erros || erros.length === 0) return;
     1002
     1003      for (var i = 0; i < erros.length; i++) {
     1004        var erro = erros[i];
     1005
     1006        if (_ignoredWords.indexOf(block.text.substr(erro.spos, erro.len)) >= 0)
     1007          continue;
     1008
     1009        var tag = createTag(erro.type);
     1010        surroundNodeTextWithTag(block, erro.spos, erro.len, tag);
     1011        tag.error = erro;
     1012        erro.tag = tag;
     1013      }
     1014    }
     1015
     1016    function getBlockHash(block) {
     1017      if (!(block && block.element && block.element.parentNode))
     1018        return '';
     1019
     1020      var r = textContainer.ownerDocument.createRange();
     1021      r.selectNode(block.element);
     1022      return getHash(r.toString().substr(block.offset, block.size));
     1023    }
     1024
     1025    function markBlocks(element) {
     1026      if (!element)
     1027        return [];
     1028
     1029      var tn = getAllTextNodes(element);
     1030      if (tn.length === 0)
     1031        return [];
     1032
     1033      var range = textContainer.ownerDocument.createRange();
     1034      if (!element.blocks)
     1035        element.blocks = [];
     1036
     1037      var blocks = [];
     1038      var offset = 0;
     1039      for (var i = 0; i < tn.length; i++) {
     1040        var n = tn[i];
     1041        range.setStartBefore(n[0]);
     1042        range.setEndAfter(n[n.length - 1]);
     1043        var str = range.toString();
     1044        var hash = getHash(str);
     1045
     1046        if (i < element.blocks.length && hash === element.blocks[i].hash) {
     1047          blocks.push(element.blocks[i]);
     1048        }
     1049        else {
     1050          var block = {
     1051            element: element,
     1052            index: i,
     1053            offset: offset,
     1054            size: str.length,
     1055            hash: hash,
     1056            text: str,
     1057            erros: [],
     1058            checked: 0
     1059          };
     1060          blocks.push(block);
     1061        }
     1062        offset += str.length;
     1063      }
     1064
     1065      element.blocks = blocks;
     1066      return blocks;
     1067    }
     1068  };
    10421069})(window);
  • flip/trunk/flip.php

    r1762083 r1795661  
    44 * Plugin URI: https://www.flip.pt/Produtos/Plugin-do-FLiP-para-WordPress
    55 * Description: Portuguese spell checker, grammar checker and style checker, with or without the Spelling Reform of 1990.
    6  * Version: 1.5.8
     6 * Version: 1.5.9
    77 * Author: Priberam
    88 * Author URI: http://www.priberam.pt
  • flip/trunk/readme.txt

    r1767893 r1795661  
    44Requires at least: 4.3
    55Tested up to: 4.9
    6 Stable tag: 1.5.8
     6Stable tag: 1.5.9
    77License: GPLv2 or later
    88License URI: https://www.gnu.org/licenses/gpl-2.0.html
     
    3636
    3737== Changelog ==
     38= 1.5.9 =
     39* Fix collapsing spaces between errors.
     40
    3841= 1.5.8 =
    3942* Minor fixes
     
    6568
    6669== Upgrade Notice ==
     70= 1.5.9 =
     71* Fix collapsing spaces between errors.
     72
    6773= 1.5.8 =
    6874* Minor fixes
Note: See TracChangeset for help on using the changeset viewer.