You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@apisix.apache.org by li...@apache.org on 2021/09/05 10:03:17 UTC

[apisix-website] branch master updated: feat: optimize the blog page display effect (#553)

This is an automated email from the ASF dual-hosted git repository.

liuxiran 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 e6cbdbb  feat: optimize the blog page display effect (#553)
e6cbdbb is described below

commit e6cbdbb2638979bcdccb43ec4d74f30af73e7950
Author: Baoyuan <ba...@gmail.com>
AuthorDate: Sun Sep 5 05:03:11 2021 -0500

    feat: optimize the blog page display effect (#553)
---
 website/docusaurus.config.js                     |   3 +-
 website/src/assets/icons/blog-date.svg           |   1 +
 website/src/assets/icons/blog-tags.svg           |   1 +
 website/src/theme/BlogListPage/index.js          |  75 +++++++++++
 website/src/theme/BlogPostItem/index.js          | 153 +++++++++++++++++++++++
 website/src/theme/BlogPostItem/styles.module.css |  49 ++++++++
 website/src/theme/BlogPostPage/index.js          |  57 +++++++++
 website/src/theme/BlogSidebar/index.js           |  67 ++++++++++
 website/src/theme/BlogSidebar/styles.module.css  |  68 ++++++++++
 website/src/theme/BlogTagsPostsPage/index.js     |  69 ++++++++++
 10 files changed, 542 insertions(+), 1 deletion(-)

diff --git a/website/docusaurus.config.js b/website/docusaurus.config.js
index 1050c8c..7ae1910 100644
--- a/website/docusaurus.config.js
+++ b/website/docusaurus.config.js
@@ -215,7 +215,8 @@ module.exports = {
         },
         blog: {
           path: "blog",
-          blogSidebarCount: 0,
+          // see https://github.com/facebook/docusaurus/issues/5353
+          postsPerPage: 10000,
         },
         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..134ccc7
--- /dev/null
+++ b/website/src/theme/BlogListPage/index.js
@@ -0,0 +1,75 @@
+/**
+ * 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';
+
+function BlogListPage(props) {
+  const {metadata, items, sidebar} = 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;
+    })
+    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="container margin-vert--lg">
+        <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/BlogPostItem/index.js b/website/src/theme/BlogPostItem/index.js
new file mode 100644
index 0000000..4ded666
--- /dev/null
+++ b/website/src/theme/BlogPostItem/index.js
@@ -0,0 +1,153 @@
+/**
+ * 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, {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>
+        <div className={styles.postHeader}>
+          <div className="avatar margin-bottom--md">
+            <div className="avatar__intro">
+              {author && (
+                <>
+                  <h4 className="avatar__name">
+                    <Link href={authorURL} className={styles.authorName}>{`Author: ${author}`}</Link>
+                  </h4>
+                </>
+              )}
+            </div>
+          </div>
+          <div className={`margin-bottom--md ${styles.line}`}>
+            <div></div>
+          </div>
+          <div className={`margin-bottom--md ${styles.headerDate}`}>
+            <DateLogo />
+            <time dateTime={date} className={styles.blogPostDate}>
+              {formattedDate}
+              {readingTime && (
+                <>
+                  {' ยท '}
+                  {readingTimePlural(readingTime)}
+                </>
+              )}
+            </time>
+          </div>
+          <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>
+        
+      </header>
+    );
+  };
+
+  return (
+    <>
+      <Seo
+        {...{
+          keywords,
+          image,
+        }}
+      />
+
+      <article className={!isBlogPostPage ? 'margin-bottom--xl' : undefined}>
+        {renderPostHeader()}
+        <div className="markdown">
+          <MDXProvider components={MDXComponents}>{children}</MDXProvider>
+        </div>
+        {(tags.length > 0 || truncated) && (
+          <footer className="row margin-vert--lg">
+            {truncated && (
+              <div className="col text--right">
+                <Link
+                  to={metadata.permalink}
+                  aria-label={`Read more about ${title}`}>
+                  <strong>
+                    <Translate
+                      id="theme.blog.post.readMore"
+                      description="The label used in blog post item excerpts to link to full blog posts">
+                      Read More
+                    </Translate>
+                  </strong>
+                </Link>
+              </div>
+            )}
+          </footer>
+        )}
+      </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..cb58acc
--- /dev/null
+++ b/website/src/theme/BlogPostItem/styles.module.css
@@ -0,0 +1,49 @@
+/**
+ * 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;
+  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;
+}
diff --git a/website/src/theme/BlogPostPage/index.js b/website/src/theme/BlogPostPage/index.js
new file mode 100644
index 0000000..247ba1f
--- /dev/null
+++ b/website/src/theme/BlogPostPage/index.js
@@ -0,0 +1,57 @@
+/**
+ * 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 BlogSidebar from '@theme/BlogSidebar';
+import TOC from '@theme/TOC';
+import EditThisPage from '@theme/EditThisPage';
+import {ThemeClassNames} from '@docusaurus/theme-common';
+
+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">
+            <div className="col col--2"></div>
+            <main className="col col--8">
+              <BlogPostItem
+                frontMatter={frontMatter}
+                metadata={metadata}
+                isBlogPostPage>
+                <BlogPostContents />
+              </BlogPostItem>
+              <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/BlogSidebar/index.js b/website/src/theme/BlogSidebar/index.js
new file mode 100644
index 0000000..e873afe
--- /dev/null
+++ b/website/src/theme/BlogSidebar/index.js
@@ -0,0 +1,67 @@
+/**
+ * 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('-', ' '));
+      } else {
+        setSelected(path[3]);
+      }
+    } else {
+      setSelected('All');
+    }
+  }, [path]);
+
+  if (!count) {
+    return null;
+  }
+
+  const handleTagClick = (tag) => {
+    setSelected(tag);
+    if (tag === "All") {
+      history.push('/blog');
+    } else {
+      if (tag.indexOf(' ') !== -1) {
+        tag = tag.replace(' ', '-');
+      }
+      history.push(`/blog/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..8ff68c4
--- /dev/null
+++ b/website/src/theme/BlogTagsPostsPage/index.js
@@ -0,0 +1,69 @@
+/**
+ * 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 Link from '@docusaurus/Link';
+import BlogSidebar from '@theme/BlogSidebar';
+import Translate, {translate} from '@docusaurus/Translate';
+import {ThemeClassNames, usePluralForm} from '@docusaurus/theme-common'; // Very simple pluralization: probably good enough for now
+
+function BlogTagsPostPage(props) {
+  const {metadata, items, sidebar} = props;
+  const {allTagsPath, name: tagName, count} = metadata;
+  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;
+    })
+    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={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;