@cyberyozh/text-editor 0.1.17 → 0.2.0
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/editor.d.mts +9 -1
- package/dist/editor.d.ts +9 -1
- package/dist/editor.js +160 -21
- package/dist/editor.js.map +1 -1
- package/dist/editor.mjs +167 -28
- package/dist/editor.mjs.map +1 -1
- package/package.json +3 -2
- package/src/editor/dialogs/image-dialog/image-dialog.tsx +9 -1
- package/src/editor/editor.tsx +52 -9
- package/src/editor/handlers/index.ts +4 -0
- package/src/editor/handlers/insertion-handlers.ts +113 -0
- package/src/editor/index.ts +1 -1
- package/src/types/image-handler.ts +5 -0
- package/src/utils/prosemirror-iterator.ts +38 -0
package/dist/editor.d.mts
CHANGED
|
@@ -11,11 +11,15 @@ interface ImageUploadResult {
|
|
|
11
11
|
interface ImageUploadHandler {
|
|
12
12
|
(file: File): Promise<ImageUploadResult>;
|
|
13
13
|
}
|
|
14
|
+
interface ImageUploadMultipleByURLHandler {
|
|
15
|
+
(files: string[]): Promise<(ImageUploadResult | null)[]>;
|
|
16
|
+
}
|
|
14
17
|
interface ImageGetHandler {
|
|
15
18
|
(fileId: string): string | null;
|
|
16
19
|
}
|
|
17
20
|
interface ImageHandlers {
|
|
18
21
|
upload: ImageUploadHandler;
|
|
22
|
+
uploadMultipleByURL?: ImageUploadMultipleByURLHandler;
|
|
19
23
|
getImage?: ImageGetHandler;
|
|
20
24
|
}
|
|
21
25
|
|
|
@@ -47,8 +51,12 @@ interface TiptapProps {
|
|
|
47
51
|
lang?: SupportedLanguage;
|
|
48
52
|
error?: string;
|
|
49
53
|
}
|
|
54
|
+
interface KatexDialogContextProps {
|
|
55
|
+
latex?: string;
|
|
56
|
+
pos?: number;
|
|
57
|
+
}
|
|
50
58
|
declare const Tiptap: (props: TiptapProps) => react_jsx_runtime.JSX.Element;
|
|
51
59
|
|
|
52
60
|
declare function htmlToTiptapContent(content: string | JSONContent$1): JSONContent$1;
|
|
53
61
|
|
|
54
|
-
export { Tiptap, type SupportedLanguage as TiptapSupportedLanguages, htmlToTiptapContent };
|
|
62
|
+
export { type KatexDialogContextProps, Tiptap, type TiptapProps, type SupportedLanguage as TiptapSupportedLanguages, htmlToTiptapContent };
|
package/dist/editor.d.ts
CHANGED
|
@@ -11,11 +11,15 @@ interface ImageUploadResult {
|
|
|
11
11
|
interface ImageUploadHandler {
|
|
12
12
|
(file: File): Promise<ImageUploadResult>;
|
|
13
13
|
}
|
|
14
|
+
interface ImageUploadMultipleByURLHandler {
|
|
15
|
+
(files: string[]): Promise<(ImageUploadResult | null)[]>;
|
|
16
|
+
}
|
|
14
17
|
interface ImageGetHandler {
|
|
15
18
|
(fileId: string): string | null;
|
|
16
19
|
}
|
|
17
20
|
interface ImageHandlers {
|
|
18
21
|
upload: ImageUploadHandler;
|
|
22
|
+
uploadMultipleByURL?: ImageUploadMultipleByURLHandler;
|
|
19
23
|
getImage?: ImageGetHandler;
|
|
20
24
|
}
|
|
21
25
|
|
|
@@ -47,8 +51,12 @@ interface TiptapProps {
|
|
|
47
51
|
lang?: SupportedLanguage;
|
|
48
52
|
error?: string;
|
|
49
53
|
}
|
|
54
|
+
interface KatexDialogContextProps {
|
|
55
|
+
latex?: string;
|
|
56
|
+
pos?: number;
|
|
57
|
+
}
|
|
50
58
|
declare const Tiptap: (props: TiptapProps) => react_jsx_runtime.JSX.Element;
|
|
51
59
|
|
|
52
60
|
declare function htmlToTiptapContent(content: string | JSONContent$1): JSONContent$1;
|
|
53
61
|
|
|
54
|
-
export { Tiptap, type SupportedLanguage as TiptapSupportedLanguages, htmlToTiptapContent };
|
|
62
|
+
export { type KatexDialogContextProps, Tiptap, type TiptapProps, type SupportedLanguage as TiptapSupportedLanguages, htmlToTiptapContent };
|
package/dist/editor.js
CHANGED
|
@@ -36,10 +36,10 @@ __export(editor_exports, {
|
|
|
36
36
|
module.exports = __toCommonJS(editor_exports);
|
|
37
37
|
|
|
38
38
|
// src/editor/editor.tsx
|
|
39
|
-
var
|
|
39
|
+
var import_react37 = require("react");
|
|
40
40
|
var import_state6 = require("@tiptap/pm/state");
|
|
41
|
-
var
|
|
42
|
-
var
|
|
41
|
+
var import_model2 = require("@tiptap/pm/model");
|
|
42
|
+
var import_react38 = require("@tiptap/react");
|
|
43
43
|
var import_menus = require("@tiptap/react/menus");
|
|
44
44
|
var import_github_dark = require("highlight.js/styles/github-dark.css");
|
|
45
45
|
var import_katex_min = require("katex/dist/katex.min.css");
|
|
@@ -1917,6 +1917,7 @@ var import_jsx_runtime67 = require("react/jsx-runtime");
|
|
|
1917
1917
|
var ImageDialog = ({
|
|
1918
1918
|
open,
|
|
1919
1919
|
onClose,
|
|
1920
|
+
defaultFile,
|
|
1920
1921
|
onImageAdd,
|
|
1921
1922
|
onImageEdit,
|
|
1922
1923
|
uploadHandler,
|
|
@@ -2000,6 +2001,11 @@ var ImageDialog = ({
|
|
|
2000
2001
|
}));
|
|
2001
2002
|
};
|
|
2002
2003
|
};
|
|
2004
|
+
(0, import_react13.useEffect)(() => {
|
|
2005
|
+
if (defaultFile) {
|
|
2006
|
+
handleSelectFile(defaultFile);
|
|
2007
|
+
}
|
|
2008
|
+
}, []);
|
|
2003
2009
|
const handleAreaClick = () => {
|
|
2004
2010
|
inputRef.current?.click();
|
|
2005
2011
|
};
|
|
@@ -6774,10 +6780,110 @@ var createHandleKatexAdd = (editor, closeDialogCommand, savedPos) => {
|
|
|
6774
6780
|
};
|
|
6775
6781
|
};
|
|
6776
6782
|
|
|
6783
|
+
// src/editor/handlers/insertion-handlers.ts
|
|
6784
|
+
var import_react35 = require("react");
|
|
6785
|
+
|
|
6786
|
+
// src/utils/prosemirror-iterator.ts
|
|
6787
|
+
var import_model = require("@tiptap/pm/model");
|
|
6788
|
+
function prosemirrorIterator(slice, cb) {
|
|
6789
|
+
const stack = [];
|
|
6790
|
+
if (slice instanceof import_model.Slice) {
|
|
6791
|
+
collectFragmentChildren(slice.content, stack);
|
|
6792
|
+
} else if (slice instanceof import_model.Fragment) {
|
|
6793
|
+
collectFragmentChildren(slice, stack);
|
|
6794
|
+
} else {
|
|
6795
|
+
stack.push(slice);
|
|
6796
|
+
}
|
|
6797
|
+
while (stack.length > 0) {
|
|
6798
|
+
const node = stack.pop();
|
|
6799
|
+
const shouldStop = cb(node);
|
|
6800
|
+
if (shouldStop === true) break;
|
|
6801
|
+
if (node.content.size > 0) {
|
|
6802
|
+
collectFragmentChildren(node.content, stack);
|
|
6803
|
+
}
|
|
6804
|
+
}
|
|
6805
|
+
}
|
|
6806
|
+
function collectFragmentChildren(fragment, stack) {
|
|
6807
|
+
for (let i = fragment.childCount - 1; i >= 0; i--) {
|
|
6808
|
+
stack.push(fragment.child(i));
|
|
6809
|
+
}
|
|
6810
|
+
}
|
|
6811
|
+
|
|
6812
|
+
// src/editor/handlers/insertion-handlers.ts
|
|
6813
|
+
function useInsertionHandlers() {
|
|
6814
|
+
const [insertedImage, setInsertedImage] = (0, import_react35.useState)(null);
|
|
6815
|
+
const { activeDialog, openDialogCommand } = useDialogCommand();
|
|
6816
|
+
(0, import_react35.useEffect)(() => {
|
|
6817
|
+
if (insertedImage && !activeDialog) {
|
|
6818
|
+
setInsertedImage(null);
|
|
6819
|
+
}
|
|
6820
|
+
}, [insertedImage, activeDialog]);
|
|
6821
|
+
function genericInsertionHandler(file, event) {
|
|
6822
|
+
if (!file || !file.type.startsWith("image/")) return;
|
|
6823
|
+
event.preventDefault();
|
|
6824
|
+
event.stopPropagation();
|
|
6825
|
+
setInsertedImage(file);
|
|
6826
|
+
openDialogCommand("image");
|
|
6827
|
+
}
|
|
6828
|
+
function onPaste(_, event) {
|
|
6829
|
+
const file = event.clipboardData?.files?.[0];
|
|
6830
|
+
genericInsertionHandler(file, event);
|
|
6831
|
+
}
|
|
6832
|
+
function onDrop(_, event) {
|
|
6833
|
+
const file = event.dataTransfer?.files?.[0];
|
|
6834
|
+
genericInsertionHandler(file, event);
|
|
6835
|
+
}
|
|
6836
|
+
function onMarkupInsertion(view, event, slice, setDisabled, uploadMultipleByURL) {
|
|
6837
|
+
if (!uploadMultipleByURL) return;
|
|
6838
|
+
let hasMarkupInsertion = false;
|
|
6839
|
+
if ("clipboardData" in event && Array.from(event.clipboardData?.items ?? []).some(
|
|
6840
|
+
(item) => item.type === "text/html"
|
|
6841
|
+
)) {
|
|
6842
|
+
hasMarkupInsertion = true;
|
|
6843
|
+
}
|
|
6844
|
+
if ("dataTransfer" in event && Array.from(event.dataTransfer?.items ?? []).some(
|
|
6845
|
+
(item) => item.type === "text/html"
|
|
6846
|
+
)) {
|
|
6847
|
+
hasMarkupInsertion = true;
|
|
6848
|
+
}
|
|
6849
|
+
if (hasMarkupInsertion) {
|
|
6850
|
+
const imagesToNodes = [];
|
|
6851
|
+
setDisabled(true);
|
|
6852
|
+
prosemirrorIterator(slice, (node) => {
|
|
6853
|
+
if (node.type.name === "image" && !node.attrs.fileId) {
|
|
6854
|
+
try {
|
|
6855
|
+
const url = new URL(node.attrs.src).toString();
|
|
6856
|
+
imagesToNodes.push([url, node]);
|
|
6857
|
+
} catch {
|
|
6858
|
+
}
|
|
6859
|
+
}
|
|
6860
|
+
});
|
|
6861
|
+
uploadMultipleByURL(imagesToNodes.map(([url]) => url)).then((results) => {
|
|
6862
|
+
results.forEach((result, index) => {
|
|
6863
|
+
const node = imagesToNodes[index][1];
|
|
6864
|
+
node.attrs.src = null;
|
|
6865
|
+
node.attrs.fileId = result?.fileId;
|
|
6866
|
+
node.attrs.storageKey = result?.storageKey;
|
|
6867
|
+
});
|
|
6868
|
+
view.dispatch(view.state.tr.replaceSelection(slice));
|
|
6869
|
+
}).finally(() => {
|
|
6870
|
+
setDisabled(false);
|
|
6871
|
+
});
|
|
6872
|
+
return true;
|
|
6873
|
+
}
|
|
6874
|
+
}
|
|
6875
|
+
return {
|
|
6876
|
+
insertedImage,
|
|
6877
|
+
onPaste,
|
|
6878
|
+
onDrop,
|
|
6879
|
+
onMarkupInsertion
|
|
6880
|
+
};
|
|
6881
|
+
}
|
|
6882
|
+
|
|
6777
6883
|
// src/editor/commands/suggestion.tsx
|
|
6778
6884
|
var import_dom = require("@floating-ui/dom");
|
|
6779
6885
|
var import_core19 = require("@tiptap/core");
|
|
6780
|
-
var
|
|
6886
|
+
var import_react36 = require("@tiptap/react");
|
|
6781
6887
|
|
|
6782
6888
|
// src/editor/commands/items.ts
|
|
6783
6889
|
var getCommandItems = (translations2, hasImageHandlers) => [
|
|
@@ -7110,7 +7216,7 @@ var createSuggestions = (translations2, hasImageHandlers) => ({
|
|
|
7110
7216
|
render: () => {
|
|
7111
7217
|
return {
|
|
7112
7218
|
onStart: async (props) => {
|
|
7113
|
-
component = new
|
|
7219
|
+
component = new import_react36.ReactRenderer(CommandsList, {
|
|
7114
7220
|
props,
|
|
7115
7221
|
editor: props.editor
|
|
7116
7222
|
});
|
|
@@ -7533,27 +7639,35 @@ function TiptapContent({
|
|
|
7533
7639
|
lang,
|
|
7534
7640
|
error
|
|
7535
7641
|
}) {
|
|
7536
|
-
const [linkDialogContext, setLinkDialogContext] = (0,
|
|
7537
|
-
const [katexDialogContext, setKatexDialogContext] = (0,
|
|
7642
|
+
const [linkDialogContext, setLinkDialogContext] = (0, import_react37.useState)(null);
|
|
7643
|
+
const [katexDialogContext, setKatexDialogContext] = (0, import_react37.useState)(
|
|
7538
7644
|
null
|
|
7539
7645
|
);
|
|
7540
|
-
const [editFigureContext, setEditFigureContext] = (0,
|
|
7541
|
-
const [savedDialogCursorPos, setSavedDialogCursorPos] = (0,
|
|
7646
|
+
const [editFigureContext, setEditFigureContext] = (0, import_react37.useState)(null);
|
|
7647
|
+
const [savedDialogCursorPos, setSavedDialogCursorPos] = (0, import_react37.useState)(void 0);
|
|
7542
7648
|
const { activeDialog, closeDialogCommand, openDialogCommand } = useDialogCommand();
|
|
7543
7649
|
const translations2 = useTiptapTranslations();
|
|
7650
|
+
const [isDisabledInternal, setIsDisabledInternal] = (0, import_react37.useState)(isDisabled);
|
|
7651
|
+
const disabled = isDisabled || isDisabledInternal;
|
|
7544
7652
|
let safeContent;
|
|
7545
7653
|
try {
|
|
7546
7654
|
safeContent = JSON.parse(content);
|
|
7547
7655
|
} catch {
|
|
7548
7656
|
safeContent = { type: "doc", content: [{ type: "paragraph" }] };
|
|
7549
7657
|
}
|
|
7550
|
-
const
|
|
7658
|
+
const {
|
|
7659
|
+
insertedImage,
|
|
7660
|
+
onPaste,
|
|
7661
|
+
onDrop,
|
|
7662
|
+
onMarkupInsertion
|
|
7663
|
+
} = useInsertionHandlers();
|
|
7664
|
+
const editor = (0, import_react38.useEditor)(
|
|
7551
7665
|
{
|
|
7552
|
-
editable: isEditable && !
|
|
7666
|
+
editable: isEditable && !disabled,
|
|
7553
7667
|
extensions: getEditorExtensions({
|
|
7554
7668
|
translations: translations2,
|
|
7555
7669
|
suggestion: createSuggestions(translations2, !!imageHandlers),
|
|
7556
|
-
isEditable: isEditable && !
|
|
7670
|
+
isEditable: isEditable && !disabled,
|
|
7557
7671
|
getImageById: imageHandlers?.getImage,
|
|
7558
7672
|
onKatexClick: (latex, pos) => {
|
|
7559
7673
|
setKatexDialogContext({ latex, pos });
|
|
@@ -7567,14 +7681,38 @@ function TiptapContent({
|
|
|
7567
7681
|
content: safeContent,
|
|
7568
7682
|
immediatelyRender: false,
|
|
7569
7683
|
editorProps: {
|
|
7570
|
-
|
|
7684
|
+
handleDOMEvents: {
|
|
7685
|
+
paste: onPaste,
|
|
7686
|
+
drop: onDrop
|
|
7687
|
+
},
|
|
7688
|
+
handleDrop: (view, event, slice) => {
|
|
7689
|
+
if (view.dragging) return false;
|
|
7690
|
+
const toReturn = onMarkupInsertion(
|
|
7691
|
+
view,
|
|
7692
|
+
event,
|
|
7693
|
+
slice,
|
|
7694
|
+
setIsDisabledInternal,
|
|
7695
|
+
imageHandlers?.uploadMultipleByURL
|
|
7696
|
+
);
|
|
7697
|
+
if (typeof toReturn === "boolean") return toReturn;
|
|
7698
|
+
return false;
|
|
7699
|
+
},
|
|
7700
|
+
handlePaste: (view, event, slice) => {
|
|
7701
|
+
const toReturn = onMarkupInsertion(
|
|
7702
|
+
view,
|
|
7703
|
+
event,
|
|
7704
|
+
slice,
|
|
7705
|
+
setIsDisabledInternal,
|
|
7706
|
+
imageHandlers?.uploadMultipleByURL
|
|
7707
|
+
);
|
|
7708
|
+
if (typeof toReturn === "boolean") return toReturn;
|
|
7571
7709
|
try {
|
|
7572
7710
|
const text = event.clipboardData?.getData("text/plain");
|
|
7573
7711
|
if (!text) return false;
|
|
7574
7712
|
const json = JSON.parse(text);
|
|
7575
7713
|
if (json.type === "doc" && json.content) {
|
|
7576
7714
|
const node = view.state.schema.nodeFromJSON(json);
|
|
7577
|
-
const newSlice = new
|
|
7715
|
+
const newSlice = new import_model2.Slice(node.content, 0, 0);
|
|
7578
7716
|
view.dispatch(view.state.tr.replaceSelection(newSlice));
|
|
7579
7717
|
return true;
|
|
7580
7718
|
}
|
|
@@ -7589,7 +7727,7 @@ function TiptapContent({
|
|
|
7589
7727
|
},
|
|
7590
7728
|
[lang]
|
|
7591
7729
|
);
|
|
7592
|
-
(0,
|
|
7730
|
+
(0, import_react37.useEffect)(() => {
|
|
7593
7731
|
if (activeDialog && editor && !editor.isDestroyed) {
|
|
7594
7732
|
const pos = editor.state.selection.from;
|
|
7595
7733
|
setSavedDialogCursorPos(pos);
|
|
@@ -7616,7 +7754,7 @@ function TiptapContent({
|
|
|
7616
7754
|
const katexHandler = createHandleKatexAdd(editor, handleCloseDialog, savedDialogCursorPos);
|
|
7617
7755
|
katexHandler(latex, katexDialogContext?.pos);
|
|
7618
7756
|
};
|
|
7619
|
-
(0,
|
|
7757
|
+
(0, import_react37.useEffect)(() => {
|
|
7620
7758
|
if (!editor || editor.isDestroyed) return;
|
|
7621
7759
|
editor.commands.setContent(safeContent, {
|
|
7622
7760
|
emitUpdate: false
|
|
@@ -7637,8 +7775,8 @@ function TiptapContent({
|
|
|
7637
7775
|
if (!editor) return null;
|
|
7638
7776
|
return /* @__PURE__ */ (0, import_jsx_runtime92.jsxs)(import_jsx_runtime92.Fragment, { children: [
|
|
7639
7777
|
/* @__PURE__ */ (0, import_jsx_runtime92.jsx)(DragHandle, { editor }),
|
|
7640
|
-
isEditable && !
|
|
7641
|
-
isEditable && !
|
|
7778
|
+
isEditable && !disabled && /* @__PURE__ */ (0, import_jsx_runtime92.jsx)(TableMenu, { editor }),
|
|
7779
|
+
isEditable && !disabled && /* @__PURE__ */ (0, import_jsx_runtime92.jsx)(
|
|
7642
7780
|
import_menus.BubbleMenu,
|
|
7643
7781
|
{
|
|
7644
7782
|
shouldShow: shouldBubbleMenuShow,
|
|
@@ -7656,10 +7794,10 @@ function TiptapContent({
|
|
|
7656
7794
|
/* @__PURE__ */ (0, import_jsx_runtime92.jsxs)(
|
|
7657
7795
|
"div",
|
|
7658
7796
|
{
|
|
7659
|
-
className: `tiptap-content-wrapper ${!!error ? "has-error" : ""} ${
|
|
7797
|
+
className: `tiptap-content-wrapper ${!!error ? "has-error" : ""} ${disabled ? "is-disabled" : ""}`,
|
|
7660
7798
|
children: [
|
|
7661
|
-
/* @__PURE__ */ (0, import_jsx_runtime92.jsx)(
|
|
7662
|
-
isEditable && !
|
|
7799
|
+
/* @__PURE__ */ (0, import_jsx_runtime92.jsx)(import_react38.EditorContent, { editor }),
|
|
7800
|
+
isEditable && !disabled && /* @__PURE__ */ (0, import_jsx_runtime92.jsx)(CharacterCount, { editor })
|
|
7663
7801
|
]
|
|
7664
7802
|
}
|
|
7665
7803
|
),
|
|
@@ -7670,6 +7808,7 @@ function TiptapContent({
|
|
|
7670
7808
|
{
|
|
7671
7809
|
open: true,
|
|
7672
7810
|
onClose: handleCloseDialog,
|
|
7811
|
+
defaultFile: insertedImage,
|
|
7673
7812
|
onImageAdd: handleImageAdd,
|
|
7674
7813
|
onImageEdit: handleImageEdit,
|
|
7675
7814
|
uploadHandler: imageHandlers?.upload,
|