@hectasquare/medusa-plugin-category-tree 1.0.11 → 1.0.12

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.
@@ -126,12 +126,15 @@ const CategoryProductsManager = ({ categoryId, categoryName, initialMetadata, on
126
126
  }
127
127
  };
128
128
  // Sibling product ranking handlers (Move Up / Move Down)
129
- const handleMoveProduct = async (index, direction) => {
130
- const targetIndex = direction === "up" ? index - 1 : index + 1;
129
+ const handleMoveProduct = async (productId, direction) => {
130
+ const currentIndex = products.findIndex((p) => p.id === productId);
131
+ if (currentIndex === -1)
132
+ return;
133
+ const targetIndex = direction === "up" ? currentIndex - 1 : currentIndex + 1;
131
134
  if (targetIndex < 0 || targetIndex >= products.length)
132
135
  return;
133
136
  const reordered = [...products];
134
- const [moved] = reordered.splice(index, 1);
137
+ const [moved] = reordered.splice(currentIndex, 1);
135
138
  reordered.splice(targetIndex, 0, moved);
136
139
  setProducts(reordered);
137
140
  const newOrder = reordered.map((p) => p.id);
@@ -294,11 +297,11 @@ const CategoryProductsManager = ({ categoryId, categoryName, initialMetadata, on
294
297
  }, className: "rounded border-ui-border-base cursor-pointer" }) }), (0, jsx_runtime_1.jsx)("td", { className: "py-2.5 px-3", children: (0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-1 font-mono", children: [(0, jsx_runtime_1.jsxs)("span", { className: "text-xs font-bold text-ui-fg-subtle px-1.5 py-0.5 rounded bg-ui-bg-subtle border border-ui-border-base", children: ["#", index + 1] }), (0, jsx_runtime_1.jsxs)("div", { className: "flex flex-col", children: [(0, jsx_runtime_1.jsx)("button", { type: "button", title: "Move Up", disabled: index === 0, onClick: (e) => {
295
298
  e.preventDefault();
296
299
  e.stopPropagation();
297
- handleMoveProduct(index, "up");
300
+ handleMoveProduct(p.id, "up");
298
301
  }, className: "text-[9px] text-ui-fg-muted hover:text-ui-fg-base disabled:opacity-20 px-0.5 leading-none cursor-pointer", children: "\u25B2" }), (0, jsx_runtime_1.jsx)("button", { type: "button", title: "Move Down", disabled: index === displayedProducts.length - 1, onClick: (e) => {
299
302
  e.preventDefault();
300
303
  e.stopPropagation();
301
- handleMoveProduct(index, "down");
304
+ handleMoveProduct(p.id, "down");
302
305
  }, className: "text-[9px] text-ui-fg-muted hover:text-ui-fg-base disabled:opacity-20 px-0.5 leading-none cursor-pointer", children: "\u25BC" })] })] }) }), (0, jsx_runtime_1.jsx)("td", { className: "py-2.5 px-3", children: (0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-2.5 min-w-0", children: [(0, jsx_runtime_1.jsx)("a", { href: `/app/products/${p.id}`, target: "_blank", rel: "noopener noreferrer", className: "w-9 h-9 rounded-md border border-ui-border-base overflow-hidden bg-ui-bg-subtle flex-shrink-0 flex items-center justify-center hover:opacity-80 transition-opacity", title: `Open ${p.title} in new tab`, children: p.thumbnail ? ((0, jsx_runtime_1.jsx)("img", { src: p.thumbnail, alt: p.title, className: "w-full h-full object-cover" })) : ((0, jsx_runtime_1.jsx)("span", { className: "text-[9px] text-ui-fg-muted", children: "IMG" })) }), (0, jsx_runtime_1.jsx)("div", { className: "flex flex-col min-w-0", children: (0, jsx_runtime_1.jsxs)("a", { href: `/app/products/${p.id}`, target: "_blank", rel: "noopener noreferrer", className: "font-semibold text-ui-fg-base hover:text-ui-fg-interactive truncate leading-snug hover:underline flex items-center gap-1 group/pname", title: `Open ${p.title} in new tab`, children: [(0, jsx_runtime_1.jsx)("span", { className: "truncate", children: p.title }), (0, jsx_runtime_1.jsx)(icons_1.ArrowUpRightMicro, { className: "w-3.5 h-3.5 text-ui-fg-muted group-hover/pname:text-ui-fg-interactive transition-colors shrink-0" })] }) })] }) }), (0, jsx_runtime_1.jsx)("td", { className: "py-2.5 px-3", children: (0, jsx_runtime_1.jsxs)("button", { type: "button", onClick: (e) => {
303
306
  e.preventDefault();
304
307
  e.stopPropagation();
@@ -12,17 +12,17 @@ function extractYouTubeVideoId(url) {
12
12
  return null;
13
13
  const trimmed = url.trim();
14
14
  const patterns = [
15
- /(?:youtube\.com\/shorts\/)([^?&\s]+)/i,
16
- /(?:youtube\.com\/watch\?.*v=)([^?&\s]+)/i,
17
- /(?:youtu\.be\/)([^?&\s]+)/i,
18
- /(?:youtube\.com\/embed\/)([^?&\s]+)/i,
15
+ /(?:youtube\.com\/shorts\/)([a-zA-Z0-9_-]{11})/i,
16
+ /(?:youtube\.com\/watch\?.*v=)([a-zA-Z0-9_-]{11})/i,
17
+ /(?:youtu\.be\/)([a-zA-Z0-9_-]{11})/i,
18
+ /(?:youtube\.com\/embed\/)([a-zA-Z0-9_-]{11})/i,
19
19
  ];
20
20
  for (const pattern of patterns) {
21
21
  const match = trimmed.match(pattern);
22
22
  if (match && match[1])
23
23
  return match[1];
24
24
  }
25
- if (/^[a-zA-Z0-9_-]{11}$/.test(trimmed))
25
+ if (/^[a-zA-Z0-9_-]{11}$/.test(trimmed) && !/^[A-Za-z]{11}$/.test(trimmed))
26
26
  return trimmed;
27
27
  return null;
28
28
  }
@@ -58,15 +58,21 @@ const EditCategoryDrawer = ({ open, onOpenChange, category, salesChannels, onSuc
58
58
  setIsInternal(Boolean(category.is_internal));
59
59
  setSelectedChannels(category.sales_channels || salesChannels.map((s) => s.id));
60
60
  setCurrentMetadata(category.metadata || {});
61
- // Parse YouTube URLs from metadata
61
+ // Parse YouTube URLs from metadata (including legacy fields)
62
62
  const meta = category.metadata;
63
63
  const urls = [];
64
64
  if (meta?.youtube_urls && Array.isArray(meta.youtube_urls)) {
65
65
  meta.youtube_urls.forEach((u) => typeof u === "string" && u.trim() && urls.push(u.trim()));
66
66
  }
67
- else if (meta?.youtube_url && typeof meta.youtube_url === "string") {
67
+ else if (meta?.youtube_url && typeof meta.youtube_url === "string" && meta.youtube_url.trim()) {
68
68
  urls.push(meta.youtube_url.trim());
69
69
  }
70
+ else if (meta?.youtube_link && typeof meta.youtube_link === "string" && meta.youtube_link.trim()) {
71
+ urls.push(meta.youtube_link.trim());
72
+ }
73
+ else if (meta?.video_url && typeof meta.video_url === "string" && meta.video_url.trim()) {
74
+ urls.push(meta.video_url.trim());
75
+ }
70
76
  setYoutubeUrls(urls);
71
77
  setActiveTab("general");
72
78
  // Load category media from backend
@@ -313,6 +319,8 @@ const EditCategoryDrawer = ({ open, onOpenChange, category, salesChannels, onSuc
313
319
  ...(currentMetadata || {}),
314
320
  youtube_urls: nextUrls,
315
321
  youtube_url: nextUrls[0] || null,
322
+ youtube_link: nextUrls[0] || null,
323
+ video_url: nextUrls[0] || null,
316
324
  };
317
325
  setCurrentMetadata(updatedMeta);
318
326
  try {
@@ -336,6 +344,8 @@ const EditCategoryDrawer = ({ open, onOpenChange, category, salesChannels, onSuc
336
344
  ...(currentMetadata || {}),
337
345
  youtube_urls: nextUrls,
338
346
  youtube_url: nextUrls[0] || null,
347
+ youtube_link: nextUrls[0] || null,
348
+ video_url: nextUrls[0] || null,
339
349
  };
340
350
  setCurrentMetadata(updatedMeta);
341
351
  try {
@@ -372,6 +382,8 @@ const EditCategoryDrawer = ({ open, onOpenChange, category, salesChannels, onSuc
372
382
  sales_channels: selectedChannels,
373
383
  youtube_urls: youtubeUrls,
374
384
  youtube_url: youtubeUrls[0] || null,
385
+ youtube_link: youtubeUrls[0] || null,
386
+ video_url: youtubeUrls[0] || null,
375
387
  images: categoryImages,
376
388
  thumbnail: thumbImg ? thumbImg.url : null,
377
389
  image_url: thumbImg ? thumbImg.url : null,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hectasquare/medusa-plugin-category-tree",
3
- "version": "1.0.11",
3
+ "version": "1.0.12",
4
4
  "description": "AAA Interactive Category Tree & Mind Map for Medusa v2 Admin with drag-and-drop reparenting, tier indicators, superscript ranks, multi-storefront filtering, and NotebookLM-style subtree collapse.",
5
5
  "author": "HectaSquare Team",
6
6
  "license": "Apache-2.0",
@@ -134,12 +134,15 @@ export const CategoryProductsManager: React.FC<CategoryProductsManagerProps> = (
134
134
  }
135
135
 
136
136
  // Sibling product ranking handlers (Move Up / Move Down)
137
- const handleMoveProduct = async (index: number, direction: "up" | "down") => {
138
- const targetIndex = direction === "up" ? index - 1 : index + 1
137
+ const handleMoveProduct = async (productId: string, direction: "up" | "down") => {
138
+ const currentIndex = products.findIndex((p) => p.id === productId)
139
+ if (currentIndex === -1) return
140
+
141
+ const targetIndex = direction === "up" ? currentIndex - 1 : currentIndex + 1
139
142
  if (targetIndex < 0 || targetIndex >= products.length) return
140
143
 
141
144
  const reordered = [...products]
142
- const [moved] = reordered.splice(index, 1)
145
+ const [moved] = reordered.splice(currentIndex, 1)
143
146
  reordered.splice(targetIndex, 0, moved)
144
147
 
145
148
  setProducts(reordered)
@@ -436,7 +439,7 @@ export const CategoryProductsManager: React.FC<CategoryProductsManagerProps> = (
436
439
  onClick={(e) => {
437
440
  e.preventDefault()
438
441
  e.stopPropagation()
439
- handleMoveProduct(index, "up")
442
+ handleMoveProduct(p.id, "up")
440
443
  }}
441
444
  className="text-[9px] text-ui-fg-muted hover:text-ui-fg-base disabled:opacity-20 px-0.5 leading-none cursor-pointer"
442
445
  >
@@ -449,7 +452,7 @@ export const CategoryProductsManager: React.FC<CategoryProductsManagerProps> = (
449
452
  onClick={(e) => {
450
453
  e.preventDefault()
451
454
  e.stopPropagation()
452
- handleMoveProduct(index, "down")
455
+ handleMoveProduct(p.id, "down")
453
456
  }}
454
457
  className="text-[9px] text-ui-fg-muted hover:text-ui-fg-base disabled:opacity-20 px-0.5 leading-none cursor-pointer"
455
458
  >
@@ -27,16 +27,16 @@ function extractYouTubeVideoId(url: string): string | null {
27
27
  if (!url || typeof url !== "string") return null
28
28
  const trimmed = url.trim()
29
29
  const patterns = [
30
- /(?:youtube\.com\/shorts\/)([^?&\s]+)/i,
31
- /(?:youtube\.com\/watch\?.*v=)([^?&\s]+)/i,
32
- /(?:youtu\.be\/)([^?&\s]+)/i,
33
- /(?:youtube\.com\/embed\/)([^?&\s]+)/i,
30
+ /(?:youtube\.com\/shorts\/)([a-zA-Z0-9_-]{11})/i,
31
+ /(?:youtube\.com\/watch\?.*v=)([a-zA-Z0-9_-]{11})/i,
32
+ /(?:youtu\.be\/)([a-zA-Z0-9_-]{11})/i,
33
+ /(?:youtube\.com\/embed\/)([a-zA-Z0-9_-]{11})/i,
34
34
  ]
35
35
  for (const pattern of patterns) {
36
36
  const match = trimmed.match(pattern)
37
37
  if (match && match[1]) return match[1]
38
38
  }
39
- if (/^[a-zA-Z0-9_-]{11}$/.test(trimmed)) return trimmed
39
+ if (/^[a-zA-Z0-9_-]{11}$/.test(trimmed) && !/^[A-Za-z]{11}$/.test(trimmed)) return trimmed
40
40
  return null
41
41
  }
42
42
 
@@ -84,13 +84,17 @@ export const EditCategoryDrawer: React.FC<EditCategoryDrawerProps> = ({
84
84
  setSelectedChannels(category.sales_channels || salesChannels.map((s) => s.id))
85
85
  setCurrentMetadata((category.metadata as Record<string, any>) || {})
86
86
 
87
- // Parse YouTube URLs from metadata
87
+ // Parse YouTube URLs from metadata (including legacy fields)
88
88
  const meta = category.metadata as Record<string, any> | null
89
89
  const urls: string[] = []
90
90
  if (meta?.youtube_urls && Array.isArray(meta.youtube_urls)) {
91
91
  meta.youtube_urls.forEach((u) => typeof u === "string" && u.trim() && urls.push(u.trim()))
92
- } else if (meta?.youtube_url && typeof meta.youtube_url === "string") {
92
+ } else if (meta?.youtube_url && typeof meta.youtube_url === "string" && meta.youtube_url.trim()) {
93
93
  urls.push(meta.youtube_url.trim())
94
+ } else if (meta?.youtube_link && typeof meta.youtube_link === "string" && meta.youtube_link.trim()) {
95
+ urls.push(meta.youtube_link.trim())
96
+ } else if (meta?.video_url && typeof meta.video_url === "string" && meta.video_url.trim()) {
97
+ urls.push(meta.video_url.trim())
94
98
  }
95
99
  setYoutubeUrls(urls)
96
100
  setActiveTab("general")
@@ -362,6 +366,8 @@ export const EditCategoryDrawer: React.FC<EditCategoryDrawerProps> = ({
362
366
  ...(currentMetadata || {}),
363
367
  youtube_urls: nextUrls,
364
368
  youtube_url: nextUrls[0] || null,
369
+ youtube_link: nextUrls[0] || null,
370
+ video_url: nextUrls[0] || null,
365
371
  }
366
372
  setCurrentMetadata(updatedMeta)
367
373
 
@@ -386,6 +392,8 @@ export const EditCategoryDrawer: React.FC<EditCategoryDrawerProps> = ({
386
392
  ...(currentMetadata || {}),
387
393
  youtube_urls: nextUrls,
388
394
  youtube_url: nextUrls[0] || null,
395
+ youtube_link: nextUrls[0] || null,
396
+ video_url: nextUrls[0] || null,
389
397
  }
390
398
  setCurrentMetadata(updatedMeta)
391
399
 
@@ -426,6 +434,8 @@ export const EditCategoryDrawer: React.FC<EditCategoryDrawerProps> = ({
426
434
  sales_channels: selectedChannels,
427
435
  youtube_urls: youtubeUrls,
428
436
  youtube_url: youtubeUrls[0] || null,
437
+ youtube_link: youtubeUrls[0] || null,
438
+ video_url: youtubeUrls[0] || null,
429
439
  images: categoryImages,
430
440
  thumbnail: thumbImg ? thumbImg.url : null,
431
441
  image_url: thumbImg ? thumbImg.url : null,