Skip to content

Commit

Permalink
Classify commands, change FormatBlock command to operate semantically
Browse files Browse the repository at this point in the history
 * Add tests for showing/hiding toolbar and selection for heading
  • Loading branch information
bantic committed Aug 4, 2015
1 parent 674d399 commit 3e7e829
Show file tree
Hide file tree
Showing 18 changed files with 340 additions and 154 deletions.
24 changes: 12 additions & 12 deletions src/js/commands/base.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
function Command(options) {
options = options || {};
var command = this;
var name = options.name;
var prompt = options.prompt;
command.name = name;
command.button = options.button || name;
if (prompt) { command.prompt = prompt; }
}

Command.prototype.exec = function() {};
export default class Command {
constructor(options={}) {
var command = this;
var name = options.name;
var prompt = options.prompt;
command.name = name;
command.button = options.button || name;
if (prompt) { command.prompt = prompt; }
}

export default Command;
exec() {/* override in subclass */}
unexec() {/* override in subclass */}
}
32 changes: 14 additions & 18 deletions src/js/commands/card.js
Original file line number Diff line number Diff line change
@@ -1,28 +1,24 @@
import Command from './base';
import { inherit } from 'content-kit-utils';

function injectCardBlock(/* cardName, cardPayload, editor, index */) {
throw new Error('Unimplemented: BlockModel and Type.CARD are no longer things');
}

function CardCommand() {
Command.call(this, {
name: 'card',
button: '<i>CA</i>'
});
}
inherit(CardCommand, Command);
export default class CardCommand extends Command {
constructor() {
super({
name: 'card',
button: '<i>CA</i>'
});
}

CardCommand.prototype = {
exec: function() {
CardCommand._super.prototype.exec.call(this);
var editor = this.editorContext;
var currentEditingIndex = editor.getCurrentBlockIndex();
exec() {
super.exec();
const editor = this.editor;
const currentEditingIndex = editor.getCurrentBlockIndex();

var cardName = 'pick-color';
var cardPayload = { options: ['red', 'blue'] };
const cardName = 'pick-color';
const cardPayload = { options: ['red', 'blue'] };
injectCardBlock(cardName, cardPayload, editor, currentEditingIndex);
}
};

export default CardCommand;
}
62 changes: 32 additions & 30 deletions src/js/commands/format-block.js
Original file line number Diff line number Diff line change
@@ -1,36 +1,38 @@
import TextFormatCommand from './text-format';
import { getSelectionBlockElement, selectNode } from '../utils/selection-utils';
import { inherit } from 'content-kit-utils';

function FormatBlockCommand(options) {
options = 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 text block
if(tag === blockElement.tagName.toLowerCase()) {
throw new Error('Unimplemented: Type.BOLD.paragraph must be replaced');
/*
value = Type.PARAGRAPH.tag;
*/
} else {
// Flattens the selection before applying the block format.
// Otherwise, undesirable nested blocks can occur.
// TODO: would love to be able to remove this
var flatNode = document.createTextNode(blockElement.textContent);
blockElement.parentNode.insertBefore(flatNode, blockElement);
blockElement.parentNode.removeChild(blockElement);
selectNode(flatNode);
class FormatBlockCommand extends TextFormatCommand {
constructor(options={}) {
super(options);
}

exec() {
const editor = this.editor;
const activeSections = editor.activeSections;

activeSections.forEach(s => {
editor.resetSectionMarkers(s);
editor.setSectionTagName(s, this.tag);
});

editor.rerender();
editor.trigger('update'); // FIXME -- should be handled by editor

editor.selectSections(activeSections);
}

FormatBlockCommand._super.prototype.exec.call(this, value);
};
unexec() {
const editor = this.editor;
const activeSections = editor.activeSections;

activeSections.forEach(s => {
editor.resetSectionTagName(s);
});

editor.rerender();
editor.trigger('update'); // FIXME -- should be handled by editor

editor.selectSections(activeSections);
}
}

export default FormatBlockCommand;
19 changes: 9 additions & 10 deletions src/js/commands/heading.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import FormatBlockCommand from './format-block';
import { inherit } from 'content-kit-utils';

function HeadingCommand() {
FormatBlockCommand.call(this, {
name: 'heading',
tag: 'h2',
button: '<i class="ck-icon-heading"></i>1'
});
export default class HeadingCommand extends FormatBlockCommand {
constructor() {
const options = {
name: 'heading',
tag: 'h2',
button: '<i class="ck-icon-heading"></i>1'
};
super(options);
}
}
inherit(HeadingCommand, FormatBlockCommand);

export default HeadingCommand;
48 changes: 23 additions & 25 deletions src/js/commands/image.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
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';

Expand All @@ -10,24 +9,24 @@ function readFromFile(file, callback) {
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
});
}
inherit(ImageCommand, Command);
export default class ImageCommand extends Command {
constructor(options={}) {
super({
name: 'image',
button: '<i class="ck-icon-image"></i>'
});
this.uploader = new FileUploader({
url: options.serviceUrl,
maxFileSize: 5000000
});
}

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

getFileInput() {
if (this._fileInput) {
return this._fileInput;
Expand All @@ -41,15 +40,16 @@ ImageCommand.prototype = {
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();
this.editor.insertSectionAtCursor(imageSection);
this.editor.rerender();
});

this.uploader.upload({
Expand All @@ -61,16 +61,14 @@ ImageCommand.prototype = {
if (!error && response && response.url) {
imageSection.src = response.url;
imageSection.renderNode.markDirty();
this.editorContext.rerender();
this.editorContext.trigger('update');
this.editor.rerender();
this.editor.trigger('update');
} else {
this.editorContext.removeSection(imageSection);
this.editor.removeSection(imageSection);
new Message().showError(error.message || 'Error uploading image');
}
this.editorContext.rerender();
this.editor.rerender();
}
});
}
};

export default ImageCommand;
}
2 changes: 0 additions & 2 deletions src/js/commands/oembed.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,7 @@ inherit(OEmbedCommand, Command);

OEmbedCommand.prototype.exec = function(url) {
var command = this;
// var editorContext = command.editorContext;
var embedIntent = command.embedIntent;
// var index = editorContext.getCurrentBlockIndex();

embedIntent.showLoading();
this.embedService.fetch({
Expand Down
33 changes: 15 additions & 18 deletions src/js/commands/text-format.js
Original file line number Diff line number Diff line change
@@ -1,24 +1,21 @@
import Command from './base';
import { inherit } from 'content-kit-utils';

function TextFormatCommand(options) {
options = options || {};
Command.call(this, options);
this.tag = options.tag;
this.mappedTags = options.mappedTags || [];
this.mappedTags.push(this.tag);
this.action = options.action || this.name;
this.removeAction = options.removeAction || this.action;
}
inherit(TextFormatCommand, Command);
export default class TextFormatCommand extends Command {
constructor(options={}) {
super(options);

this.tag = options.tag;
this.mappedTags = options.mappedTags || [];
this.mappedTags.push(this.tag);
this.action = options.action || this.name;
this.removeAction = options.removeAction || this.action;
}

TextFormatCommand.prototype = {
exec: function(value) {
exec(value) {
document.execCommand(this.action, false, value || null);
},
unexec: function(value) {
document.execCommand(this.removeAction, false, value || null);
}
};

export default TextFormatCommand;
unexec(value) {
document.execCommand(this.removeAction, false, value || null);
}
}
46 changes: 44 additions & 2 deletions src/js/editor/editor.js
Original file line number Diff line number Diff line change
Expand Up @@ -266,7 +266,7 @@ class Editor {
rerender() {
let postRenderNode = this.post.renderNode;

// if we haven't rendered this renderNode before, mark it dirty
// if we haven't rendered this post's renderNode before, mark it dirty
if (!postRenderNode.element) {
postRenderNode.element = this.element;
postRenderNode.markDirty();
Expand Down Expand Up @@ -428,6 +428,10 @@ class Editor {
this.trigger('update');
}

selectSections(sections) {
this.cursor.selectSections(sections);
}

getActiveSections() {
const cursor = this.cursor;
return cursor.activeSections;
Expand Down Expand Up @@ -535,7 +539,7 @@ class Editor {
rightOffset
} = this.cursor.offsets;

// The cursor will lose its textNode if we have parsed (and thus rerendered)
// The cursor will lose its textNode if we have reparsed (and thus will rerender, below)
// its section. Ensure the cursor is placed where it should be after render.
//
// New sections are presumed clean, and thus do not get rerendered and lose
Expand Down Expand Up @@ -570,6 +574,44 @@ class Editor {
}
}

get cursorSelection() {
return this.cursor.cursorSelection;
}

/*
* Returns the active sections. If the cursor selection is collapsed this will be
* an array of 1 item. Else will return an array containing each section that is either
* wholly or partly contained by the cursor selection.
*
* @return {array} The sections from the cursor's selection start to the selection end
*/
get activeSections() {
return this.cursor.activeSections;
}

/*
* Clear the markups from each of the section's markers
*/
resetSectionMarkers(section) {
section.markers.forEach(m => {
m.clearMarkups();
m.renderNode.markDirty();
});
}

/*
* Change the tag name for the given section
*/
setSectionTagName(section, tagName) {
section.setTagName(tagName);
section.renderNode.markDirty();
}

resetSectionTagName(section) {
section.resetTagName();
section.renderNode.markDirty();
}

reparseSection(section) {
this._parser.reparseSection(section, this._renderTree);
}
Expand Down
Loading

0 comments on commit 3e7e829

Please sign in to comment.