SlateJS 编辑器:保存和显示内容

SlateJS Editor: Saving and displaying Content

以下代码段是我的 SlateJS 实现。

在应用中某个文章的详细信息页面上,我想嵌入此编辑器(表情符号)组件,向它传递与该文章 ID 相关的相应值道具。不幸的是,现在 Emoji 组件的设置方式,我无法传入价值道具。表情符号编辑器将始终考虑 Value.json 或现有的 localStorage 值。

如何在 Emoji 组件中注入值?

旁注: 其他可能的实现是什么?实现渲染功能而不是利用表情符号(编辑器)组件是否更有意义?

import React, { Component } from 'react'
import styled from 'styled-components'
import { Editor } from 'slate-react'
import { Value } from 'slate'
import initialValue from '../../components/utilities/Value.json'

const Toolbar = styled.div`
  display: flex;
  justify-content: center;
`

const Icon = styled.span`
  cursor: pointer;
`

const existingValue = JSON.parse(localStorage.getItem('content'))
const currentValue = Value.fromJSON(
  existingValue || initialValue)

function MarkHotkey(options) {
  const { type, key } = options
  return {
    onKeyDown(event, change) {
      if (!event.ctrlKey || event.key !== key) return

      event.preventDefault()
      change.toggleMark(type)
      return true
    },
  }
}

const plugins = [
  MarkHotkey({ key: 'b', type: 'bold' }),
  MarkHotkey({ key: '`', type: 'code' }),
  MarkHotkey({ key: 'i', type: 'italic' }),
  MarkHotkey({ key: '~', type: 'strikethrough' }),
  MarkHotkey({ key: 'u', type: 'underline' }),
  MarkHotkey({ key: 'q', type: 'blockquote' }),
]

const EMOJIS = [
  '',
  '',
  '',
  '',
  '',
  '',
  '',
  '',
  '',
  '',
  '',
  '',
  '',
  '',
  '',
  '',
  '',
  '',
]

const noop = e => e.preventDefault()

class Emoji extends Component {
  state = {
    value: currentValue,
  }

  onChange = ({ value }) => {
    if (value.document !== this.state.value.document) {
      const content = JSON.stringify(value.toJSON())
      localStorage.setItem('content', content)
    }
    this.setState({ value })
  }

  // When clicking an emoji, insert it
  onClickEmoji = (e, code) => {
    e.preventDefault()
    const { value } = this.state
    const change = value.change()

    change
      .insertInline({
        type: 'emoji',
        isVoid: true,
        data: { code },
      })
      .collapseToStartOfNextText()
      .focus()

    this.onChange(change)
  }

  renderToolbar = () => {
    return (
      <Toolbar>
        {EMOJIS.map((emoji, i) => {
          const onMouseDown = e => this.onClickEmoji(e, emoji)
          return (
            // eslint-disable-next-line react/jsx-no-bind
            <Icon key={i} onMouseDown={onMouseDown}>
              <span>{emoji}</span>
            </Icon>
          )
        })}
      </Toolbar>
    )
  }

  renderEditor = () => {
    return (
        <Editor
          placeholder="What do you want to say..? "
          plugins={plugins}
          value={this.state.value}
          onChange={this.onChange}
          renderNode={this.renderNode}
          renderMark={this.renderMark}
        />
    )
  }

  // Add a `renderNode` method to render nodes.
  renderNode = props => {
    const { attributes, children, node, isSelected } = props
    switch (node.type) {
      case 'heading':
        return <h1 {...attributes}>{children}</h1>

      case 'paragraph': 
        return <p {...attributes}>{children}</p>

      case 'emoji':
        const { data } = node
        const code = data.get('code')
        return (
          <span
            className={`emoji ${isSelected ? 'selected' : ''}`}
            {...props.attributes}
            contentEditable={false}
            onDrop={noop}
          >
            {code}
          </span>
        )

      default:
        return
    }
  }

  // Add a `renderMark` method to render marks.
  renderMark = props => {
    const { mark, children } = props
    switch (mark.type) {
      case 'bold':
        return <strong>{children}</strong>
      case 'code':
        return <code>{children}</code>
      case 'italic':
        return <em>{children}</em>
      case 'strikethrough':
        return <del>{children}</del>
      case 'underline':
        return <u>{children}</u>
      case 'blockquote':
        return <blockquote>{children}</blockquote>
      default:
        return
    }
  }

  render = () => {
    return (
      <div>
        {this.renderEditor()}
        {this.renderToolbar()}
      </div>
    )
  }
}

export default Emoji

重写我的渲染函数以首先检查 Emoji 组件是否收到 JSON 属性。

renderEditor = () => {
    if (this.props.data) {

      // parse the JSON received via props (from blog's detail page)
      // && deserialize it
      const data = JSON.parse(this.props.data)
      const d = Value.fromJSON(data)
      return (
        <Editor
          value={d}
          renderNode={this.renderNode}
          renderMark={this.renderMark}
        />
      )
    } else {
      return (
        <div>
          <Editor
            placeholder="What do you want to say..? "
            plugins={plugins}
            value={this.state.value}
            onChange={this.onChange}
            renderNode={this.renderNode}
            renderMark={this.renderMark}
          />
          {this.renderToolbar}
        </div>
      )
    }
  }

感谢改进意见。