Add keep-raw option to decide how to render post (#1668)

* feat: Content add front-end rendering options and refactor post params

* feat: add default value for serverSidemarkdownRender

* refactor: content save api

* fix: generate summary

* refactor: remove useless server-side markdown render code

* refactor: allow the formatContent to be empty

* refactor: Rename serverSideMarkdownRender to keepRaw

* refactor: Rename test case
pull/1670/head
guqing 2022-02-21 22:50:35 +08:00 committed by GitHub
parent 923eb17577
commit d889a08833
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 315 additions and 174 deletions

View File

@ -159,9 +159,11 @@ public class PostController {
public BasePostDetailDTO updateDraftBy(
@PathVariable("postId") Integer postId,
@RequestBody PostContentParam contentParam) {
Post postToUse = postService.getById(postId);
String formattedContent = contentParam.decideContentBy(postToUse.getEditorType());
// Update draft content
Post post = postService.updateDraftContent(contentParam.getContent(),
contentParam.getContent(), postId);
Post post = postService.updateDraftContent(formattedContent,
contentParam.getOriginalContent(), postId);
return postService.convertToDetail(post);
}

View File

@ -126,9 +126,12 @@ public class SheetController {
public BasePostDetailDTO updateDraftBy(
@PathVariable("sheetId") Integer sheetId,
@RequestBody PostContentParam contentParam) {
Sheet sheetToUse = sheetService.getById(sheetId);
String formattedContent = contentParam.decideContentBy(sheetToUse.getEditorType());
// Update draft content
Sheet sheet = sheetService.updateDraftContent(contentParam.getContent(),
contentParam.getContent(), sheetId);
Sheet sheet = sheetService.updateDraftContent(formattedContent,
contentParam.getOriginalContent(), sheetId);
return sheetService.convertToDetail(sheet);
}

View File

@ -10,7 +10,6 @@ import run.halo.app.model.entity.Content;
import run.halo.app.model.entity.Content.PatchedContent;
import run.halo.app.model.entity.Sheet;
import run.halo.app.model.entity.SheetMeta;
import run.halo.app.model.enums.PostEditorType;
import run.halo.app.model.enums.PostStatus;
import run.halo.app.model.support.HaloConst;
import run.halo.app.model.vo.SheetDetailVO;
@ -18,7 +17,6 @@ import run.halo.app.service.OptionService;
import run.halo.app.service.SheetMetaService;
import run.halo.app.service.SheetService;
import run.halo.app.service.ThemeService;
import run.halo.app.utils.MarkdownUtils;
/**
* Sheet model.
@ -75,12 +73,6 @@ public class SheetModel {
}
// render markdown to html when preview sheet
PatchedContent sheetContent = sheetService.getLatestContentById(sheet.getId());
if (sheet.getEditorType().equals(PostEditorType.MARKDOWN)) {
sheetContent.setContent(
MarkdownUtils.renderHtml(sheetContent.getOriginalContent()));
} else {
sheetContent.setContent(sheetContent.getOriginalContent());
}
sheet.setContent(sheetContent);
}

View File

@ -0,0 +1,78 @@
package run.halo.app.model.params;
import java.util.Date;
import java.util.Objects;
import javax.validation.constraints.Min;
import javax.validation.constraints.Size;
import lombok.Data;
import org.springframework.util.Assert;
import run.halo.app.model.entity.BasePost;
import run.halo.app.model.entity.Content;
import run.halo.app.model.enums.PostEditorType;
import run.halo.app.model.enums.PostStatus;
import run.halo.app.service.impl.BasePostServiceImpl;
import run.halo.app.utils.MarkdownUtils;
/**
* @author guqing
* @date 2022-02-21
*/
@Data
public abstract class BasePostParam {
protected String title;
protected PostStatus status = PostStatus.DRAFT;
protected String slug;
protected String password;
protected PostEditorType editorType;
protected String content;
protected String originalContent;
protected String summary;
@Size(max = 1023, message = "封面图链接的字符长度不能超过 {max}")
protected String thumbnail;
protected Boolean disallowComment = false;
@Size(max = 255, message = "模版字符长度不能超过 {max}")
protected String template;
@Min(value = 0, message = "排序字段值不能小于 {value}")
protected Integer topPriority = 0;
protected Date createTime;
protected String metaKeywords;
protected String metaDescription;
/**
* if {@code true}, it means is that do not let the back-end render the original content
* because the content has been rendered, and you only need to store the original content.
*/
protected Boolean keepRaw = false;
protected <T extends BasePost> void populateContent(T post) {
Assert.notNull(post, "The post must not be null.");
Content postContent = new Content();
postContent.setOriginalContent(originalContent);
if (Objects.equals(keepRaw, false)
&& PostEditorType.MARKDOWN.equals(editorType)) {
postContent.setContent(MarkdownUtils.renderHtml(originalContent));
} else if (PostEditorType.RICHTEXT.equals(editorType)) {
postContent.setContent(originalContent);
} else {
postContent.setContent(content);
}
post.setContent(Content.PatchedContent.of(postContent));
}
}

View File

@ -1,6 +1,10 @@
package run.halo.app.model.params;
import java.util.Objects;
import lombok.Data;
import org.apache.commons.lang3.StringUtils;
import run.halo.app.model.enums.PostEditorType;
import run.halo.app.utils.MarkdownUtils;
/**
* Post content param.
@ -9,5 +13,35 @@ import lombok.Data;
*/
@Data
public class PostContentParam {
private String content;
private String originalContent;
/**
* if {@code true}, it means is that do not let the back-end render the original content
* because the content has been rendered, and you only need to store the original content.
* otherwise, need server-side rendering.
*/
private Boolean keepRaw = false;
/**
* Decide on post content based on {@link PostEditorType} and serverSideMarkdownRender.
*
* @param editorType edit type to use
* @return formatted content of post.
*/
public String decideContentBy(PostEditorType editorType) {
String originalContentToUse = StringUtils.defaultString(originalContent, "");
String result;
if (Objects.equals(keepRaw, false)
&& PostEditorType.MARKDOWN.equals(editorType)) {
result = MarkdownUtils.renderHtml(originalContentToUse);
} else if (PostEditorType.RICHTEXT.equals(editorType)) {
result = originalContentToUse;
} else {
result = this.content;
}
return result;
}
}

View File

@ -1,22 +1,17 @@
package run.halo.app.model.params;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.apache.commons.lang3.StringUtils;
import org.springframework.util.CollectionUtils;
import run.halo.app.model.dto.base.InputConverter;
import run.halo.app.model.entity.Content;
import run.halo.app.model.entity.Content.PatchedContent;
import run.halo.app.model.entity.Post;
import run.halo.app.model.entity.PostMeta;
import run.halo.app.model.enums.PostEditorType;
import run.halo.app.model.enums.PostStatus;
import run.halo.app.utils.MarkdownUtils;
import run.halo.app.utils.SlugUtils;
/**
@ -28,42 +23,8 @@ import run.halo.app.utils.SlugUtils;
* @date 2019-03-21
*/
@Data
public class PostParam implements InputConverter<Post> {
@NotBlank(message = "文章标题不能为空")
@Size(max = 100, message = "文章标题的字符长度不能超过 {max}")
private String title;
private PostStatus status = PostStatus.DRAFT;
@Size(max = 255, message = "文章别名的字符长度不能超过 {max}")
private String slug;
private PostEditorType editorType;
private String originalContent;
private String summary;
@Size(max = 1023, message = "封面图链接的字符长度不能超过 {max}")
private String thumbnail;
private Boolean disallowComment = false;
@Size(max = 255, message = "文章密码的字符长度不能超过 {max}")
private String password;
@Size(max = 255, message = "Length of template must not be more than {max}")
private String template;
@Min(value = 0, message = "Post top priority must not be less than {value}")
private Integer topPriority = 0;
private Date createTime;
private String metaKeywords;
private String metaDescription;
@EqualsAndHashCode(callSuper = true)
public class PostParam extends BasePostParam implements InputConverter<Post> {
private Set<Integer> tagIds;
@ -71,6 +32,38 @@ public class PostParam implements InputConverter<Post> {
private Set<PostMetaParam> metas;
@Override
@NotBlank(message = "文章标题不能为空")
@Size(max = 100, message = "文章标题的字符长度不能超过 {max}")
public String getTitle() {
return super.getTitle();
}
@Override
@Size(max = 255, message = "文章别名的字符长度不能超过 {max}")
public String getSlug() {
return super.getSlug();
}
@Override
@Size(max = 255, message = "文章密码的字符长度不能超过 {max}")
public String getPassword() {
return super.getPassword();
}
public Set<PostMeta> getPostMetas() {
Set<PostMeta> postMetaSet = new HashSet<>();
if (CollectionUtils.isEmpty(metas)) {
return postMetaSet;
}
for (PostMetaParam postMetaParam : metas) {
PostMeta postMeta = postMetaParam.convertTo();
postMetaSet.add(postMeta);
}
return postMetaSet;
}
@Override
public Post convertTo() {
slug = StringUtils.isBlank(slug) ? SlugUtils.slug(title) : SlugUtils.slug(slug);
@ -102,28 +95,4 @@ public class PostParam implements InputConverter<Post> {
populateContent(post);
InputConverter.super.update(post);
}
public Set<PostMeta> getPostMetas() {
Set<PostMeta> postMetaSet = new HashSet<>();
if (CollectionUtils.isEmpty(metas)) {
return postMetaSet;
}
for (PostMetaParam postMetaParam : metas) {
PostMeta postMeta = postMetaParam.convertTo();
postMetaSet.add(postMeta);
}
return postMetaSet;
}
private void populateContent(Post post) {
Content postContent = new Content();
if (PostEditorType.MARKDOWN.equals(editorType)) {
postContent.setContent(MarkdownUtils.renderHtml(originalContent));
} else {
postContent.setContent(postContent.getOriginalContent());
}
postContent.setOriginalContent(originalContent);
post.setContent(PatchedContent.of(postContent));
}
}

View File

@ -1,22 +1,17 @@
package run.halo.app.model.params;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.apache.commons.lang3.StringUtils;
import org.springframework.util.CollectionUtils;
import run.halo.app.model.dto.base.InputConverter;
import run.halo.app.model.entity.Content;
import run.halo.app.model.entity.Content.PatchedContent;
import run.halo.app.model.entity.Sheet;
import run.halo.app.model.entity.SheetMeta;
import run.halo.app.model.enums.PostEditorType;
import run.halo.app.model.enums.PostStatus;
import run.halo.app.utils.MarkdownUtils;
import run.halo.app.utils.SlugUtils;
/**
@ -28,45 +23,43 @@ import run.halo.app.utils.SlugUtils;
* @date 2019-4-24
*/
@Data
public class SheetParam implements InputConverter<Sheet> {
@NotBlank(message = "页面标题不能为空")
@Size(max = 100, message = "页面标题的字符长度不能超过 {max}")
private String title;
private PostStatus status = PostStatus.DRAFT;
@Size(max = 255, message = "页面别名的字符长度不能超过 {max}")
private String slug;
private PostEditorType editorType;
private String originalContent;
private String summary;
@Size(max = 255, message = "封面图链接的字符长度不能超过 {max}")
private String thumbnail;
private Boolean disallowComment = false;
@Size(max = 255, message = "页面密码的字符长度不能超过 {max}")
private String password;
@Size(max = 255, message = "Length of template must not be more than {max}")
private String template;
@Min(value = 0, message = "Post top priority must not be less than {value}")
private Integer topPriority = 0;
private Date createTime;
private String metaKeywords;
private String metaDescription;
@EqualsAndHashCode(callSuper = true)
public class SheetParam extends BasePostParam implements InputConverter<Sheet> {
private Set<SheetMetaParam> metas;
@Override
@Size(max = 255, message = "页面别名的字符长度不能超过 {max}")
public String getSlug() {
return super.getSlug();
}
@Override
@NotBlank(message = "页面标题不能为空")
@Size(max = 100, message = "页面标题的字符长度不能超过 {max}")
public String getTitle() {
return super.getTitle();
}
@Override
@Size(max = 255, message = "页面密码的字符长度不能超过 {max}")
public String getPassword() {
return super.getPassword();
}
public Set<SheetMeta> getSheetMetas() {
Set<SheetMeta> sheetMetasSet = new HashSet<>();
if (CollectionUtils.isEmpty(metas)) {
return sheetMetasSet;
}
for (SheetMetaParam sheetMetaParam : metas) {
SheetMeta sheetMeta = sheetMetaParam.convertTo();
sheetMetasSet.add(sheetMeta);
}
return sheetMetasSet;
}
@Override
public Sheet convertTo() {
slug = StringUtils.isBlank(slug) ? SlugUtils.slug(title) : SlugUtils.slug(slug);
@ -98,28 +91,4 @@ public class SheetParam implements InputConverter<Sheet> {
populateContent(sheet);
InputConverter.super.update(sheet);
}
public Set<SheetMeta> getSheetMetas() {
Set<SheetMeta> sheetMetasSet = new HashSet<>();
if (CollectionUtils.isEmpty(metas)) {
return sheetMetasSet;
}
for (SheetMetaParam sheetMetaParam : metas) {
SheetMeta sheetMeta = sheetMetaParam.convertTo();
sheetMetasSet.add(sheetMeta);
}
return sheetMetasSet;
}
private void populateContent(Sheet sheet) {
Content sheetContent = new Content();
if (PostEditorType.MARKDOWN.equals(editorType)) {
sheetContent.setContent(MarkdownUtils.renderHtml(originalContent));
} else {
sheetContent.setContent(sheetContent.getOriginalContent());
}
sheetContent.setOriginalContent(originalContent);
sheet.setContent(PatchedContent.of(sheetContent));
}
}

View File

@ -330,8 +330,8 @@ public interface BasePostService<POST extends BasePost> extends CrudService<POST
/**
* Generate description.
*
* @param content html content must not be null.
* @param content html content.
* @return description
*/
String generateDescription(@NonNull String content);
String generateDescription(@Nullable String content);
}

View File

@ -16,6 +16,7 @@ import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
@ -29,7 +30,6 @@ import run.halo.app.model.dto.post.BasePostSimpleDTO;
import run.halo.app.model.entity.BasePost;
import run.halo.app.model.entity.Content;
import run.halo.app.model.entity.Content.PatchedContent;
import run.halo.app.model.enums.PostEditorType;
import run.halo.app.model.enums.PostStatus;
import run.halo.app.model.properties.PostProperties;
import run.halo.app.repository.base.BasePostRepository;
@ -40,7 +40,6 @@ import run.halo.app.service.base.AbstractCrudService;
import run.halo.app.service.base.BasePostService;
import run.halo.app.utils.DateUtils;
import run.halo.app.utils.HaloUtils;
import run.halo.app.utils.MarkdownUtils;
import run.halo.app.utils.ServiceUtils;
/**
@ -301,23 +300,10 @@ public abstract class BasePostServiceImpl<POST extends BasePost>
@Transactional
public POST createOrUpdateBy(POST post) {
Assert.notNull(post, "Post must not be null");
PostStatus postStatus = post.getStatus();
PatchedContent postContent = post.getContent();
String originalContent = postContent.getOriginalContent();
if (originalContent != null) {
// CS304 issue link : https://github.com/halo-dev/halo/issues/1224
// Render content and set word count
if (post.getEditorType().equals(PostEditorType.MARKDOWN)) {
postContent.setContent(MarkdownUtils.renderHtml(originalContent));
post.setWordCount(htmlFormatWordCount(postContent.getContent()));
} else {
postContent.setContent(originalContent);
post.setWordCount(htmlFormatWordCount(originalContent));
}
post.setContent(postContent);
}
// word count stat
post.setWordCount(htmlFormatWordCount(postContent.getContent()));
post.setContent(postContent);
POST savedPost;
// Create or update post
@ -441,14 +427,9 @@ public abstract class BasePostServiceImpl<POST extends BasePost>
originalContent = "";
}
POST post = getById(postId);
if (PostEditorType.MARKDOWN.equals(post.getEditorType())) {
content = MarkdownUtils.renderHtml(originalContent);
} else {
content = originalContent;
}
contentService.createOrUpdateDraftBy(postId, content, originalContent);
POST post = getById(postId);
post.setContent(getLatestContentById(postId));
return post;
@ -496,8 +477,10 @@ public abstract class BasePostServiceImpl<POST extends BasePost>
}
@Override
public String generateDescription(String content) {
Assert.notNull(content, "html content must not be null");
public String generateDescription(@Nullable String content) {
if (StringUtils.isBlank(content)) {
return StringUtils.EMPTY;
}
String text = HaloUtils.cleanHtmlTag(content);
@ -558,8 +541,10 @@ public abstract class BasePostServiceImpl<POST extends BasePost>
}
@NonNull
protected String generateSummary(@NonNull String htmlContent) {
Assert.notNull(htmlContent, "html content must not be null");
protected String generateSummary(@Nullable String htmlContent) {
if (StringUtils.isBlank(htmlContent)) {
return StringUtils.EMPTY;
}
String text = HaloUtils.cleanHtmlTag(htmlContent);

View File

@ -12,8 +12,10 @@ import com.github.difflib.patch.InsertDelta;
import com.github.difflib.patch.Patch;
import com.github.difflib.patch.PatchFailedException;
import com.google.common.base.Splitter;
import java.util.Collections;
import java.util.List;
import lombok.Data;
import org.apache.commons.lang3.StringUtils;
/**
* Content patch utilities.
@ -76,6 +78,9 @@ public class PatchUtils {
}
public static List<String> breakLine(String content) {
if (StringUtils.isBlank(content)) {
return Collections.emptyList();
}
return lineSplitter.splitToList(content);
}

View File

@ -0,0 +1,104 @@
package run.halo.app.model.params;
import static org.assertj.core.api.Assertions.assertThat;
import java.util.Set;
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import run.halo.app.model.entity.Post;
import run.halo.app.model.enums.PostEditorType;
import run.halo.app.model.enums.PostStatus;
/**
* Test for {@link PostParam}.
*
* @author guqing
* @date 2022-02-21
*/
public class PostParamTest {
private Validator validator;
@BeforeEach
public void setUp() {
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
validator = factory.getValidator();
}
@Test
public void validationTest() {
PostParam postParam = new PostParam();
postParam.setTitle("Title");
postParam.setSlug("Slug");
postParam.setPassword("123");
postParam.setTopPriority(-1);
Set<ConstraintViolation<PostParam>> validate = validator.validate(postParam);
assertThat(validate).isNotNull();
assertThat(validate).hasSize(1);
assertThat(validate.iterator().next().getMessage()).isEqualTo("排序字段值不能小于 0");
}
@Test
public void convertToTest() {
PostParam postParam = new PostParam();
postParam.setTitle("Title");
postParam.setSlug("Slug");
postParam.setPassword("123");
postParam.setStatus(PostStatus.INTIMATE);
postParam.setMetaDescription("Meta description");
postParam.setTagIds(Set.of(1, 2, 3));
Post post = postParam.convertTo();
assertThat(post).isNotNull();
assertThat(post.getTitle()).isEqualTo(postParam.getTitle());
assertThat(post.getSlug()).isEqualTo(postParam.getSlug());
assertThat(post.getPassword()).isEqualTo(postParam.getPassword());
}
@Test
public void shouldServerSideRender() {
PostParam postParam = new PostParam();
postParam.setSlug("slug");
// server side rendering
postParam.setKeepRaw(false);
postParam.setOriginalContent("两个黄鹂鸣翠柳,一行白鹭上青天。");
Post post = postParam.convertTo();
assertThat(post).isNotNull();
assertThat(post.getContent().getContent()).isEqualTo("<p>两个黄鹂鸣翠柳,一行白鹭上青天。</p>\n");
}
@Test
public void shouldNotServerSideRenderTest() {
PostParam postParam = new PostParam();
postParam.setSlug("slug");
// server side render
postParam.setKeepRaw(false);
postParam.setOriginalContent("两个黄鹂鸣翠柳,一行白鹭上青天。");
// The keepRaw is true value and edit type is equals to markdown
postParam.setKeepRaw(true);
postParam.setContent("front-end rendering");
Post post1 = postParam.convertTo();
assertThat(post1).isNotNull();
assertThat(post1.getContent().getContent()).isEqualTo(postParam.getContent());
// Edit type not equals to markdown and keepRaw is true
postParam.setKeepRaw(true);
postParam.setEditorType(PostEditorType.RICHTEXT);
Post post2 = postParam.convertTo();
assertThat(post2).isNotNull();
assertThat(post2.getContent().getContent()).isEqualTo(postParam.getOriginalContent());
// Edit type not equals to markdown but want to let server rendering
postParam.setKeepRaw(false);
Post post3 = postParam.convertTo();
assertThat(post3).isNotNull();
assertThat(post3.getContent().getContent()).isEqualTo(postParam.getOriginalContent());
}
}