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;

Reply via email to