add bookmarks

pull/50/head
sunface 4 years ago
parent 4a148aa80c
commit 5717798903

@ -20,7 +20,7 @@ import { useRouter } from "next/router"
import { ReserveUrls } from "src/data/reserve-urls"
import Link from "next/link"
import DarkMode from "components/dark-mode"
import AccountMenu from "components/account-menu"
import AccountMenu from "components/user-menu"
const navLinks = [{
title: '主页',

@ -15,7 +15,7 @@ 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 AccountMenu from "components/user-menu"
import { FaGithub, FaTwitter, FaUserPlus } from "react-icons/fa"

@ -23,7 +23,7 @@ import {
import { ReserveUrls } from "src/data/reserve-urls"
import Link from "next/link"
import DarkMode from "components/dark-mode"
import AccountMenu from "components/account-menu"
import AccountMenu from "components/user-menu"
import { getSvgIcon } from "components/svg-icon"
const navLinks = [{

@ -0,0 +1,122 @@
import {
chakra, Flex, Heading, HStack, Text, VStack, Menu,
MenuButton,
MenuList,
MenuItem,
IconButton,
Divider,
Wrap,
Image,
useColorModeValue
} from "@chakra-ui/react"
import SEO from "components/seo"
import siteConfig from "configs/site-config"
import PageContainer1 from "layouts/page-container1"
import React, { useEffect, useState } from "react"
import { HomeSidebar } from 'pages/index'
import Card from "components/card"
import { config } from "configs/config"
import { getSvgIcon } from "components/svg-icon"
import { Tag } from "src/types/tag"
import { requestApi } from "utils/axios/request"
import TagCard from 'src/components/tags/tag-card'
import { Post } from "src/types/posts"
import Posts from "components/posts/posts"
import { find } from "lodash"
import userCustomTheme from "theme/user-custom"
import Empty from "components/empty"
const BookmarksPage = () => {
const [filter, setFilter]:[Tag,any] = useState({id:-1})
const [tags, setTags]: [Tag[], any] = useState([])
const [rawPosts,setRawPosts]: [Post[],any] = useState([])
const [posts,setPosts]: [Post[],any] = useState([])
useEffect(() => {
getBookmarkPosts()
}, [])
useEffect(() => {
filterPosts()
}, [filter])
const getBookmarkPosts = async() => {
const res = await requestApi.get(`/bookmark/posts`)
setRawPosts(res.data)
setPosts(res.data)
const ts = [{id:-1,title:'All Tags',icon: 'https://cdn.hashnode.com/res/hashnode/image/upload/v1605105898259/3vuMFM8qM.png?w=200&h=200&fit=crop&crop=entropy&auto=compress&auto=compress'}]
res.data.forEach(post => {
post.rawTags?.forEach(tag => {
if (!find(ts, t => t.id === tag.id)) {
ts.push(tag)
}
})
})
setTags(ts)
}
const filterPosts = () => {
if (filter.id === -1) {
setPosts(rawPosts)
return
}
const newPosts = []
rawPosts.forEach(post => {
post.rawTags?.forEach(tag => {
if (tag.id === filter.id) {
newPosts.push(post)
}
})
})
setPosts(newPosts)
}
return (
<>
<SEO
title={siteConfig.seo.title}
description={siteConfig.seo.description}
/>
<PageContainer1>
<HStack alignItems="top" p="4" spacing="3">
<VStack alignItems="left" width={["100%", "100%", "70%", "70%"]} spacing="3">
<Card>
<VStack py="3" spacing="3">
<Heading size="md" fontSize="1.6rem">Bookmarks</Heading>
<Text layerStyle="textSecondary">All the discussions, stories and comments you have bookmarked on {config.appName}.</Text>
</VStack>
</Card>
<Card p="0">
<Wrap pt="4" pb="1" pl="4" alignItems="center">
{
tags.map(t =>
<HStack px="2" py="1" spacing="1" mr="3" cursor="pointer" key={t.id} className={t.id===filter.id ?"tag-bg": null} onClick={() => setFilter(t)}>
<Image src={t.icon} width="30px" height="30px" className="bordered"/>
<Text fontSize=".9rem">{t.title}</Text>
</HStack>)
}
</Wrap>
<Divider mt="3" mb="5" />
{posts.length !== 0
?
<Posts posts={posts} showFooter={false}/>
:
<Empty />
}
</Card>
</VStack>
<HomeSidebar />
</HStack>
</PageContainer1>
</>
)
}
export default BookmarksPage

@ -68,7 +68,7 @@ const HomePage = () => {
</Menu>
</Flex>
</Card>
<Card width="100%" height="fit-content" p="0" px="3">
<Card width="100%" height="fit-content" p="0">
<Posts posts={posts} />
</Card>
</VStack>

@ -58,3 +58,16 @@ func GetHomePosts(c *gin.Context) {
c.JSON(http.StatusOK, common.RespSuccess(posts))
}
func GetBookmarkPosts(c *gin.Context) {
filter := c.Param("filter")
user := user.CurrentUser(c)
posts, err := story.BookmarkPosts(user, filter)
if err != nil {
c.JSON(err.Status, common.RespError(err.Message))
return
}
c.JSON(http.StatusOK, common.RespSuccess(posts))
}

@ -81,6 +81,7 @@ func (s *Server) Start() error {
r.GET("/session", IsLogin(), api.GetSession)
r.POST("/bookmark/:storyID", IsLogin(), api.Bookmark)
r.GET("/bookmark/posts", IsLogin(), api.GetBookmarkPosts)
err := router.Run(config.Data.Server.Addr)
if err != nil {

@ -7,6 +7,7 @@ import (
"sort"
"strings"
"github.com/imdotdev/im.dev/server/internal/tags"
"github.com/imdotdev/im.dev/server/pkg/db"
"github.com/imdotdev/im.dev/server/pkg/e"
"github.com/imdotdev/im.dev/server/pkg/models"
@ -69,6 +70,46 @@ func TagPosts(user *models.User, tagID int64) (models.Posts, *e.Error) {
return posts, nil
}
func BookmarkPosts(user *models.User, filter string) (models.Posts, *e.Error) {
// get post ids
rows, err := db.Conn.Query("select story_id from bookmarks where user_id=?", user.ID)
if err != nil {
logger.Warn("get bookmarks error", "error", err)
return nil, e.New(http.StatusInternalServerError, e.Internal)
}
postIDs := make([]string, 0)
for rows.Next() {
var id string
rows.Scan(&id)
postIDs = append(postIDs, id)
}
ids := strings.Join(postIDs, "','")
q := fmt.Sprintf("select id,slug,title,url,cover,brief,likes,views,creator,created,updated from posts where id in ('%s')", ids)
rows, err = db.Conn.Query(q)
if err != nil && err != sql.ErrNoRows {
logger.Warn("get user posts error", "error", err)
return nil, e.New(http.StatusInternalServerError, e.Internal)
}
posts := getPosts(user, rows)
for _, post := range posts {
ts, err := tags.GetStoryTags(post.ID)
if err != nil {
logger.Warn("get story tags error", "error", err)
continue
}
post.RawTags = ts
}
sort.Sort(posts)
return posts, nil
}
func getPosts(user *models.User, rows *sql.Rows) models.Posts {
posts := make(models.Posts, 0)
for rows.Next() {

@ -133,3 +133,41 @@ func GetTag(id int64, name string) (*models.Tag, *e.Error) {
tag.SetCover()
return tag, nil
}
func GetSimpleTag(id int64, name string) (*models.Tag, *e.Error) {
tag := &models.Tag{}
err := db.Conn.QueryRow("SELECT id,title,icon from tags where id=? or name=?", id, name).Scan(
&tag.ID, &tag.Title, &tag.Icon,
)
if err != nil {
if err == sql.ErrNoRows {
return nil, e.New(http.StatusNotFound, e.NotFound)
}
logger.Warn("get tag error", "error", err)
return nil, e.New(http.StatusInternalServerError, e.Internal)
}
return tag, nil
}
func GetStoryTags(storyID string) ([]*models.Tag, error) {
ids := make([]int64, 0)
rows, err := db.Conn.Query("SELECT tag_id FROM tag_post WHERE post_id=?", storyID)
if err != nil {
return nil, err
}
rawTags := make([]*models.Tag, 0)
for rows.Next() {
var id int64
err = rows.Scan(&id)
ids = append(ids, id)
rawTag, err := GetSimpleTag(id, "")
if err == nil {
rawTags = append(rawTags, rawTag)
}
}
return rawTags, nil
}

@ -4,15 +4,15 @@ import "time"
type Tag struct {
ID int64 `json:"id"`
Creator int64 `json:"creator"`
Creator int64 `json:"creator,omitempty"`
Title string `json:"title"`
Name string `json:"name"`
Md string `json:"md"`
Cover string `json:"cover"`
Name string `json:"name,omitempty"`
Md string `json:"md,omitempty"`
Cover string `json:"cover,omitempty"`
Icon string `json:"icon"`
PostCount int `json:"postCount"`
Created time.Time `json:"created"`
Updated time.Time `json:"updated"`
PostCount int `json:"postCount,omitempty"`
Created time.Time `json:"created,omitempty"`
Updated time.Time `json:"updated,omitempty"`
}
func (t *Tag) SetCover() {

@ -16,11 +16,22 @@ export const Posts = (props: Props) => {
const { posts,card=PostCard,showFooter=true} = props
const postBorderColor = useColorModeValue(userCustomTheme.borderColor.light, userCustomTheme.borderColor.dark)
const Card = card
const showBorder = i => {
if (i < posts.length -1) {
return true
}
if (showFooter) {
return true
} else {
return false
}
}
return (
<>
<VStack alignItems="left">
{posts.map((post,i) =>
<Box py="2" borderBottom={i<posts.length-1 ? `1px solid ${postBorderColor}`:null} key={post.id}>
<Box py="2" borderBottom={showBorder(i)? `1px solid ${postBorderColor}`:null} key={post.id}>
<Card post={post} size={props.size}/>
</Box>)}
</VStack>

@ -3,7 +3,7 @@ import { Box, chakra, Flex, Heading, HStack, Image, Text, useMediaQuery, VStack
import { Post } from "src/types/posts"
import PostAuthor from "./post-author"
import Link from "next/link"
import UnicornLike from "./heart-like"
import UnicornLike from "./like"
import { FaHeart, FaRegBookmark, FaRegComment, FaRegHeart } from "react-icons/fa"
import SvgButton from "components/svg-button"

@ -22,7 +22,7 @@ export const TagCard= (props:Props) =>{
<Image src={tag.icon} width="43px" mr="2" borderWidth="1px" className="bordered"/>
<Box>
<Heading size="sm">{tag.title}</Heading>
<Tooltip openDelay={300} label={tag.md}><Text layerStyle="textSecondary" fontSize=".85rem" mt="1" overflow="hidden" textOverflow="ellipsis" whiteSpace="nowrap" width={{"sm": "100px","md":"400px","xl":"600px"}}>{tag.md}</Text></Tooltip>
<Tooltip openDelay={300} label={tag.md}><Text layerStyle="textSecondary" fontSize=".85rem" mt="1" overflow="hidden" textOverflow="ellipsis" whiteSpace="nowrap" width={{"sm": "100px","md":"400px"}}>{tag.md}</Text></Tooltip>
</Box>
</HStack>
</NextLink>

@ -19,7 +19,7 @@ import { isAdmin, isEditor } from "utils/role"
import { logout } from "utils/session"
import Link from "next/link"
export const AccountMenu = () => {
export const UserMenu = () => {
const session: Session = useSession()
const router = useRouter()
@ -57,7 +57,7 @@ export const AccountMenu = () => {
<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>
<Link href={`${ReserveUrls.Bookmarks}`}><MenuItem icon={<FaBookmark fontSize="16" />}></MenuItem></Link>
<MenuDivider />
<Link href={`${ReserveUrls.Settings}/profile`}><MenuItem icon={<FaRegSun fontSize="16" />}></MenuItem></Link>
<MenuItem onClick={() => logout()} icon={<FaSignOutAlt fontSize="16" />}></MenuItem>
@ -77,4 +77,4 @@ export const AccountMenu = () => {
)
}
export default AccountMenu
export default UserMenu

@ -22,7 +22,9 @@ const customTheme = extendTheme({
"0 0 0 1px rgba(16,22,26,.1), 0 4px 8px rgba(16,22,26,.2), 0 18px 46px 6px rgba(16,22,26,.2)",
},
styles: {
global: (props) => ({
global: (props) => {
console.log(props)
return ({
'.hover-bg:hover': {
background: mode(userCustomTheme.hoverBg.light,userCustomTheme.hoverBg.dark )(props),
borderRadius: '6px'
@ -35,6 +37,11 @@ const customTheme = extendTheme({
boxShadow: 'rgba(0, 0, 0, 0) 0px 0px 0px 0px, rgba(0, 0, 0, 0) 0px 0px 0px 0px, rgba(0, 0, 0, 0.1) 0px 1px 3px 0px, rgba(0, 0, 0, 0.06) 0px 1px 2px 0px',
borderRadius: '6px'
},
'.tag-bg': {
background: mode(props.theme.colors.cyan['100'],'rgba(157, 236, 249, 0.16)' )(props),
color: mode(props.theme.colors.cyan['800'],props.theme.colors.cyan['200'] )(props),
borderRadius: '6px'
},
body: {
background: mode("white","gray.800" )(props),
color: mode("gray.700", "whiteAlpha.900")(props),
@ -51,7 +58,7 @@ const customTheme = extendTheme({
},
...markdownEditor(props),
...markdownRender(props)
}),
})},
},
textStyles: {
heading: {

@ -8,7 +8,7 @@ const userCustomTheme = {
hoverBg: {
light: theme.colors.gray['100'],
dark: theme.colors.whiteAlpha['200']
},
}
}
export default userCustomTheme
Loading…
Cancel
Save