Skip to content

Commit

Permalink
Add ListSection, ListItem, bump MOBILEDOC_VERSION -> 0.2.0
Browse files Browse the repository at this point in the history
  * simplify editor reparse
  * update post parser to reparse list sections
  * hit enter to split a list item
  * can delete to exit a list
  * hitting enter in empty list item exits list section altogether
  * Use Helpers.dom.build rather than `makeDOM`
  * Test to ensure that hitting enter in a list exits the list appropriately
  • Loading branch information
bantic committed Aug 27, 2015
1 parent 789e252 commit 44494f0
Show file tree
Hide file tree
Showing 32 changed files with 987 additions and 307 deletions.
2 changes: 1 addition & 1 deletion .jshintrc
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
"undef" : true, // Require all non-global variables be declared before they are used.
"unused" : true, // Warn when variables are created but not used.
"trailing" : true, // Prohibit trailing whitespaces.
"es3" : false, // Prohibit trailing commas for old IE
"es3" : true, // Prohibit trailing commas for old IE
"esnext" : true, // Allow ES.next specific features such as `const` and `let`.

// == Relaxing Options ================================================
Expand Down
15 changes: 6 additions & 9 deletions demo/demo.js
Original file line number Diff line number Diff line change
Expand Up @@ -326,7 +326,7 @@ function attemptEditorReboot(editor, textPayload) {

var MOBILEDOC_VERSION = "0.1";
var sampleMobiledocs = {
xsimpleMobiledoc: {
simpleMobiledoc: {
version: MOBILEDOC_VERSION,
sections: [
[],
Expand All @@ -341,21 +341,18 @@ var sampleMobiledocs = {
]
},

//simpleMobiledocWithList: {
simpleMobiledoc: {
simpleMobiledocWithList: {
version: MOBILEDOC_VERSION,
sections: [
[],
[
[1, "H2", [
[[], 0, "To do today:"]
]],
[1, "UL", [
[
[[], 0, "buy milk"],
[[], 0, "water cows"],
[[], 0, "world domination"]
]
[3, 'ul', [
[[[], 0, 'buy milk']],
[[[], 0, 'water plants']],
[[[], 0, 'world domination']]
]]
]
]
Expand Down
1 change: 1 addition & 0 deletions demo/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ <h2>Try a Demo</h2>
<select id='select-mobiledoc'>
<option disabled>Load a new Mobiledoc</option>
<option value='simpleMobiledoc'>Simple text content</option>
<option value='simpleMobiledocWithList'>List example</option>
<option value='mobileDocWithInputCard'>Card with Input</option>
<option value='mobileDocWithSelfieCard'>Selfie Card</option>
</select>
Expand Down
3 changes: 2 additions & 1 deletion src/js/commands/image.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,13 @@ export default class ImageCommand extends Command {
let beforeSection = headMarker.section;
let afterSection = beforeSection.next;
let section = this.editor.builder.createCardSection('image');
const collection = beforeSection.parent.sections;

this.editor.run((postEditor) => {
if (beforeSection.isBlank) {
postEditor.removeSection(beforeSection);
}
postEditor.insertSectionBefore(section, afterSection);
postEditor.insertSectionBefore(collection, section, afterSection);
});
}
}
90 changes: 20 additions & 70 deletions src/js/editor/editor.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ import {
} from '../utils/dom-utils';
import {
forEach,
detect
filter
} from '../utils/array-utils';
import { getData, setData } from '../utils/element-utils';
import mixin from '../utils/mixin';
Expand Down Expand Up @@ -446,78 +446,27 @@ class Editor {
}

reparse() {
// find added sections
let sectionsInDOM = [];
let newSections = [];
let previousSection;

forEach(this.element.childNodes, (node) => {
// FIXME: this is kind of slow
let sectionRenderNode = detect(this._renderTree.node.childNodes, (renderNode) => {
return renderNode.element === node;
});
if (!sectionRenderNode) {
let section = this._parser.parseSection(node);
newSections.push(section);

// create a clean "already-rendered" node to represent the fact that
// this (new) section is already in DOM
sectionRenderNode = this._renderTree.buildRenderNode(section);
sectionRenderNode.element = node;
sectionRenderNode.markClean();

let previousSectionRenderNode = previousSection && previousSection.renderNode;
this.post.sections.insertAfter(section, previousSection);
this._renderTree.node.childNodes.insertAfter(sectionRenderNode, previousSectionRenderNode);
}

// may cause duplicates to be included
let section = sectionRenderNode.postNode;
sectionsInDOM.push(section);
previousSection = section;
});

// remove deleted nodes
const deletedSections = [];
forEach(this.post.sections, (section) => {
if (!section.renderNode) {
throw new Error('All sections are expected to have a renderNode');
}

if (sectionsInDOM.indexOf(section) === -1) {
deletedSections.push(section);
}
});
forEach(deletedSections, (s) => s.renderNode.scheduleForRemoval());

// reparse the new section(s) with the cursor
// to ensure that we catch any changed html that the browser might have
// added
const sectionsWithCursor = this.cursor.activeSections;
forEach(sectionsWithCursor, (section) => {
if (newSections.indexOf(section) === -1) {
this.reparseSection(section);
}
});

let {
headSection,
headSectionOffset
} = this.cursor.sectionOffsets;
let { headSection, headSectionOffset } = this.cursor.offsets;
if (headSectionOffset === 0) {
// FIXME if the offset is 0, the user is typing the first character
// in an empty section, so we need to move the cursor 1 letter forward
headSectionOffset = 1;
}

// 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
// their cursor position.
let resetCursor = sectionsWithCursor.indexOf(headSection) !== -1;
this._reparseCurrentSection();
this._removeDetachedSections();

this.rerender();
this.trigger('update');

if (resetCursor) {
this.cursor.moveToSection(headSection, headSectionOffset);
}
this.cursor.moveToSection(headSection, headSectionOffset);
}

_removeDetachedSections() {
forEach(
filter(this.post.sections, s => !s.renderNode.isAttached()),
s => s.renderNode.scheduleForRemoval()
);
}

/*
Expand Down Expand Up @@ -571,8 +520,9 @@ class Editor {
section.renderNode.markDirty();
}

reparseSection(section) {
this._parser.reparseSection(section, this._renderTree);
_reparseCurrentSection() {
const {headSection:currentSection } = this.cursor.offsets;
this._parser.reparseSection(currentSection, this._renderTree);
}

serialize() {
Expand Down
87 changes: 69 additions & 18 deletions src/js/editor/post.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { MARKUP_SECTION_TYPE } from '../models/markup-section';
import { LIST_ITEM_TYPE } from '../models/list-item';
import {
filter
filter,
compact
} from '../utils/array-utils';

import { DIRECTION } from '../utils/key';
Expand All @@ -9,9 +11,18 @@ function isMarkupSection(section) {
return section.type === MARKUP_SECTION_TYPE;
}

function isListItem(section) {
return section.type === LIST_ITEM_TYPE;
}

function isBlankAndListItem(section) {
return isListItem(section) && section.isBlank;
}

class PostEditor {
constructor(editor) {
this.editor = editor;
this.builder = this.editor.builder;
this._completionWorkQueue = [];
this._didRerender = false;
this._didUpdate = false;
Expand Down Expand Up @@ -99,12 +110,11 @@ class PostEditor {
}

_coalesceMarkers(section) {
let {builder} = this.editor;
filter(section.markers, m => m.isEmpty).forEach(m => {
this.removeMarker(m);
});
if (section.markers.isEmpty) {
section.markers.append(builder.createBlankMarker());
section.markers.append(this.builder.createBlankMarker());
section.renderNode.markDirty();
}
}
Expand Down Expand Up @@ -191,6 +201,18 @@ class PostEditor {
};
}

_convertListItemToMarkupSection(listItem) {
const listSection = listItem.parent;

const newSections = listItem.splitIntoSections();
const newMarkupSection = newSections[1];

this._replaceSection(listSection, compact(newSections));

const newCursorPosition = {marker: newMarkupSection.markers.head, offset: 0};
return newCursorPosition;
}

/**
* delete 1 character in the BACKWARD direction from the given position
* @method _deleteBackwardFrom
Expand All @@ -206,10 +228,15 @@ class PostEditor {
if (prevMarker) {
return this._deleteBackwardFrom({marker: prevMarker, offset: prevMarker.length});
} else {
const prevSection = marker.section.prev;

if (prevSection) {
if (isMarkupSection(prevSection)) {
const section = marker.section;

if (isListItem(section)) {
const newCursorPos = this._convertListItemToMarkupSection(section);
nextCursorMarker = newCursorPos.marker;
nextCursorOffset = newCursorPos.offset;
} else {
const prevSection = section.prev;
if (prevSection && isMarkupSection(prevSection)) {
nextCursorMarker = prevSection.markers.tail;
nextCursorOffset = nextCursorMarker.length;

Expand All @@ -225,7 +252,6 @@ class PostEditor {
nextCursorOffset = 0;
}
}
// ELSE: FIXME: card section -- what should deleting into it do?
}
}

Expand Down Expand Up @@ -258,7 +284,7 @@ class PostEditor {
}

/**
* Split makers at two positions, once at the head, and if necessary once
* Split markers at two positions, once at the head, and if necessary once
* at the tail. This method is designed to accept `editor.cursor.offsets`
* as an argument.
*
Expand Down Expand Up @@ -375,17 +401,40 @@ class PostEditor {

const [beforeSection, afterSection] = section.splitAtMarker(headMarker, headMarkerOffset);

this._replaceSection(section, [beforeSection, afterSection]);
const newSections = [beforeSection, afterSection];
let replacementSections = [beforeSection, afterSection];

if (isBlankAndListItem(beforeSection) && isBlankAndListItem(section)) {
const isLastItemInList = section === section.parent.sections.tail;

if (isLastItemInList) {
// when hitting enter in a final empty list item, do not insert a new
// empty item
replacementSections.shift();
}
}

this._replaceSection(section, replacementSections);

this.scheduleRerender();
this.scheduleDidUpdate();

return [beforeSection, afterSection];
// FIXME we must return 2 sections because other code expects this to always return 2
return newSections;
}

_replaceSection(section, newSections) {
let nextSection = section.next;
newSections.forEach(s => this.insertSectionBefore(s, nextSection));
let collection = section.parent.sections;

let nextNewSection = newSections[0];
if (isMarkupSection(nextNewSection) && isListItem(section)) {
// put the new section after the ListSection (section.parent) instead of after the ListItem
collection = section.parent.parent.sections;
nextSection = section.parent.next;
}

newSections.forEach(s => this.insertSectionBefore(collection, s, nextSection));
this.removeSection(section);
}

Expand Down Expand Up @@ -472,18 +521,20 @@ class PostEditor {
* let markerRange = editor.cursor.offsets;
* let sectionWithCursor = markerRange.headMarker.section;
* let section = editor.builder.createCardSection('my-image');
* let collection = sectionWithCursor.parent.sections;
* editor.run((postEditor) => {
* postEditor.insertSectionBefore(section, sectionWithCursor);
* postEditor.insertSectionBefore(collection, section, sectionWithCursor);
* });
*
* @method insertSectionBefore
* @param {LinkedList} collection The list of sections to insert into
* @param {Object} section The new section
* @param {Object} beforeSection The section "before" is relative to
* @public
*/
insertSectionBefore(section, beforeSection) {
this.editor.post.sections.insertBefore(section, beforeSection);
this.editor.post.renderNode.markDirty();
insertSectionBefore(collection, section, beforeSection) {
collection.insertBefore(section, beforeSection);
section.parent.renderNode.markDirty();

this.scheduleRerender();
this.scheduleDidUpdate();
Expand All @@ -500,13 +551,13 @@ class PostEditor {
* postEditor.removeSection(sectionWithCursor);
* });
*
* @method insertSectionBefore
* @method removeSection
* @param {Object} section The section to remove
* @public
*/
removeSection(section) {
section.renderNode.scheduleForRemoval();
section.post.sections.remove(section);
section.parent.sections.remove(section);

this.scheduleRerender();
this.scheduleDidUpdate();
Expand Down
Loading

0 comments on commit 44494f0

Please sign in to comment.