Skip to content

Commit

Permalink
Paste improvements
Browse files Browse the repository at this point in the history
 * handle pasting an empty post
 * Handle passing a non-markup section (list or card) to `insertPost`
 * make insertPost replace empty sections when pasting
 * Restrict tagNames to valid ones for markup section, list section, list item

fixes #196
fixes #190
  • Loading branch information
bantic committed Oct 28, 2015
1 parent ff395b6 commit d4ce47a
Show file tree
Hide file tree
Showing 8 changed files with 237 additions and 55 deletions.
88 changes: 45 additions & 43 deletions src/js/editor/post.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,30 @@ import {
DEFAULT_TAG_NAME as DEFAULT_MARKUP_SECTION_TAG_NAME
} from '../models/markup-section';
import { isMarkerable } from '../models/_section';
import { POST_TYPE, MARKUP_SECTION_TYPE, LIST_ITEM_TYPE } from '../models/types';
import { POST_TYPE, MARKUP_SECTION_TYPE, LIST_ITEM_TYPE, LIST_SECTION_TYPE } from '../models/types';
import Position from '../utils/cursor/position';
import { isArrayEqual, forEach, filter, compact } from '../utils/array-utils';
import { DIRECTION } from '../utils/key';
import LifecycleCallbacksMixin from '../utils/lifecycle-callbacks';
import mixin from '../utils/mixin';

function isJoinable(section1, section2) {
return isMarkerable(section1) &&
isMarkerable(section2) &&
section1.type === section2.type &&
section1.tagName === section2.tagName;
}

function endPosition(section) {
if (isMarkerable(section)) {
return new Position(section, section.length);
} else if (section.type === LIST_SECTION_TYPE) {
return endPosition(section.items.tail);
} else {
return new Position(section, 0);
}
}

function isMarkupSection(section) {
return section.type === MARKUP_SECTION_TYPE;
}
Expand Down Expand Up @@ -624,29 +641,6 @@ class PostEditor {
});
}

/**
* @method insertMarkers
* @param {Position} position to insert at
* @param {Array} markers to insert
* @return {Position} position at end of inserted markers
* @private
*/
insertMarkers(position, markers=[]) {
let { section, offset } = position;
this.splitSectionMarkerAtOffset(section, offset);
let {marker:prevMarker} = section.markerPositionAtOffset(offset);
let currentMarker = offset === 0 ? prevMarker : prevMarker.next;

markers.forEach(marker => {
marker = marker.clone();
section.markers.insertBefore(marker, currentMarker);
offset += marker.length;
this._markDirty(marker);
});

return new Position(section, offset);
}

/**
* Toggle the given markup on the current selection. If anything in the current
* selection has the markup, the markup will be removed from it. If nothing in the selection
Expand Down Expand Up @@ -695,7 +689,7 @@ class PostEditor {
m.clearMarkups();
this._markDirty(m);
});
section.setTagName(newTagName);
section.tagName = newTagName;
this._markDirty(section);
}

Expand Down Expand Up @@ -762,39 +756,47 @@ class PostEditor {
* @private
*/
insertPost(position, newPost) {
const post = this.editor.post;
const shouldSplitSection = newPost.sections.length > 1;

if (!shouldSplitSection) {
const markers = newPost.sections.head.markers;
return this.insertMarkers(position, markers);
if (newPost.isBlank) {
return position;
}
const post = this.editor.post;

let [preSplit, postSplit] = this.splitSection(position);
const headSection = newPost.sections.head;
let lastInsertedSection = headSection;
let nextPosition = position.clone();

newPost.sections.forEach(section => {
if (section === headSection) {
this._mergeSectionAtEnd(section, preSplit);
newPost.sections.forEach((section, index) => {
if (index === 0 &&
isJoinable(preSplit, section)) {

preSplit.join(section);
this._markDirty(preSplit);

nextPosition = endPosition(preSplit);
} else {
section = section.clone();
lastInsertedSection = section;
this.insertSectionBefore(post.sections, section, postSplit);

nextPosition = endPosition(section);
}
});

if (postSplit.isBlank) {
this.removeSection(postSplit);
}

return new Position(lastInsertedSection, lastInsertedSection.length);
}
if (isJoinable(preSplit, postSplit) &&
preSplit.next === postSplit) {

nextPosition = endPosition(preSplit);

preSplit.join(postSplit);
this._markDirty(preSplit);
this.removeSection(postSplit);
} else if (preSplit.isBlank) {
this.removeSection(preSplit);
}

_mergeSectionAtEnd(sectionToMerge, existingSection) {
const markers = sectionToMerge.markers;
const position = new Position(existingSection, existingSection.length);
return this.insertMarkers(position, markers);
return nextPosition;
}

/**
Expand Down
10 changes: 9 additions & 1 deletion src/js/models/_section.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,21 @@ export default class Section extends LinkedItem {
}

set tagName(val) {
this._tagName = normalizeTagName(val);
let normalizedTagName = normalizeTagName(val);
if (!this.isValidTagName(normalizedTagName)) {
throw new Error(`Cannot set section tagName to ${val}`);
}
this._tagName = normalizedTagName;
}

get tagName() {
return this._tagName;
}

isValidTagName(/* normalizedTagName */) {
throw new Error('`isValidTagName` must be implemented by subclass');
}

get isBlank() {
throw new Error('`isBlank` must be implemented by subclass');
}
Expand Down
5 changes: 5 additions & 0 deletions src/js/models/list-item.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { LIST_ITEM_TYPE } from './types';
import {
normalizeTagName
} from 'content-kit-editor/utils/dom-utils';
import { contains } from 'content-kit-editor/utils/array-utils';

export const VALID_LIST_ITEM_TAGNAMES = [
'li'
Expand All @@ -13,6 +14,10 @@ export default class ListItem extends Markerable {
super(LIST_ITEM_TYPE, tagName, markers);
}

isValidTagName(normalizedTagName) {
return contains(VALID_LIST_ITEM_TAGNAMES, normalizedTagName);
}

splitAtMarker(marker, offset=0) {
// FIXME need to check if we are going to split into two list items
// or a list item and a new markup section:
Expand Down
9 changes: 8 additions & 1 deletion src/js/models/list-section.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import LinkedList from '../utils/linked-list';
import { forEach } from '../utils/array-utils';
import {
forEach,
contains
} from '../utils/array-utils';
import { LIST_SECTION_TYPE } from './types';
import Section from './_section';
import {
Expand Down Expand Up @@ -27,6 +30,10 @@ export default class ListSection extends Section {
items.forEach(i => this.items.append(i));
}

isValidTagName(normalizedTagName) {
return contains(VALID_LIST_SECTION_TAGNAMES, normalizedTagName);
}

get isBlank() {
return this.items.isEmpty;
}
Expand Down
13 changes: 3 additions & 10 deletions src/js/models/markup-section.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import Markerable from './_markerable';
import { normalizeTagName } from '../utils/dom-utils';
import { contains } from '../utils/array-utils';
import { MARKUP_SECTION_TYPE } from './types';

// valid values of `tagName` for a MarkupSection
Expand All @@ -20,16 +21,8 @@ const MarkupSection = class MarkupSection extends Markerable {
super(MARKUP_SECTION_TYPE, tagName, markers);
}

setTagName(newTagName) {
newTagName = normalizeTagName(newTagName);
if (VALID_MARKUP_SECTION_TAGNAMES.indexOf(newTagName) === -1) {
throw new Error(`Cannot change section tagName to "${newTagName}`);
}
this.tagName = newTagName;
}

resetTagName() {
this.tagName = DEFAULT_TAG_NAME;
isValidTagName(normalizedTagName) {
return contains(VALID_MARKUP_SECTION_TAGNAMES, normalizedTagName);
}

splitAtMarker(marker, offset=0) {
Expand Down
4 changes: 4 additions & 0 deletions src/js/utils/cursor.js
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,10 @@ const Cursor = class Cursor {
}

moveToPosition(position) {
if (position._inCard) {
// FIXME add the ability to position the cursor on/in a card
return;
}
this.selectRange(new Range(position, position));
}

Expand Down
Loading

0 comments on commit d4ce47a

Please sign in to comment.