Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ Markdownファイルのフロントマターにかける設定は以下の通り
- `name`:著者名.任意.
- `breadcrumb`:ページ上部のパンくずリストを定義する.詳しくは[後述](#パンくずリストの表示).
- `redirect_from`・`redirect_to`:リダイレクトの設定.詳しくは[後述](#redirect_from--redirect_to).
- `ogpImage`: ページに任意のOGP画像を設定する. 詳しくは[後述](#任意のOGP画像を設定する)

## Scripts

Expand Down Expand Up @@ -271,6 +272,22 @@ pattern: "^\/utol\/"
---
```

### 任意のOGP画像を設定する

`ogpImage`をMarkdownのfrontmatterに記述することで,SNS共有時などに表示されるOGP画像をページごとに個別設定できます.設定がない場合はデフォルトのOGP画像が利用されます.

指定する画像は`public/assets/images/`以下に配置し,`/assets/images/custom_ogp.png`のように指定してください.

### 記述例

```md
---
title: "記事のタイトル"
lang: "ja"
ogpImage: "/assets/images/custom_ogp.png"
---
```

### ボタン・タブUIを利用したい場合

[多要素認証の初期設定手順](https://utelecon.adm.u-tokyo.ac.jp/utokyo_account/mfa/initial/)や[サポート窓口](https://utelecon.adm.u-tokyo.ac.jp/support/)のページでは,ボタンとタブを用いたUIが利用されており,ユーザの選択によって表示内容を切り替えたり,ボタンをクリックして別のページに遷移したりすることができます.
Expand Down
79 changes: 13 additions & 66 deletions src/layouts/Layout.astro
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
---
import { SEO, type Props as SEOProps } from "astro-seo";
import SEO from "./SEO.astro";
import GoogleAnalytics from "./GoogleAnalytics.astro";
import MicrosoftClarity from "./MicrosoftClarity.astro";
import Header from "./Header/index.astro";
import Footer from "./Footer/index.astro";
import Toc from "./Toc.astro";
import "@styles/layout.scss";
import type { MDXLayoutProps, MarkdownHeading } from "astro";
import type { MDXLayoutProps } from "astro";
import Author, { type Author as AuthorProps } from "./Author.astro";
import Breadcrumb from "./Breadcrumb.astro";
import type { Breadcrumb as BreadcrumbProps } from "./resolve-breadcrumb";
import Markdown from "@components/utils/Markdown.astro";
import type { Lang } from "@components/types";
import { parse as parsePath } from "path";
import "@fontsource/material-icons";
import "@fontsource-variable/open-sans";
import resolveBreadcrumb from "./resolve-breadcrumb";
Expand All @@ -33,36 +32,20 @@ export interface Frontmatter {
redirect_to?: string;
author?: AuthorProps;
breadcrumb?: BreadcrumbProps;
ogpImage?: string;
}

function createDefaultDescription(
lang: Lang,
headings: MarkdownHeading[],
): string {
return (
(
{
ja: "目次: ",
en: "Table of Contents: ",
}[lang] +
headings
.filter(({ depth }) => depth === 2)
.map(({ text }) => text)
.join("; ")
).slice(0, 97) + "..."
);
}

const { frontmatter, headings, file } = Astro.props;
const { frontmatter, headings } = Astro.props;
const {
title,
description = createDefaultDescription(frontmatter.lang, headings),
description,
lang,
h1,
top,
support,
author,
breadcrumb,
ogpImage,
} = frontmatter;

const displayToc = !top && (frontmatter.toc ?? true) && headings.length > 0;
Expand All @@ -77,61 +60,25 @@ const htmlAttributes = {
},
}[lang];

const locale = {
en: "en_US",
ja: "ja_JP",
}[lang];

const gaid = "G-ZM6H96EWBD";
const clarityProjectId = "vat2444uow";

const canonicalUrl = Astro.url;

const seoProps: SEOProps = {
title,
titleTemplate: "%s | utelecon",
titleDefault: "utelecon",
description,
charset: "UTF-8",
canonical: canonicalUrl.href,
};

const ogpImagePath = {
en: "/assets/images/ogp_en.png",
ja: "/assets/images/ogp.png",
}[lang];

if (title) {
seoProps.openGraph = {
basic: {
title,
type: "website",
url: canonicalUrl.href,
image: new URL(ogpImagePath, Astro.site).href,
},
optional: {
description,
locale,
siteName: "utelecon",
},
image: {
alt: "utelecon",
},
};
seoProps.twitter = {
title,
description,
};
}

const resolvedBreadcrumb =
breadcrumb && (await resolveBreadcrumb(Astro.url, breadcrumb));
---

<!doctype html>
<html {...htmlAttributes}>
<head>
<SEO {...seoProps} />
<SEO
title={title}
description={description}
lang={lang}
headings={headings}
ogpImage={ogpImage}
/>

<GoogleAnalytics id={gaid} />
<MicrosoftClarity projectId={clarityProjectId} />
Expand Down
84 changes: 84 additions & 0 deletions src/layouts/SEO.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
---
import { SEO as AstroSEO, type Props as SEOProps } from "astro-seo";
import type { MarkdownHeading } from "astro";
import type { Lang } from "@components/types";

interface Props {
title?: string;
description?: string;
lang: Lang;
headings?: MarkdownHeading[];
ogpImage?: string;
}

const { title, description, lang, headings = [], ogpImage } = Astro.props;

function createDefaultDescription(
lang: Lang,
headings: MarkdownHeading[],
): string {
return (
(
{
ja: "目次: ",
en: "Table of Contents: ",
}[lang] +
headings
.filter(({ depth }) => depth === 2)
.map(({ text }) => text)
.join("; ")
).slice(0, 97) + "..."
);
}

const resolvedDescription =
description ?? createDefaultDescription(lang, headings);

const locale = {
en: "en_US",
ja: "ja_JP",
}[lang];

const canonicalUrl = Astro.url;

const seoProps: SEOProps = {
title,
titleTemplate: "%s | utelecon",
titleDefault: "utelecon",
description,
charset: "UTF-8",
canonical: canonicalUrl.href,
};

const defaultOgpImagePath = {
en: "/assets/images/ogp_en.png",
ja: "/assets/images/ogp.png",
}[lang];

const ogpImagePath = ogpImage ?? defaultOgpImagePath;

if (title) {
seoProps.openGraph = {
basic: {
title,
type: "website",
url: canonicalUrl.href,
image: new URL(ogpImagePath, Astro.site).href,
},
optional: {
description: resolvedDescription,
locale,
siteName: "utelecon",
},
image: {
alt: "utelecon",
},
};
seoProps.twitter = {
title,
description: resolvedDescription,
};
}
---

<AstroSEO {...seoProps} />