medium-editor
Advanced tools
Comparing version 4.10.2 to 4.11.0
{ | ||
"name": "medium-editor", | ||
"version": "4.10.2", | ||
"version": "4.11.0", | ||
"homepage": "http://daviferreira.github.io/medium-editor/", | ||
@@ -5,0 +5,0 @@ "authors": [ |
@@ -0,1 +1,12 @@ | ||
4.11.0 / 2015-05-26 | ||
================== | ||
* Add hideToolbar and showToolbar custom events | ||
* Add hideOnClick option for placeholder extension | ||
* Fix issue with linebreaks in Safari | ||
* Fix issue with calling setup again after destroy | ||
* Add support for CDN hosting | ||
* Pass window and document to each extension | ||
* Deprecate .parent property of extensions | ||
4.10.2 / 2015-05-21 | ||
@@ -2,0 +13,0 @@ ================== |
{ | ||
"name": "medium-editor", | ||
"version": "4.10.2", | ||
"version": "4.11.0", | ||
"author": "Davi Ferreira <hi@daviferreira.com>", | ||
@@ -5,0 +5,0 @@ "contributors": [ |
@@ -38,2 +38,11 @@ # MediumEditor | ||
**Via CDNJS** | ||
[CDNJS hosts this library](https://cdnjs.com/libraries/medium-editor) and you can load it from CDN this way: | ||
```html | ||
<script src="https://cdnjs.cloudflare.com/ajax/libs/medium-editor/4.10.1/medium-editor.min.js"></script> | ||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/medium-editor/4.10.1/medium-editor.min.css" type="text/css" media="screen" charset="utf-8"> | ||
``` | ||
**Manual installation:** | ||
@@ -97,4 +106,2 @@ | ||
* __lastButtonClass__: CSS class added to the last button in the toolbar. Default: 'medium-editor-button-last' | ||
* __onShowToolbar__: optional callback that will be called each time the toolbar is actually shown for this instance of medium-editor. | ||
* __onHideToolbar__: optional callback that will be called each time the toolbar is actually hidden for this instance of medium-editor. | ||
* __staticToolbar__: enable/disable the toolbar always displaying in the same location relative to the medium-editor element. Default: false | ||
@@ -101,0 +108,0 @@ * __stickyToolbar__: enable/disable the toolbar "sticking" to the medium-editor element when the page is being scrolled. Default: false |
@@ -192,2 +192,20 @@ /*global describe, it, expect, spyOn, | ||
}); | ||
it('with ctrl key, should not call formatBlock', function () { | ||
this.el.innerHTML = '<p>lorem ipsum</p>'; | ||
var editor = this.newMediumEditor('.editor'), | ||
p = editor.elements[0].querySelector('p'); | ||
spyOn(document, 'execCommand').and.callThrough(); | ||
placeCursorInsideElement(p, 0); | ||
fireEvent(p, 'keyup', { | ||
keyCode: Util.keyCode.ENTER, | ||
ctrlKey: true | ||
}); | ||
expect(document.execCommand).not.toHaveBeenCalledWith('formatBlock', false, 'p'); | ||
}); | ||
}); | ||
@@ -194,0 +212,0 @@ |
@@ -322,2 +322,34 @@ /*global describe, it, expect, jasmine, | ||
}); | ||
describe('Setup some listeners', function () { | ||
var links = [ | ||
'externalInteraction', | ||
'blur', | ||
'focus', | ||
'editableInput', | ||
'editableClick', | ||
'editableBlur', | ||
'editableKeypress', | ||
'editableKeyup', | ||
'editableKeydown', | ||
'editableKeydownEnter', | ||
'editableKeydownTab', | ||
'editableKeydownDelete', | ||
'editableMouseover', | ||
'editableDrag', | ||
'editableDrop', | ||
'editablePaste' | ||
]; | ||
links.forEach(function (listener) { | ||
it('should setup "' + listener + '" listener', function () { | ||
var editor = this.newMediumEditor('.editor'), | ||
events = new Events(editor); | ||
events.setupListener(listener); | ||
expect(events.listeners[listener]).toBe(true); | ||
}); | ||
}); | ||
}); | ||
}); |
@@ -56,3 +56,3 @@ /*global MediumEditor, describe, it, expect, spyOn, | ||
it('should call init (and pass the instance of itself) on extensions if the method exists', function () { | ||
it('should call init (and pass the deprecated instance of itself) on extensions if the method exists', function () { | ||
var ExtensionOne = function () { | ||
@@ -85,2 +85,35 @@ this.init = function (me) { | ||
}); | ||
it('should set window and document properties on each extension', function () { | ||
var TempExtension = MediumEditor.Extension.extend({}), | ||
extInstance = new TempExtension(), | ||
fakeDocument = { | ||
body: document.body, | ||
documentElement: document.documentElement, | ||
querySelectorAll: function () { | ||
return document.querySelectorAll.apply(document, arguments); | ||
}, | ||
createElement: function () { | ||
return document.createElement.apply(document, arguments); | ||
} | ||
}, | ||
fakeWindow = { | ||
addEventListener: function () { | ||
return window.addEventListener.apply(window, arguments); | ||
}, | ||
removeEventListener: function () { | ||
return window.removeEventListener.apply(window, arguments); | ||
} | ||
}; | ||
this.newMediumEditor('.editor', { | ||
ownerDocument: fakeDocument, | ||
contentWindow: fakeWindow, | ||
extensions: { | ||
'temp-extension': extInstance | ||
} | ||
}); | ||
expect(extInstance.window).toBe(fakeWindow); | ||
expect(extInstance.document).toBe(fakeDocument); | ||
}); | ||
}); | ||
@@ -110,3 +143,2 @@ | ||
Sub = Extension.extend({ | ||
parent: true, | ||
y: 10 | ||
@@ -221,47 +253,62 @@ }); | ||
describe('Set data in extensions', function () { | ||
var ExtensionOne = function () { | ||
this.parent = true; | ||
}, | ||
ExtensionTwo = function () {}, | ||
extOne = new ExtensionOne(), | ||
extTwo = new ExtensionTwo(); | ||
it('should set the base property to an instance of MediumEditor', function () { | ||
var extOne = new MediumEditor.Extension(), | ||
editor = this.newMediumEditor('.editor', { | ||
extensions: { | ||
'one': extOne | ||
} | ||
}); | ||
it('should check if extension class has parent attribute', function () { | ||
var editor = this.newMediumEditor('.editor', { | ||
extensions: { | ||
'one': extOne, | ||
'two': extTwo | ||
} | ||
}); | ||
expect(editor instanceof MediumEditor).toBeTruthy(); | ||
expect(extOne.parent).toBeTruthy(); | ||
expect(extTwo.parent).toBeUndefined(); | ||
expect(extOne.base instanceof MediumEditor).toBeTruthy(); | ||
}); | ||
it('should set the base attribute to be an instance of editor', function () { | ||
var editor = this.newMediumEditor('.editor', { | ||
extensions: { | ||
'one': extOne, | ||
'two': extTwo | ||
} | ||
}); | ||
it('should not set the base property when deprecated parent attribute is set to false', function () { | ||
var TempExtension = MediumEditor.Extension.extend({ | ||
parent: false | ||
}), | ||
editor = this.newMediumEditor('.editor', { | ||
extensions: { | ||
'temp': new TempExtension() | ||
} | ||
}); | ||
expect(editor instanceof MediumEditor).toBeTruthy(); | ||
expect(extOne.base instanceof MediumEditor).toBeTruthy(); | ||
expect(extTwo.base).toBeUndefined(); | ||
expect(editor.getExtensionByName('temp').base).toBeUndefined(); | ||
}); | ||
it('should set the name of the extension', function () { | ||
this.newMediumEditor('.editor', { | ||
extensions: { | ||
'one': extOne, | ||
'two': extTwo | ||
} | ||
}); | ||
it('should not override the base or name properties of an extension if overriden', function () { | ||
var TempExtension = MediumEditor.Extension.extend({ | ||
name: 'tempExtension', | ||
base: 'something' | ||
}), | ||
editor = this.newMediumEditor('.editor', { | ||
extensions: { | ||
'one': new TempExtension() | ||
} | ||
}); | ||
expect(editor.getExtensionByName('one')).toBeUndefined(); | ||
expect(editor.getExtensionByName('tempExtension').base).toBe('something'); | ||
}); | ||
it('should set the name of property of extensions', function () { | ||
var ExtensionOne = function () {}, | ||
ExtensionTwo = function () {}, | ||
extOne = new ExtensionOne(), | ||
extTwo = new ExtensionTwo(), | ||
editor = this.newMediumEditor('.editor', { | ||
extensions: { | ||
'one': extOne, | ||
'two': extTwo | ||
} | ||
}); | ||
expect(extOne.name).toBe('one'); | ||
expect(extTwo.name).toBe('two'); | ||
expect(editor.getExtensionByName('one')).toBe(extOne); | ||
expect(editor.getExtensionByName('two')).toBe(extTwo); | ||
}); | ||
}); | ||
}); |
@@ -36,4 +36,4 @@ /*global MediumEditor, describe, it, expect, spyOn, | ||
var editor = this.newMediumEditor('.test'); | ||
expect(editor.id).toBe(undefined); | ||
expect(editor.setup).not.toHaveBeenCalled(); | ||
expect(editor.isActive).toBeFalsy(); | ||
expect(editor.events).toBeUndefined(); | ||
expect(editor.toolbar).toBeUndefined(); | ||
@@ -89,2 +89,11 @@ expect(editor.getExtensionByName('anchor')).toBeUndefined(); | ||
}); | ||
it('should be available after destroying and calling setup again', function () { | ||
var editor = this.newMediumEditor('.editor'); | ||
expect(editor.elements.length).toBe(1); | ||
editor.destroy(); | ||
expect(editor.elements.length).toBe(0); | ||
editor.setup(); | ||
expect(editor.elements.length).toBe(1); | ||
}); | ||
}); | ||
@@ -202,2 +211,2 @@ | ||
}); | ||
}); | ||
}); |
@@ -1,2 +0,2 @@ | ||
/*global describe, it, expect, | ||
/*global describe, it, expect, Util, | ||
afterEach, beforeEach, fireEvent, setupTestHelpers, | ||
@@ -71,2 +71,17 @@ Placeholder */ | ||
it('should NOT remove the placeholder on click', function () { | ||
var editor = this.newMediumEditor('.editor', { placeholder: { hideOnClick: false }}); | ||
expect(editor.elements[0].className).toContain('medium-editor-placeholder'); | ||
fireEvent(editor.elements[0], 'click'); | ||
expect(editor.elements[0].className).toContain('medium-editor-placeholder'); | ||
fireEvent(editor.elements[0], 'blur'); | ||
expect(editor.elements[0].className).toContain('medium-editor-placeholder'); | ||
this.el.innerHTML = '<p>lorem</p><p id="target">ipsum</p><p>dolor</p>'; | ||
fireEvent(document.getElementById('target'), 'keypress'); | ||
expect(editor.elements[0].className).not.toContain('medium-editor-placeholder'); | ||
this.el.innerHTML = ''; | ||
fireEvent(editor.elements[0], 'keyup', { keyCode: Util.keyCode.DELETE }); | ||
expect(editor.elements[0].className).toContain('medium-editor-placeholder'); | ||
}); | ||
it('should add a placeholder to empty elements on blur', function () { | ||
@@ -73,0 +88,0 @@ this.el.innerHTML = 'some text'; |
/*global MediumEditor, describe, it, expect, spyOn, | ||
afterEach, beforeEach, selectElementContents, | ||
fireEvent, setupTestHelpers, jasmine, selectElementContentsAndFire, | ||
placeCursorInsideElement, Toolbar */ | ||
placeCursorInsideElement, Toolbar*/ | ||
@@ -78,3 +78,61 @@ describe('Toolbar TestCase', function () { | ||
it('should call onShowToolbar when toolbar is shwon and onHideToolbar when toolbar is hidden', function () { | ||
it('should trigger the showToolbar custom event when toolbar is shown', function () { | ||
var editor = this.newMediumEditor('.editor'), | ||
callback = jasmine.createSpy(); | ||
this.el.innerHTML = 'specOnShowToolbarTest'; | ||
editor.subscribe('showToolbar', callback); | ||
selectElementContentsAndFire(this.el, { eventToFire: 'focus' }); | ||
expect(callback).toHaveBeenCalledWith({}, this.el); | ||
}); | ||
it('should trigger the hideToolbar custom event when toolbar is hidden', function () { | ||
var editor = this.newMediumEditor('.editor'), | ||
callback = jasmine.createSpy(); | ||
this.el.innerHTML = 'specOnShowToolbarTest'; | ||
editor.subscribe('hideToolbar', callback); | ||
selectElementContentsAndFire(this.el, { eventToFire: 'focus' }); | ||
// Remove selection and call check selection, which should make the toolbar be hidden | ||
jasmine.clock().tick(1); | ||
window.getSelection().removeAllRanges(); | ||
editor.checkSelection(); | ||
expect(callback).toHaveBeenCalledWith({}, this.el); | ||
}); | ||
it('should be possible to listen to toolbar events from extensions', function () { | ||
var callbackShow = jasmine.createSpy('show'), | ||
callbackHide = jasmine.createSpy('hide'), | ||
TestExtension = MediumEditor.Extension.extend({ | ||
parent: true, | ||
init: function () { | ||
this.base.subscribe('showToolbar', callbackShow); | ||
this.base.subscribe('hideToolbar', callbackHide); | ||
} | ||
}), | ||
editor = this.newMediumEditor('.editor', { | ||
extensions: { 'testExtension': new TestExtension() } | ||
}); | ||
this.el.innerHTML = 'specOnShowToolbarTest'; | ||
selectElementContentsAndFire(this.el, { eventToFire: 'focus' }); | ||
expect(callbackShow).toHaveBeenCalledWith({}, this.el); | ||
// Remove selection and call check selection, which should make the toolbar be hidden | ||
jasmine.clock().tick(1); | ||
window.getSelection().removeAllRanges(); | ||
editor.checkSelection(); | ||
expect(callbackHide).toHaveBeenCalledWith({}, this.el); | ||
}); | ||
it('should call deprecated onShowToolbar option when toolbar is shown and deprecated onHideToolbar option when toolbar is hidden', function () { | ||
var editor, | ||
@@ -81,0 +139,0 @@ temp = { |
@@ -1,5 +0,5 @@ | ||
/*global Util, ButtonsData, Button, | ||
Selection, FontSizeForm, Extension, extensionDefaults, | ||
Toolbar, AutoLink, ImageDragging, Events, editorDefaults, | ||
DefaultButton, AnchorExtension, FontSizeExtension, AnchorPreviewDeprecated */ | ||
/*global Util, ButtonsData, Selection, Extension, | ||
extensionDefaults, Toolbar, Events, editorDefaults, | ||
DefaultButton, AnchorExtension, FontSizeExtension, | ||
AnchorPreviewDeprecated*/ | ||
@@ -152,3 +152,3 @@ function MediumEditor(elements, options) { | ||
this.options.ownerDocument.execCommand('unlink', false, null); | ||
} else if (!event.shiftKey) { | ||
} else if (!event.shiftKey && !event.ctrlKey) { | ||
// only format block if this is not a header tag | ||
@@ -190,9 +190,34 @@ if (!/h\d/.test(tagName)) { | ||
function setExtensionDefaults(extension, defaults) { | ||
Object.keys(defaults).forEach(function (prop) { | ||
if (extension[prop] === undefined) { | ||
extension[prop] = defaults[prop]; | ||
} | ||
}); | ||
return extension; | ||
} | ||
function initExtension(extension, name, instance) { | ||
if (extension.parent) { | ||
extension.base = instance; | ||
if (typeof extension.parent !== 'undefined') { | ||
Util.warn('Extension .parent property has been deprecated. ' + | ||
'The .base property for extensions will always be set to MediumEditor in version 5.0.0'); | ||
} | ||
var extensionDefaults = { | ||
'window': instance.options.contentWindow, | ||
'document': instance.options.ownerDocument | ||
}; | ||
// TODO: Deprecated (Remove .parent check in v5.0.0) | ||
if (extension.parent !== false) { | ||
extensionDefaults.base = instance; | ||
} | ||
// Add default options into the extension | ||
extension = setExtensionDefaults(extension, extensionDefaults); | ||
// Call init on the extension | ||
if (typeof extension.init === 'function') { | ||
// Passing instance into init() will be deprecated in v5.0.0 | ||
extension.init(instance); | ||
} | ||
// Set extension name (if not already set) | ||
if (!extension.name) { | ||
@@ -372,5 +397,3 @@ extension.name = name; | ||
var defaultsBC = { | ||
text: (typeof this.options.placeholder === 'string') ? this.options.placeholder : undefined, // deprecated | ||
'window': this.options.contentWindow, | ||
'document': this.options.ownerDocument | ||
text: (typeof this.options.placeholder === 'string') ? this.options.placeholder : undefined // deprecated | ||
}; | ||
@@ -387,4 +410,2 @@ | ||
hideDelay: this.options.anchorPreviewHideDelay, // deprecated | ||
'window': this.options.contentWindow, | ||
'document': this.options.ownerDocument, | ||
diffLeft: this.options.diffLeft, | ||
@@ -407,5 +428,3 @@ diffTop: this.options.diffTop, | ||
targetCheckbox: this.options.anchorTarget, // deprecated | ||
targetCheckboxText: this.options.anchorInputCheckboxLabel, // deprecated | ||
'window': this.options.contentWindow, | ||
'document': this.options.ownerDocument | ||
targetCheckboxText: this.options.anchorInputCheckboxLabel // deprecated | ||
}; | ||
@@ -424,5 +443,3 @@ | ||
disableReturn: this.options.disableReturn, | ||
targetBlank: this.options.targetBlank, | ||
'window': this.options.contentWindow, | ||
'document': this.options.ownerDocument | ||
targetBlank: this.options.targetBlank | ||
}; | ||
@@ -442,2 +459,9 @@ | ||
// add toolbar custom events to the list of known events by the editor | ||
// we need to have this for the initialization of extensions | ||
// initToolbar is called after initCommands | ||
// add toolbar custom events to the list of known events by the editor | ||
this.createEvent('showToolbar'); | ||
this.createEvent('hideToolbar'); | ||
buttons.forEach(function (buttonName) { | ||
@@ -451,6 +475,6 @@ if (extensions[buttonName]) { | ||
} else if (buttonName === 'fontsize') { | ||
ext = initExtension(new FontSizeForm(), buttonName, this); | ||
ext = initExtension(new MediumEditor.extensions.fontSize(), buttonName, this); | ||
this.commands.push(ext); | ||
} else if (ButtonsData.hasOwnProperty(buttonName)) { | ||
ext = initExtension(new Button(ButtonsData[buttonName]), buttonName, this); | ||
ext = initExtension(new MediumEditor.extensions.button(ButtonsData[buttonName]), buttonName, this); | ||
this.commands.push(ext); | ||
@@ -478,7 +502,7 @@ } | ||
if (shouldAddDefaultAutoLink.call(this)) { | ||
this.commands.push(initExtension(new AutoLink(), 'auto-link', this)); | ||
this.commands.push(initExtension(new MediumEditor.extensions.autoLink(), 'auto-link', this)); | ||
} | ||
if (shouldAddDefaultImageDragging.call(this)) { | ||
this.commands.push(initExtension(new ImageDragging(), 'image-dragging', this)); | ||
this.commands.push(initExtension(new MediumEditor.extensions.imageDragging(), 'image-dragging', this)); | ||
} | ||
@@ -504,3 +528,5 @@ | ||
['disableAnchorPreview', 'anchorPreview: false'], | ||
['disablePlaceholders', 'placeholder: false'] | ||
['disablePlaceholders', 'placeholder: false'], | ||
['onShowToolbar', 'showToolbar custom event'], | ||
['onHideToolbar', 'hideToolbar custom event'] | ||
]; | ||
@@ -575,6 +601,3 @@ // warn about using deprecated properties | ||
this.options = mergeOptions.call(this, this.defaults, options); | ||
createElementsArray.call(this, elements); | ||
if (this.elements.length === 0) { | ||
return; | ||
} | ||
this.origElements = elements; | ||
@@ -599,2 +622,7 @@ if (!this.options.elementsContainer) { | ||
createElementsArray.call(this, this.origElements); | ||
if (this.elements.length === 0) { | ||
return; | ||
} | ||
this.events = new Events(this); | ||
@@ -666,2 +694,10 @@ this.isActive = true; | ||
createEvent: function (event) { | ||
this.events.defineCustomEvent(event); | ||
}, | ||
trigger: function (name, data, editable) { | ||
this.events.triggerCustomEvent(name, data, editable); | ||
}, | ||
delay: function (fn) { | ||
@@ -668,0 +704,0 @@ var self = this; |
@@ -1,2 +0,2 @@ | ||
/*global Util */ | ||
/*global Util*/ | ||
@@ -58,3 +58,3 @@ var Events; | ||
this.setupListener(event); | ||
// If we don't suppot this custom event, don't do anything | ||
// If we don't support this custom event, don't do anything | ||
if (this.listeners[event]) { | ||
@@ -76,2 +76,6 @@ if (!this.customEvents[event]) { | ||
defineCustomEvent: function (event) { | ||
this.listeners[event] = true; | ||
}, | ||
indexOfCustomListener: function (event, listener) { | ||
@@ -201,3 +205,2 @@ if (!this.customEvents[event] || !this.customEvents[event].length) { | ||
// Listening to browser events to emit events medium-editor cares about | ||
setupListener: function (name) { | ||
@@ -209,122 +212,122 @@ if (this.listeners[name]) { | ||
switch (name) { | ||
case 'externalInteraction': | ||
// Detecting when user has interacted with elements outside of MediumEditor | ||
this.attachDOMEvent(this.options.ownerDocument.body, 'mousedown', this.handleBodyMousedown.bind(this), true); | ||
this.attachDOMEvent(this.options.ownerDocument.body, 'click', this.handleBodyClick.bind(this), true); | ||
this.attachDOMEvent(this.options.ownerDocument.body, 'focus', this.handleBodyFocus.bind(this), true); | ||
this.listeners[name] = true; | ||
break; | ||
case 'blur': | ||
// Detecting when focus is lost | ||
this.setupListener('externalInteraction'); | ||
this.listeners[name] = true; | ||
break; | ||
case 'focus': | ||
// Detecting when focus moves into some part of MediumEditor | ||
this.setupListener('externalInteraction'); | ||
this.listeners[name] = true; | ||
break; | ||
case 'editableInput': | ||
// setup cache for knowing when the content has changed | ||
this.contentCache = []; | ||
this.base.elements.forEach(function (element) { | ||
this.contentCache[element.getAttribute('medium-editor-index')] = element.innerHTML; | ||
case 'externalInteraction': | ||
// Detecting when user has interacted with elements outside of MediumEditor | ||
this.attachDOMEvent(this.options.ownerDocument.body, 'mousedown', this.handleBodyMousedown.bind(this), true); | ||
this.attachDOMEvent(this.options.ownerDocument.body, 'click', this.handleBodyClick.bind(this), true); | ||
this.attachDOMEvent(this.options.ownerDocument.body, 'focus', this.handleBodyFocus.bind(this), true); | ||
this.listeners[name] = true; | ||
break; | ||
case 'blur': | ||
// Detecting when focus is lost | ||
this.setupListener('externalInteraction'); | ||
this.listeners[name] = true; | ||
break; | ||
case 'focus': | ||
// Detecting when focus moves into some part of MediumEditor | ||
this.setupListener('externalInteraction'); | ||
this.listeners[name] = true; | ||
break; | ||
case 'editableInput': | ||
// setup cache for knowing when the content has changed | ||
this.contentCache = []; | ||
this.base.elements.forEach(function (element) { | ||
this.contentCache[element.getAttribute('medium-editor-index')] = element.innerHTML; | ||
// Attach to the 'oninput' event, handled correctly by most browsers | ||
if (this.InputEventOnContenteditableSupported) { | ||
this.attachDOMEvent(element, 'input', this.handleInput.bind(this)); | ||
// Attach to the 'oninput' event, handled correctly by most browsers | ||
if (this.InputEventOnContenteditableSupported) { | ||
this.attachDOMEvent(element, 'input', this.handleInput.bind(this)); | ||
} | ||
}.bind(this)); | ||
// For browsers which don't support the input event on contenteditable (IE) | ||
// we'll attach to 'selectionchange' on the document and 'keypress' on the editables | ||
if (!this.InputEventOnContenteditableSupported) { | ||
this.setupListener('editableKeypress'); | ||
this.keypressUpdateInput = true; | ||
this.attachDOMEvent(document, 'selectionchange', this.handleDocumentSelectionChange.bind(this)); | ||
// Listen to calls to execCommand | ||
this.attachToExecCommand(); | ||
} | ||
}.bind(this)); | ||
// For browsers which don't support the input event on contenteditable (IE) | ||
// we'll attach to 'selectionchange' on the document and 'keypress' on the editables | ||
if (!this.InputEventOnContenteditableSupported) { | ||
this.setupListener('editableKeypress'); | ||
this.keypressUpdateInput = true; | ||
this.attachDOMEvent(document, 'selectionchange', this.handleDocumentSelectionChange.bind(this)); | ||
// Listen to calls to execCommand | ||
this.attachToExecCommand(); | ||
} | ||
this.listeners[name] = true; | ||
break; | ||
case 'editableClick': | ||
// Detecting click in the contenteditables | ||
this.base.elements.forEach(function (element) { | ||
this.attachDOMEvent(element, 'click', this.handleClick.bind(this)); | ||
}.bind(this)); | ||
this.listeners[name] = true; | ||
break; | ||
case 'editableBlur': | ||
// Detecting blur in the contenteditables | ||
this.base.elements.forEach(function (element) { | ||
this.attachDOMEvent(element, 'blur', this.handleBlur.bind(this)); | ||
}.bind(this)); | ||
this.listeners[name] = true; | ||
break; | ||
case 'editableKeypress': | ||
// Detecting keypress in the contenteditables | ||
this.base.elements.forEach(function (element) { | ||
this.attachDOMEvent(element, 'keypress', this.handleKeypress.bind(this)); | ||
}.bind(this)); | ||
this.listeners[name] = true; | ||
break; | ||
case 'editableKeyup': | ||
// Detecting keyup in the contenteditables | ||
this.base.elements.forEach(function (element) { | ||
this.attachDOMEvent(element, 'keyup', this.handleKeyup.bind(this)); | ||
}.bind(this)); | ||
this.listeners[name] = true; | ||
break; | ||
case 'editableKeydown': | ||
// Detecting keydown on the contenteditables | ||
this.base.elements.forEach(function (element) { | ||
this.attachDOMEvent(element, 'keydown', this.handleKeydown.bind(this)); | ||
}.bind(this)); | ||
this.listeners[name] = true; | ||
break; | ||
case 'editableKeydownEnter': | ||
// Detecting keydown for ENTER on the contenteditables | ||
this.setupListener('editableKeydown'); | ||
this.listeners[name] = true; | ||
break; | ||
case 'editableKeydownTab': | ||
// Detecting keydown for TAB on the contenteditable | ||
this.setupListener('editableKeydown'); | ||
this.listeners[name] = true; | ||
break; | ||
case 'editableKeydownDelete': | ||
// Detecting keydown for DELETE/BACKSPACE on the contenteditables | ||
this.setupListener('editableKeydown'); | ||
this.listeners[name] = true; | ||
break; | ||
case 'editableMouseover': | ||
// Detecting mouseover on the contenteditables | ||
this.base.elements.forEach(function (element) { | ||
this.attachDOMEvent(element, 'mouseover', this.handleMouseover.bind(this)); | ||
}, this); | ||
this.listeners[name] = true; | ||
break; | ||
case 'editableDrag': | ||
// Detecting dragover and dragleave on the contenteditables | ||
this.base.elements.forEach(function (element) { | ||
this.attachDOMEvent(element, 'dragover', this.handleDragging.bind(this)); | ||
this.attachDOMEvent(element, 'dragleave', this.handleDragging.bind(this)); | ||
}, this); | ||
this.listeners[name] = true; | ||
break; | ||
case 'editableDrop': | ||
// Detecting drop on the contenteditables | ||
this.base.elements.forEach(function (element) { | ||
this.attachDOMEvent(element, 'drop', this.handleDrop.bind(this)); | ||
}, this); | ||
this.listeners[name] = true; | ||
break; | ||
case 'editablePaste': | ||
// Detecting paste on the contenteditables | ||
this.base.elements.forEach(function (element) { | ||
this.attachDOMEvent(element, 'paste', this.handlePaste.bind(this)); | ||
}, this); | ||
this.listeners[name] = true; | ||
break; | ||
this.listeners[name] = true; | ||
break; | ||
case 'editableClick': | ||
// Detecting click in the contenteditables | ||
this.base.elements.forEach(function (element) { | ||
this.attachDOMEvent(element, 'click', this.handleClick.bind(this)); | ||
}.bind(this)); | ||
this.listeners[name] = true; | ||
break; | ||
case 'editableBlur': | ||
// Detecting blur in the contenteditables | ||
this.base.elements.forEach(function (element) { | ||
this.attachDOMEvent(element, 'blur', this.handleBlur.bind(this)); | ||
}.bind(this)); | ||
this.listeners[name] = true; | ||
break; | ||
case 'editableKeypress': | ||
// Detecting keypress in the contenteditables | ||
this.base.elements.forEach(function (element) { | ||
this.attachDOMEvent(element, 'keypress', this.handleKeypress.bind(this)); | ||
}.bind(this)); | ||
this.listeners[name] = true; | ||
break; | ||
case 'editableKeyup': | ||
// Detecting keyup in the contenteditables | ||
this.base.elements.forEach(function (element) { | ||
this.attachDOMEvent(element, 'keyup', this.handleKeyup.bind(this)); | ||
}.bind(this)); | ||
this.listeners[name] = true; | ||
break; | ||
case 'editableKeydown': | ||
// Detecting keydown on the contenteditables | ||
this.base.elements.forEach(function (element) { | ||
this.attachDOMEvent(element, 'keydown', this.handleKeydown.bind(this)); | ||
}.bind(this)); | ||
this.listeners[name] = true; | ||
break; | ||
case 'editableKeydownEnter': | ||
// Detecting keydown for ENTER on the contenteditables | ||
this.setupListener('editableKeydown'); | ||
this.listeners[name] = true; | ||
break; | ||
case 'editableKeydownTab': | ||
// Detecting keydown for TAB on the contenteditable | ||
this.setupListener('editableKeydown'); | ||
this.listeners[name] = true; | ||
break; | ||
case 'editableKeydownDelete': | ||
// Detecting keydown for DELETE/BACKSPACE on the contenteditables | ||
this.setupListener('editableKeydown'); | ||
this.listeners[name] = true; | ||
break; | ||
case 'editableMouseover': | ||
// Detecting mouseover on the contenteditables | ||
this.base.elements.forEach(function (element) { | ||
this.attachDOMEvent(element, 'mouseover', this.handleMouseover.bind(this)); | ||
}, this); | ||
this.listeners[name] = true; | ||
break; | ||
case 'editableDrag': | ||
// Detecting dragover and dragleave on the contenteditables | ||
this.base.elements.forEach(function (element) { | ||
this.attachDOMEvent(element, 'dragover', this.handleDragging.bind(this)); | ||
this.attachDOMEvent(element, 'dragleave', this.handleDragging.bind(this)); | ||
}, this); | ||
this.listeners[name] = true; | ||
break; | ||
case 'editableDrop': | ||
// Detecting drop on the contenteditables | ||
this.base.elements.forEach(function (element) { | ||
this.attachDOMEvent(element, 'drop', this.handleDrop.bind(this)); | ||
}, this); | ||
this.listeners[name] = true; | ||
break; | ||
case 'editablePaste': | ||
// Detecting paste on the contenteditables | ||
this.base.elements.forEach(function (element) { | ||
this.attachDOMEvent(element, 'paste', this.handlePaste.bind(this)); | ||
}, this); | ||
this.listeners[name] = true; | ||
break; | ||
} | ||
@@ -506,12 +509,12 @@ }, | ||
switch (event.which) { | ||
case Util.keyCode.ENTER: | ||
this.triggerCustomEvent('editableKeydownEnter', event, event.currentTarget); | ||
break; | ||
case Util.keyCode.TAB: | ||
this.triggerCustomEvent('editableKeydownTab', event, event.currentTarget); | ||
break; | ||
case Util.keyCode.DELETE: | ||
case Util.keyCode.BACKSPACE: | ||
this.triggerCustomEvent('editableKeydownDelete', event, event.currentTarget); | ||
break; | ||
case Util.keyCode.ENTER: | ||
this.triggerCustomEvent('editableKeydownEnter', event, event.currentTarget); | ||
break; | ||
case Util.keyCode.TAB: | ||
this.triggerCustomEvent('editableKeydownTab', event, event.currentTarget); | ||
break; | ||
case Util.keyCode.DELETE: | ||
case Util.keyCode.BACKSPACE: | ||
this.triggerCustomEvent('editableKeydownDelete', event, event.currentTarget); | ||
break; | ||
} | ||
@@ -518,0 +521,0 @@ } |
@@ -1,8 +0,7 @@ | ||
/* global Util */ | ||
var Extension; | ||
(function () { | ||
'use strict'; | ||
/* global Util */ | ||
Extension = function (options) { | ||
@@ -73,25 +72,17 @@ Util.extend(this, options); | ||
Extension.prototype = { | ||
init: function (/* instance */) { | ||
// called when properly decorated and used. | ||
// has a .base value pointing to the editor | ||
// owning us. has been given a .name if no | ||
// name present | ||
}, | ||
/* parent: [boolean] | ||
/* init: [function] | ||
* | ||
* Setting this to false will prevent MediumEditor | ||
* from setting the .base property. | ||
* If left as true, the .base property of the extension | ||
* will be assigned a reference to the | ||
* MediumEditor instance that is using the extension | ||
* Called by MediumEditor during initialization. | ||
* The .base property will already have been set to | ||
* current instance of MediumEditor when this is called. | ||
* All helper methods will exist as well | ||
*/ | ||
parent: true, | ||
init: function () {}, | ||
/* base: [MediumEditor instance] | ||
* | ||
* If .parent is set to true, this will be set to the | ||
* current MediumEditor instance before init() is called | ||
* If not overriden, this will be set to the current instance | ||
* of MediumEditor, before the init method is called | ||
*/ | ||
base: null, | ||
base: undefined, | ||
@@ -105,3 +96,3 @@ /* name: [string] | ||
*/ | ||
name: null, | ||
name: undefined, | ||
@@ -119,3 +110,3 @@ /* checkState: [function (node)] | ||
*/ | ||
checkState: null, | ||
checkState: undefined, | ||
@@ -138,3 +129,3 @@ /* As alternatives to checkState, these functions provide a more structured | ||
*/ | ||
queryCommandState: null, | ||
queryCommandState: undefined, | ||
@@ -149,3 +140,3 @@ /* isActive: [function ()] | ||
*/ | ||
isActive: null, | ||
isActive: undefined, | ||
@@ -163,3 +154,3 @@ /* isAlreadyApplied: [function (node)] | ||
*/ | ||
isAlreadyApplied: null, | ||
isAlreadyApplied: undefined, | ||
@@ -174,3 +165,3 @@ /* setActive: [function ()] | ||
*/ | ||
setActive: null, | ||
setActive: undefined, | ||
@@ -187,11 +178,29 @@ /* setInactive: [function ()] | ||
*/ | ||
setInactive: null, | ||
setInactive: undefined, | ||
/* onHide: [function ()] | ||
/************************ Helpers ************************ | ||
* The following are helpers that are either set by MediumEditor | ||
* during initialization, or are helper methods which either | ||
* route calls to the MediumEditor instance or provide common | ||
* functionality for all extensions | ||
*********************************************************/ | ||
/* window: [Window] | ||
* | ||
* If implemented, this function is called each time the | ||
* toolbar is hidden | ||
* If not overriden, this will be set to the window object | ||
* to be used by MediumEditor and its extensions. This is | ||
* passed via the 'contentWindow' option to MediumEditor | ||
* and is the global 'window' object by default | ||
*/ | ||
onHide: null | ||
'window': undefined, | ||
/* document: [Document] | ||
* | ||
* If not overriden, this will be set to the document object | ||
* to be used by MediumEditor and its extensions. This is | ||
* passed via the 'ownerDocument' optin to MediumEditor | ||
* and is the global 'document' object by default | ||
*/ | ||
'document': undefined | ||
}; | ||
})(); | ||
})(); |
@@ -9,3 +9,2 @@ var AnchorPreview; | ||
name: 'anchor-preview', | ||
parent: true, | ||
@@ -12,0 +11,0 @@ // Anchor Preview Options |
@@ -17,3 +17,2 @@ /*global Extension, Util */ | ||
AutoLink = Extension.extend({ | ||
parent: true, | ||
@@ -25,3 +24,3 @@ init: function () { | ||
// MS IE has it's own auto-URL detect feature but ours is better in some ways. Be consistent. | ||
this.base.options.ownerDocument.execCommand('AutoUrlDetect', false, false); | ||
this.document.execCommand('AutoUrlDetect', false, false); | ||
}, | ||
@@ -135,3 +134,3 @@ | ||
findOrCreateMatchingTextNodes: function (element, match) { | ||
var treeWalker = this.base.options.ownerDocument.createTreeWalker(element, NodeFilter.SHOW_TEXT, | ||
var treeWalker = this.document.createTreeWalker(element, NodeFilter.SHOW_TEXT, | ||
null, false), | ||
@@ -186,4 +185,4 @@ matchedNodes = [], | ||
var anchor = document.createElement('a'), | ||
span = document.createElement('span'); | ||
var anchor = this.document.createElement('a'), | ||
span = this.document.createElement('span'); | ||
Util.moveTextRangeIntoElement(textNodes[0], textNodes[textNodes.length - 1], span); | ||
@@ -190,0 +189,0 @@ span.setAttribute('data-auto-link', 'true'); |
@@ -37,3 +37,3 @@ var Button; | ||
createButton: function () { | ||
var button = this.base.options.ownerDocument.createElement('button'), | ||
var button = this.document.createElement('button'), | ||
content = this.contentDefault, | ||
@@ -116,3 +116,3 @@ ariaLabel = this.getAria(); | ||
styleVals = this.style.value.split('|'); | ||
computedStyle = this.base.options.contentWindow.getComputedStyle(node, null).getPropertyValue(this.style.prop); | ||
computedStyle = this.window.getComputedStyle(node, null).getPropertyValue(this.style.prop); | ||
styleVals.forEach(function (val) { | ||
@@ -119,0 +119,0 @@ if (!this.knownState) { |
@@ -23,3 +23,3 @@ var FontSizeForm; | ||
// Get fontsize of current selection (convert to string since IE returns this as number) | ||
var fontSize = this.base.options.ownerDocument.queryCommandValue('fontSize') + ''; | ||
var fontSize = this.document.queryCommandValue('fontSize') + ''; | ||
this.showForm(fontSize); | ||
@@ -90,3 +90,3 @@ } | ||
createForm: function () { | ||
var doc = this.base.options.ownerDocument, | ||
var doc = this.document, | ||
form = doc.createElement('div'), | ||
@@ -144,3 +144,3 @@ input = doc.createElement('input'), | ||
clearFontSize: function () { | ||
Selection.getSelectedElements(this.base.options.ownerDocument).forEach(function (el) { | ||
Selection.getSelectedElements(this.document).forEach(function (el) { | ||
if (el.tagName === 'FONT' && el.hasAttribute('size')) { | ||
@@ -147,0 +147,0 @@ el.removeAttribute('size'); |
@@ -8,4 +8,2 @@ /*global Util, Extension */ | ||
ImageDragging = Extension.extend({ | ||
// Need a reference to MediumEditor (this.base) | ||
parent: true, | ||
@@ -46,6 +44,6 @@ init: function () { | ||
id = 'medium-img-' + (+new Date()); | ||
Util.insertHTMLCommand(this.base.options.ownerDocument, '<img class="medium-image-loading" id="' + id + '" />'); | ||
Util.insertHTMLCommand(this.document, '<img class="medium-image-loading" id="' + id + '" />'); | ||
fileReader.onload = function () { | ||
var img = this.base.options.ownerDocument.getElementById(id); | ||
var img = this.document.getElementById(id); | ||
if (img) { | ||
@@ -52,0 +50,0 @@ img.removeAttribute('id'); |
@@ -88,5 +88,2 @@ /*global Util, Selection, Extension */ | ||
// Need a reference to MediumEditor (this.base) | ||
parent: true, | ||
init: function () { | ||
@@ -93,0 +90,0 @@ if (this.forcePlainText || this.cleanPastedHTML) { |
@@ -10,3 +10,2 @@ var Placeholder; | ||
name: 'placeholder', | ||
parent: true, | ||
@@ -20,2 +19,7 @@ /* Placeholder Options */ | ||
/* hideOnClick: [boolean] | ||
* Should we hide the placeholder on click (true) or when user starts typing (false) | ||
*/ | ||
hideOnClick: true, | ||
init: function () { | ||
@@ -63,4 +67,10 @@ this.initPlaceholders(); | ||
// if we don't want the placeholder to be removed on click but when user start typing | ||
if (this.hideOnClick) { | ||
this.base.subscribe('editableClick', this.handleHidePlaceholderEvent.bind(this)); | ||
} else { | ||
this.base.subscribe('editableKeyup', this.handleBlur.bind(this)); | ||
} | ||
// Events where we always hide the placeholder | ||
this.base.subscribe('editableClick', this.handleHidePlaceholderEvent.bind(this)); | ||
this.base.subscribe('editableKeypress', this.handleHidePlaceholderEvent.bind(this)); | ||
@@ -67,0 +77,0 @@ this.base.subscribe('editablePaste', this.handleHidePlaceholderEvent.bind(this)); |
@@ -1,2 +0,2 @@ | ||
/*global Util, Selection */ | ||
/*global Util, Selection*/ | ||
@@ -11,2 +11,3 @@ var Toolbar; | ||
this.options = instance.options; | ||
this.initThrottledMethods(); | ||
@@ -196,3 +197,6 @@ }; | ||
this.getToolbarElement().classList.add('medium-editor-toolbar-active'); | ||
this.base.trigger('showToolbar', {}, this.base.getFocusedElement()); | ||
if (typeof this.options.onShowToolbar === 'function') { | ||
Util.deprecated('onShowToolbar', 'the showToolbar custom event', 'v5.0.0'); | ||
this.options.onShowToolbar(); | ||
@@ -205,4 +209,8 @@ } | ||
if (this.isDisplayed()) { | ||
this.getToolbarElement().classList.remove('medium-editor-toolbar-active'); | ||
this.base.trigger('hideToolbar', {}, this.base.getFocusedElement()); | ||
this.base.commands.forEach(function (extension) { | ||
if (typeof extension.onHide === 'function') { | ||
Util.deprecated('onHide', 'the hideToolbar custom event', 'v5.0.0'); | ||
extension.onHide(); | ||
@@ -212,4 +220,4 @@ } | ||
this.getToolbarElement().classList.remove('medium-editor-toolbar-active'); | ||
if (typeof this.options.onHideToolbar === 'function') { | ||
Util.deprecated('onHideToolbar', 'the hideToolbar custom event', 'v5.0.0'); | ||
this.options.onHideToolbar(); | ||
@@ -310,3 +318,3 @@ } | ||
// hide toolbar | ||
if (!this.getFocusedElement() || | ||
if (!this.base.getFocusedElement() || | ||
Selection.selectionInContentEditableFalse(this.options.contentWindow)) { | ||
@@ -346,9 +354,5 @@ this.hideToolbar(); | ||
// leaving here backward compatibility / statics | ||
getFocusedElement: function () { | ||
for (var i = 0; i < this.base.elements.length; i += 1) { | ||
if (this.base.elements[i].getAttribute('data-medium-focused')) { | ||
return this.base.elements[i]; | ||
} | ||
} | ||
return null; | ||
return this.base.getFocusedElement(); | ||
}, | ||
@@ -437,3 +441,3 @@ | ||
setToolbarPosition: function () { | ||
var container = this.getFocusedElement(), | ||
var container = this.base.getFocusedElement(), | ||
selection = this.options.contentWindow.getSelection(), | ||
@@ -440,0 +444,0 @@ anchorPreview; |
@@ -567,3 +567,3 @@ /*global NodeFilter, console, Selection*/ | ||
var afterLast = lastChild.nextSibling, | ||
fragment = document.createDocumentFragment(); | ||
fragment = rootNode.ownerDocument.createDocumentFragment(); | ||
@@ -570,0 +570,0 @@ // build up fragment on startNode side of tree |
@@ -14,3 +14,3 @@ /*global MediumEditor */ | ||
// grunt-bump looks for this: | ||
'version': '4.10.2' | ||
'version': '4.11.0' | ||
}).version.split('.')); |
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
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
1315124
16965
420