diff --git a/demo/demo.css b/demo/demo.css index 89ec43202..c8ed63494 100644 --- a/demo/demo.css +++ b/demo/demo.css @@ -7,9 +7,8 @@ body { font-family: "Helvetica Neue", Helvetica, sans-serif; color: #333; - margin: 0 1.45em 3em; + margin: 0; padding: 0; - overflow-y: scroll; } @media only screen and (max-width: 767px) { body { @@ -17,9 +16,15 @@ body { } } -.wrapper { +.editor-pane { max-width: 50em; - margin: 6.7em auto 1em; + margin: 0 auto; + padding: 5.5em 1.45em 3em; + width: 100%; +} +.code-pane-open .editor-pane { + width: 50%; + margin: 0; } header { @@ -74,23 +79,30 @@ header { outline: none; } -#code-panes { - display: none; -} -.code-pane { - position: absolute; + +.code-panes { + position: fixed; top: 3.125em; bottom: 0; + right: -50%; + width: 0; +} +.code-pane-open .code-panes { right: 0; width: 50%; - border-left: 1px dotted #c0c5ce; +} +.code-pane { + display: none; + position: absolute; + top: 0; + bottom: 0; + left: 0; + width: 100%; background-color: #2b303b; overflow: hidden; } .code-pane:first-child { - left: 0; - right: auto; - border-left: none; + display: block; } .code-pane code { white-space: pre-wrap; diff --git a/demo/demo.js b/demo/demo.js index 3190c761e..dadff3ec6 100644 --- a/demo/demo.js +++ b/demo/demo.js @@ -3,29 +3,33 @@ 'use strict'; exports.ContentKitDemo = { - toggleCode: function(e, button, editor) { - var codeUI = document.getElementById('code-panes'), - editorUI = editor.element; - - if(codeUI.style.display === '') { - var codePaneJSON = document.getElementById('code-json'), - codePaneHTML = document.getElementById('code-html'), - json = editor.model, - html = editor.compiler.render(json); + toggleCodePane: function(editor) { + if(document.body.className === 'code-pane-open') { + this.closeCodePane(); + } else { + this.openCodePane(editor); + } + }, + + openCodePane: function(editor) { + this.syncCodePane(editor); + window.getSelection().removeAllRanges(); + document.body.className = 'code-pane-open'; + }, - codePaneJSON.innerHTML = this.syntaxHighlight(json); - codePaneHTML.textContent = this.formatXML(html); + closeCodePane: function() { + window.getSelection().removeAllRanges(); + document.body.className = ''; + }, - window.getSelection().removeAllRanges(); + syncCodePane: function(editor) { + var codePaneJSON = document.getElementById('code-json'); + var codePaneHTML = document.getElementById('code-html'); + var json = editor.model; + var html = editor.compiler.render(json); - codeUI.style.display = 'block'; - editorUI.style.display = 'none'; - button.textContent = 'Show Editor'; - } else { - codeUI.style.display = ''; - editorUI.style.display = 'block'; - button.textContent = 'Show Code'; - } + codePaneJSON.innerHTML = this.syntaxHighlight(json); + codePaneHTML.textContent = this.formatXML(html); }, formatXML: function(xml) { diff --git a/demo/index.html b/demo/index.html index 165258bf6..66e072abc 100644 --- a/demo/index.html +++ b/demo/index.html @@ -16,11 +16,13 @@

ContentKit Editor

- +
-
+
+ +

A modern, minimalist text editor allowing you to write in a distraction free environment. Simply select any text you would like to format and a toolbar will appear where you can toggle options such as bold and italic, or create a link.

Create headings by pressing "H1" on the toolbar

@@ -53,9 +55,10 @@

Keyboard shortcuts:

Enjoy focusing on your content and not worrying about formatting!

+
-
+
@@ -72,58 +75,10 @@

Keyboard shortcuts:

diff --git a/dist/content-kit-editor.css b/dist/content-kit-editor.css index cdf0673aa..4a06d080c 100755 --- a/dist/content-kit-editor.css +++ b/dist/content-kit-editor.css @@ -285,10 +285,11 @@ .ck-embed figcaption { position: absolute; top: 0; - right: -140px; - width: 120px; + right: -130px; + width: 130px; text-align: left; margin: 0; + padding-left: 2em; } } .ck-video-container { diff --git a/dist/content-kit-editor.js b/dist/content-kit-editor.js index e540a3d24..d0a1087f2 100755 --- a/dist/content-kit-editor.js +++ b/dist/content-kit-editor.js @@ -3,7 +3,7 @@ * @version 0.1.0 * @author Garth Poitras (http://garthpoitras.com/) * @license MIT - * Last modified: Aug 14, 2014 + * Last modified: Aug 22, 2014 */ (function(exports, document) { @@ -40,15 +40,14 @@ define("content-kit", __exports__["default"] = ContentKit; }); 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__) { + ["./parsers/html-parser","./renderers/html-renderer","./types/default-types","../content-kit-utils/object-utils","exports"], + function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __exports__) { "use strict"; var HTMLParser = __dependency1__["default"]; var HTMLRenderer = __dependency2__["default"]; - var Type = __dependency3__["default"]; - var DefaultBlockTypeSet = __dependency4__.DefaultBlockTypeSet; - var DefaultMarkupTypeSet = __dependency4__.DefaultMarkupTypeSet; - var mergeWithOptions = __dependency5__.mergeWithOptions; + var DefaultBlockTypeSet = __dependency3__.DefaultBlockTypeSet; + var DefaultMarkupTypeSet = __dependency3__.DefaultMarkupTypeSet; + var mergeWithOptions = __dependency4__.mergeWithOptions; /** * @class Compiler @@ -96,9 +95,7 @@ define("content-kit-compiler/compiler", * @param {Type} type */ Compiler.prototype.registerBlockType = function(type) { - if (type instanceof Type) { - return this.blockTypes.addType(type); - } + return this.blockTypes.addType(type); }; /** @@ -106,9 +103,7 @@ define("content-kit-compiler/compiler", * @param {Type} type */ Compiler.prototype.registerMarkupType = function(type) { - if (type instanceof Type) { - return this.markupTypes.addType(type); - } + return this.markupTypes.addType(type); }; __exports__["default"] = Compiler; @@ -214,9 +209,55 @@ define("content-kit-editor/editor-factory", __exports__["default"] = EditorFactory; }); +define("content-kit-editor/editor-html-renderer", + ["../content-kit-compiler/renderers/html-renderer","../content-kit-compiler/types/type","../content-kit-utils/object-utils","exports"], + function(__dependency1__, __dependency2__, __dependency3__, __exports__) { + "use strict"; + var HTMLRenderer = __dependency1__["default"]; + var Type = __dependency2__["default"]; + var inherit = __dependency3__.inherit; + + function embedRenderer(model) { + var embedAttrs = model.attributes; + var isVideo = embedAttrs.embed_type === 'video'; + return '
' + + '
' + + (isVideo ? '
' : '') + this.render(model) + (isVideo ? '
' : '') + + '
' + embedAttrs.provider_name + ': ' + + '' + embedAttrs.title + '' + + '
' + + '
' + + '
'; + } + + function imageRenderer(model) { + return '
' + + '
' + this.render(model) + '
' + + '
'; + } + + var typeRenderers = {}; + typeRenderers[Type.EMBED.id] = embedRenderer; + typeRenderers[Type.IMAGE.id] = imageRenderer; + + /** + * @class EditorHTMLRenderer + * @constructor + * Subclass of HTMLRenderer specifically for the Editor + * Wraps interactive elements to add functionality + */ + function EditorHTMLRenderer() { + HTMLRenderer.call(this, { + typeRenderers: typeRenderers + }); + } + inherit(EditorHTMLRenderer, HTMLRenderer); + + __exports__["default"] = EditorHTMLRenderer; + }); define("content-kit-editor/editor", - ["./views/text-format-toolbar","./views/tooltip","./views/embed-intent","./commands/unordered-list","./commands/ordered-list","./commands/text-format","./constants","./utils/selection-utils","../content-kit-compiler/compiler","../content-kit-compiler/models/text","../content-kit-compiler/types/type","../content-kit-utils/array-utils","../content-kit-utils/object-utils","exports"], - function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __dependency9__, __dependency10__, __dependency11__, __dependency12__, __dependency13__, __exports__) { + ["./views/text-format-toolbar","./views/tooltip","./views/embed-intent","./commands/unordered-list","./commands/ordered-list","./commands/text-format","./constants","./utils/selection-utils","../content-kit-compiler/compiler","../content-kit-compiler/models/text","../content-kit-compiler/types/type","../content-kit-utils/array-utils","../content-kit-utils/object-utils","./editor-html-renderer","exports"], + function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __dependency9__, __dependency10__, __dependency11__, __dependency12__, __dependency13__, __dependency14__, __exports__) { "use strict"; var TextFormatToolbar = __dependency1__["default"]; var Tooltip = __dependency2__["default"]; @@ -237,10 +278,10 @@ define("content-kit-editor/editor", var Type = __dependency11__["default"]; var toArray = __dependency12__.toArray; var merge = __dependency13__.merge; + var EditorHTMLRenderer = __dependency14__["default"]; var editorClassName = 'ck-editor'; var editorClassNameRegExp = new RegExp(editorClassName); - var afterRenderHooks = []; function plainTextToBlocks(plainText, blockTag) { var blocks = plainText.split(RegEx.NEWLINE), @@ -330,12 +371,6 @@ define("content-kit-editor/editor", }); } - function runAfterRenderHooks(element, blockModel) { - for (var i = 0, len = afterRenderHooks.length; i < len; i++) { - afterRenderHooks[i].call(null, element, blockModel); - } - } - /** * @class Editor * An individual Editor @@ -365,7 +400,10 @@ define("content-kit-editor/editor", element.setAttribute('contentEditable', true); editor.element = element; - var compiler = editor.compiler = options.compiler || new Compiler(); + var compiler = editor.compiler = options.compiler || new Compiler({ + includeTypeNames: true, // output type names for easier debugging + renderer: new EditorHTMLRenderer() + }); editor.syncModel(); bindTypingEvents(editor); @@ -413,7 +451,6 @@ define("content-kit-editor/editor", var blockElements = toArray(this.element.children); var element = blockElements[index]; element.innerHTML = html; - runAfterRenderHooks(element, blockModel); }; Editor.prototype.getCurrentBlockIndex = function() { @@ -441,16 +478,6 @@ define("content-kit-editor/editor", this.textFormatToolbar.addCommand(command); }; - Editor.prototype.willRenderType = function(type, renderer) { - this.compiler.renderer.willRenderType(type, renderer); - }; - - Editor.prototype.afterRender = function(callback) { - if ('function' === typeof callback) { - afterRenderHooks.push(callback); - } - }; - __exports__["default"] = Editor; }); define("content-kit-utils/array-utils", @@ -928,11 +955,10 @@ define("ext/loader", })(); }); define("content-kit-compiler/models/block", - ["./model","../../content-kit-utils/object-utils","exports"], - function(__dependency1__, __dependency2__, __exports__) { + ["./model","exports"], + function(__dependency1__, __exports__) { "use strict"; var Model = __dependency1__["default"]; - var inherit = __dependency2__.inherit; /** * Ensures block markups at the same index are always in a specific order. @@ -959,17 +985,15 @@ define("content-kit-compiler/models/block", 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__) { + ["../models/model","../types/type","exports"], + function(__dependency1__, __dependency2__, __exports__) { "use strict"; - var inherit = __dependency1__.inherit; - var Model = __dependency2__["default"]; - var Type = __dependency3__["default"]; + var Model = __dependency1__["default"]; + var Type = __dependency2__["default"]; /** * @class EmbedModel @@ -1009,17 +1033,15 @@ define("content-kit-compiler/models/embed", 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__) { + ["./block","../types/type","exports"], + function(__dependency1__, __dependency2__, __exports__) { "use strict"; var BlockModel = __dependency1__["default"]; var Type = __dependency2__["default"]; - var inherit = __dependency3__.inherit; /** * @class ImageModel @@ -1036,16 +1058,14 @@ define("content-kit-compiler/models/image", } 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__) { + ["./model","exports"], + function(__dependency1__, __exports__) { "use strict"; var Model = __dependency1__["default"]; - var inherit = __dependency2__.inherit; /** * @class MarkupModel @@ -1058,7 +1078,6 @@ define("content-kit-compiler/models/markup", this.start = options.start || 0; this.end = options.end || 0; } - inherit(MarkupModel, Model); __exports__["default"] = MarkupModel; }); @@ -1088,12 +1107,11 @@ define("content-kit-compiler/models/model", __exports__["default"] = Model; }); define("content-kit-compiler/models/text", - ["./block","../types/type","../../content-kit-utils/object-utils","exports"], - function(__dependency1__, __dependency2__, __dependency3__, __exports__) { + ["./block","../types/type","exports"], + function(__dependency1__, __dependency2__, __exports__) { "use strict"; var BlockModel = __dependency1__["default"]; var Type = __dependency2__["default"]; - var inherit = __dependency3__.inherit; /** * @class TextModel @@ -1107,7 +1125,6 @@ define("content-kit-compiler/models/text", options.type_name = Type.TEXT.name; BlockModel.call(this, options); } - inherit(TextModel, BlockModel); __exports__["default"] = TextModel; }); @@ -1476,7 +1493,8 @@ define("content-kit-compiler/renderers/html-renderer", function HTMLRenderer(options) { var defaults = { blockTypes : DefaultBlockTypeSet, - markupTypes : DefaultMarkupTypeSet + markupTypes : DefaultMarkupTypeSet, + typeRenderers : {} }; mergeWithOptions(this, defaults, options); } @@ -1487,12 +1505,11 @@ define("content-kit-compiler/renderers/html-renderer", * @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; + this.typeRenderers[type] = renderer; }; /** @@ -1522,7 +1539,7 @@ define("content-kit-compiler/renderers/html-renderer", for (i = 0; i < len; i++) { item = model[i]; renderer = this.rendererFor(item); - renderHook = renderHooks[item.type]; + renderHook = this.typeRenderers[item.type]; itemHtml = renderHook ? renderHook.call(renderer, item) : renderer.render(item); if (itemHtml) { html += itemHtml; } } @@ -1570,9 +1587,11 @@ define("content-kit-compiler/types/default-types", __exports__.DefaultMarkupTypeSet = DefaultMarkupTypeSet; }); define("content-kit-compiler/types/type-set", - ["exports"], - function(__exports__) { + ["./type","exports"], + function(__dependency1__, __exports__) { "use strict"; + var Type = __dependency1__["default"]; + /** * @class TypeSet * @private @@ -1596,15 +1615,17 @@ define("content-kit-compiler/types/type-set", * 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; + if (type instanceof 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; } - return type; }, /** @@ -1762,6 +1783,17 @@ define("content-kit-editor/commands/embed", var RegEx = __dependency6__.RegEx; var OEmbedder = __dependency7__.OEmbedder; + function loadTwitterWidgets(element) { + if (window.twttr) { + window.twttr.widgets.load(element); + } else { + var script = document.createElement('script'); + script.async = true; + script.src = 'http://platform.twitter.com/widgets.js'; + document.head.appendChild(script); + } + } + function EmbedCommand(options) { Command.call(this, { name: 'embed', @@ -1779,21 +1811,30 @@ define("content-kit-editor/commands/embed", EmbedCommand.prototype.exec = function(url) { var command = this; var editorContext = command.editorContext; + var embedIntent = command.embedIntent; var index = editorContext.getCurrentBlockIndex(); - command.embedIntent.showLoading(); + embedIntent.showLoading(); this.embedService.fetch({ url: url, complete: function(response, error) { - command.embedIntent.hideLoading(); - + embedIntent.hideLoading(); if (error) { - new Message().show('Embed error'); + var errorMsg = error; + if (error.target && error.target.status === 0) { + errorMsg = 'Could not connect to embed service'; + } else if (typeof error !== 'string') { + errorMsg = 'Embed error'; + } + new Message().show(errorMsg); } else { var embedModel = new EmbedModel(response); editorContext.insertBlockAt(embedModel, index); editorContext.syncVisualAt(index); + if (embedModel.attributes.provider_name.toLowerCase() === 'twitter') { + loadTwitterWidgets(editorContext.element); + } } } }); @@ -1868,6 +1909,17 @@ define("content-kit-editor/commands/image", var inherit = __dependency4__.inherit; var FileUploader = __dependency5__.FileUploader; + function createFileInput(command) { + var fileInput = document.createElement('input'); + fileInput.type = 'file'; + fileInput.accept = 'image/*'; + fileInput.className = 'ck-file-input'; + fileInput.addEventListener('change', function(e) { + command.handleFile(e); + }); + return fileInput; + } + function ImageCommand(options) { Command.call(this, { name: 'image', @@ -1880,19 +1932,12 @@ define("content-kit-editor/commands/image", 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); - }); + var fileInput = this.fileInput; + if (!fileInput) { + fileInput = this.fileInput = createFileInput(this); document.body.appendChild(fileInput); } - this.fileInput.dispatchEvent(clickEvent); + fileInput.dispatchEvent(new MouseEvent('click', { bubbles: false })); }, handleFile: function(e) { var fileInput = e.target; @@ -2303,7 +2348,7 @@ define("content-kit-editor/utils/selection-utils", function selectionIsEditable(selection) { var el = getSelectionBlockElement(selection); - return el.contentEditable !== 'false'; + return el.isContentEditable; } /* @@ -2831,7 +2876,7 @@ define("content-kit-editor/views/tooltip", rootElement.addEventListener('mouseover', function(e) { var target = getEventTargetMatchingTag(options.showForTag, e.target, rootElement); - if (target) { + if (target && target.isContentEditable) { timeout = setTimeout(function() { tooltip.showLink(target.href, target); }, delay); @@ -2920,6 +2965,30 @@ define("content-kit-compiler/renderers/embeds/instagram", __exports__["default"] = InstagramRenderer; }); +define("content-kit-compiler/renderers/embeds/link", + ["exports"], + function(__exports__) { + "use strict"; + + function LinkEmbedRenderer() {} + LinkEmbedRenderer.prototype.render = function(model) { + return ''; + }; + + __exports__["default"] = LinkEmbedRenderer; + }); +define("content-kit-compiler/renderers/embeds/photo", + ["exports"], + function(__exports__) { + "use strict"; + + function PhotoEmbedRenderer() {} + PhotoEmbedRenderer.prototype.render = function(model) { + return ''; + }; + + __exports__["default"] = PhotoEmbedRenderer; + }); define("content-kit-compiler/renderers/embeds/twitter", ["exports"], function(__exports__) { @@ -2940,7 +3009,7 @@ define("content-kit-compiler/renderers/embeds/youtube", var RegExVideoId = /.*(?:youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=)([^#\&\?]*).*/; function getVideoIdFromUrl(url) { - var match = url.match(RegExVideoId); + var match = url && url.match(RegExVideoId); if (match && match[1].length === 11){ return match[1]; } diff --git a/gulpfile.js b/gulpfile.js index 5b153b56a..cb9f7f77a 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -107,7 +107,7 @@ gulp.task('clean', function() { // Watches when js files change and automatically lints/builds gulp.task('watch-js', function() { - gulp.watch(jsSrc.concat(jsExtSrc), ['lint', 'build-js']); + gulp.watch(jsSrc, ['lint', 'build-js']); }); // Watches when css files change and automatically builds diff --git a/src/css/embeds.less b/src/css/embeds.less index 64270ed71..f76dfd8c0 100644 --- a/src/css/embeds.less +++ b/src/css/embeds.less @@ -90,10 +90,11 @@ .ck-embed figcaption { position: absolute; top: 0; - right: -140px; - width: 120px; + right: -130px; + width: 130px; text-align: left; margin: 0; + padding-left: 2em; } } diff --git a/src/js/content-kit-compiler/compiler.js b/src/js/content-kit-compiler/compiler.js index 348898681..b2d6fc0bc 100644 --- a/src/js/content-kit-compiler/compiler.js +++ b/src/js/content-kit-compiler/compiler.js @@ -1,8 +1,7 @@ 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'; +import { mergeWithOptions } from '../content-kit-utils/object-utils'; /** * @class Compiler @@ -50,9 +49,7 @@ Compiler.prototype.render = function(data) { * @param {Type} type */ Compiler.prototype.registerBlockType = function(type) { - if (type instanceof Type) { - return this.blockTypes.addType(type); - } + return this.blockTypes.addType(type); }; /** @@ -60,9 +57,7 @@ Compiler.prototype.registerBlockType = function(type) { * @param {Type} type */ Compiler.prototype.registerMarkupType = function(type) { - if (type instanceof Type) { - return this.markupTypes.addType(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 index 07c4e223f..b97e44ab9 100644 --- a/src/js/content-kit-compiler/models/block.js +++ b/src/js/content-kit-compiler/models/block.js @@ -1,5 +1,4 @@ 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. @@ -26,6 +25,5 @@ function BlockModel(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 index da4a5927c..ff5f1288a 100644 --- a/src/js/content-kit-compiler/models/embed.js +++ b/src/js/content-kit-compiler/models/embed.js @@ -1,4 +1,3 @@ -import { inherit } from '../../content-kit-utils/object-utils'; import Model from '../models/model'; import Type from '../types/type'; @@ -40,6 +39,5 @@ function EmbedModel(options) { 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 index 3e1c7ff34..29553f96a 100644 --- a/src/js/content-kit-compiler/models/image.js +++ b/src/js/content-kit-compiler/models/image.js @@ -1,6 +1,5 @@ import BlockModel from './block'; import Type from '../types/type'; -import { inherit } from '../../content-kit-utils/object-utils'; /** * @class ImageModel @@ -17,6 +16,5 @@ function ImageModel(options) { } 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 index da3aa4876..4971b7fbf 100644 --- a/src/js/content-kit-compiler/models/markup.js +++ b/src/js/content-kit-compiler/models/markup.js @@ -1,5 +1,4 @@ import Model from './model'; -import { inherit } from '../../content-kit-utils/object-utils'; /** * @class MarkupModel @@ -12,6 +11,5 @@ function MarkupModel(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/text.js b/src/js/content-kit-compiler/models/text.js index 602c8c650..342188419 100644 --- a/src/js/content-kit-compiler/models/text.js +++ b/src/js/content-kit-compiler/models/text.js @@ -1,6 +1,5 @@ import BlockModel from './block'; import Type from '../types/type'; -import { inherit } from '../../content-kit-utils/object-utils'; /** * @class TextModel @@ -14,6 +13,5 @@ function TextModel(options) { options.type_name = Type.TEXT.name; BlockModel.call(this, options); } -inherit(TextModel, BlockModel); export default TextModel; diff --git a/src/js/content-kit-compiler/renderers/embeds/link.js b/src/js/content-kit-compiler/renderers/embeds/link.js new file mode 100644 index 000000000..8f4f9baf9 --- /dev/null +++ b/src/js/content-kit-compiler/renderers/embeds/link.js @@ -0,0 +1,7 @@ + +function LinkEmbedRenderer() {} +LinkEmbedRenderer.prototype.render = function(model) { + return ''; +}; + +export default LinkEmbedRenderer; diff --git a/src/js/content-kit-compiler/renderers/embeds/photo.js b/src/js/content-kit-compiler/renderers/embeds/photo.js new file mode 100644 index 000000000..c06d86f52 --- /dev/null +++ b/src/js/content-kit-compiler/renderers/embeds/photo.js @@ -0,0 +1,7 @@ + +function PhotoEmbedRenderer() {} +PhotoEmbedRenderer.prototype.render = function(model) { + return ''; +}; + +export default PhotoEmbedRenderer; diff --git a/src/js/content-kit-compiler/renderers/embeds/youtube.js b/src/js/content-kit-compiler/renderers/embeds/youtube.js index e88de5a32..3d9b4bf61 100644 --- a/src/js/content-kit-compiler/renderers/embeds/youtube.js +++ b/src/js/content-kit-compiler/renderers/embeds/youtube.js @@ -2,7 +2,7 @@ var RegExVideoId = /.*(?:youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=)([^#\&\?]*).*/; function getVideoIdFromUrl(url) { - var match = url.match(RegExVideoId); + var match = url && url.match(RegExVideoId); if (match && match[1].length === 11){ return match[1]; } diff --git a/src/js/content-kit-compiler/renderers/html-renderer.js b/src/js/content-kit-compiler/renderers/html-renderer.js index 6eb111643..a6991d484 100644 --- a/src/js/content-kit-compiler/renderers/html-renderer.js +++ b/src/js/content-kit-compiler/renderers/html-renderer.js @@ -11,7 +11,8 @@ import { mergeWithOptions } from '../../content-kit-utils/object-utils'; function HTMLRenderer(options) { var defaults = { blockTypes : DefaultBlockTypeSet, - markupTypes : DefaultMarkupTypeSet + markupTypes : DefaultMarkupTypeSet, + typeRenderers : {} }; mergeWithOptions(this, defaults, options); } @@ -22,12 +23,11 @@ function HTMLRenderer(options) { * @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; + this.typeRenderers[type] = renderer; }; /** @@ -57,7 +57,7 @@ HTMLRenderer.prototype.render = function(model) { for (i = 0; i < len; i++) { item = model[i]; renderer = this.rendererFor(item); - renderHook = renderHooks[item.type]; + renderHook = this.typeRenderers[item.type]; itemHtml = renderHook ? renderHook.call(renderer, item) : renderer.render(item); if (itemHtml) { html += itemHtml; } } diff --git a/src/js/content-kit-compiler/types/type-set.js b/src/js/content-kit-compiler/types/type-set.js index e31b111ad..776dccb16 100644 --- a/src/js/content-kit-compiler/types/type-set.js +++ b/src/js/content-kit-compiler/types/type-set.js @@ -1,3 +1,5 @@ +import Type from './type'; + /** * @class TypeSet * @private @@ -21,15 +23,17 @@ 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; + if (type instanceof 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; } - return type; }, /** diff --git a/src/js/content-kit-editor/commands/embed.js b/src/js/content-kit-editor/commands/embed.js index 0ef81035d..29578c38a 100644 --- a/src/js/content-kit-editor/commands/embed.js +++ b/src/js/content-kit-editor/commands/embed.js @@ -6,6 +6,17 @@ import { inherit } from '../../content-kit-utils/object-utils'; import { RegEx } from '../constants'; import { OEmbedder } from '../../ext/content-kit-services'; +function loadTwitterWidgets(element) { + if (window.twttr) { + window.twttr.widgets.load(element); + } else { + var script = document.createElement('script'); + script.async = true; + script.src = 'http://platform.twitter.com/widgets.js'; + document.head.appendChild(script); + } +} + function EmbedCommand(options) { Command.call(this, { name: 'embed', @@ -23,21 +34,30 @@ inherit(EmbedCommand, Command); EmbedCommand.prototype.exec = function(url) { var command = this; var editorContext = command.editorContext; + var embedIntent = command.embedIntent; var index = editorContext.getCurrentBlockIndex(); - command.embedIntent.showLoading(); + embedIntent.showLoading(); this.embedService.fetch({ url: url, complete: function(response, error) { - command.embedIntent.hideLoading(); - + embedIntent.hideLoading(); if (error) { - new Message().show('Embed error'); + var errorMsg = error; + if (error.target && error.target.status === 0) { + errorMsg = 'Could not connect to embed service'; + } else if (typeof error !== 'string') { + errorMsg = 'Embed error'; + } + new Message().show(errorMsg); } else { var embedModel = new EmbedModel(response); editorContext.insertBlockAt(embedModel, index); editorContext.syncVisualAt(index); + if (embedModel.attributes.provider_name.toLowerCase() === 'twitter') { + loadTwitterWidgets(editorContext.element); + } } } }); diff --git a/src/js/content-kit-editor/commands/image.js b/src/js/content-kit-editor/commands/image.js index c3977e518..2ea815d39 100644 --- a/src/js/content-kit-editor/commands/image.js +++ b/src/js/content-kit-editor/commands/image.js @@ -4,6 +4,17 @@ import ImageModel from '../../content-kit-compiler/models/image'; import { inherit } from '../../content-kit-utils/object-utils'; import { FileUploader } from '../../ext/content-kit-services'; +function createFileInput(command) { + var fileInput = document.createElement('input'); + fileInput.type = 'file'; + fileInput.accept = 'image/*'; + fileInput.className = 'ck-file-input'; + fileInput.addEventListener('change', function(e) { + command.handleFile(e); + }); + return fileInput; +} + function ImageCommand(options) { Command.call(this, { name: 'image', @@ -16,19 +27,12 @@ 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); - }); + var fileInput = this.fileInput; + if (!fileInput) { + fileInput = this.fileInput = createFileInput(this); document.body.appendChild(fileInput); } - this.fileInput.dispatchEvent(clickEvent); + fileInput.dispatchEvent(new MouseEvent('click', { bubbles: false })); }, handleFile: function(e) { var fileInput = e.target; diff --git a/src/js/content-kit-editor/editor-html-renderer.js b/src/js/content-kit-editor/editor-html-renderer.js new file mode 100644 index 000000000..5c7a77496 --- /dev/null +++ b/src/js/content-kit-editor/editor-html-renderer.js @@ -0,0 +1,41 @@ +import HTMLRenderer from '../content-kit-compiler/renderers/html-renderer'; +import Type from '../content-kit-compiler/types/type'; +import { inherit } from '../content-kit-utils/object-utils'; + +function embedRenderer(model) { + var embedAttrs = model.attributes; + var isVideo = embedAttrs.embed_type === 'video'; + return '
' + + '
' + + (isVideo ? '
' : '') + this.render(model) + (isVideo ? '
' : '') + + '
' + embedAttrs.provider_name + ': ' + + '' + embedAttrs.title + '' + + '
' + + '
' + + '
'; +} + +function imageRenderer(model) { + return '
' + + '
' + this.render(model) + '
' + + '
'; +} + +var typeRenderers = {}; +typeRenderers[Type.EMBED.id] = embedRenderer; +typeRenderers[Type.IMAGE.id] = imageRenderer; + +/** + * @class EditorHTMLRenderer + * @constructor + * Subclass of HTMLRenderer specifically for the Editor + * Wraps interactive elements to add functionality + */ +function EditorHTMLRenderer() { + HTMLRenderer.call(this, { + typeRenderers: typeRenderers + }); +} +inherit(EditorHTMLRenderer, HTMLRenderer); + +export default EditorHTMLRenderer; diff --git a/src/js/content-kit-editor/editor.js b/src/js/content-kit-editor/editor.js index 49772a84a..b5faf55c3 100644 --- a/src/js/content-kit-editor/editor.js +++ b/src/js/content-kit-editor/editor.js @@ -11,10 +11,10 @@ import TextModel from '../content-kit-compiler/models/text'; import Type from '../content-kit-compiler/types/type'; import { toArray } from '../content-kit-utils/array-utils'; import { merge } from '../content-kit-utils/object-utils'; +import EditorHTMLRenderer from './editor-html-renderer'; var editorClassName = 'ck-editor'; var editorClassNameRegExp = new RegExp(editorClassName); -var afterRenderHooks = []; function plainTextToBlocks(plainText, blockTag) { var blocks = plainText.split(RegEx.NEWLINE), @@ -104,12 +104,6 @@ function bindPasteEvents(editor) { }); } -function runAfterRenderHooks(element, blockModel) { - for (var i = 0, len = afterRenderHooks.length; i < len; i++) { - afterRenderHooks[i].call(null, element, blockModel); - } -} - /** * @class Editor * An individual Editor @@ -139,7 +133,10 @@ function Editor(element, options) { element.setAttribute('contentEditable', true); editor.element = element; - var compiler = editor.compiler = options.compiler || new Compiler(); + var compiler = editor.compiler = options.compiler || new Compiler({ + includeTypeNames: true, // output type names for easier debugging + renderer: new EditorHTMLRenderer() + }); editor.syncModel(); bindTypingEvents(editor); @@ -187,7 +184,6 @@ Editor.prototype.syncVisualAt = function(index) { var blockElements = toArray(this.element.children); var element = blockElements[index]; element.innerHTML = html; - runAfterRenderHooks(element, blockModel); }; Editor.prototype.getCurrentBlockIndex = function() { @@ -215,14 +211,4 @@ Editor.prototype.addTextFormat = function(opts) { this.textFormatToolbar.addCommand(command); }; -Editor.prototype.willRenderType = function(type, renderer) { - this.compiler.renderer.willRenderType(type, renderer); -}; - -Editor.prototype.afterRender = function(callback) { - if ('function' === typeof callback) { - afterRenderHooks.push(callback); - } -}; - export default Editor; diff --git a/src/js/content-kit-editor/utils/selection-utils.js b/src/js/content-kit-editor/utils/selection-utils.js index 066ec4162..7dabd0628 100644 --- a/src/js/content-kit-editor/utils/selection-utils.js +++ b/src/js/content-kit-editor/utils/selection-utils.js @@ -62,7 +62,7 @@ function selectionIsInElement(selection, element) { function selectionIsEditable(selection) { var el = getSelectionBlockElement(selection); - return el.contentEditable !== 'false'; + return el.isContentEditable; } /* diff --git a/src/js/content-kit-editor/views/tooltip.js b/src/js/content-kit-editor/views/tooltip.js index 392f2fb7e..f56d99a7a 100644 --- a/src/js/content-kit-editor/views/tooltip.js +++ b/src/js/content-kit-editor/views/tooltip.js @@ -12,7 +12,7 @@ function Tooltip(options) { rootElement.addEventListener('mouseover', function(e) { var target = getEventTargetMatchingTag(options.showForTag, e.target, rootElement); - if (target) { + if (target && target.isContentEditable) { timeout = setTimeout(function() { tooltip.showLink(target.href, target); }, delay);