atom

package module
v1.0.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 24, 2025 License: MIT Imports: 11 Imported by: 0

README

atom

An extensible Atom feed generator library that aims to be very close to RFC4287. It diligently checks for compliance with the standard and provides functions for easy creation, extension and deletion of elements.

Installation

To install the latest version of the module, use the following command:

go get git.streifling.com/jason/atom@latest

Usage

Basic Feed

This library provides convenient functions to safely create, extend and delete elements and attributes of Atom feeds. It also provides checks for all constructs' adherence to RFC4287.

package main

import (
    "fmt"
    "log"

    "git.streifling.com/jason/atom"
)

func main() {
    feed := atom.NewFeed("Example Feed")
    if err := feed.Check(); err != nil {
        log.Fatalln(err)
    }

    author := atom.NewPerson("John Doe")
    author.Email = "john.doe@example.com"
    if err := author.Check(); err != nil {
        log.Fatalln(err)
    }
    feed.AddAuthor(author)

    entry := atom.NewEntry("First Entry")
    entry.Content = atom.NewContent(atom.InlineText, "text", "This is the content of the first entry.")
    if err := entry.Check(); err != nil {
        log.Fatalln(err)
    }
    feed.AddEntry(entry)

    feedString, err := feed.ToXML("UTF-8")
    if err != nil {
        log.Fatalln(err)
    }
    fmt.Println(feedString)
}

It is also possible to use this library in a way similar to what other libraries provide.

package main

import (
    "fmt"
    "log"
    "time"

    "git.streifling.com/jason/atom"
    "github.com/google/uuid"
)

func main() {
    now := time.Now()

    feed := &atom.Feed{
        Title: &atom.PlainText{
            Type: "text",
            Text: "Example Feed",
        },
        ID:      &atom.ID{URI: fmt.Sprint("urn:uuid:", uuid.New())},
        Updated: &atom.Date{DateTime: atom.DateTime(now)},
        Authors: []*atom.Person{
            {
                Name:  "John Doe",
                Email: "john.doe@example.com",
            },
        },
        Entries: []*atom.Entry{
            {
                Title: &atom.PlainText{
                    Type: "text",
                    Text: "First Entry",
                },
                ID:      &atom.ID{URI: fmt.Sprint("urn:uuid:", uuid.New())},
                Updated: &atom.Date{DateTime: atom.DateTime(now)},
                Content: &atom.InlineTextContent{
                    Type: "text",
                    Text: "This is the content of the first entry.",
                },
            },
        },
    }

    feedString, err := feed.ToXML("UTF-8")
    if err != nil {
        log.Fatalln(err)
    }
    fmt.Println(feedString)
}

The output of both ways of using it is an RFC4287 compliant Atom feed.

<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
    <author>
        <name>John Doe</name>
        <email>john.doe@example.com</email>
    </author>
    <id>urn:uuid:60a76c80-d399-11d9-b93C-0003939e0af6</id>
    <title type="text">Example Feed</title>
    <updated>2024-10-18T05:49:08+02:00</updated>
    <entry>
        <content type="text">This is the content of the first entry.</content>
        <id>urn:uuid:1225c695-cfb8-4ebb-aaaa-80da344efa6a</id>
        <title type="text">First Entry</title>
        <updated>2006-01-02T15:04:05+07:00</updated>
    </entry>
</feed>
Compliance and Error Checking

All Atom constructs have their own construct.Check() method. It checks for compliance with RFC4287 and errors. This allows one to be certain that only compliant constructs are added to their respective parent construct. It also means that not the entire feed has to be checked every time a new construct is added. Instead one only checks the current construct and all of its sub-constructs with a single construct.Check() call.

package main

import (
    "fmt"
    "log"

    "git.streifling.com/jason/atom"
)

func main() {
    feed := atom.NewFeed("Example Feed")
    if err := feed.Check(); err != nil {
        log.Fatalln(err)
    }

    entry := atom.NewEntry("One")
    entry.Content = atom.NewContent(atom.InlineText, "text", "Entry One")
    entry.AddAuthor(atom.NewPerson("John Doe"))
    if err := entry.Check(); err != nil {
        log.Fatalln(err)
    }
    feed.AddEntry(entry)

    feedString, err := feed.ToXML("UTF-8")
    if err != nil {
        log.Fatalln(err)
    }
    fmt.Println(feedString)
}
Adding and Deleting Items

To add elements to any slice one calls the appropriate someone.AddSomething(toBeAdded) method. It returns the item's index number. To delete the item one calls the Delete method.

package main

import (
    "fmt"
    "log"

    "git.streifling.com/jason/atom"
)

func main() {
    feed := atom.NewFeed("Feed")
    if err := feed.Check(); err != nil {
        log.Fatalln(err)
    }

    e1 := atom.NewEntry("One")
    e1.Content = atom.NewContent(atom.InlineText, "text", "Entry One")
    if err := e1.Check(); err != nil {
        log.Fatalln(err)
    }
    i1 := feed.AddEntry(e1)

    e2 := atom.NewEntry("Two")
    e2.Content = atom.NewContent(atom.InlineText, "text", "Entry Two")
    if err := e2.Check(); err != nil {
        log.Fatalln(err)
    }
    feed.AddEntry(e2)

    if err := feed.DeleteEntry(i1); err != nil {
        log.Fatalln(err)
    }

    feedString, err := feed.ToXML("UTF-8")
    if err != nil {
        log.Fatalln(err)
    }
    fmt.Println(feedString)
}

The output of this example looks like this. It only shows the second entry.

<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
    <id>urn:uuid:ae89d343-b535-447d-ac14-4b80d3e02a2f</id>
    <title type="text">Example Feed</title>
    <updated>2024-10-20T14:57:02+02:00</updated>
    <entry>
        <content type="text">Entry Two</content>
        <id>urn:uuid:620c6f73-ee1d-4c1e-be98-b0b1ad7a053f</id>
        <title type="text">Two</title>
        <updated>2024-10-20T14:57:02+02:00</updated>
    </entry>
</feed>

Documentation

Index

Constants

View Source
const (
	InlineText = iota
	InlineXHTML
	InlineOther
	OutOfLine
)

Variables

This section is empty.

Functions

func DateTime

func DateTime(t time.Time) string

DateTime formats the time.Time t to a string as defined by RFC3339. It returns a string.

func NewURN added in v0.2.0

func NewURN() string

NewURN generates an new valid IRI based on a UUID. It returns an IRI.

func Unescape added in v0.4.0

func Unescape(s string) string

Unescape unescapes the string s. It returns an IRI.

Types

type Category

type Category struct {
	XMLName xml.Name `xml:"category"`
	*CommonAttributes
	Term   string `xml:"term,attr"`
	Scheme string `xml:"scheme,attr,omitempty"` // IRI
	Label  string `xml:"label,attr,omitempty"`  // Must be unescaped
}

func NewCategory

func NewCategory(term string) *Category

NewCategory creates a new Category. It takes in a string term and returns a *Category.

func (*Category) Check

func (c *Category) Check() error

Check checks the Category for incompatibilities with RFC4287. It returns an error.

type CommonAttributes

type CommonAttributes struct {
	Base                string      `xml:"base,attr,omitempty"` // IRI
	Lang                string      `xml:"lang,attr,omitempty"` // LanguageTag
	UndefinedAttributes []*xml.Attr `xml:",attr,omitempty"`
}

func NewCommonAttributes added in v0.2.1

func NewCommonAttributes() *CommonAttributes

NewCommonAttributes creates a new set of CommonAttributes. It returns a *CommonAttributes.

func (*CommonAttributes) AddAttribute added in v0.4.0

func (c *CommonAttributes) AddAttribute(name, value string) int

AddAttribute adds an attribute to the CommonAttributes. It takes in the strings name and value and returns the index as an int.

func (*CommonAttributes) Check

func (c *CommonAttributes) Check() error

Check checks the CommonAttributes for incompatibilities with RFC4287. It returns an error.

func (*CommonAttributes) DeleteAttribute added in v0.5.0

func (c *CommonAttributes) DeleteAttribute(index int) error

DeleteAttribute deletes the attribute at index from the CommonAttributes. It returns an error.

type Content

type Content interface {
	Check() error
	// contains filtered or unexported methods
}

func NewContent

func NewContent(contentType int, mediaType string, content any) Content

NewContent creates a new Content. It takes in an int contentType, a string mediaType and an any content and returns a Content.

If contentType is invalid, it returns nil.

type Countable added in v0.5.0

type Countable interface {
	*xml.Attr | *Person | *Category | *Link | *ExtensionElement | *Entry
}

type Date

type Date struct {
	*CommonAttributes
	DateTime string `xml:",chardata"`
}

func NewDate

func NewDate(t time.Time) *Date

NewDate creates a new Date. It takes in a time.Time t and returns a *Date.

func (*Date) Check

func (d *Date) Check() error

Check checks the Date for incompatibilities with RFC4287. It returns an error.

type Entry

type Entry struct {
	XMLName xml.Name `xml:"entry"`
	*CommonAttributes
	Authors      []*Person   `xml:"author,omitempty"`
	Categories   []*Category `xml:",omitempty"`
	Content      Content     `xml:",omitempty"`
	Contributors []*Person   `xml:"contributors,omitempty"`
	ID           *ID
	Links        []*Link             `xml:",omitempty"`
	Published    *Date               `xml:"published,omitempty"`
	Rights       Text                `xml:"rights,omitempty"`
	Source       *Source             `xml:",omitempty"`
	Summary      Text                `xml:"summary,omitempty"`
	Title        Text                `xml:"title"`
	Updated      *Date               `xml:"updated"`
	Extensions   []*ExtensionElement `xml:",any,omitempty"`
}

It is advisable that each atom:entry element contain a non-empty atom:title element, a non-empty atom:content element when that element is present, and a non-empty atom:summary element when the entry contains no atom:content element.

func NewEntry

func NewEntry(title string) *Entry

NewEntry creates a new Entry. It takes in a string title and returns a *Entry.

func (*Entry) AddAuthor added in v0.1.3

func (e *Entry) AddAuthor(a *Person) int

AddAuthor adds the Person a as an author to the Entry. It returns the index as an int.

func (*Entry) AddCategory added in v0.1.3

func (e *Entry) AddCategory(c *Category) int

AddCategory adds the Category c to the Entry. It returns the index as an int.

func (*Entry) AddContributor added in v0.1.3

func (e *Entry) AddContributor(c *Person) int

AddContributor adds the Person c as a contributor to the Entry. It returns the index as an int.

func (*Entry) AddExtension

func (e *Entry) AddExtension(x *ExtensionElement) int

AddExtension adds the ExtensionElement x to the Entry. It returns the index as an int.

func (e *Entry) AddLink(l *Link) int

AddLink adds the Link l to the Entry. It returns the index as an int.

func (*Entry) Check

func (e *Entry) Check() error

Check checks the Entry for incompatibilities with RFC4287. It returns an error.

func (*Entry) DeleteAuthor added in v0.5.0

func (e *Entry) DeleteAuthor(index int) error

DeleteAuthor deletes the Person at index from the Entry. It returns an error.

func (*Entry) DeleteCategory added in v0.5.0

func (e *Entry) DeleteCategory(index int) error

DeleteCategory deletes the Category at index from the Entry. It returns an error.

func (*Entry) DeleteContributor added in v0.5.0

func (e *Entry) DeleteContributor(index int) error

DeleteContributor deletes the Person at index from the Entry. It returns an error.

func (*Entry) DeleteExtension added in v0.5.0

func (e *Entry) DeleteExtension(index int) error

DeleteExtension deletes the Extension at index from the Entry. It returns an error.

func (e *Entry) DeleteLink(index int) error

DeleteLink deletes the Link at index from the Entry. It returns an error.

func (*Entry) ToXML added in v0.3.0

func (e *Entry) ToXML(encoding string) (string, error)

ToXML converts the Feed to XML. It takes in a string encoding and returns a string and an error.

type ExtensionElement

type ExtensionElement struct {
	Value   any `xml:",innerxml"`
	XMLName xml.Name
}

func NewExtensionElement

func NewExtensionElement(name string, value any) *ExtensionElement

NewExtensionElement creates a new ExtensionElement. It takes in a string name and any value and returns a *ExtensionElement.

func (*ExtensionElement) Check

func (e *ExtensionElement) Check() error

Check checks the ExtensionElement for incompatibilities with RFC4287. It returns an error.

type Feed

type Feed struct {
	XMLName xml.Name `xml:"http://www.w3.org/2005/Atom feed"`
	*CommonAttributes
	Authors      []*Person   `xml:"author,omitempty"`
	Categories   []*Category `xml:",omitempty"`
	Contributors []*Person   `xml:"contributor,omitempty"`
	Generator    *Generator  `xml:",omitempty"`
	Icon         *Icon       `xml:",omitempty"`
	ID           *ID
	Links        []*Link             `xml:",omitempty"`
	Rights       Text                `xml:"rights,omitempty"`
	Subtitle     Text                `xml:"subtitle,omitempty"`
	Title        Text                `xml:"title"`
	Updated      *Date               `xml:"updated"`
	Extensions   []*ExtensionElement `xml:",any,omitempty"`
	Entries      []*Entry            `xml:",omitempty"`
}

func NewFeed

func NewFeed(title string) *Feed

NewFeed creates a new Feed. It takes in a string title and returns a *Feed.

func (*Feed) AddAuthor

func (f *Feed) AddAuthor(a *Person) int

AddAuthor adds the Person a as an author to the Feed. It returns the index as an int.

func (*Feed) AddCategory

func (f *Feed) AddCategory(c *Category) int

AddCategory adds the Category c to the Feed. It returns the index as an int.

func (*Feed) AddContributor

func (f *Feed) AddContributor(c *Person) int

AddContributor adds the Person c as a contributor to the Feed. It returns the index as an int.

func (*Feed) AddEntry

func (f *Feed) AddEntry(e *Entry) int

AddEntry adds the Entry e to the Feed. It returns the index as an int.

func (*Feed) AddExtension

func (f *Feed) AddExtension(e *ExtensionElement) int

AddExtension adds the Extension e to the Feed. It returns the index as an int.

func (f *Feed) AddLink(l *Link) int

AddLink adds the Link l to the Feed. It returns the index as an int.

There should be one Link with Rel "self".

func (*Feed) Check

func (f *Feed) Check() error

Check checks the Feed for incompatibilities with RFC4287. It returns an error.

func (*Feed) DeleteAuthor added in v0.5.0

func (f *Feed) DeleteAuthor(index int) error

DeleteAuthor deletes the Person at index from the Feed. It returns an error.

func (*Feed) DeleteCategory added in v0.5.0

func (f *Feed) DeleteCategory(index int) error

DeleteCategory deletes the Category at index from the Feed. It returns an error.

func (*Feed) DeleteContributor added in v0.5.0

func (f *Feed) DeleteContributor(index int) error

DeleteContributor deletes the Person at index from the Feed. It returns an error.

func (*Feed) DeleteEntry added in v0.5.0

func (f *Feed) DeleteEntry(index int) error

DeleteEntry deletes the Entry at index from the Feed. It returns an error.

func (*Feed) DeleteEntryByURI added in v0.5.0

func (f *Feed) DeleteEntryByURI(uri string) error

DeleteEntryByURI deletes the Entry from the Feed. It takes in a string uri and returns an error.

func (*Feed) DeleteExtension added in v0.5.0

func (f *Feed) DeleteExtension(index int) error

DeleteExtension deletes the Extension at index from the Feed. It returns an error.

func (f *Feed) DeleteLink(index int) error

DeleteLink deletes the Link at index from the Feed. It returns an error.

func (*Feed) ToXML

func (f *Feed) ToXML(encoding string) (string, error)

ToXML converts the Feed to XML. It returns a string and an error.

type Generator

type Generator struct {
	XMLName xml.Name `xml:"generator"`
	*CommonAttributes
	URI     string `xml:"uri,attr,omitempty"` // IRI
	Version string `xml:"version,attr,omitempty"`
	Text    string `xml:",chardata"`
}

func NewGenerator

func NewGenerator(text string) *Generator

NewGenerator creates a new Generator. It takes in a string text and returns a *Generator.

func (*Generator) Check

func (g *Generator) Check() error

Check checks the Generator for incompatibilities with RFC4287. It returns an error.

type ID

type ID struct {
	XMLName xml.Name `xml:"id"`
	*CommonAttributes
	URI string `xml:",chardata"` // IRI
}

func NewID

func NewID(uri string) *ID

NewID creates a new ID. It takes in a string uri and returns a *ID.

func (*ID) Check

func (i *ID) Check() error

Check checks the ID for incompatibilities with RFC4287. It returns an error.

type Icon

type Icon struct {
	XMLName xml.Name `xml:"icon"`
	*CommonAttributes
	URI string `xml:",chardata"` // IRI
}

func NewIcon

func NewIcon(uri string) *Icon

NewIcon creates a new Icon. It takes in a string uri and returns a *Icon.

func (*Icon) Check

func (i *Icon) Check() error

Check checks the Icon for incompatibilities with RFC4287. It returns an error.

type InlineOtherContent

type InlineOtherContent struct {
	XMLName xml.Name `xml:"content"`
	*CommonAttributes
	AnyElement any    `xml:",chardata"`
	Type       string `xml:"type,attr,omitempty"` // MediaType
}

func (*InlineOtherContent) Check

func (i *InlineOtherContent) Check() error

Check checks the InlineOtherContent for incompatibilities with RFC4287. It returns an error.

type InlineTextContent

type InlineTextContent struct {
	XMLName xml.Name `xml:"content"`
	*CommonAttributes
	Type string `xml:"type,attr,omitempty"` // Must be text or html
	Text string `xml:",chardata"`
}

func (*InlineTextContent) Check

func (i *InlineTextContent) Check() error

Check checks the InlineTextContent for incompatibilities with RFC4287. It returns an error.

type InlineXHTMLContent

type InlineXHTMLContent struct {
	XMLName xml.Name `xml:"content"`
	*CommonAttributes
	XHTMLDiv *XHTMLDiv
	Type     string `xml:"type,attr"`
}

func (*InlineXHTMLContent) Check

func (i *InlineXHTMLContent) Check() error

Check checks the InlineXHTMLContent for incompatibilities with RFC4287. It returns an error.

type Link struct {
	XMLName xml.Name `xml:"link"`
	*CommonAttributes
	Title    string `xml:"title,attr,omitempty"`
	Href     string `xml:"href,attr"` // IRI
	Rel      string `xml:"rel,attr,omitempty"`
	Type     string `xml:"type,attr,omitempty"`     // MediaType
	HrefLang string `xml:"hreflang,attr,omitempty"` // LanguageTag
	Length   uint   `xml:"length,attr,omitempty"`
}
func NewLink(href string) *Link

NewLink creates a new Link. It takes in the string href and returns a *Link.

func (*Link) Check

func (l *Link) Check() error

Check checks the Link for incompatibilities with RFC4287. It returns an error.

type Logo struct {
	XMLName xml.Name `xml:"logo"`
	*CommonAttributes
	URI string `xml:",chardata"` // IRI
}
func NewLogo(uri string) *Logo

NewLogo creates a new Logo. It takes in a string uri and returns a *Logo.

func (*Logo) Check

func (l *Logo) Check() error

Check checks the Logo for incompatibilities with RFC4287. It returns an error.

type OutOfLineContent

type OutOfLineContent struct {
	XMLName xml.Name `xml:"content"`
	*CommonAttributes
	Type string `xml:"type,attr,omitempty"` // MediaType
	SRC  string `xml:"src,attr"`            // IRI
}

func (*OutOfLineContent) Check

func (o *OutOfLineContent) Check() error

Check checks the OutOfLineContent for incompatibilities with RFC4287. It returns an error.

type Person

type Person struct {
	*CommonAttributes
	Name       string              `xml:"name"`
	URI        string              `xml:"uri,omitempty"`   // IRI
	Email      string              `xml:"email,omitempty"` // EmailAddress
	Extensions []*ExtensionElement `xml:",any,omitempty"`
}

func NewPerson

func NewPerson(name string) *Person

NewPerson creates a new Person. It takes in a string name and returns a *Person.

func (*Person) AddExtension

func (p *Person) AddExtension(e *ExtensionElement) int

AddExtension adds the Extension e to the Person. It returns the index as an int.

func (*Person) Check

func (p *Person) Check() error

Check checks the Person for incompatibilities with RFC4287. It returns an error.

func (*Person) DeleteExtension added in v0.5.0

func (p *Person) DeleteExtension(index int) error

DeleteExtension deletes the Extension at index from the Person. It returns an error.

type PlainText

type PlainText struct {
	*CommonAttributes
	Type string `xml:"type,attr,omitempty"` // Must be text or html
	Text string `xml:",chardata"`
}

func (*PlainText) Check

func (p *PlainText) Check() error

Check checks the PlainText for incompatibilities with RFC4287. It returns an error.

type Source

type Source struct {
	XMLName xml.Name `xml:"source"`
	*CommonAttributes
	Authors      []*Person           `xml:"author,omitempty"`
	Categories   []*Category         `xml:",omitempty"`
	Contributors []*Person           `xml:"contributor,omitempty"`
	Generator    *Generator          `xml:",omitempty"`
	Icon         *Icon               `xml:",omitempty"`
	ID           *ID                 `xml:",omitempty"`
	Links        []*Link             `xml:",omitempty"`
	Rights       Text                `xml:"rights,omitempty"`
	Subtitle     Text                `xml:"subtitle,omitempty"`
	Title        Text                `xml:"title,omitempty"`
	Updated      *Date               `xml:"updated,omitempty"`
	Extensions   []*ExtensionElement `xml:",any,omitempty"`
}

func NewSource added in v0.4.0

func NewSource() *Source

NewSource creates a new Source. It returns a *Source.

func (*Source) AddAuthor added in v0.5.0

func (s *Source) AddAuthor(a *Person) int

AddAuthor adds the Person a as an author to the Source. It returns the index as an int.

func (*Source) AddCategory added in v0.5.0

func (s *Source) AddCategory(c *Category) int

AddCategory adds the Category c to the Source. It returns the index as an int.

func (*Source) AddContributor added in v0.5.0

func (s *Source) AddContributor(c *Person) int

AddContributor adds the Person c as a contributor to the Source. It returns the index as an int.

func (*Source) AddExtension added in v0.5.0

func (s *Source) AddExtension(e *ExtensionElement) int

AddExtension adds the ExtensionElement e to the Source. It returns the index as an int.

func (s *Source) AddLink(l *Link) int

AddLink adds the Link l to the Source. It returns the index as an int.

func (*Source) Check

func (s *Source) Check() error

Check checks the Source for incompatibilities with RFC4287. It returns an error.

func (*Source) DeleteAuthor added in v0.5.0

func (s *Source) DeleteAuthor(index int) error

DeleteAuthor deletes the Person at index from the Source. It returns an error.

func (*Source) DeleteCategory added in v0.5.0

func (s *Source) DeleteCategory(index int) error

DeleteCategory deletes the Category at index from the Source. It returns an error.

func (*Source) DeleteContributor added in v0.5.0

func (s *Source) DeleteContributor(index int) error

DeleteContributor deletes the Person at index from the Source. It returns an error.

func (*Source) DeleteExtension added in v0.5.0

func (s *Source) DeleteExtension(index int) error

DeleteExtension deletes the Extension at index from the Source. It returns an error.

func (s *Source) DeleteLink(index int) error

DeleteLink deletes the Link at index from the Source. It returns an error.

type Text

type Text interface {
	Check() error
	// contains filtered or unexported methods
}

func NewText

func NewText(textType, content string) Text

NewText creates a new Text. It takes in the strings textType and content and returns a Text.

If textType is invalid it returns nil.

type XHTMLDiv

type XHTMLDiv struct {
	XMLName xml.Name `xml:"div"`
	XMLNS   string   `xml:"xmlns,attr"`
	Content string   `xml:",innerxml"`
}

func NewXHTMLDiv added in v0.2.0

func NewXHTMLDiv(content string) *XHTMLDiv

NewXHTMLDiv creates a new XHTMLDiv. It takes in a string content and returns a *XHTMLDiv.

func (*XHTMLDiv) Check added in v0.2.0

func (x *XHTMLDiv) Check() error

Check checks the XHTMLDiv for incompatibilities with RFC4287. It returns an error.

type XHTMLText

type XHTMLText struct {
	*CommonAttributes
	XHTMLDiv *XHTMLDiv
	Type     string `xml:"type,attr"` // Must be xhtml
}

func (*XHTMLText) Check

func (x *XHTMLText) Check() error

Check checks the XHTMLText for incompatibilities with RFC4287. It returns an error.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL