From fccf762efd537d2b70db5fb86d7402ddbed87139 Mon Sep 17 00:00:00 2001 From: Ian Storm Taylor Date: Tue, 21 Jun 2016 18:00:18 -0700 Subject: [PATCH] add wrap transform --- examples/auto-markdown/index.js | 33 ++++++++++-------- lib/components/content.js | 59 +++++++++++++++++++++++---------- lib/models/block.js | 2 +- lib/models/document.js | 3 +- lib/models/inline.js | 2 +- lib/models/node.js | 40 +++++++++++++--------- lib/models/state.js | 15 +++++++++ lib/models/text.js | 3 +- lib/models/transform.js | 6 +++- 9 files changed, 110 insertions(+), 53 deletions(-) diff --git a/examples/auto-markdown/index.js b/examples/auto-markdown/index.js index 789a36943..b060b43f4 100644 --- a/examples/auto-markdown/index.js +++ b/examples/auto-markdown/index.js @@ -63,7 +63,7 @@ class App extends React.Component { return (props) =>
{props.children}
} case 'bulleted-list': { - return (props) => + return (props) => } case 'heading-one': { return (props) =>

{props.children}

@@ -84,10 +84,7 @@ class App extends React.Component { return (props) =>
{props.children}
} case 'list-item': { - return (props) =>
  • {props.chidlren}
  • - } - case 'numbered-list': { - return (props) =>
      {props.children}
    + return (props) =>
  • {props.children}
  • } case 'paragraph': { return (props) =>

    {props.children}

    @@ -153,24 +150,28 @@ class App extends React.Component { case '>': transform = transform.setType('block-quote') break + case '*': case '-': - transform = transform.setType('list-item') - const wrapper = document.getParentNode(node) - if (wrapper.type == 'paragraph') transform = transform.setType('bulleted-list') - if (wrapper.type == 'bulleted-list') transform = transform.wrap('list-item') - if (wrapper.type == 'list-item') transform = transform.wrap('unordered-list') + case '+': + if (node.type == 'list-item') break + transform = node.type == 'list-item' + ? transform + : transform + .setType('list-item') + .wrap('bulleted-list') break default: return } + e.preventDefault() + state = transform .deleteAtRange(selection.extendBackwardToStartOf(node)) .apply() selection = selection.moveToStartOf(node) state = state.merge({ selection }) - e.preventDefault() return state } @@ -189,12 +190,16 @@ class App extends React.Component { const node = state.currentBlockNodes.first() if (!node) debugger if (node.type == 'paragraph') return - e.preventDefault() - return state + + let transform = state .transform() .setType('paragraph') - .apply() + + if (node.type == 'list-item') transform = transform.unwrap('bulleted-list') + + state = transform.apply() + return state } /** diff --git a/lib/components/content.js b/lib/components/content.js index b8e3a04d7..1a4cd8aae 100644 --- a/lib/components/content.js +++ b/lib/components/content.js @@ -145,7 +145,6 @@ class Content extends React.Component { */ render() { - console.log('Rendered content.') const { state } = this.props const { document } = state const children = document.nodes @@ -175,39 +174,65 @@ class Content extends React.Component { } /** - * Render a single `node`. + * Render a `node`. * * @param {Node} node * @return {Component} component */ renderNode(node) { - const { renderMark, renderNode, state } = this.props - - if (node instanceof TextModel) { - return ( - - ) + switch (node.kind) { + case 'text': + return this.renderText(node) + case 'block': + case 'inline': + return this.renderElement(node) } + } + /** + * Render a text `node`. + * + * @param {Node} node + * @return {Component} component + */ + + renderText(node) { + const { renderMark, renderNode, state } = this.props + return ( + + ) + } + + /** + * Render an element `node`. + * + * @param {Node} node + * @return {Component} component + */ + + renderElement(node) { + const { renderNode, state } = this.props const Component = renderNode(node) const children = node.nodes - .map(node => this.renderNode(node)) + .map(child => this.renderNode(child)) .toArray() return ( + > + {children} + ) + } } diff --git a/lib/models/block.js b/lib/models/block.js index 7dc547bd5..4c797758b 100644 --- a/lib/models/block.js +++ b/lib/models/block.js @@ -1,7 +1,7 @@ import Node from './node' import uid from 'uid' -import { OrderedMap, Record } from 'immutable' +import { Map, OrderedMap, Record } from 'immutable' /** * Default properties. diff --git a/lib/models/document.js b/lib/models/document.js index 0dce6574b..6e20b2407 100644 --- a/lib/models/document.js +++ b/lib/models/document.js @@ -7,8 +7,7 @@ import { OrderedMap, Record } from 'immutable' */ const DEFAULTS = { - nodes: new OrderedMap(), - parent: null + nodes: new OrderedMap() } /** diff --git a/lib/models/inline.js b/lib/models/inline.js index 5da17958c..20aafafd1 100644 --- a/lib/models/inline.js +++ b/lib/models/inline.js @@ -1,7 +1,7 @@ import Node from './node' import uid from 'uid' -import { OrderedMap, Record } from 'immutable' +import { Map, OrderedMap, Record } from 'immutable' /** * Record. diff --git a/lib/models/node.js b/lib/models/node.js index 31c52f547..5828efbde 100644 --- a/lib/models/node.js +++ b/lib/models/node.js @@ -886,32 +886,42 @@ const Node = { }, /** - * Wrap all of the nodes in a `range` in a new `parent` node. + * Wrap all of the nodes in a `range` in a new parent node of `type`. * * @param {Selection} range - * @param {Node or String} parent + * @param {String} type * @return {Node} node */ - wrapAtRange(range, parent) { + wrapAtRange(range, type) { + range = range.normalize(this) let node = this - range = range.normalize(node) + let blocks = node.getBlockNodesAtRange(range) - // Allow for the parent to by just a type. - if (typeof parent == 'string') { - parent = Block.create({ type: parent }) - } + // Iterate each of the block nodes, wrapping them. + blocks.forEach((block) => { + let isDirectChild = node.nodes.has(block.key) + let parent = isDirectChild ? node : node.getParentNode(block) - // Add the child to the parent's nodes. - const child = node.findNode(key) - parent = node.nodes.set(child.key, child) + // Create a new wrapper containing the block. + let nodes = Block.createMap([ block ]) + let wrapper = Block.create({ type, nodes }) - // Remove the child from this node. - node = node.removeNode(child) + // Replace the block in it's parent with the wrapper. + nodes = parent.nodes.takeUntil(node => node == block) + .set(wrapper.key, wrapper) + .concat(parent.nodes.skipUntil(node => node == block).rest()) - // Add the parent to this node. + // Update the parent. + if (isDirectChild) { + node = node.merge({ nodes }) + } else { + parent = parent.merge({ nodes }) + node = node.updateNode(parent) + } + }) - return node + return node.normalize() } /** diff --git a/lib/models/state.js b/lib/models/state.js index 173a7ed4d..c30ed3549 100644 --- a/lib/models/state.js +++ b/lib/models/state.js @@ -338,6 +338,21 @@ class State extends Record(DEFAULTS) { return state } + /** + * Wrap the block nodes in the current selection in new nodes of `type`. + * + * @param {String} type + * @return {State} state + */ + + wrap(type) { + let state = this + let { document, selection } = state + document = document.wrapAtRange(selection, type) + state = state.merge({ document }) + return state + } + } /** diff --git a/lib/models/text.js b/lib/models/text.js index 81f22d2fc..75811c48b 100644 --- a/lib/models/text.js +++ b/lib/models/text.js @@ -8,8 +8,7 @@ import { List, Record } from 'immutable' const DEFAULTS = { characters: new List(), - key: null, - parent: null + key: null } /** diff --git a/lib/models/transform.js b/lib/models/transform.js index 906984a68..9b3582625 100644 --- a/lib/models/transform.js +++ b/lib/models/transform.js @@ -51,7 +51,11 @@ const TRANSFORM_TYPES = [ 'split', 'splitAtRange', 'unmark', - 'unmarkAtRange' + 'unmarkAtRange', + 'unwrap', + 'unwrapAtRange', + 'wrap', + 'wrapAtRange' ] /**