rtl
10 years ago
tinymce
7 years ago
atd-autoproofread.js
10 years ago
atd-nonvis-editor-plugin.js
9 years ago
atd-rtl.css
9 years ago
atd-rtl.min.css
9 years ago
atd.core.js
9 years ago
atd.css
9 years ago
atd.min.css
9 years ago
button.gif
15 years ago
config-options.php
7 years ago
config-unignore.php
7 years ago
jquery.atd.js
7 years ago
proxy.php
7 years ago
atd.core.js
641 lines
| 1 | /* |
| 2 | * atd.core.js - A building block to create a front-end for AtD |
| 3 | * Author : Raphael Mudge, Automattic |
| 4 | * License : LGPL |
| 5 | * Project : http://www.afterthedeadline.com/developers.slp |
| 6 | * Contact : raffi@automattic.com |
| 7 | */ |
| 8 | |
| 9 | /* jshint sub: true, devel: true, onevar: false, smarttabs: true, loopfunc: true */ |
| 10 | /* exported EXPORTED_SYMBOLS, atd_sprintf */ |
| 11 | |
| 12 | /* EXPORTED_SYMBOLS is set so this file can be a JavaScript Module */ |
| 13 | var EXPORTED_SYMBOLS = ['AtDCore']; |
| 14 | |
| 15 | function AtDCore() { |
| 16 | /* these are the categories of errors AtD should ignore */ |
| 17 | this.ignore_types = ['Bias Language', 'Cliches', 'Complex Expression', 'Diacritical Marks', 'Double Negatives', 'Hidden Verbs', 'Jargon Language', 'Passive voice', 'Phrases to Avoid', 'Redundant Expression']; |
| 18 | |
| 19 | /* these are the phrases AtD should ignore */ |
| 20 | this.ignore_strings = {}; |
| 21 | |
| 22 | /* Localized strings */ |
| 23 | // Back-compat, not used |
| 24 | this.i18n = {}; |
| 25 | } |
| 26 | |
| 27 | /* |
| 28 | * Internationalization Functions |
| 29 | */ |
| 30 | |
| 31 | AtDCore.prototype.getLang = function( key, defaultk ) { |
| 32 | return ( window.AtD_l10n_r0ar && window.AtD_l10n_r0ar[key] ) || defaultk; |
| 33 | }; |
| 34 | |
| 35 | AtDCore.prototype.addI18n = function( obj ) { |
| 36 | // Back-compat |
| 37 | window.AtD_l10n_r0ar = obj; |
| 38 | }; |
| 39 | |
| 40 | /* |
| 41 | * Setters |
| 42 | */ |
| 43 | |
| 44 | AtDCore.prototype.setIgnoreStrings = function(string) { |
| 45 | var parent = this; |
| 46 | |
| 47 | this.map(string.split(/,\s*/g), function(string) { |
| 48 | parent.ignore_strings[string] = 1; |
| 49 | }); |
| 50 | }; |
| 51 | |
| 52 | AtDCore.prototype.showTypes = function(string) { |
| 53 | var show_types = string.split(/,\s*/g); |
| 54 | var types = {}; |
| 55 | |
| 56 | /* set some default types that we want to make optional */ |
| 57 | |
| 58 | /* grammar checker options */ |
| 59 | types['Double Negatives'] = 1; |
| 60 | types['Hidden Verbs'] = 1; |
| 61 | types['Passive voice'] = 1; |
| 62 | types['Bias Language'] = 1; |
| 63 | |
| 64 | /* style checker options */ |
| 65 | types['Cliches'] = 1; |
| 66 | types['Complex Expression'] = 1; |
| 67 | types['Diacritical Marks'] = 1; |
| 68 | types['Jargon Language'] = 1; |
| 69 | types['Phrases to Avoid'] = 1; |
| 70 | types['Redundant Expression'] = 1; |
| 71 | |
| 72 | var ignore_types = []; |
| 73 | |
| 74 | this.map(show_types, function(string) { |
| 75 | types[string] = undefined; |
| 76 | }); |
| 77 | |
| 78 | this.map(this.ignore_types, function(string) { |
| 79 | if (types[string] !== undefined) { |
| 80 | ignore_types.push(string); |
| 81 | } |
| 82 | }); |
| 83 | |
| 84 | this.ignore_types = ignore_types; |
| 85 | }; |
| 86 | |
| 87 | /* |
| 88 | * Error Parsing Code |
| 89 | */ |
| 90 | |
| 91 | AtDCore.prototype.makeError = function(error_s, tokens, type, seps/*, pre*/) { |
| 92 | var struct = {}; |
| 93 | struct.type = type; |
| 94 | struct.string = error_s; |
| 95 | struct.tokens = tokens; |
| 96 | |
| 97 | if (new RegExp('\\b' + error_s + '\\b').test(error_s)) { |
| 98 | struct.regexp = new RegExp('(?!'+error_s+'<)\\b' + error_s.replace(/\s+/g, seps) + '\\b'); |
| 99 | } |
| 100 | else if (new RegExp(error_s + '\\b').test(error_s)) { |
| 101 | struct.regexp = new RegExp('(?!'+error_s+'<)' + error_s.replace(/\s+/g, seps) + '\\b'); |
| 102 | } |
| 103 | else if (new RegExp('\\b' + error_s).test(error_s)) { |
| 104 | struct.regexp = new RegExp('(?!'+error_s+'<)\\b' + error_s.replace(/\s+/g, seps)); |
| 105 | } |
| 106 | else { |
| 107 | struct.regexp = new RegExp('(?!'+error_s+'<)' + error_s.replace(/\s+/g, seps)); |
| 108 | } |
| 109 | |
| 110 | struct.used = false; /* flag whether we've used this rule or not */ |
| 111 | |
| 112 | return struct; |
| 113 | }; |
| 114 | |
| 115 | AtDCore.prototype.addToErrorStructure = function(errors, list, type, seps) { |
| 116 | var parent = this; |
| 117 | |
| 118 | this.map(list, function(error) { |
| 119 | var tokens = error['word'].split(/\s+/); |
| 120 | var pre = error['pre']; |
| 121 | var first = tokens[0]; |
| 122 | |
| 123 | if (errors['__' + first] === undefined) { |
| 124 | errors['__' + first] = {}; |
| 125 | errors['__' + first].pretoks = {}; |
| 126 | errors['__' + first].defaults = []; |
| 127 | } |
| 128 | |
| 129 | if (pre === '') { |
| 130 | errors['__' + first].defaults.push(parent.makeError(error['word'], tokens, type, seps, pre)); |
| 131 | } else { |
| 132 | if (errors['__' + first].pretoks['__' + pre] === undefined) { |
| 133 | errors['__' + first].pretoks['__' + pre] = []; |
| 134 | } |
| 135 | |
| 136 | errors['__' + first].pretoks['__' + pre].push(parent.makeError(error['word'], tokens, type, seps, pre)); |
| 137 | } |
| 138 | }); |
| 139 | }; |
| 140 | |
| 141 | AtDCore.prototype.buildErrorStructure = function(spellingList, enrichmentList, grammarList) { |
| 142 | var seps = this._getSeparators(); |
| 143 | var errors = {}; |
| 144 | |
| 145 | this.addToErrorStructure(errors, spellingList, 'hiddenSpellError', seps); |
| 146 | this.addToErrorStructure(errors, grammarList, 'hiddenGrammarError', seps); |
| 147 | this.addToErrorStructure(errors, enrichmentList, 'hiddenSuggestion', seps); |
| 148 | return errors; |
| 149 | }; |
| 150 | |
| 151 | AtDCore.prototype._getSeparators = function() { |
| 152 | var re = '', i; |
| 153 | var str = '"s!#$%&()*+,./:;<=>?@[\\]^_{|}'; |
| 154 | |
| 155 | // Build word separator regexp |
| 156 | for (i=0; i<str.length; i++) { |
| 157 | re += '\\' + str.charAt(i); |
| 158 | } |
| 159 | |
| 160 | return '(?:(?:[\xa0' + re + '])|(?:\\-\\-))+'; |
| 161 | }; |
| 162 | |
| 163 | AtDCore.prototype.processXML = function(responseXML) { |
| 164 | |
| 165 | /* types of errors to ignore */ |
| 166 | var types = {}; |
| 167 | |
| 168 | this.map(this.ignore_types, function(type) { |
| 169 | types[type] = 1; |
| 170 | }); |
| 171 | |
| 172 | /* save suggestions in the editor object */ |
| 173 | this.suggestions = []; |
| 174 | |
| 175 | /* process through the errors */ |
| 176 | var errors = responseXML.getElementsByTagName('error'); |
| 177 | |
| 178 | /* words to mark */ |
| 179 | var grammarErrors = []; |
| 180 | var spellingErrors = []; |
| 181 | var enrichment = []; |
| 182 | |
| 183 | for (var i = 0; i < errors.length; i++) { |
| 184 | if (errors[i].getElementsByTagName('string').item(0).firstChild !== null) { |
| 185 | var errorString = errors[i].getElementsByTagName('string').item(0).firstChild.data; |
| 186 | var errorType = errors[i].getElementsByTagName('type').item(0).firstChild.data; |
| 187 | var errorDescription = errors[i].getElementsByTagName('description').item(0).firstChild.data; |
| 188 | |
| 189 | var errorContext; |
| 190 | |
| 191 | if (errors[i].getElementsByTagName('precontext').item(0).firstChild !== null) { |
| 192 | errorContext = errors[i].getElementsByTagName('precontext').item(0).firstChild.data; |
| 193 | } else { |
| 194 | errorContext = ''; |
| 195 | } |
| 196 | |
| 197 | /* create a hashtable with information about the error in the editor object, we will use this later |
| 198 | to populate a popup menu with information and suggestions about the error */ |
| 199 | |
| 200 | if (this.ignore_strings[errorString] === undefined) { |
| 201 | var suggestion = {}; |
| 202 | suggestion['description'] = errorDescription; |
| 203 | suggestion['suggestions'] = []; |
| 204 | |
| 205 | /* used to find suggestions when a highlighted error is clicked on */ |
| 206 | suggestion['matcher'] = new RegExp('^' + errorString.replace(/\s+/, this._getSeparators()) + '$'); |
| 207 | |
| 208 | suggestion['context'] = errorContext; |
| 209 | suggestion['string'] = errorString; |
| 210 | suggestion['type'] = errorType; |
| 211 | |
| 212 | this.suggestions.push(suggestion); |
| 213 | |
| 214 | if (errors[i].getElementsByTagName('suggestions').item(0) !== null) { |
| 215 | var suggestions = errors[i].getElementsByTagName('suggestions').item(0).getElementsByTagName('option'); |
| 216 | for (var j = 0; j < suggestions.length; j++) { |
| 217 | suggestion['suggestions'].push(suggestions[j].firstChild.data); |
| 218 | } |
| 219 | } |
| 220 | |
| 221 | /* setup the more info url */ |
| 222 | if (errors[i].getElementsByTagName('url').item(0) !== null) { |
| 223 | var errorUrl = errors[i].getElementsByTagName('url').item(0).firstChild.data; |
| 224 | suggestion['moreinfo'] = errorUrl + '&theme=tinymce'; |
| 225 | } |
| 226 | |
| 227 | if (types[errorDescription] === undefined) { |
| 228 | if (errorType === 'suggestion') { |
| 229 | enrichment.push({ word: errorString, pre: errorContext }); |
| 230 | } |
| 231 | |
| 232 | if (errorType === 'grammar') { |
| 233 | grammarErrors.push({ word: errorString, pre: errorContext }); |
| 234 | } |
| 235 | } |
| 236 | |
| 237 | if (errorType === 'spelling' || errorDescription === 'Homophone') { |
| 238 | spellingErrors.push({ word: errorString, pre: errorContext }); |
| 239 | } |
| 240 | |
| 241 | if (errorDescription === 'Cliches') { |
| 242 | suggestion['description'] = 'Clichés'; /* done here for backwards compatability with current user settings */ |
| 243 | } |
| 244 | |
| 245 | if (errorDescription === 'Spelling') { |
| 246 | suggestion['description'] = this.getLang('menu_title_spelling', 'Spelling'); |
| 247 | } |
| 248 | |
| 249 | if (errorDescription === 'Repeated Word') { |
| 250 | suggestion['description'] = this.getLang('menu_title_repeated_word', 'Repeated Word'); |
| 251 | } |
| 252 | |
| 253 | if (errorDescription === 'Did you mean...') { |
| 254 | suggestion['description'] = this.getLang('menu_title_confused_word', 'Did you mean...'); |
| 255 | } |
| 256 | } // end if ignore[errorString] == undefined |
| 257 | } // end if |
| 258 | } // end for loop |
| 259 | |
| 260 | var errorStruct; |
| 261 | var ecount = spellingErrors.length + grammarErrors.length + enrichment.length; |
| 262 | |
| 263 | if (ecount > 0) { |
| 264 | errorStruct = this.buildErrorStructure(spellingErrors, enrichment, grammarErrors); |
| 265 | } else { |
| 266 | errorStruct = undefined; |
| 267 | } |
| 268 | |
| 269 | /* save some state in this object, for retrieving suggestions later */ |
| 270 | return { errors: errorStruct, count: ecount, suggestions: this.suggestions }; |
| 271 | }; |
| 272 | |
| 273 | AtDCore.prototype.findSuggestion = function(element) { |
| 274 | var text = element.innerHTML; |
| 275 | var context = ( this.getAttrib(element, 'pre') + '' ).replace(/[\\,!\\?\\."\s]/g, ''); |
| 276 | if (this.getAttrib(element, 'pre') === undefined) { |
| 277 | alert(element.innerHTML); |
| 278 | } |
| 279 | |
| 280 | var errorDescription; |
| 281 | var len = this.suggestions.length; |
| 282 | |
| 283 | for (var i = 0; i < len; i++) { |
| 284 | if ((context === '' || context === this.suggestions[i]['context']) && this.suggestions[i]['matcher'].test(text)) { |
| 285 | errorDescription = this.suggestions[i]; |
| 286 | break; |
| 287 | } |
| 288 | } |
| 289 | return errorDescription; |
| 290 | }; |
| 291 | |
| 292 | /* |
| 293 | * TokenIterator class |
| 294 | */ |
| 295 | |
| 296 | function TokenIterator(tokens) { |
| 297 | this.tokens = tokens; |
| 298 | this.index = 0; |
| 299 | this.count = 0; |
| 300 | this.last = 0; |
| 301 | } |
| 302 | |
| 303 | TokenIterator.prototype.next = function() { |
| 304 | var current = this.tokens[this.index]; |
| 305 | this.count = this.last; |
| 306 | this.last += current.length + 1; |
| 307 | this.index++; |
| 308 | |
| 309 | /* strip single quotes from token, AtD does this when presenting errors */ |
| 310 | if (current !== '') { |
| 311 | if (current[0] === '\'') { |
| 312 | current = current.substring(1, current.length); |
| 313 | } |
| 314 | |
| 315 | if (current[current.length - 1] === '\'') { |
| 316 | current = current.substring(0, current.length - 1); |
| 317 | } |
| 318 | } |
| 319 | |
| 320 | return current; |
| 321 | }; |
| 322 | |
| 323 | TokenIterator.prototype.hasNext = function() { |
| 324 | return this.index < this.tokens.length; |
| 325 | }; |
| 326 | |
| 327 | TokenIterator.prototype.hasNextN = function(n) { |
| 328 | return (this.index + n) < this.tokens.length; |
| 329 | }; |
| 330 | |
| 331 | TokenIterator.prototype.skip = function(m, n) { |
| 332 | this.index += m; |
| 333 | this.last += n; |
| 334 | |
| 335 | if (this.index < this.tokens.length) { |
| 336 | this.count = this.last - this.tokens[this.index].length; |
| 337 | } |
| 338 | }; |
| 339 | |
| 340 | TokenIterator.prototype.getCount = function() { |
| 341 | return this.count; |
| 342 | }; |
| 343 | |
| 344 | TokenIterator.prototype.peek = function(n) { |
| 345 | var peepers = []; |
| 346 | var end = this.index + n; |
| 347 | for (var x = this.index; x < end; x++) { |
| 348 | peepers.push(this.tokens[x]); |
| 349 | } |
| 350 | return peepers; |
| 351 | }; |
| 352 | |
| 353 | /* |
| 354 | * code to manage highlighting of errors |
| 355 | */ |
| 356 | AtDCore.prototype.markMyWords = function(container_nodes, errors) { |
| 357 | var seps = new RegExp(this._getSeparators()), |
| 358 | nl = [], |
| 359 | ecount = 0, /* track number of highlighted errors */ |
| 360 | parent = this, |
| 361 | bogus = this._isTinyMCE ? ' data-mce-bogus="1"' : '', |
| 362 | emptySpan = '<span class="mceItemHidden"' + bogus + '> </span>', |
| 363 | textOnlyMode; |
| 364 | |
| 365 | /** |
| 366 | * Split a text node into an ordered list of siblings: |
| 367 | * - text node to the left of the match |
| 368 | * - the element replacing the match |
| 369 | * - text node to the right of the match |
| 370 | * |
| 371 | * We have to leave the text to the left and right of the match alone |
| 372 | * in order to prevent XSS |
| 373 | * |
| 374 | * @return array |
| 375 | */ |
| 376 | function splitTextNode( textnode, regexp, replacement ) { |
| 377 | var text = textnode.nodeValue, |
| 378 | index = text.search( regexp ), |
| 379 | match = text.match( regexp ), |
| 380 | captured = [], |
| 381 | cursor; |
| 382 | |
| 383 | if ( index < 0 || ! match.length ) { |
| 384 | return [ textnode ]; |
| 385 | } |
| 386 | |
| 387 | if ( index > 0 ) { |
| 388 | // capture left text node |
| 389 | captured.push( document.createTextNode( text.substr( 0, index ) ) ); |
| 390 | } |
| 391 | |
| 392 | // capture the replacement of the matched string |
| 393 | captured.push( parent.create( match[0].replace( regexp, replacement ) ) ); |
| 394 | |
| 395 | cursor = index + match[0].length; |
| 396 | |
| 397 | if ( cursor < text.length ) { |
| 398 | // capture right text node |
| 399 | captured.push( document.createTextNode( text.substr( cursor ) ) ); |
| 400 | } |
| 401 | |
| 402 | return captured; |
| 403 | } |
| 404 | |
| 405 | function _isInPre( node ) { |
| 406 | if ( node ) { |
| 407 | while ( node.parentNode ) { |
| 408 | if ( node.nodeName === 'PRE' ) { |
| 409 | return true; |
| 410 | } |
| 411 | node = node.parentNode; |
| 412 | } |
| 413 | } |
| 414 | |
| 415 | return false; |
| 416 | } |
| 417 | |
| 418 | /* Collect all text nodes */ |
| 419 | /* Our goal--ignore nodes that are already wrapped */ |
| 420 | |
| 421 | this._walk( container_nodes, function( n ) { |
| 422 | if ( n.nodeType === 3 && ! parent.isMarkedNode( n ) && ! _isInPre( n ) ) { |
| 423 | nl.push( n ); |
| 424 | } |
| 425 | }); |
| 426 | |
| 427 | /* walk through the relevant nodes */ |
| 428 | |
| 429 | var iterator; |
| 430 | |
| 431 | this.map( nl, function( n ) { |
| 432 | var v; |
| 433 | |
| 434 | if ( n.nodeType === 3 ) { |
| 435 | v = n.nodeValue; /* we don't want to mangle the HTML so use the actual encoded string */ |
| 436 | var tokens = n.nodeValue.split( seps ); /* split on the unencoded string so we get access to quotes as " */ |
| 437 | var previous = ''; |
| 438 | |
| 439 | var doReplaces = []; |
| 440 | |
| 441 | iterator = new TokenIterator(tokens); |
| 442 | |
| 443 | while ( iterator.hasNext() ) { |
| 444 | var token = iterator.next(); |
| 445 | var current = errors['__' + token]; |
| 446 | |
| 447 | var defaults; |
| 448 | |
| 449 | if ( current !== undefined && current.pretoks !== undefined ) { |
| 450 | defaults = current.defaults; |
| 451 | current = current.pretoks['__' + previous]; |
| 452 | |
| 453 | var done = false; |
| 454 | var prev, curr; |
| 455 | |
| 456 | prev = v.substr(0, iterator.getCount()); |
| 457 | curr = v.substr(prev.length, v.length); |
| 458 | |
| 459 | var checkErrors = function( error ) { |
| 460 | if ( error !== undefined && ! error.used && foundStrings[ '__' + error.string ] === undefined && error.regexp.test( curr ) ) { |
| 461 | foundStrings[ '__' + error.string ] = 1; |
| 462 | doReplaces.push([ error.regexp, '<span class="'+error.type+'" pre="'+previous+'"' + bogus + '>$&</span>' ]); |
| 463 | |
| 464 | error.used = true; |
| 465 | done = true; |
| 466 | } |
| 467 | }; // jshint ignore:line |
| 468 | |
| 469 | var foundStrings = {}; |
| 470 | |
| 471 | if (current !== undefined) { |
| 472 | previous = previous + ' '; |
| 473 | parent.map(current, checkErrors); |
| 474 | } |
| 475 | |
| 476 | if (!done) { |
| 477 | previous = ''; |
| 478 | parent.map(defaults, checkErrors); |
| 479 | } |
| 480 | } |
| 481 | |
| 482 | previous = token; |
| 483 | } // end while |
| 484 | |
| 485 | /* do the actual replacements on this span */ |
| 486 | if ( doReplaces.length > 0 ) { |
| 487 | var newNode = n; |
| 488 | |
| 489 | for ( var x = 0; x < doReplaces.length; x++ ) { |
| 490 | var regexp = doReplaces[x][0], result = doReplaces[x][1]; |
| 491 | |
| 492 | /* it's assumed that this function is only being called on text nodes (nodeType == 3), the iterating is necessary |
| 493 | because eventually the whole thing gets wrapped in an mceItemHidden span and from there it's necessary to |
| 494 | handle each node individually. */ |
| 495 | var bringTheHurt = function( node ) { |
| 496 | var span, splitNodes; |
| 497 | |
| 498 | if ( node.nodeType === 3 ) { |
| 499 | ecount++; |
| 500 | |
| 501 | /* sometimes IE likes to ignore the space between two spans, solution is to insert a placeholder span with |
| 502 | a non-breaking space. The markup removal code substitutes this span for a space later */ |
| 503 | if ( parent.isIE() && node.nodeValue.length > 0 && node.nodeValue.substr(0, 1) === ' ' ) { |
| 504 | return parent.create( emptySpan + node.nodeValue.substr( 1, node.nodeValue.length - 1 ).replace( regexp, result ), false ); |
| 505 | } else { |
| 506 | if ( textOnlyMode ) { |
| 507 | return parent.create( node.nodeValue.replace( regexp, result ), false ); |
| 508 | } |
| 509 | |
| 510 | span = parent.create( '<span />' ); |
| 511 | if ( typeof textOnlyMode === 'undefined' ) { |
| 512 | // cache this to avoid adding / removing nodes unnecessarily |
| 513 | textOnlyMode = typeof span.appendChild !== 'function'; |
| 514 | if ( textOnlyMode ) { |
| 515 | parent.remove( span ); |
| 516 | return parent.create( node.nodeValue.replace( regexp, result ), false ); |
| 517 | } |
| 518 | } |
| 519 | |
| 520 | // "Visual" mode |
| 521 | splitNodes = splitTextNode( node, regexp, result ); |
| 522 | for ( var i = 0; i < splitNodes.length; i++ ) { |
| 523 | span.appendChild( splitNodes[i] ); |
| 524 | } |
| 525 | |
| 526 | node = span; |
| 527 | return node; |
| 528 | } |
| 529 | } |
| 530 | else { |
| 531 | var contents = parent.contents(node); |
| 532 | |
| 533 | for ( var y = 0; y < contents.length; y++ ) { |
| 534 | if ( contents[y].nodeType === 3 && regexp.test( contents[y].nodeValue ) ) { |
| 535 | var nnode; |
| 536 | |
| 537 | if ( parent.isIE() && contents[y].nodeValue.length > 0 && contents[y].nodeValue.substr(0, 1) === ' ') { |
| 538 | nnode = parent.create( emptySpan + contents[y].nodeValue.substr( 1, contents[y].nodeValue.length - 1 ).replace( regexp, result ), true ); |
| 539 | } else { |
| 540 | nnode = parent.create( contents[y].nodeValue.replace( regexp, result ), true ); |
| 541 | } |
| 542 | |
| 543 | parent.replaceWith( contents[y], nnode ); |
| 544 | parent.removeParent( nnode ); |
| 545 | |
| 546 | ecount++; |
| 547 | |
| 548 | return node; /* we did a replacement so we can call it quits, errors only get used once */ |
| 549 | } |
| 550 | } |
| 551 | |
| 552 | return node; |
| 553 | } |
| 554 | }; // jshint ignore:line |
| 555 | |
| 556 | newNode = bringTheHurt(newNode); |
| 557 | } |
| 558 | |
| 559 | parent.replaceWith(n, newNode); |
| 560 | } |
| 561 | } |
| 562 | }); |
| 563 | |
| 564 | return ecount; |
| 565 | }; |
| 566 | |
| 567 | AtDCore.prototype._walk = function(elements, f) { |
| 568 | var i; |
| 569 | for (i = 0; i < elements.length; i++) { |
| 570 | f.call(f, elements[i]); |
| 571 | this._walk(this.contents(elements[i]), f); |
| 572 | } |
| 573 | }; |
| 574 | |
| 575 | AtDCore.prototype.removeWords = function(node, w) { |
| 576 | var count = 0; |
| 577 | var parent = this; |
| 578 | |
| 579 | this.map(this.findSpans(node).reverse(), function(n) { |
| 580 | if (n && (parent.isMarkedNode(n) || parent.hasClass(n, 'mceItemHidden') || parent.isEmptySpan(n)) ) { |
| 581 | if (n.innerHTML === ' ') { |
| 582 | var nnode = document.createTextNode(' '); /* hax0r */ |
| 583 | parent.replaceWith(n, nnode); |
| 584 | } else if (!w || n.innerHTML === w) { |
| 585 | parent.removeParent(n); |
| 586 | count++; |
| 587 | } |
| 588 | } |
| 589 | }); |
| 590 | |
| 591 | return count; |
| 592 | }; |
| 593 | |
| 594 | AtDCore.prototype.isEmptySpan = function(node) { |
| 595 | return (this.getAttrib(node, 'class') === '' && this.getAttrib(node, 'style') === '' && this.getAttrib(node, 'id') === '' && !this.hasClass(node, 'Apple-style-span') && this.getAttrib(node, 'mce_name') === ''); |
| 596 | }; |
| 597 | |
| 598 | AtDCore.prototype.isMarkedNode = function(node) { |
| 599 | return (this.hasClass(node, 'hiddenGrammarError') || this.hasClass(node, 'hiddenSpellError') || this.hasClass(node, 'hiddenSuggestion')); |
| 600 | }; |
| 601 | |
| 602 | /* |
| 603 | * Context Menu Helpers |
| 604 | */ |
| 605 | AtDCore.prototype.applySuggestion = function(element, suggestion) { |
| 606 | if (suggestion === '(omit)') { |
| 607 | this.remove(element); |
| 608 | } |
| 609 | else { |
| 610 | var node = this.create(suggestion); |
| 611 | this.replaceWith(element, node); |
| 612 | this.removeParent(node); |
| 613 | } |
| 614 | }; |
| 615 | |
| 616 | /* |
| 617 | * Check for an error |
| 618 | */ |
| 619 | AtDCore.prototype.hasErrorMessage = function(xmlr) { |
| 620 | return (xmlr !== undefined && xmlr.getElementsByTagName('message').item(0) !== null); |
| 621 | }; |
| 622 | |
| 623 | AtDCore.prototype.getErrorMessage = function(xmlr) { |
| 624 | return xmlr.getElementsByTagName('message').item(0); |
| 625 | }; |
| 626 | |
| 627 | /* this should always be an error, alas... not practical */ |
| 628 | AtDCore.prototype.isIE = function() { |
| 629 | return navigator.appName === 'Microsoft Internet Explorer'; |
| 630 | }; |
| 631 | |
| 632 | // TODO: this doesn't seem used anywhere in AtD, moved here from install_atd_l10n.js for eventual back-compat |
| 633 | /* a quick poor man's sprintf */ |
| 634 | function atd_sprintf(format, values) { |
| 635 | var result = format; |
| 636 | for (var x = 0; x < values.length; x++) { |
| 637 | result = result.replace(new RegExp('%' + (x + 1) + '\\$', 'g'), values[x]); |
| 638 | } |
| 639 | return result; |
| 640 | } |
| 641 |