mirror of https://github.com/halo-dev/halo
Add support for hidden comments (#7679)
* Add 'hidden' field to comment and reply requests Signed-off-by: Ryan Wang <i@ryanc.cc> * Add support for filtering comments with hidden * Specify hidden=false and approved=true for anonymous users * Set default hidden flag only if null in comments * Add 'private reply' option to comment modals * Add private tag for hidden comments and replies * Allow hiding comments only * Enhance comment visibility logic to allow owners to view hidden comments * Remove hidden input for reply form Signed-off-by: Ryan Wang <i@ryanc.cc> * Refine i18n Signed-off-by: Ryan Wang <i@ryanc.cc> --------- Signed-off-by: Ryan Wang <i@ryanc.cc> Co-authored-by: John Niang <johnniang@foxmail.com>pull/7711/head
parent
3f5b69d5d0
commit
3487132154
|
@ -3597,7 +3597,7 @@
|
|||
},
|
||||
"/apis/api.console.halo.run/v1alpha1/posts/{name}/unpublish": {
|
||||
"put": {
|
||||
"description": "Publish a post.",
|
||||
"description": "UnPublish a post.",
|
||||
"operationId": "UnpublishPost",
|
||||
"parameters": [
|
||||
{
|
||||
|
@ -16462,6 +16462,10 @@
|
|||
"minLength": 1,
|
||||
"type": "string"
|
||||
},
|
||||
"hidden": {
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
},
|
||||
"owner": {
|
||||
"$ref": "#/components/schemas/CommentEmailOwner"
|
||||
},
|
||||
|
@ -21328,6 +21332,10 @@
|
|||
"minLength": 1,
|
||||
"type": "string"
|
||||
},
|
||||
"hidden": {
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
},
|
||||
"owner": {
|
||||
"$ref": "#/components/schemas/CommentEmailOwner"
|
||||
},
|
||||
|
|
|
@ -1464,7 +1464,7 @@
|
|||
},
|
||||
"/apis/api.console.halo.run/v1alpha1/posts/{name}/unpublish": {
|
||||
"put": {
|
||||
"description": "Publish a post.",
|
||||
"description": "UnPublish a post.",
|
||||
"operationId": "UnpublishPost",
|
||||
"parameters": [
|
||||
{
|
||||
|
@ -3868,6 +3868,10 @@
|
|||
"minLength": 1,
|
||||
"type": "string"
|
||||
},
|
||||
"hidden": {
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
},
|
||||
"owner": {
|
||||
"$ref": "#/components/schemas/CommentEmailOwner"
|
||||
},
|
||||
|
@ -5643,6 +5647,10 @@
|
|||
"minLength": 1,
|
||||
"type": "string"
|
||||
},
|
||||
"hidden": {
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
},
|
||||
"owner": {
|
||||
"$ref": "#/components/schemas/CommentEmailOwner"
|
||||
},
|
||||
|
|
|
@ -1455,6 +1455,10 @@
|
|||
"minLength": 1,
|
||||
"type": "string"
|
||||
},
|
||||
"hidden": {
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
},
|
||||
"owner": {
|
||||
"$ref": "#/components/schemas/CommentEmailOwner"
|
||||
},
|
||||
|
@ -2826,6 +2830,10 @@
|
|||
"minLength": 1,
|
||||
"type": "string"
|
||||
},
|
||||
"hidden": {
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
},
|
||||
"owner": {
|
||||
"$ref": "#/components/schemas/CommentEmailOwner"
|
||||
},
|
||||
|
|
|
@ -22,6 +22,14 @@ public interface UserService {
|
|||
|
||||
Mono<User> grantRoles(String username, Set<String> roles);
|
||||
|
||||
/**
|
||||
* Check if the user has sufficient roles.
|
||||
*
|
||||
* @param roles roles to check
|
||||
* @return a Mono that emits true if the user has all the roles, false otherwise
|
||||
*/
|
||||
Mono<Boolean> hasSufficientRoles(Collection<String> roles);
|
||||
|
||||
Mono<User> signUp(SignUpData signUpData);
|
||||
|
||||
Mono<User> createUser(User user, Set<String> roles);
|
||||
|
|
|
@ -32,6 +32,9 @@ public class CommentRequest {
|
|||
@Schema(defaultValue = "false")
|
||||
private Boolean allowNotification;
|
||||
|
||||
@Schema(defaultValue = "false")
|
||||
private Boolean hidden;
|
||||
|
||||
/**
|
||||
* Converts {@link CommentRequest} to {@link Comment}.
|
||||
*
|
||||
|
@ -48,6 +51,7 @@ public class CommentRequest {
|
|||
spec.setRaw(raw);
|
||||
spec.setContent(content);
|
||||
spec.setAllowNotification(allowNotification);
|
||||
spec.setHidden(hidden);
|
||||
|
||||
if (owner != null) {
|
||||
spec.setOwner(owner.toCommentOwner());
|
||||
|
|
|
@ -107,7 +107,10 @@ public class CommentServiceImpl extends AbstractCommentService implements Commen
|
|||
comment.getSpec().setCreationTime(Instant.now());
|
||||
}
|
||||
|
||||
comment.getSpec().setHidden(false);
|
||||
if (comment.getSpec().getHidden() == null) {
|
||||
comment.getSpec().setHidden(false);
|
||||
}
|
||||
|
||||
return Mono.just(comment);
|
||||
})
|
||||
.flatMap(populatedComment -> Mono.when(populateOwner(populatedComment),
|
||||
|
|
|
@ -26,6 +26,9 @@ public class ReplyRequest {
|
|||
@Schema(defaultValue = "false")
|
||||
private Boolean allowNotification;
|
||||
|
||||
@Schema(defaultValue = "false")
|
||||
private Boolean hidden;
|
||||
|
||||
private CommentEmailOwner owner;
|
||||
|
||||
private String quoteReply;
|
||||
|
@ -45,6 +48,7 @@ public class ReplyRequest {
|
|||
spec.setRaw(raw);
|
||||
spec.setContent(content);
|
||||
spec.setAllowNotification(allowNotification);
|
||||
spec.setHidden(hidden);
|
||||
spec.setQuoteReply(quoteReply);
|
||||
|
||||
if (owner != null) {
|
||||
|
|
|
@ -64,7 +64,7 @@ public class ReplyServiceImpl extends AbstractCommentService implements ReplySer
|
|||
.flatMap(this::approveComment)
|
||||
.filter(comment -> isTrue(comment.getSpec().getApproved()))
|
||||
.switchIfEmpty(Mono.error(requestRestrictedExceptionSupplier))
|
||||
.flatMap(comment -> prepareReply(commentName, reply))
|
||||
.flatMap(comment -> prepareReply(comment, reply))
|
||||
.flatMap(this::doCreateReply);
|
||||
}
|
||||
|
||||
|
@ -76,6 +76,9 @@ public class ReplyServiceImpl extends AbstractCommentService implements ReplySer
|
|||
return approveReply(quotedReply)
|
||||
.filter(reply -> isTrue(reply.getSpec().getApproved()))
|
||||
.switchIfEmpty(Mono.error(requestRestrictedExceptionSupplier))
|
||||
.doOnNext(approvedQuoteReply -> prepared.getSpec()
|
||||
.setHidden(approvedQuoteReply.getSpec().getHidden())
|
||||
)
|
||||
.flatMap(approvedQuoteReply -> client.create(prepared));
|
||||
}
|
||||
|
||||
|
@ -131,8 +134,9 @@ public class ReplyServiceImpl extends AbstractCommentService implements ReplySer
|
|||
.filter(OptimisticLockingFailureException.class::isInstance));
|
||||
}
|
||||
|
||||
private Mono<Reply> prepareReply(String commentName, Reply reply) {
|
||||
reply.getSpec().setCommentName(commentName);
|
||||
private Mono<Reply> prepareReply(Comment comment, Reply reply) {
|
||||
reply.getSpec().setCommentName(comment.getMetadata().getName());
|
||||
reply.getSpec().setHidden(comment.getSpec().getHidden());
|
||||
if (reply.getSpec().getTop() == null) {
|
||||
reply.getSpec().setTop(false);
|
||||
}
|
||||
|
|
|
@ -176,8 +176,6 @@ public class CommentFinderEndpoint implements CustomEndpoint {
|
|||
Reply reply = replyRequest.toReply();
|
||||
reply.getSpec().setIpAddress(IpAddressUtils.getIpAddress(request));
|
||||
reply.getSpec().setUserAgent(HaloUtils.userAgentFrom(request));
|
||||
// fix gh-2951
|
||||
reply.getSpec().setHidden(false);
|
||||
return environmentFetcher.fetchComment()
|
||||
.map(commentSetting -> {
|
||||
if (isFalse(commentSetting.getEnable())) {
|
||||
|
@ -191,6 +189,11 @@ public class CommentFinderEndpoint implements CustomEndpoint {
|
|||
}
|
||||
reply.getSpec()
|
||||
.setApproved(isFalse(commentSetting.getRequireReviewForNew()));
|
||||
|
||||
if (reply.getSpec().getHidden() == null) {
|
||||
reply.getSpec().setHidden(false);
|
||||
}
|
||||
|
||||
return reply;
|
||||
})
|
||||
.defaultIfEmpty(reply);
|
||||
|
|
|
@ -14,6 +14,8 @@ import java.util.Set;
|
|||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.dao.OptimisticLockingFailureException;
|
||||
import org.springframework.security.core.context.ReactiveSecurityContextHolder;
|
||||
import org.springframework.security.core.context.SecurityContext;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.ReactiveTransactionManager;
|
||||
|
@ -50,6 +52,7 @@ import run.halo.app.infra.exception.EmailVerificationFailed;
|
|||
import run.halo.app.infra.exception.UnsatisfiedAttributeValueException;
|
||||
import run.halo.app.infra.exception.UserNotFoundException;
|
||||
import run.halo.app.plugin.extensionpoint.ExtensionGetter;
|
||||
import run.halo.app.security.authorization.AuthorityUtils;
|
||||
import run.halo.app.security.device.DeviceService;
|
||||
|
||||
@Service
|
||||
|
@ -183,6 +186,15 @@ public class UserServiceImpl implements UserService {
|
|||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Boolean> hasSufficientRoles(Collection<String> roles) {
|
||||
return ReactiveSecurityContextHolder.getContext()
|
||||
.map(SecurityContext::getAuthentication)
|
||||
.map(a -> AuthorityUtils.authoritiesToRoles(a.getAuthorities()))
|
||||
.flatMap(userRoles -> roleService.contains(userRoles, roles))
|
||||
.defaultIfEmpty(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<User> signUp(SignUpData signUpData) {
|
||||
return environmentFetcher.fetch(SystemSetting.User.GROUP, SystemSetting.User.class)
|
||||
|
|
|
@ -3,25 +3,28 @@ package run.halo.app.theme.finders.impl;
|
|||
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
import static org.apache.commons.lang3.ObjectUtils.defaultIfNull;
|
||||
import static run.halo.app.core.extension.content.Comment.CommentOwner.ownerIdentity;
|
||||
import static run.halo.app.extension.index.query.QueryFactory.and;
|
||||
import static run.halo.app.extension.index.query.QueryFactory.equal;
|
||||
import static run.halo.app.extension.index.query.QueryFactory.isNull;
|
||||
import static run.halo.app.extension.index.query.QueryFactory.or;
|
||||
|
||||
import com.google.common.hash.Hashing;
|
||||
import java.security.Principal;
|
||||
import java.util.HashMap;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.commons.lang3.BooleanUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.context.ReactiveSecurityContextHolder;
|
||||
import org.springframework.security.core.context.SecurityContext;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.server.ServerWebInputException;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import run.halo.app.content.comment.OwnerInfo;
|
||||
|
@ -32,14 +35,13 @@ import run.halo.app.core.extension.content.Comment;
|
|||
import run.halo.app.core.extension.content.Reply;
|
||||
import run.halo.app.core.user.service.UserService;
|
||||
import run.halo.app.extension.AbstractExtension;
|
||||
import run.halo.app.extension.ExtensionUtil;
|
||||
import run.halo.app.extension.ListOptions;
|
||||
import run.halo.app.extension.ListResult;
|
||||
import run.halo.app.extension.PageRequest;
|
||||
import run.halo.app.extension.PageRequestImpl;
|
||||
import run.halo.app.extension.ReactiveExtensionClient;
|
||||
import run.halo.app.extension.Ref;
|
||||
import run.halo.app.extension.index.query.Query;
|
||||
import run.halo.app.extension.router.selector.FieldSelector;
|
||||
import run.halo.app.infra.AnonymousUserConst;
|
||||
import run.halo.app.theme.finders.CommentPublicQueryService;
|
||||
import run.halo.app.theme.finders.vo.CommentStatsVo;
|
||||
|
@ -59,6 +61,8 @@ import run.halo.app.theme.finders.vo.ReplyVo;
|
|||
public class CommentPublicQueryServiceImpl implements CommentPublicQueryService {
|
||||
private static final int DEFAULT_SIZE = 10;
|
||||
|
||||
private static final String COMMENT_VIEW_PERMISSION = "role-template-view-comments";
|
||||
|
||||
private final ReactiveExtensionClient client;
|
||||
private final UserService userService;
|
||||
private final CounterService counterService;
|
||||
|
@ -80,22 +84,18 @@ public class CommentPublicQueryServiceImpl implements CommentPublicQueryService
|
|||
var pageRequest = Optional.ofNullable(pageParam)
|
||||
.map(page -> page.withSort(page.getSort().and(defaultCommentSort())))
|
||||
.orElse(PageRequestImpl.ofSize(0));
|
||||
return fixedCommentFieldSelector(ref)
|
||||
.flatMap(fieldSelector -> {
|
||||
var listOptions = new ListOptions();
|
||||
listOptions.setFieldSelector(fieldSelector);
|
||||
return client.listBy(Comment.class, listOptions, pageRequest)
|
||||
.flatMap(listResult -> Flux.fromStream(listResult.get())
|
||||
.map(this::toCommentVo)
|
||||
.flatMapSequential(Function.identity())
|
||||
.collectList()
|
||||
.map(commentVos -> new ListResult<>(listResult.getPage(),
|
||||
listResult.getSize(),
|
||||
listResult.getTotal(),
|
||||
commentVos)
|
||||
)
|
||||
);
|
||||
})
|
||||
return populateCommentListOptions(ref)
|
||||
.flatMap(listOptions -> client.listBy(Comment.class, listOptions, pageRequest))
|
||||
.flatMap(listResult -> Flux.fromStream(listResult.get())
|
||||
.map(this::toCommentVo)
|
||||
.flatMapSequential(Function.identity())
|
||||
.collectList()
|
||||
.map(commentVos -> new ListResult<>(listResult.getPage(),
|
||||
listResult.getSize(),
|
||||
listResult.getTotal(),
|
||||
commentVos)
|
||||
)
|
||||
)
|
||||
.defaultIfEmpty(ListResult.emptyResult());
|
||||
}
|
||||
|
||||
|
@ -127,10 +127,10 @@ public class CommentPublicQueryServiceImpl implements CommentPublicQueryService
|
|||
|
||||
@Override
|
||||
public Mono<ListResult<ReplyVo>> listReply(String commentName, PageRequest pageParam) {
|
||||
return fixedReplyFieldSelector(commentName)
|
||||
.flatMap(fieldSelector -> {
|
||||
var listOptions = new ListOptions();
|
||||
listOptions.setFieldSelector(fieldSelector);
|
||||
// check comment
|
||||
return client.get(Comment.class, commentName)
|
||||
.flatMap(this::populateReplyListOptions)
|
||||
.flatMap(listOptions -> {
|
||||
var pageRequest = Optional.ofNullable(pageParam)
|
||||
.map(page -> page.withSort(page.getSort().and(defaultReplySort())))
|
||||
.orElse(PageRequestImpl.ofSize(0));
|
||||
|
@ -250,53 +250,76 @@ public class CommentPublicQueryServiceImpl implements CommentPublicQueryService
|
|||
.map(OwnerInfo::from);
|
||||
}
|
||||
|
||||
private Mono<FieldSelector> fixedCommentFieldSelector(@Nullable Ref ref) {
|
||||
return Mono.fromSupplier(
|
||||
() -> {
|
||||
var baseQuery = isNull("metadata.deletionTimestamp");
|
||||
if (ref != null) {
|
||||
baseQuery =
|
||||
and(baseQuery,
|
||||
equal("spec.subjectRef", Comment.toSubjectRefKey(ref)));
|
||||
private Mono<ListOptions> populateCommentListOptions(@Nullable Ref ref) {
|
||||
return populateVisibleListOptions(null)
|
||||
.doOnNext(builder -> {
|
||||
if (ref != null) {
|
||||
builder.andQuery(
|
||||
equal("spec.subjectRef", Comment.toSubjectRefKey(ref)));
|
||||
}
|
||||
})
|
||||
.map(ListOptions.ListOptionsBuilder::build);
|
||||
}
|
||||
|
||||
private Mono<ListOptions.ListOptionsBuilder> populateVisibleListOptions(
|
||||
@Nullable Comment comment) {
|
||||
return ReactiveSecurityContextHolder.getContext()
|
||||
.map(SecurityContext::getAuthentication)
|
||||
.map(Authentication::getName)
|
||||
.defaultIfEmpty(AnonymousUserConst.PRINCIPAL)
|
||||
.zipWith(userService.hasSufficientRoles(Set.of(COMMENT_VIEW_PERMISSION))
|
||||
.defaultIfEmpty(false))
|
||||
.flatMap(tuple2 -> {
|
||||
var username = tuple2.getT1();
|
||||
var hasViewPermission = tuple2.getT2();
|
||||
var commentHidden = false;
|
||||
var isCommentOwner = false;
|
||||
if (comment != null) {
|
||||
commentHidden = Boolean.TRUE.equals(comment.getSpec().getHidden());
|
||||
var owner = comment.getSpec().getOwner();
|
||||
isCommentOwner = owner != null && Objects.equals(
|
||||
ownerIdentity(owner.getKind(), owner.getName()),
|
||||
ownerIdentity(User.KIND, username)
|
||||
);
|
||||
boolean hasPermission =
|
||||
(!commentHidden) || (hasViewPermission || isCommentOwner);
|
||||
if (ExtensionUtil.isDeleted(comment) || !hasPermission) {
|
||||
return Mono.error(new ServerWebInputException(
|
||||
"The comment was not found, hidden or deleted."
|
||||
));
|
||||
}
|
||||
return baseQuery;
|
||||
})
|
||||
.flatMap(this::concatVisibleQuery)
|
||||
.map(FieldSelector::of);
|
||||
}
|
||||
|
||||
var builder = ListOptions.builder();
|
||||
builder.andQuery(isNull("metadata.deletionTimestamp"));
|
||||
var visibleQuery = and(
|
||||
equal("spec.hidden", BooleanUtils.FALSE),
|
||||
equal("spec.approved", BooleanUtils.TRUE)
|
||||
);
|
||||
|
||||
var isAnonymous = AnonymousUserConst.isAnonymousUser(username);
|
||||
if (isAnonymous) {
|
||||
builder.andQuery(visibleQuery);
|
||||
} else if (!(hasViewPermission || (commentHidden && isCommentOwner))) {
|
||||
builder.andQuery(or(
|
||||
equal("spec.owner", ownerIdentity(User.KIND, username)),
|
||||
visibleQuery
|
||||
));
|
||||
}
|
||||
// View all replies if the user is not an anonymous user, has view permission
|
||||
// or is the comment owner.
|
||||
return Mono.just(builder);
|
||||
});
|
||||
}
|
||||
|
||||
private Mono<Query> concatVisibleQuery(Query query) {
|
||||
Assert.notNull(query, "The query must not be null");
|
||||
var approvedQuery = and(
|
||||
equal("spec.approved", BooleanUtils.TRUE),
|
||||
equal("spec.hidden", BooleanUtils.FALSE)
|
||||
);
|
||||
// we should list all comments that the user owns
|
||||
return getCurrentUserWithoutAnonymous()
|
||||
.map(username -> or(approvedQuery, equal("spec.owner",
|
||||
Comment.CommentOwner.ownerIdentity(User.KIND, username)))
|
||||
)
|
||||
.defaultIfEmpty(approvedQuery)
|
||||
.map(compositeQuery -> and(query, compositeQuery));
|
||||
}
|
||||
|
||||
private Mono<FieldSelector> fixedReplyFieldSelector(String commentName) {
|
||||
Assert.notNull(commentName, "The commentName must not be null");
|
||||
private Mono<ListOptions> populateReplyListOptions(Comment comment) {
|
||||
// The comment name must be equal to the comment name of the reply
|
||||
// is approved and not hidden
|
||||
return Mono.fromSupplier(() -> and(
|
||||
equal("spec.commentName", commentName),
|
||||
isNull("metadata.deletionTimestamp")
|
||||
))
|
||||
.flatMap(this::concatVisibleQuery)
|
||||
.map(FieldSelector::of);
|
||||
}
|
||||
|
||||
Mono<String> getCurrentUserWithoutAnonymous() {
|
||||
return ReactiveSecurityContextHolder.getContext()
|
||||
.mapNotNull(SecurityContext::getAuthentication)
|
||||
.map(Principal::getName)
|
||||
.filter(username -> !AnonymousUserConst.PRINCIPAL.equals(username));
|
||||
return populateVisibleListOptions(comment)
|
||||
.doOnNext(builder ->
|
||||
builder.andQuery(equal("spec.commentName", comment.getMetadata().getName()))
|
||||
)
|
||||
.map(ListOptions.ListOptionsBuilder::build);
|
||||
}
|
||||
|
||||
static Sort defaultCommentSort() {
|
||||
|
|
|
@ -35,6 +35,7 @@ import run.halo.app.extension.SchemeManager;
|
|||
import run.halo.app.extension.index.IndexerFactory;
|
||||
import run.halo.app.extension.store.ReactiveExtensionStoreClient;
|
||||
import run.halo.app.infra.AnonymousUserConst;
|
||||
import run.halo.app.infra.exception.DuplicateNameException;
|
||||
import run.halo.app.infra.utils.JsonUtils;
|
||||
|
||||
@DirtiesContext
|
||||
|
@ -288,11 +289,19 @@ class CommentPublicQueryServiceIntegrationTest {
|
|||
@Nested
|
||||
class ListReplyTest {
|
||||
private final List<Reply> storedReplies = mockRelies();
|
||||
|
||||
@Autowired
|
||||
private CommentPublicQueryServiceImpl commentPublicQueryService;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
// create comment
|
||||
var comment = createComment();
|
||||
client.create(comment)
|
||||
.onErrorResume(DuplicateNameException.class, e -> Mono.just(comment))
|
||||
.as(StepVerifier::create)
|
||||
.expectNextCount(1)
|
||||
.verifyComplete();
|
||||
Flux.fromIterable(storedReplies)
|
||||
.flatMap(reply -> client.create(reply))
|
||||
.as(StepVerifier::create)
|
||||
|
|
|
@ -14,6 +14,7 @@ import {
|
|||
VDescriptionItem,
|
||||
VModal,
|
||||
VSpace,
|
||||
VTag,
|
||||
} from "@halo-dev/components";
|
||||
import { useQueryClient } from "@tanstack/vue-query";
|
||||
import { useUserAgent } from "@uc/modules/profile/tabs/composables/use-user-agent";
|
||||
|
@ -177,6 +178,11 @@ const { data: contentProvider } = useContentProviderExtensionPoint();
|
|||
<VDescriptionItem
|
||||
:label="$t('core.comment.comment_detail_modal.fields.content')"
|
||||
>
|
||||
<div v-if="comment.comment.spec.hidden" class="mb-2">
|
||||
<VTag>
|
||||
{{ $t("core.comment.list.fields.private") }}
|
||||
</VTag>
|
||||
</div>
|
||||
<component
|
||||
:is="contentProvider?.component"
|
||||
:content="comment.comment.spec.content"
|
||||
|
|
|
@ -21,6 +21,7 @@ import {
|
|||
VLoading,
|
||||
VSpace,
|
||||
VStatusDot,
|
||||
VTag,
|
||||
} from "@halo-dev/components";
|
||||
import type { OperationItem } from "@halo-dev/console-shared";
|
||||
import { useQuery, useQueryClient } from "@tanstack/vue-query";
|
||||
|
@ -291,6 +292,9 @@ const { data: contentProvider } = useContentProviderExtensionPoint();
|
|||
:owner="comment?.owner"
|
||||
@click="detailModalVisible = true"
|
||||
/>
|
||||
<VTag v-if="comment.comment.spec.hidden">
|
||||
{{ $t("core.comment.list.fields.private") }}
|
||||
</VTag>
|
||||
<span class="whitespace-nowrap text-sm text-gray-900">
|
||||
{{ $t("core.comment.text.commented_on") }}
|
||||
</span>
|
||||
|
|
|
@ -15,6 +15,7 @@ import {
|
|||
VDescriptionItem,
|
||||
VModal,
|
||||
VSpace,
|
||||
VTag,
|
||||
} from "@halo-dev/components";
|
||||
import { useQueryClient } from "@tanstack/vue-query";
|
||||
import { useUserAgent } from "@uc/modules/profile/tabs/composables/use-user-agent";
|
||||
|
@ -185,17 +186,25 @@ const { data: contentProvider } = useContentProviderExtensionPoint();
|
|||
<VDescriptionItem
|
||||
:label="$t('core.comment.reply_detail_modal.fields.original_comment')"
|
||||
>
|
||||
<OwnerButton :owner="comment.owner" />
|
||||
<div class="mt-2">
|
||||
<component
|
||||
:is="contentProvider?.component"
|
||||
:content="comment.comment.spec.content"
|
||||
/>
|
||||
<div class="mb-2 flex items-center gap-2">
|
||||
<OwnerButton :owner="comment.owner" />
|
||||
<VTag v-if="comment.comment.spec.hidden">
|
||||
{{ $t("core.comment.list.fields.private") }}
|
||||
</VTag>
|
||||
</div>
|
||||
<component
|
||||
:is="contentProvider?.component"
|
||||
:content="comment.comment.spec.content"
|
||||
/>
|
||||
</VDescriptionItem>
|
||||
<VDescriptionItem
|
||||
:label="$t('core.comment.reply_detail_modal.fields.content')"
|
||||
>
|
||||
<div v-if="reply.reply.spec.hidden" class="mb-2">
|
||||
<VTag>
|
||||
{{ $t("core.comment.list.fields.private") }}
|
||||
</VTag>
|
||||
</div>
|
||||
<div>
|
||||
<span
|
||||
v-if="quoteReply"
|
||||
|
|
|
@ -15,6 +15,7 @@ import {
|
|||
VEntity,
|
||||
VEntityField,
|
||||
VStatusDot,
|
||||
VTag,
|
||||
} from "@halo-dev/components";
|
||||
import type { OperationItem } from "@halo-dev/console-shared";
|
||||
import { useQueryClient } from "@tanstack/vue-query";
|
||||
|
@ -226,6 +227,9 @@ const { data: contentProvider } = useContentProviderExtensionPoint();
|
|||
:owner="reply?.owner"
|
||||
@click="detailModalVisible = true"
|
||||
/>
|
||||
<VTag v-if="comment.comment.spec.hidden">
|
||||
{{ $t("core.comment.list.fields.private") }}
|
||||
</VTag>
|
||||
<span class="whitespace-nowrap text-sm text-gray-900">
|
||||
{{ $t("core.comment.text.replied_below") }}
|
||||
</span>
|
||||
|
|
|
@ -520,7 +520,7 @@ export const PostV1alpha1ConsoleApiAxiosParamCreator = function (configuration?:
|
|||
};
|
||||
},
|
||||
/**
|
||||
* Publish a post.
|
||||
* UnPublish a post.
|
||||
* @param {string} name
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
|
@ -797,7 +797,7 @@ export const PostV1alpha1ConsoleApiFp = function(configuration?: Configuration)
|
|||
return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath);
|
||||
},
|
||||
/**
|
||||
* Publish a post.
|
||||
* UnPublish a post.
|
||||
* @param {string} name
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
|
@ -935,7 +935,7 @@ export const PostV1alpha1ConsoleApiFactory = function (configuration?: Configura
|
|||
return localVarFp.revertToSpecifiedSnapshotForPost(requestParameters.name, requestParameters.revertSnapshotForPostParam, options).then((request) => request(axios, basePath));
|
||||
},
|
||||
/**
|
||||
* Publish a post.
|
||||
* UnPublish a post.
|
||||
* @param {PostV1alpha1ConsoleApiUnpublishPostRequest} requestParameters Request parameters.
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
|
@ -1362,7 +1362,7 @@ export class PostV1alpha1ConsoleApi extends BaseAPI {
|
|||
}
|
||||
|
||||
/**
|
||||
* Publish a post.
|
||||
* UnPublish a post.
|
||||
* @param {PostV1alpha1ConsoleApiUnpublishPostRequest} requestParameters Request parameters.
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
|
|
|
@ -38,6 +38,12 @@ export interface CommentRequest {
|
|||
* @memberof CommentRequest
|
||||
*/
|
||||
'content': string;
|
||||
/**
|
||||
*
|
||||
* @type {boolean}
|
||||
* @memberof CommentRequest
|
||||
*/
|
||||
'hidden'?: boolean;
|
||||
/**
|
||||
*
|
||||
* @type {CommentEmailOwner}
|
||||
|
|
|
@ -35,6 +35,12 @@ export interface ReplyRequest {
|
|||
* @memberof ReplyRequest
|
||||
*/
|
||||
'content': string;
|
||||
/**
|
||||
*
|
||||
* @type {boolean}
|
||||
* @memberof ReplyRequest
|
||||
*/
|
||||
'hidden'?: boolean;
|
||||
/**
|
||||
*
|
||||
* @type {CommentEmailOwner}
|
||||
|
|
|
@ -197,6 +197,13 @@ core:
|
|||
button: Reply and approve
|
||||
cancel_approve:
|
||||
button: Cancel approve
|
||||
list:
|
||||
fields:
|
||||
private: Private
|
||||
reply_modal:
|
||||
fields:
|
||||
hidden:
|
||||
label: Private reply
|
||||
detail_modal:
|
||||
fields:
|
||||
owner: Commentator
|
||||
|
|
|
@ -595,6 +595,7 @@ core:
|
|||
reply_count: "{count} Replies"
|
||||
has_new_replies: New replies
|
||||
pending_review: Pending review
|
||||
private: Private
|
||||
subject_refs:
|
||||
post: Post
|
||||
page: Page
|
||||
|
|
|
@ -561,6 +561,7 @@ core:
|
|||
reply_count: "{count} 条回复"
|
||||
has_new_replies: 有新的回复
|
||||
pending_review: 待审核
|
||||
private: 私密
|
||||
subject_refs:
|
||||
post: 文章
|
||||
page: 页面
|
||||
|
|
|
@ -546,6 +546,7 @@ core:
|
|||
reply_count: "{count} 條回覆"
|
||||
has_new_replies: 有新的回覆
|
||||
pending_review: 待審核
|
||||
private: 私密
|
||||
subject_refs:
|
||||
post: 文章
|
||||
page: 頁面
|
||||
|
|
Loading…
Reference in New Issue