Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: print templates, slide layers and rewrite export function #1513

Closed
wants to merge 27 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion demo/starter/package.json
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
{
"name": "slidev-demo",
"type": "module",
"private": true,
"scripts": {
"build": "slidev build",
"dev": "nodemon -w '../../packages/slidev/dist/*.mjs' --exec \"slidev ./slides.md --open=false --log=info --inspect\"",
"export": "slidev export",
"export-notes": "slidev export-notes"
"export-notes": "slidev export -t notes"
},
"devDependencies": {
"@slidev/cli": "workspace:*",
Expand Down
2 changes: 1 addition & 1 deletion demo/vue-runner/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"build": "slidev build",
"dev": "nodemon -w '../../packages/slidev/dist/*.mjs' --exec \"slidev ./slides.md --open=false --log=info --inspect\"",
"export": "slidev export",
"export-notes": "slidev export-notes"
"export-notes": "slidev export -t notes"
},
"devDependencies": {
"@slidev/cli": "workspace:*",
Expand Down
16 changes: 10 additions & 6 deletions docs/.vitepress/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,8 +113,12 @@ const Customizations: (DefaultTheme.NavItemWithLink | DefaultTheme.NavItemChildr
link: '/custom/directory-structure',
},
{
text: 'Fonts',
link: '/custom/fonts',
text: 'Vue Global Context',
link: '/custom/vue-context',
},
{
text: 'Global Layers',
link: '/custom/global-layers',
},
{
text: 'Highlighters',
Expand Down Expand Up @@ -165,12 +169,12 @@ const Customizations: (DefaultTheme.NavItemWithLink | DefaultTheme.NavItemChildr
link: '/custom/config-context-menu',
},
{
text: 'Vue Global Context',
link: '/custom/vue-context',
text: 'Custom Print Templates',
link: '/custom/print-templates',
},
{
text: 'Global Layers',
link: '/custom/global-layers',
text: 'Fonts',
link: '/custom/fonts',
},
]

Expand Down
1 change: 0 additions & 1 deletion docs/.vitepress/theme/components/Demo.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
<script setup lang="ts">
import { onMounted, ref, watch } from 'vue'

// @ts-expect-error missing types
import TypeIt from 'typeit'
import Markdown from 'markdown-it'
import type { SlidevMarkdown } from '@slidev/types'
Expand Down
6 changes: 4 additions & 2 deletions docs/custom/directory-structure.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,12 @@ your-slidev/
├── layouts/ # custom layouts
├── public/ # static assets
├── setup/ # custom setup / hooks
├── styles/ # custom style
├── styles/ # custom styles
├── snippets/ # code snippets
├── pages/print/ # custom print templates
├── index.html # injections to index.html
├── slides.md # the main slides entry
└── vite.config.ts # extending vite config
└── vite.config.ts # extending the vite config
```

All of them are optional.
Expand Down
16 changes: 10 additions & 6 deletions docs/custom/global-layers.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,14 @@ Global layers allow you to have custom components that **persist** across slides

Slidev provides three layers for this usage, create `global-top.vue`, `global-bottom.vue` or `custom-nav-controls.vue` under your project root and it will pick up automatically.

There are also layers for **each** slide: `layouts/slide-top.vue` and `layouts/slide-bottom.vue`. The usage is similar to the global layers, but they are applied to every slide, so there may be more than one instance of them.

Layers relationship:

- Global Top (`global-top.vue`)
- Slide Top (`layouts/slide-top.vue`)
- Slides
- Slide Bottom (`layouts/slide-bottom.vue`)
- Global Bottom (`global-bottom.vue`)
- NavControls
- Customized Navigation Controls (`custom-nav-controls.vue`)
Expand All @@ -28,7 +32,7 @@ The text `Your Name` will appear on all your slides.
```html
<!-- custom-nav-controls -->
<template>
<button class="icon-btn" title="Next" @click="$slidev.nav.next">
<button class="icon-btn" title="Next" @click="$nav.next">
<carbon:arrow-right />
</button>
</template>
Expand All @@ -42,7 +46,7 @@ To enable it conditionally, you can apply it with the [Vue Global Context](/cust
<!-- hide the footer from Page 4 -->
<template>
<footer
v-if="$slidev.nav.currentPage !== 4"
v-if="$nav.currentPage !== 4"
class="absolute bottom-0 left-0 right-0 p-2"
>
Your Name
Expand All @@ -54,7 +58,7 @@ To enable it conditionally, you can apply it with the [Vue Global Context](/cust
<!-- hide the footer from "cover" layout -->
<template>
<footer
v-if="$slidev.nav.currentLayout !== 'cover'"
v-if="$nav.currentLayout !== 'cover'"
class="absolute bottom-0 left-0 right-0 p-2"
>
Your Name
Expand All @@ -66,10 +70,10 @@ To enable it conditionally, you can apply it with the [Vue Global Context](/cust
<!-- an example footer for pages -->
<template>
<footer
v-if="$slidev.nav.currentLayout !== 'cover'"
v-if="$nav.currentLayout !== 'cover'"
class="absolute bottom-0 left-0 right-0 p-2"
>
{{ $slidev.nav.currentPage }} / {{ $slidev.nav.total }}
{{ $nav.currentPage }} / {{ $nav.total }}
</footer>
</template>
```
Expand All @@ -78,7 +82,7 @@ To enable it conditionally, you can apply it with the [Vue Global Context](/cust
<!-- custom-nav-controls -->
<!-- hide the button in Presenter model -->
<template>
<button v-if="!$slidev.nav.isPresenter" class="icon-btn" title="Next" @click="$slidev.nav.next">
<button v-if="!$nav.isPresenter" class="icon-btn" title="Next" @click="$nav.next">
<carbon:arrow-right />
</button>
</template>
Expand Down
7 changes: 7 additions & 0 deletions docs/custom/print-templates.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Custom Print Templates

You can create custom print templates for your documents via custom print templates. To start, create a new file in the `./pages/print` directory, for example, `./pages/print/my-template.vue`.

To print your slides using the custom template, use the `--template` option in the CLI, more details in the [exporting guide](/guide/exporting#print-template).

To debug your print template, you can use the `--print-template` option in dev mode, and access `http://localhost:3030/print?print` to see the print preview.
31 changes: 17 additions & 14 deletions docs/guide/exporting.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,21 @@ By default, Slidev exports one page per slide with clicks animations disabled. I
$ slidev export --with-clicks
```

### Print Template

By default, Slidev uses the `default` template to print your slides to PDF. You can also specify the print template with the `--template` (`-t`) option:

```bash
$ slidev export --template notes
```

Currently, Slidev provides the following templates:

- `default`: Slides only template. Each
- `notes`: Notes only.

But it is also possible to create your own print template. See [Custom Print Templates](/custom/print-templates) for details.

### Slide range

You can also specify a range of slides to export with the `--range` option:
Expand Down Expand Up @@ -104,29 +119,17 @@ The example above would export slides 1,6,7,8 and 10.
You can also export multiple slides at once:

```bash
$ slidev export slides1.md slides1.md
$ slidev export slides1.md slides2.md
```

Or

```bash
$ slidev export *.md
$ slidev export *.md # This based on your shell
```

In this case, each input file will generate its own PDf file.

## Presenter notes

> Available since v0.36.8

Export only the presenter notes (the last comment block for each slide) into a text document in PDF:

```bash
$ slidev export-notes
```

This command also accepts multiple entries like for the [export command](#multiple-entries)

## Single-Page Application (SPA)

See [Static Hosting](/guide/hosting).
Expand Down
4 changes: 3 additions & 1 deletion docs/guide/install.md
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,7 @@ Options:
- `--log` (`'error', 'warn', 'info', 'silent'`, default: `'warn'`): Log level.
- `--force`, `-f` (`boolean`, default: `false`): force the optimizer to ignore the cache and re-bundle.
- `--theme`, `-t` (`string`): override theme.
- `--print-template` (`string`): specify [the print template](/guide/exporting#print-template).

### `slidev build [entry]`

Expand Down Expand Up @@ -202,7 +203,8 @@ Options:
- `--range` (`string`): page ranges to export (example: `'1,4-5,6'`).
- `--dark` (`boolean`, default: `false`): export as dark theme.
- `--with-clicks`, `-c` (`boolean`, default: `false`): export pages for every click animation (see https://sli.dev/guide/animations.html#click-animations).
- `--theme`, `-t` (`string`): override theme.
- `--theme` (`string`): override theme.
- `--template`, `-t` (`string`, default: `default`): specify [the print template](/guide/exporting#print-template).
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would prefer --type with hard-coded options like slides notes handout - and let ppl to override them instead of creating arbitrary options. Because we might want to do some special handling for each type - over-generalize it might not be ideal for long term.

Copy link
Member Author

@KermanX KermanX Apr 18, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it is OK to allow users to create new print templates. This is because it will be the user/addon's responsibility to handle other options like --with-clicks or so. Otherwise, users are forced to override one of the 3 templates even if there is no connection between the default one and the overridden one.

About the name of this option, should we change all "print templates" into "print types" or only the CLI flag?


### `slidev format [entry]`

Expand Down
36 changes: 25 additions & 11 deletions packages/client/composables/useNav.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import type { RouteLocationNormalized, Router } from 'vue-router'
import { createSharedComposable } from '@vueuse/core'
import { logicOr } from '@vueuse/math'
import { clamp } from '@antfu/utils'
import { parseRangeString } from '@slidev/parser'
import { getCurrentTransition } from '../logic/transition'
import { getSlide, getSlidePath } from '../logic/slides'
import { CLICKS_MAX } from '../constants'
Expand All @@ -29,6 +30,8 @@ export interface SlidevContextNav {

nextRoute: ComputedRef<SlideRoute>
prevRoute: ComputedRef<SlideRoute>
hasNextSlide: ComputedRef<boolean>
hasPrevSlide: ComputedRef<boolean>
hasNext: ComputedRef<boolean>
hasPrev: ComputedRef<boolean>

Expand Down Expand Up @@ -72,6 +75,7 @@ export interface SlidevContextNavState {
currentRoute: ComputedRef<RouteLocationNormalized>
isPrintMode: ComputedRef<boolean>
isPrintWithClicks: ComputedRef<boolean>
slidesToPrint: ComputedRef<SlideRoute[]>
isEmbedded: ComputedRef<boolean>
isPlaying: ComputedRef<boolean>
isPresenter: ComputedRef<boolean>
Expand All @@ -93,15 +97,16 @@ export function useNavBase(
clicksContext: ComputedRef<ClicksContext>,
queryClicks: Ref<number> = ref(0),
isPresenter: Ref<boolean>,
isPrint: Ref<boolean>,
isPrintMode: Ref<boolean>,
router?: Router,
): SlidevContextNav {
const total = computed(() => slides.value.length)

const navDirection = ref(0)
const clicksDirection = ref(0)

const currentPath = computed(() => getSlidePath(currentSlideRoute.value, isPresenter.value))
const pathPrefix = computed(() => isPresenter.value ? '/presenter' : isPrintMode.value ? '/print' : '')
const currentPath = computed(() => getSlidePath(currentSlideRoute.value, pathPrefix.value))
const currentSlideNo = computed(() => currentSlideRoute.value.no)
const currentLayout = computed(() => currentSlideRoute.value.meta?.layout || (currentSlideNo.value === 1 ? 'cover' : 'default'))

Expand All @@ -110,8 +115,10 @@ export function useNavBase(
const clicksTotal = computed(() => clicksContext.value.total)
const nextRoute = computed(() => slides.value[Math.min(slides.value.length, currentSlideNo.value + 1) - 1])
const prevRoute = computed(() => slides.value[Math.max(1, currentSlideNo.value - 1) - 1])
const hasNext = computed(() => currentSlideNo.value < slides.value.length || clicks.value < clicksTotal.value)
const hasPrev = computed(() => currentSlideNo.value > 1 || clicks.value > 0)
const hasNextSlide = computed(() => currentSlideNo.value < slides.value.length)
const hasPrevSlide = computed(() => currentSlideNo.value > 1)
const hasNext = computed(() => hasNextSlide.value || clicks.value < clicksTotal.value)
const hasPrev = computed(() => hasPrevSlide.value || clicks.value > 0)

const currentTransition = computed(() => getCurrentTransition(navDirection.value, currentSlideRoute.value, prevRoute.value))

Expand Down Expand Up @@ -166,7 +173,7 @@ export function useNavBase(
await go(
next,
lastClicks
? isPrint.value
? isPrintMode.value
? undefined
: getSlide(next)?.meta.__clicksContext?.total ?? CLICKS_MAX
: undefined,
Expand All @@ -190,7 +197,7 @@ export function useNavBase(
clicks = clamp(clicks, clicksStart, meta?.__clicksContext?.total ?? CLICKS_MAX)
if (force || pageChanged || clicksChanged) {
await router?.push({
path: getSlidePath(page, isPresenter.value),
path: getSlidePath(page, pathPrefix.value),
query: {
...router.currentRoute.value.query,
clicks: clicks === 0 ? undefined : clicks.toString(),
Expand All @@ -201,13 +208,13 @@ export function useNavBase(

function enterPresenter() {
router?.push({
path: getSlidePath(currentSlideNo.value, true),
path: getSlidePath(currentSlideNo.value, '/presenter'),
query: { ...router.currentRoute.value.query },
})
}
function exitPresenter() {
router?.push({
path: getSlidePath(currentSlideNo.value, false),
path: getSlidePath(currentSlideNo.value, ''),
query: { ...router.currentRoute.value.query },
})
}
Expand All @@ -228,6 +235,8 @@ export function useNavBase(
clicks,
clicksStart,
clicksTotal,
hasNextSlide,
hasPrevSlide,
hasNext,
hasPrev,
tocTree,
Expand Down Expand Up @@ -277,14 +286,18 @@ const useNavState = createSharedComposable((): SlidevContextNavState => {
router.currentRoute.value.query
return new URLSearchParams(location.search)
})
const isPrintMode = computed(() => query.value.has('print'))
const isPrintWithClicks = computed(() => query.value.get('print') === 'clicks')
const isPrintMode = computed(() => currentRoute.value.name === 'print')
const isPrintWithClicks = computed(() => query.value.has('with-clicks'))
const slidesToPrint = computed(() => {
const range = query.value.get('range')
return range ? parseRangeString(slides.value.length, range).map(i => slides.value[i - 1]) : slides.value
})
const isEmbedded = computed(() => query.value.has('embedded'))
const isPlaying = computed(() => currentRoute.value.name === 'play')
const isPresenter = computed(() => currentRoute.value.name === 'presenter')
const isNotesViewer = computed(() => currentRoute.value.name === 'notes')
const isPresenterAvailable = computed(() => !isPresenter.value && (!configs.remote || query.value.get('password') === configs.remote))
const hasPrimarySlide = logicOr(isPlaying, isPresenter)
const hasPrimarySlide = logicOr(isPlaying, isPresenter, isPrintMode)

const currentSlideNo = computed(() => hasPrimarySlide.value ? getSlide(currentRoute.value.params.no as string)?.no ?? 1 : 1)
const currentSlideRoute = computed(() => slides.value[currentSlideNo.value - 1])
Expand Down Expand Up @@ -349,6 +362,7 @@ const useNavState = createSharedComposable((): SlidevContextNavState => {
currentRoute,
isPrintMode,
isPrintWithClicks,
slidesToPrint,
isEmbedded,
isPlaying,
isPresenter,
Expand Down
Loading
Loading