## EditorEventsContext

### `withEditorContext`

`withEditorContext` is a High-Order component, allowing to trigger `publish` event when content editing is done.

Return value is a promise with finalized content.

```ts
editorEvents: {
  publish: () => Promise<RichContent>;
}
```

#### Usage Example

```tsx widget
import { fromPlainText, RicosEditor, withEditorContext } from '@wix/ricos';
import React from 'react';

export default withEditorContext(({ editorEvents }) => {
  const [logs, setLogs] = React.useState<string[]>([]);
  const onPublish = async () => {
    // @essential-start
    const content = await editorEvents.publish();
    // @essential-end
    setLogs(prevState => [JSON.stringify(content), ...prevState]);
  };
  return (
    <>
      <RicosEditor content={fromPlainText('Sample editor content')} />
      <p style={{ margin: '20px 0' }}>
        <button
          style={{ background: '#eaeaea', padding: '4px', borderRadius: '4px' }}
          type="button"
          onClick={onPublish}
        >
          Publish
        </button>
      </p>
      <ul>
        {logs.map((log, index) => (
          <li
            key={index}
            style={{
              marginBottom: '15px',
              fontSize: '12px',
              background: '#f1f1f1',
              padding: '4px',
              borderRadius: '4px',
            }}
          >
            <code>{log}</code>
          </li>
        ))}
      </ul>
    </>
  );
});
```

## Content API

### AppEmbed Data Extractor

The AppEmbed Data Extractor provides access to AppEmbed related content regardless of its nesting level.

The `extractAppEmbedData` API accepts `RichContent` object, and exposes the available AppEmbed data as array:

```ts
 extractAppEmbedData = (content: RichContent) => {
  type: PRODUCT | EVENT | BOOKING;
  id?: string;
  url?: string;

  bookingData?: {
    durations?: string;
  }

  eventData?: {
    scheduling?: string;
    location?: string;
  }
 }[]
```

### Content Applications

While [Content Builder](#content-builder), [Content Extractor](#content-extractor) and [Content Modifier](#content-modifier) APIs are very flexible, yet they also too generic.

The `content-application` is a collection of more specific APIs which aim to reduce boilerplate for consumer.

##### `getText`

Extracts textual content of Headers & Paragraphs from a given content

```ts
import { getText } from '@wix/ricos';

const text = getText(content); // input content

console.log(text);
```

##### Output

`["This is an example content, with header 3 ", "and header 4"]`

##### `getPluginData`

Extracts data of a plugin from a given content.

###### Example

```ts
import { getPluginData } from '@wix/ricos';
import { content } from 'somewhere';

const pluginData = getPluginData(content, 'IMAGE');
console.log(pluginData);
```

###### Output

```ts
[
  {
    image: { ... },
    altText: 'alt1',
    caption: 'Cool image',
    // ...
  },
  {
    image: { ... },
    altText: 'alt2',
    caption: 'Cool image 2',
    // ...
  },
];
```

### Content Extractor

#### extract API

The Content Extractor provides access to content nodes regardless of their nesting level.

The `extract` API accepts RichContent nodes array, and exposes `Extractor<T>` that allows chaining:

```ts
interface Extractor<DT> {
  filter: (predicate: (data: DT) => boolean) => Extractor<DT>;
  map: <MT>(mapper: (data: DT) => MT) => Extractor<MT>;
  chain: <CT>(mapper: (data: DT) => Extractor<CT>) => Extractor<CT>[];
  get: () => DT[];
}
```

#### Examples

1. Extract node keys of images with invalid (empty) alt text:

```ts
import { extract } from '@wix/ricos';
...
const keys = extract(richContent.nodes)
  .filter(({ imageData }) => !!imageData && !imageData.altText)
  .map(({ key }) => key)
  .get();
```

2. Extract first image URL:

```ts
const url = extract(richContent.nodes)
  .map(({ imageData }) => imageData?.image?.src?.url || imageData?.image?.src?.custom)
  .get()[0];
```

3. Extract total image count:

```ts
const imageCount = extract(richContent.nodes)
  .filter(({ type }) => type === Node_Type.IMAGE)
  .get().length;
const galleryImageCount = extract(richContent.nodes)
  .map(({ galleryData }) => galleryData?.items || [])
  .map(
    items =>
      items.filter(({ metadata }) => (!!metadata && metadata?.type === 'image') || !metadata?.type)
        .length
  )
  .get()
  .reduce((sum, count) => sum + count, 0);
```

4. Extract list item texts, grouped by lists:

```ts
const listTexts = extract(richContent.nodes)
  .filter(({ type }) => type === Node_Type.BULLETED_LIST || type === Node_Type.ORDERED_LIST)
  .chain(n => extract(n).map(({ textData }) => textData?.text || ''))
  .map(extractor => extractor.get());
```

Note the recursive `extract` and `chain` combination.

### Content Modifier

#### modify API

The Content Modifier provides access to content nodes regardless of their nesting level.

The `modify` API accepts RichContent object, and exposes `Modifier`:

```ts
interface Modifier {
  filter: (pred: (node: Node) => boolean) => Modifier;
  set: (setter: (node: Node) => Node | Node[]) => RichContent;
}
```

#### Examples

1. Modify image nodes - ensure altText is not empty:

```ts
import { modify } from '@wix/ricos';
import { content } from 'your-own-content-from-somewhere';
...
const newContent = modify(content)
  .filter(n => n.type === Node_Type.IMAGE)
  .set(n => ({
    ...n,
    imageData: {
      ...n.imageData,
      altText: n.imageData?.altText || 'Custom alt text',
    },
  }));
```

2. Multiply dividers:

```ts
const newContent = modify(content)
  .filter(({ type }) => type === Node_Type.DIVIDER)
  .set(n => [n, n]);
```

### Content Builder

This doc focuses on various API types, and not the exhaustive list of APIs. Examples based on Image demonstrate the "block" plugin related APIs like Image, Divider, etc -- all the plugins that require a separate block (unlike "inline" plugins).
Examples based on Paragraph demonstrate text related APIs.

The Content Builder is stateless and immutable. This means, each API accepts RichContent to process, and returns a new copy of RichContent.

All the building blocks (e.g. ImageData objects) are defined in `ricos-schema` package.

#### Instantiation

The ContentBuilder depends on key generation utility `() => string` which provides unique node keys. The `wix-rich-content-common` package exports such utility.

```ts
import {
  generateKey,
  setupContentBuilder,
} from '@wix/ricos';

const api = setupContentBuilder(generateKey);
```

#### Append / insert new content

The _add_ methods add new content nodes. The node position is defined either by index (i.e. sequential number inside nodes collection), or by before/after key (adds the content relatively to a given existing node addressed by key). If no index/before/after keys provided, the new content appended to the end.

```ts
addImage({
  data: ImageData,
  index?: number,
  before?: string,
  after?: string,
  content: RichContent
}): RichContent;

addParagraph({
  text?: string | TextData | (string | TextData)[],
  data?: ParagraphData,
  index?: number,
  before?: string,
  after?: string
}): RichContent;

```

##### Adding bullet/ordered list

The list addition API is a bit different. It accepts _items_ parameter which contains textual data, and an optional _data_ parameter.

This parameter determines the default data for paragraph that contains text. At the moment, lists support only paragraphs as list item containers.

```ts
addBulletList({
  items: string | TextData | ListItemData | (string | TextData | ListItemData)[],
  data?: ParagraphData,
  index?: number,
  before?: string,
  after?: string
}): RichContent;

```

#### Examples

##### Image

```ts
const api = setupContentBuilder(generateKey);
const imageData: ImageData = {
  containerData: {
    width: { size: PluginContainerData_Width_Type.SMALL },
    alignment: PluginContainerData_Alignment.CENTER,
  },
  image: {...},
};
const content = api.addImage({ data: imageData, content: { nodes: [] } });
```

returns the following content:

```ts
{
  nodes: [
    {
      type: Node_Type.IMAGE,
      imageData,
      nodes: [],
      key: 'foo',
    },
  ],
}
```

##### List

```ts
const paragraphData: ParagraphData = {
  textStyle: {
    textAlignment: TextStyle_TextAlignment.RIGHT,
  },
};

const content = api.addBulletList({
  items: ['item1', 'item2'],
  data: paragraphData,
  content: { nodes: [] },
});
```

results in:

```ts
{
  nodes: [
    {
      type: Node_Type.BULLETED_LIST,
      key: 'foo',
      nodes: [
        {
          type: Node_Type.LIST_ITEM,
          key: 'foo',
          nodes: [
            {
              type: Node_Type.PARAGRAPH,
              key: 'foo',
              paragraphData: {
                textStyle: {
                  textAlignment: TextStyle_TextAlignment.RIGHT,
                },
              },
              nodes: [
                {
                  key: 'foo',
                  type: Node_Type.TEXT,
                  textData: {
                    text: 'item1',
                    decorations: [],
                  },
                  nodes: [],
                },
              ],
            },
          ],
        },
        {
          type: Node_Type.LIST_ITEM,
          key: 'foo',
          nodes: [
            {
              type: Node_Type.PARAGRAPH,
              key: 'foo',
              paragraphData: {
                textStyle: {
                  textAlignment: TextStyle_TextAlignment.RIGHT,
                },
              },
              nodes: [
                {
                  key: 'foo',
                  type: Node_Type.TEXT,
                  textData: {
                    text: 'item2',
                    decorations: [],
                  },
                  nodes: [],
                },
              ],
            },
          ],
        },
      ],
    },
  ],
}
```

### Content Converters

#### Draft.js

Ricos is in the preparation phase of migrating from a [draft-js](https://draftjs.org) based content to our new [Ricos](https://github.com/wix/ricos/tree/master/packages/wix-ricos-schema) schema. To support this we provide conversion functions to & from draft.

```tsx
fromDraft: (draftJSON: DraftContent, opts: FromDraftOptions = {}) => RichContent;
toDraft: (content: RichContent) => DraftContent;
ensureRicosContent: (content: RichContent | DraftContent, opts?: FromDraftOptions = {}) =>
  RichContent;
ensureDraftContent: (content: RichContent | DraftContent) => DraftContent;
```

##### FromDraftOptions

By default, the `fromDraft` API throws error when it encounters invalid `DraftContent`. Sometimes, this behavior is not desired.

The `opts: FromDraftOptions` parameter allows to alter it, by setting `{ ignoreUnsupportedValues: true }`. In this case, any invalid values are gracefully ignored.

##### Example

```tsx
import {
  type DraftContent,
  fromDraft,
  toDraft,
  RichContent,
  RicosEditor,
} from '@wix/ricos';
import ricosContent from './ricosContent.json';

const draftContent: DraftContent = toDraft(ricosContent);

const onChange = (content: DraftContent) => publishContent(fromDraft(content));

<RicosEditor content={draftContent} onChange={onChange} />;
```

#### Plain Text

Ricos is a rich text & rich content editor. The following functions allow converting between plain text and rich content.

##### Plain text to rich content

```tsx
fromPlainText: (text: string) => RichContent;
```

##### Example

```tsx
import {
  type DraftContent,
  fromPlainText,
  toDraft,
  RichContent,
  RicosEditor,
} from '@wix/ricos';

const TEXT = 'Hello there!';

const content: RichContent = fromPlainText(TEXT);
const draftContent: DraftContent = toDraft(content);

<RicosEditor content={draftContent} />;
```

##### Rich content to plain text

```tsx
toPlainText: (
  content: RichContent,
  options?: {
    urlShortener?: (url: string) => Promise<string>;
    getVideoUrl?: (fileId: string) => Promise<string>;
  }
) => Promise<string>;
```

###### urlShortener

This function will be called on image source URLs and can be used with URL shortener services.

###### getVideoUrl

Used to resolve a `VideoSource` object into an absolute URL.<br>
Defaults to `src => https://video.wixstatic.com/${src.pathname}`

##### Example

```tsx
import { toPlainText } from '@wix/ricos';
import ricosContent from './ricosContent.json';

const text: string = await toPlainText(ricosContent);

console.log(text);
```

#### HTML

The following functions allow converting rich content to and from HTML.

Supporting:

- Headings
- Bold
- Italic
- Links
- Lists
- Line break
- Emojis

##### HTML to rich content

###### Overview

The `fromRichTextHtml` function is used to convert HTML to a defined schema format. It processes the HTML data, applies a set of conversion rules, and provides an output in the `RichContent` format.
Interceptor function allows users to modify the conversion perocess as per their requirements.

###### API

```tsx
fromRichTextHtml: (
  html: string,
  interceptorFn: (context: { contentNode: ContentNode; context: Context; result: Node[] }) => Node[]
) => RichContent;
```

| interceptorFn.context | description                                                                                                                                                                                                                  |
| --------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| contentNode           | The contentNode parameter refers to the currently processed node in the HTML document. It contains the information of the node in the HTML that is being converted to the schema format.                                     |
| context               | The context parameter offers detailed information about the HTML document and the capability to traverse the HTML fragments. It gives access to the HTML being processed, allowing for more complex or global modifications. |
| result                | The result parameter represents the outcome of the conversion if no interception was to occur.                                                                                                                               |

##### Examples

###### 1. Basic example

```tsx
import {
  fromRichTextHtml,
  RicosEditor,
} from '@wix/ricos';

const html = '<p><strong>Bold</strong> <em>italic</em> <u>underline</u></p>';

const content = fromRichTextHtml(html);

<RicosEditor content={content} />;
```

###### 2. Traversing unrecognized HTML fragments

When encountering unrecognized or custom HTML tags for which we don't have default rules, the default behavior
is to stop traversing such HTML nodes. However, the interceptor can leverage the context to traverse and process
the content within these tags.

```ts
import {
  fromRichTextHtml,
  RichContent,
} from '@wix/ricos';

const html = '<div><p>some text</p></div>';

const content = fromRichTextHtml(html, ({ contentNode, result, context }) => {
  if (contentNode.nodeName === 'div') {
    return context.visit(contentNode);
  }

  return result;
});
```

###### 3. Overriding HTML tag

The interceptor can convert unrecognized HTML tags into recognizable ones, such as converting an unrecognized tag into a paragraph node.

```ts
import {
  createParagraphNode,
  fromRichTextHtml,
  RichContent,
} from '@wix/ricos';

const html = '<unrecognized>text</unrecognized>';

const content = fromRichTextHtml(html, ({ contentNode, result, context }) => {
  if (contentNode.nodeName === 'unrecognized') {
    return [createParagraphNode(context.visit(contentNode))];
  }

  return result;
});
```

##### Rich content to HTML

###### Overview

The `toHtml` function is a converter used to transform data from a defined schema
format back to HTML. It processes the `RichContent` data and translates it into HTML
according to a predefined set of conversion rules.

###### API

```tsx
toHtml: (
  content: RichContent,
  interceptorFn: (context: { node: Node; result: string | string[] }) => string | string[]
) => string;
```

| interceptorFn.context | description                                                                                                                                        |
| --------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
| node                  | The node parameter is the current node being processed in the schema data. It provides information about the node that is being converted to HTML. |
| result                | The result parameter represents the outcome of the conversion if no interception was to occur.                                                     |

##### Examples

###### 1. Basic example

```tsx
import {
  RichContent,
  toHtml,
} from '@wix/ricos';
import ricosContent from './ricosContent.json';

const html = toHtml(RichContent.fromJSON(ricosContent));
```

###### 2. Capturing and modifying the conversion result

The interceptor can be used to modify the HTML representation of specific node types. For instance,
it can replace the default `<p>` and `<text>` tags with `<p-override>` and text-override respectively.

```ts
import {
  type InterceptorToHtml,
  toHtml,
} from '@wix/ricos';

const paragraphContent: RichContent = {
  // content which has paragraph with text value "some-text"
  ...
};

const inerceptor: InterceptorToHtml = ({ node, result }) => {
  if (node.type === Node_Type.PARAGRAPH) {
    return `<p-override>${toHtml(node.nodes, interceptor)}</p-override>`;
  }

  if (node.type === Node_Type.TEXT) {
    return `text-override`;
  }

  return result;
}

const html = toHtml(paragraphContent, interceptor);

console.log(html); // <p-override>text-override</p-override>
```

### Extract Rich Content Media

#### Extracted Data

The extracted data will be an array of objects, each object representing a media item. There are two types of media items that can be extracted:

##### ImageMedia

```ts
import { type ImageMedia } from '@wix/ricos';
```

| property  | optional | info                                                            |
| --------- | -------- | --------------------------------------------------------------- |
| `type`    | No       | The type of media item. For images, this will always be `image` |
| `url`     | Yes      | The URL of the image                                            |
| `alt`     | Yes      | The alt text of the image                                       |
| `caption` | Yes      | The title of the image                                          |
| `width`   | Yes      | The width of the image                                          |
| `height`  | Yes      | The height of the image                                         |

##### VideoMedia

```ts
import { type VideoMedia } from '@wix/ricos';
```

| property       | optional | info                                                            |
| -------------- | -------- | --------------------------------------------------------------- |
| `type`         | No       | The type of media item. For videos, this will always be `video` |
| `url`          | Yes      | The URL of the video                                            |
| `thumbnailUrl` | Yes      | The URL of the video thumbnail                                  |
| `uploadDate`   | Yes      | The date the video was uploaded                                 |
| `width`        | Yes      | The width of the video                                          |
| `height`       | Yes      | The height of the video                                         |
| `title`        | Yes      | The title of the video                                          |
| `duration`     | Yes      | The duration of the video in seconds                            |

###### Example

```ts
[
  {
    type: 'image',
    url: 'https://example.com/image.jpg',
    alt: 'An image',
    caption: 'A beautiful image',
    width: 1920,
    height: 1080,
  },
  {
    type: 'video',
    url: 'https://example.com/video.mp4',
    thumbnailUrl: 'https://example.com/video-thumbnail.jpg',
    uploadDate: '2021-01-01T00:00:00Z',
    width: 1920,
    height: 1080,
    title: 'A video',
    duration: 60,
  },
];
```

#### getMediaData

##### Parameters

| name                | type                | required | description                                                                    |
| ------------------- | ------------------- | -------- | ------------------------------------------------------------------------------ |
| `content`           | `RichContent`       | Yes      | The rich content from which to extract media data                              |
| `mediaUrlResolvers` | `MediaUrlResolvers` | No       | An object with functions to resolve media IDs to URLs. See below for more info |

```ts
type MediaUrlResolvers = {
  image?: (id: string) => string;
  video?: (id: string) => string;
  thumbnail?: (id: string) => string;
};

type GetMediaData = (
  content: RichContent,
  mediaUrlResolvers?: MediaUrlResolvers
) => (ImageMedia | VideoMedia)[];
```

##### Code example

```ts
import { getMediaData } from '@wix/ricos';

const content = {
  nodes: [
    {
      type: 'PARAGRAPH',
      id: 'aho6i53',
      nodes: [],
      paragraphData: {},
    },
    {
      type: 'IMAGE',
      id: 'w1b3852',
      nodes: [],
      imageData: {
        containerData: {
          width: {
            size: 'CONTENT',
          },
          alignment: 'CENTER',
          textWrap: true,
        },
        image: {
          src: {
            id: '8bb438_94a4e8c055d84430af593cb3d31fd67a.jpg',
          },
          width: 3370,
          height: 2247,
        },
        altText: 'Text',
        caption: 'Capt',
      },
    },
    {
      type: 'PARAGRAPH',
      id: '2ocq654',
      nodes: [],
      paragraphData: {},
    },
  ],
};

const mediaData = getMediaData(content);

console.log(mediaData);

// [
//   {
//     type: 'image',
//     url: 'https://static.wixstatic.com/media/8bb438_94a4e8c055d84430af593cb3d31fd67a.jpg',
//     alt: 'Text',
//     caption: 'Capt',
//     width: 3370,
//     height: 2247,
//   },
// ];
```

### Metadata Utilities

##### `ensureContentId`

Ensure that a given `RichContent` object contains an id by generating one (if not exist) or leaving it as is (if it does).

If no content is provided, an empty content with a new id is generated.

###### Example

```ts
import { ensureContentId } from '@wix/ricos';
import { someContent } from 'somwhere'; // content without ID:

const content1: RichContent = ensureContentId();
// => Empty content with ID

const content2: RichContent = ensureContentId(someContent);
// = `someContent` with newly generated ID

const content3: RichContent = ensureContentId(content2);
// = `content2` (identical)
```

##### `createEmptyContent`

Creates an empty, valid `RichContent` object

###### Example

```ts
import { createEmptyContent } from '@wix/ricos';

const content: RichContent = createEmptyContent();
```

### Translatables API

`Translation API` provides methods for translatable fragment extraction and replacement within a `RichContent` instance.

`toTanslatables: (content: RichContent) => Translatable[]`

`translateContent: (content: RichContent, translatables: Translatable[]) => RichContent`

where `Translatable` consists of `text` and some metadata fields required for translation fragment replacement.

For the purpose of translating content, there are 3 stages that you should follow:

1. **Extraction**: getting a `Translatable[]` from a `RichContent` object
2. **Translation**: modifying the `Translatable[]` with translated values
3. **Application**: applying the modified `Translatable[]` array into a `RichContent` object

Ricos does not provide a Translation service. Hence, stage 2 is up to your implementation

##### Example

```ts
import { translateToSpanish } from '3rd-party-translation-service';
import { toTranslatables, translateContent } from '@wix/ricos';

function translate(translatables: Translatable[]) {
  return translatables.map(t => ({ ...t, text: translateToSpanish(t.text) }));
}

const english: RichContent = { ... };

const englishChunks: Translatable[] = toTranslatables(english);

const spanishChunks = translate(englishChunks);

const spanish: RichContent = translateContent(english, spanishChunks);
```

### Truncate Content

```tsx
truncateContent: (
  content: RichContent | DraftContent,
  opts?: { wordsCount?: number; maxPlugins?: number; blocksCount?: number }
) => {
  content: RichContent | DraftContent;
  isTruncated: boolean;
};
```

Truncate existing content according to number of blocks, and optional words count and/or plugin count.

The content will be truncated by the first limit reached.
For example passing `blocksCount=5`, `wordsCount=100`, `maxPlugins=3` will yield a content that is not more than 5 blocks, shorter than 100 words and have no more than 3 plugin blocks

#### Example Usage

```js
import { truncateContent } from '@wix/ricos';
const {content} = truncateContent(fullContent, 4)
<RicosViewer content={content} ... />
```

### MediaUrlResolvers

The `mediaUrlResolvers` parameter is an object with three optional functions:

- `image` - A function that resolves an image ID to a URL
- `video` - A function that resolves a video ID to a URL
- `thumbnail` - A function that resolves a thumbnail ID to a URL

These functions are used to resolve the IDs of media items to URLs. If a function is not provided, the default ID resolver will be used.

#### Example

```ts
const mediaUrlResolvers = {
  image: id => `https://example.com/images/${id}`,
  video: id => `https://example.com/videos/${id}`,
  thumbnail: id => `https://example.com/thumbnails/${id}`,
};

const mediaData = getMediaData(content, mediaUrlResolvers);

console.log(mediaData);

// [
//   {
//     type: 'image',
//     url: 'https://example.com/images/8bb438_94a4e8c055d84430af593cb3d31fd67a.jpg',
//     alt: 'Text',
//     caption: 'Capt',
//     width: 3370,
//     height: 2247,
//   },
// ];
```
