lightnet 3.12.2 → 4.0.0
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/CHANGELOG.md +49 -0
- package/__e2e__/basics-fixture.ts +9 -63
- package/__e2e__/fixtures/basics/astro.config.mjs +7 -12
- package/__e2e__/fixtures/basics/node_modules/.bin/astro +4 -4
- package/__e2e__/fixtures/basics/node_modules/.bin/tailwind +2 -2
- package/__e2e__/fixtures/basics/node_modules/.bin/tailwindcss +2 -2
- package/__e2e__/fixtures/basics/node_modules/.bin/tsc +2 -2
- package/__e2e__/fixtures/basics/node_modules/.bin/tsserver +2 -2
- package/__e2e__/fixtures/basics/package.json +6 -5
- package/__e2e__/fixtures/basics/src/content/categories/christian-living.json +4 -1
- package/__e2e__/fixtures/basics/src/content/categories/teens.json +4 -1
- package/__e2e__/fixtures/basics/src/content/categories/theology.json +4 -1
- package/__e2e__/fixtures/basics/src/content/media/faithful-freestyle--en.json +6 -2
- package/__e2e__/fixtures/basics/src/content/media/how-to-kickflip--de.json +6 -1
- package/__e2e__/fixtures/basics/src/content/media/skate-sounds--en.json +8 -2
- package/__e2e__/fixtures/basics/src/content/media-collections/how-to-articles.json +5 -1
- package/__e2e__/fixtures/basics/src/content/media-types/audio.json +5 -2
- package/__e2e__/fixtures/basics/src/content/media-types/book.json +10 -4
- package/__e2e__/fixtures/basics/src/content/media-types/video.json +5 -2
- package/__e2e__/fixtures/basics/src/pages/[locale]/index.astro +0 -1
- package/__e2e__/fixtures/basics/src/translations/de.yml +0 -8
- package/__e2e__/fixtures/basics/src/translations/en.yml +0 -8
- package/__e2e__/global.teardown.ts +2 -2
- package/__tests__/astro-integration/config.spec.ts +364 -0
- package/__tests__/astro-integration/integration.spec.ts +125 -0
- package/__tests__/astro-integration/tailwind.spec.ts +36 -0
- package/__tests__/content/content-schema.spec.ts +109 -0
- package/__tests__/content/get-media-collections.spec.ts +72 -0
- package/__tests__/content/query-media-items.spec.ts +213 -0
- package/__tests__/i18n/resolve-current-locale.spec.ts +65 -0
- package/__tests__/i18n/translate-map.spec.ts +19 -0
- package/__tests__/i18n/translate.spec.ts +91 -0
- package/__tests__/pages/details-page/create-content-metadata.spec.ts +43 -25
- package/__tests__/pages/details-page/get-translations.spec.ts +56 -0
- package/__tests__/utils/paths.spec.ts +116 -0
- package/__tests__/utils/urls.spec.ts +9 -4
- package/exports/content.ts +7 -2
- package/exports/i18n.ts +0 -1
- package/exports/index.ts +1 -5
- package/exports/utils.ts +0 -1
- package/package.json +16 -12
- package/src/astro-integration/config.ts +60 -49
- package/src/astro-integration/integration.ts +13 -24
- package/src/astro-integration/tailwind.ts +86 -0
- package/src/astro-integration/validators/validate-inline-translations.ts +51 -0
- package/src/astro-integration/validators/validate-languages.ts +39 -0
- package/src/astro-integration/virtual.d.ts +8 -6
- package/src/astro-integration/vite-plugin-lightnet-config.ts +29 -9
- package/src/components/CarouselSection.astro +7 -11
- package/src/components/CategoriesSection.astro +2 -2
- package/src/components/HighlightSection.astro +4 -7
- package/src/components/Icon.tsx +2 -2
- package/src/components/MediaGallerySection.astro +88 -68
- package/src/components/MediaList.astro +9 -7
- package/src/components/SearchInput.astro +7 -4
- package/src/components/Section.astro +7 -5
- package/src/components/VideoPlayer.astro +2 -3
- package/src/content/content-schema.ts +129 -142
- package/src/content/get-categories.ts +52 -28
- package/src/content/get-languages.ts +29 -8
- package/src/content/get-media-collections.ts +43 -0
- package/src/content/get-media-types.ts +41 -7
- package/src/content/query-media-items.ts +23 -13
- package/src/i18n/bcp-47.ts +8 -0
- package/src/i18n/get-locale-paths.ts +1 -3
- package/src/i18n/locals.d.ts +21 -3
- package/src/i18n/locals.ts +18 -11
- package/src/i18n/resolve-current-locale.ts +18 -0
- package/src/i18n/resolve-language.ts +10 -5
- package/src/i18n/translate-map.ts +70 -0
- package/src/i18n/translate.ts +68 -47
- package/src/layouts/Page.astro +5 -3
- package/src/layouts/components/LanguagePicker.astro +22 -17
- package/src/layouts/components/Menu.astro +2 -5
- package/src/layouts/components/MenuItem.astro +1 -1
- package/src/layouts/components/PageNavigation.astro +29 -29
- package/src/layouts/components/PageTitle.astro +23 -7
- package/src/pages/404Route.astro +2 -1
- package/src/pages/RootRoute.astro +6 -1
- package/src/pages/details-page/DefaultDetailsPage.astro +9 -2
- package/src/pages/details-page/DetailsPageRoute.astro +1 -2
- package/src/pages/details-page/components/AudioPanel.astro +7 -3
- package/src/pages/details-page/components/AudioPlayer.astro +2 -2
- package/src/pages/details-page/components/ContentSection.astro +67 -44
- package/src/pages/details-page/components/MediaCollection.astro +8 -4
- package/src/pages/details-page/components/MediaCollectionsSection.astro +3 -6
- package/src/pages/details-page/components/main-details/EditButton.astro +22 -10
- package/src/pages/details-page/components/main-details/OpenButton.astro +17 -12
- package/src/pages/details-page/components/main-details/ShareButton.astro +3 -2
- package/src/pages/details-page/components/more-details/Categories.astro +5 -3
- package/src/pages/details-page/components/more-details/Languages.astro +12 -7
- package/src/pages/details-page/utils/create-content-metadata.ts +24 -9
- package/src/pages/details-page/utils/get-translations.ts +6 -0
- package/src/pages/search-page/components/LoadingSkeleton.tsx +6 -5
- package/src/pages/search-page/components/SearchFilter.astro +10 -21
- package/src/pages/search-page/components/SearchFilter.tsx +2 -2
- package/src/pages/search-page/components/SearchList.astro +10 -7
- package/src/pages/search-page/components/SearchListItem.tsx +5 -4
- package/src/pages/search-page/hooks/use-search.ts +5 -2
- package/src/utils/lazy.ts +20 -0
- package/src/utils/paths.ts +40 -3
- package/src/utils/urls.ts +1 -2
- package/src/utils/verify-schema.ts +12 -10
- package/tailwind.config.ts +1 -25
- package/vitest.config.js +18 -2
- package/src/astro-integration/project-context.ts +0 -5
- package/src/content/compare-media-collection-items.ts +0 -24
- package/src/i18n/resolve-default-locale.ts +0 -19
- package/src/i18n/resolve-locales.ts +0 -5
- package/src/pages/details-page/utils/get-collection-items.ts +0 -29
|
@@ -14,33 +14,49 @@ function getWidth(logo: ImageMetadata) {
|
|
|
14
14
|
return Math.floor(size * Math.max(1, logo.width / logo.height))
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
const
|
|
18
|
-
config.logo?.alt ?? (config.logo?.replacesTitle ? config.title : "")
|
|
17
|
+
const { currentLocale, tMap } = Astro.locals.i18n
|
|
19
18
|
|
|
20
|
-
const
|
|
19
|
+
const logoAlt = (() => {
|
|
20
|
+
if (config.logo?.alt) {
|
|
21
|
+
return tMap(config.logo.alt, {
|
|
22
|
+
path: ["config", "logo", "alt"],
|
|
23
|
+
})
|
|
24
|
+
}
|
|
25
|
+
if (config.logo?.replacesTitle) {
|
|
26
|
+
return tMap(config.title, {
|
|
27
|
+
path: ["config", "title"],
|
|
28
|
+
})
|
|
29
|
+
}
|
|
30
|
+
return ""
|
|
31
|
+
})()
|
|
21
32
|
---
|
|
22
33
|
|
|
23
34
|
<a
|
|
24
35
|
class="flex items-center gap-3 font-bold text-gray-600 md:text-lg"
|
|
25
|
-
href={localizePath(
|
|
36
|
+
href={localizePath(currentLocale, "/")}
|
|
26
37
|
>
|
|
27
38
|
{
|
|
28
39
|
logo &&
|
|
29
40
|
(logo.format === "svg" ? (
|
|
30
41
|
<img
|
|
31
42
|
src={logo.src}
|
|
32
|
-
alt={
|
|
43
|
+
alt={logoAlt}
|
|
33
44
|
style={`width:${getWidth(logo)}px;`}
|
|
34
45
|
/>
|
|
35
46
|
) : (
|
|
36
47
|
<Image
|
|
37
48
|
src={logo}
|
|
38
|
-
alt={
|
|
49
|
+
alt={logoAlt}
|
|
39
50
|
style={`width:${getWidth(logo)}px;`}
|
|
40
51
|
width={getWidth(logo)}
|
|
41
52
|
densities={[1.5, 2, 3]}
|
|
42
53
|
loading="eager"
|
|
43
54
|
/>
|
|
44
55
|
))
|
|
45
|
-
}{
|
|
56
|
+
}{
|
|
57
|
+
!config.logo?.replacesTitle &&
|
|
58
|
+
tMap(config.title, {
|
|
59
|
+
path: ["config", "title"],
|
|
60
|
+
})
|
|
61
|
+
}</a
|
|
46
62
|
>
|
package/src/pages/404Route.astro
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
import Page from "../layouts/Page.astro"
|
|
3
|
+
import { localizePath } from "../utils/paths"
|
|
3
4
|
---
|
|
4
5
|
|
|
5
6
|
<Page>
|
|
@@ -8,7 +9,7 @@ import Page from "../layouts/Page.astro"
|
|
|
8
9
|
{Astro.locals.i18n.t("ln.404.page-not-found")}
|
|
9
10
|
</p>
|
|
10
11
|
<a
|
|
11
|
-
href={
|
|
12
|
+
href={localizePath(Astro.locals.i18n.defaultLocale, "/")}
|
|
12
13
|
class="rounded-2xl bg-gray-200 px-8 py-4 text-sm font-bold transition-colors ease-in-out hover:bg-gray-400"
|
|
13
14
|
>{Astro.locals.i18n.t("ln.404.go-to-the-home-page")}</a
|
|
14
15
|
>
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
+
import type { TranslationMap } from "../../i18n/translate-map"
|
|
2
3
|
import ContentSection from "./components/ContentSection.astro"
|
|
3
4
|
import DescriptionSection from "./components/DescriptionSection.astro"
|
|
4
5
|
import DetailsPage from "./components/DetailsPage.astro"
|
|
@@ -9,10 +10,16 @@ import MoreDetailsSection from "./components/MoreDetailsSection.astro"
|
|
|
9
10
|
|
|
10
11
|
export type Props = {
|
|
11
12
|
mediaId: string
|
|
12
|
-
openActionLabel?:
|
|
13
|
+
openActionLabel?: TranslationMap
|
|
13
14
|
}
|
|
14
15
|
|
|
15
|
-
const
|
|
16
|
+
const defaultOpenActionLabel = Object.fromEntries(
|
|
17
|
+
Astro.locals.i18n.locales.map((locale) => [
|
|
18
|
+
locale,
|
|
19
|
+
Astro.locals.i18n.t("ln.details.open", { lng: locale }),
|
|
20
|
+
]),
|
|
21
|
+
)
|
|
22
|
+
const { mediaId, openActionLabel = defaultOpenActionLabel } = Astro.props
|
|
16
23
|
---
|
|
17
24
|
|
|
18
25
|
<DetailsPage mediaId={mediaId}>
|
|
@@ -6,14 +6,13 @@ import config from "virtual:lightnet/config"
|
|
|
6
6
|
|
|
7
7
|
import { getMediaItem } from "../../content/get-media-items"
|
|
8
8
|
import { getMediaType } from "../../content/get-media-types"
|
|
9
|
-
import { resolveLocales } from "../../i18n/resolve-locales"
|
|
10
9
|
import AudioDetailsPage from "./AudioDetailsPage.astro"
|
|
11
10
|
import DefaultDetailsPage from "./DefaultDetailsPage.astro"
|
|
12
11
|
import VideoDetailsPage from "./VideoDetailsPage.astro"
|
|
13
12
|
|
|
14
13
|
export const getStaticPaths = (async () => {
|
|
15
14
|
const mediaItems = await getCollection("media")
|
|
16
|
-
return
|
|
15
|
+
return config.locales.flatMap((locale) =>
|
|
17
16
|
mediaItems.map(({ id: mediaId }) => ({ params: { mediaId, locale } })),
|
|
18
17
|
)
|
|
19
18
|
}) satisfies GetStaticPaths
|
|
@@ -9,11 +9,15 @@ interface Props {
|
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
const { mediaId, className } = Astro.props
|
|
12
|
-
const {
|
|
12
|
+
const { tMap } = Astro.locals.i18n
|
|
13
13
|
|
|
14
14
|
const item = await getMediaItem(mediaId)
|
|
15
15
|
|
|
16
|
-
const content = item.data.content.map((
|
|
16
|
+
const content = item.data.content.map((contentItem, index) =>
|
|
17
|
+
createContentMetadata(contentItem, tMap, {
|
|
18
|
+
path: ["media-items", mediaId, "content", index],
|
|
19
|
+
}),
|
|
20
|
+
)
|
|
17
21
|
---
|
|
18
22
|
|
|
19
23
|
<ol
|
|
@@ -24,7 +28,7 @@ const content = item.data.content.map((c) => createContentMetadata(c))
|
|
|
24
28
|
{
|
|
25
29
|
content.map((c) => (
|
|
26
30
|
<li>
|
|
27
|
-
{content.length > 1 && <div class="mb-3 font-bold">{
|
|
31
|
+
{content.length > 1 && <div class="mb-3 font-bold">{c.labelText}</div>}
|
|
28
32
|
<AudioPlayer className="w-full" src={c.url} />
|
|
29
33
|
</li>
|
|
30
34
|
))
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
import
|
|
2
|
+
import { ExternalLinkIcon } from "lucide-react"
|
|
3
3
|
|
|
4
4
|
type Props = {
|
|
5
5
|
src: string
|
|
@@ -37,7 +37,7 @@ const isMp3 = audioExtension.endsWith(".mp3")
|
|
|
37
37
|
</span>
|
|
38
38
|
<div class="flex h-full grow flex-col gap-3">
|
|
39
39
|
<span class="flex items-center gap-2 font-semibold text-gray-900">
|
|
40
|
-
<
|
|
40
|
+
<ExternalLinkIcon className="shrink-0" />
|
|
41
41
|
{t("ln.external-link")}
|
|
42
42
|
</span>
|
|
43
43
|
<div class="h-2 w-full rounded-full bg-gray-200">
|
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
---
|
|
2
|
-
import
|
|
2
|
+
import {
|
|
3
|
+
ChevronRightIcon,
|
|
4
|
+
CodeIcon,
|
|
5
|
+
DownloadIcon,
|
|
6
|
+
ImageIcon,
|
|
7
|
+
LinkIcon,
|
|
8
|
+
type LucideIcon,
|
|
9
|
+
MusicIcon,
|
|
10
|
+
PackageIcon,
|
|
11
|
+
TextIcon,
|
|
12
|
+
VideoIcon,
|
|
13
|
+
} from "lucide-react"
|
|
14
|
+
|
|
3
15
|
import { getMediaItem } from "../../../content/get-media-items"
|
|
4
16
|
import {
|
|
5
17
|
createContentMetadata,
|
|
@@ -18,19 +30,23 @@ const item = await getMediaItem(mediaId)
|
|
|
18
30
|
if (item.data.content.length < minimumItems) {
|
|
19
31
|
return
|
|
20
32
|
}
|
|
33
|
+
const { t, tMap, direction } = Astro.locals.i18n
|
|
21
34
|
|
|
22
|
-
const content = item.data.content.map((
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
35
|
+
const content = item.data.content.map((contentItem, index) =>
|
|
36
|
+
createContentMetadata(contentItem, tMap, {
|
|
37
|
+
path: ["media-items", mediaId, "content", index],
|
|
38
|
+
}),
|
|
39
|
+
)
|
|
40
|
+
const typeIcons: Record<UrlType, LucideIcon> = {
|
|
41
|
+
audio: MusicIcon,
|
|
42
|
+
text: TextIcon,
|
|
43
|
+
source: CodeIcon,
|
|
44
|
+
link: LinkIcon,
|
|
45
|
+
image: ImageIcon,
|
|
46
|
+
video: VideoIcon,
|
|
47
|
+
package: PackageIcon,
|
|
31
48
|
} as const
|
|
32
|
-
|
|
33
|
-
const { t, direction } = Astro.locals.i18n
|
|
49
|
+
const iconDirectionClass = direction === "rtl" ? "scale-x-[-1]" : ""
|
|
34
50
|
---
|
|
35
51
|
|
|
36
52
|
<ol
|
|
@@ -38,39 +54,46 @@ const { t, direction } = Astro.locals.i18n
|
|
|
38
54
|
>
|
|
39
55
|
{
|
|
40
56
|
content.map(
|
|
41
|
-
({ extension,
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
57
|
+
({ extension, labelText, type, canBeOpened, url, target }, index) => {
|
|
58
|
+
const TypeIcon = typeIcons[type]
|
|
59
|
+
|
|
60
|
+
return (
|
|
61
|
+
<li class="group -mt-px px-4 transition-colors ease-in-out hover:bg-gray-300 md:px-8">
|
|
62
|
+
<a
|
|
63
|
+
href={url}
|
|
64
|
+
target={target}
|
|
65
|
+
class="flex items-center justify-between py-8"
|
|
66
|
+
>
|
|
67
|
+
<span class="flex h-10 w-10 shrink-0 items-center justify-center rounded-full bg-gray-800 text-gray-200">
|
|
68
|
+
<TypeIcon size="1.2rem" />
|
|
69
|
+
</span>
|
|
54
70
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
71
|
+
<div class="ms-4 line-clamp-1 shrink grow overflow-hidden sm:ms-8">
|
|
72
|
+
{labelText}
|
|
73
|
+
</div>
|
|
74
|
+
<div class="me-4 ms-2 shrink-0 font-bold uppercase text-gray-600 sm:me-8">
|
|
75
|
+
{extension}
|
|
76
|
+
</div>
|
|
77
|
+
{canBeOpened ? (
|
|
78
|
+
<>
|
|
79
|
+
<ChevronRightIcon
|
|
80
|
+
className={`shrink-0 text-gray-600 group-hover:text-gray-800 ${iconDirectionClass}`}
|
|
81
|
+
/>
|
|
82
|
+
<span class="sr-only">{t("ln.details.open")}</span>
|
|
83
|
+
</>
|
|
84
|
+
) : (
|
|
85
|
+
<>
|
|
86
|
+
<DownloadIcon className="shrink-0 text-gray-600 group-hover:text-gray-800" />
|
|
87
|
+
<span class="sr-only">{t("ln.details.download")}</span>
|
|
88
|
+
</>
|
|
89
|
+
)}
|
|
90
|
+
</a>
|
|
91
|
+
{index !== content.length - 1 && (
|
|
92
|
+
<div class="h-px w-full bg-gray-300" />
|
|
93
|
+
)}
|
|
94
|
+
</li>
|
|
95
|
+
)
|
|
96
|
+
},
|
|
74
97
|
)
|
|
75
98
|
}
|
|
76
99
|
</ol>
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
---
|
|
2
2
|
import { AstroError } from "astro/errors"
|
|
3
3
|
import { getEntry } from "astro:content"
|
|
4
|
+
import { getEntries } from "astro:content"
|
|
4
5
|
|
|
5
6
|
import MediaList from "../../../components/MediaList.astro"
|
|
6
|
-
import { getCollectionItems } from "../utils/get-collection-items"
|
|
7
7
|
|
|
8
8
|
interface Props {
|
|
9
9
|
collectionId: string
|
|
@@ -18,7 +18,7 @@ if (!collection) {
|
|
|
18
18
|
`To fix the issue, add a media-collection at "src/content/media-collections/${collectionId}.json".`,
|
|
19
19
|
)
|
|
20
20
|
}
|
|
21
|
-
const items = (await
|
|
21
|
+
const items = (await getEntries(collection.data.mediaItems)).map((item) => ({
|
|
22
22
|
...item,
|
|
23
23
|
disabled: item.id === disableItem,
|
|
24
24
|
}))
|
|
@@ -26,7 +26,7 @@ const items = (await getCollectionItems(collection.id)).map((item) => ({
|
|
|
26
26
|
if (items.length < 2) {
|
|
27
27
|
return
|
|
28
28
|
}
|
|
29
|
-
const t = Astro.locals.i18n
|
|
29
|
+
const { t, tMap } = Astro.locals.i18n
|
|
30
30
|
---
|
|
31
31
|
|
|
32
32
|
<section class="mx-auto mt-16 max-w-screen-md px-4 md:mt-20 md:px-8">
|
|
@@ -34,7 +34,11 @@ const t = Astro.locals.i18n.t
|
|
|
34
34
|
{t("ln.details.part-of-collection")}
|
|
35
35
|
</h2>
|
|
36
36
|
<h3 class="mb-6 text-lg font-bold text-gray-700 sm:mb-8">
|
|
37
|
-
{
|
|
37
|
+
{
|
|
38
|
+
tMap(collection.data.label, {
|
|
39
|
+
path: ["media-collections", collectionId, "label"],
|
|
40
|
+
})
|
|
41
|
+
}
|
|
38
42
|
</h3>
|
|
39
43
|
<MediaList items={items} />
|
|
40
44
|
</section>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
import {
|
|
2
|
+
import { getCollectionsForMediaItem } from "../../../content/get-media-collections"
|
|
3
3
|
import MediaCollection from "./MediaCollection.astro"
|
|
4
4
|
|
|
5
5
|
interface Props {
|
|
@@ -7,15 +7,12 @@ interface Props {
|
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
const { mediaId } = Astro.props
|
|
10
|
-
const item = await getMediaItem(mediaId)
|
|
11
10
|
|
|
12
|
-
const collections =
|
|
13
|
-
({ collection }) => collection.id,
|
|
14
|
-
)
|
|
11
|
+
const collections = await getCollectionsForMediaItem(mediaId)
|
|
15
12
|
---
|
|
16
13
|
|
|
17
14
|
{
|
|
18
|
-
collections
|
|
15
|
+
collections.map((id) => (
|
|
19
16
|
<MediaCollection collectionId={id} disableItem={mediaId} />
|
|
20
17
|
))
|
|
21
18
|
}
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
---
|
|
2
|
-
import
|
|
2
|
+
import { SquarePenIcon } from "lucide-react"
|
|
3
|
+
import mediaItemEditButtonController from "virtual:lightnet/components/media-item-edit-button-controller"
|
|
4
|
+
|
|
5
|
+
if (!mediaItemEditButtonController) {
|
|
6
|
+
return
|
|
7
|
+
}
|
|
3
8
|
|
|
4
9
|
interface Props {
|
|
5
10
|
mediaId: string
|
|
@@ -11,18 +16,25 @@ const { mediaId } = Astro.props
|
|
|
11
16
|
<a
|
|
12
17
|
class="hidden cursor-pointer items-center gap-2 font-bold text-gray-700 underline"
|
|
13
18
|
id="edit-btn"
|
|
14
|
-
data-
|
|
15
|
-
href=
|
|
16
|
-
><
|
|
19
|
+
data-media-id={mediaId}
|
|
20
|
+
href="#"
|
|
21
|
+
><SquarePenIcon />
|
|
17
22
|
{Astro.locals.i18n.t("ln.details.edit")}</a
|
|
18
23
|
>
|
|
19
24
|
<script>
|
|
25
|
+
import mediaItemEditButtonController from "virtual:lightnet/components/media-item-edit-button-controller"
|
|
26
|
+
|
|
20
27
|
const btn: HTMLAnchorElement | null = document.querySelector("#edit-btn")
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
btn
|
|
26
|
-
|
|
28
|
+
const mediaId = btn?.dataset.mediaId
|
|
29
|
+
|
|
30
|
+
if (
|
|
31
|
+
mediaItemEditButtonController &&
|
|
32
|
+
btn &&
|
|
33
|
+
mediaId &&
|
|
34
|
+
mediaItemEditButtonController.shouldShow()
|
|
35
|
+
) {
|
|
36
|
+
btn.href = mediaItemEditButtonController.createHref(mediaId)
|
|
37
|
+
btn.classList.remove("hidden")
|
|
38
|
+
btn.classList.add("flex")
|
|
27
39
|
}
|
|
28
40
|
</script>
|
|
@@ -1,16 +1,21 @@
|
|
|
1
1
|
---
|
|
2
|
-
import
|
|
2
|
+
import { ExternalLinkIcon } from "lucide-react"
|
|
3
|
+
|
|
3
4
|
import { getMediaItem } from "../../../../content/get-media-items"
|
|
5
|
+
import type { TranslationMap } from "../../../../i18n/translate-map"
|
|
4
6
|
import { createContentMetadata } from "../../utils/create-content-metadata"
|
|
5
7
|
|
|
6
8
|
interface Props {
|
|
7
9
|
mediaId: string
|
|
8
|
-
openActionLabel:
|
|
10
|
+
openActionLabel: TranslationMap
|
|
9
11
|
className?: string
|
|
10
12
|
}
|
|
11
13
|
const { mediaId, openActionLabel, className } = Astro.props
|
|
14
|
+
const { tMap } = Astro.locals.i18n
|
|
12
15
|
const item = await getMediaItem(mediaId)
|
|
13
|
-
const content = createContentMetadata(item.data.content[0]
|
|
16
|
+
const content = createContentMetadata(item.data.content[0], tMap, {
|
|
17
|
+
path: ["media-items", mediaId, "content", 0],
|
|
18
|
+
})
|
|
14
19
|
---
|
|
15
20
|
|
|
16
21
|
<a
|
|
@@ -20,17 +25,17 @@ const content = createContentMetadata(item.data.content[0])
|
|
|
20
25
|
hreflang={item.data.language}
|
|
21
26
|
class:list={[className]}
|
|
22
27
|
>
|
|
23
|
-
{
|
|
24
|
-
content.isExternal && (
|
|
25
|
-
<Icon
|
|
26
|
-
ariaLabel={Astro.locals.i18n.t("ln.external-link")}
|
|
27
|
-
className={`shrink-0 mdi--external-link`}
|
|
28
|
-
/>
|
|
29
|
-
)
|
|
30
|
-
}
|
|
28
|
+
{content.isExternal && <ExternalLinkIcon className="shrink-0" />}
|
|
31
29
|
{
|
|
32
30
|
content.canBeOpened
|
|
33
|
-
?
|
|
31
|
+
? tMap(openActionLabel, {
|
|
32
|
+
path: [
|
|
33
|
+
"media-types",
|
|
34
|
+
item.data.type.id,
|
|
35
|
+
"detailsPage",
|
|
36
|
+
"openActionLabel",
|
|
37
|
+
],
|
|
38
|
+
})
|
|
34
39
|
: Astro.locals.i18n.t("ln.details.download")
|
|
35
40
|
}
|
|
36
41
|
</a>
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
---
|
|
2
|
-
import
|
|
2
|
+
import { ForwardIcon } from "lucide-react"
|
|
3
|
+
|
|
3
4
|
import Toast from "../../../../components/Toast"
|
|
4
5
|
|
|
5
6
|
interface Props {
|
|
@@ -11,7 +12,7 @@ interface Props {
|
|
|
11
12
|
class="flex cursor-pointer items-center gap-2 font-bold text-gray-700 underline"
|
|
12
13
|
class:list={[Astro.props.className]}
|
|
13
14
|
id="share-btn"
|
|
14
|
-
><
|
|
15
|
+
><ForwardIcon />
|
|
15
16
|
{Astro.locals.i18n.t("ln.details.share")}</button
|
|
16
17
|
>
|
|
17
18
|
<Toast id="share-success" variant="success">
|
|
@@ -10,7 +10,7 @@ interface Props {
|
|
|
10
10
|
|
|
11
11
|
const item = await getMediaItem(Astro.props.mediaId)
|
|
12
12
|
|
|
13
|
-
const { t } = Astro.locals.i18n
|
|
13
|
+
const { t, tMap, currentLocale } = Astro.locals.i18n
|
|
14
14
|
|
|
15
15
|
const categories = await Promise.all(
|
|
16
16
|
item.data.categories?.map(({ id }) => getCategory(id)) ?? [],
|
|
@@ -25,11 +25,13 @@ const categories = await Promise.all(
|
|
|
25
25
|
{categories.map(async (category) => (
|
|
26
26
|
<li class="flex rounded-lg bg-gray-200 px-4 py-1 text-gray-500 hover:bg-gray-300">
|
|
27
27
|
<a
|
|
28
|
-
href={searchPagePath(
|
|
28
|
+
href={searchPagePath(currentLocale, {
|
|
29
29
|
category: category.id,
|
|
30
30
|
})}
|
|
31
31
|
>
|
|
32
|
-
{
|
|
32
|
+
{tMap(category.label, {
|
|
33
|
+
path: ["categories", category.id, "label"],
|
|
34
|
+
})}
|
|
33
35
|
</a>
|
|
34
36
|
</li>
|
|
35
37
|
))}
|
|
@@ -14,23 +14,28 @@ const item = await getMediaItem(Astro.props.mediaId)
|
|
|
14
14
|
const { mediaId } = Astro.props
|
|
15
15
|
const translations = await getTranslations(mediaId)
|
|
16
16
|
|
|
17
|
-
const { t } = Astro.locals.i18n
|
|
17
|
+
const { t, tMap, currentLocale } = Astro.locals.i18n
|
|
18
|
+
const currentLanguage = resolveTranslatedLanguage(item.data.language, tMap)
|
|
19
|
+
const translationLanguages = await Promise.all(
|
|
20
|
+
translations.map(async (translation) => ({
|
|
21
|
+
...translation,
|
|
22
|
+
labelText: resolveTranslatedLanguage(translation.language, tMap).labelText,
|
|
23
|
+
})),
|
|
24
|
+
)
|
|
18
25
|
---
|
|
19
26
|
|
|
20
27
|
<div>
|
|
21
28
|
<Label>{translations.length ? t("ln.languages") : t("ln.language")}</Label>
|
|
22
29
|
<ul class="flex flex-wrap gap-2">
|
|
23
|
-
<li class="py-1 pe-2 text-gray-800">
|
|
24
|
-
{resolveTranslatedLanguage(item.data.language, t).labelText}
|
|
25
|
-
</li>
|
|
30
|
+
<li class="py-1 pe-2 text-gray-800">{currentLanguage.labelText}</li>
|
|
26
31
|
{
|
|
27
|
-
|
|
32
|
+
translationLanguages.map((translation) => (
|
|
28
33
|
<li class="flex rounded-lg border border-gray-200 px-4 py-1 text-gray-600 hover:bg-gray-200">
|
|
29
34
|
<a
|
|
30
|
-
href={detailsPagePath(
|
|
35
|
+
href={detailsPagePath(currentLocale, translation)}
|
|
31
36
|
hreflang={translation.language}
|
|
32
37
|
>
|
|
33
|
-
{
|
|
38
|
+
{translation.labelText}
|
|
34
39
|
</a>
|
|
35
40
|
</li>
|
|
36
41
|
))
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type TranslateMapFn,
|
|
3
|
+
type TranslationMap,
|
|
4
|
+
} from "../../../i18n/translate-map"
|
|
1
5
|
import { isExternalUrl } from "../../../utils/urls"
|
|
2
6
|
|
|
3
7
|
export type UrlType =
|
|
@@ -43,13 +47,17 @@ const KNOWN_EXTENSIONS: Record<
|
|
|
43
47
|
docx: { type: "text" },
|
|
44
48
|
} as const
|
|
45
49
|
|
|
46
|
-
export function createContentMetadata(
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
50
|
+
export function createContentMetadata(
|
|
51
|
+
{
|
|
52
|
+
url,
|
|
53
|
+
label: customLabel,
|
|
54
|
+
}: {
|
|
55
|
+
url: string
|
|
56
|
+
label?: TranslationMap
|
|
57
|
+
},
|
|
58
|
+
tMap: TranslateMapFn,
|
|
59
|
+
context: { path: (string | number)[] },
|
|
60
|
+
) {
|
|
53
61
|
const isExternal = isExternalUrl(url)
|
|
54
62
|
const path = isExternal ? new URL(url).pathname : url
|
|
55
63
|
|
|
@@ -63,7 +71,14 @@ export function createContentMetadata({
|
|
|
63
71
|
const fileName = hasExtension
|
|
64
72
|
? lastPathSegment.slice(0, -(extension.length + 1))
|
|
65
73
|
: undefined
|
|
66
|
-
|
|
74
|
+
|
|
75
|
+
const labelText =
|
|
76
|
+
(customLabel &&
|
|
77
|
+
tMap(customLabel, {
|
|
78
|
+
path: [...context.path, "label"],
|
|
79
|
+
})) ??
|
|
80
|
+
fileName ??
|
|
81
|
+
linkName
|
|
67
82
|
const type = KNOWN_EXTENSIONS[extension]?.type ?? "link"
|
|
68
83
|
const canBeOpened =
|
|
69
84
|
!hasExtension || !!KNOWN_EXTENSIONS[extension]?.canBeOpened
|
|
@@ -72,7 +87,7 @@ export function createContentMetadata({
|
|
|
72
87
|
url,
|
|
73
88
|
extension,
|
|
74
89
|
isExternal,
|
|
75
|
-
|
|
90
|
+
labelText,
|
|
76
91
|
canBeOpened,
|
|
77
92
|
type,
|
|
78
93
|
target: isExternal ? "_blank" : "_self",
|
|
@@ -3,6 +3,9 @@ import { getMediaItem, getMediaItems } from "../../../content/get-media-items"
|
|
|
3
3
|
const groupItemsByCommonId = async () => {
|
|
4
4
|
const items = await getMediaItems()
|
|
5
5
|
items.forEach(({ id, data: { commonId, language } }) => {
|
|
6
|
+
if (!commonId) {
|
|
7
|
+
return
|
|
8
|
+
}
|
|
6
9
|
if (!itemsByCommonId.has(commonId)) {
|
|
7
10
|
itemsByCommonId.set(commonId, [])
|
|
8
11
|
}
|
|
@@ -24,6 +27,9 @@ export const getTranslations = async (mediaId: string) => {
|
|
|
24
27
|
await groupItemsByCommonId()
|
|
25
28
|
}
|
|
26
29
|
const item = await getMediaItem(mediaId)
|
|
30
|
+
if (!item.data.commonId) {
|
|
31
|
+
return []
|
|
32
|
+
}
|
|
27
33
|
const sameCommonId = itemsByCommonId.get(item.data.commonId)
|
|
28
34
|
if (!sameCommonId) {
|
|
29
35
|
return []
|
|
@@ -1,8 +1,11 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { ChevronRightIcon } from "lucide-react"
|
|
2
|
+
|
|
2
3
|
import { useI18n } from "../../../i18n/react/use-i18n"
|
|
3
4
|
|
|
4
5
|
export default function LoadingSkeleton() {
|
|
5
6
|
const { direction } = useI18n()
|
|
7
|
+
const iconDirectionClass = direction === "rtl" ? "scale-x-[-1]" : ""
|
|
8
|
+
|
|
6
9
|
return (
|
|
7
10
|
<ul>
|
|
8
11
|
{Array.from({ length: 8 }, (_, index) => (
|
|
@@ -16,10 +19,8 @@ export default function LoadingSkeleton() {
|
|
|
16
19
|
<div className="h-4 w-3/4 rounded-md bg-gray-200 md:h-6"></div>
|
|
17
20
|
<div className="h-4 w-5/6 rounded-md bg-gray-200 md:h-6"></div>
|
|
18
21
|
</div>
|
|
19
|
-
<
|
|
20
|
-
className=
|
|
21
|
-
flipIcon={direction === "rtl"}
|
|
22
|
-
ariaLabel=""
|
|
22
|
+
<ChevronRightIcon
|
|
23
|
+
className={`my-auto me-4 ms-2 hidden shrink-0 text-gray-300 sm:block ${iconDirectionClass}`}
|
|
23
24
|
/>
|
|
24
25
|
</li>
|
|
25
26
|
))}
|