Skip to content

Commit 2901f66

Browse files
committed
Merge pull request #52 from beyounic/cursor-position
Bug: wrong cursor position when merging
2 parents cc5c1e5 + dcb7708 commit 2901f66

File tree

7 files changed

+120
-17
lines changed

7 files changed

+120
-17
lines changed

spec/cursor.spec.js

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@ describe('Cursor', function() {
44
expect(Cursor).toBeDefined();
55
});
66

7-
describe('with a range', function() {
7+
describe('with a collapsed range at the end', function() {
88

99
beforeEach(function() {
10-
this.oneWord = $('<div>foobar</div>')[0];
10+
this.oneWord = $('<div class="'+ config.editableClass +'">foobar</div>')[0];
1111
this.range = rangy.createRange();
1212
this.range.selectNodeContents(this.oneWord);
1313
this.range.collapse(false);
@@ -25,6 +25,32 @@ describe('Cursor', function() {
2525
expect(this.range.startOffset).toEqual(1);
2626
expect(this.range.endOffset).toEqual(1);
2727
});
28+
29+
describe('isAtEnd()', function() {
30+
it('is true', function() {
31+
expect(this.cursor.isAtEnd()).toBe(true);
32+
});
33+
});
34+
35+
describe('isAtBeginning()', function() {
36+
it('is false', function() {
37+
expect(this.cursor.isAtBeginning()).toBe(false);
38+
});
39+
});
40+
41+
describe('save() and restore()', function() {
42+
43+
it('saves and restores the cursor', function() {
44+
this.cursor.save();
45+
46+
// move the cursor so we can check the restore method.
47+
this.cursor.moveAtBeginning();
48+
expect(this.cursor.isAtBeginning()).toBe(true);
49+
50+
this.cursor.restore();
51+
expect(this.cursor.isAtEnd()).toBe(true);
52+
});
53+
});
2854
});
2955

3056
});

spec/parser.spec.js

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,28 @@ describe('Parser', function() {
3131
var linkWithSpan = $('<div><a href="#">foo <span class="important">bar</span></a></div>')[0];
3232

3333

34+
describe('getHost()', function() {
35+
36+
beforeEach(function() {
37+
this.$host = $('<div class="'+ config.editableClass +'""></div>');
38+
});
39+
40+
it('works if host is passed', function() {
41+
expect( parser.getHost(this.$host[0]) ).toBe( this.$host[0] );
42+
});
43+
44+
it('works if a child of host is passed', function() {
45+
this.$host.html('a<em>b</em>');
46+
expect( parser.getHost(this.$host.find('em')[0]) ).toBe( this.$host[0] );
47+
});
48+
49+
it('works if a text node is passed', function() {
50+
this.$host.html('a<em>b</em>');
51+
expect( parser.getHost(this.$host[0].firstChild) ).toBe( this.$host[0] );
52+
});
53+
});
54+
55+
3456
describe('getNodeIndex()', function() {
3557

3658
it('gets element index of link in text', function() {

src/behavior.js

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -119,9 +119,9 @@ var behavior = (function() {
119119
return;
120120

121121
if(container.childNodes.length > 0)
122-
cursor.moveAfter(container.lastChild);
122+
cursor.moveAtEnd(container);
123123
else
124-
cursor.moveBefore(container);
124+
cursor.moveAtBeginning(container);
125125
cursor.setSelection();
126126

127127
fragment = document.createDocumentFragment();
@@ -133,10 +133,11 @@ var behavior = (function() {
133133

134134
merger.parentNode.removeChild(merger);
135135

136-
range = rangeSaveRestore.save(cursor.range);
136+
cursor.save();
137137
content.normalizeTags(container);
138138
content.normalizeSpaces(container);
139-
rangeSaveRestore.restore(element, range);
139+
cursor.restore();
140+
cursor.setSelection();
140141
},
141142

142143
empty: function(element) {
@@ -151,15 +152,15 @@ var behavior = (function() {
151152
switch(direction) {
152153
case 'before':
153154
previous = block.previous(element);
154-
if(previous) {
155-
cursor.moveAfter(previous);
155+
if (previous) {
156+
cursor.moveAtEnd(previous);
156157
cursor.setSelection();
157158
}
158159
break;
159160
case 'after':
160161
next = block.next(element);
161-
if(next) {
162-
cursor.moveBefore(next);
162+
if (next) {
163+
cursor.moveAtBeginning(next);
163164
cursor.setSelection();
164165
}
165166
break;

src/cursor.js

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -118,13 +118,55 @@ var Cursor = (function() {
118118
},
119119

120120
moveBefore: function(element) {
121-
this.range.setStart(element, 0);
122-
this.range.setEnd(element, 0);
121+
this.setHost(element);
122+
this.range.setStartBefore(element);
123+
this.range.setEndBefore(element);
124+
if (this.isSelection) return new Cursor(this.host, this.range);
123125
},
124126

125127
moveAfter: function(element) {
128+
this.setHost(element);
129+
this.range.setStartAfter(element);
130+
this.range.setEndAfter(element);
131+
if (this.isSelection) return new Cursor(this.host, this.range);
132+
},
133+
134+
moveAtBeginning: function(element) {
135+
if (!element) element = this.host;
136+
this.setHost(element);
137+
this.range.selectNodeContents(element);
138+
this.range.collapse(true);
139+
if (this.isSelection) return new Cursor(this.host, this.range);
140+
},
141+
142+
moveAtEnd: function(element) {
143+
if (!element) element = this.host;
144+
this.setHost(element);
126145
this.range.selectNodeContents(element);
127146
this.range.collapse(false);
147+
if (this.isSelection) return new Cursor(this.host, this.range);
148+
},
149+
150+
setHost: function(element) {
151+
this.host = parser.getHost(element);
152+
if (!this.host) {
153+
error('Can not set cursor outside of an editable block');
154+
}
155+
},
156+
157+
save: function() {
158+
this.savedRangeInfo = rangeSaveRestore.save(this.range);
159+
this.savedRangeInfo.host = this.host;
160+
},
161+
162+
restore: function() {
163+
if (this.savedRangeInfo) {
164+
this.host = this.savedRangeInfo.host;
165+
this.range = rangeSaveRestore.restore(this.host, this.savedRangeInfo);
166+
this.savedRangeInfo = undefined;
167+
} else {
168+
error('Could not restore selection');
169+
}
128170
},
129171

130172
equals: function(cursor) {

src/dispatcher.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ var dispatcher = (function() {
5757
return;
5858

5959
cursor = selectionWatcher.getSelection();
60-
if (cursor.isSelection) return;
60+
if (!cursor || cursor.isSelection) return;
6161

6262
// Detect if the browser moved the cursor in the next tick.
6363
// If the cursor stays at its position, fire the switch event.

src/parser.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,19 @@ var parser = (function() {
3131
*/
3232
return {
3333

34+
/**
35+
* Get the editableJS host block of a node.
36+
*
37+
* @method getHost
38+
* @param {DOM Node}
39+
* @return {DOM Node}
40+
*/
41+
getHost: function(node) {
42+
var editableSelector = '.' + config.editableClass;
43+
var hostNode = $(node).closest(editableSelector);
44+
return hostNode.length ? hostNode[0] : undefined;
45+
},
46+
3447
/**
3548
* Get the index of a node.
3649
* So that parent.childNodes[ getIndex(node) ] would return the node again

src/selection-watcher.js

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,9 @@ var selectionWatcher = (function() {
2323
// (on a mac hold down the command key to select multiple ranges)
2424
if (rangySelection.rangeCount) {
2525
var range = rangySelection.getRangeAt(0);
26-
var editableSelector = '.' + config.editableClass;
27-
var hostNode = $(range.commonAncestorContainer).closest(editableSelector);
28-
if (hostNode.length) {
29-
return new RangeContainer(hostNode[0], range);
26+
var hostNode = parser.getHost(range.commonAncestorContainer);
27+
if (hostNode) {
28+
return new RangeContainer(hostNode, range);
3029
}
3130
}
3231

0 commit comments

Comments
 (0)