diff --git a/demo/demo.js b/demo/demo.js index dadff3ec6..1c751f968 100644 --- a/demo/demo.js +++ b/demo/demo.js @@ -2,8 +2,8 @@ 'use strict'; -exports.ContentKitDemo = { - toggleCodePane: function(editor) { +var ContentKitDemo = exports.ContentKitDemo = { + toggleCodePane: function() { if(document.body.className === 'code-pane-open') { this.closeCodePane(); } else { @@ -11,25 +11,26 @@ exports.ContentKitDemo = { } }, - openCodePane: function(editor) { - this.syncCodePane(editor); + openCodePane: function() { window.getSelection().removeAllRanges(); document.body.className = 'code-pane-open'; + location.hash = 'code'; }, closeCodePane: function() { window.getSelection().removeAllRanges(); document.body.className = ''; + location.hash = ''; }, syncCodePane: function(editor) { var codePaneJSON = document.getElementById('code-json'); var codePaneHTML = document.getElementById('code-html'); var json = editor.model; - var html = editor.compiler.render(json); + //var html = editor.compiler.render(json); codePaneJSON.innerHTML = this.syntaxHighlight(json); - codePaneHTML.textContent = this.formatXML(html); + //codePaneHTML.textContent = this.formatXML(html); }, formatXML: function(xml) { @@ -91,4 +92,12 @@ exports.ContentKitDemo = { }; +// Initialize +if (editor) { + ContentKitDemo.syncCodePane(editor); +} +if (location.hash === '#code') { + ContentKitDemo.openCodePane(); +} + }(this, document)); diff --git a/demo/index.html b/demo/index.html index 66e072abc..92842fe66 100644 --- a/demo/index.html +++ b/demo/index.html @@ -16,7 +16,7 @@

ContentKit Editor

- +
diff --git a/dist/content-kit-editor.js b/dist/content-kit-editor.js index 3b3013f58..89c70e30c 100755 --- a/dist/content-kit-editor.js +++ b/dist/content-kit-editor.js @@ -3,7 +3,7 @@ * @version 0.1.0 * @author Garth Poitras (http://garthpoitras.com/) * @license MIT - * Last modified: Aug 22, 2014 + * Last modified: Aug 25, 2014 */ (function(exports, document) { @@ -11,7 +11,7 @@ 'use strict'; define("content-kit", - ["./content-kit-compiler/types/type","./content-kit-compiler/models/block","./content-kit-compiler/models/text","./content-kit-compiler/models/image","./content-kit-compiler/models/embed","./content-kit-compiler/compiler","./content-kit-compiler/parsers/html-parser","./content-kit-compiler/renderers/html-renderer","./content-kit-editor/editor-factory","exports"], + ["./content-kit-compiler/types/type","./content-kit-compiler/models/block","./content-kit-compiler/models/text","./content-kit-compiler/models/image","./content-kit-compiler/models/embed","./content-kit-compiler/compiler","./content-kit-compiler/parsers/html-parser","./content-kit-compiler/renderers/html-renderer","./content-kit-editor/editor/editor-factory","exports"], function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __dependency9__, __exports__) { "use strict"; var Type = __dependency1__["default"]; @@ -39,6 +39,75 @@ define("content-kit", __exports__["default"] = ContentKit; }); +define("content-kit-compiler/compiler", + ["./parsers/html-parser","./renderers/html-renderer","./types/default-types","../content-kit-utils/object-utils","exports"], + function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __exports__) { + "use strict"; + var HTMLParser = __dependency1__["default"]; + var HTMLRenderer = __dependency2__["default"]; + var DefaultBlockTypeSet = __dependency3__.DefaultBlockTypeSet; + var DefaultMarkupTypeSet = __dependency3__.DefaultMarkupTypeSet; + var mergeWithOptions = __dependency4__.mergeWithOptions; + + /** + * @class Compiler + * @constructor + * @param options + */ + function Compiler(options) { + var parser = new HTMLParser(); + var renderer = new HTMLRenderer(); + var defaults = { + parser : parser, + renderer : renderer, + blockTypes : DefaultBlockTypeSet, + markupTypes : DefaultMarkupTypeSet, + includeTypeNames : false // true will output type_name: 'TEXT' etc. when parsing for easier debugging + }; + mergeWithOptions(this, defaults, options); + + // Reference the compiler settings + parser.blockTypes = renderer.blockTypes = this.blockTypes; + parser.markupTypes = renderer.markupTypes = this.markupTypes; + parser.includeTypeNames = this.includeTypeNames; + } + + /** + * @method parse + * @param input + * @return Object + */ + Compiler.prototype.parse = function(input) { + return this.parser.parse(input); + }; + + /** + * @method render + * @param data + * @return Object + */ + Compiler.prototype.render = function(data) { + return this.renderer.render(data); + }; + + /** + * @method registerBlockType + * @param {Type} type + */ + Compiler.prototype.registerBlockType = function(type) { + return this.blockTypes.addType(type); + }; + + /** + * @method registerMarkupType + * @param {Type} type + */ + Compiler.prototype.registerMarkupType = function(type) { + return this.markupTypes.addType(type); + }; + + __exports__["default"] = Compiler; + }); define("content-kit-editor/constants", ["exports"], function(__exports__) { @@ -92,616 +161,228 @@ define("content-kit-editor/constants", __exports__.Tags = Tags; __exports__.RootTags = RootTags; }); -define("content-kit-editor/editor-factory", - ["./editor","./commands/commands","./constants","../content-kit-utils/object-utils","exports"], - function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __exports__) { +define("content-kit-utils/array-utils", + ["exports"], + function(__exports__) { "use strict"; - var Editor = __dependency1__["default"]; - var TextFormatCommands = __dependency2__.TextFormatCommands; - var EmbedCommands = __dependency2__.EmbedCommands; - var Tags = __dependency3__.Tags; - var merge = __dependency4__.merge; - - var defaults = { - defaultFormatter: Tags.PARAGRAPH, - placeholder: 'Write here...', - spellcheck: true, - autofocus: true, - textFormatCommands: TextFormatCommands.all, - embedCommands: EmbedCommands.all - }; - /** - * Publically expose this class which sets up indiviual `Editor` classes - * depending if user passes string selector, Node, or NodeList + * Converts an array-like object (i.e. NodeList) to Array + * Note: could just use Array.prototype.slice but does not work in IE <= 8 */ - function EditorFactory(element, options) { - var editors = []; - var elements, elementsLen, i; - - if (typeof element === 'string') { - elements = document.querySelectorAll(element); - } else if (element && element.length) { - elements = element; - } else if (element) { - elements = [element]; + function toArray(obj) { + var array = []; + var i = obj && obj.length >>> 0; // cast to Uint32 + while (i--) { + array[i] = obj[i]; } + return array; + } - if (elements) { - options = merge(defaults, options); - elementsLen = elements.length; - for (i = 0; i < elementsLen; i++) { - editors.push(new Editor(elements[i], options)); + /** + * Computes the sum of values in a (sparse) array + */ + function sumSparseArray(array) { + var sum = 0, i; + for (i in array) { // 'for in' is better for sparse arrays + if (array.hasOwnProperty(i)) { + sum += array[i]; } } - - return editors.length > 1 ? editors : editors[0]; + return sum; } - __exports__["default"] = EditorFactory; + __exports__.toArray = toArray; + __exports__.sumSparseArray = sumSparseArray; }); -define("content-kit-editor/editor-html-renderer", - ["../content-kit-compiler/renderers/html-renderer","../content-kit-compiler/types/type","../content-kit-utils/object-utils","exports"], - function(__dependency1__, __dependency2__, __dependency3__, __exports__) { +define("content-kit-utils/node-utils", + ["./string-utils","./array-utils","exports"], + function(__dependency1__, __dependency2__, __exports__) { "use strict"; - var HTMLRenderer = __dependency1__["default"]; - var Type = __dependency2__["default"]; - var inherit = __dependency3__.inherit; - - function embedRenderer(model) { - var embedAttrs = model.attributes; - var isVideo = embedAttrs.embed_type === 'video'; - return '
' + - '
' + - (isVideo ? '
' : '') + this.render(model) + (isVideo ? '
' : '') + - '
' + embedAttrs.provider_name + ': ' + - '' + embedAttrs.title + '' + - '
' + - '
' + - '
'; - } + var sanitizeWhitespace = __dependency1__.sanitizeWhitespace; + var toArray = __dependency2__.toArray; - function imageRenderer(model) { - return '
' + - '
' + this.render(model) + '
' + - '
'; - } + /** + * A document instance separate from the page's document. (if browser supports it) + * Prevents images, scripts, and styles from executing while parsing nodes. + */ + var standaloneDocument = (function() { + var implementation = document.implementation; + var createHTMLDocument = implementation.createHTMLDocument; - var typeRenderers = {}; - typeRenderers[Type.EMBED.id] = embedRenderer; - typeRenderers[Type.IMAGE.id] = imageRenderer; + if (createHTMLDocument) { + return createHTMLDocument.call(implementation, ''); + } + return document; + })(); /** - * @class EditorHTMLRenderer - * @constructor - * Subclass of HTMLRenderer specifically for the Editor - * Wraps interactive elements to add functionality + * document.createElement with our lean, standalone document */ - function EditorHTMLRenderer() { - HTMLRenderer.call(this, { - typeRenderers: typeRenderers - }); + function createElement(type) { + return standaloneDocument.createElement(type); } - inherit(EditorHTMLRenderer, HTMLRenderer); - __exports__["default"] = EditorHTMLRenderer; - }); -define("content-kit-editor/editor", - ["./views/text-format-toolbar","./views/tooltip","./views/embed-intent","./commands/unordered-list","./commands/ordered-list","./commands/text-format","./constants","./utils/selection-utils","../content-kit-compiler/compiler","../content-kit-compiler/models/text","../content-kit-compiler/types/type","../content-kit-utils/array-utils","../content-kit-utils/object-utils","./editor-html-renderer","exports"], - function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __dependency9__, __dependency10__, __dependency11__, __dependency12__, __dependency13__, __dependency14__, __exports__) { - "use strict"; - var TextFormatToolbar = __dependency1__["default"]; - var Tooltip = __dependency2__["default"]; - var EmbedIntent = __dependency3__["default"]; - var UnorderedListCommand = __dependency4__["default"]; - var OrderedListCommand = __dependency5__["default"]; - var TextFormatCommand = __dependency6__["default"]; - var Tags = __dependency7__.Tags; - var RootTags = __dependency7__.RootTags; - var Keycodes = __dependency7__.Keycodes; - var RegEx = __dependency7__.RegEx; - var moveCursorToBeginningOfSelection = __dependency8__.moveCursorToBeginningOfSelection; - var getSelectionTagName = __dependency8__.getSelectionTagName; - var getSelectionBlockElement = __dependency8__.getSelectionBlockElement; - var getSelectionBlockTagName = __dependency8__.getSelectionBlockTagName; - var Compiler = __dependency9__["default"]; - var TextModel = __dependency10__["default"]; - var Type = __dependency11__["default"]; - var toArray = __dependency12__.toArray; - var merge = __dependency13__.merge; - var EditorHTMLRenderer = __dependency14__["default"]; + /** + * A reusable DOM Node for parsing html content. + */ + var DOMParsingNode = createElement('div'); - var editorClassName = 'ck-editor'; - var editorClassNameRegExp = new RegExp(editorClassName); + /** + * Returns plain-text of a `Node` + */ + function textOfNode(node) { + var text = node.textContent || node.innerText; + return text ? sanitizeWhitespace(text) : ''; + } - function plainTextToBlocks(plainText, blockTag) { - var blocks = plainText.split(RegEx.NEWLINE), - len = blocks.length, - block, openTag, closeTag, content, i; - if(len < 2) { - return plainText; - } else { - content = ''; - openTag = '<' + blockTag + '>'; - closeTag = ''; - for(i=0; i>> 0; // cast to Uint32 - while (i--) { - array[i] = obj[i]; - } - return array; - } - - /** - * Computes the sum of values in a (sparse) array - */ - function sumSparseArray(array) { - var sum = 0, i; - for (i in array) { // 'for in' is better for sparse arrays - if (array.hasOwnProperty(i)) { - sum += array[i]; - } - } - return sum; - } - - __exports__.toArray = toArray; - __exports__.sumSparseArray = sumSparseArray; - }); -define("content-kit-utils/node-utils", - ["./string-utils","./array-utils","exports"], - function(__dependency1__, __dependency2__, __exports__) { - "use strict"; - var sanitizeWhitespace = __dependency1__.sanitizeWhitespace; - var toArray = __dependency2__.toArray; - - /** - * A document instance separate from the page's document. (if browser supports it) - * Prevents images, scripts, and styles from executing while parsing nodes. - */ - var standaloneDocument = (function() { - var implementation = document.implementation; - var createHTMLDocument = implementation.createHTMLDocument; - - if (createHTMLDocument) { - return createHTMLDocument.call(implementation, ''); - } - return document; - })(); - - /** - * document.createElement with our lean, standalone document - */ - function createElement(type) { - return standaloneDocument.createElement(type); - } - - /** - * A reusable DOM Node for parsing html content. - */ - var DOMParsingNode = createElement('div'); - - /** - * Returns plain-text of a `Node` - */ - function textOfNode(node) { - var text = node.textContent || node.innerText; - return text ? sanitizeWhitespace(text) : ''; - } - - /** - * Replaces a `Node` with its children - */ - function unwrapNode(node) { - var children = toArray(node.childNodes); - var len = children.length; - var parent = node.parentNode, i; - for (i = 0; i < len; i++) { - parent.insertBefore(children[i], node); - } - } - - /** - * Extracts attributes of a `Node` to a hash of key/value pairs - */ - function attributesForNode(node /*,blacklist*/) { - var attrs = node.attributes; - var len = attrs && attrs.length; - var i, attr, name, hash; - - for (i = 0; i < len; i++) { - attr = attrs[i]; - name = attr.name; - if (attr.specified) { - //if (blacklist && name in blacklist)) { continue; } - hash = hash || {}; - hash[name] = attr.value; - } - } - return hash; - } - - __exports__.createElement = createElement; - __exports__.DOMParsingNode = DOMParsingNode; - __exports__.textOfNode = textOfNode; - __exports__.unwrapNode = unwrapNode; - __exports__.attributesForNode = attributesForNode; - }); -define("content-kit-utils/object-utils", - ["exports"], - function(__exports__) { - "use strict"; - /** - * Merges defaults/options into an Object - * Useful for constructors - */ - function mergeWithOptions(original, updates, options) { - options = options || {}; - for(var prop in updates) { - if (options.hasOwnProperty(prop)) { - original[prop] = options[prop]; - } else if (updates.hasOwnProperty(prop)) { - original[prop] = updates[prop]; - } - } - return original; - } - - /** - * Merges properties of one object into another - */ - function merge(original, updates) { - return mergeWithOptions(original, updates); - } - - /** - * Prototype inheritance helper - */ - function inherit(Subclass, Superclass) { - if (typeof Object.create === 'function') { - Subclass._super = Superclass; - Subclass.prototype = Object.create(Superclass.prototype, { - constructor: { - value: Subclass, - enumerable: false, - writable: true, - configurable: true - } - }); - } else { - for (var key in Superclass) { - if (Superclass.hasOwnProperty(key)) { - Subclass[key] = Superclass[key]; - } - } - Subclass.prototype = new Superclass(); - Subclass.constructor = Subclass; - } - } - - __exports__.mergeWithOptions = mergeWithOptions; - __exports__.merge = merge; - __exports__.inherit = inherit; - }); -define("content-kit-utils/string-utils", - ["exports"], - function(__exports__) { - "use strict"; - var RegExpTrim = /^\s+|\s+$/g; - var RegExpTrimLeft = /^\s+/; - var RegExpWSChars = /(\r\n|\n|\r|\t|\u00A0)/gm; - var RegExpMultiWS = /\s+/g; - var RegExpNonAlphaNum = /[^a-zA-Z\d]/g; - - /** - * String.prototype.trim polyfill - * Removes whitespace at beginning and end of string - */ - function trim(string) { - return string ? (string + '').replace(RegExpTrim, '') : ''; - } - - /** - * String.prototype.trimLeft polyfill - * Removes whitespace at beginning of string - */ - function trimLeft(string) { - return string ? (string + '').replace(RegExpTrimLeft, '') : ''; - } - - /** - * Replaces non-alphanumeric chars with underscores - */ - function underscore(string) { - return string ? trim(string + '').replace(RegExpNonAlphaNum, '_') : ''; - } - - /** - * Cleans line breaks, tabs, non-breaking spaces, then multiple occuring whitespaces. - */ - function sanitizeWhitespace(string) { - return string ? (string + '').replace(RegExpWSChars, '').replace(RegExpMultiWS, ' ') : ''; - } - - /** - * Injects a string into another string at the index specified - */ - function injectIntoString(string, injection, index) { - return string.substr(0, index) + injection + string.substr(index); - } - - __exports__.trim = trim; - __exports__.trimLeft = trimLeft; - __exports__.underscore = underscore; - __exports__.sanitizeWhitespace = sanitizeWhitespace; - __exports__.injectIntoString = injectIntoString; - }); -define("ext/content-kit-services", +define("ext/content-kit-services", ["exports"], function(__exports__) { "use strict"; @@ -954,190 +635,915 @@ define("ext/loader", }; })(); }); -define("content-kit-editor/commands/base", - ["exports"], - function(__exports__) { +define("content-kit-compiler/models/block", + ["./model","exports"], + function(__dependency1__, __exports__) { "use strict"; - function Command(options) { - var command = this; - var name = options.name; - var prompt = options.prompt; - command.name = name; - command.button = options.button || name; - command.editorContext = null; - if (prompt) { command.prompt = prompt; } + var Model = __dependency1__["default"]; + + /** + * Ensures block markups at the same index are always in a specific order. + * For example, so all bold links are consistently marked up + * as text instead of text + */ + function sortBlockMarkups(markups) { + return markups.sort(function(a, b) { + if (a.start === b.start && a.end === b.end) { + return b.type - a.type; + } + return 0; + }); } - Command.prototype.exec = function(){}; + /** + * @class BlockModel + * @constructor + * @extends Model + */ + function BlockModel(options) { + options = options || {}; + Model.call(this, options); + this.value = options.value || ''; + this.markup = sortBlockMarkups(options.markup || []); + } - __exports__["default"] = Command; + __exports__["default"] = BlockModel; }); -define("content-kit-editor/commands/bold", - ["./text-format","../../content-kit-utils/object-utils","../constants","../utils/selection-utils","exports"], - function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __exports__) { +define("content-kit-compiler/models/embed", + ["../models/model","../types/type","exports"], + function(__dependency1__, __dependency2__, __exports__) { "use strict"; - var TextFormatCommand = __dependency1__["default"]; - var inherit = __dependency2__.inherit; - var Tags = __dependency3__.Tags; - var RegEx = __dependency3__.RegEx; - var getSelectionBlockTagName = __dependency4__.getSelectionBlockTagName; + var Model = __dependency1__["default"]; + var Type = __dependency2__["default"]; - function BoldCommand() { - TextFormatCommand.call(this, { - name: 'bold', - tag: Tags.BOLD, - button: '' + /** + * @class EmbedModel + * @constructor + * @extends Model + * Massages data from an oEmbed response into an EmbedModel + */ + function EmbedModel(options) { + if (!options) { return null; } + + Model.call(this, { + type: Type.EMBED.id, + type_name: Type.EMBED.name, + attributes: {} }); - } - inherit(BoldCommand, TextFormatCommand); - BoldCommand.prototype.exec = function() { - // Don't allow executing bold command on heading tags - if (!RegEx.HEADING_TAG.test(getSelectionBlockTagName())) { - BoldCommand._super.prototype.exec.call(this); + var attributes = this.attributes; + var embedType = options.type; + var providerName = options.provider_name; + var embedUrl = options.url; + var embedTitle = options.title; + var embedThumbnail = options.thumbnail_url; + var embedHtml = options.html; + + if (embedType) { attributes.embed_type = embedType; } + if (providerName) { attributes.provider_name = providerName; } + if (embedUrl) { attributes.url = embedUrl; } + if (embedTitle) { attributes.title = embedTitle; } + + if (embedType === 'photo') { + attributes.thumbnail = options.media_url || embedUrl; + } else if (embedThumbnail) { + attributes.thumbnail = embedThumbnail; } - }; - __exports__["default"] = BoldCommand; + if (embedHtml && embedType === 'rich') { + attributes.html = embedHtml; + } + } + + __exports__["default"] = EmbedModel; }); -define("content-kit-editor/commands/commands", - ["./bold","./italic","./link","./quote","./heading","./subheading","./image","./embed","exports"], - function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __exports__) { +define("content-kit-compiler/models/image", + ["./block","../types/type","exports"], + function(__dependency1__, __dependency2__, __exports__) { "use strict"; - // TODO: eliminate this file - var BoldCommand = __dependency1__["default"]; - var ItalicCommand = __dependency2__["default"]; - var LinkCommand = __dependency3__["default"]; - var QuoteCommand = __dependency4__["default"]; - var HeadingCommand = __dependency5__["default"]; - var SubheadingCommand = __dependency6__["default"]; - var ImageCommand = __dependency7__["default"]; - var EmbedCommand = __dependency8__["default"]; + var BlockModel = __dependency1__["default"]; + var Type = __dependency2__["default"]; - function createCommandIndex(commands) { - var index = {}; - var len = commands.length, i, command; - for(i = 0; i < len; i++) { - command = commands[i]; - index[command.name] = command; + /** + * @class ImageModel + * @constructor + * @extends BlockModel + * A simple BlockModel subclass representing an image + */ + function ImageModel(options) { + options = options || {}; + options.type = Type.IMAGE.id; + options.type_name = Type.IMAGE.name; + if (options.src) { + options.attributes = { src: options.src }; } - return index; + BlockModel.call(this, options); } - var TextFormatCommands = {}; - TextFormatCommands.all = [ - new BoldCommand(), - new ItalicCommand(), - new LinkCommand(), - new QuoteCommand(), - new HeadingCommand(), - new SubheadingCommand() - ]; - - TextFormatCommands.index = createCommandIndex(TextFormatCommands.all); - - var EmbedCommands = {}; - EmbedCommands.all = [ - new ImageCommand(), - new EmbedCommand() - ]; - EmbedCommands.index = createCommandIndex(EmbedCommands.all); - - __exports__.TextFormatCommands = TextFormatCommands; - __exports__.EmbedCommands = EmbedCommands; + __exports__["default"] = ImageModel; }); -define("content-kit-editor/commands/embed", - ["./base","../views/prompt","../views/message","../../content-kit-compiler/models/embed","../../content-kit-utils/object-utils","../constants","../../ext/content-kit-services","exports"], - function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __exports__) { +define("content-kit-compiler/models/markup", + ["./model","exports"], + function(__dependency1__, __exports__) { "use strict"; - var Command = __dependency1__["default"]; - var Prompt = __dependency2__["default"]; - var Message = __dependency3__["default"]; - var EmbedModel = __dependency4__["default"]; - var inherit = __dependency5__.inherit; - var RegEx = __dependency6__.RegEx; - var OEmbedder = __dependency7__.OEmbedder; + var Model = __dependency1__["default"]; - function loadTwitterWidgets(element) { - if (window.twttr) { - window.twttr.widgets.load(element); - } else { - var script = document.createElement('script'); - script.async = true; - script.src = 'http://platform.twitter.com/widgets.js'; - document.head.appendChild(script); - } + /** + * @class MarkupModel + * @constructor + * @extends Model + */ + function MarkupModel(options) { + options = options || {}; + Model.call(this, options); + this.start = options.start || 0; + this.end = options.end || 0; } - function EmbedCommand(options) { - Command.call(this, { - name: 'embed', - button: '', - prompt: new Prompt({ - command: this, - placeholder: 'Paste a YouTube or Twitter url...' - }) - }); + __exports__["default"] = MarkupModel; + }); +define("content-kit-compiler/models/model", + ["exports"], + function(__exports__) { + "use strict"; + /** + * @class Model + * @constructor + * @private + */ + function Model(options) { + options = options || {}; + var type_name = options.type_name; + var attributes = options.attributes; - this.embedService = new OEmbedder({ url: '/embed' }); + this.type = options.type || null; + if (type_name) { + this.type_name = type_name; + } + if (attributes) { + this.attributes = attributes; + } } - inherit(EmbedCommand, Command); - - EmbedCommand.prototype.exec = function(url) { - var command = this; - var editorContext = command.editorContext; - var embedIntent = command.embedIntent; - var index = editorContext.getCurrentBlockIndex(); - - embedIntent.showLoading(); - this.embedService.fetch({ - url: url, - complete: function(response, error) { - embedIntent.hideLoading(); - if (error) { - var errorMsg = error; - if (error.target && error.target.status === 0) { - errorMsg = 'Could not connect to embed service'; - } else if (typeof error !== 'string') { - errorMsg = 'Embed error'; - } - new Message().show(errorMsg); - } else { - var embedModel = new EmbedModel(response); - editorContext.insertBlockAt(embedModel, index); - editorContext.syncVisualAt(index); - if (embedModel.attributes.provider_name.toLowerCase() === 'twitter') { - loadTwitterWidgets(editorContext.element); - } - } - } - }); - }; - __exports__["default"] = EmbedCommand; + __exports__["default"] = Model; }); -define("content-kit-editor/commands/format-block", - ["./text-format","../constants","../../content-kit-utils/object-utils","../utils/selection-utils","exports"], - function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __exports__) { +define("content-kit-compiler/models/text", + ["./block","../types/type","exports"], + function(__dependency1__, __dependency2__, __exports__) { "use strict"; - var TextFormatCommand = __dependency1__["default"]; - var Tags = __dependency2__.Tags; - var inherit = __dependency3__.inherit; - var getSelectionBlockElement = __dependency4__.getSelectionBlockElement; - var selectNode = __dependency4__.selectNode; + var BlockModel = __dependency1__["default"]; + var Type = __dependency2__["default"]; - function FormatBlockCommand(options) { - options.action = 'formatBlock'; - TextFormatCommand.call(this, options); + /** + * @class TextModel + * @constructor + * @extends BlockModel + * A simple BlockModel subclass representing a paragraph of text + */ + function TextModel(options) { + options = options || {}; + options.type = Type.TEXT.id; + options.type_name = Type.TEXT.name; + BlockModel.call(this, options); } - inherit(FormatBlockCommand, TextFormatCommand); - FormatBlockCommand.prototype.exec = function() { - var tag = this.tag; - // Brackets neccessary for certain browsers - var value = '<' + tag + '>'; - var blockElement = getSelectionBlockElement(); - // Allow block commands to be toggled back to a paragraph + __exports__["default"] = TextModel; + }); +define("content-kit-compiler/parsers/html-parser", + ["../models/block","../models/markup","../types/default-types","../../content-kit-utils/object-utils","../../content-kit-utils/array-utils","../../content-kit-utils/string-utils","../../content-kit-utils/node-utils","exports"], + function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __exports__) { + "use strict"; + var BlockModel = __dependency1__["default"]; + var MarkupModel = __dependency2__["default"]; + var DefaultBlockTypeSet = __dependency3__.DefaultBlockTypeSet; + var DefaultMarkupTypeSet = __dependency3__.DefaultMarkupTypeSet; + var mergeWithOptions = __dependency4__.mergeWithOptions; + var toArray = __dependency5__.toArray; + var trim = __dependency6__.trim; + var trimLeft = __dependency6__.trimLeft; + var sanitizeWhitespace = __dependency6__.sanitizeWhitespace; + var createElement = __dependency7__.createElement; + var DOMParsingNode = __dependency7__.DOMParsingNode; + var textOfNode = __dependency7__.textOfNode; + var unwrapNode = __dependency7__.unwrapNode; + var attributesForNode = __dependency7__.attributesForNode; + + /** + * Gets the last block in the set or creates and return a default block if none exist yet. + */ + function getLastBlockOrCreate(parser, blocks) { + var block; + if (blocks.length) { + block = blocks[blocks.length - 1]; + } else { + block = parser.parseBlock(createElement(DefaultBlockTypeSet.TEXT.tag)); + blocks.push(block); + } + return block; + } + + /** + * Helper to retain stray elements at the root of the html that aren't blocks + */ + function handleNonBlockElementAtRoot(parser, elementNode, blocks) { + var block = getLastBlockOrCreate(parser, blocks), + markup = parser.parseElementMarkup(elementNode, block.value.length); + if (markup) { + block.markup.push(markup); + } + block.value += textOfNode(elementNode); + } + + /** + * @class HTMLParser + * @constructor + */ + function HTMLParser(options) { + var defaults = { + blockTypes : DefaultBlockTypeSet, + markupTypes : DefaultMarkupTypeSet, + includeTypeNames : false + }; + mergeWithOptions(this, defaults, options); + } + + /** + * @method parse + * @param html String of HTML content + * @return Array Parsed JSON content array + */ + HTMLParser.prototype.parse = function(html) { + DOMParsingNode.innerHTML = sanitizeWhitespace(html); + + var children = toArray(DOMParsingNode.childNodes), + len = children.length, + blocks = [], + i, currentNode, block, text; + + for (i = 0; i < len; i++) { + currentNode = children[i]; + // All top level nodes *should be* `Element` nodes and supported block types. + // We'll handle some cases if it isn't so we don't lose any content when parsing. + // Parser assumes sane input (such as from the ContentKit Editor) and is not intended to be a full html sanitizer. + if (currentNode.nodeType === 1) { + block = this.parseBlock(currentNode); + if (block) { + blocks.push(block); + } else { + handleNonBlockElementAtRoot(this, currentNode, blocks); + } + } else if (currentNode.nodeType === 3) { + text = currentNode.nodeValue; + if (trim(text)) { + block = getLastBlockOrCreate(this, blocks); + block.value += text; + } + } + } + + return blocks; + }; + + /** + * @method parseBlock + * @param node DOM node to parse + * @return {BlockModel} parsed block model + * Parses a single block type node into a model + */ + HTMLParser.prototype.parseBlock = function(node) { + var type = this.blockTypes.findByNode(node); + if (type) { + return new BlockModel({ + type : type.id, + type_name : this.includeTypeNames && type.name, + value : trim(textOfNode(node)), + attributes : attributesForNode(node), + markup : this.parseBlockMarkup(node) + }); + } + }; + + /** + * @method parseBlockMarkup + * @param node DOM node to parse + * @return {Array} parsed markups + * Parses a single block type node's markup + */ + HTMLParser.prototype.parseBlockMarkup = function(node) { + var processedText = '', + markups = [], + index = 0, + currentNode, markup; + + // Clone the node since it will be recursively torn down + node = node.cloneNode(true); + + while (node.hasChildNodes()) { + currentNode = node.firstChild; + if (currentNode.nodeType === 1) { + markup = this.parseElementMarkup(currentNode, processedText.length); + if (markup) { + markups.push(markup); + } + // unwrap the element so we can process any children + if (currentNode.hasChildNodes()) { + unwrapNode(currentNode); + } + } else if (currentNode.nodeType === 3) { + var text = sanitizeWhitespace(currentNode.nodeValue); + if (index === 0) { text = trimLeft(text); } + if (text) { processedText += text; } + } + + // node has been processed, remove it + currentNode.parentNode.removeChild(currentNode); + index++; + } + + return markups; + }; + + /** + * @method parseElementMarkup + * @param node DOM node to parse + * @param startIndex DOM node to parse + * @return {MarkupModel} parsed markup model + * Parses markup of a single html element node + */ + HTMLParser.prototype.parseElementMarkup = function(node, startIndex) { + var type = this.markupTypes.findByNode(node), + selfClosing, endIndex; + + if (type) { + selfClosing = type.selfClosing; + if (!selfClosing && !node.hasChildNodes()) { return; } // check for empty nodes + + endIndex = startIndex + (selfClosing ? 0 : textOfNode(node).length); + if (endIndex > startIndex || (selfClosing && endIndex === startIndex)) { // check for empty nodes + return new MarkupModel({ + type : type.id, + type_name : this.includeTypeNames && type.name, + start : startIndex, + end : endIndex, + attributes : attributesForNode(node) + }); + } + } + }; + + __exports__["default"] = HTMLParser; + }); +define("content-kit-compiler/renderers/html-element-renderer", + ["../../content-kit-utils/string-utils","../../content-kit-utils/array-utils","exports"], + function(__dependency1__, __dependency2__, __exports__) { + "use strict"; + var injectIntoString = __dependency1__.injectIntoString; + var sumSparseArray = __dependency2__.sumSparseArray; + + /** + * Builds an opening html tag. i.e. ''; } - function restoreRange(range) { - var selection = window.getSelection(); - selection.removeAllRanges(); - selection.addRange(range); + function imageRenderer(model) { + return '
' + + '
' + this.render(model) + '
' + + '
'; } - function selectNode(node) { - var range = document.createRange(); - var selection = window.getSelection(); - range.setStart(node, 0); - range.setEnd(node, node.length); - selection.removeAllRanges(); - selection.addRange(range); + var typeRenderers = {}; + typeRenderers[Type.EMBED.id] = embedRenderer; + typeRenderers[Type.IMAGE.id] = imageRenderer; + + /** + * @class EditorHTMLRenderer + * @constructor + * Subclass of HTMLRenderer specifically for the Editor + * Wraps interactive elements to add functionality + */ + function EditorHTMLRenderer() { + HTMLRenderer.call(this, { + typeRenderers: typeRenderers + }); } + inherit(EditorHTMLRenderer, HTMLRenderer); - __exports__.getDirectionOfSelection = getDirectionOfSelection; - __exports__.getSelectionElement = getSelectionElement; - __exports__.getSelectionBlockElement = getSelectionBlockElement; - __exports__.getSelectionTagName = getSelectionTagName; - __exports__.getSelectionBlockTagName = getSelectionBlockTagName; - __exports__.tagsInSelection = tagsInSelection; - __exports__.selectionIsInElement = selectionIsInElement; - __exports__.selectionIsEditable = selectionIsEditable; - __exports__.moveCursorToBeginningOfSelection = moveCursorToBeginningOfSelection; - __exports__.restoreRange = restoreRange; - __exports__.selectNode = selectNode; + __exports__["default"] = EditorHTMLRenderer; }); -define("content-kit-editor/views/embed-intent", - ["./view","./toolbar","../../content-kit-utils/object-utils","../utils/selection-utils","../utils/element-utils","../constants","exports"], - function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __exports__) { +define("content-kit-editor/editor/editor", + ["./editor-html-renderer","../views/text-format-toolbar","../views/tooltip","../views/embed-intent","../commands/unordered-list","../commands/ordered-list","../commands/text-format","../constants","../utils/selection-utils","../utils/paste-utils","../../content-kit-compiler/compiler","../../content-kit-compiler/models/text","../../content-kit-compiler/types/type","../../content-kit-utils/array-utils","../../content-kit-utils/object-utils","exports"], + function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __dependency9__, __dependency10__, __dependency11__, __dependency12__, __dependency13__, __dependency14__, __dependency15__, __exports__) { "use strict"; - var View = __dependency1__["default"]; - var Toolbar = __dependency2__["default"]; - var inherit = __dependency3__.inherit; - var getSelectionBlockElement = __dependency4__.getSelectionBlockElement; - var positionElementToLeftOf = __dependency5__.positionElementToLeftOf; - var positionElementCenteredIn = __dependency5__.positionElementCenteredIn; - var ToolbarDirection = __dependency6__.ToolbarDirection; - var Keycodes = __dependency6__.Keycodes; - var nodeIsDescendantOfElement = __dependency5__.nodeIsDescendantOfElement; - var createDiv = __dependency5__.createDiv; + var EditorHTMLRenderer = __dependency1__["default"]; + var TextFormatToolbar = __dependency2__["default"]; + var Tooltip = __dependency3__["default"]; + var EmbedIntent = __dependency4__["default"]; + var UnorderedListCommand = __dependency5__["default"]; + var OrderedListCommand = __dependency6__["default"]; + var TextFormatCommand = __dependency7__["default"]; + var Tags = __dependency8__.Tags; + var RootTags = __dependency8__.RootTags; + var Keycodes = __dependency8__.Keycodes; + var RegEx = __dependency8__.RegEx; + var moveCursorToBeginningOfSelection = __dependency9__.moveCursorToBeginningOfSelection; + var getSelectionTagName = __dependency9__.getSelectionTagName; + var getSelectionBlockElement = __dependency9__.getSelectionBlockElement; + var getSelectionBlockTagName = __dependency9__.getSelectionBlockTagName; + var cleanPastedContent = __dependency10__.cleanPastedContent; + var Compiler = __dependency11__["default"]; + var TextModel = __dependency12__["default"]; + var Type = __dependency13__["default"]; + var toArray = __dependency14__.toArray; + var merge = __dependency15__.merge; - function EmbedIntent(options) { - var embedIntent = this; - var rootElement = options.rootElement; - options.tagName = 'button'; - options.classNames = ['ck-embed-intent-btn']; - View.call(embedIntent, options); - embedIntent.editorContext = options.editorContext; - embedIntent.loadingIndicator = createDiv('ck-embed-loading'); - embedIntent.element.title = 'Insert image or embed...'; - embedIntent.element.addEventListener('mouseup', function(e) { - if (embedIntent.isActive) { - embedIntent.deactivate(); - } else { - embedIntent.activate(); - } - e.stopPropagation(); - }); + var editorClassName = 'ck-editor'; + var editorClassNameRegExp = new RegExp(editorClassName); - embedIntent.toolbar = new Toolbar({ embedIntent: embedIntent, editor: embedIntent.editorContext, commands: options.commands, direction: ToolbarDirection.RIGHT }); - embedIntent.isActive = false; + function bindTypingEvents(editor) { + var editorEl = editor.element; - function embedIntentHandler() { - var blockElement = getSelectionBlockElement(); - var blockElementContent = blockElement && blockElement.innerHTML; - if (blockElementContent === '' || blockElementContent === '
') { - embedIntent.showAt(blockElement); - } else { - embedIntent.hide(); + // Breaks out of blockquotes when pressing enter. + editorEl.addEventListener('keyup', function(e) { + if(!e.shiftKey && e.which === Keycodes.ENTER) { + if(Tags.QUOTE === getSelectionBlockTagName()) { + document.execCommand('formatBlock', false, editor.defaultFormatter); + e.stopPropagation(); + } } - } + }); - rootElement.addEventListener('keyup', embedIntentHandler); + // Creates unordered list when block starts with '- ', or ordered if starts with '1. ' + editorEl.addEventListener('keyup', function(e) { + var selection = window.getSelection(); + var selectionNode = selection.anchorNode; + if (!selectionNode) { return; } - document.addEventListener('mouseup', function(e) { - setTimeout(function() { - if (!nodeIsDescendantOfElement(e.target, embedIntent.toolbar.element)) { - embedIntentHandler(); + var selectedText = selectionNode.textContent; + var command, replaceRegex; + + if (Tags.LIST_ITEM !== getSelectionTagName()) { + if (RegEx.UL_START.test(selectedText)) { + command = new UnorderedListCommand(); + replaceRegex = RegEx.UL_START; + } else if (RegEx.OL_START.test(selectedText)) { + command = new OrderedListCommand(); + replaceRegex = RegEx.OL_START; } - }); - }); - document.addEventListener('keyup', function(e) { - if (e.keyCode === Keycodes.ESC) { - embedIntent.hide(); + if (command) { + command.editorContext = editor; + command.exec(); + selection = window.getSelection(); + selection.anchorNode.textContent = selectedText.replace(replaceRegex, ''); + moveCursorToBeginningOfSelection(selection); + e.stopPropagation(); + } } }); - window.addEventListener('resize', function() { - if(embedIntent.isShowing) { - positionElementToLeftOf(embedIntent.element, embedIntent.atNode); - if (embedIntent.toolbar.isShowing) { - embedIntent.toolbar.positionToContent(embedIntent.element); - } + // Assure there is always a supported root tag, and not empty text nodes or divs. + editorEl.addEventListener('keyup', function() { + if (this.innerHTML.length && RootTags.indexOf(getSelectionBlockTagName()) === -1) { + document.execCommand('formatBlock', false, editor.defaultFormatter); } }); - } - inherit(EmbedIntent, View); - - EmbedIntent.prototype.hide = function() { - if (EmbedIntent._super.prototype.hide.call(this)) { - this.deactivate(); - } - }; - EmbedIntent.prototype.showAt = function(node) { - this.show(); - this.deactivate(); - this.atNode = node; - positionElementToLeftOf(this.element, node); - }; + // Experimental: Live update - sync model with textual content as you type + editorEl.addEventListener('keyup', function() { + var index = editor.getCurrentBlockIndex(); + editor.syncModelAt(index); + }); + } - EmbedIntent.prototype.activate = function() { - if (!this.isActive) { - this.addClass('activated'); - this.toolbar.show(); - this.toolbar.positionToContent(this.element); - this.isActive = true; - } - }; + /** + * @class Editor + * An individual Editor + * @param element `Element` node + * @param options hash of options + */ + function Editor(element, options) { + var editor = this; + merge(editor, options); - EmbedIntent.prototype.deactivate = function() { - if (this.isActive) { - this.removeClass('activated'); - this.toolbar.hide(); - this.isActive = false; - } - }; + if (element) { + var className = element.className; + var dataset = element.dataset; - EmbedIntent.prototype.showLoading = function() { - var embedIntent = this; - var loadingIndicator = embedIntent.loadingIndicator; - embedIntent.hide(); - embedIntent.container.appendChild(loadingIndicator); - positionElementCenteredIn(loadingIndicator, embedIntent.atNode); - }; + if (!editorClassNameRegExp.test(className)) { + className += (className ? ' ' : '') + editorClassName; + } + element.className = className; - EmbedIntent.prototype.hideLoading = function() { - this.container.removeChild(this.loadingIndicator); - }; + if (!dataset.placeholder) { + dataset.placeholder = editor.placeholder; + } + if(!editor.spellcheck) { + element.spellcheck = false; + } - __exports__["default"] = EmbedIntent; - }); -define("content-kit-editor/views/message", - ["./view","../../content-kit-utils/object-utils","exports"], - function(__dependency1__, __dependency2__, __exports__) { - "use strict"; - var View = __dependency1__["default"]; - var inherit = __dependency2__.inherit; + element.setAttribute('contentEditable', true); + editor.element = element; - function Message(options) { - options = options || {}; - options.classNames = ['ck-message']; - View.call(this, options); - } - inherit(Message, View); + var compiler = editor.compiler = options.compiler || new Compiler({ + includeTypeNames: true, // output type names for easier debugging + renderer: new EditorHTMLRenderer() + }); + editor.syncModel(); - Message.prototype.show = function(message) { - var messageView = this; - messageView.element.innerHTML = message; - Message._super.prototype.show.call(messageView); - setTimeout(function() { - messageView.hide(); - }, 3000); - }; + bindTypingEvents(editor); + editor.element.addEventListener('paste', function(e) { + var cleanedContent = cleanPastedContent(e, editor.defaultFormatter); + if (cleanedContent) { + document.execCommand('insertHTML', false, cleanedContent); + editor.syncModel(); // TODO: can optimize to just sync to index range + } + }); - __exports__["default"] = Message; - }); -define("content-kit-editor/views/prompt", - ["./view","../../content-kit-utils/object-utils","../utils/selection-utils","../utils/element-utils","../constants","exports"], - function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __exports__) { - "use strict"; - var View = __dependency1__["default"]; - var inherit = __dependency2__.inherit; - var inherit = __dependency2__.inherit; - var restoreRange = __dependency3__.restoreRange; - var createDiv = __dependency4__.createDiv; - var positionElementToRect = __dependency4__.positionElementToRect; - var Keycodes = __dependency5__.Keycodes; + editor.textFormatToolbar = new TextFormatToolbar({ rootElement: element, editor: editor, commands: editor.textFormatCommands }); + var linkTooltips = new Tooltip({ rootElement: element, showForTag: Tags.LINK }); - var container = document.body; - var hiliter = createDiv('ck-editor-hilite'); + if(editor.embedCommands) { + // NOTE: must come after bindTypingEvents so those keyup handlers are executed first. + // TODO: manage event listener order + var embedIntent = new EmbedIntent({ + editorContext: editor, + commands: editor.embedCommands, + rootElement: element + }); - function positionHiliteRange(range) { - var rect = range.getBoundingClientRect(); - var style = hiliter.style; - style.width = rect.width + 'px'; - style.height = rect.height + 'px'; - positionElementToRect(hiliter, rect); + if (editor.imageServiceUrl) { + // TODO: lookup by name + editor.embedCommands[0].uploader.url = editor.imageServiceUrl; + } + if (editor.embedServiceUrl) { + // TODO: lookup by name + editor.embedCommands[1].embedService.url = editor.embedServiceUrl; + } + } + + if(editor.autofocus) { element.focus(); } + } } - function Prompt(options) { - var prompt = this; - options.tagName = 'input'; - View.call(prompt, options); - - prompt.command = options.command; - prompt.element.placeholder = options.placeholder || ''; - prompt.element.addEventListener('mouseup', function(e) { e.stopPropagation(); }); // prevents closing prompt when clicking input - prompt.element.addEventListener('keyup', function(e) { - var entry = this.value; - if(entry && prompt.range && !e.shiftKey && e.which === Keycodes.ENTER) { - restoreRange(prompt.range); - prompt.command.exec(entry); - if (prompt.onComplete) { prompt.onComplete(); } - } - }); + Editor.prototype.syncModel = function() { + this.model = this.compiler.parse(this.element.innerHTML); + }; - window.addEventListener('resize', function() { - var activeHilite = hiliter.parentNode; - var range = prompt.range; - if(activeHilite && range) { - positionHiliteRange(range); - } - }); - } - inherit(Prompt, View); + Editor.prototype.syncModelAt = function(index) { + if (index > -1) { + var blockElements = toArray(this.element.children); + var parsedBlockModel = this.compiler.parser.parseBlock(blockElements[index]); + this.model[index] = parsedBlockModel; - Prompt.prototype.show = function(callback) { - var prompt = this; - var element = prompt.element; - var selection = window.getSelection(); - var range = selection && selection.rangeCount && selection.getRangeAt(0); - element.value = null; - prompt.range = range || null; - if (range) { - container.appendChild(hiliter); - positionHiliteRange(prompt.range); - setTimeout(function(){ element.focus(); }); // defer focus (disrupts mouseup events) - if (callback) { prompt.onComplete = callback; } + // TODO: event subscription + ContentKitDemo.syncCodePane(this); } }; - Prompt.prototype.hide = function() { - if (hiliter.parentNode) { - container.removeChild(hiliter); + Editor.prototype.syncVisualAt = function(index) { + if (index > -1) { + var blockModel = this.model[index]; + var html = this.compiler.render([blockModel]); + var blockElements = toArray(this.element.children); + var element = blockElements[index]; + element.innerHTML = html; } }; - __exports__["default"] = Prompt; - }); -define("content-kit-editor/views/text-format-toolbar", - ["./toolbar","../../content-kit-utils/object-utils","../utils/selection-utils","../constants","exports"], - function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __exports__) { - "use strict"; - var Toolbar = __dependency1__["default"]; - var inherit = __dependency2__.inherit; - var selectionIsEditable = __dependency3__.selectionIsEditable; - var selectionIsInElement = __dependency3__.selectionIsInElement; - var Keycodes = __dependency4__.Keycodes; - - function TextFormatToolbar(options) { - var toolbar = this; - Toolbar.call(this, options); - toolbar.rootElement = options.rootElement; - toolbar.rootElement.addEventListener('keyup', function() { toolbar.handleTextSelection(); }); - - document.addEventListener('keyup', function(e) { - if (e.keyCode === Keycodes.ESC) { - toolbar.hide(); - } - }); + Editor.prototype.getCurrentBlockIndex = function() { + var selectionEl = getSelectionBlockElement(); + var blockElements = toArray(this.element.children); + return blockElements.indexOf(selectionEl); + }; - document.addEventListener('mouseup', function() { - setTimeout(function() { toolbar.handleTextSelection(); }); - }); + Editor.prototype.insertBlock = function(model) { + this.insertBlockAt(model, this.getCurrentBlockIndex()); + }; - window.addEventListener('resize', function() { - if(toolbar.isShowing) { - var activePromptRange = toolbar.activePrompt && toolbar.activePrompt.range; - toolbar.positionToContent(activePromptRange ? activePromptRange : window.getSelection().getRangeAt(0)); - } - }); - } - inherit(TextFormatToolbar, Toolbar); + Editor.prototype.insertBlockAt = function(model, index) { + model = model || new TextModel(); + this.model.splice(index, 0, model); + }; - TextFormatToolbar.prototype.handleTextSelection = function() { - var toolbar = this; - var selection = window.getSelection(); - if (selection.isCollapsed || !selectionIsEditable(selection) || selection.toString().trim() === '' || !selectionIsInElement(selection, toolbar.rootElement)) { - toolbar.hide(); - } else { - toolbar.updateForSelection(selection); - } + Editor.prototype.addTextFormat = function(opts) { + var command = new TextFormatCommand(opts); + this.compiler.registerMarkupType(new Type({ + name : opts.name, + tag : opts.tag || opts.name + })); + this.textFormatCommands.push(command); + this.textFormatToolbar.addCommand(command); }; - __exports__["default"] = TextFormatToolbar; + __exports__["default"] = Editor; }); -define("content-kit-editor/views/toolbar-button", - ["../commands/base","exports"], - function(__dependency1__, __exports__) { +define("content-kit-editor/utils/element-utils", + ["exports"], + function(__exports__) { "use strict"; - var Command = __dependency1__["default"]; - - var buttonClassName = 'ck-toolbar-btn'; + function createDiv(className) { + var div = document.createElement('div'); + if (className) { + div.className = className; + } + return div; + } - function ToolbarButton(options) { - var button = this; - var toolbar = options.toolbar; - var command = options.command; - var prompt = command.prompt; - var element = document.createElement('button'); + function hideElement(element) { + element.style.display = 'none'; + } - if(typeof command === 'string') { - command = Command.index[command]; - } + function showElement(element) { + element.style.display = 'block'; + } - button.element = element; - button.command = command; - button.isActive = false; + function swapElements(elementToShow, elementToHide) { + hideElement(elementToHide); + showElement(elementToShow); + } - element.title = command.name; - element.className = buttonClassName; - element.innerHTML = command.button; - element.addEventListener('click', function(e) { - if (!button.isActive && prompt) { - toolbar.displayPrompt(prompt); - } else { - command.exec(); + function getEventTargetMatchingTag(tag, target, container) { + // Traverses up DOM from an event target to find the node matching specifed tag + while (target && target !== container) { + if (target.tagName === tag) { + return target; } - }); + target = target.parentNode; + } } - ToolbarButton.prototype = { - setActive: function() { - var button = this; - if (!button.isActive) { - button.element.className = buttonClassName + ' active'; - button.isActive = true; - } - }, - setInactive: function() { - var button = this; - if (button.isActive) { - button.element.className = buttonClassName; - button.isActive = false; + function nodeIsDescendantOfElement(node, element) { + var parentNode = node.parentNode; + while(parentNode) { + if (parentNode === element) { + return true; } + parentNode = parentNode.parentNode; } - }; - - __exports__["default"] = ToolbarButton; - }); -define("content-kit-editor/views/toolbar", - ["./view","./toolbar-button","../../content-kit-utils/object-utils","../utils/selection-utils","../constants","../utils/element-utils","exports"], - function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __exports__) { - "use strict"; - var View = __dependency1__["default"]; - var ToolbarButton = __dependency2__["default"]; - var inherit = __dependency3__.inherit; - var tagsInSelection = __dependency4__.tagsInSelection; - var ToolbarDirection = __dependency5__.ToolbarDirection; - var createDiv = __dependency6__.createDiv; - var swapElements = __dependency6__.swapElements; - var positionElementToRightOf = __dependency6__.positionElementToRightOf; - var positionElementCenteredAbove = __dependency6__.positionElementCenteredAbove; + return false; + } - function updateButtonsForSelection(buttons, selection) { - var selectedTags = tagsInSelection(selection), - len = buttons.length, - i, button; + function getElementRelativeOffset(element) { + var offset = { left: 0, top: -window.pageYOffset }; + var offsetParent = element.offsetParent; + var offsetParentPosition = window.getComputedStyle(offsetParent).position; + var offsetParentRect; - for (i = 0; i < len; i++) { - button = buttons[i]; - if (selectedTags.indexOf(button.command.tag) > -1) { - button.setActive(); - } else { - button.setInactive(); - } + if (offsetParentPosition === 'relative') { + offsetParentRect = offsetParent.getBoundingClientRect(); + offset.left = offsetParentRect.left; + offset.top = offsetParentRect.top; } + return offset; } - function Toolbar(options) { - var toolbar = this; - var commands = options.commands; - var commandCount = commands && commands.length; - var i, button, command; - toolbar.editor = options.editor || null; - toolbar.embedIntent = options.embedIntent || null; - toolbar.direction = options.direction || ToolbarDirection.TOP; - options.classNames = ['ck-toolbar']; - if (toolbar.direction === ToolbarDirection.RIGHT) { - options.classNames.push('right'); - } + function getElementComputedStyleNumericProp(element, prop) { + return parseFloat(window.getComputedStyle(element)[prop]); + } - View.call(toolbar, options); + function positionElementToRect(element, rect, topOffset, leftOffset) { + var relativeOffset = getElementRelativeOffset(element); + var style = element.style; + var round = Math.round; - toolbar.activePrompt = null; - toolbar.buttons = []; + topOffset = topOffset || 0; + leftOffset = leftOffset || 0; + style.left = round(rect.left - relativeOffset.left - leftOffset) + 'px'; + style.top = round(rect.top - relativeOffset.top - topOffset) + 'px'; + } - toolbar.promptContainerElement = createDiv('ck-toolbar-prompt'); - toolbar.buttonContainerElement = createDiv('ck-toolbar-buttons'); - toolbar.element.appendChild(toolbar.promptContainerElement); - toolbar.element.appendChild(toolbar.buttonContainerElement); + function positionElementHorizontallyCenteredToRect(element, rect, topOffset) { + var horizontalCenter = (element.offsetWidth / 2) - (rect.width / 2); + positionElementToRect(element, rect, topOffset, horizontalCenter); + } - for(i = 0; i < commandCount; i++) { - this.addCommand(commands[i]); - } + function positionElementCenteredAbove(element, aboveElement) { + var elementMargin = getElementComputedStyleNumericProp(element, 'marginBottom'); + positionElementHorizontallyCenteredToRect(element, aboveElement.getBoundingClientRect(), element.offsetHeight + elementMargin); + } - // Closes prompt if displayed when changing selection - document.addEventListener('mouseup', function() { - toolbar.dismissPrompt(); - }); + function positionElementCenteredBelow(element, belowElement) { + var elementMargin = getElementComputedStyleNumericProp(element, 'marginTop'); + positionElementHorizontallyCenteredToRect(element, belowElement.getBoundingClientRect(), -element.offsetHeight - elementMargin); } - inherit(Toolbar, View); - Toolbar.prototype.hide = function() { - if (Toolbar._super.prototype.hide.call(this)) { - var style = this.element.style; - style.left = ''; - style.top = ''; - this.dismissPrompt(); - } - }; + function positionElementCenteredIn(element, inElement) { + var verticalCenter = (inElement.offsetHeight / 2) - (element.offsetHeight / 2); + positionElementHorizontallyCenteredToRect(element, inElement.getBoundingClientRect(), -verticalCenter); + } - Toolbar.prototype.addCommand = function(command) { - command.editorContext = this.editor; - command.embedIntent = this.embedIntent; - var button = new ToolbarButton({ command: command, toolbar: this }); - this.buttons.push(button); - this.buttonContainerElement.appendChild(button.element); - }; + function positionElementToLeftOf(element, leftOfElement) { + var verticalCenter = (leftOfElement.offsetHeight / 2) - (element.offsetHeight / 2); + var elementMargin = getElementComputedStyleNumericProp(element, 'marginRight'); + positionElementToRect(element, leftOfElement.getBoundingClientRect(), -verticalCenter, element.offsetWidth + elementMargin); + } - Toolbar.prototype.displayPrompt = function(prompt) { - var toolbar = this; - swapElements(toolbar.promptContainerElement, toolbar.buttonContainerElement); - toolbar.promptContainerElement.appendChild(prompt.element); - prompt.show(function() { - toolbar.dismissPrompt(); - toolbar.updateForSelection(window.getSelection()); - }); - toolbar.activePrompt = prompt; - }; + function positionElementToRightOf(element, rightOfElement) { + var verticalCenter = (rightOfElement.offsetHeight / 2) - (element.offsetHeight / 2); + var elementMargin = getElementComputedStyleNumericProp(element, 'marginLeft'); + var rightOfElementRect = rightOfElement.getBoundingClientRect(); + positionElementToRect(element, rightOfElementRect, -verticalCenter, -rightOfElement.offsetWidth - elementMargin); + } - Toolbar.prototype.dismissPrompt = function() { - var toolbar = this; - var activePrompt = toolbar.activePrompt; - if (activePrompt) { - activePrompt.hide(); - swapElements(toolbar.buttonContainerElement, toolbar.promptContainerElement); - toolbar.activePrompt = null; - } - }; + __exports__.createDiv = createDiv; + __exports__.hideElement = hideElement; + __exports__.showElement = showElement; + __exports__.swapElements = swapElements; + __exports__.getEventTargetMatchingTag = getEventTargetMatchingTag; + __exports__.nodeIsDescendantOfElement = nodeIsDescendantOfElement; + __exports__.getElementRelativeOffset = getElementRelativeOffset; + __exports__.getElementComputedStyleNumericProp = getElementComputedStyleNumericProp; + __exports__.positionElementToRect = positionElementToRect; + __exports__.positionElementHorizontallyCenteredToRect = positionElementHorizontallyCenteredToRect; + __exports__.positionElementCenteredAbove = positionElementCenteredAbove; + __exports__.positionElementCenteredBelow = positionElementCenteredBelow; + __exports__.positionElementCenteredIn = positionElementCenteredIn; + __exports__.positionElementToLeftOf = positionElementToLeftOf; + __exports__.positionElementToRightOf = positionElementToRightOf; + }); +define("content-kit-editor/utils/paste-utils", + ["../constants","exports"], + function(__dependency1__, __exports__) { + "use strict"; + var RegEx = __dependency1__.RegEx; - Toolbar.prototype.updateForSelection = function(selection) { - var toolbar = this; - if (selection.isCollapsed) { - toolbar.hide(); + function plainTextToBlocks(plainText, tag) { + var blocks = plainText.split(RegEx.NEWLINE), + len = blocks.length, + block, openTag, closeTag, content, i; + if(len < 2) { + return plainText; } else { - toolbar.show(); - toolbar.positionToContent(selection.getRangeAt(0)); - updateButtonsForSelection(toolbar.buttons, selection); + content = ''; + openTag = '<' + tag + '>'; + closeTag = ''; + for(i = 0; i < len; ++i) { + block = blocks[i]; + if(block !== '') { + content += openTag + block + closeTag; + } + } + return content; } - }; + } - Toolbar.prototype.positionToContent = function(content) { - var directions = ToolbarDirection; - var positioningMethod; - switch(this.direction) { - case directions.RIGHT: - positioningMethod = positionElementToRightOf; - break; - default: - positioningMethod = positionElementCenteredAbove; + function cleanPastedContent(event, defaultBlockTag) { + event.preventDefault(); + var data = event.clipboardData, plainText; + if(data && data.getData) { + plainText = data.getData('text/plain'); + return plainTextToBlocks(plainText, defaultBlockTag); } - positioningMethod(this.element, content); - }; + } - __exports__["default"] = Toolbar; + __exports__.cleanPastedContent = cleanPastedContent; }); -define("content-kit-editor/views/tooltip", - ["./view","../../content-kit-utils/object-utils","../utils/element-utils","exports"], - function(__dependency1__, __dependency2__, __dependency3__, __exports__) { +define("content-kit-editor/utils/selection-utils", + ["../constants","./element-utils","exports"], + function(__dependency1__, __dependency2__, __exports__) { "use strict"; - var View = __dependency1__["default"]; - var inherit = __dependency2__.inherit; - var positionElementCenteredBelow = __dependency3__.positionElementCenteredBelow; - var getEventTargetMatchingTag = __dependency3__.getEventTargetMatchingTag; + var SelectionDirection = __dependency1__.SelectionDirection; + var RootTags = __dependency1__.RootTags; + var nodeIsDescendantOfElement = __dependency2__.nodeIsDescendantOfElement; - function Tooltip(options) { - var tooltip = this; - var rootElement = options.rootElement; - var delay = options.delay || 200; - var timeout; - options.classNames = ['ck-tooltip']; - View.call(tooltip, options); + function getDirectionOfSelection(selection) { + var node = selection.anchorNode; + var position = node && node.compareDocumentPosition(selection.focusNode); + if (position & Node.DOCUMENT_POSITION_FOLLOWING) { + return SelectionDirection.LEFT_TO_RIGHT; + } else if (position & Node.DOCUMENT_POSITION_PRECEDING) { + return SelectionDirection.RIGHT_TO_LEFT; + } + return SelectionDirection.SAME_NODE; + } - rootElement.addEventListener('mouseover', function(e) { - var target = getEventTargetMatchingTag(options.showForTag, e.target, rootElement); - if (target && target.isContentEditable) { - timeout = setTimeout(function() { - tooltip.showLink(target.href, target); - }, delay); - } - }); - - rootElement.addEventListener('mouseout', function(e) { - clearTimeout(timeout); - var toElement = e.toElement || e.relatedTarget; - if (toElement && toElement.className !== tooltip.element.className) { - tooltip.hide(); - } - }); + function getSelectionElement(selection) { + selection = selection || window.getSelection(); + var node = getDirectionOfSelection(selection) === SelectionDirection.LEFT_TO_RIGHT ? selection.anchorNode : selection.focusNode; + return node && (node.nodeType === 3 ? node.parentNode : node); } - inherit(Tooltip, View); - Tooltip.prototype.showMessage = function(message, element) { - var tooltip = this; - var tooltipElement = tooltip.element; - tooltipElement.innerHTML = message; - tooltip.show(); - positionElementCenteredBelow(tooltipElement, element); - }; + function getSelectionBlockElement(selection) { + selection = selection || window.getSelection(); + var element = getSelectionElement(); + var tag = element && element.tagName; + while (tag && RootTags.indexOf(tag) === -1) { + if (element.contentEditable === 'true') { break; } // Stop traversing up dom when hitting an editor element + element = element.parentNode; + tag = element.tagName; + } + return element; + } - Tooltip.prototype.showLink = function(link, element) { - var message = '' + link + ''; - this.showMessage(message, element); - }; + function getSelectionTagName() { + var element = getSelectionElement(); + return element ? element.tagName : null; + } - __exports__["default"] = Tooltip; - }); -define("content-kit-editor/views/view", - ["exports"], - function(__exports__) { - "use strict"; - function View(options) { - this.tagName = options.tagName || 'div'; - this.classNames = options.classNames || []; - this.element = document.createElement(this.tagName); - this.element.className = this.classNames.join(' '); - this.container = options.container || document.body; - this.isShowing = false; + function getSelectionBlockTagName() { + var element = getSelectionBlockElement(); + return element ? element.tagName : null; } - View.prototype = { - show: function() { - var view = this; - if(!view.isShowing) { - view.container.appendChild(view.element); - view.isShowing = true; - return true; + function tagsInSelection(selection) { + var element = getSelectionElement(selection); + var tags = []; + if (!selection.isCollapsed) { + while(element) { + if (element.contentEditable === 'true') { break; } // Stop traversing up dom when hitting an editor element + if (element.tagName) { + tags.push(element.tagName); + } + element = element.parentNode; } - }, - hide: function() { - var view = this; - if(view.isShowing) { - view.container.removeChild(view.element); - view.isShowing = false; - return true; + } + return tags; + } + + function selectionIsInElement(selection, element) { + var node = selection.anchorNode; + return node && nodeIsDescendantOfElement(node, element); + } + + function selectionIsEditable(selection) { + var el = getSelectionBlockElement(selection); + return el.isContentEditable; + } + + /* + function saveSelection() { + var sel = window.getSelection(); + var ranges = [], i; + if (sel.rangeCount) { + var rangeCount = sel.rangeCount; + for (i = 0; i < rangeCount; i++) { + ranges.push(sel.getRangeAt(i)); } - }, - focus: function() { - this.element.focus(); - }, - addClass: function(className) { - this.classNames.push(className); - this.element.className = this.classNames.join(' '); - }, - removeClass: function(className) { - this.classNames.splice(this.classNames.indexOf(className), 1); - this.element.className = this.classNames.join(' '); } - }; + return ranges; + } - __exports__["default"] = View; + function restoreSelection(savedSelection) { + var sel = window.getSelection(); + var len = savedSelection.length, i; + sel.removeAllRanges(); + for (i = 0; i < len; i++) { + sel.addRange(savedSelection[i]); + } + } + */ + + function moveCursorToBeginningOfSelection(selection) { + var range = document.createRange(); + var node = selection.anchorNode; + range.setStart(node, 0); + range.setEnd(node, 0); + selection.removeAllRanges(); + selection.addRange(range); + } + + function restoreRange(range) { + var selection = window.getSelection(); + selection.removeAllRanges(); + selection.addRange(range); + } + + function selectNode(node) { + var range = document.createRange(); + var selection = window.getSelection(); + range.setStart(node, 0); + range.setEnd(node, node.length); + selection.removeAllRanges(); + selection.addRange(range); + } + + __exports__.getDirectionOfSelection = getDirectionOfSelection; + __exports__.getSelectionElement = getSelectionElement; + __exports__.getSelectionBlockElement = getSelectionBlockElement; + __exports__.getSelectionTagName = getSelectionTagName; + __exports__.getSelectionBlockTagName = getSelectionBlockTagName; + __exports__.tagsInSelection = tagsInSelection; + __exports__.selectionIsInElement = selectionIsInElement; + __exports__.selectionIsEditable = selectionIsEditable; + __exports__.moveCursorToBeginningOfSelection = moveCursorToBeginningOfSelection; + __exports__.restoreRange = restoreRange; + __exports__.selectNode = selectNode; }); -define("content-kit-compiler/models/block", - ["./model","exports"], - function(__dependency1__, __exports__) { +define("content-kit-editor/views/embed-intent", + ["./view","./toolbar","../../content-kit-utils/object-utils","../utils/selection-utils","../utils/element-utils","../constants","exports"], + function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __exports__) { "use strict"; - var Model = __dependency1__["default"]; + var View = __dependency1__["default"]; + var Toolbar = __dependency2__["default"]; + var inherit = __dependency3__.inherit; + var getSelectionBlockElement = __dependency4__.getSelectionBlockElement; + var positionElementToLeftOf = __dependency5__.positionElementToLeftOf; + var positionElementCenteredIn = __dependency5__.positionElementCenteredIn; + var ToolbarDirection = __dependency6__.ToolbarDirection; + var Keycodes = __dependency6__.Keycodes; + var nodeIsDescendantOfElement = __dependency5__.nodeIsDescendantOfElement; + var createDiv = __dependency5__.createDiv; - /** - * Ensures block markups at the same index are always in a specific order. - * For example, so all bold links are consistently marked up - * as text instead of text - */ - function sortBlockMarkups(markups) { - return markups.sort(function(a, b) { - if (a.start === b.start && a.end === b.end) { - return b.type - a.type; + function EmbedIntent(options) { + var embedIntent = this; + var rootElement = options.rootElement; + options.tagName = 'button'; + options.classNames = ['ck-embed-intent-btn']; + View.call(embedIntent, options); + + embedIntent.editorContext = options.editorContext; + embedIntent.loadingIndicator = createDiv('ck-embed-loading'); + embedIntent.element.title = 'Insert image or embed...'; + embedIntent.element.addEventListener('mouseup', function(e) { + if (embedIntent.isActive) { + embedIntent.deactivate(); + } else { + embedIntent.activate(); } - return 0; + e.stopPropagation(); }); - } - /** - * @class BlockModel - * @constructor - * @extends Model - */ - function BlockModel(options) { - options = options || {}; - Model.call(this, options); - this.value = options.value || ''; - this.markup = sortBlockMarkups(options.markup || []); - } + embedIntent.toolbar = new Toolbar({ embedIntent: embedIntent, editor: embedIntent.editorContext, commands: options.commands, direction: ToolbarDirection.RIGHT }); + embedIntent.isActive = false; - __exports__["default"] = BlockModel; - }); -define("content-kit-compiler/models/embed", - ["../models/model","../types/type","exports"], - function(__dependency1__, __dependency2__, __exports__) { - "use strict"; - var Model = __dependency1__["default"]; - var Type = __dependency2__["default"]; + function embedIntentHandler() { + var blockElement = getSelectionBlockElement(); + var blockElementContent = blockElement && blockElement.innerHTML; + if (blockElementContent === '' || blockElementContent === '
') { + embedIntent.showAt(blockElement); + } else { + embedIntent.hide(); + } + } - /** - * @class EmbedModel - * @constructor - * @extends Model - * Massages data from an oEmbed response into an EmbedModel - */ - function EmbedModel(options) { - if (!options) { return null; } + rootElement.addEventListener('keyup', embedIntentHandler); - Model.call(this, { - type: Type.EMBED.id, - type_name: Type.EMBED.name, - attributes: {} + document.addEventListener('mouseup', function(e) { + setTimeout(function() { + if (!nodeIsDescendantOfElement(e.target, embedIntent.toolbar.element)) { + embedIntentHandler(); + } + }); }); - var attributes = this.attributes; - var embedType = options.type; - var providerName = options.provider_name; - var embedUrl = options.url; - var embedTitle = options.title; - var embedThumbnail = options.thumbnail_url; - var embedHtml = options.html; - - if (embedType) { attributes.embed_type = embedType; } - if (providerName) { attributes.provider_name = providerName; } - if (embedUrl) { attributes.url = embedUrl; } - if (embedTitle) { attributes.title = embedTitle; } + document.addEventListener('keyup', function(e) { + if (e.keyCode === Keycodes.ESC) { + embedIntent.hide(); + } + }); - if (embedType === 'photo') { - attributes.thumbnail = options.media_url || embedUrl; - } else if (embedThumbnail) { - attributes.thumbnail = embedThumbnail; - } + window.addEventListener('resize', function() { + if(embedIntent.isShowing) { + positionElementToLeftOf(embedIntent.element, embedIntent.atNode); + if (embedIntent.toolbar.isShowing) { + embedIntent.toolbar.positionToContent(embedIntent.element); + } + } + }); + } + inherit(EmbedIntent, View); - if (embedHtml && embedType === 'rich') { - attributes.html = embedHtml; + EmbedIntent.prototype.hide = function() { + if (EmbedIntent._super.prototype.hide.call(this)) { + this.deactivate(); } - } + }; - __exports__["default"] = EmbedModel; - }); -define("content-kit-compiler/models/image", - ["./block","../types/type","exports"], - function(__dependency1__, __dependency2__, __exports__) { - "use strict"; - var BlockModel = __dependency1__["default"]; - var Type = __dependency2__["default"]; + EmbedIntent.prototype.showAt = function(node) { + this.show(); + this.deactivate(); + this.atNode = node; + positionElementToLeftOf(this.element, node); + }; - /** - * @class ImageModel - * @constructor - * @extends BlockModel - * A simple BlockModel subclass representing an image - */ - function ImageModel(options) { - options = options || {}; - options.type = Type.IMAGE.id; - options.type_name = Type.IMAGE.name; - if (options.src) { - options.attributes = { src: options.src }; + EmbedIntent.prototype.activate = function() { + if (!this.isActive) { + this.addClass('activated'); + this.toolbar.show(); + this.toolbar.positionToContent(this.element); + this.isActive = true; } - BlockModel.call(this, options); - } - - __exports__["default"] = ImageModel; - }); -define("content-kit-compiler/models/markup", - ["./model","exports"], - function(__dependency1__, __exports__) { - "use strict"; - var Model = __dependency1__["default"]; + }; - /** - * @class MarkupModel - * @constructor - * @extends Model - */ - function MarkupModel(options) { - options = options || {}; - Model.call(this, options); - this.start = options.start || 0; - this.end = options.end || 0; - } + EmbedIntent.prototype.deactivate = function() { + if (this.isActive) { + this.removeClass('activated'); + this.toolbar.hide(); + this.isActive = false; + } + }; - __exports__["default"] = MarkupModel; - }); -define("content-kit-compiler/models/model", - ["exports"], - function(__exports__) { - "use strict"; - /** - * @class Model - * @constructor - * @private - */ - function Model(options) { - options = options || {}; - var type_name = options.type_name; - var attributes = options.attributes; + EmbedIntent.prototype.showLoading = function() { + var embedIntent = this; + var loadingIndicator = embedIntent.loadingIndicator; + embedIntent.hide(); + embedIntent.container.appendChild(loadingIndicator); + positionElementCenteredIn(loadingIndicator, embedIntent.atNode); + }; - this.type = options.type || null; - if (type_name) { - this.type_name = type_name; - } - if (attributes) { - this.attributes = attributes; - } - } + EmbedIntent.prototype.hideLoading = function() { + this.container.removeChild(this.loadingIndicator); + }; - __exports__["default"] = Model; + __exports__["default"] = EmbedIntent; }); -define("content-kit-compiler/models/text", - ["./block","../types/type","exports"], +define("content-kit-editor/views/message", + ["./view","../../content-kit-utils/object-utils","exports"], function(__dependency1__, __dependency2__, __exports__) { "use strict"; - var BlockModel = __dependency1__["default"]; - var Type = __dependency2__["default"]; + var View = __dependency1__["default"]; + var inherit = __dependency2__.inherit; - /** - * @class TextModel - * @constructor - * @extends BlockModel - * A simple BlockModel subclass representing a paragraph of text - */ - function TextModel(options) { + function Message(options) { options = options || {}; - options.type = Type.TEXT.id; - options.type_name = Type.TEXT.name; - BlockModel.call(this, options); - } - - __exports__["default"] = TextModel; - }); -define("content-kit-compiler/parsers/html-parser", - ["../models/block","../models/markup","../types/default-types","../../content-kit-utils/object-utils","../../content-kit-utils/array-utils","../../content-kit-utils/string-utils","../../content-kit-utils/node-utils","exports"], - function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __exports__) { - "use strict"; - var BlockModel = __dependency1__["default"]; - var MarkupModel = __dependency2__["default"]; - var DefaultBlockTypeSet = __dependency3__.DefaultBlockTypeSet; - var DefaultMarkupTypeSet = __dependency3__.DefaultMarkupTypeSet; - var mergeWithOptions = __dependency4__.mergeWithOptions; - var toArray = __dependency5__.toArray; - var trim = __dependency6__.trim; - var trimLeft = __dependency6__.trimLeft; - var sanitizeWhitespace = __dependency6__.sanitizeWhitespace; - var createElement = __dependency7__.createElement; - var DOMParsingNode = __dependency7__.DOMParsingNode; - var textOfNode = __dependency7__.textOfNode; - var unwrapNode = __dependency7__.unwrapNode; - var attributesForNode = __dependency7__.attributesForNode; - - /** - * Gets the last block in the set or creates and return a default block if none exist yet. - */ - function getLastBlockOrCreate(parser, blocks) { - var block; - if (blocks.length) { - block = blocks[blocks.length - 1]; - } else { - block = parser.parseBlock(createElement(DefaultBlockTypeSet.TEXT.tag)); - blocks.push(block); - } - return block; - } - - /** - * Helper to retain stray elements at the root of the html that aren't blocks - */ - function handleNonBlockElementAtRoot(parser, elementNode, blocks) { - var block = getLastBlockOrCreate(parser, blocks), - markup = parser.parseElementMarkup(elementNode, block.value.length); - if (markup) { - block.markup.push(markup); - } - block.value += textOfNode(elementNode); + options.classNames = ['ck-message']; + View.call(this, options); } + inherit(Message, View); - /** - * @class HTMLParser - * @constructor - */ - function HTMLParser(options) { - var defaults = { - blockTypes : DefaultBlockTypeSet, - markupTypes : DefaultMarkupTypeSet, - includeTypeNames : false - }; - mergeWithOptions(this, defaults, options); + Message.prototype.show = function(message) { + var messageView = this; + messageView.element.innerHTML = message; + Message._super.prototype.show.call(messageView); + setTimeout(function() { + messageView.hide(); + }, 3000); + }; + + __exports__["default"] = Message; + }); +define("content-kit-editor/views/prompt", + ["./view","../../content-kit-utils/object-utils","../utils/selection-utils","../utils/element-utils","../constants","exports"], + function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __exports__) { + "use strict"; + var View = __dependency1__["default"]; + var inherit = __dependency2__.inherit; + var inherit = __dependency2__.inherit; + var restoreRange = __dependency3__.restoreRange; + var createDiv = __dependency4__.createDiv; + var positionElementToRect = __dependency4__.positionElementToRect; + var Keycodes = __dependency5__.Keycodes; + + var container = document.body; + var hiliter = createDiv('ck-editor-hilite'); + + function positionHiliteRange(range) { + var rect = range.getBoundingClientRect(); + var style = hiliter.style; + style.width = rect.width + 'px'; + style.height = rect.height + 'px'; + positionElementToRect(hiliter, rect); } - /** - * @method parse - * @param html String of HTML content - * @return Array Parsed JSON content array - */ - HTMLParser.prototype.parse = function(html) { - DOMParsingNode.innerHTML = sanitizeWhitespace(html); + function Prompt(options) { + var prompt = this; + options.tagName = 'input'; + View.call(prompt, options); - var children = toArray(DOMParsingNode.childNodes), - len = children.length, - blocks = [], - i, currentNode, block, text; + prompt.command = options.command; + prompt.element.placeholder = options.placeholder || ''; + prompt.element.addEventListener('mouseup', function(e) { e.stopPropagation(); }); // prevents closing prompt when clicking input + prompt.element.addEventListener('keyup', function(e) { + var entry = this.value; + if(entry && prompt.range && !e.shiftKey && e.which === Keycodes.ENTER) { + restoreRange(prompt.range); + prompt.command.exec(entry); + if (prompt.onComplete) { prompt.onComplete(); } + } + }); - for (i = 0; i < len; i++) { - currentNode = children[i]; - // All top level nodes *should be* `Element` nodes and supported block types. - // We'll handle some cases if it isn't so we don't lose any content when parsing. - // Parser assumes sane input (such as from the ContentKit Editor) and is not intended to be a full html sanitizer. - if (currentNode.nodeType === 1) { - block = this.parseBlock(currentNode); - if (block) { - blocks.push(block); - } else { - handleNonBlockElementAtRoot(this, currentNode, blocks); - } - } else if (currentNode.nodeType === 3) { - text = currentNode.nodeValue; - if (trim(text)) { - block = getLastBlockOrCreate(this, blocks); - block.value += text; - } + window.addEventListener('resize', function() { + var activeHilite = hiliter.parentNode; + var range = prompt.range; + if(activeHilite && range) { + positionHiliteRange(range); } - } + }); + } + inherit(Prompt, View); - return blocks; + Prompt.prototype.show = function(callback) { + var prompt = this; + var element = prompt.element; + var selection = window.getSelection(); + var range = selection && selection.rangeCount && selection.getRangeAt(0); + element.value = null; + prompt.range = range || null; + if (range) { + container.appendChild(hiliter); + positionHiliteRange(prompt.range); + setTimeout(function(){ element.focus(); }); // defer focus (disrupts mouseup events) + if (callback) { prompt.onComplete = callback; } + } }; - /** - * @method parseBlock - * @param node DOM node to parse - * @return {BlockModel} parsed block model - * Parses a single block type node into a model - */ - HTMLParser.prototype.parseBlock = function(node) { - var type = this.blockTypes.findByNode(node); - if (type) { - return new BlockModel({ - type : type.id, - type_name : this.includeTypeNames && type.name, - value : trim(textOfNode(node)), - attributes : attributesForNode(node), - markup : this.parseBlockMarkup(node) - }); + Prompt.prototype.hide = function() { + if (hiliter.parentNode) { + container.removeChild(hiliter); } }; - /** - * @method parseBlockMarkup - * @param node DOM node to parse - * @return {Array} parsed markups - * Parses a single block type node's markup - */ - HTMLParser.prototype.parseBlockMarkup = function(node) { - var processedText = '', - markups = [], - index = 0, - currentNode, markup; - - // Clone the node since it will be recursively torn down - node = node.cloneNode(true); - - while (node.hasChildNodes()) { - currentNode = node.firstChild; - if (currentNode.nodeType === 1) { - markup = this.parseElementMarkup(currentNode, processedText.length); - if (markup) { - markups.push(markup); - } - // unwrap the element so we can process any children - if (currentNode.hasChildNodes()) { - unwrapNode(currentNode); - } - } else if (currentNode.nodeType === 3) { - var text = sanitizeWhitespace(currentNode.nodeValue); - if (index === 0) { text = trimLeft(text); } - if (text) { processedText += text; } - } + __exports__["default"] = Prompt; + }); +define("content-kit-editor/views/text-format-toolbar", + ["./toolbar","../../content-kit-utils/object-utils","../utils/selection-utils","../constants","exports"], + function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __exports__) { + "use strict"; + var Toolbar = __dependency1__["default"]; + var inherit = __dependency2__.inherit; + var selectionIsEditable = __dependency3__.selectionIsEditable; + var selectionIsInElement = __dependency3__.selectionIsInElement; + var Keycodes = __dependency4__.Keycodes; - // node has been processed, remove it - currentNode.parentNode.removeChild(currentNode); - index++; + function handleTextSelection(toolbar) { + var selection = window.getSelection(); + if (selection.isCollapsed || !selectionIsEditable(selection) || selection.toString().trim() === '' || !selectionIsInElement(selection, toolbar.rootElement)) { + toolbar.hide(); + } else { + toolbar.updateForSelection(selection); } + } - return markups; - }; + function TextFormatToolbar(options) { + var toolbar = this; + Toolbar.call(this, options); + toolbar.rootElement = options.rootElement; + toolbar.rootElement.addEventListener('keyup', function() { handleTextSelection(toolbar); }); - /** - * @method parseElementMarkup - * @param node DOM node to parse - * @param startIndex DOM node to parse - * @return {MarkupModel} parsed markup model - * Parses markup of a single html element node - */ - HTMLParser.prototype.parseElementMarkup = function(node, startIndex) { - var type = this.markupTypes.findByNode(node), - selfClosing, endIndex; + document.addEventListener('keyup', function(e) { + if (e.keyCode === Keycodes.ESC) { + toolbar.hide(); + } + }); - if (type) { - selfClosing = type.selfClosing; - if (!selfClosing && !node.hasChildNodes()) { return; } // check for empty nodes + document.addEventListener('mouseup', function() { + setTimeout(function() { handleTextSelection(toolbar); }); + }); - endIndex = startIndex + (selfClosing ? 0 : textOfNode(node).length); - if (endIndex > startIndex || (selfClosing && endIndex === startIndex)) { // check for empty nodes - return new MarkupModel({ - type : type.id, - type_name : this.includeTypeNames && type.name, - start : startIndex, - end : endIndex, - attributes : attributesForNode(node) - }); + window.addEventListener('resize', function() { + if(toolbar.isShowing) { + var activePromptRange = toolbar.activePrompt && toolbar.activePrompt.range; + toolbar.positionToContent(activePromptRange ? activePromptRange : window.getSelection().getRangeAt(0)); } - } - }; + }); + } + inherit(TextFormatToolbar, Toolbar); - __exports__["default"] = HTMLParser; + __exports__["default"] = TextFormatToolbar; }); -define("content-kit-compiler/renderers/html-element-renderer", - ["../../content-kit-utils/string-utils","../../content-kit-utils/array-utils","exports"], - function(__dependency1__, __dependency2__, __exports__) { +define("content-kit-editor/views/toolbar-button", + ["../commands/base","exports"], + function(__dependency1__, __exports__) { "use strict"; - var injectIntoString = __dependency1__.injectIntoString; - var sumSparseArray = __dependency2__.sumSparseArray; + var Command = __dependency1__["default"]; - /** - * Builds an opening html tag. i.e. '' + link + ''; + this.showMessage(message, element); }; - __exports__["default"] = TypeSet; + __exports__["default"] = Tooltip; }); -define("content-kit-compiler/types/type", - ["../../content-kit-utils/string-utils","exports"], - function(__dependency1__, __exports__) { +define("content-kit-editor/views/view", + ["exports"], + function(__exports__) { "use strict"; - var underscore = __dependency1__.underscore; + function View(options) { + this.tagName = options.tagName || 'div'; + this.classNames = options.classNames || []; + this.element = document.createElement(this.tagName); + this.element.className = this.classNames.join(' '); + this.container = options.container || document.body; + this.isShowing = false; + } - /** - * @class Type - * @constructor - * Contains meta info about a node type (id, name, tag, etc). - */ - function Type(options) { - if (options) { - this.name = underscore(options.name || options.tag).toUpperCase(); - if (options.id !== undefined) { - this.id = options.id; + View.prototype = { + show: function() { + var view = this; + if(!view.isShowing) { + view.container.appendChild(view.element); + view.isShowing = true; + return true; } - if (options.tag) { - this.tag = options.tag.toLowerCase(); - this.selfClosing = /^(br|img|hr|meta|link|embed)$/i.test(this.tag); + }, + hide: function() { + var view = this; + if(view.isShowing) { + view.container.removeChild(view.element); + view.isShowing = false; + return true; } - - // Register the type as constant - Type[this.name] = this; + }, + focus: function() { + this.element.focus(); + }, + addClass: function(className) { + this.classNames.push(className); + this.element.className = this.classNames.join(' '); + }, + removeClass: function(className) { + this.classNames.splice(this.classNames.indexOf(className), 1); + this.element.className = this.classNames.join(' '); } - } + }; - __exports__["default"] = Type; + __exports__["default"] = View; }); define("content-kit-compiler/renderers/embeds/instagram", ["exports"], diff --git a/src/js/content-kit-editor/commands/text-format.js b/src/js/content-kit-editor/commands/text-format.js index ce3a877ee..6c40ff05d 100644 --- a/src/js/content-kit-editor/commands/text-format.js +++ b/src/js/content-kit-editor/commands/text-format.js @@ -12,6 +12,7 @@ inherit(TextFormatCommand, Command); TextFormatCommand.prototype = { exec: function(value) { document.execCommand(this.action, false, value || null); + this.editorContext.syncModelAt(this.editorContext.getCurrentBlockIndex()); }, unexec: function(value) { document.execCommand(this.removeAction, false, value || null); diff --git a/src/js/content-kit-editor/editor-factory.js b/src/js/content-kit-editor/editor/editor-factory.js similarity index 84% rename from src/js/content-kit-editor/editor-factory.js rename to src/js/content-kit-editor/editor/editor-factory.js index f96e20cb2..a2bd1e9fd 100644 --- a/src/js/content-kit-editor/editor-factory.js +++ b/src/js/content-kit-editor/editor/editor-factory.js @@ -1,7 +1,7 @@ import Editor from './editor'; -import { TextFormatCommands, EmbedCommands } from './commands/commands'; -import { Tags } from './constants'; -import { merge } from '../content-kit-utils/object-utils'; +import { TextFormatCommands, EmbedCommands } from '../commands/commands'; +import { Tags } from '../constants'; +import { merge } from '../../content-kit-utils/object-utils'; var defaults = { defaultFormatter: Tags.PARAGRAPH, diff --git a/src/js/content-kit-editor/editor-html-renderer.js b/src/js/content-kit-editor/editor/editor-html-renderer.js similarity index 85% rename from src/js/content-kit-editor/editor-html-renderer.js rename to src/js/content-kit-editor/editor/editor-html-renderer.js index 5c7a77496..f6e7064a2 100644 --- a/src/js/content-kit-editor/editor-html-renderer.js +++ b/src/js/content-kit-editor/editor/editor-html-renderer.js @@ -1,6 +1,6 @@ -import HTMLRenderer from '../content-kit-compiler/renderers/html-renderer'; -import Type from '../content-kit-compiler/types/type'; -import { inherit } from '../content-kit-utils/object-utils'; +import HTMLRenderer from '../../content-kit-compiler/renderers/html-renderer'; +import Type from '../../content-kit-compiler/types/type'; +import { inherit } from '../../content-kit-utils/object-utils'; function embedRenderer(model) { var embedAttrs = model.attributes; diff --git a/src/js/content-kit-editor/editor.js b/src/js/content-kit-editor/editor/editor.js similarity index 63% rename from src/js/content-kit-editor/editor.js rename to src/js/content-kit-editor/editor/editor.js index b5faf55c3..61d8a261a 100644 --- a/src/js/content-kit-editor/editor.js +++ b/src/js/content-kit-editor/editor/editor.js @@ -1,41 +1,23 @@ -import TextFormatToolbar from './views/text-format-toolbar'; -import Tooltip from './views/tooltip'; -import EmbedIntent from './views/embed-intent'; -import UnorderedListCommand from './commands/unordered-list'; -import OrderedListCommand from './commands/ordered-list'; -import TextFormatCommand from './commands/text-format'; -import { Tags, RootTags, Keycodes, RegEx } from './constants'; -import { moveCursorToBeginningOfSelection, getSelectionTagName, getSelectionBlockElement, getSelectionBlockTagName } from './utils/selection-utils'; -import Compiler from '../content-kit-compiler/compiler'; -import TextModel from '../content-kit-compiler/models/text'; -import Type from '../content-kit-compiler/types/type'; -import { toArray } from '../content-kit-utils/array-utils'; -import { merge } from '../content-kit-utils/object-utils'; import EditorHTMLRenderer from './editor-html-renderer'; +import TextFormatToolbar from '../views/text-format-toolbar'; +import Tooltip from '../views/tooltip'; +import EmbedIntent from '../views/embed-intent'; +import UnorderedListCommand from '../commands/unordered-list'; +import OrderedListCommand from '../commands/ordered-list'; +import TextFormatCommand from '../commands/text-format'; +import { Tags, RootTags, Keycodes, RegEx } from '../constants'; +import { moveCursorToBeginningOfSelection, getSelectionTagName, getSelectionBlockElement, getSelectionBlockTagName } from '../utils/selection-utils'; +import { cleanPastedContent } from '../utils/paste-utils'; +import Compiler from '../../content-kit-compiler/compiler'; +import TextModel from '../../content-kit-compiler/models/text'; +import Type from '../../content-kit-compiler/types/type'; +import { toArray } from '../../content-kit-utils/array-utils'; +import { merge } from '../../content-kit-utils/object-utils'; + var editorClassName = 'ck-editor'; var editorClassNameRegExp = new RegExp(editorClassName); -function plainTextToBlocks(plainText, blockTag) { - var blocks = plainText.split(RegEx.NEWLINE), - len = blocks.length, - block, openTag, closeTag, content, i; - if(len < 2) { - return plainText; - } else { - content = ''; - openTag = '<' + blockTag + '>'; - closeTag = ''; - for(i=0; i -1) { + var blockElements = toArray(this.element.children); + var parsedBlockModel = this.compiler.parser.parseBlock(blockElements[index]); + this.model[index] = parsedBlockModel; + + // TODO: event subscription + ContentKitDemo.syncCodePane(this); + } }; Editor.prototype.syncVisualAt = function(index) { - var blockModel = this.model[index]; - var html = this.compiler.render([blockModel]); - var blockElements = toArray(this.element.children); - var element = blockElements[index]; - element.innerHTML = html; + if (index > -1) { + var blockModel = this.model[index]; + var html = this.compiler.render([blockModel]); + var blockElements = toArray(this.element.children); + var element = blockElements[index]; + element.innerHTML = html; + } }; Editor.prototype.getCurrentBlockIndex = function() { diff --git a/src/js/content-kit-editor/utils/paste-utils.js b/src/js/content-kit-editor/utils/paste-utils.js new file mode 100644 index 000000000..0e428df29 --- /dev/null +++ b/src/js/content-kit-editor/utils/paste-utils.js @@ -0,0 +1,32 @@ +import { RegEx } from '../constants'; + +function plainTextToBlocks(plainText, tag) { + var blocks = plainText.split(RegEx.NEWLINE), + len = blocks.length, + block, openTag, closeTag, content, i; + if(len < 2) { + return plainText; + } else { + content = ''; + openTag = '<' + tag + '>'; + closeTag = ''; + for(i = 0; i < len; ++i) { + block = blocks[i]; + if(block !== '') { + content += openTag + block + closeTag; + } + } + return content; + } +} + +function cleanPastedContent(event, defaultBlockTag) { + event.preventDefault(); + var data = event.clipboardData, plainText; + if(data && data.getData) { + plainText = data.getData('text/plain'); + return plainTextToBlocks(plainText, defaultBlockTag); + } +} + +export { cleanPastedContent }; diff --git a/src/js/content-kit-editor/views/text-format-toolbar.js b/src/js/content-kit-editor/views/text-format-toolbar.js index a08d205da..4b40dc4ac 100644 --- a/src/js/content-kit-editor/views/text-format-toolbar.js +++ b/src/js/content-kit-editor/views/text-format-toolbar.js @@ -3,11 +3,20 @@ import { inherit } from '../../content-kit-utils/object-utils'; import { selectionIsEditable, selectionIsInElement } from '../utils/selection-utils'; import { Keycodes } from '../constants'; +function handleTextSelection(toolbar) { + var selection = window.getSelection(); + if (selection.isCollapsed || !selectionIsEditable(selection) || selection.toString().trim() === '' || !selectionIsInElement(selection, toolbar.rootElement)) { + toolbar.hide(); + } else { + toolbar.updateForSelection(selection); + } +} + function TextFormatToolbar(options) { var toolbar = this; Toolbar.call(this, options); toolbar.rootElement = options.rootElement; - toolbar.rootElement.addEventListener('keyup', function() { toolbar.handleTextSelection(); }); + toolbar.rootElement.addEventListener('keyup', function() { handleTextSelection(toolbar); }); document.addEventListener('keyup', function(e) { if (e.keyCode === Keycodes.ESC) { @@ -16,7 +25,7 @@ function TextFormatToolbar(options) { }); document.addEventListener('mouseup', function() { - setTimeout(function() { toolbar.handleTextSelection(); }); + setTimeout(function() { handleTextSelection(toolbar); }); }); window.addEventListener('resize', function() { @@ -28,14 +37,4 @@ function TextFormatToolbar(options) { } inherit(TextFormatToolbar, Toolbar); -TextFormatToolbar.prototype.handleTextSelection = function() { - var toolbar = this; - var selection = window.getSelection(); - if (selection.isCollapsed || !selectionIsEditable(selection) || selection.toString().trim() === '' || !selectionIsInElement(selection, toolbar.rootElement)) { - toolbar.hide(); - } else { - toolbar.updateForSelection(selection); - } -}; - export default TextFormatToolbar; diff --git a/src/js/content-kit-utils/node-utils.js b/src/js/content-kit-utils/node-utils.js index 5f9f1ac05..9315cda8f 100644 --- a/src/js/content-kit-utils/node-utils.js +++ b/src/js/content-kit-utils/node-utils.js @@ -58,7 +58,7 @@ function attributesForNode(node /*,blacklist*/) { for (i = 0; i < len; i++) { attr = attrs[i]; name = attr.name; - if (attr.specified) { + if (attr.specified && attr.value) { //if (blacklist && name in blacklist)) { continue; } hash = hash || {}; hash[name] = attr.value; diff --git a/src/js/content-kit.js b/src/js/content-kit.js index 94a129b52..64f8a6c91 100644 --- a/src/js/content-kit.js +++ b/src/js/content-kit.js @@ -7,7 +7,7 @@ import Compiler from './content-kit-compiler/compiler'; import HTMLParser from './content-kit-compiler/parsers/html-parser'; import HTMLRenderer from './content-kit-compiler/renderers/html-renderer'; -import EditorFactory from './content-kit-editor/editor-factory'; +import EditorFactory from './content-kit-editor/editor/editor-factory'; var ContentKit = {}; ContentKit.Type = Type;