Skip to content

Commit dc818c7

Browse files
committed
#58 - Listen to theme changes
1 parent 8d1626f commit dc818c7

File tree

8 files changed

+96
-38
lines changed

8 files changed

+96
-38
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Change Log
22

3+
## [0.0.85] - 2025-03-xx
4+
5+
- [#58](https://github.com/estruyf/vscode-demo-time/issues/58): Support theme changes in Shiki codeblocks on slides
6+
37
## [0.0.84] - 2025-03-18
48

59
- Fix in dispose check on the presenter webview

src/preview/Preview.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,8 @@ export class Preview {
104104
Preview.postRequestMessage(WebViewMessages.toVscode.getStyles, requestId, cssWebviewPath);
105105
} else if (command === WebViewMessages.toVscode.getTheme && requestId) {
106106
try {
107-
const theme = await getTheme();
107+
const themeName = payload || "";
108+
const theme = await getTheme(themeName);
108109
Preview.postRequestMessage(WebViewMessages.toVscode.getTheme, requestId, theme);
109110
} catch (e) {
110111
// This can happen in a Dev Container where the theme is not available

src/preview/components/Markdown.tsx

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,6 @@ export const Markdown: React.FunctionComponent<IMarkdownProps> = ({
2525
setMarkdown,
2626
matter
2727
} = useRemark({
28-
rehypePlugins: [
29-
[rehypePrettyCode, {
30-
theme: vsCodeTheme ? vsCodeTheme : {},
31-
}]
32-
],
3328
rehypeReactOptions: {
3429
components: {
3530
img: ({ node, src, ...props }) => {
@@ -52,7 +47,7 @@ export const Markdown: React.FunctionComponent<IMarkdownProps> = ({
5247
updateLayout(matter?.layout || "default");
5348

5449
if (matter?.image) {
55-
const img = transformImageUrl(webviewUrl || "", matter?.image)
50+
const img = transformImageUrl(webviewUrl || "", matter?.image);
5651
updateBgStyles({
5752
color: 'white',
5853
backgroundImage: `url(${img})`,
@@ -67,9 +62,10 @@ export const Markdown: React.FunctionComponent<IMarkdownProps> = ({
6762

6863
React.useEffect(() => {
6964
if (content) {
70-
setMarkdown(twoColumnFormatting(content));
65+
// Passing the theme here as it could be that the theme has been updated
66+
setMarkdown(twoColumnFormatting(content), [[rehypePrettyCode, { theme: vsCodeTheme ? vsCodeTheme : {} }]]);
7167
}
72-
}, [content]);
68+
}, [content, vsCodeTheme]);
7369

7470
return (
7571
<>

src/preview/components/MarkdownPreview.tsx

Lines changed: 3 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
import * as React from 'react';
2-
import { messageHandler, Messenger } from '@estruyf/vscode/dist/client/webview';
2+
import { Messenger } from '@estruyf/vscode/dist/client/webview';
33
import { SlideLayout, WebViewMessages } from '../../constants';
44
import { Markdown } from './Markdown';
55
import { EventData } from '@estruyf/vscode';
66
import { useScale } from '../hooks/useScale';
77
import { useFileContents } from '../hooks/useFileContents';
88
import useCursor from '../hooks/useCursor';
99
import { SlideControls } from './SlideControls';
10+
import useTheme from '../hooks/useTheme';
1011

1112
export interface IMarkdownPreviewProps {
1213
fileUri: string;
@@ -18,14 +19,14 @@ export const MarkdownPreview: React.FunctionComponent<IMarkdownPreviewProps> = (
1819
webviewUrl
1920
}: React.PropsWithChildren<IMarkdownPreviewProps>) => {
2021
const { content, crntFilePath, getFileContents } = useFileContents();
21-
const [vsCodeTheme, setVsCodeTheme] = React.useState<any | undefined>(undefined);
2222
const [theme, setTheme] = React.useState<string | undefined>(undefined);
2323
const [layout, setLayout] = React.useState<string | undefined>(undefined);
2424
const [bgStyles, setBgStyles] = React.useState<any | null>(null);
2525
const [showControls, setShowControls] = React.useState(false);
2626
const ref = React.useRef<HTMLDivElement>(null);
2727
const slideRef = React.useRef<HTMLDivElement>(null);
2828
const { cursorVisible, resetCursorTimeout } = useCursor();
29+
const { vsCodeTheme } = useTheme();
2930
useScale(ref, slideRef);
3031

3132
const messageListener = (message: MessageEvent<EventData<any>>) => {
@@ -68,24 +69,6 @@ export const MarkdownPreview: React.FunctionComponent<IMarkdownPreviewProps> = (
6869
React.useEffect(() => {
6970
Messenger.listen(messageListener);
7071

71-
messageHandler.request<any | null>(WebViewMessages.toVscode.getTheme).then((theme) => {
72-
if (theme === null) {
73-
// Check if light or dark theme
74-
const elm = document.body.getAttribute(`data-vscode-theme-kind`);
75-
if (elm === 'vscode-light') {
76-
setVsCodeTheme("github-light");
77-
} else if (elm === 'vscode-dark') {
78-
setVsCodeTheme("github-dark");
79-
} else if (elm === 'vscode-high-contrast') {
80-
setVsCodeTheme("github-dark-high-contrast");
81-
} else {
82-
setVsCodeTheme("github-light-high-contrast");
83-
}
84-
} else {
85-
setVsCodeTheme(theme);
86-
}
87-
});
88-
8972
return () => {
9073
Messenger.unlisten(messageListener);
9174
};

src/preview/hooks/usePrevious.tsx

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { useEffect, useRef } from 'react';
2+
3+
export function usePrevious<T>(value: T): T | undefined {
4+
const ref = useRef<T>();
5+
6+
useEffect(() => {
7+
ref.current = value;
8+
}, [value]);
9+
10+
return ref.current;
11+
}

src/preview/hooks/useRemark.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,13 @@ export const useRemark = ({
3030
remarkToRehypeOptions,
3131
}: UseRemarkOptions = {}): {
3232
markdown: null | ReactElement,
33-
setMarkdown: (source: string) => void,
33+
setMarkdown: (source: string, customPlugins?: PluggableList) => void,
3434
matter: null | any,
3535
} => {
3636
const [reactContent, setReactContent] = useState<null | ReactElement>(null);
3737
const [metadata, setMetadata] = useState<null | any>(null);
3838

39-
const setMarkdownSource = useCallback((source: string) => {
39+
const setMarkdownSource = useCallback((source: string, customPlugins?: PluggableList) => {
4040
unified()
4141
.use(remarkParse, remarkParseOptions)
4242
.use(remarkToRehype, {
@@ -45,7 +45,7 @@ export const useRemark = ({
4545
})
4646
.use(rehypeRaw)
4747
.use(remarkPlugins)
48-
.use(rehypePlugins)
48+
.use([...rehypePlugins, ...(customPlugins || [])])
4949
.use(rehypeReact, {
5050
...rehypeReactOptions,
5151
Fragment: jsxRuntime.Fragment,

src/preview/hooks/useTheme.tsx

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import { messageHandler } from '@estruyf/vscode/dist/client/webview';
2+
import { useState, useEffect } from 'react';
3+
import { WebViewMessages } from '../../constants';
4+
import { usePrevious } from './usePrevious';
5+
6+
export default function useTheme(options?: any) {
7+
const [theme, setTheme] = useState('');
8+
const prevTheme = usePrevious(theme);
9+
const [vsCodeTheme, setVsCodeTheme] = useState('');
10+
11+
const getThemeName = () => {
12+
return document.body.getAttribute("data-vscode-theme-name") || '';
13+
};
14+
15+
const mutationObserver = new MutationObserver((_, __) => {
16+
setTheme(getThemeName());
17+
});
18+
19+
const getVsCodeTheme = () => {
20+
21+
messageHandler.request<any | null>(WebViewMessages.toVscode.getTheme, getThemeName()).then((theme) => {
22+
console.log('getVsCodeTheme', theme);
23+
if (theme === null) {
24+
// Check if light or dark theme
25+
const elm = document.body.getAttribute(`data-vscode-theme-kind`);
26+
if (elm === 'vscode-light') {
27+
setVsCodeTheme("github-light");
28+
} else if (elm === 'vscode-dark') {
29+
setVsCodeTheme("github-dark");
30+
} else if (elm === 'vscode-high-contrast') {
31+
setVsCodeTheme("github-dark-high-contrast");
32+
} else {
33+
setVsCodeTheme("github-light-high-contrast");
34+
}
35+
} else {
36+
setVsCodeTheme(theme);
37+
}
38+
});
39+
};
40+
41+
useEffect(() => {
42+
console.log('Theme changed', theme);
43+
console.log('prevTheme changed', prevTheme);
44+
if (theme !== prevTheme) {
45+
getVsCodeTheme();
46+
}
47+
}, [theme, prevTheme]);
48+
49+
useEffect(() => {
50+
mutationObserver.observe(document.body, { childList: false, attributes: true });
51+
getVsCodeTheme();
52+
53+
return () => {
54+
mutationObserver.disconnect();
55+
};
56+
}, []);
57+
58+
return {
59+
vsCodeTheme
60+
};
61+
}

src/utils/getTheme.ts

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,36 +2,38 @@ import { extensions, Uri, workspace } from "vscode";
22
import { Theme } from "../models";
33
import { readFile } from ".";
44

5-
export const getTheme = async () => {
6-
let crntTheme = workspace.getConfiguration("workbench").get("colorTheme");
5+
export const getTheme = async (themeName?: string) => {
6+
let crntTheme = workspace.getConfiguration("workbench").get("colorTheme") as string;
77

88
// Get all the theme extensions
99
const allExtensions = extensions.all.filter((e) => {
1010
const pkg = e.packageJSON;
1111
return pkg.contributes && pkg.contributes.themes && pkg.contributes.themes.length > 0;
1212
});
1313

14+
themeName = !themeName || themeName === "" ? crntTheme : themeName;
15+
1416
// Get the theme extension that matches the active theme
1517
const themeExtension = allExtensions.find((e) => {
1618
const pkg = e.packageJSON;
17-
return pkg.contributes.themes.find((theme: Theme) => theme.label === crntTheme || theme.id === crntTheme);
19+
return pkg.contributes.themes.find((theme: Theme) => theme.label === themeName || theme.id === themeName);
1820
});
1921

2022
if (!themeExtension) {
21-
throw new Error(`Could not find theme extension for ${crntTheme}`);
23+
throw new Error(`Could not find theme extension for ${themeName}`);
2224
}
2325

2426
// Get the theme file
2527
const themeFile: Theme = themeExtension.packageJSON.contributes.themes.find(
26-
(theme: Theme) => theme.label === crntTheme || theme.id === crntTheme
28+
(theme: Theme) => theme.label === themeName || theme.id === themeName
2729
);
2830

2931
const themePath = Uri.joinPath(themeExtension.extensionUri, themeFile.path);
3032
const fileContents = await readFile(themePath);
3133

3234
const theme = JSON.parse(fileContents);
3335
if (!theme) {
34-
throw new Error(`Could not find theme file for ${crntTheme}`);
36+
throw new Error(`Could not find theme file for ${themeName}`);
3537
}
3638

3739
if (theme.include) {

0 commit comments

Comments
 (0)