@berlysia/vertical-writing-slide-system 0.0.32 → 0.1.0

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@berlysia/vertical-writing-slide-system",
3
- "version": "0.0.32",
3
+ "version": "0.1.0",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "vertical-slides": "./cli.js"
@@ -15,6 +15,20 @@
15
15
  "src",
16
16
  "index.html"
17
17
  ],
18
+ "scripts": {
19
+ "dev": "vite",
20
+ "build": "tsc -b && vite build",
21
+ "build:cli": "tsc --project tsconfig.cli.json",
22
+ "preversion": "pnpm build:cli",
23
+ "lint": "eslint .",
24
+ "preview": "vite preview",
25
+ "build:pages": "node scripts/build-pages.ts",
26
+ "test:vrt": "playwright test",
27
+ "test:vrt:clear": "rm -rf tests/__snapshots__",
28
+ "test:vrt:update": "playwright test --update-snapshots",
29
+ "ai:test:vrt": "AI=1 playwright test",
30
+ "ai:test:vrt:update": "AI=1 playwright test --update-snapshots"
31
+ },
18
32
  "dependencies": {
19
33
  "@emotion/react": "^11.14.0",
20
34
  "@mdx-js/mdx": "^3.1.0",
@@ -55,19 +69,5 @@
55
69
  "typescript": "~5.8.2",
56
70
  "typescript-eslint": "^8.26.0",
57
71
  "unist-util-visit": "^5.0.0"
58
- },
59
- "scripts": {
60
- "dev": "vite",
61
- "build": "tsc -b && vite build",
62
- "build:cli": "tsc --project tsconfig.cli.json",
63
- "preversion": "pnpm build:cli",
64
- "lint": "eslint .",
65
- "preview": "vite preview",
66
- "build:pages": "node scripts/build-pages.ts",
67
- "test:vrt": "playwright test",
68
- "test:vrt:clear": "rm -rf tests/__snapshots__",
69
- "test:vrt:update": "playwright test --update-snapshots",
70
- "ai:test:vrt": "AI=1 playwright test",
71
- "ai:test:vrt:update": "AI=1 playwright test --update-snapshots"
72
72
  }
73
- }
73
+ }
@@ -10,6 +10,8 @@ export interface SlideMetadata {
10
10
  author?: string;
11
11
  /** 作成日時 */
12
12
  date?: string;
13
+ /** OGP画像のパス(ogp.pngが存在する場合に自動設定) */
14
+ ogImage?: string;
13
15
  /** その他のカスタムフィールド */
14
16
  [key: string]: unknown;
15
17
  }
@@ -94,6 +94,22 @@ function loadAdjacentCSS(slidesDir: string, collection: string): string[] {
94
94
  return [];
95
95
  }
96
96
 
97
+ /**
98
+ * OGP画像ファイル(ogp.png)が存在するか確認
99
+ * 存在する場合はファイル名を返す
100
+ */
101
+ function checkOgpImage(slidesDir: string, collection: string): string | null {
102
+ const collectionDir = path.resolve(slidesDir, collection);
103
+ const ogpPath = path.resolve(collectionDir, "ogp.png");
104
+
105
+ if (fs.existsSync(ogpPath)) {
106
+ logger.info("Found OGP image: ogp.png");
107
+ return "ogp.png";
108
+ }
109
+
110
+ return null;
111
+ }
112
+
97
113
  /**
98
114
  * 隣接スクリプト設定ファイルを検索して読み込み
99
115
  */
@@ -332,6 +348,12 @@ export default async function slidesPlugin(
332
348
  slideMetadata = parsedFile.metadata;
333
349
  const slideContent = parsedFile.content;
334
350
 
351
+ // OGP画像を検出してメタデータに設定
352
+ const ogpImageFile = checkOgpImage(config.slidesDir, config.collection);
353
+ if (ogpImageFile) {
354
+ slideMetadata.ogImage = `${base}slide-assets/${ogpImageFile}`;
355
+ }
356
+
335
357
  logger.info(`Slide metadata: ${JSON.stringify(slideMetadata)}`);
336
358
 
337
359
  // 隣接CSSファイルを読み込み
@@ -575,6 +597,29 @@ export default async function slidesPlugin(
575
597
  } else {
576
598
  logger.info(`No images directory found at: ${sourceImagesDir}`);
577
599
  }
600
+
601
+ // OGP画像をコピー
602
+ const targetAssetsDir = isExternalCLI
603
+ ? path.resolve(process.cwd(), "public/slide-assets")
604
+ : path.resolve(resolvedConfig.root, "public/slide-assets");
605
+ const sourceOgpPath = path.resolve(
606
+ config.slidesDir,
607
+ config.collection,
608
+ "ogp.png",
609
+ );
610
+
611
+ if (fs.existsSync(sourceOgpPath)) {
612
+ try {
613
+ mkdirSync(targetAssetsDir, { recursive: true });
614
+ const targetOgpPath = path.join(targetAssetsDir, "ogp.png");
615
+ await copyFile(sourceOgpPath, targetOgpPath);
616
+ logger.info("Copied OGP image: ogp.png");
617
+ } catch (error) {
618
+ if (error instanceof Error) {
619
+ logger.error("Failed to copy OGP image", error);
620
+ }
621
+ }
622
+ }
578
623
  }
579
624
  },
580
625
 
@@ -609,6 +654,21 @@ export default async function slidesPlugin(
609
654
  : "<!-- CSS is included in the JS bundle -->";
610
655
 
611
656
  const pageTitle = slideMetadata?.title || "Vertical Writing Slides";
657
+
658
+ // OGPメタタグを生成
659
+ const ogpTags: string[] = [];
660
+ if (slideMetadata?.title) {
661
+ ogpTags.push(`<meta property="og:title" content="${slideMetadata.title}" />`);
662
+ }
663
+ if (slideMetadata?.description) {
664
+ ogpTags.push(`<meta property="og:description" content="${slideMetadata.description}" />`);
665
+ }
666
+ if (slideMetadata?.ogImage) {
667
+ ogpTags.push(`<meta property="og:image" content="${slideMetadata.ogImage}" />`);
668
+ ogpTags.push(`<meta name="twitter:card" content="summary_large_image" />`);
669
+ ogpTags.push(`<meta name="twitter:image" content="${slideMetadata.ogImage}" />`);
670
+ }
671
+
612
672
  const virtualIndexHtml = `<!doctype html>
613
673
  <html lang="ja">
614
674
  <head>
@@ -617,6 +677,7 @@ export default async function slidesPlugin(
617
677
  <title>${pageTitle}</title>
618
678
  ${slideMetadata?.description ? `<meta name="description" content="${slideMetadata.description}" />` : ""}
619
679
  ${slideMetadata?.author ? `<meta name="author" content="${slideMetadata.author}" />` : ""}
680
+ ${ogpTags.join("\n ")}
620
681
  <link rel="preconnect" href="https://fonts.googleapis.com">
621
682
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
622
683
  <link href="https://fonts.googleapis.com/css2?family=Noto+Sans+JP&family=Noto+Sans+Mono:wght@100..900&display=swap" rel="stylesheet">
@@ -689,6 +750,30 @@ export default async function slidesPlugin(
689
750
  } else {
690
751
  logger.warn(`No images directory found at: ${sourceImagesDir}`);
691
752
  }
753
+
754
+ // OGP画像をバンドルに追加
755
+ const sourceOgpPath = path.resolve(
756
+ config.slidesDir,
757
+ config.collection,
758
+ "ogp.png",
759
+ );
760
+
761
+ if (fs.existsSync(sourceOgpPath)) {
762
+ try {
763
+ const ogpContent = fs.readFileSync(sourceOgpPath);
764
+ this.emitFile({
765
+ type: "asset",
766
+ fileName: "slide-assets/ogp.png",
767
+ source: ogpContent,
768
+ });
769
+ logger.info("Added OGP image to bundle: ogp.png");
770
+ } catch (error) {
771
+ logger.error(
772
+ "Failed to add OGP image to bundle",
773
+ error instanceof Error ? error : new Error(String(error)),
774
+ );
775
+ }
776
+ }
692
777
  }
693
778
  },
694
779
 
@@ -803,6 +888,51 @@ export default async function slidesPlugin(
803
888
  });
804
889
  }
805
890
 
891
+ // OGPメタタグを追加
892
+ if (slideMetadata?.title) {
893
+ tags.push({
894
+ tag: "meta",
895
+ attrs: {
896
+ property: "og:title",
897
+ content: slideMetadata.title,
898
+ },
899
+ });
900
+ }
901
+
902
+ if (slideMetadata?.description) {
903
+ tags.push({
904
+ tag: "meta",
905
+ attrs: {
906
+ property: "og:description",
907
+ content: slideMetadata.description,
908
+ },
909
+ });
910
+ }
911
+
912
+ if (slideMetadata?.ogImage) {
913
+ tags.push({
914
+ tag: "meta",
915
+ attrs: {
916
+ property: "og:image",
917
+ content: slideMetadata.ogImage,
918
+ },
919
+ });
920
+ tags.push({
921
+ tag: "meta",
922
+ attrs: {
923
+ name: "twitter:card",
924
+ content: "summary_large_image",
925
+ },
926
+ });
927
+ tags.push({
928
+ tag: "meta",
929
+ attrs: {
930
+ name: "twitter:image",
931
+ content: slideMetadata.ogImage,
932
+ },
933
+ });
934
+ }
935
+
806
936
  return {
807
937
  html: htmlWithoutTitle,
808
938
  tags,