Skip to content

Commit 5862380

Browse files
committed
Enable markdown descriptions.
1 parent 3fd58fe commit 5862380

File tree

7 files changed

+221
-2
lines changed

7 files changed

+221
-2
lines changed
Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
package org.segrada.rendering.markdown;
2+
3+
import com.google.inject.Injector;
4+
import com.vladsch.flexmark.ext.emoji.EmojiExtension;
5+
import com.vladsch.flexmark.ext.tables.TablesExtension;
6+
import com.vladsch.flexmark.ext.typographic.TypographicExtension;
7+
import com.vladsch.flexmark.ext.wikilink.WikiLink;
8+
import com.vladsch.flexmark.ext.wikilink.internal.WikiLinkLinkRefProcessor;
9+
import com.vladsch.flexmark.html.HtmlRenderer;
10+
import com.vladsch.flexmark.html.HtmlRenderer.Builder;
11+
import com.vladsch.flexmark.html.HtmlWriter;
12+
import com.vladsch.flexmark.html.renderer.*;
13+
import com.vladsch.flexmark.parser.Parser;
14+
import com.vladsch.flexmark.util.ast.Node;
15+
import com.vladsch.flexmark.util.ast.TextCollectingVisitor;
16+
import com.vladsch.flexmark.util.data.DataHolder;
17+
import com.vladsch.flexmark.util.data.MutableDataHolder;
18+
import com.vladsch.flexmark.util.data.MutableDataSet;
19+
import org.jetbrains.annotations.NotNull;
20+
import org.jetbrains.annotations.Nullable;
21+
import org.segrada.model.prototype.ISource;
22+
import org.segrada.service.SourceService;
23+
import org.unbescape.html.HtmlEscape;
24+
25+
import java.util.*;
26+
27+
public class MarkdownRenderer {
28+
/**
29+
* reference to injector
30+
*/
31+
public static Injector injector;
32+
33+
/**
34+
* cache for source reference links
35+
* TODO: use different cache, because otherwise this might fill up memory eventually, rather unlikely, but there is no deletion of old keys here
36+
*/
37+
private static final Map<String, String> sourceReferenceCache = new HashMap<>();
38+
39+
public static void setInjector(Injector injector) {
40+
MarkdownRenderer.injector = injector;
41+
}
42+
43+
/**
44+
* get current source service instance from injector
45+
* @return SourceService instance
46+
*/
47+
public static SourceService getSourceService() {
48+
return injector.getInstance(SourceService.class);
49+
}
50+
51+
// markdown data holder with custom extension + more extensions defined below
52+
final private static DataHolder OPTIONS = new MutableDataSet()
53+
.set(TablesExtension.CLASS_NAME, "table table-condensed")
54+
.set(Parser.EXTENSIONS, Arrays.asList(
55+
CustomExtension.create(), // will use wiki link renderer to work links
56+
EmojiExtension.create(),
57+
TablesExtension.create(),
58+
TypographicExtension.create()
59+
)).toImmutable();
60+
61+
// markdown parser and renderer
62+
static final Parser PARSER = Parser.builder(OPTIONS).build();
63+
static final HtmlRenderer RENDERER = HtmlRenderer.builder(OPTIONS).build();
64+
65+
/**
66+
* Render or remove links
67+
*/
68+
public static class BibliographicEntryLinkNodeRenderer implements NodeRenderer {
69+
@Override
70+
public @Nullable Set<NodeRenderingHandler<?>> getNodeRenderingHandlers() {
71+
HashSet<NodeRenderingHandler<?>> set = new HashSet();
72+
set.add(new NodeRenderingHandler(WikiLink.class, this::render));
73+
return set;
74+
}
75+
76+
private void render(Node node, @NotNull NodeRendererContext context, @NotNull HtmlWriter html) {
77+
if (node instanceof WikiLink) {
78+
WikiLink link = (WikiLink)node;
79+
80+
String possibleKey = link.getLink().toString();
81+
82+
SourceService sourceService = getSourceService();
83+
84+
// fallback in case of errors
85+
if (sourceService == null) {
86+
html.text(node.getChars().unescape());
87+
return;
88+
}
89+
90+
// find corresponding source
91+
ISource source = sourceService.findByRef(possibleKey);
92+
if (source == null) {
93+
// not found: just print text
94+
html.text(node.getChars().unescape());
95+
} else {
96+
// otherwise: create nice link
97+
String url = "source/show/" + source.getUid();
98+
99+
ResolvedLink resolvedLink = context.resolveLink(LinkType.LINK, url, (Boolean)null);
100+
html.attr("href", url);
101+
html.attr("class", "sg-data-add");
102+
html.attr("title", source.getShortRef());
103+
html.srcPos(node.getChars()).withAttr(resolvedLink).tag("a");
104+
html.text(source.getShortTitle());
105+
html.tag("/a");
106+
}
107+
}
108+
}
109+
110+
public static class Factory implements NodeRendererFactory {
111+
public Factory() {
112+
}
113+
114+
@NotNull
115+
public NodeRenderer apply(@NotNull DataHolder options) {
116+
return new BibliographicEntryLinkNodeRenderer();
117+
}
118+
}
119+
}
120+
121+
static class CustomExtension implements Parser.ParserExtension, HtmlRenderer.HtmlRendererExtension {
122+
@Override
123+
public void rendererOptions(@NotNull MutableDataHolder options) {
124+
125+
}
126+
127+
@Override
128+
public void parserOptions(MutableDataHolder mutableDataHolder) {
129+
}
130+
131+
@Override
132+
public void extend(Parser.Builder parserBuilder) {
133+
parserBuilder.linkRefProcessorFactory(new WikiLinkLinkRefProcessor.Factory()); // we will use wiki link extension to help us build links
134+
}
135+
136+
@Override
137+
public void extend(@NotNull Builder htmlRendererBuilder, @NotNull String rendererType) {
138+
htmlRendererBuilder.nodeRendererFactory(new BibliographicEntryLinkNodeRenderer.Factory());
139+
}
140+
141+
static CustomExtension create() {
142+
return new CustomExtension();
143+
}
144+
}
145+
146+
/**
147+
* renders markdown to HTML
148+
* @param markdown source
149+
* @return html
150+
*/
151+
public static String render(String markdown) {
152+
return RENDERER.render(PARSER.parse(markdown));
153+
}
154+
155+
/**
156+
* converts markdown to plain text
157+
* @param markdown source
158+
* @return text
159+
*/
160+
public static String toPlainText(String markdown) {
161+
TextCollectingVisitor textCollectingVisitor = new TextCollectingVisitor();
162+
return textCollectingVisitor.collectAndGetText(PARSER.parse(markdown));
163+
}
164+
}

src/main/java/org/segrada/rendering/markup/DefaultMarkupFilter.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ public class DefaultMarkupFilter extends MarkupFilter {
6464
"-&gt;", "&rarr;",
6565
};
6666

67-
private static final Pattern bibRefPattern = Pattern.compile("\\[\\[([^:\\[\\]]+:[^\\[\\]]+)\\]\\]");
67+
private static final Pattern bibRefPattern = Pattern.compile("\\[\\[([^]]+)\\]\\]");
6868

6969
/**
7070
* @return pattern to find bibliographic references
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package org.segrada.rendering.markup;
2+
3+
import org.jsoup.Jsoup;
4+
import org.jsoup.parser.Parser;
5+
import org.jsoup.safety.Whitelist;
6+
import org.segrada.rendering.markdown.MarkdownRenderer;
7+
8+
/**
9+
* Copyright 2015-2021 Maximilian Kalus [[email protected]]
10+
*
11+
* Licensed under the Apache License, Version 2.0 (the "License");
12+
* you may not use this file except in compliance with the License.
13+
* You may obtain a copy of the License at
14+
*
15+
* http://www.apache.org/licenses/LICENSE-2.0
16+
*
17+
* Unless required by applicable law or agreed to in writing, software
18+
* distributed under the License is distributed on an "AS IS" BASIS,
19+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20+
* See the License for the specific language governing permissions and
21+
* limitations under the License.
22+
*
23+
* Markdown markup filter
24+
*/
25+
public class MarkdownMarkupFilter extends MarkupFilter {
26+
@Override
27+
public String toHTML(String markupText) {
28+
return MarkdownRenderer.render(markupText);
29+
}
30+
31+
@Override
32+
public String toPlain(String markupText) {
33+
// sane default
34+
if (markupText == null || markupText.equals("")) return "";
35+
36+
// remove bibliographic entries
37+
String plainText = markupText.replaceAll("\\[\\[[\\w:]+\\]\\]", "").replaceAll("\\[[0-9f]+:\\]", "");
38+
39+
// clean
40+
return MarkdownRenderer.toPlainText(plainText);
41+
}
42+
}

src/main/java/org/segrada/servlet/SegradaGuiceServletContextListener.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@
1010
import org.segrada.config.ServiceModule;
1111
import org.segrada.config.TemplateModule;
1212
import org.segrada.controller.*;
13+
import org.segrada.rendering.markdown.MarkdownRenderer;
1314
import org.segrada.rendering.markup.DefaultMarkupFilter;
15+
import org.segrada.rendering.markup.MarkdownMarkupFilter;
1416

1517
import javax.servlet.annotation.WebListener;
1618
import java.util.Map;
@@ -94,6 +96,7 @@ protected void configureServlets() {
9496
OrientDBFilter.setInjector(injector);
9597
SegradaSimplePageCachingFilter.setInjector(injector);
9698
DefaultMarkupFilter.setInjector(injector);
99+
MarkdownRenderer.setInjector(injector);
97100
CheckAuthentication.setInjector(injector);
98101

99102
return injector;

src/main/resources/less/single/segrada.less

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,10 @@ td .sg-data-taglist span {
145145

146146
.sg-description {
147147
margin-top: 20px;
148+
149+
hr {
150+
border-color: #000;
151+
}
148152
}
149153

150154
.sg-map {

src/main/webapp/WEB-INF/templates/partials/form.html

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,12 @@ <h2>Text area with markup</h2>
6969
HTML
7070
</label>
7171
</div>
72+
<div class="radio-inline">
73+
<label>
74+
<input class="sg-plain-editor" th:attr="data-editor=${'#' + name + '-' + id}" type="radio" th:id="${markupName + '-' + id + '_markdown'}" th:name="${markupName}" value="markdown" th:checked="${#strings.equals('markdown', markupField)}">
75+
Markdown
76+
</label>
77+
</div>
7278
</div>
7379
</div>
7480
</div>

src/main/webapp/css/segrada.css

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)