MarkView: in-app Markdown
MarkView is a small Markdown viewer that ships with ScriptWeaver. It renders
Markdown into a native Text widget — styled headings, code
blocks, tables, images, and clickable links — and adds page navigation with
back/forward history. It's what scriptweaver --help opens, and you can reuse
it in your own apps to show help, a README, release notes, or any Markdown
content, with no web engine.
It's plain JavaScript — a few small modules you bundle with your app:
| Module | What it provides |
|---|---|
parser.js |
parseMarkdown(src) → a document tree (the AST) |
render.js |
Renderer — paints a parsed document into a Text widget |
nav.js |
Navigator — a browsable viewer (loads pages, resolves links, history) |
demos.js |
optional — live widget previews (swdemo blocks, below) |
Copy those files next to your app's main.js (they have no dependencies beyond
the Player itself), then import them. demos.js is needed only if your pages
embed live previews.
Quick start
A complete viewer is a Text widget, a scrollbar, and a Navigator pointed at
a folder of .md files:
import { Navigator } from './nav.js';
const text = app.Text({ wrap: 'word', borderwidth: 0 });
const sb = app.TScrollbar({ orient: 'vertical' });
__native_tcl(text._id, 'configure', '-yscrollcommand', sb._id + ' set');
__native_tcl(sb._id, 'configure', '-command', text._id + ' yview');
sb.pack.configure({ side: 'right', fill: 'y' });
text.pack.configure({ side: 'left', fill: 'both', expand: true });
const nav = new Navigator(text, { base: 'docs' });
nav.go('index.md');
That renders docs/index.md; clicking a link to another page loads it, and
nav.back() / nav.forward() move through history. See the
worked example for a viewer with a Back / Forward /
Home toolbar.
The Navigator
new Navigator(textWidget, {
base: 'docs', // folder that page paths are resolved against
onNavigate(info) {}, // { title, path, canBack, canForward } after each move
openExternal(url) {}, // for http/mailto links (default: sw.sys.open)
});
| Method | Does |
|---|---|
go(target) |
Load a page relative to base (e.g. 'guides/x.md' or 'x.md#anchor'). Pushes history. |
follow(href) |
Follow a link from the current page (relative ../api/Y.md, #anchor, or external). |
back() / forward() |
Move through history. Return false at the ends. |
canBack() / canForward() |
Whether a move is possible — handy for enabling toolbar buttons. |
Use onNavigate to update your window title and toolbar:
const nav = new Navigator(text, {
base: 'docs',
onNavigate(info) {
app.wm.title(info.title);
backBtn.state(info.canBack ? '!disabled' : 'disabled');
},
});
Pages load through the Tcl virtual file system, so the same code reads from a
folder on disk and from a mounted .zip — point base at 'docs' when
developing and '//zipfs:/app/docs' when bundled (or probe for whichever
opens). Relative links and #anchor fragments resolve against the current
page; http(s): / mailto: links go to openExternal. A page that fails to
load renders an in-viewer "not found" message instead of throwing.
Rendering without navigation
To render a single Markdown string (no link-following), use the Renderer
directly:
import { parseMarkdown } from './parser.js';
import { Renderer } from './render.js';
const r = new Renderer(text, {
onLink: (href) => sw.sys.open(href), // optional: handle link clicks yourself
});
r.render(parseMarkdown('# Hello\n\nSome **Markdown** text.'));
r.scrollToAnchor('hello'); // jump to a heading by its slug
parseMarkdown(src) returns the document tree if you want to inspect or
transform it before rendering.
What's supported
A pragmatic subset of GitHub-Flavored Markdown — enough for real documentation:
- ATX headings (
#…######), each linkable by its slug (page.md#a-heading) - paragraphs (soft-wrapped — single newlines in the source become spaces, so text reflows to the window width)
- bold, italic,
inline code, links, and images - fenced code blocks (
```) - blockquotes, ordered / unordered / nested lists
- pipe tables (rendered as a native table) and horizontal rules
Not supported: raw HTML, and Markdown "hard breaks" (a line ending in two spaces). Colours follow the active theme automatically (readable on Azure light and dark).
Live previews
A page can embed a live, interactive widget — the real thing, rendered inline
— instead of a screenshot. Tag a fenced code block swdemo and name a widget in
its body:
```swdemo
TButton
```
MarkView builds a representative instance from a small registry (demos.js) and
drops it into the page, next to the prose that describes it. Because the viewer
shares one theme, the Theme switcher in the toolbar reskins these previews
live — so a widget's documentation shows how it actually looks, in any theme,
while you read. An unknown name (or a viewer bundled without demos.js) falls
back to rendering the block as ordinary code, so pages stay readable everywhere.
Demos are registered in demos.js as name → factory(parent); add entries
there to preview your own widgets. The themed widget reference pages — for
example TButton — use this.
Theming
The viewer reads the Text widget's background and picks a light or dark
palette to match — links, code panels, quotes, and (in dark mode) the body text
colour all adapt. Nothing to configure; just run under -light or -dark.
scriptweaver --help also puts a Theme switcher at the east end of its
toolbar: choosing a theme calls app.setTheme(), repaints the
body to match, and restyles any live previews on the current page.
Packaging a help bundle
A self-contained help app is a .zip containing your viewer plus the MarkView
modules and your pages:
help.zip
├── main.js ← your viewer (imports ./nav.js)
├── parser.js
├── render.js
├── nav.js
├── demos.js ← optional: live previews (swdemo blocks)
└── docs/
├── index.md
└── … more .md pages …
Run it like any bundle: scriptweaver help.zip. Inside the zip the docs live at
//zipfs:/app/docs, so have main.js point base there (or probe both). This
is exactly how scriptweaver --help works — the Player embeds such a bundle of
these docs and opens it in MarkView. See Packaging apps for the
bundle format.
See also
- The MarkView example — a full viewer with a toolbar
- Text — the widget MarkView renders into
- Packaging apps — bundling your viewer and pages as a
.zip