From ac7b1f0098465c66489a0d7d714d21a5dc299586 Mon Sep 17 00:00:00 2001
From: gdub22
Date: Mon, 11 Aug 2014 12:09:34 -0400
Subject: [PATCH] es6 modules
---
.jshintrc | 4 +-
dist/content-kit-editor.js | 4493 +++++++++--------
gulpfile.js | 29 +-
package.json | 4 +-
src/js/commands.js | 294 --
src/js/content-kit-compiler/compiler.js | 68 +
src/js/content-kit-compiler/models/block.js | 31 +
src/js/content-kit-compiler/models/embed.js | 45 +
src/js/content-kit-compiler/models/image.js | 22 +
src/js/content-kit-compiler/models/markup.js | 17 +
src/js/content-kit-compiler/models/model.js | 20 +
src/js/content-kit-compiler/models/text.js | 19 +
.../parsers/html-parser.js | 172 +
.../renderers/embeds/instagram.js | 7 +
.../renderers/embeds/twitter.js | 7 +
.../renderers/embeds/youtube.js | 19 +
.../renderers/html-element-renderer.js | 98 +
.../renderers/html-embed-renderer.js | 55 +
.../renderers/html-renderer.js | 67 +
.../types/default-types.js | 32 +
src/js/content-kit-compiler/types/type-set.js | 55 +
src/js/content-kit-compiler/types/type.js | 24 +
src/js/content-kit-editor/commands/base.js | 13 +
src/js/content-kit-editor/commands/bold.js | 22 +
.../content-kit-editor/commands/commands.js | 40 +
src/js/content-kit-editor/commands/embed.js | 53 +
.../commands/format-block.js | 32 +
src/js/content-kit-editor/commands/heading.js | 14 +
src/js/content-kit-editor/commands/image.js | 58 +
src/js/content-kit-editor/commands/italic.js | 14 +
src/js/content-kit-editor/commands/link.js | 33 +
src/js/content-kit-editor/commands/list.js | 24 +
.../commands/ordered-list.js | 14 +
src/js/content-kit-editor/commands/quote.js | 14 +
.../content-kit-editor/commands/subheading.js | 14 +
.../commands/text-format.js | 21 +
.../commands/unordered-list.js | 14 +
src/js/{ => content-kit-editor}/constants.js | 4 +-
src/js/content-kit-editor/editor-factory.js | 42 +
src/js/content-kit-editor/editor.js | 224 +
.../utils/element-utils.js | 5 +
src/js/content-kit-editor/utils/http-utils.js | 13 +
.../utils/selection-utils.js | 7 +
.../content-kit-editor/views/embed-intent.js | 113 +
.../{ => content-kit-editor}/views/message.js | 7 +-
src/js/content-kit-editor/views/prompt.js | 67 +
.../views/text-format-toolbar.js | 41 +
.../views/toolbar-button.js | 49 +
src/js/content-kit-editor/views/toolbar.js | 119 +
.../{ => content-kit-editor}/views/tooltip.js | 8 +-
src/js/{ => content-kit-editor}/views/view.js | 2 +
src/js/content-kit-utils/array-utils.js | 27 +
src/js/content-kit-utils/node-utils.js | 70 +
src/js/content-kit-utils/object-utils.js | 49 +
src/js/content-kit-utils/string-utils.js | 43 +
src/js/content-kit.js | 27 +
src/js/editor.js | 250 -
src/js/ext/content-kit-compiler.js | 1089 ----
src/js/index.js | 5 -
src/js/utils/array-utils.js | 11 -
src/js/utils/http-utils.js | 33 -
src/js/utils/object-utils.js | 21 -
src/js/views/embed-intent.js | 106 -
src/js/views/prompt.js | 63 -
src/js/views/toolbar-button.js | 50 -
src/js/views/toolbar.js | 156 -
66 files changed, 4447 insertions(+), 4216 deletions(-)
delete mode 100644 src/js/commands.js
create mode 100644 src/js/content-kit-compiler/compiler.js
create mode 100644 src/js/content-kit-compiler/models/block.js
create mode 100644 src/js/content-kit-compiler/models/embed.js
create mode 100644 src/js/content-kit-compiler/models/image.js
create mode 100644 src/js/content-kit-compiler/models/markup.js
create mode 100644 src/js/content-kit-compiler/models/model.js
create mode 100644 src/js/content-kit-compiler/models/text.js
create mode 100644 src/js/content-kit-compiler/parsers/html-parser.js
create mode 100644 src/js/content-kit-compiler/renderers/embeds/instagram.js
create mode 100644 src/js/content-kit-compiler/renderers/embeds/twitter.js
create mode 100644 src/js/content-kit-compiler/renderers/embeds/youtube.js
create mode 100644 src/js/content-kit-compiler/renderers/html-element-renderer.js
create mode 100644 src/js/content-kit-compiler/renderers/html-embed-renderer.js
create mode 100644 src/js/content-kit-compiler/renderers/html-renderer.js
create mode 100644 src/js/content-kit-compiler/types/default-types.js
create mode 100644 src/js/content-kit-compiler/types/type-set.js
create mode 100644 src/js/content-kit-compiler/types/type.js
create mode 100644 src/js/content-kit-editor/commands/base.js
create mode 100644 src/js/content-kit-editor/commands/bold.js
create mode 100644 src/js/content-kit-editor/commands/commands.js
create mode 100644 src/js/content-kit-editor/commands/embed.js
create mode 100644 src/js/content-kit-editor/commands/format-block.js
create mode 100644 src/js/content-kit-editor/commands/heading.js
create mode 100644 src/js/content-kit-editor/commands/image.js
create mode 100644 src/js/content-kit-editor/commands/italic.js
create mode 100644 src/js/content-kit-editor/commands/link.js
create mode 100644 src/js/content-kit-editor/commands/list.js
create mode 100644 src/js/content-kit-editor/commands/ordered-list.js
create mode 100644 src/js/content-kit-editor/commands/quote.js
create mode 100644 src/js/content-kit-editor/commands/subheading.js
create mode 100644 src/js/content-kit-editor/commands/text-format.js
create mode 100644 src/js/content-kit-editor/commands/unordered-list.js
rename src/js/{ => content-kit-editor}/constants.js (89%)
create mode 100644 src/js/content-kit-editor/editor-factory.js
create mode 100644 src/js/content-kit-editor/editor.js
rename src/js/{ => content-kit-editor}/utils/element-utils.js (90%)
create mode 100644 src/js/content-kit-editor/utils/http-utils.js
rename src/js/{ => content-kit-editor}/utils/selection-utils.js (88%)
create mode 100644 src/js/content-kit-editor/views/embed-intent.js
rename src/js/{ => content-kit-editor}/views/message.js (71%)
create mode 100644 src/js/content-kit-editor/views/prompt.js
create mode 100644 src/js/content-kit-editor/views/text-format-toolbar.js
create mode 100644 src/js/content-kit-editor/views/toolbar-button.js
create mode 100644 src/js/content-kit-editor/views/toolbar.js
rename src/js/{ => content-kit-editor}/views/tooltip.js (82%)
rename src/js/{ => content-kit-editor}/views/view.js (97%)
create mode 100644 src/js/content-kit-utils/array-utils.js
create mode 100644 src/js/content-kit-utils/node-utils.js
create mode 100644 src/js/content-kit-utils/object-utils.js
create mode 100644 src/js/content-kit-utils/string-utils.js
create mode 100644 src/js/content-kit.js
delete mode 100644 src/js/editor.js
delete mode 100755 src/js/ext/content-kit-compiler.js
delete mode 100644 src/js/index.js
delete mode 100644 src/js/utils/array-utils.js
delete mode 100644 src/js/utils/http-utils.js
delete mode 100644 src/js/utils/object-utils.js
delete mode 100644 src/js/views/embed-intent.js
delete mode 100644 src/js/views/prompt.js
delete mode 100644 src/js/views/toolbar-button.js
delete mode 100644 src/js/views/toolbar.js
diff --git a/.jshintrc b/.jshintrc
index 97daaa015..63890519f 100644
--- a/.jshintrc
+++ b/.jshintrc
@@ -28,10 +28,11 @@
"noempty" : true, // Prohibit use of empty blocks.
"nonew" : true, // Prohibit use of constructors for side-effects.
"regexp" : true, // Prohibit `.` and `[^...]` in regular expressions.
- //"undef" : true, // Require all non-global variables be declared before they are used.
+ "undef" : true, // Require all non-global variables be declared before they are used.
//"unused" : true, // Warn when variables are created but not used.
"trailing" : true, // Prohibit trailing whitespaces.
"es3" : true, // Prohibit trailing commas for old IE
+ "esnext" : true, // Allow ES.next specific features such as `const` and `let`.
// == Relaxing Options ================================================
//
@@ -47,7 +48,6 @@
"debug" : false, // Allow debugger statements e.g. browser breakpoints.
"eqnull" : false, // Tolerate use of `== null`.
"es5" : false, // Allow EcmaScript 5 syntax.
- "esnext" : false, // Allow ES.next specific features such as `const` and `let`.
"evil" : false, // Tolerate use of `eval`.
"expr" : false, // Tolerate `ExpressionStatement` as Programs.
"funcscope" : false, // Tolerate declarations of variables inside of control structures while accessing them later from the outside.
diff --git a/dist/content-kit-editor.js b/dist/content-kit-editor.js
index fe05cc666..bc31dbdbd 100755
--- a/dist/content-kit-editor.js
+++ b/dist/content-kit-editor.js
@@ -3,1379 +3,16 @@
* @version 0.1.0
* @author Garth Poitras (http://garthpoitras.com/)
* @license MIT
- * Last modified: Aug 10, 2014
+ * Last modified: Aug 11, 2014
*/
(function(exports, document) {
'use strict';
-/**
- * @namespace ContentKit
- */
-var ContentKit = exports.ContentKit || {};
-exports.ContentKit = ContentKit;
-
-var Keycodes = {
- BKSP : 8,
- ENTER : 13,
- ESC : 27,
- DEL : 46
-};
-
-var Regex = {
- NEWLINE : /[\r\n]/g,
- HTTP_PROTOCOL : /^https?:\/\//i,
- HEADING_TAG : /^(H1|H2|H3|H4|H5|H6)$/i,
- UL_START : /^[-*]\s/,
- OL_START : /^1\.\s/
-};
-
-var SelectionDirection = {
- LEFT_TO_RIGHT : 1,
- RIGHT_TO_LEFT : 2,
- SAME_NODE : 3
-};
-
-var ToolbarDirection = {
- TOP : 1,
- RIGHT : 2
-};
-
-var Tags = {
- PARAGRAPH : 'P',
- HEADING : 'H2',
- SUBHEADING : 'H3',
- QUOTE : 'BLOCKQUOTE',
- FIGURE : 'FIGURE',
- LIST : 'UL',
- ORDERED_LIST : 'OL',
- LIST_ITEM : 'LI',
- LINK : 'A',
- BOLD : 'B',
- ITALIC : 'I'
-};
-
-var RootTags = [ Tags.PARAGRAPH, Tags.HEADING, Tags.SUBHEADING, Tags.QUOTE, Tags.FIGURE, Tags.LIST, Tags.ORDERED_LIST ];
-
-/**
- * Converts an array-like object (i.e. NodeList) to Array
- */
-function toArray(obj) {
- var array = [],
- i = obj.length >>> 0; // cast to Uint32
- while (i--) {
- array[i] = obj[i];
- }
- return array;
-}
-
-function createDiv(className) {
- var div = document.createElement('div');
- if (className) {
- div.className = className;
- }
- return div;
-}
-
-function hideElement(element) {
- element.style.display = 'none';
-}
-
-function showElement(element) {
- element.style.display = 'block';
-}
-
-function swapElements(elementToShow, elementToHide) {
- hideElement(elementToHide);
- showElement(elementToShow);
-}
-
-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;
- }
-}
-
-function nodeIsDescendantOfElement(node, element) {
- var parentNode = node.parentNode;
- while(parentNode) {
- if (parentNode === element) {
- return true;
- }
- parentNode = parentNode.parentNode;
- }
- return false;
-}
-
-function getElementRelativeOffset(element) {
- var offset = { left: 0, top: -window.pageYOffset };
- var offsetParent = element.offsetParent;
- var offsetParentPosition = window.getComputedStyle(offsetParent).position;
- var offsetParentRect;
-
- if (offsetParentPosition === 'relative') {
- offsetParentRect = offsetParent.getBoundingClientRect();
- offset.left = offsetParentRect.left;
- offset.top = offsetParentRect.top;
- }
- return offset;
-}
-
-function getElementComputedStyleNumericProp(element, prop) {
- return parseFloat(window.getComputedStyle(element)[prop]);
-}
-
-function positionElementToRect(element, rect, topOffset, leftOffset) {
- var relativeOffset = getElementRelativeOffset(element);
- var style = element.style;
- var round = Math.round;
-
- topOffset = topOffset || 0;
- leftOffset = leftOffset || 0;
- style.left = round(rect.left - relativeOffset.left - leftOffset) + 'px';
- style.top = round(rect.top - relativeOffset.top - topOffset) + 'px';
-}
-
-function positionElementHorizontallyCenteredToRect(element, rect, topOffset) {
- var horizontalCenter = (element.offsetWidth / 2) - (rect.width / 2);
- positionElementToRect(element, rect, topOffset, horizontalCenter);
-}
-
-function positionElementCenteredAbove(element, aboveElement) {
- var elementMargin = getElementComputedStyleNumericProp(element, 'marginBottom');
- positionElementHorizontallyCenteredToRect(element, aboveElement.getBoundingClientRect(), element.offsetHeight + elementMargin);
-}
-
-function positionElementCenteredBelow(element, belowElement) {
- var elementMargin = getElementComputedStyleNumericProp(element, 'marginTop');
- positionElementHorizontallyCenteredToRect(element, belowElement.getBoundingClientRect(), -element.offsetHeight - elementMargin);
-}
-
-function positionElementCenteredIn(element, inElement) {
- var verticalCenter = (inElement.offsetHeight / 2) - (element.offsetHeight / 2);
- positionElementHorizontallyCenteredToRect(element, inElement.getBoundingClientRect(), -verticalCenter);
-}
-
-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);
-}
-
-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);
-}
-
-var HTTP = (function() {
-
- var head = document.head;
- var uuid = 0;
-
- return {
- get: function(url, callback) {
- var request = new XMLHttpRequest();
- request.onload = function() {
- callback(this.responseText);
- };
- request.onerror = function(error) {
- callback(null, error);
- };
- request.open('GET', url);
- request.send();
- },
-
- jsonp: function(url, callback) {
- var script = document.createElement('script');
- var name = '_jsonp_' + uuid++;
- url += ( url.match(/\?/) ? '&' : '?' ) + 'callback=' + name;
- script.src = url;
- exports[name] = function(response) {
- callback(JSON.parse(response));
- head.removeChild(script);
- delete exports[name];
- };
- head.appendChild(script);
- }
- };
-
-}());
-
-function merge(object, updates) {
- updates = updates || {};
- for(var o in updates) {
- if (updates.hasOwnProperty(o)) {
- object[o] = updates[o];
- }
- }
- return object;
-}
-
-function inherits(Subclass, Superclass) {
- Subclass._super = Superclass;
- Subclass.prototype = Object.create(Superclass.prototype, {
- constructor: {
- value: Subclass,
- enumerable: false,
- writable: true,
- configurable: true
- }
- });
-}
-
-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;
-}
-
-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);
-}
-
-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;
-}
-
-function getSelectionTagName() {
- var element = getSelectionElement();
- return element ? element.tagName : null;
-}
-
-function getSelectionBlockTagName() {
- var element = getSelectionBlockElement();
- return element ? element.tagName : null;
-}
-
-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;
- }
- }
- return tags;
-}
-
-function selectionIsInElement(selection, element) {
- var node = selection.anchorNode;
- return node && nodeIsDescendantOfElement(node, element);
-}
-
-function selectionIsEditable(selection) {
- var el = getSelectionBlockElement(selection);
- return el.contentEditable !== 'false';
-}
-
-/*
-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));
- }
- }
- return ranges;
-}
-
-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);
-}
-
-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;
-}
-
-View.prototype = {
- show: function() {
- var view = this;
- if(!view.isShowing) {
- view.container.appendChild(view.element);
- view.isShowing = true;
- return true;
- }
- },
- hide: function() {
- var view = this;
- if(view.isShowing) {
- view.container.removeChild(view.element);
- view.isShowing = false;
- return true;
- }
- },
- 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(' ');
- }
-};
-
-var EmbedIntent = (function() {
-
- 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.element.title = 'Insert image or embed...';
- embedIntent.element.addEventListener('mouseup', function(e) {
- if (embedIntent.isActive) {
- embedIntent.deactivate();
- } else {
- embedIntent.activate();
- }
- e.stopPropagation();
- });
-
- embedIntent.toolbar = new Toolbar({ embedIntent: embedIntent, editor: embedIntent.editorContext, commands: options.commands, direction: ToolbarDirection.RIGHT });
- embedIntent.isActive = false;
-
- function embedIntentHandler() {
- var blockElement = getSelectionBlockElement();
- var blockElementContent = blockElement && blockElement.innerHTML;
- if (blockElementContent === '' || blockElementContent === '
') {
- embedIntent.showAt(blockElement);
- } else {
- embedIntent.hide();
- }
- }
-
- rootElement.addEventListener('keyup', embedIntentHandler);
-
- document.addEventListener('mouseup', function(e) {
- setTimeout(function() {
- if (!nodeIsDescendantOfElement(e.target, embedIntent.toolbar.element)) {
- embedIntentHandler();
- }
- });
- });
-
- document.addEventListener('keyup', function(e) {
- if (e.keyCode === Keycodes.ESC) {
- embedIntent.hide();
- }
- });
-
- window.addEventListener('resize', function() {
- if(embedIntent.isShowing) {
- positionElementToLeftOf(embedIntent.element, embedIntent.atNode);
- if (embedIntent.toolbar.isShowing) {
- embedIntent.toolbar.positionToContent(embedIntent.element);
- }
- }
- });
- }
- inherits(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);
- };
-
- EmbedIntent.prototype.activate = function() {
- if (!this.isActive) {
- this.addClass('activated');
- this.toolbar.show();
- this.toolbar.positionToContent(this.element);
- this.isActive = true;
- }
- };
-
- EmbedIntent.prototype.deactivate = function() {
- if (this.isActive) {
- this.removeClass('activated');
- this.toolbar.hide();
- this.isActive = false;
- }
- };
-
- var loading = createDiv('div');
- loading.className = 'ck-embed-loading';
- loading.innerHTML = 'LOADING';
- EmbedIntent.prototype.showLoading = function() {
- this.hide();
- document.body.appendChild(loading);
- positionElementCenteredIn(loading, this.atNode);
- };
-
- EmbedIntent.prototype.hideLoading = function() {
- document.body.removeChild(loading);
- };
-
-
- return EmbedIntent;
-}());
-
-function Message(options) {
- options = options || {};
- options.classNames = ['ck-message'];
- View.call(this, options);
-}
-inherits(Message, View);
-
-Message.prototype.show = function(message) {
- var messageView = this;
- messageView.element.innerHTML = message;
- Message._super.prototype.show.call(messageView);
- setTimeout(function() {
- messageView.hide();
- }, 3000);
-};
-
-var Prompt = (function() {
-
- var container = document.body;
- var hiliter = createDiv('ck-editor-hilite');
-
- 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(); }
- }
- });
-
- window.addEventListener('resize', function() {
- var activeHilite = hiliter.parentNode;
- var range = prompt.range;
- if(activeHilite && range) {
- positionHiliteRange(range);
- }
- });
- }
- inherits(Prompt, View);
-
- 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; }
- }
- };
-
- Prompt.prototype.hide = function() {
- if (hiliter.parentNode) {
- container.removeChild(hiliter);
- }
- };
-
- function positionHiliteRange(range) {
- var rect = range.getBoundingClientRect();
- var style = hiliter.style;
- style.width = rect.width + 'px';
- style.height = rect.height + 'px';
- positionElementToRect(hiliter, rect);
- }
-
- return Prompt;
-}());
-
-var ToolbarButton = (function() {
-
- var buttonClassName = 'ck-toolbar-btn';
-
- function ToolbarButton(options) {
- var button = this;
- var toolbar = options.toolbar;
- var command = options.command;
- var prompt = command.prompt;
- var element = document.createElement('button');
-
- if(typeof command === 'string') {
- command = Command.index[command];
- }
-
- button.element = element;
- button.command = command;
- button.isActive = false;
-
- 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();
- }
- });
- }
-
- 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;
- }
- }
- };
-
- return ToolbarButton;
-}());
-
-var Toolbar = (function() {
-
- 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');
- }
-
- View.call(toolbar, options);
-
- toolbar.activePrompt = null;
- toolbar.buttons = [];
-
- toolbar.promptContainerElement = createDiv('ck-toolbar-prompt');
- toolbar.buttonContainerElement = createDiv('ck-toolbar-buttons');
- toolbar.element.appendChild(toolbar.promptContainerElement);
- toolbar.element.appendChild(toolbar.buttonContainerElement);
-
- for(i = 0; i < commandCount; i++) {
- this.addCommand(commands[i]);
- }
-
- // Closes prompt if displayed when changing selection
- document.addEventListener('mouseup', function() {
- toolbar.dismissPrompt();
- });
- }
- inherits(Toolbar, View);
-
- Toolbar.prototype.hide = function() {
- if (Toolbar._super.prototype.hide.call(this)) {
- var style = this.element.style;
- style.left = '';
- style.top = '';
- this.dismissPrompt();
- }
- };
-
- 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);
- };
-
- 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;
- };
-
- Toolbar.prototype.dismissPrompt = function() {
- var toolbar = this;
- var activePrompt = toolbar.activePrompt;
- if (activePrompt) {
- activePrompt.hide();
- swapElements(toolbar.buttonContainerElement, toolbar.promptContainerElement);
- toolbar.activePrompt = null;
- }
- };
-
- Toolbar.prototype.updateForSelection = function(selection) {
- var toolbar = this;
- if (selection.isCollapsed) {
- toolbar.hide();
- } else {
- toolbar.show();
- toolbar.positionToContent(selection.getRangeAt(0));
- updateButtonsForSelection(toolbar.buttons, selection);
- }
- };
-
- Toolbar.prototype.positionToContent = function(content) {
- var directions = ToolbarDirection;
- var positioningMethod;
- switch(this.direction) {
- case directions.RIGHT:
- positioningMethod = positionElementToRightOf;
- break;
- default:
- positioningMethod = positionElementCenteredAbove;
- }
- positioningMethod(this.element, content);
- };
-
- function updateButtonsForSelection(buttons, selection) {
- var selectedTags = tagsInSelection(selection),
- len = buttons.length,
- i, button;
-
- for (i = 0; i < len; i++) {
- button = buttons[i];
- if (selectedTags.indexOf(button.command.tag) > -1) {
- button.setActive();
- } else {
- button.setInactive();
- }
- }
- }
-
- return Toolbar;
-}());
-
-
-var TextFormatToolbar = (function() {
-
- 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();
- }
- });
-
- document.addEventListener('mouseup', function() {
- setTimeout(function() { toolbar.handleTextSelection(); });
- });
-
- window.addEventListener('resize', function() {
- if(toolbar.isShowing) {
- var activePromptRange = toolbar.activePrompt && toolbar.activePrompt.range;
- toolbar.positionToContent(activePromptRange ? activePromptRange : window.getSelection().getRangeAt(0));
- }
- });
- }
- inherits(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);
- }
- };
-
- return TextFormatToolbar;
-}());
-
-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);
-
- rootElement.addEventListener('mouseover', function(e) {
- var target = getEventTargetMatchingTag(options.showForTag, e.target, rootElement);
- if (target) {
- 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();
- }
- });
-}
-inherits(Tooltip, View);
-
-Tooltip.prototype.showMessage = function(message, element) {
- var tooltip = this;
- var tooltipElement = tooltip.element;
- tooltipElement.innerHTML = message;
- tooltip.show();
- positionElementCenteredBelow(tooltipElement, element);
-};
-
-Tooltip.prototype.showLink = function(link, element) {
- var message = '' + link + '';
- this.showMessage(message, element);
-};
-
-function createCommandIndex(commands) {
- var index = {};
- var len = commands.length, i, command;
- for(i = 0; i < len; i++) {
- command = commands[i];
- index[command.name] = command;
- }
- return index;
-}
-
-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; }
-}
-Command.prototype.exec = function(){};
-
-function TextFormatCommand(options) {
- Command.call(this, options);
- this.tag = options.tag.toUpperCase();
- this.action = options.action || this.name;
- this.removeAction = options.removeAction || this.action;
-}
-inherits(TextFormatCommand, Command);
-
-TextFormatCommand.prototype = {
- exec: function(value) {
- document.execCommand(this.action, false, value || null);
- },
- unexec: function(value) {
- document.execCommand(this.removeAction, false, value || null);
- }
-};
-
-function BoldCommand() {
- TextFormatCommand.call(this, {
- name: 'bold',
- tag: Tags.BOLD,
- button: ''
- });
-}
-inherits(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);
- }
-};
-
-function ItalicCommand() {
- TextFormatCommand.call(this, {
- name: 'italic',
- tag: Tags.ITALIC,
- button: ''
- });
-}
-inherits(ItalicCommand, TextFormatCommand);
-
-function LinkCommand() {
- TextFormatCommand.call(this, {
- name: 'link',
- tag: Tags.LINK,
- action: 'createLink',
- removeAction: 'unlink',
- button: '',
- prompt: new Prompt({
- command: this,
- placeholder: 'Enter a url, press return...'
- })
- });
-}
-inherits(LinkCommand, TextFormatCommand);
-LinkCommand.prototype.exec = function(url) {
- if(this.tag === getSelectionTagName()) {
- this.unexec();
- } else {
- if (!Regex.HTTP_PROTOCOL.test(url)) {
- url = 'http://' + url;
- }
- LinkCommand._super.prototype.exec.call(this, url);
- }
-};
-
-function FormatBlockCommand(options) {
- options.action = 'formatBlock';
- TextFormatCommand.call(this, options);
-}
-inherits(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
- if(tag === blockElement.tagName) {
- value = Tags.PARAGRAPH;
- } else {
- // Flattens the selection before applying the block format.
- // Otherwise, undesirable nested blocks can occur.
- var flatNode = document.createTextNode(blockElement.textContent);
- blockElement.parentNode.insertBefore(flatNode, blockElement);
- blockElement.parentNode.removeChild(blockElement);
- selectNode(flatNode);
- }
-
- FormatBlockCommand._super.prototype.exec.call(this, value);
-};
-
-function QuoteCommand() {
- FormatBlockCommand.call(this, {
- name: 'quote',
- tag: Tags.QUOTE,
- button: ''
- });
-}
-inherits(QuoteCommand, FormatBlockCommand);
-
-function HeadingCommand() {
- FormatBlockCommand.call(this, {
- name: 'heading',
- tag: Tags.HEADING,
- button: '1'
- });
-}
-inherits(HeadingCommand, FormatBlockCommand);
-
-function SubheadingCommand() {
- FormatBlockCommand.call(this, {
- name: 'subheading',
- tag: Tags.SUBHEADING,
- button: '2'
- });
-}
-inherits(SubheadingCommand, FormatBlockCommand);
-
-function ListCommand(options) {
- TextFormatCommand.call(this, options);
-}
-inherits(ListCommand, TextFormatCommand);
-ListCommand.prototype.exec = function() {
- ListCommand._super.prototype.exec.call(this);
-
- // After creation, lists need to be unwrapped from the default formatter P tag
- var listElement = getSelectionBlockElement();
- var wrapperNode = listElement.parentNode;
- if (wrapperNode.firstChild === listElement) {
- var editorNode = wrapperNode.parentNode;
- editorNode.insertBefore(listElement, wrapperNode);
- editorNode.removeChild(wrapperNode);
- selectNode(listElement);
- }
-};
-
-function UnorderedListCommand() {
- ListCommand.call(this, {
- name: 'list',
- tag: Tags.LIST,
- action: 'insertUnorderedList'
- });
-}
-inherits(UnorderedListCommand, ListCommand);
-
-function OrderedListCommand() {
- ListCommand.call(this, {
- name: 'ordered list',
- tag: Tags.ORDERED_LIST,
- action: 'insertOrderedList'
- });
-}
-inherits(OrderedListCommand, ListCommand);
-
-TextFormatCommand.all = [
- new BoldCommand(),
- new ItalicCommand(),
- new LinkCommand(),
- new QuoteCommand(),
- new HeadingCommand(),
- new SubheadingCommand()
-];
-
-TextFormatCommand.index = createCommandIndex(TextFormatCommand.all);
-
-
-function EmbedCommand(options) {
- Command.call(this, options);
-}
-inherits(EmbedCommand, Command);
-
-function ImageEmbedCommand(options) {
- EmbedCommand.call(this, {
- name: 'image',
- button: ''
- });
- if (window.XHRFileUploader) {
- this.uploader = new XHRFileUploader({ url: '/upload', maxFileSize: 5000000 });
- }
-}
-inherits(ImageEmbedCommand, EmbedCommand);
-
-ImageEmbedCommand.prototype = {
- exec: function() {
- ImageEmbedCommand._super.prototype.exec.call(this);
- var clickEvent = new MouseEvent('click', { bubbles: false });
- if (!this.fileInput) {
- var command = this;
- var fileInput = this.fileInput = document.createElement('input');
- fileInput.type = 'file';
- fileInput.accept = 'image/*';
- fileInput.className = 'ck-file-input';
- fileInput.addEventListener('change', function(e) {
- command.handleFile(e);
- });
- document.body.appendChild(fileInput);
- }
- this.fileInput.dispatchEvent(clickEvent);
- },
- handleFile: function(e) {
- var fileInput = e.target;
- var editor = this.editorContext;
- var embedIntent = this.embedIntent;
-
- embedIntent.showLoading();
- this.uploader.upload({
- fileInput: fileInput,
- complete: function(response, error) {
- embedIntent.hideLoading();
- if (error || !response || !response.url) {
- return new Message().show(error.message || 'Error uploading image');
- }
- var imageModel = new ContentKit.ImageModel({ src: response.url });
- var index = editor.getCurrentBlockIndex();
- editor.insertBlockAt(imageModel, index);
- editor.syncVisualAt(index);
- }
- });
- fileInput.value = null; // reset file input
- // TODO: client-side render while uploading
- }
-};
-
-function OEmbedCommand(options) {
- EmbedCommand.call(this, {
- name: 'oEmbed',
- button: '',
- prompt: new Prompt({
- command: this,
- placeholder: 'Paste a YouTube or Twitter url...'
- })
- });
-}
-inherits(OEmbedCommand, EmbedCommand);
-
-OEmbedCommand.prototype.exec = function(url) {
- var command = this;
- var editorContext = command.editorContext;
- var index = editorContext.getCurrentBlockIndex();
- var oEmbedEndpoint = 'http://noembed.com/embed?url=';
-
- command.embedIntent.showLoading();
- if (!Regex.HTTP_PROTOCOL.test(url)) {
- url = 'http://' + url;
- }
-
- HTTP.get(oEmbedEndpoint + url, function(responseText, error) {
- command.embedIntent.hideLoading();
- if (error) {
- new Message().show('Embed error: status code ' + error.currentTarget.status);
- } else {
- var json = JSON.parse(responseText);
- if (json.error) {
- new Message().show('Embed error: ' + json.error);
- } else {
- var embedModel = new ContentKit.EmbedModel(json);
- //if (!embedModel.attributes.provider_id) {
- // new Message().show('Embed error: "' + embedModel.attributes.provider_name + '" embeds are not supported at this time');
- //} else {
- editorContext.insertBlockAt(embedModel, index);
- editorContext.syncVisualAt(index);
- //}
- }
- }
- });
-};
-
-EmbedCommand.all = [
- new ImageEmbedCommand(),
- new OEmbedCommand()
-];
-
-EmbedCommand.index = createCommandIndex(EmbedCommand.all);
-
-ContentKit.Editor = (function() {
-
- // Default `Editor` options
- var defaults = {
- defaultFormatter: Tags.PARAGRAPH,
- placeholder: 'Write here...',
- spellcheck: true,
- autofocus: true,
- textFormatCommands: TextFormatCommand.all,
- embedCommands: EmbedCommand.all
- };
-
- var editorClassName = 'ck-editor';
- var editorClassNameRegExp = new RegExp(editorClassName);
-
- /**
- * Publically expose this class which sets up indiviual `Editor` classes
- * depending if user passes string selector, Node, or NodeList
- */
- 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];
- }
-
- if (elements) {
- options = merge(defaults, options);
- elementsLen = elements.length;
- for (i = 0; i < elementsLen; i++) {
- editors.push(new Editor(elements[i], options));
- }
- }
-
- return editors.length > 1 ? editors : editors[0];
- }
-
- /**
- * @class Editor
- * An individual Editor
- * @param element `Element` node
- * @param options hash of options
- */
- function Editor(element, options) {
- var editor = this;
- merge(editor, options);
-
- if (element) {
- var className = element.className;
- var dataset = element.dataset;
-
- if (!editorClassNameRegExp.test(className)) {
- className += (className ? ' ' : '') + editorClassName;
- }
- element.className = className;
-
- if (!dataset.placeholder) {
- dataset.placeholder = editor.placeholder;
- }
- if(!editor.spellcheck) {
- element.spellcheck = false;
- }
-
- element.setAttribute('contentEditable', true);
- editor.element = element;
-
- var compiler = editor.compiler = options.compiler || new ContentKit.Compiler();
- editor.syncModel();
-
- bindTypingEvents(editor);
- bindPasteEvents(editor);
-
- editor.textFormatToolbar = new TextFormatToolbar({ rootElement: element, commands: editor.textFormatCommands });
- var linkTooltips = new Tooltip({ rootElement: element, showForTag: Tags.LINK });
-
- 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
- });
-
- if (editor.imageServiceUrl) {
- // TODO: lookup by name
- editor.embedCommands[0].uploader.url = editor.imageServiceUrl;
- }
- }
-
- if(editor.autofocus) { element.focus(); }
- }
- }
-
- Editor.prototype.syncModel = function() {
- this.model = this.compiler.parse(this.element.innerHTML);
- };
-
- Editor.prototype.syncModelAt = function(index) {
- var blockElements = toArray(this.element.children);
- var parsedBlockModel = this.compiler.parser.parseBlock(blockElements[index]);
- this.model[index] = parsedBlockModel;
- };
-
- 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;
- runAfterRenderHooks(element, blockModel);
- };
-
- Editor.prototype.getCurrentBlockIndex = function() {
- var selectionEl = getSelectionBlockElement();
- var blockElements = toArray(this.element.children);
- return blockElements.indexOf(selectionEl);
- };
-
- Editor.prototype.insertBlock = function(model) {
- this.insertBlockAt(model, this.getCurrentBlockIndex());
- };
-
- Editor.prototype.insertBlockAt = function(model, index) {
- model = model || new ContentKit.TextModel();
- this.model.splice(index, 0, model);
- };
-
- Editor.prototype.addTextFormat = function(opts) {
- var command = new TextFormatCommand(opts);
- this.compiler.registerMarkupType(new ContentKit.Type({
- name : opts.name,
- tag : opts.tag || opts.name
- }));
- this.textFormatCommands.push(command);
- this.textFormatToolbar.addCommand(command);
- };
-
- Editor.prototype.willRenderType = function(type, renderer) {
- this.compiler.renderer.willRenderType(type, renderer);
- };
-
- function bindTypingEvents(editor) {
- var editorEl = editor.element;
-
- // 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();
- }
- }
- });
-
- // Creates unordered list when block starts with '- ', or ordered if starts with '1. '
- editorEl.addEventListener('keyup', function(e) {
- var selectedText = window.getSelection().anchorNode.textContent,
- selection, selectionNode, 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;
- }
-
- if (command) {
- command.exec();
- selection = window.getSelection();
- selectionNode = selection.anchorNode;
- selectionNode.textContent = selectedText.replace(replaceRegex, '');
- moveCursorToBeginningOfSelection(selection);
- e.stopPropagation();
- }
- }
- });
-
- // 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);
- }
- });
-
- // Experimental: Live update - sync model with textual content as you type
- editorEl.addEventListener('keyup', function(e) {
- if (editor.model && editor.model.length) {
- var index = editor.getCurrentBlockIndex();
- if (editor.model[index].type === 1) {
- editor.syncModelAt(index);
- }
- }
- });
- }
-
- var afterRenderHooks = [];
- Editor.prototype.afterRender = function(callback) {
- if ('function' === typeof callback) {
- afterRenderHooks.push(callback);
- }
- };
-
- function runAfterRenderHooks(element, blockModel) {
- for (var i = 0, len = afterRenderHooks.length; i < len; i++) {
- afterRenderHooks[i].call(null, element, blockModel);
- }
- }
-
- function bindPasteEvents(editor) {
- editor.element.addEventListener('paste', function(e) {
- var data = e.clipboardData, plainText;
- e.preventDefault();
- if(data && data.getData) {
- plainText = data.getData('text/plain');
- var formattedContent = plainTextToBlocks(plainText, editor.defaultFormatter);
- document.execCommand('insertHTML', false, formattedContent);
- }
- });
- }
-
- 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 = '' + blockTag + '>';
- for(i=0; i (http://garthpoitras.com/)
- * @license MIT
- * Last modified: Aug 9, 2014
- */
-
-(function(window, document, define, undefined) {
-
define("content-kit",
- ["./types/type","./models/block","./models/text","./models/image","./models/embed","./compiler","./parsers/html-parser","./renderers/html-renderer","exports"],
- function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __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-factory","exports"],
+ function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __dependency9__, __exports__) {
"use strict";
var Type = __dependency1__["default"];
var BlockModel = __dependency2__["default"];
@@ -1385,6 +22,7 @@ define("content-kit",
var Compiler = __dependency6__["default"];
var HTMLParser = __dependency7__["default"];
var HTMLRenderer = __dependency8__["default"];
+ var EditorFactory = __dependency9__["default"];
/**
* @namespace ContentKit
@@ -1400,11 +38,12 @@ define("content-kit",
ContentKit.Compiler = Compiler;
ContentKit.HTMLParser = HTMLParser;
ContentKit.HTMLRenderer = HTMLRenderer;
+ ContentKit.Editor = EditorFactory;
__exports__["default"] = ContentKit;
});
-define("compiler",
- ["./parsers/html-parser","./renderers/html-renderer","./types/type","./types/default-types","../utils/object-utils","exports"],
+define("content-kit-compiler/compiler",
+ ["./parsers/html-parser","./renderers/html-renderer","./types/type","./types/default-types","../../content-kit-utils/object-utils","exports"],
function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __exports__) {
"use strict";
var HTMLParser = __dependency1__["default"];
@@ -1412,7 +51,7 @@ define("compiler",
var Type = __dependency3__["default"];
var DefaultBlockTypeSet = __dependency4__.DefaultBlockTypeSet;
var DefaultMarkupTypeSet = __dependency4__.DefaultMarkupTypeSet;
- var merge = __dependency5__.merge;
+ var mergeWithOptions = __dependency5__.mergeWithOptions;
/**
* @class Compiler
@@ -1429,7 +68,7 @@ define("compiler",
markupTypes : DefaultMarkupTypeSet,
includeTypeNames : false // true will output type_name: 'TEXT' etc. when parsing for easier debugging
};
- merge(this, defaults, options);
+ mergeWithOptions(this, defaults, options);
// Reference the compiler settings
parser.blockTypes = renderer.blockTypes = this.blockTypes;
@@ -1477,293 +116,849 @@ define("compiler",
__exports__["default"] = Compiler;
});
-define("models/block",
- ["./model","../utils/object-utils","exports"],
- function(__dependency1__, __dependency2__, __exports__) {
+define("content-kit-editor/constants",
+ ["exports"],
+ function(__exports__) {
"use strict";
- var Model = __dependency1__["default"];
- var inherit = __dependency2__.inherit;
+ var Keycodes = {
+ BKSP : 8,
+ ENTER : 13,
+ ESC : 27,
+ DEL : 46
+ };
+
+ var RegEx = {
+ NEWLINE : /[\r\n]/g,
+ HTTP_PROTOCOL : /^https?:\/\//i,
+ HEADING_TAG : /^(H1|H2|H3|H4|H5|H6)$/i,
+ UL_START : /^[-*]\s/,
+ OL_START : /^1\.\s/
+ };
+
+ var SelectionDirection = {
+ LEFT_TO_RIGHT : 1,
+ RIGHT_TO_LEFT : 2,
+ SAME_NODE : 3
+ };
+
+ var ToolbarDirection = {
+ TOP : 1,
+ RIGHT : 2
+ };
+
+ var Tags = {
+ PARAGRAPH : 'P',
+ HEADING : 'H2',
+ SUBHEADING : 'H3',
+ QUOTE : 'BLOCKQUOTE',
+ FIGURE : 'FIGURE',
+ LIST : 'UL',
+ ORDERED_LIST : 'OL',
+ LIST_ITEM : 'LI',
+ LINK : 'A',
+ BOLD : 'B',
+ ITALIC : 'I'
+ };
+
+ var RootTags = [ Tags.PARAGRAPH, Tags.HEADING, Tags.SUBHEADING, Tags.QUOTE, Tags.FIGURE, Tags.LIST, Tags.ORDERED_LIST ];
+
+ __exports__.Keycodes = Keycodes;
+ __exports__.RegEx = RegEx;
+ __exports__.SelectionDirection = SelectionDirection;
+ __exports__.ToolbarDirection = ToolbarDirection;
+ __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__) {
+ "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
+ };
/**
- * 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
+ * Publically expose this class which sets up indiviual `Editor` classes
+ * depending if user passes string selector, Node, or NodeList
*/
- function sortBlockMarkups(markups) {
- return markups.sort(function(a, b) {
- if (a.start === b.start && a.end === b.end) {
- return b.type - a.type;
+ 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];
+ }
+
+ if (elements) {
+ options = merge(defaults, options);
+ elementsLen = elements.length;
+ for (i = 0; i < elementsLen; i++) {
+ editors.push(new Editor(elements[i], options));
}
- return 0;
- });
- }
+ }
- /**
- * @class BlockModel
- * @constructor
- * @extends Model
- */
- function BlockModel(options) {
- options = options || {};
- Model.call(this, options);
- this.value = options.value || '';
- this.markup = sortBlockMarkups(options.markup || []);
+ return editors.length > 1 ? editors : editors[0];
}
- inherit(BlockModel, Model);
- __exports__["default"] = BlockModel;
+ __exports__["default"] = EditorFactory;
});
-define("models/embed",
- ["../utils/object-utils","../models/model","../types/type","exports"],
- function(__dependency1__, __dependency2__, __dependency3__, __exports__) {
+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","exports"],
+ function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __dependency9__, __dependency10__, __dependency11__, __dependency12__, __dependency13__, __exports__) {
"use strict";
- var inherit = __dependency1__.inherit;
- var Model = __dependency2__["default"];
- var Type = __dependency3__["default"];
+ 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 editorClassName = 'ck-editor';
+ var editorClassNameRegExp = new RegExp(editorClassName);
+ var afterRenderHooks = [];
+
+ 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 = '' + blockTag + '>';
+ for(i=0; i>> 0; // cast to Uint32
+ while (i--) {
+ array[i] = obj[i];
}
- if (attributes) {
- this.attributes = attributes;
+ 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__["default"] = Model;
+ __exports__.toArray = toArray;
+ __exports__.sumSparseArray = sumSparseArray;
});
-define("models/text",
- ["./block","../types/type","../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 BlockModel = __dependency1__["default"];
- var Type = __dependency2__["default"];
- var inherit = __dependency3__.inherit;
+ var sanitizeWhitespace = __dependency1__.sanitizeWhitespace;
+ var toArray = __dependency2__.toArray;
/**
- * @class TextModel
- * @constructor
- * @extends BlockModel
- * A simple BlockModel subclass representing a paragraph of text
+ * A document instance separate from the page's document. (if browser supports it)
+ * Prevents images, scripts, and styles from executing while parsing nodes.
*/
- function TextModel(options) {
- options = options || {};
- options.type = Type.TEXT.id;
- options.type_name = Type.TEXT.name;
- BlockModel.call(this, options);
+ 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);
}
- inherit(TextModel, BlockModel);
- __exports__["default"] = TextModel;
- });
-define("parsers/html-parser",
- ["../models/block","../models/markup","../types/default-types","../utils/object-utils","../utils/array-utils","../utils/string-utils","../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 merge = __dependency4__.merge;
- 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;
+ /**
+ * A reusable DOM Node for parsing html content.
+ */
+ var DOMParsingNode = createElement('div');
/**
- * Gets the last block in the set or creates and return a default block if none exist yet.
+ * Returns plain-text of a `Node`
*/
- 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;
+ function textOfNode(node) {
+ var text = node.textContent || node.innerText;
+ return text ? sanitizeWhitespace(text) : '';
}
/**
- * Helper to retain stray elements at the root of the html that aren't blocks
+ * Replaces a `Node` with its children
*/
- function handleNonBlockElementAtRoot(parser, elementNode, blocks) {
- var block = getLastBlockOrCreate(parser, blocks),
- markup = parser.parseElementMarkup(elementNode, block.value.length);
- if (markup) {
- block.markup.push(markup);
+ 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);
}
- block.value += textOfNode(elementNode);
}
/**
- * @class HTMLParser
- * @constructor
+ * Extracts attributes of a `Node` to a hash of key/value pairs
*/
- function HTMLParser(options) {
- var defaults = {
- blockTypes : DefaultBlockTypeSet,
- markupTypes : DefaultMarkupTypeSet,
- includeTypeNames : false
- };
- merge(this, defaults, options);
+ 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";
/**
- * @method parse
- * @param html String of HTML content
- * @return Array Parsed JSON content array
+ * Merges defaults/options into an Object
+ * Useful for constructors
*/
- HTMLParser.prototype.parse = function(html) {
- DOMParsingNode.innerHTML = sanitizeWhitespace(html);
+ 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;
+ }
- var children = toArray(DOMParsingNode.childNodes),
- len = children.length,
- blocks = [],
- i, currentNode, block, text;
+ /**
+ * Merges properties of one object into another
+ */
+ function merge(original, updates) {
+ return mergeWithOptions(original, updates);
+ }
- 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);
+ /**
+ * 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 if (currentNode.nodeType === 3) {
- text = currentNode.nodeValue;
- if (trim(text)) {
- block = getLastBlockOrCreate(this, blocks);
- block.value += text;
+ });
+ } else {
+ for (var key in Superclass) {
+ if (Superclass.hasOwnProperty(key)) {
+ Subclass[key] = Superclass[key];
}
}
+ Subclass.prototype = new Superclass();
+ Subclass.constructor = Subclass;
}
+ }
- return blocks;
- };
+ __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;
/**
- * @method parseBlock
- * @param node DOM node to parse
- * @return {BlockModel} parsed block model
- * Parses a single block type node into a model
+ * String.prototype.trim polyfill
+ * Removes whitespace at beginning and end of string
*/
- HTMLParser.prototype.parseBlock = function(node) {
- var type = this.blockTypes.findByNode(node);
+ 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 ? (string + '').replace(/ /g, '_') : '';
+ }
+
+ /**
+ * 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("content-kit-compiler/models/block",
+ ["./model","../../content-kit-utils/object-utils","exports"],
+ function(__dependency1__, __dependency2__, __exports__) {
+ "use strict";
+ var Model = __dependency1__["default"];
+ var inherit = __dependency2__.inherit;
+
+ /**
+ * 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;
+ });
+ }
+
+ /**
+ * @class BlockModel
+ * @constructor
+ * @extends Model
+ */
+ function BlockModel(options) {
+ options = options || {};
+ Model.call(this, options);
+ this.value = options.value || '';
+ this.markup = sortBlockMarkups(options.markup || []);
+ }
+ inherit(BlockModel, Model);
+
+ __exports__["default"] = BlockModel;
+ });
+define("content-kit-compiler/models/embed",
+ ["../../content-kit-utils/object-utils","../models/model","../types/type","exports"],
+ function(__dependency1__, __dependency2__, __dependency3__, __exports__) {
+ "use strict";
+ var inherit = __dependency1__.inherit;
+ var Model = __dependency2__["default"];
+ var Type = __dependency3__["default"];
+
+ /**
+ * @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: {}
+ });
+
+ 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;
+ }
+
+ if (embedHtml && embedType === 'rich') {
+ attributes.html = embedHtml;
+ }
+ }
+ inherit(Model, EmbedModel);
+
+ __exports__["default"] = EmbedModel;
+ });
+define("content-kit-compiler/models/image",
+ ["./block","../types/type","../../content-kit-utils/object-utils","exports"],
+ function(__dependency1__, __dependency2__, __dependency3__, __exports__) {
+ "use strict";
+ var BlockModel = __dependency1__["default"];
+ var Type = __dependency2__["default"];
+ var inherit = __dependency3__.inherit;
+
+ /**
+ * @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 };
+ }
+ BlockModel.call(this, options);
+ }
+ inherit(ImageModel, BlockModel);
+
+ __exports__["default"] = ImageModel;
+ });
+define("content-kit-compiler/models/markup",
+ ["./model","../../content-kit-utils/object-utils","exports"],
+ function(__dependency1__, __dependency2__, __exports__) {
+ "use strict";
+ var Model = __dependency1__["default"];
+ var inherit = __dependency2__.inherit;
+
+ /**
+ * @class MarkupModel
+ * @constructor
+ * @extends Model
+ */
+ function MarkupModel(options) {
+ options = options || {};
+ Model.call(this, options);
+ this.start = options.start || 0;
+ this.end = options.end || 0;
+ }
+ inherit(MarkupModel, Model);
+
+ __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.type = options.type || null;
+ if (type_name) {
+ this.type_name = type_name;
+ }
+ if (attributes) {
+ this.attributes = attributes;
+ }
+ }
+
+ __exports__["default"] = Model;
+ });
+define("content-kit-compiler/models/text",
+ ["./block","../types/type","../../content-kit-utils/object-utils","exports"],
+ function(__dependency1__, __dependency2__, __dependency3__, __exports__) {
+ "use strict";
+ var BlockModel = __dependency1__["default"];
+ var Type = __dependency2__["default"];
+ var inherit = __dependency3__.inherit;
+
+ /**
+ * @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(TextModel, BlockModel);
+
+ __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,
@@ -1775,632 +970,1716 @@ define("parsers/html-parser",
}
};
- /**
- * @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;
+ /**
+ * @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 createOpeningTag(tagName, attributes, selfClosing /*,blacklist*/) {
+ var tag = '<' + tagName;
+ for (var attr in attributes) {
+ if (attributes.hasOwnProperty(attr)) {
+ //if (blacklist && attr in blacklist) { continue; }
+ tag += ' ' + attr + '="' + attributes[attr] + '"';
+ }
+ }
+ if (selfClosing) { tag += '/'; }
+ tag += '>';
+ return tag;
+ }
+
+ /**
+ * Builds a closing html tag. i.e. '
'
+ */
+ function createCloseTag(tagName) {
+ return '' + tagName + '>';
+ }
+
+ /**
+ * @class HTMLElementRenderer
+ * @constructor
+ */
+ function HTMLElementRenderer(options) {
+ options = options || {};
+ this.type = options.type;
+ this.markupTypes = options.markupTypes;
+ }
+
+ /**
+ * @method render
+ * @param model a block model
+ * @return String html
+ * Renders a block model into a HTML string.
+ */
+ HTMLElementRenderer.prototype.render = function(model) {
+ var html = '';
+ var type = this.type;
+ var tagName = type.tag;
+ var selfClosing = type.selfClosing;
+
+ if (tagName) {
+ html += createOpeningTag(tagName, model.attributes, selfClosing);
+ }
+ if (!selfClosing) {
+ html += this.renderMarkup(model.value, model.markup);
+ if (tagName) {
+ html += createCloseTag(tagName);
+ }
+ }
+ return html;
+ };
+
+ /**
+ * @method renderMarkup
+ * @param text plain text to apply markup to
+ * @param markup an array of markup models
+ * @return String html
+ * Renders a markup model into a HTML string.
+ */
+ HTMLElementRenderer.prototype.renderMarkup = function(text, markups) {
+ var parsedTagsIndexes = [],
+ len = markups && markups.length, i;
+
+ for (i = 0; i < len; i++) {
+ var markup = markups[i],
+ markupMeta = this.markupTypes.findById(markup.type),
+ tagName = markupMeta.tag,
+ selfClosing = markupMeta.selfClosing,
+ start = markup.start,
+ end = markup.end,
+ openTag = createOpeningTag(tagName, markup.attributes, selfClosing),
+ parsedTagLengthAtIndex = parsedTagsIndexes[start] || 0,
+ parsedTagLengthBeforeIndex = sumSparseArray(parsedTagsIndexes.slice(0, start + 1));
+
+ text = injectIntoString(text, openTag, start + parsedTagLengthBeforeIndex);
+ parsedTagsIndexes[start] = parsedTagLengthAtIndex + openTag.length;
+
+ if (!selfClosing) {
+ var closeTag = createCloseTag(tagName);
+ parsedTagLengthAtIndex = parsedTagsIndexes[end] || 0;
+ parsedTagLengthBeforeIndex = sumSparseArray(parsedTagsIndexes.slice(0, end));
+ text = injectIntoString(text, closeTag, end + parsedTagLengthBeforeIndex);
+ parsedTagsIndexes[end] = parsedTagLengthAtIndex + closeTag.length;
+ }
+ }
+
+ return text;
+ };
+
+ __exports__["default"] = HTMLElementRenderer;
+ });
+define("content-kit-compiler/renderers/html-embed-renderer",
+ ["./embeds/youtube","./embeds/twitter","./embeds/instagram","exports"],
+ function(__dependency1__, __dependency2__, __dependency3__, __exports__) {
+ "use strict";
+ var YouTubeRenderer = __dependency1__["default"];
+ var TwitterRenderer = __dependency2__["default"];
+ var InstagramRenderer = __dependency3__["default"];
+
+ /**
+ * A dictionary of supported embed services
+ */
+ var services = {
+ YOUTUBE : {
+ id: 1,
+ renderer: new YouTubeRenderer()
+ },
+ TWITTER : {
+ id: 2,
+ renderer: new TwitterRenderer()
+ },
+ INSTAGRAM : {
+ id: 3,
+ renderer: new InstagramRenderer()
+ }
+ };
+
+ /**
+ * @class EmbedRenderer
+ * @constructor
+ */
+ function EmbedRenderer() {}
+
+ /**
+ * @method render
+ * @param model
+ * @return String html
+ */
+ EmbedRenderer.prototype.render = function(model) {
+ var renderer = this.rendererFor(model);
+ if (renderer) {
+ return renderer.render(model);
+ }
+ var attrs = model.attributes;
+ return attrs && attrs.html || '';
+ };
+
+ /**
+ * @method rendererFor
+ * @param model
+ * @return service renderer
+ */
+ EmbedRenderer.prototype.rendererFor = function(model) {
+ var provider = model.attributes.provider_name;
+ var providerKey = provider && provider.toUpperCase();
+ var service = services[providerKey];
+ return service && service.renderer;
+ };
+
+ __exports__["default"] = EmbedRenderer;
+ });
+define("content-kit-compiler/renderers/html-renderer",
+ ["../types/type","./html-element-renderer","./html-embed-renderer","../types/default-types","../../content-kit-utils/object-utils","exports"],
+ function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __exports__) {
+ "use strict";
+ var Type = __dependency1__["default"];
+ var HTMLElementRenderer = __dependency2__["default"];
+ var HTMLEmbedRenderer = __dependency3__["default"];
+ var DefaultBlockTypeSet = __dependency4__.DefaultBlockTypeSet;
+ var DefaultMarkupTypeSet = __dependency4__.DefaultMarkupTypeSet;
+ var mergeWithOptions = __dependency5__.mergeWithOptions;
+
+ /**
+ * @class HTMLRenderer
+ * @constructor
+ */
+ function HTMLRenderer(options) {
+ var defaults = {
+ blockTypes : DefaultBlockTypeSet,
+ markupTypes : DefaultMarkupTypeSet
+ };
+ mergeWithOptions(this, defaults, options);
+ }
+
+ /**
+ * @method willRenderType
+ * @param type {Number|Type}
+ * @param renderer the rendering function that returns a string of html
+ * Registers custom rendering hooks for a type
+ */
+ var renderHooks = {};
+ HTMLRenderer.prototype.willRenderType = function(type, renderer) {
+ if ('number' !== typeof type) {
+ type = type.id;
+ }
+ renderHooks[type] = renderer;
+ };
+
+ /**
+ * @method rendererFor
+ * @param model
+ * @returns renderer
+ * Returns an instance of a renderer for supplied model
+ */
+ HTMLRenderer.prototype.rendererFor = function(model) {
+ var type = this.blockTypes.findById(model.type);
+ if (type === Type.EMBED) {
+ return new HTMLEmbedRenderer();
+ }
+ return new HTMLElementRenderer({ type: type, markupTypes: this.markupTypes });
+ };
+
+ /**
+ * @method render
+ * @param model
+ * @return String html
+ */
+ HTMLRenderer.prototype.render = function(model) {
+ var html = '';
+ var len = model && model.length;
+ var i, item, renderer, renderHook, itemHtml;
+
+ for (i = 0; i < len; i++) {
+ item = model[i];
+ renderer = this.rendererFor(item);
+ renderHook = renderHooks[item.type];
+ itemHtml = renderHook ? renderHook.call(renderer, item) : renderer.render(item);
+ if (itemHtml) { html += itemHtml; }
+ }
+ return html;
+ };
+
+ __exports__["default"] = HTMLRenderer;
+ });
+define("content-kit-compiler/types/default-types",
+ ["./type-set","./type","exports"],
+ function(__dependency1__, __dependency2__, __exports__) {
+ "use strict";
+ var TypeSet = __dependency1__["default"];
+ var Type = __dependency2__["default"];
+
+ /**
+ * Default supported block types
+ */
+ var DefaultBlockTypeSet = new TypeSet([
+ new Type({ tag: 'p', name: 'text' }),
+ new Type({ tag: 'h2', name: 'heading' }),
+ new Type({ tag: 'h3', name: 'subheading' }),
+ new Type({ tag: 'img', name: 'image' }),
+ new Type({ tag: 'blockquote', name: 'quote' }),
+ new Type({ tag: 'ul', name: 'list' }),
+ new Type({ tag: 'ol', name: 'ordered list' }),
+ new Type({ name: 'embed' })
+ ]);
+
+ /**
+ * Default supported markup types
+ */
+ var DefaultMarkupTypeSet = new TypeSet([
+ new Type({ tag: 'b', name: 'bold' }),
+ new Type({ tag: 'i', name: 'italic' }),
+ new Type({ tag: 'u', name: 'underline' }),
+ new Type({ tag: 'a', name: 'link' }),
+ new Type({ tag: 'br', name: 'break' }),
+ new Type({ tag: 'li', name: 'list item' }),
+ new Type({ tag: 'sub', name: 'subscript' }),
+ new Type({ tag: 'sup', name: 'superscript' })
+ ]);
+
+ __exports__.DefaultBlockTypeSet = DefaultBlockTypeSet;
+ __exports__.DefaultMarkupTypeSet = DefaultMarkupTypeSet;
+ });
+define("content-kit-compiler/types/type-set",
+ ["exports"],
+ function(__exports__) {
+ "use strict";
+ /**
+ * @class TypeSet
+ * @private
+ * @constructor
+ * A Set of Types
+ */
+ function TypeSet(types) {
+ var len = types && types.length, i;
+
+ this._autoId = 1; // Auto-increment id counter
+ this.idLookup = {}; // Hash cache for finding by id
+ this.tagLookup = {}; // Hash cache for finding by tag
+
+ for (i = 0; i < len; i++) {
+ this.addType(types[i]);
+ }
+ }
+
+ TypeSet.prototype = {
+ /**
+ * Adds a type to the set
+ */
+ addType: function(type) {
+ this[type.name] = type;
+ if (type.id === undefined) {
+ type.id = this._autoId++;
+ }
+ this.idLookup[type.id] = type;
+ if (type.tag) {
+ this.tagLookup[type.tag] = type;
+ }
+ return type;
+ },
+
+ /**
+ * Returns type info for a given Node
+ */
+ findByNode: function(node) {
+ return this.findByTag(node.tagName);
+ },
+ /**
+ * Returns type info for a given tag
+ */
+ findByTag: function(tag) {
+ return this.tagLookup[tag.toLowerCase()];
+ },
+ /**
+ * Returns type info for a given id
+ */
+ findById: function(id) {
+ return this.idLookup[id];
+ }
+ };
+
+ __exports__["default"] = TypeSet;
+ });
+define("content-kit-compiler/types/type",
+ ["../../content-kit-utils/string-utils","exports"],
+ function(__dependency1__, __exports__) {
+ "use strict";
+ var underscore = __dependency1__.underscore;
+
+ /**
+ * @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;
+ }
+ if (options.tag) {
+ this.tag = options.tag.toLowerCase();
+ this.selfClosing = /^(br|img|hr|meta|link|embed)$/i.test(this.tag);
+ }
+
+ // Register the type as constant
+ Type[this.name] = this;
+ }
+ }
+
+ __exports__["default"] = Type;
+ });
+define("content-kit-editor/commands/base",
+ ["exports"],
+ function(__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; }
+ }
+
+ Command.prototype.exec = function(){};
+
+ __exports__["default"] = Command;
+ });
+define("content-kit-editor/commands/bold",
+ ["./text-format","../../content-kit-utils/object-utils","../constants","../utils/selection-utils","exports"],
+ function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __exports__) {
+ "use strict";
+ var TextFormatCommand = __dependency1__["default"];
+ var inherit = __dependency2__.inherit;
+ var Tags = __dependency3__.Tags;
+ var RegEx = __dependency3__.RegEx;
+ var getSelectionBlockTagName = __dependency4__.getSelectionBlockTagName;
+
+ function BoldCommand() {
+ TextFormatCommand.call(this, {
+ name: 'bold',
+ tag: Tags.BOLD,
+ button: ''
+ });
+ }
+ 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);
+ }
+ };
+
+ __exports__["default"] = BoldCommand;
+ });
+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__) {
+ "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"];
+
+ function createCommandIndex(commands) {
+ var index = {};
+ var len = commands.length, i, command;
+ for(i = 0; i < len; i++) {
+ command = commands[i];
+ index[command.name] = command;
+ }
+ return index;
+ }
+
+ 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;
+ });
+define("content-kit-editor/commands/embed",
+ ["./base","../views/prompt","../views/message","../../content-kit-compiler/models/embed","../../content-kit-utils/object-utils","../utils/http-utils","../constants","exports"],
+ function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __exports__) {
+ "use strict";
+ var Command = __dependency1__["default"];
+ var Prompt = __dependency2__["default"];
+ var Message = __dependency3__["default"];
+ var EmbedModel = __dependency4__["default"];
+ var inherit = __dependency5__.inherit;
+ var xhrGet = __dependency6__.xhrGet;
+ var RegEx = __dependency7__.RegEx;
+
+ function EmbedCommand(options) {
+ Command.call(this, {
+ name: 'embed',
+ button: '',
+ prompt: new Prompt({
+ command: this,
+ placeholder: 'Paste a YouTube or Twitter url...'
+ })
+ });
+ }
+ inherit(EmbedCommand, Command);
- // Clone the node since it will be recursively torn down
- node = node.cloneNode(true);
+ EmbedCommand.prototype.exec = function(url) {
+ var command = this;
+ var editorContext = command.editorContext;
+ var index = editorContext.getCurrentBlockIndex();
+ var oEmbedEndpoint = 'http://noembed.com/embed?url=';
+
+ command.embedIntent.showLoading();
+ if (!RegEx.HTTP_PROTOCOL.test(url)) {
+ url = 'http://' + url;
+ }
- 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);
+ xhrGet(oEmbedEndpoint + url, function(responseText, error) {
+ command.embedIntent.hideLoading();
+ if (error) {
+ new Message().show('Embed error: status code ' + error.currentTarget.status);
+ } else {
+ var json = JSON.parse(responseText);
+ if (json.error) {
+ new Message().show('Embed error: ' + json.error);
+ } else {
+ var embedModel = new EmbedModel(json);
+ //if (!embedModel.attributes.provider_id) {
+ // new Message().show('Embed error: "' + embedModel.attributes.provider_name + '" embeds are not supported at this time');
+ //} else {
+ editorContext.insertBlockAt(embedModel, index);
+ editorContext.syncVisualAt(index);
+ //}
}
- } 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++;
- }
+ __exports__["default"] = EmbedCommand;
+ });
+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__) {
+ "use strict";
+ var TextFormatCommand = __dependency1__["default"];
+ var Tags = __dependency2__.Tags;
+ var inherit = __dependency3__.inherit;
+ var getSelectionBlockElement = __dependency4__.getSelectionBlockElement;
+ var selectNode = __dependency4__.selectNode;
- return markups;
+ function FormatBlockCommand(options) {
+ options.action = 'formatBlock';
+ TextFormatCommand.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
+ if(tag === blockElement.tagName) {
+ value = Tags.PARAGRAPH;
+ } else {
+ // Flattens the selection before applying the block format.
+ // Otherwise, undesirable nested blocks can occur.
+ var flatNode = document.createTextNode(blockElement.textContent);
+ blockElement.parentNode.insertBefore(flatNode, blockElement);
+ blockElement.parentNode.removeChild(blockElement);
+ selectNode(flatNode);
+ }
+
+ FormatBlockCommand._super.prototype.exec.call(this, value);
};
- /**
- * @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;
+ __exports__["default"] = FormatBlockCommand;
+ });
+define("content-kit-editor/commands/heading",
+ ["./format-block","../constants","../../content-kit-utils/object-utils","exports"],
+ function(__dependency1__, __dependency2__, __dependency3__, __exports__) {
+ "use strict";
+ var FormatBlockCommand = __dependency1__["default"];
+ var Tags = __dependency2__.Tags;
+ var inherit = __dependency3__.inherit;
- if (type) {
- selfClosing = type.selfClosing;
- if (!selfClosing && !node.hasChildNodes()) { return; } // check for empty nodes
+ function HeadingCommand() {
+ FormatBlockCommand.call(this, {
+ name: 'heading',
+ tag: Tags.HEADING,
+ button: '1'
+ });
+ }
+ inherit(HeadingCommand, FormatBlockCommand);
- 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"] = HeadingCommand;
+ });
+define("content-kit-editor/commands/image",
+ ["./base","../views/message","../../content-kit-compiler/models/image","../../content-kit-utils/object-utils","exports"],
+ function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __exports__) {
+ "use strict";
+ var Command = __dependency1__["default"];
+ var Message = __dependency2__["default"];
+ var ImageModel = __dependency3__["default"];
+ var inherit = __dependency4__.inherit;
+
+ function ImageCommand(options) {
+ Command.call(this, {
+ name: 'image',
+ button: ''
+ });
+ if (window.XHRFileUploader) {
+ this.uploader = new window.XHRFileUploader({ url: '/upload', maxFileSize: 5000000 });
+ }
+ }
+ inherit(ImageCommand, Command);
+
+ ImageCommand.prototype = {
+ exec: function() {
+ ImageCommand._super.prototype.exec.call(this);
+ var clickEvent = new MouseEvent('click', { bubbles: false });
+ if (!this.fileInput) {
+ var command = this;
+ var fileInput = this.fileInput = document.createElement('input');
+ fileInput.type = 'file';
+ fileInput.accept = 'image/*';
+ fileInput.className = 'ck-file-input';
+ fileInput.addEventListener('change', function(e) {
+ command.handleFile(e);
});
+ document.body.appendChild(fileInput);
}
+ this.fileInput.dispatchEvent(clickEvent);
+ },
+ handleFile: function(e) {
+ var fileInput = e.target;
+ var editor = this.editorContext;
+ var embedIntent = this.embedIntent;
+
+ embedIntent.showLoading();
+ this.uploader.upload({
+ fileInput: fileInput,
+ complete: function(response, error) {
+ embedIntent.hideLoading();
+ if (error || !response || !response.url) {
+ return new Message().show(error.message || 'Error uploading image');
+ }
+ var imageModel = new ImageModel({ src: response.url });
+ var index = editor.getCurrentBlockIndex();
+ editor.insertBlockAt(imageModel, index);
+ editor.syncVisualAt(index);
+ }
+ });
+ fileInput.value = null; // reset file input
+ // TODO: client-side render while uploading
}
};
- __exports__["default"] = HTMLParser;
+ __exports__["default"] = ImageCommand;
+ });
+define("content-kit-editor/commands/italic",
+ ["./text-format","../constants","../../content-kit-utils/object-utils","exports"],
+ function(__dependency1__, __dependency2__, __dependency3__, __exports__) {
+ "use strict";
+ var TextFormatCommand = __dependency1__["default"];
+ var Tags = __dependency2__.Tags;
+ var inherit = __dependency3__.inherit;
+
+ function ItalicCommand() {
+ TextFormatCommand.call(this, {
+ name: 'italic',
+ tag: Tags.ITALIC,
+ button: ''
+ });
+ }
+ inherit(ItalicCommand, TextFormatCommand);
+
+ __exports__["default"] = ItalicCommand;
+ });
+define("content-kit-editor/commands/link",
+ ["./text-format","../views/prompt","../constants","../../content-kit-utils/object-utils","../utils/selection-utils","exports"],
+ function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __exports__) {
+ "use strict";
+ var TextFormatCommand = __dependency1__["default"];
+ var Prompt = __dependency2__["default"];
+ var Tags = __dependency3__.Tags;
+ var RegEx = __dependency3__.RegEx;
+ var inherit = __dependency4__.inherit;
+ var getSelectionTagName = __dependency5__.getSelectionTagName;
+
+ function LinkCommand() {
+ TextFormatCommand.call(this, {
+ name: 'link',
+ tag: Tags.LINK,
+ action: 'createLink',
+ removeAction: 'unlink',
+ button: '',
+ prompt: new Prompt({
+ command: this,
+ placeholder: 'Enter a url, press return...'
+ })
+ });
+ }
+ inherit(LinkCommand, TextFormatCommand);
+
+ LinkCommand.prototype.exec = function(url) {
+ if(this.tag === getSelectionTagName()) {
+ this.unexec();
+ } else {
+ if (!RegEx.HTTP_PROTOCOL.test(url)) {
+ url = 'http://' + url;
+ }
+ LinkCommand._super.prototype.exec.call(this, url);
+ }
+ };
+
+ __exports__["default"] = LinkCommand;
+ });
+define("content-kit-editor/commands/list",
+ ["./text-format","../../content-kit-utils/object-utils","../utils/selection-utils","exports"],
+ function(__dependency1__, __dependency2__, __dependency3__, __exports__) {
+ "use strict";
+ var TextFormatCommand = __dependency1__["default"];
+ var inherit = __dependency2__.inherit;
+ var getSelectionBlockElement = __dependency3__.getSelectionBlockElement;
+ var selectNode = __dependency3__.selectNode;
+
+ function ListCommand(options) {
+ TextFormatCommand.call(this, options);
+ }
+ inherit(ListCommand, TextFormatCommand);
+
+ ListCommand.prototype.exec = function() {
+ ListCommand._super.prototype.exec.call(this);
+
+ // After creation, lists need to be unwrapped from the default formatter P tag
+ var listElement = getSelectionBlockElement();
+ var wrapperNode = listElement.parentNode;
+ if (wrapperNode.firstChild === listElement) {
+ var editorNode = wrapperNode.parentNode;
+ editorNode.insertBefore(listElement, wrapperNode);
+ editorNode.removeChild(wrapperNode);
+ selectNode(listElement);
+ }
+ };
+
+ __exports__["default"] = ListCommand;
+ });
+define("content-kit-editor/commands/ordered-list",
+ ["./list","../constants","../../content-kit-utils/object-utils","exports"],
+ function(__dependency1__, __dependency2__, __dependency3__, __exports__) {
+ "use strict";
+ var ListCommand = __dependency1__["default"];
+ var Tags = __dependency2__.Tags;
+ var inherit = __dependency3__.inherit;
+
+ function OrderedListCommand() {
+ ListCommand.call(this, {
+ name: 'ordered list',
+ tag: Tags.ORDERED_LIST,
+ action: 'insertOrderedList'
+ });
+ }
+ inherit(OrderedListCommand, ListCommand);
+
+ __exports__["default"] = OrderedListCommand;
+ });
+define("content-kit-editor/commands/quote",
+ ["./format-block","../constants","../../content-kit-utils/object-utils","exports"],
+ function(__dependency1__, __dependency2__, __dependency3__, __exports__) {
+ "use strict";
+ var FormatBlockCommand = __dependency1__["default"];
+ var Tags = __dependency2__.Tags;
+ var inherit = __dependency3__.inherit;
+
+ function QuoteCommand() {
+ FormatBlockCommand.call(this, {
+ name: 'quote',
+ tag: Tags.QUOTE,
+ button: ''
+ });
+ }
+ inherit(QuoteCommand, FormatBlockCommand);
+
+ __exports__["default"] = QuoteCommand;
+ });
+define("content-kit-editor/commands/subheading",
+ ["./format-block","../constants","../../content-kit-utils/object-utils","exports"],
+ function(__dependency1__, __dependency2__, __dependency3__, __exports__) {
+ "use strict";
+ var FormatBlockCommand = __dependency1__["default"];
+ var Tags = __dependency2__.Tags;
+ var inherit = __dependency3__.inherit;
+
+ function SubheadingCommand() {
+ FormatBlockCommand.call(this, {
+ name: 'subheading',
+ tag: Tags.SUBHEADING,
+ button: '2'
+ });
+ }
+ inherit(SubheadingCommand, FormatBlockCommand);
+
+ __exports__["default"] = SubheadingCommand;
});
-define("renderers/html-element-renderer",
- ["../utils/string-utils","../utils/array-utils","exports"],
+define("content-kit-editor/commands/text-format",
+ ["./base","../../content-kit-utils/object-utils","exports"],
function(__dependency1__, __dependency2__, __exports__) {
"use strict";
- var injectIntoString = __dependency1__.injectIntoString;
- var sumSparseArray = __dependency2__.sumSparseArray;
+ var Command = __dependency1__["default"];
+ var inherit = __dependency2__.inherit;
- /**
- * Builds an opening html tag. i.e. ''
- */
- function createOpeningTag(tagName, attributes, selfClosing /*,blacklist*/) {
- var tag = '<' + tagName;
- for (var attr in attributes) {
- if (attributes.hasOwnProperty(attr)) {
- //if (blacklist && attr in blacklist) { continue; }
- tag += ' ' + attr + '="' + attributes[attr] + '"';
+ function TextFormatCommand(options) {
+ Command.call(this, options);
+ this.tag = options.tag.toUpperCase();
+ this.action = options.action || this.name;
+ this.removeAction = options.removeAction || this.action;
+ }
+ inherit(TextFormatCommand, Command);
+
+ TextFormatCommand.prototype = {
+ exec: function(value) {
+ document.execCommand(this.action, false, value || null);
+ },
+ unexec: function(value) {
+ document.execCommand(this.removeAction, false, value || null);
+ }
+ };
+
+ __exports__["default"] = TextFormatCommand;
+ });
+define("content-kit-editor/commands/unordered-list",
+ ["./list","../constants","../../content-kit-utils/object-utils","exports"],
+ function(__dependency1__, __dependency2__, __dependency3__, __exports__) {
+ "use strict";
+ var ListCommand = __dependency1__["default"];
+ var Tags = __dependency2__.Tags;
+ var inherit = __dependency3__.inherit;
+
+ function UnorderedListCommand() {
+ ListCommand.call(this, {
+ name: 'list',
+ tag: Tags.LIST,
+ action: 'insertUnorderedList'
+ });
+ }
+ inherit(UnorderedListCommand, ListCommand);
+
+ __exports__["default"] = UnorderedListCommand;
+ });
+define("content-kit-editor/utils/element-utils",
+ ["exports"],
+ function(__exports__) {
+ "use strict";
+ function createDiv(className) {
+ var div = document.createElement('div');
+ if (className) {
+ div.className = className;
+ }
+ return div;
+ }
+
+ function hideElement(element) {
+ element.style.display = 'none';
+ }
+
+ function showElement(element) {
+ element.style.display = 'block';
+ }
+
+ function swapElements(elementToShow, elementToHide) {
+ hideElement(elementToHide);
+ showElement(elementToShow);
+ }
+
+ 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;
+ }
+ }
+
+ function nodeIsDescendantOfElement(node, element) {
+ var parentNode = node.parentNode;
+ while(parentNode) {
+ if (parentNode === element) {
+ return true;
+ }
+ parentNode = parentNode.parentNode;
+ }
+ return false;
+ }
+
+ function getElementRelativeOffset(element) {
+ var offset = { left: 0, top: -window.pageYOffset };
+ var offsetParent = element.offsetParent;
+ var offsetParentPosition = window.getComputedStyle(offsetParent).position;
+ var offsetParentRect;
+
+ if (offsetParentPosition === 'relative') {
+ offsetParentRect = offsetParent.getBoundingClientRect();
+ offset.left = offsetParentRect.left;
+ offset.top = offsetParentRect.top;
+ }
+ return offset;
+ }
+
+ function getElementComputedStyleNumericProp(element, prop) {
+ return parseFloat(window.getComputedStyle(element)[prop]);
+ }
+
+ function positionElementToRect(element, rect, topOffset, leftOffset) {
+ var relativeOffset = getElementRelativeOffset(element);
+ var style = element.style;
+ var round = Math.round;
+
+ topOffset = topOffset || 0;
+ leftOffset = leftOffset || 0;
+ style.left = round(rect.left - relativeOffset.left - leftOffset) + 'px';
+ style.top = round(rect.top - relativeOffset.top - topOffset) + 'px';
+ }
+
+ function positionElementHorizontallyCenteredToRect(element, rect, topOffset) {
+ var horizontalCenter = (element.offsetWidth / 2) - (rect.width / 2);
+ positionElementToRect(element, rect, topOffset, horizontalCenter);
+ }
+
+ function positionElementCenteredAbove(element, aboveElement) {
+ var elementMargin = getElementComputedStyleNumericProp(element, 'marginBottom');
+ positionElementHorizontallyCenteredToRect(element, aboveElement.getBoundingClientRect(), element.offsetHeight + elementMargin);
+ }
+
+ function positionElementCenteredBelow(element, belowElement) {
+ var elementMargin = getElementComputedStyleNumericProp(element, 'marginTop');
+ positionElementHorizontallyCenteredToRect(element, belowElement.getBoundingClientRect(), -element.offsetHeight - elementMargin);
+ }
+
+ function positionElementCenteredIn(element, inElement) {
+ var verticalCenter = (inElement.offsetHeight / 2) - (element.offsetHeight / 2);
+ positionElementHorizontallyCenteredToRect(element, inElement.getBoundingClientRect(), -verticalCenter);
+ }
+
+ 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);
+ }
+
+ 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);
+ }
+
+ __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/http-utils",
+ ["exports"],
+ function(__exports__) {
+ "use strict";
+ function xhrGet(url, callback) {
+ var request = new XMLHttpRequest();
+ request.onload = function() {
+ callback(this.responseText);
+ };
+ request.onerror = function(error) {
+ callback(null, error);
+ };
+ request.open('GET', url, true);
+ request.send();
+ }
+
+ __exports__.xhrGet = xhrGet;
+ });
+define("content-kit-editor/utils/selection-utils",
+ ["../constants","./element-utils","exports"],
+ function(__dependency1__, __dependency2__, __exports__) {
+ "use strict";
+ var SelectionDirection = __dependency1__.SelectionDirection;
+ var RootTags = __dependency1__.RootTags;
+ var nodeIsDescendantOfElement = __dependency2__.nodeIsDescendantOfElement;
+
+ 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;
+ }
+
+ 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);
+ }
+
+ 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;
+ }
+
+ function getSelectionTagName() {
+ var element = getSelectionElement();
+ return element ? element.tagName : null;
+ }
+
+ function getSelectionBlockTagName() {
+ var element = getSelectionBlockElement();
+ return element ? element.tagName : null;
+ }
+
+ 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;
}
}
- if (selfClosing) { tag += '/'; }
- tag += '>';
- return tag;
+ return tags;
}
- /**
- * Builds a closing html tag. i.e. ''
- */
- function createCloseTag(tagName) {
- return '' + tagName + '>';
+ function selectionIsInElement(selection, element) {
+ var node = selection.anchorNode;
+ return node && nodeIsDescendantOfElement(node, element);
}
- /**
- * @class HTMLElementRenderer
- * @constructor
- */
- function HTMLElementRenderer(options) {
- options = options || {};
- this.type = options.type;
- this.markupTypes = options.markupTypes;
+ function selectionIsEditable(selection) {
+ var el = getSelectionBlockElement(selection);
+ return el.contentEditable !== 'false';
}
- /**
- * @method render
- * @param model a block model
- * @return String html
- * Renders a block model into a HTML string.
- */
- HTMLElementRenderer.prototype.render = function(model) {
- var html = '';
- var type = this.type;
- var tagName = type.tag;
- var selfClosing = type.selfClosing;
-
- if (tagName) {
- html += createOpeningTag(tagName, model.attributes, selfClosing);
- }
- if (!selfClosing) {
- html += this.renderMarkup(model.value, model.markup);
- if (tagName) {
- html += createCloseTag(tagName);
+ /*
+ 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));
}
}
- return html;
- };
-
- /**
- * @method renderMarkup
- * @param text plain text to apply markup to
- * @param markup an array of markup models
- * @return String html
- * Renders a markup model into a HTML string.
- */
- HTMLElementRenderer.prototype.renderMarkup = function(text, markups) {
- var parsedTagsIndexes = [],
- len = markups && markups.length, i;
+ return ranges;
+ }
+ function restoreSelection(savedSelection) {
+ var sel = window.getSelection();
+ var len = savedSelection.length, i;
+ sel.removeAllRanges();
for (i = 0; i < len; i++) {
- var markup = markups[i],
- markupMeta = this.markupTypes.findById(markup.type),
- tagName = markupMeta.tag,
- selfClosing = markupMeta.selfClosing,
- start = markup.start,
- end = markup.end,
- openTag = createOpeningTag(tagName, markup.attributes, selfClosing),
- parsedTagLengthAtIndex = parsedTagsIndexes[start] || 0,
- parsedTagLengthBeforeIndex = sumSparseArray(parsedTagsIndexes.slice(0, start + 1));
+ 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-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 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;
+
+ 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.element.title = 'Insert image or embed...';
+ embedIntent.element.addEventListener('mouseup', function(e) {
+ if (embedIntent.isActive) {
+ embedIntent.deactivate();
+ } else {
+ embedIntent.activate();
+ }
+ e.stopPropagation();
+ });
- text = injectIntoString(text, openTag, start + parsedTagLengthBeforeIndex);
- parsedTagsIndexes[start] = parsedTagLengthAtIndex + openTag.length;
+ embedIntent.toolbar = new Toolbar({ embedIntent: embedIntent, editor: embedIntent.editorContext, commands: options.commands, direction: ToolbarDirection.RIGHT });
+ embedIntent.isActive = false;
- if (!selfClosing) {
- var closeTag = createCloseTag(tagName);
- parsedTagLengthAtIndex = parsedTagsIndexes[end] || 0;
- parsedTagLengthBeforeIndex = sumSparseArray(parsedTagsIndexes.slice(0, end));
- text = injectIntoString(text, closeTag, end + parsedTagLengthBeforeIndex);
- parsedTagsIndexes[end] = parsedTagLengthAtIndex + closeTag.length;
+ function embedIntentHandler() {
+ var blockElement = getSelectionBlockElement();
+ var blockElementContent = blockElement && blockElement.innerHTML;
+ if (blockElementContent === '' || blockElementContent === '
') {
+ embedIntent.showAt(blockElement);
+ } else {
+ embedIntent.hide();
}
}
- return text;
- };
+ rootElement.addEventListener('keyup', embedIntentHandler);
- __exports__["default"] = HTMLElementRenderer;
- });
-define("renderers/html-embed-renderer",
- ["./embeds/youtube","./embeds/twitter","./embeds/instagram","exports"],
- function(__dependency1__, __dependency2__, __dependency3__, __exports__) {
- "use strict";
- var YouTubeRenderer = __dependency1__["default"];
- var TwitterRenderer = __dependency2__["default"];
- var InstagramRenderer = __dependency3__["default"];
+ document.addEventListener('mouseup', function(e) {
+ setTimeout(function() {
+ if (!nodeIsDescendantOfElement(e.target, embedIntent.toolbar.element)) {
+ embedIntentHandler();
+ }
+ });
+ });
- /**
- * A dictionary of supported embed services
- */
- var services = {
- YOUTUBE : {
- id: 1,
- renderer: new YouTubeRenderer()
- },
- TWITTER : {
- id: 2,
- renderer: new TwitterRenderer()
- },
- INSTAGRAM : {
- id: 3,
- renderer: new InstagramRenderer()
- }
- };
+ document.addEventListener('keyup', function(e) {
+ if (e.keyCode === Keycodes.ESC) {
+ embedIntent.hide();
+ }
+ });
- /**
- * @class EmbedRenderer
- * @constructor
- */
- function EmbedRenderer() {}
+ window.addEventListener('resize', function() {
+ if(embedIntent.isShowing) {
+ positionElementToLeftOf(embedIntent.element, embedIntent.atNode);
+ if (embedIntent.toolbar.isShowing) {
+ embedIntent.toolbar.positionToContent(embedIntent.element);
+ }
+ }
+ });
+ }
+ inherit(EmbedIntent, View);
- /**
- * @method render
- * @param model
- * @return String html
- */
- EmbedRenderer.prototype.render = function(model) {
- var renderer = this.rendererFor(model);
- if (renderer) {
- return renderer.render(model);
+ EmbedIntent.prototype.hide = function() {
+ if (EmbedIntent._super.prototype.hide.call(this)) {
+ this.deactivate();
}
- var attrs = model.attributes;
- return attrs && attrs.html || '';
};
- /**
- * @method rendererFor
- * @param model
- * @return service renderer
- */
- EmbedRenderer.prototype.rendererFor = function(model) {
- var provider = model.attributes.provider_name;
- var providerKey = provider && provider.toUpperCase();
- var service = services[providerKey];
- return service && service.renderer;
+ EmbedIntent.prototype.showAt = function(node) {
+ this.show();
+ this.deactivate();
+ this.atNode = node;
+ positionElementToLeftOf(this.element, node);
};
- __exports__["default"] = EmbedRenderer;
- });
-define("renderers/html-renderer",
- ["../types/type","./html-element-renderer","./html-embed-renderer","../types/default-types","../utils/object-utils","exports"],
- function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __exports__) {
- "use strict";
- var Type = __dependency1__["default"];
- var HTMLElementRenderer = __dependency2__["default"];
- var HTMLEmbedRenderer = __dependency3__["default"];
- var DefaultBlockTypeSet = __dependency4__.DefaultBlockTypeSet;
- var DefaultMarkupTypeSet = __dependency4__.DefaultMarkupTypeSet;
- var merge = __dependency5__.merge;
-
- /**
- * @class HTMLRenderer
- * @constructor
- */
- function HTMLRenderer(options) {
- var defaults = {
- blockTypes : DefaultBlockTypeSet,
- markupTypes : DefaultMarkupTypeSet
- };
- merge(this, defaults, options);
- }
-
- /**
- * @method willRenderType
- * @param type instance of Type
- * @param renderer the rendering function that returns a string of html
- * Registers custom rendering hooks for a type
- */
- var renderHooks = {};
- HTMLRenderer.prototype.willRenderType = function(type, renderer) {
- renderHooks[type.id] = renderer;
+ EmbedIntent.prototype.activate = function() {
+ if (!this.isActive) {
+ this.addClass('activated');
+ this.toolbar.show();
+ this.toolbar.positionToContent(this.element);
+ this.isActive = true;
+ }
};
- /**
- * @method rendererFor
- * @param model
- * @returns renderer
- * Returns an instance of a renderer for supplied model
- */
- HTMLRenderer.prototype.rendererFor = function(model) {
- var type = this.blockTypes.findById(model.type);
- if (type === Type.EMBED) {
- return new HTMLEmbedRenderer();
+ EmbedIntent.prototype.deactivate = function() {
+ if (this.isActive) {
+ this.removeClass('activated');
+ this.toolbar.hide();
+ this.isActive = false;
}
- return new HTMLElementRenderer({ type: type, markupTypes: this.markupTypes });
};
- /**
- * @method render
- * @param model
- * @return String html
- */
- HTMLRenderer.prototype.render = function(model) {
- var html = '';
- var len = model && model.length;
- var i, item, renderer, renderHook, itemHtml;
+ // TODO: cleanup
+ var loading = createDiv('div');
+ loading.className = 'ck-embed-loading';
+ loading.innerHTML = 'LOADING';
- for (i = 0; i < len; i++) {
- item = model[i];
- renderer = this.rendererFor(item);
- renderHook = renderHooks[item.type];
- itemHtml = renderHook ? renderHook.call(renderer, item) : renderer.render(item);
- if (itemHtml) { html += itemHtml; }
- }
- return html;
+ EmbedIntent.prototype.showLoading = function() {
+ this.hide();
+ document.body.appendChild(loading);
+ positionElementCenteredIn(loading, this.atNode);
};
- __exports__["default"] = HTMLRenderer;
+ EmbedIntent.prototype.hideLoading = function() {
+ document.body.removeChild(loading);
+ };
+
+
+ __exports__["default"] = EmbedIntent;
});
-define("types/default-types",
- ["./type-set","./type","exports"],
+define("content-kit-editor/views/message",
+ ["./view","../../content-kit-utils/object-utils","exports"],
function(__dependency1__, __dependency2__, __exports__) {
"use strict";
- var TypeSet = __dependency1__["default"];
- var Type = __dependency2__["default"];
+ var View = __dependency1__["default"];
+ var inherit = __dependency2__.inherit;
- /**
- * Default supported block types
- */
- var DefaultBlockTypeSet = new TypeSet([
- new Type({ tag: 'p', name: 'text' }),
- new Type({ tag: 'h2', name: 'heading' }),
- new Type({ tag: 'h3', name: 'subheading' }),
- new Type({ tag: 'img', name: 'image' }),
- new Type({ tag: 'blockquote', name: 'quote' }),
- new Type({ tag: 'ul', name: 'list' }),
- new Type({ tag: 'ol', name: 'ordered list' }),
- new Type({ name: 'embed' })
- ]);
+ function Message(options) {
+ options = options || {};
+ options.classNames = ['ck-message'];
+ View.call(this, options);
+ }
+ inherit(Message, View);
- /**
- * Default supported markup types
- */
- var DefaultMarkupTypeSet = new TypeSet([
- new Type({ tag: 'b', name: 'bold' }),
- new Type({ tag: 'i', name: 'italic' }),
- new Type({ tag: 'u', name: 'underline' }),
- new Type({ tag: 'a', name: 'link' }),
- new Type({ tag: 'br', name: 'break' }),
- new Type({ tag: 'li', name: 'list item' }),
- new Type({ tag: 'sub', name: 'subscript' }),
- new Type({ tag: 'sup', name: 'superscript' })
- ]);
+ Message.prototype.show = function(message) {
+ var messageView = this;
+ messageView.element.innerHTML = message;
+ Message._super.prototype.show.call(messageView);
+ setTimeout(function() {
+ messageView.hide();
+ }, 3000);
+ };
- __exports__.DefaultBlockTypeSet = DefaultBlockTypeSet;
- __exports__.DefaultMarkupTypeSet = DefaultMarkupTypeSet;
+ __exports__["default"] = Message;
});
-define("types/type-set",
- ["exports"],
- function(__exports__) {
+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";
- /**
- * @class TypeSet
- * @private
- * @constructor
- * A Set of Types
- */
- function TypeSet(types) {
- var len = types && types.length, i;
+ 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);
+ }
+
+ 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(); }
+ }
+ });
- this._autoId = 1; // Auto-increment id counter
- this.idLookup = {}; // Hash cache for finding by id
- this.tagLookup = {}; // Hash cache for finding by tag
+ window.addEventListener('resize', function() {
+ var activeHilite = hiliter.parentNode;
+ var range = prompt.range;
+ if(activeHilite && range) {
+ positionHiliteRange(range);
+ }
+ });
+ }
+ inherit(Prompt, View);
+
+ 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; }
+ }
+ };
- for (i = 0; i < len; i++) {
- this.addType(types[i]);
+ Prompt.prototype.hide = function() {
+ if (hiliter.parentNode) {
+ container.removeChild(hiliter);
}
- }
+ };
- TypeSet.prototype = {
- /**
- * Adds a type to the set
- */
- addType: function(type) {
- this[type.name] = type;
- if (type.id === undefined) {
- type.id = this._autoId++;
+ __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();
}
- this.idLookup[type.id] = type;
- if (type.tag) {
- this.tagLookup[type.tag] = type;
+ });
+
+ document.addEventListener('mouseup', function() {
+ setTimeout(function() { toolbar.handleTextSelection(); });
+ });
+
+ window.addEventListener('resize', function() {
+ if(toolbar.isShowing) {
+ var activePromptRange = toolbar.activePrompt && toolbar.activePrompt.range;
+ toolbar.positionToContent(activePromptRange ? activePromptRange : window.getSelection().getRangeAt(0));
}
- return type;
- },
+ });
+ }
+ inherit(TextFormatToolbar, Toolbar);
- /**
- * Returns type info for a given Node
- */
- findByNode: function(node) {
- return this.findByTag(node.tagName);
- },
- /**
- * Returns type info for a given tag
- */
- findByTag: function(tag) {
- return this.tagLookup[tag.toLowerCase()];
- },
- /**
- * Returns type info for a given id
- */
- findById: function(id) {
- return this.idLookup[id];
+ 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);
}
};
- __exports__["default"] = TypeSet;
+ __exports__["default"] = TextFormatToolbar;
});
-define("types/type",
- ["../utils/string-utils","exports"],
+define("content-kit-editor/views/toolbar-button",
+ ["../commands/base","exports"],
function(__dependency1__, __exports__) {
"use strict";
- var underscore = __dependency1__.underscore;
+ var Command = __dependency1__["default"];
- /**
- * @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;
- }
- if (options.tag) {
- this.tag = options.tag.toLowerCase();
- this.selfClosing = /^(br|img|hr|meta|link|embed)$/i.test(this.tag);
- }
+ var buttonClassName = 'ck-toolbar-btn';
- // Register the type as constant
- Type[this.name] = this;
+ function ToolbarButton(options) {
+ var button = this;
+ var toolbar = options.toolbar;
+ var command = options.command;
+ var prompt = command.prompt;
+ var element = document.createElement('button');
+
+ if(typeof command === 'string') {
+ command = Command.index[command];
}
+
+ button.element = element;
+ button.command = command;
+ button.isActive = false;
+
+ 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();
+ }
+ });
}
- __exports__["default"] = Type;
+ 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;
+ }
+ }
+ };
+
+ __exports__["default"] = ToolbarButton;
});
-define("utils/array-utils",
- ["exports"],
- function(__exports__) {
+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";
- /**
- * 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 toArray(obj) {
- var array = [],
- i = obj.length >>> 0; // cast to Uint32
- while (i--) {
- array[i] = obj[i];
- }
- return array;
- }
+ 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;
+
+ function updateButtonsForSelection(buttons, selection) {
+ var selectedTags = tagsInSelection(selection),
+ len = buttons.length,
+ i, button;
- /**
- * Computes the sum of values in a (sparse) array
- */
- function sumSparseArray(array) {
- var sum = 0, i;
- for (i in array) { // 'for in' best for sparse arrays
- if (array.hasOwnProperty(i)) {
- sum += array[i];
+ for (i = 0; i < len; i++) {
+ button = buttons[i];
+ if (selectedTags.indexOf(button.command.tag) > -1) {
+ button.setActive();
+ } else {
+ button.setInactive();
}
}
- return sum;
}
- __exports__.toArray = toArray;
- __exports__.sumSparseArray = sumSparseArray;
- });
-define("utils/node-utils",
- ["./string-utils","./array-utils","exports"],
- function(__dependency1__, __dependency2__, __exports__) {
- "use strict";
- var sanitizeWhitespace = __dependency1__.sanitizeWhitespace;
- var toArray = __dependency2__.toArray;
+ 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');
+ }
- /**
- * 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,
- createHTMLDocument = implementation.createHTMLDocument;
- if (createHTMLDocument) {
- return createHTMLDocument.call(implementation, '');
+ View.call(toolbar, options);
+
+ toolbar.activePrompt = null;
+ toolbar.buttons = [];
+
+ toolbar.promptContainerElement = createDiv('ck-toolbar-prompt');
+ toolbar.buttonContainerElement = createDiv('ck-toolbar-buttons');
+ toolbar.element.appendChild(toolbar.promptContainerElement);
+ toolbar.element.appendChild(toolbar.buttonContainerElement);
+
+ for(i = 0; i < commandCount; i++) {
+ this.addCommand(commands[i]);
}
- return document;
- })();
- /**
- * document.createElement with our lean, standalone document
- */
- function createElement(type) {
- return standaloneDocument.createElement(type);
+ // Closes prompt if displayed when changing selection
+ document.addEventListener('mouseup', function() {
+ toolbar.dismissPrompt();
+ });
}
+ inherit(Toolbar, View);
- /**
- * A reusable DOM Node for parsing html content.
- */
- var DOMParsingNode = createElement('div');
+ Toolbar.prototype.hide = function() {
+ if (Toolbar._super.prototype.hide.call(this)) {
+ var style = this.element.style;
+ style.left = '';
+ style.top = '';
+ this.dismissPrompt();
+ }
+ };
- /**
- * Returns plain-text of a `Node`
- */
- function textOfNode(node) {
- var text = node.textContent || node.innerText;
- return text ? sanitizeWhitespace(text) : '';
- }
+ 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);
+ };
- /**
- * Replaces a `Node` with it with its children
- */
- function unwrapNode(node) {
- var children = toArray(node.childNodes),
- len = children.length,
- parent = node.parentNode, i;
- for (i = 0; i < len; i++) {
- parent.insertBefore(children[i], node);
+ 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;
+ };
+
+ Toolbar.prototype.dismissPrompt = function() {
+ var toolbar = this;
+ var activePrompt = toolbar.activePrompt;
+ if (activePrompt) {
+ activePrompt.hide();
+ swapElements(toolbar.buttonContainerElement, toolbar.promptContainerElement);
+ toolbar.activePrompt = null;
}
- }
+ };
- /**
- * Extracts attributes of a `Node` to a hash of key/value pairs
- */
- function attributesForNode(node /*,blacklist*/) {
- var attrs = node.attributes,
- len = attrs && attrs.length,
- 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;
- }
+ Toolbar.prototype.updateForSelection = function(selection) {
+ var toolbar = this;
+ if (selection.isCollapsed) {
+ toolbar.hide();
+ } else {
+ toolbar.show();
+ toolbar.positionToContent(selection.getRangeAt(0));
+ updateButtonsForSelection(toolbar.buttons, selection);
}
- return hash;
- }
+ };
- __exports__.createElement = createElement;
- __exports__.DOMParsingNode = DOMParsingNode;
- __exports__.textOfNode = textOfNode;
- __exports__.unwrapNode = unwrapNode;
- __exports__.attributesForNode = attributesForNode;
+ Toolbar.prototype.positionToContent = function(content) {
+ var directions = ToolbarDirection;
+ var positioningMethod;
+ switch(this.direction) {
+ case directions.RIGHT:
+ positioningMethod = positionElementToRightOf;
+ break;
+ default:
+ positioningMethod = positionElementCenteredAbove;
+ }
+ positioningMethod(this.element, content);
+ };
+
+ __exports__["default"] = Toolbar;
});
-define("utils/object-utils",
- ["exports"],
- function(__exports__) {
+define("content-kit-editor/views/tooltip",
+ ["./view","../../content-kit-utils/object-utils","../utils/element-utils","exports"],
+ function(__dependency1__, __dependency2__, __dependency3__, __exports__) {
"use strict";
- /**
- * Merges set of properties on a object
- * Useful for constructor defaults/options
- */
- function merge(object, defaults, updates) {
- updates = updates || {};
- for(var o in defaults) {
- if (defaults.hasOwnProperty(o)) {
- object[o] = updates[o] || defaults[o];
+ var View = __dependency1__["default"];
+ var inherit = __dependency2__.inherit;
+ var positionElementCenteredBelow = __dependency3__.positionElementCenteredBelow;
+ var getEventTargetMatchingTag = __dependency3__.getEventTargetMatchingTag;
+
+ 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);
+
+ rootElement.addEventListener('mouseover', function(e) {
+ var target = getEventTargetMatchingTag(options.showForTag, e.target, rootElement);
+ if (target) {
+ timeout = setTimeout(function() {
+ tooltip.showLink(target.href, target);
+ }, delay);
}
- }
- }
-
- /**
- * Prototype inheritance helper
- */
- function inherit(Sub, Super) {
- for (var key in Super) {
- if (Super.hasOwnProperty(key)) {
- Sub[key] = Super[key];
+ });
+
+ rootElement.addEventListener('mouseout', function(e) {
+ clearTimeout(timeout);
+ var toElement = e.toElement || e.relatedTarget;
+ if (toElement && toElement.className !== tooltip.element.className) {
+ tooltip.hide();
}
- }
- Sub.prototype = new Super();
- Sub.constructor = Sub;
+ });
}
+ inherit(Tooltip, View);
- __exports__.merge = merge;
- __exports__.inherit = inherit;
+ Tooltip.prototype.showMessage = function(message, element) {
+ var tooltip = this;
+ var tooltipElement = tooltip.element;
+ tooltipElement.innerHTML = message;
+ tooltip.show();
+ positionElementCenteredBelow(tooltipElement, element);
+ };
+
+ Tooltip.prototype.showLink = function(link, element) {
+ var message = '' + link + '';
+ this.showMessage(message, element);
+ };
+
+ __exports__["default"] = Tooltip;
});
-define("utils/string-utils",
+define("content-kit-editor/views/view",
["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;
-
- /**
- * 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 ? (string + '').replace(/ /g, '_') : '';
- }
-
- /**
- * 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);
- }
+ 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;
+ }
+
+ View.prototype = {
+ show: function() {
+ var view = this;
+ if(!view.isShowing) {
+ view.container.appendChild(view.element);
+ view.isShowing = true;
+ return true;
+ }
+ },
+ hide: function() {
+ var view = this;
+ if(view.isShowing) {
+ view.container.removeChild(view.element);
+ view.isShowing = false;
+ return true;
+ }
+ },
+ 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__.trim = trim;
- __exports__.trimLeft = trimLeft;
- __exports__.underscore = underscore;
- __exports__.sanitizeWhitespace = sanitizeWhitespace;
- __exports__.injectIntoString = injectIntoString;
+ __exports__["default"] = View;
});
-define("renderers/embeds/instagram",
+define("content-kit-compiler/renderers/embeds/instagram",
["exports"],
function(__exports__) {
"use strict";
@@ -2412,7 +2691,7 @@ define("renderers/embeds/instagram",
__exports__["default"] = InstagramRenderer;
});
-define("renderers/embeds/twitter",
+define("content-kit-compiler/renderers/embeds/twitter",
["exports"],
function(__exports__) {
"use strict";
@@ -2424,7 +2703,7 @@ define("renderers/embeds/twitter",
__exports__["default"] = TwitterRenderer;
});
-define("renderers/embeds/youtube",
+define("content-kit-compiler/renderers/embeds/youtube",
["exports"],
function(__exports__) {
"use strict";
@@ -2448,4 +2727,4 @@ define("renderers/embeds/youtube",
__exports__["default"] = YouTubeRenderer;
});
-}(this, document, define));
+}(this, document));
diff --git a/gulpfile.js b/gulpfile.js
index 8e84dd238..5b153b56a 100644
--- a/gulpfile.js
+++ b/gulpfile.js
@@ -8,24 +8,17 @@ var util = require('gulp-util');
var open = require('gulp-open');
var rimraf = require('gulp-rimraf');
var insert = require('gulp-insert');
-var runSequence = require('run-sequence');
+var es6ModuleTranspiler = require('gulp-es6-module-transpiler');
// -------------------------------------------
var pkg = require('./package.json');
var jsSrc = [
- './src/js/index.js',
- './src/js/constants.js',
- './src/js/utils/*.js',
- './src/js/views/view.js',
- './src/js/views/*.js',
- './src/js/commands.js',
- './src/js/editor.js'
+ './src/js/content-kit.js',
+ './src/js/**/*.js'
];
-var jsExtSrc = './src/js/ext/*';
-
var cssSrc = [
'./src/css/variables.less',
'./src/css/editor.less',
@@ -72,27 +65,15 @@ gulp.task('lint', function() {
.pipe(jshint.reporter('default'));
});
-// Concatenates and builds javascript source code
-gulp.task('build-js-src', function() {
+gulp.task('build-js', ['lint'], function() {
return gulp.src(jsSrc)
+ .pipe(es6ModuleTranspiler({ type: 'amd' }))
.pipe(concat(jsDistName))
.pipe(insert.wrap(iifeHeader, iifeFooter))
.pipe(header(banner, { pkg : pkg } ))
- . pipe(gulp.dest(distDest));
-});
-
-// Concatenates external js dependencies with built js source
-gulp.task('build-js-ext', ['build-js-src'], function() {
- return gulp.src([jsDistPath, jsExtSrc])
- .pipe(concat(jsDistName))
.pipe(gulp.dest(distDest));
});
-// Builds final js output in sequence (tasks are order dependent)
-gulp.task('build-js', ['lint', 'build-js-src', 'build-js-ext'], function() {
- return runSequence('build-js-src', 'build-js-ext');
-});
-
// Compiles LESS and concatenates css
gulp.task('build-css', function() {
return gulp.src(cssSrc)
diff --git a/package.json b/package.json
index 0fc089bad..06889439c 100644
--- a/package.json
+++ b/package.json
@@ -19,6 +19,7 @@
"devDependencies": {
"gulp": "^3.8.1",
"gulp-concat": "~2.1.7",
+ "gulp-es6-module-transpiler": "^0.1.3",
"gulp-header": "~1.0.2",
"gulp-insert": "^0.4.0",
"gulp-jshint": "~1.3.4",
@@ -26,7 +27,6 @@
"gulp-open": "^0.2.8",
"gulp-qunit": "^0.3.3",
"gulp-rimraf": "^0.1.0",
- "gulp-util": "~2.2.12",
- "run-sequence": "^0.3.6"
+ "gulp-util": "~2.2.12"
}
}
diff --git a/src/js/commands.js b/src/js/commands.js
deleted file mode 100644
index 62e679c95..000000000
--- a/src/js/commands.js
+++ /dev/null
@@ -1,294 +0,0 @@
-function createCommandIndex(commands) {
- var index = {};
- var len = commands.length, i, command;
- for(i = 0; i < len; i++) {
- command = commands[i];
- index[command.name] = command;
- }
- return index;
-}
-
-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; }
-}
-Command.prototype.exec = function(){};
-
-function TextFormatCommand(options) {
- Command.call(this, options);
- this.tag = options.tag.toUpperCase();
- this.action = options.action || this.name;
- this.removeAction = options.removeAction || this.action;
-}
-inherits(TextFormatCommand, Command);
-
-TextFormatCommand.prototype = {
- exec: function(value) {
- document.execCommand(this.action, false, value || null);
- },
- unexec: function(value) {
- document.execCommand(this.removeAction, false, value || null);
- }
-};
-
-function BoldCommand() {
- TextFormatCommand.call(this, {
- name: 'bold',
- tag: Tags.BOLD,
- button: ''
- });
-}
-inherits(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);
- }
-};
-
-function ItalicCommand() {
- TextFormatCommand.call(this, {
- name: 'italic',
- tag: Tags.ITALIC,
- button: ''
- });
-}
-inherits(ItalicCommand, TextFormatCommand);
-
-function LinkCommand() {
- TextFormatCommand.call(this, {
- name: 'link',
- tag: Tags.LINK,
- action: 'createLink',
- removeAction: 'unlink',
- button: '',
- prompt: new Prompt({
- command: this,
- placeholder: 'Enter a url, press return...'
- })
- });
-}
-inherits(LinkCommand, TextFormatCommand);
-LinkCommand.prototype.exec = function(url) {
- if(this.tag === getSelectionTagName()) {
- this.unexec();
- } else {
- if (!Regex.HTTP_PROTOCOL.test(url)) {
- url = 'http://' + url;
- }
- LinkCommand._super.prototype.exec.call(this, url);
- }
-};
-
-function FormatBlockCommand(options) {
- options.action = 'formatBlock';
- TextFormatCommand.call(this, options);
-}
-inherits(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
- if(tag === blockElement.tagName) {
- value = Tags.PARAGRAPH;
- } else {
- // Flattens the selection before applying the block format.
- // Otherwise, undesirable nested blocks can occur.
- var flatNode = document.createTextNode(blockElement.textContent);
- blockElement.parentNode.insertBefore(flatNode, blockElement);
- blockElement.parentNode.removeChild(blockElement);
- selectNode(flatNode);
- }
-
- FormatBlockCommand._super.prototype.exec.call(this, value);
-};
-
-function QuoteCommand() {
- FormatBlockCommand.call(this, {
- name: 'quote',
- tag: Tags.QUOTE,
- button: ''
- });
-}
-inherits(QuoteCommand, FormatBlockCommand);
-
-function HeadingCommand() {
- FormatBlockCommand.call(this, {
- name: 'heading',
- tag: Tags.HEADING,
- button: '1'
- });
-}
-inherits(HeadingCommand, FormatBlockCommand);
-
-function SubheadingCommand() {
- FormatBlockCommand.call(this, {
- name: 'subheading',
- tag: Tags.SUBHEADING,
- button: '2'
- });
-}
-inherits(SubheadingCommand, FormatBlockCommand);
-
-function ListCommand(options) {
- TextFormatCommand.call(this, options);
-}
-inherits(ListCommand, TextFormatCommand);
-ListCommand.prototype.exec = function() {
- ListCommand._super.prototype.exec.call(this);
-
- // After creation, lists need to be unwrapped from the default formatter P tag
- var listElement = getSelectionBlockElement();
- var wrapperNode = listElement.parentNode;
- if (wrapperNode.firstChild === listElement) {
- var editorNode = wrapperNode.parentNode;
- editorNode.insertBefore(listElement, wrapperNode);
- editorNode.removeChild(wrapperNode);
- selectNode(listElement);
- }
-};
-
-function UnorderedListCommand() {
- ListCommand.call(this, {
- name: 'list',
- tag: Tags.LIST,
- action: 'insertUnorderedList'
- });
-}
-inherits(UnorderedListCommand, ListCommand);
-
-function OrderedListCommand() {
- ListCommand.call(this, {
- name: 'ordered list',
- tag: Tags.ORDERED_LIST,
- action: 'insertOrderedList'
- });
-}
-inherits(OrderedListCommand, ListCommand);
-
-TextFormatCommand.all = [
- new BoldCommand(),
- new ItalicCommand(),
- new LinkCommand(),
- new QuoteCommand(),
- new HeadingCommand(),
- new SubheadingCommand()
-];
-
-TextFormatCommand.index = createCommandIndex(TextFormatCommand.all);
-
-
-function EmbedCommand(options) {
- Command.call(this, options);
-}
-inherits(EmbedCommand, Command);
-
-function ImageEmbedCommand(options) {
- EmbedCommand.call(this, {
- name: 'image',
- button: ''
- });
- if (window.XHRFileUploader) {
- this.uploader = new XHRFileUploader({ url: '/upload', maxFileSize: 5000000 });
- }
-}
-inherits(ImageEmbedCommand, EmbedCommand);
-
-ImageEmbedCommand.prototype = {
- exec: function() {
- ImageEmbedCommand._super.prototype.exec.call(this);
- var clickEvent = new MouseEvent('click', { bubbles: false });
- if (!this.fileInput) {
- var command = this;
- var fileInput = this.fileInput = document.createElement('input');
- fileInput.type = 'file';
- fileInput.accept = 'image/*';
- fileInput.className = 'ck-file-input';
- fileInput.addEventListener('change', function(e) {
- command.handleFile(e);
- });
- document.body.appendChild(fileInput);
- }
- this.fileInput.dispatchEvent(clickEvent);
- },
- handleFile: function(e) {
- var fileInput = e.target;
- var editor = this.editorContext;
- var embedIntent = this.embedIntent;
-
- embedIntent.showLoading();
- this.uploader.upload({
- fileInput: fileInput,
- complete: function(response, error) {
- embedIntent.hideLoading();
- if (error || !response || !response.url) {
- return new Message().show(error.message || 'Error uploading image');
- }
- var imageModel = new ContentKit.ImageModel({ src: response.url });
- var index = editor.getCurrentBlockIndex();
- editor.insertBlockAt(imageModel, index);
- editor.syncVisualAt(index);
- }
- });
- fileInput.value = null; // reset file input
- // TODO: client-side render while uploading
- }
-};
-
-function OEmbedCommand(options) {
- EmbedCommand.call(this, {
- name: 'oEmbed',
- button: '',
- prompt: new Prompt({
- command: this,
- placeholder: 'Paste a YouTube or Twitter url...'
- })
- });
-}
-inherits(OEmbedCommand, EmbedCommand);
-
-OEmbedCommand.prototype.exec = function(url) {
- var command = this;
- var editorContext = command.editorContext;
- var index = editorContext.getCurrentBlockIndex();
- var oEmbedEndpoint = 'http://noembed.com/embed?url=';
-
- command.embedIntent.showLoading();
- if (!Regex.HTTP_PROTOCOL.test(url)) {
- url = 'http://' + url;
- }
-
- HTTP.get(oEmbedEndpoint + url, function(responseText, error) {
- command.embedIntent.hideLoading();
- if (error) {
- new Message().show('Embed error: status code ' + error.currentTarget.status);
- } else {
- var json = JSON.parse(responseText);
- if (json.error) {
- new Message().show('Embed error: ' + json.error);
- } else {
- var embedModel = new ContentKit.EmbedModel(json);
- //if (!embedModel.attributes.provider_id) {
- // new Message().show('Embed error: "' + embedModel.attributes.provider_name + '" embeds are not supported at this time');
- //} else {
- editorContext.insertBlockAt(embedModel, index);
- editorContext.syncVisualAt(index);
- //}
- }
- }
- });
-};
-
-EmbedCommand.all = [
- new ImageEmbedCommand(),
- new OEmbedCommand()
-];
-
-EmbedCommand.index = createCommandIndex(EmbedCommand.all);
diff --git a/src/js/content-kit-compiler/compiler.js b/src/js/content-kit-compiler/compiler.js
new file mode 100644
index 000000000..348898681
--- /dev/null
+++ b/src/js/content-kit-compiler/compiler.js
@@ -0,0 +1,68 @@
+import HTMLParser from './parsers/html-parser';
+import HTMLRenderer from './renderers/html-renderer';
+import Type from './types/type';
+import { DefaultBlockTypeSet, DefaultMarkupTypeSet } from './types/default-types';
+import { mergeWithOptions } from '../../content-kit-utils/object-utils';
+
+/**
+ * @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) {
+ if (type instanceof Type) {
+ return this.blockTypes.addType(type);
+ }
+};
+
+/**
+ * @method registerMarkupType
+ * @param {Type} type
+ */
+Compiler.prototype.registerMarkupType = function(type) {
+ if (type instanceof Type) {
+ return this.markupTypes.addType(type);
+ }
+};
+
+export default Compiler;
diff --git a/src/js/content-kit-compiler/models/block.js b/src/js/content-kit-compiler/models/block.js
new file mode 100644
index 000000000..07c4e223f
--- /dev/null
+++ b/src/js/content-kit-compiler/models/block.js
@@ -0,0 +1,31 @@
+import Model from './model';
+import { inherit } from '../../content-kit-utils/object-utils';
+
+/**
+ * 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;
+ });
+}
+
+/**
+ * @class BlockModel
+ * @constructor
+ * @extends Model
+ */
+function BlockModel(options) {
+ options = options || {};
+ Model.call(this, options);
+ this.value = options.value || '';
+ this.markup = sortBlockMarkups(options.markup || []);
+}
+inherit(BlockModel, Model);
+
+export default BlockModel;
diff --git a/src/js/content-kit-compiler/models/embed.js b/src/js/content-kit-compiler/models/embed.js
new file mode 100644
index 000000000..da4a5927c
--- /dev/null
+++ b/src/js/content-kit-compiler/models/embed.js
@@ -0,0 +1,45 @@
+import { inherit } from '../../content-kit-utils/object-utils';
+import Model from '../models/model';
+import Type from '../types/type';
+
+/**
+ * @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: {}
+ });
+
+ 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;
+ }
+
+ if (embedHtml && embedType === 'rich') {
+ attributes.html = embedHtml;
+ }
+}
+inherit(Model, EmbedModel);
+
+export default EmbedModel;
diff --git a/src/js/content-kit-compiler/models/image.js b/src/js/content-kit-compiler/models/image.js
new file mode 100644
index 000000000..3e1c7ff34
--- /dev/null
+++ b/src/js/content-kit-compiler/models/image.js
@@ -0,0 +1,22 @@
+import BlockModel from './block';
+import Type from '../types/type';
+import { inherit } from '../../content-kit-utils/object-utils';
+
+/**
+ * @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 };
+ }
+ BlockModel.call(this, options);
+}
+inherit(ImageModel, BlockModel);
+
+export default ImageModel;
diff --git a/src/js/content-kit-compiler/models/markup.js b/src/js/content-kit-compiler/models/markup.js
new file mode 100644
index 000000000..da3aa4876
--- /dev/null
+++ b/src/js/content-kit-compiler/models/markup.js
@@ -0,0 +1,17 @@
+import Model from './model';
+import { inherit } from '../../content-kit-utils/object-utils';
+
+/**
+ * @class MarkupModel
+ * @constructor
+ * @extends Model
+ */
+function MarkupModel(options) {
+ options = options || {};
+ Model.call(this, options);
+ this.start = options.start || 0;
+ this.end = options.end || 0;
+}
+inherit(MarkupModel, Model);
+
+export default MarkupModel;
diff --git a/src/js/content-kit-compiler/models/model.js b/src/js/content-kit-compiler/models/model.js
new file mode 100644
index 000000000..094a8e7f0
--- /dev/null
+++ b/src/js/content-kit-compiler/models/model.js
@@ -0,0 +1,20 @@
+/**
+ * @class Model
+ * @constructor
+ * @private
+ */
+function Model(options) {
+ options = options || {};
+ var type_name = options.type_name;
+ var attributes = options.attributes;
+
+ this.type = options.type || null;
+ if (type_name) {
+ this.type_name = type_name;
+ }
+ if (attributes) {
+ this.attributes = attributes;
+ }
+}
+
+export default Model;
\ No newline at end of file
diff --git a/src/js/content-kit-compiler/models/text.js b/src/js/content-kit-compiler/models/text.js
new file mode 100644
index 000000000..602c8c650
--- /dev/null
+++ b/src/js/content-kit-compiler/models/text.js
@@ -0,0 +1,19 @@
+import BlockModel from './block';
+import Type from '../types/type';
+import { inherit } from '../../content-kit-utils/object-utils';
+
+/**
+ * @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(TextModel, BlockModel);
+
+export default TextModel;
diff --git a/src/js/content-kit-compiler/parsers/html-parser.js b/src/js/content-kit-compiler/parsers/html-parser.js
new file mode 100644
index 000000000..2d158dcd5
--- /dev/null
+++ b/src/js/content-kit-compiler/parsers/html-parser.js
@@ -0,0 +1,172 @@
+import BlockModel from '../models/block';
+import MarkupModel from '../models/markup';
+import { DefaultBlockTypeSet, DefaultMarkupTypeSet } from '../types/default-types';
+import { mergeWithOptions } from '../../content-kit-utils/object-utils';
+import { toArray } from '../../content-kit-utils/array-utils';
+import { trim, trimLeft, sanitizeWhitespace } from '../../content-kit-utils/string-utils';
+import { createElement, DOMParsingNode, textOfNode, unwrapNode, attributesForNode } from '../../content-kit-utils/node-utils';
+
+/**
+ * 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)
+ });
+ }
+ }
+};
+
+export default HTMLParser;
diff --git a/src/js/content-kit-compiler/renderers/embeds/instagram.js b/src/js/content-kit-compiler/renderers/embeds/instagram.js
new file mode 100644
index 000000000..b92005bb3
--- /dev/null
+++ b/src/js/content-kit-compiler/renderers/embeds/instagram.js
@@ -0,0 +1,7 @@
+
+function InstagramRenderer() {}
+InstagramRenderer.prototype.render = function(model) {
+ return '';
+};
+
+export default InstagramRenderer;
diff --git a/src/js/content-kit-compiler/renderers/embeds/twitter.js b/src/js/content-kit-compiler/renderers/embeds/twitter.js
new file mode 100644
index 000000000..167e09a63
--- /dev/null
+++ b/src/js/content-kit-compiler/renderers/embeds/twitter.js
@@ -0,0 +1,7 @@
+
+function TwitterRenderer() {}
+TwitterRenderer.prototype.render = function(model) {
+ return '';
+};
+
+export default TwitterRenderer;
diff --git a/src/js/content-kit-compiler/renderers/embeds/youtube.js b/src/js/content-kit-compiler/renderers/embeds/youtube.js
new file mode 100644
index 000000000..e88de5a32
--- /dev/null
+++ b/src/js/content-kit-compiler/renderers/embeds/youtube.js
@@ -0,0 +1,19 @@
+
+var RegExVideoId = /.*(?:youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=)([^#\&\?]*).*/;
+
+function getVideoIdFromUrl(url) {
+ var match = url.match(RegExVideoId);
+ if (match && match[1].length === 11){
+ return match[1];
+ }
+ return null;
+}
+
+function YouTubeRenderer() {}
+YouTubeRenderer.prototype.render = function(model) {
+ var videoId = getVideoIdFromUrl(model.attributes.url);
+ var embedUrl = 'http://www.youtube.com/embed/' + videoId + '?controls=2&showinfo=0&color=white&theme=light';
+ return '';
+};
+
+export default YouTubeRenderer;
\ No newline at end of file
diff --git a/src/js/content-kit-compiler/renderers/html-element-renderer.js b/src/js/content-kit-compiler/renderers/html-element-renderer.js
new file mode 100644
index 000000000..370083ac3
--- /dev/null
+++ b/src/js/content-kit-compiler/renderers/html-element-renderer.js
@@ -0,0 +1,98 @@
+import { injectIntoString } from '../../content-kit-utils/string-utils';
+import { sumSparseArray } from '../../content-kit-utils/array-utils';
+
+/**
+ * Builds an opening html tag. i.e. ''
+ */
+function createOpeningTag(tagName, attributes, selfClosing /*,blacklist*/) {
+ var tag = '<' + tagName;
+ for (var attr in attributes) {
+ if (attributes.hasOwnProperty(attr)) {
+ //if (blacklist && attr in blacklist) { continue; }
+ tag += ' ' + attr + '="' + attributes[attr] + '"';
+ }
+ }
+ if (selfClosing) { tag += '/'; }
+ tag += '>';
+ return tag;
+}
+
+/**
+ * Builds a closing html tag. i.e. ''
+ */
+function createCloseTag(tagName) {
+ return '' + tagName + '>';
+}
+
+/**
+ * @class HTMLElementRenderer
+ * @constructor
+ */
+function HTMLElementRenderer(options) {
+ options = options || {};
+ this.type = options.type;
+ this.markupTypes = options.markupTypes;
+}
+
+/**
+ * @method render
+ * @param model a block model
+ * @return String html
+ * Renders a block model into a HTML string.
+ */
+HTMLElementRenderer.prototype.render = function(model) {
+ var html = '';
+ var type = this.type;
+ var tagName = type.tag;
+ var selfClosing = type.selfClosing;
+
+ if (tagName) {
+ html += createOpeningTag(tagName, model.attributes, selfClosing);
+ }
+ if (!selfClosing) {
+ html += this.renderMarkup(model.value, model.markup);
+ if (tagName) {
+ html += createCloseTag(tagName);
+ }
+ }
+ return html;
+};
+
+/**
+ * @method renderMarkup
+ * @param text plain text to apply markup to
+ * @param markup an array of markup models
+ * @return String html
+ * Renders a markup model into a HTML string.
+ */
+HTMLElementRenderer.prototype.renderMarkup = function(text, markups) {
+ var parsedTagsIndexes = [],
+ len = markups && markups.length, i;
+
+ for (i = 0; i < len; i++) {
+ var markup = markups[i],
+ markupMeta = this.markupTypes.findById(markup.type),
+ tagName = markupMeta.tag,
+ selfClosing = markupMeta.selfClosing,
+ start = markup.start,
+ end = markup.end,
+ openTag = createOpeningTag(tagName, markup.attributes, selfClosing),
+ parsedTagLengthAtIndex = parsedTagsIndexes[start] || 0,
+ parsedTagLengthBeforeIndex = sumSparseArray(parsedTagsIndexes.slice(0, start + 1));
+
+ text = injectIntoString(text, openTag, start + parsedTagLengthBeforeIndex);
+ parsedTagsIndexes[start] = parsedTagLengthAtIndex + openTag.length;
+
+ if (!selfClosing) {
+ var closeTag = createCloseTag(tagName);
+ parsedTagLengthAtIndex = parsedTagsIndexes[end] || 0;
+ parsedTagLengthBeforeIndex = sumSparseArray(parsedTagsIndexes.slice(0, end));
+ text = injectIntoString(text, closeTag, end + parsedTagLengthBeforeIndex);
+ parsedTagsIndexes[end] = parsedTagLengthAtIndex + closeTag.length;
+ }
+ }
+
+ return text;
+};
+
+export default HTMLElementRenderer;
diff --git a/src/js/content-kit-compiler/renderers/html-embed-renderer.js b/src/js/content-kit-compiler/renderers/html-embed-renderer.js
new file mode 100644
index 000000000..101059b21
--- /dev/null
+++ b/src/js/content-kit-compiler/renderers/html-embed-renderer.js
@@ -0,0 +1,55 @@
+import YouTubeRenderer from './embeds/youtube';
+import TwitterRenderer from './embeds/twitter';
+import InstagramRenderer from './embeds/instagram';
+
+/**
+ * A dictionary of supported embed services
+ */
+var services = {
+ YOUTUBE : {
+ id: 1,
+ renderer: new YouTubeRenderer()
+ },
+ TWITTER : {
+ id: 2,
+ renderer: new TwitterRenderer()
+ },
+ INSTAGRAM : {
+ id: 3,
+ renderer: new InstagramRenderer()
+ }
+};
+
+/**
+ * @class EmbedRenderer
+ * @constructor
+ */
+function EmbedRenderer() {}
+
+/**
+ * @method render
+ * @param model
+ * @return String html
+ */
+EmbedRenderer.prototype.render = function(model) {
+ var renderer = this.rendererFor(model);
+ if (renderer) {
+ return renderer.render(model);
+ }
+ var attrs = model.attributes;
+ return attrs && attrs.html || '';
+};
+
+/**
+ * @method rendererFor
+ * @param model
+ * @return service renderer
+ */
+EmbedRenderer.prototype.rendererFor = function(model) {
+ var provider = model.attributes.provider_name;
+ var providerKey = provider && provider.toUpperCase();
+ var service = services[providerKey];
+ return service && service.renderer;
+};
+
+export default EmbedRenderer;
diff --git a/src/js/content-kit-compiler/renderers/html-renderer.js b/src/js/content-kit-compiler/renderers/html-renderer.js
new file mode 100644
index 000000000..6eb111643
--- /dev/null
+++ b/src/js/content-kit-compiler/renderers/html-renderer.js
@@ -0,0 +1,67 @@
+import Type from '../types/type';
+import HTMLElementRenderer from './html-element-renderer';
+import HTMLEmbedRenderer from './html-embed-renderer';
+import { DefaultBlockTypeSet, DefaultMarkupTypeSet } from '../types/default-types';
+import { mergeWithOptions } from '../../content-kit-utils/object-utils';
+
+/**
+ * @class HTMLRenderer
+ * @constructor
+ */
+function HTMLRenderer(options) {
+ var defaults = {
+ blockTypes : DefaultBlockTypeSet,
+ markupTypes : DefaultMarkupTypeSet
+ };
+ mergeWithOptions(this, defaults, options);
+}
+
+/**
+ * @method willRenderType
+ * @param type {Number|Type}
+ * @param renderer the rendering function that returns a string of html
+ * Registers custom rendering hooks for a type
+ */
+var renderHooks = {};
+HTMLRenderer.prototype.willRenderType = function(type, renderer) {
+ if ('number' !== typeof type) {
+ type = type.id;
+ }
+ renderHooks[type] = renderer;
+};
+
+/**
+ * @method rendererFor
+ * @param model
+ * @returns renderer
+ * Returns an instance of a renderer for supplied model
+ */
+HTMLRenderer.prototype.rendererFor = function(model) {
+ var type = this.blockTypes.findById(model.type);
+ if (type === Type.EMBED) {
+ return new HTMLEmbedRenderer();
+ }
+ return new HTMLElementRenderer({ type: type, markupTypes: this.markupTypes });
+};
+
+/**
+ * @method render
+ * @param model
+ * @return String html
+ */
+HTMLRenderer.prototype.render = function(model) {
+ var html = '';
+ var len = model && model.length;
+ var i, item, renderer, renderHook, itemHtml;
+
+ for (i = 0; i < len; i++) {
+ item = model[i];
+ renderer = this.rendererFor(item);
+ renderHook = renderHooks[item.type];
+ itemHtml = renderHook ? renderHook.call(renderer, item) : renderer.render(item);
+ if (itemHtml) { html += itemHtml; }
+ }
+ return html;
+};
+
+export default HTMLRenderer;
diff --git a/src/js/content-kit-compiler/types/default-types.js b/src/js/content-kit-compiler/types/default-types.js
new file mode 100644
index 000000000..607152ea7
--- /dev/null
+++ b/src/js/content-kit-compiler/types/default-types.js
@@ -0,0 +1,32 @@
+import TypeSet from './type-set';
+import Type from './type';
+
+/**
+ * Default supported block types
+ */
+var DefaultBlockTypeSet = new TypeSet([
+ new Type({ tag: 'p', name: 'text' }),
+ new Type({ tag: 'h2', name: 'heading' }),
+ new Type({ tag: 'h3', name: 'subheading' }),
+ new Type({ tag: 'img', name: 'image' }),
+ new Type({ tag: 'blockquote', name: 'quote' }),
+ new Type({ tag: 'ul', name: 'list' }),
+ new Type({ tag: 'ol', name: 'ordered list' }),
+ new Type({ name: 'embed' })
+]);
+
+/**
+ * Default supported markup types
+ */
+var DefaultMarkupTypeSet = new TypeSet([
+ new Type({ tag: 'b', name: 'bold' }),
+ new Type({ tag: 'i', name: 'italic' }),
+ new Type({ tag: 'u', name: 'underline' }),
+ new Type({ tag: 'a', name: 'link' }),
+ new Type({ tag: 'br', name: 'break' }),
+ new Type({ tag: 'li', name: 'list item' }),
+ new Type({ tag: 'sub', name: 'subscript' }),
+ new Type({ tag: 'sup', name: 'superscript' })
+]);
+
+export { DefaultBlockTypeSet, DefaultMarkupTypeSet };
diff --git a/src/js/content-kit-compiler/types/type-set.js b/src/js/content-kit-compiler/types/type-set.js
new file mode 100644
index 000000000..e31b111ad
--- /dev/null
+++ b/src/js/content-kit-compiler/types/type-set.js
@@ -0,0 +1,55 @@
+/**
+ * @class TypeSet
+ * @private
+ * @constructor
+ * A Set of Types
+ */
+function TypeSet(types) {
+ var len = types && types.length, i;
+
+ this._autoId = 1; // Auto-increment id counter
+ this.idLookup = {}; // Hash cache for finding by id
+ this.tagLookup = {}; // Hash cache for finding by tag
+
+ for (i = 0; i < len; i++) {
+ this.addType(types[i]);
+ }
+}
+
+TypeSet.prototype = {
+ /**
+ * Adds a type to the set
+ */
+ addType: function(type) {
+ this[type.name] = type;
+ if (type.id === undefined) {
+ type.id = this._autoId++;
+ }
+ this.idLookup[type.id] = type;
+ if (type.tag) {
+ this.tagLookup[type.tag] = type;
+ }
+ return type;
+ },
+
+ /**
+ * Returns type info for a given Node
+ */
+ findByNode: function(node) {
+ return this.findByTag(node.tagName);
+ },
+ /**
+ * Returns type info for a given tag
+ */
+ findByTag: function(tag) {
+ return this.tagLookup[tag.toLowerCase()];
+ },
+ /**
+ * Returns type info for a given id
+ */
+ findById: function(id) {
+ return this.idLookup[id];
+ }
+};
+
+export default TypeSet;
diff --git a/src/js/content-kit-compiler/types/type.js b/src/js/content-kit-compiler/types/type.js
new file mode 100644
index 000000000..dae7e0049
--- /dev/null
+++ b/src/js/content-kit-compiler/types/type.js
@@ -0,0 +1,24 @@
+import { underscore } from '../../content-kit-utils/string-utils';
+
+/**
+ * @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;
+ }
+ if (options.tag) {
+ this.tag = options.tag.toLowerCase();
+ this.selfClosing = /^(br|img|hr|meta|link|embed)$/i.test(this.tag);
+ }
+
+ // Register the type as constant
+ Type[this.name] = this;
+ }
+}
+
+export default Type;
diff --git a/src/js/content-kit-editor/commands/base.js b/src/js/content-kit-editor/commands/base.js
new file mode 100644
index 000000000..7378a7389
--- /dev/null
+++ b/src/js/content-kit-editor/commands/base.js
@@ -0,0 +1,13 @@
+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; }
+}
+
+Command.prototype.exec = function(){};
+
+export default Command;
\ No newline at end of file
diff --git a/src/js/content-kit-editor/commands/bold.js b/src/js/content-kit-editor/commands/bold.js
new file mode 100644
index 000000000..099390977
--- /dev/null
+++ b/src/js/content-kit-editor/commands/bold.js
@@ -0,0 +1,22 @@
+import TextFormatCommand from './text-format';
+import { inherit } from '../../content-kit-utils/object-utils';
+import { Tags, RegEx } from '../constants';
+import { getSelectionBlockTagName } from '../utils/selection-utils';
+
+function BoldCommand() {
+ TextFormatCommand.call(this, {
+ name: 'bold',
+ tag: Tags.BOLD,
+ button: ''
+ });
+}
+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);
+ }
+};
+
+export default BoldCommand;
diff --git a/src/js/content-kit-editor/commands/commands.js b/src/js/content-kit-editor/commands/commands.js
new file mode 100644
index 000000000..1d3af1c86
--- /dev/null
+++ b/src/js/content-kit-editor/commands/commands.js
@@ -0,0 +1,40 @@
+// TODO: eliminate this file
+import BoldCommand from './bold';
+import ItalicCommand from './italic';
+import LinkCommand from './link';
+import QuoteCommand from './quote';
+import HeadingCommand from './heading';
+import SubheadingCommand from './subheading';
+import ImageCommand from './image';
+import EmbedCommand from './embed';
+
+function createCommandIndex(commands) {
+ var index = {};
+ var len = commands.length, i, command;
+ for(i = 0; i < len; i++) {
+ command = commands[i];
+ index[command.name] = command;
+ }
+ return index;
+}
+
+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);
+
+export { TextFormatCommands, EmbedCommands };
diff --git a/src/js/content-kit-editor/commands/embed.js b/src/js/content-kit-editor/commands/embed.js
new file mode 100644
index 000000000..5a88eb190
--- /dev/null
+++ b/src/js/content-kit-editor/commands/embed.js
@@ -0,0 +1,53 @@
+import Command from './base';
+import Prompt from '../views/prompt';
+import Message from '../views/message';
+import EmbedModel from '../../content-kit-compiler/models/embed';
+import { inherit } from '../../content-kit-utils/object-utils';
+import { xhrGet } from '../utils/http-utils';
+import { RegEx } from '../constants';
+
+function EmbedCommand(options) {
+ Command.call(this, {
+ name: 'embed',
+ button: '',
+ prompt: new Prompt({
+ command: this,
+ placeholder: 'Paste a YouTube or Twitter url...'
+ })
+ });
+}
+inherit(EmbedCommand, Command);
+
+EmbedCommand.prototype.exec = function(url) {
+ var command = this;
+ var editorContext = command.editorContext;
+ var index = editorContext.getCurrentBlockIndex();
+ var oEmbedEndpoint = 'http://noembed.com/embed?url=';
+
+ command.embedIntent.showLoading();
+ if (!RegEx.HTTP_PROTOCOL.test(url)) {
+ url = 'http://' + url;
+ }
+
+ xhrGet(oEmbedEndpoint + url, function(responseText, error) {
+ command.embedIntent.hideLoading();
+ if (error) {
+ new Message().show('Embed error: status code ' + error.currentTarget.status);
+ } else {
+ var json = JSON.parse(responseText);
+ if (json.error) {
+ new Message().show('Embed error: ' + json.error);
+ } else {
+ var embedModel = new EmbedModel(json);
+ //if (!embedModel.attributes.provider_id) {
+ // new Message().show('Embed error: "' + embedModel.attributes.provider_name + '" embeds are not supported at this time');
+ //} else {
+ editorContext.insertBlockAt(embedModel, index);
+ editorContext.syncVisualAt(index);
+ //}
+ }
+ }
+ });
+};
+
+export default EmbedCommand;
diff --git a/src/js/content-kit-editor/commands/format-block.js b/src/js/content-kit-editor/commands/format-block.js
new file mode 100644
index 000000000..fe9a982cf
--- /dev/null
+++ b/src/js/content-kit-editor/commands/format-block.js
@@ -0,0 +1,32 @@
+import TextFormatCommand from './text-format';
+import { Tags } from '../constants';
+import { inherit } from '../../content-kit-utils/object-utils';
+import { getSelectionBlockElement, selectNode } from '../utils/selection-utils';
+
+function FormatBlockCommand(options) {
+ options.action = 'formatBlock';
+ TextFormatCommand.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
+ if(tag === blockElement.tagName) {
+ value = Tags.PARAGRAPH;
+ } else {
+ // Flattens the selection before applying the block format.
+ // Otherwise, undesirable nested blocks can occur.
+ var flatNode = document.createTextNode(blockElement.textContent);
+ blockElement.parentNode.insertBefore(flatNode, blockElement);
+ blockElement.parentNode.removeChild(blockElement);
+ selectNode(flatNode);
+ }
+
+ FormatBlockCommand._super.prototype.exec.call(this, value);
+};
+
+export default FormatBlockCommand;
diff --git a/src/js/content-kit-editor/commands/heading.js b/src/js/content-kit-editor/commands/heading.js
new file mode 100644
index 000000000..87551513a
--- /dev/null
+++ b/src/js/content-kit-editor/commands/heading.js
@@ -0,0 +1,14 @@
+import FormatBlockCommand from './format-block';
+import { Tags } from '../constants';
+import { inherit } from '../../content-kit-utils/object-utils';
+
+function HeadingCommand() {
+ FormatBlockCommand.call(this, {
+ name: 'heading',
+ tag: Tags.HEADING,
+ button: '1'
+ });
+}
+inherit(HeadingCommand, FormatBlockCommand);
+
+export default HeadingCommand;
diff --git a/src/js/content-kit-editor/commands/image.js b/src/js/content-kit-editor/commands/image.js
new file mode 100644
index 000000000..f9b03cd91
--- /dev/null
+++ b/src/js/content-kit-editor/commands/image.js
@@ -0,0 +1,58 @@
+import Command from './base';
+import Message from '../views/message';
+import ImageModel from '../../content-kit-compiler/models/image';
+import { inherit } from '../../content-kit-utils/object-utils';
+
+function ImageCommand(options) {
+ Command.call(this, {
+ name: 'image',
+ button: ''
+ });
+ if (window.XHRFileUploader) {
+ this.uploader = new window.XHRFileUploader({ url: '/upload', maxFileSize: 5000000 });
+ }
+}
+inherit(ImageCommand, Command);
+
+ImageCommand.prototype = {
+ exec: function() {
+ ImageCommand._super.prototype.exec.call(this);
+ var clickEvent = new MouseEvent('click', { bubbles: false });
+ if (!this.fileInput) {
+ var command = this;
+ var fileInput = this.fileInput = document.createElement('input');
+ fileInput.type = 'file';
+ fileInput.accept = 'image/*';
+ fileInput.className = 'ck-file-input';
+ fileInput.addEventListener('change', function(e) {
+ command.handleFile(e);
+ });
+ document.body.appendChild(fileInput);
+ }
+ this.fileInput.dispatchEvent(clickEvent);
+ },
+ handleFile: function(e) {
+ var fileInput = e.target;
+ var editor = this.editorContext;
+ var embedIntent = this.embedIntent;
+
+ embedIntent.showLoading();
+ this.uploader.upload({
+ fileInput: fileInput,
+ complete: function(response, error) {
+ embedIntent.hideLoading();
+ if (error || !response || !response.url) {
+ return new Message().show(error.message || 'Error uploading image');
+ }
+ var imageModel = new ImageModel({ src: response.url });
+ var index = editor.getCurrentBlockIndex();
+ editor.insertBlockAt(imageModel, index);
+ editor.syncVisualAt(index);
+ }
+ });
+ fileInput.value = null; // reset file input
+ // TODO: client-side render while uploading
+ }
+};
+
+export default ImageCommand;
diff --git a/src/js/content-kit-editor/commands/italic.js b/src/js/content-kit-editor/commands/italic.js
new file mode 100644
index 000000000..d6881108b
--- /dev/null
+++ b/src/js/content-kit-editor/commands/italic.js
@@ -0,0 +1,14 @@
+import TextFormatCommand from './text-format';
+import { Tags } from '../constants';
+import { inherit } from '../../content-kit-utils/object-utils';
+
+function ItalicCommand() {
+ TextFormatCommand.call(this, {
+ name: 'italic',
+ tag: Tags.ITALIC,
+ button: ''
+ });
+}
+inherit(ItalicCommand, TextFormatCommand);
+
+export default ItalicCommand;
diff --git a/src/js/content-kit-editor/commands/link.js b/src/js/content-kit-editor/commands/link.js
new file mode 100644
index 000000000..3d0462bb8
--- /dev/null
+++ b/src/js/content-kit-editor/commands/link.js
@@ -0,0 +1,33 @@
+import TextFormatCommand from './text-format';
+import Prompt from '../views/prompt';
+import { Tags, RegEx } from '../constants';
+import { inherit } from '../../content-kit-utils/object-utils';
+import { getSelectionTagName } from '../utils/selection-utils';
+
+function LinkCommand() {
+ TextFormatCommand.call(this, {
+ name: 'link',
+ tag: Tags.LINK,
+ action: 'createLink',
+ removeAction: 'unlink',
+ button: '',
+ prompt: new Prompt({
+ command: this,
+ placeholder: 'Enter a url, press return...'
+ })
+ });
+}
+inherit(LinkCommand, TextFormatCommand);
+
+LinkCommand.prototype.exec = function(url) {
+ if(this.tag === getSelectionTagName()) {
+ this.unexec();
+ } else {
+ if (!RegEx.HTTP_PROTOCOL.test(url)) {
+ url = 'http://' + url;
+ }
+ LinkCommand._super.prototype.exec.call(this, url);
+ }
+};
+
+export default LinkCommand;
diff --git a/src/js/content-kit-editor/commands/list.js b/src/js/content-kit-editor/commands/list.js
new file mode 100644
index 000000000..e877e388a
--- /dev/null
+++ b/src/js/content-kit-editor/commands/list.js
@@ -0,0 +1,24 @@
+import TextFormatCommand from './text-format';
+import { inherit } from '../../content-kit-utils/object-utils';
+import { getSelectionBlockElement, selectNode } from '../utils/selection-utils';
+
+function ListCommand(options) {
+ TextFormatCommand.call(this, options);
+}
+inherit(ListCommand, TextFormatCommand);
+
+ListCommand.prototype.exec = function() {
+ ListCommand._super.prototype.exec.call(this);
+
+ // After creation, lists need to be unwrapped from the default formatter P tag
+ var listElement = getSelectionBlockElement();
+ var wrapperNode = listElement.parentNode;
+ if (wrapperNode.firstChild === listElement) {
+ var editorNode = wrapperNode.parentNode;
+ editorNode.insertBefore(listElement, wrapperNode);
+ editorNode.removeChild(wrapperNode);
+ selectNode(listElement);
+ }
+};
+
+export default ListCommand;
diff --git a/src/js/content-kit-editor/commands/ordered-list.js b/src/js/content-kit-editor/commands/ordered-list.js
new file mode 100644
index 000000000..c652544c7
--- /dev/null
+++ b/src/js/content-kit-editor/commands/ordered-list.js
@@ -0,0 +1,14 @@
+import ListCommand from './list';
+import { Tags } from '../constants';
+import { inherit } from '../../content-kit-utils/object-utils';
+
+function OrderedListCommand() {
+ ListCommand.call(this, {
+ name: 'ordered list',
+ tag: Tags.ORDERED_LIST,
+ action: 'insertOrderedList'
+ });
+}
+inherit(OrderedListCommand, ListCommand);
+
+export default OrderedListCommand;
diff --git a/src/js/content-kit-editor/commands/quote.js b/src/js/content-kit-editor/commands/quote.js
new file mode 100644
index 000000000..fc7720da8
--- /dev/null
+++ b/src/js/content-kit-editor/commands/quote.js
@@ -0,0 +1,14 @@
+import FormatBlockCommand from './format-block';
+import { Tags } from '../constants';
+import { inherit } from '../../content-kit-utils/object-utils';
+
+function QuoteCommand() {
+ FormatBlockCommand.call(this, {
+ name: 'quote',
+ tag: Tags.QUOTE,
+ button: ''
+ });
+}
+inherit(QuoteCommand, FormatBlockCommand);
+
+export default QuoteCommand;
diff --git a/src/js/content-kit-editor/commands/subheading.js b/src/js/content-kit-editor/commands/subheading.js
new file mode 100644
index 000000000..60b7cf2d6
--- /dev/null
+++ b/src/js/content-kit-editor/commands/subheading.js
@@ -0,0 +1,14 @@
+import FormatBlockCommand from './format-block';
+import { Tags } from '../constants';
+import { inherit } from '../../content-kit-utils/object-utils';
+
+function SubheadingCommand() {
+ FormatBlockCommand.call(this, {
+ name: 'subheading',
+ tag: Tags.SUBHEADING,
+ button: '2'
+ });
+}
+inherit(SubheadingCommand, FormatBlockCommand);
+
+export default SubheadingCommand;
diff --git a/src/js/content-kit-editor/commands/text-format.js b/src/js/content-kit-editor/commands/text-format.js
new file mode 100644
index 000000000..ce3a877ee
--- /dev/null
+++ b/src/js/content-kit-editor/commands/text-format.js
@@ -0,0 +1,21 @@
+import Command from './base';
+import { inherit } from '../../content-kit-utils/object-utils';
+
+function TextFormatCommand(options) {
+ Command.call(this, options);
+ this.tag = options.tag.toUpperCase();
+ this.action = options.action || this.name;
+ this.removeAction = options.removeAction || this.action;
+}
+inherit(TextFormatCommand, Command);
+
+TextFormatCommand.prototype = {
+ exec: function(value) {
+ document.execCommand(this.action, false, value || null);
+ },
+ unexec: function(value) {
+ document.execCommand(this.removeAction, false, value || null);
+ }
+};
+
+export default TextFormatCommand;
diff --git a/src/js/content-kit-editor/commands/unordered-list.js b/src/js/content-kit-editor/commands/unordered-list.js
new file mode 100644
index 000000000..5c4d4a630
--- /dev/null
+++ b/src/js/content-kit-editor/commands/unordered-list.js
@@ -0,0 +1,14 @@
+import ListCommand from './list';
+import { Tags } from '../constants';
+import { inherit } from '../../content-kit-utils/object-utils';
+
+function UnorderedListCommand() {
+ ListCommand.call(this, {
+ name: 'list',
+ tag: Tags.LIST,
+ action: 'insertUnorderedList'
+ });
+}
+inherit(UnorderedListCommand, ListCommand);
+
+export default UnorderedListCommand;
diff --git a/src/js/constants.js b/src/js/content-kit-editor/constants.js
similarity index 89%
rename from src/js/constants.js
rename to src/js/content-kit-editor/constants.js
index d11fc735e..d04be30b3 100644
--- a/src/js/constants.js
+++ b/src/js/content-kit-editor/constants.js
@@ -5,7 +5,7 @@ var Keycodes = {
DEL : 46
};
-var Regex = {
+var RegEx = {
NEWLINE : /[\r\n]/g,
HTTP_PROTOCOL : /^https?:\/\//i,
HEADING_TAG : /^(H1|H2|H3|H4|H5|H6)$/i,
@@ -39,3 +39,5 @@ var Tags = {
};
var RootTags = [ Tags.PARAGRAPH, Tags.HEADING, Tags.SUBHEADING, Tags.QUOTE, Tags.FIGURE, Tags.LIST, Tags.ORDERED_LIST ];
+
+export { Keycodes, RegEx, SelectionDirection, ToolbarDirection, Tags, RootTags };
diff --git a/src/js/content-kit-editor/editor-factory.js b/src/js/content-kit-editor/editor-factory.js
new file mode 100644
index 000000000..f96e20cb2
--- /dev/null
+++ b/src/js/content-kit-editor/editor-factory.js
@@ -0,0 +1,42 @@
+import Editor from './editor';
+import { TextFormatCommands, EmbedCommands } from './commands/commands';
+import { Tags } from './constants';
+import { merge } from '../content-kit-utils/object-utils';
+
+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
+ */
+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];
+ }
+
+ if (elements) {
+ options = merge(defaults, options);
+ elementsLen = elements.length;
+ for (i = 0; i < elementsLen; i++) {
+ editors.push(new Editor(elements[i], options));
+ }
+ }
+
+ return editors.length > 1 ? editors : editors[0];
+}
+
+export default EditorFactory;
diff --git a/src/js/content-kit-editor/editor.js b/src/js/content-kit-editor/editor.js
new file mode 100644
index 000000000..0cf289935
--- /dev/null
+++ b/src/js/content-kit-editor/editor.js
@@ -0,0 +1,224 @@
+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';
+
+var editorClassName = 'ck-editor';
+var editorClassNameRegExp = new RegExp(editorClassName);
+var afterRenderHooks = [];
+
+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 = '' + blockTag + '>';
+ for(i=0; i') {
+ embedIntent.showAt(blockElement);
+ } else {
+ embedIntent.hide();
+ }
+ }
+
+ rootElement.addEventListener('keyup', embedIntentHandler);
+
+ document.addEventListener('mouseup', function(e) {
+ setTimeout(function() {
+ if (!nodeIsDescendantOfElement(e.target, embedIntent.toolbar.element)) {
+ embedIntentHandler();
+ }
+ });
+ });
+
+ document.addEventListener('keyup', function(e) {
+ if (e.keyCode === Keycodes.ESC) {
+ embedIntent.hide();
+ }
+ });
+
+ window.addEventListener('resize', function() {
+ if(embedIntent.isShowing) {
+ positionElementToLeftOf(embedIntent.element, embedIntent.atNode);
+ if (embedIntent.toolbar.isShowing) {
+ embedIntent.toolbar.positionToContent(embedIntent.element);
+ }
+ }
+ });
+}
+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);
+};
+
+EmbedIntent.prototype.activate = function() {
+ if (!this.isActive) {
+ this.addClass('activated');
+ this.toolbar.show();
+ this.toolbar.positionToContent(this.element);
+ this.isActive = true;
+ }
+};
+
+EmbedIntent.prototype.deactivate = function() {
+ if (this.isActive) {
+ this.removeClass('activated');
+ this.toolbar.hide();
+ this.isActive = false;
+ }
+};
+
+// TODO: cleanup
+var loading = createDiv('div');
+loading.className = 'ck-embed-loading';
+loading.innerHTML = 'LOADING';
+
+EmbedIntent.prototype.showLoading = function() {
+ this.hide();
+ document.body.appendChild(loading);
+ positionElementCenteredIn(loading, this.atNode);
+};
+
+EmbedIntent.prototype.hideLoading = function() {
+ document.body.removeChild(loading);
+};
+
+
+export default EmbedIntent;
diff --git a/src/js/views/message.js b/src/js/content-kit-editor/views/message.js
similarity index 71%
rename from src/js/views/message.js
rename to src/js/content-kit-editor/views/message.js
index de1c07766..f3f4b4c3d 100644
--- a/src/js/views/message.js
+++ b/src/js/content-kit-editor/views/message.js
@@ -1,9 +1,12 @@
+import View from './view';
+import { inherit } from '../../content-kit-utils/object-utils';
+
function Message(options) {
options = options || {};
options.classNames = ['ck-message'];
View.call(this, options);
}
-inherits(Message, View);
+inherit(Message, View);
Message.prototype.show = function(message) {
var messageView = this;
@@ -13,3 +16,5 @@ Message.prototype.show = function(message) {
messageView.hide();
}, 3000);
};
+
+export default Message;
diff --git a/src/js/content-kit-editor/views/prompt.js b/src/js/content-kit-editor/views/prompt.js
new file mode 100644
index 000000000..69f945850
--- /dev/null
+++ b/src/js/content-kit-editor/views/prompt.js
@@ -0,0 +1,67 @@
+import View from './view';
+import { inherit } from '../../content-kit-utils/object-utils';
+import { inherit } from '../../content-kit-utils/object-utils';
+import { restoreRange } from '../utils/selection-utils';
+import { createDiv, positionElementToRect } from '../utils/element-utils';
+import { Keycodes } from '../constants';
+
+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);
+}
+
+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(); }
+ }
+ });
+
+ window.addEventListener('resize', function() {
+ var activeHilite = hiliter.parentNode;
+ var range = prompt.range;
+ if(activeHilite && range) {
+ positionHiliteRange(range);
+ }
+ });
+}
+inherit(Prompt, View);
+
+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; }
+ }
+};
+
+Prompt.prototype.hide = function() {
+ if (hiliter.parentNode) {
+ container.removeChild(hiliter);
+ }
+};
+
+export default Prompt;
diff --git a/src/js/content-kit-editor/views/text-format-toolbar.js b/src/js/content-kit-editor/views/text-format-toolbar.js
new file mode 100644
index 000000000..a08d205da
--- /dev/null
+++ b/src/js/content-kit-editor/views/text-format-toolbar.js
@@ -0,0 +1,41 @@
+import Toolbar from './toolbar';
+import { inherit } from '../../content-kit-utils/object-utils';
+import { selectionIsEditable, selectionIsInElement } from '../utils/selection-utils';
+import { Keycodes } from '../constants';
+
+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();
+ }
+ });
+
+ document.addEventListener('mouseup', function() {
+ setTimeout(function() { toolbar.handleTextSelection(); });
+ });
+
+ 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);
+
+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-editor/views/toolbar-button.js b/src/js/content-kit-editor/views/toolbar-button.js
new file mode 100644
index 000000000..1dc1d4126
--- /dev/null
+++ b/src/js/content-kit-editor/views/toolbar-button.js
@@ -0,0 +1,49 @@
+import Command from '../commands/base';
+
+var buttonClassName = 'ck-toolbar-btn';
+
+function ToolbarButton(options) {
+ var button = this;
+ var toolbar = options.toolbar;
+ var command = options.command;
+ var prompt = command.prompt;
+ var element = document.createElement('button');
+
+ if(typeof command === 'string') {
+ command = Command.index[command];
+ }
+
+ button.element = element;
+ button.command = command;
+ button.isActive = false;
+
+ 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();
+ }
+ });
+}
+
+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;
+ }
+ }
+};
+
+export default ToolbarButton;
diff --git a/src/js/content-kit-editor/views/toolbar.js b/src/js/content-kit-editor/views/toolbar.js
new file mode 100644
index 000000000..a91a8277c
--- /dev/null
+++ b/src/js/content-kit-editor/views/toolbar.js
@@ -0,0 +1,119 @@
+import View from './view';
+import ToolbarButton from './toolbar-button';
+import { inherit } from '../../content-kit-utils/object-utils';
+import { tagsInSelection } from '../utils/selection-utils';
+import { ToolbarDirection } from '../constants';
+import { createDiv, swapElements, positionElementToRightOf, positionElementCenteredAbove } from '../utils/element-utils';
+
+function updateButtonsForSelection(buttons, selection) {
+ var selectedTags = tagsInSelection(selection),
+ len = buttons.length,
+ i, button;
+
+ for (i = 0; i < len; i++) {
+ button = buttons[i];
+ if (selectedTags.indexOf(button.command.tag) > -1) {
+ button.setActive();
+ } else {
+ button.setInactive();
+ }
+ }
+}
+
+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');
+ }
+
+ View.call(toolbar, options);
+
+ toolbar.activePrompt = null;
+ toolbar.buttons = [];
+
+ toolbar.promptContainerElement = createDiv('ck-toolbar-prompt');
+ toolbar.buttonContainerElement = createDiv('ck-toolbar-buttons');
+ toolbar.element.appendChild(toolbar.promptContainerElement);
+ toolbar.element.appendChild(toolbar.buttonContainerElement);
+
+ for(i = 0; i < commandCount; i++) {
+ this.addCommand(commands[i]);
+ }
+
+ // Closes prompt if displayed when changing selection
+ document.addEventListener('mouseup', function() {
+ toolbar.dismissPrompt();
+ });
+}
+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();
+ }
+};
+
+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);
+};
+
+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;
+};
+
+Toolbar.prototype.dismissPrompt = function() {
+ var toolbar = this;
+ var activePrompt = toolbar.activePrompt;
+ if (activePrompt) {
+ activePrompt.hide();
+ swapElements(toolbar.buttonContainerElement, toolbar.promptContainerElement);
+ toolbar.activePrompt = null;
+ }
+};
+
+Toolbar.prototype.updateForSelection = function(selection) {
+ var toolbar = this;
+ if (selection.isCollapsed) {
+ toolbar.hide();
+ } else {
+ toolbar.show();
+ toolbar.positionToContent(selection.getRangeAt(0));
+ updateButtonsForSelection(toolbar.buttons, selection);
+ }
+};
+
+Toolbar.prototype.positionToContent = function(content) {
+ var directions = ToolbarDirection;
+ var positioningMethod;
+ switch(this.direction) {
+ case directions.RIGHT:
+ positioningMethod = positionElementToRightOf;
+ break;
+ default:
+ positioningMethod = positionElementCenteredAbove;
+ }
+ positioningMethod(this.element, content);
+};
+
+export default Toolbar;
diff --git a/src/js/views/tooltip.js b/src/js/content-kit-editor/views/tooltip.js
similarity index 82%
rename from src/js/views/tooltip.js
rename to src/js/content-kit-editor/views/tooltip.js
index 313841b70..392f2fb7e 100644
--- a/src/js/views/tooltip.js
+++ b/src/js/content-kit-editor/views/tooltip.js
@@ -1,3 +1,7 @@
+import View from './view';
+import { inherit } from '../../content-kit-utils/object-utils';
+import { positionElementCenteredBelow, getEventTargetMatchingTag } from '../utils/element-utils';
+
function Tooltip(options) {
var tooltip = this;
var rootElement = options.rootElement;
@@ -23,7 +27,7 @@ function Tooltip(options) {
}
});
}
-inherits(Tooltip, View);
+inherit(Tooltip, View);
Tooltip.prototype.showMessage = function(message, element) {
var tooltip = this;
@@ -37,3 +41,5 @@ Tooltip.prototype.showLink = function(link, element) {
var message = '' + link + '';
this.showMessage(message, element);
};
+
+export default Tooltip;
diff --git a/src/js/views/view.js b/src/js/content-kit-editor/views/view.js
similarity index 97%
rename from src/js/views/view.js
rename to src/js/content-kit-editor/views/view.js
index a788e5427..78db7d4e3 100644
--- a/src/js/views/view.js
+++ b/src/js/content-kit-editor/views/view.js
@@ -36,3 +36,5 @@ View.prototype = {
this.element.className = this.classNames.join(' ');
}
};
+
+export default View;
diff --git a/src/js/content-kit-utils/array-utils.js b/src/js/content-kit-utils/array-utils.js
new file mode 100644
index 000000000..08efc9a9a
--- /dev/null
+++ b/src/js/content-kit-utils/array-utils.js
@@ -0,0 +1,27 @@
+/**
+ * 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 toArray(obj) {
+ var array = [];
+ var i = obj && obj.length >>> 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;
+}
+
+export { toArray, sumSparseArray };
diff --git a/src/js/content-kit-utils/node-utils.js b/src/js/content-kit-utils/node-utils.js
new file mode 100644
index 000000000..5f9f1ac05
--- /dev/null
+++ b/src/js/content-kit-utils/node-utils.js
@@ -0,0 +1,70 @@
+import { sanitizeWhitespace } from './string-utils';
+import { toArray } from './array-utils';
+
+/**
+ * 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;
+}
+
+export { createElement, DOMParsingNode, textOfNode, unwrapNode, attributesForNode };
diff --git a/src/js/content-kit-utils/object-utils.js b/src/js/content-kit-utils/object-utils.js
new file mode 100644
index 000000000..0a04873e9
--- /dev/null
+++ b/src/js/content-kit-utils/object-utils.js
@@ -0,0 +1,49 @@
+/**
+ * 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;
+ }
+}
+
+export { mergeWithOptions, merge, inherit };
diff --git a/src/js/content-kit-utils/string-utils.js b/src/js/content-kit-utils/string-utils.js
new file mode 100644
index 000000000..ae170affe
--- /dev/null
+++ b/src/js/content-kit-utils/string-utils.js
@@ -0,0 +1,43 @@
+var RegExpTrim = /^\s+|\s+$/g;
+var RegExpTrimLeft = /^\s+/;
+var RegExpWSChars = /(\r\n|\n|\r|\t|\u00A0)/gm;
+var RegExpMultiWS = /\s+/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 ? (string + '').replace(/ /g, '_') : '';
+}
+
+/**
+ * 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);
+}
+
+export { trim, trimLeft, underscore, sanitizeWhitespace, injectIntoString};
diff --git a/src/js/content-kit.js b/src/js/content-kit.js
new file mode 100644
index 000000000..887c0fdba
--- /dev/null
+++ b/src/js/content-kit.js
@@ -0,0 +1,27 @@
+import Type from './content-kit-compiler/types/type';
+import BlockModel from './content-kit-compiler/models/block';
+import TextModel from './content-kit-compiler/models/text';
+import ImageModel from './content-kit-compiler/models/image';
+import EmbedModel from './content-kit-compiler/models/embed';
+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';
+
+/**
+ * @namespace ContentKit
+ * Merge public modules into the common ContentKit namespace.
+ * Handy for working in the browser with globals.
+ */
+var ContentKit = window.ContentKit || {};
+ContentKit.Type = Type;
+ContentKit.BlockModel = BlockModel;
+ContentKit.TextModel = TextModel;
+ContentKit.ImageModel = ImageModel;
+ContentKit.EmbedModel = EmbedModel;
+ContentKit.Compiler = Compiler;
+ContentKit.HTMLParser = HTMLParser;
+ContentKit.HTMLRenderer = HTMLRenderer;
+ContentKit.Editor = EditorFactory;
+
+export default ContentKit;
diff --git a/src/js/editor.js b/src/js/editor.js
deleted file mode 100644
index 633616d8d..000000000
--- a/src/js/editor.js
+++ /dev/null
@@ -1,250 +0,0 @@
-ContentKit.Editor = (function() {
-
- // Default `Editor` options
- var defaults = {
- defaultFormatter: Tags.PARAGRAPH,
- placeholder: 'Write here...',
- spellcheck: true,
- autofocus: true,
- textFormatCommands: TextFormatCommand.all,
- embedCommands: EmbedCommand.all
- };
-
- var editorClassName = 'ck-editor';
- var editorClassNameRegExp = new RegExp(editorClassName);
-
- /**
- * Publically expose this class which sets up indiviual `Editor` classes
- * depending if user passes string selector, Node, or NodeList
- */
- 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];
- }
-
- if (elements) {
- options = merge(defaults, options);
- elementsLen = elements.length;
- for (i = 0; i < elementsLen; i++) {
- editors.push(new Editor(elements[i], options));
- }
- }
-
- return editors.length > 1 ? editors : editors[0];
- }
-
- /**
- * @class Editor
- * An individual Editor
- * @param element `Element` node
- * @param options hash of options
- */
- function Editor(element, options) {
- var editor = this;
- merge(editor, options);
-
- if (element) {
- var className = element.className;
- var dataset = element.dataset;
-
- if (!editorClassNameRegExp.test(className)) {
- className += (className ? ' ' : '') + editorClassName;
- }
- element.className = className;
-
- if (!dataset.placeholder) {
- dataset.placeholder = editor.placeholder;
- }
- if(!editor.spellcheck) {
- element.spellcheck = false;
- }
-
- element.setAttribute('contentEditable', true);
- editor.element = element;
-
- var compiler = editor.compiler = options.compiler || new ContentKit.Compiler();
- editor.syncModel();
-
- bindTypingEvents(editor);
- bindPasteEvents(editor);
-
- editor.textFormatToolbar = new TextFormatToolbar({ rootElement: element, commands: editor.textFormatCommands });
- var linkTooltips = new Tooltip({ rootElement: element, showForTag: Tags.LINK });
-
- 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
- });
-
- if (editor.imageServiceUrl) {
- // TODO: lookup by name
- editor.embedCommands[0].uploader.url = editor.imageServiceUrl;
- }
- }
-
- if(editor.autofocus) { element.focus(); }
- }
- }
-
- Editor.prototype.syncModel = function() {
- this.model = this.compiler.parse(this.element.innerHTML);
- };
-
- Editor.prototype.syncModelAt = function(index) {
- var blockElements = toArray(this.element.children);
- var parsedBlockModel = this.compiler.parser.parseBlock(blockElements[index]);
- this.model[index] = parsedBlockModel;
- };
-
- 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;
- runAfterRenderHooks(element, blockModel);
- };
-
- Editor.prototype.getCurrentBlockIndex = function() {
- var selectionEl = getSelectionBlockElement();
- var blockElements = toArray(this.element.children);
- return blockElements.indexOf(selectionEl);
- };
-
- Editor.prototype.insertBlock = function(model) {
- this.insertBlockAt(model, this.getCurrentBlockIndex());
- };
-
- Editor.prototype.insertBlockAt = function(model, index) {
- model = model || new ContentKit.TextModel();
- this.model.splice(index, 0, model);
- };
-
- Editor.prototype.addTextFormat = function(opts) {
- var command = new TextFormatCommand(opts);
- this.compiler.registerMarkupType(new ContentKit.Type({
- name : opts.name,
- tag : opts.tag || opts.name
- }));
- this.textFormatCommands.push(command);
- this.textFormatToolbar.addCommand(command);
- };
-
- Editor.prototype.willRenderType = function(type, renderer) {
- this.compiler.renderer.willRenderType(type, renderer);
- };
-
- function bindTypingEvents(editor) {
- var editorEl = editor.element;
-
- // 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();
- }
- }
- });
-
- // Creates unordered list when block starts with '- ', or ordered if starts with '1. '
- editorEl.addEventListener('keyup', function(e) {
- var selectedText = window.getSelection().anchorNode.textContent,
- selection, selectionNode, 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;
- }
-
- if (command) {
- command.exec();
- selection = window.getSelection();
- selectionNode = selection.anchorNode;
- selectionNode.textContent = selectedText.replace(replaceRegex, '');
- moveCursorToBeginningOfSelection(selection);
- e.stopPropagation();
- }
- }
- });
-
- // 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);
- }
- });
-
- // Experimental: Live update - sync model with textual content as you type
- editorEl.addEventListener('keyup', function(e) {
- if (editor.model && editor.model.length) {
- var index = editor.getCurrentBlockIndex();
- if (editor.model[index].type === 1) {
- editor.syncModelAt(index);
- }
- }
- });
- }
-
- var afterRenderHooks = [];
- Editor.prototype.afterRender = function(callback) {
- if ('function' === typeof callback) {
- afterRenderHooks.push(callback);
- }
- };
-
- function runAfterRenderHooks(element, blockModel) {
- for (var i = 0, len = afterRenderHooks.length; i < len; i++) {
- afterRenderHooks[i].call(null, element, blockModel);
- }
- }
-
- function bindPasteEvents(editor) {
- editor.element.addEventListener('paste', function(e) {
- var data = e.clipboardData, plainText;
- e.preventDefault();
- if(data && data.getData) {
- plainText = data.getData('text/plain');
- var formattedContent = plainTextToBlocks(plainText, editor.defaultFormatter);
- document.execCommand('insertHTML', false, formattedContent);
- }
- });
- }
-
- 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 = '' + blockTag + '>';
- for(i=0; i (http://garthpoitras.com/)
- * @license MIT
- * Last modified: Aug 10, 2014
- */
-
-(function(window, document, define, undefined) {
-
-define("content-kit",
- ["./types/type","./models/block","./models/text","./models/image","./models/embed","./compiler","./parsers/html-parser","./renderers/html-renderer","exports"],
- function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __exports__) {
- "use strict";
- var Type = __dependency1__["default"];
- var BlockModel = __dependency2__["default"];
- var TextModel = __dependency3__["default"];
- var ImageModel = __dependency4__["default"];
- var EmbedModel = __dependency5__["default"];
- var Compiler = __dependency6__["default"];
- var HTMLParser = __dependency7__["default"];
- var HTMLRenderer = __dependency8__["default"];
-
- /**
- * @namespace ContentKit
- * Merge public modules into the common ContentKit namespace.
- * Handy for working in the browser with globals.
- */
- var ContentKit = window.ContentKit || {};
- ContentKit.Type = Type;
- ContentKit.BlockModel = BlockModel;
- ContentKit.TextModel = TextModel;
- ContentKit.ImageModel = ImageModel;
- ContentKit.EmbedModel = EmbedModel;
- ContentKit.Compiler = Compiler;
- ContentKit.HTMLParser = HTMLParser;
- ContentKit.HTMLRenderer = HTMLRenderer;
-
- __exports__["default"] = ContentKit;
- });
-define("compiler",
- ["./parsers/html-parser","./renderers/html-renderer","./types/type","./types/default-types","../utils/object-utils","exports"],
- function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __exports__) {
- "use strict";
- var HTMLParser = __dependency1__["default"];
- var HTMLRenderer = __dependency2__["default"];
- var Type = __dependency3__["default"];
- var DefaultBlockTypeSet = __dependency4__.DefaultBlockTypeSet;
- var DefaultMarkupTypeSet = __dependency4__.DefaultMarkupTypeSet;
- var merge = __dependency5__.merge;
-
- /**
- * @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
- };
- merge(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) {
- if (type instanceof Type) {
- return this.blockTypes.addType(type);
- }
- };
-
- /**
- * @method registerMarkupType
- * @param {Type} type
- */
- Compiler.prototype.registerMarkupType = function(type) {
- if (type instanceof Type) {
- return this.markupTypes.addType(type);
- }
- };
-
- __exports__["default"] = Compiler;
- });
-define("models/block",
- ["./model","../utils/object-utils","exports"],
- function(__dependency1__, __dependency2__, __exports__) {
- "use strict";
- var Model = __dependency1__["default"];
- var inherit = __dependency2__.inherit;
-
- /**
- * 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;
- });
- }
-
- /**
- * @class BlockModel
- * @constructor
- * @extends Model
- */
- function BlockModel(options) {
- options = options || {};
- Model.call(this, options);
- this.value = options.value || '';
- this.markup = sortBlockMarkups(options.markup || []);
- }
- inherit(BlockModel, Model);
-
- __exports__["default"] = BlockModel;
- });
-define("models/embed",
- ["../utils/object-utils","../models/model","../types/type","exports"],
- function(__dependency1__, __dependency2__, __dependency3__, __exports__) {
- "use strict";
- var inherit = __dependency1__.inherit;
- var Model = __dependency2__["default"];
- var Type = __dependency3__["default"];
-
- /**
- * @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: {}
- });
-
- 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;
- }
-
- if (embedHtml && embedType === 'rich') {
- attributes.html = embedHtml;
- }
- }
- inherit(Model, EmbedModel);
-
- __exports__["default"] = EmbedModel;
- });
-define("models/image",
- ["./block","../types/type","../utils/object-utils","exports"],
- function(__dependency1__, __dependency2__, __dependency3__, __exports__) {
- "use strict";
- var BlockModel = __dependency1__["default"];
- var Type = __dependency2__["default"];
- var inherit = __dependency3__.inherit;
-
- /**
- * @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 };
- }
- BlockModel.call(this, options);
- }
- inherit(ImageModel, BlockModel);
-
- __exports__["default"] = ImageModel;
- });
-define("models/markup",
- ["./model","../utils/object-utils","exports"],
- function(__dependency1__, __dependency2__, __exports__) {
- "use strict";
- var Model = __dependency1__["default"];
- var inherit = __dependency2__.inherit;
-
- /**
- * @class MarkupModel
- * @constructor
- * @extends Model
- */
- function MarkupModel(options) {
- options = options || {};
- Model.call(this, options);
- this.start = options.start || 0;
- this.end = options.end || 0;
- }
- inherit(MarkupModel, Model);
-
- __exports__["default"] = MarkupModel;
- });
-define("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.type = options.type || null;
- if (type_name) {
- this.type_name = type_name;
- }
- if (attributes) {
- this.attributes = attributes;
- }
- }
-
- __exports__["default"] = Model;
- });
-define("models/text",
- ["./block","../types/type","../utils/object-utils","exports"],
- function(__dependency1__, __dependency2__, __dependency3__, __exports__) {
- "use strict";
- var BlockModel = __dependency1__["default"];
- var Type = __dependency2__["default"];
- var inherit = __dependency3__.inherit;
-
- /**
- * @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(TextModel, BlockModel);
-
- __exports__["default"] = TextModel;
- });
-define("parsers/html-parser",
- ["../models/block","../models/markup","../types/default-types","../utils/object-utils","../utils/array-utils","../utils/string-utils","../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 merge = __dependency4__.merge;
- 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
- };
- merge(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("renderers/html-element-renderer",
- ["../utils/string-utils","../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 createOpeningTag(tagName, attributes, selfClosing /*,blacklist*/) {
- var tag = '<' + tagName;
- for (var attr in attributes) {
- if (attributes.hasOwnProperty(attr)) {
- //if (blacklist && attr in blacklist) { continue; }
- tag += ' ' + attr + '="' + attributes[attr] + '"';
- }
- }
- if (selfClosing) { tag += '/'; }
- tag += '>';
- return tag;
- }
-
- /**
- * Builds a closing html tag. i.e. ''
- */
- function createCloseTag(tagName) {
- return '' + tagName + '>';
- }
-
- /**
- * @class HTMLElementRenderer
- * @constructor
- */
- function HTMLElementRenderer(options) {
- options = options || {};
- this.type = options.type;
- this.markupTypes = options.markupTypes;
- }
-
- /**
- * @method render
- * @param model a block model
- * @return String html
- * Renders a block model into a HTML string.
- */
- HTMLElementRenderer.prototype.render = function(model) {
- var html = '';
- var type = this.type;
- var tagName = type.tag;
- var selfClosing = type.selfClosing;
-
- if (tagName) {
- html += createOpeningTag(tagName, model.attributes, selfClosing);
- }
- if (!selfClosing) {
- html += this.renderMarkup(model.value, model.markup);
- if (tagName) {
- html += createCloseTag(tagName);
- }
- }
- return html;
- };
-
- /**
- * @method renderMarkup
- * @param text plain text to apply markup to
- * @param markup an array of markup models
- * @return String html
- * Renders a markup model into a HTML string.
- */
- HTMLElementRenderer.prototype.renderMarkup = function(text, markups) {
- var parsedTagsIndexes = [],
- len = markups && markups.length, i;
-
- for (i = 0; i < len; i++) {
- var markup = markups[i],
- markupMeta = this.markupTypes.findById(markup.type),
- tagName = markupMeta.tag,
- selfClosing = markupMeta.selfClosing,
- start = markup.start,
- end = markup.end,
- openTag = createOpeningTag(tagName, markup.attributes, selfClosing),
- parsedTagLengthAtIndex = parsedTagsIndexes[start] || 0,
- parsedTagLengthBeforeIndex = sumSparseArray(parsedTagsIndexes.slice(0, start + 1));
-
- text = injectIntoString(text, openTag, start + parsedTagLengthBeforeIndex);
- parsedTagsIndexes[start] = parsedTagLengthAtIndex + openTag.length;
-
- if (!selfClosing) {
- var closeTag = createCloseTag(tagName);
- parsedTagLengthAtIndex = parsedTagsIndexes[end] || 0;
- parsedTagLengthBeforeIndex = sumSparseArray(parsedTagsIndexes.slice(0, end));
- text = injectIntoString(text, closeTag, end + parsedTagLengthBeforeIndex);
- parsedTagsIndexes[end] = parsedTagLengthAtIndex + closeTag.length;
- }
- }
-
- return text;
- };
-
- __exports__["default"] = HTMLElementRenderer;
- });
-define("renderers/html-embed-renderer",
- ["./embeds/youtube","./embeds/twitter","./embeds/instagram","exports"],
- function(__dependency1__, __dependency2__, __dependency3__, __exports__) {
- "use strict";
- var YouTubeRenderer = __dependency1__["default"];
- var TwitterRenderer = __dependency2__["default"];
- var InstagramRenderer = __dependency3__["default"];
-
- /**
- * A dictionary of supported embed services
- */
- var services = {
- YOUTUBE : {
- id: 1,
- renderer: new YouTubeRenderer()
- },
- TWITTER : {
- id: 2,
- renderer: new TwitterRenderer()
- },
- INSTAGRAM : {
- id: 3,
- renderer: new InstagramRenderer()
- }
- };
-
- /**
- * @class EmbedRenderer
- * @constructor
- */
- function EmbedRenderer() {}
-
- /**
- * @method render
- * @param model
- * @return String html
- */
- EmbedRenderer.prototype.render = function(model) {
- var renderer = this.rendererFor(model);
- if (renderer) {
- return renderer.render(model);
- }
- var attrs = model.attributes;
- return attrs && attrs.html || '';
- };
-
- /**
- * @method rendererFor
- * @param model
- * @return service renderer
- */
- EmbedRenderer.prototype.rendererFor = function(model) {
- var provider = model.attributes.provider_name;
- var providerKey = provider && provider.toUpperCase();
- var service = services[providerKey];
- return service && service.renderer;
- };
-
- __exports__["default"] = EmbedRenderer;
- });
-define("renderers/html-renderer",
- ["../types/type","./html-element-renderer","./html-embed-renderer","../types/default-types","../utils/object-utils","exports"],
- function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __exports__) {
- "use strict";
- var Type = __dependency1__["default"];
- var HTMLElementRenderer = __dependency2__["default"];
- var HTMLEmbedRenderer = __dependency3__["default"];
- var DefaultBlockTypeSet = __dependency4__.DefaultBlockTypeSet;
- var DefaultMarkupTypeSet = __dependency4__.DefaultMarkupTypeSet;
- var merge = __dependency5__.merge;
-
- /**
- * @class HTMLRenderer
- * @constructor
- */
- function HTMLRenderer(options) {
- var defaults = {
- blockTypes : DefaultBlockTypeSet,
- markupTypes : DefaultMarkupTypeSet
- };
- merge(this, defaults, options);
- }
-
- /**
- * @method willRenderType
- * @param type {Number|Type}
- * @param renderer the rendering function that returns a string of html
- * Registers custom rendering hooks for a type
- */
- var renderHooks = {};
- HTMLRenderer.prototype.willRenderType = function(type, renderer) {
- if ('number' !== typeof type) {
- type = type.id;
- }
- renderHooks[type] = renderer;
- };
-
- /**
- * @method rendererFor
- * @param model
- * @returns renderer
- * Returns an instance of a renderer for supplied model
- */
- HTMLRenderer.prototype.rendererFor = function(model) {
- var type = this.blockTypes.findById(model.type);
- if (type === Type.EMBED) {
- return new HTMLEmbedRenderer();
- }
- return new HTMLElementRenderer({ type: type, markupTypes: this.markupTypes });
- };
-
- /**
- * @method render
- * @param model
- * @return String html
- */
- HTMLRenderer.prototype.render = function(model) {
- var html = '';
- var len = model && model.length;
- var i, item, renderer, renderHook, itemHtml;
-
- for (i = 0; i < len; i++) {
- item = model[i];
- renderer = this.rendererFor(item);
- renderHook = renderHooks[item.type];
- itemHtml = renderHook ? renderHook.call(renderer, item) : renderer.render(item);
- if (itemHtml) { html += itemHtml; }
- }
- return html;
- };
-
- __exports__["default"] = HTMLRenderer;
- });
-define("types/default-types",
- ["./type-set","./type","exports"],
- function(__dependency1__, __dependency2__, __exports__) {
- "use strict";
- var TypeSet = __dependency1__["default"];
- var Type = __dependency2__["default"];
-
- /**
- * Default supported block types
- */
- var DefaultBlockTypeSet = new TypeSet([
- new Type({ tag: 'p', name: 'text' }),
- new Type({ tag: 'h2', name: 'heading' }),
- new Type({ tag: 'h3', name: 'subheading' }),
- new Type({ tag: 'img', name: 'image' }),
- new Type({ tag: 'blockquote', name: 'quote' }),
- new Type({ tag: 'ul', name: 'list' }),
- new Type({ tag: 'ol', name: 'ordered list' }),
- new Type({ name: 'embed' })
- ]);
-
- /**
- * Default supported markup types
- */
- var DefaultMarkupTypeSet = new TypeSet([
- new Type({ tag: 'b', name: 'bold' }),
- new Type({ tag: 'i', name: 'italic' }),
- new Type({ tag: 'u', name: 'underline' }),
- new Type({ tag: 'a', name: 'link' }),
- new Type({ tag: 'br', name: 'break' }),
- new Type({ tag: 'li', name: 'list item' }),
- new Type({ tag: 'sub', name: 'subscript' }),
- new Type({ tag: 'sup', name: 'superscript' })
- ]);
-
- __exports__.DefaultBlockTypeSet = DefaultBlockTypeSet;
- __exports__.DefaultMarkupTypeSet = DefaultMarkupTypeSet;
- });
-define("types/type-set",
- ["exports"],
- function(__exports__) {
- "use strict";
- /**
- * @class TypeSet
- * @private
- * @constructor
- * A Set of Types
- */
- function TypeSet(types) {
- var len = types && types.length, i;
-
- this._autoId = 1; // Auto-increment id counter
- this.idLookup = {}; // Hash cache for finding by id
- this.tagLookup = {}; // Hash cache for finding by tag
-
- for (i = 0; i < len; i++) {
- this.addType(types[i]);
- }
- }
-
- TypeSet.prototype = {
- /**
- * Adds a type to the set
- */
- addType: function(type) {
- this[type.name] = type;
- if (type.id === undefined) {
- type.id = this._autoId++;
- }
- this.idLookup[type.id] = type;
- if (type.tag) {
- this.tagLookup[type.tag] = type;
- }
- return type;
- },
-
- /**
- * Returns type info for a given Node
- */
- findByNode: function(node) {
- return this.findByTag(node.tagName);
- },
- /**
- * Returns type info for a given tag
- */
- findByTag: function(tag) {
- return this.tagLookup[tag.toLowerCase()];
- },
- /**
- * Returns type info for a given id
- */
- findById: function(id) {
- return this.idLookup[id];
- }
- };
-
- __exports__["default"] = TypeSet;
- });
-define("types/type",
- ["../utils/string-utils","exports"],
- function(__dependency1__, __exports__) {
- "use strict";
- var underscore = __dependency1__.underscore;
-
- /**
- * @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;
- }
- if (options.tag) {
- this.tag = options.tag.toLowerCase();
- this.selfClosing = /^(br|img|hr|meta|link|embed)$/i.test(this.tag);
- }
-
- // Register the type as constant
- Type[this.name] = this;
- }
- }
-
- __exports__["default"] = Type;
- });
-define("utils/array-utils",
- ["exports"],
- function(__exports__) {
- "use strict";
- /**
- * 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 toArray(obj) {
- var array = [],
- i = obj.length >>> 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' best for sparse arrays
- if (array.hasOwnProperty(i)) {
- sum += array[i];
- }
- }
- return sum;
- }
-
- __exports__.toArray = toArray;
- __exports__.sumSparseArray = sumSparseArray;
- });
-define("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,
- 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 it with its children
- */
- function unwrapNode(node) {
- var children = toArray(node.childNodes),
- len = children.length,
- 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,
- len = attrs && attrs.length,
- 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("utils/object-utils",
- ["exports"],
- function(__exports__) {
- "use strict";
- /**
- * Merges set of properties on a object
- * Useful for constructor defaults/options
- */
- function merge(object, defaults, updates) {
- updates = updates || {};
- for(var o in defaults) {
- if (defaults.hasOwnProperty(o)) {
- object[o] = updates[o] || defaults[o];
- }
- }
- }
-
- /**
- * Prototype inheritance helper
- */
- function inherit(Sub, Super) {
- for (var key in Super) {
- if (Super.hasOwnProperty(key)) {
- Sub[key] = Super[key];
- }
- }
- Sub.prototype = new Super();
- Sub.constructor = Sub;
- }
-
- __exports__.merge = merge;
- __exports__.inherit = inherit;
- });
-define("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;
-
- /**
- * 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 ? (string + '').replace(/ /g, '_') : '';
- }
-
- /**
- * 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("renderers/embeds/instagram",
- ["exports"],
- function(__exports__) {
- "use strict";
-
- function InstagramRenderer() {}
- InstagramRenderer.prototype.render = function(model) {
- return '';
- };
-
- __exports__["default"] = InstagramRenderer;
- });
-define("renderers/embeds/twitter",
- ["exports"],
- function(__exports__) {
- "use strict";
-
- function TwitterRenderer() {}
- TwitterRenderer.prototype.render = function(model) {
- return '';
- };
-
- __exports__["default"] = TwitterRenderer;
- });
-define("renderers/embeds/youtube",
- ["exports"],
- function(__exports__) {
- "use strict";
-
- var RegExVideoId = /.*(?:youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=)([^#\&\?]*).*/;
-
- function getVideoIdFromUrl(url) {
- var match = url.match(RegExVideoId);
- if (match && match[1].length === 11){
- return match[1];
- }
- return null;
- }
-
- function YouTubeRenderer() {}
- YouTubeRenderer.prototype.render = function(model) {
- var videoId = getVideoIdFromUrl(model.attributes.url);
- var embedUrl = 'http://www.youtube.com/embed/' + videoId + '?controls=2&showinfo=0&color=white&theme=light';
- return '';
- };
-
- __exports__["default"] = YouTubeRenderer;
- });
-}(this, document, define));
diff --git a/src/js/index.js b/src/js/index.js
deleted file mode 100644
index b88346861..000000000
--- a/src/js/index.js
+++ /dev/null
@@ -1,5 +0,0 @@
-/**
- * @namespace ContentKit
- */
-var ContentKit = exports.ContentKit || {};
-exports.ContentKit = ContentKit;
diff --git a/src/js/utils/array-utils.js b/src/js/utils/array-utils.js
deleted file mode 100644
index 1a6efc5bb..000000000
--- a/src/js/utils/array-utils.js
+++ /dev/null
@@ -1,11 +0,0 @@
-/**
- * Converts an array-like object (i.e. NodeList) to Array
- */
-function toArray(obj) {
- var array = [],
- i = obj.length >>> 0; // cast to Uint32
- while (i--) {
- array[i] = obj[i];
- }
- return array;
-}
diff --git a/src/js/utils/http-utils.js b/src/js/utils/http-utils.js
deleted file mode 100644
index caadef1ac..000000000
--- a/src/js/utils/http-utils.js
+++ /dev/null
@@ -1,33 +0,0 @@
-var HTTP = (function() {
-
- var head = document.head;
- var uuid = 0;
-
- return {
- get: function(url, callback) {
- var request = new XMLHttpRequest();
- request.onload = function() {
- callback(this.responseText);
- };
- request.onerror = function(error) {
- callback(null, error);
- };
- request.open('GET', url);
- request.send();
- },
-
- jsonp: function(url, callback) {
- var script = document.createElement('script');
- var name = '_jsonp_' + uuid++;
- url += ( url.match(/\?/) ? '&' : '?' ) + 'callback=' + name;
- script.src = url;
- exports[name] = function(response) {
- callback(JSON.parse(response));
- head.removeChild(script);
- delete exports[name];
- };
- head.appendChild(script);
- }
- };
-
-}());
diff --git a/src/js/utils/object-utils.js b/src/js/utils/object-utils.js
deleted file mode 100644
index 604a8b0fe..000000000
--- a/src/js/utils/object-utils.js
+++ /dev/null
@@ -1,21 +0,0 @@
-function merge(object, updates) {
- updates = updates || {};
- for(var o in updates) {
- if (updates.hasOwnProperty(o)) {
- object[o] = updates[o];
- }
- }
- return object;
-}
-
-function inherits(Subclass, Superclass) {
- Subclass._super = Superclass;
- Subclass.prototype = Object.create(Superclass.prototype, {
- constructor: {
- value: Subclass,
- enumerable: false,
- writable: true,
- configurable: true
- }
- });
-}
diff --git a/src/js/views/embed-intent.js b/src/js/views/embed-intent.js
deleted file mode 100644
index 46e761ac8..000000000
--- a/src/js/views/embed-intent.js
+++ /dev/null
@@ -1,106 +0,0 @@
-var EmbedIntent = (function() {
-
- 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.element.title = 'Insert image or embed...';
- embedIntent.element.addEventListener('mouseup', function(e) {
- if (embedIntent.isActive) {
- embedIntent.deactivate();
- } else {
- embedIntent.activate();
- }
- e.stopPropagation();
- });
-
- embedIntent.toolbar = new Toolbar({ embedIntent: embedIntent, editor: embedIntent.editorContext, commands: options.commands, direction: ToolbarDirection.RIGHT });
- embedIntent.isActive = false;
-
- function embedIntentHandler() {
- var blockElement = getSelectionBlockElement();
- var blockElementContent = blockElement && blockElement.innerHTML;
- if (blockElementContent === '' || blockElementContent === '
') {
- embedIntent.showAt(blockElement);
- } else {
- embedIntent.hide();
- }
- }
-
- rootElement.addEventListener('keyup', embedIntentHandler);
-
- document.addEventListener('mouseup', function(e) {
- setTimeout(function() {
- if (!nodeIsDescendantOfElement(e.target, embedIntent.toolbar.element)) {
- embedIntentHandler();
- }
- });
- });
-
- document.addEventListener('keyup', function(e) {
- if (e.keyCode === Keycodes.ESC) {
- embedIntent.hide();
- }
- });
-
- window.addEventListener('resize', function() {
- if(embedIntent.isShowing) {
- positionElementToLeftOf(embedIntent.element, embedIntent.atNode);
- if (embedIntent.toolbar.isShowing) {
- embedIntent.toolbar.positionToContent(embedIntent.element);
- }
- }
- });
- }
- inherits(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);
- };
-
- EmbedIntent.prototype.activate = function() {
- if (!this.isActive) {
- this.addClass('activated');
- this.toolbar.show();
- this.toolbar.positionToContent(this.element);
- this.isActive = true;
- }
- };
-
- EmbedIntent.prototype.deactivate = function() {
- if (this.isActive) {
- this.removeClass('activated');
- this.toolbar.hide();
- this.isActive = false;
- }
- };
-
- var loading = createDiv('div');
- loading.className = 'ck-embed-loading';
- loading.innerHTML = 'LOADING';
- EmbedIntent.prototype.showLoading = function() {
- this.hide();
- document.body.appendChild(loading);
- positionElementCenteredIn(loading, this.atNode);
- };
-
- EmbedIntent.prototype.hideLoading = function() {
- document.body.removeChild(loading);
- };
-
-
- return EmbedIntent;
-}());
diff --git a/src/js/views/prompt.js b/src/js/views/prompt.js
deleted file mode 100644
index 7207505b1..000000000
--- a/src/js/views/prompt.js
+++ /dev/null
@@ -1,63 +0,0 @@
-var Prompt = (function() {
-
- var container = document.body;
- var hiliter = createDiv('ck-editor-hilite');
-
- 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(); }
- }
- });
-
- window.addEventListener('resize', function() {
- var activeHilite = hiliter.parentNode;
- var range = prompt.range;
- if(activeHilite && range) {
- positionHiliteRange(range);
- }
- });
- }
- inherits(Prompt, View);
-
- 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; }
- }
- };
-
- Prompt.prototype.hide = function() {
- if (hiliter.parentNode) {
- container.removeChild(hiliter);
- }
- };
-
- function positionHiliteRange(range) {
- var rect = range.getBoundingClientRect();
- var style = hiliter.style;
- style.width = rect.width + 'px';
- style.height = rect.height + 'px';
- positionElementToRect(hiliter, rect);
- }
-
- return Prompt;
-}());
diff --git a/src/js/views/toolbar-button.js b/src/js/views/toolbar-button.js
deleted file mode 100644
index 791a8a343..000000000
--- a/src/js/views/toolbar-button.js
+++ /dev/null
@@ -1,50 +0,0 @@
-var ToolbarButton = (function() {
-
- var buttonClassName = 'ck-toolbar-btn';
-
- function ToolbarButton(options) {
- var button = this;
- var toolbar = options.toolbar;
- var command = options.command;
- var prompt = command.prompt;
- var element = document.createElement('button');
-
- if(typeof command === 'string') {
- command = Command.index[command];
- }
-
- button.element = element;
- button.command = command;
- button.isActive = false;
-
- 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();
- }
- });
- }
-
- 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;
- }
- }
- };
-
- return ToolbarButton;
-}());
diff --git a/src/js/views/toolbar.js b/src/js/views/toolbar.js
deleted file mode 100644
index 560385e06..000000000
--- a/src/js/views/toolbar.js
+++ /dev/null
@@ -1,156 +0,0 @@
-var Toolbar = (function() {
-
- 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');
- }
-
- View.call(toolbar, options);
-
- toolbar.activePrompt = null;
- toolbar.buttons = [];
-
- toolbar.promptContainerElement = createDiv('ck-toolbar-prompt');
- toolbar.buttonContainerElement = createDiv('ck-toolbar-buttons');
- toolbar.element.appendChild(toolbar.promptContainerElement);
- toolbar.element.appendChild(toolbar.buttonContainerElement);
-
- for(i = 0; i < commandCount; i++) {
- this.addCommand(commands[i]);
- }
-
- // Closes prompt if displayed when changing selection
- document.addEventListener('mouseup', function() {
- toolbar.dismissPrompt();
- });
- }
- inherits(Toolbar, View);
-
- Toolbar.prototype.hide = function() {
- if (Toolbar._super.prototype.hide.call(this)) {
- var style = this.element.style;
- style.left = '';
- style.top = '';
- this.dismissPrompt();
- }
- };
-
- 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);
- };
-
- 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;
- };
-
- Toolbar.prototype.dismissPrompt = function() {
- var toolbar = this;
- var activePrompt = toolbar.activePrompt;
- if (activePrompt) {
- activePrompt.hide();
- swapElements(toolbar.buttonContainerElement, toolbar.promptContainerElement);
- toolbar.activePrompt = null;
- }
- };
-
- Toolbar.prototype.updateForSelection = function(selection) {
- var toolbar = this;
- if (selection.isCollapsed) {
- toolbar.hide();
- } else {
- toolbar.show();
- toolbar.positionToContent(selection.getRangeAt(0));
- updateButtonsForSelection(toolbar.buttons, selection);
- }
- };
-
- Toolbar.prototype.positionToContent = function(content) {
- var directions = ToolbarDirection;
- var positioningMethod;
- switch(this.direction) {
- case directions.RIGHT:
- positioningMethod = positionElementToRightOf;
- break;
- default:
- positioningMethod = positionElementCenteredAbove;
- }
- positioningMethod(this.element, content);
- };
-
- function updateButtonsForSelection(buttons, selection) {
- var selectedTags = tagsInSelection(selection),
- len = buttons.length,
- i, button;
-
- for (i = 0; i < len; i++) {
- button = buttons[i];
- if (selectedTags.indexOf(button.command.tag) > -1) {
- button.setActive();
- } else {
- button.setInactive();
- }
- }
- }
-
- return Toolbar;
-}());
-
-
-var TextFormatToolbar = (function() {
-
- 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();
- }
- });
-
- document.addEventListener('mouseup', function() {
- setTimeout(function() { toolbar.handleTextSelection(); });
- });
-
- window.addEventListener('resize', function() {
- if(toolbar.isShowing) {
- var activePromptRange = toolbar.activePrompt && toolbar.activePrompt.range;
- toolbar.positionToContent(activePromptRange ? activePromptRange : window.getSelection().getRangeAt(0));
- }
- });
- }
- inherits(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);
- }
- };
-
- return TextFormatToolbar;
-}());