chatbot-template/components/custom/editor.tsx

171 lines
4.5 KiB
TypeScript
Raw Normal View History

2024-10-30 16:01:24 +05:30
'use client';
import { exampleSetup } from 'prosemirror-example-setup';
import { inputRules } from 'prosemirror-inputrules';
2024-10-30 16:01:24 +05:30
import { EditorState } from 'prosemirror-state';
import { EditorView } from 'prosemirror-view';
2024-11-01 14:13:17 +05:30
import React, { memo, useEffect, useRef } from 'react';
2024-10-30 16:01:24 +05:30
2024-11-15 10:13:21 -05:00
import { 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';
type EditorProps = {
content: string;
saveContent: (updatedContent: string, debounce: boolean) => void;
2024-10-30 16:01:24 +05:30
status: 'streaming' | 'idle';
isCurrentVersion: boolean;
2024-10-30 16:01:24 +05:30
currentVersionIndex: number;
suggestions: Array<Suggestion>;
};
function PureEditor({
content,
saveContent,
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) => {
handleTransaction({ transaction, editorRef, saveContent });
},
});
2024-10-30 16:01:24 +05:30
}
}, [saveContent]);
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(() => {
if (editorRef.current && 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 (
<div className="relative prose dark:prose-invert" ref={containerRef} />
);
2024-10-30 16:01:24 +05:30
}
function areEqual(prevProps: EditorProps, nextProps: EditorProps) {
if (prevProps.suggestions !== nextProps.suggestions) {
return false;
} else if (prevProps.currentVersionIndex !== nextProps.currentVersionIndex) {
return false;
} else if (prevProps.isCurrentVersion !== nextProps.isCurrentVersion) {
return false;
} else if (
prevProps.status === 'streaming' &&
nextProps.status === 'streaming'
) {
return false;
} else if (prevProps.content !== nextProps.content) {
return false;
} else if (prevProps.saveContent !== nextProps.saveContent) {
2024-10-30 16:01:24 +05:30
return false;
}
return true;
2024-10-30 16:01:24 +05:30
}
export const Editor = memo(PureEditor, areEqual);