Project: Markdown editor
This project builds a side-by-side markdown editor with live preview — a useful tool that ties together the file I/O patterns from Module 3 with the HtmlWindow display from lesson 4.4.
Note: in this module we’re using the simple
Wx::HTMLwindow, we will enhance the project in the next module using the more advancedWx::Webviewcontrol
Type markdown on the left. The preview on the right updates automatically when you pause. Open and save .md files. Export the rendered HTML.
|
|
File structure
markdown_editor/
├── main.rb
├── Gemfile
└── lib/
├── editor_frame.rb
├── models/
│ └── markdown_document.rb
└── panels/
├── editor_panel.rb
└── preview_panel.rbThe model
MarkdownDocument follows the same pattern as the Document class in lesson 3.4 — it holds file path, content, and dirty state, with load, save, and save_as methods. The only addition is to_html and to_full_html, which convert the markdown content using kramdown:
|
|
input: 'GFM' selects the GitHub Flavoured Markdown parser, which adds support for fenced code blocks, tables, and strikethrough. The model knows nothing about widgets — it is pure Ruby.
The editor panel
A Wx::TextCtrl with a monospace font and Wx::TE_DONTWRAP to prevent line wrapping. The callback pattern from lesson 3.4 applies: evt_key_up is registered directly on the @editor widget (not the parent panel), and calls the on_change callback when the user types.
The monospace font is important for markdown editing — it makes heading markers, list indicators, and code fences easy to read in the source.
The preview panel
A Wx::HTML::HtmlWindow with set_standard_fonts(13) for a readable base size. The update(html) method calls set_page to replace the displayed content. Link clicks are intercepted — external http links open in the default browser; other links are ignored.
The frame
The frame coordinates the two panels and handles all file operations. Two details worth highlighting:
Debounced preview updates
Converting markdown and updating the preview on every single keystroke would cause noticeable lag for long documents. Instead, the app uses a one-shot timer as a debounce:
|
|
Every keypress restarts the timer. The preview only updates when the user pauses for 300ms. Wx::TIMER_ONE_SHOT means the timer fires once and stops — it does not repeat.
The @loading flag
Setting @editor.content = programmatically triggers evt_key_up which would call on_text_changed and mark the document dirty. The @loading flag suppresses this — the same pattern established in lesson 3.4.
|
|
HtmlWindow limitations visible here
Open the sample content and look at the table and the code block. They render, but:
- Code blocks have no syntax highlighting
- The table has basic styling only — no alternating row colours, no CSS
- Font choices are limited
In Module 5 we will replace PreviewPanel with a WebView-based equivalent. The model and editor panel stay exactly the same — only the preview changes. That comparison will show precisely what WebView adds over HtmlWindow.
What this project demonstrates
Every concept from Module 4 either appears directly or is referenced:
- Device contexts — the monospace editor font is set exactly as in lesson 4.1
- HtmlWindow — the preview panel, with link interception from lesson 4.4
- Debounced timer — a new pattern:
Wx::TIMER_ONE_SHOTfor deferred updates - Module 3 patterns — multi-file structure, model/panel separation,
@loadingflag, file dialogs, dirty state, confirm on close
Previous: HtmlWindow | Next: Module 5 — WebView