@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 CHANGED
@@ -1,4 +1,6 @@
1
- # blog template
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
- `./data/` 以下にマークダウンファイルを入稿します。
20
+ `./src/pages/` 以下にマークダウンファイルを入稿します。
19
21
 
20
- ファイル冒頭を `---` でくくると変数を指定できます。
21
- `title` `url` と `published`が必須です。
22
- `url` は出力先になります。
23
- `published` は index ファイルがソートするのに利用します。
22
+ とりあえずなにも設定せずに、内容は空のままで構わないので `src/pages/test.md` を追加してみます。
23
+ `src/pages/test.md` を保存したら、 `http://localhost:8000/test` にアクセスして表示できていることを確認します。
24
24
 
25
- 次のような内容で、 `data/sample_article.md` などに保存すると `http://localhost:8000/sample_article` でアクセスできるようになります。
25
+ 次に、内容を記述します。
26
+ `src/pages/test.md` を次のような内容で保存します。
26
27
 
27
- ```markdown
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
- 記事は、マークダウン記法で記述し、 `.md` ファイルで保存してください。
38
+ ファイル冒頭を `---` でくくると変数を指定できます。
44
39
 
45
- リンクは次のように記述します。
46
- [アンカーテキストはこちら](http://example.com)
40
+ `src/pages/test.md` の冒頭に次のような変数を設定してみましょう。
47
41
 
48
- リストは次のように記述します。
49
- * リスト1
50
- * リスト2
51
- * リスト3
42
+ ```markdown
43
+ ---
44
+ title: 第一章 人情の碗
45
+ url: /the_book_of_tea/section_one
46
+ ---
47
+ ```
52
48
 
53
- 画像は、 `data/image/` 以下に配置して、パスを指定してください。
49
+ `http://localhost:8000/test` ではアクセスできず、 `http://localhost:8000/the_book_of_tea/section_one` でアクセスできるようになったとおもいます。
54
50
 
55
- `data/image/sample.jpg` などがあれば、次の指定になります。
56
- `![代替テキストを入力してください](/image/sample.jpg)`
51
+ ここで定義した変数は、テンプレート内でも利用できます。
52
+ 次のように書き換えてみます。
57
53
 
58
- ![placeholdの画像](https://placehold.jp/150x150.png)
59
54
 
60
- ### 見出し3
55
+ ```markdown
56
+ ---
57
+ title: 第一章 人情の碗
58
+ url: /the_book_of_tea/section_one
59
+ ---
61
60
 
62
- スクリプトを記述することができます。
61
+ # {{title}}
63
62
 
64
- {script}
65
- return (new Date()).toString()
66
- {/script}
63
+ 茶は薬用として始まり後飲料となる。シナにおいては八世紀に高雅な遊びの一つとして詩歌の域に達した。十五世紀に至り日本はこれを高めて一種の審美的宗教、すなわち茶道にまで進めた。茶道は日常生活の俗事の中に存する美しきものを崇拝することに基づく一種の儀式であって、純粋と調和、相互愛の神秘、社会秩序のローマン主義を諄々と教えるものでる。茶道の要義は「不完全なもの」を崇拝するにある。いわゆる人生というこの不可解なもののうちに、何か可能なものを成就しようとするやさしい企てであるから。
67
64
  ```
68
65
 
69
- [data/sample.md](data/sample.md) にサンプルを置いています。
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
- and in template
85
-
86
- ```html
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
- `{{foo}}` と `{{bar}}` は `'fooVariableContent'` と `'barVariableContent'` に置換されます。
80
+ ## 設定ファイル
101
81
 
102
- ### IF
82
+ サイト全体の名称がデフォルトのままでは味気ないので変えましょう。
83
+ blog.json を開いて、 `site_name` を変更します。
103
84
 
104
- ```markdown
105
- ---
106
- someVariable: true
107
- ---
108
- {if someVariable}
109
- This content will be present.
110
- {/if}
111
-
112
- {if undefinedValue}
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
- ### SCRIPT
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
- if (typeof result === 'undefined') {
140
- result = null
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
- let path = url.pathname === '/' ? '/index.html' : decodeURIComponent(url.pathname)
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tenjuu99/blog",
3
- "version": "0.1.7",
3
+ "version": "0.1.9",
4
4
  "description": "blog template",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -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
- {script}
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>投稿: {script}return helper.dateFormat(variables.published){/script}
37
+ <p>投稿: {{ dateFormat(published) }}
41
38
  {/if}
42
- {if modified} / 更新: {script}return helper.dateFormat(variables.modified){/script}{/if}
39
+ {if modified} / 更新: {{ dateFormat(modified) }}{/if}
43
40
  {if published}
44
41
  </p>
45
42
  </aside>
@@ -4,9 +4,6 @@
4
4
  <meta charset="UTF-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
6
  <title>{{SITE_NAME}}</title>
7
- {if gtag_id}
8
- {include('template/gtag.html')}
9
- {/if}
10
7
  {include('template/css.html')}
11
8
  </head>
12
9
  <body>