Help us shape the future of Go! Take the 2026 Go Developer Survey

playground

package module
v0.0.0-...-0c97415 Latest Latest
Warning

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

Go to latest
Published: Oct 31, 2026 License: BSD-3-Clause Imports: 43 Imported by: 0

Documentation

Overview

Package playground is the go-slides playground: a deck editor beside a live preview, in the browser, with no server. It is a go-widgets application (scene.go, the View) over a go-widgets/mvvm view-model (this file); the slides are rendered by go-slides itself in a Web Worker (render.go), and shown in a sandboxed iframe the host page places over the preview pane.

Everything here is tagless and tested natively; cmd/playground-wasm is the thin js/wasm shell that wires it to the page.

Index

Constants

View Source
const (
	DraftProject    = "draft"
	PreviousProject = "previous"
	UIProject       = "ui"
)

The projects the playground keeps: the draft (the deck being edited, deck.md, with the files the file panel adds), the draft it last replaced, and the UI state, apart from the deck.

View Source
const (
	AlignNone   byte = 0
	AlignLeft   byte = 'l'
	AlignCenter byte = 'c'
	AlignRight  byte = 'r'
)

Column alignments of a table.

View Source
const (
	MaxTableCols = 12
	MaxTableRows = 30
)

Table size limits of InsertTable.

View Source
const (
	UndoShortcut = "Ctrl+Z"
	RedoShortcut = "Ctrl+Shift+Z"
)

The shortcuts the tooltips show (Cmd on a Mac; Ctrl+Y is Redo too).

View Source
const (
	MaxZipEntries = 4 * store.MaxFiles
	MaxZipSize    = store.MaxProjectSize
	MaxZipRatio   = 100
)

The bounds of an imported .zip, past the project's own (store.MaxFiles, MaxFileSize, MaxProjectSize): how many entries the archive may list at all, how large it may be, and how much larger than its compressed size its contents may be (a zip bomb inflates a few kilobytes to gigabytes).

View Source
const AutoEvery = 2 * time.Minute

AutoEvery is the least time between two automatic versions.

View Source
const ConfigPath = "slides.yaml"

ConfigPath is the project's configuration, which the slides command and the VS Code extension read next to the deck.

View Source
const ConfigTemplate = "# The project's configuration, read by the slides command and the\n" +
	"# VS Code extension too:  slides build deck.md\n"

ConfigTemplate is a new project's configuration.

View Source
const DeckFile = store.DeckPath

DeckFile is the deck's path in a project.

View Source
const DeckPath = "/playground/deck.md"

DeckPath is the path the playground's deck goes by: there is no file, but error messages and relative paths need one.

View Source
const ExportTimeout = 3 * time.Minute

ExportTimeout is how long an export may run before the host gives up on its worker (a deck of a few hundred slides takes seconds).

View Source
const JournalBudget = 1 << 20

JournalBudget bounds the text the journal holds (UTF-16 code units are what localStorage counts; a deck past it is kept by IndexedDB alone, as before, and the status says so once).

View Source
const LinkPlaceholder = "https://"

LinkPlaceholder is the url a new link starts with, selected so that it is typed over.

View Source
const MaxShare = 64 << 10

MaxShare bounds the fragment a link may carry, encoded; browsers and chat tools cut long URLs well before this anyway.

View Source
const MaxSource = 1 << 20

MaxSource bounds the deck the playground renders: a slide deck typed in a browser is a few kilobytes (media dropped into it go into the project's media/, not its text), and the bound keeps a pasted ten megabytes from holding the worker.

View Source
const MaxTextFiles = mediafiles.MaxText

MaxTextFiles bounds the text the renderer is sent besides the deck, on every keystroke.

View Source
const RuleLine = "<hr>"

RuleLine is the horizontal rule the Format tab writes. Not `---`, and not `***` or `___` either: Marpit reads every thematic break as a slide separator (rendered and checked in format_render_test.go), so a rule inside a slide is the <hr> tag, which the default HTML allow-list keeps.

View Source
const ShareBase = "https://go-slides.github.io/playground/"

ShareBase is the page a shared link opens.

View Source
const ZipName = "slides-project.zip"

ZipName is the name a downloaded project has.

Variables

View Source
var (
	MarkBold      = Marker{"bold", "**", "**"}
	MarkItalic    = Marker{"italic", "_", "_"}
	MarkStrike    = Marker{"strike", "~~", "~~"}
	MarkCode      = Marker{"code", "`", "`"}
	MarkUnderline = Marker{"underline", "<u>", "</u>"}
)

The inline marks of the Format tab. Italic is written with underscores, as the playground's own examples do; a *star* italic is found and removed as well.

View Source
var (
	LightPalette = Palette{
		Background: toolkit.RGB(0xff, 0xff, 0xff), Surface: toolkit.RGB(0xf7, 0xf8, 0xfb),
		Ink: toolkit.RGB(0x0f, 0x11, 0x15), Line: toolkit.RGB(0xe7, 0xe9, 0xee),
		Border: toolkit.RGB(0xcf, 0xd4, 0xdd), Accent: toolkit.RGB(0x4f, 0x46, 0xe5),
	}
	DarkPalette = Palette{
		Background: toolkit.RGB(0x0b, 0x0e, 0x14), Surface: toolkit.RGB(0x14, 0x1a, 0x24),
		Ink: toolkit.RGB(0xe6, 0xed, 0xf3), Line: toolkit.RGB(0x23, 0x2a, 0x36),
		Border: toolkit.RGB(0x3a, 0x45, 0x56), Accent: toolkit.RGB(0x9e, 0x99, 0xf1),
	}
)

LightPalette and DarkPalette are the landing's tokens with go-slides' indigo, the fallback when the host passes none and what tests draw with.

View Source
var BuiltinThemes = []string{"default", "gaia", "uncover"}

BuiltinThemes are Marp's themes, which go-slides bundles.

View Source
var Commands = []string{
	"bold", "italic", "underline", "strike", "code",
	"h1", "h2", "h3", "bullets", "numbered", "quote", "rule",
	"table", "row-above", "row-below", "row-delete", "col-left", "col-right", "col-delete",
	"colalign-left", "colalign-center", "colalign-right", "table-format",
	"slide-left", "slide-center", "slide-right",
	"link",
}

Commands are the names of the Format commands, in the tab's order.

View Source
var DocLinks = docLinks()

DocLinks are the documentation pages the help pane links to, in the language of the playground. Rebuilt by SetLocale.

View Source
var ErrNoDeck = errf(KErrZipNoDeck)

ErrNoDeck is an archive with no deck in it.

View Source
var ExampleNames = exampleTitles()

ExampleNames are their titles, for the picker. Rebuilt by SetLocale.

View Source
var Examples = loadExamples()

Examples are the example decks, in order.

View Source
var ExportFormats = []string{"pdf", "pptx", "odp"}

ExportFormats are the formats the toolbar offers, in its order.

View Source
var FormatShortcuts = []FormatShortcut{
	{"KeyB", false, false, "bold", "Ctrl+B"},
	{"KeyI", false, false, "italic", "Ctrl+I"},
	{"KeyU", false, false, "underline", "Ctrl+U"},
	{"KeyX", true, false, "strike", "Ctrl+Shift+X"},
	{"KeyE", false, false, "code", "Ctrl+E"},
	{"KeyK", false, false, "link", "Ctrl+K"},
	{"Digit1", false, true, "h1", "Ctrl+Alt+1"},
	{"Digit2", false, true, "h2", "Ctrl+Alt+2"},
	{"Digit3", false, true, "h3", "Ctrl+Alt+3"},
	{"Digit8", true, false, "bullets", "Ctrl+Shift+8"},
	{"Digit7", true, false, "numbered", "Ctrl+Shift+7"},
	{"Period", true, false, "quote", "Ctrl+Shift+."},
}

FormatShortcuts follow the editors whose toolbars the bibliography read: Ctrl+B, Ctrl+I, Ctrl+U and Ctrl+K as in Typora and Markdown All in One, Ctrl+E for code and Ctrl+Shift+X for strikethrough, Ctrl+Shift+7 and 8 for the lists as GitHub's editor has them, and Ctrl+Alt+1..3 for the headings (Ctrl+1..3 are the browser's tabs).

View Source
var GeneralHelp = T(KHelpGeneral)

GeneralHelp is what the help pane says when the caret is on nothing it knows: how to use the playground. One paragraph or item per line: the pane wraps them to its width. The text is the catalogue's (KHelpGeneral); rebuilt by SetLocale.

View Source
var KindTitle = T(KKindTitle)

KindTitle is the kind picker's title entry. Rebuilt by SetLocale.

Langs are the twelve languages, in the order of the site's selector.

ModeNames are the modes in Mode order. Rebuilt by SetLocale.

View Source
var ProjectDirs = []string{"kinds", "media", "themes"}

ProjectDirs are the folders the panel always shows.

View Source
var ProjectItems = projectItems()

ProjectItems are the Project menu's items; item 0 is its title. Rebuilt by SetLocale.

View Source
var RibbonLabels = ribbonLabels()

RibbonLabels are the labels of the controls a test harness clicks, by the name Scene.Rects gives them. Rebuilt by SetLocale.

View Source
var SaveItems = saveItems()

SaveItems are the Save as menu: its title, then what it saves. Rebuilt by SetLocale.

View Source
var States = []string{
	"bold", "italic", "underline", "strike", "code",
	"h1", "h2", "h3", "bullets", "numbered", "quote",
	"colalign-left", "colalign-center", "colalign-right",
	"slide-left", "slide-center", "slide-right", "link",
}

States are the names of the toggles' states.

Functions

func BlockState

func BlockState(src string, caret Pos) (heading int, bullets, numbered, quote bool)

BlockState is what the caret's line is: a heading's level (0 for none), whether it is in a bulleted or a numbered list, whether it is quoted.

func CatalogueRunes

func CatalogueRunes(l Lang) []rune

CatalogueRunes are the distinct runes the language's messages draw: what a font subset for the language must hold, whatever else it holds.

func DataTemplate

func DataTemplate() string

DataTemplate is a template data file.

func DeckTheme

func DeckTheme(src string) (string, bool)

DeckTheme is the theme a deck's front matter names, and whether it names one. A quoted value is unquoted and a trailing comment dropped; an HCL value that is an expression, not a string, is returned as written.

func DecodeShare

func DecodeShare(fragment string) (string, error)

DecodeShare reads the deck a fragment carries ("#" optional). It refuses a fragment that is not a shared deck, that does not decode, that inflates past MaxSource (a small link must not expand to gigabytes), or that is not UTF-8 text.

func EncodeShare

func EncodeShare(src string) (string, error)

EncodeShare returns the fragment (without "#") that carries src.

func ErrText

func ErrText(err error) string

ErrText is an error in the language in use: a coded error (store, media, templates) by its catalogue message "err.<code>", any other as it says itself.

func Errorf

func Errorf(k Key, args ...any) error

Errorf is an error carrying the message k, for the page's code (wasm).

func ExportZip

func ExportZip(p ProjectFiles, modified func(string) time.Time) ([]byte, error)

ExportZip writes a project as a .zip, files sorted, each with its time.

func FileLanguage

func FileLanguage(p string) string

FileLanguage names the editor's language for a file.

func HasCJKFont

func HasCJKFont() bool

HasCJKFont is whether a CJK face was given.

func InLink(src string, caret Pos) bool

InLink reports whether the caret is in a link.

func InlineActive

func InlineActive(src string, caret Pos, sel Range, m Marker) bool

InlineActive reports whether the text at the caret (the selection, or the word) is marked with m: what a toggle button shows as pressed.

func InlineMedia

func InlineMedia(page string, media map[string][]byte) string

InlineMedia puts the media a page names by relative path into the page as data: URIs, for a page that leaves the playground as one file (Save as ▸ HTML page): mediafiles.Inline.

func IsMedia

func IsMedia(p string) bool

IsMedia reports whether a project file is a medium (bytes), not text the editor opens and the renderer reads (mediafiles.IsMedia).

func KindComment

func KindComment(kind string) string

KindComment is the comment that gives a slide its kind.

func KindTemplate

func KindTemplate(name string) string

KindTemplate is a kinds file holding one kind, named name.

func ListInConfig

func ListInConfig(src, key, value string) (string, error)

ListInConfig adds value to the list under key in slides.yaml's text (making the file's text when it is empty), keeping its comments; a key that holds one path becomes a list of two; a value listed already changes nothing.

func MediaMarkdown

func MediaMarkdown(p string) string

MediaMarkdown is what a medium dropped into the editor adds to the deck: an image, or the HTML of a video or a sound (go-slides' HTML allowlist takes them).

func MediaType

func MediaType(p string, data []byte) string

MediaType is the type a medium is posted and inlined with (mediafiles.Type).

func NewTabID

func NewTabID() string

NewTabID is a random id for a page load: the store stamps a project with it, and two tabs tell their drafts apart by it.

func ParseColor

func ParseColor(s string) (toolkit.RGBA, bool)

ParseColor reads "#rrggbb", "#rgb" or "rgb(r, g, b)" — what getComputedStyle returns for a custom property holding a colour.

func PluralCategory

func PluralCategory(l Lang, n int) string

PluralCategory is the CLDR plural category of the integer n in l: "one", "few", "many" or "other".

func PluralForms

func PluralForms(l Lang) []string

PluralForms are the categories a language's catalogue must give for a counted message.

func Presentation

func Presentation(src string) (string, error)

Presentation builds the deck as the slides command would: one HTML page, with the keyboard, transitions and the presenter view. Raw HTML follows Marp's allowlist.

func PreviewClip

func PreviewClip(frame, pop toolkit.Rect, dpr float64) string

PreviewClip is the CSS clip-path that keeps the preview iframe (frame, device pixels) from covering an open drop-down list (pop): the list's rectangle cut out of the frame as a notch from its top edge, where a toolbar's list overlaps it ("none" when they do not overlap). dpr turns device pixels into CSS pixels.

func ProjectPresentation

func ProjectPresentation(src string, files map[string]string, dirs []string) (string, error)

ProjectPresentation is Presentation of a deck with its project's text files and folders (Request's Files and Dirs).

func ProjectThemeChoices

func ProjectThemeChoices(src, css string) []string

ProjectThemeChoices are ThemeChoices with the themes the project's style sheets define (/* @theme name */), after the built-in ones.

func RedoTip

func RedoTip(label string) string

RedoTip is the Redo control's tooltip.

func RenameInConfig

func RenameInConfig(src, from, to string, remove bool) (string, error)

RenameInConfig replaces a path listed under themes, kinds or data (a file renamed or moved); remove drops it instead (a file deleted). The text is returned as it is when nothing names the path.

func Replace

func Replace(src string, c Change) string

Replace applies a change to a source.

func RetagImage

func RetagImage(md, alt string) string

RetagImage rewrites the alternative text of an image's Markdown, ![alt](url), to alt: "bg" makes it a background, "bg left:40%" a split background (Marpit's image syntax). Markdown that is not an image is returned as it is.

func SetCJKFont

func SetCJKFont(ttf []byte)

SetCJKFont gives the playground the face to draw the language's script with, behind the Latin UI face: Chinese, Japanese, Korean or Cyrillic. Call it before SetupText; nil data or a face that does not parse leaves the Latin face alone.

func SetTheme

func SetTheme(src, name string) string

SetTheme sets a deck's theme in its front matter, the deck's text being the one source of truth (a shared link keeps the theme): the theme line is rewritten, or added first in the front matter, or a front matter is added. HCL front matter gets HCL. A name that is not a plain name is refused, and the deck returned as it is.

func SetupText

func SetupText(scale float64)

SetupText installs the toolkit's metric scale and an anti-aliased face at that scale. The wasm shell calls it with window.devicePixelRatio before NewScene: one logical pixel is one CSS pixel, drawn at device resolution.

func ShortcutText

func ShortcutText(command string) string

ShortcutText is the shortcut of a command as shown in a tooltip ("" for none).

func SlideAt

func SlideAt(lines []int, line int) int

SlideAt is the slide a source line belongs to: the last slide starting at or before it (0 for a line before the first; -1 when there are no slides).

func SlideLines

func SlideLines(parts []string) []int

SlideLines is the 0-based source line each slide starts at, read from the overview's slides (-1 for a slide that names none).

func T

func T(k Key, args ...any) string

T is the message k in the current language, with fmt's verbs filled from args. A key missing in the language is read in English; missing in English it is the key itself, which the test catches.

func TableState

func TableState(src string, caret Pos) (in bool, align byte)

TableState says whether the caret is in a table, and the alignment of its column.

func ThemeChoices

func ThemeChoices(src string) []string

ThemeChoices are the themes the picker offers for a deck: the built-in ones, the ones the deck defines, and the one it names if it is neither (a typo, or a theme of the command's --theme-set), so the picker always shows what the deck says.

func ThemeOptions

func ThemeOptions(themes []string) []string

ThemeOptions are the theme drop-down's entries, each saying what it is.

func ThemeTemplate

func ThemeTemplate(name, base string) string

ThemeTemplate is a theme built on a built-in one (@import), named name.

func Tn

func Tn(k Key, n int, args ...any) string

Tn is the message k in the plural form n takes in the current language: the catalogue holds k+".one", k+".few", k+".many" and k+".other" as the language's CLDR plural rules (https://cldr.unicode.org/index/cldr-spec/plural-rules) need them; "other" is always there and is what a missing form falls back to.

func UndoTip

func UndoTip(label string) string

UndoTip is the Undo control's tooltip: what it would undo, and the shortcut.

func Wrap

func Wrap(text string, cols int) string

Wrap breaks text into lines of at most cols cells, at spaces, keeping its own line breaks and the indentation of a list item's continuation. A cell is a rune, except for the wide runes of Chinese, Japanese and Korean, which take two and which a line may break between (no space separates their words), never before a closing mark or a full stop.

Types

type BytesDownloader

type BytesDownloader interface {
	DownloadBytes(name, mime string, data []byte)
}

BytesDownloader is a host that saves binary files (a .zip).

type Change

type Change struct {
	From, To Pos
	Text     string
	Caret    Pos
	// Select, when set, is the range left selected afterwards (the text a toggle
	// marked, a link's url to type over); its end is where the caret is.
	Select *Range
}

Change replaces the text from From to To (the same position when it only inserts) by Text, and says where the caret goes afterwards.

func Block

func Block(src string, caret Pos, sel Range, open, close string) Change

Block inserts a block that opens and closes with a line (math between $$, code between fences): the selection, when there is one, becomes its content; otherwise the content is empty and the caret is put on it.

func InsertLink(src string, caret Pos, sel Range) Change

InsertLink makes or edits a link at the caret.

func InsertRule

func InsertRule(src string, caret Pos, sel Range) Change

InsertRule puts a horizontal rule on a line of its own at the caret.

func InsertSlide

func InsertSlide(src string, caret Pos) Change

InsertSlide adds an empty slide after the one the caret is in: a separator and a blank line at the end of that slide's text, the caret on the new slide.

func InsertTable

func InsertTable(src string, caret Pos, sel Range, cols, rows int) Change

InsertTable writes a table of cols columns and rows rows (the header counts) on lines of its own after the caret's line (in place, on an empty line; after the table, inside one), the header's first cell selected for typing over.

func Line

func Line(src string, caret Pos, sel Range, text string) Change

Line inserts a line of text (an image, a background) on a line of its own at the caret, replacing the selection if there is one, the caret after it.

func SetKind

func SetKind(src string, caret Pos, kind string) Change

SetKind gives the slide the caret is in a kind: its first line becomes the comment (a kind it already has is replaced), the caret staying on what it was on.

func SetSlideAlignment

func SetSlideAlignment(src string, caret Pos, a SlideAlignment) Change

SetSlideAlignment aligns the content of the slide the caret is in: a block `<style scoped> section { text-align: … } </style>` at the top of its text (the block is rewritten when there is one, removed for left, the theme's own). Asking for the alignment the slide has returns it to left, as the toggle buttons do.

func TableAddColumn

func TableAddColumn(src string, caret Pos, right bool) (Change, bool)

TableAddColumn adds an empty column right of the caret's column (left of it when right is false).

func TableAddRow

func TableAddRow(src string, caret Pos, below bool) (Change, bool)

TableAddRow adds an empty body row under the caret's row (above it when below is false; nothing goes above the header).

func TableAlign

func TableAlign(src string, caret Pos, a byte) (Change, bool)

TableAlign sets the alignment of the caret's column, or clears it when the column has that alignment already.

func TableDeleteColumn

func TableDeleteColumn(src string, caret Pos) (Change, bool)

TableDeleteColumn removes the caret's column; a table keeps at least one.

func TableDeleteRow

func TableDeleteRow(src string, caret Pos) (Change, bool)

TableDeleteRow removes the caret's body row.

func TableFormat

func TableFormat(src string, caret Pos) (Change, bool)

TableFormat writes the table around the caret aligned, as it is.

func ToggleHeading

func ToggleHeading(src string, caret Pos, sel Range, n int) (Change, bool)

ToggleHeading makes the lines level-n headings, or plain lines when they all are already.

func ToggleInline

func ToggleInline(src string, caret Pos, sel Range, m Marker) Change

ToggleInline marks the text at the caret with m, or unmarks it when it is marked already, as one change of the deck (so one step of Undo). The selection, or the word, stays selected (or the caret where it was in the text) so that pressing the button again undoes it.

func ToggleList

func ToggleList(src string, caret Pos, sel Range, numbered bool) (Change, bool)

ToggleList makes the lines a bulleted or a numbered list, or plain lines when they all are such a list already.

func ToggleQuote

func ToggleQuote(src string, caret Pos, sel Range) (Change, bool)

ToggleQuote puts a quote mark on the lines, or takes one level off when they all have one.

type CompareFile

type CompareFile struct {
	Path             string
	Old, New         string
	Added, Removed   int
	Binary           bool
	OldSize, NewSize int
}

CompareFile is one file of a comparison: its text then and now.

type Comparison

type Comparison struct {
	ID    int
	Name  string // the version's name, over its column
	Title string
	Files []CompareFile // the files that differ, sorted
}

Comparison is a version compared with the project now.

func Compare

func Compare(then, now ProjectState, title string, id int) *Comparison

Compare lists the files that differ between then and now.

type Confirmer

type Confirmer interface {
	Confirm(question string) bool
}

Confirmer asks the reader a yes/no question (window.confirm).

type CreateAsk

type CreateAsk struct {
	Folder bool
	N      int
}

CreateAsk asks the file panel to start typing a new name in place (the Project menu's New file… and New folder…): N counts the asks.

type DocLink struct {
	Title, URL string
}

DocLink is a link the help pane offers.

type EditorHistory

type EditorHistory interface {
	CanUndo() bool
	CanRedo() bool
	Undo()
	Redo()
	History() toolkit.TextHistory
	SetHistory(toolkit.TextHistory)
}

EditorHistory is the editor as the undo needs it: the scene's CodeEditor.

type Example

type Example struct {
	// Name is the title in the picker, in the current language; the decks
	// themselves stay in English (no translation of them exists).
	Name, Source string
	// File is the deck's file name in examples/.
	File string
}

Example is a deck the playground offers.

type Exporter

type Exporter interface {
	// Export makes the deck's document in format ("pdf", "pptx", "odp")
	// and offers it to save.
	Export(format, source string)
}

Exporter is the host's export engine: a second WebAssembly module (cmd/playground-export, go-slides' export engine, about 5 MiB gzipped) that the page downloads the first time an export is asked for, compiling it as it arrives, and runs in a worker. It reports back through ExportLoading, ExportProgress and Exported. A host without one offers no export.

type FormatShortcut

type FormatShortcut struct {
	Code       string
	Shift, Alt bool
	Command    string
	Text       string // as shown in a tooltip
}

FormatShortcut is a keyboard shortcut of a Format command, written for Ctrl (or Cmd on a Mac) held: the key by its physical name (KeyB, Digit1), with Shift and Alt when they are.

type Formatter

type Formatter struct {

	// Cmd are the commands, by the names Commands lists; State the pressed state of
	// the toggles, by the names States lists.
	Cmd   map[string]*mvvm.Command
	State map[string]*mvvm.Observable[bool]

	// HTMLAllowed is whether the deck's HTML policy keeps the <u> tag: true for the
	// playground, which renders with Marp's default allow-list (marp-core 4.4:
	// u, mark, sub, sup, ins, del, kbd… but no style attribute). With it false the
	// Underline command is disabled.
	HTMLAllowed bool
	// contains filtered or unexported fields
}

Formatter is the Format tab's part of the view-model: the commands that mark text, shape paragraphs, build tables, align a slide and make links, and the state the caret is in, which the tab's toggles show (Bold is pressed inside **…**). Every command is a pure edit of the deck (format_*.go) applied through the editor, so one press is one step of Undo; with no editor it is written into the source, as the Insert tab's commands are.

func (*Formatter) InsertTable

func (f *Formatter) InsertTable(cols, rows int)

InsertTable writes a table of cols columns and rows rows (header included) after the caret's line.

func (*Formatter) Refresh

func (f *Formatter) Refresh()

Refresh sets the toggles' state from the deck at the caret, and tells the commands that depend on it.

func (*Formatter) Shortcut

func (f *Formatter) Shortcut(code string, shift, alt bool) bool

Shortcut runs the command a Ctrl (or Cmd) shortcut stands for, and says whether there was one.

type Host

type Host interface {
	// SetFragment replaces the URL's fragment (no history entry).
	SetFragment(fragment string)
	// Download offers a file to save.
	Download(name, mime, data string)
	// Open opens a URL in a new tab.
	Open(url string)
	// Clipboard copies text.
	Clipboard(text string)
	// OpenPresenter opens (or brings forward) the presenter window.
	OpenPresenter()
	// PatchPreview sends a patch to the preview frame's document (see
	// overviewScript), which replaces only the slides that changed.
	PatchPreview(PreviewPatch)
}

Host is what the page does for the view-model.

type ImageMode

type ImageMode string

ImageMode is what an image picked for the deck becomes.

const (
	ModeImage      ImageMode = ""             // an image on the slide
	ModeBackground ImageMode = "bg"           // a background
	ModeBgLeft     ImageMode = "bg left:40%"  // a background filling the left 40%
	ModeBgRight    ImageMode = "bg right:40%" // a background filling the right 40%
)

The modes of the Insert tab's Image and Background controls.

type ImagePicker

type ImagePicker interface {
	PickImage(mode ImageMode)
}

ImagePicker is what a host that can ask for a file offers: it shows its file picker and, once the person chose an image and the project took it into media/, gives its Markdown (MediaMarkdown: ![name](media/name.png), not a data: URI) back to Inserter.Picked with the mode it was asked for.

type Inserter

type Inserter struct {

	// Slide adds an empty slide after the current one; Math and Code insert a
	// $$ block and a fenced Go block with line numbers, around the selection if
	// there is one.
	Slide, Math, Code *mvvm.Command
	// Image, Background, BgLeft and BgRight ask the host for an image file: as an
	// image on the slide, as the slide's background, or as a background filling
	// the left or the right 40%.
	Image, Background, BgLeft, BgRight *mvvm.Command
	// Kind is the kind picker's selection: 0 its title, another index the kind
	// KindItems()[i] (the kinds the editor's completion knows), applied to the
	// current slide when chosen, the picker returning to its title. KindVersion
	// counts the changes of the list.
	Kind        *mvvm.Observable[int]
	KindVersion *mvvm.Observable[int]

	// Selection is the editor's selection (the scene keeps it); Apply writes a
	// change into the editor (the scene sets it).
	Selection *mvvm.Observable[Range]
	Apply     func(Change)
	// contains filtered or unexported fields
}

Inserter is the Insert tab's part of the view-model: the commands that put a slide, an image, a background, math, a code block or a kind into the deck, each an edit of the Markdown at the caret (or around the selection). The editor is not known here: the scene gives the selection (Selection) and applies the changes (Apply), so that they join the editor's undo; with no editor the change is written into Source directly.

func (*Inserter) KindItems

func (in *Inserter) KindItems() []string

KindItems is the kind picker's list: its title, then the kinds the editor's completion offers for a `_kind:` comment in this deck.

func (*Inserter) Picked

func (in *Inserter) Picked(mode ImageMode, md string)

Picked is the host's answer to PickImage: the Markdown of the chosen image (MediaMarkdown), put into the deck as the mode asks. An image with text selected takes it as its alternative text.

func (*Inserter) RefreshKinds

func (in *Inserter) RefreshKinds()

RefreshKinds asks the editor package for the kinds of the deck as it is, the way its completion lists them after `<!-- _kind: `, and tells the picker when they changed.

type Journal

type Journal interface {
	JournalWrite(value string) error
	JournalRead() (string, bool)
	JournalClear()
}

Journal is the page's synchronous write-ahead log of the deck's text (localStorage of the top-level page). IndexedDB writes are asynchronous, and a page being closed may not wait for one (pagehide does not); a localStorage write is done when the call returns. So the text is written here first, on every save tick and when the page is hidden, and replayed at the next load when it is newer than the stored draft. Media stay in IndexedDB: the journal holds the deck's text alone.

type Key

type Key string

Key names a message. The constants are in i18n_keys.go; a few families (the Format tab's labels and tips, by control name) are built by the functions beside them.

const (
	// the ribbon's tabs
	KTabHome   Key = "tab.home"
	KTabInsert Key = "tab.insert"
	KTabFormat Key = "tab.format"
	KTabView   Key = "tab.view"
	KTabExport Key = "tab.export"

	// the ribbon's groups
	KGrpDeck      Key = "grp.deck"
	KGrpLook      Key = "grp.look"
	KGrpPresent   Key = "grp.present"
	KGrpDraft     Key = "grp.draft"
	KGrpSlides    Key = "grp.slides"
	KGrpPictures  Key = "grp.pictures"
	KGrpText      Key = "grp.text"
	KGrpPreview   Key = "grp.preview"
	KGrpEditor    Key = "grp.editor"
	KGrpSaveAs    Key = "grp.saveas"
	KGrpParagraph Key = "grp.paragraph"
	KGrpTable     Key = "grp.table"
	KGrpRowsCols  Key = "grp.rowscols"
	KGrpColumn    Key = "grp.column"
	KGrpSlideText Key = "grp.slidetext"
	KGrpLink      Key = "grp.link"

	// the controls' labels
	KLblExample     Key = "lbl.example"
	KLblReset       Key = "lbl.reset"
	KLblTheme       Key = "lbl.theme"
	KLblThemeOption Key = "lbl.themeoption"
	KLblDraft       Key = "lbl.draft"
	KLblShare       Key = "lbl.share"
	KLblPresent     Key = "lbl.present"
	KLblPreview     Key = "lbl.preview"
	KLblMinimap     Key = "lbl.minimap"
	KLblSlides      Key = "lbl.slides"
	KLblFiles       Key = "lbl.files"
	KLblHelp        Key = "lbl.help"
	KLblNewSlide    Key = "lbl.newslide"
	KLblImage       Key = "lbl.image"
	KLblBackground  Key = "lbl.background"
	KLblBgLeft      Key = "lbl.bgleft"
	KLblBgRight     Key = "lbl.bgright"
	KLblMath        Key = "lbl.math"
	KLblCode        Key = "lbl.code"
	KLblKind        Key = "lbl.kind"
	KLblHTML        Key = "lbl.html"
	KLblPDF         Key = "lbl.pdf"
	KLblPPTX        Key = "lbl.pptx"
	KLblODP         Key = "lbl.odp"

	// the controls' tooltips
	KTipExample    Key = "tip.example"
	KTipTheme      Key = "tip.theme"
	KTipDraft      Key = "tip.draft"
	KTipReset      Key = "tip.reset"
	KTipPresent    Key = "tip.present"
	KTipShare      Key = "tip.share"
	KTipKind       Key = "tip.kind"
	KTipNewSlide   Key = "tip.newslide"
	KTipImage      Key = "tip.image"
	KTipBackground Key = "tip.background"
	KTipMath       Key = "tip.math"
	KTipCode       Key = "tip.code"
	KTipMode       Key = "tip.mode"
	KTipMinimap    Key = "tip.minimap"
	KTipThumbs     Key = "tip.thumbs"
	KTipFiles      Key = "tip.files"
	KTipHelp       Key = "tip.help"
	KTipHTML       Key = "tip.html"
	KTipPDF        Key = "tip.pdf"
	KTipPPTX       Key = "tip.pptx"
	KTipODP        Key = "tip.odp"

	// the preview's modes
	KModeOverview  Key = "mode.overview"
	KModeSlideshow Key = "mode.slideshow"

	// the examples' titles (the decks stay in English)
	KExWelcome           Key = "ex.welcome"
	KExTemplates         Key = "ex.templates"
	KExKinds             Key = "ex.kinds"
	KExHCL               Key = "ex.hcl"
	KExGaia              Key = "ex.gaia"
	KExMedia             Key = "ex.media"
	KExGalleryDefault    Key = "ex.gallery.default"
	KExGalleryGaia       Key = "ex.gallery.gaia"
	KExGalleryUncover    Key = "ex.gallery.uncover"
	KExGalleryMath       Key = "ex.gallery.math"
	KExGalleryCode       Key = "ex.gallery.code"
	KExGalleryEmoji      Key = "ex.gallery.emoji"
	KExGalleryPresenting Key = "ex.gallery.presenting"
	KExGalleryHCL        Key = "ex.gallery.hcl"

	// the status line
	KStatusStarting           Key = "status.starting"
	KStatusOpenedShared       Key = "status.openedshared"
	KStatusSharedUnreadable   Key = "status.sharedunreadable"
	KStatusRendered           Key = "status.rendered"
	KStatusStale              Key = "status.stale"
	KStatusProblems           Key = "status.problems"
	KStatusDraftNotKept       Key = "status.draftnotkept"
	KStatusPresenting         Key = "status.presenting"
	KStatusLinkCopied         Key = "status.linkcopied"
	KStatusSavedHTML          Key = "status.savedhtml"
	KStatusShareDeckOnly      Key = "status.sharedeckonly"
	KStatusRestoredDraft      Key = "status.restoreddraft"
	KStatusSharedKeptDraft    Key = "status.sharedkeptdraft"
	KStatusRestoredDraftReset Key = "status.restoreddraftreset"
	KStatusDraftSavedAgain    Key = "status.draftsavedagain"
	KStatusDraftNotSaved      Key = "status.draftnotsaved"
	KStatusOtherTab           Key = "status.othertab"
	KStatusRestoredTheDraft   Key = "status.restoredthedraft"
	KStatusForgot             Key = "status.forgot"
	KStatusNoExport           Key = "status.noexport"
	KStatusExportRunning      Key = "status.exportrunning"
	KStatusTooLargeExport     Key = "status.toolargeexport"
	KStatusPreparing          Key = "status.preparing"
	KStatusLoadingEnginePct   Key = "status.loadingenginepct"
	KStatusLoadingEngine      Key = "status.loadingengine"
	KStatusExportProgress     Key = "status.exportprogress"
	KStatusExportFailed       Key = "status.exportfailed"
	KStatusSaved              Key = "status.saved"
	KStatusIsMedium           Key = "status.ismedium"
	KStatusMadeTheme          Key = "status.madetheme"
	KStatusMadeKind           Key = "status.madekind"
	KStatusMadeData           Key = "status.madedata"
	KStatusCannotSaveFile     Key = "status.cannotsavefile"
	KStatusSavedZip           Key = "status.savedzip"
	KStatusOpenedZip          Key = "status.openedzip"
	KStatusOpenedFolder       Key = "status.openedfolder"
	KStatusAddedSlide         Key = "status.addedslide"
	KStatusNoFilePicker       Key = "status.nofilepicker"
	KStatusKindSet            Key = "status.kindset"
	KStatusInsertedRule       Key = "status.insertedrule"
	KStatusInsertedTable      Key = "status.insertedtable"
	KStatusJournalLarge       Key = "status.journallarge"
	KStatusJournalRefused     Key = "status.journalrefused"
	KStatusAdded              Key = "status.added"
	KStatusAddedPut           Key = "status.addedput"
	KStatusPopupBlocked       Key = "status.popupblocked"

	// what an Insert command said it wrote
	KStatusInsertedFormula Key = "status.insertedformula"
	KStatusInsertedCode    Key = "status.insertedcode"
	KStatusPutFormula      Key = "status.putformula"
	KStatusPutCode         Key = "status.putcode"

	// why a Format command did nothing
	KWhyHeading    Key = "why.heading"
	KWhyList       Key = "why.list"
	KWhyQuote      Key = "why.quote"
	KWhyNotInTable Key = "why.notintable"
	KWhyNotInBody  Key = "why.notinbody"
	KWhyLastColumn Key = "why.lastcolumn"

	// the problems list
	KProblemLine    Key = "problem.line"
	KProblemError   Key = "problem.error"
	KProblemWarning Key = "problem.warning"

	// confirmations
	KConfirmReset      Key = "confirm.reset"
	KConfirmShareFiles Key = "confirm.sharefiles"
	KConfirmOpenZip    Key = "confirm.openzip"
	KConfirmOpenFolder Key = "confirm.openfolder"

	// dates and sizes
	KDateToday  Key = "date.today"
	KDateAt     Key = "date.at"
	KDateShort  Key = "date.short"
	KDateMonths Key = "date.months"
	KBytes      Key = "bytes"
	KWarn       Key = "warn"

	// the Draft menu
	KDraftRestoreMine     Key = "draft.restoremine"
	KDraftRestoreReplaced Key = "draft.restorereplaced"
	KDraftForget          Key = "draft.forget"
	KDraftResetExample    Key = "draft.resetexample"
	KDraftNothing         Key = "draft.nothing"

	// the Save as menu, the kind picker
	KSaveAs    Key = "saveas"
	KKindTitle Key = "kindtitle"

	// the file panel and the Project menu
	KProject             Key = "project"
	KFilesTreeLabel      Key = "files.treelabel"
	KFilesNewThemeFrom   Key = "files.newthemefrom"
	KFilesNewKind        Key = "files.newkind"
	KFilesNewData        Key = "files.newdata"
	KFilesNewFile        Key = "files.newfile"
	KFilesNewFolder      Key = "files.newfolder"
	KFilesAddMedia       Key = "files.addmedia"
	KFilesOpenZip        Key = "files.openzip"
	KFilesOpenFolder     Key = "files.openfolder"
	KFilesDownloadZip    Key = "files.downloadzip"
	KFilesInsertIntoDeck Key = "files.insertintodeck"
	KFilesDownload       Key = "files.download"
	KFilesUsage          Key = "files.usage"

	// the panes
	KPaneProblems      Key = "pane.problems"
	KPaneDocumentation Key = "pane.documentation"

	// the frames' accessible titles
	KFrameTitlePreview Key = "frame.preview"
	KFrameTitleRail    Key = "frame.rail"

	// the documentation links
	KDocGettingStarted Key = "doc.gettingstarted"
	KDocMarp           Key = "doc.marp"
	KDocTemplates      Key = "doc.templates"
	KDocKinds          Key = "doc.kinds"
	KDocHCL            Key = "doc.hcl"
	KDocCLI            Key = "doc.cli"
	KDocVSCode         Key = "doc.vscode"
	KDocPlayground     Key = "doc.playground"

	// the help pane
	KHelpGeneral Key = "help.general"

	// what a new table's header says: "Column 1"
	KTableColumn Key = "table.column"

	// errors
	KErrExists            Key = "err.exists"
	KErrDeckName          Key = "err.deckname"
	KErrNoSuchFile        Key = "err.nosuchfile"
	KErrNotFileName       Key = "err.notfilename"
	KErrDeckTooLarge      Key = "err.decktoolarge"
	KErrMaxFiles          Key = "err.maxfiles"
	KErrPathReason        Key = "err.pathreason"
	KErrProjectOver       Key = "err.projectover"
	KErrFileSize          Key = "err.filesize"
	KErrShareTooLargeDeck Key = "err.share.toolargedeck"
	KErrShareTooLargeLink Key = "err.share.toolargelink"
	KErrShareNoDeck       Key = "err.share.nodeck"
	KErrShareTooLong      Key = "err.share.toolong"
	KErrShareDamaged      Key = "err.share.damaged"
	KErrShareLarger       Key = "err.share.larger"
	KErrShareNotText      Key = "err.share.nottext"
	KErrZipNoDeck         Key = "err.zip.nodeck"
	KErrZipSize           Key = "err.zip.size"
	KErrZipNot            Key = "err.zip.not"
	KErrZipEntries        Key = "err.zip.entries"
	KErrZipLinks          Key = "err.zip.links"
	KErrZipFileOver       Key = "err.zip.fileover"
	KErrZipBomb           Key = "err.zip.bomb"
	KErrZipTwice          Key = "err.zip.twice"
	KErrZipMoreThan       Key = "err.zip.morethan"
	KErrFileOver          Key = "err.fileover"
	KErrFailed            Key = "err.failed"
	KErrFolderCount       Key = "err.foldercount"
	KErrFolderOver        Key = "err.folderover"
	KErrFolderMore        Key = "err.foldermore"
	KErrNotImage          Key = "err.notimage"
)

The messages of the playground, by key. A key is a typed constant, so a view cannot name a message that does not exist; i18n_test.go checks that every key is in every language with the same placeholders.

Counted messages (K...Rendered, Problems, Saved, FilesUsage) are bases: the catalogue holds base+".one", ".few", ".many" and ".other" as the language's CLDR plural rules ask (Tn).

const (
	KGrpHistory  Key = "grp.history"
	KLblUndo     Key = "lbl.undo"
	KLblRedo     Key = "lbl.redo"
	KTipUndo     Key = "tip.undo"
	KTipUndoStep Key = "tip.undostep"
	KTipRedo     Key = "tip.redo"
	KTipRedoStep Key = "tip.redostep"

	// the steps of the project's history, as Undo names them
	KStepExample        Key = "step.example"
	KStepReset          Key = "step.reset"
	KStepOpenZip        Key = "step.openzip"
	KStepOpenFolder     Key = "step.openfolder"
	KStepRename         Key = "step.rename"
	KStepDelete         Key = "step.delete"
	KStepRestoreDraft   Key = "step.restoredraft"
	KStepRestoreVersion Key = "step.restoreversion"
	KStatusUndone       Key = "status.undone"
	KStatusRedone       Key = "status.redone"

	KLblSaveVersion    Key = "lbl.saveversion"
	KLblVersions       Key = "lbl.versions"
	KTipSaveVersion    Key = "tip.saveversion"
	KTipVersions       Key = "tip.versions"
	KAskVersionName    Key = "ask.versionname"
	KVersionAuto       Key = "version.auto"
	KVersionBefore     Key = "version.before"
	KVersionExport     Key = "version.export"
	KVersionShared     Key = "version.shared"
	KVersionLine       Key = "version.line"
	KVersionsEmpty     Key = "versions.empty"
	KVersionsOff       Key = "versions.off"
	KLblOnlyNamed      Key = "lbl.onlynamed"
	KLblVersionPreview Key = "lbl.versionpreview"
	KLblVersionCompare Key = "lbl.versioncompare"
	KLblVersionRestore Key = "lbl.versionrestore"
	KLblVersionDelete  Key = "lbl.versiondelete"
	KLblBackToNow      Key = "lbl.backtonow"
	KLblSideBySide     Key = "lbl.sidebyside"
	KCompareTitle      Key = "compare.title"
	KCompareFileLine   Key = "compare.fileline"
	KCompareSame       Key = "compare.same"
	KCompareBinary     Key = "compare.binary"
	KCompareNow        Key = "compare.now"

	KStatusVersionSaved    Key = "status.versionsaved"
	KStatusVersionRestored Key = "status.versionrestored"
	KStatusVersionDeleted  Key = "status.versiondeleted"
	KStatusPreviewing      Key = "status.previewing"
	KStatusVersionsFull    Key = "status.versionsfull"
	KStatusVersionsFailed  Key = "status.versionsfailed"
	KStatusVersionLimit    Key = "status.versionlimit"
	KConfirmDeleteVersion  Key = "confirm.deleteversion"
)

Undo and the versions (history.go, versions.go).

type Lang

type Lang string

Lang is a language of the playground: the code of the site's languages.

const (
	LangEN Lang = "en"
	LangFR Lang = "fr"
	LangDE Lang = "de"
	LangES Lang = "es"
	LangIT Lang = "it"
	LangPT Lang = "pt" // Brazilian Portuguese (pt-BR)
	LangPL Lang = "pl"
	LangUK Lang = "uk"
	LangRU Lang = "ru"
	LangZH Lang = "zh"
	LangKO Lang = "ko"
	LangJA Lang = "ja"
)

The seven languages. Chinese is Simplified (the site's zh is zh-Hans).

func Locale

func Locale() Lang

Locale is the language in use.

func ParseLang

func ParseLang(tag string) (Lang, bool)

ParseLang reads a language tag ("fr", "fr-CA", "zh-Hans", "ja_JP") as one of the seven; false for any other.

func SetLocale

func SetLocale(tag string) Lang

SetLocale makes l the language and rebuilds every table derived from the messages (the menus' items, the ribbon's labels...). A tag that is not one of the seven leaves English. It returns the language in effect.

func (Lang) IsCJK

func (l Lang) IsCJK() bool

IsCJK is whether the language needs the CJK font.

func (Lang) IsCyrillic

func (l Lang) IsCyrillic() bool

IsCyrillic is whether the language is written in Cyrillic.

type Marker

type Marker struct {
	Name        string
	Open, Close string
}

Marker is an inline mark: the text it puts before and after what it marks.

type Mode

type Mode int

Mode is what the preview shows.

const (
	// ModeOverview stacks every slide, scrolling with the editor.
	ModeOverview Mode = iota
	// ModeSlideshow is the presentation itself: one slide at a time, the
	// keyboard, transitions, the presenter view (p).
	ModeSlideshow
)

type Palette

type Palette struct {
	Background, Surface, Ink, Line, Border, Accent toolkit.RGBA
}

Palette is the handful of colours the landing's stylesheet defines; the host reads them from its custom properties, so the canvas wears the page's light or dark theme and its brand accent.

func (Palette) Theme

func (p Palette) Theme() *toolkit.Theme

Theme is the toolkit theme the palette paints with.

type Picker

type Picker interface {
	Pick(what string)
}

Picker is a host that lets the reader pick files: what is "media" (files to add to media/), "zip" (a project's .zip) or "folder" (a project's folder). The host hands what was picked to AddFile, OpenZip or OpenFolder.

type Pos

type Pos struct{ Line, Col int }

Pos is a position in the deck: a 0-based line and a 0-based rune column.

type PreviewPatch

type PreviewPatch struct {
	Count   int          `json:"count"`
	Changed []SlidePatch `json:"changed"`
}

PreviewPatch turns the overview the frame shows into the next one: the slides that changed, by index, and how many there are now.

type ProjectFile

type ProjectFile struct {
	Path string
	Dir  bool
	Size int64
}

ProjectFile is one entry of the file panel.

type ProjectFiles

type ProjectFiles struct {
	Files map[string][]byte
	Dirs  []string
}

ProjectFiles are the files of a project going out or coming in: path → bytes, and the folders that hold nothing.

func ImportFolder

func ImportFolder(files map[string][]byte) (ProjectFiles, error)

ImportFolder reads a project from files picked as a folder (the File System Access API's directory, or an <input webkitdirectory>): the same rules as a .zip, paths relative to the folder.

func ImportZip

func ImportZip(data []byte) (ProjectFiles, error)

ImportZip reads a project from a .zip, refusing what would leave the project (absolute paths, ..), what would exceed its bounds, and archives whose contents are out of proportion to their size. A single folder holding everything (a zipped project folder) is the project. Files are read through a limit, so a header that lies about its size gains nothing.

type ProjectState

type ProjectState struct {
	Files map[string][]byte
	Dirs  []string
	Open  string
}

ProjectState is the whole project at one moment: every file (the deck, DeckFile, included), the folders made in the panel and the file shown. Undo puts one back, and a version keeps one.

func (ProjectState) Same

func (s ProjectState) Same(o ProjectState) bool

Same reports whether two states hold the same files and folders (the file shown aside).

func (ProjectState) Size

func (s ProjectState) Size() int

Size is the bytes the files take.

type Projects

type Projects interface {
	Projects() *store.Store
	// Announce tells the page's other tabs that this one saved a project
	// (BroadcastChannel); they call DraftSavedElsewhere.
	Announce(name string, saved time.Time)
}

Projects is the host's project store (playground/store): IndexedDB in the page, the projects it holds loaded before the view-model starts. A host without one still gives a working playground; nothing is kept.

type Prompter

type Prompter interface {
	Prompt(question, value string) (string, bool)
}

Prompter asks the reader for a line of text (window.prompt).

type Range

type Range struct{ Start, End Pos }

Range is a span of the deck, Start <= End; it is empty when they are equal.

func (Range) Empty

func (r Range) Empty() bool

Empty reports whether the range selects nothing.

type Request

type Request struct {
	ID     int    `json:"id"`
	Source string `json:"source"`
	Mode   Mode   `json:"mode"`
	// Presenter asks for the presenter view's page too: the presenter
	// window is open.
	Presenter bool `json:"presenter,omitempty"`
	// Files are the project's text files beside the deck (slides.yaml,
	// themes, kinds, data), by path; Dirs its folders. Media are not sent:
	// the renderer leaves their paths as they are (see media.go).
	Files map[string]string `json:"files,omitempty"`
	Dirs  []string          `json:"dirs,omitempty"`
}

Request asks the worker for a preview.

type Response

type Response struct {
	ID int `json:"id"`
	// Doc is the slideshow's page. An overview comes as Head and Parts
	// instead (editor.Result's), which the page assembles into a document
	// (overviewPage) or a patch of the slides that changed. Parts is nil
	// for no overview, and empty for an overview with no slide.
	Doc   string   `json:"doc,omitempty"`
	Head  string   `json:"head,omitempty"`
	Parts []string `json:"parts"`
	// PresenterDoc is the presenter view's page, when the request asked.
	PresenterDoc string              `json:"presenterDoc,omitempty"`
	Slides       int                 `json:"slides"`
	Diagnostics  []editor.Diagnostic `json:"diagnostics,omitempty"`
	Millis       int64               `json:"millis"`
	Error        string              `json:"error,omitempty"`
}

Response is the worker's answer.

func Serve

func Serve(req Request) Response

Serve renders a request; the worker runs it. The document it returns is the preview iframe's: a complete page with its own Content Security Policy, shown sandboxed (no same origin) by the host page.

func (Response) Page

func (r Response) Page() string

Page is the preview's document: the slideshow's, or the overview assembled from its parts ("" for neither: a render that failed).

func (Response) PageAt

func (r Response) PageAt(top SlideTop) string

PageAt is Page, an overview starting scrolled to top.

type Scene

type Scene struct {
	// contains filtered or unexported fields
}

Scene is the View: the toolkit widget tree bound to a ViewModel, plus the bookkeeping a canvas application needs. It implements go-widgets/ webcanvas's App, Resizer, Scroller and Animator, so the wasm shell is one webcanvas.Run call. The preview itself is not drawn here: it is an iframe the host places over PreviewRect.

func NewScene

func NewScene(vm *ViewModel, host Host, w, h int) *Scene

NewScene builds the widget tree for a w×h device-pixel surface and binds it to vm. Call SetupText first.

func (*Scene) AnimationStep

func (s *Scene) AnimationStep(dt float64) bool

AnimationStep repaints when something outside an input event changed the scene: a render that came back, the theme, a paste.

func (*Scene) CaretPixel

func (s *Scene) CaretPixel(line, col int) (int, int)

CaretPixel is the device pixel of a caret position in the editor, for a test harness that clicks there.

func (*Scene) Char

func (s *Scene) Char(ch string) bool

Char is typed text.

func (*Scene) Click

func (s *Scene) Click(x, y int) bool

Click is a primary-button press at device pixel (x, y).

func (*Scene) Context

func (s *Scene) Context(x, y int) bool

Context is a secondary click: the file panel's menu, over the panel.

func (*Scene) Copy

func (s *Scene) Copy() string

Copy returns the editor's selection, for the clipboard ("" if none).

func (*Scene) Cursor

func (s *Scene) Cursor(x, y int) string

Cursor is the CSS cursor for the pointer at device pixel (x, y): the resize cursor over a divider and while one is held, "" elsewhere.

func (*Scene) Draw

func (s *Scene) Draw(buf []byte)

Draw paints the whole surface into buf (RGBA, row-major).

func (*Scene) EditorCursor

func (s *Scene) EditorCursor() (int, int)

EditorCursor is the editor's caret (line, rune column).

func (*Scene) FileDragOver

func (s *Scene) FileDragOver(x, y int, over bool)

FileDragOver outlines the folder files dragged from outside would go in (over is false when the drag left the page or ended).

func (*Scene) FileDropDir

func (s *Scene) FileDropDir(x, y int) (string, bool)

FileDropDir is the folder a file dropped at device pixel (x, y) goes in, and whether (x, y) is over the file panel at all (else it is the editor's).

func (*Scene) GoToLine

func (s *Scene) GoToLine(line int)

GoToLine moves the editor's caret to the start of a line and scrolls it into view (a click on a slide in the preview).

func (*Scene) InsertImage

func (s *Scene) InsertImage(markdown string)

InsertImage puts a medium's Markdown (see MediaMarkdown) at the deck's caret, on a line of its own, focusing the editor: a file dropped on the page or pasted.

func (*Scene) KeyDown

func (s *Scene) KeyDown(code string) bool

KeyDown is a named key. A single character here is a shortcut (⌘V arrives as "v"): it is left to the browser, which turns copy and paste into the events the host forwards to Paste and Copy.

func (*Scene) Move

func (s *Scene) Move(x, y int) bool

Move is pointer motion: a drag to the pressed widget, else hover.

func (*Scene) OpenPopover

func (s *Scene) OpenPopover() (toolkit.Rect, bool)

OpenPopover is the device-pixel bounds of the open drop-down list, if one is open: the host keeps the preview iframe, which lies over the canvas, out of its way (PreviewClip).

func (*Scene) Paste

func (s *Scene) Paste(text string) bool

Paste inserts clipboard text into the editor.

func (*Scene) PreviewRect

func (s *Scene) PreviewRect() toolkit.Rect

PreviewRect is where the host places the preview iframe, in device pixels.

func (*Scene) ProblemHighlights

func (s *Scene) ProblemHighlights() []toolkit.Selection

ProblemHighlights are the ranges the editor shows as problems.

func (*Scene) Rects

func (s *Scene) Rects() map[string]toolkit.Rect

Rects names the on-screen rectangle of each control, in device pixels, for a test harness that drives the page with synthetic events.

func (*Scene) Release

func (s *Scene) Release(x, y int) bool

Release ends a press.

func (*Scene) Resize

func (s *Scene) Resize(cw, ch int) (int, int)

Resize relays the scene out for a canvas of cw×ch CSS pixels and returns the device-pixel size it renders at.

func (*Scene) Scroll

func (s *Scene) Scroll(x, y, dx, dy int) bool

Scroll is the wheel, in rows.

func (*Scene) SetPalette

func (s *Scene) SetPalette(p Palette)

SetPalette recolours the scene.

func (*Scene) Shortcut

func (s *Scene) Shortcut(code string, shift, alt bool) bool

Shortcut is Ctrl (or Cmd) held with a key, typed while the editor has the keyboard: the Format tab's shortcuts (Ctrl+B, Ctrl+I, Ctrl+U, Ctrl+E, Ctrl+K, Ctrl+Shift+X, …). code is the key's physical name (KeyB, Digit1). It reports whether it was one, so that the host keeps the browser's own for the others.

Undo (Ctrl/Cmd+Z) and Redo (Shift+Ctrl/Cmd+Z, Ctrl+Y) are taken wherever the keyboard is: they also take back an operation on the whole project.

func (*Scene) Size

func (s *Scene) Size() (int, int)

Size is the surface size in device pixels.

func (*Scene) Theme

func (s *Scene) Theme() *toolkit.Theme

Theme is the toolkit theme the scene paints with.

func (*Scene) ThumbsCards

func (s *Scene) ThumbsCards() (first toolkit.Rect, gap int)

ThumbsCards is the first card's rectangle relative to ThumbsRect, and the gap between cards, in device pixels: the frame stacks the slides from them.

func (*Scene) ThumbsRect

func (s *Scene) ThumbsRect() (r toolkit.Rect, ok bool)

ThumbsRect is where the host places the thumbnail frame, in device pixels; ok is false while the thumbnails are off or pulled shut by their divider.

func (*Scene) VisibleThumbCards

func (s *Scene) VisibleThumbCards() (first int, cards []toolkit.Rect)

VisibleThumbCards is the first slide in the strip's window and the rectangles of the cards there, in device pixels (the surface's coordinates), as the canvas draws them: what a test harness compares the frame's slides with.

type SlideAlignment

type SlideAlignment string

SlideAlignment is how the content of a slide is aligned.

const (
	SlideLeft   SlideAlignment = "left"
	SlideCenter SlideAlignment = "center"
	SlideRight  SlideAlignment = "right"
)

The alignments of a slide. Left is the theme's own: it is written as no block.

func SlideAlignmentAt

func SlideAlignmentAt(src string, caret Pos) SlideAlignment

SlideAlignmentAt is the alignment of the slide the caret is in.

type SlidePatch

type SlidePatch struct {
	I    int    `json:"i"`
	HTML string `json:"html"`
}

SlidePatch is one slide's new markup.

type SlideTop

type SlideTop struct {
	I int
	F float64
}

SlideTop is where the overview is scrolled: the top-most visible slide, and how far down it the view starts (0 to 1). The frame reports it as it scrolls; a new document starts there, so a replaced document keeps the slide in view whatever its new height. I < 0 is none.

type Transport

type Transport interface {
	Send(Request)
}

Transport carries render requests to the worker. Send never blocks; the answer comes back through ViewModel.Receive.

type ViewModel

type ViewModel struct {
	Source  *mvvm.Observable[string]
	Example *mvvm.Observable[int]
	Mode    *mvvm.Observable[int]
	// Theme is the index in Themes of the deck's theme. The deck's text is
	// the source of truth: choosing a theme writes it into the front matter
	// (SetTheme), and editing the front matter moves the choice. Themes are
	// the choices (ThemeChoices); ThemesVersion counts their changes.
	Theme         *mvvm.Observable[int]
	Themes        []string
	ThemesVersion *mvvm.Observable[int]

	// CaretLine and CaretCol are the editor's caret (0-based; the column
	// counts runes), which the context help and the scroll sync follow.
	CaretLine *mvvm.Observable[int]
	CaretCol  *mvvm.Observable[int]

	// Derived.
	Doc *mvvm.Observable[string] // the preview iframe's document
	// PresenterOpen says whether the presenter window is open: the host
	// sets it when the window opens and closes. PresenterDoc is its page.
	PresenterOpen *mvvm.Observable[bool]
	PresenterDoc  *mvvm.Observable[string]
	Status        *mvvm.Observable[string] // "12 slides · 48 ms"
	Problems      *mvvm.Observable[string] // one line per diagnostic
	Help          *mvvm.Observable[string] // the context help at the caret
	// Diagnostics are the last render's problems, for the editor's
	// highlights; Version counts the renders that changed them.
	Diagnostics []editor.Diagnostic
	DiagVersion *mvvm.Observable[int]

	// Minimap and Thumbs are the editor's minimap and the column of slide
	// thumbnails, each on or off. SlideLines are the source lines the slides
	// start at (SlidesVersion counts their changes); CurrentSlide is the slide
	// the caret is in, or the preview is scrolled to; RailDoc is the thumbnail
	// frame's document (see rail.go).
	Minimap    *mvvm.Observable[bool]
	Thumbs     *mvvm.Observable[bool]
	SlideLines []int

	SlidesVersion *mvvm.Observable[int]
	CurrentSlide  *mvvm.Observable[int]
	RailDoc       *mvvm.Observable[string]

	// Commands.
	Share    *mvvm.Command
	Download *mvvm.Command
	Reset    *mvvm.Command
	// Present shows the slideshow and opens the presenter view beside it.
	Present *mvvm.Command
	// Export are the PDF, PPTX and ODP downloads, by format (ExportFormats);
	// SaveAction is the Save as menu's selection (SaveItems; 0 its title).
	Export     map[string]*mvvm.Command
	SaveAction *mvvm.Observable[int]

	// DraftAction is the Draft menu's selection: 0 its title, another
	// index an action (DraftItems), run when chosen. DraftVersion counts
	// the changes of DraftItems.
	DraftAction  *mvvm.Observable[int]
	DraftVersion *mvvm.Observable[int]

	// Insert is the Insert tab: slide, image, background, math, code and kind.
	Insert *Inserter
	// Format is the Format tab: marks, paragraphs, tables, the slide's alignment
	// and links, with the state the caret is in.
	Format *Formatter

	// Undo and Redo take the last edit of the file shown, else the last
	// operation on the whole project, back and forth (history.go);
	// UndoLabel and RedoLabel name the project step they would take ("" for
	// an edit), for the tooltips.
	Undo, Redo           *mvvm.Command
	UndoLabel, RedoLabel *mvvm.Observable[string]

	// The versions (versionsvm.go): the pane on or off, its list and the
	// version picked, the named ones only or all; Compare is the version
	// compared with now (nil: none), CompareFile the file shown and
	// CompareSideBySide its layout.
	VersionsPane                                                                          *mvvm.Observable[bool]
	VersionItems                                                                          *mvvm.Observable[[]string]
	VersionSelected                                                                       *mvvm.Observable[int]
	OnlyNamed                                                                             *mvvm.Observable[bool]
	Compare                                                                               *mvvm.Observable[*Comparison]
	CompareFile                                                                           *mvvm.Observable[int]
	CompareSideBySide                                                                     *mvvm.Observable[bool]
	SaveVersion, PreviewVersion, CompareVersion, RestoreVersion, DeleteVersion, BackToNow *mvvm.Command
	// contains filtered or unexported fields
}

ViewModel holds the playground's state as go-widgets/mvvm observables. It references no widget; scene.go binds the widgets to it.

func NewViewModel

func NewViewModel(t Transport, h Host, fragment string) *ViewModel

NewViewModel builds the view-model on a starting deck: the shared deck of the URL's fragment if there is one, else the first example.

func (*ViewModel) AddFile

func (vm *ViewModel) AddFile(dir, name string, data []byte) (string, error)

AddFile stores bytes taken from outside (a drop, a file picked) in folder dir under name; a medium dropped at the top goes in media/. A name taken by other bytes gets a number; the same bytes again are the same file. It returns the path.

func (*ViewModel) BindEditor

func (vm *ViewModel) BindEditor(e EditorHistory)

BindEditor gives the undo the editor (the scene calls it once).

func (*ViewModel) Busy

func (vm *ViewModel) Busy() bool

Busy reports whether a render is in flight.

func (*ViewModel) CanRedo

func (vm *ViewModel) CanRedo() bool

CanRedo reports whether Redo would do something.

func (*ViewModel) CanUndo

func (vm *ViewModel) CanUndo() bool

CanUndo reports whether Undo would do something.

func (*ViewModel) CompareItems

func (vm *ViewModel) CompareItems() []string

CompareItems are the comparison's files, for its drop-down.

func (*ViewModel) Complete

func (vm *ViewModel) Complete(line, runeCol int) []editor.Item

Complete returns the completions at a caret, rune column.

func (*ViewModel) CreateFile

func (vm *ViewModel) CreateFile(p, text string) error

CreateFile makes a file with the given text and opens it; a theme, a kinds or a data file is listed in slides.yaml, where the slides command finds it.

func (*ViewModel) CreateFolder

func (vm *ViewModel) CreateFolder(p string) error

CreateFolder makes an empty folder.

func (*ViewModel) CreateFromPanel

func (vm *ViewModel) CreateFromPanel(p string) error

CreateFromPanel makes a file typed in the panel: a theme, kind or data file made in its folder starts from its template.

func (*ViewModel) DefaultPanels

func (vm *ViewModel) DefaultPanels(cssWidth int)

DefaultPanels turns the file panel and the help on or off for a window cssWidth CSS pixels wide, for each the person has not chosen yet; the defaults are not stored, so a wider window later gets its panes back.

func (*ViewModel) DeletePath

func (vm *ViewModel) DeletePath(target string) (err error)

DeletePath deletes a file, or a folder and what it holds, and drops them from slides.yaml, as one step Undo takes back.

func (*ViewModel) DownloadFile

func (vm *ViewModel) DownloadFile(p string)

DownloadFile offers one file of the project.

func (*ViewModel) DraftItems

func (vm *ViewModel) DraftItems() []string

DraftItems are the Draft menu's items (item 0 is its title).

func (*ViewModel) DraftSavedElsewhere

func (vm *ViewModel) DraftSavedElsewhere(name, tab string, saved time.Time)

DraftSavedElsewhere is the host's word that another tab saved a project: this tab says so, and its own next save keeps the other tab's draft as the previous one.

func (*ViewModel) EditorHistoryChanged

func (vm *ViewModel) EditorHistoryChanged(edit bool)

EditorHistoryChanged is the editor's word that its history changed; edit says a new step was recorded, which drops the project's redo steps.

func (*ViewModel) EditorShare

func (vm *ViewModel) EditorShare() int

EditorShare is the editor's part of the room it divides with the preview, in thousandths.

func (*ViewModel) EditorSwitched

func (vm *ViewModel) EditorSwitched(from, to string)

EditorSwitched keeps the history of the file the editor showed and gives it the one of the file it shows now (the scene calls it when it binds another file).

func (*ViewModel) ExportLoading

func (vm *ViewModel) ExportLoading(got, total int64)

ExportLoading reports the export engine's download, the first time.

func (*ViewModel) ExportProgress

func (vm *ViewModel) ExportProgress(done, total int)

ExportProgress reports the slides laid out.

func (*ViewModel) ExportZip

func (vm *ViewModel) ExportZip() ([]byte, error)

ExportZip is the project as a .zip.

func (*ViewModel) Exported

func (vm *ViewModel) Exported(name string, size, slides int, err error)

Exported ends an export: the document was offered to save (name, size, slides), or err says why not.

func (*ViewModel) Exporting

func (vm *ViewModel) Exporting() string

Exporting is the format of the export running ("" for none).

func (*ViewModel) ForgetDrafts

func (vm *ViewModel) ForgetDrafts()

ForgetDrafts removes every stored project of the playground.

func (*ViewModel) GiveUp

func (vm *ViewModel) GiveUp(reason string)

GiveUp abandons the request in flight (the host stopped a worker that took too long) and says so.

func (*ViewModel) JournalDeck

func (vm *ViewModel) JournalDeck()

JournalDeck writes the deck's text and the project's text files to the journal, when they changed since the last write. The host calls it before SaveDraft, on the save tick (of the deck and of the files) and when the page is hidden or left.

func (*ViewModel) Media

func (vm *ViewModel) Media() map[string][]byte

Media are the project's media, path → bytes, for the preview.

func (*ViewModel) Modified

func (vm *ViewModel) Modified(p string) int64

Modified is when a project file was last written, in Unix nanoseconds (0 for none): the page keeps its copy of a medium while it holds.

func (*ViewModel) NewDataFile

func (vm *ViewModel) NewDataFile()

NewDataFile makes a template data file in data/ and opens it.

func (*ViewModel) NewKind

func (vm *ViewModel) NewKind()

NewKind makes a kinds file in kinds/ and opens it.

func (*ViewModel) NewTheme

func (vm *ViewModel) NewTheme(base string)

NewTheme makes a theme built on base in themes/ and opens it.

func (*ViewModel) OpenFile

func (vm *ViewModel) OpenFile(p string)

OpenFile shows a file in the editor: the deck, or a text file; a medium is not text, and the status says how the deck names it.

func (*ViewModel) OpenFolder

func (vm *ViewModel) OpenFolder(files map[string][]byte)

OpenFolder replaces the project with a folder's files.

func (*ViewModel) OpenZip

func (vm *ViewModel) OpenZip(data []byte)

OpenZip replaces the project with a .zip's, after asking; the draft it replaces stays available (Draft ▸ Restore replaced).

func (*ViewModel) PanesResized

func (vm *ViewModel) PanesResized(keys []string)

PanesResized is the splitter telling that the person changed these sizes (a drag, a key, a double-click): keys are the stored keys of the panes it moved. They are kept from now on, the others stay defaults.

func (*ViewModel) PreviewLoaded

func (vm *ViewModel) PreviewLoaded()

PreviewLoaded is the host's word that the preview frame loaded the document last set: an overview waiting for it is applied now.

func (*ViewModel) PreviewPlace

func (vm *ViewModel) PreviewPlace() SlideTop

PreviewPlace is where the overview was last scrolled (I < 0 if never): a document that starts there needs no scroll to the caret.

func (*ViewModel) PreviewScrolled

func (vm *ViewModel) PreviewScrolled(top SlideTop)

PreviewScrolled is the frame's report of where the overview is scrolled; the next document the overview loads starts there.

func (*ViewModel) Previewing

func (vm *ViewModel) Previewing() bool

Previewing reports whether the preview shows a version.

func (*ViewModel) ProjectText

func (vm *ViewModel) ProjectText() (deckPath string, files map[string]string, dirs []string)

ProjectText is what an export reads besides the media: the deck's path, and the project's text files and folders by their paths beside it — the same the preview renders.

func (*ViewModel) RailPage

func (vm *ViewModel) RailPage() string

RailPage is the thumbnail frame's document for the overview the preview holds now: what a frame that missed a patch is reloaded with ("" for none).

func (*ViewModel) Receive

func (vm *ViewModel) Receive(r Response)

Receive takes a render's answer.

func (*ViewModel) RenamePath

func (vm *ViewModel) RenamePath(from, to string) (err error)

RenamePath renames or moves a file or a folder (everything in it), and the paths slides.yaml lists with it, as one step Undo takes back. The deck's text is not changed: a medium moved is named by its new path there by hand.

func (*ViewModel) SaveDraft

func (vm *ViewModel) SaveDraft()

SaveDraft stores the deck when it changed: the host calls it a moment after the typing stops, and when the page is hidden. A draft another tab saved is kept as the previous draft, not overwritten.

func (*ViewModel) SlidesCount

func (vm *ViewModel) SlidesCount() int

SlidesCount is the number of slides the last overview had.

func (*ViewModel) VersionsNote

func (vm *ViewModel) VersionsNote() string

VersionsNote is what the pane says above an empty list ("" when it lists versions).

Directories

Path Synopsis
cmd
build command
Command build compiles the playground into the site, the one step that is neither Hugo's nor the browser's:
Command build compiles the playground into the site, the one step that is neither Hugo's nor the browser's:
bundle command
Command bundle packs a built site (Hugo's public/, the playground's modules in it) into a reproducible .tar.gz for self-hosting: every file under a top directory named after the version, sorted, with fixed owner, modes and time (SOURCE_DATE_EPOCH, as reproducible builds define it), and beside each compressible file its precompressed forms, file.br (brotli, quality 11) and file.gz (gzip -9), which a server such as Caddy (file_server precompressed) sends as they are.
Command bundle packs a built site (Hugo's public/, the playground's modules in it) into a reproducible .tar.gz for self-hosting: every file under a top directory named after the version, sorted, with fixed owner, modes and time (SOURCE_DATE_EPOCH, as reproducible builds define it), and beside each compressible file its precompressed forms, file.br (brotli, quality 11) and file.gz (gzip -9), which a server such as Caddy (file_server precompressed) sends as they are.
playground-export command
Command playground-export is the playground's export worker: PDF, PowerPoint and OpenDocument, made by go-slides' export engine in a Web Worker.
Command playground-export is the playground's export worker: PDF, PowerPoint and OpenDocument, made by go-slides' export engine in a Web Worker.
playground-wasm command
The playground runs in a browser only; natively this command is empty, so that `go vet ./...` and `go test ./...` cover the module on any machine.
The playground runs in a browser only; natively this command is empty, so that `go vet ./...` and `go test ./...` cover the module on any machine.
serve command
Command serve serves a built site on the loopback interface, for the browser test: it picks a free port, writes it to --port-file, and serves --dir until killed.
Command serve serves a built site on the loopback interface, for the browser test: it picks a free port, writes it to --port-file, and serves --dir until killed.
Package coded is an error that says what it is: a stable Code, the Args that fill its sentence, and its English Text.
Package coded is an error that says what it is: a stable Code, the Args that fill its sentence, and its English Text.
Package exportdoc renders a project's deck as the exports take it (a go-slides doc.Doc), as `slides --pdf` renders it: the project's slides.yaml, themes, kinds and data files, the box frame, raw HTML as the allowlist lets it through.
Package exportdoc renders a project's deck as the exports take it (a go-slides doc.Doc), as `slides --pdf` renders it: the project's slides.yaml, themes, kinds and data files, the box frame, raw HTML as the allowlist lets it through.
Package exporter is the playground's PDF, PowerPoint and OpenDocument export: the slides command's own path (cmd/slides exportBytes) with its defaults, run in a Web Worker by a module of its own (cmd/playground-export) that the page downloads the first time an export is asked for.
Package exporter is the playground's PDF, PowerPoint and OpenDocument export: the slides command's own path (cmd/slides exportBytes) with its defaults, run in a Web Worker by a module of its own (cmd/playground-export) that the page downloads the first time an export is asked for.
Package fontsubset cuts a TrueType face down to the glyphs of a set of runes, keeping a character map so that the face can be looked up by rune.
Package fontsubset cuts a TrueType face down to the glyphs of a set of runes, keeping a character map so that the face can be looked up by rune.
Package mediafiles is what the playground's renderer and its export engine (a module of its own, kept small) share about a project's files: which are media, their types, putting them into a page as data: URIs, and the bounds a project's files are checked against before a build reads them.
Package mediafiles is what the playground's renderer and its export engine (a module of its own, kept small) share about a project's files: which are media, their types, putting them into a page as data: URIs, and the bounds a project's files are checked against before a build reads them.
Package store keeps the playground's projects in the browser: a project is a set of files (path → bytes) with a little metadata, the deck being one of them (deck.md), and go-slides reads it through a vfs.FS.
Package store keeps the playground's projects in the browser: a project is a set of files (path → bytes) with a little metadata, the deck being one of them (deck.md), and go-slides reads it through a vfs.FS.
Package versions keeps snapshots of a playground project in the page's store (IndexedDB), with no server: automatic ones as the reader works, and named ones ("Save version…"), the way an editor's local history (VS Code's Timeline) and a document's version history (Google Docs) do.
Package versions keeps snapshots of a playground project in the page's store (IndexedDB), with no server: automatic ones as the reader works, and named ones ("Save version…"), the way an editor's local history (VS Code's Timeline) and a document's version history (Google Docs) do.

Jump to

Keyboard shortcuts

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