mirror of
https://github.com/hyprwm/hyprland-website.git
synced 2024-12-22 10:19:49 +01:00
Add Rss feed (#42)
* darken inline code bg in articles * improve install button * add rss feed * small cleanup * news: single row entries * news: fancy spinner * news: single col * news: fancy spinner
This commit is contained in:
parent
f414e369bb
commit
821d96b466
9 changed files with 134 additions and 35 deletions
|
@ -5,6 +5,7 @@
|
||||||
<link rel="icon" href="%sveltekit.assets%/favicon.ico" />
|
<link rel="icon" href="%sveltekit.assets%/favicon.ico" />
|
||||||
<meta name="viewport" content="width=device-width" />
|
<meta name="viewport" content="width=device-width" />
|
||||||
<meta property="og:image" content="/imgs/og-img.png" />
|
<meta property="og:image" content="/imgs/og-img.png" />
|
||||||
|
<link rel="alternate" type="application/rss+xml" title="Hyprland News" href="/rss" />
|
||||||
<style></style>
|
<style></style>
|
||||||
%sveltekit.head%
|
%sveltekit.head%
|
||||||
</head>
|
</head>
|
||||||
|
|
|
@ -46,11 +46,12 @@ export function animateIn(node, options = {}) {
|
||||||
|
|
||||||
function callback() {
|
function callback() {
|
||||||
timeoutId = setTimeout(
|
timeoutId = setTimeout(
|
||||||
effects.forEach(([effect]) => {
|
() =>
|
||||||
if (effect === 'slide') node.style.removeProperty('translate')
|
effects.forEach(([effect]) => {
|
||||||
else if (effect === 'fade') node.style.removeProperty('opacity')
|
if (effect === 'slide') node.style.removeProperty('translate')
|
||||||
else if (effect === 'zoom') node.style.removeProperty('scale')
|
else if (effect === 'fade') node.style.removeProperty('opacity')
|
||||||
}),
|
else if (effect === 'zoom') node.style.removeProperty('scale')
|
||||||
|
}),
|
||||||
options.delay ?? 0
|
options.delay ?? 0
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
import DiscordIcon from '~icons/prime/discord'
|
import DiscordIcon from '~icons/prime/discord'
|
||||||
import GithubIcon from '~icons/ri/github-fill'
|
import GithubIcon from '~icons/ri/github-fill'
|
||||||
import { discordLink } from '$lib/constants.mjs'
|
import { discordLink } from '$lib/constants.mjs'
|
||||||
|
import RssIcon from '~icons/mingcute/rss-fill'
|
||||||
|
|
||||||
/** @type {[string, string, string, string]} */
|
/** @type {[string, string, string, string]} */
|
||||||
let team = [
|
let team = [
|
||||||
|
@ -75,13 +76,21 @@
|
||||||
aria-label="Go to our Github"><GithubIcon class="h-12 w-12 " /></a
|
aria-label="Go to our Github"><GithubIcon class="h-12 w-12 " /></a
|
||||||
>
|
>
|
||||||
</li>
|
</li>
|
||||||
|
<li class="">
|
||||||
|
<a
|
||||||
|
href="/rss"
|
||||||
|
class="text-slate-400 hover:text-slate-200"
|
||||||
|
target="_blank"
|
||||||
|
aria-label="Rss Feed"><RssIcon class="h-12 w-12 " /></a
|
||||||
|
>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex w-full flex-wrap gap-4 text-sm font-medium text-slate-400">
|
<div class="flex w-full flex-wrap gap-4 text-sm font-medium text-slate-400">
|
||||||
<p>Hyprland is licensed under the BSD 3-Clause "New" or "Revised" License.</p>
|
<p>Hyprland is licensed under the BSD 3-Clause "New" or "Revised" License.</p>
|
||||||
<p>© Hyprland Development {new Date().getFullYear()}.</p>
|
<p>© Hyprland Development {new Date().getFullYear()}.</p>
|
||||||
<p>Doki doki waku waku.</p>
|
<p>Stay hydrated</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<li class="flex gap-14" use:animateIn={{ fade: 0, slide: 24 }}>
|
<li class="flex gap-14" use:animateIn={{ fade: 0, slide: 24 }}>
|
||||||
<div class="flex flex-col gap-4 rounded">
|
<article class="flex flex-col gap-4 rounded">
|
||||||
<div class="flex flex-col gap-4 text-sm font-medium text-slate-400">
|
<div class="flex flex-col gap-4 text-sm font-medium text-slate-400">
|
||||||
<p class="font-bold text-slate-400">{formatDate(entry.date)}</p>
|
<p class="font-bold text-slate-400">{formatDate(entry.date)}</p>
|
||||||
</div>
|
</div>
|
||||||
|
@ -18,7 +18,7 @@
|
||||||
<a
|
<a
|
||||||
href={link}
|
href={link}
|
||||||
class="group flex max-w-max items-center gap-4 font-medium text-slate-300 transition-all hover:text-white"
|
class="group flex max-w-max items-center gap-4 font-medium text-slate-300 transition-all hover:text-white"
|
||||||
>Read up<ArrowRight class="transition-transform group-hover:translate-x-0.5" /></a
|
>Read up <ArrowRight class="transition-transform group-hover:translate-x-0.5" /></a
|
||||||
>
|
>
|
||||||
</div>
|
</article>
|
||||||
</li>
|
</li>
|
||||||
|
|
BIN
src/lib/images/logos/HyprlandLogo.png
Normal file
BIN
src/lib/images/logos/HyprlandLogo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 32 KiB |
|
@ -27,12 +27,7 @@
|
||||||
<div
|
<div
|
||||||
class="relative flex h-32 w-32 flex-col items-center justify-center gap-3 rounded-full text-lg font-medium text-primary transition-transform group-focus-within:-translate-y-1"
|
class="relative flex h-32 w-32 flex-col items-center justify-center gap-3 rounded-full text-lg font-medium text-primary transition-transform group-focus-within:-translate-y-1"
|
||||||
>
|
>
|
||||||
<img
|
<img src={image} class="h-20 w-32 object-contain" alt="{name} Logo" loading="lazy" />{name}
|
||||||
src={image}
|
|
||||||
class="h-20 w-32 object-contain"
|
|
||||||
alt="Distrubution Logo"
|
|
||||||
loading="lazy"
|
|
||||||
/>{name}
|
|
||||||
<slot name="imageExtra" />
|
<slot name="imageExtra" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -48,7 +43,7 @@
|
||||||
<span>{command}</span>
|
<span>{command}</span>
|
||||||
</div>
|
</div>
|
||||||
<ClipboardIcon
|
<ClipboardIcon
|
||||||
class="h-6 w-6 text-white opacity-0 transition-opacity duration-100 hover:!opacity-100 group-hover:opacity-80 group-active:opacity-100"
|
class="hidden h-6 w-6 text-white opacity-0 transition-opacity duration-100 hover:!opacity-100 group-hover:opacity-80 group-active:opacity-100 sm:block"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</slot>
|
</slot>
|
||||||
|
|
|
@ -1,18 +1,64 @@
|
||||||
<script>
|
<script>
|
||||||
|
import LogoPng from '$lib/images/logos/HyprlandLogo.png'
|
||||||
import Title from '$lib/components/Title.svelte'
|
import Title from '$lib/components/Title.svelte'
|
||||||
import NewsThumb from '$lib/components/news-thumb.svelte'
|
import NewsThumb from '$lib/components/news-thumb.svelte'
|
||||||
|
import { onDestroy, onMount } from 'svelte'
|
||||||
import { onMount } from 'svelte'
|
|
||||||
|
|
||||||
export let data
|
export let data
|
||||||
|
|
||||||
const posts = data.posts
|
/** @type {HTMLElement} */
|
||||||
|
let asciiElement
|
||||||
|
|
||||||
const latest = posts.at(0)
|
const { posts } = data
|
||||||
const others = posts.slice(1)
|
|
||||||
|
|
||||||
onMount(() => {
|
let interval
|
||||||
console.log({ data })
|
let objectUrl
|
||||||
|
|
||||||
|
// Taken from https://github.com/NotAShelf/hyprascii/blob/main/web/script.js
|
||||||
|
onMount(async () => {
|
||||||
|
const logoBlob = await fetch(LogoPng).then((response) => response.blob())
|
||||||
|
objectUrl = URL.createObjectURL(logoBlob)
|
||||||
|
const img = document.createElement('img')
|
||||||
|
img.src = objectUrl
|
||||||
|
|
||||||
|
const cvs = document.createElement('canvas')
|
||||||
|
cvs.width = 72
|
||||||
|
cvs.height = 36
|
||||||
|
const ctx = cvs.getContext('2d', { willReadFrequently: true })
|
||||||
|
|
||||||
|
const getLuminance = (r, g, b) => {
|
||||||
|
return Math.sqrt(0.299 * r * r + 0.587 * g * g + 0.114 * b * b)
|
||||||
|
}
|
||||||
|
ctx.fillStyle = '#000000'
|
||||||
|
|
||||||
|
interval = setInterval(() => {
|
||||||
|
ctx.fillRect(0, 0, cvs.width, cvs.height)
|
||||||
|
|
||||||
|
const t = new Date().getTime() * 0.0007
|
||||||
|
let px = ((-Math.cos(t) + 1) / 2) * cvs.width
|
||||||
|
let sx = Math.cos(t) * cvs.width
|
||||||
|
|
||||||
|
ctx.drawImage(img, px, 0, sx, cvs.height)
|
||||||
|
|
||||||
|
const idata = ctx.getImageData(0, 0, cvs.width, cvs.height)
|
||||||
|
const pixels = idata.data
|
||||||
|
const text = []
|
||||||
|
const chars = ' .-=+'
|
||||||
|
for (let y = 0; y < cvs.height; y++) {
|
||||||
|
for (let x = 0; x < cvs.width; x++) {
|
||||||
|
const idx = 4 * ((sx < 0 ? cvs.width - x - 1 : x) + cvs.width * y)
|
||||||
|
const br = getLuminance(pixels[idx] / 256, pixels[idx + 1] / 256, pixels[idx + 2] / 256)
|
||||||
|
text.push(chars[Math.floor(br * chars.length)])
|
||||||
|
}
|
||||||
|
text.push('\n')
|
||||||
|
}
|
||||||
|
asciiElement.innerText = text.join('')
|
||||||
|
}, 40)
|
||||||
|
})
|
||||||
|
|
||||||
|
onDestroy(() => {
|
||||||
|
clearInterval(interval)
|
||||||
|
URL.revokeObjectURL(objectUrl)
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -22,7 +68,9 @@
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
<header class="header">
|
<header class="header">
|
||||||
<Title class="mb-8">
|
<pre class="spinner-wrapper" bind:this={asciiElement} />
|
||||||
|
|
||||||
|
<Title class="mb-0 duration-1000 animate-in fade-in-0">
|
||||||
<span slot="title">News</span><span slot="subtitle">
|
<span slot="title">News</span><span slot="subtitle">
|
||||||
Fresh updates straight from the oven
|
Fresh updates straight from the oven
|
||||||
</span>
|
</span>
|
||||||
|
@ -30,12 +78,9 @@
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<ul
|
<ul
|
||||||
class="row-auto grid grid-cols-1 gap-14 delay-500 duration-1000 animate-in fade-in-0 slide-in-from-bottom-6 fill-mode-backwards lg:grid-cols-2"
|
class="row-auto flex flex-col gap-14 animate-in fade-in-0 slide-in-from-bottom-6 fill-mode-backwards [animation-delay:800ms] [animation-duration:1500ms]"
|
||||||
>
|
>
|
||||||
<div class="col-span-full flex lg:justify-center">
|
{#each posts as entry}
|
||||||
<NewsThumb entry={latest} />
|
|
||||||
</div>
|
|
||||||
{#each others as entry}
|
|
||||||
<NewsThumb {entry} />
|
<NewsThumb {entry} />
|
||||||
{/each}
|
{/each}
|
||||||
</ul>
|
</ul>
|
||||||
|
@ -57,6 +102,14 @@
|
||||||
min-height: 500px;
|
min-height: 500px;
|
||||||
max-height: 900px;
|
max-height: 900px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin-bottom: min(-10vh, -6rem);
|
margin-bottom: 8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spinner-wrapper {
|
||||||
|
@apply mb-4 flex items-center justify-center bg-gradient-to-tr from-blue-500/0 to-cyan-500 bg-clip-text text-transparent animate-in fade-in-0;
|
||||||
|
animation-duration: 2000ms;
|
||||||
|
font-size: min(1vh, 1rem);
|
||||||
|
/* There are 36 rows */
|
||||||
|
height: min(36vh, 36rem);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
44
src/routes/rss/+server.js
Normal file
44
src/routes/rss/+server.js
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
const siteURL = 'https://hyprland.org'
|
||||||
|
const siteTitle = 'Hyprland'
|
||||||
|
const siteDescription = 'Tiling window manager with the looks'
|
||||||
|
|
||||||
|
export const prerender = true
|
||||||
|
|
||||||
|
export const GET = async ({ fetch }) => {
|
||||||
|
const allNews = await fetch('api/news')
|
||||||
|
.then((response) => response.json())
|
||||||
|
.then((news) => news.sort((a, b) => new Date(b.date) - new Date(a.date)))
|
||||||
|
|
||||||
|
const body = renderXml(allNews)
|
||||||
|
const options = {
|
||||||
|
headers: {
|
||||||
|
'Cache-Control': 'max-age=0, s-maxage=3600',
|
||||||
|
'Content-Type': 'application/xml'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Response(body, options)
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderXml(posts) {
|
||||||
|
return `<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
|
||||||
|
<channel>
|
||||||
|
<atom:link href="${siteURL}/rss" rel="self" type="application/rss+xml" />
|
||||||
|
<title>${siteTitle} News</title>
|
||||||
|
<link>${siteURL}/news</link>
|
||||||
|
<description>${siteDescription}</description>
|
||||||
|
${posts
|
||||||
|
.map(
|
||||||
|
(post) => `<item>
|
||||||
|
<guid isPermaLink="true">${siteURL}/news/${post.slug}</guid>
|
||||||
|
<title>${post.title}</title>
|
||||||
|
<link>${siteURL}/news/${post.slug}</link>
|
||||||
|
<pubDate>${new Date(post.date).toUTCString()}</pubDate>
|
||||||
|
</item>`
|
||||||
|
)
|
||||||
|
.join(' ')}
|
||||||
|
</channel>
|
||||||
|
</rss>
|
||||||
|
`
|
||||||
|
}
|
|
@ -1,10 +1,6 @@
|
||||||
const { fontFamily } = require('tailwindcss/defaultTheme')
|
const { fontFamily } = require('tailwindcss/defaultTheme')
|
||||||
const colors = require('tailwindcss/colors')
|
const colors = require('tailwindcss/colors')
|
||||||
|
|
||||||
console.log({ colors })
|
|
||||||
|
|
||||||
// const sansFamily =
|
|
||||||
|
|
||||||
/** @type {import('tailwindcss').Config} */
|
/** @type {import('tailwindcss').Config} */
|
||||||
export default {
|
export default {
|
||||||
content: ['./src/**/**/*.{html,js,svelte,ts}'],
|
content: ['./src/**/**/*.{html,js,svelte,ts}'],
|
||||||
|
@ -26,7 +22,7 @@ export default {
|
||||||
css: {
|
css: {
|
||||||
code: {
|
code: {
|
||||||
padding: '0.2em 0.4em',
|
padding: '0.2em 0.4em',
|
||||||
'background-color': colors.slate[700],
|
'background-color': colors.slate[800],
|
||||||
'border-radius': '6px',
|
'border-radius': '6px',
|
||||||
'font-weight': 'inherit'
|
'font-weight': 'inherit'
|
||||||
},
|
},
|
||||||
|
|
Loading…
Reference in a new issue