@kobalab/liulian 0.7.7 → 0.8.0
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 +8 -0
- package/README.md +4 -0
- package/bin/liulian.js +4 -1
- package/css/icon.png +0 -0
- package/css/liulian.css +34 -2
- 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/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 +1 -1
package/ChangeLog.md
CHANGED
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
|
@@ -95,7 +95,7 @@ pre {
|
|
|
95
95
|
background: #eee;
|
|
96
96
|
overflow: auto;
|
|
97
97
|
overflow-wrap: normal;
|
|
98
|
-
font-family: Osaka-Mono, "MS ゴシック", Courier, monospace;
|
|
98
|
+
font-family: Osaka-Mono, "HGゴシックM", "MS ゴシック", Courier, monospace;
|
|
99
99
|
font-size: 100%;
|
|
100
100
|
}
|
|
101
101
|
|
|
@@ -151,7 +151,7 @@ input[disabled] {
|
|
|
151
151
|
}
|
|
152
152
|
textarea {
|
|
153
153
|
padding: 4px;
|
|
154
|
-
font-family: Osaka-Mono, "MS ゴシック", Courier, monospace;
|
|
154
|
+
font-family: Osaka-Mono, "HGゴシックM", "MS ゴシック", Courier, monospace;
|
|
155
155
|
font-size: 100%;
|
|
156
156
|
border: solid 1px #999;
|
|
157
157
|
border-radius: 4px;
|
|
@@ -296,6 +296,38 @@ input[type="submit"] {
|
|
|
296
296
|
width: 100%;
|
|
297
297
|
height: 70vh;
|
|
298
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
|
+
}
|
|
299
331
|
|
|
300
332
|
.l-error {
|
|
301
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
|
}
|
|
@@ -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} 的差别
|