@berlysia/vertical-writing-slide-system 0.0.16 → 0.0.18
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/cli.js +15 -7
- package/package.json +5 -2
- package/src/vite-plugin-slides.ts +118 -7
package/cli.js
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// @ts-check
|
|
3
3
|
|
|
4
|
-
import {
|
|
5
|
-
import { mkdir, mkdtemp, cp, rm, writeFile, access } from "node:fs/promises";
|
|
4
|
+
import { writeFile, access } from "node:fs/promises";
|
|
6
5
|
import { resolve } from "node:path";
|
|
7
6
|
import { parseArgs } from "node:util";
|
|
8
7
|
import { build, createServer } from "vite";
|
|
@@ -15,7 +14,13 @@ async function ensureIndexHtml() {
|
|
|
15
14
|
return;
|
|
16
15
|
} catch {
|
|
17
16
|
// index.html doesn't exist, create it
|
|
18
|
-
// Use
|
|
17
|
+
// Use absolute paths from the library location for external projects
|
|
18
|
+
const libPath = import.meta.dirname;
|
|
19
|
+
const libSrcPath = resolve(libPath, "src");
|
|
20
|
+
|
|
21
|
+
// Convert to relative paths from project root that Vite can resolve
|
|
22
|
+
const relativeSrcPath = `/@fs${libSrcPath}`;
|
|
23
|
+
|
|
19
24
|
const indexHtmlContent = `<!doctype html>
|
|
20
25
|
<html lang="ja">
|
|
21
26
|
<head>
|
|
@@ -26,13 +31,13 @@ async function ensureIndexHtml() {
|
|
|
26
31
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
27
32
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
28
33
|
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+JP&family=Noto+Sans+Mono:wght@100..900&display=swap" rel="stylesheet">
|
|
29
|
-
<link rel="stylesheet" href="/
|
|
30
|
-
<link rel="stylesheet" media="screen" href="/
|
|
31
|
-
<link rel="stylesheet" media="print" href="/
|
|
34
|
+
<link rel="stylesheet" href="${relativeSrcPath}/index.css" />
|
|
35
|
+
<link rel="stylesheet" media="screen" href="${relativeSrcPath}/screen.css" />
|
|
36
|
+
<link rel="stylesheet" media="print" href="${relativeSrcPath}/print.css" />
|
|
32
37
|
</head>
|
|
33
38
|
<body>
|
|
34
39
|
<div id="root"></div>
|
|
35
|
-
<script type="module" src="/
|
|
40
|
+
<script type="module" src="${relativeSrcPath}/main.tsx"></script>
|
|
36
41
|
</body>
|
|
37
42
|
</html>`;
|
|
38
43
|
|
|
@@ -46,6 +51,9 @@ async function runDev() {
|
|
|
46
51
|
const libPath = import.meta.dirname;
|
|
47
52
|
const projectPath = process.cwd();
|
|
48
53
|
|
|
54
|
+
// Ensure index.html exists for external projects
|
|
55
|
+
await ensureIndexHtml();
|
|
56
|
+
|
|
49
57
|
const server = await createServer({
|
|
50
58
|
root: projectPath, // Use project as root for proper file watching
|
|
51
59
|
configFile: resolve(libPath, "vite.config.ts"),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@berlysia/vertical-writing-slide-system",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.18",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"vertical-slides": "./cli.js"
|
|
@@ -17,9 +17,9 @@
|
|
|
17
17
|
],
|
|
18
18
|
"dependencies": {
|
|
19
19
|
"@emotion/react": "^11.14.0",
|
|
20
|
+
"@mdx-js/mdx": "^3.1.0",
|
|
20
21
|
"@mdx-js/react": "^3.1.0",
|
|
21
22
|
"@mdx-js/rollup": "^3.1.0",
|
|
22
|
-
"@mdx-js/mdx": "^3.1.0",
|
|
23
23
|
"@vitejs/plugin-react-swc": "^3.5.0",
|
|
24
24
|
"prompts": "^2.4.2",
|
|
25
25
|
"react": "^19.0.0",
|
|
@@ -37,12 +37,14 @@
|
|
|
37
37
|
"@eslint/js": "^9.21.0",
|
|
38
38
|
"@playwright/experimental-ct-react": "^1.51.0",
|
|
39
39
|
"@playwright/test": "^1.51.0",
|
|
40
|
+
"@types/hast": "^3.0.4",
|
|
40
41
|
"@types/mdast": "^4.0.4",
|
|
41
42
|
"@types/mdx": "^2.0.13",
|
|
42
43
|
"@types/node": "^22.13.9",
|
|
43
44
|
"@types/prompts": "^2.4.9",
|
|
44
45
|
"@types/react": "^19.0.10",
|
|
45
46
|
"@types/react-dom": "^19.0.4",
|
|
47
|
+
"@types/unist": "^3.0.3",
|
|
46
48
|
"eslint": "^9.21.0",
|
|
47
49
|
"eslint-plugin-react-hooks": "^5.2.0",
|
|
48
50
|
"eslint-plugin-react-refresh": "^0.4.18",
|
|
@@ -57,6 +59,7 @@
|
|
|
57
59
|
"dev": "vite",
|
|
58
60
|
"build": "tsc -b && vite build",
|
|
59
61
|
"build:cli": "tsc --project tsconfig.cli.json",
|
|
62
|
+
"preversion": "pnpm build:cli",
|
|
60
63
|
"lint": "eslint .",
|
|
61
64
|
"preview": "vite preview",
|
|
62
65
|
"build:pages": "node scripts/build-pages.ts",
|
|
@@ -9,16 +9,75 @@ 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 { visit } from "unist-util-visit";
|
|
13
|
+
import type { Node } from "unist";
|
|
14
|
+
import type { Element, Text, ElementContent } from "hast";
|
|
12
15
|
|
|
13
|
-
|
|
16
|
+
/**
|
|
17
|
+
* CSS抽出用のrehypeプラグイン
|
|
18
|
+
* HTMLからstyleタグを抽出し、外部で管理
|
|
19
|
+
*/
|
|
20
|
+
function rehypeExtractStyles(extractedStyles: string[]) {
|
|
21
|
+
return () => {
|
|
22
|
+
return (tree: Node) => {
|
|
23
|
+
visit(tree, "element", (node: Element) => {
|
|
24
|
+
if (node.tagName === "style" && node.children) {
|
|
25
|
+
// styleタグの内容を抽出
|
|
26
|
+
const textContent = node.children
|
|
27
|
+
.filter(
|
|
28
|
+
(child: ElementContent): child is Text => child.type === "text",
|
|
29
|
+
)
|
|
30
|
+
.map((child: Text) => child.value)
|
|
31
|
+
.join("");
|
|
32
|
+
if (textContent.trim()) {
|
|
33
|
+
extractedStyles.push(textContent);
|
|
34
|
+
}
|
|
35
|
+
// styleタグをHTMLから削除
|
|
36
|
+
node.children = [];
|
|
37
|
+
node.tagName = "div";
|
|
38
|
+
node.properties = { style: "display: none;" };
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
};
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async function processMarkdown(
|
|
46
|
+
markdown: string,
|
|
47
|
+
base: string,
|
|
48
|
+
extractedStyles: string[],
|
|
49
|
+
) {
|
|
14
50
|
return await unified()
|
|
15
51
|
.use(remarkParse)
|
|
16
52
|
.use(remarkSlideImages, { base })
|
|
17
53
|
.use(remarkRehype, { allowDangerousHtml: true })
|
|
54
|
+
.use(rehypeExtractStyles(extractedStyles))
|
|
18
55
|
.use(rehypeStringify, { allowDangerousHtml: true })
|
|
19
56
|
.process(markdown);
|
|
20
57
|
}
|
|
21
58
|
|
|
59
|
+
/**
|
|
60
|
+
* 隣接CSSファイルを検索して読み込み
|
|
61
|
+
*/
|
|
62
|
+
function loadAdjacentCSS(slidesDir: string, collection: string): string[] {
|
|
63
|
+
const collectionDir = path.resolve(slidesDir, collection);
|
|
64
|
+
const cssPath = path.resolve(collectionDir, "style.css");
|
|
65
|
+
|
|
66
|
+
if (fs.existsSync(cssPath)) {
|
|
67
|
+
try {
|
|
68
|
+
const cssContent = fs.readFileSync(cssPath, "utf-8");
|
|
69
|
+
if (cssContent.trim()) {
|
|
70
|
+
logger.info("Loaded adjacent CSS file: style.css");
|
|
71
|
+
return [cssContent];
|
|
72
|
+
}
|
|
73
|
+
} catch {
|
|
74
|
+
logger.warn("Failed to read CSS file: style.css");
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return [];
|
|
79
|
+
}
|
|
80
|
+
|
|
22
81
|
export interface SlidesPluginOptions {
|
|
23
82
|
/** Directory containing the slides (absolute path) */
|
|
24
83
|
slidesDir?: string;
|
|
@@ -124,6 +183,7 @@ export default async function slidesPlugin(
|
|
|
124
183
|
let base: string;
|
|
125
184
|
let compiledSlides: string[] = [];
|
|
126
185
|
let resolvedConfig: ResolvedConfig;
|
|
186
|
+
let slideStyles: string[] = [];
|
|
127
187
|
return {
|
|
128
188
|
name: "vite-plugin-slides",
|
|
129
189
|
configResolved(config: ResolvedConfig) {
|
|
@@ -181,19 +241,47 @@ export default async function slidesPlugin(
|
|
|
181
241
|
|
|
182
242
|
const content = fs.readFileSync(filePath, "utf-8");
|
|
183
243
|
|
|
244
|
+
// 隣接CSSファイルを読み込み
|
|
245
|
+
const adjacentStyles = loadAdjacentCSS(
|
|
246
|
+
config.slidesDir,
|
|
247
|
+
config.collection,
|
|
248
|
+
);
|
|
249
|
+
slideStyles = [...adjacentStyles];
|
|
250
|
+
|
|
184
251
|
if (!isMdx) {
|
|
185
252
|
const slides = content.split(/^\s*(?:---|\*\*\*|___)\s*$/m);
|
|
253
|
+
const extractedStyles: string[] = [];
|
|
186
254
|
const processedSlides = await Promise.all(
|
|
187
|
-
slides.map((slide) =>
|
|
255
|
+
slides.map((slide) =>
|
|
256
|
+
processMarkdown(slide, base, extractedStyles),
|
|
257
|
+
),
|
|
188
258
|
);
|
|
259
|
+
|
|
260
|
+
// 抽出されたスタイルを追加
|
|
261
|
+
slideStyles = [...slideStyles, ...extractedStyles];
|
|
262
|
+
|
|
189
263
|
return `export default ${JSON.stringify(processedSlides.map((p) => p.value))}`;
|
|
190
264
|
}
|
|
191
265
|
|
|
192
266
|
const slides = content.split(/^\s*(?:---|\*\*\*|___)\s*$/m);
|
|
193
267
|
|
|
268
|
+
// MDXにもCSS抽出を適用(MDXの場合はJSXのstyleタグを抽出)
|
|
269
|
+
const extractedStyles: string[] = [];
|
|
194
270
|
const processedSlides = await Promise.all(
|
|
195
271
|
slides.map(async (slideContent) => {
|
|
196
|
-
|
|
272
|
+
// MDX内のstyleタグを手動で抽出(簡易実装)
|
|
273
|
+
const styleRegex = /<style[^>]*>([\s\S]*?)<\/style>/gi;
|
|
274
|
+
let match;
|
|
275
|
+
while ((match = styleRegex.exec(slideContent)) !== null) {
|
|
276
|
+
if (match[1].trim()) {
|
|
277
|
+
extractedStyles.push(match[1].trim());
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// styleタグを削除したコンテンツでMDXコンパイル
|
|
282
|
+
const cleanedContent = slideContent.replace(styleRegex, "");
|
|
283
|
+
|
|
284
|
+
const result = await compile(cleanedContent, {
|
|
197
285
|
outputFormat: "program",
|
|
198
286
|
development: false,
|
|
199
287
|
jsxImportSource: "react",
|
|
@@ -204,6 +292,9 @@ export default async function slidesPlugin(
|
|
|
204
292
|
}),
|
|
205
293
|
);
|
|
206
294
|
|
|
295
|
+
// 抽出されたスタイルを追加
|
|
296
|
+
slideStyles = [...slideStyles, ...extractedStyles];
|
|
297
|
+
|
|
207
298
|
compiledSlides = processedSlides;
|
|
208
299
|
|
|
209
300
|
const numberOfSlides = slides.length;
|
|
@@ -225,7 +316,13 @@ export default async function slidesPlugin(
|
|
|
225
316
|
return filename.replace(/\.[jt]sx?$/, "");
|
|
226
317
|
}
|
|
227
318
|
|
|
228
|
-
//
|
|
319
|
+
// スライド固有のCSSを文字列として生成
|
|
320
|
+
const slideStylesString =
|
|
321
|
+
slideStyles.length > 0
|
|
322
|
+
? JSON.stringify(slideStyles.join("\n\n"))
|
|
323
|
+
: "null";
|
|
324
|
+
|
|
325
|
+
// Return as a module with CSS injection
|
|
229
326
|
return `
|
|
230
327
|
${slideComponentsFilenames.map((filename) => `import * as ${filenameToComponentName(filename)} from '@components/${filename}';`).join("\n")}
|
|
231
328
|
|
|
@@ -233,6 +330,20 @@ export default async function slidesPlugin(
|
|
|
233
330
|
|
|
234
331
|
${compiledSlides.map((_, index) => `import Slide${formatSlideIndex(index)} from '${virtualFilePageId(index)}';`).join("\n")}
|
|
235
332
|
|
|
333
|
+
// スライド固有のCSSを注入
|
|
334
|
+
const slideStyles = ${slideStylesString};
|
|
335
|
+
if (slideStyles && typeof document !== 'undefined') {
|
|
336
|
+
const existingStyleElement = document.getElementById('slide-custom-styles');
|
|
337
|
+
if (existingStyleElement) {
|
|
338
|
+
existingStyleElement.textContent = slideStyles;
|
|
339
|
+
} else {
|
|
340
|
+
const styleElement = document.createElement('style');
|
|
341
|
+
styleElement.id = 'slide-custom-styles';
|
|
342
|
+
styleElement.textContent = slideStyles;
|
|
343
|
+
document.head.appendChild(styleElement);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
236
347
|
// provide slide components to each slide
|
|
237
348
|
// Wrap SlideN components to provide SlideComponents
|
|
238
349
|
${compiledSlides
|
|
@@ -434,7 +545,7 @@ export default async function slidesPlugin(
|
|
|
434
545
|
|
|
435
546
|
if (
|
|
436
547
|
absolutePath.includes(absoluteSlidesDir) &&
|
|
437
|
-
/\.(?:md|mdx)$/.test(absolutePath)
|
|
548
|
+
/\.(?:md|mdx|css)$/.test(absolutePath)
|
|
438
549
|
) {
|
|
439
550
|
logger.info(`Slide file changed: ${absolutePath}`);
|
|
440
551
|
reloadModule();
|
|
@@ -451,7 +562,7 @@ export default async function slidesPlugin(
|
|
|
451
562
|
|
|
452
563
|
if (
|
|
453
564
|
absolutePath.includes(absoluteSlidesDir) &&
|
|
454
|
-
/\.(?:md|mdx)$/.test(absolutePath)
|
|
565
|
+
/\.(?:md|mdx|css)$/.test(absolutePath)
|
|
455
566
|
) {
|
|
456
567
|
logger.info(`Slide file added: ${absolutePath}`);
|
|
457
568
|
reloadModule();
|
|
@@ -468,7 +579,7 @@ export default async function slidesPlugin(
|
|
|
468
579
|
|
|
469
580
|
if (
|
|
470
581
|
absolutePath.includes(absoluteSlidesDir) &&
|
|
471
|
-
/\.(?:md|mdx)$/.test(absolutePath)
|
|
582
|
+
/\.(?:md|mdx|css)$/.test(absolutePath)
|
|
472
583
|
) {
|
|
473
584
|
logger.info(`Slide file deleted: ${absolutePath}`);
|
|
474
585
|
reloadModule();
|