diff --git a/models/FeaturedPostsSection.yaml b/models/FeaturedPostsSection.yaml index b085f142..ac7a03d2 100644 --- a/models/FeaturedPostsSection.yaml +++ b/models/FeaturedPostsSection.yaml @@ -4,52 +4,16 @@ label: Featured posts labelField: title thumbnail: https://assets.stackbit.com/components/models/thumbnails/default.png extends: - - Section + - PostFeedSection groups: - sectionComponent -fieldGroups: - - name: styles - label: Styles - - name: settings - label: Settings fields: - - type: enum - name: variant - group: styles - label: Arrangement - options: - - label: Three columns grid - value: variant-a - - label: Two columns grid - value: variant-b - - label: Mixed grid - value: variant-c - - label: List - value: variant-d - default: variant-a - - name: colors - default: colors-h - - type: string - name: title - label: Title + - name: title default: Featured - - type: string - name: subtitle - label: Subtitle + - name: subtitle default: Featured blog posts section example - - type: list - name: actions - label: Actions - items: - type: model - models: - - Button - - Link - default: - - type: Button - label: View all - url: '/' - style: primary + - name: colors + default: colors-h - type: list name: posts label: Posts @@ -61,69 +25,3 @@ fields: - content/pages/blog/post-three.md - content/pages/blog/post-two.md - content/pages/blog/post-one.md - - type: boolean - name: showDate - label: Show post date - default: false - - type: boolean - name: showAuthor - label: Show post author - description: Show the author of the post - default: false - - type: style - name: styles - styles: - self: - height: ['auto', 'screen'] - width: ['narrow', 'wide', 'full'] - margin: ['tw0:36'] - padding: ['tw4:36'] - justifyContent: ['flex-start', 'flex-end', 'center'] - borderRadius: '*' - borderWidth: ['0:8'] - borderStyle: '*' - borderColor: - - value: 'border-primary' - label: 'Primary color' - color: '$primary' - - value: 'border-secondary' - label: 'Secondary color' - color: '$secondary' - - value: 'border-dark' - label: 'Dark color' - color: '$dark' - - value: 'border-complementary' - label: 'Complementary color' - color: '$complementary' - - value: 'border-complementary-alt' - label: 'Complementary alt color' - color: '$complementaryAlt' - title: - fontWeight: ['400', '700'] - fontStyle: ['normal', 'italic'] - textAlign: ['left', 'center', 'right'] - subtitle: - fontWeight: ['400', '700'] - fontStyle: ['normal', 'italic'] - textAlign: ['left', 'center', 'right'] - actions: - justifyContent: ['flex-start', 'flex-end', 'center'] - default: - self: - height: auto - width: wide - margin: ['mt-0', 'mb-0', 'ml-0', 'mr-0'] - padding: ['pt-12', 'pb-12', 'pl-4', 'pr-4'] - justifyContent: center - borderRadius: none - borderWidth: 0 - borderStyle: none - borderColor: border-dark - title: - textAlign: center - subtitle: - fontWeight: 400 - fontStyle: normal - textAlign: center - actions: - justifyContent: center diff --git a/models/PagedPostsSection.yaml b/models/PagedPostsSection.yaml new file mode 100644 index 00000000..3ebdf1f8 --- /dev/null +++ b/models/PagedPostsSection.yaml @@ -0,0 +1,29 @@ +type: object +name: PagedPostsSection +label: Post feed +labelField: title +extends: + - PostFeedSection +fields: + - name: title + hidden: true + default: null + - name: subtitle + hidden: true + default: null + - name: showDate + default: true + - name: showAuthor + default: true + - name: variant + default: variant-d + options: + - label: Three columns grid + value: variant-a + - label: List + value: variant-d + - name: actions + hidden: true + default: [] + - name: colors + default: colors-a diff --git a/models/Person.yaml b/models/Person.yaml index e5a2e757..03bd5c5e 100644 --- a/models/Person.yaml +++ b/models/Person.yaml @@ -26,8 +26,7 @@ fields: default: url: https://assets.stackbit.com/components/images/default/default-person.png altText: Person photo - - type: string - name: id - label: ID - default: name-surname - required: true + - type: slug + name: slug + label: Slug + description: "Slug used to render posts written by this person. For example, if the slug is 'john-doe', a page will be created under /blog/author/john-doe" diff --git a/models/PostFeedCategoryLayout.yaml b/models/PostFeedCategoryLayout.yaml new file mode 100644 index 00000000..67c872dd --- /dev/null +++ b/models/PostFeedCategoryLayout.yaml @@ -0,0 +1,6 @@ +name: PostFeedCategoryLayout +label: Blog Category +layout: PostFeedCategoryLayout +hideContent: true +extends: + - PostFeedLayout diff --git a/models/PostFeedLayout.yaml b/models/PostFeedLayout.yaml new file mode 100644 index 00000000..31467325 --- /dev/null +++ b/models/PostFeedLayout.yaml @@ -0,0 +1,42 @@ +name: PostFeedLayout +label: Blog +labelField: title +layout: PostFeedLayout +hideContent: true +fields: + - type: string + name: title + label: Title + default: This is a page title + - type: number + name: numOfPostsPerPage + label: Number of Posts per page + description: set to 0 to show all posts on a single page + default: 10 + - type: model + name: postFeed + readOnly: true + label: Post Feed + models: [PagedPostsSection] + default: + title: null + subtitle: null + showDate: true + showAuthor: true + variant: variant-d + colors: colors-a + actions: [] + - type: list + name: topSections + label: Top Sections + items: + type: model + groups: + - sectionComponent + - type: list + name: bottomSections + label: Bottom Sections + items: + type: model + groups: + - sectionComponent diff --git a/models/PostFeedSection.yaml b/models/PostFeedSection.yaml index d87ac693..bbb55c52 100644 --- a/models/PostFeedSection.yaml +++ b/models/PostFeedSection.yaml @@ -4,34 +4,43 @@ label: Post feed labelField: title extends: - Section -groups: - - sectionComponent fieldGroups: - name: styles label: Styles - name: settings label: Settings fields: + - type: string + name: title + label: Title + default: Posts + - type: string + name: subtitle + label: Subtitle + default: Blog posts + - type: boolean + name: showDate + label: Show post date + default: false + - type: boolean + name: showAuthor + label: Show post author + description: Show the author of the post + default: false - type: enum name: variant group: styles label: Arrangement + default: variant-a options: - label: Three columns grid value: variant-a - - label: List + - label: Two columns grid value: variant-b - default: variant-a - - name: colors - default: colors-h - - type: string - name: title - label: Title - default: Latest news - - type: string - name: subtitle - label: Subtitle - default: Latest blog posts section example + - label: Mixed grid + value: variant-c + - label: List + value: variant-d - type: list name: actions label: Actions @@ -45,15 +54,6 @@ fields: label: View all url: '/' style: primary - - type: boolean - name: showRecent - label: Show recent posts only - description: Show the specified number of recent posts - default: false - - type: number - name: recentCount - label: Number of recent posts to show - default: 6 - type: style name: styles styles: diff --git a/models/PostLayout.yaml b/models/PostLayout.yaml index 63012c51..d0d9357e 100644 --- a/models/PostLayout.yaml +++ b/models/PostLayout.yaml @@ -12,6 +12,11 @@ fields: name: date label: Date required: true + - type: reference + name: category + label: Category + models: + - PostFeedCategoryLayout - type: reference name: author label: Author diff --git a/models/RecentPostsSection.yaml b/models/RecentPostsSection.yaml new file mode 100644 index 00000000..bfef4272 --- /dev/null +++ b/models/RecentPostsSection.yaml @@ -0,0 +1,26 @@ +type: object +name: RecentPostsSection +label: Recent posts +labelField: title +extends: + - PostFeedSection +groups: + - sectionComponent +fields: + - name: title + default: Recent Posts + - name: subtitle + default: Latest blog posts section example + - name: variant + default: variant-a + options: + - label: Three columns grid + value: variant-a + - label: List + value: variant-d + - name: colors + default: colors-h + - type: number + name: recentCount + label: Number of recent posts to show + default: 6 diff --git a/src/components-manifest.json b/src/components-manifest.json index 60493634..32c2f41f 100644 --- a/src/components-manifest.json +++ b/src/components-manifest.json @@ -44,11 +44,6 @@ "modelName": "FeaturedPeopleSection", "isDynamic": true }, - "FeaturedPostsSection": { - "path": "components/FeaturedPostsSection", - "modelName": "FeaturedPostsSection", - "isDynamic": true - }, "Footer": { "path": "components/Footer", "modelName": "Footer", @@ -84,6 +79,16 @@ "modelName": "PostFeedSection", "isDynamic": true }, + "FeaturedPostsSection": { + "path": "components/FeaturedPostsSection", + "modelName": "FeaturedPostsSection", + "isDynamic": true + }, + "RecentPostsSection": { + "path": "components/RecentPostsSection", + "modelName": "RecentPostsSection", + "isDynamic": true + }, "QuoteSection": { "path": "components/QuoteSection", "modelName": "QuoteSection", @@ -133,5 +138,15 @@ "path": "layouts/PostLayout", "modelName": "PostLayout", "isDynamic": true + }, + "PostFeedLayout": { + "path": "layouts/PostFeedLayout", + "modelName": "PostFeedLayout", + "isDynamic": true + }, + "PostFeedCategoryLayout": { + "path": "layouts/PostFeedCategoryLayout", + "modelName": "PostFeedCategoryLayout", + "isDynamic": true } } diff --git a/src/components/ContactSection/index.tsx b/src/components/ContactSection/index.tsx index c085d3a1..56ca7719 100644 --- a/src/components/ContactSection/index.tsx +++ b/src/components/ContactSection/index.tsx @@ -3,6 +3,7 @@ import Markdown from 'markdown-to-jsx'; import classNames from 'classnames'; import { getComponent } from '../../components-registry'; import { mapStylesToClassNames as mapStyles } from '../../utils/map-styles-to-class-names'; +import { getDataAttrs } from '../../utils/get-data-attrs'; import FormBlock from '../FormBlock'; export default function ContactSection(props) { @@ -13,6 +14,7 @@ export default function ContactSection(props) { return (
-
-
- {featuredPostsHeader(props)} - {featuredPostsVariants(props)} - {featuredPostsActions(props)} -
-
-
- ); -} - -function featuredPostsHeader(props) { - if (!props.title && !props.subtitle) { - return null; - } - const styles = props.styles || {}; - return ( -
- {props.title && ( -

- {props.title} -

- )} - {props.subtitle && ( -

- {props.subtitle} -

- )} -
- ); -} - -function featuredPostsActions(props) { - const actions = props.actions || []; - if (actions.length === 0) { - return null; - } - const styles = props.styles || {}; - const Action = getComponent('Action'); - return ( -
- {props.actions.map((action, index) => ( - - ))} -
- ); -} - -function featuredPostsVariants(props) { - const variant = props.variant || 'variant-a'; - switch (variant) { - case 'variant-a': - return postsVariantA(props); - case 'variant-b': - return postsVariantB(props); - case 'variant-c': - return postsVariantC(props); - case 'variant-d': - return postsVariantD(props); - } - return null; -} - -function postsVariantA(props) { - const posts = props.posts || []; - if (posts.length === 0) { - return null; - } - const ImageBlock = getComponent('ImageBlock'); - return ( -
- {posts.map((post, index) => ( -
- {post.featuredImage && ( - - - - )} -
- {props.showDate && postDate(post.date)} -

- - {post.title} - -

- {props.showAuthor && post.author && postAuthor(post.author)} - {post.excerpt && ( -

- {post.excerpt} -

- )} -
-
- ))} -
- ); -} - -function postsVariantB(props) { - const posts = props.posts || []; - if (posts.length === 0) { - return null; - } - const ImageBlock = getComponent('ImageBlock'); - return ( -
- {posts.map((post, index) => ( -
- {post.featuredImage && ( - - - - )} -
- {props.showDate && postDate(post.date)} -

- - {post.title} - -

- {props.showAuthor && post.author && postAuthor(post.author)} - {post.excerpt && ( -

- {post.excerpt} -

- )} -
-
- ))} -
- ); -} - -function postsVariantC(props) { - const posts = props.posts || []; - if (posts.length === 0) { - return null; - } - const ImageBlock = getComponent('ImageBlock'); - return ( -
- {posts.map((post, index) => { - const isFullWidth = index % 4 === 0; - return ( -
- {post.featuredImage && ( -
- - - -
- )} -
- {props.showDate && postDate(post.date)} -

- - {post.title} - -

- {props.showAuthor && post.author && postAuthor(post.author)} - {post.excerpt && ( -

- {post.excerpt} -

- )} -
-
- ); - })} -
- ); -} - -function postsVariantD(props) { - const posts = props.posts || []; - if (posts.length === 0) { - return null; - } - const ImageBlock = getComponent('ImageBlock'); - return ( -
- {posts.map((post, index) => ( -
- {post.featuredImage && ( -
- - - -
- )} -
- {props.showDate && postDate(post.date)} -

- - {post.title} - -

- {props.showAuthor && post.author && postAuthor(post.author)} - {post.excerpt && ( -

- {post.excerpt} -

- )} -
-
- ))} -
- ); -} - -function postDate(date) { - const dateTimeAttr = dayjs(date).format('YYYY-MM-DD HH:mm:ss'); - const formattedDate = dayjs(date).format('MMMM D, YYYY'); - return ( -
- -
- ); -} - -function postAuthor(author) { - return ( -
- By{' '} - - {author.firstName && {author.firstName}}{' '} - {author.lastName && {author.lastName}} - -
- ); -} - -function mapMinHeightStyles(height) { - switch (height) { - case 'auto': - return 'min-h-0'; - case 'screen': - return 'min-h-screen'; - } - return null; -} - -function mapMaxWidthStyles(width) { - switch (width) { - case 'narrow': - return 'max-w-screen-md'; - case 'wide': - return 'max-w-screen-xl'; - case 'full': - return 'max-w-full'; - } - return null; -} +import PostFeedSection from '../PostFeedSection'; +export default PostFeedSection; diff --git a/src/components/HeroSection/index.tsx b/src/components/HeroSection/index.tsx index b88e45cd..cc545ee6 100644 --- a/src/components/HeroSection/index.tsx +++ b/src/components/HeroSection/index.tsx @@ -3,6 +3,7 @@ import Markdown from 'markdown-to-jsx'; import classNames from 'classnames'; import { getComponent } from '../../components-registry'; import { mapStylesToClassNames as mapStyles } from '../../utils/map-styles-to-class-names'; +import { getDataAttrs } from '../../utils/get-data-attrs'; export default function HeroSection(props) { const cssId = props.elementId || null; @@ -12,6 +13,7 @@ export default function HeroSection(props) { return (
-
-
+
+
{postFeedHeader(props)} {postFeedVariants(props)} {postFeedActions(props)} + {props.pageLinks}
@@ -95,6 +101,10 @@ function postFeedVariants(props) { return postsVariantA(props); case 'variant-b': return postsVariantB(props); + case 'variant-c': + return postsVariantC(props); + case 'variant-d': + return postsVariantD(props); } return null; } @@ -106,33 +116,30 @@ function postsVariantA(props) { } const ImageBlock = getComponent('ImageBlock'); return ( -
- {posts.map((post, index) => { - const dateTimeAttr = dayjs(post.date).format('YYYY-MM-DD HH:mm:ss'); - const formattedDate = dayjs(post.date).format('MMMM D, YYYY'); - return ( -
- {post.featuredImage && ( - - +
+ {posts.map((post, index) => ( +
+ {post.featuredImage && ( + + + + )} +
+ {props.showDate && } +

+ + {post.title} +

+ + {post.excerpt && ( +

+ {post.excerpt} +

)} -
-

- - {post.title} - -

-
- -
- {post.excerpt &&

{post.excerpt}

} -
-
- ); - })} +
+
+ ))}
); } @@ -144,35 +151,96 @@ function postsVariantB(props) { } const ImageBlock = getComponent('ImageBlock'); return ( -
+
+ {posts.map((post, index) => ( +
+ {post.featuredImage && ( + + + + )} +
+ {props.showDate && } +

+ + {post.title} + +

+ + {post.excerpt && ( +

+ {post.excerpt} +

+ )} +
+
+ ))} +
+ ); +} + +function postsVariantC(props) { + const posts = props.posts || []; + if (posts.length === 0) { + return null; + } + const ImageBlock = getComponent('ImageBlock'); + return ( +
{posts.map((post, index) => { - const dateTimeAttr = dayjs(post.date).format('YYYY-MM-DD HH:mm:ss'); - const formattedDate = dayjs(post.date).format('MMMM D, YYYY'); + const isFullWidth = index % 4 === 0; return ( -
+
{post.featuredImage && ( -
+
)} -
-

+
+ {props.showDate && } +

{post.title}

-
- -
- {post.excerpt &&

{post.excerpt}

} + + {post.excerpt && ( +

+ {post.excerpt} +

+ )}

); @@ -181,6 +249,121 @@ function postsVariantB(props) { ); } +function postsVariantD(props) { + const posts = props.posts || []; + if (posts.length === 0) { + return null; + } + const ImageBlock = getComponent('ImageBlock'); + return ( +
+ {posts.map((post, index) => ( +
+ {post.featuredImage && ( +
+ + + +
+ )} +
+ {props.showDate && } +

+ + {post.title} + +

+ + {post.excerpt && ( +

+ {post.excerpt} +

+ )} +
+
+ ))} +
+ ); +} + +function PostDate({ post }) { + if (!post.date) { + return null; + } + const date = post.date; + const dateTimeAttr = dayjs(date).format('YYYY-MM-DD HH:mm:ss'); + const formattedDate = dayjs(date).format('MMMM D, YYYY'); + return ( +
+ +
+ ); +} + +function PostAttribution({ showAuthor, post }) { + const author = showAuthor ? postAuthor(post) : null; + const category = postCategory(post); + if (!author && !category) { + return null; + } + return ( +
+ {author && ( + <> + {'By '} + {author} + + )} + {category && ( + <> + {author ? ' in ' : 'In '} + {category} + + )} +
+ ); +} + +function postAuthor(post) { + if (!post.author) { + return null; + } + const author = post.author; + const children = ( + <> + {author.firstName && {author.firstName}}{' '} + {author.lastName && {author.lastName}} + + ); + if (author.slug) { + return ( + + {children} + + ); + } else { + return {children}; + } +} + +function postCategory(post) { + if (!post.category) { + return null; + } + const category = post.category; + return ( + + {category.title} + + ); +} + function mapMinHeightStyles(height) { switch (height) { case 'auto': diff --git a/src/components/QuoteSection/index.tsx b/src/components/QuoteSection/index.tsx index a0aa7e17..7f3bf843 100644 --- a/src/components/QuoteSection/index.tsx +++ b/src/components/QuoteSection/index.tsx @@ -2,6 +2,7 @@ import * as React from 'react'; import Markdown from 'markdown-to-jsx'; import classNames from 'classnames'; import { mapStylesToClassNames as mapStyles } from '../../utils/map-styles-to-class-names'; +import { getDataAttrs } from '../../utils/get-data-attrs'; export default function QuoteSection(props) { const cssId = props.elementId || null; @@ -11,6 +12,7 @@ export default function QuoteSection(props) { return (
- -
- ); + return ; })}
)} diff --git a/src/layouts/PostFeedCategoryLayout/index.tsx b/src/layouts/PostFeedCategoryLayout/index.tsx new file mode 100644 index 00000000..4e6b6605 --- /dev/null +++ b/src/layouts/PostFeedCategoryLayout/index.tsx @@ -0,0 +1,2 @@ +import PostFeedLayout from '../PostFeedLayout'; +export default PostFeedLayout; diff --git a/src/layouts/PostFeedLayout/index.tsx b/src/layouts/PostFeedLayout/index.tsx new file mode 100644 index 00000000..99700ee9 --- /dev/null +++ b/src/layouts/PostFeedLayout/index.tsx @@ -0,0 +1,127 @@ +import * as React from 'react'; +import classNames from 'classnames'; +import Link from '../../utils/link'; +import { getComponent } from '../../components-registry'; +import { getBaseLayoutComponent } from '../../utils/base-layout'; + +export default function PostFeedLayout(props) { + const { page, site } = props; + const BaseLayout = getBaseLayoutComponent(page.baseLayout, site.baseLayout); + const { title, topSections = [], bottomSections = [], pageIndex, baseUrlPath, numOfPages, items, postFeed } = page; + const PostFeedSection = getComponent('PostFeedSection'); + const pageLinks = PageLinks({ pageIndex, baseUrlPath, numOfPages }); + + return ( + +
+ {title && ( +

+ {title} +

+ )} + {renderSections(topSections, 'topSections')} + + {renderSections(bottomSections, 'bottomSections')} +
+
+ ); +} + +function renderSections(sections: any[], fieldName: string) { + if (sections.length === 0) { + return null; + } + return ( +
+ {sections.map((section, index) => { + const Component = getComponent(section.type); + if (!Component) { + throw new Error(`no component matching the page section's type: ${section.type}`); + } + return ; + })} +
+ ); +} + +function PageLinks({ pageIndex, baseUrlPath, numOfPages }) { + if (numOfPages < 2) { + return null; + } + const pageLinks = []; + const padRange = 2; + const startIndex = pageIndex - padRange > 2 ? pageIndex - padRange : 0; + const endIndex = pageIndex + padRange < numOfPages - 3 ? pageIndex + padRange : numOfPages - 1; + + // following logic renders pagination controls: + // for example, if the current page is 6 (pageIndex === 5) + // ↓ + // ← 1 ... 4 5 6 7 8 ... 20 → + // ↑ ↑ + // and padRange === 2, then it renders from 4 (6 - 2) to 8 (6 + 2) + + // renders prev "←" button, if the current page is the first page, the button is disabled + if (pageIndex > 0) { + pageLinks.push(); + } else { + pageLinks.push(); + } + + // if startIndex is not 0, then render the first page followed by ellipsis, if needed. + if (startIndex > 0) { + pageLinks.push(); + if (startIndex > 1) { + pageLinks.push(); + } + } + + // render all pages between startIndex and endIndex, the current page should be disabled + for (let i = startIndex; i <= endIndex; i++) { + if (pageIndex === i) { + pageLinks.push(); + } else { + pageLinks.push(); + } + } + + // if endIndex is not the last page, then render the last page preceded by ellipsis, if needed. + if (endIndex < numOfPages - 1) { + if (endIndex < numOfPages - 2) { + pageLinks.push(); + } + pageLinks.push(); + } + + // renders next "→" button, if the current page is the last page, the button is disabled + if (pageIndex < numOfPages - 1) { + pageLinks.push(); + } else { + pageLinks.push(); + } + + return
{pageLinks}
; +} + +function PageLink({ pageIndex, buttonLabel, baseUrlPath }) { + return ( + + {buttonLabel} + + ); +} + +function PageLinkDisabled({ buttonLabel }) { + return ( + + {buttonLabel} + + ); +} + +function Ellipsis() { + return ; +} + +function urlPathForPageAtIndex(pageIndex, baseUrlPath) { + return pageIndex === 0 ? baseUrlPath : `${baseUrlPath}/page/${pageIndex + 1}`; +} diff --git a/src/layouts/PostLayout/index.tsx b/src/layouts/PostLayout/index.tsx index 116d1ac3..05d1ff86 100644 --- a/src/layouts/PostLayout/index.tsx +++ b/src/layouts/PostLayout/index.tsx @@ -3,6 +3,8 @@ import dayjs from 'dayjs'; import Markdown from 'markdown-to-jsx'; import { getBaseLayoutComponent } from '../../utils/base-layout'; import { getComponent } from '../../components-registry'; +import Link from '../../utils/link'; +import getPageUrlPath from '../../utils/get-page-url-path'; export default function PostLayout(props) { const { page, site } = props; @@ -23,7 +25,7 @@ export default function PostLayout(props) {
{page.title &&

{page.title}

} - {page.author && postAuthor(page.author)} + {page.markdown_content && ( @@ -39,11 +41,7 @@ export default function PostLayout(props) { if (!Component) { throw new Error(`no component matching the page section's type: ${section.type}`); } - return ( -
- -
- ); + return ; })}
)} @@ -52,14 +50,50 @@ export default function PostLayout(props) { ); } -function postAuthor(author) { +function PostAttribution({ post }) { + if (!post.author && !post.category) { + return null; + } + const author = post.author ? postAuthor(post.author) : null; + const category = post.category ? postCategory(post.category) : null; return ( -
- By{' '} - - {author.firstName && {author.firstName}}{' '} - {author.lastName && {author.lastName}} - +
+ {author && ( + <> + {'By '} + {author} + + )} + {category && ( + <> + {author ? ' in ' : 'In '} + {category} + + )}
); } + +function postAuthor(author) { + const children = ( + <> + {author.firstName && {author.firstName}}{' '} + {author.lastName && {author.lastName}} + + ); + return author.slug ? ( + + {children} + + ) : ( + {children} + ); +} + +function postCategory(category) { + return ( + + {category.title} + + ); +} diff --git a/src/layouts/index.ts b/src/layouts/index.ts index 595f1bd3..86ea0d08 100644 --- a/src/layouts/index.ts +++ b/src/layouts/index.ts @@ -1,4 +1,6 @@ import PageLayout from './PageLayout'; +import PostFeedLayout from './PostFeedLayout'; +import PostFeedCategoryLayout from './PostFeedCategoryLayout'; import PostLayout from './PostLayout'; -export { PageLayout, PostLayout }; +export { PageLayout, PostFeedLayout, PostFeedCategoryLayout, PostLayout }; \ No newline at end of file diff --git a/src/utils/get-data-attrs.ts b/src/utils/get-data-attrs.ts new file mode 100644 index 00000000..6f039074 --- /dev/null +++ b/src/utils/get-data-attrs.ts @@ -0,0 +1,8 @@ +export function getDataAttrs(props: any = {}): any { + return Object.entries(props).reduce((dataAttrs, [key, value]) => { + if (key.startsWith('data-')) { + dataAttrs[key] = value; + } + return dataAttrs; + }, {}); +} diff --git a/stackbit.yaml b/stackbit.yaml index abce2ced..88d65f17 100644 --- a/stackbit.yaml +++ b/stackbit.yaml @@ -12,6 +12,10 @@ contentModels: isPage: true Person: isPage: false + PostFeedLayout: + isPage: true + PostFeedCategoryLayout: + isPage: true modelsSource: type: files