Skip to content

Commit 7c3af8d

Browse files
nikoshellpre-commit-ci[bot]clytaemnestra
authored
YouTube player for markdown (#1291)
New YouTube component which can be also used in any markdown content. YT links are replaced with player. --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Mia Bajić <[email protected]>
1 parent 751624d commit 7c3af8d

File tree

9 files changed

+151
-99
lines changed

9 files changed

+151
-99
lines changed

.env.development

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
EP_SESSIONS_API="https://static.europython.eu/programme/ep2025/releases/current/sessions.json"
22
EP_SPEAKERS_API="https://static.europython.eu/programme/ep2025/releases/current/speakers.json"
33
EP_SCHEDULE_API="https://static.europython.eu/programme/ep2025/releases/current/schedule.json"
4+
EP_FAST_BUILD="true"

astro.config.mjs

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import path from "path";
2+
import { loadEnv } from "vite";
23
import { defineConfig } from "astro/config";
34
import mdx from "@astrojs/mdx";
45
import sitemap from "@astrojs/sitemap";
@@ -27,6 +28,15 @@ if (!gitVersion) {
2728
}
2829
}
2930

31+
const mode =
32+
process.argv.find((arg) =>
33+
["development", "production", "preview"].includes(arg)
34+
) || "production";
35+
const fastBuild = loadEnv(mode, process.cwd(), "").EP_FAST_BUILD === "true";
36+
console.log(
37+
`\x1b[35m[EP]\x1b[0m Fast Build: \x1b[1m\x1b[34m${fastBuild}\x1b[0m`
38+
);
39+
3040
function dontDie() {
3141
return {
3242
name: "dont-die",
@@ -114,23 +124,27 @@ export default defineConfig({
114124
},
115125
integrations: [
116126
mdx(),
117-
sitemap(),
118-
metaTags(),
119-
deleteUnusedImages(),
120127
svelte(),
121128
serviceWorker({
122129
workbox: { inlineWorkboxRuntime: true },
123130
}),
124-
compress({
125-
HTML: false,
126-
CSS: false,
127-
SVG: false,
128-
}),
129-
dontDie(),
131+
...(fastBuild
132+
? []
133+
: [
134+
sitemap(),
135+
metaTags(),
136+
deleteUnusedImages(),
137+
compress({
138+
HTML: false,
139+
CSS: false,
140+
SVG: false,
141+
}),
142+
dontDie(),
143+
]),
130144
],
131145
output: "static",
132146
build: {
133-
minify: true,
147+
...(fastBuild ? {} : { minify: true }),
134148
},
135149
image: {
136150
remotePatterns: [{ protocol: "https" }],

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
"date-fns-tz": "^3.2.0",
3434
"hastscript": "^9.0.1",
3535
"js-yaml": "^4.1.0",
36+
"lite-youtube-embed": "^0.3.3",
3637
"marked": "^15.0.12",
3738
"nanostores": "^1.0.1",
3839
"pagefind": "^1.3.0",
@@ -50,7 +51,8 @@
5051
"prettier-plugin-astro": "^0.14.1",
5152
"puppeteer": "^24.9.0",
5253
"tsx": "^4.19.4",
53-
"typescript": "^5.8.3"
54+
"typescript": "^5.8.3",
55+
"vite": "^6.3.5"
5456
},
5557
"prettier": {
5658
"proseWrap": "always"

pnpm-lock.yaml

Lines changed: 20 additions & 75 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/components/InstallPWA.astro

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,12 @@
2929

3030
<script>
3131
function showInstallPrompt() {
32-
const installBanner = document.getElementById('install-pwa');
32+
const installBanner = document.getElementById('install-pwa') as HTMLElement;
3333
installBanner.classList.remove('hidden');
3434
}
3535

36-
document.getElementById('install-btn').addEventListener('click', () => {
36+
const installBtn = document.getElementById('install-btn') as HTMLElement;
37+
installBtn.addEventListener('click', () => {
3738
if (deferredPrompt) {
3839
deferredPrompt.prompt();
3940
deferredPrompt.userChoice.then((choiceResult: any) => {

src/components/ui/Markdown.astro

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
---
22
import { marked } from 'marked';
3+
import { replaceYouTubeLinks } from "@utils/markdown";
34
45
interface Props {
56
content: string;
67
}
78
89
const { content } = Astro.props;
9-
const html = marked.parse(content);
10+
const html = marked.parse(await replaceYouTubeLinks(content) );
1011
---
1112

1213
<div class="prose prose-xl max-w-none" >

src/components/ui/YouTube.astro

Lines changed: 67 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,78 @@
11
---
2-
import { YouTube as Player } from "@astro-community/astro-embed-youtube";
32
4-
type Props = {
5-
id?: string;
6-
class?: string;
7-
[key: string]: any;
3+
import 'lite-youtube-embed/src/lite-yt-embed.css';
4+
import liteJS from 'lite-youtube-embed/src/lite-yt-embed.js?url';
5+
6+
const urlPattern =
7+
/(?=(\s*))\1(?:<a [^>]*?>)??(?=(\s*))\2(?:https?:\/\/)??(?:w{3}\.)??(?:youtube\.com|youtu\.be)\/(?:watch\?v=|embed\/|shorts\/)??([A-Za-z0-9-_]{11})(?:[^\s<>]*)(?=(\s*))\4(?:<\/a>)??(?=(\s*))\5/;
8+
9+
function urlMatcher(url: string): string | undefined {
10+
const match = url.match(urlPattern);
11+
return match?.[3];
12+
}
13+
14+
export interface Props extends astroHTML.JSX.HTMLAttributes {
15+
id: string;
16+
poster?: string;
17+
posterQuality?: 'max' | 'high' | 'default' | 'low';
18+
params?: string;
19+
playlabel?: string;
820
}
921
1022
const {
23+
id,
24+
poster,
25+
posterQuality = 'default',
26+
title,
1127
class: userClass = '',
12-
...attrs
13-
} = Astro.props;
14-
15-
const attrId = attrs.id || '';
28+
...attrs
29+
} = Astro.props as Props;
1630
1731
const defaultClass = 'border-4 border-white rounded-lg shadow-lg';
1832
const className = `${defaultClass} ${userClass}`.trim();
33+
34+
const idRegExp = /^[A-Za-z0-9-_]{11}$/;
35+
36+
function extractID(idOrUrl: string) {
37+
if (idRegExp.test(idOrUrl)) return idOrUrl;
38+
return urlMatcher(idOrUrl);
39+
}
40+
41+
const videoid = extractID(id);
42+
const posterFile =
43+
{
44+
max: 'maxresdefault',
45+
high: 'sddefault',
46+
default: 'hqdefault',
47+
low: 'default',
48+
}[posterQuality] || 'hqdefault';
49+
const posterURL =
50+
poster || `https://i.ytimg.com/vi/${videoid}/${posterFile}.jpg`;
51+
const href = `https://youtube.com/watch?v=${videoid}`;
1952
---
2053

21-
<Player id={attrId} class={className} {...attrs} />
54+
<lite-youtube
55+
{videoid}
56+
{title}
57+
data-title={title}
58+
{...attrs}
59+
class={className}
60+
style=`background-image: url('${posterURL}');`
61+
>
62+
<a {href} class="lty-playbtn ">
63+
<span class="lyt-visually-hidden">{attrs.playlabel || 'Play'}</span>
64+
</a>
65+
</lite-youtube>
66+
67+
<script is:inline type="module" src={liteJS}></script>
68+
69+
<style is:global>
70+
lite-youtube > iframe {
71+
all: unset !important;
72+
width: 100% !important;
73+
height: 100% !important;
74+
position: absolute !important;
75+
inset: 0 !important;
76+
border: 0 !important;
77+
}
78+
</style>

0 commit comments

Comments
 (0)