Split transactions when applying suggestions (#465)

This commit is contained in:
Jeremy 2024-10-30 20:36:45 +05:30 committed by GitHub
parent b3cb0ea755
commit 029cbb835e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 75 additions and 20 deletions

View file

@ -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) {

View file

@ -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',
}
)
); );
}); });

View file

@ -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

View file

@ -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} />);