Skip to content

Commit c578e0b

Browse files
authored
Merge pull request #22 from shrvansudhakara/feature/config-flags
Add configuration-based visibility control for sections and elements
2 parents b4e6b14 + 3a52e60 commit c578e0b

File tree

8 files changed

+194
-55
lines changed

8 files changed

+194
-55
lines changed

bun.lockb

403 Bytes
Binary file not shown.

config.yml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# Site Configuration - Control visibility of sections
2+
sections:
3+
about: true
4+
workExperience: true
5+
talks: true
6+
writing: true
7+
socialLinks: true
8+
9+
# Individual elements
10+
elements:
11+
avatar: true
12+
themeSwitch: true
13+
header: true
14+
footer: true

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,12 @@
1414
"@astrojs/sitemap": "3.6.0",
1515
"@tailwindcss/typography": "^0.5.19",
1616
"@tailwindcss/vite": "^4.1.16",
17+
"@types/js-yaml": "^4.0.9",
1718
"@types/react": "^18.3.3",
1819
"@types/react-dom": "^18.3.0",
1920
"astro": "5.15.2",
2021
"framer-motion": "^12.23.24",
22+
"js-yaml": "^4.1.0",
2123
"lucide-react": "^0.548.0",
2224
"mdast-util-to-string": "^4.0.0",
2325
"react": "^18.3.1",
Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
11
---
22
import Container from '@/components/Container.astro';
3+
import { getSiteConfig } from '@/lib/config';
4+
5+
const config = getSiteConfig();
36
---
47

5-
<Container as='footer' class='pt-24'>
6-
<p class="text-center text-muted-foreground text-sm">
7-
&copy; {new Date().getFullYear()}. Powered by <a href="https://astro.build" target="_blank" rel="noopener noreferrer">Astro</a> and CVfolio.
8-
</p>
9-
</Container>
8+
{config.elements.footer && (
9+
<Container as='footer' class='pt-24'>
10+
<p class="text-center text-muted-foreground text-sm">
11+
&copy; {new Date().getFullYear()}. Powered by <a href="https://astro.build" target="_blank" rel="noopener noreferrer">Astro</a> and CVfolio.
12+
</p>
13+
</Container>
14+
)}
Lines changed: 40 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,49 @@
11
---
22
import Container from '@/components/Container.astro';
3+
import { getSiteConfig } from '@/lib/config';
34
45
const pathname = Astro.url.pathname;
56
67
const isHomePage = pathname === '/';
78
const isWritingPage = pathname.startsWith('/writing');
8-
---
99
10-
<Container
11-
as="header"
12-
class="w-full max-w-full flex justify-center items-center"
13-
>
14-
<div
15-
class="w-max fixed top-0 mt-5 bg-muted-foreground/40 backdrop-blur-3xl border border-border rounded-full p-1"
10+
const config = getSiteConfig();
11+
---
12+
{config.elements.header && (
13+
<Container
14+
as="header"
15+
class="w-full max-w-full flex justify-center items-center"
1616
>
17-
<nav class="flex items-center">
18-
<ul class="flex items-center gap-1">
19-
<li>
20-
<a
21-
href="/"
22-
class:list={[
23-
'font-medium transition-colors block px-5 py-2',
24-
'hover:text-headings',
25-
isHomePage && 'text-headings bg-muted-foreground/40 rounded-full',
26-
]}>Home</a
27-
>
28-
</li>
29-
<li>
30-
<a
31-
href="/writing"
32-
class:list={[
33-
'font-medium transition-colors block px-5 py-2',
34-
'hover:text-headings',
35-
isWritingPage &&
36-
'text-headings bg-muted-foreground/40 rounded-full',
37-
]}>Writing</a
38-
>
39-
</li>
40-
</ul>
41-
</nav>
42-
</div>
43-
</Container>
17+
<div
18+
class="w-max fixed top-0 mt-5 bg-muted-foreground/40 backdrop-blur-3xl border border-border rounded-full p-1"
19+
>
20+
<nav class="flex items-center">
21+
<ul class="flex items-center gap-1">
22+
<li>
23+
<a
24+
href="/"
25+
class:list={[
26+
'font-medium transition-colors block px-5 py-2',
27+
'hover:text-headings',
28+
isHomePage && 'text-headings bg-muted-foreground/40 rounded-full',
29+
]}>Home</a
30+
>
31+
</li>
32+
{config.sections.writing && (
33+
<li>
34+
<a
35+
href="/writing"
36+
class:list={[
37+
'font-medium transition-colors block px-5 py-2',
38+
'hover:text-headings',
39+
isWritingPage &&
40+
'text-headings bg-muted-foreground/40 rounded-full',
41+
]}>Writing</a
42+
>
43+
</li>
44+
)}
45+
</ul>
46+
</nav>
47+
</div>
48+
</Container>
49+
)}

src/layouts/BaseLayout.astro

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,14 @@ import Header from '@/components/partials/Header.astro';
66
import Footer from '@/components/partials/Footer.astro';
77
import Head from '@/components/partials/Head.astro';
88
import SwitchTheme from '@/components/SwitchTheme.tsx';
9+
import { getSiteConfig } from '@/lib/config';
910
1011
interface Props {
1112
seo?: Seo
1213
}
1314
1415
const { seo } = Astro.props;
16+
const config = getSiteConfig();
1517
---
1618

1719
<html lang="en">
@@ -22,6 +24,8 @@ const { seo } = Astro.props;
2224
<slot />
2325
</main>
2426
<Footer />
25-
<SwitchTheme client:only="react" />
27+
{config.elements.themeSwitch && (
28+
<SwitchTheme client:only="react" />
29+
)}
2630
</body>
2731
</html>

src/lib/config.ts

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import fs from 'fs';
2+
import path from 'path';
3+
import yaml from 'js-yaml';
4+
import { z } from 'astro:content';
5+
6+
const SiteConfigSchema = z.object({
7+
sections: z.object({
8+
about: z.boolean(),
9+
workExperience: z.boolean(),
10+
talks: z.boolean(),
11+
writing: z.boolean(),
12+
socialLinks: z.boolean(),
13+
}),
14+
elements: z.object({
15+
avatar: z.boolean(),
16+
themeSwitch: z.boolean(),
17+
header: z.boolean(),
18+
footer: z.boolean(),
19+
}),
20+
});
21+
22+
type SiteConfig = z.infer<typeof SiteConfigSchema>;
23+
24+
const defaultConfig: SiteConfig = {
25+
sections: {
26+
about: true,
27+
workExperience: true,
28+
talks: true,
29+
writing: true,
30+
socialLinks: true,
31+
},
32+
elements: {
33+
avatar: true,
34+
themeSwitch: true,
35+
footer: true,
36+
header: true,
37+
},
38+
};
39+
40+
function deepMerge(target: any, source: any): any {
41+
// Recursively merge source into target
42+
for (const key of Object.keys(source)) {
43+
if (
44+
source[key] &&
45+
typeof source[key] === 'object' &&
46+
!Array.isArray(source[key])
47+
) {
48+
if (!target[key] || typeof target[key] !== 'object') {
49+
target[key] = {};
50+
}
51+
deepMerge(target[key], source[key]);
52+
} else {
53+
if (target[key] === undefined) {
54+
target[key] = source[key];
55+
}
56+
}
57+
}
58+
return target;
59+
}
60+
61+
function isValidConfig(config: SiteConfig): config is SiteConfig {
62+
return SiteConfigSchema.safeParse(config).success;
63+
}
64+
65+
export function getSiteConfig(): SiteConfig {
66+
const configPath = path.join(process.cwd(), 'config.yml');
67+
let loadedConfig: any = {};
68+
try {
69+
const fileContents = fs.readFileSync(configPath, 'utf8');
70+
loadedConfig = yaml.load(fileContents);
71+
if (!loadedConfig || typeof loadedConfig !== 'object') {
72+
console.warn(
73+
'config.yml is empty or not a valid YAML object, using defaults',
74+
);
75+
return defaultConfig;
76+
}
77+
// Merge loaded config with defaults
78+
const mergedConfig = deepMerge(loadedConfig, defaultConfig);
79+
if (!isValidConfig(mergedConfig)) {
80+
console.warn(
81+
'config.yml is malformed or missing required fields, using defaults',
82+
);
83+
return defaultConfig;
84+
}
85+
return mergedConfig as SiteConfig;
86+
} catch (error: any) {
87+
if (error.code === 'ENOENT') {
88+
console.warn('config.yml not found, using defaults');
89+
} else if (
90+
error.name === 'YAMLException' ||
91+
error instanceof yaml.YAMLException
92+
) {
93+
console.warn('config.yml is malformed YAML, using defaults');
94+
} else {
95+
console.warn(
96+
`Error loading config.yml: ${error.message}, using defaults`,
97+
);
98+
}
99+
return defaultConfig;
100+
}
101+
}

src/pages/index.astro

Lines changed: 22 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { DEFAULT_CONFIGURATION } from '@/lib/constants';
77
import WorkExperience from '@/components/ui/WorkExperience.astro';
88
import Talk from '@/components/ui/Talk.astro';
99
import { sortByDateRange, sortByYear } from '@/lib/utils';
10+
import { getSiteConfig } from '@/lib/config';
1011
1112
const entry = await getEntry('pages', 'homepage');
1213
@@ -23,25 +24,31 @@ const sortedJobs = sortByDateRange(jobs);
2324
2425
const talks = await getCollection('talks');
2526
const sortedTalks = sortByYear(talks);
27+
28+
const config = getSiteConfig();
2629
---
2730

2831
<BaseLayout seo={entry.data.seo}>
29-
<Container as="section" class="py-6">
30-
<Author {...DEFAULT_CONFIGURATION.author} />
31-
</Container>
32+
{config.elements.avatar && (
33+
<Container as="section" class="py-6">
34+
<Author {...DEFAULT_CONFIGURATION.author} />
35+
</Container>
36+
)}
3237

33-
<Container as="section" class="py-6">
34-
<div class="flex flex-col gap-6">
35-
<div class="flex items-center">
36-
<span class="text-headings">About</span>
37-
</div>
38-
<div class="prose dark:prose-invert">
39-
<Content />
38+
{config.sections.about && (
39+
<Container as="section" class="py-6">
40+
<div class="flex flex-col gap-6">
41+
<div class="flex items-center">
42+
<span class="text-headings">About</span>
43+
</div>
44+
<div class="prose dark:prose-invert">
45+
<Content />
46+
</div>
4047
</div>
41-
</div>
42-
</Container>
48+
</Container>
49+
)}
4350
{
44-
links.length > 0 && (
51+
config.sections.socialLinks && links.length > 0 && (
4552
<Container as="section" class="py-8">
4653
<div class="flex flex-col gap-5">
4754
<span class="text-headings">Contact</span>
@@ -69,7 +76,7 @@ const sortedTalks = sortByYear(talks);
6976
)
7077
}
7178
{
72-
sortedJobs.length > 0 && (
79+
config.sections.workExperience && sortedJobs.length > 0 && (
7380
<Container as="section" class="py-6">
7481
<div class="flex flex-col gap-5">
7582
<span class="text-headings">Work Experience</span>
@@ -83,7 +90,7 @@ const sortedTalks = sortByYear(talks);
8390
)
8491
}
8592
{
86-
talks.length > 0 && (
93+
config.sections.talks && sortedTalks.length > 0 && (
8794
<Container as="section" class="py-6">
8895
<div class="flex flex-col gap-5">
8996
<span class="text-headings">Speaking</span>

0 commit comments

Comments
 (0)