mirror of
https://github.com/KRTirtho/spotube.git
synced 2025-09-12 23:45:18 +00:00
[website] Added blog support
[website] Fixed Navbar & made responsive
This commit is contained in:
parent
68e9dfe9b3
commit
0e10ddfa54
138
website/components/ArticleCard.tsx
Normal file
138
website/components/ArticleCard.tsx
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
import { Link, Flex, Box, chakra, Image, Button, Text } from "@chakra-ui/react";
|
||||||
|
import { BlogPost } from "pages/blog";
|
||||||
|
import { FC } from "react";
|
||||||
|
import NavLink from "next/link";
|
||||||
|
|
||||||
|
const ArticleCard: FC<BlogPost> = ({
|
||||||
|
metadata: {
|
||||||
|
author,
|
||||||
|
author_avatar_url,
|
||||||
|
cover_image,
|
||||||
|
tags,
|
||||||
|
title,
|
||||||
|
summary,
|
||||||
|
date,
|
||||||
|
},
|
||||||
|
slug,
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
mx="auto"
|
||||||
|
rounded="lg"
|
||||||
|
shadow="md"
|
||||||
|
bg="white"
|
||||||
|
_dark={{
|
||||||
|
bg: "gray.800",
|
||||||
|
}}
|
||||||
|
maxW="2xl"
|
||||||
|
>
|
||||||
|
<Image
|
||||||
|
roundedTop="lg"
|
||||||
|
w="full"
|
||||||
|
h={64}
|
||||||
|
fit="cover"
|
||||||
|
src={cover_image}
|
||||||
|
alt="Article"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Box p={6}>
|
||||||
|
<Box>
|
||||||
|
{tags.map((tag, i) => {
|
||||||
|
return (
|
||||||
|
<chakra.span
|
||||||
|
key={i}
|
||||||
|
px={3}
|
||||||
|
py={1}
|
||||||
|
mx="1"
|
||||||
|
bg="gray.600"
|
||||||
|
color="gray.100"
|
||||||
|
fontSize="sm"
|
||||||
|
fontWeight="700"
|
||||||
|
rounded="md"
|
||||||
|
>
|
||||||
|
{tag}
|
||||||
|
</chakra.span>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
<NavLink href={`/blog/${slug}`} passHref>
|
||||||
|
<Link
|
||||||
|
display="block"
|
||||||
|
color="gray.800"
|
||||||
|
_dark={{
|
||||||
|
color: "white",
|
||||||
|
}}
|
||||||
|
fontWeight="bold"
|
||||||
|
fontSize="2xl"
|
||||||
|
mt={2}
|
||||||
|
_hover={{
|
||||||
|
color: "gray.600",
|
||||||
|
textDecor: "underline",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{title}
|
||||||
|
</Link>
|
||||||
|
</NavLink>
|
||||||
|
<chakra.p
|
||||||
|
mt={2}
|
||||||
|
fontSize="sm"
|
||||||
|
color="gray.600"
|
||||||
|
_dark={{
|
||||||
|
color: "gray.400",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{summary}
|
||||||
|
</chakra.p>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box mt={4}>
|
||||||
|
<Flex
|
||||||
|
alignItems="center"
|
||||||
|
justify="space-between"
|
||||||
|
flexDirection={{ base: "column", md: "row" }}
|
||||||
|
>
|
||||||
|
<Flex alignItems="center">
|
||||||
|
<Image
|
||||||
|
h={10}
|
||||||
|
fit="cover"
|
||||||
|
rounded="full"
|
||||||
|
src={author_avatar_url}
|
||||||
|
alt="Avatar"
|
||||||
|
/>
|
||||||
|
<Text
|
||||||
|
mx={2}
|
||||||
|
fontWeight="bold"
|
||||||
|
color="gray.700"
|
||||||
|
_dark={{
|
||||||
|
color: "gray.200",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{author}
|
||||||
|
</Text>
|
||||||
|
<chakra.span
|
||||||
|
mx={1}
|
||||||
|
fontSize="sm"
|
||||||
|
color="gray.600"
|
||||||
|
_dark={{
|
||||||
|
color: "gray.300",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{date}
|
||||||
|
</chakra.span>
|
||||||
|
</Flex>
|
||||||
|
<NavLink href={`/blog/${slug}`} passHref>
|
||||||
|
<Button
|
||||||
|
_hover={{ textDecor: "none" }}
|
||||||
|
colorScheme="purple"
|
||||||
|
as={Link}
|
||||||
|
>
|
||||||
|
Read the Full Article
|
||||||
|
</Button>
|
||||||
|
</NavLink>
|
||||||
|
</Flex>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ArticleCard;
|
@ -10,26 +10,60 @@ import {
|
|||||||
} from "@chakra-ui/react";
|
} from "@chakra-ui/react";
|
||||||
import { Platform, usePlatform } from "hooks/usePlatform";
|
import { Platform, usePlatform } from "hooks/usePlatform";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { FaCaretDown } from "react-icons/fa";
|
import {
|
||||||
|
FaApple,
|
||||||
|
FaCaretDown,
|
||||||
|
FaUbuntu,
|
||||||
|
FaLinux,
|
||||||
|
FaWindows,
|
||||||
|
FaAndroid,
|
||||||
|
} from "react-icons/fa";
|
||||||
|
|
||||||
const baseURL = "https://github.com/KRTirtho/spotube/releases/latest/download/";
|
const baseURL = "https://github.com/KRTirtho/spotube/releases/latest/download/";
|
||||||
|
|
||||||
const DownloadLinks = Object.freeze({
|
const DownloadLinks = Object.freeze({
|
||||||
[Platform.linux]: [
|
[Platform.linux]: [
|
||||||
{ name: "deb", url: baseURL + "Spotube-linux-x86_64.deb" },
|
{
|
||||||
{ name: "tar", url: baseURL + "Spotube-linux-x86_64.tar.xz" },
|
name: "deb",
|
||||||
{ name: "AppImage", url: baseURL + "Spotube-linux-x86_64.AppImage" },
|
url: baseURL + "Spotube-linux-x86_64.deb",
|
||||||
|
icon: <FaUbuntu />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "tar",
|
||||||
|
url: baseURL + "Spotube-linux-x86_64.tar.xz",
|
||||||
|
icon: <FaLinux />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "AppImage",
|
||||||
|
url: baseURL + "Spotube-linux-x86_64.AppImage",
|
||||||
|
icon: <FaLinux />,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
[Platform.android]: [
|
[Platform.android]: [
|
||||||
{
|
{
|
||||||
name: "apk",
|
name: "apk",
|
||||||
url: baseURL + "Spotube-android-all-arch.apk",
|
url: baseURL + "Spotube-android-all-arch.apk",
|
||||||
|
icon: <FaAndroid />,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[Platform.mac]: [
|
||||||
|
{
|
||||||
|
name: "dmg",
|
||||||
|
url: baseURL + "Spotube-macos-x86_64.dmg",
|
||||||
|
icon: <FaApple />,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
[Platform.mac]: [{ name: "dmg", url: baseURL + "Spotube-macos-x86_64.dmg" }],
|
|
||||||
[Platform.windows]: [
|
[Platform.windows]: [
|
||||||
{ name: "exe", url: baseURL + "Spotube-windows-x86_64-setup.exe" },
|
{
|
||||||
{ name: "nupkg", url: baseURL + "Spotube-windows-x86_64.nupkg " },
|
name: "exe",
|
||||||
|
url: baseURL + "Spotube-windows-x86_64-setup.exe",
|
||||||
|
icon: <FaWindows />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "nupkg",
|
||||||
|
url: baseURL + "Spotube-windows-x86_64.nupkg ",
|
||||||
|
icon: <FaWindows />,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -55,7 +89,7 @@ const DownloadButton = () => {
|
|||||||
href={currentPlatform.url}
|
href={currentPlatform.url}
|
||||||
_hover={{ textDecoration: "none" }}
|
_hover={{ textDecoration: "none" }}
|
||||||
>
|
>
|
||||||
Download for {platform} (.{currentPlatform.name}) Binary
|
Download for {platform} (.{currentPlatform.name})
|
||||||
</Button>
|
</Button>
|
||||||
<MenuButton
|
<MenuButton
|
||||||
aria-label="Show More Downloads"
|
aria-label="Show More Downloads"
|
||||||
@ -65,9 +99,9 @@ const DownloadButton = () => {
|
|||||||
/>
|
/>
|
||||||
</ButtonGroup>
|
</ButtonGroup>
|
||||||
<MenuList>
|
<MenuList>
|
||||||
{allPlatforms.map(({ name, url }) => {
|
{allPlatforms.map(({ name, url, icon }) => {
|
||||||
return (
|
return (
|
||||||
<MenuItem key={url} as={Anchor} href={url}>
|
<MenuItem key={url} as={Anchor} href={url} icon={icon}>
|
||||||
{name}
|
{name}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
);
|
);
|
||||||
|
@ -1,17 +1,29 @@
|
|||||||
import {
|
import {
|
||||||
|
Box,
|
||||||
Button,
|
Button,
|
||||||
ButtonGroup,
|
ButtonGroup,
|
||||||
|
chakra,
|
||||||
|
CloseButton,
|
||||||
|
Flex,
|
||||||
Heading,
|
Heading,
|
||||||
HStack,
|
HStack,
|
||||||
IconButton,
|
IconButton,
|
||||||
|
Link,
|
||||||
useColorMode,
|
useColorMode,
|
||||||
|
useColorModeValue,
|
||||||
|
useDisclosure,
|
||||||
|
VisuallyHidden,
|
||||||
|
VStack,
|
||||||
} from "@chakra-ui/react";
|
} from "@chakra-ui/react";
|
||||||
import NavLink from "next/link";
|
import NavLink from "next/link";
|
||||||
import { GoLightBulb } from "react-icons/go";
|
import { GoLightBulb } from "react-icons/go";
|
||||||
import { FiSun } from "react-icons/fi";
|
import { FiGithub, FiSun } from "react-icons/fi";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
|
import React from "react";
|
||||||
|
import { AiOutlineMenu } from "react-icons/ai";
|
||||||
|
import { BsHeartFill } from "react-icons/bs";
|
||||||
|
|
||||||
const Navbar = () => {
|
const ExNavbar = () => {
|
||||||
const { colorMode, toggleColorMode } = useColorMode();
|
const { colorMode, toggleColorMode } = useColorMode();
|
||||||
return (
|
return (
|
||||||
<HStack justifyContent="space-between" as="nav" w="full">
|
<HStack justifyContent="space-between" as="nav" w="full">
|
||||||
@ -34,6 +46,11 @@ const Navbar = () => {
|
|||||||
Downloads
|
Downloads
|
||||||
</Button>
|
</Button>
|
||||||
</NavLink>
|
</NavLink>
|
||||||
|
<NavLink href="/blog" passHref>
|
||||||
|
<Button as="a" variant="ghost" colorScheme="gray">
|
||||||
|
Blog
|
||||||
|
</Button>
|
||||||
|
</NavLink>
|
||||||
<NavLink href="/about" passHref>
|
<NavLink href="/about" passHref>
|
||||||
<Button as="a" variant="ghost" colorScheme="gray">
|
<Button as="a" variant="ghost" colorScheme="gray">
|
||||||
About
|
About
|
||||||
@ -53,4 +70,185 @@ const Navbar = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const Navbar = () => {
|
||||||
|
const bg = useColorModeValue("white", "gray.800");
|
||||||
|
const mobileNav = useDisclosure();
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
<chakra.header
|
||||||
|
bg={bg}
|
||||||
|
w="full"
|
||||||
|
px={{
|
||||||
|
base: 1,
|
||||||
|
sm: 3,
|
||||||
|
}}
|
||||||
|
py={2}
|
||||||
|
shadow="md"
|
||||||
|
>
|
||||||
|
<Flex alignItems="center" justifyContent="space-between" mx="auto">
|
||||||
|
<Flex align="center">
|
||||||
|
<NavLink href="/">
|
||||||
|
<Image
|
||||||
|
src="/spotube-logo.svg"
|
||||||
|
alt="Logo"
|
||||||
|
height="45"
|
||||||
|
width="45"
|
||||||
|
layout="fixed"
|
||||||
|
/>
|
||||||
|
</NavLink>
|
||||||
|
<VisuallyHidden>Spotube</VisuallyHidden>
|
||||||
|
<NavLink href="/" passHref>
|
||||||
|
<Heading p="2" as="a" size="lg" mr="2">
|
||||||
|
Spotube
|
||||||
|
</Heading>
|
||||||
|
</NavLink>
|
||||||
|
</Flex>
|
||||||
|
<HStack display="flex" alignItems="center" spacing={1}>
|
||||||
|
<HStack
|
||||||
|
spacing={1}
|
||||||
|
mr={1}
|
||||||
|
color="brand.500"
|
||||||
|
display={{
|
||||||
|
base: "none",
|
||||||
|
md: "inline-flex",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<NavLink href="/other-downloads" passHref>
|
||||||
|
<Button as="a" colorScheme="gray" variant="ghost">
|
||||||
|
Downloads
|
||||||
|
</Button>
|
||||||
|
</NavLink>
|
||||||
|
<NavLink href="/blog" passHref>
|
||||||
|
<Button as="a" variant="ghost" colorScheme="gray">
|
||||||
|
Blog
|
||||||
|
</Button>
|
||||||
|
</NavLink>
|
||||||
|
<NavLink href="/about" passHref>
|
||||||
|
<Button as="a" variant="ghost" colorScheme="gray">
|
||||||
|
About
|
||||||
|
</Button>
|
||||||
|
</NavLink>
|
||||||
|
<Button
|
||||||
|
as={Link}
|
||||||
|
href="https://github.com/KRTirtho/spotube"
|
||||||
|
bgColor="black"
|
||||||
|
color="white"
|
||||||
|
target="_blank"
|
||||||
|
_hover={{
|
||||||
|
textDecor: "none",
|
||||||
|
bgColor: "blackAlpha.800",
|
||||||
|
}}
|
||||||
|
_active={{
|
||||||
|
bgColor: "blackAlpha.700",
|
||||||
|
}}
|
||||||
|
rightIcon={<FiGithub />}
|
||||||
|
>
|
||||||
|
Give us a ⭐ on
|
||||||
|
</Button>
|
||||||
|
</HStack>
|
||||||
|
<Button
|
||||||
|
size={{
|
||||||
|
base: "sm",
|
||||||
|
md: "sm",
|
||||||
|
lg: "md",
|
||||||
|
}}
|
||||||
|
as={Link}
|
||||||
|
href="https://opencollective.com/spotube"
|
||||||
|
bgColor="pink.100"
|
||||||
|
color="pink.500"
|
||||||
|
_hover={{
|
||||||
|
bgColor: "pink.200",
|
||||||
|
textDecor: "none",
|
||||||
|
}}
|
||||||
|
_active={{
|
||||||
|
bgColor: "pink.100",
|
||||||
|
}}
|
||||||
|
rightIcon={<BsHeartFill />}
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
Donate/Sponsor us
|
||||||
|
</Button>
|
||||||
|
<Box
|
||||||
|
display={{
|
||||||
|
base: "inline-flex",
|
||||||
|
md: "none",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<IconButton
|
||||||
|
display={{
|
||||||
|
base: "flex",
|
||||||
|
md: "none",
|
||||||
|
}}
|
||||||
|
aria-label="Open menu"
|
||||||
|
fontSize="20px"
|
||||||
|
color="gray.800"
|
||||||
|
_dark={{
|
||||||
|
color: "inherit",
|
||||||
|
}}
|
||||||
|
variant="ghost"
|
||||||
|
icon={<AiOutlineMenu />}
|
||||||
|
onClick={mobileNav.onOpen}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<VStack
|
||||||
|
pos="absolute"
|
||||||
|
top={0}
|
||||||
|
left={0}
|
||||||
|
right={0}
|
||||||
|
display={mobileNav.isOpen ? "flex" : "none"}
|
||||||
|
flexDirection="column"
|
||||||
|
p={2}
|
||||||
|
pb={4}
|
||||||
|
m={2}
|
||||||
|
bg={bg}
|
||||||
|
spacing={3}
|
||||||
|
rounded="sm"
|
||||||
|
shadow="sm"
|
||||||
|
>
|
||||||
|
<CloseButton
|
||||||
|
aria-label="Close menu"
|
||||||
|
onClick={mobileNav.onClose}
|
||||||
|
/>
|
||||||
|
<NavLink href="/other-downloads" passHref>
|
||||||
|
<Button w="full" as="a" colorScheme="gray" variant="ghost">
|
||||||
|
Downloads
|
||||||
|
</Button>
|
||||||
|
</NavLink>
|
||||||
|
<NavLink href="/blog" passHref>
|
||||||
|
<Button w="full" as="a" variant="ghost" colorScheme="gray">
|
||||||
|
Blog
|
||||||
|
</Button>
|
||||||
|
</NavLink>
|
||||||
|
<NavLink href="/about" passHref>
|
||||||
|
<Button w="full" as="a" variant="ghost" colorScheme="gray">
|
||||||
|
About
|
||||||
|
</Button>
|
||||||
|
</NavLink>
|
||||||
|
<Button
|
||||||
|
as={Link}
|
||||||
|
href="https://github.com/KRTirtho/spotube"
|
||||||
|
bgColor="black"
|
||||||
|
color="white"
|
||||||
|
target="_blank"
|
||||||
|
w="full"
|
||||||
|
_hover={{
|
||||||
|
textDecor: "none",
|
||||||
|
bgColor: "blackAlpha.800",
|
||||||
|
}}
|
||||||
|
_active={{
|
||||||
|
bgColor: "blackAlpha.700",
|
||||||
|
}}
|
||||||
|
rightIcon={<FiGithub />}
|
||||||
|
>
|
||||||
|
Give us a ⭐ on
|
||||||
|
</Button>
|
||||||
|
</VStack>
|
||||||
|
</Box>
|
||||||
|
</HStack>
|
||||||
|
</Flex>
|
||||||
|
</chakra.header>
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export default Navbar;
|
export default Navbar;
|
||||||
|
@ -1,6 +1,16 @@
|
|||||||
import { Link as Anchor, Heading, Text, chakra } from "@chakra-ui/react";
|
import {
|
||||||
|
Link as Anchor,
|
||||||
|
Heading,
|
||||||
|
Text,
|
||||||
|
chakra,
|
||||||
|
Code,
|
||||||
|
HStack,
|
||||||
|
Divider,
|
||||||
|
Box,
|
||||||
|
} from "@chakra-ui/react";
|
||||||
|
import { Options } from "react-markdown";
|
||||||
|
|
||||||
export const MarkdownComponentDefs = {
|
export const MarkdownComponentDefs: Options["components"] = {
|
||||||
a: (props: any) => <Anchor {...props} color="blue.500" />,
|
a: (props: any) => <Anchor {...props} color="blue.500" />,
|
||||||
h1: (props: any) => <Heading {...props} size="xl" mt="5" mb="1.5" />,
|
h1: (props: any) => <Heading {...props} size="xl" mt="5" mb="1.5" />,
|
||||||
h2: (props: any) => <Heading {...props} size="lg" mt="5" mb="1.5" />,
|
h2: (props: any) => <Heading {...props} size="lg" mt="5" mb="1.5" />,
|
||||||
@ -10,4 +20,24 @@ export const MarkdownComponentDefs = {
|
|||||||
h6: (props: any) => <Heading {...props} size="xs" />,
|
h6: (props: any) => <Heading {...props} size="xs" />,
|
||||||
p: (props: any) => <Text {...props} />,
|
p: (props: any) => <Text {...props} />,
|
||||||
li: (props: any) => <chakra.li {...props} ml="4" />,
|
li: (props: any) => <chakra.li {...props} ml="4" />,
|
||||||
|
code: (props) => (
|
||||||
|
<Code
|
||||||
|
{...props}
|
||||||
|
p={!props.inline ? 5 : 0}
|
||||||
|
overflow="scroll"
|
||||||
|
colorScheme="gray"
|
||||||
|
maxW="full"
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
blockquote: (props) => {
|
||||||
|
return (
|
||||||
|
<HStack bgColor="blackAlpha.300" py="3" px="2">
|
||||||
|
<Box borderLeft="2px solid gray" pl="2">
|
||||||
|
<Text as="span" fontSize="sm">
|
||||||
|
{props.children}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
</HStack>
|
||||||
|
);
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
@ -18,6 +18,7 @@
|
|||||||
"@octokit/rest": "^19.0.3",
|
"@octokit/rest": "^19.0.3",
|
||||||
"@types/progress": "^2.0.5",
|
"@types/progress": "^2.0.5",
|
||||||
"framer-motion": "^6",
|
"framer-motion": "^6",
|
||||||
|
"gray-matter": "^4.0.3",
|
||||||
"next": "12.2.2",
|
"next": "12.2.2",
|
||||||
"nextjs-progressbar": "^0.0.14",
|
"nextjs-progressbar": "^0.0.14",
|
||||||
"react": "18.2.0",
|
"react": "18.2.0",
|
||||||
@ -32,6 +33,7 @@
|
|||||||
"@types/node": "18.0.5",
|
"@types/node": "18.0.5",
|
||||||
"@types/react": "18.0.15",
|
"@types/react": "18.0.15",
|
||||||
"@types/react-dom": "18.0.6",
|
"@types/react-dom": "18.0.6",
|
||||||
|
"@types/react-syntax-highlighter": "^15.5.3",
|
||||||
"eslint": "8.20.0",
|
"eslint": "8.20.0",
|
||||||
"eslint-config-next": "12.2.2",
|
"eslint-config-next": "12.2.2",
|
||||||
"eslint-config-prettier": "^8.5.0",
|
"eslint-config-prettier": "^8.5.0",
|
||||||
|
@ -1,13 +0,0 @@
|
|||||||
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
|
|
||||||
import type { NextApiRequest, NextApiResponse } from 'next'
|
|
||||||
|
|
||||||
type Data = {
|
|
||||||
name: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function handler(
|
|
||||||
req: NextApiRequest,
|
|
||||||
res: NextApiResponse<Data>
|
|
||||||
) {
|
|
||||||
res.status(200).json({ name: 'John Doe' })
|
|
||||||
}
|
|
105
website/pages/blog/[slug].tsx
Normal file
105
website/pages/blog/[slug].tsx
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
import fs from "fs";
|
||||||
|
import path from "path";
|
||||||
|
import { GetStaticPaths, GetStaticProps, NextPage } from "next";
|
||||||
|
import ReactMarkdown from "react-markdown";
|
||||||
|
import matter from "gray-matter";
|
||||||
|
import { BlogMetadata } from ".";
|
||||||
|
import gfm from "remark-gfm";
|
||||||
|
import gemoji from "remark-gemoji";
|
||||||
|
import { MarkdownComponentDefs } from "misc/MarkdownComponentDefs";
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
chakra,
|
||||||
|
Flex,
|
||||||
|
Heading,
|
||||||
|
Image,
|
||||||
|
Text,
|
||||||
|
VStack,
|
||||||
|
} from "@chakra-ui/react";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
metadata: BlogMetadata;
|
||||||
|
content: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const BlogPost: NextPage<Props> = ({
|
||||||
|
content,
|
||||||
|
metadata: { author, author_avatar_url, cover_image, date, tags, title },
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<VStack>
|
||||||
|
<Box w="full" maxH="xl" overflow="hidden" mb="5">
|
||||||
|
<Image fit="cover" src={cover_image} alt={title} />
|
||||||
|
</Box>
|
||||||
|
<VStack align="flex-start" py="5" px="10" w="full">
|
||||||
|
<Flex alignItems="center">
|
||||||
|
<Image
|
||||||
|
h="12"
|
||||||
|
fit="cover"
|
||||||
|
rounded="full"
|
||||||
|
src={author_avatar_url}
|
||||||
|
alt="Avatar"
|
||||||
|
/>
|
||||||
|
<VStack spacing="0">
|
||||||
|
<Text
|
||||||
|
mx={2}
|
||||||
|
fontWeight="bold"
|
||||||
|
color="gray.700"
|
||||||
|
_dark={{
|
||||||
|
color: "gray.200",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{author}
|
||||||
|
</Text>
|
||||||
|
<chakra.span
|
||||||
|
mx={1}
|
||||||
|
fontSize="sm"
|
||||||
|
color="gray.600"
|
||||||
|
_dark={{
|
||||||
|
color: "gray.300",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{date}
|
||||||
|
</chakra.span>
|
||||||
|
</VStack>
|
||||||
|
</Flex>
|
||||||
|
<Heading>{title}</Heading>
|
||||||
|
|
||||||
|
<ReactMarkdown
|
||||||
|
components={MarkdownComponentDefs}
|
||||||
|
remarkPlugins={[gfm, gemoji]}
|
||||||
|
>
|
||||||
|
{content}
|
||||||
|
</ReactMarkdown>
|
||||||
|
</VStack>
|
||||||
|
</VStack>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default BlogPost;
|
||||||
|
|
||||||
|
export const getStaticPaths: GetStaticPaths = async () => {
|
||||||
|
const paths = fs.readdirSync("posts").map((file) => {
|
||||||
|
return {
|
||||||
|
params: {
|
||||||
|
slug: file.replace(".md", ""),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
paths,
|
||||||
|
fallback: false,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getStaticProps: GetStaticProps<Props> = async (ctx) => {
|
||||||
|
const { content, data } = matter(
|
||||||
|
fs.readFileSync(path.join("posts", `${ctx.params?.slug}.md`), "utf-8")
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
props: {
|
||||||
|
content,
|
||||||
|
metadata: data as BlogMetadata,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
62
website/pages/blog/index.tsx
Normal file
62
website/pages/blog/index.tsx
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
import fs from "fs";
|
||||||
|
import path from "path";
|
||||||
|
import { GetStaticProps, NextPage } from "next";
|
||||||
|
import matter from "gray-matter";
|
||||||
|
import ArticleCard from "components/ArticleCard";
|
||||||
|
import { VStack } from "@chakra-ui/react";
|
||||||
|
|
||||||
|
export interface BlogMetadata {
|
||||||
|
title: string;
|
||||||
|
cover_image: string;
|
||||||
|
author: string;
|
||||||
|
date: string;
|
||||||
|
author_avatar_url: string;
|
||||||
|
tags: string[];
|
||||||
|
summary: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface BlogPost {
|
||||||
|
slug: string;
|
||||||
|
metadata: BlogMetadata;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
posts: BlogPost[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const Blog: NextPage<Props> = ({ posts }) => {
|
||||||
|
return (
|
||||||
|
<VStack mx="5" my="5" spacing="7">
|
||||||
|
{posts.map((post) => {
|
||||||
|
return (
|
||||||
|
<ArticleCard
|
||||||
|
key={post.slug}
|
||||||
|
metadata={post.metadata}
|
||||||
|
slug={post.slug}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</VStack>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Blog;
|
||||||
|
|
||||||
|
export const getStaticProps: GetStaticProps<Props> = async () => {
|
||||||
|
const posts = fs.readdirSync("posts").map((file) => {
|
||||||
|
return {
|
||||||
|
slug: file.replace(".md", ""),
|
||||||
|
metadata: matter(fs.readFileSync(path.join("posts", file)))
|
||||||
|
.data as BlogMetadata,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
props: {
|
||||||
|
posts: posts.sort(
|
||||||
|
// @ts-ignore
|
||||||
|
(a, b) => new Date(b.metadata.date) - new Date(a.metadata.date)
|
||||||
|
),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
@ -12,10 +12,12 @@ specifiers:
|
|||||||
'@types/progress': ^2.0.5
|
'@types/progress': ^2.0.5
|
||||||
'@types/react': 18.0.15
|
'@types/react': 18.0.15
|
||||||
'@types/react-dom': 18.0.6
|
'@types/react-dom': 18.0.6
|
||||||
|
'@types/react-syntax-highlighter': ^15.5.3
|
||||||
eslint: 8.20.0
|
eslint: 8.20.0
|
||||||
eslint-config-next: 12.2.2
|
eslint-config-next: 12.2.2
|
||||||
eslint-config-prettier: ^8.5.0
|
eslint-config-prettier: ^8.5.0
|
||||||
framer-motion: ^6
|
framer-motion: ^6
|
||||||
|
gray-matter: ^4.0.3
|
||||||
next: 12.2.2
|
next: 12.2.2
|
||||||
nextjs-progressbar: ^0.0.14
|
nextjs-progressbar: ^0.0.14
|
||||||
react: 18.2.0
|
react: 18.2.0
|
||||||
@ -37,6 +39,7 @@ dependencies:
|
|||||||
'@octokit/rest': 19.0.3
|
'@octokit/rest': 19.0.3
|
||||||
'@types/progress': 2.0.5
|
'@types/progress': 2.0.5
|
||||||
framer-motion: 6.5.1_biqbaboplfbrettd7655fr4n2y
|
framer-motion: 6.5.1_biqbaboplfbrettd7655fr4n2y
|
||||||
|
gray-matter: 4.0.3
|
||||||
next: 12.2.2_beenoklgwfttvph5dgxj7na7aq
|
next: 12.2.2_beenoklgwfttvph5dgxj7na7aq
|
||||||
nextjs-progressbar: 0.0.14_next@12.2.2+react@18.2.0
|
nextjs-progressbar: 0.0.14_next@12.2.2+react@18.2.0
|
||||||
react: 18.2.0
|
react: 18.2.0
|
||||||
@ -51,6 +54,7 @@ devDependencies:
|
|||||||
'@types/node': 18.0.5
|
'@types/node': 18.0.5
|
||||||
'@types/react': 18.0.15
|
'@types/react': 18.0.15
|
||||||
'@types/react-dom': 18.0.6
|
'@types/react-dom': 18.0.6
|
||||||
|
'@types/react-syntax-highlighter': 15.5.3
|
||||||
eslint: 8.20.0
|
eslint: 8.20.0
|
||||||
eslint-config-next: 12.2.2_he2ccbldppg44uulnyq4rwocfa
|
eslint-config-next: 12.2.2_he2ccbldppg44uulnyq4rwocfa
|
||||||
eslint-config-prettier: 8.5.0_eslint@8.20.0
|
eslint-config-prettier: 8.5.0_eslint@8.20.0
|
||||||
@ -1662,6 +1666,12 @@ packages:
|
|||||||
'@types/react': 18.0.15
|
'@types/react': 18.0.15
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/@types/react-syntax-highlighter/15.5.3:
|
||||||
|
resolution: {integrity: sha512-N5bgZxolo+wFuYnx4nOvIQO2P0E+KYHt3dDwb8ydUvZ96QN8Lpq60ReT+0W0JmXKZjp4udkYkIDYt9GIygBY1Q==}
|
||||||
|
dependencies:
|
||||||
|
'@types/react': 18.0.15
|
||||||
|
dev: true
|
||||||
|
|
||||||
/@types/react/18.0.15:
|
/@types/react/18.0.15:
|
||||||
resolution: {integrity: sha512-iz3BtLuIYH1uWdsv6wXYdhozhqj20oD4/Hk2DNXIn1kFsmp9x8d9QB6FnPhfkbhd2PgEONt9Q1x/ebkwjfFLow==}
|
resolution: {integrity: sha512-iz3BtLuIYH1uWdsv6wXYdhozhqj20oD4/Hk2DNXIn1kFsmp9x8d9QB6FnPhfkbhd2PgEONt9Q1x/ebkwjfFLow==}
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -1784,6 +1794,12 @@ packages:
|
|||||||
color-convert: 2.0.1
|
color-convert: 2.0.1
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/argparse/1.0.10:
|
||||||
|
resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==}
|
||||||
|
dependencies:
|
||||||
|
sprintf-js: 1.0.3
|
||||||
|
dev: false
|
||||||
|
|
||||||
/argparse/2.0.1:
|
/argparse/2.0.1:
|
||||||
resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
|
resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
|
||||||
dev: true
|
dev: true
|
||||||
@ -2441,6 +2457,12 @@ packages:
|
|||||||
eslint-visitor-keys: 3.3.0
|
eslint-visitor-keys: 3.3.0
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/esprima/4.0.1:
|
||||||
|
resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==}
|
||||||
|
engines: {node: '>=4'}
|
||||||
|
hasBin: true
|
||||||
|
dev: false
|
||||||
|
|
||||||
/esquery/1.4.0:
|
/esquery/1.4.0:
|
||||||
resolution: {integrity: sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==}
|
resolution: {integrity: sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==}
|
||||||
engines: {node: '>=0.10'}
|
engines: {node: '>=0.10'}
|
||||||
@ -2465,6 +2487,13 @@ packages:
|
|||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/extend-shallow/2.0.1:
|
||||||
|
resolution: {integrity: sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==}
|
||||||
|
engines: {node: '>=0.10.0'}
|
||||||
|
dependencies:
|
||||||
|
is-extendable: 0.1.1
|
||||||
|
dev: false
|
||||||
|
|
||||||
/extend/3.0.2:
|
/extend/3.0.2:
|
||||||
resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==}
|
resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==}
|
||||||
dev: false
|
dev: false
|
||||||
@ -2687,6 +2716,16 @@ packages:
|
|||||||
slash: 3.0.0
|
slash: 3.0.0
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/gray-matter/4.0.3:
|
||||||
|
resolution: {integrity: sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==}
|
||||||
|
engines: {node: '>=6.0'}
|
||||||
|
dependencies:
|
||||||
|
js-yaml: 3.14.1
|
||||||
|
kind-of: 6.0.3
|
||||||
|
section-matter: 1.0.0
|
||||||
|
strip-bom-string: 1.0.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
/has-bigints/1.0.2:
|
/has-bigints/1.0.2:
|
||||||
resolution: {integrity: sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==}
|
resolution: {integrity: sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==}
|
||||||
dev: true
|
dev: true
|
||||||
@ -2826,6 +2865,11 @@ packages:
|
|||||||
has-tostringtag: 1.0.0
|
has-tostringtag: 1.0.0
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/is-extendable/0.1.1:
|
||||||
|
resolution: {integrity: sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==}
|
||||||
|
engines: {node: '>=0.10.0'}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/is-extglob/2.1.1:
|
/is-extglob/2.1.1:
|
||||||
resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
|
resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
@ -2906,6 +2950,14 @@ packages:
|
|||||||
/js-tokens/4.0.0:
|
/js-tokens/4.0.0:
|
||||||
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
|
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
|
||||||
|
|
||||||
|
/js-yaml/3.14.1:
|
||||||
|
resolution: {integrity: sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==}
|
||||||
|
hasBin: true
|
||||||
|
dependencies:
|
||||||
|
argparse: 1.0.10
|
||||||
|
esprima: 4.0.1
|
||||||
|
dev: false
|
||||||
|
|
||||||
/js-yaml/4.1.0:
|
/js-yaml/4.1.0:
|
||||||
resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==}
|
resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
@ -2952,6 +3004,11 @@ packages:
|
|||||||
object.assign: 4.1.2
|
object.assign: 4.1.2
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/kind-of/6.0.3:
|
||||||
|
resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==}
|
||||||
|
engines: {node: '>=0.10.0'}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/kleur/4.1.5:
|
/kleur/4.1.5:
|
||||||
resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==}
|
resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==}
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
@ -3955,6 +4012,14 @@ packages:
|
|||||||
loose-envify: 1.4.0
|
loose-envify: 1.4.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/section-matter/1.0.0:
|
||||||
|
resolution: {integrity: sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==}
|
||||||
|
engines: {node: '>=4'}
|
||||||
|
dependencies:
|
||||||
|
extend-shallow: 2.0.1
|
||||||
|
kind-of: 6.0.3
|
||||||
|
dev: false
|
||||||
|
|
||||||
/semver/6.3.0:
|
/semver/6.3.0:
|
||||||
resolution: {integrity: sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==}
|
resolution: {integrity: sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
@ -4006,6 +4071,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-ekwEbFp5aqSPKaqeY1PGrlGQxPNaq+Cnx4+bE2D8sciBQrHpbwoBbawqTN2+6jPs9IdWxxiUcN0K2pkczD3zmw==}
|
resolution: {integrity: sha512-ekwEbFp5aqSPKaqeY1PGrlGQxPNaq+Cnx4+bE2D8sciBQrHpbwoBbawqTN2+6jPs9IdWxxiUcN0K2pkczD3zmw==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/sprintf-js/1.0.3:
|
||||||
|
resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/string.prototype.matchall/4.0.7:
|
/string.prototype.matchall/4.0.7:
|
||||||
resolution: {integrity: sha512-f48okCX7JiwVi1NXCVWcFnZgADDC/n2vePlQ/KUCNqCikLLilQvwjMO8+BHVKvgzH0JB0J9LEPgxOGT02RoETg==}
|
resolution: {integrity: sha512-f48okCX7JiwVi1NXCVWcFnZgADDC/n2vePlQ/KUCNqCikLLilQvwjMO8+BHVKvgzH0JB0J9LEPgxOGT02RoETg==}
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -4042,6 +4111,11 @@ packages:
|
|||||||
ansi-regex: 5.0.1
|
ansi-regex: 5.0.1
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/strip-bom-string/1.0.0:
|
||||||
|
resolution: {integrity: sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g==}
|
||||||
|
engines: {node: '>=0.10.0'}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/strip-bom/3.0.0:
|
/strip-bom/3.0.0:
|
||||||
resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==}
|
resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==}
|
||||||
engines: {node: '>=4'}
|
engines: {node: '>=4'}
|
||||||
|
11
website/posts/getting-started-with-spotube.md
Normal file
11
website/posts/getting-started-with-spotube.md
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
---
|
||||||
|
title: Getting Started With Spotube
|
||||||
|
cover_image: https://github.com/KRTirtho/spotube/raw/master/assets/spotube-screenshot.jpg
|
||||||
|
date: "July 16, 2022"
|
||||||
|
author: Kingkor Roy Tirtho
|
||||||
|
author_avatar_url: https://avatars.githubusercontent.com/u/61944859?v=4
|
||||||
|
tags:
|
||||||
|
- getting-started
|
||||||
|
- spotube
|
||||||
|
summary: You installed Spotube, don't know what to do now? Then don't worry we Gotchu covered here. We'll guide you through the basics of using Spotube & how you can use it to enrich your daily life with music.
|
||||||
|
---
|
14
website/posts/improve-search-accuracy.md
Normal file
14
website/posts/improve-search-accuracy.md
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
---
|
||||||
|
title: Improve Search Accuracy
|
||||||
|
cover_image: https://github.com/KRTirtho/spotube/raw/master/assets/spotube_banner.png
|
||||||
|
date: "July 18, 2022"
|
||||||
|
author: Kingkor Roy Tirtho
|
||||||
|
author_avatar_url: https://avatars.githubusercontent.com/u/61944859?v=4
|
||||||
|
tags:
|
||||||
|
- spotify
|
||||||
|
- music
|
||||||
|
- spotube
|
||||||
|
summary: Spotube has matches Spotify Song counterparts in YouTube. So there's a lot chance that sometimes you'll get the wrong song. This is where we'll help you improve the accuracy of your search for specific songs
|
||||||
|
---
|
||||||
|
|
||||||
|
# Improve Search Accuracy
|
Loading…
Reference in New Issue
Block a user