chatbot-template/components/text-editor.tsx

165 lines
4.4 KiB
TypeScript
Raw Normal View History

'use client';
2024-10-30 16:01:24 +05:30
import { exampleSetup } from 'prosemirror-example-setup';
import { inputRules } from 'prosemirror-inputrules';
import { EditorState } from 'prosemirror-state';
import { EditorView } from 'prosemirror-view';
import React, { memo, useEffect, useRef } from 'react';
2024-10-30 16:01:24 +05:30
import type { Suggestion } from '@/lib/db/schema';
2024-10-30 16:01:24 +05:30
import {
documentSchema,
handleTransaction,
headingRule,
} from '@/lib/editor/config';
import {
buildContentFromDocument,
buildDocumentFromContent,
createDecorations,
} from '@/lib/editor/functions';
import {
projectWithPositions,
2024-10-30 16:01:24 +05:30
suggestionsPlugin,
suggestionsPluginKey,
} from '@/lib/editor/suggestions';
2024-10-30 16:01:24 +05:30
type EditorProps = {
content: string;
2025-01-27 14:19:47 +05:30
onSaveContent: (updatedContent: string, debounce: boolean) => void;
status: 'streaming' | 'idle';
isCurrentVersion: boolean;
2024-10-30 16:01:24 +05:30
currentVersionIndex: number;
suggestions: Array<Suggestion>;
2024-10-30 16:01:24 +05:30
};
function PureEditor({
content,
2025-01-27 14:19:47 +05:30
onSaveContent,
suggestions,
status,
2024-10-30 16:01:24 +05:30
}: EditorProps) {
const containerRef = useRef<HTMLDivElement>(null);
const editorRef = useRef<EditorView | null>(null);
2024-10-30 16:01:24 +05:30
useEffect(() => {
if (containerRef.current && !editorRef.current) {
const state = EditorState.create({
doc: buildDocumentFromContent(content),
plugins: [
...exampleSetup({ schema: documentSchema, menuBar: false }),
inputRules({
rules: [
headingRule(1),
headingRule(2),
headingRule(3),
headingRule(4),
headingRule(5),
headingRule(6),
],
}),
suggestionsPlugin,
],
});
editorRef.current = new EditorView(containerRef.current, {
state,
});
2024-10-30 16:01:24 +05:30
}
return () => {
if (editorRef.current) {
editorRef.current.destroy();
editorRef.current = null;
2024-10-30 16:01:24 +05:30
}
};
// NOTE: we only want to run this effect once
// eslint-disable-next-line
}, []);
2024-10-30 16:01:24 +05:30
useEffect(() => {
if (editorRef.current) {
editorRef.current.setProps({
dispatchTransaction: (transaction) => {
2025-01-27 14:19:47 +05:30
handleTransaction({
transaction,
editorRef,
onSaveContent,
});
},
});
2024-10-30 16:01:24 +05:30
}
2025-01-27 14:19:47 +05:30
}, [onSaveContent]);
2024-10-30 16:01:24 +05:30
useEffect(() => {
if (editorRef.current && content) {
const currentContent = buildContentFromDocument(
editorRef.current.state.doc,
2024-10-30 16:01:24 +05:30
);
if (status === 'streaming') {
const newDocument = buildDocumentFromContent(content);
2024-10-30 16:01:24 +05:30
const transaction = editorRef.current.state.tr.replaceWith(
0,
editorRef.current.state.doc.content.size,
newDocument.content,
);
transaction.setMeta('no-save', true);
editorRef.current.dispatch(transaction);
return;
}
2024-10-30 16:01:24 +05:30
if (currentContent !== content) {
const newDocument = buildDocumentFromContent(content);
2024-10-30 16:01:24 +05:30
const transaction = editorRef.current.state.tr.replaceWith(
0,
editorRef.current.state.doc.content.size,
newDocument.content,
);
transaction.setMeta('no-save', true);
editorRef.current.dispatch(transaction);
2024-10-30 16:01:24 +05:30
}
}
}, [content, status]);
useEffect(() => {
2024-11-15 12:18:17 -05:00
if (editorRef.current?.state.doc && content) {
const projectedSuggestions = projectWithPositions(
editorRef.current.state.doc,
suggestions,
).filter(
(suggestion) => suggestion.selectionStart && suggestion.selectionEnd,
);
2024-10-30 16:01:24 +05:30
const decorations = createDecorations(
projectedSuggestions,
editorRef.current,
);
const transaction = editorRef.current.state.tr;
transaction.setMeta(suggestionsPluginKey, { decorations });
editorRef.current.dispatch(transaction);
}
}, [suggestions, content]);
return (
2025-09-09 15:44:07 -04:00
<div className="prose dark:prose-invert relative" ref={containerRef} />
);
2024-10-30 16:01:24 +05:30
}
function areEqual(prevProps: EditorProps, nextProps: EditorProps) {
2024-11-15 12:18:17 -05:00
return (
prevProps.suggestions === nextProps.suggestions &&
prevProps.currentVersionIndex === nextProps.currentVersionIndex &&
prevProps.isCurrentVersion === nextProps.isCurrentVersion &&
!(prevProps.status === 'streaming' && nextProps.status === 'streaming') &&
2024-11-15 12:18:17 -05:00
prevProps.content === nextProps.content &&
2025-01-27 14:19:47 +05:30
prevProps.onSaveContent === nextProps.onSaveContent
2024-11-15 12:18:17 -05:00
);
2024-10-30 16:01:24 +05:30
}
export const Editor = memo(PureEditor, areEqual);