feat(nx-dev): webinar page (#29913)

Adds a webinar page and a script to pull the webinar content from Notion
This commit is contained in:
Isaac Mann 2025-02-07 14:22:52 -05:00 committed by GitHub
parent a9178171b3
commit 33352bc970
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
37 changed files with 905 additions and 207 deletions

View File

@ -0,0 +1,16 @@
---
title: 'Monorepos: the Benefits, Challenges, and Importance of Tooling Support '
description: 'Learn how monorepos and better tooling can help you overcome challenges in software development like scalability, maintenance, communication, and cost.'
slug: 'monorepos-the-benefits-challenges-and-importance-of-tooling-support'
authors: ['Juri Strumpflohner']
tags: [webinar]
cover_image: /blog/images/2024-01-24/january-webinar-card.png
status: Past - Gated
registrationUrl: https://go.nx.dev/january-webinar
---
Presented by Juri Strumpflohner
Learn how monorepos and better tooling can help you overcome challenges in software development like scalability, maintenance, communication, and cost.
{% call-to-action title="Download the recording" url="https://go.nx.dev/january-webinar" description="Sign up to gain access" /%}

View File

@ -0,0 +1,17 @@
---
title: "Nx Agents Walkthrough:
Effortlessly Fast CI Built for Monorepos"
description: "Learn how you can streamline your existing CI config to its absolute simplest form, reducing CI times from 30 minutes to ~5 minutes with Nx Agents. "
slug: 'nx-agents-walkthrougheffortlessly-fast-ci-built-for-monorepos'
authors: ['Rares Matei']
tags: [webinar]
cover_image: /blog/images/2024-03-11/march-webinar.png
status: Past - Gated
registrationUrl: https://go.nx.dev/march-webinar
---
Presented by Rares Matei
Learn how you can streamline your existing CI config to its absolute simplest form, reducing CI times from 30 minutes to ~5 minutes with Nx Agents.
{% call-to-action title="Download the recording" url="https://go.nx.dev/march-webinar" description="Sign up to gain access" /%}

View File

@ -0,0 +1,16 @@
---
title: 'Making the Argument for Monorepos'
description: 'Trying to convince your colleagues to use a monorepo? Already using a monorepo and need to justify that decision? Check out this webinar to learn 7 essential reasons for using monorepos and bust a few myths and misconceptions along the way.'
slug: 'making-the-argument-for-monorepos'
authors: ['Miroslav Jonaš']
tags: [webinar]
cover_image: /blog/images/2024-04-17/april-webinar.png
status: Past - Gated
registrationUrl: https://go.nx.dev/april-webinar
---
Presented by Miroslav Jonaš
Trying to convince your colleagues to use a monorepo? Already using a monorepo and need to justify that decision? Check out this webinar to learn 7 essential reasons for using monorepos and bust a few myths and misconceptions along the way.
{% call-to-action title="Download the recording" url="https://go.nx.dev/april-webinar" description="Sign up to gain access" /%}

View File

@ -0,0 +1,18 @@
---
title: "Monorepos and CI can be a Mess - Here's How Nx and Nx Cloud Fixed It"
description: "Continuous Integration (CI) in monorepos can be notoriously slow and unreliable, quickly become a bottleneck for scaling monorepos.
Learn how Nx and Nx Cloud's new task-based approach ensures fast, resilient, and efficient CI for your monorepo projects."
slug: 'monorepos-and-ci-can-be-a-mess-heres-how-nx-and-nx-cloud-fixed-it'
authors: ['Juri Strumpflohner']
tags: [webinar]
cover_image: /blog/images/2024-06-26/June-Webinar-card.png
status: Past - Gated
registrationUrl: https://go.nx.dev/june-webinar
---
Presented by Juri Strumpflohner
Continuous Integration (CI) in monorepos can be notoriously slow and unreliable, quickly become a bottleneck for scaling monorepos.
Learn how Nx and Nx Cloud's new task-based approach ensures fast, resilient, and efficient CI for your monorepo projects.
{% call-to-action title="Download the recording" url="https://go.nx.dev/june-webinar" description="Sign up to gain access" /%}

View File

@ -0,0 +1,19 @@
---
title: 'Nx Cloud: Scale Your CI and Team with Ease'
description: 'Learn how you can attain fast, reliable CI and better coordination across your technical organization with Nx Cloud, and see our new multi-workspace features for organizational scaling in action.'
slug: 'nx-cloud-scale-your-ci-and-team-with-ease'
authors: ['Nicole Oliver', 'Rares Matei', 'James Henry']
tags: [webinar]
cover_image: /blog/images/2025-01-22/Jan-webinar-image.png
time: 1-2pm ET/6-7pm UTC
status: Upcoming
registrationUrl: https://go.nx.dev/jan2025-webinar
---
**Jan 22, 2025 - 1-2pm ET/6-7pm UTC**
Presented by Nicole Oliver, Rares Matei, and James Henry
Learn how you can attain fast, reliable CI and better coordination across your technical organization with Nx Cloud, and see our new multi-workspace features for organizational scaling in action.
{% call-to-action title="Register today!" url="https://go.nx.dev/jan2025-webinar" description="Save your spot" /%}

View File

@ -141,5 +141,23 @@
"image": "/blog/images/Nicolas Beaussart.jpeg", "image": "/blog/images/Nicolas Beaussart.jpeg",
"twitter": "beaussan", "twitter": "beaussan",
"github": "beaussan" "github": "beaussan"
},
{
"name": "Nicole Oliver",
"image": "/blog/images/Nicole Oliver.jpeg",
"twitter": "nixcodes",
"github": "nixallover"
},
{
"name": "Rares Matei",
"image": "/blog/images/Rares Matei.jpeg",
"twitter": "__rares",
"github": "rarmatei"
},
{
"name": "James Henry",
"image": "/blog/images/James Henry.jpeg",
"twitter": "MrJamesHenry",
"github": "JamesHenry"
} }
] ]

Binary file not shown.

After

Width:  |  Height:  |  Size: 632 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 599 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 600 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 534 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 621 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

View File

@ -55,9 +55,11 @@ export class BlogApi {
title: frontmatter.title ?? null, title: frontmatter.title ?? null,
description: frontmatter.description ?? null, description: frontmatter.description ?? null,
authors: authors.filter((author) => authors: authors.filter((author) =>
frontmatter.authors.includes(author.name) frontmatter.authors?.includes(author.name)
), ),
date: this.calculateDate(file, frontmatter), date: this.calculateDate(file, frontmatter),
time: frontmatter.time,
status: frontmatter.status,
cover_image: frontmatter.cover_image cover_image: frontmatter.cover_image
? `/documentation${frontmatter.cover_image}` // Match the prefix used by markdown parser ? `/documentation${frontmatter.cover_image}` // Match the prefix used by markdown parser
: null, : null,
@ -69,6 +71,7 @@ export class BlogApi {
filePath, filePath,
slug, slug,
youtubeUrl: frontmatter.youtubeUrl, youtubeUrl: frontmatter.youtubeUrl,
registrationUrl: frontmatter.registrationUrl,
podcastYoutubeId: frontmatter.podcastYoutubeId, podcastYoutubeId: frontmatter.podcastYoutubeId,
podcastSpotifyId: frontmatter.podcastSpotifyId, podcastSpotifyId: frontmatter.podcastSpotifyId,
podcastIHeartUrl: frontmatter.podcastIHeartUrl, podcastIHeartUrl: frontmatter.podcastIHeartUrl,

View File

@ -0,0 +1,16 @@
import { BlogApi } from './blog.api';
import { WebinarDataEntry } from './webinar.model';
export class WebinarApi {
_blogApi: BlogApi;
constructor(options: { blogApi: BlogApi }) {
this._blogApi = options.blogApi;
}
async getWebinarBlogs(): Promise<WebinarDataEntry[]> {
return await this._blogApi.getBlogs((post) =>
post.tags.map((t) => t.toLowerCase()).includes('webinar')
);
}
}

View File

@ -0,0 +1,7 @@
import { BlogPostDataEntry } from './blog.model';
export interface WebinarDataEntry extends BlogPostDataEntry {
status?: 'Upcoming' | 'Past - Gated' | 'Past - Ungated';
time?: string;
registrationUrl?: string;
}

View File

@ -6,3 +6,5 @@ export * from './lib/blog.model';
export * from './lib/tags.api'; export * from './lib/tags.api';
export * from './lib/podcast.model'; export * from './lib/podcast.model';
export * from './lib/podcast.api'; export * from './lib/podcast.api';
export * from './lib/webinar.model';
export * from './lib/webinar.api';

View File

@ -0,0 +1,39 @@
import { Metadata } from 'next';
import { webinarApi } from '../../lib/webinar.api';
import { DefaultLayout } from '@nx/nx-dev/ui-common';
import { Hero, WebinarList } from '@nx/nx-dev/ui-webinar';
export const metadata: Metadata = {
title: 'Nx Webinar - Updates from the Nx & Nx Cloud team',
description: 'Latest webinars from the Nx & Nx Cloud core team',
openGraph: {
url: 'https://nx.dev/webinar',
title: 'Nx Webinar - Updates from the Nx & Nx Cloud team',
description:
'Stay updated with the latest webinars 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: 'Nx',
type: 'website',
},
};
async function getWebinars() {
return await webinarApi.getWebinarBlogs();
}
export default async function Page() {
const webinars = await getWebinars();
return (
<DefaultLayout>
<Hero />
<WebinarList webinars={webinars} />
</DefaultLayout>
);
}

View File

@ -0,0 +1,4 @@
import { blogApi } from './blog.api';
import { WebinarApi } from '@nx/nx-dev/data-access-documents/node-only';
export const webinarApi = new WebinarApi({ blogApi });

View File

@ -5,9 +5,10 @@ import Image from 'next/image';
export interface BlogEntryProps { export interface BlogEntryProps {
post: BlogPostDataEntry; post: BlogPostDataEntry;
overrideLink?: string;
} }
export function BlogEntry({ post }: BlogEntryProps) { export function BlogEntry({ post, overrideLink }: BlogEntryProps) {
return ( return (
<div className="relative flex h-full transform-gpu flex-col overflow-hidden rounded-2xl border border-slate-200 shadow transition-all duration-300 ease-in-out hover:scale-[1.02] hover:shadow-lg dark:border-slate-800"> <div className="relative flex h-full transform-gpu flex-col overflow-hidden rounded-2xl border border-slate-200 shadow transition-all duration-300 ease-in-out hover:scale-[1.02] hover:shadow-lg dark:border-slate-800">
{post.cover_image && ( {post.cover_image && (
@ -25,7 +26,7 @@ export function BlogEntry({ post }: BlogEntryProps) {
<div className="flex flex-col gap-1 p-4"> <div className="flex flex-col gap-1 p-4">
<BlogAuthors authors={post.authors} /> <BlogAuthors authors={post.authors} />
<Link <Link
href={`/blog/${post.slug}`} href={overrideLink ? overrideLink : `/blog/${post.slug}`}
title={post.title} title={post.title}
className="text-balance text-lg font-semibold text-slate-900 dark:text-white" className="text-balance text-lg font-semibold text-slate-900 dark:text-white"
prefetch={false} prefetch={false}

View File

@ -203,7 +203,7 @@ export const learnItems: MenuItem[] = [
{ {
name: 'Webinars', name: 'Webinars',
description: null, description: null,
href: 'https://go.nx.dev/jan2025-webinar', href: '/webinar',
icon: ComputerDesktopIcon, icon: ComputerDesktopIcon,
isNew: false, isNew: false,
isHighlight: false, isHighlight: false,

View File

@ -58,6 +58,7 @@ import { Quote } from './lib/tags/quote.component';
import { quote } from './lib/tags/quote.schema'; import { quote } from './lib/tags/quote.schema';
import { metrics } from './lib/tags/metrics.schema'; import { metrics } from './lib/tags/metrics.schema';
import { Metrics } from './lib/tags/metrics.component'; import { Metrics } from './lib/tags/metrics.component';
export { CallToAction };
export const getMarkdocCustomConfig = ( export const getMarkdocCustomConfig = (
documentFilePath: string, documentFilePath: string,

View File

@ -0,0 +1,12 @@
{
"presets": [
[
"@nx/react/babel",
{
"runtime": "automatic",
"useBuiltIns": "usage"
}
]
],
"plugins": []
}

View 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": {}
}
]
}

View File

@ -0,0 +1,7 @@
# ui-webinar
This library was generated with [Nx](https://nx.dev).
## Running unit tests
Run `nx test ui-webinar` to execute the unit tests via [Jest](https://jestjs.io).

View File

@ -0,0 +1,9 @@
{
"name": "ui-webinar",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "nx-dev/ui-webinar/src",
"projectType": "library",
"tags": [],
"// targets": "to see all targets run: nx show project ui-webinar --web",
"targets": {}
}

View File

@ -0,0 +1,2 @@
export * from './lib/hero';
export * from './lib/webinar-list';

View File

@ -0,0 +1,21 @@
import { SectionHeading } from '@nx/nx-dev/ui-common';
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">
Nx Webinars
</SectionHeading>
<div className="flex flex-col gap-6">
<SectionHeading as="p" variant="subtitle" className="mt-8">
In-depth explanations and interactive Q&A sessions with Nx team
members
</SectionHeading>
</div>
</div>
</div>
</div>
);
}

View File

@ -0,0 +1,55 @@
import { BlogAuthors } from '@nx/nx-dev/ui-blog';
import Link from 'next/link';
import type { WebinarDataEntry } from '@nx/nx-dev/data-access-documents/node-only';
export interface WebinarListItemProps {
webinar: WebinarDataEntry;
episode: number;
}
export function WebinarListItem({ webinar, episode }: WebinarListItemProps) {
const formattedDate = new Date(webinar.date).toLocaleDateString('en-US', {
month: 'short',
day: '2-digit',
year: 'numeric',
});
const authorsList = (
webinar.authors.length > 1
? webinar.authors.map((a, i) =>
i === webinar.authors.length - 1 ? 'and ' + a.name : a.name
)
: webinar.authors.map((a) => a.name)
).join(', ');
const link =
(webinar.status === 'Past - Ungated'
? webinar.youtubeUrl
: webinar.registrationUrl) || '';
return (
<div
key={webinar.slug}
className="border-b border-slate-200 py-5 text-sm last:border-0 dark:border-slate-800 dark:before:bg-slate-800/50"
>
<Link href={link} prefetch={false}>
<h3 className="text-balance text-lg text-slate-500 sm:w-8/12 dark:text-white">
{webinar.title}
</h3>
</Link>
<span className="my-4 block">
<time dateTime={webinar.date}>{formattedDate}</time>
</span>
<span className="my-4 block">
<span className="inline-block">
<BlogAuthors authors={webinar.authors} showAuthorDetails={false} />
</span>
<span className="mx-2 inline-block">{authorsList}</span>
</span>
<p className="my-2">{webinar.description}</p>
<Link href={link} prefetch={false}>
<span className="my-4 text-balance text-slate-500 sm:w-8/12 dark:text-white">
{webinar.status === 'Past - Gated'
? 'Sign up to view the recording'
: 'Watch the recording'}
</span>
</Link>
</div>
);
}

View File

@ -0,0 +1,69 @@
import { WebinarDataEntry } from '@nx/nx-dev/data-access-documents/node-only';
import { BlogEntry } from '@nx/nx-dev/ui-blog';
import { WebinarListItem } from './webinar-list-item';
import { CallToAction } from '@nx/nx-dev/ui-markdoc';
export interface WebinarListProps {
webinars: WebinarDataEntry[];
}
export function WebinarList({ webinars }: WebinarListProps): JSX.Element {
return webinars.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 webinars as yet but stay tuned!
</h2>
</div>
) : (
<div className="mx-auto max-w-7xl px-8">
{webinars
.filter((w) => w.status === 'Upcoming')
.map((webinar, index) => {
const authorsList = (
webinar.authors.length > 1
? webinar.authors.map((a, i) =>
i === webinar.authors.length - 1 ? 'and ' + a.name : a.name
)
: webinar.authors.map((a) => a.name)
).join(', ');
const dateAndTime =
new Date(webinar.date).toLocaleDateString('en-US', {
month: 'short',
day: '2-digit',
year: 'numeric',
}) + (webinar.time ? ' - ' + webinar.time : '');
return (
<div className="mt-6 w-full max-w-xl">
<BlogEntry
post={webinar}
overrideLink={webinar.registrationUrl}
></BlogEntry>
<p className="my-4 font-bold">{dateAndTime}</p>
<p className="my-4">Presented by {authorsList}</p>
<p className="my-4">{webinar.description}</p>
{webinar.registrationUrl && (
<div className="max-w-md">
<CallToAction
title="Register today!"
description="Save your spot"
url={webinar.registrationUrl}
></CallToAction>
</div>
)}
</div>
);
})}
<div className="mt-20 border-b-2 border-slate-300 pb-3 text-lg dark:border-slate-700">
<h2 className="font-semibold">Past Webinars</h2>
</div>
<div>
{webinars
.filter((w) => w.status !== 'Upcoming')
.map((w, index) => (
<WebinarListItem key={w.slug} webinar={w} episode={index + 1} />
))}
</div>
</div>
);
}

View 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"
}

View 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"]
}

View File

@ -67,6 +67,7 @@
"@nestjs/testing": "^9.0.0", "@nestjs/testing": "^9.0.0",
"@ngrx/router-store": "19.0.0", "@ngrx/router-store": "19.0.0",
"@ngrx/store": "19.0.0", "@ngrx/store": "19.0.0",
"@notionhq/client": "^2.2.15",
"@nuxt/kit": "^3.10.0", "@nuxt/kit": "^3.10.0",
"@nuxt/schema": "^3.10.0", "@nuxt/schema": "^3.10.0",
"@nx/angular": "20.4.0-beta.2", "@nx/angular": "20.4.0-beta.2",

420
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,271 @@
import { Client } from '@notionhq/client';
import {
createWriteStream,
existsSync,
mkdirSync,
writeFileSync,
} from 'node:fs';
import { get } from 'node:https';
import { dirname } from 'node:path';
import { pipeline } from 'node:stream/promises';
import { slugify } from '../../nx-dev/feature-package-schema-viewer/src/lib/slugify.utils';
const notion = new Client({ auth: process.env.NOTION_KEY });
const BLOG_ROOT = './docs/blog';
interface RichTextProperty {
type: 'rich_text';
rich_text: RichTextItem[];
}
interface RichTextItem {
type: 'text';
text: {
content: string;
link?: string;
};
annotations: {
bold: boolean;
italic: boolean;
strikethrough: boolean;
underline: boolean;
code: boolean;
color: 'default' | string;
};
plain_text: string;
href?: string;
}
interface DateProperty {
type: 'date';
date: { start: string; end?: string; time_zone?: string };
}
interface UrlProperty {
type: 'url';
url: string;
}
interface FilesProperty {
type: 'files';
files: {
name: string;
type: 'file';
file: {
url: string;
expiry_time: string;
};
}[];
}
interface SelectProperty {
type: 'select';
select: { id: string; name: string; color: string };
}
interface TitleProperty {
type: 'title';
title: RichTextItem[];
}
interface WebinarResponse {
Description: RichTextProperty;
Date: DateProperty;
['YouTube Link']: UrlProperty;
['Webinar Card Image']: FilesProperty;
Status: SelectProperty;
['Speaker(s)']: RichTextProperty;
Time: RichTextProperty;
['Link to Landing Page']: UrlProperty;
Title: TitleProperty;
}
interface ProcessedWebinar {
Description: string;
Date: string;
['YouTube Link']: string;
['Webinar Card Image']: string[];
Status: string;
['Speaker(s)']: RichTextProperty;
Time: RichTextProperty;
['Link to Landing Page']: UrlProperty;
Title: TitleProperty;
}
const propertyParsers = {
rich_text: (prop: RichTextProperty): string => {
return prop.rich_text
.map((item) => {
let text = item.text.content;
if (item.annotations.bold) {
text = `**${text}**`;
} else if (item.annotations.code) {
text = `\`${text}\``;
} else if (item.annotations.italic) {
text = `*${text}*`;
} else if (item.annotations.strikethrough) {
text = `~${text}~`;
} else if (item.annotations.underline) {
text = `_${text}_`;
}
if (item.href) {
text = `[${text}](${item.href})`;
}
return text;
})
.join('');
},
select: (prop: SelectProperty): string => {
return prop.select.name;
},
title: (prop: TitleProperty): string => {
return propertyParsers.rich_text({
type: 'rich_text',
rich_text: prop.title,
});
},
url: (prop: UrlProperty): string => {
return prop.url;
},
date: (prop: DateProperty): string => {
return prop.date.start;
},
files: (prop: FilesProperty): string => {
return prop.files.map((entry) => {
return entry.file.url;
})[0];
},
};
async function main() {
const response = await notion.databases.query({
database_id: '17c69f3c238780eb8ef6df32ce48f919',
});
response.results.forEach(async (entry: any) => {
const webinar: WebinarResponse = entry.properties;
const processedWebinar = Object.fromEntries(
Object.entries(webinar).map(([key, val]) => {
return [key, propertyParsers[val.type](val)];
})
);
let cover_image = '';
const imageFiles = webinar['Webinar Card Image'].files;
function ensureDirectoryExistence(filePath) {
var directory = dirname(filePath);
if (existsSync(directory)) {
return true;
}
ensureDirectoryExistence(directory);
mkdirSync(directory);
}
async function download(url: string, path: string) {
return new Promise(async (onSuccess) => {
get(url, async (res) => {
ensureDirectoryExistence(path);
const fileWriteStream = createWriteStream(path, {
autoClose: true,
flags: 'w',
});
await pipeline(res, fileWriteStream);
onSuccess('success');
});
});
}
if (imageFiles.length > 0) {
download(
imageFiles[0].file.url,
BLOG_ROOT +
`/images/${webinar.Date.date.start}/${imageFiles[0].name.replaceAll(
' ',
'-'
)}`
);
console.log(
'Downloaded image',
BLOG_ROOT +
`/images/${webinar.Date.date.start}/${imageFiles[0].name.replaceAll(
' ',
'-'
)}`
);
cover_image = `/blog/images/${
webinar.Date.date.start
}/${imageFiles[0].name.replaceAll(' ', '-')}`;
}
const webinarMarkdown = `---
title: "${processedWebinar.Title}"
description: "${processedWebinar.Description}"
slug: '${slugify(processedWebinar.Title)}'
authors: [${processedWebinar['Speaker(s)']
.replace(', and ', ', ')
.split(', ')
.map((author) => `'${author}'`)
.join(', ')}]
tags: [webinar]${
cover_image
? `
cover_image: ${cover_image}`
: ''
}${
processedWebinar['Time']
? `
time: ${processedWebinar['Time']}`
: ''
}${
processedWebinar['Status']
? `
status: ${processedWebinar['Status']}`
: ''
}${
processedWebinar['YouTube Link'] &&
processedWebinar['Status'] === 'Past - Ungated'
? `
youtubeUrl: ${processedWebinar['YouTube Link']}`
: ''
}${
processedWebinar['Link to Landing Page']
? `
registrationUrl: ${processedWebinar['Link to Landing Page']}`
: ''
}
---
${
processedWebinar.Time
? `**${new Date(
processedWebinar.Date + ' ' + new Date().toTimeString()
).toLocaleDateString('en-US', {
month: 'short',
day: '2-digit',
year: 'numeric',
})} - ${processedWebinar.Time}**`
: ''
}
${
processedWebinar['Speaker(s)']
? `Presented by ${processedWebinar['Speaker(s)']}`
: ''
}
${processedWebinar.Description}
${
processedWebinar.Status === 'Upcoming'
? `{% call-to-action title="Register today!" url="${processedWebinar['Link to Landing Page']}" description="Save your spot" /%}`
: ''
}${
processedWebinar.Status === 'Past - Gated' &&
processedWebinar['Link to Landing Page']
? `{% call-to-action title="Download the recording" url="${processedWebinar['Link to Landing Page']}" description="Sign up to gain access" /%}`
: ''
}
`;
writeFileSync(
BLOG_ROOT +
'/' +
processedWebinar.Date +
'-' +
slugify(processedWebinar.Title) +
'.md',
webinarMarkdown
);
});
}
main();

View File

@ -119,6 +119,7 @@
"@nx/nx-dev/ui-sponsor-card": ["nx-dev/ui-sponsor-card/src/index.ts"], "@nx/nx-dev/ui-sponsor-card": ["nx-dev/ui-sponsor-card/src/index.ts"],
"@nx/nx-dev/ui-theme": ["nx-dev/ui-theme/src/index.ts"], "@nx/nx-dev/ui-theme": ["nx-dev/ui-theme/src/index.ts"],
"@nx/nx-dev/ui-video-courses": ["nx-dev/ui-video-courses/src/index.ts"], "@nx/nx-dev/ui-video-courses": ["nx-dev/ui-video-courses/src/index.ts"],
"@nx/nx-dev/ui-webinar": ["nx-dev/ui-webinar/src/index.ts"],
"@nx/nx-dev/util-ai": ["nx-dev/util-ai/src/index.ts"], "@nx/nx-dev/util-ai": ["nx-dev/util-ai/src/index.ts"],
"@nx/playwright": ["packages/playwright/index.ts"], "@nx/playwright": ["packages/playwright/index.ts"],
"@nx/playwright/*": ["packages/playwright/*"], "@nx/playwright/*": ["packages/playwright/*"],