r92181 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r92180‎ | r92181 | r92182 >
Date:19:22, 14 July 2011
Author:hashar
Status:ok (Comments)
Tags:
Comment:
svn:eol-style native
Modified paths:
  • /trunk/parsers/wikidom/README (modified) (history)
  • /trunk/parsers/wikidom/demos/es/index.html (modified) (history)
  • /trunk/parsers/wikidom/demos/renderers/index.html (modified) (history)
  • /trunk/parsers/wikidom/demos/surface/index.html (modified) (history)
  • /trunk/parsers/wikidom/lib/es/es.Content.js (modified) (history)
  • /trunk/parsers/wikidom/lib/es/es.Cursor.js (modified) (history)
  • /trunk/parsers/wikidom/lib/es/es.Surface.js (modified) (history)
  • /trunk/parsers/wikidom/tests/annotations/index.html (modified) (history)
  • /trunk/parsers/wikidom/tests/wikidom/index.html (modified) (history)

Diff [purge]

Property changes on: trunk/parsers/wikidom/tests/wikidom/index.html
___________________________________________________________________
Added: svn:eol-style
11 + native
Property changes on: trunk/parsers/wikidom/tests/annotations/index.html
___________________________________________________________________
Added: svn:eol-style
22 + native
Property changes on: trunk/parsers/wikidom/lib/es/es.Surface.js
___________________________________________________________________
Added: eol-style
33 + native
Index: trunk/parsers/wikidom/lib/es/es.Content.js
@@ -1,501 +1,501 @@
2 -/* Classes */
3 -
4 -/**
5 - * Content objects are wrappers around arrays of plain or annotated characters. Data in this form
6 - * is ultimately equivalent to but more efficient to work with than WikiDom line objects (plain text
7 - * paired with offset annotation), especially when performing substring operations. Content can be
8 - * derived from or converted to one or more WikiDom line objects.
9 - *
10 - * @param content {Array} List of plain or annotated characters
11 - * @returns {Content}
12 - */
13 -function Content( content ) {
14 - this.data = content || [];
15 -};
16 -
17 -/* Static Members */
18 -
19 -/**
20 - * List of annotation rendering implementations.
21 - *
22 - * Each supported annotation renderer must have an open and close property, each either a string or
23 - * a function which accepts a data argument.
24 - */
25 -Content.annotationRenderers = {
26 - 'template': {
27 - 'open': function( data ) {
28 - return '<span class="editSurface-format-object">' + data.html;
29 - },
30 - 'close': '</span>',
31 - },
32 - 'bold': {
33 - 'open': '<span class="editSurface-format-bold">',
34 - 'close': '</span>',
35 - },
36 - 'italic': {
37 - 'open': '<span class="editSurface-format-italic">',
38 - 'close': '</span>',
39 - },
40 - 'size': {
41 - 'open': function( data ) {
42 - return '<span class="editSurface-format-' + data.type + '">';
43 - },
44 - 'close': '</span>',
45 - },
46 - 'script': {
47 - 'open': function( data ) {
48 - return '<span class="editSurface-format-' + data.type + '">';
49 - },
50 - 'close': '</span>',
51 - },
52 - 'link': {
53 - 'open': function( data ) {
54 - return '<span class="editSurface-format-link" data-href="' + data.href + '">';
55 - },
56 - 'close': '</span>'
57 - }
58 -};
59 -
60 -Content.htmlCharacters = {
61 - '&': '&amp;',
62 - '<': '&lt;',
63 - '>': '&gt;',
64 - '\'': '&#039;',
65 - '"': '&quot;',
66 - ' ': '&nbsp;',
67 - '\n': '<span class="editSurface-whitespace">&#182;</span>',
68 - '\t': '<span class="editSurface-whitespace">&#8702;</span>'
69 -};
70 -
71 -/* Static Methods */
72 -
73 -/**
74 - * Recursively compares string and number property between two objects.
75 - *
76 - * A false result may be caused by property inequality or by properties in one object missing from
77 - * the other. An asymmetrical test may also be performed, which checks only that properties in the
78 - * first object are present in the second object, but not the inverse.
79 - *
80 - * @param a {Object} First object to compare
81 - * @param b {Object} Second object to compare
82 - * @param asymmetrical {Boolean} Whether to check only that b contains values from a
83 - * @return {Boolean} If the objects contain the same values as each other
84 - */
85 -Content.compareObjects = function( a, b, asymmetrical ) {
86 - var aValue, bValue, aType, bType;
87 - for ( var k in a ) {
88 - aValue = a[k];
89 - bValue = b[k];
90 - aType = typeof aValue;
91 - bType = typeof bValue;
92 - if ( aType !== bType
93 - || ( ( aType === 'string' || aType === 'number' ) && aValue !== bValue )
94 - || ( $.isPlainObject( aValue ) && !Content.compareObjects( aValue, bValue ) ) ) {
95 - return false
96 - }
97 - }
98 - // If the check is not asymmetrical, recursing with the arguments swapped will verify our result
99 - return asymmetrical ? true : Content.compareObjects( b, a, true );
100 -};
101 -
102 -/**
103 - * Gets a recursive copy of an object's string, number and plain-object property.
104 - *
105 - * @param source {Object} Object to copy
106 - * @return {Object} Copy of source object
107 - */
108 -Content.copyObject = function( source ) {
109 - var destination = {};
110 - for ( var key in source ) {
111 - sourceValue = source[key];
112 - sourceType = typeof sourceValue;
113 - if ( sourceType === 'string' || sourceType === 'number' ) {
114 - destination[key] = sourceValue;
115 - } else if ( $.isPlainObject( sourceValue ) ) {
116 - destination[key] = Content.copyObject( sourceValue );
117 - }
118 - }
119 - return destination;
120 -};
121 -
122 -/**
123 - * Gets content data from a WikiDom line object, which uses a series of offset-based annotations to
124 - * supplement plain text.
125 - *
126 - * @param line {Object} WikiDom compatible line object, containing text and optionally annotations
127 - * properties, the latter of which being an array of annotation objects including range information
128 - * @return {Array} List of plain or annotated characters
129 - */
130 -Content.convertLine = function( line ) {
131 - // Convert string to array of characters
132 - var data = line.text.split('');
133 - for ( var i in line.annotations ) {
134 - var src = line.annotations[i];
135 - // Build simplified annotation object
136 - var dst = { 'type': src.type };
137 - if ( 'data' in src ) {
138 - dst.data = Content.copyObject( src.data );
139 - }
140 - // Apply annotation to range
141 - for ( var k = src.range.start; k < src.range.end; k++ ) {
142 - // Auto-convert to array
143 - typeof data[k] === 'string' && ( data[k] = [data[k]] );
144 - // Append
145 - data[k].push( dst );
146 - }
147 - }
148 - return data;
149 -};
150 -
151 -/**
152 - * Creates a new Content object from a WikiDom line object.
153 - *
154 - * @param line {Object} WikiDom compatible line object - @see Content.convertLine
155 - * @return {Content} New content object containing data derived from the WikiDom line
156 - */
157 -Content.newFromLine = function( line ) {
158 - return new Content( Content.convertLine( line ) );
159 -};
160 -
161 -/**
162 - * Creates a new Content object from a list of WikiDom line objects.
163 - *
164 - * This plural version of Content.newFromLine inserts non-annotated new line characters between
165 - * lines, preserving the divisions between the original line objects. When Content objects are
166 - * converted to WikiDom line objects, these new line characters are used to split the content data
167 - * into multiple line objects, thus making a clean round trip possible.
168 - *
169 - * @param line {Array} List of WikiDom compatible line objects - @see Content.convertLine
170 - * @return {Content} New content object containing data derived from the WikiDom line
171 - */
172 -Content.newFromLines = function( lines ) {
173 - var data = [];
174 - for ( var i = 0; i < lines.length; i++ ) {
175 - data = data.concat( Content.convertLine( lines[i] ) );
176 - if ( i < lines.length - 1 ) {
177 - data.push( '\n' );
178 - }
179 - }
180 - return new Content( data );
181 -};
182 -
183 -/**
184 - * Gets a rendered opening or closing of an annotation.
185 - *
186 - * Tag nesting is handled using a stack, which keeps track of what is currently open. A common stack
187 - * argument should be used while rendering content.
188 - *
189 - * @param bias {String} Which side of the annotation to render, either "open" or "close"
190 - * @param annotation {Object} Annotation to render
191 - * @param stack {Array} List of currently open annotations
192 - * @return {String} Rendered annotation
193 - */
194 -Content.renderAnnotation = function( bias, annotation, stack ) {
195 - var renderers = Content.annotationRenderers,
196 - type = annotation.type,
197 - out = '';
198 - if ( type in renderers ) {
199 - if ( bias === 'open' ) {
200 - // Add annotation to the top of the stack
201 - stack.push( annotation );
202 - // Open annotation
203 - out += typeof renderers[type]['open'] === 'function'
204 - ? renderers[type]['open']( annotation.data )
205 - : renderers[type]['open'];
206 - } else {
207 - if ( stack[stack.length - 1] === annotation ) {
208 - // Remove annotation from top of the stack
209 - stack.pop();
210 - // Close annotation
211 - out += typeof renderers[type]['close'] === 'function'
212 - ? renderers[type]['close']( annotation.data )
213 - : renderers[type]['close'];
214 - } else {
215 - // Find the annotation in the stack
216 - var depth = stack.indexOf( annotation );
217 - if ( depth === -1 ) {
218 - throw 'Invalid stack error. An element is missing from the stack.';
219 - }
220 - // Close each already opened annotation
221 - for ( var i = stack.length - 1; i >= depth + 1; i-- ) {
222 - out += typeof renderers[stack[i].type]['close'] === 'function'
223 - ? renderers[stack[i].type]['close']( stack[i].data )
224 - : renderers[stack[i].type]['close'];
225 - }
226 - // Close the buried annotation
227 - out += typeof renderers[type]['close'] === 'function'
228 - ? renderers[type]['close']( annotation.data )
229 - : renderers[type]['close'];
230 - // Re-open each previously opened annotation
231 - for ( var i = depth + 1; i < stack.length; i++ ) {
232 - out += typeof renderers[stack[i].type]['open'] === 'function'
233 - ? renderers[stack[i].type]['open']( stack[i].data )
234 - : renderers[stack[i].type]['open'];
235 - }
236 - // Remove the annotation from the middle of the stack
237 - stack.splice( depth, 1 );
238 - }
239 - }
240 - }
241 - return out;
242 -};
243 -
244 -/* Methods */
245 -
246 -/**
247 - * Gets plain text version of the content within a specific range.
248 - *
249 - * Range arguments (start and end) are clamped if out of range.
250 - *
251 - * @param start {Integer} Optional beginning of range, if omitted range will begin at 0
252 - * @param end {Integer} Optional end of range, if omitted range will end a this.data.length
253 - * @return {String} Plain text within given range
254 - */
255 -Content.prototype.substring = function( start, end ) {
256 - // Wrap values
257 - start = Math.max( 0, start || 0 );
258 - if ( end === undefined ) {
259 - end = this.data.length;
260 - } else {
261 - end = Math.min( this.data.length, end )
262 - }
263 - // Copy characters
264 - var text = '';
265 - for ( var i = start; i < end; i++ ) {
266 - // If not using in IE6 or IE7 (which do not support array access for strings) use this..
267 - // text += this.data[i][0];
268 - // Otherwise use this...
269 - text += typeof this.data[i] === 'string' ? this.data[i] : this.data[i][0];
270 - }
271 - return text;
272 -};
273 -
274 -/**
275 - * Gets a new Content object containing content data within a specific range.
276 - *
277 - * Range arguments (start and end) are clamped if out of range.
278 - *
279 - * @param start {Integer} Optional beginning of range, if omitted range will begin at 0
280 - * @param end {Integer} Optional end of range, if omitted range will end a this.data.length
281 - * @return {Content} New content object
282 - */
283 -Content.prototype.slice = function( start, end ) {
284 - return new Content( this.data.slice( start, end ) );
285 -};
286 -
287 -/**
288 - * Inserts content data at a specific position.
289 - *
290 - * Inserted content will inherit annotations from neighboring content.
291 - *
292 - * @param start {Integer} Position to insert content at
293 - * @param insert {Array} Content data to insert
294 - */
295 -Content.prototype.insert = function( start, insert ) {
296 - // TODO: Prefer to not take annotations from a neighbor that's a space character
297 - var neighbor = this.data[Math.max( start - 1, 0 )];
298 - if ( $.isArray( neighbor ) ) {
299 - var annotations = neighbor.slice( 1 );
300 - for ( var i = 0; i < insert.length; i++ ) {
301 - if ( typeof insert[i] === 'string' ) {
302 - insert[i] = [insert[i]];
303 - }
304 - insert[i] = insert[i].concat( annotations );
305 - }
306 - }
307 - Array.prototype.splice.apply( this.data, [start, 0].concat( insert ) )
308 -};
309 -
310 -/**
311 - * Removes content data within a specific range.
312 - *
313 - * @param start {Integer} Beginning of range
314 - * @param end {Integer} End of range
315 - */
316 -Content.prototype.remove = function( start, end ) {
317 - this.data.splice( start, end - start );
318 -};
319 -
320 -/**
321 - * Gets the length of the content data.
322 - *
323 - * @return {Integer} Length of content data
324 - */
325 -Content.prototype.getLength = function() {
326 - return this.data.length;
327 -};
328 -
329 -/**
330 - * Gets a list of indexes within the content data which use a given annotation.
331 - *
332 - * Strict coverage may be used to compare not only annotation types, but also their data. Since new
333 - * line characters are never annotated, they are always considered covered.
334 - *
335 - * @param start {Integer} Beginning of range
336 - * @param end {Integer} End of range
337 - * @param annotation {Object} Annotation to compare with
338 - * @param strict {Boolean} Optionally compare annotation data as well as type
339 - * @return {Array} List of indexes of covered characters within content data
340 - */
341 -Content.prototype.coverageOfAnnotation = function( start, end, annotation, strict ) {
342 - var coverage = [];
343 - for ( var i = start; i < end; i++ ) {
344 - var index = this.indexOfAnnotation( i, annotation );
345 - if ( typeof this.data[i] !== 'string' && index !== -1 ) {
346 - if ( strict ) {
347 - if ( Content.compareObjects( this.data[i][index].data, annotation.data ) ) {
348 - coverage.push( i );
349 - }
350 - } else {
351 - coverage.push( i );
352 - }
353 - } else if ( this.data[i] === '\n' ) {
354 - coverage.push( i );
355 - }
356 - }
357 - return coverage;
358 -};
359 -
360 -/**
361 - * Gets the first index within an annotated character that matches a given annotation.
362 - *
363 - * Strict coverage may be used to compare not only annotation types, but also their data.
364 - *
365 - * @param offset {Integer} Index of character within content data to find annotation within
366 - * @param annotation {Object} Annotation to compare with
367 - * @param strict {Boolean} Optionally compare annotation data as well as type
368 - */
369 -Content.prototype.indexOfAnnotation = function( offset, annotation, strict ) {
370 - var annotatedCharacter = this.data[offset];
371 - if ( typeof annotatedCharacter !== 'string' ) {
372 - for ( var i = 1; i < this.data[offset].length; i++ ) {
373 - if ( annotatedCharacter[i].type === annotation.type ) {
374 - if ( strict ) {
375 - if ( Content.compareObjects( annotatedCharacter[i].data, annotation.data ) ) {
376 - return i;
377 - }
378 - } else {
379 - return i;
380 - }
381 - }
382 - }
383 - }
384 - return -1;
385 -};
386 -
387 -/**
388 - * Applies an annotation to content data within a given range.
389 - *
390 - * If a range arguments are not provided, all content will be annotated. New line characters are
391 - * never annotated. The add method will replace and the remove method will delete any existing
392 - * annotations with the same type as the annotation argument, regardless of their data properties.
393 - * The toggle method will use add if any of the content within the range is not already covered by
394 - * the annotation, or remove if all of it is.
395 - *
396 - * @param method {String} Way to apply annotation ("toggle", "add" or "remove")
397 - * @param annotation {Object} Annotation to apply
398 - * @param start {Integer} Offset to begin annotating from
399 - * @param end {Integer} Offset to stop annotating to
400 - */
401 -Content.prototype.annotate = function( method, annotation, start, end ) {
402 - start = Math.max( start, 0 );
403 - end = Math.min( end, this.data.length );
404 - if ( method === 'toggle' ) {
405 - var coverage = this.coverageOfAnnotation( start, end, annotation, false );
406 - if ( coverage.length === end - start ) {
407 - var strictCoverage = this.coverageOfAnnotation( start, end, annotation, true );
408 - method = strictCoverage.length === coverage.length ? 'remove' : 'add';
409 - } else {
410 - method = 'add';
411 - }
412 - }
413 - if ( method === 'add' ) {
414 - var duplicate;
415 - for ( var i = start; i < end; i++ ) {
416 - duplicate = -1;
417 - if ( typeof this.data[i] === 'string' ) {
418 - // Never annotate new lines
419 - if ( this.data[i] === '\n' ) {
420 - continue;
421 - }
422 - // Auto-initialize as annotated character
423 - this.data[i] = [this.data[i]];
424 - } else {
425 - // Detect duplicate annotation
426 - duplicate = this.indexOfAnnotation( i, annotation );
427 - }
428 - if ( duplicate === -1 ) {
429 - // Append new annotation
430 - this.data[i].push( annotation );
431 - } else {
432 - // Replace existing annotation
433 - this.data[i][duplicate] = annotation;
434 - }
435 - }
436 - } else if ( method === 'remove' ) {
437 - for ( var i = start; i < end; i++ ) {
438 - if ( typeof this.data[i] !== 'string' ) {
439 - if ( annotation.type === 'all' ) {
440 - // Remove all annotations by converting the annotated character to a plain
441 - // character
442 - this.data[i] = this.data[i][0];
443 - }
444 - // Remove all matching instances of annotation
445 - var j;
446 - while ( ( j = this.indexOfAnnotation( i, annotation ) ) !== -1 ) {
447 - this.data[i].splice( j, 1 );
448 - }
449 - }
450 - }
451 - }
452 -};
453 -
454 -/**
455 - * Gets an HTML rendering of a range of content data.
456 - *
457 - * @param start {Integer} Beginning of range
458 - * @param end {Integer} End of range
459 - * @param {String} Rendered HTML of content data
460 - */
461 -Content.prototype.render = function( start, end ) {
462 - if ( start || end ) {
463 - return this.slice( start, end ).render();
464 - }
465 - var out = '',
466 - left = '',
467 - right,
468 - leftPlain,
469 - rightPlain,
470 - stack = [];
471 - for ( var i = 0; i < this.data.length; i++ ) {
472 - right = this.data[i];
473 - leftPlain = typeof left === 'string';
474 - rightPlain = typeof right === 'string';
475 - if ( !leftPlain && rightPlain ) {
476 - // [formatted][plain] pair, close any annotations for left
477 - for ( var j = 1; j < left.length; j++ ) {
478 - out += Content.renderAnnotation( 'close', left[j], stack );
479 - }
480 - } else if ( leftPlain && !rightPlain ) {
481 - // [plain][formatted] pair, open any annotations for right
482 - for ( var j = 1; j < right.length; j++ ) {
483 - out += Content.renderAnnotation( 'open', right[j], stack );
484 - }
485 - } else if ( !leftPlain && !rightPlain ) {
486 - // [formatted][formatted] pair, open/close any differences
487 - for ( var j = 1; j < left.length; j++ ) {
488 - if ( right.indexOf( left[j] ) === -1 ) {
489 - out += Content.renderAnnotation( 'close', left[j], stack );
490 - }
491 - }
492 - for ( var j = 1; j < right.length; j++ ) {
493 - if ( left.indexOf( right[j] ) === -1 ) {
494 - out += Content.renderAnnotation( 'open', right[j], stack );
495 - }
496 - }
497 - }
498 - out += right[0] in Content.htmlCharacters ? Content.htmlCharacters[right[0]] : right[0];
499 - left = right;
500 - }
501 - return out;
502 -}
 2+/* Classes */
 3+
 4+/**
 5+ * Content objects are wrappers around arrays of plain or annotated characters. Data in this form
 6+ * is ultimately equivalent to but more efficient to work with than WikiDom line objects (plain text
 7+ * paired with offset annotation), especially when performing substring operations. Content can be
 8+ * derived from or converted to one or more WikiDom line objects.
 9+ *
 10+ * @param content {Array} List of plain or annotated characters
 11+ * @returns {Content}
 12+ */
 13+function Content( content ) {
 14+ this.data = content || [];
 15+};
 16+
 17+/* Static Members */
 18+
 19+/**
 20+ * List of annotation rendering implementations.
 21+ *
 22+ * Each supported annotation renderer must have an open and close property, each either a string or
 23+ * a function which accepts a data argument.
 24+ */
 25+Content.annotationRenderers = {
 26+ 'template': {
 27+ 'open': function( data ) {
 28+ return '<span class="editSurface-format-object">' + data.html;
 29+ },
 30+ 'close': '</span>',
 31+ },
 32+ 'bold': {
 33+ 'open': '<span class="editSurface-format-bold">',
 34+ 'close': '</span>',
 35+ },
 36+ 'italic': {
 37+ 'open': '<span class="editSurface-format-italic">',
 38+ 'close': '</span>',
 39+ },
 40+ 'size': {
 41+ 'open': function( data ) {
 42+ return '<span class="editSurface-format-' + data.type + '">';
 43+ },
 44+ 'close': '</span>',
 45+ },
 46+ 'script': {
 47+ 'open': function( data ) {
 48+ return '<span class="editSurface-format-' + data.type + '">';
 49+ },
 50+ 'close': '</span>',
 51+ },
 52+ 'link': {
 53+ 'open': function( data ) {
 54+ return '<span class="editSurface-format-link" data-href="' + data.href + '">';
 55+ },
 56+ 'close': '</span>'
 57+ }
 58+};
 59+
 60+Content.htmlCharacters = {
 61+ '&': '&amp;',
 62+ '<': '&lt;',
 63+ '>': '&gt;',
 64+ '\'': '&#039;',
 65+ '"': '&quot;',
 66+ ' ': '&nbsp;',
 67+ '\n': '<span class="editSurface-whitespace">&#182;</span>',
 68+ '\t': '<span class="editSurface-whitespace">&#8702;</span>'
 69+};
 70+
 71+/* Static Methods */
 72+
 73+/**
 74+ * Recursively compares string and number property between two objects.
 75+ *
 76+ * A false result may be caused by property inequality or by properties in one object missing from
 77+ * the other. An asymmetrical test may also be performed, which checks only that properties in the
 78+ * first object are present in the second object, but not the inverse.
 79+ *
 80+ * @param a {Object} First object to compare
 81+ * @param b {Object} Second object to compare
 82+ * @param asymmetrical {Boolean} Whether to check only that b contains values from a
 83+ * @return {Boolean} If the objects contain the same values as each other
 84+ */
 85+Content.compareObjects = function( a, b, asymmetrical ) {
 86+ var aValue, bValue, aType, bType;
 87+ for ( var k in a ) {
 88+ aValue = a[k];
 89+ bValue = b[k];
 90+ aType = typeof aValue;
 91+ bType = typeof bValue;
 92+ if ( aType !== bType
 93+ || ( ( aType === 'string' || aType === 'number' ) && aValue !== bValue )
 94+ || ( $.isPlainObject( aValue ) && !Content.compareObjects( aValue, bValue ) ) ) {
 95+ return false
 96+ }
 97+ }
 98+ // If the check is not asymmetrical, recursing with the arguments swapped will verify our result
 99+ return asymmetrical ? true : Content.compareObjects( b, a, true );
 100+};
 101+
 102+/**
 103+ * Gets a recursive copy of an object's string, number and plain-object property.
 104+ *
 105+ * @param source {Object} Object to copy
 106+ * @return {Object} Copy of source object
 107+ */
 108+Content.copyObject = function( source ) {
 109+ var destination = {};
 110+ for ( var key in source ) {
 111+ sourceValue = source[key];
 112+ sourceType = typeof sourceValue;
 113+ if ( sourceType === 'string' || sourceType === 'number' ) {
 114+ destination[key] = sourceValue;
 115+ } else if ( $.isPlainObject( sourceValue ) ) {
 116+ destination[key] = Content.copyObject( sourceValue );
 117+ }
 118+ }
 119+ return destination;
 120+};
 121+
 122+/**
 123+ * Gets content data from a WikiDom line object, which uses a series of offset-based annotations to
 124+ * supplement plain text.
 125+ *
 126+ * @param line {Object} WikiDom compatible line object, containing text and optionally annotations
 127+ * properties, the latter of which being an array of annotation objects including range information
 128+ * @return {Array} List of plain or annotated characters
 129+ */
 130+Content.convertLine = function( line ) {
 131+ // Convert string to array of characters
 132+ var data = line.text.split('');
 133+ for ( var i in line.annotations ) {
 134+ var src = line.annotations[i];
 135+ // Build simplified annotation object
 136+ var dst = { 'type': src.type };
 137+ if ( 'data' in src ) {
 138+ dst.data = Content.copyObject( src.data );
 139+ }
 140+ // Apply annotation to range
 141+ for ( var k = src.range.start; k < src.range.end; k++ ) {
 142+ // Auto-convert to array
 143+ typeof data[k] === 'string' && ( data[k] = [data[k]] );
 144+ // Append
 145+ data[k].push( dst );
 146+ }
 147+ }
 148+ return data;
 149+};
 150+
 151+/**
 152+ * Creates a new Content object from a WikiDom line object.
 153+ *
 154+ * @param line {Object} WikiDom compatible line object - @see Content.convertLine
 155+ * @return {Content} New content object containing data derived from the WikiDom line
 156+ */
 157+Content.newFromLine = function( line ) {
 158+ return new Content( Content.convertLine( line ) );
 159+};
 160+
 161+/**
 162+ * Creates a new Content object from a list of WikiDom line objects.
 163+ *
 164+ * This plural version of Content.newFromLine inserts non-annotated new line characters between
 165+ * lines, preserving the divisions between the original line objects. When Content objects are
 166+ * converted to WikiDom line objects, these new line characters are used to split the content data
 167+ * into multiple line objects, thus making a clean round trip possible.
 168+ *
 169+ * @param line {Array} List of WikiDom compatible line objects - @see Content.convertLine
 170+ * @return {Content} New content object containing data derived from the WikiDom line
 171+ */
 172+Content.newFromLines = function( lines ) {
 173+ var data = [];
 174+ for ( var i = 0; i < lines.length; i++ ) {
 175+ data = data.concat( Content.convertLine( lines[i] ) );
 176+ if ( i < lines.length - 1 ) {
 177+ data.push( '\n' );
 178+ }
 179+ }
 180+ return new Content( data );
 181+};
 182+
 183+/**
 184+ * Gets a rendered opening or closing of an annotation.
 185+ *
 186+ * Tag nesting is handled using a stack, which keeps track of what is currently open. A common stack
 187+ * argument should be used while rendering content.
 188+ *
 189+ * @param bias {String} Which side of the annotation to render, either "open" or "close"
 190+ * @param annotation {Object} Annotation to render
 191+ * @param stack {Array} List of currently open annotations
 192+ * @return {String} Rendered annotation
 193+ */
 194+Content.renderAnnotation = function( bias, annotation, stack ) {
 195+ var renderers = Content.annotationRenderers,
 196+ type = annotation.type,
 197+ out = '';
 198+ if ( type in renderers ) {
 199+ if ( bias === 'open' ) {
 200+ // Add annotation to the top of the stack
 201+ stack.push( annotation );
 202+ // Open annotation
 203+ out += typeof renderers[type]['open'] === 'function'
 204+ ? renderers[type]['open']( annotation.data )
 205+ : renderers[type]['open'];
 206+ } else {
 207+ if ( stack[stack.length - 1] === annotation ) {
 208+ // Remove annotation from top of the stack
 209+ stack.pop();
 210+ // Close annotation
 211+ out += typeof renderers[type]['close'] === 'function'
 212+ ? renderers[type]['close']( annotation.data )
 213+ : renderers[type]['close'];
 214+ } else {
 215+ // Find the annotation in the stack
 216+ var depth = stack.indexOf( annotation );
 217+ if ( depth === -1 ) {
 218+ throw 'Invalid stack error. An element is missing from the stack.';
 219+ }
 220+ // Close each already opened annotation
 221+ for ( var i = stack.length - 1; i >= depth + 1; i-- ) {
 222+ out += typeof renderers[stack[i].type]['close'] === 'function'
 223+ ? renderers[stack[i].type]['close']( stack[i].data )
 224+ : renderers[stack[i].type]['close'];
 225+ }
 226+ // Close the buried annotation
 227+ out += typeof renderers[type]['close'] === 'function'
 228+ ? renderers[type]['close']( annotation.data )
 229+ : renderers[type]['close'];
 230+ // Re-open each previously opened annotation
 231+ for ( var i = depth + 1; i < stack.length; i++ ) {
 232+ out += typeof renderers[stack[i].type]['open'] === 'function'
 233+ ? renderers[stack[i].type]['open']( stack[i].data )
 234+ : renderers[stack[i].type]['open'];
 235+ }
 236+ // Remove the annotation from the middle of the stack
 237+ stack.splice( depth, 1 );
 238+ }
 239+ }
 240+ }
 241+ return out;
 242+};
 243+
 244+/* Methods */
 245+
 246+/**
 247+ * Gets plain text version of the content within a specific range.
 248+ *
 249+ * Range arguments (start and end) are clamped if out of range.
 250+ *
 251+ * @param start {Integer} Optional beginning of range, if omitted range will begin at 0
 252+ * @param end {Integer} Optional end of range, if omitted range will end a this.data.length
 253+ * @return {String} Plain text within given range
 254+ */
 255+Content.prototype.substring = function( start, end ) {
 256+ // Wrap values
 257+ start = Math.max( 0, start || 0 );
 258+ if ( end === undefined ) {
 259+ end = this.data.length;
 260+ } else {
 261+ end = Math.min( this.data.length, end )
 262+ }
 263+ // Copy characters
 264+ var text = '';
 265+ for ( var i = start; i < end; i++ ) {
 266+ // If not using in IE6 or IE7 (which do not support array access for strings) use this..
 267+ // text += this.data[i][0];
 268+ // Otherwise use this...
 269+ text += typeof this.data[i] === 'string' ? this.data[i] : this.data[i][0];
 270+ }
 271+ return text;
 272+};
 273+
 274+/**
 275+ * Gets a new Content object containing content data within a specific range.
 276+ *
 277+ * Range arguments (start and end) are clamped if out of range.
 278+ *
 279+ * @param start {Integer} Optional beginning of range, if omitted range will begin at 0
 280+ * @param end {Integer} Optional end of range, if omitted range will end a this.data.length
 281+ * @return {Content} New content object
 282+ */
 283+Content.prototype.slice = function( start, end ) {
 284+ return new Content( this.data.slice( start, end ) );
 285+};
 286+
 287+/**
 288+ * Inserts content data at a specific position.
 289+ *
 290+ * Inserted content will inherit annotations from neighboring content.
 291+ *
 292+ * @param start {Integer} Position to insert content at
 293+ * @param insert {Array} Content data to insert
 294+ */
 295+Content.prototype.insert = function( start, insert ) {
 296+ // TODO: Prefer to not take annotations from a neighbor that's a space character
 297+ var neighbor = this.data[Math.max( start - 1, 0 )];
 298+ if ( $.isArray( neighbor ) ) {
 299+ var annotations = neighbor.slice( 1 );
 300+ for ( var i = 0; i < insert.length; i++ ) {
 301+ if ( typeof insert[i] === 'string' ) {
 302+ insert[i] = [insert[i]];
 303+ }
 304+ insert[i] = insert[i].concat( annotations );
 305+ }
 306+ }
 307+ Array.prototype.splice.apply( this.data, [start, 0].concat( insert ) )
 308+};
 309+
 310+/**
 311+ * Removes content data within a specific range.
 312+ *
 313+ * @param start {Integer} Beginning of range
 314+ * @param end {Integer} End of range
 315+ */
 316+Content.prototype.remove = function( start, end ) {
 317+ this.data.splice( start, end - start );
 318+};
 319+
 320+/**
 321+ * Gets the length of the content data.
 322+ *
 323+ * @return {Integer} Length of content data
 324+ */
 325+Content.prototype.getLength = function() {
 326+ return this.data.length;
 327+};
 328+
 329+/**
 330+ * Gets a list of indexes within the content data which use a given annotation.
 331+ *
 332+ * Strict coverage may be used to compare not only annotation types, but also their data. Since new
 333+ * line characters are never annotated, they are always considered covered.
 334+ *
 335+ * @param start {Integer} Beginning of range
 336+ * @param end {Integer} End of range
 337+ * @param annotation {Object} Annotation to compare with
 338+ * @param strict {Boolean} Optionally compare annotation data as well as type
 339+ * @return {Array} List of indexes of covered characters within content data
 340+ */
 341+Content.prototype.coverageOfAnnotation = function( start, end, annotation, strict ) {
 342+ var coverage = [];
 343+ for ( var i = start; i < end; i++ ) {
 344+ var index = this.indexOfAnnotation( i, annotation );
 345+ if ( typeof this.data[i] !== 'string' && index !== -1 ) {
 346+ if ( strict ) {
 347+ if ( Content.compareObjects( this.data[i][index].data, annotation.data ) ) {
 348+ coverage.push( i );
 349+ }
 350+ } else {
 351+ coverage.push( i );
 352+ }
 353+ } else if ( this.data[i] === '\n' ) {
 354+ coverage.push( i );
 355+ }
 356+ }
 357+ return coverage;
 358+};
 359+
 360+/**
 361+ * Gets the first index within an annotated character that matches a given annotation.
 362+ *
 363+ * Strict coverage may be used to compare not only annotation types, but also their data.
 364+ *
 365+ * @param offset {Integer} Index of character within content data to find annotation within
 366+ * @param annotation {Object} Annotation to compare with
 367+ * @param strict {Boolean} Optionally compare annotation data as well as type
 368+ */
 369+Content.prototype.indexOfAnnotation = function( offset, annotation, strict ) {
 370+ var annotatedCharacter = this.data[offset];
 371+ if ( typeof annotatedCharacter !== 'string' ) {
 372+ for ( var i = 1; i < this.data[offset].length; i++ ) {
 373+ if ( annotatedCharacter[i].type === annotation.type ) {
 374+ if ( strict ) {
 375+ if ( Content.compareObjects( annotatedCharacter[i].data, annotation.data ) ) {
 376+ return i;
 377+ }
 378+ } else {
 379+ return i;
 380+ }
 381+ }
 382+ }
 383+ }
 384+ return -1;
 385+};
 386+
 387+/**
 388+ * Applies an annotation to content data within a given range.
 389+ *
 390+ * If a range arguments are not provided, all content will be annotated. New line characters are
 391+ * never annotated. The add method will replace and the remove method will delete any existing
 392+ * annotations with the same type as the annotation argument, regardless of their data properties.
 393+ * The toggle method will use add if any of the content within the range is not already covered by
 394+ * the annotation, or remove if all of it is.
 395+ *
 396+ * @param method {String} Way to apply annotation ("toggle", "add" or "remove")
 397+ * @param annotation {Object} Annotation to apply
 398+ * @param start {Integer} Offset to begin annotating from
 399+ * @param end {Integer} Offset to stop annotating to
 400+ */
 401+Content.prototype.annotate = function( method, annotation, start, end ) {
 402+ start = Math.max( start, 0 );
 403+ end = Math.min( end, this.data.length );
 404+ if ( method === 'toggle' ) {
 405+ var coverage = this.coverageOfAnnotation( start, end, annotation, false );
 406+ if ( coverage.length === end - start ) {
 407+ var strictCoverage = this.coverageOfAnnotation( start, end, annotation, true );
 408+ method = strictCoverage.length === coverage.length ? 'remove' : 'add';
 409+ } else {
 410+ method = 'add';
 411+ }
 412+ }
 413+ if ( method === 'add' ) {
 414+ var duplicate;
 415+ for ( var i = start; i < end; i++ ) {
 416+ duplicate = -1;
 417+ if ( typeof this.data[i] === 'string' ) {
 418+ // Never annotate new lines
 419+ if ( this.data[i] === '\n' ) {
 420+ continue;
 421+ }
 422+ // Auto-initialize as annotated character
 423+ this.data[i] = [this.data[i]];
 424+ } else {
 425+ // Detect duplicate annotation
 426+ duplicate = this.indexOfAnnotation( i, annotation );
 427+ }
 428+ if ( duplicate === -1 ) {
 429+ // Append new annotation
 430+ this.data[i].push( annotation );
 431+ } else {
 432+ // Replace existing annotation
 433+ this.data[i][duplicate] = annotation;
 434+ }
 435+ }
 436+ } else if ( method === 'remove' ) {
 437+ for ( var i = start; i < end; i++ ) {
 438+ if ( typeof this.data[i] !== 'string' ) {
 439+ if ( annotation.type === 'all' ) {
 440+ // Remove all annotations by converting the annotated character to a plain
 441+ // character
 442+ this.data[i] = this.data[i][0];
 443+ }
 444+ // Remove all matching instances of annotation
 445+ var j;
 446+ while ( ( j = this.indexOfAnnotation( i, annotation ) ) !== -1 ) {
 447+ this.data[i].splice( j, 1 );
 448+ }
 449+ }
 450+ }
 451+ }
 452+};
 453+
 454+/**
 455+ * Gets an HTML rendering of a range of content data.
 456+ *
 457+ * @param start {Integer} Beginning of range
 458+ * @param end {Integer} End of range
 459+ * @param {String} Rendered HTML of content data
 460+ */
 461+Content.prototype.render = function( start, end ) {
 462+ if ( start || end ) {
 463+ return this.slice( start, end ).render();
 464+ }
 465+ var out = '',
 466+ left = '',
 467+ right,
 468+ leftPlain,
 469+ rightPlain,
 470+ stack = [];
 471+ for ( var i = 0; i < this.data.length; i++ ) {
 472+ right = this.data[i];
 473+ leftPlain = typeof left === 'string';
 474+ rightPlain = typeof right === 'string';
 475+ if ( !leftPlain && rightPlain ) {
 476+ // [formatted][plain] pair, close any annotations for left
 477+ for ( var j = 1; j < left.length; j++ ) {
 478+ out += Content.renderAnnotation( 'close', left[j], stack );
 479+ }
 480+ } else if ( leftPlain && !rightPlain ) {
 481+ // [plain][formatted] pair, open any annotations for right
 482+ for ( var j = 1; j < right.length; j++ ) {
 483+ out += Content.renderAnnotation( 'open', right[j], stack );
 484+ }
 485+ } else if ( !leftPlain && !rightPlain ) {
 486+ // [formatted][formatted] pair, open/close any differences
 487+ for ( var j = 1; j < left.length; j++ ) {
 488+ if ( right.indexOf( left[j] ) === -1 ) {
 489+ out += Content.renderAnnotation( 'close', left[j], stack );
 490+ }
 491+ }
 492+ for ( var j = 1; j < right.length; j++ ) {
 493+ if ( left.indexOf( right[j] ) === -1 ) {
 494+ out += Content.renderAnnotation( 'open', right[j], stack );
 495+ }
 496+ }
 497+ }
 498+ out += right[0] in Content.htmlCharacters ? Content.htmlCharacters[right[0]] : right[0];
 499+ left = right;
 500+ }
 501+ return out;
 502+}
Property changes on: trunk/parsers/wikidom/lib/es/es.Content.js
___________________________________________________________________
Added: svn:eol-style
503503 + native
Index: trunk/parsers/wikidom/lib/es/es.Cursor.js
@@ -1,49 +1,49 @@
2 -/**
3 - *
4 - * @returns {Cursor}
5 - */
6 -function Cursor() {
7 - this.cursorInterval = null;
8 - this.$ = $( '<div class="editSurface-cursor"></div>' );
9 -}
10 -
11 -/**
12 - * Shows the cursor in a new position.
13 - *
14 - * @param position {Position} Position to show the cursor at
15 - * @param offset {Position} Offset to be added to position
16 - */
17 -Cursor.prototype.show = function( position, offset ) {
18 - if ( position ) {
19 - if ( $.isPlainObject( offset ) ) {
20 - position.left += offset.left;
21 - position.top += offset.top;
22 - position.bottom += offset.top;
23 - }
24 - this.$.css({
25 - 'left': position.left,
26 - 'top': position.top,
27 - 'height': position.bottom - position.top
28 - }).show();
29 - } else {
30 - this.$.show();
31 - }
32 -
33 - if ( this.cursorInterval ) {
34 - clearInterval( this.cursorInterval );
35 - }
36 - this.cursorInterval = setInterval( function( cursor ) {
37 - cursor.$.css( 'display' ) == 'block'
38 - ? cursor.$.hide() : cursor.$.show();
39 - }, 500, this );
40 -};
41 -
42 -/**
43 - * Hides the cursor.
44 - */
45 -Cursor.prototype.hide = function() {
46 - if( this.cursorInterval ) {
47 - clearInterval( this.cursorInterval );
48 - }
49 - this.$.hide()
50 -};
 2+/**
 3+ *
 4+ * @returns {Cursor}
 5+ */
 6+function Cursor() {
 7+ this.cursorInterval = null;
 8+ this.$ = $( '<div class="editSurface-cursor"></div>' );
 9+}
 10+
 11+/**
 12+ * Shows the cursor in a new position.
 13+ *
 14+ * @param position {Position} Position to show the cursor at
 15+ * @param offset {Position} Offset to be added to position
 16+ */
 17+Cursor.prototype.show = function( position, offset ) {
 18+ if ( position ) {
 19+ if ( $.isPlainObject( offset ) ) {
 20+ position.left += offset.left;
 21+ position.top += offset.top;
 22+ position.bottom += offset.top;
 23+ }
 24+ this.$.css({
 25+ 'left': position.left,
 26+ 'top': position.top,
 27+ 'height': position.bottom - position.top
 28+ }).show();
 29+ } else {
 30+ this.$.show();
 31+ }
 32+
 33+ if ( this.cursorInterval ) {
 34+ clearInterval( this.cursorInterval );
 35+ }
 36+ this.cursorInterval = setInterval( function( cursor ) {
 37+ cursor.$.css( 'display' ) == 'block'
 38+ ? cursor.$.hide() : cursor.$.show();
 39+ }, 500, this );
 40+};
 41+
 42+/**
 43+ * Hides the cursor.
 44+ */
 45+Cursor.prototype.hide = function() {
 46+ if( this.cursorInterval ) {
 47+ clearInterval( this.cursorInterval );
 48+ }
 49+ this.$.hide()
 50+};
Property changes on: trunk/parsers/wikidom/lib/es/es.Cursor.js
___________________________________________________________________
Added: svn:eol-style
5151 + native
Property changes on: trunk/parsers/wikidom/demos/es/index.html
___________________________________________________________________
Added: svn:eol-style
5252 + native
Property changes on: trunk/parsers/wikidom/demos/surface/index.html
___________________________________________________________________
Added: svn:eol-style
5353 + native
Property changes on: trunk/parsers/wikidom/demos/renderers/index.html
___________________________________________________________________
Added: svn:eol-style
5454 + native
Property changes on: trunk/parsers/wikidom/README
___________________________________________________________________
Added: svn:eol-style
5555 + native

Comments

#Comment by Hashar (talk | contribs)   19:26, 14 July 2011

no functional change

Status & tagging log