UNPKG

medium-editor

Version:
273 lines (240 loc) 10.2 kB
(function () { 'use strict'; var Extension = function (options) { MediumEditor.util.extend(this, options); }; Extension.extend = function (protoProps) { // magic extender thinger. mostly borrowed from backbone/goog.inherits // place this function on some thing you want extend-able. // // example: // // function Thing(args){ // this.options = args; // } // // Thing.prototype = { foo: "bar" }; // Thing.extend = extenderify; // // var ThingTwo = Thing.extend({ foo: "baz" }); // // var thingOne = new Thing(); // foo === "bar" // var thingTwo = new ThingTwo(); // foo === "baz" // // which seems like some simply shallow copy nonsense // at first, but a lot more is going on there. // // passing a `constructor` to the extend props // will cause the instance to instantiate through that // instead of the parent's constructor. var parent = this, child; // The constructor function for the new subclass is either defined by you // (the "constructor" property in your `extend` definition), or defaulted // by us to simply call the parent's constructor. if (protoProps && protoProps.hasOwnProperty('constructor')) { child = protoProps.constructor; } else { child = function () { return parent.apply(this, arguments); }; } // das statics (.extend comes over, so your subclass can have subclasses too) MediumEditor.util.extend(child, parent); // Set the prototype chain to inherit from `parent`, without calling // `parent`'s constructor function. var Surrogate = function () { this.constructor = child; }; Surrogate.prototype = parent.prototype; child.prototype = new Surrogate(); if (protoProps) { MediumEditor.util.extend(child.prototype, protoProps); } // todo: $super? return child; }; Extension.prototype = { /* init: [function] * * Called by MediumEditor during initialization. * The .base property will already have been set to * current instance of MediumEditor when this is called. * All helper methods will exist as well */ init: function () {}, /* base: [MediumEditor instance] * * If not overriden, this will be set to the current instance * of MediumEditor, before the init method is called */ base: undefined, /* name: [string] * * 'name' of the extension, used for retrieving the extension. * If not set, MediumEditor will set this to be the key * used when passing the extension into MediumEditor via the * 'extensions' option */ name: undefined, /* checkState: [function (node)] * * If implemented, this function will be called one or more times * the state of the editor & toolbar are updated. * When the state is updated, the editor does the following: * * 1) Find the parent node containing the current selection * 2) Call checkState on the extension, passing the node as an argument * 3) Get the parent node of the previous node * 4) Repeat steps #2 and #3 until we move outside the parent contenteditable */ checkState: undefined, /* destroy: [function ()] * * This method should remove any created html, custom event handlers * or any other cleanup tasks that should be performed. * If implemented, this function will be called when MediumEditor's * destroy method has been called. */ destroy: undefined, /* As alternatives to checkState, these functions provide a more structured * path to updating the state of an extension (usually a button) whenever * the state of the editor & toolbar are updated. */ /* queryCommandState: [function ()] * * If implemented, this function will be called once on each extension * when the state of the editor/toolbar is being updated. * * If this function returns a non-null value, the extension will * be ignored as the code climbs the dom tree. * * If this function returns true, and the setActive() function is defined * setActive() will be called */ queryCommandState: undefined, /* isActive: [function ()] * * If implemented, this function will be called when MediumEditor * has determined that this extension is 'active' for the current selection. * This may be called when the editor & toolbar are being updated, * but only if queryCommandState() or isAlreadyApplied() functions * are implemented, and when called, return true. */ isActive: undefined, /* isAlreadyApplied: [function (node)] * * If implemented, this function is similar to checkState() in * that it will be called repeatedly as MediumEditor moves up * the DOM to update the editor & toolbar after a state change. * * NOTE: This function will NOT be called if checkState() has * been implemented. This function will NOT be called if * queryCommandState() is implemented and returns a non-null * value when called */ isAlreadyApplied: undefined, /* setActive: [function ()] * * If implemented, this function is called when MediumEditor knows * that this extension is currently enabled. Currently, this * function is called when updating the editor & toolbar, and * only if queryCommandState() or isAlreadyApplied(node) return * true when called */ setActive: undefined, /* setInactive: [function ()] * * If implemented, this function is called when MediumEditor knows * that this extension is currently disabled. Curently, this * is called at the beginning of each state change for * the editor & toolbar. After calling this, MediumEditor * will attempt to update the extension, either via checkState() * or the combination of queryCommandState(), isAlreadyApplied(node), * isActive(), and setActive() */ setInactive: undefined, /* getInteractionElements: [function ()] * * If the extension renders any elements that the user can interact with, * this method should be implemented and return the root element or an array * containing all of the root elements. MediumEditor will call this function * during interaction to see if the user clicked on something outside of the editor. * The elements are used to check if the target element of a click or * other user event is a descendant of any extension elements. * This way, the editor can also count user interaction within editor elements as * interactions with the editor, and thus not trigger 'blur' */ getInteractionElements: undefined, /************************ Helpers ************************ * The following are helpers that are either set by MediumEditor * during initialization, or are helper methods which either * route calls to the MediumEditor instance or provide common * functionality for all extensions *********************************************************/ /* window: [Window] * * If not overriden, this will be set to the window object * to be used by MediumEditor and its extensions. This is * passed via the 'contentWindow' option to MediumEditor * and is the global 'window' object by default */ 'window': undefined, /* document: [Document] * * If not overriden, this will be set to the document object * to be used by MediumEditor and its extensions. This is * passed via the 'ownerDocument' optin to MediumEditor * and is the global 'document' object by default */ 'document': undefined, /* getEditorElements: [function ()] * * Helper function which returns an array containing * all the contenteditable elements for this instance * of MediumEditor */ getEditorElements: function () { return this.base.elements; }, /* getEditorId: [function ()] * * Helper function which returns a unique identifier * for this instance of MediumEditor */ getEditorId: function () { return this.base.id; }, /* getEditorOptions: [function (option)] * * Helper function which returns the value of an option * used to initialize this instance of MediumEditor */ getEditorOption: function (option) { return this.base.options[option]; } }; /* List of method names to add to the prototype of Extension * Each of these methods will be defined as helpers that * just call directly into the MediumEditor instance. * * example for 'on' method: * Extension.prototype.on = function () { * return this.base.on.apply(this.base, arguments); * } */ [ // general helpers 'execAction', // event handling 'on', 'off', 'subscribe', 'trigger' ].forEach(function (helper) { Extension.prototype[helper] = function () { return this.base[helper].apply(this.base, arguments); }; }); MediumEditor.Extension = Extension; })();