This is an automated email from the ASF dual-hosted git repository.

hansva pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/hop.git


The following commit(s) were added to refs/heads/main by this push:
     new 1f3cebbfe4 issue #7111 : Preview in HTML for MarkDown files (#7396)
1f3cebbfe4 is described below

commit 1f3cebbfe4343a4bc026c8dce3cab1d451145ba7
Author: Matt Casters <[email protected]>
AuthorDate: Thu Jul 2 14:27:49 2026 +0200

    issue #7111 : Preview in HTML for MarkDown files (#7396)
    
    * issue #7111 : Preview in HTML for MarkDown files
    
    * issue #7111 : Preview in HTML for MarkDown files (lib sync)
---
 plugins/misc/documentation/pom.xml                 |   4 -
 plugins/transforms/textfile/pom.xml                |   6 +
 .../transforms/types/MarkDownExplorerFileType.java |  13 +-
 .../types/MarkDownExplorerFileTypeHandler.java     | 259 +++++++++++++++++++++
 .../types/messages/messages_en_US.properties       |   1 +
 .../types/messages/messages_pt_BR.properties       |   1 +
 pom.xml                                            |   1 +
 7 files changed, 275 insertions(+), 10 deletions(-)

diff --git a/plugins/misc/documentation/pom.xml 
b/plugins/misc/documentation/pom.xml
index 25b231fea9..41fc1f4d1a 100644
--- a/plugins/misc/documentation/pom.xml
+++ b/plugins/misc/documentation/pom.xml
@@ -29,10 +29,6 @@
     <packaging>jar</packaging>
     <name>Hop Plugins Miscellaneous Documentation</name>
 
-    <properties>
-        <commonmark.version>0.27.1</commonmark.version>
-    </properties>
-
     <dependencyManagement>
         <dependencies>
             <dependency>
diff --git a/plugins/transforms/textfile/pom.xml 
b/plugins/transforms/textfile/pom.xml
index b53fe4f30f..5cd80103f0 100644
--- a/plugins/transforms/textfile/pom.xml
+++ b/plugins/transforms/textfile/pom.xml
@@ -40,6 +40,12 @@
             <version>${project.version}</version>
             <scope>provided</scope>
         </dependency>
+        <dependency>
+            <groupId>org.commonmark</groupId>
+            <artifactId>commonmark</artifactId>
+            <version>${commonmark.version}</version>
+            <scope>provided</scope>
+        </dependency>
     </dependencies>
 
     <build>
diff --git 
a/plugins/transforms/textfile/src/main/java/org/apache/hop/pipeline/transforms/types/MarkDownExplorerFileType.java
 
b/plugins/transforms/textfile/src/main/java/org/apache/hop/pipeline/transforms/types/MarkDownExplorerFileType.java
index 27f797e4da..ec4664b625 100644
--- 
a/plugins/transforms/textfile/src/main/java/org/apache/hop/pipeline/transforms/types/MarkDownExplorerFileType.java
+++ 
b/plugins/transforms/textfile/src/main/java/org/apache/hop/pipeline/transforms/types/MarkDownExplorerFileType.java
@@ -6,13 +6,14 @@
  * (the "License"); you may not use this file except in compliance with
  * the License.  You may obtain a copy of the License at
  *
- *       http://www.apache.org/licenses/LICENSE-2.0
+ *      http://www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
+ *
  */
 
 package org.apache.hop.pipeline.transforms.types;
@@ -42,7 +43,7 @@ import 
org.apache.hop.ui.hopgui.perspective.explorer.file.types.text.BaseTextExp
     description = "MarkDown file handling in the explorer perspective",
     image = "markdown.svg")
 public class MarkDownExplorerFileType
-    extends BaseTextExplorerFileType<TextExplorerFileTypeHandler> {
+    extends BaseTextExplorerFileType<MarkDownExplorerFileTypeHandler> {
   public static final Class<?> PKG = MarkDownExplorerFileType.class;
 
   public static final String ACTION_ID_NEW_MARKDOWN = "NewMarkdown";
@@ -64,17 +65,17 @@ public class MarkDownExplorerFileType
   }
 
   @Override
-  public TextExplorerFileTypeHandler createFileTypeHandler(
+  public MarkDownExplorerFileTypeHandler createFileTypeHandler(
       HopGui hopGui, ExplorerPerspective perspective, ExplorerFile file) {
-    return new TextExplorerFileTypeHandler(hopGui, perspective, file);
+    return new MarkDownExplorerFileTypeHandler(hopGui, perspective, file);
   }
 
   @Override
   public IHopFileTypeHandler newFile(HopGui hopGui, IVariables variables) 
throws HopException {
     ExplorerFile explorerFile = new ExplorerFile("New markdown", null, this);
     ExplorerPerspective explorerPerspective = 
ExplorerPerspective.getInstance();
-    TextExplorerFileTypeHandler fileHandler =
-        new TextExplorerFileTypeHandler(hopGui, explorerPerspective, 
explorerFile);
+    MarkDownExplorerFileTypeHandler fileHandler =
+        new MarkDownExplorerFileTypeHandler(hopGui, explorerPerspective, 
explorerFile);
 
     explorerPerspective.addFile(fileHandler);
     explorerPerspective.activate();
diff --git 
a/plugins/transforms/textfile/src/main/java/org/apache/hop/pipeline/transforms/types/MarkDownExplorerFileTypeHandler.java
 
b/plugins/transforms/textfile/src/main/java/org/apache/hop/pipeline/transforms/types/MarkDownExplorerFileTypeHandler.java
new file mode 100644
index 0000000000..7a0508b07b
--- /dev/null
+++ 
b/plugins/transforms/textfile/src/main/java/org/apache/hop/pipeline/transforms/types/MarkDownExplorerFileTypeHandler.java
@@ -0,0 +1,259 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.apache.hop.pipeline.transforms.types;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.OutputStream;
+import java.nio.charset.StandardCharsets;
+import org.apache.hop.core.Props;
+import org.apache.hop.core.gui.plugin.GuiPlugin;
+import org.apache.hop.core.gui.plugin.toolbar.GuiToolbarElement;
+import org.apache.hop.core.gui.plugin.toolbar.GuiToolbarElementType;
+import org.apache.hop.ui.core.PropsUi;
+import org.apache.hop.ui.core.dialog.ErrorDialog;
+import org.apache.hop.ui.core.gui.GuiToolbarWidgets;
+import org.apache.hop.ui.core.gui.IToolbarContainer;
+import org.apache.hop.ui.hopgui.ContentEditorFacade;
+import org.apache.hop.ui.hopgui.HopGui;
+import org.apache.hop.ui.hopgui.ToolbarFacade;
+import org.apache.hop.ui.hopgui.perspective.explorer.ExplorerFile;
+import org.apache.hop.ui.hopgui.perspective.explorer.ExplorerPerspective;
+import 
org.apache.hop.ui.hopgui.perspective.explorer.file.types.text.BaseTextExplorerFileTypeHandler;
+import org.apache.hop.ui.util.EnvironmentUtils;
+import org.commonmark.node.Node;
+import org.commonmark.parser.Parser;
+import org.commonmark.renderer.html.HtmlRenderer;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.layout.FormAttachment;
+import org.eclipse.swt.layout.FormData;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+
+/** How do we handle a markdown file in the file explorer perspective? */
+@GuiPlugin
+public class MarkDownExplorerFileTypeHandler extends 
BaseTextExplorerFileTypeHandler {
+
+  private static final Class<?> PKG = MarkDownExplorerFileType.class;
+
+  public static final String TOOLBAR_PARENT_ID = 
"MarkDownExplorerFileTypeHandler-Toolbar";
+  public static final String TOOLBAR_ITEM_PREVIEW =
+      "MarkDownExplorerFileTypeHandler-ToolBar-Preview";
+
+  public MarkDownExplorerFileTypeHandler(
+      HopGui hopGui, ExplorerPerspective perspective, ExplorerFile 
explorerFile) {
+    super(hopGui, perspective, explorerFile);
+  }
+
+  @Override
+  protected String getLanguageId() {
+    return "markdown";
+  }
+
+  @Override
+  public void renderFile(Composite composite) {
+    // Create the toolbar container using the facade
+    IToolbarContainer toolBarContainer =
+        ToolbarFacade.createToolbarContainer(composite, SWT.WRAP | SWT.LEFT | 
SWT.HORIZONTAL);
+    Control toolBarControl = toolBarContainer.getControl();
+
+    // Create the toolbar widgets using GuiToolbarWidgets
+    GuiToolbarWidgets toolBarWidgets = new GuiToolbarWidgets();
+    toolBarWidgets.registerGuiPluginObject(this);
+    toolBarWidgets.createToolbarWidgets(toolBarContainer, TOOLBAR_PARENT_ID);
+
+    // Layout data for the toolbar control
+    FormData fdToolBar = new FormData();
+    fdToolBar.left = new FormAttachment(0, 0);
+    fdToolBar.right = new FormAttachment(100, 0);
+    fdToolBar.top = new FormAttachment(0, 0);
+    toolBarControl.setLayoutData(fdToolBar);
+    toolBarControl.pack();
+    PropsUi.setLook(toolBarControl, Props.WIDGET_STYLE_TOOLBAR);
+
+    // Create the standard editor widget
+    editorWidget = ContentEditorFacade.createContentEditor(composite, 
getLanguageId());
+
+    // Position the editor widget below the toolbar
+    Control editorControl = editorWidget.getControl();
+    FormData fdEditor = new FormData();
+    fdEditor.left = new FormAttachment(0, 0);
+    fdEditor.right = new FormAttachment(100, 0);
+    fdEditor.top = new FormAttachment(toolBarControl, 0);
+    fdEditor.bottom = new FormAttachment(100, 0);
+    editorControl.setLayoutData(fdEditor);
+
+    // If it's a new file, there's no need to reload it
+    if (this.getFilename() != null) {
+      reload();
+    }
+
+    reloadListener = true;
+    editorWidget.addModifyListener(
+        e -> {
+          if (reloadListener) {
+            this.setChanged();
+            perspective.updateGui();
+          }
+        });
+  }
+
+  @GuiToolbarElement(
+      root = TOOLBAR_PARENT_ID,
+      id = TOOLBAR_ITEM_PREVIEW,
+      toolTip = "i18n::MarkDownFileTypeHandler.Preview.Tooltip",
+      type = GuiToolbarElementType.BUTTON,
+      image = "ui/images/preview.svg")
+  public void previewMarkdown() {
+    try {
+      String markdown = editorWidget.getText();
+
+      // Parse markdown to HTML body content
+      Parser parser = Parser.builder().build();
+      HtmlRenderer renderer = HtmlRenderer.builder().build();
+      Node document = parser.parse(markdown);
+      String htmlContent = renderer.render(document);
+
+      // Wrap the content with styled CSS, supporting dark mode if configured
+      StringBuilder html = new StringBuilder();
+      html.append("<!DOCTYPE html>\n<html>\n<head>\n<meta 
charset=\"UTF-8\">\n<style>\n");
+      html.append("body {\n");
+      html.append(
+          "  font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe 
UI', Roboto, Helvetica, Arial, sans-serif;\n");
+      html.append("  line-height: 1.625;\n");
+      html.append("  margin: 40px auto;\n");
+      html.append("  max-width: 800px;\n");
+      html.append("  padding: 0 20px;\n");
+      html.append("}\n");
+      html.append("h1, h2, h3, h4, h5, h6 {\n");
+      html.append("  font-weight: 600;\n");
+      html.append("}\n");
+      html.append("h1 {\n");
+      html.append("  font-size: 2.25rem;\n");
+      html.append("  padding-bottom: 0.3em;\n");
+      html.append("  border-bottom: 1px solid;\n");
+      html.append("}\n");
+      html.append("h2 {\n");
+      html.append("  font-size: 1.5rem;\n");
+      html.append("  padding-bottom: 0.3em;\n");
+      html.append("  border-bottom: 1px solid;\n");
+      html.append("}\n");
+      html.append("a {\n");
+      html.append("  text-decoration: none;\n");
+      html.append("  font-weight: 500;\n");
+      html.append("}\n");
+      html.append("a:hover {\n");
+      html.append("  text-decoration: underline;\n");
+      html.append("}\n");
+      html.append("pre, code {\n");
+      html.append(
+          "  font-family: SFMono-Regular, Consolas, 'Liberation Mono', Menlo, 
monospace;\n");
+      html.append("  font-size: 0.9em;\n");
+      html.append("  border-radius: 6px;\n");
+      html.append("}\n");
+      html.append("code {\n");
+      html.append("  padding: 0.2em 0.4em;\n");
+      html.append("}\n");
+      html.append("pre {\n");
+      html.append("  padding: 16px;\n");
+      html.append("  overflow-x: auto;\n");
+      html.append("}\n");
+      html.append("pre code {\n");
+      html.append("  padding: 0;\n");
+      html.append("  background-color: transparent;\n");
+      html.append("  border-radius: 0;\n");
+      html.append("  border: none;\n");
+      html.append("}\n");
+      html.append("blockquote {\n");
+      html.append("  margin: 1.5em 0;\n");
+      html.append("  padding: 0.5em 1em;\n");
+      html.append("  border-left-width: 4px;\n");
+      html.append("  border-left-style: solid;\n");
+      html.append("}\n");
+
+      if (PropsUi.getInstance().isDarkMode()) {
+        html.append("body {\n");
+        html.append("  background-color: #0b0f19;\n");
+        html.append("  color: #94a3b8;\n");
+        html.append("}\n");
+        html.append("h1, h2, h3, h4, h5, h6 {\n");
+        html.append("  color: #f8fafc;\n");
+        html.append("}\n");
+        html.append("h1, h2 {\n");
+        html.append("  border-bottom-color: #1e293b;\n");
+        html.append("}\n");
+        html.append("a {\n");
+        html.append("  color: #38bdf8;\n");
+        html.append("}\n");
+        html.append("pre, code {\n");
+        html.append("  background-color: #1e293b;\n");
+        html.append("  border: 1px solid #334155;\n");
+        html.append("  color: #e2e8f0;\n");
+        html.append("}\n");
+        html.append("blockquote {\n");
+        html.append("  border-left-color: #475569;\n");
+        html.append("  color: #94a3b8;\n");
+        html.append("  background-color: #0f172a;\n");
+        html.append("}\n");
+      } else {
+        html.append("body {\n");
+        html.append("  background-color: #f8fafc;\n");
+        html.append("  color: #334155;\n");
+        html.append("}\n");
+        html.append("h1, h2, h3, h4, h5, h6 {\n");
+        html.append("  color: #0f172a;\n");
+        html.append("}\n");
+        html.append("h1, h2 {\n");
+        html.append("  border-bottom-color: #e2e8f0;\n");
+        html.append("}\n");
+        html.append("a {\n");
+        html.append("  color: #2563eb;\n");
+        html.append("}\n");
+        html.append("pre, code {\n");
+        html.append("  background-color: #f1f5f9;\n");
+        html.append("  border: 1px solid #e2e8f0;\n");
+        html.append("  color: #334155;\n");
+        html.append("}\n");
+        html.append("blockquote {\n");
+        html.append("  border-left-color: #cbd5e1;\n");
+        html.append("  color: #64748b;\n");
+        html.append("  background-color: #f8fafc;\n");
+        html.append("}\n");
+      }
+      html.append("</style>\n</head>\n<body>\n");
+      html.append(htmlContent);
+      html.append("\n</body>\n</html>");
+
+      // Create a temporary file
+      File tempFile = File.createTempFile("markdown_preview_", ".html");
+      tempFile.deleteOnExit();
+
+      // Write the HTML to the temp file
+      try (OutputStream outputStream = new FileOutputStream(tempFile)) {
+        outputStream.write(html.toString().getBytes(StandardCharsets.UTF_8));
+      }
+
+      // Open in browser
+      EnvironmentUtils.getInstance().openUrl(tempFile.toURI().toString());
+    } catch (Exception e) {
+      new ErrorDialog(
+          hopGui.getShell(), "Error", "Error generating or displaying Markdown 
preview", e);
+    }
+  }
+}
diff --git 
a/plugins/transforms/textfile/src/main/resources/org/apache/hop/pipeline/transforms/types/messages/messages_en_US.properties
 
b/plugins/transforms/textfile/src/main/resources/org/apache/hop/pipeline/transforms/types/messages/messages_en_US.properties
index 95bd580999..c5e97cfede 100644
--- 
a/plugins/transforms/textfile/src/main/resources/org/apache/hop/pipeline/transforms/types/messages/messages_en_US.properties
+++ 
b/plugins/transforms/textfile/src/main/resources/org/apache/hop/pipeline/transforms/types/messages/messages_en_US.properties
@@ -20,3 +20,4 @@ MarkDownFileType.GuiAction.NewMarkDown.Name=Markdown
 MarkDownFileType.GuiAction.NewMarkDown.Tooltip=Create a new Markdown file
 MarkDownFileType.ErrorDialog.NewMarkDownCreation.Header=Error creating 
Markdown file
 MarkDownFileType.ErrorDialog.NewMarkDownCreation.Message=An error occurred 
while creating the Markdown file:
+MarkDownFileTypeHandler.Preview.Tooltip=Preview the HTML version of this 
Markdown file in a browser
diff --git 
a/plugins/transforms/textfile/src/main/resources/org/apache/hop/pipeline/transforms/types/messages/messages_pt_BR.properties
 
b/plugins/transforms/textfile/src/main/resources/org/apache/hop/pipeline/transforms/types/messages/messages_pt_BR.properties
index 6bc211ef01..e656253da5 100644
--- 
a/plugins/transforms/textfile/src/main/resources/org/apache/hop/pipeline/transforms/types/messages/messages_pt_BR.properties
+++ 
b/plugins/transforms/textfile/src/main/resources/org/apache/hop/pipeline/transforms/types/messages/messages_pt_BR.properties
@@ -21,3 +21,4 @@ MarkDownFileType.GuiAction.NewMarkDown.Name=Markdown
 MarkDownFileType.GuiAction.NewMarkDown.Tooltip=Criar um novo arquivo Markdown
 MarkDownFileType.ErrorDialog.NewMarkDownCreation.Header=Erro ao criar o 
arquivo Markdown
 MarkDownFileType.ErrorDialog.NewMarkDownCreation.Message=Um erro ocorreu ao 
criar o arquivo Markdown:
+MarkDownFileTypeHandler.Preview.Tooltip=Visualizar a versão HTML deste arquivo 
Markdown no navegador
diff --git a/pom.xml b/pom.xml
index 89d800c5a1..fc6e04269d 100644
--- a/pom.xml
+++ b/pom.xml
@@ -104,6 +104,7 @@
         <azure-sdk-bom.version>1.2.26</azure-sdk-bom.version>
         
<build.dependenciesDirectory>${project.build.directory}/dependency</build.dependenciesDirectory>
         <byte-buddy.version>1.18.7</byte-buddy.version>
+        <commonmark.version>0.29.0</commonmark.version>
         <cyclonedx-maven-plugin.version>2.8.1</cyclonedx-maven-plugin.version>
         <grpc.version>1.80.0</grpc.version>
         <hadoop.version>3.4.2</hadoop.version>

Reply via email to