@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.
- package/.medusa/server/src/admin/components/category-tree/category-products-manager.js +8 -5
- package/.medusa/server/src/admin/components/category-tree/edit-category-drawer.js +19 -7
- package/package.json +1 -1
- package/src/admin/components/category-tree/category-products-manager.tsx +8 -5
- package/src/admin/components/category-tree/edit-category-drawer.tsx +17 -7
|
@@ -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 (
|
|
130
|
-
const
|
|
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(
|
|
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(
|
|
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(
|
|
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\/)([
|
|
16
|
-
/(?:youtube\.com\/watch\?.*v=)([
|
|
17
|
-
/(?:youtu\.be\/)([
|
|
18
|
-
/(?:youtube\.com\/embed\/)([
|
|
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.
|
|
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 (
|
|
138
|
-
const
|
|
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(
|
|
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(
|
|
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(
|
|
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\/)([
|
|
31
|
-
/(?:youtube\.com\/watch\?.*v=)([
|
|
32
|
-
/(?:youtu\.be\/)([
|
|
33
|
-
/(?:youtube\.com\/embed\/)([
|
|
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,
|