2024 年 大晦日
Blogger 記事の管理方法を変更することにした。
これまでは、Blogger の管理機能からエディタで入力していたが、 タグの入力などが面倒なため、Markdown 形式で記事を記述し、 Pandoc で HTML 形式に変換する方法に改めることにした。 画像の配置は、別途行うようにしている。
注意点の一つとして、コードブロック(インラインコードブロックも含む)以外のダブルクォートは、 Pandoc の smart 拡張機能によって、全角の「“”」に置換される。2つのハイフンも一つになったりする。
特に面倒だったのが、コードブロックで、 highlight.js を利用しているので、コードブロック毎に <pre>, <code> を追加する必要があった。
Pandoc で変換すると、自動的にコードブロックを囲ってくれるので楽になる。 変換コマンドは、以下となる。別途 highlight.js を導入しているため、 Pandoc 側でコードハイライトの修飾しないように指定している。 Mermaid も導入しているが、highlight.js と干渉するので、 Pandoc のフィルタ機能で Mermaid のコードブロックに対し、 タグ構成変更と highlight.js 処理対象外にする変更をしている。 さらに、MathJax も導入しているので、そのオプションも追加している。 その他には、Markdown 側の行の折り返しとタブ文字をそのまま残すようにしている。 (Go 言語や Makefile などは、タブインデントなため)。 出力された HTML ソースを Blogger に貼り付ける。
[mermaid-filter.lua
]
function CodeBlock(el)
if el.classes:includes('mermaid') then
local pre = pandoc.RawBlock('html', '<pre class="mermaid nohighlight">')
local content = pandoc.RawBlock('html', el.text)
local pre_end = pandoc.RawBlock('html', '</pre>')
return {pre, content, pre_end}
end
end
pandoc --no-highlight --mathjax --wrap=preserve --preserve-tabs --lua-filter=mermaid-filter.lua 入力.md -o 出力.html
Pandoc の Markdown に関する仕様は、 Pandoc User’s Guide 日本語版 - Pandoc’s Markdown を参照。
Markdown は、HTML タグを直接記述できるが、 記述が面倒で、見づらくなるため、あまりやりたくない。 見映えのために、CSS を利用したい場合、 Pandoc では、クラス、スタイルや ID などの指定ができる。 詳細は、 Pandoc User’s Guide 日本語版 - Div と Span を参照。
コロン(:)を3つ以上と続いて {} 内で追加したい属性を記述すると、 Div タグを生成して属性を設定してくれる。 インライン要素の場合は、Span タグを生成する。
以下のような Markdown を記述すると。
# 見出しの場合 {.test}
::: {#identity1 .class1 style="margin: 2em;"}
- この項目が
- Div タグで囲まれる
:::
:::: block1
::: {#block2 .class2 style="margin: 3em;"}
- 入れ子にも
- できる。
:::
::::
属性を付与したい項目を[カギ括弧]{#id1 .csl2 style="color: red;"}で囲んで波カッコ内に属性を記述する。
この[ように[**他の修飾**や[リンク](#identity1)などを含めたり]{.cls3}、入れ子]{style="color: blue;"}にすることもできる。
以下のような HTML を生成する。
<h1 class="test" id="見出しの場合">見出しの場合</h1>
<div id="identity1" class="class1" style="margin: 2em;">
<ul>
<li>この項目が</li>
<li>Div タグで囲まれる</li>
</ul>
</div>
<div class="block1">
<div id="block2" class="class2" style="margin: 3em;">
<ul>
<li>入れ子にも</li>
<li>できる。</li>
</ul>
</div>
</div>
<p>属性を付与したい項目を<span id="id1" class="csl2" style="color: red;">カギ括弧</span>で囲んで波カッコ内に属性を記述する。
この<span style="color: blue;">ように<span class="cls3"><strong>他の修飾</strong>や<a href="#identity1">リンク</a>などを含めたり</span>、入れ子</span>にすることもできる。
</p>
画像は、Markdown で管理せず、別途設定する。 画像の生成は、Gemini の自動生成で行っている。 プロンプトは、以下のようにしている。
以下の文章の内容に合った画像を生成してください。
<ここに、Markdown のソースをすべて貼り付ける>
生成された画像のダウンロードボタンを押下する。
拡張子は、.exif
となっているが、jpeg ファイルである。
画像サイズは、2048 x 2048 と大きいので、800 x 800 に縮小して利用する。
画像の縮小には、GraphicsMagick を利用する。
変換後の画像フォーマットは、WebP とする。
gm convert -resize 800 入力ファイル.exif 出力ファイル.webp
以下は、各種言語のソースコードを記載したサンプルとなる。
Go 言語ソースの Markdown 記述 (最初のインデント幅が少ないのは、行番号を入れている影響だと思うが、目を瞑る)
```go
package main
import "fmt"
func main() {
for i := 1; i <= 100; i++ {
if i%15 == 0 {
fmt.Println("FizzBuzz")
} else if i%3 == 0 {
fmt.Println("Fizz")
} else if i%5 == 0 {
fmt.Println("Buzz")
} else {
fmt.Println(i)
}
}
}
```
Go 言語ソース表示
package main
import "fmt"
func main() {
for i := 1; i <= 100; i++ {
if i%15 == 0 {
fmt.Println("FizzBuzz")
} else if i%3 == 0 {
fmt.Println("Fizz")
} else if i%5 == 0 {
fmt.Println("Buzz")
} else {
fmt.Println(i)
}
}
}
Python 言語ソースの Markdown 記述
```python
def fizzbuzz(n):
for i in range(1, n + 1):
if i % 15 == 0:
print("FizzBuzz")
elif i % 3 == 0:
print("Fizz")
elif i % 5 == 0:
print("Buzz")
else:
print(i)
fizzbuzz(100)
```
Python 言語ソースの表示
def fizzbuzz(n):
for i in range(1, n + 1):
if i % 15 == 0:
print("FizzBuzz")
elif i % 3 == 0:
print("Fizz")
elif i % 5 == 0:
print("Buzz")
else:
print(i)
fizzbuzz(100)
IntelliJ の場合は、External Tools に設定をすると変換が楽になる。
設定画面を開く
(File | Settings | Tools | External Tools
)、
設定画面で Add
し、以下のように設定し、OK
ボタンを押下。
設定を終了する。
項目 | 値 |
---|---|
Name: | to html |
Group: | External Tools |
Program: | pandoc |
Arguments: | –no-highlight –mathjax –wrap=preserve –preserve-tabs –lua-filter=mermaid-filter.lua $FileName$ -o $FileNameWithoutExtension$.html |
Working directory: | $FileDir$ |
項目 | 値 |
---|---|
Name: | to 800 webp |
Group: | External Tools |
Program: | gm |
Arguments: | convert -resize 800 $FileName$ $FileNameWithoutExtension$.webp |
Working directory: | $FileDir$ |
変換したいファイルを選択して、
右クリック | External Tools | to html
または、
右クリック | External Tools | to 800 webp
を選択すると、選択したファイルのディレクトリに、変換後のファイルが生成される。
上記のようなテーブルの場合、Blogger のテーマによっては、
なにもスタイルが設定されていないので、
別途 Blogger のテーマをカスタマイズして、スタイルシートを追加設定する必要がある。
テーマ | カスタマイズ | 詳細設定 | CSS を追加
でテーブルのスタイルを追加する。
table {
border-top: 2px solid darkgray;
border-bottom: 2px solid darkgray;
border-collapse: separate;
border-spacing: 0.5rem;
margin-top: 3em;
margin-bottom: 3em;
}
th {
border-bottom: 1px solid darkgray;
font-weight: bold;
padding-left: 0.5em;
padding-right: 0.5em;
}
td {
padding-left: 0.5em;
padding-right: 0.5em;
}
tbody tr:nth-child(2n+1) {
background-color: #080420;
}
自分用の備忘録として Blogger のテーマをカスタマイズした部分を記載しておく。
テーマ | カスタム | 詳細 | CSS を追加
.mermaid p {
border-left: unset;
padding: unset;
}
table {
border-top: 2px solid darkgray;
border-bottom: 2px solid darkgray;
border-collapse: separate;
border-spacing: 0.5rem;
margin-top: 3em;
margin-bottom: 3em;
}
th {
border-bottom: 1px solid darkgray;
font-weight: bold;
padding-left: 0.5em;
padding-right: 0.5em;
}
td {
padding-left: 0.5em;
padding-right: 0.5em;
}
tbody tr:nth-child(2n+1) {
background-color: #080420;
}
.ichili-top-link {
text-align: right;
}
.ichili-top-link p {
border-left: unset;
padding: unset;
}
.copy-button {
opacity: 0.8;
color: #888;
font-size: .6rem;
display: inline-block;
height: 20px;
line-height: 17px;
padding: 0 8px;
box-shadow: 0px 2px 5px 0px rgba(0, 0, 0, 0.16), 0px 2px 6px 0px rgba(0, 0, 0, 0.12);
border: 2px solid #666;
cursor: pointer;
background-color: #fff;
transition: all .2s ease;
position: absolute;
right: 0;
top: 0;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.copy-button.success {
border-color: #00c851;
background-color: #c8e6c9;
color: #007e33;
}
.copy-button.failed {
border-color: #ff4444;
background-color: #ffcdd2;
color: #cc0000;
}
.copy-button:hover {
opacity: 1;
box-shadow: 0px 2px 5px 0px rgba(0, 0, 0, 0.3), 0px 2px 10px 0px rgba(0, 0, 0, 0.12);
}
pre {
counter-reset: rowNumber;
}
pre code {
line-height: 1.5em;
tab-size: 4;
}
pre span.row-number {
counter-increment: rowNumber;
}
pre:not(.nonum) span.row-number::before {
content: counter(rowNumber);
width: 2rem;
display: inline-block;
color: #a0a0a0;
padding-left: 10px;
margin-right: 10px;
background: #505050;
}
レイアウト | フッター | HTML/JavaScript (タイトルは空欄)
- highlight.js をインポート
- コードの行番号表示と、コピーボタンの追加
<!-- highlight.js -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.10.0/styles/monokai.min.css" integrity="sha512-pU8Ny4jS7Uq58y/K4YLD+jF/74zC3R5SDco/Ln143vyLEmGQY7MV8p+Z5q7big/mNimhvQsfQpprGvUa2QzihQ==" crossorigin="anonymous"/>
<!-- importmap は、module (ここで定義していないものも含む)よりも、先に定義する必要がある -->
<script type="importmap">
{
"imports": {
"highlightjs": "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.10.0/es/highlight.min.js",
"groovy": "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.10.0/es/languages/groovy.min.js",
"dockerfile": "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.10.0/es/languages/dockerfile.min.js"
},
"integrity": {
"https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.10.0/es/highlight.min.js": "sha512-nlc+OFebuN78mhTZDqa8Md8mOLDJ5lKi1mN3q3eoS7uVeFOkpHD+kiaQhV+1nWHRiKwuWPhMrXl4CD/thL1Xsw==",
"https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.10.0/es/languages/groovy.min.js": "sha512-9I2VvFgixVk7CD2euE3ujw4pYEB0yw6BrgQ25UMsK6kKGch9dnA0S4hLsU/uJkl71LnzCx+l000cvxtiCwIHCQ==",
"https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.10.0/es/languages/dockerfile.min.js": "sha512-RVgu/sdw9iWocj3Fz2gY5FlZOK3gKRg7RI9pXL1JE7K/WLsDi8kVHbz2J7BBwNSYPICc8B1+hgym/9XiTW5JFw=="
}
}
</script>
<script type="module">
// Default Languages: bash, c, cpp, csharp, css, diff, go, graphql, ini, java, javascript, json, kotlin,
// less, lua, makefile, markdown, objectivec, perl, php-template, php, plaintext, python-repl, python,
// r, ruby, rust, scss, shell, sql, swift, typescript, vbnet, wasm xml, yaml
import hljs from 'highlightjs';
import groovy from 'groovy';
import dockerfile from 'dockerfile';
hljs.registerLanguage('groovy', groovy);
hljs.registerLanguage('dockerfile', dockerfile);
// Line Number & Copy Button Plugin
const copyToClipboard = async (element) => {
const ranges = [];
const selection = window.getSelection();
const range = document.createRange();
let result = false;
for (let i = 0; i < selection.rangeCount; i += 1) {
ranges[i] = selection.getRangeAt(i);
}
range.selectNodeContents(element);
selection.removeAllRanges();
selection.addRange(range);
try {
await navigator.clipboard.writeText(selection);
result = true;
} catch (e) {
console.error(e);
}
selection.removeAllRanges();
for (let i = 0; i < ranges.length; i += 1) {
selection.addRange(ranges[i]);
}
return result;
};
const body = document.querySelector("body");
const active = [];
body.addEventListener("click", async (e) => {
try {
const target = e.target;
if (!target.classList.contains('copy-button')) return;
const pre = target.closest("pre");
let result = false;
if (active.indexOf(target) !== -1) return;
if (pre) {
result = await copyToClipboard(pre);
target.innerText = (result ? "COPIED!" : "FAILED!");
target.classList.add((result ? "success" : "failed"));
active.push(target);
setTimeout(() => {
let index = active.indexOf(target);
target.className = "copy-button";
target.innerText = "COPY";
if (index !== -1) active.splice(index, 1);
}, 2000);
}
} catch (e) {
console.error(e);
}
});
hljs.addPlugin({
'after:highlightElement': ({el, result}) => {
let lines = result.value;
if (lines.split(/\n/).length >= 2) {
lines = result.value.replace(/^/gm, '<span class="row-number"></span>');
}
el.innerHTML = `<button class="copy-button" style="border-radius: 6px;">COPY</button>${lines}`;
}
});
hljs.highlightAll();
</script>
<!-- Mermaid -->
<script type="module">
import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs';
mermaid.initialize({ startOnLoad: true, theme: 'dark'});
</script>
<!-- MathJax -->
<script id="MathJax-script" async src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js"></script>