pull/50/head
sunface 4 years ago
parent 74b02b029e
commit c5205e7e98

@ -2,8 +2,6 @@ import {
chakra,
Flex,
Button,
IconButton,
useColorMode,
useColorModeValue,
Box,
useRadioGroup,
@ -14,26 +12,50 @@ import {
DrawerOverlay,
DrawerContent,
Divider,
Heading
Heading,
Tag as ChakraTag,
TagLabel,
TagCloseButton
} from "@chakra-ui/react"
import { useViewportScroll } from "framer-motion"
import NextLink from "next/link"
import React from "react"
import { FaMoon, FaSun } from "react-icons/fa"
import React, { useEffect, useState } from "react"
import Logo, { LogoIcon } from "src/components/logo"
import RadioCard from "components/radio-card"
import { EditMode } from "src/types/editor"
import Card from "components/card"
import TagInput from "components/tag-input"
import { Tag } from "src/types/tag"
import { cloneDeep, remove } from "lodash"
import { requestApi } from "utils/axios/request"
import DarkMode from "components/dark-mode"
function HeaderContent(props: any) {
const { toggleColorMode: toggleMode } = useColorMode()
const text = useColorModeValue("dark", "light")
const SwitchIcon = useColorModeValue(FaMoon, FaSun)
const [tags,setTags]:[Tag[],any] = useState([])
const [allTags,setAllTags] = useState([])
const { isOpen, onOpen, onClose } = useDisclosure()
useEffect(() => {
requestApi.get('/tags').then(res => {
setAllTags(res.data)
const t = []
props.ar.tags?.forEach(id => {
res.data.forEach(tag => {
if (tag.id === id) {
t.push(tag)
}
})
})
setTags(t)
})
},[props.ar])
const editOptions = [EditMode.Edit, EditMode.Preview]
const { getRootProps, getRadioProps } = useRadioGroup({
name: "framework",
@ -43,7 +65,24 @@ function HeaderContent(props: any) {
},
})
const group = getRootProps()
const addTag = t => {
setTags(t)
const ids = []
t.forEach(tag => ids.push(tag.id))
props.ar.tags = ids
}
const removeTag = t => {
const newTags = cloneDeep(tags)
remove(newTags, tag => tag.id === t.id)
setTags(newTags)
const ids = []
newTags.forEach(tag => ids.push(tag.id))
props.ar.tags = ids
}
return (
<>
@ -76,17 +115,7 @@ function HeaderContent(props: any) {
<Box
color={useColorModeValue("gray.500", "gray.400")}
>
<IconButton
size="md"
fontSize="lg"
aria-label={`Switch to ${text} mode`}
variant="ghost"
color="current"
ml={{ base: "0", md: "1" }}
onClick={toggleMode}
_focus={null}
icon={<SwitchIcon />}
/>
<DarkMode />
<Button layerStyle="colorButton" ml="2" onClick={onOpen}></Button>
</Box>
</Flex>
@ -109,7 +138,24 @@ function HeaderContent(props: any) {
<Heading size="xs">
</Heading>
<Input value={props.ar.cover} onChange={(e) => {props.ar.cover = e.target.value; props.onChange()}} mt="4" variant="flushed" size="sm" placeholder="图片链接你可以用github当图片存储服务" focusBorderColor="teal.400"/>
<Input value={props.ar.cover} onChange={(e) => {props.ar.cover = e.target.value; props.onChange()}} mt="4" variant="unstyled" size="sm" placeholder="输入链接可以用github或postimg.cc当图片存储服务.." focusBorderColor="teal.400"/>
</Card>
<Card mt="4">
<Heading size="xs">
</Heading>
<TagInput options={allTags} selected={tags} onChange={addTag}/>
{tags.length > 0&& <Box mt="2">
{
tags.map(tag =>
<ChakraTag key={tag.id} mr="2" colorScheme="teal" variant="solid" px="2" py="1">
<TagLabel>{tag.title}</TagLabel>
<TagCloseButton onClick={ _ => removeTag(tag)}/>
</ChakraTag>)
}
</Box>}
</Card>
</DrawerContent>
</DrawerOverlay>
@ -119,7 +165,6 @@ function HeaderContent(props: any) {
}
function EditorNav(props) {
const bg = useColorModeValue("white", "gray.800")
const ref = React.useRef<HTMLHeadingElement>()
const [y, setY] = React.useState(0)
const { height = 0 } = ref.current?.getBoundingClientRect() ?? {}
@ -137,7 +182,7 @@ function EditorNav(props) {
pos="fixed"
top="0"
zIndex="3"
// bg={bg}
bg={useColorModeValue('white','gray.800')}
left="0"
right="0"
borderTop="4px solid"

@ -1,37 +1,26 @@
import {
chakra,
Flex,
Button,
HStack,
IconButton,
useColorMode,
useColorModeValue,
useDisclosure,
useUpdateEffect,
Menu,
MenuButton,
MenuList,
MenuItem,
MenuDivider,
Image,
Box
} from "@chakra-ui/react"
import siteConfig from "configs/site-config"
import { useViewportScroll } from "framer-motion"
import NextLink from "next/link"
import React from "react"
import { FaMoon, FaSun, FaUserAlt, FaRegSun, FaSignOutAlt,FaStar, FaGithub, FaBookmark, FaEdit } from "react-icons/fa"
import { FaGithub } from "react-icons/fa"
import Logo, { LogoIcon } from "src/components/logo"
import { MobileNavButton, MobileNavContent } from "../mobile-nav"
import { MobileNavButton, MobileNavContent } from "./mobile-nav"
import AlgoliaSearch from "src/components/search/algolia-search"
import useSession from "hooks/use-session"
import { Session } from "src/types/session"
import { useRouter } from "next/router"
import storage from "utils/localStorage"
import { logout } from "utils/session"
import { isAdmin, isEditor } from "utils/role"
import { ReserveUrls } from "src/data/reserve-urls"
import Link from "next/link"
import DarkMode from "components/dark-mode"
import AccountMenu from "components/account-menu"
const navLinks = [{
title: '主页',
@ -53,22 +42,13 @@ function HeaderContent() {
const { asPath } = router
const mobileNav = useDisclosure()
const session: Session = useSession()
const { toggleColorMode: toggleMode } = useColorMode()
const text = useColorModeValue("dark", "light")
const SwitchIcon = useColorModeValue(FaMoon, FaSun)
const mobileNavBtnRef = React.useRef<HTMLButtonElement>()
useUpdateEffect(() => {
mobileNavBtnRef.current?.focus()
}, [mobileNav.isOpen])
const login = () => {
console.log(router)
storage.set("current-page", asPath)
router.push(ReserveUrls.Login)
}
return (
<>
@ -86,92 +66,39 @@ function HeaderContent() {
</NextLink>
<HStack display={{ base: "none", md: "flex" }} ml={{ base: 1, md: 4, lg: 12 }} fontSize="1rem" minWidth="250px">
{navLinks.map(link => <Box px="4" py="0.7rem" rounded="md" key={link.url} color={useColorModeValue("gray.700", "whiteAlpha.900")} aria-current={asPath === link.url ? "page" : undefined} _activeLink={{ bg: useColorModeValue("transparent", "rgba(48, 140, 122, 0.3)"), color: useColorModeValue("teal.500", "teal.200"), fontWeight: "bold", }} ><Link href={link.url}>{link.title}</Link></Box>)}
{navLinks.map(link => <Box px="4" py="0.7rem" rounded="md" key={link.url} color={useColorModeValue("gray.700", "whiteAlpha.900")} aria-current={asPath === link.url ? "page" : undefined} _activeLink={{ bg: useColorModeValue("transparent", "rgba(48, 140, 122, 0.3)"), color: useColorModeValue("teal.500", "teal.200"), fontWeight: "bold", }} ><Link href={link.url}>{link.title}</Link></Box>)}
</HStack>
</Flex>
<Flex
<HStack
w="100%"
maxW="600px"
align="center"
color={useColorModeValue("gray.500","gray.400")}
color={useColorModeValue("gray.500", "gray.400")}
>
<AlgoliaSearch />
<HStack spacing="5" display={{ base: "none", md: "flex" }}>
<Link
aria-label="Go to Chakra UI GitHub page"
href={siteConfig.repo.url}
>
<IconButton
size="md"
fontSize="lg"
aria-label={`Switch to ${text} mode`}
variant="ghost"
color="current"
ml={{ base: "0", md: "3" }}
_focus={null}
icon={<FaGithub />}
/>
</Link>
</HStack>
<IconButton
size="md"
fontSize="lg"
aria-label={`Switch to ${text} mode`}
variant="ghost"
color="current"
ml={{ base: "0", md: "1" }}
onClick={toggleMode}
_focus={null}
icon={<SwitchIcon />}
/>
{session ?
<Menu>
<MenuButton
as={IconButton}
bg="transparent"
_focus={null}
icon={session.user.avatar !== '' ? <Image
boxSize="2.8em"
borderRadius="full"
src="https://placekitten.com/100/100"
alt="user"
/> :
<FaUserAlt />
}
aria-label="Options"
ml={{ base: "0", md: "2" }}
/>
<MenuList>
<MenuItem icon={<FaUserAlt fontSize="16" />}>
<span>Sunface</span>
</MenuItem>
<MenuDivider />
{isEditor(session.user.role) && <Link href={`${ReserveUrls.Editor}/posts`}><MenuItem icon={<FaEdit fontSize="16" />} ></MenuItem></Link>}
{isAdmin(session.user.role) && <Link href={`${ReserveUrls.Admin}/tags`}><MenuItem icon={<FaStar fontSize="16" />} ></MenuItem></Link>}
<MenuItem icon={<FaBookmark fontSize="16" />}></MenuItem>
<MenuDivider />
<MenuItem icon={<FaRegSun fontSize="16" />}></MenuItem>
<MenuItem onClick={() => logout()} icon={<FaSignOutAlt fontSize="16" />}></MenuItem>
</MenuList>
</Menu> :
<Button
as="a"
ml="2"
colorScheme="teal"
fontSize=".8rem"
onClick={() => login()}
// leftIcon={<FaUserAlt />}
>
SIGN IN
</Button>
}
<Link
aria-label="Go to Chakra UI GitHub page"
href={siteConfig.repo.url}
>
<IconButton
size="md"
fontSize="lg"
aria-label="go to github"
variant="ghost"
color="current"
_focus={null}
icon={<FaGithub />}
/>
</Link>
<DarkMode />
<AccountMenu />
<MobileNavButton
ref={mobileNavBtnRef}
aria-label="Open Menu"
onClick={mobileNav.onOpen}
/>
</Flex>
</HStack>
</Flex>
<MobileNavContent isOpen={mobileNav.isOpen} onClose={mobileNav.onClose} />
</>
@ -201,6 +128,7 @@ function Header(props) {
borderTop="4px solid"
borderTopColor="teal.400"
width="full"
bg={useColorModeValue('white', 'gray.800')}
{...props}
>
<chakra.div height="4.5rem" mx="auto" maxW="1200px">

@ -0,0 +1,127 @@
import {
chakra,
Flex,
HStack,
IconButton,
useColorModeValue,
useDisclosure,
useUpdateEffect,
Heading,
Button,
Divider,
Text
} from "@chakra-ui/react"
import { useViewportScroll } from "framer-motion"
import React from "react"
import { SearchIcon } from "@chakra-ui/icons"
import DarkMode from "components/dark-mode"
import AccountMenu from "components/account-menu"
import { FaGithub, FaTwitter, FaUserPlus } from "react-icons/fa"
function HeaderContent() {
const mobileNav = useDisclosure()
const mobileNavBtnRef = React.useRef<HTMLButtonElement>()
useUpdateEffect(() => {
mobileNavBtnRef.current?.focus()
}, [mobileNav.isOpen])
return (
<>
<Flex w="100%" h="100%" align="center" justify="space-between" px={{ base: "4", md: "6" }}>
<HStack spacing="2">
<Heading size="md">Sunface</Heading>
<Button colorScheme="teal" variant="outline" leftIcon={<FaUserPlus />}>Follow</Button>
</HStack>
<HStack
color={useColorModeValue("gray.500", "gray.400")}
spacing="2"
>
<IconButton
size="md"
fontSize="lg"
variant="ghost"
color="current"
_focus={null}
onClick={() => alert('search in this blog')}
icon={<SearchIcon />}
aria-label="search in this blog"
/>
<DarkMode />
<AccountMenu />
</HStack>
</Flex>
<Flex w="100%" align="center" justify="space-between" px={{ base: "6", md: "10" }} mt="2">
<HStack spacing="4">
<Text fontSize="1.1rem" fontWeight="600">Home</Text>
<Text fontSize="1.1rem">Badges</Text>
</HStack>
<HStack
color={useColorModeValue("gray.500", "gray.400")}
spacing="2"
>
<IconButton
size="md"
fontSize="1.2rem"
aria-label="go to github"
variant="ghost"
color="current"
_focus={null}
icon={<FaGithub />}
/>
<IconButton
size="md"
fontSize="1.2rem"
aria-label="go to twitter"
variant="ghost"
color="current"
_focus={null}
icon={<FaTwitter />}
/>
</HStack>
</Flex>
<Divider mt="2"/>
</>
)
}
function PostNav(props) {
const ref = React.useRef<HTMLHeadingElement>()
const [y, setY] = React.useState(0)
const { height = 0 } = ref.current?.getBoundingClientRect() ?? {}
const { scrollY } = useViewportScroll()
React.useEffect(() => {
return scrollY.onChange(() => setY(scrollY.get()))
}, [scrollY])
return (
<chakra.header
ref={ref}
shadow={y > height ? "sm" : undefined}
transition="box-shadow 0.2s"
top="0"
zIndex="3"
left="0"
right="0"
borderTop="4px solid"
borderTopColor="teal.400"
width="full"
bg={useColorModeValue('white', 'gray.800')}
{...props}
>
<chakra.div height="4.5rem" mx="auto" maxW="1200px">
<HeaderContent />
</chakra.div>
</chakra.header>
)
}
export default PostNav

@ -1,4 +1,4 @@
import { Badge, Box, chakra } from "@chakra-ui/react"
import { Badge, Box, chakra,PropsOf } from "@chakra-ui/react"
import { SkipNavContent, SkipNavLink } from "@chakra-ui/skip-nav"
import Container from "components/container"
import Footer from "./footer"
@ -24,13 +24,14 @@ function useHeadingFocusOnRouteChange() {
}, [])
}
interface PageContainerProps {
type PageContainerProps = PropsOf<typeof chakra.div> & {
children: React.ReactNode
nav?: any
}
function PageContainer(props: PageContainerProps) {
const { children ,nav} = props
const { children ,nav, ...rest} = props
useHeadingFocusOnRouteChange()
return (
@ -48,9 +49,10 @@ function PageContainer(props: PageContainerProps) {
<Box
id="content"
pt={3}
px={{base:0,md:3}}
mt="4.5rem"
px={[0,0,2,3]}
mt={props.mt ?? "4.5rem"}
mx="auto"
{...rest}
>
<PageTransition>
{children}

@ -1,26 +1,136 @@
import { chakra } from "@chakra-ui/react"
import { Box, chakra, Divider, Flex, Heading, HStack, IconButton, Image, VStack } from "@chakra-ui/react"
import Container from "components/container"
import LikeButton from "components/like-button"
import { MarkdownRender } from "components/markdown-editor/render"
import PostAuthor from "components/posts/post-author"
import SEO from "components/seo"
import siteConfig from "configs/site-config"
import Nav from "layouts/nav/nav"
import PostNav from "layouts/nav/post-nav"
import PageContainer from "layouts/page-container"
import { cloneDeep } from "lodash"
import { useRouter } from "next/router"
import React from "react"
import { title } from "process"
import React, { useEffect, useState } from "react"
import { FaBookmark, FaGithub, FaRegBookmark, FaShare, FaShareAlt } from "react-icons/fa"
import { Post } from "src/types/posts"
import { requestApi } from "utils/axios/request"
const UserPage = () => {
const PostPage = () => {
const router = useRouter()
const slug = router.query.post_slug
const [post, setPost]: [Post, any] = useState(null)
useEffect(() => {
if (slug) {
requestApi.get(`/post/${slug}`).then(res => setPost(res.data))
}
}, [slug])
const onLike = async () => {
await requestApi.post(`/post/like/${post.id}`)
const p = cloneDeep(post)
if (post.liked) {
p.likes += -1
p.liked = false
} else {
p.likes += 1
p.liked = true
}
setPost(p)
}
return (
<>
<SEO
title={siteConfig.seo.title}
description={siteConfig.seo.description}
/>
<Nav />
<PageContainer>
<chakra.h1>{router.query.username}{router.query.post_slug}</chakra.h1>
</PageContainer>
</>
)}
export default UserPage
<>
<SEO
title={siteConfig.seo.title}
description={siteConfig.seo.description}
/>
<PageContainer nav={<PostNav />} mt="2rem">
{post &&
<>
<HStack alignItems="top" spacing={[0, 0, 14, 14]}>
<Box width={["100%", "100%", "75%", "75%"]} height="fit-content">
<Image src={post.cover} />
<Box px="2">
<Heading size="lg" my="6">{post.title}</Heading>
<Divider my="4" />
<PostAuthor post={post} />
<Divider my="4" />
<MarkdownRender md={post.md} py="2" mt="6" />
</Box>
</Box>
<Box>
<VStack alignItems="left" pos="fixed" display={{ base: "none", md: 'flex' }} width={["100%", "100%", "25%", "25%"]}>
<Box pt="16">
{/* <HStack mt="16"> */}
{/* <LikeButton type="like" count={post.likes} onClick={onLike} /> */}
<LikeButton type="unicorn" count={post.likes} onClick={onLike} liked={post.liked}/>
{/* </HStack> */}
</Box>
<Box>
<IconButton
mt="6"
aria-label="go to github"
variant="ghost"
layerStyle="textSecondary"
_focus={null}
fontSize="1.7rem"
fontWeight="300"
icon={<svg height="1.7rem" fill="currentColor" viewBox="0 0 384 512"><path d="M336 0H48C21.49 0 0 21.49 0 48v464l192-112 192 112V48c0-26.51-21.49-48-48-48zm16 456.287l-160-93.333-160 93.333V48c0-8.822 7.178-16 16-16h288c8.822 0 16 7.178 16 16v408.287z"></path></svg>}
/>
<Box mt="4">
<IconButton
aria-label="go to github"
variant="ghost"
layerStyle="textSecondary"
_focus={null}
fontWeight="300"
icon={<svg height="1.7rem" fill="currentColor" viewBox="0 0 448 512"><path d="M352 320c-28.6 0-54.2 12.5-71.8 32.3l-95.5-59.7c9.6-23.4 9.7-49.8 0-73.2l95.5-59.7c17.6 19.8 43.2 32.3 71.8 32.3 53 0 96-43 96-96S405 0 352 0s-96 43-96 96c0 13 2.6 25.3 7.2 36.6l-95.5 59.7C150.2 172.5 124.6 160 96 160c-53 0-96 43-96 96s43 96 96 96c28.6 0 54.2-12.5 71.8-32.3l95.5 59.7c-4.7 11.3-7.2 23.6-7.2 36.6 0 53 43 96 96 96s96-43 96-96c-.1-53-43.1-96-96.1-96zm0-288c35.3 0 64 28.7 64 64s-28.7 64-64 64-64-28.7-64-64 28.7-64 64-64zM96 320c-35.3 0-64-28.7-64-64s28.7-64 64-64 64 28.7 64 64-28.7 64-64 64zm256 160c-35.3 0-64-28.7-64-64s28.7-64 64-64 64 28.7 64 64-28.7 64-64 64z"></path></svg>}
/>
</Box>
</Box>
</VStack>
</Box>
</HStack>
<HStack display={{ base: "flex", md: 'none' }} spacing="4" justifyContent="center">
<Box>
{/* <LikeButton type="like" count={post.likes} onClick={onLike}/> */}
<LikeButton type="unicorn" count={post.likes} onClick={onLike} liked={post.liked}/>
</Box>
<Box>
<IconButton
aria-label="go to github"
variant="ghost"
layerStyle="textSecondary"
_focus={null}
fontSize="1.7rem"
fontWeight="300"
icon={<svg height="1.8rem" fill="currentColor" viewBox="0 0 384 512"><path d="M336 0H48C21.49 0 0 21.49 0 48v464l192-112 192 112V48c0-26.51-21.49-48-48-48zm16 456.287l-160-93.333-160 93.333V48c0-8.822 7.178-16 16-16h288c8.822 0 16 7.178 16 16v408.287z"></path></svg>}
/>
<IconButton
aria-label="go to github"
variant="ghost"
layerStyle="textSecondary"
_focus={null}
fontWeight="300"
icon={<svg height="1.8rem" fill="currentColor" viewBox="0 0 448 512"><path d="M352 320c-28.6 0-54.2 12.5-71.8 32.3l-95.5-59.7c9.6-23.4 9.7-49.8 0-73.2l95.5-59.7c17.6 19.8 43.2 32.3 71.8 32.3 53 0 96-43 96-96S405 0 352 0s-96 43-96 96c0 13 2.6 25.3 7.2 36.6l-95.5 59.7C150.2 172.5 124.6 160 96 160c-53 0-96 43-96 96s43 96 96 96c28.6 0 54.2-12.5 71.8-32.3l95.5 59.7c-4.7 11.3-7.2 23.6-7.2 36.6 0 53 43 96 96 96s96-43 96-96c-.1-53-43.1-96-96.1-96zm0-288c35.3 0 64 28.7 64 64s-28.7 64-64 64-64-28.7-64-64 28.7-64 64-64zM96 320c-35.3 0-64-28.7-64-64s28.7-64 64-64 64 28.7 64 64-28.7 64-64 64zm256 160c-35.3 0-64-28.7-64-64s28.7-64 64-64 64 28.7 64 64-28.7 64-64 64z"></path></svg>}
/>
</Box>
</HStack>
</>
}
</PageContainer>
</>
)
}
export default PostPage

@ -15,8 +15,7 @@ const UserPage = () => {
title={siteConfig.seo.title}
description={siteConfig.seo.description}
/>
<Nav />
<PageContainer>
<PageContainer mt="6rem">
<chakra.h1>{router.query.username}'s home</chakra.h1>
</PageContainer>
</>

@ -6,7 +6,7 @@ import Sidebar from "layouts/sidebar/sidebar"
import React, { useEffect, useState } from "react"
import {adminLinks} from "src/data/links"
import { requestApi } from "utils/axios/request"
import TagCard from "components/posts/tag-card"
import TagCard from "components/posts/tag-edit-card"
import { Post } from "src/types/posts"
import { useRouter } from "next/router"
import Link from "next/link"
@ -44,7 +44,6 @@ const PostsPage = () => {
return (
<>
<Nav />
<PageContainer>
<Box display="flex">
<Sidebar routes={adminLinks} width="250px" height="fit-content" />

@ -1,4 +1,4 @@
import { Box, Button, useToast} from '@chakra-ui/react';
import { Box, Button, useToast } from '@chakra-ui/react';
import React, { useEffect, useState } from 'react';
import { MarkdownEditor } from 'components/markdown-editor/editor';
import PageContainer from 'layouts/page-container';
@ -18,19 +18,19 @@ const content = `
function PostEditPage() {
const router = useRouter()
const {id} = router.query
const { id } = router.query
const [editMode, setEditMode] = useState(EditMode.Edit)
const [ar,setAr] = useState({
const [ar, setAr] = useState({
md: content,
title: ''
})
const toast = useToast()
useEffect(() => {
if (id && id !== 'new') {
requestApi.get(`/editor/post/${id}`).then(res => setAr(res.data))
}
},[id])
}, [id])
const onMdChange = newMd => {
setAr({
@ -38,7 +38,7 @@ function PostEditPage() {
md: newMd
})
}
const onChange = () => {
setAr(cloneDeep(ar))
}
@ -50,20 +50,20 @@ function PostEditPage() {
status: "error",
duration: 2000,
isClosable: true,
})
return
})
return
}
setAr({...ar, title: title})
setAr({ ...ar, title: title })
}
const publish = async () => {
const res = await requestApi.post(`/editor/post`, ar)
toast({
description: "发布成功",
status: "success",
duration: 2000,
isClosable: true,
description: "发布成功",
status: "success",
duration: 2000,
isClosable: true,
})
router.push(`/${res.data.username}/${res.data.slug}`)
}
@ -78,17 +78,18 @@ function PostEditPage() {
publish={() => publish()}
/>}
>
<Card style={{ height: 'calc(100vh - 145px)' }}>
{editMode === EditMode.Edit ?
{editMode === EditMode.Edit ?
<Card style={{ height: 'calc(100vh - 145px)' }}>
<MarkdownEditor
onChange={(md) => onMdChange(md)}
md={ar.md}
/> :
/></Card> :
<Card>
<Box height="100%" p="6">
<MarkdownRender md={ar.md} />
</Box>
}
</Card>
</Card>
}
</PageContainer>
);
}

@ -97,7 +97,6 @@ const PostsPage = () => {
return (
<>
<Nav />
<PageContainer>
<Box display="flex">
<Sidebar routes={editorLinks} width="250px" height="fit-content" />

@ -12,7 +12,6 @@ const HomePage = () => (
title={siteConfig.seo.title}
description={siteConfig.seo.description}
/>
<Nav />
<PageContainer>
<Card width="200px">
<chakra.h1>NOT FOUND</chakra.h1>

@ -1,23 +0,0 @@
import { chakra } from "@chakra-ui/react"
import Container from "components/container"
import SEO from "components/seo"
import siteConfig from "configs/site-config"
import Nav from "layouts/nav/nav"
import PageContainer from "layouts/page-container"
import React from "react"
const PostPage = () => (
<>
<SEO
title={siteConfig.seo.title}
description={siteConfig.seo.description}
/>
<Nav />
<PageContainer>
<chakra.h1>Post</chakra.h1>
</PageContainer>
</>
)
export default PostPage

@ -36,7 +36,6 @@ const UserPage = () => {
title={siteConfig.seo.title}
description={siteConfig.seo.description}
/>
<Nav />
<PageContainer>
{tag.name && <HStack alignItems="top" spacing="4">
<Box width="70%">
@ -65,7 +64,7 @@ const UserPage = () => {
</Box>
<Box>
<Heading size="lg">13.4K</Heading>
<Heading size="lg">{tag.postCount}</Heading>
<Text layerStyle="textSecondary" fontWeight="500" fontSize="1.2rem" mt="1" ml="1">Posts</Text>
</Box>
</Flex>

@ -1,7 +1,6 @@
package api
import (
"fmt"
"net/http"
"strconv"
@ -63,7 +62,6 @@ func DeletePost(c *gin.Context) {
func GetEditorPost(c *gin.Context) {
id, _ := strconv.ParseInt(c.Param("id"), 10, 64)
fmt.Println(c.Param("id"))
if id == 0 {
c.JSON(http.StatusBadRequest, common.RespError(e.ParamInvalid))
return
@ -81,7 +79,7 @@ func GetEditorPost(c *gin.Context) {
return
}
ar, err := posts.GetPost(id)
ar, err := posts.GetPost(id, "")
if err != nil {
c.JSON(err.Status, common.RespError(err.Message))
return

@ -0,0 +1,49 @@
package api
import (
"net/http"
"strconv"
"github.com/gin-gonic/gin"
"github.com/imdotdev/im.dev/server/internal/posts"
"github.com/imdotdev/im.dev/server/internal/session"
"github.com/imdotdev/im.dev/server/pkg/common"
"github.com/imdotdev/im.dev/server/pkg/e"
)
func GetPost(c *gin.Context) {
slug := c.Param("slug")
ar, err := posts.GetPost(0, slug)
if err != nil {
c.JSON(err.Status, common.RespError(err.Message))
return
}
user := session.CurrentUser(c)
if user == nil {
ar.Liked = false
} else {
ar.Liked = posts.GetLiked(ar.ID, user.ID)
}
c.JSON(http.StatusOK, common.RespSuccess(ar))
}
func LikePost(c *gin.Context) {
user := session.CurrentUser(c)
id, _ := strconv.ParseInt(c.Param("id"), 10, 64)
if id == 0 {
c.JSON(http.StatusBadRequest, common.RespError(e.ParamInvalid))
return
}
err := posts.Like(id, user.ID)
if err != nil {
c.JSON(err.Status, common.RespError(err.Message))
return
}
c.JSON(http.StatusOK, common.RespSuccess(nil))
}

@ -0,0 +1,53 @@
package posts
import (
"database/sql"
"net/http"
"github.com/imdotdev/im.dev/server/pkg/db"
"github.com/imdotdev/im.dev/server/pkg/e"
)
func Like(postId int64, userId int64) *e.Error {
// 判断文章是否存在
exist := postExist(postId)
if !exist {
return e.New(http.StatusNotFound, e.NotFound)
}
// 查询当前like状态
liked := GetLiked(postId, userId)
if liked {
// 已经喜欢过该篇文章,更改为不喜欢
_, err := db.Conn.Exec("DELETE FROM post_like WHERE post_id=? and user_id=?", postId, userId)
if err != nil {
return e.New(http.StatusInternalServerError, e.Internal)
}
db.Conn.Exec("UPDATE posts SET like_count=like_count-1 WHERE id=?", postId)
} else {
_, err := db.Conn.Exec("INSERT INTO post_like (post_id,user_id) VALUES (?,?)", postId, userId)
if err != nil {
return e.New(http.StatusInternalServerError, e.Internal)
}
db.Conn.Exec("UPDATE posts SET like_count=like_count+1 WHERE id=?", postId)
}
return nil
}
func GetLiked(postID, userID int64) bool {
liked := false
var nid int64
err := db.Conn.QueryRow("SELECT post_id FROM post_like WHERE post_id=? and user_id=?", postID, userID).Scan(&nid)
if err != nil && err != sql.ErrNoRows {
logger.Warn("query post like error", "error", err)
return false
}
if nid != 0 {
liked = true
}
return liked
}

@ -97,14 +97,17 @@ func SubmitPost(c *gin.Context) (map[string]string, *e.Error) {
md := utils.Compress(post.Md)
setSlug(user.ID, post)
if post.ID == 0 {
//create
_, err = db.Conn.Exec("INSERT INTO posts (creator,slug, title, md, url, cover, brief, created, updated) VALUES(?,?,?,?,?,?,?,?,?)",
res, err := db.Conn.Exec("INSERT INTO posts (creator,slug, title, md, url, cover, brief, created, updated) VALUES(?,?,?,?,?,?,?,?,?)",
user.ID, post.Slug, post.Title, md, post.URL, post.Cover, post.Brief, now, now)
if err != nil {
logger.Warn("submit post error", "error", err)
return nil, e.New(http.StatusInternalServerError, e.Internal)
}
post.ID, _ = res.LastInsertId()
} else {
// 只有创建者自己才能更新内容
creator, _ := GetPostCreator(post.ID)
@ -120,6 +123,23 @@ func SubmitPost(c *gin.Context) (map[string]string, *e.Error) {
}
}
//update tags
// "tag_post": `CREATE TABLE IF NOT EXISTS tag_post (
// tag_id INTEGER,
// post_id INTEGER
// );
_, err = db.Conn.Exec("DELETE FROM tag_post WHERE post_id=?", post.ID)
if err != nil {
logger.Warn("delete post tags error", "error", err)
}
for _, tag := range post.Tags {
_, err = db.Conn.Exec("INSERT INTO tag_post (tag_id,post_id) VALUES (?,?)", tag, post.ID)
if err != nil {
logger.Warn("add post tag error", "error", err)
}
}
return map[string]string{
"username": user.Username,
"slug": post.Slug,
@ -136,11 +156,11 @@ func DeletePost(id int64) *e.Error {
return nil
}
func GetPost(id int64) (*models.Post, *e.Error) {
func GetPost(id int64, slug string) (*models.Post, *e.Error) {
ar := &models.Post{}
var rawmd []byte
err := db.Conn.QueryRow("select id,slug,title,md,url,cover,brief,creator,created,updated from posts where id=?", id).Scan(
&ar.ID, &ar.Slug, &ar.Title, &rawmd, &ar.URL, &ar.Cover, &ar.Brief, &ar.CreatorID, &ar.Created, &ar.Updated,
err := db.Conn.QueryRow("select id,slug,title,md,url,cover,brief,creator,like_count,created,updated from posts where id=? or slug=?", id, slug).Scan(
&ar.ID, &ar.Slug, &ar.Title, &rawmd, &ar.URL, &ar.Cover, &ar.Brief, &ar.CreatorID, &ar.Likes, &ar.Created, &ar.Updated,
)
if err != nil {
if err == sql.ErrNoRows {
@ -155,6 +175,19 @@ func GetPost(id int64) (*models.Post, *e.Error) {
ar.Creator = &models.UserSimple{ID: ar.CreatorID}
err = ar.Creator.Query()
// get tags
tags := make([]int64, 0)
rows, err := db.Conn.Query("SELECT tag_id FROM tag_post WHERE post_id=?", id)
if err != nil && err != sql.ErrNoRows {
return nil, e.New(http.StatusInternalServerError, e.Internal)
}
for rows.Next() {
var tag int64
err = rows.Scan(&tag)
tags = append(tags, tag)
}
ar.Tags = tags
return ar, nil
}
@ -172,6 +205,21 @@ func GetPostCreator(id int64) (int64, *e.Error) {
return uid, nil
}
func postExist(id int64) bool {
var nid int64
err := db.Conn.QueryRow("SELECT id from posts WHERE id=?", id).Scan(&nid)
if err != nil {
logger.Warn("query post error", "error", err)
return false
}
if nid == 0 {
return false
}
return true
}
//slug有三个规则
// 1. 长度不能超过127
// 2. 每次title更新都要重新生成slug
@ -189,7 +237,6 @@ func setSlug(creator int64, post *models.Post) error {
return err
}
fmt.Println(count)
if count == 0 {
post.Slug = slug
} else {

@ -84,6 +84,8 @@ func GetTags() (models.Tags, *e.Error) {
}
tags = append(tags, tag)
db.Conn.QueryRow("SELECT count(*) FROM tag_post WHERE tag_id=?", tag.ID).Scan(&tag.PostCount)
}
sort.Sort(tags)
@ -118,5 +120,7 @@ func GetTag(name string) (*models.Tag, *e.Error) {
md, _ := utils.Uncompress(rawmd)
tag.Md = string(md)
db.Conn.QueryRow("SELECT count(*) FROM tag_post WHERE tag_id=?", tag.ID).Scan(&tag.PostCount)
return tag, nil
}

@ -45,6 +45,13 @@ func (s *Server) Start() error {
r.POST("/login", session.Login)
r.POST("/logout", session.Logout)
r.GET("/uiconfig", GetUIConfig)
}
postR := r.Group("/post")
{
postR.GET("/:slug", api.GetPost)
postR.POST("/like/:id", api.LikePost, IsLogin())
}
// login apis

@ -37,7 +37,7 @@ var sqlTables = map[string]string{
url VARCHAR(255),
cover VARCHAR(255),
brief TEXT,
like_count INTEGER DEFAULT 0,
created DATETIME NOT NULL,
updated DATETIME
);
@ -49,6 +49,16 @@ var sqlTables = map[string]string{
ON posts (creator, slug);
`,
"post_like": `CREATE TABLE IF NOT EXISTS post_like (
post_id INTEGER,
user_id INTEGER
);
CREATE INDEX IF NOT EXISTS post_like_postid
ON post_like (post_id);
CREATE INDEX IF NOT EXISTS post_like_userid
ON post_like (user_id);
`,
"tags": `CREATE TABLE IF NOT EXISTS tags (
id INTEGER PRIMARY KEY AUTOINCREMENT,
creator INTEGER NOT NULL,
@ -57,7 +67,7 @@ var sqlTables = map[string]string{
icon VARCHAR(255),
cover VARCHAR(255),
md TEXT,
follower_count INTEGER DEFAULT 0,
created DATETIME NOT NULL,
updated DATETIME
);
@ -66,4 +76,14 @@ var sqlTables = map[string]string{
CREATE INDEX IF NOT EXISTS tags_created
ON tags (created);
`,
"tag_post": `CREATE TABLE IF NOT EXISTS tag_post (
tag_id INTEGER,
post_id INTEGER
);
CREATE INDEX IF NOT EXISTS tag_post_tagid
ON tag_post (tag_id);
CREATE INDEX IF NOT EXISTS tag_post_postid
ON tag_post (post_id);
`,
}

@ -9,21 +9,24 @@ import (
)
type UIConfig struct {
Posts *UIPosts `json:"posts"`
Posts *PostsConfig `json:"posts"`
}
type UIPosts struct {
type PostsConfig struct {
TitleMaxLen int `json:"titleMaxLen"`
BriefMaxLen int `json:"briefMaxLen"`
WritingEnabled bool `json:"writingEnabled"`
MaxTags int `json:"maxTags"`
}
// 在后台页面配置存储到mysql中
func GetUIConfig(c *gin.Context) {
conf := &UIConfig{
Posts: &UIPosts{
Posts: &PostsConfig{
TitleMaxLen: config.Data.Posts.TitleMaxLen,
BriefMaxLen: config.Data.Posts.BriefMaxLen,
WritingEnabled: config.Data.Posts.WritingEnabled,
MaxTags: 2,
},
}

@ -3,17 +3,21 @@ package models
import "time"
type Post struct {
ID int64 `json:"id"`
Creator *UserSimple `json:"creator"`
CreatorID int64 `json:"creatorId"`
Title string `json:"title"`
Slug string `json:"slug"`
Md string `json:"md"`
URL string `json:"url"`
Cover string `json:"cover"`
Brief string `json:"brief"`
Created time.Time `json:"created"`
Updated time.Time `json:"updated"`
ID int64 `json:"id"`
Creator *UserSimple `json:"creator"`
CreatorID int64 `json:"creatorId"`
Title string `json:"title"`
Slug string `json:"slug"`
Md string `json:"md"`
URL string `json:"url"`
Cover string `json:"cover"`
Brief string `json:"brief"`
Tags []int64 `json:"tags"`
Likes int `json:"likes"`
Liked bool `json:"liked"`
Recommands int `json:"recommands"`
Created time.Time `json:"created"`
Updated time.Time `json:"updated"`
}
type Posts []*Post

@ -3,15 +3,16 @@ package models
import "time"
type Tag struct {
ID int64 `json:"id"`
Creator int64 `json:"creator"`
Title string `json:"title"`
Name string `json:"name"`
Md string `json:"md"`
Cover string `json:"cover"`
Icon string `json:"icon"`
Created time.Time `json:"created"`
Updated time.Time `json:"updated"`
ID int64 `json:"id"`
Creator int64 `json:"creator"`
Title string `json:"title"`
Name string `json:"name"`
Md string `json:"md"`
Cover string `json:"cover"`
Icon string `json:"icon"`
PostCount int `json:"postCount"`
Created time.Time `json:"created"`
Updated time.Time `json:"updated"`
}
type Tags []*Tag

@ -0,0 +1,78 @@
import React from "react"
import {
IconButton,
Menu,
MenuButton,
MenuList,
MenuItem,
MenuDivider,
Image,
Button
} from "@chakra-ui/react"
import useSession from "hooks/use-session"
import { Session } from "src/types/session"
import { useRouter } from "next/router"
import storage from "utils/localStorage"
import { ReserveUrls } from "src/data/reserve-urls"
import { FaRegSun, FaUserAlt ,FaBookmark, FaSignOutAlt,FaEdit,FaStar} from "react-icons/fa"
import { isAdmin, isEditor } from "utils/role"
import { logout } from "utils/session"
import Link from "next/link"
export const AccountMenu = () => {
const session: Session = useSession()
const router = useRouter()
const login = () => {
console.log(router)
storage.set("current-page", router.asPath)
router.push(ReserveUrls.Login)
}
return (
<>
{session ?
<Menu>
<MenuButton
as={IconButton}
bg="transparent"
_focus={null}
icon={session.user.avatar !== '' ? <Image
boxSize="2.8em"
borderRadius="full"
src="https://placekitten.com/100/100"
alt="user"
/> :
<FaUserAlt />
}
aria-label="Options"
ml={{ base: "0", md: "2" }}
/>
<MenuList>
<MenuItem icon={<FaUserAlt fontSize="16" />}>
<span>Sunface</span>
</MenuItem>
<MenuDivider />
{isEditor(session.user.role) && <Link href={`${ReserveUrls.Editor}/posts`}><MenuItem icon={<FaEdit fontSize="16" />} ></MenuItem></Link>}
{isAdmin(session.user.role) && <Link href={`${ReserveUrls.Admin}/tags`}><MenuItem icon={<FaStar fontSize="16" />} ></MenuItem></Link>}
<MenuItem icon={<FaBookmark fontSize="16" />}></MenuItem>
<MenuDivider />
<MenuItem icon={<FaRegSun fontSize="16" />}></MenuItem>
<MenuItem onClick={() => logout()} icon={<FaSignOutAlt fontSize="16" />}></MenuItem>
</MenuList>
</Menu> :
<Button
as="a"
ml="2"
colorScheme="teal"
fontSize=".8rem"
onClick={() => login()}
>
SIGN IN
</Button>
}
</>
)
}
export default AccountMenu

@ -8,7 +8,7 @@ export const Container = (props: BoxProps) => (
pt="3"
maxW="1200px"
mx="auto"
px={{ base: "4", md: "8" }}
px={[0,0,4,8]}
{...props}
/>
)

@ -0,0 +1,24 @@
import React from "react"
import { IconButton, useColorMode, useColorModeValue } from "@chakra-ui/react"
import { FaMoon, FaSun } from "react-icons/fa"
export const DarkMode = () => {
const { toggleColorMode: toggleMode } = useColorMode()
const text = useColorModeValue("dark", "light")
const SwitchIcon = useColorModeValue(FaMoon, FaSun)
return (
<IconButton
size="md"
fontSize="lg"
aria-label={`Switch to ${text} mode`}
variant="ghost"
color="current"
onClick={toggleMode}
_focus={null}
icon={<SwitchIcon />}
/>
)
}
export default DarkMode

@ -0,0 +1,41 @@
import { chakra, HStack, IconButton, Image, Tooltip, useColorMode, useColorModeValue } from "@chakra-ui/react";
interface Props {
type: string
count: number
onClick: any
liked: boolean
}
const LikeButton = (props: Props) => {
let imgSrc: string
let label: string
switch (props.type) {
case "like":
imgSrc = "https://cdn.hashnode.com/res/hashnode/image/upload/v1594643814744/9iXxz71TL.png?auto=compress"
label = "Love it"
break;
case "unicorn":
imgSrc = "https://cdn.hashnode.com/res/hashnode/image/upload/v1594643772437/FYDU5k2kQ.png?auto=compress"
label = "I love it"
default:
break;
}
return (
<HStack>
<Tooltip label={label} size="sm">
<IconButton
aria-label="go to github"
variant="ghost"
color="current"
_focus={null}
icon={<Image width="38px" src={imgSrc} />}
onClick={props.onClick}
border={props.liked ? `1px solid ${useColorModeValue('gray','pink')}` : null}
/>
</Tooltip>
<chakra.span layerStyle="textSecondary" fontWeight="600" marginBottom="-3px">{props.count}</chakra.span>
</HStack>
)
}
export default LikeButton

@ -25,9 +25,10 @@ export function MarkdownEditor(props) {
return (
<MdEditor
height="100%"
width="100%"
value={props.md}
style={{ height: "102%" }}
style={{ height: "102%"}}
renderHTML={_ => null}
onChange={handleEditorChange}
config={{

@ -11,6 +11,7 @@ type Props = PropsOf<typeof chakra.div> & {
fontSize?: string
}
const ChakraMarkdown = chakra(Markdown)
export function MarkdownRender({ md,fontSize, ...rest }:Props) {
const rootRef = useRef<HTMLDivElement>();
@ -23,10 +24,11 @@ export function MarkdownRender({ md,fontSize, ...rest }:Props) {
return (
<div ref={rootRef} style={{height:'100%'}}>
<Markdown
<ChakraMarkdown
children={md}
{...rest}
style={{height:'100%',fontSize: fontSize??'14px'}}
style={{height:'100%',fontSize: fontSize??'16px'}}
className="markdown-render"
options={{
overrides: {
WebsiteLink: {
@ -34,7 +36,7 @@ export function MarkdownRender({ md,fontSize, ...rest }:Props) {
},
},
}}
></Markdown>
></ChakraMarkdown>
</div>
);
}

@ -0,0 +1,35 @@
import React from "react"
import {chakra, Heading, Image, Text, HStack,Button, Flex,PropsOf,Box, Avatar, VStack} from "@chakra-ui/react"
import { Tag } from "src/types/tag"
import { ReserveUrls } from "src/data/reserve-urls"
import NextLink from "next/link"
import { Post } from "src/types/posts"
import moment from 'moment'
import { FaGithub } from "react-icons/fa"
import Link from "next/link"
import { useRouter } from "next/router"
type Props = PropsOf<typeof chakra.div> & {
post : Post
}
export const PostAuthor= ({post}:Props) =>{
const router = useRouter()
console.log(post)
return (
<Flex justifyContent="space-between">
<HStack spacing="4">
<Avatar src={post.creator.avatar} size="lg" onClick={() => router.push(`/${post.creator.username}`)} cursor="pointer"/>
<VStack alignItems="left" spacing="1">
<Heading size="sm" onClick={() => router.push(`/${post.creator.username}`)} cursor="pointer">{post.creator.nickname === "" ? post.creator.username : post.creator.nickname}</Heading>
<Text layerStyle="textSecondary" fontSize=".9rem"><chakra.span fontWeight="600" ml="1">{moment(post.created).fromNow()}</chakra.span></Text>
<HStack layerStyle="textSecondary" fontSize=".9rem" spacing="3">
<FaGithub /> <chakra.span>4 min read</chakra.span>
</HStack>
</VStack>
</HStack>
</Flex>
)
}
export default PostAuthor

@ -0,0 +1,28 @@
import React from "react"
import {Box, Heading, Image, Text, HStack,Button, Flex,PropsOf,Link} from "@chakra-ui/react"
import { Tag } from "src/types/tag"
import { ReserveUrls } from "src/data/reserve-urls"
import NextLink from "next/link"
interface Props {
tag: Tag
}
export const TagCard= (props:Props) =>{
const {tag} = props
return (
<Flex justifyContent="space-between">
<Box>
<Heading size="sm" display="flex" alignItems="center" cursor="pointer">
{tag.title}
</Heading>
<Text layerStyle="textSecondary" fontSize=".9rem" mt="1" fontWeight="450">{tag.postCount} posts</Text>
</Box>
<Image src={tag.icon} width="35px" />
</Flex>
)
}
export default TagCard

@ -0,0 +1,68 @@
import React, { useEffect, useState } from "react"
import { Box, Popover, PopoverTrigger, Button, PopoverContent, PopoverBody, Input, useDisclosure, Divider, useToast } from "@chakra-ui/react"
import { Tag } from "src/types/tag"
import { requestApi } from "utils/axios/request"
import { cloneDeep, findIndex } from "lodash"
import TagCard from 'src/components/posts/tag-list-card'
import { config } from "utils/config"
interface Props {
options: Tag[]
selected: Tag[]
onChange: any
}
export const TagInput = (props: Props) => {
const toast = useToast()
const [tags, setTags]: [Tag[], any] = useState([])
const { onOpen, onClose, isOpen } = useDisclosure()
const filterTags = query => {
if (query.trim() === "") {
setTags([])
return
}
const newTags = []
props.options.forEach(tag => {
if (tag.title.toLowerCase().indexOf(query.toLowerCase()) !== -1) {
if (findIndex(props.selected,t => t.id === tag.id) === -1) {
newTags.push(tag)
}
}
})
setTags(newTags)
}
const addTag = tag => {
const t = cloneDeep(props.selected)
t.push(tag)
props.onChange(t)
}
return (
<>
{props.selected.length <=config.posts.maxTags && <Input onChange={e => filterTags(e.target.value)} onFocus={onOpen} onBlur={onClose} placeholder="start typing to search.." variant="unstyled" _focus={null} mt="3" />}
{tags.length > 0 && <Popover isOpen={isOpen} closeOnBlur={false} placement="bottom-start" onOpen={onOpen} onClose={onClose} autoFocus={false}>
<PopoverTrigger><Box width="100%"></Box></PopoverTrigger>
<PopoverContent width="100%">
<PopoverBody width="100%" p="0">
{tags.map((tag, i) => {
return <Box key={tag.id} cursor="pointer" onClick={_ => addTag(tag)}>
<Box py="2" px="4" >
<TagCard tag={tag}/>
</Box>
{i < tags.length - 1 && <Divider />}
</Box>
})
}
</PopoverBody>
</PopoverContent>
</Popover>}
</>
)
}
export default TagInput

@ -11,4 +11,8 @@ export interface Post {
cover?: string
brief?: string
created?: string
tags?: number[]
likes? : number
liked? : boolean
recommands? : number
}

@ -6,4 +6,5 @@ export interface Tag {
icon?: string
cover?: string
created?: string
postCount?: number
}

@ -4,7 +4,8 @@ export let config = {
posts: {
titleMaxLen: 128,
briefMaxLen: 128,
writingEnabled: false
writingEnabled: false,
maxTags: 0
}
}

@ -1,6 +1,7 @@
import { extendTheme } from "@chakra-ui/react"
import { mode } from "@chakra-ui/theme-tools"
import reactMarkdownStyls from 'theme/react-markdown-editor'
import markdownEditor from 'theme/markdown-editor'
import markdownRender from 'theme/markdown-render'
import layerStyles from 'theme/layer-styles'
const customTheme = extendTheme({
@ -22,7 +23,7 @@ const customTheme = extendTheme({
styles: {
global: (props) => ({
body: {
background: mode("gray.50","gray.800" )(props),
background: mode("white","gray.800" )(props),
color: mode("gray.700", "whiteAlpha.900")(props),
".deleted": {
color: "#ff8383 !important",
@ -33,7 +34,8 @@ const customTheme = extendTheme({
fontStyle: "normal !important",
},
},
...reactMarkdownStyls(props)
...markdownEditor(props),
...markdownRender(props)
}),
},
textStyles: {

@ -1,8 +1,7 @@
import { mode } from "@chakra-ui/theme-tools"
import userCustomTheme from "./user-custom"
export default function reactMarkdownStyles(props) {
console.log(props)
export default function markdownEditor(props) {
return {
'.rc-md-editor': {
borderWidth: '0px',
@ -10,6 +9,7 @@ export default function reactMarkdownStyles(props) {
textarea: {
background: 'transparent!important',
color: mode("#2D3748!important", "rgba(255, 255, 255, 0.92)!important")(props),
fontSize: '16px !important'
},
'.rc-md-navigation' :{
background: 'transparent',

@ -0,0 +1,62 @@
import { mode } from "@chakra-ui/theme-tools"
import userCustomTheme from "./user-custom"
export default function markdownRender(props) {
return {
'.markdown-render': {
'.hljs' : {
padding: '1rem',
borderRadius: '8px'
},
'ul,ol' : {
paddingLeft: '1rem',
margin: '1.2rem 0',
li: {
margin: '.8rem 0'
}
},
'h1': {
fontSize: '2rem',
fontWeight: 'bold',
marginBottom: '0.8rem'
},
'h2': {
fontSize: '1.8rem',
fontWeight: 'bold',
marginBottom: '0.6rem'
},
'h3': {
fontSize: '1.6em',
fontWeight: '600',
marginBottom: '0.4rem'
},
'h4': {
fontSize: '1.4em',
fontWeight: '600'
},
'h5,h6': {
fontSize: '1.2em',
fontWeight: 'normal'
},
p: {
margin: '1.2rem 0',
},
blockquote: {
lineHeight: '2rem',
margin: '1.5rem 0',
p :{
paddingLeft: '1rem',
fontWeight: '500',
fontStyle: 'italic',
borderLeftWidth: '.25rem',
borderLeftColor: '#e5e7eb',
color: mode("inherit", "'rgb(189, 189, 189)'")(props),
fontSize: '1.2rem',
}
},
pre: {
margin: '1.6rem 0'
}
}
}
}
Loading…
Cancel
Save