mirror of
https://github.com/snowykami/neo-blog.git
synced 2025-09-26 02:56:22 +00:00
Refactor comment components and update OIDC configuration
- Updated OIDC configuration to include additional fields in the UpdateOidcConfig method. - Enhanced CommentService to include IsPrivate field in the comment DTO. - Refactored comment components: renamed neo-comment to comment, and moved related files. - Implemented new CommentInput and CommentItem components for better structure and readability. - Removed obsolete files related to the old comment system. - Added CSS animations for comment components to improve user experience.
This commit is contained in:
@ -13,6 +13,7 @@ type CommentDto struct {
|
||||
ReplyCount int64 `json:"reply_count"` // 回复数量
|
||||
LikeCount uint64 `json:"like_count"` // 点赞数量
|
||||
IsLiked bool `json:"is_liked"` // 当前用户是否点赞
|
||||
IsPrivate bool `json:"is_private"`
|
||||
}
|
||||
|
||||
type CreateCommentReq struct {
|
||||
|
@ -1,116 +1,117 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/snowykami/neo-blog/internal/dto"
|
||||
"gorm.io/gorm"
|
||||
"resty.dev/v3"
|
||||
"time"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/snowykami/neo-blog/internal/dto"
|
||||
"gorm.io/gorm"
|
||||
"resty.dev/v3"
|
||||
)
|
||||
|
||||
type OidcConfig struct {
|
||||
gorm.Model
|
||||
Name string `gorm:"uniqueIndex"` // OIDC配置名称,唯一
|
||||
ClientID string // 客户端ID
|
||||
ClientSecret string // 客户端密钥
|
||||
DisplayName string // 显示名称,例如:轻雪通行证
|
||||
Icon string // 图标url,为空则使用内置默认图标
|
||||
OidcDiscoveryUrl string // OpenID自动发现URL,例如 :https://pass.liteyuki.icu/.well-known/openid-configuration
|
||||
Enabled bool `gorm:"default:true"` // 是否启用
|
||||
Type string `gorm:"oauth2"` // OIDC类型,默认为oauth2,也可以为misskey
|
||||
// 以下字段为自动获取字段,每次更新配置时自动填充
|
||||
Issuer string
|
||||
AuthorizationEndpoint string
|
||||
TokenEndpoint string
|
||||
UserInfoEndpoint string
|
||||
JwksUri string
|
||||
gorm.Model
|
||||
Name string `gorm:"uniqueIndex"` // OIDC配置名称,唯一
|
||||
ClientID string // 客户端ID
|
||||
ClientSecret string // 客户端密钥
|
||||
DisplayName string // 显示名称,例如:轻雪通行证
|
||||
Icon string // 图标url,为空则使用内置默认图标
|
||||
OidcDiscoveryUrl string // OpenID自动发现URL,例如 :https://pass.liteyuki.org/.well-known/openid-configuration
|
||||
Enabled bool `gorm:"default:true"` // 是否启用
|
||||
Type string `gorm:"oauth2"` // OIDC类型,默认为oauth2,也可以为misskey
|
||||
// 以下字段为自动获取字段,每次更新配置时自动填充
|
||||
Issuer string
|
||||
AuthorizationEndpoint string
|
||||
TokenEndpoint string
|
||||
UserInfoEndpoint string
|
||||
JwksUri string
|
||||
}
|
||||
|
||||
type oidcDiscoveryResp struct {
|
||||
Issuer string `json:"issuer" validate:"required"`
|
||||
AuthorizationEndpoint string `json:"authorization_endpoint" validate:"required"`
|
||||
TokenEndpoint string `json:"token_endpoint" validate:"required"`
|
||||
UserInfoEndpoint string `json:"userinfo_endpoint" validate:"required"`
|
||||
JwksUri string `json:"jwks_uri" validate:"required"`
|
||||
// 可选字段
|
||||
RegistrationEndpoint string `json:"registration_endpoint,omitempty"`
|
||||
ScopesSupported []string `json:"scopes_supported,omitempty"`
|
||||
ResponseTypesSupported []string `json:"response_types_supported,omitempty"`
|
||||
GrantTypesSupported []string `json:"grant_types_supported,omitempty"`
|
||||
SubjectTypesSupported []string `json:"subject_types_supported,omitempty"`
|
||||
IdTokenSigningAlgValuesSupported []string `json:"id_token_signing_alg_values_supported,omitempty"`
|
||||
ClaimsSupported []string `json:"claims_supported,omitempty"`
|
||||
EndSessionEndpoint string `json:"end_session_endpoint,omitempty"`
|
||||
Issuer string `json:"issuer" validate:"required"`
|
||||
AuthorizationEndpoint string `json:"authorization_endpoint" validate:"required"`
|
||||
TokenEndpoint string `json:"token_endpoint" validate:"required"`
|
||||
UserInfoEndpoint string `json:"userinfo_endpoint" validate:"required"`
|
||||
JwksUri string `json:"jwks_uri" validate:"required"`
|
||||
// 可选字段
|
||||
RegistrationEndpoint string `json:"registration_endpoint,omitempty"`
|
||||
ScopesSupported []string `json:"scopes_supported,omitempty"`
|
||||
ResponseTypesSupported []string `json:"response_types_supported,omitempty"`
|
||||
GrantTypesSupported []string `json:"grant_types_supported,omitempty"`
|
||||
SubjectTypesSupported []string `json:"subject_types_supported,omitempty"`
|
||||
IdTokenSigningAlgValuesSupported []string `json:"id_token_signing_alg_values_supported,omitempty"`
|
||||
ClaimsSupported []string `json:"claims_supported,omitempty"`
|
||||
EndSessionEndpoint string `json:"end_session_endpoint,omitempty"`
|
||||
}
|
||||
|
||||
func updateOidcConfigFromUrl(url string, typ string) (*oidcDiscoveryResp, error) {
|
||||
client := resty.New()
|
||||
client.SetTimeout(10 * time.Second) // 设置超时时间
|
||||
var discovery oidcDiscoveryResp
|
||||
resp, err := client.R().
|
||||
SetHeader("Accept", "application/json").
|
||||
SetResult(&discovery).
|
||||
Get(url)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("请求OIDC发现端点失败: %w", err)
|
||||
}
|
||||
if resp.StatusCode() != 200 {
|
||||
return nil, fmt.Errorf("请求OIDC发现端点失败,状态码: %d", resp.StatusCode())
|
||||
}
|
||||
// 验证必要字段
|
||||
if typ == "misskey" {
|
||||
discovery.UserInfoEndpoint = discovery.Issuer + "/api/users/me" // Misskey的用户信息端点
|
||||
discovery.JwksUri = discovery.Issuer + "/api/jwks"
|
||||
}
|
||||
fmt.Println(discovery)
|
||||
if discovery.Issuer == "" ||
|
||||
discovery.AuthorizationEndpoint == "" ||
|
||||
discovery.TokenEndpoint == "" ||
|
||||
discovery.UserInfoEndpoint == "" ||
|
||||
discovery.JwksUri == "" {
|
||||
return nil, fmt.Errorf("OIDC发现端点响应缺少必要字段")
|
||||
}
|
||||
return &discovery, nil
|
||||
client := resty.New()
|
||||
client.SetTimeout(10 * time.Second) // 设置超时时间
|
||||
var discovery oidcDiscoveryResp
|
||||
resp, err := client.R().
|
||||
SetHeader("Accept", "application/json").
|
||||
SetResult(&discovery).
|
||||
Get(url)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("请求OIDC发现端点失败: %w", err)
|
||||
}
|
||||
if resp.StatusCode() != 200 {
|
||||
return nil, fmt.Errorf("请求OIDC发现端点失败,状态码: %d", resp.StatusCode())
|
||||
}
|
||||
// 验证必要字段
|
||||
if typ == "misskey" {
|
||||
discovery.UserInfoEndpoint = discovery.Issuer + "/api/users/me" // Misskey的用户信息端点
|
||||
discovery.JwksUri = discovery.Issuer + "/api/jwks"
|
||||
}
|
||||
fmt.Println(discovery)
|
||||
if discovery.Issuer == "" ||
|
||||
discovery.AuthorizationEndpoint == "" ||
|
||||
discovery.TokenEndpoint == "" ||
|
||||
discovery.UserInfoEndpoint == "" ||
|
||||
discovery.JwksUri == "" {
|
||||
return nil, fmt.Errorf("OIDC发现端点响应缺少必要字段")
|
||||
}
|
||||
return &discovery, nil
|
||||
}
|
||||
|
||||
func (o *OidcConfig) BeforeSave(tx *gorm.DB) (err error) {
|
||||
// 只有在创建新记录或更新 OidcDiscoveryUrl 字段时才更新端点信息
|
||||
if tx.Statement.Changed("OidcDiscoveryUrl") || o.ID == 0 {
|
||||
logrus.Infof("Updating OIDC config for %s, OidcDiscoveryUrl: %s", o.Name, o.OidcDiscoveryUrl)
|
||||
discoveryResp, err := updateOidcConfigFromUrl(o.OidcDiscoveryUrl, o.Type)
|
||||
if err != nil {
|
||||
logrus.Error("Updating OIDC config failed: ", err)
|
||||
return fmt.Errorf("updating OIDC config failed: %w", err)
|
||||
}
|
||||
o.Issuer = discoveryResp.Issuer
|
||||
o.AuthorizationEndpoint = discoveryResp.AuthorizationEndpoint
|
||||
o.TokenEndpoint = discoveryResp.TokenEndpoint
|
||||
o.UserInfoEndpoint = discoveryResp.UserInfoEndpoint
|
||||
o.JwksUri = discoveryResp.JwksUri
|
||||
}
|
||||
return nil
|
||||
// 只有在创建新记录或更新 OidcDiscoveryUrl 字段时才更新端点信息
|
||||
if tx.Statement.Changed("OidcDiscoveryUrl") || o.ID == 0 {
|
||||
logrus.Infof("Updating OIDC config for %s, OidcDiscoveryUrl: %s", o.Name, o.OidcDiscoveryUrl)
|
||||
discoveryResp, err := updateOidcConfigFromUrl(o.OidcDiscoveryUrl, o.Type)
|
||||
if err != nil {
|
||||
logrus.Error("Updating OIDC config failed: ", err)
|
||||
return fmt.Errorf("updating OIDC config failed: %w", err)
|
||||
}
|
||||
o.Issuer = discoveryResp.Issuer
|
||||
o.AuthorizationEndpoint = discoveryResp.AuthorizationEndpoint
|
||||
o.TokenEndpoint = discoveryResp.TokenEndpoint
|
||||
o.UserInfoEndpoint = discoveryResp.UserInfoEndpoint
|
||||
o.JwksUri = discoveryResp.JwksUri
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ToUserDto 返回给用户侧
|
||||
func (o *OidcConfig) ToUserDto() *dto.UserOidcConfigDto {
|
||||
return &dto.UserOidcConfigDto{
|
||||
Name: o.Name,
|
||||
DisplayName: o.DisplayName,
|
||||
Icon: o.Icon,
|
||||
}
|
||||
return &dto.UserOidcConfigDto{
|
||||
Name: o.Name,
|
||||
DisplayName: o.DisplayName,
|
||||
Icon: o.Icon,
|
||||
}
|
||||
}
|
||||
|
||||
// ToAdminDto 返回给管理员侧
|
||||
func (o *OidcConfig) ToAdminDto() *dto.AdminOidcConfigDto {
|
||||
return &dto.AdminOidcConfigDto{
|
||||
ID: o.ID,
|
||||
Name: o.Name,
|
||||
ClientID: o.ClientID,
|
||||
ClientSecret: o.ClientSecret,
|
||||
DisplayName: o.DisplayName,
|
||||
Icon: o.Icon,
|
||||
OidcDiscoveryUrl: o.OidcDiscoveryUrl,
|
||||
Enabled: o.Enabled,
|
||||
}
|
||||
return &dto.AdminOidcConfigDto{
|
||||
ID: o.ID,
|
||||
Name: o.Name,
|
||||
ClientID: o.ClientID,
|
||||
ClientSecret: o.ClientSecret,
|
||||
DisplayName: o.DisplayName,
|
||||
Icon: o.Icon,
|
||||
OidcDiscoveryUrl: o.OidcDiscoveryUrl,
|
||||
Enabled: o.Enabled,
|
||||
}
|
||||
}
|
||||
|
@ -110,7 +110,7 @@ func (cr *CommentRepo) UpdateComment(comment *model.Comment) error {
|
||||
return errs.New(http.StatusBadRequest, "invalid comment ID", nil)
|
||||
}
|
||||
|
||||
if err := GetDB().Updates(comment).Error; err != nil {
|
||||
if err := GetDB().Select("IsPrivate", "Content").Updates(comment).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -204,6 +204,7 @@ func (cr *CommentRepo) ListComments(currentUserID, targetID, commentID uint, tar
|
||||
} else {
|
||||
query = query.Where("target_id = ? AND target_type = ?", targetID, targetType)
|
||||
}
|
||||
|
||||
items, _, err := PaginateQuery[model.Comment](query, page, size, orderBy, desc)
|
||||
|
||||
if err != nil {
|
||||
|
@ -3,6 +3,9 @@ package repo
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/glebarez/sqlite"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/snowykami/neo-blog/internal/model"
|
||||
@ -10,8 +13,6 @@ import (
|
||||
"gorm.io/driver/postgres"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/logger"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
var db *gorm.DB
|
||||
|
@ -1,9 +1,10 @@
|
||||
package repo
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/snowykami/neo-blog/internal/model"
|
||||
"github.com/snowykami/neo-blog/pkg/errs"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type oidcRepo struct {
|
||||
@ -62,7 +63,9 @@ func (o *oidcRepo) UpdateOidcConfig(oidcConfig *model.OidcConfig) error {
|
||||
if oidcConfig.ID == 0 {
|
||||
return errs.New(http.StatusBadRequest, "invalid OIDC config ID", nil)
|
||||
}
|
||||
if err := GetDB().Select("Enabled").Updates(oidcConfig).Error; err != nil {
|
||||
if err := GetDB().Select("Name", "ClientID", "ClientSecret",
|
||||
"DisplayName", "Icon", "OidcDiscoveryUrl",
|
||||
"Enabled", "Type").Updates(oidcConfig).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
|
@ -157,6 +157,7 @@ func (cs *CommentService) GetCommentList(ctx context.Context, req *dto.GetCommen
|
||||
ReplyCount: replyCount,
|
||||
LikeCount: comment.LikeCount,
|
||||
IsLiked: isLiked,
|
||||
IsPrivate: comment.IsPrivate,
|
||||
}
|
||||
commentDtos = append(commentDtos, commentDto)
|
||||
}
|
||||
|
@ -1,10 +1,10 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"fmt"
|
||||
|
||||
"github.com/snowykami/neo-blog/pkg/constant"
|
||||
"resty.dev/v3"
|
||||
"github.com/snowykami/neo-blog/pkg/constant"
|
||||
"resty.dev/v3"
|
||||
)
|
||||
|
||||
type captchaUtils struct{}
|
||||
@ -12,83 +12,83 @@ type captchaUtils struct{}
|
||||
var Captcha = captchaUtils{}
|
||||
|
||||
type CaptchaConfig struct {
|
||||
Type string
|
||||
SiteSecret string // Site secret key for the captcha service
|
||||
SecretKey string // Secret key for the captcha service
|
||||
Type string
|
||||
SiteSecret string // Site secret key for the captcha service
|
||||
SecretKey string // Secret key for the captcha service
|
||||
}
|
||||
|
||||
func (c *captchaUtils) GetCaptchaConfigFromEnv() *CaptchaConfig {
|
||||
return &CaptchaConfig{
|
||||
Type: Env.Get("CAPTCHA_TYPE", "disable"),
|
||||
SiteSecret: Env.Get("CAPTCHA_SITE_SECRET", ""),
|
||||
SecretKey: Env.Get("CAPTCHA_SECRET_KEY", ""),
|
||||
}
|
||||
return &CaptchaConfig{
|
||||
Type: Env.Get("CAPTCHA_TYPE", "disable"),
|
||||
SiteSecret: Env.Get("CAPTCHA_SITE_SECRET", ""),
|
||||
SecretKey: Env.Get("CAPTCHA_SECRET_KEY", ""),
|
||||
}
|
||||
}
|
||||
|
||||
// VerifyCaptcha 根据提供的配置和令牌验证验证码
|
||||
func (c *captchaUtils) VerifyCaptcha(captchaConfig *CaptchaConfig, captchaToken string) (bool, error) {
|
||||
restyClient := resty.New()
|
||||
switch captchaConfig.Type {
|
||||
case constant.CaptchaTypeDisable:
|
||||
return true, nil
|
||||
case constant.CaptchaTypeHCaptcha:
|
||||
result := make(map[string]any)
|
||||
resp, err := restyClient.R().
|
||||
SetFormData(map[string]string{
|
||||
"secret": captchaConfig.SecretKey,
|
||||
"response": captchaToken,
|
||||
}).SetResult(&result).Post("https://hcaptcha.com/siteverify")
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if resp.IsError() {
|
||||
return false, nil
|
||||
}
|
||||
fmt.Printf("%#v\n", result)
|
||||
if success, ok := result["success"].(bool); ok && success {
|
||||
return true, nil
|
||||
} else {
|
||||
return false, nil
|
||||
}
|
||||
case constant.CaptchaTypeTurnstile:
|
||||
result := make(map[string]any)
|
||||
resp, err := restyClient.R().
|
||||
SetFormData(map[string]string{
|
||||
"secret": captchaConfig.SecretKey,
|
||||
"response": captchaToken,
|
||||
}).SetResult(&result).Post("https://challenges.cloudflare.com/turnstile/v0/siteverify")
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if resp.IsError() {
|
||||
return false, nil
|
||||
}
|
||||
fmt.Printf("%#v\n", result)
|
||||
if success, ok := result["success"].(bool); ok && success {
|
||||
return true, nil
|
||||
} else {
|
||||
return false, nil
|
||||
}
|
||||
case constant.CaptchaTypeReCaptcha:
|
||||
result := make(map[string]any)
|
||||
resp, err := restyClient.R().
|
||||
SetFormData(map[string]string{
|
||||
"secret": captchaConfig.SecretKey,
|
||||
"response": captchaToken,
|
||||
}).SetResult(&result).Post("https://www.google.com/recaptcha/api/siteverify")
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if resp.IsError() {
|
||||
return false, nil
|
||||
}
|
||||
fmt.Printf("%#v\n", result)
|
||||
if success, ok := result["success"].(bool); ok && success {
|
||||
return true, nil
|
||||
} else {
|
||||
return false, nil
|
||||
}
|
||||
default:
|
||||
return false, fmt.Errorf("invalid captcha type: %s", captchaConfig.Type)
|
||||
}
|
||||
restyClient := resty.New()
|
||||
switch captchaConfig.Type {
|
||||
case constant.CaptchaTypeDisable:
|
||||
return true, nil
|
||||
case constant.CaptchaTypeHCaptcha:
|
||||
result := make(map[string]any)
|
||||
resp, err := restyClient.R().
|
||||
SetFormData(map[string]string{
|
||||
"secret": captchaConfig.SecretKey,
|
||||
"response": captchaToken,
|
||||
}).SetResult(&result).Post("https://hcaptcha.com/siteverify")
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if resp.IsError() {
|
||||
return false, nil
|
||||
}
|
||||
fmt.Printf("%#v\n", result)
|
||||
if success, ok := result["success"].(bool); ok && success {
|
||||
return true, nil
|
||||
} else {
|
||||
return false, nil
|
||||
}
|
||||
case constant.CaptchaTypeTurnstile:
|
||||
result := make(map[string]any)
|
||||
resp, err := restyClient.R().
|
||||
SetFormData(map[string]string{
|
||||
"secret": captchaConfig.SecretKey,
|
||||
"response": captchaToken,
|
||||
}).SetResult(&result).Post("https://challenges.cloudflare.com/turnstile/v0/siteverify")
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if resp.IsError() {
|
||||
return false, nil
|
||||
}
|
||||
fmt.Printf("%#v\n", result)
|
||||
if success, ok := result["success"].(bool); ok && success {
|
||||
return true, nil
|
||||
} else {
|
||||
return false, nil
|
||||
}
|
||||
case constant.CaptchaTypeReCaptcha:
|
||||
result := make(map[string]any)
|
||||
resp, err := restyClient.R().
|
||||
SetFormData(map[string]string{
|
||||
"secret": captchaConfig.SecretKey,
|
||||
"response": captchaToken,
|
||||
}).SetResult(&result).Post("https://www.google.com/recaptcha/api/siteverify")
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if resp.IsError() {
|
||||
return false, nil
|
||||
}
|
||||
fmt.Printf("%#v\n", result)
|
||||
if success, ok := result["success"].(bool); ok && success {
|
||||
return true, nil
|
||||
} else {
|
||||
return false, nil
|
||||
}
|
||||
default:
|
||||
return false, fmt.Errorf("invalid captcha type: %s", captchaConfig.Type)
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,7 @@ import { Calendar, Clock, FileText, Flame, Heart, MessageCircle, PenLine, Square
|
||||
import { RenderMarkdown } from "@/components/common/markdown";
|
||||
import { isMobileByUA } from "@/utils/server/device";
|
||||
import { calculateReadingTime } from "@/utils/common/post";
|
||||
import {CommentSection} from "@/components/neo-comment";
|
||||
import {CommentSection} from "@/components/comment";
|
||||
import { TargetType } from '../../models/types';
|
||||
|
||||
function PostMeta({ post }: { post: Post }) {
|
||||
|
@ -10,6 +10,7 @@ import { Textarea } from "@/components/ui/textarea";
|
||||
import { Checkbox } from "@/components/ui/checkbox"
|
||||
import { Label } from "@/components/ui/label";
|
||||
|
||||
|
||||
export function CommentInput(
|
||||
{
|
||||
user,
|
||||
@ -54,7 +55,7 @@ export function CommentInput(
|
||||
</div>
|
||||
<div className="flex-1 pl-2 fade-in-up">
|
||||
<Textarea
|
||||
placeholder={t("placeholder")}
|
||||
placeholder={user?t("placeholder"):t("login_required")}
|
||||
className="w-full p-2 border border-gray-300 rounded-md fade-in-up"
|
||||
value={commentContent}
|
||||
onChange={(e) => setCommentContent(e.target.value)}
|
@ -14,6 +14,7 @@ import { createComment, deleteComment, listComments, updateComment } from "@/api
|
||||
import { OrderBy } from "@/models/common";
|
||||
import config from "@/config";
|
||||
|
||||
|
||||
export function CommentItem(
|
||||
{
|
||||
user,
|
||||
@ -33,6 +34,7 @@ export function CommentItem(
|
||||
|
||||
const [likeCount, setLikeCount] = useState(comment.likeCount);
|
||||
const [liked, setLiked] = useState(comment.isLiked);
|
||||
const [isPrivate, setIsPrivate] = useState(comment.isPrivate);
|
||||
const [replyCount, setReplyCount] = useState(comment.replyCount);
|
||||
const [showReplies, setShowReplies] = useState(false);
|
||||
const [showReplyInput, setShowReplyInput] = useState(false);
|
||||
@ -81,7 +83,7 @@ export function CommentItem(
|
||||
updateComment({ id: comment.id, content: newContent, isPrivate }).then(() => {
|
||||
toast.success(t("edit_success"));
|
||||
comment.content = newContent;
|
||||
comment.isPrivate = isPrivate;
|
||||
setIsPrivate(isPrivate);
|
||||
setShowEditInput(false);
|
||||
}).catch(error => {
|
||||
toast.error(t("edit_failed") + ": " + error.message);
|
||||
@ -127,7 +129,7 @@ export function CommentItem(
|
||||
<div className="font-bold text-base text-slate-800 dark:text-slate-100 fade-in-up">{comment.user.nickname}</div>
|
||||
<p className="text-lg text-slate-600 dark:text-slate-400 fade-in">
|
||||
{
|
||||
comment.isPrivate && <Lock className="inline w-4 h-4 mr-1 mb-1 text-slate-500 dark:text-slate-400" />
|
||||
isPrivate && <Lock className="inline w-4 h-4 mr-1 mb-1 text-slate-500 dark:text-slate-400" />
|
||||
}
|
||||
{
|
||||
parentComment &&
|
||||
@ -201,7 +203,7 @@ export function CommentItem(
|
||||
{showEditInput && !showReplyInput && <CommentInput
|
||||
user={user}
|
||||
initContent={comment.content}
|
||||
initIsPrivate={comment.isPrivate}
|
||||
initIsPrivate={isPrivate}
|
||||
onCommentSubmitted={onCommentEdit}
|
||||
isUpdate={true}
|
||||
/>}
|
@ -11,12 +11,12 @@ import { OrderBy } from "@/models/common";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
import { getLoginUser } from "@/api/user";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
|
||||
import "./style.css";
|
||||
import { CommentInput } from "./comment-input";
|
||||
import { CommentItem } from "./comment-item";
|
||||
import config from "@/config";
|
||||
|
||||
import "./style.css";
|
||||
|
||||
|
||||
|
||||
export function CommentSection(
|
Reference in New Issue
Block a user