Showing preview only (1,536K chars total). Download the full file or copy to clipboard to get everything.
Repository: yabwe/medium-editor
Branch: master
Commit: d113a74437fd
Files: 127
Total size: 1.4 MB
Directory structure:
gitextract_iannmrpu/
├── .editorconfig
├── .github/
│ ├── ISSUE_TEMPLATE.md
│ └── PULL_REQUEST_TEMPLATE.md
├── .gitignore
├── .jscsrc
├── .jshintrc
├── .npmrc
├── .travis.yml
├── API.md
├── CHANGES.md
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── CUSTOM-EVENTS.md
├── Gruntfile.js
├── LICENSE
├── MAINTAINERS.md
├── OPTIONS.md
├── README.md
├── UPGRADE-5.md
├── bower.json
├── demo/
│ ├── absolute-container.html
│ ├── auto-link.html
│ ├── button-example.html
│ ├── clean-paste.html
│ ├── css/
│ │ └── demo.css
│ ├── custom-toolbar.html
│ ├── extension-example.html
│ ├── index.html
│ ├── js/
│ │ └── extension-table.js
│ ├── multi-editor.html
│ ├── multi-one-instance.html
│ ├── multi-paragraph.html
│ ├── nested-editable.html
│ ├── pass-instance.html
│ ├── relative-toolbar.html
│ ├── static-toolbar.html
│ ├── table-extension.html
│ └── textarea.html
├── dist/
│ ├── css/
│ │ ├── medium-editor.css
│ │ └── themes/
│ │ ├── beagle.css
│ │ ├── bootstrap.css
│ │ ├── default.css
│ │ ├── flat.css
│ │ ├── mani.css
│ │ ├── roman.css
│ │ └── tim.css
│ └── js/
│ └── medium-editor.js
├── index.js
├── karma.conf.js
├── karma.dev.conf.js
├── package.json
├── spec/
│ ├── anchor-preview.spec.js
│ ├── anchor.spec.js
│ ├── auto-link.spec.js
│ ├── buttons.spec.js
│ ├── content.spec.js
│ ├── core-api.spec.js
│ ├── delay.spec.js
│ ├── drag-and-drop.spec.js
│ ├── dyn-elements.spec.js
│ ├── elements.spec.js
│ ├── events.spec.js
│ ├── exploits.spec.js
│ ├── extension.spec.js
│ ├── fontname.spec.js
│ ├── fontsize.spec.js
│ ├── full-content.spec.js
│ ├── header-tags.spec.js
│ ├── helpers/
│ │ └── util.js
│ ├── init.spec.js
│ ├── keyboard-commands.spec.js
│ ├── paste.spec.js
│ ├── placeholder.spec.js
│ ├── selection.spec.js
│ ├── serialize.spec.js
│ ├── setup.spec.js
│ ├── textarea.spec.js
│ ├── toolbar.spec.js
│ ├── util.spec.js
│ ├── vendor/
│ │ ├── jasmine-jsreporter-script.js
│ │ └── jasmine-jsreporter.js
│ └── version.spec.js
└── src/
├── js/
│ ├── core.js
│ ├── defaults/
│ │ ├── buttons.js
│ │ └── options.js
│ ├── events.js
│ ├── extension.js
│ ├── extensions/
│ │ ├── README.md
│ │ ├── WALKTHROUGH-BUTTON.md
│ │ ├── WALKTHROUGH-EXTENSION.md
│ │ ├── anchor-preview.js
│ │ ├── anchor.js
│ │ ├── auto-link.js
│ │ ├── button.js
│ │ ├── deprecated/
│ │ │ └── image-dragging.js
│ │ ├── file-dragging.js
│ │ ├── fontname.js
│ │ ├── fontsize.js
│ │ ├── form.js
│ │ ├── keyboard-commands.js
│ │ ├── paste.js
│ │ ├── placeholder.js
│ │ └── toolbar.js
│ ├── globals.js
│ ├── polyfills.js
│ ├── selection.js
│ ├── util.js
│ └── version.js
├── sass/
│ ├── _settings.scss
│ ├── animations/
│ │ ├── _image-loading.scss
│ │ └── _pop-upwards.scss
│ ├── components/
│ │ ├── _anchor-preview.scss
│ │ ├── _file-dragging.scss
│ │ ├── _placeholder.scss
│ │ ├── _toolbar-form.scss
│ │ └── _toolbar.scss
│ ├── medium-editor.scss
│ ├── themes/
│ │ ├── beagle.scss
│ │ ├── bootstrap.scss
│ │ ├── default.scss
│ │ ├── flat.scss
│ │ ├── mani.scss
│ │ ├── roman.scss
│ │ └── tim.scss
│ └── util/
│ └── _clearfix.scss
└── wrappers/
├── end.js
└── start.js
================================================
FILE CONTENTS
================================================
================================================
FILE: .editorconfig
================================================
# EditorConfig is awesome: http://EditorConfig.org
root = true
[*]
end_of_line = lf
insert_final_newline = false
indent_style = space
indent_size = 4
[*.json]
indent_size = 2
[.*rc]
indent_size = 2
================================================
FILE: .github/ISSUE_TEMPLATE.md
================================================
### Description
[Description of the bug or feature]
### Steps to reproduce
1. [First step]
2. [Second step]
3. [and so on...]
**Expected behavior:** [What you expected to happen]
**Actual behavior:** [What actually happened]
**Link to an example:** [If you're reporting a bug that's not reproducible on our [demo page](https://yabwe.github.io/medium-editor/demo.html), please try to reproduce it on [JSFiddle](https://jsfiddle.net/), [JS Bin](https://jsbin.com), [CodePen](http://codepen.io/) or a similar service and paste a link here]
### Versions
- medium-editor:
- browser:
- OS:
================================================
FILE: .github/PULL_REQUEST_TEMPLATE.md
================================================
| Q | A
| ---------------- | ---
| Bug fix? | yes/no
| New feature? | yes/no
| BC breaks? | yes/no
| Deprecations? | yes/no
| New tests added? | yes/not needed
| Fixed tickets | comma-separated list of tickets fixed by the PR, if any
| License | MIT
### Description
[Description of the bug or feature]
--
#### Please, don't submit `/dist` files with your PR!
================================================
FILE: .gitignore
================================================
*~
*.swp
.DS_Store
*.swo
node_modules/
.env
.sass-cache/
npm-debug.log
.grunt/
_SpecRunner.html
reports/
coverage/
._*
local.log
browserstack.err
# IDE
.idea/
package-lock.json
================================================
FILE: .jscsrc
================================================
{
"disallowEmptyBlocks": true,
"disallowKeywordsOnNewLine": [
"else"
],
"disallowMixedSpacesAndTabs": true,
"disallowMultipleLineBreaks": true,
"disallowMultipleLineStrings": true,
"disallowMultipleSpaces": true,
"disallowNewlineBeforeBlockStatements": true,
"disallowSpaceAfterPrefixUnaryOperators": [
"++",
"--",
"+",
"-",
"~",
"!"
],
"disallowSpaceAfterObjectKeys": true,
"disallowSpaceBeforePostfixUnaryOperators": [
"++",
"--"
],
"disallowSpacesInCallExpression": true,
"disallowSpacesInFunctionDeclaration": {
"beforeOpeningRoundBrace": true
},
"disallowSpacesInsideArrayBrackets": true,
"disallowSpacesInsideBrackets": true,
"disallowSpacesInsideParentheses": true,
"disallowTrailingComma": true,
"disallowTrailingWhitespace": true,
"requireBlocksOnNewline": true,
"requireCamelCaseOrUpperCaseIdentifiers": true,
"requireCapitalizedConstructors": true,
"requireCommaBeforeLineBreak": true,
"requireCurlyBraces": [
"if",
"else",
"for",
"while",
"do",
"try",
"catch"
],
"requireLineBreakAfterVariableAssignment": true,
"requireMultipleVarDecl": true,
"requireOperatorBeforeLineBreak": [
"?",
"=",
"+",
"-",
"/",
"*",
"==",
"===",
"!=",
"!==",
">",
">=",
"<",
"<="
],
"requireSemicolons": true,
"requireSpaceAfterBinaryOperators": [
"=",
",",
"+",
"-",
"/",
"*",
"==",
"===",
"!=",
"!=="
],
"requireSpaceAfterKeywords": [
"do",
"for",
"if",
"else",
"switch",
"case",
"try",
"catch",
"void",
"while",
"with",
"return",
"typeof",
"function"
],
"requireSpaceBeforeBinaryOperators": [
"=",
"+",
"-",
"/",
"*",
"==",
"===",
"!=",
"!=="
],
"requireSpaceBeforeBlockStatements": true,
"requireSpaceBeforeKeywords": [
"else",
"while",
"catch"
],
"requireSpaceBetweenArguments": true,
"requireSpacesInAnonymousFunctionExpression": {
"beforeOpeningRoundBrace": true,
"beforeOpeningCurlyBrace": true
},
"requireSpacesInConditionalExpression": {
"afterTest": true,
"beforeConsequent": true,
"afterConsequent": true,
"beforeAlternate": true
},
"requireSpacesInForStatement": true,
"requireSpacesInFunctionDeclaration": {
"beforeOpeningCurlyBrace": true
},
"requireSpacesInFunction": {
"beforeOpeningCurlyBrace": true
},
"requireSpacesInsideObjectBrackets": {
"allExcept": [ "}", ")" ]
},
"validateIndentation": 4,
"validateParameterSeparator": ", ",
"validateQuoteMarks": "'"
}
================================================
FILE: .jshintrc
================================================
{
"boss": true,
"browser": true,
"curly": true,
"eqeqeq": true,
"eqnull": true,
"immed": true,
"latedef": "nofunc",
"newcap": false,
"noarg": true,
"predef": [ "MediumEditor",
"afterAll", "afterEach", "beforeAll", "beforeEach", "describe", "expect", "it", "jasmine", "spyOn",
"setupTestHelpers" ],
"sub": true,
"undef": true,
"unused": true,
"validthis": true
}
================================================
FILE: .npmrc
================================================
save-exact=true
================================================
FILE: .travis.yml
================================================
# faster builds on new travis setup not using sudo
sudo: false
# cache vendor dirs
cache:
directories:
- node_modules
language: node_js
node_js:
- "12"
notifications:
email: false
webhooks:
urls:
- https://webhooks.gitter.im/e/0913a4ced1f3322b4c40
on_success: change # options: [always|never|change] default: always
on_failure: always # options: [always|never|change] default: always
on_start: false # default: false
before_script:
- npm install -g grunt-cli
script:
- npm run test:ci
================================================
FILE: API.md
================================================
# MediumEditor Object API (v5.0.0)
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
- [Initialization Functions](#initialization-functions)
- [`MediumEditor(elements, options)`](#mediumeditorelements-options)
- [`destroy()`](#destroy)
- [`setup()`](#setup)
- [`addElements()`](#addelementselements)
- [`removeElements()`](#removeelementselements)
- [Event Functions](#event-functions)
- [`on(targets, event, listener, useCapture)`](#ontargets-event-listener-usecapture)
- [`off(targets, event, listener, useCapture)`](#offtargets-event-listener-usecapture)
- [`subscribe(name, listener)`](#subscribename-listener)
- [`unsubscribe(name, listener)`](#unsubscribename-listener)
- [`trigger(name, data, editable)`](#triggername-data-editable)
- [Selection Functions](#selection-functions)
- [`checkSelection()`](#checkselection)
- [`exportSelection()`](#exportselection)
- [`importSelection(selectionState, favorLaterSelectionAnchor)`](#importselectionselectionstate-favorlaterselectionanchor)
- [`getFocusedElement()`](#getfocusedelement)
- [`getSelectedParentElement(range)`](#getselectedparentelementrange)
- [`restoreSelection()`](#restoreselection)
- [`saveSelection()`](#saveselection)
- [`selectAllContents()`](#selectallcontents)
- [`selectElement(element)`](#selectelementelement)
- [`stopSelectionUpdates()`](#stopselectionupdates)
- [`startSelectionUpdates()`](#startselectionupdates)
- [Editor Action Functions](#editor-action-functions)
- [`cleanPaste(text)`](#cleanpastetext)
- [`createLink(opts)`](#createlinkopts)
- [`execAction(action, opts)`](#execactionaction-opts)
- [`pasteHTML(html, options)`](#pastehtmlhtml-options)
- [`queryCommandState(action)`](#querycommandstateaction)
- [Helper Functions](#helper-functions)
- [`checkContentChanged(editable)`](#checkContentChangededitable)
- [`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)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
## Initialization Functions
### `MediumEditor(elements, options)`
Creating an instance of MediumEditor will:
* Convert all passed in elements into `contenteditable` elements.
* For any `<textarea>` elements:
* Hide the `<textarea>`
* Create a new `<div contenteditable=true>` element and add it to the elements array.
* Ensure the 2 elements remain sync'd.
* Initialize any custom extensions or buttons passed in.
* Create any additional elements needed.
* Setup all event handling needed to monitor the editable elements.
**Arguments**
_**elements** (`String` | `HTMLElement` | `Array` | `NodeList` | `HTMLCollection`)_:
1. `String`: If passed as a string, this is used as a selector in a call to `document.querySelectorAll()` to find elements on the page. All results are stored in the internal list of **elements**.
2. `HTMLElement`: If passed as a single element, this will be the only element in the internal list of **elements**.
3. `Array`: If passed as an `Array` of `HTMLElement`s, this will be used as the internal list of **elements**.
_**options** (`Object`)_:
Set of [custom options](OPTIONS.md) used to initialize `MediumEditor`.
***
### `destroy()`
Tear down the editor if already setup by doing the following:
* Calling the `destroy()` method on each extension within the editor. This should allow all extension to be torn down and cleaned up, including the toolbar and its elements.
* Detaching all event listeners from the DOM
* Detaching all references to custom event listeners
* Remove any custom attributes from the editor **elements**
* Unhide any `<textarea>` elements and remove any created `<div>` elements created for `<textarea>` elements.
***
### `setup()`
Initialize this instance of the editor if it has been destroyed. This will reuse the `elements` selector and `options` object passed in when the editor was instantiated.
***
### `addElements(elements)`
Dynamically add one or more elements to an already initialized instance of MediumEditor.
Passing an elements or array of elements to `addElements(elements)` will:
* Add the given element or array of elements to the editor **elements**
* Ensure the element(s) are initialized with the proper attributes and event handlers as if the element had been passed during instantiation of the editor
* For any `<textarea>` elements:
* Hide the `<textarea>`
* Create a new `<div contenteditable=true>` element and add it to the editor **elements**
* Ensure the 2 elements remain sync'd.
* Be intelligent enough to run the necessary code only once per element, no matter how often you will call it
So, every element you pass to `addElements` will turn into a fully supported contenteditable too - even earlier calls to `editor.subscribe(..)`
for custom events will work on the newly added element(s).
**Arguments**
_**elements** (`String` | `HTMLElement` | `Array` | `NodeList` | `HTMLCollection`)_:
1. `String`: If passed as a string, this is used as a selector in a call to `document.querySelectorAll()` to find elements on the page.
2. `HTMLElement`: If passed as a single element, this will be the only element added to the editor **elements**.
3. `Array` | `NodeList` | `HTMLCollection`: If passed as an `Array`-like collection of `HTMLElement`s, all of these elements will be added to the editor **elements**.
***
### `removeElements(elements)`
Remove one or more elements from an already initialized instance of MediumEditor.
Passing an elements or array of elements to `removeElements(elements)` will:
* Remove the given element or array of elements from the internal `this.elements` array.
* Remove any added event handlers or attributes (with the exception of `contenteditable`).
* Unhide any `<textarea>` elements and remove any created `<div>` elements created for `<textarea>` elements.
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.
**Arguments**
_**elements** (`String` | `HTMLElement` | `Array` | `NodeList` | `HTMLCollection`)_:
1. `String`: If passed as a string, this is used as a selector in a call to `document.querySelectorAll()` to find elements on the page.
2. `HTMLElement`: If passed as a single element, this will be the only element removed from the editor **elements**.
3. `Array` | `NodeList` | `HTMLCollection`: If passed as an `Array`-like collection of `HTMLElement`s, all of these elements will be removed from the editor **elements**.
***
## Event Functions
### `on(targets, event, listener, useCapture)`
Attaches an event listener to a specific element or elements via the browser's built-in `addEventListener(type, listener, useCapture)` API. However, this helper method also ensures that when MediumEditor is destroyed, this event listener will be automatically be detached from the DOM.
**Arguments**
1. _**targets** (`HTMLElement` / `NodeList`)_:
* Element or elements to attach listener to via `addEventListener(type, listener, useCapture)`
2. _**event** (`String`)_:
* type argument for `addEventListener(type, listener, useCapture)`
3. _**listener** (`function`)_:
* listener argument for `addEventListener(type, listener, useCapture)`
4. _**useCapture** (`boolean`)_:
* useCapture argument for `addEventListener(type, listener, useCapture)`
***
### `off(targets, event, listener, useCapture)`
Detach an event listener from a specific element or elements via the browser's built-in `removeEventListener(type, listener, useCapture)` API.
**Arguments**
1. _**targets** (`HTMLElement` / `NodeList`)_:
* Element or elements to detach listener from via `removeEventListener(type, listener, useCapture)`
2. _**event** (`String`)_:
* type argument for `removeEventListener(type, listener, useCapture)`
3. _**listener** (`function`)_:
* listener argument for `removeEventListener(type, listener, useCapture)`
4. _**useCapture** (`boolean`)_:
* useCapture argument for `removeEventListener(type, listener, useCapture)`
***
### `subscribe(name, listener)`
Attaches a listener for the specified custom event name.
**Arguments**
1. _**name** (`String`)_:
* Name of the event to listen to. See the list of built-in [Custom Events](CUSTOM-EVENTS.md).
2. _**listener(data, editable)** (`function`)_:
* Listener method that will be called whenever the custom event is triggered.
**Arguments to listener**
1. _**data** (`Event` | `object`)_
* For most custom events, this will be the browser's native `Event` object for the event that triggered the custom event to fire.
* For some custom events, this will be an object containing information describing the event (depending on which custom event it is)
2. _**editable** (`HTMLElement`)_
* A reference to the contenteditable container element that this custom event corresponds to. This is especially useful for instances where one instance of MediumEditor contains multiple elements, or there are multiple instances of MediumEditor on the page.
* For example, when `blur` fires, this argument will be the `<div contenteditable=true></div>` element that is about to receive focus.
***
### `unsubscribe(name, listener)`
Detaches a custom event listener for the specified custom event name.
**Arguments**
1. _**name** (`String`)_:
* Name of the event to detach the listener for.
2. _**listener** (`function`)_:
* A reference to the listener to detach. This must be a match by-reference and not a copy.
**NOTE**
* Calling [destroy()](#destroy) on the MediumEditor object will automatically remove all custom event listeners.
***
### `trigger(name, data, editable)`
Manually triggers a custom event.
**Arguments**
1. _**name** (`String`)_:
* Name of the custom event to trigger.
2. _**data** (`Event` | `object`)_:
* Native `Event` object or custom data object to pass to all the listeners to this custom event.
3. _**editable** (`HTMLElement`)_:
* The `<div contenteditable=true></div>` element to pass to all of the listeners to this custom event.
***
## Selection Functions
### `checkSelection()`
If the toolbar is enabled, manually forces the toolbar to update based on the user's current selection. This includes hiding/showing the toolbar, positioning the toolbar, and updating the enabled/disable state of the toolbar buttons.
***
### `exportSelection()`
Returns a data representation of the selected text, which can be applied via `importSelection(selectionState)`. This data will include the beginning and end of the selection, as well as which of the editor **elements** the selection was within.
***
### `importSelection(selectionState, favorLaterSelectionAnchor)`
Restores the selection using a data representation of previously selected text (ie value returned by `exportSelection()`).
**Arguments**
1. _**selectionState** (`Object`)_:
* Data representing the state of the selection to restore.
2. _**favorLaterSelectionAnchor** (`boolean`)_:
* If `true`, import the cursor immediately subsequent to an anchor tag if it would otherwise be placed right at the trailing edge inside the anchor. THis cursor positioning, even though visually equivalent to the user, can affect behavior in Internet Explorer.
***
### `getFocusedElement()`
Returns a reference to the editor **element** that currently has focus (if the editor has focus).
***
### `getSelectedParentElement(range)`
Returns a reference to the editor **element** that the user's selection is currently within.
**Arguments**
1. _**range** (`Range`)_: _**OPTIONAL**_
* The `Range` to find the selection parent element within
* If no element is provided, the editor will use the current range within the selection of the editor's `contentWindow`
***
### `restoreSelection()`
Restores the selection to what was selected the last time `saveSelection()` was called.
***
### `saveSelection()`
Internally stores the user's current selection. This can be restored by calling `restoreSelection()`.
***
### `selectAllContents()`
Expands the selection to contain all text within the focused editor **element**.
***
### `selectElement(element)`
Change the user's selection to select the contents of the provided element and update the toolbar to reflect this change.
**Arguments**
1. _**element** (`HTMLElement`)_:
* DOM Element -- which is a descendant of one of the editor's **elements** -- to select.
***
### `stopSelectionUpdates()`
Stop the toolbar from updating to reflect changes in the user's selection.
***
### `startSelectionUpdates()`
Enable the toolbar to start updating based on the user's selection, after a call to `stopSelectionUpdates()`
***
## Editor Action Functions
### `cleanPaste(text)`
_convert text to plaintext and replace current selection with result_
**Arguments**
1. _**text** (`String`)_:
* Content to be pasted at the location of the current selection/cursor
***
### `createLink(opts)`
_creates a link via the native `document.execCommand('createLink')` command_
**Arguments**
1. _**opts** (`Object`)_:
* Object containing additional properties needed for creating a link
**Properties of 'opts'**
1. _**value** (`String`)_ _**REQUIRED**_
* The url to set as the `href` of the created link. A non-empty value must be provided for the link to be created.
2. _**target** (`String`)_
* Attribute to set as the `target` attribute of the created link. Passing 'self' or not passing this option at all are equivalent in that they will just ensure that `target="_blank"` will NOT be present on the created link.
* **NOTE** If the `targetBlank` option on the editor is set to true, the `target` property of opts will be ignored and `target="_blank"` will be added to all created links.
3. _**buttonClass** (`String`)_
* Class (or classes) to append to the `class` attribute of the created link.
##### Example
```js
editor.createLink({ value: 'https://github.com/yabwe/medium-editor', target: '_blank', buttonClass: 'medium-link' });
```
***
### `execAction(action, opts)`
_executes an built-in action via document.execCommand_
**Arguments**
1. _**action** (`String`)_:
* Action to be passed as the 'command' argument to `document.execCommand(command, showDefaultUI, value)`
2. _**opts** (`Object`)_ _**OPTIONAL**_:
* Object containing additional properties for specific commands
**Properties of 'opts'**
1. _**value** (`String`)_
* The value to pass as the 'value' argument to `document.execCommand(command, showDefaultUI, value)`
2. For 'createLink', the `opts` are passed directly to [`.createLink(opts)`]((#createlinkopts)) so see that method for additional options for that command
***
### `pasteHTML(html, options)`
_replace the current selection with html_
**Arguments**
1. _**html** (`String`)_:
* Content to be pasted at the location of the current selection/cursor
2. _**options** (`Object`)_ _**OPTIONAL**_:
* Optional overrides for `cleanTags`, `unwrapTags`, and/or `cleanAttrs` for removing/unwrapping specific element types (`cleanTags`/`unwrapTags`), or removing specific attributes (`cleanAttrs`) from the inserted HTML. See [cleanTags](OPTIONS.md#cleantags), [unwrapTags](OPTIONS.md#unwraptags), and [cleanAttrs](OPTIONS.md#cleanattrs) in OPTIONS.md for more information.
##### Example
```js
editor.pasteHTML('<p class="classy"><strong>Some Custom HTML</strong></p>', { cleanAttrs: ['class'], cleanTags: ['strong'], unwrapTags: ['em']});
```
***
### `queryCommandState(action)`
_wrapper around the browser's built in `document.queryCommandState(command)` for checking whether a specific action has already been applied to the selection._
**Arguments**
1. _**action** (`String`)_:
* Action to be passed as the 'command' argument to `document.queryCommandState(command)`
***
## Helper Functions
### `checkContentChanged(editable)`
Trigger the editor to check for updates to the html, and trigger the `editableInput` event if needed.
**Arguments**
1. _**editable** (`HTMLElement`)_: _**OPTIONAL**_
* The `<div contenteditable=true></div>` element that contains the html that may have changed.
* If no element is provided, the editor will check the currently 'active' editor element (the element with focus).
### `delay(fn)`
Delay any function from being executed by the amount of time passed as the **delay** option.
**Arguments**
1. _**fn** (`function`)_:
* Function to delay execution for.
***
### `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)`
Get a reference to an extension with the specified name.
**Arguments**
1. _**name** (`String`)_:
* The name of the extension to retrieve (ie `toolbar`).
***
### `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()`
Returns a JSON object including the content of each of the **elements** inside the editor.
***
### `setContent(html, index)`
Sets the html content for the first editor **element**, or the **element** at `index`. Ensures the the `editableInput` event is triggered.
**Arguments**
1. _**html** (`string`)_:
* The content to set the element to
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"`)
================================================
FILE: CHANGES.md
================================================
5.23.3 / 2017-12-20
==================
* Fix medium-editor-insert plugin css fixes on beagle theme #1361
* Update jsDelivr links #1366 & #1367
* Fix Firefox console warning causing issues #1370
* Do not check only for null targets or it will fail when it's undefined. #1373
* Fix crash when 'extensions' in 'isElementDescendantOfExtension' is undefined #1362
* Fix Jasmine Unit Test errors #1385
* Fix null error on pastedPlain.split #1397
* Fix broken browser tests #1396
5.23.2 / 2017-08-02
==================
* Add noopener & noreferrer into targetBlank #1355
* Add undefined check and fallback in Paste extension #1346
5.23.1 / 2017-06-27
==================
* Remove src from bower ignored files #1330
* Add label-checkbox relation in CreateLink form #1275 #1340
5.23.0 / 2017-03-02
==================
* Only add schemes to URLs with hostnames #1258
* Fix problem with addClassToAnchors #1293
* Adding new 'html' button from #1235 #1291
* Don't encode fragment as part of linkValidation #1257
5.22.2 / 2017-01-19
==================
* Efficiency: Compile RegEx once #1230
* Error in console at link selection #1249
* Check for this.anchorPreview when hiding #1280
* Save some CPU calculations #1271
5.22.1 / 2016-09-29
==================
* Fix encoded urls (in linkValidaton) #1219
* Fix CommonJS environment #1221
5.22.0 / 2016-09-03
==================
* Add new extensions to extensions README #1188
* Fix iframe div #1179
* Fix placeholder text color in flat theme #1192
* Add unwrapTags option to paste extension #1177
* Remove first empty paragraph on backspace #1187
* Update grunt-contrib-jasmine #1185
* Added Embed Button links to README #1183
5.21.1 / 2016-08-11
==================
* Make linkValidation allow hash links #1143
* Fix toolbar in absolute container #1152
* Fix caret issue in blockquote #1162
* Handle new Google Docs font weights #1168
* Add external button example #1175
5.21.0 / 2016-06-21
==================
* Fix issue with electron environment #1125
* Fix for paste and placeholder extensions & add/remove element events #1124
* Placeholder is visible when only empty table is in Editor #1128
5.20.2 / 2016-06-17
==================
(5.20.1 was skipped because of a bad release)
* Fix test failure in Chrome 51 #1114
* Fix slow CSS selector #1115
* Improve documentation for toolbar.relativeContainer option #1122
* Fix cursor rendering incorrectly in Firefox #1113
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
==================
* Add feature for toggling anchor preview for empty or # links
* Fix keyboard paste to properly fire editablePaste
* Standardize editablePaste to always fire with mock event object
5.18.0 / 2016-05-21
==================
* Add support calling document.execCommand with arbitrary argument from execAction
* Also deprecate custom execAction option names in favor of standard .value
* Fix error from addElements when initializing editor with no elements
5.17.0 / 2016-05-17
==================
* Improved paste handling
* Includes proper support for keyboard paste in firefox
* More cleanup when pasting from Word
* Introduce support for adding and removing elements from an existing editor instances
* New addElements and removeElements methods
* Add checkContentChanged method for manually triggering editableInput
* Add selection.selectRange helper method
5.16.1 / 2016-04-14
==================
* Fix incorrect word breaking
5.16.0 / 2016-04-12
==================
* Add support for multiple targets for attaching/detach event handlers
* Add support for chaining calls to attach/detach events
* Fix issue with click anchor-preview when using showWhenToolbarIsVisible
* Fix IE issue with line-breaking within editor
* Fix Firefox error when using elements other than divs as editor
5.15.1 / 2016-04-05
==================
* Fix link validation in anchor extension
* Improve performance when dealing with a lot of data
* Enable functions to be used as keyboard commands
5.15.0 / 2016-03-23
==================
* Use class instead of inline style for hiding/showing anchor form
* Add helpers for hiding/showing form into form extension base class
* Fix issue where auto-link extension re-enabled IE's built-in auto-link when other instances still existed
* Fix anchor form to display form before attempting to position correctly
* Add new selection.clearSelection() helper method for moving cursor to beginning/end of selection
5.14.4 / 2016-02-25
==================
* editableInput event fixes
* Fix issue with event not triggering when dragging in images
* Fix issue with event not triggering on autolink
* Fix issue with event not triggering on insertHTML in Edge
* Fix issue with hitting enter when directly inside figcaption and other block elements
5.14.3 / 2016-02-22
==================
* Fix behaviour of "Open in new window" checkbox for Firefox
* Added instruction to disable file dragging all together
* Fix issue with image dragging and dropping at end of target
* Fix issue with extra space when space already exists
5.14.2 / 2016-02-10
==================
* Support Microsoft Edge
* Fallback to custom insertHTML command instead of built-in command for Edge
* Use shim code for detecting input on contenteditable for Edge
* Fix issue with converting blockquotes to paragraphs in Edge
* Update documentation, fix tests, and include Edge in browser testing
5.14.1 / 2016-02-05
==================
* Fix issue with saving selection after newline and whitespace text nodes
* Fix import/export selection to prefer start of nodes over end of nodes
* Fix for getClosestBlockContainer utility function
* Fix for getTopBlockContainer utility function
* Deprecate getFirstTextNode utility function
5.14.0 / 2016-01-31
==================
* Added pre clean replacements
* Fixed an infinite loop
* Handled enter event for empty h2/h3 tag
5.13.0 / 2016-01-18
==================
* Added stickyTopOffset option to keep on the screen a sticky toolbar
* Fixed misplacement of buttons when selection is near to the right edge
* Updated dev dependencies
* Added reference to README for who is using medium-editor
5.12.0 / 2015-12-15
==================
* Fix issue with image-only selections
* Trim src when using the image toolbar button
* Fix auto linking with comments
* Documented the process of releasing a new version
5.11.0 / 2015-12-05
==================
* Updated table extension demo
* Removed the carriage return character from a demo file
* Updated checkLinkFormat function to support more schemes
* Fixed issue with disableExtraSpaces option to allow space at the end of line
* Use editableInput instead of input event for textarea syncing
* Fixed style for correct positioning of placeholder
* Allowed to remove blockquote by pressing delete at beginning of the quote
* Fixed failing test cases in IE9 and IE10
5.10.0 / 2015-10-30
==================
* Added disableExtraSpaces option for preventing errant spaces
* Added editalbeKeydownSpace event
* Fix issue with return key at the end of text with bad formatting
* Added new font name extension (beta)
* Documentation updates and cleanup
5.9.0 / 2015-10-19
==================
* Add showWhenToolbarIsVisible option for displaying anchor-preview when toolbar is visible
* Remove trailing whitespace when creating links via anchor extension
* Fix issue with escaping list items via pressing enter
* Fix font-awesome links in demo pages
* Updates to documentation around creating links
5.8.3 / 2015-10-08
==================
* Fix changing link on images
5.8.2 / 2015-09-21
==================
* Fix type of elements which can contain auto-links
5.8.1 / 2015-09-16
==================
* Fix inconsistancies and errors in isDescendant utility method
5.8.0 / 2015-09-13
==================
* Added relativeContainer options for the toolbar
* Fix issue with auto-linking across consecutive list-items
* Added beagle theme
5.7.0 / 2015-08-21
==================
* Fix backwards compatability issue with how keyboard commands extension handles 'alt'
* Rewrite which event placeholder extension listens to for hiding/showing placeholder
* Fix issue where placeholder is not hidden when calling setContent()
* Fix issue where placeholder is displayed incorrectly when hideOnClick option is true
5.6.3 / 2015-08-18
==================
* Ensure textarea ids are unique on entire page
* Fix broken auto-link within block elements other than paragraphs
* Fix issue with editor element being removed in IE11
* Remove references to global variables from internal code
5.6.2 / 2015-08-11
==================
* Fix a regression in the paste extension related to `pasteHTML` function
5.6.1 / 2015-08-10
==================
* Fix issue with creating anchors and restoring selection at the beginning of paragraphs
* Fix issue with creating anchors and restoring selection within list items and nested blocks
* Ensure CTRL + M is respected as a way to insert new lines
5.6.0 / 2015-08-07
==================
* Add new 'tim' theme for medium-editor toolbar
* Fix issue Chrome generated comment tags when pasting
* Fix issue where 'editableInput' is triggered multiple times when creating links
5.5.4 / 2015-08-04
==================
* Fix issue with anchor and selection inconsitencies in IE
5.5.3 / 2015-08-03
==================
* Fix issue with replacing a pre-existing link
* Fix issue with selection after creating a link after empty paragraphs
5.5.2 / 2015-08-02
==================
* Fix issue where block elements where cleaned up incorrectly when pasting
* Fix anchor form checkboxes to reflect status of selected link
* Fix issue with creating links in same paragraph as another link
* Fix issue with creating links after empty paragraphs
* Ensure all attributes are copied from textareas to divs
5.5.1 / 2015-07-23
==================
* Fix appearance of anchor form when checkboxes are present
* Fix breaking issue with standardizeSelectionStart option
5.5.0 / 2015-07-21
==================
* Add setContent method into core API, which triggers editableInput
5.4.1 / 2015-07-20
==================
* Fix issue where custom anchor-preview extensions weren't overriding built-in anchor preview
* Add documentation from wiki into the source code
5.4.0 / 2015-07-16
==================
* Add support for including 'alt' key in keyboard-commands
5.3.0 / 2015-07-07
==================
* Fix issue with disabling image drag & drop via imageDragging option
* Deprecate image-dragging extension
* Introduce file-dragging extension
* Ensure autolink urls respect targetBlank option
* Expose importSelection and exportSelection as generic Selection helpers
5.2.0 / 2015-06-29
==================
* Move allowMultiParagraphSelection into toolbar options
* Deprecate global allowMultiParagraphSelection option
* Fix issue with allowMultiParagraphSelection option over empty elements
* Fix issue with creating links producing multiple anchor tags
* Fix issue where anchor preview displays while toolbar is visible
* Add demo pages for example extension and example button
5.1.0 / 2015-06-26
==================
* Add showToolbarDefaultAction helper method to form extension
* Ensure elements generated for textareas have a unique id
* Ensure all added attributes are removed during destroy
* Cleanup divs generated by Chrome during justify actions
* Add parameter to anchorPreview.positionPreview for reusability
5.0.0 / 2015-06-18
==================
* All deprecated functions have been removed
* Keyboard Shorcuts are now part of an extension and not attached to specific button/commands
* Placeholders are now part of an extension with its own dedicated options
* Toolbar is now an extension with its own dedicated options
* firstHeader and secondHeader are gone you should use h1 thru h6
* Support pre-releases
* Buttons
* The array of buttons can now contain objects, for overriding any part of the button object
* This replaces the custom object value for the buttonLabels option
* API
* Unique id for MediumEditor instance will now remain unique (regardless of how many instances are created)
* .statics references are gone
* .trigger supports triggering events without needing to declare the event
* .callExtensions(), .setToolbarPosition(), and .hideToolbarDefaultActions() have been removed
* Extension
* .window & .document are now exposed as members of the Extension
* init no longer is passed MediumEditor instance as first argument
* CSS
* All classes are now `medium-editor` prefixed
* Util
* getProp, derives, getSelectionData, setObject & getObject are gone
* getSelectionRange & getSelectionStart are now in Selection
4.12.5 / 2015-06-16
==================
* Fix issue with restoring selection within nested block elements
4.12.4 / 2015-06-15
==================
* Ensure auto-link will never select an empty element (br, hr, input, etc.)
4.12.3 / 2015-06-12
==================
* Fix bug with un-linked auto-links causing unexpected cursor positioning
4.12.2 / 2015-06-10
==================
* Fix broken keyboard shortcuts
4.12.1 / 2015-06-02
==================
* Fix break with updateOnEmptySelection option for static toolbars
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
==================
* Fix issue with auto-linked text after manually unlinking
* Fix some incorrect TLDs for auto-link
4.11.0 / 2015-05-26
==================
* Add hideToolbar and showToolbar custom events
* Add hideOnClick option for placeholder extension
* Fix issue with linebreaks in Safari
* Fix issue with calling setup again after destroy
* Add support for CDN hosting
* Pass window and document to each extension
* Deprecate .parent property of extensions
4.10.2 / 2015-05-21
==================
* Auto-Link Fixes
* Don't auto-link text after it is manually unlinked
* Trigger auto-linking when focus is lost (ie Tab key)
* Fix issue where link appears and immediately disappears when hitting Enter in IE11
* Fix issue where hostname with more than three w's only auto-links final three w's in the name
* Fix issue where valid urls were not auto-linked
* Fix issue where some text was auto-linked when it shouldn't be
4.10.1 / 2015-05-20
==================
* Fix paste issue with plain-text containing multiple paragraphs
* Fix issue with incorrect cursor positon after creating a list
* Fix disabledDoubleReturn option within a sentence
* Allow for nested contenteditables
* New style of passing options for anchor-preview and anchor
* Introduce extensions.button + extensions.form as extendable base extensions
* Convert anchor, fontsize, and anchor-preview to updated extensions model
4.9.0 / 2015-05-18
==================
* New auto-link support for detecting urls and converting them to links
* Fix target _blank issue for links in Firefox
* Don't show placeholders for empty lists
* Allow for overriding image drag and drop via extension
4.8.1 / 2015-05-13
==================
* Fix error thrown when loading MediumEditor js from head
4.8.0 / 2015-05-11
==================
* Expose new 'editableInput' event for monitoring changes to editor
* Cleanup contenteditable elements created for textareas
4.7.3 / 2015-05-07
==================
* Update version number in dist files
4.7.2 / 2015-05-06
==================
* Add shortcut to insert a link (ctrl/cmd + k)
* Fix `this.getAttribute is not a function` error
4.7.1 / 2015-04-30
==================
* Make anchor preview wrap for long links
* Fix issue when clean pasting spans with child nodes
4.7.0 / 2015-04-27
==================
* Expose importSelection + exportSelection helper methods
* Fix issue with initialization of MediumEditor using textarea
* Introduce jscs
4.6.0 / 2015-04-22
==================
* Add 'beta' version of fontSize button/form
* Add option for enabling/disabling spellcheck
* Add titles to toolbar buttons for tooltips
* Use actual anchor tag in anchor preview
* Fix anchor preview issue with tags nested inside anchors
* Speed up travis builds
* Convert paste handler into overrideable extension
4.5.2 / 2015-04-14
==================
* Fix blur event detection when clicking on elements that don't clear focus
4.5.1 / 2015-04-14
==================
* Fix broken 'paste.cleanPastedHtml' option and rename to 'paste.cleanPastedHTML'
4.5.0 / 2015-04-13
==================
* Expose 'unsubscribe' for custom events
* Detach custom events when editor is destroyed
* Fix fontawesome url in demo page
4.4.0 / 2015-04-11
==================
* Expose smart 'blur' and 'focus' events which account for toolbar interaction
* Expose selectElement method for selecting text and updating toolbar
* Fix always wrapping pasted text in a <p> tag
4.3.0 / 2015-04-10
==================
* Add override options for pasteHTML and cleanPaste
* Support overriding of scss theme variables
* Fix for justify button states in IE
* New helpers for manipulating nested objects
* Internal tooling prep for options and defaults
4.2.0 / 2015-04-05
==================
* Add textarea support
4.1.1 / 2015-04-01
==================
* Fix .version issue
4.1.0 / 2015-03-29
==================
* Expose Util and Selection methods externally via MediumEditor.util and MediumEditor.selection
* Expose MediumEditor.version for version info
* Add support for custom cleaning of attributes and tags for .pasteHTML
* Move from jslint to jshint
4.0.3 / 2015-03-27
==================
* Introduce 'removeFormat' button, for removing formatting from selection
* Fix issues with focus/blur when using standardizeSelectionStart option
4.0.2 / 2015-03-26
==================
* Fix bug causing toolbar to disappaer on click in safari (rollback fix from 4.0.1)
* Break up anchor form extension logic into more overrideable parts
4.0.1 / 2015-03-24
==================
* Fix issue with dragged in image sizes
* Fix issues with focus/blur when using standardizeSelectionStart option
4.0.0 / 2015-03-23
==================
* Introduced custom events (consumable externally)
* Reduce API surface area
* Deprecated activate & deactivated. Exposed setup and destroy as replacements
* Updated documentation to reflect API changes
* HTML standardization around list items
* Fixed throttling
* Added superscript & subscript css
* Added better paste cleaning for Microsoft Word
* Convert anchor preview into overrideable extension
* Added disableAnchorPreview option
3.0.9 / 2015-03-10
==================
* Extract toolbar
* Extract anchor preview
3.0.8 / 2015-02-27
==================
* MIT License
* Use code from selection.js which is duplicated in core.js
* Fix bug in paste handling + increase paste coverage
3.0.7 / 2015-02-26
==================
* Ensure static toolbar won't render outside window + minimize when toolbar overflows
* Fix flashing static-toolbar bug
* Fix bug with sticky-toolbar when scrolling past bottom of contenteditable
* Fix css declaration of linear-gradient
* Fix AMD "Uncaught TypeError: undefined is not a function" issue
* Account for 'full' actions when doing queryCommandState
* Fix bugs in modified queryCommandState calls
3.0.0 / 2015-02-23
==================
* Extract anchor form code from core code and convert into an extension
* Expose onShowToolbar and onHideToolbar as options
* Change button method names (now `setActive` and `setInactive`) to differentiate from core's `activate` and `deactivate`
* Simplify blur check selection
* Add Sauce Labs configuration to automate cross-browser testing
* Add IE9 polyfill to repo
* Let 'meta' key trigger shortcuts
2.4.6 / 2015-02-18
==================
* Add basic support to keyboard shortcuts
2.4.5 / 2015-02-17
==================
* Fix main file reference in npm package
2.4.3 / 2015-02-16
==================
* Introduce full content actions
2.4.2 / 2015-02-15
==================
* Fix disableDoubleReturn option
2.4.1 / 2015-02-15
==================
* Fix isListItemChild call
2.4.0 / 2015-02-15
==================
* Split source code into several files for better development flow
* Make saveSelection and restoreSelection more consistant cross browser
* Use document.queryCommandState for some button toolbar states
* Add selection storage
* Call extensions deactivate when deactivating the editor
* Turn Anchor button into an extension
2.3.0 / 2015-02-11
==================
* Fix various selection and positioning bugs
* Introduce commands as combination of buttons and extensions
* Update aria label so that setting secondHeader activates the toolbar
* Don't use styles for detecting underline + strikethrough
* Fix 'imageDragging: false' option
* Fix list item tab identation
* Add extension onHide command
2.2.0 / 2015-02-05
==================
* Fix bug in getSelectedParentElement + Fix tests in browsers
* Fall back to shimmed insertHTML in cases where firefox throws
when calling insertHTML
* Prevent "Argument not optional" error
* Prevent infinite loop after findAdjacentTextNodeWithContent
* Remove cleanups from contenteditable false areas
* Firefox fix: Don't modify value of input before calling execCommand()
* Fix selection issue for clean pasted html test case in firefox
* Add image drag and drop support
2.1.3 / 2015-01-31
==================
* Fix issue with multiple elements with the same class
on the same editor instance
2.1.2 / 2015-01-30
==================
* Specify default npm registry (`http://registry.npmjs.org`)
2.1.1 / 2015-01-30
==================
* Adds support for newlines in placeholder attribute
* Adds support and documentation for new toolbar extensions
* Adds support for changing 'open in new window' label text
* Fixes bug where `nodeValue` could unexpectedly be null
* A couple of fixes to make tests a bit more reliable when run in the browser
2.1.0 / 2015-01-27
==================
* Handles ESC key in link editor
* Standardizes usage of setTimeout for UX delays vs debouncing vs deferring
* Adds an optional onShowToolbar method
* Supports enabling/disabling checkSelection updates externally
* Standardizes where in the DOM a range begins
* Adds ARIA role information
* Fixes off() not removing any event listeners
* Misc minor bug fixes and improvements
2.0.0 / 2015-01-06
==================
* Adds static toolbar feature
* Now uses textContent instead of innerText
* Fixes plain text paste on IE
* Hides placeholder on mouse click
* Adds a 'collapse' option to 'selectElementContents' helper
* Allows toolbar button states to change when selection is collapsed
* In hideToolbarActions, calls an optional 'onHideToolbar' method
* Ensures that ul.id and anchor.id are unique
* Avoids grabbing selection on keypress for contenteditable except for spacebar
* Supports disabling anchorForm, avoiding unnecessary event handling and element creation
* Supports disabling placeholders, including not attaching event handlers when not needed
* Various minor bug fixes and improvements
1.9.13 / 2014-11-24
===================
* Adds a strikethrough option in buttonLabel
* Now uses `options.elementsContainer` to calculate ID
* Removes events during deactivate
1.9.10 / 2014-11-17
===================
* Adds custom doc and win functionality, now you can specify the editor container
* Minor bugfixes
1.9.8 / 2014-10-21
==================
* Fixes 'this' out of scope
1.9.7 / 2014-10-20
==================
* Adds justify buttons
* Fix #308 by passing clipboard content through self.htmlEntities before inserting
* Minor bug fixes
1.9.4 / 2014-09-16
==================
* Adds support for tab based indenting and outdenting of <ul> and <ol>
* Adds a save button to the anchor form
* Improves toolbar positioning
* Adds anchorButton and anchorButtonClass options
1.9.0 / 2014-08-08
==================
* Extensions
* Disables the toolbar when selecting within an element that has contenteditable="false"
* Fixes hidden placeholder content override
1.8.14 / 2014-06-11
===================
* Fixes bug where if you had an empty blockquote the placeholder would still be active
* Fixes bug that would create link without values
* Exposes save/restoreSelection()
* Allows customization of active/first/last button classes
* Adds a script to run app from the cli
* Adds protocols to checkLinkFormat regex
1.8.8 / 2014-05-08
==================
* Fixes unlink behavior on Firefox
* Fixes white space behavior at the end of anchors
1.8.6 / 2014-05-03
==================
* Adds non-minified CSS files to bower.json
1.8.5 / 2014-05-01
==================
* Changes to the element list or element selector now take effect on reactivation
* Changed innerHTML to textContent to prevent XSS through twisted href values
* Checks for data-disable-return on element on paste
* Adds disableEditing and elementsContainer options
1.8.0 / 2014-04-12
==================
* Removes anchor preview listeners on deactivate
* Implements clean paste
* Adds an option to validate links
* Adds a basic extensions support
* Misc minor fixes
1.7.5 / 2014-03-30
==================
* Fixes isActive toggling
* Removes anchor preview default value
1.7.3 / 2014-03-22
==================
* Fixes activate/deactivate behavior
1.7.2 / 2014-03-22
==================
* Removes DOM elements created by MediumEditor on deactivate
1.7.1 / 2014-03-22
==================
* Prevents new lines with shift+enter when disableReturn is set to true
1.7.0 / 2014-03-22
==================
* Removes compass dependency by using grunt with libsass
* Fixes subscript button markup
* Fixes anchor preview behavior for empty links and anchors
* Adds a new option to disable double returns
1.6.7 / 2014-03-13
==================
* Allows initialization with a single DOM node
* Adds indent and outdent buttons
1.6.5 / 2014-03-08
==================
* fixes some minor paste bugs
* adds a delay option for anchor toolbar
* fixes anchor toolbar initial positioning
* fixes heading and blockquote on IE
1.6.1 / 2014-03-04
==================
* fixes case where clicking anchor preview and then clicking into the anchorInput
causes hideToolbarActions to be called
* fixes window resize when toolbar element is not created
1.6.0 / 2014-02-27
==================
* Reorganizes CSS files
* Removes unused method bindElementToolbarEvents
* Adds a preview toolbar for anchors
* Removes paste event binding on deactivate
1.5.4 / 2014-02-12
==================
* Fixes filenames for main in bower.json
* Removes window resize event listener on deactivate
1.5.3 / 2014-01-22
==================
* Adds bootstrap theme
* Adds image button that converts selected text into an image tag
* Removes normalize.css dependency
1.5.0 / 2014-01-16
==================
* Adds 3 new themes: Roman, Mani e Flat
1.4.5 / 2014-01-13
==================
* Adds ability to set custom labels on buttons
* Updates uglify
* Fixes bug where pressing enter on formatted list item would generate
a new list instead of a new list item
1.4.0 / 2013-12-13
==================
* Adds new extra buttons: pre and strikethrough
* Fixes placeholder bug on paste
* Various code improvements
* Prevents returns using shift when disableReturn is set to true
* Improves CSS to avoid conflicts
1.3.5 / 2013-11-27
==================
* Fixes problem with text selection ending outside the container div
* Implements serialize method
* Adds a targetBlank option
* Fixes Firefox box-sizing declarations
1.3.1 / 2013-11-19
==================
* Fixes toolbar binding button issue with multi-editor mode
1.3.0 / 2013-11-18
==================
* Fixes data-disable-return not preventing paragraph creation
* Improves getSelectionElement() to work in any case
* Fixes multi element selection bug
* Fixes Issues #88 & #89
* Improves binding for multiple editor instance, checkSelection() is called only once per instance
* Improves allowMultiParagraphSelection filter by removing empty tags elements before counting
* Considers header tags has a paragraph too (same as medium)
1.2.2 / 2013-11-07
==================
* Removes blur event listener when disabling the toolbar
* Adds a light gradient opacity to the toolbar
* Fixes bug that would keep toolbar alive when moving out of the anchor input
1.2.1 / 2013-11-07
==================
* Fixes empty selectionNode.el bug
* Prevents toolbar opening when changing to selection elements
with the toolbar disabled
* Adds a transition to the toolbar when moving across elements
1.2.0 / 2013-11-06
==================
* Fixes issue on deactivation without enabled toolbar
* Fixes checkSelection error when disableToolbar option is enabled
* Adds new option to disable multiple paragraph selection
* Prevents paragraph creation on paste when disableReturn is set to true
1.1.6 / 2013-10-24
==================
* Adds extra buttons: superscript, subscript, ordered list and unordered list
1.1.5 / 2013-10-23
==================
* Changes buttons blacklist to whitelist
1.1.4 / 2013-10-13
==================
* Exports MediumEditor as module
* Changes "Underline" button to display "U" instead of "S"
1.1.3 / 2013-10-08
==================
* Pasted text is now wrapped into P elements
1.1.2 / 2013-10-06
==================
* Changes the editor to use the formatBlock command to handle block elements
* Fixes placeholder for empty elements
1.1.1 / 2013-10-04
==================
* Normalizes styles and scripts
* Improves bower manifest
1.1.0 / 2013-10-03
==================
* Adds an option to disable the toolbar and maintain only the contenteditable behavior
* Adds an option to disable returns
* Adds an placeholder option for the contenteditable areas
1.0.3 / 2013-10-01
==================
* Fixes toolbar positioning on screen top
1.0.2 / 2013-09-24
==================
* Adds the possibility to pass an element list directly into initialization
* Fixes issue with initial positioning when some actions are disabled
* Don't rely on :last-child to style first/last element, as they may be hidden
1.0.1 / 2013-09-20
==================
* Changes demo texto to something more friendly
* Fixes shift+enter behavior
1.0.0 / 2013-08-26
==================
* Initial release
================================================
FILE: CODE_OF_CONDUCT.md
================================================
# Contributor Code of Conduct
As contributors and maintainers of MediumEditor, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities.
We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, age, or religion.
Examples of unacceptable behavior by participants include the use of sexual language or imagery, derogatory comments or personal attacks, trolling, public or private harassment, insults, or other unprofessional conduct.
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. Project maintainers who do not follow the Code of Conduct may be removed from the project team.
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers.
This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.0.0, available at [http://contributor-covenant.org/version/1/0/0/](http://contributor-covenant.org/version/1/0/0/)
================================================
FILE: CONTRIBUTING.md
================================================
# Contributing
## To contribute and end up in this [list](https://github.com/yabwe/medium-editor/graphs/contributors):
[Kill some bugs :)](https://github.com/yabwe/medium-editor/issues?q=is%3Aopen+is%3Aissue+label%3Abug)
1. Fork it
2. Create your feature branch (`git checkout -b my-new-feature`)
3. Test your changes to the best of your ability.
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'`) **without files from the _dist_ directory**.
6. Push to the branch (`git push origin my-new-feature`)
7. Create new Pull Request
## Code Consitency
To help create consistent looking code throughout the project, we use a few tools to help us. They have plugins for most popular editors/IDEs to make coding for our project, but you should use them in your project as well!
#### JSHint
We use [JSHint](http://jshint.com/) on each build to find easy-to-catch errors and potential problems in our js. You can find our JSHint settings in the `.jshintrc` file in the root of the project.
#### jscs
We use [jscs](http://jscs.info/) on each build to enforce some code style rules we have for our project. You can find our jscs settings in the `.jscsrc` file in the root of the project.
#### EditorConfig
We use [EditorConfig](http://EditorConfig.org) to maintain consistent coding styles between various editors and IDEs. You can find our settings in the `.editorconfig` file in the root of the project.
## Easy First Bugs
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)!
## Development
MediumEditor development tasks are managed by Grunt. To install all the necessary packages, just invoke:
```bash
npm install
```
To run all the test and build the dist files for testing on demo pages, just invoke:
```bash
grunt
```
These are the other available grunt tasks:
* __js__: runs jslint and jasmine tests and creates minified and concatenated versions of the script;
* __css__: runs autoprefixer and csslint
* __test__: runs jasmine tests, jslint and csslint
* __watch__: watch for modifications on script/scss files
* __spec__: runs a task against a specified file
The source files are located inside the __src__ directory. Be sure to make changes to these files and not files in the dist directory.
================================================
FILE: CUSTOM-EVENTS.md
================================================
# MediumEditor Custom Events (v5.0.0)
MediumEditor exposes a variety of custom events for convenience when using the editor with your web application. You can attach and detach listeners to these custom events, as well as manually trigger any custom events including your own custom events.
**NOTE:**
Custom event listeners are triggered in the order that they were 'subscribed' to. Most functionality within medium-editor uses these custom events to trigger updates, so in general, it can be assumed that most of the built-in functionality has already been completed before any of your custom event listeners will be called.
If you need to override the editor's built-in behavior, try overriding the built-in extensions with your own [custom extension](src/js/extensions).
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
- [API Methods](#api-methods)
- [`MediumEditor.subscribe(name, listener)`](#mediumeditorsubscribename-listener)
- [`MediumEditor.unsubscribe(name, listener)`](#mediumeditorunsubscribename-listener)
- [`MediumEditor.trigger(name, data, editable)`](#mediumeditortriggername-data-editable)
- [Custom Events](#custom-events)
- [`addElement`](#addelement)
- [`blur`](#blur)
- [`editableInput`](#editableinput)
- [`externalInteraction`](#externalinteraction)
- [`focus`](#focus)
- [`removeElement`](#removeelement)
- [Toolbar Custom Events](#toolbar-custom-events)
- [`hideToolbar`](#hidetoolbar)
- [`positionToolbar`](#positiontoolbar)
- [`positionedToolbar`](#positionedtoolbar)
- [`showToolbar`](#showtoolbar)
- [Proxied Custom Events](#proxied-custom-events)
- [`editableClick`](#editableclick)
- [`editableBlur`](#editableblur)
- [`editableKeypress`](#editablekeypress)
- [`editableKeyup`](#editablekeyup)
- [`editableKeydown`](#editablekeydown)
- [`editableKeydownEnter`](#editablekeydownenter)
- [`editableKeydownTab`](#editablekeydowntab)
- [`editableKeydownDelete`](#editablekeydowndelete)
- [`editableKeydownSpace`](#editablekeydownspace)
- [`editableMouseover`](#editablemouseover)
- [`editableDrag`](#editabledrag)
- [`editableDrop`](#editabledrop)
- [`editablePaste`](#editablepaste)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
## API Methods
Use the following methods of [MediumEditor](API.md) for custom event interaction:
### `MediumEditor.subscribe(name, listener)`
Attaches a listener for the specified custom event name.
**Arguments**
1. _**name** (`String`)_:
* Name of the event to listen to. See the list of built-in [Custom Events](#custom-events) below.
2. _**listener(data, editable)** (`function`)_:
* Listener method that will be called whenever the custom event is triggered.
**Arguments to listener**
1. _**data** (`Event` | `object`)_
* For most custom events, this will be the browser's native `Event` object for the event that triggered the custom event to fire.
* For some custom events, this will be an object containing information describing the event (depending on which custom event it is)
2. _**editable** (`HTMLElement`)_
* A reference to the contenteditable container element that this custom event corresponds to. This is especially useful for instances where one instance of MediumEditor contains multiple elements, or there are multiple instances of MediumEditor on the page.
* For example, when `blur` fires, this argument will be the `<div contenteditable=true></div>` element that is about to receive focus.
***
### `MediumEditor.unsubscribe(name, listener)`
Detaches a custom event listener for the specified custom event name.
**Arguments**
1. _**name** (`String`)_:
* Name of the event to detach the listener for.
2. _**listener** (`function`)_:
* A reference to the listener to detach. This must be a match by-reference and not a copy.
**NOTE**
* Calling [destroy()](API.md#destroy) on the MediumEditor object will automatically remove all custom event listeners.
***
### `MediumEditor.trigger(name, data, editable)`
Manually triggers a custom event.
1. _**name** (`String`)_:
* Name of the custom event to trigger.
2. _**data** (`Event` | `object`)_:
* Native `Event` object or custom data object to pass to all the listeners to this custom event.
3. _**editable** (`HTMLElement`)_:
* The `<div contenteditable=true></div>` element to pass to all of the listeners to this custom event.
## Custom Events
These events are custom to MediumEditor so there may be one or more native events that can trigger them.
### `addElement`
`addElement` is triggered whenever an element is added to the editor after the editor has been instantiated. This custom event will be triggered **after** the element has already been initialized by the editor and added to the internal array of **elements**. If the element being added was a `<textarea>`, the element passed to the listener will be the created `<div contenteditable=true>` element and not the root `<textarea>`.
**Arguments to listener**
1. _**data** (`object`)_
* Properties of data object
* `target`: element which was added to the editor
* `currentTarget`: element which was added to the editor
2. _**editable** (`HTMLElement`)_
* element which was added to the editor
***
### `blur`
`blur` is triggered whenever a `contenteditable` element within an editor has lost focus to an element other than an editor maintained element (ie Toolbar, Anchor Preview, etc).
Example:
1. User selects text within an editor element, causing the toolbar to appear
2. User clicks on a toolbar button
* Technically focus may have been lost on the editor element, but since the user is interacting with the toolbar, `blur` is NOT fired.
3. User hovers over a link, anchor-preview is displayed
4. User clicks link to edit it, and the toolbar now displays a textbox to edit the url
* Focus will have lost here since focus is now in the url editing textbox, but again since it's within the toolbar, `blur` is NOT fired.
5. User clicks on another part of the page which hides the toolbar and focus is no longer in the `contenteditable`
6. `blur` is triggered
***
### `editableInput`
`editableInput` is triggered whenever the content of a `contenteditable` changes, including keypresses, toolbar actions, or any other user interaction that changes the html within the element. For non-IE browsers, this is just a proxied version of the native `input` event. However, Internet Explorer and has never supported the `input` event on `contenteditable` elements, and Edge has some support for `input` on `contenteditable` (which may be fixed in upcoming release of Edge) so for these browsers the `editableInput` event is triggered through a combination of:
* native `keypress` event on the element
* native `selectionchange` event on the document
* monitoring calls the `document.execCommand()`
***
### `externalInteraction`
`externalInteraction` is triggered whenever the user interact with any element outside of the `contenteditable` element or the other elements maintained by the editor (ie Toolbar, Anchor Preview, etc.). This event trigger regardless of whether an existing `contenteditable` element had focus or not.
***
### `focus`
`focus` is triggered whenever a `contenteditable` element within an editor receives focus. If the user interacts with any editor maintained elements (ie toolbar), `blur` is NOT triggered because focus has not been lost. Thus, `focus` will only be triggered when an `contenteditable` element (or the editor that contains it) is first interacted with.
***
### `removeElement`
`removeElement` is triggered whenever an element is removed from the editor after the editor has been instantiated. This custom event will be triggered **after** the element has already been removed from the editor and any events attached to it have already been removed. If the element being removed was a `<div>` created to correspond to a `<textarea>`, the element will already have been removed from the DOM.
**Arguments to listener**
1. _**data** (`object`)_
* Properties of data object
* `target`: element which was removed from the editor
* `currentTarget`: element which was removed from the editor
2. _**editable** (`HTMLElement`)_
* element which was removed from the editor
## Toolbar Custom Events
These events are triggered by the toolbar when the toolbar extension has not been disabled.
### `hideToolbar`
`hideToolbar` is triggered whenever the toolbar was visible and has just been hidden.
### `positionToolbar`
`positionToolbar` is triggered each time the current selection is checked and the toolbar's position is about to be updated. This event is triggered after all of the buttons have had their state updated, but before the toolbar is moved to the correct location. This event will be triggered even if nothing will be changed about the toolbar's appearance.
### `positionedToolbar`
`positionedToolbar` is triggered each time the current selection is checked, the toolbar is displayed, and the toolbar's position was updated. This differs from the `positionToolbar` event in that the visibility and location of the toolbar has already been changed (as opposed to the event triggering before those changes occur). This event will be triggered even if nothing was changed about the toolbar's appearance.
### `showToolbar`
`showToolbar` is triggered whenever the toolbar was hidden and has just been displayed.
## Proxied Custom Events
These events are triggered whenever a native browser event is triggered for any of the `contenteditable` elements monitored by this instance of MediumEditor.
For example, the `editableClick` custom event will be triggered when a native `click` event is fired on any of the `contenteditable` elements. This provides a single event listener that can get fired for all elements, and also allows for the `contenteditable` element that triggered the event to be passed to the listener.
##### `editableClick`
native `click` event for each element
##### `editableBlur`
native `blur` event for each element.
##### `editableKeypress`
native `keypress` event for each element.
##### `editableKeyup`
native `keyup` event for each element.
##### `editableKeydown`
native `keydown` event for each element.
##### `editableKeydownEnter`
native `keydown` event for each element, but only triggered if the key is `ENTER` (keycode 13).
##### `editableKeydownTab`
native `keydown` event for each element, but only triggered if the key is `TAB` (keycode 9).
##### `editableKeydownDelete`
native `keydown` event for each element, but only triggered if the key is `DELETE` (keycode 46).
##### `editableKeydownSpace`
native `keydown` event for each element, but only triggered if the key is `SPACE` (keycode 32).
##### `editableMouseover`
native `mouseover` event for each element.
##### `editableDrag`
native `drag` event for each element.
##### `editableDrop`
native `drop` event for each element.
##### `editablePaste`
native `paste` event for each element.
================================================
FILE: Gruntfile.js
================================================
/*global module, require, process*/
module.exports = function (grunt) {
'use strict';
var autoprefixerBrowsers = ['last 3 versions', 'ie >= 9'],
globalConfig = {
src: 'src',
dest: 'dev'
},
gruntConfig = {
pkg: grunt.file.readJSON('package.json'),
globalConfig: globalConfig
},
srcFiles = [
'src/js/globals.js',
'src/js/util.js',
'src/js/extension.js',
'src/js/selection.js',
'src/js/events.js',
'src/js/extensions/button.js',
'src/js/defaults/buttons.js',
'src/js/extensions/form.js',
'src/js/extensions/anchor.js',
'src/js/extensions/anchor-preview.js',
'src/js/extensions/auto-link.js',
'src/js/extensions/file-dragging.js',
'src/js/extensions/keyboard-commands.js',
'src/js/extensions/fontname.js',
'src/js/extensions/fontsize.js',
'src/js/extensions/paste.js',
'src/js/extensions/placeholder.js',
'src/js/extensions/toolbar.js',
'src/js/extensions/deprecated/image-dragging.js',
'src/js/core.js',
'src/js/defaults/options.js',
'src/js/version.js'
];
gruntConfig.connect = {
server: {
options: {
base: '',
port: 9999
}
}
};
// TODO: build check with debug and devel false
gruntConfig.jshint = {
options: {
ignores: ['src/js/polyfills.js'],
jshintrc: true,
reporter: require('jshint-stylish')
},
all: {
src: [
'src/js/**/*.js',
'spec/*.spec.js',
'Gruntfile.js'
]
}
};
// TODO: "maximumLineLength": 120
gruntConfig.jscs = {
src: [
'src/js/**/*.js',
'spec/*.spec.js',
'Gruntfile.js',
'!src/js/polyfills.js'
],
options: {
config: '.jscsrc'
}
};
gruntConfig.karma = {
unit: {
configFile: 'karma.conf.js'
},
dev: {
configFile: 'karma.dev.conf.js'
}
};
gruntConfig.uglify = {
options: {
report: 'gzip'
},
build: {
src: 'dist/js/medium-editor.js',
dest: 'dist/js/<%= pkg.name %>.min.js'
}
};
gruntConfig.csslint = {
strict: {
options: {
'box-sizing': false,
'compatible-vendor-prefixes': false,
'fallback-colors': false,
'gradients': false,
'important': false,
'import': 2,
'outline-none': false,
'adjoining-classes': false
},
src: 'dist/css/**/*.css'
}
};
gruntConfig.sass = {
dist: {
options: {
includePaths: ['src/sass/']
},
files: {
'dist/css/medium-editor.css': 'src/sass/medium-editor.scss',
'dist/css/themes/bootstrap.css': 'src/sass/themes/bootstrap.scss',
'dist/css/themes/default.css': 'src/sass/themes/default.scss',
'dist/css/themes/flat.css': 'src/sass/themes/flat.scss',
'dist/css/themes/mani.css': 'src/sass/themes/mani.scss',
'dist/css/themes/roman.css': 'src/sass/themes/roman.scss',
'dist/css/themes/tim.css': 'src/sass/themes/tim.scss',
'dist/css/themes/beagle.css': 'src/sass/themes/beagle.scss'
}
}
};
gruntConfig.cssmin = {
main: {
options: {
noAdvanced: true
},
expand: true,
cwd: 'dist/css/',
src: ['*.css', '!*.min.css'],
dest: 'dist/css/',
ext: '.min.css'
},
themes: {
options: {
noAdvanced: true
},
expand: true,
cwd: 'dist/css/themes/',
src: ['*.css', '!*.min.css'],
dest: 'dist/css/themes/',
ext: '.min.css'
}
};
gruntConfig.autoprefixer = {
main: {
expand: true,
cwd: 'dist/css/',
src: ['*.css', '!*.min.css'],
dest: 'dist/css/',
browsers: autoprefixerBrowsers
},
themes: {
expand: true,
cwd: 'dist/css/themes/',
src: ['*.css', '!*.min.css'],
dest: 'dist/css/themes/',
browsers: autoprefixerBrowsers
}
};
gruntConfig.watch = {
scripts: {
files: ['src/js/**/*.js', 'spec/**/*.js', 'Gruntfile.js'],
tasks: ['js'],
options: {
debounceDelay: 250
}
},
styles: {
files: 'src/sass/**/*.scss',
tasks: ['css'],
options: {
debounceDelay: 250
}
}
};
gruntConfig.concat = {
options: {
stripBanners: true
},
dist: {
src: ['src/js/polyfills.js']
.concat(['src/wrappers/start.js'])
.concat(srcFiles)
.concat(['src/wrappers/end.js']),
dest: 'dist/js/<%= pkg.name %>.js',
nonull: true
}
};
gruntConfig.plato = {
feed: {
files: {
'reports/plato': srcFiles
}
}
};
gruntConfig.bump = {
options: {
files: ['package.json', 'src/js/version.js'],
updateConfigs: [],
commit: false,
createTag: false,
push: false
}
};
grunt.initConfig(gruntConfig);
require('time-grunt')(grunt);
require('load-grunt-tasks')(grunt, {
pattern: [
'grunt-*',
'!grunt-template-jasmine-istanbul'
]
});
if (parseInt(process.env.TRAVIS_PULL_REQUEST, 10) > 0) {
grunt.registerTask('travis', ['jshint', 'jscs', 'karma:unit', 'csslint']);
} else {
grunt.registerTask('travis', ['jshint', 'jscs', 'csslint', 'karma:unit']);
}
grunt.registerTask('test', ['jshint', 'jscs', 'concat', 'csslint', 'karma:dev']);
grunt.registerTask('js', ['jshint', 'jscs', 'concat', 'karma:dev', 'uglify']);
grunt.registerTask('css', ['sass', 'autoprefixer', 'cssmin', 'csslint']);
grunt.registerTask('default', ['js', 'css']);
grunt.registerTask('spec', 'Runs a task on a specified file', function (taskName, fileName) {
globalConfig.file = fileName;
grunt.task.run(taskName + ':spec');
});
grunt.registerTask('patch', ['bump', 'css', 'js']);
grunt.registerTask('minor', ['bump:minor', 'css', 'js']);
grunt.registerTask('major', ['bump:major', 'css', 'js']);
};
================================================
FILE: LICENSE
================================================
Copyright Davi Ferreira, https://www.daviferreira.com/
This software consists of voluntary contributions made by many
individuals. For exact contribution history, see the revision history
available at https://github.com/yabwe/medium-editor
The following license applies to all parts of this software except as
documented below:
====
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
====
All files located in the node_modules directory are
externally maintained libraries used by this software which have their
own licenses; we recommend you read them, as their terms may differ from
the terms above.
================================================
FILE: MAINTAINERS.md
================================================
## STEPS TO RELEASE:
1. Find the last release commit in log history. Look through all the commits or PR history and see all the stuff that has happened since the last release.
2. Add a row describing each high-level change into `CHANGES.md`. Looking at `CHANGES.md` would be a good stepping off point.
3. Depending upon the changes, decide if it is a major/minor/patch release. _Read more about [semantic versioning](http://semver.org/)_.
4. Depending upon the type of release, run `grunt major`, `grunt minor`, `grunt patch` to update the version number and generate all the dist files.
5. Commit all your changes (**including `CHANGES.md`**) into your commit. Add the new release number into your commit message. And push it up to the remote master branch.
6. Go [here](https://github.com/yabwe/medium-editor/releases) and ‘Draft a new release’. Title the release as the new release number (ex: `5.11.0`). Copy/paste the entries you made in `CHANGES.md` into the release summary. **Make sure the release is against the master branch.**
7. Once the release is created, go back to your git and run `npm publish`.
## RUNNING TESTS FOR FORK BRANCHES IN SAUCELABS:
For pull requests submitted from a forked version of the repo, the test suite won't run in Saucelabs so we haven't been able to know if tests fail in various browsers until after the PR is merged into master. This is deliberate by Saucelabs as a security measure to prevent external forks from doing malicious things to the repo.
There is a workaround however, so when a PR is submitted from an external fork, follow these steps to verify the tests don't fail in Saucelabs before merging the PR into master.
For this example, let's assume there's a new pull request (#123) from a branch of an external user's fork (external-user/new-branch)
1. Create a new local branch for the pull request
* ```git checkout -b integration-123```
2. Add a remote that points to the external fork
* ```git remote add external-user git@github.com:external-user/medium-editor.git```
3. Fetch the remote repo
* ```git fetch external-user```
4. Merge the external branch into your local branch
* ```git merge external-user/new-branch```
5. Push your local branch up to the main repo
* ```git push```
That's it. Pushing the branch up should kick off a travis build, which will cause the tests to run in Saucelabs. Github is smart enough to link the existing pull request to that build and reflect the status of the build (including the results from Saucelabs) on the PR summary page!
================================================
FILE: OPTIONS.md
================================================
# MediumEditor Options (v5.0.0)
Options to customize medium-editor are passed as the second argument to the [MediumEditor constructor](API.md#mediumeditorelements-options). Example:
```js
var editor = new MediumEditor('.editor', {
// options go here
});
```
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
- [Core Options](#core-options)
- [`activeButtonClass`](#activebuttonclass)
- [`buttonLabels`](#buttonlabels)
- [`contentWindow`](#contentwindow)
- [`delay`](#delay)
- [`disableReturn`](#disablereturn)
- [`disableDoubleReturn`](#disabledoublereturn)
- [`disableExtraSpaces`](#disableextraspaces)
- [`disableEditing`](#disableediting)
- [`elementsContainer`](#elementscontainer)
- [`extensions`](#extensions)
- [`ownerDocument`](#ownerdocument)
- [`spellcheck`](#spellcheck)
- [`targetBlank`](#targetblank)
- [Toolbar options](#toolbar-options)
- [`allowMultiParagraphSelection`](#allowmultiparagraphselection)
- [`buttons`](#buttons)
- [`diffLeft`](#diffleft)
- [`diffTop`](#difftop)
- [`firstButtonClass`](#firstbuttonclass)
- [`lastButtonClass`](#lastbuttonclass)
- [`relativeContainer`](#relativecontainer)
- [`standardizeSelectionStart`](#standardizeselectionstart)
- [`static`](#static)
- ['static' Toolbar Options](#static-toolbar-options)
- [`align`](#align)
- [`sticky`](#sticky)
- [`stickyTopOffset`](#stickytopoffset)
- [`updateOnEmptySelection`](#updateonemptyselection)
- [Disabling Toolbar](#disabling-toolbar)
- [Anchor Preview options](#anchor-preview-options)
- [`hideDelay`](#hidedelay)
- [`previewValueSelector`](#previewvalueselector)
- [`showOnEmptyLinks`](#showonemptylinks)
- [`showWhenToolbarIsVisible`](#showwhentoolbarisvisible)
- [Disabling Anchor Preview](#disabling-anchor-preview)
- [Placeholder Options](#placeholder-options)
- [`text`](#text)
- [`hideOnClick`](#hideonclick)
- [Disabling Placeholders](#disabling-placeholders)
- [Anchor Form options](#anchor-form-options)
- [`customClassOption`](#customclassoption)
- [`customClassOptionText`](#customclassoptiontext)
- [`linkValidation`](#linkvalidation)
- [`placeholderText`](#placeholdertext)
- [`targetCheckbox`](#targetcheckbox)
- [`targetCheckboxText`](#targetcheckboxtext)
- [Paste Options](#paste-options)
- [`forcePlainText`](#forceplaintext)
- [`cleanPastedHTML`](#cleanpastedhtml)
- [`cleanReplacements`](#cleanreplacements)
- [`cleanAttrs`](#cleanattrs)
- [`cleanTags`](#cleantags)
- [`unwrapTags`](#unwraptags)
- [Disabling Paste Handling](#disabling-paste-handling)
- [KeyboardCommands Options](#keyboardcommands-options)
- [`commands`](#commands)
- [Disabling Keyboard Commands](#disabling-keyboard-commands)
- [Auto Link Options](#auto-link-options)
- [`autoLink`](#autolink)
- [Enabling Auto Link](#enabling-auto-link)
- [Image Dragging Options](#image-dragging-options)
- [`imageDragging`](#imagedragging)
- [Disabling Image Dragging](#disabling-image-dragging)
- [Options Example:](#options-example)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
## Core Options
These are global options that apply to the entire editor. Example:
```js
var editor = new MediumEditor('.editable', {
/* These are the default options for the editor,
if nothing is passed this is what is used */
activeButtonClass: 'medium-editor-button-active',
allowMultiParagraphSelection: true,
buttonLabels: false,
contentWindow: window,
delay: 0,
disableReturn: false,
disableDoubleReturn: false,
disableExtraSpaces: false,
disableEditing: false,
elementsContainer: false,
extensions: {},
ownerDocument: document,
spellcheck: true,
targetBlank: false
});
```
#### `activeButtonClass`
**Default:** `'medium-editor-button-active'`
CSS class added to active buttons in the toolbar.
***
#### `buttonLabels`
**Default:** `false`
Custom content for the toolbar buttons.
**Valid Values:**
* `false`
* Use default button labels
* `'fontawesome'`
* Uses fontawesome icon set for all toolbar icons
**NOTE**:
Using `'fontawesome'` as the buttonLabels requires version 4.1.0 of the fontawesome css to be on the page to ensure all icons will be displayed correctly.
***
#### `contentWindow`
**Default:** `window`
The contentWindow object that contains the contenteditable element. MediumEditor will use this for attaching events, getting selection, etc.
***
#### `delay`
**Default:** `0`
Time in milliseconds to show the toolbar or anchor tag preview.
***
#### `disableReturn`
**Default:** `false`
Enables/disables the use of the return-key. You can also set specific element behavior by using setting a data-disable-return attribute.
***
#### `disableDoubleReturn`
**Default:** `false`
Allows/disallows two (or more) empty new lines. You can also set specific element behavior by using setting a data-disable-double-return attribute.
***
#### `disableExtraSpaces`
**Default:** `false`
When set to true, it disallows spaces at the beginning and end of the element. Also it disallows entering 2 consecutive spaces between 2 words.
***
#### `disableEditing`
**Default:** `false`
Enables/disables adding the contenteditable behavior. Useful for using the toolbar with customized buttons/actions. You can also set specific element behavior by using setting a data-disable-editing attribute.
***
#### `elementsContainer`
**Default:** `ownerDocument.body`
Specifies a DOM node to contain MediumEditor's toolbar and anchor preview elements.
***
#### `extensions`
**Default:** `{}`
Custom extensions to use. See [Custom Buttons and Extensions](src/js/extensions) for more details on extensions.
***
#### `ownerDocument`
**Default:** `window.document`
The ownerDocument object for the contenteditable element. MediumEditor will use this for creating elements, getting selection, attaching events, etc.
***
#### `spellcheck`
**Default:** `true`
Enable/disable native contentEditable automatic spellcheck.
***
#### `targetBlank`
**Default:** `false`
Enables/disables automatically adding the `target="_blank"` attribute to anchor tags.
## Toolbar options
The toolbar for MediumEditor is implemented as a built-in extension which automatically displays whenever the user selects some text. The toolbar can hold any set of defined built-in buttons, but can also hold any custom buttons passed in as extensions.
Options for the toolbar are passed as an object that is a member of the outer options object. Example:
```js
var editor = new MediumEditor('.editable', {
toolbar: {
/* These are the default options for the toolbar,
if nothing is passed this is what is used */
allowMultiParagraphSelection: true,
buttons: ['bold', 'italic', 'underline', 'anchor', 'h2', 'h3', 'quote'],
diffLeft: 0,
diffTop: -10,
firstButtonClass: 'medium-editor-button-first',
lastButtonClass: 'medium-editor-button-last',
relativeContainer: null,
standardizeSelectionStart: false,
static: false,
/* options which only apply when static is true */
align: 'center',
sticky: false,
updateOnEmptySelection: false
}
});
```
***
#### `allowMultiParagraphSelection`
**Default:** `true`
enables/disables whether the toolbar should be displayed when selecting multiple paragraphs/block elements.
***
#### `buttons`
**Default:** `['bold', 'italic', 'underline', 'anchor', 'h2', 'h3', 'quote']`
The set of buttons to display on the toolbar.
***
#### `diffLeft`
**Default:** `0`
Value in pixels to be added to the X axis positioning of the toolbar.
***
#### `diffTop`
**Default:** `-10`
Value in pixels to be added to the Y axis positioning of the toolbar.
***
#### `firstButtonClass`
**Default:** `'medium-editor-button-first'`
CSS class added to the first button in the toolbar.
***
#### `lastButtonClass`
**Default:** `'medium-editor-button-last'`
CSS class added to the last button in the toolbar.
***
#### `relativeContainer`
**Default:** `null`
DOMElement to append the toolbar to instead of the body. When an element is passed the toolbar will also be positioned `relative` instead of `absolute`, which means the editor will not attempt to manually position the toolbar automatically.
**NOTE:**
* Using this in combination with the `static` option for toolbar is not explicitly supported and the behavior in this case is not defined.
***
#### `standardizeSelectionStart`
**Default:** `false`
Enables/disables standardizing how the beginning of a range is decided between browsers whenever the selected text is analyzed for updating toolbar buttons status.
***
#### `static`
**Default:** `false`
Enable/disable the toolbar always displaying in the same location relative to the medium-editor element.
### 'static' Toolbar Options
These options only apply when the `static` option is being used.
***
#### `align`
**Default:** `center`
When the __static__ option is `true`, this aligns the static toolbar relative to the medium-editor element.
**Valid Values**
`'left'` | `'center'` | `'right'`
***
#### `sticky`
**Default:** `false`
When the __static__ option is `true`, this enables/disables the toolbar "sticking" to the viewport and staying visible on the screen while the page scrolls.
***
#### `stickyTopOffset`
**Default:** `0`
When the __sticky__ option is `true`, this set in pixel a top offset above the toolbar.
***
#### `updateOnEmptySelection`
**Default:** `false`
When the __static__ option is `true`, this enables/disables updating the state of the toolbar buttons even when the selection is collapsed (there is no selection, just a cursor).
### Disabling Toolbar
To disable the toolbar (which also disables the anchor-preview extension), set the value of the `toolbar` option to `false`:
```javascript
var editor = new MediumEditor('.editable', {
toolbar: false
});
```
## Anchor Preview options
The anchor preview is a built-in extension which automatically displays a 'tooltip' when the user is hovering over a link in the editor. The tooltip will display the `href` of the link, and when click, will open the anchor editing form in the toolbar.
Options for the anchor preview 'tooltip' are passed as an object that is a member of the outer options object. Example:
```javascript
var editor = new MediumEditor('.editable', {
anchorPreview: {
/* These are the default options for anchor preview,
if nothing is passed this is what it used */
hideDelay: 500,
previewValueSelector: 'a'
}
}
});
```
***
#### `hideDelay`
**Default:** `500`
Time in milliseconds to show the anchor tag preview after the mouse has left the anchor tag.
***
#### `previewValueSelector`
**Default:** `'a'`
The default selector to locate where to put the activeAnchor value in the preview. You should only need to override this if you've modified the way in which the anchor-preview extension renders.
***
#### `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`
**Default:** `false`
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.
### Disabling Anchor Preview
To disable the anchor preview, set the value of the `anchorPreview` option to `false`:
```javascript
var editor = new MediumEditor('.editable', {
anchorPreview: false
});
```
**NOTE:**
* If the toolbar is disabled (via `toolbar: false` option or `data-disable-toolbar` attribute) the anchor-preview is automatically disabled.
* If the anchor editing form is not enabled, clicking on the anchor-preview will not allow the href of the link to be edited
## Placeholder Options
The placeholder handler is a built-in extension which displays placeholder text when the editor is empty.
Options for placeholder are passed as an object that is a member of the outer options object. Example:
```javascript
var editor = new MediumEditor('.editable', {
placeholder: {
/* This example includes the default options for placeholder,
if nothing is passed this is what it used */
text: 'Type your text',
hideOnClick: true
}
});
```
***
#### `text`
**Default:** `'Type your text'`
Defines the default placeholder for empty contenteditables when __placeholder__ is not set to false. You can overwrite it by setting a `data-placeholder` attribute on the editor elements.
***
#### `hideOnClick`
**Default:** `true`
Causes the placeholder to disappear as soon as the field gains focus. To hide the placeholder only after starting to type, and to show it again as soon as field is empty, set this option to `false`.
### Disabling Placeholders
To disable the placeholder, set the value of the `placeholder` option to `false`:
```javascript
var editor = new MediumEditor('.editable', {
placeholder: false
});
```
## Anchor Form options
The anchor form is a built-in button extension which allows the user to add/edit/remove links from within the editor. When 'anchor' is passed in as a button in the list of buttons, this extension will be enabled and can be triggered by clicking the corresponding button in the toolbar.
Options for the anchor form are passed as an object that is a member of the outer options object. Example:
```javascript
var editor = new MediumEditor('.editable', {
toolbar: {
buttons: ['bold', 'italic', 'underline', 'anchor']
},
anchor: {
/* These are the default options for anchor form,
if nothing is passed this is what it used */
customClassOption: null,
customClassOptionText: 'Button',
linkValidation: false,
placeholderText: 'Paste or type a link',
targetCheckbox: false,
targetCheckboxText: 'Open in new window'
}
}
});
```
***
#### `customClassOption`
**Default:** `null`
Custom class name the user can optionally have added to their created links (ie 'button'). If passed as a non-empty string, a checkbox will be displayed allowing the user to choose whether to have the class added to the created link or not.
***
#### `customClassOptionText`
**Default:** `'Button'`
Text to be shown in the checkbox when the __customClassOption__ is being used.
***
#### `linkValidation`
**Default:** `false`
Enables/disables check for common URL protocols on anchor links. Converts invalid url characters (ie spaces) to valid characters using `encodeURI`
***
#### `placeholderText`
**Default:** `'Paste or type a link'`
Text to be shown as placeholder of the anchor input.
***
#### `targetCheckbox`
**Default:** `false`
Enables/disables displaying a "Open in new window" checkbox, which when checked changes the `target` attribute of the created link.
***
#### `targetCheckboxText`
**Default:** `'Open in new window'`
Text to be shown in the checkbox enabled via the __targetCheckbox__ option.
## Paste Options
The paste handler is a built-in extension which attempts to filter the content when the user pastes. How the paste handler filters is configurable via specific options.
Options for paste handling are passed as an object that is a member of the outer options object. Example:
```javascript
var editor = new MediumEditor('.editable', {
paste: {
/* This example includes the default options for paste,
if nothing is passed this is what it used */
forcePlainText: true,
cleanPastedHTML: false,
cleanReplacements: [],
cleanAttrs: ['class', 'style', 'dir'],
cleanTags: ['meta']
}
});
```
***
#### `forcePlainText`
**Default:** `true`
Forces pasting as plain text.
***
#### `cleanPastedHTML`
**Default:** `false`
Cleans pasted content from different sources, like google docs etc.
***
#### `cleanReplacements`
**Default:** `[]`
Custom pairs (2 element arrays) of RegExp and replacement text to use during paste when __forcePlainText__ or __cleanPastedHTML__ are `true` OR when calling `cleanPaste(text)` helper method.
***
#### `cleanAttrs`
**Default:** `['class', 'style', 'dir']`
List of element attributes to remove during paste when __cleanPastedHTML__ is `true` or when calling `cleanPaste(text)` or `pasteHTML(html,options)` helper methods.
***
#### `cleanTags`
**Default:** `['meta']`
List of element tag names to remove during paste when __cleanPastedHTML__ is `true` or when calling `cleanPaste(text)` or `pasteHTML(html,options)` helper methods.
***
#### `unwrapTags`
**Default:** `[]`
List of element tag names to unwrap (remove the element tag but retain its child elements) during paste when __cleanPastedHTML__ is `true` or when calling `cleanPaste(text)` or `pasteHTML(html,options)` helper methods.
***
### Disabling Paste Handling
To disable MediumEditor manipulating pasted content, set the both the `forcePlainText` and `cleanPastedHTML` options to `false`:
```javascript
var editor = new MediumEditor('.editable', {
paste: {
forcePlainText: false,
cleanPastedHTML: false
}
});
```
## KeyboardCommands Options
The keyboard commands handler is a built-in extension for mapping key-combinations to actions to execute in the editor.
Options for KeyboardCommands are passed as an object that is a member of the outer options object. Example:
```javascript
var editor = new MediumEditor('.editable', {
keyboardCommands: {
/* This example includes the default options for keyboardCommands,
if nothing is passed this is what it used */
commands: [
{
command: 'bold',
key: 'b',
meta: true,
shift: false
},
{
command: 'italic',
key: 'i',
meta: true,
shift: false
},
{
command: 'underline',
key: 'u',
meta: true,
shift: false
}
],
}
});
```
***
#### `commands`
**Default:** shortcuts for `bold`, `italic`, and `underline` (See above example)
Array of objects describing each command and the combination of keys that will trigger it. Required for each object:
* _command_: argument passed to `editor.execAction()` when key-combination is used
* _key_: keyboard character that triggers this command
* _meta_: whether the ctrl/meta key has to be active or inactive
* _shift_: whether the shift key has to be active or inactive
### Disabling Keyboard Commands
To disable the keyboard commands, set the value of the `keyboardCommands` option to `false`:
```javascript
var editor = new MediumEditor('.editable', {
keyboardCommands: false
});
```
## Auto Link Options
#### `autoLink`
**Default:** `false`
The auto-link handler is a built-in extension which automatically turns URLs entered into the text field into HTML anchor tags (similar to the functionality of Markdown). This feature is OFF by default.
### Enabling Auto Link
To enable built-in auto-link support, set the value of the `autoLink` option to `true`:
```javascript
var editor = new MediumEditor('.editable', {
autoLink: true
});
```
## Image Dragging Options
#### `imageDragging`
**Default:** `true`
The image dragging handler is a built-in extension for handling dragging & dropping images into the contenteditable. This feature is ON by default.
### Disabling Image Dragging
To disable built-in image dragging, set the value of the `imageDragging` option to `false`:
```javascript
var editor = new MediumEditor('.editable', {
imageDragging: false
});
```
## Options Example:
```javascript
var editor = new MediumEditor('.editable', {
delay: 1000,
targetBlank: true,
toolbar: {
buttons: ['bold', 'italic', 'quote'],
diffLeft: 25,
diffTop: 10,
},
anchor: {
placeholderText: 'Type a link',
customClassOption: 'btn',
customClassOptionText: 'Create Button'
},
paste: {
cleanPastedHTML: true,
cleanAttrs: ['style', 'dir'],
cleanTags: ['label', 'meta']
},
anchorPreview: {
hideDelay: 300
},
placeholder: {
text: 'Click to edit'
}
});
```
================================================
FILE: README.md
================================================

If you would be interested in helping to maintain one of the most successful WYSIWYG text editors on github, let us know! (See issue [#1503](https://github.com/yabwe/medium-editor/issues/1503))
# MediumEditor
This is a clone of [medium.com](https://medium.com) inline editor toolbar.
MediumEditor has been written using vanilla JavaScript, no additional frameworks required.
[](http://yabwe.github.io/medium-editor/)
[](https://gitter.im/yabwe/medium-editor?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
## Browser Support
[](https://saucelabs.com/beta/dashboard/builds)

[](https://www.npmjs.com/package/medium-editor)
[](https://travis-ci.org/yabwe/medium-editor)
[](https://david-dm.org/yabwe/medium-editor)
[](https://david-dm.org/yabwe/medium-editor#info=devDependencies)
[](https://coveralls.io/github/yabwe/medium-editor?branch=master)
# Basic usage
### Demo
__demo__: [http://yabwe.github.io/medium-editor/](http://yabwe.github.io/medium-editor/)
### Installation
**Via npm:**
Run in your console: `npm install medium-editor`
**Via bower:**
`bower install medium-editor`
**Via an external CDN**
* Using [jsDelivr](http://www.jsdelivr.com/#!medium-editor).
For the latest version:
```html
<script src="//cdn.jsdelivr.net/npm/medium-editor@latest/dist/js/medium-editor.min.js"></script>
<link rel="stylesheet" href="//cdn.jsdelivr.net/npm/medium-editor@latest/dist/css/medium-editor.min.css" type="text/css" media="screen" charset="utf-8">
```
For a custom one:
```html
<script src="//cdn.jsdelivr.net/npm/medium-editor@5.23.2/dist/js/medium-editor.min.js"></script>
<link rel="stylesheet" href="//cdn.jsdelivr.net/npm/medium-editor@5.23.2/dist/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/yabwe/medium-editor/releases) and attach medium editor's stylesheets to your page:
Find the files to below mentioned linking in the dist folder. (./medium-editor/dist/...)
```html
<link rel="stylesheet" href="css/medium-editor.css"> <!-- Core -->
<link rel="stylesheet" href="css/themes/default.css"> <!-- or any other theme -->
```
### Usage
The next step is to reference the editor's script
```html
<script src="js/medium-editor.js"></script>
```
You can now instantiate a new MediumEditor object:
```html
<script>var editor = new MediumEditor('.editable');</script>
```
The above code will transform all the elements with the .editable class into HTML5 editable contents and add the medium editor toolbar to them.
You can also pass a list of HTML elements:
```javascript
var elements = document.querySelectorAll('.editable'),
editor = new MediumEditor(elements);
```
MediumEditor also supports textarea. If you provide a textarea element, the script will create a new div with `contentEditable=true`, hide the textarea and link the textarea value to the div HTML content.
##### Integrating with various frameworks
People have contributed wrappers around MediumEditor for integrating with different frameworks and tech stacks. Take a look at the list of existing [Wrappers and Integrations](https://github.com/yabwe/medium-editor/wiki/Wrappers-and-Integration) that have already been written for MediumEditor!
## MediumEditor Options
View the [MediumEditor Options documentation](OPTIONS.md) on all the various options for MediumEditor.
Options to customize medium-editor are passed as the second argument to the [MediumEditor constructor](API.md#mediumeditorelements-options). Example:
```js
var editor = new MediumEditor('.editor', {
// options go here
});
```
### Core options
* __activeButtonClass__: CSS class added to active buttons in the toolbar. Default: `'medium-editor-button-active'`
* __buttonLabels__: type of labels on the buttons. Values: `false` | 'fontawesome'. Default: `false`
#### NOTE:
Using `'fontawesome'` as the buttonLabels requires version 4.1.0 of the fontawesome css to be on the page to ensure all icons will be displayed correctly
* __delay__: time in milliseconds to show the toolbar or anchor tag preview. Default: `0`
* __disableReturn__: enables/disables the use of the return-key. You can also set specific element behavior by using setting a data-disable-return attribute. Default: `false`
* __disableDoubleReturn__: allows/disallows two (or more) empty new lines. You can also set specific element behavior by using setting a data-disable-double-return attribute. Default: `false`
* __disableExtraSpaces__: when set to true, it disallows spaces at the beginning and end of the element. Also it disallows entering 2 consecutive spaces between 2 words. Default: `false`
* __disableEditing__: enables/disables adding the contenteditable behavior. Useful for using the toolbar with customized buttons/actions. You can also set specific element behavior by using setting a data-disable-editing attribute. Default: `false`
* __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](src/js/extensions)) for more. Default: `{}`
* __spellcheck__: Enable/disable native contentEditable automatic spellcheck. Default: `true`
* __targetBlank__: enables/disables target="\_blank" for anchor tags. Default: `false`
### Toolbar options
The toolbar for MediumEditor is implemented as a built-in extension which automatically displays whenever the user selects some text. The toolbar can hold any set of defined built-in buttons, but can also hold any custom buttons passed in as extensions.
Options for the toolbar are passed as an object that is a member of the outer options object. Example:
```javascript
var editor = new MediumEditor('.editable', {
toolbar: {
/* These are the default options for the toolbar,
if nothing is passed this is what is used */
allowMultiParagraphSelection: true,
buttons: ['bold', 'italic', 'underline', 'anchor', 'h2', 'h3', 'quote'],
diffLeft: 0,
diffTop: -10,
firstButtonClass: 'medium-editor-button-first',
lastButtonClass: 'medium-editor-button-last',
relativeContainer: null,
standardizeSelectionStart: false,
static: false,
/* options which only apply when static is true */
align: 'center',
sticky: false,
updateOnEmptySelection: false
}
});
```
* __allowMultiParagraphSelection__: enables/disables whether the toolbar should be displayed when selecting multiple paragraphs/block elements. Default: `true`
* __buttons__: the set of buttons to display on the toolbar. Default: `['bold', 'italic', 'underline', 'anchor', 'h2', 'h3', 'quote']`
* See [Button Options](#button-options) for details on more button options
* __diffLeft__: value in pixels to be added to the X axis positioning of the toolbar. Default: `0`
* __diffTop__: value in pixels to be added to the Y axis positioning of the toolbar. Default: `-10`
* __firstButtonClass__: CSS class added to the first button in the toolbar. Default: `'medium-editor-button-first'`
* __lastButtonClass__: CSS class added to the last button in the toolbar. Default: `'medium-editor-button-last'`
* __relativeContainer__: DOMElement to append the toolbar to instead of the body. When passed, the toolbar will also be positioned `relative` instead of `absolute`. Default: `null`
* __standardizeSelectionStart__: enables/disables standardizing how the beginning of a range is decided between browsers whenever the selected text is analyzed for updating toolbar buttons status. Default: `false`
* __static__: enable/disable the toolbar always displaying in the same location relative to the medium-editor element. Default: `false`
##### Options which only apply when the `static` option is being used:
* __align__: `left`|`center`|`right` - When the __static__ option is `true`, this aligns the static toolbar relative to the medium-editor element. Default: `center`
* __sticky__: When the __static__ option is `true`, this enables/disables the toolbar "sticking" to the viewport and staying visible on the screen while the page scrolls. Default: `false`
* __updateOnEmptySelection__: When the __static__ option is `true`, this enables/disables updating the state of the toolbar buttons even when the selection is collapsed (there is no selection, just a cursor). Default: `false`
To disable the toolbar (which also disables the anchor-preview extension), set the value of the `toolbar` option to `false`:
```javascript
var editor = new MediumEditor('.editable', {
toolbar: false
});
```
#### Button Options
Button behavior can be modified by passing an object into the buttons array instead of a string. This allow for overriding some of the default behavior of buttons. The following options are some of the basic parts of buttons that you may override, but any part of the `MediumEditor.Extension.prototype` can be overridden via these button options. (Check out the [source code for buttons](src/js/extensions/button.js) to see what all can be overridden).
* __name__: name of the button being overridden
* __action__: argument to pass to `MediumEditor.execAction()` when the button is clicked.
* __aria__: value to add as the aria-label attribute of the button element displayed in the toolbar. This is also used as the tooltip for the button.
* __tagNames__: 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.
* _Example_: For 'bold', if the text is ever within a `<b>` or `<strong>` tag that indicates the text is already bold. So the array of tagNames for bold would be: `['b', 'strong']`
* __NOTE__: This is not used if `useQueryState` is set to `true`.
* __style__: A pair of css property & value(s) that 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.
* _Example_: For 'bold', if the text is ever within an element with a `'font-weight'` style property set to `700` or `'bold'`, that indicates the text is already bold. So the style object for bold would be `{ prop: 'font-weight', value: '700|bold' }`
* __NOTE__: This is not used if `useQueryState` is set to `true`.
* Properties of the __style__ object:
* __prop__: name of the css property
* __value__: value(s) of the css property (multiple values can be separated by a `'|'`)
* __useQueryState__: Enables/disables whether this button should use the built-in `document.queryCommandState()` method to determine whether the action has already been applied. If the action has already been applied, the button will be displayed as 'active' in the toolbar
* _Example_: For 'bold', if this is set to true, the code will call `document.queryCommandState('bold')` which will return true if the browser thinks the text is already bold, and false otherwise
* __contentDefault__: Default `innerHTML` to put inside the button
* __contentFA__: The `innerHTML` to use for the content of the button if the __buttonLabels__ option for MediumEditor is set to `'fontawesome'`
* __classList__: An array of classNames (strings) to be added to the button
* __attrs__: A set of key-value pairs to add to the button as custom attributes to the button element.
Example of overriding buttons (here, the goal is to mimic medium by having <kbd>H1</kbd> and <kbd>H2</kbd> buttons which actually produce `<h2>` and `<h3>` tags respectively):
```javascript
var editor = new MediumEditor('.editable', {
toolbar: {
buttons: [
'bold',
'italic',
{
name: 'h1',
action: 'append-h2',
aria: 'header type 1',
tagNames: ['h2'],
contentDefault: '<b>H1</b>',
classList: ['custom-class-h1'],
attrs: {
'data-custom-attr': 'attr-value-h1'
}
},
{
name: 'h2',
action: 'append-h3',
aria: 'header type 2',
tagNames: ['h3'],
contentDefault: '<b>H2</b>',
classList: ['custom-class-h2'],
attrs: {
'data-custom-attr': 'attr-value-h2'
}
},
'justifyCenter',
'quote',
'anchor'
]
}
});
```
### Anchor Preview options
The anchor preview is a built-in extension which automatically displays a 'tooltip' when the user is hovering over a link in the editor. The tooltip will display the `href` of the link, and when clicked, will open the anchor editing form in the toolbar.
Options for the anchor preview 'tooltip' are passed as an object that is a member of the outer options object. Example:
```javascript
var editor = new MediumEditor('.editable', {
anchorPreview: {
/* These are the default options for anchor preview,
if nothing is passed this is what it used */
hideDelay: 500,
previewValueSelector: 'a'
}
}
});
```
* __hideDelay__: time in milliseconds to show the anchor tag preview after the mouse has left the anchor tag. Default: `500`
* __previewValueSelector__: the default selector to locate where to put the activeAnchor value in the preview. You should only need to override this if you've modified the way in which the anchor-preview extension renders. Default: `'a'`
* __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`
To disable the anchor preview, set the value of the `anchorPreview` option to `false`:
```javascript
var editor = new MediumEditor('.editable', {
anchorPreview: false
});
```
##### NOTE:
* If the toolbar is disabled (via `toolbar: false` option or `data-disable-toolbar` attribute) the anchor-preview is automatically disabled.
* If the anchor editing form is not enabled, clicking on the anchor-preview will not allow the href of the link to be edited
### Placeholder Options
The placeholder handler is a built-in extension which displays placeholder text when the editor is empty.
Options for placeholder are passed as an object that is a member of the outer options object. Example:
```javascript
var editor = new MediumEditor('.editable', {
placeholder: {
/* This example includes the default options for placeholder,
if nothing is passed this is what it used */
text: 'Type your text',
hideOnClick: true
}
});
```
* __text__: Defines the default placeholder for empty contenteditables when __placeholder__ is not set to false. You can overwrite it by setting a `data-placeholder` attribute on the editor elements. Default: `'Type your text'`
* __hideOnClick__: Causes the placeholder to disappear as soon as the field gains focus. Default: `true`.
To hide the placeholder only after starting to type, and to show it again as soon as field is empty, set this option to `false`.
To disable the placeholder, set the value of the `placeholder` option to `false`:
```javascript
var editor = new MediumEditor('.editable', {
placeholder: false
});
```
### Anchor Form options
The anchor form is a built-in button extension which allows the user to add/edit/remove links from within the editor. When 'anchor' is passed in as a button in the list of buttons, this extension will be enabled and can be triggered by clicking the corresponding button in the toolbar.
Options for the anchor form are passed as an object that is a member of the outer options object. Example:
```javascript
var editor = new MediumEditor('.editable', {
toolbar: {
buttons: ['bold', 'italic', 'underline', 'anchor']
},
anchor: {
/* These are the default options for anchor form,
if nothing is passed this is what it used */
customClassOption: null,
customClassOptionText: 'Button',
linkValidation: false,
placeholderText: 'Paste or type a link',
targetCheckbox: false,
targetCheckboxText: 'Open in new window'
}
}
});
```
* __customClassOption__: custom class name the user can optionally have added to their created links (ie 'button'). If passed as a non-empty string, a checkbox will be displayed allowing the user to choose whether to have the class added to the created link or not. Default: `null`
* __customClassOptionText__: text to be shown in the checkbox when the __customClassOption__ is being used. Default: `'Button'`
* __linkValidation__: enables/disables check for common URL protocols on anchor links. Converts invalid url characters (ie spaces) to valid characters using `encodeURI`. Default: `false`
* __placeholderText__: text to be shown as placeholder of the anchor input. Default: `'Paste or type a link'`
* __targetCheckbox__: enables/disables displaying a "Open in new window" checkbox, which when checked changes the `target` attribute of the created link. Default: `false`
* __targetCheckboxText__: text to be shown in the checkbox enabled via the __targetCheckbox__ option. Default: `'Open in new window'`
### Paste Options
The paste handler is a built-in extension which attempts to filter the content when the user pastes. How the paste handler filters is configurable via specific options.
Options for paste handling are passed as an object that is a member of the outer options object. Example:
```javascript
var editor = new MediumEditor('.editable', {
paste: {
/* This example includes the default options for paste,
if nothing is passed this is what it used */
forcePlainText: true,
cleanPastedHTML: false,
cleanReplacements: [],
cleanAttrs: ['class', 'style', 'dir'],
cleanTags: ['meta'],
unwrapTags: []
}
});
```
* __forcePlainText__: Forces pasting as plain text. Default: `true`
* __cleanPastedHTML__: cleans pasted content from different sources, like google docs etc. Default: `false`
* __preCleanReplacements__: custom pairs (2 element arrays) of RegExp and replacement text to use during paste when __forcePlainText__ or __cleanPastedHTML__ are `true` OR when calling `cleanPaste(text)` helper method. These replacements are executed _before_ builtin replacements. Default: `[]`
* __cleanReplacements__: custom pairs (2 element arrays) of RegExp and replacement text to use during paste when __forcePlainText__ or __cleanPastedHTML__ are `true` OR when calling `cleanPaste(text)` helper method. These replacements are executed _after_ builtin replacements. Default: `[]`
* __cleanAttrs__: list of element attributes to remove during paste when __cleanPastedHTML__ is `true` or when calling `cleanPaste(text)` or `pasteHTML(html,options)` helper methods. Default: `['class', 'style', 'dir']`
* __cleanTags__: list of element tag names to remove during paste when __cleanPastedHTML__ is `true` or when calling `cleanPaste(text)` or `pasteHTML(html,options)` helper methods. Default: `['meta']`
* __unwrapTags__: list of element tag names to unwrap (remove the element tag but retain its child elements) during paste when __cleanPastedHTML__ is `true` or when calling `cleanPaste(text)` or `pasteHTML(html,options)` helper methods. Default: `[]`
### KeyboardCommands Options
The keyboard commands handler is a built-in extension for mapping key-combinations to actions to execute in the editor.
Options for KeyboardCommands are passed as an object that is a member of the outer options object. Example:
```javascript
var editor = new MediumEditor('.editable', {
keyboardCommands: {
/* This example includes the default options for keyboardCommands,
if nothing is passed this is what it used */
commands: [
{
command: 'bold',
key: 'B',
meta: true,
shift: false,
alt: false
},
{
command: 'italic',
key: 'I',
meta: true,
shift: false,
alt: false
},
{
command: 'underline',
key: 'U',
meta: true,
shift: false,
alt: false
}
],
}
});
```
* __commands__: Array of objects describing each command and the combination of keys that will trigger it. Required for each object:
* _command_: argument passed to `editor.execAction()` when key-combination is used
* if defined as `false`, the shortcut will be disabled
* _key_: keyboard character that triggers this command
* _meta_: whether the ctrl/meta key has to be active or inactive
* _shift_: whether the shift key has to be active or inactive
* _alt_: whether the alt key has to be active or inactive
To disable the keyboard commands, set the value of the `keyboardCommands` option to `false`:
```javascript
var editor = new MediumEditor('.editable', {
keyboardCommands: false
});
```
### Auto Link Options
The auto-link handler is a built-in extension which automatically turns URLs entered into the text field into HTML anchor tags (similar to the functionality of Markdown). This feature is OFF by default.
To enable built-in auto-link support, set the value of the `autoLink` option to `true`:
```javascript
var editor = new MediumEditor('.editable', {
autoLink: true
});
```
### Image Dragging Options
The image dragging handler is a built-in extension for handling dragging & dropping images into the contenteditable. This feature is ON by default.
To disable built-in image dragging, set the value of the `imageDragging` option to `false`:
```javascript
var editor = new MediumEditor('.editable', {
imageDragging: false
});
```
#### Disable File Dragging
To stop preventing drag & drop events and disable file dragging in general, provide a dummy ImageDragging extension.
```javascript
var editor = new MediumEditor('.editor', {
extensions: {
'imageDragging': {}
}
});
```
Due to the [state of code](https://github.com/yabwe/medium-editor/issues/966) in 5.0.0, the editor *ALWAYS* prevented any drag and drop actions.
We will have a better way to disable file dragging in 6.*
### Options Example:
```javascript
var editor = new MediumEditor('.editable', {
delay: 1000,
targetBlank: true,
toolbar: {
buttons: ['bold', 'italic', 'quote'],
diffLeft: 25,
diffTop: 10,
},
anchor: {
placeholderText: 'Type a link',
customClassOption: 'btn',
customClassOptionText: 'Create Button'
},
paste: {
cleanPastedHTML: true,
cleanAttrs: ['style', 'dir'],
cleanTags: ['label', 'meta'],
unwrapTags: ['sub', 'sup']
},
anchorPreview: {
hideDelay: 300
},
placeholder: {
text: 'Click to edit'
}
});
```
## Buttons
By default, MediumEditor supports buttons for most of the commands for `document.execCommand()` that are well-supported across all its supported browsers.
### Default buttons.
MediumEditor, by default, will show only the buttons listed here to avoid a huge toolbar:
* __bold__
* __italic__
* __underline__
* __anchor__ _(built-in support for collecting a URL via the anchor extension)_
* __h2__
* __h3__
* __quote__
### All buttons.
These are all the built-in buttons supported by MediumEditor.
* __bold__
* __italic__
* __underline__
* __strikethrough__
* __subscript__
* __superscript__
* __anchor__
* __image__ (this simply converts selected text to an image tag)
* __quote__
* __pre__
* __orderedlist__
* __unorderedlist__
* __indent__ (moves the selected text up one level)
* __outdent__ (moves the selected text down one level)
* __justifyLeft__
* __justifyCenter__
* __justifyRight__
* __justifyFull__
* __h1__
* __h2__
* __h3__
* __h4__
* __h5__
* __h6__
* __removeFormat__ (clears inline style formatting, preserves blocks)
* __html__ (parses selected html and converts into actual html elements)
## 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)
## API
View the [MediumEditor Object API documentation](API.md) on the Wiki for details on all the methods supported on the MediumEditor object.
### Initialization methods
* __MediumEditor(elements, options)__: Creates an instance of MediumEditor
* __.destroy()__: tears down the editor if already setup, removing all DOM elements and event handlers
* __.setup()__: rebuilds the editor if it has already been destroyed, recreating DOM elements and attaching event handlers
* __.addElements()__: add elements to an already initialized instance of MediumEditor
* __.removeElements()__: remove elements from an already initialized instance of MediumEditor
### Event Methods
* __.on(target, event, listener, useCapture)__: attach a listener to a DOM event which will be detached when MediumEditor is deactivated
* __.off(target, event, listener, useCapture)__: detach a listener to a DOM event that was attached via `on()`
* __.subscribe(event, listener)__: attaches a listener to a custom medium-editor event
* __.unsubscribe(event, listener)__: detaches a listener from a custom medium-editor event
* __.trigger(name, data, editable)__: manually triggers a custom medium-editor event
### Selection Methods
* __.checkSelection()__: manually trigger an update of the toolbar and extensions based on the current selection
* __.exportSelection()__: return a data representation of the selected text, which can be applied via `importSelection()`
* __.importSelection(selectionState)__: restore the selection using a data representation of previously selected text (ie value returned by `exportSelection()`)
* __.getFocusedElement()__: returns an element if any contenteditable element monitored by MediumEditor currently has focused
* __.getSelectedParentElement(range)__: get the parent contenteditable element that contains the current selection
* __.restoreSelection()__: restore the selection to what was selected when `saveSelection()` was called
* __.saveSelection()__: internally store the set of selected text
* __.selectAllContents()__: expands the selection to contain all text within the focused contenteditable
* __.selectElement(element)__: change selection to be a specific element and update the toolbar to reflect the selection
* __.stopSelectionUpdates()__: stop the toolbar from updating to reflect the state of the selected text
* __.startSelectionUpdates()__: put the toolbar back into its normal updating state
### Editor Action Methods
* __.cleanPaste(text)__: convert text to plaintext and replace current selection with result
* __.createLink(opts)__: creates a link via the native `document.execCommand('createLink')` command
* __.execAction(action, opts)__: executes an built-in action via `document.execCommand`
* __.pasteHTML(html, options)__: replace the current selection with html
* __.queryCommandState(action)__: wrapper around the browser's built in `document.queryCommandState(action)` for checking whether a specific action has already been applied to the selection.
### Helper Methods
* __.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 MediumEditor instance:
```javascript
var editor = new MediumEditor('.editable');
editor.subscribe('editableInput', this._handleEditableInput.bind(this));
// imagine an ajax fetch/any other dynamic functionality which will add new '.editable' elements to the DOM
editor.addElements('.editable');
// OR editor.addElements(document.getElementsByClassName('editable'));
// OR editor.addElements(document.querySelectorAll('.editable'));
```
Passing an elements or array of elements to `addElements(elements)` will:
* Add the given element or array of elements to the internal `this.elements` array.
* Ensure the element(s) are initialized with the proper attributes and event handlers as if the element had been passed during instantiation of the editor.
* For any `<textarea>` elements:
* Hide the `<textarea>`
* Create a new `<div contenteditable=true>` element and add it to the elements array.
* Ensure the 2 elements remain sync'd.
* Be intelligent enough to run the necessary code only once per element, no matter how often you will call it.
### Removing elements dynamically
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.
```javascript
editor.removeElements(document.querySelector('#myElement'));
// OR editor.removeElements(document.getElementById('myElement'));
// OR editor.removeElements('#myElement');
// in case you have jQuery and don't exactly know when an element was removed, for example after routing state change
var removedElements = [];
editor.elements.forEach(function (element) {
// check if the element is still available in current DOM
if (!$(element).parents('body').length) {
removedElements.push(element);
}
});
editor.removeElements(removedElements);
```
## Capturing DOM changes
For observing any changes on contentEditable, use the custom `'editableInput'` event exposed via the `subscribe()` method:
```js
var editor = new MediumEditor('.editable');
editor.subscribe('editableInput', function (event, editable) {
// Do some work
});
```
This event is supported in all browsers supported by MediumEditor (including IE9+ and Edge)! To help with cases when one instance of MediumEditor is monitoring multiple elements, the 2nd argument passed to the event handler (`editable` in the example above) will be a reference to the contenteditable element that has actually changed.
This is handy when you need to capture any modifications to the contenteditable element including:
* Typing
* Cutting/Pasting
* Changes from clicking on buttons in the toolbar
* Undo/Redo
Why is this interesting and why should you use this event instead of just attaching to the `input` event on the contenteditable element?
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.
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`.
## Extensions & Plugins
Check the [documentation](src/js/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/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).
## Development
To run the demo locally:
1. Clone this repo locally
2. Run `npm install` from your console at the root
3. Run `node index.js` from the root
4. Navigate to `http://localhost:8088/demo/index.html` to view the demo
MediumEditor development tasks are managed by Grunt. To install all the necessary packages, just invoke:
```bash
npm install
```
To run all the test and build the dist files for testing on demo pages, just invoke:
```bash
grunt
```
These are the other available grunt tasks:
* __js__: runs jslint and jasmine tests and creates minified and concatenated versions of the script;
* __css__: runs autoprefixer and csslint
* __test__: runs jasmine tests, jslint and csslint
* __watch__: watch for modifications on script/scss files
* __spec__: runs a task against a specified file
The source files are located inside the __src__ directory. Be sure to make changes to these files and not files in the dist directory.
## Contributing
[Kill some bugs :)](https://github.com/yabwe/medium-editor/issues?q=is%3Aopen+is%3Aissue+label%3Abug)
1. Fork it
2. Create your feature branch (`git checkout -b my-new-feature`)
3. Test your changes to the best of your ability.
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'`) **without files from the _dist_ directory**.
6. Push to the branch (`git push origin my-new-feature`)
7. Create a new Pull Request
### Code Consistency
To help create consistent looking code throughout the project, we use a few tools to help us. They have plugins for most popular editors/IDEs to make coding for our project, but you should use them in your project as well!
#### JSHint
We use [JSHint](http://jshint.com/) on each build to find easy-to-catch errors and potential problems in our js. You can find our JSHint settings in the `.jshintrc` file in the root of the project.
#### jscs
We use [jscs](http://jscs.info/) on each build to enforce some code style rules we have for our project. You can find our jscs settings in the `.jscsrc` file in the root of the project.
#### EditorConfig
We use [EditorConfig](http://EditorConfig.org) to maintain consistent coding styles between various editors and IDEs. You can find our settings in the `.editorconfig` file in the root of the project.
### Easy First Bugs
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/yabwe/medium-editor/graphs/contributors](https://github.com/yabwe/medium-editor/graphs/contributors)
## Is Your Org Using MediumEditor?
Add your org [here](https://github.com/yabwe/medium-editor/issues/828) and we can add you to our [landing page](https://yabwe.github.io/medium-editor/#who-is-using-it)!
## License
MIT: https://github.com/yabwe/medium-editor/blob/master/LICENSE
================================================
FILE: UPGRADE-5.md
================================================
# Upgrading to v5.0.0
Version 5.0.0 of MediumEditor introduces a significantly simpler system for building custom extensions as well as extending existing buttons and extensions. As part of moving towards this extendable model, there were significant changes to the way options are passed to MediumEditor, as well as the extensions and buttons themselves.
In addition to extension related changes, there were several other potential breaking changes related to API methods, as well as utility helper methods.
## MediumEditor Options
For details on all the currently supported MediumEditor options, refer to the [Medium Editor Options Wiki Page](https://github.com/yabwe/medium-editor/wiki/Options).
### Toolbar Options
* Options controlling the toolbar are now passed as a `'toolbar'` object inside the outer options object.
* `buttons` -> `toolbar.buttons`
* `toolbarAlign` -> `toolbar.align`
* `diffTop` -> `toolbar.diffTop`
* `diffLeft` -> `toolbar.diffLeft`
* `staticToolbar` -> `toolbar.static`
* `stickyToolbar` -> `toolbar.sticky`
* `firstButtonClass` -> `toolbar.firstButtonClass`
* `lastButtonClass` -> `toolbar.lastButtonClass`
* `updateOnEmptySelection` -> `toolbar.updateOnEmptySelection`
* `standardizeSelectionStart` -> `toolbar.standardizeSelectionStart`
### Anchor Options
* Options controlling the anchor input extension are now passed as a `'anchor'` object inside the outer options object.
* `anchorInputPlaceholder` -> `anchor.placeholderText`
* `checkLinkFormat` -> `anchor.linkValidation`
* `anchorButton` & `anchorButtonClass` -> `anchor.customClassOption`
* `anchorTarget` -> `anchor.targetCheckbox`
* `anchorInputCheckboxLabel` -> `anchor.targetCheckboxText`
### Anchor Preview Options
* Options controlling the anchor preview extension are now passed as a `'anchorPreview'` object inside the outer options object.
* `anchorPreviewHideDelay` -> `anchorPreview.hideDelay`
### Paste Options
* Options controlling paste are now passed as a `'paste'` object inside the outer options object.
* `forcePlainText` -> `paste.forcePlainText`
* `cleanPastedHTML` -> `paste.cleanPastedHTML`
### Placeholder Options
* Options controlling the placeholder extension are now passed as a `'placeholder'` object inside the outer options object.
* `placeholder` -> `placeholder.text`
### Other Options
#### `disableToolbar`
* Disabling the toolbar extension is now done by setting the `toolbar` option to `false`
#### `disableAnchorPreview`
* Disabling the anchor preview extension is now done by setting the `anchorPreview` option to `false`
#### `disablePlaceholders`
* Disabling the placeholder extension is now done by setting the `placeholder` option to `false`
#### `onShowToolbar` & `onHideToolbar`
* The `onShowToolbar` and `onHideToolbar` options are no longer supported. Instead, attach to the `'showToolbar'` and `'hideToolbar'` [custom events](https://github.com/yabwe/medium-editor/wiki/Custom-Events) via `MediumEditor.subscribe()`
#### `firstHeader` & `secondHeader`
* The `firstHeader` & `secondHeader` options have been removed. Instead, any number of the 6 header types can be passed as button names into the `toolbar.buttons` option array.
* Example: Where before the code may have sent `firstHeader: 'h2'` and `secondHeader: 'h3'`, it should now pass `['bold', 'italic', 'quote', 'h2', 'h3']` via the `toolbar.buttons` property of the MediumEditor options object.
#### `buttonLabels`
* The `buttonLabels` option no longer supports taking an object in that specifies custom labels for all buttons. Instead, pass an object into the `toolbar.buttons` option array that contains a `.name` property for the name of the button, a either a `contentDefault` or a `contentFA` property that should be in the innerHTML of the button (for default of `fontawesome` buttonLabels respectively)
## MediumEditor Extensions
#### `.parent`
* `Extension.parent` is no longer supported. All extensions will have a reference to the MediumEditor instance via their `.base` property, unless the property already exists.
#### `.init()`
* `Extension.init()` will no longer be passed any arguments. Previously, `.init(instance)` received an instance of MediumEditor as an argument, but this is not needed now that `.base` will be populated before `.init()` is called.
#### `.deactivate()`
* `Extension.deactivate()` will no longer be called by MediumEditor. `.destroy()` will be called instead when MediumEditor is destroyed.
#### `.options`
* The `.options` property of any built-in extensions or buttons has been removed. All of the properties should be retrieved and set from the prototype of the object itself.
* Example: Instead of buttons using `this.options.action`, they should now use `this.action`.
* Not all extensions had options before, or saved them via the `.options` property.
## MediumEditor API
#### `.id`
* The unique identifier used for MediumEditor elements will now remain unique and remain regardless of how many instances are created. After calling `.destroy()` and `.setup()`, the id will remain the same. This id was used to generate unique element ids for things like the id attribute of the toolbar element (`'medium-editor-toolbar-[ID]'`)
#### `.toolbar`
* The MediumEditor toolbar is now an extension, so `MediumEditor.toolbar` is no longer a valid reference. Use `MediumEditor.getExtensionByName('toolbar')` instead.
#### `.statics`
* All of the `.statics` references have been removed as the new style of extensions and buttons has been introduced. The objects exposed via `.statics` have also been changed, so code which uses them may require additonal changes.
* `MediumEditor.statics.ButtonsData` -> `MediumEditor.extensions.button.prototype.defaults` (ideally this reference should no longer be needed)
* `MediumEditor.statics.DefaultButton` -> `MediumEditor.extensions.button`
* `MediumEditor.statics.AnchorExtension` -> `MediumEditor.extensions.anchor`
* `MediumEditor.statics.FontSizeExtension` -> `MediumEditor.extensions.fontSize`
* `MediumEditor.statics.Toolbar` -> `MediumEditor.extensions.toolbar`
* `MediumEditor.statics.AnchorPreview` -> `MediumEditor.extensions.anchorPreview`
#### `.activate()`
* `MediumEditor.activate()` has been replaced with `MediumEditor.setup()`
#### `.deactivate()`
* `MediumEditor.deactivate()` has been replaced with `MediumEditor.destroy()`
#### `.createEvent()`
* `MediumEditor.createEvent()` is no longer needed in order to fire custom events. It has been removed.
#### `.hideToolbarDefaultActions()`
* `MediumEditor.hideToolbarDefaultActions()` has been removed. Use the `hideToolbarDefaultActions()` method of the toolbar extension instead.
#### `.setToolbarPosition()`
* `MediumEditor.setToolbarPosition()` has been removed. Use the `setToolbarPosition()` method of the toolbar extension instead.
#### `.callExtensions()`
* `MediumEditor.callExtensions()` has been removed and is no longer supported.
## MediumEditor Utility Methods
### MediumEditor.util
* `MediumEditor.util.getSelectionRange()` has been moved to `MediumEditor.selection.getSelectionRange()`
* `MediumEditor.util.getSelectionStart()` has been moved to `MediumEditor.selection.getSelectionStart()`
* `MediumEditor.util.unwrapElement()` has been removed. Use `MediumEditor.util.unwrap()` instead
* `MediumEditor.util.getSelectionData()` has been removed
* `MediumEditor.util.setObject()` has been removed
* `MediumEditor.util.getObject()` has been removed
* `MediumEditor.util.derives()` has been removed. Objects that can be drived from (like extensions and buttons) will have a `.extend()` method for extending.
* `MediumEditor.util.now()` has been removed. Use `Date.now()` instead.
* `MediumEditor.util.parentElements` has been renamed `MediumEditor.util.blockContainerElementNames`
### MediumEditor.selection
* `MediumEditor.selection.getSelectionData()` has been removed
## MediumEditor CSS & Markup
* The `.clearfix` class has been removed, and `.clearfix` class is no longer added to the toolbar element.
* All references to `'medium'` in CSS classes has been replaced with `'medium-editor'`
* Example: The image element added by the image dragging extension will now have a `medium-editor-image-loading` class on instead of `medium-image-loading`
* The `data-medium-element` attribute on all MediumEditor elements has been renamed to `data-medium-editor-element`
* Toolbar classes `sticky-toolbar` and `static-toolbar` have been renamed `medium-editor-sticky-toolbar` and `medium-editor-static-toolbar` respectively
## Other Changes
* The `getFocusedElement()` method of the toolbar extension has been removed. Use `MediumEditor.getFocusedElement()` instead.
* Keyboard Shortcuts are now controlled via the Keyboard Commands extension. The `.key` option on buttons is not longer supported for mapping keyboard shortcuts.
================================================
FILE: bower.json
================================================
{
"name": "medium-editor",
"homepage": "http://yabwe.github.io/medium-editor/",
"authors": [
"Davi Ferreira <hi@daviferreira.com>",
"Nate Mielnik <nathan@outlook.com>",
"Noah Chase <nchase@gmail.com>",
"Jeremy Benoist <jeremy.benoist@gmail.com>"
],
"description": "Medium.com WYSIWYG editor clone written in pure JavaScript.",
"main": ["dist/js/medium-editor.js",
"dist/css/medium-editor.css",
"dist/css/themes/default.css"],
"keywords": [
"contenteditable",
"wysiwyg",
"medium",
"rich-text",
"editor"
],
"license": "MIT",
"ignore": [
"**/.*",
"node_modules",
"bower_components",
"spec",
"coverage",
"reports",
"_SpecRunner.html",
"Gruntfile.js",
"demo",
"package.json",
"CHANGES.md",
"MAINTAINERS.md",
"CODE_OF_CONDUCT.md",
"CONTRIBUTING.md",
"UPGRADE-5.md"
]
}
================================================
FILE: demo/absolute-container.html
================================================
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>medium editor | demo</title>
<link rel="stylesheet" href="css/demo.css">
<link rel="stylesheet" href="http://netdna.bootstrapcdn.com/font-awesome/4.1.0/css/font-awesome.css">
<link rel="stylesheet" href="../dist/css/medium-editor.css">
<link rel="stylesheet" href="../dist/css/themes/default.css" id="medium-editor-theme">
<style>
body {
height: 100%;
}
#container {
position: absolute;
left: 50%;
margin-left: -480px;
height: 100%;
overflow: auto;
}
</style>
</head>
<body>
<a href="https://github.com/yabwe/medium-editor" class="github-link"><img style="z-index: 100;position: absolute; top: 0; right: 0; border: 0;" src="https://s3.amazonaws.com/github/ribbons/forkme_right_gray_6d6d6d.png" alt="Fork me on GitHub"></a>
<div class="top-bar">
Theme:
<select id="sel-themes">
<option value="themes/default" selected>default</option>
<option value="themes/roman">roman</option>
<option value="themes/mani">mani</option>
<option value="themes/flat">flat</option>
<option value="themes/bootstrap">bootstrap</option>
<option value="themes/tim">tim</option>
<option value="themes/beagle">beagle</option>
</select>
</div>
<div id="container">
<h1>Medium Editor</h1>
<div class="editable">
<p>My father’s family name being <a href="https://en.wikipedia.org/wiki/Pip_(Great_Expectations)">Pirrip</a>, and my Christian name Philip, my infant tongue could make of both names nothing longer or more explicit than Pip. So, I called myself Pip, and came to be called Pip.</p>
<p>I give Pirrip as my father’s family name, on the authority of his tombstone and my sister,—Mrs. Joe Gargery, who married the blacksmith. As I never saw my father or my mother, and never saw any likeness of either of them (for their days were long before the days of photographs), my first fancies regarding what they were like were unreasonably derived from their tombstones. The shape of the letters on my father’s, gave me an odd idea that he was a square, stout, dark man, with curly black hair. From the character and turn of the inscription, “Also Georgiana Wife of the Above,” I drew a childish conclusion that my mother was freckled and sickly. To five little stone lozenges, each about a foot and a half long, which were arranged in a neat row beside their grave, and were sacred to the memory of five little brothers of mine,—who gave up trying to get a living, exceedingly early in that universal struggle,—I am indebted for a belief I religiously entertained that they had all been born on their backs with their hands in their trousers-pockets, and had never taken them out in this state of existence.</p>
<p>Ours was the marsh country, down by the river, within, as the river wound, twenty miles of the sea. My first most vivid and broad impression of the identity of things seems to me to have been gained on a memorable raw afternoon towards evening. At such a time I found out for certain that this bleak place overgrown with nettles was the churchyard; and that Philip Pirrip, late of this parish, and also Georgiana wife of the above, were dead and buried; and that Alexander, Bartholomew, Abraham, Tobias, and Roger, infant children of the aforesaid, were also dead and buried; and that the dark flat wilderness beyond the churchyard, intersected with dikes and mounds and gates, with scattered cattle feeding on it, was the marshes; and that the low leaden line beyond was the river; and that the distant savage lair from which the wind was rushing was the sea; and that the small bundle of shivers growing afraid of it all and beginning to cry, was Pip.</p>
</div>
</div>
<p style="text-align: center;"><small><a style="color: #333;" target="_blank" href="http://www.goodreads.com/reader/475-great-expectations">Source</a></small></p>
<script src="../dist/js/medium-editor.js"></script>
<script>
var editor = new MediumEditor('.editable', {
buttonLabels: 'fontawesome',
elementsContainer: document.getElementById('container')
}),
cssLink = document.getElementById('medium-editor-theme');
document.getElementById('sel-themes').addEventListener('change', function () {
cssLink.href = '../dist/css/' + this.value + '.css';
});
</script>
</body>
</html>
================================================
FILE: demo/auto-link.html
================================================
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>medium editor | demo</title>
<link rel="stylesheet" href="css/demo.css">
<link rel="stylesheet" href="http://netdna.bootstrapcdn.com/font-awesome/4.1.0/css/font-awesome.css">
<link rel="stylesheet" href="../dist/css/medium-editor.css">
<link rel="stylesheet" href="../dist/css/themes/default.css" id="medium-editor-theme">
</head>
<body>
<a href="https://github.com/yabwe/medium-editor" class="github-link"><img style="z-index: 100;position: absolute; top: 0; right: 0; border: 0;" src="https://s3.amazonaws.com/github/ribbons/forkme_right_gray_6d6d6d.png" alt="Fork me on GitHub"></a>
<div class="top-bar">
Theme:
<select id="sel-themes">
<option value="themes/default" selected>default</option>
<option value="themes/roman">roman</option>
<option value="themes/mani">mani</option>
<option value="themes/flat">flat</option>
<option value="themes/bootstrap">bootstrap</option>
</select>
</div>
<div id="container">
<h1>Medium Editor</h1>
<div class="editable">
<p>My father’s family name being <a href="https://en.wikipedia.org/wiki/Pip_(Great_Expectations)">Pirrip</a>, and my Christian name Philip, my infant tongue could make of both names nothing longer or more explicit than Pip. So, I called myself Pip, and came to be called Pip.</p>
<p>I give Pirrip as my father’s family name, on the authority of his tombstone and my sister,—Mrs. Joe Gargery, who married the blacksmith. As I never saw my father or my mother, and never saw any likeness of either of them (for their days were long before the days of photographs), my first fancies regarding what they were like were unreasonably derived from their tombstones. The shape of the letters on my father’s, gave me an odd idea that he was a square, stout, dark man, with curly black hair. From the character and turn of the inscription, “Also Georgiana Wife of the Above,” I drew a childish conclusion that my mother was freckled and sickly. To five little stone lozenges, each about a foot and a half long, which were arranged in a neat row beside their grave, and were sacred to the memory of five little brothers of mine,—who gave up trying to get a living, exceedingly early in that universal struggle,—I am indebted for a belief I religiously entertained that they had all been born on their backs with their hands in their trousers-pockets, and had never taken them out in this state of existence.</p>
<p>Ours was the marsh country, down by the river, within, as the river wound, twenty miles of the sea. My first most vivid and broad impression of the identity of things seems to me to have been gained on a memorable raw afternoon towards evening. At such a time I found out for certain that this bleak place overgrown with nettles was the churchyard; and that Philip Pirrip, late of this parish, and also Georgiana wife of the above, were dead and buried; and that Alexander, Bartholomew, Abraham, Tobias, and Roger, infant children of the aforesaid, were also dead and buried; and that the dark flat wilderness beyond the churchyard, intersected with dikes and mounds and gates, with scattered cattle feeding on it, was the marshes; and that the low leaden line beyond was the river; and that the distant savage lair from which the wind was rushing was the sea; and that the small bundle of shivers growing afraid of it all and beginning to cry, was Pip.</p>
<p><a href="http://google.com"><img src="https://placeholdit.imgix.net/~text?txtsize=33&txt=350%C3%97150&w=350&h=150"></a></p>
</div>
</div>
<p style="text-align: center;"><small><a style="color: #333;" target="_blank" href="http://www.goodreads.com/reader/475-great-expectations">Source</a></small></p>
<script src="../dist/js/medium-editor.js"></script>
<script>
var editor = new MediumEditor('.editable', {
buttonLabels: 'fontawesome',
autoLink: true,
toolbar: {
buttons: ['bold', 'italic', 'unorderedlist', 'orderedlist', 'anchor']
}
}),
cssLink = document.getElementById('medium-editor-theme');
document.getElementById('sel-themes').addEventListener('change', function () {
cssLink.href = '../dist/css/' + this.value + '.css';
});
</script>
</body>
</html>
================================================
FILE: demo/button-example.html
================================================
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>MediumEditor - Button Example</title>
<link rel="stylesheet" href="css/demo.css">
<link href="https://netdna.bootstrapcdn.com/font-awesome/4.2.0/css/font-awesome.css" rel="stylesheet">
<link rel="stylesheet" href="../dist/css/medium-editor.css">
<link rel="stylesheet" href="../dist/css/themes/bootstrap.css">
</head>
<body>
<div id="container">
<h1>Medium Editor</h1>
<div class="editable">
<h2>Font Awesome</h2>
<p>My father’s family name being <a href="https://en.wikipedia.org/wiki/Pip_(Great_Expectations)">Pirrip</a>, and my Christian name Philip, my infant tongue could make of both names nothing longer or more explicit than Pip. So, I called myself Pip, and came to be called Pip.</p>
<p>I give Pirrip as my father’s family name, on the authority of his tombstone and my sister,—Mrs. Joe Gargery, who married the blacksmith. As I never saw my father or my mother, and never saw any likeness of either of them (for their days were long before the days of photographs), my first fancies regarding what they were like were unreasonably derived from their tombstones...</p>
</div>
</div>
<p style="text-align: center;"><small><a style="color: #333;" target="_blank" href="http://www.goodreads.com/reader/475-great-expectations">Source</a></small></p>
<script src="../dist/js/medium-editor.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/rangy/1.3.0/rangy-core.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/rangy/1.3.0/rangy-classapplier.min.js"></script>
<script>
rangy.init();
var HighlighterButton = MediumEditor.extensions.button.extend({
name: 'highlighter',
tagNames: ['mark'],
contentDefault: '<b>H</b>',
contentFA: '<i class="fa fa-paint-brush"></i>',
aria: 'Highlight',
action: 'highlight',
init: function () {
MediumEditor.extensions.button.prototype.init.call(this);
this.classApplier = rangy.createClassApplier('highlight', {
elementTagName: 'mark',
normalize: true
});
},
handleClick: function (event) {
this.classApplier.toggleSelection();
// Ensure the editor knows about an html change so watchers are notified
// ie: <textarea> elements depend on the editableInput event to stay synchronized
this.base.checkContentChanged();
}
});
var editor = new MediumEditor('.editable', {
toolbar: {
buttons: ['bold', 'italic', 'underline', 'highlighter']
},
buttonLabels: 'fontawesome',
extensions: {
'highlighter': new HighlighterButton()
}
});
</script>
</body>
</html>
================================================
FILE: demo/clean-paste.html
================================================
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>medium editor | demo</title>
<link rel="stylesheet" href="css/demo.css">
<link rel="stylesheet" href="http://netdna.bootstrapcdn.com/font-awesome/4.1.0/css/font-awesome.css">
<link rel="stylesheet" href="../dist/css/medium-editor.css">
<link rel="stylesheet" href="../dist/css/themes/default.css">
</head>
<body>
<a href="https://github.com/yabwe/medium-editor"><img style="position: absolute; top: 0; right: 0; border: 0;" src="https://s3.amazonaws.com/github/ribbons/forkme_right_gray_6d6d6d.png" alt="Fork me on GitHub"></a>
<div id="container">
<h1>Medium Editor</h1>
<div class="editable">
<p>My father’s family name being <a href="https://en.wikipedia.org/wiki/Pip_(Great_Expectations)">Pirrip</a>, and my Christian name Philip, my infant tongue could make of both names nothing longer or more explicit than Pip. So, I called myself Pip, and came to be called Pip.</p>
<p>I give Pirrip as my father’s family name, on the authority of his tombstone and my sister,—Mrs. Joe Gargery, who married the blacksmith. As I never saw my father or my mother, and never saw any likeness of either of them (for their days were long before the days of photographs), my first fancies regarding what they were like were unreasonably derived from their tombstones. The shape of the letters on my father’s, gave me an odd idea that he was a square, stout, dark man, with curly black hair. From the character and turn of the inscription, “Also Georgiana Wife of the Above,” I drew a childish conclusion that my mother was freckled and sickly. To five little stone lozenges, each about a foot and a half long, which were arranged in a neat row beside their grave, and were sacred to the memory of five little brothers of mine,—who gave up trying to get a living, exceedingly early in that universal struggle,—I am indebted for a belief I religiously entertained that they had all been born on their backs with their hands in their trousers-pockets, and had never taken them out in this state of existence.</p>
<p>Ours was the marsh country, down by the river, within, as the river wound, twenty miles of the sea. My first most vivid and broad impression of the identity of things seems to me to have been gained on a memorable raw afternoon towards evening. At such a time I found out for certain that this bleak place overgrown with nettles was the churchyard; and that Philip Pirrip, late of this parish, and also Georgiana wife of the above, were dead and buried; and that Alexander, Bartholomew, Abraham, Tobias, and Roger, infant children of the aforesaid, were also dead and buried; and that the dark flat wilderness beyond the churchyard, intersected with dikes and mounds and gates, with scattered cattle feeding on it, was the marshes; and that the low leaden line beyond was the river; and that the distant savage lair from which the wind was rushing was the sea; and that the small bundle of shivers growing afraid of it all and beginning to cry, was Pip.</p>
</div>
</div>
<p style="text-align: center;"><small><a style="color: #333;" target="_blank" href="http://www.goodreads.com/reader/475-great-expectations">Source</a></small></p>
<script src="../dist/js/medium-editor.js"></script>
<script>
var editor = new MediumEditor('.editable', {
buttonLabels: 'fontawesome',
paste: {
cleanPastedHTML: true,
forcePlainText: false
}
});
</script>
</body>
</html>
================================================
FILE: demo/css/demo.css
================================================
*:focus {
outline: none;
}
body {
font-family: Helvetica, Arial, sans-serif;
font-size: 22px;
line-height: 30px;
}
.top-bar {
position: fixed;
top: 0;
left: 0;
width: auto;
z-index: 10;
padding: 10px;
background-color: #000;
background-color: rgba(0, 0, 0, .8);
box-shadow: 0 0 4px #000;
box-sizing: border-box;
color: #ccc;
font-size: 12px;
font-weight: bold;
text-align: center;
text-transform: uppercase;
}
h1 {
font-size: 60px;
font-weight: bold;
text-align: center;
margin-bottom: 40px;
padding-bottom: 40px;
letter-spacing: -2px;
border-bottom: 1px solid #dbdbdb;
}
h2 {
font-size: 32px;
line-height: 42px;
}
h3 {
font-size: 26px;
line-height: 32px;
}
h4 {
font-size: 24px;
line-height: 28px;
}
p {
margin-bottom: 40px;
}
a {
color:black;
}
a:hover {
color:green;
}
pre {
font-family: 'Menlo', monospace;
font-size: 15px;
background-color: #f0f0f0;
padding: 15px;
border: 1px solid #ccc;
border-radius: 5px;
color: #666;
}
blockquote {
display: block;
padding-left: 20px;
border-left: 6px solid #df0d32;
margin-left: -15px;
padding-left: 15px;
font-style: italic;
color: #555;
}
#container {
width: 960px;
margin: 30px auto;
}
#all-demos {
text-align: center;
border-bottom: 1px solid #dbdbdb;
padding-bottom: 40px;
}
.editable,
.secondEditable
{
outline: none;
margin: 0 0 20px 0;
padding: 0 0 20px 0;
border-bottom: 1px solid #dbdbdb;
}
#columns {
width: 90%;
margin: 30px auto;
}
.column-container {
}
.column {
vertical-align: top;
display: inline-block;
width: 30%;
margin: 10px 1%;
}
================================================
FILE: demo/custom-toolbar.html
================================================
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>medium editor | demo</title>
<link rel="stylesheet" href="css/demo.css">
<link href="http://netdna.bootstrapcdn.com/font-awesome/4.1.0/css/font-awesome.css" rel="stylesheet">
<link rel="stylesheet" href="../dist/css/medium-editor.css">
<link rel="stylesheet" href="../dist/css/themes/flat.css">
</head>
<body>
<a href="https://github.com/yabwe/medium-editor"><img style="position: absolute; top: 0; right: 0; border: 0;" src="https://s3.amazonaws.com/github/ribbons/forkme_right_gray_6d6d6d.png" alt="Fork me on GitHub"></a>
<div id="container">
<h1>Medium Editor</h1>
<div class="editable">
<h2>Font Awesome</h2>
<p>My father’s family name being <a href="https://en.wikipedia.org/wiki/Pip_(Great_Expectations)">Pirrip</a>, and my Christian name Philip, my infant tongue could make of both names nothing longer or more explicit than Pip. So, I called myself Pip, and came to be called Pip.</p>
<p>I give Pirrip as my father’s family name, on the authority of his tombstone and my sister,—Mrs. Joe Gargery, who married the blacksmith. As I never saw my father or my mother, and never saw any likeness of either of them (for their days were long before the days of photographs), my first fancies regarding what they were like were unreasonably derived from their tombstones...</p>
</div>
<div class="secondEditable" id="c">
<h2>Custom Labels </h2>
<p>... Ours was the marsh country, down by the river, within, as the river wound, twenty miles of the sea. My first most vivid and broad impression of the identity of things seems to me to have been gained on a memorable raw afternoon towards evening. At such a time I found out for certain that this bleak place overgrown with nettles was the churchyard; and that Philip Pirrip, late of this parish, and also Georgiana wife of the above, were dead and buried; and that Alexander, Bartholomew, Abraham, Tobias, and Roger, infant children of the aforesaid, were also dead and buried; and that the dark flat wilderness beyond the churchyard, intersected with dikes and mounds and gates, with scattered cattle feeding on it, was the marshes; and that the low leaden line beyond was the river; and that the distant savage lair from which the wind was rushing was the sea; and that the small bundle of shivers growing afraid of it all and beginning to cry, was Pip.</p>
</div>
</div>
<p style="text-align: center;"><small><a style="color: #333;" target="_blank" href="http://www.goodreads.com/reader/475-great-expectations">Source</a></small></p>
<script src="../dist/js/medium-editor.js"></script>
<script>
var editor = new MediumEditor('.editable', {
toolbar: {
buttons: ['bold', 'italic', 'underline', 'strikethrough', 'quote', 'anchor', 'image', 'justifyLeft', 'justifyCenter', 'justifyRight', 'justifyFull', 'superscript', 'subscript', 'orderedlist', 'unorderedlist', 'pre', 'removeFormat', 'outdent', 'indent', 'h2', 'h3', 'html'],
},
buttonLabels: 'fontawesome',
anchor: {
targetCheckbox: true
}
});
var editor2 = new MediumEditor('.secondEditable', {
toolbar: {
buttons: [{
name: 'bold',
contentDefault: 'bold'
},
{
name: 'italic',
contentDefault: '<i>italic</i>'
},
{
name: 'underline',
contentDefault: '<u>underline</u>'
},
{
name: 'anchor',
contentDefault: 'link'
}
]
}
});
</script>
</body>
</html>
================================================
FILE: demo/extension-example.html
================================================
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>MediumEditor - Extension Example</title>
<link rel="stylesheet" href="css/demo.css">
<link rel="stylesheet" href="../dist/css/medium-editor.css">
<link rel="stylesheet" href="../dist/css/themes/default.css">
</head>
<body>
<div id="container">
<h1>Medium Editor</h1>
<div class="editable">
<h2>Font Awesome</h2>
<p>My father’s family name being <a href="https://en.wikipedia.org/wiki/Pip_(Great_Expectations)">Pirrip</a>, and my Christian name Philip, my infant tongue could make of both names nothing longer or more explicit than Pip. So, I called myself Pip, and came to be called Pip.</p>
<p>I give Pirrip as my father’s family name, on the authority of his tombstone and my sister,—Mrs. Joe Gargery, who married the blacksmith. As I never saw my father or my mother, and never saw any likeness of either of them (for their days were long before the days of photographs), my first fancies regarding what they were like were unreasonably derived from their tombstones...</p>
</div>
</div>
<p style="text-align: center;"><small><a style="color: #333;" target="_blank" href="http://www.goodreads.com/reader/475-great-expectations">Source</a></small></p>
<script src="../dist/js/medium-editor.js"></script>
<script>
var DisableContextMenuExtension = MediumEditor.Extension.extend({
name: 'disable-context-menu',
init: function () {
this.getEditorElements().forEach(function (element) {
this.on(element, 'contextmenu', this.handleContextmenu.bind(this));
}, this);
this.subscribe('editableKeydown', this.handleKeydown.bind(this));
},
handleContextmenu: function (event) {
if (!event.currentTarget.getAttribute('data-allow-context-menu')) {
event.preventDefault();
}
},
handleKeydown: function (event, editable) {
// If the user hits escape, toggle the data-allow-context-menu attribute
if (MediumEditor.util.isKey(event, MediumEditor.util.keyCode.ESCAPE)) {
if (editable.hasAttribute('data-allow-context-menu')) {
editable.removeAttribute('data-allow-context-menu');
} else {
editable.setAttribute('data-allow-context-menu', true);
}
}
}
});
var editor = new MediumEditor('.editable', {
extensions: {
'disable-context-menu': new DisableContextMenuExtension()
}
});
</script>
</body>
</html>
================================================
FILE: demo/index.html
================================================
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>medium editor | demo</title>
<link rel="stylesheet" href="css/demo.css">
<link rel="stylesheet" href="http://netdna.bootstrapcdn.com/font-awesome/4.1.0/css/font-awesome.css">
<link rel="stylesheet" href="../dist/css/medium-editor.css">
<link rel="stylesheet" href="../dist/css/themes/default.css" id="medium-editor-theme">
</head>
<body>
<a href="https://github.com/yabwe/medium-editor" class="github-link"><img style="z-index: 100;position: absolute; top: 0; right: 0; border: 0;" src="https://s3.amazonaws.com/github/ribbons/forkme_right_gray_6d6d6d.png" alt="Fork me on GitHub"></a>
<div class="top-bar">
Theme:
<select id="sel-themes">
<option value="themes/default" selected>default</option>
<option value="themes/roman">roman</option>
<option value="themes/mani">mani</option>
<option value="themes/flat">flat</option>
<option value="themes/bootstrap">bootstrap</option>
<option value="themes/tim">tim</option>
<option value="themes/beagle">beagle</option>
</select>
</div>
<div id="container">
<h1>Medium Editor</h1>
<div id="all-demos">
<a href="./absolute-container.html">Absolute Container</a> •
<a href="./auto-link.html">Auto Link</a> •
<a href="./button-example.html">Button Example</a> •
<a href="./clean-paste.html">Clean Paste</a> •
<a href="./custom-toolbar.html">Custom Toolbar</a> •
<a href="./extension-example.html">Extension Example</a> •
<a href="./multi-editor.html">Multi Editor</a> •
<a href="./multi-one-instance.html">Multi One Instance</a> •
<a href="./multi-paragraph.html">Multi Paragraph</a> •
<a href="./nested-editable.html">Nested Editable</a> •
<a href="./pass-instance.html">Pass Instance</a> •
<a href="./relative-toolbar.html">Relative Toolbar</a> •
<a href="./static-toolbar.html">Static Toolbar</a> •
<a href="./table-extension.html">Table Extension</a> •
<a href="./textarea.html">Textarea</a>
</div>
<div class="editable">
<p>My father’s family name being <a href="https://en.wikipedia.org/wiki/Pip_(Great_Expectations)">Pirrip</a>, and my Christian name Philip, my infant tongue could make of both names nothing longer or more explicit than Pip. So, I called myself Pip, and came to be called Pip.</p>
<p>I give Pirrip as my father’s family name, on the authority of his tombstone and my sister,—Mrs. Joe Gargery, who married the blacksmith. As I never saw my father or my mother, and never saw any likeness of either of them (for their days were long before the days of photographs), my first fancies regarding what they were like were unreasonably derived from their tombstones. The shape of the letters on my father’s, gave me an odd idea that he was a square, stout, dark man, with curly black hair. From the character and turn of the inscription, “Also Georgiana Wife of the Above,” I drew a childish conclusion that my mother was freckled and sickly. To five little stone lozenges, each about a foot and a half long, which were arranged in a neat row beside their grave, and were sacred to the memory of five little brothers of mine,—who gave up trying to get a living, exceedingly early in that universal struggle,—I am indebted for a belief I religiously entertained that they had all been born on their backs with their hands in their trousers-pockets, and had never taken them out in this state of existence.</p>
<p>Ours was the marsh country, down by the river, within, as the river wound, twenty miles of the sea. My first most vivid and broad impression of the identity of things seems to me to have been gained on a memorable raw afternoon towards evening. At such a time I found out for certain that this bleak place overgrown with nettles was the churchyard; and that Philip Pirrip, late of this parish, and also Georgiana wife of the above, were dead and buried; and that Alexander, Bartholomew, Abraham, Tobias, and Roger, infant children of the aforesaid, were also dead and buried; and that the dark flat wilderness beyond the churchyard, intersected with dikes and mounds and gates, with scattered cattle feeding on it, was the marshes; and that the low leaden line beyond was the river; and that the distant savage lair from which the wind was rushing was the sea; and that the small bundle of shivers growing afraid of it all and beginning to cry, was Pip.</p>
</div>
</div>
<p style="text-align: center;"><small><a style="color: #333;" target="_blank" href="http://www.goodreads.com/reader/475-great-expectations">Source</a></small></p>
<script src="../dist/js/medium-editor.js"></script>
<script>
var editor = new MediumEditor('.editable', {
buttonLabels: 'fontawesome'
}),
cssLink = document.getElementById('medium-editor-theme');
document.getElementById('sel-themes').addEventListener('change', function () {
cssLink.href = '../dist/css/' + this.value + '.css';
});
</script>
</body>
</html>
================================================
FILE: demo/js/extension-table.js
================================================
var TableExtension = MediumEditor.extensions.anchor.extend({
name: 'table',
action: 'createTable',
aria: 'table',
tagNames: ['table'],
contentDefault: '<b>T</b>',
contentFA: '<i class="fa fa-table"></i>',
doFormSave: function () {
var columnCount = this.getColumnsInput().value,
rowCount = this.getRowsInput().value,
table = this.createTable(columnCount, rowCount);
// Restore Medium Editor's selection before pasting HTML
this.base.restoreSelection();
// Paste newly created table.
this.base.pasteHTML(table.innerHTML);
// Update toolbar -> hide this form
this.base.checkSelection();
},
createTable: function (cols, rows) {
var doc = this.base.options.ownerDocument,
table = doc.createElement('table'),
header = doc.createElement('thead'),
headerRow = doc.createElement('tr'),
body = doc.createElement('tbody'),
wrap = doc.createElement('div'),
h, r, c, headerCol, bodyRow, bodyCol;
for (h = 1; h <= cols; h++) {
headerCol = doc.createElement('th');
headerCol.innerHTML = '...';
headerRow.appendChild(headerCol);
}
header.appendChild(headerRow);
for (r = 1; r <= rows; r++) {
bodyRow = doc.createElement('tr');
for (c = 1; c <= cols; c++) {
bodyCol = doc.createElement('td');
bodyCol.innerHTML = '...';
bodyRow.appendChild(bodyCol);
}
body.appendChild(bodyRow);
}
table.appendChild(header);
table.appendChild(body);
wrap.appendChild(table);
return wrap;
},
// Called when the button the toolbar is clicked
// Overrides DefaultButton.handleClick
handleClick: function (evt) {
evt.preventDefault();
evt.stopPropagation();
if (!this.isDisplayed()) {
this.showForm();
}
return false;
},
hideForm: function () {
this.getColumnsInput().value = '';
this.getRowsInput().value = '';
this.getForm().style.display = 'none';
},
showForm: function () {
var colsInput = this.getColumnsInput(),
rowsInput = this.getRowsInput();
this.base.saveSelection();
this.hideToolbarDefaultActions();
this.getForm().style.display = 'block';
this.setToolbarPosition();
colsInput.focus();
},
createForm: function () {
var doc = this.base.options.ownerDocument,
form = doc.createElement('div'),
close = doc.createElement('a'),
save = doc.createElement('a'),
columnInput = doc.createElement('input'),
rowInput = doc.createElement('input');
form.className = 'medium-editor-toolbar-form';
form.id = 'medium-editor-toolbar-form-table-' + this.base.id;
// Handle clicks on the form itself
this.base.on(form, 'click', this.handleFormClick.bind(this));
// Add columns textbox
columnInput.setAttribute('type', 'text');
columnInput.className = 'medium-editor-toolbar-input medium-editor-toolbar-input-columns';
columnInput.setAttribute('placeholder', 'Column Count');
form.appendChild(columnInput);
// Add rows textbox
rowInput.setAttribute('type', 'text');
rowInput.className = 'medium-editor-toolbar-input medium-editor-toolbar-input-rows';
rowInput.setAttribute('placeholder', 'Row Count');
form.appendChild(rowInput);
// Handle typing in the textboxes
this.base.on(columnInput, 'keyup', this.handleTextboxKeyup.bind(this));
this.base.on(rowInput, 'keyup', this.handleTextboxKeyup.bind(this));
// Add save buton
save.setAttribute('href', '#');
save.className = 'medium-editor-toolbar-save';
save.innerHTML = this.base.options.buttonLabels === 'fontawesome' ?
'<i class="fa fa-check"></i>' :
'✓';
form.appendChild(save);
// Handle save button clicks (capture)
this.base.on(save, 'click', this.handleSaveClick.bind(this), true);
// Add close button
close.setAttribute('href', '#');
close.className = 'medium-editor-toolbar-close';
close.innerHTML = this.base.options.buttonLabels === 'fontawesome' ?
'<i class="fa fa-times"></i>' :
'×';
form.appendChild(close);
// Handle close button clicks
this.base.on(close, 'click', this.handleCloseClick.bind(this));
return form;
},
getColumnsInput: function () {
return this.getForm().querySelector('input.medium-editor-toolbar-input-columns');
},
getRowsInput: function () {
return this.getForm().querySelector('input.medium-editor-toolbar-input-rows');
}
})
================================================
FILE: demo/multi-editor.html
================================================
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>medium editor | demo</title>
<link rel="stylesheet" href="css/demo.css">
<link rel="stylesheet" href="../dist/css/medium-editor.css">
<link rel="stylesheet" href="../dist/css/themes/default.css">
</head>
<body>
<a href="https://github.com/yabwe/medium-editor"><img style="position: absolute; top: 0; right: 0; border: 0;" src="https://s3.amazonaws.com/github/ribbons/forkme_right_gray_6d6d6d.png" alt="Fork me on GitHub"></a>
<div id="container">
<h1>Medium Multi Editor</h1>
<div class="editable" id="a">
<h2>First Editor </h2>
<p>This text is a <b>paragraph</b> with some <a href="http://www.w3schools.com/jsref/dom_obj_all.asp"><i>tags elements</i></a></p>
</div>
<div class="editable" id="b">
<h2>Second Editor </h2>
<p>This text is another paragraph in the same instance.</p>
</div>
<div class="secondEditable" id="c">
<h2>Third Editor in another instance </h2>
<p>This text is another paragraph in another editor instance.<br>
</p>
</div>
<div class="editable" id="d" data-disable-toolbar="true">
<h2>Disabled Toolbar Editor</h2>
<p>This text is a paragraph in a<br> <i> data-disable-toolbar="true"</i></p>
</div>
<div id="d">
<h2>Non editable Div</h2>
<p>This text is another paragraph is a normal div.</p>
</div>
</div>
<script src="../dist/js/medium-editor.js"></script>
<script>
var editor = new MediumEditor('.editable'),
editor2 = new MediumEditor('.secondEditable', {
toolbar: {
buttons: ['bold', 'italic', 'quote', 'pre']
}
});
</script>
</body>
</html>
================================================
FILE: demo/multi-one-instance.html
================================================
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>medium editor | demo</title>
<link rel="stylesheet" href="css/demo.css">
<link rel="stylesheet" href="http://netdna.bootstrapcdn.com/font-awesome/4.1.0/css/font-awesome.css">
<link rel="stylesheet" href="../dist/css/medium-editor.css">
<link rel="stylesheet" href="../dist/css/themes/default.css" id="medium-editor-theme">
</head>
<body>
<a href="https://github.com/yabwe/medium-editor" class="github-link"><img style="z-index: 100;position: absolute; top: 0; right: 0; border: 0;" src="https://s3.amazonaws.com/github/ribbons/forkme_right_gray_6d6d6d.png" alt="Fork me on GitHub"></a>
<div class="top-bar">
Theme:
<select id="sel-themes">
<option value="themes/default" selected>default</option>
<option value="themes/roman">roman</option>
<option value="themes/mani">mani</option>
<option value="themes/flat">flat</option>
<option value="themes/bootstrap">bootstrap</option>
<option value="themes/tim">tim</option>
<option value="themes/beagle">beagle</option>
</select>
</div>
<div id="container">
<h1>Medium Editor</h1>
<div class="editable">
<p>My father’s family name being <a href="https://en.wikipedia.org/wiki/Pip_(Great_Expectations)">Pirrip</a>, and my Christian name Philip, my infant tongue could make of both names nothing longer or more explicit than Pip. So, I called myself Pip, and came to be called Pip.</p>
<p>I give Pirrip as my father’s family name, on the authority of his tombstone and my sister,—Mrs. Joe Gargery, who married the blacksmith. As I never saw my father or my mother, and never saw any likeness of either of them (for their days were long before the days of photographs), my first fancies regarding what they were like were unreasonably derived from their tombstones. The shape of the letters on my father’s, gave me an odd idea that he was a square, stout, dark man, with curly black hair. From the character and turn of the inscription, “Also Georgiana Wife of the Above,” I drew a childish conclusion that my mother was freckled and sickly. To five little stone lozenges, each about a foot and a half long, which were arranged in a neat row beside their grave, and were sacred to the memory of five little brothers of mine,—who gave up trying to get a living, exceedingly early in that universal struggle,—I am indebted for a belief I religiously entertained that they had all been born on their backs with their hands in their trousers-pockets, and had never taken them out in this state of existence.</p>
<p>Ours was the marsh country, down by the river, within, as the river wound, twenty miles of the sea. My first most vivid and broad impression of the identity of things seems to me to have been gained on a memorable raw afternoon towards evening. At such a time I found out for certain that this bleak place overgrown with nettles was the churchyard; and that Philip Pirrip, late of this parish, and also Georgiana wife of the above, were dead and buried; and that Alexander, Bartholomew, Abraham, Tobias, and Roger, infant children of the aforesaid, were also dead and buried; and that the dark flat wilderness beyond the churchyard, intersected with dikes and mounds and gates, with scattered cattle feeding on it, was the marshes; and that the low leaden line beyond was the river; and that the distant savage lair from which the wind was rushing was the sea; and that the small bundle of shivers growing afraid of it all and beginning to cry, was Pip.</p>
</div>
<div class="editable2">
<p>My father’s family name being <a href="https://en.wikipedia.org/wiki/Pip_(Great_Expectations)">Pirrip</a>, and my Christian name Philip, my infant tongue could make of both names nothing longer or more explicit than Pip. So, I called myself Pip, and came to be called Pip.</p>
<p>I give Pirrip as my father’s family name, on the authority of his tombstone and my sister,—Mrs. Joe Gargery, who married the blacksmith. As I never saw my father or my mother, and never saw any likeness of either of them (for their days were long before the days of photographs), my first fancies regarding what they were like were unreasonably derived from their tombstones. The shape of the letters on my father’s, gave me an odd idea that he was a square, stout, dark man, with curly black hair. From the character and turn of the inscription, “Also Georgiana Wife of the Above,” I drew a childish conclusion that my mother was freckled and sickly. To five little stone lozenges, each about a foot and a half long, which were arranged in a neat row beside their grave, and were sacred to the memory of five little brothers of mine,—who gave up trying to get a living, exceedingly early in that universal struggle,—I am indebted for a belief I religiously entertained that they had all been born on their backs with their hands in their trousers-pockets, and had never taken them out in this state of existence.</p>
<p>Ours was the marsh country, down by the river, within, as the river wound, twenty miles of the sea. My first most vivid and broad impression of the identity of things seems to me to have been gained on a memorable raw afternoon towards evening. At such a time I found out for certain that this bleak place overgrown with nettles was the churchyard; and that Philip Pirrip, late of this parish, and also Georgiana wife of the above, were dead and buried; and that Alexander, Bartholomew, Abraham, Tobias, and Roger, infant children of the aforesaid, were also dead and buried; and that the dark flat wilderness beyond the churchyard, intersected with dikes and mounds and gates, with scattered cattle feeding on it, was the marshes; and that the low leaden line beyond was the river; and that the distant savage lair from which the wind was rushing was the sea; and that the small bundle of shivers growing afraid of it all and beginning to cry, was Pip.</p>
</div>
</div>
<p style="text-align: center;"><small><a style="color: #333;" target="_blank" href="http://www.goodreads.com/reader/475-great-expectations">Source</a></small></p>
<script src="../dist/js/medium-editor.js"></script>
<script>
var editor = new MediumEditor('.editable', {
buttonLabels: 'fontawesome'
}),
cssLink = document.getElementById('medium-editor-theme');
editor.subscribe('editableInput', function(event, editable) {
console.info('editableInput fired!', editable);
});
document.getElementById('sel-themes').addEventListener('change', function () {
cssLink.href = '../dist/css/' + this.value + '.css';
});
editor.addElements(document.querySelector('.editable2'));
</script>
</body>
</html>
================================================
FILE: demo/multi-paragraph.html
================================================
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>medium editor | demo multi paragraph</title>
<link rel="stylesheet" href="css/demo.css">
<link rel="stylesheet" href="../dist/css/medium-editor.css">
<link rel="stylesheet" href="../dist/css/themes/default.css">
</head>
<body>
<a href="https://github.com/yabwe/medium-editor"><img style="position: absolute; top: 0; right: 0; border: 0;" src="https://s3.amazonaws.com/github/ribbons/forkme_right_gray_6d6d6d.png" alt="Fork me on GitHub"></a>
<div id="container">
<h1>Medium Editor Demo</h1>
<div class="editable" data-placeholder="Type some text" spellcheck="false">
<p><b>In this demo the toolbar will not appear if you select several paragraphs or block quotes.</b></p>
<p><h2>First Paragraph</h2> Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam ultrices ullamcorper nibh, ut imperdiet arcu rutrum et. Vestibulum vitae orci metus. Praesent dapibus interdum purus, vitae mattis urna pharetra a. Praesent sodales volutpat mi et rhoncus. Phasellus quis tortor nulla. Pellentesque dapibus lorem et eros lobortis, et iaculis est accumsan. Nunc et ligula laoreet, egestas est id, placerat dolor. Praesent sed gravida tortor, non elementum enim. Integer et nulla sit amet orci suscipit sagittis pellentesque a est. Maecenas vitae purus odio. Aenean tincidunt varius arcu a vehicula. Quisque vestibulum venenatis vestibulum. Nullam pellentesque purus non dui adipiscing tempus.</p>
<p><h2>Second Paragraph </h2>Pellentesque sit amet turpis a felis ornare euismod id vitae ante. Vivamus nec orci interdum, blandit dui id, gravida orci. Aliquam malesuada tristique imperdiet. Mauris lobortis, mi vel dictum feugiat, sem nibh hendrerit mi, eu pellentesque mauris quam et lorem. Integer ante ligula, placerat id pharetra ut, lacinia sit amet ante. Donec luctus, orci eu vestibulum suscipit, nisl lorem dignissim mauris, et auctor dolor odio nec mauris. Maecenas velit justo, lobortis a accumsan et, adipiscing sit amet leo. Vestibulum pharetra nisi erat, vitae dictum eros vestibulum eget. Ut aliquet lorem eu dui auctor aliquam. Nunc mollis elementum justo non ultricies. Nam dictum egestas augue sit amet ullamcorper. Praesent laoreet lectus ut velit porta varius. Vestibulum eget gravida sapien. Mauris viverra, metus vel varius posuere, augue metus aliquam sem, in pellentesque ipsum mauris non enim.</p>
<blockquote>This is a block quote. Suspendisse potenti. Vestibulum semper felis vitae sapien ultricies venenatis. Aliquam mollis dui dolor, in auctor urna iaculis dapibus. Nam condimentum mollis sapien, non bibendum lacus feugiat eu. Phasellus feugiat erat ut varius tincidunt. Quisque suscipit ornare lacus, nec dapibus lacus vulputate at. Morbi at ipsum sollicitudin, suscipit elit sed, ultrices ligula. Suspendisse tincidunt libero iaculis velit iaculis, ac congue enim euismod. Nam molestie ligula at mattis tincidunt. Praesent porttitor nisi lectus, nec suscipit lectus porttitor nec.</blockquote>
<p>Another paragrph. Suspendisse potenti. Vestibulum semper felis vitae sapien ultricies venenatis. Aliquam mollis dui dolor, in auctor urna iaculis dapibus. Nam condimentum mollis sapien, non bibendum lacus feugiat eu. Phasellus feugiat erat ut varius tincidunt. Quisque suscipit ornare lacus, nec dapibus lacus vulputate at. Morbi at ipsum sollicitudin, suscipit elit sed, ultrices ligula. Suspendisse tincidunt libero iaculis velit iaculis, ac congue enim euismod. Nam molestie ligula at mattis tincidunt. Praesent porttitor nisi lectus, nec suscipit lectus porttitor nec.</p>
</div>
</div>
<script src="../dist/js/medium-editor.js"></script>
<script>
var editor = new MediumEditor('.editable', {
delay: 0,
toolbar: {
diffTop: -12,
allowMultiParagraphSelection: false
},
anchor: {
placeholderText: 'Type a link'
}
});
</script>
</body>
</html>
==================
gitextract_iannmrpu/
├── .editorconfig
├── .github/
│ ├── ISSUE_TEMPLATE.md
│ └── PULL_REQUEST_TEMPLATE.md
├── .gitignore
├── .jscsrc
├── .jshintrc
├── .npmrc
├── .travis.yml
├── API.md
├── CHANGES.md
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── CUSTOM-EVENTS.md
├── Gruntfile.js
├── LICENSE
├── MAINTAINERS.md
├── OPTIONS.md
├── README.md
├── UPGRADE-5.md
├── bower.json
├── demo/
│ ├── absolute-container.html
│ ├── auto-link.html
│ ├── button-example.html
│ ├── clean-paste.html
│ ├── css/
│ │ └── demo.css
│ ├── custom-toolbar.html
│ ├── extension-example.html
│ ├── index.html
│ ├── js/
│ │ └── extension-table.js
│ ├── multi-editor.html
│ ├── multi-one-instance.html
│ ├── multi-paragraph.html
│ ├── nested-editable.html
│ ├── pass-instance.html
│ ├── relative-toolbar.html
│ ├── static-toolbar.html
│ ├── table-extension.html
│ └── textarea.html
├── dist/
│ ├── css/
│ │ ├── medium-editor.css
│ │ └── themes/
│ │ ├── beagle.css
│ │ ├── bootstrap.css
│ │ ├── default.css
│ │ ├── flat.css
│ │ ├── mani.css
│ │ ├── roman.css
│ │ └── tim.css
│ └── js/
│ └── medium-editor.js
├── index.js
├── karma.conf.js
├── karma.dev.conf.js
├── package.json
├── spec/
│ ├── anchor-preview.spec.js
│ ├── anchor.spec.js
│ ├── auto-link.spec.js
│ ├── buttons.spec.js
│ ├── content.spec.js
│ ├── core-api.spec.js
│ ├── delay.spec.js
│ ├── drag-and-drop.spec.js
│ ├── dyn-elements.spec.js
│ ├── elements.spec.js
│ ├── events.spec.js
│ ├── exploits.spec.js
│ ├── extension.spec.js
│ ├── fontname.spec.js
│ ├── fontsize.spec.js
│ ├── full-content.spec.js
│ ├── header-tags.spec.js
│ ├── helpers/
│ │ └── util.js
│ ├── init.spec.js
│ ├── keyboard-commands.spec.js
│ ├── paste.spec.js
│ ├── placeholder.spec.js
│ ├── selection.spec.js
│ ├── serialize.spec.js
│ ├── setup.spec.js
│ ├── textarea.spec.js
│ ├── toolbar.spec.js
│ ├── util.spec.js
│ ├── vendor/
│ │ ├── jasmine-jsreporter-script.js
│ │ └── jasmine-jsreporter.js
│ └── version.spec.js
└── src/
├── js/
│ ├── core.js
│ ├── defaults/
│ │ ├── buttons.js
│ │ └── options.js
│ ├── events.js
│ ├── extension.js
│ ├── extensions/
│ │ ├── README.md
│ │ ├── WALKTHROUGH-BUTTON.md
│ │ ├── WALKTHROUGH-EXTENSION.md
│ │ ├── anchor-preview.js
│ │ ├── anchor.js
│ │ ├── auto-link.js
│ │ ├── button.js
│ │ ├── deprecated/
│ │ │ └── image-dragging.js
│ │ ├── file-dragging.js
│ │ ├── fontname.js
│ │ ├── fontsize.js
│ │ ├── form.js
│ │ ├── keyboard-commands.js
│ │ ├── paste.js
│ │ ├── placeholder.js
│ │ └── toolbar.js
│ ├── globals.js
│ ├── polyfills.js
│ ├── selection.js
│ ├── util.js
│ └── version.js
├── sass/
│ ├── _settings.scss
│ ├── animations/
│ │ ├── _image-loading.scss
│ │ └── _pop-upwards.scss
│ ├── components/
│ │ ├── _anchor-preview.scss
│ │ ├── _file-dragging.scss
│ │ ├── _placeholder.scss
│ │ ├── _toolbar-form.scss
│ │ └── _toolbar.scss
│ ├── medium-editor.scss
│ ├── themes/
│ │ ├── beagle.scss
│ │ ├── bootstrap.scss
│ │ ├── default.scss
│ │ ├── flat.scss
│ │ ├── mani.scss
│ │ ├── roman.scss
│ │ └── tim.scss
│ └── util/
│ └── _clearfix.scss
└── wrappers/
├── end.js
└── start.js
SYMBOL INDEX (100 symbols across 19 files)
FILE: dist/js/medium-editor.js
function MediumEditor (line 402) | function MediumEditor(elements, options) {
function copyInto (line 412) | function copyInto(overwrite, dest) {
function _s4 (line 1486) | function _s4() {
function filterOnlyParentElements (line 1776) | function filterOnlyParentElements(node) {
function isElementDescendantOfExtension (line 2454) | function isElementDescendantOfExtension(extensions, element) {
function nodeIsNotInsideAnchorTag (line 4380) | function nodeIsNotInsideAnchorTag(node) {
function clearClassNames (line 4608) | function clearClassNames(element) {
function createReplacements (line 5164) | function createReplacements() {
function getClipboardContent (line 5221) | function getClipboardContent(event, win, doc) {
function handleDisableExtraSpaces (line 6568) | function handleDisableExtraSpaces(event) {
function handleDisabledEnterKeydown (line 6578) | function handleDisabledEnterKeydown(event, element) {
function handleTabKeydown (line 6593) | function handleTabKeydown(event) {
function handleBlockDeleteKeydowns (line 6616) | function handleBlockDeleteKeydowns(event) {
function handleKeyup (line 6731) | function handleKeyup(event) {
function handleEditableInput (line 6762) | function handleEditableInput(event, editable) {
function addToEditors (line 6771) | function addToEditors(win) {
function removeFromEditors (line 6787) | function removeFromEditors(win) {
function createElementsArray (line 6802) | function createElementsArray(selector, doc, filterEditorElements) {
function cleanupTextareaElement (line 6836) | function cleanupTextareaElement(element) {
function setExtensionDefaults (line 6848) | function setExtensionDefaults(extension, defaults) {
function initExtension (line 6857) | function initExtension(extension, name, instance) {
function isToolbarEnabled (line 6879) | function isToolbarEnabled() {
function isAnchorPreviewEnabled (line 6891) | function isAnchorPreviewEnabled() {
function isPlaceholderEnabled (line 6900) | function isPlaceholderEnabled() {
function isAutoLinkEnabled (line 6904) | function isAutoLinkEnabled() {
function isImageDraggingEnabled (line 6908) | function isImageDraggingEnabled() {
function isKeyboardCommandsEnabled (line 6912) | function isKeyboardCommandsEnabled() {
function shouldUseFileDraggingExtension (line 6916) | function shouldUseFileDraggingExtension() {
function createContentEditable (line 6923) | function createContentEditable(textarea) {
function initElement (line 6969) | function initElement(element, editorId) {
function attachHandlers (line 7019) | function attachHandlers() {
function initExtensions (line 7042) | function initExtensions() {
function mergeOptions (line 7111) | function mergeOptions(defaults, options) {
function execActionInternal (line 7127) | function execActionInternal(action, opts) {
function cleanupJustifyDivFragments (line 7194) | function cleanupJustifyDivFragments(blockContainer) {
FILE: spec/auto-link.spec.js
function triggerAutolinking (line 138) | function triggerAutolinking(element, key) {
function generateLinkTest (line 146) | function generateLinkTest(link, href) {
function generateNotLinkTest (line 178) | function generateNotLinkTest(link) {
FILE: spec/buttons.spec.js
function stripAttrIfEmpty (line 1072) | function stripAttrIfEmpty(element, attribute) {
FILE: spec/dyn-elements.spec.js
function runAddTest (line 130) | function runAddTest(inputSupported) {
function detach (line 265) | function detach(node, async, fn) {
FILE: spec/events.spec.js
function runEditableInputTests (line 334) | function runEditableInputTests(inputSupported) {
FILE: spec/fontname.spec.js
function testFontNameContents (line 7) | function testFontNameContents(el, name) {
FILE: spec/fontsize.spec.js
function testFontSizeContents (line 7) | function testFontSizeContents(el, size) {
FILE: spec/helpers/util.js
function setupTestHelpers (line 3) | function setupTestHelpers() {
function isIE9 (line 47) | function isIE9() {
function isIE10 (line 51) | function isIE10() {
function isOldIE (line 55) | function isOldIE() {
function isIE (line 59) | function isIE() {
function getEdgeVersion (line 64) | function getEdgeVersion() {
function isFirefox (line 72) | function isFirefox() {
function isSafari (line 76) | function isSafari() {
function dataURItoBlob (line 80) | function dataURItoBlob(dataURI) {
function fireEvent (line 106) | function fireEvent(element, eventName, options) {
function prepareEvent (line 133) | function prepareEvent (element, eventName, options) {
function firePreparedEvent (line 198) | function firePreparedEvent (event, element, eventName) {
function placeCursorInsideElement (line 206) | function placeCursorInsideElement(el, index) {
function selectElementContents (line 214) | function selectElementContents(el, options) {
function selectElementContentsAndFire (line 229) | function selectElementContentsAndFire(el, options) {
FILE: spec/placeholder.spec.js
function validatePlaceholderContent (line 140) | function validatePlaceholderContent(element, expectedValue) {
FILE: spec/vendor/jasmine-jsreporter-script.js
function removePassing (line 11) | function removePassing(results) {
FILE: spec/vendor/jasmine-jsreporter.js
function elapsedSec (line 45) | function elapsedSec (startMs, finishMs) {
function round (line 55) | function round (amount, numOfDecDigits) {
function failures (line 64) | function failures (items) {
function getSuiteData (line 79) | function getSuiteData (suite) {
FILE: src/js/core.js
function handleDisableExtraSpaces (line 8) | function handleDisableExtraSpaces(event) {
function handleDisabledEnterKeydown (line 18) | function handleDisabledEnterKeydown(event, element) {
function handleTabKeydown (line 33) | function handleTabKeydown(event) {
function handleBlockDeleteKeydowns (line 56) | function handleBlockDeleteKeydowns(event) {
function handleKeyup (line 171) | function handleKeyup(event) {
function handleEditableInput (line 216) | function handleEditableInput(event, editable) {
function addToEditors (line 225) | function addToEditors(win) {
function removeFromEditors (line 241) | function removeFromEditors(win) {
function createElementsArray (line 256) | function createElementsArray(selector, doc, filterEditorElements) {
function cleanupTextareaElement (line 290) | function cleanupTextareaElement(element) {
function setExtensionDefaults (line 302) | function setExtensionDefaults(extension, defaults) {
function initExtension (line 311) | function initExtension(extension, name, instance) {
function isToolbarEnabled (line 333) | function isToolbarEnabled() {
function isAnchorPreviewEnabled (line 345) | function isAnchorPreviewEnabled() {
function isPlaceholderEnabled (line 354) | function isPlaceholderEnabled() {
function isAutoLinkEnabled (line 358) | function isAutoLinkEnabled() {
function isImageDraggingEnabled (line 362) | function isImageDraggingEnabled() {
function isKeyboardCommandsEnabled (line 366) | function isKeyboardCommandsEnabled() {
function shouldUseFileDraggingExtension (line 370) | function shouldUseFileDraggingExtension() {
function createContentEditable (line 377) | function createContentEditable(textarea) {
function initElement (line 423) | function initElement(element, editorId) {
function attachHandlers (line 473) | function attachHandlers() {
function initExtensions (line 496) | function initExtensions() {
function mergeOptions (line 565) | function mergeOptions(defaults, options) {
function execActionInternal (line 581) | function execActionInternal(action, opts) {
function cleanupJustifyDivFragments (line 648) | function cleanupJustifyDivFragments(blockContainer) {
FILE: src/js/events.js
function isElementDescendantOfExtension (line 4) | function isElementDescendantOfExtension(extensions, element) {
FILE: src/js/extensions/auto-link.js
function nodeIsNotInsideAnchorTag (line 32) | function nodeIsNotInsideAnchorTag(node) {
FILE: src/js/extensions/file-dragging.js
function clearClassNames (line 6) | function clearClassNames(element) {
FILE: src/js/extensions/paste.js
function createReplacements (line 20) | function createReplacements() {
function getClipboardContent (line 77) | function getClipboardContent(event, win, doc) {
FILE: src/js/globals.js
function MediumEditor (line 2) | function MediumEditor(elements, options) {
FILE: src/js/selection.js
function filterOnlyParentElements (line 4) | function filterOnlyParentElements(node) {
FILE: src/js/util.js
function copyInto (line 6) | function copyInto(overwrite, dest) {
function _s4 (line 1151) | function _s4() {
Condensed preview — 127 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (1,566K chars).
[
{
"path": ".editorconfig",
"chars": 202,
"preview": "# EditorConfig is awesome: http://EditorConfig.org\n\nroot = true\n\n[*]\nend_of_line = lf\ninsert_final_newline = false\ninden"
},
{
"path": ".github/ISSUE_TEMPLATE.md",
"chars": 592,
"preview": "### Description\n\n[Description of the bug or feature]\n\n### Steps to reproduce\n\n1. [First step]\n2. [Second step]\n3. [and s"
},
{
"path": ".github/PULL_REQUEST_TEMPLATE.md",
"chars": 411,
"preview": "| Q | A\n| ---------------- | ---\n| Bug fix? | yes/no\n| New feature? | yes/no\n| BC breaks? "
},
{
"path": ".gitignore",
"chars": 180,
"preview": "*~\n*.swp\n.DS_Store\n*.swo\nnode_modules/\n.env\n.sass-cache/\nnpm-debug.log\n.grunt/\n_SpecRunner.html\nreports/\ncoverage/\n._*\nl"
},
{
"path": ".jscsrc",
"chars": 2684,
"preview": "{\n \"disallowEmptyBlocks\": true,\n \"disallowKeywordsOnNewLine\": [\n \"else\"\n ],\n \"disallowMixedSpacesAndTabs\": true,\n"
},
{
"path": ".jshintrc",
"chars": 418,
"preview": "{\n \"boss\": true,\n \"browser\": true,\n \"curly\": true,\n \"eqeqeq\": true,\n \"eqnull\": true,\n \"immed\": true,\n \"latedef\": "
},
{
"path": ".npmrc",
"chars": 16,
"preview": "save-exact=true\n"
},
{
"path": ".travis.yml",
"chars": 572,
"preview": "# faster builds on new travis setup not using sudo\nsudo: false\n\n# cache vendor dirs\ncache:\n directories:\n - no"
},
{
"path": "API.md",
"chars": 19247,
"preview": "# MediumEditor Object API (v5.0.0)\n\n<!-- START doctoc generated TOC please keep comment here to allow auto update -->\n<!"
},
{
"path": "CHANGES.md",
"chars": 31054,
"preview": "5.23.3 / 2017-12-20\n==================\n* Fix medium-editor-insert plugin css fixes on beagle theme #1361\n* Update jsDeli"
},
{
"path": "CODE_OF_CONDUCT.md",
"chars": 1423,
"preview": "# Contributor Code of Conduct\n\nAs contributors and maintainers of MediumEditor, we pledge to respect all people who cont"
},
{
"path": "CONTRIBUTING.md",
"chars": 2458,
"preview": "# Contributing\n\n## To contribute and end up in this [list](https://github.com/yabwe/medium-editor/graphs/contributors):\n"
},
{
"path": "CUSTOM-EVENTS.md",
"chars": 11178,
"preview": "# MediumEditor Custom Events (v5.0.0)\n\nMediumEditor exposes a variety of custom events for convenience when using the ed"
},
{
"path": "Gruntfile.js",
"chars": 7069,
"preview": "/*global module, require, process*/\n\nmodule.exports = function (grunt) {\n 'use strict';\n\n var autoprefixerBrowsers"
},
{
"path": "LICENSE",
"chars": 1580,
"preview": "Copyright Davi Ferreira, https://www.daviferreira.com/\n\nThis software consists of voluntary contributions made by many\ni"
},
{
"path": "MAINTAINERS.md",
"chars": 2547,
"preview": "## STEPS TO RELEASE:\n\n1. Find the last release commit in log history. Look through all the commits or PR history and see"
},
{
"path": "OPTIONS.md",
"chars": 20704,
"preview": "# MediumEditor Options (v5.0.0)\n\nOptions to customize medium-editor are passed as the second argument to the [MediumEdit"
},
{
"path": "README.md",
"chars": 36734,
"preview": ";\n transform: scale(0)"
},
{
"path": "dist/css/themes/beagle.css",
"chars": 2630,
"preview": ".medium-toolbar-arrow-under:after {\n border-color: #000 transparent transparent transparent;\n top: 40px; }\n\n.medium-to"
},
{
"path": "dist/css/themes/bootstrap.css",
"chars": 2222,
"preview": ".medium-toolbar-arrow-under:after {\n border-color: #428bca transparent transparent transparent;\n top: 60px; }\n\n.medium"
},
{
"path": "dist/css/themes/default.css",
"chars": 2150,
"preview": ".medium-toolbar-arrow-under:after {\n border-color: #242424 transparent transparent transparent;\n top: 50px; }\n\n.medium"
},
{
"path": "dist/css/themes/flat.css",
"chars": 1885,
"preview": ".medium-toolbar-arrow-under:after {\n top: 60px;\n border-color: #57ad68 transparent transparent transparent; }\n\n.medium"
},
{
"path": "dist/css/themes/mani.css",
"chars": 1920,
"preview": ".medium-toolbar-arrow-under:after,\n.medium-toolbar-arrow-over:before {\n display: none; }\n\n.medium-editor-toolbar {\n bo"
},
{
"path": "dist/css/themes/roman.css",
"chars": 1910,
"preview": ".medium-toolbar-arrow-under:after,\n.medium-toolbar-arrow-over:before {\n display: none; }\n\n.medium-editor-toolbar {\n ba"
},
{
"path": "dist/css/themes/tim.css",
"chars": 2255,
"preview": ".medium-toolbar-arrow-under:after {\n border-color: #2f1e07 transparent transparent transparent;\n top: 60px; }\n\n.medium"
},
{
"path": "dist/js/medium-editor.js",
"chars": 314051,
"preview": "/*global self, document, DOMException */\n\n/*! @source http://purl.eligrey.com/github/classList.js/blob/master/classList."
},
{
"path": "index.js",
"chars": 129,
"preview": "var connect = require('connect');\nvar serveStatic = require('serve-static');\nconnect().use(serveStatic(__dirname)).liste"
},
{
"path": "karma.conf.js",
"chars": 4359,
"preview": "/* global module */\n\nmodule.exports = function (config) {\n config.set({\n\n browserStack: {\n apiClien"
},
{
"path": "karma.dev.conf.js",
"chars": 1993,
"preview": "/* global module */\n\nmodule.exports = function (config) {\n config.set({\n\n basePath: '',\n frameworks: ['"
},
{
"path": "package.json",
"chars": 2501,
"preview": "{\n \"name\": \"medium-editor\",\n \"version\": \"5.23.3\",\n \"author\": \"Davi Ferreira <hi@daviferreira.com>\",\n \"contributors\":"
},
{
"path": "spec/anchor-preview.spec.js",
"chars": 20271,
"preview": "/*global fireEvent, selectElementContentsAndFire */\n\ndescribe('Anchor Preview TestCase', function () {\n 'use strict';"
},
{
"path": "spec/anchor.spec.js",
"chars": 47200,
"preview": "/*global fireEvent, selectElementContents,\n selectElementContentsAndFire, getEdgeVersion */\n\ndescribe('Anchor Bu"
},
{
"path": "spec/auto-link.spec.js",
"chars": 25232,
"preview": "/*global fireEvent, selectElementContentsAndFire */\n\ndescribe('Autolink', function () {\n 'use strict';\n\n describe("
},
{
"path": "spec/buttons.spec.js",
"chars": 54723,
"preview": "/*global fireEvent, selectElementContentsAndFire,\n isIE, isOldIE */\n\ndescribe('Buttons TestCase', function () {\n"
},
{
"path": "spec/content.spec.js",
"chars": 39259,
"preview": "/*global fireEvent, firePreparedEvent,\n prepareEvent, selectElementContents,\n selectElementContentsAndFi"
},
{
"path": "spec/core-api.spec.js",
"chars": 16280,
"preview": "/*global fireEvent, selectElementContents,\n selectElementContentsAndFire */\n\ndescribe('Core-API', function () {\n 'us"
},
{
"path": "spec/delay.spec.js",
"chars": 991,
"preview": "describe('Delay TestCase', function () {\n 'use strict';\n\n beforeEach(function () {\n setupTestHelpers.call(t"
},
{
"path": "spec/drag-and-drop.spec.js",
"chars": 4875,
"preview": "/*global fireEvent */\n\ndescribe('Drag and Drop TestCase', function () {\n 'use strict';\n\n beforeEach(function () {\n"
},
{
"path": "spec/dyn-elements.spec.js",
"chars": 12902,
"preview": "/*global fireEvent */\n\ndescribe('MediumEditor.DynamicElements TestCase', function () {\n 'use strict';\n\n beforeEach"
},
{
"path": "spec/elements.spec.js",
"chars": 4613,
"preview": "describe('Elements TestCase', function () {\n 'use strict';\n\n beforeEach(function () {\n setupTestHelpers.cal"
},
{
"path": "spec/events.spec.js",
"chars": 20853,
"preview": "/*global fireEvent, selectElementContents,\n selectElementContentsAndFire */\n\ndescribe('MediumEditor.Events TestC"
},
{
"path": "spec/exploits.spec.js",
"chars": 3284,
"preview": "describe('Exploits', function () {\n 'use strict';\n\n beforeEach(function () {\n setupTestHelpers.call(this);\n"
},
{
"path": "spec/extension.spec.js",
"chars": 16250,
"preview": "/*global selectElementContentsAndFire, fireEvent */\n\ndescribe('Extensions TestCase', function () {\n 'use strict';\n\n "
},
{
"path": "spec/fontname.spec.js",
"chars": 7285,
"preview": "/*global fireEvent, selectElementContents,\n selectElementContentsAndFire */\n\ndescribe('Font Name Button TestCase"
},
{
"path": "spec/fontsize.spec.js",
"chars": 7389,
"preview": "/*global fireEvent, selectElementContents,\n selectElementContentsAndFire, isIE9 */\n\ndescribe('Font Size Button T"
},
{
"path": "spec/full-content.spec.js",
"chars": 3394,
"preview": "/*global selectElementContentsAndFire */\n\ndescribe('Full Content Action TestCase', function () {\n 'use strict';\n\n "
},
{
"path": "spec/header-tags.spec.js",
"chars": 3044,
"preview": "/*global fireEvent */\n\ndescribe('Protect Header Tags TestCase', function () {\n 'use strict';\n\n beforeEach(function"
},
{
"path": "spec/helpers/util.js",
"chars": 58526,
"preview": "/*global atob, unescape, Uint8Array, Blob*/\n\nfunction setupTestHelpers() {\n jasmine.clock().install();\n this.eleme"
},
{
"path": "spec/init.spec.js",
"chars": 8344,
"preview": "/*global _ */\n\ndescribe('Initialization TestCase', function () {\n 'use strict';\n\n beforeEach(function () {\n "
},
{
"path": "spec/keyboard-commands.spec.js",
"chars": 10237,
"preview": "/*global fireEvent, selectElementContentsAndFire */\n\ndescribe('KeyboardCommands TestCase', function () {\n 'use strict"
},
{
"path": "spec/paste.spec.js",
"chars": 55873,
"preview": "/*global selectElementContents,\n selectElementContentsAndFire,\n fireEvent, prepareEvent,\n firePr"
},
{
"path": "spec/placeholder.spec.js",
"chars": 11554,
"preview": "/*global fireEvent, selectElementContentsAndFire */\n\ndescribe('MediumEditor.extensions.placeholder TestCase', function ("
},
{
"path": "spec/selection.spec.js",
"chars": 28959,
"preview": "/*global selectElementContents, placeCursorInsideElement, isSafari */\n\ndescribe('MediumEditor.selection TestCase', funct"
},
{
"path": "spec/serialize.spec.js",
"chars": 1038,
"preview": "describe('Anchor Button TestCase', function () {\n 'use strict';\n\n beforeEach(function () {\n setupTestHelper"
},
{
"path": "spec/setup.spec.js",
"chars": 4454,
"preview": "/*global fireEvent, selectElementContentsAndFire */\n\ndescribe('Setup/Destroy TestCase', function () {\n 'use strict';\n"
},
{
"path": "spec/textarea.spec.js",
"chars": 13503,
"preview": "/*global fireEvent */\n\ndescribe('Textarea TestCase', function () {\n 'use strict';\n\n describe('MediumEditor constru"
},
{
"path": "spec/toolbar.spec.js",
"chars": 35022,
"preview": "/*global fireEvent, selectElementContents,\n selectElementContentsAndFire,\n placeCursorInsideElement */\n\n"
},
{
"path": "spec/util.spec.js",
"chars": 32432,
"preview": "/*global selectElementContents, selectElementContentsAndFire */\n\ndescribe('MediumEditor.util', function () {\n 'use st"
},
{
"path": "spec/vendor/jasmine-jsreporter-script.js",
"chars": 636,
"preview": "(function () {\n jasmine.getEnv().addReporter(new jasmine.JSReporter2()); //< for jsreporter\n\n va"
},
{
"path": "spec/vendor/jasmine-jsreporter.js",
"chars": 12274,
"preview": "/*\n This file is part of the Jasmine JSReporter project from Ivan De Marino.\n\n Copyright (C) 2011-2014 Ivan De Marino "
},
{
"path": "spec/version.spec.js",
"chars": 1723,
"preview": "describe('Core MediumEditor', function () {\n\n it('exists', function () {\n expect(MediumEditor).toBeTruthy();\n "
},
{
"path": "src/js/core.js",
"chars": 57308,
"preview": "(function () {\n 'use strict';\n\n var initialContent = {};\n\n // Event handlers that shouldn't be exposed external"
},
{
"path": "src/js/defaults/buttons.js",
"chars": 8538,
"preview": "(function () {\n 'use strict';\n\n /* MediumEditor.extensions.button.defaults: [Object]\n * Set of default config "
},
{
"path": "src/js/defaults/options.js",
"chars": 566,
"preview": "(function () {\n // summary: The default options hash used by the Editor\n\n MediumEditor.prototype.defaults = {\n "
},
{
"path": "src/js/events.js",
"chars": 24731,
"preview": "(function () {\n 'use strict';\n\n function isElementDescendantOfExtension(extensions, element) {\n if (!extens"
},
{
"path": "src/js/extension.js",
"chars": 10192,
"preview": "(function () {\n 'use strict';\n\n var Extension = function (options) {\n MediumEditor.util.extend(this, option"
},
{
"path": "src/js/extensions/README.md",
"chars": 30299,
"preview": "# Extensions\n\n* [Building An Extension (Walkthrough)](./WALKTHROUGH-EXTENSION.md)\n* [Building A Button (Walkthrough)](./"
},
{
"path": "src/js/extensions/WALKTHROUGH-BUTTON.md",
"chars": 13304,
"preview": "# Walkthrough - Building a Button\n\n<!-- START doctoc generated TOC please keep comment here to allow auto update -->\n<!-"
},
{
"path": "src/js/extensions/WALKTHROUGH-EXTENSION.md",
"chars": 5980,
"preview": "# Walkthrough - Building an Extension\n\n<!-- START doctoc generated TOC please keep comment here to allow auto update -->"
},
{
"path": "src/js/extensions/anchor-preview.js",
"chars": 12486,
"preview": "(function () {\n 'use strict';\n\n var AnchorPreview = MediumEditor.Extension.extend({\n name: 'anchor-preview'"
},
{
"path": "src/js/extensions/anchor.js",
"chars": 14118,
"preview": "(function () {\n 'use strict';\n\n var AnchorForm = MediumEditor.extensions.form.extend({\n /* Anchor Form Opti"
},
{
"path": "src/js/extensions/auto-link.js",
"chars": 12255,
"preview": "(function () {\n 'use strict';\n\n var WHITESPACE_CHARS,\n KNOWN_TLDS_FRAGMENT,\n LINK_REGEXP_TEXT,\n "
},
{
"path": "src/js/extensions/button.js",
"chars": 8651,
"preview": "(function () {\n 'use strict';\n\n var Button = MediumEditor.Extension.extend({\n\n /* Button Options */\n\n "
},
{
"path": "src/js/extensions/deprecated/image-dragging.js",
"chars": 2314,
"preview": "(function () {\n 'use strict';\n\n var ImageDragging = MediumEditor.Extension.extend({\n init: function () {\n "
},
{
"path": "src/js/extensions/file-dragging.js",
"chars": 3362,
"preview": "(function () {\n 'use strict';\n\n var CLASS_DRAG_OVER = 'medium-editor-dragover';\n\n function clearClassNames(elem"
},
{
"path": "src/js/extensions/fontname.js",
"chars": 5974,
"preview": "(function () {\n 'use strict';\n\n var FontNameForm = MediumEditor.extensions.form.extend({\n\n name: 'fontname'"
},
{
"path": "src/js/extensions/fontsize.js",
"chars": 5774,
"preview": "(function () {\n 'use strict';\n\n var FontSizeForm = MediumEditor.extensions.form.extend({\n\n name: 'fontsize'"
},
{
"path": "src/js/extensions/form.js",
"chars": 4008,
"preview": "(function () {\n 'use strict';\n\n /* Base functionality for an extension which will display\n * a 'form' inside t"
},
{
"path": "src/js/extensions/keyboard-commands.js",
"chars": 3082,
"preview": "(function () {\n 'use strict';\n\n var KeyboardCommands = MediumEditor.Extension.extend({\n name: 'keyboard-com"
},
{
"path": "src/js/extensions/paste.js",
"chars": 22425,
"preview": "(function () {\n 'use strict';\n\n /* Helpers and internal variables that don't need to be members of actual paste ha"
},
{
"path": "src/js/extensions/placeholder.js",
"chars": 4675,
"preview": "(function () {\n 'use strict';\n\n var Placeholder = MediumEditor.Extension.extend({\n name: 'placeholder',\n\n "
},
{
"path": "src/js/extensions/toolbar.js",
"chars": 28183,
"preview": "(function () {\n 'use strict';\n\n var Toolbar = MediumEditor.Extension.extend({\n name: 'toolbar',\n\n /*"
},
{
"path": "src/js/globals.js",
"chars": 185,
"preview": "/*jshint unused: false */\nfunction MediumEditor(elements, options) {\n 'use strict';\n return this.init(elements, op"
},
{
"path": "src/js/polyfills.js",
"chars": 11321,
"preview": "/*\n * classList.js: Cross-browser full element.classList implementation.\n * 2014-12-13\n *\n * By Eli Grey, http://eligrey"
},
{
"path": "src/js/selection.js",
"chars": 29664,
"preview": "(function () {\n 'use strict';\n\n function filterOnlyParentElements(node) {\n if (MediumEditor.util.isBlockCon"
},
{
"path": "src/js/util.js",
"chars": 47176,
"preview": "/*global NodeFilter*/\n\n(function (window) {\n 'use strict';\n\n function copyInto(overwrite, dest) {\n var prop"
},
{
"path": "src/js/version.js",
"chars": 657,
"preview": "MediumEditor.parseVersionString = function (release) {\n var split = release.split('-'),\n version = split[0].sp"
},
{
"path": "src/sass/_settings.scss",
"chars": 215,
"preview": "// typography\n$font-fixed: Consolas, \"Liberation Mono\", Menlo, Courier, monospace !default;\n$font-sans-serif: \"Helvetica"
},
{
"path": "src/sass/animations/_image-loading.scss",
"chars": 132,
"preview": "@keyframes medium-editor-image-loading {\n 0% {\n transform: scale(0)\n }\n 100% {\n transform: scale("
},
{
"path": "src/sass/animations/_pop-upwards.scss",
"chars": 355,
"preview": "@keyframes medium-editor-pop-upwards {\n 0% {\n opacity: 0;\n transform: matrix(.97, 0, 0, 1, 0, 12);\n "
},
{
"path": "src/sass/components/_anchor-preview.scss",
"chars": 474,
"preview": ".medium-editor-anchor-preview {\n font-family: $font-sans-serif;\n font-size: 16px;\n left: 0;\n line-height: 1."
},
{
"path": "src/sass/components/_file-dragging.scss",
"chars": 267,
"preview": ".medium-editor-dragover {\n background: #ddd;\n}\n\n.medium-editor-image-loading {\n animation: medium-editor-image-loa"
},
{
"path": "src/sass/components/_placeholder.scss",
"chars": 565,
"preview": ".medium-editor-placeholder {\n position: relative;\n\n &:after {\n content: attr(data-placeholder) !important;\n"
},
{
"path": "src/sass/components/_toolbar-form.scss",
"chars": 823,
"preview": ".medium-editor-toolbar-form {\n display: none;\n\n input,\n a {\n font-family: $font-sans-serif;\n }\n\n ."
},
{
"path": "src/sass/components/_toolbar.scss",
"chars": 1839,
"preview": "%medium-toolbar-arrow {\n border-style: solid;\n content: '';\n display: block;\n height: 0;\n left: 50%;\n "
},
{
"path": "src/sass/medium-editor.scss",
"chars": 571,
"preview": "@import \"settings\";\n@import \"animations/image-loading\";\n@import \"animations/pop-upwards\";\n@import \"components/anchor-pre"
},
{
"path": "src/sass/themes/beagle.scss",
"chars": 3547,
"preview": "// theme settings\n$medium-editor-bgcolor: #000;\n$medium-editor-button-size: 40px;\n$medium-editor-button-active-text-colo"
},
{
"path": "src/sass/themes/bootstrap.scss",
"chars": 3219,
"preview": "// theme settings\n$medium-editor-bgcolor: #428bca;\n$medium-editor-border-color: #357ebd;\n$medium-editor-button-size: 60p"
},
{
"path": "src/sass/themes/default.scss",
"chars": 2548,
"preview": "// theme settings\n$medium-editor-bgcolor: #242424;\n$medium-editor-button-size: 50px;\n$medium-editor-border-radius: 5px;\n"
},
{
"path": "src/sass/themes/flat.scss",
"chars": 2602,
"preview": "// theme settings\n$medium-editor-bgcolor: #57ad68;\n$medium-editor-border-color: #fff;\n$medium-editor-button-size: 60px;\n"
},
{
"path": "src/sass/themes/mani.scss",
"chars": 2818,
"preview": "// inspired by http://dribbble.com/shots/857472-Toolbar\n\n// theme settings\n$medium-editor-bgcolor: #dee7f0;\n$medium-edit"
},
{
"path": "src/sass/themes/roman.scss",
"chars": 2718,
"preview": "// inspired by http://dribbble.com/shots/848100-Toolbar-Psd\n\n// theme settings\n$medium-editor-bgcolor: #fff;\n$medium-edi"
},
{
"path": "src/sass/themes/tim.scss",
"chars": 3286,
"preview": "// theme settings\n$medium-editor-bgcolor: #2f1e07;\n$medium-editor-border-color: lighten($medium-editor-bgcolor, 10);\n$me"
},
{
"path": "src/sass/util/_clearfix.scss",
"chars": 100,
"preview": "%clearfix {\n &:after {\n clear: both;\n content: \"\";\n display: table;\n }\n}\n"
},
{
"path": "src/wrappers/end.js",
"chars": 32,
"preview": " return MediumEditor;\n}()));\n"
},
{
"path": "src/wrappers/start.js",
"chars": 503,
"preview": "(function (root, factory) {\n 'use strict';\n var isElectron = typeof module === 'object' && typeof process !== 'und"
}
]
About this extraction
This page contains the full source code of the yabwe/medium-editor GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 127 files (1.4 MB), approximately 325.4k tokens, and a symbol index with 100 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.