editablejs / editable

🌱 A collaborative rich-text editor framework that focuses on stability, controllability, extensibility, and performance. 一款强到离谱的富文本编辑器框架,专注于稳定性、可控性、扩展性和性能。
https://docs.editablejs.com
Apache License 2.0
1.79k stars 120 forks source link

数据转换保存跟获取转换数据,我实现不了 #134

Closed guugg closed 11 months ago

guugg commented 12 months ago
guugg commented 12 months ago

默认获取数据是通过value={initialValue}还是在useIsomorphicLayoutEffect使用接口导入啊? @big-camel

big-camel commented 12 months ago

使用序列化和反序列化

类似这里获取markdown,只不过序列化使用 editor.value https://github.com/editablejs/editable/blob/main/apps/docs/src/pages/playground.tsx#L319 同样需要这样初始化绑定解析器 https://github.com/editablejs/editable/blob/main/apps/docs/src/pages/playground.tsx#L291

在 useIsomorphicLayoutEffect 里面可以

 const { onChange } = editor

    editor.onChange = ()=> {
     ...
    }
return () => {
 editor.onChange = onChange 
}
guugg commented 11 months ago

使用序列化和反序列化

类似这里获取markdown,只是序列化使用editor.value https://github.com/editablejs/editable/blob/main/apps/docs/src/pages/playground.tsx#L319 同样需要这样初始化绑定解析器 https://github.com/editablejs/editable/blob/main/apps/docs/src/pages/playground.tsx#L291

在使用IsomorphicLayoutEffect里面就可以

 const { onChange } = editor

    editor.onChange = ()=> {
     ...
    }
return () => {
 editor.onChange = onChange 
}

以前的文章部分含有markdown,html。 这怎么反序列化

big-camel commented 11 months ago

import { MarkdownDeserializer } from '@editablejs/deserializer/markdown' import { HtmlDeserializer } from '@editablejs/deserializer/html'

import { MarkdownSerializer } from '@editablejs/serializer/markdown' import { HtmlSerializer } from '@editablejs/serializer/html'

guugg commented 11 months ago
  useIsomorphicLayoutEffect(() => {
    withMarkdownDeserializerPlugin(editor) //将markdown反序列化程序插件添加到编辑器中
    withMarkdownSerializerPlugin(editor) // 将markdown序列化程序插件添加到编辑器中
    withTextSerializerTransform(editor) // 将text 序列化程序转换添加到编辑器
    withHTMLSerializerTransform(editor) // 将HTML序列化程序转换添加到编辑器
    withMarkdownSerializerTransform(editor) // 将markdown序列化程序转换添加到编辑器
    withHTMLDeserializerTransform(editor) // 将HTML反序列化程序转换添加到编辑器
    withMarkdownDeserializerTransform(editor) // 将markdown反序列化程序转换添加到编辑器
    HTMLDeserializer.withEditor(editor, withTitleHTMLDeserializerTransform, {})
    HTMLSerializer.withEditor(editor, withTitleHTMLSerializerTransform, {})
    const { onPaste, onChange } = editor

    editor.onPaste = event => {
      ...
      ...
    }
    editor.onChange = () => {
      const element = document.getElementById('text');
      const text = element ? element.innerHTML : '';
      const madst = MarkdownDeserializer.toMdastWithEditor(editor, text)
      const contentc = MarkdownDeserializer.transformWithEditor(editor, madst)
      editor.insertFragment(contentc)    
      console.log('接口:', contentc);
    }
    return () => {
      editor.onPaste = onPaste
      editor.onChange = onChange
    }
  }, [editor])

测试


-  `console.log`中

[ { "type": "paragraph", "children": [ { "text": "如果您看到这篇文章,表示您已经安装成功." } ] }, { "type": "heading-three", "children": [ { "fontSize": "20px", "bold": "bold", "text": "测试" } ] } ]



> Editable编辑器没有改变。一直刷新`console.log`

![屏幕截图 2023-09-02 120152.png](https://x.imgs.ovh/x/2023/09/02/64f2b70758c20.png)
big-camel commented 11 months ago

鼠标点击,切换光标也会改变

guugg commented 11 months ago

鼠标点击,切换光标也会改变

我还是不会,太菜了。

如果<div id="text"></div>里有HTML它识别成paragraph

如果您看到这篇文章,表示已经安装成功.
### 测试
- 测试
<a href="http://127.0.0.66/admin/manage-posts.php">主题开发者</a>
[
    {
        "type": "paragraph",
        "children": [
            {
                "text": "如果您看到这篇文章,表示已经安装成功."
            }
        ]
    },
    {
        "type": "heading-three",
        "children": [
            {
                "fontSize": "20px",
                "bold": "bold",
                "text": "测试"
            }
        ]
    },
    {
        "key": "6nnc17bke6s0",
        "level": 0,
        "start": 0,
        "children": [
            {
                "type": "paragraph",
                "children": [
                    {
                        "text": "测试"
                    }
                ]
            }
        ],
        "type": "unordered-list"
    },
    {
        "type": "paragraph",
        "children": [
            {
                "text": "<a href=\""
            },
            {
                "href": "http://127.0.0.66/admin/manage-posts.php\"&gt;主题开发者&lt;/a",
                "children": [
                    {
                        "text": "http://127.0.0.66/admin/manage-posts.php\"&gt;主题开发者&lt;/a"
                    }
                ],
                "type": "link"
            },
            {
                "text": ">"
            }
        ]
    }
]

还有就是,新文章是json原识别是paragraph

有什么方法比较简单的,我数据来源是一个ID,然后通过函数还是变量加载数据自动判断类型使用下面的反序列,最后在<EditableProvider editor={editor} value={initialValue}>中的value导入?

    withMarkdownDeserializerPlugin(editor) //将markdown反序列化程序插件添加到编辑器中
    withMarkdownSerializerPlugin(editor) // 将markdown序列化程序插件添加到编辑器中
    withTextSerializerTransform(editor) // 将text 序列化程序转换添加到编辑器
    withHTMLSerializerTransform(editor) // 将HTML序列化程序转换添加到编辑器
    withMarkdownSerializerTransform(editor) // 将markdown序列化程序转换添加到编辑器
    withHTMLDeserializerTransform(editor) // 将HTML反序列化程序转换添加到编辑器
    withMarkdownDeserializerTransform(editor) // 将markdown反序列化程序转换添加到编辑器
    HTMLDeserializer.withEditor(editor, withTitleHTMLDeserializerTransform, {})
    HTMLSerializer.withEditor(editor, withTitleHTMLSerializerTransform, {})
big-camel commented 11 months ago
 const document = new DOMParser().parseFromString(html, TEXT_HTML)
  const fragment = HTMLDeserializer.transformWithEditor(e, document.body)
  e.insertFragment(fragment)

你也可以随时通过

 MarkdownSerializer.withEditor(editor, withImageMarkdownSerializerTransform, {});
      MarkdownDeserializer.withEditor(editor, withImageMarkdownDeserializerTransform, {});

这类接口自定义解析

import { Image } from '@editablejs/plugins';
import { MarkdownSerializerWithTransform } from '@editablejs/serializer/markdown';

export const withImageMarkdownSerializerTransform: MarkdownSerializerWithTransform = (next) => {
  return (node, options = {}) => {
    if (Image.isImage(node) && node.url) {
      const { url } = node;
      const wh = node.width && node.height ? `wh=${node.width}x${node.height}` : '';
      return [
        {
          type: 'image',
          url: wh ? url + (~url.indexOf('?') ? `&${wh}` : `?${wh}`) : url,
        },
      ];
    }
    return next(node, options);
  };
};

import { MarkdownDeserializerWithTransform } from '@editablejs/deserializer/markdown';
import { Image } from '@editablejs/plugins';

export const withImageMarkdownDeserializerTransform: MarkdownDeserializerWithTransform = (next) => {
  return (node, options = {}) => {
    const { type } = node;
    if (type === 'image') {
      const { url } = node;
      let width: number | undefined = undefined;
      let height: number | undefined = undefined;
      const match = url.match(/wh=([\d.]+)x([\d.]+)/);

      if (match) {
        width = parseInt(match[1]);
        height = parseInt(match[2]);
      }
      return [
        Image.create({
          url,
          state: 'waitingUpload',
          width,
          height,
        }),
      ];
    }
    return next(node, options);
  };
};
guugg commented 11 months ago
 const document = new DOMParser().parseFromString(html, TEXT_HTML)
  const fragment = HTMLDeserializer.transformWithEditor(e, document.body)
  e.insertFragment(fragment)

你也可以随时通过

 MarkdownSerializer.withEditor(editor, withImageMarkdownSerializerTransform, {});
      MarkdownDeserializer.withEditor(editor, withImageMarkdownDeserializerTransform, {});

这类接口自定义解析

import { Image } from '@editablejs/plugins';
import { MarkdownSerializerWithTransform } from '@editablejs/serializer/markdown';

export const withImageMarkdownSerializerTransform: MarkdownSerializerWithTransform = (next) => {
  return (node, options = {}) => {
    if (Image.isImage(node) && node.url) {
      const { url } = node;
      const wh = node.width && node.height ? `wh=${node.width}x${node.height}` : '';
      return [
        {
          type: 'image',
          url: wh ? url + (~url.indexOf('?') ? `&${wh}` : `?${wh}`) : url,
        },
      ];
    }
    return next(node, options);
  };
};

import { MarkdownDeserializerWithTransform } from '@editablejs/deserializer/markdown';
import { Image } from '@editablejs/plugins';

export const withImageMarkdownDeserializerTransform: MarkdownDeserializerWithTransform = (next) => {
  return (node, options = {}) => {
    const { type } = node;
    if (type === 'image') {
      const { url } = node;
      let width: number | undefined = undefined;
      let height: number | undefined = undefined;
      const match = url.match(/wh=([\d.]+)x([\d.]+)/);

      if (match) {
        width = parseInt(match[1]);
        height = parseInt(match[2]);
      }
      return [
        Image.create({
          url,
          state: 'waitingUpload',
          width,
          height,
        }),
      ];
    }
    return next(node, options);
  };
};
import * as React from 'react'
import { useState } from 'react';
import {
  EditableProvider,
  ContentEditable,
  useIsomorphicLayoutEffect,
  Placeholder,
  isTouchDevice,
  Editable,
  withEditable,
  parseDataTransfer,
} from '@editablejs/editor'
import { Editor, createEditor, Range, Transforms } from '@editablejs/models'
import { MarkdownDeserializer } from '@editablejs/deserializer/markdown'
import { withPlugins, useContextMenuEffect, ContextMenu } from '@editablejs/plugins'
import { withHTMLSerializerTransform } from '@editablejs/plugins/serializer/html'
import { withTextSerializerTransform } from '@editablejs/plugins/serializer/text'
import {
  withMarkdownSerializerTransform,
  withMarkdownSerializerPlugin,
} from '@editablejs/plugins/serializer/markdown'
import { withHTMLDeserializerTransform } from '@editablejs/plugins/deserializer/html'

import {
  withMarkdownDeserializerTransform,
  withMarkdownDeserializerPlugin,
} from '@editablejs/plugins/deserializer/markdown'

import { withTitleHTMLSerializerTransform } from '@editablejs/plugin-title/serializer/html'
import { withTitleHTMLDeserializerTransform } from '@editablejs/plugin-title/deserializer/html'

import { withHistory } from '@editablejs/plugin-history'

import {
  ToolbarComponent,
  useToolbarEffect,
  withToolbar,
  Toolbar,
} from '@editablejs/plugin-toolbar'
import {
  withInlineToolbar,
  useInlineToolbarEffect,
  InlineToolbar,
} from '@editablejs/plugin-toolbar/inline'
import {
  withSideToolbar,
} from '@editablejs/plugin-toolbar/side'
import {
  withSlashToolbar,
} from '@editablejs/plugin-toolbar/slash'
import { TitleEditor } from '@editablejs/plugin-title'
import { HTMLDeserializer } from '@editablejs/deserializer/html'
import { HTMLSerializer } from '@editablejs/serializer/html'

import { createInlineToolbarItems } from './components/configs/inline-toolbar-items'
import { checkMarkdownSyntax } from './components/configs/check-markdown-syntax'

import { createContextMenuItems } from './components/configs/context-menu-items'
import {
  createToolbarItems,
} from './components/configs/toolbar'

export default function Editor_masin() {
  const [initialValue, setInitialValue] = useState<{ type: string; children: { text: string }[] }[] | null>(null);
  const [isFetching, setIsFetching] = useState(true);

  const editor = React.useMemo(() => {
    let editor = withEditable(createEditor())
    //带历史记录
    editor = withHistory(editor)
    //插件
    editor = withPlugins(editor)
    //带工具栏
    editor = withToolbar(editor)
    //带内联工具栏
    editor = withInlineToolbar(withToolbar(editor))
    if (!isTouchDevice) {
      editor = withSideToolbar(editor, {
        match: n => !TitleEditor.isTitle(editor, n),
      })
    }
    //带有斜线工具栏
    editor = withSlashToolbar(editor, {
      match: () => !Editor.above(editor, { match: n => TitleEditor.isTitle(editor, n) }),
    })
    return editor
  }, [])
  useIsomorphicLayoutEffect(() => {
    const unsubscribe = Placeholder.subscribe(editor, ([node]) => {
      if (
        Editable.isFocused(editor) &&
        Editor.isBlock(editor, node) &&
        !TitleEditor.isTitle(editor, node)
      )
        return () => '编辑器块占位符'
    })
    return () => unsubscribe()

  }, [editor])
  useIsomorphicLayoutEffect(() => {
    withMarkdownDeserializerPlugin(editor) // Adds a markdown deserializer plugin to the editor
    withMarkdownSerializerPlugin(editor) // Adds a markdown serializer plugin to the editor
    withTextSerializerTransform(editor) // Adds a text serializer transform to the editor
    withHTMLSerializerTransform(editor) // Adds an HTML serializer transform to the editor
    withMarkdownSerializerTransform(editor) // Adds a markdown serializer transform to the editor
    withHTMLDeserializerTransform(editor) // Adds an HTML deserializer transform to the editor
    withMarkdownDeserializerTransform(editor) // Adds a markdown deserializer transform to the editor
    HTMLDeserializer.withEditor(editor, withTitleHTMLDeserializerTransform, {})
    HTMLSerializer.withEditor(editor, withTitleHTMLSerializerTransform, {})
    const { onPaste } = editor
    editor.onPaste = (event) => {
      const { clipboardData, type } = event
      if (!clipboardData || !editor.selection) return onPaste(event)
      const { text, fragment, html, files } = parseDataTransfer(clipboardData)
      const isPasteText = type === 'pasteText'
      if (!isPasteText && (fragment.length > 0 || files.length > 0)) {
        return onPaste(event)
      }
      if (Range.isExpanded(editor.selection)) {
        Transforms.delete(editor)
      }
      const anchor = Range.start(editor.selection)
      onPaste(event)
      // check markdown syntax
      if (checkMarkdownSyntax(text, html) && editor.selection) {
        const focus = Range.end(editor.selection)
        Promise.resolve().then(() => {
          const madst = MarkdownDeserializer.toMdastWithEditor(editor, text)
          const content = MarkdownDeserializer.transformWithEditor(editor, madst)

          editor.selection = {
            anchor,
            focus,
          }

          editor.insertFragment(content)
        })
      }
    }

    return () => {
      editor.onPaste = onPaste
    }
  }, [editor])

  //从text获取内容
  useIsomorphicLayoutEffect(() => {
    // 获取初始值
    setTimeout(() => {
      const textw = document.querySelector('#text');
      const contentww = textw ? textw.innerHTML : '';

      const madstw = MarkdownDeserializer.toMdastWithEditor(editor, contentww)
      const contentw = MarkdownDeserializer.transformWithEditor(editor, madstw)
      // 设置初始值
      if (contentw && contentw.length > 0) {
        setInitialValue(contentw as { type: string; children: { text: string }[] }[]);
      } else {
        setInitialValue([{ type: 'paragraph', children: [{ text: '🤣' }] }]);
      }
      setIsFetching(false);
    }, 200);
  }, []);

  useContextMenuEffect(() => {
    ContextMenu.setItems(editor, createContextMenuItems(editor))
  }, editor)

  useToolbarEffect(() => {
    Toolbar.setItems(editor, createToolbarItems(editor))
  }, editor)

  useInlineToolbarEffect(() => {
    InlineToolbar.setItems(editor, createInlineToolbarItems(editor))
  }, editor)

  if (isFetching) {
    return <div>加载中...</div>; // 渲染加载状态
  }

  return (
    <>
      <EditableProvider
        editor={editor}
        value={initialValue || []}
      >
        <ToolbarComponent className='flex-wrap' editor={editor} />
        <ContentEditable />
      </EditableProvider>
    </>
  )
}

大佬,我是这样获取的,只能获取到MD格式。 我不知道怎么添加让它也支持获取到html格式。

现在的编辑器没有报任何错误

big-camel commented 11 months ago

使用 HTMLDeserializer 相关api 就可以 from 阿里邮箱 iPhone------------------------------------------------------------------ @.> 日 期:2023年09月09日 17:50:19 @.> @.>; @.> 主 题:Re: [editablejs/editable] 数据转换保存跟获取转换数据,我实现不了 (Issue #134)

const document = new DOMParser().parseFromString(html, TEXT_HTML) const fragment = HTMLDeserializer.transformWithEditor(e, document.body) e.insertFragment(fragment)

你也可以随时通过 MarkdownSerializer.withEditor(editor, withImageMarkdownSerializerTransform, {}); MarkdownDeserializer.withEditor(editor, withImageMarkdownDeserializerTransform, {});

这类接口自定义解析 import { Image } from @./plugins'; import { MarkdownSerializerWithTransform } from @./serializer/markdown';

export const withImageMarkdownSerializerTransform: MarkdownSerializerWithTransform = (next) => { return (node, options = {}) => { if (Image.isImage(node) && node.url) { const { url } = node; const wh = node.width && node.height ? wh=${node.width}x${node.height} : ''; return [ { type: 'image', url: wh ? url + (~url.indexOf('?') ? &${wh} : ?${wh}) : url, }, ]; } return next(node, options); }; };

import { MarkdownDeserializerWithTransform } from @./deserializer/markdown'; import { Image } from @./plugins';

export const withImageMarkdownDeserializerTransform: MarkdownDeserializerWithTransform = (next) => { return (node, options = {}) => { const { type } = node; if (type === 'image') { const { url } = node; let width: number | undefined = undefined; let height: number | undefined = undefined; const match = url.match(/wh=([\d.]+)x([\d.]+)/);

  if (match) {
    width = parseInt(match[1]);
    height = parseInt(match[2]);
  }
  return [
    Image.create({
      url,
      state: 'waitingUpload',
      width,
      height,
    }),
  ];
}
return next(node, options);

}; };

import * as React from 'react' import { useState } from 'react'; import { EditableProvider, ContentEditable, useIsomorphicLayoutEffect, Placeholder, isTouchDevice, Editable, withEditable, parseDataTransfer, } from @./editor' import { Editor, createEditor, Range, Transforms } from @./models' import { MarkdownDeserializer } from @./deserializer/markdown' import { withPlugins, useContextMenuEffect, ContextMenu } from @./plugins' import { withHTMLSerializerTransform } from @./plugins/serializer/html' import { withTextSerializerTransform } from @./plugins/serializer/text' import { withMarkdownSerializerTransform, withMarkdownSerializerPlugin, } from @./plugins/serializer/markdown' import { withHTMLDeserializerTransform } from @./plugins/deserializer/html'

import { withMarkdownDeserializerTransform, withMarkdownDeserializerPlugin, } from @.***/plugins/deserializer/markdown'

import { withTitleHTMLSerializerTransform } from @./plugin-title/serializer/html' import { withTitleHTMLDeserializerTransform } from @./plugin-title/deserializer/html'

import { withHistory } from @.***/plugin-history'

import { ToolbarComponent, useToolbarEffect, withToolbar, Toolbar, } from @./plugin-toolbar' import { withInlineToolbar, useInlineToolbarEffect, InlineToolbar, } from @./plugin-toolbar/inline' import { withSideToolbar, } from @./plugin-toolbar/side' import { withSlashToolbar, } from @./plugin-toolbar/slash' import { TitleEditor } from @./plugin-title' import { HTMLDeserializer } from @./deserializer/html' import { HTMLSerializer } from @.***/serializer/html'

import { createInlineToolbarItems } from './components/configs/inline-toolbar-items' import { checkMarkdownSyntax } from './components/configs/check-markdown-syntax'

import { createContextMenuItems } from './components/configs/context-menu-items' import { createToolbarItems, } from './components/configs/toolbar'

export default function Editor_masin() { const [initialValue, setInitialValue] = useState<{ type: string; children: { text: string }[] }[] | null>(null); const [isFetching, setIsFetching] = useState(true);

const editor = React.useMemo(() => { let editor = withEditable(createEditor()) //带历史记录 editor = withHistory(editor) //插件 editor = withPlugins(editor) //带工具栏 editor = withToolbar(editor) //带内联工具栏 editor = withInlineToolbar(withToolbar(editor)) if (!isTouchDevice) { editor = withSideToolbar(editor, { match: n => !TitleEditor.isTitle(editor, n), }) } //带有斜线工具栏 editor = withSlashToolbar(editor, { match: () => !Editor.above(editor, { match: n => TitleEditor.isTitle(editor, n) }), }) return editor }, []) useIsomorphicLayoutEffect(() => { const unsubscribe = Placeholder.subscribe(editor, ([node]) => { if ( Editable.isFocused(editor) && Editor.isBlock(editor, node) && !TitleEditor.isTitle(editor, node) ) return () => '编辑器块占位符' }) return () => unsubscribe()

}, [editor]) useIsomorphicLayoutEffect(() => { withMarkdownDeserializerPlugin(editor) // Adds a markdown deserializer plugin to the editor withMarkdownSerializerPlugin(editor) // Adds a markdown serializer plugin to the editor withTextSerializerTransform(editor) // Adds a text serializer transform to the editor withHTMLSerializerTransform(editor) // Adds an HTML serializer transform to the editor withMarkdownSerializerTransform(editor) // Adds a markdown serializer transform to the editor withHTMLDeserializerTransform(editor) // Adds an HTML deserializer transform to the editor withMarkdownDeserializerTransform(editor) // Adds a markdown deserializer transform to the editor HTMLDeserializer.withEditor(editor, withTitleHTMLDeserializerTransform, {}) HTMLSerializer.withEditor(editor, withTitleHTMLSerializerTransform, {}) const { onPaste } = editor editor.onPaste = (event) => { const { clipboardData, type } = event if (!clipboardData || !editor.selection) return onPaste(event) const { text, fragment, html, files } = parseDataTransfer(clipboardData) const isPasteText = type === 'pasteText' if (!isPasteText && (fragment.length > 0 || files.length > 0)) { return onPaste(event) } if (Range.isExpanded(editor.selection)) { Transforms.delete(editor) } const anchor = Range.start(editor.selection) onPaste(event) // check markdown syntax if (checkMarkdownSyntax(text, html) && editor.selection) { const focus = Range.end(editor.selection) Promise.resolve().then(() => { const madst = MarkdownDeserializer.toMdastWithEditor(editor, text) const content = MarkdownDeserializer.transformWithEditor(editor, madst)

      editor.selection = {
        anchor,
        focus,
      }

      editor.insertFragment(content)
    })
  }
}

return () => {
  editor.onPaste = onPaste
}

}, [editor])

//从text获取内容 useIsomorphicLayoutEffect(() => { // 获取初始值 setTimeout(() => { const textw = document.querySelector('#text'); const contentww = textw ? textw.innerHTML : '';

  const madstw = MarkdownDeserializer.toMdastWithEditor(editor, contentww)
  const contentw = MarkdownDeserializer.transformWithEditor(editor, madstw)
  // 设置初始值
  if (contentw && contentw.length > 0) {
    setInitialValue(contentw as { type: string; children: { text: string }[] }[]);
  } else {
    setInitialValue([{ type: 'paragraph', children: [{ text: '🤣' }] }]);
  }
  setIsFetching(false);
}, 200);

}, []);

useContextMenuEffect(() => { ContextMenu.setItems(editor, createContextMenuItems(editor)) }, editor)

useToolbarEffect(() => { Toolbar.setItems(editor, createToolbarItems(editor)) }, editor)

useInlineToolbarEffect(() => { InlineToolbar.setItems(editor, createInlineToolbarItems(editor)) }, editor)

if (isFetching) { return

加载中...
; // 渲染加载状态 }

return ( <> <EditableProvider editor={editor} value={initialValue || []}

</> ) }

大佬,我是这样获取的,只能获取到MD格式。 我不知道怎么添加让它也支持获取到html格式。 现在的编辑器没有报任何错误 — Reply to this email directly, view it on GitHub, or unsubscribe. You are receiving this because you were mentioned.Message ID: @.***>