diff --git a/src/content/built-in/cli.md b/src/content/built-in/cli.md index 372de73..c0f4dd5 100644 --- a/src/content/built-in/cli.md +++ b/src/content/built-in/cli.md @@ -13,26 +13,50 @@ The entry file is `slides.md` unless you name another one. A first argument that `slide-ng --version` prints the version, `slide-ng --help` lists the commands, and `slide-ng --help` lists that command's flags. +A mistyped command or flag gets the nearest real one: `Unknown command "biuld". Did you mean "build"?`, also for `--verison`, `-P` (for `-p`), `theme ejcet` and `skills instal`. A flag of another command names that command (`build --with-clicks` is a flag of `export`), and `present` says to open `/presenter` of `dev` or of a build. + ## dev Starts the dev server. This is the default command. | Flag | Meaning | | --- | --- | -| `--port`, `-p` | Port. Default 4230. | +| `--port`, `-p` | Port. Default 4230, or the next free one when 4230 is taken. A port given here that is taken stops the server, with a free one to try. | | `--open`, `-o` | Open the deck in a browser. | | `--remote[=password]` | Listen on every address so other devices can open the deck and follow the presenter. Always with a password: the one given, `SLIDE_NG_PASSWORD`, a prompt on a terminal, or a random one that is printed. See [Remote access](../features/remote-access.md). | | `--bind
` | The address to listen on with `--remote`. Default `0.0.0.0`. | | `--log error\|warn\|info\|silent` | How much of the child programs' output to show. Default `info`. `error` and `silent` hide it. | | `--force`, `-f` | Clear the build cache. | | `--theme`, `-t` | A theme name or path, instead of the headmatter's. | +| `--lang ` | Use this language (`tr`, `pt-BR`) instead of the headmatter's `lang`: the interface, the locale of dates and numbers, and ``. See [Interface language](../features/interface-language.md). | `--tunnel` and `--base` are refused with a message. The dev server always runs at `/`. +In a terminal the dev server reads keys, as Slidev's does, and prints a line that lists them: + +| Key | What it does | +| --- | --- | +| `r` | Restart the generator, the companion server and the Angular dev server. A running presenter timer is kept, and `--open` opens no new tab. | +| `o` | Open `http://localhost:/` in the default browser. | +| `e` | Open the entry file in `$VISUAL`, `$EDITOR` (flags allowed, such as `code -w`) or else `code`. Under `npm run` and `npx`, npm's own `EDITOR=vi` counts as unset, so `code` is used when it is installed. | +| `q` | Quit. Ctrl+C works too. | +| `c` | With `--remote`, print a QR code of the private address for each network address. Without it, say how to start with it. | + +- One deck runs one dev server. A second `slide-ng dev` on the same deck stops before it changes anything, whatever its `--port`, and names the running one. When the default port 4230 is taken by something else, the server takes the next free one and says so (`port 4230 is in use, so the dev server uses 4231`). A `--port` that is taken stops it, with a free port to try. Run two decks side by side from their own folders: the second one moves to the next port by itself. +- During the first build the port shows a "Building the slides" page, so the port is taken from the start. When that build fails, the page shows the error until the build works, and an open tab then loads the slides by itself. +- The dev server keeps watching after an editor saves by renaming a new file over the old one (vim, JetBrains, `git checkout`). +- It also serves MCP over HTTP at `/__mcp`, see [MCP server](../features/mcp.md#over-http-from-the-dev-server). + ## build Makes a static site. See [Hosting](../guide/hosting.md). +```sh +slide-ng build a.md b.md --base /talks/ # dist/a/ at /talks/a/, dist/b/ at /talks/b/ +``` + +With several entries each deck is built in turn into `//` and served under `/`, where `` is the file name without `.md`, as in Slidev. Two entries with the same file name, or a missing one, are refused before the first deck is built. `--out` then belongs to the build as a whole: anything else an earlier build left there is removed. + | Flag | Meaning | | --- | --- | | `--out`, `-o` | Output folder. Default `dist`. | @@ -40,21 +64,25 @@ Makes a static site. See [Hosting](../guide/hosting.md). | `--offline` | A folder that opens from disk. Cannot be combined with `--base`. See [Presenting offline](../guide/offline.md). | | `--download` | Also make the PDF and add the download button. Fails when the PDF cannot be made, where `download: true` alone only warns. | | `--without-notes` | Leave the speaker notes out. | +| `--router-mode` | `history`, `hash` or `memory`. Replaces the headmatter's `routerMode` for this build, and the build records it for `export --dist`. With `--offline` only `hash`. See [Slide addresses](../features/routing.md). | | `--executable-path` | The Chromium to use for the PDF, the install icons and the Open Graph picture. | | `--no-sandbox` | Run that Chromium without its sandbox. Needed as root or in a container. Use it for trusted decks only. | | `--allow-local` | Let the page Chromium renders reach loopback and private addresses. They are blocked otherwise. | | `--timeout` | Timeout in milliseconds for each page Chromium renders. | | `--theme`, `-t` | A theme name or path. | +| `--lang ` | Use this language instead of the headmatter's `lang`, for one deck in several languages: `slide-ng build slides.tr.md --out dist/tr`. | ## export Makes a PDF, PNG, PPTX or Markdown file. See [Export](../guide/export.md). +With several entries (`slide-ng export a.md b.md`) each deck is built and exported in turn, to its own file. `--output`, `--dist` and `--url` name one place, so they are refused then, and so are two decks that would get the same file name. To export one deck of a build of several, name its folder: `slide-ng export a.md --dist dist/a`. + | Flag | Meaning | | --- | --- | | `--format` | `pdf` (default), `png`, `pptx` or `md`. | | `--output`, `-o` | Output name or path. | -| `--range 1,4-5` | Which slides. | +| `--range 1,4-5` | Which slides. Text that is not a range, such as `2-x`, is refused. | | `--with-clicks`, `-c` | One page per click step. On by default for `pptx`. | | `--dark` | Dark mode. | | `--timeout` | Milliseconds, 1000 to 600000. Default 30000. | @@ -63,20 +91,21 @@ Makes a PDF, PNG, PPTX or Markdown file. See [Export](../guide/export.md). | `--executable-path` | The Chromium to use. Or set `CHROME_PATH`. | | `--no-sandbox` | Run Chromium without its sandbox, as root or in a container. Or set `SLIDE_NG_NO_SANDBOX=1`. | | `--allow-local` | Let the exported page reach loopback and private addresses. | -| `--per-slide` | Make each slide separately and join them. | +| `--per-slide` | Make each slide separately and join them. Links between slides and the tags for screen readers are lost, and the command says so. Use it only for a deck too big to print in one go. | | `--scale` | Device scale factor, 0.25 to 8. Default 2. | | `--omit-background` | A transparent background. | | `--with-toc` | Put an outline made from slide titles into the PDF. | -| `--dist ` | Export an existing build instead of building first. | +| `--dist ` | Export an existing build instead of building first, with the router mode it was built with. | | `--url
` | Export a running server, for example the dev server. | -| `--base` | The base path the deck is served under, for `--dist`. | +| `--base` | The base path the build at `--dist` was made with. Read from its `index.html` when left out. | | `--theme`, `-t` | A theme name or path. | +| `--lang ` | Use this language instead of the headmatter's `lang`. Refused with `--dist` and `--url`, whose language was fixed when they were built. | -`export` takes one entry file per run. A `--range` that picks no slide fails before the deck is built. +A `--range` that picks no slide fails before the deck is built. ## export-notes -Writes a PDF of the speaker notes. Takes `--output`, `--range` and the Chromium flags of `export`. +Writes a PDF of the speaker notes. Takes `--output`, `--range` and the Chromium flags of `export`, and several entries as `export` does. The date on it is written in the deck's language. ## format @@ -84,7 +113,7 @@ Writes a PDF of the speaker notes. Takes `--output`, `--range` and the Chromium slide-ng format [slides.md ...] ``` -Tidies the files given, `slides.md` by default. No flags. See [Formatting](../features/format.md). +Tidies the files given, `slides.md` by default. No flags. Frontmatter that is not valid YAML stops it with the file, line and slide and what is wrong, and the file is left as it is. See [Formatting](../features/format.md). ## theme eject @@ -116,7 +145,7 @@ Copies the agent skill into `/slide-ng`. `--dir` defaults to `.agents/skill slide-ng create [--force] ``` -Makes a new deck folder. `--force`, `-f` adds the files that are missing to a folder that already has files. See [Your first deck](../getting-started/first-deck.md). +Makes a new deck folder: `slides.md`, `package.json` with the scripts `dev`, `build`, `export` and `format`, a README and `.gitignore`. It refuses a folder with files. `--force`, `-f` adds only the files that are missing, fills in the name only in the files it adds, and keeps an existing `.gitignore`. The `.gitignore` leaves out what `export` and `export-notes` write by default. See [Your first deck](../getting-started/first-deck.md). ## Environment @@ -124,6 +153,7 @@ Makes a new deck folder. `--force`, `-f` adds the files that are missing to a fo | --- | --- | | `CHROME_PATH` | The Chromium that export, `--download`, `pwa` icons and `ogImage: auto` use. | | `SLIDE_NG_THEME` | A theme name or path that overrides the headmatter. `--theme` sets it. | +| `SLIDE_NG_LANG` | The same as `--lang`. | | `SLIDE_NG_NO_SANDBOX` | `1` is the same as `--no-sandbox`. | | `SLIDE_NG_PASSWORD` | The password for `dev --remote`. It keeps the password out of the shell history. | | `SLIDES_FILE` | The entry file, when no argument names one. | diff --git a/src/content/built-in/components.md b/src/content/built-in/components.md index a1c687d..01ba496 100644 --- a/src/content/built-in/components.md +++ b/src/content/built-in/components.md @@ -14,16 +14,18 @@ These tags work in every slide. The generator imports a component only into the | `` | Table of contents from slide titles and levels. Options: `columns`, `listClass`, `listStyle`, `start`, `minDepth`, `maxDepth`, `mode`. See [Table of contents](../features/table-of-contents.md). | | `` | The number of the slide it is on. | | `` | The number of slides. | +| `` | A bar that shows how far through the deck the slide is. Options: `position` (`top`, `bottom`, `inline`), `height`, `color`, `track`, `clicks`, `minutes`. See [Progress bar](../features/progress-bar.md). | +| `` | The title of the slide it is on, as HTML: its inline markdown and `$math$` are drawn. `no="3"` shows the title of slide 3. Raw HTML in a title is shown as text. Slidev's `TitleRenderer`. | | `text` | A link to slide 3, or to a `routeAlias`. It has the prefix because `link` is a built-in HTML tag. The link has the slide's real address, so Ctrl or Cmd + click opens it in a new tab. `title="Details"` is the text when the tag has none. | | `` | Shows `
...
` in dark mode and `
...
` in light mode. | -| `` | Content only where the slide is drawn: `slide`, `presenter`, `print`, `overview`, or `main` (slide or presenter). `[context]="['slide','overview']"` takes several. Content marked `
` shows everywhere else. | +| `` | Content only where the slide is drawn: `slide`, `presenter`, `print`, `overview`, or `main` (slide or presenter). `visible` shows it while the element is on the screen. `[context]="['slide','overview']"` takes several, and in a list `visible` must hold too. Content marked `
` shows everywhere else. In print and export every slide counts as visible. | ## Clicks | Tag | What it does | | --- | --- | -| `` | Gives each child a click, as Slidev's `v-clicks`: each item of a single list, each body row of a single table, otherwise each child element (paragraphs, pictures, several lists). Options: `depth`, `every`, `at`, `hide`. | -| `` | One child per click: `
a
b
c
`. A number lasts until the next one starts. `3-5` is clicks 3 and 4: the end is not included. | +| `` | Gives each child a click, as Slidev's `v-clicks`: each item of a single list, each body row of a single table, otherwise each child element (paragraphs, pictures, several lists). Options: `depth`, `every`, `at`, `hide`, `animation`. | +| `` | One child per click: `
a
b
c
`. A number lasts until the next one starts. `3-5` is clicks 3 and 4: the end is not included. `unmount` takes the children that are not shown out of the page; their components stay alive. `transition="fade"` animates the change, also with `slide-left`, `slide-up`, `fade-out`, a `forward \| backward` pair or a [transition of your own](../guide/animations.md#your-own-transition); a bare `transition` is a fade. | | `` | Leaves two clicks empty. | `slide-ng-click` and `slide-ng-after` are directives, see [Directives](./directives.md). @@ -42,8 +44,8 @@ These tags work in every slide. The generator imports a component only into the | Tag | What it does | | --- | --- | -| `` | An embedded YouTube video. `id` is the video id. There are no start time or playlist options. | -| `` | A video that plays when its slide, or its `slide-ng-click`, comes. `autoplay="once"` plays once. `timestamp` starts at a second. Put `` tags inside. | +| `` | An embedded YouTube video. `id` is the video id, optionally followed by YouTube's embed options, as in Slidev: `?start=60&end=90`, `list`, `loop`, `autoplay`, `mute` and the other documented ones. Other options are dropped. `start` (seconds) and `list` (a playlist id) can also be attributes, and they win over the same option in `id`. A start time, `start` or the `t` of a shared link, may be written YouTube's way, `1m30s` or `1h2m3s`; anything else is dropped with a warning. | +| `` | A video that plays when its slide, or its `slide-ng-click`, comes. `autoplay="once"` plays once. `timestamp` starts at a second. Put `` tags inside. A class or size on it, such as `class="h-60"`, sizes the video, which keeps its shape. Where the video does not play (print, export, overview), `printPoster="/still.png"` replaces `poster`, and `printTimestamp="12"` (seconds, or `last`) picks the frame to show. | | `` | An embedded tweet. Loads Twitter's script, so it needs the network. | | `` | An embedded Bluesky post, needs the network. `uri` can also be the post's web address. | @@ -63,4 +65,4 @@ These tags work in every slide. The generator imports a component only into the ## Not there -Slidev's `PoweredBySlidev` badge and `TitleRenderer` are not available. Slide titles are plain text, except that `$math$` in a title is drawn in ``. Your own components work like these, see [Layouts and components](../customize/layouts-components.md). +Slidev's `PoweredBySlidev` badge is not available. Your own components work like these, see [Layouts and components](../customize/layouts-components.md). diff --git a/src/content/built-in/directives.md b/src/content/built-in/directives.md index e8aa878..862277b 100644 --- a/src/content/built-in/directives.md +++ b/src/content/built-in/directives.md @@ -9,16 +9,20 @@ Directives are attributes you put on an HTML element in a slide. | Directive | What it does | Page | | --- | --- | --- | -| `slide-ng-click` | Shows the element at a click. Takes nothing, a number (`"3"`), a relative step (`"+2"`) or, as a binding, a range `[slide-ng-click]="[2, 4]"`. | [Clicks](../guide/animations.md) | +| `slide-ng-click` | Shows the element at a click. Takes nothing, a number (`"3"`), a relative step (`"+2"`) or a range, `[slide-ng-click]="[2, 4]"` or `slide-ng-click="[2, 4]"`. `slide-ng-click.hide` hides the element at its click instead. | [Clicks](../guide/animations.md) | | `slide-ng-after` | Shows the element at the same click as the one before it, a relative `+0`. | [Clicks](../guide/animations.md) | -| `slide-ng-click-hide` | With `slide-ng-click`, hides the element at the click instead of showing it. | [Clicks](../guide/animations.md) | +| `slide-ng-click-hide` | Hides the element at a click: `slide-ng-click-hide="3"` at click 3. As a bare flag next to `slide-ng-click`, it hides at that click instead of showing. | [Clicks](../guide/animations.md) | | `slide-ng-mark` | Draws a hand-made mark at a click. Modifiers pick type and colour. | [Marks](../features/marks.md) | | `slide-ng-motion` | Moves an element between named states. | [Motion](../features/motion.md) | | `slide-ng-drag` | Makes an element movable and resizable, and saves the position. Takes `[x, y, width, height]`, a name from `dragPos`, or nothing. | [Draggable elements](../features/draggable.md) | ## Two forms of the same value -Angular reads `slide-ng-click="3"` as plain text, and slide.ng turns it into the number 3. For a value that is computed, or an array, use a binding: `[slide-ng-click]="[2, 4]"`. +Angular reads `slide-ng-click="3"` as plain text, and slide.ng turns it into the number 3. For a value that is computed, use a binding: `[slide-ng-click]="[2, 4]"`. A range written as text, `slide-ng-click="[2, 4]"`, is bound for you. + +## Modifiers + +Click animation presets go after the name, as in Slidev: `slide-ng-click.fade.right`, `slide-ng-after.up`, `slide-ng-click-hide.none`. See [Click animations](../features/click-animations.md). `.hide` on `slide-ng-click` or `slide-ng-after` hides the element at its click. A modifier one or two letters away from a known one gets a warning. ## Relative and absolute clicks @@ -30,4 +34,4 @@ A bare `slide-ng-click`, `+n`, `slide-ng-after` and a bare `slide-ng-mark` count ## Not available -Vue's own directives (`v-if`, `v-for`, `v-model`, `v-bind`) are not there. Use Angular's control flow, see [Angular in markdown](../guide/angular-templates.md). Slidev's slide hooks (`onSlideEnter`, `onSlideLeave`) are not supported either. +Vue's own directives (`v-if`, `v-for`, `v-model`, `v-bind`) are not there. Use Angular's control flow, see [Angular in markdown](../guide/angular-templates.md). Slidev's slide hooks are functions for components, see [Layouts and components](../customize/layouts-components.md#slide-hooks). diff --git a/src/content/built-in/headmatter.md b/src/content/built-in/headmatter.md index b0c7199..102d50e 100644 --- a/src/content/built-in/headmatter.md +++ b/src/content/built-in/headmatter.md @@ -16,13 +16,15 @@ The first block of YAML in `slides.md` is the headmatter. It describes the deck. | `title` | The slide title. Otherwise the first `#` heading. Used in the browser tab, the table of contents and the go-to box. | | `level` | Depth in the table of contents. Otherwise the number of `#` in the first heading. | | `hideInToc` | Leave this slide out of ``. | -| `routeAlias` | An extra address: `routeAlias: intro` makes `/intro` open the slide. | +| `routeAlias` | An extra address: `routeAlias: intro` makes `/intro` open the slide. Names of the app's pages, numbers and names with `/`, `.`, `?`, `#` or `\` are refused, see [Slide addresses](../features/routing.md#aliases). | | `hide`, `disabled` | The slide is left out of the deck completely. | -| `clicks` | Fixes the number of clicks, whatever the elements ask for. | -| `clicksStart` | The first click number. Default 0. | +| `clicks` | Fixes the number of clicks, whatever the elements ask for. A value that is not a number, or a negative one, is ignored with a warning. | +| `clicksStart` | The first click number. Default 0. A negative one counts as 0. | +| `clickAnimation` | The click animation preset of the slide's click elements, such as `up` or `fade right`. See [Click animations](../features/click-animations.md). | | `transition` | The transition to the next slide. | | `src` | Take the slides from another file, optionally with a `#range`. | | `zoom` | Scale this slide. Wins over the headmatter. | +| `preload` | `false` leaves the slide's pictures out of [loading ahead](../features/preload-images.md). | | `dragPos` | Named positions for `slide-ng-drag`. It is read per slide, so in the headmatter it covers slide 1 only. | | `image`, `url`, `scale`, `backgroundSize`, `background`, `layoutClass` | Props of some layouts. | @@ -40,8 +42,13 @@ The first block of YAML in `slides.md` is the headmatter. It describes the deck. | `colorSchema` | `auto` (default), `light` or `dark`. Fixed values hide the toggle. A theme that sets `light` or `dark` in its `theme.json` keeps that mode: the deck's own value is not used, and the generator says so. | | `canvasWidth`, `aspectRatio` | The canvas. Defaults 980 and `16/9`. | | `zoom` | Scale every slide. | -| `routerMode` | `history` (default) or `hash`. | +| `routerMode` | `history` (default), `hash` or `memory`. See [Slide addresses](../features/routing.md). | +| `clickAnimation` | The click animation preset of every slide. | +| `progressBar` | A progress bar on every slide: `true`, `top`, `bottom` or an object. See [Progress bar](../features/progress-bar.md). | +| `preloadImages` | `true` (default), `{ ahead: n }` or `false`. See [Loading pictures ahead](../features/preload-images.md). | +| `unocss` | `{ attributify: false }` turns attributify off. See [UnoCSS](../features/unocss.md). | | `fonts` | Web fonts. See [Fonts](../features/fonts.md). | +| `themeConfig` | Settings for the theme, such as `themeConfig: { primary: '#5d8392' }`. Each text or number key becomes the CSS variable `--slidev-theme-`. See [Themes and addons](../customize/themes.md#theme-settings). | | `customElements` | Web component tags the slides may use, such as `[model-viewer, sl-*]`. A name ending in `*` is a prefix, and `true` allows any tag with a dash. See [Layouts and components](../customize/layouts-components.md#web-components). | ### Code and diagrams @@ -52,7 +59,9 @@ The first block of YAML in `slides.md` is the headmatter. It describes the deck. | `codeCopy` | `false` removes the copy button. | | `codeAliases` | Extra language names, `{ mylang: ts }`. | | `shiki` | Shiki options such as `themes: { light, dark }`. | -| `magicMoveDuration`, `magicMoveCopy` | Duration in milliseconds, and the copy button of Magic Move. | +| `magicMoveDuration`, `magicMoveCopy` | Duration in milliseconds, and the copy button of Magic Move. `shiki.magicMove` takes the other animation options, see [Magic Move](../features/magic-move.md). | +| `monaco`, `twoslash` | `true` (default), `false`, `dev` or `build`. Off, `{monaco}` blocks are plain highlighted code and Twoslash does not run. | +| `monacoRunUseStrict` | `false` runs `{monaco-run}` code without `"use strict"`. | | `plantUmlServer` | The PlantUML server. | ### Presenting @@ -65,13 +74,16 @@ The first block of YAML in `slides.md` is the headmatter. It describes the deck. | `presenter` | `false`, or `dev` or `build`, to limit the presenter page. | | `editor` | `false` hides the side editor button. | | `contextMenu` | `true`, `false`, `dev` or `build`. | -| `wakeLock` | `true` starts with the screen kept awake. The viewer can change it in the settings menu. | +| `wakeLock` | `true` (default), `false`, `dev` or `build`. Keeps the screen awake. A choice stored in the browser wins, and the viewer can change it in the settings menu. | +| `notesAutoRuby` | `{ word: reading }` shows a reading above each listed word in the notes. See [Speaker notes](../features/speaker-notes.md#readings-above-words). | +| `mcp` | `false` turns off the dev server's MCP endpoint. See [MCP server](../features/mcp.md). | ### The page, export and build | Key | What it does | | --- | --- | -| `titleTemplate`, `author`, `keywords`, `favicon`, `lang`, `htmlAttrs`, `seoMeta` | The page's ``. See [Page head and SEO](../features/seo-meta.md). | +| `titleTemplate`, `author`, `keywords`, `favicon`, `htmlAttrs`, `seoMeta` | The page's ``. See [Page head and SEO](../features/seo-meta.md). | +| `lang`, `uiLang` | `lang` is ``, the interface language and the locale of the `date`, `number` and `currency` pipes. `ar`, `he`, `fa` and `ur` mirror the page. `uiLang` changes the interface language only. See [Interface language](../features/interface-language.md). | | `download` | A PDF button: `true`, a file name or an address. With `true`, `slide-ng build` makes the PDF. Without a working Chromium it warns and the button stays disabled. | | `exportFilename` | The name of exported files, without the extension. | | `export` | Defaults for `slide-ng export`. See [Export](../guide/export.md). | @@ -81,4 +93,4 @@ The first block of YAML in `slides.md` is the headmatter. It describes the deck. ## Not read -These Slidev keys are accepted in the file but have no effect: `selectable`, `themeConfig`, `preload`, `clickAnimation`, `overviewSnapshots`, `browserExporter`, `notesAutoRuby`, `monacoTypesSource`, and `colorSchema: all`. `mdc: true` and `comark: true` have no effect, and the generator warns that MDC syntax stays text. `highlighter: prism` warns too, because code is always highlighted with Shiki. The `monaco` and `twoslash` switches of Slidev are not read either: a `{monaco}` or `twoslash` code fence is what turns them on. A key a theme brings through `theme.json` is merged under the deck's headmatter, except a fixed `colorSchema`, see [Themes and addons](../customize/themes.md). +These Slidev keys are accepted in the file but have no effect: `selectable`, `overviewSnapshots`, `browserExporter`, `monacoTypesSource`, and `colorSchema: all`. `mdc: true` and `comark: true` have no effect, and the generator warns that MDC syntax stays text. `highlighter: prism` warns too, because code is always highlighted with Shiki. A key a theme brings through `theme.json` is merged under the deck's headmatter, except a fixed `colorSchema`, see [Themes and addons](../customize/themes.md). diff --git a/src/content/built-in/layouts.md b/src/content/built-in/layouts.md index ff7f612..c59fc59 100644 --- a/src/content/built-in/layouts.md +++ b/src/content/built-in/layouts.md @@ -30,7 +30,7 @@ Set a layout in a slide's frontmatter with `layout: `. The names and props `image` is an address (`/photo.png` is served from the `public/` folder) or a colour for the background. `backgroundSize` defaults to `cover`. -An unknown layout name prints a warning that lists the names and uses `default`. Themes and decks can add layouts or replace these, see [Themes and addons](../customize/themes.md). +An unknown layout name prints a warning that names the nearest layout (`image-rigth`: "Did you mean "image-right"?"), lists the names and uses `default`. Themes and decks can add layouts or replace these, see [Themes and addons](../customize/themes.md). ## Pictures diff --git a/src/content/built-in/shortcuts.md b/src/content/built-in/shortcuts.md index 7b27dc9..7ae664f 100644 --- a/src/content/built-in/shortcuts.md +++ b/src/content/built-in/shortcuts.md @@ -14,20 +14,25 @@ description: The keyboard and mouse controls of a deck, and how to change them. | `f` | Fullscreen | | `o` or backtick | Slide overview. Arrows move, Enter opens, Escape closes. | | `d` | Dark or light mode | -| `g` | Go to a slide: type a number or part of a title, then Enter | +| `g` | Go to a slide: type a number or part of a title, in any case and with or without accents, then Enter | | Escape | Close the overview, dialogs and menus | +| `u` | Presenter page: show or hide the [click preview](../guide/presenter-mode.md#click-preview) | Shortcuts are off while you type in a text field or an editor, and while the drawing toolbar is open. Escape leaves a text field (not an editor), so the keys work again. After a click on a checkbox or a button, the arrow keys still move the slides. While the info dialog or a modal `` of a slide is open, only Escape works. When a `slide-ng-drag` element is selected, the arrow keys nudge it instead of moving through the slides. ## Mouse -The bar at the bottom left has buttons for fullscreen, previous, next, overview, dark mode, drawing, presenter mode, info, settings and a counter. It shows when the mouse is there. +The bar at the bottom left has buttons for fullscreen, previous, next, overview, dark mode, drawing, presenter mode, info, sync mode, settings and a counter. It shows when the mouse is there. On the presenter page it also has the layout button and the [click preview](../guide/presenter-mode.md#click-preview). A `layers/custom-nav-controls.ts` adds buttons of your own at its end, see [Global layers](../features/global-layers.md). The fullscreen button is left out where the page may not go fullscreen: in an iframe without `allow="fullscreen"`, and on an iPhone. `f` does nothing there. -The settings button (More Options) opens the same menu as in Slidev: Invert, then sliders with a number box for Brightness, Contrast, Saturation, Sepia and Hue Rotate (double click a slider to reset it; a dot on the button shows that a filter is active), then Cursor Style (Cursor or Laser), Slide Scale (Fit or 1:1), Wake Lock (where the browser has it) and Hide Idle Cursor. The filters change how the slides look on your screen and are remembered. With Laser selected, a red dot follows your pointer on the slide, in the normal view and in the presenter view (where the audience sees it too). A switch for the dark mode (when neither the deck nor its theme fixes it), the camera and microphone choices and "Download as PDF" are listed below a divider. +The settings button (More Options) opens the same menu as in Slidev: Invert, then sliders with a number box for Brightness, Contrast, Saturation, Sepia and Hue Rotate (double click a slider to reset it; a dot on the button shows that a filter is active), then Cursor Style (Cursor or Laser), Slide Scale (Fit or 1:1), Wake Lock and Hide Idle Cursor. Slide Scale and Hide Idle Cursor are not on the presenter page. Wake Lock keeps the screen from going to sleep, where the browser has it. It is on by default, as in Slidev; a choice stored in the browser wins, and `wakeLock: false` in the headmatter turns it off and removes the entry (`dev` or `build` keeps it for that mode only). The filters change how the slides look on your screen and are remembered. With Laser selected, a red dot follows your pointer on the slide, in the normal view and in the presenter view (where the audience sees it too). A switch for the dark mode (when neither the deck nor its theme fixes it), the camera and microphone choices and "Download as PDF" are listed below a divider. -Right click opens a menu with the same actions. Hold Shift while right clicking for the browser's own menu. Click the dark area beside the slide: the right half goes to the next click, the left half to the previous one. On a touch screen, swiping left or right moves through clicks, and swiping up or down moves between slides. +Right click opens a menu with the same actions. Hold Shift while right clicking for the browser's own menu. From the keyboard, Shift+F10 or the menu key opens it with the focus on its first item; the arrow keys, Home, End and Tab move between the items, Enter or Space runs one, and Escape closes it and gives the focus back. While the menu is open the deck's keys do nothing. Click the dark area beside the slide: the right half goes to the next click, the left half to the previous one. + +## Touch + +On the audience page, swiping left or right moves through clicks, and swiping up or down moves between slides. A swipe that starts on a `slide-ng-drag` element or one of its handles moves the element instead, and there are no swipes while drawing. The presenter page and `/notes` have no swipes. The bar has no hover on a touch screen: the first tap on the hidden bar only shows it, the next tap presses a button. On a screen taller than the slide, such as a phone held upright, the bar is always shown. ## Change the keys @@ -43,7 +48,7 @@ export default (actions: ShortcutActions, list: Shortcut[]): Shortcut[] => [ ]; ``` -`key` is a `KeyboardEvent.key` name, with `shift+` in front if needed, and `space` for the space bar. When two shortcuts have the same key, the one later in the list wins, so appending `{ key: 'k' }` replaces a built-in `k`. A deck's `setup/shortcuts.ts` runs after the shortcuts of its theme and addons, so it gets their list and has the last word. The names of the default shortcuts are `next_space`, `prev_space`, `next_right`, `prev_left`, `next_page_key`, `prev_page_key`, `next_down`, `prev_up`, `next_shift`, `prev_shift`, `toggle_dark`, `toggle_overview`, `toggle_overview_backtick`, `hide_overview`, `goto`, `fullscreen`, and the overview moves `right_overview`, `left_overview`, `up_overview`, `down_overview` and `goto_from_overview`. +`key` is a `KeyboardEvent.key` name, with `shift+` in front if needed, and `space` for the space bar. When two shortcuts have the same key, the one later in the list wins, so appending `{ key: 'k' }` replaces a built-in `k`. A deck's `setup/shortcuts.ts` runs after the shortcuts of its theme and addons, so it gets their list and has the last word. The names of the default shortcuts are `next_space`, `prev_space`, `next_right`, `prev_left`, `next_page_key`, `prev_page_key`, `next_down`, `prev_up`, `next_shift`, `prev_shift`, `toggle_dark`, `toggle_overview`, `toggle_overview_backtick`, `hide_overview`, `goto`, `fullscreen`, `toggle_click_preview` (presenter page only), and the overview moves `right_overview`, `left_overview`, `up_overview`, `down_overview` and `goto_from_overview`. ## Color mode diff --git a/src/content/customize/directory-structure.md b/src/content/customize/directory-structure.md index 60c0dc9..e2bda9c 100644 --- a/src/content/customize/directory-structure.md +++ b/src/content/customize/directory-structure.md @@ -12,12 +12,14 @@ my-deck/ slides.md layouts/ your own layouts components/ your own components, usable as tags in slides - layers/ global-top, global-bottom, slide-top, slide-bottom + layers/ global-top, global-bottom, slide-top, slide-bottom, custom-nav-controls styles/index.css CSS added after the app's styles public/ files served from the site root (/logo.png) setup/ setup hooks: shiki.ts, katex.ts, mermaid.ts, shortcuts.ts, ... + locales/ translations of the interface: tr.json, de.json, ... uno.config.mjs UnoCSS settings theme.json inputs and slots of your own layouts + index.html extra tags for the page's head and body snippets/ anything you import with <<< ``` @@ -42,7 +44,9 @@ So a deck can put a `layouts/cover.ts` next to `slides.md` and it beats the them | `styles/index.css` | CSS, added after the app's styles. | | `public/` | Files served from the site root. The later root wins on the same name. | | `setup/*.ts` | [Setup hooks](./setup-hooks.md). | +| `locales/.json` | Translations of the interface, flat `"key": "text"` pairs. Merged key by key, the later root wins. See [Interface language](../features/interface-language.md). | | `uno.config.mjs` | UnoCSS settings, default export. `presets`, `rules`, `safelist` and `variants` are added to the app's. `shortcuts` and `theme` are merged key by key. | +| `index.html` | Tags added to the page's `` and ``, such as analytics. See [Page head and SEO](../features/seo-meta.md#your-own-index-html). | | `theme.json` | Name, default headmatter and layout inputs and slots. A deck's own folder can have one too, for its own layouts. See [Writing a theme](./writing-a-theme.md). | ## Importing from the app @@ -59,3 +63,5 @@ Angular and other packages are imported as usual. Symbolic links in these folder ## Pictures next to the deck A picture in `public/` is written with a leading slash, `/photo.png`. A relative address that `public/` does not have, but that names a file in the deck's folder, such as `![](./images/chart.png)` or `` from a slide in `pages/`, is copied into the build too and served from `/_local/`. That works in a slide's markup only. A file outside the deck's folder is not copied, and neither are pictures named in components, in CSS `url()` or in the frontmatter: put those in `public/`. + +The generator warns about a picture with a leading `/` that `public/` does not have, about `/public/x.png` (write `/x.png`), and about a relative picture that is neither next to the markdown file nor in `public/`. `public/` must be a real folder: a symbolic link there is not followed, and the generator warns. diff --git a/src/content/customize/layouts-components.md b/src/content/customize/layouts-components.md index 6d1fd72..dde0552 100644 --- a/src/content/customize/layouts-components.md +++ b/src/content/customize/layouts-components.md @@ -62,6 +62,38 @@ A slide is drawn in every window that shows it: the presenter page and each audi In the PDF and in the overview every slide is drawn at once, so `Nav.no()` is the slide being presented, not the slide being drawn. Inject `SLIDE_NO` to know which slide a component is in. +## Slide hooks + +Slidev's slide hooks are in `slide-ng/lib/slide-hooks`. Call them in an injection context, which is a field or the constructor of a component: + +- `onSlideEnter((to, from) => ...)` runs once the component is drawn while its slide is on screen, and each time the deck comes back to the slide. +- `onSlideLeave((to, from) => ...)` runs when another slide is shown after this one. +- `isSlideActive()` is a signal, true while the slide is on screen. It is Slidev's `useIsSlideActive`. + +```ts +// components/enter-count.ts +import { Component, signal } from '@angular/core'; +import { isSlideActive, onSlideEnter } from 'slide-ng/lib/slide-hooks'; + +@Component({ + selector: 'enter-count', + template: '{{ active() ? "here" : "away" }}, entered {{ n() }} times', +}) +export class EnterCount { + active = isSlideActive(); + n = signal(0); + constructor() { + onSlideEnter(() => this.n.update((v) => v + 1)); + } +} +``` + +- `to` and `from` are slide numbers, from 1. `from` is `undefined` when no other slide was shown before. +- The hooks run on the slide and presenter pages and in print (`export` and the PDF), not in thumbnails or the presenter's next-step pane. +- In print every page is its slide on screen, as in Slidev: `onSlideEnter` runs once per page, `onSlideLeave` never runs, and `isSlideActive()` is true. Something a hook animates is captured as it is when the page is taken, so give `export` a `--wait`. +- The deck removes a slide it leaves, so a component that should remember something across visits keeps it outside the component. +- A hook that throws is reported in the browser console. The other hooks of the slide still run. + ## A layout A layout is a standalone component that puts the slide's content in ``. Put it in `layouts/`: diff --git a/src/content/customize/setup-hooks.md b/src/content/customize/setup-hooks.md index 9fd17c9..3ea429d 100644 --- a/src/content/customize/setup-hooks.md +++ b/src/content/customize/setup-hooks.md @@ -12,7 +12,7 @@ The folder can be in the deck's own folder, in a theme and in an addon. Every ro There are two kinds of hooks: - Node hooks run while the slides are generated: `shiki`, `katex`, `transformers` and `preparser`. They are bundled with esbuild first, so they can be written in TypeScript and import their own files and packages. An error names the root and the file. -- Browser hooks become part of the app: `main`, `root`, `shortcuts`, `context-menu`, `mermaid`, `monaco` and `code-runners`. They must be `.ts` files. Angular compiles them with the rest of the app, so `import type { ... } from 'slide-ng/...'` and `@angular/core` work. +- Browser hooks become part of the app: `main`, `root`, `routes`, `shortcuts`, `context-menu`, `mermaid`, `mermaid-renderer`, `monaco` and `code-runners`. They must be `.ts` files. Angular compiles them with the rest of the app, so `import type { ... } from 'slide-ng/...'` and `@angular/core` work. | File | Runs | The function receives | Returns | | --- | --- | --- | --- | @@ -21,12 +21,14 @@ There are two kinds of hooks: | `setup/transformers.ts` | generating | nothing | `{ pre, codeblocks }` | | `setup/preparser.ts` | generating | `{ filepath, headmatter, mode }` | a list of extensions | | `setup/mermaid.ts` | browser | nothing | Mermaid config | +| `setup/mermaid-renderer.ts` | browser | nothing | a function that draws a diagram, or nothing | | `setup/monaco.ts` | browser | `monaco` | editor options, or nothing | | `setup/code-runners.ts` | browser | nothing | `{ language: run }` | | `setup/shortcuts.ts` | browser | `(actions, list)` | the list of shortcuts | | `setup/context-menu.ts` | browser | `items` | the list of entries | | `setup/main.ts` | browser, at start | nothing | Angular providers | | `setup/root.ts` | browser, at start | nothing | nothing | +| `setup/routes.ts` | browser, at start | the app's routes | the list of routes | | `setup/providers.ts` | browser, at start | the older way | an array of providers is the default export | ## Shiki @@ -97,6 +99,27 @@ export default () => [ export default () => ({ theme: 'base', themeVariables: { primaryColor: '#ff0000' } }); ``` +## Mermaid renderer + +`setup/mermaid-renderer.ts` returns a function that draws a diagram with another library, as in Slidev. The function gets the diagram's code and its options (the theme, the config of `setup/mermaid.ts` and the fence's own options, merged in that order) and returns the SVG as a string, or a promise of it. Mermaid itself is then not loaded. `scale` is still applied, and the export waits for the drawing. + +```ts +// setup/mermaid-renderer.ts: Mermaid's hand-drawn look for every diagram +let counter = 0; + +export default () => async (code: string, options: Record) => { + const { default: mermaid } = await import('mermaid'); + mermaid.initialize({ startOnLoad: false, ...options, look: 'handDrawn' }); + return (await mermaid.render(`hand-drawn-${++counter}`, code)).svg; +}; +``` + +- The setup function may be async, and it may return nothing to keep Mermaid. +- When several roots have the file, the last one that returns a function wins. +- A setup that throws is skipped with a warning in the console, and the next root or Mermaid draws instead. +- A renderer that throws, or that resolves to something other than a string, shows the red error box in place of the diagram. +- A renderer that uses another library imports it the same way. Install the library in the deck first. + ## Monaco `setup/monaco.ts` runs once Monaco is loaded, only for decks that have an editor. It gets the `monaco` module, so it can add themes or languages, and the object it returns is merged into the options of every editor. It may be async. TypeScript settings are `monaco.typescript.typescriptDefaults`: the old `monaco.languages.typescript` is undefined in this Monaco version. An editor's model is `file:///model-N.mts`, so an extra library for `import './context'` goes at `file:///context.ts`. A hook that throws is reported, and the editors still work. @@ -135,7 +158,7 @@ See [Shortcuts](../built-in/shortcuts.md). The roots are chained: each gets the ## Context menu -`setup/context-menu.ts` gets the entries of the right-click menu and returns the list to show. An entry is `{ icon, label, run, disabled? }`, and `icon` is an icon class such as `i-carbon:star`. The roots are chained. +`setup/context-menu.ts` gets the entries of the right-click menu and returns the list to show. An entry is `{ icon, label, run, disabled? }`, and `icon` is an icon class such as `i-carbon:star`. The roots are chained. The built-in labels are in the deck's [interface language](../features/interface-language.md), so pick an entry by its `icon`, not its label. ```ts // setup/context-menu.ts @@ -173,9 +196,33 @@ export default () => { }; ``` +## Routes + +`setup/routes.ts` gets the app's Angular routes and returns the list to use, so a deck can add pages of its own, as with Slidev's `setup/routes.ts`. A page is an Angular component, in the file itself or imported from the root, such as `import { Gallery } from '../components/gallery'`. The roots are chained: each gets the list the one before returned. + +```ts +// setup/routes.ts +import { Component } from '@angular/core'; +import type { Routes } from '@angular/router'; + +@Component({ selector: 'deck-about', template: '

About this talk

' }) +class About {} + +export default (routes: Routes): Routes => [...routes, { path: 'about', component: About }]; +``` + +Now `/about` opens the page. + +- Angular takes the first route that matches. So that a page you append is reached, the app's routes that match any address (the slide and the catch-all) are moved to the end afterwards. Routes the file adds stay where it put them. +- A page comes before the slides, so a slide whose `routeAlias` has the page's path is not reached at that address. The build cannot see these routes, so it cannot warn: give pages and aliases different names. +- The page is shown on its own, without the slide frame, the bar or the keys of the slides. With `dev --remote` it is not behind the password; add Angular's `canActivate` if it should be. +- The services of a deck page are not there: `inject(Nav)` fails, and `inject(Nav, { optional: true })` gives `null`. A page can inject Angular's `Router`, `Dark` from `slide-ng/lib/dark`, and the providers of `setup/main.ts`. +- A file that throws, or returns something other than a list, is skipped with a message in the console, and the slides still open. + ## Differences from Slidev - Slidev's setup files use `define...Setup` helpers from `@slidev/types`. Here a plain function is enough, and types come from `slide-ng/...`. - `setup/main.ts` returns Angular providers, where Slidev's gets the Vue app. `setup/root.ts` has no component to work on. - Shortcuts are `{ name, key, run, when }`, where Slidev's are `{ key, fn, autoRepeat }`. Context menu entries are `{ icon, label, run, disabled }`. -- Not available: `setup/vite.config.ts`, `setup/vue.ts`, `setup/routes.ts`, a run context for `setup/code-runners.ts` (it gets the text only), Monaco's `monacoTypesSource`, and markdown-it plugins in `setup/transformers.ts`. The `codeblocks` function is the way to render a block yourself. +- `setup/routes.ts` gets Angular routes, where Slidev's gets Vue router records, and the slide routes stay last instead of being ranked by the router. +- Not available: `setup/vite.config.ts`, `setup/vue.ts`, a run context for `setup/code-runners.ts` (it gets the text only), Monaco's `monacoTypesSource`, and markdown-it plugins in `setup/transformers.ts`. The `codeblocks` function is the way to render a block yourself. diff --git a/src/content/customize/themes.md b/src/content/customize/themes.md index 83ffe13..e8151e8 100644 --- a/src/content/customize/themes.md +++ b/src/content/customize/themes.md @@ -48,6 +48,36 @@ A theme can bring layouts, components, layers, CSS, public files, UnoCSS setting The one exception is `colorSchema`. A theme that sets `light` or `dark` has colours for that mode only, so every deck that uses it keeps that mode, as in Slidev. The toggle is gone, and when the deck's headmatter asks for another mode the generator says that it is not used. Of the official themes, `bricks` is light only and `shibainu` dark only. +## Theme settings + +`themeConfig` holds the values a theme offers to change, as in Slidev. Each key with a text or number value becomes the CSS variable `--slidev-theme-` on ``, with the key as written: `primary` is `--slidev-theme-primary`, `accentDark` is `--slidev-theme-accentDark`. It is there in the page, the presenter, the PDF and offline builds. + +```md +--- +themeConfig: + primary: '#5d8392' +--- +``` + +`primary` recolours the built-in accent: `text-primary`, `bg-primary`, `border-primary`, the progress bar and the controls. Other keys do what the theme makes of them. + +- The theme's `theme.json` can give defaults. The deck's keys win, key by key, and so do an addon's. +- A key must be letters, digits, `-` and `_`. A value that could leave the CSS declaration (`;`, `{`, `}`, `<`, `>`, `\`, a comment, a line break, quotes or brackets that do not close) is skipped with a warning. So is a `url(...)` or `image-set(...)`, because the generated CSS would look for the file in the wrong folder. Name pictures in the theme's or the deck's CSS. +- A number is written without a unit, as in Slidev: `radius: 6` is `--slidev-theme-radius: 6`, which CSS does not take as a length. Write `radius: 6px`. +- There is one value for both colour modes, as in Slidev. +- A slide reads the whole map as `themeConfig`, for example `{{ themeConfig['primary'] }}`. The brackets are needed because the map has no fixed keys. Lists and objects are kept there, but they are not CSS variables. +- A theme's component reads it with `inject(THEME_CONFIG)` from `slide-ng/lib/theme-config`. See [Writing a theme](./writing-a-theme.md#5-default-settings). + +## Translating the interface + +A theme or an addon can translate slide.ng's own interface (the bar, the menus, the presenter) with a `locales/` folder. `locales/tr.json`, or `pt.json` and `pt-BR.json`, holds flat `"key": "text"` pairs with their `{name}` placeholders kept: + +```json +{ "nav.next": "Sonraki slayta git", "presenter.noNotes": "{no}. slayt için not yok." } +``` + +The deck's `lang` or `uiLang` picks the file. The built-in translations come first, then the theme, the addons and the deck, key by key, so a deck can change one text of its theme. A theme's own components can show the same texts with the `t` pipe: import `T` from `slide-ng/i18n/t`, then write `{{ 'nav.next' | t }}`. See [Interface language](../features/interface-language.md). + ## Eject a theme ```sh @@ -63,8 +93,6 @@ The built-in styles still load before the ejected `styles/index.css`. Your copy - There is no versioning or compatibility check between a theme and the app. - A layout's inputs are not read from the component, because Angular decorators are not parsed. They come from the built-in layout or from `theme.json`. - Class names in a theme are found by UnoCSS in `.ts` files of the roots and in the deck's markdown, not in CSS files. -- `themeConfig` is not read. -- `@apply` in a theme's CSS does nothing, because there is no UnoCSS directive transformer. The generator warns. Write the CSS out, or put the classes on the elements. - A theme package whose `files` leaves its folders out installs empty. The generator warns, instead of failing later with "is not a known element". Next: [Writing a theme](./writing-a-theme.md). diff --git a/src/content/customize/writing-a-theme.md b/src/content/customize/writing-a-theme.md index 6277242..0b9a417 100644 --- a/src/content/customize/writing-a-theme.md +++ b/src/content/customize/writing-a-theme.md @@ -15,9 +15,10 @@ my-theme/ layouts/ components/ styles/index.css - layers/ optional: global-top.ts, global-bottom.ts, slide-top.ts, slide-bottom.ts + layers/ optional: global-top.ts, global-bottom.ts, slide-top.ts, slide-bottom.ts, custom-nav-controls.ts setup/ optional: setup hooks public/ optional: pictures and fonts, served from / + locales/ optional: translations of the interface, tr.json, de.json, ... uno.config.mjs optional: UnoCSS shortcuts and rules ``` @@ -66,10 +67,12 @@ That is for slides only. A layout, a layer or another component imports the comp export default { shortcuts: { 'my-muted': 'op60 text-sm' } }; ``` -`@apply` does nothing in this CSS, and the generator warns. Write the CSS out, or use the classes in the templates. +UnoCSS's directives work in this CSS, as in Slidev: `@apply p-4 text-red-500;`, `--uno: p-4;`, `@screen md { ... }` and `theme('colors.red.500')` are written out as CSS while the deck is generated. A utility UnoCSS does not know gives a warning. Variant groups such as `hover:(a b)` are not expanded in the `.ts` files of a theme: write the classes out there. Dark mode is the class `dark` on ``: write `html.dark .my-quote { ... }` for the dark palette. The core styles dim the paragraph right after a heading (`h1 + p`, a subtitle). Set its `opacity` in your CSS if your theme should not. +Write sides with logical properties (`border-inline-start`, `padding-inline-start`, `ms-4`, `ps-4`) rather than `left` and `right`, so the theme mirrors in a right-to-left deck (`lang: ar`, `he`, `fa` or `ur`). The core blockquote line is a `border-inline-start`: a theme that sets `border-left` on a blockquote shows both lines in such a deck. + ## 5. Default settings `theme.json` can set headmatter defaults and describe layouts: @@ -86,9 +89,23 @@ Dark mode is the class `dark` on ``: write `html.dark .my-quote { ... }` f - A `colorSchema` of `light` or `dark` says the theme has colours for that mode only: every deck that uses it keeps that mode, and the generator warns when a deck asks for another. Leave it out when the theme styles both. - `layouts.`: `props` are the frontmatter keys that become inputs, `slots` are the names accepted after `::name::`, and `classProp: true` sends the slide's `class` to the input `contentClass` instead of the tag. +Settings a deck may change, such as the accent colour, go in `themeConfig`, as in Slidev. Each one is the CSS variable `--slidev-theme-` on ``, and the deck's `themeConfig: { accent: '#22c55e' }` replaces your default: + +```json +{ "headmatter": { "themeConfig": { "accent": "#f59e0b" } } } +``` + +```css +.my-quote { + border-left: 4px solid var(--slidev-theme-accent); +} +``` + +The variables are written after the roots' style sheets, so they win over a value you set on `:root` or `html.dark`. Use them in rules on the elements, or alias them on `body` or below: an alias on `:root` is worked out before the variable exists there. `primary` is the colour of the built-in accent (`text-primary`, the progress bar). In a component, `inject(THEME_CONFIG)` from `slide-ng/lib/theme-config` gives the whole map, lists and objects too. There is one value for both colour modes, so a theme that wants another dark colour can offer a second key, such as `primaryDark`, and use it under `html.dark`. The rules for values are in [Themes and addons](./themes.md#theme-settings). + Keys other than `name`, `headmatter` and `layouts` are not read, and the generator says so: settings such as `colorSchema` and `fonts` go under `headmatter`. A value of the wrong type is reported and ignored. A deck's own folder can have a `theme.json` too, to describe the inputs and slots of its own layouts. -Pictures go in `public/`. +Pictures go in `public/`. Translations of slide.ng's interface go in `locales/.json`, see [Themes and addons](./themes.md#translating-the-interface). ## A footer or page number @@ -114,7 +131,7 @@ A theme is a folder, so publish it as an npm package with these folders at the t "version": "1.0.0", "license": "MIT", "keywords": ["slide-ng-theme", "slide-ng"], - "files": ["layouts", "components", "layers", "styles", "public", "setup", "theme.json", "uno.config.mjs"], + "files": ["layouts", "components", "layers", "styles", "public", "setup", "locales", "theme.json", "uno.config.mjs"], "peerDependencies": { "@slide-ng/core": "*" } } ``` diff --git a/src/content/features/click-animations.md b/src/content/features/click-animations.md new file mode 100644 index 0000000..9aad343 --- /dev/null +++ b/src/content/features/click-animations.md @@ -0,0 +1,53 @@ +--- +title: Click animations +description: Choose how a part comes in at its click, with Slidev's presets or your own CSS. +--- + +# Click animations + +A part that waits for its click is hidden. A preset changes how it looks while hidden, and so how it comes in, as Slidev's click animation presets do. + +```md +--- +clickAnimation: up +--- + +
Uses the slide's preset
+
Scales up from 0.9
+
Dimmed and 20px to the left until click 3
+
No animation
+``` + +`clickAnimation` in the headmatter sets the preset of every slide, also through `defaults`. In a slide's frontmatter it sets that slide's. A modifier on the attribute wins over both, and several can be combined. The presets work on `slide-ng-click`, `slide-ng-after` and `slide-ng-click-hide`, and `` gives one to every item. + +| Preset | While hidden | +| --- | --- | +| `fade` | At 0.5 opacity instead of invisible. | +| `fade-in` | Invisible, and it fades in over 0.3s. | +| `up`, `down`, `left`, `right` | 20px away, so it moves 20px up, down, left or right as it comes in. | +| `scale` | Scaled to 0.9. | +| `none` | No transition: it shows at once. | + +A part that waits for its click is inert, also with `fade`, where it is dimmed but visible: screen readers skip it, and its links and buttons take no focus or clicks. + +## A preset of your own + +The element gets the class `slidev-vclick-anim-`, so a preset of your own is CSS in `styles/index.css`: + +```css +.slidev-vclick-anim-pop.slidev-vclick-hidden { + transform: scale(0.96) translateY(8px); +} +``` + +```md +
Pops in
+``` + +A name one or two letters away from a built-in preset, such as `slide-ng-click.fadee`, or from the `hide` modifier, gets a warning that names the one meant, unless the deck's styles define it. + +## Limits + +- In a PDF or PNG export nothing moves: each page shows the state of its click at once. +- When the system asks for less motion, the parts change at once. +- The presenter's [click preview](../guide/presenter-mode.md#click-preview) draws every part at its place, at the ghost opacity, whatever its preset. diff --git a/src/content/features/code-groups.md b/src/content/features/code-groups.md index 4a04a49..fabc01d 100644 --- a/src/content/features/code-groups.md +++ b/src/content/features/code-groups.md @@ -21,4 +21,18 @@ yarn add @slide-ng/core :: ````` -The `~icon~` title syntax and the automatic file icons of Slidev are not drawn. +## Icons in titles + +An icon goes in front of a code block, code group tab or Magic Move title, as in Slidev. Write it in the title as `~i-set:name~`: + +`````md +```sh [npm ~i-carbon:terminal~] +npm i @slide-ng/core +``` +````` + +Its `@iconify-json` set must be installed, and a name the installed set does not have gets a warning that names the slide. With `@iconify-json/vscode-icons` installed, Slidev's built-in icons are matched from the title: `npm`, `pnpm`, `vue`, `package.json`, `.ts`, `.svg` and more, the longest name first. + +- A built-in name has to be a whole word of the title, a file's extension (`main.ts`) or the start of a file name (`eslint.config.js`). Slidev looks anywhere in the title, so there `results.tsv` gets a TypeScript icon; here it does not. +- Every `~...~` in a title is taken out of the text shown, and the first one is the icon. +- A `<<<` snippet takes the title after the language, with spaces and an icon: `<<< ./a.py python [a.py ~i-carbon:code~]`. See [Import snippets](./import-snippets.md). diff --git a/src/content/features/draggable.md b/src/content/features/draggable.md index efb133f..1ab8236 100644 --- a/src/content/features/draggable.md +++ b/src/content/features/draggable.md @@ -24,7 +24,7 @@ dragPos: --- ``` -Click an element to select it. Corner handles resize it (Shift keeps the shape), the round handle above rotates it (Shift snaps to 15 degrees), and the arrow keys move it by 1 pixel (Shift: 10). +Click an element to select it. Corner handles resize it (Shift keeps the shape), the round handle above rotates it (Shift snaps to 15 degrees), and the arrow keys move it by 1 pixel (Shift: 10). On a touch screen, a finger drag that starts on the element or a handle moves it and does not swipe to another slide. `slide-ng-drag-arrow` takes `pos` as `x,y,width,height`, as in Slidev: it goes from (x, y) to (x + width, y + height), and the sizes may be negative. diff --git a/src/content/features/format.md b/src/content/features/format.md index c823f19..06b136b 100644 --- a/src/content/features/format.md +++ b/src/content/features/format.md @@ -15,4 +15,4 @@ Rewrites the file with the same parser that reads it. Slide by slide it: - prints the frontmatter again as clean YAML, keeping comments - puts the speaker note in an HTML comment at the end -Files that `src:` imports are not touched. Name them as arguments to format them too. A file that is already tidy is reported as such. The [VS Code extension](https://github.com/slide-ng/vscode-extension) can run it from the editor (first version, not released). There is no Prettier plugin. +Frontmatter that is not valid YAML stops it with the file, line and slide, and what is wrong in plain words, and the file is left as it is. A frontmatter that lost its closing `---`, so that a heading became a YAML comment, gets a warning, and the line is kept. Files that `src:` imports are not touched. Name them as arguments to format them too. A file that is already tidy is reported as such. The [VS Code extension](https://github.com/slide-ng/vscode-extension) can run it from the editor (first version, not released). There is no Prettier plugin. diff --git a/src/content/features/global-layers.md b/src/content/features/global-layers.md index 02348cc..37d81e5 100644 --- a/src/content/features/global-layers.md +++ b/src/content/features/global-layers.md @@ -13,6 +13,7 @@ A layer is a component that is drawn on top of or below slides. Add a file named | `layers/global-bottom.ts` | Once, under all slides. | | `layers/slide-top.ts` | Inside every slide, on top. | | `layers/slide-bottom.ts` | Inside every slide, below. | +| `layers/custom-nav-controls.ts` | At the end of the navigation bar, after the slide counter, on the deck and on the presenter page. | Each file default-exports a standalone component: @@ -31,3 +32,27 @@ export default class MyFooter { ``` Layers let clicks through. Give your own elements `pointer-events: auto` if they need the mouse. Inject `SLIDE_NO` to know which slide a layer is drawn on. That is right in the PDF and in the overview too, where every slide is drawn at once. `Nav.no()` stays the presented slide there, so a footer that reads it prints the same number on every page. + +## Buttons in the bar + +`layers/custom-nav-controls.ts` adds its content to the end of the navigation bar, as Slidev's fifth global layer does. For the other layers the deck's file replaces a theme's or an addon's. This one is drawn for every root: the theme's first, then each addon's, then the deck's. + +```ts +// layers/custom-nav-controls.ts +import { Component, inject } from '@angular/core'; +import { Nav } from 'slide-ng/lib/nav'; + +@Component({ + selector: 'my-nav-controls', + template: ``, +}) +export default class MyNavControls { + nav = inject(Nav); +} +``` + +Give a button `class="slidev-icon-btn"` and a `title` to look like the built-in ones. + +A layer is not a slide: the built-in tags are not there by themselves, so import each one it uses, such as `SlideProgressBar` from `slide-ng/builtin/progress-bar`. See [Progress bar](./progress-bar.md). diff --git a/src/content/features/import-snippets.md b/src/content/features/import-snippets.md index 6511a67..ecbcfba 100644 --- a/src/content/features/import-snippets.md +++ b/src/content/features/import-snippets.md @@ -12,9 +12,12 @@ description: Put a file, or one region of it, into a code block. <<< ./snippets/example.ts#greet <<< ./snippets/example.ts#greet ts {1|2} <<< @/snippets/example.ts {*}{lines:true} +<<< ./snippets/greet.py python [greet.py ~i-carbon:code~] {2}{lines:true} ``` -The path is relative to the markdown file. `@/` is the folder of the entry `slides.md`. +The parts come in this order, each one after the path optional: `<<< path#region lang [title] {ranges}{options}`. The title may have spaces and an [icon](./code-groups.md#icons-in-titles). The path cannot have spaces, because a word after it is read as the language. + +The path is relative to the markdown file. `@/` is the folder of the entry `slides.md`. Inside a file imported with `src:`, a path starts at that file's folder: write `<<< @/snippets/a.ts` for the deck's own `snippets/` folder. The "not found" warning says so, and names the slide and the file and line it comes from. `#greet` is a region written in the file between markers: diff --git a/src/content/features/interface-language.md b/src/content/features/interface-language.md new file mode 100644 index 0000000..ebfba46 --- /dev/null +++ b/src/content/features/interface-language.md @@ -0,0 +1,90 @@ +--- +title: Interface language +description: Show the bar, the menus and the presenter in the deck's language, and translate them with a JSON file. +--- + +# Interface language + +slide.ng's own interface is in English: the bar and its tooltips, the settings and sync menus, the overview, the go-to box, the right-click menu, the presenter and its click preview, the `/overview` and notes pages, the info dialog, the drawing toolbar and the password prompt of `dev --remote`. The headmatter's `lang` picks another language. Turkish is built in. + +```md +--- +lang: tr # , the interface in Turkish, dates and numbers written the Turkish way +uiLang: en # optional: the interface in another language than the slides +--- +``` + +| Key | What it does | +| --- | --- | +| `lang` | The language of the deck: ``, the interface language and the locale of the `date`, `number`, `currency` and `percent` pipes. | +| `uiLang` | The interface language only. It wins over `lang`. | + +A tag is written the usual way: `pt_br` is read as `pt-BR`. Without either key the interface is English. + +## Translate it + +For a language that is not built in, put `locales/.json` next to `slides.md`. It is flat JSON, with the keys you translate: + +```json +{ "nav.next": "Nächste Folie", "presenter.noNotes": "Keine Notizen für Folie {no}." } +``` + +The keys and their English text are listed in [UI-STRINGS.md](https://github.com/slide-ng/slide.ng/blob/main/docs/UI-STRINGS.md) in the slide.ng repository. Keep the `{name}` placeholders, in any order the sentence needs. + +- A key that no file translates stays English, and so does a language without a file. +- A theme or an addon can have a `locales/` folder too. The built-in translations come first, then the theme, the addons and the deck, key by key, so a deck can change one text of its theme. See [Themes and addons](../customize/themes.md#translating-the-interface). +- In each folder the shorter tag is read first: `de.json`, then `de-AT.json`. +- The generator warns about a key that does not exist, a value that is not text and a placeholder that was lost or added. That key stays English. A file that is not JSON stops the build. + +## Dates and numbers + +`lang` also sets Angular's `LOCALE_ID`, so the pipes in a slide write for that language: + +```md +{{ price | currency: 'EUR' }} · {{ day | date }} · {{ n | number }} +``` + +Only that locale's data goes into the build. `pt-BR` uses Angular's `pt-BR` data, or else `pt`. A language Angular has no data for is written the English way, and the generator says so. `uiLang` does not change the locale. The date on the notes PDF follows `lang` too. + +The "go to" box (`g`) compares titles by the rules of the deck's language and without accents, so in a Turkish deck `izmir` finds "İzmir" and `cafe` finds "Café". A slide number may be typed with Arabic-Indic or Persian digits (`٣`, `۵`). + +## Right to left + +`ar`, `he`, `fa` and `ur` set `dir="rtl"` on the page, unless `htmlAttrs.dir` says otherwise. The bar, the menus, the go-to box and the presenter are mirrored, and so are a blockquote's line and the progress bar's minutes. Mermaid drawings stay left to right, as Mermaid lays them out; their Arabic or Hebrew labels still read right to left. + +- The keys stay physical, as in Slidev: right and space go to the next click, left to the one before. A selected `slide-ng-drag` element moves the way the arrow points. +- In the quick overview the arrow keys follow the mirrored grid: left goes to the next card, which is on the left. In the sync menu ArrowLeft goes to the next mode. +- Transitions are not mirrored: `slide-left` still slides to the left. +- An interface language written the other way than the page, such as `uiLang: en` in an Arabic deck, keeps the direction of its text, so "2 slides" keeps its order. +- The note editors and the side editor take the direction of each line from its first letter, so a line that mixes Arabic and Latin is not reordered. + +## One deck in several languages + +Write one entry file per language, and import the slides that are the same in every language with `src:`: + +``` +slides.md lang: en +slides.tr.md lang: tr, its own translated slides, and src: ./pages/demo.md for a shared one +pages/demo.md +``` + +Build each into its own folder: + +```sh +slide-ng build slides.md --out dist/en +slide-ng build slides.tr.md --out dist/tr +``` + +`--lang tr` on `dev`, `build` and `export` sets `lang` for one run without changing the file, and replaces `uiLang` too, so the interface follows it. `SLIDE_NG_LANG` does the same. See the [CLI reference](../built-in/cli.md). + +`--lang` is for slides written in that language. It also sets `` and the language of the PDF, so `--lang en` on a Turkish deck tells screen readers that the Turkish text is English. To change only the interface, set `uiLang` in the headmatter instead. + +The language of a deck is fixed when it is built, so `export --dist` and `export --url` refuse `--lang`. Build each language with `slide-ng build --lang `, then export that folder. + +## Limits + +- Turkish is the only built-in translation. +- Error messages that come from the browser (camera, recording, screen sharing) and the generator's warnings stay English. +- In a right-to-left deck the keys and the transitions are not mirrored, on purpose, as in Slidev. +- The language cannot be switched while presenting. Build one deck per language instead. +- The built-in context menu labels follow the language, so a `setup/context-menu.ts` should pick an entry by its `icon`, not its label. diff --git a/src/content/features/line-highlighting.md b/src/content/features/line-highlighting.md index 6e37e50..a067654 100644 --- a/src/content/features/line-highlighting.md +++ b/src/content/features/line-highlighting.md @@ -27,7 +27,7 @@ const d = 4 ``` ```` -- A range is a line (`2`), a list (`2,3`) or a span (`3-4`). With `startLine` in the options, the numbers are the ones shown, see [Line numbers](./line-numbers.md). +- A range is a line (`2`), a list (`2,3`) or a span (`3-4`). With `startLine` in the options, the numbers are the ones shown, see [Line numbers](./line-numbers.md). A step that picks none of the numbers shown, such as `{3-5}` with `startLine:10`, gets a warning. - `all` shows every line normally. `hide` shows nothing. `*` highlights nothing and keeps every line normal. - Each `|` adds one click to the slide. - In the options brace, `at` is the first click of the ranges (default `+1`) and `finally` is what is shown after the last click: `last`, `all`, `hide` or a range. Example: `{1|2} {at:'+1', finally:'last'}`. diff --git a/src/content/features/magic-move.md b/src/content/features/magic-move.md index 4392a90..8eea0ad 100644 --- a/src/content/features/magic-move.md +++ b/src/content/features/magic-move.md @@ -28,4 +28,15 @@ const add = () => count += 1 - `magicMoveDuration` in the headmatter sets the duration for the deck, and `magicMoveCopy: false` removes the copy button. - A `[title]` before the options gives a title bar. -Only some `shiki.magicMove` options of Slidev are read. The stagger is fixed. +The headmatter's `shiki.magicMove` takes the animation options of Slidev and `@shikijs/magic-move`: `duration`, `stagger` (milliseconds, default 1), `delayMove`, `delayLeave`, `delayEnter`, `delayContainer`, `easing`, `animateContainer`, `containerStyle`, `splitTokens` and `enhanceMatching`. + +```md +--- +shiki: + magicMove: + stagger: 20 + easing: ease-in-out +--- +``` + +A block's own `duration` wins, then `magicMoveDuration`, then `shiki.magicMove.duration`. A title may have an icon, see [Code groups](./code-groups.md#icons-in-titles). diff --git a/src/content/features/math.md b/src/content/features/math.md index e8a0325..1f6d56a 100644 --- a/src/content/features/math.md +++ b/src/content/features/math.md @@ -18,7 +18,7 @@ $$ {1|2|all} $$ ``` -- `{1|2|all}` after the opening `$$` steps through the formula's lines with clicks, with the same range syntax as code. +- `{1|2|all}` after the opening `$$` steps through the formula's lines with clicks, with the same range syntax as code. The lines are the rows of its environment, split by `\\`, and a step that picks none of them gets a warning. - Inline math follows Slidev's rule, so `$5 and $10` stays text. - Inside math, `{{` is math, never an Angular interpolation. - `mhchem` is not loaded. diff --git a/src/content/features/mcp.md b/src/content/features/mcp.md index b4c2eb2..c1574c4 100644 --- a/src/content/features/mcp.md +++ b/src/content/features/mcp.md @@ -24,3 +24,23 @@ Starts an MCP server on stdio. A client that speaks the protocol can read and ed - Slide 1, the headmatter, cannot be removed or moved. - Insert, remove and move need all slides to be in the entry file. - Every change is parsed again before it is written, and only the deck's own markdown files are touched. A path outside the deck's folder is refused. + +## Over HTTP from the dev server + +`slide-ng dev` also serves MCP at `http://localhost:4230/__mcp`, with the streamable HTTP transport, as Slidev does. It is stateless and takes POST only. It has the seven tools above and two more: + +| Tool | What it does | +| --- | --- | +| `get_info` | The entry file, the title, the number of slides, the files, the dev address and the current slide. With `--remote` also `remoteUrls`, the addresses other machines open, without the password. | +| `goto_slide` | Moves every open window to a slide and click, and answers the click step the windows took. A step past the slide's last click becomes the last. With no window open the answer is the asked step, and `windows` is 0. | + +A client configuration: + +```json +{ "mcpServers": { "slide-ng": { "type": "http", "url": "http://localhost:4230/__mcp" } } } +``` + +- It has the dev server's rules: the Host and Origin checks, and with `--remote` the password. The client sends it as `Authorization: Bearer `. Spaces at its start or end are ignored. +- A password that is not ASCII may be sent as UTF-8, as `curl` does. `fetch`, and so the MCP TypeScript SDK's client, cannot send a character above U+00FF: such a client puts the UTF-8 bytes in the header itself, in Node `'Bearer ' + Buffer.from(password, 'utf8').toString('latin1')`. +- `mcp: false` in the headmatter turns the endpoint off. +- A window whose sync is turned off does not follow `goto_slide`. It is waited for 5 seconds, and the answer is then the asked step. diff --git a/src/content/features/mermaid.md b/src/content/features/mermaid.md index 46d78c5..8f51edf 100644 --- a/src/content/features/mermaid.md +++ b/src/content/features/mermaid.md @@ -20,4 +20,4 @@ A[Text] --> B{Decision} - `` works as a tag too. - While a diagram is being drawn its element has `data-slide-ng-waitfor`, and the attribute goes away when the picture is there. The exporter waits for it. -A `setup/mermaid.ts` file returns Mermaid's config for every diagram. See [Setup hooks](../customize/setup-hooks.md). +A `setup/mermaid.ts` file returns Mermaid's config for every diagram. A `setup/mermaid-renderer.ts` file draws the diagrams with another library, or with Mermaid in another way. See [Setup hooks](../customize/setup-hooks.md#mermaid-renderer). diff --git a/src/content/features/monaco-editor.md b/src/content/features/monaco-editor.md index 82fcd4d..ac3c1cb 100644 --- a/src/content/features/monaco-editor.md +++ b/src/content/features/monaco-editor.md @@ -25,8 +25,24 @@ console.log('Modified text') Keys typed in an editor do not trigger slide shortcuts. A `setup/monaco.ts` file changes editor options for the whole deck, see [Setup hooks](../customize/setup-hooks.md). +## On or off per mode + +`monaco` in the headmatter takes `true` (the default), `false`, `dev` or `build`, as in Slidev: + +```md +--- +monaco: dev # editors in slide-ng dev, plain highlighted code in builds and exports +--- +``` + +Where it is off, every `{monaco}`, `{monaco-diff}`, `{monaco-run}` and `{monaco-write}` block is a plain highlighted block. A `{monaco-run}` with `showOutputAt` then has no click, so with `monaco: build` its slide has one click less in `slide-ng dev` than in the build, and the `[click]` markers of its notes move. + +The editor costs nothing on the first slide: Monaco and its stylesheet are downloaded the first time a slide with an editor is shown. + Monaco does not load type definitions of packages (`monacoTypesSource` is not supported), so it does not know types from `node_modules`. - A deck with Monaco and Mermaid becomes a large script in an offline folder. See [Presenting offline](../guide/offline.md). + +A deck with Monaco and Mermaid becomes a large script in an offline folder. See [Presenting offline](../guide/offline.md). + diff --git a/src/content/features/monaco-run.md b/src/content/features/monaco-run.md index 2c18d5f..fa72088 100644 --- a/src/content/features/monaco-run.md +++ b/src/content/features/monaco-run.md @@ -15,7 +15,9 @@ console.log('runs on a click and the result appears') - `autorun` is `true`, `false` or `'once'`. - `showOutputAt:'+1'` shows the output after a click. -- The code runs in a sandboxed frame with no access to the slides, and stops after 5 seconds. +- The code runs in a worker of a sandboxed frame, with no access to the slides, and stops after 5 seconds. A run stuck in an endless loop holds up nothing else, and pressing run again or leaving the slide stops it at once. `console` works; the worker has no `document`, `parent` or `alert`. +- The code runs in strict mode, as in Slidev: `total = 1` without `let` or `const` stops with "ReferenceError: total is not defined". `monacoRunUseStrict: false` in the headmatter runs it without `"use strict"`. +- The output takes its click in document order: an output at `showOutputAt:'+1'` followed by a `slide-ng-click` shows first. - TypeScript is turned into JavaScript for you. - `console.table` and `console.group` are shown too. - `import` is not supported. diff --git a/src/content/features/overview-pages.md b/src/content/features/overview-pages.md new file mode 100644 index 0000000..2832153 --- /dev/null +++ b/src/content/features/overview-pages.md @@ -0,0 +1,43 @@ +--- +title: Overview and notes pages +description: The /overview, /entry and /notes-edit pages list the slides with their notes, and let you edit the notes while the dev server runs. +--- + +# Overview and notes pages + +Besides the slides, the presenter and `/notes`, a deck has three pages of its own, as in Slidev. + +| Address | What it shows | +| --- | --- | +| `/overview` | Every slide in a list, with a clicks slider under it and its notes beside it. On a narrow screen the notes go below each slide. | +| `/entry` | Links to the slides, the presenter, the notes, the overview and, while the dev server runs, the notes editor. Open it on a second device to pick a page. | +| `/notes-edit` | Every note of the deck in one text, while the dev server runs. | + +The button at the top of the quick overview (`o`) opens `/overview`. With `dev --remote`, `/overview` and `/notes-edit` need the password, like the presenter. + +## Edit the notes + +While the dev server runs, a double click on a note in `/overview`, or Enter on it, turns it into a text box. Leaving the text saves it into the markdown file, and Escape cancels. A live region says how the save ended, "Saved." or "Not saved.". + +`/notes-edit` has every note after a `--- #N` line, where `N` is the slide number: + +```md +--- #1 +Say hello. + +--- #2 +[click] Then show the first point. +``` + +The notes that changed are saved after a short pause. Keep the `--- #N` lines as they are: a text whose lines were changed is not saved. Saving rebuilds the deck and reloads the page, and the page keeps the caret and what you typed meanwhile. + +- A `-->` in a note would end the HTML comment early, so it is written `-- >`, as in the presenter's note editor. +- Each line takes its direction from its first letter, so a line that mixes Arabic and Latin keeps its order. +- The notes of a slide imported from outside the deck's folder cannot be edited here. + +The presenter page has its own note editor, see [Presenter mode](../guide/presenter-mode.md#notes). + +## Limits + +- `/notes-edit` and the note editing of `/overview` work only while the dev server runs. A static build leaves `/notes-edit` out, and `/overview` shows the notes read-only. +- The names `overview`, `entry` and `notes-edit` cannot be a `routeAlias`, see [Slide addresses](./routing.md). diff --git a/src/content/features/preload-images.md b/src/content/features/preload-images.md new file mode 100644 index 0000000..9a5a42d --- /dev/null +++ b/src/content/features/preload-images.md @@ -0,0 +1,47 @@ +--- +title: Loading pictures ahead +description: The pictures of the next slides are loaded before they are shown, as with Slidev's preloadImages. +--- + +# Loading pictures ahead + +Pictures are loaded before their slide is shown, as in Slidev, so a slide does not appear half drawn. It is on by default. + +```md +--- +preloadImages: { ahead: 3 } +--- +``` + +| Value | What it does | +| --- | --- | +| `true` | The default: 3 slides ahead. | +| `{ ahead: n }` | `n` slides ahead. `0` loads none ahead. | +| `false` | Turns it off. | + +After each move, the pictures of the slide on screen, the one before and the next `ahead` slides load in idle time. 3 seconds after the page has loaded, every slide's pictures load. At most two pictures load at a time, the ones near the current slide first, so on a slow network the whole deck does not hold up the next slide. + +- When the browser asks to save data, or the connection is 2g or 3g, only the slides around the current one are loaded ahead. The whole deck would cost a download of every picture. +- The pictures are found by the generator: `` and markdown pictures, `url()` in inline styles, and the frontmatter's `image`, `backgroundImage` and `background`. +- `preload: false` in a slide's frontmatter leaves that slide out. +- All slides are in the first bundle, so `preload` has no other effect here. + +## Progress in a component + +`preloadProgress` (0 to 1) and `preloadComplete` in `slide-ng/lib/preload-images` are signals a component can read: + +```ts +import { Component } from '@angular/core'; +import { preloadComplete, preloadProgress } from 'slide-ng/lib/preload-images'; + +@Component({ + selector: 'preload-status', + template: '@if (!done()) { Loading pictures: {{ (progress() * 100).toFixed(0) }}% }', +}) +export class PreloadStatus { + progress = preloadProgress; + done = preloadComplete; +} +``` + +A picture that still fails after two retries counts as done, so the progress reaches 1. With `preloadImages: false` it is complete from the start. When the whole-deck pass is skipped to save data, `preloadComplete` becomes true only once every slide's pictures were loaded by visiting them. diff --git a/src/content/features/progress-bar.md b/src/content/features/progress-bar.md new file mode 100644 index 0000000..b8b1cd4 --- /dev/null +++ b/src/content/features/progress-bar.md @@ -0,0 +1,55 @@ +--- +title: Progress bar +description: A thin bar on every slide that shows how far through the deck you are. +--- + +# Progress bar + +`progressBar` in the headmatter draws a thin bar along the edge of every slide. It is empty on the first slide and full on the last, and the clicks of a slide fill part of the way to the next one. + +```md +--- +progressBar: + position: top + height: 6 + color: '#e11d48' +--- +``` + +`progressBar: true` uses the defaults, and `progressBar: top` or `bottom` is short for the position. Any other value draws no bar, and the build warns about it. + +| Option | What it does | +| --- | --- | +| `position` | `top` or `bottom` (default). | +| `height` | In slide pixels. Default `4`. | +| `color` | The colour of the filled part. Default: the theme's primary colour, also one set with `themeConfig.primary`. | +| `track` | The colour of the empty part. Default transparent. | +| `clicks` | `false` counts whole slides only. Default `true`. The clicks of the last slide do not count. | +| `minutes` | `true` shows the minutes of the presenter [timer](./timer.md) while it runs: those left of a countdown, or those passed on a stopwatch. | + +The bar is drawn on the audience page, on the presenter's slide and next step, and in print and export. Slidev has a bar on its presenter page only. + +## Place it yourself + +`` draws the same bar in a slide, with the same options as attributes. `position="inline"` keeps it where it is written instead of on an edge. + +```md + +``` + +In a [layer](./global-layers.md), which is a plain Angular component, import it first: + +```ts +// layers/global-bottom.ts +import { Component } from '@angular/core'; +import { SlideProgressBar } from 'slide-ng/builtin/progress-bar'; + +@Component({ + selector: 'my-global-bottom', + imports: [SlideProgressBar], + template: '', +}) +export default class MyGlobalBottom {} +``` + +For screen readers the bar is a `progressbar` named "Progress through the slides". The name and the minutes are in the deck's [interface language](./interface-language.md). In forced colors mode the bar is filled with the system's highlight colour. diff --git a/src/content/features/remote-access.md b/src/content/features/remote-access.md index 4bb3d61..12879d1 100644 --- a/src/content/features/remote-access.md +++ b/src/content/features/remote-access.md @@ -11,13 +11,18 @@ slide-ng --remote # the same, with a random password that is printed SLIDE_NG_PASSWORD=secret slide-ng --remote # keeps the password out of the process list ``` -The command prints the public and private deck addresses and the presenter address, `/presenter/1?password=secret`. Use that address on the presenter's device. A viewer opens `http://:4230/1?password=secret`. +The command prints the public and private deck addresses and the presenter address, `/presenter/1?password=secret`. Use that address on the presenter's device. A viewer opens `http://:4230/1?password=secret`. The page logs in with the password, then takes it out of the address bar. With `routerMode: hash` the addresses are `/#/?password=...` and `/#/presenter/1?password=...`. + +Press `c` in the terminal of the dev server to print a QR code of the private address for each network address, so a phone can open it. The QR code holds the password, like the address it encodes. - Only the dev server listens on the network, on `0.0.0.0` unless you pass `--bind
`. Its companion server stays on `127.0.0.1`. - There is always a password: the one given, `SLIDE_NG_PASSWORD`, a prompt on a terminal, or a random one. This differs from Slidev, which allows a remote mode without one. The host, origin and session token of every request are checked as well. -- It lets a device sync and write, and opens the presenter and notes pages. Without it, those addresses go to the slides. +- It lets a device sync and write, and opens the presenter, notes, overview and notes editor pages. Without it, those addresses go to the slides. +- Opened without a password, the page asks once, in the deck's [interface language](./interface-language.md). A device that dismisses the prompt only shows the slides: it does not follow the presenter, since syncing needs the password. +- A viewer on another device only follows. Its sync menu has Send Only and Bidirectional Sync disabled, see [Presenter mode](../guide/presenter-mode.md#sync-mode). +- Spaces in a password: the terminal prompt trims them, but `SLIDE_NG_PASSWORD` and `--remote=...` keep them as written. The login compares exactly what is typed. - It is sent without encryption and there is no HTTPS, so use it on a network you trust. Whoever has it can edit your slides. - A login lasts 8 hours. It is signed with the password, so it survives a restart of the dev server with the same password. Changing the password ends every login at once. There is no other way to log a device out. - Windows of one browser sync with no server. Other devices need this. -See [Security](../project/security.md) for the checks the server makes. `--tunnel` is not available. +The dev server's MCP endpoint needs the same password, see [MCP server](./mcp.md#over-http-from-the-dev-server). See [Security](../project/security.md) for the checks the server makes. `--tunnel` is not available. diff --git a/src/content/features/routing.md b/src/content/features/routing.md index 83eefb0..2024767 100644 --- a/src/content/features/routing.md +++ b/src/content/features/routing.md @@ -5,7 +5,7 @@ description: Slide numbers in the URL, aliases, and hash or history routing. # Slide addresses -Every slide has an address, `/3` for the third slide. The URL is the state: `/7?clicks=2` is slide 7 at click 2. +Every slide has an address, `/3` for the third slide. The URL is the state: `/7?clicks=2` is slide 7 at click 2. A slide at its first click is just `/7`, as in Slidev. ## Aliases @@ -17,7 +17,16 @@ routeAlias: intro --- ``` -Now `/intro` opens the slide. `` links to it, see [Components](../built-in/components.md). +Now `/intro` opens the slide. `` links to it, see [Components](../built-in/components.md), and so does the markdown link `[intro](./intro)`, see [Links](../guide/syntax.md#links). An alias may be any name, also `café`. + +The build stops, naming the slides, on an alias that cannot work: + +- `print`, `notes`, `presenter`, `overview`, `entry` or `notes-edit`, which are pages of the app; +- a number, because `/2` is slide 2; +- an empty one, or one with `/`, `.`, `?`, `#` or `\`; +- one on two slides, also through a `src:` import of several slides. + +A page a deck adds itself with `setup/routes.ts` is not seen by the build, so give it a name no slide uses as an alias. ## Hash or history @@ -27,6 +36,19 @@ routerMode: hash --- ``` -With `hash` the address looks like `/#/3`. The default is `history`. Hash mode needs nothing from the web server, see [Hosting](../guide/hosting.md). Slidev's `memory` mode is not available. +With `hash` the address looks like `/#/3`. The default is `history`. Hash mode needs nothing from the web server, see [Hosting](../guide/hosting.md). + +`slide-ng build --router-mode history|hash|memory` replaces the headmatter's `routerMode` for one build. The build records it, so `export --dist` opens it with the right addresses. `build --offline` always uses hash mode. + +## Memory mode + +`routerMode: memory` never changes the address bar, as Slidev's memory router. Use it for a kiosk, or a deck shown inside another page. + +- The deck always opens at slide 1. An address such as `/7` or `/presenter/1` opens slide 1 too. +- `/presenter`, `/notes`, `/overview` and the other pages of the app cannot be opened by their address, and a new tab starts at slide 1. Open the presenter with the bar's button, in the same window. +- A slide link, `[x](5)` or ``, has the address `#/5`. The deck reads it once when it starts, so "open in new tab" shows that slide, and then drops it from the address. +- The browser's Back and Forward buttons leave the deck, which adds no history entries. Reloading the presenter page returns to the audience view at slide 1. +- The print pages that `export` opens are still read from the address, so `--download` and the Open Graph picture work. +- In the dev server the slide is kept when the page reloads after a save. An address that matches no slide shows the `404` layout. diff --git a/src/content/features/scoped-styles.md b/src/content/features/scoped-styles.md index fbd2353..3bfcbba 100644 --- a/src/content/features/scoped-styles.md +++ b/src/content/features/scoped-styles.md @@ -17,4 +17,4 @@ h1 { color: red; } The CSS is placed inside the slide's own element with native CSS nesting, so every selector matches only inside that slide, layout elements included. For example `.two-cols-header { column-gap: 20px }` works. Nested rules and `&` work too. -`@keyframes` work. UnoCSS directives such as `--uno: ...` and `@apply` do not work, and the generator warns about them. Use utility classes in the HTML instead, see [Styling](../guide/styling.md). +`@keyframes` work. UnoCSS's directives work as in Slidev: `@apply p-4 text-red-500;`, `--uno: p-4;`, `@screen md { ... }` and `theme('colors.red.500')` are written out as CSS. `h1 { @apply dark:text-white; }` becomes a rule for dark mode, and `forward:`, `backward:`, `light:` and `rtl:` work the same way. See [UnoCSS](./unocss.md#directives). diff --git a/src/content/features/seo-meta.md b/src/content/features/seo-meta.md index e54bc59..1533c8d 100644 --- a/src/content/features/seo-meta.md +++ b/src/content/features/seo-meta.md @@ -29,10 +29,32 @@ seoMeta: | `title`, `titleTemplate` | The browser tab. `%s` is the title. | | `info` | Markdown for the info dialog. With the markup taken out and cut at 300 characters, it is also the page description and the PDF's subject. | | `author`, `keywords` | Meta tags. `keywords` is a list or a comma separated text. | -| `lang`, `htmlAttrs` | Attributes on ``. | +| `lang`, `htmlAttrs` | Attributes on ``. `lang` also picks the [interface language](./interface-language.md) and the locale of dates and numbers, and `ar`, `he`, `fa` and `ur` add `dir="rtl"` unless `htmlAttrs.dir` is set. | | `favicon` | A file of `public/`, or a web address. | | `seoMeta` | Open Graph and Twitter tags: `ogTitle`, `ogDescription`, `ogImage`, `ogUrl`, `twitterCard` and so on. | `seoMeta.ogImage` is a web address, `auto` (a screenshot of slide 1 made by the build, 1470 x 828, written as `og-image.png`), or left out: an `og-image.png` next to `slides.md` is then copied and used. Crawlers need a full address, so set `seoMeta.ogUrl` to the public address of the deck. `auto` needs a Chromium at build time. + +## Your own index.html + +An `index.html` next to `slides.md`, or at the top of a theme or addon, adds tags that the headmatter has no key for: analytics, an extra font, a ``. Only what is inside its `` and `` is used, so it does not need to be a whole page: + +```html + + + + + + + +``` + +- The inside of its `` goes into the page's head, after the tags made from the headmatter. The inside of its `` goes after the app: out of sight, but screen readers read it. The parts of every root are added in order: theme, addons, deck. +- A `` or a `<meta>` with the same name and `media` as one made from the headmatter replaces it, as Slidev's head manager does. Tags meant to repeat, such as several `og:image`, are all kept. While the deck runs, the tab's title still follows the slides. +- The attributes of its `<html>` and `<body>` are added to the page's. The headmatter's `htmlAttrs` win, and `class` values are joined. `lang` comes from the headmatter only, because it also picks the interface language, so the `lang` of an `index.html` is ignored with a warning. +- A root address such as `src="/analytics.js"` gets the base in front, like `/x.svg` in a slide. +- The file is the deck's own code: it is copied as it is, and its scripts run in `dev`, in a build, in an offline folder and in an export. In an offline folder a `<script type="module" src>` does not run; use a plain `<script>`. + +`slide-ng build` moves its inline scripts into files, see [Hosting](../guide/hosting.md#content-security-policy). Rename the file if it was not meant for the deck. diff --git a/src/content/features/side-editor.md b/src/content/features/side-editor.md index 0e67182..41b205c 100644 --- a/src/content/features/side-editor.md +++ b/src/content/features/side-editor.md @@ -9,4 +9,4 @@ While `slide-ng dev` runs, the edit button in the bar opens a side editor with t <ngmd-image src="/images/ui/side-editor.png" alt="The side editor next to a slide" caption="The side editor" width="640"></ngmd-image> -`editor: false` in the headmatter hides the button. A static build has no side editor and makes no requests. +On the presenter page the side editor opens in the notes pane. `editor: false` in the headmatter hides the button. A static build leaves the side editor out and makes no requests. Each line takes its direction from its first letter, so a line that mixes Arabic and Latin keeps its order. diff --git a/src/content/features/speaker-notes.md b/src/content/features/speaker-notes.md index d8591b6..668f47c 100644 --- a/src/content/features/speaker-notes.md +++ b/src/content/features/speaker-notes.md @@ -18,4 +18,20 @@ In a note, `[click]` marks where the next click starts, and `[click:3]` moves th - `/notes` shows only the notes and follows the presenter. - `slide-ng export-notes` writes a PDF of the notes. - `slide-ng build --without-notes` leaves the notes out of the built site. -- The notes are plain text. Ruby annotations for Japanese (`notesAutoRuby`) are not supported. +- A note is markdown: lists, `**bold**`, `code` and links are drawn, with the typography preset's `prose` look, as in Slidev. +- A link to a web page in a note opens a new tab, so the presenter view stays. A link to a slide, such as `[demo](5)`, moves the presenter to that slide when you click it in the presenter view. In `/notes` and elsewhere it opens the address. +- `/overview` lists every note next to its slide, and while the dev server runs you can edit the notes there, in the presenter and on `/notes-edit`. See [Overview and notes pages](./overview-pages.md). + +## Readings above words + +`notesAutoRuby` in the headmatter shows a reading above each listed word in every note, with `<ruby>`, as in Slidev. It helps when you read a language such as Japanese aloud: + +```md +--- +notesAutoRuby: + 日本語: にほんご + 発表: はっぴょう +--- +``` + +Words in code, inside a tag's attributes and in a `<ruby>` you wrote yourself are left alone. A longer listed word is matched before a shorter one. diff --git a/src/content/features/table-of-contents.md b/src/content/features/table-of-contents.md index de3d088..619a110 100644 --- a/src/content/features/table-of-contents.md +++ b/src/content/features/table-of-contents.md @@ -23,4 +23,4 @@ Leave a slide out with `hideInToc: true`. | `minDepth`, `maxDepth` | Which levels to list. | | `mode` | `all`, `onlyCurrentTree` (the path to the current slide and what is below it) or `onlySiblings` (the lists that contain the current slide). | -The slide's title is plain text: the markup of links, code and HTML tags in a heading is taken out, also in the tab title, the go-to box and the PDF outline. `$math$` in a title is drawn with KaTeX here. Slidev's `TitleRenderer`, which shows a title as HTML, is not there. +The list shows each title as HTML: inline markdown such as `**bold**` and `code` is drawn, and `$math$` with KaTeX. Raw HTML in a title is shown as text. `<slide-ng-title-renderer no="3" />` shows one title the same way anywhere, see [Components](../built-in/components.md). The tab title, the go-to box and the PDF outline use the plain text, with the markup taken out: `$O(1)$` is "O(1)", and TeX's one-character escapes are undone, so `$400\%$` is "400%". Screen readers read the math of a title as its TeX. Bidi override characters are left out of the titles. diff --git a/src/content/features/timer.md b/src/content/features/timer.md index 7582555..a9b8984 100644 --- a/src/content/features/timer.md +++ b/src/content/features/timer.md @@ -17,4 +17,11 @@ timer: countdown - `duration` is `30min`, `1h30min`, `90s`, or a number of minutes. - `timer` is `stopwatch` (the default) or `countdown`. -The timer starts, pauses and resets from the buttons on the presenter page, and the state is shared with the other presenter windows. A reload of the presenter page keeps a running or paused timer, and a window that opens later gets the same time. A bar shows how much of the duration has passed. +The timer starts, pauses and resets from the buttons on the presenter page. Hover the clock, or Tab to it, for the pause, play and reset buttons. On a touch screen, such as a phone used as the remote, they are always shown. + +- The timer is shared: every window and device shows the same time, also when their clocks are not set alike. +- A reload of the presenter page keeps a running or paused timer. A presenter tab that was closed and opened again gets it from any other open window of the deck, and from the dev server while it runs. In a built deck with no other window open it starts again. +- A restart of the dev server (`r`) keeps the timer too. +- The newest start, pause or reset wins, so a tab that comes back with the time it kept does not undo a later reset from another window. + +A bar shows how much of the duration has passed. `progressBar: { minutes: true }` shows the minutes on the slides too, see [Progress bar](./progress-bar.md). diff --git a/src/content/features/twoslash.md b/src/content/features/twoslash.md index 4b4c74d..c935d4a 100644 --- a/src/content/features/twoslash.md +++ b/src/content/features/twoslash.md @@ -16,4 +16,6 @@ const count = Math.max(1, 2) `// ^?` shows the type of the word above the `^`, on a line of its own under that code line, so it does not cover the code. -A TypeScript error in the block is shown under its line, and the types still work. Add `// @errors: <code>` to mark an error as expected. The `twoslash: dev|build` switch of Slidev is not read. +A TypeScript error in the block is shown under its line, and the types still work. Add `// @errors: <code>` to mark an error as expected. A code that `// @errors` lists but TypeScript does not report gets a warning that names the slide; the block is still drawn. When Twoslash fails on a block, the warning gives its reason and the slide. + +`twoslash` in the headmatter takes `true` (the default), `false`, `dev` or `build`, as in Slidev. `false` shows the blocks without types, and `dev` or `build` runs Twoslash in that mode only. A build and an export count as `build`. diff --git a/src/content/features/unocss.md b/src/content/features/unocss.md new file mode 100644 index 0000000..c907d1c --- /dev/null +++ b/src/content/features/unocss.md @@ -0,0 +1,61 @@ +--- +title: UnoCSS +description: Attributify, variant groups, the prose preset and UnoCSS directives, as in Slidev. +--- + +# UnoCSS + +Slides are styled with *UnoCSS utility classes, see [Styling](../guide/styling.md). These UnoCSS features work as in Slidev. + +```md +<div flex="~ col" text="sm red-500" p="4">Attributify</div> +<div class="hover:(bg-blue-500 text-white)">A variant group</div> +<article class="prose dark:prose-invert">Long text, styled</article> +``` + +## Attributify + +Utilities can be attributes: `<div flex="~ col" text="sm red-500" p="4">`, with a variant in the name (`hover:bg="blue-500"`) or without a value (`<div flex items-center>`). Angular keeps such attributes as they are. + +- Only the deck's own files are read this way: slides, `components/`, layouts and the other files of the deck, its theme and addons. +- Some attributes are never read as utilities, because they are inputs or draw an SVG: those of slide.ng's components (`<slide-ng-toc columns="2">`), the frontmatter keys a layout takes (`scale: 0.8` of `iframe-right`), the inputs of the deck's own components, and the attributes of SVG elements (`<text font-size="14">`). Use `class` there. +- Plain HTML attributes are read too: `<table border="1">` gets a border, `<span font="bold">` is bold, `<div m="4">` gets a margin. +- An attribute name cannot have a `/` in it. A bare `<div bg-black/50>` stops the generator with a message that says to write it in the class, `class="bg-black/50"`, or with a value, `bg="black/50"`. + +`unocss: { attributify: false }` in the headmatter turns attributify off for the deck, for a deck written for 1.x that uses such attributes as HTML. Classes and variant groups still work. + +## Variant groups + +`class="hover:(bg-blue-500 text-white)"` is written as `hover:bg-blue-500 hover:text-white` while the deck is generated. It works in attributify values (`text="hover:(red-500 xl)"`) and as a bare attribute, `<div p-4 hover:(bg-teal-50 border-teal-400)>`. + +Only a slide's markup is expanded. Code blocks and inline code are shown as written, and the `.ts` files of components, layouts, setup files, themes and addons write the classes out. + +## Typography + +`class="prose"` styles long text: headings, lists, links and quotes. Add `dark:prose-invert` for dark mode, and `not-prose` on a part that should keep its own look. The speaker notes use it too. + +## Directives + +UnoCSS's directives work in a slide's `<style>` and in `styles/` of the deck, a theme or an addon: + +```md +<style> +h1 { + @apply text-4xl text-red-500; +} +.card { + --uno: p-4 rounded shadow; +} +@screen md { + .card { color: theme('colors.red.700'); } +} +</style> +``` + +They are written out as CSS while the deck is generated. A utility UnoCSS does not know gives a warning. In a slide's `<style>`, a variant for something outside the slide works too: `h1 { @apply dark:text-white; }` becomes `.dark <slide> h1`, and `forward:`, `backward:`, `light:` and `rtl:` the same way. `group-hover:` and `peer-checked:` look for the `.group` or `.peer` inside the slide. + +## Limits + +- `text-primary`, `bg-primary` and `border-primary` are shortcuts for the theme's colour, as in Slidev, so they take no opacity: `bg-primary/10` does nothing. Write the colour out, such as `bg-teal-600/10`, or use `color-mix(in srgb, var(--slidev-theme-primary) 10%, transparent)` in a `<style>`. +- The deck's files are read as text, as in Slidev, so a word in backticks in a slide or a note, such as `` `grid` ``, still makes its CSS. That rule also styles an element whose `grid` attribute is an input of your component; name the input otherwise. +- Class names in a theme are found in the `.ts` files of the roots and in the deck's markdown, not in CSS files. diff --git a/src/content/getting-started/cli.md b/src/content/getting-started/cli.md index ac49691..2d2333f 100644 --- a/src/content/getting-started/cli.md +++ b/src/content/getting-started/cli.md @@ -11,6 +11,7 @@ description: The commands of slide-ng and what each one does. slide-ng create my-deck # a new deck folder slide-ng [dev] [slides.md] # dev server slide-ng build [slides.md] # a static site in ./dist +slide-ng build a.md b.md # two decks, in dist/a and dist/b slide-ng build --offline # a folder you open from disk slide-ng export [slides.md] # PDF, PNG, PPTX or Markdown slide-ng export-notes [slides.md] # a PDF of the speaker notes @@ -35,4 +36,6 @@ A deck inside the slide.ng repository is built in place. Any other deck gets a w | `export` | Opens the deck in Chromium and writes a PDF. `--format png`, `pptx` or `md` for the others. | | `format` | Rewrites `slides.md` with tidy spacing and clean YAML. | +While the dev server runs, keys in its terminal work as in Slidev: `r` restarts it, `o` opens the browser, `e` opens the deck in your editor, `c` prints a QR code for `--remote` and `q` quits. See the [CLI reference](../built-in/cli.md#dev). + `slide-ng --version` prints the version, and `slide-ng <command> --help` lists the flags of a command. Every flag is in the [CLI reference](../built-in/cli.md). The Guide pages [Export](../guide/export.md), [Hosting](../guide/hosting.md) and [Presenting offline](../guide/offline.md) show them in use. diff --git a/src/content/getting-started/install.md b/src/content/getting-started/install.md index 91dff10..0a09cfb 100644 --- a/src/content/getting-started/install.md +++ b/src/content/getting-started/install.md @@ -5,6 +5,8 @@ description: Requirements and how to get slide.ng running today. # Install +These pages describe slide.ng 2.0. The commands below install the newest version. A deck that uses 1.x keeps working with it: to move it to 2.0, read [Upgrading from 1.x to 2.0](../migrating/upgrading-to-2.md), or pin the old version with `npm install -D @slide-ng/core@^1`. + ## Requirements - Node 22.22.3 or newer. The `engines` field of the package says `>=22.22.3`. @@ -29,7 +31,7 @@ npm install -D @slide-ng/core npx slide-ng dev ``` -The command is `slide-ng`, described in [The command line](./cli.md). [Your first deck](./first-deck.md) shows what to do next. +`npx slide-ng --version` prints the version you have. The command is `slide-ng`, described in [The command line](./cli.md). [Your first deck](./first-deck.md) shows what to do next. ## From a checkout diff --git a/src/content/guide/accessibility.md b/src/content/guide/accessibility.md new file mode 100644 index 0000000..f11ac51 --- /dev/null +++ b/src/content/guide/accessibility.md @@ -0,0 +1,51 @@ +--- +title: Accessibility +description: What slide.ng does for screen readers, keyboards, contrast and motion, and what a deck has to do itself. +--- + +# Accessibility + +This page lists what the slides, the presenter and the other pages do for people who use a screen reader, a keyboard, high contrast or less motion. The second half is what a deck author has to do, because slide.ng cannot do it for you. + +## Screen readers + +- Each new slide is announced with its number and title, in a polite live region. A title is read as plain text: its markdown marks go, and `$math$` is its TeX, with TeX's one-character escapes undone, so `$400\%$` is "400%". +- Elements that wait for a click are hidden from screen readers until they show. They are inert too: their links and buttons take no focus, also with the `fade` preset, where they are visible but dimmed. +- The slide, with the bar, is the main landmark of the audience page. The presenter, `/overview` and `/notes` each have a main landmark and a level-one heading, and `/entry`, `/notes-edit` and the notes print page have a main landmark. +- The presenter page has three named regions: Current slide, Next step and Notes. The next-step picture is skipped, so headings are not read twice. Line numbers of code and of Magic Move are skipped too. +- The quick overview, the right-click menu and the "go to" box are named dialogs, with all their controls inside. Each overview card is named "Go to slide N: title". +- The progress bar is a `progressbar` named "Progress through the slides". +- Math is read as its TeX source, in the slide and in a title drawn by `<slide-ng-title-renderer>` or the table of contents. +- An exported PDF is tagged for screen readers and has the deck's language. `--per-slide` loses the tags. + +## Keyboard + +- In the quick overview (`o`) the arrow keys move the focus from card to card. +- The right-click menu opens with Shift+F10 or the menu key, see [Shortcuts](../built-in/shortcuts.md#mouse). +- Escape in the settings menu, the sync menu and the drawing toolbar closes it and gives the focus back to its button. After Edit Notes, Escape gives the focus back to the Edit Notes button. +- The borders of the presenter's notes pane are Tab stops that the arrow keys move. +- In the presenter's [click preview](./presenter-mode.md#click-preview) the badges are buttons named "Go to click N". +- On `/overview`, while the dev server runs, each note says it can be edited, and a live region says how a save ended. + +## Contrast, size and zoom + +- The interface's text is at least 4.5:1, or 3:1 for large text, in light and dark mode. That includes the notes of later clicks, which are faded once, and the "No notes." placeholder. +- Icon buttons are at least 24 by 24 pixels, also the click preview's badges and the timer's buttons. +- The settings and sync menus and the info dialog stay inside the window down to 320 by 180 pixels, which is 400% zoom, and scroll. +- In forced colors mode, such as Windows high contrast, the icons are drawn in the text colour and the progress bars in the system's highlight colour. + +A theme that changes the colours, or `.slidev-note-fade`, has to keep these contrasts. + +## Less motion + +When the system asks for less motion (`prefers-reduced-motion: reduce`), slide transitions, click animations, `slide-ng-motion` and Magic Move change at once. A transition with a fixed `duration` ends at once too. JavaScript timers in your own components are not changed: check `matchMedia('(prefers-reduced-motion: reduce)')` there. + +## What the deck has to do + +- Give every picture a text alternative: `![A red bridge at night](/bridge.jpg)` or `<img alt="...">`. A picture given as `image:` or `background:` in the frontmatter is a CSS background and cannot have one. When it carries meaning, put an `<img alt>` in the slide or say it in the text. +- Give each slide a title, with a heading or `title:`. It is what screen readers announce and what the "go to" box finds. +- Set `lang` in the headmatter to the language of the slides. It is `<html lang>`, the PDF's language and the [interface language](../features/interface-language.md). +- In a Mermaid diagram, `accTitle: ...` and `accDescr: ...` lines become the title and description of the SVG. +- Say a formula in words, in the text or the notes, when the audience needs it. Math is read as TeX, not as "a over b". +- Use `slide-ng-click` rather than `@if ($clicks() > 1)` for content that appears: it stays on the page, hidden and inert, and the click preview shows it. +- Check the colours of your own components in both modes. diff --git a/src/content/guide/agents.md b/src/content/guide/agents.md index 2894ae8..d7351e1 100644 --- a/src/content/guide/agents.md +++ b/src/content/guide/agents.md @@ -31,7 +31,7 @@ The skill is part of the `slide-ng` package, so an installed copy has it. Point slide-ng mcp [slides.md] ``` -This starts an MCP server on stdio, so a client can read and edit the deck's slides. See [MCP server](../features/mcp.md) for the tools. +This starts an MCP server on stdio, so a client can read and edit the deck's slides. While `slide-ng dev` runs, the same tools, plus `get_info` and `goto_slide`, are served over HTTP at `http://localhost:4230/__mcp`. See [MCP server](../features/mcp.md) for the tools. ## See also diff --git a/src/content/guide/angular-templates.md b/src/content/guide/angular-templates.md index ac3c1eb..bbcd687 100644 --- a/src/content/guide/angular-templates.md +++ b/src/content/guide/angular-templates.md @@ -31,6 +31,7 @@ The generator turns the markdown of each slide into an Angular template, and it - `{{ ... }}` is passed through untouched. - A single `{`, `}` or `@` in normal text, such as an email address, is safe. It is escaped for you. - An `@if` or `@for` in the middle of a line is not read as control flow, and the generator warns. +- A control-flow block that is not complete, such as `@if (x >= 1 {`, stops the build with a message that names the block and its line. `@let x = 5` without its `;` is shown as text, with a warning. ## Pipes and directives @@ -42,6 +43,35 @@ Price: {{ 1234.5 | currency }} So do the common directives (`*ngTemplateOutlet`, `[ngClass]`, `[ngStyle]`, `*ngIf`, `*ngFor`, `[ngSwitch]`, `[ngPlural]`, `ngComponentOutlet`) and the forms directives: `[(ngModel)]` imports `FormsModule`, and `[formGroup]`, `formControlName` and the like import `ReactiveFormsModule`. Each is imported only by the slides that use it. A pipe or a directive of your own in `components/` is imported the same way, see [Layouts and components](../customize/layouts-components.md). +## The global context + +Every slide has Slidev's global context, under the same names. The values that change are Angular signals, so they are called with `()`: + +| Name | What it is | +| --- | --- | +| `$clicks()` | The current click of this slide. | +| `$page()` | This slide's number, from 1. | +| `$renderContext()` | Where the slide is drawn: `slide`, `presenter`, `previewNext`, `print` or `overview`. | +| `$frontmatter` | This slide's frontmatter: `{{ $frontmatter.title }}`. | +| `$nav` | The navigation: `currentPage()`, `currentLayout()`, `currentFrontmatter()`, `total`, `clicks()`, `next()`, `prev()`, `go(no)` and more. | +| `$slidev.nav` | The same as `$nav`. | +| `$slidev.configs` | The headmatter: `{{ $slidev.configs.title }}`. | +| `$slidev.themeConfigs` | The headmatter's `themeConfig`, merged with the theme's. | + +```md +Slide {{ $page() }} of {{ $nav.total }} + +@if ($clicks() > 1) { +Shown from the second click. +} + +<button (click)="$nav.go(1)">Back to the start</button> +``` + +Vue reads `$clicks` without the call. Here `{{ $clicks }}` would print the signal's function, so the generator warns about the uncalled forms (`$clicks`, `$page`, `$nav.currentPage`, ...) in interpolations, bindings, `@let` and control flow. In a comparison, such as `$page === 2` or `@if ($clicks > 1)`, it is a build error, because a signal is not a number. The other way round, `$nav.total`, `$nav.slides` and `$nav.isPresenter` are plain values, and `{{ $nav.total() }}` stops the generator with a message that says to drop the `()`. + +`$page()` is the number of the slide that is drawn, `$nav.currentPage()` the one on screen. They differ in thumbnails and in the PDF. `{{ clicks.current() }}` is the older spelling of `{{ $clicks() }}`. + ## Sharing state A slide has no component class of its own, so an event handler cannot assign a slide field: `(click)="x = 1"` does not compile. Call a method or set a signal of a template reference instead. To share state between parts of a slide, use template references or `@let`: diff --git a/src/content/guide/animations.md b/src/content/guide/animations.md index be32ef4..64f493a 100644 --- a/src/content/guide/animations.md +++ b/src/content/guide/animations.md @@ -16,9 +16,16 @@ Clicks reveal things one step at a time. Add `slide-ng-click` to any HTML elemen <div [slide-ng-click]="[2, 4]">Shows from click 2 until click 4, then hides</div> <div slide-ng-after>Same click as the element before it (a relative +0)</div> <div slide-ng-click="2" slide-ng-click-hide>Visible first, hides at click 2</div> +<div slide-ng-click-hide="3">Visible first, hides at click 3</div> +<div slide-ng-click.hide="2">Visible first, hides at click 2</div> +<div slide-ng-click="2" slide-ng-click-hide="3">Shows at click 2, hides at click 3</div> ``` -There are two forms because of how Angular reads attributes. `slide-ng-click="3"` and `slide-ng-click="+2"` are plain text, and slide.ng turns them into a number or a relative step. `[slide-ng-click]="[2, 4]"` is an Angular binding, for ranges and for any value you compute. +With a value on both, `slide-ng-click` is the click that shows the element and `slide-ng-click-hide` the click that hides it again, the same as `[slide-ng-click]="[2, 3]"`. With the bare `slide-ng-click-hide` flag, the `slide-ng-click` value is the click that hides it. These work as `v-click-hide` and `v-click.hide` do in Slidev. + +There are two forms because of how Angular reads attributes. `slide-ng-click="3"` and `slide-ng-click="+2"` are plain text, and slide.ng turns them into a number or a relative step. `[slide-ng-click]="[2, 4]"` is an Angular binding, for ranges and for any value you compute. A range written as plain text, `slide-ng-click="[2, 4]"`, is bound for you, so a deck copied from Slidev keeps its ranges. + +The generator warns, with the slide and its line, about values written as text that cannot work: `slide-ng-click="abc"`, `"0"` (clicks count from 1), a range with one value or whose end is not after its start (`[3, 1]`), and an `at="x"` of a `<slide-ng-switch>` child. It warns too about `clicks` and `clicksStart` that are not numbers or are negative. Values computed in a binding are checked by the app only. `slide-ng-after` and `+n` count from the relative counter, as in Slidev: the last plain or `+n` click before them, not an absolute `slide-ng-click="3"`. After only absolute clicks, `slide-ng-after` shows from the start, so give it a number too. @@ -35,15 +42,21 @@ To reveal list items one by one, wrap the list in `<slide-ng-clicks>`. Leave an As in Slidev's `v-clicks`, a single list gives each item a click, a single table each body row, and anything else (paragraphs, pictures, several lists) each child of `<slide-ng-clicks>`. -Options: `depth="2"` includes nested lists, `every="2"` shows two items per click and `every="0.5"` gives each item two clicks, `at="3"` sets the first click, `hide` makes the items hide instead of show. +Options: `depth="2"` includes nested lists, `every="2"` shows two items per click and `every="0.5"` gives each item two clicks, `at="3"` sets the first click, `hide` makes the items hide instead of show, and `animation="fade up"` gives every item a [click animation preset](../features/click-animations.md). `<slide-ng-switch>` shows one child per click (`at="3-5"` is clicks 3 and 4, the end is not included), and `<slide-ng-click-gap size="2" />` leaves two clicks empty. See [Directives](../built-in/directives.md) and [Components](../built-in/components.md). <ngmd-callout type="tip" title="Set the number of clicks yourself"> - `clicks: 3` in a slide's frontmatter fixes the number of clicks, whatever its elements ask for. `clicksStart` sets the first click number, `0` by default. + +`clicks: 3` in a slide's frontmatter fixes the number of clicks, whatever its elements ask for. `clicksStart` sets the first click number, `0` by default. A negative `clicksStart` counts as 0. + </ngmd-callout> -The URL carries the state: `/7?clicks=2` is slide 7 at click 2. +The URL carries the state: `/7?clicks=2` is slide 7 at click 2. A slide at its first click is just `/7`, as in Slidev; an address with `?clicks=0` still opens. + +## How a part comes in + +A part that waits for its click is hidden. `clickAnimation: up` in the headmatter or a slide's frontmatter, or a modifier such as `slide-ng-click.fade.right`, changes how it comes in, as Slidev's presets do. See [Click animations](../features/click-animations.md). ## Transitions @@ -65,7 +78,42 @@ transition: slide-left The duration is the CSS variable `--slidev-transition-duration` (0.5s). After a move, the class `slidev-nav-go-forward` or `slidev-nav-go-backward` is on the stage until the next move, so the UnoCSS variants `forward:` and `backward:` work. See [Direction variants](../features/direction-variants.md). -Custom transitions given as an object are not supported, only the named ones. +### Your own transition + +Any other name is a transition written in CSS, as in Slidev. During the move the entering slide gets Vue's classes `<name>-enter-from` and `<name>-enter-active`, a frame later `<name>-enter-to` instead of `-enter-from`, and the leaving slide the same with `leave`. Each slide's classes are removed when its CSS transition or animation ends. + +```css +/* styles/index.css */ +.my-fade-enter-active, +.my-fade-leave-active { + transition: opacity 0.6s ease; +} +.my-fade-enter-from, +.my-fade-leave-to { + opacity: 0; +} +``` + +```md +--- +transition: my-fade +--- +``` + +The object form takes other class names (`enterFromClass`, `enterActiveClass`, `enterToClass`, `leaveFromClass`, `leaveActiveClass`, `leaveToClass`) and a fixed `duration` in milliseconds, a number or `{ enter, leave }`: + +```md +--- +transition: + name: grow + enterFromClass: scale-50 + enterActiveClass: transition-transform duration-500 +--- +``` + +The built-in names keep their own animation unless the object names a class. `<slide-ng-switch transition="my-fade">` uses the same classes, see [Components](../built-in/components.md). + +When the system asks for less motion (`prefers-reduced-motion`), transitions run in almost no time and a fixed `duration` ends at once, so the slide changes straight away. ## More diff --git a/src/content/guide/drawing-recording.md b/src/content/guide/drawing-recording.md index fd64ea9..d0167dc 100644 --- a/src/content/guide/drawing-recording.md +++ b/src/content/guide/drawing-recording.md @@ -34,5 +34,7 @@ The camera is a round, draggable picture over the slides. Recording uses the bro The presenter page has a Screen Mirror switch that shows a shared window instead of the slide. <ngmd-callout type="info" title="Limits"> - There is no separate camera file, no mp4 or mkv, and no cloud upload. See [Recording](../features/recording.md). + +There is no separate camera file, no mp4 or mkv, and no cloud upload. See [Recording](../features/recording.md). + </ngmd-callout> diff --git a/src/content/guide/export.md b/src/content/guide/export.md index bbfd148..e74662b 100644 --- a/src/content/guide/export.md +++ b/src/content/guide/export.md @@ -26,6 +26,10 @@ Export opens the deck in Chromium. slide.ng never downloads a browser, so set `C In a PDF, a slide that has clicks shows its last state. `--with-clicks` makes a page for every click step. A `@defer` block that waits (`on timer`, `on viewport`) may still show its placeholder when the page is captured: give `export` a `--wait`. `<slide-ng-render-when context="print">` shows content only on paper, see [Components](../built-in/components.md). +## Links in a PDF + +A link to a slide, written in markdown (`[x](5)`) or as `<slide-ng-link>`, points at the first page of that slide in the PDF, so a click jumps there instead of opening the local server's address. It works only when the target slide is in the same file. With a `--range` that leaves the slide out, the link is no link. With `--per-slide` every link between slides is plain text, also one to the slide it is on, and the tags for screen readers are lost; the command says so. Links to web pages stay links. Other links to the deck's own site, such as `/file.pdf` or an `<a href="/3" target="_blank">`, are text without a link in the PDF, because they would point at the temporary server the export reads the deck from. + ## Choosing slides and looks ```sh @@ -58,8 +62,18 @@ slide-ng export --dist dist # export a build you already made slide-ng export --url http://localhost:4230/ # export a running server ``` +`--dist` opens the build with the router mode it was built with. `--lang` cannot change the language of an existing build or a running server, so it is refused with `--dist` and `--url`. + Without `--dist` or `--url`, export builds the deck first, in its own working copy, so it does not disturb a dev server that is running. If the page never goes idle, the export says so: try `--wait-until load`. +## Several decks + +```sh +slide-ng export a.md b.md # a-export.pdf and b-export.pdf +``` + +Each deck is built and exported in turn, to its own file named as for one deck. `--output`, `--dist` and `--url` name one place, so they are refused with several entries. `export-notes` takes several entries too. + ## Notes Without `--output` or `exportFilename`, the file is named after the deck: `slides.md` gives `slides-export.pdf`. `slide-ng export-notes` writes `slides-notes.pdf`. The full list of flags is in the [CLI reference](../built-in/cli.md). diff --git a/src/content/guide/hosting.md b/src/content/guide/hosting.md index 40f8d4c..083cb3f 100644 --- a/src/content/guide/hosting.md +++ b/src/content/guide/hosting.md @@ -14,17 +14,25 @@ slide-ng build --out public-html # another output folder slide-ng build --download # also writes the PDF and adds the download button slide-ng build --without-notes # speaker notes are not shipped slide-ng build --offline # a folder that opens from disk +slide-ng build a.md b.md --base /x/ # several decks: dist/a at /x/a/, dist/b at /x/b/ ``` ## Safe output folders The build empties its output folder. To make sure it never wipes something of yours, it only does so when the folder is new, empty, or has a `.slide-ng-build` file written by an earlier build. Any other folder with files in it is refused. The current folder, your home folder and `/` are always refused. +## Several decks + +With several entries each deck is built in turn into its own folder of `--out`, named after its file without `.md`, and served under that name below `--base`, as Slidev's multiple builds. The rest of `--out` is emptied, so it holds only these folders. Each folder is a whole site with its own `404.html` and `_redirects`. A host that serves one folder per site needs one site per deck, or a rule that sends `/x/a/*` to `/x/a/index.html`. With a deck at the root as well, the rule of each deck under the sub-path must come before the root's catch-all. + +Decks on one origin, such as these folders or the project sites of one GitHub Pages user, keep to themselves: the windows that follow each other, the drawings and the settings kept in the browser belong to the deck's base path. Only dark mode is shared. A deck at `/` keeps the names of 1.x; settings that a deck under a sub-path saved with 1.x are not read. + ## What is in the build | File | What for | | --- | --- | | `index.html`, `main-*.js`, `chunk-*.js`, `styles-*.css` | The app. Monaco, Mermaid and the like are separate chunks, fetched only when a slide uses them. | +| `inline-*.js` | The inline scripts of `index.html`, as files, see [Content-Security-Policy](#content-security-policy). | | `404.html` | A copy of `index.html`. GitHub Pages shows it for unknown paths, so `/talk/3` opens the app. | | `_redirects` | The same rule for Netlify. | | `<exportFilename>.pdf` | Only with `--download` or `download: true`. With `download: true` alone, a build without a working Chromium warns and leaves it out. | @@ -40,6 +48,8 @@ The deck lives at the base. Every slide is `<base><number>` in history mode, the - History mode needs the host to answer unknown paths with `index.html`. `404.html`, `_redirects` and the examples below do that. - Hash mode needs nothing from the host. Use it where you cannot change the server, such as a plain file share. +- Memory mode, `routerMode: memory`, never changes the address, for a kiosk or a deck inside another page. See [Slide addresses](../features/routing.md#memory-mode). +- `--router-mode` picks the mode for one build, without changing the headmatter. - `--base` is a path such as `/talk/`, not a full address. Build for the prefix you will serve under. ## GitHub Pages @@ -109,6 +119,28 @@ server { Under a sub-path build with `--base /talk/`, put the files in `/usr/share/nginx/html/talk/` and use `try_files $uri $uri/ /talk/index.html;`. +## Content-Security-Policy + +`index.html` has no inline script. The few lines Angular adds there, and the inline scripts of the deck's own [`index.html`](../features/seo-meta.md#your-own-index-html), are written to `inline-*.js` files next to it. A `type="module"` script stays a module, and other attributes such as `data-cfasync="false"` are kept. Data blocks (`application/ld+json`) stay inline. An inline import map or `speculationrules` cannot be a file, so the build leaves it and warns. A host that sends a strict policy therefore runs the deck, as long as styles may be inline: + +``` +Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self' data: +``` + +Add what the slides load from elsewhere: `img-src https:` for remote pictures, `frame-src` for `<slide-ng-youtube>` and other frames, `font-src` and `style-src` for a web font provider. Only `slide-ng build` moves the scripts, not `--offline`. Trusted Types are not supported: with them Mermaid diagrams are not drawn. + +## Embedding the deck in another page + +A built deck works in an `<iframe>`. Give the frame `allow="fullscreen"`, or the fullscreen button is left out: + +```html +<iframe src="https://you.github.io/talk/" allow="fullscreen" width="960" height="540"></iframe> +``` + +The wake lock, which keeps the screen from going to sleep, is on by default. In a frame the deck asks for it only once the reader clicks, taps or presses a key in the deck, or makes it full screen, so a reader who only scrolls past it is not kept awake. To let an embedded deck keep the screen awake, add `screen-wake-lock` to `allow`. For a deck that is mostly shown embedded, `wakeLock: false` in the headmatter turns it off. + +A host that sends `frame-ancestors` or `X-Frame-Options` must allow the page that embeds the deck. `routerMode: memory` keeps the address of a deck in a frame unchanged. + ## Size A deck without heavy features is about 450 kB at startup, about 110 kB compressed. Decks that use Monaco or KaTeX add their style sheets to that. Every slide is in the first bundle, and highlighted code compiles to a lot of markup, so a long workshop with code on every slide can pass 3 MB. The build warns above 6 MB at startup and fails only above 100 MB. Monaco, Mermaid and Magic Move are lazy chunks and do not count. diff --git a/src/content/guide/layouts.md b/src/content/guide/layouts.md index fb36b2c..b595e04 100644 --- a/src/content/guide/layouts.md +++ b/src/content/guide/layouts.md @@ -18,7 +18,7 @@ image: /photo.png Text next to the picture. ``` -slide.ng has all the layouts of Slidev, with the same names and props, plus `404` and `error`. The first slide is `cover` and every other slide is `default` unless you say otherwise. An unknown name prints a warning that lists the real names and uses `default`. +slide.ng has all the layouts of Slidev, with the same names and props, plus `404` and `error`. The first slide is `cover` and every other slide is `default` unless you say otherwise. An unknown name prints a warning that names the nearest layout, lists the real names and uses `default`. <ngmd-image src="/images/layouts/two-cols.png" alt="A slide with two columns" caption="The two-cols layout" width="480"></ngmd-image> diff --git a/src/content/guide/offline.md b/src/content/guide/offline.md index 2702c23..d516cb8 100644 --- a/src/content/guide/offline.md +++ b/src/content/guide/offline.md @@ -24,7 +24,7 @@ Copy `talk-offline` to the presenting machine, or to a USB stick, and open `talk What the flag does: - One classic script holds the app and all its lazy chunks (Mermaid, Monaco, KaTeX). Chromium does not run module scripts or `import()` from `file://`, so a normal build shows a blank page there. -- Addresses are relative and there is no `<base>`, so the folder works from any place on disk. The slide is after a `#`, as in `index.html#/3`, and `routerMode` is forced to `hash`. +- Addresses are relative and there is no `<base>`, so the folder works from any place on disk. The slide is after a `#`, as in `index.html#/3`, and `routerMode` is forced to `hash`: `--router-mode memory` is refused, and a headmatter `routerMode: memory` is replaced with a warning. A `<script type="module" src>` in the deck's own `index.html` does not run there; use a plain `<script>`. - There is no service worker, because browsers do not allow one on `file://`. `pwa` is ignored. - Pictures from other servers are downloaded into the folder, unless the deck says `remoteAssets: false`. - Web fonts are downloaded into the folder, unless the deck says `fonts: { selfHost: false }`. diff --git a/src/content/guide/presenter-mode.md b/src/content/guide/presenter-mode.md index b0db9b7..d2800c8 100644 --- a/src/content/guide/presenter-mode.md +++ b/src/content/guide/presenter-mode.md @@ -9,7 +9,7 @@ Open `/presenter` or press the presenter button in the bar. The page shows the c <ngmd-image src="/images/ui/presenter-layout-1.png" alt="The presenter page: the current slide, the next step and the notes" caption="The presenter page, first layout" width="640"></ngmd-image> -The audience page, for example `/3`, in another window or on another device follows the presenter. `/notes` shows only the notes and follows too. A layout button cycles through three arrangements of the presenter page. +The audience page, for example `/3`, in another window or on another device follows the presenter. `/notes` shows only the notes and follows too. A layout button cycles through three arrangements of the presenter page. `/overview` lists every slide with its notes, and `/entry` links to all these pages, see [Overview and notes pages](../features/overview-pages.md). ## Notes @@ -25,8 +25,52 @@ Say hello. --> ``` +Drag the borders of the notes pane to change its width and height. Each layout keeps its own sizes in the browser, from 240 to 720 pixels wide (at most 70% of the page) and at least 160 pixels high (at most 75%). The borders are Tab stops too: the arrow keys move one by 16 pixels, 64 with Shift, and Home and End give the notes their smallest and biggest size. Layout 1 on a tall, narrow screen has no borders to drag. + +While the dev server runs, **Edit Notes** (the pencil next to the font size buttons) turns the notes into a text box. What you type is saved as the slide's note in the markdown, and only the note, half a second after you stop or at once with Ctrl or Cmd + S. Escape, a click outside or the pencil ends editing, and Escape gives the focus back to the pencil. The bar's editor button shows the [side editor](../features/side-editor.md) in the notes pane. Neither is in a build. + `slide-ng export-notes` makes a PDF of the notes. See [Speaker notes](../features/speaker-notes.md). +## Click preview + +The eye button next to the layout button, or `u`, turns on a click preview of the current slide. The preview is on the presenter page only. The audience page, `/notes` and the next-step pane do not change. + +- Parts that are already shown look as usual. +- The part of the next click is brighter and has a dashed outline. +- The parts of later clicks are greyed out. +- Each part that a later click changes has a badge with the number of that click. + +Click a badge or a greyed-out part to go to that click. Every window follows. A link or button inside a greyed-out part is not run; parts already shown keep their own clicks. While you draw, the pen gets the click and the slide does not move. The choice is remembered in the browser, under the key `slide-ng-presenter-click-preview`. The shortcut is named `toggle_click_preview`, see [Shortcuts](../built-in/shortcuts.md#change-the-keys). + +- `slide-ng-click`, `slide-ng-after`, ranges, `<slide-ng-clicks>` items and the output of `{monaco-run}` are greyed out until their click. A range that is on screen gets a second badge where it ends. +- A part that a click hides (`slide-ng-click-hide`) stays as it is until it goes. Its badge has a struck-through number, and as the next click its outline is dotted, not dashed. +- A part with a [click animation preset](../features/click-animations.md) is drawn at its place and size, and always at the ghost opacity, also with `fade`. +- A fractional click such as `slide-ng-click="1.5"` gets the badge of the whole click it shows at, 2. +- In a right-to-left deck the badge sits at the right edge of the part. + +A theme or a deck can change the look with CSS variables: + +| Variable | Default | What it sets | +| --- | --- | --- | +| `--slide-ng-ghost-opacity` | `0.47`, `0.4` in dark mode | Opacity of the parts of later clicks, about 3:1 for the default text colours | +| `--slide-ng-ghost-next-opacity` | `0.75` | Opacity of the part of the next click | +| `--slide-ng-ghost-next` | unset: the theme's primary colour | Colour of the outline and the badge of the next click. A `themeConfig.primary` counts too. | +| `--slide-ng-ghost-badge-background` | `#6b7280` | Background of the other badges | +| `--slide-ng-ghost-badge-foreground` | `white` | Text of the badges | + +The parts carry the classes `slide-ng-click-ahead` and, for the next click, `slide-ng-click-next` (`slide-ng-click-hides` for a part that goes away). The click is in `data-slide-ng-click-at`. + +Screen readers skip the greyed-out parts, since the next-step pane already shows the next click. What a greyed-out part holds is inert: its links, buttons and fields are no Tab stops until the part shows. The keyboard way is the badges. Each badge is a button named "Go to click N", 24 by 24 pixels at any size of the slide, and after Enter on one the focus moves to the badge of the click after it. + +The preview has limits: + +- Content inside `@if (clicks.current() > 2) { ... }` is not on the page until its click, so it has no greyed-out part and no badge. Use `slide-ng-click` for content the preview should show. +- A `<slide-ng-switch>` shows only its current child. The badge on the switch says when it changes. +- Code line steps, Magic Move, `slide-ng-mark` and `slide-ng-motion` show their state at the current click, not their later state. A badge says when they change next. +- The notes PDF has no click preview. + +Slidev has no click preview, see [What differs from Slidev](../migrating/from-slidev.md#added-in-slide-ng). + ## Timer ```md @@ -36,7 +80,7 @@ timer: countdown --- ``` -`duration` takes `30min`, `1h30min`, `90s`, or a number of minutes. `timer` is `stopwatch` (the default) or `countdown`. A running timer survives a reload of the presenter page. See [Timer](../features/timer.md). +`duration` takes `30min`, `1h30min`, `90s`, or a number of minutes. `timer` is `stopwatch` (the default) or `countdown`. Every window and device shows the same time, and a reload of the presenter page keeps it. See [Timer](../features/timer.md). ## Cursor and laser @@ -46,7 +90,24 @@ The presenter's mouse is shown on the audience page as a pointer or a red laser Windows of one browser follow each other with no server, over a `BroadcastChannel`. Other devices need the dev server with `--remote`, see [Remote access](../features/remote-access.md). There, the presenter and notes pages need the password; without it they go to the slides. A window or device that joins later catches up with the presenter, timer included. -Every window follows the presenter. Slidev's sync mode menu (send only, receive only, off) is not there. +An audience page that opens or reloads while a presenter page is open goes to the presenter's slide and click, once that presenter has moved. A presenter page that only opened does not move the audience: an audience page at the start of the deck joins it, and one opened at another slide keeps that slide until a presenter moves. + +Use one presenter page. Two presenter pages do not follow each other: each keeps its own slide, and the audience follows the one that moved last, also when the devices' clocks are not set alike. To use a phone as the remote, open the presenter page on the phone and only the audience page on the laptop that drives the projector. + +### Sync mode + +The sync button in the bar (up and down arrows, before the settings) opens Slidev's sync mode menu: + +| Mode | What the window does | +| --- | --- | +| Bidirectional Sync | Sends its moves and follows the others. The default. | +| Receive Only | Follows the others, and its own moves stay on this screen. | +| Send Only | Sends its moves, and does not follow. | +| Disable | Neither. Use it to look around on your own. | + +The choice is kept for each role, viewer or presenter, in the browser (`slidev-sync-directions`), and applies to every window of that role in the browser. A viewer on another device only follows, so there Bidirectional Sync and Send Only are disabled, with a note that says why. Opening the menu puts the focus on the ticked mode. The arrow keys move between the modes, and Escape closes the menu and gives the focus back to the button. + +Several decks on one site, such as the decks of `slide-ng build a.md b.md` or the project sites of one GitHub Pages user, do not follow each other: the sync channel and the stored settings belong to the deck's base path. Only dark mode is shared. ## Turning it off diff --git a/src/content/guide/styling.md b/src/content/guide/styling.md index d8c3709..89a5f8a 100644 --- a/src/content/guide/styling.md +++ b/src/content/guide/styling.md @@ -5,7 +5,7 @@ description: Style slides with UnoCSS utility classes, dark mode variants and pl # Styling -slide.ng uses *UnoCSS with a Tailwind-like preset. Write utility classes in your HTML and in the `class:` frontmatter key. The CSS for the classes you use is built automatically, and only for them. +slide.ng uses *UnoCSS with a Tailwind-like preset. Write utility classes in your HTML and in the `class:` frontmatter key. The CSS for the classes you use is built automatically, and only for them. Attributify (`<div text="sm red-500">`), variant groups (`hover:(bg-blue-500 text-white)`), the `prose` typography and directives such as `@apply` work as in Slidev, see [UnoCSS](../features/unocss.md). ```md --- @@ -36,10 +36,10 @@ Dark mode follows the system until the viewer presses `d` or the sun and moon bu A `<style>` block applies to its slide only. See [Scoped styles](../features/scoped-styles.md). For CSS that applies to every slide, a `styles/index.css` in a theme, addon or the deck's folder is added after the app's styles. -`@apply` does nothing in either, because there is no UnoCSS directive transformer. The generator warns and names the slide or the file. Put the classes on the element, or write the CSS out. +UnoCSS's directives work in both: `@apply`, `--uno:`, `@screen` and `theme()`, see [UnoCSS](../features/unocss.md#directives). The core styles follow Slidev's: code is 12px (`--slidev-code-font-size`), and the paragraph right after a heading is dimmed (`h1 + p`), also when it holds only a component. A raw `<pre>` is not styled. ## Shortcuts and icons -slide.ng defines a few of Slidev's utility shortcuts, such as `bg-main`, `text-primary` and `border-main`. Icons are classes or tags, see [Icons](../features/icons.md). A theme or deck can add its own UnoCSS settings in `uno.config.mjs`, see [Directory structure](../customize/directory-structure.md). +slide.ng defines a few of Slidev's utility shortcuts, such as `bg-main`, `text-primary` and `border-main`. The `primary` ones take no opacity, so `bg-primary/10` does nothing. `themeConfig: { primary: '#5d8392' }` in the headmatter changes the colour of `text-primary`, see [Theme settings](../customize/themes.md#theme-settings). Icons are classes or tags, see [Icons](../features/icons.md). A theme or deck can add its own UnoCSS settings in `uno.config.mjs`, see [Directory structure](../customize/directory-structure.md). diff --git a/src/content/guide/syntax.md b/src/content/guide/syntax.md index 3d887c4..b522ea9 100644 --- a/src/content/guide/syntax.md +++ b/src/content/guide/syntax.md @@ -42,6 +42,28 @@ The last HTML comment of a slide is its speaker note. It is stored with the slid <!-- This text is a note. --> ``` +## Links + +As in Slidev, a markdown link to a slide moves inside the deck, and a link to a web page opens a new tab: + +```md +[go to slide 5](5), or [5](/5), [5](./5), [5](#/5) +[pricing](/pricing) goes to the slide with routeAlias: pricing +[results](#results) goes to a heading of this slide +[the docs](https://example.org) opens in a new tab +``` + +- An address of only digits, or `/`, `./` or `#/` followed by a slide number or a `routeAlias`, becomes a `<slide-ng-link>`. A click moves to that slide without loading the page again, with history or hash routing, a base or an offline folder. Ctrl or Cmd + click opens it in a new tab. A number past the last slide goes to the last slide. An alias may be any name, also `./café` or `./My%20Alias`. +- Other addresses that start with `/` or `./`, such as `/file.pdf` or a name that is no `routeAlias`, stay plain links. +- The generator warns, naming the slide, about a number past the last slide (`[far](99)`), a `./name` or `#/name` that is no `routeAlias`, and a link to the alias of a slide with `hide:`. A `/name` is not warned about, since it may be a page of the site. +- `#id` and `./#id` scroll to that element of the slide. `#5` is such an anchor too: write `5` or `#/5` for slide 5. +- An `http:` or `https:` address gets `target="_blank" rel="noopener"`. `mailto:` and other links stay as they are. To open a web page in the same tab, write the link as HTML: `<a href="https://example.com">x</a>` is left as written. +- An `<a>` written as HTML whose `href` is a slide (`<a href="/3">`, also `./3`, `#/3` or an alias) becomes a `<slide-ng-link>` too. It keeps its `class`, `id` and `style`. With other attributes, such as `target`, it is left as written, with a warning that names its line, because it does not reach the slide with hash routing, a base or offline. `<slide-ng-link to="4">` always works. +- In speaker notes a web page opens a new tab too. A link to a slide moves the presenter to that slide, see [Speaker notes](../features/speaker-notes.md). +- In an exported PDF a link to a slide points at that slide's page, see [Export](./export.md#links-in-a-pdf). + +The differences with Slidev are on the [differences page](../migrating/from-slidev.md). + ## Slots A line with only `::name::` starts a named part of the slide, which a layout can place somewhere: @@ -76,6 +98,15 @@ Fenced code is highlighted while the deck is generated. Math uses `$...$` and `$ Interpolation, control flow and your own components work in a slide. Read [Angular in markdown](./angular-templates.md) for the few rules that keep the markdown converter from breaking your template. +## Errors and warnings + +Messages start with the file and line they are about (`slides.md:12: ...`), or with the slide (`[slides] slide 4: ...`). A warning keeps the deck running, and an error stops the build. In the dev server a red banner or Angular's error overlay shows the message, the same after a reload as in the terminal, and the last good slides stay on screen until the file is fixed. + +- A mistyped name gets the nearest real one: `layout: image-rigth` gets "Did you mean "image-right"?", and so do `<slide-ng-tocc />`, `slide-ng-clik` and a component tag one or two letters away from one of yours. Any unknown `slide-ng-*` name is a warning. +- Angular's own errors name the slide and the line of the markdown they are on. +- An element left open, such as a `<div>` without `</div>`, a closing tag with another name, or a closing tag that closes nothing, stops the build with a message that names the slide and the tag. +- The usual YAML slips in a frontmatter are said in plain words, with the line: a value with `: ` in it (quote it, `title: "Part 2: the plan"`), a tab (YAML needs spaces), a quote that is never closed, and a missing closing `---`. A heading that became a YAML comment because the closing `---` is missing gets a warning too. + ## Formatting the file `slide-ng format` tidies spacing and rewrites the frontmatter as clean YAML. See [Formatting](../features/format.md). diff --git a/src/content/migrating/from-slidev.md b/src/content/migrating/from-slidev.md index 5639ec2..4615d4a 100644 --- a/src/content/migrating/from-slidev.md +++ b/src/content/migrating/from-slidev.md @@ -16,6 +16,12 @@ slide.ng reads the same markdown file format as *Slidev, with the same slide spl - Most headmatter keys, see [Headmatter and frontmatter](../built-in/headmatter.md), and most of the command line. - The same keyboard shortcuts, the presenter view, drawing and recording. +## Added in slide.ng + +- A click preview on the presenter page: the eye button or `u` shows what the later clicks of the current slide show, greyed out, and a click on a part goes to its click. See [Presenter mode](../guide/presenter-mode.md#click-preview). +- A progress bar on the slides, `progressBar` or `<slide-ng-progress-bar />`. Slidev has one on its presenter page only. See [Progress bar](../features/progress-bar.md). +- A built-in Turkish interface and `locales/<lang>.json` to translate the interface. See [Interface language](../features/interface-language.md). + ## Syntax differences | In Slidev | In slide.ng | @@ -23,20 +29,23 @@ slide.ng reads the same markdown file format as *Slidev, with the same slide spl | Vue template syntax: `{{ x }}`, `v-if`, `v-for`, `v-bind` | Angular template syntax: `{{ x }}`, `@if`, `@for`, `@switch`, `@let`, `[prop]="x"`, `(event)="y"` | | `<script setup>` for state | No equivalent. State lives in components, and parts of a slide share state with `#ref` and `@let` | | `v-click`, `v-after`, `v-click-hide`, `<v-clicks>`, `<v-click-gap>`, `<v-switch>`, `v-mark`, `v-motion`, `v-drag`, `<v-drag>`, `<v-drag-arrow>` | The same name with the `slide-ng-` prefix: `slide-ng-click`, `slide-ng-after`, `slide-ng-click-hide`, `<slide-ng-clicks>`, `<slide-ng-click-gap>`, `<slide-ng-switch>`, `slide-ng-mark`, `slide-ng-motion`, `slide-ng-drag`, `<slide-ng-drag>`, `<slide-ng-drag-arrow>`. The old names stop the generator | -| `v-click="[2, 4]"` | `[slide-ng-click]="[2, 4]"`, because Angular reads a plain attribute as text. `slide-ng-click="3"` and `"+2"` are fine as they are | -| PascalCase components: `<Toc />`, `<Link>`, `<Youtube>`, `<SlidevVideo>`, `<AutoFitText>`, `<LightOrDark>`, `<RenderWhen>`, `<SlideCurrentNo>`, `<SlidesTotal>`, `<Transform>`, `<Arrow>`, `<VDragArrow>` | kebab-case tags with the `slide-ng-` prefix: `<slide-ng-toc />`, `<slide-ng-link>`, `<slide-ng-youtube>`, `<slide-ng-video>`, `<slide-ng-auto-fit-text>`, `<slide-ng-light-or-dark>`, `<slide-ng-render-when>`, `<slide-ng-current-no />`, `<slide-ng-total />`, `<slide-ng-transform>`, `<slide-ng-arrow>`, `<slide-ng-drag-arrow>` | -| `$slidev`, `$nav`, `$clicks`, `$page` | Angular injection: `Nav`, `ClicksContext`, `SLIDE_NO`, `RENDER_CONTEXT` from `slide-ng/lib/...`. There is no global in markdown: a small component exposes the value and a `#ref` reads it | -| Slide hooks `onSlideEnter`, `onSlideLeave` | Not available. A component can inject `Nav` or `ClicksContext` and use signals | +| `v-click="[2, 4]"` | `slide-ng-click="[2, 4]"` is bound for you, and `[slide-ng-click]="[2, 4]"` is the Angular binding. `slide-ng-click="3"` and `"+2"` are fine as they are | +| PascalCase components: `<Toc />`, `<TitleRenderer />`, `<Link>`, `<Youtube>`, `<SlidevVideo>`, `<AutoFitText>`, `<LightOrDark>`, `<RenderWhen>`, `<SlideCurrentNo>`, `<SlidesTotal>`, `<Transform>`, `<Arrow>`, `<VDragArrow>` | kebab-case tags with the `slide-ng-` prefix: `<slide-ng-toc />`, `<slide-ng-title-renderer />`, `<slide-ng-link>`, `<slide-ng-youtube>`, `<slide-ng-video>`, `<slide-ng-auto-fit-text>`, `<slide-ng-light-or-dark>`, `<slide-ng-render-when>`, `<slide-ng-current-no />`, `<slide-ng-total />`, `<slide-ng-transform>`, `<slide-ng-arrow>`, `<slide-ng-drag-arrow>` | +| `{{ $clicks }}`, `{{ $page }}`, `$nav.currentPage` | The same names, but the values that change are Angular signals, so they are called: `{{ $clicks() }}`, `{{ $page() }}`, `$nav.currentPage()`. The generator warns about the uncalled forms. `$frontmatter`, `$slidev.configs` and `$slidev.themeConfigs` are read as they are. See [Angular in markdown](../guide/angular-templates.md#the-global-context) | +| `{{ $slidev.themeConfigs.primary }}` | The same, or `{{ themeConfig['primary'] }}`. The CSS variables `--slidev-theme-<key>` are the same | +| Slide hooks `onSlideEnter`, `onSlideLeave`, `useIsSlideActive` | `onSlideEnter`, `onSlideLeave` and `isSlideActive()` from `slide-ng/lib/slide-hooks`, called in a component. See [Layouts and components](../customize/layouts-components.md#slide-hooks) | | Your own components in `components/`, written in Vue | Standalone Angular components in `components/`, found by their `selector` | | Layouts in `layouts/*.vue` | Layouts in `layouts/*.ts`, see [Layouts and components](../customize/layouts-components.md) | | UnoCSS config in `uno.config.ts` | `uno.config.mjs` | | `v-pre` for a literal `{{ }}` | `ngNonBindable` on the element, or `\{\{` or `{{` in text | | `<template #1>` children of `<v-switch>`, `<template #dark>` of `<LightOrDark>` | `<div at="1">` children of `<slide-ng-switch>`, and `<div dark>` or `<div light>` in `<slide-ng-light-or-dark>`. A `<template #...>` slot renders nothing, and the generator reports it | | A named slot of your own component, `<template #title>` | An element with an attribute, `<span slot-title>`, and `<ng-content select="[slot-title]" />` in the component | -| `@click="$slidev.nav.next"` | A small component that injects `Nav` and calls `nav.next()` | +| `@click="$slidev.nav.next"` | `(click)="$nav.next()"` | | Self-closing HTML tags, `<div />` | `<div></div>`. Angular stops with NG5002 otherwise. Components, `<img />` and `<br />` are fine | -| `@apply` in CSS | Does nothing, and the generator warns. Write the CSS out, or put the classes on the elements | | Windi's comma in arbitrary values, `grid-cols-[80px,1fr]` | `_` as in UnoCSS, `grid-cols-[80px_1fr]` | +| Any `/x` or `./x` markdown link is a link inside the deck | Only a slide number or a `routeAlias` is: `[x](5)`, `/5`, `./5`, `#/5`, `/alias`. Other addresses, such as `/file.pdf`, stay plain links. See [Links](../guide/syntax.md#links) | +| `[x](#5)` goes to slide 5 | `#5` is an anchor on the slide. Write `5` or `#/5` | +| Every link that is not a slide opens a new tab | `http` and `https` links do. `mailto:` and the like stay as they are | `{{ }}` looks the same in both, but the expression inside is an Angular expression, not a JavaScript one with Vue's globals. An event handler cannot assign a slide field, because a slide has no class. `@if` and `@for` lines must be on their own line, see [Angular in markdown](../guide/angular-templates.md). @@ -48,7 +57,6 @@ slide.ng reads the same markdown file format as *Slidev, with the same slide spl | `slidev build --base /talk/` | `slide-ng build --base /talk/` | | `slidev dev --base` | Refused. The dev server always runs at `/` | | `slidev --tunnel` | Refused. Use `--remote` on your network | -| `slidev export` with several entries | One entry per run | | `slidev export --format pptx-editable` | Not made. `pptx` holds pictures and notes | | `npm init slidev` | `slide-ng create <folder>` | @@ -66,7 +74,7 @@ Slidev's setup files use `define...Setup` helpers and get Vue and Vite objects. 1. Run `slide-ng` on the deck and read the warnings. Unknown layouts, a missing theme and unknown slots are all reported. 2. Replace `v-if`, `v-for` and `<script setup>` with Angular's control flow and with components. -3. Rename component tags to kebab-case with the `slide-ng-` prefix, `v-click` and the other `v-` names to `slide-ng-click` and so on, and `v-click` ranges to `[slide-ng-click]`. Search for `v-click`, `v-after`, `v-mark`, `v-motion`, `v-drag` and `<v-`. +3. Rename component tags to kebab-case with the `slide-ng-` prefix, and `v-click` and the other `v-` names to `slide-ng-click` and so on. The generator stops on the old names, Slidev's PascalCase components (`<Toc />`, `<VClicks>`, `<Youtube>`, ...) included, and lists every one of them with its new name in one run. 4. Replace the theme with an official one, or write one. A Slidev theme name such as `seriph` is looked up as `@slide-ng/theme-seriph` and stops the build when that is not installed. 5. Pictures next to `slides.md` (`./images/x.png`) in a slide's markup are copied into the build. Move the ones that components, CSS `url()` and the frontmatter name into `public/`. 6. Put each component file directly in `components/`. Sub-folders are not read. diff --git a/src/content/migrating/not-supported.md b/src/content/migrating/not-supported.md index 4f03a76..975f9cb 100644 --- a/src/content/migrating/not-supported.md +++ b/src/content/migrating/not-supported.md @@ -13,32 +13,28 @@ This list is taken from the parity table that is kept with the slide.ng source, - Comark or MDC syntax (`comark: true`, `mdc: true`). The generator warns, and the syntax stays text. - `:emoji:` shortcodes. Type the emoji itself. - `v-pre`. Use Angular's `ngNonBindable`, or write `{{` for a literal `{{`. -- `@apply` and UnoCSS's directive transformer. The generator warns. -- Slidev's sync mode menu (send only, receive only, off). Windows and devices always follow the presenter. -- Slide hooks `onSlideEnter`, `onSlideLeave` and `useIsSlideActive`. -- Custom transitions given as an object, `clickAnimation`, `preloadImages` and slide `preload`. -- `selectable`, `themeConfig`, `notesAutoRuby` and `TitleRenderer`. Slide titles are plain text, except `$math$`, which is drawn in the table of contents. -- `dev --base`, `dev --tunnel`, `dev --inspect` and `build --router-mode`. -- `export` with several entries, and `export --format pptx-editable`. +- `selectable`. +- `dev --base`, `dev --tunnel` and `dev --inspect`. +- `export --format pptx-editable`. - A Prettier plugin, a converter from Vue syntax, `overviewSnapshots` and the browser exporter page. These are left out on purpose. - A released VS Code extension. A first version is in the slide-ng/vscode-extension repository; it is not released and was not run inside VS Code. - Theme and addon galleries, and community themes. -- `memory` router mode and `setup/routes.ts`. - `setup/vite.config.ts` and `setup/vue.ts`, which are about Vite and Vue. - Monaco type loading (`monacoTypesSource`), markdown-it plugins in transformers, and a run context for code runners. -- The `~icon~` title syntax and automatic file icons of code groups. - `mhchem` in KaTeX. ## Only in part - `slide-ng-motion`: CSS transitions between states, no spring physics or presets. - Recording: no separate camera file, no mp4 or mkv, no cloud upload. -- `<youtube>`: no start time or playlist options. +- Links to slides: in a PDF made with `--per-slide`, or with a `--range` that leaves the slide out, a link to another slide is no link. A link to a slide in the notes moves the presenter page only; in `/notes` it opens the address. +- Markdown links: only a slide number or a `routeAlias` becomes a link inside the deck, and only `http` and `https` links open a new tab. See [What differs from Slidev](./from-slidev.md#syntax-differences). +- The interface language: Turkish is the only built-in translation, and browser error messages and the generator's warnings stay English. See [Interface language](../features/interface-language.md). +- `routerMode: memory`: only the print pages and a slide link's `#/<number>` are read from the address, so the presenter and the other pages of the app cannot be opened by address. +- `setup/routes.ts`: a page of your own is not behind the `--remote` password, and the build cannot warn when its path is also a `routeAlias`. - `fonts`: loaded from the provider like Slidev's, plus `selfHost`, which is tested against a fake provider only. -- Magic Move: `magicMoveDuration` and `magicMoveCopy` are read, the other `shiki.magicMove` options are not. - `colorSchema`: `auto`, `light` and `dark` work, `all` does not. - Touch: swiping works, but pinch zoom and touch tuning for drawing and `slide-ng-drag` were not tried on a real device. -- Keyboard and screen reader use of the menus: Escape does not always give focus back, the overview moves a highlight instead of the focus, and the context menu has no arrow keys. - `addons`: resolution and merge order are tested, but no deck was built with an addon end to end. - Raw HTML: HTML and `<style>` work, `<script setup>` has no equivalent. diff --git a/src/content/migrating/upgrading-to-2.md b/src/content/migrating/upgrading-to-2.md new file mode 100644 index 0000000..7f2ff81 --- /dev/null +++ b/src/content/migrating/upgrading-to-2.md @@ -0,0 +1,80 @@ +--- +title: Upgrading from 1.x to 2.0 +description: How to move a deck from slide.ng 1.x to 2.0, and the changes that can alter a deck that 1.x showed correctly. +--- + +# Upgrading from 1.x to 2.0 + +slide.ng 2.0 needs the same Node (22.22.3 or newer) and the same Angular (22.2) as 1.x. Update the package in the deck's folder: + +```sh +npm install -D @slide-ng/core@^2.0.0 +npx slide-ng dev +``` + +Then read the warnings in the terminal and step through the deck once. Most decks need no change. The list below is what can alter a deck that 1.x showed correctly, and how to keep the old behaviour. + +## Changes that can alter a deck + +### Addresses and links + +- `routeAlias: overview`, `entry` or `notes-edit` stops `build` and `dev`: they are pages of the app now. Rename the alias. A markdown link `[x](/overview)` opens the app's [overview page](../features/overview-pages.md) instead of that slide, with a full page load. +- A `routeAlias` that is a number, that is empty, that has `/`, `.`, `?`, `#` or `\`, or that is on two slides stops the build, naming the slides. +- Markdown links to `http` and `https` addresses open in a new tab. To keep one in the same tab, write it as HTML: `<a href="https://example.com">x</a>` is left as written. +- An `<a href="/3">` written as HTML with attributes other than `class`, `id` or `style`, such as `target`, is left as written, with a warning. In a `routerMode: hash` deck it loads `/3` from the server. Write a markdown link, `[x](3)`, or `<slide-ng-link to="3">`. A plain `<a href="/3">` goes to the slide, as in 1.x. +- The address leaves out `?clicks=0`: slide 2 at its first click is `/2`. An address with `?clicks=0` still opens. + +See [Links](../guide/syntax.md#links) and [Slide addresses](../features/routing.md). + +### Language + +- `lang` now also sets the locale of the `date`, `number` and `currency` pipes: `lang: en-GB` writes "30 September 2026", and `pt_br` writes `1.234.567,891`. Give a pipe its own locale argument to keep the old format, such as `{{ day | date: 'mediumDate' : '' : 'en-US' }}`. +- `<html lang>` is written in the usual spelling: `lang: pt_br` gives `pt-BR`. +- `lang` picks the language of the interface too. See [Interface language](../features/interface-language.md). + +### Styling + +- Attributify reads plain HTML attributes as utilities: `<table border="1">` gets a border, `<span font="bold">` is bold, `<div m="4">` gets a margin. `unocss: { attributify: false }` in the headmatter turns it off. See [UnoCSS](../features/unocss.md). +- `themeConfig` is applied as CSS variables, `--slidev-theme-<key>`, so a `themeConfig.primary` recolours `text-primary`, the progress bar and the controls. See [Theme settings](../customize/themes.md#theme-settings). +- The speaker notes get the typography preset's `prose` styles: body colour and spacing. + +### Files next to the deck + +- An `index.html` next to `slides.md` is merged into the page: its `<title>`, `<meta>` and scripts now count, and what its `<body>` holds is added to the page after the slides, out of sight but read by screen readers. Rename it if it was not meant for the deck. See [Your own index.html](../features/seo-meta.md#your-own-index-html). + +### Clicks and code + +- `slide-ng-click.hide` and `slide-ng-after.hide` hide the element at its click instead of being ignored. That click counts, so the slide has one more click, and `export --with-clicks` one more page. +- `slide-ng-click-hide="3"` hides at click 3 instead of at the next click, and `slide-ng-click="2" slide-ng-click-hide="3"` shows at click 2 and hides at click 3. +- A click range written as text, `slide-ng-click="[2, 4]"`, is a range. In 1.x it was ignored and the element was always visible. +- `{monaco-run}` code runs in strict mode: assigning a name that was never declared stops with a `ReferenceError`. Declare it, or set `monacoRunUseStrict: false`. + +See [Clicks and transitions](../guide/animations.md) and [Running code](../features/monaco-run.md). + +### Loading and the screen + +- Pictures of the next slides are loaded ahead. `preloadImages: false` turns it off. See [Loading pictures ahead](../features/preload-images.md). +- The screen wake lock is on by default. `wakeLock: false` turns it off; a choice already stored in the browser is kept, so a viewer who used 1.x may have to turn it on once in the settings menu. + +### Builds and the browser + +- Several decks on one origin, such as the project sites of one GitHub Pages user, no longer share their slides, drawings and stored settings: they belong to the deck's base path. A deck at `/` keeps its old keys, but settings that a deck under a sub-path stored with 1.x are not read. +- A static build no longer contains the side editor, the dev server's sync and drag saving, or the `/notes-edit` page. They only ever worked with the dev server. +- In a PDF made by `export` or `build --download`, links to other addresses of the deck's own site, such as `/file.pdf`, are text without a link. + +## What is new + +The full list is in the [release notes](https://github.com/slide-ng/slide.ng/blob/main/CHANGELOG.md). The new pages of this site: + +- [Interface language](../features/interface-language.md), with right-to-left decks and one deck in several languages +- [Click animations](../features/click-animations.md) and transitions of your own, see [Clicks and transitions](../guide/animations.md#your-own-transition) +- [Progress bar](../features/progress-bar.md) +- [Overview and notes pages](../features/overview-pages.md), the sync mode menu and the click preview, see [Presenter mode](../guide/presenter-mode.md) +- [UnoCSS](../features/unocss.md): attributify, variant groups, `prose` and directives +- [Loading pictures ahead](../features/preload-images.md) +- [Accessibility](../guide/accessibility.md) +- Memory routing and `build --router-mode`, see [Slide addresses](../features/routing.md#memory-mode) +- Several decks in one `build` or `export`, see [Hosting](../guide/hosting.md#several-decks) +- Slide hooks and the global context, see [Layouts and components](../customize/layouts-components.md#slide-hooks) and [Angular in markdown](../guide/angular-templates.md#the-global-context) +- `setup/routes.ts` and `setup/mermaid-renderer.ts`, see [Setup hooks](../customize/setup-hooks.md) +- MCP over HTTP from the dev server, see [MCP server](../features/mcp.md#over-http-from-the-dev-server) diff --git a/src/content/project/security.md b/src/content/project/security.md index 4ec3306..ad2f811 100644 --- a/src/content/project/security.md +++ b/src/content/project/security.md @@ -13,7 +13,7 @@ Problems in a theme belong in the slide-ng/themes repository, and problems in th ## A deck is code -A deck can contain Angular templates, components, setup files and themes, and slide.ng compiles and runs them. Treat a deck from someone else as you would treat a project from someone else: read it before you run `slide-ng` on it. A `<script>` element in a slide is removed with a warning, and a link to a `javascript:` address is replaced by `#`, but components, setup files and themes still run code. The checks below limit what a deck can ask the tool to do. They do not make it safe to run an unknown deck. +A deck can contain Angular templates, components, setup files and themes, and slide.ng compiles and runs them. Treat a deck from someone else as you would treat a project from someone else: read it before you run `slide-ng` on it. A `<script>` element in a slide is removed with a warning, and a `javascript:` address in a link, an `<iframe>`'s `src` or an `<object>`'s `data` is replaced by `#`, so is `data:text/html` in the last two. An inline handler such as `onclick="..."` is kept and runs, with a warning. Components, setup files, themes and an `index.html` next to `slides.md` still run code. The checks below limit what a deck can ask the tool to do. They do not make it safe to run an unknown deck. ## What the tool checks @@ -23,7 +23,7 @@ A deck can contain Angular templates, components, setup files and themes, and sl **Export and build.** File names pass a sanitiser, so `exportFilename` from a deck can only be a plain name in the current folder. A deck cannot choose the output folder or the browser program: `output` and `executablePath` come only from the command line or the environment. The build only empties a folder that is new, empty or marked `.slide-ng-build`, and refuses the current folder, your home folder and `/`. The static server used for export confines paths too. -**The MCP server.** Every change is parsed again before it is written, and only the deck's own markdown files are touched. +**The MCP server.** Every change is parsed again before it is written, and only the deck's own markdown files are touched. The dev server's MCP endpoint, `/__mcp`, has the dev server's Host and Origin checks, and with `--remote` it needs the password as a bearer token; wrong ones are limited like the login. `mcp: false` in the headmatter turns it off. **The code runner.** `{monaco-run}` code runs in a sandboxed frame with no access to the slides, and stops after 5 seconds. diff --git a/src/content/welcome.md b/src/content/welcome.md index 6cf4c96..a177be9 100644 --- a/src/content/welcome.md +++ b/src/content/welcome.md @@ -49,5 +49,7 @@ Every page that shows a deck embeds a real build of it. You can also open them d - [The theme gallery](/customize/theme-gallery), one deck for each official theme <ngmd-callout type="warning" title="Status"> - slide.ng is early. Version 0.1.0 is on npm as `@slide-ng/core`, and some Slidev features are missing. The [list of what is not supported](./migrating/not-supported.md) is kept honest. + +These pages describe slide.ng 2.0, which is on npm as `@slide-ng/core`. A deck made with 1.x: read [Upgrading from 1.x to 2.0](./migrating/upgrading-to-2.md). Some Slidev features are missing, and the [list of what is not supported](./migrating/not-supported.md) is kept honest. + </ngmd-callout> diff --git a/src/ngmd.config.ts b/src/ngmd.config.ts index 7e6af47..7bfe8cf 100644 --- a/src/ngmd.config.ts +++ b/src/ngmd.config.ts @@ -209,14 +209,15 @@ const config: NgmdConfig = { {label: 'Syntax', href: '/guide/syntax'}, {label: 'Angular in markdown', href: '/guide/angular-templates'}, {label: 'Layouts', href: '/guide/layouts'}, - {label: 'Clicks and transitions', href: '/guide/animations'}, + {label: 'Clicks and transitions', href: '/guide/animations', status: 'updated'}, {label: 'Code', href: '/guide/code'}, {label: 'Diagrams and math', href: '/guide/diagrams-math'}, {label: 'Styling', href: '/guide/styling'}, - {label: 'Presenter mode', href: '/guide/presenter-mode'}, + {label: 'Presenter mode', href: '/guide/presenter-mode', status: 'updated'}, {label: 'Drawing and recording', href: '/guide/drawing-recording'}, {label: 'Export', href: '/guide/export'}, - {label: 'Hosting', href: '/guide/hosting'}, + {label: 'Hosting', href: '/guide/hosting', status: 'updated'}, + {label: 'Accessibility', href: '/guide/accessibility', status: 'new'}, {label: 'Presenting offline', href: '/guide/offline'}, {label: 'Agents and the skill', href: '/guide/agents'}, ], @@ -230,10 +231,11 @@ const config: NgmdConfig = { {label: 'Slots', href: '/features/slots'}, {label: 'Scoped styles', href: '/features/scoped-styles'}, {label: 'Speaker notes', href: '/features/speaker-notes'}, - {label: 'Slide addresses', href: '/features/routing'}, + {label: 'Slide addresses', href: '/features/routing', status: 'updated'}, {label: 'Table of contents', href: '/features/table-of-contents'}, {label: 'Icons', href: '/features/icons'}, {label: 'Markdown extras', href: '/features/markdown-extras'}, + {label: 'UnoCSS', href: '/features/unocss', status: 'new'}, {label: 'Canvas size', href: '/features/canvas-size'}, {label: 'Zoom', href: '/features/zoom'}, {label: 'Transform', href: '/features/transform'}, @@ -241,6 +243,8 @@ const config: NgmdConfig = { {label: 'Global layers', href: '/features/global-layers'}, {label: 'Draggable elements', href: '/features/draggable'}, {label: 'Marks', href: '/features/marks'}, + {label: 'Click animations', href: '/features/click-animations', status: 'new'}, + {label: 'Progress bar', href: '/features/progress-bar', status: 'new'}, {label: 'Motion', href: '/features/motion'}, ], }, @@ -265,12 +269,15 @@ const config: NgmdConfig = { { label: 'Features: presenting', items: [ + {label: 'Overview and notes pages', href: '/features/overview-pages', status: 'new'}, {label: 'Timer', href: '/features/timer'}, {label: 'Remote access', href: '/features/remote-access'}, {label: 'Side editor', href: '/features/side-editor'}, {label: 'Drawing', href: '/features/drawing'}, {label: 'Recording', href: '/features/recording'}, {label: 'Context menu', href: '/features/context-menu'}, + {label: 'Interface language', href: '/features/interface-language', status: 'new'}, + {label: 'Loading pictures ahead', href: '/features/preload-images', status: 'new'}, ], }, { @@ -281,7 +288,7 @@ const config: NgmdConfig = { {label: 'Page head and SEO', href: '/features/seo-meta'}, {label: 'Fonts', href: '/features/fonts'}, {label: 'Offline and PWA', href: '/features/pwa'}, - {label: 'MCP server', href: '/features/mcp'}, + {label: 'MCP server', href: '/features/mcp', status: 'updated'}, {label: 'Formatting', href: '/features/format'}, ], }, @@ -316,9 +323,10 @@ const config: NgmdConfig = { ], }, { - label: 'Migrating from Slidev', + label: 'Migrating', items: [ - {label: 'What differs', href: '/migrating/from-slidev'}, + {label: 'Upgrading from 1.x', href: '/migrating/upgrading-to-2', status: 'new'}, + {label: 'What differs from Slidev', href: '/migrating/from-slidev'}, {label: 'Not supported', href: '/migrating/not-supported'}, ], },