@underverse-ui/underverse 1.0.186 → 1.0.187

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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "package": "@underverse-ui/underverse",
3
- "version": "1.0.186",
3
+ "version": "1.0.187",
4
4
  "sourceEntry": "src/index.ts",
5
5
  "totalExports": 248,
6
6
  "exports": [
package/dist/index.cjs CHANGED
@@ -1010,7 +1010,8 @@ var en_default = {
1010
1010
  addBlock: "Add block"
1011
1011
  },
1012
1012
  linkInput: {
1013
- placeholder: "Paste or type a link..."
1013
+ placeholder: "Paste or type a link...",
1014
+ invalid: "Enter a valid link, such as https://example.com."
1014
1015
  },
1015
1016
  imageInput: {
1016
1017
  urlTab: "URL",
@@ -1437,7 +1438,8 @@ var vi_default = {
1437
1438
  addBlock: "Th\xEAm kh\u1ED1i"
1438
1439
  },
1439
1440
  linkInput: {
1440
- placeholder: "D\xE1n ho\u1EB7c nh\u1EADp li\xEAn k\u1EBFt..."
1441
+ placeholder: "D\xE1n ho\u1EB7c nh\u1EADp li\xEAn k\u1EBFt...",
1442
+ invalid: "Vui l\xF2ng nh\u1EADp li\xEAn k\u1EBFt h\u1EE3p l\u1EC7, v\xED d\u1EE5: https://example.com."
1441
1443
  },
1442
1444
  imageInput: {
1443
1445
  urlTab: "URL",
@@ -1864,7 +1866,8 @@ var ko_default = {
1864
1866
  addBlock: "\uBE14\uB85D \uCD94\uAC00"
1865
1867
  },
1866
1868
  linkInput: {
1867
- placeholder: "\uB9C1\uD06C \uBD99\uC5EC\uB123\uAE30 \uB610\uB294 \uC785\uB825..."
1869
+ placeholder: "\uB9C1\uD06C \uBD99\uC5EC\uB123\uAE30 \uB610\uB294 \uC785\uB825...",
1870
+ invalid: "https://example.com\uACFC \uAC19\uC740 \uC62C\uBC14\uB978 \uB9C1\uD06C\uB97C \uC785\uB825\uD574 \uC8FC\uC138\uC694."
1868
1871
  },
1869
1872
  imageInput: {
1870
1873
  urlTab: "URL",
@@ -2291,7 +2294,8 @@ var ja_default = {
2291
2294
  addBlock: "\u30D6\u30ED\u30C3\u30AF\u3092\u8FFD\u52A0"
2292
2295
  },
2293
2296
  linkInput: {
2294
- placeholder: "\u30EA\u30F3\u30AF\u3092\u8CBC\u308A\u4ED8\u3051\u307E\u305F\u306F\u5165\u529B..."
2297
+ placeholder: "\u30EA\u30F3\u30AF\u3092\u8CBC\u308A\u4ED8\u3051\u307E\u305F\u306F\u5165\u529B...",
2298
+ invalid: "https://example.com \u306E\u3088\u3046\u306A\u6709\u52B9\u306A\u30EA\u30F3\u30AF\u3092\u5165\u529B\u3057\u3066\u304F\u3060\u3055\u3044\u3002"
2295
2299
  },
2296
2300
  imageInput: {
2297
2301
  urlTab: "URL",
@@ -27745,6 +27749,32 @@ function isProtocolRelativeUrl(value) {
27745
27749
  function isRelativeUrl(value) {
27746
27750
  return value.startsWith("/") || value.startsWith("./") || value.startsWith("../") || value.startsWith("#");
27747
27751
  }
27752
+ function isValidIpv4Hostname(hostname) {
27753
+ const parts = hostname.split(".");
27754
+ return parts.length === 4 && parts.every((part) => /^\d{1,3}$/.test(part) && Number(part) <= 255);
27755
+ }
27756
+ function isValidWebHostname(hostname) {
27757
+ const normalized = hostname.toLowerCase();
27758
+ if (normalized === "localhost" || isValidIpv4Hostname(normalized)) return true;
27759
+ if (normalized.startsWith("[") && normalized.endsWith("]") && normalized.includes(":")) return true;
27760
+ const labels = normalized.split(".");
27761
+ if (labels.length < 2) return false;
27762
+ const validLabel = /^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)$/;
27763
+ return labels.every((label) => validLabel.test(label)) && /[a-z]/.test(labels.at(-1) ?? "");
27764
+ }
27765
+ function isValidLinkUrl(parsed) {
27766
+ if (parsed.protocol === "http:" || parsed.protocol === "https:") {
27767
+ return isValidWebHostname(parsed.hostname);
27768
+ }
27769
+ if (parsed.protocol === "mailto:") {
27770
+ return /^[^@]+@[^@]+\.[^@]+$/.test(decodeURIComponent(parsed.pathname));
27771
+ }
27772
+ if (parsed.protocol === "tel:") {
27773
+ const number = decodeURIComponent(parsed.pathname);
27774
+ return /^\+?[\d().-]+$/.test(number) && (number.match(/\d/g)?.length ?? 0) >= 3;
27775
+ }
27776
+ return false;
27777
+ }
27748
27778
  function isDataImageUrl(value) {
27749
27779
  return /^data:image\/(?:png|jpe?g|gif|webp|svg\+xml|bmp|x-icon|avif);base64,/i.test(value);
27750
27780
  }
@@ -27762,7 +27792,7 @@ function isSafeUEditorUrl(raw, kind) {
27762
27792
  const parsed = new URL(value);
27763
27793
  if (kind === "image") return IMAGE_PROTOCOLS.has(parsed.protocol);
27764
27794
  if (kind === "file") return FILE_PROTOCOLS.has(parsed.protocol);
27765
- return LINK_PROTOCOLS.has(parsed.protocol);
27795
+ return LINK_PROTOCOLS.has(parsed.protocol) && isValidLinkUrl(parsed);
27766
27796
  } catch {
27767
27797
  return false;
27768
27798
  }
@@ -32505,7 +32535,9 @@ var LinkInput = ({
32505
32535
  }) => {
32506
32536
  const t = useSmartTranslations("UEditor");
32507
32537
  const [url, setUrl] = (0, import_react69.useState)(initialUrl);
32538
+ const [error, setError] = (0, import_react69.useState)("");
32508
32539
  const inputRef = (0, import_react69.useRef)(null);
32540
+ const errorId = (0, import_react69.useId)();
32509
32541
  (0, import_react69.useEffect)(() => {
32510
32542
  inputRef.current?.focus();
32511
32543
  inputRef.current?.select();
@@ -32513,22 +32545,35 @@ var LinkInput = ({
32513
32545
  const handleSubmit = (e) => {
32514
32546
  e.preventDefault();
32515
32547
  const normalized = normalizeUrl(url);
32516
- if (normalized) onSubmit(normalized);
32548
+ if (!normalized) {
32549
+ setError(t("linkInput.invalid"));
32550
+ return;
32551
+ }
32552
+ setError("");
32553
+ onSubmit(normalized);
32517
32554
  };
32518
- return /* @__PURE__ */ (0, import_jsx_runtime90.jsxs)("form", { onSubmit: handleSubmit, className: "flex items-center gap-2 p-2", children: [
32519
- /* @__PURE__ */ (0, import_jsx_runtime90.jsx)(
32520
- "input",
32521
- {
32522
- ref: inputRef,
32523
- type: "text",
32524
- value: url,
32525
- onChange: (e) => setUrl(e.target.value),
32526
- placeholder: t("linkInput.placeholder"),
32527
- className: "flex-1 px-3 py-2 text-sm bg-muted/50 border-0 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary/20"
32528
- }
32529
- ),
32530
- /* @__PURE__ */ (0, import_jsx_runtime90.jsx)("button", { type: "submit", className: "p-2 rounded-lg bg-primary text-primary-foreground hover:bg-primary/90 transition-colors", children: /* @__PURE__ */ (0, import_jsx_runtime90.jsx)(import_lucide_react52.Check, { className: "w-4 h-4" }) }),
32531
- /* @__PURE__ */ (0, import_jsx_runtime90.jsx)("button", { type: "button", onClick: onCancel, className: "p-2 rounded-lg hover:bg-muted transition-colors text-muted-foreground", children: /* @__PURE__ */ (0, import_jsx_runtime90.jsx)(import_lucide_react52.X, { className: "w-4 h-4" }) })
32555
+ return /* @__PURE__ */ (0, import_jsx_runtime90.jsxs)("form", { onSubmit: handleSubmit, className: "p-2", children: [
32556
+ /* @__PURE__ */ (0, import_jsx_runtime90.jsxs)("div", { className: "flex items-center gap-2", children: [
32557
+ /* @__PURE__ */ (0, import_jsx_runtime90.jsx)(
32558
+ "input",
32559
+ {
32560
+ ref: inputRef,
32561
+ type: "text",
32562
+ value: url,
32563
+ onChange: (e) => {
32564
+ setUrl(e.target.value);
32565
+ if (error) setError("");
32566
+ },
32567
+ placeholder: t("linkInput.placeholder"),
32568
+ "aria-invalid": Boolean(error),
32569
+ "aria-describedby": error ? errorId : void 0,
32570
+ className: "flex-1 px-3 py-2 text-sm bg-muted/50 border-0 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary/20 aria-invalid:ring-2 aria-invalid:ring-destructive/40"
32571
+ }
32572
+ ),
32573
+ /* @__PURE__ */ (0, import_jsx_runtime90.jsx)("button", { type: "submit", className: "p-2 rounded-lg bg-primary text-primary-foreground hover:bg-primary/90 transition-colors", children: /* @__PURE__ */ (0, import_jsx_runtime90.jsx)(import_lucide_react52.Check, { className: "w-4 h-4" }) }),
32574
+ /* @__PURE__ */ (0, import_jsx_runtime90.jsx)("button", { type: "button", onClick: onCancel, className: "p-2 rounded-lg hover:bg-muted transition-colors text-muted-foreground", children: /* @__PURE__ */ (0, import_jsx_runtime90.jsx)(import_lucide_react52.X, { className: "w-4 h-4" }) })
32575
+ ] }),
32576
+ error ? /* @__PURE__ */ (0, import_jsx_runtime90.jsx)("p", { id: errorId, role: "alert", className: "mt-1.5 px-1 text-xs text-destructive", children: error }) : null
32532
32577
  ] });
32533
32578
  };
32534
32579
  var ImageInput = ({ onSubmit, onCancel }) => {
@@ -32589,8 +32634,28 @@ var ImageInput = ({ onSubmit, onCancel }) => {
32589
32634
  ] });
32590
32635
  };
32591
32636
 
32592
- // src/components/UEditor/table-cell-commands.ts
32637
+ // src/components/UEditor/link-commands.ts
32593
32638
  var import_state9 = require("@tiptap/pm/state");
32639
+ function applyEditorLink(editor, href) {
32640
+ const isEditingLink = editor.isActive("link");
32641
+ const hasSelectedText = !editor.state.selection.empty;
32642
+ const chain = editor.chain().focus();
32643
+ if (isEditingLink) {
32644
+ chain.extendMarkRange("link");
32645
+ }
32646
+ chain.setLink({ href });
32647
+ if (!hasSelectedText && !isEditingLink) {
32648
+ chain.insertContent(href);
32649
+ }
32650
+ return chain.command(({ tr }) => {
32651
+ tr.setSelection(import_state9.TextSelection.create(tr.doc, tr.selection.to));
32652
+ tr.removeStoredMark(editor.schema.marks.link);
32653
+ return true;
32654
+ }).run();
32655
+ }
32656
+
32657
+ // src/components/UEditor/table-cell-commands.ts
32658
+ var import_state10 = require("@tiptap/pm/state");
32594
32659
  var import_tables3 = require("@tiptap/pm/tables");
32595
32660
  function getCellSelectionPositions(selection) {
32596
32661
  const value = selection;
@@ -32624,7 +32689,7 @@ function getFocusableCellPos(editor, cellPos) {
32624
32689
  return node?.isTextblock ? offset + 1 : cellPos + 1;
32625
32690
  }
32626
32691
  function focusCell(editor, cellPos) {
32627
- const selection = import_state9.TextSelection.near(editor.state.doc.resolve(getFocusableCellPos(editor, cellPos)));
32692
+ const selection = import_state10.TextSelection.near(editor.state.doc.resolve(getFocusableCellPos(editor, cellPos)));
32628
32693
  editor.view.dispatch(editor.state.tr.setSelection(selection));
32629
32694
  editor.view.focus();
32630
32695
  }
@@ -33170,7 +33235,7 @@ var EditorToolbar = ({
33170
33235
  {
33171
33236
  initialUrl: String(editor.getAttributes("link").href ?? ""),
33172
33237
  onSubmit: (url) => {
33173
- editor.chain().focus().extendMarkRange("link").setLink({ href: url }).run();
33238
+ applyEditorLink(editor, url);
33174
33239
  setShowLinkInput(false);
33175
33240
  },
33176
33241
  onCancel: () => setShowLinkInput(false)
@@ -33407,7 +33472,7 @@ var EditorToolbar = ({
33407
33472
  {
33408
33473
  initialUrl: String(editor.getAttributes("link").href ?? ""),
33409
33474
  onSubmit: (url) => {
33410
- editor.chain().focus().extendMarkRange("link").setLink({ href: url }).run();
33475
+ applyEditorLink(editor, url);
33411
33476
  setShowLinkInput(false);
33412
33477
  },
33413
33478
  onCancel: () => setShowLinkInput(false)
@@ -34004,7 +34069,7 @@ var import_react_dom8 = require("react-dom");
34004
34069
  var import_lucide_react54 = require("lucide-react");
34005
34070
 
34006
34071
  // src/components/UEditor/table-formula-commands.ts
34007
- var import_state10 = require("@tiptap/pm/state");
34072
+ var import_state11 = require("@tiptap/pm/state");
34008
34073
  var import_tables5 = require("@tiptap/pm/tables");
34009
34074
 
34010
34075
  // src/components/UEditor/table-formula.ts
@@ -34620,7 +34685,7 @@ function restoreSelectionAtTableCell(editor, anchor) {
34620
34685
  if (!cellNode || cellNode.type.name !== "tableCell" && cellNode.type.name !== "tableHeader") return false;
34621
34686
  const selectionPos = Math.min(cellPos + 2, editor.state.doc.content.size);
34622
34687
  editor.view.dispatch(
34623
- editor.state.tr.setSelection(import_state10.TextSelection.near(editor.state.doc.resolve(selectionPos))).setMeta("addToHistory", false)
34688
+ editor.state.tr.setSelection(import_state11.TextSelection.near(editor.state.doc.resolve(selectionPos))).setMeta("addToHistory", false)
34624
34689
  );
34625
34690
  return true;
34626
34691
  }
@@ -34697,7 +34762,7 @@ function removeSelectedTableCellFormula(editor, clearContent) {
34697
34762
  );
34698
34763
  let transaction = editor.state.tr.replaceWith(selectedCell.cellPos, selectedCell.cellPos + selectedCell.node.nodeSize, nextCell2).setMeta(UEDITOR_TABLE_FORMULA_RECALCULATE_META, true);
34699
34764
  const selectionPos = Math.min(selectedCell.cellPos + 2, transaction.doc.content.size);
34700
- transaction = transaction.setSelection(import_state10.TextSelection.near(transaction.doc.resolve(selectionPos)));
34765
+ transaction = transaction.setSelection(import_state11.TextSelection.near(transaction.doc.resolve(selectionPos)));
34701
34766
  editor.view.dispatch(transaction);
34702
34767
  recalculateActiveTableFormulas(editor);
34703
34768
  restoreSelectionAtTableCell(editor, selectedCell);
@@ -35183,7 +35248,7 @@ var BubbleMenuContent = ({
35183
35248
  {
35184
35249
  initialUrl: editor.getAttributes("link").href || "",
35185
35250
  onSubmit: (url) => {
35186
- editor.chain().focus().extendMarkRange("link").setLink({ href: url }).run();
35251
+ applyEditorLink(editor, url);
35187
35252
  setShowLinkInput(false);
35188
35253
  requestAnimationFrame(() => editor.commands.focus());
35189
35254
  },
@@ -39040,7 +39105,7 @@ var Selection = class {
39040
39105
  found.
39041
39106
  */
39042
39107
  static findFrom($pos, dir, textOnly = false) {
39043
- let inner = $pos.parent.inlineContent ? new TextSelection4($pos) : findSelectionIn($pos.node(0), $pos.parent, $pos.pos, $pos.index(), dir, textOnly);
39108
+ let inner = $pos.parent.inlineContent ? new TextSelection5($pos) : findSelectionIn($pos.node(0), $pos.parent, $pos.pos, $pos.index(), dir, textOnly);
39044
39109
  if (inner)
39045
39110
  return inner;
39046
39111
  for (let depth = $pos.depth - 1; depth >= 0; depth--) {
@@ -39109,7 +39174,7 @@ var Selection = class {
39109
39174
  returns the bookmark for that.
39110
39175
  */
39111
39176
  getBookmark() {
39112
- return TextSelection4.between(this.$anchor, this.$head).getBookmark();
39177
+ return TextSelection5.between(this.$anchor, this.$head).getBookmark();
39113
39178
  }
39114
39179
  };
39115
39180
  Selection.prototype.visible = true;
@@ -39129,7 +39194,7 @@ function checkTextSelection($pos) {
39129
39194
  console["warn"]("TextSelection endpoint not pointing into a node with inline content (" + $pos.parent.type.name + ")");
39130
39195
  }
39131
39196
  }
39132
- var TextSelection4 = class _TextSelection extends Selection {
39197
+ var TextSelection5 = class _TextSelection extends Selection {
39133
39198
  /**
39134
39199
  Construct a text selection between the given points.
39135
39200
  */
@@ -39215,7 +39280,7 @@ var TextSelection4 = class _TextSelection extends Selection {
39215
39280
  return new _TextSelection($anchor, $head);
39216
39281
  }
39217
39282
  };
39218
- Selection.jsonID("text", TextSelection4);
39283
+ Selection.jsonID("text", TextSelection5);
39219
39284
  var TextBookmark = class _TextBookmark {
39220
39285
  constructor(anchor, head) {
39221
39286
  this.anchor = anchor;
@@ -39225,7 +39290,7 @@ var TextBookmark = class _TextBookmark {
39225
39290
  return new _TextBookmark(mapping.map(this.anchor), mapping.map(this.head));
39226
39291
  }
39227
39292
  resolve(doc) {
39228
- return TextSelection4.between(doc.resolve(this.anchor), doc.resolve(this.head));
39293
+ return TextSelection5.between(doc.resolve(this.anchor), doc.resolve(this.head));
39229
39294
  }
39230
39295
  };
39231
39296
  var NodeSelection2 = class _NodeSelection extends Selection {
@@ -39344,7 +39409,7 @@ var AllBookmark = {
39344
39409
  };
39345
39410
  function findSelectionIn(doc, node, pos, index, dir, text = false) {
39346
39411
  if (node.inlineContent)
39347
- return TextSelection4.create(doc, pos);
39412
+ return TextSelection5.create(doc, pos);
39348
39413
  for (let i = index - (dir > 0 ? 0 : 1); dir > 0 ? i < node.childCount : i >= 0; i += dir) {
39349
39414
  let child = node.child(i);
39350
39415
  if (!child.isAtom) {
@@ -39936,7 +40001,7 @@ var CellSelection = class CellSelection2 extends Selection {
39936
40001
  else if (tableChanged && this.isColSelection()) return CellSelection2.colSelection($anchorCell, $headCell);
39937
40002
  else return new CellSelection2($anchorCell, $headCell);
39938
40003
  }
39939
- return TextSelection4.between($anchorCell, $headCell);
40004
+ return TextSelection5.between($anchorCell, $headCell);
39940
40005
  }
39941
40006
  content() {
39942
40007
  const table = this.$anchorCell.node(-1);
@@ -40551,7 +40616,7 @@ function shiftArrow(axis, dir) {
40551
40616
  };
40552
40617
  }
40553
40618
  function atEndOfCell(view, axis, dir) {
40554
- if (!(view.state.selection instanceof TextSelection4)) return null;
40619
+ if (!(view.state.selection instanceof TextSelection5)) return null;
40555
40620
  const { $head } = view.state.selection;
40556
40621
  for (let d = $head.depth - 1; d >= 0; d--) {
40557
40622
  const parent = $head.node(d);
@@ -42442,7 +42507,7 @@ var import_react77 = require("react");
42442
42507
  var import_tables8 = require("@tiptap/pm/tables");
42443
42508
 
42444
42509
  // src/components/UEditor/table-formula-range-picker.ts
42445
- var import_state11 = require("@tiptap/pm/state");
42510
+ var import_state12 = require("@tiptap/pm/state");
42446
42511
  var import_tables7 = require("@tiptap/pm/tables");
42447
42512
  function getCellText2(cellNode) {
42448
42513
  return cellNode.textBetween(0, cellNode.content.size, "\n").trim();
@@ -42520,7 +42585,7 @@ function cancelFormulaEditing(view) {
42520
42585
  if (!context) return false;
42521
42586
  let tr = view.state.tr.delete(context.cellContentStart, context.cellContentEnd);
42522
42587
  const selectionPos = Math.min(context.cellContentStart, tr.doc.content.size);
42523
- tr = tr.setSelection(import_state11.TextSelection.near(tr.doc.resolve(selectionPos)));
42588
+ tr = tr.setSelection(import_state12.TextSelection.near(tr.doc.resolve(selectionPos)));
42524
42589
  view.dispatch(tr);
42525
42590
  view.focus();
42526
42591
  return true;
@@ -42597,7 +42662,7 @@ function replacePickedLabel(view, pickState, nextLabel, currentCell) {
42597
42662
  }
42598
42663
  let tr = view.state.tr.insertText(nextLabel, pickState.insertedFrom, pickState.insertedTo);
42599
42664
  const nextTo = pickState.insertedFrom + nextLabel.length;
42600
- tr = tr.setSelection(import_state11.TextSelection.create(tr.doc, nextTo));
42665
+ tr = tr.setSelection(import_state12.TextSelection.create(tr.doc, nextTo));
42601
42666
  view.dispatch(tr);
42602
42667
  return {
42603
42668
  ...pickState,
@@ -42634,7 +42699,7 @@ function beginFormulaRangePick(view, event) {
42634
42699
  const insertedText = `${prefix}${picked.label}`;
42635
42700
  const insertedFrom = from + prefix.length;
42636
42701
  let tr = view.state.tr.insertText(insertedText, from, to);
42637
- tr = tr.setSelection(import_state11.TextSelection.create(tr.doc, from + insertedText.length));
42702
+ tr = tr.setSelection(import_state12.TextSelection.create(tr.doc, from + insertedText.length));
42638
42703
  view.dispatch(tr);
42639
42704
  view.focus();
42640
42705
  event.preventDefault();
@@ -43571,7 +43636,7 @@ var MenuBar = ({
43571
43636
  closeInsertMenu();
43572
43637
  };
43573
43638
  const handleLinkSubmit = (url) => {
43574
- editor.chain().focus().setLink({ href: url }).run();
43639
+ applyEditorLink(editor, url);
43575
43640
  closeInsertMenu();
43576
43641
  };
43577
43642
  const insertMenuItems = [