Skip to content

Commit

Permalink
Merge pull request #762 from yabwe/4-12-anchor-fix
Browse files Browse the repository at this point in the history
Porting #761 back to v4.12.x branch
  • Loading branch information
nmielnik committed Aug 10, 2015
2 parents 5423db4 + f635dc1 commit 73372ad
Show file tree
Hide file tree
Showing 4 changed files with 100 additions and 11 deletions.
80 changes: 79 additions & 1 deletion spec/anchor.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -316,7 +316,85 @@ describe('Anchor Button TestCase', function () {
expect(link.classList.contains('btn-default')).toBe(true);
});

it('should not select empty paragraphs when link is created at beginning of paragraph', function () {
// https://github.com/yabwe/medium-editor/issues/757
it('should not select empty paragraphs when link is created at beginning of paragraph after empty paragraphs', function () {
spyOn(MediumEditor.prototype, 'createLink').and.callThrough();
this.el.innerHTML = '<p>Some text</p><p><br/></p><p><br/></p><p>link text more text</p>';
var editor = this.newMediumEditor('.editor'),
lastP = this.el.lastChild,
anchorExtension = editor.getExtensionByName('anchor');

// Select the text 'link text' in the last paragraph
MediumEditor.selection.select(document, lastP.firstChild, 0, lastP.firstChild, 'link text'.length);
fireEvent(editor.elements[0], 'focus');
jasmine.clock().tick(1);

// Click the 'anchor' button in the toolbar
fireEvent(editor.toolbar.getToolbarElement().querySelector('[data-action="createLink"]'), 'click');

// Input a url and save
var input = anchorExtension.getInput();
input.value = 'http://www.example.com';
fireEvent(input, 'keyup', {
keyCode: Util.keyCode.ENTER
});

expect(editor.createLink).toHaveBeenCalledWith({
url: 'http://www.example.com',
target: '_self'
});

// Make sure the <p> wasn't removed, and the <a> was added to the end
expect(this.el.lastChild).toBe(lastP);
expect(lastP.firstChild.nodeName.toLowerCase()).toBe('a');

// Make sure selection is only the link
var range = window.getSelection().getRangeAt(0);
expect(MediumEditor.util.isDescendant(lastP, range.startContainer, true)).toBe(true, 'The start of the selection is incorrect');
expect(range.startOffset).toBe(0);
expect(MediumEditor.util.isDescendant(lastP.firstChild, range.endContainer, true)).toBe(true, 'The end of the selection is not contained within the link');
});

// https://github.com/yabwe/medium-editor/issues/757
it('should not select empty paragraphs when link is created at beginning of paragraph after another paragraph', function () {
spyOn(MediumEditor.prototype, 'createLink').and.callThrough();
this.el.innerHTML = '<p>Some text</p><p>link text more text</p>';
var editor = this.newMediumEditor('.editor'),
lastP = this.el.lastChild,
anchorExtension = editor.getExtensionByName('anchor');

// Select the text 'link text' in the last paragraph
MediumEditor.selection.select(document, lastP.firstChild, 0, lastP.firstChild, 'link text'.length);
fireEvent(editor.elements[0], 'focus');
jasmine.clock().tick(1);

// Click the 'anchor' button in the toolbar
fireEvent(editor.toolbar.getToolbarElement().querySelector('[data-action="createLink"]'), 'click');

// Input a url and save
var input = anchorExtension.getInput();
input.value = 'http://www.example.com';
fireEvent(input, 'keyup', {
keyCode: Util.keyCode.ENTER
});

expect(editor.createLink).toHaveBeenCalledWith({
url: 'http://www.example.com',
target: '_self'
});

// Make sure the <p> wasn't removed, and the <a> was added to the end
expect(this.el.lastChild).toBe(lastP);
expect(lastP.firstChild.nodeName.toLowerCase()).toBe('a');

// Make sure selection is only the link
var range = window.getSelection().getRangeAt(0);
expect(MediumEditor.util.isDescendant(lastP, range.startContainer, true)).toBe(true, 'The start of the selection is incorrect');
expect(range.startOffset).toBe(0);
expect(MediumEditor.util.isDescendant(lastP.firstChild, range.endContainer, true)).toBe(true, 'The end of the selection is not contained within the link');
});

it('should not remove the <p> container when adding a link inside a top-level <p> with a single text-node child', function () {
spyOn(MediumEditor.prototype, 'createLink').and.callThrough();
this.el.innerHTML = '<p>Some text</p><p><br/></p><p><br/></p><p>link text more text</p>';
var editor = this.newMediumEditor('.editor'),
Expand Down
10 changes: 10 additions & 0 deletions spec/selection.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,16 @@ describe('Selection TestCase', function () {
expect(exportedSelection.emptyBlocksIndex).toEqual(undefined);
});

it('should export a position indicating the cursor is at the beginning of a paragraph', function () {
this.el.innerHTML = '<p><span>www.google.com</span></p><p><b>Whatever</b></p>';
var editor = this.newMediumEditor('.editor', {
buttons: ['italic', 'underline', 'strikethrough']
});
placeCursorInsideElement(editor.elements[0].querySelector('b'), 0); // beginning of <b> tag
var exportedSelection = editor.exportSelection();
expect(exportedSelection.emptyBlocksIndex).toEqual(0);
});

it('should not export a position indicating the cursor is after an empty paragraph', function () {
this.el.innerHTML = '<p><span>www.google.com</span></p><p><br /></p>' +
'<p class="target">Whatever</p>';
Expand Down
6 changes: 3 additions & 3 deletions src/js/core.js
Original file line number Diff line number Diff line change
Expand Up @@ -924,7 +924,7 @@ function MediumEditor(elements, options) {
this.elements[editableElementIndex],
range.startContainer,
range.startOffset);
if (emptyBlocksIndex !== 0) {
if (emptyBlocksIndex !== -1) {
selectionState.emptyBlocksIndex = emptyBlocksIndex;
}
}
Expand Down Expand Up @@ -1002,11 +1002,11 @@ function MediumEditor(elements, options) {
}
}

if (inSelectionState.emptyBlocksIndex) {
if (typeof inSelectionState.emptyBlocksIndex !== 'undefined') {
var targetNode = Util.getBlockContainer(range.startContainer),
index = 0;
// Skip over empty blocks until we hit the block we want the selection to be in
while (index < inSelectionState.emptyBlocksIndex && targetNode.nextSibling) {
while ((index === 0 || index < inSelectionState.emptyBlocksIndex) && targetNode.nextSibling) {
targetNode = targetNode.nextSibling;
index++;
// If we find a non-empty block, ignore the emptyBlocksIndex and just put selection here
Expand Down
15 changes: 8 additions & 7 deletions src/js/selection.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,23 +67,24 @@ var Selection;
return range;
},

// Returns 0 unless the cursor is within or preceded by empty paragraphs/blocks,
// in which case it returns the count of such preceding paragraphs, including
// the empty paragraph in which the cursor itself may be embedded.
// Returns -1 unless the cursor is at the beginning of a paragraph/block
// If the paragraph/block is preceeded by empty paragraphs/block (with no text)
// it will return the number of empty paragraphs before the cursor.
// Otherwise, it will return 0, which indicates the cursor is at the beginning
// of a paragraph/block, and not at the end of the paragraph/block before it
getIndexRelativeToAdjacentEmptyBlocks: function (doc, root, cursorContainer, cursorOffset) {
// If there is text in front of the cursor, that means there isn't only empty blocks before it
if (cursorContainer.nodeType === 3 && cursorOffset > 0) {
return 0;
if (cursorContainer.textContent.length > 0 && cursorOffset > 0) {
return -1;
}

// Check if the block that contains the cursor has any other text in front of the cursor
var node = cursorContainer;
if (node.nodeType !== 3) {
//node = cursorContainer.childNodes.length === cursorOffset ? null : cursorContainer.childNodes[cursorOffset];
node = cursorContainer.childNodes[cursorOffset];
}
if (node && !Util.isElementAtBeginningOfBlock(node)) {
return 0;
return -1;
}

// Walk over block elements, counting number of empty blocks between last piece of text
Expand Down

0 comments on commit 73372ad

Please sign in to comment.