Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 13 additions & 3 deletions apps/web/content-collections.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ const articles = defineCollection({
display_title: z.string().optional(),
meta_title: z.string(),
meta_description: z.string(),
author: z.string(),
author: z.enum(["Harshika", "John Jeong", "Yujong Lee"]),
created: z.string(),
updated: z.string().optional(),
coverImage: z.string().optional(),
Expand Down Expand Up @@ -133,6 +133,7 @@ const docs = defineCollection({
exclude: ["AGENTS.md", "hooks/**", "deeplinks/**"],
schema: z.object({
title: z.string(),
section: z.string(),
summary: z.string().optional(),
category: z.string().optional(),
author: z.string().optional(),
Expand Down Expand Up @@ -163,16 +164,25 @@ const docs = defineCollection({

const sectionFolder = pathParts[0] || "general";

const slug = document._meta.path.replace(/\.mdx$/, "");

const isIndex = fileName === "index";

const orderMatch = fileName.match(/^(\d+)\./);
const order = orderMatch ? parseInt(orderMatch[1], 10) : 999;

const cleanFileName = fileName.replace(/^\d+\./, "");
const cleanPath =
pathParts.length > 0
? `${pathParts.join("/")}/${cleanFileName}`
: cleanFileName;
const slug = cleanPath;

return {
...document,
mdx,
slug,
sectionFolder,
isIndex,
order,
toc,
};
},
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
---
title: "Hello World!"
section: "About"
description: "We are making a world where work is simply talking to others and thinking deeply on your own — nothing else needs to be done."
---

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
---
title: "What is Hyprnote?"
section: "About"
description: "Hyprnote is a privacy-first AI notepad for meetings. Think of it as the open-source version of Granola — except that we're cooler."
---

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
---
title: "Why Local-First?"
section: "About"
description: "What if I said Excel beats Google Sheets? Not because it's Microsoft(lol), but because it's local-first."
---

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
---
title: "Why Self-Hosted?"
section: "About"
description: "Why the heck would you want to self-host Hyprnote? Because you can, and because it gives you control over your data and privacy."
---

Expand Down
4 changes: 0 additions & 4 deletions apps/web/content/docs/cli.mdx

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
---
title: Analytics
description: Learn about analytics in Hyprnote
title: "Analytics"
section: "Developers"
description: "Learn about analytics in Hyprnote"
---

## Analytics
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
---
title: "Bug Reports & Debugging"
section: "Developers"
description: "How to report bugs and find log files for troubleshooting Hyprnote"
---

Expand Down
5 changes: 5 additions & 0 deletions apps/web/content/docs/developers/2.cli.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
title: "CLI"
section: "Developers"
description: "Learn how to use CLI in Hyprnote"
---
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
---
title: Deeplinks
summary: Learn how to use deeplinks in Hyprnote
title: "Deeplinks"
section: "Developers"
summary: "Learn how to use deeplinks in Hyprnote"
---

# Overview
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
---
title: Hooks
description: Learn how to use hooks in Hyprnote
title: "Hooks"
section: "Developers"
description: "Learn how to use hooks in Hyprnote"
---

# Overview
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
---
title: Run
description: Learn how to run Hyprnote for development
title: "Run"
section: "Developers"
description: "Learn how to run Hyprnote for development"
---

# Development
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
---
title: Storage
description: Learn about storage in Hyprnote
title: "Storage"
section: "Developers"
description: "Learn about storage in Hyprnote"
---

# How Hyprnote stores data
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
---
title: Versioning
description: How Hyprnote handles versioning for desktop releases
title: "Versioning"
section: "Developers"
description: "How Hyprnote handles versioning for desktop releases"
---

# Overview
Expand Down
Empty file.
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
---
title: "License Activation"
section: "Pro"
description: "Learn how to activate Hyprnote Pro License"
---

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
---
title: "Better Transcription"
section: "Pro"
description: "Description of your new file."
---
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
---
title: "Cloud"
section: "Pro"
description: "Managed cloud service for pro users"
---

Expand Down
101 changes: 67 additions & 34 deletions apps/web/netlify/edge-functions/og.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,14 @@ const blogSchema = z.object({
type: z.literal("blog"),
title: z.string(),
description: z.string().optional(),
author: z.string(),
date: z.string(),
});

const docsSchema = z.object({
type: z.literal("docs"),
title: z.string(),
section: z.string(),
description: z.string().optional(),
});

Expand Down Expand Up @@ -55,16 +58,19 @@ function parseSearchParams(url: URL): z.infer<typeof OGSchema> | null {
if (type === "blog") {
const title = url.searchParams.get("title");
const description = url.searchParams.get("description") || undefined;
const author = url.searchParams.get("author") || undefined;
const date = url.searchParams.get("date") || undefined;

const result = OGSchema.safeParse({ type, title, description });
const result = OGSchema.safeParse({ type, title, description, author, date });
return result.success ? result.data : null;
}

if (type === "docs") {
const title = url.searchParams.get("title");
const section = url.searchParams.get("section");
const description = url.searchParams.get("description") || undefined;

const result = OGSchema.safeParse({ type, title, description });
const result = OGSchema.safeParse({ type, title, section, description });
return result.success ? result.data : null;
}

Expand Down Expand Up @@ -142,57 +148,76 @@ function renderChangelogTemplate(params: z.infer<typeof changelogSchema>) {

if (isNightly) {
return (
<div style={{width: '100%', height: '100%', position: 'relative', background: 'linear-gradient(180deg, #03BCF1 0%, #127FE5 100%), linear-gradient(0deg, #FAFAF9 0%, #E7E5E4 100%)', overflow: 'hidden', display: 'flex', flexDirection: 'column'}}>
<div style={{left: 56, top: 436, position: 'absolute', color: '#FAFAF9', fontSize: 60, fontFamily: 'Lora', fontWeight: '700', wordWrap: 'break-word', display: 'flex'}}>Changelog</div>
<div style={{left: 56, top: 513, position: 'absolute', color: '#F5F5F4', fontSize: 48, fontFamily: 'IBM Plex Mono', fontWeight: '400', wordWrap: 'break-word', display: 'flex'}}>v.{params.version}</div>
<div style={{left: 56.25, top: 61.12, position: 'absolute', color: '#F5F5F4', fontSize: 40, fontFamily: 'Lora', fontWeight: '400', wordWrap: 'break-word', display: 'flex'}}>The AI notepad for private meetings</div>
<div style={{left: 903, top: 55, position: 'absolute', textAlign: 'right', color: '#FAFAF9', fontSize: 50, fontFamily: 'Lora', fontWeight: '700', wordWrap: 'break-word', display: 'flex'}}>Hyprnote.</div>
<div style={{width: 140, height: 0, left: 755, top: 87, position: 'absolute', borderTop: '2px solid #F5F5F4'}}></div>
<img style={{width: 462, height: 462, right: 57, bottom: -69, position: 'absolute'}} src="https://ijoptyyjrfqwaqhyxkxj.supabase.co/storage/v1/object/public/public_images/icons/nightly-icon.png" />
<div style={{ width: '100%', height: '100%', position: 'relative', background: 'linear-gradient(180deg, #03BCF1 0%, #127FE5 100%), linear-gradient(0deg, #FAFAF9 0%, #E7E5E4 100%)', overflow: 'hidden', display: 'flex', flexDirection: 'column' }}>
<div style={{ left: 56, top: 436, position: 'absolute', color: '#FAFAF9', fontSize: 60, fontFamily: 'Lora', fontWeight: '700', wordWrap: 'break-word', display: 'flex' }}>Changelog</div>
<div style={{ left: 56, top: 513, position: 'absolute', color: '#F5F5F4', fontSize: 48, fontFamily: 'IBM Plex Mono', fontWeight: '400', wordWrap: 'break-word', display: 'flex' }}>v.{params.version}</div>
<div style={{ left: 56.25, top: 61.12, position: 'absolute', color: '#F5F5F4', fontSize: 40, fontFamily: 'Lora', fontWeight: '400', wordWrap: 'break-word', display: 'flex' }}>The AI notepad for private meetings</div>
<div style={{ left: 903, top: 55, position: 'absolute', textAlign: 'right', color: '#FAFAF9', fontSize: 50, fontFamily: 'Lora', fontWeight: '700', wordWrap: 'break-word', display: 'flex' }}>Hyprnote.</div>
<div style={{ width: 140, height: 0, left: 755, top: 87, position: 'absolute', borderTop: '2px solid #F5F5F4', display: 'flex' }}></div>
<img style={{ width: 462, height: 462, right: 57, bottom: -69, position: 'absolute' }} src="https://ijoptyyjrfqwaqhyxkxj.supabase.co/storage/v1/object/public/public_images/icons/nightly-icon.png" />
</div>
);
}

return (
<div style={{width: '100%', height: '100%', position: 'relative', background: 'linear-gradient(180deg, #A8A29E 0%, #57534E 100%)', overflow: 'hidden', display: 'flex', flexDirection: 'column'}}>
<div style={{left: 56, top: 436, position: 'absolute', color: '#FAFAF9', fontSize: 60, fontFamily: 'Lora', fontWeight: '700', wordWrap: 'break-word', display: 'flex'}}>Changelog</div>
<div style={{left: 56, top: 513, position: 'absolute', color: '#F5F5F4', fontSize: 48, fontFamily: 'IBM Plex Mono', fontWeight: '400', wordWrap: 'break-word', display: 'flex'}}>v.{params.version}</div>
<div style={{left: 56.25, top: 61.12, position: 'absolute', color: '#F5F5F4', fontSize: 40, fontFamily: 'Lora', fontWeight: '400', wordWrap: 'break-word', display: 'flex'}}>The AI notepad for private meetings</div>
<div style={{left: 903, top: 55, position: 'absolute', textAlign: 'right', color: '#FAFAF9', fontSize: 50, fontFamily: 'Lora', fontWeight: '700', wordWrap: 'break-word', display: 'flex'}}>Hyprnote.</div>
<div style={{width: 140, height: 0, left: 755, top: 87, position: 'absolute', borderTop: '2px solid #F5F5F4'}}></div>
<img style={{width: 462, height: 462, right: 57, bottom: -69, position: 'absolute'}} src="https://ijoptyyjrfqwaqhyxkxj.supabase.co/storage/v1/object/public/public_images/icons/stable-icon.png" />
<div style={{ width: '100%', height: '100%', position: 'relative', background: 'linear-gradient(180deg, #A8A29E 0%, #57534E 100%)', overflow: 'hidden', display: 'flex', flexDirection: 'column' }}>
<div style={{ left: 56, top: 436, position: 'absolute', color: '#FAFAF9', fontSize: 60, fontFamily: 'Lora', fontWeight: '700', wordWrap: 'break-word', display: 'flex' }}>Changelog</div>
<div style={{ left: 56, top: 513, position: 'absolute', color: '#F5F5F4', fontSize: 48, fontFamily: 'IBM Plex Mono', fontWeight: '400', wordWrap: 'break-word', display: 'flex' }}>v.{params.version}</div>
<div style={{ left: 56.25, top: 61.12, position: 'absolute', color: '#F5F5F4', fontSize: 40, fontFamily: 'Lora', fontWeight: '400', wordWrap: 'break-word', display: 'flex' }}>The AI notepad for private meetings</div>
<div style={{ left: 903, top: 55, position: 'absolute', textAlign: 'right', color: '#FAFAF9', fontSize: 50, fontFamily: 'Lora', fontWeight: '700', wordWrap: 'break-word', display: 'flex' }}>Hyprnote.</div>
<div style={{ width: 140, height: 0, left: 755, top: 87, position: 'absolute', borderTop: '2px solid #F5F5F4', display: 'flex' }}></div>
<img style={{ width: 462, height: 462, right: 57, bottom: -69, position: 'absolute' }} src="https://ijoptyyjrfqwaqhyxkxj.supabase.co/storage/v1/object/public/public_images/icons/stable-icon.png" />
</div>
);
}

function getAuthorAvatar(author: string): string {
const authorMap: Record<string, string> = {
"John Jeong": "https://ijoptyyjrfqwaqhyxkxj.supabase.co/storage/v1/object/public/public_images/team/john.png",
"Yujong Lee": "https://ijoptyyjrfqwaqhyxkxj.supabase.co/storage/v1/object/public/public_images/team/yujong.png",
};

return authorMap[author] || "https://ijoptyyjrfqwaqhyxkxj.supabase.co/storage/v1/object/public/public_images/icons/stable-icon.png";
}

function renderBlogTemplate(params: z.infer<typeof blogSchema>) {
const avatarUrl = getAuthorAvatar(params.author);

return (
<div style={{width: '100%', height: '100%', position: 'relative', background: 'linear-gradient(180deg, #A8A29E 0%, #57534E 100%)', overflow: 'hidden', display: 'flex', flexDirection: 'column'}}>
<div style={{left: 56, top: 380, position: 'absolute', color: '#FAFAF9', fontSize: 52, fontFamily: 'Lora', fontWeight: '700', wordWrap: 'break-word', display: 'flex', maxWidth: 700, lineHeight: 1.2}}>{preventWidow(params.title)}</div>
{params.description && (
<div style={{left: 56, top: 500, position: 'absolute', color: '#F5F5F4', fontSize: 28, fontFamily: 'Lora', fontWeight: '400', wordWrap: 'break-word', display: 'flex', maxWidth: 700, lineHeight: 1.4}}>{params.description}</div>
)}
<div style={{left: 56.25, top: 61.12, position: 'absolute', color: '#F5F5F4', fontSize: 40, fontFamily: 'Lora', fontWeight: '400', wordWrap: 'break-word', display: 'flex'}}>The AI notepad for private meetings</div>
<div style={{left: 903, top: 55, position: 'absolute', textAlign: 'right', color: '#FAFAF9', fontSize: 50, fontFamily: 'Lora', fontWeight: '700', wordWrap: 'break-word', display: 'flex'}}>Hyprnote.</div>
<div style={{width: 140, height: 0, left: 755, top: 87, position: 'absolute', borderTop: '2px solid #F5F5F4'}}></div>
<div style={{left: 56, top: 140, position: 'absolute', color: '#FAFAF9', fontSize: 24, fontFamily: 'IBM Plex Mono', fontWeight: '400', letterSpacing: 2, textTransform: 'uppercase', display: 'flex'}}>Blog</div>
<img style={{width: 380, height: 380, right: 40, bottom: -40, position: 'absolute'}} src="https://ijoptyyjrfqwaqhyxkxj.supabase.co/storage/v1/object/public/public_images/icons/stable-icon.png" />
<div style={{ width: '100%', height: '100%', padding: 60, background: 'linear-gradient(0deg, #FAFAF9 0%, #E7E5E4 100%)', display: 'flex', flexDirection: 'column', justifyContent: 'space-between' }}>
<div style={{ display: 'flex', flexDirection: 'column', gap: 24 }}>
<div style={{ width: '100%', color: 'black', fontSize: 60, fontFamily: 'Lora', fontWeight: '700', wordWrap: 'break-word' }}>{preventWidow(params.title)}</div>
<div style={{ display: 'flex', alignItems: 'center', gap: 16 }}>
<img style={{ width: 44, height: 44, borderRadius: 1000 }} src={avatarUrl} />
<div style={{ color: '#292524', fontSize: 28, fontFamily: 'Lora', fontWeight: '400', wordWrap: 'break-word' }}>{params.author}</div>
</div>
<div style={{ color: '#525252', fontSize: 24, fontFamily: 'Lora', fontWeight: '400', wordWrap: 'break-word' }}>{params.date}</div>
</div>
<div style={{ display: 'flex', flexDirection: 'column' }}>
<div style={{ color: '#525252', fontSize: 36, fontFamily: 'Lora', fontWeight: '400', wordWrap: 'break-word' }}>The AI notepad for private meetings</div>
<div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
<img style={{ width: 48, height: 48 }} src="https://ijoptyyjrfqwaqhyxkxj.supabase.co/storage/v1/object/public/public_images/icons/stable-icon.png" />
<div style={{ color: '#292524', fontSize: 48, fontFamily: 'Lora', fontWeight: '700', wordWrap: 'break-word' }}>Hyprnote.</div>
</div>
</div>
</div>
);
}

function renderDocsTemplate(params: z.infer<typeof docsSchema>) {
return (
<div style={{width: '100%', height: '100%', position: 'relative', background: 'linear-gradient(180deg, #A8A29E 0%, #57534E 100%)', overflow: 'hidden', display: 'flex', flexDirection: 'column'}}>
<div style={{left: 56, top: 380, position: 'absolute', color: '#FAFAF9', fontSize: 52, fontFamily: 'Lora', fontWeight: '700', wordWrap: 'break-word', display: 'flex', maxWidth: 700, lineHeight: 1.2}}>{preventWidow(params.title)}</div>
<div style={{ width: '100%', height: '100%', paddingLeft: 56, paddingRight: 56, paddingTop: 55, paddingBottom: 55, background: 'linear-gradient(0deg, #FAFAF9 0%, #E7E5E4 100%)', overflow: 'hidden', flexDirection: 'column', justifyContent: 'space-between', alignItems: 'flex-start', display: 'flex' }}>
<div style={{ justifyContent: 'flex-start', alignItems: 'center', gap: 12, display: 'flex' }}>
<img style={{ width: 48, height: 48 }} src="https://ijoptyyjrfqwaqhyxkxj.supabase.co/storage/v1/object/public/public_images/icons/stable-icon.png" />
<div style={{ color: '#292524', fontSize: 36, fontFamily: 'Lora', fontWeight: '700', wordWrap: 'break-word' }}>Hyprnote Docs</div>
</div>
<div style={{ alignSelf: 'stretch', flexDirection: 'column', justifyContent: 'flex-start', alignItems: 'flex-start', gap: 12, display: 'flex' }}>
<div style={{ color: '#525252', fontSize: 32, fontFamily: 'IBM Plex Mono', fontWeight: '500', wordWrap: 'break-word' }}>{params.section}</div>
<div style={{ alignSelf: 'stretch', color: '#292524', fontSize: 60, fontFamily: 'Lora', fontWeight: '700', wordWrap: 'break-word' }}>{preventWidow(params.title)}</div>
{params.description && (
<div style={{left: 56, top: 500, position: 'absolute', color: '#F5F5F4', fontSize: 28, fontFamily: 'Lora', fontWeight: '400', wordWrap: 'break-word', display: 'flex', maxWidth: 700, lineHeight: 1.4}}>{params.description}</div>
<div style={{ alignSelf: 'stretch', color: '#525252', fontSize: 36, fontFamily: 'IBM Plex Mono', fontWeight: '400', wordWrap: 'break-word' }}>{params.description}</div>
)}
<div style={{left: 56.25, top: 61.12, position: 'absolute', color: '#F5F5F4', fontSize: 40, fontFamily: 'Lora', fontWeight: '400', wordWrap: 'break-word', display: 'flex'}}>The AI notepad for private meetings</div>
<div style={{left: 903, top: 55, position: 'absolute', textAlign: 'right', color: '#FAFAF9', fontSize: 50, fontFamily: 'Lora', fontWeight: '700', wordWrap: 'break-word', display: 'flex'}}>Hyprnote.</div>
<div style={{width: 140, height: 0, left: 755, top: 87, position: 'absolute', borderTop: '2px solid #F5F5F4'}}></div>
<div style={{left: 56, top: 140, position: 'absolute', color: '#FAFAF9', fontSize: 24, fontFamily: 'IBM Plex Mono', fontWeight: '400', letterSpacing: 2, textTransform: 'uppercase', display: 'flex'}}>Documentation</div>
<img style={{width: 380, height: 380, right: 40, bottom: -40, position: 'absolute'}} src="https://ijoptyyjrfqwaqhyxkxj.supabase.co/storage/v1/object/public/public_images/icons/stable-icon.png" />
</div>
</div>
);
}
Expand Down Expand Up @@ -236,6 +261,14 @@ export default async function handler(req: Request) {
weight: 700 as const,
style: "normal" as const,
},
{
name: "Lora",
data: await fetch(
"https://fonts.gstatic.com/s/lora/v37/0QI6MX1D_JOuGQbT0gvTJPa787weuyJGmKxemMeZ.ttf"
).then((res) => res.arrayBuffer()),
weight: 400 as const,
style: "normal" as const,
},
{
name: "IBM Plex Mono",
data: await fetch(
Expand Down
Loading