ryedin / medium-editor

Medium.com WYSIWYG editor clone. Uses contenteditable API to implement a rich text solution.

Home Page:https://daviferreira.github.io/medium-editor/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

MediumEditor

Join the chat at https://gitter.im/daviferreira/medium-editor

This is a clone of medium.com inline editor toolbar.

MediumEditor has been written using vanilla JavaScript, no additional frameworks required.

Browser Support

Sauce Test Status

Supportd Browsers

NPM info

Travis build status dependencies devDependency Status Coverage Status

Basic usage

screenshot

demo: http://daviferreira.github.io/medium-editor/

Installation

Via npm:

Run in your console: npm install medium-editor

Via bower:

bower install medium-editor

Via CDNJS

CDNJS hosts this library and you can load it from CDN this way:

<script src="https://cdnjs.cloudflare.com/ajax/libs/medium-editor/4.10.1/medium-editor.min.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/medium-editor/4.10.1/medium-editor.min.css" type="text/css" media="screen" charset="utf-8">

Manual installation:

Download the latest release and attach medium editor's stylesheets to your page:

<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

<script src="js/medium-editor.js"></script>

You can now instantiate a new MediumEditor object:

<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:

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.

Initialization options

Core options

  • allowMultiParagraphSelection: enables the toolbar when selecting multiple paragraphs/block elements. Default: true
  • 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
  • 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) for more. Default: {}
  • firstHeader: HTML tag to be used as first header. Default: h3
  • secondHeader: HTML tag to be used as second header. Default: h4
  • spellcheck: Enable/disable native contentEditable automatic spellcheck. Default: true
  • standardizeSelectionStart: Standardizes how the beginning of a range is decided between browsers whenever the selected text is analyzed for updating toolbar buttons status
  • targetBlank: enables/disables target="_blank" for anchor tags. Default: false

Toolbar options

  • activeButtonClass: CSS class added to active buttons in the toolbar. Default: 'medium-editor-button-active'
  • buttons: the set of buttons to display on the toolbar. Default: ['bold', 'italic', 'underline', 'anchor', 'header1', 'header2', 'quote']
  • buttonLabels: type of labels on the buttons. Values: 'fontawesome', {'bold': '<b>b</b>', 'italic': '<i>i</i>'}. Default: false
  • 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
  • disableToolbar: enables/disables the toolbar, adding only the contenteditable behavior. You can also set specific element behavior by using setting a data-disable-toolbar attribute. Default: false
  • 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'
  • staticToolbar: enable/disable the toolbar always displaying in the same location relative to the medium-editor element. Default: false
  • stickyToolbar: enable/disable the toolbar "sticking" to the medium-editor element when the page is being scrolled. Default: false
  • toolbarAlign: left|center|right - when using the staticToolbar option, this aligns the static toolbar relative to the medium-editor element. Default: center
  • updateOnEmptySelection: update the state of the toolbar buttons even when the selection is collapse (there is no selection, just a cursor). Default: 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:

var editor = new MediumEditor('.editable', {
    buttons: ['bold', 'italic', 'underline'],
    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'

To disable the anchor preview, set the value of the anchorPreview option to false:

var editor = new MediumEditor('.editable', {
    anchorPreview: false
});
NOTE:
  • If the toolbar is disabled (via disableToolbar 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:

var editor = new MediumEditor('.editable', {
    buttons: ['bold', 'italic', 'quote'],
    placeholder: {
        /* This example includes the default options for placeholder,
           if nothing is passed this is what it used */
        text: 'Type your text'
    }
});
  • text: Defines the default placeholder for empty contenteditables when placeholder is not set to false. You can overwrite it by setting a data-placeholder attribute on the editor elements. Default: 'Type your text'

To disable the placeholder, set the value of the placeholder option to false:

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:

var editor = new MediumEditor('.editable', {
    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. 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:

var editor = new MediumEditor('.editable', {
    buttons: ['bold', 'italic', 'quote'],
    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: Forces pasting as plain text. Default: true
  • cleanPastedHTML: cleans pasted content from different sources, like google docs etc. Default: false
  • 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. 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']

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':

var editor = new MediumEditor('.editable', {
    autoLink: true
});

Image Dragging Options

The image dragging handler is a built-in extenson for handling dragging & dropping images into the contenteditable. This feature is ON by default.

To disable built-in image dragging, set the value of the imageDragging option to false:

var editor = new MediumEditor('.editable', {
    imageDragging: false
});

Options Example:

var editor = new MediumEditor('.editable', {
    buttons: ['bold', 'italic', 'quote'],
    diffLeft: 25,
    diffTop: 10,
    firstHeader: 'h1',
    secondHeader: 'h2',
    delay: 1000,
    targetBlank: true,
    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'
    }
});

Extra buttons

Medium Editor, by default, will show only the buttons listed above to avoid a huge toolbar. There are a few extra buttons you can use:

  • superscript
  • subscript
  • strikethrough
  • unorderedlist
  • orderedlist
  • pre
  • justifyLeft
  • justifyFull
  • justifyCenter
  • justifyRight
  • image (this simply converts selected text to an image tag)
  • indent (moves the selected text up one level)
  • outdent (moves the selected text down one level)
  • removeFormat (clears inline style formatting, preserves blocks)

Themes

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

API

Core Methods

  • .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
  • .serialize(): returns a JSON object with elements contents
  • .execAction(action, opts): executes an built-in action via document.execCommand
  • .createLink(opts): creates a link via the native document.execCommand('createLink') command
  • .subscribe(event, listener): attaches a listener to a custom medium-editor event
  • .unsubscribe(event, listener): detaches a listener from a custom medium-editor event
  • .saveSelection(): internally store the set of selected text
  • .restoreSelection(): restore the selection to what was selected when saveSelection() was called
  • .selectAllContents(): expands the selection to contain all text within the focused contenteditable
  • .stopSelectionUpdates(): stop the toolbar from updating to reflect the state of the selected text
  • .startSelectionUpdates(): put the toolbar back into its normal updating state
  • .cleanPaste(text): convert text to plaintext and replace current selection with result
  • .pasteHTML(html, options): replace the current selection with html

Helper 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()
  • .delay(fn): delay any function from being executed by the amount of time passed as the delay option
  • .getSelectionParentElement(range): get the parent contenteditable element that contains the current selection
  • .getExtensionByName(name): get a reference to an extension with the specified name
  • .getFocusedElement(): returns an element if any contenteditable element monitored by MediumEditor currently has focused
  • .selectElement(element): change selection to be a specific element and update the toolbar to reflect the 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())

Capturing DOM changes

For observing any changes on contentEditable, use the custom 'editableInput' event exposed via the subscribe() method:

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+)! 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. Infact, editableInput is just a proxy for the input event in those browsers. However, the input event is not supported for contenteditable elements in IE 9-11.

So, to properly support the editableInput event in Internet Explorer, MediumEditor uses a combination of the selectionchange and keypress events, as well as monitoring calls to document.execCommand.

Extensions & Plugins

Check the documentation in order to learn how to develop extensions for MediumEditor.

A list of existing extensions and plugins, such as Images and Media embeds, Tables and Markdown can be found here.

Development

MediumEditor development tasks are managed by Grunt. To install all the necessary packages, just invoke:

npm install

To run all the test and build the dist files for testing on demo pages, just invoke:

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 :)

  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 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 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 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 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!

Contributors (100+ and counting!)

https://github.com/daviferreira/medium-editor/graphs/contributors

License

MIT: https://github.com/daviferreira/medium-editor/blob/master/LICENSE

About

Medium.com WYSIWYG editor clone. Uses contenteditable API to implement a rich text solution.

https://daviferreira.github.io/medium-editor/

License:Other


Languages

Language:JavaScript 91.7%Language:HTML 4.6%Language:CSS 3.6%