lightnet 3.10.4 → 3.10.6

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 (45) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/__e2e__/admin.spec.ts +93 -53
  3. package/__e2e__/{test-utils.ts → basics-fixture.ts} +20 -23
  4. package/__e2e__/fixtures/basics/node_modules/.bin/astro +2 -2
  5. package/__e2e__/fixtures/basics/package.json +2 -2
  6. package/__e2e__/fixtures/basics/src/content/media/faithful-freestyle--en.json +1 -1
  7. package/__e2e__/fixtures/basics/src/content/media/skate-sounds--en.json +1 -1
  8. package/__e2e__/global.teardown.ts +5 -0
  9. package/__e2e__/homepage.spec.ts +17 -19
  10. package/__e2e__/search.spec.ts +3 -5
  11. package/package.json +6 -6
  12. package/playwright.config.ts +1 -0
  13. package/src/admin/components/form/DynamicArray.tsx +74 -0
  14. package/src/admin/components/form/Input.tsx +8 -5
  15. package/src/admin/components/form/LazyLoadedMarkdownEditor.tsx +78 -0
  16. package/src/admin/components/form/MarkdownEditor.tsx +52 -0
  17. package/src/admin/components/form/Select.tsx +11 -10
  18. package/src/admin/components/form/SubmitButton.tsx +9 -12
  19. package/src/admin/components/form/atoms/ErrorMessage.tsx +7 -28
  20. package/src/admin/components/form/atoms/Hint.tsx +2 -2
  21. package/src/admin/components/form/atoms/Label.tsx +5 -1
  22. package/src/admin/components/form/atoms/Legend.tsx +12 -2
  23. package/src/admin/components/form/hooks/use-field-error.tsx +3 -11
  24. package/src/admin/i18n/translations/en.yml +16 -5
  25. package/src/admin/pages/media/EditForm.tsx +48 -6
  26. package/src/admin/pages/media/EditRoute.astro +29 -10
  27. package/src/admin/pages/media/fields/Authors.tsx +51 -51
  28. package/src/admin/pages/media/fields/Categories.tsx +70 -0
  29. package/src/admin/pages/media/fields/Collections.tsx +117 -0
  30. package/src/admin/pages/media/media-item-store.ts +6 -1
  31. package/src/admin/types/media-item.ts +35 -2
  32. package/src/components/CategoriesSection.astro +2 -2
  33. package/src/components/MediaGallerySection.astro +3 -3
  34. package/src/components/MediaList.astro +2 -2
  35. package/src/content/get-categories.ts +18 -3
  36. package/src/i18n/resolve-language.ts +1 -1
  37. package/src/layouts/Page.astro +3 -2
  38. package/src/layouts/components/LanguagePicker.astro +1 -1
  39. package/src/pages/details-page/components/more-details/Languages.astro +2 -2
  40. package/src/pages/search-page/components/SearchFilter.astro +7 -7
  41. package/src/pages/search-page/components/SearchFilter.tsx +4 -4
  42. package/src/pages/search-page/components/SearchList.astro +4 -4
  43. package/src/pages/search-page/components/SearchListItem.tsx +4 -4
  44. package/src/pages/search-page/components/Select.tsx +3 -3
  45. package/src/pages/search-page/hooks/use-search.ts +4 -4
@@ -8,29 +8,32 @@ import { useFieldError } from "./hooks/use-field-error"
8
8
  export default function Input<TFieldValues extends FieldValues>({
9
9
  name,
10
10
  label,
11
+ defaultValue,
11
12
  hint,
12
13
  control,
13
14
  type = "text",
14
15
  }: {
15
16
  name: Path<TFieldValues>
16
17
  label: string
18
+ defaultValue?: string
17
19
  hint?: string
18
20
  control: Control<TFieldValues>
19
21
  type?: "text" | "date"
20
22
  }) {
21
- const hasError = !!useFieldError({ control, name })
23
+ const errorMessage = useFieldError({ control, name })
22
24
  return (
23
25
  <div key={name} className="flex w-full flex-col">
24
26
  <Label for={name} label={label} />
25
27
  <input
26
- className={`dy-input dy-input-bordered ${hasError ? "dy-input-error" : ""}`}
28
+ className={`dy-input dy-input-bordered shadow-inner ${errorMessage ? "dy-input-error" : ""}`}
27
29
  type={type}
28
30
  id={name}
29
- aria-invalid={hasError}
31
+ defaultValue={defaultValue}
32
+ aria-invalid={!!errorMessage}
30
33
  {...control.register(name)}
31
34
  />
32
- <ErrorMessage name={name} control={control} />
33
- <Hint hint={hint} />
35
+ <ErrorMessage message={errorMessage} />
36
+ <Hint label={hint} />
34
37
  </div>
35
38
  )
36
39
  }
@@ -0,0 +1,78 @@
1
+ import "@mdxeditor/editor/style.css"
2
+
3
+ import {
4
+ BlockTypeSelect,
5
+ BoldItalicUnderlineToggles,
6
+ CreateLink,
7
+ diffSourcePlugin,
8
+ DiffSourceToggleWrapper,
9
+ headingsPlugin,
10
+ linkDialogPlugin,
11
+ linkPlugin,
12
+ listsPlugin,
13
+ ListsToggle,
14
+ MDXEditor,
15
+ quotePlugin,
16
+ toolbarPlugin,
17
+ UndoRedo,
18
+ } from "@mdxeditor/editor"
19
+ import {
20
+ type Control,
21
+ Controller,
22
+ type FieldValues,
23
+ type Path,
24
+ } from "react-hook-form"
25
+
26
+ /**
27
+ * IMPORTANT: Do not import this component directly. It is
28
+ * very big. Use it with React lazy loading.
29
+ */
30
+ export default function LazyLoadedMarkdownEditor<
31
+ TFieldValues extends FieldValues,
32
+ >({
33
+ control,
34
+ defaultValue,
35
+ name,
36
+ }: {
37
+ name: Path<TFieldValues>
38
+ control: Control<TFieldValues>
39
+ defaultValue?: string
40
+ }) {
41
+ return (
42
+ <Controller
43
+ control={control}
44
+ name={name}
45
+ render={({ field: { onBlur, onChange, value, ref } }) => (
46
+ <MDXEditor
47
+ markdown={value ?? ""}
48
+ onBlur={onBlur}
49
+ onChange={onChange}
50
+ contentEditableClassName="prose bg-gray-50 h-80 w-full max-w-full overflow-y-auto"
51
+ ref={ref}
52
+ plugins={[
53
+ headingsPlugin(),
54
+ listsPlugin(),
55
+ linkPlugin(),
56
+ linkDialogPlugin(),
57
+ diffSourcePlugin({
58
+ viewMode: "rich-text",
59
+ diffMarkdown: defaultValue,
60
+ }),
61
+ quotePlugin(),
62
+ toolbarPlugin({
63
+ toolbarContents: () => (
64
+ <DiffSourceToggleWrapper>
65
+ <UndoRedo />
66
+ <BoldItalicUnderlineToggles />
67
+ <BlockTypeSelect />
68
+ <ListsToggle options={["bullet", "number"]} />
69
+ <CreateLink />
70
+ </DiffSourceToggleWrapper>
71
+ ),
72
+ }),
73
+ ]}
74
+ />
75
+ )}
76
+ />
77
+ )
78
+ }
@@ -0,0 +1,52 @@
1
+ import { lazy, Suspense } from "react"
2
+ import { type Control, type FieldValues, type Path } from "react-hook-form"
3
+
4
+ import ErrorMessage from "./atoms/ErrorMessage"
5
+ import Hint from "./atoms/Hint"
6
+ import Legend from "./atoms/Legend"
7
+ import { useFieldError } from "./hooks/use-field-error"
8
+
9
+ const LazyLoadedMarkdownEditor = lazy(
10
+ () => import("./LazyLoadedMarkdownEditor"),
11
+ )
12
+
13
+ export default function MarkdownEditor<TFieldValues extends FieldValues>({
14
+ control,
15
+ defaultValue,
16
+ name,
17
+ label,
18
+ hint,
19
+ }: {
20
+ name: Path<TFieldValues>
21
+ label: string
22
+ hint?: string
23
+ control: Control<TFieldValues>
24
+ defaultValue?: string
25
+ }) {
26
+ const errorMessage = useFieldError({ control, name })
27
+
28
+ return (
29
+ <fieldset key={name}>
30
+ <Legend label={label} />
31
+ <div
32
+ className={`overflow-hidden rounded-lg border border-gray-300 shadow-sm ${errorMessage ? "border-rose-800" : ""}`}
33
+ >
34
+ <Suspense
35
+ fallback={
36
+ <div className="h-[22.75rem] w-full bg-gray-50">
37
+ <div className="h-10 bg-gray-100"></div>
38
+ </div>
39
+ }
40
+ >
41
+ <LazyLoadedMarkdownEditor
42
+ control={control as Control<any>}
43
+ name={name}
44
+ defaultValue={defaultValue}
45
+ />
46
+ </Suspense>
47
+ </div>
48
+ <ErrorMessage message={errorMessage} />
49
+ <Hint label={hint} />
50
+ </fieldset>
51
+ )
52
+ }
@@ -1,6 +1,5 @@
1
1
  import type { Control, FieldValues, Path } from "react-hook-form"
2
2
 
3
- import { useI18n } from "../../../i18n/react/useI18n"
4
3
  import ErrorMessage from "./atoms/ErrorMessage"
5
4
  import Hint from "./atoms/Hint"
6
5
  import Label from "./atoms/Label"
@@ -10,34 +9,36 @@ export default function Select<TFieldValues extends FieldValues>({
10
9
  name,
11
10
  label,
12
11
  control,
12
+ defaultValue,
13
13
  hint,
14
14
  options,
15
15
  }: {
16
16
  name: Path<TFieldValues>
17
17
  label: string
18
18
  hint?: string
19
+ defaultValue?: string
19
20
  control: Control<TFieldValues>
20
- options: { id: string; label?: string }[]
21
+ options: { id: string; labelText?: string }[]
21
22
  }) {
22
- const { t } = useI18n()
23
- const hasError = !!useFieldError({ control, name })
23
+ const errorMessage = useFieldError({ control, name })
24
24
  return (
25
25
  <div key={name} className="flex w-full flex-col">
26
26
  <Label for={name} label={label} />
27
27
  <select
28
28
  {...control.register(name)}
29
29
  id={name}
30
- aria-invalid={hasError}
31
- className={`dy-select dy-select-bordered ${hasError ? "dy-select-error" : ""}"`}
30
+ aria-invalid={!!errorMessage}
31
+ defaultValue={defaultValue}
32
+ className={`dy-select dy-select-bordered text-base shadow-sm ${errorMessage ? "dy-select-error" : ""}`}
32
33
  >
33
- {options.map(({ id, label }) => (
34
+ {options.map(({ id, labelText }) => (
34
35
  <option key={id} value={id}>
35
- {label ? t(label) : id}
36
+ {labelText ?? id}
36
37
  </option>
37
38
  ))}
38
39
  </select>
39
- <ErrorMessage name={name} control={control} />
40
- <Hint hint={hint} />
40
+ <ErrorMessage message={errorMessage} />
41
+ <Hint label={hint} />
41
42
  </div>
42
43
  )
43
44
  }
@@ -8,7 +8,7 @@ import type { MediaItem } from "../../types/media-item"
8
8
  const SUCCESS_DURATION_MS = 2000
9
9
 
10
10
  const baseButtonClass =
11
- "flex min-w-52 items-center justify-center gap-2 rounded-2xl px-6 py-3 font-bold uppercase shadow-sm transition-colors duration-200 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-gray-900 disabled:cursor-not-allowed"
11
+ "flex min-w-52 items-center justify-center gap-2 rounded-2xl px-4 py-3 font-bold uppercase shadow-sm transition-colors easy-in-out focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-gray-900 disabled:cursor-not-allowed"
12
12
 
13
13
  const buttonStateClasses = {
14
14
  idle: "bg-gray-800 text-gray-100 hover:bg-gray-950 hover:text-gray-300 disabled:bg-gray-600 disabled:text-gray-200",
@@ -19,8 +19,10 @@ const buttonStateClasses = {
19
19
  } as const
20
20
 
21
21
  const buttonLabels = {
22
- idle: "ln.admin.save",
23
- success: "ln.admin.saved",
22
+ idle: import.meta.env.DEV
23
+ ? "ln.admin.save-changes"
24
+ : "ln.admin.publish-changes",
25
+ success: import.meta.env.DEV ? "ln.admin.saved" : "ln.admin.published",
24
26
  error: "ln.admin.failed",
25
27
  } as const
26
28
 
@@ -38,10 +40,9 @@ export default function SubmitButton({
38
40
  className?: string
39
41
  }) {
40
42
  const { t } = useI18n()
41
- const { isValid, isSubmitting, isSubmitSuccessful, submitCount } =
42
- useFormState({
43
- control,
44
- })
43
+ const { isSubmitting, isSubmitSuccessful, submitCount } = useFormState({
44
+ control,
45
+ })
45
46
 
46
47
  const buttonState = useButtonState(isSubmitSuccessful, submitCount)
47
48
  const buttonClass = `${baseButtonClass} ${buttonStateClasses[buttonState]} ${className}`
@@ -49,11 +50,7 @@ export default function SubmitButton({
49
50
  const icon = icons[buttonState]
50
51
 
51
52
  return (
52
- <button
53
- className={buttonClass}
54
- type="submit"
55
- disabled={!isValid || isSubmitting}
56
- >
53
+ <button className={buttonClass} type="submit" disabled={isSubmitting}>
57
54
  {icon && <Icon className={icon} ariaLabel="" />}
58
55
  {t(label)}
59
56
  </button>
@@ -1,34 +1,13 @@
1
- import { ErrorMessage as RhfErrorMessage } from "@hookform/error-message"
2
- import { type Control, useFormState } from "react-hook-form"
3
-
4
1
  import { useI18n } from "../../../../i18n/react/useI18n"
5
2
 
6
- export default function ErrorMessage({
7
- name,
8
- control,
9
- }: {
10
- name: string
11
- control: Control<any>
12
- }) {
3
+ export default function ErrorMessage({ message }: { message?: string }) {
13
4
  const { t } = useI18n()
14
- const { errors } = useFormState({ control, name, exact: true })
5
+ if (!message) {
6
+ return null
7
+ }
15
8
  return (
16
- <RhfErrorMessage
17
- errors={errors}
18
- name={name}
19
- render={({ message }) => {
20
- if (!message) {
21
- return null
22
- }
23
- return (
24
- <p
25
- className="my-2 flex flex-col gap-1 text-sm text-rose-800"
26
- role="alert"
27
- >
28
- {t(message)}
29
- </p>
30
- )
31
- }}
32
- />
9
+ <p className="my-2 flex flex-col gap-1 text-sm text-rose-800" role="alert">
10
+ {t(message)}
11
+ </p>
33
12
  )
34
13
  }
@@ -1,10 +1,10 @@
1
1
  import { useI18n } from "../../../../i18n/react/useI18n"
2
2
 
3
- export default function Hint({ hint }: { hint?: string }) {
3
+ export default function Hint({ label }: { label?: string }) {
4
4
  const { t } = useI18n()
5
5
  return (
6
6
  <div className="flex h-12 w-full items-start justify-end p-2">
7
- {hint && <span className="dy-label-text-alt">{t(hint)}</span>}
7
+ {label && <span className="dy-label-text-alt">{t(label)}</span>}
8
8
  </div>
9
9
  )
10
10
  }
@@ -3,15 +3,19 @@ import { useI18n } from "../../../../i18n/react/useI18n"
3
3
  export default function Label({
4
4
  label,
5
5
  for: htmlFor,
6
+ size = "sm",
7
+ className,
6
8
  }: {
7
9
  label: string
8
10
  for: string
11
+ className?: string
12
+ size?: "sm" | "xs"
9
13
  }) {
10
14
  const { t } = useI18n()
11
15
  return (
12
16
  <label
13
17
  htmlFor={htmlFor}
14
- className="pb-2 text-sm font-bold uppercase text-gray-600"
18
+ className={`font-bold uppercase text-gray-600 ${size === "sm" ? "pb-2 text-sm" : "pb-1 text-xs"} ${className}`}
15
19
  >
16
20
  {t(label)}
17
21
  </label>
@@ -1,9 +1,19 @@
1
1
  import { useI18n } from "../../../../i18n/react/useI18n"
2
2
 
3
- export default function Label({ label }: { label: string }) {
3
+ export default function Legend({
4
+ label,
5
+ size = "sm",
6
+ className,
7
+ }: {
8
+ label: string
9
+ className?: string
10
+ size?: "sm" | "xs"
11
+ }) {
4
12
  const { t } = useI18n()
5
13
  return (
6
- <legend className="pb-2 text-sm font-bold uppercase text-gray-600">
14
+ <legend
15
+ className={`pb-2 font-bold uppercase text-gray-600 ${size === "sm" ? "text-sm" : "text-xs"} ${className}`}
16
+ >
7
17
  {t(label)}
8
18
  </legend>
9
19
  )
@@ -1,21 +1,13 @@
1
- import { type Control, type FieldError, useFormState } from "react-hook-form"
1
+ import { type Control, get, useFormState } from "react-hook-form"
2
2
 
3
3
  export function useFieldError({
4
4
  control,
5
5
  name,
6
- index,
7
6
  }: {
8
7
  control: Control<any>
9
8
  name: string
10
- index?: number
11
9
  }) {
12
10
  const { errors } = useFormState({ control, name, exact: true })
13
- const error = errors[name]
14
- if (!error) {
15
- return undefined
16
- }
17
- if (Array.isArray(error)) {
18
- return index !== undefined ? (error[index] as FieldError) : undefined
19
- }
20
- return error as FieldError
11
+ const error = get(errors, name) as { message: string } | undefined
12
+ return error?.message
21
13
  }
@@ -1,17 +1,28 @@
1
1
  ln.admin.edit: Edit
2
- ln.admin.save: Save
2
+ ln.admin.publish-changes: Publish Changes
3
+ ln.admin.published: Published
4
+ ln.admin.save-changes: Save Changes
3
5
  ln.admin.saved: Saved
4
6
  ln.admin.failed: Failed
5
7
  ln.admin.remove: Remove
8
+ ln.admin.name: Name
6
9
  ln.admin.add-author: Add Author
10
+ ln.admin.add-category: Add Category
11
+ ln.admin.collections: Collections
12
+ ln.admin.add-collection: Add Collection
7
13
  ln.admin.edit-media-item: Edit media item
14
+ ln.admin.position-in-collection: Position in Collection
8
15
  ln.admin.back-to-details-page: Back to details page
9
16
  ln.admin.title: Title
10
17
  ln.admin.common-id: Common ID
11
18
  ln.admin.authors: Authors
19
+ ln.admin.description: Description
12
20
  ln.admin.created-on: Created on
13
21
  ln.admin.created-on-hint: When has this item been created on this library?
14
- ln.admin.common-id-hint: The english title, all lowercase, words separated with hyphens.
15
- ln.admin.errors.non-empty-string: String must contain at least 1 character(s)
16
- ln.admin.errors.invalid-date: Invalid date
17
- ln.admin.errors.required: Required field
22
+ ln.admin.common-id-hint: The English title, all lowercase, words separated with hyphens.
23
+ ln.admin.errors.non-empty-string: Please enter at least one character.
24
+ ln.admin.errors.invalid-date: That date doesn't look right.
25
+ ln.admin.errors.required: This field is required.
26
+ ln.admin.errors.gte-0: Use a number zero or greater.
27
+ ln.admin.errors.unique-elements: Please choose a different value for each entry.
28
+ ln.admin.errors.integer: Please enter a whole number.
@@ -7,28 +7,40 @@ import {
7
7
  I18nContext,
8
8
  } from "../../../i18n/react/i18n-context"
9
9
  import Input from "../../components/form/Input"
10
+ import MarkdownEditor from "../../components/form/MarkdownEditor"
10
11
  import Select from "../../components/form/Select"
11
12
  import SubmitButton from "../../components/form/SubmitButton"
12
13
  import { type MediaItem, mediaItemSchema } from "../../types/media-item"
13
14
  import Authors from "./fields/Authors"
15
+ import Categories from "./fields/Categories"
16
+ import Collections from "./fields/Collections"
14
17
  import { updateMediaItem } from "./media-item-store"
15
18
 
19
+ type SelectOption = { id: string; labelText: string }
20
+
16
21
  export default function EditForm({
17
22
  mediaId,
18
23
  mediaItem,
19
24
  i18nConfig,
20
25
  mediaTypes,
21
26
  languages,
27
+ categories,
28
+ collections,
22
29
  }: {
23
30
  mediaId: string
24
31
  mediaItem: MediaItem
25
32
  i18nConfig: I18nConfig
26
- mediaTypes: { id: string; label: string }[]
27
- languages: { id: string; label: string }[]
33
+ mediaTypes: SelectOption[]
34
+ languages: SelectOption[]
35
+ categories: SelectOption[]
36
+ collections: SelectOption[]
28
37
  }) {
29
38
  const { handleSubmit, control } = useForm({
39
+ // Provide per-input defaults so SSG prerender matches, but keep a full
40
+ // defaultValues object here because useFieldArray does not accept default values.
30
41
  defaultValues: mediaItem,
31
42
  mode: "onTouched",
43
+ shouldFocusError: true,
32
44
  resolver: zodResolver(mediaItemSchema),
33
45
  })
34
46
  const onSubmit = handleSubmit(
@@ -37,36 +49,66 @@ export default function EditForm({
37
49
  const i18n = createI18n(i18nConfig)
38
50
  return (
39
51
  <I18nContext.Provider value={i18n}>
40
- <form onSubmit={onSubmit}>
41
- <Input name="title" label="ln.admin.title" control={control} />
52
+ <form className="flex flex-col" onSubmit={onSubmit}>
53
+ <div className="mb-8 flex items-end justify-between">
54
+ <h1 className="text-3xl">{i18n.t("ln.admin.edit-media-item")}</h1>
55
+ <SubmitButton control={control} />
56
+ </div>
57
+
58
+ <Input
59
+ name="title"
60
+ label="ln.admin.title"
61
+ control={control}
62
+ defaultValue={mediaItem.title}
63
+ />
42
64
  <Input
43
65
  name="commonId"
44
66
  label="ln.admin.common-id"
45
67
  hint="ln.admin.common-id-hint"
46
68
  control={control}
69
+ defaultValue={mediaItem.commonId}
47
70
  />
48
71
  <Select
49
72
  name="type"
50
73
  label="ln.type"
51
74
  options={mediaTypes}
52
75
  control={control}
76
+ defaultValue={mediaItem.type}
53
77
  />
54
78
  <Select
55
79
  name="language"
56
80
  label="ln.language"
81
+ defaultValue={mediaItem.language}
57
82
  options={languages}
58
83
  control={control}
59
84
  />
60
- <Authors control={control} />
85
+ <Authors control={control} defaultValue={mediaItem.authors} />
61
86
  <Input
62
87
  name="dateCreated"
63
88
  label="ln.admin.created-on"
64
89
  hint="ln.admin.created-on-hint"
65
90
  type="date"
91
+ defaultValue={mediaItem.dateCreated}
92
+ control={control}
93
+ />
94
+ <Categories
95
+ categories={categories}
96
+ control={control}
97
+ defaultValue={mediaItem.categories}
98
+ />
99
+ <Collections
100
+ collections={collections}
101
+ control={control}
102
+ defaultValue={mediaItem.collections}
103
+ />
104
+ <MarkdownEditor
66
105
  control={control}
106
+ name="description"
107
+ label="ln.admin.description"
108
+ defaultValue={mediaItem.description}
67
109
  />
68
110
 
69
- <SubmitButton className="mt-8" control={control} />
111
+ <SubmitButton className="self-end" control={control} />
70
112
  </form>
71
113
  </I18nContext.Provider>
72
114
  )
@@ -3,6 +3,7 @@ import type { GetStaticPaths } from "astro"
3
3
  import { getCollection } from "astro:content"
4
4
  import config from "virtual:lightnet/config"
5
5
 
6
+ import { getCategories } from "../../../content/get-categories"
6
7
  import { getRawMediaItem } from "../../../content/get-media-items"
7
8
  import { getMediaTypes } from "../../../content/get-media-types"
8
9
  import { prepareI18nConfig } from "../../../i18n/react/prepare-i18n-config"
@@ -23,42 +24,60 @@ const { data: mediaItem } = await getRawMediaItem(mediaId)
23
24
  const formData = {
24
25
  ...mediaItem,
25
26
  authors: mediaItem.authors?.map((value) => ({ value })) ?? [],
27
+ categories:
28
+ mediaItem.categories?.map((value) => ({
29
+ value,
30
+ })) ?? [],
31
+ collections: mediaItem.collections ?? [],
26
32
  }
27
33
 
28
34
  const i18nConfig = prepareI18nConfig(Astro.locals.i18n, [
29
35
  "ln.admin.*",
30
36
  "ln.type",
31
37
  "ln.language",
38
+ "ln.categories",
32
39
  ])
33
- const { t } = Astro.locals.i18n
40
+ const { t, currentLocale } = Astro.locals.i18n
34
41
 
35
42
  const mediaTypes = (await getMediaTypes()).map(({ id, data: { label } }) => ({
36
43
  id,
37
- label: t(label),
44
+ labelText: t(label),
38
45
  }))
46
+
47
+ const categories = (await getCategories(currentLocale, t)).map(
48
+ ({ id, labelText }) => ({ id, labelText }),
49
+ )
50
+
51
+ const collections = (await getCollection("media-collections")).map(
52
+ ({ id, data: { label } }) => ({ id, labelText: t(label) }),
53
+ )
54
+
39
55
  const languages = config.languages.map(({ code, label }) => ({
40
56
  id: code,
41
- label: t(label),
57
+ labelText: t(label),
42
58
  }))
43
59
  ---
44
60
 
45
- <Page>
46
- <div class="mx-auto max-w-screen-md px-4 pt-12 md:px-8">
61
+ <Page mainClass="bg-slate-500">
62
+ <div class="mx-auto block max-w-screen-md px-4 md:px-8">
47
63
  <a
48
- class="underline"
49
- href=`/${Astro.currentLocale}/media/faithful-freestyle--en`
64
+ class="block pb-4 pt-8 text-gray-200 underline"
65
+ href=`/${Astro.currentLocale}/media/${mediaId}`
50
66
  >{t("ln.admin.back-to-details-page")}</a
51
67
  >
52
- <h1 class="mb-10 mt-10 text-2xl">
53
- {t("ln.admin.edit-media-item")}
54
- </h1>
68
+ </div>
55
69
 
70
+ <div
71
+ class="mx-auto max-w-screen-md rounded-2xl bg-gray-200 px-4 py-8 shadow-md md:px-8"
72
+ >
56
73
  <EditForm
57
74
  mediaId={mediaId}
58
75
  mediaItem={formData}
59
76
  i18nConfig={i18nConfig}
60
77
  mediaTypes={mediaTypes}
61
78
  languages={languages}
79
+ categories={categories}
80
+ collections={collections}
62
81
  client:load
63
82
  />
64
83
  </div>