uni farm

gatsbyカスタム作業メモ

gatsbyカスタム作業メモ

やったことをかいておく

gatsby-starter-blogを生成し、カスタムしている

table of contents

exclude: Table of Contents
tight: false
ordered: false
from-heading: 2
to-heading: 6
class-name: "table-of-contents"

slugの生成方法を変える

slugは各postのpath部分に使われる

gatsby-node.jsをみると、markdownの入ったファイルのパスを取得し、設定している

// graphqlのnodeにslugというfieldを作成している
exports.onCreateNode = ({ node, actions, getNode }) => {
  const { createNodeField } = actions

  if (node.internal.type === `MarkdownRemark`) {
    const value = createFilePath({ node, getNode })
    createNodeField({
      name: `slug`,
      node,
      value,
    })
  }
}

.mdファイルのfrontmatterにslugを定義して、これとdateよりYYYY/MM/DDをprefixに使いたい

---
title: My Second Post!
date: "2015-05-06T23:46:37.121Z"
slug: "aa"
---
...

について/2015/05/06/aaをこのpostのpathとしたい

graphqlで値を取得して、onCreateNode時に値を渡してやればよい

...
    const slug = node.frontmatter.slug
    const dt = moment(node.frontmatter.date).format("YYYY/MM/DD")
    const value = dt + "/" + slug
...

参考

どのようにmarkdownからページを作成しているか

draftのコンテンツは生成しない

markdownファイルのfrontmatterにdraft項目を追加する

コンテンツページ生成、post一覧取得時のクエリに条件式を追加してやればよい

 allMarkdownRemark(
   sort: { fields: [frontmatter___date], order: DESC },
   filter: {frontmatter: {draft: {eq: false}}},
   limit: 1000
 ) {

develop(local開発)時には、draftの値関係なく生成するようにしたかったが、一旦これで

↓を使ってどうにかできそう Different GraphQL query in development and production · Issue #12460 · gatsbyjs/gatsby · GitHub

参考

投稿一覧ページを無限スクロールにする

react-infinite-scrollerを使用

コード例

// 表示する分だけsliceして渡すようにしている
const Posts: React.FC<Props> = ({ posts, showPosts }) => {
  return posts.slice(0, showPosts).map(({ node }) => {
    return <Article post={node} />
  })
}

const BlogIndex = ({ data, location }) => {
  const siteTitle = data.site.siteMetadata.title
  const posts = data.allMarkdownRemark.edges

  const pageSize = 10
  const [hasMore, setHasMore] = React.useState(true)
  const [pageNum, setPageNum] = React.useState(1)

  let loadMore = () => {
    if (pageSize * pageNum >= posts.length) {
      setHasMore(false)
      return
    }
    setPageNum(pageNum + 1)
  }

  const loader = <div className="loader">Loading...</div>
  return (
    <Layout location={location} title={siteTitle}>
      <SEO title="All posts" />
      <InfiniteScroll
        pageStart={0}
        loadMore={loadMore}
        hasMore={hasMore}
        loader={loader}
      >
        <Posts posts={posts} showPosts={pageNum * pageSize} />
      </InfiniteScroll>
    </Layout>
  )
}

参考

search consoleのtagを設定する

サイトの所有権を確認する - Search Console ヘルプ のHTML タグを設定する

./src/components/seo.jsでmeta tagの管理をしているので、以下のように追記して設置した

コード例

  <Helmet
    meta={[
    ...
      {
        name: "google-site-verification",
        content: "xxxx",
      },
    ...
    ]}

記事にtex(katex)記法を使用

packageを導入

gatsby-remark-katex | Gatsby

gatsby-browser.jsにcssを追加

$$x=a$$なんかの構文を使用できる

import "katex/dist/katex.min.css"

記事中でiframe tagの使用

記事中にtableau publicで作成したdashboardを表示したかったので

iframe tagをmdファイルに入れることにした

gatsby-remark-responsive-iframeを使用

参考

  • https://kb.tableau.com/articles/howto/embedding-tableau-public-views-in-iframes?lang=ja-jp

iframeにてtableau dashboardを埋め込む方法

back to topボタンの追加

App Bar React component - Material-UI

back to topを追加した

コード例

import React from "react"
import styled from "styled-components"

import { Tooltip, Zoom, useScrollTrigger } from "@mui/material"

type Props = { children: React.ReactNode }

const StyledDiv = styled.div`
  position: fixed;
  bottom: 1.25rem;
  right: 1.25rem;
`

const ScrollTop: React.FC<Props> = props => {
  const trigger = useScrollTrigger({
    disableHysteresis: true,
    threshold: 0,
  })
  const onClick = (event: React.MouseEvent<HTMLDivElement>) => {
    const anchor = (
      (event.target as HTMLDivElement).ownerDocument || document
    ).querySelector("#back-to-top-anchor")
    if (anchor) {
      anchor.scrollIntoView({ behavior: "smooth", block: "center" })
    }
  }

  return (
    <Zoom in={trigger}>
      <Tooltip title="back to top">
        <StyledDiv onClick={onClick}>{props.children}</StyledDiv>
      </Tooltip>
    </Zoom>
  )
}

export default ScrollTop
import Fab from "@mui/material/Fab"
import ScrollTop from "./scroll-top"

    <ScrollTop>
      <Fab color="primary" size="small" aria-label="scroll back to top">
        <KeyboardArrowUpIcon />
      </Fab>
    </ScrollTop>

tagページの追加

Creating Tags Pages for Blog Posts | Gatsby を参考に

postのfrontmatterに、tagsを追加しておく

---
title: "xxx"
tags:
  - tag1
  - tag2

tagごとにページを作成した。こんな感じ

algolia検索を追加

Adding Search with Algolia | Gatsbyを参考に

gatsby-plugin-algoliaを設定すると、yarn run build時に送信してくれる

送信するデータはgraphqlで、index名はqueries.jsのindexNameに記載しておく

...
[Algolia] 1 queries to index
[Algolia] Running 1 query for index prod_blog...
[Algolia] Query resulted in a total of 90 results
[Algolia] Found 90 new `/` updated records...
[Algolia] Done!
⠋ onPostBuild
success onPostBuild - 36.783s

検索機能は、チュートリアル通りのものをヘッダー部分に追加した

記事の目次を追加

記事中に追加

gatsby-remark-table-of-contentsを用いる

readme通りにすると、markdown内に各見出しに対するlinkが作られる

この記事のはじめの方に、折りたたみ形式にして入れている

目次をcomponentとして追加

記事中にあると目次まで戻るの大変なので

記事横など、好きな場所に表示できるよう、componentを作成した

コード例

import React from "react"
import { Link } from "gatsby"
import TocIcon from "@mui/icons-material/Toc"

import { MarkdownHeading } from "../../../graphql-types"

import "./blog-toc.scss"

type TOCProps = {
  links: Array<MarkdownHeading>
}

const TOC: React.FC<TOCProps> = ({ links }) => {
  const texts = links.map(link => {
    return (
      <>
        <li>
          <Link className={`depth-${link.depth} link`} to={`#${link.id}`}>
            {link.value}
          </Link>
        </li>
      </>
    )
  })
  return (
    <div className="blog-toc">
      <TocIcon className="toc-icon" />
      目次
      <ol>{texts}</ol>
    </div>
  )
}

export default TOC
.blog-toc {
  font-family: "ヒラギノ丸ゴ Pro W4", "ヒラギノ丸ゴ Pro", "sans-serif";
  position: -webkit-sticky;
  position: sticky;
  top: 0;
  padding: 0.5rem;
  border-left: solid 0.1rem #d3d3d3;
  font-size: 13px;
  font-weight: 1000;

  .toc-icon {
    vertical-align: middle;
  }

  li {
    margin-bottom: 0.5rem;
  }

  .link {
    box-shadow: none;
    text-decoration: none;
    color: inherit;
  }
}

右側に追加される

observer APIを用いる

各indexが見えた時に該当する目次項目にactiveというクラスを追加する

コード例

const TOC: React.FC<TOCProps> = ({ links }) => {
  const [activeId, setActiveId] = React.useState(``)
  React.useEffect(() => {
    const observer = new IntersectionObserver(
      entries => {
        entries.forEach(entry => {
          if (entry.isIntersecting) {
            setActiveId(entry.target.id)
          }
        })
      },
      {
        rootMargin: `0% 0% -95% 0%`,
      }
    )

    links.forEach(link => {
      observer.observe(document.getElementById(link.id))
    })

    return () => {
      links.forEach(link => {
        observer.unobserve(document.getElementById(link.id))
      })
    }
  }, [links])

  const texts = links.map(link => {
    const active = link.id === activeId ? "active" : ""
    return (
      <>
        <li>
          <Link
            id={`${link.id}`}
            className={`depth-${link.depth} link ${active}`}
            to={`#${link.id}`}
          >
            {link.value}
          </Link>
        </li>
      </>
    )
  })
  return (
    <div className="blog-toc">
      <TocIcon className="toc-icon" />
      目次
      <ol>{texts}</ol>
    </div>
  )
}

太字になるように設定した

参考

google adsense component

コード例

import React from "react"

const GoogleAdsense: React.FC = () => {
  React.useEffect(() => {
    if (window) {
      window.adsbygoogle = window.adsbygoogle || []
      window.adsbygoogle.push({})
    }
  }, [])

  return (
    <>
      <ins
        className="adsbygoogle"
        style={{ display: "block" }}
        data-ad-client="ca-pub-xxxxxxx"
        data-Namelot="xxxxxxx"
        data-ad-format="auto"
        data-full-width-responsive="true"
      ></ins>
    </>
  )
}

export default GoogleAdsense

読み込み時にスクリプトを実行しなければならないらしく、useEffectでどうにかした

code snippetにタイトル表示

local pluginを作成した

コード例

const visit = require("unist-util-visit")

const titleSep = ":"

module.exports = ({ markdownAST }, { className }) => {
  visit(markdownAST, "code", (node, index) => {
    // ```xxx:xxx.txt -> lang, title
    const [lang, title] = (node.lang || "").split(titleSep)
    if (!title) {
      return
    }

    const titleNode = {
      type: "html",
      value: `
        <div class="${className}">
          <span>${title}</span>
        </div>
       `,
    }

    markdownAST.children.splice(index, 0, titleNode)
    node.lang = lang
  })
  return markdownAST
}

src/styles/global.cssにてstyleを設定した

gatsby-browser.jsにてimport "./src/styles/global.css"として読み込ませる

.gatsby-code-title {
  position: relative;
  background: #f5f2f0;
  width: 100%;
  top: 0.5rem;
}

.gatsby-code-title span {
  font-family: Consolas, Monaco, "Andale Mono", "Ubuntu Mono", monospace;
  color: #fcfcfc;
  background: #a9a9a9;
  padding: 0.25rem;
}

prismjsのpluginに対して要素を追加している

参考

  • https://www.gatsbyjs.com/docs/creating-a-local-plugin/

local pluginの作り方公式

buy me a coffee buttonの追加

https://www.buymeacoffee.com

コード例

const PayButton: React.FC = () => {
  React.useEffect(() => {
    let script = document.createElement("script")
    script.setAttribute("data-name", "BMC-Widget")
    script.setAttribute("data-id", "xxxx")
    script.setAttribute("data-description", "Support me on Buy me a coffee!")
    script.setAttribute("data-message","☕")
    script.setAttribute("data-color", "#FFDD00")
    script.setAttribute("data-position", "right")
    script.src = "https://cdnjs.buymeacoffee.com/1.0.0/widget.prod.min.js"
    script.async = true

    script.onload = function () {
      var evt = document.createEvent("Event")
      evt.initEvent("DOMContentLoaded", false, false)
      window.dispatchEvent(evt)
    }

    document.head.appendChild(script)
  }, [])

  return null
}

アカウント、widgetを作成し記事の右下に設置した

fixedのみなので、右下に出しっぱなしでやや見栄えが悪い

参考

  • https://stackoverflow.com/questions/62039217/add-buy-me-a-coffee-widget-to-react-application

gitpodで動作

installさえできれば起動させて Simple Browserを使って画面をみることができる

.gitpod.ymlは↓の感じ

tasks:
  - init: yarn
    command: yarn run develop
2023, Built with Gatsby. This site uses Google Analytics.