diff --git a/.eslintrc.base.json b/.eslintrc.base.json new file mode 100644 index 00000000000000..9ca2e830cbddfb --- /dev/null +++ b/.eslintrc.base.json @@ -0,0 +1,35 @@ +{ + "root": true, + "ignorePatterns": ["**/*"], + "plugins": ["@nx"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": { + "@nx/enforce-module-boundaries": [ + "error", + { + "enforceBuildableLibDependency": true, + "allow": [], + "depConstraints": [ + { + "sourceTag": "*", + "onlyDependOnLibsWithTags": ["*"] + } + ] + } + ] + } + }, + { + "files": ["*.ts", "*.tsx"], + "extends": ["plugin:@nx/typescript"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "extends": ["plugin:@nx/javascript"], + "rules": {} + } + ] +} diff --git a/docs/blog/design-article-3.md b/docs/blog/2019-01-01-placeholder.md similarity index 81% rename from docs/blog/design-article-3.md rename to docs/blog/2019-01-01-placeholder.md index cc6c9608f792eb..cebd0ca5c1ce79 100644 --- a/docs/blog/design-article-3.md +++ b/docs/blog/2019-01-01-placeholder.md @@ -1,16 +1,16 @@ --- -title: 'Design Article 3' +title: 'Pinned post from 2019' description: '' authors: - 'Juri Strumpflohner' -published: '2019-01-01' -updated: '2019-01-01' + - 'Zack DeRose' cover_image: '/documentation/blog/images/thumbnail-nx15_7.png' tags: - design - Nx reposts: - https://dev.to/nx/nx-conf-2023-recap-53ep +pinned: true --- Bla bla some intro text. diff --git a/docs/blog/design-article-4.md b/docs/blog/2019-02-01-placeholder.md similarity index 64% rename from docs/blog/design-article-4.md rename to docs/blog/2019-02-01-placeholder.md index d27b6990099439..11471c93df0053 100644 --- a/docs/blog/design-article-4.md +++ b/docs/blog/2019-02-01-placeholder.md @@ -1,11 +1,10 @@ --- -title: 'Design Article 4' +title: 'Placeholder' description: '' authors: - 'Juri Strumpflohner' -published: '2019-01-01' -updated: '2019-01-01' -cover_image: '/documentation/blog/images/thumbnail-own-trpc.png' + - 'Zack DeRose' +cover_image: '/documentation/blog/images/thumbnail-nx15_7.png' tags: - design - Nx diff --git a/docs/blog/2020-02-01-placeholder.md b/docs/blog/2020-02-01-placeholder.md new file mode 100644 index 00000000000000..c111f5e8a84305 --- /dev/null +++ b/docs/blog/2020-02-01-placeholder.md @@ -0,0 +1,24 @@ +--- +title: 'Placeholder' +description: '' +authors: + - 'Juri Strumpflohner' +cover_image: '/documentation/blog/images/thumbnail-nx15_7.png' +tags: + - design + - Nx +reposts: + - https://dev.to/nx/nx-conf-2023-recap-53ep +--- + +Bla bla some intro text. + +## Code Example + +```ts +const foo = 'bar'; + +function someFn() { + return foo; +} +``` diff --git a/docs/blog/2022-02-01-placeholder.md b/docs/blog/2022-02-01-placeholder.md new file mode 100644 index 00000000000000..c111f5e8a84305 --- /dev/null +++ b/docs/blog/2022-02-01-placeholder.md @@ -0,0 +1,24 @@ +--- +title: 'Placeholder' +description: '' +authors: + - 'Juri Strumpflohner' +cover_image: '/documentation/blog/images/thumbnail-nx15_7.png' +tags: + - design + - Nx +reposts: + - https://dev.to/nx/nx-conf-2023-recap-53ep +--- + +Bla bla some intro text. + +## Code Example + +```ts +const foo = 'bar'; + +function someFn() { + return foo; +} +``` diff --git a/docs/blog/design-article-2.md b/docs/blog/2023-02-01-placeholder.md similarity index 78% rename from docs/blog/design-article-2.md rename to docs/blog/2023-02-01-placeholder.md index 7f0cae39f86886..28faccad176a84 100644 --- a/docs/blog/design-article-2.md +++ b/docs/blog/2023-02-01-placeholder.md @@ -1,17 +1,15 @@ --- -title: 'Design Article 2x' +title: 'Nx Console Meets Nx Cloud' description: '' authors: - 'Juri Strumpflohner' -published: '2019-01-01' -updated: '2019-01-01' cover_image: '/documentation/blog/images/thumbnail-nx-console-nx-cloud.png' -slug: custom-slug tags: - design - Nx reposts: - https://dev.to/nx/nx-conf-2023-recap-53ep +pinned: true --- Bla bla some intro text. diff --git a/docs/blog/2023-03-01-placeholder.md b/docs/blog/2023-03-01-placeholder.md new file mode 100644 index 00000000000000..c111f5e8a84305 --- /dev/null +++ b/docs/blog/2023-03-01-placeholder.md @@ -0,0 +1,24 @@ +--- +title: 'Placeholder' +description: '' +authors: + - 'Juri Strumpflohner' +cover_image: '/documentation/blog/images/thumbnail-nx15_7.png' +tags: + - design + - Nx +reposts: + - https://dev.to/nx/nx-conf-2023-recap-53ep +--- + +Bla bla some intro text. + +## Code Example + +```ts +const foo = 'bar'; + +function someFn() { + return foo; +} +``` diff --git a/docs/blog/2023-04-01-placeholder.md b/docs/blog/2023-04-01-placeholder.md new file mode 100644 index 00000000000000..0644e91141b5fa --- /dev/null +++ b/docs/blog/2023-04-01-placeholder.md @@ -0,0 +1,24 @@ +--- +title: 'Post with a very long title that spans more than one line' +description: '' +authors: + - 'Juri Strumpflohner' +cover_image: '/documentation/blog/images/thumbnail-nx15_7.png' +tags: + - design + - Nx +reposts: + - https://dev.to/nx/nx-conf-2023-recap-53ep +--- + +Bla bla some intro text. + +## Code Example + +```ts +const foo = 'bar'; + +function someFn() { + return foo; +} +``` diff --git a/docs/blog/design-article-5.md b/docs/blog/2023-09-01-placeholder.md similarity index 81% rename from docs/blog/design-article-5.md rename to docs/blog/2023-09-01-placeholder.md index 4b5321077b1c0b..4a08e981ca2b2a 100644 --- a/docs/blog/design-article-5.md +++ b/docs/blog/2023-09-01-placeholder.md @@ -1,16 +1,15 @@ --- -title: 'Design Article 5' +title: 'Build Your Own CLI' description: '' authors: - 'Juri Strumpflohner' -published: '2019-01-01' -updated: '2019-01-01' cover_image: '/documentation/blog/images/thumbnail-build-own-cli.png' tags: - design - Nx reposts: - https://dev.to/nx/nx-conf-2023-recap-53ep +pinned: true --- Bla bla some intro text. diff --git a/docs/blog/nx-17-release.md b/docs/blog/2023-10-20-nx-17-release.md similarity index 99% rename from docs/blog/nx-17-release.md rename to docs/blog/2023-10-20-nx-17-release.md index 01b54ba482ae98..62b1c92ba8ef41 100644 --- a/docs/blog/nx-17-release.md +++ b/docs/blog/2023-10-20-nx-17-release.md @@ -4,13 +4,14 @@ description: '' authors: - 'Juri Strumpflohner' - 'Zack DeRose' -published: '2023-10-20' +date: '2023-10-20' cover_image: '/documentation/blog/images/thumbnail-nx-conf-recap.png' tags: - design - Nx reposts: - https://dev.to/nx/nx-conf-2023-recap-53ep +pinned: true --- We're excited to announce the release of Nx version 17! In this article, we'll cover the main things you need to know to get the most of Nx 17! diff --git a/nx-dev/data-access-documents/src/lib/blog.api.ts b/nx-dev/data-access-documents/src/lib/blog.api.ts index fe773de0e600f5..0ddcc2f7956d96 100644 --- a/nx-dev/data-access-documents/src/lib/blog.api.ts +++ b/nx-dev/data-access-documents/src/lib/blog.api.ts @@ -1,29 +1,8 @@ import { readFileSync, readdirSync } from 'fs'; import { join, basename } from 'path'; import { extractFrontmatter } from '@nx/nx-dev/ui-markdoc'; - -export type BlogPostFrontmatter = { - title: string; - description: string; - authors: string[]; - published: string; - updated?: string; - cover_image: string; - tags: string[]; - reposts: string[]; -}; - -export type BlogPostDataEntry = { - content: string; - frontmatter: BlogPostFrontmatter; - filePath: string; - slug: string; -}; - -const calculateSlug = (filePath: string, frontmatter: any) => { - const baseName = basename(filePath, '.md'); - return frontmatter.slug || baseName; -}; +import { sortPosts } from './blog.util'; +import { BlogPostDataEntry } from './blog.model'; export class BlogApi { constructor( @@ -41,38 +20,59 @@ export class BlogApi { } getBlogPosts(): BlogPostDataEntry[] { - const files = readdirSync(this.options.blogRoot); + const files: string[] = readdirSync(this.options.blogRoot); + 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; - return files - .map((file) => { - const filePath = join(this.options.blogRoot, file); + const content = readFileSync(filePath, 'utf8'); + const frontmatter = extractFrontmatter(content); + const slug = this.calculateSlug(filePath, frontmatter); + const post = { + content, + frontmatter: { + title: frontmatter.title, + description: frontmatter.description, + authors: frontmatter.authors, + date: this.calculateDate(file, frontmatter), + cover_image: frontmatter.cover_image ?? null, + tags: frontmatter.tags, + reposts: frontmatter.reposts, + pinned: frontmatter.pinned ?? false, + }, + filePath, + slug, + }; - // filter out directories (e.g. images etc) - if (!filePath.endsWith('.md')) { - return null; - } + if (!frontmatter.draft || process.env.NODE_ENV === 'development') { + allPosts.push(post); + } + } - const content = readFileSync(filePath, 'utf8'); + return sortPosts(allPosts); + } - const frontmatter = extractFrontmatter(content); - const slug = calculateSlug(filePath, frontmatter); + private calculateSlug(filePath: string, frontmatter: any): string { + const baseName = basename(filePath, '.md'); + return frontmatter.slug || baseName; + } - return { - content, - frontmatter: { - title: frontmatter.title, - description: frontmatter.description, - authors: frontmatter.authors, - published: frontmatter.published, - updated: frontmatter.updated ?? null, - cover_image: frontmatter.cover_image ?? null, - tags: frontmatter.tags, - reposts: frontmatter.reposts, - }, - filePath, - slug, - }; - }) - .filter((x) => !!x); + private calculateDate(filename: string, frontmatter: any): string { + const date: Date = new Date(); + const timeString = date.toTimeString(); + if (frontmatter.date) { + return new Date(frontmatter.date + ' ' + timeString).toISOString(); + } else { + const regexp = /^(\d\d\d\d-\d\d-\d\d).+$/; + const match = filename.match(regexp); + if (match) { + return new Date(match[1] + ' ' + timeString).toISOString(); + } else { + throw new Error(`Could not parse date from filename: ${filename}`); + } + } } } diff --git a/nx-dev/data-access-documents/src/lib/blog.model.ts b/nx-dev/data-access-documents/src/lib/blog.model.ts new file mode 100644 index 00000000000000..f54255703d7ba9 --- /dev/null +++ b/nx-dev/data-access-documents/src/lib/blog.model.ts @@ -0,0 +1,18 @@ +export type BlogPostFrontmatter = { + title: string; + description: string; + authors: string[]; + date: string; + updated?: string; + cover_image: string; + tags: string[]; + reposts: string[]; + pinned?: boolean; +}; + +export type BlogPostDataEntry = { + content: string; + frontmatter: BlogPostFrontmatter; + filePath: string; + slug: string; +}; diff --git a/nx-dev/data-access-documents/src/lib/blog.util.spec.ts b/nx-dev/data-access-documents/src/lib/blog.util.spec.ts new file mode 100644 index 00000000000000..1b6e0830ea6760 --- /dev/null +++ b/nx-dev/data-access-documents/src/lib/blog.util.spec.ts @@ -0,0 +1,72 @@ +import { sortPosts } from './blog.util'; + +describe('sortPosts', () => { + test('sort from latest to earliest', () => { + const posts = [ + createPost({ date: '2022-01-01', title: 'Post 1', slug: 'post-1' }), + createPost({ date: '2023-01-01', title: 'Post 2', slug: 'post-2' }), + ]; + + const results = sortPosts(posts); + + expect(results.map((p) => p.slug)).toEqual(['post-2', 'post-1']); + }); + + test('latest pinned posts are presented first', () => { + const posts = [ + createPost({ + date: '2023-01-01', + title: 'Post 1', + slug: 'post-1', + pinned: true, + }), + createPost({ date: '2023-02-01', title: 'Post 2', slug: 'post-2' }), + createPost({ + date: '2023-03-01', + title: 'Post 3', + slug: 'post-3', + pinned: true, + }), + createPost({ + date: '2023-04-01', + title: 'Post 4', + slug: 'post-4', + pinned: true, + }), + createPost({ date: '2023-05-01', title: 'Post 5', slug: 'post-5' }), + ]; + + const results = sortPosts(posts); + + expect(results.map((p) => p.slug)).toEqual([ + 'post-4', + 'post-3', + 'post-1', + 'post-5', + 'post-2', + ]); + }); +}); + +function createPost(data: { + date: string; + title: string; + slug: string; + pinned?: boolean; +}) { + return { + content: '', + filePath: `${data.slug}.md`, + slug: data.slug, + frontmatter: { + date: data.date, + title: data.title, + description: '', + authors: [], + cover_image: '', + tags: [], + reposts: [], + pinned: data.pinned ?? undefined, + }, + }; +} diff --git a/nx-dev/data-access-documents/src/lib/blog.util.ts b/nx-dev/data-access-documents/src/lib/blog.util.ts new file mode 100644 index 00000000000000..5aba3d87c6840c --- /dev/null +++ b/nx-dev/data-access-documents/src/lib/blog.util.ts @@ -0,0 +1,12 @@ +import { BlogPostDataEntry } from './blog.model'; + +export function sortPosts(posts: BlogPostDataEntry[]): BlogPostDataEntry[] { + return posts.sort((a, b) => { + if (a.frontmatter.pinned && !b.frontmatter.pinned) return -1; + if (b.frontmatter.pinned && !a.frontmatter.pinned) return 1; + return ( + new Date(b.frontmatter.date).getTime() - + new Date(a.frontmatter.date).getTime() + ); + }); +} diff --git a/nx-dev/data-access-documents/src/node.index.ts b/nx-dev/data-access-documents/src/node.index.ts index c9fb30d3d54e6c..fd8c5675de6ae7 100644 --- a/nx-dev/data-access-documents/src/node.index.ts +++ b/nx-dev/data-access-documents/src/node.index.ts @@ -1,4 +1,5 @@ export * from './lib/documents.api'; export * from './lib/changelog.api'; +export * from './lib/blog.model'; export * from './lib/blog.api'; export * from './lib/tags.api'; diff --git a/nx-dev/nx-dev/copy-docs.js b/nx-dev/nx-dev/copy-docs.js index 04940d80007e4f..08eb85a8ba9ed8 100644 --- a/nx-dev/nx-dev/copy-docs.js +++ b/nx-dev/nx-dev/copy-docs.js @@ -1,9 +1,13 @@ -const { copySync } = require('fs-extra'); +const { copySync, rmSync } = require('fs-extra'); const path = require('path'); /** * Copies the documentation into the proper Next.js public folder */ +rmSync(path.resolve(path.join(__dirname, 'public/documentation')), { + recursive: true, + force: true, +}); copySync( path.resolve(path.join(__dirname, '../../docs')), path.resolve(path.join(__dirname, 'public/documentation')), diff --git a/nx-dev/nx-dev/lib/blog/authors.tsx b/nx-dev/nx-dev/lib/blog/authors.tsx deleted file mode 100644 index 42f153b1793ec7..00000000000000 --- a/nx-dev/nx-dev/lib/blog/authors.tsx +++ /dev/null @@ -1,37 +0,0 @@ -export interface BlogAuthorsProps { - authors: string[]; -} - -export function BlogAuthors({ authors }: BlogAuthorsProps) { - return ( -
-
- {authors.map((author, index) => ( -
0 ? '-0.8rem' : undefined, - }} - > -
- {author} -
-
- ))} -
-
- {authors.slice(0, 2).join(', ')} - {authors.length > 2 && `, +${authors.length - 2} more`} -
-
- ); -} diff --git a/nx-dev/nx-dev/lib/blog/blogentry.tsx b/nx-dev/nx-dev/lib/blog/blogentry.tsx deleted file mode 100644 index 95be8ad5083bd8..00000000000000 --- a/nx-dev/nx-dev/lib/blog/blogentry.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import { BlogPostDataEntry } from '@nx/nx-dev/data-access-documents/node-only'; -import { BlogAuthors } from './authors'; - -export interface BlogEntryProps { - blogpost: BlogPostDataEntry; - showImage?: boolean; -} - -export function BlogEntry({ blogpost, showImage }: BlogEntryProps) { - return ( -
- - {showImage && blogpost.frontmatter.cover_image && ( - - )} -
-
- {blogpost.frontmatter.title} -
-
- -
-
-
-
- ); -} diff --git a/nx-dev/nx-dev/pages/blog/[slug].tsx b/nx-dev/nx-dev/pages/blog/[slug].tsx index 1fa2f37c44bbb3..b9fdfb366da02b 100644 --- a/nx-dev/nx-dev/pages/blog/[slug].tsx +++ b/nx-dev/nx-dev/pages/blog/[slug].tsx @@ -1,11 +1,13 @@ +import Link from 'next/link'; +import Image from 'next/image'; +import { ChevronLeftIcon } from '@heroicons/react/24/solid'; import { GetStaticProps, GetStaticPaths } from 'next'; import { blogApi } from '../../lib/blog.api'; import { BlogPostDataEntry } from '@nx/nx-dev/data-access-documents/node-only'; import { renderMarkdown } from '@nx/nx-dev/ui-markdoc'; import { NextSeo } from 'next-seo'; import { Footer, Header } from '@nx/nx-dev/ui-common'; -import { cx } from '@nx/nx-dev/ui-primitives'; -import { BlogAuthors } from '../../lib/blog/authors'; +import { BlogAuthors } from '@nx/nx-dev/ui-blog'; interface BlogPostDetailProps { post: BlogPostDataEntry; @@ -16,6 +18,15 @@ export default function BlogPostDetail({ post }: BlogPostDetailProps) { filePath: post.filePath ?? '', }); + const formattedDate = new Date(post.frontmatter.date).toLocaleDateString( + 'en-US', + { + month: 'long', + day: 'numeric', + year: 'numeric', + } + ); + return ( <>
-
-
-
- {/* */} +
+
+ + + Blog + +
+ + {formattedDate} +
+
+
+ {post.frontmatter.cover_image && ( +
+
-
-

+ )} +
+
+

{post.frontmatter.title}

-
- {post.frontmatter.cover_image && ( -
- -
- )}
- {/*MAIN CONTENT*/}
{node}
- {/* {!hideTableOfContent && ( - - )}*/}
- {/*
-
-
- - {document.filePath ? ( - - ) : null} -
-
*/}
-
+