Skip to content

Commit

Permalink
Post images to be cards
Browse files Browse the repository at this point in the history
  • Loading branch information
mixonic committed Aug 5, 2015
1 parent 58ffbc8 commit 2b88550
Show file tree
Hide file tree
Showing 15 changed files with 188 additions and 75 deletions.
10 changes: 8 additions & 2 deletions demo/demo.js
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,8 @@ var ContentKitDemo = exports.ContentKitDemo = {
'simple-card': simpleCard,
'edit-card': cardWithEditMode,
'input-card': cardWithInput,
'selfie-card': selfieCard
'selfie-card': selfieCard,
'image': ContentKit.ImageCard
};
var renderer = new MobiledocDOMRenderer();
var rendered = renderer.render(mobiledoc, document.createElement('div'), cards);
Expand Down Expand Up @@ -262,7 +263,12 @@ function bootEditor(element, mobiledoc) {
editor = new ContentKit.Editor(element, {
autofocus: false,
mobiledoc: mobiledoc,
cards: [simpleCard, cardWithEditMode, cardWithInput, selfieCard]
cards: [simpleCard, cardWithEditMode, cardWithInput, selfieCard],
cardOptions: {
image: {
uploadUrl: 'http://localhost:5000/upload'
}
}
});

function sync() {
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,8 @@
"broccoli-test-builder": "^0.1.0",
"content-kit-utils": "^0.2.0",
"jquery": "^2.1.4",
"mobiledoc-dom-renderer": "^0.1.8",
"mobiledoc-html-renderer": "^0.1.5",
"mobiledoc-dom-renderer": "^0.1.10",
"mobiledoc-html-renderer": "^0.1.6",
"testem": "^0.8.4"
}
}
96 changes: 96 additions & 0 deletions src/js/cards/image.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import placeholderImage from './placeholder-image';
import { FileUploader } from '../utils/http-utils';

function buildFileInput() {
let input = document.createElement('input');
input.type = 'file';
input.accept = 'image/*';
input.classList.add('ck-file-input');
document.body.appendChild(input);
return input;
}

function buildButton(text) {
let button = document.createElement('button');
button.innerHTML = text;
return button;
}

function upload(imageOptions, fileInput, success, failure) {
let uploader = new FileUploader({
url: imageOptions.uploadUrl,
maxFileSize: 5000000
});
uploader.upload({
fileInput,
complete: (response, error) => {
if (!error && response && response.url) {
success({
src: response.url
});
} else {
window.alert('There was a problem uploading the image: '+error);
failure();
}
}
});
}

export default {
name: 'image',

display: {
setup(element, options, {edit}, payload) {
var img = document.createElement('img');
img.src = payload.src || placeholderImage;
if (edit) {
img.onclick = edit;
}
element.appendChild(img);
return img;
},
teardown(element) {
element.parentNode.removeChild(element);
}
},

edit: {
setup(element, options, {save, cancel}) {
let uploadButton = buildButton('Upload');
let cancelButton = buildButton('Cancel');
cancelButton.onclick = cancel;

let {image: imageOptions} = options;
if (!imageOptions || (imageOptions && !imageOptions.uploadUrl)) {
window.alert('Image card must have `image.uploadUrl` included in cardOptions');
cancel();
return;
}


let fileInput = buildFileInput();
uploadButton.onclick = () => {
fileInput.dispatchEvent(new MouseEvent('click', { bubbles: false }));
};
element.appendChild(uploadButton);
element.appendChild(cancelButton);

fileInput.onchange = () => {
try {
if (fileInput.files.length === 0) {
cancel();
}
upload(imageOptions, fileInput, save, cancel);
} catch(error) {
window.alert('There was a starting the upload: '+error);
cancel();
}
};
return [uploadButton, cancelButton, fileInput];
},
teardown(elements) {
elements.forEach(element => element.parentNode.removeChild(element));
}
}

};
3 changes: 3 additions & 0 deletions src/js/cards/placeholder-image.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

72 changes: 11 additions & 61 deletions src/js/commands/image.js
Original file line number Diff line number Diff line change
@@ -1,74 +1,24 @@
import Command from './base';
import Message from '../views/message';
import { FileUploader } from '../utils/http-utils';
import { generateBuilder } from '../utils/post-builder';

function readFromFile(file, callback) {
var reader = new FileReader();
reader.onload = ({target}) => callback(target.result);
reader.readAsDataURL(file);
}

export default class ImageCommand extends Command {
constructor(options={}) {
constructor() {
super({
name: 'image',
button: '<i class="ck-icon-image"></i>'
});
this.uploader = new FileUploader({
url: options.serviceUrl,
maxFileSize: 5000000
});
this.builder = generateBuilder();
}

exec() {
super.exec();
var fileInput = this.getFileInput();
fileInput.dispatchEvent(new MouseEvent('click', { bubbles: false }));
}

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.editor.insertSectionAtCursor(imageSection);
this.editor.rerender();
});

this.uploader.upload({
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.editor.rerender();
this.editor.trigger('update');
} else {
this.editor.removeSection(imageSection);
new Message().showError(error.message || 'Error uploading image');
}
this.editor.rerender();
}
});
let {post} = this.editor;
let sections = this.editor.activeSections;
let lastSection = sections[sections.length - 1];
let section = this.builder.generateCardSection('image');
post.insertSectionAfter(section, lastSection);
sections.forEach(section => section.renderNode.scheduleForRemoval());

this.editor.rerender();
this.editor.trigger('update');
}
}
6 changes: 5 additions & 1 deletion src/js/editor/editor.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import ImageCommand from '../commands/image';
import OEmbedCommand from '../commands/oembed';
import CardCommand from '../commands/card';

import ImageCard from '../cards/image';

import Keycodes from '../utils/keycodes';
import {
getSelectionBlockElement
Expand Down Expand Up @@ -60,7 +62,7 @@ const defaults = {
new LinkCommand()
],
embedCommands: [
new ImageCommand({ serviceUrl: '/upload' }),
new ImageCommand(),
new OEmbedCommand({ serviceUrl: '/embed' }),
new CardCommand()
],
Expand Down Expand Up @@ -211,6 +213,8 @@ class Editor {
// FIXME: This should merge onto this.options
mergeWithOptions(this, defaults, options);

this.cards.push(ImageCard);

this._parser = PostParser;
this._renderer = new Renderer(this, this.cards, this.unknownCardHandler, this.cardOptions);

Expand Down
4 changes: 3 additions & 1 deletion src/js/index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import Editor from './editor/editor';
import ImageCard from './cards/image';

const ContentKit = {
Editor
Editor,
ImageCard
};

export function registerGlobal(global) {
Expand Down
11 changes: 10 additions & 1 deletion src/js/renderers/editor-dom.js
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,16 @@ class Visitor {
const element = document.createElement('div');
element.contentEditable = 'false';
renderNode.element = element;
renderNode.parentNode.element.appendChild(renderNode.element);
if (renderNode.previousSibling) {
let previousElement = renderNode.previousSibling.element;
let nextElement = previousElement.nextSibling;
if (nextElement) {
nextElement.parentNode.insertBefore(element, nextElement);
}
}
if (!element.parentNode) {
renderNode.parentNode.element.appendChild(element);
}

if (card) {
let cardNode = new CardNode(editor, card, section, renderNode.element, options);
Expand Down
4 changes: 2 additions & 2 deletions src/js/views/embed-intent.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ function EmbedIntent(options) {
embedIntent.button.title = 'Insert image or embed...';
embedIntent.element.appendChild(embedIntent.button);

this.addEventListener(embedIntent.button, 'mouseup', (e) => {
this.addEventListener(embedIntent.button, 'click', (e) => {
if (embedIntent.isActive) {
embedIntent.deactivate();
} else {
Expand All @@ -59,7 +59,7 @@ function EmbedIntent(options) {
}

this.addEventListener(rootElement, 'keyup', embedIntentHandler);
this.addEventListener(document, 'mouseup', () => {
this.addEventListener(document, 'click', () => {
setTimeout(() => {
embedIntentHandler();
});
Expand Down
2 changes: 1 addition & 1 deletion src/js/views/prompt.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ class Prompt extends View {

prompt.command = options.command;
prompt.element.placeholder = options.placeholder || '';
this.addEventListener(prompt.element, 'mouseup', (e) => {
this.addEventListener(prompt.element, 'click', (e) => {
// prevents closing prompt when clicking input
e.stopPropagation();
});
Expand Down
2 changes: 1 addition & 1 deletion src/js/views/reversible-toolbar-button.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ class ReversibleToolbarButton {
this.element = this.createElement();
this.active = false;

this.addEventListener(this.element, 'mouseup', e => this.handleClick(e));
this.addEventListener(this.element, 'click', e => this.handleClick(e));
this.editor.on('selection', () => this.updateActiveState());
this.editor.on('selectionUpdated', () => this.updateActiveState());
this.editor.on('selectionEnded', () => this.updateActiveState());
Expand Down
5 changes: 4 additions & 1 deletion src/js/views/toolbar-button.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,15 @@ function ToolbarButton(options) {
element.title = command.name;
element.className = buttonClassName;
element.innerHTML = command.button;
this.addEventListener(element, 'mouseup', (e) => {
this.addEventListener(element, 'click', (e) => {
if (!button.isActive && prompt) {
toolbar.displayPrompt(prompt);
} else {
command.exec();
toolbar.updateForSelection();
if (toolbar.embedIntent) {
toolbar.embedIntent.hide();
}
}
e.stopPropagation();
});
Expand Down
2 changes: 1 addition & 1 deletion src/js/views/toolbar.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ class Toolbar extends View {
(options.commands || []).forEach(c => this.addCommand(c));

// Closes prompt if displayed when changing selection
this.addEventListener(document, 'mouseup', () => {
this.addEventListener(document, 'click', () => {
this.dismissPrompt();
});
}
Expand Down
2 changes: 1 addition & 1 deletion tests/acceptance/editor-commands-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ function assertToolbarHidden(assert) {

function clickToolbarButton(assert, name) {
const button = getToolbarButton(assert, name);
Helpers.dom.triggerEvent(button[0], 'mouseup');
Helpers.dom.triggerEvent(button[0], 'click');
}

function assertActiveToolbarButton(assert, buttonTitle) {
Expand Down
Loading

0 comments on commit 2b88550

Please sign in to comment.