@rxdrag/website-lib-core 0.0.11 → 0.0.13
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/package.json +4 -4
- package/src/component-logic/modal.ts +11 -2
- package/src/controller/OpenableController.ts +5 -0
- package/src/controller/PageLoader.ts +1 -1
- package/src/entify/Entify.ts +56 -0
- package/src/entify/IEntify.ts +43 -3
- package/src/entify/lib/langFields.ts +2 -0
- package/src/entify/lib/newPageMetaOptions.ts +4 -6
- package/src/entify/lib/newQueryPageOptions.ts +14 -0
- package/src/entify/lib/queryAllProducts.ts +5 -0
- package/src/entify/lib/queryFeaturedProducts.ts +5 -0
- package/src/entify/lib/queryLangs.ts +8 -21
- package/src/entify/lib/queryLatestPosts.ts +6 -1
- package/src/entify/lib/queryOnePostBySlug.ts +27 -10
- package/src/entify/lib/queryOnePostCategoryBySlug.ts +5 -0
- package/src/entify/lib/queryOneProductBySlug.ts +13 -3
- package/src/entify/lib/queryOneProductCategoryBySlug.ts +24 -5
- package/src/entify/lib/queryOneTheme.ts +2 -1
- package/src/entify/lib/queryPageBySlug.ts +43 -0
- package/src/entify/lib/queryPageByType.ts +44 -0
- package/src/entify/lib/queryPostCategories.ts +7 -0
- package/src/entify/lib/queryPostSlugs.ts +5 -0
- package/src/entify/lib/queryPosts.ts +56 -39
- package/src/entify/lib/queryProductCategories.ts +7 -0
- package/src/entify/lib/queryProducts.ts +5 -0
- package/src/entify/lib/queryProductsInMenu.ts +14 -12
- package/src/entify/lib/queryUserPosts.ts +5 -0
- package/src/entify/lib/queryWebSiteSettings.ts +2 -3
- package/src/entify/lib/queryWebsite.ts +43 -0
- package/src/entify/lib/searchProducts.ts +7 -0
- package/src/entify/view-model/models.ts +0 -8
- package/src/react/components/ContactForm/index.tsx +6 -29
- package/src/react/components/Medias/index.tsx +270 -273
- package/src/react/components/ProductCard/ProductCta/index.tsx +1 -1
- package/src/react/components/RichTextOutline/index.tsx +4 -5
- package/src/react/components/RichTextOutline/useAcitviedHeading.ts +81 -54
|
@@ -8,146 +8,227 @@ export type MediasProps = {
|
|
|
8
8
|
children?: React.ReactNode;
|
|
9
9
|
// Aspect ratio, format is `aspect-[width/height]`
|
|
10
10
|
aspect?: string;
|
|
11
|
+
thumbnailAspect?: string;
|
|
11
12
|
};
|
|
12
13
|
|
|
13
|
-
export const Medias = forwardRef<HTMLDivElement, MediasProps>(
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
14
|
+
export const Medias = forwardRef<HTMLDivElement, MediasProps>((props, ref) => {
|
|
15
|
+
const {
|
|
16
|
+
value,
|
|
17
|
+
className,
|
|
18
|
+
children,
|
|
19
|
+
aspect = "aspect-[1/1]",
|
|
20
|
+
thumbnailAspect = "aspect-[5/4]",
|
|
21
|
+
...rest
|
|
22
|
+
} = props;
|
|
23
|
+
const [selectedId, setSelectedId] = useState<string | undefined | null>(
|
|
24
|
+
value?.externalVideoUrl ? "video" : value?.medias?.[0]?.id || ""
|
|
25
|
+
);
|
|
26
|
+
const [videoUrl, setVideoUrl] = useState<string>("");
|
|
27
|
+
const [thumbnailUrl, setThumbnailUrl] = useState<string>("");
|
|
28
|
+
|
|
29
|
+
useEffect(() => {
|
|
30
|
+
setSelectedId(
|
|
23
31
|
value?.externalVideoUrl ? "video" : value?.medias?.[0]?.id || ""
|
|
24
32
|
);
|
|
25
|
-
|
|
26
|
-
const [thumbnailUrl, setThumbnailUrl] = useState<string>("");
|
|
33
|
+
}, [value]);
|
|
27
34
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
35
|
+
useEffect(() => {
|
|
36
|
+
if (value?.externalVideoUrl) {
|
|
37
|
+
const baseUrl = value.externalVideoUrl.replace(
|
|
38
|
+
"https://youtu.be/",
|
|
39
|
+
"https://www.youtube.com/embed/"
|
|
31
40
|
);
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
const separator = baseUrl.includes("?") ? "&" : "?";
|
|
41
|
-
setVideoUrl(
|
|
42
|
-
`${baseUrl}${separator}autoplay=1&muted=1&modestbranding=1&rel=0&controls=1&playsinline=1&enablejsapi=1&origin=${encodeURIComponent(
|
|
43
|
-
window.location.origin
|
|
44
|
-
)}`
|
|
45
|
-
);
|
|
46
|
-
}
|
|
47
|
-
}, [value?.externalVideoUrl]);
|
|
41
|
+
const separator = baseUrl.includes("?") ? "&" : "?";
|
|
42
|
+
setVideoUrl(
|
|
43
|
+
`${baseUrl}${separator}autoplay=1&muted=1&modestbranding=1&rel=0&controls=1&playsinline=1&enablejsapi=1&origin=${encodeURIComponent(
|
|
44
|
+
window.location.origin
|
|
45
|
+
)}`
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
}, [value?.externalVideoUrl]);
|
|
48
49
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
50
|
+
useEffect(() => {
|
|
51
|
+
if (value?.externalVideoUrl) {
|
|
52
|
+
const videoId = value.externalVideoUrl.includes("youtu.be/")
|
|
53
|
+
? value.externalVideoUrl.split("youtu.be/")[1].split("?")[0]
|
|
54
|
+
: value.externalVideoUrl.split("v=")[1]?.split("&")[0];
|
|
54
55
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
`https://img.youtube.com/vi/${videoId}/hqdefault.jpg`
|
|
58
|
-
);
|
|
59
|
-
}
|
|
56
|
+
if (videoId) {
|
|
57
|
+
setThumbnailUrl(`https://img.youtube.com/vi/${videoId}/hqdefault.jpg`);
|
|
60
58
|
}
|
|
61
|
-
}
|
|
59
|
+
}
|
|
60
|
+
}, [value?.externalVideoUrl]);
|
|
62
61
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
: value?.medias?.findIndex((media) => media.id === selectedId) || 0;
|
|
62
|
+
const selectedIndex = value?.externalVideoUrl
|
|
63
|
+
? selectedId === "video"
|
|
64
|
+
? 0
|
|
65
|
+
: (value?.medias?.findIndex((media) => media.id === selectedId) || 0) + 1
|
|
66
|
+
: value?.medias?.findIndex((media) => media.id === selectedId) || 0;
|
|
69
67
|
|
|
70
|
-
|
|
71
|
-
|
|
68
|
+
const totalItems =
|
|
69
|
+
(value?.externalVideoUrl ? 1 : 0) + (value?.medias?.length || 0);
|
|
72
70
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
71
|
+
// Calculate visible thumbnails (show 6 items)
|
|
72
|
+
const visibleCount = 6;
|
|
73
|
+
const halfVisible = Math.floor(visibleCount / 2);
|
|
74
|
+
const startIndex = Math.max(
|
|
75
|
+
0,
|
|
76
|
+
Math.min(selectedIndex - halfVisible, totalItems - visibleCount)
|
|
77
|
+
);
|
|
78
|
+
const endIndex = Math.min(startIndex + visibleCount, totalItems);
|
|
81
79
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
} else {
|
|
88
|
-
const prevIndex = selectedIndex - 2;
|
|
89
|
-
setSelectedId(value.medias?.[prevIndex]?.id || "");
|
|
90
|
-
}
|
|
80
|
+
const handlePrevious = useCallback(() => {
|
|
81
|
+
if (selectedIndex > 0) {
|
|
82
|
+
if (value?.externalVideoUrl) {
|
|
83
|
+
if (selectedIndex === 1) {
|
|
84
|
+
setSelectedId("video");
|
|
91
85
|
} else {
|
|
92
|
-
const prevIndex = selectedIndex -
|
|
93
|
-
setSelectedId(value
|
|
86
|
+
const prevIndex = selectedIndex - 2;
|
|
87
|
+
setSelectedId(value.medias?.[prevIndex]?.id || "");
|
|
94
88
|
}
|
|
89
|
+
} else {
|
|
90
|
+
const prevIndex = selectedIndex - 1;
|
|
91
|
+
setSelectedId(value?.medias?.[prevIndex]?.id || "");
|
|
95
92
|
}
|
|
96
|
-
}
|
|
93
|
+
}
|
|
94
|
+
}, [selectedIndex, value]);
|
|
97
95
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
} else {
|
|
104
|
-
const currentMediaIndex =
|
|
105
|
-
value.medias?.findIndex((media) => media.id === selectedId) || 0;
|
|
106
|
-
const nextIndex = currentMediaIndex + 1;
|
|
107
|
-
setSelectedId(value.medias?.[nextIndex]?.id || "");
|
|
108
|
-
}
|
|
96
|
+
const handleNext = useCallback(() => {
|
|
97
|
+
if (selectedIndex < totalItems - 1) {
|
|
98
|
+
if (value?.externalVideoUrl) {
|
|
99
|
+
if (selectedId === "video") {
|
|
100
|
+
setSelectedId(value.medias?.[0]?.id || "");
|
|
109
101
|
} else {
|
|
110
102
|
const currentMediaIndex =
|
|
111
|
-
value
|
|
103
|
+
value.medias?.findIndex((media) => media.id === selectedId) || 0;
|
|
112
104
|
const nextIndex = currentMediaIndex + 1;
|
|
113
|
-
setSelectedId(value
|
|
105
|
+
setSelectedId(value.medias?.[nextIndex]?.id || "");
|
|
114
106
|
}
|
|
107
|
+
} else {
|
|
108
|
+
const currentMediaIndex =
|
|
109
|
+
value?.medias?.findIndex((media) => media.id === selectedId) || 0;
|
|
110
|
+
const nextIndex = currentMediaIndex + 1;
|
|
111
|
+
setSelectedId(value?.medias?.[nextIndex]?.id || "");
|
|
115
112
|
}
|
|
116
|
-
}
|
|
113
|
+
}
|
|
114
|
+
}, [selectedIndex, totalItems, value, selectedId]);
|
|
117
115
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
116
|
+
const handleKeyDown = useCallback(
|
|
117
|
+
(e: KeyboardEvent) => {
|
|
118
|
+
if (e.key === "ArrowLeft") {
|
|
119
|
+
handlePrevious();
|
|
120
|
+
} else if (e.key === "ArrowRight") {
|
|
121
|
+
handleNext();
|
|
122
|
+
}
|
|
123
|
+
},
|
|
124
|
+
[handleNext, handlePrevious]
|
|
125
|
+
);
|
|
128
126
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
127
|
+
useEffect(() => {
|
|
128
|
+
window.addEventListener("keydown", handleKeyDown);
|
|
129
|
+
return () => window.removeEventListener("keydown", handleKeyDown);
|
|
130
|
+
}, [handleKeyDown]);
|
|
133
131
|
|
|
134
|
-
|
|
135
|
-
|
|
132
|
+
const canPrevious = selectedIndex > 0;
|
|
133
|
+
const canNext = selectedIndex < totalItems - 1;
|
|
134
|
+
|
|
135
|
+
return (
|
|
136
|
+
<div ref={ref} className={clsx("flex flex-col", className)} {...rest}>
|
|
137
|
+
{children}
|
|
138
|
+
{/* Main display area */}
|
|
139
|
+
<div className={clsx("relative group")}>
|
|
140
|
+
{canPrevious && (
|
|
141
|
+
<button
|
|
142
|
+
onClick={handlePrevious}
|
|
143
|
+
className="absolute left-4 top-1/2 transform -translate-y-1/2 z-10 bg-black/50 hover:bg-black/70 text-white rounded-full p-2 opacity-0 group-hover:opacity-100 transition-opacity duration-200"
|
|
144
|
+
aria-label="Previous slide"
|
|
145
|
+
>
|
|
146
|
+
<svg
|
|
147
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
148
|
+
className="h-6 w-6"
|
|
149
|
+
fill="none"
|
|
150
|
+
viewBox="0 0 24 24"
|
|
151
|
+
stroke="currentColor"
|
|
152
|
+
>
|
|
153
|
+
<path
|
|
154
|
+
strokeLinecap="round"
|
|
155
|
+
strokeLinejoin="round"
|
|
156
|
+
strokeWidth={2}
|
|
157
|
+
d="M15 19l-7-7 7-7"
|
|
158
|
+
/>
|
|
159
|
+
</svg>
|
|
160
|
+
</button>
|
|
161
|
+
)}
|
|
162
|
+
{canNext && (
|
|
163
|
+
<button
|
|
164
|
+
onClick={handleNext}
|
|
165
|
+
className="absolute right-4 top-1/2 transform -translate-y-1/2 z-10 bg-black/50 hover:bg-black/70 text-white rounded-full p-2 opacity-0 group-hover:opacity-100 transition-opacity duration-200"
|
|
166
|
+
aria-label="Next slide"
|
|
167
|
+
>
|
|
168
|
+
<svg
|
|
169
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
170
|
+
className="h-6 w-6"
|
|
171
|
+
fill="none"
|
|
172
|
+
viewBox="0 0 24 24"
|
|
173
|
+
stroke="currentColor"
|
|
174
|
+
>
|
|
175
|
+
<path
|
|
176
|
+
strokeLinecap="round"
|
|
177
|
+
strokeLinejoin="round"
|
|
178
|
+
strokeWidth={2}
|
|
179
|
+
d="M9 5l7 7-7 7"
|
|
180
|
+
/>
|
|
181
|
+
</svg>
|
|
182
|
+
</button>
|
|
183
|
+
)}
|
|
184
|
+
{value?.externalVideoUrl && selectedId === "video" && (
|
|
185
|
+
<div className={clsx("w-full rounded-md overflow-hidden", aspect)}>
|
|
186
|
+
<iframe
|
|
187
|
+
src={videoUrl}
|
|
188
|
+
className="w-full h-full"
|
|
189
|
+
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; autoplay"
|
|
190
|
+
referrerPolicy="strict-origin-when-cross-origin"
|
|
191
|
+
allowFullScreen
|
|
192
|
+
/>
|
|
193
|
+
</div>
|
|
194
|
+
)}
|
|
195
|
+
{value?.medias?.map((media) => (
|
|
196
|
+
<div
|
|
197
|
+
key={media.id}
|
|
198
|
+
className={clsx(
|
|
199
|
+
"transition-opacity duration-300 overflow-hidden rounded-md",
|
|
200
|
+
media.id === selectedId ? "opacity-100" : "opacity-0 hidden",
|
|
201
|
+
aspect
|
|
202
|
+
)}
|
|
203
|
+
>
|
|
204
|
+
<img
|
|
205
|
+
src={media?.resize || media?.url}
|
|
206
|
+
alt={media?.alt}
|
|
207
|
+
className="w-full h-full object-cover object-center"
|
|
208
|
+
/>
|
|
209
|
+
</div>
|
|
210
|
+
))}
|
|
211
|
+
</div>
|
|
136
212
|
|
|
137
|
-
|
|
138
|
-
<div
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
<div className={clsx("relative group")}>
|
|
142
|
-
{canPrevious && (
|
|
213
|
+
{/* Thumbnail navigation */}
|
|
214
|
+
<div className="relative mt-4">
|
|
215
|
+
<div className="flex items-stretch gap-2">
|
|
216
|
+
{totalItems > 6 && (
|
|
143
217
|
<button
|
|
144
218
|
onClick={handlePrevious}
|
|
145
|
-
|
|
146
|
-
|
|
219
|
+
disabled={!canPrevious}
|
|
220
|
+
className={clsx(
|
|
221
|
+
"flex items-center justify-center w-6",
|
|
222
|
+
"transition-colors duration-200 rounded-l-md",
|
|
223
|
+
!canPrevious
|
|
224
|
+
? "bg-gray-100 text-gray-400 cursor-not-allowed"
|
|
225
|
+
: "bg-gray-200 hover:bg-gray-300 text-gray-700"
|
|
226
|
+
)}
|
|
227
|
+
aria-label="Previous"
|
|
147
228
|
>
|
|
148
229
|
<svg
|
|
149
230
|
xmlns="http://www.w3.org/2000/svg"
|
|
150
|
-
className="h-
|
|
231
|
+
className="h-4 w-4 md:h-5 md:w-5"
|
|
151
232
|
fill="none"
|
|
152
233
|
viewBox="0 0 24 24"
|
|
153
234
|
stroke="currentColor"
|
|
@@ -161,15 +242,88 @@ export const Medias = forwardRef<HTMLDivElement, MediasProps>(
|
|
|
161
242
|
</svg>
|
|
162
243
|
</button>
|
|
163
244
|
)}
|
|
164
|
-
|
|
245
|
+
<div className="flex-1">
|
|
246
|
+
<div className="grid grid-cols-6 gap-2">
|
|
247
|
+
{value?.externalVideoUrl && startIndex === 0 && (
|
|
248
|
+
<div
|
|
249
|
+
className={clsx(
|
|
250
|
+
"relative cursor-pointer overflow-hidden rounded-sm border-2",
|
|
251
|
+
thumbnailAspect,
|
|
252
|
+
selectedId === "video"
|
|
253
|
+
? "border-primary-500"
|
|
254
|
+
: "border-transparent hover:border-primary-300"
|
|
255
|
+
)}
|
|
256
|
+
onClick={() => setSelectedId("video")}
|
|
257
|
+
>
|
|
258
|
+
<img
|
|
259
|
+
src={thumbnailUrl}
|
|
260
|
+
alt="Video thumbnail"
|
|
261
|
+
className="w-full h-full object-cover"
|
|
262
|
+
/>
|
|
263
|
+
<div className="absolute inset-0 flex items-center justify-center bg-black/30">
|
|
264
|
+
<svg
|
|
265
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
266
|
+
className="h-8 w-8 text-white"
|
|
267
|
+
viewBox="0 0 24 24"
|
|
268
|
+
>
|
|
269
|
+
<path
|
|
270
|
+
fill="currentColor"
|
|
271
|
+
fill-rule="evenodd"
|
|
272
|
+
d="M12 22c5.523 0 10-4.477 10-10S17.523 2 12 2S2 6.477 2 12s4.477 10 10 10"
|
|
273
|
+
clipRule="evenodd"
|
|
274
|
+
opacity="0.5"
|
|
275
|
+
/>
|
|
276
|
+
<path
|
|
277
|
+
fill="currentColor"
|
|
278
|
+
d="m15.414 13.059l-4.72 2.787C9.934 16.294 9 15.71 9 14.786V9.214c0-.924.934-1.507 1.694-1.059l4.72 2.787c.781.462.781 1.656 0 2.118"
|
|
279
|
+
/>
|
|
280
|
+
</svg>
|
|
281
|
+
</div>
|
|
282
|
+
</div>
|
|
283
|
+
)}
|
|
284
|
+
{value?.medias?.map((media, index) => {
|
|
285
|
+
const adjustedIndex = value?.externalVideoUrl
|
|
286
|
+
? index + 1
|
|
287
|
+
: index;
|
|
288
|
+
return adjustedIndex >= startIndex &&
|
|
289
|
+
adjustedIndex < endIndex ? (
|
|
290
|
+
<div
|
|
291
|
+
key={media.id}
|
|
292
|
+
className={clsx(
|
|
293
|
+
"relative cursor-pointer overflow-hidden rounded-sm border-2",
|
|
294
|
+
thumbnailAspect,
|
|
295
|
+
selectedId === media.id
|
|
296
|
+
? "border-primary-500"
|
|
297
|
+
: "border-transparent hover:border-primary-300"
|
|
298
|
+
)}
|
|
299
|
+
onClick={() => setSelectedId(media.id)}
|
|
300
|
+
>
|
|
301
|
+
<img
|
|
302
|
+
src={media?.resize || media?.url}
|
|
303
|
+
alt={media?.alt}
|
|
304
|
+
className="w-full h-full object-cover"
|
|
305
|
+
/>
|
|
306
|
+
</div>
|
|
307
|
+
) : null;
|
|
308
|
+
})}
|
|
309
|
+
</div>
|
|
310
|
+
</div>
|
|
311
|
+
{totalItems > 6 && (
|
|
165
312
|
<button
|
|
166
313
|
onClick={handleNext}
|
|
167
|
-
|
|
168
|
-
|
|
314
|
+
disabled={!canNext}
|
|
315
|
+
className={clsx(
|
|
316
|
+
"flex items-center justify-center w-6",
|
|
317
|
+
"transition-colors duration-200 rounded-r-md",
|
|
318
|
+
!canNext
|
|
319
|
+
? "bg-gray-100 text-gray-400 cursor-not-allowed"
|
|
320
|
+
: "bg-gray-200 hover:bg-gray-300 text-gray-700"
|
|
321
|
+
)}
|
|
322
|
+
aria-label="Next"
|
|
169
323
|
>
|
|
170
324
|
<svg
|
|
171
325
|
xmlns="http://www.w3.org/2000/svg"
|
|
172
|
-
className="h-
|
|
326
|
+
className="h-4 w-4 md:h-5 md:w-5"
|
|
173
327
|
fill="none"
|
|
174
328
|
viewBox="0 0 24 24"
|
|
175
329
|
stroke="currentColor"
|
|
@@ -183,165 +337,8 @@ export const Medias = forwardRef<HTMLDivElement, MediasProps>(
|
|
|
183
337
|
</svg>
|
|
184
338
|
</button>
|
|
185
339
|
)}
|
|
186
|
-
{value?.externalVideoUrl && selectedId === "video" && (
|
|
187
|
-
<div className={clsx("w-full rounded-md overflow-hidden", aspect)}>
|
|
188
|
-
<iframe
|
|
189
|
-
src={videoUrl}
|
|
190
|
-
className="w-full h-full"
|
|
191
|
-
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; autoplay"
|
|
192
|
-
referrerPolicy="strict-origin-when-cross-origin"
|
|
193
|
-
allowFullScreen
|
|
194
|
-
/>
|
|
195
|
-
</div>
|
|
196
|
-
)}
|
|
197
|
-
{value?.medias?.map((media) => (
|
|
198
|
-
<div
|
|
199
|
-
key={media.id}
|
|
200
|
-
className={clsx(
|
|
201
|
-
"transition-opacity duration-300 overflow-hidden rounded-md",
|
|
202
|
-
media.id === selectedId ? "opacity-100" : "opacity-0 hidden",
|
|
203
|
-
aspect
|
|
204
|
-
)}
|
|
205
|
-
>
|
|
206
|
-
<img
|
|
207
|
-
src={media?.resize || media?.url}
|
|
208
|
-
alt={media?.alt}
|
|
209
|
-
className="w-full h-full object-cover object-center"
|
|
210
|
-
/>
|
|
211
|
-
</div>
|
|
212
|
-
))}
|
|
213
|
-
</div>
|
|
214
|
-
|
|
215
|
-
{/* Thumbnail navigation */}
|
|
216
|
-
<div className="relative mt-4">
|
|
217
|
-
<div className="flex items-stretch gap-2">
|
|
218
|
-
{totalItems > 6 && (
|
|
219
|
-
<button
|
|
220
|
-
onClick={handlePrevious}
|
|
221
|
-
disabled={!canPrevious}
|
|
222
|
-
className={clsx(
|
|
223
|
-
"flex items-center justify-center w-6",
|
|
224
|
-
"transition-colors duration-200 rounded-l-md",
|
|
225
|
-
!canPrevious
|
|
226
|
-
? "bg-gray-100 text-gray-400 cursor-not-allowed"
|
|
227
|
-
: "bg-gray-200 hover:bg-gray-300 text-gray-700"
|
|
228
|
-
)}
|
|
229
|
-
aria-label="Previous"
|
|
230
|
-
>
|
|
231
|
-
<svg
|
|
232
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
233
|
-
className="h-4 w-4 md:h-5 md:w-5"
|
|
234
|
-
fill="none"
|
|
235
|
-
viewBox="0 0 24 24"
|
|
236
|
-
stroke="currentColor"
|
|
237
|
-
>
|
|
238
|
-
<path
|
|
239
|
-
strokeLinecap="round"
|
|
240
|
-
strokeLinejoin="round"
|
|
241
|
-
strokeWidth={2}
|
|
242
|
-
d="M15 19l-7-7 7-7"
|
|
243
|
-
/>
|
|
244
|
-
</svg>
|
|
245
|
-
</button>
|
|
246
|
-
)}
|
|
247
|
-
<div className="flex-1">
|
|
248
|
-
<div className="grid grid-cols-6 gap-2">
|
|
249
|
-
{value?.externalVideoUrl && startIndex === 0 && (
|
|
250
|
-
<div
|
|
251
|
-
className={clsx(
|
|
252
|
-
"relative cursor-pointer overflow-hidden rounded-sm border-2",
|
|
253
|
-
aspect,
|
|
254
|
-
selectedId === "video"
|
|
255
|
-
? "border-primary-500"
|
|
256
|
-
: "border-transparent hover:border-primary-300"
|
|
257
|
-
)}
|
|
258
|
-
onClick={() => setSelectedId("video")}
|
|
259
|
-
>
|
|
260
|
-
<img
|
|
261
|
-
src={thumbnailUrl}
|
|
262
|
-
alt="Video thumbnail"
|
|
263
|
-
className="w-full h-full object-cover"
|
|
264
|
-
/>
|
|
265
|
-
<div className="absolute inset-0 flex items-center justify-center bg-black/30">
|
|
266
|
-
<svg
|
|
267
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
268
|
-
className="h-8 w-8 text-white"
|
|
269
|
-
viewBox="0 0 24 24"
|
|
270
|
-
>
|
|
271
|
-
<path
|
|
272
|
-
fill="currentColor"
|
|
273
|
-
fill-rule="evenodd"
|
|
274
|
-
d="M12 22c5.523 0 10-4.477 10-10S17.523 2 12 2S2 6.477 2 12s4.477 10 10 10"
|
|
275
|
-
clipRule="evenodd"
|
|
276
|
-
opacity="0.5"
|
|
277
|
-
/>
|
|
278
|
-
<path
|
|
279
|
-
fill="currentColor"
|
|
280
|
-
d="m15.414 13.059l-4.72 2.787C9.934 16.294 9 15.71 9 14.786V9.214c0-.924.934-1.507 1.694-1.059l4.72 2.787c.781.462.781 1.656 0 2.118"
|
|
281
|
-
/>
|
|
282
|
-
</svg>
|
|
283
|
-
</div>
|
|
284
|
-
</div>
|
|
285
|
-
)}
|
|
286
|
-
{value?.medias?.map((media, index) => {
|
|
287
|
-
const adjustedIndex = value?.externalVideoUrl
|
|
288
|
-
? index + 1
|
|
289
|
-
: index;
|
|
290
|
-
return adjustedIndex >= startIndex &&
|
|
291
|
-
adjustedIndex < endIndex ? (
|
|
292
|
-
<div
|
|
293
|
-
key={media.id}
|
|
294
|
-
className={clsx(
|
|
295
|
-
"relative cursor-pointer overflow-hidden rounded-sm border-2",
|
|
296
|
-
aspect,
|
|
297
|
-
selectedId === media.id
|
|
298
|
-
? "border-primary-500"
|
|
299
|
-
: "border-transparent hover:border-primary-300"
|
|
300
|
-
)}
|
|
301
|
-
onClick={() => setSelectedId(media.id)}
|
|
302
|
-
>
|
|
303
|
-
<img
|
|
304
|
-
src={media?.resize || media?.url}
|
|
305
|
-
alt={media?.alt}
|
|
306
|
-
className="w-full h-full object-cover"
|
|
307
|
-
/>
|
|
308
|
-
</div>
|
|
309
|
-
) : null;
|
|
310
|
-
})}
|
|
311
|
-
</div>
|
|
312
|
-
</div>
|
|
313
|
-
{totalItems > 6 && (
|
|
314
|
-
<button
|
|
315
|
-
onClick={handleNext}
|
|
316
|
-
disabled={!canNext}
|
|
317
|
-
className={clsx(
|
|
318
|
-
"flex items-center justify-center w-6",
|
|
319
|
-
"transition-colors duration-200 rounded-r-md",
|
|
320
|
-
!canNext
|
|
321
|
-
? "bg-gray-100 text-gray-400 cursor-not-allowed"
|
|
322
|
-
: "bg-gray-200 hover:bg-gray-300 text-gray-700"
|
|
323
|
-
)}
|
|
324
|
-
aria-label="Next"
|
|
325
|
-
>
|
|
326
|
-
<svg
|
|
327
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
328
|
-
className="h-4 w-4 md:h-5 md:w-5"
|
|
329
|
-
fill="none"
|
|
330
|
-
viewBox="0 0 24 24"
|
|
331
|
-
stroke="currentColor"
|
|
332
|
-
>
|
|
333
|
-
<path
|
|
334
|
-
strokeLinecap="round"
|
|
335
|
-
strokeLinejoin="round"
|
|
336
|
-
strokeWidth={2}
|
|
337
|
-
d="M9 5l7 7-7 7"
|
|
338
|
-
/>
|
|
339
|
-
</svg>
|
|
340
|
-
</button>
|
|
341
|
-
)}
|
|
342
|
-
</div>
|
|
343
340
|
</div>
|
|
344
341
|
</div>
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
);
|
|
342
|
+
</div>
|
|
343
|
+
);
|
|
344
|
+
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { extractOutline, mdxToSlate } from "@rxdrag/slate-preview";
|
|
2
|
-
import { forwardRef,
|
|
2
|
+
import { forwardRef, useEffect } from "react";
|
|
3
3
|
import { useAcitviedHeading } from "./useAcitviedHeading";
|
|
4
4
|
import clsx from "clsx";
|
|
5
5
|
|
|
@@ -16,8 +16,8 @@ export const RichTextOutline = forwardRef<
|
|
|
16
16
|
HTMLUListElement,
|
|
17
17
|
RichTextOutlineProps
|
|
18
18
|
>((props, ref) => {
|
|
19
|
-
const { className, itemClassName, value, yOffset =
|
|
20
|
-
const activiedId = useAcitviedHeading();
|
|
19
|
+
const { className, itemClassName, value, yOffset = 200, ...rest } = props;
|
|
20
|
+
const activiedId = useAcitviedHeading(yOffset);
|
|
21
21
|
const nodes = mdxToSlate(value ?? "");
|
|
22
22
|
const outline = extractOutline(nodes ?? []);
|
|
23
23
|
|
|
@@ -38,7 +38,7 @@ export const RichTextOutline = forwardRef<
|
|
|
38
38
|
return () => {
|
|
39
39
|
window.removeEventListener("hashchange", handleHashChange);
|
|
40
40
|
};
|
|
41
|
-
}, []);
|
|
41
|
+
}, [yOffset]);
|
|
42
42
|
|
|
43
43
|
return outline?.length ? (
|
|
44
44
|
<ul ref={ref} className={className} {...rest}>
|
|
@@ -57,7 +57,6 @@ export const RichTextOutline = forwardRef<
|
|
|
57
57
|
e.preventDefault();
|
|
58
58
|
const element = document.getElementById(item?.key);
|
|
59
59
|
if (element) {
|
|
60
|
-
const yOffset = -100; // 偏移量
|
|
61
60
|
const y =
|
|
62
61
|
element.getBoundingClientRect().top +
|
|
63
62
|
window.scrollY +
|