From 580903410a39b7afb0631fd2e9a8958a8aacb7b3 Mon Sep 17 00:00:00 2001 From: Felix Feng Date: Sun, 9 Feb 2025 11:59:28 +0800 Subject: [PATCH] fork --- .../src/lib/BaseSuggestionPlugin.ts | 61 ++-- .../suggestion/src/lib/diffToSuggestions.ts | 1 + .../src/lib/queries/findSuggestionId.ts | 64 +++- .../src/lib/transforms/acceptSuggestion.ts | 100 ++++-- .../src/lib/transforms/addMarkSuggestion.ts | 61 ++++ .../src/lib/transforms/addSuggestionMark.ts | 12 +- .../transforms/deleteFragmentSuggestion.ts | 8 +- .../src/lib/transforms/deleteSuggestion.ts | 93 +++++- .../src/lib/transforms/getSuggestionProps.ts | 4 + .../suggestion/src/lib/transforms/index.ts | 3 + .../transforms/insertFragmentSuggestion.ts | 57 ++-- .../lib/transforms/insertTextSuggestion.ts | 23 +- .../src/lib/transforms/rejectSuggestion.ts | 139 +++++++-- .../lib/transforms/removeMarkSuggestion.ts | 59 ++++ .../lib/transforms/removeNodesSuggestion.ts | 52 ++++ .../src/lib/transforms/setSuggestionNodes.ts | 23 +- packages/suggestion/src/lib/types.ts | 57 +++- .../utils/getActiveSuggestionDescriptions.ts | 5 +- .../src/lib/utils/getSuggestionId.ts | 77 ++++- .../src/lib/utils/getSuggestionKeys.ts | 14 +- packages/suggestion/src/lib/withSuggestion.ts | 284 ++++++------------ .../suggestion/src/react/SuggestionPlugin.tsx | 39 +++ .../src/react/useHooksSuggestion.ts | 4 +- yarn.lock | 220 +++++++------- 24 files changed, 1018 insertions(+), 442 deletions(-) create mode 100644 packages/suggestion/src/lib/transforms/addMarkSuggestion.ts create mode 100644 packages/suggestion/src/lib/transforms/removeMarkSuggestion.ts create mode 100644 packages/suggestion/src/lib/transforms/removeNodesSuggestion.ts diff --git a/packages/suggestion/src/lib/BaseSuggestionPlugin.ts b/packages/suggestion/src/lib/BaseSuggestionPlugin.ts index 2c57fe0dbb..4a445214e7 100644 --- a/packages/suggestion/src/lib/BaseSuggestionPlugin.ts +++ b/packages/suggestion/src/lib/BaseSuggestionPlugin.ts @@ -1,16 +1,22 @@ import { + type Path, type PluginConfig, type WithPartial, createTSlatePlugin, + isSlateString, nanoid, } from '@udecode/plate'; import type { SuggestionUser, TSuggestion } from './types'; +import { findSuggestionNode } from './queries'; +import { getSuggestionId } from './utils'; import { withSuggestion } from './withSuggestion'; export const SUGGESTION_KEYS = { id: 'suggestionId', + createdAt: 'suggestionCreateAt', + lineBreak: 'suggestionLineBreak', } as const; export type SuggestionConfig = PluginConfig< @@ -18,8 +24,10 @@ export type SuggestionConfig = PluginConfig< { activeSuggestionId: string | null; currentUserId: string | null; + hoverSuggestionId: string | null; isSuggesting: boolean; suggestions: Record; + uniquePathMap: Map; users: Record; onSuggestionAdd: ((value: Partial) => void) | null; onSuggestionDelete: ((id: string) => void) | null; @@ -28,35 +36,35 @@ export type SuggestionConfig = PluginConfig< value: Partial> & Pick ) => void) | null; - }, - { - suggestion: { - addSuggestion: ( - value: WithPartial - ) => void; - removeSuggestion: (id: string | null) => void; - updateSuggestion: ( - id: string | null, - value: Partial - ) => void; - }; - }, - {}, + } & SuggestionSelectors, { - currentSuggestionUser?: () => SuggestionUser | null; - suggestionById?: (id: string | null) => TSuggestion | null; - suggestionUserById?: (id: string | null) => SuggestionUser | null; + suggestion: SuggestionPluginApi; } >; +export type SuggestionPluginApi = { + addSuggestion: (value: WithPartial) => void; + removeSuggestion: (id: string | null) => void; + updateSuggestion: (id: string | null, value: Partial) => void; + withoutSuggestions: (fn: () => void) => void; +}; + +export type SuggestionSelectors = { + currentSuggestionUser?: () => SuggestionUser | null; + suggestionById?: (id: string | null) => TSuggestion | null; + suggestionUserById?: (id: string | null) => SuggestionUser | null; +}; + export const BaseSuggestionPlugin = createTSlatePlugin({ key: 'suggestion', node: { isLeaf: true }, options: { activeSuggestionId: null, currentUserId: null, + hoverSuggestionId: null, isSuggesting: false, suggestions: {}, + uniquePathMap: new Map(), users: {}, onSuggestionAdd: null, onSuggestionDelete: null, @@ -64,27 +72,27 @@ export const BaseSuggestionPlugin = createTSlatePlugin({ }, }) .overrideEditor(withSuggestion) - .extendSelectors(({ getOptions }) => ({ - currentSuggestionUser: () => { + .extendSelectors(({ getOptions }) => ({ + currentSuggestionUser: (): SuggestionUser | null => { const { currentUserId, users } = getOptions(); if (!currentUserId) return null; return users[currentUserId]; }, - suggestionById: (id) => { + suggestionById: (id: string | null): TSuggestion | null => { if (!id) return null; return getOptions().suggestions[id]; }, - suggestionUserById: (id) => { + suggestionUserById: (id: string | null): SuggestionUser | null => { if (!id) return null; return getOptions().users[id]; }, })) - .extendApi>( - ({ getOptions, setOptions }) => ({ + .extendApi>( + ({ getOption, getOptions, setOption, setOptions }) => ({ addSuggestion: (value) => { const { currentUserId } = getOptions(); @@ -93,7 +101,6 @@ export const BaseSuggestionPlugin = createTSlatePlugin({ const id = value.id ?? nanoid(); const newSuggestion: TSuggestion = { id, - createdAt: Date.now(), userId: currentUserId, ...value, }; @@ -116,5 +123,11 @@ export const BaseSuggestionPlugin = createTSlatePlugin({ draft.suggestions![id] = { ...draft.suggestions![id], ...value }; }); }, + withoutSuggestions: (fn) => { + const prev = getOption('isSuggesting'); + setOption('isSuggesting', false); + fn(); + setOption('isSuggesting', prev); + }, }) ); diff --git a/packages/suggestion/src/lib/diffToSuggestions.ts b/packages/suggestion/src/lib/diffToSuggestions.ts index 50704cd196..43713c3ede 100644 --- a/packages/suggestion/src/lib/diffToSuggestions.ts +++ b/packages/suggestion/src/lib/diffToSuggestions.ts @@ -8,6 +8,7 @@ import { type ComputeDiffOptions, computeDiff } from '@udecode/plate-diff'; import { getSuggestionProps } from './transforms'; +// TODO: refactor export function diffToSuggestions( editor: E, doc0: Descendant[], diff --git a/packages/suggestion/src/lib/queries/findSuggestionId.ts b/packages/suggestion/src/lib/queries/findSuggestionId.ts index 8198cf9570..5ac86e84de 100644 --- a/packages/suggestion/src/lib/queries/findSuggestionId.ts +++ b/packages/suggestion/src/lib/queries/findSuggestionId.ts @@ -1,13 +1,29 @@ -import type { Point, SlateEditor, TLocation } from '@udecode/plate'; +import { + type Point, + type SlateEditor, + type TElement, + type TLocation, + nanoid, +} from '@udecode/plate'; -import { SUGGESTION_KEYS } from '../BaseSuggestionPlugin'; +import { + getSuggestionData, + getSuggestionId, + getSuggestionLineBreakData, + getSuggestionLineBreakId, + isCurrentUserSuggestion, +} from '../utils'; import { findSuggestionNode } from './findSuggestionNode'; -/** - * Find the suggestion id at the cursor point, the point before and after (if - * offset = 0). - */ -export const findSuggestionId = (editor: SlateEditor, at: TLocation) => { +export const findSuggestionProps = ( + editor: SlateEditor, + { at, type }: { at: TLocation; type: 'insert' | 'remove' | 'update' } +): { id: string; createdAt: number } => { + const defaultProps = { + id: nanoid(), + createdAt: Date.now(), + }; + let entry = findSuggestionNode(editor, { at, }); @@ -19,7 +35,7 @@ export const findSuggestionId = (editor: SlateEditor, at: TLocation) => { try { [start, end] = editor.api.edges(at)!; } catch { - return; + return defaultProps; } const nextPoint = editor.api.after(end); @@ -37,10 +53,38 @@ export const findSuggestionId = (editor: SlateEditor, at: TLocation) => { at: prevPoint, }); } + //

111111

+ //

+ // in this case we need to find the parent node + // TODO: test + if (!entry && editor.api.isStart(start, at)) { + const _at = prevPoint ?? at; + + const lineBreak = editor.api.above({ at: _at }); + + if (lineBreak) { + return { + id: getSuggestionLineBreakId(lineBreak[0]) ?? nanoid(), + createdAt: + getSuggestionLineBreakData(lineBreak[0])?.createdAt ?? + Date.now(), + }; + } + } } } } - if (entry) { - return entry[0][SUGGESTION_KEYS.id]; + // same type and same user merge suggestions + if ( + entry && + getSuggestionData(entry[0])?.type === type && + isCurrentUserSuggestion(editor, entry[0]) + ) { + return { + id: getSuggestionId(entry[0]) ?? nanoid(), + createdAt: getSuggestionData(entry[0])?.createdAt ?? Date.now(), + }; } + + return defaultProps; }; diff --git a/packages/suggestion/src/lib/transforms/acceptSuggestion.ts b/packages/suggestion/src/lib/transforms/acceptSuggestion.ts index 039cacd59d..304730d5be 100644 --- a/packages/suggestion/src/lib/transforms/acceptSuggestion.ts +++ b/packages/suggestion/src/lib/transforms/acceptSuggestion.ts @@ -1,41 +1,95 @@ -import type { SlateEditor } from '@udecode/plate'; +import { type SlateEditor, ElementApi, PathApi, TextApi } from '@udecode/plate'; -import type { TSuggestionText } from '../types'; +import type { TResolvedSuggestion } from '../types'; -import { BaseSuggestionPlugin, SUGGESTION_KEYS } from '../BaseSuggestionPlugin'; -import { type TSuggestionDescription, getSuggestionKey } from '../utils/index'; +import { SUGGESTION_KEYS } from '../BaseSuggestionPlugin'; +import { + getSuggestionData, + getSuggestionDataList, + getSuggestionLineBreakData, +} from '../utils'; export const acceptSuggestion = ( editor: SlateEditor, - description: TSuggestionDescription + description: TResolvedSuggestion ) => { editor.tf.withoutNormalizing(() => { - const suggestionKey = getSuggestionKey(description.userId); + const mergeNodes = [ + ...editor.api.nodes({ + at: [], + match: (n) => { + if (!ElementApi.isElement(n)) return false; - editor.tf.unsetNodes([BaseSuggestionPlugin.key, suggestionKey], { + const lineBreakData = getSuggestionLineBreakData(n); + + if (lineBreakData) + return ( + lineBreakData.type === 'remove' && + lineBreakData.id === description.suggestionId + ); + + return false; + }, + }), + ]; + + mergeNodes.reverse().forEach(([, path]) => { + editor.tf.mergeNodes({ at: PathApi.next(path) }); + }); + + editor.tf.unsetNodes([description.keyId, SUGGESTION_KEYS.lineBreak], { at: [], + mode: 'all', match: (n) => { - const node = n as TSuggestionText; - - // unset additions - return ( - node[SUGGESTION_KEYS.id] === description.suggestionId && - !node.suggestionDeletion && - !!node[suggestionKey] - ); + if (TextApi.isText(n)) { + const suggestionDataList = getSuggestionDataList(n); + const includeUpdate = suggestionDataList.some( + (data) => data.type === 'update' + ); + + if (includeUpdate) { + return suggestionDataList.some( + (d) => d.id === description.suggestionId + ); + } else { + const suggestionData = getSuggestionData(n); + + if (suggestionData) + return ( + suggestionData.type === 'insert' && + suggestionData.id === description.suggestionId + ); + } + + return false; + } + if (ElementApi.isElement(n)) { + const lineBreakData = getSuggestionLineBreakData(n); + + if (lineBreakData) + return lineBreakData.id === description.suggestionId; + } + + return false; }, }); + editor.tf.removeNodes({ at: [], + mode: 'all', match: (n) => { - const node = n as TSuggestionText; - - // remove deletions - return ( - node[SUGGESTION_KEYS.id] === description.suggestionId && - !!node.suggestionDeletion && - !!node[suggestionKey] - ); + if (TextApi.isText(n)) { + const suggestionData = getSuggestionData(n); + + if (suggestionData) { + return ( + suggestionData.type === 'remove' && + suggestionData.id === description.suggestionId + ); + } + + return false; + } }, }); }); diff --git a/packages/suggestion/src/lib/transforms/addMarkSuggestion.ts b/packages/suggestion/src/lib/transforms/addMarkSuggestion.ts new file mode 100644 index 0000000000..391182935d --- /dev/null +++ b/packages/suggestion/src/lib/transforms/addMarkSuggestion.ts @@ -0,0 +1,61 @@ +import { type SlateEditor, type TNode, TextApi } from '@udecode/plate'; +import { nanoid } from 'nanoid'; + +import { getSuggestionData, getSuggestionKey } from '../..'; +import { BaseSuggestionPlugin } from '../BaseSuggestionPlugin'; + +const getAddMarkProps = () => { + const defaultProps = { + id: nanoid(), + createdAt: Date.now(), + }; + + return defaultProps; +}; + +export const addMarkSuggestion = ( + editor: SlateEditor, + key: string, + value: any +) => { + editor.getApi(BaseSuggestionPlugin).suggestion.withoutSuggestions(() => { + const { id, createdAt} = getAddMarkProps(); + + + const match = (n: TNode) => { + if (!TextApi.isText(n)) return false; + // if the node is already marked as a suggestion, we don't want to remove it unless it's a removeMark suggestion + if (n[BaseSuggestionPlugin.key]) { + const data = getSuggestionData(n); + + if (data?.type === 'update') { + return true; + } + + return false; + } + + return true; + }; + + editor.tf.setNodes( + { + [key]: value, + [BaseSuggestionPlugin.key]: true, + [getSuggestionKey(id)]: { + id: id, + createdAt: createdAt, + newProperties: { + [key]: value, + }, + type: 'update', + userId: editor.getOptions(BaseSuggestionPlugin).currentUserId, + }, + }, + { + match, + split: true, + } + ); + }); +}; diff --git a/packages/suggestion/src/lib/transforms/addSuggestionMark.ts b/packages/suggestion/src/lib/transforms/addSuggestionMark.ts index a637a9b079..c95891d5bb 100644 --- a/packages/suggestion/src/lib/transforms/addSuggestionMark.ts +++ b/packages/suggestion/src/lib/transforms/addSuggestionMark.ts @@ -1,12 +1,15 @@ -import { type SlateEditor, nanoid } from '@udecode/plate'; +import type { SlateEditor } from '@udecode/plate'; import { BaseSuggestionPlugin, SUGGESTION_KEYS } from '../BaseSuggestionPlugin'; -import { findSuggestionId } from '../queries/findSuggestionId'; +import { findSuggestionProps } from '../queries/findSuggestionId'; export const addSuggestionMark = (editor: SlateEditor) => { if (!editor.selection) return; - const id = findSuggestionId(editor, editor.selection) ?? nanoid(); + const { id, createdAt: createdAt } = findSuggestionProps(editor, { + at: editor.selection, + type: 'update', + }); if (!editor.marks?.[BaseSuggestionPlugin.key]) { editor.tf.addMark(BaseSuggestionPlugin.key, true); @@ -14,4 +17,7 @@ export const addSuggestionMark = (editor: SlateEditor) => { if (!editor.marks?.[SUGGESTION_KEYS.id]) { editor.tf.addMark(SUGGESTION_KEYS.id, id); } + if (!editor.marks?.[SUGGESTION_KEYS.createdAt]) { + editor.tf.addMark(SUGGESTION_KEYS.createdAt, createdAt); + } }; diff --git a/packages/suggestion/src/lib/transforms/deleteFragmentSuggestion.ts b/packages/suggestion/src/lib/transforms/deleteFragmentSuggestion.ts index 8e2edd3ac9..b77e92df51 100644 --- a/packages/suggestion/src/lib/transforms/deleteFragmentSuggestion.ts +++ b/packages/suggestion/src/lib/transforms/deleteFragmentSuggestion.ts @@ -6,6 +6,8 @@ export const deleteFragmentSuggestion = ( editor: SlateEditor, { reverse }: { reverse?: boolean } = {} ) => { + let resId: string | undefined; + editor.tf.withoutNormalizing(() => { const selection = editor.selection!; @@ -13,14 +15,16 @@ export const deleteFragmentSuggestion = ( if (reverse) { editor.tf.collapse({ edge: 'end' }); - deleteSuggestion( + resId = deleteSuggestion( editor, { anchor: end, focus: start }, { reverse: true } ); } else { editor.tf.collapse({ edge: 'start' }); - deleteSuggestion(editor, { anchor: start, focus: end }); + resId = deleteSuggestion(editor, { anchor: start, focus: end }); } }); + + return resId; }; diff --git a/packages/suggestion/src/lib/transforms/deleteSuggestion.ts b/packages/suggestion/src/lib/transforms/deleteSuggestion.ts index 92ab67a8ae..aebd29b017 100644 --- a/packages/suggestion/src/lib/transforms/deleteSuggestion.ts +++ b/packages/suggestion/src/lib/transforms/deleteSuggestion.ts @@ -3,14 +3,20 @@ import { type SlateEditor, type TElement, type TRange, - nanoid, + ElementApi, + PathApi, PointApi, + TextApi, } from '@udecode/plate'; -import { BaseSuggestionPlugin } from '../BaseSuggestionPlugin'; -import { findSuggestionId } from '../queries/findSuggestionId'; +import { BaseSuggestionPlugin, SUGGESTION_KEYS } from '../BaseSuggestionPlugin'; +import { findSuggestionProps } from '../queries/findSuggestionId'; import { findSuggestionNode } from '../queries/index'; -import { getSuggestionCurrentUserKey } from './getSuggestionProps'; +import { + getSuggestionData, + getSuggestionLineBreakData, + isCurrentUserSuggestion, +} from '../utils'; import { setSuggestionNodes } from './setSuggestionNodes'; /** @@ -26,10 +32,17 @@ export const deleteSuggestion = ( reverse?: boolean; } = {} ) => { + let resId: string | undefined; + editor.tf.withoutNormalizing(() => { const { anchor: from, focus: to } = at; - const suggestionId = findSuggestionId(editor, from) ?? nanoid(); + const { id, createdAt: createdAt } = findSuggestionProps(editor, { + at: from, + type: 'remove', + }); + + resId = id; const toRef = editor.api.pointRef(to); @@ -91,8 +104,9 @@ export const deleteSuggestion = ( block: true, match: (n) => n[BaseSuggestionPlugin.key] && - !n.suggestionDeletion && - n[getSuggestionCurrentUserKey(editor)], + TextApi.isText(n) && + getSuggestionData(n)?.type === 'insert' && + isCurrentUserSuggestion(editor, n), }); if ( @@ -106,6 +120,58 @@ export const deleteSuggestion = ( continue; } + // if the range is across blocks, delete the line break + if (editor.api.isAt({ at: range, blocks: true })) { + const previousAboveNode = editor.api.above({ at: range.anchor }); + + if (previousAboveNode && ElementApi.isElement(previousAboveNode[0])) { + const lineBreakData = getSuggestionLineBreakData( + previousAboveNode[0] + ); + + if (lineBreakData && lineBreakData.type === 'insert') { + editor + .getApi(BaseSuggestionPlugin) + .suggestion.withoutSuggestions(() => { + editor.tf.unsetNodes([SUGGESTION_KEYS.lineBreak], { + at: previousAboveNode[1], + }); + editor.tf.mergeNodes({ + at: PathApi.next(previousAboveNode[1]), + }); + }); + + break; + } else { + const { id, createdAt: createdAt } = findSuggestionProps(editor, { + at: range, + type: 'remove', + }); + + editor.tf.setNodes( + { + [SUGGESTION_KEYS.lineBreak]: { + id, + createdAt, + type: 'remove', + userId: + editor.getOptions(BaseSuggestionPlugin).currentUserId!, + }, + }, + { at: previousAboveNode[1] } + ); + + editor.tf.move({ + reverse, + unit: 'character', + }); + + continue; + } + } + + break; + } // move selection if still the same if (PointApi.equals(pointCurrent, editor.selection!.anchor)) { editor.tf.move({ @@ -113,16 +179,14 @@ export const deleteSuggestion = ( unit: 'character', }); } - // skip if the range is across blocks - if (editor.api.isAt({ at: range, blocks: true })) { - continue; - } // if the current point is in addition suggestion, delete const entryText = findSuggestionNode(editor, { at: range, match: (n) => - !n.suggestionDeletion && n[getSuggestionCurrentUserKey(editor)], + TextApi.isText(n) && + getSuggestionData(n)?.type === 'insert' && + isCurrentUserSuggestion(editor, n), }); if (entryText) { @@ -133,9 +197,12 @@ export const deleteSuggestion = ( setSuggestionNodes(editor, { at: range, + createdAt: createdAt as number, suggestionDeletion: true, - suggestionId, + suggestionId: id, }); } }); + + return resId; }; diff --git a/packages/suggestion/src/lib/transforms/getSuggestionProps.ts b/packages/suggestion/src/lib/transforms/getSuggestionProps.ts index 2f1ba5e59e..942d88647f 100644 --- a/packages/suggestion/src/lib/transforms/getSuggestionProps.ts +++ b/packages/suggestion/src/lib/transforms/getSuggestionProps.ts @@ -3,6 +3,7 @@ import type { SlateEditor } from '@udecode/plate'; import { BaseSuggestionPlugin, SUGGESTION_KEYS } from '../BaseSuggestionPlugin'; import { getSuggestionKey } from '../utils/index'; +// DEPRECATED export const getSuggestionCurrentUserKey = (editor: SlateEditor) => { const { currentUserId } = editor.getOptions(BaseSuggestionPlugin); @@ -13,9 +14,11 @@ export const getSuggestionProps = ( editor: SlateEditor, id: string, { + createdAt, suggestionDeletion, suggestionUpdate, }: { + createdAt?: number; suggestionDeletion?: boolean; suggestionUpdate?: any; } = {} @@ -23,6 +26,7 @@ export const getSuggestionProps = ( const res = { [BaseSuggestionPlugin.key]: true, [getSuggestionCurrentUserKey(editor)]: true, + [SUGGESTION_KEYS.createdAt]: createdAt, [SUGGESTION_KEYS.id]: id, }; diff --git a/packages/suggestion/src/lib/transforms/index.ts b/packages/suggestion/src/lib/transforms/index.ts index cb8bfd79c9..db33b73784 100644 --- a/packages/suggestion/src/lib/transforms/index.ts +++ b/packages/suggestion/src/lib/transforms/index.ts @@ -3,6 +3,7 @@ */ export * from './acceptSuggestion'; +export * from './addMarkSuggestion'; export * from './addSuggestionMark'; export * from './deleteFragmentSuggestion'; export * from './deleteSuggestion'; @@ -10,4 +11,6 @@ export * from './getSuggestionProps'; export * from './insertFragmentSuggestion'; export * from './insertTextSuggestion'; export * from './rejectSuggestion'; +export * from './removeMarkSuggestion'; +export * from './removeNodesSuggestion'; export * from './setSuggestionNodes'; diff --git a/packages/suggestion/src/lib/transforms/insertFragmentSuggestion.ts b/packages/suggestion/src/lib/transforms/insertFragmentSuggestion.ts index 501a24425d..24bb03dbf1 100644 --- a/packages/suggestion/src/lib/transforms/insertFragmentSuggestion.ts +++ b/packages/suggestion/src/lib/transforms/insertFragmentSuggestion.ts @@ -2,14 +2,13 @@ import { type Descendant, type SlateEditor, applyDeepToNodes, - nanoid, + TextApi, } from '@udecode/plate'; import { BaseSuggestionPlugin, SUGGESTION_KEYS } from '../BaseSuggestionPlugin'; -import { findSuggestionId } from '../queries/findSuggestionId'; -import { getSuggestionKeys } from '../utils/index'; +import { findSuggestionProps } from '../queries/findSuggestionId'; +import { getSuggestionKey, getSuggestionKeys } from '../utils/index'; import { deleteFragmentSuggestion } from './deleteFragmentSuggestion'; -import { getSuggestionCurrentUserKey } from './getSuggestionProps'; export const insertFragmentSuggestion = ( editor: SlateEditor, @@ -23,36 +22,48 @@ export const insertFragmentSuggestion = ( editor.tf.withoutNormalizing(() => { deleteFragmentSuggestion(editor); - const id = findSuggestionId(editor, editor.selection!) ?? nanoid(); + const { id, createdAt: createdAt } = findSuggestionProps(editor, { + at: editor.selection!, + type: 'insert', + }); fragment.forEach((node) => { applyDeepToNodes({ node, source: {}, apply: (n) => { - if (!n[BaseSuggestionPlugin.key]) { - // Add suggestion mark - n[BaseSuggestionPlugin.key] = true; - } - if (n.suggestionDeletion) { - // Remove suggestion deletion mark - delete n.suggestionDeletion; - } + if (TextApi.isText(n)) { + if (!n[BaseSuggestionPlugin.key]) { + // Add suggestion mark + n[BaseSuggestionPlugin.key] = true; + } - n[SUGGESTION_KEYS.id] = id; + // remove the other suggestion data + const otherUserKeys = getSuggestionKeys(n); + otherUserKeys.forEach((key) => { + delete n[key]; + }); - // remove the other user keys - const otherUserKeys = getSuggestionKeys(n); - otherUserKeys.forEach((key) => { - delete n[key]; - }); - - // set current user key - n[getSuggestionCurrentUserKey(editor)] = true; + n[getSuggestionKey(id)] = { + id, + createdAt, + type: 'insert', + userId: editor.getOptions(BaseSuggestionPlugin).currentUserId!, + }; + } else { + n[SUGGESTION_KEYS.lineBreak] = { + id, + createdAt, + type: 'insert', + userId: editor.getOptions(BaseSuggestionPlugin).currentUserId!, + }; + } }, }); }); - insertFragment(fragment); + editor.getApi(BaseSuggestionPlugin).suggestion.withoutSuggestions(() => { + insertFragment(fragment); + }); }); }; diff --git a/packages/suggestion/src/lib/transforms/insertTextSuggestion.ts b/packages/suggestion/src/lib/transforms/insertTextSuggestion.ts index db6d6e7a98..8e22f1871a 100644 --- a/packages/suggestion/src/lib/transforms/insertTextSuggestion.ts +++ b/packages/suggestion/src/lib/transforms/insertTextSuggestion.ts @@ -1,23 +1,34 @@ -import { type SlateEditor, nanoid } from '@udecode/plate'; +import type { SlateEditor } from '@udecode/plate'; import type { TSuggestionText } from '../types'; -import { findSuggestionId } from '../queries/findSuggestionId'; +import { BaseSuggestionPlugin } from '../BaseSuggestionPlugin'; +import { findSuggestionProps } from '../queries'; +import { getSuggestionKey } from '../utils'; import { deleteFragmentSuggestion } from './deleteFragmentSuggestion'; -import { getSuggestionProps } from './getSuggestionProps'; export const insertTextSuggestion = (editor: SlateEditor, text: string) => { editor.tf.withoutNormalizing(() => { - const id = findSuggestionId(editor, editor.selection!) ?? nanoid(); + let resId: string | undefined; + const { id, createdAt: createdAt } = findSuggestionProps(editor, { + at: editor.selection!, + type: 'insert', + }); if (editor.api.isExpanded()) { - deleteFragmentSuggestion(editor); + resId = deleteFragmentSuggestion(editor); } editor.tf.insertNodes( { + [getSuggestionKey(resId ?? id)]: { + id: resId ?? id, + createdAt: createdAt, + type: 'insert', + userId: editor.getOptions(BaseSuggestionPlugin).currentUserId!, + }, + suggestion: true, text, - ...getSuggestionProps(editor, id), }, { at: editor.selection!, diff --git a/packages/suggestion/src/lib/transforms/rejectSuggestion.ts b/packages/suggestion/src/lib/transforms/rejectSuggestion.ts index 15c435a7dd..43c6df0d4f 100644 --- a/packages/suggestion/src/lib/transforms/rejectSuggestion.ts +++ b/packages/suggestion/src/lib/transforms/rejectSuggestion.ts @@ -1,42 +1,141 @@ -import type { SlateEditor } from '@udecode/plate'; +import { + type SlateEditor, + type TText, + ElementApi, + PathApi, + TextApi, +} from '@udecode/plate'; -import type { TSuggestionText } from '../types'; +import type { TResolvedSuggestion, TSuggestionText } from '../types'; import { SUGGESTION_KEYS } from '../BaseSuggestionPlugin'; -import { type TSuggestionDescription, getSuggestionKey } from '../utils/index'; +import { + getSuggestionData, + getSuggestionDataList, + getSuggestionKey, + getSuggestionLineBreakData, +} from '../utils'; export const rejectSuggestion = ( editor: SlateEditor, - description: TSuggestionDescription + description: TResolvedSuggestion ) => { - const suggestionKey = getSuggestionKey(description.userId); - editor.tf.withoutNormalizing(() => { - editor.tf.unsetNodes([suggestionKey], { + const mergeNodes = [ + ...editor.api.nodes({ + at: [], + match: (n) => { + if (!ElementApi.isElement(n)) return false; + + const lineBreakData = getSuggestionLineBreakData(n); + + if (lineBreakData) + return ( + lineBreakData.type === 'insert' && + lineBreakData.id === description.suggestionId + ); + + return false; + }, + }), + ]; + + mergeNodes.reverse().forEach(([, path]) => { + editor.tf.mergeNodes({ at: PathApi.next(path) }); + }); + + editor.tf.unsetNodes([description.keyId, SUGGESTION_KEYS.lineBreak], { at: [], + mode: 'all', match: (n) => { - const node = n as TSuggestionText; + if (TextApi.isText(n)) { + const node = n as TSuggestionText; + const suggestionData = getSuggestionData(node); - // unset deletions - return ( - node[SUGGESTION_KEYS.id] === description.suggestionId && - !!node.suggestionDeletion && - !!node[suggestionKey] - ); + if (suggestionData) + return ( + suggestionData.type === 'remove' && + suggestionData.id === description.suggestionId + ); + + return false; + } + if (ElementApi.isElement(n)) { + const lineBreakData = getSuggestionLineBreakData(n); + + if (lineBreakData) + return lineBreakData.id === description.suggestionId; + } + + return false; }, }); + editor.tf.removeNodes({ at: [], match: (n) => { const node = n as TSuggestionText; - // remove additions - return ( - node[SUGGESTION_KEYS.id] === description.suggestionId && - !node.suggestionDeletion && - !!node[suggestionKey] - ); + const suggestionData = getSuggestionData(node); + + if (suggestionData) + return ( + suggestionData.type === 'insert' && + suggestionData.id === description.suggestionId + ); + + return false; }, }); + + const updateNodes = [ + ...editor.api.nodes({ + at: [], + match: (n) => { + if (ElementApi.isElement(n)) return false; + if (TextApi.isText(n)) { + const datalist = getSuggestionDataList(n); + + if (datalist.length > 0) + return datalist.some( + (data) => + data.type === 'update' && data.id === description.suggestionId + ); + + return false; + } + }, + }), + ]; + + updateNodes.forEach(([node, path]) => { + const datalist = getSuggestionDataList(node); + const targetData = datalist.find( + (data) => data.type === 'update' && data.id === description.suggestionId + ); + + if (!targetData) return; + if ('newProperties' in targetData) { + const unsetProps = Object.keys(targetData.newProperties).filter( + (key) => targetData.newProperties[key] + ); + + editor.tf.unsetNodes([...unsetProps, getSuggestionKey(targetData.id)], { + at: path, + }); + } + if ('properties' in targetData) { + const addProps = Object.keys(targetData.properties).filter( + (key) => !targetData.properties[key] + ); + + editor.tf.setNodes( + Object.fromEntries(addProps.map((key) => [key, true])), + { + at: path, + } + ); + } + }); }); }; diff --git a/packages/suggestion/src/lib/transforms/removeMarkSuggestion.ts b/packages/suggestion/src/lib/transforms/removeMarkSuggestion.ts new file mode 100644 index 0000000000..0856537b45 --- /dev/null +++ b/packages/suggestion/src/lib/transforms/removeMarkSuggestion.ts @@ -0,0 +1,59 @@ +import { type SlateEditor, type TNode, TextApi } from '@udecode/plate'; +import { nanoid } from 'nanoid'; + +import { getSuggestionData, getSuggestionKey } from '../..'; +import { BaseSuggestionPlugin } from '../BaseSuggestionPlugin'; + +const getRemoveMarkProps = () => { + const defaultProps = { + id: nanoid(), + createdAt: Date.now(), + }; + + return defaultProps; +}; + +export const removeMarkSuggestion = (editor: SlateEditor, key: string) => { + editor.getApi(BaseSuggestionPlugin).suggestion.withoutSuggestions(() => { + const { id, createdAt } = getRemoveMarkProps(); + + + const match = (n: TNode) => { + if (!TextApi.isText(n)) return false; + // if the node is already marked as a suggestion, we don't want to remove it unless it's a removeMark suggestion + if (n[BaseSuggestionPlugin.key]) { + const data = getSuggestionData(n); + + if (data?.type === 'update') { + return true; + } + + return false; + } + + return true; + }; + + editor.tf.unsetNodes(key, { + match, + }); + + editor.tf.setNodes( + { + [BaseSuggestionPlugin.key]: true, + [getSuggestionKey(id)]: { + id: id, + createdAt: createdAt, + properties: { + [key]: undefined, + }, + type: 'update', + userId: editor.getOptions(BaseSuggestionPlugin).currentUserId, + }, + }, + { + match, + } + ); + }); +}; diff --git a/packages/suggestion/src/lib/transforms/removeNodesSuggestion.ts b/packages/suggestion/src/lib/transforms/removeNodesSuggestion.ts new file mode 100644 index 0000000000..f5798e120e --- /dev/null +++ b/packages/suggestion/src/lib/transforms/removeNodesSuggestion.ts @@ -0,0 +1,52 @@ +import { + type RemoveNodesOptions, + type SlateEditor, + NodeApi, +} from '@udecode/plate'; + +import { BaseSuggestionPlugin, SUGGESTION_KEYS } from '../BaseSuggestionPlugin'; +import { findSuggestionProps } from '../queries'; +import { getSuggestionKey } from '../utils'; + +export const removeNodesSuggestion = ( + editor: SlateEditor, + options?: RemoveNodesOptions +) => { + const nodes = [...editor.api.nodes(options)]; + + if (nodes.length === 0) return; + + const { id, createdAt: createdAt } = findSuggestionProps(editor, { + at: editor.selection!, + type: 'remove', + }); + + nodes.forEach(([, blockPath]) => { + const children = NodeApi.children(editor, blockPath); + + children.forEach(([, path]) => { + editor.tf.setNodes( + { + [BaseSuggestionPlugin.key]: true, + [getSuggestionKey(id)]: { + id, + createdAt: createdAt, + type: 'remove', + userId: editor.getOptions(BaseSuggestionPlugin).currentUserId, + }, + }, + { at: path } + ); + }); + + editor.tf.setNodes( + { + [SUGGESTION_KEYS.lineBreak]: { + id, + type: 'remove', + }, + }, + { at: blockPath } + ); + }); +}; diff --git a/packages/suggestion/src/lib/transforms/setSuggestionNodes.ts b/packages/suggestion/src/lib/transforms/setSuggestionNodes.ts index b22b9ee381..0acdfff0ba 100644 --- a/packages/suggestion/src/lib/transforms/setSuggestionNodes.ts +++ b/packages/suggestion/src/lib/transforms/setSuggestionNodes.ts @@ -1,5 +1,4 @@ import { - type NodeProps, type SetNodesOptions, type SlateEditor, ElementApi, @@ -7,13 +6,15 @@ import { nanoid, } from '@udecode/plate'; -import type { TSuggestionText } from '../types'; +import type { TSuggestionData, TSuggestionText } from '../types'; -import { getSuggestionProps } from './getSuggestionProps'; +import { BaseSuggestionPlugin } from '../BaseSuggestionPlugin'; +import { getSuggestionKey } from '../utils'; export const setSuggestionNodes = ( editor: SlateEditor, options?: { + createdAt?: number; suggestionDeletion?: boolean; suggestionId?: string; } & SetNodesOptions @@ -32,11 +33,17 @@ export const setSuggestionNodes = ( const nodeEntries = [..._nodeEntries]; editor.tf.withoutNormalizing(() => { - const props: NodeProps = getSuggestionProps( - editor, - suggestionId, - options - ); + const data: TSuggestionData = { + id: suggestionId, + createdAt: options?.createdAt ?? Date.now(), + type: 'remove', + userId: editor.getOptions(BaseSuggestionPlugin).currentUserId!, + }; + + const props = { + [BaseSuggestionPlugin.key]: true, + [getSuggestionKey(suggestionId)]: data, + }; editor.tf.setNodes(props, { at, diff --git a/packages/suggestion/src/lib/types.ts b/packages/suggestion/src/lib/types.ts index c937bf8d0a..73ecd22039 100644 --- a/packages/suggestion/src/lib/types.ts +++ b/packages/suggestion/src/lib/types.ts @@ -12,19 +12,62 @@ export interface SuggestionUser extends UnknownObject { avatarUrl?: string; } -export interface TSuggestion extends UnknownObject { +export type TInsertSuggestionData = { id: string; + createdAt: number; + type: 'insert'; + userId: string; +}; - /** @default Date.now() */ +export type TRemoveSuggestionData = { + id: string; createdAt: number; + type: 'remove'; + userId: string; +}; + +export type TResolvedSuggestion = { + createdAt: Date; + keyId: string; + suggestionId: string; + type: 'insert' | 'remove' | 'replace' | 'update'; + userId: string; + newText?: string; + text?: string; + updateProps?: any; +}; + +export interface TSuggestion extends UnknownObject { + id: string; isAccepted?: boolean; isRejected?: boolean; } -export interface TSuggestionText extends TText { - suggestion?: boolean; - suggestionDeletion?: boolean; - suggestionId?: string; -} +export type TSuggestionData = + | TInsertSuggestionData + | TRemoveSuggestionData + | TUpdateSuggestionData; + +export type TSuggestionLineBreak = { + id: string; + createdAt: number; + type: 'insert' | 'remove'; + userId: string; +}; + +export type TSuggestionText = TText & { + [key: string]: TSuggestionData | boolean | string; + suggestion: true; + text: string; +}; + +export type TUpdateSuggestionData = { + id: string; + createdAt: number; + type: 'update'; + userId: string; + newProperties?: any; + properties?: any; +}; diff --git a/packages/suggestion/src/lib/utils/getActiveSuggestionDescriptions.ts b/packages/suggestion/src/lib/utils/getActiveSuggestionDescriptions.ts index 917670750a..43c0f5aff1 100644 --- a/packages/suggestion/src/lib/utils/getActiveSuggestionDescriptions.ts +++ b/packages/suggestion/src/lib/utils/getActiveSuggestionDescriptions.ts @@ -1,6 +1,7 @@ import type { SlateEditor } from '@udecode/plate'; import { findSuggestionNode } from '../queries/index'; +import { getSuggestionId } from './getSuggestionId'; import { getSuggestionKey, getSuggestionUserIds } from './getSuggestionKeys'; import { getSuggestionNodeEntries } from './getSuggestionNodeEntries'; @@ -44,7 +45,9 @@ export const getActiveSuggestionDescriptions = ( if (!aboveEntry) return []; const aboveNode = aboveEntry[0]; - const suggestionId = aboveNode.suggestionId!; + const suggestionId = getSuggestionId(aboveNode); + + if (!suggestionId) return []; const userIds = getSuggestionUserIds(aboveNode); diff --git a/packages/suggestion/src/lib/utils/getSuggestionId.ts b/packages/suggestion/src/lib/utils/getSuggestionId.ts index e052f98637..06bbe307ca 100644 --- a/packages/suggestion/src/lib/utils/getSuggestionId.ts +++ b/packages/suggestion/src/lib/utils/getSuggestionId.ts @@ -1,7 +1,76 @@ -import type { TNode } from '@udecode/plate'; +import { + type TElement, + type TNode, + type TText, + ElementApi, + TextApi, +} from '@udecode/plate'; -import { SUGGESTION_KEYS } from '../BaseSuggestionPlugin'; +import type { TSuggestionData, TSuggestionLineBreak } from '../types'; -export const getSuggestionId = (node: TNode) => { - return node[SUGGESTION_KEYS.id] as string | undefined; +import { BaseSuggestionPlugin, SUGGESTION_KEYS } from '../BaseSuggestionPlugin'; + +// the last id is the active id +export const getSuggestionKeyId = (node: TText) => { + const ids = Object.keys(node).filter((key) => { + return key.startsWith(`${BaseSuggestionPlugin.key}_`); + }); + + return ids.at(-1); +}; + +export const getAllSuggestionId = (node: TNode) => { + if (TextApi.isText(node)) { + return getSuggestionId(node); + } + if (ElementApi.isElement(node)) { + return getSuggestionLineBreakId(node); + } +}; + +export const getAllSuggestionData = ( + node: TNode +): TSuggestionData | TSuggestionLineBreak | undefined => { + if (TextApi.isText(node)) { + return getSuggestionData(node); + } + if (ElementApi.isElement(node)) { + return getSuggestionLineBreakData(node); + } +}; + +export const getSuggestionId = (node: TText) => { + const keyId = getSuggestionKeyId(node); + + if (!keyId) return; + + return keyId.replace(`${BaseSuggestionPlugin.key}_`, ''); +}; + +export const getSuggestionData = (node: TText) => { + const keyId = getSuggestionKeyId(node); + + if (!keyId) return; + + return node[keyId] as TSuggestionData | undefined; +}; + +export const getSuggestionLineBreakData = (node: TElement) => { + return node[SUGGESTION_KEYS.lineBreak] as TSuggestionLineBreak | undefined; +}; + +export const getSuggestionLineBreakId = (node: TElement) => { + return getSuggestionLineBreakData(node)?.id; +}; + +export const getSuggestionDataList = (node: TText) => { + return Object.keys(node) + .filter((key) => { + return key.startsWith(`${BaseSuggestionPlugin.key}_`); + }) + .map((key) => node[key] as TSuggestionData); +}; + +export const keyId2SuggestionId = (keyId: string) => { + return keyId.replace(`${BaseSuggestionPlugin.key}_`, ''); }; diff --git a/packages/suggestion/src/lib/utils/getSuggestionKeys.ts b/packages/suggestion/src/lib/utils/getSuggestionKeys.ts index 59d5fbf718..d6face70cf 100644 --- a/packages/suggestion/src/lib/utils/getSuggestionKeys.ts +++ b/packages/suggestion/src/lib/utils/getSuggestionKeys.ts @@ -1,6 +1,12 @@ -import { type TNode, isDefined } from '@udecode/plate'; +import { + type SlateEditor, + type TNode, + type TText, + isDefined, +} from '@udecode/plate'; import { BaseSuggestionPlugin } from '../BaseSuggestionPlugin'; +import { getSuggestionData } from './getSuggestionId'; export const getSuggestionKey = (id = '0') => `${BaseSuggestionPlugin.key}_${id}`; @@ -30,3 +36,9 @@ export const getSuggestionUserIds = (node: TNode) => { export const getSuggestionUserId = (node: TNode) => { return getSuggestionUserIds(node)[0]; }; + +export const isCurrentUserSuggestion = (editor: SlateEditor, node: TText) => { + const { currentUserId } = editor.getOptions(BaseSuggestionPlugin); + + return getSuggestionData(node)?.userId === currentUserId; +}; diff --git a/packages/suggestion/src/lib/withSuggestion.ts b/packages/suggestion/src/lib/withSuggestion.ts index 8f872685a9..2af4fdab86 100644 --- a/packages/suggestion/src/lib/withSuggestion.ts +++ b/packages/suggestion/src/lib/withSuggestion.ts @@ -1,22 +1,28 @@ -import { type OverrideEditor, NodeApi } from '@udecode/plate'; - -import type { TSuggestionText } from './types'; +import { type OverrideEditor, TextApi } from '@udecode/plate'; +import { ParagraphPlugin } from '@udecode/plate/react'; import { type SuggestionConfig, BaseSuggestionPlugin, SUGGESTION_KEYS, } from './BaseSuggestionPlugin'; +import { findSuggestionProps } from './queries'; +import { addMarkSuggestion } from './transforms/addMarkSuggestion'; import { deleteFragmentSuggestion } from './transforms/deleteFragmentSuggestion'; import { deleteSuggestion } from './transforms/deleteSuggestion'; import { insertFragmentSuggestion } from './transforms/insertFragmentSuggestion'; import { insertTextSuggestion } from './transforms/insertTextSuggestion'; -import { getSuggestionId, getSuggestionKeys } from './utils/index'; +import { removeMarkSuggestion } from './transforms/removeMarkSuggestion'; +import { removeNodesSuggestion } from './transforms/removeNodesSuggestion'; +import { getSuggestionData, getSuggestionKeyId } from './utils/index'; export const withSuggestion: OverrideEditor = ({ + api, editor, getOptions, tf: { + addMark, + apply, deleteBackward, deleteForward, deleteFragment, @@ -24,14 +30,26 @@ export const withSuggestion: OverrideEditor = ({ insertFragment, insertText, normalizeNode, + removeMark, + removeNodes, }, }) => ({ transforms: { + addMark(key, value) { + if (getOptions().isSuggesting && api.isExpanded()) { + return addMarkSuggestion(editor, key, value); + } + + return addMark(key, value); + }, + apply(operation) { + return apply(operation); + }, deleteBackward(unit) { - if (getOptions().isSuggesting) { - const selection = editor.selection!; - const pointTarget = editor.api.before(selection, { unit }); + const selection = editor.selection!; + const pointTarget = editor.api.before(selection, { unit }); + if (getOptions().isSuggesting) { if (!pointTarget) return; deleteSuggestion( @@ -41,11 +59,24 @@ export const withSuggestion: OverrideEditor = ({ ); return; + } else { + // remove line break when across blocks + if (pointTarget) { + const isCrossBlock = editor.api.isAt({ + at: { anchor: selection.anchor, focus: pointTarget }, + blocks: true, + }); + + if (isCrossBlock) { + editor.tf.unsetNodes([SUGGESTION_KEYS.lineBreak], { + at: pointTarget, + }); + } + } } deleteBackward(unit); }, - deleteForward(unit) { if (getOptions().isSuggesting) { const selection = editor.selection!; @@ -76,8 +107,31 @@ export const withSuggestion: OverrideEditor = ({ insertBreak() { if (getOptions().isSuggesting) { - // TODO: split node - insertTextSuggestion(editor, '\n'); + const [node, path] = editor.api.above()!; + + // TODO: options + if (path.length > 1 || node.type !== ParagraphPlugin.key) { + return insertTextSuggestion(editor, '\n'); + } + + const { id, createdAt } = findSuggestionProps(editor, { + at: editor.selection!, + type: 'insert', + }); + + insertBreak(); + + editor.tf.setNodes( + { + [SUGGESTION_KEYS.lineBreak]: { + id, + createdAt, + type: 'insert', + userId: editor.getOptions(BaseSuggestionPlugin).currentUserId!, + }, + }, + { at: path } + ); return; } @@ -106,43 +160,30 @@ export const withSuggestion: OverrideEditor = ({ }, normalizeNode(entry) { - const [node, path] = entry; - - if (node[BaseSuggestionPlugin.key]) { - const pointBefore = editor.api.before(path); - - // Merge with previous suggestion - if (pointBefore) { - const nodeBefore = NodeApi.get(editor, pointBefore.path); - - if ( - (nodeBefore as any)?.[BaseSuggestionPlugin.key] && - (nodeBefore as any)[SUGGESTION_KEYS.id] !== node[SUGGESTION_KEYS.id] - ) { - editor.tf.setNodes( - { [SUGGESTION_KEYS.id]: (nodeBefore as any)[SUGGESTION_KEYS.id] }, - { at: path } - ); - - return; - } - } - // Unset suggestion when there is no suggestion id - if (!getSuggestionId(node)) { - const keys = getSuggestionKeys(node); - editor.tf.unsetNodes( - [BaseSuggestionPlugin.key, 'suggestionDeletion', ...keys], - { at: path } - ); + api.suggestion.withoutSuggestions(() => { + const [node, path] = entry; + + if ( + node[BaseSuggestionPlugin.key] && // Unset suggestion when there is no suggestion id + TextApi.isText(node) && + !getSuggestionKeyId(node) + ) { + editor.tf.unsetNodes([BaseSuggestionPlugin.key, 'suggestionData'], { + at: path, + }); return; } // Unset suggestion when there is no suggestion user id - if (getSuggestionKeys(node).length === 0) { - if (node.suggestionDeletion) { + if ( + node[BaseSuggestionPlugin.key] && + TextApi.isText(node) && + !getSuggestionData(node)?.userId + ) { + if (getSuggestionData(node)?.type === 'remove') { // Unset deletions editor.tf.unsetNodes( - [BaseSuggestionPlugin.key, SUGGESTION_KEYS.id], + [BaseSuggestionPlugin.key, getSuggestionKeyId(node)!], { at: path, } @@ -154,152 +195,25 @@ export const withSuggestion: OverrideEditor = ({ return; } + + normalizeNode(entry); + }); + }, + + removeMark(key) { + if (getOptions().isSuggesting && api.isExpanded()) { + return removeMarkSuggestion(editor, key); } - normalizeNode(entry); + return removeMark(key); + }, + // Remove nodes by block selection + removeNodes(options) { + if (getOptions().isSuggesting) { + return removeNodesSuggestion(editor, options); + } + + return removeNodes(options); }, }, }); - -// editor.tf.apply = (op) => { -// if (getOptions().isSuggesting) { -// if (op.type === 'insert_text') { -// const { text, path, offset } = op; -// -// const id = findSuggestionId(editor, { path, offset }) ?? nanoid(); -// -// // const node = NodeApi.get(editor, path) as TSuggestionText; -// // if (node && node.suggestionId !== id) { -// editor.tf.insertNodes( -// { text, [SuggestionPlugin.key]: true, [SUGGESTION_KEYS.id]: id }, -// { -// at: { -// path, -// offset, -// }, -// select: true, -// } -// ); -// return; -// // } -// } -// if (op.type === 'insert_node') { -// const { node, path } = op; -// -// const suggestionNode = node as TSuggestionText; -// -// if ( -// suggestionNode[SuggestionPlugin.key] && -// suggestionNode[SUGGESTION_KEYS.id] && -// !suggestionNode.suggestionDeletion -// ) { -// apply(op); -// return; -// } -// -// if (!suggestionNode[SuggestionPlugin.key]) { -// // Add suggestion mark -// suggestionNode[SuggestionPlugin.key] = true; -// } -// if (suggestionNode.suggestionDeletion) { -// // Remove suggestion deletion mark -// delete suggestionNode.suggestionDeletion; -// } -// -// const id = findSuggestionId(editor, path) ?? nanoid(); -// suggestionNode[SUGGESTION_KEYS.id] = id; -// -// editor.tf.insertNodes(cloneDeep(node) as any, { at: path }); -// return; -// } -// if (op.type === 'remove_node') { -// const { node } = op; -// -// // additions are safe to remove -// if (node[SuggestionPlugin.key]) { -// if (!node.suggestionDeletion) { -// apply(op); -// } -// return; -// } -// -// const path = editor.api.findPath(node); -// if (!path) return; -// -// const id = findSuggestionId(editor, path) ?? nanoid(); -// -// setSuggestionNodes(editor, { -// at: path, -// suggestionDeletion: true, -// suggestionId: id, -// }); -// // 💡 set instead of remove -> selection gets wrong -// return; -// } -// if (op.type === 'remove_text') { -// const { path, offset, text } = op; -// -// const from = { path, offset }; -// -// const node = NodeApi.get(editor, path); -// if (!node) return; -// -// // additions are safe to remove -// if (node[SuggestionPlugin.key] && !node.suggestionDeletion) { -// apply(op); -// return; -// } -// -// const to = { -// path, -// offset: offset + text.length, -// }; -// const id = -// findSuggestionId(editor, { -// anchor: from, -// focus: to, -// }) ?? nanoid(); -// -// setSuggestionNodes(editor, { -// at: { -// anchor: from, -// focus: to, -// }, -// suggestionDeletion: true, -// suggestionId: id, -// }); -// // 💡 set instead of remove -> selection gets wrong -// return; -// } -// if (op.type === 'move_node') { -// const node = NodeApi.get(editor, op.path); -// if (node && editor.api.isBlock(node) && !node[SuggestionPlugin.key]) { -// // TODO: ? -// return; -// } -// } -// if (op.type === 'merge_node') { -// const node = NodeApi.get(editor, op.path); -// if (node && editor.api.isBlock(node)) { -// // if (node && editor.api.isBlock(node) && !node[SuggestionPlugin.key]) { -// // TODO: delete block suggestion -// return; -// } -// } -// if (op.type === 'split_node') { -// const node = NodeApi.get(editor, op.path); -// // allow splitting suggestion blocks -// if (node && editor.api.isBlock(node) && !node[SuggestionPlugin.key]) { -// // TODO: insert block suggestion -// return; -// } -// } -// if (op.type === 'set_selection') { -// if (editor.preventSelection) { -// return; -// } -// } -// } -// -// apply(op); -// }; diff --git a/packages/suggestion/src/react/SuggestionPlugin.tsx b/packages/suggestion/src/react/SuggestionPlugin.tsx index e7020ff8ab..4d210bc219 100644 --- a/packages/suggestion/src/react/SuggestionPlugin.tsx +++ b/packages/suggestion/src/react/SuggestionPlugin.tsx @@ -2,8 +2,47 @@ import { toPlatePlugin } from '@udecode/plate/react'; import { BaseSuggestionPlugin } from '../lib/BaseSuggestionPlugin'; import { useHooksSuggestion } from './useHooksSuggestion'; +import { isSlateString } from '@udecode/plate'; +import { findSuggestionNode, getSuggestionId } from '../lib'; /** Enables support for suggestions in the editor. */ export const SuggestionPlugin = toPlatePlugin(BaseSuggestionPlugin, { useHooks: useHooksSuggestion as any, + handlers:{ + // unset active suggestion when clicking outside of suggestion + onClick: ({ editor, event, setOption }) => { + let leaf = event.target as HTMLElement; + let isSet = false; + + const unsetActiveSuggestion = () => { + setOption('activeSuggestionId', null); + isSet = true; + }; + + if (!isSlateString(leaf)) unsetActiveSuggestion(); + + while (leaf.parentElement) { + if (leaf.classList.contains(`slate-${BaseSuggestionPlugin.key}`)) { + const suggestionEntry = findSuggestionNode(editor); + + if (!suggestionEntry) { + unsetActiveSuggestion(); + + break; + } + + const id = getSuggestionId(suggestionEntry[0]); + + setOption('activeSuggestionId', id ?? null); + isSet = true; + + break; + } + + leaf = leaf.parentElement; + } + + if (!isSet) unsetActiveSuggestion(); + }, + } }); diff --git a/packages/suggestion/src/react/useHooksSuggestion.ts b/packages/suggestion/src/react/useHooksSuggestion.ts index a5c4f78ca2..2cbafab705 100644 --- a/packages/suggestion/src/react/useHooksSuggestion.ts +++ b/packages/suggestion/src/react/useHooksSuggestion.ts @@ -7,7 +7,7 @@ import { useEditorVersion } from '@udecode/plate/react'; import { type SuggestionConfig, findSuggestionNode, - getSuggestionId, + getSuggestionKeyId, } from '../lib'; export const useHooksSuggestion: UseHooks = ({ @@ -34,7 +34,7 @@ export const useHooksSuggestion: UseHooks = ({ const [suggestionNode] = suggestionEntry; - const id = getSuggestionId(suggestionNode); + const id = getSuggestionKeyId(suggestionNode); if (!id) return resetActiveSuggestion(); diff --git a/yarn.lock b/yarn.lock index 21b53120fc..0b4287e452 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6021,7 +6021,7 @@ __metadata: version: 0.0.0-use.local resolution: "@udecode/cn@workspace:packages/cn" dependencies: - "@udecode/react-utils": "npm:43.0.0" + "@udecode/react-utils": "npm:44.0.1" peerDependencies: class-variance-authority: ">=0.7.0" react: ">=18.0.0" @@ -6034,13 +6034,13 @@ __metadata: version: 0.0.0-use.local resolution: "@udecode/plate-ai@workspace:packages/ai" dependencies: - "@udecode/plate-combobox": "npm:43.0.0" - "@udecode/plate-markdown": "npm:43.0.0" - "@udecode/plate-selection": "npm:43.0.0" + "@udecode/plate-combobox": "npm:44.0.0" + "@udecode/plate-markdown": "npm:44.0.0" + "@udecode/plate-selection": "npm:44.0.0" ai: "npm:^3.4.33" lodash: "npm:^4.17.21" peerDependencies: - "@udecode/plate": ">=43.0.5" + "@udecode/plate": ">=44.0.0" react: ">=18.0.0" react-dom: ">=18.0.0" languageName: unknown @@ -6052,7 +6052,7 @@ __metadata: dependencies: "@udecode/plate": "workspace:^" peerDependencies: - "@udecode/plate": ">=43.0.5" + "@udecode/plate": ">=44.0.1" react: ">=18.0.0" react-dom: ">=18.0.0" languageName: unknown @@ -6065,7 +6065,7 @@ __metadata: "@udecode/plate": "workspace:^" lodash: "npm:^4.17.21" peerDependencies: - "@udecode/plate": ">=43.0.5" + "@udecode/plate": ">=44.0.1" react: ">=18.0.0" react-dom: ">=18.0.0" languageName: unknown @@ -6076,11 +6076,11 @@ __metadata: resolution: "@udecode/plate-basic-elements@workspace:packages/basic-elements" dependencies: "@udecode/plate": "workspace:^" - "@udecode/plate-block-quote": "npm:43.0.0" - "@udecode/plate-code-block": "npm:43.1.0" - "@udecode/plate-heading": "npm:43.0.0" + "@udecode/plate-block-quote": "npm:44.0.0" + "@udecode/plate-code-block": "npm:44.0.0" + "@udecode/plate-heading": "npm:44.0.0" peerDependencies: - "@udecode/plate": ">=43.0.5" + "@udecode/plate": ">=44.0.1" react: ">=18.0.0" react-dom: ">=18.0.0" languageName: unknown @@ -6092,19 +6092,19 @@ __metadata: dependencies: "@udecode/plate": "workspace:^" peerDependencies: - "@udecode/plate": ">=43.0.5" + "@udecode/plate": ">=44.0.1" react: ">=18.0.0" react-dom: ">=18.0.0" languageName: unknown linkType: soft -"@udecode/plate-block-quote@npm:43.0.0, @udecode/plate-block-quote@workspace:^, @udecode/plate-block-quote@workspace:packages/block-quote": +"@udecode/plate-block-quote@npm:44.0.0, @udecode/plate-block-quote@workspace:^, @udecode/plate-block-quote@workspace:packages/block-quote": version: 0.0.0-use.local resolution: "@udecode/plate-block-quote@workspace:packages/block-quote" dependencies: "@udecode/plate": "workspace:^" peerDependencies: - "@udecode/plate": ">=43.0.5" + "@udecode/plate": ">=44.0.1" react: ">=18.0.0" react-dom: ">=18.0.0" languageName: unknown @@ -6116,7 +6116,7 @@ __metadata: dependencies: "@udecode/plate": "workspace:^" peerDependencies: - "@udecode/plate": ">=43.0.5" + "@udecode/plate": ">=44.0.1" react: ">=18.0.0" react-dom: ">=18.0.0" languageName: unknown @@ -6128,7 +6128,7 @@ __metadata: dependencies: "@udecode/plate": "workspace:^" peerDependencies: - "@udecode/plate": ">=43.0.5" + "@udecode/plate": ">=44.0.1" react: ">=18.0.0" react-dom: ">=18.0.0" languageName: unknown @@ -6141,32 +6141,32 @@ __metadata: "@udecode/plate": "workspace:^" react-textarea-autosize: "npm:^8.5.7" peerDependencies: - "@udecode/plate": ">=43.0.5" + "@udecode/plate": ">=44.0.1" react: ">=18.0.0" react-dom: ">=18.0.0" languageName: unknown linkType: soft -"@udecode/plate-code-block@npm:43.1.0, @udecode/plate-code-block@workspace:^, @udecode/plate-code-block@workspace:packages/code-block": +"@udecode/plate-code-block@npm:44.0.0, @udecode/plate-code-block@workspace:^, @udecode/plate-code-block@workspace:packages/code-block": version: 0.0.0-use.local resolution: "@udecode/plate-code-block@workspace:packages/code-block" dependencies: "@udecode/plate": "workspace:^" prismjs: "npm:^1.29.0" peerDependencies: - "@udecode/plate": ">=43.0.5" + "@udecode/plate": ">=44.0.1" react: ">=18.0.0" react-dom: ">=18.0.0" languageName: unknown linkType: soft -"@udecode/plate-combobox@npm:43.0.0, @udecode/plate-combobox@workspace:^, @udecode/plate-combobox@workspace:packages/combobox": +"@udecode/plate-combobox@npm:44.0.0, @udecode/plate-combobox@workspace:^, @udecode/plate-combobox@workspace:packages/combobox": version: 0.0.0-use.local resolution: "@udecode/plate-combobox@workspace:packages/combobox" dependencies: "@udecode/plate": "workspace:^" peerDependencies: - "@udecode/plate": ">=43.0.5" + "@udecode/plate": ">=44.0.1" react: ">=18.0.0" react-dom: ">=18.0.0" languageName: unknown @@ -6179,19 +6179,19 @@ __metadata: "@udecode/plate": "workspace:^" lodash: "npm:^4.17.21" peerDependencies: - "@udecode/plate": ">=43.0.5" + "@udecode/plate": ">=44.0.1" react: ">=18.0.0" react-dom: ">=18.0.0" languageName: unknown linkType: soft -"@udecode/plate-core@npm:43.0.5, @udecode/plate-core@workspace:packages/core": +"@udecode/plate-core@npm:44.0.1, @udecode/plate-core@workspace:packages/core": version: 0.0.0-use.local resolution: "@udecode/plate-core@workspace:packages/core" dependencies: "@udecode/react-hotkeys": "npm:37.0.0" - "@udecode/react-utils": "npm:43.0.0" - "@udecode/slate": "npm:42.2.5" + "@udecode/react-utils": "npm:44.0.1" + "@udecode/slate": "npm:44.0.0" "@udecode/utils": "npm:42.0.0" clsx: "npm:^2.1.1" html-entities: "npm:^2.5.2" @@ -6219,10 +6219,10 @@ __metadata: dependencies: "@types/papaparse": "npm:^5.3.15" "@udecode/plate": "workspace:^" - "@udecode/plate-table": "npm:43.0.3" + "@udecode/plate-table": "npm:44.0.0" papaparse: "npm:^5.5.2" peerDependencies: - "@udecode/plate": ">=43.0.5" + "@udecode/plate": ">=44.0.1" react: ">=18.0.0" react-dom: ">=18.0.0" languageName: unknown @@ -6234,7 +6234,7 @@ __metadata: dependencies: "@udecode/plate": "workspace:^" peerDependencies: - "@udecode/plate": ">=43.0.5" + "@udecode/plate": ">=44.0.1" react: ">=18.0.0" react-dom: ">=18.0.0" languageName: unknown @@ -6246,13 +6246,13 @@ __metadata: dependencies: "@udecode/plate": "workspace:^" peerDependencies: - "@udecode/plate": ">=43.0.5" + "@udecode/plate": ">=44.0.1" react: ">=18.0.0" react-dom: ">=18.0.0" languageName: unknown linkType: soft -"@udecode/plate-diff@npm:43.0.0, @udecode/plate-diff@workspace:^, @udecode/plate-diff@workspace:packages/diff": +"@udecode/plate-diff@npm:44.0.0, @udecode/plate-diff@workspace:^, @udecode/plate-diff@workspace:packages/diff": version: 0.0.0-use.local resolution: "@udecode/plate-diff@workspace:packages/diff" dependencies: @@ -6260,7 +6260,7 @@ __metadata: diff-match-patch-ts: "npm:^0.6.0" lodash: "npm:^4.17.21" peerDependencies: - "@udecode/plate": ">=43.0.5" + "@udecode/plate": ">=44.0.1" react: ">=18.0.0" react-dom: ">=18.0.0" languageName: unknown @@ -6274,7 +6274,7 @@ __metadata: lodash: "npm:^4.17.21" raf: "npm:^3.4.1" peerDependencies: - "@udecode/plate": ">=43.0.5" + "@udecode/plate": ">=44.0.1" react: ">=18.0.0" react-dnd: ">=14.0.0" react-dnd-html5-backend: ">=14.0.0" @@ -6287,14 +6287,14 @@ __metadata: resolution: "@udecode/plate-docx@workspace:packages/docx" dependencies: "@udecode/plate": "workspace:^" - "@udecode/plate-heading": "npm:43.0.0" - "@udecode/plate-indent": "npm:43.0.0" - "@udecode/plate-indent-list": "npm:43.0.5" - "@udecode/plate-media": "npm:43.0.0" - "@udecode/plate-table": "npm:43.0.3" + "@udecode/plate-heading": "npm:44.0.0" + "@udecode/plate-indent": "npm:44.0.0" + "@udecode/plate-indent-list": "npm:44.0.0" + "@udecode/plate-media": "npm:44.0.0" + "@udecode/plate-table": "npm:44.0.0" validator: "npm:^13.12.0" peerDependencies: - "@udecode/plate": ">=43.0.5" + "@udecode/plate": ">=44.0.1" react: ">=18.0.0" react-dom: ">=18.0.0" languageName: unknown @@ -6306,10 +6306,10 @@ __metadata: dependencies: "@emoji-mart/data": "npm:^1.2.1" "@udecode/plate": "workspace:^" - "@udecode/plate-combobox": "npm:43.0.0" + "@udecode/plate-combobox": "npm:44.0.0" peerDependencies: "@emoji-mart/data": ">=1.2.0" - "@udecode/plate": ">=43.0.5" + "@udecode/plate": ">=44.0.1" react: ">=18.0.0" react-dom: ">=18.0.0" languageName: unknown @@ -6322,7 +6322,7 @@ __metadata: "@excalidraw/excalidraw": "npm:0.16.4" "@udecode/plate": "workspace:^" peerDependencies: - "@udecode/plate": ">=43.0.5" + "@udecode/plate": ">=44.0.1" react: ">=18.0.0" react-dom: ">=18.0.0" languageName: unknown @@ -6334,13 +6334,13 @@ __metadata: dependencies: "@udecode/plate": "workspace:^" peerDependencies: - "@udecode/plate": ">=43.0.5" + "@udecode/plate": ">=44.0.1" react: ">=18.0.0" react-dom: ">=18.0.0" languageName: unknown linkType: soft -"@udecode/plate-floating@npm:43.0.0, @udecode/plate-floating@workspace:^, @udecode/plate-floating@workspace:packages/floating": +"@udecode/plate-floating@npm:44.0.0, @udecode/plate-floating@workspace:^, @udecode/plate-floating@workspace:packages/floating": version: 0.0.0-use.local resolution: "@udecode/plate-floating@workspace:packages/floating" dependencies: @@ -6348,7 +6348,7 @@ __metadata: "@floating-ui/react": "npm:^0.27.3" "@udecode/plate": "workspace:^" peerDependencies: - "@udecode/plate": ">=43.0.5" + "@udecode/plate": ">=44.0.1" react: ">=18.0.0" react-dom: ">=18.0.0" languageName: unknown @@ -6361,19 +6361,19 @@ __metadata: "@udecode/plate": "workspace:^" lodash: "npm:^4.17.21" peerDependencies: - "@udecode/plate": ">=43.0.5" + "@udecode/plate": ">=44.0.1" react: ">=18.0.0" react-dom: ">=18.0.0" languageName: unknown linkType: soft -"@udecode/plate-heading@npm:43.0.0, @udecode/plate-heading@workspace:^, @udecode/plate-heading@workspace:packages/heading": +"@udecode/plate-heading@npm:44.0.0, @udecode/plate-heading@workspace:^, @udecode/plate-heading@workspace:packages/heading": version: 0.0.0-use.local resolution: "@udecode/plate-heading@workspace:packages/heading" dependencies: "@udecode/plate": "workspace:^" peerDependencies: - "@udecode/plate": ">=43.0.5" + "@udecode/plate": ">=44.0.1" react: ">=18.0.0" react-dom: ">=18.0.0" languageName: unknown @@ -6385,7 +6385,7 @@ __metadata: dependencies: "@udecode/plate": "workspace:^" peerDependencies: - "@udecode/plate": ">=43.0.5" + "@udecode/plate": ">=44.0.1" react: ">=18.0.0" react-dom: ">=18.0.0" languageName: unknown @@ -6397,34 +6397,34 @@ __metadata: dependencies: "@udecode/plate": "workspace:^" peerDependencies: - "@udecode/plate": ">=43.0.5" + "@udecode/plate": ">=44.0.1" react: ">=18.0.0" react-dom: ">=18.0.0" languageName: unknown linkType: soft -"@udecode/plate-indent-list@npm:43.0.5, @udecode/plate-indent-list@workspace:^, @udecode/plate-indent-list@workspace:packages/indent-list": +"@udecode/plate-indent-list@npm:44.0.0, @udecode/plate-indent-list@workspace:^, @udecode/plate-indent-list@workspace:packages/indent-list": version: 0.0.0-use.local resolution: "@udecode/plate-indent-list@workspace:packages/indent-list" dependencies: "@udecode/plate": "workspace:^" - "@udecode/plate-indent": "npm:43.0.0" - "@udecode/plate-list": "npm:43.0.0" + "@udecode/plate-indent": "npm:44.0.0" + "@udecode/plate-list": "npm:44.0.0" clsx: "npm:^2.1.1" peerDependencies: - "@udecode/plate": ">=43.0.5" + "@udecode/plate": ">=44.0.1" react: ">=18.0.0" react-dom: ">=18.0.0" languageName: unknown linkType: soft -"@udecode/plate-indent@npm:43.0.0, @udecode/plate-indent@workspace:^, @udecode/plate-indent@workspace:packages/indent": +"@udecode/plate-indent@npm:44.0.0, @udecode/plate-indent@workspace:^, @udecode/plate-indent@workspace:packages/indent": version: 0.0.0-use.local resolution: "@udecode/plate-indent@workspace:packages/indent" dependencies: "@udecode/plate": "workspace:^" peerDependencies: - "@udecode/plate": ">=43.0.5" + "@udecode/plate": ">=44.0.1" react: ">=18.0.0" react-dom: ">=18.0.0" languageName: unknown @@ -6437,7 +6437,7 @@ __metadata: "@udecode/plate": "workspace:^" juice: "npm:^11.0.0" peerDependencies: - "@udecode/plate": ">=43.0.5" + "@udecode/plate": ">=44.0.1" react: ">=18.0.0" react-dom: ">=18.0.0" languageName: unknown @@ -6449,7 +6449,7 @@ __metadata: dependencies: "@udecode/plate": "workspace:^" peerDependencies: - "@udecode/plate": ">=43.0.5" + "@udecode/plate": ">=44.0.1" react: ">=18.0.0" react-dom: ">=18.0.0" languageName: unknown @@ -6461,7 +6461,7 @@ __metadata: dependencies: "@udecode/plate": "workspace:^" peerDependencies: - "@udecode/plate": ">=43.0.5" + "@udecode/plate": ">=44.0.1" react: ">=18.0.0" react-dom: ">=18.0.0" languageName: unknown @@ -6473,7 +6473,7 @@ __metadata: dependencies: "@udecode/plate": "workspace:^" peerDependencies: - "@udecode/plate": ">=43.0.5" + "@udecode/plate": ">=44.0.1" react: ">=18.0.0" react-dom: ">=18.0.0" languageName: unknown @@ -6484,30 +6484,30 @@ __metadata: resolution: "@udecode/plate-link@workspace:packages/link" dependencies: "@udecode/plate": "workspace:^" - "@udecode/plate-floating": "npm:43.0.0" - "@udecode/plate-normalizers": "npm:43.0.0" + "@udecode/plate-floating": "npm:44.0.0" + "@udecode/plate-normalizers": "npm:44.0.0" peerDependencies: - "@udecode/plate": ">=43.0.5" + "@udecode/plate": ">=44.0.1" react: ">=18.0.0" react-dom: ">=18.0.0" languageName: unknown linkType: soft -"@udecode/plate-list@npm:43.0.0, @udecode/plate-list@workspace:^, @udecode/plate-list@workspace:packages/list": +"@udecode/plate-list@npm:44.0.0, @udecode/plate-list@workspace:^, @udecode/plate-list@workspace:packages/list": version: 0.0.0-use.local resolution: "@udecode/plate-list@workspace:packages/list" dependencies: "@udecode/plate": "workspace:^" - "@udecode/plate-reset-node": "npm:43.0.0" + "@udecode/plate-reset-node": "npm:44.0.0" lodash: "npm:^4.17.21" peerDependencies: - "@udecode/plate": ">=43.0.5" + "@udecode/plate": ">=44.0.1" react: ">=18.0.0" react-dom: ">=18.0.0" languageName: unknown linkType: soft -"@udecode/plate-markdown@npm:43.0.0, @udecode/plate-markdown@workspace:^, @udecode/plate-markdown@workspace:packages/markdown": +"@udecode/plate-markdown@npm:44.0.0, @udecode/plate-markdown@workspace:^, @udecode/plate-markdown@workspace:packages/markdown": version: 0.0.0-use.local resolution: "@udecode/plate-markdown@workspace:packages/markdown" dependencies: @@ -6518,7 +6518,7 @@ __metadata: remark-parse: "npm:^11.0.0" unified: "npm:^11.0.5" peerDependencies: - "@udecode/plate": ">=43.0.5" + "@udecode/plate": ">=44.0.1" react: ">=18.0.0" react-dom: ">=18.0.0" languageName: unknown @@ -6532,20 +6532,20 @@ __metadata: "@udecode/plate": "workspace:^" katex: "npm:0.16.21" peerDependencies: - "@udecode/plate": ">=43.0.5" + "@udecode/plate": ">=44.0.1" react: ">=18.0.0" react-dom: ">=18.0.0" languageName: unknown linkType: soft -"@udecode/plate-media@npm:43.0.0, @udecode/plate-media@workspace:^, @udecode/plate-media@workspace:packages/media": +"@udecode/plate-media@npm:44.0.0, @udecode/plate-media@workspace:^, @udecode/plate-media@workspace:packages/media": version: 0.0.0-use.local resolution: "@udecode/plate-media@workspace:packages/media" dependencies: "@udecode/plate": "workspace:^" js-video-url-parser: "npm:^0.5.1" peerDependencies: - "@udecode/plate": ">=43.0.5" + "@udecode/plate": ">=44.0.1" react: ">=18.0.0" react-dom: ">=18.0.0" languageName: unknown @@ -6556,35 +6556,35 @@ __metadata: resolution: "@udecode/plate-mention@workspace:packages/mention" dependencies: "@udecode/plate": "workspace:^" - "@udecode/plate-combobox": "npm:43.0.0" + "@udecode/plate-combobox": "npm:44.0.0" peerDependencies: - "@udecode/plate": ">=43.0.5" + "@udecode/plate": ">=44.0.1" react: ">=18.0.0" react-dom: ">=18.0.0" languageName: unknown linkType: soft -"@udecode/plate-node-id@npm:43.0.3, @udecode/plate-node-id@workspace:^, @udecode/plate-node-id@workspace:packages/node-id": +"@udecode/plate-node-id@npm:44.0.0, @udecode/plate-node-id@workspace:^, @udecode/plate-node-id@workspace:packages/node-id": version: 0.0.0-use.local resolution: "@udecode/plate-node-id@workspace:packages/node-id" dependencies: "@udecode/plate": "workspace:^" lodash: "npm:^4.17.21" peerDependencies: - "@udecode/plate": ">=43.0.5" + "@udecode/plate": ">=44.0.1" react: ">=18.0.0" react-dom: ">=18.0.0" languageName: unknown linkType: soft -"@udecode/plate-normalizers@npm:43.0.0, @udecode/plate-normalizers@workspace:^, @udecode/plate-normalizers@workspace:packages/normalizers": +"@udecode/plate-normalizers@npm:44.0.0, @udecode/plate-normalizers@workspace:^, @udecode/plate-normalizers@workspace:packages/normalizers": version: 0.0.0-use.local resolution: "@udecode/plate-normalizers@workspace:packages/normalizers" dependencies: "@udecode/plate": "workspace:^" lodash: "npm:^4.17.21" peerDependencies: - "@udecode/plate": ">=43.0.5" + "@udecode/plate": ">=44.0.1" react: ">=18.0.0" react-dom: ">=18.0.0" languageName: unknown @@ -6597,31 +6597,31 @@ __metadata: "@udecode/plate": "workspace:^" peerDependencies: "@playwright/test": ">=1.42.1" - "@udecode/plate": ">=43.0.5" + "@udecode/plate": ">=44.0.1" react: ">=18.0.0" react-dom: ">=18.0.0" languageName: unknown linkType: soft -"@udecode/plate-reset-node@npm:43.0.0, @udecode/plate-reset-node@workspace:^, @udecode/plate-reset-node@workspace:packages/reset-node": +"@udecode/plate-reset-node@npm:44.0.0, @udecode/plate-reset-node@workspace:^, @udecode/plate-reset-node@workspace:packages/reset-node": version: 0.0.0-use.local resolution: "@udecode/plate-reset-node@workspace:packages/reset-node" dependencies: "@udecode/plate": "workspace:^" peerDependencies: - "@udecode/plate": ">=43.0.5" + "@udecode/plate": ">=44.0.1" react: ">=18.0.0" react-dom: ">=18.0.0" languageName: unknown linkType: soft -"@udecode/plate-resizable@npm:43.0.0, @udecode/plate-resizable@workspace:^, @udecode/plate-resizable@workspace:packages/resizable": +"@udecode/plate-resizable@npm:44.0.0, @udecode/plate-resizable@workspace:^, @udecode/plate-resizable@workspace:packages/resizable": version: 0.0.0-use.local resolution: "@udecode/plate-resizable@workspace:packages/resizable" dependencies: "@udecode/plate": "workspace:^" peerDependencies: - "@udecode/plate": ">=43.0.5" + "@udecode/plate": ">=44.0.1" react: ">=18.0.0" react-dom: ">=18.0.0" languageName: unknown @@ -6633,20 +6633,20 @@ __metadata: dependencies: "@udecode/plate": "workspace:^" peerDependencies: - "@udecode/plate": ">=43.0.5" + "@udecode/plate": ">=44.0.1" react: ">=18.0.0" react-dom: ">=18.0.0" languageName: unknown linkType: soft -"@udecode/plate-selection@npm:43.0.0, @udecode/plate-selection@workspace:^, @udecode/plate-selection@workspace:packages/selection": +"@udecode/plate-selection@npm:44.0.0, @udecode/plate-selection@workspace:^, @udecode/plate-selection@workspace:packages/selection": version: 0.0.0-use.local resolution: "@udecode/plate-selection@workspace:packages/selection" dependencies: "@udecode/plate": "workspace:^" copy-to-clipboard: "npm:^3.3.3" peerDependencies: - "@udecode/plate": ">=43.0.5" + "@udecode/plate": ">=44.0.1" react: ">=18.0.0" react-dom: ">=18.0.0" languageName: unknown @@ -6657,9 +6657,9 @@ __metadata: resolution: "@udecode/plate-slash-command@workspace:packages/slash-command" dependencies: "@udecode/plate": "workspace:^" - "@udecode/plate-combobox": "npm:43.0.0" + "@udecode/plate-combobox": "npm:44.0.0" peerDependencies: - "@udecode/plate": ">=43.0.5" + "@udecode/plate": ">=44.0.1" react: ">=18.0.0" react-dom: ">=18.0.0" languageName: unknown @@ -6670,10 +6670,10 @@ __metadata: resolution: "@udecode/plate-suggestion@workspace:packages/suggestion" dependencies: "@udecode/plate": "workspace:^" - "@udecode/plate-diff": "npm:43.0.0" + "@udecode/plate-diff": "npm:44.0.0" lodash: "npm:^4.17.21" peerDependencies: - "@udecode/plate": ">=43.0.5" + "@udecode/plate": ">=44.0.1" react: ">=18.0.0" react-dom: ">=18.0.0" languageName: unknown @@ -6686,22 +6686,22 @@ __metadata: "@udecode/plate": "workspace:^" tabbable: "npm:^6.2.0" peerDependencies: - "@udecode/plate": ">=43.0.5" + "@udecode/plate": ">=44.0.1" react: ">=18.0.0" react-dom: ">=18.0.0" languageName: unknown linkType: soft -"@udecode/plate-table@npm:43.0.3, @udecode/plate-table@workspace:^, @udecode/plate-table@workspace:packages/table": +"@udecode/plate-table@npm:44.0.0, @udecode/plate-table@workspace:^, @udecode/plate-table@workspace:packages/table": version: 0.0.0-use.local resolution: "@udecode/plate-table@workspace:packages/table" dependencies: "@udecode/plate": "workspace:^" - "@udecode/plate-node-id": "npm:43.0.3" - "@udecode/plate-resizable": "npm:43.0.0" + "@udecode/plate-node-id": "npm:44.0.0" + "@udecode/plate-resizable": "npm:44.0.0" lodash: "npm:^4.17.21" peerDependencies: - "@udecode/plate": ">=43.0.5" + "@udecode/plate": ">=44.0.1" react: ">=18.0.0" react-dom: ">=18.0.0" languageName: unknown @@ -6713,7 +6713,7 @@ __metadata: dependencies: "@udecode/plate": "workspace:^" peerDependencies: - "@udecode/plate": ">=43.0.5" + "@udecode/plate": ">=44.0.1" react: ">=18.0.0" react-dom: ">=18.0.0" languageName: unknown @@ -6723,7 +6723,7 @@ __metadata: version: 0.0.0-use.local resolution: "@udecode/plate-test-utils@workspace:packages/test-utils" dependencies: - "@udecode/slate": "npm:42.2.5" + "@udecode/slate": "npm:44.0.0" slate-hyperscript: "npm:0.100.0" languageName: unknown linkType: soft @@ -6733,11 +6733,11 @@ __metadata: resolution: "@udecode/plate-toggle@workspace:packages/toggle" dependencies: "@udecode/plate": "workspace:^" - "@udecode/plate-indent": "npm:43.0.0" - "@udecode/plate-node-id": "npm:43.0.3" + "@udecode/plate-indent": "npm:44.0.0" + "@udecode/plate-node-id": "npm:44.0.0" lodash: "npm:^4.17.21" peerDependencies: - "@udecode/plate": ">=43.0.5" + "@udecode/plate": ">=44.0.1" react: ">=18.0.0" react-dom: ">=18.0.0" languageName: unknown @@ -6749,19 +6749,19 @@ __metadata: dependencies: "@udecode/plate": "workspace:^" peerDependencies: - "@udecode/plate": ">=43.0.5" + "@udecode/plate": ">=44.0.1" react: ">=18.0.0" react-dom: ">=18.0.0" languageName: unknown linkType: soft -"@udecode/plate-utils@npm:43.0.5, @udecode/plate-utils@workspace:packages/plate-utils": +"@udecode/plate-utils@npm:44.0.1, @udecode/plate-utils@workspace:packages/plate-utils": version: 0.0.0-use.local resolution: "@udecode/plate-utils@workspace:packages/plate-utils" dependencies: - "@udecode/plate-core": "npm:43.0.5" - "@udecode/react-utils": "npm:43.0.0" - "@udecode/slate": "npm:42.2.5" + "@udecode/plate-core": "npm:44.0.1" + "@udecode/react-utils": "npm:44.0.1" + "@udecode/slate": "npm:44.0.0" "@udecode/utils": "npm:42.0.0" clsx: "npm:^2.1.1" lodash: "npm:^4.17.21" @@ -6780,7 +6780,7 @@ __metadata: "@udecode/plate": "workspace:^" yjs: "npm:^13.6.23" peerDependencies: - "@udecode/plate": ">=43.0.5" + "@udecode/plate": ">=44.0.1" react: ">=18.0.0" react-dom: ">=18.0.0" languageName: unknown @@ -6790,11 +6790,11 @@ __metadata: version: 0.0.0-use.local resolution: "@udecode/plate@workspace:packages/plate" dependencies: - "@udecode/plate-core": "npm:43.0.5" - "@udecode/plate-utils": "npm:43.0.5" + "@udecode/plate-core": "npm:44.0.1" + "@udecode/plate-utils": "npm:44.0.1" "@udecode/react-hotkeys": "npm:37.0.0" - "@udecode/react-utils": "npm:43.0.0" - "@udecode/slate": "npm:42.2.5" + "@udecode/react-utils": "npm:44.0.1" + "@udecode/slate": "npm:44.0.0" "@udecode/utils": "npm:42.0.0" peerDependencies: react: ">=18.0.0" @@ -6811,7 +6811,7 @@ __metadata: languageName: unknown linkType: soft -"@udecode/react-utils@npm:43.0.0, @udecode/react-utils@workspace:packages/react-utils": +"@udecode/react-utils@npm:44.0.1, @udecode/react-utils@workspace:packages/react-utils": version: 0.0.0-use.local resolution: "@udecode/react-utils@workspace:packages/react-utils" dependencies: @@ -6824,7 +6824,7 @@ __metadata: languageName: unknown linkType: soft -"@udecode/slate@npm:42.2.5, @udecode/slate@workspace:packages/slate": +"@udecode/slate@npm:44.0.0, @udecode/slate@workspace:packages/slate": version: 0.0.0-use.local resolution: "@udecode/slate@workspace:packages/slate" dependencies: