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