参考
astro-notion-blogはNotionで書いた記事をHTMLに変換していますが、Notion上に記述したHTML要素の変換には対応していません。
今回参考にさせて頂いたastro-notion-blogでHTMLタグを文字列ではなくHTML要素として扱うという記事においては、paragraph(通常のテキストブロック)をHTMLに変換する手法を取っていました。
ブロックの種別ごとに処理を行う箇所をif文で分岐させ、特定文字列が含まれている場合にブロックを丸ごとHTMLに変換する、というものです。
この記事の終わりにあった、
複数のタグを設定したい場合は、設定ファイルなどで管理するようにしてもいいかもしれません。
という一文を読んで、環境変数から任意文字列を設定できたら良いなと思い、どのようにすれば実現しやすいか考えていたのですが、「コードブロックのキャプションに特定文字列を入れたらコードブロックの中身をHTMLに変換する」という方法を思いつき、やってみたらできました。変換のトリガとする文字列は1つにできるので、環境変数による設定も簡単です。
↑この記事冒頭に書いた通り、Arduino言語レベルしか使えない人間なので非常に苦労しました。肩が凝りすぎて痛みだしている状態でこの記事を書いています。
先行事例の動作検証
AstroもCSSもHTMLもさっぱり分からないので、まずは先行事例の真似をしてどのような処理を行っているのか理解するところから始めました。
src\components\NotionBlocks.astro
を開き、118行目で
これを
case 'paragraph':
return <Paragraph block={block} headings={headings} />
こう書き換えます。
case 'paragraph':
if(typeof block.Paragraph.RichTexts[0] != "undefined"){
if(block.Paragraph.RichTexts[0].PlainText.indexOf('autodesk360') != -1
){
return <Fragment set:html={block.Paragraph.RichTexts[0].PlainText}/>
}
}
return <Paragraph block={block} headings={headings} />
Notionにただのテキストとして配置するHTMLタグはこれです。
<iframe src="https://myhub.autodesk360.com/ue2af766a/shares/public/SH56a43QTfd62c1cd968793ce3017c05306f?mode=embed" width="800" height="600" allowfullscreen="true" webkitallowfullscreen="true" mozallowfullscreen="true" frameborder="0"></iframe>
- 埋め込み(Notion上に埋め込まれて3Dモデルが見れる)
- コードブロック
- プレーンテキスト
の3種を配置して、ブラウザ上でどうなるのかを確認しました。
推測が合っているらしいということが分かりました。また、プレーンテキストは完全にプレーンである必要があり、URL部分に勝手に埋め込まれていたリンクを削除して本当にただのプレーンテキストにしないとこうなりませんでした。
まず公式のAPIの解説からブロックの中身を拾ってきました。なんかズレていたり見づらかったりしたので整形済です。
{
"type": "paragraph",
"paragraph": {
"rich_text": [
{
"type": "text",
"text": {
"content": "Lacinato kale",
"link": null
}
}
],
"color": "default"
}
}
{
"type": "code",
"code": {
"caption": [],
"rich_text": [
{
"type": "text",
"text": {
"content": "const a = 3"
}
}
],
"language": "javascript"
}
}
caption
の中にはコードブロックのキャプションのリッチテキストがそのまま入っているため、paragraphにおけるリッチテキストと同様に扱います。
次に、どこからか出てきたblock.Paragraph.RichTexts[0]
などは何がどうなっているのかを調べます。
src\lib\interfaces.ts
が由来であることが分かったので該当部分を持ってきます。
export interface Paragraph {
RichTexts: RichText[]
Color: string
Children?: Block[]
}
export interface Code {
Caption: RichText[]
RichTexts: RichText[]
Language: string
}
export interface RichText {
Text?: Text
Annotation: Annotation
PlainText: string
Href?: string
Equation?: Equation
Mention?: Mention
}
src\lib\interfaces.ts
で形を分かりやすくして送り込んでいることが分かりました。
これをなぞります。
case 'paragraph':
if(typeof block.Paragraph.RichTexts[0] != "undefined"){
if(block.Paragraph.RichTexts[0].PlainText.indexOf('autodesk360') != -1
){
return <Fragment set:html={block.Paragraph.RichTexts[0].PlainText}/>
}
}
return <Paragraph block={block} headings={headings} />
最初の条件文typeof block.Paragraph.RichTexts[0] != "undefined"
はblock.Paragraph.RichTexts
が定義されているかの確認。
Code blockに置き換えるとtypeof block.Code.Caption[0] != "undefined"
になる。
次の条件文block.Paragraph.RichTexts[0].PlainText.indexOf('autodesk360') != -1
はblock.Paragraph.RichTexts[0].PlainText
の中にマッチする文字列があることの確認。
Code blockに置き換えるとblock.Code.Caption[0].PlainText.indexOf('文字列') != -1
マッチする場合にHTMLとして返すのはblock.Paragraph.RichTexts[0].PlainText
で、これはブロックのプレーンテキストがそのまま変換される。
コードブロックのキャプションで判定したあとは、コードブロック内の文字列をHTMLとして返したいから、block.Code.RichTexts[0].PlainText
にする。
下の完成形のコードにて演算子を変更してあります。
src\components\NotionBlocks.astro
130行目付近
これが
case 'code':
return <Code block={block} />
こうなる
case 'code':
if(typeof block.Code.Caption[0] !== "undefined"){
if(block.Code.Caption[0].PlainText.indexOf('文字列') !== -1
){
return <Fragment set:html={block.Code.RichTexts[0].PlainText}/>
}
}
return <Code block={block} />
環境変数が使えるようにするとこうなる。
case 'code':
if(typeof block.Code.Caption[0] !== "undefined"){
if(block.Code.Caption[0].PlainText.indexOf(HTML_CONVERSION_TRIGGER) !== -1
){
return <Fragment set:html={block.Code.RichTexts[0].PlainText}/>
}
}
return <Code block={block} />
src\server-constants.ts
を編集。
末尾にexport const HTML_CONVERSION_TRIGGER = import.meta.env.HTML_CONVERSION_TRIGGER
を追記する。
export const NOTION_API_SECRET =
import.meta.env.NOTION_API_SECRET || process.env.NOTION_API_SECRET || ''
export const DATABASE_ID =
import.meta.env.DATABASE_ID || process.env.DATABASE_ID || ''
export const CUSTOM_DOMAIN =
import.meta.env.CUSTOM_DOMAIN || process.env.CUSTOM_DOMAIN || '' // <- Set your costom domain if you have. e.g. alpacat.com
export const BASE_PATH =
import.meta.env.BASE_PATH || process.env.BASE_PATH || '' // <- Set sub directory path if you want. e.g. /docs/
export const PUBLIC_GA_TRACKING_ID = import.meta.env.PUBLIC_GA_TRACKING_ID
export const NUMBER_OF_POSTS_PER_PAGE = 10
export const REQUEST_TIMEOUT_MS = parseInt(
import.meta.env.REQUEST_TIMEOUT_MS || '10000',
10
)
export const ENABLE_LIGHTBOX = import.meta.env.ENABLE_LIGHTBOX
export const NOTION_API_SECRET =
import.meta.env.NOTION_API_SECRET || process.env.NOTION_API_SECRET || ''
export const DATABASE_ID =
import.meta.env.DATABASE_ID || process.env.DATABASE_ID || ''
export const CUSTOM_DOMAIN =
import.meta.env.CUSTOM_DOMAIN || process.env.CUSTOM_DOMAIN || '' // <- Set your costom domain if you have. e.g. alpacat.com
export const BASE_PATH =
import.meta.env.BASE_PATH || process.env.BASE_PATH || '' // <- Set sub directory path if you want. e.g. /docs/
export const PUBLIC_GA_TRACKING_ID = import.meta.env.PUBLIC_GA_TRACKING_ID
export const NUMBER_OF_POSTS_PER_PAGE = 10
export const REQUEST_TIMEOUT_MS = parseInt(
import.meta.env.REQUEST_TIMEOUT_MS || '10000',
10
)
export const ENABLE_LIGHTBOX = import.meta.env.ENABLE_LIGHTBOX
export const HTML_CONVERSION_TRIGGER = import.meta.env.HTML_CONVERSION_TRIGGER
src\components\NotionBlocks.astro
で環境変数をimportさせる。
27行目付近にimport { HTML_CONVERSION_TRIGGER } from '../server-constants.ts’
を追加
import Toggle from './notion-blocks/Toggle.astro'
import File from './notion-blocks/File.astro'
import LinkToPage from './notion-blocks/LinkToPage.astro'
import { HTML_CONVERSION_TRIGGER } from '../server-constants.ts'
.envファイル及びCloudflareの環境変数に追加しておく。
処理の置き換えのところが最高にオブジェクト指向~~~って感じで脳みそ融けそうになりました。デバッグの方法も何も分からず、取得できた結果の中身も見ずに、失敗したことだけが分かる状態での手探りは中々堪えますね。Arduinoと戯れたいです。
環境変数を使えるようにするときに他のファイルも沢山読んだので、どうやって動いているのか裏側がちょっとわかった気がします。最終的には勘になっちゃったので変な不具合あると困るんですが、動いてるのでヨシ!の精神でいきます。ブログなので。
<div style="position: relative; padding-bottom: 75%;"><iframe style="position: absolute; top: 0; left: 0; width: 100%; height: 100%;" src="https://myhub.autodesk360.com/ue2af766a/shares/public/SH56a43QTfd62c1cd968793ce3017c05306f?mode=embed" allowfullscreen="true" webkitallowfullscreen="true" mozallowfullscreen="true" frameborder="0"></iframe></div>
これは僕の机です。アルミフレームが廃盤になったのでもう作れません。
同期ブロック内でも動作します。と書いて実際に見せたいところなのですが、この埋め込みを複数入れると机だけ消えるのでやってません。同期ブロックの場合見た目は完全に同じでした。
トグル内でも
表示できます。(が、2つしか埋め込んでないのにやっぱり机が消えてることがあります。なぜ。)