Skip to content

Commit d049b72

Browse files
committed
feat: generate pdf!
1 parent 3c2b2a8 commit d049b72

File tree

12 files changed

+306
-25
lines changed

12 files changed

+306
-25
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,4 @@ dist-ssr
77
node_modules
88
# intellij stuff
99
.idea/
10+
*.pdf

README.md

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,10 @@ vite-slides build
169169

170170
**Export**
171171

172-
> Not yet.
172+
```bash
173+
npm i -D playwright
174+
vite-slides export
175+
```
173176

174177
## TODO
175178

@@ -182,10 +185,10 @@ vite-slides build
182185
- [x] Slides Overview
183186
- [ ] Foot notes
184187
- [x] `v-click` directive
185-
- [ ] Standalone package
188+
- [x] Standalone package
186189
- [x] Dev Mode
187190
- [x] Build Mode
188-
- [ ] Export PDF
191+
- [x] Export PDF
189192
- [x] Configurable themes
190193

191194
## Sponsors

demo/components/NumBox.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ const value = useVModel(props, 'value', emit)
1515

1616
<template>
1717
<div
18-
class="w-16 h-16 rounded-xl shadow text-white overflow-hidden"
18+
class="w-16 h-16 rounded-xl text-white overflow-hidden"
1919
style="background-image: radial-gradient(farthest-corner at 0 0, var(--tw-gradient-from) 30%, var(--tw-gradient-to))"
2020
>
2121
<div

packages/vite-slides/client/App.vue

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
<script setup lang="ts">
22
import { useHead } from '@vueuse/head'
3-
import { computed } from 'vue'
3+
import { computed, provide } from 'vue'
44
import { useNavigateControls } from './logic'
55
import { scale, targetHeight, targetWidth } from './logic/scale'
6+
import { injectClickDisabled } from './modules/directives'
67
78
useHead({
89
title: 'Vite Slides',
@@ -16,21 +17,26 @@ const style = computed(() => ({
1617
transform: `translate(-50%, -50%) scale(${scale.value})`,
1718
}))
1819
20+
const query = new URLSearchParams(location.search)
21+
if (query.has('print'))
22+
provide(injectClickDisabled, true)
23+
1924
function onClick(e: MouseEvent) {
2025
const classList = (e.target as HTMLElement)?.classList
2126
if (classList?.contains('page-root'))
2227
controls.next()
2328
}
24-
2529
</script>
2630

2731
<template>
28-
<div class="page-root" @click="onClick">
29-
<div class="slide-container" :style="style">
30-
<RouterView :class="controls.current.value?.meta?.class || ''" />
32+
<div>
33+
<div class="page-root" @click="onClick">
34+
<div class="slide-container" :style="style">
35+
<RouterView :class="controls.current.value?.meta?.class || ''" />
36+
</div>
3137
</div>
38+
<SlideControls v-if="!query.has('print')" />
3239
</div>
33-
<SlideControls />
3440
</template>
3541

3642
<style lang="postcss">

packages/vite-slides/client/logic/controls.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,14 +55,14 @@ export function createNavigateControls(router: Router) {
5555
clickCurrent.value = 0
5656
clickElements.value = []
5757
counter.value = Math.min(routes.length - 1, counter.value + 1)
58-
router.push(`/${counter.value}`)
58+
router.push(`/${counter.value}${location.search}`)
5959
}
6060

6161
function prevSlide() {
6262
clickCurrent.value = 0
6363
clickElements.value = []
6464
counter.value = Math.max(0, counter.value - 1)
65-
router.push(`/${counter.value}`)
65+
router.push(`/${counter.value}${location.search}`)
6666
}
6767

6868
const { space, right, left, up, down } = useMagicKeys()

packages/vite-slides/client/logic/scale.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
import { useElementSize } from '@vueuse/core'
1+
import { useWindowSize } from '@vueuse/core'
22
import { reactive, computed } from 'vue'
33

44
export const aspect = 16 / 9
5-
export const targetWidth = 1920 / 2.2
5+
export const targetWidth = 980
66
export const targetHeight = targetWidth / aspect
77

8-
const screen = reactive(useElementSize(document.body))
8+
const screen = reactive(useWindowSize())
99
const screenAspect = computed(() => screen.width / screen.height)
1010

1111
export const scale = computed(() => {

packages/vite-slides/client/modules/directives.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ export const install: UserModule = ({ app }) => {
7575
},
7676

7777
beforeUnmount(el, dir) {
78-
dirProvide(dir, injectClickDisabled, false)
78+
dirProvide(dir, injectClickDisabled, true)
7979
},
8080
})
8181
}

packages/vite-slides/node/cli.ts

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
/* eslint-disable @typescript-eslint/no-var-requires */
22
import chalk from 'chalk'
33
import minimist from 'minimist'
4-
import { createServer } from './server'
5-
import { build } from './build'
64

75
const argv: any = minimist(process.argv.slice(2))
86

@@ -13,18 +11,29 @@ const command = argv._[0]
1311
const entry = argv._[command ? 1 : 0]
1412

1513
if (!command || command === 'dev') {
16-
createServer(entry, argv)
14+
import('./server')
15+
.then(i => i.createServer(entry, argv))
1716
.then(server => server.listen())
1817
.catch((err) => {
1918
console.error(chalk.red('failed to start server. error:\n'), err)
2019
process.exit(1)
2120
})
2221
}
2322
else if (command === 'build') {
24-
build(entry, argv).catch((err) => {
25-
console.error(chalk.red('build error:\n'), err)
26-
process.exit(1)
27-
})
23+
import('./build')
24+
.then(i => i.build(entry, argv))
25+
.catch((err) => {
26+
console.error(chalk.red('build error:\n'), err)
27+
process.exit(1)
28+
})
29+
}
30+
else if (command === 'export') {
31+
import('./export')
32+
.then(i => i.genratePDF(entry, argv))
33+
.catch((err) => {
34+
console.error(chalk.red('export error:\n'), err)
35+
process.exit(1)
36+
})
2837
}
2938
else {
3039
console.log(chalk.red(`unknown command "${command}".`))

packages/vite-slides/node/export.ts

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import { promises as fs } from 'fs'
2+
import { chromium } from 'playwright'
3+
import { InlineConfig } from 'vite'
4+
import { PDFDocument } from 'pdf-lib'
5+
import { green } from 'chalk'
6+
import { createServer } from './server'
7+
import { parseSlidesMarkdown } from './parser'
8+
9+
export async function genratePDF(entry = 'slides.md', config: InlineConfig = {}) {
10+
const pages = parseSlidesMarkdown(await fs.readFile(entry, 'utf-8'))
11+
const server = await createServer(entry, {
12+
...config,
13+
logLevel: 'silent',
14+
clearScreen: false,
15+
})
16+
const port = 18724
17+
await server.listen(port)
18+
19+
const browser = await chromium.launch()
20+
const context = await browser.newContext({
21+
viewport: {
22+
width: 1920,
23+
height: 1080,
24+
},
25+
deviceScaleFactor: 1,
26+
})
27+
const page = await context.newPage()
28+
29+
const buffers: Buffer[] = []
30+
const pagesCount = pages.length - 1
31+
for (let i = 0; i < pagesCount; i++) {
32+
console.log(`Exporting: ${i + 1} / ${pagesCount}`)
33+
await page.goto(`http://localhost:${port}/${i}?print`, {
34+
waitUntil: 'networkidle',
35+
})
36+
await page.emulateMedia({ media: 'screen' })
37+
const pdf = await page.pdf({
38+
width: 1920,
39+
height: 1080,
40+
margin: {
41+
left: 0,
42+
top: 0,
43+
right: 0,
44+
bottom: 0,
45+
},
46+
pageRanges: '1',
47+
printBackground: true,
48+
preferCSSPageSize: true,
49+
})
50+
buffers.push(pdf)
51+
}
52+
53+
const mergedPdf = await PDFDocument.create({})
54+
for (const pdfBytes of buffers) {
55+
const pdf = await PDFDocument.load(pdfBytes)
56+
const copiedPages = await mergedPdf.copyPages(pdf, pdf.getPageIndices())
57+
copiedPages.forEach((page) => {
58+
mergedPdf.addPage(page)
59+
})
60+
}
61+
62+
const buffer = await mergedPdf.save()
63+
await fs.writeFile('slides.pdf', buffer)
64+
console.log(green`Exporting finished: ./slides.pdf`)
65+
66+
browser.close()
67+
server.close()
68+
process.exit(0)
69+
}

packages/vite-slides/node/server.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { createServer as createViteServer, InlineConfig, mergeConfig } from 'vite'
22
import { ViteSlides } from './plugins/preset'
33

4-
export async function createServer(entry: string, config: InlineConfig = {}) {
4+
export async function createServer(entry?: string, config: InlineConfig = {}) {
55
return await createViteServer(
66
mergeConfig(
77
config,

0 commit comments

Comments
 (0)