Skip to content

Commit d9127b9

Browse files
committed
feat: embedded styles in markdown
1 parent ca1f5be commit d9127b9

File tree

7 files changed

+98
-17
lines changed

7 files changed

+98
-17
lines changed

docs/guide/syntax.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,42 @@ console.log('HelloWorld')
114114

115115
Learn more about [configuring Monaco](/custom/config-monaco).
116116

117+
## Embedded Styles
118+
119+
You can use `<style>` tag in your Markdown directly to override styles for the **current slide**.
120+
121+
```md
122+
# This is Red
123+
124+
<style>
125+
h1 {
126+
color: red
127+
}
128+
</style>
129+
130+
---
131+
132+
# Next slide is not affected
133+
```
134+
135+
`<style>` tag in Markdown is always [scoped](https://vue-loader.vuejs.org/guide/scoped-css.html). To have global style overrides, check out the [customization section](/custom/directory-structure#style).
136+
137+
Powered by [Windi CSS](https://windicss.org), you can directly use nested css and [directives](https://windicss.org/features/directives.html) (e.g. `@apply`)
138+
139+
```md
140+
# Slidev
141+
142+
> Hello `world`
143+
144+
<style>
145+
blockquote {
146+
code {
147+
@apply text-teal-500 dark:text-teal-400;
148+
}
149+
}
150+
</style>
151+
```
152+
117153
## Notes
118154

119155
You can also take notes for each slide. They will show up in [Presenter Mode](/guide/presenter-mode) for you to reference during presentations.

packages/client/internals/SlideContainer.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ const classes = computed(() => {
8989
<div id="slide-container" ref="root">
9090
<div id="slide-content" :style="style">
9191
<slot>
92-
<component :is="props.is || route.component" :classes="classes" />
92+
<component :is="props.is || route.component" :class="classes" />
9393
</slot>
9494
</div>
9595
<slot name="controls" />

packages/create-app/template/slides.md

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,23 @@ Slidev is a slides maker and presenter designed for developers, consist of the f
4545

4646
Read more about [Why Slidev?](https://sli.dev/guide/why)
4747

48+
<!--
49+
You can have `style` tag in markdown to override the style for the current page.
50+
Learn more: https://sli.dev/guide/syntax#embedded-styles
51+
-->
52+
53+
<style>
54+
h1 {
55+
background-color: #2B90B6;
56+
background-image: linear-gradient(45deg, #4EC5D4 10%, #146b8c 20%);
57+
background-size: 100%;
58+
-webkit-background-clip: text;
59+
-moz-background-clip: text;
60+
-webkit-text-fill-color: transparent;
61+
-moz-text-fill-color: transparent;
62+
}
63+
</style>
64+
4865

4966
---
5067

@@ -93,10 +110,6 @@ function updateUser(id: number, update: Partial<User>) {
93110
}
94111
```
95112

96-
<!--
97-
asd
98-
-->
99-
100113
---
101114

102115
# Components

packages/slidev/node/plugins/loaders.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,12 @@ function prepareSlideInfo(data: SlideInfo): SlideInfoExtended {
6262
}
6363
}
6464

65-
export function createSlidesLoader({ data, entry, clientRoot, themeRoots, userRoot }: ResolvedSlidevOptions, pluginOptions: SlidevPluginOptions, VuePlugin: Plugin): Plugin[] {
65+
export function createSlidesLoader(
66+
{ data, entry, clientRoot, themeRoots, userRoot }: ResolvedSlidevOptions,
67+
pluginOptions: SlidevPluginOptions,
68+
VuePlugin: Plugin,
69+
MarkdownPlugin: Plugin,
70+
): Plugin[] {
6671
const slidePrefix = '/@slidev/slides/'
6772
const hmrPages = new Set<number>()
6873

@@ -154,9 +159,7 @@ export function createSlidesLoader({ data, entry, clientRoot, themeRoots, userRo
154159
...ctx,
155160
modules: Array.from(module?.importedModules || []),
156161
file: id,
157-
read() {
158-
return newData.slides[i - 1]?.raw
159-
},
162+
read: () => (<any>MarkdownPlugin.transform)(newData.slides[i]?.raw, id),
160163
},
161164
)
162165
}),

packages/slidev/node/plugins/markdown.ts

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { Plugin } from 'vite'
55
import type { ShikiOptions } from '@slidev/types'
66
import type MarkdownIt from 'markdown-it'
77
import base64 from 'js-base64'
8-
import { isTruthy } from '@antfu/utils'
8+
import { isTruthy, slash } from '@antfu/utils'
99
// @ts-expect-error
1010
import Katex from 'markdown-it-katex'
1111
import { ResolvedSlidevOptions, SlidevPluginOptions } from '../options'
@@ -21,10 +21,11 @@ const DEFAULT_SHIKI_OPTIONS: ShikiOptions = {
2121
}
2222

2323
export async function createMarkdownPlugin(
24-
{ data: { config }, roots, mode }: ResolvedSlidevOptions,
24+
{ data: { config }, roots, mode, entry }: ResolvedSlidevOptions,
2525
{ markdown: mdOptions }: SlidevPluginOptions,
2626
): Promise<Plugin> {
2727
const setups: ((md: MarkdownIt) => void)[] = []
28+
const entryPath = slash(entry)
2829

2930
if (config.highlighter === 'shiki') {
3031
const { getHighlighter } = await import('shiki')
@@ -59,13 +60,17 @@ export async function createMarkdownPlugin(
5960
setups.forEach(i => i(md))
6061
},
6162
transforms: {
62-
before(code) {
63+
before(code, id) {
64+
if (id === entryPath)
65+
return ''
66+
6367
const monaco = (config.monaco === true || config.monaco === mode)
6468
? transformMarkdownMonaco
6569
: truncateMancoMark
6670

6771
code = monaco(code)
6872
code = transformHighlighter(code)
73+
code = transformPageCSS(code, id)
6974

7075
return code
7176
},
@@ -102,10 +107,32 @@ export function truncateMancoMark(code: string) {
102107
return code.replace(/{monaco.*?}/g, '')
103108
}
104109

110+
/**
111+
* Transform Monaco code block to component
112+
*/
105113
export function transformHighlighter(md: string) {
106-
// transform monaco
107114
return md.replace(/\n```(\w+?)\s*{([\d\w*,\|-]+)}[\s\n]*([\s\S]+?)\n```/mg, (full, lang = '', rangeStr: string, code: string) => {
108115
const ranges = rangeStr.split(/\|/g).map(i => i.trim())
109116
return `\n<CodeHighlightController :ranges='${JSON.stringify(ranges)}'>\n\n\`\`\`${lang}\n${code}\n\`\`\`\n\n</CodeHighlightController>`
110117
})
111118
}
119+
120+
/**
121+
* Transform <style> in markdown to scoped style with page selector
122+
*/
123+
export function transformPageCSS(md: string, id: string) {
124+
const page = id.match(/(\d+)\.md$/)?.[1]
125+
if (!page)
126+
return md
127+
128+
const result = md.replace(
129+
/(\n<style[^>]*?>)([\s\S]+?)(<\/style>)/g,
130+
(full, start, css, end) => {
131+
if (!start.includes('scoped'))
132+
start = start.replace('<style', '<style scoped')
133+
return `${start}\n.slidev-page-${page}{${css}}${end}`
134+
},
135+
)
136+
137+
return result
138+
}

packages/slidev/node/plugins/preset.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -67,13 +67,14 @@ export async function ViteSlidevPlugin(
6767
...vueOptions,
6868
})
6969

70+
const MarkdownPlugin = await createMarkdownPlugin(options, pluginOptions)
71+
7072
return [
7173
createWindiCSSPlugin(options, pluginOptions),
72-
await createMarkdownPlugin(options, pluginOptions),
73-
74+
MarkdownPlugin,
7475
VuePlugin,
7576

76-
createSlidesLoader(options, pluginOptions, VuePlugin),
77+
createSlidesLoader(options, pluginOptions, VuePlugin, MarkdownPlugin),
7778

7879
ViteComponents({
7980
extensions: ['vue', 'md', 'ts'],

packages/theme-seriph/styles/layouts.css

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
.slidev-layout:not(.cover) {
1+
.slidev-layout {
22
h1 {
33
@apply text-primary;
44
}
@@ -9,6 +9,7 @@
99
@apply h-full grid;
1010

1111
h1 {
12+
color: inherit;
1213
@apply text-6xl leading-20;
1314
}
1415

0 commit comments

Comments
 (0)