diff --git a/layouts/nav/nav.tsx b/layouts/nav/nav.tsx index b2ff91e8..070100d7 100644 --- a/layouts/nav/nav.tsx +++ b/layouts/nav/nav.tsx @@ -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: '主页', diff --git a/layouts/nav/post-nav.tsx b/layouts/nav/post-nav.tsx index a651e092..32b8b943 100644 --- a/layouts/nav/post-nav.tsx +++ b/layouts/nav/post-nav.tsx @@ -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" diff --git a/layouts/nav/vertical-nav.tsx b/layouts/nav/vertical-nav.tsx index 6b88320a..4cbbd3bc 100644 --- a/layouts/nav/vertical-nav.tsx +++ b/layouts/nav/vertical-nav.tsx @@ -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 = [{ diff --git a/pages/bookmarks.tsx b/pages/bookmarks.tsx new file mode 100644 index 00000000..8f4abcb6 --- /dev/null +++ b/pages/bookmarks.tsx @@ -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 ( + <> + + + + + + + Bookmarks + All the discussions, stories and comments you have bookmarked on {config.appName}. + + + + + + { + tags.map(t => + setFilter(t)}> + + {t.title} + ) + } + + + {posts.length !== 0 + ? + + : + + } + + + + + + + ) + } + + export default BookmarksPage + + \ No newline at end of file diff --git a/pages/index.tsx b/pages/index.tsx index 83ea1ab8..ffd60fe5 100644 --- a/pages/index.tsx +++ b/pages/index.tsx @@ -68,7 +68,7 @@ const HomePage = () => { - + diff --git a/server/internal/api/posts.go b/server/internal/api/posts.go index 60dd522e..ad88c461 100644 --- a/server/internal/api/posts.go +++ b/server/internal/api/posts.go @@ -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)) +} diff --git a/server/internal/server.go b/server/internal/server.go index b489b1d0..16a2d653 100644 --- a/server/internal/server.go +++ b/server/internal/server.go @@ -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 { diff --git a/server/internal/story/posts.go b/server/internal/story/posts.go index f150e402..02087e61 100644 --- a/server/internal/story/posts.go +++ b/server/internal/story/posts.go @@ -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() { diff --git a/server/internal/tags/tags.go b/server/internal/tags/tags.go index 9418f757..beb5658e 100644 --- a/server/internal/tags/tags.go +++ b/server/internal/tags/tags.go @@ -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 +} diff --git a/server/pkg/models/tag.go b/server/pkg/models/tag.go index 7691297c..dbd392d5 100644 --- a/server/pkg/models/tag.go +++ b/server/pkg/models/tag.go @@ -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() { diff --git a/src/components/posts/posts.tsx b/src/components/posts/posts.tsx index 57a2ed8f..5e827abe 100644 --- a/src/components/posts/posts.tsx +++ b/src/components/posts/posts.tsx @@ -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 ( <> {posts.map((post,i) => - + )} diff --git a/src/components/posts/simple-post-card.tsx b/src/components/posts/simple-post-card.tsx index e46d9044..241a1adb 100644 --- a/src/components/posts/simple-post-card.tsx +++ b/src/components/posts/simple-post-card.tsx @@ -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" diff --git a/src/components/tags/tag-card.tsx b/src/components/tags/tag-card.tsx index 0fb7746a..12b5d4eb 100644 --- a/src/components/tags/tag-card.tsx +++ b/src/components/tags/tag-card.tsx @@ -22,7 +22,7 @@ export const TagCard= (props:Props) =>{ {tag.title} - {tag.md} + {tag.md} diff --git a/src/components/account-menu.tsx b/src/components/user-menu.tsx similarity index 93% rename from src/components/account-menu.tsx rename to src/components/user-menu.tsx index 0899aef6..aeb0fcdf 100644 --- a/src/components/account-menu.tsx +++ b/src/components/user-menu.tsx @@ -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 = () => { {isEditor(session.user.role) && } >创作中心} {isAdmin(session.user.role) && } >管理员} - }>书签收藏 + }>书签收藏 }>偏好设置 logout()} icon={}>账号登出 @@ -77,4 +77,4 @@ export const AccountMenu = () => { ) } -export default AccountMenu +export default UserMenu diff --git a/theme.ts b/theme.ts index f2e700e0..5569de20 100644 --- a/theme.ts +++ b/theme.ts @@ -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: { diff --git a/theme/user-custom.js b/theme/user-custom.js index 5fe64e04..80045a51 100644 --- a/theme/user-custom.js +++ b/theme/user-custom.js @@ -8,7 +8,7 @@ const userCustomTheme = { hoverBg: { light: theme.colors.gray['100'], dark: theme.colors.whiteAlpha['200'] - }, + } } export default userCustomTheme \ No newline at end of file