feat: add favicon head processor (#2582)

#### What type of PR is this?
/kind feature
/area core
/milestone 2.0

#### What this PR does / why we need it:
模板渲染自动填充 Favicon 到 head 标签
#### Which issue(s) this PR fixes:

Fixes #2581

#### Special notes for your reviewer:
how to test it?
在 console 系统设置 -> 基础设置 填写 Favicon 后,到主题端能到看它

/cc @halo-dev/sig-halo 
#### Does this PR introduce a user-facing change?

```release-note
支持设置 Favicon
```
pull/2574/head^2
guqing 2022-10-18 10:22:09 +08:00 committed by GitHub
parent 2527eb42e2
commit a3448adee2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 58 additions and 0 deletions

View File

@ -0,0 +1,46 @@
package run.halo.app.theme.dialect;
import lombok.AllArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import org.thymeleaf.context.ITemplateContext;
import org.thymeleaf.model.IModel;
import org.thymeleaf.model.IModelFactory;
import org.thymeleaf.processor.element.IElementModelStructureHandler;
import reactor.core.publisher.Mono;
import run.halo.app.infra.SystemConfigurableEnvironmentFetcher;
import run.halo.app.infra.SystemSetting;
/**
* Theme template <code>head</code> tag snippet injection processor for favicon.
*
* @author guqing
* @since 2.0.0
*/
@Component
@AllArgsConstructor
public class DefaultFaviconHeadProcessor implements TemplateHeadProcessor {
private final SystemConfigurableEnvironmentFetcher fetcher;
@Override
public Mono<Void> process(ITemplateContext context, IModel model,
IElementModelStructureHandler structureHandler) {
return fetchBasicSetting()
.filter(basic -> StringUtils.isNotBlank(basic.getFavicon()))
.map(basic -> {
IModelFactory modelFactory = context.getModelFactory();
model.add(modelFactory.createText(faviconSnippet(basic.getFavicon())));
return model;
})
.then();
}
private String faviconSnippet(String favicon) {
return String.format("<link rel=\"icon\" href=\"%s\" />\n", favicon);
}
private Mono<SystemSetting.Basic> fetchBasicSetting() {
return fetcher.fetch(SystemSetting.Basic.GROUP, SystemSetting.Basic.class);
}
}

View File

@ -69,6 +69,7 @@ class HaloProcessorDialectTest {
Map<String, TemplateHeadProcessor> map = new HashMap<>();
map.put("postTemplateHeadProcessor", new PostTemplateHeadProcessor(postFinder));
map.put("templateGlobalHeadProcessor", new TemplateGlobalHeadProcessor(fetcher));
map.put("faviconHeadProcessor", new DefaultFaviconHeadProcessor(fetcher));
lenient().when(applicationContext.getBeansOfType(TemplateHeadProcessor.class))
.thenReturn(map);
@ -85,6 +86,11 @@ class HaloProcessorDialectTest {
@Test
void globalHeadAndFooterProcessors() {
SystemSetting.Basic basic = new SystemSetting.Basic();
basic.setFavicon("favicon.ico");
when(fetcher.fetch(eq(SystemSetting.Basic.GROUP),
eq(SystemSetting.Basic.class))).thenReturn(Mono.just(basic));
Context context = getContext();
String result = templateEngine.process("index", context);
@ -95,6 +101,7 @@ class HaloProcessorDialectTest {
<meta charset="UTF-8" />
<title>Index</title>
<meta name="global-head-test" content="test" />
<link rel="icon" href="favicon.ico" />
</head>
<body>
<p>index</p>
@ -124,6 +131,11 @@ class HaloProcessorDialectTest {
.metadata(metadata).build();
when(postFinder.getByName(eq("fake-post"))).thenReturn(postVo);
SystemSetting.Basic basic = new SystemSetting.Basic();
basic.setFavicon(null);
when(fetcher.fetch(eq(SystemSetting.Basic.GROUP),
eq(SystemSetting.Basic.class))).thenReturn(Mono.just(basic));
String result = templateEngine.process("post", context);
assertThat(result).isEqualTo("""
<!DOCTYPE html>