diff --git a/examples/code-highlighting/index.js b/examples/code-highlighting/index.js
index 6ec28a58a..57d616903 100644
--- a/examples/code-highlighting/index.js
+++ b/examples/code-highlighting/index.js
@@ -55,7 +55,7 @@ function CodeBlock(props) {
*/
function codeBlockDecorator(text, block) {
- let characters = text.characters.asMutable()
+ const characters = text.characters.asMutable()
const language = block.data.get('language')
const string = text.text
const grammar = Prism.languages[language]
@@ -76,7 +76,7 @@ function codeBlockDecorator(text, block) {
let { marks } = char
marks = marks.add(Mark.create({ type }))
char = char.merge({ marks })
- characters = characters.set(i, char)
+ characters.set(i, char)
}
offset = length
diff --git a/examples/index.js b/examples/index.js
index 4a5ae5c62..3d839cfa3 100644
--- a/examples/index.js
+++ b/examples/index.js
@@ -7,7 +7,6 @@ import { Router, Route, Link, IndexRedirect, hashHistory } from 'react-router'
* Examples.
*/
-import AutoMarkdown from './auto-markdown'
import CheckLists from './check-lists'
import CodeHighlighting from './code-highlighting'
import Embeds from './embeds'
@@ -18,6 +17,8 @@ import Iframes from './iframes'
import Images from './images'
import LargeDocument from './large-document'
import Links from './links'
+import MarkdownPreview from './markdown-preview'
+import MarkdownShortcuts from './markdown-shortcuts'
import PasteHtml from './paste-html'
import PlainText from './plain-text'
import Plugins from './plugins'
@@ -94,22 +95,23 @@ class App extends React.Component {
{this.renderTab('Rich Text', 'rich-text')}
{this.renderTab('Plain Text', 'plain-text')}
- {this.renderTab('Auto-markdown', 'auto-markdown')}
{this.renderTab('Hovering Menu', 'hovering-menu')}
- {this.renderTab('Large Document', 'large')}
{this.renderTab('Links', 'links')}
{this.renderTab('Images', 'images')}
{this.renderTab('Embeds', 'embeds')}
{this.renderTab('Emojis', 'emojis')}
- {this.renderTab('Tables', 'tables')}
+ {this.renderTab('Markdown Preview', 'markdown-preview')}
+ {this.renderTab('Markdown Shortcuts', 'markdown-shortcuts')}
{this.renderTab('Check Lists', 'check-lists')}
{this.renderTab('Code Highlighting', 'code-highlighting')}
+ {this.renderTab('Tables', 'tables')}
{this.renderTab('Paste HTML', 'paste-html')}
{this.renderTab('Read-only', 'read-only')}
{this.renderTab('RTL', 'rtl')}
{this.renderTab('Plugins', 'plugins')}
{this.renderTab('Iframes', 'iframes')}
{this.renderTab('Focus & Blur', 'focus-blur')}
+ {this.renderTab('Large Document', 'large')}
)
}
@@ -154,7 +156,6 @@ const router = (
-
@@ -165,6 +166,8 @@ const router = (
+
+
diff --git a/examples/auto-markdown/Readme.md b/examples/markdown-preview/Readme.md
similarity index 100%
rename from examples/auto-markdown/Readme.md
rename to examples/markdown-preview/Readme.md
diff --git a/examples/markdown-preview/index.js b/examples/markdown-preview/index.js
new file mode 100644
index 000000000..f7a18db62
--- /dev/null
+++ b/examples/markdown-preview/index.js
@@ -0,0 +1,156 @@
+
+import { Editor, Mark, Plain } from '../..'
+import Prism from 'prismjs'
+import React from 'react'
+
+/**
+ * Add the markdown syntax to Prism.
+ */
+
+// eslint-disable-next-line
+Prism.languages.markdown=Prism.languages.extend("markup",{}),Prism.languages.insertBefore("markdown","prolog",{blockquote:{pattern:/^>(?:[\t ]*>)*/m,alias:"punctuation"},code:[{pattern:/^(?: {4}|\t).+/m,alias:"keyword"},{pattern:/``.+?``|`[^`\n]+`/,alias:"keyword"}],title:[{pattern:/\w+.*(?:\r?\n|\r)(?:==+|--+)/,alias:"important",inside:{punctuation:/==+$|--+$/}},{pattern:/(^\s*)#+.+/m,lookbehind:!0,alias:"important",inside:{punctuation:/^#+|#+$/}}],hr:{pattern:/(^\s*)([*-])([\t ]*\2){2,}(?=\s*$)/m,lookbehind:!0,alias:"punctuation"},list:{pattern:/(^\s*)(?:[*+-]|\d+\.)(?=[\t ].)/m,lookbehind:!0,alias:"punctuation"},"url-reference":{pattern:/!?\[[^\]]+\]:[\t ]+(?:\S+|<(?:\\.|[^>\\])+>)(?:[\t ]+(?:"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|\((?:\\.|[^)\\])*\)))?/,inside:{variable:{pattern:/^(!?\[)[^\]]+/,lookbehind:!0},string:/(?:"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|\((?:\\.|[^)\\])*\))$/,punctuation:/^[\[\]!:]|[<>]/},alias:"url"},bold:{pattern:/(^|[^\\])(\*\*|__)(?:(?:\r?\n|\r)(?!\r?\n|\r)|.)+?\2/,lookbehind:!0,inside:{punctuation:/^\*\*|^__|\*\*$|__$/}},italic:{pattern:/(^|[^\\])([*_])(?:(?:\r?\n|\r)(?!\r?\n|\r)|.)+?\2/,lookbehind:!0,inside:{punctuation:/^[*_]|[*_]$/}},url:{pattern:/!?\[[^\]]+\](?:\([^\s)]+(?:[\t ]+"(?:\\.|[^"\\])*")?\)| ?\[[^\]\n]*\])/,inside:{variable:{pattern:/(!?\[)[^\]]+(?=\]$)/,lookbehind:!0},string:{pattern:/"(?:\\.|[^"\\])*"(?=\)$)/}}}}),Prism.languages.markdown.bold.inside.url=Prism.util.clone(Prism.languages.markdown.url),Prism.languages.markdown.italic.inside.url=Prism.util.clone(Prism.languages.markdown.url),Prism.languages.markdown.bold.inside.italic=Prism.util.clone(Prism.languages.markdown.italic),Prism.languages.markdown.italic.inside.bold=Prism.util.clone(Prism.languages.markdown.bold);
+
+/**
+ * Define a decorator for markdown styles.
+ *
+ * @param {Text} text
+ * @param {Block} block
+ */
+
+function markdownDecorator(text, block) {
+ const characters = text.characters.asMutable()
+ const language = 'markdown'
+ const string = text.text
+ const grammar = Prism.languages[language]
+ const tokens = Prism.tokenize(string, grammar)
+ addMarks(characters, tokens, 0)
+ return characters.asImmutable()
+}
+
+function addMarks(characters, tokens, offset) {
+ for (const token of tokens) {
+ if (typeof token == 'string') {
+ offset += token.length
+ continue
+ }
+
+ const { content, length, type } = token
+ const mark = Mark.create({ type })
+
+ for (let i = offset; i < offset + length; i++) {
+ let char = characters.get(i)
+ let { marks } = char
+ marks = marks.add(mark)
+ char = char.merge({ marks })
+ characters.set(i, char)
+ }
+
+ if (Array.isArray(content)) {
+ addMarks(characters, content, offset)
+ }
+
+ offset += length
+ }
+}
+
+/**
+ * Define a schema.
+ *
+ * @type {Object}
+ */
+
+const schema = {
+ marks: {
+ 'title': {
+ fontWeight: 'bold',
+ fontSize: '20px',
+ margin: '20px 0 10px 0',
+ display: 'inline-block'
+ },
+ 'bold': {
+ fontWeight: 'bold'
+ },
+ 'italic': {
+ fontStyle: 'italic'
+ },
+ 'punctuation': {
+ opacity: 0.2
+ },
+ 'code': {
+ fontFamily: 'monospace',
+ display: 'inline-block',
+ padding: '2px 1px',
+ },
+ 'list': {
+ paddingLeft: '10px',
+ lineHeight: '10px',
+ fontSize: '20px'
+ },
+ 'hr': {
+ borderBottom: '2px solid #000',
+ display: 'block',
+ opacity: 0.2
+ }
+ },
+ rules: [
+ {
+ match: () => true,
+ decorate: markdownDecorator,
+ }
+ ]
+}
+
+/**
+ * The markdown preview example.
+ *
+ * @type {Component}
+ */
+
+class MarkdownPreview extends React.Component {
+
+ /**
+ * Deserialize the initial editor state.
+ *
+ * @type {Object}
+ */
+
+ state = {
+ state: Plain.deserialize('Slate is flexible enough to add **decorators** that can format text based on its content. For example, this editor has **Markdown** preview decorators on it, to make it _dead_ simple to make an editor with built-in Markdown previewing.\n## Try it out!\nTry it out for yourself!')
+ }
+
+ /**
+ *
+ * Render the example.
+ *
+ * @return {Component} component
+ */
+
+ render = () => {
+ return (
+
+
+
+ )
+ }
+
+ /**
+ * On change.
+ *
+ * @param {State} state
+ */
+
+ onChange = (state) => {
+ this.setState({ state })
+ }
+
+}
+
+/**
+ * Export.
+ */
+
+export default MarkdownPreview
diff --git a/examples/auto-markdown/state.json b/examples/markdown-preview/state.json
similarity index 100%
rename from examples/auto-markdown/state.json
rename to examples/markdown-preview/state.json
diff --git a/examples/markdown-shortcuts/Readme.md b/examples/markdown-shortcuts/Readme.md
new file mode 100644
index 000000000..16cee1cec
--- /dev/null
+++ b/examples/markdown-shortcuts/Readme.md
@@ -0,0 +1,8 @@
+
+# Auto-markdown Example
+
+
+
+This example shows you can add a few key command handlers to get Markdown-like shortcuts in the editor. Such that once you press `> ` at the start of a line it turns it into a block quote!
+
+Check out the [Examples readme](..) to see how to run it!
diff --git a/examples/auto-markdown/index.js b/examples/markdown-shortcuts/index.js
similarity index 98%
rename from examples/auto-markdown/index.js
rename to examples/markdown-shortcuts/index.js
index 5c10686ee..961325be5 100644
--- a/examples/auto-markdown/index.js
+++ b/examples/markdown-shortcuts/index.js
@@ -29,7 +29,7 @@ const schema = {
* @type {Component}
*/
-class AutoMarkdown extends React.Component {
+class MarkdownShortcuts extends React.Component {
/**
* Deserialize the raw initial state.
@@ -212,4 +212,4 @@ class AutoMarkdown extends React.Component {
* Export.
*/
-export default AutoMarkdown
+export default MarkdownShortcuts
diff --git a/examples/markdown-shortcuts/state.json b/examples/markdown-shortcuts/state.json
new file mode 100644
index 000000000..83b5b286b
--- /dev/null
+++ b/examples/markdown-shortcuts/state.json
@@ -0,0 +1,54 @@
+{
+ "nodes": [
+ {
+ "kind": "block",
+ "type": "paragraph",
+ "nodes": [
+ {
+ "kind": "text",
+ "text": "The editor gives you full control over the logic you can add. For example, it's fairly common to want to add markdown-like shortcuts to editors. So that, when you start a line with \"> \" you get a blockquote that looks like this:"
+ }
+ ]
+ },
+ {
+ "kind": "block",
+ "type": "block-quote",
+ "nodes": [
+ {
+ "kind": "text",
+ "text": "A wise quote."
+ }
+ ]
+ },
+ {
+ "kind": "block",
+ "type": "paragraph",
+ "nodes": [
+ {
+ "kind": "text",
+ "text": "Order when you start a line with \"## \" you get a level-two heading, like this:"
+ }
+ ]
+ },
+ {
+ "kind": "block",
+ "type": "heading-two",
+ "nodes": [
+ {
+ "kind": "text",
+ "text": "Try it out!"
+ }
+ ]
+ },
+ {
+ "kind": "block",
+ "type": "paragraph",
+ "nodes": [
+ {
+ "kind": "text",
+ "text": "Try it out for yourself! Try starting a new line with \">\", \"-\", or \"#\"s."
+ }
+ ]
+ }
+ ]
+}
diff --git a/src/components/content.js b/src/components/content.js
index 4331370c7..105066329 100644
--- a/src/components/content.js
+++ b/src/components/content.js
@@ -9,6 +9,7 @@ import Selection from '../models/selection'
import getTransferData from '../utils/get-transfer-data'
import TYPES from '../constants/types'
import getWindow from 'get-window'
+import findDeepestNode from '../utils/find-deepest-node'
import keycode from 'keycode'
import { IS_FIREFOX, IS_MAC } from '../constants/environment'
@@ -135,8 +136,8 @@ class Content extends React.Component {
*/
updateSelection = () => {
- const { state } = this.props
- const { selection } = state
+ const { editor, state } = this.props
+ const { document, selection } = state
const el = ReactDOM.findDOMNode(this)
const window = getWindow(el)
const native = window.getSelection()
@@ -157,8 +158,11 @@ class Content extends React.Component {
// Otherwise, figure out which DOM nodes should be selected...
const { anchorText, focusText } = state
const { anchorKey, anchorOffset, focusKey, focusOffset } = selection
- const anchorRanges = anchorText.getRanges()
- const focusRanges = focusText.getRanges()
+ const schema = editor.getSchema()
+ const anchorDecorators = document.getDescendantDecorators(anchorKey, schema)
+ const focusDecorators = document.getDescendantDecorators(focusKey, schema)
+ const anchorRanges = anchorText.getRanges(anchorDecorators)
+ const focusRanges = focusText.getRanges(focusDecorators)
let a = 0
let f = 0
let anchorIndex
@@ -186,8 +190,8 @@ class Content extends React.Component {
const anchorSpan = el.querySelector(`[data-offset-key="${anchorKey}-${anchorIndex}"]`)
const focusSpan = el.querySelector(`[data-offset-key="${focusKey}-${focusIndex}"]`)
- const anchorEl = anchorSpan.firstChild
- const focusEl = focusSpan.firstChild
+ const anchorEl = findDeepestNode(anchorSpan)
+ const focusEl = findDeepestNode(focusSpan)
// If they are already selected, do nothing.
if (
diff --git a/src/components/leaf.js b/src/components/leaf.js
index 181a406ab..b99d29f1b 100644
--- a/src/components/leaf.js
+++ b/src/components/leaf.js
@@ -3,6 +3,7 @@ import Debug from 'debug'
import OffsetKey from '../utils/offset-key'
import React from 'react'
import ReactDOM from 'react-dom'
+import findDeepestNode from '../utils/find-deepest-node'
import { IS_FIREFOX } from '../constants/environment'
/**
@@ -190,19 +191,6 @@ class Leaf extends React.Component {
}
-/**
- * Find the deepest descendant of a DOM `element`.
- *
- * @param {Element} node
- * @return {Element}
- */
-
-function findDeepestNode(element) {
- return element.firstChild
- ? findDeepestNode(element.firstChild)
- : element
-}
-
/**
* Export.
*
diff --git a/src/utils/find-deepest-node.js b/src/utils/find-deepest-node.js
new file mode 100644
index 000000000..53e2a863b
--- /dev/null
+++ b/src/utils/find-deepest-node.js
@@ -0,0 +1,21 @@
+
+/**
+ * Find the deepest descendant of a DOM `element`.
+ *
+ * @param {Element} node
+ * @return {Element}
+ */
+
+function findDeepestNode(element) {
+ return element.firstChild
+ ? findDeepestNode(element.firstChild)
+ : element
+}
+
+/**
+ * Export.
+ *
+ * @type {Function}
+ */
+
+export default findDeepestNode