Skip to content

Commit 4136afb

Browse files
committed
feat: support notes auto ruby
1 parent 8a1d831 commit 4136afb

File tree

6 files changed

+175
-5
lines changed

6 files changed

+175
-5
lines changed

docs/components.d.ts

Lines changed: 97 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
/* eslint-disable */
22
// @ts-nocheck
3+
// biome-ignore lint: disable
4+
// oxlint-disable
5+
// ------
36
// Generated by unplugin-vue-components
47
// Read more: https://github.com/vuejs/core/pull/3399
5-
// biome-ignore lint: disable
8+
import { GlobalComponents } from 'vue'
9+
610
export {}
711

812
/* prettier-ignore */
@@ -98,3 +102,95 @@ declare module 'vue' {
98102
Youtube: typeof import('./node_modules/@slidev/client/builtin/Youtube.vue')['default']
99103
}
100104
}
105+
106+
// For TSX support
107+
declare global {
108+
const AddonGallery: typeof import('./.vitepress/theme/components/AddonGallery.vue')['default']
109+
const AddonInfo: typeof import('./.vitepress/theme/components/AddonInfo.vue')['default']
110+
const Arrow: typeof import('./node_modules/@slidev/client/builtin/Arrow.vue')['default']
111+
const AutoFitText: typeof import('./node_modules/@slidev/client/builtin/AutoFitText.vue')['default']
112+
const 'Carbon:chevronLeft': typeof import('~icons/carbon/chevron-left')['default']
113+
const 'Carbon:chevronRight': typeof import('~icons/carbon/chevron-right')['default']
114+
const 'Carbon:close': typeof import('~icons/carbon/close')['default']
115+
const 'Carbon:filterRemove': typeof import('~icons/carbon/filter-remove')['default']
116+
const 'Carbon:logoGithub': typeof import('~icons/carbon/logo-github')['default']
117+
const 'Carbon:logoTwitter': typeof import('~icons/carbon/logo-twitter')['default']
118+
const 'Carbon:presentationFile': typeof import('~icons/carbon/presentation-file')['default']
119+
const 'Carbon:reset': typeof import('~icons/carbon/reset')['default']
120+
const 'Carbon:search': typeof import('~icons/carbon/search')['default']
121+
const 'Carbon:tag': typeof import('~icons/carbon/tag')['default']
122+
const 'Carbon:video': typeof import('~icons/carbon/video')['default']
123+
const CarbonApps: typeof import('~icons/carbon/apps')['default']
124+
const CarbonArrowLeft: typeof import('~icons/carbon/arrow-left')['default']
125+
const CarbonArrowRight: typeof import('~icons/carbon/arrow-right')['default']
126+
const CarbonBadge: typeof import('~icons/carbon/badge')['default']
127+
const CarbonDocumentPdf: typeof import('~icons/carbon/document-pdf')['default']
128+
const CarbonDownload: typeof import('~icons/carbon/download')['default']
129+
const CarbonEdit: typeof import('~icons/carbon/edit')['default']
130+
const CarbonInformation: typeof import('~icons/carbon/information')['default']
131+
const CarbonListBoxes: typeof import('~icons/carbon/list-boxes')['default']
132+
const CarbonMaximize: typeof import('~icons/carbon/maximize')['default']
133+
const CarbonMinimize: typeof import('~icons/carbon/minimize')['default']
134+
const CarbonMoon: typeof import('~icons/carbon/moon')['default']
135+
const CarbonPen: typeof import('~icons/carbon/pen')['default']
136+
const CarbonSettingsAdjust: typeof import('~icons/carbon/settings-adjust')['default']
137+
const CarbonSun: typeof import('~icons/carbon/sun')['default']
138+
const CarbonTextAnnotationToggle: typeof import('~icons/carbon/text-annotation-toggle')['default']
139+
const CarbonUserAvatar: typeof import('~icons/carbon/user-avatar')['default']
140+
const CarbonUserSpeaker: typeof import('~icons/carbon/user-speaker')['default']
141+
const CarbonVideo: typeof import('~icons/carbon/video')['default']
142+
const CodeBlockWrapper: typeof import('./node_modules/@slidev/client/builtin/CodeBlockWrapper.vue')['default']
143+
const CodeGroup: typeof import('./node_modules/@slidev/client/builtin/CodeGroup.vue')['default']
144+
const CodiconAdd: typeof import('~icons/codicon/add')['default']
145+
const CodiconEye: typeof import('~icons/codicon/eye')['default']
146+
const CodiconGlobe: typeof import('~icons/codicon/globe')['default']
147+
const CodiconLock: typeof import('~icons/codicon/lock')['default']
148+
const CodiconRunAll: typeof import('~icons/codicon/run-all')['default']
149+
const Demo: typeof import('./.vitepress/theme/components/Demo.vue')['default']
150+
const DemoEditor: typeof import('./.vitepress/theme/components/DemoEditor.vue')['default']
151+
const DemoSlide: typeof import('./.vitepress/theme/components/DemoSlide.vue')['default']
152+
const Environment: typeof import('./.vitepress/theme/components/Environment.vue')['default']
153+
const FeaturesAnimation: typeof import('./.vitepress/theme/components/FeaturesAnimation.vue')['default']
154+
const FeaturesAnimationInner: typeof import('./.vitepress/theme/components/FeaturesAnimationInner.vue')['default']
155+
const FeaturesOverview: typeof import('./.vitepress/theme/components/FeaturesOverview.vue')['default']
156+
const FeatureTag: typeof import('./.vitepress/theme/components/FeatureTag.vue')['default']
157+
const KaTexBlockWrapper: typeof import('./node_modules/@slidev/client/builtin/KaTexBlockWrapper.vue')['default']
158+
const LandingPage: typeof import('./.vitepress/theme/components/LandingPage.vue')['default']
159+
const Layout: typeof import('./.vitepress/theme/components/Layout.vue')['default']
160+
const LightOrDark: typeof import('./node_modules/@slidev/client/builtin/LightOrDark.vue')['default']
161+
const Link: typeof import('./node_modules/@slidev/client/builtin/Link.vue')['default']
162+
const LinkCard: typeof import('./.vitepress/theme/components/LinkCard.vue')['default']
163+
const LinkInline: typeof import('./.vitepress/theme/components/LinkInline.vue')['default']
164+
const LogosVue: typeof import('~icons/logos/vue')['default']
165+
const MdiAccountCircle: typeof import('~icons/mdi/account-circle')['default']
166+
const Mermaid: typeof import('./node_modules/@slidev/client/builtin/Mermaid.vue')['default']
167+
const Monaco: typeof import('./node_modules/@slidev/client/builtin/Monaco.vue')['default']
168+
const PlantUml: typeof import('./node_modules/@slidev/client/builtin/PlantUml.vue')['default']
169+
const PoweredBySlidev: typeof import('./node_modules/@slidev/client/builtin/PoweredBySlidev.vue')['default']
170+
const RenderWhen: typeof import('./node_modules/@slidev/client/builtin/RenderWhen.vue')['default']
171+
const RouterLink: typeof import('vue-router')['RouterLink']
172+
const RouterView: typeof import('vue-router')['RouterView']
173+
const SeeAlso: typeof import('./.vitepress/theme/components/SeeAlso.vue')['default']
174+
const ShikiMagicMove: typeof import('./node_modules/@slidev/client/builtin/ShikiMagicMove.vue')['default']
175+
const ShowCaseInfo: typeof import('./.vitepress/theme/components/ShowCaseInfo.vue')['default']
176+
const ShowCases: typeof import('./.vitepress/theme/components/ShowCases.vue')['default']
177+
const SimpleIconsGithub: typeof import('~icons/simple-icons/github')['default']
178+
const SimpleIconsNpm: typeof import('~icons/simple-icons/npm')['default']
179+
const SlideContainer: typeof import('./.vitepress/theme/components/SlideContainer.vue')['default']
180+
const SlideCurrentNo: typeof import('./node_modules/@slidev/client/builtin/SlideCurrentNo.vue')['default']
181+
const SlidesTotal: typeof import('./node_modules/@slidev/client/builtin/SlidesTotal.vue')['default']
182+
const SlidevVideo: typeof import('./node_modules/@slidev/client/builtin/SlidevVideo.vue')['default']
183+
const ThemeGallery: typeof import('./.vitepress/theme/components/ThemeGallery.vue')['default']
184+
const ThemeInfo: typeof import('./.vitepress/theme/components/ThemeInfo.vue')['default']
185+
const TheTweet: typeof import('./.vitepress/theme/components/TheTweet.vue')['default']
186+
const Toc: typeof import('./node_modules/@slidev/client/builtin/Toc.vue')['default']
187+
const TocList: typeof import('./node_modules/@slidev/client/builtin/TocList.vue')['default']
188+
const Transform: typeof import('./node_modules/@slidev/client/builtin/Transform.vue')['default']
189+
const Tweet: typeof import('./node_modules/@slidev/client/builtin/Tweet.vue')['default']
190+
const TwemojiCatWithTearsOfJoy: typeof import('~icons/twemoji/cat-with-tears-of-joy')['default']
191+
const UimRocket: typeof import('~icons/uim/rocket')['default']
192+
const VClickGap: typeof import('./node_modules/@slidev/client/builtin/VClickGap.vue')['default']
193+
const VDrag: typeof import('./node_modules/@slidev/client/builtin/VDrag.vue')['default']
194+
const VDragArrow: typeof import('./node_modules/@slidev/client/builtin/VDragArrow.vue')['default']
195+
const Youtube: typeof import('./node_modules/@slidev/client/builtin/Youtube.vue')['default']
196+
}

docs/features/notes-auto-ruby.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
---
2+
tags: [notes, presenter]
3+
description: Automatically add `<ruby>` tags to your notes.
4+
---
5+
6+
# Notes Auto Ruby
7+
8+
> Available since v52.4.0
9+
10+
When you write notes in your slides, you might want to add some ruby text to help pronouncing the some words. You can always add `<ruby>` tags to your notes manually, but Slidev also provides a convenient way to do this automatically by a global auto-replacement.
11+
12+
In the headmatter, you can set the `notesAutoRuby` option to a map of words to their ruby text:
13+
14+
```md
15+
---
16+
notesAutoRuby:
17+
日本語: ni hon go
18+
勉強: べんきょう
19+
---
20+
21+
# Your slides here
22+
23+
<!--
24+
私は日本語を勉強しています。
25+
-->
26+
```
27+
28+
And the notes will be rendered as:
29+
30+
<p>私は<ruby>日本語<rt>ni hon go</rt></ruby>を<ruby>勉強<rt>べんきょう</rt></ruby>しています。</p>
31+

packages/client/constants.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,4 +84,5 @@ export const HEADMATTER_FIELDS = [
8484
'contextMenu',
8585
'wakeLock',
8686
'seoMeta',
87+
'notesAutoRuby',
8788
]

packages/slidev/node/vite/loaders.ts

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -366,14 +366,35 @@ export function createSlidesLoader(
366366

367367
function renderNote(text: string = '') {
368368
let clickCount = 0
369-
const html = notesMd.render(text
370-
// replace [click] marker with span
369+
const notesAutoRuby: Record<string, string | undefined> = (data.headmatter as any).notesAutoRuby || {}
370+
371+
// Apply [click] marker
372+
let md = text
373+
// replace [click] marker with span
371374
.replace(/\[click(?::(\d+))?\]/gi, (_, count = 1) => {
372375
clickCount += Number(count)
373376
return `<span class="slidev-note-click-mark" data-clicks="${clickCount}"></span>`
374-
}),
375-
)
377+
})
378+
379+
// Apply notesAutoRuby
380+
const keys = Object.keys(notesAutoRuby)
381+
.sort((b, a) => b.length - a.length)
382+
// Add word boundaries to the keys when they are simple alphabets or numbers
383+
.map(i => /^[\w-]+$/.test(i) ? `\\b${i}\\b` : i)
384+
385+
if (keys.length) {
386+
const regex = new RegExp(`(${keys.join('|')})`, 'g')
387+
md = md.replace(
388+
regex,
389+
(match) => {
390+
if (notesAutoRuby[match])
391+
return `<ruby>${match}<rt>${notesAutoRuby[match]}</rt></ruby>`
392+
return match
393+
},
394+
)
395+
}
376396

397+
const html = notesMd.render(md)
377398
return html
378399
}
379400

packages/types/src/frontmatter.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,18 @@ export interface HeadmatterConfig extends TransitionOptions {
270270
* @default {}
271271
*/
272272
seoMeta?: SeoMeta
273+
/**
274+
* Auto replace words with `<ruby>` tags in notes
275+
*
276+
* @default {}
277+
*
278+
* @example
279+
* ```yaml
280+
* notesAutoRuby:
281+
* 大丈夫: だいじょうぶ
282+
* ```
283+
*/
284+
notesAutoRuby?: Record<string, string>
273285
}
274286

275287
export interface Frontmatter extends TransitionOptions {

packages/vscode/schema/headmatter.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -511,6 +511,15 @@
511511
"markdownDescription": "Seo meta tags settings",
512512
"default": {}
513513
},
514+
"notesAutoRuby": {
515+
"type": "object",
516+
"additionalProperties": {
517+
"type": "string"
518+
},
519+
"description": "Auto replace words with `<ruby>` tags in notes",
520+
"markdownDescription": "Auto replace words with `<ruby>` tags in notes",
521+
"default": {}
522+
},
514523
"defaults": {
515524
"$ref": "#/definitions/Frontmatter",
516525
"description": "Default frontmatter options applied to all slides",

0 commit comments

Comments
 (0)