Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: automated DocsButton #1188

Merged
merged 21 commits into from
Jan 20, 2023
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions components/MDX.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import Figure from '../components/Figure'
import Profile from '../components/Profile'
import GeneratorInstallation from '../components/GeneratorInstallation'
import NewsletterSubscribe from '../components/NewsletterSubscribe'
import DocsButton from '../components/buttons/DocsButton';
import { DocsCards } from '../components/docs/DocsCards'

let mermaidInitialized = false;
Expand Down Expand Up @@ -68,7 +67,6 @@ function getMDXComponents() {
CodeBlock,
ChapterSuggestions,
YouTube,
DocsButton,
Remember,
Warning,
Sponsors,
Expand Down
37 changes: 12 additions & 25 deletions components/buttons/DocsButton.js
Original file line number Diff line number Diff line change
@@ -1,24 +1,12 @@
import Link from 'next/link';

export default function DocsButton({ suggestions = [], className }) {
if (suggestions.length === 0) {
return null;
}

const backPages = suggestions.filter(
(suggestion) => suggestion.type === 'back'
);
const nextPages = suggestions.filter(
(suggestion) => suggestion.type === 'next'
);

export default function DocsButton({ post, className='' }) {
return (
<div className={`flex flex-row ${className}`}>
<div className="pr-2 flex flex-col grow w-full">
{backPages.map((suggestion, key) => (
<Link href={suggestion.href} key={key} passHref>
<div className={`flex flex-row gap-4 mb-4 h-full ${className}`}>
<div className="w-1/2 h-auto">
{ post?.prevPage && <Link href={post.prevPage.href} passHref>
<a>
<div className="p-4 rounded shadow-md border border-gray-200 transition-all duration-300 ease-in-out hover:shadow-lg hover:border-gray-300 mb-4 text-center lg:text-left cursor-pointer">
<div className="p-4 rounded shadow-md border border-gray-200 transition-all duration-300 ease-in-out hover:shadow-lg hover:border-gray-300 text-center lg:text-left cursor-pointer">
<div className="text-secondary-500">
<svg
xmlns="http://www.w3.org/2000/svg"
Expand All @@ -38,17 +26,16 @@ export default function DocsButton({ suggestions = [], className }) {
Go Back
</div>
</div>
<div className="font-medium text-base my-2">{suggestion.title}</div>
<div className="font-medium text-base my-2">{post.prevPage.title}</div>
</div>
</a>
</Link>
))}
}
</div>
<div className="pl-2 flex flex-col grow w-full">
{nextPages.map((suggestion, key) => (
<Link href={suggestion.href} key={key} passHref>
<div className="w-1/2 h-auto">
{ post?.nextPage && <Link href={post.nextPage.href} className='h-auto' passHref>
<a>
<div className="p-4 rounded shadow-md border border-gray-200 transition-all duration-300 ease-in-out hover:shadow-lg hover:border-gray-300 mb-4 text-center lg:text-right cursor-pointer">
<div className="p-4 rounded shadow-md border border-gray-200 transition-all duration-300 ease-in-out hover:shadow-lg hover:border-gray-300 text-center lg:text-right cursor-pointer">
<div className="text-secondary-500">
<div className="font-bold my-auto text-sm inline uppercase">
Up Next
Expand All @@ -68,11 +55,11 @@ export default function DocsButton({ suggestions = [], className }) {
/>
</svg>
</div>
<div className="font-medium text-base my-2">{suggestion.title}</div>
<div className="font-medium text-base my-2">{post.nextPage.title}</div>
</div>
</a>
</Link>
))}
}
</div>
</div>
);
Expand Down
6 changes: 5 additions & 1 deletion components/layout/DocsLayout.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import DocsContext from '../../context/DocsContext'
import TOC from '../TOC'
import DocsNav from '../navigation/DocsNav'
import DocsMobileMenu from '../navigation/DocsMobileMenu'
import DocsButton from '../buttons/DocsButton'
import NavBar from '../navigation/NavBar'
import ArrowRight from '../icons/ArrowRight'
import Feedback from '../Feedback'
Expand All @@ -23,7 +24,7 @@ function generateEditLink(post) {
return <a target="_blank" rel="noopener noreferrer" href={`https://github.com/asyncapi/website/blob/master/pages${post.isIndex ? post.slug + '/index' : post.slug}.md`} className="ml-1 underline">Edit this page on GitHub</a>
}

function buildNavTree(navItems) {
export function buildNavTree(navItems) {
const tree = {
'welcome': {
item: { title: 'Welcome', weight: 0, isRootSection: true, isSection: true, rootSectionId: 'welcome', sectionWeight: 0, slug: '/docs' },
Expand Down Expand Up @@ -185,6 +186,9 @@ export default function DocsLayout({ post, navItems = {}, children }) {
/>
{ children }
</article>
<div>
<DocsButton post={post} />
</div>
<div className="">
<Feedback />
</div>
Expand Down
97 changes: 90 additions & 7 deletions components/layout/Layout.js
Original file line number Diff line number Diff line change
@@ -1,40 +1,123 @@
import { useRouter } from 'next/router'
import DocsLayout from './DocsLayout'
import DocsLayout, {buildNavTree} from './DocsLayout'
import BlogLayout from './BlogLayout'
import JobsLayout from './JobsLayout'
import GenericPostLayout from './GenericPostLayout'
import BlogContext from '../../context/BlogContext'
import JobsContext from '../../context/JobsContext'
import { getPostBySlug, getAllPosts } from '../../lib/api'
import { getPostBySlug, getAllPosts, getDocBySlug } from '../../lib/api'

export default function Layout({ children }) {
const { pathname } = useRouter()
const posts = getAllPosts()
const allDocPosts = posts.filter(p => p.slug.startsWith('/docs/'))
const treePosts = buildNavTree(allDocPosts) // builds a NavTree of all DocPosts to structurize the order of pages
let structuredPosts = [] // stores the list of DocPosts in the order it has to be shown

// A recursion function, works on the logic of Depth First Search to traverse all the root and child posts of the
// DocTree to get sequential order of the Doc Posts
const convertDocPosts = (docObject) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for future us 😄 , please add a comment what the purpose of this function is, why the conversion is needed and what is converted. I don't think you are converting anything, you are rather generating a flat list of all docs 🤔

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure 👍

let docsArray = []
// certain entries in the DocPosts are either a parent to many posts or itself a post.
docsArray.push(docObject?.item || docObject)
if(docObject.children){
let children = docObject.children
Object.keys(children).forEach((child) => {
let docChildArray = convertDocPosts(children[child])
docsArray = [...docsArray, ...docChildArray]
})
}
return docsArray
}

// Traversing the whole DocTree and storing each post inside them in sequential order
Object.keys(treePosts).forEach((rootElement) => {
structuredPosts.push(treePosts[rootElement].item)
if(treePosts[rootElement].children){
let children = treePosts[rootElement].children
Object.keys(children).forEach((child) => {
let docChildArray = convertDocPosts(children[child])
structuredPosts = [...structuredPosts, ...docChildArray]
})
}
})
// Appending the content of welcome page pf Docs from the posts.json
structuredPosts[0] = posts.filter(p => p.slug === '/docs')[0]

// Traversing the strucutredPosts in order to add `nextPage` and `prevPage` details for each page
let countDocPages = structuredPosts.length
structuredPosts = structuredPosts.map((post, index) => {
// post item specifying the root Section or sub-section in the docs are excluded as
// they doesn't comprise any Doc Page or content to be shown in website.
if(post?.isRootElement || post?.isSection || index==0)
return post

let nextPage = {}, prevPage = {}
let docPost = post;

// checks whether the next page for the current docPost item exists or not
if(index+1<countDocPages){
// checks whether the next item inside structuredPosts is a rootElement or a sectionElement
// if yes, it goes again to a next to next item in structuredPosts to link the nextPage
if(!structuredPosts[index+1].isRootElement && !structuredPosts[index+1].isSection){
nextPage = {
title: structuredPosts[index+1].title,
href: structuredPosts[index+1].slug
}
}else{
nextPage = {
title: structuredPosts[index+2].title,
href: structuredPosts[index+2].slug
}
}
docPost = {...docPost, nextPage}
}

// checks whether the previous page for the current docPost item exists or not
if(index>0){
// checks whether the previous item inside structuredPosts is a rootElement or a sectionElement
// if yes, it goes again to a next previous item in structuredPosts to link the prevPage
if(!structuredPosts[index-1]?.isRootElement && !structuredPosts[index-1]?.isSection){
prevPage = {
title: structuredPosts[index-1].title,
href: structuredPosts[index-1].slug
}
docPost = {...docPost, prevPage}
}else{
// additonal check for the first page of Docs so that it doesn't give any Segementation fault
if(index-2>=0){
prevPage = {
title: structuredPosts[index-2].title,
href: structuredPosts[index-2].slug
}
docPost = {...docPost, prevPage}
}
}
}
return docPost
})

if (pathname.startsWith('/docs')) {
const posts = getAllPosts()
const post = getPostBySlug(pathname)
const post = getDocBySlug(structuredPosts, pathname)
return (
<DocsLayout post={post} navItems={posts.filter(p => p.slug.startsWith('/docs/'))}>
{children}
</DocsLayout>
)
} else if (pathname.startsWith('/blog/')) {
const posts = getAllPosts()
const post = getPostBySlug(pathname)
return (
<BlogLayout post={post} navItems={posts.filter(p => p.slug.startsWith('/blog/'))}>
{children}
</BlogLayout>
)
} else if (pathname === '/blog') {
const posts = getAllPosts()
return (
<BlogContext.Provider value={{ navItems: posts.filter(p => p.slug.startsWith('/blog/')) }}>
{children}
</BlogContext.Provider>
)
} else if (pathname === '/jobs') {
const posts = getAllPosts()
return (
<JobsContext.Provider value={{ navItems: posts.filter(p => p.slug.startsWith('/jobs/')) }}>
{children}
Expand Down
4 changes: 4 additions & 0 deletions lib/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,7 @@ export function getAllPosts() {
export function getPostBySlug(slug) {
return posts.find(post => post.slug === slug && !post.isSection)
}

export function getDocBySlug(structuredPosts, slug) {
return structuredPosts.find(post => post.slug === slug && !post.isSection)
}
15 changes: 0 additions & 15 deletions pages/docs/concepts/application.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,18 +25,3 @@ The above diagram describes a message communication traveling through a channel
<Remember>
When writing your AsyncAPI document, make sure to describe what a user can do with your application; not what the application does. In other words, if your <em>application</em> is a <b>producer</b>, your AsyncAPI document should describe where users can subscribe to, to receive messages produced by your <b>producer</b> application.
</Remember>

<DocsButton
suggestions={[
{
href: '/docs/concepts/channel',
title: 'Channel',
type:'back',
},
{
href: '/docs/concepts/protocol',
title: 'Protocol',
type:'next',
}
]}
/>
16 changes: 0 additions & 16 deletions pages/docs/concepts/channel.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,19 +20,3 @@ graph LR
C --> F[Consumer]
```
The diagram above shows the communication between a `producer` and `consumer`, with the producer sending a `message` through the `channel`. The channel then queues the message to the specific consumer.

---
<DocsButton
suggestions={[
{
href:'/docs/concepts/consumer',
type: 'back',
title: 'Consumer',
},
{
href:'/docs/concepts/application',
type: 'next',
title: 'Application',
}
]}
/>
16 changes: 0 additions & 16 deletions pages/docs/concepts/consumer.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,19 +30,3 @@ The above diagram depicts a sample flow of events from `producer` to `broker` to
<Remember>
<b>Subscribers</b> can also be <a href="https://www.asyncapi.com/docs/concepts/producer">producers</a>.
</Remember>

---
<DocsButton
suggestions={[
{
href:'/docs/concepts/producer',
type: 'back',
title: 'Producer',
},
{
href:'/docs/concepts/channel',
type: 'next',
title: 'Channel',
}
]}
/>
17 changes: 0 additions & 17 deletions pages/docs/concepts/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,20 +23,3 @@ Do you have a documentation contributor question and you're wondering how to tag

Tag me in your AsyncAPI Doc PRs or [GitHub Discussions](https://github.com/asyncapi/community/discussions/categories/docs) via my GitHub handle, [`alequetzalli`](https://github.com/alequetzalli) 🐙.
</Remember>

---

<DocsButton
suggestions={[
{
href:'/docs',
type: 'back',
title: 'Welcome',
},
{
href:'/docs/concepts/server',
type: 'next',
title: 'Server',
}
]}
/>
16 changes: 0 additions & 16 deletions pages/docs/concepts/message.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,19 +33,3 @@ graph TD
```

Overall, `events` are `messages` but not all `messages` are `events`.

---
<DocsButton
suggestions={[
{
href:'/docs/concepts/protocol',
type: 'back',
title: 'Protocol',
},
{
href:'/docs/tutorials',
type: 'next',
title: 'Tutorials - Overview',
}
]}
/>
17 changes: 0 additions & 17 deletions pages/docs/concepts/producer.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,20 +32,3 @@ flowchart LR
```

In the diagram above, we see a producer publishing messages to a specific channel and a consumer subscribing to messages from that channel. We also have a second producer who publishes to one channel, but subscribes to messages from another.

---

<DocsButton
suggestions={[
{
href:'/docs/concepts/server',
type: 'back',
title: 'Server',
},
{
href:'/docs/concepts/consumer',
type: 'next',
title: 'Consumer',
}
]}
/>
15 changes: 0 additions & 15 deletions pages/docs/concepts/protocol.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,18 +28,3 @@ The diagram above depicts the message exchange flow from `producer` to `broker`
The quality of service information rule is specified on a protocol level. Broker implementations and other involved actors must act accordingly.

In AsyncAPI documents, all protocol-specific details that the application follows can be described using [bindings](/docs/reference/specification/v2.5.0#definitionsBindings).

<DocsButton
suggestions={[
{
href:'/docs/concepts/application',
type: 'back',
title: 'Application',
},
{
href:'/docs/concepts/message',
type: 'next',
title: 'Message',
}
]}
/>
Loading