• React富文本编辑器开发(十二)插件


    插件

    您已经看到了如何覆盖 Slate 编辑器的行为。这些覆盖也可以打包成 “插件”,以便重用、测试和共享。这是 Slate 架构中最强大的方面之一。

    插件简单地是一个接受 Editor 对象并在某种方式上增强它后返回它的函数。

    例如,一个将图像节点标记为 “void” 的插件:

    const withImages = editor => {
      const { isVoid } = editor
    
      editor.isVoid = element => {
        return element.type === 'image' ? true : isVoid(element)
      }
    
      return editor
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    然后要使用插件,简单地:

    import { createEditor } from 'slate'
    
    const editor = withImages(createEditor())
    
    
    • 1
    • 2
    • 3
    • 4

    这种插件组合模型使得 Slate 极易扩展!

    辅助函数

    除了插件函数之外,您可能还想公开与您的插件一起使用的辅助函数。例如:

    import { Editor, Element } from 'slate'
    
    const MyEditor = {
      ...Editor,
      insertImage(editor, url) {
        const element = { type: 'image', url, children: [{ text: '' }] }
        Transforms.insertNodes(editor, element)
      },
    }
    
    const MyElement = {
      ...Element,
      isImageElement(value) {
        return Element.isElement(element) && element.type === 'image'
      },
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    然后您可以在任何地方使用 MyEditor 和 MyElement,并在一个地方访问到所有的辅助函数。

    渲染

    Slate 最棒的部分之一是它构建在 React 上,因此它可以完美地适应您现有的应用程序。它不会重新发明自己的视图层,您不必学习新的东西。它尽可能地保持与 React 的一致性。

    为此,Slate 允许您控制自定义节点和属性在您的富文本领域中的渲染行为。

    您可以通过向顶级 组件传递 "render props" 来定义这些行为。

    例如,如果您想要渲染自定义元素组件,您可以传递 renderElement prop

    import { createEditor } from 'slate'
    import { Slate, Editable, withReact } from 'slate-react'
    
    const MyEditor = () => {
      const [editor] = useState(() => withReact(createEditor()))
      const renderElement = useCallback(({ attributes, children, element }) => {
        switch (element.type) {
          case 'quote':
            return <blockquote {...attributes}>{children}</blockquote>
          case 'link':
            return (
              <a {...attributes} href={element.url}>
                {children}
              </a>
            )
          default:
            return <p {...attributes}>{children}</p>
        }
      }, [])
    
      return (
        <Slate editor={editor}>
          <Editable renderElement={renderElement} />
        </Slate>
      )
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    请确保在自定义组件中混合使用 props.attributesrender props.children!这些 attributes 必须添加到组件内部的顶级 DOM 元素中,因为它们是 SlateDOM 帮助函数所必需的。而 children 则是文本内容和内联元素所持有的 "leaves"

    您不必使用简单的 HTML 元素,您也可以使用自己的自定义 React 组件:

    const renderElement = useCallback(props => {
      switch (props.element.type) {
        case 'quote':
          return <QuoteElement {...props} />
        case 'link':
          return <LinkElement {...props} />
        default:
          return <DefaultElement {...props} />
      }
    }, [])
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    叶子

    当渲染文本级别的格式时,字符被分组为每个具有相同格式(标记)的文本 "leaves"

    要自定义每个叶子的渲染,您可以使用自定义 renderLeaf prop

    const renderLeaf = useCallback(({ attributes, children, leaf }) => {
      return (
        <span
          {...attributes}
          style={{
            fontWeight: leaf.bold ? 'bold' : 'normal',
            fontStyle: leaf.italic ? 'italic' : 'normal',
          }}
        >
          {children}
        </span>
      )
    }, [])
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    请注意,我们处理它的方式与 renderElement 稍有不同。由于文本格式化通常相对简单,我们选择放弃 switch 语句,而只是切换一些样式开关。(但是,如果您愿意,您也可以使用自定义组件!)

    与 Element 渲染器一样,确保在叶子渲染器中混合使用 props.attributesrender props.children!这些 attributes 必须添加到组件内部的顶级 DOM 元素中,因为它们是 SlateDOM 帮助函数所必需的。而 children 则是 Slate 为您自动管理的文档的实际文本内容。

    文本级别的格式化的一个缺点是您不能保证任何给定格式是 “连续的” —— 也就是说它会作为一个单独的叶子保留。这与叶子相关的限制类似于 DOM,其中这是无效的:

    <em>t<strong>eem>xstrong>t
    
    • 1

    上面示例中的元素未正确关闭,因此无效。相反,您应该按以下方式编写上面的 HTML:

    <em>tem><strong><em>eem>xstrong>t
    
    • 1

    如果您还添加了另一个重叠的 部分到该文本中,您可能不得不再次调整闭合标签。在 Slate 中渲染叶子是类似的——您不能保证即使一个单词具有一种格式,该叶子也是连续的,因为它取决于它与其他格式的重叠方式。

    当然,这个叶子的东西听起来很复杂。但是,只要您将文本级别的格式化和元素级别的格式化用于其预期目的,就不必过多考虑它:

    • 文本属性用于非连续的、字符级别的格式化。
    • 元素属性用于文档中连续的、语义化的元素。

    装饰

    装饰是另一种文本级别的格式化。它们与普通的自定义属性类似,只是每个装饰应用于文档的一个 Range 范围,而不是与给定文本节点关联。

    然而,装饰是在渲染时基于内容本身计算的。这对于动态格式化(如语法高亮或搜索关键字)非常有帮助,因为内容的更改(或一些外部数据)可能会改变格式化。

    装饰与 Marks 不同之处在于它们不存储在编辑器状态中。

    工具栏、菜单、覆盖等等!

    除了控制 Slate 内部节点的渲染之外,您还可以使用 useSlate hook 从其他组件中检索当前编辑器上下文。

    这样,其他组件就可以执行命令、查询编辑器状态或执行其他任何操作。

    一个常见的用例是渲染一个工具栏,其中的格式按钮基于当前选择而高亮:

    const MyEditor = () => {
      const [editor] = useState(() => withReact(createEditor()))
      return (
        <Slate editor={editor}>
          <Toolbar />
          <Editable />
        </Slate>
      )
    }
    
    const Toolbar = () => {
      const editor = useSlate()
      return (
        <div>
          <Button active={isBoldActive(editor)}>B</Button>
          <Button active={isItalicActive(editor)}>I</Button>
        </div>
      )
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    因为 使用 useSlate hook 检索上下文,所以当编辑器更改时它会重新渲染,这样按钮的活动状态就保持同步。

    编辑器样式

    可以通过在 组件上使用 style prop 来为编辑器自定义样式。

    const MyEditor = () => {
      const [editor] = useState(() => withReact(createEditor()))
      return (
        <Slate editor={editor}>
          <Editable style={{ minHeight: '200px', backgroundColor: 'lime' }} />
        </Slate>
      )
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    也可以使用样式表和 className 来应用自定义样式。但是,Slate 使用内联样式为编辑器提供了一些默认样式。由于内联样式优先于样式表,您使用样式表提供的样式将不会覆盖默认样式。如果您尝试使用样式表,但规则没有生效,请执行以下操作之一:

    使用 style prop 而不是样式表来提供您的样式,这样会覆盖默认的内联样式。
    disableDefaultStyles prop 传递给 组件。
    在样式表声明中使用 !important,使其覆盖内联样式。

  • 相关阅读:
    GPU释放显存
    英国消费“避之不及”,东南亚“爱不释手”,TikTok Shop为何?
    电商项目项目讲解【杭州多测师】【杭州多测师_王sir】
    什么是yandex.metrica 目标?
    Python编程学习:random.shuffle的简介、使用方法之详细攻略
    C/C++字符函数和字符串函数详解————长度受限制的字符串函数
    单⽬相机成像过程_看这一篇就够了
    c# 字符串转化成语音合成,System.Speech
    第九周实验记录
    《数据结构C++版》实验三:二叉树实验
  • 原文地址:https://blog.csdn.net/zxmatline/article/details/136519037