From d4e46b099faaab5787c6df89898014a1ec34a61c Mon Sep 17 00:00:00 2001
From: AlexanderGirgis <11259344+AlexanderGirgis@users.noreply.github.com>
Date: Sat, 4 Apr 2020 00:17:27 +0200
Subject: [PATCH] Allow basic markup syntax custom previews (#6232)
Add MarkdownFormatter using https://github.com/vsch/flexmark-java/ to format markdown.
To configure Markdown in custom previews add the "Markdown" formatter.
Markdown is enabled by default for the comment field as requested in https://github.com/JabRef/jabref/issues/6194
---
CHANGELOG.md | 1 +
build.gradle | 1 +
external-libraries.txt | 5 ++
src/main/java/module-info.java | 6 ++
.../org/jabref/logic/layout/LayoutEntry.java | 3 +
.../layout/format/MarkdownFormatter.java | 41 ++++++++++
.../migrations/PreferencesMigrations.java | 7 ++
.../jabref/preferences/JabRefPreferences.java | 2 +-
.../layout/format/MarkdownFormatterTest.java | 57 ++++++++++++++
.../migrations/PreferencesMigrationsTest.java | 74 ++++++++++++++++++-
10 files changed, 194 insertions(+), 3 deletions(-)
create mode 100644 src/main/java/org/jabref/logic/layout/format/MarkdownFormatter.java
create mode 100644 src/test/java/org/jabref/logic/layout/format/MarkdownFormatterTest.java
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9aaaa4312fc..d713bed9d8f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -14,6 +14,7 @@ Note that this project **does not** adhere to [Semantic Versioning](http://semve
- We added support for searching ShortScience for an entry through the user's browser. [#6018](https://github.com/JabRef/jabref/pull/6018)
- We updated EditionChecker to permit edition to start with a number. [#6144](https://github.com/JabRef/jabref/issues/6144)
- We added tooltips for most fields in the entry editor containing a short description. [#5847](https://github.com/JabRef/jabref/issues/5847)
+- We added support for basic markdown in custom formatted previews [#6194](https://github.com/JabRef/jabref/issues/6194)
### Changed
diff --git a/build.gradle b/build.gradle
index 0e0031b434e..ebb001d5086 100644
--- a/build.gradle
+++ b/build.gradle
@@ -195,6 +195,7 @@ dependencies {
exclude module: "log4j-core"
}
+ implementation 'com.vladsch.flexmark:flexmark-all:0.60.2'
testImplementation 'io.github.classgraph:classgraph:4.8.66'
testImplementation 'org.junit.jupiter:junit-jupiter:5.6.1'
diff --git a/external-libraries.txt b/external-libraries.txt
index f67e09af7fd..05e11258f7d 100644
--- a/external-libraries.txt
+++ b/external-libraries.txt
@@ -334,6 +334,11 @@ Project: OpenOffice.org
URL: http://www.openoffice.org/api/SDK
License: Apache-2.0
+Id: com.vladsch.flexmark:flexmark-all
+Project: flexmark-java
+URL: https://github.com/vsch/flexmark-java
+License: BSD-2-Clause
+
## Sorted list of runtime dependencies output by gradle
```text
diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java
index 91b721e8a4a..6bbf87db090 100644
--- a/src/main/java/module-info.java
+++ b/src/main/java/module-info.java
@@ -82,4 +82,10 @@
requires org.antlr.antlr4.runtime;
requires flowless;
requires org.apache.tika.core;
+
+ requires flexmark;
+ requires flexmark.ext.gfm.strikethrough;
+ requires flexmark.ext.gfm.tasklist;
+ requires flexmark.util.ast;
+ requires flexmark.util.data;
}
diff --git a/src/main/java/org/jabref/logic/layout/LayoutEntry.java b/src/main/java/org/jabref/logic/layout/LayoutEntry.java
index ec7d6067a7e..ee6632a7046 100644
--- a/src/main/java/org/jabref/logic/layout/LayoutEntry.java
+++ b/src/main/java/org/jabref/logic/layout/LayoutEntry.java
@@ -57,6 +57,7 @@
import org.jabref.logic.layout.format.JournalAbbreviator;
import org.jabref.logic.layout.format.LastPage;
import org.jabref.logic.layout.format.LatexToUnicodeFormatter;
+import org.jabref.logic.layout.format.MarkdownFormatter;
import org.jabref.logic.layout.format.NameFormatter;
import org.jabref.logic.layout.format.NoSpaceBetweenAbbreviations;
import org.jabref.logic.layout.format.NotFoundFormatter;
@@ -536,6 +537,8 @@ private LayoutFormatter getLayoutFormatterByName(String name) {
return new WrapContent();
case "WrapFileLinks":
return new WrapFileLinks(prefs.getFileLinkPreferences());
+ case "Markdown":
+ return new MarkdownFormatter();
default:
return null;
}
diff --git a/src/main/java/org/jabref/logic/layout/format/MarkdownFormatter.java b/src/main/java/org/jabref/logic/layout/format/MarkdownFormatter.java
new file mode 100644
index 00000000000..e8eadf2bded
--- /dev/null
+++ b/src/main/java/org/jabref/logic/layout/format/MarkdownFormatter.java
@@ -0,0 +1,41 @@
+package org.jabref.logic.layout.format;
+
+import java.util.List;
+import java.util.Objects;
+
+import org.jabref.logic.layout.LayoutFormatter;
+
+import com.vladsch.flexmark.ext.gfm.strikethrough.StrikethroughExtension;
+import com.vladsch.flexmark.ext.gfm.tasklist.TaskListExtension;
+import com.vladsch.flexmark.html.HtmlRenderer;
+import com.vladsch.flexmark.parser.Parser;
+import com.vladsch.flexmark.util.ast.Node;
+import com.vladsch.flexmark.util.data.MutableDataSet;
+
+public class MarkdownFormatter implements LayoutFormatter {
+
+ private final Parser parser;
+ private final HtmlRenderer renderer;
+
+ public MarkdownFormatter() {
+ MutableDataSet options = new MutableDataSet();
+ options.set(Parser.EXTENSIONS, List.of(
+ StrikethroughExtension.create(),
+ TaskListExtension.create()
+ ));
+
+ parser = Parser.builder(options).build();
+ renderer = HtmlRenderer.builder(options).build();
+ }
+
+ @Override
+ public String format(final String fieldText) {
+ Objects.requireNonNull(fieldText, "Field Text should not be null, when handed to formatter");
+
+ Node document = parser.parse(fieldText);
+ String html = renderer.render(document);
+
+ // workaround HTMLChars transforming "\n" into
by returning a one liner
+ return html.replaceAll("\\r\\n|\\r|\\n", " ").trim();
+ }
+}
diff --git a/src/main/java/org/jabref/migrations/PreferencesMigrations.java b/src/main/java/org/jabref/migrations/PreferencesMigrations.java
index d841a6c0c08..27f6c67fed3 100644
--- a/src/main/java/org/jabref/migrations/PreferencesMigrations.java
+++ b/src/main/java/org/jabref/migrations/PreferencesMigrations.java
@@ -49,6 +49,7 @@ public static void runMigrations() {
addCrossRefRelatedFieldsForAutoComplete(Globals.prefs);
upgradePreviewStyleFromReviewToComment(Globals.prefs);
upgradeColumnPreferences(Globals.prefs);
+ upgradePreviewStyleAllowMarkdown(Globals.prefs);
}
/**
@@ -301,6 +302,12 @@ static void upgradePreviewStyleFromReviewToComment(JabRefPreferences prefs) {
prefs.setPreviewStyle(migratedStyle);
}
+ static void upgradePreviewStyleAllowMarkdown(JabRefPreferences prefs) {
+ String currentPreviewStyle = prefs.getPreviewStyle();
+ String migratedStyle = currentPreviewStyle.replace("\\format[HTMLChars]{\\comment}", "\\format[Markdown,HTMLChars]{\\comment}");
+ prefs.setPreviewStyle(migratedStyle);
+ }
+
/**
* The former preferences default of columns was a simple list of strings ("author;title;year;..."). Since 5.0
* the preferences store the type of the column too, so that the formerly hardwired columns like the graphic groups
diff --git a/src/main/java/org/jabref/preferences/JabRefPreferences.java b/src/main/java/org/jabref/preferences/JabRefPreferences.java
index c3cc1adef51..45326331cba 100644
--- a/src/main/java/org/jabref/preferences/JabRefPreferences.java
+++ b/src/main/java/org/jabref/preferences/JabRefPreferences.java
@@ -672,7 +672,7 @@ private JabRefPreferences() {
+ "\\begin{year}\\year\\end{year}\\begin{volume}, \\volume\\end{volume}"
+ "\\begin{pages}, \\format[FormatPagesForHTML]{\\pages} \\end{pages}__NEWLINE__"
+ "\\begin{abstract}
Abstract: \\format[HTMLChars]{\\abstract} \\end{abstract}__NEWLINE__"
- + "\\begin{comment}
Comment: \\format[HTMLChars]{\\comment} \\end{comment}"
+ + "\\begin{comment}
Comment: \\format[Markdown,HTMLChars]{\\comment} \\end{comment}"
+ "__NEWLINE__
Hello World
", markdownFormatter.format("Hello World")); + } + + @Test + void formatWhenFormattingComplexMarkupThenReturnsOnlyOneLine() { + assertFalse(markdownFormatter.format("Markup\n\n* list item one\n* list item 2\n\n rest").contains("\n")); + } + + @Test + void formatWhenFormattingEmptyStringThenReturnsEmptyString() { + assertEquals("", markdownFormatter.format("")); + } + + @Test + void formatWhenFormattingNullThenThrowsException() { + Exception exception = assertThrows(NullPointerException.class, () -> markdownFormatter.format(null)); + assertEquals("Field Text should not be null, when handed to formatter", exception.getMessage()); + } + + @Test + void formatWhenMarkupContainingStrikethroughThenContainsMatchingDel() { + // Only test strikethrough extension + assertTrue(markdownFormatter.format("a ~~b~~ b").contains("