Skip to content

Commit

Permalink
fork
Browse files Browse the repository at this point in the history
  • Loading branch information
felixfeng33 committed Feb 9, 2025
1 parent 0301a60 commit 5809034
Show file tree
Hide file tree
Showing 24 changed files with 1,018 additions and 442 deletions.
61 changes: 37 additions & 24 deletions packages/suggestion/src/lib/BaseSuggestionPlugin.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,33 @@
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<
'suggestion',
{
activeSuggestionId: string | null;
currentUserId: string | null;
hoverSuggestionId: string | null;
isSuggesting: boolean;
suggestions: Record<string, TSuggestion>;
uniquePathMap: Map<string, Path>;
users: Record<string, SuggestionUser>;
onSuggestionAdd: ((value: Partial<TSuggestion>) => void) | null;
onSuggestionDelete: ((id: string) => void) | null;
Expand All @@ -28,63 +36,63 @@ export type SuggestionConfig = PluginConfig<
value: Partial<Omit<TSuggestion, 'id'>> & Pick<TSuggestion, 'id'>
) => void)
| null;
},
{
suggestion: {
addSuggestion: (
value: WithPartial<TSuggestion, 'createdAt' | 'id' | 'userId'>
) => void;
removeSuggestion: (id: string | null) => void;
updateSuggestion: (
id: string | null,
value: Partial<TSuggestion>
) => 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<TSuggestion, 'id' | 'userId'>) => void;
removeSuggestion: (id: string | null) => void;
updateSuggestion: (id: string | null, value: Partial<TSuggestion>) => 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<SuggestionConfig>({
key: 'suggestion',
node: { isLeaf: true },
options: {
activeSuggestionId: null,
currentUserId: null,
hoverSuggestionId: null,
isSuggesting: false,
suggestions: {},
uniquePathMap: new Map(),
users: {},
onSuggestionAdd: null,
onSuggestionDelete: null,
onSuggestionUpdate: null,
},
})
.overrideEditor(withSuggestion)
.extendSelectors<SuggestionConfig['selectors']>(({ 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<Partial<SuggestionConfig['api']['suggestion']>>(
({ getOptions, setOptions }) => ({
.extendApi<Partial<SuggestionPluginApi>>(
({ getOption, getOptions, setOption, setOptions }) => ({
addSuggestion: (value) => {
const { currentUserId } = getOptions();

Expand All @@ -93,7 +101,6 @@ export const BaseSuggestionPlugin = createTSlatePlugin<SuggestionConfig>({
const id = value.id ?? nanoid();
const newSuggestion: TSuggestion = {
id,
createdAt: Date.now(),
userId: currentUserId,
...value,
};
Expand All @@ -116,5 +123,11 @@ export const BaseSuggestionPlugin = createTSlatePlugin<SuggestionConfig>({
draft.suggestions![id] = { ...draft.suggestions![id], ...value };
});
},
withoutSuggestions: (fn) => {
const prev = getOption('isSuggesting');
setOption('isSuggesting', false);
fn();
setOption('isSuggesting', prev);
},
})
);
1 change: 1 addition & 0 deletions packages/suggestion/src/lib/diffToSuggestions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { type ComputeDiffOptions, computeDiff } from '@udecode/plate-diff';

import { getSuggestionProps } from './transforms';

// TODO: refactor
export function diffToSuggestions<E extends SlateEditor>(
editor: E,
doc0: Descendant[],
Expand Down
64 changes: 54 additions & 10 deletions packages/suggestion/src/lib/queries/findSuggestionId.ts
Original file line number Diff line number Diff line change
@@ -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,
});
Expand All @@ -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);
Expand All @@ -37,10 +53,38 @@ export const findSuggestionId = (editor: SlateEditor, at: TLocation) => {
at: prevPoint,
});
}
// <p>111111<insert_break></p>
// <p><cursor /></p>
// 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<TElement>({ 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;
};
100 changes: 77 additions & 23 deletions packages/suggestion/src/lib/transforms/acceptSuggestion.ts
Original file line number Diff line number Diff line change
@@ -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;
}
},
});
});
Expand Down
Loading

0 comments on commit 5809034

Please sign in to comment.