chore(prettier): update prettier config

This commit is contained in:
Shintaro Jokagi 2025-05-28 13:50:37 +12:00
parent ceef83d609
commit a77c141d35
No known key found for this signature in database
GPG key ID: 0DDF8FA44C9A0DA8
66 changed files with 709 additions and 709 deletions

View file

@ -7,7 +7,7 @@ export default {
tabWidth: 2,
useTabs: false,
semi: false,
singleQuote: false,
singleQuote: true,
quoteProps: "as-needed",
trailingComma: "es5",
bracketSpacing: true,

View file

@ -1,10 +1,10 @@
export function getTitleAnimation(delay = 0, duration = 0.3, once = true) {
return {
initial: { opacity: 0.001, translateY: 20, filter: "blur(4px)" },
initial: { opacity: 0.001, translateY: 20, filter: 'blur(4px)' },
whileInView: {
opacity: 1,
translateY: 0,
filter: "blur(0px)",
filter: 'blur(0px)',
transition: { duration, delay },
},
viewport: { once },

View file

@ -1,6 +1,6 @@
---
import ArrowLeftIcon from "~/icons/ArrowLeftIcon.astro"
import { getLocale, getUI } from "~/utils/i18n"
import ArrowLeftIcon from '~/icons/ArrowLeftIcon.astro'
import { getLocale, getUI } from '~/utils/i18n'
const locale = getLocale(Astro)

View file

@ -1,5 +1,5 @@
---
import { getLocale, getPath } from "~/utils/i18n"
import { getLocale, getPath } from '~/utils/i18n'
const locale = getLocale(Astro)
const getLocalePath = getPath(locale)
@ -13,15 +13,15 @@ const { class: className, isPrimary, isAlert, isBordered, href, id, extra } = As
{...extra}
href={getLocalePath(href)}
class:list={[
"transition-bg flex items-center justify-center gap-2 rounded-xl px-6 py-4 transition-transform duration-150 hover:scale-[1.02] active:scale-[0.98]",
'transition-bg flex items-center justify-center gap-2 rounded-xl px-6 py-4 transition-transform duration-150 hover:scale-[1.02] active:scale-[0.98]',
className,
isPrimary
? "border-dark bg-dark text-paper shadow-lg"
? 'border-dark bg-dark text-paper shadow-lg'
: isAlert
? "bg-red-300 text-dark"
? 'bg-red-300 text-dark'
: !isBordered
? "bg-subtle"
: "!transition-bg border-2 border-dark hover:bg-dark hover:text-paper hover:shadow-sm",
? 'bg-subtle'
: '!transition-bg border-2 border-dark hover:bg-dark hover:text-paper hover:shadow-sm',
]}
>
<slot />
@ -31,15 +31,15 @@ const { class: className, isPrimary, isAlert, isBordered, href, id, extra } = As
id={id}
{...extra}
class:list={[
"transition-bg flex items-center justify-center gap-2 rounded-lg px-6 py-3 transition-transform duration-150 hover:scale-[1.02]",
'transition-bg flex items-center justify-center gap-2 rounded-lg px-6 py-3 transition-transform duration-150 hover:scale-[1.02]',
className,
isPrimary
? "border-dark bg-dark text-paper shadow-md"
? 'border-dark bg-dark text-paper shadow-md'
: isAlert
? "bg-red-300 text-dark"
? 'bg-red-300 text-dark'
: !isBordered
? ""
: "!transition-bg border-2 border-dark hover:bg-dark hover:text-paper hover:shadow-sm",
? ''
: '!transition-bg border-2 border-dark hover:bg-dark hover:text-paper hover:shadow-sm',
]}
>
<slot />

View file

@ -4,14 +4,14 @@ const sizes = [216, 396, 576, 756]
const borderWidths = [20, 30, 40, 50]
---
<div id="circles" class:list={["pointer-events-none inset-0 overflow-hidden", classList]}>
<div id="circles" class:list={['pointer-events-none inset-0 overflow-hidden', classList]}>
<div class="mx-auto opacity-10 lg:opacity-100">
{
[...Array(4)].map((_, i) => (
<div
class:list={[
"absolute -translate-x-1/2 -translate-y-1/2 rounded-full",
white ? "border-paper" : "border-coral",
'absolute -translate-x-1/2 -translate-y-1/2 rounded-full',
white ? 'border-paper' : 'border-coral',
]}
style={{
width: `${multiplier * sizes[i]}px`,

View file

@ -1,13 +1,13 @@
---
import Image from "astro/components/Image.astro"
import { motion } from "motion/react"
import { getTitleAnimation } from "~/animations"
import ComImage from "~/assets/ComImage.png"
import Button from "~/components/Button.astro"
import Description from "~/components/Description.astro"
import CheckIcon from "~/icons/CheckIcon.astro"
import GitHubIcon from "~/icons/GitHubIcon.astro"
import { getLocale, getUI } from "~/utils/i18n"
import Image from 'astro/components/Image.astro'
import { motion } from 'motion/react'
import { getTitleAnimation } from '~/animations'
import ComImage from '~/assets/ComImage.png'
import Button from '~/components/Button.astro'
import Description from '~/components/Description.astro'
import CheckIcon from '~/icons/CheckIcon.astro'
import GitHubIcon from '~/icons/GitHubIcon.astro'
import { getLocale, getUI } from '~/utils/i18n'
const locale = getLocale(Astro)
@ -38,7 +38,7 @@ const {
</motion.p>
<div class="flex w-full flex-wrap gap-3 sm:gap-10 md:justify-center">
<motion.span client:load {...getTitleAnimation(0.8)}>
<Button class:list={["px-4"]} href="https://github.com/zen-browser">
<Button class:list={['px-4']} href="https://github.com/zen-browser">
<GitHubIcon class="size-4" />
<span>{community.lists.freeAndOpenSource.title}</span>
</Button>

View file

@ -1,15 +1,15 @@
---
import { motion } from "motion/react"
import { getTitleAnimation } from "~/animations"
import Description from "~/components/Description.astro"
import { motion } from 'motion/react'
import { getTitleAnimation } from '~/animations'
import Description from '~/components/Description.astro'
import CompactModeVideo from "~/assets/CompactMode.webm"
import GlanceVideo from "~/assets/Glance.webm"
import SplitViewsVideo from "~/assets/SplitViews.webm"
import WorkspacesVideo from "~/assets/Workspaces.webm"
import CompactModeVideo from '~/assets/CompactMode.webm'
import GlanceVideo from '~/assets/Glance.webm'
import SplitViewsVideo from '~/assets/SplitViews.webm'
import WorkspacesVideo from '~/assets/Workspaces.webm'
import { getLocale, getUI } from "~/utils/i18n"
import Video from "./Video.astro"
import { getLocale, getUI } from '~/utils/i18n'
import Video from './Video.astro'
const locale = getLocale(Astro)
@ -161,17 +161,17 @@ const descriptions = Object.values(features.featureTabs).map(tab => tab.descript
</section>
<script>
const features = document.querySelectorAll(".feature, .feature-tab") as NodeListOf<HTMLElement>
const features = document.querySelectorAll('.feature, .feature-tab') as NodeListOf<HTMLElement>
// Set initial description
const descriptionEl = document.querySelector(".feature-description") as HTMLDivElement
const descriptions = descriptionEl?.dataset.descriptions?.split(",")
const descriptionEl = document.querySelector('.feature-description') as HTMLDivElement
const descriptions = descriptionEl?.dataset.descriptions?.split(',')
if (descriptionEl && descriptions) {
descriptionEl.textContent = descriptions[0]
}
function changeToFeature({ target }: { target: HTMLElement | undefined | null }) {
target = target?.closest(".feature, .feature-tab")
target = target?.closest('.feature, .feature-tab')
if (!target) return
const index = Array.from(features).indexOf(target) % 4
@ -180,19 +180,19 @@ const descriptions = Object.values(features.featureTabs).map(tab => tab.descript
// Update both mobile and desktop elements
features.forEach((f, i) => {
if (i % 4 === index) {
f.setAttribute("data-active", "true")
f.setAttribute('data-active', 'true')
} else {
f.removeAttribute("data-active")
f.removeAttribute('data-active')
}
})
// Update mobile description
const descriptionEl = document.querySelector(".feature-description")
const descriptionEl = document.querySelector('.feature-description')
if (descriptionEl && descriptions) {
descriptionEl.textContent = descriptions[index]
}
const videos = document.querySelectorAll(".feature-video") as NodeListOf<HTMLVideoElement>
const videos = document.querySelectorAll('.feature-video') as NodeListOf<HTMLVideoElement>
videos.forEach((vid, i) => {
const yOffset = (i - index) * 20
const zOffset = i === index ? 0 : -100 - Math.abs(i - index) * 50
@ -200,14 +200,14 @@ const descriptions = Object.values(features.featureTabs).map(tab => tab.descript
const rotation = (i - index) * 3
if (i === index) {
vid.setAttribute("data-active", "true")
vid.style.opacity = "1"
vid.setAttribute('data-active', 'true')
vid.style.opacity = '1'
vid.style.transform = `translate3d(-50%, 0, 0) scale(${scale})`
vid.style.zIndex = "10"
vid.style.zIndex = '10'
vid.currentTime = 0
vid.play()
} else {
vid.removeAttribute("data-active")
vid.removeAttribute('data-active')
vid.style.transform = `translate3d(-50%, ${yOffset}px, ${zOffset}px)
rotate3d(1, 0, 0, ${rotation}deg)
scale(${scale})`
@ -219,7 +219,7 @@ const descriptions = Object.values(features.featureTabs).map(tab => tab.descript
for (const feature of features) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
feature.addEventListener("click", changeToFeature as any)
feature.addEventListener('click', changeToFeature as any)
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
@ -231,7 +231,7 @@ const descriptions = Object.values(features.featureTabs).map(tab => tab.descript
@apply w-full cursor-pointer select-none rounded-lg p-4 opacity-0 hover:bg-subtle;
transition: background 0.2s ease-in-out;
&[data-active="true"] {
&[data-active='true'] {
@apply bg-subtle;
}
}
@ -240,7 +240,7 @@ const descriptions = Object.values(features.featureTabs).map(tab => tab.descript
@apply rounded-lg px-4 py-2 text-lg font-medium opacity-0 hover:bg-subtle;
transition: background 0.2s ease-in-out;
&[data-active="true"] {
&[data-active='true'] {
@apply bg-subtle;
}
}
@ -279,7 +279,7 @@ const descriptions = Object.values(features.featureTabs).map(tab => tab.descript
display: none;
object-fit: cover;
&[data-active="true"] {
&[data-active='true'] {
display: block;
opacity: 1;
}

View file

@ -1,10 +1,10 @@
---
import Button from "~/components/Button.astro"
import Circles from "~/components/Circles.astro"
import Description from "~/components/Description.astro"
import SocialMediaStrip from "~/components/SocialMediaStrip.astro"
import ArrowRightIcon from "~/icons/ArrowRightIcon.astro"
import { getLocale, getPath, getUI } from "~/utils/i18n"
import Button from '~/components/Button.astro'
import Circles from '~/components/Circles.astro'
import Description from '~/components/Description.astro'
import SocialMediaStrip from '~/components/SocialMediaStrip.astro'
import ArrowRightIcon from '~/icons/ArrowRightIcon.astro'
import { getLocale, getPath, getUI } from '~/utils/i18n'
const locale = getLocale(Astro)
const getLocalePath = getPath(locale)
@ -61,12 +61,12 @@ const {
<nav aria-label="About navigation">
<ul class="grid gap-2 opacity-80">
<li>
<a href={getLocalePath("/about")} class="font-normal"
<a href={getLocalePath('/about')} class="font-normal"
>{footer.teamAndContributors}</a
>
</li>
<li>
<a href={getLocalePath("/privacy-policy")} class="font-normal"
<a href={getLocalePath('/privacy-policy')} class="font-normal"
>{footer.privacyPolicy}</a
>
</li>
@ -83,14 +83,14 @@ const {
<a href="https://docs.zen-browser.app/" class="font-normal">{footer.documentation}</a>
</li>
<li>
<a href={getLocalePath("/mods")} class="font-normal">{footer.zenMods}</a>
<a href={getLocalePath('/mods')} class="font-normal">{footer.zenMods}</a>
</li>
<li>
<a href={getLocalePath("/release-notes")} class="font-normal">{footer.releaseNotes}</a
<a href={getLocalePath('/release-notes')} class="font-normal">{footer.releaseNotes}</a
>
</li>
<li>
<a href={getLocalePath("/download?twilight")} class="font-normal">{footer.twilight}</a
<a href={getLocalePath('/download?twilight')} class="font-normal">{footer.twilight}</a
>
</li>
</ul>
@ -122,7 +122,7 @@ const {
>
<p
class="flex justify-center gap-2 lg:justify-start"
set:html={footer.madeWith.replace("{link}", getLocalePath("/about"))}
set:html={footer.madeWith.replace('{link}', getLocalePath('/about'))}
/>
</section>
<section class="absolute bottom-0 right-0">

View file

@ -1,14 +1,14 @@
---
import { motion } from "motion/react"
import { getTitleAnimation } from "~/animations"
import HomePageVideo from "~/assets/HomePageVideo.webm"
import Button from "~/components/Button.astro"
import Description from "~/components/Description.astro"
import Title from "~/components/Title.astro"
import ArrowRightIcon from "~/icons/ArrowRightIcon.astro"
import { getLocale, getPath, getUI } from "~/utils/i18n"
import SocialMediaStrip from "./SocialMediaStrip.astro"
import Video from "./Video.astro"
import { motion } from 'motion/react'
import { getTitleAnimation } from '~/animations'
import HomePageVideo from '~/assets/HomePageVideo.webm'
import Button from '~/components/Button.astro'
import Description from '~/components/Description.astro'
import Title from '~/components/Title.astro'
import ArrowRightIcon from '~/icons/ArrowRightIcon.astro'
import { getLocale, getPath, getUI } from '~/utils/i18n'
import SocialMediaStrip from './SocialMediaStrip.astro'
import Video from './Video.astro'
let titleAnimationCounter = 0
function getNewAnimationDelay() {
@ -63,13 +63,13 @@ const {
</motion.span>
<div class="mt-6 flex w-2/3 flex-col gap-3 sm:gap-6 md:w-fit md:flex-row">
<motion.span client:load {...getHeroTitleAnimation()}>
<Button class="w-full" href={getLocalePath("/download")} isPrimary>
<Button class="w-full" href={getLocalePath('/download')} isPrimary>
{hero.buttons.beta}
<ArrowRightIcon class="size-4" />
</Button>
</motion.span>
<motion.span client:load {...getHeroTitleAnimation()}>
<Button href={getLocalePath("/donate")}>{hero.buttons.support}</Button>
<Button href={getLocalePath('/donate')}>{hero.buttons.support}</Button>
</motion.span>
</div>
<motion.span

View file

@ -1,5 +1,5 @@
---
import { getLocale, getPath, getUI } from "~/utils/i18n"
import { getLocale, getPath, getUI } from '~/utils/i18n'
const locale = getLocale(Astro)
const getLocalePath = getPath(locale)
@ -46,12 +46,12 @@ const {
<div class="mb-2 font-bold">{menu.gettingStarted}</div>
<ul class="ml-4 space-y-2">
<li>
<a href={getLocalePath("/mods")} class="block text-dark hover:text-coral"
<a href={getLocalePath('/mods')} class="block text-dark hover:text-coral"
>{menu.zenMods}</a
>
</li>
<li>
<a href={getLocalePath("/release-notes")} class="block text-dark hover:text-coral"
<a href={getLocalePath('/release-notes')} class="block text-dark hover:text-coral"
>{menu.releaseNotes}</a
>
</li>
@ -67,12 +67,12 @@ const {
<div class="mb-2 font-bold">{menu.usefulLinks}</div>
<ul class="ml-4 space-y-2">
<li>
<a href={getLocalePath("/donate")} class="block text-dark hover:text-coral"
<a href={getLocalePath('/donate')} class="block text-dark hover:text-coral"
>{menu.donate}</a
>
</li>
<li>
<a href={getLocalePath("/about")} class="block text-dark hover:text-coral"
<a href={getLocalePath('/about')} class="block text-dark hover:text-coral"
>{menu.aboutUs}</a
>
</li>
@ -92,12 +92,12 @@ const {
</li>
<!-- Extra Links -->
<li>
<a href={getLocalePath("/mods")} class="block font-bold text-dark hover:text-coral"
<a href={getLocalePath('/mods')} class="block font-bold text-dark hover:text-coral"
>{menu.mods}</a
>
</li>
<li>
<a href={getLocalePath("/download")} class="block font-bold text-dark hover:text-coral"
<a href={getLocalePath('/download')} class="block font-bold text-dark hover:text-coral"
>{menu.download}</a
>
</li>

View file

@ -1,18 +1,18 @@
import { icon, library } from "@fortawesome/fontawesome-svg-core"
import { faSort, faSortDown, faSortUp } from "@fortawesome/free-solid-svg-icons"
import { useEffect, useState, type FormEvent } from "react"
import { icon, library } from '@fortawesome/fontawesome-svg-core'
import { faSort, faSortDown, faSortUp } from '@fortawesome/free-solid-svg-icons'
import { useEffect, useState, type FormEvent } from 'react'
import { useModsSearch } from "~/hooks/useModsSearch"
import { type ZenTheme } from "~/mods"
import { getUI, type Locale } from "~/utils/i18n"
import { useModsSearch } from '~/hooks/useModsSearch'
import { type ZenTheme } from '~/mods'
import { getUI, type Locale } from '~/utils/i18n'
// Add icons to the library
library.add(faSort, faSortUp, faSortDown)
// Create icon objects
const defaultSortIcon = icon({ prefix: "fas", iconName: "sort" })
const ascSortIcon = icon({ prefix: "fas", iconName: "sort-up" })
const descSortIcon = icon({ prefix: "fas", iconName: "sort-down" })
const defaultSortIcon = icon({ prefix: 'fas', iconName: 'sort' })
const ascSortIcon = icon({ prefix: 'fas', iconName: 'sort-up' })
const descSortIcon = icon({ prefix: 'fas', iconName: 'sort-down' })
type ModsListProps = {
allMods: ZenTheme[]
@ -44,9 +44,9 @@ const ModsList = ({ allMods, locale }: ModsListProps) => {
setPageInput(page.toString())
}, [page])
function getSortIcon(state: "default" | "asc" | "desc") {
if (state === "asc") return ascSortIcon
if (state === "desc") return descSortIcon
function getSortIcon(state: 'default' | 'asc' | 'desc') {
if (state === 'asc') return ascSortIcon
if (state === 'desc') return descSortIcon
return defaultSortIcon
}
@ -90,14 +90,14 @@ const ModsList = ({ allMods, locale }: ModsListProps) => {
return (
<div className="mx-auto mb-12 flex items-center justify-center gap-4 px-8">
<button
className={`px-3 py-2 ${page === 1 ? "pointer-events-none text-gray-400" : "text-dark hover:text-gray-600"}`}
className={`px-3 py-2 ${page === 1 ? 'pointer-events-none text-gray-400' : 'text-dark hover:text-gray-600'}`}
onClick={() => navigatePage(page - 1)}
type="button"
>
&lt;
</button>
<form className="flex items-center gap-2" onSubmit={handlePageSubmit}>
{mods.pagination.pagination.split("{input}").map((value, index) => {
{mods.pagination.pagination.split('{input}').map((value, index) => {
if (index === 0) {
return (
<input
@ -113,14 +113,14 @@ const ModsList = ({ allMods, locale }: ModsListProps) => {
return (
<span className="text-sm" key={value}>
{value
.replace("{totalPages}", totalPages.toString())
.replace("{totalItems}", totalItems.toString())}
.replace('{totalPages}', totalPages.toString())
.replace('{totalItems}', totalItems.toString())}
</span>
)
})}
</form>
<button
className={`px-3 py-2 ${page === totalPages ? "pointer-events-none text-gray-400" : "text-dark hover:text-gray-600"}`}
className={`px-3 py-2 ${page === totalPages ? 'pointer-events-none text-gray-400' : 'text-dark hover:text-gray-600'}`}
onClick={() => navigatePage(page + 1)}
type="button"
>

View file

@ -1,14 +1,14 @@
---
import { Astronav, Dropdown, DropdownItems, MenuItems } from "astro-navbar"
import Button from "~/components/Button.astro"
import ArrowRightIcon from "~/icons/ArrowRightIcon.astro"
import ChevronDownIcon from "~/icons/ChevronDownIcon.astro"
import DownloadIcon from "~/icons/DownloadIcon.astro"
import MenuIcon from "~/icons/MenuIcon.astro"
import { getLocale, getPath, getUI } from "~/utils/i18n"
import Logo from "./Logo.astro"
import MobileMenu from "./MobileMenu.astro"
import ThemeSwitch from "./ThemeSwitch.astro"
import { Astronav, Dropdown, DropdownItems, MenuItems } from 'astro-navbar'
import Button from '~/components/Button.astro'
import ArrowRightIcon from '~/icons/ArrowRightIcon.astro'
import ChevronDownIcon from '~/icons/ChevronDownIcon.astro'
import DownloadIcon from '~/icons/DownloadIcon.astro'
import MenuIcon from '~/icons/MenuIcon.astro'
import { getLocale, getPath, getUI } from '~/utils/i18n'
import Logo from './Logo.astro'
import MobileMenu from './MobileMenu.astro'
import ThemeSwitch from './ThemeSwitch.astro'
const locale = getLocale(Astro)
const getLocalePath = getPath(locale)
@ -24,7 +24,7 @@ const {
<MenuItems
class="container relative z-20 grid w-full grid-cols-2 items-center gap-2 bg-paper py-3 lg:grid lg:grid-cols-[auto_1fr_auto] lg:py-6"
>
<a class="flex items-center gap-2 text-lg font-bold" href={getLocalePath("/")}>
<a class="flex items-center gap-2 text-lg font-bold" href={getLocalePath('/')}>
<Logo class="text-coral" />
<span>{brand}</span>
</a>
@ -40,7 +40,7 @@ const {
</button>
<DropdownItems>
<div class="navbar-dropdown">
<a class="dropdown-item bg-dark/5 row-span-2" href={getLocalePath("/mods")}>
<a class="dropdown-item bg-dark/5 row-span-2" href={getLocalePath('/mods')}>
<div class="dropdown-title">{menu.zenMods}</div>
<div class="dropdown-description">
{menu.zenModsDesc}
@ -50,7 +50,7 @@ const {
<ArrowRightIcon class="size-4" />
</Button>
</a>
<a class="dropdown-item" href={getLocalePath("/release-notes")}>
<a class="dropdown-item" href={getLocalePath('/release-notes')}>
<div class="dropdown-title">{menu.releaseNotes}</div>
<div class="dropdown-description">
{menu.releaseNotesDesc}
@ -74,13 +74,13 @@ const {
</button>
<DropdownItems>
<div class="navbar-dropdown !grid-cols-1 gap-1">
<a class="dropdown-item" href={getLocalePath("/donate")}>
<a class="dropdown-item" href={getLocalePath('/donate')}>
<div class="dropdown-title">{menu.donate}</div>
<div class="dropdown-description">
{menu.donateDesc}
</div>
</a>
<a class="dropdown-item" href={getLocalePath("/about")}>
<a class="dropdown-item" href={getLocalePath('/about')}>
<div class="dropdown-title">{menu.aboutUs}</div>
<div class="dropdown-description">
{menu.aboutUsDesc}
@ -101,7 +101,7 @@ const {
</div>
</DropdownItems>
</Dropdown>
<a class="hidden items-center lg:block" href={getLocalePath("/mods")}>
<a class="hidden items-center lg:block" href={getLocalePath('/mods')}>
<span>{menu.mods}</span>
</a>
</div>

View file

@ -1,10 +1,10 @@
---
import InfoIcon from "~/icons/InfoIcon.astro"
import InfoIcon from '~/icons/InfoIcon.astro'
import { releaseNotes as releaseNotesData } from "~/release-notes"
import { getLocale, getPath, getUI } from "~/utils/i18n"
import { type ReleaseNote, getReleaseNoteFirefoxVersion } from "../release-notes"
import ReleaseNoteListItem from "./ReleaseNoteListItem.astro"
import { releaseNotes as releaseNotesData } from '~/release-notes'
import { getLocale, getPath, getUI } from '~/utils/i18n'
import { type ReleaseNote, getReleaseNoteFirefoxVersion } from '../release-notes'
import ReleaseNoteListItem from './ReleaseNoteListItem.astro'
export type Props = ReleaseNote
const { isTwilight, ...props } = Astro.props
@ -20,7 +20,7 @@ const {
let date: Date | undefined
if (props.date) {
const [day, month, year] = props.date.split("/")
const [day, month, year] = props.date.split('/')
date = new Date(Date.parse(`${year}-${month}-${day}`))
}
@ -29,7 +29,7 @@ const currentReleaseIndex = releaseNotesData.findIndex(
(releaseNote: ReleaseNote) => releaseNote.version === props.version
)
const prevReleaseNote = releaseNotesData[currentReleaseIndex + 1]
let compareLink = ""
let compareLink = ''
if (prevReleaseNote && !isTwilight) {
compareLink = `https://github.com/zen-browser/desktop/compare/${prevReleaseNote.version}...${props.version}`
}
@ -47,15 +47,15 @@ const generateItems = (items: any, type: string) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
items.forEach((item: any) => {
switch (type) {
case "feature":
case 'feature':
listItems[type].push({
type: "feature",
type: 'feature',
content: item,
})
break
case "fix":
case 'fix':
listItems[type].push({
type: "fix",
type: 'fix',
content: item.description ?? item,
...(item.issue
? {
@ -67,36 +67,36 @@ const generateItems = (items: any, type: string) => {
: {}),
})
break
case "security":
case 'security':
listItems[type].push({
type: "security",
type: 'security',
link: {
text: "Various security fixes.",
text: 'Various security fixes.',
href: item,
},
})
break
case "theme":
case 'theme':
listItems[type].push({
type: "theme",
type: 'theme',
content: item,
})
break
case "break":
case 'break':
listItems[type].push({
type: "break",
type: 'break',
content: item,
})
break
}
})
}
generateItems(props.security ? [props.security] : null, "security")
generateItems(props.fixes, "fix")
generateItems(props.features, "feature")
generateItems(props.themeChanges, "theme")
generateItems(props.breakingChanges, "break")
generateItems(props.knownIssues, "known")
generateItems(props.security ? [props.security] : null, 'security')
generateItems(props.fixes, 'fix')
generateItems(props.features, 'feature')
generateItems(props.themeChanges, 'theme')
generateItems(props.breakingChanges, 'break')
generateItems(props.knownIssues, 'known')
---
<section
@ -108,7 +108,7 @@ generateItems(props.knownIssues, "known")
isTwilight ? (
<a
class="!mb-2 block w-fit rounded-full bg-coral px-3 py-1 text-xs text-paper"
href={getLocalePath("/download?twilight")}
href={getLocalePath('/download?twilight')}
>
{releaseNoteItem.twilight}
</a>
@ -121,11 +121,11 @@ generateItems(props.knownIssues, "known")
{
isTwilight ? (
<>
{releaseNoteItem.twilightChanges}{" "}
{props.version.replaceAll("{version}", props.version)}
{releaseNoteItem.twilightChanges}{' '}
{props.version.replaceAll('{version}', props.version)}
</>
) : (
<>{releaseNoteItem.releaseChanges.replaceAll("{version}", props.version)}</>
<>{releaseNoteItem.releaseChanges.replaceAll('{version}', props.version)}</>
)
}
{
@ -139,7 +139,7 @@ generateItems(props.knownIssues, "known")
target="_blank"
rel="noopener noreferrer"
>
{releaseNoteItem.firefoxVersion.replace("{version}", ffVersion)}
{releaseNoteItem.firefoxVersion.replace('{version}', ffVersion)}
</a>
</>
) : null
@ -149,7 +149,7 @@ generateItems(props.knownIssues, "known")
rel="noopener noreferrer"
class="zen-link whitespace-nowrap text-xs !no-underline opacity-80"
target="_blank"
href={`https://github.com/zen-browser/desktop/releases/tag/${isTwilight ? "twilight" : props.version}`}
href={`https://github.com/zen-browser/desktop/releases/tag/${isTwilight ? 'twilight' : props.version}`}
>{releaseNoteItem.githubRelease}</a
>
{
@ -168,7 +168,7 @@ generateItems(props.knownIssues, "known")
) : null
}
{
compareLink !== "" ? (
compareLink !== '' ? (
<>
<span class="text-muted-foreground mx-3 hidden sm:block">•</span>
<a
@ -184,13 +184,13 @@ generateItems(props.knownIssues, "known")
}
</div>
<div class="text-xs font-bold opacity-80">
{date && date.toLocaleDateString("en-US", { dateStyle: "long" })}
{date && date.toLocaleDateString('en-US', { dateStyle: 'long' })}
</div>
</div>
{
props.extra?.length ? (
<p class="text-md text-muted-foreground extra mt-2">
<Fragment set:html={props.extra.replace(/\n/g, "<br />")} />
<Fragment set:html={props.extra.replace(/\n/g, '<br />')} />
</p>
) : null
}

View file

@ -1,8 +1,8 @@
---
import { getLocale, getPath, getUI } from "~/utils/i18n"
import { getLocale, getPath, getUI } from '~/utils/i18n'
const { type, content, link } = Astro.props as {
type: "security" | "feature" | "fix" | "theme" | "break" | "known"
type: 'security' | 'feature' | 'fix' | 'theme' | 'break' | 'known'
content: string
link?: {
text: string
@ -22,13 +22,13 @@ const {
<li class="flex gap-2">
<div
class:list={[
(type === "security" && "text-[#e3401f]") ||
(type === "feature" && "text-[#bf3316] dark:text-[#ffb1a1]") ||
(type === "fix" && "text-[#fe846b]") ||
(type === "theme" && "text-[#f76f53]") ||
(type === "break" && "text-[#471308] dark:text-[#D02908]") ||
"",
"min-w-16 font-bold opacity-80",
(type === 'security' && 'text-[#e3401f]') ||
(type === 'feature' && 'text-[#bf3316] dark:text-[#ffb1a1]') ||
(type === 'fix' && 'text-[#fe846b]') ||
(type === 'theme' && 'text-[#f76f53]') ||
(type === 'break' && 'text-[#471308] dark:text-[#D02908]') ||
'',
'min-w-16 font-bold opacity-80',
]}
>
{itemType[type]}

View file

@ -1,21 +1,21 @@
---
const { gap = 4 } = Astro.props
import { icon, library } from "@fortawesome/fontawesome-svg-core"
import { icon, library } from '@fortawesome/fontawesome-svg-core'
import {
faBluesky,
faGithub,
faMastodon,
faReddit,
faXTwitter,
} from "@fortawesome/free-brands-svg-icons"
} from '@fortawesome/free-brands-svg-icons'
library.add(faMastodon, faBluesky, faGithub, faXTwitter, faReddit)
const Mastodon = icon({ prefix: "fab", iconName: "mastodon" })
const Bluesky = icon({ prefix: "fab", iconName: "bluesky" })
const Github = icon({ prefix: "fab", iconName: "github" })
const XTwitter = icon({ prefix: "fab", iconName: "x-twitter" })
const Reddit = icon({ prefix: "fab", iconName: "reddit" })
const Mastodon = icon({ prefix: 'fab', iconName: 'mastodon' })
const Bluesky = icon({ prefix: 'fab', iconName: 'bluesky' })
const Github = icon({ prefix: 'fab', iconName: 'github' })
const XTwitter = icon({ prefix: 'fab', iconName: 'x-twitter' })
const Reddit = icon({ prefix: 'fab', iconName: 'reddit' })
---
<ul class={`flex items-center opacity-80 gap-${gap}`}>

View file

@ -1,14 +1,14 @@
---
import { motion } from "motion/react"
import { getTitleAnimation } from "~/animations"
import Description from "~/components/Description.astro"
import { getLocale, getUI } from "~/utils/i18n"
import { motion } from 'motion/react'
import { getTitleAnimation } from '~/animations'
import Description from '~/components/Description.astro'
import { getLocale, getUI } from '~/utils/i18n'
const locale = getLocale(Astro)
import tutaLogo from "~/assets/sponsors/tutaLogo_monochrome.svg"
import tutaLogo from '~/assets/sponsors/tutaLogo_monochrome.svg'
import Image from "astro/components/Image.astro"
import Image from 'astro/components/Image.astro'
const { showSponsors = true } = Astro.props
const {
@ -18,7 +18,7 @@ const {
} = getUI(locale)
---
<section id="sponsors" class:list={["py-12", !showSponsors && "hidden"]}>
<section id="sponsors" class:list={['py-12', !showSponsors && 'hidden']}>
<div class="mx-auto flex flex-col text-center">
<motion.span client:load {...getTitleAnimation(0.2)}>
<Description class="mb-2 text-6xl font-bold">Our Sponsors</Description>
@ -28,10 +28,10 @@ const {
</motion.span>
<div class="relative mt-8 flex items-center justify-center">
<motion.span client:load {...getTitleAnimation(0.6)}>
<a href={sponsors.sponsors["tuta"].url} target="_blank" class="w-fit">
<a href={sponsors.sponsors['tuta'].url} target="_blank" class="w-fit">
<Image
src={tutaLogo}
alt={sponsors.sponsors["tuta"].name}
alt={sponsors.sponsors['tuta'].name}
class="h-16 w-fit object-contain"
/>
</a>

View file

@ -4,14 +4,14 @@ interface Props {
className?: string
}
const { label, className = "" } = Astro.props
const { label, className = '' } = Astro.props
---
<button
type="button"
class:list={["inline-flex h-8 w-8 cursor-pointer items-center justify-center", className]}
class:list={['inline-flex h-8 w-8 cursor-pointer items-center justify-center', className]}
id="theme-switcher"
aria-label={label || "Toggle theme"}
aria-label={label || 'Toggle theme'}
>
<svg class="hidden h-5 w-5 dark:block" viewBox="0 0 24 24">
<path
@ -26,24 +26,24 @@ const { label, className = "" } = Astro.props
</button>
<script>
const themeSwitch = document.getElementById("theme-switcher") as HTMLButtonElement
const themeSwitch = document.getElementById('theme-switcher') as HTMLButtonElement
const resolveTheme = () => {
if (typeof localStorage !== "undefined" && localStorage.getItem("theme")) {
return localStorage.getItem("theme") ?? "light"
if (typeof localStorage !== 'undefined' && localStorage.getItem('theme')) {
return localStorage.getItem('theme') ?? 'light'
}
if (window.matchMedia("(prefers-color-scheme: dark)").matches) {
return "dark"
if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
return 'dark'
}
return "light"
return 'light'
}
const html = document.documentElement
themeSwitch.addEventListener("click", () => {
const newTheme = resolveTheme() === "light" ? "dark" : "light"
html.setAttribute("data-theme", newTheme)
html.classList.toggle("dark", newTheme === "dark")
localStorage.setItem("theme", newTheme)
themeSwitch.addEventListener('click', () => {
const newTheme = resolveTheme() === 'light' ? 'dark' : 'light'
html.setAttribute('data-theme', newTheme)
html.classList.toggle('dark', newTheme === 'dark')
localStorage.setItem('theme', newTheme)
})
</script>

View file

@ -1,10 +1,10 @@
---
import { cn } from "~/utils/merge"
import { cn } from '~/utils/merge'
const { class: className } = Astro.props
---
<h1
class={cn("mb-[0.4rem] font-junicode text-5xl font-semibold leading-[0.9] text-dark", className)}
class={cn('mb-[0.4rem] font-junicode text-5xl font-semibold leading-[0.9] text-dark', className)}
>
<slot />
</h1>

View file

@ -1,22 +1,22 @@
---
const { src, class: className, ...rest } = Astro.props
const type = src.split(".").pop() || "webm"
const type = src.split('.').pop() || 'webm'
---
{/* eslint-disable-next-line jsx-a11y/media-has-caption */}
<video class:list={["w-fit", className]} data-src={src} preload="none" {...rest}>
<video class:list={['w-fit', className]} data-src={src} preload="none" {...rest}>
<source src="" type={`video/${type}`} />
</video>
<script>
const videos = document.querySelectorAll("video[data-src]") as NodeListOf<HTMLVideoElement>
const videos = document.querySelectorAll('video[data-src]') as NodeListOf<HTMLVideoElement>
const loadVideo = (video: HTMLVideoElement) => {
const source = video.querySelector("source")
const dataSrc = video.getAttribute("data-src")
const source = video.querySelector('source')
const dataSrc = video.getAttribute('data-src')
if (dataSrc && source) {
source.src = dataSrc
video.removeAttribute("data-src")
video.removeAttribute('data-src')
video.load()
}
}

View file

@ -89,12 +89,12 @@ const { label, href, checksum } = Astro.props
<script>
const checksumButtons = document.querySelectorAll(
".checksum-icon-btn"
'.checksum-icon-btn'
) as NodeListOf<HTMLButtonElement>
const checksumTooltips = document.querySelectorAll(
".checksum-tooltip"
'.checksum-tooltip'
) as NodeListOf<HTMLDivElement>
const copyButtons = document.querySelectorAll(".copy-btn") as NodeListOf<HTMLButtonElement>
const copyButtons = document.querySelectorAll('.copy-btn') as NodeListOf<HTMLButtonElement>
function stopEvent(e: Event) {
e.preventDefault?.()
@ -107,25 +107,25 @@ const { label, href, checksum } = Astro.props
navigator.clipboard.writeText(checksum)
const btn = e.currentTarget as HTMLButtonElement
const original = btn.innerText
btn.innerText = "Copied!"
btn.innerText = 'Copied!'
setTimeout(() => (btn.innerText = original), 1200)
}
checksumButtons.forEach(btn => {
btn.addEventListener("click", stopEvent)
btn.addEventListener('click', stopEvent)
})
checksumTooltips.forEach(tooltip => {
tooltip.addEventListener("mousedown", stopEvent)
tooltip.addEventListener("click", stopEvent)
tooltip.addEventListener('mousedown', stopEvent)
tooltip.addEventListener('click', stopEvent)
})
copyButtons.forEach(btn => {
btn.addEventListener("click", e =>
btn.addEventListener('click', e =>
copyChecksum(
e,
(btn.closest(".checksum-tooltip")?.querySelector(".font-mono") as HTMLSpanElement)
(btn.closest('.checksum-tooltip')?.querySelector('.font-mono') as HTMLSpanElement)
?.innerText
)
)
btn.addEventListener("mousedown", stopEvent)
btn.addEventListener('mousedown', stopEvent)
})
</script>

View file

@ -1,17 +1,17 @@
<script>
// Handle platform selection
const platformButtons = document.querySelectorAll(".platform-selector")
const platformSections = document.querySelectorAll(".platform-section")
const platformButtons = document.querySelectorAll('.platform-selector')
const platformSections = document.querySelectorAll('.platform-section')
// Function to detect OS and select appropriate platform
function detectOS() {
const userAgent = window.navigator.userAgent
let detectedOS = "mac" // Default to macOS
let detectedOS = 'mac' // Default to macOS
if (userAgent.indexOf("Windows") !== -1) {
detectedOS = "windows"
} else if (userAgent.indexOf("Linux") !== -1) {
detectedOS = "linux"
if (userAgent.indexOf('Windows') !== -1) {
detectedOS = 'windows'
} else if (userAgent.indexOf('Linux') !== -1) {
detectedOS = 'linux'
}
return detectedOS
@ -27,28 +27,28 @@
async function selectPlatform(platform: string) {
// Update button styling
for (const button of platformButtons) {
const buttonPlatform = button.getAttribute("data-platform")
const buttonPlatform = button.getAttribute('data-platform')
if (buttonPlatform === platform) {
button.setAttribute("data-active", "true")
button.setAttribute('data-active', 'true')
} else {
button.setAttribute("data-active", "false")
button.setAttribute('data-active', 'false')
}
}
// Show/hide platform sections
for (const section of platformSections) {
if (section.id === `${platform}-downloads`) {
section.setAttribute("data-active", "true")
section.setAttribute('data-active', 'true')
} else {
section.setAttribute("data-active", "false")
section.setAttribute('data-active', 'false')
}
}
}
// Handle platform button clicks
for (const button of platformButtons) {
button.addEventListener("click", () => {
const platform = button.getAttribute("data-platform") ?? ""
button.addEventListener('click', () => {
const platform = button.getAttribute('data-platform') ?? ''
selectPlatform(platform)
})
}
@ -56,40 +56,40 @@
// Check for twilight mode
async function checkTwilightMode() {
const urlParams = new URLSearchParams(window.location.search)
const isTwilight = urlParams.has("twilight")
const isTwilight = urlParams.has('twilight')
if (isTwilight) {
const twilightInfoElem = document.getElementById("twilight-info")
twilightInfoElem?.setAttribute("data-twilight", "true")
const twilightInfoElem = document.getElementById('twilight-info')
twilightInfoElem?.setAttribute('data-twilight', 'true')
// Update UI to show twilight mode with animation
const titleElem = document.getElementById("download-title")
const titleElem = document.getElementById('download-title')
if (titleElem) {
const zenText = titleElem.innerHTML
titleElem.innerHTML = zenText.replace("Zen", "Twilight")
titleElem.innerHTML = zenText.replace('Zen', 'Twilight')
}
const tags = document.querySelectorAll(".release-type-tag")
const tags = document.querySelectorAll('.release-type-tag')
for (const tag of tags) {
tag.innerHTML = tag.innerHTML.replace("Beta", "Twilight")
tag.innerHTML = tag.innerHTML.replace('Beta', 'Twilight')
}
// Apply twilight mode to all relevant elements
const coralElements = document.querySelectorAll(
".download-browser-logo, .release-type-tag, .decorative-gradient, .download-link, .download-arrow-icon, .download-card__icon, .checksum-icon-btn, .copy-btn, .flathub-download"
'.download-browser-logo, .release-type-tag, .decorative-gradient, .download-link, .download-arrow-icon, .download-card__icon, .checksum-icon-btn, .copy-btn, .flathub-download'
)
for (const element of coralElements) {
element.setAttribute("data-twilight", "true")
element.setAttribute('data-twilight', 'true')
}
// Replace all download links with twilight versions
const downloadLinks = document.querySelectorAll("a.download-link")
const downloadLinks = document.querySelectorAll('a.download-link')
for (const link of downloadLinks) {
if (!link.id.includes("beta")) {
const href = link.getAttribute("href")
if (href && href.includes("/latest/download/")) {
const twilightHref = href.replace("/latest/download/", "/download/twilight/")
link.setAttribute("href", twilightHref)
if (!link.id.includes('beta')) {
const href = link.getAttribute('href')
if (href && href.includes('/latest/download/')) {
const twilightHref = href.replace('/latest/download/', '/download/twilight/')
link.setAttribute('href', twilightHref)
}
}
}

View file

@ -22,7 +22,7 @@ interface PlatformReleases {
}
interface Props {
platform: "mac" | "windows" | "linux"
platform: 'mac' | 'windows' | 'linux'
icon: string[]
title: string
description: string
@ -30,19 +30,19 @@ interface Props {
}
const { platform, icon, title, description, releases } = Astro.props
import { Image } from "astro:assets"
import AppIconDark from "../../assets/app-icon-dark.png"
import AppIconLight from "../../assets/app-icon-light.png"
import DownloadCard from "./ButtonCard.astro"
import { Image } from 'astro:assets'
import AppIconDark from '../../assets/app-icon-dark.png'
import AppIconLight from '../../assets/app-icon-light.png'
import DownloadCard from './ButtonCard.astro'
function isFlatReleaseInfo(obj: unknown): obj is ReleaseInfo {
return !!obj && typeof obj === "object" && "link" in obj
return !!obj && typeof obj === 'object' && 'link' in obj
}
---
<div
id={`${platform}-downloads`}
data-active={platform === "mac"}
data-active={platform === 'mac'}
class="platform-section data-[active='false']:hidden"
>
<div class="items-center gap-8 md:flex">
@ -56,7 +56,7 @@ function isFlatReleaseInfo(obj: unknown): obj is ReleaseInfo {
<p class="text-muted-foreground mb-6" set:html={description} />
<div class="space-y-6">
{
platform === "linux" ? (
platform === 'linux' ? (
<>
{releases.flathub && releases.flathub.all.label && (
<article class="flathub-download data-[twilight='true']:hidden">
@ -71,16 +71,16 @@ function isFlatReleaseInfo(obj: unknown): obj is ReleaseInfo {
</article>
)}
{releases.x86_64 &&
typeof releases.x86_64 === "object" &&
"tarball" in releases.x86_64 &&
typeof releases.x86_64 === 'object' &&
'tarball' in releases.x86_64 &&
releases.x86_64.tarball && (
<article>
<h4 class="mb-3 text-lg font-medium">x86_64</h4>
<div class="">
{releases.x86_64.tarball && (
<DownloadCard
label={releases.x86_64.tarball.label ? releases.x86_64.tarball.label : ""}
href={releases.x86_64.tarball.link ? releases.x86_64.tarball.link : ""}
label={releases.x86_64.tarball.label ? releases.x86_64.tarball.label : ''}
href={releases.x86_64.tarball.link ? releases.x86_64.tarball.link : ''}
variant="x86_64"
checksum={releases.x86_64.tarball.checksum}
/>
@ -89,8 +89,8 @@ function isFlatReleaseInfo(obj: unknown): obj is ReleaseInfo {
</article>
)}
{releases.aarch64 &&
typeof releases.aarch64 === "object" &&
"tarball" in releases.aarch64 &&
typeof releases.aarch64 === 'object' &&
'tarball' in releases.aarch64 &&
releases.aarch64.tarball && (
<article>
<h4 class="mb-3 text-lg font-medium">ARM64</h4>
@ -98,9 +98,9 @@ function isFlatReleaseInfo(obj: unknown): obj is ReleaseInfo {
{releases.aarch64.tarball && (
<DownloadCard
label={
releases.aarch64.tarball.label ? releases.aarch64.tarball.label : ""
releases.aarch64.tarball.label ? releases.aarch64.tarball.label : ''
}
href={releases.aarch64.tarball.link ? releases.aarch64.tarball.link : ""}
href={releases.aarch64.tarball.link ? releases.aarch64.tarball.link : ''}
variant="aarch64"
checksum={releases.aarch64.tarball.checksum}
/>

View file

@ -6,42 +6,42 @@ export function getReleasesWithChecksums(checksums: Record<string, string>) {
return {
macos: {
universal: {
link: "https://github.com/zen-browser/desktop/releases/latest/download/zen.macos-universal.dmg",
label: "Universal",
checksum: checksums["zen.macos-universal.dmg"],
link: 'https://github.com/zen-browser/desktop/releases/latest/download/zen.macos-universal.dmg',
label: 'Universal',
checksum: checksums['zen.macos-universal.dmg'],
},
},
windows: {
x86_64: {
link: "https://github.com/zen-browser/desktop/releases/latest/download/zen.installer.exe",
label: "64-bit (Recommended)",
checksum: checksums["zen.installer.exe"],
link: 'https://github.com/zen-browser/desktop/releases/latest/download/zen.installer.exe',
label: '64-bit (Recommended)',
checksum: checksums['zen.installer.exe'],
},
arm64: {
link: "https://github.com/zen-browser/desktop/releases/latest/download/zen.installer-arm64.exe",
label: "ARM64",
checksum: checksums["zen.installer-arm64.exe"],
link: 'https://github.com/zen-browser/desktop/releases/latest/download/zen.installer-arm64.exe',
label: 'ARM64',
checksum: checksums['zen.installer-arm64.exe'],
},
},
linux: {
x86_64: {
tarball: {
link: "https://github.com/zen-browser/desktop/releases/latest/download/zen.linux-x86_64.tar.xz",
label: "Tarball",
checksum: checksums["zen.linux-x86_64.tar.xz"],
link: 'https://github.com/zen-browser/desktop/releases/latest/download/zen.linux-x86_64.tar.xz',
label: 'Tarball',
checksum: checksums['zen.linux-x86_64.tar.xz'],
},
},
aarch64: {
tarball: {
link: "https://github.com/zen-browser/desktop/releases/latest/download/zen.linux-aarch64.tar.xz",
label: "Tarball",
checksum: checksums["zen.linux-aarch64.tar.xz"],
link: 'https://github.com/zen-browser/desktop/releases/latest/download/zen.linux-aarch64.tar.xz',
label: 'Tarball',
checksum: checksums['zen.linux-aarch64.tar.xz'],
},
},
flathub: {
all: {
link: "https://flathub.org/apps/app.zen_browser.zen",
label: "Flathub",
link: 'https://flathub.org/apps/app.zen_browser.zen',
label: 'Flathub',
},
},
},

View file

@ -1,7 +1,7 @@
export const CHECKSUMS = {
"zen.macos-universal.dmg": "macsum",
"zen.installer.exe": "winsum",
"zen.installer-arm64.exe": "winarmsum",
"zen.linux-x86_64.tar.xz": "linuxsum",
"zen.linux-aarch64.tar.xz": "linuxarmsum",
'zen.macos-universal.dmg': 'macsum',
'zen.installer.exe': 'winsum',
'zen.installer-arm64.exe': 'winarmsum',
'zen.linux-x86_64.tar.xz': 'linuxsum',
'zen.linux-aarch64.tar.xz': 'linuxarmsum',
}

View file

@ -1,4 +1,4 @@
export const I18N = {
DEFAULT_LOCALE: "en",
LOCALES: [{ label: "English", value: "en" }],
DEFAULT_LOCALE: 'en',
LOCALES: [{ label: 'English', value: 'en' }],
} as const

View file

@ -1,5 +1,5 @@
import { CHECKSUMS } from "./checksum"
import { I18N } from "./i18n"
import { CHECKSUMS } from './checksum'
import { I18N } from './i18n'
export const CONSTANT = {
I18N,

View file

@ -1,8 +1,8 @@
import { useEffect, useState } from "react"
import { useEffect, useState } from 'react'
import { type ZenTheme } from "../mods"
import { type ZenTheme } from '../mods'
type SortOrder = "default" | "asc" | "desc"
type SortOrder = 'default' | 'asc' | 'desc'
type ModsSearchState = {
search: string
@ -17,9 +17,9 @@ const DEFAULT_LIMIT = 12
export function useModsSearch(mods: ZenTheme[]) {
const [searchParams, setSearchParams] = useState<URLSearchParams>()
const [state, setState] = useState<ModsSearchState>({
search: "",
createdSort: "desc",
updatedSort: "default",
search: '',
createdSort: 'desc',
updatedSort: 'default',
page: 1,
limit: DEFAULT_LIMIT,
})
@ -29,11 +29,11 @@ export function useModsSearch(mods: ZenTheme[]) {
const params = new URLSearchParams(window.location.search)
setSearchParams(params)
setState({
search: params.get("q") || "",
createdSort: (params.get("created") as SortOrder) || "desc",
updatedSort: (params.get("updated") as SortOrder) || "default",
page: Number.parseInt(params.get("page") || "1", 10),
limit: Number.parseInt(params.get("limit") || String(DEFAULT_LIMIT), 10),
search: params.get('q') || '',
createdSort: (params.get('created') as SortOrder) || 'desc',
updatedSort: (params.get('updated') as SortOrder) || 'default',
page: Number.parseInt(params.get('page') || '1', 10),
limit: Number.parseInt(params.get('limit') || String(DEFAULT_LIMIT), 10),
})
}, [])
@ -42,41 +42,41 @@ export function useModsSearch(mods: ZenTheme[]) {
if (!searchParams) return
if (state.search) {
searchParams.set("q", state.search)
searchParams.set('q', state.search)
} else {
searchParams.delete("q")
searchParams.delete('q')
}
if (state.createdSort !== "default") {
searchParams.set("created", state.createdSort)
if (state.createdSort !== 'default') {
searchParams.set('created', state.createdSort)
} else {
searchParams.delete("created")
searchParams.delete('created')
}
if (state.updatedSort !== "default") {
searchParams.set("updated", state.updatedSort)
if (state.updatedSort !== 'default') {
searchParams.set('updated', state.updatedSort)
} else {
searchParams.delete("updated")
searchParams.delete('updated')
}
if (state.page > 1) {
searchParams.set("page", state.page.toString())
searchParams.set('page', state.page.toString())
} else {
searchParams.delete("page")
searchParams.delete('page')
}
if (state.limit !== DEFAULT_LIMIT) {
searchParams.set("limit", state.limit.toString())
searchParams.set('limit', state.limit.toString())
} else {
searchParams.delete("limit")
searchParams.delete('limit')
}
const newUrl = `${window.location.pathname}${searchParams.toString() ? `?${searchParams.toString()}` : ""}`
const newUrl = `${window.location.pathname}${searchParams.toString() ? `?${searchParams.toString()}` : ''}`
if (state.page > 1) {
window.history.pushState({}, "", newUrl)
window.history.pushState({}, '', newUrl)
} else {
window.history.replaceState({}, "", newUrl)
window.history.replaceState({}, '', newUrl)
}
}, [state, searchParams])
@ -96,18 +96,18 @@ export function useModsSearch(mods: ZenTheme[]) {
}
// Sort by createdAt if chosen
if (state.createdSort !== "default") {
if (state.createdSort !== 'default') {
filtered.sort((a, b) => {
const diff = new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime()
return state.createdSort === "asc" ? diff : -diff
return state.createdSort === 'asc' ? diff : -diff
})
}
// Sort by updatedAt if chosen
if (state.updatedSort !== "default") {
if (state.updatedSort !== 'default') {
filtered.sort((a, b) => {
const diff = new Date(a.updatedAt).getTime() - new Date(b.updatedAt).getTime()
return state.updatedSort === "asc" ? diff : -diff
return state.updatedSort === 'asc' ? diff : -diff
})
}
@ -128,7 +128,7 @@ export function useModsSearch(mods: ZenTheme[]) {
setState(prev => ({
...prev,
createdSort:
prev.createdSort === "default" ? "asc" : prev.createdSort === "asc" ? "desc" : "default",
prev.createdSort === 'default' ? 'asc' : prev.createdSort === 'asc' ? 'desc' : 'default',
page: 1, // Reset page when sort changes
}))
}
@ -137,7 +137,7 @@ export function useModsSearch(mods: ZenTheme[]) {
setState(prev => ({
...prev,
updatedSort:
prev.updatedSort === "default" ? "asc" : prev.updatedSort === "asc" ? "desc" : "default",
prev.updatedSort === 'default' ? 'asc' : prev.updatedSort === 'asc' ? 'desc' : 'default',
page: 1, // Reset page when sort changes
}))
}

View file

@ -12,6 +12,6 @@ const { class: className, ...props } = Astro.props
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class:list={["lucide lucide-arrow-left-icon lucide-arrow-left", className]}
class:list={['lucide lucide-arrow-left-icon lucide-arrow-left', className]}
{...props}><path d="m12 19-7-7 7-7"></path><path d="M19 12H5"></path></svg
>

View file

@ -12,6 +12,6 @@ const { class: className, ...props } = Astro.props
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class:list={["lucide lucide-arrow-right-icon lucide-arrow-right", className]}
class:list={['lucide lucide-arrow-right-icon lucide-arrow-right', className]}
{...props}><path d="M5 12h14"></path><path d="m12 5 7 7-7 7"></path></svg
>

View file

@ -12,6 +12,6 @@ const { class: className, ...props } = Astro.props
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class:list={["lucide lucide-arrow-up-icon lucide-arrow-up", className]}
class:list={['lucide lucide-arrow-up-icon lucide-arrow-up', className]}
{...props}><path d="m5 12 7-7 7 7"></path><path d="M12 19V5"></path></svg
>

View file

@ -12,6 +12,6 @@ const { class: className, ...props } = Astro.props
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class:list={["lucide lucide-check-icon lucide-check", className]}
class:list={['lucide lucide-check-icon lucide-check', className]}
{...props}><path d="M20 6 9 17l-5-5"></path></svg
>

View file

@ -12,6 +12,6 @@ const { class: className, ...props } = Astro.props
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class:list={["lucide lucide-chevron-down-icon lucide-chevron-down", className]}
class:list={['lucide lucide-chevron-down-icon lucide-chevron-down', className]}
{...props}><path d="m6 9 6 6 6-6"></path></svg
>

View file

@ -12,7 +12,7 @@ const { class: className, ...props } = Astro.props
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class:list={["lucide lucide-download-icon lucide-download", className]}
class:list={['lucide lucide-download-icon lucide-download', className]}
{...props}
><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path><polyline points="7 10 12 15 17 10"
></polyline><line x1="12" x2="12" y1="15" y2="3"></line></svg

View file

@ -12,7 +12,7 @@ const { class: className, ...props } = Astro.props
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class:list={["lucide lucide-external-link-icon lucide-external-link", className]}
class:list={['lucide lucide-external-link-icon lucide-external-link', className]}
{...props}
><path d="M15 3h6v6"></path><path d="M10 14 21 3"></path><path
d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"></path></svg

View file

@ -12,7 +12,7 @@ const { class: className, ...props } = Astro.props
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class:list={["lucide lucide-github-icon lucide-github", className]}
class:list={['lucide lucide-github-icon lucide-github', className]}
{...props}
><path
d="M15 22v-4a4.8 4.8 0 0 0-1-3.5c3 0 6-2 6-5.5.08-1.25-.27-2.48-1-3.5.28-1.15.28-2.35 0-3.5 0 0-1 0-3 1.5-2.64-.5-5.36-.5-8 0C6 2 5 2 5 2c-.3 1.15-.3 2.35 0 3.5A5.403 5.403 0 0 0 4 9c0 3.5 3 5.5 6 5.5-.39.49-.68 1.05-.85 1.65-.17.6-.22 1.23-.15 1.85v4"

View file

@ -12,7 +12,7 @@ const { class: className, ...props } = Astro.props
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class:list={["lucide lucide-info-icon lucide-info", className]}
class:list={['lucide lucide-info-icon lucide-info', className]}
{...props}
><circle cx="12" cy="12" r="10"></circle><path d="M12 16v-4"></path><path d="M12 8h.01"
></path></svg

View file

@ -12,7 +12,7 @@ const { class: className, ...props } = Astro.props
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class:list={["lucide lucide-lock-icon lucide-lock", className]}
class:list={['lucide lucide-lock-icon lucide-lock', className]}
{...props}
><rect width="18" height="11" x="3" y="11" rx="2" ry="2"></rect><path d="M7 11V7a5 5 0 0 1 10 0v4"
></path></svg

View file

@ -12,6 +12,6 @@ const { class: className, ...props } = Astro.props
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class:list={["lucide lucide-menu-icon lucide-menu", className]}
class:list={['lucide lucide-menu-icon lucide-menu', className]}
{...props}><path d="M4 12h16"></path><path d="M4 18h16"></path><path d="M4 6h16"></path></svg
>

View file

@ -9,32 +9,32 @@ interface Props {
const { title, description, ogImage, isHome, redirect } = Astro.props
const defaultDescription =
"Zen Browser is built for speed, security, and true privacy. Download now to enjoy a beautifully-designed, distraction-free web experience packed with features."
const defaultOgImage = "/share-pic.png"
import "@fontsource/bricolage-grotesque/400.css"
import "@fontsource/bricolage-grotesque/500.css"
import "@fontsource/bricolage-grotesque/600.css"
import Footer from "~/components/Footer.astro"
import NavBar from "~/components/NavBar.astro"
import { getLocale } from "~/utils/i18n"
'Zen Browser is built for speed, security, and true privacy. Download now to enjoy a beautifully-designed, distraction-free web experience packed with features.'
const defaultOgImage = '/share-pic.png'
import '@fontsource/bricolage-grotesque/400.css'
import '@fontsource/bricolage-grotesque/500.css'
import '@fontsource/bricolage-grotesque/600.css'
import Footer from '~/components/Footer.astro'
import NavBar from '~/components/NavBar.astro'
import { getLocale } from '~/utils/i18n'
const locale = getLocale(Astro)
---
<script is:inline data-cfasync="false">
const theme = (() => {
if (typeof localStorage !== "undefined" && localStorage.getItem("theme")) {
return localStorage.getItem("theme") ?? "light"
if (typeof localStorage !== 'undefined' && localStorage.getItem('theme')) {
return localStorage.getItem('theme') ?? 'light'
}
if (window.matchMedia("(prefers-color-scheme: dark)").matches) {
return "dark"
if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
return 'dark'
}
return "light"
return 'light'
})()
if (theme === "light") {
document.documentElement.setAttribute("data-theme", "light")
if (theme === 'light') {
document.documentElement.setAttribute('data-theme', 'light')
} else {
document.documentElement.setAttribute("data-theme", "dark")
document.documentElement.setAttribute('data-theme', 'dark')
}
</script>
@ -55,10 +55,10 @@ const locale = getLocale(Astro)
is:inline
type="application/ld+json"
set:html={JSON.stringify({
"@context": "https://schema.org",
"@type": "WebSite",
name: "Zen Browser",
url: "https://zen-browser.app/",
'@context': 'https://schema.org',
'@type': 'WebSite',
name: 'Zen Browser',
url: 'https://zen-browser.app/',
})}
/>
</>
@ -91,7 +91,7 @@ const locale = getLocale(Astro)
<script>
// eslint-disable-next-line no-console
console.log(
"%c✌ Zen-Browser%c\nWelcome to a calmer internet!",
'%c✌ Zen-Browser%c\nWelcome to a calmer internet!',
'filter: invert(1); font-size: 28px; font-weight: bolder; font-family: "Rubik"; margin-top: 20px; margin-bottom: 8px;',
'color: #f76f53; font-size: 16px; font-family: "Rubik"; margin-bottom: 20px;'
)
@ -105,14 +105,14 @@ const locale = getLocale(Astro)
</html>
<style is:global>
@font-face {
font-family: "Junicode";
src: url("/fonts/JunicodeVF-Roman-subset.woff2") format("woff2");
font-family: 'Junicode';
src: url('/fonts/JunicodeVF-Roman-subset.woff2') format('woff2');
font-display: swap;
}
@font-face {
font-family: "Junicode-Italic";
src: url("/fonts/JunicodeVF-Italic-subset.woff2") format("woff2");
font-family: 'Junicode-Italic';
src: url('/fonts/JunicodeVF-Italic-subset.woff2') format('woff2');
font-display: swap;
}
@ -128,7 +128,7 @@ const locale = getLocale(Astro)
--zen-muted: rgba(0, 0, 0, 0.05);
--zen-subtle: rgba(0, 0, 0, 0.05);
&[data-theme="dark"] {
&[data-theme='dark'] {
--zen-paper: #1f1f1f;
--zen-dark: #d1cfc0;
--zen-muted: rgba(255, 255, 255, 0.05);
@ -142,17 +142,17 @@ const locale = getLocale(Astro)
body,
body > * {
font-family: "Bricolage Grotesque", sans-serif;
font-family: 'Bricolage Grotesque', sans-serif;
font-optical-sizing: auto;
font-style: normal;
font-weight: 500;
font-variation-settings: "wdth" 100;
font-variation-settings: 'wdth' 100;
}
h1 .italic {
font-family: "Junicode-Italic", serif;
font-family: 'Junicode-Italic', serif;
font-weight: 400;
font-feature-settings: "swsh" 0;
font-feature-settings: 'swsh' 0;
font-style: normal;
}

View file

@ -1,4 +1,4 @@
import { format } from "date-fns"
import { format } from 'date-fns'
export type ZenTheme = {
name: string
@ -17,7 +17,7 @@ export type ZenTheme = {
updatedAt: Date
}
const THEME_API = "https://zen-browser.github.io/theme-store/themes.json"
const THEME_API = 'https://zen-browser.github.io/theme-store/themes.json'
export async function getAllMods(): Promise<ZenTheme[]> {
try {
@ -37,5 +37,5 @@ export function getAuthorLink(author: string): string {
}
export function getLocalizedDate(date: Date): string {
return format(date, "PP")
return format(date, 'PP')
}

View file

@ -1,5 +1,5 @@
---
import NotFound from "./[...locale]/404.astro"
import NotFound from './[...locale]/404.astro'
---
<NotFound />

View file

@ -1,10 +1,10 @@
---
import Button from "~/components/Button.astro"
import Description from "~/components/Description.astro"
import Title from "~/components/Title.astro"
import Layout from "~/layouts/Layout.astro"
import { getLocale, getPath, getUI } from "~/utils/i18n"
export { getStaticPaths } from "~/utils/i18n"
import Button from '~/components/Button.astro'
import Description from '~/components/Description.astro'
import Title from '~/components/Title.astro'
import Layout from '~/layouts/Layout.astro'
import { getLocale, getPath, getUI } from '~/utils/i18n'
export { getStaticPaths } from '~/utils/i18n'
const locale = getLocale(Astro)
const getLocalePath = getPath(locale)
@ -25,7 +25,7 @@ const {
<p class="max-w-xl text-lg text-gray-500 dark:text-gray-400">
{notFound.description}
</p>
<Button href={getLocalePath("/")} isPrimary class="w-fit">
<Button href={getLocalePath('/')} isPrimary class="w-fit">
{notFound.button}
</Button>
</div>

View file

@ -1,10 +1,10 @@
---
import { Image } from "astro:assets"
import Description from "~/components/Description.astro"
import Layout from "~/layouts/Layout.astro"
import { getLocale, getUI } from "~/utils/i18n"
export { getStaticPaths } from "~/utils/i18n"
import Button from "~/components/Button.astro"
import { Image } from 'astro:assets'
import Description from '~/components/Description.astro'
import Layout from '~/layouts/Layout.astro'
import { getLocale, getUI } from '~/utils/i18n'
export { getStaticPaths } from '~/utils/i18n'
import Button from '~/components/Button.astro'
const locale = getLocale(Astro)
@ -38,7 +38,7 @@ const {
<ul class="flex flex-col gap-2">
{Object.entries(members).map(([_key, member]) => (
<li class="text-sm">
{member.link && typeof member.link === "string" ? (
{member.link && typeof member.link === 'string' ? (
<a href={member.link}>
<strong class="zen-link font-bold">{member.name}</strong>
</a>

View file

@ -1,10 +1,10 @@
---
import Button from "~/components/Button.astro"
import Description from "~/components/Description.astro"
import ArrowRightIcon from "~/icons/ArrowRightIcon.astro"
import Layout from "~/layouts/Layout.astro"
import { getLocale, getUI } from "~/utils/i18n"
export { getStaticPaths } from "~/utils/i18n"
import Button from '~/components/Button.astro'
import Description from '~/components/Description.astro'
import ArrowRightIcon from '~/icons/ArrowRightIcon.astro'
import Layout from '~/layouts/Layout.astro'
import { getLocale, getUI } from '~/utils/i18n'
export { getStaticPaths } from '~/utils/i18n'
const locale = getLocale(Astro)
const {

View file

@ -1,18 +1,18 @@
---
import Description from "~/components/Description.astro"
import DownloadScript from "~/components/download/DownloadScript.astro"
import PlatformDownload from "~/components/download/PlatformDownload.astro"
import { getReleasesWithChecksums } from "~/components/download/release-data"
import Layout from "~/layouts/Layout.astro"
import { getChecksums } from "~/utils/githubChecksums"
import { getLocale, getUI } from "~/utils/i18n"
import Description from '~/components/Description.astro'
import DownloadScript from '~/components/download/DownloadScript.astro'
import PlatformDownload from '~/components/download/PlatformDownload.astro'
import { getReleasesWithChecksums } from '~/components/download/release-data'
import Layout from '~/layouts/Layout.astro'
import { getChecksums } from '~/utils/githubChecksums'
import { getLocale, getUI } from '~/utils/i18n'
import { icon, library } from "@fortawesome/fontawesome-svg-core"
import { faApple, faGithub, faLinux, faWindows } from "@fortawesome/free-brands-svg-icons"
import ExternalLinkIcon from "~/icons/ExternalLink.astro"
import LockIcon from "~/icons/LockIcon.astro"
import { icon, library } from '@fortawesome/fontawesome-svg-core'
import { faApple, faGithub, faLinux, faWindows } from '@fortawesome/free-brands-svg-icons'
import ExternalLinkIcon from '~/icons/ExternalLink.astro'
import LockIcon from '~/icons/LockIcon.astro'
export { getStaticPaths } from "~/utils/i18n"
export { getStaticPaths } from '~/utils/i18n'
const locale = getLocale(Astro)
const {
@ -21,10 +21,10 @@ const {
} = getUI(locale)
library.add(faWindows, faLinux, faApple, faGithub)
const windowsIcon = icon({ prefix: "fab", iconName: "windows" })
const linuxIcon = icon({ prefix: "fab", iconName: "linux" })
const appleIcon = icon({ prefix: "fab", iconName: "apple" })
const githubIcon = icon({ prefix: "fab", iconName: "github" })
const windowsIcon = icon({ prefix: 'fab', iconName: 'windows' })
const linuxIcon = icon({ prefix: 'fab', iconName: 'linux' })
const appleIcon = icon({ prefix: 'fab', iconName: 'apple' })
const githubIcon = icon({ prefix: 'fab', iconName: 'github' })
const checksums = await getChecksums()
const releases = getReleasesWithChecksums(checksums)

View file

@ -1,8 +1,8 @@
import rss, { type RSSOptions } from "@astrojs/rss"
import rss, { type RSSOptions } from '@astrojs/rss'
import { releaseNotes, type ReleaseNote } from "~/release-notes"
import { releaseNotes, type ReleaseNote } from '~/release-notes'
export { getStaticPaths } from "~/utils/i18n"
export { getStaticPaths } from '~/utils/i18n'
/** The default number of entries to include in the RSS feed. */
const RSS_ENTRY_LIMIT = 20
@ -17,8 +17,8 @@ export function GET(context: { url: URL }) {
releaseNotes.length > 0 ? formatRssDate(releaseNotes[0].date as string) : new Date()
const rssData: RSSOptions = {
title: "Zen Browser Release Notes",
description: "Release Notes for the Zen Browser",
title: 'Zen Browser Release Notes',
description: 'Release Notes for the Zen Browser',
site: context.url,
items: [],
customData: `
@ -55,9 +55,9 @@ export function GET(context: { url: URL }) {
* @returns The passed in date string as a Date object.
*/
function formatRssDate(dateStr: string) {
const splitDate = dateStr.split("/")
const splitDate = dateStr.split('/')
if (splitDate.length !== 3) {
throw new Error("Invalid date format")
throw new Error('Invalid date format')
}
const day = Number(splitDate[0])
@ -78,43 +78,43 @@ function formatReleaseNote(releaseNote: ReleaseNote) {
</p>`
if (releaseNote.extra) {
content += `<p>${releaseNote.extra.replace(/(\n)/g, "<br />")}</p>`
content += `<p>${releaseNote.extra.replace(/(\n)/g, '<br />')}</p>`
}
content += addReleaseNoteSection(
"⚠️ Breaking changes",
'⚠️ Breaking changes',
releaseNote.breakingChanges?.map(breakingChangeToReleaseNote)
)
content += addReleaseNoteSection("✓ Fixes", releaseNote.fixes?.map(fixToReleaseNote))
content += addReleaseNoteSection("🖌 Theme Changes", releaseNote.themeChanges)
content += addReleaseNoteSection("⭐ Features", releaseNote.features)
content += addReleaseNoteSection('✓ Fixes', releaseNote.fixes?.map(fixToReleaseNote))
content += addReleaseNoteSection('🖌 Theme Changes', releaseNote.themeChanges)
content += addReleaseNoteSection('⭐ Features', releaseNote.features)
return content
}
function addReleaseNoteSection(title: string, items?: string[]): string {
if (!items) {
return ""
return ''
}
let content = `<h2>${title}</h2>`
content += "<ul>"
content += '<ul>'
for (const item of items) {
if (item && item.length > 0) {
content += `<li>${item}</li>`
}
}
content += "</ul>"
content += '</ul>'
return content
}
function fixToReleaseNote(fix?: Exclude<ReleaseNote["fixes"], undefined>[number]) {
if (typeof fix === "string") {
function fixToReleaseNote(fix?: Exclude<ReleaseNote['fixes'], undefined>[number]) {
if (typeof fix === 'string') {
return fix
}
if (!fix || !fix.description || fix.description.length === 0) {
return ""
return ''
}
let note = fix.description
@ -125,14 +125,14 @@ function fixToReleaseNote(fix?: Exclude<ReleaseNote["fixes"], undefined>[number]
}
function breakingChangeToReleaseNote(
breakingChange?: Exclude<ReleaseNote["breakingChanges"], undefined>[number]
breakingChange?: Exclude<ReleaseNote['breakingChanges'], undefined>[number]
) {
if (typeof breakingChange === "string") {
if (typeof breakingChange === 'string') {
return breakingChange
}
if (!breakingChange || !breakingChange.description || breakingChange.description.length === 0) {
return ""
return ''
}
return `${breakingChange.description} (<a href="${breakingChange.link}" target="_blank">Learn more</a>)`
@ -141,10 +141,10 @@ function breakingChangeToReleaseNote(
function pubDate(date?: Date) {
const newDate = date ?? new Date()
const pieces = newDate.toString().split(" ")
const pieces = newDate.toString().split(' ')
const offsetTime = pieces[5].match(/[-+]\d{4}/)
const offset = offsetTime ? offsetTime : pieces[5]
const parts = [`${pieces[0]},`, pieces[2], pieces[1], pieces[3], pieces[4], offset]
return parts.join(" ")
return parts.join(' ')
}

View file

@ -1,11 +1,11 @@
---
import Community from "~/components/Community.astro"
import Features from "~/components/Features.astro"
import Hero from "~/components/Hero.astro"
import Sponsors from "~/components/Sponsors.astro"
import Layout from "~/layouts/Layout.astro"
import { getLocale, getUI } from "~/utils/i18n"
export { getStaticPaths } from "~/utils/i18n"
import Community from '~/components/Community.astro'
import Features from '~/components/Features.astro'
import Hero from '~/components/Hero.astro'
import Sponsors from '~/components/Sponsors.astro'
import Layout from '~/layouts/Layout.astro'
import { getLocale, getUI } from '~/utils/i18n'
export { getStaticPaths } from '~/utils/i18n'
const locale = getLocale(Astro)

View file

@ -1,13 +1,13 @@
---
import BackButton from "~/components/BackButton.astro"
import Button from "~/components/Button.astro"
import Description from "~/components/Description.astro"
import ArrowRightIcon from "~/icons/ArrowRightIcon.astro"
import InfoIcon from "~/icons/InfoIcon.astro"
import Layout from "~/layouts/Layout.astro"
import { getAllMods, getAuthorLink, getLocalizedDate } from "~/mods"
import { getUI } from "~/utils/i18n"
import { getLocale, getOtherLocales } from "~/utils/i18n"
import BackButton from '~/components/BackButton.astro'
import Button from '~/components/Button.astro'
import Description from '~/components/Description.astro'
import ArrowRightIcon from '~/icons/ArrowRightIcon.astro'
import InfoIcon from '~/icons/InfoIcon.astro'
import Layout from '~/layouts/Layout.astro'
import { getAllMods, getAuthorLink, getLocalizedDate } from '~/mods'
import { getUI } from '~/utils/i18n'
import { getLocale, getOtherLocales } from '~/utils/i18n'
export async function getStaticPaths() {
const mods = await getAllMods()
@ -54,8 +54,8 @@ const {
---
<Layout
title={slug.title.replace("{name}", mod.name)}
description={slug.description.replace("{name}", mod.name)}
title={slug.title.replace('{name}', mod.name)}
description={slug.description.replace('{name}', mod.name)}
ogImage={mod.image}
>
<main class="mt-6 2xl:mt-0">
@ -93,14 +93,14 @@ const {
<div class="flex flex-shrink-0 flex-col gap-2 font-normal">
<p
set:html={slug.createdBy
.replace("{author}", mod.author)
.replace("{version}", mod.version)
.replace("{link}", getAuthorLink(mod.author))}
.replace('{author}', mod.author)
.replace('{version}', mod.version)
.replace('{link}', getAuthorLink(mod.author))}
/>
<p set:html={slug.creationDate.replace("{createdAt}", dates.createdAt)} />
<p set:html={slug.creationDate.replace('{createdAt}', dates.createdAt)} />
{
dates.createdAt !== dates.updatedAt && (
<p set:html={slug.latestUpdate.replace("{updatedAt}", dates.updatedAt)} />
<p set:html={slug.latestUpdate.replace('{updatedAt}', dates.updatedAt)} />
)
}
{
@ -112,13 +112,13 @@ const {
}
</div>
<div class="flex flex-col sm:items-end">
<Button class="hidden" id="install-theme" extra={{ "zen-theme-id": mod.id }} isPrimary>
<Button class="hidden" id="install-theme" extra={{ 'zen-theme-id': mod.id }} isPrimary>
{slug.installMod}
</Button>
<Button
class="hidden"
id="install-theme-uninstall"
extra={{ "zen-theme-id": mod.id }}
extra={{ 'zen-theme-id': mod.id }}
isPrimary
>
{slug.uninstallMod}

View file

@ -1,11 +1,11 @@
---
import Description from "~/components/Description.astro"
import ModsList from "~/components/ModsList"
import { CONSTANT } from "~/constants"
import Layout from "~/layouts/Layout.astro"
import { getAllMods } from "~/mods"
import { getLocale, getUI } from "~/utils/i18n"
export { getStaticPaths } from "~/utils/i18n"
import Description from '~/components/Description.astro'
import ModsList from '~/components/ModsList'
import { CONSTANT } from '~/constants'
import Layout from '~/layouts/Layout.astro'
import { getAllMods } from '~/mods'
import { getLocale, getUI } from '~/utils/i18n'
export { getStaticPaths } from '~/utils/i18n'
const locale = getLocale(Astro)

View file

@ -1,8 +1,8 @@
---
import Title from "~/components/Title.astro"
import Layout from "~/layouts/Layout.astro"
import { getLocale, getUI } from "~/utils/i18n"
export { getStaticPaths } from "~/utils/i18n"
import Title from '~/components/Title.astro'
import Layout from '~/layouts/Layout.astro'
import { getLocale, getUI } from '~/utils/i18n'
export { getStaticPaths } from '~/utils/i18n'
const locale = getLocale(Astro)

View file

@ -1,7 +1,7 @@
---
import Layout from "~/layouts/Layout.astro"
import { releaseNotes } from "~/release-notes"
import { getStaticPaths as getI18nPaths, getLocale, getUI } from "~/utils/i18n"
import Layout from '~/layouts/Layout.astro'
import { releaseNotes } from '~/release-notes'
import { getStaticPaths as getI18nPaths, getLocale, getUI } from '~/utils/i18n'
const locale = getLocale(Astro)
@ -20,7 +20,7 @@ export async function getStaticPaths() {
props: { ...release },
})),
{
params: { slug: "latest", locale },
params: { slug: 'latest', locale },
props: { ...releaseNotes[0] },
},
])
@ -31,6 +31,6 @@ const release = Astro.props
<Layout title={slug.title} redirect={`/release-notes#${release.version}`}>
<main class="flex flex-col items-center pb-52 pt-36">
{slug.redirect.replaceAll("{version}", release.version)}
{slug.redirect.replaceAll('{version}', release.version)}
</main>
</Layout>

View file

@ -1,13 +1,13 @@
---
import { Modal, ModalBody, ModalHeader } from "free-astro-components"
import Button from "~/components/Button.astro"
import Description from "~/components/Description.astro"
import ReleaseNoteItem from "~/components/ReleaseNoteItem.astro"
import ArrowUpIcon from "~/icons/ArrowUp.astro"
import Layout from "~/layouts/Layout.astro"
import { releaseNotes as releaseNotesData, releaseNotesTwilight } from "~/release-notes"
import { getLocale, getUI } from "~/utils/i18n"
export { getStaticPaths } from "~/utils/i18n"
import { Modal, ModalBody, ModalHeader } from 'free-astro-components'
import Button from '~/components/Button.astro'
import Description from '~/components/Description.astro'
import ReleaseNoteItem from '~/components/ReleaseNoteItem.astro'
import ArrowUpIcon from '~/icons/ArrowUp.astro'
import Layout from '~/layouts/Layout.astro'
import { releaseNotes as releaseNotesData, releaseNotesTwilight } from '~/release-notes'
import { getLocale, getUI } from '~/utils/i18n'
export { getStaticPaths } from '~/utils/i18n'
const locale = getLocale(Astro)
@ -26,7 +26,7 @@ const {
<p
class="text-base opacity-55"
set:html={releaseNotes.topSection.description.replaceAll(
"{latestVersion}",
'{latestVersion}',
releaseNotesData[0].version
)}
/>
@ -75,39 +75,39 @@ const {
</ModalBody>
</Modal>
<script>
import { closeModal, openModal } from "free-astro-components"
import { closeModal, openModal } from 'free-astro-components'
const scrollTopButton = document.getElementById("scroll-top")
const versionButton = document.getElementById("navigate-to-version")
const container = document.getElementById("release-notes")
const modal = document.getElementById("version-modal")
const versionList = document.getElementById("version-list")
const scrollTopButton = document.getElementById('scroll-top')
const versionButton = document.getElementById('navigate-to-version')
const container = document.getElementById('release-notes')
const modal = document.getElementById('version-modal')
const versionList = document.getElementById('version-list')
const toggleScrollButton = () => {
if (!scrollTopButton || !versionButton) return
const descriptionPosition = versionButton.getBoundingClientRect().bottom
if (descriptionPosition < 0) {
scrollTopButton.classList.remove("hidden")
scrollTopButton.classList.add("block")
scrollTopButton.classList.remove('hidden')
scrollTopButton.classList.add('block')
} else {
scrollTopButton.classList.remove("block")
scrollTopButton.classList.add("hidden")
scrollTopButton.classList.remove('block')
scrollTopButton.classList.add('hidden')
}
}
const navigateToVersion = (e: MouseEvent) => {
const target = e.target as HTMLElement
if (!container || !target?.closest("[data-version]")) return
if (!container || !target?.closest('[data-version]')) return
const version = target.getAttribute("data-version")
const version = target.getAttribute('data-version')
if (!version) return
window.location.hash = version
const versionDetails = document.getElementById(version)?.getElementsByTagName("details")
const versionDetails = document.getElementById(version)?.getElementsByTagName('details')
if (versionDetails && versionDetails.length > 0) {
Array.from(versionDetails).forEach(accordion => {
accordion.setAttribute("open", "")
accordion.setAttribute('open', '')
})
}
@ -119,21 +119,21 @@ const {
openModal(modal)
}
window.addEventListener("scroll", toggleScrollButton)
versionButton?.addEventListener("click", openVersionModal)
versionList?.addEventListener("click", navigateToVersion)
window.addEventListener('scroll', toggleScrollButton)
versionButton?.addEventListener('click', openVersionModal)
versionList?.addEventListener('click', navigateToVersion)
document.addEventListener("keydown", e => {
if (e.key === "Escape" && modal?.hasAttribute("open")) {
document.addEventListener('keydown', e => {
if (e.key === 'Escape' && modal?.hasAttribute('open')) {
closeModal(modal)
// Remove scroll lock if present
document.body.style.overflow = ""
document.body.style.overflow = ''
}
})
</script>
<style is:global>
#version-modal > * {
font-family: "Bricolage Grotesque", sans-serif !important;
font-family: 'Bricolage Grotesque', sans-serif !important;
}
</style>

View file

@ -1,8 +1,8 @@
---
import Features from "~/components/Features.astro"
import Layout from "~/layouts/Layout.astro"
import { getLocale, getUI } from "~/utils/i18n"
export { getStaticPaths } from "~/utils/i18n"
import Features from '~/components/Features.astro'
import Layout from '~/layouts/Layout.astro'
import { getLocale, getUI } from '~/utils/i18n'
export { getStaticPaths } from '~/utils/i18n'
const locale = getLocale(Astro)

View file

@ -1,16 +1,16 @@
---
import Button from "~/components/Button.astro"
import Description from "~/components/Description.astro"
import SocialMediaStrip from "~/components/SocialMediaStrip.astro"
import ArrowRightIcon from "~/icons/ArrowRightIcon.astro"
import Layout from "~/layouts/Layout.astro"
import Button from '~/components/Button.astro'
import Description from '~/components/Description.astro'
import SocialMediaStrip from '~/components/SocialMediaStrip.astro'
import ArrowRightIcon from '~/icons/ArrowRightIcon.astro'
import Layout from '~/layouts/Layout.astro'
import whatsNewVideo from "~/assets/whats-new.mp4"
import Video from "~/components/Video.astro"
import { releaseNotes } from "~/release-notes"
import whatsNewText from "~/release-notes/whats-new.json"
import { getLocale, getUI } from "~/utils/i18n"
export { getStaticPaths } from "~/utils/i18n"
import whatsNewVideo from '~/assets/whats-new.mp4'
import Video from '~/components/Video.astro'
import { releaseNotes } from '~/release-notes'
import whatsNewText from '~/release-notes/whats-new.json'
import { getLocale, getUI } from '~/utils/i18n'
export { getStaticPaths } from '~/utils/i18n'
const latestVersion = releaseNotes[0]
@ -22,24 +22,24 @@ const {
} = getUI(locale)
// Just redirect to the release notes if we are in a patch version
if (latestVersion.version.split(".").length > 2 && whatsNewText[1] !== latestVersion.version) {
if (latestVersion.version.split('.').length > 2 && whatsNewText[1] !== latestVersion.version) {
return Astro.redirect(`/release-notes#${latestVersion.version}`)
}
---
<Layout title={layout.whatsNew.title.replace("{latestVersion.version}", latestVersion.version)}>
<Layout title={layout.whatsNew.title.replace('{latestVersion.version}', latestVersion.version)}>
<main
class="xl:mt-22 container flex flex-col gap-12 py-12 xl:grid xl:min-h-[calc(100vh-12rem)] xl:grid-cols-[2fr_3fr]"
>
<div class="flex flex-col gap-8">
<div>
<Description class="text-5xl font-bold md:text-6xl"
>{whatsNew.title.replace("{latestVersion.version}", latestVersion.version)}</Description
>{whatsNew.title.replace('{latestVersion.version}', latestVersion.version)}</Description
>
<Description>{latestVersion.date}</Description>
</div>
<div>
<Fragment set:html={whatsNewText[0].replace(/\n/g, "<br>")} />
<Fragment set:html={whatsNewText[0].replace(/\n/g, '<br>')} />
</div>
<ul class="hidden list-disc flex-col gap-2 xl:container xl:flex">
<a href="https://github.com/zen-browser/desktop/issues/new/choose" target="_blank">

View file

@ -1,4 +1,4 @@
import releaseNotesStable from "./release-notes/stable.json"
import releaseNotesStable from './release-notes/stable.json'
type FixWithIssue = {
description: string
@ -25,12 +25,12 @@ export type ReleaseNote = {
}
export const releaseNotes: ReleaseNote[] = releaseNotesStable.reverse()
export { default as releaseNotesTwilight } from "./release-notes/twilight.json"
export { default as releaseNotesTwilight } from './release-notes/twilight.json'
export function getReleaseNoteFirefoxVersion(releaseNote: ReleaseNote): string | null {
// Check if "firefox" is on the feature list
for (const feature of releaseNote.features || []) {
if (feature.toLowerCase().includes("firefox")) {
if (feature.toLowerCase().includes('firefox')) {
// may be X or X.X or X.X.X
const match = feature.match(/(\d+(\.\d+){0,2})/)
if (match) {

View file

@ -1,101 +1,101 @@
import { experimental_AstroContainer as AstroContainer } from "astro/container"
import { beforeEach, describe, expect, it } from "vitest"
import { experimental_AstroContainer as AstroContainer } from 'astro/container'
import { beforeEach, describe, expect, it } from 'vitest'
import Button from "~/components/Button.astro"
import Button from '~/components/Button.astro'
describe("<Button />", () => {
describe('<Button />', () => {
let container: Awaited<ReturnType<typeof AstroContainer.create>>
beforeEach(async () => {
container = await AstroContainer.create()
})
describe("as <button>", () => {
it("renders default <button> with slot", async () => {
describe('as <button>', () => {
it('renders default <button> with slot', async () => {
const result = await container.renderToString(Button, {
props: {},
slots: { default: "Click me" },
slots: { default: 'Click me' },
})
expect(result).toContain("<button")
expect(result).toContain("Click me")
expect(result).toContain('<button')
expect(result).toContain('Click me')
})
it.each([
["isPrimary", { isPrimary: true }, "bg-dark"],
["isAlert", { isAlert: true }, "bg-red-300"],
["isBordered", { isBordered: true }, "border-2"],
])("applies %s style", async (_label, propObj, expectedClass) => {
['isPrimary', { isPrimary: true }, 'bg-dark'],
['isAlert', { isAlert: true }, 'bg-red-300'],
['isBordered', { isBordered: true }, 'border-2'],
])('applies %s style', async (_label, propObj, expectedClass) => {
const result = await container.renderToString(Button, {
props: { ...propObj },
slots: { default: "Test" },
slots: { default: 'Test' },
})
expect(result).toContain("<button")
expect(result).toContain('<button')
expect(result).toContain(expectedClass)
})
it("applies id and extra props", async () => {
it('applies id and extra props', async () => {
const result = await container.renderToString(Button, {
props: {
id: "my-btn",
extra: { "data-test": "foo" },
id: 'my-btn',
extra: { 'data-test': 'foo' },
},
slots: { default: "Test" },
slots: { default: 'Test' },
})
expect(result).toContain('id="my-btn"')
expect(result).toContain('data-test="foo"')
})
})
describe("as <a>", () => {
it("renders <a> with slot and href", async () => {
describe('as <a>', () => {
it('renders <a> with slot and href', async () => {
const result = await container.renderToString(Button, {
props: { href: "/link" },
slots: { default: "Go" },
props: { href: '/link' },
slots: { default: 'Go' },
})
expect(result).toContain("<a")
expect(result).toContain("Go")
expect(result).toContain('<a')
expect(result).toContain('Go')
expect(result).toContain('href="/en/link"')
})
it.each([
["isPrimary", { isPrimary: true }, "bg-dark"],
["isAlert", { isAlert: true }, "bg-red-300"],
["isBordered", { isBordered: true }, "border-2"],
])("applies %s style", async (_label, propObj, expectedClass) => {
['isPrimary', { isPrimary: true }, 'bg-dark'],
['isAlert', { isAlert: true }, 'bg-red-300'],
['isBordered', { isBordered: true }, 'border-2'],
])('applies %s style', async (_label, propObj, expectedClass) => {
const result = await container.renderToString(Button, {
props: { href: "/link", ...propObj },
slots: { default: "Test" },
props: { href: '/link', ...propObj },
slots: { default: 'Test' },
})
expect(result).toContain("<a")
expect(result).toContain('<a')
expect(result).toContain(expectedClass)
})
it("applies id and extra props", async () => {
it('applies id and extra props', async () => {
const result = await container.renderToString(Button, {
props: {
href: "/link",
id: "my-link",
extra: { "data-test": "bar" },
href: '/link',
id: 'my-link',
extra: { 'data-test': 'bar' },
},
slots: { default: "Test" },
slots: { default: 'Test' },
})
expect(result).toContain('id="my-link"')
expect(result).toContain('data-test="bar"')
})
})
it("applies custom className", async () => {
it('applies custom className', async () => {
const result = await container.renderToString(Button, {
props: { class: "custom-class" },
slots: { default: "Test" },
props: { class: 'custom-class' },
slots: { default: 'Test' },
})
expect(result).toContain("custom-class")
expect(result).toContain('custom-class')
})
it("uses locale path for href", async () => {
it('uses locale path for href', async () => {
const result = await container.renderToString(Button, {
props: { href: "/foo" },
slots: { default: "Test" },
props: { href: '/foo' },
slots: { default: 'Test' },
})
expect(result).toContain('href="/en/foo"')
})

View file

@ -1,48 +1,48 @@
import { experimental_AstroContainer as AstroContainer } from "astro/container"
import { beforeEach, describe, expect, it } from "vitest"
import { experimental_AstroContainer as AstroContainer } from 'astro/container'
import { beforeEach, describe, expect, it } from 'vitest'
import ButtonCard from "~/components/download/ButtonCard.astro"
import ButtonCard from '~/components/download/ButtonCard.astro'
describe("<ButtonCard />", () => {
describe('<ButtonCard />', () => {
let container: Awaited<ReturnType<typeof AstroContainer.create>>
beforeEach(async () => {
container = await AstroContainer.create()
})
it("renders with required props", async () => {
it('renders with required props', async () => {
const result = await container.renderToString(ButtonCard, {
props: {
label: "Download",
href: "/download",
label: 'Download',
href: '/download',
},
})
expect(result).toContain("Download")
expect(result).toContain('Download')
expect(result).toContain('href="/download"')
expect(result).not.toContain("Show SHA-256")
expect(result).not.toContain('Show SHA-256')
})
it("renders with checksum", async () => {
it('renders with checksum', async () => {
const result = await container.renderToString(ButtonCard, {
props: {
label: "Download",
href: "/download",
checksum: "sha256sum",
label: 'Download',
href: '/download',
checksum: 'sha256sum',
},
})
expect(result).toContain("Show SHA-256")
expect(result).toContain("sha256sum")
expect(result).toContain("Copy")
expect(result).toContain('Show SHA-256')
expect(result).toContain('sha256sum')
expect(result).toContain('Copy')
})
it("renders with variant", async () => {
it('renders with variant', async () => {
const result = await container.renderToString(ButtonCard, {
props: {
label: "Download",
href: "/download",
variant: "flathub",
label: 'Download',
href: '/download',
variant: 'flathub',
},
})
expect(result).toContain("Download")
expect(result).toContain("Beta")
expect(result).toContain('Download')
expect(result).toContain('Beta')
})
})

View file

@ -1,126 +1,126 @@
import { experimental_AstroContainer as AstroContainer } from "astro/container"
import { beforeEach, describe, expect, it } from "vitest"
import { experimental_AstroContainer as AstroContainer } from 'astro/container'
import { beforeEach, describe, expect, it } from 'vitest'
import PlatformDownload from "~/components/download/PlatformDownload.astro"
import PlatformDownload from '~/components/download/PlatformDownload.astro'
const mockIcon = ["<svg></svg>"]
const mockIcon = ['<svg></svg>']
const mockReleases = {
universal: { label: "Universal", link: "/universal", checksum: "abc123" },
x86_64: { label: "x86_64", link: "/x86_64", checksum: "def456" },
arm64: { label: "ARM64", link: "/arm64", checksum: "ghi789" },
flathub: { all: { label: "Flathub", link: "/flathub" } },
universal: { label: 'Universal', link: '/universal', checksum: 'abc123' },
x86_64: { label: 'x86_64', link: '/x86_64', checksum: 'def456' },
arm64: { label: 'ARM64', link: '/arm64', checksum: 'ghi789' },
flathub: { all: { label: 'Flathub', link: '/flathub' } },
}
describe("<PlatformDownload />", () => {
describe('<PlatformDownload />', () => {
let container: Awaited<ReturnType<typeof AstroContainer.create>>
beforeEach(async () => {
container = await AstroContainer.create()
})
it("renders mac platform", async () => {
it('renders mac platform', async () => {
const result = await container.renderToString(PlatformDownload, {
props: {
platform: "mac",
platform: 'mac',
icon: mockIcon,
title: "Mac Title",
description: "Mac Desc",
title: 'Mac Title',
description: 'Mac Desc',
releases: mockReleases,
},
})
expect(result).toContain("Mac Title")
expect(result).toContain("Mac Desc")
expect(result).toContain("Universal")
expect(result).toContain('Mac Title')
expect(result).toContain('Mac Desc')
expect(result).toContain('Universal')
})
it("renders windows platform", async () => {
it('renders windows platform', async () => {
const result = await container.renderToString(PlatformDownload, {
props: {
platform: "windows",
platform: 'windows',
icon: mockIcon,
title: "Win Title",
description: "Win Desc",
title: 'Win Title',
description: 'Win Desc',
releases: mockReleases,
},
})
expect(result).toContain("Win Title")
expect(result).toContain("Win Desc")
expect(result).toContain("x86_64")
expect(result).toContain("ARM64")
expect(result).toContain('Win Title')
expect(result).toContain('Win Desc')
expect(result).toContain('x86_64')
expect(result).toContain('ARM64')
})
it("renders linux platform with flathub and tarball", async () => {
it('renders linux platform with flathub and tarball', async () => {
const linuxReleases = {
flathub: { all: { label: "Flathub", link: "/flathub" } },
flathub: { all: { label: 'Flathub', link: '/flathub' } },
x86_64: {
tarball: {
label: "Tarball x86_64",
link: "/tarball-x86_64",
checksum: "sha256",
label: 'Tarball x86_64',
link: '/tarball-x86_64',
checksum: 'sha256',
},
},
}
const result = await container.renderToString(PlatformDownload, {
props: {
platform: "linux",
platform: 'linux',
icon: mockIcon,
title: "Linux Title",
description: "Linux Desc",
title: 'Linux Title',
description: 'Linux Desc',
releases: linuxReleases,
},
})
expect(result).toContain("Linux Title")
expect(result).toContain("Linux Desc")
expect(result).toContain("Flathub")
expect(result).toContain("Tarball")
expect(result).toContain("x86_64")
expect(result).toContain('Linux Title')
expect(result).toContain('Linux Desc')
expect(result).toContain('Flathub')
expect(result).toContain('Tarball')
expect(result).toContain('x86_64')
})
it("renders linux platform with all branches", async () => {
it('renders linux platform with all branches', async () => {
const linuxReleases = {
flathub: { all: { label: "Flathub", link: "/flathub" } },
flathub: { all: { label: 'Flathub', link: '/flathub' } },
x86_64: {
tarball: {
label: "Tarball x86_64",
link: "/tarball-x86_64",
checksum: "sha256",
label: 'Tarball x86_64',
link: '/tarball-x86_64',
checksum: 'sha256',
},
},
aarch64: {
tarball: {
label: "Tarball ARM64",
link: "/tarball-arm64",
checksum: "sha256-arm64",
label: 'Tarball ARM64',
link: '/tarball-arm64',
checksum: 'sha256-arm64',
},
},
}
const result = await container.renderToString(PlatformDownload, {
props: {
platform: "linux",
platform: 'linux',
icon: mockIcon,
title: "Linux Title",
description: "Linux Desc",
title: 'Linux Title',
description: 'Linux Desc',
releases: linuxReleases,
},
})
// Test basic content
expect(result).toContain("Linux Title")
expect(result).toContain("Linux Desc")
expect(result).toContain('Linux Title')
expect(result).toContain('Linux Desc')
// Test Flathub section
expect(result).toContain("Flathub")
expect(result).toContain("/flathub")
expect(result).toContain('Flathub')
expect(result).toContain('/flathub')
// Test x86_64 section
expect(result).toContain("x86_64")
expect(result).toContain("Tarball x86_64")
expect(result).toContain("/tarball-x86_64")
expect(result).toContain("sha256")
expect(result).toContain('x86_64')
expect(result).toContain('Tarball x86_64')
expect(result).toContain('/tarball-x86_64')
expect(result).toContain('sha256')
// Test ARM64 section
expect(result).toContain("ARM64")
expect(result).toContain("Tarball ARM64")
expect(result).toContain("/tarball-arm64")
expect(result).toContain("sha256-arm64")
expect(result).toContain('ARM64')
expect(result).toContain('Tarball ARM64')
expect(result).toContain('/tarball-arm64')
expect(result).toContain('sha256-arm64')
})
})

View file

@ -1,23 +1,23 @@
import { describe, expect, it } from "vitest"
import { describe, expect, it } from 'vitest'
import { getReleasesWithChecksums } from "~/components/download/release-data"
import { getReleasesWithChecksums } from '~/components/download/release-data'
describe("getReleasesWithChecksums", () => {
it("returns correct structure with checksums", () => {
describe('getReleasesWithChecksums', () => {
it('returns correct structure with checksums', () => {
const checksums = {
"zen.macos-universal.dmg": "macsum",
"zen.installer.exe": "winsum",
"zen.installer-arm64.exe": "winarmsum",
"zen.linux-x86_64.tar.xz": "linx86sum",
"zen.linux-aarch64.tar.xz": "linaarchsum",
'zen.macos-universal.dmg': 'macsum',
'zen.installer.exe': 'winsum',
'zen.installer-arm64.exe': 'winarmsum',
'zen.linux-x86_64.tar.xz': 'linx86sum',
'zen.linux-aarch64.tar.xz': 'linaarchsum',
}
const releases = getReleasesWithChecksums(checksums)
expect(releases.macos.universal.checksum).toBe("macsum")
expect(releases.windows.x86_64.checksum).toBe("winsum")
expect(releases.windows.arm64.checksum).toBe("winarmsum")
expect(releases.linux.x86_64.tarball.checksum).toBe("linx86sum")
expect(releases.linux.aarch64.tarball.checksum).toBe("linaarchsum")
expect(releases.linux.flathub.all.label).toBe("Flathub")
expect(releases.linux.flathub.all.link).toBe("https://flathub.org/apps/app.zen_browser.zen")
expect(releases.macos.universal.checksum).toBe('macsum')
expect(releases.windows.x86_64.checksum).toBe('winsum')
expect(releases.windows.arm64.checksum).toBe('winarmsum')
expect(releases.linux.x86_64.tarball.checksum).toBe('linx86sum')
expect(releases.linux.aarch64.tarball.checksum).toBe('linaarchsum')
expect(releases.linux.flathub.all.label).toBe('Flathub')
expect(releases.linux.flathub.all.link).toBe('https://flathub.org/apps/app.zen_browser.zen')
})
})

View file

@ -1,7 +1,7 @@
import { expect, test, type BrowserContextOptions, type Page } from "@playwright/test"
import { expect, test, type BrowserContextOptions, type Page } from '@playwright/test'
import { getReleasesWithChecksums } from "~/components/download/release-data"
import { CONSTANT } from "~/constants"
import { getReleasesWithChecksums } from '~/components/download/release-data'
import { CONSTANT } from '~/constants'
// Helper to get the platform section by id
const getPlatformSection = (page: Page, platform: string) =>
@ -17,68 +17,68 @@ const _ = (page: Page, platform: string, label: string) =>
const platformConfigs: { name: string; userAgent: string; platform: string }[] = [
{
name: "windows",
name: 'windows',
userAgent:
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
platform: "Win32",
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
platform: 'Win32',
},
{
name: "mac",
name: 'mac',
userAgent:
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.1 Safari/605.1.15",
platform: "MacIntel",
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.1 Safari/605.1.15',
platform: 'MacIntel',
},
{
name: "linux",
name: 'linux',
userAgent:
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
platform: "Linux x86_64",
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
platform: 'Linux x86_64',
},
]
test.describe("Download page default tab per platform", () => {
test.describe('Download page default tab per platform', () => {
for (const { name, userAgent, platform } of platformConfigs) {
test(`shows correct default tab for ${name} platform`, async ({ browser }) => {
const context = await browser.newContext({
userAgent,
locale: "en-US",
locale: 'en-US',
platform,
} as BrowserContextOptions)
const page = await context.newPage()
await page.goto("/download")
await page.goto('/download')
await expect(getPlatformSection(page, name)).toBeVisible()
await expect(getPlatformButton(page, name)).toHaveAttribute("data-active", "true")
await expect(getPlatformButton(page, name)).toHaveAttribute('data-active', 'true')
// Other platforms should not be active
for (const other of platformConfigs.filter(p => p.name !== name)) {
await expect(getPlatformSection(page, other.name)).toBeHidden()
await expect(getPlatformButton(page, other.name)).not.toHaveAttribute("data-active", "true")
await expect(getPlatformButton(page, other.name)).not.toHaveAttribute('data-active', 'true')
}
await context.close()
})
}
})
test.describe("Download page platform detection and tab switching", () => {
test("shows correct platform section and tab when switching platforms", async ({ page }) => {
await page.goto("/download")
const platforms = ["windows", "mac", "linux"]
test.describe('Download page platform detection and tab switching', () => {
test('shows correct platform section and tab when switching platforms', async ({ page }) => {
await page.goto('/download')
const platforms = ['windows', 'mac', 'linux']
for (const platform of platforms) {
await getPlatformButton(page, platform).click()
await expect(getPlatformSection(page, platform)).toBeVisible()
await expect(getPlatformButton(page, platform)).toHaveAttribute("data-active", "true")
await expect(getPlatformButton(page, platform)).toHaveAttribute('data-active', 'true')
// other platform sections should be hidden
for (const otherPlatform of platforms.filter(p => p !== platform)) {
await expect(getPlatformSection(page, otherPlatform)).toBeHidden()
await expect(getPlatformButton(page, otherPlatform)).not.toHaveAttribute(
"data-active",
"true"
'data-active',
'true'
)
}
}
})
})
test.describe("Download page download links", () => {
test.describe('Download page download links', () => {
const releases = getReleasesWithChecksums(CONSTANT.CHECKSUMS)
function getPlatformLinks(releases: ReturnType<typeof getReleasesWithChecksums>) {
@ -93,11 +93,11 @@ test.describe("Download page download links", () => {
}
}
test("all platform download links are correct", async ({ page }) => {
const platforms = ["windows", "mac", "linux"]
test('all platform download links are correct', async ({ page }) => {
const platforms = ['windows', 'mac', 'linux']
const platformLinkSelectors = getPlatformLinks(releases)
await page.goto("/download")
await page.waitForLoadState("domcontentloaded")
await page.goto('/download')
await page.waitForLoadState('domcontentloaded')
for (const platform of platforms) {
await getPlatformButton(page, platform).click()
for (const { label, link } of platformLinkSelectors[
@ -105,7 +105,7 @@ test.describe("Download page download links", () => {
]) {
const downloadLink = page.locator(`#${platform}-downloads .download-link[href="${link}"]`)
await expect(downloadLink).toContainText(label)
await expect(downloadLink).toHaveAttribute("href", link)
await expect(downloadLink).toHaveAttribute('href', link)
}
}
})

View file

@ -1,11 +1,11 @@
import { expect, test } from "@playwright/test"
import { expect, test } from '@playwright/test'
test("clicking back button navigates to previous page", async ({ page }) => {
await page.goto("/mods?created=asc")
test('clicking back button navigates to previous page', async ({ page }) => {
await page.goto('/mods?created=asc')
const currentUrl = page.url()
const modCards = await page.locator(".mod-card").all()
const modCards = await page.locator('.mod-card').all()
await modCards[0].click()
await page.getByRole("button", { name: "Back" }).click()
await page.getByRole('button', { name: 'Back' }).click()
await page.waitForURL(currentUrl)
expect(page.url()).toStrictEqual(currentUrl)
})

View file

@ -1,7 +1,7 @@
import { expect, test } from "@playwright/test"
import { expect, test } from '@playwright/test'
test("all routes do not return 404", async ({ page }) => {
const routes = ["/", "/welcome", "/about", "/privacy-policy", "/download", "/donate", "/whatsnew"]
test('all routes do not return 404', async ({ page }) => {
const routes = ['/', '/welcome', '/about', '/privacy-policy', '/download', '/donate', '/whatsnew']
for (const route of routes) {
const response = await page.goto(route)
expect(response?.status()).not.toBe(404)

View file

@ -1,9 +1,9 @@
import { vi } from "vitest"
import { vi } from 'vitest'
import translation from "~/i18n/en/translation.json"
import translation from '~/i18n/en/translation.json'
vi.mock("~/utils/i18n", () => ({
getLocale: () => "en",
vi.mock('~/utils/i18n', () => ({
getLocale: () => 'en',
getPath: () => (href: string) => `/en${href}`,
getUI: () => translation,
}))

View file

@ -1,4 +1,4 @@
import { CONSTANT } from "~/constants"
import { CONSTANT } from '~/constants'
/**
* Fetches the latest release notes from GitHub and parses the SHA-256 checksums.
@ -8,11 +8,11 @@ export async function getChecksums() {
if (import.meta.env.DEV) {
return CONSTANT.CHECKSUMS
}
const res = await fetch("https://api.github.com/repos/zen-browser/desktop/releases/latest", {
const res = await fetch('https://api.github.com/repos/zen-browser/desktop/releases/latest', {
headers: {
Accept: "application/vnd.github+json",
"X-GitHub-Api-Version": "2022-11-28",
"User-Agent": "zen-browser-checksum-fetcher",
Accept: 'application/vnd.github+json',
'X-GitHub-Api-Version': '2022-11-28',
'User-Agent': 'zen-browser-checksum-fetcher',
},
})
if (!res.ok) throw new Error(`Failed to fetch GitHub release: ${res.statusText}`)
@ -23,7 +23,7 @@ export async function getChecksums() {
const match = body.match(/File Checksums \(SHA-256\)[\s\S]*?```([\s\S]*?)```/)
const checksums: Record<string, string> = {}
if (match?.[1]) {
for (const line of match[1].split("\n")) {
for (const line of match[1].split('\n')) {
const [hash, filename] = line.trim().split(/\s+/, 2)
if (hash && filename) checksums[filename] = hash
}

View file

@ -1,7 +1,7 @@
import { type GetStaticPaths } from "astro"
import { type GetStaticPaths } from 'astro'
import { CONSTANT } from "~/constants"
import UI_EN from "~/i18n/en/translation.json"
import { CONSTANT } from '~/constants'
import UI_EN from '~/i18n/en/translation.json'
/**
* Represents the available locales in the application
@ -16,7 +16,7 @@ export type Locale = (typeof locales)[number]
*/
export const getPath = (locale?: Locale) => (path: string) => {
if (locale && locale !== CONSTANT.I18N.DEFAULT_LOCALE && !path.startsWith(`/${locale}`)) {
return `/${locale}${path.startsWith("/") ? "" : "/"}${path}`
return `/${locale}${path.startsWith('/') ? '' : '/'}${path}`
}
return path
}
@ -86,10 +86,10 @@ export const getUI = (locale?: Locale | string): UI => {
*/
function deepMerge<T extends object>(defaultObj: T, overrideObj: Partial<T>): T {
// Handle non-object cases
if (typeof defaultObj !== "object" || defaultObj === null) {
if (typeof defaultObj !== 'object' || defaultObj === null) {
return (overrideObj ?? defaultObj) as T
}
if (typeof overrideObj !== "object" || overrideObj === null) {
if (typeof overrideObj !== 'object' || overrideObj === null) {
return (overrideObj ?? defaultObj) as T
}
@ -105,8 +105,8 @@ export const getUI = (locale?: Locale | string): UI => {
if (
defaultValue !== null &&
overrideValue !== null &&
typeof defaultValue === "object" &&
typeof overrideValue === "object"
typeof defaultValue === 'object' &&
typeof overrideValue === 'object'
) {
// Type assertion to handle nested merging
;(result as Record<keyof T, unknown>)[key] = deepMerge(

View file

@ -1,5 +1,5 @@
import { type ClassValue, clsx } from "clsx"
import { twMerge } from "tailwind-merge"
import { type ClassValue, clsx } from 'clsx'
import { twMerge } from 'tailwind-merge'
export const cn = (...inputs: ClassValue[]) => {
return twMerge(clsx(inputs))