initial markdown support

This commit is contained in:
MiniDigger 2020-07-27 19:02:54 +02:00
parent 117448a78a
commit a3c2236b0d
5 changed files with 144 additions and 6 deletions

View File

@ -116,6 +116,13 @@
<artifactId>postgresql</artifactId>
</dependency>
<!-- markdown -->
<dependency>
<groupId>com.vladsch.flexmark</groupId>
<artifactId>flexmark-all</artifactId>
<version>0.62.2</version>
</dependency>
<!-- webjars -->
<dependency>
<groupId>org.webjars</groupId>

View File

@ -10,6 +10,7 @@ import java.util.HashMap;
import java.util.Map;
import me.minidigger.hangar.config.HangarConfig;
import me.minidigger.hangar.service.MarkdownService;
import me.minidigger.hangar.service.UserService;
import me.minidigger.hangar.util.RouteHelper;
import me.minidigger.hangar.util.TemplateHelper;
@ -24,6 +25,8 @@ public abstract class HangarController {
private TemplateHelper templateHelper;
@Autowired
private HangarConfig hangarConfig;
@Autowired
private MarkdownService markdownService;
protected ModelAndView fillModel(ModelAndView mav) {
// helpers
@ -34,6 +37,7 @@ public abstract class HangarController {
builder.setUseModelCache(true);
mav.addObject("@helper", builder.build().getStaticModels());
mav.addObject("config", hangarConfig);
mav.addObject("markdownService", markdownService);
// alerts
if (mav.getModelMap().getAttribute("alerts") == null) {

View File

@ -36,11 +36,6 @@ public class ProjectPagesTable {
return slug;
}
public String html(ProjectsTable project) {
// TODO markdown renderer
return contents;
}
public boolean isHome() {
return name.equals("Home") && parentId == null;
}

View File

@ -0,0 +1,132 @@
package me.minidigger.hangar.service;
import com.vladsch.flexmark.ast.MailLink;
import com.vladsch.flexmark.ext.anchorlink.AnchorLinkExtension;
import com.vladsch.flexmark.ext.autolink.AutolinkExtension;
import com.vladsch.flexmark.ext.gfm.strikethrough.StrikethroughExtension;
import com.vladsch.flexmark.ext.gfm.tasklist.TaskListExtension;
import com.vladsch.flexmark.ext.tables.TablesExtension;
import com.vladsch.flexmark.ext.typographic.TypographicExtension;
import com.vladsch.flexmark.ext.wikilink.WikiLinkExtension;
import com.vladsch.flexmark.html.HtmlRenderer;
import com.vladsch.flexmark.html.LinkResolver;
import com.vladsch.flexmark.html.renderer.LinkResolverBasicContext;
import com.vladsch.flexmark.html.renderer.LinkStatus;
import com.vladsch.flexmark.html.renderer.LinkType;
import com.vladsch.flexmark.html.renderer.ResolvedLink;
import com.vladsch.flexmark.parser.Parser;
import com.vladsch.flexmark.util.ast.Node;
import com.vladsch.flexmark.util.data.MutableDataSet;
import org.jetbrains.annotations.NotNull;
import org.springframework.stereotype.Service;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Arrays;
@Service
public class MarkdownService {
private final Parser markdownParser;
private final MutableDataSet options;
public MarkdownService() {
options = new MutableDataSet()
.set(HtmlRenderer.SUPPRESS_HTML, true)
.set(AnchorLinkExtension.ANCHORLINKS_TEXT_PREFIX, "<i class=\"fas fa-link\"></i>")
.set(AnchorLinkExtension.ANCHORLINKS_ANCHOR_CLASS, "headeranchor")
.set(AnchorLinkExtension.ANCHORLINKS_WRAP_TEXT, false)
// GFM table compatibility
.set(TablesExtension.COLUMN_SPANS, false)
.set(TablesExtension.APPEND_MISSING_COLUMNS, true)
.set(TablesExtension.DISCARD_EXTRA_COLUMNS, true)
.set(TablesExtension.HEADER_SEPARATOR_COLUMN_MATCH, true)
.set(
Parser.EXTENSIONS,
Arrays.asList(
AutolinkExtension.create(),
AnchorLinkExtension.create(),
StrikethroughExtension.create(),
TaskListExtension.create(),
TablesExtension.create(),
TypographicExtension.create(),
WikiLinkExtension.create()
)
);
markdownParser = Parser.builder(options).build();
}
public String render(String input) {
return this.render(input, RenderSettings.defaultSettings);
}
public String render(String input, RenderSettings settings) {
MutableDataSet options = new MutableDataSet(this.options);
if (settings.linkEscapeChars != null) {
options.set(WikiLinkExtension.LINK_ESCAPE_CHARS, settings.linkEscapeChars);
}
if (settings.linkPrefix != null) {
options.set(WikiLinkExtension.LINK_PREFIX, settings.linkPrefix);
}
HtmlRenderer htmlRenderer = HtmlRenderer
.builder(options)
// .linkResolverFactory(new ExternalLinkResolver())
.build();
return htmlRenderer.render(markdownParser.parse(input));
}
static class RenderSettings {
private String linkEscapeChars;
private String linkPrefix;
public static final RenderSettings defaultSettings = new RenderSettings(null, null);
public RenderSettings(String linkEscapeChars, String linkPrefix) {
this.linkEscapeChars = linkEscapeChars;
this.linkPrefix = linkPrefix;
}
}
// TODO external links for markdown shit
// static class ExternalLinkResolver implements LinkResolver {
//
// @Override
// public @NotNull ResolvedLink resolveLink(@NotNull Node node, @NotNull LinkResolverBasicContext context, @NotNull ResolvedLink link) {
// if (link.getLinkType() == LinkType.IMAGE || node instanceof MailLink) {
// return link;
// } else {
// return link.withStatus(LinkStatus.VALID).withUrl(wrapExternal(link.getUrl()));
// }
// }
//
// private String wrapExternal(String urlString) {
// try {
// URI uri = new URI(urlString);
// String host = uri.getHost();
// if (uri.getScheme() != null && host == null) {
// if (uri.getScheme().equals("mailto")) {
// return urlString;
// } else {
// return controllers.routes.Application.linkOut(urlString).toString
// }
// } else {
// String trustedUrlHosts = this.config.app.trustedUrlHosts;
// val checkSubdomain = (trusted: String) =>
// trusted(0) == '.' && (host.endsWith(trusted) || host == trusted.substring(1))
// if (host == null || trustedUrlHosts.exists(trusted => trusted == host || checkSubdomain(trusted))) { // scalafix:ok
// return urlString
// } else {
// return controllers.routes.Application.linkOut(urlString).toString
// }
// }
// } catch (URISyntaxException ex) {
// return controllers.routes.Application.linkOut(urlString).toString
// }
// }
// }
}

View File

@ -38,7 +38,7 @@ Documentation page within Project overview.
deletable=!page.isHome()
enabled=canEditPages()
raw=page.contents
cooked=page.html(p.project)
cooked=markdownService.render(page.contents)
subject="Page"
extraFormValue=page.name />
</div>