Merge branch 'master' of github.com:halo-dev/halo

pull/1436/head
Ryan Wang 2021-07-21 23:11:59 +08:00
commit 5e656fe06b
22 changed files with 1085 additions and 143 deletions

View File

@ -1,6 +1,34 @@
# CHANGELOG
# 1.4.9
# 1.4.10
## Features
- 编辑器支持脚注语法。halo-dev/halo#1406 halo-dev/halo-admin#341
## Improvements
- 优化文章字数计算。halo-dev/halo#1354
- Content Api 中的获取文章列表支持传入关键字和分类 id 筛选项。halo-dev/halo#1373
- 优化导入 Markdown 时对多级分类的处理。halo-dev/halo#1380
## Security Fixes
- 修复 Freemarker SSTI 漏洞。halo-dev/halo#1402 halo-dev/halo#1427 Thanks @LazyMaple @5wimming
## Bug Fixes
- 修复在分类文章列表可以显示私密文章的问题。halo-dev/halo#1379
- 修复使用后台的小工具数据导出迁移后分类密码消失的问题。halo-dev/halo#1390
- 修复在站点初始化的时候,`全局绝对路径` 选项设置错误的问题。halo-dev/halo#1396
- 修复 Content Api 的文章点赞接口限流没有按照文章 id 做处理的问题。halo-dev/halo#1410
- 修复回收站的文章可以访问的问题。halo-dev/halo#1414
- 修复后台评论回复时输入框无法输入空格的问题。halo-dev/halo-admin#322
- 修复后台菜单管理中菜单项的链接过长会导致挡住操作按钮的问题。halo-dev/halo-admin#328
- 修复后台日志管理中长文本无法换行的问题。halo-dev/halo-admin#330
- 修复后台在登录页面无法通过回车键进行登录的问题。halo-dev/halo-admin#332
# 1.4.9(deprecated)
## Features

View File

@ -29,7 +29,7 @@
下载最新的 Halo 运行包:
```bash
curl -L https://github.com/halo-dev/halo/releases/download/v1.4.9/halo-1.4.9.jar --output halo.jar
curl -L https://github.com/halo-dev/halo/releases/download/v1.4.10/halo-1.4.10.jar --output halo.jar
```
其他地址https://docs.halo.run/install/downloads

View File

@ -6,7 +6,7 @@ plugins {
}
group = "run.halo.app"
version = "1.4.9"
version = "1.4.10"
description = "Halo, An excellent open source blog publishing application."
sourceCompatibility = JavaVersion.VERSION_11
@ -98,7 +98,6 @@ ext {
huaweiObsVersion = "3.19.7"
templateInheritanceVersion = "0.4.RELEASE"
jsoupVersion = "1.13.1"
byteBuddyAgentVersion = "1.10.22"
}
dependencies {
@ -142,15 +141,12 @@ dependencies {
implementation "com.vladsch.flexmark:flexmark-ext-superscript:$flexmarkVersion"
implementation "com.vladsch.flexmark:flexmark-ext-yaml-front-matter:$flexmarkVersion"
implementation "com.vladsch.flexmark:flexmark-ext-gitlab:$flexmarkVersion"
implementation "com.vladsch.flexmark:flexmark-ext-footnotes:$flexmarkVersion"
implementation "kr.pe.kwonnam.freemarker:freemarker-template-inheritance:$templateInheritanceVersion"
implementation "net.coobird:thumbnailator:$thumbnailatorVersion"
implementation "net.sf.image4j:image4j:$image4jVersion"
implementation "org.flywaydb:flyway-core:$flywayVersion"
implementation "com.google.zxing:core:$zxingVersion"
implementation "net.bytebuddy:byte-buddy-agent:$byteBuddyAgentVersion"
implementation "org.iq80.leveldb:leveldb:$levelDbVersion"
runtimeOnly "com.h2database:h2:$h2Version"

View File

@ -194,6 +194,7 @@ public class LocalFileHandler implements FileHandler {
boolean deleteResult = Files.deleteIfExists(thumbnailPath);
if (!deleteResult) {
log.warn("Thumbnail: [{}] may not exist", thumbnailPath.toString());
throw new FileOperationException("附件缩略图 " + thumbnailName + " 删除失败");
}
} catch (IOException e) {
throw new FileOperationException("附件缩略图 " + thumbnailName + " 删除失败", e);

View File

@ -185,6 +185,7 @@ public class QiniuOssFileHandler implements FileHandler {
Response response = bucketManager.delete(bucket, key);
if (!response.isOK()) {
log.warn("附件 " + key + " 从七牛云删除失败");
throw new FileOperationException("附件 " + key + " 从七牛云删除失败");
}
} catch (QiniuException e) {
log.error("Qiniu oss error response: [{}]", e.response);

View File

@ -132,6 +132,7 @@ public class UpOssFileHandler implements FileHandler {
Response result = manager.deleteFile(key, null);
if (!result.isSuccessful()) {
log.warn("附件 " + key + " 从又拍云删除失败");
throw new FileOperationException("附件 " + key + " 从又拍云删除失败");
}
} catch (IOException | UpException e) {
e.printStackTrace();

View File

@ -6,7 +6,6 @@ import com.vladsch.flexmark.ext.emoji.EmojiExtension;
import com.vladsch.flexmark.ext.emoji.EmojiImageType;
import com.vladsch.flexmark.ext.emoji.EmojiShortcutType;
import com.vladsch.flexmark.ext.escaped.character.EscapedCharacterExtension;
import com.vladsch.flexmark.ext.footnotes.FootnoteExtension;
import com.vladsch.flexmark.ext.gfm.strikethrough.StrikethroughExtension;
import com.vladsch.flexmark.ext.gfm.tasklist.TaskListExtension;
import com.vladsch.flexmark.ext.gitlab.GitLabExtension;
@ -30,6 +29,7 @@ import java.util.regex.Pattern;
import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils;
import run.halo.app.model.support.HaloConst;
import run.halo.app.utils.footnotes.FootnoteExtension;
/**
* Markdown utils.
@ -111,8 +111,6 @@ public class MarkdownUtils {
markdown = markdown
.replaceAll(HaloConst.YOUTUBE_VIDEO_REG_PATTERN, HaloConst.YOUTUBE_VIDEO_IFRAME);
}
// footnote render method delegation.
FootnoteNodeRendererInterceptor.doDelegationMethod();
Node document = PARSER.parse(markdown);

View File

@ -0,0 +1,140 @@
package run.halo.app.utils.footnotes;
import com.vladsch.flexmark.ast.LinkRendered;
import com.vladsch.flexmark.util.ast.DelimitedNode;
import com.vladsch.flexmark.util.ast.DoNotDecorate;
import com.vladsch.flexmark.util.ast.Document;
import com.vladsch.flexmark.util.ast.Node;
import com.vladsch.flexmark.util.ast.ReferencingNode;
import com.vladsch.flexmark.util.sequence.BasedSequence;
import org.jetbrains.annotations.NotNull;
import run.halo.app.utils.footnotes.internal.FootnoteRepository;
/**
* A Footnote referencing node
*/
public class Footnote extends Node implements DelimitedNode, DoNotDecorate, LinkRendered,
ReferencingNode<FootnoteRepository, FootnoteBlock> {
protected BasedSequence openingMarker = BasedSequence.NULL;
protected BasedSequence text = BasedSequence.NULL;
protected BasedSequence closingMarker = BasedSequence.NULL;
protected FootnoteBlock footnoteBlock;
public int getReferenceOrdinal() {
return referenceOrdinal;
}
public void setReferenceOrdinal(int referenceOrdinal) {
this.referenceOrdinal = referenceOrdinal;
}
protected int referenceOrdinal;
@NotNull
@Override
public BasedSequence getReference() {
return text;
}
@Override
public FootnoteBlock getReferenceNode(Document document) {
if (footnoteBlock != null || text.isEmpty()) {
return footnoteBlock;
}
footnoteBlock = getFootnoteBlock(FootnoteExtension.FOOTNOTES.get(document));
return footnoteBlock;
}
@Override
public FootnoteBlock getReferenceNode(FootnoteRepository repository) {
if (footnoteBlock != null || text.isEmpty()) {
return footnoteBlock;
}
footnoteBlock = getFootnoteBlock(repository);
return footnoteBlock;
}
@Override
public boolean isDefined() {
return footnoteBlock != null;
}
/**
* @return true if this node will be rendered as text because it depends on a reference which
* is not defined.
*/
@Override
public boolean isTentative() {
return footnoteBlock == null;
}
public FootnoteBlock getFootnoteBlock(FootnoteRepository footnoteRepository) {
return text.isEmpty() ? null : footnoteRepository.get(text.toString());
}
public FootnoteBlock getFootnoteBlock() {
return footnoteBlock;
}
public void setFootnoteBlock(FootnoteBlock footnoteBlock) {
this.footnoteBlock = footnoteBlock;
}
@NotNull
@Override
public BasedSequence[] getSegments() {
return new BasedSequence[] {openingMarker, text, closingMarker};
}
@Override
public void getAstExtra(@NotNull StringBuilder out) {
out.append(" ordinal: ")
.append(footnoteBlock != null ? footnoteBlock.getFootnoteOrdinal() : 0).append(" ");
delimitedSegmentSpanChars(out, openingMarker, text, closingMarker, "text");
}
public Footnote() {
}
public Footnote(BasedSequence chars) {
super(chars);
}
public Footnote(BasedSequence openingMarker, BasedSequence text, BasedSequence closingMarker) {
super(openingMarker
.baseSubSequence(openingMarker.getStartOffset(), closingMarker.getEndOffset()));
this.openingMarker = openingMarker;
this.text = text;
this.closingMarker = closingMarker;
}
@Override
public BasedSequence getOpeningMarker() {
return openingMarker;
}
@Override
public void setOpeningMarker(BasedSequence openingMarker) {
this.openingMarker = openingMarker;
}
@Override
public BasedSequence getText() {
return text;
}
@Override
public void setText(BasedSequence text) {
this.text = text;
}
@Override
public BasedSequence getClosingMarker() {
return closingMarker;
}
@Override
public void setClosingMarker(BasedSequence closingMarker) {
this.closingMarker = closingMarker;
}
}

View File

@ -0,0 +1,171 @@
package run.halo.app.utils.footnotes;
import com.vladsch.flexmark.ast.Paragraph;
import com.vladsch.flexmark.ast.ParagraphItemContainer;
import com.vladsch.flexmark.parser.ListOptions;
import com.vladsch.flexmark.util.ast.Block;
import com.vladsch.flexmark.util.ast.Node;
import com.vladsch.flexmark.util.ast.ReferenceNode;
import com.vladsch.flexmark.util.data.DataHolder;
import com.vladsch.flexmark.util.sequence.BasedSequence;
import com.vladsch.flexmark.util.sequence.SequenceUtils;
import java.util.Objects;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import run.halo.app.utils.footnotes.internal.FootnoteRepository;
/**
* A Footnote definition node containing text and other inline nodes nodes as children.
*/
public class FootnoteBlock extends Block
implements ReferenceNode<FootnoteRepository, FootnoteBlock, Footnote>, ParagraphItemContainer {
protected BasedSequence openingMarker = BasedSequence.NULL;
protected BasedSequence text = BasedSequence.NULL;
protected BasedSequence closingMarker = BasedSequence.NULL;
protected BasedSequence footnote = BasedSequence.NULL;
private int footnoteOrdinal = 0;
private int firstReferenceOffset = Integer.MAX_VALUE;
private int footnoteReferences = 0;
@Override
public int compareTo(FootnoteBlock other) {
return SequenceUtils.compare(text, other.text, true);
}
public int getFootnoteReferences() {
return footnoteReferences;
}
public void setFootnoteReferences(int footnoteReferences) {
this.footnoteReferences = footnoteReferences;
}
@Nullable
@Override
public Footnote getReferencingNode(@NotNull Node node) {
return node instanceof Footnote ? (Footnote) node : null;
}
public int getFirstReferenceOffset() {
return firstReferenceOffset;
}
public void setFirstReferenceOffset(int firstReferenceOffset) {
this.firstReferenceOffset = firstReferenceOffset;
}
public void addFirstReferenceOffset(int firstReferenceOffset) {
if (this.firstReferenceOffset < firstReferenceOffset) {
this.firstReferenceOffset = firstReferenceOffset;
}
}
public boolean isReferenced() {
return this.firstReferenceOffset < Integer.MAX_VALUE;
}
public int getFootnoteOrdinal() {
return footnoteOrdinal;
}
public void setFootnoteOrdinal(int footnoteOrdinal) {
this.footnoteOrdinal = footnoteOrdinal;
}
@Override
public void getAstExtra(@NotNull StringBuilder out) {
out.append(" ordinal: ").append(footnoteOrdinal).append(" ");
segmentSpan(out, openingMarker, "open");
segmentSpan(out, text, "text");
segmentSpan(out, closingMarker, "close");
segmentSpan(out, footnote, "footnote");
}
@NotNull
@Override
public BasedSequence[] getSegments() {
return new BasedSequence[] {openingMarker, text, closingMarker, footnote};
}
public FootnoteBlock() {
}
public FootnoteBlock(BasedSequence chars) {
super(chars);
}
public BasedSequence getOpeningMarker() {
return openingMarker;
}
public void setOpeningMarker(BasedSequence openingMarker) {
this.openingMarker = openingMarker;
}
public BasedSequence getText() {
return text;
}
public void setText(BasedSequence text) {
this.text = text;
}
public BasedSequence getClosingMarker() {
return closingMarker;
}
public void setClosingMarker(BasedSequence closingMarker) {
this.closingMarker = closingMarker;
}
public BasedSequence getFootnote() {
return footnote;
}
public void setFootnote(BasedSequence footnote) {
this.footnote = footnote;
}
@Override
public boolean isItemParagraph(Paragraph node) {
return node == getFirstChild();
}
@Override
public boolean isParagraphWrappingDisabled(Paragraph node, ListOptions listOptions,
DataHolder options) {
return false;
}
@Override
public boolean isParagraphInTightListItem(Paragraph node) {
return false;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
FootnoteBlock that = (FootnoteBlock) o;
return footnoteOrdinal == that.footnoteOrdinal
&& firstReferenceOffset == that.firstReferenceOffset
&& footnoteReferences == that.footnoteReferences
&& Objects.equals(openingMarker, that.openingMarker)
&& Objects.equals(text, that.text)
&& Objects.equals(closingMarker, that.closingMarker)
&& Objects.equals(footnote, that.footnote);
}
@Override
public int hashCode() {
return Objects
.hash(openingMarker, text, closingMarker, footnote, footnoteOrdinal,
firstReferenceOffset,
footnoteReferences);
}
}

View File

@ -0,0 +1,98 @@
package run.halo.app.utils.footnotes;
import com.vladsch.flexmark.formatter.Formatter;
import com.vladsch.flexmark.html.HtmlRenderer;
import com.vladsch.flexmark.parser.Parser;
import com.vladsch.flexmark.util.ast.KeepType;
import com.vladsch.flexmark.util.data.DataHolder;
import com.vladsch.flexmark.util.data.DataKey;
import com.vladsch.flexmark.util.data.MutableDataHolder;
import com.vladsch.flexmark.util.format.options.ElementPlacement;
import com.vladsch.flexmark.util.format.options.ElementPlacementSort;
import org.jetbrains.annotations.NotNull;
import run.halo.app.utils.footnotes.internal.FootnoteBlockParser;
import run.halo.app.utils.footnotes.internal.FootnoteLinkRefProcessor;
import run.halo.app.utils.footnotes.internal.FootnoteNodeFormatter;
import run.halo.app.utils.footnotes.internal.FootnoteNodeRenderer;
import run.halo.app.utils.footnotes.internal.FootnoteRepository;
/**
* Extension for footnotes
* <p>
* Create it with {@link #create()} and then configure it on the builders
* <p>
* The parsed footnote references in text regions are turned into {@link Footnote} nodes. The parsed
* footnote definitions are turned into {@link FootnoteBlock} nodes.
*/
public class FootnoteExtension
implements Parser.ParserExtension, HtmlRenderer.HtmlRendererExtension,
Parser.ReferenceHoldingExtension, Formatter.FormatterExtension {
public static final DataKey<KeepType> FOOTNOTES_KEEP =
new DataKey<>("FOOTNOTES_KEEP", KeepType.FIRST);
public static final DataKey<FootnoteRepository> FOOTNOTES =
new DataKey<>("FOOTNOTES", new FootnoteRepository(null), FootnoteRepository::new);
public static final DataKey<String> FOOTNOTE_REF_PREFIX =
new DataKey<>("FOOTNOTE_REF_PREFIX", "");
public static final DataKey<String> FOOTNOTE_REF_SUFFIX =
new DataKey<>("FOOTNOTE_REF_SUFFIX", "");
public static final DataKey<String> FOOTNOTE_BACK_REF_STRING =
new DataKey<>("FOOTNOTE_BACK_REF_STRING", "&#8617;");
public static final DataKey<String> FOOTNOTE_LINK_REF_CLASS =
new DataKey<>("FOOTNOTE_LINK_REF_CLASS", "footnote-ref");
public static final DataKey<String> FOOTNOTE_BACK_LINK_REF_CLASS =
new DataKey<>("FOOTNOTE_BACK_LINK_REF_CLASS", "footnote-backref");
// formatter options
public static final DataKey<ElementPlacement> FOOTNOTE_PLACEMENT =
new DataKey<>("FOOTNOTE_PLACEMENT", ElementPlacement.AS_IS);
public static final DataKey<ElementPlacementSort> FOOTNOTE_SORT =
new DataKey<>("FOOTNOTE_SORT", ElementPlacementSort.AS_IS);
private FootnoteExtension() {
}
public static FootnoteExtension create() {
return new FootnoteExtension();
}
@Override
public void extend(Formatter.Builder formatterBuilder) {
formatterBuilder.nodeFormatterFactory(new FootnoteNodeFormatter.Factory());
}
@Override
public void extend(@NotNull HtmlRenderer.Builder htmlRendererBuilder,
@NotNull String rendererType) {
if (htmlRendererBuilder.isRendererType("HTML")) {
htmlRendererBuilder.nodeRendererFactory(new FootnoteNodeRenderer.Factory());
}
}
@Override
public void extend(Parser.Builder parserBuilder) {
parserBuilder.customBlockParserFactory(new FootnoteBlockParser.Factory());
parserBuilder.linkRefProcessorFactory(new FootnoteLinkRefProcessor.Factory());
}
@Override
public void rendererOptions(@NotNull MutableDataHolder options) {
}
@Override
public void parserOptions(MutableDataHolder options) {
}
@Override
public boolean transferReferences(MutableDataHolder document, DataHolder included) {
if (document.contains(FOOTNOTES) && included.contains(FOOTNOTES)) {
return Parser.transferReferences(FOOTNOTES.get(document), FOOTNOTES.get(included),
FOOTNOTES_KEEP.get(document) == KeepType.FIRST);
}
return false;
}
}

View File

@ -0,0 +1,7 @@
package run.halo.app.utils.footnotes;
public interface FootnoteVisitor {
void visit(FootnoteBlock node);
void visit(Footnote node);
}

View File

@ -0,0 +1,13 @@
package run.halo.app.utils.footnotes;
import com.vladsch.flexmark.util.ast.VisitHandler;
public class FootnoteVisitorExt {
public static <V extends FootnoteVisitor> VisitHandler<?>[] visitHandlers(V visitor) {
return new VisitHandler<?>[] {
new VisitHandler<>(FootnoteBlock.class, visitor::visit),
new VisitHandler<>(Footnote.class, visitor::visit),
};
}
}

View File

@ -0,0 +1,168 @@
package run.halo.app.utils.footnotes.internal;
import com.vladsch.flexmark.parser.block.AbstractBlockParser;
import com.vladsch.flexmark.parser.block.AbstractBlockParserFactory;
import com.vladsch.flexmark.parser.block.BlockContinue;
import com.vladsch.flexmark.parser.block.BlockParser;
import com.vladsch.flexmark.parser.block.BlockParserFactory;
import com.vladsch.flexmark.parser.block.BlockStart;
import com.vladsch.flexmark.parser.block.CustomBlockParserFactory;
import com.vladsch.flexmark.parser.block.MatchedBlockParser;
import com.vladsch.flexmark.parser.block.ParserState;
import com.vladsch.flexmark.util.ast.Block;
import com.vladsch.flexmark.util.ast.BlockContent;
import com.vladsch.flexmark.util.data.DataHolder;
import com.vladsch.flexmark.util.sequence.BasedSequence;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import run.halo.app.utils.footnotes.FootnoteBlock;
import run.halo.app.utils.footnotes.FootnoteExtension;
public class FootnoteBlockParser extends AbstractBlockParser {
static String FOOTNOTE_ID = ".*";
static Pattern FOOTNOTE_ID_PATTERN = Pattern.compile("\\[\\^\\s*(" + FOOTNOTE_ID + ")\\s*\\]");
static Pattern FOOTNOTE_DEF_PATTERN =
Pattern.compile("^\\[\\^\\s*(" + FOOTNOTE_ID + ")\\s*\\]:");
private final FootnoteBlock block = new FootnoteBlock();
private final FootnoteOptions options;
private final int contentOffset;
private BlockContent content = new BlockContent();
public FootnoteBlockParser(FootnoteOptions options, int contentOffset) {
this.options = options;
this.contentOffset = contentOffset;
}
@Override
public BlockContent getBlockContent() {
return content;
}
@Override
public Block getBlock() {
return block;
}
@Override
public BlockContinue tryContinue(ParserState state) {
final int nonSpaceIndex = state.getNextNonSpaceIndex();
if (state.isBlank()) {
if (block.getFirstChild() == null) {
// Blank line after empty list item
return BlockContinue.none();
} else {
return BlockContinue.atIndex(nonSpaceIndex);
}
}
if (state.getIndent() >= options.contentIndent) {
int contentIndent = state.getIndex() + options.contentIndent;
return BlockContinue.atIndex(contentIndent);
} else {
return BlockContinue.none();
}
}
@Override
public void addLine(ParserState state, BasedSequence line) {
content.add(line, state.getIndent());
}
@Override
public void closeBlock(ParserState state) {
// set the footnote from closingMarker to end
block.setCharsFromContent();
block.setFootnote(block.getChars()
.subSequence(block.getClosingMarker().getEndOffset() - block.getStartOffset())
.trimStart());
// add it to the map
FootnoteRepository footnoteMap = FootnoteExtension.FOOTNOTES.get(state.getProperties());
footnoteMap.put(footnoteMap.normalizeKey(block.getText()), block);
content = null;
}
@Override
public boolean isContainer() {
return true;
}
@Override
public boolean canContain(ParserState state, BlockParser blockParser, Block block) {
return true;
}
public static class Factory implements CustomBlockParserFactory {
@Nullable
@Override
public Set<Class<?>> getAfterDependents() {
return null;
}
@Nullable
@Override
public Set<Class<?>> getBeforeDependents() {
return null;
}
@Override
public boolean affectsGlobalScope() {
return false;
}
@NotNull
@Override
public BlockParserFactory apply(@NotNull DataHolder options) {
return new BlockFactory(options);
}
}
private static class BlockFactory extends AbstractBlockParserFactory {
private final FootnoteOptions options;
private BlockFactory(DataHolder options) {
super(options);
this.options = new FootnoteOptions(options);
}
@Override
public BlockStart tryStart(ParserState state, MatchedBlockParser matchedBlockParser) {
if (state.getIndent() >= 4) {
return BlockStart.none();
}
BasedSequence line = state.getLine();
int nextNonSpace = state.getNextNonSpaceIndex();
BasedSequence trySequence = line.subSequence(nextNonSpace, line.length());
Matcher matcher = FOOTNOTE_DEF_PATTERN.matcher(trySequence);
if (matcher.find()) {
// abbreviation definition
int openingStart = nextNonSpace + matcher.start();
int openingEnd = nextNonSpace + matcher.end();
BasedSequence openingMarker = line.subSequence(openingStart, openingStart + 2);
BasedSequence text = line.subSequence(openingStart + 2, openingEnd - 2).trim();
BasedSequence closingMarker = line.subSequence(openingEnd - 2, openingEnd);
int contentOffset = options.contentIndent;
FootnoteBlockParser footnoteBlockParser =
new FootnoteBlockParser(options, contentOffset);
footnoteBlockParser.block.setOpeningMarker(openingMarker);
footnoteBlockParser.block.setText(text);
footnoteBlockParser.block.setClosingMarker(closingMarker);
return BlockStart.of(footnoteBlockParser)
.atIndex(openingEnd);
} else {
return BlockStart.none();
}
}
}
}

View File

@ -0,0 +1,17 @@
package run.halo.app.utils.footnotes.internal;
import com.vladsch.flexmark.util.data.DataHolder;
import com.vladsch.flexmark.util.format.options.ElementPlacement;
import com.vladsch.flexmark.util.format.options.ElementPlacementSort;
import run.halo.app.utils.footnotes.FootnoteExtension;
public class FootnoteFormatOptions {
public final ElementPlacement footnotePlacement;
public final ElementPlacementSort footnoteSort;
public FootnoteFormatOptions(DataHolder options) {
footnotePlacement = FootnoteExtension.FOOTNOTE_PLACEMENT.get(options);
footnoteSort = FootnoteExtension.FOOTNOTE_SORT.get(options);
}
}

View File

@ -0,0 +1,94 @@
package run.halo.app.utils.footnotes.internal;
import com.vladsch.flexmark.parser.LinkRefProcessor;
import com.vladsch.flexmark.parser.LinkRefProcessorFactory;
import com.vladsch.flexmark.util.ast.Document;
import com.vladsch.flexmark.util.ast.Node;
import com.vladsch.flexmark.util.data.DataHolder;
import com.vladsch.flexmark.util.sequence.BasedSequence;
import org.jetbrains.annotations.NotNull;
import run.halo.app.utils.footnotes.Footnote;
import run.halo.app.utils.footnotes.FootnoteBlock;
import run.halo.app.utils.footnotes.FootnoteExtension;
public class FootnoteLinkRefProcessor implements LinkRefProcessor {
static final boolean WANT_EXCLAMATION_PREFIX = false;
static final int BRACKET_NESTING_LEVEL = 0;
private final FootnoteRepository footnoteRepository;
public FootnoteLinkRefProcessor(Document document) {
this.footnoteRepository = FootnoteExtension.FOOTNOTES.get(document);
}
@Override
public boolean getWantExclamationPrefix() {
return WANT_EXCLAMATION_PREFIX;
}
@Override
public int getBracketNestingLevel() {
return BRACKET_NESTING_LEVEL;
}
@Override
public boolean isMatch(@NotNull BasedSequence nodeChars) {
return nodeChars.length() >= 3 && nodeChars.charAt(0) == '[' && nodeChars.charAt(1) == '^'
&& nodeChars.endCharAt(1) == ']';
}
@NotNull
@Override
public Node createNode(@NotNull BasedSequence nodeChars) {
BasedSequence footnoteId = nodeChars.midSequence(2, -1).trim();
FootnoteBlock footnoteBlock =
footnoteId.length() > 0 ? footnoteRepository.get(footnoteId.toString()) : null;
Footnote footnote =
new Footnote(nodeChars.subSequence(0, 2), footnoteId, nodeChars.endSequence(1));
footnote.setFootnoteBlock(footnoteBlock);
if (footnoteBlock != null) {
footnoteRepository.addFootnoteReference(footnoteBlock, footnote);
}
return footnote;
}
@NotNull
@Override
public BasedSequence adjustInlineText(@NotNull Document document, @NotNull Node node) {
assert node instanceof Footnote;
return ((Footnote) node).getText();
}
@Override
public boolean allowDelimiters(@NotNull BasedSequence chars, @NotNull Document document,
@NotNull Node node) {
return true;
}
@Override
public void updateNodeElements(@NotNull Document document, @NotNull Node node) {
}
public static class Factory implements LinkRefProcessorFactory {
@NotNull
@Override
public LinkRefProcessor apply(@NotNull Document document) {
return new FootnoteLinkRefProcessor(document);
}
@Override
public boolean getWantExclamationPrefix(@NotNull DataHolder options) {
return WANT_EXCLAMATION_PREFIX;
}
@Override
public int getBracketNestingLevel(@NotNull DataHolder options) {
return BRACKET_NESTING_LEVEL;
}
}
}

View File

@ -0,0 +1,109 @@
package run.halo.app.utils.footnotes.internal;
import com.vladsch.flexmark.formatter.MarkdownWriter;
import com.vladsch.flexmark.formatter.NodeFormatter;
import com.vladsch.flexmark.formatter.NodeFormatterContext;
import com.vladsch.flexmark.formatter.NodeFormatterFactory;
import com.vladsch.flexmark.formatter.NodeFormattingHandler;
import com.vladsch.flexmark.formatter.NodeRepositoryFormatter;
import com.vladsch.flexmark.util.data.DataHolder;
import com.vladsch.flexmark.util.data.DataKey;
import com.vladsch.flexmark.util.format.options.ElementPlacement;
import com.vladsch.flexmark.util.format.options.ElementPlacementSort;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import run.halo.app.utils.footnotes.Footnote;
import run.halo.app.utils.footnotes.FootnoteBlock;
import run.halo.app.utils.footnotes.FootnoteExtension;
public class FootnoteNodeFormatter
extends NodeRepositoryFormatter<FootnoteRepository, FootnoteBlock, Footnote> {
public static final DataKey<Map<String, String>> FOOTNOTE_TRANSLATION_MAP =
new DataKey<>("FOOTNOTE_TRANSLATION_MAP", new HashMap<>()); // translated references
public static final DataKey<Map<String, String>> FOOTNOTE_UNIQUIFICATION_MAP =
new DataKey<>("FOOTNOTE_UNIQUIFICATION_MAP", new HashMap<>()); // uniquified references
private final FootnoteFormatOptions options;
public FootnoteNodeFormatter(DataHolder options) {
super(options, FOOTNOTE_TRANSLATION_MAP, FOOTNOTE_UNIQUIFICATION_MAP);
this.options = new FootnoteFormatOptions(options);
}
@Override
public FootnoteRepository getRepository(DataHolder options) {
return FootnoteExtension.FOOTNOTES.get(options);
}
@Override
public ElementPlacement getReferencePlacement() {
return options.footnotePlacement;
}
@Override
public ElementPlacementSort getReferenceSort() {
return options.footnoteSort;
}
@Override
public void renderReferenceBlock(FootnoteBlock node, NodeFormatterContext context,
MarkdownWriter markdown) {
markdown.blankLine().append("[^");
markdown.append(transformReferenceId(node.getText().toString(), context));
markdown.append("]: ");
markdown.pushPrefix().addPrefix(" ");
context.renderChildren(node);
markdown.popPrefix();
markdown.blankLine();
}
@Nullable
@Override
public Set<NodeFormattingHandler<?>> getNodeFormattingHandlers() {
return new HashSet<>(Arrays.asList(
new NodeFormattingHandler<>(Footnote.class, FootnoteNodeFormatter.this::render),
new NodeFormattingHandler<>(FootnoteBlock.class, FootnoteNodeFormatter.this::render)
));
}
@Nullable
@Override
public Set<Class<?>> getNodeClasses() {
if (options.footnotePlacement.isNoChange() || !options.footnoteSort.isUnused()) {
return null;
}
// noinspection ArraysAsListWithZeroOrOneArgument
return new HashSet<>(Arrays.asList(
Footnote.class
));
}
private void render(FootnoteBlock node, NodeFormatterContext context, MarkdownWriter markdown) {
renderReference(node, context, markdown);
}
private void render(Footnote node, NodeFormatterContext context, MarkdownWriter markdown) {
markdown.append("[^");
if (context.isTransformingText()) {
String referenceId = transformReferenceId(node.getText().toString(), context);
context.nonTranslatingSpan((context1, markdown1) -> markdown1.append(referenceId));
} else {
markdown.append(node.getText());
}
markdown.append("]");
}
public static class Factory implements NodeFormatterFactory {
@NotNull
@Override
public NodeFormatter create(@NotNull DataHolder options) {
return new FootnoteNodeFormatter(options);
}
}
}

View File

@ -1,122 +1,60 @@
package run.halo.app.utils;
package run.halo.app.utils.footnotes.internal;
import com.vladsch.flexmark.ast.Link;
import com.vladsch.flexmark.ast.LinkNodeBase;
import com.vladsch.flexmark.ext.footnotes.Footnote;
import com.vladsch.flexmark.ext.footnotes.FootnoteBlock;
import com.vladsch.flexmark.ext.footnotes.internal.FootnoteNodeRenderer;
import com.vladsch.flexmark.ext.footnotes.internal.FootnoteOptions;
import com.vladsch.flexmark.ext.footnotes.internal.FootnoteRepository;
import com.vladsch.flexmark.html.HtmlRenderer;
import com.vladsch.flexmark.html.HtmlWriter;
import com.vladsch.flexmark.html.renderer.NodeRenderer;
import com.vladsch.flexmark.html.renderer.NodeRendererContext;
import com.vladsch.flexmark.html.renderer.NodeRendererFactory;
import com.vladsch.flexmark.html.renderer.NodeRenderingHandler;
import com.vladsch.flexmark.html.renderer.PhasedNodeRenderer;
import com.vladsch.flexmark.html.renderer.RenderingPhase;
import com.vladsch.flexmark.util.ast.Document;
import com.vladsch.flexmark.util.ast.NodeVisitor;
import com.vladsch.flexmark.util.ast.VisitHandler;
import com.vladsch.flexmark.util.data.DataHolder;
import com.vladsch.flexmark.util.sequence.BasedSequence;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Locale;
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.agent.ByteBuddyAgent;
import net.bytebuddy.dynamic.loading.ClassReloadingStrategy;
import net.bytebuddy.implementation.MethodDelegation;
import net.bytebuddy.implementation.bind.annotation.Argument;
import net.bytebuddy.implementation.bind.annotation.FieldValue;
import net.bytebuddy.matcher.ElementMatchers;
import java.util.Set;
import org.apache.commons.lang3.StringUtils;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import org.yaml.snakeyaml.nodes.SequenceNode;
import org.jetbrains.annotations.NotNull;
import run.halo.app.utils.footnotes.Footnote;
import run.halo.app.utils.footnotes.FootnoteBlock;
import run.halo.app.utils.footnotes.FootnoteExtension;
/**
* <code>Flexmark</code> footnote node render interceptor.
* Delegate the render method to intercept the FootNoteNodeRender by ByteBuddy runtime.
*
* @author guqing
* @date 2021-06-26
*/
public class FootnoteNodeRendererInterceptor {
public class FootnoteNodeRenderer implements PhasedNodeRenderer {
/**
* Delegate the render method to intercept the FootNoteNodeRender by ByteBuddy runtime.
*/
public static void doDelegationMethod() {
ByteBuddyAgent.install();
new ByteBuddy()
.redefine(FootnoteNodeRenderer.class)
private final FootnoteRepository footnoteRepository;
private final FootnoteOptions options;
private final boolean recheckUndefinedReferences;
.method(ElementMatchers.named("render").and(ElementMatchers.takesArguments(
Footnote.class, NodeRendererContext.class, HtmlWriter.class)))
.intercept(MethodDelegation.to(FootnoteNodeRendererInterceptor.class))
.method(ElementMatchers.named("renderDocument"))
.intercept(MethodDelegation.to(FootnoteNodeRendererInterceptor.class))
.make()
.load(Thread.currentThread().getContextClassLoader(),
ClassReloadingStrategy.fromInstalledAgent());
public FootnoteNodeRenderer(DataHolder options) {
this.options = new FootnoteOptions(options);
this.footnoteRepository = FootnoteExtension.FOOTNOTES.get(options);
this.recheckUndefinedReferences = HtmlRenderer.RECHECK_UNDEFINED_REFERENCES.get(options);
this.footnoteRepository.resolveFootnoteOrdinals();
}
/**
* footnote render see {@link FootnoteNodeRenderer#renderDocument}.
*
* @param node footnote node
* @param context node renderer context
* @param html html writer
*/
public static void render(Footnote node, NodeRendererContext context, HtmlWriter html) {
FootnoteBlock footnoteBlock = node.getFootnoteBlock();
if (footnoteBlock == null) {
//just text
html.raw("[^");
context.renderChildren(node);
html.raw("]");
} else {
int footnoteOrdinal = footnoteBlock.getFootnoteOrdinal();
int i = node.getReferenceOrdinal();
html.attr("class", "footnote-ref");
html.srcPos(node.getChars()).withAttr()
.tag("sup", false, false, () -> {
// if (!options.footnoteLinkRefClass.isEmpty()) html.attr("class", options
// .footnoteLinkRefClass);
String ordinal = footnoteOrdinal + (i == 0 ? "" : String.format(Locale.US,
":%d", i));
html.attr("id", "fnref"
+ ordinal);
html.attr("href", "#fn" + footnoteOrdinal);
html.withAttr().tag("a");
html.raw("[" + ordinal + "]");
html.tag("/a");
});
}
@Override
public Set<NodeRenderingHandler<?>> getNodeRenderingHandlers() {
return new HashSet<>(Arrays.asList(
new NodeRenderingHandler<>(Footnote.class, this::render),
new NodeRenderingHandler<>(FootnoteBlock.class, this::render)
));
}
/**
* render document.
*
* @param footnoteRepository footnoteRepository field of FootNoteRenderer class
* @param options options field of FootNoteRenderer class
* @param recheckUndefinedReferences recheckUndefinedReferences field of FootNoteRenderer class
* @param context node render context
* @param html html writer
* @param document document
* @param phase rendering phase
*/
public static void renderDocument(@FieldValue("footnoteRepository")
FootnoteRepository footnoteRepository,
@FieldValue("options") FootnoteOptions options,
@FieldValue("recheckUndefinedReferences")
boolean recheckUndefinedReferences,
@Argument(0) NodeRendererContext context,
@Argument(1) HtmlWriter html, @Argument(2) Document document,
@Argument(3)
RenderingPhase phase) {
final String footnoteBackLinkRefClass =
(String) getFootnoteOptionsFieldValue("footnoteBackLinkRefClass", options);
final String footnoteBackRefString = ObjectUtils
.getDisplayString(getFootnoteOptionsFieldValue("footnoteBackRefString", options));
@Override
public Set<RenderingPhase> getRenderingPhases() {
Set<RenderingPhase> set = new HashSet<>();
set.add(RenderingPhase.BODY_TOP);
set.add(RenderingPhase.BODY_BOTTOM);
return set;
}
@Override
public void renderDocument(@NotNull NodeRendererContext context, @NotNull HtmlWriter html,
@NotNull Document document, @NotNull RenderingPhase phase) {
if (phase == RenderingPhase.BODY_TOP) {
if (recheckUndefinedReferences) {
// need to see if have undefined footnotes that were defined after parsing
@ -138,7 +76,7 @@ public class FootnoteNodeRendererInterceptor {
visitor.visit(document);
if (hadNewFootnotes[0]) {
footnoteRepository.resolveFootnoteOrdinals();
this.footnoteRepository.resolveFootnoteOrdinals();
}
}
}
@ -166,11 +104,14 @@ public class FootnoteNodeRendererInterceptor {
sb.append(" <a href=\"#fnref").append(footnoteOrdinal)
.append(i == 0 ? "" : String
.format(Locale.US, ":%d", i)).append("\"");
if (StringUtils.isNotBlank(footnoteBackLinkRefClass)) {
sb.append(" class=\"").append(footnoteBackLinkRefClass)
if (StringUtils
.isNotBlank(options.footnoteBackLinkRefClass)) {
sb.append(" class=\"")
.append(options.footnoteBackLinkRefClass)
.append("\"");
}
sb.append(">").append(footnoteBackRefString).append("</a>");
sb.append(">").append(options.footnoteBackRefString)
.append("</a>");
html.setLine(html.getLineCount() - 1, "",
line.insert(line.lastIndexOf("</p"), sb.toString()));
}
@ -179,11 +120,12 @@ public class FootnoteNodeRendererInterceptor {
for (int i = 0; i < iMax; i++) {
html.attr("href", "#fnref" + footnoteOrdinal
+ (i == 0 ? "" : String.format(Locale.US, ":%d", i)));
if (StringUtils.isNotBlank(footnoteBackLinkRefClass)) {
html.attr("class", footnoteBackLinkRefClass);
if (StringUtils
.isNotBlank(options.footnoteBackLinkRefClass)) {
html.attr("class", options.footnoteBackLinkRefClass);
}
html.line().withAttr().tag("a");
html.raw(footnoteBackRefString);
html.raw(options.footnoteBackRefString);
html.tag("/a");
}
}
@ -195,25 +137,44 @@ public class FootnoteNodeRendererInterceptor {
}
}
/**
* Gets field value from FootnoteOptions.
*
* @param fieldName field name of FootNoteOptions class, must not be null.
* @param options target object, must not be null.
* @return field value.
*/
private static Object getFootnoteOptionsFieldValue(String fieldName, FootnoteOptions options) {
Assert.notNull(fieldName, "FieldName must not be null");
Assert.notNull(options, "FootnoteOptions type must not be null");
private void render(FootnoteBlock node, NodeRendererContext context, HtmlWriter html) {
Object value = null;
try {
Field field = FootnoteOptions.class.getDeclaredField(fieldName);
field.setAccessible(true);
value = field.get(options);
} catch (NoSuchFieldException | IllegalAccessException e) {
e.printStackTrace();
}
private void render(Footnote node, NodeRendererContext context, HtmlWriter html) {
FootnoteBlock footnoteBlock = node.getFootnoteBlock();
if (footnoteBlock == null) {
//just text
html.raw("[^");
context.renderChildren(node);
html.raw("]");
} else {
int footnoteOrdinal = footnoteBlock.getFootnoteOrdinal();
int i = node.getReferenceOrdinal();
html.attr("class", "footnote-ref");
html.srcPos(node.getChars()).withAttr()
.tag("sup", false, false, () -> {
// if (!options.footnoteLinkRefClass.isEmpty())
// html.attr("class", options.footnoteLinkRefClass);
String ordinal = footnoteOrdinal + (i == 0 ? "" : String.format(Locale.US,
":%d", i));
html.attr("id", "fnref"
+ ordinal);
html.attr("href", "#fn" + footnoteOrdinal);
html.withAttr().tag("a");
html.raw("[" + ordinal + "]");
html.tag("/a");
});
}
}
public static class Factory implements NodeRendererFactory {
@NotNull
@Override
public NodeRenderer apply(@NotNull DataHolder options) {
return new FootnoteNodeRenderer(options);
}
return value;
}
}

View File

@ -0,0 +1,24 @@
package run.halo.app.utils.footnotes.internal;
import com.vladsch.flexmark.parser.Parser;
import com.vladsch.flexmark.util.data.DataHolder;
import run.halo.app.utils.footnotes.FootnoteExtension;
public class FootnoteOptions {
final String footnoteRefPrefix;
final String footnoteRefSuffix;
final String footnoteBackRefString;
final String footnoteLinkRefClass;
final String footnoteBackLinkRefClass;
final int contentIndent;
public FootnoteOptions(DataHolder options) {
this.footnoteRefPrefix = FootnoteExtension.FOOTNOTE_REF_PREFIX.get(options);
this.footnoteRefSuffix = FootnoteExtension.FOOTNOTE_REF_SUFFIX.get(options);
this.footnoteBackRefString = FootnoteExtension.FOOTNOTE_BACK_REF_STRING.get(options);
this.footnoteLinkRefClass = FootnoteExtension.FOOTNOTE_LINK_REF_CLASS.get(options);
this.footnoteBackLinkRefClass = FootnoteExtension.FOOTNOTE_BACK_LINK_REF_CLASS.get(options);
this.contentIndent = Parser.LISTS_ITEM_INDENT.get(options);
}
}

View File

@ -0,0 +1,109 @@
package run.halo.app.utils.footnotes.internal;
import com.vladsch.flexmark.util.ast.Document;
import com.vladsch.flexmark.util.ast.KeepType;
import com.vladsch.flexmark.util.ast.Node;
import com.vladsch.flexmark.util.ast.NodeRepository;
import com.vladsch.flexmark.util.ast.NodeVisitor;
import com.vladsch.flexmark.util.ast.VisitHandler;
import com.vladsch.flexmark.util.data.DataHolder;
import com.vladsch.flexmark.util.data.DataKey;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.jetbrains.annotations.NotNull;
import run.halo.app.utils.footnotes.Footnote;
import run.halo.app.utils.footnotes.FootnoteBlock;
import run.halo.app.utils.footnotes.FootnoteExtension;
@SuppressWarnings("WeakerAccess")
public class FootnoteRepository extends NodeRepository<FootnoteBlock> {
private final ArrayList<FootnoteBlock> referencedFootnoteBlocks = new ArrayList<>();
public static void resolveFootnotes(Document document) {
FootnoteRepository footnoteRepository = FootnoteExtension.FOOTNOTES.get(document);
boolean[] hadNewFootnotes = {false};
NodeVisitor visitor = new NodeVisitor(
new VisitHandler<>(Footnote.class, node -> {
if (!node.isDefined()) {
FootnoteBlock footonoteBlock = node.getFootnoteBlock(footnoteRepository);
if (footonoteBlock != null) {
footnoteRepository.addFootnoteReference(footonoteBlock, node);
node.setFootnoteBlock(footonoteBlock);
hadNewFootnotes[0] = true;
}
}
})
);
visitor.visit(document);
if (hadNewFootnotes[0]) {
footnoteRepository.resolveFootnoteOrdinals();
}
}
public void addFootnoteReference(FootnoteBlock footnoteBlock, Footnote footnote) {
if (!footnoteBlock.isReferenced()) {
referencedFootnoteBlocks.add(footnoteBlock);
}
footnoteBlock.setFirstReferenceOffset(footnote.getStartOffset());
int referenceOrdinal = footnoteBlock.getFootnoteReferences();
footnoteBlock.setFootnoteReferences(referenceOrdinal + 1);
footnote.setReferenceOrdinal(referenceOrdinal);
}
public void resolveFootnoteOrdinals() {
// need to sort by first referenced offset then set each to its ordinal position in the
// array+1
Collections.sort(referencedFootnoteBlocks,
(f1, f2) -> f1.getFirstReferenceOffset() - f2.getFirstReferenceOffset());
int ordinal = 0;
for (FootnoteBlock footnoteBlock : referencedFootnoteBlocks) {
footnoteBlock.setFootnoteOrdinal(++ordinal);
}
}
public List<FootnoteBlock> getReferencedFootnoteBlocks() {
return referencedFootnoteBlocks;
}
public FootnoteRepository(DataHolder options) {
super(FootnoteExtension.FOOTNOTES_KEEP.get(options));
}
@NotNull
@Override
public DataKey<FootnoteRepository> getDataKey() {
return FootnoteExtension.FOOTNOTES;
}
@NotNull
@Override
public DataKey<KeepType> getKeepDataKey() {
return FootnoteExtension.FOOTNOTES_KEEP;
}
@NotNull
@Override
public Set<FootnoteBlock> getReferencedElements(Node parent) {
HashSet<FootnoteBlock> references = new HashSet<>();
visitNodes(parent, value -> {
if (value instanceof Footnote) {
FootnoteBlock reference =
((Footnote) value).getReferenceNode(FootnoteRepository.this);
if (reference != null) {
references.add(reference);
}
}
}, Footnote.class);
return references;
}
}

View File

@ -0,0 +1,9 @@
package run.halo.app.utils.footnotes;
/*
* This package uses {@link https://github.com/vsch/flexmark-java/tree/master/flexmark-ext-footnotes}
* In order to solve the rendering inconsistent of between flexmark and marked-it.
* Deprecated on 1.5.x version
*
* @author guqing
* @since 1.4.9
*/

File diff suppressed because one or more lines are too long

View File

@ -1,12 +1,9 @@
package run.halo.app.utils;
import cn.hutool.core.lang.Assert;
import com.vladsch.flexmark.ext.attributes.AttributesExtension;
import com.vladsch.flexmark.ext.autolink.AutolinkExtension;
import com.vladsch.flexmark.ext.emoji.EmojiExtension;
import com.vladsch.flexmark.ext.emoji.EmojiImageType;
import com.vladsch.flexmark.ext.emoji.EmojiShortcutType;
import com.vladsch.flexmark.ext.footnotes.FootnoteExtension;
import com.vladsch.flexmark.html.HtmlRenderer;
import com.vladsch.flexmark.parser.Parser;
import com.vladsch.flexmark.util.ast.Node;
@ -15,9 +12,10 @@ import com.vladsch.flexmark.util.data.MutableDataSet;
import java.util.Arrays;
import org.apache.commons.lang3.StringUtils;
import org.junit.jupiter.api.Test;
import run.halo.app.utils.footnotes.FootnoteExtension;
/**
* Compare the rendering result of FootnoteNodeRendererInterceptor
* Compare the rendering result of FootnoteNodeRenderer
* and <a href="https://github.com/markdown-it/markdown-it-footnote">markdown-it-footnote</a>.
* You can view <code>markdown-it-footnote's</code> rendering HTML results on this
* link <a href="https://markdown-it.github.io/">markdown-it-footnote example page</a>.
@ -25,7 +23,7 @@ import org.junit.jupiter.api.Test;
* @author guqing
* @date 2021-06-26
*/
public class FootnoteNodeRendererInterceptorTest {
public class FootnoteTest {
private static final DataHolder OPTIONS =
new MutableDataSet().set(Parser.EXTENSIONS, Arrays.asList(EmojiExtension.create(),
FootnoteExtension.create()))
@ -38,7 +36,6 @@ public class FootnoteNodeRendererInterceptorTest {
private static final HtmlRenderer RENDERER = HtmlRenderer.builder(OPTIONS).build();
private String renderHtml(String markdown) {
FootnoteNodeRendererInterceptor.doDelegationMethod();
Node document = PARSER.parse(markdown);