lightnet 2.18.2 → 3.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.
Files changed (42) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/__e2e__/fixtures/basics/package.json +2 -2
  3. package/__e2e__/fixtures/basics/src/pages/[locale]/index.astro +7 -5
  4. package/exports/components.ts +5 -4
  5. package/exports/experimental-details-page.ts +12 -0
  6. package/package.json +1 -1
  7. package/src/astro-integration/config.ts +7 -0
  8. package/src/astro-integration/integration.ts +1 -1
  9. package/src/astro-integration/virtual.d.ts +5 -0
  10. package/src/astro-integration/vite-plugin-lightnet-config.ts +11 -1
  11. package/src/components/{Gallery.astro → MediaGallery.astro} +6 -3
  12. package/src/components/MediaGallerySection.astro +27 -0
  13. package/src/components/{MediaItemList.astro → MediaList.astro} +18 -4
  14. package/src/components/Section.astro +29 -11
  15. package/src/{pages/details-page/components → components}/VideoPlayer.astro +40 -27
  16. package/src/layouts/Page.astro +2 -0
  17. package/src/pages/details-page/DefaultDetailsPage.astro +26 -0
  18. package/src/pages/details-page/DetailsPage.astro +16 -9
  19. package/src/pages/details-page/VideoDetailsPage.astro +19 -0
  20. package/src/pages/details-page/components/{Content.astro → ContentSection.astro} +3 -3
  21. package/src/pages/details-page/components/{Description.astro → DescriptionSection.astro} +3 -3
  22. package/src/pages/details-page/components/MainDetailsSection.astro +26 -0
  23. package/src/pages/details-page/components/MediaCollection.astro +4 -4
  24. package/src/pages/details-page/components/{MediaCollections.astro → MediaCollectionsSection.astro} +4 -4
  25. package/src/pages/details-page/components/MoreDetailsSection.astro +17 -0
  26. package/src/pages/details-page/components/VideoDetailsSection.astro +30 -0
  27. package/src/pages/details-page/components/{Authors.astro → main-details/Authors.astro} +3 -3
  28. package/src/pages/details-page/components/{Cover.astro → main-details/Cover.astro} +4 -4
  29. package/src/pages/details-page/components/{OpenButton.astro → main-details/OpenButton.astro} +6 -6
  30. package/src/pages/details-page/components/{ShareButton.astro → main-details/ShareButton.astro} +1 -1
  31. package/src/pages/details-page/components/main-details/Title.astro +28 -0
  32. package/src/pages/details-page/components/{details → more-details}/Categories.astro +2 -2
  33. package/src/pages/details-page/components/{details → more-details}/Languages.astro +5 -4
  34. package/exports/details-page.ts +0 -1
  35. package/src/pages/details-page/DefaultDetails.astro +0 -44
  36. package/src/pages/details-page/VideoDetails.astro +0 -43
  37. package/src/pages/details-page/components/SectionTitle.astro +0 -8
  38. package/src/pages/details-page/components/Title.astro +0 -18
  39. package/src/pages/details-page/components/details/Details.astro +0 -17
  40. /package/src/components/{CategoriesOverview.astro → CategoriesSection.astro} +0 -0
  41. /package/src/components/{Hero.astro → HeroSection.astro} +0 -0
  42. /package/src/pages/details-page/components/{details → more-details}/Label.astro +0 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,34 @@
1
1
  # lightnet
2
2
 
3
+ ## 3.0.0
4
+
5
+ ### Major Changes
6
+
7
+ - [#234](https://github.com/LightNetDev/LightNet/pull/234) [`1cb0dc0`](https://github.com/LightNetDev/LightNet/commit/1cb0dc0ef2d7a3ecb5d2a37a36f2f2e8fa97a756) Thanks [@smn-cds](https://github.com/smn-cds)! - Renames custom details pages prop `slug` to `mediaId`.
8
+
9
+ - [#234](https://github.com/LightNetDev/LightNet/pull/234) [`1cb0dc0`](https://github.com/LightNetDev/LightNet/commit/1cb0dc0ef2d7a3ecb5d2a37a36f2f2e8fa97a756) Thanks [@smn-cds](https://github.com/smn-cds)! - Streamline handling of components. Make sure all components including a section are suffixed with `Section`.
10
+
11
+ - Removes `Gallery` export
12
+ - Adds `MediaGallerySection` that includes the section component
13
+ - Renames `Hero` to `HeroSection`
14
+ - Renames `CategoriesOverview` to `CategoriesSection`
15
+ - Renames `MediaItemList` to `MediaList`
16
+
17
+ - [#234](https://github.com/LightNetDev/LightNet/pull/234) [`1cb0dc0`](https://github.com/LightNetDev/LightNet/commit/1cb0dc0ef2d7a3ecb5d2a37a36f2f2e8fa97a756) Thanks [@smn-cds](https://github.com/smn-cds)! - Improve Section
18
+
19
+ - rename maxWidth values: `full` => `wide`, `prose` => `narrow`
20
+ - add marginTop settings: `none`, `sm`, `lg`
21
+
22
+ ### Minor Changes
23
+
24
+ - [#234](https://github.com/LightNetDev/LightNet/pull/234) [`1cb0dc0`](https://github.com/LightNetDev/LightNet/commit/1cb0dc0ef2d7a3ecb5d2a37a36f2f2e8fa97a756) Thanks [@smn-cds](https://github.com/smn-cds)! - Exports experimental details page components
25
+
26
+ - [#234](https://github.com/LightNetDev/LightNet/pull/234) [`1cb0dc0`](https://github.com/LightNetDev/LightNet/commit/1cb0dc0ef2d7a3ecb5d2a37a36f2f2e8fa97a756) Thanks [@smn-cds](https://github.com/smn-cds)! - Accept undefined media items on Gallery and List component.
27
+
28
+ - [#234](https://github.com/LightNetDev/LightNet/pull/234) [`1cb0dc0`](https://github.com/LightNetDev/LightNet/commit/1cb0dc0ef2d7a3ecb5d2a37a36f2f2e8fa97a756) Thanks [@smn-cds](https://github.com/smn-cds)! - Add VideoPlayer component
29
+
30
+ - [#234](https://github.com/LightNetDev/LightNet/pull/234) [`1cb0dc0`](https://github.com/LightNetDev/LightNet/commit/1cb0dc0ef2d7a3ecb5d2a37a36f2f2e8fa97a756) Thanks [@smn-cds](https://github.com/smn-cds)! - Add support for custom head components.
31
+
3
32
  ## 2.18.2
4
33
 
5
34
  ### Patch Changes
@@ -6,9 +6,9 @@
6
6
  "dependencies": {
7
7
  "@astrojs/react": "^4.2.1",
8
8
  "@astrojs/tailwind": "^6.0.0",
9
- "@lightnet/decap-admin": "^2.4.2",
9
+ "@lightnet/decap-admin": "^3.0.0",
10
10
  "astro": "^5.5.3",
11
- "lightnet": "^2.18.0",
11
+ "lightnet": "^3.0.0",
12
12
  "react": "^19.0.0",
13
13
  "react-dom": "^19.0.0",
14
14
  "sharp": "^0.33.5",
@@ -1,14 +1,16 @@
1
1
  ---
2
2
  export { getLocalePaths as getStaticPaths } from "lightnet/i18n"
3
3
 
4
- import { Gallery, Page, Section } from "lightnet/components"
4
+ import { MediaGallerySection, Page } from "lightnet/components"
5
5
  import { getMediaItems } from "lightnet/content"
6
6
 
7
- const allItems = await getMediaItems({})
7
+ const allItems = await getMediaItems()
8
8
  ---
9
9
 
10
10
  <Page>
11
- <Section title={Astro.locals.i18n.t("home.all-items")}>
12
- <Gallery items={allItems} layout="book" />
13
- </Section>
11
+ <MediaGallerySection
12
+ title={Astro.locals.i18n.t("home.all-items")}
13
+ items={allItems}
14
+ layout="book"
15
+ />
14
16
  </Page>
@@ -1,9 +1,10 @@
1
- export { default as CategoriesOverview } from "../src/components/CategoriesOverview.astro"
2
- export { default as Gallery } from "../src/components/Gallery.astro"
3
- export { default as Hero } from "../src/components/Hero.astro"
1
+ export { default as CategoriesSection } from "../src/components/CategoriesSection.astro"
2
+ export { default as HeroSection } from "../src/components/HeroSection.astro"
4
3
  export { default as HighlightSection } from "../src/components/HighlightSection.astro"
5
4
  export { default as Icon } from "../src/components/Icon"
6
- export { default as MediaItemList } from "../src/components/MediaItemList.astro"
5
+ export { default as MediaGallerySection } from "../src/components/MediaGallerySection.astro"
6
+ export { default as MediaList } from "../src/components/MediaList.astro"
7
7
  export { default as Section } from "../src/components/Section.astro"
8
+ export { default as VideoPlayer } from "../src/components/VideoPlayer.astro"
8
9
  export { default as MarkdownPage } from "../src/layouts/MarkdownPage.astro"
9
10
  export { default as Page } from "../src/layouts/Page.astro"
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Details page components are still experimental.
3
+ * We might break the API without updating the major version.
4
+ */
5
+ export { default as ContentSection } from "../src/pages/details-page/components/ContentSection.astro"
6
+ export { default as DescriptionSection } from "../src/pages/details-page/components/DescriptionSection.astro"
7
+ export { default as OpenButton } from "../src/pages/details-page/components/main-details/OpenButton.astro"
8
+ export { default as ShareButton } from "../src/pages/details-page/components/main-details/ShareButton.astro"
9
+ export { default as MainDetailsSection } from "../src/pages/details-page/components/MainDetailsSection.astro"
10
+ export { default as MediaCollectionsSection } from "../src/pages/details-page/components/MediaCollectionsSection.astro"
11
+ export { default as MoreDetailsSection } from "../src/pages/details-page/components/MoreDetailsSection.astro"
12
+ export { default as VideoDetailsSection } from "../src/pages/details-page/components/VideoDetailsSection.astro"
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "lightnet",
3
3
  "type": "module",
4
4
  "license": "MIT",
5
- "version": "2.18.2",
5
+ "version": "3.0.0",
6
6
  "repository": {
7
7
  "type": "git",
8
8
  "url": "https://github.com/LightNetDev/lightnet",
@@ -179,6 +179,13 @@ export const configSchema = z.object({
179
179
  * - Ensure that only trusted and necessary domains are included in this list.
180
180
  */
181
181
  internalDomains: z.array(z.string()).default([]),
182
+ /**
183
+ * Path to an Astro component to be added to the HTML head element of all pages.
184
+ * For example use this if you need to add an analytics script to every page.
185
+ *
186
+ * @example "./src/components/MyHeadTag.astro"
187
+ */
188
+ headComponent: z.string().optional(),
182
189
  /**
183
190
  * Configure search page behavior
184
191
  */
@@ -51,7 +51,7 @@ export function lightnet(lightnetConfig: LightnetConfig): AstroIntegration {
51
51
  })
52
52
 
53
53
  injectRoute({
54
- pattern: "/[locale]/media/[slug]",
54
+ pattern: "/[locale]/media/[mediaId]",
55
55
  entrypoint: "lightnet/pages/DetailsPage.astro",
56
56
  prerender: true,
57
57
  })
@@ -12,3 +12,8 @@ declare module "virtual:lightnet/project-context" {
12
12
  const context: import("./project-context").ProjectContext
13
13
  export default context
14
14
  }
15
+
16
+ declare module "virtual:lightnet/components/CustomHeadComponent" {
17
+ const CustomHeadComponent: ((props: Record<string, any>) => any) | undefined
18
+ export default CustomHeadComponent
19
+ }
@@ -8,8 +8,14 @@ import { type LightnetConfig } from "./config"
8
8
  const CONFIG = "virtual:lightnet/config"
9
9
  const LOGO = "virtual:lightnet/logo"
10
10
  const PROJECT_CONTEXT = "virtual:lightnet/project-context"
11
+ const CUSTOM_HEAD_COMPONENT = "virtual:lightnet/components/CustomHeadComponent"
11
12
 
12
- const VIRTUAL_MODULES = [CONFIG, LOGO, PROJECT_CONTEXT] as const
13
+ const VIRTUAL_MODULES = [
14
+ CONFIG,
15
+ LOGO,
16
+ PROJECT_CONTEXT,
17
+ CUSTOM_HEAD_COMPONENT,
18
+ ] as const
13
19
 
14
20
  export function vitePluginLightnetConfig(
15
21
  config: LightnetConfig,
@@ -46,6 +52,10 @@ export function vitePluginLightnetConfig(
46
52
  : "export default undefined;"
47
53
  case PROJECT_CONTEXT:
48
54
  return `export default ${JSON.stringify({ root, srcDir, site })}`
55
+ case CUSTOM_HEAD_COMPONENT:
56
+ return config.headComponent
57
+ ? `export { default } from ${resolveFilePath(config.headComponent)};`
58
+ : "export default undefined;"
49
59
  }
50
60
  },
51
61
  }
@@ -6,7 +6,7 @@ import { getMediaTypes } from "../content/get-media-types"
6
6
  import { detailsPagePath } from "../utils/paths"
7
7
  import Icon from "./Icon"
8
8
 
9
- type GalleryItem = {
9
+ type MediaItem = {
10
10
  id: string
11
11
  data: {
12
12
  title: string
@@ -25,11 +25,14 @@ const types = Object.fromEntries(
25
25
  )
26
26
 
27
27
  interface Props {
28
- items: GalleryItem[]
28
+ items: (MediaItem | undefined)[]
29
29
  layout: "book" | "video" | "portrait" | "landscape"
30
30
  }
31
31
 
32
- const { items, layout } = Astro.props
32
+ const { items: itemsInput, layout } = Astro.props
33
+ // We allow users to pass undefined values because
34
+ // this is in the return type of getEntry
35
+ const items = itemsInput.filter((item) => !!item)
33
36
  ---
34
37
 
35
38
  {
@@ -0,0 +1,27 @@
1
+ ---
2
+ import type { ImageMetadata } from "astro"
3
+
4
+ import MediaGallery from "./MediaGallery.astro"
5
+ import Section from "./Section.astro"
6
+
7
+ type MediaItem = {
8
+ id: string
9
+ data: {
10
+ title: string
11
+ type: { id: string }
12
+ image: ImageMetadata
13
+ }
14
+ }
15
+
16
+ interface Props {
17
+ title?: string
18
+ items: (MediaItem | undefined)[]
19
+ layout: "book" | "video" | "portrait" | "landscape"
20
+ }
21
+
22
+ const { title, items, layout } = Astro.props
23
+ ---
24
+
25
+ <Section title={title}>
26
+ <MediaGallery items={items} layout={layout} />
27
+ </Section>
@@ -1,16 +1,30 @@
1
1
  ---
2
+ import type { ImageMetadata } from "astro"
2
3
  import { Image } from "astro:assets"
3
4
 
4
- import type { MediaItemEntry } from "../content/content-schema-internal"
5
5
  import { getMediaTypes } from "../content/get-media-types"
6
6
  import { detailsPagePath } from "../utils/paths"
7
7
  import Icon from "./Icon"
8
8
 
9
+ type MediaItem = {
10
+ id: string
11
+ disabled?: boolean
12
+ data: {
13
+ title: string
14
+ language: string
15
+ authors?: string[]
16
+ type: { id: string }
17
+ image: ImageMetadata
18
+ }
19
+ }
20
+
9
21
  interface Props {
10
- items: (MediaItemEntry & { disabled?: boolean })[]
22
+ items: (MediaItem | undefined)[]
11
23
  }
12
24
 
13
- const { items } = Astro.props
25
+ // We allow users to pass undefined values because
26
+ // this is in the return type of getEntry
27
+ const items = Astro.props.items.filter((item) => !!item)
14
28
 
15
29
  const { t, direction } = Astro.locals.i18n
16
30
 
@@ -38,7 +52,7 @@ const mediaTypes = Object.fromEntries(
38
52
  id: item.id,
39
53
  })
40
54
  }
41
- class="group flex overflow-hidden py-4 transition-colors ease-in-out md:rounded-sm md:py-8 md:hover:bg-gray-100"
55
+ class="group flex overflow-hidden py-4 transition-colors ease-in-out md:rounded-sm md:py-6 md:hover:bg-gray-100"
42
56
  class:list={
43
57
  item.disabled ? "pointer-events-none cursor-default opacity-50" : ""
44
58
  }
@@ -6,15 +6,21 @@ interface Props {
6
6
  */
7
7
  id?: string
8
8
  /**
9
- * Maximum width of the section container.
9
+ * Maximum width of the section container. This limits how wide the section grows
10
+ * on bigger screens. After reaching this width the section will be centered horizontally.
10
11
  *
11
- * - "full" takes the full available width of the LightNet page. This is not equal to the screen width.
12
- * For example the full width typically is used on the homepage.
13
- * - "prose" width ensures text is well readable. This is the width used on the details page.
12
+ * - "wide" (1280 px) is the maximum homepage width.
13
+ * - "narrow" (768 px) is the maximum details page width. Optimized for good text readability.
14
14
  *
15
- * @default "full"
15
+ * @default "wide"
16
16
  */
17
- maxWidth?: "full" | "prose"
17
+ maxWidth?: "wide" | "narrow"
18
+ /**
19
+ * Gap to the component above.
20
+ *
21
+ * @default "lg"
22
+ */
23
+ marginTop?: "sm" | "lg" | "none"
18
24
  /**
19
25
  * Title on top of the section.
20
26
  */
@@ -25,17 +31,29 @@ interface Props {
25
31
  className?: string
26
32
  }
27
33
 
28
- const { id, maxWidth = "full", title, className } = Astro.props
34
+ const {
35
+ id,
36
+ maxWidth = "wide",
37
+ marginTop = "lg",
38
+ title,
39
+ className,
40
+ } = Astro.props
29
41
 
30
42
  const maxWidths = {
31
- full: "max-w-screen-xl",
32
- prose: "max-w-screen-md",
43
+ wide: "max-w-screen-xl",
44
+ narrow: "max-w-screen-md",
45
+ }
46
+
47
+ const marginTopValues = {
48
+ none: "",
49
+ sm: "mt-16 md:mt-20",
50
+ lg: "mt-24 md:mt-28",
33
51
  }
34
52
  ---
35
53
 
36
54
  <section
37
- class="mx-auto mt-24 px-4 md:mt-28 md:px-8"
38
- class:list={[maxWidths[maxWidth], className]}
55
+ class="mx-auto px-4 md:px-8"
56
+ class:list={[maxWidths[maxWidth], marginTopValues[marginTop], className]}
39
57
  id={id}
40
58
  >
41
59
  {
@@ -3,9 +3,18 @@ import type { ImageMetadata } from "astro"
3
3
  import { getImage } from "astro:assets"
4
4
 
5
5
  interface Props {
6
+ /**
7
+ * Url of the video. This can be YouTube, vimeo, or a mp4 file.
8
+ */
6
9
  url: string
7
- title: string
8
- image: ImageMetadata
10
+ /**
11
+ * Title attribute of the video. Used for screen readers.
12
+ */
13
+ title?: string
14
+ /**
15
+ * Poster image to use for the mp4 video player.
16
+ */
17
+ image?: ImageMetadata
9
18
  }
10
19
 
11
20
  const { url, title, image: imageMetadata } = Astro.props
@@ -45,34 +54,38 @@ async function parseUrl(urlToParse: string): Promise<{
45
54
 
46
55
  // https://domain.com/video.mp4
47
56
  if (url.pathname.endsWith(".mp4")) {
48
- const image = (await getImage({ src: imageMetadata, format: "webp" })).src
57
+ const image =
58
+ imageMetadata &&
59
+ (await getImage({ src: imageMetadata, format: "webp" })).src
49
60
  return { host: "mp4", id: url.toString(), image }
50
61
  }
51
62
  throw Error(`Unsupported video url: ${urlToParse}`)
52
63
  }
53
64
  ---
54
65
 
55
- {
56
- host === "youtube" ? (
57
- <iframe
58
- class="h-full w-full"
59
- src={`https://www.youtube-nocookie.com/embed/${id}`}
60
- title={title}
61
- allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
62
- referrerpolicy="strict-origin-when-cross-origin"
63
- allowfullscreen
64
- />
65
- ) : host === "vimeo" ? (
66
- <iframe
67
- class="h-full w-full"
68
- src={`https://player.vimeo.com/video/${id}`}
69
- allow="autoplay; fullscreen; picture-in-picture"
70
- allowfullscreen
71
- />
72
- ) : host === "mp4" ? (
73
- <video class="h-full w-full" controls preload="auto" poster={image}>
74
- <source src={id} type="video/mp4" />
75
- Your browser does not support the video tag.
76
- </video>
77
- ) : null
78
- }
66
+ <div class="aspect-video w-full overflow-hidden bg-black md:rounded-lg">
67
+ {
68
+ host === "youtube" ? (
69
+ <iframe
70
+ class="h-full w-full"
71
+ src={`https://www.youtube-nocookie.com/embed/${id}`}
72
+ title={title}
73
+ allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
74
+ referrerpolicy="strict-origin-when-cross-origin"
75
+ allowfullscreen
76
+ />
77
+ ) : host === "vimeo" ? (
78
+ <iframe
79
+ class="h-full w-full"
80
+ title={title}
81
+ src={`https://player.vimeo.com/video/${id}`}
82
+ allow="autoplay; fullscreen; picture-in-picture"
83
+ allowfullscreen
84
+ />
85
+ ) : host === "mp4" ? (
86
+ <video class="h-full w-full" controls preload="auto" poster={image}>
87
+ <source src={id} type="video/mp4" />
88
+ </video>
89
+ ) : null
90
+ }
91
+ </div>
@@ -1,5 +1,6 @@
1
1
  ---
2
2
  import { ClientRouter } from "astro:transitions"
3
+ import CustomHeadComponent from "virtual:lightnet/components/CustomHeadComponent"
3
4
  import config from "virtual:lightnet/config"
4
5
 
5
6
  import { resolveLanguage } from "../i18n/resolve-language"
@@ -25,6 +26,7 @@ const language = resolveLanguage(currentLocale)
25
26
  <head>
26
27
  <meta charset="UTF-8" />
27
28
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
29
+ {CustomHeadComponent && <CustomHeadComponent />}
28
30
  <title>{title ? `${title} | ${configTitle}` : configTitle}</title>
29
31
  {description && <meta name="description" content={description} />}
30
32
  {config.manifest && <link rel="manifest" href={config.manifest} />}
@@ -0,0 +1,26 @@
1
+ ---
2
+ import ContentSection from "./components/ContentSection.astro"
3
+ import DescriptionSection from "./components/DescriptionSection.astro"
4
+ import OpenButton from "./components/main-details/OpenButton.astro"
5
+ import ShareButton from "./components/main-details/ShareButton.astro"
6
+ import MainDetailsSection from "./components/MainDetailsSection.astro"
7
+ import MediaCollectionsSection from "./components/MediaCollectionsSection.astro"
8
+ import MoreDetailsSection from "./components/MoreDetailsSection.astro"
9
+
10
+ export type Props = {
11
+ mediaId: string
12
+ coverStyle?: "default" | "book"
13
+ openActionLabel?: string
14
+ }
15
+
16
+ const { mediaId, coverStyle, openActionLabel = "ln.details.open" } = Astro.props
17
+ ---
18
+
19
+ <MainDetailsSection mediaId={mediaId} coverStyle={coverStyle}>
20
+ <OpenButton mediaId={mediaId} openActionLabel={openActionLabel} />
21
+ <ShareButton />
22
+ </MainDetailsSection>
23
+ <DescriptionSection mediaId={mediaId} />
24
+ <ContentSection mediaId={mediaId} />
25
+ <MediaCollectionsSection mediaId={mediaId} />
26
+ <MoreDetailsSection mediaId={mediaId} />
@@ -8,18 +8,18 @@ import { getMediaType } from "../../content/get-media-types"
8
8
  import { resolveLocales } from "../../i18n/resolve-locales"
9
9
  import Page from "../../layouts/Page.astro"
10
10
  import { markdownToText } from "../../utils/markdown"
11
- import DefaultDetails from "./DefaultDetails.astro"
12
- import VideoDetails from "./VideoDetails.astro"
11
+ import DefaultDetailsPage from "./DefaultDetailsPage.astro"
12
+ import VideoDetailsPage from "./VideoDetailsPage.astro"
13
13
 
14
14
  export const getStaticPaths = (async () => {
15
15
  const mediaItems = await getMediaItems()
16
16
  return resolveLocales(config).flatMap((locale) =>
17
- mediaItems.map(({ id: slug }) => ({ params: { slug, locale } })),
17
+ mediaItems.map(({ id: mediaId }) => ({ params: { mediaId, locale } })),
18
18
  )
19
19
  }) satisfies GetStaticPaths
20
20
 
21
- const { slug } = Astro.params
22
- const mediaItem = (await getMediaItem(Astro.params.slug)).data
21
+ const { mediaId } = Astro.params
22
+ const mediaItem = (await getMediaItem(Astro.params.mediaId)).data
23
23
 
24
24
  const {
25
25
  data: { detailsPage },
@@ -31,7 +31,6 @@ if (detailsPage?.layout === "custom") {
31
31
  const d = import.meta.glob("/src/details-pages/*.astro")
32
32
  const customDetailsImport = d[
33
33
  `/src/details-pages/${detailsPage.customComponent}`
34
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
35
34
  ] as () => Promise<any>
36
35
  if (!customDetailsImport) {
37
36
  throw new AstroError(
@@ -47,7 +46,15 @@ if (detailsPage?.layout === "custom") {
47
46
  title={mediaItem.title}
48
47
  description={markdownToText(mediaItem.description)}
49
48
  >
50
- {layout === "default" && <DefaultDetails slug={slug} {...detailsPage} />}
51
- {layout === "video" && <VideoDetails slug={slug} {...detailsPage} />}
52
- {CustomDetails && <CustomDetails slug={slug} {...detailsPage} />}
49
+ {
50
+ layout === "default" && (
51
+ <DefaultDetailsPage mediaId={mediaId} {...detailsPage} />
52
+ )
53
+ }
54
+ {
55
+ layout === "video" && (
56
+ <VideoDetailsPage mediaId={mediaId} {...detailsPage} />
57
+ )
58
+ }
59
+ {CustomDetails && <CustomDetails mediaId={mediaId} {...detailsPage} />}
53
60
  </Page>
@@ -0,0 +1,19 @@
1
+ ---
2
+ import ContentSection from "./components/ContentSection.astro"
3
+ import DescriptionSection from "./components/DescriptionSection.astro"
4
+ import MediaCollectionsSection from "./components/MediaCollectionsSection.astro"
5
+ import MoreDetailsSection from "./components/MoreDetailsSection.astro"
6
+ import VideoDetailsSection from "./components/VideoDetailsSection.astro"
7
+
8
+ export type Props = {
9
+ mediaId: string
10
+ }
11
+
12
+ const { mediaId } = Astro.props
13
+ ---
14
+
15
+ <VideoDetailsSection mediaId={mediaId} />
16
+ <DescriptionSection mediaId={mediaId} />
17
+ <ContentSection mediaId={mediaId} />
18
+ <MediaCollectionsSection mediaId={mediaId} />
19
+ <MoreDetailsSection mediaId={mediaId} />
@@ -7,10 +7,10 @@ import {
7
7
  } from "../utils/create-content-metadata"
8
8
 
9
9
  interface Props {
10
- slug: string
10
+ mediaId: string
11
11
  }
12
12
 
13
- const item = await getMediaItem(Astro.props.slug)
13
+ const item = await getMediaItem(Astro.props.mediaId)
14
14
 
15
15
  if (item.data.content.length < 2) {
16
16
  return
@@ -31,7 +31,7 @@ const { t, direction } = Astro.locals.i18n
31
31
  ---
32
32
 
33
33
  <ol
34
- class="mx-auto mt-20 max-w-screen-md overflow-hidden bg-gray-200 md:mt-24 md:rounded-xl"
34
+ class="mx-auto mt-16 max-w-screen-md overflow-hidden bg-gray-200 md:mt-20 md:rounded-xl"
35
35
  >
36
36
  {
37
37
  content.map(
@@ -4,9 +4,9 @@ import { resolveLanguage } from "../../../i18n/resolve-language"
4
4
  import { markdownToHtml } from "../../../utils/markdown"
5
5
 
6
6
  interface Props {
7
- slug: string
7
+ mediaId: string
8
8
  }
9
- const item = await getMediaItem(Astro.props.slug)
9
+ const item = await getMediaItem(Astro.props.mediaId)
10
10
 
11
11
  const { description, language } = item.data
12
12
 
@@ -17,7 +17,7 @@ const { direction, code: lang } = resolveLanguage(language)
17
17
  description && (
18
18
  <div
19
19
  dir={direction}
20
- class="prose prose-gray mx-auto mt-10 max-w-screen-md px-4 sm:mt-14 md:px-8"
20
+ class="prose prose-gray mx-auto mt-16 max-w-screen-md px-4 md:mt-20 md:px-8"
21
21
  set:html={await markdownToHtml(description)}
22
22
  lang={lang}
23
23
  id="description"
@@ -0,0 +1,26 @@
1
+ ---
2
+ import Authors from "./main-details/Authors.astro"
3
+ import Cover from "./main-details/Cover.astro"
4
+ import Title from "./main-details/Title.astro"
5
+
6
+ export type Props = {
7
+ mediaId: string
8
+ coverStyle?: "default" | "book"
9
+ }
10
+
11
+ const { mediaId, coverStyle = "default" } = Astro.props
12
+ ---
13
+
14
+ <div
15
+ class="mx-auto mt-10 flex max-w-screen-md flex-col items-center gap-8 px-4 sm:mt-20 sm:flex-row sm:items-start sm:gap-14 md:px-8"
16
+ >
17
+ <Cover mediaId={mediaId} style={coverStyle} />
18
+ <div class="flex flex-col items-center sm:items-start">
19
+ <Title className="text-center sm:text-start" mediaId={mediaId} />
20
+ <Authors className="text-center sm:text-start" mediaId={mediaId} />
21
+
22
+ <div class="mt-10 flex flex-col justify-center gap-4 sm:justify-start">
23
+ <slot />
24
+ </div>
25
+ </div>
26
+ </div>
@@ -2,7 +2,7 @@
2
2
  import { AstroError } from "astro/errors"
3
3
  import { getEntry } from "astro:content"
4
4
 
5
- import MediaItemList from "../../../components/MediaItemList.astro"
5
+ import MediaList from "../../../components/MediaList.astro"
6
6
  import { getMediaItems } from "../../../content/get-media-items"
7
7
  import { queryMediaItems } from "../../../content/query-media-items"
8
8
 
@@ -29,11 +29,11 @@ const t = Astro.locals.i18n.t
29
29
  ---
30
30
 
31
31
  <section class="mx-auto mt-16 max-w-screen-md px-4 md:mt-20 md:px-8">
32
- <h2 class="mb-3 text-xs font-bold uppercase text-gray-600">
32
+ <h2 class="mb-1 text-xs font-bold uppercase text-gray-600 md:mb-2">
33
33
  {t("ln.details.part-of-collection")}
34
34
  </h2>
35
- <h3 class="mb-2 text-lg font-bold text-gray-700">
35
+ <h3 class="text-lg font-bold text-gray-700">
36
36
  {t(collection.data.label)}
37
37
  </h3>
38
- <MediaItemList items={items} />
38
+ <MediaList items={items} />
39
39
  </section>
@@ -3,11 +3,11 @@ import { getMediaItem } from "../../../content/get-media-items"
3
3
  import MediaCollection from "./MediaCollection.astro"
4
4
 
5
5
  interface Props {
6
- slug: string
6
+ mediaId: string
7
7
  }
8
8
 
9
- const { slug } = Astro.props
10
- const item = await getMediaItem(slug)
9
+ const { mediaId } = Astro.props
10
+ const item = await getMediaItem(mediaId)
11
11
 
12
12
  const collections = item.data.collections?.map(
13
13
  ({ collection }) => collection.id,
@@ -16,6 +16,6 @@ const collections = item.data.collections?.map(
16
16
 
17
17
  {
18
18
  collections?.map((id) => (
19
- <MediaCollection collectionId={id} disableItem={slug} />
19
+ <MediaCollection collectionId={id} disableItem={mediaId} />
20
20
  ))
21
21
  }
@@ -0,0 +1,17 @@
1
+ ---
2
+ import Categories from "./more-details/Categories.astro"
3
+ import Languages from "./more-details/Languages.astro"
4
+
5
+ interface Props {
6
+ mediaId: string
7
+ }
8
+
9
+ const { mediaId } = Astro.props
10
+ ---
11
+
12
+ <div class="mx-auto mt-16 max-w-screen-md px-4 md:mt-20 md:px-8" id="details">
13
+ <div class="flex flex-col gap-4">
14
+ <Languages mediaId={mediaId} />
15
+ <Categories mediaId={mediaId} />
16
+ </div>
17
+ </div>
@@ -0,0 +1,30 @@
1
+ ---
2
+ import VideoPlayer from "../../../components/VideoPlayer.astro"
3
+ import { getMediaItem } from "../../../content/get-media-items"
4
+ import Authors from "./main-details/Authors.astro"
5
+ import ShareButton from "./main-details/ShareButton.astro"
6
+ import Title from "./main-details/Title.astro"
7
+
8
+ export type Props = {
9
+ mediaId: string
10
+ }
11
+
12
+ const { mediaId } = Astro.props
13
+
14
+ const item = await getMediaItem(mediaId)
15
+ ---
16
+
17
+ <div class="mx-auto max-w-screen-md md:mt-8 md:px-8">
18
+ <VideoPlayer
19
+ url={item.data.content[0].url!}
20
+ title={item.data.title}
21
+ image={item.data.image}
22
+ />
23
+ </div>
24
+ <div
25
+ class="mx-auto mt-6 flex max-w-screen-md flex-col items-start px-4 md:mt-10 md:px-8"
26
+ >
27
+ <Title mediaId={mediaId} />
28
+ <Authors mediaId={mediaId} />
29
+ <ShareButton className="mt-6 md:mt-10" />
30
+ </div>
@@ -1,12 +1,12 @@
1
1
  ---
2
- import { getMediaItem } from "../../../content/get-media-items"
2
+ import { getMediaItem } from "../../../../content/get-media-items"
3
3
 
4
4
  interface Props {
5
- slug: string
5
+ mediaId: string
6
6
  className?: string
7
7
  }
8
8
 
9
- const item = await getMediaItem(Astro.props.slug)
9
+ const item = await getMediaItem(Astro.props.mediaId)
10
10
  const authors = item.data.authors?.join(", ")
11
11
  ---
12
12
 
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  import { Image } from "astro:assets"
3
3
 
4
- import { getMediaItem } from "../../../content/get-media-items"
4
+ import { getMediaItem } from "../../../../content/get-media-items"
5
5
 
6
6
  interface Props {
7
- slug: string
7
+ mediaId: string
8
8
  style?: "default" | "book"
9
9
  }
10
- const { slug, style = "default" } = Astro.props
10
+ const { mediaId, style = "default" } = Astro.props
11
11
 
12
- const item = await getMediaItem(slug)
12
+ const item = await getMediaItem(mediaId)
13
13
  const image = item.data.image
14
14
  const isPortraitImage = image.height > image.width
15
15
  ---
@@ -1,14 +1,14 @@
1
1
  ---
2
- import Icon from "../../../components/Icon"
3
- import { getMediaItem } from "../../../content/get-media-items"
4
- import { createContentMetadata } from "../utils/create-content-metadata"
2
+ import Icon from "../../../../components/Icon"
3
+ import { getMediaItem } from "../../../../content/get-media-items"
4
+ import { createContentMetadata } from "../../utils/create-content-metadata"
5
5
 
6
6
  interface Props {
7
- slug: string
7
+ mediaId: string
8
8
  openActionLabel: string
9
9
  }
10
- const { slug, openActionLabel } = Astro.props
11
- const item = await getMediaItem(slug)
10
+ const { mediaId, openActionLabel } = Astro.props
11
+ const item = await getMediaItem(mediaId)
12
12
  const content = createContentMetadata(item.data.content[0])
13
13
  ---
14
14
 
@@ -1,5 +1,5 @@
1
1
  ---
2
- import Icon from "../../../components/Icon"
2
+ import Icon from "../../../../components/Icon"
3
3
 
4
4
  interface Props {
5
5
  className?: string
@@ -0,0 +1,28 @@
1
+ ---
2
+ import { getMediaItem } from "../../../../content/get-media-items"
3
+
4
+ interface Props {
5
+ mediaId: string
6
+ className?: string
7
+ }
8
+
9
+ const {
10
+ data: { title, language },
11
+ } = await getMediaItem(Astro.props.mediaId)
12
+
13
+ const titleSize = {
14
+ md: "text-3xl sm:text-4xl",
15
+ xl: "text-4xl sm:text-5xl",
16
+ }
17
+ ---
18
+
19
+ <h1
20
+ class="font-bold"
21
+ lang={language}
22
+ class:list={[
23
+ titleSize[title.length > 20 ? "md" : "xl"],
24
+ Astro.props.className,
25
+ ]}
26
+ >
27
+ {title}
28
+ </h1>
@@ -5,10 +5,10 @@ import { searchPagePath } from "../../../../utils/paths"
5
5
  import Label from "./Label.astro"
6
6
 
7
7
  interface Props {
8
- slug: string
8
+ mediaId: string
9
9
  }
10
10
 
11
- const item = await getMediaItem(Astro.props.slug)
11
+ const item = await getMediaItem(Astro.props.mediaId)
12
12
 
13
13
  const categories = item.data.categories?.map(({ id }) => id)
14
14
  const { t } = Astro.locals.i18n
@@ -8,15 +8,16 @@ import { detailsPagePath } from "../../../../utils/paths"
8
8
  import Label from "./Label.astro"
9
9
 
10
10
  interface Props {
11
- slug: string
11
+ mediaId: string
12
12
  }
13
13
 
14
- const item = await getMediaItem(Astro.props.slug)
14
+ const item = await getMediaItem(Astro.props.mediaId)
15
15
 
16
- const { slug } = Astro.props
16
+ const { mediaId } = Astro.props
17
17
  const translations = (await getMediaItems())
18
18
  .filter(
19
- (entry) => entry.data.commonId === item.data.commonId && entry.id !== slug,
19
+ (entry) =>
20
+ entry.data.commonId === item.data.commonId && entry.id !== mediaId,
20
21
  )
21
22
  .sort((a, b) => a.data.language.localeCompare(b.data.language))
22
23
 
@@ -1 +0,0 @@
1
- export { default as MediaCollections } from "../src/pages/details-page/components/MediaCollections.astro"
@@ -1,44 +0,0 @@
1
- ---
2
- import Authors from "./components/Authors.astro"
3
- import Content from "./components/Content.astro"
4
- import Cover from "./components/Cover.astro"
5
- import Description from "./components/Description.astro"
6
- import Details from "./components/details/Details.astro"
7
- import MediaCollections from "./components/MediaCollections.astro"
8
- import OpenButton from "./components/OpenButton.astro"
9
- import ShareButton from "./components/ShareButton.astro"
10
- import Title from "./components/Title.astro"
11
-
12
- export type Props = {
13
- slug: string
14
- coverStyle?: "default" | "book"
15
- openActionLabel?: string
16
- }
17
-
18
- const {
19
- slug,
20
- coverStyle = "default",
21
- openActionLabel = "ln.details.open",
22
- } = Astro.props
23
- ---
24
-
25
- <div
26
- class="mx-auto mt-10 flex max-w-screen-md flex-col items-center gap-8 px-4 sm:mt-20 sm:flex-row sm:items-start sm:gap-14 md:px-8"
27
- >
28
- <Cover slug={slug} style={coverStyle} />
29
- <div class="flex flex-col items-center sm:items-start">
30
- <Title className="text-center sm:text-start" slug={slug} />
31
- <Authors className="text-center sm:text-start" slug={slug} />
32
-
33
- <div
34
- class="mt-8 flex flex-col justify-center gap-4 sm:justify-start md:mt-14"
35
- >
36
- <OpenButton slug={slug} openActionLabel={openActionLabel} />
37
- <ShareButton />
38
- </div>
39
- </div>
40
- </div>
41
- <Description slug={slug} />
42
- <Content slug={slug} />
43
- <MediaCollections slug={slug} />
44
- <Details slug={slug} />
@@ -1,43 +0,0 @@
1
- ---
2
- import { getMediaItem } from "../../content/get-media-items"
3
- import Authors from "./components/Authors.astro"
4
- import Content from "./components/Content.astro"
5
- import Description from "./components/Description.astro"
6
- import Details from "./components/details/Details.astro"
7
- import MediaCollections from "./components/MediaCollections.astro"
8
- import ShareButton from "./components/ShareButton.astro"
9
- import Title from "./components/Title.astro"
10
- import VideoPlayer from "./components/VideoPlayer.astro"
11
-
12
- export type Props = {
13
- slug: string
14
- }
15
-
16
- const { slug } = Astro.props
17
-
18
- const item = await getMediaItem(slug)
19
- ---
20
-
21
- <div class="mx-auto max-w-screen-md md:px-8">
22
- <div
23
- class="aspect-video w-full overflow-hidden bg-black md:mt-8 md:rounded-lg"
24
- >
25
- <VideoPlayer
26
- url={item.data.content[0].url!}
27
- title={item.data.title}
28
- image={item.data.image}
29
- />
30
- </div>
31
- </div>
32
- <div
33
- class="mx-auto mt-12 flex max-w-screen-md flex-col items-start px-4 md:mt-14 md:px-8"
34
- >
35
- <Title slug={slug} />
36
- <Authors slug={slug} />
37
- <ShareButton className="mt-8" />
38
- </div>
39
-
40
- <Description slug={slug} />
41
- <Content slug={slug} />
42
- <MediaCollections slug={slug} />
43
- <Details slug={slug} />
@@ -1,8 +0,0 @@
1
- ---
2
- interface Props {
3
- text: string
4
- }
5
- const { text } = Astro.props
6
- ---
7
-
8
- <h2 class="mb-2 text-xs font-bold uppercase text-gray-600">{text}</h2>
@@ -1,18 +0,0 @@
1
- ---
2
- import { getMediaItem } from "../../../content/get-media-items"
3
-
4
- interface Props {
5
- slug: string
6
- className?: string
7
- }
8
-
9
- const item = await getMediaItem(Astro.props.slug)
10
- ---
11
-
12
- <h1
13
- class="text-4xl font-bold sm:text-5xl"
14
- lang={item.data.language}
15
- class:list={Astro.props.className}
16
- >
17
- {item.data.title}
18
- </h1>
@@ -1,17 +0,0 @@
1
- ---
2
- import Categories from "./Categories.astro"
3
- import Languages from "./Languages.astro"
4
-
5
- interface Props {
6
- slug: string
7
- }
8
-
9
- const { slug } = Astro.props
10
- ---
11
-
12
- <div class="mx-auto mt-20 max-w-screen-md px-4 sm:mt-24 md:px-8" id="details">
13
- <div class="flex flex-col gap-4">
14
- <Languages slug={slug} />
15
- <Categories slug={slug} />
16
- </div>
17
- </div>