diff --git a/jpro-mdfx/build.gradle b/jpro-mdfx/build.gradle index a40663fe..22ee7671 100644 --- a/jpro-mdfx/build.gradle +++ b/jpro-mdfx/build.gradle @@ -1,4 +1,5 @@ dependencies { + implementation project(':jpro-youtube') implementation "com.vladsch.flexmark:flexmark-ext-gfm-strikethrough:$FLEXMARK_VERSION" implementation "com.vladsch.flexmark:flexmark-ext-gfm-tasklist:$FLEXMARK_VERSION" implementation "com.vladsch.flexmark:flexmark-ext-tables:$FLEXMARK_VERSION" diff --git a/jpro-mdfx/example/build.gradle b/jpro-mdfx/example/build.gradle index 32d6acc4..745912cf 100644 --- a/jpro-mdfx/example/build.gradle +++ b/jpro-mdfx/example/build.gradle @@ -12,7 +12,7 @@ dependencies { javafx { version = "$JAVAFX_VERSION" - modules = ['javafx.controls', 'javafx.fxml'] + modules = ['javafx.controls', 'javafx.fxml', 'javafx.web'] } mainClassName = 'one.jpro.platform.mdfx.example.MarkdownViewSample' diff --git a/jpro-mdfx/example/src/main/java/one/jpro/platform/mdfx/example/MarkdownViewSample.java b/jpro-mdfx/example/src/main/java/one/jpro/platform/mdfx/example/MarkdownViewSample.java index 25fbc078..56e4d964 100644 --- a/jpro-mdfx/example/src/main/java/one/jpro/platform/mdfx/example/MarkdownViewSample.java +++ b/jpro-mdfx/example/src/main/java/one/jpro/platform/mdfx/example/MarkdownViewSample.java @@ -12,6 +12,7 @@ import javafx.scene.layout.Priority; import javafx.stage.Stage; import one.jpro.platform.mdfx.MarkdownView; +import one.jpro.platform.mdfx.extensions.YoutubeExtension; import org.apache.commons.io.IOUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -57,7 +58,10 @@ public Parent createRoot(Stage stage) { } }); - final var markdownView = new MarkdownView() { + var markdownExtensions = MarkdownView.defaultExtensions(); + markdownExtensions.add(YoutubeExtension.create()); + + final var markdownView = new MarkdownView("", markdownExtensions) { @Override protected List getDefaultStylesheets() { Optional defaultStylesheet = Optional.ofNullable(getClass().getResource(SAMPLE_CSS)) diff --git a/jpro-mdfx/example/src/main/resources/one/jpro/platform/mdfx/example/sample.md b/jpro-mdfx/example/src/main/resources/one/jpro/platform/mdfx/example/sample.md index 0a7ec13d..499a2417 100644 --- a/jpro-mdfx/example/src/main/resources/one/jpro/platform/mdfx/example/sample.md +++ b/jpro-mdfx/example/src/main/resources/one/jpro/platform/mdfx/example/sample.md @@ -99,3 +99,12 @@ public class HelloWorld { >> This is a nested block quote. > > - Author + + +## Images and extensions + +CoPilots favorite music: +![alt](youtube:dQw4w9WgXcQ) + +Some Image: +![alt](https://www.jpro.one/app/default/resourcesencoded/cp:/1/1/one/jpro/img/landing/DUKE-forward.png) diff --git a/jpro-mdfx/src/main/java/module-info.java b/jpro-mdfx/src/main/java/module-info.java index 6e93e067..8274acd5 100644 --- a/jpro-mdfx/src/main/java/module-info.java +++ b/jpro-mdfx/src/main/java/module-info.java @@ -13,7 +13,10 @@ requires flexmark.util.sequence; requires flexmark.util.data; requires flexmark.util.collection; + requires one.jpro.platform.youtube; opens one.jpro.platform.mdfx; exports one.jpro.platform.mdfx; + exports one.jpro.platform.mdfx.extensions; + opens one.jpro.platform.mdfx.extensions; } \ No newline at end of file diff --git a/jpro-mdfx/src/main/java/one/jpro/platform/mdfx/MarkdownView.java b/jpro-mdfx/src/main/java/one/jpro/platform/mdfx/MarkdownView.java index 866ed80a..5eb8bfef 100644 --- a/jpro-mdfx/src/main/java/one/jpro/platform/mdfx/MarkdownView.java +++ b/jpro-mdfx/src/main/java/one/jpro/platform/mdfx/MarkdownView.java @@ -6,6 +6,7 @@ import javafx.scene.Node; import javafx.scene.image.Image; import javafx.scene.layout.VBox; +import one.jpro.platform.mdfx.extensions.ImageExtension; import one.jpro.platform.mdfx.impl.AdaptiveImage; import one.jpro.platform.mdfx.impl.MDFXNodeHelper; @@ -15,9 +16,21 @@ public class MarkdownView extends VBox { + private List extensions = new ArrayList<>(); + private final SimpleStringProperty mdString = new SimpleStringProperty(""); public MarkdownView(String mdString) { + this(mdString, defaultExtensions()); + } + + public MarkdownView(String mdString, List extensions) { + // Check whether only one extension has the scheme null + if(extensions.stream().filter(e -> e.getScheme() == null).count() > 1) { + throw new IllegalArgumentException("Only one extension can have a scheme of null"); + } + + this.extensions = extensions; this.mdString.set(mdString); this.mdString.addListener((p,o,n) -> updateContent()); Optional.ofNullable(MarkdownView.class.getResource("/one/jpro/platform/mdfx/mdfx.css")) @@ -65,6 +78,28 @@ public void setLink(Node node, String link, String description) { } public Node generateImage(String url) { + // Let's find an extension with a matching scheme + // But be aware, that the url is sometimes relative without a scheme + + var res = extensions.stream() + .filter(e -> e.getScheme() != null && url.startsWith(e.getScheme())) + .findFirst(); + + if(res.isEmpty()) { + res = extensions.stream() + .filter(e -> e.getScheme() == null) + .findFirst(); + } + + return res.get().getFunction().apply(url, this); + } + + + public static List defaultExtensions() { + return new ArrayList<>(List.of(DEFAULT_IMAGE_EXTENSION)); + } + + static ImageExtension DEFAULT_IMAGE_EXTENSION = new ImageExtension(null, (url, view) -> { if(url.isEmpty()) { return new Group(); } else { @@ -73,10 +108,9 @@ public Node generateImage(String url) { // The TextFlow does not limit the width of its node based on the available width // As a workaround, we bind to the width of the MarkDownView. - r.maxWidthProperty().bind(widthProperty()); + r.maxWidthProperty().bind(view.widthProperty()); return r; } - - } + }); } diff --git a/jpro-mdfx/src/main/java/one/jpro/platform/mdfx/extensions/ImageExtension.java b/jpro-mdfx/src/main/java/one/jpro/platform/mdfx/extensions/ImageExtension.java new file mode 100644 index 00000000..d5b98f81 --- /dev/null +++ b/jpro-mdfx/src/main/java/one/jpro/platform/mdfx/extensions/ImageExtension.java @@ -0,0 +1,30 @@ +package one.jpro.platform.mdfx.extensions; + +import javafx.scene.Node; +import one.jpro.platform.mdfx.MarkdownView; + +import java.util.function.BiFunction; + +/** + * An extension for the MarkdownView. + * The scheme is used to identify the extension in the markdown string. + * + * An scheme of null is used to identify the default image extension. + */ +public class ImageExtension { + String scheme; + BiFunction function; + + public ImageExtension(String scheme, BiFunction function) { + this.scheme = scheme; + this.function = function; + } + + public String getScheme() { + return scheme; + } + + public BiFunction getFunction() { + return function; + } +} diff --git a/jpro-mdfx/src/main/java/one/jpro/platform/mdfx/extensions/YoutubeExtension.java b/jpro-mdfx/src/main/java/one/jpro/platform/mdfx/extensions/YoutubeExtension.java new file mode 100644 index 00000000..15b44d7f --- /dev/null +++ b/jpro-mdfx/src/main/java/one/jpro/platform/mdfx/extensions/YoutubeExtension.java @@ -0,0 +1,20 @@ +package one.jpro.platform.mdfx.extensions; + +import javafx.scene.control.Label; +import javafx.scene.layout.VBox; +import one.jpro.platform.youtube.YoutubeNode; + +public class YoutubeExtension { + + public static ImageExtension create() { + return new ImageExtension( + "youtube", (url, view) -> { + var uri = java.net.URI.create(url); + var schemeSpecificPart = uri.getSchemeSpecificPart(); + var node = new VBox(new Label(""), new YoutubeNode(schemeSpecificPart)); + node.prefWidthProperty().bind(view.widthProperty()); + node.maxWidthProperty().bind(view.widthProperty()); + return node; + }); + } +}