Skip to content

Commit 32135bc

Browse files
authored
Add Floater component for wrapping a toolbar (#16)
* Add Floater component * Only show the menu when the selection is non-empty * Pass through all the attributes * Pass the whole doc to onChange, only if it's changed * Increment the version number
1 parent a6f9105 commit 32135bc

File tree

15 files changed

+3211
-1282
lines changed

15 files changed

+3211
-1282
lines changed

react-prosemirror-config-default/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,8 @@
4343
"webpack-node-externals": "^1.6.0"
4444
},
4545
"peerDependencies": {
46-
"react": "^16.2.0",
47-
"react-dom": "^16.2.0"
46+
"react": "^16.3.0",
47+
"react-dom": "^16.3.0"
4848
},
4949
"scripts": {
5050
"prepublishOnly": "npm run build",

react-prosemirror-example/package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@aeaton/react-prosemirror-example",
3-
"version": "0.2.0",
3+
"version": "0.3.0",
44
"private": true,
55
"description": "An app with a React ProseMirror component",
66
"repository": "",
@@ -11,8 +11,8 @@
1111
"dependencies": {
1212
"@aeaton/react-prosemirror": "^0.11.0",
1313
"@aeaton/react-prosemirror-config-default": "^0.9.0",
14-
"react": "^16.2.0",
15-
"react-dom": "^16.2.0",
14+
"react": "^16.3.0",
15+
"react-dom": "^16.3.0",
1616
"react-scripts": "^1.1.0",
1717
"styled-components": "^3.1.0"
1818
},

react-prosemirror-example/src/index.js

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import React from 'react'
22
import ReactDOM from 'react-dom'
33
import styled from 'styled-components'
4-
import { Editor, MenuBar } from '@aeaton/react-prosemirror'
4+
import { Editor, Floater, MenuBar } from '@aeaton/react-prosemirror'
55
import { options, menu } from '@aeaton/react-prosemirror-config-default'
66

77
const Container = styled('div')`
@@ -33,12 +33,17 @@ ReactDOM.render(
3333
<Editor
3434
options={options}
3535
value={`<h1>This is a title</h1><p>This is a paragraph</p>`}
36-
onChange={value => {
37-
document.getElementById('output').textContent = JSON.stringify(value, null, 2)
36+
onChange={doc => {
37+
document.getElementById('output').textContent = JSON.stringify(doc, null, 2)
3838
}}
39-
render={({ editor, state, dispatch }) => (
39+
render={({ editor, view }) => (
4040
<React.Fragment>
41-
<MenuBar menu={menu} state={state} dispatch={dispatch} />
41+
<MenuBar menu={menu} view={view} />
42+
43+
<Floater view={view}>
44+
<MenuBar menu={{ marks: menu.marks }} view={view} />
45+
</Floater>
46+
4247
{editor}
4348
</React.Fragment>
4449
)}

react-prosemirror/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,9 @@ const CustomEditor = ({ value, onChange }) => (
4242
options={options}
4343
value={value}
4444
onChange={onChange}
45-
render={({ editor, state, dispatch }) => (
45+
render={({ editor, view }) => (
4646
<div>
47-
<MenuBar menu={menu} state={state} dispatch={dispatch}/>
47+
<MenuBar menu={menu} view={view} />
4848
{editor}
4949
</div>
5050
)}

react-prosemirror/package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@aeaton/react-prosemirror",
3-
"version": "0.11.0",
3+
"version": "0.20.0",
44
"description": "A React component for ProseMirror",
55
"main": "dist",
66
"files": [
@@ -33,8 +33,8 @@
3333
"webpack-node-externals": "^1.6.0"
3434
},
3535
"peerDependencies": {
36-
"react": "^16.2.0",
37-
"react-dom": "^16.2.0"
36+
"react": "^16.3.0",
37+
"react-dom": "^16.3.0"
3838
},
3939
"scripts": {
4040
"prepublishOnly": "npm run build",

react-prosemirror/src/Editor.js

Lines changed: 23 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -8,41 +8,39 @@ class Editor extends React.Component {
88
constructor (props) {
99
super(props)
1010

11-
this.state = {
12-
state: EditorState.create(props.options)
13-
}
14-
}
11+
this.editorRef = React.createRef()
12+
13+
this.view = new EditorView(null, {
14+
state: EditorState.create(props.options),
15+
dispatchTransaction: transaction => {
16+
const { state, transactions } = this.view.state.applyTransaction(transaction)
17+
18+
this.view.updateState(state)
1519

16-
createEditorView = node => {
17-
if (!this.view) {
18-
this.view = new EditorView(node, {
19-
state: this.state.state,
20-
dispatchTransaction: this.dispatchTransaction,
21-
attributes: {
22-
placeholder: this.props.placeholder
20+
if (transactions.some(tr => tr.docChanged)) {
21+
this.props.onChange(state.doc)
2322
}
24-
})
2523

26-
if (this.props.autoFocus) {
27-
this.view.focus()
28-
}
29-
}
24+
this.forceUpdate()
25+
},
26+
attributes: this.props.attributes
27+
})
3028
}
3129

32-
dispatchTransaction = transaction => {
33-
const state = this.view.state.apply(transaction)
34-
this.view.updateState(state)
35-
this.setState({ state })
36-
this.props.onChange(state.doc.content)
30+
componentDidMount () {
31+
this.editorRef.current.appendChild(this.view.dom)
32+
33+
if (this.props.autoFocus) {
34+
this.view.focus()
35+
}
3736
}
3837

3938
render () {
40-
const editor = <div ref={this.createEditorView} />
39+
const editor = <div ref={this.editorRef} />
4140

4241
return this.props.render ? this.props.render({
43-
state: this.state.state,
44-
dispatch: this.dispatchTransaction,
45-
editor
42+
editor,
43+
view: this.view
4644
}) : editor
4745
}
4846
}

react-prosemirror/src/Editor.md

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,11 @@ const editorStyle = {
1818

1919
<Editor
2020
options={options}
21-
placeholder="Enter some text…"
2221
autoFocus
2322
onChange={doc => setState({ doc })}
24-
render={({ editor, state, dispatch }) => (
23+
render={({ editor, view }) => (
2524
<div style={editorStyle}>
26-
<MenuBar menu={menu} state={state} dispatch={dispatch}/>
25+
<MenuBar menu={menu} view={view} />
2726
{editor}
2827
</div>
2928
)}

react-prosemirror/src/Floater.js

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import React from 'react'
2+
import classes from './Floater.module.css'
3+
4+
class Floater extends React.Component {
5+
constructor (props) {
6+
super(props)
7+
8+
this.state = {
9+
style: {
10+
left: 0,
11+
top: 0
12+
}
13+
}
14+
15+
this.menuRef = React.createRef()
16+
}
17+
18+
componentDidMount () {
19+
this.setState({
20+
style: this.calculateStyle(this.props)
21+
})
22+
}
23+
24+
componentWillReceiveProps (nextProps) {
25+
this.setState({
26+
style: this.calculateStyle(nextProps)
27+
})
28+
}
29+
30+
render () {
31+
return (
32+
<div ref={this.menuRef} className={classes.floater} style={this.state.style}>
33+
{this.props.children}
34+
</div>
35+
)
36+
}
37+
38+
calculateStyle (props) {
39+
const { view } = props
40+
41+
const { selection } = view.state
42+
43+
if (!selection || selection.empty) {
44+
return {
45+
left: -1000,
46+
top: 0
47+
}
48+
}
49+
50+
const coords = view.coordsAtPos(selection.$anchor.pos)
51+
52+
const { offsetWidth } = this.menuRef.current
53+
54+
return {
55+
left: window.innerWidth - offsetWidth < coords.left ? coords.left - offsetWidth + 20 : coords.left,
56+
top: coords.top - 40 > 0 ? coords.top - 40 : coords.top + 20
57+
}
58+
}
59+
}
60+
61+
export default Floater
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
.floater {
2+
position: absolute;
3+
z-index: 10;
4+
border: 3px solid #151616;
5+
border-radius: 3px;
6+
white-space: nowrap;
7+
margin: 0;
8+
display: flex;
9+
}
10+
11+
.floater :last-child {
12+
margin: 0;
13+
}

react-prosemirror/src/HtmlEditor.js

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,18 @@ const parser = schema => {
1010
return content => {
1111
const container = document.createElement('article')
1212
container.innerHTML = content
13+
1314
return parser.parse(container)
1415
}
1516
}
1617

1718
const serializer = schema => {
1819
const serializer = DOMSerializer.fromSchema(schema)
1920

20-
return content => {
21+
return doc => {
2122
const container = document.createElement('article')
22-
container.appendChild(serializer.serializeFragment(content))
23+
container.appendChild(serializer.serializeFragment(doc.content))
24+
2325
return container.innerHTML
2426
}
2527
}
@@ -34,19 +36,19 @@ class HtmlEditor extends React.Component {
3436

3537
options.doc = parse(value)
3638

37-
this.onChange = debounce(value => {
38-
onChange(serialize(value))
39+
this.onChange = debounce(doc => {
40+
onChange(serialize(doc))
3941
}, 1000, { maxWait: 5000 })
4042
}
4143

4244
render () {
43-
const { autoFocus, options, placeholder, render } = this.props
45+
const { autoFocus, options, attributes, render } = this.props
4446

4547
return (
4648
<Editor
4749
autoFocus={autoFocus}
4850
options={options}
49-
placeholder={placeholder}
51+
attributes={attributes}
5052
render={render}
5153
onChange={this.onChange}
5254
/>

0 commit comments

Comments
 (0)