Index: trunk/parsers/wikidom/tests/transactions/index.html |
— | — | @@ -13,8 +13,6 @@ |
14 | 14 | <script src="../../lib/es/es.js"></script> |
15 | 15 | <script src="../../lib/es/es.EventEmitter.js"></script> |
16 | 16 | <script src="../../lib/es/es.Content.js"></script> |
17 | | - <script src="../../lib/es/es.Content.Operation.js"></script> |
18 | | - <script src="../../lib/es/es.Content.Selection.js"></script> |
19 | 17 | <script src="../../lib/es/es.Content.Transaction.js"></script> |
20 | 18 | <script src="../../lib/es/es.Range.js"></script> |
21 | 19 | <script src="../../lib/jquery.js"></script> |
Index: trunk/parsers/wikidom/tests/transactions/test.js |
— | — | @@ -40,20 +40,8 @@ |
41 | 41 | /* Tests */ |
42 | 42 | |
43 | 43 | test( 'Insert, retain and remove', 4, function() { |
44 | | - var transactions = { |
45 | | - 'by calling constructor manually': new es.Content.Transaction( |
46 | | - content, |
47 | | - new es.Content.Operation.Retain( 5 ), |
48 | | - new es.Content.Operation.Insert( es.Content.newFromText( 'used to be' ) ), |
49 | | - new es.Content.Operation.Remove( 2 ), |
50 | | - new es.Content.Operation.Retain( 18 ) |
51 | | - ), |
52 | | - 'using es.Content.Transaction.newFromReplace': new es.Content.Transaction.newFromReplace( |
53 | | - content, new es.Range( 5, 7), new es.Content.newFromText( 'used to be' ) |
54 | | - ) |
55 | | - }; |
56 | | - var before = content.getData(), |
57 | | - after = [ |
| 44 | + var before = content.getContent(), |
| 45 | + after = new es.Content( [ |
58 | 46 | ["T", { "type": "italic" }], |
59 | 47 | ["h", { "type": "italic" }], |
60 | 48 | ["i", { "type": "italic" }], |
— | — | @@ -87,39 +75,28 @@ |
88 | 76 | "p", |
89 | 77 | "h", |
90 | 78 | "!" |
91 | | - ]; |
92 | | - for ( var method in transactions ) { |
93 | | - var transaction = transactions[method]; |
94 | | - deepEqual( |
95 | | - transaction.commit( content ).getData(), |
96 | | - after, |
97 | | - 'Committing transaction built with ' + method |
98 | | - ); |
99 | | - deepEqual( |
100 | | - transaction.rollback( content ).getData(), |
101 | | - before, |
102 | | - 'Rolling back transaction built ' + method |
103 | | - ); |
104 | | - } |
| 79 | + ] ); |
| 80 | + |
| 81 | + var tx = new es.Content.Transaction(), |
| 82 | + insertion = es.Content.newFromText( 'used to be' ), |
| 83 | + removal = content.getContent( new es.Range( 5, 7 ) ); |
| 84 | + |
| 85 | + tx.add( 'retain', 5 ); |
| 86 | + tx.add( 'insert', insertion ); |
| 87 | + tx.add( 'remove', removal ); |
| 88 | + tx.add( 'retain', 18 ); |
| 89 | + |
| 90 | + var committed = tx.commit( content ); |
| 91 | + equal( committed.getText(), after.getText(), 'Committing' ); |
| 92 | + deepEqual( committed.getData(), after.getData(), 'Committing' ); |
| 93 | + var rolledback = tx.rollback( committed ); |
| 94 | + equal( rolledback.getText(), before.getText(), 'Rolling back' ); |
| 95 | + deepEqual( rolledback.getData(), before.getData(), 'Rolling back' ); |
105 | 96 | } ); |
106 | 97 | |
107 | | -/* |
108 | 98 | test( 'Annotating', 4, function() { |
109 | | - var transactions = { |
110 | | - 'by calling constructor manually': new es.Content.Transaction( |
111 | | - content, |
112 | | - new es.Content.Operation( 'retain', 4 ), |
113 | | - new es.Content.Operation( 'begin', { 'type': 'italic' } ), |
114 | | - new es.Content.Operation( 'retain', 3 ), |
115 | | - new es.Content.Operation( 'end', { 'type': 'italic' } ), |
116 | | - new es.Content.Operation( 'retain', 18 ) |
117 | | - ), |
118 | | - 'using es.Content.Transaction.newFromAnnotate': new es.Content.Transaction.newFromAnnotate( |
119 | | - content, new es.Range( 4, 7), 'add', { 'type': 'italic' } |
120 | | - ) |
121 | | - }; |
122 | | - var before = content.getData(), |
123 | | - after = [ |
| 99 | + var before = content.getContent(), |
| 100 | + after = new es.Content( [ |
124 | 101 | ["T", { "type": "italic" }], |
125 | 102 | ["h", { "type": "italic" }], |
126 | 103 | ["i", { "type": "italic" }], |
— | — | @@ -145,19 +122,22 @@ |
146 | 123 | "p", |
147 | 124 | "h", |
148 | 125 | "!" |
149 | | - ]; |
150 | | - for ( var method in transactions ) { |
151 | | - var transaction = transactions[method]; |
152 | | - deepEqual( |
153 | | - transaction.commit( content ).getData(), |
154 | | - after, |
155 | | - 'Committing transaction built with ' + method |
156 | | - ); |
157 | | - deepEqual( |
158 | | - transaction.rollback( content ).getData(), |
159 | | - before, |
160 | | - 'Rolling back transaction built ' + method |
161 | | - ); |
162 | | - } |
| 126 | + ] ); |
| 127 | + |
| 128 | + var tx = new es.Content.Transaction(), |
| 129 | + annotation = { 'method': 'add', 'annotation': { 'type': 'italic' } }; |
| 130 | + |
| 131 | + tx.add( 'retain', 4 ); |
| 132 | + tx.add( 'start', annotation ); |
| 133 | + tx.add( 'retain', 3 ); |
| 134 | + tx.add( 'end', annotation ); |
| 135 | + tx.add( 'retain', 18 ); |
| 136 | + |
| 137 | + var committed = tx.commit( content ); |
| 138 | + |
| 139 | + equal( committed.getText(), after.getText(), 'Committing' ); |
| 140 | + deepEqual( committed.getData(), after.getData(), 'Committing' ); |
| 141 | + var rolledback = tx.rollback( committed ); |
| 142 | + equal( rolledback.getText(), before.getText(), 'Rolling back' ); |
| 143 | + deepEqual( rolledback.getData(), before.getData(), 'Rolling back' ); |
163 | 144 | } ); |
164 | | -*/ |
\ No newline at end of file |
Index: trunk/parsers/wikidom/lib/es/es.Content.Operation.js |
— | — | @@ -1,162 +0,0 @@ |
2 | | -/** |
3 | | - * Creates an operation to be applied to a content object. |
4 | | - * |
5 | | - * @class |
6 | | - * @constructor |
7 | | - */ |
8 | | - |
9 | | -es.Content.Operation = function( type, content, data ) { |
10 | | - this.type = type; |
11 | | - this.content = content || null; |
12 | | - this.data = data || null; |
13 | | -}; |
14 | | - |
15 | | -es.Content.Operation.prototype.getType = function() { |
16 | | - return this.type; |
17 | | -}; |
18 | | - |
19 | | -es.Content.Operation.prototype.getContent = function() { |
20 | | - return this.content; |
21 | | -}; |
22 | | - |
23 | | -es.Content.Operation.prototype.setContent = function( content ) { |
24 | | - this.content = content; |
25 | | -}; |
26 | | - |
27 | | -es.Content.Operation.prototype.hasContent = function() { |
28 | | - return !!this.content; |
29 | | -}; |
30 | | - |
31 | | -es.Content.Operation.prototype.getLength = function() { |
32 | | - return 0; |
33 | | -}; |
34 | | - |
35 | | -es.Content.Operation.prototype.getAdvance = function() { |
36 | | - return 0 |
37 | | -}; |
38 | | - |
39 | | -es.Content.Operation.prototype.getData = function() { |
40 | | - return this.data; |
41 | | -}; |
42 | | - |
43 | | -es.Content.Operation.prototype.setData = function() { |
44 | | - return this.data; |
45 | | -}; |
46 | | - |
47 | | -es.Content.Operation.prototype.commit = function( content, annotations ) { |
48 | | - throw 'es.Content.Operation.commit not implemented in this subclass.'; |
49 | | -}; |
50 | | - |
51 | | -es.Content.Operation.prototype.rollback = function( content, annotations ) { |
52 | | - throw 'es.Content.Operation.rollback not implemented in this subclass.'; |
53 | | -}; |
54 | | - |
55 | | -/** |
56 | | - * @class |
57 | | - * @constructor |
58 | | - */ |
59 | | -es.Content.Operation.Retain = function( length ) { |
60 | | - es.Content.Operation.call( this, 'remove' ); |
61 | | - this.type = 'retain'; |
62 | | - this.content = null; |
63 | | - this.length = length; |
64 | | -}; |
65 | | -es.extend( es.Content.Operation.Retain, es.Content.Operation ); |
66 | | - |
67 | | -es.Content.Operation.Retain.prototype.getLength = function() { |
68 | | - return this.length; |
69 | | -}; |
70 | | - |
71 | | -es.Content.Operation.Retain.prototype.getAdvance = function() { |
72 | | - return this.length; |
73 | | -}; |
74 | | - |
75 | | -es.Content.Operation.Retain.prototype.commit = function( content, annotations ) { |
76 | | - content.insert( content.getLength(), this.content.getData() ); |
77 | | - return this.length; |
78 | | -}; |
79 | | - |
80 | | -es.Content.Operation.Retain.prototype.rollback = es.Content.Operation.Retain.prototype.commit; |
81 | | - |
82 | | -/** |
83 | | - * @class |
84 | | - * @constructor |
85 | | - */ |
86 | | -es.Content.Operation.Insert = function( content ) { |
87 | | - es.Content.Operation.call( this, 'insert', content ); |
88 | | -}; |
89 | | -es.extend( es.Content.Operation.Insert, es.Content.Operation ); |
90 | | - |
91 | | -es.Content.Operation.Insert.prototype.getLength = function() { |
92 | | - return this.content.getLength(); |
93 | | -}; |
94 | | - |
95 | | -es.Content.Operation.Insert.prototype.commit = function( content, annotations ) { |
96 | | - content.insert( content.getLength(), this.content.getData() ); |
97 | | - return 0; |
98 | | -}; |
99 | | - |
100 | | -es.Content.Operation.Insert.prototype.rollback = function( content, annotations ) { |
101 | | - return this.content.getLength(); |
102 | | -}; |
103 | | - |
104 | | -/** |
105 | | - * @class |
106 | | - * @constructor |
107 | | - */ |
108 | | -es.Content.Operation.Remove = function( length ) { |
109 | | - es.Content.Operation.call( this, 'remove' ); |
110 | | - this.length = length; |
111 | | -}; |
112 | | -es.extend( es.Content.Operation.Remove, es.Content.Operation ); |
113 | | - |
114 | | -es.Content.Operation.Remove.prototype.getLength = function() { |
115 | | - return this.length; |
116 | | -}; |
117 | | - |
118 | | -es.Content.Operation.Remove.prototype.getAdvance = function() { |
119 | | - return this.length; |
120 | | -}; |
121 | | - |
122 | | -es.Content.Operation.Remove.prototype.commit = es.Content.Operation.Insert.prototype.rollback; |
123 | | -es.Content.Operation.Remove.prototype.rollback = es.Content.Operation.Insert.prototype.commit; |
124 | | - |
125 | | -/** |
126 | | - * @class |
127 | | - * @constructor |
128 | | - */ |
129 | | -es.Content.Operation.Begin = function( annotation ) { |
130 | | - es.Content.Operation.call( this, 'begin', null, annotation ); |
131 | | -}; |
132 | | -es.extend( es.Content.Operation.Begin, es.Content.Operation ); |
133 | | - |
134 | | -es.Content.Operation.Begin.prototype.commit = function( content, annotations ) { |
135 | | - annotations.push( this.data ); |
136 | | - return 0; |
137 | | -}; |
138 | | - |
139 | | -es.Content.Operation.Begin.prototype.rollback = function( content, annotations ) { |
140 | | - var index; |
141 | | - for ( var i = 0; i < annotations.length; i++ ) { |
142 | | - if ( es.Content.compareObjects( annotations[i], this.data ) ) { |
143 | | - index = i; |
144 | | - } |
145 | | - } |
146 | | - if ( index === undefined ) { |
147 | | - throw 'Annotation stack error. Annotation is missing.'; |
148 | | - } |
149 | | - annotations.splice( index, 1 ); |
150 | | - return 0; |
151 | | -}; |
152 | | - |
153 | | -/** |
154 | | - * @class |
155 | | - * @constructor |
156 | | - */ |
157 | | -es.Content.Operation.End = function( annotation ) { |
158 | | - es.Content.Operation.call( this, 'end', null, annotation ); |
159 | | -}; |
160 | | -es.extend( es.Content.Operation.End, es.Content.Operation ); |
161 | | - |
162 | | -es.Content.Operation.End.prototype.commit = es.Content.Operation.Begin.prototype.rollback; |
163 | | -es.Content.Operation.End.prototype.rollback = es.Content.Operation.Begin.prototype.commit; |
Index: trunk/parsers/wikidom/lib/es/es.Content.Transaction.js |
— | — | @@ -3,125 +3,166 @@ |
4 | 4 | * |
5 | 5 | * @class |
6 | 6 | * @constructor |
7 | | - * @param operations {Array} List of operations - can also be the first in a list of variadic |
8 | | - * arguments, each containing a single operation |
| 7 | + * @param content {es.Content} Content to operate on |
9 | 8 | * @property operations {Array} List of operations |
10 | 9 | */ |
11 | | -es.Content.Transaction = function( content, operations ) { |
12 | | - this.content = content; |
| 10 | +es.Content.Transaction = function() { |
13 | 11 | this.operations = []; |
14 | | - this.length = 0; |
15 | | - if ( arguments.length > 1 ) { |
16 | | - // Support variadic arguments |
17 | | - if ( !$.isArray( operations ) ) { |
18 | | - operations = Array.prototype.slice.call( arguments, 1 ); |
19 | | - } |
20 | | - for ( var i = 0; i < operations.length; i++ ) { |
21 | | - this.add( operations[i] ); |
22 | | - } |
23 | | - } |
| 12 | + this.cursor = 0; |
24 | 13 | }; |
25 | 14 | |
26 | | -es.Content.Transaction.prototype.add = function( operation ) { |
27 | | - // Auto-add content to operations which affect a range but don't have content (yet) |
28 | | - var range = new es.Range( this.length, this.length + operation.getLength() ); |
29 | | - if ( operation.getLength() && !operation.hasContent() ) { |
30 | | - if ( !operation.hasContent() ) { |
31 | | - operation.setContent( content.getContent( range ) ); |
32 | | - } |
33 | | - } |
34 | | - this.length += operation.getAdvance(); |
35 | | - this.operations.push( operation ); |
36 | | -}; |
37 | | - |
38 | 15 | /** |
39 | | - * Builds a transaction that removes and or inserts content. |
40 | | - * |
41 | | - * @param content {es.Content} Content to operate on |
42 | | - * @param range {es.Range} Range of content to remove, or zero length range when inserting only |
43 | | - * @param insert {es.Content} Content to insert (optional) |
| 16 | + * List of operation implementations. |
44 | 17 | */ |
45 | | -es.Content.Transaction.newFromReplace = function( content, range, insert ) { |
46 | | - var transaction = new es.Content.Transaction( content ); |
47 | | - range.normalize(); |
48 | | - if ( content.getLength() ) { |
49 | | - // Delete range |
50 | | - if ( range.start ) { |
51 | | - // Use content up until the range begins |
52 | | - transaction.add( new es.Content.Operation.Retain( range.start ) ); |
| 18 | +es.Content.Transaction.operations = ( function() { |
| 19 | + function annotate( con, add, rem ) { |
| 20 | + // Ensure that modifications to annotated characters do not affect other uses of the same |
| 21 | + // content by isolating it - performing a deep-slice |
| 22 | + con.isolate(); |
| 23 | + for ( var i = 0; i < add.length; i++ ) { |
| 24 | + con.annotate( 'add', add[i] ); |
53 | 25 | } |
54 | | - // Skip over the range |
55 | | - if ( range.getLength() ) { |
56 | | - transaction.add( new es.Content.Operation.Remove( range.getLength() ) ); |
| 26 | + for ( var i = 0; i < rem.length; i++ ) { |
| 27 | + con.annotate( 'remove', rem[i] ); |
57 | 28 | } |
58 | 29 | } |
59 | | - if ( insert ) { |
60 | | - // Add content to the output |
61 | | - transaction.add( new es.Content.Operation.Insert( insert ) ); |
| 30 | + function retain( val, cur, src, dst, add, rem ) { |
| 31 | + var con = src.getContent( new es.Range( cur, cur + val ) ); |
| 32 | + if ( add.length || rem.length ) { |
| 33 | + annotate( con, add, rem ); |
| 34 | + } |
| 35 | + dst.insert( dst.getLength(), con.getData() ); |
| 36 | + return val; |
62 | 37 | } |
63 | | - // Retain remaining content |
64 | | - if ( range.end < content.getLength() ) { |
65 | | - transaction.add( new es.Content.Operation.Retain( content.getLength() - range.end ) ); |
| 38 | + function insert( val, cur, src, dst, add, rem ) { |
| 39 | + var con = val.getContent(); |
| 40 | + if ( add.length || rem.length ) { |
| 41 | + annotate( con, add, rem ); |
| 42 | + } |
| 43 | + dst.insert( dst.getLength(), con.getData() ); |
| 44 | + return 0; |
66 | 45 | } |
67 | | - return transaction; |
68 | | -}; |
69 | | - |
70 | | -/** |
71 | | - * Builds a transaction that applies annotations |
72 | | - * |
73 | | - * TODO: Support method argument |
74 | | - * |
75 | | - * @param content {es.Content} Content to operate on |
76 | | - * @param range {es.Range} Range of content to annotate, content will be retained |
77 | | - * @param method {String} Mode of application; "add", "remove", or "toggle" |
78 | | - * @param annotation {Object} Annotation to apply |
79 | | - */ |
80 | | -es.Content.Transaction.newFromAnnotate = function( content, range, method, annotation ) { |
81 | | - var transaction = new es.Content.Transaction(); |
82 | | - range.normalize(); |
83 | | - if ( content.getLength() ) { |
84 | | - if ( range.start ) { |
85 | | - // Use content up until the range begins |
86 | | - transaction.add( new es.Content.Operation.Retain( range.start ) ); |
| 46 | + function start( val, cur, src, dst, add, rem ) { |
| 47 | + if ( val.method === 'add' ) { |
| 48 | + add.push( val.annotation ); |
| 49 | + } else if ( val.method === 'remove' ) { |
| 50 | + rem.push( val.annotation ); |
| 51 | + } else { |
| 52 | + throw 'Annotation method error. Unsupported annotation method: ' + val.method; |
87 | 53 | } |
88 | | - if ( range.getLength() ) { |
89 | | - // Apply annotation to range |
90 | | - transaction.add( new es.Content.Operation.Begin( annotation ) ); |
91 | | - transaction.add( new es.Content.Operation.Retain( range.getLength() ) ); |
92 | | - transaction.add( new es.Content.Operation.End( annotation ) ); |
| 54 | + return 0; |
| 55 | + } |
| 56 | + function end( val, cur, src, dst, add, rem ) { |
| 57 | + var stack; |
| 58 | + if ( val.method === 'add' ) { |
| 59 | + stack = add; |
| 60 | + } else if ( val.method === 'remove' ) { |
| 61 | + stack = rem; |
| 62 | + } else { |
| 63 | + throw 'Annotation method error. Unsupported annotation method: ' + val.method; |
93 | 64 | } |
94 | | - // Retain remaining content |
95 | | - if ( range.end < content.getLength() ) { |
96 | | - transaction.add( new es.Content.Operation.Begin( content.getLength() - range.end ) ); |
| 65 | + var index; |
| 66 | + for ( var i = 0; i < stack.length; i++ ) { |
| 67 | + // TODO: Compare data too: es.Content.compareObjects( stack[i], val.annotation ) |
| 68 | + if ( stack[i].type === val.annotation.type ) { |
| 69 | + index = i; |
| 70 | + break; |
| 71 | + } |
97 | 72 | } |
| 73 | + if ( index === undefined ) { |
| 74 | + throw 'Annotation stack error. Annotation is missing.'; |
| 75 | + } |
| 76 | + stack.splice( index, 1 ); |
| 77 | + return 0; |
98 | 78 | } |
99 | | - return transaction; |
| 79 | + function measure( val ) { |
| 80 | + return val.getLength(); |
| 81 | + } |
| 82 | + function pass( val ) { |
| 83 | + return val; |
| 84 | + } |
| 85 | + function zero( val ) { |
| 86 | + return 0; |
| 87 | + } |
| 88 | + return { |
| 89 | + 'retain': { |
| 90 | + 'commit': retain, |
| 91 | + 'rollback': retain, |
| 92 | + 'advance': pass |
| 93 | + }, |
| 94 | + 'insert': { |
| 95 | + 'commit': insert, |
| 96 | + 'rollback': measure, |
| 97 | + 'advance': zero |
| 98 | + }, |
| 99 | + 'remove': { |
| 100 | + 'commit': measure, |
| 101 | + 'rollback': insert, |
| 102 | + 'advance': measure |
| 103 | + }, |
| 104 | + 'start': { |
| 105 | + 'commit': start, |
| 106 | + 'rollback': function( val, cur, src, dst, add, rem ) { |
| 107 | + return start( val, cur, src, dst, rem, add ); |
| 108 | + }, |
| 109 | + 'advance': zero |
| 110 | + }, |
| 111 | + 'end': { |
| 112 | + 'commit': end, |
| 113 | + 'rollback': function( val, cur, src, dst, add, rem ) { |
| 114 | + return end( val, cur, src, dst, rem, add ); |
| 115 | + }, |
| 116 | + 'advance': zero |
| 117 | + } |
| 118 | + }; |
| 119 | +} )(); |
| 120 | + |
| 121 | +es.Content.Transaction.prototype.getCursor = function() { |
| 122 | + return this.cursor; |
100 | 123 | }; |
101 | 124 | |
102 | | -es.Content.Transaction.prototype.commit = function() { |
103 | | - var offset = 0, |
104 | | - content = new es.Content(), |
105 | | - annotations = []; |
106 | | - for ( var i = 0; i < this.operations.length; i++ ) { |
107 | | - var length = this.operations[i].commit( content, annotations ); |
108 | | - // TODO: Apply annotations in stack |
109 | | - offset += length; |
| 125 | +es.Content.Transaction.prototype.reset = function() { |
| 126 | + this.operations = []; |
| 127 | + this.cursor = 0; |
| 128 | +}; |
| 129 | + |
| 130 | +es.Content.Transaction.prototype.add = function( type, val ) { |
| 131 | + if ( !( type in es.Content.Transaction.operations ) ) { |
| 132 | + throw 'Unknown operation error. Operation type is not supported: ' + type; |
110 | 133 | } |
111 | | - return content; |
| 134 | + var model = es.Content.Transaction.operations[type]; |
| 135 | + this.operations.push( { |
| 136 | + 'type': type, |
| 137 | + 'val': val, |
| 138 | + 'model': model |
| 139 | + } ); |
| 140 | + this.cursor += model.advance( val ); |
112 | 141 | }; |
113 | 142 | |
114 | | -es.Content.Transaction.prototype.rollback = function() { |
115 | | - var offset = 0, |
116 | | - content = new es.Content(), |
117 | | - annotations = []; |
| 143 | +es.Content.Transaction.prototype.commit = function( src ) { |
| 144 | + var cur = 0, |
| 145 | + dst = new es.Content(), |
| 146 | + add = [], |
| 147 | + rem = [], |
| 148 | + adv; |
118 | 149 | for ( var i = 0; i < this.operations.length; i++ ) { |
119 | | - var length = this.operations[i].rollback( content, annotations ); |
120 | | - // TODO: Apply annotations in stack |
121 | | - offset += length; |
| 150 | + var op = this.operations[i]; |
| 151 | + adv = op.model.commit( op.val, cur, src, dst, add, rem ); |
| 152 | + cur += adv; |
122 | 153 | } |
123 | | - return content; |
| 154 | + return dst; |
124 | 155 | }; |
125 | 156 | |
126 | | -es.Content.Transaction.prototype.optimize = function() { |
127 | | - // reduce consecutive operations of the same type to single operations if possible |
| 157 | +es.Content.Transaction.prototype.rollback = function( src ) { |
| 158 | + var cur = 0, |
| 159 | + dst = new es.Content(), |
| 160 | + add = [], |
| 161 | + rem = [], |
| 162 | + adv; |
| 163 | + for ( var i = 0; i < this.operations.length; i++ ) { |
| 164 | + var op = this.operations[i]; |
| 165 | + adv = op.model.rollback( op.val, cur, src, dst, add, rem ); |
| 166 | + cur += adv; |
| 167 | + } |
| 168 | + return dst; |
128 | 169 | }; |
Index: trunk/parsers/wikidom/lib/es/es.Content.js |
— | — | @@ -380,9 +380,8 @@ |
381 | 381 | es.Content.prototype.getContent = function( range ) { |
382 | 382 | if ( !range ) { |
383 | 383 | range = new es.Range( 0, this.data.length ); |
384 | | - } else { |
385 | | - range.normalize(); |
386 | 384 | } |
| 385 | + range.normalize(); |
387 | 386 | return new es.Content( this.data.slice( range.start, range.end ) ); |
388 | 387 | }; |
389 | 388 | |
— | — | @@ -396,13 +395,32 @@ |
397 | 396 | es.Content.prototype.getData = function( range ) { |
398 | 397 | if ( !range ) { |
399 | 398 | range = new es.Range( 0, this.data.length ); |
400 | | - } else { |
401 | | - range.normalize(); |
402 | 399 | } |
| 400 | + range.normalize(); |
403 | 401 | return this.data.slice( range.start, range.end ); |
404 | 402 | }; |
405 | 403 | |
406 | 404 | /** |
| 405 | + * Breaks cross references, which occur when content is copied around. |
| 406 | + * |
| 407 | + * Because content data is an array of characters, or arrays containing a character and any |
| 408 | + * number of references to annotation objects, slicing the array still leaves annotated characters |
| 409 | + * as references to shared memory. This should be used only when annotations are going to be |
| 410 | + * changed because it is rather expensive. |
| 411 | + * |
| 412 | + * @method |
| 413 | + */ |
| 414 | +es.Content.prototype.isolate = function() { |
| 415 | + var i = 0, |
| 416 | + length = this.data.length; |
| 417 | + while ( i < length ) { |
| 418 | + // The slice method works for array or string character type |
| 419 | + this.data[i] = this.data[i].slice( 0 ); |
| 420 | + i++; |
| 421 | + } |
| 422 | +}; |
| 423 | + |
| 424 | +/** |
407 | 425 | * Inserts content data at a specific position. |
408 | 426 | * |
409 | 427 | * Inserted content will inherit annotations from neighboring content. |
— | — | @@ -413,17 +431,19 @@ |
414 | 432 | * @emits "insert" with offset and content data properties |
415 | 433 | * @emits "change" with type:"insert" data property |
416 | 434 | */ |
417 | | -es.Content.prototype.insert = function( offset, content ) { |
418 | | - // TODO: Prefer to not take annotations from a neighbor that's a space character |
419 | | - var neighbor = this.data[Math.max( offset - 1, 0 )]; |
420 | | - if ( $.isArray( neighbor ) ) { |
421 | | - var annotations = neighbor.slice( 1 ); |
422 | | - var i; |
423 | | - for ( i = 0; i < content.length; i++ ) { |
424 | | - if ( typeof content[i] === 'string' ) { |
425 | | - content[i] = [content[i]]; |
| 435 | +es.Content.prototype.insert = function( offset, content, autoAnnotate ) { |
| 436 | + if ( autoAnnotate ) { |
| 437 | + // TODO: Prefer to not take annotations from a neighbor that's a space character |
| 438 | + var neighbor = this.data[Math.max( offset - 1, 0 )]; |
| 439 | + if ( $.isArray( neighbor ) ) { |
| 440 | + var annotations = neighbor.slice( 1 ); |
| 441 | + var i; |
| 442 | + for ( i = 0; i < content.length; i++ ) { |
| 443 | + if ( typeof content[i] === 'string' ) { |
| 444 | + content[i] = [content[i]]; |
| 445 | + } |
| 446 | + content[i] = content[i].concat( annotations ); |
426 | 447 | } |
427 | | - content[i] = content[i].concat( annotations ); |
428 | 448 | } |
429 | 449 | } |
430 | 450 | Array.prototype.splice.apply( this.data, [offset, 0].concat( content ) ); |
— | — | @@ -554,7 +574,7 @@ |
555 | 575 | if ( this.data[i] === '\n' ) { |
556 | 576 | continue; |
557 | 577 | } |
558 | | - // Auto-initialize as annotated character |
| 578 | + // Auto-convert to annotated character format |
559 | 579 | this.data[i] = [this.data[i]]; |
560 | 580 | } else { |
561 | 581 | // Detect duplicate annotation |
— | — | @@ -581,6 +601,10 @@ |
582 | 602 | while ( ( j = this.indexOfAnnotation( i, annotation ) ) !== -1 ) { |
583 | 603 | this.data[i].splice( j, 1 ); |
584 | 604 | } |
| 605 | + // Auto-convert to plain character format |
| 606 | + if ( this.data[i].length === 1 ) { |
| 607 | + this.data[i] = this.data[i][0]; |
| 608 | + } |
585 | 609 | } |
586 | 610 | } |
587 | 611 | } |
Index: trunk/parsers/wikidom/lib/es/es.ParagraphBlock.js |
— | — | @@ -57,9 +57,10 @@ |
58 | 58 | * @method |
59 | 59 | * @param offset {Integer} Position to insert content at |
60 | 60 | * @param content {Object} Content to insert |
| 61 | + * @param autoAnnotate {Boolean} Content to insert |
61 | 62 | */ |
62 | | -es.ParagraphBlock.prototype.insertContent = function( offset, content ) { |
63 | | - this.content.insert( offset, content ); |
| 63 | +es.ParagraphBlock.prototype.insertContent = function( offset, content, autoAnnotate ) { |
| 64 | + this.content.insert( offset, content, autoAnnotate ); |
64 | 65 | }; |
65 | 66 | |
66 | 67 | /** |
Index: trunk/parsers/wikidom/lib/es/es.Surface.js |
— | — | @@ -693,7 +693,7 @@ |
694 | 694 | if ( typeof location.block === 'undefined' || typeof location.offset === 'undefined' ) { |
695 | 695 | throw 'Invalid selection error. Properties for from and to locations expected.'; |
696 | 696 | } |
697 | | - this.location.block.insertContent( location.offset, content ); |
| 697 | + this.location.block.insertContent( location.offset, content, true ); |
698 | 698 | }; |
699 | 699 | |
700 | 700 | es.Surface.prototype.deleteContent = function( selection ) { |
Index: trunk/parsers/wikidom/lib/es/es.Block.js |
— | — | @@ -108,8 +108,9 @@ |
109 | 109 | * @method |
110 | 110 | * @param offset {Integer} Position to insert content at |
111 | 111 | * @param content {Object} Content to insert |
| 112 | + * @param autoAnnotate {Boolean} Content to insert |
112 | 113 | */ |
113 | | -es.Block.prototype.insertContent = function( offset, content ) { |
| 114 | +es.Block.prototype.insertContent = function( offset, content, autoAnnotate ) { |
114 | 115 | throw 'Block.insertContent not implemented in this subclass.'; |
115 | 116 | }; |
116 | 117 | |