lightnet 4.2.1 → 4.4.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.
- package/CHANGELOG.md +24 -0
- package/package.json +9 -9
- package/src/astro-integration/config.ts +19 -11
- package/src/i18n/translations/ar.yml +1 -1
- package/src/i18n/translations/bn.yml +1 -1
- package/src/i18n/translations/de.yml +1 -1
- package/src/i18n/translations/en.yml +1 -1
- package/src/i18n/translations/es.yml +1 -1
- package/src/i18n/translations/fi.yml +1 -1
- package/src/i18n/translations/fr.yml +1 -1
- package/src/i18n/translations/hi.yml +1 -1
- package/src/i18n/translations/kk.yml +1 -1
- package/src/i18n/translations/pt.yml +1 -1
- package/src/i18n/translations/ru.yml +1 -1
- package/src/i18n/translations/uk.yml +1 -1
- package/src/i18n/translations/ur.yml +1 -1
- package/src/i18n/translations/zh.yml +1 -1
- package/src/i18n/translations.ts +1 -1
- package/src/layouts/Page.astro +27 -6
- package/src/layouts/components/footer/Credits.astro +14 -0
- package/src/layouts/components/footer/Footer.astro +88 -0
- package/src/layouts/components/footer/LanguageSelectionMenu.astro +44 -0
- package/src/layouts/components/{LightNetLogo.svg → footer/LightNetLogo.svg} +1 -1
- package/src/layouts/components/footer/format-footer-text.ts +14 -0
- package/src/layouts/components/get-language-selection-menu-items.ts +47 -0
- package/src/layouts/components/header/LanguageSelectionMenu.astro +34 -0
- package/src/layouts/components/{MenuItem.astro → header/MenuItem.astro} +1 -1
- package/src/layouts/components/{PageNavigation.astro → header/PageNavigation.astro} +4 -4
- package/src/layouts/components/{PageTitle.astro → header/PageTitle.astro} +1 -1
- package/src/layouts/components/Footer.astro +0 -110
- package/src/layouts/components/LanguagePicker.astro +0 -49
- /package/src/layouts/components/{Header.astro → header/Header.astro} +0 -0
- /package/src/layouts/components/{Menu.astro → header/Menu.astro} +0 -0
- /package/src/layouts/components/{Favicon.astro → meta/Favicon.astro} +0 -0
- /package/src/layouts/components/{ViewTransition.astro → meta/ViewTransition.astro} +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,29 @@
|
|
|
1
1
|
# lightnet
|
|
2
2
|
|
|
3
|
+
## 4.4.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#409](https://github.com/LightNetDev/LightNet/pull/409) [`6d214f6`](https://github.com/LightNetDev/LightNet/commit/6d214f6b620dc00b8860da50006778649e977d59) - Add ` disallowSearchIndexing` settings to exclude entire site or pages from search engines.
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- [#409](https://github.com/LightNetDev/LightNet/pull/409) [`6d214f6`](https://github.com/LightNetDev/LightNet/commit/6d214f6b620dc00b8860da50006778649e977d59) - Improved footer layout
|
|
12
|
+
|
|
13
|
+
## 4.3.0
|
|
14
|
+
|
|
15
|
+
### Minor Changes
|
|
16
|
+
|
|
17
|
+
- [#405](https://github.com/LightNetDev/LightNet/pull/405) [`522ffae`](https://github.com/LightNetDev/LightNet/commit/522ffaed830b9e68f5479567ee326c5439ea0f27) - Add a footer language selector with shared locale-path logic and a footer-specific accessible label.
|
|
18
|
+
|
|
19
|
+
- [#408](https://github.com/LightNetDev/LightNet/pull/408) [`d0aaa12`](https://github.com/LightNetDev/LightNet/commit/d0aaa12477bec601bcd10d46290f287dc2174b60) - Support a `{{year}}` placeholder in localized footer text.
|
|
20
|
+
|
|
21
|
+
### Patch Changes
|
|
22
|
+
|
|
23
|
+
- [#407](https://github.com/LightNetDev/LightNet/pull/407) [`e5fa59b`](https://github.com/LightNetDev/LightNet/commit/e5fa59bbfce0aca158db08c91201fc8e03ecbaeb) - Update dependencies
|
|
24
|
+
|
|
25
|
+
- [#405](https://github.com/LightNetDev/LightNet/pull/405) [`522ffae`](https://github.com/LightNetDev/LightNet/commit/522ffaed830b9e68f5479567ee326c5439ea0f27) - Rename the shared language selection translation key to `ln.select-language` and update all locale files, comments, and call sites. This changes the public translation key used by downstream custom translations.
|
|
26
|
+
|
|
3
27
|
## 4.2.1
|
|
4
28
|
|
|
5
29
|
### Patch Changes
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"description": "LightNet makes it easy to run your own digital media library.",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
6
|
-
"version": "4.
|
|
6
|
+
"version": "4.4.0",
|
|
7
7
|
"repository": {
|
|
8
8
|
"type": "git",
|
|
9
9
|
"url": "https://github.com/LightNetDev/lightnet",
|
|
@@ -51,29 +51,29 @@
|
|
|
51
51
|
"tailwindcss": ">=3.4.0 <4.0.0"
|
|
52
52
|
},
|
|
53
53
|
"dependencies": {
|
|
54
|
-
"@astrojs/react": "^5.0.
|
|
54
|
+
"@astrojs/react": "^5.0.7",
|
|
55
55
|
"@iconify-json/lucide": "^1.2.111",
|
|
56
56
|
"@iconify-json/mdi": "^1.2.3",
|
|
57
57
|
"@iconify/tailwind": "^1.2.0",
|
|
58
58
|
"@tailwindcss/typography": "^0.5.19",
|
|
59
|
-
"@tanstack/react-virtual": "^3.
|
|
59
|
+
"@tanstack/react-virtual": "^3.14.2",
|
|
60
60
|
"autoprefixer": "^10.5.0",
|
|
61
61
|
"embla-carousel": "^8.6.0",
|
|
62
62
|
"embla-carousel-wheel-gestures": "^8.1.0",
|
|
63
|
-
"fuse.js": "^7.
|
|
64
|
-
"i18next": "^26.3.
|
|
63
|
+
"fuse.js": "^7.4.2",
|
|
64
|
+
"i18next": "^26.3.1",
|
|
65
65
|
"lucide-react": "^1.17.0",
|
|
66
|
-
"marked": "^18.0.
|
|
66
|
+
"marked": "^18.0.5",
|
|
67
67
|
"postcss": "^8.5.15",
|
|
68
68
|
"postcss-load-config": "^6.0.1",
|
|
69
69
|
"yaml": "^2.9.0"
|
|
70
70
|
},
|
|
71
71
|
"devDependencies": {
|
|
72
72
|
"@playwright/test": "^1.60.0",
|
|
73
|
-
"@types/react": "^19.2.
|
|
74
|
-
"astro": "^6.4.
|
|
73
|
+
"@types/react": "^19.2.17",
|
|
74
|
+
"astro": "^6.4.4",
|
|
75
75
|
"typescript": "^6.0.3",
|
|
76
|
-
"vitest": "^4.1.
|
|
76
|
+
"vitest": "^4.1.8",
|
|
77
77
|
"@internal/e2e-test-utils": "^0.0.1"
|
|
78
78
|
},
|
|
79
79
|
"engines": {
|
|
@@ -110,18 +110,10 @@ export const configSchema = z.object({
|
|
|
110
110
|
*/
|
|
111
111
|
favicon: faviconSchema.array().optional(),
|
|
112
112
|
/**
|
|
113
|
-
*
|
|
113
|
+
* If set too true, this sets robots meta tag to no index to tell search engines like google to
|
|
114
|
+
* not add any page of this media library to their search results.
|
|
114
115
|
*/
|
|
115
|
-
|
|
116
|
-
/**
|
|
117
|
-
* Optional localized text to display in your site's footer.
|
|
118
|
-
*/
|
|
119
|
-
footerText: translationMapSchema.optional(),
|
|
120
|
-
/**
|
|
121
|
-
* Optional links to display in your site's footer.
|
|
122
|
-
*/
|
|
123
|
-
footerLinks: z.array(linkSchema).optional(),
|
|
124
|
-
|
|
116
|
+
disallowSearchIndexing: z.boolean().optional(),
|
|
125
117
|
/**
|
|
126
118
|
* Link to manifest file within public/ folder
|
|
127
119
|
*/
|
|
@@ -182,6 +174,22 @@ export const configSchema = z.object({
|
|
|
182
174
|
* @example "./src/components/MyHeadTag.astro"
|
|
183
175
|
*/
|
|
184
176
|
headComponent: z.string().optional(),
|
|
177
|
+
/**
|
|
178
|
+
* Enable displaying a “Powered by LightNet” link in your site’s footer.
|
|
179
|
+
*/
|
|
180
|
+
credits: z.boolean().default(false),
|
|
181
|
+
/**
|
|
182
|
+
* Optional localized text to display in your site's footer.
|
|
183
|
+
*
|
|
184
|
+
* Use `{{year}}` to render the current calendar year.
|
|
185
|
+
*
|
|
186
|
+
* @example { en: "Copyright {{year}} LightNet" }
|
|
187
|
+
*/
|
|
188
|
+
footerText: translationMapSchema.optional(),
|
|
189
|
+
/**
|
|
190
|
+
* Optional links to display in your site's footer.
|
|
191
|
+
*/
|
|
192
|
+
footerLinks: z.array(linkSchema).optional(),
|
|
185
193
|
/**
|
|
186
194
|
* Path to an Astro component to be added at the bottom of all pages.
|
|
187
195
|
*
|
|
@@ -14,7 +14,7 @@ ln.details.download: Download
|
|
|
14
14
|
ln.details.edit: Bearbeiten
|
|
15
15
|
ln.details.share: Teilen
|
|
16
16
|
ln.header.open-main-menu: Öffne Hauptmenü
|
|
17
|
-
ln.
|
|
17
|
+
ln.select-language: Sprache auswählen
|
|
18
18
|
ln.home.title: Startseite
|
|
19
19
|
ln.search.all-categories: Alle Kategorien
|
|
20
20
|
ln.search.all-languages: Alle Sprachen
|
package/src/i18n/translations.ts
CHANGED
|
@@ -82,7 +82,7 @@ export type LightNetTranslationKey =
|
|
|
82
82
|
| "ln.details.part-of-collection"
|
|
83
83
|
| "ln.details.download"
|
|
84
84
|
| "ln.header.open-main-menu"
|
|
85
|
-
| "ln.
|
|
85
|
+
| "ln.select-language"
|
|
86
86
|
| "ln.home.title"
|
|
87
87
|
| "ln.search.all-categories"
|
|
88
88
|
| "ln.search.all-languages"
|
package/src/layouts/Page.astro
CHANGED
|
@@ -7,10 +7,10 @@ import config from "virtual:lightnet/config"
|
|
|
7
7
|
|
|
8
8
|
import { resolveLanguage } from "../i18n/resolve-language"
|
|
9
9
|
import { pathWithBase } from "../utils/paths"
|
|
10
|
-
import
|
|
11
|
-
import
|
|
12
|
-
import
|
|
13
|
-
import ViewTransition from "./components/ViewTransition.astro"
|
|
10
|
+
import Footer from "./components/footer/Footer.astro"
|
|
11
|
+
import Header from "./components/header/Header.astro"
|
|
12
|
+
import Favicon from "./components/meta/Favicon.astro"
|
|
13
|
+
import ViewTransition from "./components/meta/ViewTransition.astro"
|
|
14
14
|
|
|
15
15
|
export interface Props {
|
|
16
16
|
/**
|
|
@@ -37,10 +37,26 @@ export interface Props {
|
|
|
37
37
|
* analytics providers that choose to respect it. e.g. @lightnet/plausible-analytics
|
|
38
38
|
*/
|
|
39
39
|
disableShouldTrack?: boolean
|
|
40
|
+
/**
|
|
41
|
+
* Disallow search indexing for this page from search tools like google.
|
|
42
|
+
* This tells them to not index content on this page.
|
|
43
|
+
*
|
|
44
|
+
* There is also a global setting to disable search indexing for all pages.
|
|
45
|
+
* The page specific setting will take precedence.
|
|
46
|
+
*
|
|
47
|
+
* default: false
|
|
48
|
+
*/
|
|
49
|
+
disallowSearchIndexing?: boolean
|
|
40
50
|
}
|
|
41
51
|
|
|
42
|
-
const {
|
|
43
|
-
|
|
52
|
+
const {
|
|
53
|
+
title,
|
|
54
|
+
description,
|
|
55
|
+
mainClass,
|
|
56
|
+
locale,
|
|
57
|
+
disableShouldTrack,
|
|
58
|
+
disallowSearchIndexing,
|
|
59
|
+
} = Astro.props
|
|
44
60
|
const { currentLocale: pathCurrentLocale, tConfigField } = Astro.locals.i18n
|
|
45
61
|
const currentLocale = locale ?? pathCurrentLocale
|
|
46
62
|
const configTitle = tConfigField(config.title, config)
|
|
@@ -56,6 +72,11 @@ const { direction } = resolveLanguage(currentLocale)
|
|
|
56
72
|
<head>
|
|
57
73
|
<meta charset="UTF-8" />
|
|
58
74
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
75
|
+
{
|
|
76
|
+
(disallowSearchIndexing ?? config.disallowSearchIndexing) && (
|
|
77
|
+
<meta name="robots" content="noindex" />
|
|
78
|
+
)
|
|
79
|
+
}
|
|
59
80
|
{CustomHead && <CustomHead />}
|
|
60
81
|
<title>{title ? `${title} | ${configTitle}` : configTitle}</title>
|
|
61
82
|
{description && <meta name="description" content={description} />}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { getLinkAttributes } from "../../../utils/link-attributes"
|
|
3
|
+
import LightNetLogo from "./LightNetLogo.svg"
|
|
4
|
+
|
|
5
|
+
const { t } = Astro.locals.i18n
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
<a
|
|
9
|
+
class="inline-flex items-center gap-2 whitespace-nowrap underline-offset-4 hover:underline"
|
|
10
|
+
{...getLinkAttributes("https://lightnet.community")}
|
|
11
|
+
>
|
|
12
|
+
<img src={LightNetLogo.src} alt="" class="h-5 w-auto" loading="lazy" />
|
|
13
|
+
<span>{t("ln.footer.powered-by-lightnet")}</span>
|
|
14
|
+
</a>
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
---
|
|
2
|
+
import config from "virtual:lightnet/config"
|
|
3
|
+
|
|
4
|
+
import { getLinkAttributes } from "../../../utils/link-attributes"
|
|
5
|
+
import { localizePath } from "../../../utils/paths"
|
|
6
|
+
import Credits from "./Credits.astro"
|
|
7
|
+
import { formatFooterText } from "./format-footer-text"
|
|
8
|
+
import LanguageSelectionMenu from "./LanguageSelectionMenu.astro"
|
|
9
|
+
|
|
10
|
+
const { tConfigField, currentLocale } = Astro.locals.i18n
|
|
11
|
+
|
|
12
|
+
// Resolve optional footer text for the current locale.
|
|
13
|
+
const footerText = config.footerText
|
|
14
|
+
? formatFooterText(tConfigField(config.footerText, config))
|
|
15
|
+
: undefined
|
|
16
|
+
|
|
17
|
+
// Prepare footer links using the same locale rules as the main menu.
|
|
18
|
+
const footerLinks = (config.footerLinks ?? []).map(
|
|
19
|
+
({ href, label, requiresLocale }) => {
|
|
20
|
+
return {
|
|
21
|
+
href: requiresLocale ? localizePath(currentLocale, href) : href,
|
|
22
|
+
label: tConfigField(label, config),
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
const shouldShowLanguageSelectionMenu = config.locales.length > 1
|
|
28
|
+
|
|
29
|
+
// Hide the footer entirely when there is nothing to show.
|
|
30
|
+
const shouldRenderFooter =
|
|
31
|
+
config.credits ||
|
|
32
|
+
!!footerText ||
|
|
33
|
+
footerLinks.length > 0 ||
|
|
34
|
+
shouldShowLanguageSelectionMenu
|
|
35
|
+
|
|
36
|
+
if (!shouldRenderFooter) {
|
|
37
|
+
return
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const footerItems = [
|
|
41
|
+
...(footerText ? [{ type: "text" as const, label: footerText }] : []),
|
|
42
|
+
...footerLinks.map((link) => ({ type: "link" as const, ...link })),
|
|
43
|
+
]
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
<footer class="w-full border-t border-gray-200 bg-white text-sm text-gray-800">
|
|
47
|
+
<div
|
|
48
|
+
class="mx-auto flex w-full max-w-screen-xl px-4 py-4 md:px-8 md:py-6"
|
|
49
|
+
class:list={footerItems.length === 0
|
|
50
|
+
? "flex-col justify-between gap-4 sm:flex-row sm:items-center"
|
|
51
|
+
: "flex-col gap-2"}
|
|
52
|
+
>
|
|
53
|
+
{shouldShowLanguageSelectionMenu && <LanguageSelectionMenu />}
|
|
54
|
+
{
|
|
55
|
+
(footerItems.length > 0 || config.credits) && (
|
|
56
|
+
<div class="flex shrink flex-col gap-6 md:flex-row md:items-start md:justify-between">
|
|
57
|
+
{footerItems.length > 0 && (
|
|
58
|
+
<div class="flex w-full min-w-0 flex-1 shrink flex-wrap">
|
|
59
|
+
{footerItems.map((item, index) => (
|
|
60
|
+
<span class="inline-flex">
|
|
61
|
+
{item.type === "text" ? (
|
|
62
|
+
<span>{item.label}</span>
|
|
63
|
+
) : (
|
|
64
|
+
<a
|
|
65
|
+
{...getLinkAttributes(item.href)}
|
|
66
|
+
class="underline-offset-4 hover:underline"
|
|
67
|
+
>
|
|
68
|
+
{item.label}
|
|
69
|
+
</a>
|
|
70
|
+
)}
|
|
71
|
+
{index !== footerItems.length - 1 && (
|
|
72
|
+
<span
|
|
73
|
+
class="shrink-0 px-2 text-gray-400"
|
|
74
|
+
aria-hidden="true"
|
|
75
|
+
>
|
|
76
|
+
·
|
|
77
|
+
</span>
|
|
78
|
+
)}
|
|
79
|
+
</span>
|
|
80
|
+
))}
|
|
81
|
+
</div>
|
|
82
|
+
)}
|
|
83
|
+
{config.credits && <Credits />}
|
|
84
|
+
</div>
|
|
85
|
+
)
|
|
86
|
+
}
|
|
87
|
+
</div>
|
|
88
|
+
</footer>
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { GlobeIcon } from "lucide-react"
|
|
3
|
+
|
|
4
|
+
import { getLanguageSelectionMenuItems } from "../get-language-selection-menu-items"
|
|
5
|
+
|
|
6
|
+
const { t, tConfigField, currentLocale } = Astro.locals.i18n
|
|
7
|
+
|
|
8
|
+
const { links: languageMenuItems } = getLanguageSelectionMenuItems({
|
|
9
|
+
currentLocale,
|
|
10
|
+
pathname: Astro.url.pathname,
|
|
11
|
+
tConfigField,
|
|
12
|
+
})
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
<label class="inline-flex items-center gap-2 py-2 font-medium">
|
|
16
|
+
<GlobeIcon className="h-5 w-5 shrink-0 text-gray-600" aria-hidden="true" />
|
|
17
|
+
<span class="sr-only">{t("ln.select-language")}</span>
|
|
18
|
+
<select
|
|
19
|
+
class="min-w-0 cursor-pointer appearance-none border-0 bg-transparent p-0 pr-0 outline-none focus:ring-0"
|
|
20
|
+
aria-label={t("ln.select-language")}
|
|
21
|
+
data-footer-language-select
|
|
22
|
+
>
|
|
23
|
+
{
|
|
24
|
+
languageMenuItems.map(({ href, label, locale, active }) => (
|
|
25
|
+
<option value={href} selected={active} lang={locale}>
|
|
26
|
+
{label}
|
|
27
|
+
</option>
|
|
28
|
+
))
|
|
29
|
+
}
|
|
30
|
+
</select>
|
|
31
|
+
</label>
|
|
32
|
+
<script>
|
|
33
|
+
const footerLanguageSelect = document.querySelector<HTMLSelectElement>(
|
|
34
|
+
"[data-footer-language-select]",
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
footerLanguageSelect?.addEventListener("change", () => {
|
|
38
|
+
if (!footerLanguageSelect.value) {
|
|
39
|
+
return
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
window.location.assign(footerLanguageSelect.value)
|
|
43
|
+
})
|
|
44
|
+
</script>
|
|
@@ -1 +1 @@
|
|
|
1
|
-
<svg viewBox="16 16 141 173" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M88.007 180.642c-21.767-5.846-39.688-19.304-40.574-42.862-.887-23.558 17.751-43.334 45.426-40.84v-7.844c.011-4.03 4.977-5.495 8.021-2.928l29.136 23.729c2.155 1.821 2.15 5.16-.019 7.08l-29.117 24.152c-3.059 2.72-8.031.681-8.02-3.349v-8.005c-2.916-.453-5.137-.579-6.708-.453-25.207 2.016-23.083 44.6 20.454 42.388 18.705-.951 49.148-21.73 50.394-53.985 0-48.62-60.4-64.433-45.205-97.762 1.529-3.354-4.627-4.867-8.501-3.393-15.573 4.29-32.752 13.714-42.526 41.693-1.251 3.581-2.824 12.038-4.168 15.558-1.242 3.533-3.034 11.445-7.996 11.025-6.501-.55-4.236-11.168-2.815-15.74 1.197-3.854-4.14-7.109-9.028-2.073-8.3 8.549-16.598 19.982-19.804 37.823-1.259 7.001-1.327 16.903.101 25.208 6.204 36.183 37.705 59.276 68.356 58.915 7.58-.09 7.787-6.942 2.593-8.337Z" fill="#
|
|
1
|
+
<svg viewBox="16 16 141 173" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M88.007 180.642c-21.767-5.846-39.688-19.304-40.574-42.862-.887-23.558 17.751-43.334 45.426-40.84v-7.844c.011-4.03 4.977-5.495 8.021-2.928l29.136 23.729c2.155 1.821 2.15 5.16-.019 7.08l-29.117 24.152c-3.059 2.72-8.031.681-8.02-3.349v-8.005c-2.916-.453-5.137-.579-6.708-.453-25.207 2.016-23.083 44.6 20.454 42.388 18.705-.951 49.148-21.73 50.394-53.985 0-48.62-60.4-64.433-45.205-97.762 1.529-3.354-4.627-4.867-8.501-3.393-15.573 4.29-32.752 13.714-42.526 41.693-1.251 3.581-2.824 12.038-4.168 15.558-1.242 3.533-3.034 11.445-7.996 11.025-6.501-.55-4.236-11.168-2.815-15.74 1.197-3.854-4.14-7.109-9.028-2.073-8.3 8.549-16.598 19.982-19.804 37.823-1.259 7.001-1.327 16.903.101 25.208 6.204 36.183 37.705 59.276 68.356 58.915 7.58-.09 7.787-6.942 2.593-8.337Z" fill="#4b5563"/></svg>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Replaces supported placeholders in localized footer text.
|
|
3
|
+
*
|
|
4
|
+
* Use `{{year}}` to render the current calendar year.
|
|
5
|
+
*
|
|
6
|
+
* @param text Localized footer text from LightNet config.
|
|
7
|
+
* @param currentYear Year to render. Defaults to the current system year.
|
|
8
|
+
*/
|
|
9
|
+
export const formatFooterText = (
|
|
10
|
+
text: string,
|
|
11
|
+
currentYear = new Date().getFullYear(),
|
|
12
|
+
) => {
|
|
13
|
+
return text.replaceAll("{{year}}", String(currentYear))
|
|
14
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import config from "virtual:lightnet/config"
|
|
2
|
+
|
|
3
|
+
import { resolveLanguage } from "../../i18n/resolve-language"
|
|
4
|
+
import type { TranslateConfigFieldFn } from "../../i18n/translate-map"
|
|
5
|
+
import { localizePath, pathWithoutBase } from "../../utils/paths"
|
|
6
|
+
|
|
7
|
+
export type LanguageLink = {
|
|
8
|
+
active: boolean
|
|
9
|
+
href: string
|
|
10
|
+
label: string
|
|
11
|
+
locale: string
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function getLanguageSelectionMenuItems({
|
|
15
|
+
currentLocale,
|
|
16
|
+
pathname,
|
|
17
|
+
tConfigField,
|
|
18
|
+
}: {
|
|
19
|
+
currentLocale: string
|
|
20
|
+
pathname: string
|
|
21
|
+
tConfigField: TranslateConfigFieldFn
|
|
22
|
+
}) {
|
|
23
|
+
const currentPath = pathWithoutBase(pathname)
|
|
24
|
+
const hasLocale = Boolean(
|
|
25
|
+
currentLocale &&
|
|
26
|
+
(currentPath.startsWith(`/${currentLocale}/`) ||
|
|
27
|
+
currentPath === `/${currentLocale}`),
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
const links = config.locales
|
|
31
|
+
.map((locale) => ({
|
|
32
|
+
locale,
|
|
33
|
+
label: tConfigField(resolveLanguage(locale).label, config),
|
|
34
|
+
active: locale === currentLocale,
|
|
35
|
+
href: localizePath(
|
|
36
|
+
locale,
|
|
37
|
+
hasLocale ? currentPath.slice(currentLocale.length + 1) : currentPath,
|
|
38
|
+
),
|
|
39
|
+
}))
|
|
40
|
+
.sort((a, b) => a.label.localeCompare(b.label, currentLocale))
|
|
41
|
+
|
|
42
|
+
return {
|
|
43
|
+
currentPath,
|
|
44
|
+
hasLocale,
|
|
45
|
+
links,
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { GlobeIcon } from "lucide-react"
|
|
3
|
+
|
|
4
|
+
import { getLanguageSelectionMenuItems } from "../get-language-selection-menu-items"
|
|
5
|
+
import Menu from "./Menu.astro"
|
|
6
|
+
import MenuItem from "./MenuItem.astro"
|
|
7
|
+
|
|
8
|
+
const { currentLocale, tConfigField } = Astro.locals.i18n
|
|
9
|
+
|
|
10
|
+
const {
|
|
11
|
+
currentPath,
|
|
12
|
+
hasLocale,
|
|
13
|
+
links: translations,
|
|
14
|
+
} = getLanguageSelectionMenuItems({
|
|
15
|
+
currentLocale,
|
|
16
|
+
pathname: Astro.url.pathname,
|
|
17
|
+
tConfigField,
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
const disabled = !hasLocale && currentPath !== "/"
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
{
|
|
24
|
+
translations.length > 1 && (
|
|
25
|
+
<Menu disabled={disabled} label="ln.select-language">
|
|
26
|
+
<GlobeIcon slot="icon" />
|
|
27
|
+
{translations.map(({ label, locale, active, href }) => (
|
|
28
|
+
<MenuItem href={href} hreflang={locale} active={active}>
|
|
29
|
+
{label}
|
|
30
|
+
</MenuItem>
|
|
31
|
+
))}
|
|
32
|
+
</Menu>
|
|
33
|
+
)
|
|
34
|
+
}
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
import { ExternalLinkIcon, MenuIcon, SearchIcon } from "lucide-react"
|
|
3
3
|
import config from "virtual:lightnet/config"
|
|
4
4
|
|
|
5
|
-
import { isExternalUrl } from "
|
|
6
|
-
import { localizePath, searchPagePath } from "
|
|
7
|
-
import
|
|
5
|
+
import { isExternalUrl } from "../../../utils/is-external-url"
|
|
6
|
+
import { localizePath, searchPagePath } from "../../../utils/paths"
|
|
7
|
+
import LanguageSelectionMenu from "./LanguageSelectionMenu.astro"
|
|
8
8
|
import Menu from "./Menu.astro"
|
|
9
9
|
import MenuItem from "./MenuItem.astro"
|
|
10
10
|
|
|
@@ -40,7 +40,7 @@ const items = (config.mainMenu ?? []).map(({ href, label, requiresLocale }) => {
|
|
|
40
40
|
)
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
<
|
|
43
|
+
<LanguageSelectionMenu />
|
|
44
44
|
|
|
45
45
|
{
|
|
46
46
|
!!items.length && (
|
|
@@ -4,7 +4,7 @@ import { Image } from "astro:assets"
|
|
|
4
4
|
import config from "virtual:lightnet/config"
|
|
5
5
|
import logo from "virtual:lightnet/logo"
|
|
6
6
|
|
|
7
|
-
import { localizePath } from "
|
|
7
|
+
import { localizePath } from "../../../utils/paths"
|
|
8
8
|
|
|
9
9
|
function getWidth(logo: ImageMetadata) {
|
|
10
10
|
const size = config.logo?.size ?? 28
|
|
@@ -1,110 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
import config from "virtual:lightnet/config"
|
|
3
|
-
|
|
4
|
-
import { getLinkAttributes } from "../../utils/link-attributes"
|
|
5
|
-
import { localizePath } from "../../utils/paths"
|
|
6
|
-
import LightNetLogo from "./LightNetLogo.svg"
|
|
7
|
-
|
|
8
|
-
const { t, tConfigField, currentLocale } = Astro.locals.i18n
|
|
9
|
-
|
|
10
|
-
// Resolve optional footer text for the current locale.
|
|
11
|
-
const footerText = config.footerText
|
|
12
|
-
? tConfigField(config.footerText, config)
|
|
13
|
-
: undefined
|
|
14
|
-
|
|
15
|
-
type FooterTextItem = {
|
|
16
|
-
label: string
|
|
17
|
-
type: "text"
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
type FooterLinkItem = {
|
|
21
|
-
href: string
|
|
22
|
-
label: string
|
|
23
|
-
type: "link"
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
// Prepare footer links using the same locale rules as the main menu.
|
|
27
|
-
const footerLinks = (config.footerLinks ?? []).map(
|
|
28
|
-
({ href, label, requiresLocale }) => {
|
|
29
|
-
return {
|
|
30
|
-
href: requiresLocale ? localizePath(currentLocale, href) : href,
|
|
31
|
-
label: tConfigField(label, config),
|
|
32
|
-
}
|
|
33
|
-
},
|
|
34
|
-
)
|
|
35
|
-
|
|
36
|
-
// Hide the footer entirely when there is nothing to show.
|
|
37
|
-
const shouldRenderFooter =
|
|
38
|
-
config.credits || !!footerText || footerLinks.length > 0
|
|
39
|
-
|
|
40
|
-
if (!shouldRenderFooter) {
|
|
41
|
-
return
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
const footerItems: Array<FooterTextItem | FooterLinkItem> = [
|
|
45
|
-
...(footerText ? [{ type: "text" as const, label: footerText }] : []),
|
|
46
|
-
...footerLinks.map((link): FooterLinkItem => ({ type: "link", ...link })),
|
|
47
|
-
]
|
|
48
|
-
---
|
|
49
|
-
|
|
50
|
-
<footer class="w-full border-t border-gray-200 bg-white">
|
|
51
|
-
<div class="mx-auto w-full max-w-screen-xl px-4 md:px-8">
|
|
52
|
-
<div
|
|
53
|
-
class="flex flex-col items-start justify-between gap-6 py-6 sm:flex-row sm:items-start"
|
|
54
|
-
>
|
|
55
|
-
<div
|
|
56
|
-
class="flex w-full flex-col items-start gap-3 text-sm text-gray-800 sm:min-w-0 sm:flex-1 sm:flex-row sm:flex-wrap sm:items-center sm:gap-2"
|
|
57
|
-
>
|
|
58
|
-
{
|
|
59
|
-
footerItems.length > 0 && (
|
|
60
|
-
<div class="flex w-full flex-row flex-wrap items-center gap-x-2 gap-y-1 sm:w-auto">
|
|
61
|
-
{footerItems.map((item, index) => (
|
|
62
|
-
<span class="inline-flex items-center gap-1.5">
|
|
63
|
-
<span class="text-gray-800">
|
|
64
|
-
{item.type === "text" ? (
|
|
65
|
-
item.label
|
|
66
|
-
) : (
|
|
67
|
-
<a
|
|
68
|
-
{...getLinkAttributes(item.href)}
|
|
69
|
-
class="underline-offset-4 hover:underline"
|
|
70
|
-
>
|
|
71
|
-
{item.label}
|
|
72
|
-
</a>
|
|
73
|
-
)}
|
|
74
|
-
</span>
|
|
75
|
-
{index < footerItems.length - 1 && (
|
|
76
|
-
<span
|
|
77
|
-
class="text-[0.8em] leading-none text-gray-400"
|
|
78
|
-
aria-hidden="true"
|
|
79
|
-
>
|
|
80
|
-
·
|
|
81
|
-
</span>
|
|
82
|
-
)}
|
|
83
|
-
</span>
|
|
84
|
-
))}
|
|
85
|
-
</div>
|
|
86
|
-
)
|
|
87
|
-
}
|
|
88
|
-
</div>
|
|
89
|
-
|
|
90
|
-
{
|
|
91
|
-
config.credits && (
|
|
92
|
-
<div class="flex w-full items-center text-sm sm:w-auto sm:flex-none sm:justify-end">
|
|
93
|
-
<a
|
|
94
|
-
class="flex items-center gap-2 whitespace-nowrap text-gray-800 underline-offset-4 hover:underline"
|
|
95
|
-
{...getLinkAttributes("https://lightnet.community")}
|
|
96
|
-
>
|
|
97
|
-
<img
|
|
98
|
-
src={LightNetLogo.src}
|
|
99
|
-
alt=""
|
|
100
|
-
class="h-5 w-auto"
|
|
101
|
-
loading="lazy"
|
|
102
|
-
/>
|
|
103
|
-
<span>{t("ln.footer.powered-by-lightnet")}</span>
|
|
104
|
-
</a>
|
|
105
|
-
</div>
|
|
106
|
-
)
|
|
107
|
-
}
|
|
108
|
-
</div>
|
|
109
|
-
</div>
|
|
110
|
-
</footer>
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
import { GlobeIcon } from "lucide-react"
|
|
3
|
-
|
|
4
|
-
import { resolveTranslatedLanguage } from "../../i18n/resolve-language"
|
|
5
|
-
import { localizePath, pathWithoutBase } from "../../utils/paths"
|
|
6
|
-
import Menu from "./Menu.astro"
|
|
7
|
-
import MenuItem from "./MenuItem.astro"
|
|
8
|
-
|
|
9
|
-
const { locales, currentLocale, tConfigField } = Astro.locals.i18n
|
|
10
|
-
|
|
11
|
-
const currentPath = pathWithoutBase(Astro.url.pathname)
|
|
12
|
-
const hasLocale =
|
|
13
|
-
currentLocale &&
|
|
14
|
-
(currentPath.startsWith(`/${currentLocale}/`) ||
|
|
15
|
-
currentPath === `/${currentLocale}`)
|
|
16
|
-
|
|
17
|
-
const translations = (
|
|
18
|
-
await Promise.all(
|
|
19
|
-
locales.map(async (locale) => ({
|
|
20
|
-
locale,
|
|
21
|
-
label: resolveTranslatedLanguage(locale, tConfigField).labelText,
|
|
22
|
-
active: locale === currentLocale,
|
|
23
|
-
href: currentPathWithLocale(locale),
|
|
24
|
-
})),
|
|
25
|
-
)
|
|
26
|
-
).sort((a, b) => a.label.localeCompare(b.label))
|
|
27
|
-
|
|
28
|
-
function currentPathWithLocale(locale: string) {
|
|
29
|
-
const currentPathWithoutLocale = hasLocale
|
|
30
|
-
? currentPath.slice(currentLocale.length + 1)
|
|
31
|
-
: currentPath
|
|
32
|
-
return localizePath(locale, currentPathWithoutLocale)
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
const disabled = !hasLocale && currentPath !== "/"
|
|
36
|
-
---
|
|
37
|
-
|
|
38
|
-
{
|
|
39
|
-
translations.length > 1 && (
|
|
40
|
-
<Menu disabled={disabled} label="ln.header.select-language">
|
|
41
|
-
<GlobeIcon slot="icon" />
|
|
42
|
-
{translations.map(({ label, locale, active, href }) => (
|
|
43
|
-
<MenuItem href={href} hreflang={locale} active={active}>
|
|
44
|
-
{label}
|
|
45
|
-
</MenuItem>
|
|
46
|
-
))}
|
|
47
|
-
</Menu>
|
|
48
|
-
)
|
|
49
|
-
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|