medium-editor
Advanced tools
Comparing version 4.10.1 to 4.10.2
{ | ||
"name": "medium-editor", | ||
"version": "4.10.1", | ||
"version": "4.10.2", | ||
"homepage": "http://daviferreira.github.io/medium-editor/", | ||
@@ -5,0 +5,0 @@ "authors": [ |
@@ -0,1 +1,12 @@ | ||
4.10.2 / 2015-05-21 | ||
================== | ||
* Auto-Link Fixes | ||
* Don't auto-link text after it is manually unlinked | ||
* Trigger auto-linking when focus is lost (ie Tab key) | ||
* Fix issue where link appears and immediately disappears when hitting Enter in IE11 | ||
* Fix issue where hostname with more than three w's only auto-links final three w's in the name | ||
* Fix issue where valid urls were not auto-linked | ||
* Fix issue where some text was auto-linked when it shouldn't be | ||
4.10.1 / 2015-05-20 | ||
@@ -2,0 +13,0 @@ ================== |
@@ -34,4 +34,4 @@ /*global module, require, process*/ | ||
'src/js/extensions/paste.js', | ||
'src/js/extensions/placeholder.js', | ||
'src/js/toolbar.js', | ||
'src/js/placeholders.js', | ||
'src/js/defaults/options.js', | ||
@@ -38,0 +38,0 @@ 'src/js/defaults/extensions.js', |
{ | ||
"name": "medium-editor", | ||
"version": "4.10.1", | ||
"version": "4.10.2", | ||
"author": "Davi Ferreira <hi@daviferreira.com>", | ||
@@ -5,0 +5,0 @@ "contributors": [ |
@@ -76,12 +76,8 @@ # MediumEditor | ||
* __delay__: time in milliseconds to show the toolbar or anchor tag preview. Default: 0 | ||
* __autoLink__: enables/disables the auto-link feature, which automatically turns URLs entered into the text field into HTML anchor tags (similar to the functionality of Markdown). Default: false | ||
* __disableReturn__: enables/disables the use of the return-key. You can also set specific element behavior by using setting a data-disable-return attribute. Default: false | ||
* __disableDoubleReturn__: allows/disallows two (or more) empty new lines. You can also set specific element behavior by using setting a data-disable-double-return attribute. Default: false | ||
* __disableEditing__: enables/disables adding the contenteditable behavior. Useful for using the toolbar with customized buttons/actions. You can also set specific element behavior by using setting a data-disable-editing attribute. Default: false | ||
* __disablePlaceholders__: enables/disables support for __placeholder__, including DOM element creation and attaching event handlers. When disabled, medium-editor will ignore the __placeholder__ option and not show placeholder text. Default: false | ||
* __elementsContainer__: specifies a DOM node to contain MediumEditor's toolbar and anchor preview elements. Default: document.body | ||
* __extensions__: extension to use (see [Custom Buttons and Extensions](https://github.com/daviferreira/medium-editor/wiki/Custom-Buttons-and-Extensions)) for more. Default: {} | ||
* __firstHeader__: HTML tag to be used as first header. Default: h3 | ||
* __imageDragging__: Allows image drag and drop into the editor. Default: true | ||
* __placeholder__: Defines the default placeholder for empty contenteditables when __disablePlaceholders__ is not set to true. You can overwrite it by setting a data-placeholder attribute on your elements. Default: 'Type your text' | ||
* __secondHeader__: HTML tag to be used as second header. Default: h4 | ||
@@ -117,3 +113,4 @@ * __spellcheck__: Enable/disable native contentEditable automatic spellcheck. Default: true | ||
anchorPreview: { | ||
// These are the default options for anchor form, if nothing is passed this is what it used | ||
/* These are the default options for anchor preview, | ||
if nothing is passed this is what it used */ | ||
hideDelay: 500, | ||
@@ -139,2 +136,27 @@ previewValueSelector: 'a' | ||
### Placeholder Options | ||
The placeholder handler is a built-in extension which displays placeholder text when the editor is empty. | ||
Options for placeholder are passed as an object that is a member of the outer options object. Example: | ||
```javascript | ||
var editor = new MediumEditor('.editable', { | ||
buttons: ['bold', 'italic', 'quote'], | ||
placeholder: { | ||
/* This example includes the default options for placeholder, | ||
if nothing is passed this is what it used */ | ||
text: 'Type your text' | ||
} | ||
}); | ||
``` | ||
* __text__: Defines the default placeholder for empty contenteditables when __placeholder__ is not set to false. You can overwrite it by setting a `data-placeholder` attribute on the editor elements. Default: 'Type your text' | ||
To disable the placeholder, set the value of the `placeholder` option to `false`: | ||
```javascript | ||
var editor = new MediumEditor('.editable', { | ||
placeholder: false | ||
}); | ||
``` | ||
### Anchor Form options | ||
@@ -149,3 +171,4 @@ | ||
anchor: { | ||
// These are the default options for anchor form, if nothing is passed this is what it used | ||
/* These are the default options for anchor form, | ||
if nothing is passed this is what it used */ | ||
customClassOption: null, | ||
@@ -178,3 +201,4 @@ customClassOptionText: 'Button', | ||
paste: { | ||
// This example includes the default options for paste, if nothing is passed this is what it used | ||
/* This example includes the default options for paste, | ||
if nothing is passed this is what it used */ | ||
forcePlainText: true, | ||
@@ -195,2 +219,25 @@ cleanPastedHTML: false, | ||
### Auto Link Options | ||
The auto-link handler is a built-in extension which automatically turns URLs entered into the text field into HTML anchor tags (similar to the functionality of Markdown). This feature is OFF by default. | ||
To enable built-in auto-link support, set the value of the `autoLink` option to `true': | ||
```javascript | ||
var editor = new MediumEditor('.editable', { | ||
autoLink: true | ||
}); | ||
``` | ||
### Image Dragging Options | ||
The image dragging handler is a built-in extenson for handling dragging & dropping images into the contenteditable. This feature is ON by default. | ||
To disable built-in image dragging, set the value of the `imageDragging` option to `false`: | ||
```javascript | ||
var editor = new MediumEditor('.editable', { | ||
imageDragging: false | ||
}); | ||
``` | ||
### Options Example: | ||
@@ -219,4 +266,6 @@ | ||
hideDelay: 300 | ||
}, | ||
placeholder: { | ||
text: 'Click to edit' | ||
} | ||
} | ||
}); | ||
@@ -223,0 +272,0 @@ ``` |
@@ -29,5 +29,7 @@ /*global describe, it, expect, beforeEach, afterEach, | ||
'http://www.royal.gov.uk', | ||
'http://www.bbc.co.uk', | ||
'http://mountaindew.com', | ||
'http://coca-cola.com', | ||
'http://example.com', | ||
'http://wwww.example.com', // with more "w"s it's still a valid subdomain | ||
'http://www.example.com', | ||
@@ -39,2 +41,8 @@ 'http://www.example.com/foo/bar', | ||
'http://www.example.com/#buzz' | ||
], | ||
notLinks = [ | ||
'http:google.com', | ||
'http:/example.com', | ||
'app.can', | ||
'sadasda.sdfasf.sdfas' | ||
]; | ||
@@ -65,2 +73,3 @@ | ||
expect('Text content: ' + anchors[0].textContent).toBe('Text content: ' + link); | ||
expect(anchors[0].firstChild.getAttribute('data-auto-link')).toBe('true'); | ||
}; | ||
@@ -81,2 +90,22 @@ } | ||
function generateNotLinkTest(link) { | ||
return function () { | ||
var selection = window.getSelection(), | ||
newRange = document.createRange(); | ||
this.el.innerHTML = '<p>' + link + ' </p>'; | ||
selection.removeAllRanges(); | ||
newRange.setStart(this.el.firstChild.childNodes[0], link.length + 1); | ||
newRange.setEnd(this.el.firstChild.childNodes[0], link.length + 1); | ||
selection.addRange(newRange); | ||
triggerAutolinking(this.el); | ||
var anchors = this.el.getElementsByTagName('a'); | ||
expect(anchors.length).toBe(0); | ||
}; | ||
} | ||
notLinks.forEach(function (link) { | ||
it('should not auto-link "' + link + '" when typed in', generateNotLinkTest(link)); | ||
}); | ||
it('should auto-link text on its own', function () { | ||
@@ -87,3 +116,7 @@ this.el.innerHTML = 'http://www.example.com'; | ||
triggerAutolinking(this.el); | ||
expect(this.el.innerHTML).toBe('<a href="http://www.example.com">http://www.example.com</a>'); | ||
var links = this.el.getElementsByTagName('a'); | ||
expect(links.length).toBe(1); | ||
expect(links[0].getAttribute('href')).toBe('http://www.example.com'); | ||
expect(links[0].firstChild.getAttribute('data-auto-link')).toBe('true'); | ||
expect(links[0].textContent).toBe('http://www.example.com'); | ||
}); | ||
@@ -96,3 +129,8 @@ | ||
triggerAutolinking(this.el); | ||
expect(this.el.innerHTML).toBe('Text with <a href="http://www.example.com">http://www.example.com</a> inside!'); | ||
var links = this.el.getElementsByTagName('a'); | ||
expect(links.length).toBe(1); | ||
expect(links[0].getAttribute('href')).toBe('http://www.example.com'); | ||
expect(links[0].getAttribute('href')).toBe(links[0].textContent); | ||
expect(this.el.childNodes[0].nodeValue).toBe('Text with '); | ||
expect(this.el.childNodes[this.el.childNodes.length - 1].nodeValue).toBe(' inside!'); | ||
}); | ||
@@ -105,3 +143,10 @@ | ||
triggerAutolinking(this.el); | ||
expect(this.el.innerHTML).toBe('<span>Text with <a href="http://www.example.com">http://www.example.com</a> inside!</span>'); | ||
var links = this.el.getElementsByTagName('a'); | ||
expect(links.length).toBe(1); | ||
expect(this.el.firstChild.tagName.toLowerCase()).toBe('span'); | ||
expect(this.el.firstChild.textContent).toBe('Text with http://www.example.com inside!'); | ||
expect(this.el.firstChild.getElementsByTagName('a').length).toBe(1); | ||
expect(links[0].getAttribute('href')).toBe('http://www.example.com'); | ||
expect(links[0].textContent).toBe('http://www.example.com'); | ||
expect(links[0].firstChild.getAttribute('data-auto-link')).toBe('true'); | ||
}); | ||
@@ -120,4 +165,11 @@ | ||
expect(this.el.innerHTML).toBe('<p><span class="a"><b>Here is the link: </b></span>' + | ||
'<a href="http://www.example.com"><span class="a"><b>http://www.</b>exa</span>mple.com</a> </p>'); | ||
expect(this.el.innerHTML.indexOf('<p><span class="a"><b>Here is the link: </b></span>')).toBe(0); | ||
var links = this.el.getElementsByTagName('a'); | ||
expect(links.length).toBe(1); | ||
expect(links[0].innerHTML).toBe('<span data-auto-link="true">' + | ||
'<span class="a"><b>http://www.</b>exa</span>mple.com</span>'); | ||
expect(links[0].getAttribute('href')).toBe('http://www.example.com'); | ||
expect(links[0].firstChild.getAttribute('data-auto-link')).toBe('true'); | ||
expect(this.el.firstChild.lastChild.nodeValue).toBe(' '); | ||
}); | ||
@@ -136,4 +188,10 @@ | ||
expect(this.el.innerHTML).toBe('<p><b>Here is the link: </b>' + | ||
'<a href="http://www.example.com"><b>http://www.</b>exampl<b>e</b>.com</a> </p>'); | ||
expect(this.el.innerHTML.indexOf('<p><b>Here is the link: </b>')).toBe(0); | ||
var links = this.el.getElementsByTagName('a'); | ||
expect(links.length).toBe(1); | ||
expect(links[0].innerHTML).toBe('<span data-auto-link="true">' + | ||
'<b>http://www.</b>exampl<b>e</b>.com</span>'); | ||
expect(links[0].getAttribute('href')).toBe('http://www.example.com'); | ||
expect(links[0].firstChild.getAttribute('data-auto-link')).toBe('true'); | ||
expect(this.el.firstChild.lastChild.nodeValue).toBe(' '); | ||
}); | ||
@@ -160,2 +218,6 @@ | ||
var links = this.el.getElementsByTagName('a'); | ||
expect(links.length).toBe(1); | ||
expect(links[0].firstChild.getAttribute('data-auto-link')).toBe('true'); | ||
var expectedOutput = '' + | ||
@@ -166,13 +228,15 @@ '<span>' + | ||
'<a href="http://www.google.com/wow">' + | ||
'<span>' + | ||
'<b>http</b>' + | ||
'<i>://</i>' + | ||
'<span data-auto-link="true">' + | ||
'<span>' + | ||
'<b>http</b>' + | ||
'<i>://</i>' + | ||
'</span>' + | ||
'<span>' + | ||
'<b>www</b>' + | ||
'<u>.google.com</u>' + | ||
'</span>' + | ||
'<span>' + | ||
'<b>/wow</b>' + | ||
'</span>' + | ||
'</span>' + | ||
'<span>' + | ||
'<b>www</b>' + | ||
'<u>.google.com</u>' + | ||
'</span>' + | ||
'<span>' + | ||
'<b>/wow</b>' + | ||
'</span>' + | ||
'</a>' + | ||
@@ -193,8 +257,32 @@ '<span>' + | ||
triggerAutolinking(this.el); | ||
expect(this.el.innerHTML).toBe('Click this <a href="http://www.example.com">http://www.example.com</a> link'); | ||
var links = this.el.getElementsByTagName('a'); | ||
expect(links.length).toBe(1); | ||
expect(links[0].getAttribute('href')).toBe('http://www.example.com'); | ||
expect(links[0].firstChild.getAttribute('data-auto-link')).toBe('true'); | ||
triggerAutolinking(this.el); | ||
expect(this.el.innerHTML).toBe('Click this <a href="http://www.example.com">http://www.example.com</a> link'); | ||
links = this.el.getElementsByTagName('a'); | ||
expect(links.length).toBe(1); | ||
expect(links[0].getAttribute('href')).toBe('http://www.example.com'); | ||
expect(links[0].firstChild.getAttribute('data-auto-link')).toBe('true'); | ||
}); | ||
it('should not auto-link text inside a span with data-auto-link=true', function () { | ||
this.el.innerHTML = 'Click this <span data-auto-link="true">http://www.example.com</span> link'; | ||
selectElementContentsAndFire(this.el.firstChild); | ||
triggerAutolinking(this.el); | ||
expect(this.el.getElementsByTagName('a').length).toBe(0, 'should not create a link'); | ||
}); | ||
it('should not auto-link text containing a span with data-auto-link=true', function () { | ||
this.el.innerHTML = 'Click this <span data-auto-link="true">http://www.example.com</span>foo/bar/baz link'; | ||
selectElementContentsAndFire(this.el.firstChild); | ||
triggerAutolinking(this.el); | ||
expect(this.el.getElementsByTagName('a').length).toBe(0, 'should not create a link'); | ||
}); | ||
it('should stop attempting to auto-link on keypress if an error is encountered', function () { | ||
@@ -201,0 +289,0 @@ var spy = spyOn(MediumEditor.extensions.autoLink.prototype, 'performLinking').and.throwError('DOM ERROR'); |
@@ -100,3 +100,2 @@ /*global MediumEditor, describe, it, expect, spyOn, | ||
disableToolbar: false, | ||
disablePlaceholders: false, | ||
autoLink: false, | ||
@@ -111,3 +110,2 @@ toolbarAlign: 'center', | ||
allowMultiParagraphSelection: true, | ||
placeholder: 'Type your text', | ||
secondHeader: 'h4', | ||
@@ -114,0 +112,0 @@ buttons: ['bold', 'italic', 'underline', 'anchor', 'header1', 'header2', 'quote'], |
/*global describe, it, expect, | ||
afterEach, beforeEach, fireEvent, setupTestHelpers */ | ||
afterEach, beforeEach, fireEvent, setupTestHelpers, | ||
Placeholder */ | ||
@@ -96,5 +97,5 @@ describe('Placeholder TestCase', function () { | ||
// In firefox, getComputedStyle().getPropertyValue('content') can return attr() instead of what attr() evaluates to | ||
expect(match[1]).toEqual('data-placeholder'); | ||
expect(match[1]).toBe('data-placeholder'); | ||
} else { | ||
expect(placeholder).toEqual('\'' + expectedValue + '\''); | ||
expect(placeholder).toBe('\'' + expectedValue + '\''); | ||
} | ||
@@ -106,3 +107,3 @@ } | ||
var editor = this.newMediumEditor('.editor'); | ||
validatePlaceholderContent(editor.elements[0], editor.options.placeholder); | ||
validatePlaceholderContent(editor.elements[0], Placeholder.prototype.text); | ||
}); | ||
@@ -118,3 +119,21 @@ | ||
it('should not set placeholder for empty elements when disablePlaceholders is set to true', function () { | ||
it('should use custom placeholder text when passed as a deprecated option', function () { | ||
var placeholderText = 'Custom placeholder', | ||
editor = this.newMediumEditor('.editor', { | ||
placeholder: placeholderText | ||
}); | ||
validatePlaceholderContent(editor.elements[0], placeholderText); | ||
}); | ||
it('should use custom placeholder text when passed as the placeholder.text option', function () { | ||
var placeholderText = 'Custom placeholder', | ||
editor = this.newMediumEditor('.editor', { | ||
placeholder: { | ||
text: placeholderText | ||
} | ||
}); | ||
validatePlaceholderContent(editor.elements[0], placeholderText); | ||
}); | ||
it('should not set placeholder for empty elements when deprecated disablePlaceholders is set to true', function () { | ||
var editor = this.newMediumEditor('.editor', { | ||
@@ -126,6 +145,13 @@ disablePlaceholders: true | ||
it('should not add a placeholder to empty elements on blur when disablePlaceholders is set to true', function () { | ||
it('should not set placeholder for empty elements when placeholder is set to false', function () { | ||
var editor = this.newMediumEditor('.editor', { | ||
placeholder: false | ||
}); | ||
expect(editor.elements[0].className).not.toContain('medium-editor-placeholder'); | ||
}); | ||
it('should not add a placeholder to empty elements on blur when placeholder is set to false', function () { | ||
this.el.innerHTML = 'some text'; | ||
var editor = this.newMediumEditor('.editor', { | ||
disablePlaceholders: true | ||
placeholder: false | ||
}); | ||
@@ -132,0 +158,0 @@ expect(editor.elements[0].className).not.toContain('medium-editor-placeholder'); |
/*global MediumEditor, describe, it, expect, spyOn, | ||
afterEach, beforeEach, fireEvent, | ||
jasmine, selectElementContents, setupTestHelpers, | ||
selectElementContentsAndFire, Selection */ | ||
selectElementContentsAndFire, Selection, placeCursorInsideElement */ | ||
@@ -43,2 +43,25 @@ describe('Selection TestCase', function () { | ||
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', { | ||
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'); | ||
}); | ||
it('should have an index in the exported selection when it is in the second contenteditable', function () { | ||
@@ -45,0 +68,0 @@ this.createElement('div', 'editor', 'lorem <i>ipsum</i> dolor'); |
/*global Util, ButtonsData, Button, | ||
Selection, FontSizeForm, Extension, extensionDefaults, | ||
Toolbar, AutoLink, ImageDragging, Events, Placeholders, | ||
editorDefaults, | ||
Toolbar, AutoLink, ImageDragging, Events, editorDefaults, | ||
DefaultButton, AnchorExtension, FontSizeExtension, AnchorPreviewDeprecated */ | ||
@@ -235,2 +234,15 @@ | ||
function shouldAddDefaultPlaceholder() { | ||
if (this.options.extensions['placeholder']) { | ||
return false; | ||
} | ||
// TODO: deprecated | ||
if (this.options.disablePlaceholders) { | ||
return false; | ||
} | ||
return this.options.placeholder !== false; | ||
} | ||
function shouldAddDefaultAutoLink() { | ||
@@ -241,3 +253,3 @@ if (this.options.extensions['auto-link']) { | ||
return !!this.options.autoLink; | ||
return this.options.autoLink !== false; | ||
} | ||
@@ -250,3 +262,3 @@ | ||
return !!this.options.imageDragging; | ||
return this.options.imageDragging !== false; | ||
} | ||
@@ -293,5 +305,2 @@ | ||
} | ||
if (!element.getAttribute('data-placeholder')) { | ||
element.setAttribute('data-placeholder', this.options.placeholder); | ||
} | ||
element.setAttribute('data-medium-element', true); | ||
@@ -362,2 +371,15 @@ element.setAttribute('role', 'textbox'); | ||
function initPlaceholder(options) { | ||
// Backwards compatability | ||
var defaultsBC = { | ||
text: (typeof this.options.placeholder === 'string') ? this.options.placeholder : undefined, // deprecated | ||
'window': this.options.contentWindow, | ||
'document': this.options.ownerDocument | ||
}; | ||
return new MediumEditor.extensions.placeholder( | ||
Util.extend({}, options, defaultsBC) | ||
); | ||
} | ||
function initAnchorPreview(options) { | ||
@@ -459,2 +481,7 @@ // Backwards compatability | ||
} | ||
if (shouldAddDefaultPlaceholder.call(this)) { | ||
var placeholderOpts = (typeof this.options.placeholder === 'string') ? {} : this.options.placeholder; | ||
this.commands.push(initExtension(initPlaceholder.call(this, placeholderOpts), 'placeholder', this)); | ||
} | ||
} | ||
@@ -473,3 +500,4 @@ | ||
['anchorPreviewHideDelay', 'anchorPreview.hideDelay'], | ||
['disableAnchorPreview', 'anchorPreview: false'] | ||
['disableAnchorPreview', 'anchorPreview: false'], | ||
['disablePlaceholders', 'placeholder: false'] | ||
]; | ||
@@ -483,2 +511,6 @@ // warn about using deprecated properties | ||
}); | ||
if (options.hasOwnProperty('placeholder') && typeof options.placeholder === 'string') { | ||
Util.deprecated('placeholder', 'placeholder.text', 'v5.0.0'); | ||
} | ||
} | ||
@@ -572,6 +604,2 @@ return Util.defaults({}, options, defaults); | ||
attachHandlers.call(this); | ||
if (!this.options.disablePlaceholders) { | ||
this.placeholders = new Placeholders(this); | ||
} | ||
}, | ||
@@ -878,3 +906,9 @@ | ||
// TODO: move to selection.js and clean up old methods there | ||
importSelection: function (inSelectionState) { | ||
// | ||
// {object} inSelectionState - the selection to import | ||
// {boolean} [favorLaterSelectionAnchor] - defaults to false. If true, import the cursor immediately | ||
// subsequent to an anchor tag if it would otherwise be placed right at the trailing edge inside the | ||
// anchor. This cursor positioning, even though visually equivalent to the user, can affect behavior | ||
// in MS IE. | ||
importSelection: function (inSelectionState, favorLaterSelectionAnchor) { | ||
if (!inSelectionState) { | ||
@@ -930,2 +964,7 @@ return; | ||
// If the selection is right at the ending edge of a link, put it outside the anchor tag instead of inside. | ||
if (favorLaterSelectionAnchor) { | ||
range = Selection.importSelectionMoveCursorPastAnchor(selectionState, range); | ||
} | ||
sel = this.options.contentWindow.getSelection(); | ||
@@ -932,0 +971,0 @@ sel.removeAllRanges(); |
/*global Button, FormExtension, | ||
AnchorForm, AnchorPreview, AutoLink, | ||
FontSizeForm, ImageDragging, PasteHandler */ | ||
FontSizeForm, ImageDragging, PasteHandler, | ||
Placeholder */ | ||
@@ -18,4 +19,5 @@ var extensionDefaults; | ||
imageDragging: ImageDragging, | ||
paste: PasteHandler | ||
paste: PasteHandler, | ||
placeholder: Placeholder | ||
}; | ||
})(); |
@@ -18,3 +18,2 @@ var editorDefaults; | ||
disableEditing: false, | ||
disablePlaceholders: false, | ||
autoLink: false, | ||
@@ -28,3 +27,2 @@ toolbarAlign: 'center', | ||
firstHeader: 'h3', | ||
placeholder: 'Type your text', | ||
secondHeader: 'h4', | ||
@@ -31,0 +29,0 @@ targetBlank: false, |
@@ -9,5 +9,5 @@ /*global Extension, Util */ | ||
// Version of Gruber URL Regexp optimized for JS: http://stackoverflow.com/a/17733640 | ||
'((?:[a-z][\\w-]+:(?:\\\/{1,3}|[a-z0-9%])|www\\d{0,3}[.]|[a-z0-9.\\-]+[.][a-z]{2,4}\\\/)\\S+(?:[^\\s`!\\[\\]{};:\'\".,?\u00AB\u00BB\u201C\u201D\u2018\u2019]))' + | ||
// Addition to above Regexp to support bare domains with common non-i18n TLDs and without www prefix: | ||
')|([a-z0-9\\-]+\\.(com|net|org|edu|gov|mil|aero|asia|biz|cat|coop|info|int|jobs|mobi|museum|name|post|pro|tel|travel|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|bz|ca|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|co|cr|cs|cu|cv|cx|cy|cz|dd|de|dj|dk|dm|do|dz|ec|ee|eg|eh|er|es|et|eu|fi|fj|fk|fm|fo|fr|ga|gb|gd|ge|gf|gg|gh|gi|gl|gm|gn|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|il|im|in|io|iq|ir|is|it|je|jm|jo|jp|ke|kg|kh|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|lr|ls|lt|lu|lv|ly|ma|mc|md|me|mg|mh|mk|ml|mm|mn|mo|mp|mq|mr|ms|mt|mu|mv|mw|mx|my|mz|na|nc|ne|nf|ng|ni|nl|no|np|nr|nu|nz|om|pa|pe|pf|pg|ph|pk|pl|pm|pn|pr|ps|pt|pw|py|qa|re|ro|rs|ru|rw|sa|sb|sc|sd|se|sg|sh|si|sj| Ja|sk|sl|sm|sn|so|sr|ss|st|su|sv|sx|sy|sz|tc|td|tf|tg|th|tj|tk|tl|tm|tn|to|tp|tr|tt|tv|tw|tz|ua|ug|uk|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|ye|yt|yu|za|zm|zw))'; | ||
'((?:(https?://|ftps?://|nntp://)|www\\d{0,3}[.]|[a-z0-9.\\-]+[.][a-z]{2,4}\\\/)\\S+(?:[^\\s`!\\[\\]{};:\'\".,?\u00AB\u00BB\u201C\u201D\u2018\u2019]))' + | ||
// Addition to above Regexp to support bare domains/one level subdomains with common non-i18n TLDs and without www prefix: | ||
')|(([a-z0-9\\-]+\\.)?[a-z0-9\\-]+\\.(com|net|org|edu|gov|mil|aero|asia|biz|cat|coop|info|int|jobs|mobi|museum|name|post|pro|tel|travel|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|bz|ca|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|co|cr|cs|cu|cv|cx|cy|cz|dd|de|dj|dk|dm|do|dz|ec|ee|eg|eh|er|es|et|eu|fi|fj|fk|fm|fo|fr|ga|gb|gd|ge|gf|gg|gh|gi|gl|gm|gn|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|il|im|in|io|iq|ir|is|it|je|jm|jo|jp|ke|kg|kh|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|lr|ls|lt|lu|lv|ly|ma|mc|md|me|mg|mh|mk|ml|mm|mn|mo|mp|mq|mr|ms|mt|mu|mv|mw|mx|my|mz|na|nc|ne|nf|ng|ni|nl|no|np|nr|nu|nz|om|pa|pe|pf|pg|ph|pk|pl|pm|pn|pr|ps|pt|pw|py|qa|re|ro|rs|ru|rw|sa|sb|sc|sd|se|sg|sh|si|sj| Ja|sk|sl|sm|sn|so|sr|ss|st|su|sv|sx|sy|sz|tc|td|tf|tg|th|tj|tk|tl|tm|tn|to|tp|tr|tt|tv|tw|tz|ua|ug|uk|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|ye|yt|yu|za|zm|zw))'; | ||
@@ -23,2 +23,3 @@ (function () { | ||
this.base.subscribe('editableKeypress', this.onKeypress.bind(this)); | ||
this.base.subscribe('editableBlur', this.onBlur.bind(this)); | ||
// MS IE has it's own auto-URL detect feature but ours is better in some ways. Be consistent. | ||
@@ -28,2 +29,6 @@ this.base.options.ownerDocument.execCommand('AutoUrlDetect', false, false); | ||
onBlur: function (blurEvent, editable) { | ||
this.performLinking(editable); | ||
}, | ||
onKeypress: function (keyPressEvent) { | ||
@@ -43,3 +48,5 @@ if (this.disableEventHandling) { | ||
if (this.performLinking(keyPressEvent.target)) { | ||
this.base.importSelection(sel); | ||
// pass true for favorLaterSelectionAnchor - this is needed for links at the end of a | ||
// paragraph in MS IE, or MS IE causes the link to be deleted right after adding it. | ||
this.base.importSelection(sel, true); | ||
} | ||
@@ -108,2 +115,4 @@ } catch (e) { | ||
var linkRegExp = new RegExp(LINK_REGEXP_TEXT, 'gi'), | ||
whitespaceChars = [' ', '\t', '\n', '\r', '\u00A0', '\u2000', '\u2001', '\u2002', '\u2003', | ||
'\u2028', '\u2029'], | ||
textContent = contenteditable.textContent, | ||
@@ -114,7 +123,12 @@ match = null, | ||
while ((match = linkRegExp.exec(textContent)) !== null) { | ||
matches.push({ | ||
href: match[0], | ||
start: match.index, | ||
end: match.index + match[0].length | ||
}); | ||
var matchEnd = match.index + match[0].length; | ||
// If the regexp detected something as a link that has text immediately preceding/following it, bail out. | ||
if ((match.index === 0 || whitespaceChars.indexOf(textContent[match.index - 1]) !== -1) && | ||
(matchEnd === textContent.length || whitespaceChars.indexOf(textContent[matchEnd]) !== -1)) { | ||
matches.push({ | ||
href: match[0], | ||
start: match.index, | ||
end: matchEnd | ||
}); | ||
} | ||
} | ||
@@ -163,12 +177,21 @@ return matches; | ||
createLink: function (textNodes, href) { | ||
var alreadyLinked = Util.traverseUp(textNodes[0], function (node) { | ||
return node.nodeName.toLowerCase() === 'a'; | ||
}); | ||
if (alreadyLinked) { | ||
var shouldNotLink = false; | ||
for (var i = 0; i < textNodes.length && shouldNotLink === false; i++) { | ||
// Do not link if the text node is either inside an anchor or inside span[data-auto-link] | ||
shouldNotLink = !!Util.traverseUp(textNodes[i], function (node) { | ||
return node.nodeName.toLowerCase() === 'a' || | ||
(node.getAttribute && node.getAttribute('data-auto-link') === 'true'); | ||
}); | ||
} | ||
if (shouldNotLink) { | ||
return false; | ||
} | ||
var anchor = document.createElement('a'); | ||
Util.moveTextRangeIntoElement(textNodes[0], textNodes[textNodes.length - 1], anchor); | ||
var anchor = document.createElement('a'), | ||
span = document.createElement('span'); | ||
Util.moveTextRangeIntoElement(textNodes[0], textNodes[textNodes.length - 1], span); | ||
span.setAttribute('data-auto-link', 'true'); | ||
anchor.setAttribute('href', Util.ensureUrlHasProtocol(href)); | ||
span.parentNode.insertBefore(anchor, span); | ||
anchor.appendChild(span); | ||
return true; | ||
@@ -175,0 +198,0 @@ } |
@@ -29,2 +29,35 @@ /*global Util */ | ||
// Utility method called from importSelection only | ||
importSelectionMoveCursorPastAnchor: function (selectionState, range) { | ||
var nodeInsideAnchorTagFunction = function (node) { | ||
return node.nodeName.toLowerCase() === 'a'; | ||
}; | ||
if (selectionState.start === selectionState.end && | ||
range.startContainer.nodeType === 3 && | ||
range.startOffset === range.startContainer.nodeValue.length && | ||
Util.traverseUp(range.startContainer, nodeInsideAnchorTagFunction)) { | ||
var prevNode = range.startContainer, | ||
currentNode = range.startContainer.parentNode; | ||
while (currentNode !== null && currentNode.nodeName.toLowerCase() !== 'a') { | ||
if (currentNode.childNodes[currentNode.childNodes.length - 1] !== prevNode) { | ||
currentNode = null; | ||
} else { | ||
prevNode = currentNode; | ||
currentNode = currentNode.parentNode; | ||
} | ||
} | ||
if (currentNode !== null && currentNode.nodeName.toLowerCase() === 'a') { | ||
var currentNodeIndex = null; | ||
for (var i = 0; currentNodeIndex === null && i < currentNode.parentNode.childNodes.length; i++) { | ||
if (currentNode.parentNode.childNodes[i] === currentNode) { | ||
currentNodeIndex = i; | ||
} | ||
} | ||
range.setStart(currentNode.parentNode, currentNodeIndex + 1); | ||
range.collapse(true); | ||
} | ||
} | ||
return range; | ||
}, | ||
selectionInContentEditableFalse: function (contentWindow) { | ||
@@ -31,0 +64,0 @@ // determine if the current selection is exclusively inside |
@@ -14,3 +14,3 @@ /*global MediumEditor */ | ||
// grunt-bump looks for this: | ||
'version': '4.10.1' | ||
'version': '4.10.2' | ||
}).version.split('.')); |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
1300174
16728
413