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

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.11.1 to 4.12.0

4

bower.json
{
"name": "medium-editor",
"version": "4.11.1",
"homepage": "http://daviferreira.github.io/medium-editor/",
"version": "4.12.0",
"homepage": "http://yabwe.github.io/medium-editor/",
"authors": [

@@ -6,0 +6,0 @@ "Davi Ferreira <hi@daviferreira.com>",

@@ -0,1 +1,15 @@

4.12.0 / 2015-06-01
==================
* Fix pasting links when targetBlank option is being used
* Fix for spellcheck option after destroy
* Fix over-reaching keyboard shortcuts for commands
* Expose new 'positionToolbar' custom event
* Add new isKey() helper in util
* Add cleanup on destroy for auto-link and placeholder extensions
* Base extension changes
* Add getEditorElements(), getEditorId(), and getEditorOption() helpers
* Add on(), off(), subscribe(), and execAction() helpers
* Introduce destroy() lifecycle method + deprecate deactivate()
4.11.1 / 2015-05-26

@@ -2,0 +16,0 @@ ==================

# Contributing
## To contribute and end up in this [list](https://github.com/daviferreira/medium-editor/graphs/contributors):
## To contribute and end up in this [list](https://github.com/yabwe/medium-editor/graphs/contributors):
[Kill some bugs :)](https://github.com/daviferreira/medium-editor/issues?q=is%3Aopen+is%3Aissue+label%3Abug)
[Kill some bugs :)](https://github.com/yabwe/medium-editor/issues?q=is%3Aopen+is%3Aissue+label%3Abug)

@@ -11,3 +11,3 @@ 1. Fork it

4. Update the documentation to reflect your changes if they add or changes current functionality.
5. Commit your changes (`git commit -am 'Added some feature'`)
5. Commit your changes (`git commit -am 'Added some feature'`) **without files from the _dist_ directory**.
6. Push to the branch (`git push origin my-new-feature`)

@@ -34,3 +34,3 @@ 7. Create new Pull Request

Looking for something simple for a first contribution? Try fixing an [easy first bug](https://github.com/daviferreira/medium-editor/issues?q=is%3Aopen+is%3Aissue+label%3A%22easy+first+bug%22)!
Looking for something simple for a first contribution? Try fixing an [easy first bug](https://github.com/yabwe/medium-editor/issues?q=is%3Aopen+is%3Aissue+label%3A%22easy+first+bug%22)!

@@ -37,0 +37,0 @@ ## Development

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

@@ -13,2 +13,6 @@ "contributors": [

"email": "nchase@gmail.com"
},
{
"name": "Jeremy Benoist",
"email": "jeremy.benoist@gmail.com"
}

@@ -20,9 +24,9 @@ ],

"type": "git",
"url": "https://github.com/daviferreira/medium-editor"
"url": "https://github.com/yabwe/medium-editor"
},
"bugs": {
"url": "https://github.com/daviferreira/medium-editor/issues",
"url": "https://github.com/yabwe/medium-editor/issues",
"email": "hi@daviferreira.com"
},
"homepage": "http://daviferreira.github.io/medium-editor/",
"homepage": "http://yabwe.github.io/medium-editor/",
"keywords": [

@@ -29,0 +33,0 @@ "contenteditable",

# MediumEditor
[![Join the chat at https://gitter.im/daviferreira/medium-editor](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/daviferreira/medium-editor?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
[![Join the chat at https://gitter.im/yabwe/medium-editor](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/yabwe/medium-editor?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)

@@ -17,12 +17,12 @@ This is a clone of [medium.com](https://medium.com) inline editor toolbar.

[![Travis build status](http://img.shields.io/travis/daviferreira/medium-editor/master.svg?style=flat-square)](https://travis-ci.org/daviferreira/medium-editor)
[![dependencies](http://img.shields.io/david/daviferreira/medium-editor.svg?style=flat-square)](https://david-dm.org/daviferreira/medium-editor)
[![devDependency Status](http://img.shields.io/david/dev/daviferreira/medium-editor.svg?style=flat-square)](https://david-dm.org/daviferreira/medium-editor#info=devDependencies)
[![Coverage Status](http://img.shields.io/coveralls/daviferreira/medium-editor.svg?style=flat-square)](https://coveralls.io/r/daviferreira/medium-editor?branch=master)
[![Travis build status](http://img.shields.io/travis/yabwe/medium-editor/master.svg?style=flat-square)](https://travis-ci.org/yabwe/medium-editor)
[![dependencies](http://img.shields.io/david/yabwe/medium-editor.svg?style=flat-square)](https://david-dm.org/yabwe/medium-editor)
[![devDependency Status](http://img.shields.io/david/dev/yabwe/medium-editor.svg?style=flat-square)](https://david-dm.org/yabwe/medium-editor#info=devDependencies)
[![Coverage Status](http://img.shields.io/coveralls/yabwe/medium-editor.svg?style=flat-square)](https://coveralls.io/r/yabwe/medium-editor?branch=master)
# Basic usage
![screenshot](https://raw.github.com/daviferreira/medium-editor/master/demo/img/medium-editor.jpg)
![screenshot](https://raw.github.com/yabwe/medium-editor/master/demo/img/medium-editor.jpg)
__demo__: [http://daviferreira.github.io/medium-editor/](http://daviferreira.github.io/medium-editor/)
__demo__: [http://yabwe.github.io/medium-editor/](http://yabwe.github.io/medium-editor/)

@@ -39,14 +39,25 @@ ### Installation

**Via CDNJS**
**Via an external CDN**
[CDNJS hosts this library](https://cdnjs.com/libraries/medium-editor) and you can load it from CDN this way:
* Using [jsDelivr](http://www.jsdelivr.com/#!medium-editor).
```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">
```
For the latest version:
```html
<script src="//cdn.jsdelivr.net/medium-editor/latest/js/medium-editor.min.js"></script>
<link rel="stylesheet" href="//cdn.jsdelivr.net/medium-editor/latest/css/medium-editor.min.css" type="text/css" media="screen" charset="utf-8">
```
For a custom one:
```html
<script src="//cdn.jsdelivr.net/medium-editor/4.11.1/js/medium-editor.min.js"></script>
<link rel="stylesheet" href="//cdn.jsdelivr.net/medium-editor/4.11.1/css/medium-editor.min.css" type="text/css" media="screen" charset="utf-8">
```
* Using [CDNJS](https://cdnjs.com/libraries/medium-editor).
**Manual installation:**
Download the [latest release](https://github.com/daviferreira/medium-editor/releases) and attach medium editor's stylesheets to your page:
Download the [latest release](https://github.com/yabwe/medium-editor/releases) and attach medium editor's stylesheets to your page:

@@ -91,3 +102,3 @@ ```html

* __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: {}
* __extensions__: extension to use (see [Custom Buttons and Extensions](https://github.com/yabwe/medium-editor/wiki/Custom-Buttons-and-Extensions)) for more. Default: {}
* __firstHeader__: HTML tag to be used as first header. Default: h3

@@ -299,3 +310,3 @@ * __secondHeader__: HTML tag to be used as second header. Default: h4

Check out the Wiki page for a list of available themes: [https://github.com/daviferreira/medium-editor/wiki/Themes](https://github.com/daviferreira/medium-editor/wiki/Themes)
Check out the Wiki page for a list of available themes: [https://github.com/yabwe/medium-editor/wiki/Themes](https://github.com/yabwe/medium-editor/wiki/Themes)

@@ -358,5 +369,5 @@ ## API

Check the [documentation](https://github.com/daviferreira/medium-editor/wiki/Custom-Buttons-and-Extensions) in order to learn how to develop extensions for MediumEditor.
Check the [documentation](https://github.com/yabwe/medium-editor/wiki/Custom-Buttons-and-Extensions) in order to learn how to develop extensions for MediumEditor.
A list of existing extensions and plugins, such as [Images and Media embeds](http://orthes.github.io/medium-editor-insert-plugin/), [Tables](https://github.com/daviferreira/medium-editor-tables) and [Markdown](https://github.com/IonicaBizau/medium-editor-markdown) can be found [here](https://github.com/daviferreira/medium-editor/wiki/Extensions-Plugins).
A list of existing extensions and plugins, such as [Images and Media embeds](http://orthes.github.io/medium-editor-insert-plugin/), [Tables](https://github.com/yabwe/medium-editor-tables) and [Markdown](https://github.com/IonicaBizau/medium-editor-markdown) can be found [here](https://github.com/yabwe/medium-editor/wiki/Extensions-Plugins).

@@ -388,3 +399,3 @@ ## Development

[Kill some bugs :)](https://github.com/daviferreira/medium-editor/issues?q=is%3Aopen+is%3Aissue+label%3Abug)
[Kill some bugs :)](https://github.com/yabwe/medium-editor/issues?q=is%3Aopen+is%3Aissue+label%3Abug)

@@ -395,3 +406,3 @@ 1. Fork it

4. Update the documentation to reflect your changes if they add or changes current functionality.
5. Commit your changes (`git commit -am 'Added some feature'`)
5. Commit your changes (`git commit -am 'Added some feature'`) **without files from the _dist_ directory**.
6. Push to the branch (`git push origin my-new-feature`)

@@ -418,10 +429,10 @@ 7. Create new Pull Request

Looking for something simple for a first contribution? Try fixing an [easy first bug](https://github.com/daviferreira/medium-editor/issues?q=is%3Aopen+is%3Aissue+label%3A%22easy+first+bug%22)!
Looking for something simple for a first contribution? Try fixing an [easy first bug](https://github.com/yabwe/medium-editor/issues?q=is%3Aopen+is%3Aissue+label%3A%22easy+first+bug%22)!
## Contributors (100+ and counting!)
[https://github.com/daviferreira/medium-editor/graphs/contributors](https://github.com/daviferreira/medium-editor/graphs/contributors)
[https://github.com/yabwe/medium-editor/graphs/contributors](https://github.com/yabwe/medium-editor/graphs/contributors)
## License
MIT: https://github.com/daviferreira/medium-editor/blob/master/LICENSE
MIT: https://github.com/yabwe/medium-editor/blob/master/LICENSE

@@ -181,3 +181,3 @@ /*global MediumEditor, describe, it, expect, spyOn, AnchorForm,

spyOn(AnchorPreview.prototype, 'deactivate').and.callThrough();
spyOn(AnchorPreview.prototype, 'destroy').and.callThrough();
expect(document.querySelector('.medium-editor-anchor-preview')).not.toBeNull();

@@ -194,3 +194,3 @@ expect(document.querySelector('.medium-editor-anchor-preview-active')).toBeNull();

editor.destroy();
expect(anchorPreview.deactivate).toHaveBeenCalled();
expect(anchorPreview.destroy).toHaveBeenCalled();
expect(document.querySelector('.medium-editor-anchor-preview-active')).toBeNull();

@@ -197,0 +197,0 @@ expect(document.querySelector('.medium-editor-anchor-preview')).toBeNull();

@@ -20,3 +20,3 @@ /*global MediumEditor, describe, it, expect, spyOn,

it('should not hide the toolbar when mouseup fires inside the anchor form', function () {
var editor = this.newMediumEditor('.editor'),
var editor = this.newMediumEditor('.editor', { buttonLabels: 'fontawesome' }),
anchorExtension = editor.getExtensionByName('anchor');

@@ -58,4 +58,3 @@

var editor = this.newMediumEditor('.editor'),
button,
input;
button, input;

@@ -71,2 +70,3 @@ selectElementContents(editor.elements[0]);

expect(editor.createLink).toHaveBeenCalled();
expect(this.el.innerHTML).toBe('<a href="test">lorem ipsum</a>');
});

@@ -73,0 +73,0 @@

@@ -8,2 +8,55 @@ /*global describe, it, expect, beforeEach, afterEach,

describe('extension', function () {
beforeEach(function () {
setupTestHelpers.call(this);
this.el = this.createElement('div', 'editor', '');
});
afterEach(function () {
this.cleanupTest();
});
it('should turn off browser auto-link during initialization', function () {
var autoUrlDetectTurnedOn = true,
origExecCommand = document.execCommand;
spyOn(document, 'execCommand').and.callFake(function (command, showUi, val) {
if (command === 'AutoUrlDetect') {
autoUrlDetectTurnedOn = val;
}
return origExecCommand.apply(document, arguments);
});
this.newMediumEditor('.editor', {
autoLink: true
});
expect(autoUrlDetectTurnedOn).toBe(false);
});
it('should reset browser auto-link (if supported) during destroy', function () {
var autoUrlDetectTurnedOn = true,
origExecCommand = document.execCommand,
origQCS = document.queryCommandSupported;
spyOn(document, 'execCommand').and.callFake(function (command, showUi, val) {
if (command === 'AutoUrlDetect') {
autoUrlDetectTurnedOn = val;
}
return origExecCommand.apply(document, arguments);
});
spyOn(document, 'queryCommandSupported').and.callFake(function (command) {
if (command === 'AutoUrlDetect') {
return true;
}
return origQCS.apply(document, arguments);
});
var editor = this.newMediumEditor('.editor', {
autoLink: true
});
expect(autoUrlDetectTurnedOn).toBe(false);
editor.destroy();
expect(autoUrlDetectTurnedOn).toBe(true);
});
});
describe('integration', function () {

@@ -55,5 +108,6 @@

function triggerAutolinking(element) {
function triggerAutolinking(element, key) {
var keyPressed = key || Util.keyCode.SPACE;
fireEvent(element, 'keypress', {
keyCode: Util.keyCode.SPACE
keyCode: keyPressed
});

@@ -127,2 +181,41 @@ jasmine.clock().tick(1);

it('should auto-link text on all SPACE or ENTER', function () {
var links;
this.el.innerHTML = 'http://www.example.enter';
selectElementContentsAndFire(this.el);
triggerAutolinking(this.el, Util.keyCode.ENTER);
links = this.el.getElementsByTagName('a');
expect(links.length).toBe(1);
expect(links[0].getAttribute('href')).toBe('http://www.example.enter');
expect(links[0].firstChild.getAttribute('data-auto-link')).toBe('true');
expect(links[0].textContent).toBe('http://www.example.enter');
this.el.innerHTML = 'http://www.example.space';
selectElementContentsAndFire(this.el);
triggerAutolinking(this.el, Util.keyCode.SPACE);
links = this.el.getElementsByTagName('a');
expect(links.length).toBe(1);
expect(links[0].getAttribute('href')).toBe('http://www.example.space');
expect(links[0].firstChild.getAttribute('data-auto-link')).toBe('true');
expect(links[0].textContent).toBe('http://www.example.space');
});
it('should auto-link text on blur', function () {
var links;
this.el.innerHTML = 'http://www.example.blur';
selectElementContentsAndFire(this.el);
fireEvent(this.el, 'blur');
jasmine.clock().tick(1);
links = this.el.getElementsByTagName('a');
expect(links.length).toBe(1);
expect(links[0].getAttribute('href')).toBe('http://www.example.blur');
expect(links[0].firstChild.getAttribute('data-auto-link')).toBe('true');
expect(links[0].textContent).toBe('http://www.example.blur');
});
it('should auto-link link within basic text', function () {

@@ -129,0 +222,0 @@ this.el.innerHTML = 'Text with http://www.example.com inside!';

@@ -75,2 +75,17 @@ /*global MediumEditor, describe, it, expect, spyOn, AnchorForm,

});
it('should not execute the button action when shift key is pressed', function () {
spyOn(MediumEditor.prototype, 'execAction');
var editor = this.newMediumEditor('.editor'),
code = 'b'.charCodeAt(0);
selectElementContentsAndFire(editor.elements[0]);
jasmine.clock().tick(1);
fireEvent(editor.elements[0], 'keydown', {
keyCode: code,
ctrlKey: true,
metaKey: true,
shiftKey: true
});
expect(editor.execAction).not.toHaveBeenCalled();
});
});

@@ -124,3 +139,2 @@

it('should contain default content if no custom labels are provided', function () {
spyOn(MediumEditor.prototype, 'execAction');
var button,

@@ -534,2 +548,20 @@ editor = this.newMediumEditor('.editor', {

describe('Image', function () {
it('should create an image', function () {
spyOn(document, 'execCommand').and.callThrough();
var editor = this.newMediumEditor('.editor', {
buttons: ['image']
}),
button = editor.toolbar.getToolbarElement().querySelector('[data-action="image"]');
this.el.innerHTML = '<span id="span-image">http://i.imgur.com/twlXfUq.jpg</span>';
selectElementContentsAndFire(document.getElementById('span-image'));
fireEvent(button, 'click');
expect(this.el.innerHTML).toContain('<img src="http://i.imgur.com/twlXfUq.jpg">');
expect(document.execCommand).toHaveBeenCalledWith('insertImage', false, window.getSelection());
});
});
describe('OrderedList', function () {

@@ -536,0 +568,0 @@ it('button should be active if the selection already has the element', function () {

@@ -142,3 +142,3 @@ /*global describe, it, expect, spyOn,

this.el.innerHTML = '<p><br></p>';
this.el.setAttribute('data-disable-return', true);
this.el.setAttribute('data-disable-double-return', true);

@@ -145,0 +145,0 @@ var editor = this.newMediumEditor('.editor'),

@@ -45,2 +45,14 @@ /*global describe, it, expect, jasmine,

describe('Custom Events', function () {
it('should be attachable and triggerable if they are not built-in events', function () {
var editor = this.newMediumEditor('.editor'),
spy = jasmine.createSpy('handler'),
tempData = { temp: 'data' };
editor.subscribe('myIncredibleEvent', spy);
expect(spy).not.toHaveBeenCalled();
editor.trigger('myIncredibleEvent', tempData, editor.elements[0]);
expect(spy).toHaveBeenCalledWith(tempData, editor.elements[0]);
});
});
describe('Custom Focus/Blur Listener', function () {

@@ -47,0 +59,0 @@ it('should be called and passed the editable element when the editable gets focus', function () {

@@ -85,2 +85,62 @@ /*global MediumEditor, describe, it, expect, spyOn,

it('should set the base property to an instance of MediumEditor', function () {
var extOne = new MediumEditor.Extension(),
editor = this.newMediumEditor('.editor', {
extensions: {
'one': extOne
}
});
expect(editor instanceof MediumEditor).toBeTruthy();
expect(extOne.base instanceof MediumEditor).toBeTruthy();
});
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(editor.getExtensionByName('temp').base).toBeUndefined();
});
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);
});
it('should set window and document properties on each extension', function () {

@@ -118,2 +178,32 @@ var TempExtension = MediumEditor.Extension.extend({}),

});
it('should call destroy on extensions when being destroyed', function () {
var TempExtension = MediumEditor.Extension.extend({
destroy: function () {}
}),
extInstance = new TempExtension();
spyOn(extInstance, 'destroy');
var editor = this.newMediumEditor('.editor', {
extensions: {
'temp-extension': extInstance
}
});
editor.destroy();
expect(extInstance.destroy).toHaveBeenCalled();
});
it('should call deprecated deactivate on extensions when being destroyed if destroy is not implemented', function () {
var TempExtension = MediumEditor.Extension.extend({
deactivate: function () {}
}),
extInstance = new TempExtension();
spyOn(extInstance, 'deactivate');
var editor = this.newMediumEditor('.editor', {
extensions: {
'temp-extension': extInstance
}
});
editor.destroy();
expect(extInstance.deactivate).toHaveBeenCalled();
});
});

@@ -167,2 +257,61 @@

describe('All extensions', function () {
it('should get helper methods to call into base instance methods', function () {
var noop = function () {},
helpers = {
'on': [document, 'click', noop, false],
'off': [document, 'click', noop, false],
'subscribe': ['editableClick', noop],
'execAction': ['bold']
},
tempExtension = new MediumEditor.Extension(),
editor = this.newMediumEditor('.editor', {
extensions: {
'temp-extension': tempExtension
}
});
Object.keys(helpers).forEach(function (helper) {
spyOn(editor, helper);
});
Object.keys(helpers).forEach(function (helper) {
tempExtension[helper].apply(tempExtension, helpers[helper]);
expect((editor[helper]).calls.count()).toBe(1);
});
});
it('should be able to access the editor id via getEditorId()', function () {
var tempExtension = new MediumEditor.Extension(),
editor = this.newMediumEditor('.editor', {
extensions: {
'temp-extension': tempExtension
}
});
expect(tempExtension.getEditorId()).toBe(editor.id);
});
it('should be able to access elements in this editor via getEditorElements()', function () {
var tempExtension = new MediumEditor.Extension(),
editor = this.newMediumEditor('.editor', {
extensions: {
'temp-extension': tempExtension
}
});
expect(tempExtension.getEditorElements()).toBe(editor.elements);
});
it('should be able to access editor options via getEditorOption()', function () {
var tempExtension = new MediumEditor.Extension(),
editor = this.newMediumEditor('.editor', {
disableReturn: true,
extensions: {
'temp-extension': tempExtension
}
});
expect(tempExtension.getEditorOption('disableReturn')).toBe(true);
expect(tempExtension.getEditorOption('spellcheck')).toBe(editor.options.spellcheck);
});
});
describe('Button integration', function () {

@@ -251,64 +400,2 @@

});
describe('Set data in extensions', function () {
it('should set the base property to an instance of MediumEditor', function () {
var extOne = new MediumEditor.Extension(),
editor = this.newMediumEditor('.editor', {
extensions: {
'one': extOne
}
});
expect(editor instanceof MediumEditor).toBeTruthy();
expect(extOne.base instanceof MediumEditor).toBeTruthy();
});
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(editor.getExtensionByName('temp').base).toBeUndefined();
});
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);
});
});
});

@@ -47,3 +47,3 @@ /*global MediumEditor, describe, it, expect, spyOn,

spyOn(document, 'execCommand').and.callThrough();
var editor = this.newMediumEditor('.editor', this.mediumOpts),
var editor = this.newMediumEditor('.editor', { buttons: ['fontsize'], buttonLabels: 'fontawesome' }),
fontSizeExtension = editor.getExtensionByName('fontsize'),

@@ -140,4 +140,4 @@ button,

describe('Destroying MediumEditor', function () {
it('should deactivate the font size extension and remove the form', function () {
spyOn(FontSizeForm.prototype, 'deactivate').and.callThrough();
it('should destroy the font size extension and remove the form', function () {
spyOn(FontSizeForm.prototype, 'destroy').and.callThrough();
var editor = this.newMediumEditor('.editor', this.mediumOpts),

@@ -149,3 +149,3 @@ fontSizeExtension = editor.getExtensionByName('fontsize');

expect(fontSizeExtension.deactivate).toHaveBeenCalled();
expect(fontSizeExtension.destroy).toHaveBeenCalled();
expect(document.getElementById('medium-editor-toolbar-form-fontsize-1')).not.toBeTruthy();

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

@@ -11,4 +11,4 @@ /*global MediumEditor, describe, it, expect, spyOn,

source: 'Google docs',
paste: '<meta charset=\'utf-8\'><meta charset="utf-8"><b style="font-weight:normal;" id="docs-internal-guid-b1bb8bfe-f54c-2e1f-72e2-4c7608d2be70"><p dir="ltr" style="line-height:1.15;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:15px;font-family:Arial;color:#000000;background-color:transparent;font-weight:bold;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre-wrap;">Bold</span></p><br><span style="font-size:15px;font-family:Arial;color:#000000;background-color:transparent;font-weight:normal;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre-wrap;"></span><p dir="ltr" style="line-height:1.15;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:15px;font-family:Arial;color:#000000;background-color:transparent;font-weight:normal;font-style:italic;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre-wrap;">Italic</span></p><br><span style="font-size:15px;font-family:Arial;color:#000000;background-color:transparent;font-weight:normal;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre-wrap;"></span><p dir="ltr" style="line-height:1.15;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:15px;font-family:Arial;color:#000000;background-color:transparent;font-weight:bold;font-style:italic;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre-wrap;">Bold and Italic</span></p><br><span style="font-size:15px;font-family:Arial;color:#000000;background-color:transparent;font-weight:normal;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre-wrap;"></span><p dir="ltr" style="line-height:1.15;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:15px;font-family:Arial;color:#000000;background-color:transparent;font-weight:normal;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre-wrap;">A </span><a href="http://en.wikipedia.org/wiki/Link_(The_Legend_of_Zelda)" style="text-decoration:none;"><span style="font-size:15px;font-family:Arial;color:#1155cc;background-color:transparent;font-weight:normal;font-style:normal;font-variant:normal;text-decoration:underline;vertical-align:baseline;white-space:pre-wrap;">link</span></a><span style="font-size:15px;font-family:Arial;color:#000000;background-color:transparent;font-weight:normal;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre-wrap;">.</span></p></b><br class="Apple-interchange-newline">',
output: '<p><b>Bold</b></p><p><i>Italic</i></p><p><b><i>Bold and Italic</i></b></p><p>A <a href="http://en.wikipedia.org/wiki/Link_(The_Legend_of_Zelda)">link</a>.</p>'
paste: '<meta charset=\'utf-8\'><meta charset="utf-8"><b style="font-weight:normal;" id="docs-internal-guid-b1bb8bfe-f54c-2e1f-72e2-4c7608d2be70"><div dir="ltr" style="line-height:1.15;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:15px;font-family:Arial;color:#000000;background-color:transparent;font-weight:bold;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre-wrap;">Bold</span></div><br><span style="font-size:15px;font-family:Arial;color:#000000;background-color:transparent;font-weight:normal;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre-wrap;"></span><p dir="ltr" style="line-height:1.15;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:15px;font-family:Arial;color:#000000;background-color:transparent;font-weight:normal;font-style:italic;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre-wrap;">Italic</span></p><br><span style="font-size:15px;font-family:Arial;color:#000000;background-color:transparent;font-weight:normal;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre-wrap;"></span><p dir="ltr" style="line-height:1.15;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:15px;font-family:Arial;color:#000000;background-color:transparent;font-weight:bold;font-style:italic;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre-wrap;">Bold and Italic</span></p><br><span style="font-size:15px;font-family:Arial;color:#000000;background-color:transparent;font-weight:normal;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre-wrap;"></span><p dir="ltr" style="line-height:1.15;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:15px;font-family:Arial;color:#000000;background-color:transparent;font-weight:normal;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre-wrap;">A </span><a href="http://en.wikipedia.org/wiki/Link_(The_Legend_of_Zelda)" style="text-decoration:none;"><span style="font-size:15px;font-family:Arial;color:#1155cc;background-color:transparent;font-weight:normal;font-style:normal;font-variant:normal;text-decoration:underline;vertical-align:baseline;white-space:pre-wrap;">link</span></a><span style="font-size:15px;font-family:Arial;color:#000000;background-color:transparent;font-weight:normal;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre-wrap;">.</span></p></b><br class="Apple-interchange-newline">',
output: '<div><b>Bold</b></div><p><i>Italic</i></p><p><b><i>Bold and Italic</i></b></p><p>A <a href="http://en.wikipedia.org/wiki/Link_(The_Legend_of_Zelda)">link</a>.</p>'
},

@@ -199,2 +199,21 @@ {

});
it('should add target="_blank" on anchor', function () {
var editor = this.newMediumEditor('.editor', {
targetBlank: true,
paste: {
forcePlainText: false,
cleanPastedHTML: true
}
});
spyOn(document, 'queryCommandSupported').and.returnValue(false);
this.el.innerHTML = '<span id="editor-inner">lorem ipsum</span>';
selectElementContentsAndFire(this.el);
editor.cleanPaste('<a href="http://0.0.0.0/bar.html">foo<a>');
expect(this.el.innerHTML).toContain('target="_blank"');
});
});

@@ -201,0 +220,0 @@

@@ -124,2 +124,23 @@ /*global describe, it, expect, Util,

it('should remove the added data-placeholder attribute when destroyed', function () {
expect(this.el.hasAttribute('data-placeholder')).toBe(false);
var editor = this.newMediumEditor('.editor');
expect(this.el.getAttribute('data-placeholder')).toBe(Placeholder.prototype.text);
editor.destroy();
expect(this.el.hasAttribute('data-placeholder')).toBe(false);
});
it('should not remove custom data-placeholder attribute when destroyed', function () {
var placeholderText = 'Custom placeholder';
this.el.setAttribute('data-placeholder', placeholderText);
var editor = this.newMediumEditor('.editor');
expect(this.el.getAttribute('data-placeholder')).toBe(placeholderText);
editor.destroy();
expect(this.el.getAttribute('data-placeholder')).toBe(placeholderText);
});
it('should use the data-placeholder when it is present', function () {

@@ -126,0 +147,0 @@ var editor,

@@ -183,2 +183,58 @@ /*global MediumEditor, describe, it, expect, spyOn,

describe('getSelectedElements', function () {
it('no selected elements on empty selection', function () {
var elements = Selection.getSelectedElements(document);
expect(elements.length).toBe(0);
});
it('should select element from selection', function () {
this.el.innerHTML = 'lorem <i>ipsum</i> dolor';
var editor = this.newMediumEditor('.editor'),
elements;
selectElementContents(editor.elements[0].querySelector('i').firstChild);
elements = Selection.getSelectedElements(document);
expect(elements.length).toBe(1);
expect(elements[0].tagName.toLowerCase()).toBe('i');
expect(elements[0].innerHTML).toBe('ipsum');
});
it('should select first element when selection is global (ie: all the editor)', function () {
this.el.innerHTML = 'lorem <i>ipsum</i> dolor';
var elements;
selectElementContents(this.el);
elements = Selection.getSelectedElements(document);
expect(elements.length).toBe(1);
expect(elements[0].tagName.toLowerCase()).toBe('i');
expect(elements[0].innerHTML).toBe('ipsum');
});
});
describe('getSelectedParentElement', function () {
it('should return null on bad range', function () {
expect(Selection.getSelectedParentElement(null)).toBe(null);
expect(Selection.getSelectedParentElement(false)).toBe(null);
});
it('should select the document', function () {
this.el.innerHTML = '<p>lorem <i>ipsum</i> dolor <span>hello</span> <b>you</b> </p>';
var range = document.createRange(),
sel = window.getSelection(),
element;
range.setStart(document, 0);
range.setEnd(this.el.querySelector('b').firstChild, 2);
sel.removeAllRanges();
sel.addRange(range);
element = Selection.getSelectedParentElement(range);
expect(element).toBe(document);
});
});
});

@@ -102,3 +102,3 @@ /*global MediumEditor, describe, it, expect, spyOn, jasmine,

// regression test for https://github.com/daviferreira/medium-editor/issues/390
// regression test for https://github.com/yabwe/medium-editor/issues/390
it('should work with multiple elements of the same class', function () {

@@ -141,3 +141,3 @@ var editor,

// regression test for https://github.com/daviferreira/medium-editor/issues/197
// regression test for https://github.com/yabwe/medium-editor/issues/197
it('should not crash when destroy immediately after a mouse click', function () {

@@ -144,0 +144,0 @@ var editor = this.newMediumEditor('.editor');

@@ -56,2 +56,9 @@ /*global describe, it, beforeEach, afterEach, expect,

});
it('should cleanup after destroy', function () {
var editor = this.newMediumEditor('.editor');
expect(this.el.classList.contains('medium-editor-hidden')).toBe(true);
editor.destroy();
expect(this.el.classList.contains('medium-editor-hidden')).toBe(false);
});
});

@@ -91,2 +91,33 @@ /*global MediumEditor, describe, it, expect, spyOn,

it('should trigger positionToolbar custom event when toolbar is moved', function () {
var editor = this.newMediumEditor('.editor'),
callback = jasmine.createSpy();
this.el.innerHTML = 'specOnUpdateToolbarTest';
editor.subscribe('positionToolbar', callback);
selectElementContentsAndFire(this.el, { eventToFire: 'focus' });
expect(callback).toHaveBeenCalledWith({}, this.el);
});
it('should trigger positionToolbar before position called', function () {
var editor = this.newMediumEditor('.editor'),
temp = {
update: function () {
expect(editor.toolbar.positionToolbar).not.toHaveBeenCalled();
}
};
spyOn(editor.toolbar, 'positionToolbar').and.callThrough();
spyOn(temp, 'update').and.callThrough();
this.el.innerHTML = 'position sanity check';
editor.subscribe('positionToolbar', temp.update);
selectElementContentsAndFire(this.el, { eventToFire: 'focus' });
expect(temp.update).toHaveBeenCalled();
expect(editor.toolbar.positionToolbar).toHaveBeenCalled();
});
it('should trigger the hideToolbar custom event when toolbar is hidden', function () {

@@ -247,3 +278,3 @@ var editor = this.newMediumEditor('.editor'),

selectElementContentsAndFire(this.el.querySelector('b'));
window.getSelection().getRangeAt(0).collapse(false);
window.getSelection().removeAllRanges();
editor.checkSelection();

@@ -385,2 +416,55 @@ jasmine.clock().tick(1); // checkSelection delay

});
describe('Static & sticky toolbar position', function () {
it('should position static + sticky toolbar on the left', function () {
this.el.innerHTML = '<b>lorem ipsum</b>';
var editor = this.newMediumEditor('.editor', {
staticToolbar: true,
stickyToolbar: true,
toolbarAlign: 'left'
}),
toolbar = editor.toolbar.getToolbarElement();
selectElementContentsAndFire(this.el.querySelector('b'));
window.getSelection().getRangeAt(0).collapse(false);
editor.checkSelection();
jasmine.clock().tick(1); // checkSelection delay
expect(toolbar.style.left).not.toBe('');
});
it('should position static + sticky toolbar on the right', function () {
this.el.innerHTML = '<b>lorem ipsum</b>';
var editor = this.newMediumEditor('.editor', {
staticToolbar: true,
stickyToolbar: true,
toolbarAlign: 'right'
}),
toolbar = editor.toolbar.getToolbarElement();
selectElementContentsAndFire(this.el.querySelector('b'));
window.getSelection().getRangeAt(0).collapse(false);
editor.checkSelection();
jasmine.clock().tick(1); // checkSelection delay
expect(toolbar.style.left).not.toBe('');
});
it('should position static + sticky toolbar on the center', function () {
this.el.innerHTML = '<b>lorem ipsum</b>';
var editor = this.newMediumEditor('.editor', {
staticToolbar: true,
stickyToolbar: true,
toolbarAlign: 'center'
}),
toolbar = editor.toolbar.getToolbarElement();
selectElementContentsAndFire(this.el.querySelector('b'));
window.getSelection().getRangeAt(0).collapse(false);
editor.checkSelection();
jasmine.clock().tick(1); // checkSelection delay
expect(toolbar.style.left).not.toBe('');
});
});
});

@@ -16,3 +16,2 @@ /*global MediumEditor, Util, describe, it, expect, spyOn,

describe('Exposure', function () {
it('is exposed on the MediumEditor ctor', function () {

@@ -45,2 +44,8 @@ expect(MediumEditor.util).toBeTruthy();

});
it('should overwrite nothing without args', function () {
var result = MediumEditor.util.defaults();
expect(result).toEqual({});
});
});

@@ -50,3 +55,2 @@

it('should warn when a method is deprecated', function () {
var testObj = {

@@ -65,3 +69,2 @@ newMethod: function () {}

it('should warn when an option is deprecated', function () {
spyOn(Util, 'warn').and.callThrough();

@@ -84,6 +87,7 @@ Util.deprecated('oldOption', 'sub.newOption');

describe('getobject', function () {
it('should get nested objects', function () {
var obj = { a: { b: { c: { d: 10 } } } };
expect(Util.getObject('a.b.c.d', false, obj)).toBe(10);
expect(Util.getObject('a.b.c.d', false, false)).toBe(undefined);
expect(Util.getObject(false, false, obj)).toBe(obj);
expect(Util.getObject('a.b.c', false, obj)).toEqual({ d: 10 });

@@ -104,7 +108,5 @@ expect(Util.getObject('a', false, obj)).toEqual({ b: { c: { d: 10 } } });

});
});
describe('setobject', function () {
it('sets returns the value', function () {

@@ -116,6 +118,9 @@ var obj = {};

it('sets returns undefined because of empty string', function () {
var obj = {};
expect(Util.setObject('', 10, obj)).toBe(undefined);
});
});
describe('settargetblank', function () {
it('sets target blank on a A element from a A element', function () {

@@ -140,3 +145,28 @@ var el = this.createElement('a', '', 'lorem ipsum');

});
});
describe('addClassToAnchors', function () {
it('add class to anchors on a A element from a A element', function () {
var el = this.createElement('a', '', 'lorem ipsum');
el.attributes.href = 'http://0.0.0.0/bar.html';
Util.addClassToAnchors(el, 'firstclass');
expect(el.classList.length).toBe(1);
expect(el.classList.contains('firstclass')).toBe(true);
});
it('add class to anchors on a A element from a DIV element', function () {
var el = this.createElement('div', '', '<a href="http://1.1.1.1/foo.html">foo</a> <a href="http://0.0.0.0/bar.html">bar</a>');
Util.addClassToAnchors(el, 'firstclass');
var nodes = el.getElementsByTagName('a');
expect(nodes[0].classList.length).toBe(1);
expect(nodes[1].classList.length).toBe(1);
expect(nodes[0].classList.contains('firstclass')).toBe(true);
expect(nodes[1].classList.contains('firstclass')).toBe(true);
});
});

@@ -269,2 +299,104 @@

});
describe('getClosestTag', function () {
it('should get closed tag', function () {
var el = this.createElement('div', '', '<span>my <b>text</b></span>'),
tag = el.querySelector('b').firstChild,
closestTag = Util.getClosestTag(tag, 'span');
expect(closestTag.tagName.toLowerCase()).toBe('span');
});
it('should not get closed tag with data-medium-element', function () {
var el = this.createElement('div', '', '<p>youpi<span data-medium-element="true">my <b>text</b></span></p>'),
tag = el.querySelector('b').firstChild,
closestTag = Util.getClosestTag(tag, 'p');
expect(closestTag).toBe(false);
});
it('should not get closed tag from empty element', function () {
var closestTag = Util.getClosestTag(false, 'span');
expect(closestTag).toBe(false);
});
});
describe('isListItem', function () {
it('should be a list item but inside a ul', function () {
var el = this.createElement('ul', '', '<li>test</li>'),
result = Util.isListItem(el);
expect(result).toBe(false);
});
it('should be a list item', function () {
var el = this.createElement('ul', '', '<li>test</li>'),
li = el.querySelector('li'),
result = Util.isListItem(li);
expect(result).toBe(true);
});
it('should not be a list item', function () {
var result = Util.isListItem(false);
expect(result).toBe(false);
});
it('should not be a list item', function () {
var el = this.createElement('p', '', '<b>test</b>'),
result = Util.isListItem(el);
expect(result).toBe(false);
});
});
describe('isKey', function () {
it('should return true no matter how the key associated to the event is defined', function () {
var event;
event = {
which: 13,
keyCode: null,
charCode: null
};
expect(Util.isKey(event, 13)).toBeTruthy();
event = {
which: null,
keyCode: 13,
charCode: null
};
expect(Util.isKey(event, 13)).toBeTruthy();
event = {
which: null,
keyCode: null,
charCode: 13
};
expect(Util.isKey(event, 13)).toBeTruthy();
});
it('should return true when a key associated to event is listed to one we are looking for', function () {
var event = {
which: 13
};
expect(Util.isKey(event, 13)).toBeTruthy();
expect(Util.isKey(event, [13])).toBeTruthy();
expect(Util.isKey(event, [13, 12])).toBeTruthy();
expect(Util.isKey(event, [12, 13])).toBeTruthy();
});
it('should return false when a key associated to event is NOT listed to one we are looking for', function () {
var event = {
which: 13
};
expect(Util.isKey(event, 65)).toBeFalsy();
expect(Util.isKey(event, [65])).toBeFalsy();
expect(Util.isKey(event, [65, 66])).toBeFalsy();
});
});
});

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

} else if (this.options.disableDoubleReturn || element.getAttribute('data-disable-double-return')) {
var node = Util.getSelectionStart(this.options.ownerDocument);
var node = Selection.getSelectionStart(this.options.ownerDocument);

@@ -33,3 +33,3 @@ // if current text selection is empty OR previous sibling text is empty

// Override tab only for pre nodes
var node = Util.getSelectionStart(this.options.ownerDocument),
var node = Selection.getSelectionStart(this.options.ownerDocument),
tag = node && node.tagName.toLowerCase();

@@ -56,3 +56,3 @@

function handleBlockDeleteKeydowns(event) {
var p, node = Util.getSelectionStart(this.options.ownerDocument),
var p, node = Selection.getSelectionStart(this.options.ownerDocument),
tagName = node.tagName.toLowerCase(),

@@ -62,3 +62,3 @@ isEmpty = /^(\s+|<br\/?>)?$/i,

if ((event.which === Util.keyCode.BACKSPACE || event.which === Util.keyCode.ENTER) &&
if (Util.isKey(event, [Util.keyCode.BACKSPACE, Util.keyCode.ENTER]) &&
// has a preceeding sibling

@@ -70,3 +70,3 @@ node.previousElementSibling &&

Selection.getCaretOffsets(node).left === 0) {
if (event.which === Util.keyCode.BACKSPACE && isEmpty.test(node.previousElementSibling.innerHTML)) {
if (Util.isKey(event, Util.keyCode.BACKSPACE) && isEmpty.test(node.previousElementSibling.innerHTML)) {
// backspacing the begining of a header into an empty previous element will

@@ -77,3 +77,3 @@ // change the tagName of the current node to prevent one

event.preventDefault();
} else if (event.which === Util.keyCode.ENTER) {
} else if (Util.isKey(event, Util.keyCode.ENTER)) {
// hitting return in the begining of a header will create empty header elements before the current one

@@ -86,3 +86,3 @@ // instead, make "<p><br></p>" element, which are what happens if you hit return in an empty paragraph

}
} else if (event.which === Util.keyCode.DELETE &&
} else if (Util.isKey(event, Util.keyCode.DELETE) &&
// between two sibling elements

@@ -108,3 +108,3 @@ node.nextElementSibling &&

event.preventDefault();
} else if (event.which === Util.keyCode.BACKSPACE &&
} else if (Util.isKey(event, Util.keyCode.BACKSPACE) &&
tagName === 'li' &&

@@ -144,3 +144,3 @@ // hitting backspace inside an empty li

function handleKeyup(event) {
var node = Util.getSelectionStart(this.options.ownerDocument),
var node = Selection.getSelectionStart(this.options.ownerDocument),
tagName;

@@ -156,3 +156,3 @@

if (event.which === Util.keyCode.ENTER && !Util.isListItem(node)) {
if (Util.isKey(event, Util.keyCode.ENTER) && !Util.isListItem(node)) {
tagName = node.tagName.toLowerCase();

@@ -417,5 +417,5 @@ // For anchor tags, unlink

hideDelay: this.options.anchorPreviewHideDelay, // deprecated
diffLeft: this.options.diffLeft,
diffTop: this.options.diffTop,
elementsContainer: this.options.elementsContainer
diffLeft: this.options.diffLeft, // deprecated (should use .getEditorOption() instead)
diffTop: this.options.diffTop, // deprecated (should use .getEditorOption() instead)
elementsContainer: this.options.elementsContainer // deprecated (should use .getEditorOption() instead)
};

@@ -448,4 +448,4 @@

cleanPastedHTML: this.options.cleanPastedHTML, // deprecated
disableReturn: this.options.disableReturn,
targetBlank: this.options.targetBlank
disableReturn: this.options.disableReturn, // deprecated (should use .getEditorOption() instead)
targetBlank: this.options.targetBlank // deprecated (should use .getEditorOption() instead)
};

@@ -465,9 +465,2 @@

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

@@ -548,2 +541,3 @@ if (extensions[buttonName]) {

}
return Util.defaults({}, options, defaults);

@@ -597,3 +591,2 @@ }

MediumEditor.prototype = {
defaults: editorDefaults,

@@ -648,4 +641,13 @@

this.commands.forEach(function (extension) {
if (typeof extension.destroy === 'function') {
extension.destroy();
} else if (typeof extension.deactivate === 'function') {
Util.warn('Extension .deactivate() function has been deprecated. Use .destroy() instead. This will be removed in version 5.0.0');
extension.deactivate();
}
}, this);
if (this.toolbar !== undefined) {
this.toolbar.deactivate();
this.toolbar.destroy();
delete this.toolbar;

@@ -655,2 +657,7 @@ }

this.elements.forEach(function (element) {
// Reset elements content, fix for issue where after editor destroyed the red underlines on spelling errors are left
if (this.options.spellcheck) {
element.innerHTML = element.innerHTML;
}
element.removeAttribute('contentEditable');

@@ -674,8 +681,2 @@ element.removeAttribute('spellcheck');

this.commands.forEach(function (extension) {
if (typeof extension.deactivate === 'function') {
extension.deactivate();
}
}, this);
this.events.destroy();

@@ -700,4 +701,5 @@ },

createEvent: function (event) {
this.events.defineCustomEvent(event);
createEvent: function () {
Util.warn('.createEvent() has been deprecated and is no longer needed. ' +
'You can attach and trigger custom events without calling this method. This will be removed in v5.0.0');
},

@@ -1031,7 +1033,7 @@

if (this.options.targetBlank || opts.target === '_blank') {
Util.setTargetBlank(Util.getSelectionStart(this.options.ownerDocument), opts.url);
Util.setTargetBlank(Selection.getSelectionStart(this.options.ownerDocument), opts.url);
}
if (opts.buttonClass) {
Util.addClassToAnchors(Util.getSelectionStart(this.options.ownerDocument), opts.buttonClass);
Util.addClassToAnchors(Selection.getSelectionStart(this.options.ownerDocument), opts.buttonClass);
}

@@ -1038,0 +1040,0 @@ }

var editorDefaults;
(function () {
// summary: The default options hash used by the Editor
editorDefaults = {
allowMultiParagraphSelection: true,

@@ -34,3 +32,2 @@ buttons: ['bold', 'italic', 'underline', 'anchor', 'header1', 'header2', 'quote'],

};
})();

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

Events.prototype = {
InputEventOnContenteditableSupported: !Util.isIE,

@@ -59,9 +58,6 @@

this.setupListener(event);
// If we don't support this custom event, don't do anything
if (this.listeners[event]) {
if (!this.customEvents[event]) {
this.customEvents[event] = [];
}
this.customEvents[event].push(listener);
if (!this.customEvents[event]) {
this.customEvents[event] = [];
}
this.customEvents[event].push(listener);
},

@@ -73,10 +69,6 @@

this.customEvents[event].splice(index, 1);
// TODO: If array is empty, should detach internal listeners via destoryListener()
// TODO: If array is empty, should detach internal listeners via destroyListener()
}
},
defineCustomEvent: function (event) {
this.listeners[event] = true;
},
indexOfCustomListener: function (event, listener) {

@@ -169,14 +161,16 @@ if (!this.customEvents[event] || !this.customEvents[event].length) {

if (doc.execCommand.listeners) {
var args = Array.prototype.slice.call(arguments);
doc.execCommand.listeners.forEach(function (listener) {
listener({
command: aCommandName,
value: aValueArgument,
args: args,
result: result
});
});
if (!doc.execCommand.listeners) {
return result;
}
var args = Array.prototype.slice.call(arguments);
doc.execCommand.listeners.forEach(function (listener) {
listener({
command: aCommandName,
value: aValueArgument,
args: args,
result: result
});
});
return result;

@@ -218,3 +212,2 @@ };

this.attachDOMEvent(this.options.ownerDocument.body, 'focus', this.handleBodyFocus.bind(this), true);
this.listeners[name] = true;
break;

@@ -224,3 +217,2 @@ case 'blur':

this.setupListener('externalInteraction');
this.listeners[name] = true;
break;

@@ -230,3 +222,2 @@ case 'focus':

this.setupListener('externalInteraction');
this.listeners[name] = true;
break;

@@ -254,4 +245,2 @@ case 'editableInput':

}
this.listeners[name] = true;
break;

@@ -263,3 +252,2 @@ case 'editableClick':

}.bind(this));
this.listeners[name] = true;
break;

@@ -271,3 +259,2 @@ case 'editableBlur':

}.bind(this));
this.listeners[name] = true;
break;

@@ -279,3 +266,2 @@ case 'editableKeypress':

}.bind(this));
this.listeners[name] = true;
break;

@@ -287,3 +273,2 @@ case 'editableKeyup':

}.bind(this));
this.listeners[name] = true;
break;

@@ -295,3 +280,2 @@ case 'editableKeydown':

}.bind(this));
this.listeners[name] = true;
break;

@@ -301,3 +285,2 @@ case 'editableKeydownEnter':

this.setupListener('editableKeydown');
this.listeners[name] = true;
break;

@@ -307,3 +290,2 @@ case 'editableKeydownTab':

this.setupListener('editableKeydown');
this.listeners[name] = true;
break;

@@ -313,3 +295,2 @@ case 'editableKeydownDelete':

this.setupListener('editableKeydown');
this.listeners[name] = true;
break;

@@ -321,3 +302,2 @@ case 'editableMouseover':

}, this);
this.listeners[name] = true;
break;

@@ -330,3 +310,2 @@ case 'editableDrag':

}, this);
this.listeners[name] = true;
break;

@@ -338,3 +317,2 @@ case 'editableDrop':

}, this);
this.listeners[name] = true;
break;

@@ -346,5 +324,5 @@ case 'editablePaste':

}, this);
this.listeners[name] = true;
break;
}
this.listeners[name] = true;
},

@@ -430,4 +408,3 @@

// which will point to whatever element has focus.
if (event.currentTarget &&
event.currentTarget.activeElement) {
if (event.currentTarget && event.currentTarget.activeElement) {
var activeElement = event.currentTarget.activeElement,

@@ -525,16 +502,15 @@ currentTarget;

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;
if (Util.isKey(event, Util.keyCode.ENTER)) {
return this.triggerCustomEvent('editableKeydownEnter', event, event.currentTarget);
}
if (Util.isKey(event, Util.keyCode.TAB)) {
return this.triggerCustomEvent('editableKeydownTab', event, event.currentTarget);
}
if (Util.isKey(event, [Util.keyCode.DELETE, Util.keyCode.BACKSPACE])) {
return this.triggerCustomEvent('editableKeydownDelete', event, event.currentTarget);
}
}
};
}());

@@ -110,2 +110,11 @@ var Extension;

/* destroy: [function ()]
*
* This method should remove any created html, custom event handlers
* or any other cleanup tasks that should be performed.
* If implemented, this function will be called when MediumEditor's
* destroy method has been called.
*/
destroy: undefined,
/* As alternatives to checkState, these functions provide a more structured

@@ -197,4 +206,56 @@ * path to updating the state of an extension (usually a button) whenever

*/
'document': undefined
'document': undefined,
/* getEditorElements: [function ()]
*
* Helper function which returns an array containing
* all the contenteditable elements for this instance
* of MediumEditor
*/
getEditorElements: function () {
return this.base.elements;
},
/* getEditorId: [function ()]
*
* Helper function which returns a unique identifier
* for this instance of MediumEditor
*/
getEditorId: function () {
return this.base.id;
},
/* getEditorOptions: [function (option)]
*
* Helper function which returns the value of an option
* used to initialize this instance of MediumEditor
*/
getEditorOption: function (option) {
return this.base.options[option];
}
};
/* List of method names to add to the prototype of Extension
* Each of these methods will be defined as helpers that
* just call directly into the MediumEditor instance.
*
* example for 'on' method:
* Extension.prototype.on = function () {
* return this.base.on.apply(this.base, arguments);
* }
*/
[
// general helpers
'execAction',
// event handling
'on',
'off',
'subscribe'
].forEach(function (helper) {
Extension.prototype[helper] = function () {
return this.base[helper].apply(this.base, arguments);
};
});
})();

@@ -23,7 +23,5 @@ var AnchorPreview;

/* ----- internal options needed from base ----- */
'window': window,
'document': document,
diffLeft: 0,
diffTop: -10,
elementsContainer: false,
diffLeft: 0, // deprecated (should use .getEditorOption() instead)
diffTop: -10, // deprecated (should use .getEditorOption() instead)
elementsContainer: false, // deprecated (should use .getEditorOption() instead)

@@ -48,7 +46,7 @@ init: function () {

el.id = 'medium-editor-anchor-preview-' + this.base.id;
el.id = 'medium-editor-anchor-preview-' + this.getEditorId();
el.className = 'medium-editor-anchor-preview';
el.innerHTML = this.getTemplate();
this.base.on(el, 'click', this.handleClick.bind(this));
this.on(el, 'click', this.handleClick.bind(this));

@@ -64,3 +62,3 @@ return el;

deactivate: function () {
destroy: function () {
if (this.anchorPreview) {

@@ -74,2 +72,7 @@ if (this.anchorPreview.parentNode) {

// TODO: deprecate
deactivate: function () {
Util.deprecatedMethod.call(this, 'deactivate', 'destroy', arguments, 'v5.0.0');
},
hidePreview: function () {

@@ -127,3 +130,3 @@ this.anchorPreview.classList.remove('medium-editor-anchor-preview-active');

attachToEditables: function () {
this.base.subscribe('editableMouseover', this.handleEditableMouseover.bind(this));
this.subscribe('editableMouseover', this.handleEditableMouseover.bind(this));
},

@@ -155,3 +158,3 @@

this.anchorToPreview = null;
this.base.off(this.activeAnchor, 'mouseout', this.instanceHandleAnchorMouseout);
this.off(this.activeAnchor, 'mouseout', this.instanceHandleAnchorMouseout);
this.instanceHandleAnchorMouseout = null;

@@ -163,36 +166,37 @@ },

if (target) {
if (false === target) {
return;
}
// Detect empty href attributes
// The browser will make href="" or href="#top"
// into absolute urls when accessed as event.targed.href, so check the html
if (!/href=["']\S+["']/.test(target.outerHTML) || /href=["']#\S+["']/.test(target.outerHTML)) {
return true;
}
// Detect empty href attributes
// The browser will make href="" or href="#top"
// into absolute urls when accessed as event.targed.href, so check the html
if (!/href=["']\S+["']/.test(target.outerHTML) || /href=["']#\S+["']/.test(target.outerHTML)) {
return true;
}
// only show when hovering on anchors
if (this.base.toolbar && this.base.toolbar.isDisplayed()) {
// only show when toolbar is not present
return true;
}
// only show when hovering on anchors
if (this.base.toolbar && this.base.toolbar.isDisplayed()) {
// only show when toolbar is not present
return true;
}
// detach handler for other anchor in case we hovered multiple anchors quickly
if (this.activeAnchor && this.activeAnchor !== target) {
this.detachPreviewHandlers();
}
// detach handler for other anchor in case we hovered multiple anchors quickly
if (this.activeAnchor && this.activeAnchor !== target) {
this.detachPreviewHandlers();
}
this.anchorToPreview = target;
this.anchorToPreview = target;
this.instanceHandleAnchorMouseout = this.handleAnchorMouseout.bind(this);
this.base.on(this.anchorToPreview, 'mouseout', this.instanceHandleAnchorMouseout);
// Using setTimeout + delay because:
// - We're going to show the anchor preview according to the configured delay
// if the mouse has not left the anchor tag in that time
this.base.delay(function () {
if (this.anchorToPreview) {
//this.activeAnchor = this.anchorToPreview;
this.showPreview(this.anchorToPreview);
}
}.bind(this));
}
this.instanceHandleAnchorMouseout = this.handleAnchorMouseout.bind(this);
this.on(this.anchorToPreview, 'mouseout', this.instanceHandleAnchorMouseout);
// Using setTimeout + delay because:
// - We're going to show the anchor preview according to the configured delay
// if the mouse has not left the anchor tag in that time
this.base.delay(function () {
if (this.anchorToPreview) {
//this.activeAnchor = this.anchorToPreview;
this.showPreview(this.anchorToPreview);
}
}.bind(this));
},

@@ -226,7 +230,7 @@

if (this.instanceHandlePreviewMouseover) {
this.base.off(this.anchorPreview, 'mouseover', this.instanceHandlePreviewMouseover);
this.base.off(this.anchorPreview, 'mouseout', this.instanceHandlePreviewMouseout);
this.off(this.anchorPreview, 'mouseover', this.instanceHandlePreviewMouseover);
this.off(this.anchorPreview, 'mouseout', this.instanceHandlePreviewMouseout);
if (this.activeAnchor) {
this.base.off(this.activeAnchor, 'mouseover', this.instanceHandlePreviewMouseover);
this.base.off(this.activeAnchor, 'mouseout', this.instanceHandlePreviewMouseout);
this.off(this.activeAnchor, 'mouseover', this.instanceHandlePreviewMouseover);
this.off(this.activeAnchor, 'mouseout', this.instanceHandlePreviewMouseout);
}

@@ -250,8 +254,8 @@ }

this.base.on(this.anchorPreview, 'mouseover', this.instanceHandlePreviewMouseover);
this.base.on(this.anchorPreview, 'mouseout', this.instanceHandlePreviewMouseout);
this.base.on(this.activeAnchor, 'mouseover', this.instanceHandlePreviewMouseover);
this.base.on(this.activeAnchor, 'mouseout', this.instanceHandlePreviewMouseout);
this.on(this.anchorPreview, 'mouseover', this.instanceHandlePreviewMouseover);
this.on(this.anchorPreview, 'mouseout', this.instanceHandlePreviewMouseout);
this.on(this.activeAnchor, 'mouseover', this.instanceHandlePreviewMouseover);
this.on(this.activeAnchor, 'mouseout', this.instanceHandlePreviewMouseout);
}
});
}());

@@ -8,3 +8,2 @@ var AnchorForm;

AnchorForm = FormExtension.extend({
/* Anchor Form Options */

@@ -45,6 +44,2 @@

/* ----- internal options needed from base ----- */
'window': window,
'document': document,
// Options for the Button base class

@@ -61,10 +56,10 @@ name: 'anchor',

// Overrides ButtonExtension.handleClick
handleClick: function (evt) {
evt.preventDefault();
evt.stopPropagation();
handleClick: function (event) {
event.preventDefault();
event.stopPropagation();
var selectedParentElement = Selection.getSelectedParentElement(Util.getSelectionRange(this.document));
var selectedParentElement = Selection.getSelectedParentElement(Selection.getSelectionRange(this.document));
if (selectedParentElement.tagName &&
selectedParentElement.tagName.toLowerCase() === 'a') {
return this.base.execAction('unlink');
return this.execAction('unlink');
}

@@ -81,7 +76,5 @@

// Overrides Button.handleKeydown
handleKeydown: function (evt) {
var key = String.fromCharCode(evt.which || evt.keyCode).toLowerCase();
if (this.key === key && Util.isMetaCtrlKey(evt)) {
this.handleClick(evt);
handleKeydown: function (event) {
if (Util.isKey(event, this.key.charCodeAt(0)) && Util.isMetaCtrlKey(event)) {
this.handleClick(event);
}

@@ -99,3 +92,2 @@ },

getTemplate: function () {
var template = [

@@ -107,3 +99,3 @@ '<input type="text" class="medium-editor-toolbar-input" placeholder="', this.placeholderText, '">'

'<a href="#" class="medium-editor-toolbar-save">',
this.base.options.buttonLabels === 'fontawesome' ? '<i class="fa fa-check"></i>' : this.formSaveLabel,
this.getEditorOption('buttonLabels') === 'fontawesome' ? '<i class="fa fa-check"></i>' : this.formSaveLabel,
'</a>'

@@ -113,3 +105,3 @@ );

template.push('<a href="#" class="medium-editor-toolbar-close">',
this.base.options.buttonLabels === 'fontawesome' ? '<i class="fa fa-times"></i>' : this.formCloseLabel,
this.getEditorOption('buttonLabels') === 'fontawesome' ? '<i class="fa fa-times"></i>' : this.formCloseLabel,
'</a>');

@@ -168,4 +160,4 @@

// Called by core when tearing down medium-editor (deactivate)
deactivate: function () {
// Called by core when tearing down medium-editor (destroy)
destroy: function () {
if (!this.form) {

@@ -182,2 +174,7 @@ return false;

// TODO: deprecate
deactivate: function () {
Util.deprecatedMethod.call(this, 'deactivate', 'destroy', arguments, 'v5.0.0');
},
// core methods

@@ -197,6 +194,5 @@

opts.target = '_self';
if (targetCheckbox && targetCheckbox.checked) {
opts.target = '_blank';
} else {
opts.target = '_self';
}

@@ -218,3 +214,3 @@

this.base.restoreSelection();
this.base.createLink(opts);
this.execAction(this.action, opts);
this.base.checkSelection();

@@ -234,3 +230,2 @@ },

// form creation and event handling
attachFormEvents: function (form) {

@@ -242,12 +237,12 @@ var close = form.querySelector('.medium-editor-toolbar-close'),

// Handle clicks on the form itself
this.base.on(form, 'click', this.handleFormClick.bind(this));
this.on(form, 'click', this.handleFormClick.bind(this));
// Handle typing in the textbox
this.base.on(input, 'keyup', this.handleTextboxKeyup.bind(this));
this.on(input, 'keyup', this.handleTextboxKeyup.bind(this));
// Handle close button clicks
this.base.on(close, 'click', this.handleCloseClick.bind(this));
this.on(close, 'click', this.handleCloseClick.bind(this));
// Handle save button clicks (capture)
this.base.on(save, 'click', this.handleSaveClick.bind(this), true);
this.on(save, 'click', this.handleSaveClick.bind(this), true);

@@ -262,3 +257,3 @@ },

form.className = 'medium-editor-toolbar-form';
form.id = 'medium-editor-toolbar-form-anchor-' + this.base.id;
form.id = 'medium-editor-toolbar-form-anchor-' + this.getEditorId();
form.innerHTML = this.getTemplate();

@@ -265,0 +260,0 @@ this.attachFormEvents(form);

@@ -32,7 +32,6 @@ /*global Extension, Util */

AutoLink = Extension.extend({
init: function () {
this.disableEventHandling = false;
this.base.subscribe('editableKeypress', this.onKeypress.bind(this));
this.base.subscribe('editableBlur', this.onBlur.bind(this));
this.subscribe('editableKeypress', this.onKeypress.bind(this));
this.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.

@@ -42,2 +41,9 @@ this.document.execCommand('AutoUrlDetect', false, false);

destroy: function () {
// Turn AutoUrlDetect back on
if (this.document.queryCommandSupported('AutoUrlDetect')) {
this.document.execCommand('AutoUrlDetect', false, true);
}
},
onBlur: function (blurEvent, editable) {

@@ -52,5 +58,3 @@ this.performLinking(editable);

if (keyPressEvent.keyCode === Util.keyCode.SPACE ||
keyPressEvent.keyCode === Util.keyCode.ENTER ||
keyPressEvent.which === Util.keyCode.SPACE) {
if (Util.isKey(keyPressEvent, [Util.keyCode.SPACE, Util.keyCode.ENTER])) {
clearTimeout(this.performLinkingTimeout);

@@ -166,2 +170,3 @@ // Saving/restoring the selection in the middle of a keypress doesn't work well...

KNOWN_TLDS_REGEXP.test(match[0].split('.').pop().split('?').shift()));
if (matchOk) {

@@ -179,4 +184,3 @@ matches.push({

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

@@ -183,0 +187,0 @@ currentTextIndex = 0,

@@ -8,8 +8,7 @@ var Button;

Button = Extension.extend({
init: function () {
this.button = this.createButton();
this.base.on(this.button, 'click', this.handleClick.bind(this));
this.on(this.button, 'click', this.handleClick.bind(this));
if (this.key) {
this.base.subscribe('editableKeydown', this.handleKeydown.bind(this));
this.subscribe('editableKeydown', this.handleKeydown.bind(this));
}

@@ -28,15 +27,20 @@ },

},
getAction: function () {
return (typeof this.action === 'function') ? this.action(this.base.options) : this.action;
},
getAria: function () {
return (typeof this.aria === 'function') ? this.aria(this.base.options) : this.aria;
},
getTagNames: function () {
return (typeof this.tagNames === 'function') ? this.tagNames(this.base.options) : this.tagNames;
},
createButton: function () {
var button = this.document.createElement('button'),
content = this.contentDefault,
ariaLabel = this.getAria();
ariaLabel = this.getAria(),
buttonLabels = this.getEditorOption('buttonLabels');
button.classList.add('medium-editor-action');

@@ -49,7 +53,7 @@ button.classList.add('medium-editor-action-' + this.name);

}
if (this.base.options.buttonLabels) {
if (this.base.options.buttonLabels === 'fontawesome' && this.contentFA) {
if (buttonLabels) {
if (buttonLabels === 'fontawesome' && this.contentFA) {
content = this.contentFA;
} else if (typeof this.base.options.buttonLabels === 'object' && this.base.options.buttonLabels[this.name]) {
content = this.base.options.buttonLabels[this.name];
} else if (typeof buttonLabels === 'object' && buttonLabels[this.name]) {
content = buttonLabels[this.name];
}

@@ -60,37 +64,42 @@ }

},
handleKeydown: function (evt) {
var key = String.fromCharCode(evt.which || evt.keyCode).toLowerCase(),
action;
if (this.key === key && Util.isMetaCtrlKey(evt)) {
evt.preventDefault();
evt.stopPropagation();
handleKeydown: function (event) {
var action;
if (Util.isKey(event, this.key.charCodeAt(0)) && Util.isMetaCtrlKey(event) && !event.shiftKey) {
event.preventDefault();
event.stopPropagation();
action = this.getAction();
if (action) {
this.base.execAction(action);
this.execAction(action);
}
}
},
handleClick: function (evt) {
evt.preventDefault();
evt.stopPropagation();
handleClick: function (event) {
event.preventDefault();
event.stopPropagation();
var action = this.getAction();
if (action) {
this.base.execAction(action);
this.execAction(action);
}
},
isActive: function () {
return this.button.classList.contains(this.base.options.activeButtonClass);
return this.button.classList.contains(this.getEditorOption('activeButtonClass'));
},
setInactive: function () {
this.button.classList.remove(this.base.options.activeButtonClass);
this.button.classList.remove(this.getEditorOption('activeButtonClass'));
delete this.knownState;
},
setActive: function () {
this.button.classList.add(this.base.options.activeButtonClass);
this.button.classList.add(this.getEditorOption('activeButtonClass'));
delete this.knownState;
},
queryCommandState: function () {

@@ -103,2 +112,3 @@ var queryState = null;

},
isAlreadyApplied: function (node) {

@@ -137,2 +147,2 @@ var isMatch = false,

});
}());
}());

@@ -40,3 +40,3 @@ /*global Util, Selection, DefaultButton */

var selectedParentElement = Selection.getSelectedParentElement(Util.getSelectionRange(this.base.options.ownerDocument));
var selectedParentElement = Selection.getSelectedParentElement(Selection.getSelectionRange(this.base.options.ownerDocument));
if (selectedParentElement.tagName &&

@@ -43,0 +43,0 @@ selectedParentElement.tagName.toLowerCase() === 'a') {

@@ -5,3 +5,3 @@ var FontSizeForm;

/*global FormExtension, Selection */
/*global FormExtension, Selection, Util */

@@ -61,4 +61,4 @@ FontSizeForm = FormExtension.extend({

// Called by core when tearing down medium-editor (deactivate)
deactivate: function () {
// Called by core when tearing down medium-editor (destroy)
destroy: function () {
if (!this.form) {

@@ -75,2 +75,7 @@ return false;

// TODO: deprecate
deactivate: function () {
Util.deprecatedMethod.call(this, 'deactivate', 'destroy', arguments, 'v5.0.0');
},
// core methods

@@ -90,3 +95,2 @@

// form creation and event handling
createForm: function () {

@@ -101,6 +105,6 @@ var doc = this.document,

form.className = 'medium-editor-toolbar-form';
form.id = 'medium-editor-toolbar-form-fontsize-' + this.base.id;
form.id = 'medium-editor-toolbar-form-fontsize-' + this.getEditorId();
// Handle clicks on the form itself
this.base.on(form, 'click', this.handleFormClick.bind(this));
this.on(form, 'click', this.handleFormClick.bind(this));

@@ -115,3 +119,3 @@ // Add font size slider

// Handle typing in the textbox
this.base.on(input, 'change', this.handleSliderChange.bind(this));
this.on(input, 'change', this.handleSliderChange.bind(this));

@@ -121,3 +125,3 @@ // Add save buton

save.className = 'medium-editor-toobar-save';
save.innerHTML = this.base.options.buttonLabels === 'fontawesome' ?
save.innerHTML = this.getEditorOption('buttonLabels') === 'fontawesome' ?
'<i class="fa fa-check"></i>' :

@@ -128,3 +132,3 @@ '&#10003;';

// Handle save button clicks (capture)
this.base.on(save, 'click', this.handleSaveClick.bind(this), true);
this.on(save, 'click', this.handleSaveClick.bind(this), true);

@@ -134,3 +138,3 @@ // Add close button

close.className = 'medium-editor-toobar-close';
close.innerHTML = this.base.options.buttonLabels === 'fontawesome' ?
close.innerHTML = this.getEditorOption('buttonLabels') === 'fontawesome' ?
'<i class="fa fa-times"></i>' :

@@ -141,3 +145,3 @@ '&times;';

// Handle close button clicks
this.base.on(close, 'click', this.handleCloseClick.bind(this));
this.on(close, 'click', this.handleCloseClick.bind(this));

@@ -164,3 +168,3 @@ return form;

} else {
this.base.execAction('fontSize', { size: size });
this.execAction('fontSize', { size: size });
}

@@ -167,0 +171,0 @@ },

@@ -13,3 +13,2 @@ var FormExtension;

FormExtension = Button.extend({
// default labels for the form buttons

@@ -50,3 +49,2 @@ formSaveLabel: '&#10003;',

});
})();

@@ -8,6 +8,5 @@ /*global Util, Extension */

ImageDragging = Extension.extend({
init: function () {
this.base.subscribe('editableDrag', this.handleDrag.bind(this));
this.base.subscribe('editableDrop', this.handleDrop.bind(this));
this.subscribe('editableDrag', this.handleDrag.bind(this));
this.subscribe('editableDrop', this.handleDrop.bind(this));
},

@@ -60,3 +59,2 @@

});
}());

@@ -51,3 +51,2 @@ /*global Util, Selection, Extension */

PasteHandler = Extension.extend({
/* Paste Options */

@@ -84,10 +83,8 @@

/* ----- internal options needed from base ----- */
'window': window,
'document': document,
targetBlank: false,
disableReturn: false,
targetBlank: false, // deprecated (should use .getEditorOption() instead)
disableReturn: false, // deprecated (should use .getEditorOption() instead)
init: function () {
if (this.forcePlainText || this.cleanPastedHTML) {
this.base.subscribe('editablePaste', this.handlePaste.bind(this));
this.subscribe('editablePaste', this.handlePaste.bind(this));
}

@@ -157,39 +154,34 @@ },

if (multiline) {
// double br's aren't converted to p tags, but we want paragraphs.
elList = text.split('<br><br>');
if (!multiline) {
return this.pasteHTML(text);
}
this.pasteHTML('<p>' + elList.join('</p><p>') + '</p>');
// double br's aren't converted to p tags, but we want paragraphs.
elList = text.split('<br><br>');
try {
this.document.execCommand('insertText', false, '\n');
} catch (ignore) { }
this.pasteHTML('<p>' + elList.join('</p><p>') + '</p>');
// block element cleanup
elList = el.querySelectorAll('a,p,div,br');
for (i = 0; i < elList.length; i += 1) {
workEl = elList[i];
try {
this.document.execCommand('insertText', false, '\n');
} catch (ignore) { }
// Microsoft Word replaces some spaces with newlines.
// While newlines between block elements are meaningless, newlines within
// elements are sometimes actually spaces.
workEl.innerHTML = workEl.innerHTML.replace(/\n/gi, ' ');
// block element cleanup
elList = el.querySelectorAll('a,p,div,br');
for (i = 0; i < elList.length; i += 1) {
workEl = elList[i];
switch (workEl.tagName.toLowerCase()) {
case 'a':
if (this.targetBlank) {
Util.setTargetBlank(workEl);
}
break;
case 'p':
case 'div':
this.filterCommonBlocks(workEl);
break;
case 'br':
this.filterLineBreak(workEl);
break;
}
// Microsoft Word replaces some spaces with newlines.
// While newlines between block elements are meaningless, newlines within
// elements are sometimes actually spaces.
workEl.innerHTML = workEl.innerHTML.replace(/\n/gi, ' ');
switch (workEl.tagName.toLowerCase()) {
case 'p':
case 'div':
this.filterCommonBlocks(workEl);
break;
case 'br':
this.filterLineBreak(workEl);
break;
}
} else {
this.pasteHTML(text);
}

@@ -217,2 +209,7 @@ },

workEl = elList[i];
if ('a' === workEl.tagName.toLowerCase() && this.targetBlank) {
Util.setTargetBlank(workEl);
}
Util.cleanupAttrs(workEl, options.cleanAttrs);

@@ -236,3 +233,2 @@ Util.cleanupTags(workEl, options.cleanTags);

filterLineBreak: function (el) {
if (this.isCommonBlock(el.previousElementSibling)) {

@@ -297,3 +293,2 @@ // remove stray br's following common block elements

});
}());

@@ -29,3 +29,3 @@ var Placeholder;

initPlaceholders: function () {
this.base.elements.forEach(function (el) {
this.getEditorElements().forEach(function (el) {
if (!el.getAttribute('data-placeholder')) {

@@ -38,2 +38,10 @@ el.setAttribute('data-placeholder', this.text);

destroy: function () {
this.getEditorElements().forEach(function (el) {
if (el.getAttribute('data-placeholder') === this.text) {
el.removeAttribute('data-placeholder');
}
}, this);
},
showPlaceholder: function (el) {

@@ -54,6 +62,6 @@ if (el) {

if (!(el.querySelector('img, blockquote, ul, ol')) && el.textContent.replace(/^\s+|\s+$/g, '') === '') {
this.showPlaceholder(el);
} else {
this.hidePlaceholder(el);
return this.showPlaceholder(el);
}
this.hidePlaceholder(el);
},

@@ -63,17 +71,17 @@

// Custom events
this.base.subscribe('blur', this.handleExternalInteraction.bind(this));
this.subscribe('blur', this.handleExternalInteraction.bind(this));
// Check placeholder on blur
this.base.subscribe('editableBlur', this.handleBlur.bind(this));
this.subscribe('editableBlur', this.handleBlur.bind(this));
// 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));
this.subscribe('editableClick', this.handleHidePlaceholderEvent.bind(this));
} else {
this.base.subscribe('editableKeyup', this.handleBlur.bind(this));
this.subscribe('editableKeyup', this.handleBlur.bind(this));
}
// Events where we always hide the placeholder
this.base.subscribe('editableKeypress', this.handleHidePlaceholderEvent.bind(this));
this.base.subscribe('editablePaste', this.handleHidePlaceholderEvent.bind(this));
this.subscribe('editableKeypress', this.handleHidePlaceholderEvent.bind(this));
this.subscribe('editablePaste', this.handleHidePlaceholderEvent.bind(this));
},

@@ -80,0 +88,0 @@

@@ -134,11 +134,18 @@ /*global Util */

getSelectedParentElement: function (range) {
var selectedParentElement = null;
if (!range) {
return null;
}
// Selection encompasses a single element
if (this.rangeSelectsSingleNode(range) && range.startContainer.childNodes[range.startOffset].nodeType !== 3) {
selectedParentElement = range.startContainer.childNodes[range.startOffset];
} else if (range.startContainer.nodeType === 3) {
selectedParentElement = range.startContainer.parentNode;
} else {
selectedParentElement = range.startContainer;
return range.startContainer.childNodes[range.startOffset];
}
return selectedParentElement;
// Selection range starts inside a text node, so get its parent
if (range.startContainer.nodeType === 3) {
return range.startContainer.parentNode;
}
// Selection starts inside an element
return range.startContainer;
},

@@ -152,4 +159,3 @@

if (!selection.rangeCount ||
!selection.getRangeAt(0).commonAncestorContainer) {
if (!selection.rangeCount || selection.isCollapsed || !selection.getRangeAt(0).commonAncestorContainer) {
return [];

@@ -204,4 +210,41 @@ }

sel.addRange(range);
},
getSelectionRange: function (ownerDocument) {
var selection = ownerDocument.getSelection();
if (selection.rangeCount === 0 || true === selection.isCollapsed) {
return null;
}
return selection.getRangeAt(0);
},
// http://stackoverflow.com/questions/1197401/how-can-i-get-the-element-the-caret-is-in-with-javascript-when-using-contentedi
// by You
getSelectionStart: function (ownerDocument) {
var node = ownerDocument.getSelection().anchorNode,
startNode = (node && node.nodeType === 3 ? node.parentNode : node);
return startNode;
},
getSelectionData: function (el) {
var tagName;
if (el && el.tagName) {
tagName = el.tagName.toLowerCase();
}
while (el && Util.parentElements.indexOf(tagName) === -1) {
el = el.parentNode;
if (el && el.tagName) {
tagName = el.tagName.toLowerCase();
}
}
return {
el: el,
tagName: tagName
};
}
};
}());

@@ -8,3 +8,3 @@ /*global Util, Selection*/

Toolbar = function Toolbar(instance) {
Toolbar = function (instance) {
this.base = instance;

@@ -17,3 +17,2 @@ this.options = instance.options;

Toolbar.prototype = {
// Toolbar creation/deletion

@@ -81,3 +80,3 @@

deactivate: function () {
destroy: function () {
if (this.toolbar) {

@@ -91,2 +90,7 @@ if (this.toolbar.parentNode) {

// TODO: deprecate
deactivate: function () {
Util.deprecatedMethod.call(this, 'deactivate', 'destroy', arguments, 'v5.0.0');
},
// Toolbar accessors

@@ -120,3 +124,2 @@

attachEventHandlers: function () {
// MediumEditor custom events for when user beings and ends interaction with a contenteditable and its elements

@@ -314,40 +317,37 @@ this.base.subscribe('blur', this.handleBlur.bind(this));

checkState: function () {
if (this.base.preventSelectionUpdates) {
return;
}
if (!this.base.preventSelectionUpdates) {
// If no editable has focus OR selection is inside contenteditable = false
// hide toolbar
if (!this.base.getFocusedElement() ||
Selection.selectionInContentEditableFalse(this.options.contentWindow)) {
return this.hideToolbar();
}
// If no editable has focus OR selection is inside contenteditable = false
// hide toolbar
if (!this.base.getFocusedElement() ||
Selection.selectionInContentEditableFalse(this.options.contentWindow)) {
this.hideToolbar();
return;
}
// If there's no selection element, selection element doesn't belong to this editor
// or toolbar is disabled for this selection element
// hide toolbar
var selectionElement = Selection.getSelectionElement(this.options.contentWindow);
if (!selectionElement ||
this.base.elements.indexOf(selectionElement) === -1 ||
selectionElement.getAttribute('data-disable-toolbar')) {
return this.hideToolbar();
}
// If there's no selection element, selection element doesn't belong to this editor
// or toolbar is disabled for this selection element
// hide toolbar
var selectionElement = Selection.getSelectionElement(this.options.contentWindow);
if (!selectionElement ||
this.base.elements.indexOf(selectionElement) === -1 ||
selectionElement.getAttribute('data-disable-toolbar')) {
this.hideToolbar();
return;
}
// Now we know there's a focused editable with a selection
// Now we know there's a focused editable with a selection
// If the updateOnEmptySelection option is true, show the toolbar
if (this.options.updateOnEmptySelection && this.options.staticToolbar) {
return this.showAndUpdateToolbar();
}
// If the updateOnEmptySelection option is true, show the toolbar
if (this.options.updateOnEmptySelection && this.options.staticToolbar) {
this.showAndUpdateToolbar();
return;
}
// If we don't have a 'valid' selection -> hide toolbar
if (this.options.contentWindow.getSelection().toString().trim() === '' ||
(this.options.allowMultiParagraphSelection === false && this.multipleBlockElementsSelected())) {
return this.hideToolbar();
}
// If we don't have a 'valid' selection -> hide toolbar
if (this.options.contentWindow.getSelection().toString().trim() === '' ||
(this.options.allowMultiParagraphSelection === false && this.multipleBlockElementsSelected())) {
this.hideToolbar();
} else {
this.showAndUpdateToolbar();
}
}
this.showAndUpdateToolbar();
},

@@ -365,2 +365,3 @@

this.setToolbarButtonStates();
this.base.trigger('positionToolbar', {}, this.base.getFocusedElement());
this.showToolbarDefaultActions();

@@ -377,2 +378,3 @@ this.setToolbarPosition();

}.bind(this));
this.checkActiveButtons();

@@ -384,3 +386,3 @@ },

queryState = null,
selectionRange = Util.getSelectionRange(this.options.ownerDocument),
selectionRange = Selection.getSelectionRange(this.options.ownerDocument),
parentNode,

@@ -456,3 +458,2 @@ updateExtensionState = function (extension) {

this.positionStaticToolbar(container);
} else if (!selection.isCollapsed) {

@@ -506,8 +507,14 @@ this.showToolbar();

if (this.options.toolbarAlign === 'left') {
targetLeft = containerRect.left;
} else if (this.options.toolbarAlign === 'center') {
targetLeft = containerCenter - halfOffsetWidth;
} else if (this.options.toolbarAlign === 'right') {
targetLeft = containerRect.right - toolbarWidth;
switch (this.options.toolbarAlign) {
case 'left':
targetLeft = containerRect.left;
break;
case 'right':
targetLeft = containerRect.right - toolbarWidth;
break;
case 'center':
targetLeft = containerCenter - halfOffsetWidth;
break;
}

@@ -548,2 +555,3 @@

}
if (middleBoundary < halfOffsetWidth) {

@@ -550,0 +558,0 @@ toolbarElement.style.left = defaultLeft + halfOffsetWidth + 'px';

@@ -83,2 +83,28 @@ /*global NodeFilter, console, Selection*/

/**
* Returns true if the key associated to the event is inside keys array
*
* @see : https://github.com/jquery/jquery/blob/0705be475092aede1eddae01319ec931fb9c65fc/src/event.js#L473-L484
* @see : http://stackoverflow.com/q/4471582/569101
*/
isKey: function (event, keys) {
var keyCode = event.which;
// getting the key code from event
if (null === keyCode) {
keyCode = event.charCode !== null ? event.charCode : event.keyCode;
}
// it's not an array let's just compare strings!
if (false === Array.isArray(keys)) {
return keyCode === keys;
}
if (-1 === keys.indexOf(keyCode)) {
return false;
}
return true;
},
parentElements: ['p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'blockquote', 'pre'],

@@ -279,39 +305,21 @@

getSelectionRange: function (ownerDocument) {
var selection = ownerDocument.getSelection();
if (selection.rangeCount === 0) {
return null;
}
return selection.getRangeAt(0);
this.deprecated('Util.getSelectionRange', 'Selection.getSelectionRange', 'v5.0.0');
return Selection.getSelectionRange(ownerDocument);
},
// http://stackoverflow.com/questions/1197401/how-can-i-get-the-element-the-caret-is-in-with-javascript-when-using-contentedi
// by You
getSelectionStart: function (ownerDocument) {
var node = ownerDocument.getSelection().anchorNode,
startNode = (node && node.nodeType === 3 ? node.parentNode : node);
return startNode;
this.deprecated('Util.getSelectionStart', 'Selection.getSelectionStart', 'v5.0.0');
return Selection.getSelectionStart(ownerDocument);
},
getSelectionData: function (el) {
var tagName;
this.deprecated('Util.getSelectionData', 'Selection.getSelectionData', 'v5.0.0');
if (el && el.tagName) {
tagName = el.tagName.toLowerCase();
}
while (el && this.parentElements.indexOf(tagName) === -1) {
el = el.parentNode;
if (el && el.tagName) {
tagName = el.tagName.toLowerCase();
}
}
return {
el: el,
tagName: tagName
};
return Selection.getSelectionData(el);
},
execFormatBlock: function (doc, tagName) {
var selectionData = this.getSelectionData(this.getSelectionStart(doc));
var selectionData = Selection.getSelectionData(Selection.getSelectionStart(doc));
// FF handles blockquote differently on formatBlock

@@ -555,3 +563,3 @@ // allowing nesting, we need to use outdent

if (!firstChild) {
if (Util.isDescendant(nextNode, startNode, true)) {
if (this.isDescendant(nextNode, startNode, true)) {
firstChild = nextNode;

@@ -691,4 +699,5 @@ }

getClosestTag: function (el, tag) { // get the closest parent
return Util.traverseUp(el, function (element) {
// get the closest parent
getClosestTag: function (el, tag) {
return this.traverseUp(el, function (element) {
return element.tagName.toLowerCase() === tag.toLowerCase();

@@ -695,0 +704,0 @@ });

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

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

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

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

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

Sorry, the diff of this file is not supported yet

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