Podcast page (#26971)
This commit is contained in:
parent
4f1db3e07a
commit
6d7cdd7d41
@ -1,4 +1,4 @@
|
|||||||
import { readFileSync, readdirSync, accessSync, constants } from 'fs';
|
import { readFileSync, accessSync, constants } from 'fs';
|
||||||
import { join, basename, parse, resolve } from 'path';
|
import { join, basename, parse, resolve } from 'path';
|
||||||
import { extractFrontmatter } from '@nx/nx-dev/ui-markdoc';
|
import { extractFrontmatter } from '@nx/nx-dev/ui-markdoc';
|
||||||
import { sortPosts } from './blog.util';
|
import { sortPosts } from './blog.util';
|
||||||
@ -19,7 +19,6 @@ export class BlogApi {
|
|||||||
throw new Error('public blog root cannot be undefined');
|
throw new Error('public blog root cannot be undefined');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async getBlogTags(): Promise<string[]> {
|
async getBlogTags(): Promise<string[]> {
|
||||||
const blogs = await this.getBlogs();
|
const blogs = await this.getBlogs();
|
||||||
const tags = new Set<string>();
|
const tags = new Set<string>();
|
||||||
@ -28,8 +27,15 @@ export class BlogApi {
|
|||||||
});
|
});
|
||||||
return Array.from(tags);
|
return Array.from(tags);
|
||||||
}
|
}
|
||||||
|
async getBlogs(
|
||||||
|
filterFn?: (post: BlogPostDataEntry) => boolean
|
||||||
|
): Promise<BlogPostDataEntry[]> {
|
||||||
|
return await this.getAllBlogs(filterFn);
|
||||||
|
}
|
||||||
|
|
||||||
async getBlogs(): Promise<BlogPostDataEntry[]> {
|
async getAllBlogs(
|
||||||
|
filterFn?: (post: BlogPostDataEntry) => boolean
|
||||||
|
): Promise<BlogPostDataEntry[]> {
|
||||||
const files: string[] = await readdir(this.options.blogRoot);
|
const files: string[] = await readdir(this.options.blogRoot);
|
||||||
const authors = JSON.parse(
|
const authors = JSON.parse(
|
||||||
readFileSync(join(this.options.blogRoot, 'authors.json'), 'utf8')
|
readFileSync(join(this.options.blogRoot, 'authors.json'), 'utf8')
|
||||||
@ -63,65 +69,16 @@ export class BlogApi {
|
|||||||
filePath,
|
filePath,
|
||||||
slug,
|
slug,
|
||||||
};
|
};
|
||||||
if (!frontmatter.draft || process.env.NODE_ENV === 'development') {
|
const isDevelopment = process.env.NODE_ENV === 'development';
|
||||||
|
const shouldIncludePost = !frontmatter.draft || isDevelopment;
|
||||||
|
|
||||||
|
if (shouldIncludePost && (!filterFn || filterFn(post))) {
|
||||||
allPosts.push(post);
|
allPosts.push(post);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return sortPosts(allPosts);
|
return sortPosts(allPosts);
|
||||||
}
|
}
|
||||||
|
|
||||||
getBlogPosts(): BlogPostDataEntry[] {
|
|
||||||
const files: string[] = readdirSync(this.options.blogRoot);
|
|
||||||
const authors = JSON.parse(
|
|
||||||
readFileSync(join(this.options.blogRoot, 'authors.json'), 'utf8')
|
|
||||||
);
|
|
||||||
const allPosts: BlogPostDataEntry[] = [];
|
|
||||||
|
|
||||||
for (const file of files) {
|
|
||||||
const filePath = join(this.options.blogRoot, file);
|
|
||||||
// filter out directories (e.g. images)
|
|
||||||
if (!filePath.endsWith('.md')) continue;
|
|
||||||
|
|
||||||
const content = readFileSync(filePath, 'utf8');
|
|
||||||
const frontmatter = extractFrontmatter(content);
|
|
||||||
const slug = this.calculateSlug(filePath, frontmatter);
|
|
||||||
const { image, type } = this.determineOgImage(frontmatter.cover_image);
|
|
||||||
const post = {
|
|
||||||
content,
|
|
||||||
title: frontmatter.title ?? null,
|
|
||||||
description: frontmatter.description ?? null,
|
|
||||||
authors: authors.filter((author) =>
|
|
||||||
frontmatter.authors.includes(author.name)
|
|
||||||
),
|
|
||||||
date: this.calculateDate(file, frontmatter),
|
|
||||||
cover_image: frontmatter.cover_image
|
|
||||||
? `/documentation${frontmatter.cover_image}` // Match the prefix used by markdown parser
|
|
||||||
: null,
|
|
||||||
tags: frontmatter.tags ?? [],
|
|
||||||
reposts: frontmatter.reposts ?? [],
|
|
||||||
pinned: frontmatter.pinned ?? false,
|
|
||||||
ogImage: image,
|
|
||||||
ogImageType: type,
|
|
||||||
filePath,
|
|
||||||
slug,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!frontmatter.draft || process.env.NODE_ENV === 'development') {
|
|
||||||
allPosts.push(post);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return sortPosts(allPosts);
|
|
||||||
}
|
|
||||||
|
|
||||||
getBlogPost(slug: string): BlogPostDataEntry {
|
|
||||||
const blogs = this.getBlogPosts();
|
|
||||||
const blog = blogs.find((b) => b.slug === slug);
|
|
||||||
if (!blog) {
|
|
||||||
throw new Error(`Could not find blog post with slug: ${slug}`);
|
|
||||||
}
|
|
||||||
return blog;
|
|
||||||
}
|
|
||||||
// Optimize this so we don't read the FS multiple times
|
// Optimize this so we don't read the FS multiple times
|
||||||
async getBlogPostBySlug(
|
async getBlogPostBySlug(
|
||||||
slug: string | null
|
slug: string | null
|
||||||
|
|||||||
16
nx-dev/data-access-documents/src/lib/podcast.api.ts
Normal file
16
nx-dev/data-access-documents/src/lib/podcast.api.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import { BlogApi } from './blog.api';
|
||||||
|
import { PodcastDataEntry } from './podcast.model';
|
||||||
|
|
||||||
|
export class PodcastApi {
|
||||||
|
_blogApi: BlogApi;
|
||||||
|
|
||||||
|
constructor(options: { blogApi: BlogApi }) {
|
||||||
|
this._blogApi = options.blogApi;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getPodcastBlogs(): Promise<PodcastDataEntry[]> {
|
||||||
|
return await this._blogApi.getBlogs((post) =>
|
||||||
|
post.tags.map((t) => t.toLowerCase()).includes('podcast')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
5
nx-dev/data-access-documents/src/lib/podcast.model.ts
Normal file
5
nx-dev/data-access-documents/src/lib/podcast.model.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import { BlogPostDataEntry } from './blog.model';
|
||||||
|
|
||||||
|
export interface PodcastDataEntry extends BlogPostDataEntry {
|
||||||
|
duration?: string;
|
||||||
|
}
|
||||||
@ -4,3 +4,5 @@ export * from './lib/blog.util';
|
|||||||
export * from './lib/blog.api';
|
export * from './lib/blog.api';
|
||||||
export * from './lib/blog.model';
|
export * from './lib/blog.model';
|
||||||
export * from './lib/tags.api';
|
export * from './lib/tags.api';
|
||||||
|
export * from './lib/podcast.model';
|
||||||
|
export * from './lib/podcast.api';
|
||||||
|
|||||||
@ -26,7 +26,7 @@ export const metadata: Metadata = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
async function getBlogs() {
|
async function getBlogs() {
|
||||||
return await blogApi.getBlogPosts();
|
return await blogApi.getBlogs();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getBlogTags() {
|
async function getBlogTags() {
|
||||||
|
|||||||
39
nx-dev/nx-dev/app/podcast/page.tsx
Normal file
39
nx-dev/nx-dev/app/podcast/page.tsx
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import { Metadata } from 'next';
|
||||||
|
import { podcastApi } from '../../lib/podcast.api';
|
||||||
|
import { DefaultLayout } from '@nx/nx-dev/ui-common';
|
||||||
|
import { Hero, PodcastList } from '@nx/nx-dev/ui-podcast';
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: 'Nx Podcast - Updates from the Nx & Nx Cloud team',
|
||||||
|
description: 'Latest podcasts from the Nx & Nx Cloud core team',
|
||||||
|
openGraph: {
|
||||||
|
url: 'https://nx.dev/podcast',
|
||||||
|
title: 'Nx Podcast - Updates from the Nx & Nx Cloud team',
|
||||||
|
description:
|
||||||
|
'Stay updated with the latest podcasts from the Nx & Nx Cloud team.',
|
||||||
|
images: [
|
||||||
|
{
|
||||||
|
url: 'https://nx.dev/socials/nx-media.png',
|
||||||
|
width: 800,
|
||||||
|
height: 421,
|
||||||
|
alt: 'Nx: Smart Monorepos · Fast CI',
|
||||||
|
type: 'image/jpeg',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
siteName: 'NxDev',
|
||||||
|
type: 'website',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
async function getPodcasts() {
|
||||||
|
return await podcastApi.getPodcastBlogs();
|
||||||
|
}
|
||||||
|
export default async function Page() {
|
||||||
|
const podcasts = await getPodcasts();
|
||||||
|
return (
|
||||||
|
<DefaultLayout>
|
||||||
|
<Hero />
|
||||||
|
<PodcastList podcasts={podcasts} />
|
||||||
|
</DefaultLayout>
|
||||||
|
);
|
||||||
|
}
|
||||||
4
nx-dev/nx-dev/lib/podcast.api.ts
Normal file
4
nx-dev/nx-dev/lib/podcast.api.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
import { blogApi } from './blog.api';
|
||||||
|
import { PodcastApi } from '@nx/nx-dev/data-access-documents/node-only';
|
||||||
|
|
||||||
|
export const podcastApi = new PodcastApi({ blogApi });
|
||||||
BIN
nx-dev/nx-dev/public/images/podcast/podcast-hero.avif
Normal file
BIN
nx-dev/nx-dev/public/images/podcast/podcast-hero.avif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 23 KiB |
@ -13,6 +13,8 @@ import {
|
|||||||
UserGroupIcon,
|
UserGroupIcon,
|
||||||
ComputerDesktopIcon,
|
ComputerDesktopIcon,
|
||||||
GlobeAltIcon,
|
GlobeAltIcon,
|
||||||
|
MicrophoneIcon,
|
||||||
|
VideoCameraIcon,
|
||||||
} from '@heroicons/react/24/outline';
|
} from '@heroicons/react/24/outline';
|
||||||
import { FC, SVGProps } from 'react';
|
import { FC, SVGProps } from 'react';
|
||||||
import { DiscordIcon } from '../discord-icon';
|
import { DiscordIcon } from '../discord-icon';
|
||||||
@ -172,6 +174,22 @@ export const learnItems: MenuItem[] = [
|
|||||||
isNew: false,
|
isNew: false,
|
||||||
isHighlight: false,
|
isHighlight: false,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'Podcasts',
|
||||||
|
description: null,
|
||||||
|
href: '/podcast',
|
||||||
|
icon: MicrophoneIcon,
|
||||||
|
isNew: false,
|
||||||
|
isHighlight: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Webinars',
|
||||||
|
description: null,
|
||||||
|
href: 'https://go.nx.dev/webinar',
|
||||||
|
icon: ComputerDesktopIcon,
|
||||||
|
isNew: false,
|
||||||
|
isHighlight: false,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'Video tutorials',
|
name: 'Video tutorials',
|
||||||
description: null,
|
description: null,
|
||||||
@ -215,10 +233,10 @@ export const eventItems: MenuItem[] = [
|
|||||||
isHighlight: false,
|
isHighlight: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Webinars',
|
name: 'Live Streams',
|
||||||
description: null,
|
description: null,
|
||||||
href: 'https://go.nx.dev/webinar',
|
href: 'https://www.youtube.com/@nxdevtools/streams',
|
||||||
icon: ComputerDesktopIcon,
|
icon: VideoCameraIcon,
|
||||||
isNew: false,
|
isNew: false,
|
||||||
isHighlight: false,
|
isHighlight: false,
|
||||||
},
|
},
|
||||||
|
|||||||
@ -10,7 +10,7 @@ export function SectionsMenu({
|
|||||||
<div className="flex flex-col gap-2 overflow-hidden rounded-lg bg-white shadow-lg ring-1 ring-slate-200 dark:bg-slate-900 dark:ring-slate-800">
|
<div className="flex flex-col gap-2 overflow-hidden rounded-lg bg-white shadow-lg ring-1 ring-slate-200 dark:bg-slate-900 dark:ring-slate-800">
|
||||||
<div className="divide-y divide-slate-200 dark:divide-slate-800">
|
<div className="divide-y divide-slate-200 dark:divide-slate-800">
|
||||||
{Object.keys(sections).map((section) => (
|
{Object.keys(sections).map((section) => (
|
||||||
<div>
|
<div key={section}>
|
||||||
<h5 className="px-4 pt-6 text-sm text-slate-500 dark:text-slate-400">
|
<h5 className="px-4 pt-6 text-sm text-slate-500 dark:text-slate-400">
|
||||||
{section}
|
{section}
|
||||||
</h5>
|
</h5>
|
||||||
|
|||||||
@ -119,3 +119,9 @@ export * from './lib/technologies/vite';
|
|||||||
export * from './lib/technologies/vitest';
|
export * from './lib/technologies/vitest';
|
||||||
export * from './lib/technologies/vue';
|
export * from './lib/technologies/vue';
|
||||||
export * from './lib/technologies/webpack';
|
export * from './lib/technologies/webpack';
|
||||||
|
|
||||||
|
// PODCASTS
|
||||||
|
export * from './lib/podcasts/amazon-music';
|
||||||
|
export * from './lib/podcasts/apple-podcasts';
|
||||||
|
export * from './lib/podcasts/i-heart-radio';
|
||||||
|
export * from './lib/podcasts/spotify';
|
||||||
|
|||||||
18
nx-dev/ui-icons/src/lib/podcasts/amazon-music.tsx
Normal file
18
nx-dev/ui-icons/src/lib/podcasts/amazon-music.tsx
Normal file
File diff suppressed because one or more lines are too long
20
nx-dev/ui-icons/src/lib/podcasts/apple-podcasts.tsx
Normal file
20
nx-dev/ui-icons/src/lib/podcasts/apple-podcasts.tsx
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import { FC, SVGProps } from 'react';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Color: #9933CC
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const ApplePodcastsIcon: FC<SVGProps<SVGSVGElement>> = (props) => {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
role="img"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
fill="currentColor"
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<title>Apple Podcasts</title>
|
||||||
|
<path d="M5.34 0A5.328 5.328 0 000 5.34v13.32A5.328 5.328 0 005.34 24h13.32A5.328 5.328 0 0024 18.66V5.34A5.328 5.328 0 0018.66 0zm6.525 2.568c2.336 0 4.448.902 6.056 2.587 1.224 1.272 1.912 2.619 2.264 4.392.12.59.12 2.2.007 2.864a8.506 8.506 0 01-3.24 5.296c-.608.46-2.096 1.261-2.336 1.261-.088 0-.096-.091-.056-.46.072-.592.144-.715.48-.856.536-.224 1.448-.874 2.008-1.435a7.644 7.644 0 002.008-3.536c.208-.824.184-2.656-.048-3.504-.728-2.696-2.928-4.792-5.624-5.352-.784-.16-2.208-.16-3 0-2.728.56-4.984 2.76-5.672 5.528-.184.752-.184 2.584 0 3.336.456 1.832 1.64 3.512 3.192 4.512.304.2.672.408.824.472.336.144.408.264.472.856.04.36.03.464-.056.464-.056 0-.464-.176-.896-.384l-.04-.03c-2.472-1.216-4.056-3.274-4.632-6.012-.144-.706-.168-2.392-.03-3.04.36-1.74 1.048-3.1 2.192-4.304 1.648-1.737 3.768-2.656 6.128-2.656zm.134 2.81c.409.004.803.04 1.106.106 2.784.62 4.76 3.408 4.376 6.174-.152 1.114-.536 2.03-1.216 2.88-.336.43-1.152 1.15-1.296 1.15-.023 0-.048-.272-.048-.603v-.605l.416-.496c1.568-1.878 1.456-4.502-.256-6.224-.664-.67-1.432-1.064-2.424-1.246-.64-.118-.776-.118-1.448-.008-1.02.167-1.81.562-2.512 1.256-1.72 1.704-1.832 4.342-.264 6.222l.413.496v.608c0 .336-.027.608-.06.608-.03 0-.264-.16-.512-.36l-.034-.011c-.832-.664-1.568-1.842-1.872-2.997-.184-.698-.184-2.024.008-2.72.504-1.878 1.888-3.335 3.808-4.019.41-.145 1.133-.22 1.814-.211zm-.13 2.99c.31 0 .62.06.844.178.488.253.888.745 1.04 1.259.464 1.578-1.208 2.96-2.72 2.254h-.015c-.712-.331-1.096-.956-1.104-1.77 0-.733.408-1.371 1.112-1.745.224-.117.534-.176.844-.176zm-.011 4.728c.988-.004 1.706.349 1.97.97.198.464.124 1.932-.218 4.302-.232 1.656-.36 2.074-.68 2.356-.44.39-1.064.498-1.656.288h-.003c-.716-.257-.87-.605-1.164-2.644-.341-2.37-.416-3.838-.218-4.302.262-.616.974-.966 1.97-.97z" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
};
|
||||||
20
nx-dev/ui-icons/src/lib/podcasts/i-heart-radio.tsx
Normal file
20
nx-dev/ui-icons/src/lib/podcasts/i-heart-radio.tsx
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import { FC, SVGProps } from 'react';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Color: #C6002B
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const IHeartRadioIcon: FC<SVGProps<SVGSVGElement>> = (props) => {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
role="img"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
fill="currentColor"
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<title>iHeartRadio</title>
|
||||||
|
<path d="M4.403 21.983c.597 0 1.023-.306 1.023-.817v-.012c0-.489-.375-.784-1.017-.784H3.182v1.613zm-1.67-1.8c0-.125.102-.228.221-.228h1.489c.488 0 .88.148 1.13.398.193.193.307.472.307.784v.011c0 .654-.443 1.034-1.062 1.154l.988 1.272c.046.051.074.102.074.164 0 .12-.114.222-.227.222-.091 0-.16-.05-.21-.12l-1.12-1.453H3.183v1.346a.228.228 0 01-.228.227.227.227 0 01-.221-.227v-3.55m6.674 2.29l-.914-2.035-.915 2.034zm-2.812 1.164l1.614-3.528c.056-.125.142-.2.284-.2h.022c.137 0 .228.075.279.2l1.613 3.522a.31.31 0 01.029.113c0 .12-.097.216-.216.216-.108 0-.182-.074-.222-.165l-.415-.914H7.402l-.415.926c-.04.097-.113.153-.216.153a.204.204 0 01-.204-.204.26.26 0 01.028-.12m6.078-.118c1.005 0 1.647-.682 1.647-1.563v-.011c0-.88-.642-1.574-1.647-1.574h-.932v3.148zm-1.38-3.335c0-.125.102-.228.221-.228h1.16c1.249 0 2.112.858 2.112 1.977v.012c0 1.119-.863 1.988-2.113 1.988h-1.159a.226.226 0 01-.221-.227v-3.522m4.481-.029c0-.124.103-.227.222-.227.125 0 .227.103.227.227v3.579a.228.228 0 01-.227.227.227.227 0 01-.222-.227v-3.579m5.027 1.801v-.011c0-.904-.659-1.642-1.568-1.642s-1.556.727-1.556 1.63v.012c0 .903.659 1.642 1.567 1.642.91 0 1.557-.728 1.557-1.631zm-3.59 0v-.011c0-1.097.824-2.057 2.033-2.057 1.21 0 2.023.949 2.023 2.045v.012c0 1.096-.824 2.056-2.034 2.056s-2.022-.949-2.022-2.045m2.03-17.192c0 1.397-.754 2.773-2.242 4.092a.345.345 0 01-.458-.517c1.333-1.182 2.01-2.385 2.01-3.575v-.016c0-.966-.606-2.103-1.38-2.588a.345.345 0 11.367-.586c.97.61 1.703 1.974 1.703 3.174zM14.76 7.677a.345.345 0 11-.337-.602c.799-.448 1.336-1.318 1.339-2.167a2.096 2.096 0 00-1.124-1.855.345.345 0 11.321-.611 2.785 2.785 0 011.493 2.46v.011c-.004 1.09-.683 2.199-1.692 2.764zm-2.772-1.015a1.498 1.498 0 11.001-2.997 1.498 1.498 0 01-.001 2.997zm-2.303.882a.345.345 0 01-.47.133c-1.009-.565-1.688-1.674-1.692-2.764v-.01a2.785 2.785 0 011.493-2.461.346.346 0 01.321.611 2.096 2.096 0 00-1.124 1.855c.003.849.54 1.719 1.34 2.166a.345.345 0 01.132.47zM7.464 8.825a.344.344 0 01-.488.03C5.49 7.536 4.734 6.16 4.734 4.763v-.016c0-1.2.732-2.564 1.703-3.174a.346.346 0 01.367.586c-.774.485-1.38 1.622-1.38 2.588v.016c0 1.19.677 2.393 2.01 3.575a.345.345 0 01.03.487zM16.152 0c-1.727 0-3.27.915-4.164 2.252C11.094.915 9.55 0 7.823 0A4.982 4.982 0 002.84 4.983c0 1.746 1.106 3.005 2.261 4.17l4.518 4.272a.371.371 0 00.626-.27V9.827c0-.963.78-1.743 1.743-1.745a1.745 1.745 0 011.742 1.745v3.328c0 .326.39.493.626.27l4.518-4.272c1.155-1.165 2.261-2.424 2.261-4.17A4.982 4.982 0 0016.152 0M4.582 14.766h1.194v1.612h1.532v-1.612H8.5v4.307H7.308v-1.637H5.776v1.637H4.582v-4.307m6.527 2.353a.563.563 0 00-.578-.587c-.308 0-.55.238-.578.587zm-2.264.305v-.012c0-.972.696-1.741 1.68-1.741 1.15 0 1.68.842 1.68 1.82 0 .075 0 .16-.007.24H9.971c.093.364.357.549.72.549.277 0 .498-.105.738-.34l.647.536c-.32.406-.782.677-1.447.677-1.045 0-1.784-.695-1.784-1.729m7.29-1.68h1.17v.67c.19-.454.498-.75 1.051-.725v1.23h-.098c-.609 0-.954.351-.954 1.12v1.034h-1.168v-3.329m2.95 2.295v-1.353h-.393v-.942h.393v-.842h1.17v.842h.775v.942h-.775v1.126c0 .234.105.332.32.332.153 0 .301-.043.442-.11v.916c-.209.117-.485.19-.812.19-.7 0-1.12-.307-1.12-1.1m-15.65-3.584a.62.62 0 100 1.24.62.62 0 000-1.24m10.502 3.952c-.303.013-.483-.161-.483-.371 0-.203.16-.307.454-.307h.667v.036c-.004.137-.06.617-.638.642zm1.746-1.008c0-1.033-.739-1.729-1.784-1.729-.665 0-1.126.271-1.447.677l.647.536c.24-.234.461-.34.738-.34.359 0 .621.182.716.537l.001.025-.77.003c-.956.013-1.458.37-1.458 1.045 0 .65.464.999 1.262.999.432 0 .764-.17.987-.401v.32h1.106v-1.628l.002-.032V17.4M3.458 15.99h-.043a.61.61 0 00-.61.61v2.474h1.263v-2.474a.61.61 0 00-.61-.61" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
};
|
||||||
20
nx-dev/ui-icons/src/lib/podcasts/spotify.tsx
Normal file
20
nx-dev/ui-icons/src/lib/podcasts/spotify.tsx
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import { FC, SVGProps } from 'react';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Color: #1DB954
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const SpotifyIcon: FC<SVGProps<SVGSVGElement>> = (props) => {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
role="img"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
fill="currentColor"
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<title>Spotify</title>
|
||||||
|
<path d="M12 0C5.4 0 0 5.4 0 12s5.4 12 12 12 12-5.4 12-12S18.66 0 12 0zm5.521 17.34c-.24.359-.66.48-1.021.24-2.82-1.74-6.36-2.101-10.561-1.141-.418.122-.779-.179-.899-.539-.12-.421.18-.78.54-.9 4.56-1.021 8.52-.6 11.64 1.32.42.18.479.659.301 1.02zm1.44-3.3c-.301.42-.841.6-1.262.3-3.239-1.98-8.159-2.58-11.939-1.38-.479.12-1.02-.12-1.14-.6-.12-.48.12-1.021.6-1.141C9.6 9.9 15 10.561 18.72 12.84c.361.181.54.78.241 1.2zm.12-3.36C15.24 8.4 8.82 8.16 5.16 9.301c-.6.179-1.2-.181-1.38-.721-.18-.601.18-1.2.72-1.381 4.26-1.26 11.28-1.02 15.721 1.621.539.3.719 1.02.419 1.56-.299.421-1.02.599-1.559.3z" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
};
|
||||||
12
nx-dev/ui-podcast/.babelrc
Normal file
12
nx-dev/ui-podcast/.babelrc
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"presets": [
|
||||||
|
[
|
||||||
|
"@nx/react/babel",
|
||||||
|
{
|
||||||
|
"runtime": "automatic",
|
||||||
|
"useBuiltIns": "usage"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"plugins": []
|
||||||
|
}
|
||||||
18
nx-dev/ui-podcast/.eslintrc.json
Normal file
18
nx-dev/ui-podcast/.eslintrc.json
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"extends": ["plugin:@nx/react", "../../.eslintrc.json"],
|
||||||
|
"ignorePatterns": ["!**/*"],
|
||||||
|
"overrides": [
|
||||||
|
{
|
||||||
|
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
|
||||||
|
"rules": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"files": ["*.ts", "*.tsx"],
|
||||||
|
"rules": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"files": ["*.js", "*.jsx"],
|
||||||
|
"rules": {}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
7
nx-dev/ui-podcast/README.md
Normal file
7
nx-dev/ui-podcast/README.md
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
# ui-podcast
|
||||||
|
|
||||||
|
This library was generated with [Nx](https://nx.dev).
|
||||||
|
|
||||||
|
## Running unit tests
|
||||||
|
|
||||||
|
Run `nx test ui-podcast` to execute the unit tests via [Jest](https://jestjs.io).
|
||||||
9
nx-dev/ui-podcast/project.json
Normal file
9
nx-dev/ui-podcast/project.json
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"name": "ui-podcast",
|
||||||
|
"$schema": "../../node_modules/nx/schemas/project-schema.json",
|
||||||
|
"sourceRoot": "nx-dev/ui-podcast/src",
|
||||||
|
"projectType": "library",
|
||||||
|
"tags": [],
|
||||||
|
"// targets": "to see all targets run: nx show project ui-podcast --web",
|
||||||
|
"targets": {}
|
||||||
|
}
|
||||||
2
nx-dev/ui-podcast/src/index.ts
Normal file
2
nx-dev/ui-podcast/src/index.ts
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export * from './lib/hero';
|
||||||
|
export * from './lib/podcast-list';
|
||||||
35
nx-dev/ui-podcast/src/lib/hero.tsx
Normal file
35
nx-dev/ui-podcast/src/lib/hero.tsx
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import { SectionHeading } from '@nx/nx-dev/ui-common';
|
||||||
|
import { ListenOn } from './listen-on';
|
||||||
|
|
||||||
|
export function Hero(): JSX.Element {
|
||||||
|
return (
|
||||||
|
<div className="mx-auto max-w-7xl">
|
||||||
|
<div className="grid grid-cols-4 gap-x-4 px-8 lg:grid-cols-12 lg:gap-x-6">
|
||||||
|
<div className="col-span-full md:col-span-4 lg:col-span-6">
|
||||||
|
<SectionHeading as="h1" variant="title">
|
||||||
|
The Enterprise Software Podcast
|
||||||
|
</SectionHeading>
|
||||||
|
<div className="flex flex-col gap-6">
|
||||||
|
<SectionHeading as="p" variant="subtitle" className="mt-8">
|
||||||
|
Listen in to some exciting conversations with the hidden
|
||||||
|
architects of enterprise software.
|
||||||
|
</SectionHeading>
|
||||||
|
<div className="flex flex-col gap-3 text-lg">
|
||||||
|
<p className="font-medium text-slate-950 dark:text-white">
|
||||||
|
Available On:{' '}
|
||||||
|
</p>
|
||||||
|
<ListenOn />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="hidden lg:col-span-3 lg:col-start-10 lg:block">
|
||||||
|
<img
|
||||||
|
className="aspect[1/1] rounded-lg border-8 border-slate-800/50 object-cover dark:border-white"
|
||||||
|
src="/images/podcast/podcast-hero.avif"
|
||||||
|
alt="Illustration of a microphone"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
53
nx-dev/ui-podcast/src/lib/listen-on.tsx
Normal file
53
nx-dev/ui-podcast/src/lib/listen-on.tsx
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
import {
|
||||||
|
AmazonMusicIcon,
|
||||||
|
ApplePodcastsIcon,
|
||||||
|
IHeartRadioIcon,
|
||||||
|
SpotifyIcon,
|
||||||
|
} from '@nx/nx-dev/ui-icons';
|
||||||
|
|
||||||
|
export function ListenOn(): JSX.Element {
|
||||||
|
const platforms = [
|
||||||
|
{
|
||||||
|
name: 'Amazon Music',
|
||||||
|
url: 'https://music.amazon.com/podcasts/a221fdad-36fd-4695-a5b4-038d7b99d284/the-enterprise-software-podcast-by-nx',
|
||||||
|
icon: AmazonMusicIcon,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Apple Podcasts',
|
||||||
|
url: 'https://podcasts.apple.com/us/podcast/the-enterprise-software-podcast-by-nx/id1752704996',
|
||||||
|
icon: ApplePodcastsIcon,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'iHeartRadio',
|
||||||
|
url: 'https://www.iheart.com/podcast/269-the-enterprise-software-po-186891508/',
|
||||||
|
icon: IHeartRadioIcon,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Spotify',
|
||||||
|
url: 'https://open.spotify.com/show/6Axjn4Qh7PUWlGbNqzE7J4',
|
||||||
|
icon: SpotifyIcon,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ul className="flex flex-wrap gap-6 sm:gap-4">
|
||||||
|
{platforms.map((platform) => {
|
||||||
|
return (
|
||||||
|
<li
|
||||||
|
key={platform.name}
|
||||||
|
className="inline-block cursor-pointer place-items-center rounded-2xl border border-slate-100 bg-white p-4 text-slate-600 transition-all hover:scale-[1.02] hover:text-slate-950 dark:border-slate-800/60 dark:bg-slate-950 dark:text-slate-400 dark:hover:text-white"
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
href={platform.url}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="flex h-full w-full items-center justify-center"
|
||||||
|
>
|
||||||
|
<platform.icon className="h-8 w-8 shrink-0" />
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</ul>
|
||||||
|
);
|
||||||
|
}
|
||||||
33
nx-dev/ui-podcast/src/lib/podcast-list-item.tsx
Normal file
33
nx-dev/ui-podcast/src/lib/podcast-list-item.tsx
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import { BlogAuthors } from '@nx/nx-dev/ui-blog';
|
||||||
|
import Link from 'next/link';
|
||||||
|
import type { PodcastDataEntry } from '@nx/nx-dev/data-access-documents/node-only';
|
||||||
|
|
||||||
|
export interface PodcastListItemProps {
|
||||||
|
podcast: PodcastDataEntry;
|
||||||
|
episode: number;
|
||||||
|
}
|
||||||
|
export function PodcastListItem({ podcast, episode }: PodcastListItemProps) {
|
||||||
|
const formattedDate = new Date(podcast.date).toLocaleDateString('en-US', {
|
||||||
|
month: 'short',
|
||||||
|
day: '2-digit',
|
||||||
|
year: 'numeric',
|
||||||
|
});
|
||||||
|
return (
|
||||||
|
<Link
|
||||||
|
href={`/blog/${podcast.slug}`}
|
||||||
|
key={podcast.slug}
|
||||||
|
className="relative flex items-center gap-6 border-b border-slate-200 py-5 text-sm before:absolute before:inset-x-[-16px] before:inset-y-[-2px] before:z-[-1] before:rounded-xl before:bg-slate-200 before:opacity-0 last:border-0 before:hover:opacity-100 dark:border-slate-800 dark:before:bg-slate-800/50"
|
||||||
|
prefetch={false}
|
||||||
|
>
|
||||||
|
<span className="w-1/2 flex-none text-balance text-slate-500 sm:w-8/12 dark:text-white">
|
||||||
|
Episode {episode}: {podcast.title}
|
||||||
|
</span>
|
||||||
|
<span className="hidden w-2/12 flex-none sm:inline-block">
|
||||||
|
<time dateTime={podcast.date}>{formattedDate}</time>
|
||||||
|
</span>
|
||||||
|
<span className="hidden flex-1 overflow-hidden sm:inline-block">
|
||||||
|
<BlogAuthors authors={podcast.authors} showAuthorDetails={false} />
|
||||||
|
</span>
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
}
|
||||||
27
nx-dev/ui-podcast/src/lib/podcast-list.tsx
Normal file
27
nx-dev/ui-podcast/src/lib/podcast-list.tsx
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import { PodcastDataEntry } from '@nx/nx-dev/data-access-documents/node-only';
|
||||||
|
import { PodcastListItem } from './podcast-list-item';
|
||||||
|
|
||||||
|
export interface PodcastListProps {
|
||||||
|
podcasts: PodcastDataEntry[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function PodcastList({ podcasts }: PodcastListProps): JSX.Element {
|
||||||
|
return podcasts.length < 1 ? (
|
||||||
|
<div>
|
||||||
|
<h2 className="mt-32 text-center text-xl font-semibold text-slate-500 sm:text-2xl xl:mb-24 dark:text-white ">
|
||||||
|
No podcasts as yet but stay tuned!
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="mx-auto max-w-7xl px-8">
|
||||||
|
<div className="mb-8 mt-20 border-b-2 border-slate-300 pb-3 text-sm dark:border-slate-700">
|
||||||
|
<h2 className="font-semibold">Podcasts</h2>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{podcasts?.map((post, index) => (
|
||||||
|
<PodcastListItem key={post.slug} podcast={post} episode={index + 1} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
17
nx-dev/ui-podcast/tsconfig.json
Normal file
17
nx-dev/ui-podcast/tsconfig.json
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
"allowJs": false,
|
||||||
|
"esModuleInterop": false,
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"strict": true
|
||||||
|
},
|
||||||
|
"files": [],
|
||||||
|
"include": [],
|
||||||
|
"references": [
|
||||||
|
{
|
||||||
|
"path": "./tsconfig.lib.json"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"extends": "../../tsconfig.base.json"
|
||||||
|
}
|
||||||
24
nx-dev/ui-podcast/tsconfig.lib.json
Normal file
24
nx-dev/ui-podcast/tsconfig.lib.json
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
"extends": "./tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "../../dist/out-tsc",
|
||||||
|
"types": [
|
||||||
|
"node",
|
||||||
|
|
||||||
|
"@nx/react/typings/cssmodule.d.ts",
|
||||||
|
"@nx/react/typings/image.d.ts"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"exclude": [
|
||||||
|
"jest.config.ts",
|
||||||
|
"src/**/*.spec.ts",
|
||||||
|
"src/**/*.test.ts",
|
||||||
|
"src/**/*.spec.tsx",
|
||||||
|
"src/**/*.test.tsx",
|
||||||
|
"src/**/*.spec.js",
|
||||||
|
"src/**/*.test.js",
|
||||||
|
"src/**/*.spec.jsx",
|
||||||
|
"src/**/*.test.jsx"
|
||||||
|
],
|
||||||
|
"include": ["src/**/*.js", "src/**/*.jsx", "src/**/*.ts", "src/**/*.tsx"]
|
||||||
|
}
|
||||||
@ -108,6 +108,7 @@
|
|||||||
"@nx/nx-dev/ui-icons": ["nx-dev/ui-icons/src/index.ts"],
|
"@nx/nx-dev/ui-icons": ["nx-dev/ui-icons/src/index.ts"],
|
||||||
"@nx/nx-dev/ui-markdoc": ["nx-dev/ui-markdoc/src/index.ts"],
|
"@nx/nx-dev/ui-markdoc": ["nx-dev/ui-markdoc/src/index.ts"],
|
||||||
"@nx/nx-dev/ui-member-card": ["nx-dev/ui-member-card/src/index.ts"],
|
"@nx/nx-dev/ui-member-card": ["nx-dev/ui-member-card/src/index.ts"],
|
||||||
|
"@nx/nx-dev/ui-podcast": ["nx-dev/ui-podcast/src/index.ts"],
|
||||||
"@nx/nx-dev/ui-pricing": ["nx-dev/ui-pricing/src/index.ts"],
|
"@nx/nx-dev/ui-pricing": ["nx-dev/ui-pricing/src/index.ts"],
|
||||||
"@nx/nx-dev/ui-primitives": ["nx-dev/ui-primitives/src/index.ts"],
|
"@nx/nx-dev/ui-primitives": ["nx-dev/ui-primitives/src/index.ts"],
|
||||||
"@nx/nx-dev/ui-references": ["nx-dev/ui-references/src/index.ts"],
|
"@nx/nx-dev/ui-references": ["nx-dev/ui-references/src/index.ts"],
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user