Skip to content

Commit

Permalink
Allow basic markup syntax custom previews (#6232)
Browse files Browse the repository at this point in the history
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 #6194
  • Loading branch information
AlexanderGirgis authored Apr 3, 2020
1 parent 50c2b41 commit d4e46b0
Show file tree
Hide file tree
Showing 10 changed files with 194 additions and 3 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
5 changes: 5 additions & 0 deletions external-libraries.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 6 additions & 0 deletions src/main/java/module-info.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
3 changes: 3 additions & 0 deletions src/main/java/org/jabref/logic/layout/LayoutEntry.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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 <br> by returning a one liner
return html.replaceAll("\\r\\n|\\r|\\n", " ").trim();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ public static void runMigrations() {
addCrossRefRelatedFieldsForAutoComplete(Globals.prefs);
upgradePreviewStyleFromReviewToComment(Globals.prefs);
upgradeColumnPreferences(Globals.prefs);
upgradePreviewStyleAllowMarkdown(Globals.prefs);
}

/**
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -672,7 +672,7 @@ private JabRefPreferences() {
+ "\\begin{year}<b>\\year</b>\\end{year}\\begin{volume}<i>, \\volume</i>\\end{volume}"
+ "\\begin{pages}, \\format[FormatPagesForHTML]{\\pages} \\end{pages}__NEWLINE__"
+ "\\begin{abstract}<BR><BR><b>Abstract: </b> \\format[HTMLChars]{\\abstract} \\end{abstract}__NEWLINE__"
+ "\\begin{comment}<BR><BR><b>Comment: </b> \\format[HTMLChars]{\\comment} \\end{comment}"
+ "\\begin{comment}<BR><BR><b>Comment: </b> \\format[Markdown,HTMLChars]{\\comment} \\end{comment}"
+ "</dd>__NEWLINE__<p></p></font>");

// set default theme
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package org.jabref.logic.layout.format;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;

class MarkdownFormatterTest {

private MarkdownFormatter markdownFormatter;

@BeforeEach
void setUp() {
markdownFormatter = new MarkdownFormatter();
}

@Test
void formatWhenFormattingPlainTextThenReturnsTextWrappedInParagraph() {
assertEquals("<p>Hello World</p>", 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("<del>b</del>"));
}

@Test
void formatWhenMarkupContainingTaskListThenContainsFormattedTaskList() {
String actual = markdownFormatter.format("Some text\n" +
"* [ ] open task\n" +
"* [x] closed task\n\n" +
"some other text");
// Only test list items
assertTrue(actual.contains("<li class=\"task-list-item\"><input type=\"checkbox\" class=\"task-list-item-checkbox\" disabled=\"disabled\" readonly=\"readonly\" />&nbsp;open task</li>"));
assertTrue(actual.contains("<li class=\"task-list-item\"><input type=\"checkbox\" class=\"task-list-item-checkbox\" checked=\"checked\" disabled=\"disabled\" readonly=\"readonly\" />&nbsp;closed task</li>"));
}
}
74 changes: 72 additions & 2 deletions src/test/java/org/jabref/migrations/PreferencesMigrationsTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ class PreferencesMigrationsTest {
private final String[] oldStylePatterns = new String[]{"\\bibtexkey",
"\\bibtexkey\\begin{title} - \\format[RemoveBrackets]{\\title}\\end{title}"};
private final String[] newStylePatterns = new String[]{"[bibtexkey]",
"[bibtexkey] - [title]"};
"[bibtexkey] - [title]"};

@BeforeEach
void setUp() {
Expand Down Expand Up @@ -118,6 +118,76 @@ void testPreviewStyle() {
verify(prefs).setPreviewStyle(newPreviewStyle);
}

@Test
void upgradePreviewStyleAllowMarkupDefault() {
String oldPreviewStyle = "<font face=\"sans-serif\">"
+ "<b><i>\\bibtextype</i><a name=\"\\bibtexkey\">\\begin{bibtexkey} (\\bibtexkey)</a>"
+ "\\end{bibtexkey}</b><br>__NEWLINE__"
+ "\\begin{author} \\format[Authors(LastFirst,Initials,Semicolon,Amp),HTMLChars]{\\author}<BR>\\end{author}__NEWLINE__"
+ "\\begin{editor} \\format[Authors(LastFirst,Initials,Semicolon,Amp),HTMLChars]{\\editor} "
+ "<i>(\\format[IfPlural(Eds.,Ed.)]{\\editor})</i><BR>\\end{editor}__NEWLINE__"
+ "\\begin{title} \\format[HTMLChars]{\\title} \\end{title}<BR>__NEWLINE__"
+ "\\begin{chapter} \\format[HTMLChars]{\\chapter}<BR>\\end{chapter}__NEWLINE__"
+ "\\begin{journal} <em>\\format[HTMLChars]{\\journal}, </em>\\end{journal}__NEWLINE__"
// Include the booktitle field for @inproceedings, @proceedings, etc.
+ "\\begin{booktitle} <em>\\format[HTMLChars]{\\booktitle}, </em>\\end{booktitle}__NEWLINE__"
+ "\\begin{school} <em>\\format[HTMLChars]{\\school}, </em>\\end{school}__NEWLINE__"
+ "\\begin{institution} <em>\\format[HTMLChars]{\\institution}, </em>\\end{institution}__NEWLINE__"
+ "\\begin{publisher} <em>\\format[HTMLChars]{\\publisher}, </em>\\end{publisher}__NEWLINE__"
+ "\\begin{year}<b>\\year</b>\\end{year}\\begin{volume}<i>, \\volume</i>\\end{volume}"
+ "\\begin{pages}, \\format[FormatPagesForHTML]{\\pages} \\end{pages}__NEWLINE__"
+ "\\begin{abstract}<BR><BR><b>Abstract: </b> \\format[HTMLChars]{\\abstract} \\end{abstract}__NEWLINE__"
+ "\\begin{comment}<BR><BR><b>Comment: </b> \\format[HTMLChars]{\\comment} \\end{comment}"
+ "</dd>__NEWLINE__<p></p></font>";

String newPreviewStyle = "<font face=\"sans-serif\">"
+ "<b><i>\\bibtextype</i><a name=\"\\bibtexkey\">\\begin{bibtexkey} (\\bibtexkey)</a>"
+ "\\end{bibtexkey}</b><br>__NEWLINE__"
+ "\\begin{author} \\format[Authors(LastFirst,Initials,Semicolon,Amp),HTMLChars]{\\author}<BR>\\end{author}__NEWLINE__"
+ "\\begin{editor} \\format[Authors(LastFirst,Initials,Semicolon,Amp),HTMLChars]{\\editor} "
+ "<i>(\\format[IfPlural(Eds.,Ed.)]{\\editor})</i><BR>\\end{editor}__NEWLINE__"
+ "\\begin{title} \\format[HTMLChars]{\\title} \\end{title}<BR>__NEWLINE__"
+ "\\begin{chapter} \\format[HTMLChars]{\\chapter}<BR>\\end{chapter}__NEWLINE__"
+ "\\begin{journal} <em>\\format[HTMLChars]{\\journal}, </em>\\end{journal}__NEWLINE__"
// Include the booktitle field for @inproceedings, @proceedings, etc.
+ "\\begin{booktitle} <em>\\format[HTMLChars]{\\booktitle}, </em>\\end{booktitle}__NEWLINE__"
+ "\\begin{school} <em>\\format[HTMLChars]{\\school}, </em>\\end{school}__NEWLINE__"
+ "\\begin{institution} <em>\\format[HTMLChars]{\\institution}, </em>\\end{institution}__NEWLINE__"
+ "\\begin{publisher} <em>\\format[HTMLChars]{\\publisher}, </em>\\end{publisher}__NEWLINE__"
+ "\\begin{year}<b>\\year</b>\\end{year}\\begin{volume}<i>, \\volume</i>\\end{volume}"
+ "\\begin{pages}, \\format[FormatPagesForHTML]{\\pages} \\end{pages}__NEWLINE__"
+ "\\begin{abstract}<BR><BR><b>Abstract: </b> \\format[HTMLChars]{\\abstract} \\end{abstract}__NEWLINE__"
+ "\\begin{comment}<BR><BR><b>Comment: </b> \\format[Markdown,HTMLChars]{\\comment} \\end{comment}"
+ "</dd>__NEWLINE__<p></p></font>";

prefs.setPreviewStyle(oldPreviewStyle);
when(prefs.getPreviewStyle()).thenReturn(oldPreviewStyle);

PreferencesMigrations.upgradePreviewStyleAllowMarkdown(prefs);

verify(prefs).setPreviewStyle(newPreviewStyle);
}

@Test
void upgradePreviewStyleAllowMarkupCustomized() {
String oldPreviewStyle = "<font face=\"sans-serif\">"
+ "My highly customized format only using comments:<br>"
+ "\\begin{comment} Something: \\format[HTMLChars]{\\comment} special \\end{comment}"
+ "</dd>__NEWLINE__<p></p></font>";

String newPreviewStyle = "<font face=\"sans-serif\">"
+ "My highly customized format only using comments:<br>"
+ "\\begin{comment} Something: \\format[Markdown,HTMLChars]{\\comment} special \\end{comment}"
+ "</dd>__NEWLINE__<p></p></font>";

prefs.setPreviewStyle(oldPreviewStyle);
when(prefs.getPreviewStyle()).thenReturn(oldPreviewStyle);

PreferencesMigrations.upgradePreviewStyleAllowMarkdown(prefs);

verify(prefs).setPreviewStyle(newPreviewStyle);
}

@Test
void testUpgradeColumnPreferencesAlreadyMigrated() {
List<String> columnNames = Arrays.asList("entrytype", "author/editor", "title", "year", "journal/booktitle", "bibtexkey", "printed");
Expand All @@ -138,7 +208,7 @@ void testUpgradeColumnPreferencesFromWithoutTypes() {
List<String> columnWidths = Arrays.asList("75", "300", "470", "60", "130", "100", "30");
List<String> updatedNames = Arrays.asList("groups", "files", "linked_id", "field:entrytype", "field:author/editor", "field:title", "field:year", "field:journal/booktitle", "field:bibtexkey", "special:printed");
List<String> updatedWidths = Arrays.asList("28", "28", "28", "75", "300", "470", "60", "130", "100", "30");
List<String> newSortTypes = Arrays.asList("ASCENDING","ASCENDING","ASCENDING","ASCENDING","ASCENDING","ASCENDING","ASCENDING","ASCENDING","ASCENDING","ASCENDING");
List<String> newSortTypes = Arrays.asList("ASCENDING", "ASCENDING", "ASCENDING", "ASCENDING", "ASCENDING", "ASCENDING", "ASCENDING", "ASCENDING", "ASCENDING", "ASCENDING");

when(prefs.getStringList(JabRefPreferences.COLUMN_NAMES)).thenReturn(columnNames);
when(prefs.getStringList(JabRefPreferences.COLUMN_WIDTHS)).thenReturn(columnWidths);
Expand Down

0 comments on commit d4e46b0

Please sign in to comment.