1
0
mirror of https://github.com/ianstormtaylor/slate.git synced 2025-08-12 18:24:03 +02:00

more work on richtext

This commit is contained in:
Ian Storm Taylor
2016-06-19 00:41:36 -07:00
parent 7435c4019c
commit e293c9794c
6 changed files with 128 additions and 31 deletions

View File

@@ -15,6 +15,34 @@ p {
margin: 0; margin: 0;
} }
p + p { .editor p + p {
margin-top: 1em; margin-top: 1em;
} }
.menu {
margin: 0 -10px;
padding: 1px 0 9px 7px;
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;
}

View File

@@ -2,6 +2,7 @@
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<title>Editor | Richtext Example</title> <title>Editor | Richtext Example</title>
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
<link rel="stylesheet" href="index.css"> <link rel="stylesheet" href="index.css">
</head> </head>
<body> <body>

View File

@@ -17,18 +17,7 @@ const state = {
type: 'text', type: 'text',
ranges: [ ranges: [
{ {
text: 'This is ' text: 'This is editable '
},
{
text: 'editable',
marks: [
{
type: 'italic'
}
]
},
{
text: ' '
}, },
{ {
text: 'rich', text: 'rich',
@@ -39,7 +28,18 @@ const state = {
] ]
}, },
{ {
text: ' text, much better than a ' text: ' text, '
},
{
text: 'much',
marks: [
{
type: 'italic'
}
]
},
{
text: ' better than a '
}, },
{ {
text: '<textarea>', text: '<textarea>',
@@ -69,18 +69,77 @@ class App extends React.Component {
state: Raw.deserialize(state) state: Raw.deserialize(state)
}; };
isMarkActive(type) {
const { state } = this.state
const { document, selection } = state
const { startKey, startOffset } = selection
const startNode = document.getNode(startKey)
if (!startNode) return false
const { characters } = startNode
const character = characters.get(startOffset)
const { marks } = character
return marks.some(mark => mark.type == type)
}
onClickMark(e, type) {
e.preventDefault()
let { state } = this.state
const { marks } = state
const isActive = this.isMarkActive(type)
state = state
.transform()
[isActive ? 'unmark' : 'mark']()
.apply()
this.onChange(state)
}
render() { render() {
return ( return (
<Editor <div>
state={this.state.state} {this.renderToolbar()}
renderNode={node => this.renderNode(node)} {this.renderEditor()}
renderMark={mark => this.renderMark(mark)} </div>
onChange={(state) => { )
console.log('Document:', state.document.toJS()) }
console.log('Content:', Raw.serialize(state))
this.setState({ state }) renderToolbar() {
}} const isBold = this.isMarkActive('bold')
/> const isItalic = this.isMarkActive('italic')
const isCode = this.isMarkActive('code')
return (
<div className="menu">
<span className="button" onClick={e => this.onClickMark(e, 'bold')} data-active={isBold}>
<span className="material-icons">format_bold</span>
</span>
<span className="button" onClick={e => this.onClickMark(e, 'italic')} data-active={isItalic}>
<span className="material-icons">format_italic</span>
</span>
<span className="button" onClick={e => this.onClickMark(e, 'code')} data-active={isCode}>
<span className="material-icons">code</span>
</span>
</div>
)
}
renderEditor() {
return (
<div className="editor">
<Editor
state={this.state.state}
renderNode={node => this.renderNode(node)}
renderMark={mark => this.renderMark(mark)}
onChange={(state) => {
console.log('Document:', state.document.toJS())
console.log('Content:', Raw.serialize(state))
this.setState({ state })
}}
/>
</div>
) )
} }

View File

@@ -39,7 +39,7 @@ class Text extends React.Component {
: ranges.map((range, i, ranges) => { : ranges.map((range, i, ranges) => {
const previous = ranges.slice(0, i) const previous = ranges.slice(0, i)
const offset = previous.size const offset = previous.size
? previous.map(range => range.get('text')).join('').length ? previous.map(range => range.text).join('').length
: 0 : 0
return this.renderLeaf(range, offset) return this.renderLeaf(range, offset)
}) })
@@ -47,8 +47,8 @@ class Text extends React.Component {
renderLeaf(range, offset) { renderLeaf(range, offset) {
const { node, renderMark, state } = this.props const { node, renderMark, state } = this.props
const text = range.get('text') const text = range.text
const marks = range.get('marks') const marks = range.marks
const start = offset const start = offset
const end = offset + text.length const end = offset + text.length
const offsetKey = OffsetKey.stringify({ const offsetKey = OffsetKey.stringify({

View File

@@ -64,7 +64,7 @@ function serializeCharacters(characters) {
.map((range) => { .map((range) => {
return { return {
text: range.text, text: range.text,
mark: serializeMark(range.mark) marks: range.marks.map(serializeMark)
} }
}) })
} }

View File

@@ -1,5 +1,14 @@
import { List, Map } from 'immutable' import { List, Map, Record } from 'immutable'
/**
* Range.
*/
const Range = new Record({
text: '',
marks: new List()
})
/** /**
* Group a list of `characters` into ranges by the marks they have. * Group a list of `characters` into ranges by the marks they have.
@@ -16,7 +25,7 @@ function groupByMarks(characters) {
// The first one can always just be created. // The first one can always just be created.
if (i == 0) { if (i == 0) {
return ranges.push(new Map({ text, marks })) return ranges.push(new Range({ text, marks }))
} }
// Otherwise, compare to the previous and see if a new range should be // Otherwise, compare to the previous and see if a new range should be
@@ -37,7 +46,7 @@ function groupByMarks(characters) {
} }
// Otherwise, create a new range. // Otherwise, create a new range.
return ranges.push(new Map({ text, marks })) return ranges.push(new Range({ text, marks }))
}, new List()) }, new List())
} }