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" />
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
<meta property="og:image" content="/imgs/og-img.png" />
|
||||
<link rel="alternate" type="application/rss+xml" title="Hyprland News" href="/rss" />
|
||||
<style></style>
|
||||
%sveltekit.head%
|
||||
</head>
|
||||
|
|
|
@ -46,6 +46,7 @@ export function animateIn(node, options = {}) {
|
|||
|
||||
function callback() {
|
||||
timeoutId = setTimeout(
|
||||
() =>
|
||||
effects.forEach(([effect]) => {
|
||||
if (effect === 'slide') node.style.removeProperty('translate')
|
||||
else if (effect === 'fade') node.style.removeProperty('opacity')
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
import DiscordIcon from '~icons/prime/discord'
|
||||
import GithubIcon from '~icons/ri/github-fill'
|
||||
import { discordLink } from '$lib/constants.mjs'
|
||||
import RssIcon from '~icons/mingcute/rss-fill'
|
||||
|
||||
/** @type {[string, string, string, string]} */
|
||||
let team = [
|
||||
|
@ -75,13 +76,21 @@
|
|||
aria-label="Go to our Github"><GithubIcon class="h-12 w-12 " /></a
|
||||
>
|
||||
</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>
|
||||
</div>
|
||||
|
||||
<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 Development {new Date().getFullYear()}.</p>
|
||||
<p>Doki doki waku waku.</p>
|
||||
<p>Stay hydrated</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
</script>
|
||||
|
||||
<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">
|
||||
<p class="font-bold text-slate-400">{formatDate(entry.date)}</p>
|
||||
</div>
|
||||
|
@ -20,5 +20,5 @@
|
|||
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
|
||||
>
|
||||
</div>
|
||||
</article>
|
||||
</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
|
||||
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
|
||||
src={image}
|
||||
class="h-20 w-32 object-contain"
|
||||
alt="Distrubution Logo"
|
||||
loading="lazy"
|
||||
/>{name}
|
||||
<img src={image} class="h-20 w-32 object-contain" alt="{name} Logo" loading="lazy" />{name}
|
||||
<slot name="imageExtra" />
|
||||
</div>
|
||||
|
||||
|
@ -48,7 +43,7 @@
|
|||
<span>{command}</span>
|
||||
</div>
|
||||
<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>
|
||||
</slot>
|
||||
|
|
|
@ -1,18 +1,64 @@
|
|||
<script>
|
||||
import LogoPng from '$lib/images/logos/HyprlandLogo.png'
|
||||
import Title from '$lib/components/Title.svelte'
|
||||
import NewsThumb from '$lib/components/news-thumb.svelte'
|
||||
|
||||
import { onMount } from 'svelte'
|
||||
import { onDestroy, onMount } from 'svelte'
|
||||
|
||||
export let data
|
||||
|
||||
const posts = data.posts
|
||||
/** @type {HTMLElement} */
|
||||
let asciiElement
|
||||
|
||||
const latest = posts.at(0)
|
||||
const others = posts.slice(1)
|
||||
const { posts } = data
|
||||
|
||||
onMount(() => {
|
||||
console.log({ data })
|
||||
let interval
|
||||
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>
|
||||
|
||||
|
@ -22,7 +68,9 @@
|
|||
|
||||
<section>
|
||||
<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">
|
||||
Fresh updates straight from the oven
|
||||
</span>
|
||||
|
@ -30,12 +78,9 @@
|
|||
</header>
|
||||
|
||||
<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">
|
||||
<NewsThumb entry={latest} />
|
||||
</div>
|
||||
{#each others as entry}
|
||||
{#each posts as entry}
|
||||
<NewsThumb {entry} />
|
||||
{/each}
|
||||
</ul>
|
||||
|
@ -57,6 +102,14 @@
|
|||
min-height: 500px;
|
||||
max-height: 900px;
|
||||
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>
|
||||
|
|
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 colors = require('tailwindcss/colors')
|
||||
|
||||
console.log({ colors })
|
||||
|
||||
// const sansFamily =
|
||||
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
export default {
|
||||
content: ['./src/**/**/*.{html,js,svelte,ts}'],
|
||||
|
@ -26,7 +22,7 @@ export default {
|
|||
css: {
|
||||
code: {
|
||||
padding: '0.2em 0.4em',
|
||||
'background-color': colors.slate[700],
|
||||
'background-color': colors.slate[800],
|
||||
'border-radius': '6px',
|
||||
'font-weight': 'inherit'
|
||||
},
|
||||
|
|
Loading…
Reference in a new issue