Add canvas interface (#461)
This commit is contained in:
parent
1a74a5ca9a
commit
b3cb0ea755
44 changed files with 7454 additions and 4691 deletions
118
lib/editor/suggestions.tsx
Normal file
118
lib/editor/suggestions.tsx
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
import { Node } from 'prosemirror-model';
|
||||
import { PluginKey, Plugin } from 'prosemirror-state';
|
||||
import { DecorationSet, EditorView } from 'prosemirror-view';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
|
||||
import { Suggestion as PreviewSuggestion } from '@/components/custom/suggestion';
|
||||
import { Suggestion } from '@/db/schema';
|
||||
|
||||
export interface UISuggestion extends Suggestion {
|
||||
selectionStart: number;
|
||||
selectionEnd: number;
|
||||
}
|
||||
|
||||
interface Position {
|
||||
start: number;
|
||||
end: number;
|
||||
}
|
||||
|
||||
function findPositionsInDoc(doc: Node, searchText: string): Position | null {
|
||||
let positions: { start: number; end: number } | null = null;
|
||||
|
||||
doc.nodesBetween(0, doc.content.size, (node, pos) => {
|
||||
if (node.isText && node.text) {
|
||||
const index = node.text.indexOf(searchText);
|
||||
|
||||
if (index !== -1) {
|
||||
positions = {
|
||||
start: pos + index,
|
||||
end: pos + index + searchText.length,
|
||||
};
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
return positions;
|
||||
}
|
||||
|
||||
export function projectWithHighlights(
|
||||
doc: Node,
|
||||
suggestions: Array<Suggestion>
|
||||
): Array<UISuggestion> {
|
||||
return suggestions.map((suggestion) => {
|
||||
const positions = findPositionsInDoc(doc, suggestion.originalText);
|
||||
|
||||
if (!positions) {
|
||||
return {
|
||||
...suggestion,
|
||||
selectionStart: 0,
|
||||
selectionEnd: 0,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
...suggestion,
|
||||
selectionStart: positions.start,
|
||||
selectionEnd: positions.end,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
export function createSuggestionWidget(
|
||||
suggestion: UISuggestion,
|
||||
view: EditorView
|
||||
): { dom: HTMLElement; destroy: () => void } {
|
||||
const dom = document.createElement('span');
|
||||
const root = createRoot(dom);
|
||||
|
||||
const onApply = () => {
|
||||
const { state, dispatch } = view;
|
||||
const tr = state.tr.replaceWith(
|
||||
suggestion.selectionStart,
|
||||
suggestion.selectionEnd,
|
||||
state.schema.text(suggestion.suggestedText)
|
||||
);
|
||||
|
||||
dispatch(tr);
|
||||
};
|
||||
|
||||
root.render(<PreviewSuggestion suggestion={suggestion} onApply={onApply} />);
|
||||
|
||||
return {
|
||||
dom,
|
||||
destroy: () => {
|
||||
// Wrapping unmount in setTimeout to avoid synchronous unmounting during render
|
||||
setTimeout(() => {
|
||||
root.unmount();
|
||||
}, 0);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export const suggestionsPluginKey = new PluginKey('suggestions');
|
||||
export const suggestionsPlugin = new Plugin({
|
||||
key: suggestionsPluginKey,
|
||||
state: {
|
||||
init() {
|
||||
return { decorations: DecorationSet.empty, selected: null };
|
||||
},
|
||||
apply(tr, state) {
|
||||
const newDecorations = tr.getMeta(suggestionsPluginKey);
|
||||
if (newDecorations) return newDecorations;
|
||||
|
||||
return {
|
||||
decorations: state.decorations.map(tr.mapping, tr.doc),
|
||||
selected: state.selected,
|
||||
};
|
||||
},
|
||||
},
|
||||
props: {
|
||||
decorations(state) {
|
||||
return this.getState(state)?.decorations ?? DecorationSet.empty;
|
||||
},
|
||||
},
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue