mirror of https://github.com/halo-dev/halo
feat: support setting rendering templates for related posts on category (#6106)
#### What type of PR is this? /kind feature /area core /milestone 2.17.x #### What this PR does / why we need it: 支持在分类上为关联的文章统一设置渲染模板 现在文章的模板生效顺序为: 1. 文章关联的分类上设置的文章模板,如果有多个则选择第一个 2. 文章上设置的自定义模板 3. 文章的默认模板 #### Which issue(s) this PR fixes: Fixes #6101 #### Does this PR introduce a user-facing change? ```release-note 支持在分类上为关联的文章统一设置渲染模板 ```pull/6031/head^2
parent
6d3a157d35
commit
b5f9010e60
|
@ -13499,6 +13499,10 @@
|
|||
"minLength": 1,
|
||||
"type": "string"
|
||||
},
|
||||
"postTemplate": {
|
||||
"maxLength": 255,
|
||||
"type": "string"
|
||||
},
|
||||
"priority": {
|
||||
"type": "integer",
|
||||
"format": "int32",
|
||||
|
@ -13509,6 +13513,7 @@
|
|||
"type": "string"
|
||||
},
|
||||
"template": {
|
||||
"maxLength": 255,
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
|
@ -17446,12 +17451,12 @@
|
|||
},
|
||||
"visible": {
|
||||
"type": "string",
|
||||
"default": "PUBLIC",
|
||||
"enum": [
|
||||
"PUBLIC",
|
||||
"INTERNAL",
|
||||
"PRIVATE"
|
||||
],
|
||||
"default": "PUBLIC"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -19101,12 +19106,12 @@
|
|||
},
|
||||
"visible": {
|
||||
"type": "string",
|
||||
"default": "PUBLIC",
|
||||
"enum": [
|
||||
"PUBLIC",
|
||||
"INTERNAL",
|
||||
"PRIVATE"
|
||||
],
|
||||
"default": "PUBLIC"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package run.halo.app.core.extension.content;
|
||||
|
||||
import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.NOT_REQUIRED;
|
||||
import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED;
|
||||
import static run.halo.app.core.extension.content.Category.KIND;
|
||||
|
||||
|
@ -53,8 +54,17 @@ public class Category extends AbstractExtension {
|
|||
|
||||
private String cover;
|
||||
|
||||
@Schema(requiredMode = NOT_REQUIRED, maxLength = 255)
|
||||
private String template;
|
||||
|
||||
/**
|
||||
* <p>Used to specify the template for the posts associated with the category.</p>
|
||||
* <p>The priority is not as high as that of the post.</p>
|
||||
* <p>If the post also specifies a template, the post's template will prevail.</p>
|
||||
*/
|
||||
@Schema(requiredMode = NOT_REQUIRED, maxLength = 255)
|
||||
private String postTemplate;
|
||||
|
||||
@Schema(requiredMode = REQUIRED, defaultValue = "0")
|
||||
private Integer priority;
|
||||
|
||||
|
|
|
@ -1,11 +1,14 @@
|
|||
package run.halo.app.theme.router.factories;
|
||||
|
||||
import static org.apache.commons.lang3.ObjectUtils.defaultIfNull;
|
||||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||
import static org.springframework.web.reactive.function.server.RequestPredicates.GET;
|
||||
import static org.springframework.web.reactive.function.server.RequestPredicates.accept;
|
||||
|
||||
import com.google.common.cache.Cache;
|
||||
import com.google.common.cache.CacheBuilder;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
@ -120,13 +123,24 @@ public class PostRouteFactory implements RouteFactory {
|
|||
})
|
||||
.flatMap(postVo -> {
|
||||
Map<String, Object> model = ModelMapUtils.postModel(postVo);
|
||||
String template = postVo.getSpec().getTemplate();
|
||||
return viewNameResolver.resolveViewNameOrDefault(request, template,
|
||||
DefaultTemplateEnum.POST.getValue())
|
||||
return determineTemplate(request, postVo)
|
||||
.flatMap(templateName -> ServerResponse.ok().render(templateName, model));
|
||||
});
|
||||
}
|
||||
|
||||
Mono<String> determineTemplate(ServerRequest request, PostVo postVo) {
|
||||
return Flux.fromIterable(defaultIfNull(postVo.getCategories(), List.of()))
|
||||
.filter(category -> isNotBlank(category.getSpec().getPostTemplate()))
|
||||
.concatMap(category -> viewNameResolver.resolveViewNameOrDefault(request,
|
||||
category.getSpec().getPostTemplate(), null)
|
||||
)
|
||||
.next()
|
||||
.switchIfEmpty(Mono.defer(() -> viewNameResolver.resolveViewNameOrDefault(request,
|
||||
postVo.getSpec().getTemplate(),
|
||||
DefaultTemplateEnum.POST.getValue())
|
||||
));
|
||||
}
|
||||
|
||||
Mono<PostVo> bestMatchPost(PostPatternVariable variable) {
|
||||
return postsByPredicates(variable)
|
||||
.filter(post -> {
|
||||
|
@ -143,10 +157,10 @@ public class PostRouteFactory implements RouteFactory {
|
|||
}
|
||||
|
||||
Flux<Post> postsByPredicates(PostPatternVariable patternVariable) {
|
||||
if (StringUtils.isNotBlank(patternVariable.getName())) {
|
||||
if (isNotBlank(patternVariable.getName())) {
|
||||
return fetchPostsByName(patternVariable.getName());
|
||||
}
|
||||
if (StringUtils.isNotBlank(patternVariable.getSlug())) {
|
||||
if (isNotBlank(patternVariable.getSlug())) {
|
||||
return fetchPostsBySlug(patternVariable.getSlug());
|
||||
}
|
||||
return Flux.empty();
|
||||
|
@ -163,7 +177,7 @@ public class PostRouteFactory implements RouteFactory {
|
|||
private Flux<Post> fetchPostsBySlug(String slug) {
|
||||
return queryPostPredicateResolver.getListOptions()
|
||||
.flatMapMany(listOptions -> {
|
||||
if (StringUtils.isNotBlank(slug)) {
|
||||
if (isNotBlank(slug)) {
|
||||
var other = QueryFactory.equal("spec.slug", slug);
|
||||
listOptions.setFieldSelector(listOptions.getFieldSelector().andQuery(other));
|
||||
}
|
||||
|
|
|
@ -51,6 +51,7 @@ const formState = ref<Category>({
|
|||
description: "",
|
||||
cover: "",
|
||||
template: "",
|
||||
postTemplate: "",
|
||||
priority: 0,
|
||||
children: [],
|
||||
},
|
||||
|
@ -160,6 +161,7 @@ onMounted(() => {
|
|||
|
||||
// custom templates
|
||||
const { templates } = useThemeCustomTemplates("category");
|
||||
const { templates: postTemplates } = useThemeCustomTemplates("post");
|
||||
|
||||
// slug
|
||||
const { handleGenerateSlug } = useSlugify(
|
||||
|
@ -243,9 +245,26 @@ const { handleGenerateSlug } = useSlugify(
|
|||
:label="
|
||||
$t('core.post_category.editing_modal.fields.template.label')
|
||||
"
|
||||
:help="
|
||||
$t('core.post_category.editing_modal.fields.template.help')
|
||||
"
|
||||
type="select"
|
||||
name="template"
|
||||
></FormKit>
|
||||
<FormKit
|
||||
v-model="formState.spec.postTemplate"
|
||||
:options="postTemplates"
|
||||
:label="
|
||||
$t(
|
||||
'core.post_category.editing_modal.fields.post_template.label'
|
||||
)
|
||||
"
|
||||
:help="
|
||||
$t('core.post_category.editing_modal.fields.post_template.help')
|
||||
"
|
||||
type="select"
|
||||
name="postTemplate"
|
||||
></FormKit>
|
||||
<FormKit
|
||||
v-model="formState.spec.cover"
|
||||
:help="$t('core.post_category.editing_modal.fields.cover.help')"
|
||||
|
|
|
@ -44,6 +44,12 @@ export interface CategorySpec {
|
|||
* @memberof CategorySpec
|
||||
*/
|
||||
'displayName': string;
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof CategorySpec
|
||||
*/
|
||||
'postTemplate'?: string;
|
||||
/**
|
||||
*
|
||||
* @type {number}
|
||||
|
|
|
@ -366,12 +366,20 @@ core:
|
|||
refresh_message: Regenerate slug based on display name.
|
||||
template:
|
||||
label: Custom template
|
||||
help: >-
|
||||
Customize the rendering template of the category archive page, which
|
||||
requires support from the theme
|
||||
cover:
|
||||
label: Cover
|
||||
help: Theme adaptation is required to support
|
||||
description:
|
||||
label: Description
|
||||
help: Theme adaptation is required to support
|
||||
post_template:
|
||||
label: Custom post template
|
||||
help: >-
|
||||
Customize the rendering template of posts in the current category,
|
||||
which requires support from the theme
|
||||
page:
|
||||
title: Pages
|
||||
actions:
|
||||
|
@ -728,9 +736,9 @@ core:
|
|||
version_mismatch:
|
||||
title: Version mismatch
|
||||
description: >-
|
||||
The imported configuration file version does not match the
|
||||
current theme version, which may lead to compatibility issues.
|
||||
Do you want to continue importing?
|
||||
The imported configuration file version does not match the current
|
||||
theme version, which may lead to compatibility issues. Do you want
|
||||
to continue importing?
|
||||
invalid_format: Invalid theme configuration file
|
||||
mismatched_theme: Configuration file does not match the selected theme
|
||||
list_modal:
|
||||
|
|
|
@ -366,12 +366,16 @@ core:
|
|||
refresh_message: 根据名称重新生成别名
|
||||
template:
|
||||
label: 自定义模板
|
||||
help: 自定义分类归档页面的渲染模版,需要主题提供支持
|
||||
cover:
|
||||
label: 封面图
|
||||
help: 需要主题适配以支持
|
||||
description:
|
||||
label: 描述
|
||||
help: 需要主题适配以支持
|
||||
post_template:
|
||||
label: 自定义文章模板
|
||||
help: 自定义当前分类下文章的渲染模版,需要主题提供支持
|
||||
page:
|
||||
title: 页面
|
||||
actions:
|
||||
|
|
|
@ -346,12 +346,16 @@ core:
|
|||
refresh_message: 根據名稱重新生成別名
|
||||
template:
|
||||
label: 自定義模板
|
||||
help: 自定義分類歸檔頁面的渲染模板,需要主題提供支持
|
||||
cover:
|
||||
label: 封面圖
|
||||
help: 需要主題適配以支持
|
||||
description:
|
||||
label: 描述
|
||||
help: 需要主題適配以支持
|
||||
post_template:
|
||||
label: 自定義文章模板
|
||||
help: 自定義當前分類下文章的渲染模板,需要主題提供支持
|
||||
page:
|
||||
title: 頁面
|
||||
actions:
|
||||
|
|
Loading…
Reference in New Issue