Skip to content

Commit

Permalink
Refactor Image and Card sections to a new renderer
Browse files Browse the repository at this point in the history
  • Loading branch information
mixonic committed Jul 15, 2015
1 parent 6261c7c commit 67c2e0d
Show file tree
Hide file tree
Showing 20 changed files with 958 additions and 128 deletions.
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,8 @@
"broccoli-test-builder": "^0.1.0",
"content-kit-utils": "^0.2.0",
"jquery": "^2.1.4",
"mobiledoc-dom-renderer": "^0.1.3",
"mobiledoc-html-renderer": "^0.1.0",
"mobiledoc-dom-renderer": "^0.1.4",
"mobiledoc-html-renderer": "^0.1.1",
"testem": "^0.8.4"
}
}
6 changes: 3 additions & 3 deletions server/config.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
{
"s3" : {
"bucketName" : "content-kit"
"bucketName" : "201-bustle-demo"
}
}
}
1 change: 0 additions & 1 deletion server/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ var EmbedService = require('./services/embed');

// Express app
var app = express();
app.use(express.static('demo'));
app.use('/dist', express.static('dist'));

// Enable cors
Expand Down
104 changes: 48 additions & 56 deletions src/js/commands/image.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,82 +2,74 @@ import Command from './base';
import Message from '../views/message';
import { inherit } from 'content-kit-utils';
import { FileUploader } from '../utils/http-utils';
import { generateBuilder } from '../utils/post-builder';

function createFileInput(command) {
var fileInput = document.createElement('input');
fileInput.type = 'file';
fileInput.accept = 'image/*';
fileInput.className = 'ck-file-input';
// FIXME should this listener be torn down when the ImageCommand is not active?
fileInput.addEventListener('change', function(e) {
command.handleFile(e);
});
return fileInput;
}

function injectImageBlock(/* src, editor, index */) {
throw new Error('Unimplemented: BlockModel and Type.IMAGE are no longer things');
/*
var imageModel = BlockModel.createWithType(Type.IMAGE, { attributes: { src: src } });
editor.replaceBlock(imageModel, index);
*/
}

function renderFromFile(file, editor, index) {
if (file && window.FileReader) {
var reader = new FileReader();
reader.onload = function(e) {
var base64Src = e.target.result;
injectImageBlock(base64Src, editor, index);
editor.renderBlockAt(index, true);
};
reader.readAsDataURL(file);
}
function readFromFile(file, callback) {
var reader = new FileReader();
reader.onload = ({target}) => callback(target.result);
reader.readAsDataURL(file);
}

function ImageCommand(options) {
Command.call(this, {
name: 'image',
button: '<i class="ck-icon-image"></i>'
});
this.uploader = new FileUploader({ url: options.serviceUrl, maxFileSize: 5000000 });
this.uploader = new FileUploader({
url: options.serviceUrl,
maxFileSize: 5000000
});
}
inherit(ImageCommand, Command);

ImageCommand.prototype = {
exec: function() {
exec() {
ImageCommand._super.prototype.exec.call(this);
var fileInput = this.fileInput;
if (!fileInput) {
fileInput = this.fileInput = createFileInput(this);
document.body.appendChild(fileInput);
}
var fileInput = this.getFileInput();
fileInput.dispatchEvent(new MouseEvent('click', { bubbles: false }));
},
handleFile: function(e) {
var fileInput = e.target;
var file = fileInput.files && fileInput.files[0];
var editor = this.editorContext;
var embedIntent = this.embedIntent;
var currentEditingIndex = editor.getCurrentBlockIndex();
getFileInput() {
if (this._fileInput) {
return this._fileInput;
}

var fileInput = document.createElement('input');
fileInput.type = 'file';
fileInput.accept = 'image/*';
fileInput.className = 'ck-file-input';
fileInput.addEventListener('change', e => this.handleFile(e));
document.body.appendChild(fileInput);

return fileInput;
},
handleFile({target: fileInput}) {
let imageSection;

let file = fileInput.files[0];
readFromFile(file, (base64Image) => {
imageSection = generateBuilder().generateImageSection(base64Image);
this.editorContext.insertSectionAtCursor(imageSection);
this.editorContext.rerender();
});

embedIntent.showLoading();
renderFromFile(file, editor, currentEditingIndex); // render image immediately client-side
this.uploader.upload({
fileInput: fileInput,
complete: function(response, error) {
embedIntent.hideLoading();
if (error || !response || !response.url) {
setTimeout(function() {
editor.removeBlockAt(currentEditingIndex);
editor.syncVisual();
}, 1000);
return new Message().showError(error.message || 'Error uploading image');
fileInput,
complete: (response, error) => {
if (!imageSection) {
throw new Error('Upload completed before the image was read into memory');
}
if (!error && response && response.url) {
imageSection.src = response.url;
imageSection.renderNode.markDirty();
this.editorContext.rerender();
this.editorContext.trigger('update');
} else {
this.editorContext.removeSection(imageSection);
new Message().showError(error.message || 'Error uploading image');
}
injectImageBlock(response.url, editor, currentEditingIndex);
this.editorContext.rerender();
}
});
fileInput.value = null; // reset file input
}
};

Expand Down
103 changes: 79 additions & 24 deletions src/js/editor/editor.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ import EventEmitter from '../utils/event-emitter';

import MobiledocParser from "../parsers/mobiledoc";
import DOMParser from "../parsers/dom";
import EditorDOMRenderer from "../renderers/editor-dom";
import Renderer from 'content-kit-editor/renderers/editor-dom';
import RenderTree from 'content-kit-editor/models/render-tree';
import MobiledocRenderer from '../renderers/mobiledoc';

import { toArray, merge, mergeWithOptions } from 'content-kit-utils';
Expand Down Expand Up @@ -53,7 +54,9 @@ var defaults = {
new UnorderedListCommand(),
new OrderedListCommand()
],
cards: {},
cards: [],
cardOptions: {},
unknownCardHandler: () => { throw new Error('Unknown card encountered'); },
mobiledoc: null
};

Expand Down Expand Up @@ -88,7 +91,7 @@ function bindContentEditableTypingListeners(editor) {
var sanitizedHTML = pastedHTML && editor._renderer.rerender(pastedHTML);
if (sanitizedHTML) {
document.execCommand('insertHTML', false, sanitizedHTML);
editor.syncVisual();
editor.rerender();
}
e.preventDefault();
return false;
Expand Down Expand Up @@ -175,8 +178,8 @@ function Editor(element, options) {
// FIXME: This should merge onto this.options
mergeWithOptions(this, defaults, options);

this._renderer = new EditorDOMRenderer(window.document, this.cards);
this._parser = new DOMParser();
this._renderer = new Renderer(this.cards, this.unknownCardHandler, this.cardOptions);

this.applyClassName();
this.applyPlaceholder();
Expand All @@ -193,12 +196,12 @@ function Editor(element, options) {
}

clearChildNodes(element);
this.syncVisual();
this.rerender();

bindContentEditableTypingListeners(this);
bindAutoTypingListeners(this);
bindDragAndDrop(this);
this.addEventListener(element, 'input', () => this.handleInput(...arguments));
this.addEventListener(element, 'input', () => this.handleInput());
initEmbedCommands(this);

this.addView(new TextFormatToolbar({
Expand Down Expand Up @@ -227,22 +230,34 @@ merge(Editor.prototype, {

loadModel(post) {
this.post = post;
this.syncVisual();
this.rerender();
this.trigger('update');
},

parseModelFromDOM(element) {
this.post = this._parser.parse(element);
this._renderTree = new RenderTree();
let node = this._renderTree.buildRenderNode(this.post);
this._renderTree.node = node;
this.trigger('update');
},

parseModelFromMobiledoc(mobiledoc) {
this.post = new MobiledocParser().parse(mobiledoc);
this._renderTree = new RenderTree();
let node = this._renderTree.buildRenderNode(this.post);
this._renderTree.node = node;
this.trigger('update');
},

syncVisual() {
this._renderer.render(this.post, this.element);
rerender() {
let postRenderNode = this.post.renderNode;
if (!postRenderNode.element) {
postRenderNode.element = this.element;
postRenderNode.markDirty();
}

this._renderer.render(this._renderTree);
},

getCurrentBlockIndex() {
Expand Down Expand Up @@ -344,21 +359,28 @@ merge(Editor.prototype, {
let newSections = [];
let previousSection;
forEachChildNode(this.element, (node) => {
let section = this.post.getElementSection(node);
if (!section) {
section = this._parser.parseSection(
let sectionRenderNode = this._renderTree.getElementRenderNode(node);
if (!sectionRenderNode) {
let section = this._parser.parseSection(
previousSection,
node
);
this.post.setSectionElement(section, node);
newSections.push(section);

sectionRenderNode = this._renderTree.buildRenderNode(section);
sectionRenderNode.element = node;
sectionRenderNode.markClean();

if (previousSection) {
this.post.insertSectionAfter(section, previousSection);
this._renderTree.node.insertAfter(sectionRenderNode, previousSection.renderNode);
} else {
this.post.prependSection(section);
this._renderTree.node.insertAfter(sectionRenderNode, null);
}
}
// may cause duplicates to be included
let section = sectionRenderNode.postNode;
sectionsInDOM.push(section);
previousSection = section;
});
Expand All @@ -368,7 +390,11 @@ merge(Editor.prototype, {
for (i=this.post.sections.length-1;i>=0;i--) {
let section = this.post.sections[i];
if (sectionsInDOM.indexOf(section) === -1) {
this.post.removeSection(section);
if (section.renderNode) {
section.renderNode.scheduleForRemoval();
} else {
throw new Error('All sections are expected to have a renderNode');
}
}
}

Expand All @@ -388,9 +414,18 @@ merge(Editor.prototype, {
this.reparseSection(section);
}
});

this.rerender();
this.trigger('update');
},

getSectionsWithCursor() {
return this.getRenderNodesWithCursor().map( renderNode => {
return renderNode.postNode;
});
},

getRenderNodesWithCursor() {
const selection = document.getSelection();
if (selection.rangeCount === 0) {
return null;
Expand All @@ -400,26 +435,32 @@ merge(Editor.prototype, {

let { startContainer:startElement, endContainer:endElement } = range;

let getElementSection = (e) => this.post.getElementSection(e);
let { result:startSection } = detectParentNode(startElement, getElementSection);
let { result:endSection } = detectParentNode(endElement, getElementSection);

let startIndex = this.post.sections.indexOf(startSection),
endIndex = this.post.sections.indexOf(endSection);
let getElementRenderNode = (e) => {
return this._renderTree.getElementRenderNode(e);
};
let { result:startRenderNode } = detectParentNode(startElement, getElementRenderNode);
let { result:endRenderNode } = detectParentNode(endElement, getElementRenderNode);

let nodes = [];
let node = startRenderNode;
while (node && (!endRenderNode.nextSibling || endRenderNode.nextSibling !== node)) {
nodes.push(node);
node = node.nextSibling;
}

return this.post.sections.slice(startIndex, endIndex+1);
return nodes;
},

reparseSection(section) {
let sectionElement = this.post.getSectionElement(section);
let sectionRenderNode = section.renderNode;
let sectionElement = sectionRenderNode.element;
let previousSection = this.post.getPreviousSection(section);

var newSection = this._parser.parseSection(
previousSection,
sectionElement
);
this.post.replaceSection(section, newSection);
this.post.setSectionElement(newSection, sectionElement);
section.markers = newSection.markers;

this.trigger('update');
},
Expand All @@ -433,6 +474,20 @@ merge(Editor.prototype, {
this._views = [];
},

insertSectionAtCursor(newSection) {
let newRenderNode = this._renderTree.buildRenderNode(newSection);
let renderNodes = this.getRenderNodesWithCursor();
let lastRenderNode = renderNodes[renderNodes.length-1];
lastRenderNode.parentNode.insertAfter(newRenderNode, lastRenderNode);
this.post.insertSectionAfter(newSection, lastRenderNode.postNode);
renderNodes.forEach(renderNode => renderNode.scheduleForRemoval());
this.trigger('update');
},

removeSection(section) {
this.post.removeSection(section);
},

destroy() {
this.removeAllEventListeners();
this.removeAllViews();
Expand Down
Loading

0 comments on commit 67c2e0d

Please sign in to comment.