feat: add blogs support

This commit is contained in:
Kingkor Roy Tirtho 2024-02-10 00:59:59 +06:00
parent eaf2d19541
commit 842f602b64
24 changed files with 1607 additions and 274 deletions

248
package-lock.json generated
View File

@ -1,248 +0,0 @@
{
"name": "spotube",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"dependencies": {
"svelte-media-queries": "^1.6.2",
"svelte-persisted-store": "^0.9.1"
}
},
"node_modules/@ampproject/remapping": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz",
"integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==",
"peer": true,
"dependencies": {
"@jridgewell/gen-mapping": "^0.3.0",
"@jridgewell/trace-mapping": "^0.3.9"
},
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/@jridgewell/gen-mapping": {
"version": "0.3.3",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz",
"integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==",
"peer": true,
"dependencies": {
"@jridgewell/set-array": "^1.0.1",
"@jridgewell/sourcemap-codec": "^1.4.10",
"@jridgewell/trace-mapping": "^0.3.9"
},
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/@jridgewell/resolve-uri": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz",
"integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==",
"peer": true,
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/@jridgewell/set-array": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz",
"integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==",
"peer": true,
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/@jridgewell/sourcemap-codec": {
"version": "1.4.15",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz",
"integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==",
"peer": true
},
"node_modules/@jridgewell/trace-mapping": {
"version": "0.3.22",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.22.tgz",
"integrity": "sha512-Wf963MzWtA2sjrNt+g18IAln9lKnlRp+K2eH4jjIoF1wYeq3aMREpG09xhlhdzS0EjwU7qmUJYangWa+151vZw==",
"peer": true,
"dependencies": {
"@jridgewell/resolve-uri": "^3.1.0",
"@jridgewell/sourcemap-codec": "^1.4.14"
}
},
"node_modules/@types/estree": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz",
"integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==",
"peer": true
},
"node_modules/acorn": {
"version": "8.11.3",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz",
"integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==",
"peer": true,
"bin": {
"acorn": "bin/acorn"
},
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/aria-query": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz",
"integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==",
"peer": true,
"dependencies": {
"dequal": "^2.0.3"
}
},
"node_modules/axobject-query": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.0.0.tgz",
"integrity": "sha512-+60uv1hiVFhHZeO+Lz0RYzsVHy5Wr1ayX0mwda9KPDVLNJgZ1T9Ny7VmFbLDzxsH0D87I86vgj3gFrjTJUYznw==",
"peer": true,
"dependencies": {
"dequal": "^2.0.3"
}
},
"node_modules/code-red": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/code-red/-/code-red-1.0.4.tgz",
"integrity": "sha512-7qJWqItLA8/VPVlKJlFXU+NBlo/qyfs39aJcuMT/2ere32ZqvF5OSxgdM5xOfJJ7O429gg2HM47y8v9P+9wrNw==",
"peer": true,
"dependencies": {
"@jridgewell/sourcemap-codec": "^1.4.15",
"@types/estree": "^1.0.1",
"acorn": "^8.10.0",
"estree-walker": "^3.0.3",
"periscopic": "^3.1.0"
}
},
"node_modules/css-tree": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz",
"integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==",
"peer": true,
"dependencies": {
"mdn-data": "2.0.30",
"source-map-js": "^1.0.1"
},
"engines": {
"node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0"
}
},
"node_modules/dequal": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
"integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==",
"peer": true,
"engines": {
"node": ">=6"
}
},
"node_modules/estree-walker": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz",
"integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==",
"peer": true,
"dependencies": {
"@types/estree": "^1.0.0"
}
},
"node_modules/is-reference": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.2.tgz",
"integrity": "sha512-v3rht/LgVcsdZa3O2Nqs+NMowLOxeOm7Ay9+/ARQ2F+qEoANRcqrjAZKGN0v8ymUetZGgkp26LTnGT7H0Qo9Pg==",
"peer": true,
"dependencies": {
"@types/estree": "*"
}
},
"node_modules/locate-character": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz",
"integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==",
"peer": true
},
"node_modules/magic-string": {
"version": "0.30.7",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.7.tgz",
"integrity": "sha512-8vBuFF/I/+OSLRmdf2wwFCJCz+nSn0m6DPvGH1fS/KiQoSaR+sETbov0eIk9KhEKy8CYqIkIAnbohxT/4H0kuA==",
"peer": true,
"dependencies": {
"@jridgewell/sourcemap-codec": "^1.4.15"
},
"engines": {
"node": ">=12"
}
},
"node_modules/mdn-data": {
"version": "2.0.30",
"resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz",
"integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==",
"peer": true
},
"node_modules/periscopic": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/periscopic/-/periscopic-3.1.0.tgz",
"integrity": "sha512-vKiQ8RRtkl9P+r/+oefh25C3fhybptkHKCZSPlcXiJux2tJF55GnEj3BVn4A5gKfq9NWWXXrxkHBwVPUfH0opw==",
"peer": true,
"dependencies": {
"@types/estree": "^1.0.0",
"estree-walker": "^3.0.0",
"is-reference": "^3.0.0"
}
},
"node_modules/source-map-js": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
"integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==",
"peer": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/svelte": {
"version": "4.2.10",
"resolved": "https://registry.npmjs.org/svelte/-/svelte-4.2.10.tgz",
"integrity": "sha512-Ep06yCaCdgG1Mafb/Rx8sJ1QS3RW2I2BxGp2Ui9LBHSZ2/tO/aGLc5WqPjgiAP6KAnLJGaIr/zzwQlOo1b8MxA==",
"peer": true,
"dependencies": {
"@ampproject/remapping": "^2.2.1",
"@jridgewell/sourcemap-codec": "^1.4.15",
"@jridgewell/trace-mapping": "^0.3.18",
"@types/estree": "^1.0.1",
"acorn": "^8.9.0",
"aria-query": "^5.3.0",
"axobject-query": "^4.0.0",
"code-red": "^1.0.3",
"css-tree": "^2.3.1",
"estree-walker": "^3.0.3",
"is-reference": "^3.0.1",
"locate-character": "^3.0.0",
"magic-string": "^0.30.4",
"periscopic": "^3.1.0"
},
"engines": {
"node": ">=16"
}
},
"node_modules/svelte-media-queries": {
"version": "1.6.2",
"resolved": "https://registry.npmjs.org/svelte-media-queries/-/svelte-media-queries-1.6.2.tgz",
"integrity": "sha512-SMz6od/vIeZEGlc4P0HKJK4G0fZotuwFhCSpBQaPqh75h6sL6sNf+4+IjbegFKXbP7b+SOfyzVOIMXTr8jynkA=="
},
"node_modules/svelte-persisted-store": {
"version": "0.9.1",
"resolved": "https://registry.npmjs.org/svelte-persisted-store/-/svelte-persisted-store-0.9.1.tgz",
"integrity": "sha512-l00I8Dy5GKjdjnE9ZcMeXLLMhvgV0+Iuru0Mue7eU3tB+pHBwBB2RVVqw2uC2Hbrf7cyZtsV/lnPKhjTHIWphQ==",
"engines": {
"node": ">=0.14"
},
"peerDependencies": {
"svelte": "^3.48.0 || ^4.0.0 || ^5.0.0-next.0"
}
}
}
}

View File

@ -1,6 +0,0 @@
{
"dependencies": {
"svelte-media-queries": "^1.6.2",
"svelte-persisted-store": "^0.9.1"
}
}

1372
website/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -49,9 +49,15 @@
"date-fns": "^3.3.1",
"highlight.js": "11.9.0",
"lucide-svelte": "^0.323.0",
"mdsvex-relative-images": "^1.0.3",
"rehype-autolink-headings": "^7.1.0",
"rehype-slug": "^6.0.0",
"remark-container": "^0.1.2",
"remark-external-links": "^9.0.1",
"remark-gfm": "^4.0.0",
"remark-github": "^12.0.0",
"remark-reading-time": "^2.0.1",
"svelte-fa": "^4.0.2",
"svelte-markdown": "^0.4.1"
}
}
}

View File

View File

@ -0,0 +1,41 @@
---
title: Spotube Basics
author: Kingkor Roy Tirtho
date: 2024-02-10
published: true
---
Spotube is an open-source Spotify client that allows users to stream music from Spotify. To use Spotube, you need to sign in with your Spotify account. Here's a step-by-step guide on how to sign in to Spotube.
## Prerequisites
Before you begin, make sure you have the following:
- A Spotify account
- The Spotube application installed on your device
## Steps
1. Open the Spotube application on your device.
2. Click on the "Sign in with Spotify" button.
3. You will be redirected to the Spotify login page. Enter your Spotify username and password and click on the "Log In" button.
4. You will be asked to grant Spotube permission to access your Spotify account. Click on the "Agree" button to grant permission.
5. You will be redirected back to the Spotube application, and you should now be signed in to your Spotify account.
That's it! You are now signed in to Spotube and can start streaming music from Spotify.
| Title | Author | Date | Published |
| -------------- | ------------------ | ---------- | --------- |
| Spotube Basics | Kingkor Roy Tirtho | 2024-02-10 | true |
```bash
$ git clone
```
```javascript
const a = 1;
```
## Conclusion
Signing in to Spotube is a simple process that requires you to have a Spotify account and the Spotube application installed on your device. By following the steps outlined in this tutorial, you should be able to sign in to Spotube and start streaming music from Spotify.

View File

@ -1,3 +0,0 @@
<article class="prose lg:prose-lg max-w-3xl">
<slot />
</article>

View File

@ -0,0 +1,3 @@
<article class="prose lg:prose-lg dark:prose-invert max-w-3xl">
<slot />
</article>

41
website/src/lib/posts.ts Normal file
View File

@ -0,0 +1,41 @@
export interface Post {
date: string;
title: string;
tags: string[];
published: boolean;
author: string;
readingTime: {
text: string;
minutes: number;
time: number;
words: number;
};
reading_time_text: string;
preview_html: string;
preview: string;
previewHtml: string;
slug: string | null;
path: string;
}
export const getPosts = async () => {
// Fetch posts from local Markdown files
const posts: Post[] = await Promise.all(
Object.entries(import.meta.glob('../../posts/**/*.md')).map(async ([path, resolver]) => {
const resolved = (await resolver()) as { metadata: Post };
const { metadata } = resolved;
const slug = path.split('/').pop()?.slice(0, -3) ?? '';
return { ...metadata, slug };
})
);
let sortedPosts = posts.sort((a, b) => +new Date(b.date) - +new Date(a.date));
sortedPosts = sortedPosts.map((post) => ({
...post
}));
return {
posts: sortedPosts
};
};

View File

@ -1,6 +1,6 @@
<script lang="ts">
import '../app.postcss';
import Navbar from '../components/navbar/navbar.svelte';
import Navbar from '$lib/components/navbar/navbar.svelte';
// Highlight JS
import hljs from 'highlight.js/lib/core';
@ -23,7 +23,7 @@
storePopup.set({ computePosition, autoUpdate, flip, shift, offset, arrow });
import { initializeStores } from '@skeletonlabs/skeleton';
import NavDrawer from '../components/navdrawer/navdrawer.svelte';
import NavDrawer from '../lib/components/navdrawer/navdrawer.svelte';
import Fa from 'svelte-fa';
import { faGithub } from '@fortawesome/free-brands-svg-icons';
initializeStores();

View File

@ -0,0 +1,9 @@
import { getPosts } from '$lib/posts';
import type { RequestHandler } from '@sveltejs/kit';
import { json } from '@sveltejs/kit';
export const GET: RequestHandler = async () => {
const { posts } = await getPosts();
return json(posts);
};

View File

@ -0,0 +1,32 @@
<script lang="ts">
import type { PageData } from './$types';
export let data: PageData;
const formatter = Intl.DateTimeFormat('en-US', {
dateStyle: 'medium'
});
</script>
<section class="p-4 md:p-16 flex flex-col gap-4">
<h2 class="h2">Blog Posts</h2>
<br />
<article class="grid sm:grid-cols-2 md:grid-cols-3 xl:grid-cols-4">
{#each data.posts as post}
<a
href={`/blog/${post.slug}`}
class="card hover:brightness-95 active:bg-secondary-hover-token active:scale-95 transition-all variant-ghost-secondary p-4"
>
<h4 class="h4">{post.title}</h4>
<p>By {post.author}</p>
<br />
<p class="text-end">
Published on
<span class="font-medium underline decoration-dotted">
{formatter.format(new Date(post.date))}
</span>
</p>
</a>
{/each}
</article>
</section>

View File

@ -0,0 +1,11 @@
import type { Post } from '$lib/posts.js';
export const load = async ({ fetch }) => {
const res = await fetch(`api/posts`);
if (res.ok) {
const posts: Post[] = await res.json();
return { posts };
} else {
return { posts: [] };
}
};

View File

@ -0,0 +1,26 @@
<script lang="ts">
import Layout from '$lib/components/markdown/layout.svelte';
import type { PageData } from './$types';
export let data: PageData;
let {
Content,
meta: { date, title, readingTime }
} = data as Required<PageData>;
</script>
<svelte:head>
<title>Blog | {title}</title>
</svelte:head>
<article class="p-4 md:p-16 flex flex-grow flex-col">
<h1 class="h1">{title}</h1>
<br />
<div class="">
<p>{new Date(date).toDateString()}</p>
<p class="mb-16">{readingTime?.text ?? ''}</p>
<Layout>
<svelte:component this={Content} />
</Layout>
</div>
</article>

View File

@ -0,0 +1,23 @@
import type { Post } from '$lib/posts.js';
export const load = async ({ params }) => {
const { slug } = params;
try {
const post = await import(`../../../../posts/${slug}.md`);
return {
Content: post.default as ConstructorOfATypedSvelteComponent,
meta: {
...post.metadata,
slug,
path: `/blog/${slug}`
} as Post
};
} catch (err) {
console.error('Error loading the post:', err);
return {
status: 500,
error: `Could not load the post: ${(err as Error).message || err}`
};
}
};

View File

@ -2,7 +2,7 @@
import { extendedDownloadLinks } from '$lib';
import { Download } from 'lucide-svelte';
import { History, Sparkles, Package } from 'lucide-svelte';
import DownloadItems from '../../components/downloads/download-items.svelte';
import DownloadItems from '$lib/components/downloads/download-items.svelte';
const otherDownloads: [string, string, any][] = [
['/downloads/packages', 'CLI Packages Managers', Package],

View File

@ -1,6 +1,6 @@
<script>
import { AlertTriangle, Bug, Sparkles } from 'lucide-svelte';
import DownloadItems from '../../../components/downloads/download-items.svelte';
import DownloadItems from '$lib/components/downloads/download-items.svelte';
import { extendedNightlyDownloadLinks } from '$lib';
</script>

View File

@ -2,7 +2,7 @@
import SvelteMarkdown from 'svelte-markdown';
import type { PageData } from './$types';
import { formatDistanceToNow, formatRelative } from 'date-fns';
import Layout from '../../../components/markdown/layout.svelte';
import Layout from '$lib/components/markdown/layout.svelte';
import { Accordion, AccordionItem } from '@skeletonlabs/skeleton';
import { Book, History } from 'lucide-svelte';
import {

View File

@ -1,6 +1,12 @@
import adapter from '@sveltejs/adapter-netlify';
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
import { mdsvex } from 'mdsvex';
import readingTime from 'remark-reading-time';
import remarkExternalLinks from 'remark-external-links';
import slugPlugin from 'rehype-slug';
import autolinkHeadings from 'rehype-autolink-headings';
import relativeImages from 'mdsvex-relative-images';
import remarkGfm from 'remark-gfm';
/** @type {import('@sveltejs/kit').Config} */
const config = {
@ -12,7 +18,28 @@ const config = {
mdsvex({
extensions: ['.svx', '.md'],
highlight: {},
layout: './src/components/markdown/layout.svelte'
layout: './src/lib/components/markdown/layout.svelte',
smartypants: {
dashes: 'oldschool'
},
remarkPlugins: [
remarkGfm,
// adds a `readingTime` frontmatter attribute
readingTime(),
relativeImages,
// external links open in a new tab
[remarkExternalLinks, { target: '_blank', rel: 'noopener' }]
],
rehypePlugins: [
slugPlugin,
[
autolinkHeadings,
{
behavior: 'wrap'
}
]
]
})
],
vitePlugin: {

View File

@ -3,11 +3,20 @@ import { sveltekit } from '@sveltejs/kit/vite';
import { defineConfig } from 'vite';
export default defineConfig({
plugins: [sveltekit(), purgeCss({
plugins: [
sveltekit(),
purgeCss({
safelist: {
// any selectors that begin with "hljs-" will not be purged
greedy: [/^hljs-/],
},
}),
greedy: [/^hljs-/]
}
})
],
});
server: {
fs: {
// Allow serving files from one level up to the project root
// posts, copy
allow: ['..']
}
}
});