Coder Social home page Coder Social logo

yabwe / medium-editor Goto Github PK

View Code? Open in Web Editor NEW
16.0K 362.0 1.9K 10.15 MB

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

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

License: Other

JavaScript 97.47% CSS 2.53%
medium-editor contenteditable editor javascript rich-text-editor wysiwyg

medium-editor's Introduction

medium-editor needs help!

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)

MediumEditor

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

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

screenshot

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

Browser Support

Saucelabs Build Status

Supported Browsers

NPM info

Travis build status Dependency Status devDependency Status Coverage Status

Basic usage

Demo

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

For the latest version:

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

<script src="//cdn.jsdelivr.net/npm/[email protected]/dist/js/medium-editor.min.js"></script>
<link rel="stylesheet" href="//cdn.jsdelivr.net/npm/[email protected]/dist/css/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:

Find the files to below mentioned linking in the dist folder. (./medium-editor/dist/...)

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

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 that have already been written for MediumEditor!

MediumEditor Options

View the MediumEditor Options documentation on all the various options for MediumEditor.

Options to customize medium-editor are passed as the second argument to the MediumEditor constructor. Example:

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

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

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 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 H1 and H2 buttons which actually produce <h2> and <h3> tags respectively):

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:

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:

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:

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:

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

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:

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:

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:

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:

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.

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

Due to the state of code 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:

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

API

View the MediumEditor Object API documentation 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:

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.

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:

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

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:

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 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 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/yabwe/medium-editor/graphs/contributors

Is Your Org Using MediumEditor?

Add your org here and we can add you to our landing page!

License

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

medium-editor's People

Contributors

anilmaurya avatar arteconceito avatar charlgottschalk avatar daviferreira avatar dayjo avatar devalnor avatar eden-lane avatar ionicabizau avatar j0k3r avatar jarlg avatar julianxhokaxhiu avatar ksorv avatar lexderuijter avatar linkesch avatar lizraeli avatar minikomi avatar mphasize avatar nchase avatar nmielnik avatar omniagm avatar pascalpp avatar patrick-webs avatar pedronasser avatar phiggins42 avatar pixelpony avatar ppoulsen avatar rishijain avatar stefanpejcic avatar steoo avatar zuchmanski avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

medium-editor's Issues

Pluggable actions

Create a pluggable template for button actions and convert medium default actions to the new standard (yet to be defined, something like a JS object with pre-defined methods like label, icon, action, etc.).

Any appetite for tables?

It seems that most 'medium-style' editing experiences eschew HTML tables. I think I get the reasoning, but I also think there's certainly a place for one that includes simplified table management. Is there any appetite for this in your project?

Enable editor on Textarea

Hello, is it possible to enable the editor on Textarea?

The issue is referenced to getSelectionCoords function.

Thanks.

<shift+enter> wrong behaviour

When hitting <shift+enter> it does a line break <br> but then wraps the new line into a paragraph such as:

<p>Now that there is the Tec-9, a crappy spray gun from South Miami.
    <br>
    <p>This gun is advertised as the most popular gun in American crime. Do you believe that shit? It actually says that in the little book that comes with it: the most popular gun in American crime. Like they're actually proud of that shit.</p>
</p>

Return key change H1 into p

Hi, I just get started with this project. It looks great!

I found that the behevior has changed in the lastest version compared to the github demo version.

In latest version, if I set a title with <H1> and press enter to move the the next line and start typing a paragraph, the <H1> is transformed in a <p>. Demo version is not doing it and I think its the good behaviour.

This commit e3e6c5c changed from "keyup" to "keypress" the event to create a paragraph. Reverting to "keyup" fixes the issue but I wonder why its been changed in the first place... If it need to stay "keypress", its possible to perform document.execCommand('formatBlock', false, 'p'); after a small timeout. It feels more like a hack though.

Jeff

functionality breaks when using bootstrap

Hi first off super project.

I'm a bit afraid to post this because it might as well be me, my platform or something third.

But I have noticed that the functionality of the editor breaks when I add bootstrap.css. to the project.

Bootstrap 2x - 3, Firefox 24, Linux 3.10.10-1-ARCH

Font-face

Since font-faces can be slightly bigger than regular fonts after rendering, we need to verify that when checking the selection dimension to avoit toolbar mislocation.

[Request] When hitting return key 2 or x times insert element and disable return-key

Like on Medium when you hit the return key 2 times a <hr> element is inserted on the empty line just above and return-key are disabled until text has been typed.

The <hr> could perhaps be any element set through the options object and the number of allowed empty lines could be as well.

Medium example:

Medumm Example

It's helping the user to create nicely formatted and a schematic layouts.

Proper handling of ctrl-a

Currently, selecting text using ctrl-a and manually selecting everything using the mouse, produce different results.

For example if all text is italic, and you select everything using ctrl-a, the "i" in the toolbar is not highlighted (signifying the selected text is already italic). The behaviour, however is correct and will undo the italics. It will also highlight the "i" while undoing it, which is backwards.

The worst issue I've encountered is when starting with an empty .editable:

<div class='editable'></div>

Then start to write the first line, ctrl-a, select h1. This transforms the div into:

<h1> Text you entered </h1>

i.e. a non-editable element. Which means you can't write more. Proper behaviour should result in:

<div class='editable'>
    <h1> Text you entered </h1>
</div>

I am not sure how to tackle this. It seems the selection of ctrl-a is different from a manual, visual selection. For visual selection, the above works well.

Related StackExchange.

Make it possible to disable the editor

Currently you can start the editor, but there is no easy way to turn it off after user has finished editing. This would be helpful for Create.js integration where users can switch between editing and browsing mode.

Undo misses the block-level changes.

Reproduction steps:

  1. Make a block-level change, e.g blockquote the first para.
  2. Make a word-level change, e.g. bold a letter or two, underline something.
  3. Make another block-level change, e.g. change the second para to h1.
  4. Hit Ctrl-Z (Winows/Linux) to undo.

Only the change that occurs in step 2 is undone.

PS: Great work, nonetheless! I was looking for something like this for the landing page on my site. :)

Provide change events

Themes

Document and implement new themes.

Option to disable placeholders

From #50:

"A simple solution is to give an option to turn off placeholder. It would be even better if it was possible to add/remove the placeHolder manually without affecting the innerHTML content. I could then add / remove it depending on my bound content..."

End Of Line char translate to <p> or <br>

I've noticed that the behaviour is not the same when I type a bunch of paragraphs and when I paste the same content (using forcePlainText).

Typing:

<p>
paragraph1
</p>
<p>
paragraph2
</p>

Pasting:

<p>
paragraph1
<br>
paragraph2
</p>

I think it would be better to have the same behaviour.

To create <p> instead of <br> while pasting, the change would be:

document.execCommand('insertHTML', false, e.clipboardData.getData('text/plain').replace(/[\r\n]/g, '<br>'));
becomes
document.execCommand('insertHTML', false, '<p>' + e.clipboardData.getData('text/plain').replace(/[\r\n]/g, '</p><p>') + '</p>');

Jeff

i get this error "Uncaught TypeError: Cannot call method 'toLowerCase' of undefined medium.editor.js?da833dcf63a62ae704445479a4bbdc13fb4538d5:317"

when i select text of first line, then click h1 or h2 button, this error show up at console

i use this with meteor.js and my code is like

Template.editDocument.rendered = ->
    editor = new MediumEditor '.editable'

<template name="editDocument">

    {{#with document}}
        <div id="main-head">
            <a href="{{pathFor 'viewDocument'}}" class="title">{{title}}</a>
        </div>

        <div id="main-body">
            <div class="content editable" data-placeholder="开始写文档。。。">{{content}}</div>
        </div>
    {{/with}}

</template>

this is part of error codes indicate around 317

 while (this.parentElements.indexOf(tagName) === -1) {
            selectionEl = selectionEl.parentNode;
            tagName = selectionEl.tagName.toLowerCase();
        }

Better demo

We need a new demo page with on the fly feature switching and a better UI.

default to <p> tag

when i start typing in an empty editor, the content isn't wrapped in a < p > tag.

it works fine after a line-break...
screen shot 2013-09-26 at 10 51 35 am

Yet another broken WYSIWYG editor

The problem with contenteditable is that it does whatever it likes to do.

I played with the editor and quote features a while, and managed to get multiple paragraphs inside each other like this:

<p><q>idm</q><p><q>idm</q><p><q>idmidmidmmid</q><p><q>dimidmimd</q><p><q>t comes with it: the most popular gun in American crime. Like they're actually proud of that shit.  </q><p><p>idmidm</p></p></p></p></p></p></p>

But it looked as if it all was correct. though.

I think your editor is as fucked as the rest of them in this category.

p tags instead of divs

Starting with an empty editor and then inserting content will cause it to use <div> tags instead of a <p> tags. This seems to break the block level functions
Also pressing enter after a heading tag will do the same thing.

Possible solution is using a Mutation Observer to be notified when a new child element is added and if its a <div> change it to a <p>.

Support for Heading Tags <h1> through <h6>

While the current header1 and header2 for heading tags makes sense in situations where I have to give options for users to create headings but want them to use only few headings.

But, there are some limitations to current approach.

  • Currently only two headers are supported
  • It nags me everytime I click on H1 knowing its really <h3> that I'm entering

While I actually think header1 and header2 approach has its advantages, I have two suggestions that may improve current situation

  1. Increase the number of headers supported to about four.
  2. Give access to low level heading tags with different set of option labels, probably like <h1> <h2> <h3> etc.

PS: I love this project. Thanks a ton for making this wonderful editor.

Edit links

Just found this project. Very nice! One thing strikes me, however, with a mild level of annoyance; when you set a link (#, <a href>) to a selection, there is no way to edit that link. Clicking on the link button again clears the entire anchor. It should display the currently set link ref and have an X to dispose of it instead.

Control over pasted content

When HTML is pasted you might want to have a control over which tags or attributes to ignore.

Use case: When a user pastes content that contains

<p style="font-size: 40px; color: purple; background: green;">
    Lorem Ipsum is <b>important</b>
</p>

I would obviously want to ignore style attribute, but to preserve <b>, so it would rendered according to my stylesheet.

Copy-pasting from another topic: Hallo.js uses https://code.google.com/p/jquery-clean/ which is pretty powerful.

paste

remove formatting when paste

More understandable buttons

Hello,

This editor seems to be pretty good, but in my opinion the buttons are too much technical.

Personally I known what is a h1, a h2, or a b tag, but my grand-mother (and maybe a lot of people) doesn't.

It could be a good improvement to have understandable buttons for everyone.

<ul> & <ol> support

List support would be good to have. Either Hallo.js way via buttons on the toolbar or Medium way via intelligent number/dash parsing.

small issue with toolbar

hey @daviferreira
when u select a text which is near to the scrollbar breaks the toolbar element. saw in chrome.

UPDATE: i also noticed this behaviour.

Also Georgiana Wife of the Above,

select "above" from this sentence(second para, somewhere in the middle), toolbar covers that word when u apply H1.
also, selecting text near the scrollbar, toolbar hides the text. cant specify exactly where. one has to just try that to find it. most notably in the last para without scrolling that down, else it wont come into notice.

i guess it just css problem.

thanks

PlaceHolder remove innerHTML

The setPlaceholders function empty the innerHTML when textContent is void (https://github.com/daviferreira/medium-editor/blob/master/src/js/medium-editor.js#L528).
It creates a problem if the content is not really void but contains some <script> tags. For instance, Ember.js with Handlebars will add those to provide bindings.

A simple solution is to give an option to turn off placeholder. It would be even better if it was possible to add/remove the placeHolder manually without affecting the innerHTML content. I could then add / remove it depending on my bound content...

Thanks and continue your good work.

Jeff

[Proposal] Disable returns and/or Toolbar on elements

Disable the availability for the user to use return-key on element(s).

This would disable the use of return-key on a specific element.

<div class="editable" data-disable-returns="true"></div>

This will disable the use of return keys on all elements.

var elements = document.querySelectorAll('.editable');
new MediumEditor( elements, { enableReturn: false });

Disable the toolbar when user selects text

This would disable the toolbar on a specific element.

<div class="editable" data-disable-toolbar="true"></div>

This will disable the use of the toolbar on all elements.

var elements = document.querySelectorAll('.editable');
new MediumEditor( elements, { disableToolbar: true });

__

Let me know if you want the pull-request.

Modularize

Modularize MediumEditor components, specially util functions like getSelectionCoords etc.

Toolbar for images

It would be nice with an action/toolbar for images. One similar to the link-toolbar (basically just the src of the image) to be shown upon clicking on an image would be a good start.

npm publish?

This has a package.json, but isn't published to npm - do you plan to? Looks like this would only require a few tweaks to export MediumEditor and insert CSS with insert-css and you'd be good to go.

License

What is the License for this project?

Link bug in Firefox

I'm getting this linking bug in Firefox on a Mac when i create a link and continue writing - the link doesn't stop...
It's working fine in Chrome...

screen shot 2013-09-25 at 4 34 55 pm

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.