mirror of
https://github.com/ianstormtaylor/slate.git
synced 2025-08-12 02:03:59 +02:00
more work on richtext
This commit is contained in:
@@ -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;
|
||||||
|
}
|
||||||
|
@@ -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>
|
||||||
|
@@ -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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -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({
|
||||||
|
@@ -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)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@@ -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())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user