Merge branch 'main' into fix-2
This commit is contained in:
@@ -3,17 +3,19 @@ const nextConfig = {
|
|||||||
images: {
|
images: {
|
||||||
remotePatterns: [
|
remotePatterns: [
|
||||||
{
|
{
|
||||||
protocol: 'https',
|
protocol: "https",
|
||||||
hostname: 'raw.githubusercontent.com',
|
hostname: "raw.githubusercontent.com",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
experimental: {
|
experimental: {
|
||||||
serverActions: {
|
serverActions: {
|
||||||
// edit: updated to new key. Was previously `allowedForwardedHosts`
|
// edit: updated to new key. Was previously `allowedForwardedHosts`
|
||||||
allowedOrigins: ['localhost:3000', 'get-zen.vercel.app'],
|
allowedOrigins: ["localhost:3000", "get-zen.vercel.app"],
|
||||||
},
|
},
|
||||||
|
},
|
||||||
|
compiler: {
|
||||||
|
styledComponents: true,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
31
package-lock.json
generated
31
package-lock.json
generated
@@ -27,6 +27,7 @@
|
|||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"cobe": "^0.6.3",
|
"cobe": "^0.6.3",
|
||||||
"dotenv": "^16.4.5",
|
"dotenv": "^16.4.5",
|
||||||
|
"feed": "^4.2.2",
|
||||||
"framer-motion": "^11.3.24",
|
"framer-motion": "^11.3.24",
|
||||||
"lucide-react": "^0.400.0",
|
"lucide-react": "^0.400.0",
|
||||||
"next": "14.2.4",
|
"next": "14.2.4",
|
||||||
@@ -7780,6 +7781,18 @@
|
|||||||
"bser": "2.1.1"
|
"bser": "2.1.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/feed": {
|
||||||
|
"version": "4.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/feed/-/feed-4.2.2.tgz",
|
||||||
|
"integrity": "sha512-u5/sxGfiMfZNtJ3OvQpXcvotFpYkL0n9u9mM2vkui2nGo8b4wvDkJ8gAkYqbA8QpGyFCv3RK0Z+Iv+9veCS9bQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"xml-js": "^1.6.11"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/file-entry-cache": {
|
"node_modules/file-entry-cache": {
|
||||||
"version": "6.0.1",
|
"version": "6.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
|
||||||
@@ -13396,6 +13409,12 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/sax": {
|
||||||
|
"version": "1.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz",
|
||||||
|
"integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==",
|
||||||
|
"license": "ISC"
|
||||||
|
},
|
||||||
"node_modules/scheduler": {
|
"node_modules/scheduler": {
|
||||||
"version": "0.23.2",
|
"version": "0.23.2",
|
||||||
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
|
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
|
||||||
@@ -15453,6 +15472,18 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/xml-js": {
|
||||||
|
"version": "1.6.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/xml-js/-/xml-js-1.6.11.tgz",
|
||||||
|
"integrity": "sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"sax": "^1.2.4"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"xml-js": "bin/cli.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/xtend": {
|
"node_modules/xtend": {
|
||||||
"version": "4.0.2",
|
"version": "4.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
|
||||||
|
|||||||
@@ -31,6 +31,7 @@
|
|||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"cobe": "^0.6.3",
|
"cobe": "^0.6.3",
|
||||||
"dotenv": "^16.4.5",
|
"dotenv": "^16.4.5",
|
||||||
|
"feed": "^4.2.2",
|
||||||
"framer-motion": "^11.3.24",
|
"framer-motion": "^11.3.24",
|
||||||
"lucide-react": "^0.400.0",
|
"lucide-react": "^0.400.0",
|
||||||
"next": "14.2.4",
|
"next": "14.2.4",
|
||||||
|
|||||||
110
src/app/feed.xml/route.ts
Normal file
110
src/app/feed.xml/route.ts
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
import { Feed } from "feed";
|
||||||
|
import { releaseNotes } from "@/lib/release-notes";
|
||||||
|
import type { ReleaseNote } from "@/lib/release-notes";
|
||||||
|
|
||||||
|
// Force feed.xml to be cached as static and remain constant for the lifetime of the current site build.
|
||||||
|
// The supplied releaseNotes array is constant per build, so this will always be the latest release notes.
|
||||||
|
export const dynamic = "force-static";
|
||||||
|
|
||||||
|
/** The default number of entries to include in the RSS feed. */
|
||||||
|
const RSS_ENTRY_LIMIT = 20;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the GET request for the `feed.xml` endpoint.
|
||||||
|
* @returns The RSS feed for the Zen Browser release notes.
|
||||||
|
*/
|
||||||
|
export async function GET() {
|
||||||
|
// Just in case the release notes array is empty for whatever reason.
|
||||||
|
const latestDate = releaseNotes.length > 0
|
||||||
|
? formatRssDate(releaseNotes[0].date)
|
||||||
|
: new Date();
|
||||||
|
|
||||||
|
const feed = new Feed({
|
||||||
|
id: "https://www.zen-browser.app/release-notes",
|
||||||
|
link: "https://www.zen-browser.app/release-notes",
|
||||||
|
title: "Zen Browser Release Notes",
|
||||||
|
description: "Release Notes for the Zen Browser",
|
||||||
|
language: "en",
|
||||||
|
favicon: "https://www.zen-browser.app/favicon.ico",
|
||||||
|
copyright: `Zen Browser © ${new Date().getFullYear()} - Made with ❤️ by the Zen team.`,
|
||||||
|
updated: latestDate,
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const releaseNote of releaseNotes.slice(0, RSS_ENTRY_LIMIT)) {
|
||||||
|
feed.addItem({
|
||||||
|
title: `Release notes for version ${releaseNote.version}`,
|
||||||
|
id: `https://www.zen-browser.app/release-notes/${releaseNote.version}`,
|
||||||
|
link: `https://www.zen-browser.app/release-notes/${releaseNote.version}`,
|
||||||
|
date: formatRssDate(releaseNote.date),
|
||||||
|
description: releaseNote.extra,
|
||||||
|
content: formatReleaseNote(releaseNote),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Response(feed.rss2(), {
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/xml; charset=utf-8',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Formats a date string in the format day/month/year.
|
||||||
|
*
|
||||||
|
* Note: If release notes change to ISO format, this will need to be updated.
|
||||||
|
* @param dateStr The date string to format.
|
||||||
|
* @returns The passed in date string as a Date object.
|
||||||
|
*/
|
||||||
|
function formatRssDate(dateStr: string) {
|
||||||
|
const splitDate = dateStr.split("/");
|
||||||
|
if (splitDate.length !== 3) {
|
||||||
|
throw new Error("Invalid date format");
|
||||||
|
}
|
||||||
|
|
||||||
|
const day = Number(splitDate[0]);
|
||||||
|
const month = Number(splitDate[1]) - 1;
|
||||||
|
const year = Number(splitDate[2]);
|
||||||
|
return new Date(year, month, day);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Formats the release note entry for use as the content of the RSS feed.
|
||||||
|
* @param releaseNote The release note to format.
|
||||||
|
* @returns The formatted release note as a HTML string.
|
||||||
|
*/
|
||||||
|
function formatReleaseNote(releaseNote: ReleaseNote) {
|
||||||
|
let content = "<p>If you encounter any issues, please report them on <a href=\"https://github.com/zen-browser/desktop/issues/\">the issues page</a>. Thanks everyone for your feedback! ❤️</p>";
|
||||||
|
|
||||||
|
if (releaseNote.extra) {
|
||||||
|
content += `<p>${releaseNote.extra.replace(/(\n)/g, "<br />")}</p>`
|
||||||
|
}
|
||||||
|
|
||||||
|
if (releaseNote.breakingChanges) {
|
||||||
|
content += `<h2>⚠️ Breaking changes</h2>`
|
||||||
|
content += `<ul>`
|
||||||
|
for (const breakingChange of releaseNote.breakingChanges) {
|
||||||
|
content += `<li>${breakingChange}</li>`
|
||||||
|
}
|
||||||
|
content += `</ul>`
|
||||||
|
}
|
||||||
|
|
||||||
|
if (releaseNote.features) {
|
||||||
|
content += `<h2>⭐ Features</h2>`
|
||||||
|
content += `<ul>`
|
||||||
|
for (const feature of releaseNote.features) {
|
||||||
|
content += `<li>${feature}</li>`
|
||||||
|
}
|
||||||
|
content += `</ul>`
|
||||||
|
}
|
||||||
|
|
||||||
|
if (releaseNote.fixes) {
|
||||||
|
content += `<h2>✓ Fixes</h2>`
|
||||||
|
content += `<ul>`
|
||||||
|
for (const fix of releaseNote.fixes) {
|
||||||
|
content += `<li>${fix.description}</li>`
|
||||||
|
}
|
||||||
|
content += `</ul>`
|
||||||
|
}
|
||||||
|
|
||||||
|
return content;
|
||||||
|
}
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
import type { Metadata } from "next";
|
import type { Metadata } from "next";
|
||||||
import { Inter } from "next/font/google";
|
import { Inter } from "next/font/google";
|
||||||
import "./globals.css";
|
import "./globals.css";
|
||||||
import { ThemeProvider } from "@/components/theme-provider"
|
import { ThemeProvider } from "@/components/theme-provider";
|
||||||
|
import StyledComponentsRegistry from "@/lib/styled-components-registry";
|
||||||
|
|
||||||
const inter = Inter({ subsets: ["latin"] });
|
const inter = Inter({ subsets: ["latin"] });
|
||||||
|
|
||||||
@@ -18,6 +19,10 @@ export default function RootLayout({
|
|||||||
}>) {
|
}>) {
|
||||||
return (
|
return (
|
||||||
<html lang="en" suppressHydrationWarning>
|
<html lang="en" suppressHydrationWarning>
|
||||||
|
<head>
|
||||||
|
<link rel="me" href="https://fosstodon.org/@zenbrowser"></link>
|
||||||
|
<link rel="alternate" type="application/rss+xml" title="Zen Browser Release Notes" href="https://www.zen-browser.app/feed.xml" />
|
||||||
|
</head>
|
||||||
<body className={inter.className}>
|
<body className={inter.className}>
|
||||||
<ThemeProvider
|
<ThemeProvider
|
||||||
attribute="class"
|
attribute="class"
|
||||||
@@ -25,7 +30,7 @@ export default function RootLayout({
|
|||||||
enableSystem
|
enableSystem
|
||||||
disableTransitionOnChange
|
disableTransitionOnChange
|
||||||
>
|
>
|
||||||
{children}
|
<StyledComponentsRegistry>{children}</StyledComponentsRegistry>
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ Zen Browser offers a "Sync" feature, this is implemented using Mozilla Firefox's
|
|||||||
# 4. Data Security
|
# 4. Data Security
|
||||||
Although Zen Browser does not collect your data, we are committed to protecting the information that is stored locally on your device and, if you use the Sync feature, the encrypted data stored on Mozilla's servers. We recommend that you use secure passwords, enable device encryption, and regularly update your software to ensure your data remains safe.
|
Although Zen Browser does not collect your data, we are committed to protecting the information that is stored locally on your device and, if you use the Sync feature, the encrypted data stored on Mozilla's servers. We recommend that you use secure passwords, enable device encryption, and regularly update your software to ensure your data remains safe.
|
||||||
|
|
||||||
* Note that most of the security measures are taken care by mozilla firefox.
|
* Note that most of the security measures are taken care by Mozilla Firefox.
|
||||||
|
|
||||||
# 5. Your Control
|
# 5. Your Control
|
||||||
## 5.1. Data Deletion
|
## 5.1. Data Deletion
|
||||||
|
|||||||
@@ -1,22 +1,45 @@
|
|||||||
"use client";
|
|
||||||
import Footer from "@/components/footer";
|
import Footer from "@/components/footer";
|
||||||
import { Navigation } from "@/components/navigation";
|
import { Navigation } from "@/components/navigation";
|
||||||
import ThemePage from "@/components/theme-page";
|
import ThemePage from "@/components/theme-page";
|
||||||
import { getThemeFromId } from "@/lib/themes";
|
import { getThemeFromId } from "@/lib/themes";
|
||||||
import { useParams } from "next/navigation";
|
import { Metadata, ResolvingMetadata } from "next";
|
||||||
|
|
||||||
export default async function ThemeInfoPage() {
|
export async function generateMetadata(
|
||||||
const params = useParams<{ theme: string }>();
|
{ params, searchParams }: any,
|
||||||
const { theme: themeID } = params;
|
parent: ResolvingMetadata
|
||||||
|
): Promise<Metadata> {
|
||||||
const theme = await getThemeFromId(themeID);
|
const theme = params.theme
|
||||||
if (!theme) {
|
const themeData = await getThemeFromId(theme);
|
||||||
return <div>Theme not found</div>;
|
if (!themeData) {
|
||||||
|
return {
|
||||||
|
title: "Theme not found",
|
||||||
|
description: "Theme not found",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
title: themeData.name,
|
||||||
|
description: themeData.description,
|
||||||
|
keywords: [themeData.name, themeData.description],
|
||||||
|
openGraph: {
|
||||||
|
title: themeData.name,
|
||||||
|
description: themeData.description,
|
||||||
|
images: [
|
||||||
|
{
|
||||||
|
url: themeData.image,
|
||||||
|
width: 500,
|
||||||
|
height: 500,
|
||||||
|
alt: themeData.name,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default async function ThemeInfoPage() {
|
||||||
return (
|
return (
|
||||||
<main className="flex min-h-screen flex-col items-center justify-start">
|
<main className="flex min-h-screen flex-col items-center justify-start">
|
||||||
<ThemePage theme={theme} />
|
<ThemePage />
|
||||||
<Footer />
|
<Footer />
|
||||||
<Navigation /> {/* At the bottom of the page */}
|
<Navigation /> {/* At the bottom of the page */}
|
||||||
</main>
|
</main>
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
|
||||||
import { LOGO_COLORS } from "@/lib/logos";
|
import { LOGO_COLORS } from "@/lib/logos";
|
||||||
|
|
||||||
export function BrandingAssets() {
|
export function BrandingAssets() {
|
||||||
@@ -19,6 +20,7 @@ export function BrandingAssets() {
|
|||||||
<div className="flex items-center my-2">
|
<div className="flex items-center my-2">
|
||||||
<a
|
<a
|
||||||
href={`/logos/zen-${color}.svg`}
|
href={`/logos/zen-${color}.svg`}
|
||||||
|
download={`zen-${color}.svg`}
|
||||||
className="text-blue-500 text-md ml-2"
|
className="text-blue-500 text-md ml-2"
|
||||||
>
|
>
|
||||||
{color}
|
{color}
|
||||||
@@ -40,7 +42,7 @@ export function BrandingAssets() {
|
|||||||
<div className="flex items-center my-2">
|
<div className="flex items-center my-2">
|
||||||
<a
|
<a
|
||||||
href={`/logos/zen-alpha-${color}.svg`}
|
href={`/logos/zen-alpha-${color}.svg`}
|
||||||
download={`zen-alpha-${color}.png`}
|
download={`zen-alpha-${color}.svg`}
|
||||||
className="text-blue-500 text-md ml-2"
|
className="text-blue-500 text-md ml-2"
|
||||||
>
|
>
|
||||||
{color}
|
{color}
|
||||||
|
|||||||
@@ -382,7 +382,7 @@ export default function DownloadPage() {
|
|||||||
<h1 className="text-5xl my-2 opacity-40 dark:opacity-20">
|
<h1 className="text-5xl my-2 opacity-40 dark:opacity-20">
|
||||||
🍏
|
🍏
|
||||||
</h1>
|
</h1>
|
||||||
<h1 className="text-2xl font-semibold my-2">aarch64</h1>
|
<h1 className="text-2xl font-semibold my-2">AArch64</h1>
|
||||||
<p className="text-muted-foreground mx-auto text-center">64-bit ARM architecture, for Apple's M Series Chips</p>
|
<p className="text-muted-foreground mx-auto text-center">64-bit ARM architecture, for Apple's M Series Chips</p>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ export default function Features() {
|
|||||||
return () => clearInterval(interval);
|
return () => clearInterval(interval);
|
||||||
});
|
});
|
||||||
return (
|
return (
|
||||||
<div className="relative my-32 mx-auto md:border-2 rounded-md md:w-full xl:w-4/5 2xl:w-3/5">
|
<div className="max-w-[1300px] relative my-32 mx-auto md:border-2 rounded-md md:w-full xl:w-4/5 2xl:w-3/5">
|
||||||
<div className="grid grid-cols-1 md:grid-cols-3 md:grid-rows-10 w-full">
|
<div className="grid grid-cols-1 md:grid-cols-3 md:grid-rows-10 w-full">
|
||||||
<div className="items-center justify-center flex flex-col p-16">
|
<div className="items-center justify-center flex flex-col p-16">
|
||||||
<div className="rounded-full px-8 py-3 shadow border-2 flex items-center justify-center">
|
<div className="rounded-full px-8 py-3 shadow border-2 flex items-center justify-center">
|
||||||
@@ -102,7 +102,7 @@ export default function Features() {
|
|||||||
Secure by default
|
Secure by default
|
||||||
</h2>
|
</h2>
|
||||||
<p className="text-center text-sm mt-2 text-muted-foreground">
|
<p className="text-center text-sm mt-2 text-muted-foreground">
|
||||||
We are always using the latest security features from firefox to
|
We are always using the latest security features from Firefox to
|
||||||
keep you safe. <a className="text-blue-500" href="https://docs.zen-browser.app/faq#how-do-i-know-zen-is-safe">Learn more</a>
|
keep you safe. <a className="text-blue-500" href="https://docs.zen-browser.app/faq#how-do-i-know-zen-is-safe">Learn more</a>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -118,8 +118,6 @@ export default function Features() {
|
|||||||
</Sticky>
|
</Sticky>
|
||||||
</div>
|
</div>
|
||||||
<div className="relative md:grid border-t-2 md:border-l-2 md:col-span-2 md:grid-cols-2 row-span-2">
|
<div className="relative md:grid border-t-2 md:border-l-2 md:col-span-2 md:grid-cols-2 row-span-2">
|
||||||
<div className="w-1/2 absolute md:relative z-[-1] opacity-50 md:opacity-1 md:w-full h-full border-r-2 md:border-r"></div>
|
|
||||||
<div className="w-1/2 absolute md:relative z-[-1] hidden md:block md:w-full h-full border-l"></div>
|
|
||||||
<div className="p-16 md:px-32 h-full md:absolute top-0 left-0 flex flex-col">
|
<div className="p-16 md:px-32 h-full md:absolute top-0 left-0 flex flex-col">
|
||||||
<div className="">
|
<div className="">
|
||||||
<div>
|
<div>
|
||||||
@@ -149,8 +147,6 @@ export default function Features() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="relative md:grid md:border-l-2 border-t-2 md:col-span-2 grid-cols-2 row-span-2">
|
<div className="relative md:grid md:border-l-2 border-t-2 md:col-span-2 grid-cols-2 row-span-2">
|
||||||
<div className="w-1/2 absolute md:relative z-[-1] opacity-50 md:opacity-1 md:w-full h-full border-r-2 md:border-r"></div>
|
|
||||||
<div className="w-1/2 absolute md:relative z-[-1] hidden md:block md:w-full h-full border-l"></div>
|
|
||||||
<div className="p-16 md:px-32 h-full md:absolute top-0 left-0 flex flex-col">
|
<div className="p-16 md:px-32 h-full md:absolute top-0 left-0 flex flex-col">
|
||||||
<div className="flex flex-col md:flex-row">
|
<div className="flex flex-col md:flex-row">
|
||||||
<div className='relative'>
|
<div className='relative'>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { getThemeAuthorLink, ZenTheme } from "@/lib/themes";
|
|||||||
import styled from "styled-components";
|
import styled from "styled-components";
|
||||||
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger } from "./ui/dialog";import { Button } from "./ui/button";
|
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger } from "./ui/dialog";import { Button } from "./ui/button";
|
||||||
|
|
||||||
const ThemeCardWrapepr = styled.div`
|
const ThemeCardWrapper = styled.div`
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export default function ThemeCard({
|
export default function ThemeCard({
|
||||||
@@ -12,7 +12,7 @@ export default function ThemeCard({
|
|||||||
theme: ZenTheme;
|
theme: ZenTheme;
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<ThemeCardWrapepr onClick={(event) => {
|
<ThemeCardWrapper onClick={(event) => {
|
||||||
if (event.target instanceof HTMLAnchorElement) return;
|
if (event.target instanceof HTMLAnchorElement) return;
|
||||||
window.open(`/themes/${theme.id}`, "_self");
|
window.open(`/themes/${theme.id}`, "_self");
|
||||||
}} className="flex flex-col justify-start p-5 rounded-lg shadow-sm bg-muted dark:bg-muted/50 border border-grey-900 dark:border-muted w-full hover:shadow-lg transition duration-300 ease-in-out hover:bg-muted/100 hover:border-blue-500 cursor-pointer select-none ">
|
}} className="flex flex-col justify-start p-5 rounded-lg shadow-sm bg-muted dark:bg-muted/50 border border-grey-900 dark:border-muted w-full hover:shadow-lg transition duration-300 ease-in-out hover:bg-muted/100 hover:border-blue-500 cursor-pointer select-none ">
|
||||||
@@ -35,6 +35,6 @@ export default function ThemeCard({
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-md mt-2 overflow-ellipsis text-muted-foreground text-start">{theme.description}</p>
|
<p className="text-md mt-2 overflow-ellipsis text-muted-foreground text-start">{theme.description}</p>
|
||||||
</ThemeCardWrapepr>
|
</ThemeCardWrapper>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,20 +1,31 @@
|
|||||||
|
"use client";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import { getThemeAuthorLink, getThemeMarkdown, ZenTheme } from "@/lib/themes";
|
import { getThemeAuthorLink, getThemeFromId, getThemeMarkdown, ZenTheme } from "@/lib/themes";
|
||||||
import { Button } from "./ui/button";
|
import { Button } from "./ui/button";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import Markdown from "react-markdown";
|
import Markdown from "react-markdown";
|
||||||
import '../app/privacy-policy/markdown.css';
|
import '../app/privacy-policy/markdown.css';
|
||||||
import { ChevronLeft, LoaderCircleIcon, LoaderIcon, LoaderPinwheelIcon, MoveLeftIcon } from "lucide-react";
|
import { ChevronLeft, LoaderCircleIcon } from "lucide-react";
|
||||||
|
import { useParams } from "next/navigation";
|
||||||
|
|
||||||
export default function ThemePage({ theme }: { theme: ZenTheme }) {
|
export default async function ThemePage() {
|
||||||
const [readme, setReadme] = useState<string | null>(null);
|
const params = useParams<{ theme: string }>();
|
||||||
useEffect(() => {
|
const { theme: themeID } = params;
|
||||||
getThemeMarkdown(theme).then(setReadme);
|
|
||||||
}, [theme]);
|
const theme = await getThemeFromId(themeID);
|
||||||
|
if (!theme) {
|
||||||
|
return <div>Theme not found</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const readme = await getThemeMarkdown(theme);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mt-24 lg:mt-56 flex-col lg:flex-row flex mx-auto items-start relative">
|
<div className="mt-24 lg:mt-56 flex-col lg:flex-row flex mx-auto items-start relative">
|
||||||
<div className="flex flex-col relative lg:fixed w-md h-full p-5 lg:p-0 lg:pr-5 mr-5 w-full md:max-w-sm">
|
<div className="flex flex-col relative lg:sticky lg:top-0 w-md h-full p-5 lg:p-0 lg:pr-5 mr-5 w-full md:max-w-sm">
|
||||||
|
<div className="flex mt-2 mb-9 items-center cursor-pointer opacity-70" onClick={() => window.history.back()}>
|
||||||
|
<ChevronLeft className="w-4 h-4 mr-1" />
|
||||||
|
<h3 className="text-md">Go back</h3>
|
||||||
|
</div>
|
||||||
<Image src={theme.image} alt={theme.name} width={500} height={500} className="w-full object-cover rounded-lg border-2 shadow" />
|
<Image src={theme.image} alt={theme.name} width={500} height={500} className="w-full object-cover rounded-lg border-2 shadow" />
|
||||||
<h1 className="text-2xl mt-5 font-bold">{theme.name}</h1>
|
<h1 className="text-2xl mt-5 font-bold">{theme.name}</h1>
|
||||||
<p className="text-sm text-muted-foreground mt-2">{theme.description}</p>
|
<p className="text-sm text-muted-foreground mt-2">{theme.description}</p>
|
||||||
@@ -42,11 +53,7 @@ export default function ThemePage({ theme }: { theme: ZenTheme }) {
|
|||||||
<p id="install-theme-error" className="text-muted-foreground text-sm mt-2">You need to have Zen Browser installed to install this theme. <a href="/download" className="text-blue-500">Download now!</a></p>
|
<p id="install-theme-error" className="text-muted-foreground text-sm mt-2">You need to have Zen Browser installed to install this theme. <a href="/download" className="text-blue-500">Download now!</a></p>
|
||||||
</div>
|
</div>
|
||||||
<hr className="block my-4 lg:hidden" />
|
<hr className="block my-4 lg:hidden" />
|
||||||
<div className="flex flex-col lg:border-l lg:min-h-96 pl-10 lg:ml-[25rem] max-w-xl lg:min-w-96 w-full">
|
<div className="flex flex-col lg:border-l lg:min-h-96 px-5 lg:pl-10 max-w-xl lg:min-w-96 w-full">
|
||||||
<div className="flex my-2 items-center cursor-pointer opacity-70" onClick={() => window.history.back()}>
|
|
||||||
<ChevronLeft className="w-4 h-4 mr-1" />
|
|
||||||
<h3 className="text-md">Go back</h3>
|
|
||||||
</div>
|
|
||||||
<div id="policy" className="w-full">
|
<div id="policy" className="w-full">
|
||||||
{readme === null ? (
|
{readme === null ? (
|
||||||
<LoaderCircleIcon className="animate-spin w-12 h-12 mx-auto" />
|
<LoaderCircleIcon className="animate-spin w-12 h-12 mx-auto" />
|
||||||
|
|||||||
@@ -601,6 +601,31 @@ export const releaseNotes: ReleaseNote[] = [
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
version: "1.0.0-a.29",
|
||||||
|
date: "24/08/2024",
|
||||||
|
extra: "This release is the twenty-ninth alpha release of the 1.0.0-alpha series.",
|
||||||
|
features: [
|
||||||
|
"Added Spanish translations",
|
||||||
|
"Added documentation for contributing",
|
||||||
|
"Added support for multi-tab splitting with shortcuts",
|
||||||
|
"Fixed sidebar shortcuts"
|
||||||
|
],
|
||||||
|
fixes: [
|
||||||
|
{
|
||||||
|
description: "Text on websites is blurry",
|
||||||
|
issue: 383
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Expanded compact mode triggers too early",
|
||||||
|
issue: 520
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Ampersand in workspace name breaks workspace menu",
|
||||||
|
issue: 439
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
].reverse();
|
].reverse();
|
||||||
|
|
||||||
export function releaseNoteIsAlpha(note: ReleaseNote) {
|
export function releaseNoteIsAlpha(note: ReleaseNote) {
|
||||||
|
|||||||
29
src/lib/styled-components-registry.tsx
Normal file
29
src/lib/styled-components-registry.tsx
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import React, { useState } from "react";
|
||||||
|
import { useServerInsertedHTML } from "next/navigation";
|
||||||
|
import { ServerStyleSheet, StyleSheetManager } from "styled-components";
|
||||||
|
|
||||||
|
export default function StyledComponentsRegistry({
|
||||||
|
children,
|
||||||
|
}: {
|
||||||
|
children: React.ReactNode;
|
||||||
|
}) {
|
||||||
|
// Only create stylesheet once with lazy initial state
|
||||||
|
// x-ref: https://reactjs.org/docs/hooks-reference.html#lazy-initial-state
|
||||||
|
const [styledComponentsStyleSheet] = useState(() => new ServerStyleSheet());
|
||||||
|
|
||||||
|
useServerInsertedHTML(() => {
|
||||||
|
const styles = styledComponentsStyleSheet.getStyleElement();
|
||||||
|
styledComponentsStyleSheet.instance.clearTag();
|
||||||
|
return <>{styles}</>;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (typeof window !== "undefined") return <>{children}</>;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyleSheetManager sheet={styledComponentsStyleSheet.instance}>
|
||||||
|
{children}
|
||||||
|
</StyleSheetManager>
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user