Skip to content

Commit

Permalink
Merge pull request #23 from bustlelabs/markers
Browse files Browse the repository at this point in the history
Post, Section, Markers
  • Loading branch information
mixonic committed Jul 27, 2015
2 parents 5b59865 + aec3812 commit 95398a3
Show file tree
Hide file tree
Showing 23 changed files with 1,010 additions and 226 deletions.
4 changes: 3 additions & 1 deletion src/js/models/image.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
export const IMAGE_SECTION_TYPE = 'image-section';

export default class Image {
constructor() {
this.type = 'imageSection';
this.type = IMAGE_SECTION_TYPE;
this.src = null;
}
}
101 changes: 101 additions & 0 deletions src/js/models/marker.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
export const MARKER_TYPE = 'marker';

import { detect } from 'content-kit-editor/utils/array-utils';

const Marker = class Marker {
constructor(value='', markups=[]) {
this.value = value;
this.markups = [];
this.type = MARKER_TYPE;

if (markups && markups.length) {
markups.forEach(m => this.addMarkup(m));
}
}

get length() {
return this.value.length;
}

truncateFrom(offset) {
this.value = this.value.substr(0, offset);
}

truncateTo(offset) {
this.value = this.value.substr(offset);
}

addMarkup(markup) {
this.markups.push(markup);
}

hasMarkup(tagName) {
tagName = tagName.toLowerCase();
return detect(this.markups, markup => markup.tagName === tagName);
}

getMarkup(tagName) {
return this.hasMarkup(tagName);
}

join(other) {
const joined = new Marker(this.value + other.value);
this.markups.forEach(m => joined.addMarkup(m));
other.markups.forEach(m => joined.addMarkup(m));

return joined;
}

split(offset) {
const [m1, m2] = [
new Marker(this.value.substr(0, offset)),
new Marker(this.value.substr(offset))
];
this.markups.forEach(m => {m1.addMarkup(m); m2.addMarkup(m);});

return [m1, m2];
}

get openedMarkups() {
if (!this.previousSibling) {
return this.markups.slice();
}
let i;
for (i=0; i<this.markups.length; i++) {
if (this.markups[i] !== this.previousSibling.markups[i]) {
return this.markups.slice(i);
}
}
return [];
}

get closedMarkups() {
if (!this.nextSibling) {
return this.markups.slice();
}
let i;
for (i=0; i<this.markups.length; i++) {
if (this.markups[i] !== this.nextSibling.markups[i]) {
return this.markups.slice(i);
}
}
return [];
}

// FIXME this should be implemented as a linked list
get nextSibling() {
let index = this.section.markers.indexOf(this);
if (index > -1 && index < this.section.markers.length-1) {
return this.section.markers[index + 1];
}
}

get previousSibling() {
let index = this.section.markers.indexOf(this);
if (index > 0) {
return this.section.markers[index - 1];
}
}
};

export default Marker;
68 changes: 68 additions & 0 deletions src/js/models/markup-section.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
export const DEFAULT_TAG_NAME = 'p';
export const VALID_MARKUP_SECTION_TAGNAMES = [
'p', 'h3', 'h2', 'h1', 'blockquote', 'ul', 'ol'
];
export const MARKUP_SECTION_TYPE = 'markup-section';

export default class Section {
constructor(tagName, markers=[]) {
this.markers = [];
this.tagName = tagName || DEFAULT_TAG_NAME;
this.type = MARKUP_SECTION_TYPE;

markers.forEach(m => this.appendMarker(m));
}

appendMarker(marker) {
marker.section = this;
this.markers.push(marker);
}

/**
* @return {Array} 2 new sections
*/
split(offset) {
let left = [], right = [], middle;

middle = this.markerContaining(offset);
const middleIndex = this.markers.indexOf(middle);

for (let i=0; i<this.markers.length; i++) {
if (i < middleIndex) { left.push(this.markers[i]); }
if (i > middleIndex) { right.push(this.markers[i]); }
}

let leftLength = left.reduce((prev, cur) => prev + cur.length, 0);
let middleOffset = offset - leftLength;

let [leftMiddle, rightMiddle] = middle.split(middleOffset);
left.push(leftMiddle);
right.push(rightMiddle);

return [
new this.constructor(this.tagName, left),
new this.constructor(this.tagName, right)
];
}

/**
* A marker contains this offset if:
* * The offset is between the marker's start and end
* * it is the first marker and the offset is 0
* * it is the last marker and the offset is >= total length of all the markers
* * the offset is between two markers and it is the left marker (right-inclusive)
*
* @return {Marker} The marker that contains this offset
*/
markerContaining(offset) {
var length=0, i=0;

if (offset === 0) { return this.markers[0]; }

while (length < offset && i < this.markers.length) {
length += this.markers[i].length;
i++;
}
return this.markers[i-1];
}
}
21 changes: 21 additions & 0 deletions src/js/models/markup.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
export const MARKUP_TYPE = 'markup';
export const VALID_MARKUP_TAGNAMES = [
'b',
'i',
'strong',
'em',
'a',
'li'
];

export default class Markup {
constructor(tagName, attributes=[]) {
this.tagName = tagName.toLowerCase();
this.attributes = attributes;
this.type = MARKUP_TYPE;

if (VALID_MARKUP_TAGNAMES.indexOf(this.tagName) === -1) {
throw new Error(`Cannot create markup of tagName ${tagName}`);
}
}
}
4 changes: 3 additions & 1 deletion src/js/models/post.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
export const POST_TYPE = 'post';

// FIXME: making sections a linked-list would greatly improve this
export default class Post {
constructor() {
this.type = 'post';
this.type = POST_TYPE;
this.sections = [];
}
appendSection(section) {
Expand Down
42 changes: 15 additions & 27 deletions src/js/parsers/dom.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { generateBuilder } from '../utils/post-builder';
import { trim } from 'content-kit-utils';
import { VALID_MARKUP_SECTION_TAGNAMES } from '../models/markup-section';
import { VALID_MARKUP_TAGNAMES } from '../models/markup';

const ELEMENT_NODE = 1;
const TEXT_NODE = 3;

const MARKUP_SECTION_TAG_NAMES = ['P', 'H3', 'H2', 'H1', 'BLOCKQUOTE', 'UL', 'OL'];

const ALLOWED_ATTRIBUTES = ['href', 'rel', 'src'];

function isEmptyTextNode(node) {
Expand Down Expand Up @@ -57,28 +57,19 @@ function readAttributes(node) {
return null;
}

const VALID_MARKER_ELEMENTS = [
'B',
'I',
'STRONG',
'EM',
'A',
'LI'
];

function isValidMarkerElement(element) {
return VALID_MARKER_ELEMENTS.indexOf(element.tagName) !== -1;
return VALID_MARKUP_TAGNAMES.indexOf(element.tagName.toLowerCase()) !== -1;
}

function parseMarkers(section, postBuilder, topNode) {
var markerTypes = [];
var markups = [];
var text = null;
var currentNode = topNode;
while (currentNode) {
switch(currentNode.nodeType) {
case ELEMENT_NODE:
if (isValidMarkerElement(currentNode)) {
markerTypes.push(postBuilder.generateMarkerType(currentNode.tagName, readAttributes(currentNode)));
markups.push(postBuilder.generateMarkup(currentNode.tagName, readAttributes(currentNode)));
}
break;
case TEXT_NODE:
Expand All @@ -88,34 +79,31 @@ function parseMarkers(section, postBuilder, topNode) {

if (currentNode.firstChild) {
if (isValidMarkerElement(currentNode) && text !== null) {
section.markers.push(postBuilder.generateMarker(markerTypes, 0, text));
markerTypes = [];
section.appendMarker(postBuilder.generateMarker(markups.slice(), text));
text = null;
}
currentNode = currentNode.firstChild;
} else if (currentNode.nextSibling) {
if (currentNode === topNode) {
section.markers.push(postBuilder.generateMarker(markerTypes, markerTypes.length, text));
section.appendMarker(postBuilder.generateMarker(markups.slice(), text));
break;
} else {
currentNode = currentNode.nextSibling;
if (currentNode.nodeType === ELEMENT_NODE && isValidMarkerElement(currentNode) && text !== null) {
section.markers.push(postBuilder.generateMarker(markerTypes, 0, text));
markerTypes = [];
section.appendMarker(postBuilder.generateMarker(markups.slice(), text));
text = null;
}
}
} else {
var toClose = 0;
section.appendMarker(postBuilder.generateMarker(markups.slice(), text));

while (currentNode && !currentNode.nextSibling && currentNode !== topNode) {
currentNode = currentNode.parentNode;
if (isValidMarkerElement(currentNode)) {
toClose++;
markups.pop();
}
}

section.markers.push(postBuilder.generateMarker(markerTypes, toClose, text));
markerTypes = [];
text = null;

if (currentNode === topNode) {
Expand All @@ -142,8 +130,8 @@ NewHTMLParser.prototype = {
case ELEMENT_NODE:
var tagName = sectionElement.tagName;
// <p> <h2>, etc
if (MARKUP_SECTION_TAG_NAMES.indexOf(tagName) !== -1) {
section = postBuilder.generateSection(tagName, readAttributes(sectionElement));
if (VALID_MARKUP_SECTION_TAGNAMES.indexOf(tagName.toLowerCase()) !== -1) {
section = postBuilder.generateMarkupSection(tagName, readAttributes(sectionElement));
var node = sectionElement.firstChild;
while (node) {
parseMarkers(section, postBuilder, node);
Expand All @@ -154,7 +142,7 @@ NewHTMLParser.prototype = {
if (previousSection && previousSection.isGenerated) {
section = previousSection;
} else {
section = postBuilder.generateSection('P', {}, true);
section = postBuilder.generateMarkupSection('P', {}, true);
}
parseMarkers(section, postBuilder, sectionElement);
}
Expand All @@ -163,7 +151,7 @@ NewHTMLParser.prototype = {
if (previousSection && previousSection.isGenerated) {
section = previousSection;
} else {
section = postBuilder.generateSection('P', {}, true);
section = postBuilder.generateMarkupSection('P', {}, true);
}
parseMarkers(section, postBuilder, sectionElement);
break;
Expand Down
14 changes: 9 additions & 5 deletions src/js/parsers/mobiledoc.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export default class MobiledocParser {

const post = this.builder.generatePost();

this.markups = [];
this.markerTypes = this.parseMarkerTypes(markerTypes);
this.parseSections(sections, post);

Expand All @@ -29,7 +30,7 @@ export default class MobiledocParser {
}

parseMarkerType([tagName, attributes]) {
return this.builder.generateMarkerType(tagName, attributes);
return this.builder.generateMarkup(tagName, attributes);
}

parseSections(sections, post) {
Expand Down Expand Up @@ -66,7 +67,7 @@ export default class MobiledocParser {
parseMarkupSection([type, tagName, markers], post) {
const attributes = null;
const isGenerated = false;
const section = this.builder.generateSection(tagName, attributes, isGenerated);
const section = this.builder.generateMarkupSection(tagName, attributes, isGenerated);

post.appendSection(section);
this.parseMarkers(markers, section);
Expand All @@ -77,8 +78,11 @@ export default class MobiledocParser {
}

parseMarker([markerTypeIndexes, closeCount, value], section) {
const markerTypes = markerTypeIndexes.map(index => this.markerTypes[index]);
const marker = this.builder.generateMarker(markerTypes, closeCount, value);
section.markers.push(marker);
markerTypeIndexes.forEach(index => {
this.markups.push(this.markerTypes[index]);
});
const marker = this.builder.generateMarker(this.markups.slice(), value);
section.appendMarker(marker);
this.markups = this.markups.slice(0, this.markups.length-closeCount);
}
}
19 changes: 19 additions & 0 deletions src/js/parsers/post.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import Post from 'content-kit-editor/models/post';
import SectionParser from 'content-kit-editor/parsers/section';
import { forEach } from 'content-kit-editor/utils/array-utils';

export default {
parse(element) {
const post = new Post();

forEach(element.childNodes, child => {
post.appendSection(SectionParser.parse(child));
});

return post;
},

parseSection(element) {
return SectionParser.parse(element);
}
};
Loading

0 comments on commit 95398a3

Please sign in to comment.