1
0
mirror of https://github.com/ianstormtaylor/slate.git synced 2025-08-28 09:29:49 +02:00

add markdown example, fix selection handling with decorators

This commit is contained in:
Ian Storm Taylor
2017-03-30 14:12:56 -04:00
parent 1ec92f2414
commit 2743348d65
11 changed files with 262 additions and 28 deletions

View File

@@ -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

View File

@@ -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 {
<div className="tabs">
{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')}
</div>
)
}
@@ -154,7 +156,6 @@ const router = (
<Route path="/" component={App}>
<IndexRedirect to="rich-text" />
<Route path="auto-markdown" component={AutoMarkdown} />
<Route path="check-lists" component={CheckLists} />
<Route path="code-highlighting" component={CodeHighlighting} />
<Route path="embeds" component={Embeds} />
@@ -165,6 +166,8 @@ const router = (
<Route path="images" component={Images} />
<Route path="large" component={LargeDocument} />
<Route path="links" component={Links} />
<Route path="markdown-preview" component={MarkdownPreview} />
<Route path="markdown-shortcuts" component={MarkdownShortcuts} />
<Route path="paste-html" component={PasteHtml} />
<Route path="plain-text" component={PlainText} />
<Route path="plugins" component={Plugins} />

View File

@@ -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 (
<div className="editor">
<Editor
schema={schema}
state={this.state.state}
onChange={this.onChange}
/>
</div>
)
}
/**
* On change.
*
* @param {State} state
*/
onChange = (state) => {
this.setState({ state })
}
}
/**
* Export.
*/
export default MarkdownPreview

View File

@@ -0,0 +1,8 @@
# Auto-markdown Example
![](../../docs/images/auto-markdown-example.png)
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!

View File

@@ -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

View File

@@ -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."
}
]
}
]
}

View File

@@ -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 (

View File

@@ -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.
*

View File

@@ -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