簡體   English   中英

使用 slate.js 實現實時 markdown 渲染

[英]Implement live markdown rendering using slate.js

我正在使用slate.js編寫 markdown 文本編輯器。 我正在嘗試實現以下實時渲染效果(來自Typora ):

實時 Markdown 渲染

如你看到的,

  1. 當我輸入時,文本會自動變為粗體。
  2. 當我按空格鍵時,四個星號消失了,只有文本本身可見。
  3. 當我將 cursor 聚焦回文本時,星號再次出現(所以我可以修改它們)。

由於MarkdownPreview的示例,我已經實現了第一項,這是它的代碼(取自slate 存儲庫):

import Prism from 'prismjs'
import React, { useCallback, useMemo } from 'react'
import { Slate, Editable, withReact } from 'slate-react'
import { Text, createEditor, Descendant } from 'slate'
import { withHistory } from 'slate-history'
import { css } from '@emotion/css'

// eslint-disable-next-line
;Prism.languages.markdown=Prism.languages.extend("markup",{}),Prism.languages.insertBefore("markdown","prolog",{blockquote:{pattern:/^>(?:[\t ]*>)*/m,alias:"punctuation"},code:[{pattern:/^(?: {4}|\t).+/m,alias:"keyword"},{pattern:/``.+?``|`[^`\n]+`/,alias:"keyword"}],title:[{pattern:/\w+.*(?:\r?\n|\r)(?:==+|--+)/,alias:"important",inside:{punctuation:/==+$|--+$/}},{pattern:/(^\s*)#+.+/m,lookbehind:!0,alias:"important",inside:{punctuation:/^#+|#+$/}}],hr:{pattern:/(^\s*)([*-])([\t ]*\2){2,}(?=\s*$)/m,lookbehind:!0,alias:"punctuation"},list:{pattern:/(^\s*)(?:[*+-]|\d+\.)(?=[\t ].)/m,lookbehind:!0,alias:"punctuation"},"url-reference":{pattern:/!?\[[^\]]+\]:[\t ]+(?:\S+|<(?:\\.|[^>\\])+>)(?:[\t ]+(?:"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|\((?:\\.|[^)\\])*\)))?/,inside:{variable:{pattern:/^(!?\[)[^\]]+/,lookbehind:!0},string:/(?:"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|\((?:\\.|[^)\\])*\))$/,punctuation:/^[\[\]!:]|[<>]/},alias:"url"},bold:{pattern:/(^|[^\\])(\*\*|__)(?:(?:\r?\n|\r)(?!\r?\n|\r)|.)+?\2/,lookbehind:!0,inside:{punctuation:/^\*\*|^__|\*\*$|__$/}},italic:{pattern:/(^|[^\\])([*_])(?:(?:\r?\n|\r)(?!\r?\n|\r)|.)+?\2/,lookbehind:!0,inside:{punctuation:/^[*_]|[*_]$/}},url:{pattern:/!?\[[^\]]+\](?:\([^\s)]+(?:[\t ]+"(?:\\.|[^"\\])*")?\)| ?\[[^\]\n]*\])/,inside:{variable:{pattern:/(!?\[)[^\]]+(?=\]$)/,lookbehind:!0},string:{pattern:/"(?:\\.|[^"\\])*"(?=\)$)/}}}}),Prism.languages.markdown.bold.inside.url=Prism.util.clone(Prism.languages.markdown.url),Prism.languages.markdown.italic.inside.url=Prism.util.clone(Prism.languages.markdown.url),Prism.languages.markdown.bold.inside.italic=Prism.util.clone(Prism.languages.markdown.italic),Prism.languages.markdown.italic.inside.bold=Prism.util.clone(Prism.languages.markdown.bold); // prettier-ignore

const MarkdownPreviewExample = () => {
  const renderLeaf = useCallback(props => <Leaf {...props} />, [])
  const editor = useMemo(() => withHistory(withReact(createEditor())), [])
  const decorate = useCallback(([node, path]) => {
    const ranges = []

    if (!Text.isText(node)) {
      return ranges
    }

    const getLength = token => {
      if (typeof token === 'string') {
        return token.length
      } else if (typeof token.content === 'string') {
        return token.content.length
      } else {
        return token.content.reduce((l, t) => l + getLength(t), 0)
      }
    }

    const tokens = Prism.tokenize(node.text, Prism.languages.markdown)
    let start = 0

    for (const token of tokens) {
      const length = getLength(token)
      const end = start + length

      if (typeof token !== 'string') {
        ranges.push({
          [token.type]: true,
          anchor: { path, offset: start },
          focus: { path, offset: end },
        })
      }

      start = end
    }

    return ranges
  }, [])

  return (
    <Slate editor={editor} value={initialValue}>
      <Editable
        decorate={decorate}
        renderLeaf={renderLeaf}
        placeholder="Write some markdown..."
      />
    </Slate>
  )
}

const Leaf = ({ attributes, children, leaf }) => {
  return (
    <span
      {...attributes}
      className={css`
        font-weight: ${leaf.bold && 'bold'};
        font-style: ${leaf.italic && 'italic'};
        text-decoration: ${leaf.underlined && 'underline'};
        ${leaf.title &&
          css`
            display: inline-block;
            font-weight: bold;
            font-size: 20px;
            margin: 20px 0 10px 0;
          `}
        ${leaf.list &&
          css`
            padding-left: 10px;
            font-size: 20px;
            line-height: 10px;
          `}
        ${leaf.hr &&
          css`
            display: block;
            text-align: center;
            border-bottom: 2px solid #ddd;
          `}
        ${leaf.blockquote &&
          css`
            display: inline-block;
            border-left: 2px solid #ddd;
            padding-left: 10px;
            color: #aaa;
            font-style: italic;
          `}
        ${leaf.code &&
          css`
            font-family: monospace;
            background-color: #eee;
            padding: 3px;
          `}
      `}
    >
      {children}
    </span>
  )
}

const initialValue: Descendant[] = [
  {
    type: 'paragraph',
    children: [
      {
        text:
          'Slate is flexible enough to add **decorations** that can format text based on its content. For example, this editor has **Markdown** preview decorations on it, to make it _dead_ simple to make an editor with built-in Markdown previewing.',
      },
    ],
  },
  {
    type: 'paragraph',
    children: [{ text: '## Try it out!' }],
  },
  {
    type: 'paragraph',
    children: [{ text: 'Try it out for yourself!' }],
  },
]

export default MarkdownPreviewExample

我的問題是,我該如何實施第二項和第三項? 我想了很久,但沒有找到任何好的方法來實現它們。

好消息:

  • 原始文本保存完好(在您的應用程序中的某處)
  • Slate.js正在為每種樣式呈現一個新元素。 感謝您發布的現場演示鏈接,很明顯 DOM 正在保存您的state的 state(這表明如果需要我們可以在石板上添加操作)

壞消息:

  • 實現一個編輯器是一件令人頭疼的事情。 使用現有的並不總是像我們預期的那樣直觀和容易。 這就是為什么我如此喜歡Monaco 編輯器項目(一個免費的可編輯編輯器,讓我免費體驗VS code ?是的,請!)

我們能做什么?

簡單的方法:

在我們的項目中使用 Monaco 編輯器。 根據我的經驗 - 在編碼和格式化技術文本方面更好(也許這是VS code背后的引擎)

不那么簡單的方法:

我們可以做一個更簡單的實現——一個帶有原始文本的面板和另一個用於預覽的面板(順便說一下——這正是你在VS code中編輯 markdown 文件時發生的情況)。 這樣,我們總是可以渲染視圖以反映我們最近的更改。 對於初學者 - 你可以使用這個項目來做它並添加調整以滿足你的需求

艱難的道路:

使用slate.js commands (閱讀此文檔了解一般概念 + 有用示例)。 這種方法將讓 slate 處理負載,但也需要您深入研究該項目。 請注意,您可以注冊自定義命令和查詢以滿足您的需要,而不會破壞您的工作管道(閱讀

瘋狂的方式

嘗試通過在渲染元素之上使用自定義事件來覆蓋 slate。 如果您喜歡玩項目內部並即時注入價值,這可能會很棘手,但可以實現。 雖然不是很推薦

我的建議

創建一個自定義命令,該命令將應用您想要的markdown style (例如:粗體

使用 slate 處理程序來跟蹤space鍵命中並使用您的命令

function onKeyDown(event, editor, next) {
  if (event.key == 'Enter') {
    // TODO: add markdown style 
    editor.applyMarkdownBold() // applyMarkdownBold is a made-up name for your custom command
  } else {
    return next()
  }
}

我知道-這個示例將對所有文本應用粗體樣式,但是-如果將其與range selection結合使用,您將在編輯器中獲得相關區域所需的樣式(再次-救援文檔

從這一點開始 - 通過按鍵應用特定選擇或通過單擊添加 markdown 字符(使用事件 + 查詢 + 命令)只是時間問題

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM