first commit

This commit is contained in:
2025-04-24 13:11:28 +08:00
commit ff9c54d5e4
5960 changed files with 834111 additions and 0 deletions

View File

@@ -0,0 +1,91 @@
import React from 'react'
import { useTranslation } from 'react-i18next'
import LanguagePicker from '../language-picker'
import FacebookLogo from '@/shared/svgs/facebook-logo'
import LinkedInLogo from '@/shared/svgs/linkedin-logo'
import XLogo from '@/shared/svgs/x-logo'
import classNames from 'classnames'
type FooterLinkProps = {
href: string
children: React.ReactNode
}
type SocialMediaLinkProps = {
href: string
icon: React.ReactNode
className: string
accessibilityLabel: string
}
function FatFooterBase() {
const { t } = useTranslation()
const currentYear = new Date().getFullYear()
return (
<footer className="fat-footer-base">
<div className="fat-footer-base-section fat-footer-base-meta">
<div className="fat-footer-base-item">
<div className="fat-footer-base-copyright">
© {currentYear} Overleaf
</div>
<FooterBaseLink href="/legal">
{t('privacy_and_terms')}
</FooterBaseLink>
<FooterBaseLink href="https://www.digital-science.com/security-certifications/">
{t('compliance')}
</FooterBaseLink>
</div>
<div className="fat-footer-base-item fat-footer-base-language">
<LanguagePicker showHeader={false} />
</div>
</div>
<div className="fat-footer-base-section fat-footer-base-social">
<div className="fat-footer-base-item">
<SocialMediaLink
href="https://x.com/overleaf"
icon={<XLogo />}
className="x-logo"
accessibilityLabel={t('app_on_x', { social: 'X' })}
/>
<SocialMediaLink
href="https://www.facebook.com/overleaf.editor"
icon={<FacebookLogo />}
className="facebook-logo"
accessibilityLabel={t('app_on_x', { social: 'Facebook' })}
/>
<SocialMediaLink
href="https://www.linkedin.com/company/writelatex-limited"
icon={<LinkedInLogo />}
className="linkedin-logo"
accessibilityLabel={t('app_on_x', { social: 'LinkedIn' })}
/>
</div>
</div>
</footer>
)
}
function FooterBaseLink({ href, children }: FooterLinkProps) {
return (
<a className="fat-footer-link" href={href}>
{children}
</a>
)
}
function SocialMediaLink({
href,
icon,
className,
accessibilityLabel,
}: SocialMediaLinkProps) {
return (
<a className={classNames('fat-footer-social', className)} href={href}>
{icon}
<span className="visually-hidden">{accessibilityLabel}</span>
</a>
)
}
export default FatFooterBase

View File

@@ -0,0 +1,133 @@
import React from 'react'
import { useTranslation } from 'react-i18next'
import FatFooterBase from './fat-footer-base'
type FooterLinkProps = {
href: string
label: string
}
type FooterSectionProps = {
title: string
links: FooterLinkProps[]
}
function FatFooter() {
const { t } = useTranslation()
const hideFatFooter = false
const sections = [
{
title: t('About'),
links: [
{ href: '/about', label: t('footer_about_us') },
{ href: '/about/values', label: t('our_values') },
{ href: '/about/careers', label: t('careers') },
{ href: '/for/press', label: t('press_and_awards') },
{ href: '/blog', label: t('blog') },
],
},
{
title: t('Learn'),
links: [
{
href: '/learn/latex/Learn_LaTeX_in_30_minutes',
label: t('latex_in_thirty_minutes'),
},
{ href: '/latex/templates', label: t('templates') },
{ href: '/events/webinars', label: t('webinars') },
{ href: '/learn/latex/Tutorials', label: t('tutorials') },
{
href: '/learn/latex/Inserting_Images',
label: t('how_to_insert_images'),
},
{ href: '/learn/latex/Tables', label: t('how_to_create_tables') },
],
},
{
title: t('Plans and Pricing'),
links: [
{
href: '/learn/how-to/Overleaf_premium_features',
label: t('premium_features'),
},
{
href: '/user/subscription/plans?itm_referrer=footer-for-indv-groups',
label: t('for_individuals_and_groups'),
},
{ href: '/for/enterprises', label: t('for_enterprise') },
{ href: '/for/universities', label: t('for_universities') },
{
href: '/user/subscription/plans?itm_referrer=footer-for-students#student-annual',
label: t('for_students'),
},
{ href: '/for/government', label: t('for_government') },
],
},
{
title: t('Get Involved'),
links: [
{ href: '/for/community/advisors', label: t('become_an_advisor') },
{
href: 'https://forms.gle/67PSpN1bLnjGCmPQ9',
label: t('let_us_know_what_you_think'),
},
{ href: '/beta/participate', label: t('join_beta_program') },
],
},
{
title: t('Help'),
links: [
{ href: '/about/why-latex', label: t('why_latex') },
{ href: '/learn', label: t('Documentation') },
{ href: '/contact', label: t('footer_contact_us') },
{ href: 'https://status.overleaf.com/', label: t('website_status') },
],
},
]
return (
<footer className="fat-footer hidden-print">
<div
role="navigation"
aria-label={t('footer_navigation')}
className="fat-footer-container"
>
<div className={`fat-footer-sections ${hideFatFooter ? 'hidden' : ''}`}>
<div className="footer-section" id="footer-brand">
<a href="/" aria-label={t('overleaf')} className="footer-brand">
<span className="visually-hidden">{t('overleaf')}</span>
</a>
</div>
{sections.map(section => (
<div className="footer-section" key={section.title}>
<FooterSection title={section.title} links={section.links} />
</div>
))}
</div>
<FatFooterBase />
</div>
</footer>
)
}
function FooterSection({ title, links }: FooterSectionProps) {
const { t } = useTranslation()
return (
<>
<h2 className="footer-section-heading">{t(title)}</h2>
<ul className="list-unstyled">
{links.map(link => (
<li key={link.href}>
<a href={link.href}>{t(link.label)}</a>
</li>
))}
</ul>
</>
)
}
export default FatFooter

View File

@@ -0,0 +1,9 @@
import { FooterMetadata } from '@/features/ui/components/types/footer-metadata'
import ThinFooter from '@/features/ui/components/bootstrap-5/footer/thin-footer'
import FatFooter from '@/features/ui/components/bootstrap-5/footer/fat-footer'
function Footer(props: FooterMetadata) {
return props.showThinFooter ? <ThinFooter {...props} /> : <FatFooter />
}
export default Footer

View File

@@ -0,0 +1,92 @@
import type {
FooterItem,
FooterMetadata,
} from '@/features/ui/components/types/footer-metadata'
import OLRow from '@/features/ui/components/ol/ol-row'
import LanguagePicker from '@/features/ui/components/bootstrap-5/language-picker'
import React from 'react'
function FooterItemLi({
text,
translatedText,
url: href,
class: className,
label,
}: FooterItem) {
const textToDisplay = translatedText || text
if (!href) {
return <li>{textToDisplay}</li>
}
const linkProps = {
href,
className,
'aria-label': label,
}
return (
<li>
<a {...linkProps} dangerouslySetInnerHTML={{ __html: textToDisplay }} />
</li>
)
}
function Separator() {
return (
<li role="separator" className="text-muted">
<strong>|</strong>
</li>
)
}
function ThinFooter({
showPoweredBy,
subdomainLang,
leftItems,
rightItems,
}: FooterMetadata) {
const showLanguagePicker = Boolean(
subdomainLang && Object.keys(subdomainLang).length > 1
)
const hasCustomLeftNav = Boolean(leftItems && leftItems.length > 0)
return (
<footer className="site-footer">
<div className="site-footer-content d-print-none">
<OLRow>
<ul className="site-footer-items col-lg-9">
{showPoweredBy ? (
<>
<li>
{/* year of Server Pro release, static */}© 2025{' '}
<a href="https://www.overleaf.com/for/enterprises">
Powered by Overleaf
</a>
</li>
{showLanguagePicker || hasCustomLeftNav ? <Separator /> : null}
</>
) : null}
{showLanguagePicker ? (
<>
<li>
<LanguagePicker showHeader />
</li>
{hasCustomLeftNav ? <Separator /> : null}
</>
) : null}
{leftItems?.map(item => <FooterItemLi key={item.text} {...item} />)}
</ul>
<ul className="site-footer-items col-lg-3 text-end">
{rightItems?.map(item => (
<FooterItemLi key={item.text} {...item} />
))}
</ul>
</OLRow>
</div>
</footer>
)
}
export default ThinFooter