Skip to content

Commit

Permalink
Extract Markerable base class for ListItem and MarkupSection
Browse files Browse the repository at this point in the history
  • Loading branch information
bantic committed Aug 31, 2015
1 parent 787bd5a commit cab841a
Show file tree
Hide file tree
Showing 8 changed files with 195 additions and 146 deletions.
24 changes: 16 additions & 8 deletions src/js/editor/post.js
Original file line number Diff line number Diff line change
Expand Up @@ -221,14 +221,22 @@ class PostEditor {
return this._deleteForwardFrom(result);
}
} else {
if (direction === DIRECTION.BACKWARD && section.prev) {
let prevSection = section.prev;
prevSection.join(section);
prevSection.renderNode.markDirty();
this.removeSection(section);
this.scheduleRerender();
this.scheduleDidUpdate();
return { currentSection: prevSection, currentOffset: prevSection.text.length };
if (direction === DIRECTION.BACKWARD) {
if (isMarkupSection(section) && section.prev) {
let prevSection = section.prev;
prevSection.join(section);
prevSection.renderNode.markDirty();
this.removeSection(section);
this.scheduleRerender();
this.scheduleDidUpdate();
return { currentSection: prevSection, currentOffset: prevSection.text.length };
} else if (isListItem(section)) {
this.scheduleRerender();
this.scheduleDidUpdate();

const results = this._convertListItemToMarkupSection(section);
return {currentSection: results.section, currentOffset: results.offset};
}
} else if (section.prev || section.next) {
let nextSection = section.next || section.post.tail;
this.removeSection(section);
Expand Down
136 changes: 136 additions & 0 deletions src/js/models/_markerable.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
import { normalizeTagName } from '../utils/dom-utils';
import { forEach, filter, reduce } from '../utils/array-utils';

import LinkedItem from '../utils/linked-item';
import LinkedList from '../utils/linked-list';

export default class Markerable extends LinkedItem {
constructor(tagName, markers=[]) {
super();
this.tagName = tagName;
this.markers = new LinkedList({
adoptItem: m => m.section = m.parent = this,
freeItem: m => m.section = m.parent = null
});

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

set tagName(val) {
this._tagName = normalizeTagName(val);
}

get tagName() {
return this._tagName;
}

get isBlank() {
if (!this.markers.length) {
return true;
}
let markerWithLength = this.markers.detect((marker) => {
return !!marker.length;
});
return !markerWithLength;
}

/**
* Splits the marker at the offset, filters empty markers from the result,
* and replaces this marker with the new non-empty ones
* @param {Marker} marker the marker to split
* @return {Array} the new markers that replaced `marker`
*/
splitMarker(marker, offset, endOffset=marker.length) {
const newMarkers = filter(marker.split(offset, endOffset), m => !m.isEmpty);
this.markers.splice(marker, 1, newMarkers);
if (this.markers.length === 0) {
let blankMarker = this.builder.createBlankMarker();
this.markers.append(blankMarker);
newMarkers.push(blankMarker);
}
return newMarkers;
}

// puts clones of this.markers into beforeSection and afterSection,
// all markers before the marker/offset split go in beforeSection, and all
// after the marker/offset split go in afterSection
// @return {Array} [beforeSection, afterSection], two new sections
_redistributeMarkers(beforeSection, afterSection, marker, offset=0) {
let currentSection = beforeSection;
forEach(this.markers, m => {
if (m === marker) {
const [beforeMarker, ...afterMarkers] = marker.split(offset);
beforeSection.markers.append(beforeMarker);
forEach(afterMarkers, _m => afterSection.markers.append(_m));
currentSection = afterSection;
} else {
currentSection.markers.append(m.clone());
}
});

return [beforeSection, afterSection];
}

splitAtMarker(/*marker, offset=0*/) {
throw new Error('splitAtMarker must be implemented by sub-class');
}

markerPositionAtOffset(offset) {
let currentOffset = 0;
let currentMarker;
let remaining = offset;
this.markers.detect((marker) => {
currentOffset = Math.min(remaining, marker.length);
remaining -= currentOffset;
if (remaining === 0) {
currentMarker = marker;
return true; // break out of detect
}
});

return {marker:currentMarker, offset:currentOffset};
}

get text() {
return reduce(this.markers, (prev, m) => prev + m.value, '');
}

markersFor(headOffset, tailOffset) {
let markers = [];
let adjustedHead = 0, adjustedTail = 0;
this.markers.forEach(m => {
adjustedTail += m.length;

// if current 'window' of [adjustedHead..adjustedTail] is within
// [headOffset..tailOffset] range
if (adjustedTail > headOffset && adjustedHead < tailOffset) {
let head = Math.max(headOffset - adjustedHead, 0);
let tail = m.length - Math.max(adjustedTail - tailOffset, 0);
let cloned = m.clone();

cloned.value = m.value.slice(head, tail);
markers.push(cloned);
}
adjustedHead += m.length;
});
return markers;
}

// mutates this by appending the other section's (cloned) markers to it
join(otherSection) {
let beforeMarker = this.markers.tail;
let afterMarker = null;

otherSection.markers.forEach(m => {
if (!m.isEmpty) {
m = m.clone();
this.markers.append(m);
if (!afterMarker) {
afterMarker = m;
}
}
});

return { beforeMarker, afterMarker };
}
}
13 changes: 7 additions & 6 deletions src/js/models/list-item.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import Section from './markup-section';
import Markerable from './_markerable';

export const LIST_ITEM_TYPE = 'list-item';

export default class ListItem extends Section {
export default class ListItem extends Markerable {
constructor(tagName, markers=[]) {
super(tagName, markers);
this.type = LIST_ITEM_TYPE;
Expand All @@ -12,15 +12,16 @@ export default class ListItem extends Section {
// FIXME need to check if we are going to split into two list items
// or a list item and a new markup section:
const isLastItem = !this.next;
const createNewSection =
(marker.isEmpty && offset === 0) && isLastItem;
const createNewSection = (!marker && offset === 0 && isLastItem);

let [beforeSection, afterSection] = [
this.builder.createListItem(),
createNewSection ? this.builder.createMarkupSection('p') : this.builder.createListItem()
createNewSection ? this.builder.createMarkupSection() :
this.builder.createListItem()
];

return this._redistributeMarkers(beforeSection, afterSection, marker, offset);
return this._redistributeMarkers(
beforeSection, afterSection, marker, offset);
}

splitIntoSections() {
Expand Down
9 changes: 3 additions & 6 deletions src/js/models/list-section.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,13 @@
import Section from './markup-section';
import { normalizeTagName } from '../utils/dom-utils';
import LinkedList from '../utils/linked-list';

export const LIST_SECTION_TYPE = 'list-section';

export default class ListSection extends Section {
export default class ListSection {
constructor(tagName, items=[]) {
super(tagName);
this.tagName = normalizeTagName(tagName);
this.type = LIST_SECTION_TYPE;

// remove the inherited `markers` because they do nothing on a ListSection but confuse
this.markers = undefined;

this.items = new LinkedList({
adoptItem: i => i.section = i.parent = this,
freeItem: i => i.section = i.parent = null
Expand Down
132 changes: 9 additions & 123 deletions src/js/models/markup-section.js
Original file line number Diff line number Diff line change
@@ -1,31 +1,17 @@
import {
normalizeTagName
} from '../utils/dom-utils';
import Markerable from './_markerable';
import { normalizeTagName } from '../utils/dom-utils';

import {
forEach,
filter
} from '../utils/array-utils';

export const DEFAULT_TAG_NAME = normalizeTagName('p');
export const VALID_MARKUP_SECTION_TAGNAMES = [
'p', 'h3', 'h2', 'h1', 'blockquote', 'ul', 'ol'
].map(normalizeTagName);
export const DEFAULT_TAG_NAME = VALID_MARKUP_SECTION_TAGNAMES[0];

export const MARKUP_SECTION_TYPE = 'markup-section';
import LinkedList from "content-kit-editor/utils/linked-list";
import LinkedItem from "content-kit-editor/utils/linked-item";

export default class Section extends LinkedItem {
constructor(tagName, markers=[]) {
super();
this.markers = new LinkedList({
adoptItem: m => m.section = m.parent = this,
freeItem: m => m.section = m.parent = null
});
this.tagName = tagName || DEFAULT_TAG_NAME;
const MarkupSection = class MarkupSection extends Markerable {
constructor(tagName=DEFAULT_TAG_NAME, markers=[]) {
super(tagName, markers);
this.type = MARKUP_SECTION_TYPE;

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

set tagName(val) {
Expand All @@ -36,16 +22,6 @@ export default class Section extends LinkedItem {
return this._tagName;
}

get isBlank() {
if (!this.markers.length) {
return true;
}
let markerWithLength = this.markers.detect((marker) => {
return !!marker.length;
});
return !markerWithLength;
}

setTagName(newTagName) {
newTagName = normalizeTagName(newTagName);
if (VALID_MARKUP_SECTION_TAGNAMES.indexOf(newTagName) === -1) {
Expand All @@ -58,39 +34,6 @@ export default class Section extends LinkedItem {
this.tagName = DEFAULT_TAG_NAME;
}

/**
* Splits the marker at the offset, filters empty markers from the result,
* and replaces this marker with the new non-empty ones
* @param {Marker} marker the marker to split
* @return {Array} the new markers that replaced `marker`
*/
splitMarker(marker, offset, endOffset=marker.length) {
const newMarkers = filter(marker.split(offset, endOffset), m => !m.isEmpty);
this.markers.splice(marker, 1, newMarkers);
if (this.markers.length === 0) {
let blankMarker = this.builder.createBlankMarker();
this.markers.append(blankMarker);
newMarkers.push(blankMarker);
}
return newMarkers;
}

_redistributeMarkers(beforeSection, afterSection, marker, offset=0) {
let currentSection = beforeSection;
forEach(this.markers, m => {
if (m === marker) {
const [beforeMarker, ...afterMarkers] = marker.split(offset);
beforeSection.markers.append(beforeMarker);
forEach(afterMarkers, _m => afterSection.markers.append(_m));
currentSection = afterSection;
} else {
currentSection.markers.append(m.clone());
}
});

return [beforeSection, afterSection];
}

splitAtMarker(marker, offset=0) {
let [beforeSection, afterSection] = [
this.builder.createMarkupSection(this.tagName, []),
Expand All @@ -99,63 +42,6 @@ export default class Section extends LinkedItem {

return this._redistributeMarkers(beforeSection, afterSection, marker, offset);
}
};

markerPositionAtOffset(offset) {
let currentOffset = 0;
let currentMarker;
let remaining = offset;
this.markers.detect((marker) => {
currentOffset = Math.min(remaining, marker.length);
remaining -= currentOffset;
if (remaining === 0) {
currentMarker = marker;
return true; // break out of detect
}
});

return {marker:currentMarker, offset:currentOffset};
}

// mutates this by appending the other section's (cloned) markers to it
join(otherSection) {
let beforeMarker = this.markers.tail;
let afterMarker = null;

otherSection.markers.forEach(m => {
if (!m.isEmpty) {
m = m.clone();
this.markers.append(m);
if (!afterMarker) {
afterMarker = m;
}
}
});

return { beforeMarker, afterMarker };
}

get text() {
let text = '';
this.markers.forEach(m => text += m.value);
return text;
}

markersFor(headOffset, tailOffset) {
let markers = [];
let adjustedHead = 0, adjustedTail = 0;
this.markers.forEach(m => {
adjustedTail += m.length;

if (adjustedTail > headOffset && adjustedHead < tailOffset) {
let head = Math.max(headOffset - adjustedHead, 0);
let tail = m.length - Math.max(adjustedTail - tailOffset, 0);
let cloned = m.clone();

cloned.value = m.value.slice(head, tail);
markers.push(cloned);
}
adjustedHead += m.length;
});
return markers;
}
}
export default MarkupSection;
3 changes: 2 additions & 1 deletion src/js/models/post-node-builder.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import Marker from '../models/marker';
import Markup from '../models/markup';
import Card from '../models/card';
import { normalizeTagName } from '../utils/dom-utils';
import { DEFAULT_TAG_NAME } from '../models/markup-section';

export default class PostNodeBuilder {
constructor() {
Expand All @@ -27,7 +28,7 @@ export default class PostNodeBuilder {
return this.createPost([ blankMarkupSection ]);
}

createMarkupSection(tagName, markers=[], isGenerated=false) {
createMarkupSection(tagName=DEFAULT_TAG_NAME, markers=[], isGenerated=false) {
tagName = normalizeTagName(tagName);
const section = new MarkupSection(tagName, markers);
if (isGenerated) {
Expand Down
Loading

0 comments on commit cab841a

Please sign in to comment.