@cyberyozh/text-editor 0.1.16 → 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.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  // src/editor/editor.tsx
2
- import { useState as useState12, useEffect as useEffect9 } from "react";
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 useEffect5, useRef as useRef7, useState as useState7 } from "react";
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
- useEffect5(() => {
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 useEffect6, useRef as useRef8, useState as useState8 } from "react";
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
- useEffect6(() => {
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 useEffect7, useRef as useRef9, useState as useState9 } from "react";
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
- useEffect7(() => {
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 useEffect8, useRef as useRef10 } from "react";
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
- useEffect8(() => {
5651
+ useEffect9(() => {
5646
5652
  const handleClickOutside = (event) => {
5647
5653
  if (pickerRef.current && !pickerRef.current.contains(event.target)) {
5648
5654
  onClose();
@@ -6433,7 +6439,8 @@ var Figure2 = BaseFigure.extend({
6433
6439
  };
6434
6440
  const updateImageDimensions = (width, height) => {
6435
6441
  imageContainer.style.width = `${width}px`;
6436
- imageContainer.style.height = `${height}px`;
6442
+ imageContainer.style.aspectRatio = `${width} / ${height}`;
6443
+ imageContainer.style.height = "";
6437
6444
  };
6438
6445
  const figure = document.createElement("figure");
6439
6446
  figure.setAttribute("data-drag-handle", "");
@@ -6445,7 +6452,7 @@ var Figure2 = BaseFigure.extend({
6445
6452
  if (attrs.title) img.title = attrs.title;
6446
6453
  if (attrs.width && attrs.height) {
6447
6454
  imageContainer.style.width = `${attrs.width}px`;
6448
- imageContainer.style.height = `${attrs.height}px`;
6455
+ imageContainer.style.aspectRatio = `${attrs.width} / ${attrs.height}`;
6449
6456
  }
6450
6457
  img.onload = () => {
6451
6458
  naturalAspectRatio = img.naturalHeight / img.naturalWidth;
@@ -6453,15 +6460,11 @@ var Figure2 = BaseFigure.extend({
6453
6460
  maxHeight = Math.round(maxWidth * naturalAspectRatio);
6454
6461
  }
6455
6462
  if (attrs.width && attrs.height && maxWidth && attrs.width > maxWidth) {
6456
- const width = maxWidth;
6457
- const height = Math.round(attrs.height / attrs.width * maxWidth);
6458
- imageContainer.style.width = `${width}px`;
6459
- imageContainer.style.height = `${height}px`;
6460
6463
  const pos = getPos();
6461
6464
  if (typeof pos === "number") {
6462
6465
  editor.chain().updateAttributes("image", {
6463
- width: Math.round(width),
6464
- height: Math.round(height)
6466
+ width: maxWidth,
6467
+ height: Math.round(attrs.height / attrs.width * maxWidth)
6465
6468
  }).run();
6466
6469
  }
6467
6470
  }
@@ -6503,11 +6506,12 @@ var Figure2 = BaseFigure.extend({
6503
6506
  }
6504
6507
  },
6505
6508
  onUpdate: (updatedNode) => {
6506
- if (updatedNode.attrs.width) {
6507
- imageContainer.style.width = `${updatedNode.attrs.width}px`;
6508
- }
6509
- if (updatedNode.attrs.height) {
6510
- imageContainer.style.height = `${updatedNode.attrs.height}px`;
6509
+ const newWidth = updatedNode.attrs.width;
6510
+ const newHeight = updatedNode.attrs.height;
6511
+ if (newWidth) imageContainer.style.width = `${newWidth}px`;
6512
+ if (newWidth && newHeight) {
6513
+ imageContainer.style.aspectRatio = `${newWidth} / ${newHeight}`;
6514
+ imageContainer.style.height = "";
6511
6515
  }
6512
6516
  const existingCaption = figure.querySelector("figcaption");
6513
6517
  if (updatedNode.attrs.caption && !existingCaption) {
@@ -6545,6 +6549,7 @@ var Figure2 = BaseFigure.extend({
6545
6549
  }
6546
6550
  }
6547
6551
  });
6552
+ imageContainer.style.height = "";
6548
6553
  figure.appendChild(resizableView.dom);
6549
6554
  if (attrs.caption) {
6550
6555
  const figcaption = document.createElement("figcaption");
@@ -6738,6 +6743,106 @@ var createHandleKatexAdd = (editor, closeDialogCommand, savedPos) => {
6738
6743
  };
6739
6744
  };
6740
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
+
6741
6846
  // src/editor/commands/suggestion.tsx
6742
6847
  import { computePosition, flip, shift } from "@floating-ui/dom";
6743
6848
  import { posToDOMRect } from "@tiptap/core";
@@ -7487,7 +7592,7 @@ function getTranslations(lang) {
7487
7592
  }
7488
7593
 
7489
7594
  // src/editor/editor.tsx
7490
- import { Fragment as Fragment3, jsx as jsx92, jsxs as jsxs35 } from "react/jsx-runtime";
7595
+ import { Fragment as Fragment4, jsx as jsx92, jsxs as jsxs35 } from "react/jsx-runtime";
7491
7596
  function TiptapContent({
7492
7597
  content,
7493
7598
  handleChange,
@@ -7497,27 +7602,35 @@ function TiptapContent({
7497
7602
  lang,
7498
7603
  error
7499
7604
  }) {
7500
- const [linkDialogContext, setLinkDialogContext] = useState12(null);
7501
- const [katexDialogContext, setKatexDialogContext] = useState12(
7605
+ const [linkDialogContext, setLinkDialogContext] = useState13(null);
7606
+ const [katexDialogContext, setKatexDialogContext] = useState13(
7502
7607
  null
7503
7608
  );
7504
- const [editFigureContext, setEditFigureContext] = useState12(null);
7505
- const [savedDialogCursorPos, setSavedDialogCursorPos] = useState12(void 0);
7609
+ const [editFigureContext, setEditFigureContext] = useState13(null);
7610
+ const [savedDialogCursorPos, setSavedDialogCursorPos] = useState13(void 0);
7506
7611
  const { activeDialog, closeDialogCommand, openDialogCommand } = useDialogCommand();
7507
7612
  const translations2 = useTiptapTranslations();
7613
+ const [isDisabledInternal, setIsDisabledInternal] = useState13(isDisabled);
7614
+ const disabled = isDisabled || isDisabledInternal;
7508
7615
  let safeContent;
7509
7616
  try {
7510
7617
  safeContent = JSON.parse(content);
7511
7618
  } catch {
7512
7619
  safeContent = { type: "doc", content: [{ type: "paragraph" }] };
7513
7620
  }
7621
+ const {
7622
+ insertedImage,
7623
+ onPaste,
7624
+ onDrop,
7625
+ onMarkupInsertion
7626
+ } = useInsertionHandlers();
7514
7627
  const editor = useEditor(
7515
7628
  {
7516
- editable: isEditable && !isDisabled,
7629
+ editable: isEditable && !disabled,
7517
7630
  extensions: getEditorExtensions({
7518
7631
  translations: translations2,
7519
7632
  suggestion: createSuggestions(translations2, !!imageHandlers),
7520
- isEditable: isEditable && !isDisabled,
7633
+ isEditable: isEditable && !disabled,
7521
7634
  getImageById: imageHandlers?.getImage,
7522
7635
  onKatexClick: (latex, pos) => {
7523
7636
  setKatexDialogContext({ latex, pos });
@@ -7531,14 +7644,38 @@ function TiptapContent({
7531
7644
  content: safeContent,
7532
7645
  immediatelyRender: false,
7533
7646
  editorProps: {
7534
- handlePaste: (view, event) => {
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;
7535
7672
  try {
7536
7673
  const text = event.clipboardData?.getData("text/plain");
7537
7674
  if (!text) return false;
7538
7675
  const json = JSON.parse(text);
7539
7676
  if (json.type === "doc" && json.content) {
7540
7677
  const node = view.state.schema.nodeFromJSON(json);
7541
- const newSlice = new Slice(node.content, 0, 0);
7678
+ const newSlice = new Slice2(node.content, 0, 0);
7542
7679
  view.dispatch(view.state.tr.replaceSelection(newSlice));
7543
7680
  return true;
7544
7681
  }
@@ -7553,7 +7690,7 @@ function TiptapContent({
7553
7690
  },
7554
7691
  [lang]
7555
7692
  );
7556
- useEffect9(() => {
7693
+ useEffect11(() => {
7557
7694
  if (activeDialog && editor && !editor.isDestroyed) {
7558
7695
  const pos = editor.state.selection.from;
7559
7696
  setSavedDialogCursorPos(pos);
@@ -7580,7 +7717,7 @@ function TiptapContent({
7580
7717
  const katexHandler = createHandleKatexAdd(editor, handleCloseDialog, savedDialogCursorPos);
7581
7718
  katexHandler(latex, katexDialogContext?.pos);
7582
7719
  };
7583
- useEffect9(() => {
7720
+ useEffect11(() => {
7584
7721
  if (!editor || editor.isDestroyed) return;
7585
7722
  editor.commands.setContent(safeContent, {
7586
7723
  emitUpdate: false
@@ -7599,10 +7736,10 @@ function TiptapContent({
7599
7736
  return true;
7600
7737
  };
7601
7738
  if (!editor) return null;
7602
- return /* @__PURE__ */ jsxs35(Fragment3, { children: [
7739
+ return /* @__PURE__ */ jsxs35(Fragment4, { children: [
7603
7740
  /* @__PURE__ */ jsx92(DragHandle, { editor }),
7604
- isEditable && !isDisabled && /* @__PURE__ */ jsx92(TableMenu, { editor }),
7605
- isEditable && !isDisabled && /* @__PURE__ */ jsx92(
7741
+ isEditable && !disabled && /* @__PURE__ */ jsx92(TableMenu, { editor }),
7742
+ isEditable && !disabled && /* @__PURE__ */ jsx92(
7606
7743
  BubbleMenu,
7607
7744
  {
7608
7745
  shouldShow: shouldBubbleMenuShow,
@@ -7620,20 +7757,21 @@ function TiptapContent({
7620
7757
  /* @__PURE__ */ jsxs35(
7621
7758
  "div",
7622
7759
  {
7623
- className: `tiptap-content-wrapper ${!!error ? "has-error" : ""} ${isDisabled ? "is-disabled" : ""}`,
7760
+ className: `tiptap-content-wrapper ${!!error ? "has-error" : ""} ${disabled ? "is-disabled" : ""}`,
7624
7761
  children: [
7625
7762
  /* @__PURE__ */ jsx92(EditorContent, { editor }),
7626
- isEditable && !isDisabled && /* @__PURE__ */ jsx92(CharacterCount, { editor })
7763
+ isEditable && !disabled && /* @__PURE__ */ jsx92(CharacterCount, { editor })
7627
7764
  ]
7628
7765
  }
7629
7766
  ),
7630
7767
  !!error && /* @__PURE__ */ jsx92("p", { className: "tiptap-error-message", children: error }),
7631
- isEditable && /* @__PURE__ */ jsxs35(Fragment3, { children: [
7768
+ isEditable && /* @__PURE__ */ jsxs35(Fragment4, { children: [
7632
7769
  activeDialog === "image" && /* @__PURE__ */ jsx92(
7633
7770
  ImageDialog,
7634
7771
  {
7635
7772
  open: true,
7636
7773
  onClose: handleCloseDialog,
7774
+ defaultFile: insertedImage,
7637
7775
  onImageAdd: handleImageAdd,
7638
7776
  onImageEdit: handleImageEdit,
7639
7777
  uploadHandler: imageHandlers?.upload,