@tenjuu99/blog 0.4.1 → 0.4.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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tenjuu99/blog",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.3",
|
|
4
4
|
"description": "blog template",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
"server": "node bin/server",
|
|
9
9
|
"generate": "node bin/generate",
|
|
10
10
|
"test": "node --test --test-concurrency=1 tests/**/*.test.js",
|
|
11
|
-
"test:watch": "node --test --watch tests/**/*.test.js"
|
|
11
|
+
"test:watch": "node --test --watch tests/**/*.test.js",
|
|
12
|
+
"test:acceptance": "npx playwright test --project=chromium"
|
|
12
13
|
},
|
|
13
14
|
"bin": {
|
|
14
15
|
"generate": "bin/generate",
|
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
:root {
|
|
2
|
+
--sidebar-width: 300px;
|
|
3
|
+
--sidebar-close-width: 20px;
|
|
4
|
+
--sidebar-transition-speed: 0.2s;
|
|
5
|
+
--header-height: 30px;
|
|
6
|
+
--footer-height: 20px;
|
|
7
|
+
--light-green: #79afb4;
|
|
8
|
+
--deep-blue: #313d64;
|
|
9
|
+
--hover-bg: rgba(104, 117, 154, .3);
|
|
10
|
+
}
|
|
1
11
|
body {
|
|
2
12
|
background: #fafafa;
|
|
3
13
|
height: 100vh;
|
|
@@ -13,10 +23,6 @@ header {
|
|
|
13
23
|
}
|
|
14
24
|
main {
|
|
15
25
|
height: 100%;
|
|
16
|
-
/*margin: 0 auto;*/
|
|
17
|
-
display: flex;
|
|
18
|
-
flex-direction: row;
|
|
19
|
-
justify-content: flex-start;
|
|
20
26
|
}
|
|
21
27
|
main.container {
|
|
22
28
|
padding: 0;
|
|
@@ -25,7 +31,6 @@ main.container {
|
|
|
25
31
|
display: flex;
|
|
26
32
|
margin: 0;
|
|
27
33
|
border: 1px solid #666;
|
|
28
|
-
border-radius: 5px;
|
|
29
34
|
height: 100%;
|
|
30
35
|
}
|
|
31
36
|
.textareaAndPreview>* {
|
|
@@ -34,37 +39,52 @@ main.container {
|
|
|
34
39
|
.editor {
|
|
35
40
|
display: flex;
|
|
36
41
|
flex-direction: column;
|
|
37
|
-
padding: 5px;
|
|
38
42
|
justify-content:center;
|
|
39
|
-
height: 100
|
|
43
|
+
height: calc(100% - var(--footer-height));
|
|
40
44
|
/* width: 100%; */
|
|
41
45
|
margin: 0;
|
|
42
46
|
position: relative;
|
|
43
|
-
width: 100
|
|
47
|
+
width: calc(100% - var(--sidebar-close-width));
|
|
44
48
|
left: 0;
|
|
45
49
|
flex-basis: 100%;
|
|
50
|
+
transition: width var(--sidebar-transition-speed), left var(--sidebar-transition-speed);
|
|
46
51
|
}
|
|
47
52
|
.sidebar-close .editor {
|
|
48
|
-
|
|
49
|
-
|
|
53
|
+
margin-left: var(--sidebar-close-width);
|
|
54
|
+
}
|
|
55
|
+
main:not(.sidebar-close) .editor {
|
|
56
|
+
width: calc(100% - var(--sidebar-width));
|
|
57
|
+
left: var(--sidebar-width);
|
|
58
|
+
transition: width var(--sidebar-transition-speed), left var(--sidebar-transition-speed);
|
|
50
59
|
}
|
|
51
60
|
.editor-options {
|
|
52
61
|
display: flex;
|
|
53
62
|
justify-content: space-between;
|
|
54
63
|
align-items: center;
|
|
55
64
|
gap: 8px;
|
|
56
|
-
|
|
65
|
+
height: var(--header-height);
|
|
57
66
|
}
|
|
58
67
|
.editor-options-left {
|
|
59
68
|
display: flex;
|
|
60
69
|
align-items: center;
|
|
61
|
-
gap: 8px;
|
|
62
70
|
}
|
|
63
71
|
.editor-options-right {
|
|
64
72
|
display: flex;
|
|
65
73
|
align-items: center;
|
|
66
|
-
gap: 8px;
|
|
67
74
|
}
|
|
75
|
+
#publishBtn {
|
|
76
|
+
height: var(--header-height);
|
|
77
|
+
width: 100px;
|
|
78
|
+
background: var(--deep-blue);
|
|
79
|
+
color: #ddd;
|
|
80
|
+
border-radius: 0;
|
|
81
|
+
border: none;
|
|
82
|
+
cursor: pointer;
|
|
83
|
+
}
|
|
84
|
+
#publishBtn:hover {
|
|
85
|
+
background: var(--light-green);
|
|
86
|
+
}
|
|
87
|
+
|
|
68
88
|
#newFileDialog {
|
|
69
89
|
border: 1px solid #ccc;
|
|
70
90
|
border-radius: 8px;
|
|
@@ -93,14 +113,17 @@ main.container {
|
|
|
93
113
|
background: #cccccc;
|
|
94
114
|
color: #333;
|
|
95
115
|
padding: 5px;
|
|
96
|
-
border-radius: 5px 0 0 5px;
|
|
97
116
|
border: 0;
|
|
98
117
|
transition: background 0.2s;
|
|
118
|
+
box-shadow: inset -0.1em 0em 0.3em 0 rgba(0,0,0, 0.5);
|
|
119
|
+
font-weight: 500;
|
|
120
|
+
color: rgba(100,100,100,0.7);
|
|
99
121
|
}
|
|
100
122
|
#editorTextArea:focus {
|
|
101
123
|
background: #eee;
|
|
102
124
|
transition: background 0.2s;
|
|
103
125
|
outline: none;
|
|
126
|
+
color: rgba(100,100,100,1);
|
|
104
127
|
}
|
|
105
128
|
form select {
|
|
106
129
|
padding: 5px;
|
|
@@ -117,44 +140,127 @@ form select {
|
|
|
117
140
|
width: 100%;
|
|
118
141
|
height: 100%;
|
|
119
142
|
border: none;
|
|
120
|
-
border-radius: 0 5px 5px 0;
|
|
121
143
|
}
|
|
122
144
|
|
|
123
145
|
.sidebar {
|
|
124
|
-
--sidebar-bg:
|
|
125
|
-
--sidebar-dir:
|
|
146
|
+
--sidebar-bg: var(--deep-blue);
|
|
147
|
+
--sidebar-dir: var(--light-green);
|
|
126
148
|
--sidebar-file: #cbcbcb;
|
|
127
149
|
--sidebar-active-file-fg: #333;
|
|
128
150
|
--sidebar-active-file-bg: rgba(165,218,246,0.8);
|
|
129
151
|
--sidebar-modified-fg: #ff7777;
|
|
130
|
-
--sidebar-hover-bg:
|
|
131
|
-
display: flex;
|
|
132
|
-
justify-content: space-between;
|
|
152
|
+
--sidebar-hover-bg: var(--hover-bg);
|
|
133
153
|
width: 300px;
|
|
134
154
|
overflow: scroll;
|
|
135
155
|
left: 0;
|
|
136
156
|
background: var(--sidebar-bg);
|
|
137
157
|
height: 100%;
|
|
138
158
|
padding: 0;
|
|
139
|
-
|
|
140
|
-
position:
|
|
159
|
+
width: var(--sidebar-width);
|
|
160
|
+
position: fixed;
|
|
141
161
|
z-index: 3;
|
|
142
162
|
font-size: 0.85em;
|
|
163
|
+
transition: left var(--sidebar-transition-speed);
|
|
164
|
+
display: flex;
|
|
165
|
+
justify-content: flex-start;
|
|
166
|
+
flex-direction: column;
|
|
167
|
+
}
|
|
168
|
+
.sidebar-tabs {
|
|
169
|
+
display: flex;
|
|
170
|
+
height: var(--header-height);
|
|
171
|
+
background: #2a3557;
|
|
172
|
+
flex-shrink: 0;
|
|
173
|
+
}
|
|
174
|
+
.sidebar-tab {
|
|
175
|
+
flex: 1;
|
|
176
|
+
border: none;
|
|
177
|
+
background: transparent;
|
|
178
|
+
color: rgba(203, 203, 203, 0.5);
|
|
179
|
+
cursor: pointer;
|
|
180
|
+
font-size: 0.8em;
|
|
181
|
+
border-bottom: 2px solid transparent;
|
|
182
|
+
transition: color 0.15s, border-color 0.15s;
|
|
183
|
+
padding: 0 8px;
|
|
184
|
+
}
|
|
185
|
+
.sidebar-tab.active {
|
|
186
|
+
color: #cbcbcb;
|
|
187
|
+
border-bottom-color: var(--light-green);
|
|
188
|
+
}
|
|
189
|
+
.sidebar-tab:hover:not(.active) {
|
|
190
|
+
color: rgba(203, 203, 203, 0.8);
|
|
191
|
+
background: rgba(255, 255, 255, 0.05);
|
|
192
|
+
}
|
|
193
|
+
.sidebar-tab-content {
|
|
194
|
+
display: none;
|
|
195
|
+
flex-direction: column;
|
|
196
|
+
flex: 1;
|
|
197
|
+
overflow: auto;
|
|
198
|
+
min-height: 0;
|
|
199
|
+
}
|
|
200
|
+
.sidebar-tab-content.active {
|
|
201
|
+
display: flex;
|
|
202
|
+
}
|
|
203
|
+
.sidebar-new-file {
|
|
204
|
+
display: flex;
|
|
205
|
+
flex-direction: column;
|
|
206
|
+
gap: 12px;
|
|
207
|
+
padding: 16px 12px;
|
|
208
|
+
color: var(--sidebar-file);
|
|
209
|
+
font-size: 0.9em;
|
|
210
|
+
}
|
|
211
|
+
.sidebar-new-file label {
|
|
212
|
+
display: flex;
|
|
213
|
+
flex-direction: column;
|
|
214
|
+
gap: 4px;
|
|
215
|
+
}
|
|
216
|
+
.sidebar-new-file input[type="text"],
|
|
217
|
+
.sidebar-new-file select {
|
|
218
|
+
background: rgba(255, 255, 255, 0.1);
|
|
219
|
+
border: 1px solid rgba(255, 255, 255, 0.2);
|
|
220
|
+
border-radius: 4px;
|
|
221
|
+
color: var(--sidebar-file);
|
|
222
|
+
padding: 5px 8px;
|
|
223
|
+
font-size: 0.95em;
|
|
224
|
+
}
|
|
225
|
+
.sidebar-new-file input[type="text"]:focus,
|
|
226
|
+
.sidebar-new-file select:focus {
|
|
227
|
+
outline: none;
|
|
228
|
+
border-color: var(--sidebar-dir);
|
|
229
|
+
}
|
|
230
|
+
.sidebar-new-file select option {
|
|
231
|
+
background: var(--sidebar-bg);
|
|
232
|
+
}
|
|
233
|
+
.sidebar-new-file #newFileError {
|
|
234
|
+
color: var(--sidebar-modified-fg);
|
|
235
|
+
font-size: 0.9em;
|
|
236
|
+
min-height: 1.2em;
|
|
237
|
+
}
|
|
238
|
+
.sidebar-new-file button {
|
|
239
|
+
background: var(--sidebar-dir);
|
|
240
|
+
border: none;
|
|
241
|
+
border-radius: 4px;
|
|
242
|
+
color: #1a2540;
|
|
243
|
+
cursor: pointer;
|
|
244
|
+
font-weight: bold;
|
|
245
|
+
padding: 6px 12px;
|
|
246
|
+
align-self: flex-start;
|
|
247
|
+
}
|
|
248
|
+
.sidebar-new-file button:hover {
|
|
249
|
+
filter: brightness(1.1);
|
|
143
250
|
}
|
|
144
251
|
.sidebar-close .sidebar {
|
|
145
|
-
left: -
|
|
146
|
-
flex-basis: 0;
|
|
252
|
+
left: calc(0px - var(--sidebar-width));
|
|
147
253
|
}
|
|
148
254
|
.sidebar ul {
|
|
149
255
|
padding: 0;
|
|
150
256
|
margin: 0;
|
|
151
257
|
}
|
|
152
|
-
.sidebar > ul {
|
|
258
|
+
.sidebar-files > ul {
|
|
153
259
|
padding: 0 0 0 15px;
|
|
154
260
|
}
|
|
155
261
|
.sidebar-close .sidebar ul {
|
|
156
262
|
position: absolute;
|
|
157
|
-
left: -
|
|
263
|
+
left: calc(10px - var(--sidebar-width));
|
|
158
264
|
transition: left 0.3s;
|
|
159
265
|
}
|
|
160
266
|
.sidebar li {
|
|
@@ -210,10 +316,10 @@ form select {
|
|
|
210
316
|
}
|
|
211
317
|
.sidebar-toggle {
|
|
212
318
|
cursor: pointer;
|
|
213
|
-
width:
|
|
319
|
+
width: var(--sidebar-close-width);
|
|
214
320
|
height: 100%;
|
|
215
321
|
position: absolute;
|
|
216
|
-
|
|
322
|
+
right: 0;
|
|
217
323
|
}
|
|
218
324
|
.sidebar-close .sidebar-toggle {
|
|
219
325
|
position: fixed;
|
|
@@ -225,24 +331,35 @@ form select {
|
|
|
225
331
|
right: 0;
|
|
226
332
|
content: '';
|
|
227
333
|
height: 100%;
|
|
228
|
-
width:
|
|
334
|
+
width: var(--sidebar-close-width);
|
|
229
335
|
background: rgba(255, 255,255, 0.3);
|
|
230
336
|
}
|
|
337
|
+
.footer {
|
|
338
|
+
height: var(--footer-height);
|
|
339
|
+
background: #333;
|
|
340
|
+
padding-left: var(--sidebar-width);
|
|
341
|
+
color: white;
|
|
342
|
+
transition: padding-left var(--sidebar-transition-speed);
|
|
343
|
+
}
|
|
344
|
+
.sidebar-close .footer {
|
|
345
|
+
padding-left: var(--sidebar-close-width);
|
|
346
|
+
transition: padding-left var(--sidebar-transition-speed);
|
|
347
|
+
}
|
|
231
348
|
@media screen and (max-width: 600px) {
|
|
349
|
+
:root {
|
|
350
|
+
--sidebar-sp-transition: transform var(--sidebar-transition-speed), background-color var(--sidebar-transition-speed);
|
|
351
|
+
--header-height: 50px;
|
|
352
|
+
}
|
|
232
353
|
main {
|
|
233
354
|
width: 100%;
|
|
234
355
|
display: block;
|
|
235
356
|
}
|
|
236
357
|
main.container {
|
|
237
|
-
padding: 0
|
|
358
|
+
padding: 0;
|
|
238
359
|
}
|
|
239
|
-
.sidebar {
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
z-index: 3;
|
|
243
|
-
right: 0;
|
|
244
|
-
left: unset;
|
|
245
|
-
transition: right .3s, width .3s;
|
|
360
|
+
main:not(.sidebar-close) .editor {
|
|
361
|
+
width: 100%;
|
|
362
|
+
left: 0;
|
|
246
363
|
}
|
|
247
364
|
.sidebar-close .sidebar {
|
|
248
365
|
width: 0;
|
|
@@ -251,24 +368,43 @@ form select {
|
|
|
251
368
|
left: unset;
|
|
252
369
|
transition: right .3s, width .3s;
|
|
253
370
|
}
|
|
371
|
+
.sidebar-close .editor {
|
|
372
|
+
margin-left: 0;
|
|
373
|
+
margin: 0;
|
|
374
|
+
}
|
|
254
375
|
.editor {
|
|
255
|
-
|
|
376
|
+
width: 100%;
|
|
377
|
+
padding: 0;
|
|
378
|
+
}
|
|
379
|
+
.editor-options {
|
|
380
|
+
margin-right: 50px;
|
|
381
|
+
}
|
|
382
|
+
.editor-options * {
|
|
383
|
+
display: flex;
|
|
384
|
+
flex-direction: column;
|
|
256
385
|
}
|
|
257
386
|
.preview {
|
|
258
387
|
display: none;
|
|
259
388
|
}
|
|
260
389
|
#editorTextArea {
|
|
261
390
|
flex-basis: 100%;
|
|
391
|
+
box-shadow: none;
|
|
262
392
|
}
|
|
263
393
|
.sidebar-toggle {
|
|
264
394
|
display: none;
|
|
265
395
|
}
|
|
266
|
-
.sidebar {
|
|
267
|
-
flex-basis: 80%;
|
|
268
|
-
}
|
|
269
396
|
.sidebar.close {
|
|
270
397
|
flex-basis: 5%;
|
|
271
398
|
}
|
|
399
|
+
.sidebar {
|
|
400
|
+
position: fixed;
|
|
401
|
+
flex-basis: 80%;
|
|
402
|
+
width: 80vw;
|
|
403
|
+
z-index: 3;
|
|
404
|
+
right: 0;
|
|
405
|
+
left: unset;
|
|
406
|
+
transition: right .3s, width .3s;
|
|
407
|
+
}
|
|
272
408
|
}
|
|
273
409
|
/**
|
|
274
410
|
* hamburger
|
|
@@ -277,17 +413,6 @@ form select {
|
|
|
277
413
|
display: none;
|
|
278
414
|
}
|
|
279
415
|
@media screen and (max-width: 600px) {
|
|
280
|
-
.sidebar-close .editor {
|
|
281
|
-
margin-left: 0;
|
|
282
|
-
margin: 0;
|
|
283
|
-
}
|
|
284
|
-
.editor-options {
|
|
285
|
-
margin-right: 50px;
|
|
286
|
-
}
|
|
287
|
-
.editor-options * {
|
|
288
|
-
display: flex;
|
|
289
|
-
flex-direction: column;
|
|
290
|
-
}
|
|
291
416
|
.hamburger-menu {
|
|
292
417
|
display: block;
|
|
293
418
|
}
|
|
@@ -301,7 +426,6 @@ form select {
|
|
|
301
426
|
justify-content: center;
|
|
302
427
|
align-items: center;
|
|
303
428
|
z-index: 90;
|
|
304
|
-
/* background-color: #3584bb; */
|
|
305
429
|
}
|
|
306
430
|
.menu-btn span,
|
|
307
431
|
.menu-btn span:before,
|
|
@@ -313,7 +437,7 @@ form select {
|
|
|
313
437
|
border-radius: 3px;
|
|
314
438
|
background-color: #666666;
|
|
315
439
|
position: absolute;
|
|
316
|
-
transition:
|
|
440
|
+
transition: var(--sidebar-sp-transition);
|
|
317
441
|
}
|
|
318
442
|
.menu-btn span:before {
|
|
319
443
|
bottom: 8px;
|
|
@@ -321,18 +445,18 @@ form select {
|
|
|
321
445
|
.menu-btn span:after {
|
|
322
446
|
top: 8px;
|
|
323
447
|
}
|
|
324
|
-
#menu-btn-check:checked ~ .menu-btn span {
|
|
448
|
+
#menu-btn-check:not(:checked) ~ .menu-btn span {
|
|
325
449
|
background-color: rgba(255, 255, 255, 0);/*メニューオープン時は真ん中の線を透明にする*/
|
|
326
450
|
}
|
|
327
|
-
#menu-btn-check:checked ~ .menu-btn span::before {
|
|
451
|
+
#menu-btn-check:not(:checked) ~ .menu-btn span::before {
|
|
328
452
|
bottom: 0;
|
|
329
453
|
transform: rotate(45deg);
|
|
330
|
-
transition:
|
|
454
|
+
transition: var(--sidebar-sp-transition);
|
|
331
455
|
}
|
|
332
|
-
#menu-btn-check:checked ~ .menu-btn span::after {
|
|
456
|
+
#menu-btn-check:not(:checked) ~ .menu-btn span::after {
|
|
333
457
|
top: 0;
|
|
334
458
|
transform: rotate(-45deg);
|
|
335
|
-
transition:
|
|
459
|
+
transition: var(--sidebar-sp-transition);
|
|
336
460
|
}
|
|
337
461
|
#menu-btn-check {
|
|
338
462
|
display: none;
|
|
@@ -173,31 +173,13 @@ const onloadFunction = async (e) => {
|
|
|
173
173
|
initDropReceiver(textarea, () => inputFileName.value, () => submit('/preview', form), () => debouncedUpdate.cancel())
|
|
174
174
|
|
|
175
175
|
// @vocab: 新規作成UI
|
|
176
|
-
const newFileBtn = document.querySelector('#newFileBtn')
|
|
177
|
-
const newFileDialog = document.querySelector('#newFileDialog')
|
|
178
176
|
const newFileNameInput = document.querySelector('#newFileName')
|
|
179
177
|
const newFileTemplateSelect = document.querySelector('#newFileTemplate')
|
|
180
178
|
const newFileError = document.querySelector('#newFileError')
|
|
181
179
|
const confirmNewFile = document.querySelector('#confirmNewFile')
|
|
182
|
-
const cancelNewFile = document.querySelector('#cancelNewFile')
|
|
183
180
|
|
|
184
181
|
const refreshSidebar = () => initSidebarContent(inputFileName.value)
|
|
185
182
|
|
|
186
|
-
newFileBtn.addEventListener('click', () => {
|
|
187
|
-
newFileNameInput.value = ''
|
|
188
|
-
newFileError.textContent = ''
|
|
189
|
-
// テンプレート選択肢を再構築して auto-select をリセット
|
|
190
|
-
newFileTemplateSelect.innerHTML = '<option value="">テンプレートなし</option>'
|
|
191
|
-
for (const tmpl of _frontmatterTemplates) {
|
|
192
|
-
const option = document.createElement('option')
|
|
193
|
-
option.value = tmpl.path_prefix
|
|
194
|
-
option.textContent = `テンプレート: ${tmpl.path_prefix}`
|
|
195
|
-
newFileTemplateSelect.appendChild(option)
|
|
196
|
-
}
|
|
197
|
-
newFileTemplateSelect.value = ''
|
|
198
|
-
newFileDialog.showModal()
|
|
199
|
-
})
|
|
200
|
-
|
|
201
183
|
// ファイル名入力に応じてテンプレートを auto-select
|
|
202
184
|
newFileNameInput.addEventListener('input', () => {
|
|
203
185
|
const filename = newFileNameInput.value
|
|
@@ -213,7 +195,7 @@ const onloadFunction = async (e) => {
|
|
|
213
195
|
}
|
|
214
196
|
})
|
|
215
197
|
|
|
216
|
-
// F-02:
|
|
198
|
+
// F-02: 重複チェック付き作成。エラー時はタブを閉じない
|
|
217
199
|
confirmNewFile.addEventListener('click', async () => {
|
|
218
200
|
const filename = newFileNameInput.value.trim()
|
|
219
201
|
if (!filename) return
|
|
@@ -239,7 +221,7 @@ const onloadFunction = async (e) => {
|
|
|
239
221
|
return
|
|
240
222
|
}
|
|
241
223
|
|
|
242
|
-
|
|
224
|
+
switchSidebarTab('files')
|
|
243
225
|
textarea.value = content
|
|
244
226
|
setCurrentFile(filename)
|
|
245
227
|
url.searchParams.set('md', filename)
|
|
@@ -249,8 +231,6 @@ const onloadFunction = async (e) => {
|
|
|
249
231
|
refreshSidebar()
|
|
250
232
|
})
|
|
251
233
|
|
|
252
|
-
cancelNewFile.addEventListener('click', () => newFileDialog.close())
|
|
253
|
-
|
|
254
234
|
// インプレースファイル読み込み
|
|
255
235
|
// サイドバーリンクのクリックやブラウザ履歴移動で呼ばれる。
|
|
256
236
|
// turbolink によるページ全体の差し替えではなく、エディタ状態をその場で更新することでちらつきを防ぐ。
|
|
@@ -367,16 +347,52 @@ const initSidebarContent = async (activeFile) => {
|
|
|
367
347
|
const res = await fetch('/get_sidebar')
|
|
368
348
|
if (!res.ok) return
|
|
369
349
|
const { html } = await res.json()
|
|
370
|
-
const sidebar = document.querySelector('.sidebar')
|
|
371
|
-
const toggle = sidebar.querySelector('.sidebar-toggle')
|
|
350
|
+
const sidebar = document.querySelector('.sidebar-files')
|
|
372
351
|
sidebar.innerHTML = html
|
|
373
|
-
sidebar.appendChild(toggle)
|
|
374
352
|
initSidebarTree(activeFile)
|
|
375
353
|
} catch (e) {
|
|
376
354
|
console.log('[sidebar] init failed', e)
|
|
377
355
|
}
|
|
378
356
|
}
|
|
379
357
|
|
|
358
|
+
// @vocab: サイドバータブ
|
|
359
|
+
let switchSidebarTab = () => {}
|
|
360
|
+
const initSidebarTabs = () => {
|
|
361
|
+
const tabs = document.querySelectorAll('.sidebar-tab')
|
|
362
|
+
const contents = document.querySelectorAll('.sidebar-tab-content')
|
|
363
|
+
switchSidebarTab = (tabName) => {
|
|
364
|
+
tabs.forEach(t => {
|
|
365
|
+
const selected = t.dataset.tab === tabName
|
|
366
|
+
t.classList.toggle('active', selected)
|
|
367
|
+
t.setAttribute('aria-selected', String(selected))
|
|
368
|
+
t.tabIndex = selected ? 0 : -1
|
|
369
|
+
})
|
|
370
|
+
contents.forEach(c => {
|
|
371
|
+
const selected = c.dataset.tab === tabName
|
|
372
|
+
c.classList.toggle('active', selected)
|
|
373
|
+
c.toggleAttribute('hidden', !selected)
|
|
374
|
+
})
|
|
375
|
+
}
|
|
376
|
+
tabs.forEach(tab => {
|
|
377
|
+
tab.addEventListener('click', () => {
|
|
378
|
+
switchSidebarTab(tab.dataset.tab)
|
|
379
|
+
if (tab.dataset.tab === 'new-file') {
|
|
380
|
+
const select = document.querySelector('#newFileTemplate')
|
|
381
|
+
select.innerHTML = '<option value="">テンプレートなし</option>'
|
|
382
|
+
for (const tmpl of _frontmatterTemplates) {
|
|
383
|
+
const option = document.createElement('option')
|
|
384
|
+
option.value = tmpl.path_prefix
|
|
385
|
+
option.textContent = `テンプレート: ${tmpl.path_prefix}`
|
|
386
|
+
select.appendChild(option)
|
|
387
|
+
}
|
|
388
|
+
document.querySelector('#newFileName').value = ''
|
|
389
|
+
document.querySelector('#newFileError').textContent = ''
|
|
390
|
+
document.querySelector('#newFileName').focus()
|
|
391
|
+
}
|
|
392
|
+
})
|
|
393
|
+
})
|
|
394
|
+
}
|
|
395
|
+
|
|
380
396
|
// @vocab: サイドバー
|
|
381
397
|
// @test: tests/editor/editor-sidebar.test.js
|
|
382
398
|
const sidebarToggle = (e) => {
|
|
@@ -441,6 +457,7 @@ const initDropReceiver = (textarea, getMdFile, onUpdate, cancelPendingDebounce)
|
|
|
441
457
|
document.addEventListener('DOMContentLoaded', async (event) => {
|
|
442
458
|
const url = new URL(location)
|
|
443
459
|
const activeFile = url.searchParams.get('md') || ''
|
|
460
|
+
initSidebarTabs()
|
|
444
461
|
await initSidebarContent(activeFile)
|
|
445
462
|
await initFrontmatterTemplate()
|
|
446
463
|
onloadFunction(event)
|
|
@@ -4,20 +4,47 @@
|
|
|
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
|
-
<
|
|
7
|
+
<style>
|
|
8
|
+
*,*::before,*::after{box-sizing:border-box}body,h1,h2,h3,h4,p,figure,blockquote,dl,dd{margin:0}ul[role="list"],ol[role="list"]{list-style:none}html:focus-within{scroll-behavior:smooth}body{min-height:100vh;text-rendering:optimizeSpeed;line-height:1.5}a:not([class]){text-decoration-skip-ink:auto}img,picture{max-width:100%;display:block}input,button,textarea,select{font:inherit}@media(prefers-reduced-motion:reduce){html:focus-within{scroll-behavior:auto}*,*::before,*::after{animation-duration:.01ms !important;animation-iteration-count:1 !important;transition-duration:.01ms !important;scroll-behavior:auto !important}}
|
|
9
|
+
</style>
|
|
10
|
+
<link rel="stylesheet" href="${/css/editor.css<<editor.css}">
|
|
8
11
|
<script type="module" src="/js/editor.js"></script>
|
|
9
12
|
|
|
10
13
|
</head>
|
|
11
14
|
<body>
|
|
12
15
|
<main class="container">
|
|
16
|
+
<!-- layout shift 対策 -->
|
|
17
|
+
<script>if(localStorage.getItem('sidebar-is-open')!=='true'){document.currentScript.closest('main').classList.add('sidebar-close')}</script>
|
|
13
18
|
<div class="sidebar">
|
|
19
|
+
<div class="sidebar-tabs" role="tablist">
|
|
20
|
+
<button class="sidebar-tab active" data-tab="files" role="tab" id="sidebar-tab-files" aria-selected="true" aria-controls="sidebar-tabpanel-files" tabindex="0">Files</button>
|
|
21
|
+
<button class="sidebar-tab" data-tab="new-file" role="tab" id="sidebar-tab-new-file" aria-selected="false" aria-controls="sidebar-tabpanel-new-file" tabindex="-1">新規作成</button>
|
|
22
|
+
</div>
|
|
23
|
+
<div class="sidebar-tab-content active" data-tab="files" role="tabpanel" id="sidebar-tabpanel-files" aria-labelledby="sidebar-tab-files">
|
|
24
|
+
<div class="sidebar-files"></div>
|
|
25
|
+
</div>
|
|
26
|
+
<div class="sidebar-tab-content" data-tab="new-file" role="tabpanel" id="sidebar-tabpanel-new-file" aria-labelledby="sidebar-tab-new-file" hidden>
|
|
27
|
+
<div class="sidebar-new-file">
|
|
28
|
+
<label>
|
|
29
|
+
ファイル名
|
|
30
|
+
<input type="text" id="newFileName" placeholder="path/to/file.md" autocomplete="off">
|
|
31
|
+
</label>
|
|
32
|
+
<label>
|
|
33
|
+
テンプレート
|
|
34
|
+
<select id="newFileTemplate">
|
|
35
|
+
<option value="">テンプレートなし</option>
|
|
36
|
+
</select>
|
|
37
|
+
</label>
|
|
38
|
+
<p id="newFileError" role="alert" aria-live="assertive"></p>
|
|
39
|
+
<button type="button" id="confirmNewFile">作成</button>
|
|
40
|
+
</div>
|
|
41
|
+
</div>
|
|
14
42
|
<div class="sidebar-toggle"> </div>
|
|
15
43
|
</div>
|
|
16
44
|
<form action="/editor" class="editor" method="post" id="editor">
|
|
17
45
|
<div class="editor-options">
|
|
18
46
|
<div class="editor-options-left">
|
|
19
47
|
<span id="fileStatus"><span id="currentFileName"></span><span id="publicationStatus"></span></span>
|
|
20
|
-
<button type="button" id="newFileBtn">新規作成</button>
|
|
21
48
|
<input type="hidden" id="inputFileName" name="inputFileName" value="">
|
|
22
49
|
</div>
|
|
23
50
|
<div class="editor-options-right">
|
|
@@ -35,26 +62,7 @@
|
|
|
35
62
|
</div>
|
|
36
63
|
<input type="hidden" name="token" value="{{TOKEN}}">
|
|
37
64
|
</form>
|
|
38
|
-
<
|
|
39
|
-
<form method="dialog">
|
|
40
|
-
<h3>新規ファイルを作成</h3>
|
|
41
|
-
<label>
|
|
42
|
-
ファイル名(例: book/my-book.md)
|
|
43
|
-
<input type="text" id="newFileName" placeholder="path/to/file.md" autocomplete="off">
|
|
44
|
-
</label>
|
|
45
|
-
<label>
|
|
46
|
-
テンプレート
|
|
47
|
-
<select id="newFileTemplate">
|
|
48
|
-
<option value="">テンプレートなし</option>
|
|
49
|
-
</select>
|
|
50
|
-
</label>
|
|
51
|
-
<p id="newFileError" role="alert" aria-live="assertive"></p>
|
|
52
|
-
<div>
|
|
53
|
-
<button type="button" id="confirmNewFile">作成</button>
|
|
54
|
-
<button type="button" id="cancelNewFile">キャンセル</button>
|
|
55
|
-
</div>
|
|
56
|
-
</form>
|
|
57
|
-
</dialog>
|
|
65
|
+
<footer class="footer"></footer>
|
|
58
66
|
<div class="hamburger-menu">
|
|
59
67
|
<input type="checkbox" id="menu-btn-check">
|
|
60
68
|
<label for="menu-btn-check" class="menu-btn"><span></span></label>
|