Luat: templates and blocks
The files below are from examples/site-luat in the Studio repository.
The page
load_page returns the page’s props; the page template renders its content with the package’s Blocks component:
<!-- src/lib/Page.luat — used by "/" and the catch-all route -->
<script>
local Blocks = require("@maravilla/studio/Blocks")
setContext("studio", props.studio or {}) -- editing state, locale, visitor, media URLs
setContext("studio.components", require("$lib/blocks")) -- your own blocks (optional)
</script>
{#if props.connecting}
<p class="notice">Connecting to the editor…</p>
{:else}
<article>
<h1 data-studio-doc-field="title">{props.title}</h1>
<div data-studio-section="root" data-studio-section-name="content">
<Blocks blocks={props.content or {}} />
</div>
</article>
{/if}
data-studio-doc-field makes the page’s own title editable, and data-studio-section turns the content list into the editor’s drop target — the same annotations every Studio site uses. props.connecting is the brief state while the editor opens the page.
The layout
The layout’s load builds the chrome around every page — the story’s header and footer pages, the navigation, the language switch — and the editor mount:
-- src/routes/+layout.server.lua (abridged)
local studio = require("@maravilla/studio")
function load(ctx)
local s = studio.new()
local rc = s:context(ctx)
s:send_headers(ctx, rc) -- framing for the editor, noindex for previews
local chrome = s:chrome(rc) -- story, header and footer (with drafts), nav
local visitor = s:visitor(rc, ctx)
return {
chrome = chrome,
lang = rc.locale,
head = table.concat({
require("@maravilla/studio/styles").tag(), -- optional default block CSS
studio.editor.head(rc, { regions = { "#studio-page", "#site-header", "#site-footer" } }),
}, "\n"),
layout_studio = s:template_context(rc, visitor),
}
end
<!-- src/routes/+layout.luat (abridged) -->
<html lang={props.lang or "en"}>
<head>
<title>{props.title}</title>
{@html props.head or ""}
</head>
<body>
<header id="site-header" data-studio-ignore>…</header>
<main id="studio-page" data-studio-root>{@html props.children}</main>
<footer id="site-footer" data-studio-ignore>…</footer>
</body>
</html>
src/app.html is reduced to <!DOCTYPE html>%luat.body%: the layout renders the whole document, because the language, title and editor mount change per request. editor.head renders the editor mount only for requests from the editor; the regions are what the editor swaps in place after an edit. Header and footer are data-studio-ignore: they are pages of their own and are edited there.
The standard blocks
require("@maravilla/studio/<Name>"):
| Component | Block |
|---|---|
Blocks, Element | a content list / one block, through the registry |
Hero | standard:Hero |
TextBlock | standard:Prose |
Heading | standard:Heading |
ImageBlock | standard:Figure |
FeatureGrid | standard:FeatureGrid |
Section | standard:Section (a nested drop container) |
Variants, StudioVariants | standard:Variants / standard:Switcher |
Fallback | anything not registered — shown, never silently dropped |
RichText | <RichText value={props.body} as="div" field="body" /> |
Images and files render through the stable /studio-media/… route, so a page never embeds a URL that expires.
Your own blocks
A block component receives the block’s fields as props (props.uuid, props.element_type and the block’s own fields) and annotates itself so the editor can select and edit it:
<!-- src/lib/blocks/Pullquote.luat -->
<script>
local studio = require("@maravilla/studio")
</script>
<blockquote class="pullquote" data-studio-id={props.uuid} data-studio-type={props.element_type}>
<p data-studio-field="quote">{props.quote or ""}</p>
{#if studio.util.present(props.attribution)}
<cite data-studio-field="attribution">{props.attribution}</cite>
{/if}
</blockquote>
Register it by element type — a site entry wins over the package’s component, so you can also replace a standard block:
-- src/lib/blocks.lua
return {
["standard:Pullquote"] = require("$lib/blocks/Pullquote"),
}
studio.editor has helpers that build the same attributes as tables (editor.block(el), editor.field(name, { kind = "rich" }), editor.section(owner, name)) for components that assemble attributes in code.
Pilot documentation. Studio is in a partner pilot. Screens and YAML shown here are current as of the version noted on each page; tell your Maravilla contact when something does not match what you see.