lightnet 4.3.0 → 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 CHANGED
@@ -1,5 +1,15 @@
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
+
3
13
  ## 4.3.0
4
14
 
5
15
  ### Minor 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.3.0",
6
+ "version": "4.4.0",
7
7
  "repository": {
8
8
  "type": "git",
9
9
  "url": "https://github.com/LightNetDev/lightnet",
@@ -110,22 +110,10 @@ export const configSchema = z.object({
110
110
  */
111
111
  favicon: faviconSchema.array().optional(),
112
112
  /**
113
- * Enable displaying a “Powered by LightNet” link in your site’s footer.
114
- */
115
- credits: z.boolean().default(false),
116
- /**
117
- * Optional localized text to display in your site's footer.
118
- *
119
- * Use `{{year}}` to render the current calendar year.
120
- *
121
- * @example { en: "Copyright {{year}} LightNet" }
122
- */
123
- footerText: translationMapSchema.optional(),
124
- /**
125
- * Optional links to display in your site's footer.
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.
126
115
  */
127
- footerLinks: z.array(linkSchema).optional(),
128
-
116
+ disallowSearchIndexing: z.boolean().optional(),
129
117
  /**
130
118
  * Link to manifest file within public/ folder
131
119
  */
@@ -186,6 +174,22 @@ export const configSchema = z.object({
186
174
  * @example "./src/components/MyHeadTag.astro"
187
175
  */
188
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(),
189
193
  /**
190
194
  * Path to an Astro component to be added at the bottom of all pages.
191
195
  *
@@ -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 Favicon from "./components/Favicon.astro"
11
- import Footer from "./components/Footer.astro"
12
- import Header from "./components/Header.astro"
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 { title, description, mainClass, locale, disableShouldTrack } =
43
- Astro.props
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
+ &middot;
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="#374151"/></svg>
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>
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  import { GlobeIcon } from "lucide-react"
3
3
 
4
- import { getLanguageSelectionMenuItems } from "./get-language-selection-menu-items"
4
+ import { getLanguageSelectionMenuItems } from "../get-language-selection-menu-items"
5
5
  import Menu from "./Menu.astro"
6
6
  import MenuItem from "./MenuItem.astro"
7
7
 
@@ -1,5 +1,5 @@
1
1
  ---
2
- import { getLinkAttributes } from "../../utils/link-attributes"
2
+ import { getLinkAttributes } from "../../../utils/link-attributes"
3
3
 
4
4
  interface Props {
5
5
  href: string
@@ -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 "../../utils/is-external-url"
6
- import { localizePath, searchPagePath } from "../../utils/paths"
7
- import LanguagePicker from "./LanguagePicker.astro"
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
- <LanguagePicker />
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 "../../utils/paths"
7
+ import { localizePath } from "../../../utils/paths"
8
8
 
9
9
  function getWidth(logo: ImageMetadata) {
10
10
  const size = config.logo?.size ?? 28
@@ -1,149 +0,0 @@
1
- ---
2
- import { GlobeIcon } from "lucide-react"
3
- import config from "virtual:lightnet/config"
4
-
5
- import { getLinkAttributes } from "../../utils/link-attributes"
6
- import { localizePath } from "../../utils/paths"
7
- import { formatFooterText } from "./format-footer-text"
8
- import { getLanguageSelectionMenuItems } from "./get-language-selection-menu-items"
9
- import LightNetLogo from "./LightNetLogo.svg"
10
-
11
- const { t, tConfigField, currentLocale } = Astro.locals.i18n
12
-
13
- // Resolve optional footer text for the current locale.
14
- const footerText = config.footerText
15
- ? formatFooterText(tConfigField(config.footerText, config))
16
- : undefined
17
-
18
- // Prepare footer links using the same locale rules as the main menu.
19
- const footerLinks = (config.footerLinks ?? []).map(
20
- ({ href, label, requiresLocale }) => {
21
- return {
22
- href: requiresLocale ? localizePath(currentLocale, href) : href,
23
- label: tConfigField(label, config),
24
- }
25
- },
26
- )
27
-
28
- const { links: languageMenuItems } = getLanguageSelectionMenuItems({
29
- currentLocale,
30
- pathname: Astro.url.pathname,
31
- tConfigField,
32
- })
33
-
34
- const shouldShowLanguageSelector = languageMenuItems.length > 1
35
-
36
- // Hide the footer entirely when there is nothing to show.
37
- const shouldRenderFooter =
38
- config.credits ||
39
- !!footerText ||
40
- footerLinks.length > 0 ||
41
- shouldShowLanguageSelector
42
-
43
- if (!shouldRenderFooter) {
44
- return
45
- }
46
-
47
- const footerItems = [
48
- ...(footerText ? [{ type: "text" as const, label: footerText }] : []),
49
- ...footerLinks.map((link) => ({ type: "link" as const, ...link })),
50
- ]
51
- ---
52
-
53
- <footer class="w-full border-t border-gray-200 bg-white">
54
- <div class="mx-auto w-full max-w-screen-xl px-4 md:px-8">
55
- <div class="flex flex-col gap-4 py-6">
56
- {
57
- shouldShowLanguageSelector && (
58
- <div class="flex w-full justify-start">
59
- <label class="inline-flex items-center gap-2 bg-white py-2 text-sm text-gray-800">
60
- <GlobeIcon
61
- className="h-4 w-4 shrink-0 text-gray-500"
62
- aria-hidden="true"
63
- />
64
- <span class="sr-only">{t("ln.select-language")}</span>
65
- <select
66
- class="min-w-0 cursor-pointer appearance-none border-0 bg-transparent p-0 pr-0 text-sm font-medium text-gray-800 outline-none focus:ring-0"
67
- aria-label={t("ln.select-language")}
68
- data-footer-language-select
69
- >
70
- {languageMenuItems.map(({ href, label, locale, active }) => (
71
- <option value={href} selected={active} lang={locale}>
72
- {label}
73
- </option>
74
- ))}
75
- </select>
76
- </label>
77
- </div>
78
- )
79
- }
80
-
81
- <div
82
- class="flex w-full flex-col gap-4 text-sm text-gray-800 md:flex-row md:items-center md:justify-between md:gap-6"
83
- >
84
- {
85
- footerItems.length > 0 && (
86
- <div class="flex w-full flex-row flex-wrap items-center gap-x-2 gap-y-1 md:w-auto">
87
- {footerItems.map((item, index) => (
88
- <span class="inline-flex items-center gap-1.5">
89
- <span
90
- class:list={[
91
- "w-3 shrink-0 justify-center text-center text-[0.8em] leading-none text-gray-400",
92
- index === 0 ? "hidden" : "inline-flex",
93
- ]}
94
- aria-hidden="true"
95
- >
96
- &middot;
97
- </span>
98
- {item.type === "text" ? (
99
- <span class="text-gray-800">{item.label}</span>
100
- ) : (
101
- <a
102
- {...getLinkAttributes(item.href)}
103
- class="text-gray-800 underline-offset-4 hover:underline"
104
- >
105
- {item.label}
106
- </a>
107
- )}
108
- </span>
109
- ))}
110
- </div>
111
- )
112
- }
113
-
114
- {
115
- config.credits && (
116
- <div class="flex w-full items-center justify-start md:w-auto md:justify-end">
117
- <a
118
- class="flex items-center gap-2 whitespace-nowrap text-gray-800 underline-offset-4 hover:underline"
119
- {...getLinkAttributes("https://lightnet.community")}
120
- >
121
- <img
122
- src={LightNetLogo.src}
123
- alt=""
124
- class="h-5 w-auto"
125
- loading="lazy"
126
- />
127
- <span>{t("ln.footer.powered-by-lightnet")}</span>
128
- </a>
129
- </div>
130
- )
131
- }
132
- </div>
133
- </div>
134
- </div>
135
- </footer>
136
-
137
- <script>
138
- const footerLanguageSelect = document.querySelector<HTMLSelectElement>(
139
- "[data-footer-language-select]",
140
- )
141
-
142
- footerLanguageSelect?.addEventListener("change", () => {
143
- if (!footerLanguageSelect.value) {
144
- return
145
- }
146
-
147
- window.location.assign(footerLanguageSelect.value)
148
- })
149
- </script>