@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.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// src/editor/editor.tsx
|
|
2
|
-
import { useState as
|
|
2
|
+
import { useState as useState13, useEffect as useEffect11 } from "react";
|
|
3
3
|
import { NodeSelection } from "@tiptap/pm/state";
|
|
4
|
-
import { Slice } from "@tiptap/pm/model";
|
|
4
|
+
import { Slice as Slice2 } from "@tiptap/pm/model";
|
|
5
5
|
import { useEditor, EditorContent } from "@tiptap/react";
|
|
6
6
|
import { BubbleMenu } from "@tiptap/react/menus";
|
|
7
7
|
import "highlight.js/styles/github-dark.css";
|
|
@@ -1764,7 +1764,7 @@ var TableMenu = ({ editor }) => {
|
|
|
1764
1764
|
};
|
|
1765
1765
|
|
|
1766
1766
|
// src/editor/dialogs/image-dialog/image-dialog.tsx
|
|
1767
|
-
import { useRef as useRef6, useState as useState6 } from "react";
|
|
1767
|
+
import { useEffect as useEffect5, useRef as useRef6, useState as useState6 } from "react";
|
|
1768
1768
|
|
|
1769
1769
|
// src/editor/components/button/button.tsx
|
|
1770
1770
|
import { jsx as jsx64 } from "react/jsx-runtime";
|
|
@@ -1880,6 +1880,7 @@ import { Fragment as Fragment2, jsx as jsx67, jsxs as jsxs18 } from "react/jsx-r
|
|
|
1880
1880
|
var ImageDialog = ({
|
|
1881
1881
|
open,
|
|
1882
1882
|
onClose,
|
|
1883
|
+
defaultFile,
|
|
1883
1884
|
onImageAdd,
|
|
1884
1885
|
onImageEdit,
|
|
1885
1886
|
uploadHandler,
|
|
@@ -1963,6 +1964,11 @@ var ImageDialog = ({
|
|
|
1963
1964
|
}));
|
|
1964
1965
|
};
|
|
1965
1966
|
};
|
|
1967
|
+
useEffect5(() => {
|
|
1968
|
+
if (defaultFile) {
|
|
1969
|
+
handleSelectFile(defaultFile);
|
|
1970
|
+
}
|
|
1971
|
+
}, []);
|
|
1966
1972
|
const handleAreaClick = () => {
|
|
1967
1973
|
inputRef.current?.click();
|
|
1968
1974
|
};
|
|
@@ -2154,7 +2160,7 @@ var ImageDialog = ({
|
|
|
2154
2160
|
};
|
|
2155
2161
|
|
|
2156
2162
|
// src/editor/dialogs/add-katex-dialog/add-katex-dialog.tsx
|
|
2157
|
-
import { useEffect as
|
|
2163
|
+
import { useEffect as useEffect6, useRef as useRef7, useState as useState7 } from "react";
|
|
2158
2164
|
import { jsx as jsx68, jsxs as jsxs19 } from "react/jsx-runtime";
|
|
2159
2165
|
var AddKatexDialog = ({
|
|
2160
2166
|
open,
|
|
@@ -2165,7 +2171,7 @@ var AddKatexDialog = ({
|
|
|
2165
2171
|
const translations2 = useTiptapTranslations();
|
|
2166
2172
|
const [content, setContent] = useState7(initialContent);
|
|
2167
2173
|
const contentInputRef = useRef7(null);
|
|
2168
|
-
|
|
2174
|
+
useEffect6(() => {
|
|
2169
2175
|
if (open) {
|
|
2170
2176
|
setContent(initialContent);
|
|
2171
2177
|
setTimeout(() => {
|
|
@@ -2264,7 +2270,7 @@ var AddKatexDialog = ({
|
|
|
2264
2270
|
};
|
|
2265
2271
|
|
|
2266
2272
|
// src/editor/dialogs/add-link-dialog/add-link-dialog.tsx
|
|
2267
|
-
import { useEffect as
|
|
2273
|
+
import { useEffect as useEffect7, useRef as useRef8, useState as useState8 } from "react";
|
|
2268
2274
|
import { jsx as jsx69, jsxs as jsxs20 } from "react/jsx-runtime";
|
|
2269
2275
|
var AddLinkDialog = ({
|
|
2270
2276
|
open,
|
|
@@ -2288,7 +2294,7 @@ var AddLinkDialog = ({
|
|
|
2288
2294
|
setAddNoopener(initialLinkRel);
|
|
2289
2295
|
setError("");
|
|
2290
2296
|
};
|
|
2291
|
-
|
|
2297
|
+
useEffect7(() => {
|
|
2292
2298
|
if (open) {
|
|
2293
2299
|
clearDialogState();
|
|
2294
2300
|
setTimeout(() => {
|
|
@@ -2455,7 +2461,7 @@ var AddLinkDialog = ({
|
|
|
2455
2461
|
};
|
|
2456
2462
|
|
|
2457
2463
|
// src/editor/dialogs/add-video-dialog/add-video-dialog.tsx
|
|
2458
|
-
import { useEffect as
|
|
2464
|
+
import { useEffect as useEffect8, useRef as useRef9, useState as useState9 } from "react";
|
|
2459
2465
|
|
|
2460
2466
|
// src/utils/detect-video-hosting.ts
|
|
2461
2467
|
var VIDEO_PATTERNS = {
|
|
@@ -2542,7 +2548,7 @@ var AddVideoDialog = ({ open, onClose, onVideoAdd }) => {
|
|
|
2542
2548
|
const [error, setError] = useState9("");
|
|
2543
2549
|
const [isValidating, setIsValidating] = useState9(false);
|
|
2544
2550
|
const urlInputRef = useRef9(null);
|
|
2545
|
-
|
|
2551
|
+
useEffect8(() => {
|
|
2546
2552
|
if (open) {
|
|
2547
2553
|
setUrl("");
|
|
2548
2554
|
setError("");
|
|
@@ -5636,13 +5642,13 @@ var Video = ({ src, children }) => {
|
|
|
5636
5642
|
};
|
|
5637
5643
|
|
|
5638
5644
|
// src/editor/controls/emoji-picker.tsx
|
|
5639
|
-
import { useEffect as
|
|
5645
|
+
import { useEffect as useEffect9, useRef as useRef10 } from "react";
|
|
5640
5646
|
import EmojiPicker from "emoji-picker-react";
|
|
5641
5647
|
import { jsx as jsx83 } from "react/jsx-runtime";
|
|
5642
5648
|
var EmojiPickerComponent = ({ isOpen, onClose, onEmojiSelect }) => {
|
|
5643
5649
|
const pickerRef = useRef10(null);
|
|
5644
5650
|
const translations2 = useTiptapTranslations();
|
|
5645
|
-
|
|
5651
|
+
useEffect9(() => {
|
|
5646
5652
|
const handleClickOutside = (event) => {
|
|
5647
5653
|
if (pickerRef.current && !pickerRef.current.contains(event.target)) {
|
|
5648
5654
|
onClose();
|
|
@@ -6737,6 +6743,106 @@ var createHandleKatexAdd = (editor, closeDialogCommand, savedPos) => {
|
|
|
6737
6743
|
};
|
|
6738
6744
|
};
|
|
6739
6745
|
|
|
6746
|
+
// src/editor/handlers/insertion-handlers.ts
|
|
6747
|
+
import { useEffect as useEffect10, useState as useState12 } from "react";
|
|
6748
|
+
|
|
6749
|
+
// src/utils/prosemirror-iterator.ts
|
|
6750
|
+
import { Fragment as Fragment3, Slice } from "@tiptap/pm/model";
|
|
6751
|
+
function prosemirrorIterator(slice, cb) {
|
|
6752
|
+
const stack = [];
|
|
6753
|
+
if (slice instanceof Slice) {
|
|
6754
|
+
collectFragmentChildren(slice.content, stack);
|
|
6755
|
+
} else if (slice instanceof Fragment3) {
|
|
6756
|
+
collectFragmentChildren(slice, stack);
|
|
6757
|
+
} else {
|
|
6758
|
+
stack.push(slice);
|
|
6759
|
+
}
|
|
6760
|
+
while (stack.length > 0) {
|
|
6761
|
+
const node = stack.pop();
|
|
6762
|
+
const shouldStop = cb(node);
|
|
6763
|
+
if (shouldStop === true) break;
|
|
6764
|
+
if (node.content.size > 0) {
|
|
6765
|
+
collectFragmentChildren(node.content, stack);
|
|
6766
|
+
}
|
|
6767
|
+
}
|
|
6768
|
+
}
|
|
6769
|
+
function collectFragmentChildren(fragment, stack) {
|
|
6770
|
+
for (let i = fragment.childCount - 1; i >= 0; i--) {
|
|
6771
|
+
stack.push(fragment.child(i));
|
|
6772
|
+
}
|
|
6773
|
+
}
|
|
6774
|
+
|
|
6775
|
+
// src/editor/handlers/insertion-handlers.ts
|
|
6776
|
+
function useInsertionHandlers() {
|
|
6777
|
+
const [insertedImage, setInsertedImage] = useState12(null);
|
|
6778
|
+
const { activeDialog, openDialogCommand } = useDialogCommand();
|
|
6779
|
+
useEffect10(() => {
|
|
6780
|
+
if (insertedImage && !activeDialog) {
|
|
6781
|
+
setInsertedImage(null);
|
|
6782
|
+
}
|
|
6783
|
+
}, [insertedImage, activeDialog]);
|
|
6784
|
+
function genericInsertionHandler(file, event) {
|
|
6785
|
+
if (!file || !file.type.startsWith("image/")) return;
|
|
6786
|
+
event.preventDefault();
|
|
6787
|
+
event.stopPropagation();
|
|
6788
|
+
setInsertedImage(file);
|
|
6789
|
+
openDialogCommand("image");
|
|
6790
|
+
}
|
|
6791
|
+
function onPaste(_, event) {
|
|
6792
|
+
const file = event.clipboardData?.files?.[0];
|
|
6793
|
+
genericInsertionHandler(file, event);
|
|
6794
|
+
}
|
|
6795
|
+
function onDrop(_, event) {
|
|
6796
|
+
const file = event.dataTransfer?.files?.[0];
|
|
6797
|
+
genericInsertionHandler(file, event);
|
|
6798
|
+
}
|
|
6799
|
+
function onMarkupInsertion(view, event, slice, setDisabled, uploadMultipleByURL) {
|
|
6800
|
+
if (!uploadMultipleByURL) return;
|
|
6801
|
+
let hasMarkupInsertion = false;
|
|
6802
|
+
if ("clipboardData" in event && Array.from(event.clipboardData?.items ?? []).some(
|
|
6803
|
+
(item) => item.type === "text/html"
|
|
6804
|
+
)) {
|
|
6805
|
+
hasMarkupInsertion = true;
|
|
6806
|
+
}
|
|
6807
|
+
if ("dataTransfer" in event && Array.from(event.dataTransfer?.items ?? []).some(
|
|
6808
|
+
(item) => item.type === "text/html"
|
|
6809
|
+
)) {
|
|
6810
|
+
hasMarkupInsertion = true;
|
|
6811
|
+
}
|
|
6812
|
+
if (hasMarkupInsertion) {
|
|
6813
|
+
const imagesToNodes = [];
|
|
6814
|
+
setDisabled(true);
|
|
6815
|
+
prosemirrorIterator(slice, (node) => {
|
|
6816
|
+
if (node.type.name === "image" && !node.attrs.fileId) {
|
|
6817
|
+
try {
|
|
6818
|
+
const url = new URL(node.attrs.src).toString();
|
|
6819
|
+
imagesToNodes.push([url, node]);
|
|
6820
|
+
} catch {
|
|
6821
|
+
}
|
|
6822
|
+
}
|
|
6823
|
+
});
|
|
6824
|
+
uploadMultipleByURL(imagesToNodes.map(([url]) => url)).then((results) => {
|
|
6825
|
+
results.forEach((result, index) => {
|
|
6826
|
+
const node = imagesToNodes[index][1];
|
|
6827
|
+
node.attrs.src = null;
|
|
6828
|
+
node.attrs.fileId = result?.fileId;
|
|
6829
|
+
node.attrs.storageKey = result?.storageKey;
|
|
6830
|
+
});
|
|
6831
|
+
view.dispatch(view.state.tr.replaceSelection(slice));
|
|
6832
|
+
}).finally(() => {
|
|
6833
|
+
setDisabled(false);
|
|
6834
|
+
});
|
|
6835
|
+
return true;
|
|
6836
|
+
}
|
|
6837
|
+
}
|
|
6838
|
+
return {
|
|
6839
|
+
insertedImage,
|
|
6840
|
+
onPaste,
|
|
6841
|
+
onDrop,
|
|
6842
|
+
onMarkupInsertion
|
|
6843
|
+
};
|
|
6844
|
+
}
|
|
6845
|
+
|
|
6740
6846
|
// src/editor/commands/suggestion.tsx
|
|
6741
6847
|
import { computePosition, flip, shift } from "@floating-ui/dom";
|
|
6742
6848
|
import { posToDOMRect } from "@tiptap/core";
|
|
@@ -7486,7 +7592,7 @@ function getTranslations(lang) {
|
|
|
7486
7592
|
}
|
|
7487
7593
|
|
|
7488
7594
|
// src/editor/editor.tsx
|
|
7489
|
-
import { Fragment as
|
|
7595
|
+
import { Fragment as Fragment4, jsx as jsx92, jsxs as jsxs35 } from "react/jsx-runtime";
|
|
7490
7596
|
function TiptapContent({
|
|
7491
7597
|
content,
|
|
7492
7598
|
handleChange,
|
|
@@ -7496,27 +7602,35 @@ function TiptapContent({
|
|
|
7496
7602
|
lang,
|
|
7497
7603
|
error
|
|
7498
7604
|
}) {
|
|
7499
|
-
const [linkDialogContext, setLinkDialogContext] =
|
|
7500
|
-
const [katexDialogContext, setKatexDialogContext] =
|
|
7605
|
+
const [linkDialogContext, setLinkDialogContext] = useState13(null);
|
|
7606
|
+
const [katexDialogContext, setKatexDialogContext] = useState13(
|
|
7501
7607
|
null
|
|
7502
7608
|
);
|
|
7503
|
-
const [editFigureContext, setEditFigureContext] =
|
|
7504
|
-
const [savedDialogCursorPos, setSavedDialogCursorPos] =
|
|
7609
|
+
const [editFigureContext, setEditFigureContext] = useState13(null);
|
|
7610
|
+
const [savedDialogCursorPos, setSavedDialogCursorPos] = useState13(void 0);
|
|
7505
7611
|
const { activeDialog, closeDialogCommand, openDialogCommand } = useDialogCommand();
|
|
7506
7612
|
const translations2 = useTiptapTranslations();
|
|
7613
|
+
const [isDisabledInternal, setIsDisabledInternal] = useState13(isDisabled);
|
|
7614
|
+
const disabled = isDisabled || isDisabledInternal;
|
|
7507
7615
|
let safeContent;
|
|
7508
7616
|
try {
|
|
7509
7617
|
safeContent = JSON.parse(content);
|
|
7510
7618
|
} catch {
|
|
7511
7619
|
safeContent = { type: "doc", content: [{ type: "paragraph" }] };
|
|
7512
7620
|
}
|
|
7621
|
+
const {
|
|
7622
|
+
insertedImage,
|
|
7623
|
+
onPaste,
|
|
7624
|
+
onDrop,
|
|
7625
|
+
onMarkupInsertion
|
|
7626
|
+
} = useInsertionHandlers();
|
|
7513
7627
|
const editor = useEditor(
|
|
7514
7628
|
{
|
|
7515
|
-
editable: isEditable && !
|
|
7629
|
+
editable: isEditable && !disabled,
|
|
7516
7630
|
extensions: getEditorExtensions({
|
|
7517
7631
|
translations: translations2,
|
|
7518
7632
|
suggestion: createSuggestions(translations2, !!imageHandlers),
|
|
7519
|
-
isEditable: isEditable && !
|
|
7633
|
+
isEditable: isEditable && !disabled,
|
|
7520
7634
|
getImageById: imageHandlers?.getImage,
|
|
7521
7635
|
onKatexClick: (latex, pos) => {
|
|
7522
7636
|
setKatexDialogContext({ latex, pos });
|
|
@@ -7530,14 +7644,38 @@ function TiptapContent({
|
|
|
7530
7644
|
content: safeContent,
|
|
7531
7645
|
immediatelyRender: false,
|
|
7532
7646
|
editorProps: {
|
|
7533
|
-
|
|
7647
|
+
handleDOMEvents: {
|
|
7648
|
+
paste: onPaste,
|
|
7649
|
+
drop: onDrop
|
|
7650
|
+
},
|
|
7651
|
+
handleDrop: (view, event, slice) => {
|
|
7652
|
+
if (view.dragging) return false;
|
|
7653
|
+
const toReturn = onMarkupInsertion(
|
|
7654
|
+
view,
|
|
7655
|
+
event,
|
|
7656
|
+
slice,
|
|
7657
|
+
setIsDisabledInternal,
|
|
7658
|
+
imageHandlers?.uploadMultipleByURL
|
|
7659
|
+
);
|
|
7660
|
+
if (typeof toReturn === "boolean") return toReturn;
|
|
7661
|
+
return false;
|
|
7662
|
+
},
|
|
7663
|
+
handlePaste: (view, event, slice) => {
|
|
7664
|
+
const toReturn = onMarkupInsertion(
|
|
7665
|
+
view,
|
|
7666
|
+
event,
|
|
7667
|
+
slice,
|
|
7668
|
+
setIsDisabledInternal,
|
|
7669
|
+
imageHandlers?.uploadMultipleByURL
|
|
7670
|
+
);
|
|
7671
|
+
if (typeof toReturn === "boolean") return toReturn;
|
|
7534
7672
|
try {
|
|
7535
7673
|
const text = event.clipboardData?.getData("text/plain");
|
|
7536
7674
|
if (!text) return false;
|
|
7537
7675
|
const json = JSON.parse(text);
|
|
7538
7676
|
if (json.type === "doc" && json.content) {
|
|
7539
7677
|
const node = view.state.schema.nodeFromJSON(json);
|
|
7540
|
-
const newSlice = new
|
|
7678
|
+
const newSlice = new Slice2(node.content, 0, 0);
|
|
7541
7679
|
view.dispatch(view.state.tr.replaceSelection(newSlice));
|
|
7542
7680
|
return true;
|
|
7543
7681
|
}
|
|
@@ -7552,7 +7690,7 @@ function TiptapContent({
|
|
|
7552
7690
|
},
|
|
7553
7691
|
[lang]
|
|
7554
7692
|
);
|
|
7555
|
-
|
|
7693
|
+
useEffect11(() => {
|
|
7556
7694
|
if (activeDialog && editor && !editor.isDestroyed) {
|
|
7557
7695
|
const pos = editor.state.selection.from;
|
|
7558
7696
|
setSavedDialogCursorPos(pos);
|
|
@@ -7579,7 +7717,7 @@ function TiptapContent({
|
|
|
7579
7717
|
const katexHandler = createHandleKatexAdd(editor, handleCloseDialog, savedDialogCursorPos);
|
|
7580
7718
|
katexHandler(latex, katexDialogContext?.pos);
|
|
7581
7719
|
};
|
|
7582
|
-
|
|
7720
|
+
useEffect11(() => {
|
|
7583
7721
|
if (!editor || editor.isDestroyed) return;
|
|
7584
7722
|
editor.commands.setContent(safeContent, {
|
|
7585
7723
|
emitUpdate: false
|
|
@@ -7598,10 +7736,10 @@ function TiptapContent({
|
|
|
7598
7736
|
return true;
|
|
7599
7737
|
};
|
|
7600
7738
|
if (!editor) return null;
|
|
7601
|
-
return /* @__PURE__ */ jsxs35(
|
|
7739
|
+
return /* @__PURE__ */ jsxs35(Fragment4, { children: [
|
|
7602
7740
|
/* @__PURE__ */ jsx92(DragHandle, { editor }),
|
|
7603
|
-
isEditable && !
|
|
7604
|
-
isEditable && !
|
|
7741
|
+
isEditable && !disabled && /* @__PURE__ */ jsx92(TableMenu, { editor }),
|
|
7742
|
+
isEditable && !disabled && /* @__PURE__ */ jsx92(
|
|
7605
7743
|
BubbleMenu,
|
|
7606
7744
|
{
|
|
7607
7745
|
shouldShow: shouldBubbleMenuShow,
|
|
@@ -7619,20 +7757,21 @@ function TiptapContent({
|
|
|
7619
7757
|
/* @__PURE__ */ jsxs35(
|
|
7620
7758
|
"div",
|
|
7621
7759
|
{
|
|
7622
|
-
className: `tiptap-content-wrapper ${!!error ? "has-error" : ""} ${
|
|
7760
|
+
className: `tiptap-content-wrapper ${!!error ? "has-error" : ""} ${disabled ? "is-disabled" : ""}`,
|
|
7623
7761
|
children: [
|
|
7624
7762
|
/* @__PURE__ */ jsx92(EditorContent, { editor }),
|
|
7625
|
-
isEditable && !
|
|
7763
|
+
isEditable && !disabled && /* @__PURE__ */ jsx92(CharacterCount, { editor })
|
|
7626
7764
|
]
|
|
7627
7765
|
}
|
|
7628
7766
|
),
|
|
7629
7767
|
!!error && /* @__PURE__ */ jsx92("p", { className: "tiptap-error-message", children: error }),
|
|
7630
|
-
isEditable && /* @__PURE__ */ jsxs35(
|
|
7768
|
+
isEditable && /* @__PURE__ */ jsxs35(Fragment4, { children: [
|
|
7631
7769
|
activeDialog === "image" && /* @__PURE__ */ jsx92(
|
|
7632
7770
|
ImageDialog,
|
|
7633
7771
|
{
|
|
7634
7772
|
open: true,
|
|
7635
7773
|
onClose: handleCloseDialog,
|
|
7774
|
+
defaultFile: insertedImage,
|
|
7636
7775
|
onImageAdd: handleImageAdd,
|
|
7637
7776
|
onImageEdit: handleImageEdit,
|
|
7638
7777
|
uploadHandler: imageHandlers?.upload,
|