Skip to content

Commit

Permalink
change block commands to operate semantically, add ReversibleToolbarB…
Browse files Browse the repository at this point in the history
…utton

 * Test that clicking multiple heading keeps correct active buttons
  • Loading branch information
bantic committed Aug 4, 2015
1 parent 3e7e829 commit 8ded94f
Show file tree
Hide file tree
Showing 9 changed files with 154 additions and 39 deletions.
19 changes: 14 additions & 5 deletions src/js/commands/format-block.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,21 @@
import TextFormatCommand from './text-format';
import {
any
} from '../utils/array-utils';

class FormatBlockCommand extends TextFormatCommand {
constructor(options={}) {
constructor(editor, options={}) {
super(options);
this.editor = editor;
}

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

return any(activeSections, section => {
return any(this.mappedTags, t => section.tagName === t);
});
}

exec() {
Expand All @@ -15,8 +28,6 @@ class FormatBlockCommand extends TextFormatCommand {
});

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

editor.selectSections(activeSections);
}

Expand All @@ -29,8 +40,6 @@ class FormatBlockCommand extends TextFormatCommand {
});

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

editor.selectSections(activeSections);
}
}
Expand Down
4 changes: 2 additions & 2 deletions src/js/commands/heading.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import FormatBlockCommand from './format-block';

export default class HeadingCommand extends FormatBlockCommand {
constructor() {
constructor(editor) {
const options = {
name: 'heading',
tag: 'h2',
button: '<i class="ck-icon-heading"></i>1'
};
super(options);
super(editor, options);
}
}
18 changes: 8 additions & 10 deletions src/js/commands/quote.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
import FormatBlockCommand from './format-block';
import { inherit } from 'content-kit-utils';

function QuoteCommand() {
FormatBlockCommand.call(this, {
name: 'quote',
tag: 'blockquote',
button: '<i class="ck-icon-quote"></i>'
});
export default class QuoteCommand extends FormatBlockCommand {
constructor(editor) {
super(editor, {
name: 'quote',
tag: 'blockquote',
button: '<i class="ck-icon-quote"></i>'
});
}
}
inherit(QuoteCommand, FormatBlockCommand);

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

function SubheadingCommand() {
FormatBlockCommand.call(this, {
name: 'subheading',
tag: 'h3',
button: '<i class="ck-icon-heading"></i>2'
});
export default class SubheadingCommand extends FormatBlockCommand {
constructor(editor) {
super(editor, {
name: 'subheading',
tag: 'h3',
button: '<i class="ck-icon-heading"></i>2'
});
}
}
inherit(SubheadingCommand, FormatBlockCommand);

export default SubheadingCommand;
27 changes: 23 additions & 4 deletions src/js/editor/editor.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import TextFormatToolbar from '../views/text-format-toolbar';
import Tooltip from '../views/tooltip';
import EmbedIntent from '../views/embed-intent';

import ReversibleToolbarButton from '../views/reversible-toolbar-button';
import BoldCommand from '../commands/bold';
import ItalicCommand from '../commands/italic';
import LinkCommand from '../commands/link';
Expand Down Expand Up @@ -56,10 +57,7 @@ const defaults = {
textFormatCommands: [
new BoldCommand(),
new ItalicCommand(),
new LinkCommand(),
new QuoteCommand(),
new HeadingCommand(),
new SubheadingCommand()
new LinkCommand()
],
embedCommands: [
new ImageCommand({ serviceUrl: '/upload' }),
Expand Down Expand Up @@ -177,6 +175,23 @@ function initEmbedCommands(editor) {
}
}

function makeButtons(editor) {
const headingCommand = new HeadingCommand(editor);
const headingButton = new ReversibleToolbarButton(headingCommand, editor);

const subheadingCommand = new SubheadingCommand(editor);
const subheadingButton = new ReversibleToolbarButton(subheadingCommand, editor);

const quoteCommand = new QuoteCommand(editor);
const quoteButton = new ReversibleToolbarButton(quoteCommand, editor);

return [
headingButton,
subheadingButton,
quoteButton
];
}

/**
* @class Editor
* An individual Editor
Expand Down Expand Up @@ -225,7 +240,10 @@ class Editor {
this.addView(new TextFormatToolbar({
editor: this,
rootElement: element,
// FIXME -- eventually all the commands should migrate to being buttons
// that can be added
commands: this.textFormatCommands,
buttons: makeButtons(this),
sticky: this.stickyToolbar
}));

Expand Down Expand Up @@ -430,6 +448,7 @@ class Editor {

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

getActiveSections() {
Expand Down
4 changes: 3 additions & 1 deletion src/js/models/cursor.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,9 @@ export default class Cursor {
const { rangeCount } = selection;
const range = rangeCount > 0 && selection.getRangeAt(0);

if (!range) { throw new Error('Unable to get activeSections because no range'); }
if (!range) {
return [];
}

const { startContainer, endContainer } = range;
const isSectionElement = (element) => {
Expand Down
62 changes: 62 additions & 0 deletions src/js/views/reversible-toolbar-button.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import mixin from '../utils/mixin';
import EventListenerMixin from '../utils/event-listener';

const ELEMENT_TYPE = 'button';
const BUTTON_CLASS_NAME = 'ck-toolbar-btn';

class ReversibleToolbarButton {
constructor(command, editor) {
this.command = command;
this.editor = editor;
this.element = this.createElement();
this.active = false;

this.addEventListener(this.element, 'mouseup', e => this.handleClick(e));
this.editor.on('selection', () => this.updateActiveState());
this.editor.on('selectionUpdated', () => this.updateActiveState());
this.editor.on('selectionEnded', () => this.updateActiveState());
}

// These are here to match the API of the ToolbarButton class
setInactive() {}
setActive() {}

handleClick(e) {
e.stopPropagation();

if (this.active) {
this.command.unexec();
} else {
this.command.exec();
}
}

updateActiveState() {
this.active = this.command.isActive();
}

createElement() {
const element = document.createElement(ELEMENT_TYPE);
element.className = BUTTON_CLASS_NAME;
element.innerHTML = this.command.button;
element.title = this.command.name;
return element;
}

set active(val) {
this._active = val;
if (this._active) {
this.element.className = BUTTON_CLASS_NAME + ' active';
} else {
this.element.className = BUTTON_CLASS_NAME;
}
}

get active() {
return this._active;
}
}

mixin(ReversibleToolbarButton, EventListenerMixin);

export default ReversibleToolbarButton;
12 changes: 6 additions & 6 deletions src/js/views/toolbar.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,6 @@ class Toolbar extends View {
options.classNames = ['ck-toolbar'];
super(options);

let commands = options.commands;
let commandCount = commands && commands.length;

this.setDirection(options.direction || ToolbarDirection.TOP);
this.editor = options.editor || null;
this.embedIntent = options.embedIntent || null;
Expand All @@ -50,9 +47,8 @@ class Toolbar extends View {
this.contentElement.appendChild(this.buttonContainerElement);
this.element.appendChild(this.contentElement);

for(let i = 0; i < commandCount; i++) {
this.addCommand(commands[i]);
}
(options.buttons || []).forEach(b => this.addButton(b));
(options.commands || []).forEach(c => this.addCommand(c));

// Closes prompt if displayed when changing selection
this.addEventListener(document, 'mouseup', () => {
Expand All @@ -73,6 +69,10 @@ class Toolbar extends View {
command.editor = this.editor;
command.embedIntent = this.embedIntent;
let button = new ToolbarButton({command: command, toolbar: this});
this.addButton(button);
}

addButton(button) {
this.buttons.push(button);
this.buttonContainerElement.appendChild(button.element);
}
Expand Down
29 changes: 28 additions & 1 deletion tests/acceptance/editor-commands-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,8 @@ test('highlighting heading text activates toolbar button', (assert) => {
Helpers.dom.triggerEvent(document, 'mouseup');

setTimeout(() => {
assertActiveToolbarButton(assert, 'heading');
assertActiveToolbarButton(assert, 'heading',
'heading button is active when text is selected');

done();
});
Expand Down Expand Up @@ -159,6 +160,32 @@ test('when heading text is highlighted, clicking heading button turns it to plai
});
});

test('clicking multiple heading buttons keeps the correct ones active', (assert) => {
const done = assert.async();

setTimeout(() => {
// click subheading, makes its button active, changes the display
clickToolbarButton(assert, 'subheading');
assert.hasElement('#editor h3:contains(THIS IS A TEST)');
assertActiveToolbarButton(assert, 'subheading');
assertInactiveToolbarButton(assert, 'heading');

// click heading, makes its button active and no others, changes display
clickToolbarButton(assert, 'heading');
assert.hasElement('#editor h2:contains(THIS IS A TEST)');
assertActiveToolbarButton(assert, 'heading');
assertInactiveToolbarButton(assert, 'subheading');

// click heading again, removes headline from display, no active buttons
clickToolbarButton(assert, 'heading');
assert.hasElement('#editor p:contains(THIS IS A TEST)');
assertInactiveToolbarButton(assert, 'heading');
assertInactiveToolbarButton(assert, 'subheading');

done();
});
});

test('highlight text, click "subheading" button turns text into h3 header', (assert) => {
const done = assert.async();

Expand Down

0 comments on commit 8ded94f

Please sign in to comment.