Commented code for clarity
This commit is contained in:
@@ -77,10 +77,13 @@ public class MarkdownFromURLMacro extends BaseMacro implements Macro
|
|||||||
@Override
|
@Override
|
||||||
public String execute(Map<String, String> parameters, String bodyContent, ConversionContext conversionContext) throws MacroExecutionException
|
public String execute(Map<String, String> parameters, String bodyContent, ConversionContext conversionContext) throws MacroExecutionException
|
||||||
{
|
{
|
||||||
|
//If the macro was not left blank, run the macro. Otherwise, return an empty string.
|
||||||
if (bodyContent != null) {
|
if (bodyContent != null) {
|
||||||
|
//Include highlight.js in the webpage.
|
||||||
|
//Adds something like <script type="text/javascript" src="path-to-javascript-file.js"></script> to the <head> of the page
|
||||||
pageBuilderService.assembler().resources().requireWebResource("com.atlassian.plugins.confluence.markdown.confluence-markdown-macro:highlightjs");
|
pageBuilderService.assembler().resources().requireWebResource("com.atlassian.plugins.confluence.markdown.confluence-markdown-macro:highlightjs");
|
||||||
|
|
||||||
|
//Set options for flexmark. See flexmark documentation for more info.
|
||||||
MutableDataSet options = new MutableDataSet();
|
MutableDataSet options = new MutableDataSet();
|
||||||
|
|
||||||
options.set(Parser.EXTENSIONS, Arrays.asList(
|
options.set(Parser.EXTENSIONS, Arrays.asList(
|
||||||
@@ -98,26 +101,35 @@ public class MarkdownFromURLMacro extends BaseMacro implements Macro
|
|||||||
|
|
||||||
));
|
));
|
||||||
|
|
||||||
|
|
||||||
|
//JavaScript for syntax highlighting. See highlight.js documentation for more info.
|
||||||
String highlightjs = "<script>\n" +
|
String highlightjs = "<script>\n" +
|
||||||
"AJS.$('[data-macro-name=\"markdown\"] code').each(function(i, block) {\n" +
|
"AJS.$('[data-macro-name=\"markdown\"] code').each(function(i, block) {\n" +
|
||||||
" hljs.highlightBlock(block);\n" +
|
" hljs.highlightBlock(block);\n" +
|
||||||
" });\n" +
|
" });\n" +
|
||||||
"</script>";
|
"</script>";
|
||||||
|
|
||||||
|
//Define a custom exception for trying to import from a private Bitbucket repository
|
||||||
class privateRepositoryException extends Exception {
|
class privateRepositoryException extends Exception {
|
||||||
public privateRepositoryException(String message) {
|
public privateRepositoryException(String message) {
|
||||||
super(message);
|
super(message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Set up parser and renderer for flexmark.
|
||||||
Parser parser = Parser.builder(options).build();
|
Parser parser = Parser.builder(options).build();
|
||||||
HtmlRenderer renderer = HtmlRenderer.builder(options).build();
|
HtmlRenderer renderer = HtmlRenderer.builder(options).build();
|
||||||
|
|
||||||
String exceptionsToReturn = "";
|
String exceptionsToReturn = "";
|
||||||
String html = "";
|
String html = "";
|
||||||
String toParse = "";
|
String toParse = "";
|
||||||
|
|
||||||
|
//Import markdown from a URL and render it to html.
|
||||||
|
//Enclosed in a try/catch block in order to catch exceptions and return error messages to the user
|
||||||
try {
|
try {
|
||||||
|
//Create a URL object from the URL typed in by the user,
|
||||||
|
// then fetch the URL content line by line and store each line temporarily in inputLine.
|
||||||
|
//Concatenate all the lines into toParse and trim leading and trailing whitespace.
|
||||||
URL importFrom = new URL(bodyContent);
|
URL importFrom = new URL(bodyContent);
|
||||||
BufferedReader in = new BufferedReader(
|
BufferedReader in = new BufferedReader(
|
||||||
new InputStreamReader(importFrom.openStream())
|
new InputStreamReader(importFrom.openStream())
|
||||||
@@ -128,12 +140,17 @@ public class MarkdownFromURLMacro extends BaseMacro implements Macro
|
|||||||
}
|
}
|
||||||
in.close();
|
in.close();
|
||||||
toParse = toParse.trim();
|
toParse = toParse.trim();
|
||||||
|
|
||||||
|
|
||||||
|
//If the content of the URL is a file in a private Bitbucket repository, then throw an exception.
|
||||||
|
//Otherwise, parse and render the markdown.
|
||||||
if (toParse.startsWith("<html>\n<head>\n <title>OpenID transaction in progress</title>")) {
|
if (toParse.startsWith("<html>\n<head>\n <title>OpenID transaction in progress</title>")) {
|
||||||
throw new privateRepositoryException("Cannot import from private repository.");
|
throw new privateRepositoryException("Cannot import from private repository.");
|
||||||
}else {
|
}else {
|
||||||
Node document = parser.parse(toParse);
|
Node document = parser.parse(toParse);
|
||||||
html = renderer.render(document) + highlightjs;
|
html = renderer.render(document) + highlightjs;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
catch (MalformedURLException u) {
|
catch (MalformedURLException u) {
|
||||||
exceptionsToReturn = exceptionsToReturn + "<strong>Error with Markdown From URL macro: Invalid URL.</strong><br>Please enter a valid URL. If you are not trying to import markdown from a URL, use the Markdown macro instead of the Markdown from URL macro.<br>For support <a href='https://community.atlassian.com/t5/tag/addon-com.atlassian.plugins.confluence.markdown.confluence-markdown-macro/tg-p'>visit our Q&A in the Atlassian Community</a>. You can ask a new question by clicking the \"Create\" button on the top right of the Q&A.<br>";
|
exceptionsToReturn = exceptionsToReturn + "<strong>Error with Markdown From URL macro: Invalid URL.</strong><br>Please enter a valid URL. If you are not trying to import markdown from a URL, use the Markdown macro instead of the Markdown from URL macro.<br>For support <a href='https://community.atlassian.com/t5/tag/addon-com.atlassian.plugins.confluence.markdown.confluence-markdown-macro/tg-p'>visit our Q&A in the Atlassian Community</a>. You can ask a new question by clicking the \"Create\" button on the top right of the Q&A.<br>";
|
||||||
@@ -148,9 +165,12 @@ public class MarkdownFromURLMacro extends BaseMacro implements Macro
|
|||||||
exceptionsToReturn = exceptionsToReturn + "<strong>Error with Markdown From URL macro: Unexpected error.</strong><br>" + e.toString() + "<br>For support <a href='https://community.atlassian.com/t5/tag/addon-com.atlassian.plugins.confluence.markdown.confluence-markdown-macro/tg-p'>visit our Q&A in the Atlassian Community</a>. You can ask a new question by clicking the \"Create\" button on the top right of the Q&A.<br>";
|
exceptionsToReturn = exceptionsToReturn + "<strong>Error with Markdown From URL macro: Unexpected error.</strong><br>" + e.toString() + "<br>For support <a href='https://community.atlassian.com/t5/tag/addon-com.atlassian.plugins.confluence.markdown.confluence-markdown-macro/tg-p'>visit our Q&A in the Atlassian Community</a>. You can ask a new question by clicking the \"Create\" button on the top right of the Q&A.<br>";
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
|
//If there were exceptions, set the html to return to an error message (or multiple)
|
||||||
if (exceptionsToReturn != "") {
|
if (exceptionsToReturn != "") {
|
||||||
html = "<p style='background: #ffe0e0; border-radius: 5px; padding: 10px;'>" + exceptionsToReturn + "</p>";
|
html = "<p style='background: #ffe0e0; border-radius: 5px; padding: 10px;'>" + exceptionsToReturn + "</p>";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Return the output, which is either rendered markdown or an error message.
|
||||||
return html;
|
return html;
|
||||||
}
|
}
|
||||||
}else {
|
}else {
|
||||||
|
|||||||
@@ -70,9 +70,11 @@ public class MarkdownMacro extends BaseMacro implements Macro
|
|||||||
public String execute(Map<String, String> parameters, String bodyContent, ConversionContext conversionContext) throws MacroExecutionException
|
public String execute(Map<String, String> parameters, String bodyContent, ConversionContext conversionContext) throws MacroExecutionException
|
||||||
{
|
{
|
||||||
|
|
||||||
|
//Include highlight.js in the webpage.
|
||||||
|
//Adds something like <script type="text/javascript" src="path-to-javascript-file.js"></script> to the <head> of the page
|
||||||
pageBuilderService.assembler().resources().requireWebResource("com.atlassian.plugins.confluence.markdown.confluence-markdown-macro:highlightjs");
|
pageBuilderService.assembler().resources().requireWebResource("com.atlassian.plugins.confluence.markdown.confluence-markdown-macro:highlightjs");
|
||||||
|
|
||||||
|
//Set options for flexmark. See flexmark documentation for more info.
|
||||||
MutableDataSet options = new MutableDataSet();
|
MutableDataSet options = new MutableDataSet();
|
||||||
|
|
||||||
options.set(Parser.EXTENSIONS, Arrays.asList(
|
options.set(Parser.EXTENSIONS, Arrays.asList(
|
||||||
@@ -89,16 +91,21 @@ public class MarkdownMacro extends BaseMacro implements Macro
|
|||||||
YouTubeLinkExtension.create()
|
YouTubeLinkExtension.create()
|
||||||
|
|
||||||
));
|
));
|
||||||
|
|
||||||
|
|
||||||
|
//JavaScript for syntax highlighting. See highlight.js documentation for more info.
|
||||||
String highlightjs = "<script>\n" +
|
String highlightjs = "<script>\n" +
|
||||||
"AJS.$('[data-macro-name=\"markdown\"] code').each(function(i, block) {\n" +
|
"AJS.$('[data-macro-name=\"markdown\"] code').each(function(i, block) {\n" +
|
||||||
" hljs.highlightBlock(block);\n" +
|
" hljs.highlightBlock(block);\n" +
|
||||||
" });\n" +
|
" });\n" +
|
||||||
"</script>";
|
"</script>";
|
||||||
|
|
||||||
|
//Set up parser and renderer for flexmark.
|
||||||
Parser parser = Parser.builder(options).build();
|
Parser parser = Parser.builder(options).build();
|
||||||
HtmlRenderer renderer = HtmlRenderer.builder(options).build();
|
HtmlRenderer renderer = HtmlRenderer.builder(options).build();
|
||||||
|
|
||||||
|
// Parse and render the body content from markdown to HTML,
|
||||||
|
// then return the output along with the JavaScript for syntax highlighting
|
||||||
Node document = parser.parse(bodyContent);
|
Node document = parser.parse(bodyContent);
|
||||||
String html = renderer.render(document) + highlightjs;
|
String html = renderer.render(document) + highlightjs;
|
||||||
return html;
|
return html;
|
||||||
|
|||||||
@@ -37,27 +37,51 @@ public class MarkdownFromURLUnitTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testMarkdownRendering() throws MacroExecutionException, MalformedURLException {
|
public void testMarkdownRendering() throws MacroExecutionException, MalformedURLException {
|
||||||
Mockito.when(pageBuilderService.assembler()).thenReturn(webResourceAssembler);
|
//Mock methods for pageBuilderService.assembler().resources().requireWebResource("com.atlassian.plugins.confluence.markdown.confluence-markdown-macro:highlightjs");
|
||||||
|
Mockito.when(pageBuilderService.assembler()).thenReturn(webResourceAssembler);
|
||||||
Mockito.when(webResourceAssembler.resources()).thenReturn(requiredResources);
|
Mockito.when(webResourceAssembler.resources()).thenReturn(requiredResources);
|
||||||
Mockito.when(requiredResources.requireWebResource("com.atlassian.plugins.confluence.markdown.confluence-markdown-macro:highlightjs")).thenReturn(requiredResources);
|
Mockito.when(requiredResources.requireWebResource("com.atlassian.plugins.confluence.markdown.confluence-markdown-macro:highlightjs")).thenReturn(requiredResources);
|
||||||
|
|
||||||
|
/*Test that markdown is correctly retrieved from a URL and rendered into HTML*/
|
||||||
|
// Run the macro using a URL that points to a file containing test markdown,
|
||||||
|
// then assert that *Italic* was correctly rendered into <em>Italic</em>
|
||||||
String file = new File("src/test/resources/testMarkdown.md").toURI().toURL().toString();
|
String file = new File("src/test/resources/testMarkdown.md").toURI().toURL().toString();
|
||||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||||
String output = markdownMacro.execute(new HashMap(), file, conversionContext);
|
String output = markdownMacro.execute(new HashMap(), file, conversionContext);
|
||||||
assertThat(Pattern.matches("[\\S\\s]*<em>Italic</em>[\\S\\s]*", output), is(true));
|
assertThat(Pattern.matches("[\\S\\s]*<em>Italic</em>[\\S\\s]*", output), is(true)); //Uses [\S\s] (anything that is either whitespace or not whitespace) instead of . (any character) because . does not match newline characters.
|
||||||
}
|
}
|
||||||
@Test
|
@Test
|
||||||
public void testErrorHandling() throws MacroExecutionException, MalformedURLException {
|
public void testErrorHandling() throws MacroExecutionException, MalformedURLException {
|
||||||
Mockito.when(pageBuilderService.assembler()).thenReturn(webResourceAssembler);
|
//Mock methods for pageBuilderService.assembler().resources().requireWebResource("com.atlassian.plugins.confluence.markdown.confluence-markdown-macro:highlightjs");
|
||||||
|
Mockito.when(pageBuilderService.assembler()).thenReturn(webResourceAssembler);
|
||||||
Mockito.when(webResourceAssembler.resources()).thenReturn(requiredResources);
|
Mockito.when(webResourceAssembler.resources()).thenReturn(requiredResources);
|
||||||
Mockito.when(requiredResources.requireWebResource("com.atlassian.plugins.confluence.markdown.confluence-markdown-macro:highlightjs")).thenReturn(requiredResources);
|
Mockito.when(requiredResources.requireWebResource("com.atlassian.plugins.confluence.markdown.confluence-markdown-macro:highlightjs")).thenReturn(requiredResources);
|
||||||
String input1 = new File("src/test/resources/nonexistantfile.md").toURI().toURL().toString();
|
|
||||||
|
/*Test error handling of nonexistent URLs*/
|
||||||
|
// Run the macro using a URL pointing to a file that does not exist,
|
||||||
|
// then assert that the output of the macro is not an empty string
|
||||||
|
// i.e. that it returned an error message instead of simply breaking.
|
||||||
|
String input1 = new File("src/test/resources/nonexistentfile.md").toURI().toURL().toString();
|
||||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||||
String output1 = markdownMacro.execute(new HashMap(), input1, conversionContext);
|
String output1 = markdownMacro.execute(new HashMap(), input1, conversionContext);
|
||||||
assertThat(output1, is(not("")));
|
assertThat(output1, is(not("")));
|
||||||
|
|
||||||
|
|
||||||
|
/*Test error handling of invalid URLs*/
|
||||||
|
// Run the macro using a string that is not a URL,
|
||||||
|
// then assert that the output of the macro is not an empty string
|
||||||
|
// i.e. that it returned an error message instead of simply breaking.
|
||||||
String input2 = "not_a_URL";
|
String input2 = "not_a_URL";
|
||||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||||
String output2 = markdownMacro.execute(new HashMap(), input2, conversionContext);
|
String output2 = markdownMacro.execute(new HashMap(), input2, conversionContext);
|
||||||
assertThat(output2, is(not("")));
|
assertThat(output2, is(not("")));
|
||||||
|
|
||||||
|
|
||||||
|
/*Test error handling of importing from a private Bitbucket repository*/
|
||||||
|
// Run the macro using a URL pointing to a file that mimics the text
|
||||||
|
// returned when trying to import from a private Bitbucket repository,
|
||||||
|
// then assert that the output of the macro is not an empty string
|
||||||
|
// i.e. that it returned an error message instead of simply breaking.
|
||||||
String input3 = new File("src/test/resources/testPrivateBitbucket.html").toURI().toURL().toString();
|
String input3 = new File("src/test/resources/testPrivateBitbucket.html").toURI().toURL().toString();
|
||||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||||
String output3 = markdownMacro.execute(new HashMap(), input3, conversionContext);
|
String output3 = markdownMacro.execute(new HashMap(), input3, conversionContext);
|
||||||
|
|||||||
@@ -33,21 +33,34 @@ public class MarkdownUnitTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testMarkdownRendering() throws MacroExecutionException {
|
public void testMarkdownRendering() throws MacroExecutionException {
|
||||||
|
//Mock methods for pageBuilderService.assembler().resources().requireWebResource("com.atlassian.plugins.confluence.markdown.confluence-markdown-macro:highlightjs");
|
||||||
Mockito.when(pageBuilderService.assembler()).thenReturn(webResourceAssembler);
|
Mockito.when(pageBuilderService.assembler()).thenReturn(webResourceAssembler);
|
||||||
Mockito.when(webResourceAssembler.resources()).thenReturn(requiredResources);
|
Mockito.when(webResourceAssembler.resources()).thenReturn(requiredResources);
|
||||||
Mockito.when(requiredResources.requireWebResource("com.atlassian.plugins.confluence.markdown.confluence-markdown-macro:highlightjs")).thenReturn(requiredResources);
|
Mockito.when(requiredResources.requireWebResource("com.atlassian.plugins.confluence.markdown.confluence-markdown-macro:highlightjs")).thenReturn(requiredResources);
|
||||||
|
|
||||||
|
|
||||||
|
/*Test that markdown is correctly rendered into HTML*/
|
||||||
|
// Run the macro using input text of *Italic*,
|
||||||
|
// then assert that *Italic* was correctly rendered into <em>Italic</em>
|
||||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||||
String output = markdownMacro.execute(new HashMap(), "*Italic*", conversionContext);
|
String output = markdownMacro.execute(new HashMap(), "*Italic*", conversionContext);
|
||||||
assertTrue(Pattern.matches("[\\S\\s]*<em>Italic</em>[\\S\\s]*", output));
|
assertTrue(Pattern.matches("[\\S\\s]*<em>Italic</em>[\\S\\s]*", output)); //Uses [\S\s] (anything that is either whitespace or not whitespace) instead of . (any character) because . does not match newline characters.
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testSyntaxHighlighting() throws MacroExecutionException {
|
public void testSyntaxHighlighting() throws MacroExecutionException {
|
||||||
|
//Mock methods for pageBuilderService.assembler().resources().requireWebResource("com.atlassian.plugins.confluence.markdown.confluence-markdown-macro:highlightjs");
|
||||||
Mockito.when(pageBuilderService.assembler()).thenReturn(webResourceAssembler);
|
Mockito.when(pageBuilderService.assembler()).thenReturn(webResourceAssembler);
|
||||||
Mockito.when(webResourceAssembler.resources()).thenReturn(requiredResources);
|
Mockito.when(webResourceAssembler.resources()).thenReturn(requiredResources);
|
||||||
Mockito.when(requiredResources.requireWebResource("com.atlassian.plugins.confluence.markdown.confluence-markdown-macro:highlightjs")).thenReturn(requiredResources);
|
Mockito.when(requiredResources.requireWebResource("com.atlassian.plugins.confluence.markdown.confluence-markdown-macro:highlightjs")).thenReturn(requiredResources);
|
||||||
|
|
||||||
|
|
||||||
|
/*Test that the correct JavaScript is returned for highlight.js to work*/
|
||||||
|
// Run the macro using input of a line of code in a code block,
|
||||||
|
// then assert that the correct JavaScript was returned.
|
||||||
|
// Intended only as a temporary test until I can program a better one
|
||||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||||
String output = markdownMacro.execute(new HashMap(), "public class JavaClass {}", conversionContext);
|
String output = markdownMacro.execute(new HashMap(), "'public class JavaClass {}'", conversionContext);
|
||||||
assertTrue(Pattern.matches("[\\S\\s]*<script>\\sAJS\\.\\$\\('\\[data\\-macro\\-name=\"markdown\"\\] code'\\)\\.each\\(function\\(i, block\\) \\{\\s hljs\\.highlightBlock\\(block\\);\\s \\}\\);\\s<\\/script>[\\S\\s]*", output));
|
assertTrue(Pattern.matches("[\\S\\s]*<script>\\sAJS\\.\\$\\('\\[data\\-macro\\-name=\"markdown\"\\] code'\\)\\.each\\(function\\(i, block\\) \\{\\s hljs\\.highlightBlock\\(block\\);\\s \\}\\);\\s<\\/script>[\\S\\s]*", output));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user