|
7 | 7 | import javafx.scene.control.CheckBox; |
8 | 8 | import javafx.scene.control.ComboBox; |
9 | 9 | import javafx.scene.control.ButtonType; |
| 10 | +import javafx.scene.control.Tab; |
| 11 | +import javafx.scene.control.TabPane; |
10 | 12 | import javafx.scene.control.ProgressBar; |
11 | 13 | import javafx.scene.control.TextArea; |
12 | 14 | import javafx.scene.control.Button; |
|
44 | 46 | import javafx.geometry.Side; |
45 | 47 | import javafx.scene.layout.VBox; |
46 | 48 | import javafx.scene.image.Image; |
| 49 | +import javafx.scene.web.WebView; |
47 | 50 | import javafx.stage.FileChooser; |
48 | 51 | import javafx.util.Pair; |
49 | 52 | import javafx.util.Duration; |
50 | 53 | import org.kordamp.ikonli.javafx.FontIcon; |
51 | 54 | import org.kordamp.ikonli.materialdesign2.MaterialDesignL; |
52 | 55 | import org.kordamp.ikonli.materialdesign2.MaterialDesignC; |
53 | 56 | import org.kordamp.ikonli.materialdesign2.MaterialDesignP; |
| 57 | +import com.vladsch.flexmark.html.HtmlRenderer; |
| 58 | +import com.vladsch.flexmark.parser.Parser; |
| 59 | +import com.vladsch.flexmark.util.data.MutableDataSet; |
54 | 60 |
|
55 | 61 | import java.awt.Desktop; |
56 | 62 | import java.io.File; |
@@ -81,12 +87,18 @@ public record EditorResult(Note savedNote, Set<String> newTags) { |
81 | 87 | @FXML |
82 | 88 | private TextField titleField; |
83 | 89 | @FXML |
84 | | - private ComboBox<Note.Priority> priorityComboBox; |
| 90 | + private TabPane contentTabPane; |
85 | 91 | @FXML |
86 | | - private DatePicker dueDatePicker; |
| 92 | + private Tab previewTab; |
87 | 93 | @FXML |
88 | 94 | private TextArea contentArea; |
89 | 95 | @FXML |
| 96 | + private WebView contentPreview; |
| 97 | + @FXML |
| 98 | + private ComboBox<Note.Priority> priorityComboBox; |
| 99 | + @FXML |
| 100 | + private DatePicker dueDatePicker; |
| 101 | + @FXML |
90 | 102 | private ProgressBar goalsProgressBar; |
91 | 103 | @FXML |
92 | 104 | private VBox goalsContainer; // Placeholder for the TreeView |
@@ -150,7 +162,14 @@ public record EditorResult(Note savedNote, Set<String> newTags) { |
150 | 162 |
|
151 | 163 | private ContextMenu noteSuggestionsPopup; |
152 | 164 | private UUID noteToOpen = null; |
| 165 | + private final Parser markdownParser; |
| 166 | + private final HtmlRenderer markdownRenderer; |
153 | 167 |
|
| 168 | + public NoteDetailViewController() { |
| 169 | + MutableDataSet options = new MutableDataSet(); |
| 170 | + this.markdownParser = Parser.builder(options).build(); |
| 171 | + this.markdownRenderer = HtmlRenderer.builder(options).build(); |
| 172 | + } |
154 | 173 |
|
155 | 174 | @FXML |
156 | 175 | private void initialize() { |
@@ -248,6 +267,15 @@ protected void updateItem(Note.Comment item, boolean empty) { |
248 | 267 | if (referenceImagesFlowPane != null) { |
249 | 268 | setupReferencePaneDragAndDrop(); |
250 | 269 | } |
| 270 | + |
| 271 | + // --- NEW: Markdown Preview Setup --- |
| 272 | + if (previewTab != null) { |
| 273 | + previewTab.setOnSelectionChanged(event -> { |
| 274 | + if (previewTab.isSelected()) { |
| 275 | + renderMarkdown(); |
| 276 | + } |
| 277 | + }); |
| 278 | + } |
251 | 279 | } |
252 | 280 |
|
253 | 281 | public void setDialogStage(Stage dialogStage) { |
@@ -539,6 +567,70 @@ private Node createTagLabel(String tag) { |
539 | 567 | return tagView; |
540 | 568 | } |
541 | 569 |
|
| 570 | + private void renderMarkdown() { |
| 571 | + if (contentArea == null || contentPreview == null) { |
| 572 | + return; |
| 573 | + } |
| 574 | + String markdownText = contentArea.getText(); |
| 575 | + com.vladsch.flexmark.util.ast.Node document = markdownParser.parse(markdownText); |
| 576 | + String rawHtml = markdownRenderer.render(document); |
| 577 | + |
| 578 | + // This HTML uses CSS variables defined by the AtlantaFX theme, so it will adapt to light/dark mode. |
| 579 | + String fullHtml = """ |
| 580 | + <html> |
| 581 | + <head> |
| 582 | + <style> |
| 583 | + body { |
| 584 | + font-family: -apple-system, "system-ui", "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; |
| 585 | + background-color: var(-color-bg-default, #22272e); |
| 586 | + color: var(-color-fg-default, #adbac7); |
| 587 | + line-height: 1.6; |
| 588 | + } |
| 589 | + a { color: var(-color-accent-fg, #9370DB); text-decoration: none; } |
| 590 | + a:hover { text-decoration: underline; } |
| 591 | + h1, h2, h3, h4, h5, h6 { |
| 592 | + color: var(-color-header-fg, #adbac7); |
| 593 | + border-bottom: 1px solid var(-color-border-muted, #444c56); |
| 594 | + padding-bottom: 0.3em; |
| 595 | + margin-top: 24px; |
| 596 | + margin-bottom: 16px; |
| 597 | + } |
| 598 | + code { |
| 599 | + font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace; |
| 600 | + background-color: var(-color-neutral-muted, rgba(173, 186, 199, 0.1)); |
| 601 | + padding: 0.2em 0.4em; |
| 602 | + margin: 0; |
| 603 | + font-size: 85%%; |
| 604 | + border-radius: 6px; |
| 605 | + } |
| 606 | + pre { |
| 607 | + background-color: var(-color-canvas-subtle, #2d333b); |
| 608 | + padding: 16px; |
| 609 | + overflow: auto; |
| 610 | + border-radius: 6px; |
| 611 | + } |
| 612 | + pre > code { padding: 0; margin: 0; font-size: 100%%; background-color: transparent; border: 0; } |
| 613 | + blockquote { |
| 614 | + border-left: 0.25em solid var(-color-border-default, #444c56); |
| 615 | + padding: 0 1em; |
| 616 | + color: var(-color-fg-muted, #768390); |
| 617 | + } |
| 618 | + table { border-collapse: collapse; width: 100%%; } |
| 619 | + th, td { border: 1px solid var(-color-border-muted, #444c56); padding: 8px 13px; } |
| 620 | + th { font-weight: bold; background-color: var(-color-canvas-subtle, #2d333b); } |
| 621 | + img { max-width: 100%%; height: auto; border-radius: 6px; } |
| 622 | + ul, ol { padding-left: 2em; } |
| 623 | + </style> |
| 624 | + </head> |
| 625 | + <body> |
| 626 | + %s |
| 627 | + </body> |
| 628 | + </html> |
| 629 | + """.formatted(rawHtml); |
| 630 | + |
| 631 | + contentPreview.getEngine().loadContent(fullHtml); |
| 632 | + } |
| 633 | + |
542 | 634 | // --- Reference Image Methods --- |
543 | 635 |
|
544 | 636 | private void setupReferencePaneDragAndDrop() { |
|
0 commit comments