diff --git a/src/js/editor/post.js b/src/js/editor/post.js index 3380fb19b..278e36fbd 100644 --- a/src/js/editor/post.js +++ b/src/js/editor/post.js @@ -1,12 +1,6 @@ -import { MARKUP_SECTION_TYPE } from '../models/markup-section'; -import { LIST_ITEM_TYPE } from '../models/list-item'; -import { LIST_SECTION_TYPE } from '../models/list-section'; +import { MARKUP_SECTION_TYPE, LIST_ITEM_TYPE } from '../models/types'; import Position from '../utils/cursor/position'; -import { - filter, - compact -} from '../utils/array-utils'; - +import { filter, compact } from '../utils/array-utils'; import { DIRECTION } from '../utils/key'; function isMarkupSection(section) { @@ -25,21 +19,6 @@ function isMarkerable(section) { return !!section.markers; } -function isListSection(section) { - return section.type === LIST_SECTION_TYPE; -} - -// finds the immediately preceding section that is markerable -function findPreviousMarkerableSection(section) { - const prev = section.prev; - if (!prev) { return null; } - if (isMarkerable(prev)) { - return prev; - } else if (isListSection(prev)) { - return prev.items.tail; - } -} - class PostEditor { constructor(editor) { this.editor = editor; @@ -231,7 +210,7 @@ class PostEditor { } else if (isListItem(section)) { nextPosition = this._convertListItemToMarkupSection(section); } else { - const prevSection = findPreviousMarkerableSection(section); + const prevSection = section.immediatelyPreviousMarkerableSection(); if (prevSection) { const { beforeMarker } = prevSection.join(section); diff --git a/src/js/editor/text-expansions.js b/src/js/editor/text-expansions.js index 9edb7bde8..0c8c34cf4 100644 --- a/src/js/editor/text-expansions.js +++ b/src/js/editor/text-expansions.js @@ -1,7 +1,7 @@ import Keycodes from '../utils/keycodes'; import Key from '../utils/key'; import { detect } from '../utils/array-utils'; -import { MARKUP_SECTION_TYPE } from '../models/markup-section'; +import { MARKUP_SECTION_TYPE } from '../models/types'; const { SPACE } = Keycodes; diff --git a/src/js/models/_markerable.js b/src/js/models/_markerable.js index a9fa16f22..0a008c078 100644 --- a/src/js/models/_markerable.js +++ b/src/js/models/_markerable.js @@ -1,13 +1,12 @@ -import { normalizeTagName } from '../utils/dom-utils'; import { forEach, filter, reduce } from '../utils/array-utils'; import Set from '../utils/set'; -import LinkedItem from '../utils/linked-item'; import LinkedList from '../utils/linked-list'; +import Section from './_section'; -export default class Markerable extends LinkedItem { - constructor(tagName, markers=[]) { - super(); +export default class Markerable extends Section { + constructor(type, tagName, markers=[]) { + super(type); this.tagName = tagName; this.markers = new LinkedList({ adoptItem: m => m.section = m.parent = this, @@ -17,14 +16,6 @@ export default class Markerable extends LinkedItem { 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; diff --git a/src/js/models/_section.js b/src/js/models/_section.js new file mode 100644 index 000000000..bcb4a9b47 --- /dev/null +++ b/src/js/models/_section.js @@ -0,0 +1,57 @@ +import { LIST_ITEM_TYPE, LIST_SECTION_TYPE } from './types'; +import { normalizeTagName } from '../utils/dom-utils'; +import LinkedItem from '../utils/linked-item'; + +function isMarkerable(section) { + return !!section.markers; +} + +function isListSection(section) { + return section.type === LIST_SECTION_TYPE; +} + +function isListItem(section) { + return section.type === LIST_ITEM_TYPE; +} + +export default class Section extends LinkedItem { + constructor(type) { + super(); + if (!type) { throw new Error('Cannot create section without type'); } + this.type = type; + } + + set tagName(val) { + this._tagName = normalizeTagName(val); + } + + get tagName() { + return this._tagName; + } + + immediatelyNextMarkerableSection() { + const next = this.next; + if (next) { + if (isMarkerable(next)) { + return next; + } else if (isListSection(next)) { + const firstListItem = next.items.head; + return firstListItem; + } + } else if (isListItem(this)) { + const listSection = this.parent; + return listSection.immediatelyNextMarkerableSection(); + } + } + + immediatelyPreviousMarkerableSection() { + const prev = this.prev; + if (!prev) { return null; } + if (isMarkerable(prev)) { + return prev; + } else if (isListSection(prev)) { + const lastListItem = prev.items.tail; + return lastListItem; + } + } +} diff --git a/src/js/models/card.js b/src/js/models/card.js index ab2f7aa2b..1ea23ce46 100644 --- a/src/js/models/card.js +++ b/src/js/models/card.js @@ -1,12 +1,10 @@ -import LinkedItem from "content-kit-editor/utils/linked-item"; +import Section from './_section'; +import { CARD_TYPE } from './types'; -export const CARD_TYPE = 'card-section'; - -export default class Card extends LinkedItem { +export default class Card extends Section { constructor(name, payload) { - super(); + super(CARD_TYPE); this.name = name; this.payload = payload; - this.type = CARD_TYPE; } } diff --git a/src/js/models/image.js b/src/js/models/image.js index aadf218ea..21b9196da 100644 --- a/src/js/models/image.js +++ b/src/js/models/image.js @@ -1,8 +1,9 @@ -export const IMAGE_SECTION_TYPE = 'image-section'; +import { IMAGE_SECTION_TYPE } from './types'; +import Section from './_section'; -export default class Image { +export default class Image extends Section { constructor() { - this.type = IMAGE_SECTION_TYPE; + super(IMAGE_SECTION_TYPE); this.src = null; } } diff --git a/src/js/models/list-item.js b/src/js/models/list-item.js index 21c6d8b12..b26804335 100644 --- a/src/js/models/list-item.js +++ b/src/js/models/list-item.js @@ -1,11 +1,9 @@ import Markerable from './_markerable'; - -export const LIST_ITEM_TYPE = 'list-item'; +import { LIST_ITEM_TYPE } from './types'; export default class ListItem extends Markerable { constructor(tagName, markers=[]) { - super(tagName, markers); - this.type = LIST_ITEM_TYPE; + super(LIST_ITEM_TYPE, tagName, markers); } splitAtMarker(marker, offset=0) { diff --git a/src/js/models/list-section.js b/src/js/models/list-section.js index c92aff4a4..e362dbd1c 100644 --- a/src/js/models/list-section.js +++ b/src/js/models/list-section.js @@ -1,13 +1,14 @@ -import { normalizeTagName } from '../utils/dom-utils'; import LinkedList from '../utils/linked-list'; +import { LIST_SECTION_TYPE } from './types'; +import Section from './_section'; export const DEFAULT_TAG_NAME = 'ul'; -export const LIST_SECTION_TYPE = 'list-section'; -export default class ListSection { +export default class ListSection extends Section { constructor(tagName=DEFAULT_TAG_NAME, items=[]) { - this.tagName = normalizeTagName(tagName); - this.type = LIST_SECTION_TYPE; + super(LIST_SECTION_TYPE); + + this.tagName = tagName; this.items = new LinkedList({ adoptItem: i => i.section = i.parent = this, diff --git a/src/js/models/marker.js b/src/js/models/marker.js index f2892dd16..0c5f6ce08 100644 --- a/src/js/models/marker.js +++ b/src/js/models/marker.js @@ -1,15 +1,8 @@ -export const MARKER_TYPE = 'marker'; - -import { - normalizeTagName -} from '../utils/dom-utils'; -import { - detect, - commonItemLength, - forEach, - filter -} from 'content-kit-editor/utils/array-utils'; -import LinkedItem from "content-kit-editor/utils/linked-item"; +import { MARKER_TYPE } from './types'; + +import { normalizeTagName } from '../utils/dom-utils'; +import { detect, commonItemLength, forEach, filter } from '../utils/array-utils'; +import LinkedItem from '../utils/linked-item'; const Marker = class Marker extends LinkedItem { constructor(value='', markups=[]) { @@ -17,10 +10,7 @@ const Marker = class Marker extends LinkedItem { this.value = value; this.markups = []; this.type = MARKER_TYPE; - - if (markups && markups.length) { - markups.forEach(m => this.addMarkup(m)); - } + markups.forEach(m => this.addMarkup(m)); } clone() { diff --git a/src/js/models/markup-section.js b/src/js/models/markup-section.js index 278d40e67..89e837e5c 100644 --- a/src/js/models/markup-section.js +++ b/src/js/models/markup-section.js @@ -1,25 +1,15 @@ import Markerable from './_markerable'; import { normalizeTagName } from '../utils/dom-utils'; +import { MARKUP_SECTION_TYPE } from './types'; 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'; - const MarkupSection = class MarkupSection extends Markerable { constructor(tagName=DEFAULT_TAG_NAME, markers=[]) { - super(tagName, markers); - this.type = MARKUP_SECTION_TYPE; - } - - set tagName(val) { - this._tagName = normalizeTagName(val); - } - - get tagName() { - return this._tagName; + super(MARKUP_SECTION_TYPE, tagName, markers); } setTagName(newTagName) { diff --git a/src/js/models/markup.js b/src/js/models/markup.js index 994247caf..8bfe65e23 100644 --- a/src/js/models/markup.js +++ b/src/js/models/markup.js @@ -1,7 +1,6 @@ -import { - normalizeTagName -} from '../utils/dom-utils'; -export const MARKUP_TYPE = 'markup'; +import { normalizeTagName } from '../utils/dom-utils'; +import { MARKUP_TYPE } from './types'; + export const VALID_MARKUP_TAGNAMES = [ 'b', 'i', diff --git a/src/js/models/post.js b/src/js/models/post.js index 3faaf7626..7b0727d64 100644 --- a/src/js/models/post.js +++ b/src/js/models/post.js @@ -1,4 +1,4 @@ -export const POST_TYPE = 'post'; +import { POST_TYPE } from './types'; import LinkedList from 'content-kit-editor/utils/linked-list'; import { forEach, compact } from 'content-kit-editor/utils/array-utils'; import Set from 'content-kit-editor/utils/set'; diff --git a/src/js/models/types.js b/src/js/models/types.js new file mode 100644 index 000000000..12dd1a07b --- /dev/null +++ b/src/js/models/types.js @@ -0,0 +1,8 @@ +export const MARKUP_SECTION_TYPE = 'markup-section'; +export const LIST_SECTION_TYPE = 'list-section'; +export const MARKUP_TYPE = 'markup'; +export const MARKER_TYPE = 'marker'; +export const POST_TYPE = 'post'; +export const LIST_ITEM_TYPE = 'list-item'; +export const CARD_TYPE = 'card-section'; +export const IMAGE_SECTION_TYPE = 'image-section'; diff --git a/src/js/parsers/post.js b/src/js/parsers/post.js index 26d138ca1..dd42e3a9b 100644 --- a/src/js/parsers/post.js +++ b/src/js/parsers/post.js @@ -1,6 +1,9 @@ -import { MARKUP_SECTION_TYPE } from '../models/markup-section'; -import { LIST_SECTION_TYPE } from '../models/list-section'; -import { LIST_ITEM_TYPE } from '../models/list-item'; +import { + MARKUP_SECTION_TYPE, + LIST_SECTION_TYPE, + LIST_ITEM_TYPE +} from '../models/types'; + import SectionParser from 'content-kit-editor/parsers/section'; import { forEach } from 'content-kit-editor/utils/array-utils'; import { getAttributesArray, walkTextNodes } from '../utils/dom-utils'; diff --git a/src/js/renderers/editor-dom.js b/src/js/renderers/editor-dom.js index 47d078d79..2088ce35c 100644 --- a/src/js/renderers/editor-dom.js +++ b/src/js/renderers/editor-dom.js @@ -1,13 +1,15 @@ import RenderNode from 'content-kit-editor/models/render-node'; import CardNode from 'content-kit-editor/models/card-node'; import { detect } from 'content-kit-editor/utils/array-utils'; -import { POST_TYPE } from '../models/post'; -import { MARKUP_SECTION_TYPE } from '../models/markup-section'; -import { LIST_SECTION_TYPE } from '../models/list-section'; -import { LIST_ITEM_TYPE } from '../models/list-item'; -import { MARKER_TYPE } from '../models/marker'; -import { IMAGE_SECTION_TYPE } from '../models/image'; -import { CARD_TYPE } from '../models/card'; +import { + POST_TYPE, + MARKUP_SECTION_TYPE, + LIST_SECTION_TYPE, + LIST_ITEM_TYPE, + MARKER_TYPE, + IMAGE_SECTION_TYPE, + CARD_TYPE +} from '../models/types'; import { startsWith, endsWith } from '../utils/string-utils'; import { addClassName } from '../utils/dom-utils'; diff --git a/src/js/renderers/mobiledoc.js b/src/js/renderers/mobiledoc.js index 7b6e3bb32..f33f2523c 100644 --- a/src/js/renderers/mobiledoc.js +++ b/src/js/renderers/mobiledoc.js @@ -1,21 +1,22 @@ -import {visit, visitArray, compile} from "../utils/compiler"; -import { POST_TYPE } from "../models/post"; -import { MARKUP_SECTION_TYPE } from "../models/markup-section"; -import { LIST_SECTION_TYPE } from "../models/list-section"; -import { LIST_ITEM_TYPE } from "../models/list-item"; -import { IMAGE_SECTION_TYPE } from "../models/image"; -import { MARKER_TYPE } from "../models/marker"; -import { MARKUP_TYPE } from "../models/markup"; -import { CARD_TYPE } from "../models/card"; +import {visit, visitArray, compile} from '../utils/compiler'; +import { + POST_TYPE, + MARKUP_SECTION_TYPE, + LIST_SECTION_TYPE, + LIST_ITEM_TYPE, + MARKER_TYPE, + MARKUP_TYPE, + IMAGE_SECTION_TYPE, + CARD_TYPE +} from '../models/types'; export const MOBILEDOC_VERSION = '0.2.0'; - export const MOBILEDOC_MARKUP_SECTION_TYPE = 1; export const MOBILEDOC_IMAGE_SECTION_TYPE = 2; export const MOBILEDOC_LIST_SECTION_TYPE = 3; export const MOBILEDOC_CARD_SECTION_TYPE = 10; -let visitor = { +const visitor = { [POST_TYPE](node, opcodes) { opcodes.push(['openPost']); visitArray(visitor, node.sections, opcodes); @@ -47,7 +48,7 @@ let visitor = { } }; -let postOpcodeCompiler = { +const postOpcodeCompiler = { openMarker(closeCount, value) { this.markupMarkerIds = []; this.markers.push([ diff --git a/src/js/utils/cursor/position.js b/src/js/utils/cursor/position.js index 0498e8774..16ddfacd5 100644 --- a/src/js/utils/cursor/position.js +++ b/src/js/utils/cursor/position.js @@ -1,7 +1,7 @@ import { isTextNode, walkTextNodes } from 'content-kit-editor/utils/dom-utils'; -import { MARKUP_SECTION_TYPE } from 'content-kit-editor/models/markup-section'; -import { LIST_ITEM_TYPE } from 'content-kit-editor/models/list-item'; -import { CARD_TYPE } from 'content-kit-editor/models/card'; +import { + MARKUP_SECTION_TYPE, LIST_ITEM_TYPE, CARD_TYPE +} from 'content-kit-editor/models/types'; function isSection(postNode) { if (!(postNode && postNode.type)) { return false; } diff --git a/tests/acceptance/editor-sections-test.js b/tests/acceptance/editor-sections-test.js index 317cfec4b..733730f65 100644 --- a/tests/acceptance/editor-sections-test.js +++ b/tests/acceptance/editor-sections-test.js @@ -197,7 +197,6 @@ test('hitting enter in a section creates a new basic section', (assert) => { assert.hasElement('#editor p:contains(X)', 'p tag instead of h2 generated'); }); -// Phantom does not recognize toggling contenteditable off test('deleting across 2 sections does nothing if editing is disabled', (assert) => { editor = new Editor({mobiledoc: mobileDocWith2Sections}); editor.render(editorElement); diff --git a/tests/unit/parsers/dom-test.js b/tests/unit/parsers/dom-test.js index b2994b92a..b6c7f2091 100644 --- a/tests/unit/parsers/dom-test.js +++ b/tests/unit/parsers/dom-test.js @@ -1,7 +1,8 @@ import DOMParser from 'content-kit-editor/parsers/dom'; import PostNodeBuilder from 'content-kit-editor/models/post-node-builder'; +import Helpers from '../../test-helpers'; -const { module, test } = window.QUnit; +const { module, test } = Helpers; function buildDOM(html) { var div = document.createElement('div'); @@ -190,7 +191,9 @@ test('sup tag (stray markup) without a block should filter SUP and create a bloc assert.deepEqual(post, expectedPost); }); -test('list (stray markup) without a block should create a block', (assert) => { +// This is not the way a list should be created -- it should be list sections +// and list items +Helpers.skip('list (stray markup) without a block should create a block', (assert) => { const post = parser.parse(buildDOM('')); let expectedFirst = builder.createMarkupSection('UL'); @@ -202,7 +205,7 @@ test('list (stray markup) without a block should create a block', (assert) => { ])); expectedPost.sections.append(expectedFirst); - assert.deepEqual(post, expectedPost); + assert.ok(post === expectedPost, 'should generate correct output'); }); test('nested tags (section markup) should create a block', (assert) => {