DevGang
Авторизоваться

Создание редактора Markdown, используя Next.js и TailwindCss

Присоединяйтесь ко мне в этом проекте, где мы создаем онлайн-редактор Markdown, используя последнюю версию Nextjs.

1. Создайте целевую страницу

Мне нужен простой макет, поэтому я разделил экран на две части; слева — редактор, а справа мы видим рендеринг Markdown.

const Homepage = () => {
    return (
    <div className='h-screen flex justify-between'>
        // Input the markdown
        <section className='w-full pt-5 h-full'>
          <textarea
            className='w-full ... placeholder:opacity-80'
            placeholder='Feed me some Markdown 🍕'
            autoFocus
          />
        </section>
        <div className='fixed ... border-dashed' />
        // Render the markdown
        <article className='w-full pt-5 pl-6'>
          Markdown lies here
        </article>
      </div>
    )
}

return Homepage

2. Добавьте состояния для хранения данных

Теперь давайте изменим его на клиентский компонент и добавим хук useState.

"use client"

import { useState } from "react"

const Homepage = () => {
    const [source, setSource] = useState('');
    return (
        ...
        <textarea
          className='w-full ... placeholder:opacity-80'
          placeholder='Feed me some Markdown 🍕'
          value={source}
          onChange={(e) => setSource(e.target.value)}
          autoFocus
        />
        ...
    )
}

3. Настройте React Markdown и @tailwindcss/typography

Мы используем react-markdown для рендеринга уценки и @tailwindcss/typography стилизации уценки. Установите их, выполнив следующие команды.

npm install react-markdown
npm install -D @tailwindcss/typography

Теперь импортируйте и добавьте компонент Markdown и передайте его source как дочерний. Не забудьте добавить prose имя класса в компонент Markdown.

import Markdown from 'react-markdown'

const Homepage = () => {
    return (
        ...
        <div className='fixed ... border-dashed' />
        // Render the markdown
        <article className='w-full pt-5 pl-6'>
          <Markdown
            className='prose prose-invert min-w-full'
          >
            {source}
          </Markdown>
        </article>
        ...
    )
}

Теперь, если вы введете любую уценку, вы все равно не найдете никаких изменений. Это потому, что мы забыли добавить @tailwindcss/typography плагин в конфигурацию Tailwindcss.

Измените свой tailwind.config.ts на следующее:

import type { Config } from 'tailwindcss'

const config: Config = {
  content: [
    './src/pages/**/*.{js,ts,jsx,tsx,mdx}',
    './src/components/**/*.{js,ts,jsx,tsx,mdx}',
    './src/app/**/*.{js,ts,jsx,tsx,mdx}',
  ],
  // Add the plugin here
  plugins: [require('@tailwindcss/typography')]
}

export default config

Теперь напишите уценку и вы увидите изменения вживую.

4. Подсветка кода и пользовательские компоненты

Теперь нам нужно установить react-syntax-highlighter пакет, чтобы добавить подсветку кода в наш проект.

npm i react-syntax-highlighter
npm i --save @types/react-syntax-highlighter

Теперь мы собираемся создать пользовательский компонент для средства выделения кода.

Создайте папку с именем components внутри src папки. Теперь создайте файл с именем Code.tsx внутри папки компонентов.

Добавьте следующий код из документации реакции-синтаксиса-выделителя:

import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
import { materialOceanic } from 'react-syntax-highlighter/dist/cjs/styles/prism';

export const CodeBlock = ({ ...props }) => {
  return (
    <SyntaxHighlighter
      language={props.className?.replace(/(?:lang(?:uage)?-)/, '')}
      style={materialOceanic}
      wrapLines={true}
      className='not-prose rounded-md'
    >
      {props.children}
    </SyntaxHighlighter>
  )
}

Здесь реквизит содержит имя класса с языком кода в формате: lang-typescript или иногда language-typescript мы используем какое-нибудь регулярное выражение, чтобы удалить все, кроме названия языка. Имя not-prose класса удалит стили оформления по умолчанию.

Теперь вернитесь к основному page.tsx файлу, импортируйте CodeBlock компонент и передайте его исходному <Markdown /> компоненту.

import Markdown from 'react-markdown'
import { CodeBlock } from '@/components/Code'

const Homepage = () => {
    const options = { code: CodeBlock }
    return (
        ...
          <Markdown
            className='prose prose-invert min-w-full'
            components={options}
          >
            {source}
          </Markdown>
        ...
    )
}

Это заменит каждое появление code нашего пользовательского CodeBlock компонента.

Необязательно

ОШИБКА: вокруг компонента кода может возникнуть странная темная рамка, вызванная тегом pre и стилями tailwind.

Чтобы исправить это, вернитесь к своему файлу Code.tsx и добавьте следующий код, который удаляет стили tailwind из тега pre.

export const Pre = ({ ...props }) => {
  return (
    <div className='not-prose'>
      {props.children}
    </div>
  )
}

Импортируйте это в свой page.tsx и добавьте в options переменную:

const Homepage = () => {
    const options = { 
        code: CodeBlock,
        // Add here
        pre: Pre,
    }
    return ( ... )
}

Это удалит границу.

5. Добавление плагинов Rehype и Remark

Rehype и Remark — это плагины, используемые для преобразования и управления содержимым HTML и Markdown веб-сайта, помогая улучшить его функциональность и внешний вид.

Мы собираемся использовать следующее:

  • rehype-sanitize: Очистит уценку.
  • rehype-external-links: Добавит значок 🔗 к ссылкам.
  • remark-gfm: Плагин для поддержки GFM (Поддержка таблиц, сносок и т. д.)

Установите плагины:

npm i remark-gfm rehype-external-links rehype-sanitize

Вернемся к нашему page.tsx

import remarkGfm from 'remark-gfm'
import rehypeSanitize from 'rehype-sanitize'
import rehypeExternalLinks from 'rehype-external-links'

... 
<Markdown
  ...
  remarkPlugins={[remarkGfm]}
  rehypePlugins={[
    rehypeSanitize,
    [rehypeExternalLinks,
     { content: { type: 'text', value: '🔗' } }
    ],
  ]}
>{source}</Markdown>

Передайте плагины примечаний remarkPlugins и переделайте плагины rehypePlugins(я знаю, это очень удивительно).

Если какой-либо плагин требует какой-либо настройки, поместите его в квадратные скобки, за которыми следует имя плагина и параметры в фигурных скобках в следующем синтаксисе: [veryCoolPlugin, { { options } }]

6. Заголовок с кнопками Markdown

Затем мы добавляем компонент заголовка с кнопками, при нажатии на которые вставляются определенные элементы Markdown.

Сначала создайте Header.tsx в папке components и напишите следующий код:

const Header = () => {
  const btns = [
    { name: 'B', syntax: '**Bold**' },
    { name: 'I', syntax: '*Italic*' },
    { name: 'S', syntax: '~Strikethrough~' },
    { name: 'H1', syntax: '# ' },
]

  return (
    <header className="flex ... bg-[#253237]">
        {btns.map(btn => (
          <button
            key={btn.syntax}
            className="flex ...rounded-md"
          >
            {btn.name}
          </button>
        ))}
    </header>
  )
}

export default Header

Импортируйте его в основной page.tsx

import Header from '@/components/Header'

const Homepage = () => {
    const options = { code: CodeBlock }
    return (
        <>
        <Header /> // Should be on top
        <div className='h-screen flex justify-between'>
            ...
        </div>
        </>
    )
}

Вот в чем загвоздка. Наши состояния лежат в родительском компоненте, а Header компонент — в дочернем.  

Как мы работаем с состояниями в дочернем компоненте? Лучшее решение — создать функцию для изменения состояния родительского компонента и передать эту функцию дочернему компоненту.

const Homepage = () => {
  const [source, setSource] = useState('');

  const feedElement = (syntax: string) => {
    return setSource(source + syntax)
  }
  
  return (
     <>
     <Header />
    ...
  )
}

В Header.tsx нужно принять функцию в качестве параметра и добавить ее к кнопке с помощью onClick атрибута:  

const Header = (
  { feedElement }: 
  { feedElement: (syntax: string) => void }
) => {
  const btns = [ ... ]

  return (
    ...
    <button
      key={btn.syntax}
      className="flex ...rounded-md"
      onClick={() => feedElement(btn.syntax)}
    >
      {btn.name}
    </button>
  )
}

Возвращаемся к тому, что page.tsx мы передаем feedElement функцию в Header.

const feedElement = (syntax: string) => {
  return setSource(source + syntax)
}
  
return (
  <>
  <Header feedElement={feedElement} />
  ...
)

Теперь каждый раз, когда вы нажимаете на кнопку, вы должны получать следующий элемент Markdown.

Подведение итогов

Вот и все. Теперь у нас есть полнофункциональный редактор Markdown, созданный с использованием Nextjs.

Источник:

#JavaScript
Комментарии
Чтобы оставить комментарий, необходимо авторизоваться

Присоединяйся в тусовку

В этом месте могла бы быть ваша реклама

Разместить рекламу