@trafica/editor 1.0.44 → 1.0.46

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/index.js CHANGED
@@ -2203,6 +2203,10 @@ function mergeAtCursor(state, pastedDoc, sel) {
2203
2203
  const lastIdx = mergedDoc.children.length - 1;
2204
2204
  return { mergedDoc, cursorPos: endOfBlock(mergedDoc.children[lastIdx], lastIdx) };
2205
2205
  }
2206
+ const LIST_CONTAINERS = /* @__PURE__ */ new Set(["bullet_list", "ordered_list", "check_list"]);
2207
+ if (LIST_CONTAINERS.has(currentBlock.type) && relPath.length > 0) {
2208
+ return mergeIntoListContainer(state, pasted, existingBlocks, blockIdx, currentBlock, relPath, charOffset);
2209
+ }
2206
2210
  const [beforeChildren, afterChildren] = splitBlockChildren(currentBlock, relPath, charOffset);
2207
2211
  let newBlocks;
2208
2212
  let cursorBlockOffset;
@@ -2260,6 +2264,85 @@ function mergeAtCursor(state, pastedDoc, sel) {
2260
2264
  cursorPos: cursorInBlock
2261
2265
  };
2262
2266
  }
2267
+ function mergeIntoListContainer(state, pasted, existingBlocks, blockIdx, listBlock, relPath, charOffset) {
2268
+ var _a, _b, _c, _d, _e;
2269
+ const itemIdx = relPath[0];
2270
+ const itemRelPath = relPath.slice(1);
2271
+ const listItems = listBlock.children;
2272
+ const currentItem = listItems[itemIdx];
2273
+ const isCheckList = listBlock.type === "check_list";
2274
+ const itemType = isCheckList ? "check_list_item" : "list_item";
2275
+ if (!currentItem) {
2276
+ const newBlocks = [...existingBlocks, ...pasted];
2277
+ const lastIdx = newBlocks.length - 1;
2278
+ return {
2279
+ mergedDoc: { ...state.doc, children: newBlocks },
2280
+ cursorPos: endOfBlock(newBlocks[lastIdx], lastIdx)
2281
+ };
2282
+ }
2283
+ const [beforeChildren, afterChildren] = splitBlockChildren(currentItem, itemRelPath, charOffset);
2284
+ const makeItem = (children) => ({
2285
+ type: itemType,
2286
+ attrs: isCheckList ? { checked: false } : {},
2287
+ children: children.length > 0 ? children : [{ type: "text", text: "", marks: [] }]
2288
+ });
2289
+ let newItems;
2290
+ let cursorItemIdx;
2291
+ if (pasted.length === 1) {
2292
+ const pastedChildren = (_a = pasted[0].children) != null ? _a : [];
2293
+ const merged = [...beforeChildren, ...pastedChildren, ...afterChildren];
2294
+ newItems = [
2295
+ ...listItems.slice(0, itemIdx),
2296
+ { ...currentItem, children: merged.length > 0 ? merged : [{ type: "text", text: "", marks: [] }] },
2297
+ ...listItems.slice(itemIdx + 1)
2298
+ ];
2299
+ cursorItemIdx = itemIdx;
2300
+ } else {
2301
+ const firstPasted = pasted[0];
2302
+ const lastPasted = pasted[pasted.length - 1];
2303
+ const middlePasted = pasted.slice(1, -1);
2304
+ const firstItem = { ...currentItem, children: [...beforeChildren, ...(_b = firstPasted.children) != null ? _b : []] };
2305
+ const lastItem = makeItem([...(_c = lastPasted.children) != null ? _c : [], ...afterChildren]);
2306
+ const midItems = middlePasted.map((p) => {
2307
+ var _a2;
2308
+ return makeItem((_a2 = p.children) != null ? _a2 : []);
2309
+ });
2310
+ newItems = [
2311
+ ...listItems.slice(0, itemIdx),
2312
+ firstItem,
2313
+ ...midItems,
2314
+ lastItem,
2315
+ ...listItems.slice(itemIdx + 1)
2316
+ ];
2317
+ cursorItemIdx = itemIdx + 1 + midItems.length;
2318
+ }
2319
+ const newList = { ...listBlock, children: newItems };
2320
+ const mergedBlocks = [
2321
+ ...existingBlocks.slice(0, blockIdx),
2322
+ newList,
2323
+ ...existingBlocks.slice(blockIdx + 1)
2324
+ ];
2325
+ const targetItem = newItems[cursorItemIdx];
2326
+ let cursorPos;
2327
+ if (pasted.length === 1) {
2328
+ const pastedChildren = (_d = pasted[0].children) != null ? _d : [];
2329
+ const cursorChildIdx = beforeChildren.length + pastedChildren.length;
2330
+ const nodeBefore = targetItem == null ? void 0 : targetItem.children[cursorChildIdx - 1];
2331
+ cursorPos = nodeBefore && isTextNode(nodeBefore) ? { path: [blockIdx, cursorItemIdx, cursorChildIdx - 1], offset: nodeBefore.text.length } : { path: [blockIdx, cursorItemIdx], offset: 0 };
2332
+ } else {
2333
+ const lpChildren = (_e = pasted[pasted.length - 1].children) != null ? _e : [];
2334
+ if (lpChildren.length > 0) {
2335
+ const lastNode = lpChildren[lpChildren.length - 1];
2336
+ cursorPos = {
2337
+ path: [blockIdx, cursorItemIdx, lpChildren.length - 1],
2338
+ offset: isTextNode(lastNode) ? lastNode.text.length : 0
2339
+ };
2340
+ } else {
2341
+ cursorPos = { path: [blockIdx, cursorItemIdx], offset: 0 };
2342
+ }
2343
+ }
2344
+ return { mergedDoc: { ...state.doc, children: mergedBlocks }, cursorPos };
2345
+ }
2263
2346
  function splitBlockChildren(block, relPath, offset) {
2264
2347
  const children = block.children;
2265
2348
  if (children.length === 0) return [[], []];
@@ -7953,6 +8036,7 @@ function EditorCore({
7953
8036
  const isComposingRef = react.useRef(false);
7954
8037
  const stateRef = react.useRef(engine.getState());
7955
8038
  const [selectedImagePath, setSelectedImagePath] = react.useState(null);
8039
+ const [imageDragState, setImageDragState] = react.useState(null);
7956
8040
  const [findReplaceOpen, setFindReplaceOpen] = react.useState(false);
7957
8041
  const [findReplaceMode, setFindReplaceMode] = react.useState("find");
7958
8042
  const [linkPopupOpen, setLinkPopupOpen] = react.useState(false);
@@ -8433,7 +8517,21 @@ function EditorCore({
8433
8517
  const fig = target.closest("[data-image-path]");
8434
8518
  if (fig == null ? void 0 : fig.dataset.imagePath) {
8435
8519
  e.preventDefault();
8436
- setSelectedImagePath(JSON.parse(fig.dataset.imagePath));
8520
+ const path = JSON.parse(fig.dataset.imagePath);
8521
+ setSelectedImagePath(path);
8522
+ if (path.length === 1) {
8523
+ const imgEl = fig.querySelector("img");
8524
+ const rect = imgEl == null ? void 0 : imgEl.getBoundingClientRect();
8525
+ imageDragRef.current = {
8526
+ imagePath: path,
8527
+ startX: e.clientX,
8528
+ startY: e.clientY,
8529
+ active: false,
8530
+ ghostW: Math.min((rect == null ? void 0 : rect.width) || 300, 320),
8531
+ ghostH: Math.min((rect == null ? void 0 : rect.height) || 200, 200),
8532
+ ghostSrc: (imgEl == null ? void 0 : imgEl.src) || ""
8533
+ };
8534
+ }
8437
8535
  return;
8438
8536
  }
8439
8537
  setSelectedImagePath(null);
@@ -8486,6 +8584,7 @@ function EditorCore({
8486
8584
  engine.dispatch(tr);
8487
8585
  }, [engine]);
8488
8586
  const imageResizeRef = react.useRef(null);
8587
+ const imageDragRef = react.useRef(null);
8489
8588
  react.useEffect(() => {
8490
8589
  const onMouseMove = (e) => {
8491
8590
  const drag = imageResizeRef.current;
@@ -8518,6 +8617,48 @@ function EditorCore({
8518
8617
  document.removeEventListener("mouseup", onMouseUp);
8519
8618
  };
8520
8619
  }, [engine]);
8620
+ react.useEffect(() => {
8621
+ const DRAG_THRESHOLD = 6;
8622
+ const onMouseMove = (e) => {
8623
+ const drag = imageDragRef.current;
8624
+ if (!drag) return;
8625
+ const dx = e.clientX - drag.startX;
8626
+ const dy = e.clientY - drag.startY;
8627
+ if (!drag.active && Math.sqrt(dx * dx + dy * dy) < DRAG_THRESHOLD) return;
8628
+ drag.active = true;
8629
+ const container = containerRef.current;
8630
+ const { dropIndex, indicatorClientY } = getImageDropTarget(container, e.clientY, drag.imagePath);
8631
+ setImageDragState({
8632
+ ghostX: e.clientX,
8633
+ ghostY: e.clientY,
8634
+ ghostW: drag.ghostW,
8635
+ ghostH: drag.ghostH,
8636
+ ghostSrc: drag.ghostSrc,
8637
+ dropIndicatorClientY: indicatorClientY,
8638
+ dropIndex,
8639
+ imagePath: drag.imagePath
8640
+ });
8641
+ };
8642
+ const onMouseUp = (e) => {
8643
+ const drag = imageDragRef.current;
8644
+ if (!drag) return;
8645
+ imageDragRef.current = null;
8646
+ if (!drag.active) {
8647
+ setImageDragState(null);
8648
+ return;
8649
+ }
8650
+ const container = containerRef.current;
8651
+ const { dropIndex } = getImageDropTarget(container, e.clientY, drag.imagePath);
8652
+ setImageDragState(null);
8653
+ moveImageInDoc(drag.imagePath, dropIndex, engine);
8654
+ };
8655
+ document.addEventListener("mousemove", onMouseMove);
8656
+ document.addEventListener("mouseup", onMouseUp);
8657
+ return () => {
8658
+ document.removeEventListener("mousemove", onMouseMove);
8659
+ document.removeEventListener("mouseup", onMouseUp);
8660
+ };
8661
+ }, [engine]);
8521
8662
  react.useEffect(() => {
8522
8663
  const container = containerRef.current;
8523
8664
  if (!container) return;
@@ -8635,6 +8776,19 @@ function EditorCore({
8635
8776
  ].join(" ")
8636
8777
  }
8637
8778
  ),
8779
+ imageDragState && (() => {
8780
+ const container = containerRef.current;
8781
+ if (!container) return null;
8782
+ const cr = container.getBoundingClientRect();
8783
+ const relY = imageDragState.dropIndicatorClientY - cr.top + container.scrollTop;
8784
+ return /* @__PURE__ */ jsxRuntime.jsx(
8785
+ "div",
8786
+ {
8787
+ className: "editor-image-drop-indicator",
8788
+ style: { top: relY }
8789
+ }
8790
+ );
8791
+ })(),
8638
8792
  linkTooltip && /* @__PURE__ */ jsxRuntime.jsxs(
8639
8793
  "div",
8640
8794
  {
@@ -8695,6 +8849,26 @@ function EditorCore({
8695
8849
  onClose: () => setSelectedImagePath(null)
8696
8850
  }
8697
8851
  ),
8852
+ imageDragState && /* @__PURE__ */ jsxRuntime.jsx(
8853
+ "div",
8854
+ {
8855
+ className: "editor-image-drag-ghost",
8856
+ style: {
8857
+ left: imageDragState.ghostX,
8858
+ top: imageDragState.ghostY,
8859
+ width: imageDragState.ghostW,
8860
+ height: imageDragState.ghostH
8861
+ },
8862
+ children: /* @__PURE__ */ jsxRuntime.jsx(
8863
+ "img",
8864
+ {
8865
+ src: imageDragState.ghostSrc,
8866
+ alt: "",
8867
+ style: { width: "100%", height: "100%", objectFit: "contain", display: "block" }
8868
+ }
8869
+ )
8870
+ }
8871
+ ),
8698
8872
  !readOnly && /* @__PURE__ */ jsxRuntime.jsx(
8699
8873
  "div",
8700
8874
  {
@@ -8717,6 +8891,46 @@ function EditorCore({
8717
8891
  }
8718
8892
  );
8719
8893
  }
8894
+ function getImageDropTarget(container, clientY, imagePath) {
8895
+ const fallback = { dropIndex: imagePath[0], indicatorClientY: clientY };
8896
+ if (!container) return fallback;
8897
+ const blocks = Array.from(
8898
+ container.querySelectorAll(":scope > [data-block-path]")
8899
+ );
8900
+ if (blocks.length === 0) return fallback;
8901
+ const gaps = [];
8902
+ gaps.push({ y: blocks[0].getBoundingClientRect().top, index: 0 });
8903
+ for (let i = 0; i < blocks.length - 1; i++) {
8904
+ const bottom = blocks[i].getBoundingClientRect().bottom;
8905
+ const top = blocks[i + 1].getBoundingClientRect().top;
8906
+ gaps.push({ y: (bottom + top) / 2, index: i + 1 });
8907
+ }
8908
+ gaps.push({ y: blocks[blocks.length - 1].getBoundingClientRect().bottom, index: blocks.length });
8909
+ let best = gaps[0];
8910
+ let bestDist = Math.abs(clientY - best.y);
8911
+ for (const gap of gaps) {
8912
+ const dist = Math.abs(clientY - gap.y);
8913
+ if (dist < bestDist) {
8914
+ bestDist = dist;
8915
+ best = gap;
8916
+ }
8917
+ }
8918
+ return { dropIndex: best.index, indicatorClientY: best.y };
8919
+ }
8920
+ function moveImageInDoc(imagePath, targetIndex, engine) {
8921
+ const state = engine.getState();
8922
+ const imageNode = getNodeAtPath(state.doc, imagePath);
8923
+ if (!imageNode) return;
8924
+ const oldIndex = imagePath[imagePath.length - 1];
8925
+ const parentPath = imagePath.slice(0, -1);
8926
+ let adj = targetIndex > oldIndex ? targetIndex - 1 : targetIndex;
8927
+ adj = Math.max(0, Math.min(state.doc.children.length - 1, adj));
8928
+ if (adj === oldIndex) return;
8929
+ const tr = createTransaction();
8930
+ tr.steps.push({ type: "delete_node", path: imagePath });
8931
+ tr.steps.push({ type: "insert_node", parentPath, index: adj, node: imageNode });
8932
+ engine.dispatch(tr);
8933
+ }
8720
8934
  var BLOCK_TAGS = "p|h[1-6]|ul|ol|li|blockquote|pre|figure|table|thead|tbody|tr|th|td";
8721
8935
  var _OPEN_ONLY_RE = new RegExp(`^<(${BLOCK_TAGS})(?:\\s[^>]*)?>$`, "i");
8722
8936
  var _CLOSE_ONLY_RE = new RegExp(`^<\\/(${BLOCK_TAGS})>$`, "i");
@@ -9068,7 +9282,9 @@ ${text}
9068
9282
  }
9069
9283
  }
9070
9284
  function serializeMDInline(children) {
9285
+ if (!children) return "";
9071
9286
  return children.map((child) => {
9287
+ if (!child) return "";
9072
9288
  if (!isTextNode(child)) return serializeMDBlock(child, 0);
9073
9289
  return serializeMDText(child);
9074
9290
  }).join("");