@tenjuu99/blog 0.2.1 → 0.2.3
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/lib/config.js +0 -7
- package/lib/distribute.js +4 -1
- package/lib/files.js +1 -1
- package/lib/filter.js +18 -1
- package/lib/indexer.js +3 -1
- package/lib/pageData.js +1 -0
- package/package.json +4 -1
- package/src-sample/css/editor.css +137 -0
- package/src-sample/css/layout.css +9 -6
- package/src-sample/helper/index.js +6 -0
- package/src-sample/js/editor.js +100 -0
- package/src-sample/pages/{editor.html → editor.md} +2 -0
- package/src-sample/pages/post/index.md +1 -0
- package/src-sample/server/editor.js +3 -4
- package/src-sample/server/get_editor_target.js +2 -3
- package/src-sample/server/preview.js +2 -3
- package/src-sample/template/default.html +4 -4
- package/src-sample/template/editor.html +19 -167
package/lib/config.js
CHANGED
|
@@ -18,13 +18,6 @@ try {
|
|
|
18
18
|
config[item] = configOverride[item]
|
|
19
19
|
}
|
|
20
20
|
}
|
|
21
|
-
const keys = Object.keys(process.env)
|
|
22
|
-
for (const item in config) {
|
|
23
|
-
const upper = item.toUpperCase()
|
|
24
|
-
if (keys.includes(upper)) {
|
|
25
|
-
config[item] = process.env[upper]
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
21
|
} catch (e) {
|
|
29
22
|
console.log(e)
|
|
30
23
|
}
|
package/lib/distribute.js
CHANGED
|
@@ -21,8 +21,11 @@ const distribute = async (data, srcDir, distDir) => {
|
|
|
21
21
|
const promises = []
|
|
22
22
|
const newIndex = []
|
|
23
23
|
for (const name in data) {
|
|
24
|
+
if (!data[name].distribute) {
|
|
25
|
+
continue
|
|
26
|
+
}
|
|
24
27
|
promises.push(renderPage(data[name]))
|
|
25
|
-
newIndex.push({ name: data.name, url: data.url, __output: data.__output })
|
|
28
|
+
newIndex.push({ name: data[name].name, url: data[name].url, __output: data[name].__output })
|
|
26
29
|
}
|
|
27
30
|
const renderedString = await Promise.all(promises)
|
|
28
31
|
for (const page of renderedString) {
|
package/lib/files.js
CHANGED
|
@@ -21,7 +21,7 @@ const staticFile = (name) => {
|
|
|
21
21
|
if (!loaded) {
|
|
22
22
|
throw new Error('not initialized')
|
|
23
23
|
}
|
|
24
|
-
if (staticFilesContainer[name]) {
|
|
24
|
+
if (typeof staticFilesContainer[name] !== 'undefined') {
|
|
25
25
|
return staticFilesContainer[name]
|
|
26
26
|
}
|
|
27
27
|
if (name.indexOf('template/') === 0) {
|
package/lib/filter.js
CHANGED
|
@@ -37,6 +37,23 @@ const ifConditionEvaluator = (condition, variables) => {
|
|
|
37
37
|
return left != right
|
|
38
38
|
}
|
|
39
39
|
} else {
|
|
40
|
+
const match = condition.match(/([\w]+)\((.*)\)/)
|
|
41
|
+
if (match) {
|
|
42
|
+
const func = match[1]
|
|
43
|
+
if (helper[func] instanceof Function) {
|
|
44
|
+
let args = match[2].trim()
|
|
45
|
+
if (args) {
|
|
46
|
+
args = args.split(',').map(arg => {
|
|
47
|
+
const match = arg.match(/^(['"])?\s*([\w]+)(["'])?$/)
|
|
48
|
+
if (match) {
|
|
49
|
+
return match[1] ? `${match[2]}` : variables[match[2]]
|
|
50
|
+
}
|
|
51
|
+
return arg
|
|
52
|
+
})
|
|
53
|
+
}
|
|
54
|
+
return helper[func].call(args)
|
|
55
|
+
}
|
|
56
|
+
}
|
|
40
57
|
if (variables.hasOwnProperty(condition)) {
|
|
41
58
|
return !!variables[condition]
|
|
42
59
|
}
|
|
@@ -56,7 +73,7 @@ const replaceIfFilter = (text, variables) => {
|
|
|
56
73
|
const target = item[0]
|
|
57
74
|
const content = item.groups.content
|
|
58
75
|
const elseContent = item.groups.elsecontent
|
|
59
|
-
if (ifConditionEvaluator(item.groups.condition, variables)) {
|
|
76
|
+
if (ifConditionEvaluator(item.groups.condition.trim(), variables)) {
|
|
60
77
|
text = text.replace(target, content)
|
|
61
78
|
} else if (elseContent) {
|
|
62
79
|
text = text.replace(target, elseContent)
|
package/lib/indexer.js
CHANGED
|
@@ -33,7 +33,9 @@ const indexing = async () => {
|
|
|
33
33
|
const files = await Promise.all(collect(pageDir))
|
|
34
34
|
files.forEach((file) => {
|
|
35
35
|
const pageData = makePageData(file[0], file[1])
|
|
36
|
-
|
|
36
|
+
if (pageData.distribute) {
|
|
37
|
+
allData[pageData.name] = pageData
|
|
38
|
+
}
|
|
37
39
|
})
|
|
38
40
|
}
|
|
39
41
|
|
package/lib/pageData.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tenjuu99/blog",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.3",
|
|
4
4
|
"description": "blog template",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -23,6 +23,9 @@
|
|
|
23
23
|
"chokidar": "^3.6.0",
|
|
24
24
|
"marked": "^13.x"
|
|
25
25
|
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"@tenjuu99/blog": "file:./"
|
|
28
|
+
},
|
|
26
29
|
"type": "module",
|
|
27
30
|
"engines": {
|
|
28
31
|
"node": ">=21.7"
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
body {
|
|
2
|
+
background: #fafafa;
|
|
3
|
+
--container-max-width: 90%;
|
|
4
|
+
--container-min-width: 90%;
|
|
5
|
+
--container-width: 90%;
|
|
6
|
+
height: 100vh;
|
|
7
|
+
}
|
|
8
|
+
main {
|
|
9
|
+
height: 100%;
|
|
10
|
+
margin: 0 auto;
|
|
11
|
+
}
|
|
12
|
+
.textareaAndPreview {
|
|
13
|
+
display: flex;
|
|
14
|
+
margin: 10px 0;
|
|
15
|
+
border: 1px solid #666;
|
|
16
|
+
border-radius: 5px;
|
|
17
|
+
height: 100%;
|
|
18
|
+
}
|
|
19
|
+
.textareaAndPreview>* {
|
|
20
|
+
flex-basis: 50%;
|
|
21
|
+
}
|
|
22
|
+
.editor {
|
|
23
|
+
display: flex;
|
|
24
|
+
flex-direction: column;
|
|
25
|
+
padding: 15px 0;
|
|
26
|
+
justify-content:center;
|
|
27
|
+
height: 100%;
|
|
28
|
+
}
|
|
29
|
+
#editorTextArea {
|
|
30
|
+
resize: none;
|
|
31
|
+
background: #cccccc;
|
|
32
|
+
color: #333;
|
|
33
|
+
padding: 5px;
|
|
34
|
+
border-radius: 5px 0 0 5px;
|
|
35
|
+
border: 0;
|
|
36
|
+
transition: background 0.2s;
|
|
37
|
+
}
|
|
38
|
+
#editorTextArea:focus {
|
|
39
|
+
background: #eee;
|
|
40
|
+
transition: background 0.2s;
|
|
41
|
+
outline: none;
|
|
42
|
+
}
|
|
43
|
+
form select {
|
|
44
|
+
padding: 5px;
|
|
45
|
+
margin-top: 5px;
|
|
46
|
+
border-radius: 5px;
|
|
47
|
+
}
|
|
48
|
+
#previewContent {
|
|
49
|
+
flex-basis: 100%;
|
|
50
|
+
height: 100%;
|
|
51
|
+
min-height: 90%;
|
|
52
|
+
display: block;
|
|
53
|
+
width: 100%;
|
|
54
|
+
}
|
|
55
|
+
#previewContent iframe {
|
|
56
|
+
width: 100%;
|
|
57
|
+
height: 100%;
|
|
58
|
+
border: none;
|
|
59
|
+
border-radius: 0 5px 5px 0;
|
|
60
|
+
}
|
|
61
|
+
.sidebar {
|
|
62
|
+
display: flex;
|
|
63
|
+
justify-content: space-between;
|
|
64
|
+
width: 50vw;
|
|
65
|
+
overflow: hidden;
|
|
66
|
+
position: absolute;
|
|
67
|
+
background: rgba(104, 117, 154);
|
|
68
|
+
height: 100%;
|
|
69
|
+
padding: 0;
|
|
70
|
+
box-shadow: 3px 13px 13px rgba(50, 50, 50, 0.5);
|
|
71
|
+
left: 0;
|
|
72
|
+
transition: left 0.3s, width 0.3s;
|
|
73
|
+
overflow-y: hidden;
|
|
74
|
+
}
|
|
75
|
+
.sidebar.close {
|
|
76
|
+
left: -48vw;
|
|
77
|
+
transition: left 0.3s, width 0.3s;
|
|
78
|
+
}
|
|
79
|
+
@media screen and (min-width: 1100px) {
|
|
80
|
+
.sidebar {
|
|
81
|
+
width: 40vw;
|
|
82
|
+
}
|
|
83
|
+
.sidebar.close {
|
|
84
|
+
left: -39vw;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
@media screen and (min-width: 1400px) {
|
|
88
|
+
.sidebar {
|
|
89
|
+
width: 30vw;
|
|
90
|
+
}
|
|
91
|
+
.sidebar.close {
|
|
92
|
+
left: -29vw;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
@media screen and (max-width: 600px) {
|
|
96
|
+
.sidebar {
|
|
97
|
+
width: 80vw;
|
|
98
|
+
}
|
|
99
|
+
.sidebar.close {
|
|
100
|
+
left: -77vw;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
.sidebar li {
|
|
104
|
+
list-style: none;
|
|
105
|
+
}
|
|
106
|
+
.sidebar a {
|
|
107
|
+
color: #fff;
|
|
108
|
+
}
|
|
109
|
+
.sidebar-toggle {
|
|
110
|
+
cursor: pointer;
|
|
111
|
+
width: 20px;
|
|
112
|
+
height: 100%;
|
|
113
|
+
position: relative;
|
|
114
|
+
}
|
|
115
|
+
.sidebar-toggle:hover:after {
|
|
116
|
+
position: absolute;
|
|
117
|
+
right: 0;
|
|
118
|
+
content: ' ';
|
|
119
|
+
height: 100%;
|
|
120
|
+
width: 20px;
|
|
121
|
+
background: rgba(255, 255,255, 0.3);
|
|
122
|
+
}
|
|
123
|
+
@media screen and (max-width: 600px) {
|
|
124
|
+
main {
|
|
125
|
+
width: 100%;
|
|
126
|
+
display: block;
|
|
127
|
+
}
|
|
128
|
+
.editor {
|
|
129
|
+
padding: 15px 0;
|
|
130
|
+
}
|
|
131
|
+
.preview {
|
|
132
|
+
display: none;
|
|
133
|
+
}
|
|
134
|
+
#editorTextArea {
|
|
135
|
+
flex-basis: 100%;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
@@ -4,6 +4,9 @@
|
|
|
4
4
|
--headline-3: 16px;
|
|
5
5
|
--text-size: 16px;
|
|
6
6
|
--text-size-small: 12px;
|
|
7
|
+
--container-max-width: 80%;
|
|
8
|
+
--container-min-width: 600px;
|
|
9
|
+
--container-width: 70vw;
|
|
7
10
|
}
|
|
8
11
|
|
|
9
12
|
body {
|
|
@@ -21,16 +24,16 @@ body {
|
|
|
21
24
|
.container {
|
|
22
25
|
margin: 0 auto;
|
|
23
26
|
padding: 5px 30px;
|
|
24
|
-
max-width: 80
|
|
25
|
-
min-width: 600px;
|
|
26
|
-
width: 70vw;
|
|
27
|
+
max-width: var(--container-max-width, 80%);
|
|
28
|
+
min-width: var(--container-min-width, 600px);
|
|
29
|
+
width: var(--container-width, 70vw);
|
|
27
30
|
}
|
|
28
31
|
|
|
29
32
|
@media screen and (max-width: 600px) {
|
|
30
33
|
.container {
|
|
31
|
-
max-width: 100%;
|
|
32
|
-
width: 100%;
|
|
33
|
-
|
|
34
|
+
--container-max-width: 100%;
|
|
35
|
+
--container-min-width: 100%;
|
|
36
|
+
--container-width: 100%;
|
|
34
37
|
padding: 5px 15px;
|
|
35
38
|
}
|
|
36
39
|
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
const sleep = waitTime => new Promise( resolve => setTimeout(resolve, waitTime) );
|
|
2
|
+
|
|
3
|
+
const fetchData = (target) => {
|
|
4
|
+
return fetch(`/get_editor_target?md=${target}`)
|
|
5
|
+
.then(async res => {
|
|
6
|
+
const json = await res.json()
|
|
7
|
+
return json
|
|
8
|
+
})
|
|
9
|
+
}
|
|
10
|
+
const onloadFunction = async (e) => {
|
|
11
|
+
const form = document.querySelector('#editor')
|
|
12
|
+
const textarea = form.querySelector('#editorTextArea')
|
|
13
|
+
const select = form.querySelector('#selectDataFile')
|
|
14
|
+
const inputFileName = form.querySelector('#inputFileName')
|
|
15
|
+
const preview = document.querySelector('#previewContent')
|
|
16
|
+
const url = new URL(location)
|
|
17
|
+
const target = url.searchParams.get('md')
|
|
18
|
+
if (target) {
|
|
19
|
+
fetchData(target).then(json => {
|
|
20
|
+
textarea.value = json.content
|
|
21
|
+
select.value = json.filename
|
|
22
|
+
inputFileName.value = json.filename
|
|
23
|
+
inputFileName.setAttribute('disabled', true)
|
|
24
|
+
submit('/preview', form)
|
|
25
|
+
})
|
|
26
|
+
}
|
|
27
|
+
select.addEventListener('change', async (event) => {
|
|
28
|
+
if (select.value) {
|
|
29
|
+
const json = await fetchData(select.value)
|
|
30
|
+
textarea.value = json.content
|
|
31
|
+
inputFileName.value = json.filename
|
|
32
|
+
inputFileName.setAttribute('disabled', true)
|
|
33
|
+
url.searchParams.set('md', select.value)
|
|
34
|
+
submit('/preview', form)
|
|
35
|
+
} else {
|
|
36
|
+
inputFileName.value = ""
|
|
37
|
+
inputFileName.removeAttribute('disabled')
|
|
38
|
+
textarea.value = ''
|
|
39
|
+
url.searchParams.set('md', "")
|
|
40
|
+
const iframe = preview.querySelector('iframe')
|
|
41
|
+
if (iframe) {
|
|
42
|
+
iframe.srcdoc = ''
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
history.pushState({}, "", url)
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
const submit = (fetchUrl, form) => {
|
|
49
|
+
const formData = new FormData(form)
|
|
50
|
+
const obj = {}
|
|
51
|
+
formData.forEach((v, k) => {
|
|
52
|
+
obj[k] = v
|
|
53
|
+
})
|
|
54
|
+
fetch(fetchUrl, {
|
|
55
|
+
method: 'post',
|
|
56
|
+
body: JSON.stringify(obj)
|
|
57
|
+
}).then(async response => {
|
|
58
|
+
const json = await response.json()
|
|
59
|
+
if (!response.ok) {
|
|
60
|
+
alert(json.message)
|
|
61
|
+
return
|
|
62
|
+
}
|
|
63
|
+
if (json.href) {
|
|
64
|
+
await sleep(300)
|
|
65
|
+
location.href = json.href
|
|
66
|
+
}
|
|
67
|
+
if (json.preview) {
|
|
68
|
+
const iframe = document.createElement('iframe')
|
|
69
|
+
iframe.setAttribute('srcdoc', json.preview)
|
|
70
|
+
iframe.setAttribute('sandbox', '')
|
|
71
|
+
const old = preview.querySelector('iframe')
|
|
72
|
+
if (!old) {
|
|
73
|
+
preview.appendChild(iframe)
|
|
74
|
+
}
|
|
75
|
+
old.setAttribute('srcdoc', json.preview)
|
|
76
|
+
}
|
|
77
|
+
}).catch(e => {
|
|
78
|
+
console.log(e.message)
|
|
79
|
+
})
|
|
80
|
+
}
|
|
81
|
+
form.addEventListener('submit', (event) => {
|
|
82
|
+
event.preventDefault()
|
|
83
|
+
const fetchUrl = event.submitter.dataset.url
|
|
84
|
+
submit(fetchUrl, event.target)
|
|
85
|
+
})
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const sidebarToggle = (e) => {
|
|
89
|
+
const sidebar = document.querySelector('.sidebar')
|
|
90
|
+
const toggle = sidebar.querySelector('.sidebar-toggle')
|
|
91
|
+
toggle.addEventListener('click', (e) => {
|
|
92
|
+
e.preventDefault()
|
|
93
|
+
sidebar.classList.toggle('close')
|
|
94
|
+
})
|
|
95
|
+
}
|
|
96
|
+
document.addEventListener('DOMContentLoaded', (event) => {
|
|
97
|
+
console.log(event)
|
|
98
|
+
onloadFunction(event)
|
|
99
|
+
sidebarToggle(event)
|
|
100
|
+
})
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { IncomingMessage, ServerResponse } from 'http'
|
|
2
2
|
import fs from 'node:fs/promises'
|
|
3
3
|
import { styleText } from 'node:util'
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
const dir = (await import('../../lib/dir.js'))
|
|
4
|
+
import config from '@tenjuu99/blog/lib/config.js'
|
|
5
|
+
import { pageDir } from '@tenjuu99/blog/lib/dir.js'
|
|
7
6
|
|
|
8
7
|
export const path = '/editor'
|
|
9
8
|
|
|
@@ -25,7 +24,7 @@ export const post = async (req, res) => {
|
|
|
25
24
|
}))
|
|
26
25
|
return
|
|
27
26
|
}
|
|
28
|
-
await fs.writeFile(`${
|
|
27
|
+
await fs.writeFile(`${pageDir}/${file}`, json.content)
|
|
29
28
|
console.log(styleText('blue', '[editor/post] finished'))
|
|
30
29
|
|
|
31
30
|
const href = file.split('.')[0]
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { IncomingMessage, ServerResponse } from 'http'
|
|
2
2
|
import fs from 'node:fs'
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
const pageDir = (await import('../../lib/dir.js')).pageDir
|
|
3
|
+
import config from '@tenjuu99/blog/lib/config.js'
|
|
4
|
+
import { pageDir } from '@tenjuu99/blog/lib/dir.js'
|
|
6
5
|
|
|
7
6
|
export const path = '/get_editor_target'
|
|
8
7
|
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { IncomingMessage, ServerResponse } from 'http'
|
|
2
2
|
import { styleText } from 'node:util'
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
const makePageData = (await import('../../lib/pageData.js')).default
|
|
3
|
+
import render from '@tenjuu99/blog/lib/render.js'
|
|
4
|
+
import makePageData from '@tenjuu99/blog/lib/pageData.js'
|
|
6
5
|
|
|
7
6
|
export const path = '/preview'
|
|
8
7
|
|
|
@@ -43,15 +43,15 @@
|
|
|
43
43
|
{/if}
|
|
44
44
|
{{MARKDOWN}}
|
|
45
45
|
</article>
|
|
46
|
-
|
|
46
|
+
|
|
47
|
+
{include('template/prevNext.html')}
|
|
48
|
+
</main>
|
|
49
|
+
{if isEditorEnabled() }
|
|
47
50
|
<div class="container">
|
|
48
51
|
<a href="/editor?md={{name}}.{{__filetype}}">編集する</a>
|
|
49
52
|
</div>
|
|
50
53
|
{/if}
|
|
51
54
|
|
|
52
|
-
{include('template/prevNext.html')}
|
|
53
|
-
</main>
|
|
54
|
-
|
|
55
55
|
{include('template/footer.html')}
|
|
56
56
|
</body>
|
|
57
57
|
</html>
|
|
@@ -5,175 +5,27 @@
|
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
6
|
<title>{{SITE_NAME}}</title>
|
|
7
7
|
{include('template/css.html')}
|
|
8
|
-
<
|
|
9
|
-
|
|
10
|
-
background: #fafafa;
|
|
11
|
-
}
|
|
12
|
-
main {
|
|
13
|
-
width: 90%;
|
|
14
|
-
padding: 15px;
|
|
15
|
-
height: 100%;
|
|
16
|
-
margin: 0 auto;
|
|
17
|
-
}
|
|
18
|
-
.textareaAndPreview {
|
|
19
|
-
display: flex;
|
|
20
|
-
margin: 10px 0;
|
|
21
|
-
border: 1px solid #666;
|
|
22
|
-
border-radius: 5px;
|
|
23
|
-
}
|
|
24
|
-
.textareaAndPreview>* {
|
|
25
|
-
flex-basis: 50%;
|
|
26
|
-
height: 80vh;
|
|
27
|
-
}
|
|
28
|
-
.editor {
|
|
29
|
-
display: flex;
|
|
30
|
-
flex-direction: column;
|
|
31
|
-
padding: 15px 0;
|
|
32
|
-
justify-content:center;
|
|
33
|
-
}
|
|
34
|
-
#editorTextArea {
|
|
35
|
-
resize: none;
|
|
36
|
-
background: #cccccc;
|
|
37
|
-
color: #333;
|
|
38
|
-
padding: 5px;
|
|
39
|
-
border-radius: 5px 0 0 5px;
|
|
40
|
-
border: 0;
|
|
41
|
-
}
|
|
42
|
-
form select {
|
|
43
|
-
padding: 5px;
|
|
44
|
-
margin-top: 5px;
|
|
45
|
-
border-radius: 5px;
|
|
46
|
-
}
|
|
47
|
-
#previewContent {
|
|
48
|
-
flex-basis: 100%;
|
|
49
|
-
height: 100%;
|
|
50
|
-
min-height: 90%;
|
|
51
|
-
display: block;
|
|
52
|
-
width: 100%;
|
|
53
|
-
}
|
|
54
|
-
#previewContent iframe {
|
|
55
|
-
width: 100%;
|
|
56
|
-
height: 100%;
|
|
57
|
-
border: none;
|
|
58
|
-
border-radius: 0 5px 5px 0;
|
|
59
|
-
pointer-events: none;
|
|
60
|
-
}
|
|
61
|
-
@media screen and (max-width: 600px) {
|
|
62
|
-
main {
|
|
63
|
-
width: 100%;
|
|
64
|
-
display: block;
|
|
65
|
-
}
|
|
66
|
-
.editor {
|
|
67
|
-
padding: 15px 0;
|
|
68
|
-
}
|
|
69
|
-
.preview {
|
|
70
|
-
display: none;
|
|
71
|
-
}
|
|
72
|
-
#editorTextArea {
|
|
73
|
-
flex-basis: 100%;
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
</style>
|
|
8
|
+
<link rel="stylesheet" href="${/css/editor.css<<editor.css}">
|
|
9
|
+
<script src="/js/editor.js" defer></script>
|
|
77
10
|
|
|
78
|
-
<link href="https://cdn.jsdelivr.net/npm/prismjs@v1.x/themes/prism.css" rel="stylesheet" />
|
|
79
|
-
|
|
80
|
-
<script src="https://cdn.jsdelivr.net/npm/prismjs@v1.x/components/prism-core.min.js"></script>
|
|
81
|
-
<script src="https://cdn.jsdelivr.net/npm/prismjs@v1.x/plugins/autoloader/prism-autoloader.min.js"></script>
|
|
82
11
|
</head>
|
|
83
12
|
<body>
|
|
84
|
-
<
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
const url = new URL(location)
|
|
101
|
-
const target = url.searchParams.get('md')
|
|
102
|
-
if (target) {
|
|
103
|
-
fetchData(target).then(json => {
|
|
104
|
-
textarea.value = json.content
|
|
105
|
-
select.value = json.filename
|
|
106
|
-
inputFileName.value = json.filename
|
|
107
|
-
inputFileName.setAttribute('disabled', true)
|
|
108
|
-
submit('/preview', form)
|
|
109
|
-
})
|
|
110
|
-
}
|
|
111
|
-
select.addEventListener('change', async (event) => {
|
|
112
|
-
if (select.value) {
|
|
113
|
-
const json = await fetchData(select.value)
|
|
114
|
-
textarea.value = json.content
|
|
115
|
-
inputFileName.value = json.filename
|
|
116
|
-
inputFileName.setAttribute('disabled', true)
|
|
117
|
-
url.searchParams.set('md', select.value)
|
|
118
|
-
submit('/preview', form)
|
|
119
|
-
} else {
|
|
120
|
-
inputFileName.value = ""
|
|
121
|
-
inputFileName.removeAttribute('disabled')
|
|
122
|
-
textarea.value = ''
|
|
123
|
-
url.searchParams.set('md', "")
|
|
124
|
-
const iframe = preview.querySelector('iframe')
|
|
125
|
-
if (iframe) {
|
|
126
|
-
iframe.srcdoc = ''
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
history.pushState({}, "", url)
|
|
130
|
-
})
|
|
131
|
-
|
|
132
|
-
const submit = (fetchUrl, form) => {
|
|
133
|
-
const formData = new FormData(form)
|
|
134
|
-
const obj = {}
|
|
135
|
-
formData.forEach((v, k) => {
|
|
136
|
-
obj[k] = v
|
|
137
|
-
})
|
|
138
|
-
fetch(fetchUrl, {
|
|
139
|
-
method: 'post',
|
|
140
|
-
body: JSON.stringify(obj)
|
|
141
|
-
}).then(async response => {
|
|
142
|
-
const json = await response.json()
|
|
143
|
-
if (!response.ok) {
|
|
144
|
-
alert(json.message)
|
|
145
|
-
return
|
|
146
|
-
}
|
|
147
|
-
if (json.href) {
|
|
148
|
-
await sleep(300)
|
|
149
|
-
location.href = json.href
|
|
150
|
-
}
|
|
151
|
-
if (json.preview) {
|
|
152
|
-
const iframe = document.createElement('iframe')
|
|
153
|
-
iframe.setAttribute('srcdoc', json.preview)
|
|
154
|
-
iframe.setAttribute('sandbox', '')
|
|
155
|
-
const old = preview.querySelector('iframe')
|
|
156
|
-
if (!old) {
|
|
157
|
-
preview.appendChild(iframe)
|
|
158
|
-
}
|
|
159
|
-
old.setAttribute('srcdoc', json.preview)
|
|
160
|
-
}
|
|
161
|
-
}).catch(e => {
|
|
162
|
-
console.log(e.message)
|
|
163
|
-
})
|
|
164
|
-
}
|
|
165
|
-
form.addEventListener('submit', (event) => {
|
|
166
|
-
event.preventDefault()
|
|
167
|
-
const fetchUrl = event.submitter.dataset.url
|
|
168
|
-
submit(fetchUrl, event.target)
|
|
169
|
-
})
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
document.addEventListener('DOMContentLoaded', (event) => {
|
|
173
|
-
onloadFunction(event)
|
|
174
|
-
})
|
|
175
|
-
</script>
|
|
176
|
-
<main>
|
|
13
|
+
<header>
|
|
14
|
+
<p class="container">
|
|
15
|
+
<a href="/">{{SITE_NAME}}/Editor</a>
|
|
16
|
+
</p>
|
|
17
|
+
</header>
|
|
18
|
+
<div class="sidebar close">
|
|
19
|
+
<ul>
|
|
20
|
+
<script type="ssg">
|
|
21
|
+
return helper.readIndex().map(p => {
|
|
22
|
+
return `<li><a href="/editor?md=${p.name}.${p.__filetype}">${p.url}.${p.__filetype}</a></li>`
|
|
23
|
+
}).join("\n")
|
|
24
|
+
</script>
|
|
25
|
+
</ul>
|
|
26
|
+
<div class="sidebar-toggle"> </div>
|
|
27
|
+
</div>
|
|
28
|
+
<main class="container">
|
|
177
29
|
<form action="/editor" class="editor" method="post" id="editor">
|
|
178
30
|
<input id="inputFileName" name="inputFileName" type="text" value="" placeholder="sample.md">
|
|
179
31
|
<select id="selectDataFile" name="selectDataFile">
|
|
@@ -197,7 +49,7 @@
|
|
|
197
49
|
<input type="hidden" name="token" value="{{TOKEN}}">
|
|
198
50
|
<div>
|
|
199
51
|
<input type="submit" value="preview" data-url="/preview">
|
|
200
|
-
<input type="submit" value="
|
|
52
|
+
<input type="submit" value="save" data-url="/editor">
|
|
201
53
|
<a href="/">戻る</a>
|
|
202
54
|
</div>
|
|
203
55
|
</form>
|