From af49765582094d804580251a3f13c13474307491 Mon Sep 17 00:00:00 2001 From: codemystery <937193167@qq.com> Date: Fri, 12 Mar 2021 12:44:16 +0800 Subject: [PATCH] add pin for posts --- pages/[username]/index.tsx | 2 +- pages/editor/posts.tsx | 6 ++- pages/editor/series.tsx | 7 ++- server/internal/api/story.go | 22 +++++++++ server/internal/server.go | 1 + server/internal/storage/sql_tables.go | 9 ++++ server/internal/story/pin.go | 51 ++++++++++++++++++++ server/internal/story/posts.go | 16 ++++++- server/pkg/models/story.go | 3 +- src/components/interaction/like.tsx | 2 +- src/components/story/simple-story-card.tsx | 14 +++--- src/components/story/stories.tsx | 5 +- src/components/story/story-card.tsx | 9 ++-- src/components/story/text-story-card.tsx | 55 +++++++++++++++++----- src/components/svg-icon.tsx | 25 ++++++---- src/data/links.tsx | 5 ++ src/types/story.ts | 1 + 17 files changed, 192 insertions(+), 41 deletions(-) create mode 100644 server/internal/story/pin.go diff --git a/pages/[username]/index.tsx b/pages/[username]/index.tsx index b9edcc0f..cfdf1eac 100644 --- a/pages/[username]/index.tsx +++ b/pages/[username]/index.tsx @@ -202,7 +202,7 @@ const UserPage = () => { : - + } diff --git a/pages/editor/posts.tsx b/pages/editor/posts.tsx index 63b357fb..8545179d 100644 --- a/pages/editor/posts.tsx +++ b/pages/editor/posts.tsx @@ -96,6 +96,10 @@ const PostsPage = () => { }) } + const onPinPost = async id => { + await requestApi.post(`/story/pin/${id}`) + getPosts() + } return ( <> @@ -125,7 +129,7 @@ const PostsPage = () => { {posts.map(post => - editPost(post)} onDelete={() => onDeletePost(post.id)} /> + editPost(post)} onDelete={() => onDeletePost(post.id)} onPin={() => onPinPost(post.id)} /> )} diff --git a/pages/editor/series.tsx b/pages/editor/series.tsx index 3944edab..665378d7 100644 --- a/pages/editor/series.tsx +++ b/pages/editor/series.tsx @@ -145,6 +145,11 @@ const PostsPage = () => { setCurrentSeries(newSeries) } + const onPinPost = async id => { + await requestApi.post(`/story/pin/${id}`) + getSeries() + } + return ( <> @@ -272,7 +277,7 @@ const PostsPage = () => { {series.map(post => - editSeries(post)} onDelete={() => onDeleteSeries(post.id)} showSource={false} /> + editSeries(post)} onDelete={() => onDeleteSeries(post.id)} showSource={false} onPin={() => onPinPost(post.id)}/> )} diff --git a/server/internal/api/story.go b/server/internal/api/story.go index 4a2c143e..8644c246 100644 --- a/server/internal/api/story.go +++ b/server/internal/api/story.go @@ -197,3 +197,25 @@ func GetSeries(c *gin.Context) { c.JSON(http.StatusOK, common.RespSuccess(series)) } + +type PinData struct { + TargetID string `json:"targetID"` + StoryID string `json:"storyID"` +} + +func PinStory(c *gin.Context) { + storyID := c.Param("storyID") + u := user.CurrentUser(c) + + if !models.IsStoryCreator(u.ID, storyID) { + c.JSON(http.StatusForbidden, common.RespError(e.NoPermission)) + } + + err := story.PinStory(storyID, u.ID) + if err != nil { + c.JSON(err.Status, common.RespError(err.Message)) + return + } + + c.JSON(http.StatusOK, common.RespSuccess(nil)) +} diff --git a/server/internal/server.go b/server/internal/server.go index d475da35..7ff46203 100644 --- a/server/internal/server.go +++ b/server/internal/server.go @@ -54,6 +54,7 @@ func (s *Server) Start() error { r.GET("/story/posts/drafts", IsLogin(), api.GetEditorDrafts) r.GET("/story/posts/home/:filter", api.GetHomePosts) r.POST("/story", IsLogin(), api.SubmitStory) + r.POST("/story/pin/:storyID", IsLogin(), api.PinStory) r.POST("/story/series", api.GetSeries) r.POST("/story/series/post/:id", IsLogin(), api.SubmitSeriesPost) r.GET("/story/series/post/:id", api.GetSeriesPost) diff --git a/server/internal/storage/sql_tables.go b/server/internal/storage/sql_tables.go index eb6b6a98..dd1a3048 100644 --- a/server/internal/storage/sql_tables.go +++ b/server/internal/storage/sql_tables.go @@ -177,4 +177,13 @@ var sqlTables = map[string]string{ CREATE INDEX IF NOT EXISTS series_post_postid ON series_post (post_id); `, + + "pin": `CREATE TABLE IF NOT EXISTS pin ( + target_id VARCHAR(255), + story_id VARCHAR(255), + created DATETIME + ); + CREATE INDEX IF NOT EXISTS pin_targetid + ON pin (target_id); + `, } diff --git a/server/internal/story/pin.go b/server/internal/story/pin.go new file mode 100644 index 00000000..788d276c --- /dev/null +++ b/server/internal/story/pin.go @@ -0,0 +1,51 @@ +package story + +import ( + "database/sql" + "net/http" + "time" + + "github.com/imdotdev/im.dev/server/pkg/db" + "github.com/imdotdev/im.dev/server/pkg/e" +) + +func PinStory(storyID string, targetID string) *e.Error { + pinned := false + + var nid string + err := db.Conn.QueryRow("SELECT target_id FROM pin WHERE target_id=? and story_id=?", targetID, storyID).Scan(&nid) + if err != nil && err != sql.ErrNoRows { + logger.Warn("query pinned error", "error", err) + return e.New(http.StatusInternalServerError, e.Internal) + } + + if nid == targetID { + pinned = true + } + + if pinned { + _, err = db.Conn.Exec("DELETE FROM pin WHERE target_id=? and story_id=?", targetID, storyID) + if err != nil { + logger.Warn("delete pin error", "error", err) + return e.New(http.StatusInternalServerError, e.Internal) + } + } else { + _, err = db.Conn.Exec("INSERT INTO pin (target_id,story_id,created) VALUES (?,?,?)", targetID, storyID, time.Now()) + if err != nil { + logger.Warn("add pin error", "error", err) + return e.New(http.StatusInternalServerError, e.Internal) + } + } + + return nil +} + +func GetPinned(storyID string, targetID string) bool { + var nid string + err := db.Conn.QueryRow("SELECT target_id FROM pin WHERE target_id=? and story_id=?", targetID, storyID).Scan(&nid) + if err != nil { + return false + } + + return true +} diff --git a/server/internal/story/posts.go b/server/internal/story/posts.go index b6a5445b..10897110 100644 --- a/server/internal/story/posts.go +++ b/server/internal/story/posts.go @@ -47,7 +47,21 @@ func UserPosts(tp string, user *models.User, uid string) (models.Stories, *e.Err posts := GetPosts(user, rows) sort.Sort(posts) - return posts, nil + + pinned := make([]*models.Story, 0) + unpinned := make([]*models.Story, 0) + + for _, post := range posts { + post.Pinned = GetPinned(post.ID, user.ID) + if post.Pinned { + pinned = append(pinned, post) + } else { + unpinned = append(unpinned, post) + } + } + + newPosts := append(pinned, unpinned...) + return newPosts, nil } func UserDrafts(user *models.User, uid string) (models.Stories, *e.Error) { diff --git a/server/pkg/models/story.go b/server/pkg/models/story.go index 014b277c..69549fe6 100644 --- a/server/pkg/models/story.go +++ b/server/pkg/models/story.go @@ -1,7 +1,6 @@ package models import ( - "fmt" "time" "github.com/imdotdev/im.dev/server/pkg/db" @@ -28,6 +27,7 @@ type Story struct { RawTags []*Tag `json:"rawTags"` Likes int `json:"likes"` Liked bool `json:"liked"` + Pinned bool `json:"pinned,omitempty"` Comments int `json:"comments"` Views int `json:"views"` Bookmarked bool `json:"bookmarked"` @@ -78,7 +78,6 @@ func (s SeriesPosts) Less(i, j int) bool { func IsStoryCreator(userID string, storyID string) bool { var nid string err := db.Conn.QueryRow("SELECT creator FROM story WHERE id=?", storyID).Scan(&nid) - fmt.Println(userID, storyID, err) if err != nil { return false } diff --git a/src/components/interaction/like.tsx b/src/components/interaction/like.tsx index 4014ec24..a374b2e6 100644 --- a/src/components/interaction/like.tsx +++ b/src/components/interaction/like.tsx @@ -53,7 +53,7 @@ const Like = (props: Props) => { />} - + ) } diff --git a/src/components/story/simple-story-card.tsx b/src/components/story/simple-story-card.tsx index b09eaa65..e0045c89 100644 --- a/src/components/story/simple-story-card.tsx +++ b/src/components/story/simple-story-card.tsx @@ -21,21 +21,19 @@ export const SimpleStoryCard = (props: Props) => { return ( {story.title} - - {story.creator.nickname} + + {story.creator.nickname} - + - - {getSvgIcon("comments", "1rem")} - {story.comments} + + {getSvgIcon("comments1", "0.9rem")} + {story.comments} - - ) diff --git a/src/components/story/stories.tsx b/src/components/story/stories.tsx index 55da74eb..125a4297 100644 --- a/src/components/story/stories.tsx +++ b/src/components/story/stories.tsx @@ -9,13 +9,14 @@ interface Props { card?: any size?: 'sm' | 'md' showFooter?: boolean + showPinned?: boolean type?: string highlight?: string } export const Stroies = (props: Props) => { - const { stories,card=StoryCard,showFooter=true,type="classic"} = props + const { stories,card=StoryCard,showFooter=true,type="classic",showPinned = false} = props const borderColor = useColorModeValue(userCustomTheme.borderColor.light, userCustomTheme.borderColor.dark) const Card = card const showBorder = i => { @@ -34,7 +35,7 @@ export const Stroies = (props: Props) => { {stories.map((story,i) => - + )} {showFooter &&
没有更多文章了
} diff --git a/src/components/story/story-card.tsx b/src/components/story/story-card.tsx index 3a547de9..6d242de0 100644 --- a/src/components/story/story-card.tsx +++ b/src/components/story/story-card.tsx @@ -30,14 +30,15 @@ export const StoryCard = (props: Props) => { - - + + - {story.type === IDType.Series && SERIES} + {story.type === IDType.Series && SERIES} + {story.pinned && 置顶} {type !== "classic" && {story.rawTags.map(t => #{t.name})}} @@ -47,7 +48,7 @@ export const StoryCard = (props: Props) => { searchWords={[props.highlight]} /> - {story.cover && type === "classic" && } + {story.cover && type === "classic" && } diff --git a/src/components/story/text-story-card.tsx b/src/components/story/text-story-card.tsx index 234e9090..cb225dbf 100644 --- a/src/components/story/text-story-card.tsx +++ b/src/components/story/text-story-card.tsx @@ -1,42 +1,73 @@ import React from "react" -import {chakra, Heading, VStack, Text, HStack,Button, Flex,PropsOf, Tag, useMediaQuery } from "@chakra-ui/react" +import { chakra, Heading, VStack, Text, HStack, Button, Flex, PropsOf, Tag, useMediaQuery, IconButton, Tooltip } from "@chakra-ui/react" import { Story } from "src/types/story" import moment from 'moment' import { IDType } from "src/types/id" import { getStoryUrl } from "utils/story" +import { FaPaperclip, FaRegTrashAlt, FaTrash } from "react-icons/fa" +import { getSvgIcon } from "components/svg-icon" type Props = PropsOf & { story: Story showActions: boolean onEdit?: any onDelete?: any + onPin?: any showSource?: boolean } -export const TextStoryCard= (props:Props) =>{ - const {story,showActions,onEdit,onDelete,showSource=true ,...rest} = props - +export const TextStoryCard = (props: Props) => { + const { story, showActions, onEdit, onDelete, showSource = true,onPin, ...rest } = props const [isSmallScreen] = useMediaQuery("(max-width: 768px)") const Lay = isSmallScreen ? VStack : Flex const gap = moment(story.created).fromNow() + return ( //@ts-ignore - - + + {showSource && <> {story.url ? 外部 : 原创}} - {story.title ?story.title : 'No Title'} + {story.title ? story.title : 'No Title'} 发布于{gap} - {props.showActions && - - + {props.showActions && + + } + onClick={onPin} + color={story.pinned? "teal" : null} + /> + + + + + + + + } + onClick={props.onDelete} + /> + } - + ) -} +} export default TextStoryCard diff --git a/src/components/svg-icon.tsx b/src/components/svg-icon.tsx index ffcbf4a4..f6adb3b8 100644 --- a/src/components/svg-icon.tsx +++ b/src/components/svg-icon.tsx @@ -1,17 +1,20 @@ -export function getSvgIcon(name,height="1.4rem") { +export function getSvgIcon(name, height = "1.4rem") { let svg switch (name) { + case "comments1": + svg = + break case "comments": svg = break case "best": - svg = + svg = break case "home": svg = break case "tags": - svg = + svg = break case "post": svg = @@ -20,7 +23,7 @@ export function getSvgIcon(name,height="1.4rem") { svg = break case "explore": - svg = + svg = break case "feature": svg = @@ -29,19 +32,25 @@ export function getSvgIcon(name,height="1.4rem") { svg = break case "search": - svg = + svg = break case "user": - svg = + svg = break case "favorites": - svg = + svg = break case "drafts": svg = break + case "share": + svg = + break + case "edit": + svg = + break default: - break; + break; } return svg diff --git a/src/data/links.tsx b/src/data/links.tsx index 464780c4..a9b55b28 100644 --- a/src/data/links.tsx +++ b/src/data/links.tsx @@ -40,6 +40,11 @@ export const interactionLinks: any[] = [ path: `${ReserveUrls.Interaction}/followers`, disabled: false }, + { + title: 'Followers', + path: `${ReserveUrls.Interaction}/followers`, + disabled: false + }, ] export const searchLinks: any[] = [{ diff --git a/src/types/story.ts b/src/types/story.ts index db800078..543f88d2 100644 --- a/src/types/story.ts +++ b/src/types/story.ts @@ -24,6 +24,7 @@ export interface Story { rawTags?: Tag[] likes? : number liked? : boolean + pinned?: boolean comments? : number bookmarked?: boolean status?: number