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

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.1",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "vertical-slides": "./cli.js"
@@ -10,6 +10,10 @@ export interface SlideMetadata {
10
10
  author?: string;
11
11
  /** 作成日時 */
12
12
  date?: string;
13
+ /** スライドの公開URL(OGP画像のフルURL生成に使用) */
14
+ url?: string;
15
+ /** OGP画像のパス(ogp.pngが存在する場合に自動設定) */
16
+ ogImage?: string;
13
17
  /** その他のカスタムフィールド */
14
18
  [key: string]: unknown;
15
19
  }
@@ -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,21 @@ export default async function slidesPlugin(
332
348
  slideMetadata = parsedFile.metadata;
333
349
  const slideContent = parsedFile.content;
334
350
 
351
+ // OGP画像を検出してメタデータに設定
352
+ // baseがフルURL(http/https)の場合はフルURLを生成、そうでなければ相対パス
353
+ const ogpImageFile = checkOgpImage(config.slidesDir, config.collection);
354
+ if (ogpImageFile) {
355
+ const ogpPath = `slide-assets/${ogpImageFile}`;
356
+ if (base.startsWith("http://") || base.startsWith("https://")) {
357
+ // フルURLの場合: baseの末尾スラッシュを正規化して結合
358
+ const normalizedBase = base.endsWith("/") ? base : `${base}/`;
359
+ slideMetadata.ogImage = `${normalizedBase}${ogpPath}`;
360
+ } else {
361
+ // 相対パスの場合: そのまま相対パスを設定
362
+ slideMetadata.ogImage = `${base}${ogpPath}`;
363
+ }
364
+ }
365
+
335
366
  logger.info(`Slide metadata: ${JSON.stringify(slideMetadata)}`);
336
367
 
337
368
  // 隣接CSSファイルを読み込み
@@ -575,6 +606,29 @@ export default async function slidesPlugin(
575
606
  } else {
576
607
  logger.info(`No images directory found at: ${sourceImagesDir}`);
577
608
  }
609
+
610
+ // OGP画像をコピー
611
+ const targetAssetsDir = isExternalCLI
612
+ ? path.resolve(process.cwd(), "public/slide-assets")
613
+ : path.resolve(resolvedConfig.root, "public/slide-assets");
614
+ const sourceOgpPath = path.resolve(
615
+ config.slidesDir,
616
+ config.collection,
617
+ "ogp.png",
618
+ );
619
+
620
+ if (fs.existsSync(sourceOgpPath)) {
621
+ try {
622
+ mkdirSync(targetAssetsDir, { recursive: true });
623
+ const targetOgpPath = path.join(targetAssetsDir, "ogp.png");
624
+ await copyFile(sourceOgpPath, targetOgpPath);
625
+ logger.info("Copied OGP image: ogp.png");
626
+ } catch (error) {
627
+ if (error instanceof Error) {
628
+ logger.error("Failed to copy OGP image", error);
629
+ }
630
+ }
631
+ }
578
632
  }
579
633
  },
580
634
 
@@ -609,6 +663,21 @@ export default async function slidesPlugin(
609
663
  : "<!-- CSS is included in the JS bundle -->";
610
664
 
611
665
  const pageTitle = slideMetadata?.title || "Vertical Writing Slides";
666
+
667
+ // OGPメタタグを生成
668
+ const ogpTags: string[] = [];
669
+ if (slideMetadata?.title) {
670
+ ogpTags.push(`<meta property="og:title" content="${slideMetadata.title}" />`);
671
+ }
672
+ if (slideMetadata?.description) {
673
+ ogpTags.push(`<meta property="og:description" content="${slideMetadata.description}" />`);
674
+ }
675
+ if (slideMetadata?.ogImage) {
676
+ ogpTags.push(`<meta property="og:image" content="${slideMetadata.ogImage}" />`);
677
+ ogpTags.push(`<meta name="twitter:card" content="summary_large_image" />`);
678
+ ogpTags.push(`<meta name="twitter:image" content="${slideMetadata.ogImage}" />`);
679
+ }
680
+
612
681
  const virtualIndexHtml = `<!doctype html>
613
682
  <html lang="ja">
614
683
  <head>
@@ -617,6 +686,7 @@ export default async function slidesPlugin(
617
686
  <title>${pageTitle}</title>
618
687
  ${slideMetadata?.description ? `<meta name="description" content="${slideMetadata.description}" />` : ""}
619
688
  ${slideMetadata?.author ? `<meta name="author" content="${slideMetadata.author}" />` : ""}
689
+ ${ogpTags.join("\n ")}
620
690
  <link rel="preconnect" href="https://fonts.googleapis.com">
621
691
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
622
692
  <link href="https://fonts.googleapis.com/css2?family=Noto+Sans+JP&family=Noto+Sans+Mono:wght@100..900&display=swap" rel="stylesheet">
@@ -689,6 +759,30 @@ export default async function slidesPlugin(
689
759
  } else {
690
760
  logger.warn(`No images directory found at: ${sourceImagesDir}`);
691
761
  }
762
+
763
+ // OGP画像をバンドルに追加
764
+ const sourceOgpPath = path.resolve(
765
+ config.slidesDir,
766
+ config.collection,
767
+ "ogp.png",
768
+ );
769
+
770
+ if (fs.existsSync(sourceOgpPath)) {
771
+ try {
772
+ const ogpContent = fs.readFileSync(sourceOgpPath);
773
+ this.emitFile({
774
+ type: "asset",
775
+ fileName: "slide-assets/ogp.png",
776
+ source: ogpContent,
777
+ });
778
+ logger.info("Added OGP image to bundle: ogp.png");
779
+ } catch (error) {
780
+ logger.error(
781
+ "Failed to add OGP image to bundle",
782
+ error instanceof Error ? error : new Error(String(error)),
783
+ );
784
+ }
785
+ }
692
786
  }
693
787
  },
694
788
 
@@ -803,6 +897,51 @@ export default async function slidesPlugin(
803
897
  });
804
898
  }
805
899
 
900
+ // OGPメタタグを追加
901
+ if (slideMetadata?.title) {
902
+ tags.push({
903
+ tag: "meta",
904
+ attrs: {
905
+ property: "og:title",
906
+ content: slideMetadata.title,
907
+ },
908
+ });
909
+ }
910
+
911
+ if (slideMetadata?.description) {
912
+ tags.push({
913
+ tag: "meta",
914
+ attrs: {
915
+ property: "og:description",
916
+ content: slideMetadata.description,
917
+ },
918
+ });
919
+ }
920
+
921
+ if (slideMetadata?.ogImage) {
922
+ tags.push({
923
+ tag: "meta",
924
+ attrs: {
925
+ property: "og:image",
926
+ content: slideMetadata.ogImage,
927
+ },
928
+ });
929
+ tags.push({
930
+ tag: "meta",
931
+ attrs: {
932
+ name: "twitter:card",
933
+ content: "summary_large_image",
934
+ },
935
+ });
936
+ tags.push({
937
+ tag: "meta",
938
+ attrs: {
939
+ name: "twitter:image",
940
+ content: slideMetadata.ogImage,
941
+ },
942
+ });
943
+ }
944
+
806
945
  return {
807
946
  html: htmlWithoutTitle,
808
947
  tags,
package/vite.config.ts CHANGED
@@ -6,7 +6,7 @@ import slidesPlugin from "./src/vite-plugin-slides";
6
6
 
7
7
  export default defineConfig(async () => {
8
8
  return {
9
- base: "./",
9
+ base: process.env.BASE_URL || "./",
10
10
  resolve: {
11
11
  alias: {
12
12
  "@components": path.resolve(