@tenjuu99/blog 0.1.7 → 0.1.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +54 -82
- package/helper/index.js +63 -0
- package/lib/filter.js +6 -45
- package/lib/replaceVariablesFilter.js +54 -0
- package/lib/server.js +2 -1
- package/package.json +1 -1
- package/src-sample/pages/index.md +1 -28
- package/src-sample/template/default.html +2 -5
- package/src-sample/template/index.html +0 -3
package/README.md
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
# blog
|
|
1
|
+
# @tenjuu99/blog
|
|
2
|
+
|
|
3
|
+
静的なブログを作るのにどうして React を必要としてしまったんですか
|
|
2
4
|
|
|
3
5
|
## setup
|
|
4
6
|
|
|
@@ -15,111 +17,81 @@ npx server
|
|
|
15
17
|
|
|
16
18
|
## 記事を書く
|
|
17
19
|
|
|
18
|
-
`./
|
|
20
|
+
`./src/pages/` 以下にマークダウンファイルを入稿します。
|
|
19
21
|
|
|
20
|
-
|
|
21
|
-
`
|
|
22
|
-
`url` は出力先になります。
|
|
23
|
-
`published` は index ファイルがソートするのに利用します。
|
|
22
|
+
とりあえずなにも設定せずに、内容は空のままで構わないので `src/pages/test.md` を追加してみます。
|
|
23
|
+
`src/pages/test.md` を保存したら、 `http://localhost:8000/test` にアクセスして表示できていることを確認します。
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
次に、内容を記述します。
|
|
26
|
+
`src/pages/test.md` を次のような内容で保存します。
|
|
26
27
|
|
|
27
|
-
```
|
|
28
|
-
|
|
29
|
-
title: sample article
|
|
30
|
-
url: /sample_article
|
|
31
|
-
published: 2024/03/18 00:00
|
|
32
|
-
---
|
|
33
|
-
|
|
34
|
-
トップレベル見出しは `title` 変数が利用されます。
|
|
35
|
-
変えたい場合は template/default.html を修正してください。
|
|
28
|
+
```
|
|
29
|
+
# 第一章 人情の碗
|
|
36
30
|
|
|
37
|
-
|
|
31
|
+
茶は薬用として始まり後飲料となる。シナにおいては八世紀に高雅な遊びの一つとして詩歌の域に達した。十五世紀に至り日本はこれを高めて一種の審美的宗教、すなわち茶道にまで進めた。茶道は日常生活の俗事の中に存する美しきものを崇拝することに基づく一種の儀式であって、純粋と調和、相互愛の神秘、社会秩序のローマン主義を諄々と教えるものでる。茶道の要義は「不完全なもの」を崇拝するにある。いわゆる人生というこの不可解なもののうちに、何か可能なものを成就しようとするやさしい企てであるから。
|
|
32
|
+
```
|
|
38
33
|
|
|
39
|
-
|
|
34
|
+
保存したら、 `http://localhost:8000/test` をリロードして表示を確認しましょう。
|
|
40
35
|
|
|
41
|
-
##
|
|
36
|
+
## タイトルとURL
|
|
42
37
|
|
|
43
|
-
|
|
38
|
+
ファイル冒頭を `---` でくくると変数を指定できます。
|
|
44
39
|
|
|
45
|
-
|
|
46
|
-
[アンカーテキストはこちら](http://example.com)
|
|
40
|
+
`src/pages/test.md` の冒頭に次のような変数を設定してみましょう。
|
|
47
41
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
42
|
+
```markdown
|
|
43
|
+
---
|
|
44
|
+
title: 第一章 人情の碗
|
|
45
|
+
url: /the_book_of_tea/section_one
|
|
46
|
+
---
|
|
47
|
+
```
|
|
52
48
|
|
|
53
|
-
|
|
49
|
+
`http://localhost:8000/test` ではアクセスできず、 `http://localhost:8000/the_book_of_tea/section_one` でアクセスできるようになったとおもいます。
|
|
54
50
|
|
|
55
|
-
|
|
56
|
-
|
|
51
|
+
ここで定義した変数は、テンプレート内でも利用できます。
|
|
52
|
+
次のように書き換えてみます。
|
|
57
53
|
|
|
58
|
-

|
|
59
54
|
|
|
60
|
-
|
|
55
|
+
```markdown
|
|
56
|
+
---
|
|
57
|
+
title: 第一章 人情の碗
|
|
58
|
+
url: /the_book_of_tea/section_one
|
|
59
|
+
---
|
|
61
60
|
|
|
62
|
-
|
|
61
|
+
# {{title}}
|
|
63
62
|
|
|
64
|
-
|
|
65
|
-
return (new Date()).toString()
|
|
66
|
-
{/script}
|
|
63
|
+
茶は薬用として始まり後飲料となる。シナにおいては八世紀に高雅な遊びの一つとして詩歌の域に達した。十五世紀に至り日本はこれを高めて一種の審美的宗教、すなわち茶道にまで進めた。茶道は日常生活の俗事の中に存する美しきものを崇拝することに基づく一種の儀式であって、純粋と調和、相互愛の神秘、社会秩序のローマン主義を諄々と教えるものでる。茶道の要義は「不完全なもの」を崇拝するにある。いわゆる人生というこの不可解なもののうちに、何か可能なものを成就しようとするやさしい企てであるから。
|
|
67
64
|
```
|
|
68
65
|
|
|
69
|
-
|
|
66
|
+
URLの変更は、変数による定義ではなく、ファイルを移動しても問題ありません。
|
|
67
|
+
`src/pages/test.md` を `src/pages/the_book_of_tea/section_one.md` としてファイルを移動すれば、変数定義がなくてもこのURLになります。
|
|
70
68
|
|
|
71
|
-
##
|
|
69
|
+
## テンプレート
|
|
72
70
|
|
|
73
|
-
|
|
71
|
+
タイトルが重複して表示されて鬱陶しいので、テンプレートを編集します。
|
|
74
72
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
```markdown
|
|
78
|
-
---
|
|
79
|
-
foo: fooVariableContent
|
|
80
|
-
bar: varVariableContent
|
|
81
|
-
---
|
|
82
|
-
```
|
|
73
|
+
`src/template/default.html` を開き、h1 を削除しましょう。
|
|
83
74
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
<!DOCTYPE html>
|
|
88
|
-
<html lang="en">
|
|
89
|
-
<head>
|
|
90
|
-
<meta charset="UTF-8">
|
|
91
|
-
<title></title>
|
|
92
|
-
</head>
|
|
93
|
-
<body>
|
|
94
|
-
{{foo}}
|
|
95
|
-
{{bar}}
|
|
96
|
-
</body>
|
|
97
|
-
</html>
|
|
75
|
+
```diff
|
|
76
|
+
<article class="container">
|
|
77
|
+
- <h1>{{TITLE}}</h1>
|
|
98
78
|
```
|
|
99
79
|
|
|
100
|
-
|
|
80
|
+
## 設定ファイル
|
|
101
81
|
|
|
102
|
-
|
|
82
|
+
サイト全体の名称がデフォルトのままでは味気ないので変えましょう。
|
|
83
|
+
blog.json を開いて、 `site_name` を変更します。
|
|
103
84
|
|
|
104
|
-
```
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
This content will be removed.
|
|
114
|
-
{else}
|
|
115
|
-
This is else content.
|
|
116
|
-
{/if}
|
|
85
|
+
```json
|
|
86
|
+
{
|
|
87
|
+
"site_name": "茶の本",
|
|
88
|
+
"url_base": "http://localhost:8000",
|
|
89
|
+
"src_dir": "src",
|
|
90
|
+
"dist_dir": "dist",
|
|
91
|
+
"distribute_raw": "image",
|
|
92
|
+
"helper": "helper/index.js"
|
|
93
|
+
}
|
|
117
94
|
```
|
|
118
95
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
```markdown
|
|
122
|
-
<script type="ssg">
|
|
123
|
-
return 'これはスクリプトが実行された結果出力されました。'
|
|
124
|
-
</script>
|
|
125
|
-
```
|
|
96
|
+
設定ファイルを変更したら、一旦 `CTRL+c` で `npx server` を停止して、もういちど `npx server` を実行します。
|
|
97
|
+
ヘッダーが「茶の本」になっていたら設定更新が完了です。
|
package/helper/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { allData } from "../lib/indexer.js";
|
|
2
2
|
import { replaceVariablesFilter } from "../lib/filter.js";
|
|
3
|
+
import config from '../lib/config.js'
|
|
3
4
|
|
|
4
5
|
export function readIndex (filter = null) {
|
|
5
6
|
const data = Object.entries(allData)
|
|
@@ -51,3 +52,65 @@ export function indexedItems() {
|
|
|
51
52
|
indexedItemsSorted = sorted
|
|
52
53
|
return indexedItemsSorted
|
|
53
54
|
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* 配列を再帰的に順不同リストに変換する
|
|
58
|
+
* @param {Array|string} arrayOrText
|
|
59
|
+
* @returns {mixed}
|
|
60
|
+
*/
|
|
61
|
+
export function arrayToList(arrayOrText) {
|
|
62
|
+
if (typeof arrayOrText === 'string') {
|
|
63
|
+
return `<li>${arrayOrText}</li>`
|
|
64
|
+
}
|
|
65
|
+
if (Array.isArray(arrayOrText)) {
|
|
66
|
+
let resultListText = '<ul>'
|
|
67
|
+
for (const item of arrayOrText) {
|
|
68
|
+
if (Array.isArray(item)) {
|
|
69
|
+
resultListText += `<li>${arrayToList(item)}</li>`
|
|
70
|
+
} else {
|
|
71
|
+
resultListText += `<li>${item}</li>`
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
resultListText += '</ul>'
|
|
75
|
+
arrayOrText = resultListText
|
|
76
|
+
}
|
|
77
|
+
return arrayOrText
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function renderIndex(pages, nodate = 'nodate', headingTag = 'h3') {
|
|
81
|
+
if (!pages) {
|
|
82
|
+
pages = readIndex()
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const renderList = {}
|
|
86
|
+
for (const page of pages) {
|
|
87
|
+
if (page.index) {
|
|
88
|
+
const url = config.relative_path ? config.relative_path + page.url : page.url
|
|
89
|
+
if (page.published === '1970-01-01') {
|
|
90
|
+
if (!renderList[nodate]) {
|
|
91
|
+
renderList[nodate] = []
|
|
92
|
+
}
|
|
93
|
+
renderList[nodate].push(`<a href="${url}">${page.title}</a>`)
|
|
94
|
+
continue
|
|
95
|
+
}
|
|
96
|
+
const published = new Date(page.published)
|
|
97
|
+
const year = `${published.getFullYear()}年`
|
|
98
|
+
const date = `${published.getMonth() +1}月${published.getDate()}日`
|
|
99
|
+
if (!renderList[year]) {
|
|
100
|
+
renderList[year] = []
|
|
101
|
+
}
|
|
102
|
+
renderList[year].push(`<a href="${url}">${page.title}</a> (${date})`)
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const resultText = []
|
|
107
|
+
for (const key in renderList) {
|
|
108
|
+
resultText.push(`<${headingTag}>${key}</${headingTag}>`)
|
|
109
|
+
if (Array.isArray(renderList[key])) {
|
|
110
|
+
resultText.push(arrayToList(renderList[key]))
|
|
111
|
+
} else {
|
|
112
|
+
resultText.push(`<p>${renderList[key]}</p>`)
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return resultText.join('\n')
|
|
116
|
+
}
|
package/lib/filter.js
CHANGED
|
@@ -2,37 +2,7 @@ import helper from '../lib/helper.js'
|
|
|
2
2
|
import includeFilter from './includeFilter.js'
|
|
3
3
|
import { srcDir } from './dir.js'
|
|
4
4
|
import config from './config.js'
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* @param {string} text
|
|
8
|
-
* @params {object} variables
|
|
9
|
-
* @return {text}
|
|
10
|
-
*/
|
|
11
|
-
const replaceVariablesFilter = (text, variables) => {
|
|
12
|
-
const matched = [...text.matchAll(/(\\)?{{\s?([\w\d\s,-_\(\)]+)\s?}}/g)]
|
|
13
|
-
const replace = Object.fromEntries(matched.map(match => [match[2].toLowerCase(), {replace: match[0], backslash: match[1]}]))
|
|
14
|
-
let replaced = text
|
|
15
|
-
for (const elm in replace) {
|
|
16
|
-
const toBeReplace = replace[elm].replace
|
|
17
|
-
const toBeReplaceScript = toBeReplace.match(/([\w\d_]+)\((.*)\)/)
|
|
18
|
-
|
|
19
|
-
if (replace[elm].backslash) { // escape variable syntax
|
|
20
|
-
const removeBackslash = replace[elm].replace.replace(/\\/, '')
|
|
21
|
-
replaced = replaced.replaceAll(toBeReplace, removeBackslash)
|
|
22
|
-
} else if (toBeReplaceScript) { // execute helper
|
|
23
|
-
const func = toBeReplaceScript[1]
|
|
24
|
-
const args = toBeReplaceScript[2].split(',').map(v => variables[v.trim()] ?? undefined)
|
|
25
|
-
if (!helper[func]) {
|
|
26
|
-
throw new Error('helper function is missing. function name: ' + func);
|
|
27
|
-
}
|
|
28
|
-
const replaceText = helper[func].call(null, ...args)
|
|
29
|
-
replaced = replaced.replaceAll(toBeReplace, replaceText)
|
|
30
|
-
} else {
|
|
31
|
-
replaced = replaced.replaceAll(toBeReplace, variables[elm])
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
return replaced
|
|
35
|
-
}
|
|
5
|
+
import replaceVariablesFilter from './replaceVariablesFilter.js'
|
|
36
6
|
|
|
37
7
|
/**
|
|
38
8
|
* @param {string} condition
|
|
@@ -136,22 +106,13 @@ const replaceScriptFilter = async (text, variables) => {
|
|
|
136
106
|
if (result instanceof Promise) {
|
|
137
107
|
result = await result
|
|
138
108
|
}
|
|
139
|
-
|
|
140
|
-
|
|
109
|
+
switch (typeof result) {
|
|
110
|
+
case 'undefined':
|
|
111
|
+
case 'null':
|
|
112
|
+
result = ''
|
|
141
113
|
}
|
|
142
114
|
if (Array.isArray(result)) {
|
|
143
|
-
result = arrayToList(result)
|
|
144
|
-
} else if (typeof result === 'object') {
|
|
145
|
-
const resultText = []
|
|
146
|
-
for (const key in result) {
|
|
147
|
-
resultText.push(`<h3>${key}</h3>`)
|
|
148
|
-
if (Array.isArray(result[key])) {
|
|
149
|
-
resultText.push(arrayToList(result[key]))
|
|
150
|
-
} else {
|
|
151
|
-
resultText.push(`<p>${result[key]}</p>`)
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
result = resultText.join('\n')
|
|
115
|
+
result = helper.arrayToList(result)
|
|
155
116
|
}
|
|
156
117
|
replaced = replaced.replace(script.replace, result)
|
|
157
118
|
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import helper from './helper.js'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* テキストに含まれる変数を一括変換する
|
|
5
|
+
* ヘルパー処理もここで行われる
|
|
6
|
+
*
|
|
7
|
+
* @param {string} text
|
|
8
|
+
* @params {object} variables
|
|
9
|
+
* @return {text}
|
|
10
|
+
*/
|
|
11
|
+
const replaceVariablesFilter = (text, variables) => {
|
|
12
|
+
const matched = [...text.matchAll(/(\\)?{{[\s]*([^{}]+)[\s]*}}/g)]
|
|
13
|
+
const replace = Object.fromEntries(matched.map(match => [match[0], {variableName: match[2].trim().toLowerCase(), backslash: !!match[1]}]))
|
|
14
|
+
let replaced = text
|
|
15
|
+
for (const elm in replace) {
|
|
16
|
+
const [toBeReplace, replacedText] = replaceVariable(replace[elm].variableName, elm, replace[elm].backslash, variables)
|
|
17
|
+
replaced = replaced.replaceAll(toBeReplace, replacedText)
|
|
18
|
+
}
|
|
19
|
+
return replaced
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* @param {string} target
|
|
24
|
+
* @param {string} replaceTargetRawText
|
|
25
|
+
* @param {boolean} backslash
|
|
26
|
+
* @param {object} variables
|
|
27
|
+
* @return {Array.<string>}
|
|
28
|
+
*/
|
|
29
|
+
const replaceVariable = (target, replaceTargetRawText, backslash, variables) => {
|
|
30
|
+
const toBeReplace = replaceTargetRawText
|
|
31
|
+
const toBeReplaceScript = toBeReplace.match(/([\w\d_]+)\((.*)\)/)
|
|
32
|
+
if (backslash) { // escape variable syntax
|
|
33
|
+
const removeBackslash = replaceTargetRawText.replace(/\\/, '')
|
|
34
|
+
return [replaceTargetRawText, removeBackslash]
|
|
35
|
+
} else if (toBeReplaceScript) { // execute helper
|
|
36
|
+
const func = toBeReplaceScript[1]
|
|
37
|
+
const args = toBeReplaceScript[2].split(',').map(v => {
|
|
38
|
+
const val = v.trim()
|
|
39
|
+
const argAsString = val.match(/^['"](.+)['"]$/)
|
|
40
|
+
if (argAsString) {
|
|
41
|
+
return argAsString[1]
|
|
42
|
+
}
|
|
43
|
+
return variables[val] ?? undefined
|
|
44
|
+
})
|
|
45
|
+
if (!helper[func]) {
|
|
46
|
+
throw new Error('helper function is missing. function name: ' + func);
|
|
47
|
+
}
|
|
48
|
+
const replaceText = helper[func].call(null, ...args)
|
|
49
|
+
return [replaceTargetRawText, replaceText]
|
|
50
|
+
}
|
|
51
|
+
return [replaceTargetRawText, variables[target]]
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export default replaceVariablesFilter
|
package/lib/server.js
CHANGED
|
@@ -33,7 +33,8 @@ const contentType = (ext) => {
|
|
|
33
33
|
const server = () => {
|
|
34
34
|
return http.createServer((request, response) => {
|
|
35
35
|
const url = new URL(`http://${request.headers.host}${request.url}`)
|
|
36
|
-
|
|
36
|
+
const isIndex = url.pathname.match(/(.+)?\/$/)
|
|
37
|
+
let path = isIndex ? `${url.pathname}index.html` : decodeURIComponent(url.pathname)
|
|
37
38
|
if (!path.includes('.')) {
|
|
38
39
|
path += '.html'
|
|
39
40
|
}
|
package/package.json
CHANGED
|
@@ -2,34 +2,7 @@
|
|
|
2
2
|
title: INDEX
|
|
3
3
|
template: index.html
|
|
4
4
|
index: false
|
|
5
|
-
url: /
|
|
6
|
-
published: 2023-03-03 20:21
|
|
7
|
-
modified: 2023-03-03 20:21
|
|
8
5
|
---
|
|
9
6
|
## INDEX
|
|
10
7
|
|
|
11
|
-
{
|
|
12
|
-
const data = helper.readIndex()
|
|
13
|
-
|
|
14
|
-
const pages = {}
|
|
15
|
-
for (const page of data) {
|
|
16
|
-
if (page.index) {
|
|
17
|
-
const url = variables.relative_path ? variables.relative_path + page.url : page.url
|
|
18
|
-
if (page.published === '1970-01-01') {
|
|
19
|
-
if (!pages['日付なし']) {
|
|
20
|
-
pages['日付なし'] = []
|
|
21
|
-
}
|
|
22
|
-
pages['日付なし'].push(`<a href="${url}">${page.title}</a>`)
|
|
23
|
-
continue
|
|
24
|
-
}
|
|
25
|
-
const published = new Date(page.published)
|
|
26
|
-
const year = `${published.getFullYear()}年`
|
|
27
|
-
const date = `${published.getMonth() +1}月${published.getDate()}日`
|
|
28
|
-
if (!pages[year]) {
|
|
29
|
-
pages[year] = []
|
|
30
|
-
}
|
|
31
|
-
pages[year].push(`<a href="${url}">${page.title}</a> (${date})`)
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
return pages
|
|
35
|
-
{/script}
|
|
8
|
+
{{ renderIndex(null, "日付なし") }}
|
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
<!DOCTYPE html>
|
|
2
2
|
<html lang="{{LANG}}">
|
|
3
3
|
<head prefix="og: http://ogp.me/ns#">
|
|
4
|
-
{if gtag_id}
|
|
5
|
-
{include('template/gtag.html')}
|
|
6
|
-
{/if}
|
|
7
4
|
<meta charset="UTF-8">
|
|
8
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
9
6
|
<title>{{TITLE}} | {{SITE_NAME}}</title>
|
|
@@ -37,9 +34,9 @@
|
|
|
37
34
|
<h1>{{TITLE}}</h1>
|
|
38
35
|
{if published != '1970-01-01'}
|
|
39
36
|
<aside class="published">
|
|
40
|
-
<p>投稿: {
|
|
37
|
+
<p>投稿: {{ dateFormat(published) }}
|
|
41
38
|
{/if}
|
|
42
|
-
{if modified} / 更新: {
|
|
39
|
+
{if modified} / 更新: {{ dateFormat(modified) }}{/if}
|
|
43
40
|
{if published}
|
|
44
41
|
</p>
|
|
45
42
|
</aside>
|