-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
feat: handout export (slides + notes + cover/footer/ending) #2278
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
base: main
Are you sure you want to change the base?
Conversation
✅ Deploy Preview for slidev ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
@slidev/client
create-slidev
create-slidev-theme
@slidev/parser
@slidev/cli
@slidev/types
commit: |
| export interface ExportArgs extends CommonArgs { | ||
| 'output'?: string | ||
| 'handout'?: boolean | ||
| 'cover'?: boolean |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not so sure about the cover part, as it doesn't really feel like to be part of Slidev, and I am concern if we do we would need to handle request of mutli-pages cover or the ending page etc. As people can always edit the PDF to prepend or append pages, I don't think it's a must-have right now. Can we leave it to be discussed the future?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For me, the cover page is an essential use case. One of the great things I love about Slidev—and the main reason I moved all my slides from PowerPoint—is that it allows me to keep consistent layouts while easily changing branding and slide styles, all without touching the content.
For example, I have training materials written in Slidev. When I deliver a training for Client A, the slides need different logos and branding than for Client B. Achieving this with PowerPoint and master slides is a pain and very difficult to keep in sync. With Slidev, it’s straightforward thanks to custom layouts and components.
Using Slidev for delivering courses is, I believe, a common use case. For students, it’s also standard to receive a handout with the speaker notes after the training (this is an industry standard and a core feature of competing software such as PowerPoint). To make these handouts look professional, a cover page is necessary—because the output then essentially becomes a book. That’s why I see this as a feature that aligns very well with Slidev.
I understand the hesitation to integrate a feature that, at first glance, might seem unrelated to presentation software. And yes, one could implement it externally. But doing so makes certain things very difficult, such as:
• having consistent page numbers on the cover pages, content pages and ending pages
• ensuring consistent layout across cover pages, ending pages, and content,
• applying a common footer.
An external solution would create repetition and friction. Since Slidev already has much of the necessary infrastructure and code in place, I believe this is a natural fit.
In my recent commits, I’ve added support for ending pages, additional output formats, and made some documentation enhancements and cleanups.
7789684 to
1863494
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for your work! I'd love to have it, let's move it forward.
A few suggestions:
| </h1> | ||
| <p class="mt-6 max-w-xl text-lg leading-relaxed text-slate-700"> | ||
| A companion handout for the demo deck that showcases Slidev's text-first authoring, navigation helpers, and Vue-driven | ||
| enhancements. Explore the pages ahead to follow along without missing any of the live interactions. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As this is the starter, I wanted to keep it simple. Could you reduce the content of handing-cover and handout-ending to only keep the minimal content for demonstration.
| - `--cover` (`boolean`, default: `false`): prepend global cover pages from `handout-cover.vue` when handouts are exported. | ||
| - `--ending` (`boolean`, default: `false`): append closing pages from `handout-ending.vue` when handouts are exported. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's remove these two flags; we could auto-apply when the pages exist. I believe it's not a common need to toggle them through the cli flag.
| </div> | ||
| </template> | ||
|
|
||
| <style scoped> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's avoid using scoped style, but use a .slidev- prefixed CSS in the style.css, which would allow user to override
| </template> | ||
|
|
||
| <style> | ||
| html.print, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems we are duplicating the styles and logics here?
| }, | ||
| { | ||
| name: 'cover', | ||
| path: '/cover', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's do /handout/cover or /handout-cover?
| 'handout-bottom.vue', | ||
| 'handout-cover.vue', | ||
| 'handout-ending.vue', | ||
| 'HandoutBottom.vue', | ||
| 'HandoutCover.vue', | ||
| 'HandoutEnding.vue', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| 'handout-bottom.vue', | |
| 'handout-cover.vue', | |
| 'handout-ending.vue', | |
| 'HandoutBottom.vue', | |
| 'HandoutCover.vue', | |
| 'HandoutEnding.vue', | |
| 'handout-bottom.vue', | |
| 'handout-cover.vue', | |
| 'handout-ending.vue', |
Let's keep it simple and consistent
| .option('handout', { | ||
| type: 'boolean', | ||
| describe: 'export handout PDF (configurable page size, one page per slide with notes and header/footer) to a separate file', | ||
| }) | ||
| .option('cover', { | ||
| type: 'boolean', | ||
| describe: 'prepend handout cover page(s) if available (requires handout-cover.vue)', | ||
| }) | ||
| .option('ending', { | ||
| type: 'boolean', | ||
| describe: 'append handout ending page(s) if available (requires handout-ending.vue)', | ||
| }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| .option('handout', { | |
| type: 'boolean', | |
| describe: 'export handout PDF (configurable page size, one page per slide with notes and header/footer) to a separate file', | |
| }) | |
| .option('cover', { | |
| type: 'boolean', | |
| describe: 'prepend handout cover page(s) if available (requires handout-cover.vue)', | |
| }) | |
| .option('ending', { | |
| type: 'boolean', | |
| describe: 'append handout ending page(s) if available (requires handout-ending.vue)', | |
| }) | |
| .option('handout', { | |
| type: 'boolean', | |
| describe: 'export handout PDF (configurable page size, one page per slide with notes and header/footer) to a separate file', | |
| }) |
Introduces a dedicated handout export that renders one A4 page per slide with:
Optionally, a multi-page cover can be prepended.
Export is supported both in the CLI and the browser exporter, designed for reliable print output.
Handout rendering and routes
New print routes for handout and cover, enabled whenever print or browser exporter features are active.
packages/client/setup/routes.tspackages/client/pages/handout/print.vuepackages/client/pages/cover/print.vuePage layout
Each page: slide (top), notes (middle), footer (bottom).
Uses fixed A4 dimensions in CSS pixels (96 DPI).
Enforces print media styles for consistency.
packages/client/internals/PrintHandout.vuepackages/client/internals/PrintContainerHandout.vueVirtual global components (user-overridable)
Auto-discovers
handout-cover.vue/HandoutCover.vueandhandout-bottom.vue/HandoutBottom.vue.Falls back to no-op components if not present.
Props like
pageNumberare forwarded (supports camel/kebab case).packages/slidev/node/virtual/handout-components.tspackages/slidev/node/virtual/index.tsCLI integration
slidev export --handout→<output>-handout.pdf.--coverprepends cover pages if defined.--rangelimits exported slides.Uses fixed A4 viewport and print media for stability.
packages/slidev/node/commands/export.tspackages/slidev/node/cli.tspackages/types/src/cli.tsPrint safety
Disables slide-sized
@pagerules for handout/cover routes so A4 applies cleanly.packages/client/composables/usePrintStyles.tsNavigation updates
isPrintModenow includeshandoutandcover.packages/client/composables/useNav.tsStarter examples
Demo components provided:
demo/starter/handout-cover.vuedemo/starter/handout-bottom.vueUsage
CLI
👉 Produces
slides-handout.pdf.Browser
/handout?print,/handout?cover, or/handout?range=1,3-5.Customization
handout-cover.vuefor one or more cover pages. Use.break-after-pageto force page breaks.handout-bottom.vueto customize footer content (e.g. company name, year). ReceivespageNumberprop.Implementation Notes
preferCSSPageSize, zero margins, and light color scheme..break-after-pageelements.