Skip to content

Commit

Permalink
Add editor#editCard and editor#displayCard
Browse files Browse the repository at this point in the history
Cards normally render into "display" mode by default. Calling
`editor#editCard` before the card is rendered will change it so that the
card gets rendered in edit mode initially. If the card has already been
rendered, it switches it to edit mode.
  • Loading branch information
bantic committed Oct 15, 2015
1 parent 1be2d1d commit 2ef19f1
Show file tree
Hide file tree
Showing 10 changed files with 267 additions and 2 deletions.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,10 @@ editor.render(element);
current post with their cursor. Programmatic edits are still allowed.
* `editor.enableEditing()` - allow the user to make direct edits directly
to a post's text.
* `editor.editCard(cardSection)` - change the card to its edit mode (will change
immediately if the card is already rendered, or will ensure that when the card
does get rendered it will be rendered in the "edit" state initially)
* `editor.displayCard(cardSection)` - same as `editCard` except in display mode.

### Editor Lifecycle Hooks

Expand Down
36 changes: 36 additions & 0 deletions src/js/editor/editor.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import {
} from './key-commands';
import { capitalize } from '../utils/string-utils';
import LifecycleCallbacksMixin from '../utils/lifecycle-callbacks';
import { CARD_MODES } from '../models/card';

export const EDITOR_ELEMENT_CLASS_NAME = 'ck-editor';

Expand Down Expand Up @@ -416,6 +417,30 @@ class Editor {
}
}

/**
* Change a cardSection into edit mode
* If called before the card has been rendered, it will be marked so that
* it is rendered in edit mode when it gets rendered.
* @param {CardSection} cardSection
* @return undefined
* @public
*/
editCard(cardSection) {
this._setCardMode(cardSection, CARD_MODES.EDIT);
}

/**
* Change a cardSection into display mode
* If called before the card has been rendered, it will be marked so that
* it is rendered in display mode when it gets rendered.
* @param {CardSection} cardSection
* @return undefined
* @public
*/
displayCard(cardSection) {
this._setCardMode(cardSection, CARD_MODES.DISPLAY);
}

/**
* Run a new post editing session. Yields a block with a new `postEditor`
* instance. This instance can be used to interact with the post abstract,
Expand Down Expand Up @@ -635,6 +660,17 @@ class Editor {
handlePaste(event) {
event.preventDefault(); // FIXME for now, just prevent pasting
}

// @private
_setCardMode(cardSection, mode) {
const renderNode = this._renderTree.getRenderNode(cardSection);
if (renderNode && renderNode.isRendered) {
const cardNode = renderNode.cardNode;
cardNode[mode]();
} else {
cardSection.setInitialMode(mode);
}
}
}

mixin(Editor, EventEmitter);
Expand Down
21 changes: 20 additions & 1 deletion src/js/models/card.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,33 @@
import Section from './_section';
import { CARD_TYPE } from './types';
import { shallowCopyObject } from '../utils/copy';

export const CARD_MODES = {
DISPLAY: 'display',
EDIT: 'edit'
};

const DEFAULT_INITIAL_MODE = CARD_MODES.DISPLAY;

export default class Card extends Section {
constructor(name, payload) {
super(CARD_TYPE);
this.name = name;
this.payload = payload;
this.setInitialMode(DEFAULT_INITIAL_MODE);
}

clone() {
return this.builder.createCardSection(this.name, this.payload);
const payload = shallowCopyObject(this.payload);
return this.builder.createCardSection(this.name, payload);
}

/**
* set the mode that this will be rendered into initially
* @private
*/
setInitialMode(initialMode) {
// TODO validate initialMode
this._initialMode = initialMode;
}
}
3 changes: 3 additions & 0 deletions src/js/models/render-node.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ export default class RenderNode extends LinkedItem {
this.isDirty = true;
if (this.parent) { this.parent.markDirty(); }
}
get isRendered() {
return !!this.element;
}
markClean() {
this.isDirty = false;
}
Expand Down
3 changes: 3 additions & 0 deletions src/js/models/render-tree.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,7 @@ export default class RenderTree {
postNode.renderNode = renderNode;
return renderNode;
}
getRenderNode(postNode) {
return postNode.renderNode;
}
}
3 changes: 2 additions & 1 deletion src/js/renderers/editor-dom.js
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,8 @@ class Visitor {
const cardNode = new CardNode(
editor, card, section, renderNode.element, options);
renderNode.cardNode = cardNode;
cardNode.display();
const initialMode = section._initialMode;
cardNode[initialMode]();
} else {
const env = { name: section.name };
this.unknownCardHandler(
Expand Down
11 changes: 11 additions & 0 deletions src/js/utils/copy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
function shallowCopyObject(object) {
let copy = {};
Object.keys(object).forEach(key => {
copy[key] = object[key];
});
return copy;
}

export {
shallowCopyObject
};
137 changes: 137 additions & 0 deletions tests/acceptance/editor-post-editor-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,143 @@ test('#insertSection inserts at end when no active cursor section', (assert) =>
assert.hasElement('#editor p:eq(1):contains(def)', '2nd section -> same spot');
});

test('#insertSection can insert card, render it in display mode', (assert) => {
const mobiledoc = Helpers.mobiledoc.build(({post, markupSection, marker}) => {
return post([markupSection('p', [marker('abc')])]);
});

let displayedCard = false;
let cards = [{
name: 'sample-card',
display: {
setup() {
displayedCard = true;
}
}
}];

editor = new Editor({mobiledoc, cards});
editor.render(editorElement);

editor.run(postEditor => {
let cardSection = postEditor.builder.createCardSection('sample-card');
postEditor.insertSection(cardSection);
});

assert.ok(displayedCard, 'rendered card in display mode');
});

test('#insertSection inserts card, can render it in edit mode using #editCard', (assert) => {
const mobiledoc = Helpers.mobiledoc.build(({post, markupSection, marker}) => {
return post([markupSection('p', [marker('abc')])]);
});

let displayedCard = false,
editCard = false;
let cards = [{
name: 'sample-card',
display: {
setup() {
displayedCard = true;
}
},
edit: {
setup() {
editCard = true;
}
}
}];

editor = new Editor({mobiledoc, cards});
editor.render(editorElement);

editor.run(postEditor => {
let cardSection = postEditor.builder.createCardSection('sample-card');
postEditor.insertSection(cardSection);
editor.editCard(cardSection);
});

assert.ok(editCard, 'rendered card in edit mode');
assert.ok(!displayedCard, 'did not render in display mode');
});

test('after inserting a section, can use editor#editCard to switch it to edit mode', (assert) => {
const mobiledoc = Helpers.mobiledoc.build(({post, cardSection}) => {
return post([cardSection('sample-card')]);
});

let displayedCard = false,
editedCard = false;
let cards = [{
name: 'sample-card',
display: {
setup() {
displayedCard = true;
}
},
edit: {
setup() {
editedCard = true;
}
}
}];

editor = new Editor({mobiledoc, cards});
editor.render(editorElement);
assert.ok(displayedCard, 'called display#setup');
assert.ok(!editedCard, 'did not call edit#setup yet');

displayedCard = false;
const card = editor.post.sections.head;
editor.editCard(card);

assert.ok(editedCard, 'called edit#setup');
assert.ok(!displayedCard, 'did not call display#setup again');
});

test('can call editor#displayCard to swtich card into display mode', (assert) => {
const mobiledoc = Helpers.mobiledoc.build(({post, cardSection}) => {
return post([cardSection('sample-card')]);
});

let displayedCard = false,
editedCard = false;

let cards = [{
name: 'sample-card',
display: {
setup() {
displayedCard = true;
}
},
edit: {
setup() {
editedCard = true;
}
}
}];

editor = new Editor({mobiledoc, cards});
editor.render(editorElement);

assert.ok(displayedCard, 'precond - called display#setup');
assert.ok(!editedCard, 'precond - did not call edit#setup yet');

displayedCard = false;
const card = editor.post.sections.head;
editor.editCard(card);

assert.ok(!displayedCard, 'card not in display mode');
assert.ok(editedCard, 'card in edit mode');

editedCard = false;

editor.displayCard(card);

assert.ok(displayedCard, 'card back in display mode');
assert.ok(!editedCard, 'card not in edit mode');
});

test('#toggleMarkup adds markup by tag name', (assert) => {
const mobiledoc = Helpers.mobiledoc.build(({post, markupSection, marker}) => {
return post([
Expand Down
33 changes: 33 additions & 0 deletions tests/unit/models/card-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
const {module, test} = QUnit;

import PostNodeBuilder from 'content-kit-editor/models/post-node-builder';

let builder;
module('Unit: Card', {
beforeEach() {
builder = new PostNodeBuilder();
},
afterEach() {
builder = null;
}
});

test('can create a card with payload', (assert) => {
const payload = {};
const card = builder.createCardSection('card-name', payload);
assert.ok(!!card, 'creates card');
assert.ok(card.payload === payload, 'has payload');
});

test('cloning a card copies payload', (assert) => {
const payload = {foo:'bar'};

const card = builder.createCardSection('card-name', payload);
const card2 = card.clone();

assert.ok(card !== card2, 'card !== cloned');
assert.ok(card.payload !== card2.payload, 'payload is copied');

card.payload.foo = 'other foo';
assert.equal(card2.payload.foo, 'bar', 'card2 payload not updated');
});
18 changes: 18 additions & 0 deletions tests/unit/utils/copy-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import Helpers from '../../test-helpers';
import { shallowCopyObject } from 'content-kit-editor/utils/copy';

const {module, test} = Helpers;

module('Unit: Utils: copy');

test('#shallowCopyObject breaks references', (assert) => {
let obj = {a: 1, b:'b'};
let obj2 = shallowCopyObject(obj);
obj.a = 2;
obj.b = 'new b';

assert.ok(obj !== obj2, 'obj !== obj2');
assert.equal(obj2.a, 1, 'obj2 "a" preserved');
assert.equal(obj2.b, 'b', 'obj2 "b" preserved');
});

0 comments on commit 2ef19f1

Please sign in to comment.