@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 CHANGED
@@ -1,3 +1,11 @@
1
+ ## v0.8.0 / 2021-12-30
2
+
3
+ - #4 自動バックアップの機能を追加
4
+ - redirect モジュールを追加
5
+ - アイコンを変更
6
+ - include モジュールの再帰的使用を検知できるようにした
7
+ - HEADとTAILを二重インクルードしないよう修正
8
+
1
9
  ### v0.7.7 / 2021-12-03
2
10
 
3
11
  - | のみの行があると異常終了するバグを修正
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 == 'edit' ? '' : '?cmd=edit')
22
- + '" accesskey="e">'
23
- + cdata(this.msg('toolbar.edit'))
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.title(this._r.name);
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(this._r.name)}</a></h1>\n`;
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
  }
@@ -4,39 +4,8 @@
4
4
  "use strict";
5
5
 
6
6
  const File = require('./file');
7
- const { cdata, fixpath } = require('../util/html-escape');
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
  }
@@ -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
  }
@@ -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();
@@ -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 '';
@@ -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.cmd == 'edit' && ! this._req.user) throw 403;
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 (this._req.cmd == 'edit')
70
- res.sendText(new HTML(this).edit());
71
- else res.sendFile(this._path, this.type);
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
  }
@@ -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);
@@ -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 = await this._seekToParent('HEAD') + '\n'
52
+ this._text = (this.name != 'HEAD'
53
+ ? await this._seekToParent('HEAD') + '\n' : '')
46
54
  + this._text + '\n'
47
- + await this._seekToParent('TAIL');
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
  }
@@ -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
- if (this._req.cmd == 'edit' && ! this._req.user) throw 403;
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 (this._req.cmd == 'edit')
43
- res.sendText(new HTML(this).edit());
44
- else res.sendFile(this._path, this.type);
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} 的差别
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kobalab/liulian",
3
- "version": "0.7.7",
3
+ "version": "0.8.0",
4
4
  "description": "Node.jsで動作するWebサイト作成ツール",
5
5
  "publishConfig": {
6
6
  "access": "public"