ブログに Embed を追加しました。
とりあえずは Twitter, CodePen, GitHub の gist を追加しました。
サンプル
実装
Notion の API からは↓のようなJSONが降ってきます
{
"object": "block",
"id": "ba08ae81-7321-48be-958a-89a526d7853e",
"created_time": "2022-05-08T09:58:00.000Z",
"last_edited_time": "2022-05-08T09:58:00.000Z",
"created_by": {
"object": "user",
"id": "a7d811a0-5862-44b9-b0f8-8b5151542736"
},
"last_edited_by": {
"object": "user",
"id": "a7d811a0-5862-44b9-b0f8-8b5151542736"
},
"has_children": false,
"archived": false,
"type": "embed",
"embed": {
"caption": [],
"url": "https://twitter.com/uzimaru0000/status/1327932457224597504?ref_src=twsrc%5Etfw"
}
}
見てわかるように embed という識別しかされておらず何の embed なのかは embed.url の host を見るしかありません
URL から識別する
以下のようなコードで URL から何の embed なのかを識別しました。
type KnownURL = 'twitter' | 'gist' | 'codepen';
type TypedURL = {
type: KnownURL;
url: URL;
};
const isTwitter = (x: URL) => {
return x.host === 'twitter.com';
};
const isGist = (x: URL) => {
return x.host === 'gist.github.com';
};
const isCodePen = (x: URL) => {
return x.host === 'codepen.io';
};
const typedUrl = (rawUrl: string): TypedURL => {
try {
const url = new URL(rawUrl);
if (isTwitter(url)) {
return {
type: 'twitter' as const,
url,
};
} else if (isGist(url)) {
return {
type: 'gist' as const,
url,
};
} else if (isCodePen(url)) {
return {
type: 'codepen' as const,
url,
};
} else {
return null;
}
} catch {
return null;
}
};
はい、愚直にやってます。
TypedURL の type で何の Embed なのかを識別できるようになったのでそれを使ってコンポーネントを出し分けます。
Twitter コンポーネント
雑実装ですが
const Twitter = ({ url }: TypedURL) => {
useEffect(() => {
const script = document.createElement('script');
script.async = true;
script.src = 'https://platform.twitter.com/widgets.js';
document.head.appendChild(script);
return () => {
document.head.removeChild(script);
};
}, []);
return (
<Box display="flex" justifyContent="center" w="full">
<blockquote className="twitter-tweet">
<a href={url.href} />
</blockquote>
</Box>
);
};
Twitter の Embed に必要な Script を useEffect で入れてます
CodePen コンポーネント
CodePen は、 iframe が提供されているのでそれを使いました
const CodePen = ({ url }: TypedURL) => {
const { userId, codeId } = useMemo(() => {
const [, userId, , codeId] = url.pathname.split('/');
return {
userId,
codeId,
};
}, [url]);
return (
<iframe
height="800"
style={{ width: '100%' }}
scrolling="no"
title="Recursion"
src={`https://codepen.io/${userId}/embed/${codeId}`}
frameBorder="no"
loading="lazy"
>
See the Pen <a href={url.href}>Recursion</a> by
<a href={`https://codepen.io/${userId}`}>{`@${userId}`}</a> on{' '}
<a href="https://codepen.io">CodePen</a>.
</iframe>
);
};
Gist コンポーネント
const Gist = ({ url }: TypedURL) => {
const { gistId } = useMemo(() => {
const [, userId, gistId] = url.pathname.split('/');
return {
userId,
gistId,
};
}, [url]);
useEffect(() => {
const script = document.createElement('script');
script.src =
'https://cdn.jsdelivr.net/npm/gist-embed@1.0.4/dist/gist-embed.min.js';
script.type = 'text/javascript';
document.head.appendChild(script);
return () => {
document.head.removeChild(script);
};
}, [url]);
return (
<code
data-gist-id={gistId}
style={{
width: '100%',
}}
/>
);
};
まとめ
これで最低限のブログとしての機能はそろったかな?という感じです。
更新頑張ります