Skip to content

Commit

Permalink
section#clone, postEditor#moveSectionBefore moveSectionUp moveSection…
Browse files Browse the repository at this point in the history
…Down

Remove unused builder#createBlankPost -- it added an empty markup
section, which in addition to being unnecessary was causing issues with
usage of content-kit-editor in other apps that expected loading an
editor with no mobiledoc would create a completely empty editor
mobiledoc.
  • Loading branch information
bantic committed Sep 23, 2015
1 parent b53ae7a commit 099bc21
Show file tree
Hide file tree
Showing 14 changed files with 214 additions and 15 deletions.
6 changes: 5 additions & 1 deletion CARDS.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,8 @@ var exampleCard = {
The content for the card should be pushed on that array as a string.
* `options` is the `cardOptions` argument passed to the editor or renderer.
* `env` contains information about the running of this hook. It may contain
the following functions:
the following properties:
* `env.name` The name of this card
* `env.save(payload)` will save a new payload for a card instance, then
swap a card in edit mode to display.
* `env.cancel()` will swap a card in edit mode to display without changing
Expand All @@ -76,6 +77,9 @@ var exampleCard = {
* `env.remove()` remove this card. This calls the current mode's `teardown()`
hook and removes the card from DOM and from the post abstract.
the instance to edit mode.
* `env.section` the CardSection from the Post -- this can be used when
programmatically interacting with the card, for example to move the card
using `editor.run(postEditor => postEditor.moveSectionUp(section)`.
* `payload` is the payload for this card instance. It was either loaded from
a Mobiledoc or generated and passed into an `env.save` call.

Expand Down
2 changes: 1 addition & 1 deletion src/js/editor/editor.js
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ class Editor {
}
return new DOMParser(this.builder).parse(this.html);
} else {
return this.builder.createBlankPost();
return this.builder.createPost();
}
}

Expand Down
34 changes: 34 additions & 0 deletions src/js/editor/post.js
Original file line number Diff line number Diff line change
Expand Up @@ -509,6 +509,40 @@ class PostEditor {
}
}

moveSectionBefore(collection, renderedSection, beforeSection) {
const newSection = renderedSection.clone();
this.removeSection(renderedSection);
this.insertSectionBefore(collection, newSection, beforeSection);
}

/**
* @method moveSectionUp
* @param {Section} section A section that is already in DOM
* @public
*/
moveSectionUp(renderedSection) {
const isFirst = !renderedSection.prev;
if (isFirst) { return; }

const collection = renderedSection.parent.sections;
const beforeSection = renderedSection.prev;
this.moveSectionBefore(collection, renderedSection, beforeSection);
}

/**
* @method moveSectionDown
* @param {Section} section A section that is already in DOM
* @public
*/
moveSectionDown(renderedSection) {
const isLast = !renderedSection.next;
if (isLast) { return; }

const beforeSection = renderedSection.next.next;
const collection = renderedSection.parent.sections;
this.moveSectionBefore(collection, renderedSection, beforeSection);
}

_replaceSection(section, newSections) {
let nextSection = section.next;
let collection = section.parent.sections;
Expand Down
6 changes: 6 additions & 0 deletions src/js/models/_markerable.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@ export default class Markerable extends Section {
markers.forEach(m => this.markers.append(m));
}

clone() {
const newMarkers = this.markers.map(m => m.clone());
return this.builder.createMarkerableSection(
this.type, this.tagName, newMarkers);
}

get isBlank() {
if (!this.markers.length) {
return true;
Expand Down
4 changes: 4 additions & 0 deletions src/js/models/_section.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ export default class Section extends LinkedItem {
return this._tagName;
}

clone() {
throw new Error('clone() must be implemented by subclass');
}

immediatelyNextMarkerableSection() {
const next = this.next;
if (next) {
Expand Down
3 changes: 2 additions & 1 deletion src/js/models/card-node.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ export default class CardNode {
this.display();
},
cancel: () => this.display(),
remove: () => this.remove()
remove: () => this.remove(),
section: this.section
};
}

Expand Down
4 changes: 4 additions & 0 deletions src/js/models/card.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,8 @@ export default class Card extends Section {
this.name = name;
this.payload = payload;
}

clone() {
return this.builder.createCardSection(this.name, this.payload);
}
}
19 changes: 16 additions & 3 deletions src/js/models/post-node-builder.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ import Markup from '../models/markup';
import Card from '../models/card';
import { normalizeTagName } from '../utils/dom-utils';
import { objectToSortedKVArray } from '../utils/array-utils';
import {
LIST_ITEM_TYPE,
MARKUP_SECTION_TYPE
} from '../models/types';
import {
DEFAULT_TAG_NAME as DEFAULT_MARKUP_SECTION_TAG_NAME
} from '../models/markup-section';
Expand Down Expand Up @@ -43,8 +47,15 @@ export default class PostNodeBuilder {
return post;
}

createBlankPost() {
return this.createPost([this.createMarkupSection()]);
createMarkerableSection(type, tagName, markers=[]) {
switch (type) {
case LIST_ITEM_TYPE:
return this.createListItem(tagName, markers);
case MARKUP_SECTION_TYPE:
return this.createMarkupSection(tagName, markers);
default:
throw new Error(`Cannot create markerable section of type ${type}`);
}
}

createMarkupSection(tagName=DEFAULT_MARKUP_SECTION_TAG_NAME, markers=[], isGenerated=false) {
Expand Down Expand Up @@ -80,7 +91,9 @@ export default class PostNodeBuilder {
}

createCardSection(name, payload={}) {
return new Card(name, payload);
const card = new Card(name, payload);
card.builder = this;
return card;
}

createMarker(value, markups=[]) {
Expand Down
5 changes: 5 additions & 0 deletions src/js/utils/linked-list.js
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,11 @@ export default class LinkedList {
item = item.next;
}
}
map(callback) {
let result = [];
this.forEach(i => result.push(callback(i)));
return result;
}
walk(startItem, endItem, callback) {
let item = startItem || this.head;
while (item) {
Expand Down
2 changes: 0 additions & 2 deletions tests/acceptance/basic-editor-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,6 @@ test('sets element as contenteditable', (assert) => {
assert.equal(editorElement.getAttribute('contenteditable'),
'true',
'element is contenteditable');
assert.equal(editorElement.firstChild.tagName, 'P',
`editor element has a P as its first child`);
});

test('#disableEditing before render is meaningful', (assert) => {
Expand Down
28 changes: 28 additions & 0 deletions tests/unit/editor/card-lifecycle-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,34 @@ test('rendering a mobiledoc for editing calls card#setup', (assert) => {
editor.render(editorElement);
});

test('rendered card env has `name`, `edit`, `save`, `remove`, `section', (assert) => {
let cardEnv;

const cardName = 'test-card';
const cards = [{
name: cardName,
display: {
setup(element, options, env) { cardEnv = env; },
teardown() {}
}
}];

const mobiledoc = Helpers.mobiledoc.build(({post, cardSection}) => {
return post([cardSection('test-card')]);
});
editor = new Editor({mobiledoc, cards});
editor.render(editorElement);

assert.ok(!!cardEnv, 'card env is present');
assert.equal(cardEnv.name, cardName, 'env name is correct');
assert.ok(!!cardEnv.edit, 'has edit hook');
assert.ok(!!cardEnv.save, 'has save hook');
assert.ok(!!cardEnv.cancel, 'has cancel hook');
assert.ok(!!cardEnv.remove, 'has remove hook');
const cardSection = editor.post.sections.head;
assert.ok(cardEnv.section && cardEnv.section === cardSection, 'has `section`');
});

test('rendering a mobiledoc for editing calls #unknownCardHandler when it encounters an unknown card', (assert) => {
assert.expect(1);

Expand Down
12 changes: 6 additions & 6 deletions tests/unit/editor/editor-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@ import { EDITOR_ELEMENT_CLASS_NAME } from 'content-kit-editor/editor/editor';
import { normalizeTagName } from 'content-kit-editor/utils/dom-utils';
import { MOBILEDOC_VERSION } from 'content-kit-editor/renderers/mobiledoc';
import Range from 'content-kit-editor/utils/cursor/range';
import Helpers from '../../test-helpers';

const { module, test } = window.QUnit;
const { module, test } = Helpers;

let fixture, editorElement, editor;

Expand All @@ -29,10 +30,6 @@ test('can render an editor via dom node reference', (assert) => {
editor.render(editorElement);
assert.equal(editor.element, editorElement);
assert.ok(editor.post);
assert.equal(editor.post.sections.length, 1);
assert.equal(editor.post.sections.head.tagName, 'p');
assert.equal(editor.post.sections.head.markers.length, 0);
assert.equal(editor.post.sections.head.text, '');
});

test('creating an editor with DOM node throws', (assert) => {
Expand Down Expand Up @@ -84,7 +81,10 @@ test('editor fires lifecycle hooks', (assert) => {

test('editor fires lifecycle hooks for edit', (assert) => {
assert.expect(4);
editor = new Editor();
const mobiledoc = Helpers.mobiledoc.build(({post, markupSection}) => {
return post([markupSection()]);
});
editor = new Editor({mobiledoc});
editor.render(editorElement);

let didCallUpdatePost, didCallWillRender, didCallDidRender;
Expand Down
99 changes: 98 additions & 1 deletion tests/unit/editor/post-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -668,7 +668,7 @@ test('markers with identical markups get coalesced after deletion', (assert) =>
section = markupSection('p', [marker('a'), marker('b',[strong]), marker('c')]);
return post([section]);
});
renderBuiltAbstract(post);
let mockEditor = renderBuiltAbstract(post);

let range = Range.create(section, 1, section, 2);
postEditor = new PostEditor(mockEditor);
Expand All @@ -678,3 +678,100 @@ test('markers with identical markups get coalesced after deletion', (assert) =>
assert.equal(section.markers.length, 1, 'similar markers are coalesced');
assert.equal(section.markers.head.value, 'ac', 'marker value is correct');
});

test('#moveSectionBefore moves the section as expected', (assert) => {
const post = Helpers.postAbstract.build(({post, markupSection, marker}) => {
return post([
markupSection('p', [marker('abc')]),
markupSection('p', [marker('123')])
]);
});
let mockEditor = renderBuiltAbstract(post);

const [headSection, tailSection] = post.sections.toArray();
const collection = post.sections;
postEditor = new PostEditor(mockEditor);
postEditor.moveSectionBefore(collection, tailSection, headSection);
postEditor.complete();

assert.equal(post.sections.head.text, '123', 'tail section is now head');
assert.equal(post.sections.tail.text, 'abc', 'head section is now tail');
});

test('#moveSectionBefore moves card sections', (assert) => {
const listiclePayload = {some:'thing'};
const otherPayload = {some:'other thing'};
const post = Helpers.postAbstract.build(({post, cardSection}) => {
return post([
cardSection('listicle-card', listiclePayload),
cardSection('other-card', otherPayload)
]);
});
let mockEditor = renderBuiltAbstract(post);

const collection = post.sections;
let [headSection, tailSection] = post.sections.toArray();
postEditor = new PostEditor(mockEditor);
postEditor.moveSectionBefore(collection, tailSection, headSection);
postEditor.complete();

([headSection, tailSection] = post.sections.toArray());
assert.equal(headSection.name, 'other-card', 'other-card moved to first spot');
assert.equal(tailSection.name, 'listicle-card', 'listicle-card moved to last spot');
assert.deepEqual(headSection.payload, otherPayload, 'payload is correct for other-card');
assert.deepEqual(tailSection.payload, listiclePayload, 'payload is correct for listicle-card');
});

test('#moveSectionUp moves it up', (assert) => {
const post = Helpers.postAbstract.build(({post, cardSection}) => {
return post([
cardSection('listicle-card'),
cardSection('other-card')
]);
});
let mockEditor = renderBuiltAbstract(post);

let [headSection, tailSection] = post.sections.toArray();
postEditor = new PostEditor(mockEditor);
postEditor.moveSectionUp(tailSection);
postEditor.complete();

([headSection, tailSection] = post.sections.toArray());
assert.equal(headSection.name, 'other-card', 'other-card moved to first spot');
assert.equal(tailSection.name, 'listicle-card', 'listicle-card moved to last spot');

postEditor = new PostEditor(mockEditor);
postEditor.moveSectionUp(headSection);
postEditor.complete();

([headSection, tailSection] = post.sections.toArray());
assert.equal(headSection.name, 'other-card', 'moveSectionUp is no-op when card is at top');
});

test('moveSectionDown moves it down', (assert) => {
const post = Helpers.postAbstract.build(({post, cardSection}) => {
return post([
cardSection('listicle-card'),
cardSection('other-card')
]);
});
let mockEditor = renderBuiltAbstract(post);

let [headSection, tailSection] = post.sections.toArray();
postEditor = new PostEditor(mockEditor);
postEditor.moveSectionDown(headSection);
postEditor.complete();

([headSection, tailSection] = post.sections.toArray());
assert.equal(headSection.name, 'other-card', 'other-card moved to first spot');
assert.equal(tailSection.name, 'listicle-card', 'listicle-card moved to last spot');

postEditor = new PostEditor(mockEditor);
postEditor.moveSectionDown(tailSection);
postEditor.complete();

([headSection, tailSection] = post.sections.toArray());
assert.equal(tailSection.name, 'listicle-card',
'moveSectionDown is no-op when card is at bottom');

});
5 changes: 5 additions & 0 deletions tests/unit/models/post-node-builder-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,8 @@ test('#createMarkup normalizes tagName', (assert) => {
m3 === m4, 'all markups are the same');
});

test('#createCardSection creates card with builder', (assert) => {
const builder = new PostNodeBuilder();
const cardSection = builder.createCardSection('test-card');
assert.ok(cardSection.builder === builder, 'card section has builder');
});

0 comments on commit 099bc21

Please sign in to comment.