Skip to content

Commit

Permalink
Drop all blank markers, section renders br
Browse files Browse the repository at this point in the history
Before this patch, Content-Kit would insert blank markers in any empty
section to ensure a cursor could be placed there. This changes that to
be a section responsibility. When there are no child markers, a markup
section will create a br.

This means throughout the codebase sections and section offset should be
used to manage editing actions. There may be no marker at a given cursor
position, only using the offset in a section is reliable.

Also ensures correct behavior when deleting two sections in their
entirety.
  • Loading branch information
mixonic committed Aug 28, 2015
1 parent b57465d commit 787bd5a
Show file tree
Hide file tree
Showing 20 changed files with 422 additions and 236 deletions.
3 changes: 1 addition & 2 deletions src/js/commands/image.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,7 @@ export default class ImageCommand extends Command {
}

exec() {
let {headMarker} = this.editor.cursor.offsets;
let beforeSection = headMarker.section;
let {headSection: beforeSection} = this.editor.cursor.offsets;
let afterSection = beforeSection.next;
let section = this.editor.builder.createCardSection('image');
const collection = beforeSection.parent.sections;
Expand Down
10 changes: 7 additions & 3 deletions src/js/editor/editor.js
Original file line number Diff line number Diff line change
Expand Up @@ -338,14 +338,14 @@ class Editor {
this.cursor.moveToSection(offsets.headSection, offsets.headSectionOffset);
} else {
let results = this.run(postEditor => {
const {headMarker, headMarkerOffset} = offsets;
const {headSection, headSectionOffset} = offsets;
const key = Key.fromEvent(event);

const deletePosition = {marker: headMarker, offset: headMarkerOffset},
const deletePosition = {section: headSection, offset: headSectionOffset},
direction = key.direction;
return postEditor.deleteFrom(deletePosition, direction);
});
this.cursor.moveToMarker(results.currentMarker, results.currentOffset);
this.cursor.moveToSection(results.currentSection, results.currentOffset);
}
}

Expand All @@ -364,6 +364,10 @@ class Editor {
this.run((postEditor) => {
if (this.cursor.hasSelection()) {
postEditor.deleteRange(offsets);
if (offsets.headSection.isBlank) {
cursorSection = offsets.headSection;
return;
}
}
cursorSection = postEditor.splitSection(offsets)[1];
});
Expand Down
207 changes: 153 additions & 54 deletions src/js/editor/post.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ class PostEditor {
*
* @method deleteRange
* @param {Object} markerRange Object with offsets, {headSection, headSectionOffset, tailSection, tailSectionOffset}
* @return {Object} {currentMarker, currentOffset} for cursor
* @return {Object} {currentSection, currentOffset} for cursor
* @public
*/
deleteRange(markerRange) {
Expand Down Expand Up @@ -85,6 +85,7 @@ class PostEditor {
tailSection.markersFor(tailSectionOffset, section.text.length).forEach(m => {
headSection.markers.append(m);
});
headSection.renderNode.markDirty(); // May have added nodes
removedSections.push(tailSection);
break;
default:
Expand All @@ -101,32 +102,88 @@ class PostEditor {
}

cutSection(section, headSectionOffset, tailSectionOffset) {
const {marker:headMarker, offset:headMarkerOffset} = section.markerPositionAtOffset(headSectionOffset);
const {marker:tailMarker, offset:tailMarkerOffset} = section.markerPositionAtOffset(tailSectionOffset);
const markers = this.splitMarkers({headMarker, headMarkerOffset, tailMarker, tailMarkerOffset});
section.markers.removeBy(m => {
return markers.indexOf(m) !== -1;
});
if (section.markers.isEmpty) {
return;
}

let adjustedHead = 0,
marker = section.markers.head,
adjustedTail = marker.length;

// Walk to the first node inside the headSectionOffset, splitting
// a marker if needed. Leave marker as the first node inside.
while (marker) {
if (adjustedTail >= headSectionOffset) {
let splitOffset = headSectionOffset - adjustedHead;
let { afterMarker } = this.splitMarker(marker, splitOffset);
adjustedHead = adjustedHead + splitOffset;
// FIXME: That these two loops cannot agree on adjustedTail being
// incremented at the start or end seems prime for refactoring.
adjustedTail = adjustedHead;
marker = afterMarker;
break;
}
adjustedHead += marker.length;
marker = marker.next;
if (marker) {
adjustedTail += marker.length;
}
}

// Walk each marker inside, removing it if needed. when the last is
// reached split it and remove the part inside the tailSectionOffset
while (marker) {
adjustedTail += marker.length;
if (adjustedTail >= tailSectionOffset) {
let splitOffset = marker.length - (adjustedTail - tailSectionOffset);
let {
beforeMarker
} = this.splitMarker(marker, splitOffset);
if (beforeMarker) {
this.removeMarker(beforeMarker);
}
break;
}
adjustedHead += marker.length;
let nextMarker = marker.next;
this.removeMarker(marker);
marker = nextMarker;
}
}

removeMarkerRange(headMarker, tailMarker) {
let marker = headMarker;
while (marker) {
let nextMarker = marker.next;
this.removeMarker(marker);
if (marker === tailMarker) {
break;
}
marker = nextMarker;
}
}

_coalesceMarkers(section) {
filter(section.markers, m => m.isEmpty).forEach(m => {
this.removeMarker(m);
filter(section.markers, m => m.isEmpty).forEach(marker => {
this.removeMarker(marker);
});
if (section.markers.isEmpty) {
section.markers.append(this.builder.createBlankMarker());
section.renderNode.markDirty();
}
}

removeMarker(marker) {
let didChange = false;
if (marker.renderNode) {
marker.renderNode.scheduleForRemoval();
didChange = true;
}
if (marker.section) {
marker.section.markers.remove(marker);
didChange = true;
}
marker.section.markers.remove(marker);

this.scheduleRerender();
this.scheduleDidUpdate();
if (didChange) {
this.scheduleRerender();
this.scheduleDidUpdate();
}
}

/**
Expand All @@ -138,7 +195,7 @@ class PostEditor {
* let marker = editor.post.sections.head.markers.head;
* // marker has text of "Howdy!"
* editor.run((postEditor) => {
* postEditor.deleteFrom({marker, offset: 3});
* postEditor.deleteFrom({section, offset: 3});
* });
* // marker has text of "Hody!"
*
Expand All @@ -149,16 +206,34 @@ class PostEditor {
* marker.
*
* @method deleteFrom
* @param {Object} position object with {marker, offset} the marker and offset to delete from
* @param {Object} position object with {section, offset} the marker and offset to delete from
* @param {Number} direction The direction to delete in (default is BACKWARD)
* @return {Object} {currentMarker, currentOffset} for positioning the cursor
* @return {Object} {currentSection, currentOffset} for positioning the cursor
* @public
*/
deleteFrom({marker, offset}, direction=DIRECTION.BACKWARD) {
if (direction === DIRECTION.BACKWARD) {
return this._deleteBackwardFrom({marker, offset});
deleteFrom({section, offset}, direction=DIRECTION.BACKWARD) {
if (section.markers.length) {
// {{marker, offset}}
let result = section.markerPositionAtOffset(offset);
if (direction === DIRECTION.BACKWARD) {
return this._deleteBackwardFrom(result);
} else {
return this._deleteForwardFrom(result);
}
} else {
return this._deleteForwardFrom({marker, offset});
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 };
} else if (section.prev || section.next) {
let nextSection = section.next || section.post.tail;
this.removeSection(section);
return { currentSection: nextSection, currentOffset: 0 };
}
}
}

Expand All @@ -168,8 +243,8 @@ class PostEditor {
* @private
*/
_deleteForwardFrom({marker, offset}) {
const nextCursorMarker = marker,
nextCursorOffset = offset;
const nextCursorSection = marker.section,
nextCursorOffset = marker.offsetInParent(offset);

if (offset === marker.length) {
const nextMarker = marker.next;
Expand All @@ -182,21 +257,22 @@ class PostEditor {
const currentSection = marker.section;

currentSection.join(nextSection);

currentSection.renderNode.markDirty();
nextSection.renderNode.scheduleForRemoval();

this.removeSection(nextSection);
}
}
} else {
marker.deleteValueAtOffset(offset);
marker.renderNode.markDirty();
this._coalesceMarkers(marker.section);
}

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

return {
currentMarker: nextCursorMarker,
currentSection: nextCursorSection,
currentOffset: nextCursorOffset
};
}
Expand All @@ -209,7 +285,10 @@ class PostEditor {

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

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

Expand All @@ -219,8 +298,8 @@ class PostEditor {
* @private
*/
_deleteBackwardFrom({marker, offset}) {
let nextCursorMarker = marker,
nextCursorOffset = offset;
let nextCursorSection = marker.section,
nextCursorOffset = marker.offsetInParent(offset);

if (offset === 0) {
const prevMarker = marker.prev;
Expand All @@ -232,23 +311,25 @@ class PostEditor {

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

let { beforeMarker, afterMarker } = prevSection.join(marker.section);
let {
beforeMarker
} = prevSection.join(marker.section);
prevSection.renderNode.markDirty();
marker.section.renderNode.scheduleForRemoval();
this.removeSection(marker.section);

nextCursorSection = prevSection;

if (beforeMarker) {
nextCursorMarker = beforeMarker;
nextCursorOffset = beforeMarker.length;
nextCursorOffset = beforeMarker.offsetInParent(beforeMarker.length);
} else {
nextCursorMarker = afterMarker;
nextCursorOffset = 0;
}
}
Expand All @@ -257,28 +338,17 @@ class PostEditor {

} else if (offset <= marker.length) {
const offsetToDeleteAt = offset - 1;

marker.deleteValueAtOffset(offsetToDeleteAt);

if (marker.isEmpty && marker.prev) {
marker.renderNode.scheduleForRemoval();
nextCursorMarker = marker.prev;
nextCursorOffset = nextCursorMarker.length;
} else if (marker.isEmpty && marker.next) {
marker.renderNode.scheduleForRemoval();
nextCursorMarker = marker.next;
nextCursorOffset = 0;
} else {
marker.renderNode.markDirty();
nextCursorOffset = offsetToDeleteAt;
}
nextCursorOffset--;
marker.renderNode.markDirty();
this._coalesceMarkers(marker.section);
}

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

return {
currentMarker: nextCursorMarker,
currentSection: nextCursorSection,
currentOffset: nextCursorOffset
};
}
Expand Down Expand Up @@ -366,6 +436,33 @@ class PostEditor {
return selectedMarkers;
}

splitMarker(marker, offset) {
let beforeMarker, afterMarker;

if (offset === 0) {
beforeMarker = marker.prev;
afterMarker = marker;
} else if (offset === marker.length) {
beforeMarker = marker;
afterMarker = marker.next;
} else {
let { builder } = this.editor,
{ section } = marker;
beforeMarker = builder.createMarker(marker.value.substring(0, offset), marker.markups);
afterMarker = builder.createMarker(marker.value.substring(offset, marker.length), marker.markups);
section.markers.splice(marker, 1, [beforeMarker, afterMarker]);
if (marker.renderNode) {
marker.renderNode.scheduleForRemoval();
}
if (section.renderNode) {
section.renderNode.markDirty();
}
}
this.scheduleRerender();
this.scheduleDidUpdate();
return { beforeMarker, afterMarker };
}

/**
* Split a section at one position. This method is designed to accept
* `editor.cursor.offsets` as an argument, but will only split at the
Expand Down Expand Up @@ -400,6 +497,8 @@ class PostEditor {
} = section.markerPositionAtOffset(headSectionOffset);

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

const newSections = [beforeSection, afterSection];
let replacementSections = [beforeSection, afterSection];
Expand Down
Loading

0 comments on commit 787bd5a

Please sign in to comment.