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

Fix/mark deletion block start #2142

Merged
merged 8 commits into from
Jan 24, 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
7 changes: 7 additions & 0 deletions .changeset/hungry-radios-fly.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@udecode/plate-core": minor
---

- New core plugin: `editorProtocol` following https://github.com/udecode/editor-protocol core specs
- Fixes https://github.com/udecode/editor-protocol/issues/81
- Slate types: replaced editor mark types by `string`. Derived types from `EMarks<V>` are often unusable.
5 changes: 5 additions & 0 deletions .changeset/hungry-radios-flya.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@udecode/plate-table": patch
---

fix: pass `deleteFragment` params
1 change: 1 addition & 0 deletions packages/core/src/hooks/plate/usePlateEffects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export type UsePlateEffectsProps<
| {
deserializeAst?: boolean;
deserializeHtml?: boolean;
editorProtocol?: boolean;
eventEditor?: boolean;
inlineVoid?: boolean;
insertData?: boolean;
Expand Down
99 changes: 99 additions & 0 deletions packages/core/src/plugins/createEditorProtocolPlugin.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
/** @jsx jsx */

import { jsx } from '@udecode/plate-test-utils';
import { createPlateEditor } from '../utils/index';

jsx;

// https://github.com/udecode/editor-protocol/issues/81
describe('delete marked text at block start', () => {
it('delete backward in a marked text at offset 1, it should remove the mark', () => {
const input = (
<editor>
<hp>
<htext bold>
a<cursor />
bc
</htext>
</hp>
</editor>
) as any;

const output = (
<editor>
<hp>
a<htext bold>bc</htext>
</hp>
</editor>
) as any;

const editor = createPlateEditor({
editor: input,
});

editor.deleteBackward('character');
editor.insertText('a');

expect(editor.children).toEqual(output.children);
});

it('when delete forward at start of a marked block, it should remove the mark', () => {
const input = (
<editor>
<hp>
<htext bold>
<cursor />
abc
</htext>
</hp>
</editor>
) as any;

const output = (
<editor>
<hp>
a<htext bold>bc</htext>
</hp>
</editor>
) as any;

const editor = createPlateEditor({
editor: input,
});

editor.deleteForward('character');
editor.insertText('a');

expect(editor.children).toEqual(output.children);
});

it('when delete fragment with anchor or focus at start of a marked block, should remove the mark', () => {
const input = (
<editor>
<hp>
<htext bold>
<anchor />b<focus />c
</htext>
</hp>
</editor>
) as any;

const output = (
<editor>
<hp>
b<cursor />
<htext bold>c</htext>
</hp>
</editor>
) as any;

const editor = createPlateEditor({
editor: input,
});

editor.deleteBackward('character');
editor.insertText('b');

expect(editor.children).toEqual(output.children);
});
});
47 changes: 47 additions & 0 deletions packages/core/src/plugins/createEditorProtocolPlugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { isSelectionAtBlockStart } from '../queries/index';
import { Value } from '../slate/index';
import { removeSelectionMark } from '../transforms/index';
import { PlateEditor } from '../types/index';
import { createPluginFactory } from '../utils/plate/createPluginFactory';

export const KEY_EDITOR_PROTOCOL = 'editorProtocol';

export const withEditorProtocol = <
V extends Value = Value,
E extends PlateEditor<V> = PlateEditor<V>
>(
editor: E
) => {
const { deleteBackward, deleteForward, deleteFragment } = editor;

const resetMarks = () => {
if (isSelectionAtBlockStart(editor)) {
removeSelectionMark(editor);
}
};

editor.deleteBackward = (unit) => {
deleteBackward(unit);

resetMarks();
};

editor.deleteForward = (unit) => {
deleteForward(unit);

resetMarks();
};

editor.deleteFragment = (direction) => {
deleteFragment(direction);

resetMarks();
};

return editor;
};

export const createEditorProtocolPlugin = createPluginFactory({
key: KEY_EDITOR_PROTOCOL,
withOverrides: withEditorProtocol,
});
1 change: 1 addition & 0 deletions packages/core/src/plugins/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
*/

export * from './createDeserializeAstPlugin';
export * from './createEditorProtocolPlugin';
export * from './createEventEditorPlugin';
export * from './createHistoryPlugin';
export * from './createInlineVoidPlugin';
Expand Down
2 changes: 2 additions & 0 deletions packages/core/src/plugins/withPlate.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { getPlugin } from '../utils/plate/getPlugin';
import { createTEditor } from '../utils/slate/createTEditor';
import { KEY_DESERIALIZE_HTML } from './html-deserializer/createDeserializeHtmlPlugin';
import { KEY_DESERIALIZE_AST } from './createDeserializeAstPlugin';
import { KEY_EDITOR_PROTOCOL } from './createEditorProtocolPlugin';
import { KEY_EVENT_EDITOR } from './createEventEditorPlugin';
import { KEY_INLINE_VOID } from './createInlineVoidPlugin';
import { KEY_INSERT_DATA } from './createInsertDataPlugin';
Expand All @@ -22,6 +23,7 @@ const coreKeys = [
KEY_PREV_SELECTION,
KEY_DESERIALIZE_HTML,
KEY_DESERIALIZE_AST,
KEY_EDITOR_PROTOCOL,
];

describe('withPlate', () => {
Expand Down
10 changes: 3 additions & 7 deletions packages/core/src/queries/getMark.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,13 @@
import { getMarks } from '../slate/editor/getMarks';
import { TEditor, Value } from '../slate/editor/TEditor';
import { EMarks } from '../slate/text/TText';

/**
* Get selected mark by type.
* Get selection mark value by key.
*/
export const getMark = <V extends Value, K extends keyof EMarks<V>>(
editor: TEditor<V>,
type: K
) => {
export const getMark = <V extends Value>(editor: TEditor<V>, key: string) => {
if (!editor) return;

const marks = getMarks(editor);

return marks?.[type] as K | undefined;
return marks?.[key] as unknown;
};
5 changes: 2 additions & 3 deletions packages/core/src/queries/isMarkActive.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
import { TEditor, Value } from '../slate/editor/TEditor';
import { EMarks } from '../slate/text/TText';
import { isDefined } from '../utils/misc/type-utils';
import { getMark } from './getMark';

/**
* Is the mark defined in the selection.
*/
export const isMarkActive = <V extends Value, K extends keyof EMarks<V>>(
export const isMarkActive = <V extends Value>(
editor: TEditor<V>,
type: K
type: string
) => {
return isDefined(getMark(editor, type));
};
13 changes: 11 additions & 2 deletions packages/core/src/queries/isSelectionAtBlockStart.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,27 @@
import { GetAboveNodeOptions } from '../slate/editor/getAboveNode';
import { isStartPoint } from '../slate/editor/isStartPoint';
import { TEditor, Value } from '../slate/editor/TEditor';
import { isExpanded } from '../slate/index';
import { getBlockAbove } from './getBlockAbove';

/**
* Is the selection focus at the start of its parent block.
* Is the selection anchor or focus at the start of its parent block.
*
* Supports the same options provided by {@link getBlockAbove}.
*/
export const isSelectionAtBlockStart = <V extends Value>(
editor: TEditor<V>,
options?: GetAboveNodeOptions<V>
) => {
const { selection } = editor;
if (!selection) return false;

const path = getBlockAbove(editor, options)?.[1];
if (!path) return false;

return !!path && isStartPoint(editor, editor.selection?.focus, path);
return (
isStartPoint(editor, selection.focus, path) ||
(isExpanded(editor.selection) &&
isStartPoint(editor, selection.anchor, path))
);
};
13 changes: 4 additions & 9 deletions packages/core/src/slate/editor/addMark.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { Editor } from 'slate';
import { EMarks } from '../text/TText';
import { TEditor, Value } from './TEditor';

/**
Expand All @@ -8,12 +7,8 @@ import { TEditor, Value } from './TEditor';
* If the selection is currently collapsed, the marks will be added to the
* `editor.marks` property instead, and applied when text is inserted next.
*/
export const addMark = <
V extends Value,
M extends EMarks<V>,
K extends keyof M & string
>(
export const addMark = <V extends Value>(
editor: TEditor<V>,
key: {} extends M ? string : K,
value: {} extends M ? unknown : M[K]
): void => Editor.addMark(editor as any, key, value);
key: string,
value: any
) => Editor.addMark(editor as any, key, value);
2 changes: 1 addition & 1 deletion packages/core/src/slate/editor/getMarks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@ import { TEditor, Value } from './TEditor';
* Get the marks that would be added to text at the current selection.
*/
export const getMarks = <V extends Value>(editor: TEditor<V>) =>
Editor.marks(editor as any) as Partial<EMarks<V>> | null;
Editor.marks(editor as any) as EMarks<V> | null;
7 changes: 3 additions & 4 deletions packages/core/src/slate/editor/removeEditorMark.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { Editor } from 'slate';
import { EMarks } from '../text/TText';
import { TEditor, Value } from './TEditor';

/**
Expand All @@ -9,7 +8,7 @@ import { TEditor, Value } from './TEditor';
* If the selection is currently collapsed, the removal will be stored on
* `editor.marks` and applied to the text inserted next.
*/
export const removeEditorMark = <V extends Value, M extends EMarks<V>>(
export const removeEditorMark = <V extends Value>(
editor: TEditor<V>,
key: {} extends M ? string : keyof M & string
): void => Editor.removeMark(editor as any, key);
key: string
) => Editor.removeMark(editor as any, key);
2 changes: 1 addition & 1 deletion packages/core/src/slate/text/TText.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Simplify, UnionToIntersection } from '../../types/index';
import { UnknownObject } from '../../types/misc/AnyObject';
import { Simplify, UnionToIntersection } from '../../types/misc/types';
import { TEditor, Value } from '../editor/TEditor';
import { TElement } from '../element/TElement';
import { TNode, TNodeProps } from '../node/TNode';
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/transforms/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export * from './insertEmptyElement';
export * from './moveChildren';
export * from './removeMark';
export * from './removeNodeChildren';
export * from './removeSelectionMark';
export * from './replaceNodeChildren';
export * from './resetEditorChildren';
export * from './selectEditor';
Expand Down
15 changes: 6 additions & 9 deletions packages/core/src/transforms/removeMark.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,15 @@ import { Range } from 'slate';
import { getMarks } from '../slate/editor/getMarks';
import { TEditor, Value } from '../slate/editor/TEditor';
import { isText } from '../slate/text/isText';
import { EMarks } from '../slate/text/TText';
import { SetNodesOptions } from '../slate/transforms/setNodes';
import { unsetNodes } from '../slate/transforms/unsetNodes';

export interface RemoveMarkOptions<
V extends Value = Value,
K extends keyof EMarks<V> = keyof EMarks<V>
> extends Omit<SetNodesOptions<V>, 'match' | 'split'> {
export interface RemoveMarkOptions<V extends Value = Value>
extends Omit<SetNodesOptions<V>, 'match' | 'split'> {
/**
* Mark or the array of marks that will be removed
*/
key: K | K[];
key: string | string[];

/**
* When location is not a Range,
Expand All @@ -32,9 +29,9 @@ export interface RemoveMarkOptions<
/**
* Remove mark and trigger `onChange` if collapsed selection.
*/
export const removeMark = <V extends Value, K extends keyof EMarks<V>>(
export const removeMark = <V extends Value>(
editor: TEditor<V>,
{ key, at, shouldChange = true, ...rest }: RemoveMarkOptions<V, K>
{ key, at, shouldChange = true, ...rest }: RemoveMarkOptions<V>
) => {
const selection = at ?? editor.selection;
key = castArray(key);
Expand All @@ -48,7 +45,7 @@ export const removeMark = <V extends Value, K extends keyof EMarks<V>>(
...rest,
});
} else if (editor.selection) {
const marks: Partial<EMarks<V>> = { ...(getMarks(editor) || {}) };
const marks = getMarks(editor) ?? {};
key.forEach((k) => {
delete marks[k];
});
Expand Down
17 changes: 17 additions & 0 deletions packages/core/src/transforms/removeSelectionMark.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { getMarks, TEditor, Value } from '../slate/index';
import { removeMark } from './removeMark';

/**
* Remove selection marks.
*/
export const removeSelectionMark = <V extends Value = Value>(
editor: TEditor<V>
) => {
const marks = getMarks(editor);
if (!marks) return;

// remove all marks
removeMark(editor, {
key: Object.keys(marks),
});
};
Loading