Example: Markdown help viewer

A browsable documentation viewer built with MarkView: a Back / Forward / Home toolbar over a scrolled Text widget, driven by a Navigator. It's a trimmed-down version of the very app scriptweaver --help runs.

Bundle the three MarkView modules (parser.js, render.js, nav.js) and your .md pages next to this main.js, then run the folder or a .zip of it.

A toolbar

Three themed buttons and a title label, packed across the top:

import { Navigator } from './nav.js';

const bar = app.TFrame({ padding: 6 });
bar.pack.configure({ fill: 'x' });

const backBtn = bar.TButton({ text: '←  Back' });
const fwdBtn = bar.TButton({ text: 'Forward  →' });
const homeBtn = bar.TButton({ text: 'Home' });
const title = bar.TLabel({ text: '' });
for (const w of [backBtn, fwdBtn, homeBtn]) w.pack.configure({ side: 'left' });
title.pack.configure({ side: 'left', padx: 14 });

The page area

A Text widget and a vertical scrollbar, wired together:

const body = app.TFrame();
body.pack.configure({ fill: 'both', expand: true });

const text = body.Text({ wrap: 'word', borderwidth: 0, highlightthickness: 0 });
const sb = body.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 });

Wiring the Navigator

The Navigator renders pages into the Text. Its onNavigate callback keeps the title and the toolbar buttons in sync; the buttons drive history:

const nav = new Navigator(text, {
  base: findBase(),
  onNavigate(info) {
    title.text = info.title;
    app.wm.title('Help — ' + info.title);
    backBtn.state(info.canBack ? '!disabled' : 'disabled');
    fwdBtn.state(info.canForward ? '!disabled' : 'disabled');
  },
});

backBtn.onClick = () => nav.back();
fwdBtn.onClick = () => nav.forward();
homeBtn.onClick = () => nav.go('index.md');

nav.go('index.md'); // open the home page

Finding the docs, disk or zip

Run loose during development and bundled in production from the same file by probing for wherever index.md opens — on disk (docs/) or in a mounted zip (//zipfs:/app/docs):

function findBase() {
  for (const b of ['//zipfs:/app/docs', 'docs', '.']) {
    try {
      __native_tcl_eval('close [open {' + b + '/index.md} r]');
      return b;
    } catch (e) {}
  }
  return 'docs';
}

Mouse-wheel scrolling, relative links, #anchor jumps, external links (opened in the system browser), and light/dark theming all come from MarkView — there's nothing else to wire.

Run it

scriptweaver markview/main.js          # loose, from the source tree
scriptweaver help.zip                  # bundled (main.js + modules + docs/)

Full source: markview/main.js.

See also