テクノなまこ

科学の力

GitHub Pagesのmarkdownに自作テーマを適用し、ダークテーマや、tex数式や、シンタックスハイライトと言語名表示つきのコードブロックを使えるようにする

完成例

参考用

github pagesのmarkdownに自作テーマを適用し、ダークテーマや、tex数式や、シンタックスハイライトと言語名表示つきのコードブロックを使えるようにした。この記事ではその全コードを掲示し簡単な解説をする。本当は最小構成を掲示するべきだろうけれど面倒なのでやめた。

最終構造
Mikanixonable.github.io
├── _layouts
│ └──default.html
├── md
│ └──style.css
├── _config.yml
└── index.md


やったこと(全6工程)(スクロール量とても長い)

_config.ymlを作成しリポジトリに置く

markdown: kramdown
plugins:
 - jekyll-sitemap
highlighter: rouge
kramdown:
  hard_wrap: true

markdown -> html変換をkramdownで行う」
xml形式のサイトマップを作ってgoogleにクロールされやすくし、検索に乗りやすくする」
シンタックスハイライトをrougeで行う」
「通常のmarkdownでは無視される改行を無視しない」
という意味のことが書いてある

github pagesに使用しているリポジトリに_layoutsというフォルダを作り、その中にdefault.htmlを作成する

(必ず_layouts/フォルダ下である必要がある。defaut.htmlの名前は変えてもいいが、その場合後述のmarkdownのlayout: defaultの部分を別の名前に書き換える必要がある)

default.htmlの中身を書く

default.htmlの中身に、以下をコピーする

<!doctype html>
<html lang="{{ site.lang | default: " ja" }}">

<head>
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="color-scheme" content="light dark" />
  <!-- IE互換性のためのおまじない -->
  <link rel="stylesheet" href="./md/style.css">
  <script
    type="text/x-mathjax-config">MathJax.Hub.Config({tex2jax:{inlineMath:[['\$','\$'],['\\(','\\)']],processEscapes:true},CommonHTML: {matchFontHeight:false}});</script>
  <script type="text/javascript" async
    src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.1/MathJax.js?config=TeX-MML-AM_CHTML"></script>

  <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
  {% seo %}
</head>
<body>
  <div class="wrapper">
      {{ content }}
  </div>
</body>
</html>
<script>
  let sheets = document.styleSheets
  let sheet = sheets[sheets.length - 1];
  let codeElements = document.querySelectorAll("code");
  for (let i = 0; i < codeElements.length; i++) {
    let codeElement = codeElements[i]
    codeElement.parentElement.classList.add("pre" + i)

    // <div class="language-shell highlighter-rouge">
    // <div class="highlight">
    // <pre class="highlight">
    // <code>
    // から「python」を抽出
    clsName = codeElement
    .parentElement
      .parentElement
      .parentElement
      .className
    langName = codeElement
      .parentElement
      .parentElement
      .parentElement
      .className
      .match(/language-(\S+)\s/)[1]

    sheet.insertRule(
      `.pre${i}:before { content: "${langName}";}`,
      sheet.cssRules.length);
  }
</script>

github pagesに使用しているリポジトリにmdというフォルダを作り、その中にstyle.cssを作成する。

(フォルダ名を「md」以外にしてもいいが、その場合上に載せたdefault.htmlのの「md」の箇所を別のフォルダ名に書き換える必要がある。また、フォルダ名を_layoutsにすると読み込まれなくなる。おそらく「_」で始まると読み込まれなくなると思う(未確認))

4. style.cssの中身を書く

style.cssの中身に、以下をコピーする

:root{
  --b1: #181921;
  --b2: #1a2638;
  --b3: #323e52;
  --b4: #323e52;
  --b4: #1a2a2c;
}

body {
  padding:0 0;
  margin: 0 0;
  font:14px/1.5 "OpenSansRegular", "Helvetica Neue", Helvetica, Arial, sans-serif;
  color:#ddd9ca;
  font-weight: normal;
  background: #181921;
  display: flex;
  align-items: center;
  flex-direction: column;
  /* background-attachment: fixed !important; */
}

.linkcard {
  border-radius: 5em;
  display: flex;
  flex-direction: column;
  align-items: center;
}
.linkcard img{
  width: 8em;
  height: 12em;
  object-fit: cover;
  border-radius: 5em 5em 5em 5em   
}
.row {
  display: flex;
  flex-direction: row;
  align-items: center;
}
.row a{
  margin: 1em;
}
.columun {
  display: flex;
  flex-direction: column;
  align-items: center;
}

article{
  background-color: #1a2638;
  margin: 1em 0em;
  padding: 1em;
  border-radius: 1em;
}
article article {
  margin: 1em -0.5em;
  padding: 1em 0.5em;
  background-color: #323e52;
}

.wrapper {
  margin: 0 auto;
  padding: 30px;
  max-width:660px;
  position:relative;
}

svg {
  max-width: 660px;
  max-height: 400px;
}

img{
  max-height: 80vh;
  max-width: 80vw;
}

section img {
  max-width: 100%;
}

button {
  background-color: slategrey;
  color: white;
  border-radius: 1em;
  border-width: 0;
}
button:hover {
  filter: hue-rotate(20deg) saturate(90%) brightness(90%)
}
button.playing {
  filter: hue-rotate(20deg) saturate(200%)
}
.mikuButton button {
  background-color: #47848b;
}
.musicButton button {
  background-color: #8b4755;
}

h1, h2, h3, h4, h5, h6 {
  margin:0px;
  color: white;
  font-family: 'OpenSansRegular', "Helvetica Neue", Helvetica, Arial, sans-serif;
  font-weight: normal;
}

p, ul, ol, table, pre, dl {
  margin:0 0 20px;
}

h1, h2, h3 {
  line-height:1.1;
}

h1 {font-size:28px;}

h2 {font-size: 24px;}

h3 {
  font-size: 18px;
  line-height: 24px;
  font-family: 'OpenSansRegular', "Helvetica Neue", Helvetica, Arial, sans-serif !important;
  font-weight: normal;
}

a {
  color:#f5abb5;
  font-weight:400;
  text-decoration:none;
}
a:hover {
  color: #f69eaa;
  text-decoration: none;
}

a small {
  font-size:11px;
  color:#666;
  margin-top:-0.6em;
  display:block;
}


ul{
  list-style-image:url('../images/bullet.png');
}

strong {
  font-family: 'OpenSansBold', "Helvetica Neue", Helvetica, Arial, sans-serif !important;
  font-weight: normal;
}

blockquote {
  border-left:3px solid cadetblue;
  font-style: italic;
  color: #c8dfdf;
  margin:0;
  padding:0 0 0 20px;
  
}

.codepre:before {
  display: block;
  /* position: absolute; */
  inset-block-start: 0em;
  inset-inline-start: 0;
  /* content: attr(class); */
  padding-block: .5em;
  padding-inline: 1em;
  inline-size: fit-content;
  background-color: var(--b3);
  color: white;
  font-family: Monaco, Bitstream Vera Sans Mono, Lucida Console, Terminal, monospace;
  border-radius: .5em .5em 0 0;
  /* content: "python"; */
}

.code {
  display: block;
  position: relative;
  padding: 1em 1em;
  background-color: var(--b2);
  border-radius: 0 .5em .5em .5em;
  font-family: Monaco, Bitstream Vera Sans Mono, Lucida Console, Terminal, monospace;
  color:#efefef;
  font-size:13px;
  overflow: auto;
  /* overflow-y: hidden; */
}

.mermaidpre {
  /* background-color: #5292c7; */
  display: flex;
  justify-content: center;
}
/* .language-mermaid {
  background-color: #51e1bd;
  margin: 0 auto;
} */

table {
  width:100%;
  border-collapse:collapse;
}

th {
  text-align:left;
  padding:5px 10px;
  border-bottom:1px solid #434343;
  color: #b6b6b6;
  font-family: 'OpenSansSemibold', "Helvetica Neue", Helvetica, Arial, sans-serif !important;
  font-weight: normal;
}

td {
  text-align:left;
  padding:5px 10px;
  border-bottom:1px solid #434343;
}

hr {
  border: 0;
  outline: none;
  height: 3px;
  background: transparent url('../images/hr.gif') center center repeat-x;
  margin: 0 0 20px;
}

dt {
  color:#F0E7D5;
  font-family: 'OpenSansSemibold', "Helvetica Neue", Helvetica, Arial, sans-serif !important;
  font-weight: normal;
}


/* コードブロック */

@media print, screen and (max-width: 720px) {

  #title {
    .credits {
      display: block;
      width: 100%;
      line-height: 30px;
      text-align: center;

      .left {
        float: none;
        display: block;
      }

      .right {
        float: none;
        display: block;
      }
    }
  }
}

@media print, screen and (max-width: 480px) {

  #header {
    margin-top: -20px;
  }

  section {
    margin-top: 40px;
  }
  nav {
    display: none;
  }
}
/*
   generated by rouge http://rouge.jneen.net/
   original base16 by Chris Kempson (https://github.com/chriskempson/base16)
*/

.highlight table td { padding: 5px; }
.highlight table pre { margin: 0; }
.highlight, .highlight .w {
  color: #d0d0d0;
}
.highlight .err {
  color: #151515;
  background-color: #ac4142;
}

.highlight .c, .highlight .cd, .highlight .cm, .highlight .c1, .highlight .cs {
  color: #888;
}
.highlight .cp {
  color: #f4bf75;
}
.highlight .nt {
  color: #f4bf75;
}
.highlight .o, .highlight .ow {
  color: #adcace;
}
.highlight .pi {
  color: #d0d0d0;
}
.highlight .p {
  color: #dbd700;
}
.highlight .gi {
  color: #90a959;
}
.highlight .gd {
  color: #ac4142;
}
.highlight .gh {
  color: #aa759f;
  font-weight: bold;
}
.highlight .k, .highlight .kn, .highlight .kp, .highlight .kr, .highlight .kv {
  color: #5292c7;
}
.highlight .s {
  color: #ea8e85;
}

.highlight .kc {
  color: #d28445;
}
.highlight .kt {
  color: #d28445;
}
.highlight .kd {
  color: #d28445;
}
.highlight .sb, .highlight .sc, .highlight .sd, .highlight .s2, .highlight .sh, .highlight .sx, .highlight .s1 {
  color: #90a959;
}
.highlight .sr {
  color: #75b5aa;
}
.highlight .si {
  color: #8f5536;
}
.highlight .se {
  color: #8f5536;
}
.highlight .nn {
  color: #51e1bd;
}

.highlight .nc {
  color: #f4bf75;
}
.highlight .no {
  color: #f4bf75;
}
.highlight .na {
  color: #6a9fb5;
}
.highlight .nf, .highlight .nb {
  color: #dcdcaa;
}
.highlight .m, .highlight .mf, .highlight .mh, .highlight .mi, .highlight .il, .highlight .mo, .highlight .mb, .highlight .mx {
  color: #ce916a;
}
.highlight .ss {
  color: #90a959;
}

markdownを用意する

github pagesに使用しているリポジトリに、1.mdなど、拡張子がmdのファイルを用意して、それに以下のようなmarkdownを書きこみ、15分くらい待つ

---
layout: default
title: 月面植物園
---
# 月面植物園
みかぶるのホームページ

> テクノなまこ、科学の力

SNS
- [bluesky](https://bsky.app/profile/mikanixonable.bsky.social)
- [misskey](https://misskey.io/@Mikanixonable)
- [twitter](https://twitter.com/Mikanixonable)

### テイラー展開
$$
f(x) = \sum_{n=0}^{\infty} \frac{f^{(n)}(a)}{n!}(x-a)^n
$$

~~~python
from matplotlib import pyplot as plt
import cv2 as cv2
import numpy as np

img = cv2.imread("1.png")
img = cv2.resize(img,(64,64))

print(img)
r = img[:,:,2]
g = img[:,:,1]
b = img[:,:,0]

rgb = np.dstack((r, g, b))
rgb_flat = rgb.reshape((rgb.shape[0]*rgb.shape[1], 3))
def rgb_to_hex(rgb):
    return '#{:02x}{:02x}{:02x}'.format(*rgb)
cCodes = np.apply_along_axis(rgb_to_hex, 1, rgb_flat)

# print(cCodes)
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1, projection='3d')
ax.scatter(b, g, r, c=cCodes,alpha=1)
plt.show()
~~~

背景

 最初に、無テーマのgithub pagesのmarkdown機能を試した。github pagesはjekyllという静的サイトビルダーを標準装備しており、github pagesの設定を済ませたリポジトリに拡張子がmdのファイルを作りmarkdownを書きこむと15分くらい待てばhtmlに変換して表示してくれる。公式サイトによればレンダリングエンジンをGFMにすれば(_config.ymlにmarkdown: GFMと書けば)githubのreadme.mdに使えるGFMのmarkdownと同等のものが使えるらしいが、readme.mdでは表示できるtex数式をgithub pagesでは表示できないし、readme.mdのようにダークテーマにはならないし、強制的にリポジトリ名をサイト上部に表示されるので、無テーマのmarkdownビルド機能にはたくさん不満があった。
 次に、標準テーマを試してみた。github pagesはいくつかのjekyllテーマを標準で使える。(_config.ymlにtheme: Caymanと書くとCaymanになる)標準テーマではコードブロックのシンタックスハイライトに対応していて、midnightやhackerではダークテーマに対応しており見た目が少しきれいになる。しかし不満もあり、すべてのページの末尾にテーマ作者へのクレジットリンクが挿入されるようになる。作者への敬意を忘れたくはないが、自宅に等しい個人サイトで毎ページテーマ作者へのリンクと宣伝が挟まるのは心理的に実用外に思えた。見た目を変える以上のことをするものをテーマと呼んではいけないと思う。何かオプトアウト設定でクレジットを外せるようになっているかもしれないが、もうすべて自分の制御下にあるものしか使う気がなくなってしまった。
 他にgithub pages公式のサポート外のjekyllテーマも使える(_config.ymlにremote_theme: chrisrhymes/bulma-clean-themeとリポジトリ名を書くとbulma-clean-themeになる)が、もうテーマを自作する気分になっていた。

経緯と解説

上のコード例が出来上がるまでの経緯を追い、ついでにコードの意味を解説する
参考github pages入門 - Qiita
 リポジトリの_layoutsフォルダ以下にdefault.htmlを置くと、html中の {{ content }}の部分にhtml化されたmarkdownが埋め込まれて表示されるようになる。default.htmlにいろいろ入れることで望みのページを作ることができる。私の場合は、midnightテーマをもとに改造したstyle.cssを読み込んで独自テーマを適用し、scriptタグを使ってmathjaxのcdnを読込みtex数式を使えるようにし、scriptタグにjsを書きこんでcssの疑似要素を書きこむことでコードブロックをzenn風に改造した。

(1/5)default.htmlを_layouts以下に置き、改造する

 私は、cc0で公開されている[https://github.com/pages-themes/midnight:title=midnight]をもとに改造して自作テーマを作った。まず、リポジトリに行き、_layouts以下にあるdefaulr.htmlをダウンロードしてきて自分のリポジトリの_layoutsに置く。次に、使用しない行を削除する。私が削除したのは、jqueryやstylesheetやrespond.jsへのリンク行と、

までのヘッダー(リポジトリ名を挿入する)と、
から
までのタイトル・クレジットを挿入する行だった。これらを削除した私にとってのdefault.htmlの最小構成は下のようになった。

<!doctype html>
<html lang="{{ site.lang | default: "ja" }}">
  <head>
    <meta charset="utf-8">
   {% seo %}
    <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
 <meta name="color-scheme" content="light dark" />
  </head>
  <body>
  <div class="wrapper">
    <section>
    {{ content }}
    </section>
  </div>
  </body>
</html>

{% seo %}というのはjekyll-seo-tagと呼ばれるもので、サイトの内容を分析して自動的にメタデータのタグを生成し、google検索に引っ掛かりやすく(SEO対策)してくれる。SEOは嫌いだが、サイトのメタデータが参照可能になるのはいいことだと思うので、残した。github pagesのSEO、OGPについて参考になるページ:https://takezoe.hatenablog.com/entry/2016/07/03/104536
はスクロールバーがダークモード仕様にし、読込中の背景を黒にする。

は、あとのcssでいじるために用意してあるもので、サイトの横幅の上限を設定することでデスクトップで開いたときにサイトの要素が左端に寄りすぎないようにする

(2/4)_layouts/dafault.htmlからmd/style.cssを読込み自作テーマを適用する

dafault.htmlのhead要素以下にの行を追加する。
midnightリポジトリの_sass内にあるmidnightテーマのcss1のjekyll-theme-midnight.scssとシンタックスハイライト用のrouge-base16-dark.scssをダウンロードしてきて、片方をstyle.cssに改名して、もう片方の内容をコピーしてきて付け足す。style.cssを自分のリポジトリのmd以下に置く。
style.css内の
@import "normalize";
@import "fonts";
@import "rouge-base16-dark.scss";
の行を削除する。
するとmidnightテーマとシンタックスハイライトをローカルで適用することができる。style.cssをいじれば背景色やその他を自分好みに変更できる。

(3/5)数式を使えるようにする

default.htmlのhead以下に

  <script type="text/x-mathjax-config">MathJax.Hub.Config({tex2jax:{inlineMath:[['\$','\$'],['\\(','\\)']],processEscapes:true},CommonHTML: {matchFontHeight:false}});</script>
  <script type="text/javascript" async src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.1/MathJax.js?config=TeX-MML-AM_CHTML"></script>

を足すとtex数式が使えるようになる。数式は$で囲むと使うことができる。例えば$\sqrt{2}$でルート2を表示できる。\$\sqrt{2}\$でインラインでルート2を表示できる。

(4/5)コードブロックをzenn.dev風に改造する


zenn.devのこういうファイル名が左上に出ているコードブロックが好きだったので、再現しようとした。ツイッターで再現したい旨つぶやいていると、レさん(@wartemeinnightさん)が再現して送ってくれた!

jekyllの生成するコードブロックは次のような要素で包まれている

<div class="language-python highlighter-rouge">
          <div class="highlight">
            <pre class="highlight"><code>

このclass名から言語名を取り出してzenn.dev風にコードブロックの左上に表示できないだろうかと考えた。レさんのCSSでは言語名を疑似要素beforeを使って再現してあり、jsで疑似要素は操作できないため、jsで疑似要素を書きこむことにした。レさんにもらったCSSでは、コードブロックに疑似要素を付けて言語名を表示していたが、これではoverflow: auto;で行が長いときの横スクロールを実装すると疑似要素が消えてしまうため(https://teratail.com/questions/c9nkgcddbeb0wv
CSSでoverflow-x: scrollをつけたらbefore擬似クラスがが消える)、code要素の親のpreに疑似要素をつけるようにした。style.cssとdefault.htmlの追加箇所は以下のようになる。

pre:before {
  display: block;
  /* position: absolute; */
  inset-block-start: 0em;
  inset-inline-start: 0;
  /* content: attr(class); */
  padding-block: .5em;
  padding-inline: 1em;
  inline-size: fit-content;
  background-color: #323e52;
  color: white;
  border-radius: .5em .5em 0 0;
  /* content: "python"; */
  
}

code {

  display: block;
  position: relative;
  padding: 1em 1em;
  background-color: #1a2638;
  border-radius: 0 .5em .5em .5em;
  font-family: Monaco, Bitstream Vera Sans Mono, Lucida Console, Terminal, monospace;
  color:#efefef;
  font-size:13px;
  overflow: auto;
  /* overflow-y: hidden; */
}

<script>
  let sheets = document.styleSheets
  let sheet = sheets[sheets.length - 1];
  let codeElements = document.querySelectorAll("code");
  for (let i = 0; i < codeElements.length; i++) {
    let codeElement = codeElements[i]
    codeElement.parentElement.classList.add("pre" + i)

    // <div class="language-shell highlighter-rouge">
    // <div class="highlight">
    // <pre class="highlight">
    // <code>
    // から「python」を抽出
    clsName = codeElement
    .parentElement
      .parentElement
      .parentElement
      .className
    langName = codeElement
      .parentElement
      .parentElement
      .parentElement
      .className

      .match(/language-(\S+)\s/)[1]
    sheet.insertRule(
      `.pre${i}:before { content: "${langName}";}`,
      sheet.cssRules.length);
  }
</script>

(5/5)mermaidを使えるようにする

mermaidはツリー図を書いたりできるマークダウン
参考GitHub Pages で Mermaid を使う - みちのぶのねぐら
default.html中のbodyタグをbody onload="initializeMermaid()"にする
default.html中(最後とか)に以下の記述を足す

<script src="https://unpkg.com/mermaid@10.5.1/dist/mermaid.min.js"></script>
<script>
  function initializeMermaid() {
   mermaid.initialize({
  "theme": "base",
  "fontFamily": "TimesNewRoman Times Serif",
  "themeVariables": {
    "lineColor": "aliceblue",
    "primaryColor": "#323e52",
    "secondaryColor": "#397",
    "tertiaryColor": "#1a2638",
    "primaryTextColor": "white",
    "secondaryTextColor": "white",
    "tertiaryTextColor": "white",
    "primaryBorderColor": "aliceblue",
    "secondaryBorderColor": "aliceblue",
    "tertiaryBorderColor": "slategray"
  }
   });
   window.mermaid.init(
      undefined,
      document.querySelectorAll('.language-mermaid'),
   );
}
</script>