lightnet 3.11.0 → 3.12.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 +8 -0
- package/__e2e__/fixtures/basics/node_modules/.bin/astro +2 -2
- package/__e2e__/fixtures/basics/package.json +1 -1
- package/package.json +4 -4
- package/src/admin/components/form/LazyLoadedMarkdownEditor.tsx +10 -3
- package/src/admin/components/form/atoms/Label.tsx +2 -2
- package/src/admin/components/form/utils/get-border-class.ts +1 -1
- package/src/admin/pages/media/fields/Content.tsx +2 -2
- package/src/components/VideoPlayer.astro +74 -10
- package/src/i18n/translations/en.yml +3 -4
- package/src/pages/details-page/components/AudioPanel.astro +1 -12
- package/src/pages/details-page/components/AudioPlayer.astro +40 -8
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# lightnet
|
|
2
2
|
|
|
3
|
+
## 3.12.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#351](https://github.com/LightNetDev/LightNet/pull/351) [`a1f63bf`](https://github.com/LightNetDev/LightNet/commit/a1f63bfa30448fdf58bfb1ff24007caa750349e0) Thanks [@smn-cds](https://github.com/smn-cds)! - Add an external fallback UI for audio content so non-mp3 URLs show a player-style link that opens in a new tab.
|
|
8
|
+
|
|
9
|
+
- [#351](https://github.com/LightNetDev/LightNet/pull/351) [`a1f63bf`](https://github.com/LightNetDev/LightNet/commit/a1f63bfa30448fdf58bfb1ff24007caa750349e0) Thanks [@smn-cds](https://github.com/smn-cds)! - Add a fallback in the video player so any video URL can be used on the "video" details page. Unsupported providers now render a poster-style link that opens the video on the external site.
|
|
10
|
+
|
|
3
11
|
## 3.11.0
|
|
4
12
|
|
|
5
13
|
### Minor Changes
|
|
@@ -10,9 +10,9 @@ case `uname` in
|
|
|
10
10
|
esac
|
|
11
11
|
|
|
12
12
|
if [ -z "$NODE_PATH" ]; then
|
|
13
|
-
export NODE_PATH="/home/runner/work/LightNet/LightNet/node_modules/.pnpm/astro@5.16.6_@types+node@25.0.3_jiti@2.4.2_lightningcss@1.29.1_rollup@4.
|
|
13
|
+
export NODE_PATH="/home/runner/work/LightNet/LightNet/node_modules/.pnpm/astro@5.16.6_@types+node@25.0.3_jiti@2.4.2_lightningcss@1.29.1_rollup@4.55.1_terser@5.39.0_typescript@5.9.3_yaml@2.8.2/node_modules/astro/node_modules:/home/runner/work/LightNet/LightNet/node_modules/.pnpm/astro@5.16.6_@types+node@25.0.3_jiti@2.4.2_lightningcss@1.29.1_rollup@4.55.1_terser@5.39.0_typescript@5.9.3_yaml@2.8.2/node_modules:/home/runner/work/LightNet/LightNet/node_modules/.pnpm/node_modules"
|
|
14
14
|
else
|
|
15
|
-
export NODE_PATH="/home/runner/work/LightNet/LightNet/node_modules/.pnpm/astro@5.16.6_@types+node@25.0.3_jiti@2.4.2_lightningcss@1.29.1_rollup@4.
|
|
15
|
+
export NODE_PATH="/home/runner/work/LightNet/LightNet/node_modules/.pnpm/astro@5.16.6_@types+node@25.0.3_jiti@2.4.2_lightningcss@1.29.1_rollup@4.55.1_terser@5.39.0_typescript@5.9.3_yaml@2.8.2/node_modules/astro/node_modules:/home/runner/work/LightNet/LightNet/node_modules/.pnpm/astro@5.16.6_@types+node@25.0.3_jiti@2.4.2_lightningcss@1.29.1_rollup@4.55.1_terser@5.39.0_typescript@5.9.3_yaml@2.8.2/node_modules:/home/runner/work/LightNet/LightNet/node_modules/.pnpm/node_modules:$NODE_PATH"
|
|
16
16
|
fi
|
|
17
17
|
if [ -x "$basedir/node" ]; then
|
|
18
18
|
exec "$basedir/node" "$basedir/../astro/astro.js" "$@"
|
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": "3.
|
|
6
|
+
"version": "3.12.0",
|
|
7
7
|
"repository": {
|
|
8
8
|
"type": "git",
|
|
9
9
|
"url": "https://github.com/LightNetDev/lightnet",
|
|
@@ -50,16 +50,16 @@
|
|
|
50
50
|
"@hookform/resolvers": "^5.2.2",
|
|
51
51
|
"@iconify-json/mdi": "^1.2.3",
|
|
52
52
|
"@iconify/tailwind": "^1.2.0",
|
|
53
|
-
"@mdxeditor/editor": "^3.52.
|
|
53
|
+
"@mdxeditor/editor": "^3.52.3",
|
|
54
54
|
"@tailwindcss/typography": "^0.5.19",
|
|
55
|
-
"@tanstack/react-virtual": "^3.13.
|
|
55
|
+
"@tanstack/react-virtual": "^3.13.17",
|
|
56
56
|
"daisyui": "^4.12.24",
|
|
57
57
|
"embla-carousel": "^8.6.0",
|
|
58
58
|
"embla-carousel-wheel-gestures": "^8.1.0",
|
|
59
59
|
"fuse.js": "^7.1.0",
|
|
60
60
|
"i18next": "^25.7.3",
|
|
61
61
|
"marked": "^16.4.2",
|
|
62
|
-
"react-hook-form": "^7.
|
|
62
|
+
"react-hook-form": "^7.70.0",
|
|
63
63
|
"yaml": "^2.8.2"
|
|
64
64
|
},
|
|
65
65
|
"devDependencies": {
|
|
@@ -6,6 +6,8 @@ import {
|
|
|
6
6
|
type CodeBlockEditorProps,
|
|
7
7
|
codeBlockPlugin,
|
|
8
8
|
CreateLink,
|
|
9
|
+
diffSourcePlugin,
|
|
10
|
+
DiffSourceToggleWrapper,
|
|
9
11
|
headingsPlugin,
|
|
10
12
|
linkDialogPlugin,
|
|
11
13
|
linkPlugin,
|
|
@@ -22,6 +24,7 @@ import {
|
|
|
22
24
|
type Control,
|
|
23
25
|
Controller,
|
|
24
26
|
type FieldValues,
|
|
27
|
+
get,
|
|
25
28
|
type Path,
|
|
26
29
|
} from "react-hook-form"
|
|
27
30
|
|
|
@@ -42,7 +45,10 @@ export default function LazyLoadedMarkdownEditor<
|
|
|
42
45
|
<Controller
|
|
43
46
|
control={control}
|
|
44
47
|
name={name}
|
|
45
|
-
render={({
|
|
48
|
+
render={({
|
|
49
|
+
field: { onBlur, onChange, value, ref },
|
|
50
|
+
formState: { defaultValues },
|
|
51
|
+
}) => (
|
|
46
52
|
<MDXEditor
|
|
47
53
|
markdown={value ?? ""}
|
|
48
54
|
onBlur={onBlur}
|
|
@@ -68,15 +74,16 @@ export default function LazyLoadedMarkdownEditor<
|
|
|
68
74
|
linkDialogPlugin(),
|
|
69
75
|
quotePlugin(),
|
|
70
76
|
markdownShortcutPlugin(),
|
|
77
|
+
diffSourcePlugin({ diffMarkdown: get(defaultValues, name) }),
|
|
71
78
|
toolbarPlugin({
|
|
72
79
|
toolbarContents: () => (
|
|
73
|
-
|
|
80
|
+
<DiffSourceToggleWrapper>
|
|
74
81
|
<UndoRedo />
|
|
75
82
|
<BoldItalicUnderlineToggles />
|
|
76
83
|
<BlockTypeSelect />
|
|
77
84
|
<ListsToggle options={["bullet", "number"]} />
|
|
78
85
|
<CreateLink />
|
|
79
|
-
|
|
86
|
+
</DiffSourceToggleWrapper>
|
|
80
87
|
),
|
|
81
88
|
toolbarClassName: "!rounded-none !bg-slate-200",
|
|
82
89
|
}),
|
|
@@ -22,9 +22,9 @@ export default function Label({
|
|
|
22
22
|
return "bg-rose-800 text-white"
|
|
23
23
|
}
|
|
24
24
|
if (isDirty) {
|
|
25
|
-
return "bg-slate-
|
|
25
|
+
return "bg-slate-600 text-slate-50"
|
|
26
26
|
}
|
|
27
|
-
return "bg-slate-300 text-slate-
|
|
27
|
+
return "bg-slate-300 text-slate-700"
|
|
28
28
|
}
|
|
29
29
|
return (
|
|
30
30
|
<div
|
|
@@ -16,7 +16,7 @@ export const getBorderClass = ({
|
|
|
16
16
|
return "border border-rose-800 " + focusColors(focusWithin)
|
|
17
17
|
}
|
|
18
18
|
if (isDirty) {
|
|
19
|
-
return "border border-slate-
|
|
19
|
+
return "border border-slate-600 " + focusColors(focusWithin)
|
|
20
20
|
}
|
|
21
21
|
return "border border-slate-300 " + focusColors(focusWithin)
|
|
22
22
|
}
|
|
@@ -42,8 +42,8 @@ export default function Content({
|
|
|
42
42
|
return <span></span>
|
|
43
43
|
}
|
|
44
44
|
return (
|
|
45
|
-
<span className="ms-1 flex items-center gap-1 rounded-lg bg-
|
|
46
|
-
<Icon className="text-sm mdi--star" ariaLabel="" />
|
|
45
|
+
<span className="ms-1 flex items-center gap-1 rounded-lg bg-slate-200 px-2 py-1 text-xs font-bold uppercase text-slate-600">
|
|
46
|
+
<Icon className="text-sm text-sky-700 mdi--star" ariaLabel="" />
|
|
47
47
|
{t("ln.admin.primary-content")}
|
|
48
48
|
</span>
|
|
49
49
|
)
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
---
|
|
2
2
|
import type { ImageMetadata } from "astro"
|
|
3
3
|
import { getImage } from "astro:assets"
|
|
4
|
+
import { Image } from "astro:assets"
|
|
5
|
+
|
|
6
|
+
import Icon from "./Icon"
|
|
4
7
|
|
|
5
8
|
interface Props {
|
|
6
9
|
/**
|
|
@@ -12,21 +15,43 @@ interface Props {
|
|
|
12
15
|
*/
|
|
13
16
|
title?: string
|
|
14
17
|
/**
|
|
15
|
-
* Poster image to use for the mp4 video player.
|
|
18
|
+
* Poster image to use for the mp4 video player or external fallback.
|
|
16
19
|
*/
|
|
17
20
|
image?: ImageMetadata
|
|
18
21
|
}
|
|
19
22
|
|
|
20
23
|
const { url, title, image: imageMetadata } = Astro.props
|
|
21
24
|
|
|
22
|
-
const { host, id, image } = await parseUrl(url)
|
|
25
|
+
const { host, id, image, href } = await parseUrl(url, imageMetadata)
|
|
26
|
+
const { t } = Astro.locals.i18n
|
|
23
27
|
|
|
24
|
-
async function parseUrl(
|
|
25
|
-
|
|
28
|
+
async function parseUrl(
|
|
29
|
+
urlToParse: string,
|
|
30
|
+
imageMetadata?: ImageMetadata,
|
|
31
|
+
): Promise<{
|
|
32
|
+
host: "youtube" | "vimeo" | "mp4" | "external"
|
|
26
33
|
id: string | null
|
|
27
34
|
image?: string
|
|
35
|
+
href?: string
|
|
28
36
|
}> {
|
|
29
|
-
const
|
|
37
|
+
const resolvedImage = async () => {
|
|
38
|
+
if (!imageMetadata) {
|
|
39
|
+
return undefined
|
|
40
|
+
}
|
|
41
|
+
return (await getImage({ src: imageMetadata, format: "webp" })).src
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
let url: URL
|
|
45
|
+
try {
|
|
46
|
+
url = new URL(urlToParse)
|
|
47
|
+
} catch {
|
|
48
|
+
return {
|
|
49
|
+
host: "external",
|
|
50
|
+
id: null,
|
|
51
|
+
href: urlToParse,
|
|
52
|
+
image: await resolvedImage(),
|
|
53
|
+
}
|
|
54
|
+
}
|
|
30
55
|
// https://www.youtube.com/embed/ABC123abc
|
|
31
56
|
// https://www.youtube.com/watch?v=ABC123abc
|
|
32
57
|
if (url.hostname === "www.youtube.com") {
|
|
@@ -54,12 +79,15 @@ async function parseUrl(urlToParse: string): Promise<{
|
|
|
54
79
|
|
|
55
80
|
// https://domain.com/video.mp4
|
|
56
81
|
if (url.pathname.endsWith(".mp4")) {
|
|
57
|
-
const image =
|
|
58
|
-
imageMetadata &&
|
|
59
|
-
(await getImage({ src: imageMetadata, format: "webp" })).src
|
|
82
|
+
const image = await resolvedImage()
|
|
60
83
|
return { host: "mp4", id: url.toString(), image }
|
|
61
84
|
}
|
|
62
|
-
|
|
85
|
+
return {
|
|
86
|
+
host: "external",
|
|
87
|
+
id: null,
|
|
88
|
+
href: url.toString(),
|
|
89
|
+
image: await resolvedImage(),
|
|
90
|
+
}
|
|
63
91
|
}
|
|
64
92
|
---
|
|
65
93
|
|
|
@@ -86,6 +114,42 @@ async function parseUrl(urlToParse: string): Promise<{
|
|
|
86
114
|
<video class="h-full w-full" controls preload="auto" poster={image}>
|
|
87
115
|
<source src={id} type="video/mp4" />
|
|
88
116
|
</video>
|
|
89
|
-
) :
|
|
117
|
+
) : (
|
|
118
|
+
<a
|
|
119
|
+
class="group relative flex h-full w-full items-center justify-center focus:outline-none focus-visible:ring-2 focus-visible:ring-white/80 focus-visible:ring-offset-2 focus-visible:ring-offset-black"
|
|
120
|
+
href={href ?? url}
|
|
121
|
+
target="_blank"
|
|
122
|
+
rel="noreferrer noopener"
|
|
123
|
+
>
|
|
124
|
+
{imageMetadata ? (
|
|
125
|
+
<Image
|
|
126
|
+
src={imageMetadata}
|
|
127
|
+
alt={title ?? ""}
|
|
128
|
+
class="h-full w-full object-cover"
|
|
129
|
+
/>
|
|
130
|
+
) : (
|
|
131
|
+
<div class="flex h-full w-full items-center justify-center bg-gray-900 text-gray-200">
|
|
132
|
+
<span class="text-sm font-medium">{t("ln.external-link")}</span>
|
|
133
|
+
</div>
|
|
134
|
+
)}
|
|
135
|
+
<div class="absolute inset-0 z-10 bg-black/35 transition group-hover:bg-black/50" />
|
|
136
|
+
<div class="absolute inset-0 z-20 flex items-center justify-center">
|
|
137
|
+
<span class="flex h-14 w-14 items-center justify-center rounded-full bg-white/90 text-black shadow-lg transition group-hover:scale-105">
|
|
138
|
+
<svg
|
|
139
|
+
class="h-7 w-7"
|
|
140
|
+
viewBox="0 0 24 24"
|
|
141
|
+
fill="currentColor"
|
|
142
|
+
aria-hidden="true"
|
|
143
|
+
>
|
|
144
|
+
<path d="M8 5v14l11-7z" />
|
|
145
|
+
</svg>
|
|
146
|
+
</span>
|
|
147
|
+
</div>
|
|
148
|
+
<div class="absolute bottom-0 end-0 start-0 z-20 flex items-center gap-2 rounded bg-black/70 px-4 py-4 text-sm font-medium text-white backdrop-blur md:text-sm">
|
|
149
|
+
<Icon className="mdi--external-link" ariaLabel="" />
|
|
150
|
+
{t("ln.external-link")}
|
|
151
|
+
</div>
|
|
152
|
+
</a>
|
|
153
|
+
)
|
|
90
154
|
}
|
|
91
155
|
</div>
|
|
@@ -60,12 +60,11 @@ ln.previous: Previous
|
|
|
60
60
|
# English: Next
|
|
61
61
|
ln.next: Next
|
|
62
62
|
|
|
63
|
-
#
|
|
64
|
-
# This is only "visible" to a screen-reader.
|
|
63
|
+
# Hint for an external link.
|
|
65
64
|
#
|
|
66
|
-
# English: External link
|
|
65
|
+
# English: External link - opens in a new tab
|
|
67
66
|
# Used on: https://sk8-ministries.pages.dev/en/media/ollie-with-integrity--en/
|
|
68
|
-
ln.external-link: External link
|
|
67
|
+
ln.external-link: External link - opens in a new tab
|
|
69
68
|
|
|
70
69
|
# Title for the search page.
|
|
71
70
|
#
|
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
---
|
|
2
|
-
import { AstroError } from "astro/errors"
|
|
3
|
-
|
|
4
2
|
import { getMediaItem } from "../../../content/get-media-items"
|
|
5
3
|
import { createContentMetadata } from "../utils/create-content-metadata"
|
|
6
4
|
import AudioPlayer from "./AudioPlayer.astro"
|
|
@@ -15,16 +13,7 @@ const { t } = Astro.locals.i18n
|
|
|
15
13
|
|
|
16
14
|
const item = await getMediaItem(mediaId)
|
|
17
15
|
|
|
18
|
-
const content = item.data.content
|
|
19
|
-
.map((c) => createContentMetadata(c))
|
|
20
|
-
.filter((c) => c.extension === "mp3")
|
|
21
|
-
|
|
22
|
-
if (!content.length) {
|
|
23
|
-
throw new AstroError(
|
|
24
|
-
`Missing mp3 content for ${mediaId}`,
|
|
25
|
-
`Add at least one mp3 file to the content array of /src/media/${mediaId}.json`,
|
|
26
|
-
)
|
|
27
|
-
}
|
|
16
|
+
const content = item.data.content.map((c) => createContentMetadata(c))
|
|
28
17
|
---
|
|
29
18
|
|
|
30
19
|
<ol
|
|
@@ -1,20 +1,52 @@
|
|
|
1
1
|
---
|
|
2
|
-
import
|
|
2
|
+
import Icon from "../../../components/Icon"
|
|
3
3
|
|
|
4
4
|
type Props = {
|
|
5
5
|
src: string
|
|
6
6
|
className?: string
|
|
7
7
|
}
|
|
8
8
|
const { src, className } = Astro.props
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
}
|
|
9
|
+
const { t } = Astro.locals.i18n
|
|
10
|
+
|
|
11
|
+
const normalizedSrc = src.trim()
|
|
12
|
+
const audioExtension = normalizedSrc.split("?")[0].split("#")[0].toLowerCase()
|
|
13
|
+
const isMp3 = audioExtension.endsWith(".mp3")
|
|
15
14
|
---
|
|
16
15
|
|
|
17
|
-
|
|
16
|
+
{
|
|
17
|
+
isMp3 ? (
|
|
18
|
+
<audio class="rounded-2xl" class:list={[className]} src={src} controls />
|
|
19
|
+
) : (
|
|
20
|
+
<a
|
|
21
|
+
class="group flex w-full items-center gap-4 rounded-2xl border border-gray-200 bg-white px-4 py-4 shadow-sm transition hover:border-gray-300 hover:bg-gray-50 focus:outline-none focus-visible:ring-2 focus-visible:ring-gray-900/70"
|
|
22
|
+
class:list={[className]}
|
|
23
|
+
href={src}
|
|
24
|
+
target="_blank"
|
|
25
|
+
rel="noreferrer noopener"
|
|
26
|
+
aria-label={t("ln.external-link")}
|
|
27
|
+
>
|
|
28
|
+
<span class="flex h-12 w-12 items-center justify-center rounded-full border border-gray-700 text-gray-700 shadow-sm transition group-hover:scale-105">
|
|
29
|
+
<svg
|
|
30
|
+
class="h-6 w-6"
|
|
31
|
+
viewBox="0 0 24 24"
|
|
32
|
+
fill="currentColor"
|
|
33
|
+
aria-hidden="true"
|
|
34
|
+
>
|
|
35
|
+
<path d="M8 5v14l11-7z" />
|
|
36
|
+
</svg>
|
|
37
|
+
</span>
|
|
38
|
+
<div class="flex h-full grow flex-col gap-3">
|
|
39
|
+
<span class="flex items-center gap-2 font-semibold text-gray-900">
|
|
40
|
+
<Icon className="mdi--external-link" ariaLabel="" />
|
|
41
|
+
{t("ln.external-link")}
|
|
42
|
+
</span>
|
|
43
|
+
<div class="h-2 w-full rounded-full bg-gray-200">
|
|
44
|
+
<div class="h-full w-2 rounded-full bg-gray-400" />
|
|
45
|
+
</div>
|
|
46
|
+
</div>
|
|
47
|
+
</a>
|
|
48
|
+
)
|
|
49
|
+
}
|
|
18
50
|
|
|
19
51
|
<style>
|
|
20
52
|
audio::-webkit-media-controls-enclosure {
|