~eliasnaur/giouiorg

8f9e71db8af9773d525cbc7687f6ee070d8b9b4b — Egon Elbre 5 months ago df7eb5f
site: add rss and summary

Signed-off-by: Egon Elbre <egonelbre@gmail.com>
M cmd/giouiorg/main.go => cmd/giouiorg/main.go +1 -0
@@ 20,6 20,7 @@ func main() {
	subHandler("/patch/", http.HandlerFunc(patchesHandler))

	config := site.Config{
		BaseURL:   "https://gioui.org",
		Content:   os.DirFS("content"),
		Templates: os.DirFS("template"),
		Includes:  os.DirFS("include"),

M content/news/index.tmpl => content/news/index.tmpl +1 -0
@@ 2,6 2,7 @@
title: Newsletter
subtitle: Gioverse news
hidechildren: true
rss: true
children:
    - "./2022-11"
    - "./2022-10"

M site/front.go => site/front.go +2 -0
@@ 26,6 26,8 @@ type FrontMatter struct {
	ChildrenNoLink bool     `yaml:"childrennolink"`
	Children       []string `yaml:"children"`

	RSS bool `yaml:"rss"`

	Images []Image        `yaml:"images"`
	Links  []ExternalLink `yaml:"links"`
}

M site/markdown.go => site/markdown.go +8 -3
@@ 43,11 43,16 @@ func ParseMarkdown(includes fs.FS, path string, content []byte) (*Page, error) {
			page.TOC = append(page.TOC,
				InternalLink{
					Level:     h.Level,
					Title:     renderAsText(h),
					Title:     renderAsText(h.Container),
					HeadingID: h.HeadingID,
				},
			)
		}
		if page.Summary == "" {
			if p, ok := node.(*ast.Paragraph); ok {
				page.Summary = renderAsText(p.Container)
			}
		}
	}

	page.Content = template.HTML(markdown.Render(doc, html.NewRenderer(html.RendererOptions{Flags: html.CommonFlags})))


@@ 91,9 96,9 @@ func extractInclude(content []byte, addr string) ([]byte, error) {
	return content, nil
}

func renderAsText(h *ast.Heading) string {
func renderAsText(p ast.Container) string {
	var s string
	for _, c := range h.Children {
	for _, c := range p.Children {
		if t, ok := c.(*ast.Text); ok {
			s += string(t.Literal)
		}

A site/rss.go => site/rss.go +70 -0
@@ 0,0 1,70 @@
// SPDX-License-Identifier: Unlicense OR MIT

package site

import (
	"encoding/xml"
	"fmt"
	"net/url"
)

// RenderRSS creates an rss.xml from the specified items.
func (site *Site) renderRSS(page PageData) ([]byte, error) {
	channelLink, err := url.JoinPath(site.BaseURL, page.URL())
	if err != nil {
		return nil, fmt.Errorf("unable to create channel link %q %q: %w", site.BaseURL, page.URL(), err)
	}

	channel := RSSChannel{
		Title:       page.Title,
		Link:        channelLink,
		Description: page.Subtitle,
	}

	for _, child := range page.Children {
		itemLink, err := url.JoinPath(site.BaseURL, child.URL())
		if err != nil {
			return nil, fmt.Errorf("unable to create item link %q %q: %w", site.BaseURL, child.URL(), err)
		}

		item := RSSItem{
			Title:       child.Title,
			Link:        itemLink,
			Description: child.Summary,
		}

		channel.Items = append(channel.Items, item)
	}

	data, err := xml.MarshalIndent(RSSXML{
		Version:  "2.0",
		Channels: []RSSChannel{channel},
	}, "", "    ")
	if err != nil {
		return nil, fmt.Errorf("failed to marshal rss: %w", err)
	}

	return append([]byte(xml.Header), data...), nil
}

type RSSXML struct {
	XMLName  xml.Name     `xml:"rss"`
	Version  string       `xml:"version,attr"`
	Channels []RSSChannel `xml:"channel"`
}

type RSSChannel struct {
	XMLName     xml.Name  `xml:"channel"`
	Title       string    `xml:"title"`
	Link        string    `xml:"link"`
	Description string    `xml:"description"`
	Items       []RSSItem `xml:"item"`
}

type RSSItem struct {
	XMLName     xml.Name `xml:"item"`
	Title       string   `xml:"title"`
	Link        string   `xml:"link"`
	Description string   `xml:"description"`
	PubDate     string   `xml:"pubDate,omitempty"`
}

M site/serve.go => site/serve.go +22 -1
@@ 40,13 40,34 @@ func (site *Site) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	}

	slug := strings.TrimPrefix(r.URL.Path, "/")

	requestRSS := strings.HasSuffix(slug, "/rss.xml")
	if requestRSS {
		slug = strings.TrimSuffix(slug, "/rss.xml")
	}

	page, ok := site.Pages[slug]
	if !ok {
		site.Fallback(w, r)
		return
	}
	if page.Slug != slug {
		http.Redirect(w, r, "/"+page.Slug, http.StatusPermanentRedirect)
		target := "/" + page.Slug
		if requestRSS {
			target += "/rss.xml"
		}
		http.Redirect(w, r, target, http.StatusPermanentRedirect)
		return
	}

	if requestRSS {
		if !page.RSS {
			http.NotFound(w, r)
			return
		}

		w.Header().Set("Content-Type", "application/rss+xml")
		_, _ = w.Write(page.RenderedRSS)
		return
	}


M site/site.go => site/site.go +24 -9
@@ 12,6 12,8 @@ import (
)

type Config struct {
	BaseURL string

	Content   fs.FS
	Templates fs.FS
	Includes  fs.FS


@@ 20,6 22,8 @@ type Config struct {
}

type Site struct {
	BaseURL string

	// Pages are indexed without prefixed "/".
	Pages        map[string]*Page
	Template     *template.Template


@@ 35,8 39,10 @@ type Page struct {

	Template string

	Content  template.HTML
	Rendered []byte
	Summary     string
	Content     template.HTML
	Rendered    []byte
	RenderedRSS []byte

	Parent   *Page
	Before   *Page


@@ 52,6 58,7 @@ type InternalLink struct {

func (config Config) Parse() (*Site, error) {
	site := &Site{
		BaseURL:  config.BaseURL,
		Pages:    map[string]*Page{},
		Content:  config.Content,
		Files:    config.Files,


@@ 204,16 211,10 @@ func (site *Site) renderPages() error {
	root := site.Pages[""]

	for _, page := range site.Pages {
		type renderData struct {
			Nav   Nav
			Front *FrontMatter
			*Page
		}

		nav := buildNav(root, page)
		nav.Active = root == page // override index.md active

		data := renderData{
		data := PageData{
			Nav:   nav,
			Front: &page.FrontMatter,
			Page:  page,


@@ 238,10 239,24 @@ func (site *Site) renderPages() error {
			}
		}
		page.Rendered = buf.Bytes()

		if page.RSS {
			var err error
			page.RenderedRSS, err = site.renderRSS(data)
			if err != nil {
				return fmt.Errorf("rss rendering failed: %w", err)
			}
		}
	}
	return nil
}

type PageData struct {
	Nav   Nav
	Front *FrontMatter
	*Page
}

type Nav struct {
	Page     *Page
	Active   bool

M site/site_test.go => site/site_test.go +18 -1
@@ 16,6 16,7 @@ var (

func TestParse(t *testing.T) {
	config := Config{
		BaseURL:   "https://gioui.org",
		Content:   os.DirFS("./testdata/content"),
		Templates: os.DirFS("./testdata/templates"),
		Includes:  os.DirFS("./testdata/includes"),


@@ 28,11 29,18 @@ func TestParse(t *testing.T) {

	for _, page := range site.Pages {
		path := filepath.FromSlash("testdata/ref/"+page.Slug) + ".html"
		rsspath := filepath.Join(filepath.FromSlash("testdata/ref/"+page.Slug), "rss.xml")

		if *updateRender {
			_ = os.MkdirAll(filepath.Dir(path), 0600)
			if err := os.WriteFile(path, page.Rendered, 0600); err != nil {
				t.Errorf("failed to write %q: %v", path, err)
			}
			if page.RSS {
				if err := os.WriteFile(rsspath, page.RenderedRSS, 0600); err != nil {
					t.Errorf("failed to write %q: %v", path, err)
				}
			}
			continue
		}



@@ 40,9 48,18 @@ func TestParse(t *testing.T) {
		if err != nil {
			t.Errorf("failed to read %q: %v", path, err)
		}

		if !bytes.Equal(data, page.Rendered) {
			t.Errorf("different content %q", path)
		}

		if page.RSS {
			rssdata, err := os.ReadFile(rsspath)
			if err != nil {
				t.Errorf("failed to read %q: %v", path, err)
			}
			if !bytes.Equal(rssdata, page.RenderedRSS) {
				t.Errorf("different content %q", path)
			}
		}
	}
}

M site/testdata/content/doc/architecture/index.md => site/testdata/content/doc/architecture/index.md +1 -0
@@ 4,6 4,7 @@ after: "./window"
children:
    - "./window"
    - "./operations"
rss: true
---

Preamble.

M site/testdata/content/doc/architecture/operations.md => site/testdata/content/doc/architecture/operations.md +5 -6
@@ 2,9 2,8 @@
title: Operations
---

Operations ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse
cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non
proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
\ No newline at end of file
Operations nec scelerisque odio. Morbi consequat dapibus suscipit. Morbi fermentum lacus nec quam consectetur, et auctor neque tempus. Sed luctus efficitur dolor in feugiat. Curabitur nec ante eget odio scelerisque gravida. Proin ac odio et enim tincidunt bibendum. Phasellus feugiat eros augue, malesuada vestibulum ante tempus a.

Fusce quis pretium dolor. Vestibulum a lectus vel eros varius hendrerit ut et orci. Curabitur ultricies consequat convallis. Donec congue magna ut libero luctus fringilla. Phasellus commodo ante non ligula gravida consectetur. Praesent mattis purus in nulla iaculis, eu fermentum massa fermentum. Praesent sit amet dui ut diam luctus molestie at sit amet nisi. Nulla fringilla a orci ac interdum. Aenean gravida pharetra placerat. Fusce viverra tortor urna, at dignissim tortor mattis ac. Sed malesuada risus vitae ipsum pharetra facilisis. Fusce et posuere elit. Ut maximus augue magna, vel posuere urna aliquet laoreet.

Sed elementum nisi ut lorem sodales, quis ullamcorper augue sodales. Vestibulum vel tellus commodo, laoreet neque eget, ultricies turpis. Aliquam non venenatis libero, quis malesuada dui. Sed semper erat ut hendrerit vulputate. Duis ut eros elit. Aenean blandit tortor eu nisl faucibus tristique. In sollicitudin libero et interdum commodo. Maecenas rutrum vitae turpis hendrerit malesuada. Proin et est quis justo iaculis volutpat.
\ No newline at end of file

M site/testdata/content/doc/architecture/window.md => site/testdata/content/doc/architecture/window.md +5 -6
@@ 2,9 2,8 @@
title: Window
---

Window ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse
cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non
proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
\ No newline at end of file
Window consequat magna et odio consequat, ac rutrum lorem congue. Sed auctor augue et nibh auctor, placerat elementum libero aliquet. Vestibulum iaculis tellus sed velit scelerisque ullamcorper. Nullam ultricies orci ac leo faucibus, quis placerat felis efficitur. Phasellus dictum vel eros sollicitudin sodales. Nulla neque dolor, vehicula ultrices aliquet a, semper id enim. Sed at ex porta, consequat elit sagittis, luctus dolor. Nam commodo, eros quis aliquam vestibulum, sapien orci sagittis eros, in pretium metus quam a orci. Pellentesque in ante faucibus massa ullamcorper accumsan.

Nulla sollicitudin mi dui, eu faucibus est interdum sit amet. Proin consequat quis quam id congue. Fusce convallis ullamcorper luctus. Suspendisse nisi risus, aliquet tincidunt ante eget, tincidunt egestas velit. Mauris tempus mi nec erat convallis, sed euismod massa pharetra. Aliquam volutpat non ex non pulvinar. Donec ut rutrum risus. Pellentesque purus ex, placerat in ipsum at, sagittis aliquam nibh. Vestibulum posuere laoreet tempus. Integer sed leo sagittis, sollicitudin velit aliquam, viverra massa. Integer ornare elit tortor, nec convallis nunc accumsan sed. Vestibulum mattis, nisi id posuere tristique, nulla mauris consectetur eros, luctus vehicula nisi elit nec tellus.

Mauris vel lacus id magna eleifend porttitor sit amet vel est. Fusce a risus molestie, maximus sem ac, pretium nulla. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nam tristique ultricies elit, eu finibus nulla. Nullam ipsum nulla, dignissim ac augue vitae, feugiat fermentum sem. Pellentesque sed pharetra turpis, quis interdum metus. Nulla elementum vehicula erat, in dapibus erat tempor sed. In eros elit, mattis sed gravida sed, luctus sit amet libero. Vivamus eget dapibus libero. Phasellus vitae enim eget erat varius rhoncus. Nulla sed dolor justo. Praesent blandit sodales nulla, quis blandit ligula tempor eu. Maecenas laoreet rhoncus velit eget dictum. Proin eget viverra metus. Praesent egestas molestie leo ac tincidunt. Morbi vel odio viverra, iaculis dui eget, faucibus mi.
\ No newline at end of file

M site/testdata/content/doc/contribute.md => site/testdata/content/doc/contribute.md +3 -6
@@ 3,9 3,6 @@ title: Index
subtitle: Subtitle
---

Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse
cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non
proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
\ No newline at end of file
Index et sapien ligula. Vivamus sollicitudin cursus arcu, vel faucibus nunc mollis sit amet. Etiam tincidunt eros in metus auctor, eget convallis justo condimentum. Vestibulum tincidunt velit at risus gravida iaculis. Nullam in justo consequat, interdum ex a, ultrices elit. Sed et aliquet felis, ac pharetra turpis. Cras porta pretium nibh at venenatis. Nullam sit amet justo nec ligula interdum sollicitudin sed quis ante. Nam luctus condimentum odio, eget egestas nulla. Integer gravida sodales convallis. Nullam blandit sodales lobortis. Vivamus at nisi eget turpis egestas pretium id a ligula. Vestibulum elementum elementum imperdiet. Nunc bibendum tincidunt dapibus. Ut pharetra libero ac lorem ultricies, eu hendrerit justo aliquam. Quisque vel eros tristique, vulputate lacus sit amet, rhoncus mi.

Sed vulputate, arcu nec pretium euismod, eros sem ultrices tellus, at vehicula elit turpis ac est. Duis elementum arcu a pellentesque auctor. Ut nec porttitor magna, id cursus erat. Nam auctor congue pellentesque. Donec purus lorem, ultricies ac mauris vitae, consequat accumsan justo. Vestibulum id ex fringilla, porta diam non, placerat mauris. Duis fermentum sagittis massa, id condimentum dui. Vestibulum quis lorem vitae nisl cursus rhoncus. Ut vitae luctus odio. Quisque eget ante ornare, vulputate neque non, pellentesque diam. Etiam quis bibendum odio.
\ No newline at end of file

M site/testdata/content/index.md => site/testdata/content/index.md +5 -6
@@ 8,9 8,8 @@ children:
    - "doc/contribute"
---

Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse
cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non
proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
\ No newline at end of file
Home ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum mattis ultrices dui eu facilisis. Sed commodo condimentum pharetra. Praesent feugiat sapien ac rhoncus pharetra. Aenean pulvinar lectus dignissim eros aliquam, vitae egestas nibh consectetur. Donec vulputate suscipit imperdiet. Pellentesque ut arcu vitae arcu laoreet laoreet. Pellentesque suscipit, orci placerat malesuada vestibulum, quam magna eleifend lectus, ut accumsan diam nisl id odio. Duis mattis augue vitae tempor hendrerit. In ante enim, efficitur ut leo eget, tincidunt mollis odio. Proin molestie tincidunt est, ac viverra risus venenatis vitae.

Fusce magna lacus, euismod vitae felis nec, vestibulum sollicitudin dolor. Mauris auctor pulvinar dolor quis venenatis. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Etiam at augue at elit volutpat semper. Ut gravida aliquam sapien, a tempus odio dapibus ac. Aenean id auctor urna. Vivamus venenatis mollis ipsum sed lacinia. Etiam ex enim, euismod et ligula sit amet, egestas condimentum mauris. Integer laoreet felis non velit laoreet, in convallis sem convallis. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Nunc accumsan ante ligula, vel facilisis velit euismod sit amet. Proin turpis enim, elementum nec consectetur at, aliquet a justo. Phasellus nec interdum nisl, quis dignissim odio. Etiam in mauris sollicitudin est mattis pulvinar. Vivamus tempor urna orci, at fringilla purus egestas ac. Nunc sit amet lorem bibendum, vehicula turpis sit amet, ultrices massa.

Maecenas sed consequat neque. Aliquam erat volutpat. Sed vitae tortor sagittis, consequat enim sit amet, tincidunt dui. Phasellus rutrum, orci hendrerit tincidunt interdum, tellus diam aliquam risus, nec aliquet diam tortor id odio. In imperdiet tristique dolor a imperdiet. Donec tempor sit amet diam ultrices porta. Quisque diam erat, pellentesque et nisi nec, euismod ullamcorper neque.
\ No newline at end of file

M site/testdata/ref/.html => site/testdata/ref/.html +7 -6
@@ 15,12 15,13 @@
		<child>doc/contribute</child>
	</children>

	<content><p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse
cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non
proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
	<summary>Home ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum mattis ultrices dui eu facilisis. Sed commodo condimentum pharetra. Praesent feugiat sapien ac rhoncus pharetra. Aenean pulvinar lectus dignissim eros aliquam, vitae egestas nibh consectetur. Donec vulputate suscipit imperdiet. Pellentesque ut arcu vitae arcu laoreet laoreet. Pellentesque suscipit, orci placerat malesuada vestibulum, quam magna eleifend lectus, ut accumsan diam nisl id odio. Duis mattis augue vitae tempor hendrerit. In ante enim, efficitur ut leo eget, tincidunt mollis odio. Proin molestie tincidunt est, ac viverra risus venenatis vitae.</summary>

	<content><p>Home ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum mattis ultrices dui eu facilisis. Sed commodo condimentum pharetra. Praesent feugiat sapien ac rhoncus pharetra. Aenean pulvinar lectus dignissim eros aliquam, vitae egestas nibh consectetur. Donec vulputate suscipit imperdiet. Pellentesque ut arcu vitae arcu laoreet laoreet. Pellentesque suscipit, orci placerat malesuada vestibulum, quam magna eleifend lectus, ut accumsan diam nisl id odio. Duis mattis augue vitae tempor hendrerit. In ante enim, efficitur ut leo eget, tincidunt mollis odio. Proin molestie tincidunt est, ac viverra risus venenatis vitae.</p>

<p>Fusce magna lacus, euismod vitae felis nec, vestibulum sollicitudin dolor. Mauris auctor pulvinar dolor quis venenatis. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Etiam at augue at elit volutpat semper. Ut gravida aliquam sapien, a tempus odio dapibus ac. Aenean id auctor urna. Vivamus venenatis mollis ipsum sed lacinia. Etiam ex enim, euismod et ligula sit amet, egestas condimentum mauris. Integer laoreet felis non velit laoreet, in convallis sem convallis. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Nunc accumsan ante ligula, vel facilisis velit euismod sit amet. Proin turpis enim, elementum nec consectetur at, aliquet a justo. Phasellus nec interdum nisl, quis dignissim odio. Etiam in mauris sollicitudin est mattis pulvinar. Vivamus tempor urna orci, at fringilla purus egestas ac. Nunc sit amet lorem bibendum, vehicula turpis sit amet, ultrices massa.</p>

<p>Maecenas sed consequat neque. Aliquam erat volutpat. Sed vitae tortor sagittis, consequat enim sit amet, tincidunt dui. Phasellus rutrum, orci hendrerit tincidunt interdum, tellus diam aliquam risus, nec aliquet diam tortor id odio. In imperdiet tristique dolor a imperdiet. Donec tempor sit amet diam ultrices porta. Quisque diam erat, pellentesque et nisi nec, euismod ullamcorper neque.</p>
</content>

	<nav>

M site/testdata/ref/doc/architecture.html => site/testdata/ref/doc/architecture.html +2 -0
@@ 19,6 19,8 @@
		<child>doc/architecture/operations</child>
	</children>

	<summary>Preamble.</summary>

	<content><p>Preamble.</p>

<pre><code class="language-go">func main() {

M site/testdata/ref/doc/architecture/operations.html => site/testdata/ref/doc/architecture/operations.html +7 -6
@@ 13,12 13,13 @@
	<children>
	</children>

	<content><p>Operations ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse
cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non
proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
	<summary>Operations nec scelerisque odio. Morbi consequat dapibus suscipit. Morbi fermentum lacus nec quam consectetur, et auctor neque tempus. Sed luctus efficitur dolor in feugiat. Curabitur nec ante eget odio scelerisque gravida. Proin ac odio et enim tincidunt bibendum. Phasellus feugiat eros augue, malesuada vestibulum ante tempus a.</summary>

	<content><p>Operations nec scelerisque odio. Morbi consequat dapibus suscipit. Morbi fermentum lacus nec quam consectetur, et auctor neque tempus. Sed luctus efficitur dolor in feugiat. Curabitur nec ante eget odio scelerisque gravida. Proin ac odio et enim tincidunt bibendum. Phasellus feugiat eros augue, malesuada vestibulum ante tempus a.</p>

<p>Fusce quis pretium dolor. Vestibulum a lectus vel eros varius hendrerit ut et orci. Curabitur ultricies consequat convallis. Donec congue magna ut libero luctus fringilla. Phasellus commodo ante non ligula gravida consectetur. Praesent mattis purus in nulla iaculis, eu fermentum massa fermentum. Praesent sit amet dui ut diam luctus molestie at sit amet nisi. Nulla fringilla a orci ac interdum. Aenean gravida pharetra placerat. Fusce viverra tortor urna, at dignissim tortor mattis ac. Sed malesuada risus vitae ipsum pharetra facilisis. Fusce et posuere elit. Ut maximus augue magna, vel posuere urna aliquet laoreet.</p>

<p>Sed elementum nisi ut lorem sodales, quis ullamcorper augue sodales. Vestibulum vel tellus commodo, laoreet neque eget, ultricies turpis. Aliquam non venenatis libero, quis malesuada dui. Sed semper erat ut hendrerit vulputate. Duis ut eros elit. Aenean blandit tortor eu nisl faucibus tristique. In sollicitudin libero et interdum commodo. Maecenas rutrum vitae turpis hendrerit malesuada. Proin et est quis justo iaculis volutpat.</p>
</content>

	<nav>

A site/testdata/ref/doc/architecture/rss.xml => site/testdata/ref/doc/architecture/rss.xml +18 -0
@@ 0,0 1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
    <channel>
        <title>Architecture</title>
        <link>https://gioui.org/doc/architecture</link>
        <description></description>
        <item>
            <title>Window</title>
            <link>https://gioui.org/doc/architecture/window</link>
            <description>Window consequat magna et odio consequat, ac rutrum lorem congue. Sed auctor augue et nibh auctor, placerat elementum libero aliquet. Vestibulum iaculis tellus sed velit scelerisque ullamcorper. Nullam ultricies orci ac leo faucibus, quis placerat felis efficitur. Phasellus dictum vel eros sollicitudin sodales. Nulla neque dolor, vehicula ultrices aliquet a, semper id enim. Sed at ex porta, consequat elit sagittis, luctus dolor. Nam commodo, eros quis aliquam vestibulum, sapien orci sagittis eros, in pretium metus quam a orci. Pellentesque in ante faucibus massa ullamcorper accumsan.</description>
        </item>
        <item>
            <title>Operations</title>
            <link>https://gioui.org/doc/architecture/operations</link>
            <description>Operations nec scelerisque odio. Morbi consequat dapibus suscipit. Morbi fermentum lacus nec quam consectetur, et auctor neque tempus. Sed luctus efficitur dolor in feugiat. Curabitur nec ante eget odio scelerisque gravida. Proin ac odio et enim tincidunt bibendum. Phasellus feugiat eros augue, malesuada vestibulum ante tempus a.</description>
        </item>
    </channel>
</rss>
\ No newline at end of file

M site/testdata/ref/doc/architecture/window.html => site/testdata/ref/doc/architecture/window.html +7 -6
@@ 13,12 13,13 @@
	<children>
	</children>

	<content><p>Window ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse
cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non
proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
	<summary>Window consequat magna et odio consequat, ac rutrum lorem congue. Sed auctor augue et nibh auctor, placerat elementum libero aliquet. Vestibulum iaculis tellus sed velit scelerisque ullamcorper. Nullam ultricies orci ac leo faucibus, quis placerat felis efficitur. Phasellus dictum vel eros sollicitudin sodales. Nulla neque dolor, vehicula ultrices aliquet a, semper id enim. Sed at ex porta, consequat elit sagittis, luctus dolor. Nam commodo, eros quis aliquam vestibulum, sapien orci sagittis eros, in pretium metus quam a orci. Pellentesque in ante faucibus massa ullamcorper accumsan.</summary>

	<content><p>Window consequat magna et odio consequat, ac rutrum lorem congue. Sed auctor augue et nibh auctor, placerat elementum libero aliquet. Vestibulum iaculis tellus sed velit scelerisque ullamcorper. Nullam ultricies orci ac leo faucibus, quis placerat felis efficitur. Phasellus dictum vel eros sollicitudin sodales. Nulla neque dolor, vehicula ultrices aliquet a, semper id enim. Sed at ex porta, consequat elit sagittis, luctus dolor. Nam commodo, eros quis aliquam vestibulum, sapien orci sagittis eros, in pretium metus quam a orci. Pellentesque in ante faucibus massa ullamcorper accumsan.</p>

<p>Nulla sollicitudin mi dui, eu faucibus est interdum sit amet. Proin consequat quis quam id congue. Fusce convallis ullamcorper luctus. Suspendisse nisi risus, aliquet tincidunt ante eget, tincidunt egestas velit. Mauris tempus mi nec erat convallis, sed euismod massa pharetra. Aliquam volutpat non ex non pulvinar. Donec ut rutrum risus. Pellentesque purus ex, placerat in ipsum at, sagittis aliquam nibh. Vestibulum posuere laoreet tempus. Integer sed leo sagittis, sollicitudin velit aliquam, viverra massa. Integer ornare elit tortor, nec convallis nunc accumsan sed. Vestibulum mattis, nisi id posuere tristique, nulla mauris consectetur eros, luctus vehicula nisi elit nec tellus.</p>

<p>Mauris vel lacus id magna eleifend porttitor sit amet vel est. Fusce a risus molestie, maximus sem ac, pretium nulla. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nam tristique ultricies elit, eu finibus nulla. Nullam ipsum nulla, dignissim ac augue vitae, feugiat fermentum sem. Pellentesque sed pharetra turpis, quis interdum metus. Nulla elementum vehicula erat, in dapibus erat tempor sed. In eros elit, mattis sed gravida sed, luctus sit amet libero. Vivamus eget dapibus libero. Phasellus vitae enim eget erat varius rhoncus. Nulla sed dolor justo. Praesent blandit sodales nulla, quis blandit ligula tempor eu. Maecenas laoreet rhoncus velit eget dictum. Proin eget viverra metus. Praesent egestas molestie leo ac tincidunt. Morbi vel odio viverra, iaculis dui eget, faucibus mi.</p>
</content>

	<nav>

M site/testdata/ref/doc/contribute.html => site/testdata/ref/doc/contribute.html +5 -6
@@ 13,12 13,11 @@
	<children>
	</children>

	<content><p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse
cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non
proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
	<summary>Index et sapien ligula. Vivamus sollicitudin cursus arcu, vel faucibus nunc mollis sit amet. Etiam tincidunt eros in metus auctor, eget convallis justo condimentum. Vestibulum tincidunt velit at risus gravida iaculis. Nullam in justo consequat, interdum ex a, ultrices elit. Sed et aliquet felis, ac pharetra turpis. Cras porta pretium nibh at venenatis. Nullam sit amet justo nec ligula interdum sollicitudin sed quis ante. Nam luctus condimentum odio, eget egestas nulla. Integer gravida sodales convallis. Nullam blandit sodales lobortis. Vivamus at nisi eget turpis egestas pretium id a ligula. Vestibulum elementum elementum imperdiet. Nunc bibendum tincidunt dapibus. Ut pharetra libero ac lorem ultricies, eu hendrerit justo aliquam. Quisque vel eros tristique, vulputate lacus sit amet, rhoncus mi.</summary>

	<content><p>Index et sapien ligula. Vivamus sollicitudin cursus arcu, vel faucibus nunc mollis sit amet. Etiam tincidunt eros in metus auctor, eget convallis justo condimentum. Vestibulum tincidunt velit at risus gravida iaculis. Nullam in justo consequat, interdum ex a, ultrices elit. Sed et aliquet felis, ac pharetra turpis. Cras porta pretium nibh at venenatis. Nullam sit amet justo nec ligula interdum sollicitudin sed quis ante. Nam luctus condimentum odio, eget egestas nulla. Integer gravida sodales convallis. Nullam blandit sodales lobortis. Vivamus at nisi eget turpis egestas pretium id a ligula. Vestibulum elementum elementum imperdiet. Nunc bibendum tincidunt dapibus. Ut pharetra libero ac lorem ultricies, eu hendrerit justo aliquam. Quisque vel eros tristique, vulputate lacus sit amet, rhoncus mi.</p>

<p>Sed vulputate, arcu nec pretium euismod, eros sem ultrices tellus, at vehicula elit turpis ac est. Duis elementum arcu a pellentesque auctor. Ut nec porttitor magna, id cursus erat. Nam auctor congue pellentesque. Donec purus lorem, ultricies ac mauris vitae, consequat accumsan justo. Vestibulum id ex fringilla, porta diam non, placerat mauris. Duis fermentum sagittis massa, id condimentum dui. Vestibulum quis lorem vitae nisl cursus rhoncus. Ut vitae luctus odio. Quisque eget ante ornare, vulputate neque non, pellentesque diam. Etiam quis bibendum odio.</p>
</content>

	<nav>

M site/testdata/templates/root.tmpl => site/testdata/templates/root.tmpl +2 -0
@@ 19,6 19,8 @@
	{{- end }}
	</children>

	<summary>{{ .Page.Summary }}</summary>

	<content>{{ .Page.Content }}</content>

	<nav>{{template "nav" .Nav}}</nav>

M template/root.tmpl => template/root.tmpl +2 -0
@@ 13,6 13,8 @@
	<link rel="icon" type="image/png" href="/files/favicon196.png" sizes="196x196" media="(prefers-color-scheme:no-preference)"/>
	<link rel="icon" type="image/png" href="/files/favicon32.png" sizes="32x32" media="(prefers-color-scheme:no-preference)"/>
	<link rel="stylesheet" href="/files/style.css?v=4"/>

	<link rel="alternate" type="application/rss+xml" title="Gio News" href="/news/rss.xml"/>
</head>
	<body>
	<div class="layout-with-sidebar">