@berlysia/vertical-writing-slide-system 0.0.22 → 0.0.24
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/script-manager.ts +210 -0
- package/src/vite-plugin-slides.ts +89 -8
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;
|
|
@@ -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,45 @@ 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
|
+
// スライド固有のスクリプトを文字列として生成
|
|
323
|
+
const slideScriptsString = JSON.stringify(slideScripts);
|
|
324
|
+
|
|
325
|
+
return `
|
|
326
|
+
export default ${JSON.stringify(processedSlides.map((p) => p.value))};
|
|
327
|
+
export const slideScripts = ${slideScriptsString};
|
|
328
|
+
`;
|
|
264
329
|
}
|
|
265
330
|
|
|
266
331
|
const slides = content.split(/^\s*(?:---|\*\*\*|___)\s*$/m);
|
|
267
332
|
|
|
268
|
-
// MDXにもCSS
|
|
333
|
+
// MDXにもCSS・スクリプト抽出を適用
|
|
269
334
|
const extractedStyles: string[] = [];
|
|
335
|
+
const extractedScripts: ParsedScript = { external: [], inline: [] };
|
|
270
336
|
const processedSlides = await Promise.all(
|
|
271
337
|
slides.map(async (slideContent) => {
|
|
272
338
|
// MDX内のstyleタグを手動で抽出(簡易実装)
|
|
@@ -278,8 +344,15 @@ export default async function slidesPlugin(
|
|
|
278
344
|
}
|
|
279
345
|
}
|
|
280
346
|
|
|
281
|
-
//
|
|
282
|
-
const
|
|
347
|
+
// MDX内のscriptタグを抽出
|
|
348
|
+
const parsedScripts = ScriptManager.parseScripts(slideContent);
|
|
349
|
+
extractedScripts.external.push(...parsedScripts.external);
|
|
350
|
+
extractedScripts.inline.push(...parsedScripts.inline);
|
|
351
|
+
|
|
352
|
+
// style・scriptタグを削除したコンテンツでMDXコンパイル
|
|
353
|
+
let cleanedContent = slideContent.replace(styleRegex, "");
|
|
354
|
+
cleanedContent =
|
|
355
|
+
ScriptManager.removeScriptsFromContent(cleanedContent);
|
|
283
356
|
|
|
284
357
|
const result = await compile(cleanedContent, {
|
|
285
358
|
outputFormat: "program",
|
|
@@ -292,8 +365,10 @@ export default async function slidesPlugin(
|
|
|
292
365
|
}),
|
|
293
366
|
);
|
|
294
367
|
|
|
295
|
-
//
|
|
368
|
+
// 抽出されたスタイル・スクリプトを追加
|
|
296
369
|
slideStyles = [...slideStyles, ...extractedStyles];
|
|
370
|
+
slideScripts.external.push(...extractedScripts.external);
|
|
371
|
+
slideScripts.inline.push(...extractedScripts.inline);
|
|
297
372
|
|
|
298
373
|
compiledSlides = processedSlides;
|
|
299
374
|
|
|
@@ -322,6 +397,9 @@ export default async function slidesPlugin(
|
|
|
322
397
|
? JSON.stringify(slideStyles.join("\n\n"))
|
|
323
398
|
: "null";
|
|
324
399
|
|
|
400
|
+
// スライド固有のスクリプトを文字列として生成
|
|
401
|
+
const slideScriptsString = JSON.stringify(slideScripts);
|
|
402
|
+
|
|
325
403
|
// Return as a module with CSS injection
|
|
326
404
|
return `
|
|
327
405
|
${slideComponentsFilenames.map((filename) => `import * as ${filenameToComponentName(filename)} from '@components/${filename}';`).join("\n")}
|
|
@@ -344,6 +422,9 @@ export default async function slidesPlugin(
|
|
|
344
422
|
}
|
|
345
423
|
}
|
|
346
424
|
|
|
425
|
+
// スライド固有のスクリプトを外部から利用可能にする
|
|
426
|
+
export const slideScripts = ${slideScriptsString};
|
|
427
|
+
|
|
347
428
|
// provide slide components to each slide
|
|
348
429
|
// Wrap SlideN components to provide SlideComponents
|
|
349
430
|
${compiledSlides
|