| 1 | import type { ResolvedSlidevOptions } from '@slidev/types' |
| 2 | import type { Plugin } from 'vite' |
| 3 | import { bold, gray, red, yellow } from 'ansis' |
| 4 | import { toAtFS } from '../resolver' |
| 5 | import { regexSlideSourceId, templateImportContextUtils, templateInitContext, templateInjectionMarker } from './common' |
| 6 | |
| 7 | const RE_SCRIPT_SETUP_TAG = /^<script setup.*>/m |
| 8 | |
| 9 | export function createLayoutWrapperPlugin( |
| 10 | { data, utils }: ResolvedSlidevOptions, |
| 11 | ): Plugin { |
| 12 | return { |
| 13 | name: 'slidev:layout-wrapper', |
| 14 | transform: { |
| 15 | filter: { |
| 16 | id: { |
| 17 | include: regexSlideSourceId, |
| 18 | }, |
| 19 | }, |
| 20 | async handler(code, id) { |
| 21 | const match = id.match(regexSlideSourceId) |
| 22 | if (!match) |
| 23 | return |
| 24 | const [, no, type] = match |
| 25 | if (type !== 'md') |
| 26 | return |
| 27 | const index = +no - 1 |
| 28 | const layouts = await utils.getLayouts() |
| 29 | const rawLayoutName = data.slides[index]?.frontmatter?.layout ?? data.slides[0]?.frontmatter?.defaults?.layout |
| 30 | let layoutName = rawLayoutName || (index === 0 ? 'cover' : 'default') |
| 31 | if (!layouts[layoutName]) { |
| 32 | console.error(red(`\nUnknown layout "${bold(layoutName)}".${yellow(' Available layouts are:')}`) |
| 33 | + Object.keys(layouts).map((i, idx) => (idx % 3 === 0 ? '\n ' : '') + gray(i.padEnd(15, ' '))).join(' ')) |
| 34 | console.error() |
| 35 | layoutName = 'default' |
| 36 | } |
| 37 | |
| 38 | const setupTag = code.match(RE_SCRIPT_SETUP_TAG) |
| 39 | if (!setupTag) |
| 40 | throw new Error(`[Slidev] Internal error: <script setup> block not found in slide ${index + 1}.`) |
| 41 | |
| 42 | const templatePart = code.slice(0, setupTag.index!) |
| 43 | const scriptPart = code.slice(setupTag.index!) |
| 44 | |
| 45 | const bodyStart = templatePart.indexOf('<template>') + 10 |
| 46 | const bodyEnd = templatePart.lastIndexOf('</template>') |
| 47 | let body = code.slice(bodyStart, bodyEnd).trim() |
| 48 | if (body.startsWith('<div>') && body.endsWith('</div>')) |
| 49 | body = body.slice(5, -6) |
| 50 | |
| 51 | return [ |
| 52 | templatePart.slice(0, bodyStart), |
| 53 | `<InjectedLayout v-bind="_frontmatterToProps($frontmatter,${index})">\n${body}\n</InjectedLayout>`, |
| 54 | templatePart.slice(bodyEnd), |
| 55 | scriptPart.slice(0, setupTag[0].length), |
| 56 | `import InjectedLayout from "${toAtFS(layouts[layoutName])}"`, |
| 57 | templateImportContextUtils, |
| 58 | templateInitContext, |
| 59 | '$clicksContext.setup()', |
| 60 | templateInjectionMarker, |
| 61 | scriptPart.slice(setupTag[0].length), |
| 62 | ].join('\n') |
| 63 | }, |
| 64 | }, |
| 65 | } |
| 66 | } |
| 67 |