diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..723da48a --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Fictionlab sp. z o.o. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/community-projects/_template.mdx b/community-projects/_template.mdx new file mode 100644 index 00000000..df9f5d14 --- /dev/null +++ b/community-projects/_template.mdx @@ -0,0 +1,49 @@ +--- +title: My Community Project (max 60 characters) +description: A brief description of my community project, up to 160 characters. +authors: + - name: Author Name + title: Author Title + url: https://author-personal-website.com + image_url: /img/community-projects/author-image.jpg + socials: + x: author_x_handle + github: author_github_handle + linkedin: author_linkedin_handle + email: author_email_address +tags: [tag1, tag2, tag3] +image: /img/community-projects/project-image.jpg +company: + name: Company Name + url: https://company-website.com + logo_url: /img/community-projects/company-logo.jpg +--- + +{/* To see how to use this template, refer to the https://docs.fictionlab.pl/guidelines/add-community-project */} + +# [Community project title] + +[Short description of the community project, displayed on the Community Projects +page, up to 400 characters.] + +{/* truncate */} + +[Main content of the community project page, including detailed information +about the project, its features, implementation details, and any other relevant +information. You can use markdown syntax to format the content, add images, +links, and other media as needed.] + +{/* Example content */} + +## Project Overview + +Short introduction to the project, its goals, and the problem it aims to solve. + +## Key Features + +List of the main features of the project, with brief descriptions for each. + +## Resources + +Links to any relevant resources, such as the project repository, documentation, +or related articles. diff --git a/community-projects/placeholder.mdx b/community-projects/placeholder.mdx new file mode 100644 index 00000000..f03bfec2 --- /dev/null +++ b/community-projects/placeholder.mdx @@ -0,0 +1,50 @@ +--- +title: Delete after adding first community project +description: + Temporary placeholder for the first community project. Please refer to the + https://docs.fictionlab.pl/guidelines/add-community-project for instructions + on how to add a new community project. +authors: + - name: Author Name + title: Author Title + url: https://author-website.com + image_url: /img/community-projects/author-image.jpg + socials: + x: author_x_handle + github: author_github_handle + linkedin: author_linkedin_handle + email: author_email_address +image: /img/community-projects/project-image.jpg +tags: [leo-rover] +company: + name: Company Name + url: https://company-website.com + logo_url: /img/community-projects/company-logo.jpg +--- + +{/* To see how to use this template, refer to the https://docs.fictionlab.pl/guidelines/add-community-project */} + +[Short description of the community project, displayed on the Community Projects +page, up to 400 characters.] + +{/* truncate */} + +[Main content of the community project page, including detailed information +about the project, its features, implementation details, and any other relevant +information. You can use markdown syntax to format the content, add images, +links, and other media as needed.] + +{/* Example content */} + +## Project Overview + +Short introduction to the project, its goals, and the problem it aims to solve. + +## Key Features + +List of the main features of the project, with brief descriptions for each. + +## Resources + +Links to any relevant resources, such as the project repository, documentation, +or related articles. diff --git a/community-projects/tags.yml b/community-projects/tags.yml new file mode 100644 index 00000000..e919be69 --- /dev/null +++ b/community-projects/tags.yml @@ -0,0 +1,15 @@ +# Robotic platform related tags + +leo-rover: + label: Leo Rover + description: Projects related to the Leo Rover robotic platform. + permalink: 'leo-rover' + +raph-rover: + label: Raph Rover + description: Projects related to the Raph Rover robotic platform. + permalink: 'raph-rover' + +# Application area tags + +# TBD \ No newline at end of file diff --git a/docs/guidelines/add-community-project.mdx b/docs/guidelines/add-community-project.mdx new file mode 100644 index 00000000..945d9311 --- /dev/null +++ b/docs/guidelines/add-community-project.mdx @@ -0,0 +1,239 @@ +--- +title: How to add a community project +sidebar_label: Adding a community project +sidebar_position: 4 +unlisted: true +description: >- + This page provides guidelines on how to add a community project to the + Fictionlab documentation. +--- + +This page provides guidelines on how to add a community project to the +Fictionlab documentation. Community projects are third-party projects developed +by the Fictionlab community that enhance or extend the functionality of +Fictionlab products like Leo or Raph rover. + +Community project list is located in the +[Community Projects](/community-projects) page. + +:::note + +Before adding a community project, ensure that it meets the following criteria: + +- The project is based on Fictionlab products or integrates with them. + +Fictionlab can remove any community project at any time without prior notice if +it does not meet the guidelines or is deemed inappropriate. + +::: + +## Before you begin + +Before adding a community project, check +[documentation repository](https://github.com/fictionlab/docs) to check how to +start adding new content. If you are not familiar with using GitHub and +Markdown/MDX files, please refer to the following resources: + +- [GitHub Docs - Getting started with GitHub](https://docs.github.com/en/get-started/quickstart) +- [Markdown Guide - Getting started with Markdown](https://www.markdownguide.org/getting-started/) +- [Docusaurus Docs - Markdown Features](https://docusaurus.io/docs/markdown-features) + +### Custom Fictionlab's components + +Fictionlab documentation uses custom React components to enhance the content and +provide additional functionality. To see a list of available components, visit +the [Components](/guidelines/components) page. + +## Creating a community project entry + +### Prepare your workspace on a separate fork + +All community project pages need be created on the separate forks of the +Fictionlab documentation repository. Creating a separate fork allows you to work +on your community project entry without affecting the main repository until you +are ready to submit a pull request. It also allows for better collaboration and +version control of your changes. + +To create a fork, click the "Fork" button at the top right corner of the +repository page on GitHub. This will create a copy of the repository under your +GitHub account where you can make your changes. More information on how to fork +a repository can be found in the +[Fork a repository - Github Docs](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/fork-a-repo). + +When you are ready to submit your community project, you can create a pull +request from your fork to the main repository. After the pull request is +reviewed and approved by the Fictionlab team, your community project will be +merged and queued for release in the next documentation update. + +:::tip + +Check the +[Getting Started](https://github.com/fictionlab/docs?tab=readme-ov-file#getting-started) +section in the documentation repository's README for more information on how to +setup your local workspace from the scratch. + +::: + +### Create the MDX file + +To add a community project to the Fictionlab documentation, create a new MDX +file in the `community-projects/` directory of this repository. Each community +project should have its own MDX file named after the project (e.g., +`my-community-project.mdx`). + +### Front matter + +Each community project page should include the front matter data at the top of +the MDX file. It allows proper indexing and display of the project on the +Community Projects page. + +:::info + +Some of the fields requires providing image_url or logo_url. All images should +be placed in the `/static/img/community-projects` directory of this repository. + +::: + +Below is the list of required front matter fields: + +- `title` +- `description` +- `authors` +- `tags` +- `image` +- `company` + +:::tip + +To see how each of the front matter fields should be structured, refer to the +documentation of the docusaurus: + +- [Docusaurus Docs - Front matter](https://docusaurus.io/docs/api/plugins/@docusaurus/plugin-content-blog#markdown-front-matter) +- [Docusaurus Docs - Inline authors](https://docusaurus.io/docs/blog#inline-authors) + ::: + +Example front matter: + +```yml +--- +title: Automated Plant Watering System +description: + An IoT-based automated plant watering system using Leo Rover and soil moisture + sensors. +authors: + - name: John Doe + title: Lead Developer + url: https://johndoe-website.com + image_url: /img/community-projects/automated-plant-watering/johndoe.jpg + socials: + x: johnDoe + github: johndoe + linkedin: johndoe + email: johndoe@example.com +tags: [leo-rover] +image: /img/community-projects/automated-plant-watering/project-image.jpg +company: + name: GreenTech Solutions + url: https://greentech-solutions.com + logo_url: /img/community-projects/automated-plant-watering/greentech-logo.jpg +--- +``` + +#### Front matter fields description + +- `title`: The title of the community project (max 100 characters). +- `description`: A brief description of the community project (max 160 + characters). +- `tags`: A list of tags relevant to the project (e.g., leo-rover). Please note + that only tags mentioned in the + [Community projects tags](/community-projects/tags) page should be used. +- `image`: The URL or path of the image representing the community project. It + will be displayed on the Community Projects page. +- `authors`: A list of authors who contributed to the project. It is possible to + add multiple authors by adding more entries to the list. Each author entry + should include the following fields: + - `name`: The author's name. + - `title`: The author's title or role. + - `url`: The URL to the author's personal or professional website. + - `image_url`: The URL or path to the author's image. + - `socials`: A list of social media handles for the author (e.g., x, github, + linkedin, email). +- `company`: Information about the company or organization associated with the + project, including: + - `name`: The company's name. + - `url`: The URL to the company's website. + - `logo_url`: The URL or path to the company's logo image. + +:::tip + +Check existing community project pages in the `/community-projects` directory +for examples of how to structure the front matter and content. + +::: + +### Content structure + +#### Summary + +After the front matter, provide a short summary of the community project. This +section should give an overview of the project's purpose and main features. It +should have around 300-400 characters. + +#### Truncate tag + +To control the length of the project preview on the Community Projects page, use +the `{/* truncate */}` tag after the summary. Content after this tag will not be +displayed in the preview. + +#### Main content + +In this section provide detailed information about the community project. You +can structure this section using headings, subheadings, lists, images, and code +snippets as needed. We do not enforce a specific structure for this section. + +#### Resources + +As final step, you can provide links to additional resources related to the +community project, such as: + +- GitHub repository +- Documentation +- Tutorials +- Videos +- Blog posts + +and any other relevant materials. + +## Submitting the community project + +:::info + +Test your MDX file locally using Docusaurus to ensure that it renders correctly +before submitting it as a pull request. Instructions for setting up a local +Docusaurus environment can be found in the +[Testing implemented changes](https://github.com/fictionlab/docs?tab=readme-ov-file#testing-implemented-changes) +section of the documentation repository's README. + +::: + +Once you have created the MDX file for your community project, submit it as a +pull request to the Fictionlab documentation repository. Ensure that your pull +request includes the new MDX file in the `/community-projects` directory. + +:::note + +Fictionlab team will review the submitted community project to ensure it meets +the guidelines and quality standards. They may request changes or additional +information before approving the pull request. + +::: + +After the pull request is reviewed and approved, your community project will be +added to the Fictionlab documentation and will appear on the Community Projects +page. + +## License Information + +All committed changes will be licensed under the terms specified in the LICENSE +file added to the repository. Please ensure that your contributions comply with +this license. diff --git a/docusaurus.config.ts b/docusaurus.config.ts index 8ecda010..3dde41f2 100644 --- a/docusaurus.config.ts +++ b/docusaurus.config.ts @@ -95,6 +95,21 @@ const config: Config = { }, }, }, + blog: { + id: 'community-projects', + path: 'community-projects', + blogTitle: 'Fictionlab Community Projects', + blogDescription: + 'Showcase of projects created by the Fictionlab community', + routeBasePath: '/community-projects', + blogSidebarCount: 0, + feedOptions: { type: null }, + blogListComponent: '@site/src/components/CommunityProjectsPage', + blogPostComponent: '@site/src/components/CommunityProjectPage', + onInlineTags: 'throw', + onInlineAuthors: 'ignore', + showReadingTime: false, + }, theme: { customCss: './src/css/custom.css', }, @@ -166,6 +181,11 @@ const config: Config = { position: 'left', to: '/integrations', }, + { + label: 'Community projects', + position: 'left', + to: '/community-projects', + }, { type: 'docsVersionDropdown', position: 'right', diff --git a/package.json b/package.json index 0af034a4..dfd0106f 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "check:formatting": "prettier --check \"**/*.{js,jsx,ts,tsx,mdx,css}\"", "check:spelling": "cspell", "check:redirects": "node scripts/redirects-check.js", - "check:frontmatter": "node scripts/frontmatter-check.js", + "check:frontmatter": "node scripts/docs-frontmatter-check.js && node scripts/community-projects-frontmatter-check.js", "check:types": "tsc --noEmit", "check": "yarn check:formatting && yarn check:spelling && yarn check:redirects && yarn check:frontmatter && yarn check:types" }, diff --git a/scripts/community-projects-frontmatter-check.js b/scripts/community-projects-frontmatter-check.js new file mode 100644 index 00000000..fefec205 --- /dev/null +++ b/scripts/community-projects-frontmatter-check.js @@ -0,0 +1,43 @@ +const fs = require('fs'); +const glob = require('glob'); +const matter = require('gray-matter'); + +// Define the keys that must be present in the front matter. +const REQUIRED_KEYS = [ + 'title', + 'description', + 'authors', + 'tags', + 'image', + 'company', +]; + +// Path to your MDX files for community projects. +const DOCS_PATHS = ['communityProjects/**/*.mdx']; + +function validateFrontMatter() { + const filePaths = DOCS_PATHS.flatMap((pattern) => glob.sync(pattern)); + const errors = []; + + filePaths.forEach((filePath) => { + const fileContent = fs.readFileSync(filePath, 'utf8'); + const { data } = matter(fileContent); + + // Check for required keys + REQUIRED_KEYS.forEach((key) => { + if (!data[key]) { + errors.push(`Missing key '${key}' in ${filePath}`); + } + }); + }); + + if (errors.length > 0) { + console.error('Validation Errors:'); + errors.forEach((error) => console.error(error)); + process.exit(1); + } else { + console.log('All community project files have valid front matter.'); + } +} + +validateFrontMatter(); diff --git a/scripts/frontmatter-check.js b/scripts/docs-frontmatter-check.js similarity index 95% rename from scripts/frontmatter-check.js rename to scripts/docs-frontmatter-check.js index 1310981d..e82c848f 100644 --- a/scripts/frontmatter-check.js +++ b/scripts/docs-frontmatter-check.js @@ -57,7 +57,7 @@ function validateFrontMatter() { // Exit with a non-zero exit code to fail the CI job. process.exit(1); } else { - console.log('All MDX files have the required front matter keys.'); + console.log('All docs files have the required front matter keys.'); } } diff --git a/src/components/CommunityProjectCard/index.tsx b/src/components/CommunityProjectCard/index.tsx new file mode 100644 index 00000000..cda2b8c5 --- /dev/null +++ b/src/components/CommunityProjectCard/index.tsx @@ -0,0 +1,56 @@ +import { type ReactNode } from 'react'; +import clsx from 'clsx'; +import BlogPostItemContainer from '@theme/BlogPostItem/Container'; +import type { Props } from '@theme/BlogPostItem'; +import styles from './styles.module.css'; +import Link from '@docusaurus/Link'; +import useCommunityProject from '@site/src/hooks/useCommunityProject'; + +export default function CommunityProjectCard({ + children, + className, +}: Props): ReactNode { + const { metadata } = useCommunityProject(); + + return ( + + + {metadata.frontMatter.image && ( +
+ {metadata.title} +
+ )} +
+

{metadata.title}

+ {metadata.description && ( +
{children}
+ )} +
+ {metadata.frontMatter.company && + metadata.frontMatter.company.name && + (metadata.frontMatter.company.logo_url ? ( + {metadata.frontMatter.company.name} + ) : ( + + {metadata.frontMatter.company.name} + + ))} +
+ {metadata.tags && metadata.tags.length > 0 && ( +
+ {metadata.tags.slice(0, 3).map((tag) => ( + + {tag.label} + + ))} +
+ )} +
+ +
+ ); +} diff --git a/src/components/CommunityProjectCard/styles.module.css b/src/components/CommunityProjectCard/styles.module.css new file mode 100644 index 00000000..ab74100c --- /dev/null +++ b/src/components/CommunityProjectCard/styles.module.css @@ -0,0 +1,107 @@ +.compactCard { + height: 100%; + transition: + transform 0.2s ease, + box-shadow 0.2s ease; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); +} + +.compactCard:hover { + transform: translateY(-4px); + box-shadow: 0 8px 16px rgba(0, 0, 0, 0.15); +} + +.cardLink { + text-decoration: none; + color: inherit; + overflow: hidden; +} + +.cardLink:hover { + text-decoration: none; + color: inherit; +} + +.cardImage { + width: 100%; + height: 200px; + overflow: hidden; + background-color: var(--ifm-color-emphasis-200); +} + +.cardImage img { + width: 100%; + height: 100%; + object-fit: cover; + transition: transform 0.3s ease; +} + +.compactCard:hover .cardImage img { + transform: scale(1.05); +} + +.cardContent { + padding: 1rem; +} + +.cardTitle { + font-size: 1.25rem; + font-weight: 600; + margin: 0 0 0.75rem 0; + line-height: 1.4; + color: var(--ifm-heading-color); + display: -webkit-box; + -webkit-line-clamp: 2; + line-clamp: 2; + -webkit-box-orient: vertical; + overflow: hidden; +} + +.cardDescription { + font-size: 0.9rem; + color: var(--ifm-color-emphasis-700); + line-height: 1.6; + margin: 0 0 1rem 0; + display: -webkit-box; + -webkit-line-clamp: 3; + line-clamp: 3; + -webkit-box-orient: vertical; + overflow: hidden; +} + +.cardTags { + display: flex; + flex-wrap: wrap; + gap: 0.5rem; + margin-top: 1rem; + padding-top: 1rem; + border-top: 1px solid var(--ifm-color-emphasis-200); +} + +.cardTag { + font-size: 0.7rem; + padding: 0.25rem 0.75rem; + background-color: var(--ifm-color-primary); + color: var(--ifm-color-emphasis-900); + border-radius: 12px; + font-weight: 500; +} + +.cardCompanyLogo { + width: 100%; + height: 100px; + border-radius: 4px; + object-fit: contain; +} + +.cardCompanyName { + display: block; + width: 100%; + font-size: 1rem; + font-weight: 600; + color: var(--ifm-color-primary); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + text-align: center; +} diff --git a/src/components/CommunityProjectItem/Header/index.tsx b/src/components/CommunityProjectItem/Header/index.tsx new file mode 100644 index 00000000..6eaeb0e6 --- /dev/null +++ b/src/components/CommunityProjectItem/Header/index.tsx @@ -0,0 +1,29 @@ +import React, { type ReactNode } from 'react'; +import BlogPostItemHeaderTitle from '@theme/BlogPostItem/Header/Title'; +import BlogPostItemHeaderInfo from '@theme/BlogPostItem/Header/Info'; +import BlogPostItemHeaderAuthors from '@theme/BlogPostItem/Header/Authors'; +import styles from './styles.module.css'; +import useCommunityProject from '@site/src/hooks/useCommunityProject'; + +export default function CommunityProjectItemHeader(): ReactNode { + const { metadata } = useCommunityProject(); + const { frontMatter } = metadata; + return ( +
+ + +
+
+ +
+ {frontMatter.company?.logo_url && ( + {frontMatter.company?.name + )} +
+
+ ); +} diff --git a/src/components/CommunityProjectItem/Header/styles.module.css b/src/components/CommunityProjectItem/Header/styles.module.css new file mode 100644 index 00000000..ee2d05d0 --- /dev/null +++ b/src/components/CommunityProjectItem/Header/styles.module.css @@ -0,0 +1,20 @@ +.authorsContainer { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 1rem; + gap: 2rem; +} + +.authorsInfo { + flex-shrink: 1; + width: 60%; +} + +.companyLogo { + width: 40%; + height: 100%; + max-height: 100px; + object-fit: contain; + object-position: right; +} diff --git a/src/components/CommunityProjectItem/index.tsx b/src/components/CommunityProjectItem/index.tsx new file mode 100644 index 00000000..64d22e1e --- /dev/null +++ b/src/components/CommunityProjectItem/index.tsx @@ -0,0 +1,28 @@ +import React, { type ReactNode } from 'react'; +import clsx from 'clsx'; +import { useBlogPost } from '@docusaurus/plugin-content-blog/client'; +import BlogPostItemContainer from '@theme/BlogPostItem/Container'; +import CommunityProjectItemHeader from '@site/src/components/CommunityProjectItem/Header'; +import BlogPostItemContent from '@theme/BlogPostItem/Content'; +import BlogPostItemFooter from '@theme/BlogPostItem/Footer'; +import type { Props } from '@theme/BlogPostItem'; + +// apply a bottom margin in list view +function useContainerClassName() { + const { isBlogPostPage } = useBlogPost(); + return !isBlogPostPage ? 'margin-bottom--xl' : undefined; +} + +export default function CommunityProjectItem({ + children, + className, +}: Props): ReactNode { + const containerClassName = useContainerClassName(); + return ( + + + {children} + + + ); +} diff --git a/src/components/CommunityProjectItems/index.tsx b/src/components/CommunityProjectItems/index.tsx new file mode 100644 index 00000000..87be3b43 --- /dev/null +++ b/src/components/CommunityProjectItems/index.tsx @@ -0,0 +1,24 @@ +import { BlogPostProvider } from '@docusaurus/plugin-content-blog/client'; +import type { Props } from '@theme/BlogPostItems'; +import styles from './styles.module.css'; +import { ReactNode } from 'react'; +import CommunityProjectCard from '@site/src/components/CommunityProjectCard'; + +export default function CommunityProjectItems({ items }: Props): ReactNode { + return ( +
+ {items.map(({ content: BlogPostContent }) => ( + +
+ + + +
+
+ ))} +
+ ); +} diff --git a/src/components/CommunityProjectItems/styles.module.css b/src/components/CommunityProjectItems/styles.module.css new file mode 100644 index 00000000..39bbe407 --- /dev/null +++ b/src/components/CommunityProjectItems/styles.module.css @@ -0,0 +1,61 @@ +.blogPostGrid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); + gap: 1.5rem; + margin-bottom: 2rem; + max-width: 100%; + margin-left: auto; + margin-right: auto; + padding: 0 1rem; +} + +@media (min-width: 1400px) { + .blogPostGrid { + grid-template-columns: repeat(4, 1fr); + max-width: 1600px; + } +} + +@media (min-width: 1024px) and (max-width: 1399px) { + .blogPostGrid { + grid-template-columns: repeat(3, 1fr); + } +} + +@media (min-width: 768px) and (max-width: 1023px) { + .blogPostGrid { + grid-template-columns: repeat(2, 1fr); + } +} + +@media (max-width: 767px) { + .blogPostGrid { + grid-template-columns: 1fr; + } +} + +.blogPostCard { + height: 100%; + display: flex; + flex-direction: column; + border: 1px solid var(--ifm-color-emphasis-300); + border-radius: 8px; + overflow: hidden; + transition: + transform 0.2s ease-in-out, + box-shadow 0.2s ease-in-out; + background-color: var( + --ifm-card-background-color, + var(--ifm-background-surface-color) + ); +} + +.blogPostCard:hover { + transform: translateY(-4px); + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); + border-color: var(--ifm-color-primary); +} + +.blogPostCard > * { + height: 100%; +} diff --git a/src/components/CommunityProjectPage/index.tsx b/src/components/CommunityProjectPage/index.tsx new file mode 100644 index 00000000..cbbbc596 --- /dev/null +++ b/src/components/CommunityProjectPage/index.tsx @@ -0,0 +1,77 @@ +import React, { type ReactNode } from 'react'; +import clsx from 'clsx'; +import { + HtmlClassNameProvider, + ThemeClassNames, +} from '@docusaurus/theme-common'; +import { + BlogPostProvider, + useBlogPost, +} from '@docusaurus/plugin-content-blog/client'; +import BlogLayout from '@theme/BlogLayout'; +import CommunityProjectItem from '@site/src/components/CommunityProjectItem'; +import BlogPostPaginator from '@theme/BlogPostPaginator'; +import BlogPostPageMetadata from '@theme/BlogPostPage/Metadata'; +import BlogPostPageStructuredData from '@theme/BlogPostPage/StructuredData'; +import TOC from '@theme/TOC'; +import ContentVisibility from '@theme/ContentVisibility'; +import type { Props } from '@theme/BlogPostPage'; +import type { BlogSidebar } from '@docusaurus/plugin-content-blog'; + +function CommunityProjectPageContent({ + sidebar, + children, +}: { + sidebar: BlogSidebar; + children: ReactNode; +}): ReactNode { + const { metadata, toc } = useBlogPost(); + const { nextItem, prevItem, frontMatter } = metadata; + const { + hide_table_of_contents: hideTableOfContents, + toc_min_heading_level: tocMinHeadingLevel, + toc_max_heading_level: tocMaxHeadingLevel, + } = frontMatter; + return ( + 0 ? ( + + ) : undefined + } + > + + + {children} + + {(nextItem || prevItem) && ( + + )} + + ); +} + +export default function CommunityProjectPage(props: Props): ReactNode { + const BlogPostContent = props.content; + return ( + + + + + + + + + + ); +} diff --git a/src/components/CommunityProjectsPage/index.tsx b/src/components/CommunityProjectsPage/index.tsx new file mode 100644 index 00000000..2cbe1896 --- /dev/null +++ b/src/components/CommunityProjectsPage/index.tsx @@ -0,0 +1,77 @@ +import React, { type ReactNode, useMemo } from 'react'; +import clsx from 'clsx'; +import type { Tag } from '@docusaurus/utils'; + +import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; +import { + PageMetadata, + HtmlClassNameProvider, + ThemeClassNames, +} from '@docusaurus/theme-common'; +import BlogListPaginator from '@theme/BlogListPaginator'; +import SearchMetadata from '@theme/SearchMetadata'; +import type { Props } from '@theme/BlogListPage'; +import CommunityProjectItems from '@site/src/components/CommunityProjectItems'; +import BlogListPageStructuredData from '@theme/BlogListPage/StructuredData'; +import Layout from '@theme/Layout'; +import CommunityProjectsTags from '@site/src/components/CommunityProjectsTags'; + +function BlogListPageMetadata(props: Props): ReactNode { + const { metadata } = props; + const { + siteConfig: { title: siteTitle }, + } = useDocusaurusContext(); + const { blogDescription, blogTitle, permalink } = metadata; + const isBlogOnlyMode = permalink === '/'; + const title = isBlogOnlyMode ? siteTitle : blogTitle; + return ( + <> + + + + ); +} + +function CommunityProjectsPageContent(props: Props): ReactNode { + const { metadata, items } = props; + + // Extract unique tags from items metadata + const tags = useMemo(() => { + const tagsMap = new Map(); + + items.forEach(({ content }) => { + (content.metadata.tags as Tag[])?.forEach((tag) => { + if (!tagsMap.has(tag.permalink)) { + tagsMap.set(tag.permalink, tag); + } + }); + }); + + return Array.from(tagsMap.values()); + }, [items]); + + return ( + +
+ + + +
+
+ ); +} + +export default function CommunityProjectsPage(props: Props): ReactNode { + return ( + + + + + + ); +} diff --git a/src/components/CommunityProjectsTags/index.tsx b/src/components/CommunityProjectsTags/index.tsx new file mode 100644 index 00000000..0c8e5e6e --- /dev/null +++ b/src/components/CommunityProjectsTags/index.tsx @@ -0,0 +1,32 @@ +import { type ReactNode } from 'react'; +import type { Tag } from '@docusaurus/utils'; +import styles from './styles.module.css'; +import TagsListInline from '@theme/TagsListInline'; + +interface Props { + tags?: Tag[]; + className?: string; +} + +export default function CommunityProjectsTags({ + tags, + className = '', +}: Props): ReactNode { + const allTags = [ + { + label: 'All', + permalink: '/community-projects/tags', + description: 'All community projects', + }, + ].concat(tags || []); + + if (!tags || tags.length === 0) { + return null; + } + + return ( +
+ +
+ ); +} diff --git a/src/components/CommunityProjectsTags/styles.module.css b/src/components/CommunityProjectsTags/styles.module.css new file mode 100644 index 00000000..add8a455 --- /dev/null +++ b/src/components/CommunityProjectsTags/styles.module.css @@ -0,0 +1,4 @@ +.tagsContainer { + width: 100%; + margin: 1rem 0; +} diff --git a/src/hooks/useCommunityProject/index.tsx b/src/hooks/useCommunityProject/index.tsx new file mode 100644 index 00000000..24470f3c --- /dev/null +++ b/src/hooks/useCommunityProject/index.tsx @@ -0,0 +1,21 @@ +import { PropBlogPostMetadata } from '@docusaurus/plugin-content-blog'; +import { useBlogPost } from '@docusaurus/plugin-content-blog/client'; + +interface CommunityProjectMetadata + extends Omit { + frontMatter: PropBlogPostMetadata['frontMatter'] & { + company?: { + name?: string; + logo_url?: string; + url?: string; + }; + }; +} + +export default function useCommunityProject() { + const blogPost = useBlogPost(); + return { + ...blogPost, + metadata: blogPost.metadata as CommunityProjectMetadata, + }; +}