mirror of
https://github.com/zen-browser/www.git
synced 2025-07-07 08:55:32 +02:00
chore(prettier): update prettier config
This commit is contained in:
parent
ceef83d609
commit
a77c141d35
66 changed files with 709 additions and 709 deletions
|
@ -7,7 +7,7 @@ export default {
|
||||||
tabWidth: 2,
|
tabWidth: 2,
|
||||||
useTabs: false,
|
useTabs: false,
|
||||||
semi: false,
|
semi: false,
|
||||||
singleQuote: false,
|
singleQuote: true,
|
||||||
quoteProps: "as-needed",
|
quoteProps: "as-needed",
|
||||||
trailingComma: "es5",
|
trailingComma: "es5",
|
||||||
bracketSpacing: true,
|
bracketSpacing: true,
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
export function getTitleAnimation(delay = 0, duration = 0.3, once = true) {
|
export function getTitleAnimation(delay = 0, duration = 0.3, once = true) {
|
||||||
return {
|
return {
|
||||||
initial: { opacity: 0.001, translateY: 20, filter: "blur(4px)" },
|
initial: { opacity: 0.001, translateY: 20, filter: 'blur(4px)' },
|
||||||
whileInView: {
|
whileInView: {
|
||||||
opacity: 1,
|
opacity: 1,
|
||||||
translateY: 0,
|
translateY: 0,
|
||||||
filter: "blur(0px)",
|
filter: 'blur(0px)',
|
||||||
transition: { duration, delay },
|
transition: { duration, delay },
|
||||||
},
|
},
|
||||||
viewport: { once },
|
viewport: { once },
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
---
|
---
|
||||||
import ArrowLeftIcon from "~/icons/ArrowLeftIcon.astro"
|
import ArrowLeftIcon from '~/icons/ArrowLeftIcon.astro'
|
||||||
import { getLocale, getUI } from "~/utils/i18n"
|
import { getLocale, getUI } from '~/utils/i18n'
|
||||||
|
|
||||||
const locale = getLocale(Astro)
|
const locale = getLocale(Astro)
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
---
|
---
|
||||||
import { getLocale, getPath } from "~/utils/i18n"
|
import { getLocale, getPath } from '~/utils/i18n'
|
||||||
|
|
||||||
const locale = getLocale(Astro)
|
const locale = getLocale(Astro)
|
||||||
const getLocalePath = getPath(locale)
|
const getLocalePath = getPath(locale)
|
||||||
|
@ -13,15 +13,15 @@ const { class: className, isPrimary, isAlert, isBordered, href, id, extra } = As
|
||||||
{...extra}
|
{...extra}
|
||||||
href={getLocalePath(href)}
|
href={getLocalePath(href)}
|
||||||
class:list={[
|
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,
|
className,
|
||||||
isPrimary
|
isPrimary
|
||||||
? "border-dark bg-dark text-paper shadow-lg"
|
? 'border-dark bg-dark text-paper shadow-lg'
|
||||||
: isAlert
|
: isAlert
|
||||||
? "bg-red-300 text-dark"
|
? 'bg-red-300 text-dark'
|
||||||
: !isBordered
|
: !isBordered
|
||||||
? "bg-subtle"
|
? 'bg-subtle'
|
||||||
: "!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 />
|
<slot />
|
||||||
|
@ -31,15 +31,15 @@ const { class: className, isPrimary, isAlert, isBordered, href, id, extra } = As
|
||||||
id={id}
|
id={id}
|
||||||
{...extra}
|
{...extra}
|
||||||
class:list={[
|
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,
|
className,
|
||||||
isPrimary
|
isPrimary
|
||||||
? "border-dark bg-dark text-paper shadow-md"
|
? 'border-dark bg-dark text-paper shadow-md'
|
||||||
: isAlert
|
: isAlert
|
||||||
? "bg-red-300 text-dark"
|
? 'bg-red-300 text-dark'
|
||||||
: !isBordered
|
: !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 />
|
<slot />
|
||||||
|
|
|
@ -4,14 +4,14 @@ const sizes = [216, 396, 576, 756]
|
||||||
const borderWidths = [20, 30, 40, 50]
|
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">
|
<div class="mx-auto opacity-10 lg:opacity-100">
|
||||||
{
|
{
|
||||||
[...Array(4)].map((_, i) => (
|
[...Array(4)].map((_, i) => (
|
||||||
<div
|
<div
|
||||||
class:list={[
|
class:list={[
|
||||||
"absolute -translate-x-1/2 -translate-y-1/2 rounded-full",
|
'absolute -translate-x-1/2 -translate-y-1/2 rounded-full',
|
||||||
white ? "border-paper" : "border-coral",
|
white ? 'border-paper' : 'border-coral',
|
||||||
]}
|
]}
|
||||||
style={{
|
style={{
|
||||||
width: `${multiplier * sizes[i]}px`,
|
width: `${multiplier * sizes[i]}px`,
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
---
|
---
|
||||||
import Image from "astro/components/Image.astro"
|
import Image from 'astro/components/Image.astro'
|
||||||
import { motion } from "motion/react"
|
import { motion } from 'motion/react'
|
||||||
import { getTitleAnimation } from "~/animations"
|
import { getTitleAnimation } from '~/animations'
|
||||||
import ComImage from "~/assets/ComImage.png"
|
import ComImage from '~/assets/ComImage.png'
|
||||||
import Button from "~/components/Button.astro"
|
import Button from '~/components/Button.astro'
|
||||||
import Description from "~/components/Description.astro"
|
import Description from '~/components/Description.astro'
|
||||||
import CheckIcon from "~/icons/CheckIcon.astro"
|
import CheckIcon from '~/icons/CheckIcon.astro'
|
||||||
import GitHubIcon from "~/icons/GitHubIcon.astro"
|
import GitHubIcon from '~/icons/GitHubIcon.astro'
|
||||||
import { getLocale, getUI } from "~/utils/i18n"
|
import { getLocale, getUI } from '~/utils/i18n'
|
||||||
|
|
||||||
const locale = getLocale(Astro)
|
const locale = getLocale(Astro)
|
||||||
|
|
||||||
|
@ -38,7 +38,7 @@ const {
|
||||||
</motion.p>
|
</motion.p>
|
||||||
<div class="flex w-full flex-wrap gap-3 sm:gap-10 md:justify-center">
|
<div class="flex w-full flex-wrap gap-3 sm:gap-10 md:justify-center">
|
||||||
<motion.span client:load {...getTitleAnimation(0.8)}>
|
<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" />
|
<GitHubIcon class="size-4" />
|
||||||
<span>{community.lists.freeAndOpenSource.title}</span>
|
<span>{community.lists.freeAndOpenSource.title}</span>
|
||||||
</Button>
|
</Button>
|
||||||
|
|
|
@ -1,15 +1,15 @@
|
||||||
---
|
---
|
||||||
import { motion } from "motion/react"
|
import { motion } from 'motion/react'
|
||||||
import { getTitleAnimation } from "~/animations"
|
import { getTitleAnimation } from '~/animations'
|
||||||
import Description from "~/components/Description.astro"
|
import Description from '~/components/Description.astro'
|
||||||
|
|
||||||
import CompactModeVideo from "~/assets/CompactMode.webm"
|
import CompactModeVideo from '~/assets/CompactMode.webm'
|
||||||
import GlanceVideo from "~/assets/Glance.webm"
|
import GlanceVideo from '~/assets/Glance.webm'
|
||||||
import SplitViewsVideo from "~/assets/SplitViews.webm"
|
import SplitViewsVideo from '~/assets/SplitViews.webm'
|
||||||
import WorkspacesVideo from "~/assets/Workspaces.webm"
|
import WorkspacesVideo from '~/assets/Workspaces.webm'
|
||||||
|
|
||||||
import { getLocale, getUI } from "~/utils/i18n"
|
import { getLocale, getUI } from '~/utils/i18n'
|
||||||
import Video from "./Video.astro"
|
import Video from './Video.astro'
|
||||||
|
|
||||||
const locale = getLocale(Astro)
|
const locale = getLocale(Astro)
|
||||||
|
|
||||||
|
@ -161,17 +161,17 @@ const descriptions = Object.values(features.featureTabs).map(tab => tab.descript
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
const features = document.querySelectorAll(".feature, .feature-tab") as NodeListOf<HTMLElement>
|
const features = document.querySelectorAll('.feature, .feature-tab') as NodeListOf<HTMLElement>
|
||||||
|
|
||||||
// Set initial description
|
// Set initial description
|
||||||
const descriptionEl = document.querySelector(".feature-description") as HTMLDivElement
|
const descriptionEl = document.querySelector('.feature-description') as HTMLDivElement
|
||||||
const descriptions = descriptionEl?.dataset.descriptions?.split(",")
|
const descriptions = descriptionEl?.dataset.descriptions?.split(',')
|
||||||
if (descriptionEl && descriptions) {
|
if (descriptionEl && descriptions) {
|
||||||
descriptionEl.textContent = descriptions[0]
|
descriptionEl.textContent = descriptions[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
function changeToFeature({ target }: { target: HTMLElement | undefined | null }) {
|
function changeToFeature({ target }: { target: HTMLElement | undefined | null }) {
|
||||||
target = target?.closest(".feature, .feature-tab")
|
target = target?.closest('.feature, .feature-tab')
|
||||||
if (!target) return
|
if (!target) return
|
||||||
|
|
||||||
const index = Array.from(features).indexOf(target) % 4
|
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
|
// Update both mobile and desktop elements
|
||||||
features.forEach((f, i) => {
|
features.forEach((f, i) => {
|
||||||
if (i % 4 === index) {
|
if (i % 4 === index) {
|
||||||
f.setAttribute("data-active", "true")
|
f.setAttribute('data-active', 'true')
|
||||||
} else {
|
} else {
|
||||||
f.removeAttribute("data-active")
|
f.removeAttribute('data-active')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// Update mobile description
|
// Update mobile description
|
||||||
const descriptionEl = document.querySelector(".feature-description")
|
const descriptionEl = document.querySelector('.feature-description')
|
||||||
if (descriptionEl && descriptions) {
|
if (descriptionEl && descriptions) {
|
||||||
descriptionEl.textContent = descriptions[index]
|
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) => {
|
videos.forEach((vid, i) => {
|
||||||
const yOffset = (i - index) * 20
|
const yOffset = (i - index) * 20
|
||||||
const zOffset = i === index ? 0 : -100 - Math.abs(i - index) * 50
|
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
|
const rotation = (i - index) * 3
|
||||||
|
|
||||||
if (i === index) {
|
if (i === index) {
|
||||||
vid.setAttribute("data-active", "true")
|
vid.setAttribute('data-active', 'true')
|
||||||
vid.style.opacity = "1"
|
vid.style.opacity = '1'
|
||||||
vid.style.transform = `translate3d(-50%, 0, 0) scale(${scale})`
|
vid.style.transform = `translate3d(-50%, 0, 0) scale(${scale})`
|
||||||
vid.style.zIndex = "10"
|
vid.style.zIndex = '10'
|
||||||
vid.currentTime = 0
|
vid.currentTime = 0
|
||||||
vid.play()
|
vid.play()
|
||||||
} else {
|
} else {
|
||||||
vid.removeAttribute("data-active")
|
vid.removeAttribute('data-active')
|
||||||
vid.style.transform = `translate3d(-50%, ${yOffset}px, ${zOffset}px)
|
vid.style.transform = `translate3d(-50%, ${yOffset}px, ${zOffset}px)
|
||||||
rotate3d(1, 0, 0, ${rotation}deg)
|
rotate3d(1, 0, 0, ${rotation}deg)
|
||||||
scale(${scale})`
|
scale(${scale})`
|
||||||
|
@ -219,7 +219,7 @@ const descriptions = Object.values(features.featureTabs).map(tab => tab.descript
|
||||||
|
|
||||||
for (const feature of features) {
|
for (const feature of features) {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// 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
|
// 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;
|
@apply w-full cursor-pointer select-none rounded-lg p-4 opacity-0 hover:bg-subtle;
|
||||||
transition: background 0.2s ease-in-out;
|
transition: background 0.2s ease-in-out;
|
||||||
|
|
||||||
&[data-active="true"] {
|
&[data-active='true'] {
|
||||||
@apply bg-subtle;
|
@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;
|
@apply rounded-lg px-4 py-2 text-lg font-medium opacity-0 hover:bg-subtle;
|
||||||
transition: background 0.2s ease-in-out;
|
transition: background 0.2s ease-in-out;
|
||||||
|
|
||||||
&[data-active="true"] {
|
&[data-active='true'] {
|
||||||
@apply bg-subtle;
|
@apply bg-subtle;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -279,7 +279,7 @@ const descriptions = Object.values(features.featureTabs).map(tab => tab.descript
|
||||||
display: none;
|
display: none;
|
||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
|
|
||||||
&[data-active="true"] {
|
&[data-active='true'] {
|
||||||
display: block;
|
display: block;
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
---
|
---
|
||||||
import Button from "~/components/Button.astro"
|
import Button from '~/components/Button.astro'
|
||||||
import Circles from "~/components/Circles.astro"
|
import Circles from '~/components/Circles.astro'
|
||||||
import Description from "~/components/Description.astro"
|
import Description from '~/components/Description.astro'
|
||||||
import SocialMediaStrip from "~/components/SocialMediaStrip.astro"
|
import SocialMediaStrip from '~/components/SocialMediaStrip.astro'
|
||||||
import ArrowRightIcon from "~/icons/ArrowRightIcon.astro"
|
import ArrowRightIcon from '~/icons/ArrowRightIcon.astro'
|
||||||
import { getLocale, getPath, getUI } from "~/utils/i18n"
|
import { getLocale, getPath, getUI } from '~/utils/i18n'
|
||||||
|
|
||||||
const locale = getLocale(Astro)
|
const locale = getLocale(Astro)
|
||||||
const getLocalePath = getPath(locale)
|
const getLocalePath = getPath(locale)
|
||||||
|
@ -61,12 +61,12 @@ const {
|
||||||
<nav aria-label="About navigation">
|
<nav aria-label="About navigation">
|
||||||
<ul class="grid gap-2 opacity-80">
|
<ul class="grid gap-2 opacity-80">
|
||||||
<li>
|
<li>
|
||||||
<a href={getLocalePath("/about")} class="font-normal"
|
<a href={getLocalePath('/about')} class="font-normal"
|
||||||
>{footer.teamAndContributors}</a
|
>{footer.teamAndContributors}</a
|
||||||
>
|
>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href={getLocalePath("/privacy-policy")} class="font-normal"
|
<a href={getLocalePath('/privacy-policy')} class="font-normal"
|
||||||
>{footer.privacyPolicy}</a
|
>{footer.privacyPolicy}</a
|
||||||
>
|
>
|
||||||
</li>
|
</li>
|
||||||
|
@ -83,14 +83,14 @@ const {
|
||||||
<a href="https://docs.zen-browser.app/" class="font-normal">{footer.documentation}</a>
|
<a href="https://docs.zen-browser.app/" class="font-normal">{footer.documentation}</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href={getLocalePath("/mods")} class="font-normal">{footer.zenMods}</a>
|
<a href={getLocalePath('/mods')} class="font-normal">{footer.zenMods}</a>
|
||||||
</li>
|
</li>
|
||||||
<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>
|
||||||
<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>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
@ -122,7 +122,7 @@ const {
|
||||||
>
|
>
|
||||||
<p
|
<p
|
||||||
class="flex justify-center gap-2 lg:justify-start"
|
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>
|
||||||
<section class="absolute bottom-0 right-0">
|
<section class="absolute bottom-0 right-0">
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
---
|
---
|
||||||
import { motion } from "motion/react"
|
import { motion } from 'motion/react'
|
||||||
import { getTitleAnimation } from "~/animations"
|
import { getTitleAnimation } from '~/animations'
|
||||||
import HomePageVideo from "~/assets/HomePageVideo.webm"
|
import HomePageVideo from '~/assets/HomePageVideo.webm'
|
||||||
import Button from "~/components/Button.astro"
|
import Button from '~/components/Button.astro'
|
||||||
import Description from "~/components/Description.astro"
|
import Description from '~/components/Description.astro'
|
||||||
import Title from "~/components/Title.astro"
|
import Title from '~/components/Title.astro'
|
||||||
import ArrowRightIcon from "~/icons/ArrowRightIcon.astro"
|
import ArrowRightIcon from '~/icons/ArrowRightIcon.astro'
|
||||||
import { getLocale, getPath, getUI } from "~/utils/i18n"
|
import { getLocale, getPath, getUI } from '~/utils/i18n'
|
||||||
import SocialMediaStrip from "./SocialMediaStrip.astro"
|
import SocialMediaStrip from './SocialMediaStrip.astro'
|
||||||
import Video from "./Video.astro"
|
import Video from './Video.astro'
|
||||||
|
|
||||||
let titleAnimationCounter = 0
|
let titleAnimationCounter = 0
|
||||||
function getNewAnimationDelay() {
|
function getNewAnimationDelay() {
|
||||||
|
@ -63,13 +63,13 @@ const {
|
||||||
</motion.span>
|
</motion.span>
|
||||||
<div class="mt-6 flex w-2/3 flex-col gap-3 sm:gap-6 md:w-fit md:flex-row">
|
<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()}>
|
<motion.span client:load {...getHeroTitleAnimation()}>
|
||||||
<Button class="w-full" href={getLocalePath("/download")} isPrimary>
|
<Button class="w-full" href={getLocalePath('/download')} isPrimary>
|
||||||
{hero.buttons.beta}
|
{hero.buttons.beta}
|
||||||
<ArrowRightIcon class="size-4" />
|
<ArrowRightIcon class="size-4" />
|
||||||
</Button>
|
</Button>
|
||||||
</motion.span>
|
</motion.span>
|
||||||
<motion.span client:load {...getHeroTitleAnimation()}>
|
<motion.span client:load {...getHeroTitleAnimation()}>
|
||||||
<Button href={getLocalePath("/donate")}>{hero.buttons.support}</Button>
|
<Button href={getLocalePath('/donate')}>{hero.buttons.support}</Button>
|
||||||
</motion.span>
|
</motion.span>
|
||||||
</div>
|
</div>
|
||||||
<motion.span
|
<motion.span
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
---
|
---
|
||||||
import { getLocale, getPath, getUI } from "~/utils/i18n"
|
import { getLocale, getPath, getUI } from '~/utils/i18n'
|
||||||
|
|
||||||
const locale = getLocale(Astro)
|
const locale = getLocale(Astro)
|
||||||
const getLocalePath = getPath(locale)
|
const getLocalePath = getPath(locale)
|
||||||
|
@ -46,12 +46,12 @@ const {
|
||||||
<div class="mb-2 font-bold">{menu.gettingStarted}</div>
|
<div class="mb-2 font-bold">{menu.gettingStarted}</div>
|
||||||
<ul class="ml-4 space-y-2">
|
<ul class="ml-4 space-y-2">
|
||||||
<li>
|
<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
|
>{menu.zenMods}</a
|
||||||
>
|
>
|
||||||
</li>
|
</li>
|
||||||
<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
|
>{menu.releaseNotes}</a
|
||||||
>
|
>
|
||||||
</li>
|
</li>
|
||||||
|
@ -67,12 +67,12 @@ const {
|
||||||
<div class="mb-2 font-bold">{menu.usefulLinks}</div>
|
<div class="mb-2 font-bold">{menu.usefulLinks}</div>
|
||||||
<ul class="ml-4 space-y-2">
|
<ul class="ml-4 space-y-2">
|
||||||
<li>
|
<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
|
>{menu.donate}</a
|
||||||
>
|
>
|
||||||
</li>
|
</li>
|
||||||
<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
|
>{menu.aboutUs}</a
|
||||||
>
|
>
|
||||||
</li>
|
</li>
|
||||||
|
@ -92,12 +92,12 @@ const {
|
||||||
</li>
|
</li>
|
||||||
<!-- Extra Links -->
|
<!-- Extra Links -->
|
||||||
<li>
|
<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
|
>{menu.mods}</a
|
||||||
>
|
>
|
||||||
</li>
|
</li>
|
||||||
<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
|
>{menu.download}</a
|
||||||
>
|
>
|
||||||
</li>
|
</li>
|
||||||
|
|
|
@ -1,18 +1,18 @@
|
||||||
import { icon, library } from "@fortawesome/fontawesome-svg-core"
|
import { icon, library } from '@fortawesome/fontawesome-svg-core'
|
||||||
import { faSort, faSortDown, faSortUp } from "@fortawesome/free-solid-svg-icons"
|
import { faSort, faSortDown, faSortUp } from '@fortawesome/free-solid-svg-icons'
|
||||||
import { useEffect, useState, type FormEvent } from "react"
|
import { useEffect, useState, type FormEvent } from 'react'
|
||||||
|
|
||||||
import { useModsSearch } from "~/hooks/useModsSearch"
|
import { useModsSearch } from '~/hooks/useModsSearch'
|
||||||
import { type ZenTheme } from "~/mods"
|
import { type ZenTheme } from '~/mods'
|
||||||
import { getUI, type Locale } from "~/utils/i18n"
|
import { getUI, type Locale } from '~/utils/i18n'
|
||||||
|
|
||||||
// Add icons to the library
|
// Add icons to the library
|
||||||
library.add(faSort, faSortUp, faSortDown)
|
library.add(faSort, faSortUp, faSortDown)
|
||||||
|
|
||||||
// Create icon objects
|
// Create icon objects
|
||||||
const defaultSortIcon = icon({ prefix: "fas", iconName: "sort" })
|
const defaultSortIcon = icon({ prefix: 'fas', iconName: 'sort' })
|
||||||
const ascSortIcon = icon({ prefix: "fas", iconName: "sort-up" })
|
const ascSortIcon = icon({ prefix: 'fas', iconName: 'sort-up' })
|
||||||
const descSortIcon = icon({ prefix: "fas", iconName: "sort-down" })
|
const descSortIcon = icon({ prefix: 'fas', iconName: 'sort-down' })
|
||||||
|
|
||||||
type ModsListProps = {
|
type ModsListProps = {
|
||||||
allMods: ZenTheme[]
|
allMods: ZenTheme[]
|
||||||
|
@ -44,9 +44,9 @@ const ModsList = ({ allMods, locale }: ModsListProps) => {
|
||||||
setPageInput(page.toString())
|
setPageInput(page.toString())
|
||||||
}, [page])
|
}, [page])
|
||||||
|
|
||||||
function getSortIcon(state: "default" | "asc" | "desc") {
|
function getSortIcon(state: 'default' | 'asc' | 'desc') {
|
||||||
if (state === "asc") return ascSortIcon
|
if (state === 'asc') return ascSortIcon
|
||||||
if (state === "desc") return descSortIcon
|
if (state === 'desc') return descSortIcon
|
||||||
return defaultSortIcon
|
return defaultSortIcon
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -90,14 +90,14 @@ const ModsList = ({ allMods, locale }: ModsListProps) => {
|
||||||
return (
|
return (
|
||||||
<div className="mx-auto mb-12 flex items-center justify-center gap-4 px-8">
|
<div className="mx-auto mb-12 flex items-center justify-center gap-4 px-8">
|
||||||
<button
|
<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)}
|
onClick={() => navigatePage(page - 1)}
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
<
|
<
|
||||||
</button>
|
</button>
|
||||||
<form className="flex items-center gap-2" onSubmit={handlePageSubmit}>
|
<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) {
|
if (index === 0) {
|
||||||
return (
|
return (
|
||||||
<input
|
<input
|
||||||
|
@ -113,14 +113,14 @@ const ModsList = ({ allMods, locale }: ModsListProps) => {
|
||||||
return (
|
return (
|
||||||
<span className="text-sm" key={value}>
|
<span className="text-sm" key={value}>
|
||||||
{value
|
{value
|
||||||
.replace("{totalPages}", totalPages.toString())
|
.replace('{totalPages}', totalPages.toString())
|
||||||
.replace("{totalItems}", totalItems.toString())}
|
.replace('{totalItems}', totalItems.toString())}
|
||||||
</span>
|
</span>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
</form>
|
</form>
|
||||||
<button
|
<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)}
|
onClick={() => navigatePage(page + 1)}
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
---
|
---
|
||||||
import { Astronav, Dropdown, DropdownItems, MenuItems } from "astro-navbar"
|
import { Astronav, Dropdown, DropdownItems, MenuItems } from 'astro-navbar'
|
||||||
import Button from "~/components/Button.astro"
|
import Button from '~/components/Button.astro'
|
||||||
import ArrowRightIcon from "~/icons/ArrowRightIcon.astro"
|
import ArrowRightIcon from '~/icons/ArrowRightIcon.astro'
|
||||||
import ChevronDownIcon from "~/icons/ChevronDownIcon.astro"
|
import ChevronDownIcon from '~/icons/ChevronDownIcon.astro'
|
||||||
import DownloadIcon from "~/icons/DownloadIcon.astro"
|
import DownloadIcon from '~/icons/DownloadIcon.astro'
|
||||||
import MenuIcon from "~/icons/MenuIcon.astro"
|
import MenuIcon from '~/icons/MenuIcon.astro'
|
||||||
import { getLocale, getPath, getUI } from "~/utils/i18n"
|
import { getLocale, getPath, getUI } from '~/utils/i18n'
|
||||||
import Logo from "./Logo.astro"
|
import Logo from './Logo.astro'
|
||||||
import MobileMenu from "./MobileMenu.astro"
|
import MobileMenu from './MobileMenu.astro'
|
||||||
import ThemeSwitch from "./ThemeSwitch.astro"
|
import ThemeSwitch from './ThemeSwitch.astro'
|
||||||
|
|
||||||
const locale = getLocale(Astro)
|
const locale = getLocale(Astro)
|
||||||
const getLocalePath = getPath(locale)
|
const getLocalePath = getPath(locale)
|
||||||
|
@ -24,7 +24,7 @@ const {
|
||||||
<MenuItems
|
<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"
|
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" />
|
<Logo class="text-coral" />
|
||||||
<span>{brand}</span>
|
<span>{brand}</span>
|
||||||
</a>
|
</a>
|
||||||
|
@ -40,7 +40,7 @@ const {
|
||||||
</button>
|
</button>
|
||||||
<DropdownItems>
|
<DropdownItems>
|
||||||
<div class="navbar-dropdown">
|
<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-title">{menu.zenMods}</div>
|
||||||
<div class="dropdown-description">
|
<div class="dropdown-description">
|
||||||
{menu.zenModsDesc}
|
{menu.zenModsDesc}
|
||||||
|
@ -50,7 +50,7 @@ const {
|
||||||
<ArrowRightIcon class="size-4" />
|
<ArrowRightIcon class="size-4" />
|
||||||
</Button>
|
</Button>
|
||||||
</a>
|
</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-title">{menu.releaseNotes}</div>
|
||||||
<div class="dropdown-description">
|
<div class="dropdown-description">
|
||||||
{menu.releaseNotesDesc}
|
{menu.releaseNotesDesc}
|
||||||
|
@ -74,13 +74,13 @@ const {
|
||||||
</button>
|
</button>
|
||||||
<DropdownItems>
|
<DropdownItems>
|
||||||
<div class="navbar-dropdown !grid-cols-1 gap-1">
|
<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-title">{menu.donate}</div>
|
||||||
<div class="dropdown-description">
|
<div class="dropdown-description">
|
||||||
{menu.donateDesc}
|
{menu.donateDesc}
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</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-title">{menu.aboutUs}</div>
|
||||||
<div class="dropdown-description">
|
<div class="dropdown-description">
|
||||||
{menu.aboutUsDesc}
|
{menu.aboutUsDesc}
|
||||||
|
@ -101,7 +101,7 @@ const {
|
||||||
</div>
|
</div>
|
||||||
</DropdownItems>
|
</DropdownItems>
|
||||||
</Dropdown>
|
</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>
|
<span>{menu.mods}</span>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
---
|
---
|
||||||
import InfoIcon from "~/icons/InfoIcon.astro"
|
import InfoIcon from '~/icons/InfoIcon.astro'
|
||||||
|
|
||||||
import { releaseNotes as releaseNotesData } from "~/release-notes"
|
import { releaseNotes as releaseNotesData } from '~/release-notes'
|
||||||
import { getLocale, getPath, getUI } from "~/utils/i18n"
|
import { getLocale, getPath, getUI } from '~/utils/i18n'
|
||||||
import { type ReleaseNote, getReleaseNoteFirefoxVersion } from "../release-notes"
|
import { type ReleaseNote, getReleaseNoteFirefoxVersion } from '../release-notes'
|
||||||
import ReleaseNoteListItem from "./ReleaseNoteListItem.astro"
|
import ReleaseNoteListItem from './ReleaseNoteListItem.astro'
|
||||||
export type Props = ReleaseNote
|
export type Props = ReleaseNote
|
||||||
const { isTwilight, ...props } = Astro.props
|
const { isTwilight, ...props } = Astro.props
|
||||||
|
|
||||||
|
@ -20,7 +20,7 @@ const {
|
||||||
|
|
||||||
let date: Date | undefined
|
let date: Date | undefined
|
||||||
if (props.date) {
|
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}`))
|
date = new Date(Date.parse(`${year}-${month}-${day}`))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,7 +29,7 @@ const currentReleaseIndex = releaseNotesData.findIndex(
|
||||||
(releaseNote: ReleaseNote) => releaseNote.version === props.version
|
(releaseNote: ReleaseNote) => releaseNote.version === props.version
|
||||||
)
|
)
|
||||||
const prevReleaseNote = releaseNotesData[currentReleaseIndex + 1]
|
const prevReleaseNote = releaseNotesData[currentReleaseIndex + 1]
|
||||||
let compareLink = ""
|
let compareLink = ''
|
||||||
if (prevReleaseNote && !isTwilight) {
|
if (prevReleaseNote && !isTwilight) {
|
||||||
compareLink = `https://github.com/zen-browser/desktop/compare/${prevReleaseNote.version}...${props.version}`
|
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
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
items.forEach((item: any) => {
|
items.forEach((item: any) => {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case "feature":
|
case 'feature':
|
||||||
listItems[type].push({
|
listItems[type].push({
|
||||||
type: "feature",
|
type: 'feature',
|
||||||
content: item,
|
content: item,
|
||||||
})
|
})
|
||||||
break
|
break
|
||||||
case "fix":
|
case 'fix':
|
||||||
listItems[type].push({
|
listItems[type].push({
|
||||||
type: "fix",
|
type: 'fix',
|
||||||
content: item.description ?? item,
|
content: item.description ?? item,
|
||||||
...(item.issue
|
...(item.issue
|
||||||
? {
|
? {
|
||||||
|
@ -67,36 +67,36 @@ const generateItems = (items: any, type: string) => {
|
||||||
: {}),
|
: {}),
|
||||||
})
|
})
|
||||||
break
|
break
|
||||||
case "security":
|
case 'security':
|
||||||
listItems[type].push({
|
listItems[type].push({
|
||||||
type: "security",
|
type: 'security',
|
||||||
link: {
|
link: {
|
||||||
text: "Various security fixes.",
|
text: 'Various security fixes.',
|
||||||
href: item,
|
href: item,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
break
|
break
|
||||||
case "theme":
|
case 'theme':
|
||||||
listItems[type].push({
|
listItems[type].push({
|
||||||
type: "theme",
|
type: 'theme',
|
||||||
content: item,
|
content: item,
|
||||||
})
|
})
|
||||||
break
|
break
|
||||||
case "break":
|
case 'break':
|
||||||
listItems[type].push({
|
listItems[type].push({
|
||||||
type: "break",
|
type: 'break',
|
||||||
content: item,
|
content: item,
|
||||||
})
|
})
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
generateItems(props.security ? [props.security] : null, "security")
|
generateItems(props.security ? [props.security] : null, 'security')
|
||||||
generateItems(props.fixes, "fix")
|
generateItems(props.fixes, 'fix')
|
||||||
generateItems(props.features, "feature")
|
generateItems(props.features, 'feature')
|
||||||
generateItems(props.themeChanges, "theme")
|
generateItems(props.themeChanges, 'theme')
|
||||||
generateItems(props.breakingChanges, "break")
|
generateItems(props.breakingChanges, 'break')
|
||||||
generateItems(props.knownIssues, "known")
|
generateItems(props.knownIssues, 'known')
|
||||||
---
|
---
|
||||||
|
|
||||||
<section
|
<section
|
||||||
|
@ -108,7 +108,7 @@ generateItems(props.knownIssues, "known")
|
||||||
isTwilight ? (
|
isTwilight ? (
|
||||||
<a
|
<a
|
||||||
class="!mb-2 block w-fit rounded-full bg-coral px-3 py-1 text-xs text-paper"
|
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}
|
{releaseNoteItem.twilight}
|
||||||
</a>
|
</a>
|
||||||
|
@ -121,11 +121,11 @@ generateItems(props.knownIssues, "known")
|
||||||
{
|
{
|
||||||
isTwilight ? (
|
isTwilight ? (
|
||||||
<>
|
<>
|
||||||
{releaseNoteItem.twilightChanges}{" "}
|
{releaseNoteItem.twilightChanges}{' '}
|
||||||
{props.version.replaceAll("{version}", props.version)}
|
{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"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
>
|
>
|
||||||
{releaseNoteItem.firefoxVersion.replace("{version}", ffVersion)}
|
{releaseNoteItem.firefoxVersion.replace('{version}', ffVersion)}
|
||||||
</a>
|
</a>
|
||||||
</>
|
</>
|
||||||
) : null
|
) : null
|
||||||
|
@ -149,7 +149,7 @@ generateItems(props.knownIssues, "known")
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
class="zen-link whitespace-nowrap text-xs !no-underline opacity-80"
|
class="zen-link whitespace-nowrap text-xs !no-underline opacity-80"
|
||||||
target="_blank"
|
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
|
>{releaseNoteItem.githubRelease}</a
|
||||||
>
|
>
|
||||||
{
|
{
|
||||||
|
@ -168,7 +168,7 @@ generateItems(props.knownIssues, "known")
|
||||||
) : null
|
) : null
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
compareLink !== "" ? (
|
compareLink !== '' ? (
|
||||||
<>
|
<>
|
||||||
<span class="text-muted-foreground mx-3 hidden sm:block">•</span>
|
<span class="text-muted-foreground mx-3 hidden sm:block">•</span>
|
||||||
<a
|
<a
|
||||||
|
@ -184,13 +184,13 @@ generateItems(props.knownIssues, "known")
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
<div class="text-xs font-bold opacity-80">
|
<div class="text-xs font-bold opacity-80">
|
||||||
{date && date.toLocaleDateString("en-US", { dateStyle: "long" })}
|
{date && date.toLocaleDateString('en-US', { dateStyle: 'long' })}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{
|
{
|
||||||
props.extra?.length ? (
|
props.extra?.length ? (
|
||||||
<p class="text-md text-muted-foreground extra mt-2">
|
<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>
|
</p>
|
||||||
) : null
|
) : null
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
const { type, content, link } = Astro.props as {
|
||||||
type: "security" | "feature" | "fix" | "theme" | "break" | "known"
|
type: 'security' | 'feature' | 'fix' | 'theme' | 'break' | 'known'
|
||||||
content: string
|
content: string
|
||||||
link?: {
|
link?: {
|
||||||
text: string
|
text: string
|
||||||
|
@ -22,13 +22,13 @@ const {
|
||||||
<li class="flex gap-2">
|
<li class="flex gap-2">
|
||||||
<div
|
<div
|
||||||
class:list={[
|
class:list={[
|
||||||
(type === "security" && "text-[#e3401f]") ||
|
(type === 'security' && 'text-[#e3401f]') ||
|
||||||
(type === "feature" && "text-[#bf3316] dark:text-[#ffb1a1]") ||
|
(type === 'feature' && 'text-[#bf3316] dark:text-[#ffb1a1]') ||
|
||||||
(type === "fix" && "text-[#fe846b]") ||
|
(type === 'fix' && 'text-[#fe846b]') ||
|
||||||
(type === "theme" && "text-[#f76f53]") ||
|
(type === 'theme' && 'text-[#f76f53]') ||
|
||||||
(type === "break" && "text-[#471308] dark:text-[#D02908]") ||
|
(type === 'break' && 'text-[#471308] dark:text-[#D02908]') ||
|
||||||
"",
|
'',
|
||||||
"min-w-16 font-bold opacity-80",
|
'min-w-16 font-bold opacity-80',
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
{itemType[type]}
|
{itemType[type]}
|
||||||
|
|
|
@ -1,21 +1,21 @@
|
||||||
---
|
---
|
||||||
const { gap = 4 } = Astro.props
|
const { gap = 4 } = Astro.props
|
||||||
|
|
||||||
import { icon, library } from "@fortawesome/fontawesome-svg-core"
|
import { icon, library } from '@fortawesome/fontawesome-svg-core'
|
||||||
import {
|
import {
|
||||||
faBluesky,
|
faBluesky,
|
||||||
faGithub,
|
faGithub,
|
||||||
faMastodon,
|
faMastodon,
|
||||||
faReddit,
|
faReddit,
|
||||||
faXTwitter,
|
faXTwitter,
|
||||||
} from "@fortawesome/free-brands-svg-icons"
|
} from '@fortawesome/free-brands-svg-icons'
|
||||||
|
|
||||||
library.add(faMastodon, faBluesky, faGithub, faXTwitter, faReddit)
|
library.add(faMastodon, faBluesky, faGithub, faXTwitter, faReddit)
|
||||||
const Mastodon = icon({ prefix: "fab", iconName: "mastodon" })
|
const Mastodon = icon({ prefix: 'fab', iconName: 'mastodon' })
|
||||||
const Bluesky = icon({ prefix: "fab", iconName: "bluesky" })
|
const Bluesky = icon({ prefix: 'fab', iconName: 'bluesky' })
|
||||||
const Github = icon({ prefix: "fab", iconName: "github" })
|
const Github = icon({ prefix: 'fab', iconName: 'github' })
|
||||||
const XTwitter = icon({ prefix: "fab", iconName: "x-twitter" })
|
const XTwitter = icon({ prefix: 'fab', iconName: 'x-twitter' })
|
||||||
const Reddit = icon({ prefix: "fab", iconName: "reddit" })
|
const Reddit = icon({ prefix: 'fab', iconName: 'reddit' })
|
||||||
---
|
---
|
||||||
|
|
||||||
<ul class={`flex items-center opacity-80 gap-${gap}`}>
|
<ul class={`flex items-center opacity-80 gap-${gap}`}>
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
---
|
---
|
||||||
import { motion } from "motion/react"
|
import { motion } from 'motion/react'
|
||||||
import { getTitleAnimation } from "~/animations"
|
import { getTitleAnimation } from '~/animations'
|
||||||
import Description from "~/components/Description.astro"
|
import Description from '~/components/Description.astro'
|
||||||
import { getLocale, getUI } from "~/utils/i18n"
|
import { getLocale, getUI } from '~/utils/i18n'
|
||||||
|
|
||||||
const locale = getLocale(Astro)
|
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 { showSponsors = true } = Astro.props
|
||||||
|
|
||||||
const {
|
const {
|
||||||
|
@ -18,7 +18,7 @@ const {
|
||||||
} = getUI(locale)
|
} = 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">
|
<div class="mx-auto flex flex-col text-center">
|
||||||
<motion.span client:load {...getTitleAnimation(0.2)}>
|
<motion.span client:load {...getTitleAnimation(0.2)}>
|
||||||
<Description class="mb-2 text-6xl font-bold">Our Sponsors</Description>
|
<Description class="mb-2 text-6xl font-bold">Our Sponsors</Description>
|
||||||
|
@ -28,10 +28,10 @@ const {
|
||||||
</motion.span>
|
</motion.span>
|
||||||
<div class="relative mt-8 flex items-center justify-center">
|
<div class="relative mt-8 flex items-center justify-center">
|
||||||
<motion.span client:load {...getTitleAnimation(0.6)}>
|
<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
|
<Image
|
||||||
src={tutaLogo}
|
src={tutaLogo}
|
||||||
alt={sponsors.sponsors["tuta"].name}
|
alt={sponsors.sponsors['tuta'].name}
|
||||||
class="h-16 w-fit object-contain"
|
class="h-16 w-fit object-contain"
|
||||||
/>
|
/>
|
||||||
</a>
|
</a>
|
||||||
|
|
|
@ -4,14 +4,14 @@ interface Props {
|
||||||
className?: string
|
className?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const { label, className = "" } = Astro.props
|
const { label, className = '' } = Astro.props
|
||||||
---
|
---
|
||||||
|
|
||||||
<button
|
<button
|
||||||
type="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"
|
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">
|
<svg class="hidden h-5 w-5 dark:block" viewBox="0 0 24 24">
|
||||||
<path
|
<path
|
||||||
|
@ -26,24 +26,24 @@ const { label, className = "" } = Astro.props
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
const themeSwitch = document.getElementById("theme-switcher") as HTMLButtonElement
|
const themeSwitch = document.getElementById('theme-switcher') as HTMLButtonElement
|
||||||
|
|
||||||
const resolveTheme = () => {
|
const resolveTheme = () => {
|
||||||
if (typeof localStorage !== "undefined" && localStorage.getItem("theme")) {
|
if (typeof localStorage !== 'undefined' && localStorage.getItem('theme')) {
|
||||||
return localStorage.getItem("theme") ?? "light"
|
return localStorage.getItem('theme') ?? 'light'
|
||||||
}
|
}
|
||||||
if (window.matchMedia("(prefers-color-scheme: dark)").matches) {
|
if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
|
||||||
return "dark"
|
return 'dark'
|
||||||
}
|
}
|
||||||
return "light"
|
return 'light'
|
||||||
}
|
}
|
||||||
|
|
||||||
const html = document.documentElement
|
const html = document.documentElement
|
||||||
|
|
||||||
themeSwitch.addEventListener("click", () => {
|
themeSwitch.addEventListener('click', () => {
|
||||||
const newTheme = resolveTheme() === "light" ? "dark" : "light"
|
const newTheme = resolveTheme() === 'light' ? 'dark' : 'light'
|
||||||
html.setAttribute("data-theme", newTheme)
|
html.setAttribute('data-theme', newTheme)
|
||||||
html.classList.toggle("dark", newTheme === "dark")
|
html.classList.toggle('dark', newTheme === 'dark')
|
||||||
localStorage.setItem("theme", newTheme)
|
localStorage.setItem('theme', newTheme)
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
---
|
---
|
||||||
import { cn } from "~/utils/merge"
|
import { cn } from '~/utils/merge'
|
||||||
const { class: className } = Astro.props
|
const { class: className } = Astro.props
|
||||||
---
|
---
|
||||||
|
|
||||||
<h1
|
<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 />
|
<slot />
|
||||||
</h1>
|
</h1>
|
||||||
|
|
|
@ -1,22 +1,22 @@
|
||||||
---
|
---
|
||||||
const { src, class: className, ...rest } = Astro.props
|
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 */}
|
{/* 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}`} />
|
<source src="" type={`video/${type}`} />
|
||||||
</video>
|
</video>
|
||||||
|
|
||||||
<script>
|
<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 loadVideo = (video: HTMLVideoElement) => {
|
||||||
const source = video.querySelector("source")
|
const source = video.querySelector('source')
|
||||||
const dataSrc = video.getAttribute("data-src")
|
const dataSrc = video.getAttribute('data-src')
|
||||||
if (dataSrc && source) {
|
if (dataSrc && source) {
|
||||||
source.src = dataSrc
|
source.src = dataSrc
|
||||||
video.removeAttribute("data-src")
|
video.removeAttribute('data-src')
|
||||||
video.load()
|
video.load()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -89,12 +89,12 @@ const { label, href, checksum } = Astro.props
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
const checksumButtons = document.querySelectorAll(
|
const checksumButtons = document.querySelectorAll(
|
||||||
".checksum-icon-btn"
|
'.checksum-icon-btn'
|
||||||
) as NodeListOf<HTMLButtonElement>
|
) as NodeListOf<HTMLButtonElement>
|
||||||
const checksumTooltips = document.querySelectorAll(
|
const checksumTooltips = document.querySelectorAll(
|
||||||
".checksum-tooltip"
|
'.checksum-tooltip'
|
||||||
) as NodeListOf<HTMLDivElement>
|
) 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) {
|
function stopEvent(e: Event) {
|
||||||
e.preventDefault?.()
|
e.preventDefault?.()
|
||||||
|
@ -107,25 +107,25 @@ const { label, href, checksum } = Astro.props
|
||||||
navigator.clipboard.writeText(checksum)
|
navigator.clipboard.writeText(checksum)
|
||||||
const btn = e.currentTarget as HTMLButtonElement
|
const btn = e.currentTarget as HTMLButtonElement
|
||||||
const original = btn.innerText
|
const original = btn.innerText
|
||||||
btn.innerText = "Copied!"
|
btn.innerText = 'Copied!'
|
||||||
setTimeout(() => (btn.innerText = original), 1200)
|
setTimeout(() => (btn.innerText = original), 1200)
|
||||||
}
|
}
|
||||||
|
|
||||||
checksumButtons.forEach(btn => {
|
checksumButtons.forEach(btn => {
|
||||||
btn.addEventListener("click", stopEvent)
|
btn.addEventListener('click', stopEvent)
|
||||||
})
|
})
|
||||||
checksumTooltips.forEach(tooltip => {
|
checksumTooltips.forEach(tooltip => {
|
||||||
tooltip.addEventListener("mousedown", stopEvent)
|
tooltip.addEventListener('mousedown', stopEvent)
|
||||||
tooltip.addEventListener("click", stopEvent)
|
tooltip.addEventListener('click', stopEvent)
|
||||||
})
|
})
|
||||||
copyButtons.forEach(btn => {
|
copyButtons.forEach(btn => {
|
||||||
btn.addEventListener("click", e =>
|
btn.addEventListener('click', e =>
|
||||||
copyChecksum(
|
copyChecksum(
|
||||||
e,
|
e,
|
||||||
(btn.closest(".checksum-tooltip")?.querySelector(".font-mono") as HTMLSpanElement)
|
(btn.closest('.checksum-tooltip')?.querySelector('.font-mono') as HTMLSpanElement)
|
||||||
?.innerText
|
?.innerText
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
btn.addEventListener("mousedown", stopEvent)
|
btn.addEventListener('mousedown', stopEvent)
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -1,17 +1,17 @@
|
||||||
<script>
|
<script>
|
||||||
// Handle platform selection
|
// Handle platform selection
|
||||||
const platformButtons = document.querySelectorAll(".platform-selector")
|
const platformButtons = document.querySelectorAll('.platform-selector')
|
||||||
const platformSections = document.querySelectorAll(".platform-section")
|
const platformSections = document.querySelectorAll('.platform-section')
|
||||||
|
|
||||||
// Function to detect OS and select appropriate platform
|
// Function to detect OS and select appropriate platform
|
||||||
function detectOS() {
|
function detectOS() {
|
||||||
const userAgent = window.navigator.userAgent
|
const userAgent = window.navigator.userAgent
|
||||||
let detectedOS = "mac" // Default to macOS
|
let detectedOS = 'mac' // Default to macOS
|
||||||
|
|
||||||
if (userAgent.indexOf("Windows") !== -1) {
|
if (userAgent.indexOf('Windows') !== -1) {
|
||||||
detectedOS = "windows"
|
detectedOS = 'windows'
|
||||||
} else if (userAgent.indexOf("Linux") !== -1) {
|
} else if (userAgent.indexOf('Linux') !== -1) {
|
||||||
detectedOS = "linux"
|
detectedOS = 'linux'
|
||||||
}
|
}
|
||||||
|
|
||||||
return detectedOS
|
return detectedOS
|
||||||
|
@ -27,28 +27,28 @@
|
||||||
async function selectPlatform(platform: string) {
|
async function selectPlatform(platform: string) {
|
||||||
// Update button styling
|
// Update button styling
|
||||||
for (const button of platformButtons) {
|
for (const button of platformButtons) {
|
||||||
const buttonPlatform = button.getAttribute("data-platform")
|
const buttonPlatform = button.getAttribute('data-platform')
|
||||||
if (buttonPlatform === platform) {
|
if (buttonPlatform === platform) {
|
||||||
button.setAttribute("data-active", "true")
|
button.setAttribute('data-active', 'true')
|
||||||
} else {
|
} else {
|
||||||
button.setAttribute("data-active", "false")
|
button.setAttribute('data-active', 'false')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show/hide platform sections
|
// Show/hide platform sections
|
||||||
for (const section of platformSections) {
|
for (const section of platformSections) {
|
||||||
if (section.id === `${platform}-downloads`) {
|
if (section.id === `${platform}-downloads`) {
|
||||||
section.setAttribute("data-active", "true")
|
section.setAttribute('data-active', 'true')
|
||||||
} else {
|
} else {
|
||||||
section.setAttribute("data-active", "false")
|
section.setAttribute('data-active', 'false')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle platform button clicks
|
// Handle platform button clicks
|
||||||
for (const button of platformButtons) {
|
for (const button of platformButtons) {
|
||||||
button.addEventListener("click", () => {
|
button.addEventListener('click', () => {
|
||||||
const platform = button.getAttribute("data-platform") ?? ""
|
const platform = button.getAttribute('data-platform') ?? ''
|
||||||
selectPlatform(platform)
|
selectPlatform(platform)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -56,40 +56,40 @@
|
||||||
// Check for twilight mode
|
// Check for twilight mode
|
||||||
async function checkTwilightMode() {
|
async function checkTwilightMode() {
|
||||||
const urlParams = new URLSearchParams(window.location.search)
|
const urlParams = new URLSearchParams(window.location.search)
|
||||||
const isTwilight = urlParams.has("twilight")
|
const isTwilight = urlParams.has('twilight')
|
||||||
|
|
||||||
if (isTwilight) {
|
if (isTwilight) {
|
||||||
const twilightInfoElem = document.getElementById("twilight-info")
|
const twilightInfoElem = document.getElementById('twilight-info')
|
||||||
twilightInfoElem?.setAttribute("data-twilight", "true")
|
twilightInfoElem?.setAttribute('data-twilight', 'true')
|
||||||
|
|
||||||
// Update UI to show twilight mode with animation
|
// Update UI to show twilight mode with animation
|
||||||
const titleElem = document.getElementById("download-title")
|
const titleElem = document.getElementById('download-title')
|
||||||
if (titleElem) {
|
if (titleElem) {
|
||||||
const zenText = titleElem.innerHTML
|
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) {
|
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
|
// Apply twilight mode to all relevant elements
|
||||||
const coralElements = document.querySelectorAll(
|
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) {
|
for (const element of coralElements) {
|
||||||
element.setAttribute("data-twilight", "true")
|
element.setAttribute('data-twilight', 'true')
|
||||||
}
|
}
|
||||||
|
|
||||||
// Replace all download links with twilight versions
|
// 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) {
|
for (const link of downloadLinks) {
|
||||||
if (!link.id.includes("beta")) {
|
if (!link.id.includes('beta')) {
|
||||||
const href = link.getAttribute("href")
|
const href = link.getAttribute('href')
|
||||||
if (href && href.includes("/latest/download/")) {
|
if (href && href.includes('/latest/download/')) {
|
||||||
const twilightHref = href.replace("/latest/download/", "/download/twilight/")
|
const twilightHref = href.replace('/latest/download/', '/download/twilight/')
|
||||||
link.setAttribute("href", twilightHref)
|
link.setAttribute('href', twilightHref)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,7 +22,7 @@ interface PlatformReleases {
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
platform: "mac" | "windows" | "linux"
|
platform: 'mac' | 'windows' | 'linux'
|
||||||
icon: string[]
|
icon: string[]
|
||||||
title: string
|
title: string
|
||||||
description: string
|
description: string
|
||||||
|
@ -30,19 +30,19 @@ interface Props {
|
||||||
}
|
}
|
||||||
|
|
||||||
const { platform, icon, title, description, releases } = Astro.props
|
const { platform, icon, title, description, releases } = Astro.props
|
||||||
import { Image } from "astro:assets"
|
import { Image } from 'astro:assets'
|
||||||
import AppIconDark from "../../assets/app-icon-dark.png"
|
import AppIconDark from '../../assets/app-icon-dark.png'
|
||||||
import AppIconLight from "../../assets/app-icon-light.png"
|
import AppIconLight from '../../assets/app-icon-light.png'
|
||||||
import DownloadCard from "./ButtonCard.astro"
|
import DownloadCard from './ButtonCard.astro'
|
||||||
|
|
||||||
function isFlatReleaseInfo(obj: unknown): obj is ReleaseInfo {
|
function isFlatReleaseInfo(obj: unknown): obj is ReleaseInfo {
|
||||||
return !!obj && typeof obj === "object" && "link" in obj
|
return !!obj && typeof obj === 'object' && 'link' in obj
|
||||||
}
|
}
|
||||||
---
|
---
|
||||||
|
|
||||||
<div
|
<div
|
||||||
id={`${platform}-downloads`}
|
id={`${platform}-downloads`}
|
||||||
data-active={platform === "mac"}
|
data-active={platform === 'mac'}
|
||||||
class="platform-section data-[active='false']:hidden"
|
class="platform-section data-[active='false']:hidden"
|
||||||
>
|
>
|
||||||
<div class="items-center gap-8 md:flex">
|
<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} />
|
<p class="text-muted-foreground mb-6" set:html={description} />
|
||||||
<div class="space-y-6">
|
<div class="space-y-6">
|
||||||
{
|
{
|
||||||
platform === "linux" ? (
|
platform === 'linux' ? (
|
||||||
<>
|
<>
|
||||||
{releases.flathub && releases.flathub.all.label && (
|
{releases.flathub && releases.flathub.all.label && (
|
||||||
<article class="flathub-download data-[twilight='true']:hidden">
|
<article class="flathub-download data-[twilight='true']:hidden">
|
||||||
|
@ -71,16 +71,16 @@ function isFlatReleaseInfo(obj: unknown): obj is ReleaseInfo {
|
||||||
</article>
|
</article>
|
||||||
)}
|
)}
|
||||||
{releases.x86_64 &&
|
{releases.x86_64 &&
|
||||||
typeof releases.x86_64 === "object" &&
|
typeof releases.x86_64 === 'object' &&
|
||||||
"tarball" in releases.x86_64 &&
|
'tarball' in releases.x86_64 &&
|
||||||
releases.x86_64.tarball && (
|
releases.x86_64.tarball && (
|
||||||
<article>
|
<article>
|
||||||
<h4 class="mb-3 text-lg font-medium">x86_64</h4>
|
<h4 class="mb-3 text-lg font-medium">x86_64</h4>
|
||||||
<div class="">
|
<div class="">
|
||||||
{releases.x86_64.tarball && (
|
{releases.x86_64.tarball && (
|
||||||
<DownloadCard
|
<DownloadCard
|
||||||
label={releases.x86_64.tarball.label ? releases.x86_64.tarball.label : ""}
|
label={releases.x86_64.tarball.label ? releases.x86_64.tarball.label : ''}
|
||||||
href={releases.x86_64.tarball.link ? releases.x86_64.tarball.link : ""}
|
href={releases.x86_64.tarball.link ? releases.x86_64.tarball.link : ''}
|
||||||
variant="x86_64"
|
variant="x86_64"
|
||||||
checksum={releases.x86_64.tarball.checksum}
|
checksum={releases.x86_64.tarball.checksum}
|
||||||
/>
|
/>
|
||||||
|
@ -89,8 +89,8 @@ function isFlatReleaseInfo(obj: unknown): obj is ReleaseInfo {
|
||||||
</article>
|
</article>
|
||||||
)}
|
)}
|
||||||
{releases.aarch64 &&
|
{releases.aarch64 &&
|
||||||
typeof releases.aarch64 === "object" &&
|
typeof releases.aarch64 === 'object' &&
|
||||||
"tarball" in releases.aarch64 &&
|
'tarball' in releases.aarch64 &&
|
||||||
releases.aarch64.tarball && (
|
releases.aarch64.tarball && (
|
||||||
<article>
|
<article>
|
||||||
<h4 class="mb-3 text-lg font-medium">ARM64</h4>
|
<h4 class="mb-3 text-lg font-medium">ARM64</h4>
|
||||||
|
@ -98,9 +98,9 @@ function isFlatReleaseInfo(obj: unknown): obj is ReleaseInfo {
|
||||||
{releases.aarch64.tarball && (
|
{releases.aarch64.tarball && (
|
||||||
<DownloadCard
|
<DownloadCard
|
||||||
label={
|
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"
|
variant="aarch64"
|
||||||
checksum={releases.aarch64.tarball.checksum}
|
checksum={releases.aarch64.tarball.checksum}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -6,42 +6,42 @@ export function getReleasesWithChecksums(checksums: Record<string, string>) {
|
||||||
return {
|
return {
|
||||||
macos: {
|
macos: {
|
||||||
universal: {
|
universal: {
|
||||||
link: "https://github.com/zen-browser/desktop/releases/latest/download/zen.macos-universal.dmg",
|
link: 'https://github.com/zen-browser/desktop/releases/latest/download/zen.macos-universal.dmg',
|
||||||
label: "Universal",
|
label: 'Universal',
|
||||||
checksum: checksums["zen.macos-universal.dmg"],
|
checksum: checksums['zen.macos-universal.dmg'],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
windows: {
|
windows: {
|
||||||
x86_64: {
|
x86_64: {
|
||||||
link: "https://github.com/zen-browser/desktop/releases/latest/download/zen.installer.exe",
|
link: 'https://github.com/zen-browser/desktop/releases/latest/download/zen.installer.exe',
|
||||||
label: "64-bit (Recommended)",
|
label: '64-bit (Recommended)',
|
||||||
checksum: checksums["zen.installer.exe"],
|
checksum: checksums['zen.installer.exe'],
|
||||||
},
|
},
|
||||||
arm64: {
|
arm64: {
|
||||||
link: "https://github.com/zen-browser/desktop/releases/latest/download/zen.installer-arm64.exe",
|
link: 'https://github.com/zen-browser/desktop/releases/latest/download/zen.installer-arm64.exe',
|
||||||
label: "ARM64",
|
label: 'ARM64',
|
||||||
checksum: checksums["zen.installer-arm64.exe"],
|
checksum: checksums['zen.installer-arm64.exe'],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
linux: {
|
linux: {
|
||||||
x86_64: {
|
x86_64: {
|
||||||
tarball: {
|
tarball: {
|
||||||
link: "https://github.com/zen-browser/desktop/releases/latest/download/zen.linux-x86_64.tar.xz",
|
link: 'https://github.com/zen-browser/desktop/releases/latest/download/zen.linux-x86_64.tar.xz',
|
||||||
label: "Tarball",
|
label: 'Tarball',
|
||||||
checksum: checksums["zen.linux-x86_64.tar.xz"],
|
checksum: checksums['zen.linux-x86_64.tar.xz'],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
aarch64: {
|
aarch64: {
|
||||||
tarball: {
|
tarball: {
|
||||||
link: "https://github.com/zen-browser/desktop/releases/latest/download/zen.linux-aarch64.tar.xz",
|
link: 'https://github.com/zen-browser/desktop/releases/latest/download/zen.linux-aarch64.tar.xz',
|
||||||
label: "Tarball",
|
label: 'Tarball',
|
||||||
checksum: checksums["zen.linux-aarch64.tar.xz"],
|
checksum: checksums['zen.linux-aarch64.tar.xz'],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
flathub: {
|
flathub: {
|
||||||
all: {
|
all: {
|
||||||
link: "https://flathub.org/apps/app.zen_browser.zen",
|
link: 'https://flathub.org/apps/app.zen_browser.zen',
|
||||||
label: "Flathub",
|
label: 'Flathub',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
export const CHECKSUMS = {
|
export const CHECKSUMS = {
|
||||||
"zen.macos-universal.dmg": "macsum",
|
'zen.macos-universal.dmg': 'macsum',
|
||||||
"zen.installer.exe": "winsum",
|
'zen.installer.exe': 'winsum',
|
||||||
"zen.installer-arm64.exe": "winarmsum",
|
'zen.installer-arm64.exe': 'winarmsum',
|
||||||
"zen.linux-x86_64.tar.xz": "linuxsum",
|
'zen.linux-x86_64.tar.xz': 'linuxsum',
|
||||||
"zen.linux-aarch64.tar.xz": "linuxarmsum",
|
'zen.linux-aarch64.tar.xz': 'linuxarmsum',
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
export const I18N = {
|
export const I18N = {
|
||||||
DEFAULT_LOCALE: "en",
|
DEFAULT_LOCALE: 'en',
|
||||||
LOCALES: [{ label: "English", value: "en" }],
|
LOCALES: [{ label: 'English', value: 'en' }],
|
||||||
} as const
|
} as const
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { CHECKSUMS } from "./checksum"
|
import { CHECKSUMS } from './checksum'
|
||||||
import { I18N } from "./i18n"
|
import { I18N } from './i18n'
|
||||||
|
|
||||||
export const CONSTANT = {
|
export const CONSTANT = {
|
||||||
I18N,
|
I18N,
|
||||||
|
|
|
@ -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 = {
|
type ModsSearchState = {
|
||||||
search: string
|
search: string
|
||||||
|
@ -17,9 +17,9 @@ const DEFAULT_LIMIT = 12
|
||||||
export function useModsSearch(mods: ZenTheme[]) {
|
export function useModsSearch(mods: ZenTheme[]) {
|
||||||
const [searchParams, setSearchParams] = useState<URLSearchParams>()
|
const [searchParams, setSearchParams] = useState<URLSearchParams>()
|
||||||
const [state, setState] = useState<ModsSearchState>({
|
const [state, setState] = useState<ModsSearchState>({
|
||||||
search: "",
|
search: '',
|
||||||
createdSort: "desc",
|
createdSort: 'desc',
|
||||||
updatedSort: "default",
|
updatedSort: 'default',
|
||||||
page: 1,
|
page: 1,
|
||||||
limit: DEFAULT_LIMIT,
|
limit: DEFAULT_LIMIT,
|
||||||
})
|
})
|
||||||
|
@ -29,11 +29,11 @@ export function useModsSearch(mods: ZenTheme[]) {
|
||||||
const params = new URLSearchParams(window.location.search)
|
const params = new URLSearchParams(window.location.search)
|
||||||
setSearchParams(params)
|
setSearchParams(params)
|
||||||
setState({
|
setState({
|
||||||
search: params.get("q") || "",
|
search: params.get('q') || '',
|
||||||
createdSort: (params.get("created") as SortOrder) || "desc",
|
createdSort: (params.get('created') as SortOrder) || 'desc',
|
||||||
updatedSort: (params.get("updated") as SortOrder) || "default",
|
updatedSort: (params.get('updated') as SortOrder) || 'default',
|
||||||
page: Number.parseInt(params.get("page") || "1", 10),
|
page: Number.parseInt(params.get('page') || '1', 10),
|
||||||
limit: Number.parseInt(params.get("limit") || String(DEFAULT_LIMIT), 10),
|
limit: Number.parseInt(params.get('limit') || String(DEFAULT_LIMIT), 10),
|
||||||
})
|
})
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
@ -42,41 +42,41 @@ export function useModsSearch(mods: ZenTheme[]) {
|
||||||
if (!searchParams) return
|
if (!searchParams) return
|
||||||
|
|
||||||
if (state.search) {
|
if (state.search) {
|
||||||
searchParams.set("q", state.search)
|
searchParams.set('q', state.search)
|
||||||
} else {
|
} else {
|
||||||
searchParams.delete("q")
|
searchParams.delete('q')
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state.createdSort !== "default") {
|
if (state.createdSort !== 'default') {
|
||||||
searchParams.set("created", state.createdSort)
|
searchParams.set('created', state.createdSort)
|
||||||
} else {
|
} else {
|
||||||
searchParams.delete("created")
|
searchParams.delete('created')
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state.updatedSort !== "default") {
|
if (state.updatedSort !== 'default') {
|
||||||
searchParams.set("updated", state.updatedSort)
|
searchParams.set('updated', state.updatedSort)
|
||||||
} else {
|
} else {
|
||||||
searchParams.delete("updated")
|
searchParams.delete('updated')
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state.page > 1) {
|
if (state.page > 1) {
|
||||||
searchParams.set("page", state.page.toString())
|
searchParams.set('page', state.page.toString())
|
||||||
} else {
|
} else {
|
||||||
searchParams.delete("page")
|
searchParams.delete('page')
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state.limit !== DEFAULT_LIMIT) {
|
if (state.limit !== DEFAULT_LIMIT) {
|
||||||
searchParams.set("limit", state.limit.toString())
|
searchParams.set('limit', state.limit.toString())
|
||||||
} else {
|
} 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) {
|
if (state.page > 1) {
|
||||||
window.history.pushState({}, "", newUrl)
|
window.history.pushState({}, '', newUrl)
|
||||||
} else {
|
} else {
|
||||||
window.history.replaceState({}, "", newUrl)
|
window.history.replaceState({}, '', newUrl)
|
||||||
}
|
}
|
||||||
}, [state, searchParams])
|
}, [state, searchParams])
|
||||||
|
|
||||||
|
@ -96,18 +96,18 @@ export function useModsSearch(mods: ZenTheme[]) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sort by createdAt if chosen
|
// Sort by createdAt if chosen
|
||||||
if (state.createdSort !== "default") {
|
if (state.createdSort !== 'default') {
|
||||||
filtered.sort((a, b) => {
|
filtered.sort((a, b) => {
|
||||||
const diff = new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime()
|
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
|
// Sort by updatedAt if chosen
|
||||||
if (state.updatedSort !== "default") {
|
if (state.updatedSort !== 'default') {
|
||||||
filtered.sort((a, b) => {
|
filtered.sort((a, b) => {
|
||||||
const diff = new Date(a.updatedAt).getTime() - new Date(b.updatedAt).getTime()
|
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 => ({
|
setState(prev => ({
|
||||||
...prev,
|
...prev,
|
||||||
createdSort:
|
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
|
page: 1, // Reset page when sort changes
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
@ -137,7 +137,7 @@ export function useModsSearch(mods: ZenTheme[]) {
|
||||||
setState(prev => ({
|
setState(prev => ({
|
||||||
...prev,
|
...prev,
|
||||||
updatedSort:
|
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
|
page: 1, // Reset page when sort changes
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,6 @@ const { class: className, ...props } = Astro.props
|
||||||
stroke-width="2"
|
stroke-width="2"
|
||||||
stroke-linecap="round"
|
stroke-linecap="round"
|
||||||
stroke-linejoin="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
|
{...props}><path d="m12 19-7-7 7-7"></path><path d="M19 12H5"></path></svg
|
||||||
>
|
>
|
||||||
|
|
|
@ -12,6 +12,6 @@ const { class: className, ...props } = Astro.props
|
||||||
stroke-width="2"
|
stroke-width="2"
|
||||||
stroke-linecap="round"
|
stroke-linecap="round"
|
||||||
stroke-linejoin="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
|
{...props}><path d="M5 12h14"></path><path d="m12 5 7 7-7 7"></path></svg
|
||||||
>
|
>
|
||||||
|
|
|
@ -12,6 +12,6 @@ const { class: className, ...props } = Astro.props
|
||||||
stroke-width="2"
|
stroke-width="2"
|
||||||
stroke-linecap="round"
|
stroke-linecap="round"
|
||||||
stroke-linejoin="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
|
{...props}><path d="m5 12 7-7 7 7"></path><path d="M12 19V5"></path></svg
|
||||||
>
|
>
|
||||||
|
|
|
@ -12,6 +12,6 @@ const { class: className, ...props } = Astro.props
|
||||||
stroke-width="2"
|
stroke-width="2"
|
||||||
stroke-linecap="round"
|
stroke-linecap="round"
|
||||||
stroke-linejoin="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
|
{...props}><path d="M20 6 9 17l-5-5"></path></svg
|
||||||
>
|
>
|
||||||
|
|
|
@ -12,6 +12,6 @@ const { class: className, ...props } = Astro.props
|
||||||
stroke-width="2"
|
stroke-width="2"
|
||||||
stroke-linecap="round"
|
stroke-linecap="round"
|
||||||
stroke-linejoin="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
|
{...props}><path d="m6 9 6 6 6-6"></path></svg
|
||||||
>
|
>
|
||||||
|
|
|
@ -12,7 +12,7 @@ const { class: className, ...props } = Astro.props
|
||||||
stroke-width="2"
|
stroke-width="2"
|
||||||
stroke-linecap="round"
|
stroke-linecap="round"
|
||||||
stroke-linejoin="round"
|
stroke-linejoin="round"
|
||||||
class:list={["lucide lucide-download-icon lucide-download", className]}
|
class:list={['lucide lucide-download-icon lucide-download', className]}
|
||||||
{...props}
|
{...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"
|
><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
|
></polyline><line x1="12" x2="12" y1="15" y2="3"></line></svg
|
||||||
|
|
|
@ -12,7 +12,7 @@ const { class: className, ...props } = Astro.props
|
||||||
stroke-width="2"
|
stroke-width="2"
|
||||||
stroke-linecap="round"
|
stroke-linecap="round"
|
||||||
stroke-linejoin="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}
|
{...props}
|
||||||
><path d="M15 3h6v6"></path><path d="M10 14 21 3"></path><path
|
><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
|
d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"></path></svg
|
||||||
|
|
|
@ -12,7 +12,7 @@ const { class: className, ...props } = Astro.props
|
||||||
stroke-width="2"
|
stroke-width="2"
|
||||||
stroke-linecap="round"
|
stroke-linecap="round"
|
||||||
stroke-linejoin="round"
|
stroke-linejoin="round"
|
||||||
class:list={["lucide lucide-github-icon lucide-github", className]}
|
class:list={['lucide lucide-github-icon lucide-github', className]}
|
||||||
{...props}
|
{...props}
|
||||||
><path
|
><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"
|
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"
|
||||||
|
|
|
@ -12,7 +12,7 @@ const { class: className, ...props } = Astro.props
|
||||||
stroke-width="2"
|
stroke-width="2"
|
||||||
stroke-linecap="round"
|
stroke-linecap="round"
|
||||||
stroke-linejoin="round"
|
stroke-linejoin="round"
|
||||||
class:list={["lucide lucide-info-icon lucide-info", className]}
|
class:list={['lucide lucide-info-icon lucide-info', className]}
|
||||||
{...props}
|
{...props}
|
||||||
><circle cx="12" cy="12" r="10"></circle><path d="M12 16v-4"></path><path d="M12 8h.01"
|
><circle cx="12" cy="12" r="10"></circle><path d="M12 16v-4"></path><path d="M12 8h.01"
|
||||||
></path></svg
|
></path></svg
|
||||||
|
|
|
@ -12,7 +12,7 @@ const { class: className, ...props } = Astro.props
|
||||||
stroke-width="2"
|
stroke-width="2"
|
||||||
stroke-linecap="round"
|
stroke-linecap="round"
|
||||||
stroke-linejoin="round"
|
stroke-linejoin="round"
|
||||||
class:list={["lucide lucide-lock-icon lucide-lock", className]}
|
class:list={['lucide lucide-lock-icon lucide-lock', className]}
|
||||||
{...props}
|
{...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"
|
><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
|
></path></svg
|
||||||
|
|
|
@ -12,6 +12,6 @@ const { class: className, ...props } = Astro.props
|
||||||
stroke-width="2"
|
stroke-width="2"
|
||||||
stroke-linecap="round"
|
stroke-linecap="round"
|
||||||
stroke-linejoin="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
|
{...props}><path d="M4 12h16"></path><path d="M4 18h16"></path><path d="M4 6h16"></path></svg
|
||||||
>
|
>
|
||||||
|
|
|
@ -9,32 +9,32 @@ interface Props {
|
||||||
|
|
||||||
const { title, description, ogImage, isHome, redirect } = Astro.props
|
const { title, description, ogImage, isHome, redirect } = Astro.props
|
||||||
const defaultDescription =
|
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."
|
'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"
|
const defaultOgImage = '/share-pic.png'
|
||||||
import "@fontsource/bricolage-grotesque/400.css"
|
import '@fontsource/bricolage-grotesque/400.css'
|
||||||
import "@fontsource/bricolage-grotesque/500.css"
|
import '@fontsource/bricolage-grotesque/500.css'
|
||||||
import "@fontsource/bricolage-grotesque/600.css"
|
import '@fontsource/bricolage-grotesque/600.css'
|
||||||
import Footer from "~/components/Footer.astro"
|
import Footer from '~/components/Footer.astro'
|
||||||
import NavBar from "~/components/NavBar.astro"
|
import NavBar from '~/components/NavBar.astro'
|
||||||
import { getLocale } from "~/utils/i18n"
|
import { getLocale } from '~/utils/i18n'
|
||||||
const locale = getLocale(Astro)
|
const locale = getLocale(Astro)
|
||||||
---
|
---
|
||||||
|
|
||||||
<script is:inline data-cfasync="false">
|
<script is:inline data-cfasync="false">
|
||||||
const theme = (() => {
|
const theme = (() => {
|
||||||
if (typeof localStorage !== "undefined" && localStorage.getItem("theme")) {
|
if (typeof localStorage !== 'undefined' && localStorage.getItem('theme')) {
|
||||||
return localStorage.getItem("theme") ?? "light"
|
return localStorage.getItem('theme') ?? 'light'
|
||||||
}
|
}
|
||||||
if (window.matchMedia("(prefers-color-scheme: dark)").matches) {
|
if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
|
||||||
return "dark"
|
return 'dark'
|
||||||
}
|
}
|
||||||
return "light"
|
return 'light'
|
||||||
})()
|
})()
|
||||||
|
|
||||||
if (theme === "light") {
|
if (theme === 'light') {
|
||||||
document.documentElement.setAttribute("data-theme", "light")
|
document.documentElement.setAttribute('data-theme', 'light')
|
||||||
} else {
|
} else {
|
||||||
document.documentElement.setAttribute("data-theme", "dark")
|
document.documentElement.setAttribute('data-theme', 'dark')
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -55,10 +55,10 @@ const locale = getLocale(Astro)
|
||||||
is:inline
|
is:inline
|
||||||
type="application/ld+json"
|
type="application/ld+json"
|
||||||
set:html={JSON.stringify({
|
set:html={JSON.stringify({
|
||||||
"@context": "https://schema.org",
|
'@context': 'https://schema.org',
|
||||||
"@type": "WebSite",
|
'@type': 'WebSite',
|
||||||
name: "Zen Browser",
|
name: 'Zen Browser',
|
||||||
url: "https://zen-browser.app/",
|
url: 'https://zen-browser.app/',
|
||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
|
@ -91,7 +91,7 @@ const locale = getLocale(Astro)
|
||||||
<script>
|
<script>
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
console.log(
|
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;',
|
'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;'
|
'color: #f76f53; font-size: 16px; font-family: "Rubik"; margin-bottom: 20px;'
|
||||||
)
|
)
|
||||||
|
@ -105,14 +105,14 @@ const locale = getLocale(Astro)
|
||||||
</html>
|
</html>
|
||||||
<style is:global>
|
<style is:global>
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: "Junicode";
|
font-family: 'Junicode';
|
||||||
src: url("/fonts/JunicodeVF-Roman-subset.woff2") format("woff2");
|
src: url('/fonts/JunicodeVF-Roman-subset.woff2') format('woff2');
|
||||||
font-display: swap;
|
font-display: swap;
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: "Junicode-Italic";
|
font-family: 'Junicode-Italic';
|
||||||
src: url("/fonts/JunicodeVF-Italic-subset.woff2") format("woff2");
|
src: url('/fonts/JunicodeVF-Italic-subset.woff2') format('woff2');
|
||||||
font-display: swap;
|
font-display: swap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -128,7 +128,7 @@ const locale = getLocale(Astro)
|
||||||
--zen-muted: rgba(0, 0, 0, 0.05);
|
--zen-muted: rgba(0, 0, 0, 0.05);
|
||||||
--zen-subtle: rgba(0, 0, 0, 0.05);
|
--zen-subtle: rgba(0, 0, 0, 0.05);
|
||||||
|
|
||||||
&[data-theme="dark"] {
|
&[data-theme='dark'] {
|
||||||
--zen-paper: #1f1f1f;
|
--zen-paper: #1f1f1f;
|
||||||
--zen-dark: #d1cfc0;
|
--zen-dark: #d1cfc0;
|
||||||
--zen-muted: rgba(255, 255, 255, 0.05);
|
--zen-muted: rgba(255, 255, 255, 0.05);
|
||||||
|
@ -142,17 +142,17 @@ const locale = getLocale(Astro)
|
||||||
|
|
||||||
body,
|
body,
|
||||||
body > * {
|
body > * {
|
||||||
font-family: "Bricolage Grotesque", sans-serif;
|
font-family: 'Bricolage Grotesque', sans-serif;
|
||||||
font-optical-sizing: auto;
|
font-optical-sizing: auto;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
font-variation-settings: "wdth" 100;
|
font-variation-settings: 'wdth' 100;
|
||||||
}
|
}
|
||||||
|
|
||||||
h1 .italic {
|
h1 .italic {
|
||||||
font-family: "Junicode-Italic", serif;
|
font-family: 'Junicode-Italic', serif;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
font-feature-settings: "swsh" 0;
|
font-feature-settings: 'swsh' 0;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { format } from "date-fns"
|
import { format } from 'date-fns'
|
||||||
|
|
||||||
export type ZenTheme = {
|
export type ZenTheme = {
|
||||||
name: string
|
name: string
|
||||||
|
@ -17,7 +17,7 @@ export type ZenTheme = {
|
||||||
updatedAt: Date
|
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[]> {
|
export async function getAllMods(): Promise<ZenTheme[]> {
|
||||||
try {
|
try {
|
||||||
|
@ -37,5 +37,5 @@ export function getAuthorLink(author: string): string {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getLocalizedDate(date: Date): string {
|
export function getLocalizedDate(date: Date): string {
|
||||||
return format(date, "PP")
|
return format(date, 'PP')
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
---
|
---
|
||||||
import NotFound from "./[...locale]/404.astro"
|
import NotFound from './[...locale]/404.astro'
|
||||||
---
|
---
|
||||||
|
|
||||||
<NotFound />
|
<NotFound />
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
---
|
---
|
||||||
import Button from "~/components/Button.astro"
|
import Button from '~/components/Button.astro'
|
||||||
import Description from "~/components/Description.astro"
|
import Description from '~/components/Description.astro'
|
||||||
import Title from "~/components/Title.astro"
|
import Title from '~/components/Title.astro'
|
||||||
import Layout from "~/layouts/Layout.astro"
|
import Layout from '~/layouts/Layout.astro'
|
||||||
import { getLocale, getPath, getUI } from "~/utils/i18n"
|
import { getLocale, getPath, getUI } from '~/utils/i18n'
|
||||||
export { getStaticPaths } from "~/utils/i18n"
|
export { getStaticPaths } from '~/utils/i18n'
|
||||||
|
|
||||||
const locale = getLocale(Astro)
|
const locale = getLocale(Astro)
|
||||||
const getLocalePath = getPath(locale)
|
const getLocalePath = getPath(locale)
|
||||||
|
@ -25,7 +25,7 @@ const {
|
||||||
<p class="max-w-xl text-lg text-gray-500 dark:text-gray-400">
|
<p class="max-w-xl text-lg text-gray-500 dark:text-gray-400">
|
||||||
{notFound.description}
|
{notFound.description}
|
||||||
</p>
|
</p>
|
||||||
<Button href={getLocalePath("/")} isPrimary class="w-fit">
|
<Button href={getLocalePath('/')} isPrimary class="w-fit">
|
||||||
{notFound.button}
|
{notFound.button}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
---
|
---
|
||||||
import { Image } from "astro:assets"
|
import { Image } from 'astro:assets'
|
||||||
import Description from "~/components/Description.astro"
|
import Description from '~/components/Description.astro'
|
||||||
import Layout from "~/layouts/Layout.astro"
|
import Layout from '~/layouts/Layout.astro'
|
||||||
import { getLocale, getUI } from "~/utils/i18n"
|
import { getLocale, getUI } from '~/utils/i18n'
|
||||||
export { getStaticPaths } from "~/utils/i18n"
|
export { getStaticPaths } from '~/utils/i18n'
|
||||||
import Button from "~/components/Button.astro"
|
import Button from '~/components/Button.astro'
|
||||||
|
|
||||||
const locale = getLocale(Astro)
|
const locale = getLocale(Astro)
|
||||||
|
|
||||||
|
@ -38,7 +38,7 @@ const {
|
||||||
<ul class="flex flex-col gap-2">
|
<ul class="flex flex-col gap-2">
|
||||||
{Object.entries(members).map(([_key, member]) => (
|
{Object.entries(members).map(([_key, member]) => (
|
||||||
<li class="text-sm">
|
<li class="text-sm">
|
||||||
{member.link && typeof member.link === "string" ? (
|
{member.link && typeof member.link === 'string' ? (
|
||||||
<a href={member.link}>
|
<a href={member.link}>
|
||||||
<strong class="zen-link font-bold">{member.name}</strong>
|
<strong class="zen-link font-bold">{member.name}</strong>
|
||||||
</a>
|
</a>
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
---
|
---
|
||||||
import Button from "~/components/Button.astro"
|
import Button from '~/components/Button.astro'
|
||||||
import Description from "~/components/Description.astro"
|
import Description from '~/components/Description.astro'
|
||||||
import ArrowRightIcon from "~/icons/ArrowRightIcon.astro"
|
import ArrowRightIcon from '~/icons/ArrowRightIcon.astro'
|
||||||
import Layout from "~/layouts/Layout.astro"
|
import Layout from '~/layouts/Layout.astro'
|
||||||
import { getLocale, getUI } from "~/utils/i18n"
|
import { getLocale, getUI } from '~/utils/i18n'
|
||||||
export { getStaticPaths } from "~/utils/i18n"
|
export { getStaticPaths } from '~/utils/i18n'
|
||||||
|
|
||||||
const locale = getLocale(Astro)
|
const locale = getLocale(Astro)
|
||||||
const {
|
const {
|
||||||
|
|
|
@ -1,18 +1,18 @@
|
||||||
---
|
---
|
||||||
import Description from "~/components/Description.astro"
|
import Description from '~/components/Description.astro'
|
||||||
import DownloadScript from "~/components/download/DownloadScript.astro"
|
import DownloadScript from '~/components/download/DownloadScript.astro'
|
||||||
import PlatformDownload from "~/components/download/PlatformDownload.astro"
|
import PlatformDownload from '~/components/download/PlatformDownload.astro'
|
||||||
import { getReleasesWithChecksums } from "~/components/download/release-data"
|
import { getReleasesWithChecksums } from '~/components/download/release-data'
|
||||||
import Layout from "~/layouts/Layout.astro"
|
import Layout from '~/layouts/Layout.astro'
|
||||||
import { getChecksums } from "~/utils/githubChecksums"
|
import { getChecksums } from '~/utils/githubChecksums'
|
||||||
import { getLocale, getUI } from "~/utils/i18n"
|
import { getLocale, getUI } from '~/utils/i18n'
|
||||||
|
|
||||||
import { icon, library } from "@fortawesome/fontawesome-svg-core"
|
import { icon, library } from '@fortawesome/fontawesome-svg-core'
|
||||||
import { faApple, faGithub, faLinux, faWindows } from "@fortawesome/free-brands-svg-icons"
|
import { faApple, faGithub, faLinux, faWindows } from '@fortawesome/free-brands-svg-icons'
|
||||||
import ExternalLinkIcon from "~/icons/ExternalLink.astro"
|
import ExternalLinkIcon from '~/icons/ExternalLink.astro'
|
||||||
import LockIcon from "~/icons/LockIcon.astro"
|
import LockIcon from '~/icons/LockIcon.astro'
|
||||||
|
|
||||||
export { getStaticPaths } from "~/utils/i18n"
|
export { getStaticPaths } from '~/utils/i18n'
|
||||||
|
|
||||||
const locale = getLocale(Astro)
|
const locale = getLocale(Astro)
|
||||||
const {
|
const {
|
||||||
|
@ -21,10 +21,10 @@ const {
|
||||||
} = getUI(locale)
|
} = getUI(locale)
|
||||||
|
|
||||||
library.add(faWindows, faLinux, faApple, faGithub)
|
library.add(faWindows, faLinux, faApple, faGithub)
|
||||||
const windowsIcon = icon({ prefix: "fab", iconName: "windows" })
|
const windowsIcon = icon({ prefix: 'fab', iconName: 'windows' })
|
||||||
const linuxIcon = icon({ prefix: "fab", iconName: "linux" })
|
const linuxIcon = icon({ prefix: 'fab', iconName: 'linux' })
|
||||||
const appleIcon = icon({ prefix: "fab", iconName: "apple" })
|
const appleIcon = icon({ prefix: 'fab', iconName: 'apple' })
|
||||||
const githubIcon = icon({ prefix: "fab", iconName: "github" })
|
const githubIcon = icon({ prefix: 'fab', iconName: 'github' })
|
||||||
|
|
||||||
const checksums = await getChecksums()
|
const checksums = await getChecksums()
|
||||||
const releases = getReleasesWithChecksums(checksums)
|
const releases = getReleasesWithChecksums(checksums)
|
||||||
|
|
|
@ -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. */
|
/** The default number of entries to include in the RSS feed. */
|
||||||
const RSS_ENTRY_LIMIT = 20
|
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()
|
releaseNotes.length > 0 ? formatRssDate(releaseNotes[0].date as string) : new Date()
|
||||||
|
|
||||||
const rssData: RSSOptions = {
|
const rssData: RSSOptions = {
|
||||||
title: "Zen Browser Release Notes",
|
title: 'Zen Browser Release Notes',
|
||||||
description: "Release Notes for the Zen Browser",
|
description: 'Release Notes for the Zen Browser',
|
||||||
site: context.url,
|
site: context.url,
|
||||||
items: [],
|
items: [],
|
||||||
customData: `
|
customData: `
|
||||||
|
@ -55,9 +55,9 @@ export function GET(context: { url: URL }) {
|
||||||
* @returns The passed in date string as a Date object.
|
* @returns The passed in date string as a Date object.
|
||||||
*/
|
*/
|
||||||
function formatRssDate(dateStr: string) {
|
function formatRssDate(dateStr: string) {
|
||||||
const splitDate = dateStr.split("/")
|
const splitDate = dateStr.split('/')
|
||||||
if (splitDate.length !== 3) {
|
if (splitDate.length !== 3) {
|
||||||
throw new Error("Invalid date format")
|
throw new Error('Invalid date format')
|
||||||
}
|
}
|
||||||
|
|
||||||
const day = Number(splitDate[0])
|
const day = Number(splitDate[0])
|
||||||
|
@ -78,43 +78,43 @@ function formatReleaseNote(releaseNote: ReleaseNote) {
|
||||||
</p>`
|
</p>`
|
||||||
|
|
||||||
if (releaseNote.extra) {
|
if (releaseNote.extra) {
|
||||||
content += `<p>${releaseNote.extra.replace(/(\n)/g, "<br />")}</p>`
|
content += `<p>${releaseNote.extra.replace(/(\n)/g, '<br />')}</p>`
|
||||||
}
|
}
|
||||||
|
|
||||||
content += addReleaseNoteSection(
|
content += addReleaseNoteSection(
|
||||||
"⚠️ Breaking changes",
|
'⚠️ Breaking changes',
|
||||||
releaseNote.breakingChanges?.map(breakingChangeToReleaseNote)
|
releaseNote.breakingChanges?.map(breakingChangeToReleaseNote)
|
||||||
)
|
)
|
||||||
content += addReleaseNoteSection("✓ Fixes", releaseNote.fixes?.map(fixToReleaseNote))
|
content += addReleaseNoteSection('✓ Fixes', releaseNote.fixes?.map(fixToReleaseNote))
|
||||||
content += addReleaseNoteSection("🖌 Theme Changes", releaseNote.themeChanges)
|
content += addReleaseNoteSection('🖌 Theme Changes', releaseNote.themeChanges)
|
||||||
content += addReleaseNoteSection("⭐ Features", releaseNote.features)
|
content += addReleaseNoteSection('⭐ Features', releaseNote.features)
|
||||||
|
|
||||||
return content
|
return content
|
||||||
}
|
}
|
||||||
|
|
||||||
function addReleaseNoteSection(title: string, items?: string[]): string {
|
function addReleaseNoteSection(title: string, items?: string[]): string {
|
||||||
if (!items) {
|
if (!items) {
|
||||||
return ""
|
return ''
|
||||||
}
|
}
|
||||||
|
|
||||||
let content = `<h2>${title}</h2>`
|
let content = `<h2>${title}</h2>`
|
||||||
content += "<ul>"
|
content += '<ul>'
|
||||||
for (const item of items) {
|
for (const item of items) {
|
||||||
if (item && item.length > 0) {
|
if (item && item.length > 0) {
|
||||||
content += `<li>${item}</li>`
|
content += `<li>${item}</li>`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
content += "</ul>"
|
content += '</ul>'
|
||||||
return content
|
return content
|
||||||
}
|
}
|
||||||
|
|
||||||
function fixToReleaseNote(fix?: Exclude<ReleaseNote["fixes"], undefined>[number]) {
|
function fixToReleaseNote(fix?: Exclude<ReleaseNote['fixes'], undefined>[number]) {
|
||||||
if (typeof fix === "string") {
|
if (typeof fix === 'string') {
|
||||||
return fix
|
return fix
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!fix || !fix.description || fix.description.length === 0) {
|
if (!fix || !fix.description || fix.description.length === 0) {
|
||||||
return ""
|
return ''
|
||||||
}
|
}
|
||||||
|
|
||||||
let note = fix.description
|
let note = fix.description
|
||||||
|
@ -125,14 +125,14 @@ function fixToReleaseNote(fix?: Exclude<ReleaseNote["fixes"], undefined>[number]
|
||||||
}
|
}
|
||||||
|
|
||||||
function breakingChangeToReleaseNote(
|
function breakingChangeToReleaseNote(
|
||||||
breakingChange?: Exclude<ReleaseNote["breakingChanges"], undefined>[number]
|
breakingChange?: Exclude<ReleaseNote['breakingChanges'], undefined>[number]
|
||||||
) {
|
) {
|
||||||
if (typeof breakingChange === "string") {
|
if (typeof breakingChange === 'string') {
|
||||||
return breakingChange
|
return breakingChange
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!breakingChange || !breakingChange.description || breakingChange.description.length === 0) {
|
if (!breakingChange || !breakingChange.description || breakingChange.description.length === 0) {
|
||||||
return ""
|
return ''
|
||||||
}
|
}
|
||||||
|
|
||||||
return `${breakingChange.description} (<a href="${breakingChange.link}" target="_blank">Learn more</a>)`
|
return `${breakingChange.description} (<a href="${breakingChange.link}" target="_blank">Learn more</a>)`
|
||||||
|
@ -141,10 +141,10 @@ function breakingChangeToReleaseNote(
|
||||||
function pubDate(date?: Date) {
|
function pubDate(date?: Date) {
|
||||||
const newDate = date ?? new Date()
|
const newDate = date ?? new Date()
|
||||||
|
|
||||||
const pieces = newDate.toString().split(" ")
|
const pieces = newDate.toString().split(' ')
|
||||||
const offsetTime = pieces[5].match(/[-+]\d{4}/)
|
const offsetTime = pieces[5].match(/[-+]\d{4}/)
|
||||||
const offset = offsetTime ? offsetTime : pieces[5]
|
const offset = offsetTime ? offsetTime : pieces[5]
|
||||||
const parts = [`${pieces[0]},`, pieces[2], pieces[1], pieces[3], pieces[4], offset]
|
const parts = [`${pieces[0]},`, pieces[2], pieces[1], pieces[3], pieces[4], offset]
|
||||||
|
|
||||||
return parts.join(" ")
|
return parts.join(' ')
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
---
|
---
|
||||||
import Community from "~/components/Community.astro"
|
import Community from '~/components/Community.astro'
|
||||||
import Features from "~/components/Features.astro"
|
import Features from '~/components/Features.astro'
|
||||||
import Hero from "~/components/Hero.astro"
|
import Hero from '~/components/Hero.astro'
|
||||||
import Sponsors from "~/components/Sponsors.astro"
|
import Sponsors from '~/components/Sponsors.astro'
|
||||||
import Layout from "~/layouts/Layout.astro"
|
import Layout from '~/layouts/Layout.astro'
|
||||||
import { getLocale, getUI } from "~/utils/i18n"
|
import { getLocale, getUI } from '~/utils/i18n'
|
||||||
export { getStaticPaths } from "~/utils/i18n"
|
export { getStaticPaths } from '~/utils/i18n'
|
||||||
|
|
||||||
const locale = getLocale(Astro)
|
const locale = getLocale(Astro)
|
||||||
|
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
---
|
---
|
||||||
import BackButton from "~/components/BackButton.astro"
|
import BackButton from '~/components/BackButton.astro'
|
||||||
import Button from "~/components/Button.astro"
|
import Button from '~/components/Button.astro'
|
||||||
import Description from "~/components/Description.astro"
|
import Description from '~/components/Description.astro'
|
||||||
import ArrowRightIcon from "~/icons/ArrowRightIcon.astro"
|
import ArrowRightIcon from '~/icons/ArrowRightIcon.astro'
|
||||||
import InfoIcon from "~/icons/InfoIcon.astro"
|
import InfoIcon from '~/icons/InfoIcon.astro'
|
||||||
import Layout from "~/layouts/Layout.astro"
|
import Layout from '~/layouts/Layout.astro'
|
||||||
import { getAllMods, getAuthorLink, getLocalizedDate } from "~/mods"
|
import { getAllMods, getAuthorLink, getLocalizedDate } from '~/mods'
|
||||||
import { getUI } from "~/utils/i18n"
|
import { getUI } from '~/utils/i18n'
|
||||||
import { getLocale, getOtherLocales } from "~/utils/i18n"
|
import { getLocale, getOtherLocales } from '~/utils/i18n'
|
||||||
|
|
||||||
export async function getStaticPaths() {
|
export async function getStaticPaths() {
|
||||||
const mods = await getAllMods()
|
const mods = await getAllMods()
|
||||||
|
@ -54,8 +54,8 @@ const {
|
||||||
---
|
---
|
||||||
|
|
||||||
<Layout
|
<Layout
|
||||||
title={slug.title.replace("{name}", mod.name)}
|
title={slug.title.replace('{name}', mod.name)}
|
||||||
description={slug.description.replace("{name}", mod.name)}
|
description={slug.description.replace('{name}', mod.name)}
|
||||||
ogImage={mod.image}
|
ogImage={mod.image}
|
||||||
>
|
>
|
||||||
<main class="mt-6 2xl:mt-0">
|
<main class="mt-6 2xl:mt-0">
|
||||||
|
@ -93,14 +93,14 @@ const {
|
||||||
<div class="flex flex-shrink-0 flex-col gap-2 font-normal">
|
<div class="flex flex-shrink-0 flex-col gap-2 font-normal">
|
||||||
<p
|
<p
|
||||||
set:html={slug.createdBy
|
set:html={slug.createdBy
|
||||||
.replace("{author}", mod.author)
|
.replace('{author}', mod.author)
|
||||||
.replace("{version}", mod.version)
|
.replace('{version}', mod.version)
|
||||||
.replace("{link}", getAuthorLink(mod.author))}
|
.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 && (
|
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>
|
||||||
<div class="flex flex-col sm:items-end">
|
<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}
|
{slug.installMod}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
class="hidden"
|
class="hidden"
|
||||||
id="install-theme-uninstall"
|
id="install-theme-uninstall"
|
||||||
extra={{ "zen-theme-id": mod.id }}
|
extra={{ 'zen-theme-id': mod.id }}
|
||||||
isPrimary
|
isPrimary
|
||||||
>
|
>
|
||||||
{slug.uninstallMod}
|
{slug.uninstallMod}
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
---
|
---
|
||||||
import Description from "~/components/Description.astro"
|
import Description from '~/components/Description.astro'
|
||||||
import ModsList from "~/components/ModsList"
|
import ModsList from '~/components/ModsList'
|
||||||
import { CONSTANT } from "~/constants"
|
import { CONSTANT } from '~/constants'
|
||||||
import Layout from "~/layouts/Layout.astro"
|
import Layout from '~/layouts/Layout.astro'
|
||||||
import { getAllMods } from "~/mods"
|
import { getAllMods } from '~/mods'
|
||||||
import { getLocale, getUI } from "~/utils/i18n"
|
import { getLocale, getUI } from '~/utils/i18n'
|
||||||
export { getStaticPaths } from "~/utils/i18n"
|
export { getStaticPaths } from '~/utils/i18n'
|
||||||
|
|
||||||
const locale = getLocale(Astro)
|
const locale = getLocale(Astro)
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
---
|
---
|
||||||
import Title from "~/components/Title.astro"
|
import Title from '~/components/Title.astro'
|
||||||
import Layout from "~/layouts/Layout.astro"
|
import Layout from '~/layouts/Layout.astro'
|
||||||
import { getLocale, getUI } from "~/utils/i18n"
|
import { getLocale, getUI } from '~/utils/i18n'
|
||||||
export { getStaticPaths } from "~/utils/i18n"
|
export { getStaticPaths } from '~/utils/i18n'
|
||||||
|
|
||||||
const locale = getLocale(Astro)
|
const locale = getLocale(Astro)
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
---
|
---
|
||||||
import Layout from "~/layouts/Layout.astro"
|
import Layout from '~/layouts/Layout.astro'
|
||||||
import { releaseNotes } from "~/release-notes"
|
import { releaseNotes } from '~/release-notes'
|
||||||
import { getStaticPaths as getI18nPaths, getLocale, getUI } from "~/utils/i18n"
|
import { getStaticPaths as getI18nPaths, getLocale, getUI } from '~/utils/i18n'
|
||||||
|
|
||||||
const locale = getLocale(Astro)
|
const locale = getLocale(Astro)
|
||||||
|
|
||||||
|
@ -20,7 +20,7 @@ export async function getStaticPaths() {
|
||||||
props: { ...release },
|
props: { ...release },
|
||||||
})),
|
})),
|
||||||
{
|
{
|
||||||
params: { slug: "latest", locale },
|
params: { slug: 'latest', locale },
|
||||||
props: { ...releaseNotes[0] },
|
props: { ...releaseNotes[0] },
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
|
@ -31,6 +31,6 @@ const release = Astro.props
|
||||||
|
|
||||||
<Layout title={slug.title} redirect={`/release-notes#${release.version}`}>
|
<Layout title={slug.title} redirect={`/release-notes#${release.version}`}>
|
||||||
<main class="flex flex-col items-center pb-52 pt-36">
|
<main class="flex flex-col items-center pb-52 pt-36">
|
||||||
{slug.redirect.replaceAll("{version}", release.version)}
|
{slug.redirect.replaceAll('{version}', release.version)}
|
||||||
</main>
|
</main>
|
||||||
</Layout>
|
</Layout>
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
---
|
---
|
||||||
import { Modal, ModalBody, ModalHeader } from "free-astro-components"
|
import { Modal, ModalBody, ModalHeader } from 'free-astro-components'
|
||||||
import Button from "~/components/Button.astro"
|
import Button from '~/components/Button.astro'
|
||||||
import Description from "~/components/Description.astro"
|
import Description from '~/components/Description.astro'
|
||||||
import ReleaseNoteItem from "~/components/ReleaseNoteItem.astro"
|
import ReleaseNoteItem from '~/components/ReleaseNoteItem.astro'
|
||||||
import ArrowUpIcon from "~/icons/ArrowUp.astro"
|
import ArrowUpIcon from '~/icons/ArrowUp.astro'
|
||||||
import Layout from "~/layouts/Layout.astro"
|
import Layout from '~/layouts/Layout.astro'
|
||||||
import { releaseNotes as releaseNotesData, releaseNotesTwilight } from "~/release-notes"
|
import { releaseNotes as releaseNotesData, releaseNotesTwilight } from '~/release-notes'
|
||||||
import { getLocale, getUI } from "~/utils/i18n"
|
import { getLocale, getUI } from '~/utils/i18n'
|
||||||
export { getStaticPaths } from "~/utils/i18n"
|
export { getStaticPaths } from '~/utils/i18n'
|
||||||
|
|
||||||
const locale = getLocale(Astro)
|
const locale = getLocale(Astro)
|
||||||
|
|
||||||
|
@ -26,7 +26,7 @@ const {
|
||||||
<p
|
<p
|
||||||
class="text-base opacity-55"
|
class="text-base opacity-55"
|
||||||
set:html={releaseNotes.topSection.description.replaceAll(
|
set:html={releaseNotes.topSection.description.replaceAll(
|
||||||
"{latestVersion}",
|
'{latestVersion}',
|
||||||
releaseNotesData[0].version
|
releaseNotesData[0].version
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
@ -75,39 +75,39 @@ const {
|
||||||
</ModalBody>
|
</ModalBody>
|
||||||
</Modal>
|
</Modal>
|
||||||
<script>
|
<script>
|
||||||
import { closeModal, openModal } from "free-astro-components"
|
import { closeModal, openModal } from 'free-astro-components'
|
||||||
|
|
||||||
const scrollTopButton = document.getElementById("scroll-top")
|
const scrollTopButton = document.getElementById('scroll-top')
|
||||||
const versionButton = document.getElementById("navigate-to-version")
|
const versionButton = document.getElementById('navigate-to-version')
|
||||||
const container = document.getElementById("release-notes")
|
const container = document.getElementById('release-notes')
|
||||||
const modal = document.getElementById("version-modal")
|
const modal = document.getElementById('version-modal')
|
||||||
const versionList = document.getElementById("version-list")
|
const versionList = document.getElementById('version-list')
|
||||||
|
|
||||||
const toggleScrollButton = () => {
|
const toggleScrollButton = () => {
|
||||||
if (!scrollTopButton || !versionButton) return
|
if (!scrollTopButton || !versionButton) return
|
||||||
|
|
||||||
const descriptionPosition = versionButton.getBoundingClientRect().bottom
|
const descriptionPosition = versionButton.getBoundingClientRect().bottom
|
||||||
if (descriptionPosition < 0) {
|
if (descriptionPosition < 0) {
|
||||||
scrollTopButton.classList.remove("hidden")
|
scrollTopButton.classList.remove('hidden')
|
||||||
scrollTopButton.classList.add("block")
|
scrollTopButton.classList.add('block')
|
||||||
} else {
|
} else {
|
||||||
scrollTopButton.classList.remove("block")
|
scrollTopButton.classList.remove('block')
|
||||||
scrollTopButton.classList.add("hidden")
|
scrollTopButton.classList.add('hidden')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const navigateToVersion = (e: MouseEvent) => {
|
const navigateToVersion = (e: MouseEvent) => {
|
||||||
const target = e.target as HTMLElement
|
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
|
if (!version) return
|
||||||
window.location.hash = version
|
window.location.hash = version
|
||||||
|
|
||||||
const versionDetails = document.getElementById(version)?.getElementsByTagName("details")
|
const versionDetails = document.getElementById(version)?.getElementsByTagName('details')
|
||||||
if (versionDetails && versionDetails.length > 0) {
|
if (versionDetails && versionDetails.length > 0) {
|
||||||
Array.from(versionDetails).forEach(accordion => {
|
Array.from(versionDetails).forEach(accordion => {
|
||||||
accordion.setAttribute("open", "")
|
accordion.setAttribute('open', '')
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -119,21 +119,21 @@ const {
|
||||||
openModal(modal)
|
openModal(modal)
|
||||||
}
|
}
|
||||||
|
|
||||||
window.addEventListener("scroll", toggleScrollButton)
|
window.addEventListener('scroll', toggleScrollButton)
|
||||||
versionButton?.addEventListener("click", openVersionModal)
|
versionButton?.addEventListener('click', openVersionModal)
|
||||||
versionList?.addEventListener("click", navigateToVersion)
|
versionList?.addEventListener('click', navigateToVersion)
|
||||||
|
|
||||||
document.addEventListener("keydown", e => {
|
document.addEventListener('keydown', e => {
|
||||||
if (e.key === "Escape" && modal?.hasAttribute("open")) {
|
if (e.key === 'Escape' && modal?.hasAttribute('open')) {
|
||||||
closeModal(modal)
|
closeModal(modal)
|
||||||
// Remove scroll lock if present
|
// Remove scroll lock if present
|
||||||
document.body.style.overflow = ""
|
document.body.style.overflow = ''
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style is:global>
|
<style is:global>
|
||||||
#version-modal > * {
|
#version-modal > * {
|
||||||
font-family: "Bricolage Grotesque", sans-serif !important;
|
font-family: 'Bricolage Grotesque', sans-serif !important;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
---
|
---
|
||||||
import Features from "~/components/Features.astro"
|
import Features from '~/components/Features.astro'
|
||||||
import Layout from "~/layouts/Layout.astro"
|
import Layout from '~/layouts/Layout.astro'
|
||||||
import { getLocale, getUI } from "~/utils/i18n"
|
import { getLocale, getUI } from '~/utils/i18n'
|
||||||
export { getStaticPaths } from "~/utils/i18n"
|
export { getStaticPaths } from '~/utils/i18n'
|
||||||
|
|
||||||
const locale = getLocale(Astro)
|
const locale = getLocale(Astro)
|
||||||
|
|
||||||
|
|
|
@ -1,16 +1,16 @@
|
||||||
---
|
---
|
||||||
import Button from "~/components/Button.astro"
|
import Button from '~/components/Button.astro'
|
||||||
import Description from "~/components/Description.astro"
|
import Description from '~/components/Description.astro'
|
||||||
import SocialMediaStrip from "~/components/SocialMediaStrip.astro"
|
import SocialMediaStrip from '~/components/SocialMediaStrip.astro'
|
||||||
import ArrowRightIcon from "~/icons/ArrowRightIcon.astro"
|
import ArrowRightIcon from '~/icons/ArrowRightIcon.astro'
|
||||||
import Layout from "~/layouts/Layout.astro"
|
import Layout from '~/layouts/Layout.astro'
|
||||||
|
|
||||||
import whatsNewVideo from "~/assets/whats-new.mp4"
|
import whatsNewVideo from '~/assets/whats-new.mp4'
|
||||||
import Video from "~/components/Video.astro"
|
import Video from '~/components/Video.astro'
|
||||||
import { releaseNotes } from "~/release-notes"
|
import { releaseNotes } from '~/release-notes'
|
||||||
import whatsNewText from "~/release-notes/whats-new.json"
|
import whatsNewText from '~/release-notes/whats-new.json'
|
||||||
import { getLocale, getUI } from "~/utils/i18n"
|
import { getLocale, getUI } from '~/utils/i18n'
|
||||||
export { getStaticPaths } from "~/utils/i18n"
|
export { getStaticPaths } from '~/utils/i18n'
|
||||||
|
|
||||||
const latestVersion = releaseNotes[0]
|
const latestVersion = releaseNotes[0]
|
||||||
|
|
||||||
|
@ -22,24 +22,24 @@ const {
|
||||||
} = getUI(locale)
|
} = getUI(locale)
|
||||||
|
|
||||||
// Just redirect to the release notes if we are in a patch version
|
// 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}`)
|
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
|
<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]"
|
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 class="flex flex-col gap-8">
|
||||||
<div>
|
<div>
|
||||||
<Description class="text-5xl font-bold md:text-6xl"
|
<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>
|
<Description>{latestVersion.date}</Description>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<Fragment set:html={whatsNewText[0].replace(/\n/g, "<br>")} />
|
<Fragment set:html={whatsNewText[0].replace(/\n/g, '<br>')} />
|
||||||
</div>
|
</div>
|
||||||
<ul class="hidden list-disc flex-col gap-2 xl:container xl:flex">
|
<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">
|
<a href="https://github.com/zen-browser/desktop/issues/new/choose" target="_blank">
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import releaseNotesStable from "./release-notes/stable.json"
|
import releaseNotesStable from './release-notes/stable.json'
|
||||||
|
|
||||||
type FixWithIssue = {
|
type FixWithIssue = {
|
||||||
description: string
|
description: string
|
||||||
|
@ -25,12 +25,12 @@ export type ReleaseNote = {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const releaseNotes: ReleaseNote[] = releaseNotesStable.reverse()
|
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 {
|
export function getReleaseNoteFirefoxVersion(releaseNote: ReleaseNote): string | null {
|
||||||
// Check if "firefox" is on the feature list
|
// Check if "firefox" is on the feature list
|
||||||
for (const feature of releaseNote.features || []) {
|
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
|
// may be X or X.X or X.X.X
|
||||||
const match = feature.match(/(\d+(\.\d+){0,2})/)
|
const match = feature.match(/(\d+(\.\d+){0,2})/)
|
||||||
if (match) {
|
if (match) {
|
||||||
|
|
|
@ -1,101 +1,101 @@
|
||||||
import { experimental_AstroContainer as AstroContainer } from "astro/container"
|
import { experimental_AstroContainer as AstroContainer } from 'astro/container'
|
||||||
import { beforeEach, describe, expect, it } from "vitest"
|
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>>
|
let container: Awaited<ReturnType<typeof AstroContainer.create>>
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
container = await AstroContainer.create()
|
container = await AstroContainer.create()
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("as <button>", () => {
|
describe('as <button>', () => {
|
||||||
it("renders default <button> with slot", async () => {
|
it('renders default <button> with slot', async () => {
|
||||||
const result = await container.renderToString(Button, {
|
const result = await container.renderToString(Button, {
|
||||||
props: {},
|
props: {},
|
||||||
slots: { default: "Click me" },
|
slots: { default: 'Click me' },
|
||||||
})
|
})
|
||||||
expect(result).toContain("<button")
|
expect(result).toContain('<button')
|
||||||
expect(result).toContain("Click me")
|
expect(result).toContain('Click me')
|
||||||
})
|
})
|
||||||
|
|
||||||
it.each([
|
it.each([
|
||||||
["isPrimary", { isPrimary: true }, "bg-dark"],
|
['isPrimary', { isPrimary: true }, 'bg-dark'],
|
||||||
["isAlert", { isAlert: true }, "bg-red-300"],
|
['isAlert', { isAlert: true }, 'bg-red-300'],
|
||||||
["isBordered", { isBordered: true }, "border-2"],
|
['isBordered', { isBordered: true }, 'border-2'],
|
||||||
])("applies %s style", async (_label, propObj, expectedClass) => {
|
])('applies %s style', async (_label, propObj, expectedClass) => {
|
||||||
const result = await container.renderToString(Button, {
|
const result = await container.renderToString(Button, {
|
||||||
props: { ...propObj },
|
props: { ...propObj },
|
||||||
slots: { default: "Test" },
|
slots: { default: 'Test' },
|
||||||
})
|
})
|
||||||
expect(result).toContain("<button")
|
expect(result).toContain('<button')
|
||||||
expect(result).toContain(expectedClass)
|
expect(result).toContain(expectedClass)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("applies id and extra props", async () => {
|
it('applies id and extra props', async () => {
|
||||||
const result = await container.renderToString(Button, {
|
const result = await container.renderToString(Button, {
|
||||||
props: {
|
props: {
|
||||||
id: "my-btn",
|
id: 'my-btn',
|
||||||
extra: { "data-test": "foo" },
|
extra: { 'data-test': 'foo' },
|
||||||
},
|
},
|
||||||
slots: { default: "Test" },
|
slots: { default: 'Test' },
|
||||||
})
|
})
|
||||||
expect(result).toContain('id="my-btn"')
|
expect(result).toContain('id="my-btn"')
|
||||||
expect(result).toContain('data-test="foo"')
|
expect(result).toContain('data-test="foo"')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("as <a>", () => {
|
describe('as <a>', () => {
|
||||||
it("renders <a> with slot and href", async () => {
|
it('renders <a> with slot and href', async () => {
|
||||||
const result = await container.renderToString(Button, {
|
const result = await container.renderToString(Button, {
|
||||||
props: { href: "/link" },
|
props: { href: '/link' },
|
||||||
slots: { default: "Go" },
|
slots: { default: 'Go' },
|
||||||
})
|
})
|
||||||
expect(result).toContain("<a")
|
expect(result).toContain('<a')
|
||||||
expect(result).toContain("Go")
|
expect(result).toContain('Go')
|
||||||
expect(result).toContain('href="/en/link"')
|
expect(result).toContain('href="/en/link"')
|
||||||
})
|
})
|
||||||
|
|
||||||
it.each([
|
it.each([
|
||||||
["isPrimary", { isPrimary: true }, "bg-dark"],
|
['isPrimary', { isPrimary: true }, 'bg-dark'],
|
||||||
["isAlert", { isAlert: true }, "bg-red-300"],
|
['isAlert', { isAlert: true }, 'bg-red-300'],
|
||||||
["isBordered", { isBordered: true }, "border-2"],
|
['isBordered', { isBordered: true }, 'border-2'],
|
||||||
])("applies %s style", async (_label, propObj, expectedClass) => {
|
])('applies %s style', async (_label, propObj, expectedClass) => {
|
||||||
const result = await container.renderToString(Button, {
|
const result = await container.renderToString(Button, {
|
||||||
props: { href: "/link", ...propObj },
|
props: { href: '/link', ...propObj },
|
||||||
slots: { default: "Test" },
|
slots: { default: 'Test' },
|
||||||
})
|
})
|
||||||
expect(result).toContain("<a")
|
expect(result).toContain('<a')
|
||||||
expect(result).toContain(expectedClass)
|
expect(result).toContain(expectedClass)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("applies id and extra props", async () => {
|
it('applies id and extra props', async () => {
|
||||||
const result = await container.renderToString(Button, {
|
const result = await container.renderToString(Button, {
|
||||||
props: {
|
props: {
|
||||||
href: "/link",
|
href: '/link',
|
||||||
id: "my-link",
|
id: 'my-link',
|
||||||
extra: { "data-test": "bar" },
|
extra: { 'data-test': 'bar' },
|
||||||
},
|
},
|
||||||
slots: { default: "Test" },
|
slots: { default: 'Test' },
|
||||||
})
|
})
|
||||||
expect(result).toContain('id="my-link"')
|
expect(result).toContain('id="my-link"')
|
||||||
expect(result).toContain('data-test="bar"')
|
expect(result).toContain('data-test="bar"')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it("applies custom className", async () => {
|
it('applies custom className', async () => {
|
||||||
const result = await container.renderToString(Button, {
|
const result = await container.renderToString(Button, {
|
||||||
props: { class: "custom-class" },
|
props: { class: 'custom-class' },
|
||||||
slots: { default: "Test" },
|
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, {
|
const result = await container.renderToString(Button, {
|
||||||
props: { href: "/foo" },
|
props: { href: '/foo' },
|
||||||
slots: { default: "Test" },
|
slots: { default: 'Test' },
|
||||||
})
|
})
|
||||||
expect(result).toContain('href="/en/foo"')
|
expect(result).toContain('href="/en/foo"')
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,48 +1,48 @@
|
||||||
import { experimental_AstroContainer as AstroContainer } from "astro/container"
|
import { experimental_AstroContainer as AstroContainer } from 'astro/container'
|
||||||
import { beforeEach, describe, expect, it } from "vitest"
|
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>>
|
let container: Awaited<ReturnType<typeof AstroContainer.create>>
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
container = await AstroContainer.create()
|
container = await AstroContainer.create()
|
||||||
})
|
})
|
||||||
|
|
||||||
it("renders with required props", async () => {
|
it('renders with required props', async () => {
|
||||||
const result = await container.renderToString(ButtonCard, {
|
const result = await container.renderToString(ButtonCard, {
|
||||||
props: {
|
props: {
|
||||||
label: "Download",
|
label: 'Download',
|
||||||
href: "/download",
|
href: '/download',
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
expect(result).toContain("Download")
|
expect(result).toContain('Download')
|
||||||
expect(result).toContain('href="/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, {
|
const result = await container.renderToString(ButtonCard, {
|
||||||
props: {
|
props: {
|
||||||
label: "Download",
|
label: 'Download',
|
||||||
href: "/download",
|
href: '/download',
|
||||||
checksum: "sha256sum",
|
checksum: 'sha256sum',
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
expect(result).toContain("Show SHA-256")
|
expect(result).toContain('Show SHA-256')
|
||||||
expect(result).toContain("sha256sum")
|
expect(result).toContain('sha256sum')
|
||||||
expect(result).toContain("Copy")
|
expect(result).toContain('Copy')
|
||||||
})
|
})
|
||||||
|
|
||||||
it("renders with variant", async () => {
|
it('renders with variant', async () => {
|
||||||
const result = await container.renderToString(ButtonCard, {
|
const result = await container.renderToString(ButtonCard, {
|
||||||
props: {
|
props: {
|
||||||
label: "Download",
|
label: 'Download',
|
||||||
href: "/download",
|
href: '/download',
|
||||||
variant: "flathub",
|
variant: 'flathub',
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
expect(result).toContain("Download")
|
expect(result).toContain('Download')
|
||||||
expect(result).toContain("Beta")
|
expect(result).toContain('Beta')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,126 +1,126 @@
|
||||||
import { experimental_AstroContainer as AstroContainer } from "astro/container"
|
import { experimental_AstroContainer as AstroContainer } from 'astro/container'
|
||||||
import { beforeEach, describe, expect, it } from "vitest"
|
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 = {
|
const mockReleases = {
|
||||||
universal: { label: "Universal", link: "/universal", checksum: "abc123" },
|
universal: { label: 'Universal', link: '/universal', checksum: 'abc123' },
|
||||||
x86_64: { label: "x86_64", link: "/x86_64", checksum: "def456" },
|
x86_64: { label: 'x86_64', link: '/x86_64', checksum: 'def456' },
|
||||||
arm64: { label: "ARM64", link: "/arm64", checksum: "ghi789" },
|
arm64: { label: 'ARM64', link: '/arm64', checksum: 'ghi789' },
|
||||||
flathub: { all: { label: "Flathub", link: "/flathub" } },
|
flathub: { all: { label: 'Flathub', link: '/flathub' } },
|
||||||
}
|
}
|
||||||
|
|
||||||
describe("<PlatformDownload />", () => {
|
describe('<PlatformDownload />', () => {
|
||||||
let container: Awaited<ReturnType<typeof AstroContainer.create>>
|
let container: Awaited<ReturnType<typeof AstroContainer.create>>
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
container = await AstroContainer.create()
|
container = await AstroContainer.create()
|
||||||
})
|
})
|
||||||
|
|
||||||
it("renders mac platform", async () => {
|
it('renders mac platform', async () => {
|
||||||
const result = await container.renderToString(PlatformDownload, {
|
const result = await container.renderToString(PlatformDownload, {
|
||||||
props: {
|
props: {
|
||||||
platform: "mac",
|
platform: 'mac',
|
||||||
icon: mockIcon,
|
icon: mockIcon,
|
||||||
title: "Mac Title",
|
title: 'Mac Title',
|
||||||
description: "Mac Desc",
|
description: 'Mac Desc',
|
||||||
releases: mockReleases,
|
releases: mockReleases,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
expect(result).toContain("Mac Title")
|
expect(result).toContain('Mac Title')
|
||||||
expect(result).toContain("Mac Desc")
|
expect(result).toContain('Mac Desc')
|
||||||
expect(result).toContain("Universal")
|
expect(result).toContain('Universal')
|
||||||
})
|
})
|
||||||
|
|
||||||
it("renders windows platform", async () => {
|
it('renders windows platform', async () => {
|
||||||
const result = await container.renderToString(PlatformDownload, {
|
const result = await container.renderToString(PlatformDownload, {
|
||||||
props: {
|
props: {
|
||||||
platform: "windows",
|
platform: 'windows',
|
||||||
icon: mockIcon,
|
icon: mockIcon,
|
||||||
title: "Win Title",
|
title: 'Win Title',
|
||||||
description: "Win Desc",
|
description: 'Win Desc',
|
||||||
releases: mockReleases,
|
releases: mockReleases,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
expect(result).toContain("Win Title")
|
expect(result).toContain('Win Title')
|
||||||
expect(result).toContain("Win Desc")
|
expect(result).toContain('Win Desc')
|
||||||
expect(result).toContain("x86_64")
|
expect(result).toContain('x86_64')
|
||||||
expect(result).toContain("ARM64")
|
expect(result).toContain('ARM64')
|
||||||
})
|
})
|
||||||
|
|
||||||
it("renders linux platform with flathub and tarball", async () => {
|
it('renders linux platform with flathub and tarball', async () => {
|
||||||
const linuxReleases = {
|
const linuxReleases = {
|
||||||
flathub: { all: { label: "Flathub", link: "/flathub" } },
|
flathub: { all: { label: 'Flathub', link: '/flathub' } },
|
||||||
x86_64: {
|
x86_64: {
|
||||||
tarball: {
|
tarball: {
|
||||||
label: "Tarball x86_64",
|
label: 'Tarball x86_64',
|
||||||
link: "/tarball-x86_64",
|
link: '/tarball-x86_64',
|
||||||
checksum: "sha256",
|
checksum: 'sha256',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
const result = await container.renderToString(PlatformDownload, {
|
const result = await container.renderToString(PlatformDownload, {
|
||||||
props: {
|
props: {
|
||||||
platform: "linux",
|
platform: 'linux',
|
||||||
icon: mockIcon,
|
icon: mockIcon,
|
||||||
title: "Linux Title",
|
title: 'Linux Title',
|
||||||
description: "Linux Desc",
|
description: 'Linux Desc',
|
||||||
releases: linuxReleases,
|
releases: linuxReleases,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
expect(result).toContain("Linux Title")
|
expect(result).toContain('Linux Title')
|
||||||
expect(result).toContain("Linux Desc")
|
expect(result).toContain('Linux Desc')
|
||||||
expect(result).toContain("Flathub")
|
expect(result).toContain('Flathub')
|
||||||
expect(result).toContain("Tarball")
|
expect(result).toContain('Tarball')
|
||||||
expect(result).toContain("x86_64")
|
expect(result).toContain('x86_64')
|
||||||
})
|
})
|
||||||
|
|
||||||
it("renders linux platform with all branches", async () => {
|
it('renders linux platform with all branches', async () => {
|
||||||
const linuxReleases = {
|
const linuxReleases = {
|
||||||
flathub: { all: { label: "Flathub", link: "/flathub" } },
|
flathub: { all: { label: 'Flathub', link: '/flathub' } },
|
||||||
x86_64: {
|
x86_64: {
|
||||||
tarball: {
|
tarball: {
|
||||||
label: "Tarball x86_64",
|
label: 'Tarball x86_64',
|
||||||
link: "/tarball-x86_64",
|
link: '/tarball-x86_64',
|
||||||
checksum: "sha256",
|
checksum: 'sha256',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
aarch64: {
|
aarch64: {
|
||||||
tarball: {
|
tarball: {
|
||||||
label: "Tarball ARM64",
|
label: 'Tarball ARM64',
|
||||||
link: "/tarball-arm64",
|
link: '/tarball-arm64',
|
||||||
checksum: "sha256-arm64",
|
checksum: 'sha256-arm64',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
const result = await container.renderToString(PlatformDownload, {
|
const result = await container.renderToString(PlatformDownload, {
|
||||||
props: {
|
props: {
|
||||||
platform: "linux",
|
platform: 'linux',
|
||||||
icon: mockIcon,
|
icon: mockIcon,
|
||||||
title: "Linux Title",
|
title: 'Linux Title',
|
||||||
description: "Linux Desc",
|
description: 'Linux Desc',
|
||||||
releases: linuxReleases,
|
releases: linuxReleases,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
// Test basic content
|
// Test basic content
|
||||||
expect(result).toContain("Linux Title")
|
expect(result).toContain('Linux Title')
|
||||||
expect(result).toContain("Linux Desc")
|
expect(result).toContain('Linux Desc')
|
||||||
|
|
||||||
// Test Flathub section
|
// Test Flathub section
|
||||||
expect(result).toContain("Flathub")
|
expect(result).toContain('Flathub')
|
||||||
expect(result).toContain("/flathub")
|
expect(result).toContain('/flathub')
|
||||||
|
|
||||||
// Test x86_64 section
|
// Test x86_64 section
|
||||||
expect(result).toContain("x86_64")
|
expect(result).toContain('x86_64')
|
||||||
expect(result).toContain("Tarball x86_64")
|
expect(result).toContain('Tarball x86_64')
|
||||||
expect(result).toContain("/tarball-x86_64")
|
expect(result).toContain('/tarball-x86_64')
|
||||||
expect(result).toContain("sha256")
|
expect(result).toContain('sha256')
|
||||||
|
|
||||||
// Test ARM64 section
|
// Test ARM64 section
|
||||||
expect(result).toContain("ARM64")
|
expect(result).toContain('ARM64')
|
||||||
expect(result).toContain("Tarball ARM64")
|
expect(result).toContain('Tarball ARM64')
|
||||||
expect(result).toContain("/tarball-arm64")
|
expect(result).toContain('/tarball-arm64')
|
||||||
expect(result).toContain("sha256-arm64")
|
expect(result).toContain('sha256-arm64')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -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", () => {
|
describe('getReleasesWithChecksums', () => {
|
||||||
it("returns correct structure with checksums", () => {
|
it('returns correct structure with checksums', () => {
|
||||||
const checksums = {
|
const checksums = {
|
||||||
"zen.macos-universal.dmg": "macsum",
|
'zen.macos-universal.dmg': 'macsum',
|
||||||
"zen.installer.exe": "winsum",
|
'zen.installer.exe': 'winsum',
|
||||||
"zen.installer-arm64.exe": "winarmsum",
|
'zen.installer-arm64.exe': 'winarmsum',
|
||||||
"zen.linux-x86_64.tar.xz": "linx86sum",
|
'zen.linux-x86_64.tar.xz': 'linx86sum',
|
||||||
"zen.linux-aarch64.tar.xz": "linaarchsum",
|
'zen.linux-aarch64.tar.xz': 'linaarchsum',
|
||||||
}
|
}
|
||||||
const releases = getReleasesWithChecksums(checksums)
|
const releases = getReleasesWithChecksums(checksums)
|
||||||
expect(releases.macos.universal.checksum).toBe("macsum")
|
expect(releases.macos.universal.checksum).toBe('macsum')
|
||||||
expect(releases.windows.x86_64.checksum).toBe("winsum")
|
expect(releases.windows.x86_64.checksum).toBe('winsum')
|
||||||
expect(releases.windows.arm64.checksum).toBe("winarmsum")
|
expect(releases.windows.arm64.checksum).toBe('winarmsum')
|
||||||
expect(releases.linux.x86_64.tarball.checksum).toBe("linx86sum")
|
expect(releases.linux.x86_64.tarball.checksum).toBe('linx86sum')
|
||||||
expect(releases.linux.aarch64.tarball.checksum).toBe("linaarchsum")
|
expect(releases.linux.aarch64.tarball.checksum).toBe('linaarchsum')
|
||||||
expect(releases.linux.flathub.all.label).toBe("Flathub")
|
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.linux.flathub.all.link).toBe('https://flathub.org/apps/app.zen_browser.zen')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -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 { getReleasesWithChecksums } from '~/components/download/release-data'
|
||||||
import { CONSTANT } from "~/constants"
|
import { CONSTANT } from '~/constants'
|
||||||
|
|
||||||
// Helper to get the platform section by id
|
// Helper to get the platform section by id
|
||||||
const getPlatformSection = (page: Page, platform: string) =>
|
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 }[] = [
|
const platformConfigs: { name: string; userAgent: string; platform: string }[] = [
|
||||||
{
|
{
|
||||||
name: "windows",
|
name: 'windows',
|
||||||
userAgent:
|
userAgent:
|
||||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
|
'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",
|
platform: 'Win32',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "mac",
|
name: 'mac',
|
||||||
userAgent:
|
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",
|
'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",
|
platform: 'MacIntel',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "linux",
|
name: 'linux',
|
||||||
userAgent:
|
userAgent:
|
||||||
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
|
'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",
|
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) {
|
for (const { name, userAgent, platform } of platformConfigs) {
|
||||||
test(`shows correct default tab for ${name} platform`, async ({ browser }) => {
|
test(`shows correct default tab for ${name} platform`, async ({ browser }) => {
|
||||||
const context = await browser.newContext({
|
const context = await browser.newContext({
|
||||||
userAgent,
|
userAgent,
|
||||||
locale: "en-US",
|
locale: 'en-US',
|
||||||
platform,
|
platform,
|
||||||
} as BrowserContextOptions)
|
} as BrowserContextOptions)
|
||||||
const page = await context.newPage()
|
const page = await context.newPage()
|
||||||
await page.goto("/download")
|
await page.goto('/download')
|
||||||
await expect(getPlatformSection(page, name)).toBeVisible()
|
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
|
// Other platforms should not be active
|
||||||
for (const other of platformConfigs.filter(p => p.name !== name)) {
|
for (const other of platformConfigs.filter(p => p.name !== name)) {
|
||||||
await expect(getPlatformSection(page, other.name)).toBeHidden()
|
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()
|
await context.close()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
test.describe("Download page platform detection and tab switching", () => {
|
test.describe('Download page platform detection and tab switching', () => {
|
||||||
test("shows correct platform section and tab when switching platforms", async ({ page }) => {
|
test('shows correct platform section and tab when switching platforms', async ({ page }) => {
|
||||||
await page.goto("/download")
|
await page.goto('/download')
|
||||||
const platforms = ["windows", "mac", "linux"]
|
const platforms = ['windows', 'mac', 'linux']
|
||||||
for (const platform of platforms) {
|
for (const platform of platforms) {
|
||||||
await getPlatformButton(page, platform).click()
|
await getPlatformButton(page, platform).click()
|
||||||
await expect(getPlatformSection(page, platform)).toBeVisible()
|
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
|
// other platform sections should be hidden
|
||||||
for (const otherPlatform of platforms.filter(p => p !== platform)) {
|
for (const otherPlatform of platforms.filter(p => p !== platform)) {
|
||||||
await expect(getPlatformSection(page, otherPlatform)).toBeHidden()
|
await expect(getPlatformSection(page, otherPlatform)).toBeHidden()
|
||||||
await expect(getPlatformButton(page, otherPlatform)).not.toHaveAttribute(
|
await expect(getPlatformButton(page, otherPlatform)).not.toHaveAttribute(
|
||||||
"data-active",
|
'data-active',
|
||||||
"true"
|
'true'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
test.describe("Download page download links", () => {
|
test.describe('Download page download links', () => {
|
||||||
const releases = getReleasesWithChecksums(CONSTANT.CHECKSUMS)
|
const releases = getReleasesWithChecksums(CONSTANT.CHECKSUMS)
|
||||||
|
|
||||||
function getPlatformLinks(releases: ReturnType<typeof getReleasesWithChecksums>) {
|
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 }) => {
|
test('all platform download links are correct', async ({ page }) => {
|
||||||
const platforms = ["windows", "mac", "linux"]
|
const platforms = ['windows', 'mac', 'linux']
|
||||||
const platformLinkSelectors = getPlatformLinks(releases)
|
const platformLinkSelectors = getPlatformLinks(releases)
|
||||||
await page.goto("/download")
|
await page.goto('/download')
|
||||||
await page.waitForLoadState("domcontentloaded")
|
await page.waitForLoadState('domcontentloaded')
|
||||||
for (const platform of platforms) {
|
for (const platform of platforms) {
|
||||||
await getPlatformButton(page, platform).click()
|
await getPlatformButton(page, platform).click()
|
||||||
for (const { label, link } of platformLinkSelectors[
|
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}"]`)
|
const downloadLink = page.locator(`#${platform}-downloads .download-link[href="${link}"]`)
|
||||||
await expect(downloadLink).toContainText(label)
|
await expect(downloadLink).toContainText(label)
|
||||||
await expect(downloadLink).toHaveAttribute("href", link)
|
await expect(downloadLink).toHaveAttribute('href', link)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
@ -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 }) => {
|
test('clicking back button navigates to previous page', async ({ page }) => {
|
||||||
await page.goto("/mods?created=asc")
|
await page.goto('/mods?created=asc')
|
||||||
const currentUrl = page.url()
|
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 modCards[0].click()
|
||||||
await page.getByRole("button", { name: "Back" }).click()
|
await page.getByRole('button', { name: 'Back' }).click()
|
||||||
await page.waitForURL(currentUrl)
|
await page.waitForURL(currentUrl)
|
||||||
expect(page.url()).toStrictEqual(currentUrl)
|
expect(page.url()).toStrictEqual(currentUrl)
|
||||||
})
|
})
|
||||||
|
|
|
@ -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 }) => {
|
test('all routes do not return 404', async ({ page }) => {
|
||||||
const routes = ["/", "/welcome", "/about", "/privacy-policy", "/download", "/donate", "/whatsnew"]
|
const routes = ['/', '/welcome', '/about', '/privacy-policy', '/download', '/donate', '/whatsnew']
|
||||||
for (const route of routes) {
|
for (const route of routes) {
|
||||||
const response = await page.goto(route)
|
const response = await page.goto(route)
|
||||||
expect(response?.status()).not.toBe(404)
|
expect(response?.status()).not.toBe(404)
|
||||||
|
|
|
@ -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", () => ({
|
vi.mock('~/utils/i18n', () => ({
|
||||||
getLocale: () => "en",
|
getLocale: () => 'en',
|
||||||
getPath: () => (href: string) => `/en${href}`,
|
getPath: () => (href: string) => `/en${href}`,
|
||||||
getUI: () => translation,
|
getUI: () => translation,
|
||||||
}))
|
}))
|
||||||
|
|
|
@ -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.
|
* 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) {
|
if (import.meta.env.DEV) {
|
||||||
return CONSTANT.CHECKSUMS
|
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: {
|
headers: {
|
||||||
Accept: "application/vnd.github+json",
|
Accept: 'application/vnd.github+json',
|
||||||
"X-GitHub-Api-Version": "2022-11-28",
|
'X-GitHub-Api-Version': '2022-11-28',
|
||||||
"User-Agent": "zen-browser-checksum-fetcher",
|
'User-Agent': 'zen-browser-checksum-fetcher',
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
if (!res.ok) throw new Error(`Failed to fetch GitHub release: ${res.statusText}`)
|
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 match = body.match(/File Checksums \(SHA-256\)[\s\S]*?```([\s\S]*?)```/)
|
||||||
const checksums: Record<string, string> = {}
|
const checksums: Record<string, string> = {}
|
||||||
if (match?.[1]) {
|
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)
|
const [hash, filename] = line.trim().split(/\s+/, 2)
|
||||||
if (hash && filename) checksums[filename] = hash
|
if (hash && filename) checksums[filename] = hash
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { type GetStaticPaths } from "astro"
|
import { type GetStaticPaths } from 'astro'
|
||||||
|
|
||||||
import { CONSTANT } from "~/constants"
|
import { CONSTANT } from '~/constants'
|
||||||
import UI_EN from "~/i18n/en/translation.json"
|
import UI_EN from '~/i18n/en/translation.json'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents the available locales in the application
|
* Represents the available locales in the application
|
||||||
|
@ -16,7 +16,7 @@ export type Locale = (typeof locales)[number]
|
||||||
*/
|
*/
|
||||||
export const getPath = (locale?: Locale) => (path: string) => {
|
export const getPath = (locale?: Locale) => (path: string) => {
|
||||||
if (locale && locale !== CONSTANT.I18N.DEFAULT_LOCALE && !path.startsWith(`/${locale}`)) {
|
if (locale && locale !== CONSTANT.I18N.DEFAULT_LOCALE && !path.startsWith(`/${locale}`)) {
|
||||||
return `/${locale}${path.startsWith("/") ? "" : "/"}${path}`
|
return `/${locale}${path.startsWith('/') ? '' : '/'}${path}`
|
||||||
}
|
}
|
||||||
return 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 {
|
function deepMerge<T extends object>(defaultObj: T, overrideObj: Partial<T>): T {
|
||||||
// Handle non-object cases
|
// Handle non-object cases
|
||||||
if (typeof defaultObj !== "object" || defaultObj === null) {
|
if (typeof defaultObj !== 'object' || defaultObj === null) {
|
||||||
return (overrideObj ?? defaultObj) as T
|
return (overrideObj ?? defaultObj) as T
|
||||||
}
|
}
|
||||||
if (typeof overrideObj !== "object" || overrideObj === null) {
|
if (typeof overrideObj !== 'object' || overrideObj === null) {
|
||||||
return (overrideObj ?? defaultObj) as T
|
return (overrideObj ?? defaultObj) as T
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -105,8 +105,8 @@ export const getUI = (locale?: Locale | string): UI => {
|
||||||
if (
|
if (
|
||||||
defaultValue !== null &&
|
defaultValue !== null &&
|
||||||
overrideValue !== null &&
|
overrideValue !== null &&
|
||||||
typeof defaultValue === "object" &&
|
typeof defaultValue === 'object' &&
|
||||||
typeof overrideValue === "object"
|
typeof overrideValue === 'object'
|
||||||
) {
|
) {
|
||||||
// Type assertion to handle nested merging
|
// Type assertion to handle nested merging
|
||||||
;(result as Record<keyof T, unknown>)[key] = deepMerge(
|
;(result as Record<keyof T, unknown>)[key] = deepMerge(
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { type ClassValue, clsx } from "clsx"
|
import { type ClassValue, clsx } from 'clsx'
|
||||||
import { twMerge } from "tailwind-merge"
|
import { twMerge } from 'tailwind-merge'
|
||||||
|
|
||||||
export const cn = (...inputs: ClassValue[]) => {
|
export const cn = (...inputs: ClassValue[]) => {
|
||||||
return twMerge(clsx(inputs))
|
return twMerge(clsx(inputs))
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue