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.2 to 4.12.3

server.js

1

bower.json
{
"name": "medium-editor",
"version": "4.12.2",
"homepage": "http://yabwe.github.io/medium-editor/",

@@ -5,0 +4,0 @@ "authors": [

@@ -0,1 +1,6 @@

4.12.3 / 2015-06-12
==================
* Fix bug with un-linked auto-links causing unexpected cursor positioning
4.12.2 / 2015-06-10

@@ -2,0 +7,0 @@ ==================

@@ -298,3 +298,3 @@ /*global module, require, process*/

options: {
files: ['bower.json', 'package.json', 'src/js/version.js'],
files: ['package.json', 'src/js/version.js'],
updateConfigs: [],

@@ -301,0 +301,0 @@ commit: false,

{
"name": "medium-editor",
"version": "4.12.2",
"version": "4.12.3",
"author": "Davi Ferreira <hi@daviferreira.com>",

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

@@ -186,3 +186,3 @@ /*global describe, it, expect, beforeEach, afterEach,

links = this.el.getElementsByTagName('a');
expect(links.length).toBe(1);
expect(links.length).toBe(1, 'links length after ENTER');
expect(links[0].getAttribute('href')).toBe('http://www.example.enter');

@@ -197,3 +197,3 @@ expect(links[0].firstChild.getAttribute('data-auto-link')).toBe('true');

links = this.el.getElementsByTagName('a');
expect(links.length).toBe(1);
expect(links.length).toBe(1, 'links length after SPACE');
expect(links[0].getAttribute('href')).toBe('http://www.example.space');

@@ -200,0 +200,0 @@ expect(links[0].firstChild.getAttribute('data-auto-link')).toBe('true');

/*global MediumEditor, describe, it, expect, spyOn, AnchorForm,
beforeAll, afterAll,
afterEach, beforeEach, jasmine, fireEvent, setupTestHelpers,

@@ -8,2 +9,14 @@ selectElementContentsAndFire, isOldIE, isIE */

var textarea;
beforeAll(function () {
textarea = document.createElement('textarea');
textarea.innerHTML = 'Ignore me please, placed here to make create an image test pass in Gecko';
document.body.appendChild(textarea);
textarea.focus();
});
afterAll(function () {
document.body.removeChild(textarea);
});
beforeEach(function () {

@@ -10,0 +23,0 @@ setupTestHelpers.call(this);

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

@@ -77,2 +77,172 @@ selectElementContentsAndFire, Selection, placeCursorInsideElement */

});
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();
expect(exportedSelection.emptyBlocksIndex).toEqual(undefined);
});
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>';
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();
expect(exportedSelection.emptyBlocksIndex).toEqual(undefined);
});
it('should not export a position indicating the cursor is after an empty paragraph (in a complicated markup case)',
function () {
this.el.innerHTML = '<p><span>www.google.com</span></p><p><br /></p>' +
'<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();
expect(exportedSelection.emptyBlocksIndex).toEqual(undefined);
});
it('should not export a position indicating the cursor is after an empty paragraph ' +
'(in a complicated markup with selection on the element)', function () {
this.el.innerHTML = '<p><span>www.google.com</span></p><p><br /></p>' +
'<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();
expect(exportedSelection.emptyBlocksIndex).toEqual(undefined);
});
it('should export a position indicating the cursor is 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']
});
placeCursorInsideElement(editor.elements[0].getElementsByTagName('p')[1], 0);
var exportedSelection = editor.exportSelection();
expect(exportedSelection.emptyBlocksIndex).toEqual(1);
});
it('should 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>Whatever</p>';
var editor = this.newMediumEditor('.editor', {
buttons: ['italic', 'underline', 'strikethrough']
});
placeCursorInsideElement(editor.elements[0].getElementsByTagName('p')[2], 0);
var exportedSelection = editor.exportSelection();
expect(exportedSelection.emptyBlocksIndex).toEqual(2);
});
it('should export a position indicating the cursor is after an empty block element', function () {
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();
expect(exportedSelection.emptyBlocksIndex).toEqual(2);
});
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({
'start': 14,
'end': 14,
'emptyBlocksIndex': 1
});
var startParagraph = Util.getClosestTag(window.getSelection().getRangeAt(0).startContainer, 'p');
expect(startParagraph).toBe(editor.elements[0].getElementsByTagName('p')[1], 'empty paragraph');
});
it('should import a position with the cursor after 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({
'start': 14,
'end': 14,
'emptyBlocksIndex': 2
});
var startParagraph = Util.getClosestTag(window.getSelection().getRangeAt(0).startContainer, 'p');
expect(startParagraph).toBe(editor.elements[0].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({
'start': 14,
'end': 14,
'editableElementIndex': 1,
'emptyBlocksIndex': 2
});
var startParagraph = Util.getClosestTag(window.getSelection().getRangeAt(0).startContainer, 'p');
expect(startParagraph).toBe(editor.elements[1].getElementsByTagName('p')[2], 'paragraph after empty paragraph');
});
it('should import a position with the cursor after an empty block element', function () {
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({
'start': 14,
'end': 14,
'emptyBlocksIndex': 2
});
var startParagraph = Util.getClosestTag(window.getSelection().getRangeAt(0).startContainer, 'h2');
expect(startParagraph).toBe(editor.elements[0].querySelector('h2'), 'block element after empty block element');
});
it('should import a position with the cursor after an empty block element inside an element with various children', function () {
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({
'start': 14,
'end': 14,
'emptyBlocksIndex': 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');
});
it('should import not import a selection beyond any block elements that have text, even when emptyBlocksIndex indicates it should ', function () {
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({
'start': 14,
'end': 14,
'emptyBlocksIndex': 3
});
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');
});
});

@@ -79,0 +249,0 @@

@@ -917,2 +917,13 @@ /*global Util, ButtonsData, Selection, Extension,

};
// If start = 0 there may still be an empty paragraph before it, but we don't care.
if (start !== 0) {
var emptyBlocksIndex = Selection.getIndexRelativeToAdjacentEmptyBlocks(
this.options.ownerDocument,
this.elements[editableElementIndex],
range.startContainer,
range.startOffset);
if (emptyBlocksIndex !== 0) {
selectionState.emptyBlocksIndex = emptyBlocksIndex;
}
}
}

@@ -992,2 +1003,21 @@ }

if (inSelectionState.emptyBlocksIndex && selectionState.end === nextCharIndex) {
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.getFirstLeafNode(targetNode), 0);
range.collapse(true);
}
// If the selection is right at the ending edge of a link, put it outside the anchor tag instead of inside.

@@ -997,3 +1027,2 @@ if (favorLaterSelectionAnchor) {

}
sel = this.options.contentWindow.getSelection();

@@ -1000,0 +1029,0 @@ sel.removeAllRanges();

@@ -58,5 +58,6 @@ var AnchorForm;

var selectedParentElement = Selection.getSelectedParentElement(Selection.getSelectionRange(this.document));
if (selectedParentElement.tagName &&
selectedParentElement.tagName.toLowerCase() === 'a') {
var selectedParentElement = Selection.getSelectedParentElement(Selection.getSelectionRange(this.document)),
firstTextNode = Util.getFirstTextNode(selectedParentElement);
if (Util.getClosestTag(firstTextNode, 'a')) {
return this.execAction('unlink');

@@ -63,0 +64,0 @@ }

/*global Extension, Util */
var AutoLink,
WHITESPACE_CHARS,
KNOWN_TLDS_FRAGMENT,
LINK_REGEXP_TEXT;
WHITESPACE_CHARS = [' ', '\t', '\n', '\r', '\u00A0', '\u2000', '\u2001', '\u2002', '\u2003',
'\u2028', '\u2029'];
KNOWN_TLDS_FRAGMENT = 'com|net|org|edu|gov|mil|aero|asia|biz|cat|coop|info|int|jobs|mobi|museum|name|post|pro|tel|travel|' +

@@ -128,8 +131,12 @@ 'xxx|ac|ad|ae|af|ag|ai|al|am|an|ao|aq|ar|as|at|au|aw|ax|az|ba|bb|bd|be|bf|bg|bh|bi|bj|bm|bn|bo|br|bs|bt|bv|bw|by|' +

documentModified = true;
// Some editing has happened to the span, so just remove it entirely. The user can put it back
// around just the href content if they need to prevent it from linking
while (spans[i].childNodes.length > 0) {
spans[i].parentNode.insertBefore(spans[i].firstChild, spans[i]);
var trimmedTextContent = textContent.replace(/\s+$/, '');
if (spans[i].getAttribute('data-href') === trimmedTextContent) {
var charactersTrimmed = textContent.length - trimmedTextContent.length,
subtree = Util.splitOffDOMTree(spans[i], this.splitTextBeforeEnd(spans[i], charactersTrimmed));
spans[i].parentNode.insertBefore(subtree, spans[i].nextSibling);
} else {
// Some editing has happened to the span, so just remove it entirely. The user can put it back
// around just the href content if they need to prevent it from linking
Util.unwrap(spans[i], this.document);
}
spans[i].parentNode.removeChild(spans[i]);
}

@@ -140,2 +147,28 @@ }

splitTextBeforeEnd: function (element, characterCount) {
var treeWalker = this.document.createTreeWalker(element, NodeFilter.SHOW_TEXT, null, false),
lastChildNotExhausted = true;
// Start the tree walker at the last descendant of the span
while (lastChildNotExhausted) {
lastChildNotExhausted = treeWalker.lastChild() !== null;
}
var currentNode,
currentNodeValue,
previousNode;
while (characterCount > 0 && previousNode !== null) {
currentNode = treeWalker.currentNode;
currentNodeValue = currentNode.nodeValue;
if (currentNodeValue.length > characterCount) {
previousNode = currentNode.splitText(currentNodeValue.length - characterCount);
characterCount = 0;
} else {
previousNode = treeWalker.previousNode();
characterCount -= currentNodeValue.length;
}
}
return previousNode;
},
performLinkingWithinElement: function (element) {

@@ -154,4 +187,2 @@ var matches = this.findLinkableText(element),

var linkRegExp = new RegExp(LINK_REGEXP_TEXT, 'gi'),
whitespaceChars = [' ', '\t', '\n', '\r', '\u00A0', '\u2000', '\u2001', '\u2002', '\u2003',
'\u2028', '\u2029'],
textContent = contenteditable.textContent,

@@ -165,4 +196,4 @@ match = null,

// If the regexp detected something as a link that has text immediately preceding/following it, bail out.
matchOk = (match.index === 0 || whitespaceChars.indexOf(textContent[match.index - 1]) !== -1) &&
(matchEnd === textContent.length || whitespaceChars.indexOf(textContent[matchEnd]) !== -1);
matchOk = (match.index === 0 || WHITESPACE_CHARS.indexOf(textContent[match.index - 1]) !== -1) &&
(matchEnd === textContent.length || WHITESPACE_CHARS.indexOf(textContent[matchEnd]) !== -1);
// If the regexp detected a bare domain that doesn't use one of our expected TLDs, bail out.

@@ -169,0 +200,0 @@ matchOk = matchOk && (match[0].indexOf('/') !== -1 ||

@@ -7,2 +7,10 @@ /*global Util */

function filterOnlyParentElements(node) {
if (Util.parentElements.indexOf(node.nodeName.toLowerCase()) !== -1) {
return NodeFilter.FILTER_ACCEPT;
} else {
return NodeFilter.FILTER_SKIP;
}
}
Selection = {

@@ -63,2 +71,41 @@ findMatchingSelectionParent: function (testElementFunction, contentWindow) {

// 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.
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;
}
// 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;
}
// Walk over block elements, counting number of empty blocks between last piece of text
// and the block the cursor is in
var treeWalker = doc.createTreeWalker(root, NodeFilter.SHOW_ELEMENT, filterOnlyParentElements, false),
emptyBlocksCount = 0;
while (treeWalker.nextNode()) {
var blockIsEmpty = treeWalker.currentNode.textContent === '';
if (blockIsEmpty || emptyBlocksCount > 0) {
emptyBlocksCount += 1;
}
if (Util.isDescendant(treeWalker.currentNode, cursorContainer, true)) {
return emptyBlocksCount;
}
if (!blockIsEmpty) {
emptyBlocksCount = 0;
}
}
return emptyBlocksCount;
},
selectionInContentEditableFalse: function (contentWindow) {

@@ -65,0 +112,0 @@ // determine if the current selection is exclusively inside

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

/*global NodeFilter, console, Selection*/
/*global NodeFilter, Selection*/

@@ -652,2 +652,46 @@ var Util;

isElementAtBeginningOfBlock: function (node) {
var textVal,
sibling;
while (node.nodeType === 3 ||
(this.parentElements.indexOf(node.tagName.toLowerCase()) === -1 && !node.getAttribute('data-medium-element'))) { // TODO: Change this in v5.0.0
sibling = node;
while (sibling = sibling.previousSibling) {
textVal = sibling.nodeType === 3 ? sibling.nodeValue : sibling.textContent;
if (textVal.length > 0) {
return false;
}
}
node = node.parentNode;
}
return true;
},
getBlockContainer: function (element) {
return this.traverseUp(element, function (el) {
return Util.parentElements.indexOf(el.tagName.toLowerCase()) !== -1;
});
},
getFirstLeafNode: function (element) {
while (element && element.firstChild) {
element = element.firstChild;
}
return element;
},
getFirstTextNode: function (element) {
if (element.nodeType === 3) {
return element;
}
for (var i = 0; i < element.childNodes.length; i++) {
var textNode = this.getFirstTextNode(element.childNodes[i]);
if (textNode !== null) {
return textNode;
}
}
return null;
},
ensureUrlHasProtocol: function (url) {

@@ -662,3 +706,3 @@ if (url.indexOf('://') === -1) {

if (window.console !== undefined && typeof window.console.warn === 'function') {
window.console.warn.apply(console, arguments);
window.console.warn.apply(window.console, arguments);
}

@@ -665,0 +709,0 @@ },

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

// grunt-bump looks for this:
'version': '4.12.2'
'version': '4.12.3'
}).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

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc