@berlysia/vertical-writing-slide-system 0.0.21 → 0.0.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +49 -0
- package/package.json +1 -1
- package/src/App.tsx +17 -1
- package/src/index.css +1 -1
- package/src/script-manager.ts +210 -0
- package/src/vite-plugin-slides.ts +82 -7
package/README.md
CHANGED
|
@@ -6,6 +6,7 @@ A React-based slides application built with Vite that supports markdown-based pr
|
|
|
6
6
|
|
|
7
7
|
- Support for both vertical and horizontal writing modes (縦書き・横書き)
|
|
8
8
|
- MDX-based slide creation
|
|
9
|
+
- External script embedding with security validation
|
|
9
10
|
- Print mode support
|
|
10
11
|
- Browser compatibility across Chrome, Firefox, and Safari
|
|
11
12
|
|
|
@@ -60,6 +61,54 @@ Each presentation should be in its own directory containing:
|
|
|
60
61
|
|
|
61
62
|
- `index.mdx`: The main presentation file with your MDX content. \*.md is also fine
|
|
62
63
|
- `images/`: (Optional) Directory containing images used in the presentation
|
|
64
|
+
- `scripts.json`: (Optional) Configuration file for external scripts
|
|
65
|
+
- `style.css`: (Optional) Custom CSS styles for the presentation
|
|
66
|
+
|
|
67
|
+
## Script Embedding
|
|
68
|
+
|
|
69
|
+
You can embed external scripts in your slides for enhanced functionality:
|
|
70
|
+
|
|
71
|
+
### Method 1: Direct Script Tags in MDX
|
|
72
|
+
|
|
73
|
+
```mdx
|
|
74
|
+
<script
|
|
75
|
+
src="https://cdn.jsdelivr.net/npm/baseline-status@1/baseline-status.min.js"
|
|
76
|
+
type="module"
|
|
77
|
+
></script>
|
|
78
|
+
|
|
79
|
+
<script>console.log('Inline script executed');</script>
|
|
80
|
+
|
|
81
|
+
<Center>
|
|
82
|
+
<h1>External Script Example</h1>
|
|
83
|
+
<baseline-status feature="css-grid"></baseline-status>
|
|
84
|
+
</Center>
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### Method 2: Configuration File
|
|
88
|
+
|
|
89
|
+
Create a `scripts.json` file in your slide directory:
|
|
90
|
+
|
|
91
|
+
```json
|
|
92
|
+
{
|
|
93
|
+
"external": [
|
|
94
|
+
{
|
|
95
|
+
"src": "https://cdn.jsdelivr.net/npm/baseline-status@1/baseline-status.min.js",
|
|
96
|
+
"type": "module"
|
|
97
|
+
}
|
|
98
|
+
],
|
|
99
|
+
"inline": [
|
|
100
|
+
{
|
|
101
|
+
"content": "console.log('Script from configuration');"
|
|
102
|
+
}
|
|
103
|
+
]
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### Security Features
|
|
108
|
+
|
|
109
|
+
- Only trusted CDNs are allowed (jsdelivr, unpkg, cdnjs, etc.)
|
|
110
|
+
- Scripts are loaded only once per session
|
|
111
|
+
- Basic validation of script attributes and content
|
|
63
112
|
|
|
64
113
|
For development documentation, see [DEVELOPMENT.md](DEVELOPMENT.md).
|
|
65
114
|
|
package/package.json
CHANGED
package/src/App.tsx
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { useState, useRef, useEffect, useCallback } from "react";
|
|
2
|
-
import slidesContent from "virtual:slides.js";
|
|
2
|
+
import slidesContent, { slideScripts } from "virtual:slides.js";
|
|
3
|
+
import { globalScriptManager } from "./script-manager";
|
|
3
4
|
|
|
4
5
|
function App() {
|
|
5
6
|
const [writingMode, setWritingMode] = useState(() => {
|
|
@@ -34,6 +35,21 @@ function App() {
|
|
|
34
35
|
);
|
|
35
36
|
}, [withAbsoluteFontSize]);
|
|
36
37
|
|
|
38
|
+
// スクリプトの読み込み
|
|
39
|
+
useEffect(() => {
|
|
40
|
+
if (
|
|
41
|
+
slideScripts &&
|
|
42
|
+
(slideScripts.external.length > 0 || slideScripts.inline.length > 0)
|
|
43
|
+
) {
|
|
44
|
+
console.log("[App] Loading slide scripts:", slideScripts);
|
|
45
|
+
globalScriptManager.loadScripts(slideScripts).catch(console.error);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return () => {
|
|
49
|
+
// クリーンアップは慎重に行う(全てのスクリプトを削除すると他の機能に影響する場合がある)
|
|
50
|
+
};
|
|
51
|
+
}, []);
|
|
52
|
+
|
|
37
53
|
// ロード時にハッシュが入ってたらそのページにスクロール
|
|
38
54
|
useEffect(() => {
|
|
39
55
|
const hash = location.hash;
|
package/src/index.css
CHANGED
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
export interface ScriptInfo {
|
|
2
|
+
src?: string;
|
|
3
|
+
type?: string;
|
|
4
|
+
async?: boolean;
|
|
5
|
+
defer?: boolean;
|
|
6
|
+
crossorigin?: string;
|
|
7
|
+
integrity?: string;
|
|
8
|
+
content?: string;
|
|
9
|
+
id?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface ParsedScript {
|
|
13
|
+
external: ScriptInfo[];
|
|
14
|
+
inline: ScriptInfo[];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export class ScriptManager {
|
|
18
|
+
private loadedScripts = new Set<string>();
|
|
19
|
+
private scriptElements = new Map<string, HTMLScriptElement>();
|
|
20
|
+
|
|
21
|
+
static parseScripts(content: string): ParsedScript {
|
|
22
|
+
const external: ScriptInfo[] = [];
|
|
23
|
+
const inline: ScriptInfo[] = [];
|
|
24
|
+
|
|
25
|
+
const scriptRegex = /<script([^>]*)>([\s\S]*?)<\/script>/gi;
|
|
26
|
+
let match;
|
|
27
|
+
|
|
28
|
+
while ((match = scriptRegex.exec(content)) !== null) {
|
|
29
|
+
const attributes = match[1];
|
|
30
|
+
const scriptContent = match[2].trim();
|
|
31
|
+
|
|
32
|
+
const scriptInfo: ScriptInfo = {};
|
|
33
|
+
|
|
34
|
+
const srcMatch = attributes.match(/src\s*=\s*["']([^"']+)["']/);
|
|
35
|
+
if (srcMatch) {
|
|
36
|
+
scriptInfo.src = srcMatch[1];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const typeMatch = attributes.match(/type\s*=\s*["']([^"']+)["']/);
|
|
40
|
+
if (typeMatch) {
|
|
41
|
+
scriptInfo.type = typeMatch[1];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const asyncMatch = attributes.match(/\basync\b/);
|
|
45
|
+
if (asyncMatch) {
|
|
46
|
+
scriptInfo.async = true;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const deferMatch = attributes.match(/\bdefer\b/);
|
|
50
|
+
if (deferMatch) {
|
|
51
|
+
scriptInfo.defer = true;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const crossoriginMatch = attributes.match(
|
|
55
|
+
/crossorigin\s*=\s*["']([^"']+)["']/,
|
|
56
|
+
);
|
|
57
|
+
if (crossoriginMatch) {
|
|
58
|
+
scriptInfo.crossorigin = crossoriginMatch[1];
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const integrityMatch = attributes.match(
|
|
62
|
+
/integrity\s*=\s*["']([^"']+)["']/,
|
|
63
|
+
);
|
|
64
|
+
if (integrityMatch) {
|
|
65
|
+
scriptInfo.integrity = integrityMatch[1];
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const idMatch = attributes.match(/id\s*=\s*["']([^"']+)["']/);
|
|
69
|
+
if (idMatch) {
|
|
70
|
+
scriptInfo.id = idMatch[1];
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (scriptInfo.src) {
|
|
74
|
+
external.push(scriptInfo);
|
|
75
|
+
} else if (scriptContent) {
|
|
76
|
+
scriptInfo.content = scriptContent;
|
|
77
|
+
inline.push(scriptInfo);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return { external, inline };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
static removeScriptsFromContent(content: string): string {
|
|
85
|
+
return content.replace(/<script[^>]*>[\s\S]*?<\/script>/gi, "");
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
static isValidExternalScript(src: string): boolean {
|
|
89
|
+
try {
|
|
90
|
+
const url = new URL(src);
|
|
91
|
+
const allowedProtocols = ["https:", "http:"];
|
|
92
|
+
const trustedDomains = [
|
|
93
|
+
"cdn.jsdelivr.net",
|
|
94
|
+
"unpkg.com",
|
|
95
|
+
"cdnjs.cloudflare.com",
|
|
96
|
+
"code.jquery.com",
|
|
97
|
+
"stackpath.bootstrapcdn.com",
|
|
98
|
+
];
|
|
99
|
+
|
|
100
|
+
if (!allowedProtocols.includes(url.protocol)) {
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return trustedDomains.some(
|
|
105
|
+
(domain) =>
|
|
106
|
+
url.hostname === domain || url.hostname.endsWith(`.${domain}`),
|
|
107
|
+
);
|
|
108
|
+
} catch {
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async loadScript(scriptInfo: ScriptInfo): Promise<void> {
|
|
114
|
+
if (scriptInfo.src) {
|
|
115
|
+
return this.loadExternalScript(scriptInfo);
|
|
116
|
+
} else if (scriptInfo.content) {
|
|
117
|
+
return this.loadInlineScript(scriptInfo);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
private async loadExternalScript(scriptInfo: ScriptInfo): Promise<void> {
|
|
122
|
+
const { src } = scriptInfo;
|
|
123
|
+
if (!src) return;
|
|
124
|
+
|
|
125
|
+
if (!ScriptManager.isValidExternalScript(src)) {
|
|
126
|
+
console.warn(
|
|
127
|
+
`[ScriptManager] External script blocked for security: ${src}`,
|
|
128
|
+
);
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const scriptId = scriptInfo.id || `script-${src}`;
|
|
133
|
+
|
|
134
|
+
if (this.loadedScripts.has(scriptId)) {
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return new Promise((resolve, reject) => {
|
|
139
|
+
const script = document.createElement("script");
|
|
140
|
+
|
|
141
|
+
script.src = src;
|
|
142
|
+
if (scriptInfo.type) script.type = scriptInfo.type;
|
|
143
|
+
if (scriptInfo.async) script.async = true;
|
|
144
|
+
if (scriptInfo.defer) script.defer = true;
|
|
145
|
+
if (scriptInfo.crossorigin) script.crossOrigin = scriptInfo.crossorigin;
|
|
146
|
+
if (scriptInfo.integrity) script.integrity = scriptInfo.integrity;
|
|
147
|
+
if (scriptInfo.id) script.id = scriptInfo.id;
|
|
148
|
+
|
|
149
|
+
script.onload = () => {
|
|
150
|
+
this.loadedScripts.add(scriptId);
|
|
151
|
+
this.scriptElements.set(scriptId, script);
|
|
152
|
+
console.log(`[ScriptManager] Loaded external script: ${src}`);
|
|
153
|
+
resolve();
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
script.onerror = () => {
|
|
157
|
+
console.error(`[ScriptManager] Failed to load script: ${src}`);
|
|
158
|
+
reject(new Error(`Failed to load script: ${src}`));
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
document.head.appendChild(script);
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
private async loadInlineScript(scriptInfo: ScriptInfo): Promise<void> {
|
|
166
|
+
const { content, id } = scriptInfo;
|
|
167
|
+
if (!content) return;
|
|
168
|
+
|
|
169
|
+
const scriptId = id || `inline-script-${Date.now()}`;
|
|
170
|
+
|
|
171
|
+
if (this.loadedScripts.has(scriptId)) {
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const script = document.createElement("script");
|
|
176
|
+
if (scriptInfo.type) script.type = scriptInfo.type;
|
|
177
|
+
if (scriptInfo.id) script.id = scriptInfo.id;
|
|
178
|
+
script.textContent = content;
|
|
179
|
+
|
|
180
|
+
document.head.appendChild(script);
|
|
181
|
+
this.loadedScripts.add(scriptId);
|
|
182
|
+
this.scriptElements.set(scriptId, script);
|
|
183
|
+
|
|
184
|
+
console.log(`[ScriptManager] Loaded inline script: ${scriptId}`);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
cleanup(): void {
|
|
188
|
+
for (const [scriptId, element] of this.scriptElements) {
|
|
189
|
+
if (element.parentNode) {
|
|
190
|
+
element.parentNode.removeChild(element);
|
|
191
|
+
}
|
|
192
|
+
this.loadedScripts.delete(scriptId);
|
|
193
|
+
}
|
|
194
|
+
this.scriptElements.clear();
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
async loadScripts(scripts: ParsedScript): Promise<void> {
|
|
198
|
+
const allScripts = [...scripts.external, ...scripts.inline];
|
|
199
|
+
|
|
200
|
+
for (const script of allScripts) {
|
|
201
|
+
try {
|
|
202
|
+
await this.loadScript(script);
|
|
203
|
+
} catch (error) {
|
|
204
|
+
console.error("[ScriptManager] Failed to load script:", error);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
export const globalScriptManager = new ScriptManager();
|
|
@@ -9,6 +9,7 @@ import remarkParse from "remark-parse";
|
|
|
9
9
|
import remarkRehype from "remark-rehype";
|
|
10
10
|
import rehypeStringify from "rehype-stringify";
|
|
11
11
|
import remarkSlideImages from "./remark-slide-images";
|
|
12
|
+
import { ScriptManager, type ParsedScript } from "./script-manager";
|
|
12
13
|
import { visit } from "unist-util-visit";
|
|
13
14
|
import type { Node } from "unist";
|
|
14
15
|
import type { Element, Text, ElementContent } from "hast";
|
|
@@ -46,14 +47,21 @@ async function processMarkdown(
|
|
|
46
47
|
markdown: string,
|
|
47
48
|
base: string,
|
|
48
49
|
extractedStyles: string[],
|
|
50
|
+
extractedScripts: ParsedScript,
|
|
49
51
|
) {
|
|
52
|
+
const parsedScripts = ScriptManager.parseScripts(markdown);
|
|
53
|
+
extractedScripts.external.push(...parsedScripts.external);
|
|
54
|
+
extractedScripts.inline.push(...parsedScripts.inline);
|
|
55
|
+
|
|
56
|
+
const cleanedMarkdown = ScriptManager.removeScriptsFromContent(markdown);
|
|
57
|
+
|
|
50
58
|
return await unified()
|
|
51
59
|
.use(remarkParse)
|
|
52
60
|
.use(remarkSlideImages, { base })
|
|
53
61
|
.use(remarkRehype, { allowDangerousHtml: true })
|
|
54
62
|
.use(rehypeExtractStyles(extractedStyles))
|
|
55
63
|
.use(rehypeStringify, { allowDangerousHtml: true })
|
|
56
|
-
.process(
|
|
64
|
+
.process(cleanedMarkdown);
|
|
57
65
|
}
|
|
58
66
|
|
|
59
67
|
/**
|
|
@@ -78,6 +86,43 @@ function loadAdjacentCSS(slidesDir: string, collection: string): string[] {
|
|
|
78
86
|
return [];
|
|
79
87
|
}
|
|
80
88
|
|
|
89
|
+
/**
|
|
90
|
+
* 隣接スクリプト設定ファイルを検索して読み込み
|
|
91
|
+
*/
|
|
92
|
+
function loadAdjacentScripts(
|
|
93
|
+
slidesDir: string,
|
|
94
|
+
collection: string,
|
|
95
|
+
): ParsedScript {
|
|
96
|
+
const collectionDir = path.resolve(slidesDir, collection);
|
|
97
|
+
const scriptsPath = path.resolve(collectionDir, "scripts.json");
|
|
98
|
+
|
|
99
|
+
const result: ParsedScript = { external: [], inline: [] };
|
|
100
|
+
|
|
101
|
+
if (fs.existsSync(scriptsPath)) {
|
|
102
|
+
try {
|
|
103
|
+
const scriptsConfig = JSON.parse(fs.readFileSync(scriptsPath, "utf-8"));
|
|
104
|
+
|
|
105
|
+
if (scriptsConfig.external && Array.isArray(scriptsConfig.external)) {
|
|
106
|
+
result.external.push(...scriptsConfig.external);
|
|
107
|
+
logger.info(
|
|
108
|
+
`Loaded ${scriptsConfig.external.length} external scripts from scripts.json`,
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (scriptsConfig.inline && Array.isArray(scriptsConfig.inline)) {
|
|
113
|
+
result.inline.push(...scriptsConfig.inline);
|
|
114
|
+
logger.info(
|
|
115
|
+
`Loaded ${scriptsConfig.inline.length} inline scripts from scripts.json`,
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
} catch (error) {
|
|
119
|
+
logger.warn("Failed to parse scripts.json:", error);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return result;
|
|
124
|
+
}
|
|
125
|
+
|
|
81
126
|
export interface SlidesPluginOptions {
|
|
82
127
|
/** Directory containing the slides (absolute path) */
|
|
83
128
|
slidesDir?: string;
|
|
@@ -184,6 +229,7 @@ export default async function slidesPlugin(
|
|
|
184
229
|
let compiledSlides: string[] = [];
|
|
185
230
|
let resolvedConfig: ResolvedConfig;
|
|
186
231
|
let slideStyles: string[] = [];
|
|
232
|
+
let slideScripts: ParsedScript = { external: [], inline: [] };
|
|
187
233
|
return {
|
|
188
234
|
name: "vite-plugin-slides",
|
|
189
235
|
configResolved(config: ResolvedConfig) {
|
|
@@ -248,25 +294,39 @@ export default async function slidesPlugin(
|
|
|
248
294
|
);
|
|
249
295
|
slideStyles = [...adjacentStyles];
|
|
250
296
|
|
|
297
|
+
// 隣接スクリプト設定ファイルを読み込み
|
|
298
|
+
const adjacentScripts = loadAdjacentScripts(
|
|
299
|
+
config.slidesDir,
|
|
300
|
+
config.collection,
|
|
301
|
+
);
|
|
302
|
+
slideScripts = {
|
|
303
|
+
external: [...adjacentScripts.external],
|
|
304
|
+
inline: [...adjacentScripts.inline],
|
|
305
|
+
};
|
|
306
|
+
|
|
251
307
|
if (!isMdx) {
|
|
252
308
|
const slides = content.split(/^\s*(?:---|\*\*\*|___)\s*$/m);
|
|
253
309
|
const extractedStyles: string[] = [];
|
|
310
|
+
const extractedScripts: ParsedScript = { external: [], inline: [] };
|
|
254
311
|
const processedSlides = await Promise.all(
|
|
255
312
|
slides.map((slide) =>
|
|
256
|
-
processMarkdown(slide, base, extractedStyles),
|
|
313
|
+
processMarkdown(slide, base, extractedStyles, extractedScripts),
|
|
257
314
|
),
|
|
258
315
|
);
|
|
259
316
|
|
|
260
|
-
//
|
|
317
|
+
// 抽出されたスタイル・スクリプトを追加
|
|
261
318
|
slideStyles = [...slideStyles, ...extractedStyles];
|
|
319
|
+
slideScripts.external.push(...extractedScripts.external);
|
|
320
|
+
slideScripts.inline.push(...extractedScripts.inline);
|
|
262
321
|
|
|
263
322
|
return `export default ${JSON.stringify(processedSlides.map((p) => p.value))}`;
|
|
264
323
|
}
|
|
265
324
|
|
|
266
325
|
const slides = content.split(/^\s*(?:---|\*\*\*|___)\s*$/m);
|
|
267
326
|
|
|
268
|
-
// MDXにもCSS
|
|
327
|
+
// MDXにもCSS・スクリプト抽出を適用
|
|
269
328
|
const extractedStyles: string[] = [];
|
|
329
|
+
const extractedScripts: ParsedScript = { external: [], inline: [] };
|
|
270
330
|
const processedSlides = await Promise.all(
|
|
271
331
|
slides.map(async (slideContent) => {
|
|
272
332
|
// MDX内のstyleタグを手動で抽出(簡易実装)
|
|
@@ -278,8 +338,15 @@ export default async function slidesPlugin(
|
|
|
278
338
|
}
|
|
279
339
|
}
|
|
280
340
|
|
|
281
|
-
//
|
|
282
|
-
const
|
|
341
|
+
// MDX内のscriptタグを抽出
|
|
342
|
+
const parsedScripts = ScriptManager.parseScripts(slideContent);
|
|
343
|
+
extractedScripts.external.push(...parsedScripts.external);
|
|
344
|
+
extractedScripts.inline.push(...parsedScripts.inline);
|
|
345
|
+
|
|
346
|
+
// style・scriptタグを削除したコンテンツでMDXコンパイル
|
|
347
|
+
let cleanedContent = slideContent.replace(styleRegex, "");
|
|
348
|
+
cleanedContent =
|
|
349
|
+
ScriptManager.removeScriptsFromContent(cleanedContent);
|
|
283
350
|
|
|
284
351
|
const result = await compile(cleanedContent, {
|
|
285
352
|
outputFormat: "program",
|
|
@@ -292,8 +359,10 @@ export default async function slidesPlugin(
|
|
|
292
359
|
}),
|
|
293
360
|
);
|
|
294
361
|
|
|
295
|
-
//
|
|
362
|
+
// 抽出されたスタイル・スクリプトを追加
|
|
296
363
|
slideStyles = [...slideStyles, ...extractedStyles];
|
|
364
|
+
slideScripts.external.push(...extractedScripts.external);
|
|
365
|
+
slideScripts.inline.push(...extractedScripts.inline);
|
|
297
366
|
|
|
298
367
|
compiledSlides = processedSlides;
|
|
299
368
|
|
|
@@ -322,6 +391,9 @@ export default async function slidesPlugin(
|
|
|
322
391
|
? JSON.stringify(slideStyles.join("\n\n"))
|
|
323
392
|
: "null";
|
|
324
393
|
|
|
394
|
+
// スライド固有のスクリプトを文字列として生成
|
|
395
|
+
const slideScriptsString = JSON.stringify(slideScripts);
|
|
396
|
+
|
|
325
397
|
// Return as a module with CSS injection
|
|
326
398
|
return `
|
|
327
399
|
${slideComponentsFilenames.map((filename) => `import * as ${filenameToComponentName(filename)} from '@components/${filename}';`).join("\n")}
|
|
@@ -344,6 +416,9 @@ export default async function slidesPlugin(
|
|
|
344
416
|
}
|
|
345
417
|
}
|
|
346
418
|
|
|
419
|
+
// スライド固有のスクリプトを外部から利用可能にする
|
|
420
|
+
export const slideScripts = ${slideScriptsString};
|
|
421
|
+
|
|
347
422
|
// provide slide components to each slide
|
|
348
423
|
// Wrap SlideN components to provide SlideComponents
|
|
349
424
|
${compiledSlides
|