From 91769f4c9f5c78ae64e87648f6607c3ae1251ad9 Mon Sep 17 00:00:00 2001 From: sunface Date: Thu, 25 Mar 2021 17:06:30 +0800 Subject: [PATCH] update --- go.mod | 5 +- go.sum | 73 ++++++++++++++++ pages/[username]/index.tsx | 53 +++++------- pages/editor/post/[id].tsx | 10 ++- pages/editor/series.tsx | 2 +- pages/index.tsx | 45 +++++----- pages/search/posts.tsx | 49 ++++------- pages/search/users.tsx | 2 +- pages/series/[id].tsx | 9 +- pages/settings/org/series/[org_id].tsx | 13 +-- pages/tags/[name].tsx | 45 +++++----- server/internal/api/posts.go | 53 ++++++++++-- server/internal/api/search.go | 10 ++- server/internal/interaction/like.go | 6 ++ server/internal/search/search.go | 18 ++-- server/internal/server.go | 31 ++++++- server/internal/storage/sql_tables.go | 4 +- server/internal/story/post.go | 29 ++++--- server/internal/story/posts.go | 88 ++++++++++++++----- server/internal/tags/tags.go | 46 +--------- server/internal/top/top.go | 113 +++++++++++++++++++++++++ server/internal/user/user.go | 4 +- server/pkg/db/db.go | 3 + server/pkg/models/search.go | 12 ++- server/pkg/models/tag.go | 46 ++++++++++ src/components/story/stories.tsx | 74 +++++++++++++--- src/components/story/story-filter.tsx | 39 +++++++++ src/components/svg-icon.tsx | 3 + src/hooks/use-infinite-scroll.js | 24 ++++++ src/types/search.ts | 9 +- 30 files changed, 668 insertions(+), 250 deletions(-) create mode 100644 server/internal/top/top.go create mode 100644 src/components/story/story-filter.tsx create mode 100644 src/hooks/use-infinite-scroll.js diff --git a/go.mod b/go.mod index 048b19f8..32d3deae 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.14 require ( github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef github.com/gin-gonic/gin v1.6.3 + github.com/go-redis/redis/v8 v8.8.0 github.com/go-stack/stack v1.8.0 github.com/golang/snappy v0.0.2 github.com/gosimple/slug v1.9.0 @@ -15,5 +16,7 @@ require ( github.com/spf13/cobra v1.1.1 github.com/spf13/pflag v1.0.5 golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c // indirect - gopkg.in/yaml.v2 v2.2.8 + gopkg.in/bufio.v1 v1.0.0-20140618132640-567b2bfa514e // indirect + gopkg.in/redis.v2 v2.3.2 + gopkg.in/yaml.v2 v2.3.0 ) diff --git a/go.sum b/go.sum index f6f2165e..8af860cd 100644 --- a/go.sum +++ b/go.sum @@ -25,7 +25,10 @@ github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24 github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= +github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= @@ -37,9 +40,12 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= @@ -57,6 +63,9 @@ github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD87 github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= github.com/go-playground/validator/v10 v10.2.0 h1:KgJ0snyC2R9VXYN2rneOtQcw5aHQB1Vv0sFl1UcHBOY= github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI= +github.com/go-redis/redis v6.15.9+incompatible h1:K0pv1D7EQUjfyoMql+r/jZqCLizCGKFlFgcHWWmHQjg= +github.com/go-redis/redis/v8 v8.8.0 h1:fDZP58UN/1RD3DjtTXP/fFZ04TFohSYhjZDkcDe2dnw= +github.com/go-redis/redis/v8 v8.8.0/go.mod h1:F7resOH5Kdug49Otu24RjHWwgK7u9AmtqWMnCV1iP5Y= github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= @@ -71,12 +80,22 @@ github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/snappy v0.0.2 h1:aeE13tS0IiQgFjYdoL8qN3K1N2bXXtI6Vi51/y7BpMw= github.com/golang/snappy v0.0.2/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= @@ -116,6 +135,7 @@ github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/inconshreveable/log15 v0.0.0-20201112154412-8562bdadbbac h1:n1DqxAo4oWPMvH1+v+DLYlMCecgumhhgnxAPdqDIFHI= github.com/inconshreveable/log15 v0.0.0-20201112154412-8562bdadbbac/go.mod h1:cOaXtrgN4ScfRrD9Bre7U1thNq5RtJ8ZoP4iXVGRj6o= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= @@ -166,7 +186,14 @@ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lN github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.15.0/go.mod h1:hF8qUzuuC8DJGygJH3726JnCZX4MYbRB8yFfISqnKUg= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.10.5/go.mod h1:gza4q3jKQJijlu05nKWRCW/GavJumGt8aNRxWg7mt48= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -212,6 +239,7 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= @@ -219,9 +247,17 @@ github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVM github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs= github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opentelemetry.io/otel v0.19.0 h1:Lenfy7QHRXPZVsw/12CWpxX6d/JkrX8wrx2vO8G80Ng= +go.opentelemetry.io/otel v0.19.0/go.mod h1:j9bF567N9EfomkSidSfmMwIwIBuP37AMAIzVW85OxSg= +go.opentelemetry.io/otel/metric v0.19.0 h1:dtZ1Ju44gkJkYvo+3qGqVXmf88tc+a42edOywypengg= +go.opentelemetry.io/otel/metric v0.19.0/go.mod h1:8f9fglJPRnXuskQmKpnad31lcLJ2VmNNqIsx/uIwBSc= +go.opentelemetry.io/otel/oteltest v0.19.0/go.mod h1:tI4yxwh8U21v7JD6R3BcA/2+RBoTKFexE/PJ/nSO7IA= +go.opentelemetry.io/otel/trace v0.19.0 h1:1ucYlenXIDA1OlHVLDZKX0ObXV5RLaq06DtUKz5e5zc= +go.opentelemetry.io/otel/trace v0.19.0/go.mod h1:4IXiNextNOpPnRlI4ryK69mn5iC84bjBWZQA5DXz/qg= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= @@ -230,6 +266,8 @@ golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnf golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -248,8 +286,10 @@ golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -262,6 +302,9 @@ golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -270,9 +313,11 @@ golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -283,13 +328,20 @@ golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42 h1:vEOn+mP2zCOVzKckCZy6YsCtDblrpj/w7B9nxGNELpg= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c h1:VwygUrnw9jn88c4u8GD3rZQbqrP/tgas88tPUbBxQrk= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -310,7 +362,12 @@ golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc h1:NCy3Ohtk6Iny5V/reW2Ktypo4zIpWBdRJ1uFMjBxdg8= golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= @@ -332,19 +389,35 @@ google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvx google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/bufio.v1 v1.0.0-20140618132640-567b2bfa514e h1:wGA78yza6bu/mWcc4QfBuIEHEtc06xdiU0X8sY36yUU= +gopkg.in/bufio.v1 v1.0.0-20140618132640-567b2bfa514e/go.mod h1:xsQCaysVCudhrYTfzYWe577fCe7Ceci+6qjO2Rdc0Z4= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/redis.v2 v2.3.2 h1:GPVIIB/JnL1wvfULefy3qXmPu1nfNu2d0yA09FHgwfs= +gopkg.in/redis.v2 v2.3.2/go.mod h1:4wl9PJ/CqzeHk3LVq1hNLHH8krm3+AXEgut4jVc++LU= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/pages/[username]/index.tsx b/pages/[username]/index.tsx index 6f05cb39..42b5961f 100644 --- a/pages/[username]/index.tsx +++ b/pages/[username]/index.tsx @@ -23,6 +23,7 @@ import UserCard from "components/users/user-card" import userCustomTheme from "theme/user-custom" import SearchFilters from "components/search-filters" import Follow from "components/interaction/follow" +import { SearchFilter } from "src/types/search" const UserPage = () => { const { isOpen, onOpen, onClose } = useDisclosure() @@ -31,10 +32,8 @@ const UserPage = () => { const nav = router.query.nav const session = useSession() const [user, setUser]: [User, any] = useState(null) - 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) + const [tagFilter, setTagFilter]: [string, any] = useState("") const [followers, setFollowers]: [User[], any] = useState([]) const [navbars,setNavbars]:[Navbar[],any] = useState([]) const borderColor = useColorModeValue('white', 'transparent') @@ -45,15 +44,17 @@ const UserPage = () => { } }, [username]) + + const loadStories = (p) => { + return user.type === IDType.User ? requestApi.get(`/user/posts/${user.id}?filter=${tagFilter}&page=${p}&per_page=5`) : requestApi.get(`/story/posts/org/${user.id}?type=0&filter=${tagFilter}&page=${p}&per_page=5`) + } + const initData = async (username) => { const res = await requestApi.get(`/user/info/${username}`) setUser(res.data) getTags(res.data.id) getNavbars(res.data.id) - const res1 = res.data.type === IDType.User ? await requestApi.get(`/user/posts/${res.data.id}`) : await requestApi.get(`/story/posts/org/${res.data.id}?type=0`) - setPosts(res1.data) - setRawPosts(res1.data) } const getNavbars = async userID => { @@ -66,26 +67,7 @@ const UserPage = () => { setTags(res.data) } - const filterPostsByTag = tag => { - if (tag.id === tagFilter?.id) { - setTagFilter(null) - setPosts(rawPosts) - return - } - setTagFilter(tag) - const p = [] - rawPosts.forEach(post => { - for (let i = 0; i < post.rawTags.length; i++) { - if (post.rawTags[i].id === tag.id) { - p.push(post) - break - } - } - }) - - setPosts(p) - } const viewFollowers = async tp => { let res @@ -115,6 +97,15 @@ const UserPage = () => { const isSubNavActive = (id) => { return id === nav } + + const onChangeTagFilter = id => { + if (id === tagFilter) { + setTagFilter("") + } else { + setTagFilter(id) + } + } + return ( <> { { tags.map(tag => - ) } @@ -245,15 +236,9 @@ const UserPage = () => { - {posts.length === 0 ? - - - - : - + {user.id && loadStories(p)} showPinned={true} showOrg={user.type === IDType.User} filter={tagFilter}/>} - } diff --git a/pages/editor/post/[id].tsx b/pages/editor/post/[id].tsx index 7df4cc06..d15b2d8d 100644 --- a/pages/editor/post/[id].tsx +++ b/pages/editor/post/[id].tsx @@ -1,4 +1,4 @@ -import { Box, Button, Text, useToast } from '@chakra-ui/react'; +import { Box, useToast } from '@chakra-ui/react'; import React, { useEffect, useState } from 'react'; import { MarkdownEditor } from 'components/markdown-editor/editor'; import PageContainer from 'layouts/page-container'; @@ -11,7 +11,6 @@ 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'; @@ -35,6 +34,10 @@ function PostEditPage() { if (id && id !== 'new') { requestApi.get(`/story/post/${id}`).then(res => setAr(res.data)) } + + if (id === 'new') { + + } }, [id]) const onMdChange = newMd => { @@ -72,6 +75,7 @@ function PostEditPage() { ar.id = res.data.id let url = window.location.origin + `/editor/post/${ar.id}` window.history.pushState({},null,url); + setAr(cloneDeep(ar)) } } @@ -84,7 +88,7 @@ function PostEditPage() { setAr(newAr) } - const onChangeTitle = title => { + const onChangeTitle = async title => { if (title.length > config.posts.titleMaxLen) { toast({ description: `Title长度不能超过${config.posts.titleMaxLen}`, diff --git a/pages/editor/series.tsx b/pages/editor/series.tsx index dd98e990..3c513161 100644 --- a/pages/editor/series.tsx +++ b/pages/editor/series.tsx @@ -109,7 +109,7 @@ export const SeriesEditor = ({orgID=""}) => { const submitSeries = async (values, _) => { // 这里必须按照顺序同步提交 if (orgID) { - await requestApi.post(`/story`, {...values,ownerID: orgID}) + await requestApi.post(`/story`, {...values,ownerId: orgID}) } else { await requestApi.post(`/story`, values) } diff --git a/pages/index.tsx b/pages/index.tsx index d1a30d29..1dcd4fe3 100644 --- a/pages/index.tsx +++ b/pages/index.tsx @@ -16,24 +16,17 @@ import siteConfig from "configs/site-config" import PageContainer1 from "layouts/page-container1" import React, { useEffect, useState } from "react" import { requestApi } from "utils/axios/request" - -import { SearchFilter } from "src/types/search" -import SearchFilters from "components/search-filters" - +import StoryFilters from "components/story/story-filter" +import { concat } from "lodash" +import useInfiniteScroll from 'src/hooks/use-infinite-scroll' const HomePage = () => { - let filter:string - const [posts, setPosts] = useState([]) - const initData = async () => { - const res = await requestApi.get(`/story/posts/home/${filter}`) - setPosts(res.data) + const [filter,setFilter] = useState('Best') + const initData = (p) => { + return requestApi.get(`/story/posts/home?filter=${filter}&page=${p}&per_page=5`) } - useEffect(() => { - initData() - }, []) - - const onFilterChange = filter => { - + const onFilterChange = f => { + setFilter(f) } return ( @@ -47,7 +40,7 @@ const HomePage = () => { - + { - + @@ -84,14 +77,14 @@ export default HomePage export const HomeSidebar = () => { const [posts, setPosts] = useState([]) - const initData = async () => { - const res = await requestApi.get(`/story/posts/home/aa`) - setPosts(res.data) - } + // const initData = async () => { + // const res = await requestApi.get(`/story/posts/home/aa`) + // setPosts(res.data) + // } - useEffect(() => { - initData() - }, []) + // useEffect(() => { + // initData() + // }, []) return ( @@ -106,9 +99,9 @@ export const HomeSidebar = () => { - + {/* - + */} diff --git a/pages/search/posts.tsx b/pages/search/posts.tsx index d995cfc6..8d25a242 100644 --- a/pages/search/posts.tsx +++ b/pages/search/posts.tsx @@ -1,9 +1,8 @@ -import { Box, Divider, Flex, HStack, Input } from "@chakra-ui/react" +import { Box, Divider, Flex, Input } from "@chakra-ui/react" import Card from "components/card" -import Empty from "components/empty" import SEO from "components/seo" import Stories from "components/story/stories" -import SearchFilters from "components/search-filters" +import SearchFilters from "components/search-filters" import siteConfig from "configs/site-config" import PageContainer1 from "layouts/page-container1" import Sidebar from "layouts/sidebar/sidebar" @@ -16,51 +15,41 @@ import { requestApi } from "utils/axios/request" import { addParamToUrl, removeParamFromUrl } from "utils/url" const PostsSearchPage = () => { - let filter = SearchFilter.Favorites const router = useRouter() const q = router.query.q - const [results,setResults] = useState([]) - const [query,setQuery] = useState("") - const [tempQuery,setTempQuery] = useState("") - + const [query, setQuery] = useState("") + const [tempQuery, setTempQuery] = useState("") + useEffect(() => { if (q) { setQuery(q as string) setTempQuery(q as string) - initData() } - },[q]) + }, [q]) - useEffect(() => { - initData() - },[query]) - - const initData = async () => { - if (query) { - const res = await requestApi.get(`/search/posts/${filter}?query=${query}`) - setResults(res.data) - } + const [filter, setFilter] = useState(SearchFilter.Favorites) + const initPosts = (p) => { + return requestApi.get(`/search/posts?query=${query}&filter=${filter}&page=${p}&per_page=5`) } const onFilterChange = f => { - filter = f - initData() + setFilter(f) } const startSearch = e => { if (e.keyCode == 13) { if (tempQuery === '') { removeParamFromUrl(["q"]) - setResults([]) } else { - addParamToUrl({q: tempQuery}) + addParamToUrl({ q: tempQuery }) } - setQuery(tempQuery) + setQuery("") + setTimeout(() => setQuery(tempQuery), 100) } } - function getFilters():[] { + function getFilters(): [] { for (const link of searchLinks) { if (link.path.indexOf("posts") > -1) { return link.filters @@ -78,17 +67,15 @@ const PostsSearchPage = () => { /> - + setTempQuery(e.currentTarget.value)} onKeyUp={(e) => startSearch(e)} size="lg" placeholder="type and enter to search..." variant="unstyled" /> - - - {results.length === 0 && } - {results.length > 0 && - } + + + {query && } diff --git a/pages/search/users.tsx b/pages/search/users.tsx index dcc178c8..7a0ee4e2 100644 --- a/pages/search/users.tsx +++ b/pages/search/users.tsx @@ -41,7 +41,7 @@ const PostsSearchPage = () => { const initData = async () => { if (query) { - const res = await requestApi.get(`/search/users/${filter}?query=${query}`) + const res = await requestApi.get(`/search/users?query=${query}&filter=${filter}`) setResults(res.data) } } diff --git a/pages/series/[id].tsx b/pages/series/[id].tsx index 01644bae..0030fdd1 100644 --- a/pages/series/[id].tsx +++ b/pages/series/[id].tsx @@ -14,6 +14,7 @@ import { requestApi } from "utils/axios/request" import StorySidebar from "components/story/story-sidebar" import Stroies from "components/story/stories" import Card from "components/card" +import StoryCard from "components/story/story-card" const PostPage = () => { const router = useRouter() @@ -73,7 +74,13 @@ const PostPage = () => { Articles in this series - + {posts.length > 0 && + + { + posts.map(p => ) + } + + } diff --git a/pages/settings/org/series/[org_id].tsx b/pages/settings/org/series/[org_id].tsx index 6eadabf4..3bb042b3 100644 --- a/pages/settings/org/series/[org_id].tsx +++ b/pages/settings/org/series/[org_id].tsx @@ -1,6 +1,4 @@ -import { Text, Box, VStack, Divider, useToast, Heading, Alert, Tag, Button, HStack, Modal, ModalOverlay, ModalContent, ModalBody, Select, useDisclosure, Flex } from "@chakra-ui/react" -import Card from "components/card" -import Nav from "layouts/nav/nav" +import { Box} from "@chakra-ui/react" import PageContainer from "layouts/page-container" import Sidebar from "layouts/sidebar/sidebar" import React, { useEffect, useState } from "react" @@ -8,15 +6,6 @@ import { orgSettingLinks } from "src/data/links" import { requestApi } from "utils/axios/request" import { useRouter } from "next/router" import { User } from "src/types/user" -import UserCard from "components/users/user-card" -import { config } from "configs/config" -import OrgMember from "components/users/org-member" -import { Role } from "src/types/role" -import { cloneDeep } from "lodash" -import { Story } from "src/types/story" -import Empty from "components/empty" -import ManageStories from "components/story/manage-stories" -import { IDType } from "src/types/id" import { SeriesEditor } from "pages/editor/series" diff --git a/pages/tags/[name].tsx b/pages/tags/[name].tsx index dfdf123f..45695c0f 100644 --- a/pages/tags/[name].tsx +++ b/pages/tags/[name].tsx @@ -16,11 +16,11 @@ import { requestApi } from "utils/axios/request" import { isAdmin } from "utils/role" import Follow from "components/interaction/follow" import Count from "components/count" +import StoryFilters from "components/story/story-filter" const UserPage = () => { const router = useRouter() - const [posts, setPosts]: [Story[], any] = useState([]) const [tag, setTag]: [Tag, any] = useState(null) const [followed, setFollowed] = useState(null) @@ -30,13 +30,18 @@ const UserPage = () => { } }, [tag]) - + const [filter, setFilter] = useState('Recent') + const initPosts = (p) => { + return requestApi.get(`/tag/posts/${tag.id}?filter=${filter}&page=${p}&per_page=5`) + } + + const onFilterChange = f => { + setFilter(f) + } + const initData = async () => { const res = await requestApi.get(`/tag/info/${router.query.name}`) setTag(res.data) - - const res1 = await requestApi.get(`/tag/posts/${res.data.id}`) - setPosts(res1.data) } useEffect(() => { @@ -55,38 +60,36 @@ const UserPage = () => { {tag && tag.name && - + - - + + {tag.title} #{tag.name} - {followed !== null && } + {followed !== null && } {isAdmin(session?.user.role) && } - { - posts.length === 0 ? - - - - : - - - - } + + + + + {tag.id && + + } + - + - + Followers diff --git a/server/internal/api/posts.go b/server/internal/api/posts.go index 5b1fe841..93562114 100644 --- a/server/internal/api/posts.go +++ b/server/internal/api/posts.go @@ -1,10 +1,13 @@ package api import ( + "fmt" "net/http" + "strconv" "github.com/gin-gonic/gin" "github.com/imdotdev/im.dev/server/internal/story" + "github.com/imdotdev/im.dev/server/internal/top" "github.com/imdotdev/im.dev/server/internal/user" "github.com/imdotdev/im.dev/server/pkg/common" "github.com/imdotdev/im.dev/server/pkg/e" @@ -18,7 +21,7 @@ func GetEditorPosts(c *gin.Context) { c.JSON(http.StatusBadRequest, common.RespError(e.ParamInvalid)) return } - ars, err := story.UserPosts(tp, user, user.ID) + ars, err := story.UserPosts(tp, user, user.ID, 0, 0) if err != nil { c.JSON(err.Status, common.RespError(err.Message)) return @@ -57,11 +60,23 @@ func GetEditorDrafts(c *gin.Context) { } func GetUserPosts(c *gin.Context) { + filter := c.Query("filter") + page, _ := strconv.ParseInt(c.Query("page"), 10, 64) + perPage, _ := strconv.ParseInt(c.Query("per_page"), 10, 64) + userID := c.Param("userID") user := user.CurrentUser(c) - posts, err := story.UserPosts(models.IDTypeUndefined, user, userID) + var posts []*models.Story + var err *e.Error + + if filter == "" { + posts, err = story.UserPosts(models.IDTypeUndefined, user, userID, page, perPage) + } else { + posts, err = story.UserTagPosts(user, filter, userID, page, perPage) + } + if err != nil { c.JSON(err.Status, common.RespError(err.Message)) return @@ -71,9 +86,23 @@ func GetUserPosts(c *gin.Context) { } func GetTagPosts(c *gin.Context) { + var posts []*models.Story + var err *e.Error + tagID := c.Param("id") user := user.CurrentUser(c) - posts, err := story.TagPosts(user, tagID) + + filter := c.Query("filter") + page, _ := strconv.ParseInt(c.Query("page"), 10, 64) + perPage, _ := strconv.ParseInt(c.Query("per_page"), 10, 64) + if filter == models.FilterLatest { + posts, err = story.TagPosts(user, tagID, page, perPage) + } else { + var ids []string + ids = top.GetTopList(fmt.Sprintf(top.TagFormat, tagID, filter), (page-1)*perPage, page*perPage) + posts, err = story.GetPostsByIDs(user, ids) + } + if err != nil { c.JSON(err.Status, common.RespError(err.Message)) return @@ -83,9 +112,23 @@ func GetTagPosts(c *gin.Context) { } func GetHomePosts(c *gin.Context) { - filter := c.Param("filter") + var posts []*models.Story + var err *e.Error user := user.CurrentUser(c) - posts, err := story.HomePosts(user, filter) + + filter := c.Query("filter") + page, _ := strconv.ParseInt(c.Query("page"), 10, 64) + perPage, _ := strconv.ParseInt(c.Query("per_page"), 10, 64) + if filter == models.FilterBest { + posts, err = story.HomePosts(user, filter, page, perPage) + } else if filter == models.FilterLatest { + posts, err = story.HomePosts(user, filter, page, perPage) + } else { + var ids []string + ids = top.GetTopList(top.GlobalPrefix+filter, (page-1)*perPage, page*perPage) + posts, err = story.GetPostsByIDs(user, ids) + } + if err != nil { c.JSON(err.Status, common.RespError(err.Message)) return diff --git a/server/internal/api/search.go b/server/internal/api/search.go index 6808f1bd..8259b381 100644 --- a/server/internal/api/search.go +++ b/server/internal/api/search.go @@ -2,6 +2,7 @@ package api import ( "net/http" + "strconv" "github.com/gin-gonic/gin" "github.com/imdotdev/im.dev/server/internal/search" @@ -12,21 +13,24 @@ import ( ) func SearchPosts(c *gin.Context) { - filter := c.Param("filter") + filter := c.Query("filter") query := c.Query("query") if !models.ValidSearchFilter(filter) || query == "" { c.JSON(http.StatusBadRequest, common.RespError(e.ParamInvalid)) return } + page, _ := strconv.ParseInt(c.Query("page"), 10, 64) + perPage, _ := strconv.ParseInt(c.Query("per_page"), 10, 64) + user := user.CurrentUser(c) - posts := search.Posts(user, filter, query) + posts := search.Posts(user, filter, query, page, perPage) c.JSON(http.StatusOK, common.RespSuccess(posts)) } func SearchUsers(c *gin.Context) { - filter := c.Param("filter") + filter := c.Query("filter") query := c.Query("query") if !models.ValidSearchFilter(filter) || query == "" { c.JSON(http.StatusBadRequest, common.RespError(e.ParamInvalid)) diff --git a/server/internal/interaction/like.go b/server/internal/interaction/like.go index e769a8fe..40ce8af7 100644 --- a/server/internal/interaction/like.go +++ b/server/internal/interaction/like.go @@ -5,6 +5,7 @@ import ( "net/http" "time" + "github.com/imdotdev/im.dev/server/internal/top" "github.com/imdotdev/im.dev/server/pkg/db" "github.com/imdotdev/im.dev/server/pkg/e" "github.com/imdotdev/im.dev/server/pkg/models" @@ -62,7 +63,12 @@ func Like(storyID string, userId string) *e.Error { return e.New(http.StatusInternalServerError, e.Internal) } + tx.Exec("UPDATE story SET likes=? WHERE id=?", count, storyID) + tx.Commit() + + top.Update(storyID, count) + return nil } diff --git a/server/internal/search/search.go b/server/internal/search/search.go index a7d1de04..9afdcc50 100644 --- a/server/internal/search/search.go +++ b/server/internal/search/search.go @@ -1,6 +1,7 @@ package search import ( + "database/sql" "sort" "strings" @@ -13,14 +14,21 @@ import ( var logger = log.RootLogger.New("logger", "search") -func Posts(user *models.User, filter, query string) []*models.Story { +func Posts(user *models.User, filter, query string, page, perPage int64) []*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,type,slug,title,url,cover,brief,creator,created,updated from story where status=? and (title LIKE ? or brief LIKE ?)", models.StatusPublished, sqlq, sqlq) + var rows *sql.Rows + var err error + if filter == models.FilterFavorites { + rows, err = db.Conn.Query(story.PostQueryPrefix+"where status=? and (title LIKE ? or brief LIKE ?) ORDER BY likes DESC LIMIT ?,?", models.StatusPublished, sqlq, sqlq, (page-1)*perPage, perPage) + } else { + rows, err = db.Conn.Query(story.PostQueryPrefix+"where status=? and (title LIKE ? or brief LIKE ?) ORDER BY created DESC LIMIT ?,?", models.StatusPublished, sqlq, sqlq, (page-1)*perPage, perPage) + } + if err != nil { logger.Warn("get user posts error", "error", err) return posts @@ -28,12 +36,6 @@ func Posts(user *models.User, filter, query string) []*models.Story { posts = story.GetPosts(user, rows) - if filter == models.FilterFavorites { - sort.Sort(models.FavorStories(posts)) - } else { - sort.Sort(models.Stories(posts)) - } - return posts } diff --git a/server/internal/server.go b/server/internal/server.go index 689231fd..81a93053 100644 --- a/server/internal/server.go +++ b/server/internal/server.go @@ -1,15 +1,19 @@ package internal import ( + "context" "net/http" + "time" "github.com/gin-gonic/gin" + "github.com/go-redis/redis/v8" "github.com/imdotdev/im.dev/server/internal/api" "github.com/imdotdev/im.dev/server/internal/cache" "github.com/imdotdev/im.dev/server/internal/storage" "github.com/imdotdev/im.dev/server/internal/user" "github.com/imdotdev/im.dev/server/pkg/common" "github.com/imdotdev/im.dev/server/pkg/config" + "github.com/imdotdev/im.dev/server/pkg/db" "github.com/imdotdev/im.dev/server/pkg/e" "github.com/imdotdev/im.dev/server/pkg/log" ) @@ -31,6 +35,10 @@ func (s *Server) Start() error { return err } + err = initRedis() + if err != nil { + return err + } // if config.Data.Common.IsProd { gin.SetMode((gin.ReleaseMode)) // } else { @@ -50,10 +58,11 @@ func (s *Server) Start() error { 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/org/:id", IsLogin(), api.GetOrgPosts) r.GET("/story/posts/drafts", IsLogin(), api.GetEditorDrafts) - r.GET("/story/posts/home/:filter", api.GetHomePosts) + r.GET("/story/posts/home", api.GetHomePosts) r.POST("/story", IsLogin(), api.SubmitStory) r.POST("/story/pin/:storyID", IsLogin(), api.PinStory) r.POST("/story/series", api.GetSeries) @@ -100,8 +109,8 @@ func (s *Server) Start() error { r.GET("/interaction/followers/:userID", api.GetFollowers) // search apis - r.GET("/search/posts/:filter", api.SearchPosts) - r.GET("/search/users/:filter", api.SearchUsers) + r.GET("/search/posts", api.SearchPosts) + r.GET("/search/users", api.SearchUsers) // org apis r.POST("/org/create", IsLogin(), api.CreateOrg) @@ -184,3 +193,19 @@ func InvasionCheck() gin.HandlerFunc { c.Next() } } + +func initRedis() error { + // init redis client + db.Redis = redis.NewClient(&redis.Options{ + Addr: "localhost:6379", + Password: "", // no password set + DB: 0, // use default DB + }) + ctx := context.Background() + err := db.Redis.Set(ctx, "test_redis", "alive", 10*time.Second).Err() + if err != nil { + return err + } + + return nil +} diff --git a/server/internal/storage/sql_tables.go b/server/internal/storage/sql_tables.go index 1b094fe4..b61cf10b 100644 --- a/server/internal/storage/sql_tables.go +++ b/server/internal/storage/sql_tables.go @@ -82,6 +82,7 @@ var sqlTables = map[string]string{ url VARCHAR(255), cover VARCHAR(255), brief TEXT, + likes INTEGER DEFAULT 0, status tinyint NOT NULL, created DATETIME NOT NULL, updated DATETIME @@ -154,7 +155,8 @@ var sqlTables = map[string]string{ tag_id VARCHAR(255), target_type VARCHAR(1), target_id VARCHAR(255), - target_creator VARCHAR(255) + target_creator VARCHAR(255), + target_created DATETIME ); CREATE INDEX IF NOT EXISTS tags_using_tagid ON tags_using (tag_id); diff --git a/server/internal/story/post.go b/server/internal/story/post.go index 72a198db..2565c04f 100644 --- a/server/internal/story/post.go +++ b/server/internal/story/post.go @@ -2,7 +2,6 @@ package story import ( "database/sql" - "fmt" "net/http" "strings" "time" @@ -13,6 +12,7 @@ import ( "github.com/imdotdev/im.dev/server/internal/interaction" "github.com/imdotdev/im.dev/server/internal/org" "github.com/imdotdev/im.dev/server/internal/tags" + "github.com/imdotdev/im.dev/server/internal/top" "github.com/imdotdev/im.dev/server/internal/user" "github.com/imdotdev/im.dev/server/pkg/config" "github.com/imdotdev/im.dev/server/pkg/db" @@ -99,9 +99,10 @@ func SubmitStory(c *gin.Context) (map[string]string, *e.Error) { post.ID = utils.GenID(post.Type) } + post.Created = now //create _, err := db.Conn.Exec("INSERT INTO story (id,type,creator,owner,slug, title, md, url, cover, brief,status, created, updated) VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?)", - post.ID, post.Type, post.CreatorID, post.OwnerID, post.Slug, post.Title, md, post.URL, post.Cover, post.Brief, models.StatusPublished, now, now) + post.ID, post.Type, post.CreatorID, post.OwnerID, post.Slug, post.Title, md, post.URL, post.Cover, post.Brief, models.StatusPublished, post.Created, post.Created) if err != nil { logger.Warn("submit post error", "error", err) return nil, e.New(http.StatusInternalServerError, e.Internal) @@ -113,8 +114,15 @@ func SubmitStory(c *gin.Context) (map[string]string, *e.Error) { return nil, e.New(http.StatusForbidden, e.NoEditorPermission) } - _, err = db.Conn.Exec("UPDATE story SET owner=?, slug=?, title=?, md=?, url=?, cover=?, brief=?, updated=? WHERE id=?", - post.OwnerID, post.Slug, post.Title, md, post.URL, post.Cover, post.Brief, now, post.ID) + if post.Status == models.StatusDraft { + // 首次发布,需要更新创建时间 + _, err = db.Conn.Exec("UPDATE story SET owner=?, slug=?, title=?, md=?, url=?, cover=?, brief=?,created=?,updated=?,status=? WHERE id=?", + post.OwnerID, post.Slug, post.Title, md, post.URL, post.Cover, post.Brief, now, now, models.StatusPublished, post.ID) + } else { + _, err = db.Conn.Exec("UPDATE story SET owner=?, slug=?, title=?, md=?, url=?, cover=?, brief=?, updated=? WHERE id=?", + post.OwnerID, post.Slug, post.Title, md, post.URL, post.Cover, post.Brief, now, post.ID) + } + if err != nil { logger.Warn("upate post error", "error", err) return nil, e.New(http.StatusInternalServerError, e.Internal) @@ -122,7 +130,7 @@ func SubmitStory(c *gin.Context) (map[string]string, *e.Error) { } //update tags - err = tags.UpdateTargetTags(user.ID, post.ID, post.Tags) + err = tags.UpdateTargetTags(user.ID, post.ID, post.Tags, post.Created) if err != nil { logger.Warn("upate tags error", "error", err) return nil, e.New(http.StatusInternalServerError, e.Internal) @@ -162,7 +170,6 @@ func SubmitPostDraft(c *gin.Context) (map[string]string, *e.Error) { //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) @@ -182,13 +189,6 @@ func SubmitPostDraft(c *gin.Context) (map[string]string, *e.Error) { } } - //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 @@ -216,6 +216,7 @@ func DeletePost(id string) *e.Error { tx.Commit() + top.Update(id, 0) return nil } @@ -244,7 +245,7 @@ func GetStory(id string, slug string) (*models.Story, *e.Error) { } // get tags - t, rawTags, err := tags.GetTargetTags(ar.ID) + t, rawTags, err := models.GetTargetTags(ar.ID) if err != nil { return nil, e.New(http.StatusInternalServerError, e.Internal) } diff --git a/server/internal/story/posts.go b/server/internal/story/posts.go index c57db227..735aa685 100644 --- a/server/internal/story/posts.go +++ b/server/internal/story/posts.go @@ -8,7 +8,6 @@ import ( "strings" "github.com/imdotdev/im.dev/server/internal/interaction" - "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" @@ -16,9 +15,9 @@ import ( const PostQueryPrefix = "select id,type,slug,title,url,cover,brief,creator,owner,created,updated from story " -func HomePosts(user *models.User, filter string) (models.Stories, *e.Error) { +func HomePosts(user *models.User, filter string, page int64, perPage int64) (models.Stories, *e.Error) { - rows, err := db.Conn.Query(PostQueryPrefix+"where status=?", models.StatusPublished) + rows, err := db.Conn.Query(PostQueryPrefix+"where status=? ORDER BY created DESC LIMIT ?,?", models.StatusPublished, (page-1)*perPage, perPage) if err != nil && err != sql.ErrNoRows { logger.Warn("get user posts error", "error", err) return nil, e.New(http.StatusInternalServerError, e.Internal) @@ -30,13 +29,21 @@ func HomePosts(user *models.User, filter string) (models.Stories, *e.Error) { return posts, nil } -func UserPosts(tp string, user *models.User, uid string) (models.Stories, *e.Error) { +func UserPosts(tp string, user *models.User, uid string, page int64, perPage int64) (models.Stories, *e.Error) { var rows *sql.Rows var err error if tp == models.IDTypeUndefined { - rows, err = db.Conn.Query(PostQueryPrefix+"where creator=? and status=?", uid, models.StatusPublished) + if perPage == 0 { + rows, err = db.Conn.Query(PostQueryPrefix+"where creator=? and status=?", uid, models.StatusPublished) + } else { + rows, err = db.Conn.Query(PostQueryPrefix+"where creator=? and status=? ORDER BY created DESC LIMIT ?,?", uid, models.StatusPublished, (page-1)*perPage, perPage) + } } else { - rows, err = db.Conn.Query(PostQueryPrefix+"where creator=? and type=? and status=?", uid, tp, models.StatusPublished) + if perPage == 0 { + rows, err = db.Conn.Query(PostQueryPrefix+"where creator=? and type=? and status=?", uid, tp, models.StatusPublished) + } else { + rows, err = db.Conn.Query(PostQueryPrefix+"where creator=? and type=? and status=? ORDER BY created DESC LIMIT ?,? ", uid, tp, models.StatusPublished, (page-1)*perPage, perPage) + } } if err != nil && err != sql.ErrNoRows { @@ -64,6 +71,28 @@ func UserPosts(tp string, user *models.User, uid string) (models.Stories, *e.Err return newPosts, nil } +func UserTagPosts(user *models.User, tid string, uid string, page int64, perPage int64) (models.Stories, *e.Error) { + ids := make([]string, 0) + rows, err := db.Conn.Query("SELECT target_id FROM tags_using WHERE tag_id=? and target_creator=? ORDER BY target_created DESC LIMIT ?,?", tid, uid, (page-1)*perPage, perPage) + if err != nil { + logger.Warn("get user posts error", "error", err) + return nil, e.New(http.StatusInternalServerError, e.Internal) + } + + for rows.Next() { + var id string + rows.Scan(&id) + ids = append(ids, id) + } + + posts, err1 := GetPostsByIDs(user, ids) + if err1 != nil { + return nil, err1 + } + + return posts, nil +} + func OrgPosts(tp string, user *models.User, orgID string) (models.Stories, *e.Error) { var rows *sql.Rows var err error @@ -111,26 +140,25 @@ func UserDrafts(user *models.User, uid string) (models.Stories, *e.Error) { return posts, nil } -func TagPosts(user *models.User, tagID string) (models.Stories, *e.Error) { - // get post ids - postIDs, err := tags.GetTargetIDs(tagID) +func TagPosts(user *models.User, tagID string, page, perPage int64) (models.Stories, *e.Error) { + ids := make([]string, 0) + rows, err := db.Conn.Query("SELECT target_id FROM tags_using WHERE tag_id=? and target_creator != ? ORDER BY target_created DESC LIMIT ?,?", tagID, "", (page-1)*perPage, perPage) if err != nil { logger.Warn("get user posts error", "error", err) return nil, e.New(http.StatusInternalServerError, e.Internal) } - ids := strings.Join(postIDs, "','") - - q := fmt.Sprintf(PostQueryPrefix+"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) - return nil, e.New(http.StatusInternalServerError, e.Internal) + for rows.Next() { + var id string + rows.Scan(&id) + ids = append(ids, id) } - posts := GetPosts(user, rows) + posts, err1 := GetPostsByIDs(user, ids) + if err1 != nil { + return nil, err1 + } - sort.Sort(posts) return posts, nil } @@ -161,7 +189,7 @@ func BookmarkPosts(user *models.User, filter string) (models.Stories, *e.Error) posts := GetPosts(user, rows) for _, post := range posts { - _, rawTags, err := tags.GetTargetTags(post.ID) + _, rawTags, err := models.GetTargetTags(post.ID) if err != nil { logger.Warn("get story tags error", "error", err) continue @@ -174,6 +202,26 @@ func BookmarkPosts(user *models.User, filter string) (models.Stories, *e.Error) return posts, nil } +func GetPostsByIDs(user *models.User, ids []string) ([]*models.Story, *e.Error) { + posts := make([]*models.Story, 0) + for _, id := range ids { + post, err := GetStory(id, "") + if err != nil { + continue + } + + // 获取当前登录用户的like + if user != nil { + post.Liked = interaction.GetLiked(post.ID, user.ID) + // 获取当前登录用户的bookmark + post.Bookmarked, _ = Bookmarked(user.ID, post.ID) + } + + posts = append(posts, post) + } + + return posts, nil +} func GetPosts(user *models.User, rows *sql.Rows) models.Stories { posts := make(models.Stories, 0) for rows.Next() { @@ -206,7 +254,7 @@ func GetPosts(user *models.User, rows *sql.Rows) models.Stories { } ar.Likes = interaction.GetLikes(ar.ID) - tags, rawTags, err := tags.GetTargetTags(ar.ID) + tags, rawTags, err := models.GetTargetTags(ar.ID) if err != nil { logger.Warn("get tags error", "error", err) } diff --git a/server/internal/tags/tags.go b/server/internal/tags/tags.go index 8a154641..5e2fdc1b 100644 --- a/server/internal/tags/tags.go +++ b/server/internal/tags/tags.go @@ -106,7 +106,7 @@ func GetTags() (models.Tags, *e.Error) { func GetTagsByIDs(ids []string) ([]*models.Tag, *e.Error) { tags := make([]*models.Tag, 0, len(ids)) for _, id := range ids { - tag, err := GetSimpleTag(id, "") + tag, err := models.GetSimpleTag(id, "") if err != nil { logger.Warn("get tag error", "error", err) continue @@ -152,52 +152,14 @@ func GetTag(id string, name string) (*models.Tag, *e.Error) { return tag, nil } -func GetSimpleTag(id string, name string) (*models.Tag, *e.Error) { - tag := &models.Tag{} - err := db.Conn.QueryRow("SELECT id,name,title,icon from tags where id=? or name=?", id, name).Scan( - &tag.ID, &tag.Name, &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 GetTargetTags(targetID string) ([]string, []*models.Tag, error) { - ids := make([]string, 0) - rows, err := db.Conn.Query("SELECT tag_id FROM tags_using WHERE target_id=?", targetID) - if err != nil { - return nil, nil, err - } - - rawTags := make([]*models.Tag, 0) - for rows.Next() { - var id string - err = rows.Scan(&id) - ids = append(ids, id) - - rawTag, err := GetSimpleTag(id, "") - if err == nil { - rawTags = append(rawTags, rawTag) - } - } - - return ids, rawTags, nil -} - -func UpdateTargetTags(targetCreator string, targetID string, tags []string) error { +func UpdateTargetTags(targetCreator string, targetID string, tags []string, targetCreated time.Time) error { _, err := db.Conn.Exec("DELETE FROM tags_using WHERE target_id=?", targetID) if err != nil { return err } for _, tag := range tags { - _, err = db.Conn.Exec("INSERT INTO tags_using (tag_id,target_type,target_id,target_creator) VALUES (?,?,?,?)", tag, models.GetIDType(targetID), targetID, targetCreator) + _, err = db.Conn.Exec("INSERT INTO tags_using (tag_id,target_type,target_id,target_creator,target_created) VALUES (?,?,?,?,?)", tag, models.GetIDType(targetID), targetID, targetCreator, targetCreated) if err != nil { logger.Warn("add post tag error", "error", err) } @@ -259,7 +221,7 @@ func GetUserTags(userID string) ([]*models.Tag, *e.Error) { tags := make(models.Tags, 0) for _, t := range tagsMap { - tag, err := GetSimpleTag(t.ID, "") + tag, err := models.GetSimpleTag(t.ID, "") if err != nil { logger.Warn("get simple tag error", "error", err) continue diff --git a/server/internal/top/top.go b/server/internal/top/top.go new file mode 100644 index 00000000..ba23f9d7 --- /dev/null +++ b/server/internal/top/top.go @@ -0,0 +1,113 @@ +package top + +import ( + "context" + "fmt" + "time" + + "github.com/go-redis/redis/v8" + "github.com/imdotdev/im.dev/server/pkg/db" + "github.com/imdotdev/im.dev/server/pkg/log" + "github.com/imdotdev/im.dev/server/pkg/models" +) + +var logger = log.RootLogger.New("logger", "hot") + +type HotData struct { + Key string + Data *redis.Z +} + +const GlobalPrefix = "im.dev-global-" +const TagFormat = "im.dev-tag-%s-%s" +const ( + TopRecent = models.FilterRecent + TopWeek = models.FilterWeek + TopMonth = models.FilterMonth + TopYear = models.FilterYear +) + +// 更新文章的HOT列表 +// 时间维度:yestoday, week, month, year,infinity +// 范围维度: 全局、Tag +func Update(storyID string, count int) { + var created time.Time + err := db.Conn.QueryRow("SELECT created FROM story WHERE id=?", storyID).Scan(&created) + if err != nil { + logger.Warn("select story created error", "error", err) + return + } + + tillNow := time.Now().Sub(created).Hours() + hots := make([]*HotData, 0) + + if tillNow >= 365*24 { + return + } + + ts, _, err := models.GetTargetTags(storyID) + if err != nil { + logger.Warn("get tags error", "error", err) + return + } + + if tillNow < 365*24 { + hots = append(hots, &HotData{GlobalPrefix + TopYear, &redis.Z{float64(count), storyID}}) + for _, tag := range ts { + hots = append(hots, &HotData{fmt.Sprintf(TagFormat, tag, TopYear), &redis.Z{float64(count), storyID}}) + } + } + + if tillNow < 30*24 { + hots = append(hots, &HotData{GlobalPrefix + TopMonth, &redis.Z{float64(count), storyID}}) + for _, tag := range ts { + hots = append(hots, &HotData{fmt.Sprintf(TagFormat, tag, TopMonth), &redis.Z{float64(count), storyID}}) + } + } + + if tillNow < 7*24 { + hots = append(hots, &HotData{GlobalPrefix + TopWeek, &redis.Z{float64(count), storyID}}) + for _, tag := range ts { + hots = append(hots, &HotData{fmt.Sprintf(TagFormat, tag, TopWeek), &redis.Z{float64(count), storyID}}) + } + } + + if tillNow < 2*24 { + hots = append(hots, &HotData{GlobalPrefix + TopRecent, &redis.Z{float64(count), storyID}}) + for _, tag := range ts { + hots = append(hots, &HotData{fmt.Sprintf(TagFormat, tag, TopRecent), &redis.Z{float64(count), storyID}}) + } + } + + ctx := context.Background() + for _, hot := range hots { + if count > 0 { + err = db.Redis.ZAdd(ctx, hot.Key, hot.Data).Err() + } else { + err = db.Redis.ZRem(ctx, hot.Key, hot.Data.Member).Err() + } + + if err != nil { + logger.Warn("update hot error", "error", err, "key", hot.Key, "score", hot.Data.Score) + continue + } + } +} + +func GetTopList(key string, start, end int64) []string { + ids := make([]string, 0) + ctx := context.Background() + fmt.Println(start, end) + keys, err := db.Redis.ZRevRange(ctx, key, start, end-1).Result() + if err != nil { + logger.Warn("scan top list error", "error", err, "key", key) + return ids + } + + fmt.Println(keys) + for _, key := range keys { + ids = append(ids, key) + } + + return ids +} diff --git a/server/internal/user/user.go b/server/internal/user/user.go index 457eb979..b186ad58 100644 --- a/server/internal/user/user.go +++ b/server/internal/user/user.go @@ -74,7 +74,7 @@ func GetUserDetail(id string, username string) (*models.User, *e.Error) { } // get user skills - skills, rawSkills, err := tags.GetTargetTags(user.ID) + skills, rawSkills, err := models.GetTargetTags(user.ID) if err != nil { logger.Warn("get user skills error", "error", err) return nil, e.New(http.StatusInternalServerError, e.Internal) @@ -124,7 +124,7 @@ func UpdateUser(u *models.User) *e.Error { } //update user skills - err = tags.UpdateTargetTags("", u.ID, u.Skills) + err = tags.UpdateTargetTags("", u.ID, u.Skills, u.Created) if err != nil { logger.Warn("upate tags error", "error", err) return e.New(http.StatusInternalServerError, e.Internal) diff --git a/server/pkg/db/db.go b/server/pkg/db/db.go index 5375dc44..79005a28 100644 --- a/server/pkg/db/db.go +++ b/server/pkg/db/db.go @@ -2,6 +2,9 @@ package db import ( "database/sql" + + "github.com/go-redis/redis/v8" ) var Conn *sql.DB +var Redis *redis.Client diff --git a/server/pkg/models/search.go b/server/pkg/models/search.go index 671b16c5..ed150750 100644 --- a/server/pkg/models/search.go +++ b/server/pkg/models/search.go @@ -1,10 +1,14 @@ package models const ( - FilterBest = "best" - FilterFeature = "feature" - FilterRecent = "recent" - FilterFavorites = "favorites" + FilterBest = "Best" + FilterFeature = "Feature" + FilterRecent = "Recent" + FilterFavorites = "Favorites" + FilterWeek = "Week" + FilterMonth = "Month" + FilterYear = "Year" + FilterLatest = "Latest" ) func ValidSearchFilter(f string) bool { diff --git a/server/pkg/models/tag.go b/server/pkg/models/tag.go index 8c0480d5..905272f8 100644 --- a/server/pkg/models/tag.go +++ b/server/pkg/models/tag.go @@ -1,5 +1,13 @@ package models +import ( + "database/sql" + "net/http" + + "github.com/imdotdev/im.dev/server/pkg/db" + "github.com/imdotdev/im.dev/server/pkg/e" +) + type Tag struct { ID string `json:"id"` Creator string `json:"creator,omitempty"` @@ -27,3 +35,41 @@ func (t Tags) Swap(i, j int) { t[i], t[j] = t[j], t[i] } func (t Tags) Less(i, j int) bool { return t[i].Posts > t[j].Posts } + +func GetTargetTags(targetID string) ([]string, []*Tag, error) { + ids := make([]string, 0) + rows, err := db.Conn.Query("SELECT tag_id FROM tags_using WHERE target_id=?", targetID) + if err != nil { + return nil, nil, err + } + + rawTags := make([]*Tag, 0) + for rows.Next() { + var id string + err = rows.Scan(&id) + ids = append(ids, id) + + rawTag, err := GetSimpleTag(id, "") + if err == nil { + rawTags = append(rawTags, rawTag) + } + } + + return ids, rawTags, nil +} + +func GetSimpleTag(id string, name string) (*Tag, *e.Error) { + tag := &Tag{} + err := db.Conn.QueryRow("SELECT id,name,title,icon from tags where id=? or name=?", id, name).Scan( + &tag.ID, &tag.Name, &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 +} diff --git a/src/components/story/stories.tsx b/src/components/story/stories.tsx index ba3b24ec..4fc75b61 100644 --- a/src/components/story/stories.tsx +++ b/src/components/story/stories.tsx @@ -1,45 +1,93 @@ -import React from "react" +import React, { useEffect, useState } from "react" import { Box, Center, Text, useColorModeValue, VStack } from "@chakra-ui/react" import { Story } from "src/types/story" import StoryCard from "./story-card" import userCustomTheme from "theme/user-custom" +import useInfiniteScroll from 'src/hooks/use-infinite-scroll' +import { concat } from "lodash" +import Empty from "components/empty" interface Props { - stories: Story[] card?: any size?: 'sm' | 'md' showFooter?: boolean showPinned?: boolean type?: string highlight?: string - showOrg?:boolean + showOrg?: boolean + onLoad?: any + filter?: string } export const Stroies = (props: Props) => { - const { stories,card=StoryCard,showFooter=true,type="classic",showPinned = false,showOrg=true} = props + const { card = StoryCard, showFooter = true, type = "classic", showPinned = false, showOrg = true, onLoad, filter } = props + const [posts, setPosts] = useState([]) + const [noMore, setNoMore] = useState(false) + + const [isFetching, setIsFetching] = useInfiniteScroll(fetchMoreListItems); const borderColor = useColorModeValue(userCustomTheme.borderColor.light, userCustomTheme.borderColor.dark) + const [page, setPage] = useState(1) + const Card = card + + useEffect(() => { + setPosts([]) + setNoMore(false) + setPage(1) + onLoad(1).then(res => { + if (res.data.length < 5) { + setNoMore(true) + } + setPosts(res.data) + }) + }, [filter]) + + function fetchMoreListItems() { + if (noMore) { + //@ts-ignore + setIsFetching(false) + return + } + setPage(page + 1) + onLoad(page + 1).then(res => { + if (res.data.length < 5) { + setNoMore(true) + } + setPosts(concat(posts, ...res.data)) + } + ) + //@ts-ignore + setIsFetching(false) + } + const showBorder = i => { - if (i < stories.length -1) { + if (i < posts.length - 1) { return true } if (showFooter) { - return true + return true } else { return false } } + return ( <> - - {stories.map((story,i) => - - - )} - - {showFooter &&
没有更多文章了
} + { + posts.length === 0 ? : + <> + + {posts.map((story, i) => + + + )} + + {isFetching && 'Fetching more list items...'} + {noMore &&
没有更多文章了
} + + } ) } diff --git a/src/components/story/story-filter.tsx b/src/components/story/story-filter.tsx new file mode 100644 index 00000000..7a75e484 --- /dev/null +++ b/src/components/story/story-filter.tsx @@ -0,0 +1,39 @@ +import React, { useState } from "react" +import { Button, HStack } from "@chakra-ui/react" + + +interface Props { + showBest?: boolean + onChange: any +} + +export const StoryFilters = (props:Props) => { + const {showBest=true,onChange} = props + let initFilter = 'Best' + let filters = ['Recent','Week','Month','Year','Latest'] + if (!showBest) { + initFilter = 'Recent' + } else { + filters.unshift('Best') + } + + const [filter, setFilter] = useState(initFilter) + + const changeFilter = f => { + onChange(f) + setFilter(f) + } + + return ( + + { + filters.map(f => + ) + } + + ) +} + +export default StoryFilters diff --git a/src/components/svg-icon.tsx b/src/components/svg-icon.tsx index a3627c3d..b20e01b3 100644 --- a/src/components/svg-icon.tsx +++ b/src/components/svg-icon.tsx @@ -1,4 +1,7 @@ +import { toLower } from "lodash" + export function getSvgIcon(name, height = "1.4rem") { + name = toLower(name) let svg switch (name) { case "comments1": diff --git a/src/hooks/use-infinite-scroll.js b/src/hooks/use-infinite-scroll.js new file mode 100644 index 00000000..cfb3729d --- /dev/null +++ b/src/hooks/use-infinite-scroll.js @@ -0,0 +1,24 @@ +import { useState, useEffect } from 'react'; + +const useInfiniteScroll = (callback) => { + const [isFetching, setIsFetching] = useState(false); + + useEffect(() => { + window.addEventListener('scroll', handleScroll); + return () => window.removeEventListener('scroll', handleScroll); + }, []); + + useEffect(() => { + if (!isFetching) return; + callback(); + }, [isFetching]); + + function handleScroll() { + if (window.innerHeight + document.documentElement.scrollTop !== document.documentElement.offsetHeight || isFetching) return; + setIsFetching(true); + } + + return [isFetching, setIsFetching]; +}; + +export default useInfiniteScroll; \ No newline at end of file diff --git a/src/types/search.ts b/src/types/search.ts index b88b3af3..7c691ce1 100644 --- a/src/types/search.ts +++ b/src/types/search.ts @@ -1,6 +1,7 @@ export enum SearchFilter { - Best = "best", - Featured = "feature", - Recent = "recent", - Favorites = "favorites" + Best = "Best", + Featured = "Feature", + Recent = "Recent", + Favorites = "Favorites", + Latest = "Latest" } \ No newline at end of file