Add subscription and community components (#47)

Close #46

Same as those in [blog](https://gitea.com/gitea/blog/pulls/272), but for docs and api
Ejected DocPage (unsafe to eject) and ApiDoc (safe to eject) for layout changes

![Screen Shot 2023-07-17 at 15.32.29](/attachments/74f992c7-b60d-4d71-bb50-8c085e6783fe)![Screen Shot 2023-07-17 at 15.32.40](/attachments/0df35cba-0797-4f21-abbe-bb61736c1e1f)

Screenshots

![Screen Shot 2023-07-17 at 15.27.29](/attachments/39e09cee-5043-4349-aaaa-7ab585aefbc4)![Screen Shot 2023-07-17 at 15.27.36](/attachments/396a9ae1-bbae-42b5-b1ab-c4158a2be78d)![Screen Shot 2023-07-17 at 15.27.53](/attachments/e12a35eb-625c-4950-960e-61226eda4ad4)![Screen Shot 2023-07-17 at 15.28.11](/attachments/578bd742-8a8b-4324-82d4-b9dc47d7f511)![Screen Shot 2023-07-17 at 15.28.19](/attachments/f304bb80-c6cb-4af4-b16f-b19fa2099ba5)

Co-authored-by: techknowlogick <techknowlogick@noreply.gitea.com>
Co-authored-by: techknowlogick <techknowlogick@gitea.io>
Reviewed-on: https://gitea.com/gitea/gitea-docusaurus/pulls/47
Co-authored-by: HesterG <hestergong@gmail.com>
Co-committed-by: HesterG <hestergong@gmail.com>
This commit is contained in:
HesterG
2023-07-21 10:57:22 +00:00
committed by Lunny Xiao
parent c77d0e0e34
commit 02b0ebc9e1
22 changed files with 699 additions and 3 deletions

View File

@@ -0,0 +1,22 @@
import clsx from "clsx";
import React from "react";
import style from "./styles.module.css";
// skin?: "default" | "primary"
export const ActionCard = ({ skin = "default", icon, title, description, svgBackgroundColor, children, className }) => {
const styles = { background: svgBackgroundColor};
return (
<div
className={clsx(style.root, className, {
[style.skinPrimary]: skin === "primary",
})}
>
<div className={style.icon} style={styles}>{icon}</div>
<h3 className={style.title}>{title}</h3>
<p className={style.description}>{description}</p>
<div className={style.content}>{children}</div>
</div>
)
}
export default ActionCard

View File

@@ -0,0 +1,53 @@
.root {
padding: 2rem;
border-radius: 16px;
background: var(--theme-attention-card-bg-color);
}
.title {
margin-top: 1rem;
margin-bottom: 1rem;
font-size: var(--font-size-big-1);
font-weight: var(--ifm-font-weight-bold);
}
.description {
font-size: var(--font-size-large);
}
@media screen and (min-width: 880px) {
.root {
padding: 2rem;
}
.title {
font-size: 1.2rem;
}
}
.content {
display: flex;
flex: 1;
}
.skinPrimary {
background: var(--ifm-color-primary-lighter);
}
[data-theme='dark'] .skinPrimary {
background: var(--ifm-color-primary-darker);
}
.skinPrimary .title,
.skinPrimary .description {
color: var(--theme-attention-card-text-color);
}
.icon {
display: flex;
align-items: center;
justify-content: center;
height: 76px;
width: 76px;
border-radius: 50%;
}

View File

@@ -0,0 +1,19 @@
.cards {
display: grid;
gap: 1.5rem;
}
.card__link {
text-transform: uppercase;
font-size: var(--font-size-normal);
}
.card__link:not(:last-child) {
margin-right: 1.5rem;
}
@media screen and (min-width: 768px) {
.cards {
grid-template-columns: 1fr 1fr;
}
}

View File

@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="45" height="45" viewBox="0 0 576 512">
<!--! Font Awesome Free 6.4.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. -->
<path d="M80.3 44C69.8 69.9 64 98.2 64 128s5.8 58.1 16.3 84c6.6 16.4-1.3 35-17.7 41.7s-35-1.3-41.7-17.7C7.4 202.6 0 166.1 0 128S7.4 53.4 20.9 20C27.6 3.6 46.2-4.3 62.6 2.3S86.9 27.6 80.3 44zM555.1 20C568.6 53.4 576 89.9 576 128s-7.4 74.6-20.9 108c-6.6 16.4-25.3 24.3-41.7 17.7S489.1 228.4 495.7 212c10.5-25.9 16.3-54.2 16.3-84s-5.8-58.1-16.3-84C489.1 27.6 497 9 513.4 2.3s35 1.3 41.7 17.7zM352 128c0 23.7-12.9 44.4-32 55.4V480c0 17.7-14.3 32-32 32s-32-14.3-32-32V183.4c-19.1-11.1-32-31.7-32-55.4c0-35.3 28.7-64 64-64s64 28.7 64 64zM170.6 76.8C163.8 92.4 160 109.7 160 128s3.8 35.6 10.6 51.2c7.1 16.2-.3 35.1-16.5 42.1s-35.1-.3-42.1-16.5c-10.3-23.6-16-49.6-16-76.8s5.7-53.2 16-76.8c7.1-16.2 25.9-23.6 42.1-16.5s23.6 25.9 16.5 42.1zM464 51.2c10.3 23.6 16 49.6 16 76.8s-5.7 53.2-16 76.8c-7.1 16.2-25.9 23.6-42.1 16.5s-23.6-25.9-16.5-42.1c6.8-15.6 10.6-32.9 10.6-51.2s-3.8-35.6-10.6-51.2c-7.1-16.2 .3-35.1 16.5-42.1s35.1 .3 42.1 16.5z"/>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -0,0 +1,53 @@
import footerCss from "./footer.module.css";
import ActionCard from "../ActionCard";
import FossIcon from "./foss.svg";
import SubscribeIcon from "./subscribeIcon.svg";
import Subscribe from "../Subscribe";
import React from "react";
import SvgImage from "../SvgImage";
export const ActionFooter = () => (
<div className={footerCss.cards}>
<ActionCard
icon={
<SvgImage
image={<FossIcon />}
title="An icon showing wave propagation"
/>
}
svgBackgroundColor="#ffffff"
title="Join our community"
description="Gitea is open source. Star our GitHub repo, and join our community on Discord!"
>
<a
className={footerCss.card__link}
href={'https://github.com/go-gitea/gitea'}
rel="noopener noreferrer"
target="_blank"
>
Go to GitHub&nbsp;&nbsp;&gt;
</a>
<a className={footerCss.card__link} href={'https://discord.com/invite/gitea'}>
Join Discord&nbsp;&nbsp;&gt;
</a>
</ActionCard>
<ActionCard
title="Subscribe to our newsletter"
description="Stay up to date with all things Gitea"
svgBackgroundColor="#1E1F27"
icon={
<SvgImage
image={<SubscribeIcon />}
title="An icon showing a paper plane"
/>
}
skin="primary"
>
<Subscribe
placeholder="Email address"
submitButtonText = "Subscribe"
/>
</ActionCard>
</div>
)

View File

@@ -0,0 +1,27 @@
.loader {
position: absolute;
width: 20px;
height: 20px;
}
.loader:after {
content: " ";
display: block;
width: 14px;
height: 14px;
margin: 0;
border-radius: 50%;
border: 3px solid transparent;
border-color: var(--ifm-color-white) transparent var(--ifm-color-white)
transparent;
animation: loader 1.2s linear infinite;
}
@keyframes loader {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}

View File

@@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" width="45" height="45" viewBox="0 0 512 512">
<!--! Font Awesome Free 6.4.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. -->
<style>svg{fill:#ffffff}</style>
<path d="M16.1 260.2c-22.6 12.9-20.5 47.3 3.6 57.3L160 376V479.3c0 18.1 14.6 32.7 32.7 32.7c9.7 0 18.9-4.3 25.1-11.8l62-74.3 123.9 51.6c18.9 7.9 40.8-4.5 43.9-24.7l64-416c1.9-12.1-3.4-24.3-13.5-31.2s-23.3-7.5-34-1.4l-448 256zm52.1 25.5L409.7 90.6 190.1 336l1.2 1L68.2 285.7zM403.3 425.4L236.7 355.9 450.8 116.6 403.3 425.4z"/>
</svg>

After

Width:  |  Height:  |  Size: 623 B

View File

@@ -0,0 +1,66 @@
import clsx from "clsx";
import React from "react";
import styles from "./styles.module.css";
const Button = (props) => {
const { icon, variant, size, uppercase, className, to, href, children } = props;
const classes = clsx(className, styles.button, {
[styles["button--icon"]]: icon != null,
[styles["button--primary"]]: variant === "primary",
[styles["button--secondary"]]: variant === "secondary",
[styles["button--small"]]: size === "small",
[styles["button--tertiary"]]: variant === "tertiary",
[styles["button--plain"]]: variant === "plain",
[styles["button--uppercase"]]: uppercase === "true",
[styles["button--xsmall"]]: size === "xsmall",
[styles["button--xxsmall"]]: size === "xxsmall",
})
if (href != null) {
const { disabled, onClick, newtab} = props;
return (
<a
className={classes}
{...(disabled ?? false ? {} : {
href,
onClick,
})}
{...(newtab === "true" ? {
rel: "noopener noreferrer",
target: "_blank",
} : {})}
>
{icon}
{children}
</a>
)
}
if (to != null) {
return (
<a className={classes} href={to} onClick={onClick}>
{icon}
{children}
</a>
)
}
return (
<button
{...props}
className={classes}
>
{icon}
{children}
</button>
)
}
Button.defaultProps = {
newtab: "true",
size: "normal",
uppercase: "true",
variant: "primary",
}
export default Button

View File

@@ -0,0 +1,95 @@
.button {
display: inline-flex;
align-items: center;
justify-content: center;
height: 55px;
padding: 0 2rem;
border: none;
border-radius: calc(var(--ifm-global-border-radius) / 2);
font-weight: var(--ifm-font-weight-bold);
font-size: var(--font-size-normal);
transition: background-color 100ms cubic-bezier(0.17, 0.67, 0.83, 0.67);
}
.button:hover {
text-decoration: none;
cursor: pointer;
}
.button--plain {
height: auto;
padding: 0;
font-weight: unset;
font-size: unset;
}
.button--primary {
background-color: var(--theme-button-primary-background-color);
color: var(--theme-button-primary-text-color); }
.button--primary:hover {
background-color: var(--theme-button-primary-hover-background-color);
color: var(--theme-button-primary-text-color);
}
.button--icon img, .button--icon svg {
margin-right: 0.5rem;
}
.button--secondary {
background-color: var(--theme-button-secondary-background-color);
color: var(--theme-button-secondary-text-color);
}
.button--secondary:hover {
background-color: var(--theme-button-secondary-hover-background-color);
color: var(--theme-button-secondary-text-color);
}
.button--tertiary {
background-color: var(--theme-button-tertiary-background-color);
color: var(--theme-button-tertiary-text-color);
}
.button--tertiary:hover {
color: var(--theme-button-tertiary-text-color);
background-color: var(--theme-button-tertiary-hover-background-color);
}
.button--small {
height: 3.5rem;
}
.button--xsmall {
height: 2.6rem;
padding: 0 1rem;
}
.button--xxsmall {
height: 2rem;
font-weight: normal;
font-size: 0.9rem;
}
.button--uppercase {
text-transform: uppercase;
}
@media (max-width: 996px) {
.button {
padding: 0 1.75rem;
}
.button--xsmall {
padding: 0 1rem;
}
.button--xxsmall {
padding: 0 0.9rem;
}
.button--plain {
padding: 0;
}
}

View File

@@ -0,0 +1,21 @@
import clsx from "clsx";
import React from "react";
import styles from "./styles.module.css";
const Input = (props) => {
const classes = clsx(props.className, styles.input)
return (
<input
{...props}
className={classes}
/>
)
}
Input.defaultProps = {
type: "text",
}
export default Input

View File

@@ -0,0 +1,23 @@
.input {
display: flex;
height: 55px;
padding: 0 2rem;
align-items: center;
border-radius: calc(var(--ifm-global-border-radius) / 2);
border: none;
background: var(--palette-rock);
font-size: var(--font-size-normal);
color: var(--ifm-color-white);
border: 2px solid transparent;
}
.input:focus {
outline: none;
border-color: var(--ifm-color-white);
}
.input:placeholder {
color: var(--palette-pale-blue);
font-size: var(--font-size-normal);
font-weight: var(--ifm-font-weight-bold);
}

View File

@@ -0,0 +1,31 @@
import React from "react";
import style from "./styles.module.css";
import clsx from "clsx";
export const Section = ({
fullWidth,
children,
odd,
accent,
row,
noGap,
center,
className = "",
}) => (
<div
className={clsx(
style.root,
{
[style.odd]: odd,
[style.accent]: accent,
[style.row]: row,
[style.fullWidth]: fullWidth,
[style.noGap]: noGap,
[style.center]: center,
},
className,
)}
>
{children}
</div>
)

View File

@@ -0,0 +1,41 @@
.root {
display: flex;
flex-direction: column;
max-width: var(--ifm-container-width);
width: 100%;
padding: 2rem 1rem;
margin: 0 auto;
}
@media screen and (min-width: 900px) {
.root {
padding: 4.5rem 2rem;
}
}
.row {
flex-direction: row;
}
.odd {
background-color: var(--theme-section-odd-bg-color);
}
.accent {
--ifm-link-hover-color: var(--palette-pink);
--ifm-link-color: var(--palette-pink);
padding-top: 7rem;
padding-bottom: 7.5rem;
}
.fullWidth {
max-width: 100%;
}
.noGap {
padding: 0;
}
.center {
align-items: center;
}

View File

@@ -0,0 +1,39 @@
import React, { useState } from "react"
import Input from "../Input"
import Button from "../Button"
import style from "./style.module.css"
import clsx from "clsx"
const Spinner = () => <span className={style.loader} />
const Subscribe = ({placeholder, submitButtonText, className, classNameInputs}) => {
const [loading, setLoading] = useState(false)
return (
<form method="post" action="https://list.gitea.com/subscription/form" className={clsx(style.root, className)} onSubmit={() => {setLoading(true)}}>
<div className={clsx(style.inputs, classNameInputs)}>
<Input type="hidden" name="nonce" />
<input className={style.checkbox} id="2aae7" type="checkbox" name="l" value="2aae7a49-b6b9-4aa3-b35a-32c9aeace57b" checked readOnly />
<Input
className={style.input}
name="email"
type="email"
title="Email address should be valid"
placeholder={placeholder}
required
autoComplete="off"
/>
<Button
variant={"tertiary"}
type="submit"
className={style.subscribeSubmit}
>
{loading ? <Spinner /> : submitButtonText}
</Button>
</div>
</form>
)
}
export default Subscribe

View File

@@ -0,0 +1,75 @@
.root {
width: 100%;
}
.inputs {
display: grid;
gap: 1rem;
}
:global(html[data-theme="light"]) .subscribeSubmit {
background: #dde0e9;
}
:global(html[data-theme="light"]) .subscribeSubmit:hover {
opacity: 0.8;
}
@media screen and (min-width: 600px) {
.inputs {
grid-template-columns: 4fr 2fr;
}
}
.input {
color: var(--theme-input-text-color);
padding: 1rem;
font-size: var(--font-size-small);
width: 100%;
background-color: var(--theme-input-bg-color);
}
.checkbox {
display: none !important;
}
.input::placeholder {
color: var(--theme-input-text-color);
}
.submit {
white-space: nowrap;
}
.loader {
position: absolute;
width: 20px;
height: 20px;
}
.loader:after {
content: " ";
display: block;
width: 14px;
height: 14px;
margin: 0;
border-radius: 50%;
border: 3px solid transparent;
border-color: var(--ifm-color-white) transparent var(--ifm-color-white)
transparent;
animation: loader 1.2s linear infinite;
}
.success {
font-size: var(--font-size-large);
font-weight: var(--ifm-font-weight-bold);
}
@keyframes loader {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}

View File

@@ -0,0 +1,9 @@
import { cloneElement } from "react";
const SvgImage = ({ image, title = "" }) =>
cloneElement(image, {
...image.props,
title,
})
export default SvgImage

View File

@@ -6,6 +6,25 @@
/* You can override the default Infima variables here. */
:root {
--font-size-small: 15px;
--font-size-normal: 16px;
--font-size-large: 17px;
--font-size-big-1: 22px;
--font-size-big-2: 24px;
--font-size-big-3: 32px;
--font-size-big-4: 46px;
--font-size-big-5: 64px;
--palette-dark-10: rgba(0, 0, 0, 0.1);
--palette-dark-20: rgba(0, 0, 0, 0.2);
--palette-dark-30: rgba(0, 0, 0, 0.3);
--palette-dark-40: rgba(0, 0, 0, 0.4);
--palette-dark-60: rgba(0, 0, 0, 0.6);
--palette-dark-80: rgba(0, 0, 0, 0.8);
--palette-white-10: rgba(255, 255, 255, 0.1);
--palette-white-20: rgba(255, 255, 255, 0.2);
--palette-charade: #21222c;
--palette-rock: #262833;
--palette-pale-blue: #b1b5d3;
--ifm-color-primary: #2e8555;
--ifm-color-primary-dark: #29784c;
--ifm-color-primary-darker: #277148;
@@ -14,11 +33,29 @@
--ifm-color-primary-lighter: #359962;
--ifm-color-primary-lightest: #3cad6e;
--ifm-code-font-size: 95%;
--docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.1);
--ifm-global-border-radius: 8px;
--docusaurus-highlighted-code-line-bg: var(--palette-dark-10);
--theme-card-text-color: var(--palette-charade);
--theme-card-title-color: var(--palette-charade);
--theme-attention-card-bg-color: #f0f1f5;
--theme-attention-card-text-color: var(--ifm-color-white);
--theme-input-text-color: #555b88;
--theme-card-secondary-bg-color: #dde0e9;
--theme-button-primary-background-color: var(--ifm-color-primary);
--theme-button-primary-text-color: var(--ifm-color-white);
--theme-button-primary-hover-background-color: var(--ifm-color-primary-darker);
--theme-button-secondary-background-color: var(--palette-dark-10);
--theme-button-secondary-text-color: var(--palette-charade);
--theme-button-secondary-hover-background-color: var(--palette-dark-30);
--theme-button-tertiary-background-color: var(--palette-dark-30);
--theme-button-tertiary-text-color: var(--palette-dark-80);
--theme-button-tertiary-hover-background-color: var(--palette-dark-40);
--theme-input-bg-color: #f0f1f5;
}
/* For readability concerns, you should choose a lighter palette in dark mode. */
[data-theme='dark'] {
--palette-gray: #4b4e5d;
--ifm-color-primary: #25c2a0;
--ifm-color-primary-dark: #21af90;
--ifm-color-primary-darker: #1fa588;
@@ -26,7 +63,20 @@
--ifm-color-primary-light: #29d5b0;
--ifm-color-primary-lighter: #32d8b4;
--ifm-color-primary-lightest: #4fddbf;
--docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.3);
--docusaurus-highlighted-code-line-bg: var(--palette-dark-30);
--theme-attention-card-bg-color: var(--palette-gray);
--theme-input-bg-color: #44475a;
--theme-input-text-color: #b1b5d3;
--theme-card-secondary-bg-color: var(--palette-charade);
--theme-button-primary-background-color: var(--ifm-color-primary);
--theme-button-primary-text-color: var(--ifm-color-white);
--theme-button-primary-hover-background-color: var(--ifm-color-primary-darker);
--theme-button-secondary-background-color: var(--ifm-color-white);
--theme-button-secondary-text-color: var(--palette-charade);
--theme-button-secondary-hover-background-color: #d9d9d9;
--theme-button-tertiary-background-color: var(--palette-white-10);
--theme-button-tertiary-text-color: var(--ifm-color-white);
--theme-button-tertiary-hover-background-color: var(--palette-white-20);
}
[data-theme='dark'] [class*='announcementBar'] {

View File

@@ -0,0 +1,16 @@
import React from 'react';
import Layout from '@theme/Layout';
import Redoc from '@theme/Redoc';
import { ActionFooter } from "@site/src/components/ActionFooter";
import { Section } from "@site/src/components/Section";
function ApiDoc({ layoutProps, specProps }) {
const defaultTitle = specProps.spec?.info?.title || 'API Docs';
const defaultDescription = specProps.spec?.info?.description || 'Open API Reference Docs for the API';
return (<Layout title={defaultTitle} description={defaultDescription} {...layoutProps}>
<Redoc {...specProps}/>
<Section>
<ActionFooter />
</Section>
</Layout>);
}
export default ApiDoc;

View File

@@ -0,0 +1,2 @@
import ApiDoc from './ApiDoc';
export default ApiDoc;

View File

@@ -0,0 +1,36 @@
// Ejected unsafe, need to check if this changes and maintain this component
// https://github.com/facebook/docusaurus/blob/docusaurus-v2/packages/docusaurus-theme-classic/src/theme/DocPage/Layout/index.tsx
import React, {useState} from 'react';
import {useDocsSidebar} from '@docusaurus/theme-common/internal';
import Layout from '@theme/Layout';
import BackToTopButton from '@theme/BackToTopButton';
import DocPageLayoutSidebar from '@theme/DocPage/Layout/Sidebar';
import DocPageLayoutMain from '@theme/DocPage/Layout/Main';
import styles from './styles.module.css';
import { ActionFooter } from "@site/src/components/ActionFooter";
import { Section } from "@site/src/components/Section";
export default function DocPageLayout({children}) {
const sidebar = useDocsSidebar();
const [hiddenSidebarContainer, setHiddenSidebarContainer] = useState(false);
return (
<Layout wrapperClassName={styles.docsWrapper}>
<BackToTopButton />
<div className={styles.docPage}>
{sidebar && (
<DocPageLayoutSidebar
sidebar={sidebar.items}
hiddenSidebarContainer={hiddenSidebarContainer}
setHiddenSidebarContainer={setHiddenSidebarContainer}
/>
)}
<DocPageLayoutMain hiddenSidebarContainer={hiddenSidebarContainer}>
{children}
</DocPageLayoutMain>
</div>
<Section>
<ActionFooter />
</Section>
</Layout>
);
}

View File

@@ -0,0 +1,9 @@
.docPage {
display: flex;
width: 100%;
}
.docsWrapper {
display: flex;
flex: 1 0 auto;
}