merge from r/paopao-ce-plus branch

r/paopao-ce-xtra
Michael Li 10 months ago
commit a485895c64
No known key found for this signature in database

@ -2,7 +2,10 @@
All notable changes to paopao-ce are documented in this file.
## 0.6.0+dev ([`dev`](https://github.com/rocboss/paopao-ce/tree/dev))
TODO;
### Added
- add all-in-one docker image build scripts.
- frontend: add tweets filter support use tag for home page and make it as default behavior.
- add pin topic support.
## 0.5.2
### Change

@ -0,0 +1,37 @@
# syntax=docker/dockerfile:experimental
# build frontend
FROM node:19-alpine as frontend
ARG API_HOST
ARG USE_API_HOST=yes
ARG EMBED_UI=yes
ARG USE_DIST=no
WORKDIR /web
COPY web/ ./
RUN [ $EMBED_UI != yes ] || [ $USE_API_HOST != yes ] || echo "VITE_HOST='$API_HOST'">.env.local
RUN [ $EMBED_UI != yes ] || [ $USE_DIST != no ] || (yarn && yarn build)
RUN [ $EMBED_UI = yes ] || mkdir dist || echo ""
# build backend
FROM bitbus/paopao-ce-backend-builder:latest AS backend
ARG API_HOST
ARG USE_API_HOST=yes
ARG EMBED_UI=yes
ARG USE_DIST=no
WORKDIR /paopao-ce
COPY . .
COPY --from=frontend /web/dist ./web/dist
ENV GOPROXY=https://goproxy.cn
RUN [ $EMBED_UI != yes ] || make build TAGS='go_json migration'
RUN [ $EMBED_UI = yes ] || make build TAGS='slim embed go_json migration'
FROM bitbus/paopao-ce-allinone-runner:latest
ARG API_HOST
ARG USE_API_HOST=yes
ARG EMBED_UI=yes
ARG USE_DIST=no
ENV TZ=Asia/Shanghai
WORKDIR /app
COPY --from=backend /paopao-ce/release/paopao .

@ -130,10 +130,10 @@ install-plugins: install-protobuf-plugins
.PHONY: install-protobuf-plugins
install-protobuf-plugins:
@go install github.com/bufbuild/buf/cmd/buf@v1.25.0
@go install github.com/bufbuild/buf/cmd/protoc-gen-buf-breaking@v1.25.0
@go install github.com/bufbuild/buf/cmd/protoc-gen-buf-lint@v1.25.0
@go install github.com/bufbuild/connect-go/cmd/protoc-gen-connect-go@latest
@go install github.com/bufbuild/buf/cmd/buf@v1.28.1
@go install github.com/bufbuild/buf/cmd/protoc-gen-buf-breaking@v1.28.1
@go install github.com/bufbuild/buf/cmd/protoc-gen-buf-lint@v1.28.1
@go install connectrpc.com/connect/cmd/protoc-gen-connect-go@latest
@go install github.com/fullstorydev/grpcurl/cmd/grpcurl@latest
@go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
@go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest

@ -52,7 +52,7 @@ PaoPao主要由以下优秀的开源项目/工具构建
* [Mir](https://github.com/alimy/mir 'go-mir')
* [Buf](https://github.com/bufbuild/buf 'buf')
* [gRPC](https://github.com/grpc/grpc-go 'grpc-go')
* [Zinc](https://zinclabs.io/ 'zinc')
* [Meilisearch](https://www.meilisearch.com/ 'meilisearch')
#### 前端:
* [Naive UI](https://www.naiveui.com/)
@ -69,9 +69,7 @@ PaoPao主要由以下优秀的开源项目/工具构建
* Node.js (14+)
* MySQL (5.7+)
* Redis
* Zinc
> Zinc是一款轻量级全文搜索引擎可以查阅 <https://zincsearch.com/> 安装
* Meilisearch
以上环境版本为PaoPao官方的开发版本仅供参考其他版本的环境未进行充分测试
@ -221,6 +219,23 @@ PaoPao主要由以下优秀的开源项目/工具构建
docker run -d -p 8010:80 your/paopao-ce:web
```
* All-In-One:
```sh
# 构建Image
docker buildx build --build-arg USE_DIST="yes" -t your/paopao-ce:all-in-one-latest -f Dockerfile.allinone .
# 运行
docker run --name paopao-ce-allinone -d -p 8000:8008 -p 7700:7700 -v ./data/custom:/app/custom -v ./data/meili_data:/app/meili_data your/paopao-ce:all-in-one-latest
# 或者使用官方Image运行
docker run --name paopao-ce-allinone -d -p 8000:8008 -p 7700:7700 -v ./data/custom:/app/custom -v ./data/meili_data:/app/meili_data bitbus/paopao-ce:all-in-one-latest
# 或者使用官方Image运行 + 自定义config.yaml
docker run --name paopao-ce-allinone -d -p 8000:8008 -p 7700:7700 -v ./config.yaml:/app/config.yaml -v ./data/custom:/app/custom -v ./data/meili_data:/app/meili_data bitbus/paopao-ce:all-in-one-latest
```
> 注意在`config.yaml` 中`Meili.ApiKey`的值必须与容器中meili启动时设定的`MEILI_MASTER_KEY`环境变量值相同,默认为`paopao-meilisearch`. 可以在docker启动容器时通过`-e MEILI_MASTER_KEY=<custom-key>`设置该值。
### 方式三. 使用 docker-compose 运行
```sh
git clone https://github.com/rocboss/paopao-ce.git

@ -1,6 +1,6 @@
// Code generated by go-mir. DO NOT EDIT.
// versions:
// - mir v4.0.0
// - mir v4.1.0
package v1

@ -1,6 +1,6 @@
// Code generated by go-mir. DO NOT EDIT.
// versions:
// - mir v4.0.0
// - mir v4.1.0
package v1

@ -1,6 +1,6 @@
// Code generated by go-mir. DO NOT EDIT.
// versions:
// - mir v4.0.0
// - mir v4.1.0
package v1

@ -1,6 +1,6 @@
// Code generated by go-mir. DO NOT EDIT.
// versions:
// - mir v4.0.0
// - mir v4.1.0
package v1

@ -1,6 +1,6 @@
// Code generated by go-mir. DO NOT EDIT.
// versions:
// - mir v4.0.0
// - mir v4.1.0
package v1

@ -1,6 +1,6 @@
// Code generated by go-mir. DO NOT EDIT.
// versions:
// - mir v4.0.0
// - mir v4.1.0
package v1

@ -1,6 +1,6 @@
// Code generated by go-mir. DO NOT EDIT.
// versions:
// - mir v4.0.0
// - mir v4.1.0
package v1

@ -1,6 +1,6 @@
// Code generated by go-mir. DO NOT EDIT.
// versions:
// - mir v4.0.0
// - mir v4.1.0
package v1

@ -1,6 +1,6 @@
// Code generated by go-mir. DO NOT EDIT.
// versions:
// - mir v4.0.0
// - mir v4.1.0
package v1

@ -1,6 +1,6 @@
// Code generated by go-mir. DO NOT EDIT.
// versions:
// - mir v4.0.0
// - mir v4.1.0
package v1

@ -1,6 +1,6 @@
// Code generated by go-mir. DO NOT EDIT.
// versions:
// - mir v4.0.0
// - mir v4.1.0
package v1
@ -20,6 +20,7 @@ type Priv interface {
UnfollowTopic(*web.UnfollowTopicReq) mir.Error
FollowTopic(*web.FollowTopicReq) mir.Error
PinTopic(*web.PinTopicReq) (*web.PinTopicResp, mir.Error)
StickTopic(*web.StickTopicReq) (*web.StickTopicResp, mir.Error)
ThumbsDownTweetReply(*web.TweetReplyThumbsReq) mir.Error
ThumbsUpTweetReply(*web.TweetReplyThumbsReq) mir.Error
@ -91,6 +92,20 @@ func RegisterPrivServant(e *gin.Engine, s Priv, m ...PrivChain) {
}
s.Render(c, nil, s.FollowTopic(req))
})
router.Handle("POST", "/topic/pin", func(c *gin.Context) {
select {
case <-c.Request.Context().Done():
return
default:
}
req := new(web.PinTopicReq)
if err := s.Bind(c, req); err != nil {
s.Render(c, nil, err)
return
}
resp, err := s.PinTopic(req)
s.Render(c, resp, err)
})
router.Handle("POST", "/topic/stick", func(c *gin.Context) {
select {
case <-c.Request.Context().Done():
@ -406,6 +421,10 @@ func (UnimplementedPrivServant) FollowTopic(req *web.FollowTopicReq) mir.Error {
return mir.Errorln(http.StatusNotImplemented, http.StatusText(http.StatusNotImplemented))
}
func (UnimplementedPrivServant) PinTopic(req *web.PinTopicReq) (*web.PinTopicResp, mir.Error) {
return nil, mir.Errorln(http.StatusNotImplemented, http.StatusText(http.StatusNotImplemented))
}
func (UnimplementedPrivServant) StickTopic(req *web.StickTopicReq) (*web.StickTopicResp, mir.Error) {
return nil, mir.Errorln(http.StatusNotImplemented, http.StatusText(http.StatusNotImplemented))
}

@ -1,6 +1,6 @@
// Code generated by go-mir. DO NOT EDIT.
// versions:
// - mir v4.0.0
// - mir v4.1.0
package v1

@ -1,6 +1,6 @@
// Code generated by go-mir. DO NOT EDIT.
// versions:
// - mir v4.0.0
// - mir v4.1.0
package v1

@ -1,6 +1,6 @@
// Code generated by go-mir. DO NOT EDIT.
// versions:
// - mir v4.0.0
// - mir v4.1.0
package v1

@ -1,6 +1,6 @@
// Code generated by go-mir. DO NOT EDIT.
// versions:
// - mir v4.0.0
// - mir v4.1.0
package v1

@ -1,6 +1,6 @@
// Code generated by go-mir. DO NOT EDIT.
// versions:
// - mir v4.0.0
// - mir v4.1.0
package v1

@ -5,9 +5,9 @@
package corev1connect
import (
connect "connectrpc.com/connect"
context "context"
errors "errors"
connect_go "github.com/bufbuild/connect-go"
v1 "github.com/rocboss/paopao-ce/auto/rpc/core/v1"
http "net/http"
strings "strings"
@ -18,7 +18,7 @@ import (
// generated with a version of connect newer than the one compiled into your binary. You can fix the
// problem by either regenerating this code with an older version of connect or updating the connect
// version compiled into your binary.
const _ = connect_go.IsAtLeastVersion0_1_0
const _ = connect.IsAtLeastVersion1_13_0
const (
// AuthenticateServiceName is the fully-qualified name of the AuthenticateService service.
@ -44,11 +44,19 @@ const (
AuthenticateServiceLogoutProcedure = "/core.v1.AuthenticateService/logout"
)
// These variables are the protoreflect.Descriptor objects for the RPCs defined in this package.
var (
authenticateServiceServiceDescriptor = v1.File_core_v1_auth_proto.Services().ByName("AuthenticateService")
authenticateServicePreLoginMethodDescriptor = authenticateServiceServiceDescriptor.Methods().ByName("preLogin")
authenticateServiceLoginMethodDescriptor = authenticateServiceServiceDescriptor.Methods().ByName("login")
authenticateServiceLogoutMethodDescriptor = authenticateServiceServiceDescriptor.Methods().ByName("logout")
)
// AuthenticateServiceClient is a client for the core.v1.AuthenticateService service.
type AuthenticateServiceClient interface {
PreLogin(context.Context, *connect_go.Request[v1.User]) (*connect_go.Response[v1.ActionReply], error)
Login(context.Context, *connect_go.Request[v1.User]) (*connect_go.Response[v1.LoginReply], error)
Logout(context.Context, *connect_go.Request[v1.User]) (*connect_go.Response[v1.ActionReply], error)
PreLogin(context.Context, *connect.Request[v1.User]) (*connect.Response[v1.ActionReply], error)
Login(context.Context, *connect.Request[v1.User]) (*connect.Response[v1.LoginReply], error)
Logout(context.Context, *connect.Request[v1.User]) (*connect.Response[v1.ActionReply], error)
}
// NewAuthenticateServiceClient constructs a client for the core.v1.AuthenticateService service. By
@ -58,54 +66,57 @@ type AuthenticateServiceClient interface {
//
// The URL supplied here should be the base URL for the Connect or gRPC server (for example,
// http://api.acme.com or https://acme.com/grpc).
func NewAuthenticateServiceClient(httpClient connect_go.HTTPClient, baseURL string, opts ...connect_go.ClientOption) AuthenticateServiceClient {
func NewAuthenticateServiceClient(httpClient connect.HTTPClient, baseURL string, opts ...connect.ClientOption) AuthenticateServiceClient {
baseURL = strings.TrimRight(baseURL, "/")
return &authenticateServiceClient{
preLogin: connect_go.NewClient[v1.User, v1.ActionReply](
preLogin: connect.NewClient[v1.User, v1.ActionReply](
httpClient,
baseURL+AuthenticateServicePreLoginProcedure,
opts...,
connect.WithSchema(authenticateServicePreLoginMethodDescriptor),
connect.WithClientOptions(opts...),
),
login: connect_go.NewClient[v1.User, v1.LoginReply](
login: connect.NewClient[v1.User, v1.LoginReply](
httpClient,
baseURL+AuthenticateServiceLoginProcedure,
opts...,
connect.WithSchema(authenticateServiceLoginMethodDescriptor),
connect.WithClientOptions(opts...),
),
logout: connect_go.NewClient[v1.User, v1.ActionReply](
logout: connect.NewClient[v1.User, v1.ActionReply](
httpClient,
baseURL+AuthenticateServiceLogoutProcedure,
opts...,
connect.WithSchema(authenticateServiceLogoutMethodDescriptor),
connect.WithClientOptions(opts...),
),
}
}
// authenticateServiceClient implements AuthenticateServiceClient.
type authenticateServiceClient struct {
preLogin *connect_go.Client[v1.User, v1.ActionReply]
login *connect_go.Client[v1.User, v1.LoginReply]
logout *connect_go.Client[v1.User, v1.ActionReply]
preLogin *connect.Client[v1.User, v1.ActionReply]
login *connect.Client[v1.User, v1.LoginReply]
logout *connect.Client[v1.User, v1.ActionReply]
}
// PreLogin calls core.v1.AuthenticateService.preLogin.
func (c *authenticateServiceClient) PreLogin(ctx context.Context, req *connect_go.Request[v1.User]) (*connect_go.Response[v1.ActionReply], error) {
func (c *authenticateServiceClient) PreLogin(ctx context.Context, req *connect.Request[v1.User]) (*connect.Response[v1.ActionReply], error) {
return c.preLogin.CallUnary(ctx, req)
}
// Login calls core.v1.AuthenticateService.login.
func (c *authenticateServiceClient) Login(ctx context.Context, req *connect_go.Request[v1.User]) (*connect_go.Response[v1.LoginReply], error) {
func (c *authenticateServiceClient) Login(ctx context.Context, req *connect.Request[v1.User]) (*connect.Response[v1.LoginReply], error) {
return c.login.CallUnary(ctx, req)
}
// Logout calls core.v1.AuthenticateService.logout.
func (c *authenticateServiceClient) Logout(ctx context.Context, req *connect_go.Request[v1.User]) (*connect_go.Response[v1.ActionReply], error) {
func (c *authenticateServiceClient) Logout(ctx context.Context, req *connect.Request[v1.User]) (*connect.Response[v1.ActionReply], error) {
return c.logout.CallUnary(ctx, req)
}
// AuthenticateServiceHandler is an implementation of the core.v1.AuthenticateService service.
type AuthenticateServiceHandler interface {
PreLogin(context.Context, *connect_go.Request[v1.User]) (*connect_go.Response[v1.ActionReply], error)
Login(context.Context, *connect_go.Request[v1.User]) (*connect_go.Response[v1.LoginReply], error)
Logout(context.Context, *connect_go.Request[v1.User]) (*connect_go.Response[v1.ActionReply], error)
PreLogin(context.Context, *connect.Request[v1.User]) (*connect.Response[v1.ActionReply], error)
Login(context.Context, *connect.Request[v1.User]) (*connect.Response[v1.LoginReply], error)
Logout(context.Context, *connect.Request[v1.User]) (*connect.Response[v1.ActionReply], error)
}
// NewAuthenticateServiceHandler builds an HTTP handler from the service implementation. It returns
@ -113,21 +124,24 @@ type AuthenticateServiceHandler interface {
//
// By default, handlers support the Connect, gRPC, and gRPC-Web protocols with the binary Protobuf
// and JSON codecs. They also support gzip compression.
func NewAuthenticateServiceHandler(svc AuthenticateServiceHandler, opts ...connect_go.HandlerOption) (string, http.Handler) {
authenticateServicePreLoginHandler := connect_go.NewUnaryHandler(
func NewAuthenticateServiceHandler(svc AuthenticateServiceHandler, opts ...connect.HandlerOption) (string, http.Handler) {
authenticateServicePreLoginHandler := connect.NewUnaryHandler(
AuthenticateServicePreLoginProcedure,
svc.PreLogin,
opts...,
connect.WithSchema(authenticateServicePreLoginMethodDescriptor),
connect.WithHandlerOptions(opts...),
)
authenticateServiceLoginHandler := connect_go.NewUnaryHandler(
authenticateServiceLoginHandler := connect.NewUnaryHandler(
AuthenticateServiceLoginProcedure,
svc.Login,
opts...,
connect.WithSchema(authenticateServiceLoginMethodDescriptor),
connect.WithHandlerOptions(opts...),
)
authenticateServiceLogoutHandler := connect_go.NewUnaryHandler(
authenticateServiceLogoutHandler := connect.NewUnaryHandler(
AuthenticateServiceLogoutProcedure,
svc.Logout,
opts...,
connect.WithSchema(authenticateServiceLogoutMethodDescriptor),
connect.WithHandlerOptions(opts...),
)
return "/core.v1.AuthenticateService/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path {
@ -146,14 +160,14 @@ func NewAuthenticateServiceHandler(svc AuthenticateServiceHandler, opts ...conne
// UnimplementedAuthenticateServiceHandler returns CodeUnimplemented from all methods.
type UnimplementedAuthenticateServiceHandler struct{}
func (UnimplementedAuthenticateServiceHandler) PreLogin(context.Context, *connect_go.Request[v1.User]) (*connect_go.Response[v1.ActionReply], error) {
return nil, connect_go.NewError(connect_go.CodeUnimplemented, errors.New("core.v1.AuthenticateService.preLogin is not implemented"))
func (UnimplementedAuthenticateServiceHandler) PreLogin(context.Context, *connect.Request[v1.User]) (*connect.Response[v1.ActionReply], error) {
return nil, connect.NewError(connect.CodeUnimplemented, errors.New("core.v1.AuthenticateService.preLogin is not implemented"))
}
func (UnimplementedAuthenticateServiceHandler) Login(context.Context, *connect_go.Request[v1.User]) (*connect_go.Response[v1.LoginReply], error) {
return nil, connect_go.NewError(connect_go.CodeUnimplemented, errors.New("core.v1.AuthenticateService.login is not implemented"))
func (UnimplementedAuthenticateServiceHandler) Login(context.Context, *connect.Request[v1.User]) (*connect.Response[v1.LoginReply], error) {
return nil, connect.NewError(connect.CodeUnimplemented, errors.New("core.v1.AuthenticateService.login is not implemented"))
}
func (UnimplementedAuthenticateServiceHandler) Logout(context.Context, *connect_go.Request[v1.User]) (*connect_go.Response[v1.ActionReply], error) {
return nil, connect_go.NewError(connect_go.CodeUnimplemented, errors.New("core.v1.AuthenticateService.logout is not implemented"))
func (UnimplementedAuthenticateServiceHandler) Logout(context.Context, *connect.Request[v1.User]) (*connect.Response[v1.ActionReply], error) {
return nil, connect.NewError(connect.CodeUnimplemented, errors.New("core.v1.AuthenticateService.logout is not implemented"))
}

@ -5,9 +5,9 @@
package greetv1connect
import (
connect "connectrpc.com/connect"
context "context"
errors "errors"
connect_go "github.com/bufbuild/connect-go"
v1 "github.com/rocboss/paopao-ce/auto/rpc/greet/v1"
http "net/http"
strings "strings"
@ -18,7 +18,7 @@ import (
// generated with a version of connect newer than the one compiled into your binary. You can fix the
// problem by either regenerating this code with an older version of connect or updating the connect
// version compiled into your binary.
const _ = connect_go.IsAtLeastVersion0_1_0
const _ = connect.IsAtLeastVersion1_13_0
const (
// GreetServiceName is the fully-qualified name of the GreetService service.
@ -37,9 +37,15 @@ const (
GreetServiceGreetProcedure = "/greet.v1.GreetService/Greet"
)
// These variables are the protoreflect.Descriptor objects for the RPCs defined in this package.
var (
greetServiceServiceDescriptor = v1.File_greet_v1_greet_proto.Services().ByName("GreetService")
greetServiceGreetMethodDescriptor = greetServiceServiceDescriptor.Methods().ByName("Greet")
)
// GreetServiceClient is a client for the greet.v1.GreetService service.
type GreetServiceClient interface {
Greet(context.Context, *connect_go.Request[v1.GreetRequest]) (*connect_go.Response[v1.GreetResponse], error)
Greet(context.Context, *connect.Request[v1.GreetRequest]) (*connect.Response[v1.GreetResponse], error)
}
// NewGreetServiceClient constructs a client for the greet.v1.GreetService service. By default, it
@ -49,30 +55,31 @@ type GreetServiceClient interface {
//
// The URL supplied here should be the base URL for the Connect or gRPC server (for example,
// http://api.acme.com or https://acme.com/grpc).
func NewGreetServiceClient(httpClient connect_go.HTTPClient, baseURL string, opts ...connect_go.ClientOption) GreetServiceClient {
func NewGreetServiceClient(httpClient connect.HTTPClient, baseURL string, opts ...connect.ClientOption) GreetServiceClient {
baseURL = strings.TrimRight(baseURL, "/")
return &greetServiceClient{
greet: connect_go.NewClient[v1.GreetRequest, v1.GreetResponse](
greet: connect.NewClient[v1.GreetRequest, v1.GreetResponse](
httpClient,
baseURL+GreetServiceGreetProcedure,
opts...,
connect.WithSchema(greetServiceGreetMethodDescriptor),
connect.WithClientOptions(opts...),
),
}
}
// greetServiceClient implements GreetServiceClient.
type greetServiceClient struct {
greet *connect_go.Client[v1.GreetRequest, v1.GreetResponse]
greet *connect.Client[v1.GreetRequest, v1.GreetResponse]
}
// Greet calls greet.v1.GreetService.Greet.
func (c *greetServiceClient) Greet(ctx context.Context, req *connect_go.Request[v1.GreetRequest]) (*connect_go.Response[v1.GreetResponse], error) {
func (c *greetServiceClient) Greet(ctx context.Context, req *connect.Request[v1.GreetRequest]) (*connect.Response[v1.GreetResponse], error) {
return c.greet.CallUnary(ctx, req)
}
// GreetServiceHandler is an implementation of the greet.v1.GreetService service.
type GreetServiceHandler interface {
Greet(context.Context, *connect_go.Request[v1.GreetRequest]) (*connect_go.Response[v1.GreetResponse], error)
Greet(context.Context, *connect.Request[v1.GreetRequest]) (*connect.Response[v1.GreetResponse], error)
}
// NewGreetServiceHandler builds an HTTP handler from the service implementation. It returns the
@ -80,11 +87,12 @@ type GreetServiceHandler interface {
//
// By default, handlers support the Connect, gRPC, and gRPC-Web protocols with the binary Protobuf
// and JSON codecs. They also support gzip compression.
func NewGreetServiceHandler(svc GreetServiceHandler, opts ...connect_go.HandlerOption) (string, http.Handler) {
greetServiceGreetHandler := connect_go.NewUnaryHandler(
func NewGreetServiceHandler(svc GreetServiceHandler, opts ...connect.HandlerOption) (string, http.Handler) {
greetServiceGreetHandler := connect.NewUnaryHandler(
GreetServiceGreetProcedure,
svc.Greet,
opts...,
connect.WithSchema(greetServiceGreetMethodDescriptor),
connect.WithHandlerOptions(opts...),
)
return "/greet.v1.GreetService/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path {
@ -99,6 +107,6 @@ func NewGreetServiceHandler(svc GreetServiceHandler, opts ...connect_go.HandlerO
// UnimplementedGreetServiceHandler returns CodeUnimplemented from all methods.
type UnimplementedGreetServiceHandler struct{}
func (UnimplementedGreetServiceHandler) Greet(context.Context, *connect_go.Request[v1.GreetRequest]) (*connect_go.Response[v1.GreetResponse], error) {
return nil, connect_go.NewError(connect_go.CodeUnimplemented, errors.New("greet.v1.GreetService.Greet is not implemented"))
func (UnimplementedGreetServiceHandler) Greet(context.Context, *connect.Request[v1.GreetRequest]) (*connect.Response[v1.GreetResponse], error) {
return nil, connect.NewError(connect.CodeUnimplemented, errors.New("greet.v1.GreetService.Greet is not implemented"))
}

@ -1,6 +1,6 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.31.0
// protoc-gen-go v1.32.0
// protoc (unknown)
// source: core/v1/auth.proto

@ -1,6 +1,6 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.31.0
// protoc-gen-go v1.32.0
// protoc (unknown)
// source: greet/v1/greet.proto

@ -2,7 +2,7 @@ version: '3.1'
services:
db:
image: mysql:8.0
image: mysql:${MYSQL_TAG:-8.0}
restart: always
environment:
MYSQL_DATABASE: paopao
@ -33,7 +33,7 @@ services:
# - paopao-network
# redis:
# image: redis:7.2.1-alpine
# image: redis:${REDIS_TAG:-7.2.1-alpine}
# restart: always
# ports:
# - 6379:6379
@ -41,7 +41,7 @@ services:
# - paopao-network
redis:
image: redis/redis-stack:7.2.0-v2
image: redis/redis-stack:${REDIS_STACK_TAG:-7.2.0-v6}
restart: always
ports:
- 6379:6379
@ -67,7 +67,7 @@ services:
# - paopao-network
meili:
image: getmeili/meilisearch:v1.4
image: getmeili/meilisearch:${MEILI_TAG:-v1.5}
restart: always
ports:
- 7700:7700
@ -87,7 +87,7 @@ services:
# - paopao-network
# openobserve:
# image: public.ecr.aws/zinclabs/openobserve:latest
# image: public.ecr.aws/zinclabs/openobserve:${OPENOBSERVE_TAG:-latest}
# restart: always
# ports:
# - 5080:5080
@ -101,7 +101,7 @@ services:
# - paopao-network
# pyroscope:
# image: pyroscope/pyroscope:latest
# image: pyroscope/pyroscope:${PYROSCOPE_TAG:-latest}
# restart: always
# ports:
# - 4040:4040
@ -111,7 +111,7 @@ services:
# - paopao-network
# phpmyadmin:
# image: phpmyadmin:5.2
# image: phpmyadmin:${PHPMYADMIN_TAG:-5.2}
# depends_on:
# - db
# ports:
@ -124,7 +124,7 @@ services:
# - paopao-network
backend:
image: bitbus/paopao-ce:nightly
image: bitbus/paopao-ce:${PAOPAOCE_TAG:-nightly}
restart: always
depends_on:
- db

@ -0,0 +1,3 @@
# Docker for paopao-ce
TODO;

@ -0,0 +1,13 @@
#!/bin/sh
set -xe
# Create paopao user for paopao-ce
addgroup -S paopao
adduser -G paopao -H -D -g 'paopao User' paopao -h /app -s /bin/sh && usermod -p '*' paopao && passwd -u paopao
# echo "export PAOPAO_CUSTOM=${PAOPAO_CUSTOM}" >> /etc/profile
# Final cleaning
mv /app/docker/config.yaml /app/config.yaml
rm -rf /app/docker/build
rm /app/docker/README.md

@ -0,0 +1,71 @@
App: # APP基础设置项
RunMode: debug
AttachmentIncomeRate: 0.8
MaxCommentCount: 10
DefaultContextTimeout: 60
DefaultPageSize: 10
MaxPageSize: 100
Features:
Default: ["Web", "Frontend:EmbedWeb", "Meili", "LocalOSS", "Sqlite3", "LoggerFile", "Migration"]
Sms: "SmsJuhe"
WebServer: # Web服务
HttpIp: 0.0.0.0
HttpPort: 8008
ReadTimeout: 60
WriteTimeout: 60
SmsJuhe:
Gateway: https://v.juhe.cn/sms/send
Key:
TplID:
TplVal: "#code#=%s&#m#=%d"
Logger: # 日志通用配置
Level: debug # 日志级别 panic|fatal|error|warn|info|debug|trace
LoggerFile: # 使用File写日志
SavePath: custom/logs
FileName: app
FileExt: .log
JWT: # 鉴权加密
Secret: 18a6413dc4fe394c66345ebe501b2f26
Issuer: paopao-api
Expire: 86400
Meili: # Meili搜索配置
Host: 127.0.0.1:7700
Index: paopao-data
ApiKey: paopao-meilisearch
Secure: False
ObjectStorage: # 对象存储通用配置
RetainInDays: 2 # 临时对象过期时间多少天
TempDir: tmp # 临时对象存放目录名
LocalOSS: # 本地文件OSS存储配置
SavePath: custom/oss
Secure: False
Bucket: paopao
Domain: 127.0.0.1:8008
Database: # Database通用配置
LogLevel: error # 日志级别 silent|error|warn|info
TablePrefix: p_ # 表名前缀
Sqlite3: # Sqlite3数据库
Path: custom/paopao-ce.db
Redis:
InitAddress:
- 127.0.0.1:6379
WebProfile:
UseFriendship: true # 前端是否使用好友体系
EnableTrendsBar: true # 广场页面是否开启动态条栏功能
EnableWallet: false # 是否开启钱包功能
AllowTweetAttachment: true # 是否允许推文附件
AllowTweetAttachmentPrice: true # 是否允许推文付费附件
AllowTweetVideo: true # 是否允许视频推文
AllowUserRegister: true # 是否允许用户注册
AllowPhoneBind: true # 是否允许手机绑定
DefaultTweetMaxLength: 2000 # 推文允许输入的最大长度, 默认2000字值的范围需要查询后端支持的最大字数
TweetWebEllipsisSize: 400 # Web端推文作为feed显示的最长字数默认400字
TweetMobileEllipsisSize: 300 # 移动端推文作为feed显示的最长字数默认300字
DefaultTweetVisibility: friend # 推文可见性,默认好友可见 值: public/following/friend/private
DefaultMsgLoopInterval: 5000 # 拉取未读消息的间隔,单位:毫秒, 默认5000ms
CopyrightTop: "2023 paopao.info"
CopyrightLeft: "Roc's Me"
CopyrightLeftLink: ""
CopyrightRight: "泡泡(PaoPao)开源社区"
CopyrightRightLink: "https://www.paopao.info"

@ -0,0 +1,3 @@
#!/bin/sh
# do nothing now

@ -0,0 +1,8 @@
#!/bin/sh
if test -f ./setup; then
# shellcheck disable=SC2039,SC1091,SC3046
source ./setup
fi
exec gosu ${USER} /bin/meilisearch

@ -0,0 +1,3 @@
#!/bin/sh
cd /app/meili_data || exit 1

@ -0,0 +1,8 @@
#!/bin/sh
if test -f ./setup; then
# shellcheck disable=SC2039,SC1091,SC3046
source ./setup
fi
exec gosu ${USER} /app/paopao serve

@ -0,0 +1,5 @@
#!/bin/sh
# sleep 10s等待meilisearch启动完成这是笨方法暂时先凑着后面再找更靠谱的法子这里之所以要等待meili启动完成是因为paopao-ce首次启动时需要初始化一次meili的index如果初始化失败就无法在后续使用。
sleep 10
cd /app || exit 1

@ -0,0 +1,8 @@
#!/bin/sh
if test -f ./setup; then
# shellcheck disable=SC2039,SC1091,SC3046
source ./setup
fi
exec docker-entrypoint.sh redis-server

@ -0,0 +1,35 @@
#!/bin/sh
create_volume_subfolder() {
# only change ownership if needed, if using an nfs mount this could be expensive
if [ "$USER:$USER" != "$(stat /app -c '%U:%G')" ]
then
# Modify the owner of /app dir, make $USER(paopao) user have permission to create sub-dir in /app.
chown -R "$USER:$USER" /app
fi
# Create VOLUME subfolder
for f in /app/custom /app/meili_data; do
if ! test -d $f; then
gosu "$USER" mkdir -p $f
fi
done
}
setids() {
export USER=paopao
PUID=${PUID:-1000}
PGID=${PGID:-1000}
groupmod -o -g "$PGID" $USER
usermod -o -u "$PUID" $USER
}
setids
create_volume_subfolder
# Exec CMD or S6 by default if nothing present
if [ $# -gt 0 ];then
exec "$@"
else
exec /bin/s6-svscan /app/gogs/docker/s6/
fi

@ -5,16 +5,16 @@ go 1.21
toolchain go1.21.1
require (
connectrpc.com/connect v1.14.0
github.com/Masterminds/semver/v3 v3.2.1
github.com/RoaringBitmap/roaring v1.6.0
github.com/RoaringBitmap/roaring v1.7.0
github.com/afocus/captcha v0.0.0-20191010092841-4bd1f21c8868
github.com/alimy/mir/v4 v4.0.0
github.com/alimy/mir/v4 v4.1.0
github.com/alimy/tryst v0.10.1
github.com/alimy/yesql v1.9.0
github.com/aliyun/aliyun-oss-go-sdk v3.0.1+incompatible
github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible
github.com/allegro/bigcache/v3 v3.1.0
github.com/bitbus/sqlx v1.8.0
github.com/bufbuild/connect-go v1.10.0
github.com/bytedance/sonic v1.10.2
github.com/cockroachdb/errors v1.11.1
github.com/disintegration/imaging v1.6.2
@ -22,11 +22,11 @@ require (
github.com/getsentry/sentry-go v0.25.0
github.com/gin-contrib/cors v1.5.0
github.com/gin-gonic/gin v1.9.1
github.com/go-resty/resty/v2 v2.10.0
github.com/go-resty/resty/v2 v2.11.0
github.com/goccy/go-json v0.10.2
github.com/gofrs/uuid/v5 v5.0.0
github.com/golang-jwt/jwt/v5 v5.2.0
github.com/golang-migrate/migrate/v4 v4.15.2
github.com/golang-migrate/migrate/v4 v4.17.0
github.com/huaweicloud/huaweicloud-sdk-go-obs v3.23.9+incompatible
github.com/jackc/pgx/v5 v5.5.0
github.com/json-iterator/go v1.1.12
@ -34,21 +34,21 @@ require (
github.com/minio/minio-go/v7 v7.0.66
github.com/onsi/ginkgo/v2 v2.13.2
github.com/onsi/gomega v1.30.0
github.com/prometheus/client_golang v1.17.0
github.com/prometheus/client_golang v1.18.0
github.com/pyroscope-io/client v0.7.2
github.com/redis/rueidis v1.0.25
github.com/redis/rueidis v1.0.26
github.com/robfig/cron/v3 v3.0.1
github.com/sirupsen/logrus v1.9.3
github.com/smartwalle/alipay/v3 v3.2.20
github.com/sourcegraph/conc v0.3.0
github.com/spf13/cobra v1.8.0
github.com/spf13/viper v1.18.1
github.com/spf13/viper v1.18.2
github.com/sqlc-dev/sqlc v1.24.0
github.com/tencentyun/cos-go-sdk-v5 v0.7.45
github.com/yinheli/mahonia v0.0.0-20131226213531-0eef680515cc
go.uber.org/automaxprocs v1.5.3
google.golang.org/grpc v1.60.0
google.golang.org/protobuf v1.31.0
google.golang.org/grpc v1.60.1
google.golang.org/protobuf v1.32.0
gopkg.in/natefinch/lumberjack.v2 v2.2.1
gopkg.in/resty.v1 v1.12.0
gorm.io/driver/mysql v1.5.2
@ -65,7 +65,7 @@ require (
github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230321174746-8dcc6526cfb1 // indirect
github.com/antlr4-go/antlr/v4 v4.13.0 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/bits-and-blooms/bitset v1.2.0 // indirect
github.com/bits-and-blooms/bitset v1.12.0 // indirect
github.com/bytecodealliance/wasmtime-go/v14 v14.0.0 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect
@ -119,7 +119,7 @@ require (
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-sqlite3 v1.14.18 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect
github.com/minio/md5-simd v1.1.2 // indirect
github.com/minio/sha256-simd v1.0.1 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
@ -134,9 +134,9 @@ require (
github.com/pingcap/log v1.1.0 // indirect
github.com/pingcap/tidb/pkg/parser v0.0.0-20231103154709-4f00ece106b1 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16 // indirect
github.com/prometheus/common v0.44.0 // indirect
github.com/prometheus/procfs v0.11.1 // indirect
github.com/prometheus/client_model v0.5.0 // indirect
github.com/prometheus/common v0.45.0 // indirect
github.com/prometheus/procfs v0.12.0 // indirect
github.com/pyroscope-io/godeltaprof v0.1.2 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/riza-io/grpc-go v0.2.0 // indirect
@ -160,9 +160,9 @@ require (
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.26.0 // indirect
golang.org/x/arch v0.5.0 // indirect
golang.org/x/crypto v0.16.0 // indirect
golang.org/x/crypto v0.17.0 // indirect
golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect
golang.org/x/image v0.0.0-20210216034530-4410531fe030 // indirect
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8 // indirect
golang.org/x/mod v0.13.0 // indirect
golang.org/x/net v0.19.0 // indirect
golang.org/x/sync v0.5.0 // indirect

1746
go.sum

File diff suppressed because it is too large Load Diff

@ -250,7 +250,7 @@ Redis:
ConnWriteTimeout: 60 # 连接写超时时间 多少秒 默认 60秒
WebProfile:
UseFriendship: true # 前端是否使用好友体系
EnableTrendsBar: true # 广场页面是否开启动态条栏功能
EnableTrendsBar: false # 广场页面是否开启动态条栏功能
EnableWallet: false # 是否开启钱包功能
AllowTweetAttachment: true # 是否允许推文附件
AllowTweetAttachmentPrice: true # 是否允许推文付费附件

@ -9,6 +9,7 @@ const (
TagTypeHot TagType = "hot"
TagTypeNew TagType = "new"
TagTypeFollow TagType = "follow"
TagTypePin TagType = "pin"
TagTypeHotExtral TagType = "hot_extral"
)
@ -40,6 +41,7 @@ type TagItem struct {
QuoteNum int64 `json:"quote_num"`
IsFollowing int8 `json:"is_following"`
IsTop int8 `json:"is_top"`
IsPin int8 `json:"is_pin"`
}
func (t *TagInfo) Format() *TagItem {
@ -51,5 +53,6 @@ func (t *TagInfo) Format() *TagItem {
QuoteNum: t.QuoteNum,
IsFollowing: 0,
IsTop: 0,
IsPin: 0,
}
}

@ -16,10 +16,11 @@ type TopicService interface {
TagsByKeyword(keyword string) (cs.TagInfoList, error)
GetHotTags(userId int64, limit int, offset int) (cs.TagList, error)
GetNewestTags(userId int64, limit int, offset int) (cs.TagList, error)
GetFollowTags(userId int64, limit int, offset int) (cs.TagList, error)
GetFollowTags(userId int64, isPin bool, limit int, offset int) (cs.TagList, error)
FollowTopic(userId int64, topicId int64) error
UnfollowTopic(userId int64, topicId int64) error
StickTopic(userId int64, topicId int64) (int8, error)
PinTopic(userId int64, topicId int64) (int8, error)
}
// TopicServantA 话题服务(版本A)

@ -148,7 +148,7 @@ func (s *commentSrv) GetCommentRepliesByID(ids []int64) ([]*ms.CommentReplyForma
return repliesFormated, nil
}
func (s *commentManageSrv) HighlightComment(userId, commentId int64) (res int8, err error) {
func (s *commentManageSrv) HighlightComment(userId, commentId int64) (isEssence int8, err error) {
post := &dbr.Post{}
comment := &dbr.Comment{}
db := s.db.Model(comment)
@ -161,8 +161,12 @@ func (s *commentManageSrv) HighlightComment(userId, commentId int64) (res int8,
if post.UserID != userId {
return 0, cs.ErrNoPermission
}
comment.IsEssence = 1 - comment.IsEssence
return comment.IsEssence, db.Save(comment).Error
isEssence = 1 - comment.IsEssence
err = s.db.Model(comment).UpdateColumns(map[string]any{
"is_essence": isEssence,
"modified_on": time.Now().Unix(),
}).Where("id=?", commentId).Error
return
}
func (s *commentManageSrv) DeleteComment(comment *ms.Comment) (err error) {

@ -25,6 +25,7 @@ type TopicUser struct {
Remark string `json:"-"`
QuoteNum int64 `json:"quote_num"`
IsTop int8 `json:"is_top"`
IsPin int8 `json:"is_pin"`
ReserveA string `json:"-"`
ReserveB string `json:"-"`
}

@ -33,6 +33,7 @@ type topicSrvA struct {
type topicInfo struct {
TopicId int64
IsTop int8
IsPin int8
}
func newTopicService(db *gorm.DB) core.TopicService {
@ -110,17 +111,18 @@ func (s *topicSrv) GetNewestTags(userId int64, limit int, offset int) (cs.TagLis
return s.tagsFormatA(userId, tags)
}
func (s *topicSrv) GetFollowTags(userId int64, limit int, offset int) (cs.TagList, error) {
func (s *topicSrv) GetFollowTags(userId int64, isPin bool, limit int, offset int) (cs.TagList, error) {
if userId < 0 {
return nil, nil
}
userTopics := []*topicInfo{}
err := s.db.Model(&dbr.TopicUser{}).
Where("user_id=?", userId).
Order("is_top DESC").
Limit(limit).
Offset(offset).
Find(&userTopics).Error
db := s.db.Model(&dbr.TopicUser{}).Order("is_top DESC").Limit(limit).Offset(offset)
if isPin {
db = db.Where("user_id=? AND is_pin=1", userId)
} else {
db = db.Where("user_id=?", userId)
}
err := db.Find(&userTopics).Error
if err != nil {
return nil, err
}
@ -207,7 +209,7 @@ func (s *topicSrv) tagsFormatA(userId int64, tags cs.TagList) (cs.TagList, error
}
for _, tag := range tags {
if info, exist := userTopicsMap[tag.ID]; exist {
tag.IsFollowing, tag.IsTop = 1, info.IsTop
tag.IsFollowing, tag.IsTop, tag.IsPin = 1, info.IsTop, info.IsPin
}
}
}
@ -238,7 +240,7 @@ func (s *topicSrv) tagsFormatB(userTopicsMap map[int64]*topicInfo, tags cs.TagIn
if len(userTopicsMap) > 0 {
for _, tag := range tagList {
if info, exist := userTopicsMap[tag.ID]; exist {
tag.IsFollowing, tag.IsTop = 1, info.IsTop
tag.IsFollowing, tag.IsTop, tag.IsPin = 1, info.IsTop, info.IsPin
}
}
}
@ -409,3 +411,27 @@ func (s *topicSrv) StickTopic(userId int64, topicId int64) (status int8, err err
db.Commit()
return
}
func (s *topicSrv) PinTopic(userId int64, topicId int64) (status int8, err error) {
db := s.db.Begin()
defer db.Rollback()
m := &dbr.TopicUser{}
err = db.Model(m).
Where("user_id=? and topic_id=?", userId, topicId).
UpdateColumn("is_pin", gorm.Expr("1-is_pin")).Error
if err != nil {
return
}
status = -1
err = db.Model(m).Where("user_id=? and topic_id=?", userId, topicId).Select("is_pin").Scan(&status).Error
if err != nil {
return
}
if status < 0 {
return -1, errors.New("topic not exist")
}
db.Commit()
return
}

@ -21,12 +21,14 @@ const (
_SimpleIndexA_UserInfo = `SELECT * FROM @user WHERE username=?`
_TopicA_DecrTagsById = `UPDATE @tag SET quote_num=quote_num-1, modified_on=? WHERE id IN (?)`
_TopicA_ExistTopicUser = `SELECT 1 FROM @topic_user WHERE user_id=? AND topic_id=? AND is_del=0`
_TopicA_FollowTags = `SELECT t.id id, t.user_id user_id, t.tag tag, t.quote_num quote_num, u.id "u.id", 1 as is_following, c.is_top, u.nickname "u.nickname", u.username "u.username", u.status "u.status", u.avatar "u.avatar", u.is_admin "u.is_admin" FROM @topic_user c JOIN @user u ON c.user_id = u.id JOIN @tag t ON c.topic_id = t.id WHERE c.is_del = 0 AND t.quote_num > 0 AND c.user_id=? ORDER BY c.is_top DESC, t.quote_num DESC LIMIT ? OFFSET ?`
_TopicA_FollowPinTags = `SELECT t.id id, t.user_id user_id, t.tag tag, t.quote_num quote_num, u.id "u.id", 1 as is_following, c.is_top, c.is_pin, u.nickname "u.nickname", u.username "u.username", u.status "u.status", u.avatar "u.avatar", u.is_admin "u.is_admin" FROM @topic_user c JOIN @user u ON c.user_id = u.id JOIN @tag t ON c.topic_id = t.id WHERE c.is_del = 0 AND t.quote_num > 0 AND c.user_id=? AND c.is_pin=1 ORDER BY c.is_top DESC, t.quote_num DESC LIMIT ? OFFSET ?`
_TopicA_FollowTags = `SELECT t.id id, t.user_id user_id, t.tag tag, t.quote_num quote_num, u.id "u.id", 1 as is_following, c.is_top, c.is_pin, u.nickname "u.nickname", u.username "u.username", u.status "u.status", u.avatar "u.avatar", u.is_admin "u.is_admin" FROM @topic_user c JOIN @user u ON c.user_id = u.id JOIN @tag t ON c.topic_id = t.id WHERE c.is_del = 0 AND t.quote_num > 0 AND c.user_id=? ORDER BY c.is_top DESC, t.quote_num DESC LIMIT ? OFFSET ?`
_TopicA_FollowTopic = `INSERT INTO @topic_user(user_id, topic_id, created_on) VALUES (?, ?, ?)`
_TopicA_HotTags = `SELECT t.id id, t.user_id user_id, t.tag tag, t.quote_num quote_num, u.id "u.id", u.nickname "u.nickname", u.username "u.username", u.status "u.status", u.avatar "u.avatar", u.is_admin "u.is_admin" FROM @tag t JOIN @user u ON t.user_id = u.id WHERE t.is_del = 0 AND t.quote_num > 0 ORDER BY t.quote_num DESC LIMIT ? OFFSET ?`
_TopicA_IncrTagsById = `UPDATE @tag SET quote_num=quote_num+1, is_del=0, modified_on=? WHERE id IN (?)`
_TopicA_InsertTag = `INSERT INTO @tag (user_id, tag, created_on, modified_on, quote_num) VALUES (?, ?, ?, ?, 1)`
_TopicA_NewestTags = `SELECT t.id id, t.user_id user_id, t.tag tag, t.quote_num quote_num, u.id "u.id", u.nickname "u.nickname", u.username "u.username", u.status "u.status", u.avatar "u.avatar", u.is_admin "u.is_admin" FROM @tag t JOIN @user u ON t.user_id = u.id WHERE t.is_del = 0 AND t.quote_num > 0 ORDER BY t.id DESC LIMIT ? OFFSET ?`
_TopicA_PinTopic = `UPDATE @topic_user SET is_pin=1-is_pin, modified_on=? WHERE user_id=? AND topic_id=? AND is_del=0`
_TopicA_StickTopic = `UPDATE @topic_user SET is_top=1-is_top, modified_on=? WHERE user_id=? AND topic_id=? AND is_del=0`
_TopicA_TagsByIdA = `SELECT id FROM @tag WHERE id IN (?) AND is_del = 0 AND quote_num > 0`
_TopicA_TagsByIdB = `SELECT id, user_id, tag, quote_num FROM @tag WHERE id IN (?)`
@ -34,6 +36,7 @@ const (
_TopicA_TagsByKeywordB = `SELECT id, user_id, tag, quote_num FROM @tag WHERE is_del = 0 AND tag LIKE ? ORDER BY quote_num DESC LIMIT 6`
_TopicA_TagsForIncr = `SELECT id, user_id, tag, quote_num FROM @tag WHERE tag IN (?)`
_TopicA_TopicInfos = `SELECT topic_id, is_top FROM @topic_user WHERE is_del=0 AND user_id=? AND topic_id IN (?)`
_TopicA_TopicIsPin = `SELECT is_pin FROM @topic_user WHERE user_id=? AND topic_id=? AND is_del=0`
_TopicA_TopicIsTop = `SELECT is_top FROM @topic_user WHERE user_id=? AND topic_id=? AND is_del=0`
_TopicA_UnfollowTopic = `DELETE FROM @topic_user WHERE user_id=? AND topic_id=? AND is_del=0`
_TweetA_AttachmentByTweetId = `SELECT * FROM @user WHERE username=?`
@ -91,14 +94,17 @@ type TopicA struct {
TagsForIncr string `yesql:"tags_for_incr"`
TopicInfos string `yesql:"topic_infos"`
ExistTopicUser *sqlx.Stmt `yesql:"exist_topic_user"`
FollowPinTags *sqlx.Stmt `yesql:"follow_pin_tags"`
FollowTags *sqlx.Stmt `yesql:"follow_tags"`
FollowTopic *sqlx.Stmt `yesql:"follow_topic"`
HotTags *sqlx.Stmt `yesql:"hot_tags"`
InsertTag *sqlx.Stmt `yesql:"insert_tag"`
NewestTags *sqlx.Stmt `yesql:"newest_tags"`
PinTopic *sqlx.Stmt `yesql:"pin_topic"`
StickTopic *sqlx.Stmt `yesql:"stick_topic"`
TagsByKeywordA *sqlx.Stmt `yesql:"tags_by_keyword_a"`
TagsByKeywordB *sqlx.Stmt `yesql:"tags_by_keyword_b"`
TopicIsPin *sqlx.Stmt `yesql:"topic_is_pin"`
TopicIsTop *sqlx.Stmt `yesql:"topic_is_top"`
UnfollowTopic *sqlx.Stmt `yesql:"unfollow_topic"`
}
@ -161,6 +167,9 @@ func BuildTopicA(p PreparexBuilder, ctx ...context.Context) (obj *TopicA, err er
if obj.ExistTopicUser, err = p.PreparexContext(c, p.Rebind(p.QueryHook(_TopicA_ExistTopicUser))); err != nil {
return nil, fmt.Errorf("prepare _TopicA_ExistTopicUser error: %w", err)
}
if obj.FollowPinTags, err = p.PreparexContext(c, p.Rebind(p.QueryHook(_TopicA_FollowPinTags))); err != nil {
return nil, fmt.Errorf("prepare _TopicA_FollowPinTags error: %w", err)
}
if obj.FollowTags, err = p.PreparexContext(c, p.Rebind(p.QueryHook(_TopicA_FollowTags))); err != nil {
return nil, fmt.Errorf("prepare _TopicA_FollowTags error: %w", err)
}
@ -176,6 +185,9 @@ func BuildTopicA(p PreparexBuilder, ctx ...context.Context) (obj *TopicA, err er
if obj.NewestTags, err = p.PreparexContext(c, p.Rebind(p.QueryHook(_TopicA_NewestTags))); err != nil {
return nil, fmt.Errorf("prepare _TopicA_NewestTags error: %w", err)
}
if obj.PinTopic, err = p.PreparexContext(c, p.Rebind(p.QueryHook(_TopicA_PinTopic))); err != nil {
return nil, fmt.Errorf("prepare _TopicA_PinTopic error: %w", err)
}
if obj.StickTopic, err = p.PreparexContext(c, p.Rebind(p.QueryHook(_TopicA_StickTopic))); err != nil {
return nil, fmt.Errorf("prepare _TopicA_StickTopic error: %w", err)
}
@ -185,6 +197,9 @@ func BuildTopicA(p PreparexBuilder, ctx ...context.Context) (obj *TopicA, err er
if obj.TagsByKeywordB, err = p.PreparexContext(c, p.Rebind(p.QueryHook(_TopicA_TagsByKeywordB))); err != nil {
return nil, fmt.Errorf("prepare _TopicA_TagsByKeywordB error: %w", err)
}
if obj.TopicIsPin, err = p.PreparexContext(c, p.Rebind(p.QueryHook(_TopicA_TopicIsPin))); err != nil {
return nil, fmt.Errorf("prepare _TopicA_TopicIsPin error: %w", err)
}
if obj.TopicIsTop, err = p.PreparexContext(c, p.Rebind(p.QueryHook(_TopicA_TopicIsTop))); err != nil {
return nil, fmt.Errorf("prepare _TopicA_TopicIsTop error: %w", err)
}

@ -17,7 +17,9 @@ var (
)
const (
_TopicA_InsertTag = `INSERT INTO @tag (user_id, tag, created_on, modified_on, quote_num) VALUES (?, ?, ?, ?, 1) RETURNING id`
_TopicA_InsertTag = `INSERT INTO @tag (user_id, tag, created_on, modified_on, quote_num) VALUES (?, ?, ?, ?, 1) RETURNING id`
_TopicA_PinTopic = `UPDATE @topic_user SET is_pin=1-is_pin, modified_on=? WHERE user_id=? AND topic_id=? AND is_del=0; RETURNING is_pin`
_TopicA_StickTopic = `UPDATE @topic_user SET is_top=1-is_top, modified_on=? WHERE user_id=? AND topic_id=? AND is_del=0; RETURNING is_top`
)
// PreparexContext enhances the Conn interface with context.
@ -43,6 +45,8 @@ type PreparexBuilder interface {
type TopicA struct {
yesql.Namespace `yesql:"topic_a"`
InsertTag *sqlx.Stmt `yesql:"insert_tag"`
PinTopic *sqlx.Stmt `yesql:"pin_topic"`
StickTopic *sqlx.Stmt `yesql:"stick_topic"`
}
func BuildTopicA(p PreparexBuilder, ctx ...context.Context) (obj *TopicA, err error) {
@ -56,5 +60,11 @@ func BuildTopicA(p PreparexBuilder, ctx ...context.Context) (obj *TopicA, err er
if obj.InsertTag, err = p.PreparexContext(c, p.Rebind(p.QueryHook(_TopicA_InsertTag))); err != nil {
return nil, fmt.Errorf("prepare _TopicA_InsertTag error: %w", err)
}
if obj.PinTopic, err = p.PreparexContext(c, p.Rebind(p.QueryHook(_TopicA_PinTopic))); err != nil {
return nil, fmt.Errorf("prepare _TopicA_PinTopic error: %w", err)
}
if obj.StickTopic, err = p.PreparexContext(c, p.Rebind(p.QueryHook(_TopicA_StickTopic))); err != nil {
return nil, fmt.Errorf("prepare _TopicA_StickTopic error: %w", err)
}
return
}

@ -133,8 +133,12 @@ func (s *topicSrvA) GetNewestTags(userId int64, limit int, offset int) (res cs.T
return
}
func (s *topicSrvA) GetFollowTags(userId int64, limit int, offset int) (res cs.TagList, err error) {
if err = s.q.FollowTags.Select(&res, userId, limit, offset); err != nil {
func (s *topicSrvA) GetFollowTags(userId int64, isPin bool, limit int, offset int) (res cs.TagList, err error) {
stmt := s.q.FollowTags
if isPin {
stmt = s.q.FollowPinTags
}
if err = stmt.Select(&res, userId, limit, offset); err != nil {
return
}
return s.tagsFormatA(userId, res)
@ -164,6 +168,17 @@ func (s *topicSrvA) StickTopic(userId int64, topicId int64) (res int8, err error
return
}
func (s *topicSrvA) PinTopic(userId int64, topicId int64) (res int8, err error) {
s.db.Withx(func(tx *sqlx.Tx) error {
_, err = tx.Stmtx(s.q.PinTopic).Exec(time.Now().Unix(), userId, topicId)
if err == nil {
err = tx.Stmtx(s.q.TopicIsPin).Get(&res, userId, topicId)
}
return err
})
return
}
func (s *topicSrvA) tagsFormatA(userId int64, tags cs.TagList) (cs.TagList, error) {
if len(tags) == 0 {
return tags, nil

@ -78,3 +78,13 @@ func (s *pgaTopicSrvA) UpsertTags(userId int64, tags []string) (res cs.TagInfoLi
})
return
}
func (s *pgaTopicSrvA) StickTopic(userId int64, topicId int64) (res int8, err error) {
err = s.p.StickTopic.Get(&res, time.Now().Unix(), userId, topicId)
return
}
func (s *pgaTopicSrvA) PinTopic(userId int64, topicId int64) (res int8, err error) {
err = s.p.PinTopic.Get(&res, time.Now().Unix(), userId, topicId)
return
}

@ -139,6 +139,7 @@ SELECT t.id id,
u.id "u.id",
1 as is_following,
c.is_top,
c.is_pin,
u.nickname "u.nickname",
u.username "u.username",
u.status "u.status",
@ -151,6 +152,29 @@ WHERE c.is_del = 0 AND t.quote_num > 0 AND c.user_id=?
ORDER BY c.is_top DESC, t.quote_num DESC
LIMIT ? OFFSET ?;
-- name: follow_pin_tags@topic_a
-- get get follow tag information
-- prepare: stmt
SELECT t.id id,
t.user_id user_id,
t.tag tag,
t.quote_num quote_num,
u.id "u.id",
1 as is_following,
c.is_top,
c.is_pin,
u.nickname "u.nickname",
u.username "u.username",
u.status "u.status",
u.avatar "u.avatar",
u.is_admin "u.is_admin"
FROM @topic_user c
JOIN @user u ON c.user_id = u.id
JOIN @tag t ON c.topic_id = t.id
WHERE c.is_del = 0 AND t.quote_num > 0 AND c.user_id=? AND c.is_pin=1
ORDER BY c.is_top DESC, t.quote_num DESC
LIMIT ? OFFSET ?;
-- name: topic_infos@topic_a
-- prepare: raw
-- clause: in
@ -177,10 +201,20 @@ UPDATE @topic_user
SET is_top=1-is_top, modified_on=?
WHERE user_id=? AND topic_id=? AND is_del=0;
-- name: pin_topic@topic_a
-- prepare: stmt
UPDATE @topic_user
SET is_pin=1-is_pin, modified_on=?
WHERE user_id=? AND topic_id=? AND is_del=0;
-- name: topic_is_top@topic_a
-- prepare: stmt
SELECT is_top FROM @topic_user WHERE user_id=? AND topic_id=? AND is_del=0;
-- name: topic_is_pin@topic_a
-- prepare: stmt
SELECT is_pin FROM @topic_user WHERE user_id=? AND topic_id=? AND is_del=0;
-- name: tags_by_keyword_a@topic_a
-- get tags by keyword
SELECT id, user_id, tag, quote_num

@ -12,3 +12,17 @@
INSERT INTO @tag (user_id, tag, created_on, modified_on, quote_num)
VALUES (?, ?, ?, ?, 1)
RETURNING id;
-- name: stick_topic@topic_a
-- prepare: stmt
UPDATE @topic_user
SET is_top=1-is_top, modified_on=?
WHERE user_id=? AND topic_id=? AND is_del=0;
RETURNING is_top;
-- name: pin_topic@topic_a
-- prepare: stmt
UPDATE @topic_user
SET is_pin=1-is_pin, modified_on=?
WHERE user_id=? AND topic_id=? AND is_del=0;
RETURNING is_pin;

@ -20,6 +20,7 @@ const (
TagTypeHot = cs.TagTypeHot
TagTypeNew = cs.TagTypeNew
TagTypeFollow = cs.TagTypeFollow
TagTypePin = cs.TagTypePin
TagTypeHotExtral = cs.TagTypeHotExtral
)

@ -207,6 +207,15 @@ type StickTopicResp struct {
StickStatus int8 `json:"top_status"`
}
type PinTopicReq struct {
SimpleInfo `json:"-" binding:"-"`
TopicId int64 `json:"topic_id" binding:"required"`
}
type PinTopicResp struct {
PinStatus int8 `json:"pin_status"`
}
type FollowTopicReq struct {
SimpleInfo `json:"-" binding:"-"`
TopicId int64 `json:"topic_id" binding:"required"`

@ -95,6 +95,7 @@ var (
ErrFollowTopicFailed = xerror.NewError(90001, "关注话题失败")
ErrUnfollowTopicFailed = xerror.NewError(90002, "取消关注话题失败")
ErrStickTopicFailed = xerror.NewError(90003, "更行话题置顶状态失败")
ErrPinTopicFailed = xerror.NewError(90005, "更行话题钉住状态失败")
ErrThumbsUpTweetComment = xerror.NewError(90101, "评论点赞失败")
ErrThumbsDownTweetComment = xerror.NewError(90102, "评论点踩失败")
ErrThumbsUpTweetReply = xerror.NewError(90103, "评论回复点赞失败")

@ -7,8 +7,8 @@ package servants
import (
"net/http"
"connectrpc.com/connect"
"github.com/alimy/tryst/cfg"
"github.com/bufbuild/connect-go"
"github.com/gin-gonic/gin"
"github.com/rocboss/paopao-ce/internal/servants/admin"
"github.com/rocboss/paopao-ce/internal/servants/bot"

@ -7,7 +7,7 @@ package triplet
import (
"net/http"
"github.com/bufbuild/connect-go"
"connectrpc.com/connect"
api "github.com/rocboss/paopao-ce/auto/connect/core/v1/corev1connect"
)

@ -384,7 +384,9 @@ func (s *looseSrv) TopicList(req *web.TopicListReq) (*web.TopicListResp, mir.Err
case web.TagTypeNew:
tags, err = s.Ds.GetNewestTags(req.Uid, num, 0)
case web.TagTypeFollow:
tags, err = s.Ds.GetFollowTags(req.Uid, num, 0)
tags, err = s.Ds.GetFollowTags(req.Uid, false, num, 0)
case web.TagTypePin:
tags, err = s.Ds.GetFollowTags(req.Uid, true, num, 0)
case web.TagTypeHotExtral:
extralNum := req.ExtralNum
if extralNum <= 0 {
@ -392,7 +394,7 @@ func (s *looseSrv) TopicList(req *web.TopicListReq) (*web.TopicListResp, mir.Err
}
tags, err = s.Ds.GetHotTags(req.Uid, num, 0)
if err == nil {
extralTags, err = s.Ds.GetFollowTags(req.Uid, extralNum, 0)
extralTags, err = s.Ds.GetFollowTags(req.Uid, false, extralNum, 0)
}
default:
// TODO: return good error

@ -22,6 +22,7 @@ import (
"github.com/rocboss/paopao-ce/internal/model/web"
"github.com/rocboss/paopao-ce/internal/servants/base"
"github.com/rocboss/paopao-ce/internal/servants/chain"
"github.com/rocboss/paopao-ce/pkg/types"
"github.com/rocboss/paopao-ce/pkg/utils"
"github.com/rocboss/paopao-ce/pkg/xerror"
"github.com/sirupsen/logrus"
@ -124,13 +125,28 @@ func (s *privSrv) StickTopic(req *web.StickTopicReq) (*web.StickTopicResp, mir.E
}, nil
}
func (s *privSrv) PinTopic(req *web.PinTopicReq) (*web.PinTopicResp, mir.Error) {
status, err := s.Ds.PinTopic(req.Uid, req.TopicId)
if err != nil {
logrus.Errorf("user(%d) pin topic(%d) failed: %s", req.Uid, req.TopicId, err)
return nil, web.ErrPinTopicFailed
}
return &web.PinTopicResp{
PinStatus: status,
}, nil
}
func (s *privSrv) UploadAttachment(req *web.UploadAttachmentReq) (*web.UploadAttachmentResp, mir.Error) {
defer req.File.Close()
// 生成随机路径
randomPath := uuid.Must(uuid.NewV4()).String()
ossSavePath := req.UploadType + "/" + generatePath(randomPath[:8]) + "/" + randomPath[9:] + req.FileExt
objectUrl, err := s.oss.PutObject(ossSavePath, req.File, req.FileSize, req.ContentType, false)
// NOTE: 注意这里将req.File Wrap到一个io.Reader的实例对象中是为了避免下游接口去主动调Closereq.File本身是实现了
// io.Closer接口的有的下游接口会断言传参是否实现了io.Closer接口如果实现了会主动去调我们这里因为下文中可能还要继续
// 使用req.File所以应避免下游Close否则会出现潜在的bug比如这里的场景就是传一个超大的图片(>10MB)可能就会触发bug了。
data := types.PureReader(req.File)
objectUrl, err := s.oss.PutObject(ossSavePath, data, req.FileSize, req.ContentType, false)
if err != nil {
logrus.Errorf("oss.putObject err: %s", err)
return nil, web.ErrFileUploadFailed

@ -8,7 +8,7 @@ import (
"context"
"net/http"
"github.com/bufbuild/connect-go"
"connectrpc.com/connect"
hx "github.com/rocboss/paopao-ce/pkg/http"
)

@ -7,7 +7,7 @@ package service
import (
"net/http"
"github.com/bufbuild/connect-go"
"connectrpc.com/connect"
)
type baseConnectService struct {

@ -75,9 +75,12 @@ type Priv struct {
// ThumbsDownTweetReply 点踩评论回复
ThumbsDownTweetReply func(Post, web.TweetReplyThumbsReq) `mir:"/tweet/reply/thumbsdown"`
// StickTopic 置顶动态
// StickTopic 置顶话题
StickTopic func(Post, web.StickTopicReq) web.StickTopicResp `mir:"/topic/stick"`
// PinTopic 钉住话题
PinTopic func(Post, web.PinTopicReq) web.PinTopicResp `mir:"/topic/pin"`
// FollowTopic 关注话题
FollowTopic func(Post, web.FollowTopicReq) `mir:"/topic/follow"`

@ -0,0 +1,24 @@
// Copyright 2024 ROC. All rights reserved.
// Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file.
package types
import (
"io"
)
type readerWrap struct {
r io.Reader
}
func (rw *readerWrap) Read(p []byte) (n int, err error) {
return rw.r.Read(p)
}
// PureReader wrap a pure io.Reader object
func PureReader(r io.Reader) io.Reader {
return &readerWrap{
r: r,
}
}

@ -0,0 +1,36 @@
FROM getmeili/meilisearch:v1.5 as meilisearch
FROM redis:7.2-alpine
ENV TZ=Asia/Shanghai
RUN apk update && apk add --no-cache ca-certificates && update-ca-certificates
RUN apk update --quiet \
&& apk -q --no-cache --no-progress add \
ca-certificates \
libgcc \
curl \
s6 \
&& update-ca-certificates
WORKDIR /app
COPY ./docker ./docker
# add meilisearch and meilitool to the `/bin` so you can run it from anywhere
# and it's easy to find.
COPY --from=meilisearch /bin/meilisearch /bin/meilisearch
COPY --from=meilisearch /bin/meilitool /bin/meilitool
# To stay compatible with the older version of the container (pre v0.27.0) we're
# going to symlink the meilisearch binary in the path to `/meilisearch`
RUN ln -s /bin/meilisearch /meilisearch
ENV MEILI_HTTP_ADDR 0.0.0.0:7700
ENV MEILI_SERVER_PROVIDER docker
ENV MEILI_DB_PATH=/app/meili_data
ENV MEILI_MASTER_KEY=paopao-meilisearch
RUN ./docker/build/finalize.sh
# Configure Docker Container
VOLUME ["/app/meili_data", "/app/custom"]
EXPOSE 7700/tcp 6379 8008
HEALTHCHECK --interval=5s --timeout=3s --retries=3 CMD ps -ef | grep paopao || exit 1
ENTRYPOINT ["/app/docker/start.sh"]
CMD ["/bin/s6-svscan", "/app/docker/s6/"]

@ -1,6 +1,7 @@
### Dockerfile builer pre-build images
```sh
docker build -t bitbus/paopao-ce-backend-builder:latest -f Dockerfile-backend-builder .
docker build -t bitbus/paopao-ce-backend-runner:latest -f Dockerfile-backend-runner .
docker build -t bitbus/paopao-ce-backend-builder:latest -f Dockerfile.backend-builder .
docker build -t bitbus/paopao-ce-backend-runner:latest -f Dockerfile.backend-runner .
docker build -t bitbus/paopao-ce-allinone-runner:latest -f scripts/docker/Dockerfile.allinone-runner .
```

@ -0,0 +1,2 @@
ALTER TABLE `p_topic_user` DROP COLUMN `is_pin`;
DROP INDEX IF EXISTS `idx_topic_user_uid_ispin`;

@ -0,0 +1,2 @@
ALTER TABLE `p_topic_user` ADD COLUMN `is_pin` TINYINT NOT NULL DEFAULT 0 COMMENT '是否钉住 0 为未钉住、1 为已钉住';
CREATE INDEX `idx_topic_user_uid_ispin` ON `p_topic_user` (`user_id`, `is_pin`) USING BTREE;

@ -0,0 +1,2 @@
ALTER TABLE p_topic_user DROP COLUMN is_pin;
DROP INDEX IF EXISTS idx_topic_user_uid_ispin;

@ -0,0 +1,2 @@
ALTER TABLE p_topic_user ADD COLUMN is_pin SMALLINT NOT NULL DEFAULT 0; -- 是否钉住 0 为未钉住、1 为已钉住
CREATE INDEX idx_topic_user_uid_ispin ON p_topic_user USING btree ( user_id, is_pin );

@ -1,7 +1,5 @@
PRAGMA foreign_keys=off;
BEGIN TRANSACTION;
ALTER TABLE p_post_content RENAME TO _p_post_content;
ALTER TABLE p_comment_content RENAME TO _p_comment_content;
@ -53,46 +51,6 @@ CREATE TABLE p_comment_reply
PRIMARY KEY ("id")
);
-- ----------------------------
-- Indexes structure for table p_post_content
-- ----------------------------
CREATE INDEX "idx_post_content_post_id"
ON "p_post_content" (
"post_id" ASC
);
CREATE INDEX "idx_post_content_user_id"
ON "p_post_content" (
"user_id" ASC
);
-- ----------------------------
-- Indexes structure for table p_comment_reply
-- ----------------------------
CREATE INDEX "idx_comment_reply_comment_id"
ON "p_comment_reply" (
"comment_id" ASC
);
-- ----------------------------
-- Indexes structure for table p_comment_content
-- ----------------------------
CREATE INDEX "idx_comment_content_comment_id"
ON "p_comment_content" (
"comment_id" ASC
);
CREATE INDEX "idx_comment_content_sort"
ON "p_comment_content" (
"sort" ASC
);
CREATE INDEX "idx_comment_content_type"
ON "p_comment_content" (
"type" ASC
);
CREATE INDEX "idx_comment_content_user_id"
ON "p_comment_content" (
"user_id" ASC
);
INSERT INTO p_post_content (id, post_id, user_id, content, `type`, sort, created_on, modified_on, deleted_on, is_del)
SELECT id, post_id, user_id, content, `type`, sort, created_on, modified_on, deleted_on, is_del
FROM _p_post_content;
@ -112,10 +70,12 @@ DROP TABLE _p_comment_reply;
-- ----------------------------
-- Indexes structure for table p_post_content
-- ----------------------------
DROP INDEX IF EXISTS "idx_post_content_post_id";
CREATE INDEX "idx_post_content_post_id"
ON "p_post_content" (
"post_id" ASC
);
DROP INDEX IF EXISTS "idx_post_content_user_id";
CREATE INDEX "idx_post_content_user_id"
ON "p_post_content" (
"user_id" ASC
@ -124,6 +84,7 @@ ON "p_post_content" (
-- ----------------------------
-- Indexes structure for table p_comment_reply
-- ----------------------------
DROP INDEX IF EXISTS "idx_comment_reply_comment_id";
CREATE INDEX "idx_comment_reply_comment_id"
ON "p_comment_reply" (
"comment_id" ASC
@ -132,23 +93,25 @@ ON "p_comment_reply" (
-- ----------------------------
-- Indexes structure for table p_comment_content
-- ----------------------------
DROP INDEX IF EXISTS "idx_comment_content_comment_id";
CREATE INDEX "idx_comment_content_comment_id"
ON "p_comment_content" (
"comment_id" ASC
);
DROP INDEX IF EXISTS "idx_comment_content_sort";
CREATE INDEX "idx_comment_content_sort"
ON "p_comment_content" (
"sort" ASC
);
DROP INDEX IF EXISTS "idx_comment_content_type";
CREATE INDEX "idx_comment_content_type"
ON "p_comment_content" (
"type" ASC
);
DROP INDEX IF EXISTS "idx_comment_content_user_id";
CREATE INDEX "idx_comment_content_user_id"
ON "p_comment_content" (
"user_id" ASC
);
COMMIT;
PRAGMA foreign_keys=on;

@ -0,0 +1,2 @@
ALTER TABLE "p_topic_user" DROP COLUMN "is_pin";
DROP INDEX IF EXISTS "idx_topic_user_uid_ispin";

@ -0,0 +1,6 @@
ALTER TABLE "p_topic_user" ADD COLUMN "is_pin" integer NOT NULL DEFAULT 0;
CREATE INDEX "main"."idx_topic_user_uid_ispin"
ON "p_topic_user" (
"user_id" ASC,
"is_pin" ASC
);

@ -324,6 +324,7 @@ CREATE TABLE `p_topic_user` (
`remark` VARCHAR ( 512 ) COMMENT '备注',
`quote_num` BIGINT COMMENT '引用数',
`is_top` TINYINT NOT NULL DEFAULT '0' COMMENT '是否置顶 0 为未置顶、1 为已置顶',
`is_pin` TINYINT NOT NULL DEFAULT '0' COMMENT '是否钉住 0 为未钉住、1 为已钉住',
`created_on` BIGINT NOT NULL DEFAULT '0' COMMENT '创建时间',
`modified_on` BIGINT NOT NULL DEFAULT '0' COMMENT '修改时间',
`deleted_on` BIGINT NOT NULL DEFAULT '0' COMMENT '删除时间',
@ -331,7 +332,8 @@ CREATE TABLE `p_topic_user` (
`reserve_a` VARCHAR ( 255 ) COMMENT '保留字段a',
`reserve_b` VARCHAR ( 255 ) COMMENT '保留字段b',
PRIMARY KEY ( `id` ) USING BTREE,
UNIQUE KEY `idx_topic_user_uid_tid` ( `topic_id`, `user_id` ) USING BTREE
UNIQUE KEY `idx_topic_user_uid_tid` ( `topic_id`, `user_id` ) USING BTREE,
KEY `idx_topic_user_uid_ispin` ( `user_id`, `is_pin`) USING BTREE
) ENGINE = INNODB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '用户话题';
-- ----------------------------

@ -287,6 +287,7 @@ CREATE TABLE p_topic_user (
remark VARCHAR ( 512 ),-- 备注
quote_num BIGINT,-- 引用数
is_top SMALLINT NOT NULL DEFAULT 0,-- 是否置顶 0 为未置顶、1 为已置顶
is_pin SMALLINT NOT NULL DEFAULT 0,-- 是否钉住 0 为未钉住、1 为已钉住
created_on BIGINT NOT NULL DEFAULT 0,-- 创建时间
modified_on BIGINT NOT NULL DEFAULT 0,-- 修改时间
deleted_on BIGINT NOT NULL DEFAULT 0,-- 删除时间
@ -295,6 +296,7 @@ CREATE TABLE p_topic_user (
reserve_b VARCHAR ( 255 ) -- 保留字段b
);
CREATE UNIQUE INDEX idx_topic_user_uid_tid ON p_topic_user USING btree ( topic_id, user_id );
CREATE INDEX idx_topic_user_uid_ispin ON p_topic_user USING btree ( user_id, is_pin );
CREATE SEQUENCE IF NOT EXISTS user_id_seq AS BIGINT MINVALUE 100058 NO MAXVALUE;
DROP TABLE IF EXISTS p_user;

@ -341,6 +341,7 @@ CREATE TABLE "p_topic_user" (
"remark" text ( 512 ),-- 备注
"quote_num" integer,-- 引用数
"is_top" integer NOT NULL DEFAULT 0,-- 是否置顶 0 为未置顶、1 为已置顶
"is_pin" integer NOT NULL DEFAULT 0,-- 是否钉住 0 为未钉住、1 为已钉住
"created_on" integer NOT NULL DEFAULT 0,-- 创建时间
"modified_on" integer NOT NULL DEFAULT 0,-- 修改时间
"deleted_on" integer NOT NULL DEFAULT 0,-- 删除时间
@ -680,6 +681,11 @@ ON "p_topic_user" (
"topic_id",
"user_id"
);
CREATE INDEX "main"."idx_topic_user_uid_ispin"
ON "p_topic_user" (
"user_id" ASC,
"is_pin" ASC
);
-- ----------------------------
-- Indexes structure for table p_user

@ -7,7 +7,7 @@ VITE_USE_WEB_PROFILE=true # 是否使用后端web服务提供的WebPro
# 模块开启
VITE_ENABLE_ANOUNCEMENT=false
VITE_ENABLE_WALLET=false
VITE_ENABLE_TRENDS_BAR=true
VITE_ENABLE_TRENDS_BAR=false
# 功能开启
VITE_ALLOW_TWEET_ATTACHMENT=true

@ -1 +0,0 @@
import{_ as i}from"./main-nav.vue_vue_type_style_index_0_lang--76-h8Yy.js";import{u as s}from"./vue-router-22lN-LLO.js";import{G as a,e as c,a2 as u}from"./naive-ui-qF3urcFV.js";import{d as l,f as d,k as t,w as o,e as f,A as x}from"./@vue-73x4sYJ2.js";import{_ as g}from"./index-Lx4Mi1rj.js";import"./vuex-6eozxOS7.js";import"./vooks-574GUng3.js";import"./evtd-9ZCiDXyn.js";import"./@vicons-UfsZxvNZ.js";import"./seemly-tZbmuCcS.js";import"./vueuc-oXvKre1p.js";import"./@css-render-RY9kiobo.js";import"./vdirs-gz97tqc5.js";import"./@juggle--NVrOerG.js";import"./css-render-Jaty3dru.js";import"./@emotion-vV6BesBt.js";import"./lodash-es-KEIJqYRD.js";import"./treemate-hmrDCADh.js";import"./async-validator-BHjhHa7C.js";import"./date-fns-E8ESfRGG.js";import"./axios-QLjAsgXu.js";import"./moment-TH1CLKMj.js";/* empty css */const h=l({__name:"404",setup(k){const n=s(),e=()=>{n.push({path:"/"})};return(w,v)=>{const r=i,p=c,_=u,m=a;return f(),d("div",null,[t(r,{title:"404"}),t(m,{class:"main-content-wrap wrap404",bordered:""},{default:o(()=>[t(_,{status:"404",title:"404 资源不存在",description:"再看看其他的吧"},{footer:o(()=>[t(p,{onClick:e},{default:o(()=>[x("回主页")]),_:1})]),_:1})]),_:1})])}}}),O=g(h,[["__scopeId","data-v-e62daa85"]]);export{O as default};

@ -0,0 +1 @@
import{_ as i}from"./main-nav.vue_vue_type_style_index_0_lang-qm71WtqL.js";import{u as s}from"./vue-router-KVMegFg5.js";import{G as a,e as c,a2 as u}from"./naive-ui-Xe90xWx_.js";import{d as l,f as d,k as t,w as o,e as f,A as x}from"./@vue-OWLFCSZf.js";import{_ as g}from"./index-qG_8BN-j.js";import"./vuex-az5e4eav.js";import"./vooks-m9NwUyK6.js";import"./evtd-9ZCiDXyn.js";import"./@vicons-0TGbfQ8H.js";import"./seemly-hKSMrbh9.js";import"./vueuc-9lIKNc7l.js";import"./@css-render-NyXtGlUD.js";import"./vdirs-gz97tqc5.js";import"./@juggle--NVrOerG.js";import"./css-render-Adblu2bf.js";import"./@emotion-vV6BesBt.js";import"./lodash-es-KEIJqYRD.js";import"./treemate-hmrDCADh.js";import"./async-validator-BHjhHa7C.js";import"./date-fns-E8ESfRGG.js";import"./axios-kMxbiGYq.js";import"./moment-jIwEdMgI.js";/* empty css */const h=l({__name:"404",setup(k){const n=s(),e=()=>{n.push({path:"/"})};return(w,v)=>{const r=i,p=c,_=u,m=a;return f(),d("div",null,[t(r,{title:"404"}),t(m,{class:"main-content-wrap wrap404",bordered:""},{default:o(()=>[t(_,{status:"404",title:"404 资源不存在",description:"再看看其他的吧"},{footer:o(()=>[t(p,{onClick:e},{default:o(()=>[x("回主页")]),_:1})]),_:1})]),_:1})])}}}),O=g(h,[["__scopeId","data-v-e62daa85"]]);export{O as default};

@ -1,3 +1,3 @@
import{i as d}from"./@vue-73x4sYJ2.js";function C(i){let r=".",f="__",o="--",s;if(i){let e=i.blockPrefix;e&&(r=e),e=i.elementPrefix,e&&(f=e),e=i.modifierPrefix,e&&(o=e)}const b={install(e){s=e.c;const l=e.context;l.bem={},l.bem.b=null,l.bem.els=null}};function v(e){let l,n;return{before(t){l=t.bem.b,n=t.bem.els,t.bem.els=null},after(t){t.bem.b=l,t.bem.els=n},$({context:t,props:u}){return e=typeof e=="string"?e:e({context:t,props:u}),t.bem.b=e,`${(u==null?void 0:u.bPrefix)||r}${t.bem.b}`}}}function y(e){let l;return{before(n){l=n.bem.els},after(n){n.bem.els=l},$({context:n,props:t}){return e=typeof e=="string"?e:e({context:n,props:t}),n.bem.els=e.split(",").map(u=>u.trim()),n.bem.els.map(u=>`${(t==null?void 0:t.bPrefix)||r}${n.bem.b}${f}${u}`).join(", ")}}}function P(e){return{$({context:l,props:n}){e=typeof e=="string"?e:e({context:l,props:n});const t=e.split(",").map(m=>m.trim());function u(m){return t.map(x=>`&${(n==null?void 0:n.bPrefix)||r}${l.bem.b}${m!==void 0?`${f}${m}`:""}${o}${x}`).join(", ")}const c=l.bem.els;return c!==null?u(c[0]):u()}}}function _(e){return{$({context:l,props:n}){e=typeof e=="string"?e:e({context:l,props:n});const t=l.bem.els;return`&:not(${(n==null?void 0:n.bPrefix)||r}${l.bem.b}${t!==null&&t.length>0?`${f}${t[0]}`:""}${o}${e})`}}}return Object.assign(b,{cB:(...e)=>s(v(e[0]),e[1],e[2]),cE:(...e)=>s(y(e[0]),e[1],e[2]),cM:(...e)=>s(P(e[0]),e[1],e[2]),cNotM:(...e)=>s(_(e[0]),e[1],e[2])}),b}const $=Symbol("@css-render/vue3-ssr");function M(i,r){return`<style cssr-id="${i}">
import{i as d}from"./@vue-OWLFCSZf.js";function C(i){let r=".",f="__",o="--",s;if(i){let e=i.blockPrefix;e&&(r=e),e=i.elementPrefix,e&&(f=e),e=i.modifierPrefix,e&&(o=e)}const b={install(e){s=e.c;const l=e.context;l.bem={},l.bem.b=null,l.bem.els=null}};function v(e){let l,n;return{before(t){l=t.bem.b,n=t.bem.els,t.bem.els=null},after(t){t.bem.b=l,t.bem.els=n},$({context:t,props:u}){return e=typeof e=="string"?e:e({context:t,props:u}),t.bem.b=e,`${(u==null?void 0:u.bPrefix)||r}${t.bem.b}`}}}function y(e){let l;return{before(n){l=n.bem.els},after(n){n.bem.els=l},$({context:n,props:t}){return e=typeof e=="string"?e:e({context:n,props:t}),n.bem.els=e.split(",").map(u=>u.trim()),n.bem.els.map(u=>`${(t==null?void 0:t.bPrefix)||r}${n.bem.b}${f}${u}`).join(", ")}}}function P(e){return{$({context:l,props:n}){e=typeof e=="string"?e:e({context:l,props:n});const t=e.split(",").map(m=>m.trim());function u(m){return t.map(x=>`&${(n==null?void 0:n.bPrefix)||r}${l.bem.b}${m!==void 0?`${f}${m}`:""}${o}${x}`).join(", ")}const c=l.bem.els;return c!==null?u(c[0]):u()}}}function _(e){return{$({context:l,props:n}){e=typeof e=="string"?e:e({context:l,props:n});const t=l.bem.els;return`&:not(${(n==null?void 0:n.bPrefix)||r}${l.bem.b}${t!==null&&t.length>0?`${f}${t[0]}`:""}${o}${e})`}}}return Object.assign(b,{cB:(...e)=>s(v(e[0]),e[1],e[2]),cE:(...e)=>s(y(e[0]),e[1],e[2]),cM:(...e)=>s(P(e[0]),e[1],e[2]),cNotM:(...e)=>s(_(e[0]),e[1],e[2])}),b}const $=Symbol("@css-render/vue3-ssr");function M(i,r){return`<style cssr-id="${i}">
${r}
</style>`}function S(i,r){const f=d($,null);if(f===null){console.error("[css-render/vue3-ssr]: no ssr context found.");return}const{styles:o,ids:s}=f;s.has(i)||o!==null&&(s.add(i),o.push(M(i,r)))}const j=typeof document<"u";function N(){if(j)return;const i=d($,null);if(i!==null)return{adapter:S,context:i}}export{C as p,N as u};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -1 +0,0 @@
import{_ as N}from"./post-skeleton-IEvC_QvH.js";import{_ as R}from"./main-nav.vue_vue_type_style_index_0_lang--76-h8Yy.js";import{u as z}from"./vuex-6eozxOS7.js";import{b as F}from"./vue-router-22lN-LLO.js";import{J as S,_ as V}from"./index-Lx4Mi1rj.js";import{G as A,R as H,J,H as P}from"./naive-ui-qF3urcFV.js";import{d as j,H as n,b as q,f as e,k as a,w as p,e as o,bf as u,Z as l,F as D,x as E,t as _,j as s,l as G,v as I}from"./@vue-73x4sYJ2.js";import"./vooks-574GUng3.js";import"./evtd-9ZCiDXyn.js";import"./@vicons-UfsZxvNZ.js";import"./axios-QLjAsgXu.js";import"./moment-TH1CLKMj.js";/* empty css */import"./seemly-tZbmuCcS.js";import"./vueuc-oXvKre1p.js";import"./@css-render-RY9kiobo.js";import"./vdirs-gz97tqc5.js";import"./@juggle--NVrOerG.js";import"./css-render-Jaty3dru.js";import"./@emotion-vV6BesBt.js";import"./lodash-es-KEIJqYRD.js";import"./treemate-hmrDCADh.js";import"./async-validator-BHjhHa7C.js";import"./date-fns-E8ESfRGG.js";const L={key:0,class:"pagination-wrap"},M={key:0,class:"skeleton-wrap"},O={key:1},T={key:0,class:"empty-wrap"},U={class:"bill-line"},Z=j({__name:"Anouncement",setup($){const d=z(),g=F(),v=n(!1),r=n([]),i=n(+g.query.p||1),f=n(20),m=n(0),h=c=>{i.value=c};return q(()=>{}),(c,K)=>{const k=R,y=H,w=N,x=J,B=P,C=A;return o(),e("div",null,[a(k,{title:"公告"}),a(C,{class:"main-content-wrap",bordered:""},{footer:p(()=>[m.value>1?(o(),e("div",L,[a(y,{page:i.value,"onUpdate:page":h,"page-slot":u(d).state.collapsedRight?5:8,"page-count":m.value},null,8,["page","page-slot","page-count"])])):l("",!0)]),default:p(()=>[v.value?(o(),e("div",M,[a(w,{num:f.value},null,8,["num"])])):(o(),e("div",O,[r.value.length===0?(o(),e("div",T,[a(x,{size:"large",description:"暂无数据"})])):l("",!0),(o(!0),e(D,null,E(r.value,t=>(o(),I(B,{key:t.id},{default:p(()=>[s("div",U,[s("div",null,"NO."+_(t.id),1),s("div",null,_(t.reason),1),s("div",{class:G({income:t.change_amount>=0,out:t.change_amount<0})},_((t.change_amount>0?"+":"")+(t.change_amount/100).toFixed(2)),3),s("div",null,_(u(S)(t.created_on)),1)])]),_:2},1024))),128))]))]),_:1})])}}}),kt=V(Z,[["__scopeId","data-v-d4d04859"]]);export{kt as default};

@ -0,0 +1 @@
import{_ as N}from"./post-skeleton-uQTNCebs.js";import{_ as R}from"./main-nav.vue_vue_type_style_index_0_lang-qm71WtqL.js";import{u as z}from"./vuex-az5e4eav.js";import{b as F}from"./vue-router-KVMegFg5.js";import{K as S,_ as V}from"./index-qG_8BN-j.js";import{G as A,R as H,J as P,H as j}from"./naive-ui-Xe90xWx_.js";import{d as q,H as n,b as D,f as e,k as a,w as p,e as o,bk as u,Z as l,F as E,x as G,t as _,j as s,l as I,v as J}from"./@vue-OWLFCSZf.js";import"./vooks-m9NwUyK6.js";import"./evtd-9ZCiDXyn.js";import"./@vicons-0TGbfQ8H.js";import"./axios-kMxbiGYq.js";import"./moment-jIwEdMgI.js";/* empty css */import"./seemly-hKSMrbh9.js";import"./vueuc-9lIKNc7l.js";import"./@css-render-NyXtGlUD.js";import"./vdirs-gz97tqc5.js";import"./@juggle--NVrOerG.js";import"./css-render-Adblu2bf.js";import"./@emotion-vV6BesBt.js";import"./lodash-es-KEIJqYRD.js";import"./treemate-hmrDCADh.js";import"./async-validator-BHjhHa7C.js";import"./date-fns-E8ESfRGG.js";const K={key:0,class:"pagination-wrap"},L={key:0,class:"skeleton-wrap"},M={key:1},O={key:0,class:"empty-wrap"},T={class:"bill-line"},U=q({__name:"Anouncement",setup(Z){const d=z(),g=F(),v=n(!1),r=n([]),i=n(+g.query.p||1),f=n(20),m=n(0),h=c=>{i.value=c};return D(()=>{}),(c,$)=>{const k=R,y=H,w=N,x=P,B=j,C=A;return o(),e("div",null,[a(k,{title:"公告"}),a(C,{class:"main-content-wrap",bordered:""},{footer:p(()=>[m.value>1?(o(),e("div",K,[a(y,{page:i.value,"onUpdate:page":h,"page-slot":u(d).state.collapsedRight?5:8,"page-count":m.value},null,8,["page","page-slot","page-count"])])):l("",!0)]),default:p(()=>[v.value?(o(),e("div",L,[a(w,{num:f.value},null,8,["num"])])):(o(),e("div",M,[r.value.length===0?(o(),e("div",O,[a(x,{size:"large",description:"暂无数据"})])):l("",!0),(o(!0),e(E,null,G(r.value,t=>(o(),J(B,{key:t.id},{default:p(()=>[s("div",T,[s("div",null,"NO."+_(t.id),1),s("div",null,_(t.reason),1),s("div",{class:I({income:t.change_amount>=0,out:t.change_amount<0})},_((t.change_amount>0?"+":"")+(t.change_amount/100).toFixed(2)),3),s("div",null,_(u(S)(t.created_on)),1)])]),_:2},1024))),128))]))]),_:1})])}}}),kt=V(U,[["__scopeId","data-v-d4d04859"]]);export{kt as default};

@ -1 +1 @@
import{_ as D}from"./whisper-nUbeLD5N.js";import{_ as R,a as U}from"./post-item.vue_vue_type_style_index_0_lang-MnafUKn3.js";import{_ as q}from"./post-skeleton-IEvC_QvH.js";import{_ as E}from"./main-nav.vue_vue_type_style_index_0_lang--76-h8Yy.js";import{u as G}from"./vuex-6eozxOS7.js";import{b as J}from"./vue-router-22lN-LLO.js";import{W as L}from"./v3-infinite-loading-yUDJG3gQ.js";import{T as Z,u as K,f as Q,_ as X}from"./index-Lx4Mi1rj.js";import{d as Y,H as t,b as ee,f as n,k as a,w as u,v as d,Z as h,e as o,bf as f,F as b,x as $,j as z,t as oe}from"./@vue-73x4sYJ2.js";import{F as se,G as te,a as ne,J as ae,k as ie,H as le}from"./naive-ui-qF3urcFV.js";import"./content-jjSUke8z.js";import"./@vicons-UfsZxvNZ.js";import"./paopao-video-player-c1AKUL7s.js";import"./copy-to-clipboard-l6UqHK6O.js";import"./@babel-5-cIlDoe.js";import"./toggle-selection-fekekO1r.js";import"./vooks-574GUng3.js";import"./evtd-9ZCiDXyn.js";import"./axios-QLjAsgXu.js";import"./moment-TH1CLKMj.js";/* empty css */import"./seemly-tZbmuCcS.js";import"./vueuc-oXvKre1p.js";import"./@css-render-RY9kiobo.js";import"./vdirs-gz97tqc5.js";import"./@juggle--NVrOerG.js";import"./css-render-Jaty3dru.js";import"./@emotion-vV6BesBt.js";import"./lodash-es-KEIJqYRD.js";import"./treemate-hmrDCADh.js";import"./async-validator-BHjhHa7C.js";import"./date-fns-E8ESfRGG.js";const re={key:0,class:"skeleton-wrap"},_e={key:1},ue={key:0,class:"empty-wrap"},ce={key:1},me={key:2},pe={class:"load-more-wrap"},de={class:"load-more-spinner"},fe=Y({__name:"Collection",setup(ve){const v=G(),A=J(),B=se(),c=t(!1),_=t(!1),s=t([]),l=t(+A.query.p||1),w=t(20),m=t(0),g=t(!1),k=t({id:0,avatar:"",username:"",nickname:"",is_admin:!1,is_friend:!0,is_following:!1,created_on:0,follows:0,followings:0,status:1}),y=e=>{k.value=e,g.value=!0},I=()=>{g.value=!1},x=e=>{B.success({title:"提示",content:"确定"+(e.user.is_following?"取消关注":"关注")+"该用户吗?",positiveText:"确定",negativeText:"取消",onPositiveClick:()=>{e.user.is_following?K({user_id:e.user.id}).then(r=>{window.$message.success("操作成功"),C(e.user_id,!1)}).catch(r=>{}):Q({user_id:e.user.id}).then(r=>{window.$message.success("关注成功"),C(e.user_id,!0)}).catch(r=>{})}})};function C(e,r){for(let p in s.value)s.value[p].user_id==e&&(s.value[p].user.is_following=r)}const F=()=>{c.value=!0,Z({page:l.value,page_size:w.value}).then(e=>{c.value=!1,e.list.length===0&&(_.value=!0),l.value>1?s.value=s.value.concat(e.list):(s.value=e.list,window.scrollTo(0,0)),m.value=Math.ceil(e.pager.total_rows/w.value)}).catch(e=>{c.value=!1,l.value>1&&l.value--})},M=()=>{l.value<m.value||m.value==0?(_.value=!1,l.value++,F()):_.value=!0};return ee(()=>{F()}),(e,r)=>{const p=E,O=q,P=ae,T=R,S=le,H=U,N=D,V=te,W=ie,j=ne;return o(),n("div",null,[a(p,{title:"收藏"}),a(V,{class:"main-content-wrap",bordered:""},{default:u(()=>[c.value&&s.value.length===0?(o(),n("div",re,[a(O,{num:w.value},null,8,["num"])])):(o(),n("div",_e,[s.value.length===0?(o(),n("div",ue,[a(P,{size:"large",description:"暂无数据"})])):h("",!0),f(v).state.desktopModelShow?(o(),n("div",ce,[(o(!0),n(b,null,$(s.value,i=>(o(),d(S,{key:i.id},{default:u(()=>[a(T,{post:i,isOwner:f(v).state.userInfo.id==i.user_id,addFollowAction:!0,onSendWhisper:y,onHandleFollowAction:x},null,8,["post","isOwner"])]),_:2},1024))),128))])):(o(),n("div",me,[(o(!0),n(b,null,$(s.value,i=>(o(),d(S,{key:i.id},{default:u(()=>[a(H,{post:i,isOwner:f(v).state.userInfo.id==i.user_id,addFollowAction:!0,onSendWhisper:y,onHandleFollowAction:x},null,8,["post","isOwner"])]),_:2},1024))),128))]))])),a(N,{show:g.value,user:k.value,onSuccess:I},null,8,["show","user"])]),_:1}),m.value>0?(o(),d(j,{key:0,justify:"center"},{default:u(()=>[a(f(L),{class:"load-more",slots:{complete:"没有更多收藏了",error:"加载出错"},onInfinite:M},{spinner:u(()=>[z("div",pe,[_.value?h("",!0):(o(),d(W,{key:0,size:14})),z("span",de,oe(_.value?"没有更多收藏了":"加载更多"),1)])]),_:1})]),_:1})):h("",!0)])}}}),Ze=X(fe,[["__scopeId","data-v-735372fb"]]);export{Ze as default};
import{_ as j}from"./whisper-kyuywE3Q.js";import{_ as D,a as R}from"./post-item.vue_vue_type_style_index_0_lang-pCBMqHTs.js";import{_ as q}from"./post-skeleton-uQTNCebs.js";import{_ as E}from"./main-nav.vue_vue_type_style_index_0_lang-qm71WtqL.js";import{u as G}from"./vuex-az5e4eav.js";import{b as J}from"./vue-router-KVMegFg5.js";import{W as L}from"./v3-infinite-loading-vHB4M6bL.js";import{U as Z,u as K,f as Q,_ as X}from"./index-qG_8BN-j.js";import{d as Y,H as t,b as ee,f as n,k as a,w as u,v as d,Z as h,e as o,bk as f,F as b,x as $,j as z,t as oe}from"./@vue-OWLFCSZf.js";import{F as se,G as te,a as ne,J as ae,k as ie,H as le}from"./naive-ui-Xe90xWx_.js";import"./content-2RVjnZuU.js";import"./@vicons-0TGbfQ8H.js";import"./paopao-video-player-HKqQZQ5A.js";import"./copy-to-clipboard-l6UqHK6O.js";import"./@babel-5-cIlDoe.js";import"./toggle-selection-fekekO1r.js";import"./vooks-m9NwUyK6.js";import"./evtd-9ZCiDXyn.js";import"./axios-kMxbiGYq.js";import"./moment-jIwEdMgI.js";/* empty css */import"./seemly-hKSMrbh9.js";import"./vueuc-9lIKNc7l.js";import"./@css-render-NyXtGlUD.js";import"./vdirs-gz97tqc5.js";import"./@juggle--NVrOerG.js";import"./css-render-Adblu2bf.js";import"./@emotion-vV6BesBt.js";import"./lodash-es-KEIJqYRD.js";import"./treemate-hmrDCADh.js";import"./async-validator-BHjhHa7C.js";import"./date-fns-E8ESfRGG.js";const re={key:0,class:"skeleton-wrap"},_e={key:1},ue={key:0,class:"empty-wrap"},ce={key:1},me={key:2},pe={class:"load-more-wrap"},de={class:"load-more-spinner"},fe=Y({__name:"Collection",setup(ve){const v=G(),A=J(),B=se(),c=t(!1),_=t(!1),s=t([]),l=t(+A.query.p||1),w=t(20),m=t(0),g=t(!1),k=t({id:0,avatar:"",username:"",nickname:"",is_admin:!1,is_friend:!0,is_following:!1,created_on:0,follows:0,followings:0,status:1}),y=e=>{k.value=e,g.value=!0},I=()=>{g.value=!1},x=e=>{B.success({title:"提示",content:"确定"+(e.user.is_following?"取消关注":"关注")+"该用户吗?",positiveText:"确定",negativeText:"取消",onPositiveClick:()=>{e.user.is_following?K({user_id:e.user.id}).then(r=>{window.$message.success("操作成功"),C(e.user_id,!1)}).catch(r=>{}):Q({user_id:e.user.id}).then(r=>{window.$message.success("关注成功"),C(e.user_id,!0)}).catch(r=>{})}})};function C(e,r){for(let p in s.value)s.value[p].user_id==e&&(s.value[p].user.is_following=r)}const F=()=>{c.value=!0,Z({page:l.value,page_size:w.value}).then(e=>{c.value=!1,e.list.length===0&&(_.value=!0),l.value>1?s.value=s.value.concat(e.list):(s.value=e.list,window.scrollTo(0,0)),m.value=Math.ceil(e.pager.total_rows/w.value)}).catch(e=>{c.value=!1,l.value>1&&l.value--})},M=()=>{l.value<m.value||m.value==0?(_.value=!1,l.value++,F()):_.value=!0};return ee(()=>{F()}),(e,r)=>{const p=E,O=q,P=ae,H=D,S=le,N=R,T=j,U=te,V=ie,W=ne;return o(),n("div",null,[a(p,{title:"收藏"}),a(U,{class:"main-content-wrap",bordered:""},{default:u(()=>[c.value&&s.value.length===0?(o(),n("div",re,[a(O,{num:w.value},null,8,["num"])])):(o(),n("div",_e,[s.value.length===0?(o(),n("div",ue,[a(P,{size:"large",description:"暂无数据"})])):h("",!0),f(v).state.desktopModelShow?(o(),n("div",ce,[(o(!0),n(b,null,$(s.value,i=>(o(),d(S,{key:i.id},{default:u(()=>[a(H,{post:i,isOwner:f(v).state.userInfo.id==i.user_id,addFollowAction:!0,onSendWhisper:y,onHandleFollowAction:x},null,8,["post","isOwner"])]),_:2},1024))),128))])):(o(),n("div",me,[(o(!0),n(b,null,$(s.value,i=>(o(),d(S,{key:i.id},{default:u(()=>[a(N,{post:i,isOwner:f(v).state.userInfo.id==i.user_id,addFollowAction:!0,onSendWhisper:y,onHandleFollowAction:x},null,8,["post","isOwner"])]),_:2},1024))),128))]))])),a(T,{show:g.value,user:k.value,onSuccess:I},null,8,["show","user"])]),_:1}),m.value>0?(o(),d(W,{key:0,justify:"center"},{default:u(()=>[a(f(L),{class:"load-more",slots:{complete:"没有更多收藏了",error:"加载出错"},onInfinite:M},{spinner:u(()=>[z("div",pe,[_.value?h("",!0):(o(),d(V,{key:0,size:14})),z("span",de,oe(_.value?"没有更多收藏了":"加载更多"),1)])]),_:1})]),_:1})):h("",!0)])}}}),Ze=X(fe,[["__scopeId","data-v-735372fb"]]);export{Ze as default};

@ -0,0 +1 @@
import{_ as W}from"./whisper-kyuywE3Q.js";import{d as P,c as A,r as L,e as s,f as p,k as t,w as o,y as R,t as d,A as E,j as a,bk as g,h as S,H as r,b as G,v as C,Z as b,F as M,x as J}from"./@vue-OWLFCSZf.js";import{L as U,_ as x,Y}from"./index-qG_8BN-j.js";import{k as Z,r as K}from"./@vicons-0TGbfQ8H.js";import{j as N,o as Q,e as X,P as ee,O as te,G as ne,a as oe,J as se,k as ae,H as ce}from"./naive-ui-Xe90xWx_.js";import{_ as ie}from"./post-skeleton-uQTNCebs.js";import{_ as re}from"./main-nav.vue_vue_type_style_index_0_lang-qm71WtqL.js";import{W as le}from"./v3-infinite-loading-vHB4M6bL.js";import{b as _e}from"./vue-router-KVMegFg5.js";import"./vuex-az5e4eav.js";import"./axios-kMxbiGYq.js";import"./moment-jIwEdMgI.js";/* empty css */import"./seemly-hKSMrbh9.js";import"./vueuc-9lIKNc7l.js";import"./evtd-9ZCiDXyn.js";import"./@css-render-NyXtGlUD.js";import"./vooks-m9NwUyK6.js";import"./vdirs-gz97tqc5.js";import"./@juggle--NVrOerG.js";import"./css-render-Adblu2bf.js";import"./@emotion-vV6BesBt.js";import"./lodash-es-KEIJqYRD.js";import"./treemate-hmrDCADh.js";import"./async-validator-BHjhHa7C.js";import"./date-fns-E8ESfRGG.js";const ue={class:"contact-item"},pe={class:"nickname-wrap"},me={class:"username-wrap"},de={class:"user-info"},fe={class:"info-item"},ve={class:"info-item"},he={class:"item-header-extra"},ge=P({__name:"contact-item",props:{contact:{}},emits:["send-whisper"],setup(z,{emit:w}){const _=w,l=e=>()=>S(N,null,{default:()=>S(e)}),n=z,c=A(()=>[{label:"私信 @"+n.contact.username,key:"whisper",icon:l(K)}]),m=e=>{switch(e){case"whisper":const i={id:n.contact.user_id,avatar:n.contact.avatar,username:n.contact.username,nickname:n.contact.nickname,is_admin:!1,is_friend:!0,is_following:!1,created_on:0,follows:0,followings:0,status:1};_("send-whisper",i);break}};return(e,i)=>{const f=Q,k=L("router-link"),y=X,$=ee,v=te;return s(),p("div",ue,[t(v,{"content-indented":""},{avatar:o(()=>[t(f,{size:54,src:e.contact.avatar},null,8,["src"])]),header:o(()=>[a("span",pe,[t(k,{onClick:i[0]||(i[0]=R(()=>{},["stop"])),class:"username-link",to:{name:"user",query:{s:e.contact.username}}},{default:o(()=>[E(d(e.contact.nickname),1)]),_:1},8,["to"])]),a("span",me," @"+d(e.contact.username),1),a("div",de,[a("span",fe," UID. "+d(e.contact.user_id),1),a("span",ve,d(g(U)(e.contact.created_on))+" 加入 ",1)])]),"header-extra":o(()=>[a("div",he,[t($,{placement:"bottom-end",trigger:"click",size:"small",options:c.value,onSelect:m},{default:o(()=>[t(y,{quaternary:"",circle:""},{icon:o(()=>[t(g(N),null,{default:o(()=>[t(g(Z))]),_:1})]),_:1})]),_:1},8,["options"])])]),_:1})])}}}),we=x(ge,[["__scopeId","data-v-42e975ce"]]),ke={key:0,class:"skeleton-wrap"},ye={key:1},$e={key:0,class:"empty-wrap"},Ce={class:"load-more-wrap"},be={class:"load-more-spinner"},ze=P({__name:"Contacts",setup(z){const w=_e(),_=r(!1),l=r(!1),n=r([]),c=r(+w.query.p||1),m=r(20),e=r(0),i=r(!1),f=r({id:0,avatar:"",username:"",nickname:"",is_admin:!1,is_friend:!0,is_following:!1,created_on:0,follows:0,followings:0,status:1}),k=h=>{f.value=h,i.value=!0},y=()=>{i.value=!1},$=()=>{c.value<e.value||e.value==0?(l.value=!1,c.value++,v()):l.value=!0};G(()=>{v()});const v=(h=!1)=>{n.value.length===0&&(_.value=!0),Y({page:c.value,page_size:m.value}).then(u=>{_.value=!1,u.list.length===0&&(l.value=!0),c.value>1?n.value=n.value.concat(u.list):(n.value=u.list,h&&setTimeout(()=>{window.scrollTo(0,99999)},50)),e.value=Math.ceil(u.pager.total_rows/m.value)}).catch(u=>{_.value=!1,c.value>1&&c.value--})};return(h,u)=>{const B=re,V=ie,j=se,q=we,D=ce,F=W,H=ne,O=ae,T=oe;return s(),p(M,null,[a("div",null,[t(B,{title:"好友"}),t(H,{class:"main-content-wrap",bordered:""},{default:o(()=>[_.value&&n.value.length===0?(s(),p("div",ke,[t(V,{num:m.value},null,8,["num"])])):(s(),p("div",ye,[n.value.length===0?(s(),p("div",$e,[t(j,{size:"large",description:"暂无数据"})])):b("",!0),(s(!0),p(M,null,J(n.value,I=>(s(),C(D,{class:"list-item",key:I.user_id},{default:o(()=>[t(q,{contact:I,onSendWhisper:k},null,8,["contact"])]),_:2},1024))),128))])),t(F,{show:i.value,user:f.value,onSuccess:y},null,8,["show","user"])]),_:1})]),e.value>0?(s(),C(T,{key:0,justify:"center"},{default:o(()=>[t(g(le),{class:"load-more",slots:{complete:"没有更多好友了",error:"加载出错"},onInfinite:$},{spinner:o(()=>[a("div",Ce,[l.value?b("",!0):(s(),C(O,{key:0,size:14})),a("span",be,d(l.value?"没有更多好友了":"加载更多"),1)])]),_:1})]),_:1})):b("",!0)],64)}}}),Qe=x(ze,[["__scopeId","data-v-69277f0c"]]);export{Qe as default};

@ -1 +0,0 @@
import{_ as W}from"./whisper-nUbeLD5N.js";import{d as P,c as A,r as R,e as s,f as p,k as t,w as o,y as E,t as d,A as G,j as a,bf as g,h as S,H as r,b as J,v as C,Z as b,F as M,x as K}from"./@vue-73x4sYJ2.js";import{K as L,_ as x,X as U}from"./index-Lx4Mi1rj.js";import{k as X,r as Z}from"./@vicons-UfsZxvNZ.js";import{j as N,o as Q,e as Y,P as ee,O as te,G as ne,a as oe,J as se,k as ae,H as ce}from"./naive-ui-qF3urcFV.js";import{_ as ie}from"./post-skeleton-IEvC_QvH.js";import{_ as re}from"./main-nav.vue_vue_type_style_index_0_lang--76-h8Yy.js";import{W as le}from"./v3-infinite-loading-yUDJG3gQ.js";import{b as _e}from"./vue-router-22lN-LLO.js";import"./vuex-6eozxOS7.js";import"./axios-QLjAsgXu.js";import"./moment-TH1CLKMj.js";/* empty css */import"./seemly-tZbmuCcS.js";import"./vueuc-oXvKre1p.js";import"./evtd-9ZCiDXyn.js";import"./@css-render-RY9kiobo.js";import"./vooks-574GUng3.js";import"./vdirs-gz97tqc5.js";import"./@juggle--NVrOerG.js";import"./css-render-Jaty3dru.js";import"./@emotion-vV6BesBt.js";import"./lodash-es-KEIJqYRD.js";import"./treemate-hmrDCADh.js";import"./async-validator-BHjhHa7C.js";import"./date-fns-E8ESfRGG.js";const ue={class:"contact-item"},pe={class:"nickname-wrap"},me={class:"username-wrap"},de={class:"user-info"},fe={class:"info-item"},ve={class:"info-item"},he={class:"item-header-extra"},ge=P({__name:"contact-item",props:{contact:{}},emits:["send-whisper"],setup(z,{emit:w}){const _=w,l=e=>()=>S(N,null,{default:()=>S(e)}),n=z,c=A(()=>[{label:"私信 @"+n.contact.username,key:"whisper",icon:l(Z)}]),m=e=>{switch(e){case"whisper":const i={id:n.contact.user_id,avatar:n.contact.avatar,username:n.contact.username,nickname:n.contact.nickname,is_admin:!1,is_friend:!0,is_following:!1,created_on:0,follows:0,followings:0,status:1};_("send-whisper",i);break}};return(e,i)=>{const f=Q,k=R("router-link"),y=Y,$=ee,v=te;return s(),p("div",ue,[t(v,{"content-indented":""},{avatar:o(()=>[t(f,{size:54,src:e.contact.avatar},null,8,["src"])]),header:o(()=>[a("span",pe,[t(k,{onClick:i[0]||(i[0]=E(()=>{},["stop"])),class:"username-link",to:{name:"user",query:{s:e.contact.username}}},{default:o(()=>[G(d(e.contact.nickname),1)]),_:1},8,["to"])]),a("span",me," @"+d(e.contact.username),1),a("div",de,[a("span",fe," UID. "+d(e.contact.user_id),1),a("span",ve,d(g(L)(e.contact.created_on))+" 加入 ",1)])]),"header-extra":o(()=>[a("div",he,[t($,{placement:"bottom-end",trigger:"click",size:"small",options:c.value,onSelect:m},{default:o(()=>[t(y,{quaternary:"",circle:""},{icon:o(()=>[t(g(N),null,{default:o(()=>[t(g(X))]),_:1})]),_:1})]),_:1},8,["options"])])]),_:1})])}}}),we=x(ge,[["__scopeId","data-v-42e975ce"]]),ke={key:0,class:"skeleton-wrap"},ye={key:1},$e={key:0,class:"empty-wrap"},Ce={class:"load-more-wrap"},be={class:"load-more-spinner"},ze=P({__name:"Contacts",setup(z){const w=_e(),_=r(!1),l=r(!1),n=r([]),c=r(+w.query.p||1),m=r(20),e=r(0),i=r(!1),f=r({id:0,avatar:"",username:"",nickname:"",is_admin:!1,is_friend:!0,is_following:!1,created_on:0,follows:0,followings:0,status:1}),k=h=>{f.value=h,i.value=!0},y=()=>{i.value=!1},$=()=>{c.value<e.value||e.value==0?(l.value=!1,c.value++,v()):l.value=!0};J(()=>{v()});const v=(h=!1)=>{n.value.length===0&&(_.value=!0),U({page:c.value,page_size:m.value}).then(u=>{_.value=!1,u.list.length===0&&(l.value=!0),c.value>1?n.value=n.value.concat(u.list):(n.value=u.list,h&&setTimeout(()=>{window.scrollTo(0,99999)},50)),e.value=Math.ceil(u.pager.total_rows/m.value)}).catch(u=>{_.value=!1,c.value>1&&c.value--})};return(h,u)=>{const B=re,V=ie,j=se,q=we,D=ce,F=W,H=ne,O=ae,T=oe;return s(),p(M,null,[a("div",null,[t(B,{title:"好友"}),t(H,{class:"main-content-wrap",bordered:""},{default:o(()=>[_.value&&n.value.length===0?(s(),p("div",ke,[t(V,{num:m.value},null,8,["num"])])):(s(),p("div",ye,[n.value.length===0?(s(),p("div",$e,[t(j,{size:"large",description:"暂无数据"})])):b("",!0),(s(!0),p(M,null,K(n.value,I=>(s(),C(D,{class:"list-item",key:I.user_id},{default:o(()=>[t(q,{contact:I,onSendWhisper:k},null,8,["contact"])]),_:2},1024))),128))])),t(F,{show:i.value,user:f.value,onSuccess:y},null,8,["show","user"])]),_:1})]),e.value>0?(s(),C(T,{key:0,justify:"center"},{default:o(()=>[t(g(le),{class:"load-more",slots:{complete:"没有更多好友了",error:"加载出错"},onInfinite:$},{spinner:o(()=>[a("div",Ce,[l.value?b("",!0):(s(),C(O,{key:0,size:14})),a("span",be,d(l.value?"没有更多好友了":"加载更多"),1)])]),_:1})]),_:1})):b("",!0)],64)}}}),Qe=x(ze,[["__scopeId","data-v-69277f0c"]]);export{Qe as default};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -1 +0,0 @@
.compose-wrap{width:100%;padding:16px;box-sizing:border-box}.compose-wrap .compose-line{display:flex;flex-direction:row}.compose-wrap .compose-line .compose-user{width:42px;height:42px;display:flex;align-items:center}.compose-wrap .compose-line.compose-options{margin-top:6px;padding-left:42px;display:flex;justify-content:space-between}.compose-wrap .compose-line.compose-options .submit-wrap{display:flex;align-items:center}.compose-wrap .compose-line.compose-options .submit-wrap .text-statistic{margin-right:8px;width:20px;height:20px;transform:rotate(180deg)}.compose-wrap .link-wrap{margin-left:42px;margin-right:42px}.compose-wrap .eye-wrap{margin-left:64px}.compose-wrap .login-only-wrap{display:flex;justify-content:center;width:100%}.compose-wrap .login-only-wrap button{margin:0 4px;width:50%}.compose-wrap .login-wrap{display:flex;justify-content:center;width:100%}.compose-wrap .login-wrap .login-banner{margin-bottom:12px;opacity:.8}.compose-wrap .login-wrap button{margin:0 4px}.attachment-list-wrap{margin-top:12px;margin-left:42px}.attachment-list-wrap .n-upload-file-info__thumbnail{overflow:hidden}.dark .compose-wrap{background-color:#101014bf}.tiny-slide-bar .tiny-slide-bar__list>div.tiny-slide-bar__select .slide-bar-item .slide-bar-item-title[data-v-325ca19f]{color:#18a058;opacity:.8}.tiny-slide-bar .tiny-slide-bar__list>div:hover .slide-bar-item[data-v-325ca19f]{cursor:pointer}.tiny-slide-bar .tiny-slide-bar__list>div:hover .slide-bar-item .slide-bar-item-avatar[data-v-325ca19f]{color:#18a058;opacity:.8}.tiny-slide-bar .tiny-slide-bar__list>div:hover .slide-bar-item .slide-bar-item-title[data-v-325ca19f]{color:#18a058;opacity:.8}.tiny-slide-bar[data-v-325ca19f]{margin-top:-30px;margin-bottom:-30px}.tiny-slide-bar .slide-bar-item[data-v-325ca19f]{min-height:170px;width:64px;display:flex;flex-direction:column;justify-content:center;align-items:center;margin-top:8px}.tiny-slide-bar .slide-bar-item .slide-bar-item-title[data-v-325ca19f]{justify-content:center;font-size:12px;margin-top:4px;height:40px}.load-more[data-v-325ca19f]{margin:20px}.load-more .load-more-wrap[data-v-325ca19f]{display:flex;flex-direction:row;justify-content:center;align-items:center;gap:14px}.load-more .load-more-wrap .load-more-spinner[data-v-325ca19f]{font-size:14px;opacity:.65}.dark .main-content-wrap[data-v-325ca19f],.dark .pagination-wrap[data-v-325ca19f],.dark .empty-wrap[data-v-325ca19f],.dark .skeleton-wrap[data-v-325ca19f]{background-color:#101014bf}.dark .tiny-slide-bar .tiny-slide-bar__list>div.tiny-slide-bar__select .slide-bar-item .slide-bar-item-title[data-v-325ca19f]{color:#63e2b7;opacity:.8}.dark .tiny-slide-bar .tiny-slide-bar__list>div:hover .slide-bar-item .slide-bar-item-title[data-v-325ca19f]{color:#63e2b7;opacity:.8}.dark .tiny-slide-bar[data-v-325ca19f]{--ti-slider-progress-box-arrow-hover-text-color: #f2f2f2;--ti-slider-progress-box-arrow-normal-text-color: #808080}

@ -0,0 +1 @@
.compose-wrap{width:100%;padding:16px;box-sizing:border-box}.compose-wrap .compose-line{display:flex;flex-direction:row}.compose-wrap .compose-line .compose-user{width:42px;height:42px;display:flex;align-items:center}.compose-wrap .compose-line.compose-options{margin-top:6px;padding-left:42px;display:flex;justify-content:space-between}.compose-wrap .compose-line.compose-options .submit-wrap{display:flex;align-items:center}.compose-wrap .compose-line.compose-options .submit-wrap .text-statistic{margin-right:8px;width:20px;height:20px;transform:rotate(180deg)}.compose-wrap .link-wrap{margin-left:42px;margin-right:42px}.compose-wrap .eye-wrap{margin-left:64px}.compose-wrap .login-only-wrap{display:flex;justify-content:center;width:100%}.compose-wrap .login-only-wrap button{margin:0 4px;width:50%}.compose-wrap .login-wrap{display:flex;justify-content:center;width:100%}.compose-wrap .login-wrap .login-banner{margin-bottom:12px;opacity:.8}.compose-wrap .login-wrap button{margin:0 4px}.attachment-list-wrap{margin-top:12px;margin-left:42px}.attachment-list-wrap .n-upload-file-info__thumbnail{overflow:hidden}.dark .compose-wrap{background-color:#101014bf}.tiny-slide-bar .tiny-slide-bar__list>div.tiny-slide-bar__select .slide-bar-item .slide-bar-item-title[data-v-cc7d12d2]{color:#18a058;opacity:.8}.tiny-slide-bar .tiny-slide-bar__list>div:hover .slide-bar-item[data-v-cc7d12d2]{cursor:pointer}.tiny-slide-bar .tiny-slide-bar__list>div:hover .slide-bar-item .slide-bar-item-avatar[data-v-cc7d12d2]{color:#18a058;opacity:.8}.tiny-slide-bar .tiny-slide-bar__list>div:hover .slide-bar-item .slide-bar-item-title[data-v-cc7d12d2]{color:#18a058;opacity:.8}.style-wrap[data-v-cc7d12d2]{margin-top:10px;margin-left:16px;margin-bottom:4px;opacity:.8}.style-wrap .style-item.hover[data-v-cc7d12d2]{cursor:pointer}.tiny-slide-bar[data-v-cc7d12d2]{margin-top:-30px;margin-bottom:-30px}.tiny-slide-bar .slide-bar-item[data-v-cc7d12d2]{min-height:170px;width:64px;display:flex;flex-direction:column;justify-content:center;align-items:center;margin-top:8px}.tiny-slide-bar .slide-bar-item .slide-bar-item-title[data-v-cc7d12d2]{justify-content:center;font-size:12px;margin-top:4px;height:40px}.load-more[data-v-cc7d12d2]{margin:20px}.load-more .load-more-wrap[data-v-cc7d12d2]{display:flex;flex-direction:row;justify-content:center;align-items:center;gap:14px}.load-more .load-more-wrap .load-more-spinner[data-v-cc7d12d2]{font-size:14px;opacity:.65}.dark .main-content-wrap[data-v-cc7d12d2],.dark .pagination-wrap[data-v-cc7d12d2],.dark .empty-wrap[data-v-cc7d12d2],.dark .skeleton-wrap[data-v-cc7d12d2]{background-color:#101014bf}.dark .tiny-slide-bar .tiny-slide-bar__list>div.tiny-slide-bar__select .slide-bar-item .slide-bar-item-title[data-v-cc7d12d2]{color:#63e2b7;opacity:.8}.dark .tiny-slide-bar .tiny-slide-bar__list>div:hover .slide-bar-item .slide-bar-item-title[data-v-cc7d12d2]{color:#63e2b7;opacity:.8}.dark .tiny-slide-bar[data-v-cc7d12d2]{--ti-slider-progress-box-arrow-hover-text-color: #f2f2f2;--ti-slider-progress-box-arrow-normal-text-color: #808080}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -1 +1 @@
.tag-item .tag-quote{margin-left:12px;font-size:14px;opacity:.75}.tag-item .tag-follow{margin-right:22px}.tag-item .options{margin-left:-32px;margin-bottom:4px;opacity:.55}.tag-item .n-thing .n-thing-header{margin-bottom:0}.tag-item .n-thing .n-thing-avatar-header-wrapper{align-items:center}.tags-wrap[data-v-1fb31ecf]{padding:20px}.dark .tags-wrap[data-v-1fb31ecf]{background-color:#101014bf}
.tag-item .tag-quote{margin-left:12px;font-size:14px;opacity:.75}.tag-item .tag-follow{margin-right:22px}.tag-item .options{margin-left:-32px;margin-bottom:4px;opacity:.55}.tag-item .n-thing .n-thing-header{margin-bottom:0}.tag-item .n-thing .n-thing-avatar-header-wrapper{align-items:center}.tags-wrap[data-v-f89944c3]{padding:20px}.dark .tags-wrap[data-v-f89944c3],.dark .empty-wrap[data-v-f89944c3]{background-color:#101014bf}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save