Socket
Socket
Sign inDemoInstall

medium-editor

Package Overview
Dependencies
Maintainers
4
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 4.12.7 to 4.12.8

6

CHANGES.md

@@ -0,1 +1,7 @@

4.12.8 / 2015-08-10
==================
* Fix issue with creating anchors and restoring selection at the beginning of paragraphs
* Fix issue with creating anchors and restoring selection within list items and nested blocks
4.12.7 / 2015-08-04

@@ -2,0 +8,0 @@ ==================

2

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

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

@@ -236,2 +236,45 @@ /*global MediumEditor, describe, it, expect, spyOn,

});
it('should fire editableInput only once when the user creates a link open to a new window,' +
' and it should fire at the end of the DOM and selection modifications', function () {
spyOn(MediumEditor.prototype, 'createLink').and.callThrough();
this.el.innerHTML = '<p>Lorem ipsum et dolitur sunt.</p>';
var editor = this.newMediumEditor('.editor', {
anchor: {
targetCheckbox: true
}
}),
p = this.el.lastChild,
anchorExtension = editor.getExtensionByName('anchor'),
selectionWhenEventsFired = [],
listener = function () {
selectionWhenEventsFired.push(window.getSelection().toString());
};
MediumEditor.selection.select(document, p.firstChild, 'Lorem '.length, p.firstChild, 'Lorem ipsum'.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(),
checkbox = anchorExtension.getForm().querySelector('.medium-editor-toolbar-anchor-target');
input.value = 'http://www.example.com';
checkbox.checked = true;
editor.subscribe('editableInput', listener);
fireEvent(input, 'keyup', {
keyCode: Util.keyCode.ENTER
});
expect(editor.createLink).toHaveBeenCalledWith({
url: 'http://www.example.com',
target: '_blank'
});
expect(window.getSelection().toString()).toBe('ipsum', 'selected text should remain selected');
expect(selectionWhenEventsFired.length).toBe(1, 'only one editableInput event should have been registered');
expect(selectionWhenEventsFired[0]).toBe('ipsum', 'selected text should have been the same when event fired');
});
it('should create a button when deprecated anchorButton + anchorButtonClass options are used', function () {

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

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();

@@ -305,5 +349,82 @@ this.el.innerHTML = '<p>Some text</p><p><br/></p><p><br/></p><p>link text more text</p>';

// 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'),
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 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(MediumEditor.util.isDescendant(lastP, range.endContainer, true)).toBe(true, 'The end of the selection is incorrect');

@@ -310,0 +431,0 @@ });

@@ -55,2 +55,16 @@ /*global describe, it, expect, jasmine,

});
it('can be disabled for a temporary period of time on a named basis', function () {
var editor = this.newMediumEditor('.editor'),
spy = jasmine.createSpy('handler'),
tempData = { temp: 'data' };
editor.subscribe('myIncredibleEvent', spy);
expect(spy).not.toHaveBeenCalled();
editor.events.disableCustomEvent('myIncredibleEvent');
editor.trigger('myIncredibleEvent', tempData, editor.elements[0]);
expect(spy).not.toHaveBeenCalled();
editor.events.enableCustomEvent('myIncredibleEvent');
editor.trigger('myIncredibleEvent', tempData, editor.elements[0]);
expect(spy).toHaveBeenCalledWith(tempData, editor.elements[0]);
});
});

@@ -57,0 +71,0 @@

/*global MediumEditor, describe, it, expect, spyOn,
afterEach, beforeEach, fireEvent, Util,
afterEach, beforeEach, fireEvent,
jasmine, selectElementContents, setupTestHelpers,

@@ -109,2 +109,12 @@ selectElementContentsAndFire, Selection, placeCursorInsideElement */

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 () {

@@ -189,3 +199,3 @@ this.el.innerHTML = '<p><span>www.google.com</span></p><p><br /></p>' +

var startParagraph = Util.getClosestTag(window.getSelection().getRangeAt(0).startContainer, 'p');
var startParagraph = MediumEditor.util.getClosestTag(window.getSelection().getRangeAt(0).startContainer, 'p');
expect(startParagraph).toBe(editor.elements[0].getElementsByTagName('p')[1], 'empty paragraph');

@@ -205,3 +215,3 @@ });

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

@@ -222,3 +232,3 @@ });

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

@@ -238,3 +248,3 @@ });

var startParagraph = Util.getClosestTag(window.getSelection().getRangeAt(0).startContainer, 'h2');
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');

@@ -254,3 +264,3 @@ });

var startParagraph = Util.getClosestTag(window.getSelection().getRangeAt(0).startContainer, 'h2');
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');

@@ -271,3 +281,3 @@ });

var innerElement = window.getSelection().getRangeAt(0).startContainer;
expect(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(editor.elements[0].querySelector('i'), innerElement, true)).toBe(true, 'nested inline elment inside block element after empty block element');
});

@@ -310,3 +320,3 @@

// The behavior varies from browser to browser for this case, some select TH, some #textNode
expect(Util.isDescendant(editor.elements[0].querySelector('th'), innerElement, true))
expect(MediumEditor.util.isDescendant(editor.elements[0].querySelector('th'), innerElement, true))
.toBe(true, 'expect selection to be of TH or a descendant');

@@ -329,5 +339,29 @@ expect(innerElement).toBe(window.getSelection().getRangeAt(0).endContainer);

var innerElement = window.getSelection().getRangeAt(0).startContainer;
expect(Util.isDescendant(editor.elements[0].querySelectorAll('p')[1], innerElement, true)).toBe(false, 'moved selection beyond non-empty block element');
expect(Util.isDescendant(editor.elements[0].querySelector('h2'), innerElement, true)).toBe(true, 'moved selection to element to incorrect block element');
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');
});
// https://github.com/yabwe/medium-editor/issues/732
it('should support a selection correctly when space + newlines are separating block elements', function () {
this.el.innerHTML = '<ul>\n' +
' <li><a href="#">a link</a></li>\n' +
' <li>a list item</li>\n' +
' <li>target</li>\n' +
'</ul>';
var editor = this.newMediumEditor('.editor'),
lastLi = this.el.querySelectorAll('ul > li')[2];
// Select the <li> with 'target'
selectElementContents(lastLi.firstChild);
var selectionData = editor.exportSelection();
expect(selectionData.emptyBlocksIndex).toBe(0);
editor.importSelection(selectionData);
var range = window.getSelection().getRangeAt(0);
expect(range.toString()).toBe('target', 'The selection is around the wrong element');
expect(MediumEditor.util.isDescendant(lastLi, range.startContainer, true)).toBe(true, 'The start of the selection is invalid');
expect(MediumEditor.util.isDescendant(lastLi, range.endContainer, true)).toBe(true, 'The end of the selection is invalid');
});
});

@@ -441,3 +475,3 @@

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

@@ -453,3 +487,3 @@ expect(elements.length).toBe(0);

selectElementContents(editor.elements[0].querySelector('i').firstChild);
elements = Selection.getSelectedElements(document);
elements = MediumEditor.selection.getSelectedElements(document);

@@ -466,3 +500,3 @@ expect(elements.length).toBe(1);

selectElementContents(this.el);
elements = Selection.getSelectedElements(document);
elements = MediumEditor.selection.getSelectedElements(document);

@@ -477,4 +511,4 @@ expect(elements.length).toBe(1);

it('should return null on bad range', function () {
expect(Selection.getSelectedParentElement(null)).toBe(null);
expect(Selection.getSelectedParentElement(false)).toBe(null);
expect(MediumEditor.selection.getSelectedParentElement(null)).toBe(null);
expect(MediumEditor.selection.getSelectedParentElement(false)).toBe(null);
});

@@ -494,3 +528,3 @@

element = Selection.getSelectedParentElement(range);
element = MediumEditor.selection.getSelectedParentElement(range);

@@ -497,0 +531,0 @@ expect(element).toBe(document);

@@ -927,3 +927,3 @@ /*global Util, ButtonsData, Selection, Extension,

range.startOffset);
if (emptyBlocksIndex !== 0) {
if (emptyBlocksIndex !== -1) {
selectionState.emptyBlocksIndex = emptyBlocksIndex;

@@ -1006,18 +1006,4 @@ }

if (inSelectionState.emptyBlocksIndex) {
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) {
targetNode = targetNode.nextSibling;
index++;
// If we find a non-empty block, ignore the emptyBlocksIndex and just put selection here
if (targetNode.textContent.length > 0) {
break;
}
}
// We're selecting a high-level block node, so make sure the cursor gets moved into the deepest
// element at the beginning of the block
range.setStart(Util.getFirstSelectableLeafNode(targetNode), 0);
if (typeof inSelectionState.emptyBlocksIndex !== 'undefined') {
range = Selection.importSelectionMoveCursorPastBlocks(this.options.ownerDocument, editableElement, inSelectionState.emptyBlocksIndex, range);
}

@@ -1042,21 +1028,28 @@

if (opts.url && opts.url.trim().length > 0) {
this.options.ownerDocument.execCommand('createLink', false, opts.url);
try {
this.events.disableCustomEvent('editableInput');
if (opts.url && opts.url.trim().length > 0) {
this.options.ownerDocument.execCommand('createLink', false, opts.url);
if (this.options.targetBlank || opts.target === '_blank') {
Util.setTargetBlank(Selection.getSelectionStart(this.options.ownerDocument), opts.url);
}
if (this.options.targetBlank || opts.target === '_blank') {
Util.setTargetBlank(Selection.getSelectionStart(this.options.ownerDocument), opts.url);
}
if (opts.buttonClass) {
Util.addClassToAnchors(Selection.getSelectionStart(this.options.ownerDocument), opts.buttonClass);
if (opts.buttonClass) {
Util.addClassToAnchors(Selection.getSelectionStart(this.options.ownerDocument), opts.buttonClass);
}
}
}
if (this.options.targetBlank || opts.target === '_blank' || opts.buttonClass) {
customEvent = this.options.ownerDocument.createEvent('HTMLEvents');
customEvent.initEvent('input', true, true, this.options.contentWindow);
for (i = 0; i < this.elements.length; i += 1) {
this.elements[i].dispatchEvent(customEvent);
// Fire input event for backwards compatibility if anyone was listening directly to the DOM input event
if (this.options.targetBlank || opts.target === '_blank' || opts.buttonClass) {
customEvent = this.options.ownerDocument.createEvent('HTMLEvents');
customEvent.initEvent('input', true, true, this.options.contentWindow);
for (i = 0; i < this.elements.length; i += 1) {
this.elements[i].dispatchEvent(customEvent);
}
}
} finally {
this.events.enableCustomEvent('editableInput');
}
// Fire our custom editableInput event
this.events.triggerCustomEvent('editableInput', customEvent, this.getFocusedElement());
},

@@ -1063,0 +1056,0 @@

@@ -12,2 +12,3 @@ /*global Util*/

this.events = [];
this.disabledEvents = {};
this.customEvents = {};

@@ -55,2 +56,12 @@ this.listeners = {};

enableCustomEvent: function (event) {
if (this.disabledEvents[event] !== undefined) {
delete this.disabledEvents[event];
}
},
disableCustomEvent: function (event) {
this.disabledEvents[event] = true;
},
// custom events

@@ -87,3 +98,3 @@ attachCustomEvent: function (event, listener) {

triggerCustomEvent: function (name, data, editable) {
if (this.customEvents[name]) {
if (this.customEvents[name] && !this.disabledEvents[name]) {
this.customEvents[name].forEach(function (listener) {

@@ -90,0 +101,0 @@ listener(data, editable);

@@ -70,9 +70,59 @@ /*global Util */

// 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.
// Uses the emptyBlocksIndex calculated by getIndexRelativeToAdjacentEmptyBlocks
// to move the cursor back to the start of the correct paragraph
importSelectionMoveCursorPastBlocks: function (doc, root, index, range) {
var treeWalker = doc.createTreeWalker(root, NodeFilter.SHOW_ELEMENT, filterOnlyParentElements, false),
startContainer = range.startContainer,
startBlock,
targetNode,
currIndex = 0;
index = index || 1; // If index is 0, we still want to move to the next block
// Chrome counts newlines and spaces that separate block elements as actual elements.
// If the selection is inside one of these text nodes, and it has a previous sibling
// which is a block element, we want the treewalker to start at the previous sibling
// and NOT at the parent of the textnode
if (startContainer.nodeType === 3 && Util.isBlockContainer(startContainer.previousSibling)) {
startBlock = startContainer.previousSibling;
} else {
startBlock = Util.getClosestBlockContainer(startContainer);
}
// Skip over empty blocks until we hit the block we want the selection to be in
while (treeWalker.nextNode()) {
if (!targetNode) {
// Loop through all blocks until we hit the starting block element
if (startBlock === treeWalker.currentNode) {
targetNode = treeWalker.currentNode;
}
} else {
targetNode = treeWalker.currentNode;
currIndex++;
// We hit the target index, bail
if (currIndex === index) {
break;
}
// If we find a non-empty block, ignore the emptyBlocksIndex and just put selection here
if (targetNode.textContent.length > 0) {
break;
}
}
}
// We're selecting a high-level block node, so make sure the cursor gets moved into the deepest
// element at the beginning of the block
range.setStart(Util.getFirstSelectableLeafNode(targetNode), 0);
return range;
},
// 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;
}

@@ -83,7 +133,6 @@

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;
}

@@ -93,3 +142,4 @@

// and the block the cursor is in
var treeWalker = doc.createTreeWalker(root, NodeFilter.SHOW_ELEMENT, filterOnlyParentElements, false),
var closestBlock = Util.getClosestBlockContainer(cursorContainer),
treeWalker = doc.createTreeWalker(root, NodeFilter.SHOW_ELEMENT, filterOnlyParentElements, false),
emptyBlocksCount = 0;

@@ -101,3 +151,3 @@ while (treeWalker.nextNode()) {

}
if (Util.isDescendant(treeWalker.currentNode, cursorContainer, true)) {
if (treeWalker.currentNode === closestBlock) {
return emptyBlocksCount;

@@ -104,0 +154,0 @@ }

@@ -109,3 +109,10 @@ /*global NodeFilter, Selection*/

parentElements: ['p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'blockquote', 'pre'],
parentElements: [
// elements our editor generates
'p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'blockquote', 'pre', 'ul', 'li', 'ol',
// all other known block elements
'address', 'article', 'aside', 'audio', 'canvas', 'dd', 'dl', 'dt', 'fieldset',
'figcaption', 'figure', 'footer', 'form', 'header', 'hgroup', 'main', 'nav',
'noscript', 'output', 'section', 'table', 'tbody', 'tfoot', 'video'
],

@@ -400,3 +407,3 @@ extend: function extend(/* dest, source1, source2, ...*/) {

tagName = parentNode.tagName.toLowerCase();
while (!this.isBlockContainer(parentNode) && tagName !== 'div') {
while (tagName === 'li' || (!this.isBlockContainer(parentNode) && tagName !== 'div')) {
if (tagName === 'li') {

@@ -678,2 +685,8 @@ return true;

getClosestBlockContainer: function (node) {
return Util.traverseUp(node, function (node) {
return Util.isBlockContainer(node);
});
},
getBlockContainer: function (element) {

@@ -680,0 +693,0 @@ return this.traverseUp(element, function (el) {

@@ -14,3 +14,3 @@ /*global MediumEditor */

// grunt-bump looks for this:
'version': '4.12.7'
'version': '4.12.8'
}).version.split('.'));

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

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc