hfs 0.43.0 → 0.44.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 (46) hide show
  1. package/README.md +50 -9
  2. package/admin/assets/index-35f6e3dc.css +1 -0
  3. package/admin/assets/index-ef68a7ab.js +510 -0
  4. package/{frontend/assets/sha512-8ebf6e2a.js → admin/assets/sha512-d091720e.js} +1 -1
  5. package/admin/index.html +2 -2
  6. package/frontend/assets/index-a3b0d6ac.js +94 -0
  7. package/frontend/assets/index-fe0f3d77.css +1 -0
  8. package/{admin/assets/sha512-55ff2fa3.js → frontend/assets/sha512-96fd938f.js} +1 -1
  9. package/frontend/index.html +3 -2
  10. package/package.json +2 -2
  11. package/plugins/download-counter/plugin.js +5 -3
  12. package/plugins/download-counter/public/main.js +2 -2
  13. package/plugins/vhosting/plugin.js +17 -11
  14. package/src/adminApis.js +1 -1
  15. package/src/api.auth.js +4 -1
  16. package/src/api.file_list.js +7 -8
  17. package/src/api.lang.js +2 -1
  18. package/src/api.vfs.js +41 -28
  19. package/src/apiMiddleware.js +3 -2
  20. package/src/const.js +4 -3
  21. package/src/customHtml.js +1 -1
  22. package/src/debounceAsync.js +3 -3
  23. package/src/frontEndApis.js +6 -6
  24. package/src/github.js +26 -8
  25. package/src/index.js +2 -1
  26. package/src/lang.js +9 -19
  27. package/src/langs/embedded.js +13 -0
  28. package/src/langs/hfs-lang-ms.json +70 -0
  29. package/src/langs/hfs-lang-ru.json +22 -22
  30. package/src/langs/hfs-lang-zh-tw.json +106 -0
  31. package/src/listen.js +1 -1
  32. package/src/log.js +6 -2
  33. package/src/middlewares.js +14 -18
  34. package/src/misc.js +7 -3
  35. package/src/plugins.js +6 -6
  36. package/src/serveFile.js +1 -1
  37. package/src/serveGuiFiles.js +2 -2
  38. package/src/update.js +1 -2
  39. package/src/upload.js +6 -6
  40. package/src/util-http.js +5 -3
  41. package/src/vfs.js +100 -52
  42. package/src/zip.js +4 -4
  43. package/admin/assets/index-5cd667a5.js +0 -511
  44. package/admin/assets/index-8ff39373.css +0 -1
  45. package/frontend/assets/index-27488fde.js +0 -94
  46. package/frontend/assets/index-54a5c76f.css +0 -1
@@ -0,0 +1 @@
1
+ @charset "UTF-8";:root{height:100dvh;--bg: #fff;--text: #555;--ghost-contrast: #8882;--faint-contrast: #8884;--mild-contrast: #8886;--good-contrast: #000a;--button-bg: #68a;--button-text: #eaeaea;--focus-color: #468;--separator: " – "}:root .highlightedText,:root .file-menu a:hover{color:#0006;text-shadow:0 0 3px rgba(0,0,0,.4)}:root .theme-dark{--bg: #000;--text: #999;--good-contrast: #fffa;--button-bg: #345;--button-text: #999;color-scheme:dark}:root .theme-dark .highlightedText,:root .theme-dark .file-menu a:hover,:root .file-menu .theme-dark a:hover{color:#fff;text-shadow:0 0 3px #fff}:root .theme-dark a{color:#8ac}:root .theme-dark .dialog-closer{background:#633}:root .theme-dark .dialog-icon{color:#ccc}:root .theme-dark .dialog-icon .icon{color:#aaa;margin-left:-1px;font-size:95%}:root .theme-dark .dialog-backdrop{background:rgba(51,51,51,.7333333333)}:root .theme-dark .error-msg{color:#b88;background-color:#623}:root .theme-dark button.toggled{color:#eee}body{background-color:var(--bg);margin:0;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}body,button,select,input{font-size:12pt}#root{max-width:50em;margin:auto;min-height:100vh;display:flex;flex-direction:column}body,input{color:var(--text)}code{font-family:source-code-pro,Menlo,Monaco,Consolas,Courier New,monospace}input:not([type=checkbox]),select{padding:.3em .4em;border-radius:.5em;background:var(--bg);border-color:var(--mild-contrast);color:var(--good-contrast);max-width:100%;box-sizing:border-box;width:100%}input[type=checkbox]{transform:scale(1.7);accent-color:var(--button-bg)}label input[type=checkbox]{margin-right:.8em}select{text-align:center}.hidden{display:none!important}.icon{font-size:1.2em}.emoji-icon,.file-icon{display:inline-block;width:1.4em;text-align:center}.file-icon{height:1em;background-size:contain;background-repeat:no-repeat;background-position:center;vertical-align:text-bottom}.icon.mirror:before{transform:scaleX(-1)}a{text-decoration:none;color:#57a}button{background-color:var(--button-bg);color:var(--button-text);padding:.5em 1em;border:transparent;text-decoration:none;border-radius:.3em;vertical-align:middle;cursor:pointer}button:hover{outline:1px solid var(--mild-contrast)}button.toggled{color:#fff;text-shadow:0 0 3px #fff}button:focus-visible,.breadcrumb:focus-visible{outline:3px solid var(--focus-color)}a>button{width:100%}input:focus-visible,select:focus-visible,ul a:focus-visible{border-radius:.3em;border-color:transparent;outline:2px solid var(--focus-color)}.icon-button,ul.dir li .entry-panel .file-menu-button{font-size:.7em;padding:.2em .4em;margin-left:.4em;vertical-align:bottom}.error-msg{background-color:#faa;color:#833;padding:.5em 1em}.hide-back,.upload-toolbar,header{background-color:var(--bg)}header{position:sticky;top:0;padding:.2em;z-index:1}.before-sliding{width:0!important;flex:0!important;margin:0!important;height:0!important;padding:0!important;overflow:hidden!important;transition:flex .5s}.show-sliding{transition:flex .5s;overflow:clip;flex:1;white-space:nowrap}.ani-working{animation:1s blink infinite}@keyframes blink{0%{opacity:1}50%{opacity:.2}}@keyframes spin{to{transform:rotate(360deg)}}@keyframes fade-in{0%{opacity:0}to{opacity:1}}.spinner,.icon.spinner:before{animation:1.5s spin infinite linear;display:inline-flex;justify-content:center;align-items:center;width:min-content}.icon.emoji.spinner{display:inline-block}.breadcrumb{padding:.1em .6em .2em;line-height:1.8em;border-radius:.7em;background-color:var(--button-bg);color:var(--button-text);border-top:1px solid #666;margin-right:-.1em}.breadcrumb:nth-child(-n+3) .icon{padding:0 .2em}#folder-stats,#filter-bar>span{font-size:90%}#folder-stats{margin:.5em 0 0 .5em;float:right}#folder-stats .icon{margin-right:.3em}#filter{flex:1;box-sizing:border-box}#filter-bar{display:flex;align-items:center;gap:.8em;margin:.2em 0 0;padding:2px 0 1px 11px;height:1.8em}#filter-bar input[type=checkbox]{margin-top:.5em}#filter-bar span:empty{display:none}ul.dir{flex:1;padding:0;margin:0;clear:both}ul.dir>p{text-align:center}ul.dir li{display:block;list-style-type:none;padding:.3em .3em .4em;border-bottom:1px solid var(--faint-contrast)}ul.dir li:nth-of-type(odd){background-color:var(--ghost-contrast)}ul.dir li input[type=checkbox]{margin:0 .8em}ul.dir li a:last-of-type{word-break:break-word;padding-right:.3em}ul.dir li a .icon{margin-right:.3em}ul.dir li a:hover{text-decoration:underline}ul.dir li .entry-panel{float:right;margin-top:.3em;display:flex;align-items:center}ul.dir li .entry-panel .file-menu-button{margin:-3px 0 -3px .4em}ul.dir li .entry-panel .entry-details{font-size:90%;margin-left:4px;font-variant-numeric:tabular-nums}ul.dir li .entry-panel .entry-details .entry-size-unit{margin-left:.3em}ul.dir li>div:last-of-type{clear:both}ul.dir li.page-separator{margin-top:1em;position:relative}ul.dir li.page-separator:before{content:attr(label);position:absolute;top:-1.8em;font-size:smaller;margin-left:calc(50% - 1em);opacity:.9}#menu-bar{display:flex;justify-content:space-evenly;flex-wrap:wrap}#menu-bar>*{flex:1;margin:.1em}#menu-bar button{padding-left:0;padding-right:0}#searched{margin:.2em}#user-panel{display:flex;flex-direction:column;gap:1em}#user-panel a>button{width:100%}button label{cursor:inherit;margin-left:.5em}.dialog-backdrop.working{font-size:5em;animation:1s fade-in}.dialog-content{padding:.2em}.dialog-alert .dialog-content{text-align:center}.dialog-alert .dialog-content p{text-align:left;display:inline-block}.dialog{min-width:11em;--color: var(--button-bg)}#paging{position:sticky;bottom:0;display:flex;gap:.1em;background-color:var(--bg);padding:0 .2em .2em}#paging>button{z-index:1}#paging button{box-shadow:0 0 .3em .3em #0003}#paging #paging-middle{padding:0 .5em;margin:0 -.3em;display:flex;gap:.5em;flex:1;overflow-x:auto}#paging #paging-middle>button{flex:1;padding-top:0;padding-bottom:0}#paging button{background:var(--button-bg);text-align:center;white-space:nowrap;padding:.5em}.upload-toolbar{position:sticky;top:-4px}.upload-progress:before{content:var(--separator)}.entry-size:after{content:var(--separator)}.upload-progress{min-width:4em;display:inline-block;margin-left:.5em}.upload-list td:nth-child(1){width:0}.upload-list td:nth-child(2){text-align:right;width:0;white-space:nowrap;padding-left:.5em}.upload-list td:nth-child(3){padding:.2em .5em;word-break:break-word}.dialog-login form{display:flex;flex-direction:column;gap:1.2em}.dialog-login label{display:block;margin-bottom:.5em;margin-left:.1em}.miss-perm{margin:.3em}.popup-menu-button{font-size:.8em;padding:.2em .3em;position:absolute;opacity:.8}.popup-menu-button:hover{opacity:1}.file-dialog .dialog-content{min-width:calc(100% - 1em)}.file-dialog .dialog{min-width:13em}.file-dialog-properties{word-break:break-word;line-height:1.5em}.file-dialog-properties dt{font-weight:700}.file-dialog-properties dd{margin-left:1.5em}.file-menu{margin-top:1em;display:flex;flex-direction:column}.file-menu a{padding:.5em 0}.file-menu a:first-child{padding-top:1em;border-top:1px solid var(--faint-contrast)}.file-menu a .icon{margin-right:.5em}@media (min-width: 42em){body{scrollbar-width:thin;scrollbar-color:var(--button-bg) var(--ghost-contrast)}body::-webkit-scrollbar{width:12px}body::-webkit-scrollbar-track{background:var(--ghost-contrast)}body::-webkit-scrollbar-thumb{background-color:var(--button-bg);border-radius:20px;border:1px solid var(--ghost-contrast)}}@media (max-width: 42em){:root{--ghost-contrast: #8883}body,button,select{font-size:14pt}#menu-bar button label,#filter-bar button label{display:none}#filter-bar{margin-top:.4em}#filter-bar button{width:17.6vw;height:2.3em}.breadcrumb{word-break:break-all}.breadcrumb .icon{font-size:24px}}.dialog-backdrop{position:fixed;inset:0;background:#8886;backdrop-filter:blur(2px);display:flex;justify-content:center;align-items:center;z-index:1000}.dialog{background:#fff;background:var(--bg);padding:max(.5em,1vw);border-radius:1em;position:relative;margin:0 3vw;overflow:hidden;max-height:calc(100vh - 2em);display:flex;flex-direction:column;justify-content:center}.dialog-icon{color:#fff;background-color:var(--color);position:absolute;top:0;width:2em;height:1.8em;text-align:center;border-radius:.8em 0}.dialog-title{font-size:120%;margin-top:-.4em;padding:0 .5em}.dialog-closer~.dialog-title{margin-right:2em}.dialog-type~.dialog-title{margin-left:2em}.dialog-icon~.dialog-title{text-align:center}.dialog-closer{border-radius:0 .8em;right:0;padding:0;background-color:#c88}.dialog-icon~.dialog-content{margin-top:2em}.dialog-type{left:0;top:0;overflow:hidden;line-height:1.8em;opacity:.8}.dialog-content{overflow:auto;max-height:calc(100vh - 4.5em)}.dialog-content p{white-space:pre-wrap;margin:.5em 0}.dialog-confirm .dialog-content button{margin-top:1em}.dialog-alert-info{--color: #282 }.dialog-alert-warning{--color: #c91 }.dialog-alert-error{--color: #822}@media (max-width: 42em){.dialog-icon{font-size:120%}.dialog-icon~.dialog-content{margin-top:2.5em}.dialog-title{margin-top:-.2em}}.dialog-prompt label{display:block;margin-bottom:.5em;margin-left:.1em}
@@ -1,4 +1,4 @@
1
- import{c as SF}from"./index-5cd667a5.js";function OF(iF,hF){for(var eF=0;eF<hF.length;eF++){const tF=hF[eF];if(typeof tF!="string"&&!Array.isArray(tF)){for(const w in tF)if(w!=="default"&&!(w in iF)){const lF=Object.getOwnPropertyDescriptor(tF,w);lF&&Object.defineProperty(iF,w,lF.get?lF:{enumerable:!0,get:()=>tF[w]})}}}return Object.freeze(Object.defineProperty(iF,Symbol.toStringTag,{value:"Module"}))}var EF={},UF={get exports(){return EF},set exports(iF){EF=iF}};/*
1
+ import{c as SF}from"./index-a3b0d6ac.js";function OF(iF,hF){for(var eF=0;eF<hF.length;eF++){const tF=hF[eF];if(typeof tF!="string"&&!Array.isArray(tF)){for(const w in tF)if(w!=="default"&&!(w in iF)){const lF=Object.getOwnPropertyDescriptor(tF,w);lF&&Object.defineProperty(iF,w,lF.get?lF:{enumerable:!0,get:()=>tF[w]})}}}return Object.freeze(Object.defineProperty(iF,Symbol.toStringTag,{value:"Module"}))}var EF={},UF={get exports(){return EF},set exports(iF){EF=iF}};/*
2
2
  * [js-sha512]{@link https://github.com/emn178/js-sha512}
3
3
  *
4
4
  * @version 0.8.0
@@ -5,8 +5,9 @@
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=0" />
6
6
  <link href="/fontello.css" rel="stylesheet" />
7
7
 
8
- <script type="module" crossorigin src="/assets/index-27488fde.js"></script>
9
- <link rel="stylesheet" href="/assets/index-54a5c76f.css">
8
+ <script>HFS={}</script>
9
+ <script type="module" crossorigin src="/assets/index-a3b0d6ac.js"></script>
10
+ <link rel="stylesheet" href="/assets/index-fe0f3d77.css">
10
11
  </head>
11
12
  <body>
12
13
  <noscript>You need to enable JavaScript to run this app.</noscript>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hfs",
3
- "version": "0.43.0",
3
+ "version": "0.44.0",
4
4
  "description": "HTTP File Server",
5
5
  "keywords": [
6
6
  "file server",
@@ -20,7 +20,7 @@
20
20
  "build-frontend": "npm run build --workspace=frontend",
21
21
  "build-admin": "npm run build --workspace=admin",
22
22
  "server-for-test": "node dist/src --cwd . --config tests && rm custom.html",
23
- "server-for-test-dev": "cross-env DEV=1 nodemon --ignore tests/ --watch src -e ts,tsx --exec ts-node src -- --cwd . --config tests",
23
+ "server-for-test-dev": "cross-env DEV=1 FRONTEND_PROXY=3005 ADMIN_PROXY=3006 nodemon --ignore tests/ --watch src -e ts,tsx --exec ts-node src -- --cwd . --config tests",
24
24
  "test": "mocha -r ts-node/register 'tests/**/*.ts'",
25
25
  "pub": "cd dist && npm publish",
26
26
  "dist": "npm run build-all && npm run dist-bin",
@@ -3,7 +3,9 @@ exports.version = 4 // config.where
3
3
  exports.apiRequired = 8
4
4
 
5
5
  exports.config = {
6
- where: { frontend: true, type: 'select', options: ['list', 'menu'] }
6
+ where: { frontend: true, type: 'select', defaultValue: 'menu',
7
+ options: ['list', { value: 'menu', label: "file menu" }],
8
+ }
7
9
  }
8
10
  exports.configDialog = {
9
11
  sx: { maxWidth: '20em' },
@@ -45,8 +47,8 @@ exports.init = async api => {
45
47
  counters[path] = counters[path] + 1 || 1
46
48
  save()
47
49
  },
48
- onDirEntry: ({ entry, listPath }) => {
49
- const path = listPath + entry.n
50
+ onDirEntry: ({ entry, listUri }) => {
51
+ const path = listUri + entry.n
50
52
  const n = counters[path]
51
53
  if (n)
52
54
  entry.hits = n
@@ -2,8 +2,8 @@
2
2
  const config = HFS.getPluginConfig()
3
3
 
4
4
  const inMenu = config.where === 'menu'
5
- HFS.onEvent('additionalEntryProps', ({ entry: { hits } }, { t }) =>
6
- hits && !inMenu && `<span class="download-counter" title="${t`download counter`}">${hits}</span>`)
5
+ HFS.onEvent('additionalEntryDetails', ({ entry: { hits } }) =>
6
+ hits && !inMenu && `<span class="download-counter" title="${HFS.t`download counter`}">${hits}</span>`)
7
7
 
8
8
  HFS.onEvent('fileMenu', ({ entry, props }) => {
9
9
  if (inMenu && !entry.isFolder)
@@ -21,26 +21,32 @@ exports.init = api => {
21
21
  const { matches } = api.require('./misc')
22
22
  return {
23
23
  middleware(ctx) {
24
- let toModify = ctx
24
+ let params // undefined if we are not going to work on api parameters
25
25
  if (ctx.path.startsWith(api.const.SPECIAL_URI)) { // special uris should be excluded...
26
26
  // ...unless it's a frontend api with a path param
27
- if (!ctx.path.startsWith(api.const.API_URI) || ctx.params.path === undefined) return
27
+ if (!ctx.path.startsWith(api.const.API_URI)) return
28
28
  let { referer } = ctx.headers
29
29
  referer &&= new URL(referer).pathname
30
- if (referer?.startsWith(ctx.state.revProxyPath + api.const.ADMIN_URI)) return
31
- toModify = ctx.params
30
+ if (referer?.startsWith(ctx.state.revProxyPath + api.const.ADMIN_URI)) return // exclude apis for admin-panel
31
+ params = ctx.params
32
32
  }
33
+
33
34
  const hosts = api.getConfig('hosts')
34
35
  if (!hosts?.length) return
35
- for (const row of hosts)
36
- if (matches(ctx.host, row.host)) {
37
- toModify.path = row.root + toModify.path
38
- return
36
+ const row = hosts?.find(x => matches(ctx.host, x.host))
37
+ if (!row) {
38
+ if (api.getConfig('mandatory')) {
39
+ ctx.socket.destroy()
40
+ return true
39
41
  }
40
- if (api.getConfig('mandatory')) {
41
- ctx.socket.destroy()
42
- return true
42
+ return
43
43
  }
44
+ if (!params)
45
+ ctx.path = row.root + ctx.path
46
+ else
47
+ for (const [k,v] of Object.entries(params))
48
+ if (k.startsWith('uri'))
49
+ params[k] = row.root + v
44
50
  }
45
51
  }
46
52
  }
package/src/adminApis.js CHANGED
@@ -171,7 +171,7 @@ exports.adminNet = (0, config_1.defineConfig)('admin_net', '', v => (0, misc_1.m
171
171
  exports.favicon = (0, config_1.defineConfig)('favicon', '');
172
172
  exports.title = (0, config_1.defineConfig)('title', "File server");
173
173
  function ctxAdminAccess(ctx) {
174
- return !ctx.state.proxiedFor // we consider localhost_admin only if no proxy is detected
174
+ return !ctx.ips.length // we consider localhost_admin only if no proxy is being usedø
175
175
  && exports.localhostAdmin.get() && (0, misc_1.isLocalHost)(ctx)
176
176
  || (0, perm_1.getFromAccount)(ctx.state.account, a => a.admin);
177
177
  }
package/src/api.auth.js CHANGED
@@ -11,8 +11,10 @@ const misc_1 = require("./misc");
11
11
  const api_helpers_1 = require("./api.helpers");
12
12
  const adminApis_1 = require("./adminApis");
13
13
  const middlewares_1 = require("./middlewares");
14
+ const config_1 = require("./config");
14
15
  const srp6aNimbusRoutines = new tssrp6a_1.SRPRoutines(new tssrp6a_1.SRPParameters());
15
16
  const ongoingLogins = {}; // store data that doesn't fit session object
17
+ const keepSessionAlive = (0, config_1.defineConfig)('keep_session_alive', true);
16
18
  // centralized log-in state
17
19
  async function loggedIn(ctx, username) {
18
20
  const s = ctx.session;
@@ -29,7 +31,8 @@ async function loggedIn(ctx, username) {
29
31
  ctx.cookies.set('csrf', (0, misc_1.randomId)(), { signed: false, httpOnly: false });
30
32
  }
31
33
  function makeExp() {
32
- return { exp: new Date(Date.now() + const_1.SESSION_DURATION) };
34
+ return !keepSessionAlive.get() ? undefined
35
+ : { exp: new Date(Date.now() + const_1.SESSION_DURATION) };
33
36
  }
34
37
  const login = async ({ username, password }, ctx) => {
35
38
  if (!username || !password) // some validation
@@ -12,19 +12,18 @@ const plugins_1 = require("./plugins");
12
12
  const misc_1 = require("./misc");
13
13
  const lodash_1 = __importDefault(require("lodash"));
14
14
  const const_1 = require("./const");
15
- const file_list = async ({ path, offset, limit, search, omit, sse }, ctx) => {
16
- let node = await (0, vfs_1.urlToNode)(path || '/', ctx);
15
+ const file_list = async ({ uri, offset, limit, search, omit, sse }, ctx) => {
16
+ const node = await (0, vfs_1.urlToNode)(uri || '/', ctx);
17
17
  const list = new apiMiddleware_1.SendListReadable();
18
18
  if (!node)
19
19
  return fail(const_1.HTTP_NOT_FOUND);
20
- const res = (0, vfs_1.statusCodeForMissingPerm)(node, 'can_list', ctx);
21
- if (res)
22
- return fail(res);
20
+ if ((0, vfs_1.statusCodeForMissingPerm)(node, 'can_list', ctx))
21
+ return fail();
23
22
  if ((0, misc_1.dirTraversal)(search))
24
23
  return fail(const_1.HTTP_FOOL);
25
24
  if (node.default)
26
25
  return (sse ? list.custom : lodash_1.default.identity)({
27
- redirect: path // tell the browser to access the folder (instead of using this api), so it will get the default file
26
+ redirect: uri // tell the browser to access the folder (instead of using this api), so it will get the default file
28
27
  });
29
28
  if (!await (0, vfs_1.nodeIsDirectory)(node))
30
29
  return fail(const_1.HTTP_METHOD_NOT_ALLOWED);
@@ -46,7 +45,7 @@ const file_list = async ({ path, offset, limit, search, omit, sse }, ctx) => {
46
45
  list.close();
47
46
  });
48
47
  return list;
49
- function fail(code) {
48
+ function fail(code = ctx.status) {
50
49
  if (!sse)
51
50
  return new apiMiddleware_1.ApiError(code);
52
51
  list.error(code, true);
@@ -61,7 +60,7 @@ const file_list = async ({ path, offset, limit, search, omit, sse }, ctx) => {
61
60
  const entry = await nodeToDirEntry(ctx, sub);
62
61
  if (!entry)
63
62
  continue;
64
- const cbParams = { entry, ctx, listPath: path, node: sub };
63
+ const cbParams = { entry, ctx, listUri: uri, node: sub };
65
64
  try {
66
65
  if (onDirEntryHandlers.some(cb => cb(cbParams) === false))
67
66
  continue;
package/src/api.lang.js CHANGED
@@ -11,6 +11,7 @@ const promises_1 = require("fs/promises");
11
11
  const const_1 = require("./const");
12
12
  const misc_1 = require("./misc");
13
13
  const lang_1 = require("./lang");
14
+ const embedded_1 = __importDefault(require("./langs/embedded"));
14
15
  const apis = {
15
16
  list_langs() {
16
17
  return new apiMiddleware_1.SendListReadable({
@@ -24,7 +25,7 @@ const apis = {
24
25
  }
25
26
  catch (_a) { }
26
27
  }
27
- for (const [code, data] of Object.entries(lang_1.EMBEDDED_TRANSLATIONS))
28
+ for (const [code, data] of Object.entries(embedded_1.default))
28
29
  list.add({ code, embedded: true, ...lodash_1.default.omit(data, 'translate') });
29
30
  list.close();
30
31
  }
package/src/api.vfs.js CHANGED
@@ -20,37 +20,45 @@ async function urlToNodeOriginal(uri) {
20
20
  const apis = {
21
21
  async get_vfs() {
22
22
  return {
23
- root: vfs_1.vfs && await recur(vfs_1.vfs),
23
+ root: await recur(),
24
24
  defaultPerms: vfs_1.defaultPerms,
25
25
  };
26
- async function recur(node) {
26
+ async function recur(node = vfs_1.vfs) {
27
27
  var _a;
28
- const stats = Boolean(node.source) && await (0, promises_1.stat)(node.source).catch(e => false);
29
- const isDir = !node.source || stats && stats.isDirectory();
28
+ const { source } = node;
29
+ const stats = Boolean(source) && await (0, promises_1.stat)(source).catch(() => false);
30
+ const isDir = !source || stats && stats.isDirectory();
30
31
  const copyStats = stats ? lodash_1.default.pick(stats, ['size', 'ctime', 'mtime'])
31
- : { size: node.source ? -1 : undefined };
32
+ : { size: source ? -1 : undefined };
32
33
  if (copyStats.mtime && Number(copyStats.mtime) === Number(copyStats.ctime))
33
34
  delete copyStats.mtime;
34
- const isRoot = node === vfs_1.vfs;
35
+ let byMasks = node.original && lodash_1.default.pickBy(node, (v, k) => v !== node.original[k] // something is changing me...
36
+ && v !== node.parent[k] // ...and it's not inheritance...
37
+ && vfs_1.PERM_KEYS.includes(k)); // ...must be masks. Please limit this to perms
38
+ if (lodash_1.default.isEmpty(byMasks))
39
+ byMasks = undefined;
35
40
  return {
36
41
  ...copyStats,
37
- ...node,
42
+ ...node.original || node,
43
+ byMasks,
38
44
  website: Boolean((_a = node.children) === null || _a === void 0 ? void 0 : _a.find((0, vfs_1.isSameFilenameAs)('index.html')))
39
- || isDir && node.source && await (0, promises_1.stat)((0, path_1.join)(node.source, 'index.html')).then(() => true, () => undefined)
45
+ || isDir && source && await (0, promises_1.stat)((0, path_1.join)(source, 'index.html')).then(() => true, () => undefined)
40
46
  || undefined,
41
- name: isRoot ? undefined : (0, vfs_1.getNodeName)(node),
47
+ name: node === vfs_1.vfs ? undefined : (0, vfs_1.getNodeName)(node),
42
48
  type: isDir ? 'folder' : undefined,
43
- children: node.children && await Promise.all(node.children.map(recur)),
49
+ children: node.children && await Promise.all(node.children.map(async (original) => recur((0, vfs_1.applyParentToChild)(original, node))))
44
50
  };
45
51
  }
46
52
  },
47
53
  async move_vfs({ from, parent }) {
48
54
  var _a;
49
- if (from <= '/' || !parent)
55
+ if (!from || !parent)
50
56
  return new apiMiddleware_1.ApiError(const_1.HTTP_BAD_REQUEST);
51
57
  const fromNode = await urlToNodeOriginal(from);
52
58
  if (!fromNode)
53
59
  return new apiMiddleware_1.ApiError(const_1.HTTP_NOT_FOUND, 'from not found');
60
+ if (fromNode === vfs_1.vfs)
61
+ return new apiMiddleware_1.ApiError(const_1.HTTP_BAD_REQUEST, 'from is root');
54
62
  const parentNode = await urlToNodeOriginal(parent);
55
63
  if (!parentNode)
56
64
  return new apiMiddleware_1.ApiError(const_1.HTTP_NOT_FOUND, 'parent not found');
@@ -70,39 +78,39 @@ const apis = {
70
78
  const n = await urlToNodeOriginal(uri);
71
79
  if (!n)
72
80
  return new apiMiddleware_1.ApiError(const_1.HTTP_NOT_FOUND, 'path not found');
73
- props = pickProps(props, ['name', 'source', 'masks', 'default', 'accept', ...Object.keys(vfs_1.defaultPerms)]);
81
+ props = pickProps(props, ['name', 'source', 'masks', 'default', 'accept', ...vfs_1.PERM_KEYS]); // sanitize
74
82
  if (props.name && props.name !== (0, vfs_1.getNodeName)(n)) {
75
83
  const parent = await urlToNodeOriginal((0, path_1.dirname)(uri));
76
84
  if ((_a = parent === null || parent === void 0 ? void 0 : parent.children) === null || _a === void 0 ? void 0 : _a.find(x => (0, vfs_1.getNodeName)(x) === props.name))
77
85
  return new apiMiddleware_1.ApiError(const_1.HTTP_CONFLICT, 'name already present');
78
86
  }
79
- props = (0, misc_1.newObj)(props, v => v === null ? undefined : v); // null is a way to serialize undefined, that will restore default values
80
87
  if (props.masks && typeof props.masks !== 'object')
81
88
  delete props.masks;
82
89
  Object.assign(n, props);
83
- if ((0, vfs_1.getNodeName)(lodash_1.default.omit(n, ['name'])) === n.name) // name only if necessary
84
- n.name = undefined;
90
+ simplifyName(n);
85
91
  await (0, vfs_1.saveVfs)();
86
92
  return n;
87
93
  },
88
94
  async add_vfs({ parent, source, name }) {
89
95
  var _a;
90
- const n = parent ? await urlToNodeOriginal(parent) : vfs_1.vfs;
91
- if (!n)
92
- return new apiMiddleware_1.ApiError(const_1.HTTP_NOT_FOUND, 'invalid parent');
93
- if (n.isTemp || !await (0, vfs_1.nodeIsDirectory)(n))
94
- return new apiMiddleware_1.ApiError(const_1.HTTP_NOT_ACCEPTABLE, 'invalid parent');
96
+ if (!source && !name)
97
+ return new apiMiddleware_1.ApiError(const_1.HTTP_BAD_REQUEST, 'name or source required');
98
+ parent = parent ? await urlToNodeOriginal(parent) : vfs_1.vfs;
99
+ if (!parent)
100
+ return new apiMiddleware_1.ApiError(const_1.HTTP_NOT_FOUND, 'parent not found');
101
+ if (!await (0, vfs_1.nodeIsDirectory)(parent))
102
+ return new apiMiddleware_1.ApiError(const_1.HTTP_NOT_ACCEPTABLE, 'parent not a folder');
95
103
  if ((0, misc_1.isWindowsDrive)(source))
96
104
  source += '\\'; // slash must be included, otherwise it will refer to the cwd of that drive
97
- let tryName = (0, vfs_1.getNodeName)({ name, source });
98
- const ext = (0, path_1.extname)(tryName);
99
- const noExt = ext ? tryName.slice(0, -ext.length) : tryName;
105
+ const child = { source, name };
106
+ name = (0, vfs_1.getNodeName)(child); // could be not given as input
107
+ const ext = (0, path_1.extname)(name);
108
+ const noExt = ext ? name.slice(0, -ext.length) : name;
100
109
  let idx = 2;
101
- while ((_a = n.children) === null || _a === void 0 ? void 0 : _a.find((0, vfs_1.isSameFilenameAs)(tryName)))
102
- tryName = `${noExt} ${idx++}${ext}`;
103
- name = tryName;
104
- n.children || (n.children = []);
105
- n.children.unshift({ source, name });
110
+ while ((_a = parent.children) === null || _a === void 0 ? void 0 : _a.find((0, vfs_1.isSameFilenameAs)(name)))
111
+ name = `${noExt} ${idx++}${ext}`;
112
+ child.name = name;
113
+ (parent.children || (parent.children = [])).unshift({ source, name });
106
114
  await (0, vfs_1.saveVfs)();
107
115
  return { name };
108
116
  },
@@ -192,3 +200,8 @@ function pickProps(o, keys) {
192
200
  ret[k] = o[k] === null || o[k] === '' ? undefined : o[k];
193
201
  return ret;
194
202
  }
203
+ function simplifyName(node) {
204
+ const { name, ...noName } = node;
205
+ if ((0, vfs_1.getNodeName)(noName) === name)
206
+ delete node.name;
207
+ }
@@ -31,8 +31,9 @@ function apiMiddleware(apis) {
31
31
  // we don't rely on SameSite cookie option because it's https-only
32
32
  let res;
33
33
  try {
34
- if (params.path)
35
- params.path = (0, misc_1.removeStarting)(ctx.state.revProxyPath, params.path);
34
+ for (const [k, v] of Object.entries(params))
35
+ if (k.startsWith('uri') && typeof v === 'string')
36
+ params[k] = (0, misc_1.removeStarting)(ctx.state.revProxyPath, v);
36
37
  res = csrf && csrf !== params.csrf ? new ApiError(const_1.HTTP_UNAUTHORIZED, 'csrf')
37
38
  : await apiFun(params || {}, ctx);
38
39
  }
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.COMPATIBLE_API_VERSION = exports.API_VERSION = exports.SESSION_DURATION = 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_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.SESSION_DURATION = 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,13 +38,14 @@ 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-04-08T13:40:53.294Z";
41
+ exports.BUILD_TIMESTAMP = "2023-04-23T13:31:04.738Z";
42
42
  const pkg = JSON.parse(fs.readFileSync(PKG_PATH, 'utf8'));
43
43
  exports.VERSION = pkg.version;
44
44
  exports.DAY = 86400000;
45
- exports.SESSION_DURATION = exports.DAY;
45
+ exports.SESSION_DURATION = Number(process.env.SESSION_DURATION) * 1000 || exports.DAY;
46
46
  exports.API_VERSION = 8; // entry.uri + script.plugin
47
47
  exports.COMPATIBLE_API_VERSION = 1; // while changes in the api are not breaking, this number stays the same, otherwise is made equal to API_VERSION
48
+ exports.HFS_REPO = 'rejetto/hfs';
48
49
  exports.SPECIAL_URI = '/~/';
49
50
  exports.FRONTEND_URI = exports.SPECIAL_URI + 'frontend/';
50
51
  exports.ADMIN_URI = exports.SPECIAL_URI + 'admin/';
package/src/customHtml.js CHANGED
@@ -12,7 +12,7 @@ const watchLoad_1 = require("./watchLoad");
12
12
  const valtio_1 = require("valtio");
13
13
  const promises_1 = require("fs/promises");
14
14
  exports.customHtmlSections = ['top', 'bottom', 'beforeHeader', 'afterHeader',
15
- 'afterMenuBar', 'afterEntryName', 'afterList'];
15
+ 'afterMenuBar', 'afterEntryName', 'afterList', 'beforeLogin'];
16
16
  exports.customHtmlState = (0, valtio_1.proxy)({
17
17
  sections: new Map()
18
18
  });
@@ -18,7 +18,7 @@ function debounceAsync(callback, wait = 100, { leading = false, maxWait = Infini
18
18
  });
19
19
  async function debouncer(...args) {
20
20
  if (runningCallback)
21
- return await runningCallback;
21
+ return runningCallback;
22
22
  whoIsWaiting = args;
23
23
  waitingSince || (waitingSince = Date.now());
24
24
  const waitingCap = maxWait - (Date.now() - (waitingSince || started));
@@ -29,7 +29,7 @@ function debounceAsync(callback, wait = 100, { leading = false, maxWait = Infini
29
29
  return void (waitingSince = 0);
30
30
  if (whoIsWaiting !== args) // another fresher call is waiting
31
31
  return runningDebouncer;
32
- return await exec();
32
+ return exec();
33
33
  }
34
34
  async function exec() {
35
35
  if (!whoIsWaiting)
@@ -38,7 +38,7 @@ function debounceAsync(callback, wait = 100, { leading = false, maxWait = Infini
38
38
  started = Date.now();
39
39
  try {
40
40
  runningCallback = callback.apply(null, whoIsWaiting);
41
- return await runningCallback;
41
+ return await runningCallback; // await necessary to go-finally at the right time and even on exceptions
42
42
  }
43
43
  finally {
44
44
  whoIsWaiting = undefined;
@@ -52,11 +52,11 @@ exports.frontEndApis = {
52
52
  }
53
53
  });
54
54
  },
55
- async create_folder({ path, name }, ctx) {
56
- apiAssertTypes({ string: { path, name } });
55
+ async create_folder({ uri, name }, ctx) {
56
+ apiAssertTypes({ string: { uri, name } });
57
57
  if (!(0, util_files_1.isValidFileName)(name) || (0, util_files_1.dirTraversal)(name))
58
58
  return new apiMiddleware_1.ApiError(const_1.HTTP_BAD_REQUEST, 'bad name');
59
- const parentNode = await (0, vfs_1.urlToNode)(path, ctx);
59
+ const parentNode = await (0, vfs_1.urlToNode)(uri, ctx);
60
60
  if (!parentNode)
61
61
  return new apiMiddleware_1.ApiError(const_1.HTTP_NOT_FOUND, 'parent not found');
62
62
  if (!(0, vfs_1.hasPermission)(parentNode, 'can_upload', ctx))
@@ -69,9 +69,9 @@ exports.frontEndApis = {
69
69
  return new apiMiddleware_1.ApiError(e.code === 'EEXIST' ? const_1.HTTP_CONFLICT : const_1.HTTP_BAD_REQUEST, e);
70
70
  }
71
71
  },
72
- async del({ path }, ctx) {
73
- apiAssertTypes({ string: { path } });
74
- const node = await (0, vfs_1.urlToNode)(path, ctx);
72
+ async del({ uri }, ctx) {
73
+ apiAssertTypes({ string: { uri } });
74
+ const node = await (0, vfs_1.urlToNode)(uri, ctx);
75
75
  if (!node)
76
76
  throw new apiMiddleware_1.ApiError(const_1.HTTP_NOT_FOUND);
77
77
  if (!node.source)
package/src/github.js CHANGED
@@ -4,7 +4,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  return (mod && mod.__esModule) ? mod : { "default": mod };
5
5
  };
6
6
  Object.defineProperty(exports, "__esModule", { value: true });
7
- exports.searchPlugins = exports.getFolder2repo = exports.readOnlinePlugin = exports.getRepoInfo = exports.downloadPlugin = void 0;
7
+ exports.getProjectInfo = exports.searchPlugins = exports.getFolder2repo = exports.readOnlinePlugin = exports.readGithubFile = exports.getRepoInfo = exports.downloadPlugin = void 0;
8
8
  const events_1 = __importDefault(require("./events"));
9
9
  const misc_1 = require("./misc");
10
10
  const plugins_1 = require("./plugins");
@@ -48,12 +48,14 @@ function getRepoInfo(id) {
48
48
  return apiGithub('repos/' + id);
49
49
  }
50
50
  exports.getRepoInfo = getRepoInfo;
51
+ function readGithubFile(uri) {
52
+ return (0, misc_1.httpsString)('https://raw.githubusercontent.com/' + uri)
53
+ .then(res => res.body);
54
+ }
55
+ exports.readGithubFile = readGithubFile;
51
56
  async function readOnlinePlugin(repoInfo, branch = '') {
52
- const url = `https://raw.githubusercontent.com/${repoInfo.full_name}/${branch || repoInfo.default_branch}/${DIST_ROOT}plugin.js`;
53
- const res = await (0, misc_1.httpsString)(url);
54
- if (!res.ok)
55
- throw res.statusCode;
56
- const pl = (0, plugins_1.parsePluginSource)(repoInfo.full_name, res.body); // use 'repo' as 'id' client-side
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
57
59
  pl.branch = branch || undefined;
58
60
  return pl;
59
61
  }
@@ -76,10 +78,13 @@ async function apiGithub(uri) {
76
78
  return JSON.parse(res.body);
77
79
  }
78
80
  async function* searchPlugins(text = '') {
79
- var _a;
81
+ var _a, _b;
82
+ const projectInfo = await getProjectInfo();
80
83
  const res = await apiGithub('search/repositories?q=topic:hfs-plugin+' + encodeURI(text));
81
84
  for (const it of res.items) {
82
85
  const repo = it.full_name;
86
+ if ((_a = projectInfo === null || projectInfo === void 0 ? void 0 : projectInfo.plugins_blacklist) === null || _a === void 0 ? void 0 : _a.includes(repo))
87
+ continue;
83
88
  let pl = await readOnlinePlugin(it);
84
89
  if (!pl.apiRequired)
85
90
  continue; // mandatory field
@@ -100,9 +105,22 @@ async function* searchPlugins(text = '') {
100
105
  continue;
101
106
  Object.assign(pl, {
102
107
  downloading: downloading[repo],
103
- license: (_a = it.license) === null || _a === void 0 ? void 0 : _a.spdx_id,
108
+ license: (_b = it.license) === null || _b === void 0 ? void 0 : _b.spdx_id,
104
109
  }, lodash_1.default.pick(it, ['pushed_at', 'stargazers_count']));
105
110
  yield pl;
106
111
  }
107
112
  }
108
113
  exports.searchPlugins = searchPlugins;
114
+ // centralized hosted information, to be used as little as possible
115
+ let cache;
116
+ function getProjectInfo() {
117
+ return cache || (cache = readGithubFile(const_1.HFS_REPO + '/main/central.json').then(x => {
118
+ if (!x)
119
+ throw x; // go catch
120
+ setTimeout(() => cache = null, const_1.DAY); // invalidate cache
121
+ return JSON.parse(x);
122
+ }).catch(() => {
123
+ setTimeout(() => cache = null, 10000);
124
+ }));
125
+ }
126
+ exports.getProjectInfo = getProjectInfo;
package/src/index.js CHANGED
@@ -23,11 +23,12 @@ const config_1 = require("./config");
23
23
  const assert_1 = require("assert");
24
24
  const lodash_1 = __importDefault(require("lodash"));
25
25
  const misc_1 = require("./misc");
26
+ const koa_session_1 = __importDefault(require("koa-session"));
26
27
  (0, assert_1.ok)(lodash_1.default.intersection(Object.keys(frontEndApis_1.frontEndApis), Object.keys(adminApis_1.adminApis)).length === 0); // they share same endpoints
27
28
  const keys = ((_a = process.env.COOKIE_SIGN_KEYS) === null || _a === void 0 ? void 0 : _a.split(',')) || [(0, misc_1.randomId)(30)];
28
29
  exports.app = new koa_1.default({ keys });
29
30
  exports.app.use(middlewares_1.someSecurity)
30
- .use((0, middlewares_1.sessions)(exports.app))
31
+ .use((0, koa_session_1.default)({ key: 'hfs_$id', signed: true, rolling: true, maxAge: const_1.SESSION_DURATION }, exports.app))
31
32
  .use(middlewares_1.prepareState)
32
33
  .use(middlewares_1.headRequests)
33
34
  .use((0, log_1.log)())
package/src/lang.js CHANGED
@@ -3,11 +3,12 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.EMBEDDED_TRANSLATIONS = exports.getLangData = exports.file2code = exports.code2file = void 0;
6
+ exports.getLangData = exports.file2code = exports.code2file = void 0;
7
7
  const misc_1 = require("./misc");
8
8
  const promises_1 = require("fs/promises");
9
9
  const config_1 = require("./config");
10
10
  const watchLoad_1 = require("./watchLoad");
11
+ const embedded_1 = __importDefault(require("./langs/embedded"));
11
12
  const PREFIX = 'hfs-lang-';
12
13
  const SUFFIX = '.json';
13
14
  const EMBEDDED_LANGUAGE = 'en';
@@ -20,10 +21,11 @@ function file2code(fn) {
20
21
  }
21
22
  exports.file2code = file2code;
22
23
  async function getLangData(ctx) {
23
- if (forceLangData)
24
+ const param = String(ctx.query.lang || '');
25
+ if (!param && forceLangData)
24
26
  return forceLangData;
25
27
  const ret = {};
26
- const csv = String(ctx.query.lang || '') || ctx.get('Accept-Language') || '';
28
+ const csv = param || ctx.get('Accept-Language') || '';
27
29
  const langs = (0, misc_1.wantArray)(csv.split(',').map(x => x.toLowerCase()));
28
30
  let i = 0;
29
31
  while (i < langs.length) {
@@ -34,8 +36,8 @@ async function getLangData(ctx) {
34
36
  ret[k] = JSON.parse(await (0, promises_1.readFile)(`hfs-lang-${k}.json`, 'utf8'));
35
37
  }
36
38
  catch (_a) {
37
- if (k in exports.EMBEDDED_TRANSLATIONS)
38
- ret[k] = exports.EMBEDDED_TRANSLATIONS[k];
39
+ if ((0, misc_1.hasProp)(embedded_1.default, k))
40
+ ret[k] = embedded_1.default[k];
39
41
  else {
40
42
  do {
41
43
  k = k.substring(0, k.lastIndexOf('-'));
@@ -55,23 +57,11 @@ let forceLangData;
55
57
  let undo;
56
58
  (0, config_1.defineConfig)('force_lang', '', v => {
57
59
  undo === null || undo === void 0 ? void 0 : undo();
58
- forceLangData = undefined;
59
60
  if (!v)
60
- return;
61
+ return forceLangData = undefined;
62
+ forceLangData = {}; // necessary to make the embedded language work
61
63
  const res = (0, watchLoad_1.watchLoad)(code2file(v), data => {
62
64
  forceLangData = { [v]: JSON.parse(data) };
63
65
  });
64
66
  undo = res.unwatch;
65
67
  });
66
- const hfs_lang_it_json_1 = __importDefault(require("./langs/hfs-lang-it.json"));
67
- const hfs_lang_zh_json_1 = __importDefault(require("./langs/hfs-lang-zh.json"));
68
- const hfs_lang_ru_json_1 = __importDefault(require("./langs/hfs-lang-ru.json"));
69
- const hfs_lang_sr_json_1 = __importDefault(require("./langs/hfs-lang-sr.json"));
70
- const hfs_lang_ko_json_1 = __importDefault(require("./langs/hfs-lang-ko.json"));
71
- exports.EMBEDDED_TRANSLATIONS = {
72
- it: hfs_lang_it_json_1.default,
73
- zh: hfs_lang_zh_json_1.default,
74
- ru: hfs_lang_ru_json_1.default,
75
- sr: hfs_lang_sr_json_1.default,
76
- ko: hfs_lang_ko_json_1.default,
77
- };
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const hfs_lang_it_json_1 = __importDefault(require("./hfs-lang-it.json"));
7
+ const hfs_lang_zh_json_1 = __importDefault(require("./hfs-lang-zh.json"));
8
+ const hfs_lang_ru_json_1 = __importDefault(require("./hfs-lang-ru.json"));
9
+ const hfs_lang_sr_json_1 = __importDefault(require("./hfs-lang-sr.json"));
10
+ const hfs_lang_ko_json_1 = __importDefault(require("./hfs-lang-ko.json"));
11
+ const hfs_lang_ms_json_1 = __importDefault(require("./hfs-lang-ms.json"));
12
+ const hfs_lang_zh_tw_json_1 = __importDefault(require("./hfs-lang-zh-tw.json"));
13
+ 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 };