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
@@ -1,58 +1,58 @@
1
- import { type Control, useFieldArray } from "react-hook-form"
1
+ import { type Control } from "react-hook-form"
2
2
 
3
- import Icon from "../../../../components/Icon"
4
- import { useI18n } from "../../../../i18n/react/useI18n"
5
3
  import ErrorMessage from "../../../components/form/atoms/ErrorMessage"
6
- import Hint from "../../../components/form/atoms/Hint"
7
- import Legend from "../../../components/form/atoms/Legend"
4
+ import DynamicArray from "../../../components/form/DynamicArray"
5
+ import { useFieldError } from "../../../components/form/hooks/use-field-error"
8
6
  import type { MediaItem } from "../../../types/media-item"
9
7
 
10
- export default function Authors({ control }: { control: Control<MediaItem> }) {
11
- const { fields, append, remove } = useFieldArray({
12
- name: "authors",
13
- control,
14
- })
15
- const { t } = useI18n()
8
+ export default function Authors({
9
+ control,
10
+ defaultValue,
11
+ }: {
12
+ control: Control<MediaItem>
13
+ defaultValue: MediaItem["authors"]
14
+ }) {
16
15
  return (
17
- <fieldset key="authors">
18
- <Legend label="ln.admin.authors" />
19
- <div className="flex w-full flex-col divide-y divide-gray-300 rounded-lg border border-gray-300">
20
- {fields.map((author, index) => (
21
- <div className="p-2" key={author.id}>
22
- <div className="flex w-full items-center gap-2">
23
- <input
24
- className="dy-input dy-input-sm grow"
25
- {...control.register(`authors.${index}.value`)}
26
- />
27
- <button
28
- className="flex items-center p-2 text-gray-600 hover:text-gray-900"
29
- type="button"
30
- onClick={() => remove(index)}
31
- >
32
- <Icon
33
- className="mdi--remove"
34
- ariaLabel={t("ln.admin.remove")}
35
- />
36
- </button>
37
- </div>
38
- <ErrorMessage name={`authors.${index}.value`} control={control} />
39
- </div>
40
- ))}
41
- <button
42
- type="button"
43
- className="p-4 text-sm font-bold text-gray-600 hover:bg-gray-200"
44
- onClick={() => {
45
- append(
46
- { value: "" },
47
- { focusName: `authors.${fields.length}.value` },
48
- )
49
- }}
50
- >
51
- {t("ln.admin.add-author")}
52
- </button>
53
- </div>
54
- <ErrorMessage name="authors" control={control} />
55
- <Hint />
56
- </fieldset>
16
+ <DynamicArray
17
+ control={control}
18
+ name="authors"
19
+ label="ln.admin.authors"
20
+ renderElement={(index) => (
21
+ <AuthorInput
22
+ index={index}
23
+ control={control}
24
+ defaultValue={defaultValue[index]?.value}
25
+ />
26
+ )}
27
+ addButton={{
28
+ label: "ln.admin.add-author",
29
+ onClick: (append, index) =>
30
+ append({ value: "" }, { focusName: `authors.${index}.value` }),
31
+ }}
32
+ />
33
+ )
34
+ }
35
+
36
+ function AuthorInput({
37
+ index,
38
+ control,
39
+ defaultValue,
40
+ }: {
41
+ index: number
42
+ control: Control<MediaItem>
43
+ defaultValue?: string
44
+ }) {
45
+ const name = `authors.${index}.value` as const
46
+ const errorMessage = useFieldError({ name, control })
47
+ return (
48
+ <>
49
+ <input
50
+ className={`dy-input dy-input-bordered shadow-inner ${errorMessage ? "dy-input-error" : ""}`}
51
+ aria-invalid={!!errorMessage}
52
+ defaultValue={defaultValue}
53
+ {...control.register(name)}
54
+ />
55
+ <ErrorMessage message={errorMessage} />
56
+ </>
57
57
  )
58
58
  }
@@ -0,0 +1,70 @@
1
+ import { type Control } from "react-hook-form"
2
+
3
+ import ErrorMessage from "../../../components/form/atoms/ErrorMessage"
4
+ import DynamicArray from "../../../components/form/DynamicArray"
5
+ import { useFieldError } from "../../../components/form/hooks/use-field-error"
6
+ import type { MediaItem } from "../../../types/media-item"
7
+
8
+ export default function Categories({
9
+ control,
10
+ categories,
11
+ defaultValue,
12
+ }: {
13
+ control: Control<MediaItem>
14
+ defaultValue: MediaItem["categories"]
15
+ categories: { id: string; labelText: string }[]
16
+ }) {
17
+ return (
18
+ <DynamicArray
19
+ control={control}
20
+ name="categories"
21
+ label="ln.categories"
22
+ renderElement={(index) => (
23
+ <CategorySelect
24
+ categories={categories}
25
+ control={control}
26
+ index={index}
27
+ defaultValue={defaultValue[index]?.value}
28
+ />
29
+ )}
30
+ addButton={{
31
+ label: "ln.admin.add-category",
32
+ onClick: (append, index) =>
33
+ append({ value: "" }, { focusName: `categories.${index}.value` }),
34
+ }}
35
+ />
36
+ )
37
+ }
38
+
39
+ function CategorySelect({
40
+ control,
41
+ categories,
42
+ defaultValue,
43
+ index,
44
+ }: {
45
+ control: Control<MediaItem>
46
+ categories: { id: string; labelText: string }[]
47
+ defaultValue?: string
48
+ index: number
49
+ }) {
50
+ const name = `categories.${index}.value` as const
51
+ const errorMessage = useFieldError({ name, control })
52
+ return (
53
+ <>
54
+ <select
55
+ {...control.register(name)}
56
+ id={name}
57
+ defaultValue={defaultValue}
58
+ aria-invalid={!!errorMessage}
59
+ className={`dy-select dy-select-bordered text-base shadow-sm ${errorMessage ? "dy-select-error" : ""}`}
60
+ >
61
+ {categories.map(({ id, labelText }) => (
62
+ <option key={id} value={id}>
63
+ {labelText}
64
+ </option>
65
+ ))}
66
+ </select>
67
+ <ErrorMessage message={errorMessage} />
68
+ </>
69
+ )
70
+ }
@@ -0,0 +1,117 @@
1
+ import { type Control } from "react-hook-form"
2
+
3
+ import ErrorMessage from "../../../components/form/atoms/ErrorMessage"
4
+ import Label from "../../../components/form/atoms/Label"
5
+ import DynamicArray from "../../../components/form/DynamicArray"
6
+ import { useFieldError } from "../../../components/form/hooks/use-field-error"
7
+ import type { MediaItem } from "../../../types/media-item"
8
+
9
+ export default function Collections({
10
+ control,
11
+ collections,
12
+ defaultValue,
13
+ }: {
14
+ control: Control<MediaItem>
15
+ collections: { id: string; labelText: string }[]
16
+ defaultValue: MediaItem["collections"]
17
+ }) {
18
+ return (
19
+ <DynamicArray
20
+ control={control}
21
+ name="collections"
22
+ label="ln.admin.collections"
23
+ renderElement={(index) => (
24
+ <div className="flex w-full flex-col py-2">
25
+ <CollectionSelect
26
+ collections={collections}
27
+ control={control}
28
+ index={index}
29
+ defaultValue={defaultValue[index]?.collection}
30
+ />
31
+ <CollectionIndex
32
+ control={control}
33
+ index={index}
34
+ defaultValue={defaultValue[index]?.index}
35
+ />
36
+ </div>
37
+ )}
38
+ addButton={{
39
+ label: "ln.admin.add-collection",
40
+ onClick: (append, index) =>
41
+ append(
42
+ { collection: "" },
43
+ { focusName: `collections.${index}.collection` },
44
+ ),
45
+ }}
46
+ />
47
+ )
48
+ }
49
+
50
+ function CollectionSelect({
51
+ control,
52
+ collections,
53
+ index,
54
+ defaultValue,
55
+ }: {
56
+ control: Control<MediaItem>
57
+ collections: { id: string; labelText: string }[]
58
+ defaultValue?: string
59
+ index: number
60
+ }) {
61
+ const name = `collections.${index}.collection` as const
62
+ const errorMessage = useFieldError({ name, control })
63
+ return (
64
+ <>
65
+ <Label for={name} label="ln.admin.name" size="xs" />
66
+ <select
67
+ {...control.register(name)}
68
+ id={name}
69
+ defaultValue={defaultValue}
70
+ aria-invalid={!!errorMessage}
71
+ className={`dy-select dy-select-bordered text-base shadow-sm ${errorMessage ? "dy-select-error" : ""}`}
72
+ >
73
+ {collections.map(({ id, labelText }) => (
74
+ <option key={id} value={id}>
75
+ {labelText}
76
+ </option>
77
+ ))}
78
+ </select>
79
+ <ErrorMessage message={errorMessage} />
80
+ </>
81
+ )
82
+ }
83
+
84
+ function CollectionIndex({
85
+ control,
86
+ index,
87
+ defaultValue,
88
+ }: {
89
+ control: Control<MediaItem>
90
+ index: number
91
+ defaultValue?: number
92
+ }) {
93
+ const name = `collections.${index}.index` as const
94
+ const errorMessage = useFieldError({ name, control })
95
+ return (
96
+ <>
97
+ <Label
98
+ for={name}
99
+ label="ln.admin.position-in-collection"
100
+ size="xs"
101
+ className="mt-3"
102
+ />
103
+ <input
104
+ className={`dy-input dy-input-bordered shadow-inner ${errorMessage ? "dy-input-error" : ""}`}
105
+ aria-invalid={!!errorMessage}
106
+ type="number"
107
+ id={name}
108
+ defaultValue={defaultValue}
109
+ step={1}
110
+ {...control.register(name, {
111
+ setValueAs: (value) => (value === "" ? undefined : Number(value)),
112
+ })}
113
+ />
114
+ <ErrorMessage message={errorMessage} />
115
+ </>
116
+ )
117
+ }
@@ -8,6 +8,11 @@ export const updateMediaItem = async (id: string, item: MediaItem) => {
8
8
  const mapToContentSchema = (item: MediaItem) => {
9
9
  return {
10
10
  ...item,
11
- authors: item.authors.map(({ value }) => value),
11
+ authors: flatten(item.authors),
12
+ categories: flatten(item.categories),
12
13
  }
13
14
  }
15
+
16
+ const flatten = <TValue>(valueArray: { value: TValue }[]) => {
17
+ return valueArray.map(({ value }) => value)
18
+ }
@@ -1,8 +1,27 @@
1
- import { z } from "astro/zod"
1
+ import { type RefinementCtx, z } from "astro/zod"
2
2
 
3
3
  const NON_EMPTY_STRING = "ln.admin.errors.non-empty-string"
4
4
  const INVALID_DATE = "ln.admin.errors.invalid-date"
5
5
  const REQUIRED = "ln.admin.errors.required"
6
+ const GTE_0 = "ln.admin.errors.gte-0"
7
+ const INTEGER = "ln.admin.errors.integer"
8
+ const UNIQUE_ELEMENTS = "ln.admin.errors.unique-elements"
9
+
10
+ const unique = <TArrayItem>(path: Extract<keyof TArrayItem, string>) => {
11
+ return (values: TArrayItem[], ctx: RefinementCtx) => {
12
+ const seenValues = new Set<unknown>()
13
+ values.forEach((value, index) => {
14
+ if (seenValues.has(value[path])) {
15
+ ctx.addIssue({
16
+ path: [index, path],
17
+ message: UNIQUE_ELEMENTS,
18
+ code: "custom",
19
+ })
20
+ }
21
+ seenValues.add(value[path])
22
+ })
23
+ }
24
+ }
6
25
 
7
26
  export const mediaItemSchema = z.object({
8
27
  commonId: z.string().nonempty(NON_EMPTY_STRING),
@@ -12,8 +31,22 @@ export const mediaItemSchema = z.object({
12
31
  authors: z
13
32
  .object({ value: z.string().nonempty(NON_EMPTY_STRING) })
14
33
  .array()
15
- .min(1),
34
+ .superRefine(unique("value")),
35
+ categories: z
36
+ .object({
37
+ value: z.string().nonempty(REQUIRED),
38
+ })
39
+ .array()
40
+ .superRefine(unique("value")),
41
+ collections: z
42
+ .object({
43
+ collection: z.string().nonempty(REQUIRED),
44
+ index: z.number().int(INTEGER).gte(0, GTE_0).optional(),
45
+ })
46
+ .array()
47
+ .superRefine(unique("collection")),
16
48
  dateCreated: z.string().date(INVALID_DATE),
49
+ description: z.string().optional(),
17
50
  })
18
51
 
19
52
  export type MediaItem = z.input<typeof mediaItemSchema>
@@ -70,7 +70,7 @@ function getImage({ image, id }: Category) {
70
70
  ]}
71
71
  >
72
72
  <span class="line-clamp-3 select-none text-balance font-bold">
73
- {category.name}
73
+ {category.labelText}
74
74
  </span>
75
75
  </div>
76
76
  </div>
@@ -128,7 +128,7 @@ function getImage({ image, id }: Category) {
128
128
  ]}
129
129
  >
130
130
  <span class="line-clamp-3 select-none text-balance font-bold">
131
- {category.name}
131
+ {category.labelText}
132
132
  </span>
133
133
  </div>
134
134
  </div>
@@ -38,7 +38,7 @@ const t = Astro.locals.i18n.t
38
38
  const types = Object.fromEntries(
39
39
  (await getMediaTypes()).map((type) => [
40
40
  type.id,
41
- { ...type.data, name: t(type.data.label) },
41
+ { ...type.data, labelText: t(type.data.label) },
42
42
  ]),
43
43
  )
44
44
 
@@ -119,7 +119,7 @@ const coverImageStyle =
119
119
  <span class="line-clamp-2 h-12 text-balance text-sm font-bold text-gray-700">
120
120
  <Icon
121
121
  className={`${types[item.data.type.id].icon} me-2 align-bottom`}
122
- ariaLabel={types[item.data.type.id].name}
122
+ ariaLabel={types[item.data.type.id].labelText}
123
123
  />
124
124
  {item.data.title}
125
125
  </span>
@@ -159,7 +159,7 @@ const coverImageStyle =
159
159
  <span class="line-clamp-2 h-12 text-balance text-sm font-bold text-gray-700">
160
160
  <Icon
161
161
  className={`${types[item.data.type.id].icon} me-2 align-bottom`}
162
- ariaLabel={types[item.data.type.id].name}
162
+ ariaLabel={types[item.data.type.id].labelText}
163
163
  />
164
164
  {item.data.title}
165
165
  </span>
@@ -34,7 +34,7 @@ const mediaTypes = Object.fromEntries(
34
34
  type.id,
35
35
  {
36
36
  id: type.id,
37
- name: t(type.data.label),
37
+ labelText: t(type.data.label),
38
38
  icon: type.data.icon,
39
39
  coverImageStyle: type.data.coverImageStyle,
40
40
  },
@@ -84,7 +84,7 @@ const mediaTypes = Object.fromEntries(
84
84
  <p class="mb-1 line-clamp-3 text-balance font-bold text-gray-700 md:mb-3">
85
85
  <Icon
86
86
  className={`${mediaTypes[item.data.type.id].icon} me-2 align-bottom text-2xl text-gray-700`}
87
- ariaLabel={mediaTypes[item.data.type.id].name}
87
+ ariaLabel={mediaTypes[item.data.type.id].labelText}
88
88
  />
89
89
  <span>{item.data.title}</span>
90
90
  </p>
@@ -33,12 +33,27 @@ const contentCategories = Object.fromEntries(
33
33
  *
34
34
  * @param currentLocale current locale
35
35
  * @param t translate function
36
- * @returns categories sorted by name
36
+ * @returns categories sorted by labelText
37
37
  */
38
38
  export async function getUsedCategories(currentLocale: string, t: TranslateFn) {
39
39
  return [...Object.entries(contentCategories)]
40
- .map(([id, data]) => ({ id, ...data, name: t(data.label) }))
41
- .sort((a, b) => a.name.localeCompare(b.name, currentLocale))
40
+ .map(([id, data]) => ({ id, ...data, labelText: t(data.label) }))
41
+ .sort((a, b) => a.labelText.localeCompare(b.labelText, currentLocale))
42
+ }
43
+
44
+ /**
45
+ * Get all categories. This includes categories that are not
46
+ * referenced by any media item. If you only need categories that are referenced
47
+ * by media items use `getUsedCategories`.
48
+ *
49
+ * @param currentLocale current locale
50
+ * @param t translate function
51
+ * @returns categories sorted by labelText
52
+ */
53
+ export async function getCategories(currentLocale: string, t: TranslateFn) {
54
+ return [...Object.entries(categoriesById)]
55
+ .map(([id, data]) => ({ id, ...data, labelText: t(data.label) }))
56
+ .sort((a, b) => a.labelText.localeCompare(b.labelText, currentLocale))
42
57
  }
43
58
 
44
59
  export async function getCategory(id: string) {
@@ -27,6 +27,6 @@ export const resolveTranslatedLanguage = (bcp47: string, t: TranslateFn) => {
27
27
  const language = resolveLanguage(bcp47)
28
28
  return {
29
29
  ...language,
30
- name: t(language.label),
30
+ labelText: t(language.label),
31
31
  }
32
32
  }
@@ -13,9 +13,10 @@ import ViewTransition from "./components/ViewTransition.astro"
13
13
  interface Props {
14
14
  title?: string
15
15
  description?: string
16
+ mainClass?: string
16
17
  }
17
18
 
18
- const { title, description } = Astro.props
19
+ const { title, description, mainClass } = Astro.props
19
20
  const configTitle = Astro.locals.i18n.t(config.title)
20
21
 
21
22
  const { currentLocale } = Astro.locals.i18n
@@ -39,7 +40,7 @@ const language = resolveLanguage(currentLocale)
39
40
  class="flex min-h-screen flex-col overflow-y-scroll bg-gray-50 text-gray-900"
40
41
  >
41
42
  <Header />
42
- <main class="grow pb-8 pt-14 sm:py-20">
43
+ <main class={`grow pb-8 pt-14 sm:py-20 ${mainClass ?? ""}`}>
43
44
  <slot />
44
45
  </main>
45
46
  {CustomFooter ? <CustomFooter /> : <Footer />}
@@ -9,7 +9,7 @@ const { t, locales } = Astro.locals.i18n
9
9
  const translations = locales
10
10
  .map((locale) => ({
11
11
  locale,
12
- label: resolveTranslatedLanguage(locale, t).name,
12
+ label: resolveTranslatedLanguage(locale, t).labelText,
13
13
  active: locale === Astro.currentLocale,
14
14
  href: currentPathWithLocale(locale),
15
15
  }))
@@ -21,7 +21,7 @@ const { t } = Astro.locals.i18n
21
21
  <Label>{translations.length ? t("ln.languages") : t("ln.language")}</Label>
22
22
  <ul class="flex flex-wrap gap-2">
23
23
  <li class="py-1 pe-2 text-gray-800">
24
- {resolveTranslatedLanguage(item.data.language, t).name}
24
+ {resolveTranslatedLanguage(item.data.language, t).labelText}
25
25
  </li>
26
26
  {
27
27
  translations.map((translation) => (
@@ -30,7 +30,7 @@ const { t } = Astro.locals.i18n
30
30
  href={detailsPagePath(Astro.currentLocale, translation)}
31
31
  hreflang={translation.language}
32
32
  >
33
- {resolveTranslatedLanguage(translation.language, t).name}
33
+ {resolveTranslatedLanguage(translation.language, t).labelText}
34
34
  </a>
35
35
  </li>
36
36
  ))
@@ -9,24 +9,24 @@ import SearchFilterReact from "./SearchFilter.tsx"
9
9
 
10
10
  const { t, currentLocale } = Astro.locals.i18n
11
11
 
12
- const sortByName = (array: { id: string; name: string }[]) =>
13
- array.sort((a, b) => a.name.localeCompare(b.name, currentLocale))
12
+ const sortByLabelText = (array: { id: string; labelText: string }[]) =>
13
+ array.sort((a, b) => a.labelText.localeCompare(b.labelText, currentLocale))
14
14
 
15
15
  const categories = (await getUsedCategories(currentLocale, t)).map(
16
- ({ id, name }) => ({ id, name }),
16
+ ({ id, labelText }) => ({ id, labelText }),
17
17
  )
18
18
 
19
- const mediaTypes = sortByName(
19
+ const mediaTypes = sortByLabelText(
20
20
  (await getMediaTypes()).map((type) => ({
21
21
  id: type.id,
22
- name: t(type.data.label),
22
+ labelText: t(type.data.label),
23
23
  })),
24
24
  )
25
25
 
26
- const languages = sortByName(
26
+ const languages = sortByLabelText(
27
27
  contentLanguages.map((language) => ({
28
28
  id: language.code,
29
- name: t(language.label),
29
+ labelText: t(language.label),
30
30
  })),
31
31
  )
32
32
 
@@ -7,7 +7,7 @@ import { useSearchQueryParam } from "../hooks/use-search-query-param"
7
7
  import { CATEGORY, LANGUAGE, SEARCH, TYPE } from "../utils/search-query"
8
8
  import Select from "./Select"
9
9
 
10
- type FilterValue = { id: string; name: string }
10
+ type FilterValue = { id: string; labelText: string }
11
11
 
12
12
  interface Props {
13
13
  languages: FilterValue[]
@@ -66,7 +66,7 @@ export default function SearchFilter({
66
66
  initialValue={language}
67
67
  valueChange={(val) => setLanguage(val)}
68
68
  options={[
69
- { id: "", name: t("ln.search.all-languages") },
69
+ { id: "", labelText: t("ln.search.all-languages") },
70
70
  ...languages,
71
71
  ]}
72
72
  />
@@ -78,7 +78,7 @@ export default function SearchFilter({
78
78
  initialValue={type}
79
79
  valueChange={(val) => setType(val)}
80
80
  options={[
81
- { id: "", name: t("ln.search.all-types") },
81
+ { id: "", labelText: t("ln.search.all-types") },
82
82
  ...mediaTypes,
83
83
  ]}
84
84
  />
@@ -90,7 +90,7 @@ export default function SearchFilter({
90
90
  initialValue={category}
91
91
  valueChange={(val) => setCategory(val)}
92
92
  options={[
93
- { id: "", name: t("ln.search.all-categories") },
93
+ { id: "", labelText: t("ln.search.all-categories") },
94
94
  ...categories,
95
95
  ]}
96
96
  />
@@ -10,15 +10,15 @@ import SearchListReact from "./SearchList.tsx"
10
10
  const { t, currentLocale } = Astro.locals.i18n
11
11
 
12
12
  const categories: Record<string, string> = {}
13
- for (const { id, name } of await getUsedCategories(currentLocale, t)) {
14
- categories[id] = name
13
+ for (const { id, labelText } of await getUsedCategories(currentLocale, t)) {
14
+ categories[id] = labelText
15
15
  }
16
16
 
17
17
  const mediaTypes = Object.fromEntries(
18
18
  (await getMediaTypes()).map((type) => [
19
19
  type.id,
20
20
  {
21
- name: t(type.data.label),
21
+ labelText: t(type.data.label),
22
22
  icon: type.data.icon,
23
23
  coverImageStyle: type.data.coverImageStyle,
24
24
  },
@@ -28,7 +28,7 @@ const mediaTypes = Object.fromEntries(
28
28
  const languages = Object.fromEntries(
29
29
  contentLanguages.map((language) => [
30
30
  language.code,
31
- { direction: language.direction, name: t(language.label) },
31
+ { direction: language.direction, labelText: t(language.label) },
32
32
  ]),
33
33
  )
34
34
 
@@ -5,13 +5,13 @@ import { detailsPagePath } from "../../../utils/paths"
5
5
  import type { SearchItem } from "../api/search-response"
6
6
 
7
7
  export type MediaType = {
8
- name: string
8
+ labelText: string
9
9
  icon: string
10
10
  coverImageStyle: "default" | "book" | "video"
11
11
  }
12
12
 
13
13
  export type TranslatedLanguage = {
14
- name: string
14
+ labelText: string
15
15
  direction: "rtl" | "ltr"
16
16
  }
17
17
 
@@ -58,7 +58,7 @@ export default function SearchListItem({
58
58
  <h2 className="mb-1 line-clamp-2 text-balance text-sm font-bold text-gray-700 md:mb-3 md:text-base">
59
59
  <Icon
60
60
  className={`${mediaTypes[item.type].icon} me-2 align-bottom text-2xl text-gray-700`}
61
- ariaLabel={mediaTypes[item.type].name}
61
+ ariaLabel={mediaTypes[item.type].labelText}
62
62
  />
63
63
  <span>{item.title}</span>
64
64
  </h2>
@@ -70,7 +70,7 @@ export default function SearchListItem({
70
70
  )}
71
71
  {showLanguage && (
72
72
  <span className="rounded-lg border border-gray-300 px-2 py-1 text-gray-500">
73
- {languages[item.language].name}
73
+ {languages[item.language].labelText}
74
74
  </span>
75
75
  )}
76
76
  <ul