Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

medium-editor

Package Overview
Dependencies
Maintainers
5
Versions
125
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

medium-editor - npm Package Compare versions

Comparing version 5.11.0 to 5.12.0

.node-version

9

CHANGES.md

@@ -0,1 +1,9 @@

5.12.0 / 2015-12-15
==================
* Fix issue with image-only selections
* Trim src when using the image toolbar button
* Fix auto linking with comments
* Documented the process of releasing a new version
5.11.0 / 2015-12-05

@@ -12,2 +20,3 @@ ==================

5.10.0 / 2015-10-30

@@ -14,0 +23,0 @@ ==================

2

package.json
{
"name": "medium-editor",
"version": "5.11.0",
"version": "5.12.0",
"author": "Davi Ferreira <hi@daviferreira.com>",

@@ -5,0 +5,0 @@ "contributors": [

@@ -559,4 +559,4 @@ /*global fireEvent, selectElementContents,

// https://github.com/yabwe/medium-editor/issues/803
it('should update link on image', function () {
this.el.innerHTML = '<a><img src="http://image.test.com"></a>';
it('should update the href of a link containing only an image', function () {
this.el.innerHTML = '<a href="#"><img src="../demo/img/medium-editor.jpg"></a>';

@@ -573,3 +573,3 @@ spyOn(MediumEditor.prototype, 'createLink').and.callThrough();

input = editor.getExtensionByName('anchor').getInput();
input.value = 'test';
input.value = 'http://www.google.com';
fireEvent(input, 'keyup', {

@@ -579,3 +579,3 @@ keyCode: MediumEditor.util.keyCode.ENTER

expect(editor.createLink).toHaveBeenCalled();
expect(this.el.innerHTML).toContain('<a href="test"><img src="http://image.test.com"></a>');
expect(this.el.innerHTML).toContain('<a href="http://www.google.com"><img src="../demo/img/medium-editor.jpg"></a>');
});

@@ -582,0 +582,0 @@ });

@@ -739,3 +739,3 @@ /*global fireEvent, selectElementContentsAndFire,

this.el.innerHTML = '<span id="span-image">http://i.imgur.com/twlXfUq.jpg</span>';
this.el.innerHTML = '<span id="span-image">http://i.imgur.com/twlXfUq.jpg \n\n</span>';
selectElementContentsAndFire(document.getElementById('span-image'));

@@ -746,3 +746,3 @@

expect(this.el.innerHTML).toContain('<img src="http://i.imgur.com/twlXfUq.jpg">');
expect(document.execCommand).toHaveBeenCalledWith('insertImage', false, window.getSelection());
expect(document.execCommand).toHaveBeenCalledWith('insertImage', false, 'http://i.imgur.com/twlXfUq.jpg');
});

@@ -749,0 +749,0 @@ });

@@ -0,1 +1,3 @@

/*global fireEvent, selectElementContents */
describe('Core-API', function () {

@@ -54,2 +56,49 @@ 'use strict';

});
describe('saveSelection/restoreSelection', function () {
it('should be applicable if html changes but text does not', function () {
this.el.innerHTML = 'lorem <i>ipsum</i> dolor';
var editor = this.newMediumEditor('.editor', {
toolbar: {
buttons: ['italic', 'underline', 'strikethrough']
}
}),
toolbar = editor.getExtensionByName('toolbar'),
button,
regex;
// Save selection around <i> tag
selectElementContents(editor.elements[0].querySelector('i'));
editor.saveSelection();
// Underline entire element
selectElementContents(editor.elements[0]);
button = toolbar.getToolbarElement().querySelector('[data-action="underline"]');
fireEvent(button, 'click');
// Restore selection back to <i> tag and add a <strike> tag
regex = new RegExp('^<u>lorem (<i><strike>|<strike><i>)ipsum(</i></strike>|</strike></i>) dolor</u>$');
editor.restoreSelection();
button = toolbar.getToolbarElement().querySelector('[data-action="strikethrough"]');
fireEvent(button, 'click');
expect(regex.test(editor.elements[0].innerHTML)).toBe(true);
});
});
describe('exportSelection', function () {
it('should have an index in the exported selection when it is in the second contenteditable', function () {
this.createElement('div', 'editor', 'lorem <i>ipsum</i> dolor');
var editor = this.newMediumEditor('.editor', {
toolbar: {
buttons: ['italic', 'underline', 'strikethrough']
}
});
selectElementContents(editor.elements[1].querySelector('i'));
var exportedSelection = editor.exportSelection();
expect(Object.keys(exportedSelection).sort()).toEqual(['editableElementIndex', 'end', 'start']);
expect(exportedSelection.editableElementIndex).toEqual(1);
});
});
});

@@ -1,4 +0,2 @@

/*global fireEvent, selectElementContents,
selectElementContentsAndFire,
placeCursorInsideElement */
/*global selectElementContents, placeCursorInsideElement */

@@ -23,89 +21,7 @@ describe('MediumEditor.selection TestCase', function () {

describe('Export/Import Selection', function () {
it('should be able to import an exported selection', function () {
this.el.innerHTML = 'lorem <i>ipsum</i> dolor';
var editor = this.newMediumEditor('.editor', {
toolbar: {
buttons: ['italic', 'underline', 'strikethrough']
}
});
selectElementContents(editor.elements[0].querySelector('i'));
var exportedSelection = editor.exportSelection();
expect(Object.keys(exportedSelection).sort()).toEqual(['end', 'start']);
selectElementContents(editor.elements[0]);
expect(exportedSelection).not.toEqual(editor.exportSelection());
editor.importSelection(exportedSelection);
expect(exportedSelection).toEqual(editor.exportSelection());
});
it('should import an exported selection outside any anchor tag', function () {
this.el.innerHTML = '<p id=1>Hello world: <a href="#">http://www.example.com</a></p><p id=2><br></p>';
var editor = this.newMediumEditor('.editor', {
toolbar: {
buttons: ['italic', 'underline', 'strikethrough']
}
}),
link = editor.elements[0].getElementsByTagName('a')[0];
placeCursorInsideElement(link.childNodes[0], link.childNodes[0].nodeValue.length);
var exportedSelection = editor.exportSelection();
editor.importSelection(exportedSelection, true);
var range = window.getSelection().getRangeAt(0),
node = range.startContainer;
// Even though we set the range to use the P tag as the start container, Safari normalizes the range
// down to the text node. Setting the range to use the P tag for the start is necessary to support
// MSIE, where it removes the link when the cursor is placed at the end of the text node in the anchor.
while (node.nodeName.toLowerCase() !== 'p') {
node = node.parentNode;
}
expect(node.nodeName.toLowerCase()).toBe('p');
expect(node.getAttribute('id')).toBe('1');
});
// https://github.com/yabwe/medium-editor/issues/738
it('should import an exported non-collapsed selection after an empty paragraph', function () {
this.el.innerHTML = '<p>This is <a href="#">a link</a></p><p><br/></p><p>not a link</p>';
var editor = this.newMediumEditor('.editor'),
lastTextNode = this.el.childNodes[2].firstChild;
MediumEditor.selection.select(document, lastTextNode, 0, lastTextNode, 'not a link'.length);
var exportedSelection = editor.exportSelection();
expect(exportedSelection).toEqual({ start: 14, end: 24, emptyBlocksIndex: 2 });
editor.importSelection(exportedSelection);
var range = window.getSelection().getRangeAt(0);
expect(range.startContainer === lastTextNode || range.startContainer === lastTextNode.parentNode)
.toBe(true, 'The selection is starting at the wrong element');
expect(range.startOffset).toBe(0, 'The start of the selection is not at the beginning of the text node');
expect(range.endContainer === lastTextNode || range.endContainer === lastTextNode.parentNode)
.toBe(true, 'The selection is ending at the wrong element');
expect(range.endOffset).toBe('not a link'.length, 'The end of the selection is not at the end of the text node');
});
it('should have an index in the exported selection when it is in the second contenteditable', function () {
this.createElement('div', 'editor', 'lorem <i>ipsum</i> dolor');
var editor = this.newMediumEditor('.editor', {
toolbar: {
buttons: ['italic', 'underline', 'strikethrough']
}
});
selectElementContents(editor.elements[1].querySelector('i'));
var exportedSelection = editor.exportSelection();
expect(Object.keys(exportedSelection).sort()).toEqual(['editableElementIndex', 'end', 'start']);
expect(exportedSelection.editableElementIndex).toEqual(1);
});
describe('exportSelection', function () {
it('should not export a position indicating the cursor is before an empty paragraph', function () {
this.el.innerHTML = '<p><span>www.google.com</span></p><p><br /></p><p>Whatever</p>';
var editor = this.newMediumEditor('.editor', {
buttons: ['italic', 'underline', 'strikethrough']
});
placeCursorInsideElement(editor.elements[0].querySelector('span'), 1); // end of first span
var exportedSelection = editor.exportSelection();
placeCursorInsideElement(this.el.querySelector('span'), 1); // end of first span
var exportedSelection = MediumEditor.selection.exportSelection(this.el, document);
expect(exportedSelection.emptyBlocksIndex).toEqual(undefined);

@@ -116,7 +32,4 @@ });

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();
placeCursorInsideElement(this.el.querySelector('b'), 0); // beginning of <b> tag
var exportedSelection = MediumEditor.selection.exportSelection(this.el, document);
expect(exportedSelection.emptyBlocksIndex).toEqual(0);

@@ -128,8 +41,5 @@ });

'<p class="target">Whatever</p>';
var editor = this.newMediumEditor('.editor', {
buttons: ['italic', 'underline', 'strikethrough']
});
// After the 'W' in whatever
placeCursorInsideElement(editor.elements[0].querySelector('p.target').firstChild, 1);
var exportedSelection = editor.exportSelection();
placeCursorInsideElement(this.el.querySelector('p.target').firstChild, 1);
var exportedSelection = MediumEditor.selection.exportSelection(this.el, document);
expect(exportedSelection.emptyBlocksIndex).toEqual(undefined);

@@ -142,8 +52,5 @@ });

'<p>What<span class="target">ever</span></p>';
var editor = this.newMediumEditor('.editor', {
buttons: ['italic', 'underline', 'strikethrough']
});
// Before the 'e' in whatever
placeCursorInsideElement(editor.elements[0].querySelector('span.target').firstChild, 0);
var exportedSelection = editor.exportSelection();
placeCursorInsideElement(this.el.querySelector('span.target').firstChild, 0);
var exportedSelection = MediumEditor.selection.exportSelection(this.el, document);
expect(exportedSelection.emptyBlocksIndex).toEqual(undefined);

@@ -156,8 +63,5 @@ });

'<p>What<span class="target">ever</span></p>';
var editor = this.newMediumEditor('.editor', {
buttons: ['italic', 'underline', 'strikethrough']
});
// Before the 'e' in whatever
placeCursorInsideElement(editor.elements[0].querySelector('span.target'), 0);
var exportedSelection = editor.exportSelection();
placeCursorInsideElement(this.el.querySelector('span.target'), 0);
var exportedSelection = MediumEditor.selection.exportSelection(this.el, document);
expect(exportedSelection.emptyBlocksIndex).toEqual(undefined);

@@ -168,7 +72,4 @@ });

this.el.innerHTML = '<p><span>www.google.com</span></p><p><br /></p><p>Whatever</p>';
var editor = this.newMediumEditor('.editor', {
buttons: ['italic', 'underline', 'strikethrough']
});
placeCursorInsideElement(editor.elements[0].getElementsByTagName('p')[1], 0);
var exportedSelection = editor.exportSelection();
placeCursorInsideElement(this.el.getElementsByTagName('p')[1], 0);
var exportedSelection = MediumEditor.selection.exportSelection(this.el, document);
expect(exportedSelection.emptyBlocksIndex).toEqual(1);

@@ -179,7 +80,4 @@ });

this.el.innerHTML = '<p><span>www.google.com</span></p><p><br /></p><p>Whatever</p>';
var editor = this.newMediumEditor('.editor', {
buttons: ['italic', 'underline', 'strikethrough']
});
placeCursorInsideElement(editor.elements[0].getElementsByTagName('p')[2], 0);
var exportedSelection = editor.exportSelection();
placeCursorInsideElement(this.el.getElementsByTagName('p')[2], 0);
var exportedSelection = MediumEditor.selection.exportSelection(this.el, document);
expect(exportedSelection.emptyBlocksIndex).toEqual(2);

@@ -190,23 +88,112 @@ });

this.el.innerHTML = '<p><span>www.google.com</span></p><h1><br /></h1><h2><br /></h2><p>Whatever</p>';
var editor = this.newMediumEditor('.editor', {
buttons: ['italic', 'underline', 'strikethrough']
});
placeCursorInsideElement(editor.elements[0].querySelector('h2'), 0);
var exportedSelection = editor.exportSelection();
placeCursorInsideElement(this.el.querySelector('h2'), 0);
var exportedSelection = MediumEditor.selection.exportSelection(this.el, document);
expect(exportedSelection.emptyBlocksIndex).toEqual(2);
});
it('should export a selection that specifies an image is the selection', function () {
this.el.innerHTML = '<p>lorem ipsum <a href="#"><img src="../demo/img/medium-editor.jpg" /></a> dolor</p>';
selectElementContents(this.el.querySelector('a'));
var exportedSelection = MediumEditor.selection.exportSelection(this.el, document);
expect(exportedSelection.trailingImageCount).toBe(1);
expect(exportedSelection.start).toBe(12);
expect(exportedSelection.end).toBe(12);
});
it('should export a selection that can be imported when the selection starts with an image', function () {
this.el.innerHTML = '<p>lorem ipsum <a href="#"><img src="../demo/img/medium-editor.jpg" />img</a> dolor</p>';
selectElementContents(this.el.querySelector('a'));
var exportedSelection = MediumEditor.selection.exportSelection(this.el, document);
expect(exportedSelection.trailingImageCount).toBe(undefined);
expect(exportedSelection.start).toBe(12);
expect(exportedSelection.end).toBe(15);
selectElementContents(this.el);
MediumEditor.selection.importSelection(exportedSelection, this.el, document);
var range = window.getSelection().getRangeAt(0);
expect(range.toString()).toBe('img');
if (range.startContainer.nodeName.toLowerCase() === 'a') {
expect(range.startContainer).toBe(this.el.querySelector('a'));
expect(range.startOffset).toBe(0);
} else {
expect(range.startContainer.nextSibling).toBe(this.el.querySelector('a'));
expect(range.startOffset).toBe(12);
}
});
it('should export a selection that specifies an image is at the end of a selection', function () {
this.el.innerHTML = '<p>lorem ipsum <a href="#">img<b><img src="../demo/img/medium-editor.jpg" /><img src="../demo/img/roman.jpg" /></b></a> dolor</p>';
selectElementContents(this.el.querySelector('a'));
var exportedSelection = MediumEditor.selection.exportSelection(this.el, document);
expect(exportedSelection.trailingImageCount).toBe(2);
expect(exportedSelection.start).toBe(12);
expect(exportedSelection.end).toBe(15);
});
});
describe('importSelection', function () {
it('should be able to import an exported selection', function () {
this.el.innerHTML = 'lorem <i>ipsum</i> dolor';
selectElementContents(this.el.querySelector('i'));
var exportedSelection = MediumEditor.selection.exportSelection(this.el, document);
expect(Object.keys(exportedSelection).sort()).toEqual(['end', 'start']);
selectElementContents(this.el);
expect(exportedSelection).not.toEqual(MediumEditor.selection.exportSelection(this.el, document));
MediumEditor.selection.importSelection(exportedSelection, this.el, document);
expect(exportedSelection).toEqual(MediumEditor.selection.exportSelection(this.el, document));
});
it('should import an exported selection outside any anchor tag', function () {
this.el.innerHTML = '<p id=1>Hello world: <a href="#">http://www.example.com</a></p><p id=2><br></p>';
var link = this.el.getElementsByTagName('a')[0];
placeCursorInsideElement(link.childNodes[0], link.childNodes[0].nodeValue.length);
var exportedSelection = MediumEditor.selection.exportSelection(this.el, document);
MediumEditor.selection.importSelection(exportedSelection, this.el, document, true);
var range = window.getSelection().getRangeAt(0),
node = range.startContainer;
// Even though we set the range to use the P tag as the start container, Safari normalizes the range
// down to the text node. Setting the range to use the P tag for the start is necessary to support
// MSIE, where it removes the link when the cursor is placed at the end of the text node in the anchor.
while (node.nodeName.toLowerCase() !== 'p') {
node = node.parentNode;
}
expect(node.nodeName.toLowerCase()).toBe('p');
expect(node.getAttribute('id')).toBe('1');
});
// https://github.com/yabwe/medium-editor/issues/738
it('should import an exported non-collapsed selection after an empty paragraph', function () {
this.el.innerHTML = '<p>This is <a href="#">a link</a></p><p><br/></p><p>not a link</p>';
var lastTextNode = this.el.childNodes[2].firstChild;
MediumEditor.selection.select(document, lastTextNode, 0, lastTextNode, 'not a link'.length);
var exportedSelection = MediumEditor.selection.exportSelection(this.el, document);
expect(exportedSelection).toEqual({ start: 14, end: 24, emptyBlocksIndex: 2 });
MediumEditor.selection.importSelection(exportedSelection, this.el, document);
var range = window.getSelection().getRangeAt(0);
expect(range.startContainer === lastTextNode || range.startContainer === lastTextNode.parentNode)
.toBe(true, 'The selection is starting at the wrong element');
expect(range.startOffset).toBe(0, 'The start of the selection is not at the beginning of the text node');
expect(range.endContainer === lastTextNode || range.endContainer === lastTextNode.parentNode)
.toBe(true, 'The selection is ending at the wrong element');
expect(range.endOffset).toBe('not a link'.length, 'The end of the selection is not at the end of the text node');
});
it('should import a position with the cursor in an empty paragraph', function () {
this.el.innerHTML = '<p><span>www.google.com</span></p><p><br /></p><p>Whatever</p>';
var editor = this.newMediumEditor('.editor', {
buttons: ['italic', 'underline', 'strikethrough']
});
editor.importSelection({
MediumEditor.selection.importSelection({
'start': 14,
'end': 14,
'emptyBlocksIndex': 1
});
}, this.el, document);
var startParagraph = MediumEditor.util.getClosestTag(window.getSelection().getRangeAt(0).startContainer, 'p');
expect(startParagraph).toBe(editor.elements[0].getElementsByTagName('p')[1], 'empty paragraph');
expect(startParagraph).toBe(this.el.getElementsByTagName('p')[1], 'empty paragraph');
});

@@ -216,21 +203,15 @@

this.el.innerHTML = '<p><span>www.google.com</span></p><p><br /></p><p>Whatever</p>';
var editor = this.newMediumEditor('.editor', {
buttons: ['italic', 'underline', 'strikethrough']
});
editor.importSelection({
MediumEditor.selection.importSelection({
'start': 14,
'end': 14,
'emptyBlocksIndex': 2
});
}, this.el, document);
var startParagraph = MediumEditor.util.getClosestTag(window.getSelection().getRangeAt(0).startContainer, 'p');
expect(startParagraph).toBe(editor.elements[0].getElementsByTagName('p')[2], 'paragraph after empty paragraph');
expect(startParagraph).toBe(this.el.getElementsByTagName('p')[2], 'paragraph after empty paragraph');
});
it('should import a position with the cursor after an empty paragraph when there are multipled editable elements', function () {
this.createElement('div', 'editor', '<p><span>www.google.com</span></p><p><br /></p><p>Whatever</p>');
var editor = this.newMediumEditor('.editor', {
buttons: ['italic', 'underline', 'strikethrough']
});
editor.importSelection({
var el = this.createElement('div', 'editor', '<p><span>www.google.com</span></p><p><br /></p><p>Whatever</p>');
MediumEditor.selection.importSelection({
'start': 14,

@@ -240,6 +221,6 @@ 'end': 14,

'emptyBlocksIndex': 2
});
}, el, document);
var startParagraph = MediumEditor.util.getClosestTag(window.getSelection().getRangeAt(0).startContainer, 'p');
expect(startParagraph).toBe(editor.elements[1].getElementsByTagName('p')[2], 'paragraph after empty paragraph');
expect(startParagraph).toBe(el.getElementsByTagName('p')[2], 'paragraph after empty paragraph');
});

@@ -249,13 +230,10 @@

this.el.innerHTML = '<p><span>www.google.com</span></p><h1><br /></h1><h2><br /></h2><p>Whatever</p>';
var editor = this.newMediumEditor('.editor', {
buttons: ['italic', 'underline', 'strikethrough']
});
editor.importSelection({
MediumEditor.selection.importSelection({
'start': 14,
'end': 14,
'emptyBlocksIndex': 2
});
}, this.el, document);
var startParagraph = MediumEditor.util.getClosestTag(window.getSelection().getRangeAt(0).startContainer, 'h2');
expect(startParagraph).toBe(editor.elements[0].querySelector('h2'), 'block element after empty block element');
expect(startParagraph).toBe(this.el.querySelector('h2'), 'block element after empty block element');
});

@@ -265,13 +243,10 @@

this.el.innerHTML = '<blockquote><p><span>www.google.com</span></p></blockquote><h1><br /></h1><h2><br /></h2><p>Whatever</p>';
var editor = this.newMediumEditor('.editor', {
buttons: ['italic', 'underline', 'strikethrough']
});
editor.importSelection({
MediumEditor.selection.importSelection({
'start': 14,
'end': 14,
'emptyBlocksIndex': 2
});
}, this.el, document);
var startParagraph = MediumEditor.util.getClosestTag(window.getSelection().getRangeAt(0).startContainer, 'h2');
expect(startParagraph).toBe(editor.elements[0].querySelector('h2'), 'block element after empty block element');
expect(startParagraph).toBe(this.el.querySelector('h2'), 'block element after empty block element');
});

@@ -281,13 +256,10 @@

this.el.innerHTML = '<p><span>www.google.com</span></p><h1><br /></h1><h2><br /></h2><p><b><i>Whatever</i></b></p>';
var editor = this.newMediumEditor('.editor', {
buttons: ['italic', 'underline', 'strikethrough']
});
editor.importSelection({
MediumEditor.selection.importSelection({
'start': 14,
'end': 14,
'emptyBlocksIndex': 3
});
}, this.el, document);
var innerElement = window.getSelection().getRangeAt(0).startContainer;
expect(MediumEditor.util.isDescendant(editor.elements[0].querySelector('i'), innerElement, true)).toBe(true, 'nested inline elment inside block element after empty block element');
expect(MediumEditor.util.isDescendant(this.el.querySelector('i'), innerElement, true)).toBe(true, 'nested inline elment inside block element after empty block element');
});

@@ -298,10 +270,7 @@

this.el.innerHTML = '<p>Hello</p><p><' + tagName + ' /></p><p>World<p>';
var editor = this.newMediumEditor('.editor', {
buttons: ['italic', 'underline', 'strikethrough']
});
editor.importSelection({
MediumEditor.selection.importSelection({
'start': 5,
'end': 5,
'emptyBlocksIndex': 1
});
}, this.el, document);

@@ -320,14 +289,11 @@ var innerElement = window.getSelection().getRangeAt(0).startContainer;

'<tbody><tr><td>Body</td></tr></tbody></table><p>World<p>';
var editor = this.newMediumEditor('.editor', {
buttons: ['italic', 'underline', 'strikethrough']
});
editor.importSelection({
MediumEditor.selection.importSelection({
'start': 5,
'end': 5,
'emptyBlocksIndex': 1
});
}, this.el, document);
var innerElement = window.getSelection().getRangeAt(0).startContainer;
// The behavior varies from browser to browser for this case, some select TH, some #textNode
expect(MediumEditor.util.isDescendant(editor.elements[0].querySelector('th'), innerElement, true))
expect(MediumEditor.util.isDescendant(this.el.querySelector('th'), innerElement, true))
.toBe(true, 'expect selection to be of TH or a descendant');

@@ -339,15 +305,12 @@ expect(innerElement).toBe(window.getSelection().getRangeAt(0).endContainer);

this.el.innerHTML = '<p><span>www.google.com</span></p><h1><br /></h1><h2>Not Empty</h2><p><b><i>Whatever</i></b></p>';
var editor = this.newMediumEditor('.editor', {
buttons: ['italic', 'underline', 'strikethrough']
});
// Import a selection that indicates the text should be at the end of the 'www.google.com' word, but in the 3rd paragraph (at the beginning of 'Whatever')
editor.importSelection({
MediumEditor.selection.importSelection({
'start': 14,
'end': 14,
'emptyBlocksIndex': 3
});
}, this.el, document);
var innerElement = window.getSelection().getRangeAt(0).startContainer;
expect(MediumEditor.util.isDescendant(editor.elements[0].querySelectorAll('p')[1], innerElement, true)).toBe(false, 'moved selection beyond non-empty block element');
expect(MediumEditor.util.isDescendant(editor.elements[0].querySelector('h2'), innerElement, true)).toBe(true, 'moved selection to element to incorrect block element');
expect(MediumEditor.util.isDescendant(this.el.querySelectorAll('p')[1], innerElement, true)).toBe(false, 'moved selection beyond non-empty block element');
expect(MediumEditor.util.isDescendant(this.el.querySelector('h2'), innerElement, true)).toBe(true, 'moved selection to element to incorrect block element');
});

@@ -362,4 +325,3 @@

'</ul>';
var editor = this.newMediumEditor('.editor'),
lastLi = this.el.querySelectorAll('ul > li')[2];
var lastLi = this.el.querySelectorAll('ul > li')[2];

@@ -369,6 +331,6 @@ // Select the <li> with 'target'

var selectionData = editor.exportSelection();
var selectionData = MediumEditor.selection.exportSelection(this.el, document);
expect(selectionData.emptyBlocksIndex).toBe(0);
editor.importSelection(selectionData);
MediumEditor.selection.importSelection(selectionData, this.el, document);
var range = window.getSelection().getRangeAt(0);

@@ -380,117 +342,32 @@

});
});
describe('Saving Selection', function () {
it('should be applicable if html changes but text does not', function () {
this.el.innerHTML = 'lorem <i>ipsum</i> dolor';
var editor = this.newMediumEditor('.editor', {
toolbar: {
buttons: ['italic', 'underline', 'strikethrough']
}
}),
toolbar = editor.getExtensionByName('toolbar'),
button,
regex;
// Save selection around <i> tag
selectElementContents(editor.elements[0].querySelector('i'));
editor.saveSelection();
// Underline entire element
selectElementContents(editor.elements[0]);
button = toolbar.getToolbarElement().querySelector('[data-action="underline"]');
fireEvent(button, 'click');
// Restore selection back to <i> tag and add a <strike> tag
regex = new RegExp('^<u>lorem (<i><strike>|<strike><i>)ipsum(</i></strike>|</strike></i>) dolor</u>$');
editor.restoreSelection();
button = toolbar.getToolbarElement().querySelector('[data-action="strikethrough"]');
fireEvent(button, 'click');
expect(regex.test(editor.elements[0].innerHTML)).toBe(true);
it('should support a selection that specifies an image is the selection', function () {
this.el.innerHTML = '<p>lorem ipsum <a href="#"><img src="../demo/img/medium-editor.jpg" /></a> dolor</p>';
MediumEditor.selection.importSelection({ start: 12, end: 12, trailingImageCount: 1 }, this.el, document);
var range = window.getSelection().getRangeAt(0);
expect(range.toString()).toBe('');
expect(MediumEditor.util.isDescendant(range.endContainer, this.el.querySelector('img'), true)).toBe(true, 'the image is not within the selection');
});
});
describe('CheckSelection', function () {
it('should check for selection on mouseup event', function () {
spyOn(MediumEditor.extensions.toolbar.prototype, 'checkState');
var editor = this.newMediumEditor('.editor'),
toolbar = editor.getExtensionByName('toolbar');
fireEvent(editor.elements[0], 'mouseup');
expect(toolbar.checkState).toHaveBeenCalled();
it('should support a selection that starts with an image', function () {
this.el.innerHTML = '<p>lorem ipsum <a href="#"><img src="../demo/img/medium-editor.jpg" />img</a> dolor</p>';
MediumEditor.selection.importSelection({ start: 12, end: 15 }, this.el, document);
var range = window.getSelection().getRangeAt(0);
expect(range.toString()).toBe('img');
if (range.startContainer.nodeName.toLowerCase() === 'a') {
expect(range.startContainer).toBe(this.el.querySelector('a'));
expect(range.startOffset).toBe(0);
} else {
expect(range.startContainer.nextSibling).toBe(this.el.querySelector('a'));
expect(range.startOffset).toBe(12);
}
});
it('should check for selection on keyup', function () {
spyOn(MediumEditor.extensions.toolbar.prototype, 'checkState');
var editor = this.newMediumEditor('.editor'),
toolbar = editor.getExtensionByName('toolbar');
fireEvent(editor.elements[0], 'keyup');
expect(toolbar.checkState).toHaveBeenCalled();
it('should support a selection that ends with an image', function () {
this.el.innerHTML = '<p>lorem ipsum <a href="#">img<img src="../demo/img/medium-editor.jpg" /><img src="../img/roman.jpg" /></a> dolor</p>';
MediumEditor.selection.importSelection({ start: 12, end: 15, trailingImageCount: 2 }, this.el, document);
var range = window.getSelection().getRangeAt(0);
expect(range.toString()).toBe('img');
expect(MediumEditor.util.isDescendant(range.endContainer, this.el.querySelector('img'), true)).toBe(true, 'the image is not within the selection');
});
it('should hide the toolbar if selection is empty', function () {
spyOn(MediumEditor.extensions.toolbar.prototype, 'setToolbarPosition').and.callThrough();
spyOn(MediumEditor.extensions.toolbar.prototype, 'setToolbarButtonStates').and.callThrough();
spyOn(MediumEditor.extensions.toolbar.prototype, 'showAndUpdateToolbar').and.callThrough();
var editor = this.newMediumEditor('.editor'),
toolbar = editor.getExtensionByName('toolbar');
toolbar.getToolbarElement().style.display = 'block';
toolbar.getToolbarElement().classList.add('medium-editor-toolbar-active');
expect(toolbar.getToolbarElement().classList.contains('medium-editor-toolbar-active')).toBe(true);
editor.checkSelection();
expect(toolbar.getToolbarElement().classList.contains('medium-editor-toolbar-active')).toBe(false);
expect(toolbar.setToolbarPosition).not.toHaveBeenCalled();
expect(toolbar.setToolbarButtonStates).not.toHaveBeenCalled();
expect(toolbar.showAndUpdateToolbar).not.toHaveBeenCalled();
});
it('should hide the toolbar when selecting multiple paragraphs and the deprecated allowMultiParagraphSelection option is false', function () {
this.el.innerHTML = '<p id="p-one">lorem ipsum</p><p id="p-two">lorem ipsum</p>';
var editor = this.newMediumEditor('.editor', {
allowMultiParagraphSelection: false
}),
toolbar = editor.getExtensionByName('toolbar');
selectElementContentsAndFire(document.getElementById('p-one'), { eventToFire: 'focus' });
expect(toolbar.getToolbarElement().classList.contains('medium-editor-toolbar-active')).toBe(true);
selectElementContentsAndFire(this.el, { eventToFire: 'mouseup' });
expect(toolbar.getToolbarElement().classList.contains('medium-editor-toolbar-active')).toBe(false);
});
it('should show the toolbar when something is selected', function () {
var editor = this.newMediumEditor('.editor'),
toolbar = editor.getExtensionByName('toolbar');
expect(toolbar.getToolbarElement().classList.contains('medium-editor-toolbar-active')).toBe(false);
selectElementContentsAndFire(this.el);
jasmine.clock().tick(501);
expect(toolbar.getToolbarElement().classList.contains('medium-editor-toolbar-active')).toBe(true);
});
it('should update toolbar position and button states when something is selected', function () {
spyOn(MediumEditor.extensions.toolbar.prototype, 'setToolbarPosition').and.callThrough();
spyOn(MediumEditor.extensions.toolbar.prototype, 'setToolbarButtonStates').and.callThrough();
spyOn(MediumEditor.extensions.toolbar.prototype, 'showAndUpdateToolbar').and.callThrough();
var editor = this.newMediumEditor('.editor'),
toolbar = editor.getExtensionByName('toolbar');
selectElementContentsAndFire(this.el);
jasmine.clock().tick(51);
expect(toolbar.setToolbarPosition).toHaveBeenCalled();
expect(toolbar.setToolbarButtonStates).toHaveBeenCalled();
expect(toolbar.showAndUpdateToolbar).toHaveBeenCalled();
});
it('should update button states for static toolbar when updateOnEmptySelection is true and the selection is empty', function () {
spyOn(MediumEditor.extensions.toolbar.prototype, 'setToolbarButtonStates').and.callThrough();
var editor = this.newMediumEditor('.editor', {
toolbar: {
updateOnEmptySelection: true,
static: true
}
});
selectElementContentsAndFire(this.el, { collapse: 'toStart' });
jasmine.clock().tick(51);
expect(editor.getExtensionByName('toolbar').setToolbarButtonStates).toHaveBeenCalled();
});
});

@@ -500,2 +377,3 @@

it('no selected elements on empty selection', function () {
window.getSelection().removeAllRanges();
var elements = MediumEditor.selection.getSelectedElements(document);

@@ -508,8 +386,5 @@

this.el.innerHTML = 'lorem <i>ipsum</i> dolor';
var editor = this.newMediumEditor('.editor'),
elements;
selectElementContents(this.el.querySelector('i').firstChild);
var elements = MediumEditor.selection.getSelectedElements(document);
selectElementContents(editor.elements[0].querySelector('i').firstChild);
elements = MediumEditor.selection.getSelectedElements(document);
expect(elements.length).toBe(1);

@@ -522,6 +397,4 @@ expect(elements[0].nodeName.toLowerCase()).toBe('i');

this.el.innerHTML = 'lorem <i>ipsum</i> dolor';
var elements;
selectElementContents(this.el);
elements = MediumEditor.selection.getSelectedElements(document);
var elements = MediumEditor.selection.getSelectedElements(document);

@@ -557,2 +430,25 @@ expect(elements.length).toBe(1);

});
describe('selectionContainsContent', function () {
it('should return true for non-empty text', function () {
this.el.innerHTML = '<p>this is<span> </span>text</p>';
selectElementContents(this.el.querySelector('p'));
expect(MediumEditor.selection.selectionContainsContent(document)).toBe(true);
});
it('should return false for white-space only selections', function () {
this.el.innerHTML = '<p>this is<span> </span>text</p>';
selectElementContents(this.el.querySelector('span'));
expect(MediumEditor.selection.selectionContainsContent(document)).toBe(false);
});
it('should return true for image with link selections', function () {
this.el.innerHTML = '<p>this is <a href="#"><img src="../demo/img/medium-editor.jpg" /></a> image test</p>';
selectElementContents(this.el.querySelector('a'));
expect(MediumEditor.selection.selectionContainsContent(document)).toBe(true);
});
});
});

@@ -171,2 +171,16 @@ /*global fireEvent, selectElementContents,

it('should not hide when selecting a link containing only an image', function () {
this.el.innerHTML = '<p>Here is an <a href="#"><img src="../demo/img/medium-editor.jpg"></a> image</p>';
var editor = this.newMediumEditor('.editor'),
toolbar = editor.getExtensionByName('toolbar');
selectElementContentsAndFire(editor.elements[0].querySelector('a'));
fireEvent(editor.elements[0], 'mousedown');
fireEvent(document.body, 'mouseup');
fireEvent(document.body, 'click');
jasmine.clock().tick(51);
expect(toolbar.isDisplayed()).toBe(true);
});
it('should not hide when selecting text within editor, but release mouse outside of editor', function () {

@@ -186,3 +200,3 @@ this.el.innerHTML = 'lorem ipsum';

it('should hide the toolbar when clicking outside the toolbar on an element that does not clear selection', function () {
it('should hide when clicking outside the toolbar on an element that does not clear selection', function () {
this.el.innerHTML = 'lorem ipsum';

@@ -208,3 +222,3 @@ var outsideElement = this.createElement('div', '', 'Click Me, I don\'t clear selection'),

it('should hide the toolbar when selecting multiple paragraphs and the allowMultiParagraphSelection option is false', function () {
it('should hide when selecting multiple paragraphs and the allowMultiParagraphSelection option is false', function () {
this.el.innerHTML = '<p id="p-one">lorem ipsum</p><p id="p-two">lorem ipsum</p>';

@@ -222,2 +236,70 @@ var editor = this.newMediumEditor('.editor', {

});
it('should check for selection on mouseup event', function () {
spyOn(MediumEditor.extensions.toolbar.prototype, 'checkState');
var editor = this.newMediumEditor('.editor'),
toolbar = editor.getExtensionByName('toolbar');
fireEvent(editor.elements[0], 'mouseup');
expect(toolbar.checkState).toHaveBeenCalled();
});
it('should check for selection on keyup', function () {
spyOn(MediumEditor.extensions.toolbar.prototype, 'checkState');
var editor = this.newMediumEditor('.editor'),
toolbar = editor.getExtensionByName('toolbar');
fireEvent(editor.elements[0], 'keyup');
expect(toolbar.checkState).toHaveBeenCalled();
});
it('should hide if selection is empty', function () {
spyOn(MediumEditor.extensions.toolbar.prototype, 'setToolbarPosition').and.callThrough();
spyOn(MediumEditor.extensions.toolbar.prototype, 'setToolbarButtonStates').and.callThrough();
spyOn(MediumEditor.extensions.toolbar.prototype, 'showAndUpdateToolbar').and.callThrough();
var editor = this.newMediumEditor('.editor'),
toolbar = editor.getExtensionByName('toolbar');
toolbar.getToolbarElement().style.display = 'block';
toolbar.getToolbarElement().classList.add('medium-editor-toolbar-active');
expect(toolbar.getToolbarElement().classList.contains('medium-editor-toolbar-active')).toBe(true);
editor.checkSelection();
expect(toolbar.getToolbarElement().classList.contains('medium-editor-toolbar-active')).toBe(false);
expect(toolbar.setToolbarPosition).not.toHaveBeenCalled();
expect(toolbar.setToolbarButtonStates).not.toHaveBeenCalled();
expect(toolbar.showAndUpdateToolbar).not.toHaveBeenCalled();
});
it('should hide when selecting multiple paragraphs and the deprecated allowMultiParagraphSelection option is false', function () {
this.el.innerHTML = '<p id="p-one">lorem ipsum</p><p id="p-two">lorem ipsum</p>';
var editor = this.newMediumEditor('.editor', {
allowMultiParagraphSelection: false
}),
toolbar = editor.getExtensionByName('toolbar');
selectElementContentsAndFire(document.getElementById('p-one'), { eventToFire: 'focus' });
expect(toolbar.getToolbarElement().classList.contains('medium-editor-toolbar-active')).toBe(true);
selectElementContentsAndFire(this.el, { eventToFire: 'mouseup' });
expect(toolbar.getToolbarElement().classList.contains('medium-editor-toolbar-active')).toBe(false);
});
it('should show when something is selected', function () {
this.el.innerHTML = 'lorem ipsum';
var editor = this.newMediumEditor('.editor'),
toolbar = editor.getExtensionByName('toolbar');
expect(toolbar.getToolbarElement().classList.contains('medium-editor-toolbar-active')).toBe(false);
selectElementContentsAndFire(this.el);
jasmine.clock().tick(51);
expect(toolbar.getToolbarElement().classList.contains('medium-editor-toolbar-active')).toBe(true);
});
it('should update position and button states when something is selected', function () {
this.el.innerHTML = 'lorem ipsum';
spyOn(MediumEditor.extensions.toolbar.prototype, 'setToolbarPosition').and.callThrough();
spyOn(MediumEditor.extensions.toolbar.prototype, 'setToolbarButtonStates').and.callThrough();
spyOn(MediumEditor.extensions.toolbar.prototype, 'showAndUpdateToolbar').and.callThrough();
var editor = this.newMediumEditor('.editor'),
toolbar = editor.getExtensionByName('toolbar');
selectElementContentsAndFire(this.el);
jasmine.clock().tick(51);
expect(toolbar.setToolbarPosition).toHaveBeenCalled();
expect(toolbar.setToolbarButtonStates).toHaveBeenCalled();
expect(toolbar.showAndUpdateToolbar).toHaveBeenCalled();
});
});

@@ -328,2 +410,18 @@

});
it('should update button states when updateOnEmptySelection is true and the selection is empty', function () {
spyOn(MediumEditor.extensions.toolbar.prototype, 'setToolbarButtonStates').and.callThrough();
var editor = this.newMediumEditor('.editor', {
toolbar: {
updateOnEmptySelection: true,
static: true
}
});
selectElementContentsAndFire(this.el, { collapse: 'toStart' });
jasmine.clock().tick(51);
expect(editor.getExtensionByName('toolbar').setToolbarButtonStates).toHaveBeenCalled();
});
});

@@ -330,0 +428,0 @@

@@ -492,3 +492,90 @@ /*global selectElementContents*/

});
it('should ignore comments', function () {
var comment = document.createComment('comment'),
parts = MediumEditor.util.splitByBlockElements(comment);
expect(parts.length).toBe(0);
});
it('should ignore nested comments', function () {
var el = this.createElement('div');
el.innerHTML = '' +
'<p>Text</p>' +
'<!---->';
var parts = MediumEditor.util.splitByBlockElements(el);
expect(parts.length).toBe(1);
});
});
describe('findOrCreateMatchingTextNodes', function () {
it('should return text nodes within an element', function () {
var el = this.createElement('div');
el.innerHTML = '<p>Plain <b>bold</b> <a href="#">link</a> <i>italic</i> <u>underline</u> <span>span1 <span>span2</span></span></p>';
var textNodes = MediumEditor.util.findOrCreateMatchingTextNodes(document, el, { start: 0, end: el.textContent.length });
expect(textNodes.length).toBe(11);
expect(textNodes[0].nodeValue).toBe('Plain ');
expect(textNodes[9].nodeValue).toBe('span1 ');
expect(textNodes[10].nodeValue).toBe('span2');
});
it('should return text nodes within an element given a start and end range', function () {
var el = this.createElement('div');
el.innerHTML = '<p>Plain <b>bold</b> <a href="#">link</a> <i>italic</i> <u>underline</u> <span>span1 <span>span2</span></span></p>';
var textNodes = MediumEditor.util.findOrCreateMatchingTextNodes(document, el, { start: 11, end: 22 });
expect(textNodes.length).toBe(3);
expect(textNodes[0].nodeValue).toBe('link');
expect(textNodes[1].nodeValue).toBe(' ');
expect(textNodes[2].nodeValue).toBe('italic');
});
it('should split text nodes if start and end range are in the middle of a text node', function () {
var el = this.createElement('div');
el.innerHTML = '<p>Plain <b>bold</b> <a href="#">link</a> <i>italic</i> <u>underline</u> <span>span1 <span>span2</span></span></p>';
var textNodes = MediumEditor.util.findOrCreateMatchingTextNodes(document, el, { start: 13, end: 19 });
expect(el.querySelector('a').childNodes.length).toBe(2);
expect(el.querySelector('i').childNodes.length).toBe(2);
expect(textNodes.length).toBe(3);
expect(textNodes[0].nodeValue).toBe('nk');
expect(textNodes[1].nodeValue).toBe(' ');
expect(textNodes[2].nodeValue).toBe('ita');
});
it('should return an image when it falls within the specified range', function () {
var el = this.createElement('div');
el.innerHTML = '<p>Plain <b>bold</b> <a href="#">li<img src="../demo.img/medium-editor.jpg" />nk</a> <i>italic</i> <u>underline</u> <span>span1 <span>span2</span></span></p>';
var textNodes = MediumEditor.util.findOrCreateMatchingTextNodes(document, el, { start: 11, end: 15 });
expect(textNodes.length).toBe(3);
expect(textNodes[0].nodeValue).toBe('li');
expect(textNodes[1].nodeName.toLowerCase()).toBe('img');
expect(textNodes[2].nodeValue).toBe('nk');
});
it('should return an image when it is at the end of the specified range', function () {
var el = this.createElement('div');
el.innerHTML = '<p>Plain <b>bold</b> <a href="#">link<img src="../demo.img/medium-editor.jpg" /></a> <i>italic</i> <u>underline</u> <span>span1 <span>span2</span></span></p>';
var textNodes = MediumEditor.util.findOrCreateMatchingTextNodes(document, el, { start: 11, end: 15 });
expect(textNodes.length).toBe(2);
expect(textNodes[0].nodeValue).toBe('link');
expect(textNodes[1].nodeName.toLowerCase()).toBe('img');
});
it('should return an image when it is the only content in the specified range', function () {
var el = this.createElement('div');
el.innerHTML = '<p>Plain <b>bold</b> <a href="#"><img src="../demo.img/medium-editor.jpg" /></a> <i>italic</i> <u>underline</u> <span>span1 <span>span2</span></span></p>';
var textNodes = MediumEditor.util.findOrCreateMatchingTextNodes(document, el, { start: 11, end: 11 });
expect(textNodes.length).toBe(1);
expect(textNodes[0].nodeName.toLowerCase()).toBe('img');
});
it('should return images when they are at the beginning of the specified range', function () {
var el = this.createElement('div');
el.innerHTML = '<p>Plain <b>bold</b> <a href="#"><img src="../demo.img/medium-editor.jpg" /><img src="../demo.img/roman.jpg" />link</a> <i>italic</i> <u>underline</u> <span>span1 <span>span2</span></span></p>';
var textNodes = MediumEditor.util.findOrCreateMatchingTextNodes(document, el, { start: 11, end: 15 });
expect(textNodes.length).toBe(3);
expect(textNodes[0].nodeName.toLowerCase()).toBe('img');
expect(textNodes[1].nodeName.toLowerCase()).toBe('img');
expect(textNodes[2].nodeValue).toBe('link');
});
});
});

@@ -518,3 +518,4 @@ (function () {

if (action === 'image') {
return this.options.ownerDocument.execCommand('insertImage', false, this.options.contentWindow.getSelection());
var src = this.options.contentWindow.getSelection().toString().trim();
return this.options.ownerDocument.execCommand('insertImage', false, src);
}

@@ -969,3 +970,3 @@

// which can happen with the built in browser functionality
if (commonAncestorContainer.nodeType !== 3 && startContainerParentElement === endContainerParentElement) {
if (commonAncestorContainer.nodeType !== 3 && commonAncestorContainer.textContent.length !== 0 && startContainerParentElement === endContainerParentElement) {
var parentElement = (startContainerParentElement || currentEditor),

@@ -972,0 +973,0 @@ fragment = this.options.ownerDocument.createDocumentFragment();

@@ -413,3 +413,3 @@ (function () {

// If we don't have a 'valid' selection -> hide toolbar
if (this.window.getSelection().toString().trim() === '' ||
if (!MediumEditor.selection.selectionContainsContent(this.document) ||
(this.allowMultiParagraphSelection === false && this.multipleBlockElementsSelected())) {

@@ -603,5 +603,16 @@ return this.hideToolbar();

var range = selection.getRangeAt(0),
boundary = range.getBoundingClientRect();
// Handle selections with just images
if (!boundary || ((boundary.height === 0 && boundary.width === 0) && range.startContainer === range.endContainer)) {
// If there's a nested image, use that for the bounding rectangle
if (range.startContainer.nodeType === 1 && range.startContainer.querySelector('img')) {
boundary = range.startContainer.querySelector('img').getBoundingClientRect();
} else {
boundary = range.startContainer.getBoundingClientRect();
}
}
var windowWidth = this.window.innerWidth,
range = selection.getRangeAt(0),
boundary = range.getBoundingClientRect(),
middleBoundary = (boundary.left + boundary.right) / 2,

@@ -608,0 +619,0 @@ toolbarElement = this.getToolbarElement(),

@@ -57,2 +57,11 @@ (function () {

};
// Range contains an image, check to see if the selection ends with that image
if (range.endOffset !== 0 && (range.endContainer.nodeName.toLowerCase() === 'img' || (range.endContainer.nodeType === 1 && range.endContainer.querySelector('img')))) {
var trailingImageCount = this.getTrailingImageCount(root, selectionState, range.endContainer, range.endOffset);
if (trailingImageCount) {
selectionState.trailingImageCount = trailingImageCount;
}
}
// If start = 0 there may still be an empty paragraph before it, but we don't care.

@@ -93,2 +102,4 @@ if (start !== 0) {

foundStart = false,
foundEnd = false,
trailingImageCount = 0,
stop = false,

@@ -98,3 +109,9 @@ nextCharIndex;

while (!stop && node) {
if (node.nodeType === 3) {
// Only iterate over elements and text nodes
if (node.nodeType > 3) {
continue;
}
// If we hit a text node, we need to add the amount of characters to the overall count
if (node.nodeType === 3 && !foundEnd) {
nextCharIndex = charIndex + node.length;

@@ -106,13 +123,37 @@ if (!foundStart && selectionState.start >= charIndex && selectionState.start <= nextCharIndex) {

if (foundStart && selectionState.end >= charIndex && selectionState.end <= nextCharIndex) {
range.setEnd(node, selectionState.end - charIndex);
stop = true;
if (!selectionState.trailingImageCount) {
range.setEnd(node, selectionState.end - charIndex);
stop = true;
} else {
foundEnd = true;
}
}
charIndex = nextCharIndex;
} else {
var i = node.childNodes.length - 1;
while (i >= 0) {
nodeStack.push(node.childNodes[i]);
i -= 1;
if (selectionState.trailingImageCount && foundEnd) {
if (node.nodeName.toLowerCase() === 'img') {
trailingImageCount++;
}
if (trailingImageCount === selectionState.trailingImageCount) {
// Find which index the image is in its parent's children
var endIndex = 0;
while (node.parentNode.childNodes[endIndex] !== node) {
endIndex++;
}
range.setEnd(node.parentNode, endIndex + 1);
stop = true;
}
}
if (!stop && node.nodeType === 1) {
// this is an element
// add all its children to the stack
var i = node.childNodes.length - 1;
while (i >= 0) {
nodeStack.push(node.childNodes[i]);
i -= 1;
}
}
}
if (!stop) {

@@ -259,2 +300,87 @@ node = nodeStack.pop();

getTrailingImageCount: function (root, selectionState, endContainer, endOffset) {
var lastNode = endContainer.childNodes[endOffset - 1];
while (lastNode.hasChildNodes()) {
lastNode = lastNode.lastChild;
}
var node = root,
nodeStack = [],
charIndex = 0,
foundStart = false,
foundEnd = false,
stop = false,
nextCharIndex,
trailingImages = 0;
while (!stop && node) {
// Only iterate over elements and text nodes
if (node.nodeType > 3) {
continue;
}
if (node.nodeType === 3 && !foundEnd) {
trailingImages = 0;
nextCharIndex = charIndex + node.length;
if (!foundStart && selectionState.start >= charIndex && selectionState.start <= nextCharIndex) {
foundStart = true;
}
if (foundStart && selectionState.end >= charIndex && selectionState.end <= nextCharIndex) {
foundEnd = true;
}
charIndex = nextCharIndex;
} else {
if (node.nodeName.toLowerCase() === 'img') {
trailingImages++;
}
if (node === lastNode) {
stop = true;
} else if (node.nodeType === 1) {
// this is an element
// add all its children to the stack
var i = node.childNodes.length - 1;
while (i >= 0) {
nodeStack.push(node.childNodes[i]);
i -= 1;
}
}
}
if (!stop) {
node = nodeStack.pop();
}
}
return trailingImages;
},
// determine if the current selection contains any 'content'
// content being and non-white space text or an image
selectionContainsContent: function (doc) {
var sel = doc.getSelection();
// collapsed selection or selection withour range doesn't contain content
if (!sel || sel.isCollapsed || !sel.rangeCount) {
return false;
}
// if toString() contains any text, the selection contains some content
if (sel.toString().trim() !== '') {
return true;
}
// if selection contains only image(s), it will return empty for toString()
// so check for an image manually
var selectionNode = this.getSelectedParentElement(sel.getRangeAt(0));
if (selectionNode) {
if (selectionNode.nodeName.toLowerCase() === 'img' ||
(selectionNode.nodeType === 1 && selectionNode.querySelector('img'))) {
return true;
}
}
return false;
},
selectionInContentEditableFalse: function (contentWindow) {

@@ -261,0 +387,0 @@ // determine if the current selection is exclusively inside

@@ -152,3 +152,3 @@ /*global NodeFilter*/

findOrCreateMatchingTextNodes: function (document, element, match) {
var treeWalker = document.createTreeWalker(element, NodeFilter.SHOW_TEXT, null, false),
var treeWalker = document.createTreeWalker(element, NodeFilter.SHOW_ALL, null, false),
matchedNodes = [],

@@ -161,26 +161,37 @@ currentTextIndex = 0,

while ((currentNode = treeWalker.nextNode()) !== null) {
if (!startReached && match.start < (currentTextIndex + currentNode.nodeValue.length)) {
startReached = true;
newNode = Util.splitStartNodeIfNeeded(currentNode, match.start, currentTextIndex);
}
if (startReached) {
Util.splitEndNodeIfNeeded(currentNode, newNode, match.end, currentTextIndex);
}
if (startReached && currentTextIndex === match.end) {
break; // Found the node(s) corresponding to the link. Break out and move on to the next.
} else if (startReached && currentTextIndex > (match.end + 1)) {
throw new Error('PerformLinking overshot the target!'); // should never happen...
}
if (currentNode.nodeType > 3) {
continue;
} else if (currentNode.nodeType === 3) {
if (!startReached && match.start < (currentTextIndex + currentNode.nodeValue.length)) {
startReached = true;
newNode = Util.splitStartNodeIfNeeded(currentNode, match.start, currentTextIndex);
}
if (startReached) {
Util.splitEndNodeIfNeeded(currentNode, newNode, match.end, currentTextIndex);
}
if (startReached && currentTextIndex === match.end) {
break; // Found the node(s) corresponding to the link. Break out and move on to the next.
} else if (startReached && currentTextIndex > (match.end + 1)) {
throw new Error('PerformLinking overshot the target!'); // should never happen...
}
if (startReached) {
matchedNodes.push(newNode || currentNode);
}
if (startReached) {
matchedNodes.push(newNode || currentNode);
}
currentTextIndex += currentNode.nodeValue.length;
if (newNode !== null) {
currentTextIndex += newNode.nodeValue.length;
// Skip the newNode as we'll already have pushed it to the matches
treeWalker.nextNode();
currentTextIndex += currentNode.nodeValue.length;
if (newNode !== null) {
currentTextIndex += newNode.nodeValue.length;
// Skip the newNode as we'll already have pushed it to the matches
treeWalker.nextNode();
}
newNode = null;
} else if (currentNode.tagName.toLowerCase() === 'img') {
if (!startReached && (match.start <= currentTextIndex)) {
startReached = true;
}
if (startReached) {
matchedNodes.push(currentNode);
}
}
newNode = null;
}

@@ -253,2 +264,6 @@ return matchedNodes;

splitByBlockElements: function (element) {
if (element.nodeType !== 3 && element.nodeType !== 1) {
return [];
}
var toRet = [],

@@ -265,3 +280,3 @@ blockElementQuery = MediumEditor.util.blockContainerElementNames.join(',');

toRet.push(child);
} else {
} else if (child.nodeType === 1) {
var blockElements = child.querySelectorAll(blockElementQuery);

@@ -268,0 +283,0 @@ if (blockElements.length === 0) {

@@ -18,3 +18,3 @@ MediumEditor.parseVersionString = function (release) {

// grunt-bump looks for this:
'version': '5.11.0'
'version': '5.12.0'
}).version);

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc