Commit 2b5b2e40 authored by Maxime Chaillet's avatar Maxime Chaillet

enrich editor with additional features.

parent 7596dfe6
......@@ -28,6 +28,9 @@
<link rel="stylesheet" href="https://npmcdn.com/react-bootstrap-table/dist/react-bootstrap-table.min.css">
<!-- icons for the editor-->
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
<title>ESRF Portal</title>
</head>
<body>
......
This diff is collapsed.
import React from 'react';
//bootstrap
import { DropdownButton, MenuItem } from 'react-bootstrap';
import { Editor, getEventRange, getEventTransfer } from 'slate-react'
import { Block, Value } from 'slate'
import EditTable from 'slate-edit-table'
import Plain from 'slate-plain-serializer'
import imageExtensions from 'image-extensions'
import { LAST_CHILD_TYPE_INVALID } from 'slate-schema-violations'
import isUrl from 'is-url'
import propType from 'prop-types'
import { initialValue } from './initialValue'
import { tablePlugin } from './Plugins/table.jsx';
import { imagePlugin } from './Plugins/image.jsx';
import { marksHelper } from './marks';
//import Html from 'slate-html-serializer';
//import { rules } from './htmlSerializerRules'
// Create a new serializer instance with our `rules` from above.
//const html = new Html({ rules })
const tablePluginInstance = EditTable({
typeTable: 'table',
typeRow: 'table_row',
typeCell: 'table_cell',
typeContent: 'paragraph',
})
const plugins = [
tablePluginInstance,
tablePlugin(),
imagePlugin(),
marksHelper(),
]
/**
* A change function to standardize inserting images.
*
* @param {Change} change
* @param {String} src
* @param {Range} target
*/
function insertImage(change, src, target) {
if (target) {
change.select(target)
}
change.insertBlock({
type: 'image',
isVoid: true,
data: { src },
})
}
function isImage(url) {
return !!imageExtensions.find(url.endsWith)
}
/**
* A schema to enforce that there's always a paragraph as the last block.
*
* @type {Object}
*/
const schema = {
document: {
last: { types: ['paragraph'] },
normalize: (change, reason, { node, child }) => {
switch (reason) {
case LAST_CHILD_TYPE_INVALID: {
const paragraph = Block.create('paragraph')
return change.insertNodeByKey(node.key, node.nodes.size, paragraph)
}
}
},
},
}
/**
* The class representing my editor.
* @prop {}
*
*/
class MyEditor extends React.Component {
constructor(props) {
super(props);
this.state = {
value: initialValue
}
this.renderEditor = this.renderEditor.bind(this);
this.renderToolbar = this.renderToolbar.bind(this);
this.onChange = this.onChange.bind(this);
}
renderToolbar = () => {
if (this.props.formattedText == undefined) {
return (
<div className="menu toolbar-menu" style={{ height: '30px', backgroundColor: '#EEEEEE' }}>
<span onMouseDown={this.onClickBoldText}>
<span className="glyphicon glyphicon-bold" ></span>
</span>
<span> &nbsp; </span>
<span onMouseDown={this.onClickItalicText}>
<span className="glyphicon glyphicon-italic" ></span>
</span>
<span> &nbsp; | &nbsp; </span>
<span onMouseDown={this.onClickImage}>
<span className="glyphicon glyphicon-picture" ></span>
</span>
<span> &nbsp; | &nbsp; </span>
<DropdownButton
bsStyle={'default'}
bsSize="xsmall"
title={'Table'}
key={1}
id={`dropdown-basic-${1}`}
>
<MenuItem eventKey="1" onMouseDown={this.onClickNewTable}>New</MenuItem>
<MenuItem eventKey="2" onMouseDown={this.onClickDeleteTable} >Delete</MenuItem>
</DropdownButton>
</div >
)
}
}
renderEditor = () => {
return (
<div style={{ height: '80%' }}>
<Editor
plugins={plugins}
placeholder="Enter some text..."
value={this.state.value}
schema={schema}
onChange={this.onChange}
onDrop={this.onDropOrPaste}
onPaste={this.onDropOrPaste}
style={{ overflow: 'auto', height: '100%' }}
readOnly={(this.props.formattedText == undefined) ? false : true}
/>
</div>
)
}
onChange = ({ value }) => {
//localStorage.setItem("content", JSON.stringify(value));
// When the document changes, save the serialized HTML to Local Storage.
//if (value.document != this.state.value.document) {
// desactivate serialization because table support not written yet
// const htmlContent = html.serialize(value)
// localStorage.setItem('formattedHTML', htmlContent)
const plainContent = Plain.serialize(value)
localStorage.setItem('plainText', plainContent)
//Store the text in slatejs format
localStorage.setItem('formattedText', JSON.stringify(value))
if (this.props.formattedText == undefined) {
// a new event is created with slate
this.setState({ value })
} else {
// slate is used to display events
if (this.props.formattedText !== JSON.stringify(value.toJSON())) {
this.setState({ value: Value.fromJSON(JSON.parse(this.props.formattedText)) })
}
}
}
onClickImage = event => {
event.preventDefault()
const src = window.prompt('Enter the URL of the image:')
if (!src) return
const change = this.state.value.change().call(insertImage, src)
this.onChange(change)
}
onClickNewTable = event => {
event.preventDefault()
const change = this.state.value.change().call(tablePluginInstance.changes.insertTable, 2, 2)
this.onChange(change);
}
onClickDeleteTable = event => {
event.preventDefault()
const change = this.state.value.change().call(tablePluginInstance.changes.removeTable)
this.onChange(change);
}
onClickBoldText = event => {
debugger
const change = this.state.value.change().toggleMark('bold');
this.onChange(change);
}
onClickItalicText = event => {
debugger
const change = this.state.value.change().toggleMark('italic');
this.onChange(change);
}
/**
* On drop, insert the image wherever it is dropped.
*
* @param {Event} event
* @param {Change} change
* @param {Editor} editor
*/
onDropOrPaste = (event, change, editor) => {
const target = getEventRange(event, change.value)
if (!target && event.type === 'drop') return
const transfer = getEventTransfer(event)
const { type, text, files } = transfer
if (type === 'files') {
for (const file of files) {
const reader = new FileReader()
const [mime] = file.type.split('/')
if (mime != 'image') continue
reader.addEventListener('load', () => {
editor.change(c => {
c.call(insertImage, reader.result, target)
})
})
reader.readAsDataURL(file)
}
}
if (type === 'text') {
if (!isUrl(text)) return
if (!isImage(text)) return
change.call(insertImage, text, target)
}
}
render() {
if (this.props.formattedText == undefined) {
return (
<div style={{ height: '100%', border: "1px solid", borderColor: "#EEEEEE" }}>
{this.renderToolbar()}
{this.renderEditor()}
</div>)
} else {
return (
<div style={{ height: '100%' }}>
{this.renderToolbar()}
{this.renderEditor()}
</div>)
}
}
}
MyEditor.propType = {
/** formattedText optionnaly pased to the editor. When this prop is passed, the editor is in readonly mode and the editor menu is not visible */
formattedText: propType.string,
}
export default MyEditor;
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment