Skip to content

Commit e214149

Browse files
committed
update homecloud site
1 parent 1290151 commit e214149

39 files changed

Lines changed: 1076 additions & 471 deletions

README.md

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,54 @@ Host the contents of this directory to cloud.
5656
- `/config/site.json` - website related settings.
5757
- `/config/projects.json` - List the projects hosted on github for which docs will be created.
5858

59+
## Project `site.config.json`
60+
61+
Each project repo can include a `site.config.json` at the root to customize how it appears on this site. The file is fetched automatically during `npm run fetch`. All fields are optional and will fall back to sensible defaults.
62+
63+
```jsonc
64+
{
65+
// Display
66+
"title": "My Project", // Display name (default: repo name)
67+
"tagline": "Short tagline", // One-liner shown on project card
68+
"description": "Longer description", // Full description
69+
"accentColor": "red", // Theme color (matches a file in /config/themes/)
70+
"iconPath": "img/icon.png", // Path to icon in the repo (downloaded & served locally)
71+
"webAppUrl": "https://app.example.com", // Link to a hosted web app
72+
73+
// Docs
74+
"docsPath": "docs", // Folder in the repo containing documentation (set to null to disable)
75+
76+
// Downloads
77+
"showDownloads": true, // Show download/release info on the project page
78+
79+
// Download links (shown on the download page)
80+
"downloadLinks": {
81+
"macAppStore": "https://apps.apple.com/app/...", // Mac App Store (desktop macOS section)
82+
"appStore": "https://apps.apple.com/app/...", // iOS App Store (mobile section)
83+
"playStore": "https://play.google.com/store/apps/...", // Google Play (mobile section)
84+
"msStore": "ms-windows-store://pdp/?productId=...", // Microsoft Store (desktop Windows section)
85+
"macosArm64": "https://github.com/.../file.zip", // Direct download for macOS Apple Silicon
86+
"macosX64": "https://github.com/.../file.zip", // Direct download for macOS Intel
87+
"windowsExe": "https://github.com/.../Setup.exe", // Direct download for Windows installer
88+
"linuxDeb": "https://github.com/.../file.deb" // Direct download for Linux .deb package
89+
}
90+
}
91+
```
92+
93+
Additionally, per-project overrides can be set in `/config/projects.json` without modifying the project repo:
94+
95+
```jsonc
96+
[
97+
{
98+
"repo": "owner/repo",
99+
"customDownloadPage": true, // Enable a dedicated /project/download page
100+
"customLanding": true, // Use a custom landing page component (implies full-width layout)
101+
"showDownloads": true
102+
},
103+
"owner/other-repo" // Plain string for projects with no overrides
104+
]
105+
```
106+
59107
# Notes
60108

61109
- `blog-starter` uses [Tailwind CSS](https://tailwindcss.com) [(v3.0)](https://tailwindcss.com/blog/tailwindcss-v3).

components/docs-grid.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,21 @@ import type RouteItem from '../interfaces/routeItem'
22
import { themed } from '../lib/utils'
33
import Link from 'next/link'
44
import SectionTitle from './section-title'
5+
import cn from 'classnames';
56

67
type Props = {
78
routes: RouteItem[],
89
theme: string,
910
title?: string
11+
className?: string
1012
}
1113

12-
const DocsGrid = ({ routes, theme, title }: Props) => {
14+
const DocsGrid = ({ routes, theme, title, className }: Props) => {
1315

1416
const thm = themed(theme);
1517

1618
return (
17-
<section className='pt-12 mx-auto max-w-5xl'>
19+
<section className={cn('pt-12 mx-auto max-w-5xl', className)}>
1820
<hr className='mb-10' />
1921
<SectionTitle>
2022
{title || 'Read next'}

components/header.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ const Header = ({ theme, project, docsConfig, style }: {
144144
links = [{
145145
name: 'Download',
146146
url: downloadUrl(project),
147-
external: true,
147+
external: !project.customDownloadPage,
148148
}, ...links];
149149
}
150150
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import Container from '../container'
2+
import HCDownload from './hc-download'
3+
import ProjectConfigType from '../../interfaces/projectConfig'
4+
5+
const HCDownloadPage = ({ project }: { project: ProjectConfigType }) => {
6+
return (
7+
<section className="py-16 md:py-24 px-4">
8+
<Container className="max-w-7xl">
9+
<HCDownload project={project} title={`Download ${project.name}`} />
10+
</Container>
11+
</section>
12+
)
13+
}
14+
15+
export default HCDownloadPage
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
import { useState, useMemo, useEffect } from "react";
2+
import Image from "next/image";
3+
import Link from "next/link";
4+
import cn from "classnames";
5+
import ProjectConfigType from "../../interfaces/projectConfig";
6+
import { detectPlatform } from "../../lib/utils";
7+
import { getDownloadData, desktopPlatforms, type DesktopPlatform } from "../../lib/downloads";
8+
9+
// --- Store badge button ---
10+
function StoreBadge({ url, icon, alt, className }: { url: string; icon: string; alt?: string; className?: string }) {
11+
return (
12+
<Link href={url} target="_blank" rel="noopener noreferrer"
13+
className={cn("inline-block hover:opacity-80 transition-opacity", className)}>
14+
<Image src={icon} width={0} height={0} sizes="100vw" alt={alt || ''} className="h-[40px] w-auto" />
15+
</Link>
16+
);
17+
}
18+
19+
// --- Download link button ---
20+
function DownloadButton({ label, url, primary }: { label: string; url: string; primary?: boolean }) {
21+
return (
22+
<Link href={url} target="_blank" rel="noopener noreferrer"
23+
className={cn(
24+
"inline-flex items-center gap-2 px-5 py-2.5 rounded-full text-sm font-medium transition-colors",
25+
primary
26+
? "bg-neutral-900 dark:bg-white text-white dark:text-neutral-900 hover:opacity-90"
27+
: "border border-neutral-300 dark:border-neutral-600 hover:bg-neutral-100 dark:hover:bg-neutral-800"
28+
)}>
29+
{label}
30+
</Link>
31+
);
32+
}
33+
34+
const HCDownload = ({ project, title }: { project: ProjectConfigType; title: string }) => {
35+
const [desktopTab, setDesktopTab] = useState<DesktopPlatform>('macOS');
36+
const [mobileFirst, setMobileFirst] = useState(false);
37+
const [userArch, setUserArch] = useState<'arm64' | 'x64' | 'unknown'>('unknown');
38+
const { desktop, mobile, hasDesktop, hasMobile } = useMemo(() => getDownloadData(project.downloadLinks || {}), [project]);
39+
const currentDesktop = desktop[desktopTab];
40+
41+
useEffect(() => {
42+
const { os, isMobile, arch } = detectPlatform();
43+
setUserArch(arch);
44+
if (isMobile) {
45+
setMobileFirst(true);
46+
}
47+
if (os === 'Windows') setDesktopTab('Windows');
48+
else if (os === 'Linux') setDesktopTab('Linux');
49+
// macOS is already the default
50+
}, []);
51+
52+
const desktopSection = hasDesktop && (
53+
<div>
54+
<div className="flex flex-col-reverse sm:flex-row sm:items-center justify-between gap-3 mb-6">
55+
<h3 className="text-lg font-medium">Download for {desktopTab}</h3>
56+
<div className="flex gap-4 self-start border-b border-neutral-200 dark:border-neutral-700">
57+
{desktopPlatforms.map((tab) => (
58+
<button
59+
key={tab}
60+
onClick={() => setDesktopTab(tab)}
61+
className={cn(
62+
"pb-2 text-sm font-medium transition-colors border-b-2 -mb-px",
63+
desktopTab === tab
64+
? "border-neutral-900 dark:border-white text-neutral-900 dark:text-white"
65+
: "border-transparent text-neutral-500 hover:text-neutral-900 dark:hover:text-neutral-200"
66+
)}
67+
>
68+
{tab}
69+
</button>
70+
))}
71+
</div>
72+
</div>
73+
74+
<div className="flex flex-wrap items-center gap-3 min-h-[48px]">
75+
{currentDesktop.store || currentDesktop.assets.length > 0 ? (
76+
<>
77+
{currentDesktop.store && (
78+
<StoreBadge
79+
url={currentDesktop.store.url}
80+
icon={currentDesktop.store.icon}
81+
alt={currentDesktop.store.label}
82+
/>
83+
)}
84+
{currentDesktop.assets.map((asset, i) => {
85+
const isPrimary = asset.arch && userArch !== 'unknown'
86+
? asset.arch === userArch
87+
: !currentDesktop.store && i === 0;
88+
return (
89+
<DownloadButton
90+
key={i}
91+
label={asset.label}
92+
url={asset.url}
93+
primary={isPrimary}
94+
/>
95+
);
96+
})}
97+
</>
98+
) : (
99+
<p className="text-sm text-neutral-500 dark:text-neutral-400">Coming soon</p>
100+
)}
101+
</div>
102+
</div>
103+
);
104+
105+
const mobileSection = hasMobile && (
106+
<div>
107+
<h3 className="text-lg font-medium mb-5">Download for mobile</h3>
108+
<div className="flex flex-wrap gap-3">
109+
{mobile.map((store, i) => (
110+
<StoreBadge
111+
key={i}
112+
url={store.url}
113+
icon={store.icon}
114+
alt={store.label}
115+
/>
116+
))}
117+
</div>
118+
</div>
119+
);
120+
121+
const separator = hasDesktop && hasMobile && (
122+
<hr className="my-8 border-neutral-200 dark:border-neutral-700" />
123+
);
124+
125+
return (
126+
<div>
127+
<h2 className="text-3xl sm:text-4xl md:text-5xl font-normal mb-3">
128+
{title}
129+
</h2>
130+
<p className="text-base text-neutral-600 dark:text-neutral-400 max-w-4xl leading-relaxed mb-10">
131+
It&apos;s easy to set up, install the {project.name} app on all your devices and login with the same email account, all your logged-in devices will automatically start showing up everywhere. {project.name} is available for all major platforms so that none of your devices are left out.
132+
</p>
133+
134+
{/* Downloads card */}
135+
<div className="md:rounded-2xl md:border md:border-neutral-200 md:dark:border-neutral-700 md:p-8">
136+
{mobileFirst ? (
137+
<>
138+
{mobileSection}
139+
{separator}
140+
{desktopSection}
141+
</>
142+
) : (
143+
<>
144+
{desktopSection}
145+
{separator}
146+
{mobileSection}
147+
</>
148+
)}
149+
</div>
150+
</div>
151+
);
152+
};
153+
154+
export default HCDownload;

0 commit comments

Comments
 (0)