diff --git a/api-docs/openapi/v3_0/aggregated.json b/api-docs/openapi/v3_0/aggregated.json index b4d669903..0cb3b63ac 100644 --- a/api-docs/openapi/v3_0/aggregated.json +++ b/api-docs/openapi/v3_0/aggregated.json @@ -3205,6 +3205,14 @@ "schema": { "type": "string" } + }, + { + "description": "Posts filtered by category including sub-categories.", + "in": "query", + "name": "categoryWithChildren", + "schema": { + "type": "string" + } } ], "responses": { @@ -13989,6 +13997,14 @@ "schema": { "type": "string" } + }, + { + "description": "Posts filtered by category including sub-categories.", + "in": "query", + "name": "categoryWithChildren", + "schema": { + "type": "string" + } } ], "responses": { @@ -14971,6 +14987,9 @@ "maxLength": 255, "type": "string" }, + "preventParentPostCascadeQuery": { + "type": "boolean" + }, "priority": { "type": "integer", "format": "int32", @@ -19001,12 +19020,12 @@ }, "visible": { "type": "string", - "default": "PUBLIC", "enum": [ "PUBLIC", "INTERNAL", "PRIVATE" - ] + ], + "default": "PUBLIC" } } }, @@ -20702,12 +20721,12 @@ }, "visible": { "type": "string", - "default": "PUBLIC", "enum": [ "PUBLIC", "INTERNAL", "PRIVATE" - ] + ], + "default": "PUBLIC" } } }, diff --git a/api/src/main/java/run/halo/app/core/extension/content/Category.java b/api/src/main/java/run/halo/app/core/extension/content/Category.java index 76df38a84..a2ddb04b1 100644 --- a/api/src/main/java/run/halo/app/core/extension/content/Category.java +++ b/api/src/main/java/run/halo/app/core/extension/content/Category.java @@ -69,6 +69,16 @@ public class Category extends AbstractExtension { private Integer priority; private List children; + + /** + *

if a category is queried for related posts, the default behavior is to + * query all posts under the category including its subcategories, but if this field is + * set to true, cascade query behavior will be terminated here.

+ *

For example, if a category has subcategories A and B, and A has subcategories C and + * D and C marked this field as true, when querying posts under A category,all posts under A + * and B will be queried, but C and D will not be queried.

+ */ + private boolean preventParentPostCascadeQuery; } @JsonIgnore diff --git a/application/src/main/java/run/halo/app/content/CategoryService.java b/application/src/main/java/run/halo/app/content/CategoryService.java new file mode 100644 index 000000000..0b8a17ea1 --- /dev/null +++ b/application/src/main/java/run/halo/app/content/CategoryService.java @@ -0,0 +1,10 @@ +package run.halo.app.content; + +import org.springframework.lang.NonNull; +import reactor.core.publisher.Flux; +import run.halo.app.core.extension.content.Category; + +public interface CategoryService { + + Flux listChildren(@NonNull String categoryName); +} diff --git a/application/src/main/java/run/halo/app/content/PostQuery.java b/application/src/main/java/run/halo/app/content/PostQuery.java index a8e33c9fa..c6e73f211 100644 --- a/application/src/main/java/run/halo/app/content/PostQuery.java +++ b/application/src/main/java/run/halo/app/content/PostQuery.java @@ -57,6 +57,12 @@ public class PostQuery extends IListRequest.QueryListRequest { return Post.PostPhase.from(publishPhase); } + @Nullable + public String getCategoryWithChildren() { + var value = queryParams.getFirst("categoryWithChildren"); + return StringUtils.defaultIfBlank(value, null); + } + @Nullable @Schema(description = "Posts filtered by keyword.") public String getKeyword() { @@ -140,6 +146,12 @@ public class PostQuery extends IListRequest.QueryListRequest { .name("keyword") .description("Posts filtered by keyword.") .implementation(String.class) + .required(false)) + .parameter(parameterBuilder() + .in(ParameterIn.QUERY) + .name("categoryWithChildren") + .description("Posts filtered by category including sub-categories.") + .implementation(String.class) .required(false)); } } diff --git a/application/src/main/java/run/halo/app/content/impl/CategoryServiceImpl.java b/application/src/main/java/run/halo/app/content/impl/CategoryServiceImpl.java new file mode 100644 index 000000000..3ee561e61 --- /dev/null +++ b/application/src/main/java/run/halo/app/content/impl/CategoryServiceImpl.java @@ -0,0 +1,34 @@ +package run.halo.app.content.impl; + +import lombok.RequiredArgsConstructor; +import org.springframework.lang.NonNull; +import org.springframework.stereotype.Component; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; +import run.halo.app.content.CategoryService; +import run.halo.app.core.extension.content.Category; +import run.halo.app.extension.ReactiveExtensionClient; + +@Component +@RequiredArgsConstructor +public class CategoryServiceImpl implements CategoryService { + private final ReactiveExtensionClient client; + + @Override + public Flux listChildren(@NonNull String categoryName) { + return client.fetch(Category.class, categoryName) + .expand(category -> { + var children = category.getSpec().getChildren(); + if (children == null || children.isEmpty()) { + return Mono.empty(); + } + return Flux.fromIterable(children) + .flatMap(name -> client.fetch(Category.class, name)) + .filter(this::isNotIndependent); + }); + } + + private boolean isNotIndependent(Category category) { + return !category.getSpec().isPreventParentPostCascadeQuery(); + } +} diff --git a/application/src/main/java/run/halo/app/content/impl/PostServiceImpl.java b/application/src/main/java/run/halo/app/content/impl/PostServiceImpl.java index 566df88c1..1b2c97471 100644 --- a/application/src/main/java/run/halo/app/content/impl/PostServiceImpl.java +++ b/application/src/main/java/run/halo/app/content/impl/PostServiceImpl.java @@ -20,6 +20,7 @@ import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import reactor.util.retry.Retry; import run.halo.app.content.AbstractContentService; +import run.halo.app.content.CategoryService; import run.halo.app.content.ContentRequest; import run.halo.app.content.ContentWrapper; import run.halo.app.content.Contributor; @@ -36,6 +37,7 @@ import run.halo.app.core.extension.content.Tag; import run.halo.app.core.extension.service.UserService; import run.halo.app.extension.ListOptions; import run.halo.app.extension.ListResult; +import run.halo.app.extension.MetadataOperator; import run.halo.app.extension.PageRequestImpl; import run.halo.app.extension.ReactiveExtensionClient; import run.halo.app.extension.Ref; @@ -57,20 +59,23 @@ public class PostServiceImpl extends AbstractContentService implements PostServi private final ReactiveExtensionClient client; private final CounterService counterService; private final UserService userService; + private final CategoryService categoryService; public PostServiceImpl(ReactiveExtensionClient client, CounterService counterService, - UserService userService) { + UserService userService, CategoryService categoryService) { super(client); this.client = client; this.counterService = counterService; this.userService = userService; + this.categoryService = categoryService; } @Override public Mono> listPost(PostQuery query) { - return client.listBy(Post.class, query.toListOptions(), + return buildListOptions(query) + .flatMap(listOptions -> client.listBy(Post.class, listOptions, PageRequestImpl.of(query.getPage(), query.getSize(), query.getSort()) - ) + )) .flatMap(listResult -> Flux.fromStream(listResult.get()) .map(this::getListedPost) .concatMap(Function.identity()) @@ -82,6 +87,26 @@ public class PostServiceImpl extends AbstractContentService implements PostServi ); } + Mono buildListOptions(PostQuery query) { + var categoryName = query.getCategoryWithChildren(); + if (categoryName == null) { + return Mono.just(query.toListOptions()); + } + return categoryService.listChildren(categoryName) + .collectList() + .map(categories -> { + var categoryNames = categories.stream() + .map(Category::getMetadata) + .map(MetadataOperator::getName) + .toList(); + var listOptions = query.toListOptions(); + var newFiledSelector = listOptions.getFieldSelector() + .andQuery(in("spec.categories", categoryNames)); + listOptions.setFieldSelector(newFiledSelector); + return listOptions; + }); + } + Mono fetchStats(Post post) { Assert.notNull(post, "The post must not be null."); String name = post.getMetadata().getName(); diff --git a/application/src/main/java/run/halo/app/theme/finders/impl/CategoryFinderImpl.java b/application/src/main/java/run/halo/app/theme/finders/impl/CategoryFinderImpl.java index 1bfc4c893..cbbe0ddc4 100644 --- a/application/src/main/java/run/halo/app/theme/finders/impl/CategoryFinderImpl.java +++ b/application/src/main/java/run/halo/app/theme/finders/impl/CategoryFinderImpl.java @@ -17,6 +17,7 @@ import reactor.core.publisher.Mono; import run.halo.app.core.extension.content.Category; import run.halo.app.extension.ListOptions; import run.halo.app.extension.ListResult; +import run.halo.app.extension.Metadata; import run.halo.app.extension.PageRequestImpl; import run.halo.app.extension.ReactiveExtensionClient; import run.halo.app.extension.index.query.QueryFactory; @@ -114,7 +115,9 @@ public class CategoryFinderImpl implements CategoryFinder { } } }); - return listToTree(nameIdentityMap.values(), name); + var tree = listToTree(nameIdentityMap.values(), name); + recomputePostCount(tree); + return tree; }); } @@ -139,6 +142,40 @@ public class CategoryFinderImpl implements CategoryFinder { .collect(Collectors.toList()); } + private CategoryTreeVo dummyVirtualRoot(List treeNodes) { + Category.CategorySpec categorySpec = new Category.CategorySpec(); + categorySpec.setSlug("/"); + return CategoryTreeVo.builder() + .spec(categorySpec) + .postCount(0) + .children(treeNodes) + .metadata(new Metadata()) + .build(); + } + + void recomputePostCount(List treeNodes) { + var rootNode = dummyVirtualRoot(treeNodes); + recomputePostCount(rootNode); + } + + private int recomputePostCount(CategoryTreeVo rootNode) { + if (rootNode == null) { + return 0; + } + + int originalPostCount = rootNode.getPostCount(); + + for (var child : rootNode.getChildren()) { + int childSum = recomputePostCount(child); + if (!child.getSpec().isPreventParentPostCascadeQuery()) { + rootNode.setPostCount(rootNode.getPostCount() + childSum); + } + } + + return rootNode.getSpec().isPreventParentPostCascadeQuery() ? originalPostCount + : rootNode.getPostCount(); + } + static Comparator defaultTreeNodeComparator() { Function priority = category -> Objects.requireNonNullElse(category.getSpec().getPriority(), 0); diff --git a/application/src/main/java/run/halo/app/theme/finders/impl/PostFinderImpl.java b/application/src/main/java/run/halo/app/theme/finders/impl/PostFinderImpl.java index 34fda01b9..0651c5c6e 100644 --- a/application/src/main/java/run/halo/app/theme/finders/impl/PostFinderImpl.java +++ b/application/src/main/java/run/halo/app/theme/finders/impl/PostFinderImpl.java @@ -2,6 +2,7 @@ package run.halo.app.theme.finders.impl; 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.in; import java.util.Comparator; import java.util.List; @@ -15,6 +16,8 @@ import org.springframework.lang.NonNull; import org.springframework.util.Assert; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; +import run.halo.app.content.CategoryService; +import run.halo.app.core.extension.content.Category; import run.halo.app.core.extension.content.Post; import run.halo.app.extension.ListOptions; import run.halo.app.extension.ListResult; @@ -52,6 +55,8 @@ public class PostFinderImpl implements PostFinder { private final ReactiveQueryPostPredicateResolver postPredicateResolver; + private final CategoryService categoryService; + @Override public Mono getByName(String postName) { return postPredicateResolver.getPredicate() @@ -141,13 +146,24 @@ public class PostFinderImpl implements PostFinder { @Override public Mono> listByCategory(Integer page, Integer size, String categoryName) { - var fieldQuery = QueryFactory.all(); - if (StringUtils.isNotBlank(categoryName)) { - fieldQuery = and(fieldQuery, equal("spec.categories", categoryName)); + return listChildrenCategories(categoryName) + .map(category -> category.getMetadata().getName()) + .collectList() + .flatMap(categoryNames -> { + var listOptions = new ListOptions(); + var fieldQuery = in("spec.categories", categoryNames); + listOptions.setFieldSelector(FieldSelector.of(fieldQuery)); + return postPublicQueryService.list(listOptions, getPageRequest(page, size)); + }); + } + + private Flux listChildrenCategories(String categoryName) { + if (StringUtils.isBlank(categoryName)) { + return client.listAll(Category.class, new ListOptions(), + Sort.by(Sort.Order.asc("metadata.creationTimeStamp"), + Sort.Order.desc("metadata.name"))); } - var listOptions = new ListOptions(); - listOptions.setFieldSelector(FieldSelector.of(fieldQuery)); - return postPublicQueryService.list(listOptions, getPageRequest(page, size)); + return categoryService.listChildren(categoryName); } @Override diff --git a/application/src/main/java/run/halo/app/theme/finders/vo/CategoryTreeVo.java b/application/src/main/java/run/halo/app/theme/finders/vo/CategoryTreeVo.java index 6a0617667..d6780a3e5 100644 --- a/application/src/main/java/run/halo/app/theme/finders/vo/CategoryTreeVo.java +++ b/application/src/main/java/run/halo/app/theme/finders/vo/CategoryTreeVo.java @@ -53,6 +53,7 @@ public class CategoryTreeVo implements VisualizableTreeNode, Ext @Override public String nodeText() { - return String.format("%s (%s)", getSpec().getDisplayName(), getPostCount()); + return String.format("%s (%s)%s", getSpec().getDisplayName(), getPostCount(), + spec.isPreventParentPostCascadeQuery() ? " (Independent)" : ""); } } diff --git a/application/src/main/java/run/halo/app/theme/finders/vo/CategoryVo.java b/application/src/main/java/run/halo/app/theme/finders/vo/CategoryVo.java index c16f0b0dc..9a83cddde 100644 --- a/application/src/main/java/run/halo/app/theme/finders/vo/CategoryVo.java +++ b/application/src/main/java/run/halo/app/theme/finders/vo/CategoryVo.java @@ -36,7 +36,7 @@ public class CategoryVo implements ExtensionVoOperator { .metadata(category.getMetadata()) .spec(category.getSpec()) .status(category.getStatus()) - .postCount(category.getStatusOrDefault().visiblePostCount) + .postCount(category.getStatusOrDefault().getVisiblePostCount()) .build(); } } diff --git a/application/src/test/java/run/halo/app/theme/finders/impl/CategoryFinderImplTest.java b/application/src/test/java/run/halo/app/theme/finders/impl/CategoryFinderImplTest.java index cbe831757..fee3d0cb1 100644 --- a/application/src/test/java/run/halo/app/theme/finders/impl/CategoryFinderImplTest.java +++ b/application/src/test/java/run/halo/app/theme/finders/impl/CategoryFinderImplTest.java @@ -6,18 +6,22 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.when; import com.fasterxml.jackson.core.type.TypeReference; +import java.io.IOException; +import java.nio.file.Files; import java.time.Instant; import java.util.List; import java.util.Map; import java.util.Objects; import org.json.JSONException; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.skyscreamer.jsonassert.JSONAssert; import org.springframework.data.domain.Sort; +import org.springframework.util.ResourceUtils; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import run.halo.app.core.extension.content.Category; @@ -73,7 +77,8 @@ class CategoryFinderImplTest { "children": [ "C1", "C2" - ] + ], + "preventParentPostCascadeQuery": false } } """, @@ -126,14 +131,97 @@ class CategoryFinderImplTest { List treeVos = categoryFinder.listAsTree().collectList().block(); String s = visualizeTree(treeVos); assertThat(s).isEqualTo(""" - 全部 (5) - ├── FIT2CLOUD (2) + 全部 (7) + ├── FIT2CLOUD (4) │ ├── DataEase (0) │ ├── Halo (2) │ ├── MeterSphere (0) │ └── JumpServer (0) └── 默认分类 (3) - """); + """); + } + + @Nested + class CategoryPostCountTest { + + /** + *

Structure below.

+ *
+         * 全部 (35)
+         * ├── FIT2CLOUD (15)
+         * │   ├── DataEase (10)
+         * │   │   ├── SubNode1 (4)
+         * │   │   │   ├── Leaf1 (2)
+         * │   │   │   ├── Leaf2 (2)
+         * │   │   ├── SubNode2 (6)  (independent)
+         * │   │       ├── IndependentChild1 (3)
+         * │   │       ├── IndependentChild2 (3)
+         * │   ├── IndependentNode (5)  (independent)
+         * │       ├── IndependentChild3 (2)
+         * │       ├── IndependentChild4 (3)
+         * ├── AnotherRootChild (20)
+         * │   ├── Child1 (8)
+         * │   │   ├── SubChild1 (3)
+         * │   │   │   ├── DeepNode1 (1)
+         * │   │   │   ├── DeepNode2 (1)
+         * │   │   │   │   ├── DeeperNode (1)
+         * │   │   ├── SubChild2 (5)
+         * │   │       ├── DeepNode3 (2)  (independent)
+         * │   │           ├── DeepNode4 (1)
+         * │   │           ├── DeepNode5 (1)
+         * │   ├── Child2 (12)
+         * │       ├── IndependentSubNode (12)  (independent)
+         * │           ├── SubNode3 (6)
+         * │           ├── SubNode4 (6)
+         * 
+ */ + private List categories; + + @BeforeEach + void setUp() throws IOException { + var file = ResourceUtils.getFile("classpath:categories/independent-post-count.json"); + var json = Files.readString(file.toPath()); + categories = JsonUtils.jsonToObject(json, new TypeReference<>() { + }); + when(client.listAll(eq(Category.class), any(ListOptions.class), any(Sort.class))) + .thenReturn(Flux.fromIterable(categories)); + } + + @Test + void computePostCountFromTree() { + var treeVos = categoryFinder.toCategoryTreeVoFlux("全部") + .collectList().block(); + assertThat(treeVos).hasSize(1); + String s = visualizeTree(treeVos.get(0).getChildren()); + assertThat(s).isEqualTo(""" + 全部 (84) + ├── AnotherRootChild (51) + │ ├── Child1 (19) + │ │ ├── SubChild1 (6) + │ │ │ ├── DeepNode1 (1) + │ │ │ └── DeepNode2 (2) + │ │ │ └── DeeperNode (1) + │ │ └── SubChild2 (5) + │ │ └── DeepNode3 (4) (Independent) + │ │ ├── DeepNode4 (1) + │ │ └── DeepNode5 (1) + │ └── Child2 (12) + │ └── IndependentSubNode (24) (Independent) + │ ├── SubNode3 (6) + │ └── SubNode4 (6) + └── FIT2CLOUD (33) + ├── DataEase (18) + │ ├── SubNode1 (8) + │ │ ├── Leaf1 (2) + │ │ └── Leaf2 (2) + │ └── SubNode2 (12) (Independent) + │ ├── IndependentChild1 (3) + │ └── IndependentChild2 (3) + └── IndependentNode (10) (Independent) + ├── IndependentChild3 (2) + └── IndependentChild4 (3) + """); + } } private List categoriesForTree() { @@ -204,7 +292,6 @@ class CategoryFinderImplTest { return stringBuilder.toString(); } - private List categories() { Category category2 = JsonUtils.deepCopy(category()); category2.getMetadata().setName("c2"); @@ -411,4 +498,4 @@ class CategoryFinderImplTest { return JsonUtils.jsonToObject(s, new TypeReference<>() { }); } -} \ No newline at end of file +} diff --git a/application/src/test/resources/categories/independent-post-count.json b/application/src/test/resources/categories/independent-post-count.json new file mode 100644 index 000000000..f4fce09af --- /dev/null +++ b/application/src/test/resources/categories/independent-post-count.json @@ -0,0 +1,422 @@ +[ + { + "spec": { + "displayName": "全部", + "children": ["FIT2CLOUD", "AnotherRootChild"] + }, + "status": { + "visiblePostCount": 35 + }, + "apiVersion": "content.halo.run/v1alpha1", + "kind": "Category", + "metadata": { + "name": "全部", + "version": 0, + "creationTimestamp": "2024-06-14T06:17:47.589181Z" + } + }, + { + "spec": { + "displayName": "FIT2CLOUD", + "children": ["DataEase", "IndependentNode"] + }, + "status": { + "visiblePostCount": 15 + }, + "apiVersion": "content.halo.run/v1alpha1", + "kind": "Category", + "metadata": { + "name": "FIT2CLOUD", + "version": 0, + "creationTimestamp": "2024-06-14T06:17:47.589181Z" + } + }, + { + "spec": { + "displayName": "DataEase", + "children": ["SubNode1", "SubNode2"] + }, + "status": { + "visiblePostCount": 10 + }, + "apiVersion": "content.halo.run/v1alpha1", + "kind": "Category", + "metadata": { + "name": "DataEase", + "version": 0, + "creationTimestamp": "2024-06-14T06:17:47.589181Z" + } + }, + { + "spec": { + "displayName": "SubNode1", + "children": ["Leaf1", "Leaf2"] + }, + "status": { + "visiblePostCount": 4 + }, + "apiVersion": "content.halo.run/v1alpha1", + "kind": "Category", + "metadata": { + "name": "SubNode1", + "version": 0, + "creationTimestamp": "2024-06-14T06:17:47.589181Z" + } + }, + { + "spec": { + "displayName": "Leaf1", + "children": [] + }, + "status": { + "visiblePostCount": 2 + }, + "apiVersion": "content.halo.run/v1alpha1", + "kind": "Category", + "metadata": { + "name": "Leaf1", + "version": 0, + "creationTimestamp": "2024-06-14T06:17:47.589181Z" + } + }, + { + "spec": { + "displayName": "Leaf2", + "children": [] + }, + "status": { + "visiblePostCount": 2 + }, + "apiVersion": "content.halo.run/v1alpha1", + "kind": "Category", + "metadata": { + "name": "Leaf2", + "version": 0, + "creationTimestamp": "2024-06-14T06:17:47.589181Z" + } + }, + { + "spec": { + "displayName": "SubNode2", + "preventParentPostCascadeQuery": true, + "children": ["IndependentChild1", "IndependentChild2"] + }, + "status": { + "visiblePostCount": 6 + }, + "apiVersion": "content.halo.run/v1alpha1", + "kind": "Category", + "metadata": { + "name": "SubNode2", + "version": 0, + "creationTimestamp": "2024-06-14T06:17:47.589181Z" + } + }, + { + "spec": { + "displayName": "IndependentChild1", + "children": [] + }, + "status": { + "visiblePostCount": 3 + }, + "apiVersion": "content.halo.run/v1alpha1", + "kind": "Category", + "metadata": { + "name": "IndependentChild1", + "version": 0, + "creationTimestamp": "2024-06-14T06:17:47.589181Z" + } + }, + { + "spec": { + "displayName": "IndependentChild2", + "children": [] + }, + "status": { + "visiblePostCount": 3 + }, + "apiVersion": "content.halo.run/v1alpha1", + "kind": "Category", + "metadata": { + "name": "IndependentChild2", + "version": 0, + "creationTimestamp": "2024-06-14T06:17:47.589181Z" + } + }, + { + "spec": { + "displayName": "IndependentNode", + "preventParentPostCascadeQuery": true, + "children": ["IndependentChild3", "IndependentChild4"] + }, + "status": { + "visiblePostCount": 5 + }, + "apiVersion": "content.halo.run/v1alpha1", + "kind": "Category", + "metadata": { + "name": "IndependentNode", + "version": 0, + "creationTimestamp": "2024-06-14T06:17:47.589181Z" + } + }, + { + "spec": { + "displayName": "IndependentChild3", + "children": [] + }, + "status": { + "visiblePostCount": 2 + }, + "apiVersion": "content.halo.run/v1alpha1", + "kind": "Category", + "metadata": { + "name": "IndependentChild3", + "version": 0, + "creationTimestamp": "2024-06-14T06:17:47.589181Z" + } + }, + { + "spec": { + "displayName": "IndependentChild4", + "children": [] + }, + "status": { + "visiblePostCount": 3 + }, + "apiVersion": "content.halo.run/v1alpha1", + "kind": "Category", + "metadata": { + "name": "IndependentChild4", + "version": 0, + "creationTimestamp": "2024-06-14T06:17:47.589181Z" + } + }, + { + "spec": { + "displayName": "AnotherRootChild", + "children": ["Child1", "Child2"] + }, + "status": { + "visiblePostCount": 20 + }, + "apiVersion": "content.halo.run/v1alpha1", + "kind": "Category", + "metadata": { + "name": "AnotherRootChild", + "version": 0, + "creationTimestamp": "2024-06-14T06:17:47.589181Z" + } + }, + { + "spec": { + "displayName": "Child1", + "children": ["SubChild1", "SubChild2"] + }, + "status": { + "visiblePostCount": 8 + }, + "apiVersion": "content.halo.run/v1alpha1", + "kind": "Category", + "metadata": { + "name": "Child1", + "version": 0, + "creationTimestamp": "2024-06-14T06:17:47.589181Z" + } + }, + { + "spec": { + "displayName": "SubChild1", + "children": ["DeepNode1", "DeepNode2"] + }, + "status": { + "visiblePostCount": 3 + }, + "apiVersion": "content.halo.run/v1alpha1", + "kind": "Category", + "metadata": { + "name": "SubChild1", + "version": 0, + "creationTimestamp": "2024-06-14T06:17:47.589181Z" + } + }, + { + "spec": { + "displayName": "DeepNode1", + "children": [] + }, + "status": { + "visiblePostCount": 1 + }, + "apiVersion": "content.halo.run/v1alpha1", + "kind": "Category", + "metadata": { + "name": "DeepNode1", + "version": 0, + "creationTimestamp": "2024-06-14T06:17:47.589181Z" + } + }, + { + "spec": { + "displayName": "DeepNode2", + "children": ["DeeperNode"] + }, + "status": { + "visiblePostCount": 1 + }, + "apiVersion": "content.halo.run/v1alpha1", + "kind": "Category", + "metadata": { + "name": "DeepNode2", + "version": 0, + "creationTimestamp": "2024-06-14T06:17:47.589181Z" + } + }, + { + "spec": { + "displayName": "DeeperNode", + "children": [] + }, + "status": { + "visiblePostCount": 1 + }, + "apiVersion": "content.halo.run/v1alpha1", + "kind": "Category", + "metadata": { + "name": "DeeperNode", + "version": 0, + "creationTimestamp": "2024-06-14T06:17:47.589181Z" + } + }, + { + "spec": { + "displayName": "SubChild2", + "children": ["DeepNode3"] + }, + "status": { + "visiblePostCount": 5 + }, + "apiVersion": "content.halo.run/v1alpha1", + "kind": "Category", + "metadata": { + "name": "SubChild2", + "version": 0, + "creationTimestamp": "2024-06-14T06:17:47.589181Z" + } + }, + { + "spec": { + "displayName": "DeepNode3", + "preventParentPostCascadeQuery": true, + "children": ["DeepNode4", "DeepNode5"] + }, + "status": { + "visiblePostCount": 2 + }, + "apiVersion": "content.halo.run/v1alpha1", + "kind": "Category", + "metadata": { + "name": "DeepNode3", + "version": 0, + "creationTimestamp": "2024-06-14T06:17:47.589181Z" + } + }, + { + "spec": { + "displayName": "DeepNode4", + "children": [] + }, + "status": { + "visiblePostCount": 1 + }, + "apiVersion": "content.halo.run/v1alpha1", + "kind": "Category", + "metadata": { + "name": "DeepNode4", + "version": 0, + "creationTimestamp": "2024-06-14T06:17:47.589181Z" + } + }, + { + "spec": { + "displayName": "DeepNode5", + "children": [] + }, + "status": { + "visiblePostCount": 1 + }, + "apiVersion": "content.halo.run/v1alpha1", + "kind": "Category", + "metadata": { + "name": "DeepNode5", + "version": 0, + "creationTimestamp": "2024-06-14T06:17:47.589181Z" + } + }, + { + "spec": { + "displayName": "Child2", + "children": ["IndependentSubNode"] + }, + "status": { + "visiblePostCount": 12 + }, + "apiVersion": "content.halo.run/v1alpha1", + "kind": "Category", + "metadata": { + "name": "Child2", + "version": 0, + "creationTimestamp": "2024-06-14T06:17:47.589181Z" + } + }, + { + "spec": { + "displayName": "IndependentSubNode", + "preventParentPostCascadeQuery": true, + "children": ["SubNode3", "SubNode4"] + }, + "status": { + "visiblePostCount": 12 + }, + "apiVersion": "content.halo.run/v1alpha1", + "kind": "Category", + "metadata": { + "name": "IndependentSubNode", + "version": 0, + "creationTimestamp": "2024-06-14T06:17:47.589181Z" + } + }, + { + "spec": { + "displayName": "SubNode3", + "children": [] + }, + "status": { + "visiblePostCount": 6 + }, + "apiVersion": "content.halo.run/v1alpha1", + "kind": "Category", + "metadata": { + "name": "SubNode3", + "version": 0, + "creationTimestamp": "2024-06-14T06:17:47.589181Z" + } + }, + { + "spec": { + "displayName": "SubNode4", + "children": [] + }, + "status": { + "visiblePostCount": 6 + }, + "apiVersion": "content.halo.run/v1alpha1", + "kind": "Category", + "metadata": { + "name": "SubNode4", + "version": 0, + "creationTimestamp": "2024-06-14T06:17:47.589181Z" + } + } +] diff --git a/ui/console-src/modules/contents/posts/categories/components/CategoryEditingModal.vue b/ui/console-src/modules/contents/posts/categories/components/CategoryEditingModal.vue index 72d468d9b..606b015c5 100644 --- a/ui/console-src/modules/contents/posts/categories/components/CategoryEditingModal.vue +++ b/ui/console-src/modules/contents/posts/categories/components/CategoryEditingModal.vue @@ -1,9 +1,12 @@