1

I'm building a rich text editor using React and Slate.js, and I'm trying to implement alignment functionality. However, the alignment buttons I've created don't seem to be working, and I'm not sure why. The console.log statements I've added show that the alignment functions are being called, but the text doesn't actually change alignment. Can anyone help me diagnose this issue and get the alignment buttons working correctly?

It's been a week , I'm really frustrated.

Sonu Hansda
  • 109
  • 1
  • 2
  • 6
  • You need to provide some code to get an answer. What have you tried so far. Sounds like a useEffect hook may be a good starting point for you if you have not tried already. – Jeremy Feb 24 '23 at 10:17
  • Thank you for replying . But I figured it out , I have commented my working code . If you can suggest me better version of it , Please feel free to comment. Again thank you for reply❤️❤️ – Sonu Hansda Feb 25 '23 at 03:44

1 Answers1

1
import React, { useMemo, useCallback } from 'react';
import { createEditor, Editor, Transforms , Element as SlateElement } from 'slate';
import { Slate, Editable, withReact, useSlate } from 'slate-react';
import {MdFormatBold,MdFormatItalic,MdFormatUnderlined,MdFormatListBulleted ,MdFormatAlignLeft,MdFormatAlignCenter,MdFormatAlignRight,MdFormatAlignJustify,MdFormatQuote} from 'react-icons/md';

const LIST_TYPES = ['numbered-list', 'bulleted-list']
const TEXT_ALIGN_TYPES = ['left', 'center', 'right', 'justify']

const RichTextExample = () => {
  const renderElement = useCallback(props => <Element {...props} />, [])
  const renderLeaf = useCallback(props => <Leaf {...props} />, [])
  const editor = useMemo(() => withReact(createEditor()), [])

  return (
    <Slate editor={editor} value={initialValue}>
        <BlockButton format="heading-one" icon="H1" />
        <BlockButton format="heading-two" icon="H2" />
        <MarkButton format="bold" icon={<MdFormatBold/>} />
        <MarkButton format="italic" icon={<MdFormatItalic/>} />
        <MarkButton format="underline" icon={<MdFormatUnderlined/>} />
        <BlockButton format="block-quote" icon={<MdFormatQuote/>} />
        <BlockButton format="bulleted-list" icon={<MdFormatListBulleted/>} />
        <BlockButton format="left" icon={<MdFormatAlignLeft/>} />
        <BlockButton format="center" icon={<MdFormatAlignCenter/>} />
        <BlockButton format="right" icon={<MdFormatAlignRight/>} />
        <BlockButton format="justify" icon={<MdFormatAlignJustify />} />
      <Editable
        renderElement={renderElement}
        renderLeaf={renderLeaf}
        placeholder="Enter some rich text…"
        spellCheck
        autoFocus
        onKeyDown={event => {
        }}
      />
    </Slate>
  )
}

const toggleBlock = (editor, format) => {
  const isActive = isBlockActive(
    editor,
    format,
    TEXT_ALIGN_TYPES.includes(format) ? 'align' : 'type'
  )
  const isList = LIST_TYPES.includes(format)

  Transforms.unwrapNodes(editor, {
    match: n =>
      !Editor.isEditor(n) &&
      SlateElement.isElement(n) &&
      LIST_TYPES.includes(n.type) &&
      !TEXT_ALIGN_TYPES.includes(format),
    split: true,
  })
  let newProperties 
  if (TEXT_ALIGN_TYPES.includes(format)) {
    newProperties = {
      align: isActive ? undefined : format,
    }
  } else {
    newProperties = {
      type: isActive ? 'paragraph' : isList ? 'list-item' : format,
    }
  }
  Transforms.setNodes(editor, newProperties);

  if (!isActive && isList) {
    const block = { type: format, children: [] }
    Transforms.wrapNodes(editor, block)
  }
}

const toggleMark = (editor, format) => {
  const isActive = isMarkActive(editor, format)

  if (isActive) {
    Editor.removeMark(editor, format)
  } else {
    Editor.addMark(editor, format, true)
  }
}

const isBlockActive = (editor, format, blockType = 'type') => {
  const { selection } = editor
  if (!selection) return false

  const [match] = Array.from(
    Editor.nodes(editor, {
      at: Editor.unhangRange(editor, selection),
      match: n =>
        !Editor.isEditor(n) &&
        SlateElement.isElement(n) &&
        n[blockType] === format,
    })
  )

  return !!match
}

const isMarkActive = (editor, format) => {
  const marks = Editor.marks(editor)
  return marks ? marks[format] === true : false
}

const Element = ({ attributes, children, element }) => {
  const style = { textAlign: element.align }
  switch (element.type) {
    case 'block-quote':
      return (
        <blockquote style={style} {...attributes}   className='before:content-["❝"] before:pr-1 before:text-2xl before:font-semibold after:content-["❞"] after:pl-1 after:text-2xl after:font-semibold' >
            <span className='bg-gray-100 p-2 rounded'>{children}</span>
        </blockquote>
      )
    case 'bulleted-list':
      return (
        <ul style={style} {...attributes} className='list-disc'>
          {children}
        </ul>
      )
    case 'heading-one':
      return (
        <h1 style={style} {...attributes} className='font-extrabold text-3xl'>
          {children}
        </h1>
      )
    case 'heading-two':
      return (
        <h2 style={style} {...attributes} className='font-semibold text-xl'>
          {children}
        </h2>
      )
    case 'list-item':
      return (
        <li style={style} {...attributes}>
          {children}
        </li>
      )
    case 'numbered-list':
      return (
        <ol style={style} {...attributes}>
          {children}
        </ol>
      )
    default:
      return (
        <p style={style} {...attributes}>
          {children}
        </p>
      )
  }
}

const Leaf = ({ attributes, children, leaf }) => {
  if (leaf.bold) {
    children = <strong>{children}</strong>
  }

  if (leaf.code) {
    children = <code>{children}</code>
  }

  if (leaf.italic) {
    children = <em>{children}</em>
  }

  if (leaf.underline) {
    children = <u>{children}</u>
  }

  return <span {...attributes}>{children}</span>
}

const BlockButton = ({ format, icon }) => {
  const editor = useSlate();
  const isActive = isBlockActive(editor,format,TEXT_ALIGN_TYPES.includes(format)?'align':'type');
  return (
    <button
      className={`p-2 text-xl rounded mx-1 ${isActive ? 'bg-gray-300':''}`}
      onMouseDown={event => {
        event.preventDefault()
        toggleBlock(editor, format)
      }}
    >
      {icon}
    </button>
  )
}


const MarkButton = ({ format, icon }) => {
  const editor = useSlate()
  const isActive = isMarkActive(editor,format)
  return (
    <button
      className={`p-2 text-xl rounded mx-1 ${isActive? 'bg-gray-300':'' }`}
      onMouseDown={event => {
        event.preventDefault()
        toggleMark(editor, format)
      }}
    >
      {icon}
    </button>
  )
}

const initialValue = [
  {
    type: 'paragraph',
    children: [
      { text: 'This is editable ' },
      { text: 'rich', bold: true },
      { text: ' text, ' },
      { text: 'much', italic: true },
      { text: ' better than a ' },
      { text: '<textarea>', code: true },
      { text: '!' },
    ],
  },
  {
    type: 'paragraph',
    children: [
      {
        text:
          "Since it's rich text, you can do things like turn a selection of text ",
      },
      { text: 'bold', bold: true },
      {
        text:
          ', or add a semantically rendered block quote in the middle of the page, like this:',
      },
    ],
  },
  {
    type: 'block-quote',
    children: [{ text: 'A wise quote.' }],
  },
  {
    type: 'paragraph',
    align: 'center',
    children: [{ text: 'Try it out for yourself!' }],
  },
]

export default RichTextExample

Finally , I got the working code . I'm answering my own question , Feel free to use my code if anyone having the similar issues . Dependencies used :

  • Tailwindcss
  • React-Icons
  • SlateJS
Sonu Hansda
  • 109
  • 1
  • 2
  • 6