Skip to content

Commit

Permalink
Merge pull request udecode#369 from udecode/deserialize-html-insert
Browse files Browse the repository at this point in the history
deserialize-html-insert
  • Loading branch information
zbeyens authored Dec 6, 2020
2 parents add7ec3 + f5d8cd3 commit 08a6e8e
Show file tree
Hide file tree
Showing 7 changed files with 138 additions and 48 deletions.
11 changes: 11 additions & 0 deletions packages/core/src/utils/getInlineTypes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { SlatePlugin } from '../types';

/**
* Get inline types from the plugins
*/
export const getInlineTypes = (plugins: SlatePlugin[]): string[] => {
return plugins.reduce((arr: string[], plugin) => {
const types = plugin.inlineTypes || [];
return arr.concat(types);
}, []);
};
11 changes: 11 additions & 0 deletions packages/core/src/utils/getVoidTypes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { SlatePlugin } from '../types';

/**
* Get void types from the plugins
*/
export const getVoidTypes = (plugins: SlatePlugin[]): string[] => {
return plugins.reduce((arr: string[], plugin) => {
const types = plugin.voidTypes || [];
return arr.concat(types);
}, []);
};
2 changes: 2 additions & 0 deletions packages/core/src/utils/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
export * from './decoratePlugins';
export * from './getInlineTypes';
export * from './getVoidTypes';
export * from './onDOMBeforeInputPlugins';
export * from './onKeyDownPlugins';
export * from './renderElementPlugins';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,14 @@ const output = (
</editor>
) as any;

it('should do nothing', () => {
const editor = withDeserializeHTML({ plugins: [BoldPlugin()] })(
withReact(input)
);
describe('when inserting empty html', () => {
it('should do nothing', () => {
const editor = withDeserializeHTML({ plugins: [BoldPlugin()] })(
withReact(input)
);

editor.insertData(data as any);
editor.insertData(data as any);

expect(input.children).toEqual(output.children);
expect(input.children).toEqual(output.children);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -6,35 +6,68 @@ import { jsx } from '../../../../__test-utils__/jsx';
import { HeadingPlugin } from '../../../../elements/heading/index';
import { withDeserializeHTML } from '../../index';

const input = ((
<editor>
<hp>
test
<cursor />
</hp>
</editor>
) as any) as Editor;

// noinspection CheckTagEmptyBody
const data = {
getData: () => '<html><body><h1>inserted</h1></body></html>',
};

const output = (
<editor>
<hh1>
testinserted
<cursor />
</hh1>
</editor>
) as any;
describe('when inserting html', () => {
describe('when inserting h1 inside p (not empty)', () => {
it('should just insert h1 text inside p', () => {
const input = ((
<editor>
<hp>
test
<cursor />
</hp>
</editor>
) as any) as Editor;

const expected = (
<editor>
<hp>
testinserted
<cursor />
</hp>
</editor>
) as any;

const editor = withDeserializeHTML({ plugins: [HeadingPlugin()] })(
withReact(input)
);

editor.insertData(data as any);

expect(input.children).toEqual(expected.children);
});
});

describe('when inserting h1 inside an empty p', () => {
it('should set p type to h1 and insert h1 text', () => {
const input = ((
<editor>
<hp>
<cursor />
</hp>
</editor>
) as any) as Editor;

const expected = (
<editor>
<hh1>
inserted
<cursor />
</hh1>
</editor>
) as any;

it('should do nothing', () => {
const editor = withDeserializeHTML({ plugins: [HeadingPlugin()] })(
withReact(input)
);
const editor = withDeserializeHTML({ plugins: [HeadingPlugin()] })(
withReact(input)
);

editor.insertData(data as any);
editor.insertData(data as any);

expect(input.children).toEqual(output.children);
expect(input.children).toEqual(expected.children);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,14 @@ const output = (
</editor>
) as any;

it('should do nothing', () => {
const editor = withInlineVoid({})(
withDeserializeHTML({ plugins: [MediaEmbedPlugin()] })(withReact(input))
);
describe('when inserting an iframe', () => {
it('should do nothing', () => {
const editor = withInlineVoid({})(
withDeserializeHTML({ plugins: [MediaEmbedPlugin()] })(withReact(input))
);

editor.insertData(data as any);
editor.insertData(data as any);

expect(input.children).toEqual(output.children);
expect(input.children).toEqual(output.children);
});
});
Original file line number Diff line number Diff line change
@@ -1,43 +1,72 @@
import { SlatePlugin } from '@udecode/slate-plugins-core';
import { getInlineTypes, SlatePlugin } from '@udecode/slate-plugins-core';
import { Transforms } from 'slate';
import { ReactEditor } from 'slate-react';
import { isBlockAboveEmpty } from '../../common/queries/isBlockAboveEmpty';
import { SlateDocumentFragment } from '../../common/types/SlateDocument.types';
import { deserializeHTMLToDocumentFragment } from './utils';

export interface WithDeserializeHTMLOptions {
plugins?: SlatePlugin[];

/**
* Function called before inserting the deserialized html.
* Default: if the block above is empty and the first fragment node type is not inline,
* set the selected node type to the first fragment node type.
*/
preInsert?: (fragment: SlateDocumentFragment) => SlateDocumentFragment;

/**
* Function called to insert the deserialized html.
* Default: {@link Transforms.insertFragment}.
*/
insert?: (fragment: SlateDocumentFragment) => void;
}

/**
* Enables support for deserializing inserted content from HTML format to Slate format.
*/
export const withDeserializeHTML = ({
plugins = [],
...options
}: WithDeserializeHTMLOptions = {}) => <T extends ReactEditor>(editor: T) => {
const { insertData } = editor;

const inlineTypes = plugins.reduce((arr: string[], plugin) => {
const types = plugin.inlineTypes || [];
return arr.concat(types);
}, []);
const {
preInsert = (fragment) => {
const inlineTypes = getInlineTypes(plugins);

const firstNodeType = fragment[0].type as string | undefined;

// replace the selected node type by the first block type
if (
isBlockAboveEmpty(editor) &&
firstNodeType &&
!inlineTypes.includes(firstNodeType)
) {
Transforms.setNodes(editor, { type: fragment[0].type });
}

return fragment;
},

insert = (fragment) => {
Transforms.insertFragment(editor, fragment);
},
} = options;

editor.insertData = (data: DataTransfer) => {
const html = data.getData('text/html');

if (html) {
const { body } = new DOMParser().parseFromString(html, 'text/html');
const fragment = deserializeHTMLToDocumentFragment({
let fragment = deserializeHTMLToDocumentFragment({
plugins,
element: body,
});

const firstNodeType = fragment[0].type as string | undefined;

// replace the selected node type by the first block type
if (firstNodeType && !inlineTypes.includes(firstNodeType)) {
Transforms.setNodes(editor, { type: fragment[0].type });
}
fragment = preInsert(fragment);

Transforms.insertFragment(editor, fragment);
insert(fragment);
return;
}

Expand Down

0 comments on commit 08a6e8e

Please sign in to comment.