hfs 0.45.0 → 0.46.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.
Files changed (49) hide show
  1. package/README.md +7 -16
  2. package/admin/assets/index-963c15a5.js +517 -0
  3. package/{frontend/assets/sha512-b32d8af7.js → admin/assets/sha512-471fbd54.js} +2 -2
  4. package/admin/index.html +1 -1
  5. package/frontend/assets/index-c0f02a72.js +94 -0
  6. package/frontend/assets/index-f2f8fd6b.css +1 -0
  7. package/{admin/assets/sha512-c0389c0d.js → frontend/assets/sha512-7dcc825b.js} +2 -2
  8. package/frontend/fontello.css +1 -1
  9. package/frontend/index.html +2 -3
  10. package/package.json +4 -3
  11. package/plugins/download-counter/plugin.js +3 -1
  12. package/plugins/download-counter/public/main.js +3 -2
  13. package/plugins/list-uploader/plugin.js +21 -0
  14. package/plugins/list-uploader/public/main.js +27 -0
  15. package/plugins/vhosting/plugin.js +6 -3
  16. package/src/adminApis.js +9 -3
  17. package/src/api.auth.js +4 -4
  18. package/src/api.file_list.js +4 -5
  19. package/src/api.monitor.js +7 -5
  20. package/src/api.plugins.js +57 -13
  21. package/src/api.vfs.js +2 -1
  22. package/src/apiMiddleware.js +4 -0
  23. package/src/commands.js +11 -0
  24. package/src/config.js +9 -5
  25. package/src/connections.js +1 -1
  26. package/src/const.js +4 -3
  27. package/src/frontEndApis.js +33 -1
  28. package/src/github.js +65 -33
  29. package/src/index.js +4 -4
  30. package/src/lang.js +3 -2
  31. package/src/langs/embedded.js +2 -1
  32. package/src/langs/hfs-lang-fr.json +16 -4
  33. package/src/langs/hfs-lang-it.json +11 -4
  34. package/src/langs/hfs-lang-ru.json +16 -4
  35. package/src/langs/hfs-lang-vi.json +116 -0
  36. package/src/log.js +50 -47
  37. package/src/middlewares.js +10 -3
  38. package/src/plugins.js +181 -98
  39. package/src/serveFile.js +13 -5
  40. package/src/serveGuiFiles.js +8 -10
  41. package/src/throttler.js +11 -6
  42. package/src/update.js +1 -4
  43. package/src/upload.js +26 -9
  44. package/src/util-files.js +25 -6
  45. package/src/vfs.js +6 -12
  46. package/src/zip.js +2 -2
  47. package/admin/assets/index-101f19c9.js +0 -510
  48. package/frontend/assets/index-59572489.css +0 -1
  49. package/frontend/assets/index-5aeff3af.js +0 -94
package/src/commands.js CHANGED
@@ -12,6 +12,7 @@ const listen_1 = require("./listen");
12
12
  const yaml_1 = __importDefault(require("yaml"));
13
13
  const const_1 = require("./const");
14
14
  const readline_1 = require("readline");
15
+ const plugins_1 = require("./plugins");
15
16
  if (!const_1.argv.updating)
16
17
  try {
17
18
  /*
@@ -111,6 +112,8 @@ const commands = {
111
112
  params: '',
112
113
  async cb() {
113
114
  const update = await (0, update_1.getUpdate)();
115
+ if (update.name === const_1.VERSION)
116
+ throw "you already have the latest version: " + const_1.VERSION;
114
117
  console.log("new version available", update.name);
115
118
  }
116
119
  },
@@ -121,4 +124,12 @@ const commands = {
121
124
  console.log(const_1.BUILD_TIMESTAMP);
122
125
  }
123
126
  },
127
+ 'start-plugin': {
128
+ params: '<name>',
129
+ cb: plugins_1.startPlugin,
130
+ },
131
+ 'stop-plugin': {
132
+ params: '<name>',
133
+ cb: plugins_1.stopPlugin,
134
+ },
124
135
  };
package/src/config.js CHANGED
@@ -15,6 +15,7 @@ const fs_1 = require("fs");
15
15
  const path_1 = require("path");
16
16
  const events_2 = __importDefault(require("./events"));
17
17
  const os_1 = require("os");
18
+ const promises_1 = require("fs/promises");
18
19
  const FILE = 'config.yaml';
19
20
  // keep definition of config properties
20
21
  const configProps = {};
@@ -67,7 +68,7 @@ const currentVersion = new Version(const_1.VERSION);
67
68
  const configVersion = defineConfig('version', const_1.VERSION, v => new Version(v));
68
69
  function defineConfig(k, defaultValue, compiler) {
69
70
  configProps[k] = { defaultValue };
70
- let compiled = compiler === null || compiler === void 0 ? void 0 : compiler(defaultValue, undefined, currentVersion);
71
+ let compiled = compiler === null || compiler === void 0 ? void 0 : compiler(defaultValue, { version: currentVersion, defaultValue });
71
72
  const ret = {
72
73
  key() {
73
74
  return k;
@@ -77,7 +78,7 @@ function defineConfig(k, defaultValue, compiler) {
77
78
  },
78
79
  sub(cb) {
79
80
  if (started) // initial event already passed, we'll make the first call
80
- cb(getConfig(k), defaultValue, configVersion.compiled());
81
+ cb(getConfig(k), { was: defaultValue, defaultValue, version: configVersion.compiled() });
81
82
  const eventName = CONFIG_CHANGE_EVENT_PREFIX + k;
82
83
  return (0, misc_1.onOff)(cfgEvents, {
83
84
  [eventName]() {
@@ -183,9 +184,12 @@ function setConfig1(k, newV, saveChanges = true, valueVersion) {
183
184
  const saveDebounced = (0, misc_1.debounceAsync)(async () => {
184
185
  while (!started)
185
186
  await (0, misc_1.wait)(100);
186
- let txt = yaml_1.default.stringify({ ...state, version: const_1.VERSION }, { lineWidth: 1000 });
187
- if (txt.trim() === '{}') // most users wouldn't understand
188
- txt = '';
187
+ // keep backup
188
+ const bak = filePath + '.bak';
189
+ const aWeekAgo = Date.now() - const_1.DAY * 7;
190
+ if (await (0, promises_1.stat)(bak).then(x => aWeekAgo > Number(x.mtime || x.ctime), () => true))
191
+ await (0, promises_1.copyFile)(filePath, bak).catch(() => { }); // ignore errors
192
+ const txt = yaml_1.default.stringify({ ...state, version: const_1.VERSION }, { lineWidth: 1000 });
189
193
  save(filePath, txt)
190
194
  .catch(err => console.error('Failed at saving config file, please ensure it is writable.', String(err)));
191
195
  });
@@ -11,7 +11,7 @@ class Connection {
11
11
  constructor(socket) {
12
12
  this.socket = socket;
13
13
  this.started = new Date();
14
- this.sent = 0;
14
+ this.sent = 0; // socket-scoped, not request-scoped
15
15
  this.got = 0;
16
16
  all.push(this);
17
17
  socket.on('close', () => {
package/src/const.js CHANGED
@@ -27,7 +27,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
27
27
  return (mod && mod.__esModule) ? mod : { "default": mod };
28
28
  };
29
29
  Object.defineProperty(exports, "__esModule", { value: true });
30
- exports.APP_PATH = exports.IS_BINARY = exports.IS_WINDOWS = exports.HTTP_SERVER_ERROR = exports.HTTP_FOOL = exports.HTTP_RANGE_NOT_SATISFIABLE = exports.HTTP_PAYLOAD_TOO_LARGE = exports.HTTP_CONFLICT = exports.HTTP_NOT_ACCEPTABLE = exports.HTTP_METHOD_NOT_ALLOWED = exports.HTTP_NOT_FOUND = exports.HTTP_FORBIDDEN = exports.HTTP_UNAUTHORIZED = exports.HTTP_BAD_REQUEST = exports.HTTP_NOT_MODIFIED = exports.HTTP_TEMPORARY_REDIRECT = exports.HTTP_PARTIAL_CONTENT = exports.HTTP_NO_CONTENT = exports.HTTP_OK = exports.PLUGINS_PUB_URI = exports.API_URI = exports.ADMIN_URI = exports.FRONTEND_URI = exports.SPECIAL_URI = exports.HFS_REPO = exports.COMPATIBLE_API_VERSION = exports.API_VERSION = exports.DAY = exports.VERSION = exports.BUILD_TIMESTAMP = exports.HFS_STARTED = exports.ORIGINAL_CWD = exports.DEV = exports.argv = void 0;
30
+ exports.APP_PATH = exports.IS_BINARY = exports.IS_WINDOWS = exports.HTTP_SERVER_ERROR = exports.HTTP_FAILED_DEPENDENCY = exports.HTTP_FOOL = exports.HTTP_RANGE_NOT_SATISFIABLE = exports.HTTP_PAYLOAD_TOO_LARGE = exports.HTTP_CONFLICT = exports.HTTP_NOT_ACCEPTABLE = exports.HTTP_METHOD_NOT_ALLOWED = exports.HTTP_NOT_FOUND = exports.HTTP_FORBIDDEN = exports.HTTP_UNAUTHORIZED = exports.HTTP_BAD_REQUEST = exports.HTTP_NOT_MODIFIED = exports.HTTP_TEMPORARY_REDIRECT = exports.HTTP_PARTIAL_CONTENT = exports.HTTP_NO_CONTENT = exports.HTTP_OK = exports.PLUGINS_PUB_URI = exports.API_URI = exports.ADMIN_URI = exports.FRONTEND_URI = exports.SPECIAL_URI = exports.HFS_REPO = exports.COMPATIBLE_API_VERSION = exports.API_VERSION = exports.DAY = exports.VERSION = exports.BUILD_TIMESTAMP = exports.HFS_STARTED = exports.ORIGINAL_CWD = exports.DEV = exports.argv = void 0;
31
31
  const minimist_1 = __importDefault(require("minimist"));
32
32
  const fs = __importStar(require("fs"));
33
33
  const os_1 = require("os");
@@ -38,11 +38,11 @@ exports.DEV = process.env.DEV || exports.argv.dev ? 'DEV' : '';
38
38
  exports.ORIGINAL_CWD = process.cwd();
39
39
  exports.HFS_STARTED = new Date();
40
40
  const PKG_PATH = (0, path_1.join)(__dirname, '..', 'package.json');
41
- exports.BUILD_TIMESTAMP = "2023-05-21T10:24:51.009Z";
41
+ exports.BUILD_TIMESTAMP = "2023-07-03T16:52:49.276Z";
42
42
  const pkg = JSON.parse(fs.readFileSync(PKG_PATH, 'utf8'));
43
43
  exports.VERSION = pkg.version;
44
44
  exports.DAY = 86400000;
45
- exports.API_VERSION = 8.1; // entry.uri + script.plugin + absolute frontend_*
45
+ exports.API_VERSION = 8.23;
46
46
  exports.COMPATIBLE_API_VERSION = 1; // while changes in the api are not breaking, this number stays the same, otherwise it is made equal to API_VERSION
47
47
  exports.HFS_REPO = 'rejetto/hfs';
48
48
  exports.SPECIAL_URI = '/~/';
@@ -65,6 +65,7 @@ exports.HTTP_CONFLICT = 409;
65
65
  exports.HTTP_PAYLOAD_TOO_LARGE = 413;
66
66
  exports.HTTP_RANGE_NOT_SATISFIABLE = 416;
67
67
  exports.HTTP_FOOL = 418;
68
+ exports.HTTP_FAILED_DEPENDENCY = 424;
68
69
  exports.HTTP_SERVER_ERROR = 500;
69
70
  exports.IS_WINDOWS = process.platform === 'win32';
70
71
  exports.IS_BINARY = !(0, path_1.basename)(process.execPath).includes('node'); // this won't be node if pkg was used
@@ -38,6 +38,7 @@ const const_1 = require("./const");
38
38
  const vfs_1 = require("./vfs");
39
39
  const promises_1 = require("fs/promises");
40
40
  const path_1 = require("path");
41
+ const upload_1 = require("./upload");
41
42
  exports.customHeader = (0, config_1.defineConfig)('custom_header', '');
42
43
  exports.frontEndApis = {
43
44
  file_list: api_file_list_1.file_list,
@@ -52,6 +53,15 @@ exports.frontEndApis = {
52
53
  }
53
54
  });
54
55
  },
56
+ async get_file_details({ uri }, ctx) {
57
+ apiAssertTypes({ string: { uri } });
58
+ const node = await (0, vfs_1.urlToNode)(uri, ctx);
59
+ if (!node)
60
+ return new apiMiddleware_1.ApiError(const_1.HTTP_NOT_FOUND);
61
+ return {
62
+ upload: node.source && await (0, upload_1.getUploadMeta)(node.source).catch(() => undefined)
63
+ };
64
+ },
55
65
  async create_folder({ uri, name }, ctx) {
56
66
  apiAssertTypes({ string: { uri, name } });
57
67
  if (!(0, util_files_1.isValidFileName)(name) || (0, util_files_1.dirTraversal)(name))
@@ -69,7 +79,7 @@ exports.frontEndApis = {
69
79
  return new apiMiddleware_1.ApiError(e.code === 'EEXIST' ? const_1.HTTP_CONFLICT : const_1.HTTP_BAD_REQUEST, e);
70
80
  }
71
81
  },
72
- async del({ uri }, ctx) {
82
+ async delete({ uri }, ctx) {
73
83
  apiAssertTypes({ string: { uri } });
74
84
  const node = await (0, vfs_1.urlToNode)(uri, ctx);
75
85
  if (!node)
@@ -86,6 +96,28 @@ exports.frontEndApis = {
86
96
  throw new apiMiddleware_1.ApiError(e.code || const_1.HTTP_SERVER_ERROR, e);
87
97
  }
88
98
  },
99
+ async rename({ uri, dest }, ctx) {
100
+ apiAssertTypes({ string: { uri, dest } });
101
+ if (dest.includes('/') || (0, util_files_1.dirTraversal)(dest))
102
+ throw new apiMiddleware_1.ApiError(const_1.HTTP_FORBIDDEN);
103
+ const node = await (0, vfs_1.urlToNode)(uri, ctx);
104
+ if (!node)
105
+ throw new apiMiddleware_1.ApiError(const_1.HTTP_NOT_FOUND);
106
+ if (!(0, vfs_1.hasPermission)(node, 'can_delete', ctx))
107
+ throw new apiMiddleware_1.ApiError(const_1.HTTP_UNAUTHORIZED);
108
+ try {
109
+ if (node.name)
110
+ node.name = dest;
111
+ else if (node.source)
112
+ await (0, promises_1.rename)(node.source, (0, path_1.join)((0, path_1.dirname)(node.source), dest));
113
+ else
114
+ throw new apiMiddleware_1.ApiError(const_1.HTTP_SERVER_ERROR);
115
+ return {};
116
+ }
117
+ catch (e) {
118
+ throw new apiMiddleware_1.ApiError(e.code || const_1.HTTP_SERVER_ERROR, e);
119
+ }
120
+ }
89
121
  };
90
122
  function notifyClient(ctx, name, data) {
91
123
  const { notificationChannel } = ctx.query;
package/src/github.js CHANGED
@@ -11,7 +11,9 @@ const plugins_1 = require("./plugins");
11
11
  const apiMiddleware_1 = require("./apiMiddleware");
12
12
  const lodash_1 = __importDefault(require("lodash"));
13
13
  const const_1 = require("./const");
14
- const DIST_ROOT = 'dist/';
14
+ const promises_1 = require("fs/promises");
15
+ const path_1 = require("path");
16
+ const DIST_ROOT = 'dist';
15
17
  const downloading = {};
16
18
  function downloadProgress(id, status) {
17
19
  if (status === undefined)
@@ -23,25 +25,47 @@ function downloadProgress(id, status) {
23
25
  async function downloadPlugin(repo, branch = '', overwrite) {
24
26
  if (downloading[repo])
25
27
  return new apiMiddleware_1.ApiError(const_1.HTTP_CONFLICT, "already downloading");
28
+ console.log('downloading plugin', repo);
26
29
  downloadProgress(repo, true);
27
- const rec = await getRepoInfo(repo);
28
- if (!branch)
29
- branch = rec.default_branch;
30
- const short = repo.split('/')[1]; // second part, repo without the owner
31
- if (!short)
32
- return new apiMiddleware_1.ApiError(const_1.HTTP_BAD_REQUEST, "bad repo");
33
- const folder2repo = getFolder2repo();
34
- const folder = overwrite ? lodash_1.default.findKey(folder2repo, x => x === repo) // use existing folder
35
- : short in folder2repo ? repo.replace('/', '-') // longer form only if another plugin is using short form
36
- : short;
37
- const installPath = plugins_1.PATH + '/' + folder;
38
- const GITHUB_ZIP_ROOT = short + '-' + branch; // GitHub puts everything within this folder
39
- const rootWithinZip = GITHUB_ZIP_ROOT + '/' + DIST_ROOT;
40
- const stream = await (0, misc_1.httpsStream)(`https://github.com/${repo}/archive/refs/heads/${branch}.zip`);
41
- await (0, misc_1.unzip)(stream, path => path.startsWith(rootWithinZip) && installPath + '/' + path.slice(rootWithinZip.length));
42
- downloadProgress(repo, undefined);
43
- await (0, plugins_1.rescan)(); // workaround: for some reason, operations are not triggering the rescan of the watched folder. Let's invoke it.
44
- return folder;
30
+ try {
31
+ const rec = await getRepoInfo(repo);
32
+ if (!branch)
33
+ branch = rec.default_branch;
34
+ const short = repo.split('/')[1]; // second part, repo without the owner
35
+ if (!short)
36
+ return new apiMiddleware_1.ApiError(const_1.HTTP_BAD_REQUEST, "bad repo");
37
+ const folder2repo = getFolder2repo();
38
+ const folder = overwrite ? lodash_1.default.findKey(folder2repo, x => x === repo) // use existing folder
39
+ : short in folder2repo ? repo.replace('/', '-') // longer form only if another plugin is using short form
40
+ : short;
41
+ const installPath = plugins_1.PATH + '/' + folder;
42
+ const GITHUB_ZIP_ROOT = short + '-' + branch; // GitHub puts everything within this folder
43
+ const rootWithinZip = GITHUB_ZIP_ROOT + '/' + DIST_ROOT;
44
+ const foldersToCopy = [
45
+ rootWithinZip + '-' + process.platform + '-' + process.arch,
46
+ rootWithinZip + '-' + process.platform,
47
+ rootWithinZip,
48
+ ].map(x => x + '/');
49
+ // this zip doesn't have content-length, so we cannot produce progress event
50
+ const stream = await (0, misc_1.httpsStream)(`https://github.com/${repo}/archive/refs/heads/${branch}.zip`);
51
+ const MAIN = 'plugin.js';
52
+ await (0, misc_1.unzip)(stream, async (path) => {
53
+ const folder = foldersToCopy.find(x => path.startsWith(x));
54
+ if (!folder || path.endsWith('/'))
55
+ return false;
56
+ let dest = path.slice(folder.length);
57
+ if (dest === MAIN) // avoid being possibly loaded before the download is complete
58
+ dest += plugins_1.DISABLING_POSTFIX;
59
+ dest = (0, path_1.join)(installPath, dest);
60
+ return (0, promises_1.rm)(dest, { force: true }).then(() => dest, () => false);
61
+ });
62
+ const main = (0, path_1.join)(installPath, MAIN);
63
+ await (0, promises_1.rename)(main + plugins_1.DISABLING_POSTFIX, main); // we are good now, restore name
64
+ return folder;
65
+ }
66
+ finally {
67
+ downloadProgress(repo, undefined);
68
+ }
45
69
  }
46
70
  exports.downloadPlugin = downloadPlugin;
47
71
  function getRepoInfo(id) {
@@ -53,9 +77,10 @@ function readGithubFile(uri) {
53
77
  .then(res => res.body);
54
78
  }
55
79
  exports.readGithubFile = readGithubFile;
56
- async function readOnlinePlugin(repoInfo, branch = '') {
57
- const res = await readGithubFile(`${repoInfo.full_name}/${branch || repoInfo.default_branch}/${DIST_ROOT}plugin.js`);
58
- const pl = (0, plugins_1.parsePluginSource)(repoInfo.full_name, res); // use 'repo' as 'id' client-side
80
+ async function readOnlinePlugin(repo, branch = '') {
81
+ branch || (branch = (await getRepoInfo(repo)).default_branch);
82
+ const res = await readGithubFile(`${repo}/${branch}/${DIST_ROOT}/plugin.js`);
83
+ const pl = (0, plugins_1.parsePluginSource)(repo, res); // use 'repo' as 'id' client-side
59
84
  pl.branch = branch || undefined;
60
85
  return pl;
61
86
  }
@@ -67,15 +92,22 @@ function getFolder2repo() {
67
92
  }
68
93
  exports.getFolder2repo = getFolder2repo;
69
94
  async function apiGithub(uri) {
70
- const res = await (0, misc_1.httpsString)('https://api.github.com/' + uri, {
71
- headers: {
72
- 'User-Agent': 'HFS',
73
- Accept: 'application/vnd.github.v3+json',
74
- }
75
- });
76
- if (!res.ok)
77
- throw res.statusCode;
78
- return JSON.parse(res.body);
95
+ try {
96
+ const res = await (0, misc_1.httpsString)('https://api.github.com/' + uri, {
97
+ headers: {
98
+ 'User-Agent': 'HFS',
99
+ Accept: 'application/vnd.github.v3+json',
100
+ }
101
+ });
102
+ if (!res.ok)
103
+ throw res.statusCode;
104
+ return JSON.parse(res.body);
105
+ }
106
+ catch (e) {
107
+ // https://docs.github.com/en/rest/overview/resources-in-the-rest-api?apiVersion=2022-11-28#rate-limiting
108
+ throw e.message === '403' ? Error('github_quota')
109
+ : e;
110
+ }
79
111
  }
80
112
  async function* searchPlugins(text = '') {
81
113
  var _a, _b;
@@ -85,7 +117,7 @@ async function* searchPlugins(text = '') {
85
117
  const repo = it.full_name;
86
118
  if ((_a = projectInfo === null || projectInfo === void 0 ? void 0 : projectInfo.plugins_blacklist) === null || _a === void 0 ? void 0 : _a.includes(repo))
87
119
  continue;
88
- let pl = await readOnlinePlugin(it);
120
+ let pl = await readOnlinePlugin(repo, it.default_branch);
89
121
  if (!pl.apiRequired)
90
122
  continue; // mandatory field
91
123
  if (pl.badApi) { // we try other branches (starting with 'api')
@@ -106,7 +138,7 @@ async function* searchPlugins(text = '') {
106
138
  Object.assign(pl, {
107
139
  downloading: downloading[repo],
108
140
  license: (_b = it.license) === null || _b === void 0 ? void 0 : _b.spdx_id,
109
- }, lodash_1.default.pick(it, ['pushed_at', 'stargazers_count']));
141
+ }, lodash_1.default.pick(it, ['pushed_at', 'stargazers_count', 'default_branch']));
110
142
  yield pl;
111
143
  }
112
144
  }
package/src/index.js CHANGED
@@ -30,12 +30,12 @@ exports.app = new koa_1.default({ keys });
30
30
  exports.app.use(middlewares_1.someSecurity)
31
31
  .use((0, koa_session_1.default)({ key: 'hfs_$id', signed: true, rolling: true }, exports.app))
32
32
  .use(middlewares_1.prepareState)
33
- .use(middlewares_1.headRequests)
34
- .use((0, log_1.log)())
35
- .use(throttler_1.throttler)
36
33
  .use(middlewares_1.gzipper)
37
34
  .use(middlewares_1.paramsDecoder) // must be done before plugins, so they can manipulate params
38
- .use((0, plugins_1.pluginsMiddleware)())
35
+ .use(plugins_1.pluginsMiddleware)
36
+ .use(middlewares_1.headRequests)
37
+ .use(log_1.logMw)
38
+ .use(throttler_1.throttler)
39
39
  .use((0, koa_mount_1.default)(const_1.API_URI, (0, apiMiddleware_1.apiMiddleware)({ ...frontEndApis_1.frontEndApis, ...adminApis_1.adminApis })))
40
40
  .use(middlewares_1.serveGuiAndSharedFiles)
41
41
  .on('error', errorHandler);
package/src/lang.js CHANGED
@@ -59,11 +59,12 @@ let undo;
59
59
  undo === null || undo === void 0 ? void 0 : undo();
60
60
  if (!v)
61
61
  return forceLangData = undefined;
62
- forceLangData = {}; // necessary to make the embedded language work
62
+ const translation = embedded_1.default[v];
63
+ forceLangData = { [v]: translation };
63
64
  if (v === EMBEDDED_LANGUAGE)
64
65
  return;
65
66
  const res = (0, watchLoad_1.watchLoad)(code2file(v), data => {
66
- forceLangData = { [v]: JSON.parse(data) };
67
+ forceLangData = { [v]: (0, misc_1.tryJson)(data) || translation };
67
68
  });
68
69
  undo = res.unwatch;
69
70
  });
@@ -12,4 +12,5 @@ const hfs_lang_ms_json_1 = __importDefault(require("./hfs-lang-ms.json"));
12
12
  const hfs_lang_zh_tw_json_1 = __importDefault(require("./hfs-lang-zh-tw.json"));
13
13
  const hfs_lang_fr_json_1 = __importDefault(require("./hfs-lang-fr.json"));
14
14
  const hfs_lang_pt_json_1 = __importDefault(require("./hfs-lang-pt.json"));
15
- exports.default = { it: hfs_lang_it_json_1.default, zh: hfs_lang_zh_json_1.default, ru: hfs_lang_ru_json_1.default, sr: hfs_lang_sr_json_1.default, ko: hfs_lang_ko_json_1.default, ms: hfs_lang_ms_json_1.default, 'zh-tw': hfs_lang_zh_tw_json_1.default, fr: hfs_lang_fr_json_1.default, pt: hfs_lang_pt_json_1.default };
15
+ const hfs_lang_vi_json_1 = __importDefault(require("./hfs-lang-vi.json"));
16
+ exports.default = { it: hfs_lang_it_json_1.default, zh: hfs_lang_zh_json_1.default, ru: hfs_lang_ru_json_1.default, sr: hfs_lang_sr_json_1.default, ko: hfs_lang_ko_json_1.default, ms: hfs_lang_ms_json_1.default, 'zh-tw': hfs_lang_zh_tw_json_1.default, fr: hfs_lang_fr_json_1.default, pt: hfs_lang_pt_json_1.default, vi: hfs_lang_vi_json_1.default };
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "author": "Club Microtel Vernon",
3
- "version": 1.1,
4
- "hfs_version": "0.43.0",
3
+ "version": 1.2,
4
+ "hfs_version": "0.46.0 Beta",
5
5
  "translate": {
6
6
  "Select": "Selectionner",
7
7
  "n_files": "{n,plural, one{# fichier} other{# fichiers}}",
@@ -95,12 +95,24 @@
95
95
  "upload_finished": "{n} terminé(s) ({size})",
96
96
  "upload_errors": "{n} échoués",
97
97
  "upload_file_rejected": "Certains fichiers ont été refusés",
98
- "download counter": "Compteur de téléchargements",
98
+ "Download counter": "Compteur de téléchargements",
99
99
  "File menu": "Menu du fichier",
100
100
  "Folder menu": "Menu du dossier",
101
101
  "Name": "Nom",
102
102
  "file_open": "Ouvrir",
103
103
  "Download": "Télécharger",
104
- "Missing permission": "Permission manquante"
104
+ "Missing permission": "Permission manquante",
105
+ "Reload": "Recharger",
106
+ "Get list": "Lister le contenu",
107
+ "Skip existing files": "Ignorer les fichiers éxistants",
108
+ "Size": "Taille",
109
+ "Timestamp": "Horodatage",
110
+ "Show": "Afficher",
111
+ "Loading failed": "Le chargement a échoué",
112
+ "Rename": "Renommer",
113
+ "Tiles mode:": "Mode tuiles:",
114
+ "off": "Désactivé",
115
+ "Operation successful": "Opération réussie",
116
+ "Uploader": "Téléchargeur"
105
117
  }
106
118
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "author": "Massimo Melina",
3
- "version": 1.5,
4
- "hfs_version": "0.43.0",
3
+ "version": 1.6,
4
+ "hfs_version": "0.46.0",
5
5
  "translate": {
6
6
  "Select": "Seleziona",
7
7
  "n_files": "{n} file",
@@ -89,7 +89,6 @@
89
89
  "upload_finished": "{n,plural, one{# riuscito} other{# riusciti}} ({size})",
90
90
  "upload_errors": "{n,plural, one{# fallito} other{# falliti}}",
91
91
  "upload_file_rejected": "Alcuni file non sono stati accettati",
92
- "download counter": "download conteggiati",
93
92
  "File menu": "Menu file",
94
93
  "Folder menu": "Menu cartella",
95
94
  "Name": "Nome",
@@ -100,6 +99,14 @@
100
99
  "Get list": "Lista",
101
100
  "Skip existing files": "Salta file esistenti",
102
101
  "Size": "Dimensione",
103
- "Timestamp": "Data/ora"
102
+ "Timestamp": "Data/ora",
103
+ "Show": "Show",
104
+ "Loading failed": "Caricamento fallito",
105
+ "Rename": "Rinomina",
106
+ "Tiles mode:": "Griglia:",
107
+ "off": "no",
108
+ "Operation successful": "Operazione riuscita",
109
+ "Uploader": "Upload da",
110
+ "Download counter": "Download conteggiati"
104
111
  }
105
112
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "author": "Mefistofell, SanokKule",
3
- "version": 1.92,
4
- "hfs_version": "0.43.0",
3
+ "version": 1.97,
4
+ "hfs_version": "0.46.0",
5
5
  "translate": {
6
6
  "Select": "Выбор",
7
7
  "n_files": "{n,plural, one{1 файл} few{# файла} other{# файлов}}",
@@ -95,12 +95,24 @@
95
95
  "upload_finished": "{n,plural, one{# файл загружен} few{# файла загружено} other{# файлов загружено}} ({size})",
96
96
  "upload_errors": "{n,plural, one{# файл} few{# файла} other{# файлов}} не удалось загрузить",
97
97
  "upload_file_rejected": "Некоторые файлы не были приняты",
98
- "download counter": "Количество скачиваний",
99
98
  "File menu": "Меню файла",
100
99
  "Folder menu": "Меню папки",
101
100
  "Name": "Имя",
102
101
  "file_open": "Открыть",
103
102
  "Download": "Скачать",
104
- "Missing permission": "Отсутствует доступ"
103
+ "Missing permission": "Отсутствует доступ",
104
+ "Reload": "Обновить",
105
+ "Get list": "Получить список",
106
+ "Skip existing files": "Пропуск существующих файлов",
107
+ "Size": "Размер",
108
+ "Timestamp": "Дата",
109
+ "Show": "Просмотр",
110
+ "Loading failed": "Не удалось загрузить",
111
+ "Rename": "Переименовать",
112
+ "Tiles mode:": "Плитки:",
113
+ "off": "выключено",
114
+ "Operation successful": "Операция успешна",
115
+ "Uploader": "Загрузчик",
116
+ "Download counter": "Счётчик загрузок"
105
117
  }
106
118
  }
@@ -0,0 +1,116 @@
1
+ {
2
+ "author": "thenoopy12 (Nguyễn Văn Ngu)",
3
+ "version": 1.16,
4
+ "hfs_version": "0.46_beta8",
5
+ "translate": {
6
+ "Select": "Chọn",
7
+ "n_files": "{n,plural,one{# tập tin} other{# tập tin}}",
8
+ "n_folders": "{n,plural,one{# thư mục} other{# thư mục}}",
9
+ "filter_count": "{n,plural, one{# đã lọc} other{# đã lọc}}",
10
+ "select_count": "{n,plural, one{# đã chọn} other{# đã chọn}}",
11
+ "filter_placeholder": "Nhập vào đây để lọc danh sách bên dưới",
12
+ "Select some files": "Chọn một số tệp",
13
+ "zip_checkboxes": "Sử dụng hộp kiểm để chọn tệp, sau đó bạn có thể sử dụng lại \"Zip\"",
14
+ "zip_tooltip_selected": "Tải xuống các mục đã chọn dưới dạng tệp zip",
15
+ "zip_tooltip_whole": "Tải xuống toàn bộ danh sách (chưa được lọc) dưới dạng một tệp zip. Nếu bạn chọn một số mục, chỉ những phần tử đó sẽ được tải xuống.",
16
+ "zip_confirm_search": "Tải xuống TẤT CẢ kết quả của tìm kiếm này dưới dạng lưu trữ ZIP?",
17
+ "zip_confirm_folder": "Tải xuống TOÀN BỘ thư mục dưới dạng lưu trữ ZIP?",
18
+ "select_tooltip": "Lựa chọn áp dụng cho \"Zip\" và \"Delete\" (nếu có), nhưng bạn cũng có thể lọc danh sách",
19
+ "delete_hint": "Để xóa, trước tiên hãy nhấp vào \"Chọn\"",
20
+ "delete_confirm": "Xoá {n,plural, one{# cái} other{# cái}}?",
21
+ "delete_completed": "Xóa: {n} thành công",
22
+ "delete_failed": ", {n,plural, one{# thất bại} other{# thất bại}}",
23
+ "delete_select": "Chọn gì đó để xóa",
24
+ "Delete": "Xoá",
25
+ "Options": "Tùy chọn",
26
+ "Search": "Tìm kiếm",
27
+ "Zip": "Zip",
28
+ "search_msg": "Tìm kiếm folder này và các sub-folders",
29
+ "Searching": "Đang tìm",
30
+ "Searched": "Đã tìm xong!",
31
+ "Clear search": "Xoá tìm kiếm",
32
+ "Interrupted": "Tiến trình bị gián đoạn! (nôm na có thể bị huỷ bởi người dùng)",
33
+ "stopped_before": "Đã dừng",
34
+ "empty_list": "Trống",
35
+ "filter_none": "Ko có gì để lọc",
36
+ "Admin-panel": "AdminPanel",
37
+ "Login": "Đăng nhập nàh",
38
+ "Username": "Username",
39
+ "Password": "Pass",
40
+ "login_untrusted": "Đăng nhập bị hủy bỏ: danh tính máy chủ không tin cậy",
41
+ "login_bad_credentials": "Thông tin đăng nhập SAI",
42
+ "login_bad_cookies": "Cookies không hoạt động - đăng nhập không thành công",
43
+ "User panel": "UserPanel",
44
+ "Change password": "Đổi pass",
45
+ "enter_pass": "Nhập pass mới",
46
+ "enter_pass2": "NHẬP LẠI pass mới (giống nha)",
47
+ "pass2_mismatch": "Mật khẩu ghi lại SAI, tiến trình bị hủy bỏ.",
48
+ "password_changed": "Đã đổi pass thành công",
49
+ "Logout": "Đăng xuất nàh",
50
+ "connection error": "Lỗi kết nối",
51
+ "Full timestamp:": "Dấu thời gian đầy đủ:",
52
+ "Search was interrupted": "Tìm kiếm bị gián đoạn!",
53
+ "Stop list": "DỪNG",
54
+ "upload_starting": "Tải lên bây giờ sẽ bắt đầu",
55
+ "wrong_account": "Tài khoản {u} ko có quyền, chọn tk khác đi:)",
56
+ "no_upload_here": "Không có quyền tải lên cho thư mục hiện tại",
57
+ "Create folder": "Tạo thư mục",
58
+ "Pick files": "Chọn files",
59
+ "Pick folder": "Chọn folders",
60
+ "send_files": "Gửi {n,plural,one{# file} other{# file}}, {size}",
61
+ "Clear": "Dẹp",
62
+ "failed_upload": "KO thể up {name}",
63
+ "confirm_resume": "Típ tục up?",
64
+ "file too large": "File NẶNG VÃI LOK",
65
+ "Enter folder name": "Nhập tên folder",
66
+ "Successfully created": "Tạo folder thành công!",
67
+ "enter_folder": "Nhập thư mục",
68
+ "folder_exists": "Thư mục này trùng tên!",
69
+ "Sort by": "Sắp xếp theo",
70
+ "name": "Tên",
71
+ "extension": "phần mở rộng",
72
+ "size": "Kíck cỡ",
73
+ "time": "Thời gian",
74
+ "Invert order": "Đảo ngược thứ tự",
75
+ "Folders first": "Thư mục trước",
76
+ "Numeric names": "Tên số",
77
+ "theme:": "Màu:",
78
+ "auto": "Auto",
79
+ "light": "SÁNG NHƯ CỦA ĐẢNG",
80
+ "dark": "Dark bủh lmao",
81
+ "parent folder": "Trở về thư mục trước",
82
+ "home": "Trang chủ",
83
+ "Continue": "Tiếp tục",
84
+ "Confirm": "Xác nhận",
85
+ "Don't": "Không",
86
+ "Warning": "Cảnh báo",
87
+ "Error": "Lỗi",
88
+ "Info": "Thông tin",
89
+ "Unauthorized": "Ko có quyền",
90
+ "Forbidden": "Thư mục / file này CẤM vào",
91
+ "Not found": "KO thấy",
92
+ "Server error": "Lỗi vật lý (server)",
93
+ "Upload": "Up",
94
+ "upload_concluded": "Tải lên đã kết thúc:",
95
+ "upload_finished": "{n} thành công ({size})",
96
+ "upload_errors": "{n} thất bại",
97
+ "upload_file_rejected": "Một số file server không chấp nhận",
98
+ "download counter": "bộ đếm tải xuống",
99
+ "File menu": "File menu",
100
+ "Folder menu": "Folder menu",
101
+ "Name": "Tên",
102
+ "file_open": "Mở (ngay trình duyệt, ms ko hỗ trợ)",
103
+ "Download": "Tải về",
104
+ "Missing permission": "Thiếu quyền!",
105
+ "Reload": "Tải lại",
106
+ "Get list": "Lấy danh sách",
107
+ "Skip existing files": "Bỏ qua các tệp hiện có",
108
+ "Size": "Size",
109
+ "Timestamp": "Mốc thời gian",
110
+ "Show": "Hiện",
111
+ "Loading failed": "Load ko thành công",
112
+ "Rename": "Đổi tên",
113
+ "Operation successful": "Tiến trình thành công",
114
+ "Uploader": "Người đã upload"
115
+ }
116
+ }