Part X, Chapter 5

Part X, Chapter 5: The page

Package: site/src/

Language: TypeScript

Raw source

The page

Six pages share one document. This chapter is the part that decides which of them a reader is looking at, and wraps it in the chrome they all wear.

The decision is a single match on the route, closed so that adding a page to the site cannot compile until this chapter has somewhere to put it. That is the point of closing it: a route with no page is a broken link, and the compiler is a better place to find one than the browser.

Two kinds of page

The site divides in two. The marketing pages — the landing, the argument, the community — wear the centred nav and range their sections down one column. The documentation and the source page wear the same nav, and lay a menu of chapters beside the article.

Both end on the same pager and the same footer, and both can raise the same palette. So each kind is a small wrapper, and the pages themselves only supply what goes in the middle.

The view is where Foldkit hands over its builder, h, on every render. Each wrapper receives it from the view and passes it to the chrome and to the sections it holds.

TypeScript
import { Match } from 'effect'
import type { Document, Html, HtmlBuilder } from 'foldkit/html'
import { footer, nav, pager, palette } from './chrome'
import { example } from './example'
import { documentationPage, sourcePage } from './docs-page'
import { hero } from './hero'
import { fromInitToEject } from './operations'
import { vocabulary } from './vocabulary'
import { whyLoom } from './why-loom'
import { community } from './community'
import { license } from './license'
import { type Message, type Model, type Route } from './model'

const overlay = (model: Model, h: HtmlBuilder<Message>): ReadonlyArray<Html> =>
  model.searchOpen ? [palette(model, h)] : []

const marketingPage = (
  model: Model,
  sections: ReadonlyArray<Html>,
  h: HtmlBuilder<Message>,
): Html =>
  h.div(
    [],
    [
      nav(model, h),
      h.main([], [...sections, pager(model.route, h)]),
      footer(h),
      ...overlay(model, h),
    ],
  )

const woven = (model: Model, page: Html, h: HtmlBuilder<Message>): Html =>
  h.div([], [nav(model, h), h.main([], [page]), footer(h), ...overlay(model, h)])

The page a route names

Every route is built. The landing runs the hero, the example, the vocabulary and the commands down one column. The argument page, the community page and the licence each run their head and one section the same way. The documentation draws its own corpus, and the source page draws Loom's own book, through the same shell.

TypeScript
const pageFor = (model: Model, h: HtmlBuilder<Message>): Html =>
  Match.value(model.route).pipe(
    Match.when('home', () =>
      marketingPage(
        model,
        [hero(model, h), example(model, h), vocabulary(), fromInitToEject(model, h)],
        h,
      ),
    ),
    Match.when('docs', () => woven(model, documentationPage(model, h), h)),
    Match.when('why-loom', () => marketingPage(model, whyLoom(), h)),
    Match.when('community', () => marketingPage(model, community(), h)),
    Match.when('license', () => marketingPage(model, license(), h)),
    Match.when('source', () => woven(model, sourcePage(model, h), h)),
    Match.exhaustive,
  )

What the tab says

A document needs a title, and the title should say which page it is rather than repeat the site's name six times. Each route names itself, and the landing keeps the line that says what Loom is.

TypeScript
const titleOf = (route: Route): string =>
  Match.value(route).pipe(
    Match.when('home', () => 'Loom — literate programming where reasoning is the source'),
    Match.when('docs', () => 'Docs — Loom'),
    Match.when('why-loom', () => 'Why Loom'),
    Match.when('community', () => 'Community — Loom'),
    Match.when('source', () => 'Source — Loom'),
    Match.when('license', () => 'License — Loom'),
    Match.exhaustive,
  )

export const view = (model: Model, h: HtmlBuilder<Message>): Document => ({
  title: titleOf(model.route),
  body: pageFor(model, h),
})

The module

The module is the two kinds of page, the route that picks one, and the title that names it.

Loom