1 В избранное 0 Ответвления 0

OSCHINA-MIRROR/editablejs-editable

Присоединиться к Gitlife
Откройте для себя и примите участие в публичных проектах с открытым исходным кодом с участием более 10 миллионов разработчиков. Приватные репозитории также полностью бесплатны :)
Присоединиться бесплатно
Клонировать/Скачать
Внести вклад в разработку кода
Синхронизировать код
Отмена
Подсказка: Поскольку Git не поддерживает пустые директории, создание директории приведёт к созданию пустого файла .keep.
Loading...
README.md

zh-CN

Editable

Editable — это расширяемый фреймворк для создания редактора богатого текста, который акцентирует внимание на стабильности, управляемости и производительности. Для достижения этих целей мы не использовали встроенное свойство contenteditable, а вместо этого создали собственный рендерер, что позволяет нам лучше контролировать поведение редактора. Теперь вы можете не беспокоиться о проблемах совместимости между платформами и браузерами (например, Selection, Input), сосредоточившись на логике вашего бизнеса.

Вы можете просмотреть демонстрацию здесь: https://docs.editablejs.com/playground


  • Почему не используется рендеринг с помощью canvas?

    Хотя рендеринг с помощью canvas может быть быстрее, чем рендеринг с помощью DOM, опыт разработки при помощи canvas неудобен и требует написания большего количества кода.

  • Почему используется React для рендера?

    React делает плагины более гибкими и имеет хорошую экосистему. Однако производительность React не так высока, как у нативного DOM.

    В идеальном фронтенде для богатого текста должны выполняться следующие условия:

    1. Отсутствие виртуального DOM
    2. Отсутствие алгоритма сравнения (diff)
    3. Отсутствие прокси-объектовПоэтому я сравнил различные фронтенд-фреймворки, такие как Vue, Solid-js и SvelteJS. Я обнаружил, что Solid-js удовлетворяет первым двум требованиям, но каждый свойство обёрнут в прокси, что может вызвать проблемы при сравнении чистых объектов JavaScript с помощью === во время разработки расширений.

Чтобы повысить производительность, мы вероятно будем рефакторить его для использования нативного рендера DOM в дальнейшем развитии.

На данный момент React удовлетворяет следующим двум стандартам:

  • Опыт разработки
  • Расширяемость плагинов
  • Совместимость между фронтендами
  • Производительность рендера

При последующем рефакторинге мы попробуем найти баланс между этими четырьмя стандартами.

Быстрое начало

На текущий момент вам всё ещё потребуется использовать React для данной версии, однако в будущих версиях мы планируем рефакторить его для использования нативного рендера DOM.

Установите зависимости @editablejs/models и @editablejs/editor:

npm i --save @editablejs/models @editablejs/editor

Вот минимальный пример текстового редактора, который можно редактировать:

import * as React from 'react';
import { createEditor } from '@editablejs/models';
import {
  EditableProvider,
  ContentEditable,
  withEditable,
} from '@editablejs/editor';

const App = () => {
  const editor = React.useMemo(() => withEditable(createEditor()), []);
  return (
    <EditableProvider editor={editor}>
      <ContentEditable placeholder="Введите содержимое..." />
    </EditableProvider>
  );
};
```## Модель данных

`@editablejs/models` предоставляет модель данных для описания состояния редактора и операций над этим состоянием.

```ts
{
  type: 'параграф',
  children: [
    {
      type: 'текст',
      text: 'Привет, мир!'
    }
  ]
}

Как можно заметить, её структура очень схожа с Slate, и мы не создали новую модель данных, а использовали модель данных Slate напрямую и расширили её (добавили структуры данных и операции, связанные с сеткой и списками). Опираясь на эти зрелые и отличные структуры данных, наш редактор становится более стабильным.

Мы упаковали все API Slate в @editablejs/models, так что вы можете найти все API Slate в @editablejs/models.

Если вам незнакомо Slate, вы можете обратиться к его документации: https://docs.slatejs.org/

Плагины

На данный момент мы предлагаем некоторые готовые плагины, которые реализуют базовые возможности, а также поддерживают горячие клавиши, синтаксис Markdown, сериализацию Markdown, десериализацию Markdown, сериализацию HTML и десериализацию HTML.### Общие плагины
@editablejs/plugin-context-menu предоставляет контекстное меню правого щелчка мыши. Поскольку мы не используем некоторые функции встроенного контекстного меню contenteditable, нам нужно определить свою собственную функциональность для контекстного меню правого щелчка.

  • @editablejs/plugin-align для выравнивания текста
  • @editablejs/plugin-blockquote для цитат
  • @editablejs/plugin-codeblock для блоков кода
  • @editablejs/plugin-font включает цвет шрифта, фоновый цвет и размер шрифта
  • @editablejs/plugin-heading для заголовков
  • @editablejs/plugin-hr для горизонтальных линий
  • @editablejs/plugin-image для изображений
  • @editablejs/plugin-indent для отступов
  • @editablejs/plugin-leading для межстрочного интервала
  • @editablejs/plugin-link для ссылок
  • @editablejs/plugin-list включает нумерованные списки, маркированные списки и списки задач
  • @editablejs/plugin-mark включает жирный, курсив, через, подчеркнутый, ^верхний индекс^, _нижний индекс_ и код
  • @editablejs/plugin-mention для упоминаний
  • @editablejs/plugin-table для таблиц

Способ использования отдельного плагина с примером plugin-mark:```tsx import { withMark } from '@editablejs/mark'

const editor = React.useMemo(() => { const editor = withEditable(createEditor()) return withMark(editor) }, [])


Вы также можете использовать следующий метод для быстрого использования вышеупомянутых общих плагинов через `withPlugins` в `@editablejs/plugins`:

```tsx
import { withPlugins } from '@editablejs/plugins'

const editor = React.useMemo(() => {
  const editor = withEditable(createEditor())
  return withPlugins(editor)
}, [])

Плагин Истории

Плагин @editablejs/plugin-history предоставляет возможность отмены и повтора действий.

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

const editor = React.useMemo(() => {
  const editor = withEditable(createEditor())
  return withHistory(editor)
}, [])

Плагин Заголовка

Когда вы разрабатываете приложения документов или блогов, обычно у вас есть отдельный заголовок и основной контент, который часто реализуется с помощью input или textarea, расположенных вне редактора. В совместно используемой среде, поскольку он независим от редактора, требуется дополнительная работа для обеспечения реального времени синхронизации заголовка.

Плагин @editablejs/plugin-title решает эту проблему, используя первый дочерний узел редактора как заголовок, интегрируя его в общую структуру данных редактора таким образом, чтобы он имел те же возможности, что и сам редактор.

import { withTitle } from '@editablejs/plugin-title'
const editor = React.useMemo(() => {
  const editor = withEditable(createEditor())
  return withTitle(editor)
}, [])

У него также есть отдельное свойство заполнителя для установки заполнителя для заголовка.```tsx return withTitle(editor, { placeholder: 'Введите заголовок' })


### Плагин Yjs

Плагин `@editablejs/plugin-yjs` предоставляет поддержку для Yjs, что позволяет синхронизировать данные редактора в реальном времени с другими клиентами.

Необходимо установить следующие зависимости:

- yjs — основная библиотека Yjs
- @editablejs/yjs-websocket — библиотека Yjs для работы с WebSocket

Кроме того, она также предоставляет реализацию сервера Node.js, которую можно использовать для настройки службы Yjs:
```ts
import startServer from '@editablejs/yjs-websocket/server'

startServer()
  • @editablejs/plugin-yjs — плагин Yjs для использования с редактором
npm i yjs @editablejs/yjs-websocket @editablejs/plugin-yjs
Инструкции:

import * as Y from 'yjs'
import { withYHistory, withYjs, YjsEditor, withYCursors, CursorData, useRemoteStates } from '@editablejs/plugin-yjs'
import { WebsocketProvider } from '@editablejs/yjs-websocket'

// Создаем документ yjs
const document = React.useMemo(() => new Y.Doc(), [])
// Создаем провайдера websocket
const provider = React.useMemo(() => {
  return typeof window === 'undefined' 
      ? null 
      : new WebsocketProvider(yjsServiceAddress, 'editable', document, {
          connect: false,
        })
}, [document])
// Создаем редактор
const editor = React.useMemo(() => {
  // Получаем поле содержимого из документа yjs, которое является типом XmlText
  const sharedType = document.get('content', Y.XmlText) as Y.XmlText
  let editor = withYjs(withEditable(createEditor()), sharedType, { autoConnect: false })
  if (provider) {
    // Синхронизация курсора с другими клиентами
    editor = withYCursors(editor, provider.awareness, {
      data: {
        name: 'Test User',
        color: '#f00',
      },
    })
  }
  // Запись истории
  editor = withHistory(editor)
  // Запись истории yjs
  editor = withYHistory(editor)
}, [provider])
```// Подключаемся к сервису yjs
React.useEffect(() => {
  provider?.connect();
  return () => {
    provider?.disconnect();
  };
}, [provider]);

Пользовательский плагин

Создание пользовательского плагина очень просто. Мы просто должны перехватить метод renderElement, а затем определить, является ли текущий узел тем, который нам нужен. Если да, мы будем отображать наш пользовательский компонент.

Пример пользовательского плагина:

import { Editable } from '@editablejs/editor';
import { Element, Editor } from '@editablejs/models';

// Определяем тип плагина
export interface MyPlugin extends Element {
  type: 'my-plugin';
  // ... Вы также можете определить другие свойства
}
``````javascript
export const MyPlugin = {
  // Определяем, является ли узел плагином MyPlugin
  isMyPlugin(editor: Editor, element: Element): element is MyPlugin {
    return Element.isElement(value) && element.type === 'my-plugin';
  }
};
``````markdown
const с_my_plugin = <T extends Изменяемый>(редактор: T) => {
  const { является_пустым, отрисовка_элемента } = редактор;
  // Перехват метода является_пустым. Если это узел для MyPlugin, вернуть true
  // Кроме метода является_пустым, есть также методы такие как `является_блоком` `является_строкой`, которые можно перехватывать по мере необходимости.
  редактор.является_пустым = (элемент) => {
    return MyPlugin.isMyPlugin(редактор, элемент) || является_пустым(элемент);
  };
  // Перехват метода отрисовка_элемента. Если это узел для MyPlugin, отрисовать пользовательский компонент
  // атрибуты - это атрибуты узла, нам нужно передать их в пользовательский компонент
  // дети - это дочерние узлы узла, содержащие дочерние узлы узла. Мы должны их отрисовать
  // элемент - это текущий узел, и вы можете найти свои пользовательские свойства в нем
  редактор.отрисовка_элемента = ({ атрибуты, дети, элемент }) => {
    if (MyPlugin.isMyPlugin(редактор, элемент)) {
      return <div {...атрибуты}>
        <div>My Plugin</div>
        {дети}
      </div>;
    }
    return отрисовка_элемента({ атрибуты, дети, элемент });
  };
};
```<details>
<summary>Возврат редактора</summary>
<p>````tsx
  return editor;
}

Сериализация

@editablejs/serializer предоставляет сериализатор, который может сериализовать данные редактора в форматы html, text и markdown.

Преобразователи сериализации для плагинов уже реализованы, поэтому вы можете использовать их напрямую.

Сериализация HTML

  // сериализатор HTML
import { HTMLSerializer } from '@editablejs/serializer/html';
// импортируем преобразователь сериализатора HTML плагина plugin-mark, а преобразователи других плагинов аналогичны
import { withMarkHTMLSerializerTransform } from '@editablejs/plugin-mark/serializer/html';
// используем преобразователь
HTMLSerializer.withEditor(editor, withMarkHTMLSerializerTransform, {});
// сериализуем в HTML
const html = HTMLSerializer.transformWithEditor(editor, { type: 'paragraph', children: [{ text: 'hello', bold: true }] });
// вывод: <p><strong>hello</strong></p>
Сериализация текста

// сериализатор текста
import { TextSerializer } from '@editablejs/serializer/text';
// импортируем преобразователь сериализатора текста плагина plugin-mention
import { withMentionTextSerializerTransform } from '@editablejs/plugin-mention/serializer/text';
// используем преобразователь
TextSerializer.withEditor(editor, withMentionTextSerializerTransform, {});
// сериализуем в текст
const text = TextSerializer.transformWithEditor(editor, { type: 'paragraph', children: [{ text: 'hello' }, {
  type: 'mention',
  children: [{ text: '' }],
  user: {
    name: 'User',
    id: '1',
  },
}] });
// вывод: hello @User
Сериализация Markdown

```tsx // сериализатор Markdown import { MarkdownSerializer } from '@editablejs/serializer/markdown' // импортируем преобразователь сериализатора Markdown плагина plugin-mark import { withMarkMarkdownSerializerTransform } from '@editablejs/plugin-mark/serializer/markdown' // используем преобразователь MarkdownSerializer.withEditor(editor, withMarkMarkdownSerializerTransform, {}) // сериализуем в Markdown const markdown = MarkdownSerializer.transformWithEditor(editor, { type: 'paragraph', children: [{ text: 'привет', bold: true }] }) // вывод: **привет** ```

Каждый плагин требует импорта своего собственного преобразователя сериализации, что усложняет процесс, поэтому мы предоставляем преобразователи сериализации для всех встроенных плагинов в `@editablejs/plugins`.
import { withHTMLSerializerTransform } from '@editablejs/plugins/serializer/html'
import { withTextSerializerTransform } from '@editablejs/plugins/serializer/text'
import { withMarkdownSerializerTransform, withMarkdownSerializerPlugin } from '@editablejs/plugins/serializer/markdown'

useLayoutEffect(() => {
  withMarkdownSerializerPlugin(editor)
  withTextSerializerTransform(editor)
  withHTMLSerializerTransform(editor)
  withMarkdownSerializerTransform(editor)
}, [editor])

Десериализация

@editablejs/serializer предоставляет десериализатор, который может преобразовать данные в форматах html, text и markdown в данные редактора.

Преобразования десериализации для предоставленных плагинов уже реализованы, поэтому вы можете использовать их напрямую.

Использование аналогично сериализации, за исключением того, что путь к импортированию пакета следует изменить с @editablejs/serializer на @editablejs/deserializer.

Вкладчики ✨

Добро пожаловать 🌟 Звезды и 📥 PRs! Давайте будем работать вместе над созданием лучшего редактора.### Десериализация

@editablejs/serializer предоставляет десериализатор, который может преобразовать данные в форматах html, text и markdown в данные редактора.

Преобразования десериализации для предоставленных плагинов уже реализованы, поэтому вы можете использовать их напрямую.

Использование аналогично сериализации, за исключением того, что путь к импортированию пакета следует изменить с @editablejs/serializer на @editablejs/deserializer.

Вкладчики ✨

Добро пожаловать 🌟 Звезды и 📥 PRs! Давайте будем работать вместе над созданием лучшего редактора текста!

Руководство по внесению вклада находится здесь, пожалуйста, прочтите его. Если у вас есть хороший плагин, пожалуйста, поделитесь им с нами.

Особая благодарность компании Sparticle за её поддержку и вклад в сообщество открытого программного обеспечения. sparticleНаконец, спасибо всем, кто вносил свой вклад в этот проект! (ключ эмодзи):

<!-- prettier-ignore-start -->
<!-- markdownlint-disable -->
<table>
  <tbody>
    <tr>
      <td align="center" valign="top" width="14.28%"><a href="https://claviering.github.io/"><img src="https://avatars.githubusercontent.com/u/16227832?v=4?s=100" width="100px;" alt="Kevin Lin"/><br /><sub><b>Kevin Lin</b></sub></a><br /><a href="https://github.com/big-camel/Editable/commits?author=claviering" title="Код">💻</a></td>
      <td align="center" valign="top" width="14.28%"><a href="https://yaokailun.github.io/"><img src="https://avatars.githubusercontent.com/u/11460856?v=4?s=100" width="100px;" alt="kailunyao"/><br /><sub><b>kailunyao</b></sub></a><br /><a href="https://github.com/big-camel/Editable/commits?author=YaoKaiLun" title="Код">💻</a></td>
      <td align="center" valign="top" width="14.28%"><a href="https://github.com/ren-chen2021"><img src="https://avatars.githubusercontent.com/u/88533891?v=4?s=100" width="100px;" alt="ren.chen"/><br /><sub><b>ren.chen</b></sub></a><br /><a href="https://github.com/big-camel/Editable/commits?author=ren-chen2021" title="Документация">📖</a></td>
      <td align="center" valign="top" width="14.28%"><a href="https://github.com/byoungd"><img src="https://avatars.githubusercontent.com/u/16145783?v=4?s=100" width="100px;" alt="han"/><br /><sub><b>han</b></sub></a><br /><a href="https://github.com/big-camel/Editable/commits?author=byoungd" title="Документация">📖</a></td>
    </tr>
  </tbody>
</table><!-- markdownlint-restore -->
<!-- prettier-ignore-end -->
```<!-- ALL-CONTRIBUTORS-LIST:END -->

Проект следует спецификации [all-contributors](https://github.com/all-contributors/all-contributors). Всякие виды вкладов приветствуются!

## Благодарности

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

- [Slate](https://github.com/ianstormtaylor/slate) — предоставляет поддержку для моделирования данных.
- [Yjs](https://github.com/yjs/yjs) — предоставляет базовую поддержку для CRDTs, используемых для совместной работы над редактированием.
- [React](https://github.com/facebook/react) — предоставляет поддержку для слоя представлений.
- [Zustand](https://github.com/pmndrs/zustand) — минимальный инструмент управления состоянием на фронтенде.
- [Другие зависимости](https://github.com/editablejs/editable/network/dependencies)

Мы используем следующие открытые проекты для помощи в создании лучшего процесса разработки:

- [Turborepo](https://github.com/vercel/turbo) — pnpm + turbo является отличным менеджером монорепозитория и системы сборки.

## Лицензия

Подробности см. в [LICENSE](https://github.com/editablejs/editable/blob/main/LICENSE).

Комментарии ( 0 )

Вы можете оставить комментарий после Вход в систему

Введение

Описание недоступно Развернуть Свернуть
TypeScript и 5 других языков
GPL-3.0
Отмена

Обновления

Пока нет обновлений

Участники

все

Недавние действия

Загрузить больше
Больше нет результатов для загрузки
1
https://api.gitlife.ru/oschina-mirror/editablejs-editable.git
git@api.gitlife.ru:oschina-mirror/editablejs-editable.git
oschina-mirror
editablejs-editable
editablejs-editable
main