refactor: excerpt as the meta description on the page of post and single page (#3745)

#### What type of PR is this?
/kind improvement
/area core
#### What this PR does / why we need it:
将文章摘要作为 meta description 以优化文章页的 SEO

how to test it?
查看文章页和自定义页面的 head 中是否具有 `<meta name="description" content="文章摘要"/>` 标签

#### Which issue(s) this PR fixes:
Fixes #2682

#### Does this PR introduce a user-facing change?

```release-note
将文章摘要作为 meta description 以优化文章页的 SEO
```
pull/3714/head^2
guqing 2023-04-12 20:50:31 +08:00 committed by GitHub
parent e4338c111e
commit d7bfbef149
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 132 additions and 2 deletions

View File

@ -1,5 +1,10 @@
package run.halo.app.theme.dialect;
import static org.apache.commons.lang3.ObjectUtils.defaultIfNull;
import static org.apache.commons.lang3.StringUtils.defaultIfBlank;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import lombok.AllArgsConstructor;
@ -37,10 +42,20 @@ public class ContentTemplateHeadProcessor implements TemplateHeadProcessor {
Mono<List<Map<String, String>>> htmlMetasMono = Mono.empty();
if (isPostTemplate(context)) {
htmlMetasMono = nameMono.flatMap(postFinder::getByName)
.map(post -> post.getSpec().getHtmlMetas());
.map(post -> {
List<Map<String, String>> htmlMetas = post.getSpec().getHtmlMetas();
String excerpt =
post.getStatus() == null ? null : post.getStatus().getExcerpt();
return excerptToMetaDescriptionIfAbsent(htmlMetas, excerpt);
});
} else if (isPageTemplate(context)) {
htmlMetasMono = nameMono.flatMap(singlePageFinder::getByName)
.map(page -> page.getSpec().getHtmlMetas());
.map(page -> {
List<Map<String, String>> htmlMetas = page.getSpec().getHtmlMetas();
String excerpt =
page.getStatus() == null ? null : page.getStatus().getExcerpt();
return excerptToMetaDescriptionIfAbsent(htmlMetas, excerpt);
});
}
return htmlMetasMono
@ -52,6 +67,32 @@ public class ContentTemplateHeadProcessor implements TemplateHeadProcessor {
.then();
}
static List<Map<String, String>> excerptToMetaDescriptionIfAbsent(
List<Map<String, String>> htmlMetas,
String excerpt) {
final String excerptNullSafe = StringUtils.defaultString(excerpt);
List<Map<String, String>> metas = new ArrayList<>(defaultIfNull(htmlMetas, List.of()));
metas.stream()
.filter(map -> Meta.DESCRIPTION.equals(map.get(Meta.NAME)))
.distinct()
.findFirst()
.ifPresentOrElse(map ->
map.put(Meta.CONTENT, defaultIfBlank(map.get(Meta.CONTENT), excerptNullSafe)),
() -> {
Map<String, String> map = new HashMap<>();
map.put(Meta.NAME, Meta.DESCRIPTION);
map.put(Meta.CONTENT, excerptNullSafe);
metas.add(map);
});
return metas;
}
interface Meta {
String DESCRIPTION = "description";
String NAME = "name";
String CONTENT = "content";
}
private String headMetaBuilder(List<Map<String, String>> htmlMetas) {
if (htmlMetas == null) {
return StringUtils.EMPTY;

View File

@ -0,0 +1,88 @@
package run.halo.app.theme.dialect;
import static org.assertj.core.api.Assertions.assertThat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
/**
* Tests for {@link ContentTemplateHeadProcessor}.
*
* @author guqing
* @since 2.5.0
*/
class ContentTemplateHeadProcessorTest {
@Nested
class ExcerptToMetaDescriptionTest {
@Test
void toMetaWhenExcerptIsNull() {
List<Map<String, String>> htmlMetas = new ArrayList<>();
htmlMetas.add(createMetaMap("keywords", "test"));
var result = ContentTemplateHeadProcessor.excerptToMetaDescriptionIfAbsent(htmlMetas,
null);
assertThat(result).hasSize(2);
assertThat(result.get(0)).containsEntry("name", "keywords");
assertThat(result.get(1)).containsEntry("name", "description")
.containsEntry("content", "");
}
@Test
void toMetaWhenWhenHtmlMetaIsNull() {
var result = ContentTemplateHeadProcessor.excerptToMetaDescriptionIfAbsent(null,
null);
assertThat(result).hasSize(1);
assertThat(result.get(0)).containsEntry("name", "description")
.containsEntry("content", "");
}
@Test
void toMetaWhenWhenExcerptNotEmpty() {
List<Map<String, String>> htmlMetas = new ArrayList<>();
htmlMetas.add(createMetaMap("keywords", "test"));
var result = ContentTemplateHeadProcessor.excerptToMetaDescriptionIfAbsent(htmlMetas,
"test excerpt");
assertThat(result).hasSize(2);
assertThat(result.get(0)).containsEntry("name", "keywords");
assertThat(result.get(1)).containsEntry("name", "description")
.containsEntry("content", "test excerpt");
}
@Test
void toMetaWhenWhenDescriptionExistsAndEmpty() {
List<Map<String, String>> htmlMetas = new ArrayList<>();
htmlMetas.add(createMetaMap("keywords", "test"));
htmlMetas.add(createMetaMap("description", ""));
var result = ContentTemplateHeadProcessor.excerptToMetaDescriptionIfAbsent(htmlMetas,
"test excerpt");
assertThat(result).hasSize(2);
assertThat(result.get(0)).containsEntry("name", "keywords");
assertThat(result.get(1)).containsEntry("name", "description")
.containsEntry("content", "test excerpt");
}
@Test
void toMetaWhenWhenDescriptionExistsAndNotEmpty() {
List<Map<String, String>> htmlMetas = new ArrayList<>();
htmlMetas.add(createMetaMap("keywords", "test"));
htmlMetas.add(createMetaMap("description", "test description"));
var result = ContentTemplateHeadProcessor.excerptToMetaDescriptionIfAbsent(htmlMetas,
"test excerpt");
assertThat(result).hasSize(2);
assertThat(result.get(0)).containsEntry("name", "keywords");
assertThat(result.get(1)).containsEntry("name", "description")
.containsEntry("content", "test description");
}
Map<String, String> createMetaMap(String nameValue, String contentValue) {
Map<String, String> metaMap = new HashMap<>();
metaMap.put("name", nameValue);
metaMap.put("content", contentValue);
return metaMap;
}
}
}

View File

@ -169,6 +169,7 @@ class HaloProcessorDialectTest {
<title>Post</title>
<meta content="post-meta-V1" name="post-meta-V1" />
<meta content="post-meta-V2" name="post-meta-V2" />
<meta name="description" content="" />
<meta name="global-head-test" content="test" />
<meta name="content-head-test" content="test" />
</head>