Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add removeEmpty option to insertNodes #2719

Merged
merged 3 commits into from
Oct 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/tender-socks-dress.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@udecode/plate-media': patch
---

Pass additional options given to insertMedia to insertImage or insertMediaEmbed
5 changes: 5 additions & 0 deletions .changeset/tiny-carpets-help.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@udecode/slate': minor
---

Add `removeEmpty: boolean | QueryNodeOptions` option to insertNodes
17 changes: 12 additions & 5 deletions packages/media/src/media/insertMedia.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import { getPluginType, PlateEditor, Value } from '@udecode/plate-common';
import {
getPluginType,
InsertNodesOptions,
PlateEditor,
Value,
} from '@udecode/plate-common';

import {
ELEMENT_IMAGE,
Expand All @@ -7,7 +12,8 @@ import {
insertMediaEmbed,
} from '..';

export interface InsertMediaOptions {
export interface InsertMediaOptions<V extends Value>
extends InsertNodesOptions<V> {
/**
* Default onClick is getting the image url by calling this promise before inserting the image.
*/
Expand All @@ -21,7 +27,8 @@ export const insertMedia = async <V extends Value>(
{
getUrl,
type = getPluginType(editor, ELEMENT_IMAGE),
}: InsertMediaOptions = {}
...options
}: InsertMediaOptions<V> = {}
) => {
const url = getUrl
? await getUrl()
Expand All @@ -33,8 +40,8 @@ export const insertMedia = async <V extends Value>(
if (!url) return;

if (type === getPluginType(editor, ELEMENT_IMAGE)) {
insertImage(editor, url);
insertImage(editor, url, options);
} else {
insertMediaEmbed(editor, { url });
insertMediaEmbed(editor, { url }, options);
}
};
75 changes: 60 additions & 15 deletions packages/slate/src/interfaces/transforms/insertNodes.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,29 @@
import { Modify } from '@udecode/utils';
import { Path, Transforms } from 'slate';
import { Path, removeNodes, Transforms, withoutNormalizing } from 'slate';

import { QueryNodeOptions } from '../../types';
import { NodeMatchOption } from '../../types/NodeMatchOption';
import { getAboveNode, getEndPoint } from '../editor';
import { queryNode } from '../../utils';
import { getAboveNode, getEndPoint, isInline } from '../editor';
import { TEditor, Value } from '../editor/TEditor';
import { EElementOrText } from '../element/TElement';
import { getNodeString, TDescendant } from '../node';

export type InsertNodesOptions<V extends Value = Value> = Modify<
NonNullable<Parameters<typeof Transforms.insertNodes>[2]>,
NodeMatchOption<V>
> & {
/**
* Remove the currect block if empty before inserting. Only applies to
* paragraphs by default, but can be customized by passing a
* QueryNodeOptions object.
*/
removeEmpty?: boolean | QueryNodeOptions;

/**
* Insert the nodes after the currect block. Does not apply if the
* removeEmpty option caused the current block to be removed.
*/
nextBlock?: boolean;
};

Expand All @@ -22,22 +36,53 @@ export const insertNodes = <
>(
editor: TEditor<V>,
nodes: N | N[],
options?: InsertNodesOptions<V>
{ nextBlock, removeEmpty, ...options }: InsertNodesOptions<V> = {}
) => {
if (options?.nextBlock) {
const at = options?.at || editor.selection;
if (at) {
const endPoint = getEndPoint(editor, at);
const blockEntry = getAboveNode(editor, {
at: endPoint,
block: true,
});
withoutNormalizing(editor as any, () => {
if (removeEmpty) {
const blockEntry = getAboveNode(editor, { at: options.at });

if (blockEntry) {
const nextPath = Path.next(blockEntry[1]);
options.at = nextPath;
const queryNodeOptions: QueryNodeOptions =
removeEmpty === true
? {
allow: ['p'],
}
: removeEmpty;

const { filter } = queryNodeOptions;

queryNodeOptions.filter = ([node, path]) => {
if (getNodeString(node)) return false;
const children = node.children as TDescendant[];
if (children.some((n) => isInline(editor, n))) return false;
return !filter || filter([node, path]);
};

if (queryNode(blockEntry, queryNodeOptions)) {
removeNodes(editor as any, { at: blockEntry[1] });
nextBlock = false;
}
}
}

if (nextBlock) {
const { at = editor.selection } = options;

if (at) {
const endPoint = getEndPoint(editor, at);

const blockEntry = getAboveNode(editor, {
at: endPoint,
block: true,
});

if (blockEntry) {
options.at = Path.next(blockEntry[1]);
}
}
}
}

Transforms.insertNodes(editor as any, nodes, options as any);
Transforms.insertNodes(editor as any, nodes, options as any);
});
};
Loading