Skip to content

Commit

Permalink
Parse ul and ols correctly
Browse files Browse the repository at this point in the history
fixes #183
  • Loading branch information
bantic committed Oct 27, 2015
1 parent caf8ffd commit 577b3db
Show file tree
Hide file tree
Showing 7 changed files with 172 additions and 36 deletions.
7 changes: 7 additions & 0 deletions src/js/models/list-item.js
@@ -1,5 +1,12 @@
import Markerable from './_markerable';
import { LIST_ITEM_TYPE } from './types';
import {
normalizeTagName
} from 'content-kit-editor/utils/dom-utils';

export const VALID_LIST_ITEM_TAGNAMES = [
'li'
].map(normalizeTagName);

export default class ListItem extends Markerable {
constructor(tagName, markers=[]) {
Expand Down
9 changes: 8 additions & 1 deletion src/js/models/list-section.js
Expand Up @@ -2,8 +2,15 @@ import LinkedList from '../utils/linked-list';
import { forEach } from '../utils/array-utils';
import { LIST_SECTION_TYPE } from './types';
import Section from './_section';
import {
normalizeTagName
} from 'content-kit-editor/utils/dom-utils';

export const DEFAULT_TAG_NAME = 'ul';
export const VALID_LIST_SECTION_TAGNAMES = [
'ul', 'ol'
].map(normalizeTagName);

export const DEFAULT_TAG_NAME = VALID_LIST_SECTION_TAGNAMES[0];

export default class ListSection extends Section {
constructor(tagName=DEFAULT_TAG_NAME, items=[]) {
Expand Down
8 changes: 4 additions & 4 deletions src/js/models/markup-section.js
Expand Up @@ -4,14 +4,14 @@ import { MARKUP_SECTION_TYPE } from './types';

// valid values of `tagName` for a MarkupSection
export const VALID_MARKUP_SECTION_TAGNAMES = [
'p', 'h3', 'h2', 'h1', 'blockquote', 'ul', 'ol', 'pull-quote'
'p', 'h3', 'h2', 'h1', 'blockquote', 'pull-quote'
].map(normalizeTagName);

// valid element names for a MarkupSection. A MarkupSection with a tagName
// not in this should be rendered as a div with a className matching the
// tagName, instead
// not in this will be rendered as a div with a className matching the
// tagName
export const MARKUP_SECTION_ELEMENT_NAMES = [
'p', 'h3', 'h2', 'h1', 'blockquote', 'ul', 'ol'
'p', 'h3', 'h2', 'h1', 'blockquote'
].map(normalizeTagName);
export const DEFAULT_TAG_NAME = VALID_MARKUP_SECTION_TAGNAMES[0];

Expand Down
3 changes: 1 addition & 2 deletions src/js/models/markup.js
Expand Up @@ -8,8 +8,7 @@ export const VALID_MARKUP_TAGNAMES = [
'i',
'strong',
'em',
'a',
'li'
'a'
].map(normalizeTagName);

export const VALID_ATTRIBUTES = [
Expand Down
109 changes: 82 additions & 27 deletions src/js/parsers/section.js
Expand Up @@ -6,6 +6,20 @@ import {
VALID_MARKUP_SECTION_TAGNAMES
} from 'content-kit-editor/models/markup-section';

import {
VALID_LIST_SECTION_TAGNAMES
} from 'content-kit-editor/models/list-section';

import {
VALID_LIST_ITEM_TAGNAMES
} from 'content-kit-editor/models/list-item';

import {
LIST_SECTION_TYPE,
LIST_ITEM_TYPE,
MARKUP_SECTION_TYPE
} from 'content-kit-editor/models/types';

import {
VALID_MARKUP_TAGNAMES
} from 'content-kit-editor/models/markup';
Expand All @@ -17,9 +31,18 @@ import {
} from 'content-kit-editor/utils/dom-utils';

import {
forEach
forEach,
contains
} from 'content-kit-editor/utils/array-utils';

function isListSection(section) {
return section.type === LIST_SECTION_TYPE;
}

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

/**
* parses an element into a section, ignoring any non-markup
* elements contained within
Expand All @@ -39,15 +62,30 @@ export default class SectionParser {

let childNodes = isTextNode(element) ? [element] : element.childNodes;

forEach(childNodes, el => {
this.parseNode(el);
});
if (isListSection(this.state.section)) {
this.parseListItems(childNodes);
} else {
forEach(childNodes, el => {
this.parseNode(el);
});
}

this._closeCurrentSection();

return this.sections;
}

parseListItems(childNodes) {
let { state } = this;
forEach(childNodes, el => {
let parsed = new this.constructor(this.builder).parse(el);
let li = parsed[0];
if (li && isListItem(li)) {
state.section.items.append(li);
}
});
}

parseNode(node) {
if (!this.state.section) {
this._updateStateFromElement(node);
Expand Down Expand Up @@ -85,6 +123,7 @@ export default class SectionParser {
if (parsedCard) {
return;
}

const markups = this._markupsFromElement(element);
if (markups.length && state.text.length) {
this._createMarker();
Expand Down Expand Up @@ -188,39 +227,55 @@ export default class SectionParser {
state.text = '';
}

_sectionTagNameFromElement(element) {
_getSectionDetails(element) {
let sectionType,
tagName,
inferredTagName = false;
if (isTextNode(element)) {
return null;
}
let tagName;

let elementTagName = normalizeTagName(element.tagName);

if (VALID_MARKUP_SECTION_TAGNAMES.indexOf(elementTagName) !== -1) {
tagName = elementTagName;
tagName = DEFAULT_TAG_NAME;
sectionType = MARKUP_SECTION_TYPE;
inferredTagName = true;
} else {
tagName = normalizeTagName(element.tagName);

if (contains(VALID_LIST_SECTION_TAGNAMES, tagName)) {
sectionType = LIST_SECTION_TYPE;
} else if (contains(VALID_LIST_ITEM_TAGNAMES, tagName)) {
sectionType = LIST_ITEM_TYPE;
} else if (contains(VALID_MARKUP_SECTION_TAGNAMES, tagName)) {
sectionType = MARKUP_SECTION_TYPE;
} else {
sectionType = MARKUP_SECTION_TYPE;
tagName = DEFAULT_TAG_NAME;
inferredTagName = true;
}
}

return tagName;
}

_inferSectionTagNameFromElement(/* element */) {
return DEFAULT_TAG_NAME;
return {sectionType, tagName, inferredTagName};
}

_createSectionFromElement(element) {
let { builder } = this;

let inferredTagName = false;
let tagName = this._sectionTagNameFromElement(element);
if (!tagName) {
inferredTagName = true;
tagName = this._inferSectionTagNameFromElement(element);
}
let section = builder.createMarkupSection(tagName);
let section;
let {tagName, sectionType, inferredTagName} =
this._getSectionDetails(element);

if (inferredTagName) {
section._inferredTagName = true;
switch (sectionType) {
case LIST_SECTION_TYPE:
section = builder.createListSection(tagName);
break;
case LIST_ITEM_TYPE:
section = builder.createListItem();
break;
case MARKUP_SECTION_TYPE:
section = builder.createMarkupSection(tagName);
section._inferredTagName = inferredTagName;
break;
default:
throw new Error('Cannot parse section from element');
}

return section;
}

Expand Down
7 changes: 6 additions & 1 deletion src/js/utils/array-utils.js
Expand Up @@ -130,6 +130,10 @@ function filterObject(object, validKeys=[]) {
return result;
}

function contains(array, item) {
return array.indexOf(item) !== -1;
}

export {
detect,
forEach,
Expand All @@ -143,5 +147,6 @@ export {
kvArrayToObject,
isArrayEqual,
toArray,
filterObject
filterObject,
contains
};
65 changes: 64 additions & 1 deletion tests/unit/parsers/dom-test.js
Expand Up @@ -323,4 +323,67 @@ test('unrecognized attributes are ignored', (assert) => {
assert.ok(!markup.getAttribute('style'), 'style attribute not included');
});

// FIXME TODO ul, ol, li, img parsing
test('singly-nested ul lis are parsed correctly', (assert) => {
let element= buildDOM(`
<ul><li>first element</li><li>second element</li></ul>
`);
const post = parser.parse(element);

assert.equal(post.sections.length, 1, '1 section');
let section = post.sections.objectAt(0);
assert.equal(section.tagName, 'ul');
assert.equal(section.items.length, 2, '2 items');
assert.equal(section.items.objectAt(0).text, 'first element');
assert.equal(section.items.objectAt(1).text, 'second element');
});

test('singly-nested ol lis are parsed correctly', (assert) => {
let element= buildDOM(`
<ol><li>first element</li><li>second element</li></ol>
`);
const post = parser.parse(element);

assert.equal(post.sections.length, 1, '1 section');
let section = post.sections.objectAt(0);
assert.equal(section.tagName, 'ol');
assert.equal(section.items.length, 2, '2 items');
assert.equal(section.items.objectAt(0).text, 'first element');
assert.equal(section.items.objectAt(1).text, 'second element');
});

test('lis in nested uls are flattened (when ul is child of li)', (assert) => {
let element= buildDOM(`
<ul>
<li>first element</li>
<li><ul><li>nested element</li></ul></li>
</ul>
`);
const post = parser.parse(element);

assert.equal(post.sections.length, 1, '1 section');
let section = post.sections.objectAt(0);
assert.equal(section.tagName, 'ul');
assert.equal(section.items.length, 2, '2 items');
assert.equal(section.items.objectAt(0).text, 'first element');
assert.equal(section.items.objectAt(1).text, 'nested element');
});

/*
* FIXME: Google docs nests uls like this
test('lis in nested uls are flattened (when ul is child of ul)', (assert) => {
let element= buildDOM(`
<ul>
<li>outer</li>
<ul><li>inner</li></ul>
</ul>
`);
const post = parser.parse(element);
assert.equal(post.sections.length, 1, '1 section');
let section = post.sections.objectAt(0);
assert.equal(section.tagName, 'ul');
assert.equal(section.items.length, 2, '2 items');
assert.equal(section.items.objectAt(0).text, 'outer');
assert.equal(section.items.objectAt(1).text, 'inner');
});
*/

0 comments on commit 577b3db

Please sign in to comment.