@firecms/core 3.1.0 → 3.2.0-canary.4c3b8f2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/EntityCollectionView/CollectionDataErrorBanner.d.ts +4 -0
- package/dist/components/ErrorBoundary.d.ts +3 -1
- package/dist/components/HomePage/DefaultHomePage.d.ts +0 -1
- package/dist/components/LanguageToggle.d.ts +1 -0
- package/dist/components/UnsavedChangesDialog.d.ts +1 -0
- package/dist/components/index.d.ts +1 -0
- package/dist/core/DrawerNavigationGroup.d.ts +2 -2
- package/dist/editor/components/SlashCommandMenu.d.ts +6 -0
- package/dist/editor/components/editor-bubble-item.d.ts +8 -0
- package/dist/editor/components/editor-bubble.d.ts +8 -0
- package/dist/editor/components/image-bubble.d.ts +5 -0
- package/dist/editor/components/index.d.ts +16 -0
- package/dist/editor/components/table-bubble.d.ts +5 -0
- package/dist/editor/editor.d.ts +30 -0
- package/dist/editor/extensions/HighlightDecorationExtension.d.ts +24 -0
- package/dist/editor/extensions/Image/index.d.ts +6 -0
- package/dist/editor/extensions/Image.d.ts +6 -0
- package/dist/editor/extensions/TextLoadingDecorationExtension.d.ts +16 -0
- package/dist/editor/extensions/clipboard.d.ts +7 -0
- package/dist/editor/extensions/custom-keymap.d.ts +1 -0
- package/dist/editor/extensions/drag-and-drop.d.ts +9 -0
- package/dist/editor/hooks/useProseMirror.d.ts +13 -0
- package/dist/editor/hooks/useProseMirrorContext.d.ts +9 -0
- package/dist/editor/index.d.ts +2 -0
- package/dist/editor/markdown.d.ts +5 -0
- package/dist/editor/nodeViews/ImageComponent.d.ts +3 -0
- package/dist/editor/nodeViews/ReactNodeView.d.ts +29 -0
- package/dist/editor/nodeViews/TaskItemComponent.d.ts +3 -0
- package/dist/editor/nodeViews/index.d.ts +6 -0
- package/dist/editor/plugins/index.d.ts +2 -0
- package/dist/editor/plugins/inputrules.d.ts +6 -0
- package/dist/editor/plugins/placeholderPlugin.d.ts +3 -0
- package/dist/editor/plugins/slashCommandPlugin.d.ts +12 -0
- package/dist/editor/schema.d.ts +2 -0
- package/dist/editor/selectors/ai-selector.d.ts +0 -0
- package/dist/editor/selectors/color-selector.d.ts +10 -0
- package/dist/editor/selectors/link-selector.d.ts +8 -0
- package/dist/editor/selectors/node-selector.d.ts +15 -0
- package/dist/editor/selectors/text-buttons.d.ts +1 -0
- package/dist/editor/types.d.ts +5 -0
- package/dist/editor/useProseMirror.d.ts +16 -0
- package/dist/editor/utils/prosemirror-utils.d.ts +6 -0
- package/dist/editor/utils/remove_classes.d.ts +1 -0
- package/dist/editor/utils/useDebouncedCallback.d.ts +1 -0
- package/dist/form/field_bindings/MarkdownEditorFieldBinding.d.ts +1 -1
- package/dist/hooks/index.d.ts +1 -0
- package/dist/hooks/useBuildNavigationController.d.ts +0 -1
- package/dist/hooks/useCollapsedGroups.d.ts +3 -3
- package/dist/hooks/useTranslation.d.ts +17 -0
- package/dist/i18n/FireCMSi18nProvider.d.ts +33 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.es.js +12898 -2265
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +12877 -2264
- package/dist/index.umd.js.map +1 -1
- package/dist/locales/de.d.ts +2 -0
- package/dist/locales/en.d.ts +10 -0
- package/dist/locales/es.d.ts +10 -0
- package/dist/locales/fr.d.ts +2 -0
- package/dist/locales/hi.d.ts +2 -0
- package/dist/locales/it.d.ts +2 -0
- package/dist/locales/pt.d.ts +7 -0
- package/dist/types/customization_controller.d.ts +2 -1
- package/dist/types/firecms.d.ts +2 -1
- package/dist/types/index.d.ts +1 -0
- package/dist/types/navigation.d.ts +2 -2
- package/dist/types/plugins.d.ts +7 -0
- package/dist/types/storage.d.ts +1 -0
- package/dist/types/translations.d.ts +646 -0
- package/dist/util/useStorageUploadController.d.ts +10 -1
- package/package.json +45 -9
- package/src/app/Scaffold.tsx +7 -5
- package/src/components/AIIcon.tsx +3 -1
- package/src/components/ArrayContainer.tsx +6 -4
- package/src/components/ClearFilterSortButton.tsx +6 -3
- package/src/components/ConfirmationDialog.tsx +4 -2
- package/src/components/DeleteEntityDialog.tsx +10 -7
- package/src/components/EntityCollectionTable/fields/TableReferenceField.tsx +6 -3
- package/src/components/EntityCollectionTable/internal/CollectionTableToolbar.tsx +3 -1
- package/src/components/EntityCollectionTable/internal/popup_field/PopupFormField.tsx +3 -2
- package/src/components/EntityCollectionView/BoardSortableList.tsx +3 -1
- package/src/components/EntityCollectionView/CollectionDataErrorBanner.tsx +43 -0
- package/src/components/EntityCollectionView/EntityCollectionBoardView.tsx +16 -43
- package/src/components/EntityCollectionView/EntityCollectionCardView.tsx +17 -25
- package/src/components/EntityCollectionView/EntityCollectionView.tsx +26 -18
- package/src/components/EntityCollectionView/EntityCollectionViewActions.tsx +4 -3
- package/src/components/EntityCollectionView/EntityCollectionViewStartActions.tsx +4 -2
- package/src/components/EntityCollectionView/FiltersDialog.tsx +8 -5
- package/src/components/EntityCollectionView/ViewModeToggle.tsx +11 -8
- package/src/components/EntityView.tsx +3 -2
- package/src/components/ErrorBoundary.tsx +27 -15
- package/src/components/HomePage/DefaultHomePage.tsx +19 -13
- package/src/components/HomePage/HomePageDnD.tsx +3 -1
- package/src/components/HomePage/NavigationGroup.tsx +3 -1
- package/src/components/HomePage/RenameGroupDialog.tsx +15 -13
- package/src/components/LanguageToggle.tsx +66 -0
- package/src/components/NotFoundPage.tsx +5 -3
- package/src/components/ReferenceTable/ReferenceSelectionTable.tsx +9 -7
- package/src/components/ReferenceWidget.tsx +3 -2
- package/src/components/SearchIconsView.tsx +3 -1
- package/src/components/SelectableTable/filters/DateTimeFilterField.tsx +11 -0
- package/src/components/SelectableTable/filters/ReferenceFilterField.tsx +15 -2
- package/src/components/SelectableTable/filters/StringNumberFilterField.tsx +11 -0
- package/src/components/UnsavedChangesDialog.tsx +6 -4
- package/src/components/VirtualTable/VirtualTable.performance.test.tsx +1 -0
- package/src/components/VirtualTable/VirtualTableHeader.tsx +12 -10
- package/src/components/common/default_entity_actions.tsx +4 -0
- package/src/components/common/useDataSourceTableController.tsx +12 -4
- package/src/components/index.tsx +1 -0
- package/src/core/DefaultAppBar.tsx +14 -10
- package/src/core/DefaultDrawer.tsx +8 -2
- package/src/core/DrawerNavigationGroup.tsx +5 -3
- package/src/core/EntityEditView.tsx +4 -3
- package/src/core/EntityEditViewFormActions.tsx +24 -17
- package/src/core/EntitySidePanel.tsx +6 -5
- package/src/core/FireCMS.tsx +33 -6
- package/src/editor/components/SlashCommandMenu.tsx +516 -0
- package/src/editor/components/editor-bubble-item.tsx +32 -0
- package/src/editor/components/editor-bubble.tsx +118 -0
- package/src/editor/components/image-bubble.tsx +156 -0
- package/src/editor/components/index.ts +14 -0
- package/src/editor/components/table-bubble.tsx +165 -0
- package/src/editor/editor.tsx +455 -0
- package/src/editor/extensions/HighlightDecorationExtension.ts +114 -0
- package/src/editor/extensions/Image/index.ts +133 -0
- package/src/editor/extensions/Image.ts +159 -0
- package/src/editor/extensions/TextLoadingDecorationExtension.tsx +107 -0
- package/src/editor/extensions/clipboard.ts +72 -0
- package/src/editor/extensions/custom-keymap.ts +24 -0
- package/src/editor/extensions/drag-and-drop.tsx +480 -0
- package/src/editor/hooks/useProseMirror.ts +124 -0
- package/src/editor/hooks/useProseMirrorContext.ts +15 -0
- package/src/editor/index.ts +2 -0
- package/src/editor/markdown.ts +172 -0
- package/src/editor/nodeViews/ImageComponent.tsx +20 -0
- package/src/editor/nodeViews/ReactNodeView.tsx +89 -0
- package/src/editor/nodeViews/TaskItemComponent.tsx +29 -0
- package/src/editor/nodeViews/index.ts +35 -0
- package/src/editor/plugins/index.ts +58 -0
- package/src/editor/plugins/inputrules.ts +82 -0
- package/src/editor/plugins/placeholderPlugin.ts +55 -0
- package/src/editor/plugins/slashCommandPlugin.ts +61 -0
- package/src/editor/schema.ts +240 -0
- package/src/editor/selectors/ai-selector.tsx +111 -0
- package/src/editor/selectors/color-selector.tsx +200 -0
- package/src/editor/selectors/link-selector.tsx +118 -0
- package/src/editor/selectors/node-selector.tsx +157 -0
- package/src/editor/selectors/text-buttons.tsx +86 -0
- package/src/editor/types.ts +6 -0
- package/src/editor/useProseMirror.ts +126 -0
- package/src/editor/utils/prosemirror-utils.ts +108 -0
- package/src/editor/utils/remove_classes.ts +17 -0
- package/src/editor/utils/useDebouncedCallback.ts +25 -0
- package/src/form/EntityForm.tsx +16 -3
- package/src/form/EntityFormActions.tsx +19 -12
- package/src/form/PropertyFieldBinding.tsx +3 -2
- package/src/form/components/LocalChangesMenu.tsx +13 -13
- package/src/form/components/StorageItemPreview.tsx +3 -2
- package/src/form/components/StorageUploadProgress.tsx +18 -3
- package/src/form/field_bindings/ArrayOfReferencesFieldBinding.tsx +4 -4
- package/src/form/field_bindings/BlockFieldBinding.tsx +5 -2
- package/src/form/field_bindings/KeyValueFieldBinding.tsx +23 -18
- package/src/form/field_bindings/MapFieldBinding.tsx +4 -3
- package/src/form/field_bindings/MarkdownEditorFieldBinding.tsx +33 -19
- package/src/form/field_bindings/RepeatFieldBinding.tsx +3 -1
- package/src/form/field_bindings/StorageUploadFieldBinding.tsx +4 -3
- package/src/hooks/index.tsx +1 -0
- package/src/hooks/useBuildNavigationController.tsx +45 -18
- package/src/hooks/useCollapsedGroups.ts +7 -6
- package/src/hooks/useTranslation.ts +31 -0
- package/src/i18n/FireCMSi18nProvider.tsx +160 -0
- package/src/index.ts +4 -0
- package/src/internal/useBuildSideEntityController.tsx +22 -20
- package/src/locales/de.ts +691 -0
- package/src/locales/en.ts +703 -0
- package/src/locales/es.ts +703 -0
- package/src/locales/fr.ts +691 -0
- package/src/locales/hi.ts +691 -0
- package/src/locales/it.ts +691 -0
- package/src/locales/pt.ts +700 -0
- package/src/preview/components/UrlComponentPreview.tsx +4 -2
- package/src/preview/components/UserPreview.tsx +3 -1
- package/src/types/customization_controller.tsx +2 -1
- package/src/types/firecms.tsx +2 -1
- package/src/types/index.ts +1 -0
- package/src/types/navigation.ts +2 -2
- package/src/types/plugins.tsx +8 -0
- package/src/types/properties.ts +1 -0
- package/src/types/storage.ts +2 -1
- package/src/types/translations.ts +725 -0
- package/src/util/useStorageUploadController.tsx +23 -29
|
@@ -0,0 +1,455 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import React, { useEffect, useState, useRef } from "react";
|
|
3
|
+
import { EditorState } from "prosemirror-state";
|
|
4
|
+
import { cls, defaultBorderMixin, Separator, useInjectStyles, TextareaAutosize } from "@firecms/ui";
|
|
5
|
+
import { useTranslation } from "../hooks/useTranslation";
|
|
6
|
+
import { EditorBubble, ImageBubble, SlashCommandMenu, TableBubble, type JSONContent } from "./components";
|
|
7
|
+
import { NodeSelector } from "./selectors/node-selector";
|
|
8
|
+
import { LinkSelector } from "./selectors/link-selector";
|
|
9
|
+
import { TextButtons } from "./selectors/text-buttons";
|
|
10
|
+
import { removeClassesFromJson } from "./utils/remove_classes";
|
|
11
|
+
import { parser, serializer } from "./markdown";
|
|
12
|
+
import { EditorAIController } from "./types";
|
|
13
|
+
import { useProseMirror } from "./hooks/useProseMirror";
|
|
14
|
+
import { ProseMirrorContext } from "./hooks/useProseMirrorContext";
|
|
15
|
+
import { highlightCommands } from "./extensions/HighlightDecorationExtension";
|
|
16
|
+
import { schema } from "./schema";
|
|
17
|
+
|
|
18
|
+
export type CustomEditorComponent = {
|
|
19
|
+
name: string,
|
|
20
|
+
component: React.FC
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export interface MarkdownEditorConfig {
|
|
24
|
+
html?: boolean;
|
|
25
|
+
transformPastedText?: boolean;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export type FireCMSEditorTextSize = "sm" | "base" | "lg";
|
|
29
|
+
|
|
30
|
+
export type FireCMSEditorProps = {
|
|
31
|
+
content?: JSONContent | string,
|
|
32
|
+
onMarkdownContentChange?: (content: string) => void,
|
|
33
|
+
onJsonContentChange?: (content: JSONContent | null) => void,
|
|
34
|
+
onHtmlContentChange?: (content: string) => void,
|
|
35
|
+
handleImageUpload: (file: File) => Promise<string>,
|
|
36
|
+
version?: number,
|
|
37
|
+
textSize?: FireCMSEditorTextSize,
|
|
38
|
+
highlight?: { from: number, to: number },
|
|
39
|
+
aiController?: EditorAIController,
|
|
40
|
+
customComponents?: CustomEditorComponent[];
|
|
41
|
+
disabled?: boolean;
|
|
42
|
+
markdownConfig?: MarkdownEditorConfig;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const proseClasses = {
|
|
46
|
+
"sm": "prose-sm",
|
|
47
|
+
"base": "prose-base",
|
|
48
|
+
"lg": "prose-lg"
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export const FireCMSEditor = ({
|
|
52
|
+
content,
|
|
53
|
+
onJsonContentChange,
|
|
54
|
+
onHtmlContentChange,
|
|
55
|
+
onMarkdownContentChange,
|
|
56
|
+
version,
|
|
57
|
+
textSize = "base",
|
|
58
|
+
highlight,
|
|
59
|
+
handleImageUpload,
|
|
60
|
+
aiController,
|
|
61
|
+
disabled,
|
|
62
|
+
markdownConfig
|
|
63
|
+
}: FireCMSEditorProps) => {
|
|
64
|
+
const { t } = useTranslation();
|
|
65
|
+
|
|
66
|
+
const [openNode, setOpenNode] = useState(false);
|
|
67
|
+
const [openLink, setOpenLink] = useState(false);
|
|
68
|
+
|
|
69
|
+
const [isMarkdownMode, setIsMarkdownMode] = useState(false);
|
|
70
|
+
const [internalMarkdown, setInternalMarkdown] = useState<string>("");
|
|
71
|
+
|
|
72
|
+
useEffect(() => {
|
|
73
|
+
if (!isMarkdownMode) return;
|
|
74
|
+
const timeout = setTimeout(() => {
|
|
75
|
+
if (callbacksRef.current.onMarkdownContentChange) {
|
|
76
|
+
callbacksRef.current.onMarkdownContentChange(internalMarkdown);
|
|
77
|
+
}
|
|
78
|
+
}, 250);
|
|
79
|
+
return () => clearTimeout(timeout);
|
|
80
|
+
}, [internalMarkdown, isMarkdownMode]);
|
|
81
|
+
|
|
82
|
+
const handleToggleMarkdown = () => {
|
|
83
|
+
if (!isMarkdownMode) {
|
|
84
|
+
if (view) {
|
|
85
|
+
setInternalMarkdown(serializer.serialize(view.state.doc));
|
|
86
|
+
}
|
|
87
|
+
setIsMarkdownMode(true);
|
|
88
|
+
} else {
|
|
89
|
+
if (view) {
|
|
90
|
+
const newDoc = parser.parse(internalMarkdown);
|
|
91
|
+
if (newDoc) {
|
|
92
|
+
const tr = view.state.tr.replaceWith(0, view.state.doc.content.size, newDoc.content);
|
|
93
|
+
view.dispatch(tr);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
setIsMarkdownMode(false);
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
const handleMarkdownChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
|
101
|
+
setInternalMarkdown(e.target.value);
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
const handleMarkdownBlur = () => {
|
|
105
|
+
const { onMarkdownContentChange, onJsonContentChange } = callbacksRef.current;
|
|
106
|
+
if (onMarkdownContentChange) {
|
|
107
|
+
onMarkdownContentChange(internalMarkdown);
|
|
108
|
+
}
|
|
109
|
+
if (onJsonContentChange) {
|
|
110
|
+
try {
|
|
111
|
+
const newDoc = parser.parse(internalMarkdown);
|
|
112
|
+
if (newDoc) {
|
|
113
|
+
onJsonContentChange(removeClassesFromJson(newDoc.toJSON()) as JSONContent);
|
|
114
|
+
}
|
|
115
|
+
} catch (e) {
|
|
116
|
+
console.warn("Could not parse markdown to JSON", e);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
useInjectStyles("Editor", cssStyles);
|
|
122
|
+
|
|
123
|
+
const callbacksRef = useRef({ onMarkdownContentChange, onJsonContentChange });
|
|
124
|
+
useEffect(() => {
|
|
125
|
+
callbacksRef.current = { onMarkdownContentChange, onJsonContentChange };
|
|
126
|
+
}, [onMarkdownContentChange, onJsonContentChange]);
|
|
127
|
+
|
|
128
|
+
const flushChanges = (currentState: EditorState) => {
|
|
129
|
+
const { onMarkdownContentChange, onJsonContentChange } = callbacksRef.current;
|
|
130
|
+
if (onMarkdownContentChange) {
|
|
131
|
+
try {
|
|
132
|
+
const markdown = serializer.serialize(currentState.doc);
|
|
133
|
+
onMarkdownContentChange(markdown);
|
|
134
|
+
} catch (e) {
|
|
135
|
+
console.warn("[FireCMSEditor] Could not serialize editor state to markdown:", e);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
if (onJsonContentChange) {
|
|
139
|
+
const jsonContent = removeClassesFromJson(currentState.doc.toJSON()) as JSONContent;
|
|
140
|
+
onJsonContentChange(jsonContent);
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
const { state, view, editorRef } = useProseMirror({
|
|
145
|
+
initialContent: content,
|
|
146
|
+
editable: !disabled,
|
|
147
|
+
handleImageUpload,
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
const doc = state?.doc;
|
|
151
|
+
useEffect(() => {
|
|
152
|
+
if (!state) return;
|
|
153
|
+
const timeout = setTimeout(() => {
|
|
154
|
+
flushChanges(state);
|
|
155
|
+
}, 250);
|
|
156
|
+
return () => clearTimeout(timeout);
|
|
157
|
+
}, [doc]);
|
|
158
|
+
|
|
159
|
+
useEffect(() => {
|
|
160
|
+
if (!view) return;
|
|
161
|
+
const dom = view.dom;
|
|
162
|
+
const handleBlur = () => {
|
|
163
|
+
flushChanges(view.state);
|
|
164
|
+
};
|
|
165
|
+
dom.addEventListener("blur", handleBlur);
|
|
166
|
+
return () => dom.removeEventListener("blur", handleBlur);
|
|
167
|
+
}, [view]);
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
useEffect(() => {
|
|
172
|
+
if (view) {
|
|
173
|
+
if (highlight) {
|
|
174
|
+
highlightCommands.toggleAutocompleteHighlight(highlight)(view.state, view.dispatch);
|
|
175
|
+
} else {
|
|
176
|
+
highlightCommands.removeAutocompleteHighlight()(view.state, view.dispatch);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}, [highlight?.from, highlight?.to]);
|
|
180
|
+
|
|
181
|
+
const proseClass = proseClasses[textSize];
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
return (
|
|
186
|
+
<div className="relative min-h-[300px] w-full">
|
|
187
|
+
<button
|
|
188
|
+
type="button"
|
|
189
|
+
onClick={handleToggleMarkdown}
|
|
190
|
+
title={isMarkdownMode ? "Switch to Visual Editor" : "Switch to Markdown"}
|
|
191
|
+
className="absolute top-2 right-2 z-10 px-2 py-1 text-xs font-medium text-surface-400 hover:text-surface-700 dark:text-surface-600 dark:hover:text-surface-300 transition-colors opacity-50 hover:opacity-100 bg-transparent rounded"
|
|
192
|
+
>
|
|
193
|
+
{isMarkdownMode ? "Visual" : "Markdown"}
|
|
194
|
+
</button>
|
|
195
|
+
|
|
196
|
+
<ProseMirrorContext.Provider value={{ state, view }}>
|
|
197
|
+
|
|
198
|
+
<div style={{ display: isMarkdownMode ? "none" : "block" }}>
|
|
199
|
+
<div
|
|
200
|
+
ref={editorRef}
|
|
201
|
+
className={cls(proseClass, "prose-headings:font-title font-default focus:outline-none max-w-full p-12")}
|
|
202
|
+
/>
|
|
203
|
+
|
|
204
|
+
{view && (
|
|
205
|
+
<>
|
|
206
|
+
<EditorBubble
|
|
207
|
+
options={{
|
|
208
|
+
placement: "top",
|
|
209
|
+
offset: 6,
|
|
210
|
+
}}
|
|
211
|
+
className={cls("flex w-fit max-w-[90vw] h-10 overflow-hidden rounded border bg-white dark:bg-surface-900 shadow", defaultBorderMixin)}
|
|
212
|
+
>
|
|
213
|
+
<NodeSelector portalContainer={editorRef.current} open={openNode} onOpenChange={setOpenNode} />
|
|
214
|
+
<Separator orientation="vertical" />
|
|
215
|
+
<LinkSelector open={openLink} onOpenChange={setOpenLink} />
|
|
216
|
+
<Separator orientation="vertical" />
|
|
217
|
+
<TextButtons />
|
|
218
|
+
</EditorBubble>
|
|
219
|
+
|
|
220
|
+
<ImageBubble
|
|
221
|
+
options={{
|
|
222
|
+
placement: "bottom",
|
|
223
|
+
offset: 6,
|
|
224
|
+
}}
|
|
225
|
+
/>
|
|
226
|
+
<TableBubble />
|
|
227
|
+
</>
|
|
228
|
+
)}
|
|
229
|
+
|
|
230
|
+
<SlashCommandMenu upload={handleImageUpload} aiController={aiController} />
|
|
231
|
+
</div>
|
|
232
|
+
|
|
233
|
+
{isMarkdownMode && (
|
|
234
|
+
<TextareaAutosize
|
|
235
|
+
value={internalMarkdown}
|
|
236
|
+
onChange={handleMarkdownChange as any}
|
|
237
|
+
onBlur={handleMarkdownBlur as any}
|
|
238
|
+
className={cls(
|
|
239
|
+
"w-full h-full min-h-[300px] p-12 bg-transparent resize-none font-mono focus:ring-0",
|
|
240
|
+
proseClass
|
|
241
|
+
)}
|
|
242
|
+
style={{
|
|
243
|
+
tabSize: 4,
|
|
244
|
+
outline: "none",
|
|
245
|
+
border: "none",
|
|
246
|
+
boxShadow: "none"
|
|
247
|
+
}}
|
|
248
|
+
/>
|
|
249
|
+
)}
|
|
250
|
+
|
|
251
|
+
</ProseMirrorContext.Provider>
|
|
252
|
+
</div>
|
|
253
|
+
);
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
const cssStyles = `
|
|
259
|
+
.ProseMirror {
|
|
260
|
+
box-shadow: none !important;
|
|
261
|
+
}
|
|
262
|
+
.ProseMirror .is-editor-empty:first-child::before {
|
|
263
|
+
content: attr(data-placeholder);
|
|
264
|
+
float: left;
|
|
265
|
+
color: rgb(100 116 139); //500
|
|
266
|
+
pointer-events: none;
|
|
267
|
+
height: 0;
|
|
268
|
+
}
|
|
269
|
+
.ProseMirror .is-empty::before {
|
|
270
|
+
content: attr(data-placeholder);
|
|
271
|
+
float: left;
|
|
272
|
+
color: rgb(100 116 139); //500
|
|
273
|
+
pointer-events: none;
|
|
274
|
+
height: 0;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
[data-theme="dark"] {
|
|
278
|
+
.ProseMirror .is-empty::before {
|
|
279
|
+
color: rgb(100 116 139); //500
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
.is-empty {
|
|
284
|
+
cursor: text;
|
|
285
|
+
color: rgb(100 116 139); //500
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
.ProseMirror img {
|
|
289
|
+
transition: filter 0.1s ease-in-out;
|
|
290
|
+
&:hover {
|
|
291
|
+
cursor: pointer;
|
|
292
|
+
filter: brightness(90%);
|
|
293
|
+
}
|
|
294
|
+
&.ProseMirror-selectednode {
|
|
295
|
+
filter: brightness(90%);
|
|
296
|
+
box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000) !important;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
ul[data-type="taskList"] li > label {
|
|
301
|
+
margin-right: 0.2rem;
|
|
302
|
+
user-select: none;
|
|
303
|
+
}
|
|
304
|
+
@media screen and (max-width: 768px) {
|
|
305
|
+
ul[data-type="taskList"] li > label {
|
|
306
|
+
margin-right: 0.5rem;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
[data-theme="dark"] {
|
|
310
|
+
ul[data-type="taskList"] li > label input[type="checkbox"] {
|
|
311
|
+
background-color: rgb(30 41 59);
|
|
312
|
+
border: 2px solid #666;
|
|
313
|
+
&:hover { background-color: rgb(51 65 85); }
|
|
314
|
+
&:active { background-color: rgb(71 85 105); }
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
ul[data-type="taskList"] li > label input[type="checkbox"] {
|
|
318
|
+
-webkit-appearance: none;
|
|
319
|
+
appearance: none;
|
|
320
|
+
background-color: white;
|
|
321
|
+
margin: 0;
|
|
322
|
+
cursor: pointer;
|
|
323
|
+
width: 1.2em;
|
|
324
|
+
height: 1.2em;
|
|
325
|
+
position: relative;
|
|
326
|
+
top: 5px;
|
|
327
|
+
border: 2px solid #777;
|
|
328
|
+
border-radius: 0.25em;
|
|
329
|
+
margin-right: 0.3rem;
|
|
330
|
+
display: grid;
|
|
331
|
+
place-content: center;
|
|
332
|
+
&:hover { background-color: rgb(241 245 249); }
|
|
333
|
+
&:active { background-color: rgb(226 232 240); }
|
|
334
|
+
&::before {
|
|
335
|
+
content: "";
|
|
336
|
+
width: 0.65em;
|
|
337
|
+
height: 0.65em;
|
|
338
|
+
transform: scale(0);
|
|
339
|
+
transition: 120ms transform ease-in-out;
|
|
340
|
+
box-shadow: inset 1em 1em;
|
|
341
|
+
transform-origin: center;
|
|
342
|
+
clip-path: polygon(14% 44%, 0 65%, 50% 100%, 100% 16%, 80% 0%, 43% 62%);
|
|
343
|
+
}
|
|
344
|
+
&:checked::before { transform: scale(1); }
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
[data-theme="dark"] {
|
|
348
|
+
ul[data-type="taskList"] li[data-checked="true"] > div > p {
|
|
349
|
+
color: rgb(226 232 240);
|
|
350
|
+
text-decoration: line-through;
|
|
351
|
+
text-decoration-thickness: 2px;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
ul[data-type="taskList"] li[data-checked="true"] > div > p {
|
|
355
|
+
color: rgb(51 65 85);
|
|
356
|
+
text-decoration: line-through;
|
|
357
|
+
text-decoration-thickness: 2px;
|
|
358
|
+
}
|
|
359
|
+
.tippy-box { max-width: 400px !important; }
|
|
360
|
+
|
|
361
|
+
.ProseMirror:not(.dragging) .ProseMirror-selectednode {
|
|
362
|
+
background-color: rgb(219 234 254);
|
|
363
|
+
transition: background-color 0.2s;
|
|
364
|
+
box-shadow: none;
|
|
365
|
+
}
|
|
366
|
+
[data-theme="dark"] .ProseMirror:not(.dragging) .ProseMirror-selectednode {
|
|
367
|
+
background-color: rgb(51 65 85);
|
|
368
|
+
}
|
|
369
|
+
.prose-base table p { margin: 0; }
|
|
370
|
+
|
|
371
|
+
.drag-handle {
|
|
372
|
+
position: absolute;
|
|
373
|
+
opacity: 1;
|
|
374
|
+
transition: opacity ease-in 0.2s;
|
|
375
|
+
border-radius: 0.25rem;
|
|
376
|
+
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 10' style='fill: rgba(128, 128, 128, 0.9)'%3E%3Cpath d='M3,2 C2.44771525,2 2,1.55228475 2,1 C2,0.44771525 2.44771525,0 3,0 C3.55228475,0 4,0.44771525 4,1 C4,1.55228475 3.55228475,2 3,2 Z M3,6 C2.44771525,6 2,5.55228475 2,5 C2,4.44771525 2.44771525,4 3,4 C3.55228475,4 4,4.44771525 4,5 C4,5.55228475 3.55228475,6 3,6 Z M3,10 C2.44771525,10 2,9.55228475 2,9 C2,8.44771525 2.44771525,8 3,8 C3.55228475,8 4,8.44771525 4,9 C4,9.55228475 3.55228475,10 3,10 Z M7,2 C6.44771525,2 6,1.55228475 6,1 C6,0.44771525 6.44771525,0 7,0 C7.55228475,0 8,0.44771525 8,1 C8,1.55228475 7.55228475,2 7,2 Z M7,6 C6.44771525,6 6,5.55228475 6,5 C6,4.44771525 6.44771525,4 7,4 C7.55228475,4 8,4.44771525 8,5 C8,5.55228475 7.55228475,6 7,6 Z M7,10 C6.44771525,10 6,9.55228475 6,9 C6,8.44771525 6.44771525,8 7,8 C7,8.44771525 7,9 7,9 C7,9.55228475 7,10 7,10 Z'%3E%3C/path%3E%3C/svg%3E");
|
|
377
|
+
background-size: calc(0.5em + 0.375rem) calc(0.5em + 0.375rem);
|
|
378
|
+
background-repeat: no-repeat;
|
|
379
|
+
background-position: center;
|
|
380
|
+
width: 1.2rem;
|
|
381
|
+
height: 1.5rem;
|
|
382
|
+
z-index: 100;
|
|
383
|
+
cursor: grab;
|
|
384
|
+
|
|
385
|
+
/* Create a hover area around the handle itself that doesn't overlap text */
|
|
386
|
+
&::before {
|
|
387
|
+
content: '';
|
|
388
|
+
position: absolute;
|
|
389
|
+
top: -10px;
|
|
390
|
+
bottom: -10px;
|
|
391
|
+
left: -20px;
|
|
392
|
+
right: 0px;
|
|
393
|
+
z-index: -1;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
&:hover { background-color: rgb(241 245 249); transition: background-color 0.2s; }
|
|
397
|
+
&:active { background-color: rgb(226 232 240); transition: background-color 0.2s; }
|
|
398
|
+
&.hide { opacity: 0; pointer-events: none; }
|
|
399
|
+
@media screen and (max-width: 600px) { display: none; pointer-events: none; }
|
|
400
|
+
}
|
|
401
|
+
[data-theme="dark"] .drag-handle {
|
|
402
|
+
&:hover { background-color: rgb(51 65 85); }
|
|
403
|
+
&:active { background-color: rgb(51 65 85); }
|
|
404
|
+
}
|
|
405
|
+
.prosemirror-dropcursor-block {
|
|
406
|
+
background-color: var(--color-surface-accent-600);
|
|
407
|
+
}
|
|
408
|
+
[data-theme="dark"] .prosemirror-dropcursor-block {
|
|
409
|
+
background-color: var(--color-surface-accent-300);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
.ProseMirror table {
|
|
413
|
+
border-collapse: separate;
|
|
414
|
+
border-spacing: 0;
|
|
415
|
+
table-layout: fixed;
|
|
416
|
+
width: 100%;
|
|
417
|
+
margin: 1em 0;
|
|
418
|
+
overflow: hidden;
|
|
419
|
+
border-radius: 0.375rem;
|
|
420
|
+
border: 1px solid #e5e7eb;
|
|
421
|
+
}
|
|
422
|
+
[data-theme="dark"] .ProseMirror table {
|
|
423
|
+
border-color: #374151;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
.ProseMirror td, .ProseMirror th {
|
|
427
|
+
min-width: 1em;
|
|
428
|
+
padding: 8px 10px;
|
|
429
|
+
vertical-align: top;
|
|
430
|
+
box-sizing: border-box;
|
|
431
|
+
position: relative;
|
|
432
|
+
border-right: 1px solid #e5e7eb;
|
|
433
|
+
border-bottom: 1px solid #e5e7eb;
|
|
434
|
+
}
|
|
435
|
+
[data-theme="dark"] .ProseMirror td, [data-theme="dark"] .ProseMirror th {
|
|
436
|
+
border-right-color: #374151;
|
|
437
|
+
border-bottom-color: #374151;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
.ProseMirror tr:last-child td, .ProseMirror tr:last-child th {
|
|
441
|
+
border-bottom: none;
|
|
442
|
+
}
|
|
443
|
+
.ProseMirror th:last-child, .ProseMirror td:last-child {
|
|
444
|
+
border-right: none;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
.ProseMirror th {
|
|
448
|
+
font-weight: 600;
|
|
449
|
+
text-align: left;
|
|
450
|
+
background-color: #f9fafb;
|
|
451
|
+
}
|
|
452
|
+
[data-theme="dark"] .ProseMirror th {
|
|
453
|
+
background-color: #1f2937;
|
|
454
|
+
}
|
|
455
|
+
`;
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { Plugin, PluginKey, Transaction, EditorState } from "prosemirror-state";
|
|
2
|
+
import { Decoration, DecorationSet } from "prosemirror-view";
|
|
3
|
+
|
|
4
|
+
export interface HighlightRange {
|
|
5
|
+
from: number
|
|
6
|
+
to: number
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
interface AutocompleteHighlightState {
|
|
10
|
+
highlight?: HighlightRange
|
|
11
|
+
decorationSet?: DecorationSet
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const highlightDecorationKey = new PluginKey<AutocompleteHighlightState>("highlightDecoration");
|
|
15
|
+
|
|
16
|
+
function buildDecorationSet(highlight: any, doc: any) {
|
|
17
|
+
const decorations: [any?] = [];
|
|
18
|
+
|
|
19
|
+
if (highlight) {
|
|
20
|
+
decorations.push(
|
|
21
|
+
Decoration.inline(highlight.from, highlight.to, {
|
|
22
|
+
class: "dark:bg-surface-accent-700 bg-surface-accent-300"
|
|
23
|
+
})
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
const decorationSet = DecorationSet.create(doc, decorations);
|
|
27
|
+
return decorationSet;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Commands to toggle the highlight
|
|
32
|
+
*/
|
|
33
|
+
export const highlightCommands = {
|
|
34
|
+
toggleAutocompleteHighlight: (range?: HighlightRange) => (state: EditorState, dispatch?: (tr: Transaction) => void): boolean => {
|
|
35
|
+
const { selection } = state;
|
|
36
|
+
const pos = selection.from;
|
|
37
|
+
|
|
38
|
+
if (!dispatch) return false;
|
|
39
|
+
|
|
40
|
+
const tr = state.tr.setMeta(highlightDecorationKey, {
|
|
41
|
+
pos,
|
|
42
|
+
type: "highlightDecoration",
|
|
43
|
+
remove: false,
|
|
44
|
+
range
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
dispatch(tr);
|
|
48
|
+
return true;
|
|
49
|
+
},
|
|
50
|
+
|
|
51
|
+
removeAutocompleteHighlight: () => (state: EditorState, dispatch?: (tr: Transaction) => void): boolean => {
|
|
52
|
+
if (!dispatch) return false;
|
|
53
|
+
|
|
54
|
+
const tr = state.tr.setMeta(highlightDecorationKey, {
|
|
55
|
+
pos: 0,
|
|
56
|
+
type: "highlightDecoration",
|
|
57
|
+
remove: true
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
dispatch(tr);
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* This plugin is used to highlight the current autocomplete suggestion.
|
|
67
|
+
* It allows to set a range and remove it.
|
|
68
|
+
*/
|
|
69
|
+
export const highlightDecorationPlugin = (initialHighlight?: HighlightRange) => {
|
|
70
|
+
return new Plugin<AutocompleteHighlightState>({
|
|
71
|
+
key: highlightDecorationKey,
|
|
72
|
+
state: {
|
|
73
|
+
init: (_, { doc }) => {
|
|
74
|
+
const decorationSet = initialHighlight && doc ? buildDecorationSet(initialHighlight, doc) : DecorationSet.empty;
|
|
75
|
+
return {
|
|
76
|
+
decorationSet,
|
|
77
|
+
highlight: initialHighlight
|
|
78
|
+
};
|
|
79
|
+
},
|
|
80
|
+
apply(transaction, oldState) {
|
|
81
|
+
const action = transaction.getMeta(highlightDecorationKey);
|
|
82
|
+
const highlight = action?.range;
|
|
83
|
+
if (action?.type === "highlightDecoration") {
|
|
84
|
+
|
|
85
|
+
const doc = transaction.doc;
|
|
86
|
+
const { remove } = action;
|
|
87
|
+
|
|
88
|
+
if (remove) {
|
|
89
|
+
return {
|
|
90
|
+
decorationSet: DecorationSet.empty
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
const decorationSet = buildDecorationSet(highlight, doc);
|
|
94
|
+
return {
|
|
95
|
+
decorationSet: decorationSet,
|
|
96
|
+
highlight
|
|
97
|
+
}
|
|
98
|
+
} else {
|
|
99
|
+
return oldState
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
props: {
|
|
104
|
+
decorations(state) {
|
|
105
|
+
const autocompleteState = this.getState(state);
|
|
106
|
+
if (autocompleteState?.decorationSet) {
|
|
107
|
+
return autocompleteState.decorationSet
|
|
108
|
+
} else {
|
|
109
|
+
return DecorationSet.empty
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
};
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { Decoration, DecorationSet, EditorView } from "prosemirror-view";
|
|
2
|
+
import { Plugin, PluginKey } from "prosemirror-state";
|
|
3
|
+
import { schema } from "../../schema";
|
|
4
|
+
|
|
5
|
+
export type UploadFn = (image: File) => Promise<string>;
|
|
6
|
+
|
|
7
|
+
export async function onFileRead(view: EditorView, readerEvent: ProgressEvent<FileReader>, pos: number, upload: UploadFn, image: File) {
|
|
8
|
+
// @ts-ignore
|
|
9
|
+
const plugin = view.state.plugins.find((p: Plugin) => p.key === ImagePluginKey.key);
|
|
10
|
+
if (!plugin) {
|
|
11
|
+
console.error("Image plugin not found");
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
let decorationSet = plugin.getState(view.state);
|
|
15
|
+
|
|
16
|
+
const placeholder = document.createElement("div");
|
|
17
|
+
const imageElement = document.createElement("img");
|
|
18
|
+
// basic styling for loading state
|
|
19
|
+
imageElement.setAttribute("class", "opacity-40 rounded-lg border");
|
|
20
|
+
imageElement.src = readerEvent.target?.result as string;
|
|
21
|
+
placeholder.appendChild(imageElement);
|
|
22
|
+
|
|
23
|
+
const deco = Decoration.widget(pos, placeholder);
|
|
24
|
+
decorationSet = decorationSet?.add(view.state.doc, [deco]);
|
|
25
|
+
view.dispatch(view.state.tr.setMeta(plugin, { decorationSet }));
|
|
26
|
+
|
|
27
|
+
// Image Upload Logic
|
|
28
|
+
const src = await upload(image);
|
|
29
|
+
console.debug("Uploaded image", src);
|
|
30
|
+
|
|
31
|
+
// Replace placeholder with actual image
|
|
32
|
+
const imageNode = schema.nodes.image.create({ src });
|
|
33
|
+
const tr = view.state.tr.replaceWith(pos, pos, imageNode);
|
|
34
|
+
|
|
35
|
+
// Remove placeholder decoration
|
|
36
|
+
decorationSet = decorationSet?.remove([deco]);
|
|
37
|
+
tr.setMeta(plugin, { decorationSet });
|
|
38
|
+
view.dispatch(tr);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export const ImagePluginKey = new PluginKey("imagePlugin");
|
|
42
|
+
|
|
43
|
+
export const createDropImagePlugin = (upload: UploadFn): Plugin => {
|
|
44
|
+
const plugin: Plugin = new Plugin({
|
|
45
|
+
key: ImagePluginKey,
|
|
46
|
+
state: {
|
|
47
|
+
// Initialize the plugin state with an empty DecorationSet
|
|
48
|
+
init: () => DecorationSet.empty,
|
|
49
|
+
// Apply transactions to update the state
|
|
50
|
+
apply: (tr, old) => {
|
|
51
|
+
// Handle custom transaction steps that update decorations
|
|
52
|
+
const meta = tr.getMeta(plugin);
|
|
53
|
+
if (meta && meta.decorationSet) {
|
|
54
|
+
return meta.decorationSet;
|
|
55
|
+
}
|
|
56
|
+
// Map decorations to the new document structure
|
|
57
|
+
return old.map(tr.mapping, tr.doc);
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
props: {
|
|
61
|
+
handleDOMEvents: {
|
|
62
|
+
drop: (view, event) => {
|
|
63
|
+
if (!event.dataTransfer?.files || event.dataTransfer?.files.length === 0) {
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
event.preventDefault();
|
|
67
|
+
|
|
68
|
+
const files = Array.from(event.dataTransfer.files);
|
|
69
|
+
const images = files.filter(file => /image/i.test(file.type));
|
|
70
|
+
|
|
71
|
+
if (images.length === 0) {
|
|
72
|
+
console.log("No images found in dropped files");
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
images.forEach(image => {
|
|
77
|
+
const position = view.posAtCoords({
|
|
78
|
+
left: event.clientX,
|
|
79
|
+
top: event.clientY
|
|
80
|
+
});
|
|
81
|
+
if (!position) return;
|
|
82
|
+
|
|
83
|
+
const reader = new FileReader();
|
|
84
|
+
reader.onload = async (readerEvent) => {
|
|
85
|
+
await onFileRead(view, readerEvent, position.pos, upload, image);
|
|
86
|
+
};
|
|
87
|
+
reader.readAsDataURL(image);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
return true;
|
|
91
|
+
}
|
|
92
|
+
},
|
|
93
|
+
handlePaste(view, event, slice) {
|
|
94
|
+
const items = Array.from(event.clipboardData?.items || []);
|
|
95
|
+
const pos = view.state.selection.from;
|
|
96
|
+
let anyImageFound = false;
|
|
97
|
+
|
|
98
|
+
items.forEach((item) => {
|
|
99
|
+
const image = item.getAsFile();
|
|
100
|
+
if (image && /image/i.test(item.type)) {
|
|
101
|
+
anyImageFound = true;
|
|
102
|
+
const reader = new FileReader();
|
|
103
|
+
|
|
104
|
+
reader.onload = async (readerEvent) => {
|
|
105
|
+
await onFileRead(view, readerEvent, pos, upload, image);
|
|
106
|
+
};
|
|
107
|
+
reader.readAsDataURL(image);
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
return anyImageFound;
|
|
112
|
+
},
|
|
113
|
+
decorations(state) {
|
|
114
|
+
return plugin.getState(state);
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
view(editorView) {
|
|
118
|
+
// This is needed to immediately apply the decoration updates
|
|
119
|
+
return {
|
|
120
|
+
update(view, prevState) {
|
|
121
|
+
const prevDecos = plugin.getState(prevState);
|
|
122
|
+
const newDecos = plugin.getState(view.state);
|
|
123
|
+
|
|
124
|
+
if (prevDecos !== newDecos) {
|
|
125
|
+
view.updateState(view.state);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
return plugin;
|
|
133
|
+
};
|