diff --git a/Makefile b/Makefile
index 45ad6a1f7..e3c666ad0 100644
--- a/Makefile
+++ b/Makefile
@@ -39,6 +39,10 @@ example-plain-text:
example-rich-text:
@ $(browserify) --debug --transform babelify --outfile ./examples/rich-text/build.js ./examples/rich-text/index.js
+# Build the table example.
+example-table:
+ @ $(browserify) --debug --transform babelify --outfile ./examples/table/build.js ./examples/table/index.js
+
# Install the dependencies.
install:
@ npm install
@@ -74,6 +78,10 @@ watch-example-plain-text:
watch-example-rich-text:
@ $(MAKE) example-rich-text browserify=$(watchify)
+# Watch the table example.
+watch-example-table:
+ @ $(MAKE) example-table browserify=$(watchify)
+
# Phony targets.
.PHONY: examples
.PHONY: test
diff --git a/examples/table/index.css b/examples/table/index.css
new file mode 100644
index 000000000..e9da1b93d
--- /dev/null
+++ b/examples/table/index.css
@@ -0,0 +1,66 @@
+
+html {
+ background: #eee;
+ padding: 20px;
+}
+
+main {
+ background: #fff;
+ padding: 10px;
+ max-width: 40em;
+ margin: 0 auto;
+}
+
+p {
+ margin: 0;
+}
+
+table {
+ border-collapse: collapse;
+}
+
+td {
+ padding: 10px;
+ border: 2px solid #ddd;
+}
+
+blockquote {
+ border-left: 2px solid #ddd;
+ margin-left: 0;
+ padding-left: 10px;
+ color: #aaa;
+ font-style: italic;
+}
+
+.editor > * > * + * {
+ margin-top: 1em;
+ margin-bottom: 0;
+}
+
+.menu {
+ margin: 0 -10px;
+ padding: 1px 0 9px 8px;
+ border-bottom: 2px solid #eee;
+ margin-bottom: 10px;
+}
+
+.menu > * {
+ display: inline-block;
+}
+
+.menu > * + * {
+ margin-left: 10px;
+}
+
+.button {
+ color: #ccc;
+ cursor: pointer;
+}
+
+.button[data-active="true"] {
+ color: black;
+}
+
+.material-icons {
+ font-size: 18px;
+}
diff --git a/examples/table/index.html b/examples/table/index.html
new file mode 100644
index 000000000..2ee7e77dd
--- /dev/null
+++ b/examples/table/index.html
@@ -0,0 +1,12 @@
+
+
+
+ Editor | Auto-markdown Example
+
+
+
+
+
+
+
+
diff --git a/examples/table/index.js b/examples/table/index.js
new file mode 100644
index 000000000..38451003d
--- /dev/null
+++ b/examples/table/index.js
@@ -0,0 +1,165 @@
+
+import Editor, { Raw } from '../..'
+import React from 'react'
+import ReactDOM from 'react-dom'
+import keycode from 'keycode'
+import state from './state.json'
+
+/**
+ * Define our example app.
+ *
+ * @type {Component} App
+ */
+
+class App extends React.Component {
+
+ /**
+ * Deserialize the raw initial state.
+ *
+ * @type {Object}
+ */
+
+ state = {
+ state: Raw.deserialize(state)
+ };
+
+ /**
+ * Render the example.
+ *
+ * @return {Component} component
+ */
+
+ render() {
+ return (
+
+ this.renderNode(node)}
+ renderMark={mark => this.renderMark(mark)}
+ onKeyDown={(e, state) => this.onKeyDown(e, state)}
+ onChange={(state) => {
+ console.groupCollapsed('Change!')
+ console.log('Document:', state.document.toJS())
+ console.log('Selection:', state.selection.toJS())
+ console.log('Content:', Raw.serialize(state))
+ console.groupEnd()
+ this.setState({ state })
+ }}
+ />
+
+ )
+ }
+
+ /**
+ * Render each of our custom `mark` types.
+ *
+ * @param {Mark} mark
+ * @return {Component} component
+ */
+
+ renderMark(mark) {
+ switch (mark.type) {
+ case 'bold': {
+ return {
+ fontWeight: 'bold'
+ }
+ }
+ }
+ }
+
+ /**
+ * Render each of our custom `node` types.
+ *
+ * @param {Node} node
+ * @return {Component} component
+ */
+
+ renderNode(node) {
+ switch (node.type) {
+ case 'paragraph': {
+ return (props) => {props.children}
+ }
+ case 'table': {
+ return (props) =>
+ }
+ case 'table-row': {
+ return (props) => {props.children}
+ }
+ case 'table-cell': {
+ return (props) => {props.children} |
+ }
+ }
+ }
+
+ /**
+ * On key down, check for our specific key shortcuts.
+ *
+ * @param {Event} e
+ * @param {State} state
+ * @return {State or Null} state
+ */
+
+ onKeyDown(e, state) {
+ if (state.isCurrentlyExpanded) return
+ const node = state.currentBlockNodes.first()
+ if (node.type != 'table-cell') return
+
+ const key = keycode(e.which)
+ switch (key) {
+ case 'backspace': return this.onBackspace(e, state)
+ case 'delete': return this.onDelete(e, state)
+ case 'enter': return this.onEnter(e, state)
+ }
+ }
+
+ /**
+ * On backspace, do nothing if at the start of a table cell.
+ *
+ * @param {Event} e
+ * @param {State} state
+ * @return {State or Null} state
+ */
+
+ onBackspace(e, state) {
+ if (state.currentStartOffset != 0) return
+ e.preventDefault()
+ return state
+ }
+
+ /**
+ * On delete, do nothing if at the end of a table cell.
+ *
+ * @param {Event} e
+ * @param {State} state
+ * @return {State or Null} state
+ */
+
+ onDelete(e, state) {
+ const node = state.currentBlockNodes.first()
+ if (state.currentEndOffset != node.length) return
+ e.preventDefault()
+ return state
+ }
+
+ /**
+ * On return, do nothing if inside a table cell.
+ *
+ * @param {Event} e
+ * @param {State} state
+ * @return {State or Null} state
+ */
+
+ onEnter(e, state) {
+ e.preventDefault()
+ return state
+ }
+
+}
+
+/**
+ * Mount the app.
+ */
+
+const app =
+const root = document.body.querySelector('main')
+ReactDOM.render(app, root)
diff --git a/examples/table/state.json b/examples/table/state.json
new file mode 100644
index 000000000..f0f86100f
--- /dev/null
+++ b/examples/table/state.json
@@ -0,0 +1,249 @@
+{
+ "nodes": [
+ {
+ "kind": "block",
+ "type": "paragraph",
+ "nodes": [
+ {
+ "kind": "text",
+ "ranges": [
+ {
+ "text": "Since the editor is based on a recursive tree model, similar to an HTML document, you can create complex nested structures, like tables:"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "kind": "block",
+ "type": "table",
+ "nodes": [
+ {
+ "kind": "block",
+ "type": "table-row",
+ "nodes": [
+ {
+ "kind": "block",
+ "type": "table-cell",
+ "nodes": [
+ {
+ "kind": "text",
+ "ranges": [
+ {
+ "text": "",
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "kind": "block",
+ "type": "table-cell",
+ "nodes": [
+ {
+ "kind": "text",
+ "ranges": [
+ {
+ "text": "Human",
+ "marks": [
+ {
+ "type": "bold"
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "kind": "block",
+ "type": "table-cell",
+ "nodes": [
+ {
+ "kind": "text",
+ "ranges": [
+ {
+ "text": "Dog",
+ "marks": [
+ {
+ "type": "bold"
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "kind": "block",
+ "type": "table-cell",
+ "nodes": [
+ {
+ "kind": "text",
+ "ranges": [
+ {
+ "text": "Cat",
+ "marks": [
+ {
+ "type": "bold"
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "kind": "block",
+ "type": "table-row",
+ "nodes": [
+ {
+ "kind": "block",
+ "type": "table-cell",
+ "nodes": [
+ {
+ "kind": "text",
+ "ranges": [
+ {
+ "text": "# of Feet",
+ "marks": [
+ {
+ "type": "bold"
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "kind": "block",
+ "type": "table-cell",
+ "nodes": [
+ {
+ "kind": "text",
+ "ranges": [
+ {
+ "text": "1"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "kind": "block",
+ "type": "table-cell",
+ "nodes": [
+ {
+ "kind": "text",
+ "ranges": [
+ {
+ "text": "4"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "kind": "block",
+ "type": "table-cell",
+ "nodes": [
+ {
+ "kind": "text",
+ "ranges": [
+ {
+ "text": "4"
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "kind": "block",
+ "type": "table-row",
+ "nodes": [
+ {
+ "kind": "block",
+ "type": "table-cell",
+ "nodes": [
+ {
+ "kind": "text",
+ "ranges": [
+ {
+ "text": "# of Lives",
+ "marks": [
+ {
+ "type": "bold"
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "kind": "block",
+ "type": "table-cell",
+ "nodes": [
+ {
+ "kind": "text",
+ "ranges": [
+ {
+ "text": "1"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "kind": "block",
+ "type": "table-cell",
+ "nodes": [
+ {
+ "kind": "text",
+ "ranges": [
+ {
+ "text": "1"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "kind": "block",
+ "type": "table-cell",
+ "nodes": [
+ {
+ "kind": "text",
+ "ranges": [
+ {
+ "text": "9"
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "kind": "block",
+ "type": "paragraph",
+ "nodes": [
+ {
+ "kind": "text",
+ "ranges": [
+ {
+ "text": "This table is just a basic example, but you could augment it to add support for table headers, adding column and rows, or even formulas if you wanted to get really crazy..."
+ }
+ ]
+ }
+ ]
+ }
+ ]
+}