This is an automated email from the ASF dual-hosted git repository.
juzhiyuan pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/apisix-website.git
The following commit(s) were added to refs/heads/master by this push:
new 4726812 feat: support tags on Blog list (#641)
4726812 is described below
commit 4726812dfabc2f858778cbfe620939fa44e12534
Author: guoqqqi <[email protected]>
AuthorDate: Wed Oct 6 21:20:13 2021 +0800
feat: support tags on Blog list (#641)
---
website/docusaurus.config.js | 1 +
website/src/assets/icons/blog-date.svg | 1 +
website/src/assets/icons/blog-tags.svg | 1 +
website/src/theme/BlogListPage/index.js | 87 +++++++++++++++
website/src/theme/BlogListPage/styles.module.css | 18 +++
website/src/theme/BlogPostItem/index.js | 134 +++++++++++++++++++++++
website/src/theme/BlogPostItem/styles.module.css | 76 +++++++++++++
website/src/theme/BlogPostPage/index.js | 74 +++++++++++++
website/src/theme/BlogPostPage/styles.module.css | 6 +
website/src/theme/BlogSidebar/index.js | 66 +++++++++++
website/src/theme/BlogSidebar/styles.module.css | 68 ++++++++++++
website/src/theme/BlogTagsPostsPage/index.js | 68 ++++++++++++
12 files changed, 600 insertions(+)
diff --git a/website/docusaurus.config.js b/website/docusaurus.config.js
index 44bafb9..2f5b8bd 100644
--- a/website/docusaurus.config.js
+++ b/website/docusaurus.config.js
@@ -240,6 +240,7 @@ module.exports = {
},
blog: {
path: "blog",
+ postsPerPage: 'ALL',
},
theme: {
customCss: "../src/css/customTheme.css",
diff --git a/website/src/assets/icons/blog-date.svg
b/website/src/assets/icons/blog-date.svg
new file mode 100644
index 0000000..cd21dbd
--- /dev/null
+++ b/website/src/assets/icons/blog-date.svg
@@ -0,0 +1 @@
+<?xml version="1.0" encoding="UTF-8"?><svg width="16" height="16" viewBox="0 0
48 48" fill="none" xmlns="http://www.w3.org/2000/svg"><rect width="48"
height="48" fill="white" fill-opacity="0.01"/><path d="M24 44C35.0457 44 44
35.0457 44 24C44 12.9543 35.0457 4 24 4C12.9543 4 4 12.9543 4 24C4 35.0457
12.9543 44 24 44Z" fill="none" stroke="#333" stroke-width="4"
stroke-linejoin="round"/><path d="M24.0083 12L24.0071 24.0088L32.4865 32.4882"
stroke="#333" stroke-width="4" stroke-linecap="rou [...]
\ No newline at end of file
diff --git a/website/src/assets/icons/blog-tags.svg
b/website/src/assets/icons/blog-tags.svg
new file mode 100644
index 0000000..5072247
--- /dev/null
+++ b/website/src/assets/icons/blog-tags.svg
@@ -0,0 +1 @@
+<?xml version="1.0" encoding="UTF-8"?><svg width="16" height="16" viewBox="0 0
48 48" fill="none" xmlns="http://www.w3.org/2000/svg"><rect width="48"
height="48" fill="white" fill-opacity="0.01"/><path d="M42.1691 29.2451L29.2631
42.1511C28.5879 42.8271 27.6716 43.2069 26.7161 43.2069C25.7606 43.2069 24.8444
42.8271 24.1691 42.1511L8 26V8H26L42.1691 24.1691C43.5649 25.5732 43.5649
27.841 42.1691 29.2451Z" fill="none" stroke="#333" stroke-width="4"
stroke-linejoin="round"/><path fill-rule [...]
\ No newline at end of file
diff --git a/website/src/theme/BlogListPage/index.js
b/website/src/theme/BlogListPage/index.js
new file mode 100644
index 0000000..4674426
--- /dev/null
+++ b/website/src/theme/BlogListPage/index.js
@@ -0,0 +1,87 @@
+/**
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+import React, { useEffect, useState } from 'react';
+import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
+import Layout from '@theme/Layout';
+import BlogPostItem from '@theme/BlogPostItem';
+import BlogListPaginator from '@theme/BlogListPaginator';
+import BlogSidebar from '@theme/BlogSidebar';
+import { ThemeClassNames } from '@docusaurus/theme-common';
+
+import styles from './styles.module.css';
+
+function BlogListPage(props) {
+ const { metadata, items } = props;
+ const {
+ siteConfig: { title: siteTitle },
+ } = useDocusaurusContext();
+ const { blogDescription, blogTitle, permalink } = metadata;
+ const isBlogOnlyMode = permalink === '/';
+ const title = isBlogOnlyMode ? siteTitle : blogTitle;
+ const [tagsCount, setTagsCount] = useState();
+
+ useEffect(() => {
+ let totalTags = [];
+ items.forEach(item => {
+ const tags = item.content.frontMatter.tags;
+ if (tags) {
+ totalTags = totalTags.concat(tags);
+ }
+ });
+ const tagsCount = {
+ All: items.length,
+ };
+ totalTags.forEach(item => {
+ tagsCount[item] = (tagsCount[item] || 0) + 1;
+ });
+ localStorage.setItem('tagsTotal', JSON.stringify(tagsCount));
+ setTagsCount(tagsCount);
+ }, []);
+
+ return (
+ <Layout
+ title={title}
+ description={blogDescription}
+ wrapperClassName={ThemeClassNames.wrapper.blogPages}
+ pageClassName={ThemeClassNames.page.blogListPage}
+ searchMetadatas={{
+ // assign unique search tag to exclude this page from search results!
+ tag: 'blog_posts_list',
+ }}>
+ <div className={styles.backgroundBox}></div>
+ <div className="container margin-vert--lg">
+ <div className={styles.titleBox}>
+ <div className="row">
+ <div className="col col--12">
+ <h1>Blog</h1>
+ <span>We love open source.</span>
+ </div>
+ </div>
+ </div>
+ <div className="row">
+ <div className="col col--3">
+ <BlogSidebar count={tagsCount} />
+ </div>
+ <main className="col col--9">
+ {items.map(({ content: BlogPostContent }) => (
+ <BlogPostItem
+ key={BlogPostContent.metadata.permalink}
+ frontMatter={BlogPostContent.frontMatter}
+ metadata={BlogPostContent.metadata}
+ truncated={BlogPostContent.metadata.truncated}>
+ <BlogPostContent />
+ </BlogPostItem>
+ ))}
+ <BlogListPaginator metadata={metadata} />
+ </main>
+ </div>
+ </div>
+ </Layout>
+ );
+}
+
+export default BlogListPage;
diff --git a/website/src/theme/BlogListPage/styles.module.css
b/website/src/theme/BlogListPage/styles.module.css
new file mode 100644
index 0000000..72b2590
--- /dev/null
+++ b/website/src/theme/BlogListPage/styles.module.css
@@ -0,0 +1,18 @@
+.backgroundBox {
+ position: absolute;
+ width: 100%;
+ min-height: 150px;
+ z-index: -1;
+}
+
+.titleBox {
+ position: relative;
+ margin-bottom: 50px !important;
+}
+
+.titleBox h1 {
+ margin-top: 2rem;
+ font-size: 4rem;
+ font-weight: 800;
+ text-transform: uppercase;
+}
diff --git a/website/src/theme/BlogPostItem/index.js
b/website/src/theme/BlogPostItem/index.js
new file mode 100644
index 0000000..389a276
--- /dev/null
+++ b/website/src/theme/BlogPostItem/index.js
@@ -0,0 +1,134 @@
+/**
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+import React from 'react';
+import clsx from 'clsx';
+import { MDXProvider } from '@mdx-js/react';
+import { translate } from '@docusaurus/Translate';
+import Link from '@docusaurus/Link';
+import MDXComponents from '@theme/MDXComponents';
+import Seo from '@theme/Seo';
+
+import styles from './styles.module.css';
+import { usePluralForm } from '@docusaurus/theme-common'; // Very simple
pluralization: probably good enough for now
+import TagsLogo from "../../assets/icons/blog-tags.svg";
+import DateLogo from "../../assets/icons/blog-date.svg";
+
+function useReadingTimePlural() {
+ const { selectMessage } = usePluralForm();
+ return (readingTimeFloat) => {
+ const readingTime = Math.ceil(readingTimeFloat);
+ return selectMessage(
+ readingTime,
+ translate(
+ {
+ id: 'theme.blog.post.readingTime.plurals',
+ description:
+ 'Pluralized label for "{readingTime} min read". Use as much plural
forms (separated by "|") as your language support (see
https://www.unicode.org/cldr/cldr-aux/charts/34/supplemental/language_plural_rules.html)',
+ message: 'One min read|{readingTime} min read',
+ },
+ {
+ readingTime,
+ },
+ ),
+ );
+ };
+}
+
+function BlogPostItem(props) {
+ const readingTimePlural = useReadingTimePlural();
+ const {
+ children,
+ frontMatter,
+ metadata,
+ truncated,
+ isBlogPostPage = false,
+ } = props;
+ const { date, formattedDate, permalink, tags, readingTime } = metadata;
+ const { author, title, image, keywords } = frontMatter;
+ const authorURL = frontMatter.author_url || frontMatter.authorURL;
+ const authorTitle = frontMatter.author_title || frontMatter.authorTitle;
+ const authorImageURL =
+ frontMatter.author_image_url || frontMatter.authorImageURL;
+
+ const renderPostHeader = () => {
+ const TitleHeading = isBlogPostPage ? 'h1' : 'h2';
+ return (
+ <header>
+ <TitleHeading
+ className={clsx('margin-bottom--sm', styles.blogPostTitle)}>
+ {isBlogPostPage ? title : <Link to={permalink}>{title}</Link>}
+ </TitleHeading>
+ </header>
+ );
+ };
+
+ return (
+ <>
+ <Seo
+ {...{
+ keywords,
+ image,
+ }}
+ />
+
+ <article className={!isBlogPostPage ? styles.container : undefined}>
+ {renderPostHeader()}
+ <div className={styles.markdown}>
+ <MDXProvider components={MDXComponents}>{children}</MDXProvider>
+ </div>
+ <div className={styles.postHeader}>
+ <div className="avatar margin-bottom--md">
+ <div className="avatar__intro">
+ {author && (
+ <>
+ <div className={styles.authorBox}>
+ {authorImageURL &&
+ <Link href={authorURL}>
+ <div className={styles.authorImageBox}>
+ <img src={authorImageURL} />
+ </div>
+ </Link>}
+ <Link href={authorURL}
className={styles.authorName}>{authorImageURL ? author : `Author:
${author}`}</Link>
+ </div>
+ </>
+ )}
+ </div>
+ </div>
+ {author && <div className={`margin-bottom--md ${styles.line}`}>
+ <div></div>
+ </div>}
+ <div className={`margin-bottom--md ${styles.headerDate} ${author &&
styles.marginLeft}`}>
+ <DateLogo />
+ <time dateTime={date} className={styles.blogPostDate}>
+ {formattedDate}
+ </time>
+ </div>
+ {tags.length > 0 && <div className={`margin-bottom--md
${styles.line}`}>
+ <div></div>
+ </div>}
+ <div className={`margin-bottom--md`}>
+ {tags.length > 0 && (
+ <div className={`col ${styles.headerTags}`}>
+ <TagsLogo />
+ {tags.map(({ label, permalink: tagPermalink }) => (
+ <Link
+ key={tagPermalink}
+ className="margin-horiz--sm"
+ to={tagPermalink}>
+ {label}
+ </Link>
+ ))}
+ </div>
+ )}
+ </div>
+ </div>
+ </article>
+ </>
+ );
+}
+
+export default BlogPostItem;
diff --git a/website/src/theme/BlogPostItem/styles.module.css
b/website/src/theme/BlogPostItem/styles.module.css
new file mode 100644
index 0000000..a8135ac
--- /dev/null
+++ b/website/src/theme/BlogPostItem/styles.module.css
@@ -0,0 +1,76 @@
+/**
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+.blogPostTitle {
+ font-size: 2.25rem;
+}
+
+.headerDate {
+ display: flex;
+ align-items: center;
+}
+
+.marginLeft {
+ margin-left: 16px;
+}
+
+.blogPostDate {
+ font-size: 0.9rem;
+ margin-left: 5px;
+}
+
+.postHeader {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ color: #6A737D;
+}
+
+.authorName {
+ font-weight: normal;
+}
+
+.headerTags {
+ display: flex;
+ align-items: center;
+}
+
+.line {
+ margin-left: 16px;
+ display: flex;
+ align-items: center;
+}
+
+.line div {
+ width: 1px;
+ height: 12px;
+ background-color: #6A737D;
+}
+
+.container {
+ margin-bottom: 3rem !important;
+}
+
+.markdown {
+ margin: 16px 0 16px;
+}
+
+.authorBox {
+ display: flex;
+ align-items: center;
+}
+
+.authorImageBox {
+ border-radius: 50%;
+ width: 48px;
+ height: 48px;
+ margin-right: 10px;
+}
+
+.authorImageBox img {
+ border-radius: 50%;
+}
diff --git a/website/src/theme/BlogPostPage/index.js
b/website/src/theme/BlogPostPage/index.js
new file mode 100644
index 0000000..f91b492
--- /dev/null
+++ b/website/src/theme/BlogPostPage/index.js
@@ -0,0 +1,74 @@
+/**
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+import React from 'react';
+import Layout from '@theme/Layout';
+import BlogPostItem from '@theme/BlogPostItem';
+import BlogPostPaginator from '@theme/BlogPostPaginator';
+import TOC from '@theme/TOC';
+import EditThisPage from '@theme/EditThisPage';
+import { ThemeClassNames } from '@docusaurus/theme-common';
+import Link from '@docusaurus/Link';
+
+import styles from './styles.module.css';
+
+function BlogPostPage(props) {
+ const { content: BlogPostContents, sidebar } = props;
+ const { frontMatter, metadata } = BlogPostContents;
+ const { title, description, nextItem, prevItem, editUrl } = metadata;
+ const { hide_table_of_contents: hideTableOfContents } = frontMatter;
+
+ return (
+ <Layout
+ title={title}
+ description={description}
+ wrapperClassName={ThemeClassNames.wrapper.blogPages}
+ pageClassName={ThemeClassNames.page.blogPostPage}>
+ {BlogPostContents && (
+ <div className="container margin-vert--lg">
+ <div className="row">
+ <aside className="col col--3">
+ <nav className={styles.sidebar}>
+ <h3>{sidebar.title}</h3>
+ {sidebar.items.map((item) => {
+ return (
+ <Link key={item.title} href={item.permalink}>
+ <p>{item.title}</p>
+ </Link>
+ )
+ })}
+ </nav>
+ </aside>
+ <main className="col col--7">
+ <BlogPostItem
+ frontMatter={frontMatter}
+ metadata={metadata}
+ isBlogPostPage>
+ <Link onClick={() => history.back()}>
+ Back All
+ </Link>
+ </BlogPostItem>
+ <BlogPostContents />
+ <div>{editUrl && <EditThisPage editUrl={editUrl} />}</div>
+ {(nextItem || prevItem) && (
+ <div className="margin-vert--xl">
+ <BlogPostPaginator nextItem={nextItem} prevItem={prevItem} />
+ </div>
+ )}
+ </main>
+ {!hideTableOfContents && BlogPostContents.toc && (
+ <div className="col col--2">
+ <TOC toc={BlogPostContents.toc} />
+ </div>
+ )}
+ </div>
+ </div>
+ )}
+ </Layout>
+ );
+}
+
+export default BlogPostPage;
diff --git a/website/src/theme/BlogPostPage/styles.module.css
b/website/src/theme/BlogPostPage/styles.module.css
new file mode 100644
index 0000000..61d11e3
--- /dev/null
+++ b/website/src/theme/BlogPostPage/styles.module.css
@@ -0,0 +1,6 @@
+.sidebar {
+ max-height: calc(100vh - var(--ifm-navbar-height) - 2rem);
+ overflow-y: auto;
+ position: sticky;
+ top: calc(var(--ifm-navbar-height) + 2rem);
+}
diff --git a/website/src/theme/BlogSidebar/index.js
b/website/src/theme/BlogSidebar/index.js
new file mode 100644
index 0000000..c3ee76a
--- /dev/null
+++ b/website/src/theme/BlogSidebar/index.js
@@ -0,0 +1,66 @@
+/**
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+import React, { useEffect, useState } from 'react';
+import clsx from 'clsx';
+import Link from '@docusaurus/Link';
+import styles from './styles.module.css';
+import { useHistory } from '@docusaurus/router';
+
+export default function BlogSidebar({count}) {
+ const [ selected, setSelected ] = useState();
+ const history = useHistory();
+ const path = history.location.pathname.split('/');
+
+ useEffect(() => {
+ if (path.length === 2) {
+ setSelected('All');
+ } else if (path.length === 4) {
+ if (path[3].indexOf('-') !== -1) {
+ setSelected(path[3].replace(/-/g, ' '));
+ } else {
+ setSelected(path[3]);
+ }
+ } else {
+ setSelected('All');
+ }
+ }, [path]);
+
+ if (!count) {
+ return null;
+ }
+
+ const handleTagClick = (tag) => {
+ if (tag === "All") {
+ history.push(`/${path[1] || '/'}`);
+ } else {
+ if (tag.indexOf(' ') !== -1) {
+ tag = tag.replace(/ /g, '-');
+ }
+ history.push(`/${path[1]}/tags/${tag}`);
+ }
+ };
+
+ return (
+ <div className={clsx(styles.sidebar, 'thin-scrollbar')}>
+ <h3 className={styles.sidebarItemTitle}>Tags</h3>
+ <div className={styles.sidebarItemList}>
+ {Object.entries(count).map(([tag, num]) => (
+ <div
+ key={tag}
+ className={`${styles.sidebarItem} ${selected === tag ?
styles.selected : ''}`}
+ onClick={() => handleTagClick(tag)}
+ >
+ <div className={styles.sidebarItemLink}>
+ {tag}
+ </div>
+ <p>{num}</p>
+ </div>
+ ))}
+ </div>
+ </div>
+ );
+}
diff --git a/website/src/theme/BlogSidebar/styles.module.css
b/website/src/theme/BlogSidebar/styles.module.css
new file mode 100644
index 0000000..185b090
--- /dev/null
+++ b/website/src/theme/BlogSidebar/styles.module.css
@@ -0,0 +1,68 @@
+/**
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+.sidebar {
+ display: inherit;
+ max-height: calc(100vh - (var(--ifm-navbar-height) + 2rem));
+ overflow-y: auto;
+ position: sticky;
+ top: calc(var(--ifm-navbar-height) + 2rem);
+}
+
+.sidebarItemTitle {
+ margin-bottom: 0.5rem;
+}
+
+.sidebarItemList {
+ display: flex;
+ flex-direction: row;
+ justify-content: flex-start;
+ align-items: flex-start;
+ flex-wrap: wrap;
+}
+
+.sidebarItem {
+ display: flex;
+ flex-direction: row;
+ flex-wrap: nowrap;
+ align-items: center;
+ border: 1px solid var(--ifm-color-primary-dark);
+ border-radius: 4px;
+ height: 2rem;
+ line-height: 2rem;
+ margin-bottom: 0.5rem;
+ margin-right: 0.5rem;
+ padding: 0 1rem;
+}
+
+.selected {
+ background-color: var(--ifm-color-primary);
+ border: 1px solid var(--color-border);
+ color: var(--ifm-color-primary-light);
+}
+
+.sidebarItem:hover {
+ background-color: var(--ifm-color-primary);
+ border: 1px solid var(--color-border);
+ color: var(--ifm-color-primary-light);
+ cursor: pointer;
+}
+
+.sidebarItem p {
+ margin: 0 0 0 0.5rem;
+ line-height: 2rem;
+}
+
+.sidebarItemLinkActive {
+ color: var(--ifm-color-primary);
+}
+
+@media only screen and (max-width: 996px) {
+ .sidebar {
+ display: none;
+ }
+}
diff --git a/website/src/theme/BlogTagsPostsPage/index.js
b/website/src/theme/BlogTagsPostsPage/index.js
new file mode 100644
index 0000000..220bd49
--- /dev/null
+++ b/website/src/theme/BlogTagsPostsPage/index.js
@@ -0,0 +1,68 @@
+/**
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+import React, { useEffect, useState } from 'react';
+import Layout from '@theme/Layout';
+import BlogPostItem from '@theme/BlogPostItem';
+import BlogSidebar from '@theme/BlogSidebar';
+import { ThemeClassNames } from '@docusaurus/theme-common'; // Very simple
pluralization: probably good enough for now
+
+function BlogTagsPostPage(props) {
+ const { metadata, items } = props;
+ const { name: tagName } = metadata;
+ const [tagsCount, setTagsCount] = useState();
+ const tagsTotal = typeof window !== 'undefined' &&
JSON.parse(localStorage.getItem('tagsTotal'));
+
+ useEffect(() => {
+ let totalTags = [];
+ items.forEach(item => {
+ const tags = item.content.frontMatter.tags;
+ if (tags) {
+ totalTags = totalTags.concat(tags);
+ }
+ });
+ const tagsCount = {
+ All: items.length,
+ };
+ totalTags.forEach(item => {
+ tagsCount[item] = (tagsCount[item] || 0) + 1;
+ })
+ setTagsCount(tagsCount);
+ }, []);
+
+ return (
+ <Layout
+ title={`Posts tagged "${tagName}"`}
+ description={`Blog | Tagged "${tagName}"`}
+ wrapperClassName={ThemeClassNames.wrapper.blogPages}
+ pageClassName={ThemeClassNames.page.blogTagsPostPage}
+ searchMetadatas={{
+ // assign unique search tag to exclude this page from search results!
+ tag: 'blog_tags_posts',
+ }}>
+ <div className="container margin-vert--lg">
+ <div className="row">
+ <div className="col col--3">
+ <BlogSidebar count={tagsTotal || tagsCount} />
+ </div>
+ <main className="col col--9">
+ {items.map(({ content: BlogPostContent }) => (
+ <BlogPostItem
+ key={BlogPostContent.metadata.permalink}
+ frontMatter={BlogPostContent.frontMatter}
+ metadata={BlogPostContent.metadata}
+ truncated>
+ <BlogPostContent />
+ </BlogPostItem>
+ ))}
+ </main>
+ </div>
+ </div>
+ </Layout>
+ );
+}
+
+export default BlogTagsPostPage;