diff --git a/layouts/nav/editor-nav.tsx b/layouts/nav/editor-nav.tsx
index 62da05ca..16c3eca5 100644
--- a/layouts/nav/editor-nav.tsx
+++ b/layouts/nav/editor-nav.tsx
@@ -15,7 +15,9 @@ import {
Heading,
Tag as ChakraTag,
TagLabel,
- TagCloseButton
+ TagCloseButton,
+ Spinner,
+ Text
} from "@chakra-ui/react"
import { useViewportScroll } from "framer-motion"
import NextLink from "next/link"
@@ -25,16 +27,18 @@ import Card from "components/card"
import DarkMode from "components/dark-mode"
import EditModeSelect from "components/edit-mode-select"
import Tags from "components/tags/tags"
-import { Post } from "src/types/posts"
+import { Story } from "src/types/story"
+import { FaCloud } from "react-icons/fa"
interface Props {
- ar : Post
+ ar : Story
changeTitle: any
changeEditMode: any
publish: any
onChange:any
+ saved?: boolean
}
function HeaderContent(props: Props) {
@@ -64,12 +68,16 @@ function HeaderContent(props: Props) {
-
+ {(props.saved !== null )&&
+ {!props.saved ? Saving : Saved}
+ }
-
+
{
const router = useRouter()
const id = router.query.post_id
- const [post, setPost]: [Post, any] = useState(null)
+ const [post, setPost]: [Story, any] = useState(null)
useEffect(() => {
if (id) {
getData()
@@ -66,7 +53,7 @@ const PostPage = () => {
{post.title}
-
+
@@ -76,7 +63,7 @@ const PostPage = () => {
-
+
diff --git a/pages/[username]/index.tsx b/pages/[username]/index.tsx
index 0f687ede..2fbe9a62 100644
--- a/pages/[username]/index.tsx
+++ b/pages/[username]/index.tsx
@@ -4,21 +4,16 @@ import Container from "components/container"
import SEO from "components/seo"
import siteConfig from "configs/site-config"
import useSession from "hooks/use-session"
-import Nav from "layouts/nav/nav"
-import VerticalNav from "layouts/nav/vertical-nav"
-import PageContainer from "layouts/page-container"
import PageContainer1 from "layouts/page-container1"
import { useRouter } from "next/router"
import React, { useEffect, useState } from "react"
-import { FaComment, FaCommentAlt, FaDove, FaEdit, FaFacebook, FaFile, FaGithub, FaHeart, FaPlus, FaRegStar, FaStackOverflow, FaStar, FaTwitter, FaWeibo, FaZhihu } from "react-icons/fa"
+import {FaFacebook, FaFile, FaGithub, FaHeart, FaPlus, FaRegStar, FaStackOverflow, FaStar, FaTwitter, FaWeibo, FaZhihu } from "react-icons/fa"
import { ReserveUrls } from "src/data/reserve-urls"
import { User } from "src/types/user"
import { requestApi } from "utils/axios/request"
import moment from 'moment'
-import { Post } from "src/types/posts"
-import PostCard from "components/story/post-card"
-import userCustomTheme from "theme/user-custom"
-import Posts from "components/story/posts"
+import { Story } from "src/types/story"
+import Stories from "components/story/stories"
import Link from "next/link"
import Empty from "components/empty"
import Count from "components/count"
@@ -29,8 +24,8 @@ const UserPage = () => {
const username = router.query.username
const session = useSession()
const [user, setUser]: [User, any] = useState(null)
- const [rawPosts, setRawPosts]: [Post[], any] = useState([])
- const [posts, setPosts]: [Post[], any] = useState([])
+ const [rawPosts, setRawPosts]: [Story[], any] = useState([])
+ const [posts, setPosts]: [Story[], any] = useState([])
const [tags,setTags]:[Tag[],any] = useState([])
const [tagFilter,setTagFilter]:[Tag,any] = useState(null)
@@ -178,7 +173,7 @@ const UserPage = () => {
:
-
+
}
diff --git a/pages/admin/tags.tsx b/pages/admin/tags.tsx
index 604a123e..688652c4 100644
--- a/pages/admin/tags.tsx
+++ b/pages/admin/tags.tsx
@@ -7,7 +7,6 @@ import React, { useEffect, useState } from "react"
import {adminLinks} from "src/data/links"
import { requestApi } from "utils/axios/request"
import TagCard from "components/tags/tag-card"
-import { Post } from "src/types/posts"
import { useRouter } from "next/router"
import Link from "next/link"
import { ReserveUrls } from "src/data/reserve-urls"
diff --git a/pages/bookmarks.tsx b/pages/bookmarks.tsx
index 5d0e31f1..2c687a70 100644
--- a/pages/bookmarks.tsx
+++ b/pages/bookmarks.tsx
@@ -20,8 +20,8 @@ import {
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/story/posts"
+import { Story } from "src/types/story"
+import Stories from "components/story/stories"
import { find } from "lodash"
import userCustomTheme from "theme/user-custom"
import Empty from "components/empty"
@@ -31,8 +31,8 @@ import Empty from "components/empty"
const BookmarksPage = () => {
const [filter, setFilter]:[Tag,any] = useState(null)
const [tags, setTags]: [Tag[], any] = useState([])
- const [rawPosts,setRawPosts]: [Post[],any] = useState([])
- const [posts,setPosts]: [Post[],any] = useState([])
+ const [rawPosts,setRawPosts]: [Story[],any] = useState([])
+ const [posts,setPosts]: [Story[],any] = useState([])
useEffect(() => {
getBookmarkPosts()
@@ -107,7 +107,7 @@ import Empty from "components/empty"
{posts.length !== 0
?
-
+
:
}
diff --git a/pages/editor/drafts.tsx b/pages/editor/drafts.tsx
new file mode 100644
index 00000000..00d9e753
--- /dev/null
+++ b/pages/editor/drafts.tsx
@@ -0,0 +1,72 @@
+import { Text, Box, Heading, Image, Center, Flex, VStack, Divider, useToast } from "@chakra-ui/react"
+import Card from "components/card"
+import Sidebar from "layouts/sidebar/sidebar"
+import React, { useEffect, useState } from "react"
+import {editorLinks} from "src/data/links"
+import { requestApi } from "utils/axios/request"
+import { Story } from "src/types/story"
+import { useRouter } from "next/router"
+import PageContainer1 from "layouts/page-container1"
+import Empty from "components/empty"
+import TextStoryCard from "components/story/text-story-card"
+
+const PostsPage = () => {
+ const [posts, setPosts] = useState([])
+ const router = useRouter()
+ const toast = useToast()
+ const getPosts = () => {
+ requestApi.get(`/story/posts/drafts`).then((res) => setPosts(res.data)).catch(_ => setPosts([]))
+ }
+
+ useEffect(() => {
+ getPosts()
+ }, [])
+
+ const editPost = (post: Story) => {
+ router.push(`/editor/post/${post.id}`)
+ }
+
+ const onDeletePost= async (id) => {
+ await requestApi.delete(`/story/post/${id}`)
+ getPosts()
+ toast({
+ description: "删除成功",
+ status: "success",
+ duration: 2000,
+ isClosable: true,
+ })
+ }
+
+ return (
+ <>
+
+
+
+
+
+ 草稿列表({posts.length})
+
+ {
+ posts.length === 0 ?
+
+ :
+ <>
+
+ {posts.map(post =>
+
+ editPost(post)} onDelete={() => onDeletePost(post.id)} showSource={false}/>
+
+
+ )}
+
+ 没有更多文章了
+ >
+ }
+
+
+
+ >
+ )
+}
+export default PostsPage
+
diff --git a/pages/editor/post/[id].tsx b/pages/editor/post/[id].tsx
index dd423ba0..a72e7529 100644
--- a/pages/editor/post/[id].tsx
+++ b/pages/editor/post/[id].tsx
@@ -1,28 +1,33 @@
-import { Box, Button, useToast } from '@chakra-ui/react';
+import { Box, Button, Text, useToast } from '@chakra-ui/react';
import React, { useEffect, useState } from 'react';
import { MarkdownEditor } from 'components/markdown-editor/editor';
import PageContainer from 'layouts/page-container';
import EditorNav from 'layouts/nav/editor-nav'
import { EditMode } from 'src/types/editor';
import { MarkdownRender } from 'components/markdown-editor/render';
-import { Post } from 'src/types/posts';
+import { Story, StoryStatus } from 'src/types/story';
import { requestApi } from 'utils/axios/request';
import { useRouter } from 'next/router';
import { config } from 'configs/config';
import { cloneDeep } from 'lodash';
import Card from 'components/card';
+import { updateUrl } from 'utils/url';
+import { IDType } from 'src/types/id';
-const content = `
-# test原创
-`
+
+
+let saveDraftHandler = undefined;
function PostEditPage() {
const router = useRouter()
const { id } = router.query
const [editMode, setEditMode] = useState(EditMode.Edit)
- const [ar, setAr] = useState({
- md: content,
- title: ''
+ const [saved,setSaved] = useState(null)
+ const [ar, setAr]:[Story,any] = useState({
+ type: IDType.Post,
+ md: '',
+ title: '',
+ status: StoryStatus.Draft
})
const toast = useToast()
@@ -33,14 +38,50 @@ function PostEditPage() {
}, [id])
const onMdChange = newMd => {
- setAr({
+ const newAr = {
...ar,
md: newMd
- })
+ }
+ setAr(newAr)
+
+ if (ar.status === StoryStatus.Draft) {
+ onSaveDraft(newAr)
+ }
+ }
+
+ const onSaveDraft = (post?) => {
+ if (saveDraftHandler === undefined) {
+ // 没有任何保存动作,开始保存
+ saveDraftHandler = setTimeout(() => saveDraft(post),2000)
+ return
+ } else if (saveDraftHandler !== null) {
+ // 不在保存过程中,连续输入, 取消之前的定时器,重新设置handler
+ clearTimeout(saveDraftHandler)
+ saveDraftHandler = setTimeout(() => saveDraft(post),2000)
+ return
+ }
+ }
+
+ const saveDraft = async (post?) => {
+ saveDraftHandler = null
+ setSaved(false)
+ const res = await requestApi.post(`/story/post/draft`, post??ar)
+ setSaved(true)
+ saveDraftHandler = undefined
+ if (!ar.id) {
+ ar.id = res.data.id
+ let url = window.location.origin + `/editor/post/${ar.id}`
+ window.history.pushState({},null,url);
+ }
}
const onChange = () => {
- setAr(cloneDeep(ar))
+ const newAr = cloneDeep(ar)
+ if (ar.status === StoryStatus.Draft) {
+ onSaveDraft(newAr)
+ }
+
+ setAr(newAr)
}
const onChangeTitle = title => {
@@ -54,11 +95,25 @@ function PostEditPage() {
return
}
- setAr({ ...ar, title: title })
+ const newAr = { ...ar, title: title }
+ if (ar.status === StoryStatus.Draft) {
+ onSaveDraft(newAr)
+ }
+
+ setAr(newAr)
}
const publish = async () => {
- const res = await requestApi.post(`/story/post`, ar)
+ if (ar.tags?.length === 0) {
+ toast({
+ description: "请设置文章标签",
+ status: "error",
+ duration: 3000,
+ isClosable: true,
+ })
+ return
+ }
+ const res = await requestApi.post(`/story`, ar)
toast({
description: "发布成功",
status: "success",
@@ -76,6 +131,7 @@ function PostEditPage() {
changeEditMode={(v) => setEditMode(v)}
changeTitle={(e) => onChangeTitle(e.target.value)}
publish={() => publish()}
+ saved={saved}
/>}
>
{editMode === EditMode.Edit ?
diff --git a/pages/editor/posts.tsx b/pages/editor/posts.tsx
index a1da2a91..63b357fb 100644
--- a/pages/editor/posts.tsx
+++ b/pages/editor/posts.tsx
@@ -1,7 +1,5 @@
import { Menu,MenuButton,MenuList,MenuItem, Text, Box, Heading, Image, HStack, Center, Button, Flex, Modal, ModalOverlay, ModalContent, ModalHeader, ModalBody, ModalFooter, FormControl, FormLabel, FormHelperText, Input, FormErrorMessage, VStack, Textarea, Divider, useColorModeValue, useToast } from "@chakra-ui/react"
import Card from "components/card"
-import Nav from "layouts/nav/nav"
-import PageContainer from "layouts/page-container"
import Sidebar from "layouts/sidebar/sidebar"
import React, { useEffect, useState } from "react"
import {editorLinks} from "src/data/links"
@@ -9,16 +7,18 @@ import { requestApi } from "utils/axios/request"
import { useDisclosure } from "@chakra-ui/react"
import { Field, Form, Formik } from "formik"
import { config } from "configs/config"
-import TextPostCard from "components/story/text-post-card"
-import { Post } from "src/types/posts"
+import TextStoryCard from "components/story/text-story-card"
+import { Story } from "src/types/story"
import { FaExternalLinkAlt, FaRegEdit } from "react-icons/fa"
import { useRouter } from "next/router"
import { ReserveUrls } from "src/data/reserve-urls"
import Link from "next/link"
import PageContainer1 from "layouts/page-container1"
+import Empty from "components/empty"
+import { IDType } from "src/types/id"
var validator = require('validator');
-const newPost: Post = { title: '', url: '', cover: '' }
+const newPost: Story = {type: IDType.Post,title: '', url: '', cover: '' }
const PostsPage = () => {
const [currentPost, setCurrentPost] = useState(newPost)
const [posts, setPosts] = useState([])
@@ -26,7 +26,7 @@ const PostsPage = () => {
const router = useRouter()
const toast = useToast()
const getPosts = () => {
- requestApi.get(`/story/posts/editor`).then((res) => setPosts(res.data)).catch(_ => setPosts([]))
+ requestApi.get(`/story/posts/editor?type=${IDType.Post}`).then((res) => setPosts(res.data)).catch(_ => setPosts([]))
}
useEffect(() => {
@@ -64,7 +64,7 @@ const PostsPage = () => {
}
const submitPost = async (values, _) => {
- await requestApi.post(`/story/post`, values)
+ await requestApi.post(`/story`, values)
onClose()
toast({
description: "提交成功",
@@ -76,7 +76,7 @@ const PostsPage = () => {
getPosts()
}
- const editPost = (post: Post) => {
+ const editPost = (post: Story) => {
if (post.url.trim() === "") {
router.push(`/editor/post/${post.id}`)
} else {
@@ -119,20 +119,13 @@ const PostsPage = () => {
{
posts.length === 0 ?
- <>
-
-
-
-
- 你还没创建任何文章
-
- >
+
:
<>
{posts.map(post =>
- editPost(post)} onDelete={() => onDeletePost(post.id)} />
+ editPost(post)} onDelete={() => onDeletePost(post.id)} />
)}
diff --git a/pages/editor/series.tsx b/pages/editor/series.tsx
new file mode 100644
index 00000000..6376e406
--- /dev/null
+++ b/pages/editor/series.tsx
@@ -0,0 +1,180 @@
+import { Menu, MenuButton, MenuList, MenuItem, Text, Box, Heading, Image, HStack, Center, Button, Flex, Modal, ModalOverlay, ModalContent, ModalHeader, ModalBody, ModalFooter, FormControl, FormLabel, FormHelperText, Input, FormErrorMessage, VStack, Textarea, Divider, useColorModeValue, useToast } from "@chakra-ui/react"
+import Card from "components/card"
+import Sidebar from "layouts/sidebar/sidebar"
+import React, { useEffect, useState } from "react"
+import { editorLinks } from "src/data/links"
+import { requestApi } from "utils/axios/request"
+import { useDisclosure } from "@chakra-ui/react"
+import { Field, Form, Formik } from "formik"
+import { config } from "configs/config"
+import TextStoryCard from "components/story/text-story-card"
+import { Story } from "src/types/story"
+import { FaExternalLinkAlt, FaRegEdit } from "react-icons/fa"
+import { useRouter } from "next/router"
+import { ReserveUrls } from "src/data/reserve-urls"
+import Link from "next/link"
+import PageContainer1 from "layouts/page-container1"
+import Empty from "components/empty"
+import { IDType } from "src/types/id"
+var validator = require('validator');
+
+const newSeries: Story = { title: '', brief: '', cover: '',type: IDType.Series }
+const PostsPage = () => {
+ const [currentSeries, setCurrentSeries] = useState(null)
+ const [posts, setPosts] = useState([])
+ const router = useRouter()
+ const toast = useToast()
+ const getPosts = () => {
+ requestApi.get(`/story/posts/editor?type=${IDType.Series}`).then((res) => setPosts(res.data)).catch(_ => setPosts([]))
+ }
+
+ useEffect(() => {
+ getPosts()
+ }, [])
+
+
+ function validateTitle(value) {
+ let error
+ if (!value?.trim()) {
+ error = "标题不能为空"
+ }
+
+ if (value?.length > config.posts.titleMaxLen) {
+ error = "标题长度不能超过128"
+ }
+
+ return error
+ }
+
+ function validateUrl(value) {
+ let error
+ if (value && !validator.isURL(value)) {
+ error = "URL格式不合法"
+ }
+ return error
+ }
+
+ function validateBrief(value) {
+ let error
+ if (value && value.length > config.posts.briefMaxLen) {
+ error = `文本长度不能超过${config.posts.briefMaxLen}`
+ }
+ return error
+ }
+
+ const submitPost = async (values, _) => {
+ await requestApi.post(`/story`, values)
+ toast({
+ description: "提交成功",
+ status: "success",
+ duration: 2000,
+ isClosable: true,
+ })
+ setCurrentSeries(null)
+ getPosts()
+ }
+
+ const editPost = (post: Story) => {
+ if (post.url.trim() === "") {
+ router.push(`/editor/post/${post.id}`)
+ } else {
+ setCurrentSeries(post)
+ }
+ }
+
+ const onDeletePost = async (id) => {
+ await requestApi.delete(`/story/post/${id}`)
+ getPosts()
+ toast({
+ description: "删除成功",
+ status: "success",
+ duration: 2000,
+ isClosable: true,
+ })
+ }
+
+ return (
+ <>
+
+
+
+
+ {currentSeries ?
+
+ {(props) => (
+
+ )}
+ :
+ <>
+
+ 系列({posts.length})
+
+
+ {
+ posts.length === 0 ?
+ :
+ <>
+
+ {posts.map(post =>
+
+ editPost(post)} onDelete={() => onDeletePost(post.id)} showSource={false}/>
+
+
+ )}
+
+ 没有更多文章了
+ >
+ }
+ >}
+
+
+
+ >
+ )
+}
+export default PostsPage
+
diff --git a/pages/index.tsx b/pages/index.tsx
index 4f903e8a..c801c624 100644
--- a/pages/index.tsx
+++ b/pages/index.tsx
@@ -9,8 +9,8 @@ import {
Divider
} from "@chakra-ui/react"
import Card from "components/card"
-import Posts from "components/story/posts"
-import SimplePostCard from "components/story/simple-post-card"
+import Stories from "components/story/stories"
+import SimplePostCard from "components/story/simple-story-card"
import SEO from "components/seo"
import siteConfig from "configs/site-config"
import PageContainer1 from "layouts/page-container1"
@@ -69,7 +69,7 @@ const HomePage = () => {
-
+
@@ -106,7 +106,7 @@ export const HomeSidebar = () => {
-
+
diff --git a/pages/search/posts.tsx b/pages/search/posts.tsx
index 3cec2a03..d995cfc6 100644
--- a/pages/search/posts.tsx
+++ b/pages/search/posts.tsx
@@ -2,7 +2,7 @@ import { Box, Divider, Flex, HStack, Input } from "@chakra-ui/react"
import Card from "components/card"
import Empty from "components/empty"
import SEO from "components/seo"
-import Posts from "components/story/posts"
+import Stories from "components/story/stories"
import SearchFilters from "components/search-filters"
import siteConfig from "configs/site-config"
import PageContainer1 from "layouts/page-container1"
@@ -88,7 +88,7 @@ const PostsSearchPage = () => {
{results.length === 0 && }
{results.length > 0 &&
- }
+ }
diff --git a/pages/search/users.tsx b/pages/search/users.tsx
index af85c1b9..dcc178c8 100644
--- a/pages/search/users.tsx
+++ b/pages/search/users.tsx
@@ -2,7 +2,7 @@ import { Box, Divider, Flex, HStack, Input } from "@chakra-ui/react"
import Card from "components/card"
import Empty from "components/empty"
import SEO from "components/seo"
-import Posts from "components/story/posts"
+import Posts from "components/story/stories"
import SearchFilters from "components/search-filters"
import siteConfig from "configs/site-config"
import PageContainer1 from "layouts/page-container1"
@@ -14,7 +14,7 @@ import { SearchFilter } from "src/types/search"
import { requestApi } from "utils/axios/request"
import { addParamToUrl, removeParamFromUrl } from "utils/url"
-import PostAuthor from "components/story/post-author"
+import PostAuthor from "components/story/story-author"
import UserCard from "components/users/user-card"
import Users from "components/users/users"
diff --git a/pages/series/[id].tsx b/pages/series/[id].tsx
new file mode 100644
index 00000000..60e358b7
--- /dev/null
+++ b/pages/series/[id].tsx
@@ -0,0 +1,79 @@
+import { Box, Divider, Heading, HStack, Image} from "@chakra-ui/react"
+import Comments from "components/comments/comments"
+import { MarkdownRender } from "components/markdown-editor/render"
+import { StoryAuthor } from "components/story/story-author"
+import TagTextCard from "components/story/tag-text-card"
+import SEO from "components/seo"
+import siteConfig from "configs/site-config"
+import PostNav from "layouts/nav/post-nav"
+import PageContainer from "layouts/page-container"
+import { useRouter } from "next/router"
+import React, { useEffect, useState } from "react"
+import { Story } from "src/types/story"
+import { requestApi } from "utils/axios/request"
+import StorySidebar from "components/story/story-sidebar"
+
+const PostPage = () => {
+ const router = useRouter()
+ const id = router.query.id
+ const [post, setPost]: [Story, any] = useState(null)
+ useEffect(() => {
+ if (id) {
+ getData()
+ }
+ }, [id])
+
+
+ useEffect(() => {
+ if (router && router.asPath.indexOf("#comments") > -1) {
+ setTimeout(() => {
+ location.href = "#comments"
+ }, 100)
+ }
+ }, [router])
+
+ const getData = async () => {
+ const res = await requestApi.get(`/story/post/${id}`)
+ setPost(res.data)
+ }
+
+
+ return (
+ <>
+
+ {post && } mt="2rem">
+ <>
+
+
+
+
+ {post.title}
+
+
+
+
+
+
+
+ {post.rawTags.map(tag => )}
+
+
+
+
+
+
+
+
+ >
+
+ }
+ >
+ )
+}
+
+export default PostPage
+
+
diff --git a/pages/settings/profile.tsx b/pages/settings/profile.tsx
index ae2a04de..0b5cfbd3 100644
--- a/pages/settings/profile.tsx
+++ b/pages/settings/profile.tsx
@@ -6,15 +6,8 @@ import Sidebar from "layouts/sidebar/sidebar"
import React, { useEffect, useState } from "react"
import { adminLinks, settingLinks } from "src/data/links"
import { requestApi } from "utils/axios/request"
-import TagCard from "components/tags/tag-card"
-import { Post } from "src/types/posts"
import { useRouter } from "next/router"
-import Link from "next/link"
-import { ReserveUrls } from "src/data/reserve-urls"
-import { Tag } from "src/types/tag"
-import { route } from "next/dist/next-server/server/router"
import { Field, Form, Formik } from "formik"
-import useSession from "hooks/use-session"
import { config } from "configs/config"
import Tags from "components/tags/tags"
var validator = require('validator');
diff --git a/pages/tags/[name].tsx b/pages/tags/[name].tsx
index 986a1ef8..dfdf123f 100644
--- a/pages/tags/[name].tsx
+++ b/pages/tags/[name].tsx
@@ -1,19 +1,16 @@
-import { Box, Button, chakra, Flex, Heading, HStack, Image, Text, VStack } from "@chakra-ui/react"
+import { Box, Button, Flex, Heading, HStack, Image, Text, VStack } from "@chakra-ui/react"
import Card from "components/card"
-import Container from "components/container"
import Empty from "components/empty"
import { MarkdownRender } from "components/markdown-editor/render"
-import Posts from "components/story/posts"
+import Stories from "components/story/stories"
import SEO from "components/seo"
import siteConfig from "configs/site-config"
import useSession from "hooks/use-session"
-import Nav from "layouts/nav/nav"
-import PageContainer from "layouts/page-container"
import PageContainer1 from "layouts/page-container1"
import { useRouter } from "next/router"
import React, { useEffect, useState } from "react"
import { ReserveUrls } from "src/data/reserve-urls"
-import { Post } from "src/types/posts"
+import { Story } from "src/types/story"
import { Tag } from "src/types/tag"
import { requestApi } from "utils/axios/request"
import { isAdmin } from "utils/role"
@@ -23,7 +20,7 @@ import Count from "components/count"
const UserPage = () => {
const router = useRouter()
- const [posts, setPosts]: [Post[], any] = useState([])
+ const [posts, setPosts]: [Story[], any] = useState([])
const [tag, setTag]: [Tag, any] = useState(null)
const [followed, setFollowed] = useState(null)
@@ -81,7 +78,7 @@ const UserPage = () => {
:
-
+
}
diff --git a/server/internal/api/posts.go b/server/internal/api/posts.go
index 90386fcb..d3c4b0a3 100644
--- a/server/internal/api/posts.go
+++ b/server/internal/api/posts.go
@@ -7,11 +7,29 @@ import (
"github.com/imdotdev/im.dev/server/internal/story"
"github.com/imdotdev/im.dev/server/internal/user"
"github.com/imdotdev/im.dev/server/pkg/common"
+ "github.com/imdotdev/im.dev/server/pkg/e"
+ "github.com/imdotdev/im.dev/server/pkg/models"
)
func GetEditorPosts(c *gin.Context) {
user := user.CurrentUser(c)
- ars, err := story.UserPosts(user, user.ID)
+ tp := c.Query("type")
+ if !models.ValidStoryIDType(tp) {
+ c.JSON(http.StatusBadRequest, common.RespError(e.ParamInvalid))
+ return
+ }
+ ars, err := story.UserPosts(tp, user, user.ID)
+ if err != nil {
+ c.JSON(err.Status, common.RespError(err.Message))
+ return
+ }
+
+ c.JSON(http.StatusOK, common.RespSuccess(ars))
+}
+
+func GetEditorDrafts(c *gin.Context) {
+ user := user.CurrentUser(c)
+ ars, err := story.UserDrafts(nil, user.ID)
if err != nil {
c.JSON(err.Status, common.RespError(err.Message))
return
@@ -25,7 +43,7 @@ func GetUserPosts(c *gin.Context) {
user := user.CurrentUser(c)
- posts, err := story.UserPosts(user, userID)
+ posts, err := story.UserPosts(models.IDTypeUndefined, user, userID)
if err != nil {
c.JSON(err.Status, common.RespError(err.Message))
return
diff --git a/server/internal/api/story.go b/server/internal/api/story.go
index 0981409d..f45c0a6e 100644
--- a/server/internal/api/story.go
+++ b/server/internal/api/story.go
@@ -11,8 +11,18 @@ import (
"github.com/imdotdev/im.dev/server/pkg/e"
)
-func SubmitPost(c *gin.Context) {
- res, err := story.SubmitPost(c)
+func SubmitStory(c *gin.Context) {
+ res, err := story.SubmitStory(c)
+ if err != nil {
+ c.JSON(err.Status, common.RespError(err.Message))
+ return
+ }
+
+ c.JSON(http.StatusOK, common.RespSuccess(res))
+}
+
+func SubmitPostDraft(c *gin.Context) {
+ res, err := story.SubmitPostDraft(c)
if err != nil {
c.JSON(err.Status, common.RespError(err.Message))
return
@@ -48,11 +58,11 @@ func DeletePost(c *gin.Context) {
c.JSON(http.StatusOK, common.RespSuccess(nil))
}
-func GetStoryPost(c *gin.Context) {
+func GetStory(c *gin.Context) {
id := c.Param("id")
user := user.CurrentUser(c)
- ar, err := story.GetPost(id, "")
+ ar, err := story.GetStory(id, "")
if err != nil {
c.JSON(err.Status, common.RespError(err.Message))
return
diff --git a/server/internal/cache/cache.go b/server/internal/cache/cache.go
index a8f1ae68..ce6a8d83 100644
--- a/server/internal/cache/cache.go
+++ b/server/internal/cache/cache.go
@@ -13,6 +13,7 @@ var logger = log.RootLogger.New("logger", "cache")
var Users []*models.User
func Init() {
+ time.Sleep(10 * time.Second)
for {
// load users
rows, err := db.Conn.Query(`SELECT id,username,role,nickname,email,avatar,last_seen_at,created FROM user`)
diff --git a/server/internal/search/search.go b/server/internal/search/search.go
index 659067d5..f4d626b4 100644
--- a/server/internal/search/search.go
+++ b/server/internal/search/search.go
@@ -14,14 +14,14 @@ import (
var logger = log.RootLogger.New("logger", "search")
-func Posts(user *models.User, filter, query string) []*models.Post {
- posts := make([]*models.Post, 0)
+func Posts(user *models.User, filter, query string) []*models.Story {
+ posts := make([]*models.Story, 0)
// postsMap := make(map[string]*models.Post)
// search by title
sqlq := "%" + query + "%"
- rows, err := db.Conn.Query("select id,slug,title,url,cover,brief,creator,created,updated from posts where title LIKE ? or brief LIKE ?", sqlq, sqlq)
+ rows, err := db.Conn.Query("select id,slug,title,url,cover,brief,creator,created,updated from story where status=? and (title LIKE ? or brief LIKE ?)", models.StatusPublished, sqlq, sqlq)
if err != nil {
logger.Warn("get user posts error", "error", err)
return posts
@@ -30,9 +30,9 @@ func Posts(user *models.User, filter, query string) []*models.Post {
posts = story.GetPosts(user, rows)
if filter == models.FilterFavorites {
- sort.Sort(models.FavorPosts(posts))
+ sort.Sort(models.FavorStories(posts))
} else {
- sort.Sort(models.Posts(posts))
+ sort.Sort(models.Stories(posts))
}
return posts
diff --git a/server/internal/server.go b/server/internal/server.go
index 56de9800..6726e1ee 100644
--- a/server/internal/server.go
+++ b/server/internal/server.go
@@ -45,13 +45,15 @@ func (s *Server) Start() error {
r := router.Group("/api")
//story apis
- r.GET("/story/post/:id", api.GetStoryPost)
+ r.GET("/story/post/:id", api.GetStory)
r.GET("/story/comments/:id", api.GetStoryComments)
r.POST("/story/comment", IsLogin(), api.SubmitComment)
r.DELETE("/story/comment/:id", IsLogin(), api.DeleteStoryComment)
r.GET("/story/posts/editor", IsLogin(), api.GetEditorPosts)
+ r.GET("/story/posts/drafts", IsLogin(), api.GetEditorDrafts)
r.GET("/story/posts/home/:filter", api.GetHomePosts)
- r.POST("/story/post", IsLogin(), api.SubmitPost)
+ r.POST("/story", IsLogin(), api.SubmitStory)
+ r.POST("/story/post/draft", IsLogin(), api.SubmitPostDraft)
r.DELETE("/story/post/:id", IsLogin(), api.DeletePost)
r.POST("/story/bookmark/:storyID", IsLogin(), api.Bookmark)
r.GET("/story/bookmark/posts", IsLogin(), api.GetBookmarkPosts)
diff --git a/server/internal/storage/init.go b/server/internal/storage/init.go
index 93ac1f87..f39818a5 100644
--- a/server/internal/storage/init.go
+++ b/server/internal/storage/init.go
@@ -28,7 +28,7 @@ func Init() error {
return err
}
- if err == sql.ErrNoRows {
+ if err != nil {
log.RootLogger.Info("Database tables have not been created, start creating")
err = initTables()
if err != nil {
diff --git a/server/internal/storage/sql_tables.go b/server/internal/storage/sql_tables.go
index a18bcf25..8bda580a 100644
--- a/server/internal/storage/sql_tables.go
+++ b/server/internal/storage/sql_tables.go
@@ -48,12 +48,13 @@ var sqlTables = map[string]string{
);
`,
- "posts": `CREATE TABLE IF NOT EXISTS posts (
+ "story": `CREATE TABLE IF NOT EXISTS story (
id VARCHAR(255) PRIMARY KEY,
+ type VARCHAR(1) NOT NULL,
creator VARCHAR(255) NOT NULL,
- slug VARCHAR(64) NOT NULL,
- title VARCHAR(255) NOT NULL,
- md TEXT,
+ slug VARCHAR(64) DEFAULT '',
+ title VARCHAR(255) DEFAULT '',
+ md TEXT DEFAULT '',
url VARCHAR(255),
cover VARCHAR(255),
brief TEXT,
@@ -61,10 +62,12 @@ var sqlTables = map[string]string{
created DATETIME NOT NULL,
updated DATETIME
);
- CREATE INDEX IF NOT EXISTS posts_creator
- ON posts (creator);
- CREATE INDEX IF NOT EXISTS posts_created
- ON posts (created);
+ CREATE INDEX IF NOT EXISTS story_type
+ ON story (type);
+ CREATE INDEX IF NOT EXISTS story_creator
+ ON story (creator);
+ CREATE INDEX IF NOT EXISTS story_created
+ ON story (created);
`,
"likes": `CREATE TABLE IF NOT EXISTS likes (
@@ -136,14 +139,14 @@ var sqlTables = map[string]string{
"comments": `CREATE TABLE IF NOT EXISTS comments (
id VARCHAR(255) PRIMARY KEY,
- target_id VARCHAR(255),
+ story_id VARCHAR(255),
creator VARCHAR(255),
MD TEXT,
created DATETIME NOT NULL,
updated DATETIME
);
- CREATE INDEX IF NOT EXISTS comments_targetid
- ON comments (target_id);
+ CREATE INDEX IF NOT EXISTS comments_storyid
+ ON comments (story_id);
CREATE INDEX IF NOT EXISTS comments_creator
ON comments (creator);
`,
diff --git a/server/internal/story/comment.go b/server/internal/story/comment.go
index 8ea0721e..bedd4634 100644
--- a/server/internal/story/comment.go
+++ b/server/internal/story/comment.go
@@ -18,7 +18,7 @@ func AddComment(c *models.Comment) *e.Error {
md := utils.Compress(c.Md)
now := time.Now()
- _, err := db.Conn.Exec("INSERT INTO comments (id,target_id,creator,md,created,updated) VALUES(?,?,?,?,?,?)",
+ _, err := db.Conn.Exec("INSERT INTO comments (id,story_id,creator,md,created,updated) VALUES(?,?,?,?,?,?)",
c.ID, c.TargetID, c.CreatorID, md, now, now)
if err != nil {
logger.Warn("add comment error", "error", err)
@@ -28,7 +28,7 @@ func AddComment(c *models.Comment) *e.Error {
// 更新story的comment数量
// 查询到该comment所属的story id
var storyID string
- err = db.Conn.QueryRow("select target_id from comments where id=?", c.TargetID).Scan(&storyID)
+ err = db.Conn.QueryRow("select story_id from comments where id=?", c.TargetID).Scan(&storyID)
if err != nil && err != sql.ErrNoRows {
logger.Warn("select comment error", "error", err)
} else {
@@ -74,7 +74,7 @@ func EditComment(c *models.Comment) *e.Error {
func GetComments(storyID string, sorter string) ([]*models.Comment, *e.Error) {
comments := make([]*models.Comment, 0)
- rows, err := db.Conn.Query("SELECT id,target_id,creator,md,created,updated FROM comments WHERE target_id=?", storyID)
+ rows, err := db.Conn.Query("SELECT id,story_id,creator,md,created,updated FROM comments WHERE story_id=?", storyID)
if err != nil && err != sql.ErrNoRows {
logger.Warn("get comments error", "error", err)
return comments, e.New(http.StatusInternalServerError, e.Internal)
@@ -112,7 +112,7 @@ func GetComments(storyID string, sorter string) ([]*models.Comment, *e.Error) {
func GetComment(id string) (*models.Comment, *e.Error) {
c := &models.Comment{}
var rawMd []byte
- err := db.Conn.QueryRow("SELECT id,target_id,creator,md,created,updated FROM comments WHERE id=?", id).Scan(
+ err := db.Conn.QueryRow("SELECT id,story_id,creator,md,created,updated FROM comments WHERE id=?", id).Scan(
&c.ID, &c.TargetID, &c.CreatorID, &rawMd, &c.Created, &c.Updated,
)
if err != nil {
@@ -139,7 +139,7 @@ func DeleteComment(id string) *e.Error {
count := 0
if isComment {
// 如果是评论,我们要计算replies的数量,因为会一起删除
- err := db.Conn.QueryRow("select count(*) from comments where target_id=?", id).Scan(&count)
+ err := db.Conn.QueryRow("select count(*) from comments where story_id=?", id).Scan(&count)
if err != nil && err != sql.ErrNoRows {
logger.Warn("select comment error", "error", err)
return e.New(http.StatusInternalServerError, e.Internal)
@@ -161,7 +161,7 @@ func DeleteComment(id string) *e.Error {
}
// delete children replies
- _, err = tx.Exec("DELETE FROM comments WHERE target_id=?", id)
+ _, err = tx.Exec("DELETE FROM comments WHERE story_id=?", id)
if err != nil {
logger.Warn("delete comment replies error", "error", err)
tx.Rollback()
@@ -192,7 +192,7 @@ func GetCommentCount(storyID string) int {
func GetStoryIDByCommentID(cid string) (string, bool, error) {
var targetID string
- err := db.Conn.QueryRow("select target_id from comments where id=?", cid).Scan(&targetID)
+ err := db.Conn.QueryRow("select story_id from comments where id=?", cid).Scan(&targetID)
if err != nil {
return "", false, err
}
@@ -202,7 +202,7 @@ func GetStoryIDByCommentID(cid string) (string, bool, error) {
return targetID, true, nil
case models.IDTypeComment:
var nid string
- err := db.Conn.QueryRow("select target_id from comments where id=?", targetID).Scan(&nid)
+ err := db.Conn.QueryRow("select story_id from comments where id=?", targetID).Scan(&nid)
if err != nil {
return "", false, err
}
diff --git a/server/internal/story/post.go b/server/internal/story/post.go
index 989a31c6..957c4369 100644
--- a/server/internal/story/post.go
+++ b/server/internal/story/post.go
@@ -20,15 +20,19 @@ import (
"github.com/imdotdev/im.dev/server/pkg/utils"
)
-func SubmitPost(c *gin.Context) (map[string]string, *e.Error) {
+func SubmitStory(c *gin.Context) (map[string]string, *e.Error) {
user := user.CurrentUser(c)
- post := &models.Post{}
+ post := &models.Story{}
err := c.Bind(&post)
if err != nil {
return nil, e.New(http.StatusBadRequest, e.ParamInvalid)
}
+ if !models.ValidStoryIDType(post.Type) {
+ return nil, e.New(http.StatusBadRequest, e.ParamInvalid)
+ }
+
if strings.TrimSpace(post.Title) == "" || utf8.RuneCountInString(post.Brief) > config.Data.Posts.BriefMaxLen {
return nil, e.New(http.StatusBadRequest, "标题格式不合法")
}
@@ -47,16 +51,15 @@ func SubmitPost(c *gin.Context) (map[string]string, *e.Error) {
}
if isExternal {
- // internal post, need creator role
- if !user.Role.IsCreator() {
+ // external post, need editor role
+ if !user.Role.IsEditor() {
return nil, e.New(http.StatusForbidden, e.NoEditorPermission)
}
} else {
- // external post, need editor role
- if !user.Role.IsEditor() {
+ // internal post, need creator role
+ if !user.Role.IsCreator() {
return nil, e.New(http.StatusForbidden, e.NoEditorPermission)
}
-
if len(post.Md) <= config.Data.Posts.BriefMaxLen {
post.Brief = post.Md
} else {
@@ -71,10 +74,10 @@ func SubmitPost(c *gin.Context) (map[string]string, *e.Error) {
setSlug(user.ID, post)
if post.ID == "" {
- post.ID = utils.GenID(models.IDTypePost)
+ post.ID = utils.GenID(post.Type)
//create
- _, err := db.Conn.Exec("INSERT INTO posts (id,creator,slug, title, md, url, cover, brief,status, created, updated) VALUES(?,?,?,?,?,?,?,?,?,?,?)",
- post.ID, user.ID, post.Slug, post.Title, md, post.URL, post.Cover, post.Brief, models.StatusPublished, now, now)
+ _, err := db.Conn.Exec("INSERT INTO story (id,type,creator,slug, title, md, url, cover, brief,status, created, updated) VALUES(?,?,?,?,?,?,?,?,?,?,?,?)",
+ post.ID, post.Type, user.ID, post.Slug, post.Title, md, post.URL, post.Cover, post.Brief, models.StatusPublished, now, now)
if err != nil {
logger.Warn("submit post error", "error", err)
return nil, e.New(http.StatusInternalServerError, e.Internal)
@@ -86,7 +89,7 @@ func SubmitPost(c *gin.Context) (map[string]string, *e.Error) {
return nil, e.New(http.StatusForbidden, e.NoEditorPermission)
}
- _, err = db.Conn.Exec("UPDATE posts SET slug=?, title=?, md=?, url=?, cover=?, brief=?, updated=? WHERE id=?",
+ _, err = db.Conn.Exec("UPDATE story SET slug=?, title=?, md=?, url=?, cover=?, brief=?, updated=? WHERE id=?",
post.Slug, post.Title, md, post.URL, post.Cover, post.Brief, now, post.ID)
if err != nil {
logger.Warn("upate post error", "error", err)
@@ -107,6 +110,66 @@ func SubmitPost(c *gin.Context) (map[string]string, *e.Error) {
}, nil
}
+func SubmitPostDraft(c *gin.Context) (map[string]string, *e.Error) {
+ user := user.CurrentUser(c)
+
+ post := &models.Story{}
+ err := c.Bind(&post)
+ if err != nil {
+ return nil, e.New(http.StatusBadRequest, e.ParamInvalid)
+ }
+
+ if !user.Role.IsCreator() {
+ return nil, e.New(http.StatusForbidden, e.NoEditorPermission)
+ }
+
+ md := utils.Compress(post.Md)
+
+ now := time.Now()
+
+ if len(post.Md) <= config.Data.Posts.BriefMaxLen {
+ post.Brief = post.Md
+ } else {
+ post.Brief = string([]rune(post.Md)[:config.Data.Posts.BriefMaxLen])
+ }
+
+ if post.ID == "" {
+ post.ID = utils.GenID(models.IDTypePost)
+ //create
+ _, err := db.Conn.Exec("INSERT INTO story (id,type,creator,slug, title, md, url, cover, brief,status, created, updated) VALUES(?,?,?,?,?,?,?,?,?,?,?,?)",
+ post.ID, models.IDTypePost, user.ID, post.Slug, post.Title, md, post.URL, post.Cover, post.Brief, models.StatusDraft, now, now)
+ fmt.Println(post.Brief)
+ if err != nil {
+ logger.Warn("submit post draft error", "error", err)
+ return nil, e.New(http.StatusInternalServerError, e.Internal)
+ }
+ } else {
+ // 只有创建者自己才能更新内容
+ creator, _ := GetPostCreator(post.ID)
+ if creator != user.ID {
+ return nil, e.New(http.StatusForbidden, e.NoEditorPermission)
+ }
+
+ _, err = db.Conn.Exec("UPDATE story SET slug=?, title=?, md=?, url=?, cover=?, brief=?, updated=? WHERE id=?",
+ post.Slug, post.Title, md, post.URL, post.Cover, post.Brief, now, post.ID)
+ if err != nil {
+ logger.Warn("upate post draft error", "error", err)
+ return nil, e.New(http.StatusInternalServerError, e.Internal)
+ }
+ }
+
+ //update tags
+ err = tags.UpdateTargetTags(user.ID, post.ID, post.Tags)
+ if err != nil {
+ logger.Warn("upate tags error", "error", err)
+ return nil, e.New(http.StatusInternalServerError, e.Internal)
+ }
+
+ return map[string]string{
+ "id": post.ID,
+ }, nil
+}
+
func DeletePost(id string) *e.Error {
tx, err := db.Conn.Begin()
if err != nil {
@@ -114,7 +177,7 @@ func DeletePost(id string) *e.Error {
return e.New(http.StatusInternalServerError, e.Internal)
}
- _, err = tx.Exec("DELETE FROM posts WHERE id=?", id)
+ _, err = tx.Exec("DELETE FROM story WHERE id=?", id)
if err != nil {
logger.Warn("delete post error", "error", err)
return e.New(http.StatusInternalServerError, e.Internal)
@@ -132,11 +195,11 @@ func DeletePost(id string) *e.Error {
return nil
}
-func GetPost(id string, slug string) (*models.Post, *e.Error) {
- ar := &models.Post{}
+func GetStory(id string, slug string) (*models.Story, *e.Error) {
+ ar := &models.Story{}
var rawmd []byte
- err := db.Conn.QueryRow("select id,slug,title,md,url,cover,brief,creator,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.Created, &ar.Updated,
+ err := db.Conn.QueryRow("select id,type,slug,title,md,url,cover,brief,creator,status,created,updated from story where id=?", id).Scan(
+ &ar.ID, &ar.Type, &ar.Slug, &ar.Title, &rawmd, &ar.URL, &ar.Cover, &ar.Brief, &ar.CreatorID, &ar.Status, &ar.Created, &ar.Updated,
)
if err != nil {
if err == sql.ErrNoRows {
@@ -165,7 +228,7 @@ func GetPost(id string, slug string) (*models.Post, *e.Error) {
func GetPostCreator(id string) (string, *e.Error) {
var uid string
- err := db.Conn.QueryRow("SELECT creator FROM posts WHERE id=?", id).Scan(&uid)
+ err := db.Conn.QueryRow("SELECT creator FROM story WHERE id=?", id).Scan(&uid)
if err != nil {
if err == sql.ErrNoRows {
return "", e.New(http.StatusNotFound, e.NotFound)
@@ -181,24 +244,13 @@ func GetPostCreator(id string) (string, *e.Error) {
// 1. 长度不能超过127
// 2. 每次title更新,都要重新生成slug
// 3. 单个用户下的slug不能重复,如果已经存在,需要加上-1这种字符
-func setSlug(creator string, post *models.Post) error {
+func setSlug(creator string, post *models.Story) error {
slug := utils.Slugify(post.Title)
if len(slug) > 100 {
slug = slug[:100]
}
- count := 0
- err := db.Conn.QueryRow("SELECT count(*) FROM posts WHERE creator=? and title=?", creator, post.Title).Scan(&count)
- if err != nil {
- logger.Warn("count slug error", "error", err)
- return err
- }
-
- if count == 0 {
- post.Slug = slug
- } else {
- post.Slug = fmt.Sprintf("%s-%d", slug, count)
- }
+ post.Slug = slug
return nil
}
diff --git a/server/internal/story/posts.go b/server/internal/story/posts.go
index 2f9d1f83..6770489f 100644
--- a/server/internal/story/posts.go
+++ b/server/internal/story/posts.go
@@ -14,9 +14,9 @@ import (
"github.com/imdotdev/im.dev/server/pkg/models"
)
-func HomePosts(user *models.User, filter string) (models.Posts, *e.Error) {
+func HomePosts(user *models.User, filter string) (models.Stories, *e.Error) {
- rows, err := db.Conn.Query("select id,slug,title,url,cover,brief,creator,created,updated from posts")
+ rows, err := db.Conn.Query("select id,type,slug,title,url,cover,brief,creator,created,updated from story where status=?", models.StatusPublished)
if err != nil && err != sql.ErrNoRows {
logger.Warn("get user posts error", "error", err)
return nil, e.New(http.StatusInternalServerError, e.Internal)
@@ -28,8 +28,15 @@ func HomePosts(user *models.User, filter string) (models.Posts, *e.Error) {
return posts, nil
}
-func UserPosts(user *models.User, uid string) (models.Posts, *e.Error) {
- rows, err := db.Conn.Query("select id,slug,title,url,cover,brief,creator,created,updated from posts where creator=?", uid)
+func UserPosts(tp string, user *models.User, uid string) (models.Stories, *e.Error) {
+ var rows *sql.Rows
+ var err error
+ if tp == models.IDTypeUndefined {
+ rows, err = db.Conn.Query("select id,type,slug,title,url,cover,brief,creator,created,updated from story where creator=? and status=?", uid, models.StatusPublished)
+ } else {
+ rows, err = db.Conn.Query("select id,type,slug,title,url,cover,brief,creator,created,updated from story where creator=? and type=? and status=?", uid, tp, models.StatusPublished)
+ }
+
if err != nil && err != sql.ErrNoRows {
logger.Warn("get user posts error", "error", err)
return nil, e.New(http.StatusInternalServerError, e.Internal)
@@ -41,7 +48,20 @@ func UserPosts(user *models.User, uid string) (models.Posts, *e.Error) {
return posts, nil
}
-func TagPosts(user *models.User, tagID string) (models.Posts, *e.Error) {
+func UserDrafts(user *models.User, uid string) (models.Stories, *e.Error) {
+ rows, err := db.Conn.Query("select id,type,slug,title,url,cover,brief,creator,created,updated from story where creator=? and status=?", uid, models.StatusDraft)
+ if err != nil && err != sql.ErrNoRows {
+ logger.Warn("get user drafts error", "error", err)
+ return nil, e.New(http.StatusInternalServerError, e.Internal)
+ }
+
+ posts := GetPosts(user, rows)
+
+ sort.Sort(posts)
+ return posts, nil
+}
+
+func TagPosts(user *models.User, tagID string) (models.Stories, *e.Error) {
// get post ids
postIDs, err := tags.GetTargetIDs(tagID)
if err != nil {
@@ -51,7 +71,7 @@ func TagPosts(user *models.User, tagID string) (models.Posts, *e.Error) {
ids := strings.Join(postIDs, "','")
- q := fmt.Sprintf("select id,slug,title,url,cover,brief,creator,created,updated from posts where id in ('%s')", ids)
+ q := fmt.Sprintf("select id,type,slug,title,url,cover,brief,creator,created,updated from story where id in ('%s') and status='%d'", ids, models.StatusPublished)
rows, err := db.Conn.Query(q)
if err != nil && err != sql.ErrNoRows {
logger.Warn("get user posts error", "error", err)
@@ -64,7 +84,7 @@ func TagPosts(user *models.User, tagID string) (models.Posts, *e.Error) {
return posts, nil
}
-func BookmarkPosts(user *models.User, filter string) (models.Posts, *e.Error) {
+func BookmarkPosts(user *models.User, filter string) (models.Stories, *e.Error) {
// get post ids
rows, err := db.Conn.Query("select story_id from bookmarks where user_id=?", user.ID)
if err != nil {
@@ -81,7 +101,7 @@ func BookmarkPosts(user *models.User, filter string) (models.Posts, *e.Error) {
ids := strings.Join(postIDs, "','")
- q := fmt.Sprintf("select id,slug,title,url,cover,brief,creator,created,updated from posts where id in ('%s')", ids)
+ q := fmt.Sprintf("select id,type,slug,title,url,cover,brief,creator,created,updated from story 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)
@@ -104,11 +124,11 @@ func BookmarkPosts(user *models.User, filter string) (models.Posts, *e.Error) {
return posts, nil
}
-func GetPosts(user *models.User, rows *sql.Rows) models.Posts {
- posts := make(models.Posts, 0)
+func GetPosts(user *models.User, rows *sql.Rows) models.Stories {
+ posts := make(models.Stories, 0)
for rows.Next() {
- ar := &models.Post{}
- err := rows.Scan(&ar.ID, &ar.Slug, &ar.Title, &ar.URL, &ar.Cover, &ar.Brief, &ar.CreatorID, &ar.Created, &ar.Updated)
+ ar := &models.Story{}
+ err := rows.Scan(&ar.ID, &ar.Type, &ar.Slug, &ar.Title, &ar.URL, &ar.Cover, &ar.Brief, &ar.CreatorID, &ar.Created, &ar.Updated)
if err != nil {
logger.Warn("scan post error", "error", err)
continue
diff --git a/server/pkg/common/reserve_urls.go b/server/pkg/common/reserve_urls.go
index 85e0a813..7e1e523e 100644
--- a/server/pkg/common/reserve_urls.go
+++ b/server/pkg/common/reserve_urls.go
@@ -10,6 +10,7 @@ var ReserverURLs = []string{
"/settings",
"/jobs",
"/books",
+ "/series",
"/notifications",
"/sponsors",
"/explore",
diff --git a/server/pkg/models/id_type.go b/server/pkg/models/id_type.go
index be85b4a3..a1375ab8 100644
--- a/server/pkg/models/id_type.go
+++ b/server/pkg/models/id_type.go
@@ -8,11 +8,13 @@ import (
)
const (
- IDUndefined = "0"
- IDTypePost = "1"
- IDTypeComment = "2"
- IDTypeUser = "3"
- IDTypeTag = "4"
+ IDTypeUndefined = "0"
+ IDTypeTag = "1"
+ IDTypeComment = "2"
+ IDTypeUser = "3"
+ IDTypePost = "4"
+ IDTypeSeries = "5"
+ IDTypeBook = "6"
)
func GetIDType(id string) string {
@@ -26,7 +28,11 @@ func GetIDType(id string) string {
func GetIdTypeTable(id string) string {
switch id[:1] {
case IDTypePost:
- return "posts"
+ return "story"
+ case IDTypeSeries:
+ return "story"
+ case IDTypeBook:
+ return "story"
case IDTypeComment:
return "comments"
case IDTypeUser:
@@ -34,7 +40,7 @@ func GetIdTypeTable(id string) string {
case IDTypeTag:
return "tags"
default:
- return IDUndefined
+ return IDTypeUndefined
}
}
@@ -44,7 +50,7 @@ func IdExist(id string) bool {
}
tbl := GetIdTypeTable(id)
- if tbl == IDUndefined {
+ if tbl == IDTypeUndefined {
return false
}
@@ -61,3 +67,11 @@ func IdExist(id string) bool {
return true
}
+
+func ValidStoryIDType(tp string) bool {
+ if tp == IDTypePost || tp == IDTypeSeries || tp == IDTypeBook {
+ return true
+ }
+
+ return false
+}
diff --git a/server/pkg/models/post.go b/server/pkg/models/story.go
similarity index 63%
rename from server/pkg/models/post.go
rename to server/pkg/models/story.go
index c6ba3815..695b3f27 100644
--- a/server/pkg/models/post.go
+++ b/server/pkg/models/story.go
@@ -8,8 +8,9 @@ const (
StatusHidden = 3
)
-type Post struct {
+type Story struct {
ID string `json:"id"`
+ Type string `json:"type"`
Creator *UserSimple `json:"creator"`
CreatorID string `json:"creatorId"`
Title string `json:"title"`
@@ -30,18 +31,18 @@ type Post struct {
Updated time.Time `json:"updated"`
}
-type Posts []*Post
+type Stories []*Story
-func (ar Posts) Len() int { return len(ar) }
-func (ar Posts) Swap(i, j int) { ar[i], ar[j] = ar[j], ar[i] }
-func (ar Posts) Less(i, j int) bool {
- return ar[i].Created.Unix() > ar[j].Created.Unix()
+func (s Stories) Len() int { return len(s) }
+func (s Stories) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
+func (s Stories) Less(i, j int) bool {
+ return s[i].Created.Unix() > s[j].Created.Unix()
}
-type FavorPosts []*Post
+type FavorStories []*Story
-func (ar FavorPosts) Len() int { return len(ar) }
-func (ar FavorPosts) Swap(i, j int) { ar[i], ar[j] = ar[j], ar[i] }
-func (ar FavorPosts) Less(i, j int) bool {
- return ar[i].Likes > ar[j].Likes
+func (s FavorStories) Len() int { return len(s) }
+func (s FavorStories) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
+func (s FavorStories) Less(i, j int) bool {
+ return s[i].Likes > s[j].Likes
}
diff --git a/src/components/comments/comments.tsx b/src/components/comments/comments.tsx
index 15cff5dc..b0513a8b 100644
--- a/src/components/comments/comments.tsx
+++ b/src/components/comments/comments.tsx
@@ -48,7 +48,7 @@ export const Comments = ({storyID}: Props) => {
Comments ({countComments()})
-