oipage 0.1.0-alpha.1 → 0.1.0-alpha.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.
Files changed (45) hide show
  1. package/CHANGELOG +38 -0
  2. package/README.md +103 -4
  3. package/bin/options.js +45 -11
  4. package/bin/run +166 -31
  5. package/browserjs/getStyle/index.d.ts +11 -0
  6. package/browserjs/getStyle/index.js +13 -0
  7. package/browserjs/index.d.ts +10 -0
  8. package/browserjs/index.js +7 -0
  9. package/browserjs/onReady/index.d.ts +8 -0
  10. package/browserjs/onReady/index.js +8 -0
  11. package/corejs/animation/index.d.ts +12 -0
  12. package/corejs/animation/index.js +102 -0
  13. package/corejs/index.d.ts +10 -0
  14. package/corejs/index.js +7 -0
  15. package/corejs/throttle/index.d.ts +31 -0
  16. package/corejs/throttle/index.js +50 -0
  17. package/nodejs/core/file.js +128 -0
  18. package/nodejs/core/image.js +5 -0
  19. package/nodejs/core/log.js +82 -0
  20. package/nodejs/core/network.js +40 -0
  21. package/nodejs/core/options.js +49 -0
  22. package/nodejs/core/remote.js +60 -0
  23. package/nodejs/core/responseFileList.js +28 -0
  24. package/nodejs/core/server.js +163 -0
  25. package/nodejs/data/404.js +52 -0
  26. package/nodejs/data/images/file.js +1 -0
  27. package/nodejs/data/images/folder.js +1 -0
  28. package/nodejs/data/mime.types.js +112 -0
  29. package/nodejs/index.js +48 -0
  30. package/nodejs/loader/simpleScss.js +248 -0
  31. package/nodejs/loader/xhtml.js +521 -0
  32. package/package.json +17 -20
  33. package/stylecss/index.css +3 -0
  34. package/stylecss/normalize.css +94 -0
  35. package/stylecss/rasterize.css +318 -0
  36. package/stylecss/skeleton.css +16 -0
  37. package/types/get-options.d.ts +6 -0
  38. package/types/index.d.ts +187 -0
  39. package/public/index.html +0 -14
  40. package/script/command/build.js +0 -22
  41. package/script/command/dev.js +0 -38
  42. package/script/config/alignment.js +0 -15
  43. package/script/config/webpack.js +0 -60
  44. package/src/platforms/h5/index.js +0 -0
  45. package/src/runtime/index.js +0 -0
@@ -0,0 +1,31 @@
1
+ export type throttleOpportunityType = "begin" | "end" | "wide"
2
+
3
+ export interface throttleOptionType {
4
+
5
+ /**
6
+ * 节流时长
7
+ */
8
+ time?: number
9
+
10
+ /**
11
+ * 是否持续节流
12
+ */
13
+ keep?: boolean
14
+
15
+ /**
16
+ * 执行时机
17
+ *
18
+ * begin(开始触发)、end(结束触发)、wide(第一次开始触发,其余结束触发)
19
+ */
20
+ opportunity?: throttleOpportunityType
21
+
22
+ }
23
+
24
+ /**
25
+ * 节流函数
26
+ */
27
+ export interface throttleType {
28
+ (callback: Function, option?: throttleOptionType): Function
29
+ }
30
+
31
+ export let throttle: throttleType
@@ -0,0 +1,50 @@
1
+ export function throttle(callback, _option) {
2
+
3
+ // 缺省值
4
+ var option = {
5
+ time: 200,
6
+ keep: false,
7
+ opportunity: "end"
8
+ };
9
+
10
+ // 校对
11
+ if (_option) {
12
+ for (var key in _option) {
13
+ option[key] = _option[key];
14
+ }
15
+ }
16
+
17
+ var hadInterval = false, hadClick = false, oneClick = false, arg;
18
+ return function () {
19
+ const _this = this;
20
+ arg = arguments;
21
+
22
+ // 如果前置任务都完成了
23
+ if (!hadInterval) {
24
+ if (option.opportunity != 'end') {
25
+ callback.apply(_this, arg);
26
+ }
27
+ hadInterval = true;
28
+
29
+ var interval = setInterval(() => {
30
+ if (hadClick) {
31
+ if (!option.keep) {
32
+ callback.apply(_this, arg);
33
+ }
34
+ } else {
35
+ if (option.opportunity != 'begin') {
36
+ if (oneClick || option.opportunity == 'end') callback.apply(_this, arg);
37
+ }
38
+ hadInterval = false;
39
+ oneClick = false;
40
+ clearInterval(interval);
41
+ }
42
+ hadClick = false;
43
+ }, option.time);
44
+ } else {
45
+ hadClick = true;
46
+ oneClick = true;
47
+ }
48
+
49
+ };
50
+ };
@@ -0,0 +1,128 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+
4
+ // 删除文件或文件夹
5
+ function deleteSync(target) {
6
+
7
+ // 如果文件夹不存在,直接返回即可
8
+ if (!fs.existsSync(target)) return;
9
+
10
+ // 如果是文件,直接删除即可
11
+ if (!fs.lstatSync(target).isDirectory()) {
12
+ fs.unlinkSync(target);
13
+ } else {
14
+
15
+ // 读取子文件
16
+ const subFiles = fs.readdirSync(target);
17
+
18
+ subFiles.forEach(function (file) {
19
+
20
+ // 调用这个方法,删除子文件或文件夹
21
+ const curPath = path.join(target, "./" + file);
22
+ deleteSync(curPath);
23
+
24
+ });
25
+
26
+ // 等子文件或文件夹删除完毕以后,删除本文件夹
27
+ fs.rmdirSync(target);
28
+ }
29
+
30
+ };
31
+
32
+ // 复制文件或文件夹
33
+ function copySync(source, target) {
34
+
35
+ // 如果是文件,直接复制即可
36
+ if (!fs.lstatSync(source).isDirectory()) {
37
+ fs.copyFileSync(source, target);
38
+ } else {
39
+
40
+ // 读取子文件
41
+ const subFiles = fs.readdirSync(source);
42
+
43
+ // 如果文件夹不存在,创建
44
+ if (!fs.existsSync(target)) {
45
+ fs.mkdirSync(target, { recursive: true });
46
+ }
47
+
48
+ // 复制子文件或文件夹
49
+ subFiles.forEach(function (file) {
50
+ copySync(path.join(source, "./" + file), path.join(target, "./" + file));
51
+ });
52
+
53
+ }
54
+ };
55
+
56
+ // 移动文件或文件夹
57
+ function moveSync(source, target) {
58
+
59
+ // 如果是文件,直接剪切即可
60
+ if (!fs.lstatSync(source).isDirectory()) {
61
+ fs.copyFileSync(source, target);
62
+ fs.unlinkSync(source);
63
+ } else {
64
+
65
+ // 读取子文件
66
+ const subFiles = fs.readdirSync(source);
67
+
68
+ // 如果文件夹不存在,创建
69
+ if (!fs.existsSync(target)) {
70
+ fs.mkdirSync(target, { recursive: true });
71
+ }
72
+
73
+ // 移动子文件或文件夹
74
+ subFiles.forEach(function (file) {
75
+ moveSync(path.join(source, "./" + file), path.join(target, "./" + file));
76
+ });
77
+
78
+ // 移动完子文件或文件夹以后(移动完毕也意味着子文件或文件夹被删除了)
79
+ fs.rmdirSync(source);
80
+ }
81
+ }
82
+
83
+ // 遍历当前文件或文件夹中所有文件
84
+ function listFileSync(source, callback) {
85
+ // 文件夹
86
+ if (fs.lstatSync(source).isDirectory()) {
87
+
88
+ // 读取子文件
89
+ const subFiles = fs.readdirSync(source);
90
+ subFiles.forEach(function (file) {
91
+ listFileSync(path.join(source, "./" + file), callback);
92
+ });
93
+ }
94
+
95
+ // 文件
96
+ else {
97
+ let folder = path.join(source, "../");
98
+
99
+ callback({
100
+ "name": source.replace(folder, ""),
101
+ "path": source,
102
+ "folder": folder
103
+ });
104
+ }
105
+ }
106
+
107
+ // 获取文件或文件夹的全路径
108
+ function fullPathSync(pathString, contextPath) {
109
+ if (/^\//.test(pathString) || /^[A-Za-z]:\\/.test(pathString)) {
110
+ // 如果传递的就是全路径
111
+ return pathString;
112
+ }
113
+
114
+ if (arguments.length <= 1) {
115
+ // 默认把当前命令行作为上下文路径
116
+ contextPath = process.cwd();
117
+ }
118
+
119
+ // 拼全路径
120
+ return path.join(contextPath, pathString);
121
+ }
122
+
123
+ // 导出
124
+ exports.deleteSync = deleteSync;
125
+ exports.copySync = copySync;
126
+ exports.moveSync = moveSync;
127
+ exports.listFileSync = listFileSync;
128
+ exports.fullPathSync = fullPathSync;
@@ -0,0 +1,5 @@
1
+ const fs = require('fs');
2
+
3
+ exports.toBase64 = function (filepath) {
4
+ return "data:image/png;base64," + fs.readFileSync(filepath).toString('base64');
5
+ };
@@ -0,0 +1,82 @@
1
+ // 日志
2
+ exports.log = function (txt) {
3
+ console.log("\x1B[32m" + txt + "\x1B[39m");
4
+ };
5
+
6
+ // 警告
7
+ exports.warn = function (txt) {
8
+ console.log("\x1B[33m" + txt + "\x1B[39m");
9
+ };
10
+
11
+ // 错误
12
+ exports.error = function (txt) {
13
+ console.log("\x1B[35m" + txt + "\x1B[39m");
14
+ };
15
+
16
+
17
+ // 计算字符串长度的方法
18
+ const stringwidth = function (str) {
19
+ return str.length;
20
+ };
21
+
22
+ // 预定义的常量
23
+ const MOVE_LEFT = Buffer.from('1b5b3130303044', 'hex').toString();
24
+ const MOVE_UP = Buffer.from('1b5b3141', 'hex').toString();
25
+ const CLEAR_LINE = Buffer.from('1b5b304b', 'hex').toString();
26
+
27
+ // 不换行打印
28
+ const linelog = (function (stream) {
29
+
30
+ // 用来记录前置有多少行需要回退
31
+ let prevLineCount = 0;
32
+
33
+ // 返回实际同行打印的方法
34
+ return function (nextStr) {
35
+ let txt = "";
36
+
37
+ // 清除屏幕
38
+ for (let i = 0; i < prevLineCount; i++) {
39
+ txt += MOVE_LEFT + CLEAR_LINE + (i < prevLineCount - 1 ? MOVE_UP : '');
40
+ }
41
+
42
+ // 写入屏幕
43
+ stream.write(txt + nextStr);
44
+
45
+ // 重新计算需要回滚多少行
46
+ let prevLines = nextStr.split('\n');
47
+ prevLineCount = 0;
48
+ for (let i = 0; i < prevLines.length; i++) {
49
+ // 因为有时候文字过多,因此拿总长度除以一行长度得出真实的行数
50
+ prevLineCount += (Math.ceil(stringwidth(prevLines[i]) / stream.columns) || 1);
51
+ }
52
+
53
+ };
54
+ })(process.stdout);
55
+
56
+ exports.linelog = linelog;
57
+
58
+ /**
59
+ * 进度打印
60
+ *
61
+ * @param {number} percentum 进度0-100
62
+ * @param {string} stream 说明文字,可选择
63
+ */
64
+ exports.deeplog = function (percentum, stream) {
65
+
66
+ if (arguments.length <= 1) stream = "";
67
+
68
+ let txt = "",
69
+ i = 0;
70
+
71
+ // 补充已经有的进度
72
+ for (; i <= percentum && i <= 100; i += 5) {
73
+ txt += "█";
74
+ }
75
+
76
+ // 补充余下的空白
77
+ for (; i <= 100; i += 5) {
78
+ txt += "░";
79
+ }
80
+
81
+ linelog(percentum.toFixed(2) + "%[" + txt + "]" + stream);
82
+ };
@@ -0,0 +1,40 @@
1
+ module.exports = function () {
2
+
3
+ let infomation = {
4
+ IPv4: [],
5
+ IPv6: []
6
+ };
7
+
8
+ let networks = require('os').networkInterfaces()
9
+
10
+ let IPv4Had = {}, IPv6Had = {}
11
+
12
+ for (let typeName in networks) {
13
+ let network = networks[typeName]
14
+ for (let index = 0; index < network.length; index++) {
15
+ if (network[index].mac != "00:00:00:00:00:00") {
16
+ if (network[index].family == 'IPv4' && network[index].address != '127.0.0.1') {
17
+ if (!IPv4Had[network[index].mac]) {
18
+ infomation.IPv4.push({
19
+ address: network[index].address,
20
+ mac: network[index].mac
21
+ });
22
+
23
+ IPv4Had[network[index].mac] = true
24
+ }
25
+ } else if (network[index].family == 'IPv6' && network[index].address != '::1') {
26
+ if (!IPv6Had[network[index].mac]) {
27
+ infomation.IPv6.push({
28
+ address: network[index].address,
29
+ mac: network[index].mac
30
+ });
31
+
32
+ IPv6Had[network[index].mac] = true
33
+ }
34
+ }
35
+ }
36
+ }
37
+ }
38
+
39
+ return infomation;
40
+ };
@@ -0,0 +1,49 @@
1
+ /**
2
+ *
3
+ * 命令行参数解析
4
+ *
5
+ * @param {JSON} config 命令参数缩小到全写的映射
6
+ * @param {Array} argv 需要判断类型的值
7
+ *
8
+ * @returns {JSON} 返回整理后的参数
9
+ *
10
+ */
11
+ module.exports = function (config, argv) {
12
+
13
+ let resultConfig = {
14
+ __terminal__: []
15
+ }, flag = null;
16
+ for (let i = 2; i < argv.length; i++) {
17
+
18
+ // 如果是新的配置
19
+ if (/^--[0-9a-zA-Z]+$/.test(argv[i]) || /^-[0-9a-zA-Z]$/.test(argv[i])) {
20
+ let key = argv[i];
21
+
22
+ // 如果是缩写,需要映射
23
+ if (key.length == 2) {
24
+ key = config[key];
25
+
26
+ // 如果是错误缩写
27
+ if (!key) {
28
+ flag = null;
29
+ continue;
30
+ }
31
+ }
32
+
33
+ flag = key.replace(/^--/, "");
34
+ resultConfig[flag] = [];
35
+ }
36
+
37
+ // 如果是普通的参数
38
+ else if (flag != null) {
39
+ resultConfig[flag].push(argv[i]);
40
+ }
41
+
42
+ else {
43
+ resultConfig.__terminal__.push(argv[i]);
44
+ }
45
+
46
+ }
47
+
48
+ return resultConfig;
49
+ };
@@ -0,0 +1,60 @@
1
+ const https = require('https');
2
+ const http = require('http');
3
+
4
+ exports.get = function (url, options = {}) {
5
+ return new Promise((resolve, reject) => {
6
+
7
+ let handler = /^https/.test(url) ? https : http;
8
+
9
+ handler.get(url, res => {
10
+ res.on('data', (data) => {
11
+ if (options.json) {
12
+ resolve(JSON.parse(data.toString('utf8')));
13
+ } else {
14
+ resolve(data);
15
+ }
16
+ });
17
+ }).on('error', (e) => {
18
+ reject(e);
19
+ });
20
+ });
21
+ };
22
+
23
+ exports.post = function (url, options = {}) {
24
+ return new Promise((resolve, reject) => {
25
+
26
+ let handler = /^https/.test(url) ? https : http;
27
+
28
+ let execArray = /https*:\/\/([^\/]+)(.+)?/.exec(url);
29
+ let hostport = execArray[1].split(":");
30
+
31
+ const req = handler.request({
32
+ hostname: hostport[0],
33
+ port: hostport[1] || 80,
34
+ path: execArray[2] || "/",
35
+ method: "POST",
36
+ headers: options.header || {}
37
+ }, (res) => {
38
+ res.setEncoding('utf8');
39
+
40
+ let data = "";
41
+ res.on('data', (chunk) => {
42
+ data += chunk;
43
+ });
44
+ res.on('end', () => {
45
+ if (options.json) {
46
+ resolve(JSON.parse(data.toString('utf8')));
47
+ } else {
48
+ resolve(data);
49
+ }
50
+ });
51
+ });
52
+
53
+ req.write(options.params || "{}");
54
+
55
+ req.on('error', (e) => {
56
+ reject(e);
57
+ });
58
+ req.end();
59
+ });
60
+ };
@@ -0,0 +1,28 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+
4
+ const Template_404 = require('../data/404');
5
+
6
+ // 读取当前路径下的文件,方便服务器404的时候导航
7
+ module.exports = function (fullUrl) {
8
+
9
+ let files, content = fullUrl;
10
+ try {
11
+ files = fs.readdirSync(fullUrl);
12
+ } catch (e) {
13
+ try {
14
+ content = path.resolve(fullUrl, '../');
15
+ files = fs.readdirSync(content);
16
+ } catch (e) {
17
+ files = [];
18
+ }
19
+ }
20
+
21
+ let template = "<a href='../'>..</a>";
22
+ for (let i in files) {
23
+ let isDirectory = fs.lstatSync(path.join(content, files[i])).isDirectory();
24
+ template += "<a class='" + (isDirectory ? "folder" : "file") + "' href='./" + files[i] + (isDirectory ? "/" : "") + "'>" + files[i] + "</a>";
25
+ }
26
+
27
+ return Template_404(template);
28
+ };
@@ -0,0 +1,163 @@
1
+ const http = require('http');
2
+ const fs = require('fs');
3
+
4
+ const mineTypes = require('../data/mime.types.js');
5
+ const { log, warn, error } = require('./log.js');
6
+ const responseFileList = require('./responseFileList.js');
7
+ const path = require('path');
8
+
9
+ const jsonfile = JSON.parse(fs.readFileSync(path.join(__dirname, '../../package.json')));
10
+
11
+ module.exports = function (config = {}) {
12
+
13
+ let port = config.port || 20000; // 端口号
14
+ let handler = typeof config.handler == 'function' ? config.handler : function () { return false; };
15
+ let suffixs = Array.isArray(config.suffix) ? config.suffix : [".html", ".htm", ".js", ".json", ".css"];
16
+
17
+ let proxy = [];
18
+ if (config.proxy) {
19
+ for (let item of config.proxy) {
20
+ let target = /(https*):\/\/([^/:]+):*(\d+)*(.*)/.exec(item.target) || [];
21
+ proxy.push({
22
+ test: item.test,
23
+ target: {
24
+ protocol: target[1], // 使用的协议
25
+ hostname: target[2], // 请求发送至的服务器的域名或 IP 地址
26
+ port: target[3] || 80, // 端口号
27
+ path: target[4] || "", // 路径前缀
28
+ }
29
+ });
30
+ }
31
+ }
32
+
33
+ let basePath = path.join(process.cwd(), config.basePath || "./"); // 服务器根路径
34
+
35
+ let index = 0;
36
+ let Server = http.createServer(function (request, response) {
37
+ try {
38
+ let requestData = "";
39
+
40
+ request.on('data', (chunk) => {
41
+ requestData += chunk;
42
+ });
43
+
44
+ request.on('end', () => {
45
+ let url = decodeURIComponent(request.url);
46
+
47
+ log("[" + index++ + "]" + url);
48
+
49
+ // 自定义拦截
50
+ if (handler.call({
51
+ data: requestData,
52
+ base: basePath
53
+ }, request, response)) return;
54
+
55
+ // proxy拦截
56
+ for (let item of proxy) {
57
+ if (item.test.test(url)) {
58
+ let _path = item.target.path + (url.replace(item.test, ""));
59
+
60
+ warn(" ↳ [" + request.method + "] " + item.target.protocol + "://" + item.target.hostname + ":" + item.target.port + _path);
61
+
62
+ // https://www.nodeapp.cn/http.html#http_http_request_options_callback
63
+ const req = http.request({
64
+ hostname: item.target.hostname,
65
+ port: item.target.port,
66
+ path: _path,
67
+ method: request.method,
68
+ headers: request.headers
69
+ }, (res) => {
70
+ res.setEncoding('utf8');
71
+
72
+ let responseData = "";
73
+ res.on('data', (chunk) => {
74
+ responseData += chunk;
75
+ });
76
+ res.on('end', () => {
77
+ let responseHeaders = res.headers;
78
+ responseHeaders['proxy-server'] = "Powered by OIPage@" + jsonfile.version;
79
+ responseHeaders['Access-Control-Allow-Origin'] = '*';
80
+ response.writeHead(res.statusCode, responseHeaders);
81
+ response.write(responseData);
82
+ response.end();
83
+ });
84
+ });
85
+
86
+ req.on('error', (e) => {
87
+ error(` 转发的时候遇到问题: ${e.message}`);
88
+ response.writeHead('500', {
89
+ 'Content-type': "text/plain;charset=utf-8",
90
+ 'Access-Control-Allow-Origin': '*',
91
+ "proxy-server": "Powered by OIPage@" + jsonfile.version
92
+ });
93
+ response.write(e + "");
94
+ response.end();
95
+ });
96
+
97
+ req.write(requestData);
98
+ req.end();
99
+
100
+ return;
101
+ }
102
+ }
103
+
104
+ url = url.split("?")[0];
105
+
106
+ // 请求的文件路径
107
+ let filePath = path.join(basePath, url == "/" ? "index.html" : url.replace(/^\//, ""));
108
+
109
+ let dotName = /\./.test(filePath) ? filePath.match(/\.([^.]+)$/)[1] : "";
110
+
111
+ let is404 = true;
112
+ let doResponse = function (type, filePath) {
113
+ response.writeHead(200, {
114
+ 'Content-type': (type || "text/plain") + ";charset=utf-8",
115
+ 'Access-Control-Allow-Origin': '*',
116
+ 'Server': "Powered by OIPage@" + jsonfile.version
117
+ });
118
+ response.write(fs.readFileSync(filePath));
119
+ is404 = false;
120
+ };
121
+
122
+ // 文件类型
123
+ type = mineTypes[dotName];
124
+ if (fs.existsSync(filePath) && !fs.lstatSync(filePath).isDirectory()) {
125
+ doResponse(type, filePath);
126
+ } else {
127
+ for (let suffix of suffixs) {
128
+ if (fs.existsSync(filePath + suffix) && !fs.lstatSync(filePath + suffix).isDirectory()) {
129
+ type = mineTypes[suffix.replace(/^\./, "")];
130
+ doResponse(type, filePath + suffix);
131
+ }
132
+ }
133
+ }
134
+
135
+ if (is404) {
136
+ response.writeHead(404, {
137
+ 'Content-type': "text/html;charset=utf-8",
138
+ 'Access-Control-Allow-Origin': '*',
139
+ 'Server': "Powered by OIPage@" + jsonfile.version
140
+ });
141
+ response.write(responseFileList(filePath));
142
+ }
143
+
144
+ response.end();
145
+ });
146
+ } catch (e) {
147
+ error(e);
148
+
149
+ response.writeHead(500, {
150
+ 'Content-type': "text/plain;charset=utf-8",
151
+ 'Access-Control-Allow-Origin': '*',
152
+ 'Server': "Powered by OIPage@" + jsonfile.version
153
+ });
154
+ response.write(e + "");
155
+
156
+ response.end();
157
+ }
158
+
159
+ });
160
+
161
+ Server.listen(port);
162
+ log('server running on port:' + port);
163
+ };
@@ -0,0 +1,52 @@
1
+ const img_folder = require("./images/folder");
2
+ const img_file = require("./images/file");
3
+
4
+ module.exports = function (template) {
5
+ return `<!DOCTYPE html>
6
+ <html lang="zh-cn">
7
+
8
+ <head>
9
+ <meta charset="UTF-8">
10
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
11
+ <meta http-equiv="X-UA-Compatible" content="ie=edge">
12
+ <title>404 Not Found</title>
13
+ <style>
14
+ body{
15
+ margin: 0px;
16
+ background-color:#fcfcfc;
17
+ display: flex;
18
+ flex-wrap:wrap;
19
+ padding:100px;
20
+ }
21
+ body>a{
22
+ text-decoration: none;
23
+ padding:10px;
24
+ color:#000000;
25
+ text-align:center;
26
+ width:100px;
27
+ background-repeat: no-repeat;
28
+ background-position: center 0px;
29
+ padding-top: 65px;
30
+ margin-top: 50px;
31
+ font-size:12px;
32
+ }
33
+ body>a:hover{
34
+ outline:1px solid #55b9e6;
35
+ }
36
+ body>a.folder{
37
+ background-image:url('${img_folder}');
38
+ }
39
+ body>a.file{
40
+ background-image:url('${img_file}');
41
+ }
42
+ </style>
43
+ </head>
44
+
45
+ <body>
46
+
47
+ ${template}
48
+
49
+ </body>
50
+
51
+ </html>`;
52
+ };