mirror of https://github.com/halo-dev/halo
[release-2.9] fix: not using the default template when the custom template does not exist (#4648)
This is an automated cherry-pick of #4618 /assign guqing ```release-note 修复文章自定义模板不存在时没有使用默认模板的问题 ```release-2.9
parent
12d25a4300
commit
5a34fb51a8
|
@ -1,14 +1,14 @@
|
||||||
package run.halo.app.theme.router;
|
package run.halo.app.theme.router;
|
||||||
|
|
||||||
import java.util.Locale;
|
import java.nio.file.Files;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.springframework.boot.autoconfigure.thymeleaf.ThymeleafProperties;
|
import org.springframework.boot.autoconfigure.thymeleaf.ThymeleafProperties;
|
||||||
import org.springframework.context.i18n.LocaleContextHolder;
|
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
import org.springframework.web.reactive.function.server.ServerRequest;
|
import org.springframework.web.reactive.function.server.ServerRequest;
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
import run.halo.app.theme.HaloViewResolver;
|
import run.halo.app.theme.ThemeResolver;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The {@link ViewNameResolver} is used to resolve view name.
|
* The {@link ViewNameResolver} is used to resolve view name.
|
||||||
|
@ -19,7 +19,8 @@ import run.halo.app.theme.HaloViewResolver;
|
||||||
@Component
|
@Component
|
||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
public class ViewNameResolver {
|
public class ViewNameResolver {
|
||||||
private final HaloViewResolver haloViewResolver;
|
private static final String TEMPLATES = "templates";
|
||||||
|
private final ThemeResolver themeResolver;
|
||||||
private final ThymeleafProperties thymeleafProperties;
|
private final ThymeleafProperties thymeleafProperties;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -29,20 +30,22 @@ public class ViewNameResolver {
|
||||||
public Mono<String> resolveViewNameOrDefault(ServerRequest request, String name,
|
public Mono<String> resolveViewNameOrDefault(ServerRequest request, String name,
|
||||||
String defaultName) {
|
String defaultName) {
|
||||||
if (StringUtils.isBlank(name)) {
|
if (StringUtils.isBlank(name)) {
|
||||||
return Mono.just(defaultName);
|
return Mono.justOrEmpty(defaultName);
|
||||||
}
|
}
|
||||||
final String nameToUse = processName(name);
|
return themeResolver.getTheme(request.exchange())
|
||||||
Locale locale = LocaleContextHolder.getLocale(request.exchange().getLocaleContext());
|
.mapNotNull(themeContext -> {
|
||||||
return haloViewResolver.resolveViewName(nameToUse, locale)
|
String templateResourceName = computeResourceName(name);
|
||||||
.map(view -> nameToUse)
|
var resourcePath = themeContext.getPath()
|
||||||
.switchIfEmpty(Mono.just(defaultName));
|
.resolve(TEMPLATES)
|
||||||
|
.resolve(templateResourceName);
|
||||||
|
return Files.exists(resourcePath) ? name : defaultName;
|
||||||
|
})
|
||||||
|
.switchIfEmpty(Mono.justOrEmpty(defaultName));
|
||||||
}
|
}
|
||||||
|
|
||||||
String processName(String name) {
|
String computeResourceName(String name) {
|
||||||
String nameToLookup = name;
|
Assert.notNull(name, "Name must not be null");
|
||||||
if (StringUtils.endsWith(name, thymeleafProperties.getSuffix())) {
|
return StringUtils.endsWith(name, thymeleafProperties.getSuffix())
|
||||||
nameToLookup = StringUtils.substringBeforeLast(name, thymeleafProperties.getSuffix());
|
? name : name + thymeleafProperties.getSuffix();
|
||||||
}
|
|
||||||
return nameToLookup;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,15 +1,19 @@
|
||||||
package run.halo.app.theme.router;
|
package run.halo.app.theme.router;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
import static org.mockito.ArgumentMatchers.eq;
|
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.net.URISyntaxException;
|
import java.net.URISyntaxException;
|
||||||
|
import java.nio.file.Files;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.junit.jupiter.api.io.TempDir;
|
||||||
import org.mockito.InjectMocks;
|
import org.mockito.InjectMocks;
|
||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
import org.mockito.Mockito;
|
import org.mockito.Mockito;
|
||||||
|
@ -17,11 +21,11 @@ import org.springframework.boot.autoconfigure.thymeleaf.ThymeleafProperties;
|
||||||
import org.springframework.http.HttpMethod;
|
import org.springframework.http.HttpMethod;
|
||||||
import org.springframework.mock.web.reactive.function.server.MockServerRequest;
|
import org.springframework.mock.web.reactive.function.server.MockServerRequest;
|
||||||
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||||
import org.springframework.web.reactive.result.view.View;
|
|
||||||
import org.springframework.web.server.ServerWebExchange;
|
import org.springframework.web.server.ServerWebExchange;
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
import reactor.test.StepVerifier;
|
import reactor.test.StepVerifier;
|
||||||
import run.halo.app.theme.HaloViewResolver;
|
import run.halo.app.theme.ThemeContext;
|
||||||
|
import run.halo.app.theme.ThemeResolver;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests for {@link ViewNameResolver}.
|
* Tests for {@link ViewNameResolver}.
|
||||||
|
@ -33,7 +37,7 @@ import run.halo.app.theme.HaloViewResolver;
|
||||||
class ViewNameResolverTest {
|
class ViewNameResolverTest {
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
private HaloViewResolver haloViewResolver;
|
private ThemeResolver themeResolver;
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
private ThymeleafProperties thymeleafProperties;
|
private ThymeleafProperties thymeleafProperties;
|
||||||
|
@ -41,17 +45,27 @@ class ViewNameResolverTest {
|
||||||
@InjectMocks
|
@InjectMocks
|
||||||
private ViewNameResolver viewNameResolver;
|
private ViewNameResolver viewNameResolver;
|
||||||
|
|
||||||
|
@TempDir
|
||||||
|
private File themePath;
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
void setUp() {
|
void setUp() throws IOException {
|
||||||
when(thymeleafProperties.getSuffix()).thenReturn(ThymeleafProperties.DEFAULT_SUFFIX);
|
when(thymeleafProperties.getSuffix()).thenReturn(ThymeleafProperties.DEFAULT_SUFFIX);
|
||||||
|
|
||||||
when(haloViewResolver.resolveViewName(eq("post_news"), any()))
|
var templatesPath = themePath.toPath().resolve("templates");
|
||||||
.thenReturn(Mono.just(Mockito.mock(View.class)));
|
if (!Files.exists(templatesPath)) {
|
||||||
when(haloViewResolver.resolveViewName(eq("post_docs"), any()))
|
Files.createDirectory(templatesPath);
|
||||||
.thenReturn(Mono.just(new EmptyView()));
|
}
|
||||||
|
Files.createFile(templatesPath.resolve("post_news.html"));
|
||||||
|
Files.createFile(templatesPath.resolve("post_docs.html"));
|
||||||
|
|
||||||
when(haloViewResolver.resolveViewName(eq("post_nothing"), any()))
|
when(themeResolver.getTheme(any()))
|
||||||
.thenReturn(Mono.empty());
|
.thenReturn(Mono.fromSupplier(() -> ThemeContext.builder()
|
||||||
|
.name("fake-theme")
|
||||||
|
.path(themePath.toPath())
|
||||||
|
.active(true)
|
||||||
|
.build())
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -71,7 +85,7 @@ class ViewNameResolverTest {
|
||||||
String viewName = "post_docs" + thymeleafProperties.getSuffix();
|
String viewName = "post_docs" + thymeleafProperties.getSuffix();
|
||||||
viewNameResolver.resolveViewNameOrDefault(request, viewName, "post")
|
viewNameResolver.resolveViewNameOrDefault(request, viewName, "post")
|
||||||
.as(StepVerifier::create)
|
.as(StepVerifier::create)
|
||||||
.expectNext("post_docs")
|
.expectNext(viewName)
|
||||||
.verifyComplete();
|
.verifyComplete();
|
||||||
|
|
||||||
viewNameResolver.resolveViewNameOrDefault(request, "post_nothing", "post")
|
viewNameResolver.resolveViewNameOrDefault(request, "post_nothing", "post")
|
||||||
|
@ -82,11 +96,17 @@ class ViewNameResolverTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void processName() {
|
void processName() {
|
||||||
assertThat(viewNameResolver.processName("post_news")).isEqualTo("post_news");
|
var suffix = thymeleafProperties.getSuffix();
|
||||||
assertThat(viewNameResolver.processName("post_news" + thymeleafProperties.getSuffix()))
|
assertThat(viewNameResolver.computeResourceName("post_news"))
|
||||||
.isEqualTo("post_news");
|
.isEqualTo("post_news" + suffix);
|
||||||
assertThat(viewNameResolver.processName("post_news.test"))
|
assertThat(
|
||||||
.isEqualTo("post_news.test");
|
viewNameResolver.computeResourceName("post_news" + suffix))
|
||||||
assertThat(viewNameResolver.processName(null)).isNull();
|
.isEqualTo("post_news" + suffix);
|
||||||
|
assertThat(viewNameResolver.computeResourceName("post_news.test"))
|
||||||
|
.isEqualTo("post_news.test" + suffix);
|
||||||
|
|
||||||
|
assertThatThrownBy(() -> viewNameResolver.computeResourceName(null))
|
||||||
|
.isInstanceOf(IllegalArgumentException.class)
|
||||||
|
.hasMessage("Name must not be null");
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
Reference in New Issue