@kobalab/liulian 0.7.5 → 0.8.1
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/ChangeLog.md +22 -0
- package/README.md +4 -0
- package/bin/liulian.js +4 -1
- package/css/icon.png +0 -0
- package/css/liulian.css +36 -7
- package/lib/backup/git.js +96 -0
- package/lib/html/file.js +78 -5
- package/lib/html/folder.js +4 -33
- package/lib/html/index.js +17 -0
- package/lib/html/text.js +30 -0
- package/lib/http/request.js +6 -0
- package/lib/http/response.js +6 -0
- package/lib/module/core.js +12 -1
- package/lib/resource/file.js +32 -7
- package/lib/resource/index.js +4 -0
- package/lib/resource/liulian.js +11 -2
- package/lib/resource/text.js +22 -6
- package/lib/text/liulian.js +8 -2
- package/lib/util/str-tool.js +40 -0
- package/locale/en +10 -0
- package/locale/ja +10 -0
- package/locale/zh-CN +10 -0
- package/package.json +2 -2
package/ChangeLog.md
CHANGED
|
@@ -1,3 +1,25 @@
|
|
|
1
|
+
### v0.8.1 / 2022-01-24
|
|
2
|
+
|
|
3
|
+
- 脆弱性警告に対処(mocha 9.1.3 → 9.2.0)
|
|
4
|
+
|
|
5
|
+
## v0.8.0 / 2021-12-30
|
|
6
|
+
|
|
7
|
+
- #4 自動バックアップの機能を追加
|
|
8
|
+
- redirect モジュールを追加
|
|
9
|
+
- アイコンを変更
|
|
10
|
+
- include モジュールの再帰的使用を検知できるようにした
|
|
11
|
+
- HEADとTAILを二重インクルードしないよう修正
|
|
12
|
+
|
|
13
|
+
### v0.7.7 / 2021-12-03
|
|
14
|
+
|
|
15
|
+
- | のみの行があると異常終了するバグを修正
|
|
16
|
+
- スマートフォンサイズ以外でも表があふれた場合は横スクロールするように修正
|
|
17
|
+
|
|
18
|
+
### v0.7.6 / 2021-11-11
|
|
19
|
+
|
|
20
|
+
- 表のセルにclassが指定できるようにした
|
|
21
|
+
- 脆弱性警告に対処(debug 4.1.1 → 4.3.2)
|
|
22
|
+
|
|
1
23
|
### v0.7.5 / 2021-10-30
|
|
2
24
|
|
|
3
25
|
- 脆弱性警告に対処(mocha 8.1.0 → 9.1.3)
|
package/README.md
CHANGED
|
@@ -3,6 +3,9 @@
|
|
|
3
3
|
<a href="https://kobalab.net/xiumai/"><img src="https://kobalab.net/xiumai/theme/xiumai.png" alt="XiuMai" height=24 valign=bottom></a>
|
|
4
4
|
の後継。Node.jsで動作するWebサイト作成ツール。
|
|
5
5
|
|
|
6
|
+
## デモ
|
|
7
|
+
https://kobalab.net/liulian/
|
|
8
|
+
|
|
6
9
|
## インストール
|
|
7
10
|
```sh
|
|
8
11
|
$ npm i -g @kobalab/liulian
|
|
@@ -33,6 +36,7 @@ $ liulian ~/Documents/LiuLian
|
|
|
33
36
|
- [README と index](https://kobalab.net/liulian/man/readme&index)
|
|
34
37
|
- [HEAD と TAIL](https://kobalab.net/liulian/man/head&tail)
|
|
35
38
|
- [リバースプロキシを使う](https://kobalab.net/liulian/man/proxy-setting)
|
|
39
|
+
- [Gitで自動バックアップする](https://kobalab.net/liulian/man/git)
|
|
36
40
|
|
|
37
41
|
## ライセンス
|
|
38
42
|
[MIT](https://github.com/kobalab/LiuLian/blob/master/LICENSE)
|
package/bin/liulian.js
CHANGED
|
@@ -17,6 +17,8 @@ const mount = argv.mount;
|
|
|
17
17
|
|
|
18
18
|
require('../lib/setup')(home);
|
|
19
19
|
|
|
20
|
+
const backup = require('../lib/backup/git')(path.join(home, 'docs'));
|
|
21
|
+
|
|
20
22
|
const locale = require('../lib/util/locale')(
|
|
21
23
|
path.join(__dirname, '../locale'),
|
|
22
24
|
'en');
|
|
@@ -44,7 +46,8 @@ const liulian = require('../lib/liulian')({
|
|
|
44
46
|
locale: locale,
|
|
45
47
|
mount: mount,
|
|
46
48
|
auth: auth,
|
|
47
|
-
passport: passport
|
|
49
|
+
passport: passport,
|
|
50
|
+
backup: backup });
|
|
48
51
|
|
|
49
52
|
const app = express();
|
|
50
53
|
|
package/css/icon.png
CHANGED
|
Binary file
|
package/css/liulian.css
CHANGED
|
@@ -74,11 +74,6 @@ h6 {
|
|
|
74
74
|
margin-left: 0;
|
|
75
75
|
padding-left: 1.5em;
|
|
76
76
|
}
|
|
77
|
-
|
|
78
|
-
table {
|
|
79
|
-
display: block;
|
|
80
|
-
overflow: auto;
|
|
81
|
-
}
|
|
82
77
|
}
|
|
83
78
|
|
|
84
79
|
hr {
|
|
@@ -100,7 +95,7 @@ pre {
|
|
|
100
95
|
background: #eee;
|
|
101
96
|
overflow: auto;
|
|
102
97
|
overflow-wrap: normal;
|
|
103
|
-
font-family: Osaka-Mono, "MS ゴシック", Courier, monospace;
|
|
98
|
+
font-family: Osaka-Mono, "HGゴシックM", "MS ゴシック", Courier, monospace;
|
|
104
99
|
font-size: 100%;
|
|
105
100
|
}
|
|
106
101
|
|
|
@@ -113,6 +108,8 @@ blockquote {
|
|
|
113
108
|
|
|
114
109
|
table {
|
|
115
110
|
border-collapse: collapse;
|
|
111
|
+
display: block;
|
|
112
|
+
overflow: auto;
|
|
116
113
|
}
|
|
117
114
|
th, td {
|
|
118
115
|
border: solid 1px #9c9;
|
|
@@ -154,7 +151,7 @@ input[disabled] {
|
|
|
154
151
|
}
|
|
155
152
|
textarea {
|
|
156
153
|
padding: 4px;
|
|
157
|
-
font-family: Osaka-Mono, "MS ゴシック", Courier, monospace;
|
|
154
|
+
font-family: Osaka-Mono, "HGゴシックM", "MS ゴシック", Courier, monospace;
|
|
158
155
|
font-size: 100%;
|
|
159
156
|
border: solid 1px #999;
|
|
160
157
|
border-radius: 4px;
|
|
@@ -299,6 +296,38 @@ input[type="submit"] {
|
|
|
299
296
|
width: 100%;
|
|
300
297
|
height: 70vh;
|
|
301
298
|
}
|
|
299
|
+
#l-log .l-time {
|
|
300
|
+
text-align: right;
|
|
301
|
+
}
|
|
302
|
+
#l-diff {
|
|
303
|
+
border-radius: 0.2em;
|
|
304
|
+
border: solid 1px #080;
|
|
305
|
+
padding: 0;
|
|
306
|
+
background: #ddd;
|
|
307
|
+
}
|
|
308
|
+
#l-diff > * {
|
|
309
|
+
display: block;
|
|
310
|
+
padding: 2px 0.5em;
|
|
311
|
+
margin: 1px 0;
|
|
312
|
+
min-height: 1em;
|
|
313
|
+
font-family: Osaka-Mono, "MS ゴシック", Courier, monospace;
|
|
314
|
+
text-decoration: none;
|
|
315
|
+
white-space: pre-wrap;
|
|
316
|
+
color: #666;
|
|
317
|
+
background: #ffe;
|
|
318
|
+
}
|
|
319
|
+
#l-diff .l-head {
|
|
320
|
+
color: #999;
|
|
321
|
+
background: #cdf;
|
|
322
|
+
}
|
|
323
|
+
#l-diff ins {
|
|
324
|
+
color: #393;
|
|
325
|
+
background: #dfd;
|
|
326
|
+
}
|
|
327
|
+
#l-diff del {
|
|
328
|
+
color: #c33;
|
|
329
|
+
background: #fdd;
|
|
330
|
+
}
|
|
302
331
|
|
|
303
332
|
.l-error {
|
|
304
333
|
color: red;
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* backup/git
|
|
3
|
+
*/
|
|
4
|
+
"use strict";
|
|
5
|
+
|
|
6
|
+
const path = require('path');
|
|
7
|
+
const fs = require('fs');
|
|
8
|
+
const execSync = require('child_process').execFileSync;
|
|
9
|
+
const exec = require('util').promisify(require('child_process').execFile);
|
|
10
|
+
const spawn = require('child_process').spawn;
|
|
11
|
+
|
|
12
|
+
class Git {
|
|
13
|
+
|
|
14
|
+
constructor(repodir) {
|
|
15
|
+
let git_dir = path.join(repodir, '.git');
|
|
16
|
+
this._option = [ '--git-dir=' + git_dir, '--work-tree=' + repodir ];
|
|
17
|
+
fs.statSync(git_dir);
|
|
18
|
+
execSync('git', this._option.concat('init'));
|
|
19
|
+
try {
|
|
20
|
+
execSync('git', this._option.concat('log'));
|
|
21
|
+
}
|
|
22
|
+
catch(e) {
|
|
23
|
+
execSync('git', this._option.concat([
|
|
24
|
+
'commit','--allow-empty','-m','.']));
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async checkIn(location, time, user) {
|
|
29
|
+
let path = location.replace(/^\//,'');
|
|
30
|
+
let log = await exec('git', this._option.concat([
|
|
31
|
+
'log','--oneline','--', path]));
|
|
32
|
+
let diff = await exec('git', this._option.concat(['diff','--', path]));
|
|
33
|
+
if (! log.stdout || diff.stdout) {
|
|
34
|
+
await exec('git', this._option.concat(['add', path]));
|
|
35
|
+
await exec('git', this._option.concat([
|
|
36
|
+
'commit','-m', `${time}:${user}`]));
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async checkOut(location, rev) {
|
|
41
|
+
let path = location.replace(/^\//,'');
|
|
42
|
+
let rv = await exec('git', this._option.concat([
|
|
43
|
+
'show',`${rev}:${path}`]));
|
|
44
|
+
return rv.stdout;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
checkOutStream(location, rev) {
|
|
48
|
+
let path = location.replace(/^\//,'');
|
|
49
|
+
let rv = spawn('git', this._option.concat([
|
|
50
|
+
'show',`${rev}:${path}`]));
|
|
51
|
+
return rv.stdout;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async log(location) {
|
|
55
|
+
let path = location.replace(/^\//,'');
|
|
56
|
+
let rv = await exec('git', this._option.concat([
|
|
57
|
+
'log','--pretty=format:%h\t%s','--', path]));
|
|
58
|
+
let log = [];
|
|
59
|
+
if (! rv.stdout) return log;
|
|
60
|
+
for (let line of rv.stdout.split(/\n/)) {
|
|
61
|
+
let [rev, comment] = line.split(/\t/);
|
|
62
|
+
let [time, user] = comment.split(/:/);
|
|
63
|
+
log.push({ rev: rev, time: +time, user: user });
|
|
64
|
+
}
|
|
65
|
+
return log;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async diff(location, rev1, rev2) {
|
|
69
|
+
let rv;
|
|
70
|
+
let path = location.replace(/^\//,'');
|
|
71
|
+
if (rev2) {
|
|
72
|
+
rv = await exec('git', this._option.concat([
|
|
73
|
+
'diff',`${rev1}..${rev2}`,'--', path]));
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
rv = await exec('git', this._option.concat([
|
|
77
|
+
'diff', rev1, '--', path]));
|
|
78
|
+
}
|
|
79
|
+
let diff = [], body;
|
|
80
|
+
for (let line of rv.stdout.split(/\n/)) {
|
|
81
|
+
if (line.match(/^@@/)) body = 1;
|
|
82
|
+
if (! body) continue;
|
|
83
|
+
diff.push(line);
|
|
84
|
+
}
|
|
85
|
+
return diff;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
module.exports = function(repodir) {
|
|
90
|
+
try {
|
|
91
|
+
return new Git(repodir);
|
|
92
|
+
}
|
|
93
|
+
catch(e) {
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
}
|
package/lib/html/file.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
"use strict";
|
|
5
5
|
|
|
6
6
|
const { cdata, fixpath } = require('../util/html-escape');
|
|
7
|
+
const { timeStr } = require('../util/str-tool');
|
|
7
8
|
|
|
8
9
|
const HTML = require('./');
|
|
9
10
|
|
|
@@ -15,19 +16,40 @@ module.exports = class File extends HTML {
|
|
|
15
16
|
}
|
|
16
17
|
|
|
17
18
|
_editmenu() {
|
|
19
|
+
let path = fixpath(this._r.location, this._req.baseUrl);
|
|
20
|
+
let rev = this._req.param('rev');
|
|
21
|
+
let param = this._req.cmd == 'edit' ? ''
|
|
22
|
+
: this._req.cmd == 'diff' ? '?cmd=edit'
|
|
23
|
+
: rev ? `?cmd=edit&rev=${rev}`
|
|
24
|
+
: '?cmd=edit';
|
|
25
|
+
|
|
26
|
+
return `<li><a href="${cdata(path + param)}" accesskey="e">`
|
|
27
|
+
+ cdata(this.msg('toolbar.edit'))
|
|
28
|
+
+ `</a></li>\n`;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
_logmenu() {
|
|
32
|
+
if (! this._r._backup) return ''
|
|
18
33
|
let path = fixpath(this._r.location, this._req.baseUrl);
|
|
19
34
|
return '<li><a href="'
|
|
20
35
|
+ cdata(path)
|
|
21
|
-
+ (this._req.cmd == '
|
|
22
|
-
+ '"
|
|
23
|
-
+ cdata(this.msg('toolbar.
|
|
36
|
+
+ (this._req.cmd == 'log' ? '' : '?cmd=log')
|
|
37
|
+
+ '">'
|
|
38
|
+
+ cdata(this.msg('toolbar.log'))
|
|
24
39
|
+ `</a></li>\n`;
|
|
25
40
|
}
|
|
26
41
|
|
|
27
42
|
_title() {
|
|
28
|
-
this.
|
|
43
|
+
const req = this._req;
|
|
44
|
+
const msg = req.msg;
|
|
45
|
+
|
|
46
|
+
let title = this._r.name;
|
|
47
|
+
if (req.cmd == 'log') title = msg('log.title', title);
|
|
48
|
+
else if (req.cmd == 'diff') title = msg('diff.title', title);
|
|
49
|
+
|
|
50
|
+
this.title(title);
|
|
29
51
|
return `<h1 id="l-title"><a href="${cdata(this._r.name)}">`
|
|
30
|
-
+ `${cdata(
|
|
52
|
+
+ `${cdata(title)}</a></h1>\n`;
|
|
31
53
|
}
|
|
32
54
|
|
|
33
55
|
_path() {
|
|
@@ -92,6 +114,50 @@ module.exports = class File extends HTML {
|
|
|
92
114
|
+ '</script>\n';
|
|
93
115
|
}
|
|
94
116
|
|
|
117
|
+
_log() {
|
|
118
|
+
|
|
119
|
+
const msg = this._req.msg;
|
|
120
|
+
|
|
121
|
+
let html = '<table id="l-log">\n';
|
|
122
|
+
html += '<tr>'
|
|
123
|
+
+ `<th>${cdata(msg('log.time'))}</th>`
|
|
124
|
+
+ (this._r.text != null
|
|
125
|
+
? `<th colspan="2">${cdata(msg('log.diff'))}</th>`
|
|
126
|
+
: '')
|
|
127
|
+
+ '<th></th>'
|
|
128
|
+
+ (this._r.text != null ? '<th></th>' : '')
|
|
129
|
+
+ '</tr>\n'
|
|
130
|
+
|
|
131
|
+
for (let i = 0; i < this._r.log.length; i++) {
|
|
132
|
+
|
|
133
|
+
let log = this._r.log[i];
|
|
134
|
+
let log2 = this._r.log[i + 1];
|
|
135
|
+
|
|
136
|
+
html += '<tr>'
|
|
137
|
+
+ `<td class="l-time">${timeStr(log.time)}</td>`
|
|
138
|
+
+ (this._r.text != null
|
|
139
|
+
? ((log2 ? (
|
|
140
|
+
'<td class="l-diff">'
|
|
141
|
+
+ `<a href="${cdata(
|
|
142
|
+
`?cmd=diff&rev=${log2.rev}&rev=${log.rev}`)}">`
|
|
143
|
+
+ `${cdata(msg('log.prev'))}</a></td>`
|
|
144
|
+
) : '<td></td>')
|
|
145
|
+
+ '<td class="l-diff">'
|
|
146
|
+
+ `<a href="${cdata(`?cmd=diff&rev=${log.rev}`)}">`
|
|
147
|
+
+ `${cdata(msg('log.curr'))}</a></td>`)
|
|
148
|
+
: '')
|
|
149
|
+
+ `<td><a href="?rev=${log.rev}">`
|
|
150
|
+
+ `${cdata(msg('log.show'))}</a></td>`
|
|
151
|
+
+ (this._r.text != null
|
|
152
|
+
? `<td><a href="?cmd=edit&rev=${log.rev}">`
|
|
153
|
+
+ `${cdata(msg('log.edit'))}</a></td>`
|
|
154
|
+
: '')
|
|
155
|
+
+ '</tr>\n';
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return html += '</table>\n';
|
|
159
|
+
}
|
|
160
|
+
|
|
95
161
|
edit() {
|
|
96
162
|
return this.stringify(
|
|
97
163
|
this._title()
|
|
@@ -99,4 +165,11 @@ module.exports = class File extends HTML {
|
|
|
99
165
|
+ this._form()
|
|
100
166
|
);
|
|
101
167
|
}
|
|
168
|
+
|
|
169
|
+
log() {
|
|
170
|
+
return this.stringify(
|
|
171
|
+
this._title()
|
|
172
|
+
+ this._log()
|
|
173
|
+
);
|
|
174
|
+
}
|
|
102
175
|
}
|
package/lib/html/folder.js
CHANGED
|
@@ -4,39 +4,8 @@
|
|
|
4
4
|
"use strict";
|
|
5
5
|
|
|
6
6
|
const File = require('./file');
|
|
7
|
-
const { cdata, fixpath }
|
|
8
|
-
|
|
9
|
-
function timeStr(time) {
|
|
10
|
-
|
|
11
|
-
const now = new Date().getTime();
|
|
12
|
-
|
|
13
|
-
const date = new Date(time);
|
|
14
|
-
const year = date.getFullYear();
|
|
15
|
-
const m = date.getMonth() + 1;
|
|
16
|
-
const mm = ('0' + m).substr(-2);
|
|
17
|
-
const d = date.getDate();
|
|
18
|
-
const dd = ('0' + d).substr(-2);
|
|
19
|
-
const hour = ('0' + date.getHours()).substr(-2);
|
|
20
|
-
const min = ('0' + date.getMinutes()).substr(-2);
|
|
21
|
-
|
|
22
|
-
if (now - time < 1000*60*60*12) return `${hour}:${min}`;
|
|
23
|
-
else if (now - time < 1000*60*60*24*365/2)
|
|
24
|
-
return `${m}/${d} ${hour}:${min}`;
|
|
25
|
-
else return `${year}/${mm}/${dd} `;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function sizeStr(size) {
|
|
29
|
-
|
|
30
|
-
if (size == null) return '-';
|
|
31
|
-
|
|
32
|
-
let str;
|
|
33
|
-
for (let unit of ['',' KB',' MB',' GB',' TB']) {
|
|
34
|
-
str = unit ? size.toFixed(1) + unit : size + unit;
|
|
35
|
-
if (size < 1024) return str;
|
|
36
|
-
size = size / 1024;
|
|
37
|
-
}
|
|
38
|
-
return str;
|
|
39
|
-
}
|
|
7
|
+
const { cdata, fixpath } = require('../util/html-escape');
|
|
8
|
+
const { timeStr, sizeStr } = require('../util/str-tool');
|
|
40
9
|
|
|
41
10
|
function cmp(key) {
|
|
42
11
|
return key == 'n' ? (x, y)=> ! x.type && y.type ? -1
|
|
@@ -94,6 +63,8 @@ function uc(c) { return c.toUpperCase() }
|
|
|
94
63
|
|
|
95
64
|
module.exports = class Folder extends File {
|
|
96
65
|
|
|
66
|
+
_logmenu() { return '' }
|
|
67
|
+
|
|
97
68
|
_table() {
|
|
98
69
|
|
|
99
70
|
const req = this._req;
|
package/lib/html/index.js
CHANGED
|
@@ -18,6 +18,7 @@ module.exports = class HTML {
|
|
|
18
18
|
style: '',
|
|
19
19
|
icon: DEFAULT_ICON,
|
|
20
20
|
script: [],
|
|
21
|
+
meta: [],
|
|
21
22
|
};
|
|
22
23
|
this.msg = req.msg;
|
|
23
24
|
}
|
|
@@ -58,6 +59,12 @@ module.exports = class HTML {
|
|
|
58
59
|
return this;
|
|
59
60
|
}
|
|
60
61
|
|
|
62
|
+
meta(attr) {
|
|
63
|
+
if (attr) this._.meta.push(attr);
|
|
64
|
+
else return this._.meta;
|
|
65
|
+
return this;
|
|
66
|
+
}
|
|
67
|
+
|
|
61
68
|
_head() {
|
|
62
69
|
const req = this._req;
|
|
63
70
|
|
|
@@ -83,10 +90,17 @@ module.exports = class HTML {
|
|
|
83
90
|
`<script>\n${opt.code}</script>\n`
|
|
84
91
|
).join('');
|
|
85
92
|
|
|
93
|
+
const meta = this._.meta.map(attr=>{
|
|
94
|
+
let attrs = Object.keys(attr).map(key=>
|
|
95
|
+
`${cdata(key)}="${cdata(attr[key])}"`).join(' ');
|
|
96
|
+
return `<meta ${attrs}>\n`;
|
|
97
|
+
}).join('');
|
|
98
|
+
|
|
86
99
|
return '<head>\n'
|
|
87
100
|
+ '<meta charset="utf-8">\n'
|
|
88
101
|
+ '<meta name="viewport" '
|
|
89
102
|
+ 'content="width=device-width, initial-scale=1">\n'
|
|
103
|
+
+ meta
|
|
90
104
|
+ `<title>${cref(this.title())}</title>\n`
|
|
91
105
|
+ scriptRef
|
|
92
106
|
+ icon
|
|
@@ -98,6 +112,8 @@ module.exports = class HTML {
|
|
|
98
112
|
|
|
99
113
|
_editmenu() { return '' }
|
|
100
114
|
|
|
115
|
+
_logmenu() { return '' }
|
|
116
|
+
|
|
101
117
|
_toolbar() {
|
|
102
118
|
const msg = this.msg;
|
|
103
119
|
const req = this._req;
|
|
@@ -109,6 +125,7 @@ module.exports = class HTML {
|
|
|
109
125
|
+ ` alt="${cdata(msg('toolbar.home'))}"></a>\n`
|
|
110
126
|
+ `<ul>\n`;
|
|
111
127
|
if (this._req.user) {
|
|
128
|
+
html += this._logmenu();
|
|
112
129
|
html += this._editmenu();
|
|
113
130
|
const url = '?cmd=logout&session_id='
|
|
114
131
|
+ encodeURIComponent(this._req.sessionID);
|
package/lib/html/text.js
CHANGED
|
@@ -20,4 +20,34 @@ module.exports = class Text extends File {
|
|
|
20
20
|
+ `<input type="submit" value="${cdata(msg('udtext.submit'))}">\n`
|
|
21
21
|
+ '</form>\n';
|
|
22
22
|
}
|
|
23
|
+
|
|
24
|
+
_diff() {
|
|
25
|
+
|
|
26
|
+
if (! this._r.diff.length) return '';
|
|
27
|
+
|
|
28
|
+
let html = '<div id="l-diff">\n';
|
|
29
|
+
|
|
30
|
+
for (let line of this._r.diff) {
|
|
31
|
+
|
|
32
|
+
if (! line.length) continue;
|
|
33
|
+
if (line[0] == '\\') continue;
|
|
34
|
+
|
|
35
|
+
html += line[0] == '@'
|
|
36
|
+
? `<span class="l-head">${cdata(line)}</span>\n`
|
|
37
|
+
: line[0] == '+'
|
|
38
|
+
? `<ins>${cdata(line.substr(1))}</ins>\n`
|
|
39
|
+
: line[0] == '-'
|
|
40
|
+
? `<del>${cdata(line.substr(1))}</del>\n`
|
|
41
|
+
: `<span>${cdata(line.substr(1))}</span>\n`;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return html += '</div>\n';
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
diff() {
|
|
48
|
+
return this.stringify(
|
|
49
|
+
this._title()
|
|
50
|
+
+ this._diff()
|
|
51
|
+
);
|
|
52
|
+
}
|
|
23
53
|
}
|
package/lib/http/request.js
CHANGED
|
@@ -15,6 +15,7 @@ module.exports = class Request {
|
|
|
15
15
|
this._liulian = liulian;
|
|
16
16
|
this._req = req;
|
|
17
17
|
this.msg = liulian._.locale(req.acceptsLanguages());
|
|
18
|
+
this._n_open = 0;
|
|
18
19
|
}
|
|
19
20
|
|
|
20
21
|
get version() { return this._liulian._version }
|
|
@@ -62,4 +63,9 @@ module.exports = class Request {
|
|
|
62
63
|
: isAbsolute(path) ? base + path
|
|
63
64
|
: base + join(this.pathDir, path);
|
|
64
65
|
}
|
|
66
|
+
|
|
67
|
+
openFile() {
|
|
68
|
+
this._n_open++;
|
|
69
|
+
if (this._n_open > 1000) throw 508;
|
|
70
|
+
}
|
|
65
71
|
}
|
package/lib/http/response.js
CHANGED
|
@@ -39,6 +39,12 @@ module.exports = class Response {
|
|
|
39
39
|
}
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
+
sendStream(stream, type) {
|
|
43
|
+
this._res.type(type)
|
|
44
|
+
stream.pipe(this._res);
|
|
45
|
+
this._cleanUp();
|
|
46
|
+
}
|
|
47
|
+
|
|
42
48
|
sendText(text, type = 'text/html') {
|
|
43
49
|
this._res.type(type).send(text);
|
|
44
50
|
this._cleanUp();
|
package/lib/module/core.js
CHANGED
|
@@ -13,7 +13,7 @@ module.exports = class Core {
|
|
|
13
13
|
this._req = parser._r._req;
|
|
14
14
|
this._inline = ['img','color','size','br','clear','class','lang'];
|
|
15
15
|
this._block = ['title','contents','footnote','include','img','clear',
|
|
16
|
-
'class','style','icon','lang','script'];
|
|
16
|
+
'class','style','icon','lang','redirect','script'];
|
|
17
17
|
this._np = ['style','script'];
|
|
18
18
|
}
|
|
19
19
|
|
|
@@ -148,6 +148,17 @@ module.exports = class Core {
|
|
|
148
148
|
return '';
|
|
149
149
|
}
|
|
150
150
|
|
|
151
|
+
redirect(type, param, value) {
|
|
152
|
+
let [url, sec] = param.split(/,\s*/);
|
|
153
|
+
sec = sec || '0';
|
|
154
|
+
let attr = {
|
|
155
|
+
'http-equiv': 'refresh',
|
|
156
|
+
'content': `${cdata(sec)}; URL=${cdata(url)}`
|
|
157
|
+
};
|
|
158
|
+
this._r.meta(attr);
|
|
159
|
+
return '';
|
|
160
|
+
}
|
|
161
|
+
|
|
151
162
|
script(type, param, value) {
|
|
152
163
|
this._r.script({ url: param, code: value });
|
|
153
164
|
return '';
|
package/lib/resource/file.js
CHANGED
|
@@ -17,6 +17,7 @@ module.exports = class File {
|
|
|
17
17
|
this._stat = stat;
|
|
18
18
|
this._location = location;
|
|
19
19
|
this.openFile = openFile;
|
|
20
|
+
this._backup = req.config.backup;
|
|
20
21
|
}
|
|
21
22
|
|
|
22
23
|
get path() { return this._path }
|
|
@@ -34,20 +35,32 @@ module.exports = class File {
|
|
|
34
35
|
return this._type;
|
|
35
36
|
}
|
|
36
37
|
get location() { return this._location }
|
|
37
|
-
|
|
38
38
|
get redirect() { return this._redirect }
|
|
39
|
+
get log() { return this._log }
|
|
39
40
|
|
|
40
41
|
async open() {
|
|
41
|
-
if (this._req.
|
|
42
|
-
|
|
42
|
+
if (! this._req.user && (this._req.cmd == 'edit' ||
|
|
43
|
+
this._req.cmd == 'log' ||
|
|
44
|
+
this._req.cmd == 'diff' )) throw 403;
|
|
43
45
|
try {
|
|
44
46
|
const fh = await fs.open(this._path);
|
|
45
47
|
fh.close();
|
|
46
|
-
return this;
|
|
47
48
|
}
|
|
48
49
|
catch(err) {
|
|
49
50
|
throw 403;
|
|
50
51
|
}
|
|
52
|
+
if (this._backup && (this._req.cmd == 'edit' ||
|
|
53
|
+
this._req.cmd == 'log'))
|
|
54
|
+
{
|
|
55
|
+
if (Date.now() - this.time > 1000*60*60*24) {
|
|
56
|
+
await this._backup.checkIn(this.location,
|
|
57
|
+
this.time, this._req.user);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
if (this._backup && this._req.cmd == 'log') {
|
|
61
|
+
this._log = await this._backup.log(this.location);
|
|
62
|
+
}
|
|
63
|
+
return this;
|
|
51
64
|
}
|
|
52
65
|
|
|
53
66
|
async update() {
|
|
@@ -66,8 +79,20 @@ module.exports = class File {
|
|
|
66
79
|
}
|
|
67
80
|
|
|
68
81
|
send(res) {
|
|
69
|
-
if
|
|
70
|
-
|
|
71
|
-
else
|
|
82
|
+
if (this._req.cmd == 'edit') res.sendText(new HTML(this).edit());
|
|
83
|
+
else if (this._req.cmd == 'log') res.sendText(new HTML(this).log());
|
|
84
|
+
else {
|
|
85
|
+
try {
|
|
86
|
+
let rev = this._req.param('rev');
|
|
87
|
+
if (rev) {
|
|
88
|
+
res.sendStream(
|
|
89
|
+
this._backup.checkOutStream(this.location, rev),
|
|
90
|
+
this.type);
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
catch (err) {}
|
|
95
|
+
res.sendFile(this._path, this.type);
|
|
96
|
+
}
|
|
72
97
|
}
|
|
73
98
|
}
|
package/lib/resource/index.js
CHANGED
|
@@ -21,7 +21,11 @@ async function resource(req, file) {
|
|
|
21
21
|
else location = decodeURIComponent(urljoin(req.pathDir, file));
|
|
22
22
|
}
|
|
23
23
|
else location = decodeURIComponent(req.path);
|
|
24
|
+
|
|
25
|
+
if (location.match(/^\/\.git(?:\/.*)?$/)) throw 404;
|
|
26
|
+
|
|
24
27
|
const path = join(req.config.home, '/docs/', location);
|
|
28
|
+
req.openFile();
|
|
25
29
|
|
|
26
30
|
try {
|
|
27
31
|
const stat = await fs.stat(path);
|
package/lib/resource/liulian.js
CHANGED
|
@@ -16,6 +16,7 @@ module.exports = class LiuLian extends Text {
|
|
|
16
16
|
icon(url) { this._html.icon(url) }
|
|
17
17
|
lang(lang) { this._html.lang(lang) }
|
|
18
18
|
script(script) { this._html.script(script) }
|
|
19
|
+
meta(attr) { this._html.meta(attr) }
|
|
19
20
|
|
|
20
21
|
async _seekToParent(filename) {
|
|
21
22
|
let pathDir = this._req.pathDir;
|
|
@@ -41,10 +42,18 @@ module.exports = class LiuLian extends Text {
|
|
|
41
42
|
if (this._req.cmd == 'edit') {
|
|
42
43
|
res.sendText(new HTML(this).edit());
|
|
43
44
|
}
|
|
45
|
+
else if (this._req.cmd == 'log') {
|
|
46
|
+
res.sendText(new HTML(this).log());
|
|
47
|
+
}
|
|
48
|
+
else if (this._req.cmd == 'diff') {
|
|
49
|
+
res.sendText(new HTML(this).diff());
|
|
50
|
+
}
|
|
44
51
|
else {
|
|
45
|
-
this._text =
|
|
52
|
+
this._text = (this.name != 'HEAD'
|
|
53
|
+
? await this._seekToParent('HEAD') + '\n' : '')
|
|
46
54
|
+ this._text + '\n'
|
|
47
|
-
+
|
|
55
|
+
+ (this.name != 'TAIL'
|
|
56
|
+
? await this._seekToParent('TAIL') : '');
|
|
48
57
|
this._html = new HTML(this);
|
|
49
58
|
res.sendText(this._html.stringify(await parse(this)));
|
|
50
59
|
}
|
package/lib/resource/text.js
CHANGED
|
@@ -11,17 +11,32 @@ const File = require('./file');
|
|
|
11
11
|
module.exports = class Text extends File {
|
|
12
12
|
|
|
13
13
|
get text() { return this._text }
|
|
14
|
+
get diff() { return this._diff }
|
|
14
15
|
|
|
15
16
|
async open() {
|
|
16
|
-
|
|
17
|
-
|
|
17
|
+
await super.open();
|
|
18
|
+
if (this._backup && this._req.cmd == 'diff') {
|
|
19
|
+
let rev = this._req.params('rev');
|
|
20
|
+
this._diff = await this._backup.diff(this.location, ...rev);
|
|
21
|
+
return this;
|
|
22
|
+
}
|
|
23
|
+
try {
|
|
24
|
+
let rev = this._req.param('rev');
|
|
25
|
+
if (rev) {
|
|
26
|
+
this._text = await this._backup.checkOut(this.location, rev);
|
|
27
|
+
return this;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
catch(err) {
|
|
31
|
+
throw 404;
|
|
32
|
+
}
|
|
18
33
|
try {
|
|
19
34
|
this._text = await fs.readFile(this._path, 'utf-8');
|
|
20
|
-
return this;
|
|
21
35
|
}
|
|
22
36
|
catch(err) {
|
|
23
37
|
throw 403;
|
|
24
38
|
}
|
|
39
|
+
return this;
|
|
25
40
|
}
|
|
26
41
|
|
|
27
42
|
async update() {
|
|
@@ -39,8 +54,9 @@ module.exports = class Text extends File {
|
|
|
39
54
|
}
|
|
40
55
|
|
|
41
56
|
send(res) {
|
|
42
|
-
if
|
|
43
|
-
|
|
44
|
-
else
|
|
57
|
+
if (this._req.cmd == 'edit') res.sendText(new HTML(this).edit());
|
|
58
|
+
else if (this._req.cmd == 'log') res.sendText(new HTML(this).log());
|
|
59
|
+
else if (this._req.cmd == 'diff') res.sendText(new HTML(this).diff());
|
|
60
|
+
else res.sendFile(this._path, this.type);
|
|
45
61
|
}
|
|
46
62
|
}
|
package/lib/text/liulian.js
CHANGED
|
@@ -229,7 +229,7 @@ class LiuLian {
|
|
|
229
229
|
let cell = [];
|
|
230
230
|
for (;;) {
|
|
231
231
|
let line = this.readline().replace(/\|$/,'');
|
|
232
|
-
let cols = line.match(/\|(?:\[\[.*?\]\]|[^\|])*/g)
|
|
232
|
+
let cols = line.match(/\|(?:\[\[.*?\]\]|[^\|])*/g) || [];
|
|
233
233
|
cell.push(cols.map(c=>c.replace(/^\|/,'')));
|
|
234
234
|
line = this.nextline();
|
|
235
235
|
if (! line || ! line.match(/^\|/)) break;
|
|
@@ -269,13 +269,19 @@ class LiuLian {
|
|
|
269
269
|
if (align) style.push('text-align:' + align);
|
|
270
270
|
text = text.replace(/^[<=>]/,'');
|
|
271
271
|
|
|
272
|
+
let cls = (text.match(/^(?:\.[\w\-]+)+/)||[])[0];
|
|
273
|
+
text = text.replace(/^(?:\.[\w\-]+)+/,'');
|
|
274
|
+
cls = cls
|
|
275
|
+
? ` class="${cls.replace(/^\./,'').replace(/\./g,' ')}"`
|
|
276
|
+
: '';
|
|
277
|
+
|
|
272
278
|
let color = (text.match(/^#[0-9a-f]+/i)||[])[0];
|
|
273
279
|
if (color) style.push('background:' + color);
|
|
274
280
|
text = text.replace(/^#[0-9a-f]+/i,'');
|
|
275
281
|
|
|
276
282
|
if (style.length) style = ` style="${cdata(style.join(';'))}"`;
|
|
277
283
|
|
|
278
|
-
html += '<' + hd + colspan + rowspan + style + '>'
|
|
284
|
+
html += '<' + hd + colspan + rowspan + cls + style + '>'
|
|
279
285
|
+ this.inline(text) + '</' + hd + '>';
|
|
280
286
|
}
|
|
281
287
|
html += '</tr>\n';
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* util/str-tool
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
function timeStr(time) {
|
|
6
|
+
|
|
7
|
+
const now = new Date().getTime();
|
|
8
|
+
|
|
9
|
+
const date = new Date(time);
|
|
10
|
+
const year = date.getFullYear();
|
|
11
|
+
const m = date.getMonth() + 1;
|
|
12
|
+
const mm = ('0' + m).substr(-2);
|
|
13
|
+
const d = date.getDate();
|
|
14
|
+
const dd = ('0' + d).substr(-2);
|
|
15
|
+
const hour = ('0' + date.getHours()).substr(-2);
|
|
16
|
+
const min = ('0' + date.getMinutes()).substr(-2);
|
|
17
|
+
|
|
18
|
+
if (now - time < 1000*60*60*12) return `${hour}:${min}`;
|
|
19
|
+
else if (now - time < 1000*60*60*24*365/2)
|
|
20
|
+
return `${m}/${d} ${hour}:${min}`;
|
|
21
|
+
else return `${year}/${mm}/${dd}`;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function sizeStr(size) {
|
|
25
|
+
|
|
26
|
+
if (size == null) return '-';
|
|
27
|
+
|
|
28
|
+
let str;
|
|
29
|
+
for (let unit of ['',' KB',' MB',' GB',' TB']) {
|
|
30
|
+
str = unit ? size.toFixed(1) + unit : size + unit;
|
|
31
|
+
if (size < 1024) return str;
|
|
32
|
+
size = size / 1024;
|
|
33
|
+
}
|
|
34
|
+
return str;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
module.exports = {
|
|
38
|
+
timeStr: timeStr,
|
|
39
|
+
sizeStr: sizeStr,
|
|
40
|
+
};
|
package/locale/en
CHANGED
|
@@ -13,6 +13,7 @@ toolbar.home:Home
|
|
|
13
13
|
toolbar.login:Login
|
|
14
14
|
toolbar.logout:Logout
|
|
15
15
|
toolbar.edit:Edit
|
|
16
|
+
toolbar.log:History
|
|
16
17
|
# -----------------------------------------------------------------------------
|
|
17
18
|
adduser.title:Sign Up
|
|
18
19
|
adduser.user:Login Name
|
|
@@ -45,3 +46,12 @@ rmdir.submit:REMOVE
|
|
|
45
46
|
# -----------------------------------------------------------------------------
|
|
46
47
|
udtext.submit:SAVE
|
|
47
48
|
# -----------------------------------------------------------------------------
|
|
49
|
+
log.title:Revision history of {$1}
|
|
50
|
+
log.rev:Revision
|
|
51
|
+
log.time:Modified
|
|
52
|
+
log.diff:Difference
|
|
53
|
+
log.prev:from previous
|
|
54
|
+
log.curr:to current
|
|
55
|
+
log.show:Show
|
|
56
|
+
log.edit:Edit
|
|
57
|
+
diff.title:Difference of {$1}
|
package/locale/ja
CHANGED
|
@@ -13,6 +13,7 @@ toolbar.home:ホーム
|
|
|
13
13
|
toolbar.login:ログイン
|
|
14
14
|
toolbar.logout:ログアウト
|
|
15
15
|
toolbar.edit:編集
|
|
16
|
+
toolbar.log:履歴
|
|
16
17
|
# -----------------------------------------------------------------------------
|
|
17
18
|
adduser.title:ユーザー登録
|
|
18
19
|
adduser.user:ログイン名
|
|
@@ -45,3 +46,12 @@ rmdir.submit:削除
|
|
|
45
46
|
# -----------------------------------------------------------------------------
|
|
46
47
|
udtext.submit:保存
|
|
47
48
|
# -----------------------------------------------------------------------------
|
|
49
|
+
log.title:{$1} の変更履歴
|
|
50
|
+
log.rev:リビジョン
|
|
51
|
+
log.time:変更日
|
|
52
|
+
log.diff:差分
|
|
53
|
+
log.prev:前の版との差分
|
|
54
|
+
log.curr:現在との差分
|
|
55
|
+
log.show:表示
|
|
56
|
+
log.edit:編集
|
|
57
|
+
diff.title:{$1} の差分
|
package/locale/zh-CN
CHANGED
|
@@ -13,6 +13,7 @@ toolbar.home:主页
|
|
|
13
13
|
toolbar.login:登录
|
|
14
14
|
toolbar.logout:注销
|
|
15
15
|
toolbar.edit:编辑
|
|
16
|
+
toolbar.log:历史
|
|
16
17
|
# -----------------------------------------------------------------------------
|
|
17
18
|
adduser.title:注册
|
|
18
19
|
adduser.user:账号
|
|
@@ -45,3 +46,12 @@ rmdir.submit:删除
|
|
|
45
46
|
# -----------------------------------------------------------------------------
|
|
46
47
|
udtext.submit:保存
|
|
47
48
|
# -----------------------------------------------------------------------------
|
|
49
|
+
log.title:{$1} 的历史纪录
|
|
50
|
+
log.rev:版本
|
|
51
|
+
log.time:修改日期
|
|
52
|
+
log.diff:差别
|
|
53
|
+
log.prev:从前版的
|
|
54
|
+
log.curr:到现在的
|
|
55
|
+
log.show:表示这版
|
|
56
|
+
log.edit:编辑这版
|
|
57
|
+
diff.title:{$1} 的差别
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kobalab/liulian",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.1",
|
|
4
4
|
"description": "Node.jsで動作するWebサイト作成ツール",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
},
|
|
28
28
|
"homepage": "https://kobalab.net/liulian/",
|
|
29
29
|
"devDependencies": {
|
|
30
|
-
"mocha": "^9.
|
|
30
|
+
"mocha": "^9.2.0",
|
|
31
31
|
"nyc": "^15.1.0"
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|