lightnet 3.10.4 → 3.10.5

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 (36) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/__e2e__/admin.spec.ts +71 -12
  3. package/__e2e__/fixtures/basics/node_modules/.bin/astro +2 -2
  4. package/__e2e__/fixtures/basics/package.json +2 -2
  5. package/package.json +4 -5
  6. package/src/admin/components/form/DynamicArray.tsx +74 -0
  7. package/src/admin/components/form/Input.tsx +5 -5
  8. package/src/admin/components/form/Select.tsx +8 -10
  9. package/src/admin/components/form/SubmitButton.tsx +9 -12
  10. package/src/admin/components/form/atoms/ErrorMessage.tsx +7 -28
  11. package/src/admin/components/form/atoms/Hint.tsx +2 -2
  12. package/src/admin/components/form/atoms/Label.tsx +5 -1
  13. package/src/admin/components/form/atoms/Legend.tsx +12 -2
  14. package/src/admin/components/form/hooks/use-field-error.tsx +3 -11
  15. package/src/admin/i18n/translations/en.yml +15 -5
  16. package/src/admin/pages/media/EditForm.tsx +20 -4
  17. package/src/admin/pages/media/EditRoute.astro +28 -9
  18. package/src/admin/pages/media/fields/Authors.tsx +35 -50
  19. package/src/admin/pages/media/fields/Categories.tsx +64 -0
  20. package/src/admin/pages/media/fields/Collections.tsx +103 -0
  21. package/src/admin/pages/media/media-item-store.ts +6 -1
  22. package/src/admin/types/media-item.ts +34 -2
  23. package/src/components/CategoriesSection.astro +2 -2
  24. package/src/components/MediaGallerySection.astro +3 -3
  25. package/src/components/MediaList.astro +2 -2
  26. package/src/content/get-categories.ts +18 -3
  27. package/src/i18n/resolve-language.ts +1 -1
  28. package/src/layouts/Page.astro +3 -2
  29. package/src/layouts/components/LanguagePicker.astro +1 -1
  30. package/src/pages/details-page/components/more-details/Languages.astro +2 -2
  31. package/src/pages/search-page/components/SearchFilter.astro +7 -7
  32. package/src/pages/search-page/components/SearchFilter.tsx +4 -4
  33. package/src/pages/search-page/components/SearchList.astro +4 -4
  34. package/src/pages/search-page/components/SearchListItem.tsx +4 -4
  35. package/src/pages/search-page/components/Select.tsx +3 -3
  36. package/src/pages/search-page/hooks/use-search.ts +4 -4
@@ -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"
64
+ class="block py-4 text-gray-200 underline"
49
65
  href=`/${Astro.currentLocale}/media/faithful-freestyle--en`
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>
@@ -1,58 +1,43 @@
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
8
  export default function Authors({ control }: { control: Control<MediaItem> }) {
11
- const { fields, append, remove } = useFieldArray({
12
- name: "authors",
13
- control,
14
- })
15
- const { t } = useI18n()
16
9
  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>
10
+ <DynamicArray
11
+ control={control}
12
+ name="authors"
13
+ label="ln.admin.authors"
14
+ renderElement={(index) => <AuthorInput index={index} control={control} />}
15
+ addButton={{
16
+ label: "ln.admin.add-author",
17
+ onClick: (append, index) =>
18
+ append({ value: "" }, { focusName: `authors.${index}.value` }),
19
+ }}
20
+ />
21
+ )
22
+ }
23
+
24
+ function AuthorInput({
25
+ index,
26
+ control,
27
+ }: {
28
+ index: number
29
+ control: Control<MediaItem>
30
+ }) {
31
+ const name = `authors.${index}.value` as const
32
+ const errorMessage = useFieldError({ name, control })
33
+ return (
34
+ <>
35
+ <input
36
+ className={`dy-input dy-input-bordered shadow-inner ${errorMessage ? "dy-input-error" : ""}`}
37
+ aria-invalid={!!errorMessage}
38
+ {...control.register(name)}
39
+ />
40
+ <ErrorMessage message={errorMessage} />
41
+ </>
57
42
  )
58
43
  }
@@ -0,0 +1,64 @@
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
+ }: {
12
+ control: Control<MediaItem>
13
+ categories: { id: string; labelText: string }[]
14
+ }) {
15
+ return (
16
+ <DynamicArray
17
+ control={control}
18
+ name="categories"
19
+ label="ln.categories"
20
+ renderElement={(index) => (
21
+ <CategorySelect
22
+ categories={categories}
23
+ control={control}
24
+ index={index}
25
+ />
26
+ )}
27
+ addButton={{
28
+ label: "ln.admin.add-category",
29
+ onClick: (append, index) =>
30
+ append({ value: "" }, { focusName: `categories.${index}.value` }),
31
+ }}
32
+ />
33
+ )
34
+ }
35
+
36
+ function CategorySelect({
37
+ control,
38
+ categories,
39
+ index,
40
+ }: {
41
+ control: Control<MediaItem>
42
+ categories: { id: string; labelText: string }[]
43
+ index: number
44
+ }) {
45
+ const name = `categories.${index}.value` as const
46
+ const errorMessage = useFieldError({ name, control })
47
+ return (
48
+ <>
49
+ <select
50
+ {...control.register(name)}
51
+ id={name}
52
+ aria-invalid={!!errorMessage}
53
+ className={`dy-select dy-select-bordered text-base shadow-sm ${errorMessage ? "dy-select-error" : ""}`}
54
+ >
55
+ {categories.map(({ id, labelText }) => (
56
+ <option key={id} value={id}>
57
+ {labelText}
58
+ </option>
59
+ ))}
60
+ </select>
61
+ <ErrorMessage message={errorMessage} />
62
+ </>
63
+ )
64
+ }
@@ -0,0 +1,103 @@
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
+ }: {
13
+ control: Control<MediaItem>
14
+ collections: { id: string; labelText: string }[]
15
+ }) {
16
+ return (
17
+ <DynamicArray
18
+ control={control}
19
+ name="collections"
20
+ label="ln.admin.collections"
21
+ renderElement={(index) => (
22
+ <div className="flex w-full flex-col py-2">
23
+ <CollectionSelect
24
+ collections={collections}
25
+ control={control}
26
+ index={index}
27
+ />
28
+ <CollectionIndex control={control} index={index} />
29
+ </div>
30
+ )}
31
+ addButton={{
32
+ label: "ln.admin.add-collection",
33
+ onClick: (append, index) =>
34
+ append(
35
+ { collection: "" },
36
+ { focusName: `collections.${index}.collection` },
37
+ ),
38
+ }}
39
+ />
40
+ )
41
+ }
42
+
43
+ function CollectionSelect({
44
+ control,
45
+ collections,
46
+ index,
47
+ }: {
48
+ control: Control<MediaItem>
49
+ collections: { id: string; labelText: string }[]
50
+ index: number
51
+ }) {
52
+ const name = `collections.${index}.collection` as const
53
+ const errorMessage = useFieldError({ name, control })
54
+ return (
55
+ <>
56
+ <Label for={name} label="ln.admin.name" size="xs" />
57
+ <select
58
+ {...control.register(name)}
59
+ id={name}
60
+ aria-invalid={!!errorMessage}
61
+ className={`dy-select dy-select-bordered text-base shadow-sm ${errorMessage ? "dy-select-error" : ""}`}
62
+ >
63
+ {collections.map(({ id, labelText }) => (
64
+ <option key={id} value={id}>
65
+ {labelText}
66
+ </option>
67
+ ))}
68
+ </select>
69
+ <ErrorMessage message={errorMessage} />
70
+ </>
71
+ )
72
+ }
73
+
74
+ function CollectionIndex({
75
+ control,
76
+ index,
77
+ }: {
78
+ control: Control<MediaItem>
79
+ index: number
80
+ }) {
81
+ const name = `collections.${index}.index` as const
82
+ const errorMessage = useFieldError({ name, control })
83
+ return (
84
+ <>
85
+ <Label
86
+ for={name}
87
+ label="ln.admin.position-in-collection"
88
+ size="xs"
89
+ className="mt-3"
90
+ />
91
+ <input
92
+ className={`dy-input dy-input-bordered shadow-inner ${errorMessage ? "dy-input-error" : ""}`}
93
+ aria-invalid={!!errorMessage}
94
+ type="number"
95
+ step={1}
96
+ {...control.register(name, {
97
+ setValueAs: (value) => (value === "" ? undefined : Number(value)),
98
+ })}
99
+ />
100
+ <ErrorMessage message={errorMessage} />
101
+ </>
102
+ )
103
+ }
@@ -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,7 +31,20 @@ 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),
17
49
  })
18
50
 
@@ -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