medium-editor
Advanced tools
Comparing version 5.19.1 to 5.20.0
62
API.md
@@ -40,5 +40,10 @@ # MediumEditor Object API (v5.0.0) | ||
- [`delay(fn)`](#delayfn) | ||
- [`getContent(index)`](#getcontentindex) | ||
- [`getExtensionByName(name)`](#getextensionbynamename) | ||
- [`resetContent(element)`](#resetcontentelement) | ||
- [`serialize()`](#serialize) | ||
- [`setContent(html, index)`](#setcontenthtml-index) | ||
- [Static Functions/Properties](#static-functionsproperties) | ||
- [`getEditorFromElement(element)`](#geteditorfromelementelement) | ||
- [`version`](#version) | ||
@@ -440,2 +445,12 @@ <!-- END doctoc generated TOC please keep comment here to allow auto update --> | ||
*** | ||
### `getContent(index)` | ||
Returns the trimmed html content for the first editor **element**, or the **element** at `index`. | ||
**Arguments** | ||
1. _**index** (`integer`)_: _**OPTIONAL**_ | ||
* Index of the editor **element** to retrieve the content from. Defaults to 0 when not provided (returns content of the first editor **element**). | ||
*** | ||
### `getExtensionByName(name)` | ||
@@ -452,2 +467,13 @@ | ||
*** | ||
### `resetContent(element)` | ||
Reset the content of all editor **elements** to their value at the time they were added to the editor. If a specific editor **element** is provided, only the content of that element will be reset. | ||
**Arguments** | ||
1. _**element** (`DOMElement`)_: _**OPTIONAL**_ | ||
* Specific editor **element** to reset the content of. | ||
*** | ||
### `serialize()` | ||
@@ -460,4 +486,3 @@ | ||
Sets the innerHTML content for the element at `index`. | ||
Trigger the `editableInput` event. | ||
Sets the html content for the first editor **element**, or the **element** at `index`. Ensures the the `editableInput` event is triggered. | ||
@@ -469,3 +494,32 @@ **Arguments** | ||
2. _**index** (`integer`)_: | ||
* Index of the element to set the content on. Defaults to 0 when not provided. | ||
2. _**index** (`integer`)_: _**OPTIONAL**_ | ||
* Index of the editor **element** to set the content of. Defaults to 0 when not provided (sets content of the first editor **element**). | ||
*** | ||
## Static Functions/Properties | ||
### `getEditorFromElement(element)` | ||
Given an editor **element**, retrieves the instance of MediumEditor which created/is monitoring the **element** | ||
**Arguments** | ||
1. _**element** (`DOMElement`)_: | ||
* An editor **element** which is part of a MediumEditor instance | ||
### `version` | ||
Object containing data about the version of the current MediumEditor library | ||
**Properties of 'version'** | ||
1. _**major** (`Number`)_ | ||
* The major version number (ie the `3` in `"3.2.1"`) | ||
2. _**minor** (`Number`)_ | ||
* The minor version number (ie the `2` in `"3.2.1"`) | ||
3. _**revision** (`Number`)_ | ||
* The revision (aka "patch") version number (ie the `1` in `"3.2.1"`) | ||
4. _**preRelease** (`String`)_ | ||
* The pre-release version tag (ie the `"rc.1"` in `"5.0.0-rc.1"`) | ||
5. _**toString** (`Function`)_ | ||
* Returns the full version number as a string (ie `"5.0.0-rc.1"`) |
@@ -0,1 +1,10 @@ | ||
5.20.0 / 2016-06-02 | ||
================== | ||
* Fix anchor-preview bug where click preview no longer prefills href into anchor form | ||
* Add getEditorFromElement for retrieving an editor instance from an editor element | ||
* Respect form.reset for textarea elements within forms passed into the editor | ||
* Add getContent + resetContent helpers for retrieving/reverting content of editors | ||
* Add support for extensions preventing blur on editor when user interacts with extension elements | ||
5.19.1 / 2016-05-28 | ||
@@ -2,0 +11,0 @@ ================== |
@@ -45,2 +45,3 @@ # MediumEditor Options (v5.0.0) | ||
- [`previewValueSelector`](#previewvalueselector) | ||
- [`showOnEmptyLinks`](#showonemptylinks) | ||
- [Disabling Anchor Preview](#disabling-anchor-preview) | ||
@@ -339,2 +340,8 @@ - [Placeholder Options](#placeholder-options) | ||
*** | ||
#### `showOnEmptyLinks` | ||
**Default:** `true` | ||
Determines whether the anchor tag preview shows up on link with href as "" or "#something". You should set this value to false if you do not want the preview to show up in such use cases. | ||
*** | ||
#### `showWhenToolbarIsVisible` | ||
@@ -341,0 +348,0 @@ **Default:** `false` |
{ | ||
"name": "medium-editor", | ||
"version": "5.19.1", | ||
"version": "5.20.0", | ||
"author": "Davi Ferreira <hi@daviferreira.com>", | ||
@@ -46,2 +46,3 @@ "contributors": [ | ||
"grunt-bump": "0.7.0", | ||
"grunt-cli": "1.2.0", | ||
"grunt-contrib-concat": "0.5.1", | ||
@@ -67,6 +68,7 @@ "grunt-contrib-connect": "0.11.2", | ||
"scripts": { | ||
"test": "grunt test --verbose", | ||
"test:ci": "grunt travis --verbose", | ||
"start": "open ./demo/index.html" | ||
"test": "node node_modules/grunt-cli/bin/grunt test --verbose", | ||
"test:ci": "node node_modules/grunt-cli/bin/grunt travis --verbose", | ||
"start": "open ./demo/index.html", | ||
"build": "node node_modules/grunt-cli/bin/grunt" | ||
} | ||
} |
@@ -13,3 +13,3 @@ # MediumEditor | ||
![Supportd Browsers](https://cloud.githubusercontent.com/assets/2444240/12874138/d3960a04-cd9b-11e5-8cc5-8136d82cf5f6.png) | ||
![Supported Browsers](https://cloud.githubusercontent.com/assets/2444240/12874138/d3960a04-cd9b-11e5-8cc5-8136d82cf5f6.png) | ||
@@ -255,2 +255,3 @@ [![NPM info](https://nodei.co/npm/medium-editor.png?downloads=true)](https://www.npmjs.com/package/medium-editor) | ||
* __showWhenToolbarIsVisible__: determines whether the anchor tag preview shows up when the toolbar is visible. You should set this value to true if the static option for the toolbar is true and you want the preview to show at the same time. Default: `false` | ||
* __showOnEmptyLinks__: determines whether the anchor tag preview shows up on link with href as '' or '#something'. You should set this value to false if you do not want the preview to show up in such use cases. Default: `true` | ||
@@ -560,9 +561,15 @@ To disable the anchor preview, set the value of the `anchorPreview` option to `false`: | ||
* __.delay(fn)__: delay any function from being executed by the amount of time passed as the `delay` option | ||
* __.getContent(index)__: gets the trimmed `innerHTML` of the element at `index` | ||
* __.getExtensionByName(name)__: get a reference to an extension with the specified name | ||
* __.resetContent(element)__: reset the content of all elements or a specific element to its value when added to the editor initially | ||
* __.serialize()__: returns a JSON object with elements contents | ||
* __.setContent(html, index)__: sets the `innerHTML` to `html` of the element at `index` | ||
### Static Methods/Properties | ||
* __.getEditorFromElement(element)__: retrieve the instance of MediumEditor that is monitoring the provided editor element | ||
* __.version__: the version information for the MediumEditor library | ||
## Dynamically add/remove elements to your instance | ||
It is possible to dynamically add new elements to your existing MediumtEditor instance: | ||
It is possible to dynamically add new elements to your existing MediumEditor instance: | ||
@@ -573,3 +580,3 @@ ```javascript | ||
// imagine an ajax fetch/any other dynamic functionality which will add new '.editable' elements to dom | ||
// imagine an ajax fetch/any other dynamic functionality which will add new '.editable' elements to the DOM | ||
@@ -592,3 +599,3 @@ editor.addElements('.editable'); | ||
Straight forward, just call `removeElements` with the elemnt or array of elements you to want to tear down. Each element itself will remain a contenteditable - it will just remove all event handlers and all references to it so you can safely remove it from DOM. | ||
Straight forward, just call `removeElements` with the element or array of elements you to want to tear down. Each element itself will remain a contenteditable - it will just remove all event handlers and all references to it so you can safely remove it from DOM. | ||
@@ -633,3 +640,3 @@ ```javascript | ||
So for most modern browsers (Chrome, Firefox, Safari, etc.), the `input` event works just fine. Infact, `editableInput` is just a proxy for the `input` event in those browsers. However, the `input` event [is not supported for contenteditable elements in IE 9-11](https://connect.microsoft.com/IE/feedback/details/794285/ie10-11-input-event-does-not-fire-on-div-with-contenteditable-set) and is _mostly_ supported in Microsoft Edge, but not fully. | ||
So for most modern browsers (Chrome, Firefox, Safari, etc.), the `input` event works just fine. In fact, `editableInput` is just a proxy for the `input` event in those browsers. However, the `input` event [is not supported for contenteditable elements in IE 9-11](https://connect.microsoft.com/IE/feedback/details/794285/ie10-11-input-event-does-not-fire-on-div-with-contenteditable-set) and is _mostly_ supported in Microsoft Edge, but not fully. | ||
@@ -636,0 +643,0 @@ So, to properly support the `editableInput` event in Internet Explorer and Microsoft Edge, MediumEditor uses a combination of the `selectionchange` and `keypress` events, as well as monitoring calls to `document.execCommand`. |
@@ -142,2 +142,5 @@ /*global fireEvent, selectElementContentsAndFire */ | ||
// textbox should contain the url | ||
expect(anchor.getInput().value).toEqual('http://test.com'); | ||
// the checkboxes should be unchecked | ||
@@ -310,3 +313,3 @@ expect(anchor.getAnchorTargetCheckbox().checked).toBe(false); | ||
// https://github.com/yabwe/medium-editor/issues/1047 | ||
it('should display the anchor form in the toolbar when clicked when showWhenToolbarIsVisible is set to true adn toolbar is visible', function () { | ||
it('should display the anchor form in the toolbar when clicked when showWhenToolbarIsVisible is set to true and toolbar is visible', function () { | ||
var editor = this.newMediumEditor('.editor', { | ||
@@ -313,0 +316,0 @@ anchorPreview: { |
@@ -58,2 +58,75 @@ /*global fireEvent, selectElementContents, | ||
describe('getContent', function () { | ||
it('should retrieve the content of the first element', function () { | ||
var editor = this.newMediumEditor('.editor'); | ||
expect(editor.getContent()).toEqual('lore ipsum'); | ||
}); | ||
it('should retrieve the content of the element at the specified index', function () { | ||
var otherHTML = 'something different'; | ||
this.createElement('div', 'editor', otherHTML); | ||
var editor = this.newMediumEditor('.editor'); | ||
expect(editor.getContent(1)).toEqual(otherHTML); | ||
}); | ||
it('should return null if no element exists', function () { | ||
var editor = this.newMediumEditor('.no-valid-selector'); | ||
expect(editor.getContent()).toBeNull(); | ||
}); | ||
}); | ||
describe('resetContent', function () { | ||
it('should reset the content of all editor elements to their initial values', function () { | ||
var initialOne = this.el.innerHTML, | ||
initialTwo = 'Lorem ipsum dolor', | ||
elementTwo = this.createElement('div', 'editor', initialTwo), | ||
editor = this.newMediumEditor('.editor'); | ||
editor.setContent('<p>changed content</p>'); | ||
expect(this.el.innerHTML).not.toEqual(initialOne); | ||
editor.setContent('<p>changed content</p>', 1); | ||
expect(elementTwo.innerHTML).not.toEqual(initialTwo); | ||
editor.resetContent(); | ||
expect(this.el.innerHTML).toEqual(initialOne); | ||
expect(elementTwo.innerHTML).toEqual(initialTwo); | ||
}); | ||
it('should reset the content of a specific element when provided', function () { | ||
var initialOne = this.el.innerHTML, | ||
initialTwo = 'Lorem ipsum dolor', | ||
elementTwo = this.createElement('div', 'editor', initialTwo), | ||
editor = this.newMediumEditor('.editor'); | ||
editor.setContent('<p>changed content</p>'); | ||
expect(this.el.innerHTML).not.toEqual(initialOne); | ||
editor.setContent('<p>changed content</p>', 1); | ||
expect(elementTwo.innerHTML).not.toEqual(initialTwo); | ||
editor.resetContent(elementTwo); | ||
expect(this.el.innerHTML).not.toEqual(initialOne); | ||
expect(elementTwo.innerHTML).toEqual(initialTwo); | ||
}); | ||
it('should not reset anything if an invalid element is provided', function () { | ||
var initialOne = this.el.innerHTML, | ||
initialTwo = 'Lorem ipsum dolor', | ||
elementTwo = this.createElement('div', 'editor', initialTwo), | ||
dummyElement = this.createElement('div', 'not-editor', '<p>dummy element</p>'), | ||
editor = this.newMediumEditor('.editor'); | ||
editor.setContent('<p>changed content</p>'); | ||
expect(this.el.innerHTML).not.toEqual(initialOne); | ||
editor.setContent('<p>changed content</p>', 1); | ||
expect(elementTwo.innerHTML).not.toEqual(initialTwo); | ||
editor.resetContent(dummyElement); | ||
expect(this.el.innerHTML).not.toEqual(initialOne); | ||
expect(elementTwo.innerHTML).not.toEqual(initialTwo); | ||
}); | ||
}); | ||
describe('saveSelection/restoreSelection', function () { | ||
@@ -257,2 +330,19 @@ it('should be applicable if html changes but text does not', function () { | ||
}); | ||
describe('getEditorFromElement', function () { | ||
it('should return the editor instance the element belongs to', function () { | ||
var elTwo = this.createElement('div', 'editor-two', 'lore ipsum'), | ||
editorOne = this.newMediumEditor('.editor'), | ||
editorTwo = this.newMediumEditor('.editor-two'); | ||
expect(editorOne.elements[0]).toBe(this.el); | ||
expect(editorTwo.elements[0]).toBe(elTwo); | ||
expect(MediumEditor.getEditorFromElement(this.el)).toBe(editorOne); | ||
expect(MediumEditor.getEditorFromElement(elTwo)).toBe(editorTwo); | ||
}); | ||
it('should return null if the element is not within an editor', function () { | ||
expect(MediumEditor.getEditorFromElement(this.el)).toBeNull(); | ||
}); | ||
}); | ||
}); |
describe('Elements TestCase', function () { | ||
'use strict'; | ||
describe('Initialization', function () { | ||
beforeEach(function () { | ||
setupTestHelpers.call(this); | ||
this.el = this.createElement('div', 'editor', 'lore ipsum'); | ||
}); | ||
beforeEach(function () { | ||
setupTestHelpers.call(this); | ||
this.el = this.createElement('div', 'editor', 'lore ipsum'); | ||
}); | ||
afterEach(function () { | ||
this.cleanupTest(); | ||
}); | ||
afterEach(function () { | ||
this.cleanupTest(); | ||
}); | ||
describe('Initialization', function () { | ||
it('should set element contenteditable attribute to true', function () { | ||
@@ -52,3 +52,46 @@ var editor = this.newMediumEditor('.editor'); | ||
}); | ||
it('should set the data-medium-editor-editor-index attribute to be the id of the editor instance', function () { | ||
var editor = this.newMediumEditor('.editor'); | ||
expect(editor.elements[0]).toBe(this.el); | ||
expect(parseInt(this.el.getAttribute('data-medium-editor-editor-index'))).toBe(editor.id); | ||
}); | ||
}); | ||
describe('Destroy', function () { | ||
it('should remove the contenteditable attribute', function () { | ||
var editor = this.newMediumEditor('.editor'); | ||
expect(this.el.getAttribute('contenteditable')).toEqual('true'); | ||
editor.destroy(); | ||
expect(this.el.hasAttribute('contenteditable')).toBe(false); | ||
}); | ||
it('should remove the medium-editor-element attribute', function () { | ||
var editor = this.newMediumEditor('.editor'); | ||
expect(this.el.getAttribute('data-medium-editor-element')).toEqual('true'); | ||
editor.destroy(); | ||
expect(this.el.hasAttribute('data-medium-editor-element')).toBe(false); | ||
}); | ||
it('should remove the role attribute', function () { | ||
var editor = this.newMediumEditor('.editor'); | ||
expect(this.el.getAttribute('role')).toEqual('textbox'); | ||
editor.destroy(); | ||
expect(this.el.hasAttribute('role')).toBe(false); | ||
}); | ||
it('should remove the aria-multiline attribute', function () { | ||
var editor = this.newMediumEditor('.editor'); | ||
expect(this.el.getAttribute('aria-multiline')).toEqual('true'); | ||
editor.destroy(); | ||
expect(this.el.hasAttribute('aria-multiline')).toBe(false); | ||
}); | ||
it('should remove the data-medium-editor-editor-index attribute', function () { | ||
var editor = this.newMediumEditor('.editor'); | ||
expect(parseInt(this.el.getAttribute('data-medium-editor-editor-index'))).toBe(editor.id); | ||
editor.destroy(); | ||
expect(this.el.hasAttribute('data-medium-editor-editor-index')).toBe(false); | ||
}); | ||
}); | ||
}); |
@@ -1,2 +0,2 @@ | ||
/*global selectElementContentsAndFire */ | ||
/*global selectElementContentsAndFire, fireEvent */ | ||
@@ -287,2 +287,36 @@ describe('Extensions TestCase', function () { | ||
}); | ||
it('should be able to prevent blur on the editor when user iteracts with extension elements', function () { | ||
var sampleElOne = this.createElement('button', null, 'Test Button'), | ||
sampleElTwo = this.createElement('textarea', null, 'Test Div'), | ||
externalEl = this.createElement('div', 'external-element', 'External Element'), | ||
TempExtension = MediumEditor.Extension.extend({ | ||
getInteractionElements: function () { | ||
return [sampleElOne, sampleElTwo]; | ||
} | ||
}), | ||
editor = this.newMediumEditor('.editor', { | ||
extensions: { | ||
'temp-extension': new TempExtension() | ||
} | ||
}), | ||
spy = jasmine.createSpy('handler'); | ||
selectElementContentsAndFire(editor.elements[0]); | ||
jasmine.clock().tick(1); | ||
expect(editor.getExtensionByName('toolbar').isDisplayed()).toBe(true); | ||
editor.subscribe('blur', spy); | ||
fireEvent(sampleElTwo, 'mousedown'); | ||
fireEvent(sampleElTwo, 'mouseup'); | ||
fireEvent(sampleElTwo, 'click'); | ||
jasmine.clock().tick(51); | ||
expect(spy).not.toHaveBeenCalled(); | ||
fireEvent(externalEl, 'mousedown'); | ||
fireEvent(document.body, 'mouseup'); | ||
fireEvent(document.body, 'click'); | ||
expect(spy).toHaveBeenCalled(); | ||
}); | ||
}); | ||
@@ -289,0 +323,0 @@ |
@@ -61,2 +61,6 @@ /*global fireEvent */ | ||
it('should create unique div ids for multiple textareas', function () { | ||
var origDateNow = Date.now; | ||
Date.now = function () { | ||
return 1464448478887; | ||
}; | ||
for (var i = 0; i < 12; i++) { | ||
@@ -70,5 +74,10 @@ var ta = this.createElement('textarea', 'editor'); | ||
}); | ||
Date.now = origDateNow; | ||
}); | ||
it('should create unique medium-editor-textarea-ids across all editor instances', function () { | ||
var origDateNow = Date.now; | ||
Date.now = function () { | ||
return 1464448478887; | ||
}; | ||
var tas = []; | ||
@@ -87,2 +96,3 @@ for (var i = 0; i < 12; i++) { | ||
}); | ||
Date.now = origDateNow; | ||
}); | ||
@@ -96,2 +106,17 @@ | ||
}); | ||
it('should reset the value of created div when form containing textarea is reset', function () { | ||
var form = this.createElement('form', null, null, true), | ||
initialContent = this.el.value; | ||
form.appendChild(this.el); | ||
document.body.appendChild(form); | ||
var editor = this.newMediumEditor('.editor'); | ||
expect(editor.elements[0].innerHTML).toEqual(initialContent); | ||
editor.setContent('<p>custom content</p>'); | ||
expect(editor.elements[0].innerHTML).not.toEqual(initialContent); | ||
form.reset(); | ||
expect(editor.elements[0].innerHTML).toEqual(initialContent); | ||
}); | ||
}); | ||
@@ -211,2 +236,24 @@ | ||
}); | ||
it('should reset the value of created div when form containing textarea is reset', function () { | ||
var form = this.createElement('form', null, null, true), | ||
initialContent = 'initial text', | ||
otherTextarea = this.createElement('textarea', 'editor', initialContent, true), | ||
editor = this.newMediumEditor('.editor'); | ||
otherTextarea.value = initialContent; | ||
expect(editor.elements.length).toBe(1); | ||
form.appendChild(otherTextarea); | ||
document.body.appendChild(form); | ||
editor.addElements(otherTextarea); | ||
var createdDiv = editor.elements[1]; | ||
expect(createdDiv.innerHTML).toEqual(initialContent); | ||
editor.setContent('<p>custom content</p>', 1); | ||
expect(createdDiv.innerHTML).not.toEqual(initialContent); | ||
form.reset(); | ||
expect(createdDiv.innerHTML).toEqual(initialContent); | ||
}); | ||
}); | ||
@@ -213,0 +260,0 @@ |
@@ -336,6 +336,6 @@ (function () { | ||
function createContentEditable(textarea, id, doc) { | ||
var div = doc.createElement('div'), | ||
function createContentEditable(textarea) { | ||
var div = this.options.ownerDocument.createElement('div'), | ||
now = Date.now(), | ||
uniqueId = 'medium-editor-' + now + '-' + id, | ||
uniqueId = 'medium-editor-' + now, | ||
atts = textarea.attributes; | ||
@@ -345,5 +345,5 @@ | ||
// to make a unique-id, ensure that the id is actually unique on the page | ||
while (doc.getElementById(uniqueId)) { | ||
while (this.options.ownerDocument.getElementById(uniqueId)) { | ||
now++; | ||
uniqueId = 'medium-editor-' + now + '-' + id; | ||
uniqueId = 'medium-editor-' + now; | ||
} | ||
@@ -365,2 +365,12 @@ | ||
// If textarea has a form, listen for reset on the form to clear | ||
// the content of the created div | ||
if (textarea.form) { | ||
this.on(textarea.form, 'reset', function (event) { | ||
if (!event.defaultPrevented) { | ||
this.resetContent(this.options.ownerDocument.getElementById(uniqueId)); | ||
} | ||
}.bind(this)); | ||
} | ||
textarea.classList.add('medium-editor-hidden'); | ||
@@ -375,6 +385,6 @@ textarea.parentNode.insertBefore( | ||
function initElement(element, id) { | ||
function initElement(element, editorId) { | ||
if (!element.getAttribute('data-medium-editor-element')) { | ||
if (element.nodeName.toLowerCase() === 'textarea') { | ||
element = createContentEditable(element, id, this.options.ownerDocument); | ||
element = createContentEditable.call(this, element); | ||
@@ -407,6 +417,13 @@ // Make sure we only attach to editableInput once for <textarea> elements | ||
var elementId = MediumEditor.util.guid(); | ||
element.setAttribute('data-medium-editor-element', true); | ||
element.setAttribute('role', 'textbox'); | ||
element.setAttribute('aria-multiline', true); | ||
element.setAttribute('medium-editor-index', MediumEditor.util.guid()); | ||
element.setAttribute('data-medium-editor-editor-index', editorId); | ||
// TODO: Merge data-medium-editor-element and medium-editor-index attributes for 6.0.0 | ||
// medium-editor-index is not named correctly anymore and can be re-purposed to signify | ||
// whether the element has been initialized or not | ||
element.setAttribute('medium-editor-index', elementId); | ||
initialContent[elementId] = element.innerHTML; | ||
@@ -630,2 +647,4 @@ this.events.attachAllEventsToElement(element); | ||
var initialContent = {}; | ||
MediumEditor.prototype = { | ||
@@ -649,2 +668,3 @@ // NOT DOCUMENTED - exposed for backwards compatability | ||
addToEditors.call(this, this.options.contentWindow); | ||
this.events = new MediumEditor.Events(this); | ||
@@ -660,3 +680,2 @@ this.elements = []; | ||
this.isActive = true; | ||
addToEditors.call(this, this.options.contentWindow); | ||
@@ -696,2 +715,3 @@ // Call initialization helpers | ||
element.removeAttribute('medium-editor-index'); | ||
element.removeAttribute('data-medium-editor-editor-index'); | ||
@@ -1155,2 +1175,11 @@ // Remove any elements created for textareas | ||
getContent: function (index) { | ||
index = index || 0; | ||
if (this.elements[index]) { | ||
return this.elements[index].innerHTML.trim(); | ||
} | ||
return null; | ||
}, | ||
checkContentChanged: function (editable) { | ||
@@ -1161,2 +1190,20 @@ editable = editable || MediumEditor.selection.getSelectionElement(this.options.contentWindow); | ||
resetContent: function (element) { | ||
// For all elements that exist in the this.elements array, we can assume: | ||
// - Its initial content has been set in the initialContent object | ||
// - It has a medium-editor-index attribute which is the key value in the initialContent object | ||
if (element) { | ||
var index = this.elements.indexOf(element); | ||
if (index !== -1) { | ||
this.setContent(initialContent[element.getAttribute('medium-editor-index')], index); | ||
} | ||
return; | ||
} | ||
this.elements.forEach(function (el, idx) { | ||
this.setContent(initialContent[el.getAttribute('medium-editor-index')], idx); | ||
}, this); | ||
}, | ||
addElements: function (selector) { | ||
@@ -1173,3 +1220,3 @@ // Convert elements into an array | ||
// Initialize all new elements (we check that in those functions don't worry) | ||
element = initElement.call(this, element); | ||
element = initElement.call(this, element, this.id); | ||
@@ -1206,2 +1253,11 @@ // Add new elements to our internal elements array | ||
}; | ||
MediumEditor.getEditorFromElement = function (element) { | ||
var index = element.getAttribute('data-medium-editor-editor-index'), | ||
win = element && element.ownerDocument && (element.ownerDocument.defaultView || element.ownerDocument.parentWindow); | ||
if (win && win._mediumEditors && win._mediumEditors[index]) { | ||
return win._mediumEditors[index]; | ||
} | ||
return null; | ||
}; | ||
}()); |
(function () { | ||
'use strict'; | ||
function isElementDescendantOfExtension(extensions, element) { | ||
return extensions.some(function (extension) { | ||
if (typeof extension.getInteractionElements !== 'function') { | ||
return false; | ||
} | ||
var extensionElements = extension.getInteractionElements(); | ||
if (!extensionElements) { | ||
return false; | ||
} | ||
if (!Array.isArray(extensionElements)) { | ||
extensionElements = [extensionElements]; | ||
} | ||
return extensionElements.some(function (el) { | ||
return MediumEditor.util.isDescendant(el, element, true); | ||
}); | ||
}); | ||
} | ||
var Events = function (instance) { | ||
@@ -380,11 +400,7 @@ this.base = instance; | ||
updateFocus: function (target, eventObj) { | ||
var toolbar = this.base.getExtensionByName('toolbar'), | ||
toolbarEl = toolbar ? toolbar.getToolbarElement() : null, | ||
anchorPreview = this.base.getExtensionByName('anchor-preview'), | ||
previewEl = (anchorPreview && anchorPreview.getPreviewElement) ? anchorPreview.getPreviewElement() : null, | ||
hadFocus = this.base.getFocusedElement(), | ||
var hadFocus = this.base.getFocusedElement(), | ||
toFocus; | ||
// For clicks, we need to know if the mousedown that caused the click happened inside the existing focused element. | ||
// If so, we don't want to focus another element | ||
// For clicks, we need to know if the mousedown that caused the click happened inside the existing focused element | ||
// or one of the extension elements. If so, we don't want to focus another element | ||
if (hadFocus && | ||
@@ -394,4 +410,3 @@ eventObj.type === 'click' && | ||
(MediumEditor.util.isDescendant(hadFocus, this.lastMousedownTarget, true) || | ||
MediumEditor.util.isDescendant(toolbarEl, this.lastMousedownTarget, true) || | ||
MediumEditor.util.isDescendant(previewEl, this.lastMousedownTarget, true))) { | ||
isElementDescendantOfExtension(this.base.extensions, this.lastMousedownTarget))) { | ||
toFocus = hadFocus; | ||
@@ -412,6 +427,5 @@ } | ||
// Check if the target is external (not part of the editor, toolbar, or anchorpreview) | ||
// Check if the target is external (not part of the editor, toolbar, or any other extension) | ||
var externalEvent = !MediumEditor.util.isDescendant(hadFocus, target, true) && | ||
!MediumEditor.util.isDescendant(toolbarEl, target, true) && | ||
!MediumEditor.util.isDescendant(previewEl, target, true); | ||
!isElementDescendantOfExtension(this.base.extensions, target); | ||
@@ -418,0 +432,0 @@ if (toFocus !== hadFocus) { |
@@ -179,2 +179,15 @@ (function () { | ||
/* getInteractionElements: [function ()] | ||
* | ||
* If the extension renders any elements that the user can interact with, | ||
* this method should be implemented and return the root element or an array | ||
* containing all of the root elements. MediumEditor will call this function | ||
* during interaction to see if the user clicked on something outside of the editor. | ||
* The elements are used to check if the target element of a click or | ||
* other user event is a descendant of any extension elements. | ||
* This way, the editor can also count user interaction within editor elements as | ||
* interactions with the editor, and thus not trigger 'blur' | ||
*/ | ||
getInteractionElements: undefined, | ||
/************************ Helpers ************************ | ||
@@ -181,0 +194,0 @@ * The following are helpers that are either set by MediumEditor |
@@ -37,2 +37,7 @@ (function () { | ||
getInteractionElements: function () { | ||
return this.getPreviewElement(); | ||
}, | ||
// TODO: Remove this function in 6.0.0 | ||
getPreviewElement: function () { | ||
@@ -159,3 +164,3 @@ return this.anchorPreview; | ||
var opts = { | ||
url: activeAnchor.attributes.href.value, | ||
value: activeAnchor.attributes.href.value, | ||
target: activeAnchor.getAttribute('target'), | ||
@@ -162,0 +167,0 @@ buttonClass: activeAnchor.getAttribute('class') |
@@ -24,2 +24,3 @@ # Extensions | ||
* [`queryCommandState()`](#querycommandstate) | ||
* [`getInteractionElements()`](#getinteractionelements) | ||
* [`isActive()`](#isactive) | ||
@@ -238,5 +239,12 @@ * [`isAlreadyApplied(node)`](#isalreadyappliednode) | ||
*** | ||
### `getInteractionElements()` | ||
If the extension renders any elements that the user can interact with, this method should be implemented and return the root element or an array containing all of the root elements. | ||
MediumEditor will call this function during interaction to see if the user clicked on something outside of the editor. The elements are used to check if the target element of a click or other user event is a descendant of any extension elements. This way, the editor can also count user interaction within editor elements as interactions with the editor, and thus not trigger 'blur' | ||
*** | ||
### `isActive()` | ||
If implemented, this method will be called from MediumEditor to determine whether the button has already been set as 'active'. | ||
If implemented, this method will be called from MediumEditor to determine whether the button has already been set as 'active'. | ||
@@ -257,3 +265,3 @@ If it returns `true`, this extension/button will be skipped for checking its active state as MediumEditor responds to the change in selection. | ||
* This method will NOT be called if `checkState()` has been implemented. | ||
* This method will NOT be called if `checkState()` has been implemented. | ||
* This method will NOT be called if `queryCommandState()` is implemented and returns a non-null value when called. | ||
@@ -279,3 +287,3 @@ | ||
If implemented, this method is called when MediumEditor knows that this extension has not been applied to the current selection. Curently, this is called at the beginning of each state change for the editor & toolbar. | ||
If implemented, this method is called when MediumEditor knows that this extension has not been applied to the current selection. Curently, this is called at the beginning of each state change for the editor & toolbar. | ||
@@ -547,3 +555,3 @@ After calling this, MediumEditor will attempt to update the extension, either via `checkState()` or the combination of `queryCommandState()`, `isAlreadyApplied(node)`, `isActive()`, and `setActive()` | ||
action: 'bold' | ||
// ... other properties/methods ... | ||
@@ -567,3 +575,3 @@ }) | ||
aria: 'bold text' | ||
// ... other properties/methods ... | ||
@@ -575,6 +583,6 @@ }) | ||
### `tagNames` _(Array)_ | ||
Array of element tag names that would indicate that this button has already been applied. If this action has already been applied, the button will be displayed as 'active' in the toolbar. | ||
**NOTE:** | ||
**NOTE:** | ||
@@ -586,3 +594,3 @@ `tagNames` is not used if `useQueryState` is set to `true`. | ||
The following would create a custom button extension which would be 'active' in the toolbar if the selection is within a `<b>` or a `<strong>` tag: | ||
```js | ||
@@ -595,3 +603,3 @@ var CustomButtonExtension = MediumEditor.extensions.button.extend({ | ||
tagNames: ['b', 'strong'], | ||
// ... other properties/methods ... | ||
@@ -611,3 +619,3 @@ }) | ||
**NOTE:** | ||
**NOTE:** | ||
@@ -630,3 +638,3 @@ `style` is not used if `useQueryState` is set to `true`. | ||
}, | ||
// ... other properties/methods ... | ||
@@ -633,0 +641,0 @@ }) |
@@ -192,2 +192,6 @@ (function () { | ||
getInteractionElements: function () { | ||
return this.getToolbarElement(); | ||
}, | ||
getToolbarElement: function () { | ||
@@ -194,0 +198,0 @@ if (!this.toolbar) { |
@@ -18,3 +18,3 @@ MediumEditor.parseVersionString = function (release) { | ||
// grunt-bump looks for this: | ||
'version': '5.19.1' | ||
'version': '5.20.0' | ||
}).version); |
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
1895962
24530
711
23