pull/50/head
sunface 4 years ago
parent 5c65dd3ab3
commit 271f852c01

@ -1,62 +1,25 @@
<div align="center">
<img src="/docs/assets/logo.png" alt="im.dev Logo" width="150">
<h1>欢迎使用im.dev</h1>
<strong>这里有最新的技术资讯、最好的开发教程,我们的目标是打造最优秀的开发者社区👩🏽‍💻</strong>
<h1>欢迎来到im.dev</h1>
<strong>我们的目标是打造世界上最好的开发者社区</strong>
<h6>Made with ❤️ by developers for developers</h6>
</div>
## 🗞 im.dev
- 官方域名https://im.dev (im.dev = I'm dev = 我是开发)
- 国际域名https://codecc.com (code see see)
#### website
- https://im.dev
im.dev是一个开源社区为开发者提供最新、最好的技术资讯和文章同时支持通过chrome浏览器插件的方式来使用只要打开新的标签页就能看到最新的技术资讯。
im.dev会从世界各大技术网站上收集优秀的文章进行集中呈现因此im.dev不是一个传统的博客平台更类似一个技术分享社区例如只要你的网站积分达到了要求就可以成为im.dev的编辑将你喜欢的文章推荐分享给更多的用户。
作为一个用户,你可以阅读、收藏、自定义喜好、评论和点赞等。作为一个编辑,你可以录入外部的文章链接,这篇文章将得到用户的评判,好的评判可以帮你获取积分和奖励,差的评判会影响你的积分,甚至编辑身份。
在im.dev我们最关心的
* 🌟 **跨平台**: 支持多个平台上使用网页、谷歌浏览器插件、手机app
* ♾ **质量**: 现在滥竽充数的技术文章太多了有些是过时信息有些是毫无营养的垃圾有些甚至包含了技术上的谬误因此内容质量是最重要的这也是我们为啥采用分享和推荐的方式只有优秀的精品才能在im.dev被分享被推荐
* 🧵 **开源**: im.dev是完全开源的我们相信最好的开发者社区永远是广大开发者一起参与打造的你的地盘你说了算因此欢迎大家参与到im.dev中来亲自打造你想要的开发者社区!
## 📯 设计理念
我们作为开发者,会花费大量时间寻找有价值的文章和博客,这些时间往往都被浪费了,本来时间就有限再加上时间的浪费,导致大多数人很难跟得上开发技术日新月异的发展速度。
因此im.dev想到了一个办法那就是让一部分人分享他们看过的优秀内容同时引入一套评价机制确保分享出来的都是真正的精品。最终分享者收获荣誉和奖励普通用户节约了时间、收获了内容。
* 👨‍💻 推送最新的技术内容
* ⏳ 帮用户节省时间
* 📰 通过评价机制留下精品内容
## 🗂 技术剖析
下面是我们在im.dev中用到的技术栈
* 🎨 **Web端** Typescript + React + Next.js + Antd
* 🌳 **服务器端** Go + Mysql
* 🔍 **站内搜索** Algolia
* 🚨 **移动APP** Flutter
为了方便扩展和社区贡献在im.dev中大量使用了插件的形式例如用户可以打造自己的个性化主页编辑可以设置个性化的文章展示等。
## 🙌 想要参与到im.dev中来吗
我们欢迎所有类型的贡献,例如:
* 🤔 功能建议和反馈
* 🐛 申请成为编辑
* 📖 文档改进
* 👨‍💻 代码贡献
在参与贡献之前,请花些时间阅读我们的[贡献者指南](docs/contributing.md)。
## 🎩 核心开发者
来看看我们的核心开发者喜欢的话可以给他们个follow👋
* [@sunface](https://github.com/sunface)
im.dev是专门为开发者打造的技术社区针对目前市场上的博客平台/开发社区存在的问题im.dev解决了以下核心痛点:
1. 支持多种登录方式默认使用github直接登录无需繁琐的手机/邮箱认证
2. 开源版本可以直接在公司、组织内部搭建使用,功能强大
3. Github实时备份, 用户可以关联自己的个人仓库im.dev会自动将你创作的内容进行同步
4. 支持多种类型的创作形式:博文、系列文章、书籍等
5. 原生markdown编辑器同时提供了各种小组件帮助你拥有更加强大的创作表现能力
6. 打造自己的个性化博客主页
7. 帮助创作者盈利
8. 社区功能不再固化用户可以亲自参与到im.dev的开源开发中或者在线提出建议
## 📑 License

@ -1,6 +1,7 @@
import { requestApi } from "./axios/request"
import { requestApi } from "../src/utils/axios/request"
export let config = {
appName: "im.dev",
commonMaxlen: 255,
posts: {
titleMaxLen: 128,

@ -65,8 +65,8 @@ function HeaderContent() {
</chakra.a>
</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>)}
<HStack ml={{ base: 1, md: 4, lg: 12 }} fontSize="1rem">
{navLinks.map(link => <Box px={[0,0,4,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>
@ -76,7 +76,7 @@ function HeaderContent() {
align="center"
color={useColorModeValue("gray.500", "gray.400")}
>
<AlgoliaSearch />
{/* <AlgoliaSearch /> */}
<Link
aria-label="Go to Chakra UI GitHub page"
href={siteConfig.repo.url}
@ -88,6 +88,7 @@ function HeaderContent() {
variant="ghost"
color="current"
_focus={null}
display={{ base: "none", md: "block" }}
icon={<FaGithub />}
/>
</Link>

@ -115,7 +115,7 @@ import { getSvgIcon } from "components/svg-icon"
/> */}
</VStack>
</Flex>
<MobileNavContent isOpen={mobileNav.isOpen} onClose={mobileNav.onClose} />
{/* <MobileNavContent isOpen={mobileNav.isOpen} onClose={mobileNav.onClose} /> */}
</>
)
}

@ -34,8 +34,6 @@ type PageContainerProps = PropsOf<typeof chakra.div> & {
function PageContainer1(props: PageContainerProps) {
const { children ,nav, ...rest} = props
useHeadingFocusOnRouteChange()
const [isSmallScreen] = useMediaQuery("(max-width: 768px)")
const header = isSmallScreen ? <Nav /> : <VerticalNav width={["100px","100px","200px","200px"]} />
return (
<>
<SEO
@ -43,8 +41,9 @@ function PageContainer1(props: PageContainerProps) {
description={siteConfig.seo.description}
/>
<Flex px={[0,0,16,16]}>
{header}
<Box width="100%" ml={["0px","0px","150px","150px"]} pb="8" p={["1","1","4","4"]} mt={["70px","70px","0px","0px"]} {...rest}>
<VerticalNav display={{base:"none",md:"block"}} width={["100px","100px","200px","200px"]} />
<Nav display={{base:"block",md:"none"}} />
<Box width="100%" ml={["0px","0px","150px","150px"]} pb="8" p={["1","1","3","3"]} mt={["70px","70px","0px","0px"]} {...rest}>
{children}
</Box>
</Flex>

@ -8,7 +8,7 @@ import theme from "theme"
import FontFace from "src/components/font-face"
import { getSeo } from "utils/seo"
import GAScript from "analytics/ga-script"
import {initUIConfig} from 'src/utils/config'
import {initUIConfig} from 'configs/config'
Router.events.on("routeChangeComplete", (url) => {
trackPageview(url)

@ -6,7 +6,7 @@ import { EditMode } from 'src/types/editor';
import { MarkdownRender } from 'components/markdown-editor/render';
import { requestApi } from 'utils/axios/request';
import { useRouter } from 'next/router';
import { config } from 'utils/config';
import { config } from 'configs/config';
import { cloneDeep } from 'lodash';
import { FaMoon, FaSun } from 'react-icons/fa';
import Link from 'next/link';

@ -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-edit-card"
import TagCard from "components/tags/tag-card"
import { Post } from "src/types/posts"
import { useRouter } from "next/router"
import Link from "next/link"

@ -8,7 +8,7 @@ import { MarkdownRender } from 'components/markdown-editor/render';
import { Post } from 'src/types/posts';
import { requestApi } from 'utils/axios/request';
import { useRouter } from 'next/router';
import { config } from 'utils/config';
import { config } from 'configs/config';
import { cloneDeep } from 'lodash';
import Card from 'components/card';

@ -8,7 +8,7 @@ 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 "utils/config"
import { config } from "configs/config"
import TextPostCard from "components/posts/text-post-card"
import { Post } from "src/types/posts"
import { FaExternalLinkAlt, FaRegEdit } from "react-icons/fa"

@ -13,14 +13,15 @@ import PostCard from "components/posts/post-card"
import Posts from "components/posts/posts"
import SimplePostCard from "components/posts/simple-post-card"
import SEO from "components/seo"
import { getSvgIcon } from "components/svg-icon"
import siteConfig from "configs/site-config"
import PageContainer1 from "layouts/page-container1"
import React, { useEffect, useState } from "react"
import { PostFilter } from "src/types/posts"
import { PostFilter } from "src/types/posts"
import { requestApi } from "utils/axios/request"
const HomePage = () => {
const [posts,setPosts] = useState([])
const [posts, setPosts] = useState([])
const [filter, setFilter] = useState(PostFilter.Best)
const initData = async () => {
const res = await requestApi.get(`/home/posts/${filter}`)
@ -29,7 +30,7 @@ const HomePage = () => {
useEffect(() => {
initData()
},[filter])
}, [filter])
return (
<>
@ -43,9 +44,9 @@ const HomePage = () => {
<Card p="2">
<Flex justifyContent="space-between" alignItems="center">
<HStack>
<Button _focus={null} onClick={() => setFilter(PostFilter.Best)} size="sm" colorScheme={filter === PostFilter.Best ? 'teal' : null} leftIcon={<svg fill="currentColor" height="1.4rem" viewBox="0 0 448 512"><path d="M448 281.6c0-53.27-51.98-163.13-124.44-230.4-20.8 19.3-39.58 39.59-56.22 59.97C240.08 73.62 206.28 35.53 168 0 69.74 91.17 0 209.96 0 281.6 0 408.85 100.29 512 224 512c.53 0 1.04-.08 1.58-.08.32 0 .6.08.92.08 1.88 0 3.71-.35 5.58-.42C352.02 507.17 448 406.04 448 281.6zm-416 0c0-50.22 47.51-147.44 136.05-237.09 27.38 27.45 52.44 56.6 73.39 85.47l24.41 33.62 26.27-32.19a573.83 573.83 0 0130.99-34.95C379.72 159.83 416 245.74 416 281.6c0 54.69-21.53 104.28-56.28 140.21 12.51-35.29 10.88-75.92-8.03-112.02a357.34 357.34 0 00-10.83-19.19l-22.63-37.4-28.82 32.87-25.86 29.5c-24.93-31.78-59.31-75.5-63.7-80.54l-24.65-28.39-24.08 28.87C108.16 287 80 324.21 80 370.41c0 19.02 3.62 36.66 9.77 52.79C54.17 387.17 32 337.03 32 281.6zm193.54 198.32C162.86 479.49 112 437.87 112 370.41c0-33.78 21.27-63.55 63.69-114.41 6.06 6.98 86.48 109.68 86.48 109.68l51.3-58.52a334.43 334.43 0 019.87 17.48c23.92 45.66 13.83 104.1-29.26 134.24-17.62 12.33-39.14 19.71-62.37 20.73-2.06.07-4.09.29-6.17.31z"></path></svg>} variant="ghost" >Best</Button>
<Button _focus={null} onClick={() => setFilter(PostFilter.Featured)} size="sm" colorScheme={filter === PostFilter.Featured ? 'teal' : null} leftIcon={<svg fill="currentColor" height="1.4rem" viewBox="0 0 512 512"><path d="M493.7 232.4l-140.2-35 66.9-83.3c5.2-6.5 4.7-15.5-1.1-21.3-5.9-5.8-14.8-6.3-21.3-1.1l-83.4 66.7-35-140c-6.1-24.4-41-24.4-47.2 0l-35 140.2-83.3-67c-6.5-5.2-15.5-4.8-21.3 1.1-5.8 5.8-6.3 14.8-1.1 21.4l66.7 83.4-140 35C7.5 235.2 0 244.7 0 256c0 10.2 6.5 20.7 18.4 23.6l140.2 35-66.9 83.3c-5.2 6.5-4.7 15.5 1.1 21.3 5.6 5.5 14.5 6.5 21.3 1.1l83.4-66.7 35 140c3 11.9 13.3 18.4 23.6 18.4 4.5 0 19.4-2.1 23.6-18.4l35-140.2 83.3 67c6.9 5.5 15.8 4.4 21.3-1.1 5.8-5.8 6.3-14.8 1.1-21.3l-66.7-83.4 139.9-35c11.7-2.9 18.5-13.1 18.5-23.6-.1-10.3-6.6-20.6-18.4-23.6zM296 296l-40 160-40-160-160-40 160-40 40-160 40 160 160 40-160 40z"></path></svg>} variant="ghost">Fetured</Button>
<Button _focus={null} onClick={() => setFilter(PostFilter.Recent)} size="sm" colorScheme={filter === PostFilter.Recent ? 'teal' : null} leftIcon={<svg fill="currentColor" height="1.4rem" viewBox="0 0 512 512"><path d="M256 8C119 8 8 119 8 256s111 248 248 248 248-111 248-248S393 8 256 8zm216 248c0 118.7-96.1 216-216 216-118.7 0-216-96.1-216-216 0-118.7 96.1-216 216-216 118.7 0 216 96.1 216 216zm-148.9 88.3l-81.2-59c-3.1-2.3-4.9-5.9-4.9-9.7V116c0-6.6 5.4-12 12-12h14c6.6 0 12 5.4 12 12v146.3l70.5 51.3c5.4 3.9 6.5 11.4 2.6 16.8l-8.2 11.3c-3.9 5.3-11.4 6.5-16.8 2.6z"></path></svg>} variant="ghost">Fetured</Button>
<Button _focus={null} onClick={() => setFilter(PostFilter.Best)} size="sm" colorScheme={filter === PostFilter.Best ? 'teal' : null} leftIcon={getSvgIcon("hot")} variant="ghost" >Best</Button>
<Button _focus={null} onClick={() => setFilter(PostFilter.Featured)} size="sm" colorScheme={filter === PostFilter.Featured ? 'teal' : null} leftIcon={getSvgIcon("feature")} variant="ghost">Fetured</Button>
<Button _focus={null} onClick={() => setFilter(PostFilter.Recent)} size="sm" colorScheme={filter === PostFilter.Recent ? 'teal' : null} leftIcon={getSvgIcon("recent")} variant="ghost">Recent</Button>
</HStack>
<Menu>
<MenuButton
@ -82,7 +83,7 @@ export default HomePage
export const HomeSidebar = () => {
const [posts,setPosts] = useState([])
const [posts, setPosts] = useState([])
const [filter, setFilter] = useState(PostFilter.Best)
const initData = async () => {
const res = await requestApi.get(`/home/posts/${filter}`)
@ -91,22 +92,24 @@ export const HomeSidebar = () => {
useEffect(() => {
initData()
},[filter])
}, [filter])
return (
<VStack alignItems="left" width="30%" display={{ base: "none", md: "flex" }}>
<Card p="0">
<HStack px="4" py="3">
<Heading size="sm">Top ariticles</Heading>
<Card p="0">
<Flex px="4" py="3" justifyContent="space-between" alignItems="center">
<Heading size="sm"></Heading>
<HStack>
<Button variant="ghost" size="sm">1d</Button>
<Button variant="ghost" size="sm">1w</Button>
<Button variant="ghost" size="sm">1m</Button>
</HStack>
<Divider />
<VStack px="4" pt="1" alignItems="left">
<Posts posts={posts} card={SimplePostCard} size="sm" showFooter={false}></Posts>
</VStack>
</Card>
</Flex>
<Divider />
<VStack px="4" pt="3" alignItems="left">
<Posts posts={posts} card={SimplePostCard} size="sm" showFooter={false}></Posts>
</VStack>
</Card>
</VStack>
)
}

@ -6,7 +6,7 @@ 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/posts/tag-edit-card"
import TagCard from "components/tags/tag-card"
import { Post } from "src/types/posts"
import { useRouter } from "next/router"
import Link from "next/link"
@ -15,7 +15,7 @@ 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 "utils/config"
import { config } from "configs/config"
import Tags from "components/tags/tags"
var validator = require('validator');

@ -50,8 +50,8 @@ const UserPage = () => {
<HStack alignItems="top" spacing="4" p="2">
<VStack width={["100%","100%","70%","70%"]} alignItems="left" spacing="2">
<Card p="0">
<Image src={tag.cover} />
<Image src={tag.icon} width="80px" position="relative" top="-40px" left="40px" />
<Image src={tag.cover} maxHeight="250px"/>
<Image src={tag.icon} width="80px" position="relative" top="-40px" left="40px" className="shadowed"/>
<Flex justifyContent="space-between" alignItems="center" px="8" pb="6" mt="-1rem">
<Box>
<Heading size="lg">{tag.title}</Heading>

@ -1,25 +1,106 @@
import { chakra, HStack, VStack } from "@chakra-ui/react"
import {
chakra, Flex, Heading, HStack, Text, VStack, Menu,
MenuButton,
MenuList,
MenuItem,
IconButton,
Divider
} from "@chakra-ui/react"
import SEO from "components/seo"
import siteConfig from "configs/site-config"
import PageContainer1 from "layouts/page-container1"
import React from "react"
import {HomeSidebar} from 'pages/index'
const TagsPage = () => (
<>
<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">
</VStack>
<HomeSidebar />
</HStack>
</PageContainer1>
</>
)
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'
const tagsFilter = [{
name: 'Popular',
desc: 'Extremely active tags in terms of posts in the last 7 days.'
},
{
name: "Recently Added",
desc: "Tags that are recently added, sorted from newest to oldest."
},
{
name: "Most Followers",
desc: "Tags with the maximum number of followers and posts all time.",
},
{
name: "New Proposals",
desc: "Follow these tags to cast your vote. We periodically approve tags based on community interest."
}
]
const TagsPage = () => {
const [filter, setFilter] = useState(tagsFilter[0])
const [tags, setTags]: [Tag[], any] = useState([])
const getTags = () => {
requestApi.get(`/tags`).then((res) => setTags(res.data)).catch(_ => setTags([]))
}
useEffect(() => {
getTags()
}, [filter])
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">Tags On {config.appName}</Heading>
<Text layerStyle="textSecondary">Join communities on {config.appName}. Follow tags that interest you.</Text>
</VStack>
</Card>
<Card>
<Flex justifyContent="space-between" alignItems="top" p="3">
<VStack alignItems="left" width="80%">
<Heading size="md">{filter.name}</Heading>
<Text fontSize=".9rem">{filter.desc}</Text>
<Text fontSize=".9rem" layerStyle="textSecondary">List updated daily at midnight PST.</Text>
</VStack>
<Menu>
<MenuButton
as={IconButton}
aria-label="Options"
icon={<svg fill="none" stroke="currentColor" opacity="0.75" height="1.3rem" viewBox="0 0 55 55"><path d="M2 2h51v21H2V2zm0 30h51v21H2V32z" stroke="stroke-current" strokeWidth="4"></path></svg>}
size="xs"
variant="ghost"
_focus={null}
/>
<MenuList>
{
tagsFilter.map(f => <MenuItem onClick={() => setFilter(f)}>
{f.name}
</MenuItem>)
}
</MenuList>
</Menu>
</Flex>
<Divider mt="3" mb="5" />
<VStack alignItems="left" spacing="3">
{tags.map(t => <TagCard tag={t}/>)}
</VStack>
</Card>
</VStack>
<HomeSidebar />
</HStack>
</PageContainer1>
</>
)
}
export default TagsPage

@ -69,7 +69,7 @@ func SubmitTag(tag *models.Tag) *e.Error {
func GetTags() (models.Tags, *e.Error) {
tags := make(models.Tags, 0)
rows, err := db.Conn.Query("SELECT id,creator,title,name,icon,cover,created,updated from tags")
rows, err := db.Conn.Query("SELECT id,creator,title,md,name,icon,cover,created,updated from tags")
if err != nil {
if err == sql.ErrNoRows {
return tags, nil
@ -79,13 +79,18 @@ func GetTags() (models.Tags, *e.Error) {
}
for rows.Next() {
var rawMd []byte
tag := &models.Tag{}
err := rows.Scan(&tag.ID, &tag.Creator, &tag.Title, &tag.Name, &tag.Icon, &tag.Cover, &tag.Created, &tag.Updated)
err := rows.Scan(&tag.ID, &tag.Creator, &tag.Title, &rawMd, &tag.Name, &tag.Icon, &tag.Cover, &tag.Created, &tag.Updated)
if err != nil {
logger.Warn("scan tags error", "error", err)
continue
}
md, _ := utils.Uncompress(rawMd)
tag.Md = string(md)
tag.SetCover()
tags = append(tags, tag)
db.Conn.QueryRow("SELECT count(*) FROM tag_post WHERE tag_id=?", tag.ID).Scan(&tag.PostCount)
@ -125,5 +130,6 @@ func GetTag(id int64, name string) (*models.Tag, *e.Error) {
db.Conn.QueryRow("SELECT count(*) FROM tag_post WHERE tag_id=?", tag.ID).Scan(&tag.PostCount)
tag.SetCover()
return tag, nil
}

@ -9,6 +9,7 @@ import (
)
type UIConfig struct {
AppName string `json:"appName"`
CommonMaxLen int `json:"commonMaxlen"`
Posts *PostsConfig `json:"posts"`
User *UserConfig `json:"user"`
@ -29,6 +30,7 @@ type UserConfig struct {
// 在后台页面配置存储到mysql中
func GetUIConfig(c *gin.Context) {
conf := &UIConfig{
AppName: config.Data.Common.AppName,
CommonMaxLen: 255,
Posts: &PostsConfig{
TitleMaxLen: config.Data.Posts.TitleMaxLen,

@ -15,6 +15,7 @@ type Config struct {
Version string
LogLevel string `yaml:"log_level"`
IsProd bool `yaml:"is_prod"`
AppName string `yaml:"app_name"`
}
User struct {

@ -0,0 +1,7 @@
package models
const DefaultTagCover = "https://cdn.hashnode.com/res/hashnode/image/upload/v1605308227907/y6yl3YLT4.png?w=1600&h=320&fit=crop&crop=entropy&auto=compress&auto=compress"
//users
const DefaultAvatar = "https://cdn.hashnode.com/res/hashnode/image/upload/v1600792675173/rY-APy9Fc.png?auto=compress"
const DefaultCover = "https://cdn.hashnode.com/res/hashnode/image/upload/v1604243390177/JstCbDgbK.jpeg?w=1600&fit=crop&crop=entropy&auto=compress"

@ -15,6 +15,12 @@ type Tag struct {
Updated time.Time `json:"updated"`
}
func (t *Tag) SetCover() {
if t.Cover == "" {
t.Cover = DefaultTagCover
}
}
type Tags []*Tag
func (t Tags) Len() int { return len(t) }

@ -34,9 +34,6 @@ type User struct {
Created time.Time `json:"created"`
}
const DefaultAvatar = "https://cdn.hashnode.com/res/hashnode/image/upload/v1600792675173/rY-APy9Fc.png?auto=compress"
const DefaultCover = "https://cdn.hashnode.com/res/hashnode/image/upload/v1604243390177/JstCbDgbK.jpeg?w=1600&fit=crop&crop=entropy&auto=compress"
func (user *User) Query(id int64, username string, email string) error {
err := db.Conn.QueryRow(`SELECT id,username,role,nickname,email,avatar,last_seen_at,created FROM user WHERE id=? or username=? or email=?`,
id, username, email).Scan(&user.ID, &user.Username, &user.Role, &user.Nickname, &user.Email, &user.Avatar, &user.LastSeenAt, &user.Created)

@ -23,7 +23,6 @@ type Props = PropsOf<typeof chakra.div> & {
export function MarkdownEditor(props: Props) {
const bg = useColorModeValue(userCustomTheme.hoverBg.light,userCustomTheme.hoverBg.dark)
const [at,setAt] = useState('')
const [atUsers,setAtUsers]:[User[],any] = useState([])
@ -142,7 +141,7 @@ export function MarkdownEditor(props: Props) {
<VStack>
{
atUsers.map(user =>
<HStack key={user.id} py="3" px="5" cursor="pointer" onClick={() => selectAtUser(user)} _hover={{bg: bg}}>
<HStack key={user.id} py="3" px="5" cursor="pointer" onClick={() => selectAtUser(user)} className="hover-bg">
<Avatar src={user.avatar} size="sm"/>
<VStack alignItems="left">
{user.nickname !== '' && <Heading size="sm">{user.nickname}</Heading>}

@ -6,7 +6,7 @@ import { chakra,PropsOf} from '@chakra-ui/react';
import WebsiteLink from 'components/website-link';
import { cloneDeep, find, findIndex } from 'lodash';
import { isUsernameChar } from 'utils/user';
import { config } from 'utils/config';
import { config } from 'configs/config';
type Props = PropsOf<typeof chakra.div> & {

@ -19,8 +19,8 @@ export const Posts = (props: Props) => {
return (
<>
<VStack alignItems="left">
{posts.map(post =>
<Box py="2" borderBottom={`1px solid ${postBorderColor}`} key={post.id}>
{posts.map((post,i) =>
<Box py="2" borderBottom={i<posts.length-1 ? `1px solid ${postBorderColor}`:null} key={post.id}>
<Card post={post} size={props.size}/>
</Box>)}
</VStack>

@ -1,34 +0,0 @@
import React from "react"
import {chakra, 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"
type Props = PropsOf<typeof chakra.div> & {
tag: Tag
showActions: boolean
onEdit?: any
onDelete?: any
}
export const TagCard= (props:Props) =>{
const {tag,showActions,onEdit,onDelete, ...rest} = props
return (
<Flex justifyContent="space-between" {...rest}>
<NextLink href={`${ReserveUrls.Tags}/${tag.name}`}>
<Heading size="sm" display="flex" alignItems="center" cursor="pointer">
<Image src={tag.icon} width="43px" mr="2"/>
{tag.title}
</Heading>
</NextLink>
{props.showActions && <HStack>
<Button size="sm" colorScheme="teal" variant="outline" onClick={onEdit}>Edit</Button>
<Button size="sm" onClick={props.onDelete} variant="ghost">Delete</Button>
</HStack>}
</Flex>
)
}
export default TagCard

@ -9,7 +9,7 @@ interface Props {
}
export const TagCard= (props:Props) =>{
export const TagListCard= (props:Props) =>{
const {tag} = props
return (
@ -25,4 +25,4 @@ export const TagCard= (props:Props) =>{
)
}
export default TagCard
export default TagListCard

@ -1,6 +1,9 @@
export function getSvgIcon(name,height="1.4rem") {
let svg
switch (name) {
case "hot":
svg = <svg fill="currentColor" height={height} viewBox="0 0 448 512"><path d="M448 281.6c0-53.27-51.98-163.13-124.44-230.4-20.8 19.3-39.58 39.59-56.22 59.97C240.08 73.62 206.28 35.53 168 0 69.74 91.17 0 209.96 0 281.6 0 408.85 100.29 512 224 512c.53 0 1.04-.08 1.58-.08.32 0 .6.08.92.08 1.88 0 3.71-.35 5.58-.42C352.02 507.17 448 406.04 448 281.6zm-416 0c0-50.22 47.51-147.44 136.05-237.09 27.38 27.45 52.44 56.6 73.39 85.47l24.41 33.62 26.27-32.19a573.83 573.83 0 0130.99-34.95C379.72 159.83 416 245.74 416 281.6c0 54.69-21.53 104.28-56.28 140.21 12.51-35.29 10.88-75.92-8.03-112.02a357.34 357.34 0 00-10.83-19.19l-22.63-37.4-28.82 32.87-25.86 29.5c-24.93-31.78-59.31-75.5-63.7-80.54l-24.65-28.39-24.08 28.87C108.16 287 80 324.21 80 370.41c0 19.02 3.62 36.66 9.77 52.79C54.17 387.17 32 337.03 32 281.6zm193.54 198.32C162.86 479.49 112 437.87 112 370.41c0-33.78 21.27-63.55 63.69-114.41 6.06 6.98 86.48 109.68 86.48 109.68l51.3-58.52a334.43 334.43 0 019.87 17.48c23.92 45.66 13.83 104.1-29.26 134.24-17.62 12.33-39.14 19.71-62.37 20.73-2.06.07-4.09.29-6.17.31z"></path></svg>
break
case "home":
svg = <svg fill="currentColor" height={height} viewBox="0 0 576 512"><path d="M541 229.16l-232.85-190a32.16 32.16 0 00-40.38 0L35 229.16a8 8 0 00-1.16 11.24l10.1 12.41a8 8 0 0011.2 1.19L96 220.62v243a16 16 0 0016 16h128a16 16 0 0016-16v-128l64 .3V464a16 16 0 0016 16l128-.33a16 16 0 0016-16V220.62L520.86 254a8 8 0 0011.25-1.16l10.1-12.41a8 8 0 00-1.21-11.27zm-93.11 218.59h.1l-96 .3V319.88a16.05 16.05 0 00-15.95-16l-96-.27a16 16 0 00-16.05 16v128.14H128V194.51L288 63.94l160 130.57z"></path></svg>
break
@ -16,6 +19,12 @@ export function getSvgIcon(name,height="1.4rem") {
case "explore":
svg = <svg fill="currentColor" height={height} viewBox="0 0 496 512"><path d="M264.97 272.97c9.38-9.37 9.38-24.57 0-33.94-9.37-9.37-24.57-9.37-33.94 0-9.38 9.37-9.38 24.57 0 33.94 9.37 9.37 24.57 9.37 33.94 0zM351.44 125c-2.26 0-4.51.37-6.71 1.16l-154.9 55.85c-7.49 2.7-13.1 8.31-15.8 15.8l-55.85 154.91c-5.65 15.67 10.33 34.27 26.4 34.27 2.26 0 4.51-.37 6.71-1.16l154.9-55.85c7.49-2.7 13.1-8.31 15.8-15.8l55.85-154.9c5.64-15.67-10.33-34.28-26.4-34.28zm-58.65 175.79l-140.1 50.51 50.51-140.11 140.11-50.51-50.52 140.11zM248 8C111.03 8 0 119.03 0 256s111.03 248 248 248 248-111.03 248-248S384.97 8 248 8zm0 464c-119.1 0-216-96.9-216-216S128.9 40 248 40s216 96.9 216 216-96.9 216-216 216z"></path></svg>
break
case "feature":
svg = <svg fill="currentColor" height={height} viewBox="0 0 512 512"><path d="M493.7 232.4l-140.2-35 66.9-83.3c5.2-6.5 4.7-15.5-1.1-21.3-5.9-5.8-14.8-6.3-21.3-1.1l-83.4 66.7-35-140c-6.1-24.4-41-24.4-47.2 0l-35 140.2-83.3-67c-6.5-5.2-15.5-4.8-21.3 1.1-5.8 5.8-6.3 14.8-1.1 21.4l66.7 83.4-140 35C7.5 235.2 0 244.7 0 256c0 10.2 6.5 20.7 18.4 23.6l140.2 35-66.9 83.3c-5.2 6.5-4.7 15.5 1.1 21.3 5.6 5.5 14.5 6.5 21.3 1.1l83.4-66.7 35 140c3 11.9 13.3 18.4 23.6 18.4 4.5 0 19.4-2.1 23.6-18.4l35-140.2 83.3 67c6.9 5.5 15.8 4.4 21.3-1.1 5.8-5.8 6.3-14.8 1.1-21.3l-66.7-83.4 139.9-35c11.7-2.9 18.5-13.1 18.5-23.6-.1-10.3-6.6-20.6-18.4-23.6zM296 296l-40 160-40-160-160-40 160-40 40-160 40 160 160 40-160 40z"></path></svg>
break
case "recent":
svg = <svg fill="currentColor" height={height} viewBox="0 0 512 512"><path d="M256 8C119 8 8 119 8 256s111 248 248 248 248-111 248-248S393 8 256 8zm216 248c0 118.7-96.1 216-216 216-118.7 0-216-96.1-216-216 0-118.7 96.1-216 216-216 118.7 0 216 96.1 216 216zm-148.9 88.3l-81.2-59c-3.1-2.3-4.9-5.9-4.9-9.7V116c0-6.6 5.4-12 12-12h14c6.6 0 12 5.4 12 12v146.3l70.5 51.3c5.4 3.9 6.5 11.4 2.6 16.8l-8.2 11.3c-3.9 5.3-11.4 6.5-16.8 2.6z"></path></svg>
break
default:
break;
}

@ -0,0 +1,40 @@
import React from "react"
import {chakra, Heading, Image, Text, HStack,Button, Flex,PropsOf, Box, Tooltip, Tag as ChakraTag, useColorModeValue} from "@chakra-ui/react"
import { Tag } from "src/types/tag"
import { ReserveUrls } from "src/data/reserve-urls"
import NextLink from "next/link"
import userCustomTheme from "theme/user-custom"
type Props = PropsOf<typeof chakra.div> & {
tag: Tag
showActions?: boolean
onEdit?: any
onDelete?: any
}
export const TagCard= (props:Props) =>{
const {tag,showActions=false,onEdit,onDelete} = props
return (
<Flex justifyContent="space-between" alignItems="center" className="hover-bg" p="2">
<NextLink href={`${ReserveUrls.Tags}/${tag.name}`}>
<HStack cursor="pointer">
<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>
</Box>
</HStack>
</NextLink>
{showActions ?
<HStack>
<Button size="sm" colorScheme="teal" variant="outline" onClick={onEdit}>Edit</Button>
<Button size="sm" onClick={onDelete} variant="ghost">Delete</Button>
</HStack> :
<ChakraTag py="1" px="3" colorScheme="cyan">{tag.postCount} posts</ChakraTag>
}
</Flex>
)
}
export default TagCard

@ -4,7 +4,7 @@ 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"
import { config } from "configs/config"
interface Props {
options: Tag[]
selected: Tag[]

@ -26,7 +26,7 @@ export const WebsiteLink = ({type, url,...rest}: Props) => {
break;
}
return (
<Flex justifyContent="space-between" alignItems="center" cursor="pointer" as="a" href={url} target="_blank" py="1" px="2" layerStyle="textSecondary" fontSize="1.1rem" {...rest} _hover={{bg: useColorModeValue(userCustomTheme.hoverBg.light,userCustomTheme.hoverBg.dark)}}>
<Flex justifyContent="space-between" alignItems="center" cursor="pointer" as="a" href={url} target="_blank" py="1" px="2" layerStyle="textSecondary" fontSize="1.1rem" {...rest} className="hover-bg">
<HStack>
{icon0}
<Text ml="2">{title}</Text>

@ -3,6 +3,7 @@ import { mode } from "@chakra-ui/theme-tools"
import markdownEditor from 'theme/markdown-editor'
import markdownRender from 'theme/markdown-render'
import layerStyles from 'theme/layer-styles'
import userCustomTheme from "theme/user-custom"
const customTheme = extendTheme({
config: {
@ -22,6 +23,18 @@ const customTheme = extendTheme({
},
styles: {
global: (props) => ({
'.hover-bg:hover': {
background: mode(userCustomTheme.hoverBg.light,userCustomTheme.hoverBg.dark )(props),
borderRadius: '6px'
},
'.bordered' : {
border: `1px solid ${mode(userCustomTheme.borderColor.light,userCustomTheme.borderColor.dark )(props)}`,
borderRadius: '6px'
},
'.shadowed': {
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'
},
body: {
background: mode("white","gray.800" )(props),
color: mode("gray.700", "whiteAlpha.900")(props),

Loading…
Cancel
Save