lightnet 4.0.8 → 4.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +29 -0
- package/exports/content.ts +5 -7
- package/package.json +11 -11
- package/src/astro-integration/config.ts +15 -25
- package/src/components/CategoriesSection.astro +2 -2
- package/src/components/MediaGallerySection.astro +4 -9
- package/src/components/MediaList.astro +13 -13
- package/src/components/VideoPlayer.astro +4 -4
- package/src/content/content-schema.ts +5 -292
- package/src/content/get-categories.ts +26 -29
- package/src/content/get-languages.ts +13 -26
- package/src/content/get-media-collections.ts +1 -1
- package/src/content/get-media-items.ts +1 -1
- package/src/content/get-media-types.ts +21 -14
- package/src/content/query-media-items.ts +2 -1
- package/src/content/schema/category.ts +40 -0
- package/src/content/schema/media-collection.ts +31 -0
- package/src/content/schema/media-item.ts +137 -0
- package/src/content/schema/media-type.ts +90 -0
- package/src/i18n/locals.d.ts +22 -0
- package/src/i18n/locals.ts +3 -1
- package/src/i18n/record-translation.ts +74 -0
- package/src/i18n/resolve-language.ts +12 -5
- package/src/i18n/translate-map.ts +129 -19
- package/src/i18n/translate.ts +38 -0
- package/src/i18n/translation-map-schema.ts +17 -0
- package/src/i18n/translations/TRANSLATION-STATUS.md +13 -41
- package/src/i18n/translations/ar.yml +1 -0
- package/src/i18n/translations/bn.yml +1 -0
- package/src/i18n/translations/de.yml +1 -0
- package/src/i18n/translations/en.yml +24 -21
- package/src/i18n/translations/es.yml +1 -0
- package/src/i18n/translations/fi.yml +1 -0
- package/src/i18n/translations/fr.yml +1 -0
- package/src/i18n/translations/hi.yml +1 -0
- package/src/i18n/translations/kk.yml +3 -0
- package/src/i18n/translations/pt.yml +1 -0
- package/src/i18n/translations/ru.yml +1 -0
- package/src/i18n/translations/uk.yml +1 -0
- package/src/i18n/translations/ur.yml +1 -0
- package/src/i18n/translations/zh.yml +1 -0
- package/src/i18n/translations.ts +5 -2
- package/src/layouts/Page.astro +3 -4
- package/src/layouts/components/Footer.astro +63 -10
- package/src/layouts/components/LanguagePicker.astro +2 -2
- package/src/layouts/components/MenuItem.astro +4 -4
- package/src/layouts/components/PageNavigation.astro +21 -29
- package/src/layouts/components/PageTitle.astro +4 -13
- package/src/pages/details-page/DefaultDetailsPage.astro +2 -15
- package/src/pages/details-page/components/AudioPanel.astro +5 -4
- package/src/pages/details-page/components/AudioPlayer.astro +4 -4
- package/src/pages/details-page/components/ContentSection.astro +42 -43
- package/src/pages/details-page/components/MediaCollection.astro +2 -6
- package/src/pages/details-page/components/main-details/OpenButton.astro +25 -19
- package/src/pages/details-page/components/main-details/ShareButton.astro +1 -1
- package/src/pages/details-page/components/more-details/Categories.astro +3 -5
- package/src/pages/details-page/components/more-details/Languages.astro +7 -3
- package/src/pages/details-page/utils/create-content-metadata.ts +40 -56
- package/src/pages/search-page/api/search.ts +1 -1
- package/src/pages/search-page/components/SearchFilter.astro +5 -5
- package/src/pages/search-page/components/SearchList.astro +22 -19
- package/src/utils/is-external-url.ts +34 -0
- package/src/utils/link-attributes.ts +10 -0
- package/src/utils/paths.ts +6 -0
- package/src/utils/urls.ts +12 -24
- package/src/astro-integration/validators/validate-inline-translations.ts +0 -51
|
@@ -1,8 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
type TranslateMapFn,
|
|
3
|
-
type TranslationMap,
|
|
4
|
-
} from "../../../i18n/translate-map"
|
|
5
|
-
import { isExternalUrl } from "../../../utils/urls"
|
|
1
|
+
import { isExternalUrl } from "../../../utils/is-external-url"
|
|
6
2
|
|
|
7
3
|
export type UrlType =
|
|
8
4
|
| "link"
|
|
@@ -15,49 +11,45 @@ export type UrlType =
|
|
|
15
11
|
|
|
16
12
|
const KNOWN_EXTENSIONS: Record<
|
|
17
13
|
string,
|
|
18
|
-
{ type: UrlType;
|
|
14
|
+
{ type: UrlType; isDownload?: boolean } | undefined
|
|
19
15
|
> = {
|
|
20
|
-
htm: { type: "link"
|
|
21
|
-
html: { type: "link"
|
|
22
|
-
php: { type: "link"
|
|
23
|
-
json: { type: "source"
|
|
24
|
-
xml: { type: "source"
|
|
25
|
-
md: { type: "source"
|
|
26
|
-
svg: { type: "image"
|
|
27
|
-
jpg: { type: "image"
|
|
28
|
-
jpeg: { type: "image"
|
|
29
|
-
png: { type: "image"
|
|
30
|
-
gif: { type: "image"
|
|
31
|
-
ico: { type: "image"
|
|
32
|
-
webp: { type: "image"
|
|
33
|
-
mp3: { type: "audio"
|
|
34
|
-
wav: { type: "audio"
|
|
35
|
-
aac: { type: "audio"
|
|
36
|
-
ogg: { type: "audio"
|
|
37
|
-
mp4: { type: "video"
|
|
38
|
-
webm: { type: "video"
|
|
39
|
-
ogv: { type: "video"
|
|
40
|
-
pdf: { type: "text"
|
|
41
|
-
txt: { type: "text"
|
|
42
|
-
epub: { type: "text" },
|
|
43
|
-
zip: { type: "package" },
|
|
44
|
-
ppt: { type: "text" },
|
|
45
|
-
pptx: { type: "text" },
|
|
46
|
-
doc: { type: "text" },
|
|
47
|
-
docx: { type: "text" },
|
|
16
|
+
htm: { type: "link" },
|
|
17
|
+
html: { type: "link" },
|
|
18
|
+
php: { type: "link" },
|
|
19
|
+
json: { type: "source" },
|
|
20
|
+
xml: { type: "source" },
|
|
21
|
+
md: { type: "source" },
|
|
22
|
+
svg: { type: "image" },
|
|
23
|
+
jpg: { type: "image" },
|
|
24
|
+
jpeg: { type: "image" },
|
|
25
|
+
png: { type: "image" },
|
|
26
|
+
gif: { type: "image" },
|
|
27
|
+
ico: { type: "image" },
|
|
28
|
+
webp: { type: "image" },
|
|
29
|
+
mp3: { type: "audio" },
|
|
30
|
+
wav: { type: "audio" },
|
|
31
|
+
aac: { type: "audio" },
|
|
32
|
+
ogg: { type: "audio" },
|
|
33
|
+
mp4: { type: "video" },
|
|
34
|
+
webm: { type: "video" },
|
|
35
|
+
ogv: { type: "video" },
|
|
36
|
+
pdf: { type: "text" },
|
|
37
|
+
txt: { type: "text" },
|
|
38
|
+
epub: { type: "text", isDownload: true },
|
|
39
|
+
zip: { type: "package", isDownload: true },
|
|
40
|
+
ppt: { type: "text", isDownload: true },
|
|
41
|
+
pptx: { type: "text", isDownload: true },
|
|
42
|
+
doc: { type: "text", isDownload: true },
|
|
43
|
+
docx: { type: "text", isDownload: true },
|
|
48
44
|
} as const
|
|
49
45
|
|
|
50
|
-
export function createContentMetadata(
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
},
|
|
58
|
-
tMap: TranslateMapFn,
|
|
59
|
-
context: { path: (string | number)[] },
|
|
60
|
-
) {
|
|
46
|
+
export function createContentMetadata({
|
|
47
|
+
url,
|
|
48
|
+
labelText: customLabel,
|
|
49
|
+
}: {
|
|
50
|
+
url: string
|
|
51
|
+
labelText?: string
|
|
52
|
+
}) {
|
|
61
53
|
const isExternal = isExternalUrl(url)
|
|
62
54
|
const path = isExternal ? new URL(url).pathname : url
|
|
63
55
|
|
|
@@ -72,24 +64,16 @@ export function createContentMetadata(
|
|
|
72
64
|
? lastPathSegment.slice(0, -(extension.length + 1))
|
|
73
65
|
: undefined
|
|
74
66
|
|
|
75
|
-
const labelText =
|
|
76
|
-
(customLabel &&
|
|
77
|
-
tMap(customLabel, {
|
|
78
|
-
path: [...context.path, "label"],
|
|
79
|
-
})) ??
|
|
80
|
-
fileName ??
|
|
81
|
-
linkName
|
|
67
|
+
const labelText = customLabel ?? fileName ?? linkName
|
|
82
68
|
const type = KNOWN_EXTENSIONS[extension]?.type ?? "link"
|
|
83
|
-
const
|
|
84
|
-
!hasExtension || !!KNOWN_EXTENSIONS[extension]?.canBeOpened
|
|
69
|
+
const isDownload = KNOWN_EXTENSIONS[extension]?.isDownload
|
|
85
70
|
|
|
86
71
|
return {
|
|
87
72
|
url,
|
|
88
73
|
extension,
|
|
89
74
|
isExternal,
|
|
90
75
|
labelText,
|
|
91
|
-
|
|
76
|
+
isDownload,
|
|
92
77
|
type,
|
|
93
|
-
target: isExternal ? "_blank" : "_self",
|
|
94
78
|
} as const
|
|
95
79
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type { APIRoute } from "astro"
|
|
2
2
|
import { getImage } from "astro:assets"
|
|
3
3
|
|
|
4
|
-
import type { MediaItemEntry } from "../../../content/content-schema"
|
|
5
4
|
import { getMediaItems } from "../../../content/get-media-items"
|
|
5
|
+
import type { MediaItemEntry } from "../../../content/schema/media-item"
|
|
6
6
|
import { markdownToText } from "../../../utils/markdown"
|
|
7
7
|
import type { SearchItem } from "./search-response"
|
|
8
8
|
|
|
@@ -2,22 +2,22 @@
|
|
|
2
2
|
import config from "virtual:lightnet/config"
|
|
3
3
|
|
|
4
4
|
import { getUsedCategories } from "../../../content/get-categories"
|
|
5
|
-
import {
|
|
5
|
+
import { getContentLanguages } from "../../../content/get-languages"
|
|
6
6
|
import { getUsedMediaTypes } from "../../../content/get-media-types"
|
|
7
7
|
import { prepareI18nConfig } from "../../../i18n/react/prepare-i18n-config"
|
|
8
8
|
import SearchFilterReact from "./SearchFilter.tsx"
|
|
9
9
|
|
|
10
|
-
const { currentLocale,
|
|
10
|
+
const { currentLocale, tConfigField, tContentField } = Astro.locals.i18n
|
|
11
11
|
|
|
12
|
-
const categories = (await getUsedCategories(currentLocale,
|
|
12
|
+
const categories = (await getUsedCategories(currentLocale, tContentField)).map(
|
|
13
13
|
({ id, labelText }) => ({ id, labelText }),
|
|
14
14
|
)
|
|
15
15
|
|
|
16
|
-
const mediaTypes = (await getUsedMediaTypes(currentLocale,
|
|
16
|
+
const mediaTypes = (await getUsedMediaTypes(currentLocale, tContentField)).map(
|
|
17
17
|
({ id, labelText }) => ({ id, labelText }),
|
|
18
18
|
)
|
|
19
19
|
|
|
20
|
-
const languages = (await
|
|
20
|
+
const languages = (await getContentLanguages(currentLocale, tConfigField)).map(
|
|
21
21
|
({ code: id, labelText }) => ({ id, labelText }),
|
|
22
22
|
)
|
|
23
23
|
|
|
@@ -2,42 +2,45 @@
|
|
|
2
2
|
import { getCollection } from "astro:content"
|
|
3
3
|
|
|
4
4
|
import { getUsedCategories } from "../../../content/get-categories"
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
5
|
+
import { getContentLanguages } from "../../../content/get-languages"
|
|
6
|
+
import { getTranslatedMediaTypes } from "../../../content/get-media-types"
|
|
7
7
|
import { prepareI18nConfig } from "../../../i18n/react/prepare-i18n-config"
|
|
8
8
|
import SearchListReact from "./SearchList.tsx"
|
|
9
9
|
|
|
10
|
-
const { currentLocale,
|
|
10
|
+
const { currentLocale, tContentField, tConfigField } = Astro.locals.i18n
|
|
11
11
|
|
|
12
12
|
const categories: Record<string, string> = {}
|
|
13
|
-
for (const { id, labelText } of await getUsedCategories(
|
|
13
|
+
for (const { id, labelText } of await getUsedCategories(
|
|
14
|
+
currentLocale,
|
|
15
|
+
tContentField,
|
|
16
|
+
)) {
|
|
14
17
|
categories[id] = labelText
|
|
15
18
|
}
|
|
16
19
|
|
|
17
20
|
const mediaTypes = Object.fromEntries(
|
|
18
|
-
(await
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
21
|
+
(await getTranslatedMediaTypes(currentLocale, tContentField)).map(
|
|
22
|
+
({ labelText, icon, coverImageStyle, id }) => [
|
|
23
|
+
id,
|
|
24
|
+
{
|
|
25
|
+
labelText,
|
|
26
|
+
icon,
|
|
27
|
+
coverImageStyle,
|
|
28
|
+
},
|
|
29
|
+
],
|
|
30
|
+
),
|
|
28
31
|
)
|
|
29
32
|
|
|
30
|
-
const
|
|
33
|
+
const contentLanguages = await getContentLanguages(currentLocale, tConfigField)
|
|
31
34
|
const languages = Object.fromEntries(
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
{ direction
|
|
35
|
+
contentLanguages.map(({ code, direction, labelText }) => [
|
|
36
|
+
code,
|
|
37
|
+
{ direction, labelText },
|
|
35
38
|
]),
|
|
36
39
|
)
|
|
37
40
|
|
|
38
41
|
const mediaItemsTotal = (await getCollection("media")).length
|
|
39
42
|
|
|
40
|
-
const showLanguage =
|
|
43
|
+
const showLanguage = contentLanguages.length > 1
|
|
41
44
|
|
|
42
45
|
const i18nConfig = prepareI18nConfig(Astro.locals.i18n, [
|
|
43
46
|
"ln.search.no-results",
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import config from "virtual:lightnet/config"
|
|
2
|
+
|
|
3
|
+
import { parseUrl } from "./urls"
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Test if a given url is outside this site.
|
|
7
|
+
* Will return false if the url is relative or if it
|
|
8
|
+
* starts with the site config from astro config.
|
|
9
|
+
*
|
|
10
|
+
* @param url to test
|
|
11
|
+
* @returns is the url external?
|
|
12
|
+
*/
|
|
13
|
+
export function isExternalUrl(url: string) {
|
|
14
|
+
const parsedUrl = parseUrl(url)
|
|
15
|
+
if (!parsedUrl) {
|
|
16
|
+
return false
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (config.internalDomains.includes(parsedUrl.hostname)) {
|
|
20
|
+
return false
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const { SITE: site } = import.meta.env
|
|
24
|
+
if (!site) {
|
|
25
|
+
return true
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const parsedSiteUrl = parseUrl(site)
|
|
29
|
+
if (!parsedSiteUrl) {
|
|
30
|
+
return true
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return !parsedUrl.href.startsWith(parsedSiteUrl.href)
|
|
34
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { isExternalUrl } from "./is-external-url"
|
|
2
|
+
|
|
3
|
+
export function getLinkAttributes(href: string) {
|
|
4
|
+
const isExternal = isExternalUrl(href)
|
|
5
|
+
return {
|
|
6
|
+
href,
|
|
7
|
+
target: isExternal ? "_blank" : "_self",
|
|
8
|
+
rel: isExternal ? "noopener noreferrer" : undefined,
|
|
9
|
+
}
|
|
10
|
+
}
|
package/src/utils/paths.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { isAbsoluteUrl } from "./urls"
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* Prefix a site-internal path with Astro's configured base path.
|
|
3
5
|
*
|
|
@@ -91,6 +93,10 @@ export function searchPagePath(
|
|
|
91
93
|
* @returns resolved path. Eg. '/en/about' for input "en" and "/about"
|
|
92
94
|
*/
|
|
93
95
|
export function localizePath(locale: string | undefined, path: string) {
|
|
96
|
+
if (isAbsoluteUrl(path)) {
|
|
97
|
+
return path
|
|
98
|
+
}
|
|
99
|
+
|
|
94
100
|
return pathWithBase(
|
|
95
101
|
`${locale ? `/${locale}` : ""}/${path.replace(/^\//, "")}`,
|
|
96
102
|
)
|
package/src/utils/urls.ts
CHANGED
|
@@ -1,28 +1,16 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Test if a given url is outside this site.
|
|
5
|
-
* Will return false if the url is relative or if it
|
|
6
|
-
* starts with the site config from astro config.
|
|
7
|
-
*
|
|
8
|
-
* @param url to test
|
|
9
|
-
* @returns is the url external?
|
|
10
|
-
*/
|
|
11
|
-
export function isExternalUrl(url: string) {
|
|
12
|
-
let parsedUrl
|
|
1
|
+
export function parseUrl(url: string) {
|
|
13
2
|
try {
|
|
14
|
-
|
|
15
|
-
parsedUrl = new URL(url)
|
|
3
|
+
return new URL(url)
|
|
16
4
|
} catch {
|
|
17
|
-
//
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
const { SITE: site } = import.meta.env
|
|
24
|
-
if (!site) {
|
|
25
|
-
return true
|
|
5
|
+
// Support host-like values such as `example.com` without treating plain paths as external.
|
|
6
|
+
if (/^(localhost(?::\d+)?|[^/\s]+\.[^/\s]+)(?:[/?#]|$)/.test(url)) {
|
|
7
|
+
return new URL(`https://${url}`)
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
return null
|
|
26
11
|
}
|
|
27
|
-
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function isAbsoluteUrl(url: string) {
|
|
15
|
+
return !!parseUrl(url)
|
|
28
16
|
}
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
import { z } from "astro/zod"
|
|
2
|
-
|
|
3
|
-
export const validateInlineTranslations = (
|
|
4
|
-
config: {
|
|
5
|
-
title: Record<string, string>
|
|
6
|
-
languages: { label: Record<string, string> }[]
|
|
7
|
-
mainMenu?: { label: Record<string, string> }[]
|
|
8
|
-
logo?: { alt?: Record<string, string> }
|
|
9
|
-
},
|
|
10
|
-
locales: string[],
|
|
11
|
-
defaultLocale: string,
|
|
12
|
-
ctx: z.RefinementCtx,
|
|
13
|
-
) => {
|
|
14
|
-
const validateInlineTranslation = (
|
|
15
|
-
inlineTranslation: Record<string, string> | undefined,
|
|
16
|
-
path: (string | number)[],
|
|
17
|
-
) => {
|
|
18
|
-
if (!inlineTranslation) {
|
|
19
|
-
return
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
if (!(defaultLocale in inlineTranslation)) {
|
|
23
|
-
ctx.addIssue({
|
|
24
|
-
code: "custom",
|
|
25
|
-
message: `Missing translation for default locale "${defaultLocale}"`,
|
|
26
|
-
path: [...path, defaultLocale],
|
|
27
|
-
})
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
for (const locale of Object.keys(inlineTranslation)) {
|
|
31
|
-
if (locales.includes(locale)) {
|
|
32
|
-
continue
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
ctx.addIssue({
|
|
36
|
-
code: "custom",
|
|
37
|
-
message: `Invalid locale "${locale}". Inline translations only support configured site locales: ${locales.join(", ")}`,
|
|
38
|
-
path: [...path, locale],
|
|
39
|
-
})
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
validateInlineTranslation(config.title, ["title"])
|
|
44
|
-
for (const [index, language] of config.languages.entries()) {
|
|
45
|
-
validateInlineTranslation(language.label, ["languages", index, "label"])
|
|
46
|
-
}
|
|
47
|
-
for (const [index, link] of (config.mainMenu ?? []).entries()) {
|
|
48
|
-
validateInlineTranslation(link.label, ["mainMenu", index, "label"])
|
|
49
|
-
}
|
|
50
|
-
validateInlineTranslation(config.logo?.alt, ["logo", "alt"])
|
|
51
|
-
}
|