Commit e1ca5003 authored by Ubbo Veentjer's avatar Ubbo Veentjer
Browse files

Merge branch 'feature/markdown' into 'develop'

Feature/markdown

Closes #27

See merge request !10
parents 5da5c9fb 3465a500
Pipeline #138607 passed with stages
in 9 minutes and 11 seconds
......@@ -29,6 +29,9 @@ dependencies {
implementation 'io.jsonwebtoken:jjwt:0.9.1'
implementation 'com.fasterxml.jackson.jaxrs:jackson-jaxrs-json-provider'
implementation 'com.atlassian.commonmark:commonmark:0.14.0'
implementation 'com.atlassian.commonmark:commonmark-ext-heading-anchor:0.14.0'
implementation 'com.atlassian.commonmark:commonmark-ext-gfm-tables:0.14.0'
implementation 'com.atlassian.commonmark:commonmark-ext-yaml-front-matter:0.14.0'
implementation 'info.textgrid.middleware.clients:textgrid-clients:3.4.3'
developmentOnly("org.springframework.boot:spring-boot-devtools")
testImplementation('org.springframework.boot:spring-boot-starter-test') {
......
......@@ -39,6 +39,35 @@ Linking between pages is possible, adress them by their relative adress in the `
```markdown
Find more info on the [syntax](/docs/syntax) page.
```
### Linking within pages / Heading anchors
The [Heading anchor](https://github.com/atlassian/commonmark-java#heading-anchor) extension is activated which generates IDs for heading elements. Which could be used as anchors, this means that you can link to them.
An Example:
A Marddown snippet
# Eine Überschrift
## Eine Unterüberschrift
will be rendered as HTML Elements
<h1 id="eine-überschrift">Eine Überschrift</h1>
<h2 id="eine-unterüberschrift">Eine Unterüberschrift</h2>
which means the ID will be lowercase with hyphens (`-`) instead of spaces. So you can link to them in Markdown like
Siehe [Kapitel 1](#eine-überschrift) und [Kapitel 1.1](#eine-unterüberschrift)
Which allwos creation of a TOC for example. You can also reference headings or subheadings on other pages
see the [voyant-example](/docs/voyant#beispiel)
If you are unsure which ID was generated for a heading inspect the element with the developer tool of your web browser. In Firefox for example you can do this with a click on the heading and the context menu entry "Inspect Element"("Element untersuchen"), which will reveal the id:
![inspecting the anchor id](img/inspect-anchor.png)
### Images
......@@ -48,7 +77,13 @@ There is no place for placing own images yet, I will work on it and update this
For parsing Markdwon and rendering HTML the [commonmark-java](https://github.com/atlassian/commonmark-java) library is used. Currently implemented by this library is the [CommonMark Spec 0.29](https://spec.commonmark.org/0.29/). Look at the [CommonMark Dingus](http://spec.commonmark.org/dingus/) for testing and previewing the syntax.
Currently there are no extensions activated, but there are some [available](https://github.com/atlassian/commonmark-java#extensions), if you need one just ask.
Currently there are two extensions activated, there are some [more available](https://github.com/atlassian/commonmark-java#extensions), if you need one just ask.
Active extensions:
* [Heading anchor](https://github.com/atlassian/commonmark-java#heading-anchor)
* [Tables](https://github.com/atlassian/commonmark-java#tables)
* [YAML front matter](https://github.com/atlassian/commonmark-java#yaml-front-matter)
If there is a need for even more extensions or different syntax we may painlessly switch to [flexmark-java](https://github.com/vsch/flexmark-java) for parsing, which is a commonmark-java fork wich supports a lot more different markdown flavours and extensions.
......@@ -10,4 +10,4 @@ Alle veröffentlichten Inhalte sind nach dem Open-Access-Prinzip frei zugänglic
**Mitmachen**
Möchten Sie eigenes XML-erschlossenes Material im TextGrid Repository zitierfähig archivieren und zugänglich machen?
Nehmen Sie [Kontakt](https://textgrid.de/de/kontakt/) mit uns auf.
\ No newline at end of file
Nehmen Sie [Kontakt](https://textgrid.de/de/kontakt/) mit uns auf.
......@@ -15,8 +15,8 @@ import org.springframework.web.bind.annotation.SessionAttributes;
import info.textgrid.namespaces.middleware.tgsearch.ResultType;
import info.textgrid.rep.i18n.I18N;
import info.textgrid.rep.i18n.I18NProvider;
import info.textgrid.rep.service.tgsearch.TgrepConfigurationService;
import info.textgrid.rep.service.tgsearch.TgsearchClientService;
import info.textgrid.rep.service.TgrepConfigurationService;
import info.textgrid.rep.service.TgsearchClientService;
import info.textgrid.rep.shared.ViewMode;
@Controller
......
package info.textgrid.rep.browse;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.math.BigInteger;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.TreeMap;
import javax.ws.rs.client.ClientBuilder;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
......@@ -16,27 +21,37 @@ import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestParam;
import com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider;
import info.textgrid.clients.tgcrud.CrudClientException;
import info.textgrid.namespaces.middleware.tgsearch.ResultType;
import info.textgrid.namespaces.middleware.tgsearch.Revisions;
import info.textgrid.rep.i18n.I18N;
import info.textgrid.rep.i18n.I18NProvider;
import info.textgrid.rep.service.tgsearch.AggregatorClientService;
import info.textgrid.rep.service.tgsearch.TgrepConfigurationService;
import info.textgrid.rep.service.tgsearch.TgsearchClientService;
import info.textgrid.rep.markdown.MarkdownRenderService;
import info.textgrid.rep.service.AggregatorClientService;
import info.textgrid.rep.service.TgcrudClientService;
import info.textgrid.rep.service.TgrepConfigurationService;
import info.textgrid.rep.service.TgsearchClientService;
import info.textgrid.rep.shared.Utils;
import info.textgrid.rep.shared.ViewMode;
@Controller
public class BrowseController {
@Autowired
private TgsearchClientService tgsearchClientService;
@Autowired
private TgrepConfigurationService tgrepConfig;
@Autowired
private AggregatorClientService aggregatorClient;
@Autowired
private TgcrudClientService tgcrudClientService;
@Autowired
private TgsearchClientService tgsearchClientService;
@Autowired
private MarkdownRenderService markdownRenderService;
@Autowired
private I18NProvider i18nProvider;
......@@ -100,8 +115,13 @@ public class BrowseController {
handleXml(model, i18n, id, metadata, fragment);
} else if (format.contains("image")) {
handleImages(model, id);
} else if (format.equals("text/markdown")) {
handleMarkdown(model, id);
}
// add revisions info
listRevisions(model, id, metadata);
return "browse";
}
......@@ -249,6 +269,48 @@ public class BrowseController {
model.addAttribute("tools", tools);
}
private void handleMarkdown(Model model, String id) {
InputStream contentStream;
String content;
try {
contentStream = this.tgcrudClientService.read(id);
content = markdownRenderService.renderHtml(contentStream);
} catch (CrudClientException e) {
//TODO: nicer error strings
log.error("error reading data from crud", e);
content = "error reading data from crud";
} catch (IOException e) {
//TODO: nicer error strings
log.error("error parsing markdown", e);
content = "error parsing markdown";
}
model.addAttribute("htmlContent", content);
}
private void listRevisions(Model model, String id, ResultType metadata) {
String baseUri = id.substring(0, id.indexOf("."));
Revisions revisions = tgsearchClientService.listRevisions(id);
int displayRev = metadata.getObject().getGeneric().getGenerated().getRevision();
BigInteger latestRev = Collections.max(revisions.getRevision());
if(BigInteger.valueOf(displayRev).compareTo(latestRev) < 0) {
model.addAttribute("higherRevisionAvailable", true);
model.addAttribute("latestRevision", latestRev);
model.addAttribute("latestRevisionUri", baseUri+"."+latestRev);
}
if(revisions.getRevision().size() > 0) {
TreeMap revmap = new TreeMap<BigInteger, String>();
for(BigInteger rev : revisions.getRevision()) {
revmap.put(rev, baseUri + "." + rev);
}
model.addAttribute("revisions", revmap);
}
}
private boolean hasIiifManifest(String id) {
IIIFProjects iiip = ClientBuilder.newBuilder().build()
......
......@@ -14,7 +14,7 @@ import org.springframework.web.bind.annotation.RequestParam;
import info.textgrid.namespaces.middleware.tgsearch.FacetType;
import info.textgrid.rep.i18n.I18N;
import info.textgrid.rep.i18n.I18NProvider;
import info.textgrid.rep.service.tgsearch.TgsearchClientService;
import info.textgrid.rep.service.TgsearchClientService;
@Controller
public class BrowseFacetController {
......
......@@ -27,6 +27,9 @@ public class DocController {
@Autowired
private I18NProvider i18nProvider;
@Autowired
private MarkdownRenderService markdownRenderService;
private static final Log log = LogFactory.getLog(DocController.class);
@GetMapping("/")
......@@ -51,11 +54,7 @@ public class DocController {
String content;
try {
InputStreamReader reader = new InputStreamReader(res.getInputStream());
Parser parser = Parser.builder().build();
Node document = parser.parseReader(reader);
HtmlRenderer renderer = HtmlRenderer.builder().build();
content = renderer.render(document);
content = markdownRenderService.renderHtml(res.getInputStream());
} catch (IOException e) {
log.error("error rendering file " + doc, e);
content = "Sorry, there was an error opening file " + doc;
......
package info.textgrid.rep.markdown;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Arrays;
import java.util.List;
import org.commonmark.Extension;
import org.commonmark.ext.front.matter.YamlFrontMatterExtension;
import org.commonmark.ext.front.matter.YamlFrontMatterVisitor;
import org.commonmark.ext.gfm.tables.TablesExtension;
import org.commonmark.ext.heading.anchor.HeadingAnchorExtension;
import org.commonmark.node.Node;
import org.commonmark.parser.Parser;
import org.commonmark.renderer.html.HtmlRenderer;
import org.springframework.stereotype.Service;
import com.fasterxml.jackson.databind.ObjectMapper;
@Service
public class MarkdownRenderService {
// renderer and parser are meant to be threadsafe
// https://github.com/atlassian/commonmark-java#thread-safety
private static Parser parser;
private static HtmlRenderer renderer;
//private static YamlFrontMatterVisitor yamlVisitor;
public MarkdownRenderService() {
List<Extension> extensions = Arrays.asList(
HeadingAnchorExtension.create(),
TablesExtension.create()
// YamlFrontMatterExtension.create()
);
parser = Parser.builder()
.extensions(extensions).build();
renderer = HtmlRenderer.builder()
.extensions(extensions).build();
//yamlVisitor = new YamlFrontMatterVisitor();
}
public String renderHtml(InputStream in) throws IOException {
InputStreamReader reader = new InputStreamReader(in);
Node document = parser.parseReader(reader);
//document.accept(yamlVisitor);
return renderer.render(document);
}
}
......@@ -17,8 +17,8 @@ import org.springframework.web.util.HtmlUtils;
import info.textgrid.namespaces.middleware.tgsearch.Response;
import info.textgrid.rep.i18n.I18N;
import info.textgrid.rep.i18n.I18NProvider;
import info.textgrid.rep.service.tgsearch.TgrepConfigurationService;
import info.textgrid.rep.service.tgsearch.TgsearchClientService;
import info.textgrid.rep.service.TgrepConfigurationService;
import info.textgrid.rep.service.TgsearchClientService;
import info.textgrid.rep.shared.Pager;
import info.textgrid.rep.shared.Utils;
import info.textgrid.rep.shared.ViewMode;
......
package info.textgrid.rep.service.tgsearch;
package info.textgrid.rep.service;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
......
package info.textgrid.rep.service;
import java.io.InputStream;
import javax.annotation.PostConstruct;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import info.textgrid.clients.CrudClient;
import info.textgrid.clients.tgcrud.CrudClientException;
@Service
public class TgcrudClientService {
@Autowired
private TgrepConfigurationService tgrepConfig;
private CrudClient crudClient;
private static final Log log = LogFactory.getLog(TgsearchClientService.class);
@PostConstruct
public void postConstruct() throws CrudClientException {
this.setupClient();
}
private void setupClient() throws CrudClientException {
log.info("setting up tgsearch client for host: " + tgrepConfig.getTextgridHost());
// Create CrudClient with GZIP compression enabled
crudClient = new CrudClient(tgrepConfig.getTextgridHost() + "/1.0/tgcrud-public/TGCrudService")
.enableGzipCompression();
}
public InputStream read(String textgridUri) throws CrudClientException {
return this.crudClient.read().setTextgridUri(textgridUri).execute().getData();
}
public CrudClient getClient() {
return crudClient;
}
}
package info.textgrid.rep.service.tgsearch;
package info.textgrid.rep.service;
import javax.annotation.PostConstruct;
import org.apache.commons.logging.Log;
......
package info.textgrid.rep.service.tgsearch;
package info.textgrid.rep.service;
import java.util.Arrays;
import java.util.List;
......@@ -12,6 +12,7 @@ import info.textgrid.clients.SearchClient;
import info.textgrid.namespaces.middleware.tgsearch.FacetResponse;
import info.textgrid.namespaces.middleware.tgsearch.Response;
import info.textgrid.namespaces.middleware.tgsearch.ResultType;
import info.textgrid.namespaces.middleware.tgsearch.Revisions;
@Service
......@@ -98,6 +99,10 @@ public class TgsearchClientService {
}
public Revisions listRevisions(String id) {
return searchClient.infoQuery().listRevisions(id);
}
public SearchClient getSearchClient() {
return searchClient;
}
......
......@@ -86,6 +86,7 @@ collection-citation-heading=Zitationsvorschlag für diese Kollektion
aggregation-citation-heading=Zitationsvorschlag für diese Aggregation
work-citation-heading=Zitationsvorschlag für dieses Werk
item-citation-heading=Zitationsvorschlag für dieses Objekt
revisions=Revisionen
#usersettings.jsp
usersettings=Persönliche Einstellungen
......
......@@ -86,6 +86,7 @@ collection-citation-heading=Citation Suggestion for this Collection
aggregation-citation-heading=Citation Suggestion for this Aggregation
work-citation-heading=Citation Suggestion for this Work
item-citation-heading=Citation Suggestion for this Object
revisions=Revisions
#usersettings.jsp
usersettings=Personal Settings
......
<%@ page contentType="text/html" pageEncoding="UTF-8" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fmt" uri = "http://java.sun.com/jsp/jstl/fmt" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %>
<%@ taglib uri="http://textgrid.info/rep/utils" prefix="utils" %>
......@@ -83,6 +84,19 @@
</dl>
</section>
<c:if test="${revisions.size() > 1}">
<section class="tgrep sidebar_panel">
<h3 class="tgrep sidebar_subheading">${i18n['revisions']}</h3>
<ul class="tgrep sidebar_list">
<c:forEach items="${revisions}" var="rev">
<li class="tgrep sidebar_item ${rev.key == metadata.object.generic.generated.revision ? '-current' : ''}">
<a href="/browse/${rev.value}">${rev.key}</a>
</li>
</c:forEach>
</ul>
</section>
</c:if>
<section class="tgrep sidebar_panel">
<h3 class="tgrep sidebar_subheading">${i18n['download']}</h3>
<ul class="tgrep sidebar_list">
......@@ -175,9 +189,16 @@
</c:if>
<main class="tgrep main">
<!--
<h1 class="tgrep main_heading">Browse</h1>
-->
<c:if test="${higherRevisionAvailable}">
<fieldset class="tgrep advanced-search_fieldset">
<legend class="tgrep advanced-search_legend">Higher revision available</legend>
You are now viewing revision ${metadata.object.generic.generated.revision} of this document.
A higher revision of this document has been published:
<a href="/browse/${latestRevisionUri}">Revision ${latestRevision}</a>.
</fieldset>
</c:if>
<%@ include file="components/path.jsp" %>
<c:choose>
......@@ -243,7 +264,11 @@
</p>
<a target="_blank" href="${textgridHost}/1.0/tgcrud-public/rest/${metadata.object.generic.generated.textgridUri.value}/data">${i18n['open-html-in-new-window']}</a>
<iframe id="htmlIframe" width="560" height="600" frameborder="0" src="${textgridHost}/1.0/tgcrud-public/rest/${metadata.object.generic.generated.textgridUri.value}/data"></iframe>
</c:when>
</c:when>
<c:when test="${metadata.object.generic.provided.format eq 'text/markdown'}">
${htmlContent}
</c:when>
<c:otherwise>
<p>${i18n['dont-know-what-todo']} ${format}</p>
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment