Split transactions when applying suggestions (#465)
This commit is contained in:
parent
b3cb0ea755
commit
029cbb835e
4 changed files with 75 additions and 20 deletions
|
|
@ -180,13 +180,18 @@ export function Canvas({
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleEditorChange = useCallback(
|
const handleEditorChange = useCallback(
|
||||||
(updatedContent: string) => {
|
(updatedContent: string, debounce: boolean) => {
|
||||||
if (document && updatedContent !== document.content) {
|
if (document && updatedContent !== document.content) {
|
||||||
debouncedHandleEditorChange(updatedContent);
|
if (debounce) {
|
||||||
|
debouncedHandleEditorChange(updatedContent);
|
||||||
|
} else {
|
||||||
|
handleContentChange(updatedContent);
|
||||||
|
}
|
||||||
|
|
||||||
setIsContentDirty(true);
|
setIsContentDirty(true);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[document, debouncedHandleEditorChange]
|
[document, debouncedHandleEditorChange, handleContentChange]
|
||||||
);
|
);
|
||||||
|
|
||||||
function getDocumentContentById(index: number) {
|
function getDocumentContentById(index: number) {
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,7 @@ interface WidgetRoot {
|
||||||
|
|
||||||
type EditorProps = {
|
type EditorProps = {
|
||||||
content: string;
|
content: string;
|
||||||
onChange: (updatedContent: string) => void;
|
onChange: (updatedContent: string, debounce: boolean) => void;
|
||||||
status: 'streaming' | 'idle';
|
status: 'streaming' | 'idle';
|
||||||
currentVersionIndex: number;
|
currentVersionIndex: number;
|
||||||
suggestions: Array<Suggestion>;
|
suggestions: Array<Suggestion>;
|
||||||
|
|
@ -95,7 +95,12 @@ function PureEditor({
|
||||||
|
|
||||||
if (transaction.docChanged) {
|
if (transaction.docChanged) {
|
||||||
const content = defaultMarkdownSerializer.serialize(newState.doc);
|
const content = defaultMarkdownSerializer.serialize(newState.doc);
|
||||||
onChange(content);
|
|
||||||
|
if (transaction.getMeta('no-debounce')) {
|
||||||
|
onChange(content, false);
|
||||||
|
} else {
|
||||||
|
onChange(content, true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
@ -137,19 +142,34 @@ function PureEditor({
|
||||||
|
|
||||||
suggestions.forEach((suggestion) => {
|
suggestions.forEach((suggestion) => {
|
||||||
decorations.push(
|
decorations.push(
|
||||||
Decoration.inline(suggestion.selectionStart, suggestion.selectionEnd, {
|
Decoration.inline(
|
||||||
class:
|
suggestion.selectionStart,
|
||||||
'suggestion-highlight bg-yellow-100 hover:bg-yellow-200 dark:hover:bg-yellow-400/50 dark:text-yellow-50 dark:bg-yellow-400/40',
|
suggestion.selectionEnd,
|
||||||
})
|
{
|
||||||
|
class:
|
||||||
|
'suggestion-highlight bg-yellow-100 hover:bg-yellow-200 dark:hover:bg-yellow-400/50 dark:text-yellow-50 dark:bg-yellow-400/40',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
suggestionId: suggestion.id,
|
||||||
|
type: 'highlight',
|
||||||
|
}
|
||||||
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
decorations.push(
|
decorations.push(
|
||||||
Decoration.widget(suggestion.selectionStart, (view) => {
|
Decoration.widget(
|
||||||
const { dom, destroy } = createSuggestionWidget(suggestion, view);
|
suggestion.selectionStart,
|
||||||
const key = `widget-${suggestion.id}`;
|
(view) => {
|
||||||
widgetRootsRef.current.set(key, { destroy });
|
const { dom, destroy } = createSuggestionWidget(suggestion, view);
|
||||||
return dom;
|
const key = `widget-${suggestion.id}`;
|
||||||
})
|
widgetRootsRef.current.set(key, { destroy });
|
||||||
|
return dom;
|
||||||
|
},
|
||||||
|
{
|
||||||
|
suggestionId: suggestion.id,
|
||||||
|
type: 'widget',
|
||||||
|
}
|
||||||
|
)
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import { useState } from 'react';
|
||||||
import { UISuggestion } from '@/lib/editor/suggestions';
|
import { UISuggestion } from '@/lib/editor/suggestions';
|
||||||
|
|
||||||
import { CrossIcon, MessageIcon } from './icons';
|
import { CrossIcon, MessageIcon } from './icons';
|
||||||
|
import useWindowSize from './use-window-size';
|
||||||
import { Button } from '../ui/button';
|
import { Button } from '../ui/button';
|
||||||
|
|
||||||
export const Suggestion = ({
|
export const Suggestion = ({
|
||||||
|
|
@ -16,15 +17,16 @@ export const Suggestion = ({
|
||||||
onApply: () => void;
|
onApply: () => void;
|
||||||
}) => {
|
}) => {
|
||||||
const [isExpanded, setIsExpanded] = useState(false);
|
const [isExpanded, setIsExpanded] = useState(false);
|
||||||
|
const { width: windowWidth } = useWindowSize();
|
||||||
|
|
||||||
return !isExpanded ? (
|
return !isExpanded ? (
|
||||||
<div
|
<div
|
||||||
className="absolute cursor-pointer text-muted-foreground mt-1 -right-8"
|
className="absolute cursor-pointer text-muted-foreground -right-8 p-1"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setIsExpanded(true);
|
setIsExpanded(true);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<MessageIcon size={14} />
|
<MessageIcon size={windowWidth && windowWidth < 768 ? 16 : 14} />
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<motion.div
|
<motion.div
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
|
import { defaultMarkdownSerializer } from 'prosemirror-markdown';
|
||||||
import { Node } from 'prosemirror-model';
|
import { Node } from 'prosemirror-model';
|
||||||
import { PluginKey, Plugin } from 'prosemirror-state';
|
import { PluginKey, Plugin } from 'prosemirror-state';
|
||||||
import { DecorationSet, EditorView } from 'prosemirror-view';
|
import { Decoration, DecorationSet, EditorView } from 'prosemirror-view';
|
||||||
import { createRoot } from 'react-dom/client';
|
import { createRoot } from 'react-dom/client';
|
||||||
|
|
||||||
import { Suggestion as PreviewSuggestion } from '@/components/custom/suggestion';
|
import { Suggestion as PreviewSuggestion } from '@/components/custom/suggestion';
|
||||||
|
|
@ -69,15 +70,42 @@ export function createSuggestionWidget(
|
||||||
const dom = document.createElement('span');
|
const dom = document.createElement('span');
|
||||||
const root = createRoot(dom);
|
const root = createRoot(dom);
|
||||||
|
|
||||||
|
dom.addEventListener('mousedown', (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
view.dom.blur();
|
||||||
|
});
|
||||||
|
|
||||||
const onApply = () => {
|
const onApply = () => {
|
||||||
const { state, dispatch } = view;
|
const { state, dispatch } = view;
|
||||||
const tr = state.tr.replaceWith(
|
|
||||||
|
let decorationTransaction = state.tr;
|
||||||
|
const currentState = suggestionsPluginKey.getState(state);
|
||||||
|
const currentDecorations = currentState?.decorations;
|
||||||
|
|
||||||
|
if (currentDecorations) {
|
||||||
|
const newDecorations = DecorationSet.create(
|
||||||
|
state.doc,
|
||||||
|
currentDecorations.find().filter((decoration: Decoration) => {
|
||||||
|
return decoration.spec.suggestionId !== suggestion.id;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
decorationTransaction.setMeta(suggestionsPluginKey, {
|
||||||
|
decorations: newDecorations,
|
||||||
|
selected: null,
|
||||||
|
});
|
||||||
|
dispatch(decorationTransaction);
|
||||||
|
}
|
||||||
|
|
||||||
|
const textTransaction = view.state.tr.replaceWith(
|
||||||
suggestion.selectionStart,
|
suggestion.selectionStart,
|
||||||
suggestion.selectionEnd,
|
suggestion.selectionEnd,
|
||||||
state.schema.text(suggestion.suggestedText)
|
state.schema.text(suggestion.suggestedText)
|
||||||
);
|
);
|
||||||
|
|
||||||
dispatch(tr);
|
textTransaction.setMeta('no-debounce', true);
|
||||||
|
|
||||||
|
dispatch(textTransaction);
|
||||||
};
|
};
|
||||||
|
|
||||||
root.render(<PreviewSuggestion suggestion={suggestion} onApply={onApply} />);
|
root.render(<PreviewSuggestion suggestion={suggestion} onApply={onApply} />);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue