@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 +1 -1
- package/src/types/slide-metadata.ts +4 -0
- package/src/vite-plugin-slides.ts +139 -0
- package/vite.config.ts +1 -1
package/package.json
CHANGED
|
@@ -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,
|