Enable json blog feed

This commit is contained in:
tekstrand 2024-08-24 08:02:59 -05:00
parent 9391a5397a
commit becc8f1722
14 changed files with 2900 additions and 2029 deletions

View file

@ -134,6 +134,34 @@ const config = {
blogTitle: "Meshtastic Blog",
blogDescription:
"Discover in-depth insights from developers and maintainers, including project updates and changes. Hear from the community about their projects and ideas.",
feedOptions: {
type: "json",
title: "My Feed",
description: "",
copyright: "",
language: undefined,
createFeedItems: async (params) => {
const { blogPosts, defaultCreateFeedItems, ...rest } = params;
const feedItems = await defaultCreateFeedItems({
blogPosts: blogPosts.map((post) => {
return {
metadataTitle: post.metadata.title,
title: post.metadata.title,
id: post.id,
metadata: post.metadata,
permalink: post.metadata.permalink,
description: post.metadata.description,
date: post.metadata.date,
content: post.content,
authors: post.metadata.authors,
tags: post.metadata.tags,
};
}),
...rest,
});
return feedItems;
},
},
},
theme: {
customCss: require.resolve("./src/css/custom.css"),

View file

@ -14,11 +14,11 @@
},
"dependencies": {
"@algolia/client-search": "^4.22.1",
"@docusaurus/core": "3.1.1",
"@docusaurus/plugin-content-docs": "3.1.1",
"@docusaurus/preset-classic": "3.1.1",
"@docusaurus/theme-common": "3.1.1",
"@docusaurus/theme-mermaid": "3.1.1",
"@docusaurus/core": "3.5.2",
"@docusaurus/plugin-content-docs": "3.5.2",
"@docusaurus/preset-classic": "3.5.2",
"@docusaurus/theme-common": "3.5.2",
"@docusaurus/theme-mermaid": "3.5.2",
"@giscus/react": "^3.0.0",
"@heroicons/react": "^2.1.1",
"@mdx-js/react": "^3.0.1",
@ -40,7 +40,7 @@
"devDependencies": {
"@biomejs/biome": "^1.5.3",
"@buf/meshtastic_protobufs.bufbuild_es": "1.7.2-20240216123215-6b07c41c68c9.1",
"@docusaurus/module-type-aliases": "3.1.1",
"@docusaurus/module-type-aliases": "3.5.2",
"@tailwindcss/typography": "^0.5.10",
"@tsconfig/docusaurus": "^2.0.2",
"@types/node": "^20.11.19",

File diff suppressed because it is too large Load diff

View file

@ -1,37 +0,0 @@
import { PageMetadata } from "@docusaurus/theme-common";
import { useBlogPost } from "@docusaurus/theme-common/internal";
import React from "react";
export default function BlogPostPageMetadata() {
const { assets, metadata } = useBlogPost();
const { title, description, date, tags, authors, frontMatter } = metadata;
const { keywords } = frontMatter;
const image = assets.image ?? frontMatter.image;
return (
<PageMetadata
title={title}
description={description}
keywords={keywords}
image={image}
>
<meta property="og:type" content="article" />
<meta property="article:published_time" content={date} />
{/* TODO double check those article meta array syntaxes, see https://ogp.me/#array */}
{authors.some((author) => author.url) && (
<meta
property="article:author"
content={authors
.map((author) => author.url)
.filter(Boolean)
.join(",")}
/>
)}
{tags.length > 0 && (
<meta
property="article:tag"
content={tags.map((tag) => tag.label).join(",")}
/>
)}
</PageMetadata>
);
}

View file

@ -1,67 +0,0 @@
import {
HtmlClassNameProvider,
ThemeClassNames,
} from "@docusaurus/theme-common";
import {
BlogPostProvider,
useBlogPost,
} from "@docusaurus/theme-common/internal";
import GiscusComponent from "@site/src/components/GiscusComponent";
import BlogLayout from "@theme/BlogLayout";
import BlogPostItem from "@theme/BlogPostItem";
import BlogPostPageMetadata from "@theme/BlogPostPage/Metadata";
import BlogPostPaginator from "@theme/BlogPostPaginator";
import TOC from "@theme/TOC";
import Unlisted from "@theme/Unlisted";
import clsx from "clsx";
import React from "react";
function BlogPostPageContent({ sidebar, children }) {
const { metadata, toc } = useBlogPost();
const { nextItem, prevItem, frontMatter, unlisted } = metadata;
const {
hide_table_of_contents: hideTableOfContents,
toc_min_heading_level: tocMinHeadingLevel,
toc_max_heading_level: tocMaxHeadingLevel,
} = frontMatter;
return (
<BlogLayout
sidebar={sidebar}
toc={
!hideTableOfContents && toc.length > 0 ? (
<TOC
toc={toc}
minHeadingLevel={tocMinHeadingLevel}
maxHeadingLevel={tocMaxHeadingLevel}
/>
) : undefined
}
>
{unlisted && <Unlisted />}
<BlogPostItem>{children}</BlogPostItem>
<GiscusComponent />
{(nextItem || prevItem) && (
<BlogPostPaginator nextItem={nextItem} prevItem={prevItem} />
)}
</BlogLayout>
);
}
export default function BlogPostPage(props) {
const BlogPostContent = props.content;
return (
<BlogPostProvider content={props.content} isBlogPostPage>
<HtmlClassNameProvider
className={clsx(
ThemeClassNames.wrapper.blogPages,
ThemeClassNames.page.blogPostPage,
)}
>
<BlogPostPageMetadata />
<BlogPostPageContent sidebar={props.sidebar}>
<BlogPostContent />
</BlogPostPageContent>
</HtmlClassNameProvider>
</BlogPostProvider>
);
}

View file

@ -0,0 +1,11 @@
import React from 'react';
export default function FooterCopyright({copyright}) {
return (
<div
className="footer__copyright"
// Developer provided the HTML, so assume it's safe.
// eslint-disable-next-line react/no-danger
dangerouslySetInnerHTML={{__html: copyright}}
/>
);
}

View file

@ -0,0 +1,20 @@
import React from 'react';
import clsx from 'clsx';
export default function FooterLayout({style, links, logo, copyright}) {
return (
<footer
className={clsx('footer', {
'footer--dark': style === 'dark',
})}>
<div className="container container-fluid">
{links}
{(logo || copyright) && (
<div className="footer__bottom text--center">
{logo && <div className="margin-bottom--sm">{logo}</div>}
{copyright}
</div>
)}
</div>
</footer>
);
}

View file

@ -0,0 +1,25 @@
import React from 'react';
import Link from '@docusaurus/Link';
import useBaseUrl from '@docusaurus/useBaseUrl';
import isInternalUrl from '@docusaurus/isInternalUrl';
import IconExternalLink from '@theme/Icon/ExternalLink';
export default function FooterLinkItem({item}) {
const {to, href, label, prependBaseUrlToHref, ...props} = item;
const toUrl = useBaseUrl(to);
const normalizedHref = useBaseUrl(href, {forcePrependBaseUrl: true});
return (
<Link
className="footer__link-item"
{...(href
? {
href: prependBaseUrlToHref ? normalizedHref : href,
}
: {
to: toUrl,
})}
{...props}>
{label}
{href && !isInternalUrl(href) && <IconExternalLink />}
</Link>
);
}

View file

@ -0,0 +1,37 @@
import React from 'react';
import LinkItem from '@theme/Footer/LinkItem';
function ColumnLinkItem({item}) {
return item.html ? (
<li
className="footer__item"
// Developer provided the HTML, so assume it's safe.
// eslint-disable-next-line react/no-danger
dangerouslySetInnerHTML={{__html: item.html}}
/>
) : (
<li key={item.href ?? item.to} className="footer__item">
<LinkItem item={item} />
</li>
);
}
function Column({column}) {
return (
<div className="col footer__col">
<div className="footer__title">{column.title}</div>
<ul className="footer__items clean-list">
{column.items.map((item, i) => (
<ColumnLinkItem key={i} item={item} />
))}
</ul>
</div>
);
}
export default function FooterLinksMultiColumn({columns}) {
return (
<div className="row footer__links">
{columns.map((column, i) => (
<Column key={i} column={column} />
))}
</div>
);
}

View file

@ -0,0 +1,31 @@
import React from 'react';
import LinkItem from '@theme/Footer/LinkItem';
function Separator() {
return <span className="footer__link-separator">·</span>;
}
function SimpleLinkItem({item}) {
return item.html ? (
<span
className="footer__link-item"
// Developer provided the HTML, so assume it's safe.
// eslint-disable-next-line react/no-danger
dangerouslySetInnerHTML={{__html: item.html}}
/>
) : (
<LinkItem item={item} />
);
}
export default function FooterLinksSimple({links}) {
return (
<div className="footer__links text--center">
<div className="footer__links">
{links.map((item, i) => (
<React.Fragment key={i}>
<SimpleLinkItem item={item} />
{links.length !== i + 1 && <Separator />}
</React.Fragment>
))}
</div>
</div>
);
}

View file

@ -0,0 +1,11 @@
import React from 'react';
import {isMultiColumnFooterLinks} from '@docusaurus/theme-common';
import FooterLinksMultiColumn from '@theme/Footer/Links/MultiColumn';
import FooterLinksSimple from '@theme/Footer/Links/Simple';
export default function FooterLinks({links}) {
return isMultiColumnFooterLinks(links) ? (
<FooterLinksMultiColumn columns={links} />
) : (
<FooterLinksSimple links={links} />
);
}

View file

@ -0,0 +1,35 @@
import React from 'react';
import clsx from 'clsx';
import Link from '@docusaurus/Link';
import {useBaseUrlUtils} from '@docusaurus/useBaseUrl';
import ThemedImage from '@theme/ThemedImage';
import styles from './styles.module.css';
function LogoImage({logo}) {
const {withBaseUrl} = useBaseUrlUtils();
const sources = {
light: withBaseUrl(logo.src),
dark: withBaseUrl(logo.srcDark ?? logo.src),
};
return (
<ThemedImage
className={clsx('footer__logo', logo.className)}
alt={logo.alt}
sources={sources}
width={logo.width}
height={logo.height}
style={logo.style}
/>
);
}
export default function FooterLogo({logo}) {
return logo.href ? (
<Link
href={logo.href}
className={styles.footerLogoLink}
target={logo.target}>
<LogoImage logo={logo} />
</Link>
) : (
<LogoImage logo={logo} />
);
}

View file

@ -0,0 +1,9 @@
.footerLogoLink {
opacity: 0.5;
transition: opacity var(--ifm-transition-fast)
var(--ifm-transition-timing-default);
}
.footerLogoLink:hover {
opacity: 1;
}

40
src/theme/Footer/index.js Normal file
View file

@ -0,0 +1,40 @@
import React from "react";
import { useThemeConfig } from "@docusaurus/theme-common";
import FooterLinks from "@theme/Footer/Links";
import FooterLogo from "@theme/Footer/Logo";
import FooterCopyright from "@theme/Footer/Copyright";
import FooterLayout from "@theme/Footer/Layout";
import { useLocation } from "@docusaurus/router";
import GiscusComponent from "../../components/GiscusComponent";
function Footer() {
const { footer } = useThemeConfig();
if (!footer) {
return null;
}
const { copyright, links, logo, style } = footer;
const location = useLocation();
const isBlogSubPage =
location.pathname.startsWith("/blog/") && location.pathname !== "/blog/";
return (
<>
{isBlogSubPage && (
<div class="container">
<div class="row">
<div class="col col--7 col--offset-3">
<GiscusComponent />
</div>
</div>
</div>
)}
<FooterLayout
style={style}
links={links && links.length > 0 && <FooterLinks links={links} />}
logo={logo && <FooterLogo logo={logo} />}
copyright={copyright && <FooterCopyright copyright={copyright} />}
/>
</>
);
}
export default React.memo(Footer);