Coder Social home page Coder Social logo

leroyg / medium-editor Goto Github PK

View Code? Open in Web Editor NEW

This project forked from yabwe/medium-editor

0.0 2.0 0.0 3.06 MB

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

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

License: Other

JavaScript 86.18% HTML 6.82% CSS 7.00%

medium-editor's Introduction

MediumEditor

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

Firefox | Chrome | IE | Safari --- | --- | --- | --- | --- | Latest ✔ | Latest ✔ | IE 9+ ✔ | Latest ✔ |

NPM info

Travis build status dependencies devDependency Status Coverage Status

Basic usage

screenshot

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

Installation

Via bower:

Run in your console: bower install medium-editor

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

The next step is to reference the editor's script

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

Usage

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

Initialization options

Core options

  • allowMultiParagraphSelection: enables the toolbar when selecting multiple paragraphs/block elements. Default: true
  • cleanPastedHTML: cleans pasted content from different sources, like google docs etc. Default: false
  • 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
  • disablePlaceholders: enables/disables support for placeholder, including DOM element creation and attaching event handlers. When disabled, medium-editor will ignore the placholder option and not show placeholder text. Default: false
  • elementsContainer: specifies a DOM node to contain MediumEditor's toolbar and anchor preview elements. Default: document.body
  • extensions: extension to use (see Extensions) for more. Default: {}
  • firstHeader: HTML tag to be used as first header. Default: h3
  • forcePlainText: Forces pasting as plain text. Default: true
  • imageDragging: Allows image drag and drop into the editor. Default: true
  • placeholder: Defines the default placeholder for empty contenteditables when disablePlaceholders is not set to true. You can overwrite it by setting a data-placeholder attribute on your elements. Default: 'Type your text'
  • secondHeader: HTML tag to be used as second header. Default: h4
  • standardizeSelectionStart: Standardizes how the beginning of a range is decided between browsers whenever the selected text is analyzed for updating toolbar buttons status

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'
  • onShowToolbar: optional callback that will be called each time the toolbar is actually shown for this instance of medium-editor.
  • onHideToolbar: optional callback that will be called each time the toolbar is actually hidden for this instance of medium-editor.
  • 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 - Aligns the toolbar relative to the medium-editor element.
  • updateOnEmptySelection: update the state of the toolbar buttons even when the selection is collapse (there is no selection, just a cursor). Default: false

Anchor form options

  • anchorButton: enables/disables adding class anchorButtonClass to anchor tags. Default: false
  • anchorButtonClass: class to add to anchor tags, when anchorButton is set to true. Default: btn
  • anchorInputPlaceholder: text to be shown as placeholder of the anchor input. Default: Paste or type a link
  • anchorInputCheckboxLabel: text to be shown for the anchor new window target. Default: Open in new window
  • anchorPreviewHideDelay: time in milliseconds to show the anchor tag preview after the mouse has left the anchor tag. Default: 500
  • checkLinkFormat: enables/disables check for common URL protocols on anchor links. Default: false
  • targetBlank: enables/disables target="_blank" for anchor tags. Default: false

Example:

var editor = new MediumEditor('.editable', {
    anchorInputPlaceholder: 'Type a link',
    buttons: ['bold', 'italic', 'quote'],
    diffLeft: 25,
    diffTop: 10,
    firstHeader: 'h1',
    secondHeader: 'h2',
    delay: 1000,
    targetBlank: true
});

Extra buttons

Medium Editor, by default, will show only the buttons listed above to avoid a huge toolbar. There are a couple of 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)

Themes

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

API

  • .deactivate(): disables the editor
  • .activate(): re-activates the editor
  • .serialize(): returns a JSON object with elements contents

Capturing DOM changes

For observing any changes on contentEditable

$('.editable').on('input', function() {
  // Do some work
});

This is handy when you need to capture modifications other thats outside of key up's scope like clicking on toolbar buttons.

input is supported by Chrome, Firefox, IE9 and other modern browsers. If you want to read more or support older browsers, check Listening to events of a contenteditable HTML element and Detect changes in the DOM

MediumButton

Patrick Stillhar developed a new and improved way to add buttons to our toolbar. Check it out at: https://stillhart.biz/project/MediumButton/

Extensions

To add additional functions that are not supported by the native browser API you can write extensions that are then integrated into the toolbar. The Extension API is currently unstable and very minimal.

An extension is an object that has essentially three functions getButton, getForm and checkState.

  • getButton is called when the editor is initialized and should return an element that is integrated into the toolbar. Usually this will be a <button> element like the ones Medium Editor uses. All event handling on this button is entirely up to you so you should either keep a reference or bind your eventhandlers before returning it. You can also return a HTML-String that is then integrated into the toolbar also this is not really useful.

  • getForm is called when the editor is initialized and should return an element that is integrated into the toolbar. Usually this will be a <div> element that would contain some input elements. Handling the saving and canceling of the form is entirely up to you so you should either keep a reference or bind your eventhandlers before returning it.

  • checkState is called whenever a user selects some text in the area where the Medium Editor instance is running. It's responsability is to toggle the current state of the button. I.e. marking is a on or off. Again the method on how determine the state is entirely up to you. checkState will be called multiple times and will receive a DOM Element as parameter.

Properties

  • parent add this property to your extension class constructor and set it to true to be able to access the Medium Editor instance through the base property that will be set during the initialization

  • hasForm add this property to your extension class constructor and set it to true to tell Medium Editor that your extension contains a form. Medium editor will handle opening the form for you on your extension's button click.

Examples

A simple example the uses rangy and the CSS Class Applier Module to support highlighting of text:

rangy.init();

function Highlighter() {
    this.button = document.createElement('button');
    this.button.className = 'medium-editor-action';
    this.button.textContent = 'H';
    this.button.onclick = this.onClick.bind(this);
    this.classApplier = rangy.createCssClassApplier("highlight", {
        elementTagName: 'mark',
        normalize: true
    });
}
Highlighter.prototype.onClick = function() {
    this.classApplier.toggleSelection();
};
Highlighter.prototype.getButton = function() {
    return this.button;
};
Highlighter.prototype.checkState = function (node) {
    if(node.tagName == 'MARK') {
        this.button.classList.add('medium-editor-button-active');
    }
};

var e = new MediumEditor('.editor', {
    buttons: ['highlight', 'bold', 'italic', 'underline'],
    extensions: {
        'highlight': new Highlighter()
    }
});

A simple example that uses the parent attribute:

function Extension() {
  this.parent = true;

  this.button = document.createElement('button');
  this.button.className = 'medium-editor-action';
  this.button.textContent = 'X';
  this.button.onclick = this.onClick.bind(this);
}

Extension.prototype.getButton = function() {
  return this.button;
};

Extension.prototype.onClick = function() {
  alert('This is editor: #' + this.base.id);
};

var one = new MediumEditor('.one', {
    buttons: ['extension'],
    extensions: {
      extension: new Extension()
    }
});

var two = new MediumEditor('.two', {
    buttons: ['extension'],
    extensions: {
      extension: new Extension()
    }
});

A simple table example that uses the hasForm attribute:

function Table(options) {
    this.parent = true;
    this.hasForm = true;
    this.options = options;
}

Table.prototype.init = function() {
    this.createButton();
    this.createForm();
};

Table.prototype.createButton = function() {
    this.button = document.createElement('button');
    this.button.className = 'medium-editor-action';
    this.button.textContent = 'T';
    if(this.base.options.buttonLabels === 'fontawesome'){
        this.button.innerHTML = '<i class="fa fa-table"></i>';
    }
    this.button.onclick = this.onClick.bind(this);
};

Table.prototype.createForm = function() {
    this.form = document.createElement('div'),
    this.close = document.createElement('a'),
    this.save = document.createElement('a'),
    this.columnInput = document.createElement('input'),
    this.rowInput = document.createElement('input');

    this.close.setAttribute('href', '#');
    this.close.innerHTML = '&times;';
    this.close.onclick = this.onClose.bind(this);

    this.save.setAttribute('href', '#');
    this.save.innerHTML = '&#10003;';
    this.save.onclick = this.onSave.bind(this);

    this.columnInput.setAttribute('type', 'text');
    // Add the input CSS class for correct styling
    this.columnInput.className = 'medium-editor-toolbar-input';
    this.columnInput.setAttribute('placeholder', 'Column Count');

    this.rowInput.setAttribute('type', 'text');
    // Add the input CSS class for correct styling
    this.rowInput.className = 'medium-editor-toolbar-input';
    this.rowInput.setAttribute('placeholder', 'Row Count');

    this.form.appendChild(this.columnInput);
    this.form.appendChild(this.rowInput);

    this.form.appendChild(this.save);
    this.form.appendChild(this.close);
};

Table.prototype.getButton = function() {
    return this.button;
};

Table.prototype.getForm = function() {
    return this.form;
};

Table.prototype.onClick = function() {
    this.columnInput.value = this.options.defaultColumns;
    this.rowInput.value = this.options.defaultRows;
};

Table.prototype.onClose = function(e) {
    e.preventDefault();
    this.base.hideForm(this.form);
};

Table.prototype.onSave = function(e) {
    e.preventDefault();
    var columnCount = this.columnInput.value;
    var rowCount = this.rowInput.value;
    var table = this.createTable(columnCount, rowCount);

    this.base.pasteHTML(table.innerHTML);
    this.base.hideForm(this.form);
};

// Create the table element.
Table.prototype.createTable = function(cols, rows) {
    var table = document.createElement('table'),
        header = document.createElement('thead'),
        headerRow = document.createElement('tr'),
        body = document.createElement('tbody'),
        wrap = document.createElement('div');

    for (var h = 1; h <= cols; h++) {
        var headerCol = document.createElement('th');
        headerCol.innerHTML = '...';
        headerRow.appendChild(headerCol);
    }

    header.appendChild(headerRow);

    for (var r = 1; r <= rows; r++) {
        var bodyRow = document.createElement('tr');
        for (var c = 1; c <= this.options.defaultColumns; c++) {
            var bodyCol = document.createElement('td');
            bodyCol.innerHTML = '...';
            bodyRow.appendChild(bodyCol);
        }
        body.appendChild(bodyRow);
    }

    table.appendChild(header);
    table.appendChild(body);
    wrap.appendChild(table);

    return wrap;
};

// When creating your Medium Editor
var editor = new MediumEditor('.editable', {
    buttons: ['table'],
    extensions: {
        'table': new Table({
            defaultColumns: 3,
            defaultRows: 2
        }),
});

Image Upload & embeds

Pavel Linkesch has developed a jQuery plugin to upload images & embed content (from Twitter, Youtube, Vimeo, etc.) following Medium.com functionality. Check it out at http://orthes.github.io/medium-editor-insert-plugin/

Markdown

Ionică Bizău has created a Medium Editor extension, named Medium Editor Markdown, to add the functionality to render the HTML into Markdown code. Check it out at https://github.com/IonicaBizau/medium-editor-markdown.

Tables

A tables support extension is available at https://github.com/daviferreira/medium-editor-tables.

Inserting custom HTML

At @jillix, Ionică Bizău developed a Medium Editor extension that allows inserting custom HTML into the editor. Check it out here: https://github.com/jillix/medium-editor-custom-html.

Laravel

Zvonko Biškup has written an awesome tutorial about how to integrate MediumEditor into your Laravel Project.

Rails Gem

Ahmet Sezgin Duran gemified Medium Editor for Rails asset pipeline, check it out at https://github.com/marjinal1st/medium-editor-rails.

Angular directive

Thijs Wijnmaalen hacked together an AngularJS directive. Check it out at https://github.com/thijsw/angular-medium-editor

Development

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

npm install

These are the 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

The source files are located inside the src directory.

Contributing

  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')
  6. Push to the branch (git push origin my-new-feature)
  7. Create new Pull Request

Contributors

 project  : medium-editor
repo age : 1 year, 8 months
active   : 239 days
commits  : 863
files    : 62
authors  :
 550	Davi Ferreira           63.7%
  48	Nate Mielnik            5.6%
  30	Andy Yaco-Mink          3.5%
  21	Noah Chase              2.4%
  20	Maxime de Visscher      2.3%
   8	Derek Odegard           0.9%
   8	Jarl Gunnar T. Flaten   0.9%
   8	Pedro Nasser            0.9%
   8	Seif                    0.9%
   7	Aidan Threadgold        0.8%
   7	Alfonso (the fonz) de la Osa 0.8%
   7	Charl Gottschalk        0.8%
   7	OmniaGM                 0.8%
   6	Dayjo                   0.7%
   6	Pascal                  0.7%
   5	Martin Thurau           0.6%
   5	Raul Matei              0.6%
   4	Sebastian Zuchmanski    0.5%
   4	minikomi                0.5%
   3	Andrew Hubbs            0.3%
   3	Brian Reavis            0.3%
   3	Dmitri Cherniak         0.3%
   3	Ionică Bizău            0.3%
   3	Javier Marín            0.3%
   3	Nikita Korotaev         0.3%
   3	Patrick Cavanaugh       0.3%
   3	Pavel Linkesch          0.3%
   3	Troels Knak-Nielsen     0.3%
   3	arol                    0.3%
   3	ʞuıɯ-oɔɐʎ ʎpuɐ          0.3%
   2	Alexander Hofbauer      0.2%
   2	David Van Der Beek      0.2%
   2	Ethan Turkeltaub        0.2%
   2	Jacob Magnusson         0.2%
   2	Jeremy                  0.2%
   2	Joel                    0.2%
   2	Karl Sander             0.2%
   2	Robin Andersson         0.2%
   2	Son Tran-Nguyen         0.2%
   2	jj                      0.2%
   2	mako                    0.2%
   1	Adam Mulligan           0.1%
   1	Alberto Gasparin        0.1%
   1	Bitdeli Chef            0.1%
   1	Brooke McKim            0.1%
   1	Bruno Peres             0.1%
   1	Carlos Alexandre Fuechter 0.1%
   1	Cenk Dölek             0.1%
   1	Chris                   0.1%
   1	Dave Jarvis             0.1%
   1	David Collien           0.1%
   1	David Hellsing          0.1%
   1	Denis Gorbachev         0.1%
   1	Diana Liao              0.1%
   1	Enrico Berti            0.1%
   1	Harry Hogg              0.1%
   1	Harshil Shah            0.1%
   1	IndieSquidge            0.1%
   1	Jack Parker             0.1%
   1	Jeff Bellsey            0.1%
   1	Jeff Welch              0.1%
   1	Johann Troendle         0.1%
   1	Mark Kraemer            0.1%
   1	Max                     0.1%
   1	Maxime Dantec           0.1%
   1	Maxime De Visscher      0.1%
   1	Michael Kay             0.1%
   1	Moore Adam              0.1%
   1	Nic Malan               0.1%
   1	Nick Semenkovich        0.1%
   1	Noah Paessel            0.1%
   1	Patrick Kempff          0.1%
   1	Peleg Rosenthal         0.1%
   1	Randson Oliveira        0.1%
   1	Richard Park            0.1%
   1	Robert Koritnik         0.1%
   1	Sarah Squire            0.1%
   1	Scott Carleton          0.1%
   1	Søren Torp Petersen     0.1%
   1	Tom MacWright           0.1%
   1	happyaccidents          0.1%
   1	mako yass               0.1%
   1	mbrookes                0.1%
   1	muescha                 0.1%
   1	shaohua                 0.1%
   1	t_kjaergaard            0.1%
   1	typify                  0.1%
   1	valentinnew             0.1%
   1	waffleio                0.1%
   1	zzjin                   0.1%

License

"THE BEER-WARE LICENSE" (Revision 42):

As long as you retain this notice you can do whatever you want with this stuff. If we meet some day, and you think this stuff is worth it, you can buy me a beer in return.

medium-editor's People

Contributors

daviferreira avatar nmielnik avatar nchase avatar devalnor avatar pixelpony avatar jarlg avatar omniagm avatar pedronasser avatar pascalpp avatar dayjo avatar charlgottschalk avatar zuchmanski avatar minikomi avatar steoo avatar linkesch avatar arol avatar troelskn avatar patrickwebs avatar javiermarinros avatar ionicabizau avatar dmitric avatar brianreavis avatar andrewhubbs avatar petrjasek avatar jjjjw avatar robinwassen avatar martinth avatar karlsander avatar j0k3r avatar jacobsvante avatar

Watchers

LeRoy Gardner avatar James Cloos avatar

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.