diff --git a/.cloudcmd.menu.js b/.cloudcmd.menu.js index 6c808d18..0e0126b6 100644 --- a/.cloudcmd.menu.js +++ b/.cloudcmd.menu.js @@ -1,45 +1,31 @@ -'use strict'; - -module.exports = { - 'F2 - Rename file': async ({DOM}) => { - await DOM.renameCurrent(); - }, - 'L - Lint': async ({CloudCmd}) => { - const {TerminalRun} = CloudCmd; - await run(TerminalRun, 'npm run lint'); - }, - 'F - Fix Lint': async ({CloudCmd}) => { - const {TerminalRun} = CloudCmd; - await run(TerminalRun, 'npm run fix:lint'); - }, - 'T - Test': async ({CloudCmd}) => { - const {TerminalRun} = CloudCmd; - - await run(TerminalRun, 'npm run test'); - }, - 'C - Coverage': async ({CloudCmd}) => { - const {TerminalRun} = CloudCmd; - - await run(TerminalRun, 'npm run coverage'); - }, - 'D - Build Dev': async ({CloudCmd}) => { - const {TerminalRun} = CloudCmd; - - await run(TerminalRun, 'npm run build:client:dev'); - CloudCmd.refresh(); - }, - 'P - Build Prod': async ({CloudCmd}) => { - const {TerminalRun} = CloudCmd; - - await run(TerminalRun, 'npm run build:client'); - CloudCmd.refresh(); - }, +export default { + 'F2 - Rename File': renameCurrent, + 'L - Lint': run('npm run lint'), + 'F - Fix Lint': run('npm run fix:lint'), + 'T - Test': run('npm run test'), + 'C - Coverage': run('npm run coverage'), + 'D - Build Dev': run('npm run build:client:dev'), + 'P - Build Prod': run('npm run build:client'), }; -async function run(TerminalRun, command) { - await TerminalRun.show({ - command, - closeMessage: 'Press any key to close Terminal', - autoClose: false, - }); +async function renameCurrent(DOM) { + await DOM.renameCurrent(); +} + +function run(command) { + return async ({CloudCmd, DOM}) => { + const {TerminalRun, config} = CloudCmd; + + const {CurrentInfo} = DOM; + const {dirPath} = CurrentInfo; + + const cwd = config('root') + dirPath; + + return await TerminalRun.show({ + cwd, + command, + closeMessage: 'Press any key to close Terminal', + autoClose: false, + }); + }; } diff --git a/.github/workflows/docker-io.yml b/.github/workflows/docker-io.yml new file mode 100644 index 00000000..8cbeb042 --- /dev/null +++ b/.github/workflows/docker-io.yml @@ -0,0 +1,60 @@ +name: Docker IO +permissions: + contents: write +on: + - push +jobs: + buildx: + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + steps: + - name: Checkout + uses: actions/checkout@v5 + - uses: oven-sh/setup-bun@v2 + with: + bun-version: latest + - name: Use Node.js 24.x + uses: actions/setup-node@v6 + with: + node-version: 24.x + - name: Install Redrun + run: bun i redrun -g --no-save + - name: NPM Install + run: bun i --no-save + - name: Lint + run: redrun lint + - name: Build + id: build + run: | + redrun build + VERSION=$(grep '"version":' package.json -m1 | cut -d\" -f4) + echo "version=$VERSION" >> $GITHUB_OUTPUT + - name: Set up QEMU + uses: docker/setup-qemu-action@v4 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v4 + - name: Login to DockerHub + uses: docker/login-action@v4 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_TOKEN }} + - name: Login to GitHub Container Registry + uses: docker/login-action@v4 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Build and push io-image + uses: docker/build-push-action@v7 + with: + context: . + file: docker/Dockerfile.io + platforms: linux/amd64,linux/arm64 + push: true + tags: | + coderaiser/cloudcmd:io + coderaiser/cloudcmd:${{ steps.build.outputs.version }}-io + ghcr.io/${{ github.repository }}-io + ghcr.io/${{ github.repository }}:${{ steps.build.outputs.version }}-io diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 376b0f08..5c81d368 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -1,4 +1,6 @@ name: Docker CI +permissions: + contents: write on: push: tags: @@ -29,24 +31,25 @@ jobs: id: build run: | redrun build - echo "::set-output name=version::$(grep '"version":' package.json -m1 | cut -d\" -f4)" + VERSION=$(grep '"version":' package.json -m1 | cut -d\" -f4) + echo "version=$VERSION" >> $GITHUB_OUTPUT - name: Set up QEMU - uses: docker/setup-qemu-action@v3 + uses: docker/setup-qemu-action@v4 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 + uses: docker/setup-buildx-action@v4 - name: Login to DockerHub - uses: docker/login-action@v3 + uses: docker/login-action@v4 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_TOKEN }} - name: Login to GitHub Container Registry - uses: docker/login-action@v3 + uses: docker/login-action@v4 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Build and push base-image - uses: docker/build-push-action@v5 + uses: docker/build-push-action@v7 with: context: . file: docker/Dockerfile @@ -58,14 +61,28 @@ jobs: ghcr.io/${{ github.repository }}:latest ghcr.io/${{ github.repository }}:${{ steps.build.outputs.version }} - name: Build and push alpine-image - uses: docker/build-push-action@v5 + uses: docker/build-push-action@v7 with: context: . file: docker/Dockerfile.alpine platforms: linux/amd64,linux/arm64 push: true tags: | + coderaiser/cloudcmd:alpine coderaiser/cloudcmd:latest-alpine coderaiser/cloudcmd:${{ steps.build.outputs.version }}-alpine ghcr.io/${{ github.repository }}:latest-alpine ghcr.io/${{ github.repository }}:${{ steps.build.outputs.version }}-alpine + - name: Build and push slim-image + uses: docker/build-push-action@v7 + with: + context: . + file: docker/Dockerfile.slim + platforms: linux/amd64,linux/arm64 + push: true + tags: | + coderaiser/cloudcmd:slim + coderaiser/cloudcmd:latest-slim + coderaiser/cloudcmd:${{ steps.build.outputs.version }}-slim + ghcr.io/${{ github.repository }}-slim + ghcr.io/${{ github.repository }}:${{ steps.build.outputs.version }}-slim diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index 1b392900..429e5568 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -1,6 +1,8 @@ name: Node CI on: - push +permissions: + contents: write jobs: build: runs-on: ubuntu-latest @@ -11,7 +13,7 @@ jobs: node-version: - 22.x - 24.x - - 25.x + - 26.x steps: - uses: actions/checkout@v5 - uses: oven-sh/setup-bun@v2 @@ -22,26 +24,19 @@ jobs: with: node-version: ${{ matrix.node-version }} - name: Install Redrun - run: bun i redrun -g --no-save + run: bun i redrun madrun -g --no-save - name: Install run: bun i --no-save + - name: Init Madrun + run: madrun --init - name: Lint run: redrun fix:lint - - uses: actions/cache@v4 - with: - path: | - ~/.cargo/bin/ - ~/.cargo/registry/index/ - ~/.cargo/registry/cache/ - ~/.cargo/git/db/ - target/ - key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} - - name: Typos Install - run: cargo install typos-cli || echo 'already installed' - name: Typos - run: typos --write-changes + uses: coderaiser/typos.ai@v1.1.8 + with: + key: ${{ secrets.TYPOS_AI_KEY }} - name: Commit fixes - uses: EndBug/add-and-commit@v9 + uses: EndBug/add-and-commit@v10 continue-on-error: true with: message: "chore: ${{ env.NAME }}: actions: lint ☘️" diff --git a/.gitignore b/.gitignore index e6d5d021..6aa86aa9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,19 +1,17 @@ -package-lock.json -yarn.lock -yarn-error.log -node_modules -npm-debug.log* -coverage - -modules/execon -modules/emitify +*.swp +*.log +*.lock .nyc_output - -*.swp .DS_Store +.idea +package-lock.json +npm-debug.log* + +node_modules +coverage +modules/execon +modules/emitify dist dist-dev - -.idea diff --git a/.madrun.mjs b/.madrun.js similarity index 73% rename from .madrun.mjs rename to .madrun.js index 733499f3..efa8ce98 100644 --- a/.madrun.mjs +++ b/.madrun.js @@ -1,49 +1,45 @@ -import process from 'node:process'; import {run, cutEnv} from 'madrun'; +import {defineEnv} from 'supertape/env'; -const testEnv = { - SUPERTAPE_TIMEOUT: 7000, -}; +const testEnv = defineEnv({ + timeout: 7000, + css: true, +}); -const is17 = /^v1[789]/.test(process.version); -const is20 = process.version.startsWith('v2'); - -// fix for ERR_OSSL_EVP_UNSUPPORTED on node v17 -// flag '--openssl-legacy-provider' not supported -// on earlier version of node.js -// -// https://stackoverflow.com/a/69746937/4536327 -const buildEnv = (is17 || is20) && { - NODE_OPTIONS: '--openssl-legacy-provider', +const buildEnv = { NODE_ENV: 'production', }; export default { - 'start': () => 'node bin/cloudcmd.mjs', + 'start': () => 'node bin/cloudcmd.js', 'start:dev': async () => await run('start', null, { NODE_ENV: 'development', }), 'build:start': () => run(['build:client', 'start']), - 'build:start:dev': () => run(['build:client:dev', 'start:dev']), + 'build:start:dev': () => run([ + 'build:client:dev', + 'start:dev', + ]), 'lint:all': () => run('lint:progress'), - 'lint': () => 'redlint scan; putout .', + 'lint': () => 'redlint fix; putout .', 'lint:progress': () => run('lint', '-f progress'), 'watch:lint': () => 'nodemon -w client -w server -w test -w common -w .webpack -x "putout -s"', 'fresh:lint': () => run('lint', '--fresh'), 'lint:fresh': () => run('lint', '--fresh'), 'fix:lint': async () => `putout --fix . && redlint fix`, 'lint:stream': () => run('lint', '-f stream'), - 'test': () => [testEnv, `tape 'test/**/*.{js,mjs}' '{client,static,common,server}/**/*.spec.{js,mjs}' -f fail`], + 'test': () => [testEnv, `tape '{test}/**/*.js' '{bin,client,static,common,server}/**/*.spec.js' -f fail`], + 'test:e2e': () => `tape 'test-e2e/**/*.js'`, 'test:client': () => `tape 'test/client/**/*.js'`, 'test:server': () => `tape 'test/**/*.js' 'server/**/*.spec.js' 'common/**/*.spec.js'`, 'wisdom': async () => await run(['lint:all', 'build', 'test'], null, { CI: 1, }), - 'wisdom:type': () => 'bin/release.mjs', + 'wisdom:type': () => 'bin/release.js', 'coverage': async () => [testEnv, `c8 ${await cutEnv('test')}`], 'coverage:report': () => 'c8 report --reporter=lcov', 'report': () => 'c8 report --reporter=lcov', - '6to5': () => [buildEnv, 'webpack --progress'], + '6to5': () => [buildEnv, 'rspack build --config rspack.config.js'], '6to5:client': () => run('6to5', '--mode production'), '6to5:client:dev': async () => await run('6to5', '--mode development', { NODE_ENV: 'development', diff --git a/.npmignore b/.npmignore index b668cf7f..b83b5923 100644 --- a/.npmignore +++ b/.npmignore @@ -1,34 +1,29 @@ -.* *.spec.* *.config.* *.fixture.js* +*.ai +*.cdr +*.eps +*.log +*.lock + +.* + manifest.yml -docker docker-compose.yml -test +now.json +app.json +bower.json +deno.json +bin/release.* +img/logo/cloudcmd-hq.png +webpack.config.js + +docker +test* fixture fixture-* coverage css html -yarn-error.log -yarn.lock -now.json - -app.json -bower.json -manifest.yml -deno.json - -bin/release.mjs - client -img/logo/cloudcmd-hq.png - -webpack.config.js - -*.ai -*.cdr -*.eps - -*.config.* diff --git a/.nvmrc b/.nvmrc deleted file mode 100644 index 1efe0ac6..00000000 --- a/.nvmrc +++ /dev/null @@ -1 +0,0 @@ -v20.15.1 diff --git a/.nycrc.json b/.nycrc.json index 2a772159..1fe174f3 100644 --- a/.nycrc.json +++ b/.nycrc.json @@ -1,11 +1,11 @@ { - "check-coverage": false, + "checkCoverage": false, "all": false, "exclude": [ "**/*.spec.js", - "**/fixture", "**/*.*.js", "**/*.config.*", + "**/fixture", "**/test/**" ], "branches": 100, diff --git a/.putout.json b/.putout.json index 800cdeab..7154775a 100644 --- a/.putout.json +++ b/.putout.json @@ -1,11 +1,11 @@ { "plugins": ["cloudcmd"], "ignore": [ - "html", - "fixture*", + "*.md", "app.json", "fontello.json", - "*.md" + "html", + "fixture*" ], "rules": { "package-json/add-type": "off" diff --git a/.rspack/css.js b/.rspack/css.js new file mode 100644 index 00000000..10d28250 --- /dev/null +++ b/.rspack/css.js @@ -0,0 +1,38 @@ +import {env} from 'node:process'; +import {rspack} from '@rspack/core'; + +const {CssExtractRspackPlugin} = rspack; +const isDev = env.NODE_ENV === 'development'; + +const plugins = [ + new CssExtractRspackPlugin({ + filename: '[name].css', + }), +]; + +const rules = [{ + test: /\.css$/i, + use: [CssExtractRspackPlugin.loader, { + loader: 'css-loader', + options: { + url: true, + }, + }], +}, { + test: /\.(png|gif|svg|woff|woff2|eot|ttf)$/, + type: 'asset/inline', +}]; + +export default { + mode: isDev ? 'development' : 'production', + plugins, + module: { + rules, + }, + optimization: { + minimize: !isDev, + minimizer: [ + new rspack.LightningCssMinimizerRspackPlugin(), + ], + }, +}; diff --git a/.webpack/html.mjs b/.rspack/html.js similarity index 100% rename from .webpack/html.mjs rename to .rspack/html.js diff --git a/.webpack/js.mjs b/.rspack/js.js similarity index 72% rename from .webpack/js.mjs rename to .rspack/js.js index f2901384..251d85f6 100644 --- a/.webpack/js.mjs +++ b/.rspack/js.js @@ -1,12 +1,15 @@ import {resolve, sep} from 'node:path'; +import {fileURLToPath} from 'node:url'; import {env} from 'node:process'; -import webpack from 'webpack'; -import WebpackBar from 'webpackbar'; +import {rspack} from '@rspack/core'; + +const resolveModule = (a) => fileURLToPath(import.meta.resolve(a)); const { EnvironmentPlugin, NormalModuleReplacementPlugin, -} = webpack; + ProvidePlugin, +} = rspack; const modules = './modules'; const dirModules = './client/modules'; @@ -22,27 +25,31 @@ const dist = resolve(rootDir, 'dist'); const distDev = resolve(rootDir, 'dist-dev'); const devtool = isDev ? 'eval' : 'source-map'; -const notEmpty = (a) => a; -const clean = (array) => array.filter(notEmpty); - const noParse = (a) => a.endsWith('.spec.js'); -const options = { - babelrc: true, -}; -const rules = clean([ - !isDev && { - test: /\.[mc]?js$/, - exclude: /node_modules/, - loader: 'babel-loader', +// codegen.macro is a babel-macro (build-time codegen), not supported by +// Rspack's native SWC transform, so client/sw/sw.js (the only file that +// uses it) keeps going through babel-loader. Everything else uses +// Rspack's built-in SWC loader, which is the main source of the speedup. +const rules = [{ + test: /sw\/sw\.js$/, + exclude: /node_modules/, + loader: 'babel-loader', +}, { + test: /\.[mc]?js$/, + exclude: [/node_modules/, /sw\/sw\.js$/], + loader: 'builtin:swc-loader', + options: { + jsc: { + parser: { + syntax: 'ecmascript', + }, + }, + env: { + targets: 'defaults', + }, }, - isDev && { - test: /\.[mc]?js$/, - exclude: /node_modules/, - loader: 'babel-loader', - options, - }, -]); +}]; const plugins = [ new NormalModuleReplacementPlugin(/^node:/, (resource) => { @@ -52,8 +59,7 @@ const plugins = [ new EnvironmentPlugin({ NODE_ENV, }), - new WebpackBar(), - new webpack.ProvidePlugin({ + new ProvidePlugin({ process: 'process/browser', }), ]; @@ -77,6 +83,8 @@ const splitChunks = { 'themes/light', 'columns/name-size', 'columns/name-size-date', + 'columns/name-size-time', + 'columns/name-size-date-time', ]; return !lazyChunks.includes(chunk.name); @@ -97,9 +105,9 @@ export default { 'node:path': 'path', }, fallback: { - path: import.meta.resolve('path-browserify'), - process: import.meta.resolve('process/browser'), - util: import.meta.resolve('util'), + path: resolveModule('path-browserify'), + process: resolveModule('process/browser'), + util: resolveModule('util'), }, }, devtool, @@ -111,32 +119,33 @@ export default { 'themes/light': `${dirThemes}/light.css`, 'columns/name-size': `${dirColumns}/name-size.css`, 'columns/name-size-date': `${dirColumns}/name-size-date.css`, + 'columns/name-size-date-time': `${dirColumns}/name-size-date-time.css`, 'nojs': `${dirCss}/nojs.css`, 'help': `${dirCss}/help.css`, 'view': `${dirCss}/view.css`, 'config': `${dirCss}/config.css`, 'terminal': `${dirCss}/terminal.css`, 'user-menu': `${dirCss}/user-menu.css`, - 'sw': `${dir}/sw/sw.mjs`, - 'cloudcmd': `${dir}/cloudcmd.mjs`, + 'sw': `${dir}/sw/sw.js`, + 'cloudcmd': `${dir}/cloudcmd.js`, [`${modules}/edit`]: `${dirModules}/edit.js`, [`${modules}/edit-file`]: `${dirModules}/edit-file.js`, [`${modules}/edit-file-vim`]: `${dirModules}/edit-file-vim.js`, [`${modules}/edit-names`]: `${dirModules}/edit-names.js`, [`${modules}/edit-names-vim`]: `${dirModules}/edit-names-vim.js`, - [`${modules}/menu`]: `${dirModules}/menu/index.mjs`, + [`${modules}/menu`]: `${dirModules}/menu/index.js`, [`${modules}/view`]: `${dirModules}/view/index.js`, [`${modules}/help`]: `${dirModules}/help.js`, [`${modules}/markdown`]: `${dirModules}/markdown.js`, - [`${modules}/config`]: `${dirModules}/config/index.mjs`, + [`${modules}/config`]: `${dirModules}/config/index.js`, [`${modules}/contact`]: `${dirModules}/contact.js`, - [`${modules}/upload`]: `${dirModules}/upload.mjs`, - [`${modules}/operation`]: `${dirModules}/operation/index.mjs`, + [`${modules}/upload`]: `${dirModules}/upload.js`, + [`${modules}/operation`]: `${dirModules}/operation/index.js`, [`${modules}/konsole`]: `${dirModules}/konsole.js`, [`${modules}/terminal`]: `${dirModules}/terminal.js`, [`${modules}/terminal-run`]: `${dirModules}/terminal-run.js`, - [`${modules}/cloud`]: `${dirModules}/cloud.mjs`, - [`${modules}/user-menu`]: `${dirModules}/user-menu/index.mjs`, + [`${modules}/cloud`]: `${dirModules}/cloud.js`, + [`${modules}/user-menu`]: `${dirModules}/user-menu/index.js`, [`${modules}/polyfill`]: `${dirModules}/polyfill.js`, [`${modules}/command-line`]: `${dirModules}/command-line.js`, }, @@ -153,7 +162,7 @@ export default { }, plugins, performance: { - maxEntrypointSize: 600_000, + maxEntrypointSize: 800_000, maxAssetSize: 600_000, }, }; diff --git a/.webpack/css.mjs b/.webpack/css.mjs deleted file mode 100644 index 3b83715b..00000000 --- a/.webpack/css.mjs +++ /dev/null @@ -1,34 +0,0 @@ -import {env} from 'node:process'; -import OptimizeCssAssetsPlugin from 'optimize-css-assets-webpack-plugin'; -import MiniCssExtractPlugin from 'mini-css-extract-plugin'; - -const isDev = env.NODE_ENV === 'development'; - -const clean = (a) => a.filter(Boolean); - -const plugins = clean([ - new MiniCssExtractPlugin({ - filename: '[name].css', - }), - !isDev && new OptimizeCssAssetsPlugin(), -]); - -const rules = [{ - test: /\.css$/i, - use: [MiniCssExtractPlugin.loader, { - loader: 'css-loader', - options: { - url: true, - }, - }], -}, { - test: /\.(png|gif|woff|woff2|eot|ttf)$/, - type: 'asset/inline', -}]; - -export default { - plugins, - module: { - rules, - }, -}; diff --git a/ChangeLog b/ChangeLog index e87bb209..c5e0fbca 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,641 @@ +2026.06.15, v19.19.1 + +feature: +- 78b206e0 client: key: Ctrl + L -> Ctrl + Shift + L: Log out (#466) +- b1492885 docker: io: NPM_CONFIG_PACKAGE_LOCK=false +- 76d7bf4d docker: typos, shellcheck: add + +2026.05.26, v19.19.0 + +feature: +- 4ee38db6 client: view: flac: add support + +2026.05.26, v19.18.1 + +feature: +- 40ecef5e cloudcmd: ratelimit: X-Forwarded-For (#437) + +2026.05.26, v19.18.0 + +fix: +- 161bede8 client: menu: @putout/bundle@5.5. +- 447d990f cloudcmd: server: rest: sendError + +feature: +- 5d9628ce cloudcmd: rate limit: add support (#437) +- 2be395e6 cloudcmd: get rid of manifest: 401 basic auth + +2026.05.17, v19.17.0 + +fix: +- d96f6c46 css: query: hide time on mobile +- e7c55e05 docker: io: XDG_CONFIG_HOME + +feature: +- ae1ca0f8 cloudcmd: cssnano-preset-default v8.0.1 +- 94d5096a style: owner, mode: improve +- 807f8346 cloudcmd: superc8 v12.6.0 +- 67a95722 docker: io: cline: add +- 68eacb91 qword: add +- 4acd294b docker: io: tmux +- 05c80043 cloudcmd: @supertape/loader-css v1.0.0 +- 800a6545 cloudcmd: eslint-plugin-n v18.0.1 +- a985bb36 cloudcmd: supertape v13.0.0 +- 4315ec61 docker: io XDG_CONFIG_HOME: /etc -> /usr/local/etc + +2026.05.03, v19.16.0 + +feature: +- d82d0335 client: vim: rr for rename file +- acfa27cf docker: io: nvchad: add +- 14f009f8 docker: io: bash-completion: add +- c6c60146 docker: io: f4 +- 9a6b8935 docker: io: GOPATH +- a9bc22ce cloudcmd: operation: rm useless checks +- 36bacfe9 cloudcmd: client: key: vim: cc, mm +- 591da25c cloudcmd: ponse v8.0.0 +- 1e1b073c docker: io: /usr/src/cloudcmd -> /usr/local/share/cloudcmd + +2026.04.28, v19.15.0 + +feature: +- 9d97343b cloudcmd: operation: rm useless checks +- 46a88cfa cloudcmd: client: key: vim: cc, mm + +2026.04.28, v19.14.0 + +feature: +- 36a8b641 cloudcmd: ponse v8.0.0 +- 1c263c18 docker: io: /usr/src/cloudcmd -> /usr/local/share/cloudcmd +- ea7b828c docker: io: ubuntu: resolute +- cd6c11ac docker: io: far2l +- c4beeec6 docker: io: gdu: add + +2026.04.21, v19.13.1 + +feature: +- d5cd11e8 cloudcmd: montag v2.0.1 +- dc94d2db docker: io: add latest git version +- 1637beee docker: io: /usr/local/src -> /usr/local/share +- b1bc4e73 Docker: io: pv +- 7c0dca60 docker: io: git: master + +2026.04.15, v19.13.0 + +fix: +- 48693d9e docker: io: XDG_CACHE_HOME + +feature: +- 4b2395c5 cloudcmd: Ctrl + L: logout +- 977a8aaa docker: io: strace: add +- 7d0098fd docker: io: XDG_CACHE_HOME=/tmp/cache +- a89e901b actions: docker: io: add + +2026.04.12, v19.12.5 + +feature: +- 1cfc1a6f docker: io: only amd64 + +2026.04.12, v19.12.4 + +feature: +- 3c2b5658 docker: io: haskell +- d37f8cd3 docker: io: palabra: node + +2026.04.12, v19.12.3 + +feature: +- cb6cabd4 docker: io: PALABRA_DIR + +2026.04.11, v19.12.2 + +feature: +- 2ce11fa2 docker: io: get rid of haskell: to slow install +- aa741232 docker: io: rizin, yara + +2026.04.09, v19.12.1 + +feature: +- 5bd03215 docker: io: add PREFIX + +2026.04.09, v19.12.0 + +feature: +- 13b15b7b docker: io: palabra + +2026.04.07, v19.11.14 + +feature: +- a31beab0 docker: io: rustup env + +2026.04.07, v19.11.13 + +feature: +- 6a08479a docker: io: BUN_INSTALL + +2026.04.06, v19.11.12 + +feature: +- 46c65554 docker: io: npm_config_cache + +2026.04.06, v19.11.11 + +feature: +- ae2ce388 docker: io: $DENO_DIR + +2026.04.06, v19.11.10 + +feature: +- 12ea14ac docker: io: nvm: node + +2026.04.06, v19.11.9 + +feature: +- ea96d13f docker: io: net-tools + +2026.04.06, v19.11.8 + +feature: +- 5c08565f docker: io: debian -> ubuntu + +2026.04.05, v19.11.7 + +feature: +- 36cdef37 docker: io: bookworm + +2026.04.05, v19.11.6 + +feature: +- 845f9bd1 cloudcmd: gritty v10.2.0 + +2026.04.05, v19.11.5 + +feature: +- f9c513cc docker: io: hexyl: add + +2026.04.04, v19.11.4 + +feature: +- e7347d25 docker: io: neovim: apt -> github + +2026.04.04, v19.11.3 + +feature: +- 9970ff76 docker: io: btop +- 4395a471 docker: io: $PATH: add $HOME/.local/bin + +2026.04.04, v19.11.2 + +feature: +- c40ae8e4 docker: io: htop +- f463c5c5 docker: io: aptitude: add +- d1032f09 docker: io: remove unused + +2026.04.04, v19.11.1 + +feature: +- 7787bfc2 cloudcmd: user-menu: runFromCDN + +2026.04.04, v19.11.0 + +feature: +- 32f89d38 cloudcmd: user-menu: root + +2026.04.04, v19.10.2 + +feature: +- df4fb517 cloudcmd: aleman v2.0.1 + +2026.04.03, v19.10.1 + +feature: +- ceb7ef4f docker: io: keep /var/lib/apt/lists + +2026.04.02, v19.10.0 + +fix: +- d12e7bd0 distribute: fix event listener leak on socket disconnect (#462) + +2026.04.02, v19.9.24 + +feature: +- c4d26c6a docker: io: apt-get upgrade +- 9ddb8c29 docker: io: get rid of nix +- e4d7d441 docker: io: nix + +2026.04.01, v19.9.23 + +feature: +- 5a3413ce docker: io: nix + +2026.03.31, v19.9.22 + +feature: +- b4345ed4 docker: io: DENO_DIR + +2026.03.31, v19.9.21 + +fix: +- 5c6a9a95 css: columns: name: 40% -> 35% + +2026.03.30, v19.9.20 + +feature: +- cd0b5554 iocmd: io: nvm + +2026.03.30, v19.9.19 + +feature: +- 6a52b11e docker: io: go, rust + +2026.03.30, v19.9.18 + +feature: +- 738059f2 docker: io: fzf +- 3fc8932f docker: io: less, el_GR + +2026.03.30, v19.9.17 + +feature: +- cf424d6c docker: io: command-not-found update + +2026.03.29, v19.9.16 + +feature: +- 19347a2b docker: io: add command-not-found + +2026.03.29, v19.9.15 + +feature: +- ee170552 docker: io: ubuntu +- e04c4594 docker: io: net-tools: add + +2026.03.29, v19.9.14 + +fix: +- 6c7709be docker: io: PS1 + +2026.03.29, v19.9.13 + +fix: +- 439b3710 bin: currify +- e4182841 docker: io: xterm-256color + +2026.03.29, v19.9.12 + +feature: +- 811a47fd cloudcmd: bin: get rid of require + +2026.03.29, v19.9.11 + +feature: +- 1f95f188 cloudcmd: get rid of simport + +2026.03.29, v19.9.10 + +feature: +- f0deb323 docker: io: add ja_JP.UTF-8 + +2026.03.29, v19.9.9 + +fix: +- f671f798 docker: io: PS1 environment variable in Dockerfile + +feature: +- cc7f9dc7 docker: io: use ubuntu + +2026.03.28, v19.9.8 + +feature: +- 4d1cd8cd docker: io: ffmpeg +- 54b56fdc cloudcmd: vim: ESC: use only to enable, do not use to disable + +2026.03.27, v19.9.7 + +feature: +- 110908e2 docker: io: apt-get + +2026.03.26, v19.9.6 + +feature: +- 6450a2f8 docker: io: add UTF-8 + +2026.03.26, v19.9.5 + +fix: +- e761cacb columns: name-size-date-time: 20% -> 19% + +2026.03.26, v19.9.4 + +feature: +- 69498ed6 docker: io: pull.rebase by default + +2026.03.24, v19.9.3 + +feature: +- 8763788b docker: io: healthcheck + +2026.03.23, v19.9.2 + +feature: +- 09a02074 docker: io: git config: add +- c448eaa4 docker: io: buni + +2026.03.23, v19.9.1 + +fix: +- 6e5318fa client: modules: config: input: quote + +2026.03.23, v19.9.0 + +feature: +- a1216cdd cloudcmd: add ability to hide port configuration + +2026.03.23, v19.8.15 + +feature: +- 68b2aa78 docker: io: cloudcmd_vim + +2026.03.23, v19.8.14 + +fix: +- 665ed9c2 docker: io: get back port + +2026.03.23, v19.8.13 + +feature: +- 618a5615 docker: io: PS1 + +2026.03.23, v19.8.12 + +feature: +- 556b0150 docker: io: PS1 + +2026.03.23, v19.8.11 + +fix: +- 97672ef5 docker: io: apt-get install -> apt-get + +feature: +- 67f27d84 docker: io: bun, deno + +2026.03.23, v19.8.10 + +feature: +- 025b005e docker: io: add PS1 + +2026.03.23, v19.8.9 + +fix: +- b052cf22 cloudcmd: no time available: --.--.---- -> --:--:-- (#461) + +2026.03.23, v19.8.8 + +feature: +- 02dbe56d server: user-menu: when error send it + +2026.03.23, v19.8.7 + +feature: +- ecc76e8b docker: io: renamify-cli, runny, redfork + +2026.03.23, v19.8.6 + +fix: +- 53c072ab @putout/plugin-cloudcmd: devDependencies -> dependnecies + +feature: +- 4b9922bf docker: /usr/src: app -> cloudcmd + +2026.03.23, v19.8.5 + +fix: +- 56fc8b83 docker: gritty + +2026.03.22, v19.8.4 + +feature: +- 5000227e docker: vim nvim + +2026.03.22, v19.8.3 + +fix: +- 01677e6a docker: io: slim -> io + +feature: +- 7e35c606 docker: io: curl wget + +2026.03.22, v19.8.2 + +feature: +- e5b221f7 docker: io: add + +2026.03.22, v19.8.1 + +feature: +- 708a4c6b docker: slim: add +- 80613f46 docker: slim: add + +2026.03.20, v19.8.0 + +fix: +- 59037f2c cloudcmd: bin: --show-config + +feature: +- 10934b3a cloudcmd: add ability to show modification time (#230) + +2026.03.18, v19.7.1 + +feature: +- b0c1d36c cloudcmd: @cloudcmd/fileop v9.0.7 (#460) + +2026.03.17, v19.7.0 + +feature: +- daf83875 cloudfunc: override date format (#459) + +2026.03.17, v19.6.9 + +feature: +- b28a070a cloudcmd: redzip v4.6.1 +- 43c5a011 cloudcmd: css-minimizer-webpack-plugin v8.0.0 +- 15dcae5c cloudcmd: webpack-cli v7.0.2 +- 5976da81 cloudcmd: @cloudcmd/fileop v9.0.5 +- 37cb83f2 cloudcmd: redzip v4.6.0 + +2026.02.27, v19.6.8 + +feature: +- 15fab514 cloudcmd: copymitter v10.3.0 (#458) + +2026.02.26, v19.6.7 + +feature: +- 68c7d0be cloudcmd: onezip v7.0.0 + +2026.02.26, v19.6.6 + +feature: +- 3987cc82 cloudcmd: redzip v4.5.1 (#457) + +2026.02.26, v19.6.5 + +feature: +- 964ae989 cloudcmd: redzip v4.5.0 (#457) + +2026.02.25, v19.6.4 + +feature: +- a66eeda3 cloudcmd: copymitter v10.2.0 (coderaiser/cloudcmd#457) +- 4340533a cloudcmd: c8 v11.0.0 +- 0857711f cloudcmd: redzip v4.2.0 (#457) + +2026.02.24, v19.6.3 + +feature: +- 2234f1b4 cloudcmd: redzip v4.2.0 (#475) + +2026.02.24, v19.6.2 + +feature: +- 321a54dd cloudcmd: @cloudcmd/fileop v9.0.2 (#457) + +2026.02.24, v19.6.1 + +feature: +- 7c5ac408 cloudcmd: @cloudcmd/fileop v9.0.1 (#457) + +2026.02.21, v19.6.0 + +feature: +- 6d19bf2e common: object.omit -> omit + +2026.02.18, v19.5.1 + +feature: +- 6e1cf4ef cloudcmd: supermenu v5.0.0 + +2026.02.18, v19.5.0 + +feature: +- b20539ef common: entity: encode {,} +- 7ef134f4 cloudcmd: rendy v5.0.0 + +2026.02.18, v19.4.1 + +feature: +- 1e18d513 cloudcmd: @cloudcmd/fileop v9.0.0 + +2026.02.18, v19.4.0 + +fix: +- 45cf9baf menu: prefix (#456) + +feature: +- 3e647290 cloudcmd: redlint v6.0.0 +- 800ed012 cloudcmd: putout v42.0.5 +- 525c17d4 cloudcmd: madrun v13.0.0 +- 44247499 cloudcmd: eslint-plugin-putout v31.0.0 + +2026.02.15, v19.3.9 + +feature: +- 9ffe3ef1 cloudcmd: copymitter v10.0.0 (#457) + +2026.02.15, v19.3.8 + +fix: +- d274a2b3 spinner (#456) + +2026.02.13, v19.3.7 + +feature: +- 8fd79a27 cloudcmd: win32 v8.0.0 + +2026.02.12, v19.3.6 + +feature: +- ac94eccd cloudcmd: konsole: named +- 144e4a34 cloudcmd: gritty v10.0.0 +- 938f9e76 cloudcmd: console-io v15.0.1 + +2026.02.08, v19.3.5 + +feature: +- fb40bd9c rm cssnano: has no sense for spinner, option disabled by default (https://svgo.dev/docs/plugins/convertPathData/) +- 76125be9 cloudcmd: eslint v10.0.0 + +2026.02.06, v19.3.4 + +feature: +- 66a08c7f cloudcmd: deepword v11.0.0 + +2026.02.05, v19.3.3 + +feature: +- 4a5a56f4 cloudcmd: dword v16.0.0 + +2026.02.04, v19.3.2 + +fix: +- 99d8435e cloudcmd: exports +- a266c145 cloudcmd: default -> named +- 6e3ba271 Closing X in editor disappeared (#455) + +feature: +- 30f42e94 cloudcmd: restafary v13.0.1 +- f84ce853 cloudcmd: edward v16.0.0 + +2026.02.03, v19.3.1 + +fix: +- 5661bc4f Closing X in editor disappeared (#455) + +2026.02.03, v19.3.0 + +feature: +- 4533a25c cloudcmd: migrate to ESM +- e8a81c49 client: dom: events: migrate to ESM +- 071141bc client: terminal-run: migrate to ESM + +2026.02.03, v19.2.0 + +feature: +- bb32f7c4 client: dom: migrate to ESM +- 457c83db client: migrate to ESM +- f8a941bd client: modules: markdown: migrate to ESM +- 9d6cffaf client: polifyll: migrate to ESM +- 5b704d06 client: edit-names: migrate to ESM +- 2c2ca8eb client: edit-file: migrate to ESM +- c9f57c5f client: modules: edit-file-vim: migrate to ESM +- 327ac9de client: modules: help: migrate to ESM +- dc34ee87 cloudcmd: @putout/plugin-cloudcmd v5.0.0 +- 3cd3695b client: modules: edit-names-vim: migrate to ESM +- dfcbfd63 client: modules: terminal: migrate to ESM + +2026.02.02, v19.1.21 + +feature: +- e3ad330e client: konsole: migrate to ESM +- 983fd9af client: edit: migrate to ESM +- ebabad94 common: entity: migrate to ESM +- c3b71653 client: dom: images -> #images +- 8cad7514 common: cloudfunc -> #common/cloudfunc +- 1f174870 client: view: migrate to ESM +- 7173f6cb cloudcmd: smalltalk v5.0.0 + +2026.01.31, v19.1.20 + +feature: +- c1014c9c client: dom: operations: rename-current: migrate to ESM +- 2e486f8b cloudcmd: restafary v13.0.0 +- 6addb29f cloudcmd: redzip v4.0.0 + +2026.01.30, v19.1.19 + +feature: +- 2a0feac7 cloudcmd: @cloudcmd/modal v4.0.0 + 2026.01.30, v19.1.18 feature: diff --git a/HELP.md b/HELP.md index 0831dcb6..2967f0d0 100644 --- a/HELP.md +++ b/HELP.md @@ -1,6 +1,6 @@ -# Cloud Commander v19.1.18 +# Cloud Commander v19.19.1 -### [Main][MainURL] [Blog][BlogURL] [Support][SupportURL] [Demo][DemoURL] +### [Main][MainURL] [Blog][BlogURL] [Demo][DemoURL] [Deploy](#deploy) [MainURL]: https://cloudcmd.io "Main" [BlogURL]: https://blog.cloudcmd.io "Blog" @@ -9,13 +9,17 @@ [DWORD]: https://github.com/cloudcmd/dword "Editor based on CodeMirror" [EDWARD]: https://github.com/cloudcmd/edward "Editor based on Ace" [DEEPWORD]: https://github.com/cloudcmd/deepword "Editor based on Monaco" +[QWORD]: https://github.com/cloudcmd/qword "Editor based on CodeMirror 6" [EDWARD_KEYS]: https://github.com/cloudcmd/edward/#hot-keys "Edward Hot keys" [TERMUX]: https://termux.com "Termux" [INLY]: https://github.com/coderaiser/node-inly "Extract archive" +[DeployInstaPodsIMG]: https://img.shields.io/badge/deploy%20on-InstaPods-blue +[DeployInstaPodsURL]: https://app.instapods.com/dashboard/pods/create?app=cloudcmd&ref=cloudcmd + **Cloud Commander** is a file manager for the web. It includes a command-line console and a text editor. Cloud Commander helps you manage your server and work with files, directories and programs in a web browser from any computer, mobile or tablet. -![Cloud Commander](/img/logo/cloudcmd.png "Cloud Commander") +![Cloud Commander](https://cloudcmd.io/img/logo/cloudcmd.png "Cloud Commander") ## Benefits @@ -26,7 +30,7 @@ - Server works on **Windows**, **Linux**, **Mac OS** and **Android** (with help of [Termux][TERMUX]). - Can be used local or remotely. - Adapts to screen size. -- **3 built-in editors** with support of **syntax highlighting**: [Dword][DWORD], [Edward][EDWARD] and [Deepword][DEEPWORD]. +- **3 built-in editors** with support of **syntax highlighting**: [Dword][DWORD], [Edward][EDWARD] [Deepword][DEEPWORD] and [Qword][QWORD]. - [Console](https://github.com/cloudcmd/console "Console") with support of the default OS command line. - Written in **JavaScript/Node.js**. - Built-in archives pack: **zip** and **tar.gz**. @@ -88,6 +92,7 @@ Cloud Commander supports the following command-line parameters: | `--contact` | enable contact | `--config-dialog` | enable config dialog | `--config-auth` | enable auth change in config dialog +| `--config-port` | enable port change in config dialog | `--console` | enable console | `--sync-console-path` | sync console path | `--terminal` | enable terminal @@ -118,6 +123,7 @@ Cloud Commander supports the following command-line parameters: | `--no-confirm-move` | do not confirm move | `--no-config-dialog` | disable config dialog | `--no-config-auth` | disable auth change in config dialog +| `--no-config-port` | disable port change in config dialog | `--no-console` | disable console | `--no-sync-console-path` | do not sync console path | `--no-contact` | disable contact @@ -189,6 +195,7 @@ Then, start the server again with `cloudcmd` and reload the page. | `Ctrl + F3` | sort by name | `Ctrl + F5` | sort by date | `Ctrl + F6` | sort by size +| `Ctrl + Shift + L` | logout | `Ctrl + Command + .` | show/hide dot files | `Up` | move cursor up | `Down` | move cursor down @@ -211,24 +218,27 @@ Then, start the server again with `cloudcmd` and reload the page. When the `--vim` option is provided, or the configuration parameter `vim` is set, the following hotkeys become available: -|Key |Operation -|:----------------------|:-------------------------------------------- -| `j` | navigate to next file -| `k` | navigate to previous file -| `dd` | remove current file -| `G` or `$` | navigate to bottom file -| `gg` or `^` | navigate to top file -| `v` | visual mode -| `y` | copy (selected in visual mode files) -| `p` | paste files -| `Esc` | unselect all -| `/` | find file in current directory -| `n` | navigate to next found file -| `N` | navigate to previous found file -| `md` | make directory -| `mf` | make file -| `tt` | show terminal -| `e` | edit file +| Key |Operation +|:------------|:-------------------------------------------- +| `j` | navigate to next file +| `k` | navigate to previous file +| `dd` | remove current file +| `G` or `$` | navigate to bottom file +| `gg` or `^` | navigate to top file +| `v` | visual mode +| `y` | copy (selected in visual mode files) +| `p` | paste files +| `Esc` | unselect all +| `/` | find file in current directory +| `n` | navigate to next found file +| `N` | navigate to previous found file +| `md` | make directory +| `mf` | make file +| `tt` | show terminal +| `e` | edit file +| `cc` | copy +| `mm` | move +| `rr` | rename file Commands can be joined, for example: @@ -440,6 +450,7 @@ Some config options can be overridden with environment variables, such as: - `CLOUDCMD_CONTACT` - enable contact - `CLOUDCMD_CONFIG_DIALOG` - enable config dialog - `CLOUDCMD_CONFIG_AUTH` - enable auth change in config dialog +- `CLOUDCMD_CONFIG_PORT` - enable port change in config dialog - `CLOUDCMD_CONSOLE` - enable console - `CLOUDCMD_SYNC_CONSOLE_PATH` - sync console path - `CLOUDCMD_TERMINAL` - enable terminal @@ -688,7 +699,7 @@ And create `index.js`: ```js import http from 'node:http'; -import cloudcmd from 'cloudcmd'; +import {cloudcmd} from 'cloudcmd'; import {Server} from 'socket.io'; import express from 'express'; @@ -922,9 +933,9 @@ ln -s ./sites-available/io.cloudcmd.io ./sites-enabled ## Deploy -`Cloud Commander` can be easily deployed to [Heroku](https://heroku.com/deploy?template=https://github.com/coderaiser/cloudcmd "Deploy to Heroku"). +`Cloud Commander` can be easily deployed to [InstaPods][DeployInstaPodsURL]. After deploy you receive email with credentials. -[![Deploy to Heroku](https://www.herokucdn.com/deploy/button.png "Deploy to Heroku")](https://heroku.com/deploy?template=https://github.com/coderaiser/cloudcmd) +[![Deploy on InstaPods](https://instapods.com/deploy-button.svg)](https://app.instapods.com/dashboard/pods/create?app=cloudcmd&ref=cloudcmd) ## Docker @@ -1111,6 +1122,111 @@ There are a lot of ways to be involved in `Cloud Commander` development: ## Version history +- *2026.06.15*, **[v19.19.1](//github.com/coderaiser/cloudcmd/releases/tag/v19.19.1)** +- *2026.05.26*, **[v19.19.0](//github.com/coderaiser/cloudcmd/releases/tag/v19.19.0)** +- *2026.05.26*, **[v19.18.1](//github.com/coderaiser/cloudcmd/releases/tag/v19.18.1)** +- *2026.05.26*, **[v19.18.0](//github.com/coderaiser/cloudcmd/releases/tag/v19.18.0)** +- *2026.05.17*, **[v19.17.0](//github.com/coderaiser/cloudcmd/releases/tag/v19.17.0)** +- *2026.05.03*, **[v19.16.0](//github.com/coderaiser/cloudcmd/releases/tag/v19.16.0)** +- *2026.04.28*, **[v19.15.0](//github.com/coderaiser/cloudcmd/releases/tag/v19.15.0)** +- *2026.04.28*, **[v19.14.0](//github.com/coderaiser/cloudcmd/releases/tag/v19.14.0)** +- *2026.04.21*, **[v19.13.1](//github.com/coderaiser/cloudcmd/releases/tag/v19.13.1)** +- *2026.04.15*, **[v19.13.0](//github.com/coderaiser/cloudcmd/releases/tag/v19.13.0)** +- *2026.04.12*, **[v19.12.5](//github.com/coderaiser/cloudcmd/releases/tag/v19.12.5)** +- *2026.04.12*, **[v19.12.4](//github.com/coderaiser/cloudcmd/releases/tag/v19.12.4)** +- *2026.04.12*, **[v19.12.3](//github.com/coderaiser/cloudcmd/releases/tag/v19.12.3)** +- *2026.04.11*, **[v19.12.2](//github.com/coderaiser/cloudcmd/releases/tag/v19.12.2)** +- *2026.04.09*, **[v19.12.1](//github.com/coderaiser/cloudcmd/releases/tag/v19.12.1)** +- *2026.04.09*, **[v19.12.0](//github.com/coderaiser/cloudcmd/releases/tag/v19.12.0)** +- *2026.04.07*, **[v19.11.14](//github.com/coderaiser/cloudcmd/releases/tag/v19.11.14)** +- *2026.04.07*, **[v19.11.13](//github.com/coderaiser/cloudcmd/releases/tag/v19.11.13)** +- *2026.04.06*, **[v19.11.12](//github.com/coderaiser/cloudcmd/releases/tag/v19.11.12)** +- *2026.04.06*, **[v19.11.11](//github.com/coderaiser/cloudcmd/releases/tag/v19.11.11)** +- *2026.04.06*, **[v19.11.10](//github.com/coderaiser/cloudcmd/releases/tag/v19.11.10)** +- *2026.04.06*, **[v19.11.9](//github.com/coderaiser/cloudcmd/releases/tag/v19.11.9)** +- *2026.04.06*, **[v19.11.8](//github.com/coderaiser/cloudcmd/releases/tag/v19.11.8)** +- *2026.04.05*, **[v19.11.7](//github.com/coderaiser/cloudcmd/releases/tag/v19.11.7)** +- *2026.04.05*, **[v19.11.6](//github.com/coderaiser/cloudcmd/releases/tag/v19.11.6)** +- *2026.04.05*, **[v19.11.5](//github.com/coderaiser/cloudcmd/releases/tag/v19.11.5)** +- *2026.04.04*, **[v19.11.4](//github.com/coderaiser/cloudcmd/releases/tag/v19.11.4)** +- *2026.04.04*, **[v19.11.3](//github.com/coderaiser/cloudcmd/releases/tag/v19.11.3)** +- *2026.04.04*, **[v19.11.2](//github.com/coderaiser/cloudcmd/releases/tag/v19.11.2)** +- *2026.04.04*, **[v19.11.1](//github.com/coderaiser/cloudcmd/releases/tag/v19.11.1)** +- *2026.04.04*, **[v19.11.0](//github.com/coderaiser/cloudcmd/releases/tag/v19.11.0)** +- *2026.04.04*, **[v19.10.2](//github.com/coderaiser/cloudcmd/releases/tag/v19.10.2)** +- *2026.04.03*, **[v19.10.1](//github.com/coderaiser/cloudcmd/releases/tag/v19.10.1)** +- *2026.04.02*, **[v19.10.0](//github.com/coderaiser/cloudcmd/releases/tag/v19.10.0)** +- *2026.04.02*, **[v19.9.24](//github.com/coderaiser/cloudcmd/releases/tag/v19.9.24)** +- *2026.04.01*, **[v19.9.23](//github.com/coderaiser/cloudcmd/releases/tag/v19.9.23)** +- *2026.03.31*, **[v19.9.22](//github.com/coderaiser/cloudcmd/releases/tag/v19.9.22)** +- *2026.03.31*, **[v19.9.21](//github.com/coderaiser/cloudcmd/releases/tag/v19.9.21)** +- *2026.03.30*, **[v19.9.20](//github.com/coderaiser/cloudcmd/releases/tag/v19.9.20)** +- *2026.03.30*, **[v19.9.19](//github.com/coderaiser/cloudcmd/releases/tag/v19.9.19)** +- *2026.03.30*, **[v19.9.18](//github.com/coderaiser/cloudcmd/releases/tag/v19.9.18)** +- *2026.03.30*, **[v19.9.17](//github.com/coderaiser/cloudcmd/releases/tag/v19.9.17)** +- *2026.03.29*, **[v19.9.16](//github.com/coderaiser/cloudcmd/releases/tag/v19.9.16)** +- *2026.03.29*, **[v19.9.15](//github.com/coderaiser/cloudcmd/releases/tag/v19.9.15)** +- *2026.03.29*, **[v19.9.14](//github.com/coderaiser/cloudcmd/releases/tag/v19.9.14)** +- *2026.03.29*, **[v19.9.13](//github.com/coderaiser/cloudcmd/releases/tag/v19.9.13)** +- *2026.03.29*, **[v19.9.12](//github.com/coderaiser/cloudcmd/releases/tag/v19.9.12)** +- *2026.03.29*, **[v19.9.11](//github.com/coderaiser/cloudcmd/releases/tag/v19.9.11)** +- *2026.03.29*, **[v19.9.10](//github.com/coderaiser/cloudcmd/releases/tag/v19.9.10)** +- *2026.03.29*, **[v19.9.9](//github.com/coderaiser/cloudcmd/releases/tag/v19.9.9)** +- *2026.03.28*, **[v19.9.8](//github.com/coderaiser/cloudcmd/releases/tag/v19.9.8)** +- *2026.03.27*, **[v19.9.7](//github.com/coderaiser/cloudcmd/releases/tag/v19.9.7)** +- *2026.03.26*, **[v19.9.6](//github.com/coderaiser/cloudcmd/releases/tag/v19.9.6)** +- *2026.03.26*, **[v19.9.5](//github.com/coderaiser/cloudcmd/releases/tag/v19.9.5)** +- *2026.03.26*, **[v19.9.4](//github.com/coderaiser/cloudcmd/releases/tag/v19.9.4)** +- *2026.03.24*, **[v19.9.3](//github.com/coderaiser/cloudcmd/releases/tag/v19.9.3)** +- *2026.03.23*, **[v19.9.2](//github.com/coderaiser/cloudcmd/releases/tag/v19.9.2)** +- *2026.03.23*, **[v19.9.1](//github.com/coderaiser/cloudcmd/releases/tag/v19.9.1)** +- *2026.03.23*, **[v19.9.0](//github.com/coderaiser/cloudcmd/releases/tag/v19.9.0)** +- *2026.03.23*, **[v19.8.15](//github.com/coderaiser/cloudcmd/releases/tag/v19.8.15)** +- *2026.03.23*, **[v19.8.14](//github.com/coderaiser/cloudcmd/releases/tag/v19.8.14)** +- *2026.03.23*, **[v19.8.13](//github.com/coderaiser/cloudcmd/releases/tag/v19.8.13)** +- *2026.03.23*, **[v19.8.12](//github.com/coderaiser/cloudcmd/releases/tag/v19.8.12)** +- *2026.03.23*, **[v19.8.11](//github.com/coderaiser/cloudcmd/releases/tag/v19.8.11)** +- *2026.03.23*, **[v19.8.10](//github.com/coderaiser/cloudcmd/releases/tag/v19.8.10)** +- *2026.03.23*, **[v19.8.9](//github.com/coderaiser/cloudcmd/releases/tag/v19.8.9)** +- *2026.03.23*, **[v19.8.8](//github.com/coderaiser/cloudcmd/releases/tag/v19.8.8)** +- *2026.03.23*, **[v19.8.7](//github.com/coderaiser/cloudcmd/releases/tag/v19.8.7)** +- *2026.03.23*, **[v19.8.6](//github.com/coderaiser/cloudcmd/releases/tag/v19.8.6)** +- *2026.03.23*, **[v19.8.5](//github.com/coderaiser/cloudcmd/releases/tag/v19.8.5)** +- *2026.03.22*, **[v19.8.4](//github.com/coderaiser/cloudcmd/releases/tag/v19.8.4)** +- *2026.03.22*, **[v19.8.3](//github.com/coderaiser/cloudcmd/releases/tag/v19.8.3)** +- *2026.03.22*, **[v19.8.2](//github.com/coderaiser/cloudcmd/releases/tag/v19.8.2)** +- *2026.03.22*, **[v19.8.1](//github.com/coderaiser/cloudcmd/releases/tag/v19.8.1)** +- *2026.03.20*, **[v19.8.0](//github.com/coderaiser/cloudcmd/releases/tag/v19.8.0)** +- *2026.03.18*, **[v19.7.1](//github.com/coderaiser/cloudcmd/releases/tag/v19.7.1)** +- *2026.03.17*, **[v19.7.0](//github.com/coderaiser/cloudcmd/releases/tag/v19.7.0)** +- *2026.03.17*, **[v19.6.9](//github.com/coderaiser/cloudcmd/releases/tag/v19.6.9)** +- *2026.02.27*, **[v19.6.8](//github.com/coderaiser/cloudcmd/releases/tag/v19.6.8)** +- *2026.02.26*, **[v19.6.7](//github.com/coderaiser/cloudcmd/releases/tag/v19.6.7)** +- *2026.02.26*, **[v19.6.6](//github.com/coderaiser/cloudcmd/releases/tag/v19.6.6)** +- *2026.02.26*, **[v19.6.5](//github.com/coderaiser/cloudcmd/releases/tag/v19.6.5)** +- *2026.02.25*, **[v19.6.4](//github.com/coderaiser/cloudcmd/releases/tag/v19.6.4)** +- *2026.02.24*, **[v19.6.3](//github.com/coderaiser/cloudcmd/releases/tag/v19.6.3)** +- *2026.02.24*, **[v19.6.2](//github.com/coderaiser/cloudcmd/releases/tag/v19.6.2)** +- *2026.02.24*, **[v19.6.1](//github.com/coderaiser/cloudcmd/releases/tag/v19.6.1)** +- *2026.02.21*, **[v19.6.0](//github.com/coderaiser/cloudcmd/releases/tag/v19.6.0)** +- *2026.02.18*, **[v19.5.1](//github.com/coderaiser/cloudcmd/releases/tag/v19.5.1)** +- *2026.02.18*, **[v19.5.0](//github.com/coderaiser/cloudcmd/releases/tag/v19.5.0)** +- *2026.02.18*, **[v19.4.1](//github.com/coderaiser/cloudcmd/releases/tag/v19.4.1)** +- *2026.02.18*, **[v19.4.0](//github.com/coderaiser/cloudcmd/releases/tag/v19.4.0)** +- *2026.02.15*, **[v19.3.9](//github.com/coderaiser/cloudcmd/releases/tag/v19.3.9)** +- *2026.02.15*, **[v19.3.8](//github.com/coderaiser/cloudcmd/releases/tag/v19.3.8)** +- *2026.02.13*, **[v19.3.7](//github.com/coderaiser/cloudcmd/releases/tag/v19.3.7)** +- *2026.02.12*, **[v19.3.6](//github.com/coderaiser/cloudcmd/releases/tag/v19.3.6)** +- *2026.02.08*, **[v19.3.5](//github.com/coderaiser/cloudcmd/releases/tag/v19.3.5)** +- *2026.02.06*, **[v19.3.4](//github.com/coderaiser/cloudcmd/releases/tag/v19.3.4)** +- *2026.02.05*, **[v19.3.3](//github.com/coderaiser/cloudcmd/releases/tag/v19.3.3)** +- *2026.02.04*, **[v19.3.2](//github.com/coderaiser/cloudcmd/releases/tag/v19.3.2)** +- *2026.02.03*, **[v19.3.1](//github.com/coderaiser/cloudcmd/releases/tag/v19.3.1)** +- *2026.02.03*, **[v19.3.0](//github.com/coderaiser/cloudcmd/releases/tag/v19.3.0)** +- *2026.02.03*, **[v19.2.0](//github.com/coderaiser/cloudcmd/releases/tag/v19.2.0)** +- *2026.02.02*, **[v19.1.21](//github.com/coderaiser/cloudcmd/releases/tag/v19.1.21)** +- *2026.01.31*, **[v19.1.20](//github.com/coderaiser/cloudcmd/releases/tag/v19.1.20)** +- *2026.01.30*, **[v19.1.19](//github.com/coderaiser/cloudcmd/releases/tag/v19.1.19)** - *2026.01.30*, **[v19.1.18](//github.com/coderaiser/cloudcmd/releases/tag/v19.1.18)** - *2026.01.29*, **[v19.1.17](//github.com/coderaiser/cloudcmd/releases/tag/v19.1.17)** - *2026.01.28*, **[v19.1.16](//github.com/coderaiser/cloudcmd/releases/tag/v19.1.16)** diff --git a/README.md b/README.md index 5d91496b..dfe8d8c6 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Cloud Commander v19.1.18 [![Build Status][BuildStatusIMGURL]][BuildStatusURL] [![Codacy][CodacyIMG]][CodacyURL] [![Gitter][GitterIMGURL]][GitterURL] +# Cloud Commander v19.19.1 [![Build Status][BuildStatusIMGURL]][BuildStatusURL] [![Codacy][CodacyIMG]][CodacyURL] [![Gitter][GitterIMGURL]][GitterURL] [![Deploy on InstaPods][DeployInstaPodsIMG]][DeployInstaPodsURL] ### [Main][MainURL] [Blog][BlogURL] [Support][SupportURL] [Demo][DemoURL] @@ -15,6 +15,8 @@ [GitterIMGURL]: https://img.shields.io/gitter/room/coderaiser/cloudcmd.js.svg [DeployURL]: https://heroku.com/deploy?template=https://github.com/coderaiser/cloudcmd "Deploy" [DeployIMG]: https://www.herokucdn.com/deploy/button.png +[DeployInstaPodsIMG]: https://img.shields.io/badge/deploy%20on-InstaPods-blue +[DeployInstaPodsURL]: https://app.instapods.com/dashboard/pods/create?app=cloudcmd&ref=cloudcmd **Cloud Commander** a file manager for the web with console and editor. @@ -69,7 +71,7 @@ And create `index.js`: ```js import http from 'node:http'; -import cloudcmd from 'cloudcmd'; +import {cloudcmd} from 'cloudcmd'; import {Server} from 'socket.io'; import express from 'express'; diff --git a/bin/cloudcmd.mjs b/bin/cloudcmd.js similarity index 87% rename from bin/cloudcmd.mjs rename to bin/cloudcmd.js index e1c358ed..6ca6716c 100755 --- a/bin/cloudcmd.mjs +++ b/bin/cloudcmd.js @@ -1,24 +1,21 @@ #!/usr/bin/env node import process from 'node:process'; -import {createRequire} from 'node:module'; import {promisify} from 'node:util'; import {tryToCatch} from 'try-to-catch'; -import {createSimport} from 'simport'; import parse from 'yargs-parser'; import exit from '../server/exit.js'; -import {createConfig, configPath} from '../server/config.mjs'; -import * as env from '../server/env.mjs'; +import {createConfig, configPath} from '../server/config.js'; +import * as env from '../server/env.js'; import prefixer from '../server/prefixer.js'; -import * as validate from '../server/validate.mjs'; +import * as validate from '../server/validate.js'; +import Info from '../package.json' with { + type: 'json', +}; process.on('unhandledRejection', exit); -const require = createRequire(import.meta.url); - -const Info = require('../package.json'); const isUndefined = (a) => typeof a === 'undefined'; -const simport = createSimport(import.meta.url); const choose = (a, b) => { if (isUndefined(a)) @@ -31,8 +28,6 @@ const config = createConfig({ configPath, }); -const DIR_SERVER = '../server/'; - const maybeRoot = (a) => { if (a === '.') return process.cwd(); @@ -78,6 +73,7 @@ const yargsOptions = { 'open', 'config-dialog', 'config-auth', + 'config-port', 'console', 'sync-console-path', 'contact', @@ -119,7 +115,7 @@ const yargsOptions = { 'columns': env.parse('columns') || config('columns') || '', 'theme': env.parse('theme') || config('theme') || '', 'vim': choose(env.bool('vim'), config('vim')), - 'log': config('log'), + 'log': choose(env.bool('log'), config('log')), 'import-url': env.parse('import_url') || config('importUrl'), 'import-listen': choose(env.bool('import_listen'), config('importListen')), @@ -132,6 +128,7 @@ const yargsOptions = { 'sync-console-path': choose(env.bool('sync_console_path'), config('syncConsolePath')), 'config-dialog': choose(env.bool('config_dialog'), config('configDialog')), 'config-auth': choose(env.bool('config_auth'), config('configAuth')), + 'config-port': choose(env.bool('config_port'), config('configPort')), 'terminal-path': env.parse('terminal_path') || config('terminalPath'), 'terminal-command': env.parse('terminal_command') || config('terminalCommand'), 'terminal-auto-restart': choose(env.bool('terminal_auto_restart'), config('terminalAutoRestart')), @@ -168,7 +165,7 @@ else main(); async function main() { - const {validateArgs} = await simport('@putout/cli-validate-args'); + const {validateArgs} = await import('@putout/cli-validate-args'); const error = await validateArgs(args, [ ...yargsOptions.boolean, @@ -179,7 +176,7 @@ async function main() { return exit(error); if (args.repl) - repl(); + await repl(); validate.columns(args.columns); validate.theme(args.theme); @@ -214,6 +211,7 @@ async function main() { config('oneFilePanel', args.oneFilePanel); config('configDialog', args.configDialog); config('configAuth', args.configAuth); + config('configPort', args.configPort); config('keysPanel', args.keysPanel); config('export', args.export); config('exportToken', args.exportToken); @@ -248,7 +246,7 @@ async function main() { if (args.showConfig) await showConfig(); - const {distributeImport} = await simport('../server/distribute/import.mjs'); + const {distributeImport} = await import('../server/distribute/import.js'); const importConfig = promisify(distributeImport); await start(options, config); @@ -266,11 +264,12 @@ function validateRoot(root, config) { if (root === '/') return; - console.log(`root: ${root}`); + if (config('log')) + console.log(`root: ${root}`); } async function getPassword(password) { - const criton = await simport('criton'); + const {default: criton} = await import('criton'); return criton(password, config('algo')); } @@ -279,12 +278,10 @@ function version() { } async function start(options, config) { - const SERVER = `${DIR_SERVER}server.mjs`; - if (!args.server) return; - const server = await simport(SERVER); + const {default: server} = await import('../server/server.js'); server(options, config); } @@ -298,8 +295,8 @@ function port(arg) { } async function showConfig() { - const show = await simport('../server/show-config'); - const data = show(config('*')); + const {showConfig} = await import('../server/show-config.js'); + const data = showConfig(config('*')); console.log(data); } @@ -308,13 +305,13 @@ async function readConfig(name) { if (!name) return; - const tryToCatch = await simport('try-to-catch'); - const forEachKey = await simport('for-each-key'); + const {default: forEachKey} = await import('for-each-key'); - const [error, data] = await tryToCatch(simport, name); - - if (error) - return exit(error.message); + const data = await import(name, { + with: { + type: 'json', + }, + }); forEachKey(config, data); } @@ -326,8 +323,8 @@ async function help() { }, }); - const forEachKey = await simport('for-each-key'); - const currify = await simport('currify'); + const {default: forEachKey} = await import('for-each-key'); + const {default: currify} = await import('currify'); const usage = 'Usage: cloudcmd [options]'; const url = Info.homepage; @@ -339,15 +336,15 @@ async function help() { console.log('\nGeneral help using Cloud Commander: <%s>', url); } -function repl() { +async function repl() { console.log('REPL mode enabled (telnet localhost 1337)'); - require(`${DIR_SERVER}repl`); + await import('../server/repl.js'); } async function checkUpdate() { - const load = await simport('package-json'); - + const {default: load} = await import('package-json'); const {version} = await load(Info.name, 'latest'); + await showUpdateInfo(version); } @@ -355,7 +352,7 @@ async function showUpdateInfo(version) { if (version === Info.version) return; - const chalk = await simport('chalk'); + const {default: chalk} = await import('chalk'); const latestVersion = chalk.green.bold(`v${version}`); const latest = `update available: ${latestVersion}`; diff --git a/bin/cloudcmd.spec.js b/bin/cloudcmd.spec.js new file mode 100644 index 00000000..d09541b9 --- /dev/null +++ b/bin/cloudcmd.spec.js @@ -0,0 +1,26 @@ +import {spawnSync} from 'node:child_process'; +import {test} from 'supertape'; +import info from '../package.json' with { + type: 'json', +}; + +const cliPath = new URL('cloudcmd.js', import.meta.url).pathname; + +test('cloudcmd: bin: cli: -h', (t) => { + const {stdout} = spawnSync(cliPath, ['-h'], { + encoding: 'utf8', + }); + + t.match(stdout, `Options`); + t.end(); +}); + +test('cloudcmd: bin: cli: -v', (t) => { + const {version} = info; + const {stdout} = spawnSync(cliPath, ['-v'], { + encoding: 'utf8', + }); + + t.match(stdout, `v${version}`); + t.end(); +}); diff --git a/bin/release.mjs b/bin/release.js similarity index 84% rename from bin/release.mjs rename to bin/release.js index 004b908e..86e1d714 100755 --- a/bin/release.mjs +++ b/bin/release.js @@ -3,26 +3,27 @@ import {promisify} from 'node:util'; import process from 'node:process'; import {tryToCatch} from 'try-to-catch'; -import {createSimport} from 'simport'; import minor from 'minor'; import _place from 'place'; -import rendy from 'rendy'; +import {rendy} from 'rendy'; import shortdate from 'shortdate'; +import Info from '../package.json' with { + type: 'json', +}; -const simport = createSimport(import.meta.url); const place = promisify(_place); -const Info = await simport('../package.json'); - await main(); async function main() { const history = '## Version history\n\n'; const link = '//github.com/coderaiser/cloudcmd/releases/tag/'; + const template = '- ' + '*{{ date }}*, ' + '**[v{{ version }}]' + - '(' + link + + '(' + + link + 'v{{ version }})**\n'; const {version} = Info; @@ -35,10 +36,11 @@ async function main() { await replaceVersion('README.md', version, versionNew); await replaceVersion('HELP.md', version, versionNew); - const historyNew = history + rendy(template, { - date: shortdate(), - version: versionNew, - }); + const historyNew = history + + rendy(template, { + date: shortdate(), + version: versionNew, + }); await replaceVersion('HELP.md', history, historyNew); } diff --git a/client/client.mjs b/client/client.js similarity index 96% rename from client/client.mjs rename to client/client.js index b2f55b36..a0e3cb07 100644 --- a/client/client.mjs +++ b/client/client.js @@ -1,24 +1,22 @@ import process from 'node:process'; - -/* global DOM */ import Emitify from 'emitify'; import inherits from 'inherits'; -import rendy from 'rendy'; +import {rendy} from 'rendy'; import load from 'load.js'; import {tryToCatch} from 'try-to-catch'; import {addSlashToEnd} from 'format-io'; import pascalCase from 'just-pascal-case'; import currify from 'currify'; -import * as Images from './dom/images.mjs'; -import {unregisterSW} from './sw/register.mjs'; -import {getJsonFromFileTable} from './get-json-from-file-table.mjs'; -import {Key} from './key/index.mjs'; import { apiURL, formatMsg, buildFromJSON, -} from '../common/cloudfunc.mjs'; -import {loadModule} from './load-module.mjs'; +} from '#common/cloudfunc'; +import * as Images from '#dom/images'; +import {unregisterSW} from './sw/register.js'; +import {getJsonFromFileTable} from './get-json-from-file-table.js'; +import {Key} from './key/index.js'; +import {loadModule} from './load-module.js'; const noJS = (a) => a.replace(/.js$/, ''); @@ -350,7 +348,12 @@ function CloudCmdProto({DOM, Listeners}) { 'pathLink', ]; - const [error, [file, path, link, pathLink]] = await tryToCatch(Files.get, names); + const [error, [ + file, + path, + link, + pathLink, + ]] = await tryToCatch(Files.get, names); if (error) return DOM.Dialog.alert(error.responseText); diff --git a/client/cloudcmd.mjs b/client/cloudcmd.js similarity index 81% rename from client/cloudcmd.mjs rename to client/cloudcmd.js index 5a207f8a..f2416fd4 100644 --- a/client/cloudcmd.mjs +++ b/client/cloudcmd.js @@ -1,14 +1,14 @@ +import '../css/main.css'; import process from 'node:process'; import wraptile from 'wraptile'; import load from 'load.js'; import * as Util from '#common/util'; -import '../css/main.css'; -import {registerSW, listenSW} from './sw/register.mjs'; -import {initSortPanel, sortPanel} from './sort.mjs'; -import * as CloudFunc from '../common/cloudfunc.mjs'; -import DOM from './dom/index.mjs'; -import {createCloudCmd} from './client.mjs'; -import * as Listeners from './listeners/index.mjs'; +import * as CloudFunc from '#common/cloudfunc'; +import DOM from '#dom'; +import {registerSW, listenSW} from './sw/register.js'; +import {initSortPanel, sortPanel} from './sort.js'; +import {createCloudCmd} from './client.js'; +import * as Listeners from './listeners/index.js'; const isDev = process.env.NODE_ENV === 'development'; @@ -36,7 +36,7 @@ async function init(config) { if (globalThis.CloudCmd.config('menu') === 'aleman') setTimeout(() => { import('https://esm.sh/@putout/processor-html'); - import('https://esm.sh/@putout/bundle'); + import('https://esm.sh/@putout/bundle@5.5'); }, 100); } diff --git a/client/dom/buffer.mjs b/client/dom/buffer.js similarity index 100% rename from client/dom/buffer.mjs rename to client/dom/buffer.js diff --git a/client/dom/cmd.mjs b/client/dom/cmd.js similarity index 100% rename from client/dom/cmd.mjs rename to client/dom/cmd.js diff --git a/client/dom/current-file.mjs b/client/dom/current-file.js similarity index 96% rename from client/dom/current-file.mjs rename to client/dom/current-file.js index 124c1e62..b8113054 100644 --- a/client/dom/current-file.mjs +++ b/client/dom/current-file.js @@ -1,8 +1,8 @@ /* global DOM */ /* global CloudCmd */ import createElement from '@cloudcmd/create-element'; -import {encode, decode} from '../../common/entity.js'; -import {getTitle, FS} from '../../common/cloudfunc.mjs'; +import {getTitle, FS} from '#common/cloudfunc'; +import {encode, decode} from '#common/entity'; let Title; @@ -77,7 +77,7 @@ const parseHrefAttribute = (prefix, attribute) => { export const _parseHrefAttribute = parseHrefAttribute; /** - * get current direcotory path + * get current directory path */ export const getCurrentDirPath = (panel = DOM.getPanel()) => { const path = DOM.getByDataName('js-path', panel); @@ -98,7 +98,7 @@ export const getCurrentPath = (currentFile) => { }; /** - * get current direcotory name + * get current directory name */ export const getCurrentDirName = () => { const href = DOM @@ -111,7 +111,7 @@ export const getCurrentDirName = () => { }; /** - * get current direcotory path + * get current directory path */ export const getParentDirPath = (panel) => { const path = DOM.getCurrentDirPath(panel); @@ -125,7 +125,7 @@ export const getParentDirPath = (panel) => { }; /** - * get not current direcotory path + * get not current directory path */ export const getNotCurrentDirPath = () => { const panel = DOM.getPanel({ @@ -153,7 +153,7 @@ export const getCurrentByName = (name, panel = DOM.CurrentInfo.panel) => { }; /** - * private function thet unset currentFile + * private function that unset currentFile * * @currentFile */ @@ -302,6 +302,7 @@ export const isCurrentIsDir = (currentFile) => { export const getCurrentType = (currentFile) => { const current = currentFile || DOM.getCurrentFile(); const el = DOM.getByDataName('js-type', current); + const type = el.className .split(' ') .pop(); diff --git a/client/dom/current-file.spec.mjs b/client/dom/current-file.spec.js similarity index 95% rename from client/dom/current-file.spec.mjs rename to client/dom/current-file.spec.js index 2a576dce..85b18a29 100644 --- a/client/dom/current-file.spec.mjs +++ b/client/dom/current-file.spec.js @@ -1,7 +1,7 @@ import {test, stub} from 'supertape'; import {create} from 'auto-globals'; import wraptile from 'wraptile'; -import * as currentFile from './current-file.mjs'; +import * as currentFile from './current-file.js'; const id = (a) => a; @@ -19,11 +19,13 @@ test('current-file: setCurrentName: setAttribute', (t) => { currentFile.setCurrentName('hello', current); - t.calledWith(setAttribute, ['data-name', 'js-file-aGVsbG8='], 'should call setAttribute'); - globalThis.DOM = DOM; globalThis.CloudCmd = CloudCmd; + t.calledWith(setAttribute, [ + 'data-name', + 'js-file-aGVsbG8=', + ], 'should call setAttribute'); t.end(); }); @@ -38,11 +40,13 @@ test('current-file: setCurrentName: setAttribute: cyrillic', (t) => { currentFile.setCurrentName('ай', current); - t.calledWith(setAttribute, ['data-name', 'js-file-JUQwJUIwJUQwJUI5'], 'should call setAttribute'); - globalThis.DOM = DOM; globalThis.CloudCmd = CloudCmd; + t.calledWith(setAttribute, [ + 'data-name', + 'js-file-JUQwJUIwJUQwJUI5', + ], 'should call setAttribute'); t.end(); }); @@ -70,11 +74,10 @@ test('current-file: emit', (t) => { currentFile.setCurrentName('hello', current); - t.calledWith(emit, ['current-file', current], 'should call emit'); - globalThis.DOM = DOM; globalThis.CloudCmd = CloudCmd; + t.calledWith(emit, ['current-file', current], 'should call emit'); t.end(); }); @@ -90,14 +93,12 @@ test('current-file: setCurrentName: return', (t) => { globalThis.CloudCmd = getCloudCmd(); const current = create(); - const result = currentFile.setCurrentName('hello', current); - t.equal(result, link, 'should return link'); - globalThis.DOM = DOM; globalThis.CloudCmd = CloudCmd; + t.equal(result, link, 'should return link'); t.end(); }); diff --git a/client/dom/dialog.mjs b/client/dom/dialog.js similarity index 92% rename from client/dom/dialog.mjs rename to client/dom/dialog.js index 0a1e449c..b1791d92 100644 --- a/client/dom/dialog.mjs +++ b/client/dom/dialog.js @@ -1,5 +1,5 @@ import {tryToCatch} from 'try-to-catch'; -import smalltalk from 'smalltalk'; +import * as smalltalk from 'smalltalk'; const title = 'Cloud Commander'; diff --git a/client/dom/directory.mjs b/client/dom/directory.js similarity index 95% rename from client/dom/directory.mjs rename to client/dom/directory.js index e0cabab7..efbca119 100644 --- a/client/dom/directory.mjs +++ b/client/dom/directory.js @@ -1,8 +1,8 @@ /* global DOM, CloudCmd */ import philip from 'philip'; import * as Dialog from '#dom/dialog'; -import * as Images from './images.mjs'; -import {FS} from '../../common/cloudfunc.mjs'; +import {FS} from '#common/cloudfunc'; +import * as Images from '#dom/images'; export const uploadDirectory = (items) => { if (items.length) diff --git a/client/dom/dom-tree.mjs b/client/dom/dom-tree.js similarity index 100% rename from client/dom/dom-tree.mjs rename to client/dom/dom-tree.js diff --git a/client/dom/dom-tree.spec.js b/client/dom/dom-tree.spec.js new file mode 100644 index 00000000..53b376c7 --- /dev/null +++ b/client/dom/dom-tree.spec.js @@ -0,0 +1,152 @@ +import {test, stub} from 'supertape'; +import {create} from 'auto-globals'; +import {tryCatch} from 'try-catch'; +import { + isContainClass, + getByTag, + getById, + getByClass, + getByDataName, + getByClassAll, + hide, + show, +} from './dom-tree.js'; + +test('dom: isContainClass: no element', (t) => { + const [e] = tryCatch(isContainClass); + + t.equal(e.message, 'element could not be empty!', 'should throw when no element'); + t.end(); +}); + +test('dom: isContainClass: no className', (t) => { + const [e] = tryCatch(isContainClass, {}); + + t.equal(e.message, 'className could not be empty!', 'should throw when no element'); + t.end(); +}); + +test('dom: isContainClass: contains', (t) => { + const el = create(); + const {contains} = el.classList; + + const className = 'hello'; + isContainClass(el, className); + + t.calledWith(contains, [className], 'should call contains'); + t.end(); +}); + +test('dom: isContainClass: contains: array', (t) => { + const el = create(); + const {contains} = el.classList; + + const className = 'hello'; + isContainClass(el, ['world', className, 'hello']); + + t.calledWith(contains, [className], 'should call contains'); + t.end(); +}); + +test('dom: getByTag', (t) => { + const getElementsByTagName = stub(); + const element = { + getElementsByTagName, + }; + + getByTag('div', element); + + t.calledWith(getElementsByTagName, ['div'], 'should call getElementsByTagName'); + t.end(); +}); + +test('dom: getById', (t) => { + const querySelector = stub(); + const element = { + querySelector, + }; + + getById('my-id', element); + + t.calledWith(querySelector, ['#my-id'], 'should call querySelector with id selector'); + t.end(); +}); + +test('dom: getByClassAll', (t) => { + const getElementsByClassName = stub(); + const element = { + getElementsByClassName, + }; + + getByClassAll('my-class', element); + + t.calledWith(getElementsByClassName, ['my-class'], 'should call getElementsByClassName'); + t.end(); +}); + +test('dom: getByClass: calls getByClassAll', (t) => { + const element = { + getElementsByClassName: stub().returns(['first']), + }; + + const result = getByClass('my-class', element); + + t.equal(result, 'first', 'should return first element from class list'); + t.end(); +}); + +test('dom: getByDataName', (t) => { + const querySelector = stub(); + const element = { + querySelector, + }; + + getByDataName('hello', element); + + t.calledWith(querySelector, ['[data-name="hello"]'], 'should call querySelector with data-name selector'); + t.end(); +}); + +test('dom: hide', (t) => { + const add = stub(); + const element = { + classList: { + add, + }, + }; + + hide(element); + + t.calledWith(add, ['hidden'], 'should add hidden class'); + t.end(); +}); + +test('dom: show', (t) => { + const remove = stub(); + const element = { + classList: { + remove, + }, + }; + + show(element); + + t.calledWith(remove, ['hidden'], 'should remove hidden class'); + t.end(); +}); + +test('dom: getByClassAll: without element uses document', (t) => { + const getElementsByClassName = stub(); + const prevDocument = globalThis.document; + + globalThis.document = { + getElementsByClassName, + }; + + getByClassAll('my-class'); + + globalThis.document = prevDocument; + + t.calledWith(getElementsByClassName, ['my-class'], 'should fallback to document when no element'); + t.end(); +}); diff --git a/client/dom/dom-tree.spec.mjs b/client/dom/dom-tree.spec.mjs deleted file mode 100644 index a711dcb6..00000000 --- a/client/dom/dom-tree.spec.mjs +++ /dev/null @@ -1,40 +0,0 @@ -import test from 'supertape'; -import {create} from 'auto-globals'; -import {tryCatch} from 'try-catch'; -import {isContainClass} from './dom-tree.mjs'; - -test('dom: isContainClass: no element', (t) => { - const [e] = tryCatch(isContainClass); - - t.equal(e.message, 'element could not be empty!', 'should throw when no element'); - t.end(); -}); - -test('dom: isContainClass: no className', (t) => { - const [e] = tryCatch(isContainClass, {}); - - t.equal(e.message, 'className could not be empty!', 'should throw when no element'); - t.end(); -}); - -test('dom: isContainClass: contains', (t) => { - const el = create(); - const {contains} = el.classList; - - const className = 'hello'; - isContainClass(el, className); - - t.calledWith(contains, [className], 'should call contains'); - t.end(); -}); - -test('dom: isContainClass: contains: array', (t) => { - const el = create(); - const {contains} = el.classList; - - const className = 'hello'; - isContainClass(el, ['world', className, 'hello']); - - t.calledWith(contains, [className], 'should call contains'); - t.end(); -}); diff --git a/client/dom/events/event-store.js b/client/dom/events/event-store.js index ae9ad90a..1cbfe7a4 100644 --- a/client/dom/events/event-store.js +++ b/client/dom/events/event-store.js @@ -1,8 +1,6 @@ -'use strict'; - let list = []; -module.exports.add = (el, name, fn) => { +export const add = (el, name, fn) => { list.push([ el, name, @@ -10,8 +8,8 @@ module.exports.add = (el, name, fn) => { ]); }; -module.exports.clear = () => { +export const clear = () => { list = []; }; -module.exports.get = () => list; +export const get = () => list; diff --git a/client/dom/events/event-store.spec.js b/client/dom/events/event-store.spec.js index 82e6752a..6a623939 100644 --- a/client/dom/events/event-store.spec.js +++ b/client/dom/events/event-store.spec.js @@ -1,7 +1,5 @@ -'use strict'; - -const test = require('supertape'); -const eventStore = require('./event-store'); +import {test} from 'supertape'; +import * as eventStore from './event-store.js'; test('event-store: get', (t) => { const el = {}; diff --git a/client/dom/events/index.mjs b/client/dom/events/index.js similarity index 98% rename from client/dom/events/index.mjs rename to client/dom/events/index.js index 0762d560..c49d2333 100644 --- a/client/dom/events/index.mjs +++ b/client/dom/events/index.js @@ -1,5 +1,5 @@ import itype from 'itype'; -import EventStore from './event-store.js'; +import * as EventStore from './event-store.js'; /** * safe add event listener diff --git a/client/dom/files.mjs b/client/dom/files.js similarity index 100% rename from client/dom/files.mjs rename to client/dom/files.js diff --git a/client/dom/images.mjs b/client/dom/images.js similarity index 99% rename from client/dom/images.mjs rename to client/dom/images.js index 9682e666..75663e72 100644 --- a/client/dom/images.mjs +++ b/client/dom/images.js @@ -102,13 +102,13 @@ export function show(position, panel) { /** * hide load image */ -export const hide = () => { +export function hide() { const element = Images.get(); DOM.hide(element); return Images; -}; +} export const setProgress = (value, title) => { const DATA = 'data-progress'; diff --git a/client/dom/index.mjs b/client/dom/index.js similarity index 96% rename from client/dom/index.mjs rename to client/dom/index.js index 471f3b7c..a82b6f72 100644 --- a/client/dom/index.mjs +++ b/client/dom/index.js @@ -6,16 +6,16 @@ import * as Events from '#dom/events'; import {getExt} from '#common/util'; import * as Storage from '#dom/storage'; import * as RESTful from '#dom/rest'; -import * as Images from './images.mjs'; +import * as Images from '#dom/images'; import renameCurrent from './operations/rename-current.js'; -import * as CurrentFile from './current-file.mjs'; -import * as DOMTree from './dom-tree.mjs'; -import * as Cmd from './cmd.mjs'; -import IO from './io/index.js'; -import {uploadDirectory} from './directory.mjs'; -import * as Buffer from './buffer.mjs'; -import {loadRemote as _loadRemote} from './load-remote.mjs'; -import {selectByPattern} from './select-by-pattern.mjs'; +import * as CurrentFile from './current-file.js'; +import * as DOMTree from './dom-tree.js'; +import * as Cmd from './cmd.js'; +import * as IO from './io/index.js'; +import {uploadDirectory} from './directory.js'; +import * as Buffer from './buffer.js'; +import {loadRemote as _loadRemote} from './load-remote.js'; +import {selectByPattern} from './select-by-pattern.js'; const {assign} = Object; @@ -442,11 +442,7 @@ export function getFilenames(files) { if (name === '..') allFiles.shift(); - const names = allFiles.map((current) => { - return DOM.getCurrentName(current); - }); - - return names; + return allFiles.map(DOM.getCurrentName); } /** @@ -705,7 +701,10 @@ export function getPackerExt(type) { export async function goToDirectory(overrides = {}) { const {Dialog} = DOM; - const {prompt = Dialog.prompt, changeDir = CloudCmd.changeDir} = overrides; + const { + prompt = Dialog.prompt, + changeDir = CloudCmd.changeDir, + } = overrides; const msg = 'Go to directory:'; const {dirPath} = CurrentInfo; diff --git a/client/dom/index.spec.mjs b/client/dom/index.spec.js similarity index 95% rename from client/dom/index.spec.mjs rename to client/dom/index.spec.js index fb4de9e4..268efe5f 100644 --- a/client/dom/index.spec.mjs +++ b/client/dom/index.spec.js @@ -1,5 +1,5 @@ import {test, stub} from 'supertape'; -import {getCSSVar, goToDirectory} from './index.mjs'; +import {getCSSVar, goToDirectory} from '#dom'; globalThis.CloudCmd = {}; diff --git a/client/dom/io/index.js b/client/dom/io/index.js index a17360ae..192769e6 100644 --- a/client/dom/io/index.js +++ b/client/dom/io/index.js @@ -1,24 +1,35 @@ -'use strict'; +import {FS} from '#common/cloudfunc'; +import {sendRequest as _sendRequest} from './send-request.js'; -const {FS} = require('../../../common/cloudfunc.mjs'); -const _sendRequest = require('./send-request'); +const {assign} = Object; const imgPosition = { top: true, }; -module.exports.delete = async (url, data) => { - return await _sendRequest({ +export const remove = async (url, data, overrides = {}) => { + const { + sendRequest = _sendRequest, + } = overrides; + + const request = { method: 'DELETE', url: FS + url, - data, imgPosition: { top: Boolean(data), }, - }); + }; + + if (data) + assign(request, { + data, + url: `${request.url}?files`, + }); + + return await sendRequest(request); }; -module.exports.patch = async (url, data) => { +export const patch = async (url, data) => { return await _sendRequest({ method: 'PATCH', url: FS + url, @@ -27,7 +38,7 @@ module.exports.patch = async (url, data) => { }); }; -module.exports.write = async (url, data) => { +export const write = async (url, data) => { return await _sendRequest({ method: 'PUT', url: FS + url, @@ -36,7 +47,7 @@ module.exports.write = async (url, data) => { }); }; -module.exports.createDirectory = async (url, overrides = {}) => { +export const createDirectory = async (url, overrides = {}) => { const { sendRequest = _sendRequest, } = overrides; @@ -48,7 +59,7 @@ module.exports.createDirectory = async (url, overrides = {}) => { }); }; -module.exports.read = async (url, dataType = 'text') => { +export const read = async (url, dataType = 'text') => { const notLog = !url.includes('?'); return await _sendRequest({ @@ -59,7 +70,7 @@ module.exports.read = async (url, dataType = 'text') => { }); }; -module.exports.copy = async (from, to, names) => { +export const copy = async (from, to, names) => { return await _sendRequest({ method: 'PUT', url: '/copy', @@ -72,7 +83,7 @@ module.exports.copy = async (from, to, names) => { }); }; -module.exports.pack = async (data) => { +export const pack = async (data) => { return await _sendRequest({ method: 'PUT', url: '/pack', @@ -80,7 +91,7 @@ module.exports.pack = async (data) => { }); }; -module.exports.extract = async (data) => { +export const extract = async (data) => { return await _sendRequest({ method: 'PUT', url: '/extract', @@ -88,7 +99,7 @@ module.exports.extract = async (data) => { }); }; -module.exports.move = async (from, to, names) => { +export const move = async (from, to, names) => { return await _sendRequest({ method: 'PUT', url: '/move', @@ -101,7 +112,7 @@ module.exports.move = async (from, to, names) => { }); }; -module.exports.rename = async (from, to) => { +export const rename = async (from, to) => { return await _sendRequest({ method: 'PUT', url: '/rename', @@ -113,7 +124,7 @@ module.exports.rename = async (from, to) => { }); }; -module.exports.Config = { +export const Config = { read: async () => { return await _sendRequest({ method: 'GET', @@ -133,7 +144,7 @@ module.exports.Config = { }, }; -module.exports.Markdown = { +export const Markdown = { read: async (url) => { return await _sendRequest({ method: 'GET', diff --git a/client/dom/io/index.spec.js b/client/dom/io/index.spec.js index 19ebe5bd..8e9e2d08 100644 --- a/client/dom/io/index.spec.js +++ b/client/dom/io/index.spec.js @@ -1,7 +1,5 @@ -'use strict'; - -const {test, stub} = require('supertape'); -const io = require('.'); +import {test, stub} from 'supertape'; +import * as io from './index.js'; test('client: dom: io', (t) => { const sendRequest = stub(); @@ -21,3 +19,43 @@ test('client: dom: io', (t) => { t.calledWith(sendRequest, [expected]); t.end(); }); + +test('client: dom: io: remove: no files', async (t) => { + const sendRequest = stub(); + + await io.remove('/hello', null, { + sendRequest, + }); + + const expected = { + imgPosition: { + top: false, + }, + method: 'DELETE', + url: '/fs/hello', + }; + + t.calledWith(sendRequest, [expected]); + t.end(); +}); + +test('client: dom: io: remove: files', async (t) => { + const sendRequest = stub(); + const files = ['world']; + + await io.remove('/hello', files, { + sendRequest, + }); + + const expected = { + imgPosition: { + top: true, + }, + data: ['world'], + method: 'DELETE', + url: '/fs/hello?files', + }; + + t.calledWith(sendRequest, [expected]); + t.end(); +}); diff --git a/client/dom/io/send-request.js b/client/dom/io/send-request.js index 8f9197b5..177d34b5 100644 --- a/client/dom/io/send-request.js +++ b/client/dom/io/send-request.js @@ -1,12 +1,9 @@ -'use strict'; - /* global CloudCmd */ -const {promisify} = require('es6-promisify'); +import {promisify} from 'es6-promisify'; +import * as Images from '#dom/images'; +import * as load from '#dom/load'; -const Images = require('../images.mjs'); -const load = require('#dom/load'); - -module.exports = promisify((params, callback) => { +export const sendRequest = promisify((params, callback) => { const p = params; const {prefixURL} = CloudCmd; @@ -40,7 +37,8 @@ module.exports = promisify((params, callback) => { }); }); -module.exports._replaceHash = replaceHash; +export const _replaceHash = replaceHash; + function replaceHash(url) { /* * if we send ajax request - diff --git a/client/dom/io/send-request.spec.js b/client/dom/io/send-request.spec.js index e8d0449a..f8a960d8 100644 --- a/client/dom/io/send-request.spec.js +++ b/client/dom/io/send-request.spec.js @@ -1,7 +1,5 @@ -'use strict'; - -const test = require('supertape'); -const {_replaceHash} = require('./send-request'); +import {test} from 'supertape'; +import {_replaceHash} from './send-request.js'; test('cloudcmd: client: io: replaceHash', (t) => { const url = '/hello/####world'; diff --git a/client/dom/load-remote.mjs b/client/dom/load-remote.js similarity index 78% rename from client/dom/load-remote.mjs rename to client/dom/load-remote.js index 974763e0..5be8e327 100644 --- a/client/dom/load-remote.mjs +++ b/client/dom/load-remote.js @@ -1,8 +1,8 @@ /* global CloudCmd */ import {callbackify} from 'node:util'; -import rendy from 'rendy'; +import {rendy} from 'rendy'; import itype from 'itype'; -import load from 'load.js'; +import * as load from 'load.js'; import {tryToCatch} from 'try-to-catch'; import {findObjByNameInArr} from '#common/util'; import * as Files from '#dom/files'; @@ -33,13 +33,19 @@ export const loadRemote = callbackify(async (name, options) => { local = [module.local]; } - const localURL = local.map((url) => prefix + url); + const localURL = []; - const remoteURL = remoteTmpls.map((tmpl) => { - return rendy(tmpl, { + for (const url of local) { + localURL.push(prefix + url); + } + + const remoteURL = []; + + for (const tmpl of remoteTmpls) { + remoteURL.push(rendy(tmpl, { version, - }); - }); + })); + } if (online) { const [e] = await tryToCatch(load.parallel, remoteURL); diff --git a/client/dom/load.mjs b/client/dom/load.js similarity index 98% rename from client/dom/load.mjs rename to client/dom/load.js index 02158a83..8c7011c1 100644 --- a/client/dom/load.mjs +++ b/client/dom/load.js @@ -2,7 +2,7 @@ import itype from 'itype'; import jonny from 'jonny'; import Emitify from 'emitify'; import exec from 'execon'; -import * as Images from './images.mjs'; +import * as Images from '#dom/images'; /** * Function gets id by src diff --git a/client/dom/operations/rename-current.js b/client/dom/operations/rename-current.js index e8d29987..8b6536d3 100644 --- a/client/dom/operations/rename-current.js +++ b/client/dom/operations/rename-current.js @@ -1,15 +1,11 @@ -'use strict'; - /* global CloudCmd */ -const capitalize = require('just-capitalize'); +import capitalize from 'just-capitalize'; +import * as _Dialog from '#dom/dialog'; +import * as Storage from '#dom/storage'; +import * as RESTful from '#dom/rest'; +import * as _currentFile from '../current-file.js'; -const _Dialog = require('#dom/dialog'); -const Storage = require('#dom/storage'); -const RESTful = require('#dom/rest'); - -const _currentFile = require('../current-file.mjs'); - -module.exports = async (current, overrides = {}) => { +export default async (current, overrides = {}) => { const { refresh = CloudCmd.refresh, Dialog = _Dialog, diff --git a/client/dom/operations/rename-current.spec.js b/client/dom/operations/rename-current.spec.js index 4a3c7fec..a5ebb415 100644 --- a/client/dom/operations/rename-current.spec.js +++ b/client/dom/operations/rename-current.spec.js @@ -1,8 +1,5 @@ -'use strict'; - -const {test, stub} = require('supertape'); - -const renameCurrent = require('./rename-current'); +import {test, stub} from 'supertape'; +import renameCurrent from './rename-current.js'; test('cloudcmd: client: dom: renameCurrent: isCurrentFile', async (t) => { const current = {}; diff --git a/client/dom/rest.mjs b/client/dom/rest.js similarity index 85% rename from client/dom/rest.mjs rename to client/dom/rest.js index 092096df..3cf2fb66 100644 --- a/client/dom/rest.mjs +++ b/client/dom/rest.js @@ -1,8 +1,8 @@ import {tryToCatch} from 'try-to-catch'; import * as Dialog from '#dom/dialog'; -import {encode} from '../../common/entity.js'; -import * as Images from './images.mjs'; -import IO from './io/index.js'; +import * as Images from '#dom/images'; +import {encode} from '#common/entity'; +import * as IO from './io/index.js'; const handleError = (promise) => async (...args) => { const [e, data] = await tryToCatch(promise, ...args); @@ -18,7 +18,7 @@ const handleError = (promise) => async (...args) => { return [e, data]; }; -export const remove = handleError(IO.delete); +export const remove = handleError(IO.remove); export const patch = handleError(IO.patch); export const write = handleError(IO.write); export const createDirectory = handleError(IO.createDirectory); diff --git a/client/dom/select-by-pattern.mjs b/client/dom/select-by-pattern.js similarity index 93% rename from client/dom/select-by-pattern.mjs rename to client/dom/select-by-pattern.js index be992878..0bada6ef 100644 --- a/client/dom/select-by-pattern.mjs +++ b/client/dom/select-by-pattern.js @@ -1,10 +1,10 @@ import {alert, prompt} from '#dom/dialog'; import {getRegExp} from '#common/util'; -import {getCurrentName} from './current-file.mjs'; +import {getCurrentName} from './current-file.js'; import { isSelected, toggleSelectedFile, -} from './cmd.mjs'; +} from './cmd.js'; let SelectType = '*.*'; diff --git a/client/dom/storage.mjs b/client/dom/storage.js similarity index 100% rename from client/dom/storage.mjs rename to client/dom/storage.js diff --git a/client/dom/storage.spec.mjs b/client/dom/storage.spec.js similarity index 100% rename from client/dom/storage.spec.mjs rename to client/dom/storage.spec.js diff --git a/client/dom/upload-files.mjs b/client/dom/upload-files.js similarity index 90% rename from client/dom/upload-files.mjs rename to client/dom/upload-files.js index 908c402b..1c37fe1b 100644 --- a/client/dom/upload-files.mjs +++ b/client/dom/upload-files.js @@ -3,9 +3,9 @@ import {eachSeries} from 'execon'; import wraptile from 'wraptile'; import * as load from '#dom/load'; import {alert} from '#dom/dialog'; -import * as Images from './images.mjs'; -import {FS} from '../../common/cloudfunc.mjs'; -import {getCurrentDirPath} from './current-file.mjs'; +import {FS} from '#common/cloudfunc'; +import * as Images from '#dom/images'; +import {getCurrentDirPath} from './current-file.js'; const loadFile = wraptile(_loadFile); const onEnd = wraptile(_onEnd); diff --git a/client/get-json-from-file-table.mjs b/client/get-json-from-file-table.js similarity index 100% rename from client/get-json-from-file-table.mjs rename to client/get-json-from-file-table.js diff --git a/client/key/binder.mjs b/client/key/binder.js similarity index 100% rename from client/key/binder.mjs rename to client/key/binder.js diff --git a/client/key/binder.spec.js b/client/key/binder.spec.js new file mode 100644 index 00000000..f9d54fdd --- /dev/null +++ b/client/key/binder.spec.js @@ -0,0 +1,28 @@ +import {test} from 'supertape'; +import {createBinder} from './binder.js'; + +test('client: key: binder: isBind: default', (t) => { + const binder = createBinder(); + + t.notOk(binder.isBind(), 'should not be bind by default'); + t.end(); +}); + +test('client: key: binder: setBind', (t) => { + const binder = createBinder(); + + binder.setBind(); + + t.ok(binder.isBind(), 'should be bind'); + t.end(); +}); + +test('client: key: binder: unsetBind', (t) => { + const binder = createBinder(); + + binder.setBind(); + binder.unsetBind(); + + t.notOk(binder.isBind(), 'should not be bind'); + t.end(); +}); diff --git a/client/key/index.mjs b/client/key/index.js similarity index 96% rename from client/key/index.mjs rename to client/key/index.js index 06de23c8..283a7f11 100644 --- a/client/key/index.mjs +++ b/client/key/index.js @@ -2,19 +2,19 @@ import clipboard from '@cloudcmd/clipboard'; import {fullstore} from 'fullstore'; import * as Events from '#dom/events'; -import * as Buffer from '../dom/buffer.mjs'; -import * as KEY from './key.mjs'; -import _vim from './vim/index.mjs'; -import setCurrentByChar from './set-current-by-char.mjs'; -import {createBinder} from './binder.mjs'; +import * as Buffer from '../dom/buffer.js'; +import * as KEY from './key.js'; +import _vim from './vim/index.js'; +import setCurrentByChar from './set-current-by-char.js'; +import {createBinder} from './binder.js'; const Chars = fullstore(); const toggleVim = (keyCode, overrides = {}) => { const {_config, config} = overrides; - if (keyCode === KEY.ESC) - _config('vim', !config('vim')); + if (!config('vim') && keyCode === KEY.ESC) + _config('vim', true); }; const isUndefined = (a) => typeof a === 'undefined'; @@ -442,6 +442,14 @@ async function _switchKey(event) { break; + case KEY.L: + if (ctrlMeta && shift) { + CloudCmd.logOut(); + event.preventDefault(); + } + + break; + case KEY.M: if (ctrlMeta) { if (config('vim')) diff --git a/client/key/index.spec.mjs b/client/key/index.spec.js similarity index 88% rename from client/key/index.spec.mjs rename to client/key/index.spec.js index ad4753fb..bec92b31 100644 --- a/client/key/index.spec.mjs +++ b/client/key/index.spec.js @@ -1,8 +1,8 @@ import autoGlobals from 'auto-globals'; import supertape from 'supertape'; -import {ESC} from './key.mjs'; -import {Key, _listener} from './index.mjs'; -import {getDOM, getCloudCmd} from './vim/globals.fixture.mjs'; +import {ESC} from './key.js'; +import {Key, _listener} from './index.js'; +import {getDOM, getCloudCmd} from './vim/globals.fixture.js'; const test = autoGlobals(supertape); const {stub} = supertape; @@ -37,6 +37,7 @@ test('cloudcmd: client: key: enable vim', async (t) => { test('cloudcmd: client: key: disable vim', async (t) => { const _config = stub(); const config = stub(); + const event = { keyCode: ESC, key: 'Escape', diff --git a/client/key/key.mjs b/client/key/key.js similarity index 98% rename from client/key/key.mjs rename to client/key/key.js index 66120e74..6772e34c 100644 --- a/client/key/key.mjs +++ b/client/key/key.js @@ -22,6 +22,7 @@ export const D = 68; export const G = 71; export const J = 74; export const K = 75; +export const L = 76; export const M = 77; export const O = 79; export const P = 80; diff --git a/client/key/set-current-by-char.mjs b/client/key/set-current-by-char.js similarity index 100% rename from client/key/set-current-by-char.mjs rename to client/key/set-current-by-char.js diff --git a/client/key/vim/find.mjs b/client/key/vim/find.js similarity index 100% rename from client/key/vim/find.mjs rename to client/key/vim/find.js diff --git a/client/key/vim/find.spec.js b/client/key/vim/find.spec.js new file mode 100644 index 00000000..191a3652 --- /dev/null +++ b/client/key/vim/find.spec.js @@ -0,0 +1,55 @@ +import test from 'supertape'; +import {getDOM} from './globals.fixture.js'; +import { + _next, + _previous, + find, + findNext, + findPrevious, +} from './find.js'; + +globalThis.DOM = getDOM(); + +test('cloudcmd: client: vim: _next', (t) => { + const result = _next(1, 2); + + t.notOk(result, 'should return 0'); + t.end(); +}); + +test('cloudcmd: client: vim: _next: increment', (t) => { + const result = _next(0, 2); + + t.equal(result, 1, 'should return 1'); + t.end(); +}); + +test('cloudcmd: client: vim: _previous', (t) => { + const result = _previous(0, 2); + + t.equal(result, 1, 'should return 1'); + t.end(); +}); + +test('cloudcmd: client: vim: _previous: decrement', (t) => { + const result = _previous(1, 2); + + t.equal(result, 0, 'should return 0'); + t.end(); +}); + +test('cloudcmd: client: vim: findNext: after find', (t) => { + find('a', ['alpha', 'beta', 'apple']); + const result = findNext(); + + t.equal(result, 'beta', 'should return next found name'); + t.end(); +}); + +test('cloudcmd: client: vim: findPrevious: after find', (t) => { + find('a', ['alpha', 'beta', 'apple']); + const result = findPrevious(); + + t.equal(result, 'apple', 'should return previous found name'); + t.end(); +}); diff --git a/client/key/vim/find.spec.mjs b/client/key/vim/find.spec.mjs deleted file mode 100644 index ee9a9458..00000000 --- a/client/key/vim/find.spec.mjs +++ /dev/null @@ -1,19 +0,0 @@ -import test from 'supertape'; -import {getDOM} from './globals.fixture.mjs'; -import {_next, _previous} from './find.mjs'; - -globalThis.DOM = getDOM(); - -test('cloudcmd: client: vim: _next', (t) => { - const result = _next(1, 2); - - t.notOk(result, 'should return 0'); - t.end(); -}); - -test('cloudcmd: client: vim: _previous', (t) => { - const result = _previous(0, 2); - - t.equal(result, 1, 'should return 1'); - t.end(); -}); diff --git a/client/key/vim/globals.fixture.mjs b/client/key/vim/globals.fixture.js similarity index 100% rename from client/key/vim/globals.fixture.mjs rename to client/key/vim/globals.fixture.js diff --git a/client/key/vim/index.mjs b/client/key/vim/index.js similarity index 87% rename from client/key/vim/index.mjs rename to client/key/vim/index.js index 2bff53ee..981f6f19 100644 --- a/client/key/vim/index.mjs +++ b/client/key/vim/index.js @@ -1,11 +1,9 @@ -/* global CloudCmd */ -/* global DOM */ -import vim from './vim.mjs'; -import * as finder from './find.mjs'; +import vim from './vim.js'; +import * as finder from './find.js'; import { setCurrent, selectFileNotParent, -} from './set-current.mjs'; +} from './set-current.js'; export default (key, event, overrides = {}) => { const defaults = { @@ -35,13 +33,13 @@ const getOperations = (event, deps) => { prompt = globalThis.DOM.Dialog.prompt, preventDefault = event?.preventDefault?.bind(event), stopImmediatePropagation = event?.preventDefault?.bind(event), - promptNewFile = DOM.promptNewFile, - + promptNewFile = globalThis.DOM.promptNewFile, toggleSelectedFile, Buffer = {}, createFindNext = _createFindNext, createFindPrevious = _createFindPrevious, createMakeFile = _createMakeFile, + renameCurrent, } = deps; return { @@ -57,15 +55,26 @@ const getOperations = (event, deps) => { setCurrentByName, }), escape: unselectFiles, - + rename: () => { + event.preventDefault(); + renameCurrent(); + }, remove: () => { Operation.show('delete'); }, + operationCopy: () => { + event.preventDefault(); + Operation.show('copy'); + }, + operationMove: () => { + event.preventDefault(); + Operation.show('move'); + }, makeDirectory: () => { event.stopImmediatePropagation(); event.preventDefault(); - DOM.promptNewDir(); + globalThis.DOM.promptNewDir(); }, terminal: () => { diff --git a/client/key/vim/index.spec.mjs b/client/key/vim/index.spec.js similarity index 86% rename from client/key/vim/index.spec.mjs rename to client/key/vim/index.spec.js index 10392c32..231c6577 100644 --- a/client/key/vim/index.spec.mjs +++ b/client/key/vim/index.spec.js @@ -1,6 +1,7 @@ import {test, stub} from 'supertape'; -import {getDOM, getCloudCmd} from './globals.fixture.mjs'; -import vim, {selectFile as vimSelectFile} from './index.mjs'; +import {getDOM, getCloudCmd} from './globals.fixture.js'; +import vim, {selectFile as vimSelectFile} from './index.js'; +import * as finder from './find.js'; globalThis.DOM = getDOM(); globalThis.CloudCmd = getCloudCmd(); @@ -447,6 +448,7 @@ test('cloudcmd: client: key: set first file current: ^', async (t) => { test('cloudcmd: client: key: visual', (t) => { const element = {}; const toggleSelectedFile = stub(); + const Info = { element, }; @@ -463,6 +465,7 @@ test('cloudcmd: client: key: visual', (t) => { test('cloudcmd: client: key: ESC', (t) => { const element = {}; const unselectFiles = stub(); + const Info = { element, }; @@ -508,6 +511,7 @@ test('cloudcmd: client: key: Enter', async (t) => { test('cloudcmd: client: key: /', (t) => { const preventDefault = stub(); const element = {}; + const Info = { element, files: [], @@ -654,3 +658,99 @@ test('cloudcmd: client: vim: edit', async (t) => { t.calledWithNoArgs(CloudCmd.EditFileVim.show); t.end(); }); + +test('cloudcmd: client: vim: rename', async (t) => { + const DOM = getDOM(); + const renameCurrent = stub(); + + assign(DOM, { + renameCurrent, + }); + + const event = { + preventDefault: stub(), + }; + + await vim('rr', event, DOM); + + t.calledWithNoArgs(renameCurrent); + t.end(); +}); + +test('cloudcmd: client: key: cc: operationCopy', (t) => { + const show = stub(); + const preventDefault = stub(); + + const Operation = { + show, + }; + + const event = { + preventDefault, + }; + + vim('c', event, { + Operation, + }); + + vim('c', event, { + Operation, + }); + + t.calledWith(show, ['copy'], 'should show copy operation'); + t.end(); +}); + +test('cloudcmd: client: key: mm: operationMove', (t) => { + const show = stub(); + const preventDefault = stub(); + + const Operation = { + show, + }; + + const event = { + preventDefault, + }; + + vim('m', event, { + Operation, + }); + + vim('m', event, { + Operation, + }); + + t.calledWith(show, ['move'], 'should show move operation'); + t.end(); +}); + +test('cloudcmd: client: key: n: findNext: real', (t) => { + const setCurrentByName = stub(); + + finder.find('a', ['alpha', 'beta', 'apple']); + + const event = {}; + + vim('n', event, { + setCurrentByName, + }); + + t.calledWith(setCurrentByName, ['beta'], 'should set current by next found name'); + t.end(); +}); + +test('cloudcmd: client: key: N: findPrevious: real', (t) => { + const setCurrentByName = stub(); + + finder.find('a', ['alpha', 'beta', 'apple']); + + const event = {}; + + vim('N', event, { + setCurrentByName, + }); + + t.calledWith(setCurrentByName, ['apple'], 'should set current by previous found name'); + t.end(); +}); diff --git a/client/key/vim/set-current.mjs b/client/key/vim/set-current.js similarity index 100% rename from client/key/vim/set-current.mjs rename to client/key/vim/set-current.js diff --git a/client/key/vim/vim.mjs b/client/key/vim/vim.js similarity index 91% rename from client/key/vim/vim.mjs rename to client/key/vim/vim.js index 0d0404c3..95a27caf 100644 --- a/client/key/vim/vim.mjs +++ b/client/key/vim/vim.js @@ -40,6 +40,9 @@ export default (key, operations = {}) => { makeDirectory = noop, terminal = noop, edit = noop, + operationCopy = noop, + operationMove = noop, + rename = noop, } = operations; if (key === 'Enter') @@ -111,11 +114,26 @@ export default (key, operations = {}) => { return end(); } + if (value === 'cc') { + operationCopy(); + return end(); + } + + if (value === 'mm') { + operationMove(); + return end(); + } + if (value === 'mf') { makeFile(); return end(); } + if (value === 'rr') { + rename(); + return end(); + } + if (key === 'd' && (visual() || prevStore === 'd')) { stopVisual(); remove(); diff --git a/client/key/vim/vim.spec.mjs b/client/key/vim/vim.spec.js similarity index 78% rename from client/key/vim/vim.spec.mjs rename to client/key/vim/vim.spec.js index 180f114f..02fd52d4 100644 --- a/client/key/vim/vim.spec.mjs +++ b/client/key/vim/vim.spec.js @@ -1,5 +1,5 @@ import {test, stub} from 'supertape'; -import vim from './vim.mjs'; +import vim from './vim.js'; test('vim: no operations', (t) => { const result = vim('hello', {}); @@ -43,6 +43,28 @@ test('vim: ^', (t) => { t.end(); }); +test('vim: cc', (t) => { + const operationCopy = stub(); + + vim('cc', { + operationCopy, + }); + + t.calledWithNoArgs(operationCopy); + t.end(); +}); + +test('vim: mm', (t) => { + const operationMove = stub(); + + vim('mm', { + operationMove, + }); + + t.calledWithNoArgs(operationMove); + t.end(); +}); + test('vim: w', (t) => { const moveNext = stub(); diff --git a/client/listeners/get-index.mjs b/client/listeners/get-index.js similarity index 100% rename from client/listeners/get-index.mjs rename to client/listeners/get-index.js diff --git a/client/listeners/get-index.spec.mjs b/client/listeners/get-index.spec.js similarity index 90% rename from client/listeners/get-index.spec.mjs rename to client/listeners/get-index.spec.js index ae05159f..0380cdc1 100644 --- a/client/listeners/get-index.spec.mjs +++ b/client/listeners/get-index.spec.js @@ -1,5 +1,5 @@ import test from 'supertape'; -import {getIndex} from './get-index.mjs'; +import {getIndex} from './get-index.js'; test('cloudcmd: client: listeners: getIndex: not found', (t) => { const array = ['hello']; diff --git a/client/listeners/get-range.mjs b/client/listeners/get-range.js similarity index 100% rename from client/listeners/get-range.mjs rename to client/listeners/get-range.js diff --git a/client/listeners/get-range.spec.mjs b/client/listeners/get-range.spec.js similarity index 96% rename from client/listeners/get-range.spec.mjs rename to client/listeners/get-range.spec.js index 38b8ab32..77c950f4 100644 --- a/client/listeners/get-range.spec.mjs +++ b/client/listeners/get-range.spec.js @@ -1,5 +1,5 @@ import test from 'supertape'; -import {getRange} from './get-range.mjs'; +import {getRange} from './get-range.js'; test('cloudcmd: client: listeners: getRange: direct', (t) => { const expected = [ diff --git a/client/listeners/index.mjs b/client/listeners/index.js similarity index 97% rename from client/listeners/index.mjs rename to client/listeners/index.js index 99514b98..babc1e0d 100644 --- a/client/listeners/index.mjs +++ b/client/listeners/index.js @@ -6,14 +6,14 @@ import {tryToCatch} from 'try-to-catch'; import clipboard from '@cloudcmd/clipboard'; import * as Events from '#dom/events'; import {uploadFiles} from '#dom/upload-files'; -import {getRange} from './get-range.mjs'; -import {FS} from '../../common/cloudfunc.mjs'; -import {getIndex} from './get-index.mjs'; +import {FS} from '#common/cloudfunc'; +import {getRange} from './get-range.js'; +import {getIndex} from './get-index.js'; const NBSP_REG = RegExp(String.fromCharCode(160), 'g'); const SPACE = ' '; -export const init = async () => { +export async function init() { contextMenu(); dragndrop(); unload(); @@ -21,7 +21,7 @@ export const init = async () => { resize(); header(); await config(); -}; +} const unselect = (event) => { const isMac = /Mac/.test(globalThis.navigator.platform); @@ -55,6 +55,7 @@ let EXT; function header() { const fm = DOM.getFM(); const isDataset = (el) => el.dataset; + const isPanel = (el) => { return /^js-(left|right)$/.test(el.dataset.name); }; @@ -309,8 +310,12 @@ function onDragStart(event) { link.href = prefixURL + '/pack' + Info.path + EXT; } - event.dataTransfer.setData('DownloadURL', 'application/octet-stream' + ':' + name + - ':' + link); + event.dataTransfer.setData( + 'DownloadURL', + 'application/octet-stream' + ':' + name + + ':' + + link, + ); } function getLIElement(element) { diff --git a/client/load-module.mjs b/client/load-module.js similarity index 100% rename from client/load-module.mjs rename to client/load-module.js diff --git a/client/modules/cloud.mjs b/client/modules/cloud.js similarity index 96% rename from client/modules/cloud.mjs rename to client/modules/cloud.js index f0fbb31e..cfe0d708 100644 --- a/client/modules/cloud.mjs +++ b/client/modules/cloud.js @@ -4,7 +4,7 @@ import currify from 'currify'; import load from 'load.js'; import {ajax} from '#dom/load'; import * as Files from '#dom/files'; -import * as Images from '../dom/images.mjs'; +import * as Images from '#dom/images'; const {log} = CloudCmd; diff --git a/client/modules/command-line.js b/client/modules/command-line.js index 89a562fa..7a1c3993 100644 --- a/client/modules/command-line.js +++ b/client/modules/command-line.js @@ -1,18 +1,14 @@ -'use strict'; - /* global CloudCmd */ -CloudCmd.CommandLine = exports; +import * as Dialog from '#dom/dialog'; -const Dialog = require('#dom/dialog'); +export function init() {} +CloudCmd.CommandLine = { + init, + show, + hide, +}; -const noop = () => {}; - -module.exports.init = noop; - -module.exports.show = show; -module.exports.hide = hide; - -async function show() { +export async function show() { const [, cmd] = await Dialog.prompt('Command Line', ''); const TERMINAL = '^(t|terminal)'; @@ -32,4 +28,4 @@ async function show() { } } -function hide() {} +export function hide() {} diff --git a/client/modules/config/index.mjs b/client/modules/config/index.js similarity index 93% rename from client/modules/config/index.mjs rename to client/modules/config/index.js index ad7b2acb..dfa4c105 100644 --- a/client/modules/config/index.mjs +++ b/client/modules/config/index.js @@ -1,5 +1,5 @@ -/* global CloudCmd, DOM, io */ -import rendy from 'rendy'; +import '../../../css/config.css'; +import {rendy} from 'rendy'; import currify from 'currify'; import wraptile from 'wraptile'; import squad from 'squad'; @@ -9,10 +9,11 @@ import load from 'load.js'; import createElement from '@cloudcmd/create-element'; import * as Events from '#dom/events'; import * as Files from '#dom/files'; -import '../../../css/config.css'; -import * as input from './input.mjs'; -import * as Images from '../../dom/images.mjs'; -import {getTitle} from '../../../common/cloudfunc.mjs'; +import {getTitle} from '#common/cloudfunc'; +import * as Images from '#dom/images'; +import * as input from './input.js'; + +const {CloudCmd, DOM} = globalThis; const {Dialog, setTitle} = DOM; @@ -40,7 +41,7 @@ let Template; const loadCSS = load.css; -export const init = async () => { +export async function init() { if (!CloudCmd.config('configDialog')) return; @@ -56,7 +57,7 @@ export const init = async () => { ]); initSocket(); -}; +} const {config, Key} = CloudCmd; @@ -78,7 +79,7 @@ function initSocket() { const ONE_MINUTE = 60 * 1000; - const socket = io.connect(href + prefixSocket + '/config', { + const socket = globalThis.io.connect(href + prefixSocket + '/config', { reconnectionAttempts: Infinity, reconnectionDelay: ONE_MINUTE, path: `${prefix}/socket.io`, @@ -132,6 +133,7 @@ async function fillTemplate() { columns, theme, configAuth, + configPort, ...obj } = input.convert(config); @@ -141,6 +143,7 @@ async function fillTemplate() { obj[`${columns}-selected`] = 'selected'; obj[`${theme}-selected`] = 'selected'; obj.configAuth = configAuth ? '' : 'hidden'; + obj.configPort = configPort ? '' : 'hidden'; const innerHTML = rendy(Template, obj); diff --git a/client/modules/config/input.mjs b/client/modules/config/input.js similarity index 72% rename from client/modules/config/input.mjs rename to client/modules/config/input.js index 430f7e51..34a4be69 100644 --- a/client/modules/config/input.mjs +++ b/client/modules/config/input.js @@ -1,7 +1,9 @@ -import currify from 'currify'; +import {encode} from '#common/entity'; -const isType = currify((type, object, name) => type === typeof object[name]); -const isBool = isType('boolean'); +const isBool = (a) => typeof a === 'boolean'; +const isString = (a) => typeof a === 'string'; + +const {keys} = Object; export function getElementByName(selector, element) { const str = `[data-name="js-${selector}"]`; @@ -19,13 +21,19 @@ export const getName = (element) => { export const convert = (config) => { const result = config; - const array = Object.keys(config); - const filtered = array.filter(isBool(config)); - - for (const name of filtered) { + for (const name of keys(config)) { const item = config[name]; - result[name] = setState(item); + + if (isBool(item)) { + result[name] = setState(item); + continue; + } + + if (isString(item)) { + result[name] = encode(item); + continue; + } } return result; diff --git a/client/modules/config/input.spec.js b/client/modules/config/input.spec.js new file mode 100644 index 00000000..9d0866cd --- /dev/null +++ b/client/modules/config/input.spec.js @@ -0,0 +1,140 @@ +import {test, stub} from 'supertape'; +import { + convert, + getName, + getValue, + setValue, +} from './input.js'; + +test('cloudcmd: client: config: input: convert', (t) => { + const result = convert({ + name: 'hello ', + }); + + const expected = { + name: 'hello <world>', + }; + + t.deepEqual(result, expected); + t.end(); +}); + +test('cloudcmd: client: config: input: convert: bool', (t) => { + const result = convert({ + auth: true, + }); + + const expected = { + auth: ' checked', + }; + + t.deepEqual(result, expected); + t.end(); +}); + +test('cloudcmd: client: config: input: convert: bool false', (t) => { + const result = convert({ + auth: false, + }); + + const expected = { + auth: '', + }; + + t.deepEqual(result, expected); + t.end(); +}); + +test('cloudcmd: client: config: input: getName', (t) => { + const getAttribute = stub().returns('js-hello'); + const element = { + getAttribute, + }; + + const result = getName(element); + + t.equal(result, 'hello', 'should strip js- prefix'); + t.end(); +}); + +test('cloudcmd: client: config: input: getValue: checkbox', (t) => { + const querySelector = stub().returns({ + type: 'checkbox', + checked: true, + }); + + const element = { + querySelector, + }; + + const result = getValue('auth', element); + + t.ok(result, 'should return checked value'); + t.end(); +}); + +test('cloudcmd: client: config: input: getValue: number', (t) => { + const querySelector = stub().returns({ + type: 'number', + value: '42', + }); + + const element = { + querySelector, + }; + + const result = getValue('port', element); + + t.equal(result, 42, 'should return number'); + t.end(); +}); + +test('cloudcmd: client: config: input: getValue: default', (t) => { + const querySelector = stub().returns({ + type: 'text', + value: 'hello', + }); + + const element = { + querySelector, + }; + + const result = getValue('name', element); + + t.equal(result, 'hello', 'should return value as is'); + t.end(); +}); + +test('cloudcmd: client: config: input: setValue: checkbox', (t) => { + const el = { + type: 'checkbox', + checked: false, + }; + + const querySelector = stub().returns(el); + const element = { + querySelector, + }; + + setValue('auth', true, element); + + t.ok(el.checked, 'should set checked'); + t.end(); +}); + +test('cloudcmd: client: config: input: setValue: default', (t) => { + const el = { + type: 'text', + value: 'old', + }; + + const querySelector = stub().returns(el); + const element = { + querySelector, + }; + + setValue('name', 'new', element); + + t.equal(el.value, 'new', 'should set value'); + t.end(); +}); diff --git a/client/modules/contact.js b/client/modules/contact.js index c6266de0..48c7bd19 100644 --- a/client/modules/contact.js +++ b/client/modules/contact.js @@ -1,36 +1,34 @@ /* global CloudCmd */ /* global DOM */ +import olark from '@cloudcmd/olark'; +import * as Images from '#dom/images'; -'use strict'; - -CloudCmd.Contact = exports; - -const olark = require('@cloudcmd/olark'); -const Images = require('../dom/images.mjs'); +CloudCmd.Contact = { + init, + show, + hide, +}; const {Events} = DOM; const {Key} = CloudCmd; -module.exports.show = show; -module.exports.hide = hide; - -module.exports.init = () => { +export function init() { Events.addKey(onKey); olark.identify('6216-545-10-4223'); olark('api.box.onExpand', show); olark('api.box.onShow', show); olark('api.box.onShrink', hide); -}; +} -function show() { +export function show() { Key.unsetBind(); Images.hide(); olark('api.box.expand'); } -function hide() { +export function hide() { Key.setBind(); olark('api.box.hide'); } diff --git a/client/modules/edit-file-vim.js b/client/modules/edit-file-vim.js index 0edd203b..16d557ae 100644 --- a/client/modules/edit-file-vim.js +++ b/client/modules/edit-file-vim.js @@ -1,9 +1,12 @@ -'use strict'; +import * as Events from '#dom/events'; -/* global CloudCmd */ -CloudCmd.EditFileVim = exports; +const {CloudCmd} = globalThis; -const Events = require('#dom/events'); +CloudCmd.EditFileVim = { + init, + show, + hide, +}; const {Key} = CloudCmd; @@ -15,11 +18,11 @@ const ConfigView = { }, }; -module.exports.init = async () => { +export async function init() { await CloudCmd.EditFile(); -}; +} -module.exports.show = async () => { +export async function show() { Events.addKey(listener); const editFile = await CloudCmd.EditFile.show(ConfigView); @@ -27,11 +30,9 @@ module.exports.show = async () => { editFile .getEditor() .setKeyMap('vim'); -}; +} -module.exports.hide = hide; - -function hide() { +export function hide() { CloudCmd.Edit.hide(); } diff --git a/client/modules/edit-file.js b/client/modules/edit-file.js index 59050a3c..83a07e36 100644 --- a/client/modules/edit-file.js +++ b/client/modules/edit-file.js @@ -1,12 +1,15 @@ -'use strict'; - /* global CloudCmd, DOM*/ -CloudCmd.EditFile = exports; +import Format from 'format-io'; +import {fullstore} from 'fullstore'; +import exec from 'execon'; +import {supermenu} from 'supermenu'; -const Format = require('format-io'); -const {fullstore} = require('fullstore'); -const exec = require('execon'); -const supermenu = require('supermenu'); +CloudCmd.EditFile = { + init, + show, + hide, + isChanged, +}; const Info = DOM.CurrentInfo; @@ -26,7 +29,7 @@ const ConfigView = { }, }; -module.exports.init = async () => { +export async function init() { isLoading(true); await CloudCmd.Edit(); @@ -36,7 +39,7 @@ module.exports.init = async () => { setListeners(editor); isLoading(false); -}; +} function getName() { const {name, isDir} = Info; @@ -47,7 +50,7 @@ function getName() { return name; } -module.exports.show = async (options) => { +export async function show(options) { if (isLoading()) return; @@ -86,11 +89,9 @@ module.exports.show = async (options) => { CloudCmd.Edit.show(optionsEdit); return CloudCmd.Edit; -}; +} -module.exports.hide = hide; - -function hide() { +export function hide() { CloudCmd.Edit.hide(); } @@ -176,9 +177,7 @@ function setMsgChanged(name) { MSG_CHANGED = `Do you want to save changes to ${name}?`; } -module.exports.isChanged = isChanged; - -async function isChanged() { +export async function isChanged() { const editor = CloudCmd.Edit.getEditor(); const is = editor.isChanged(); diff --git a/client/modules/edit-names-vim.js b/client/modules/edit-names-vim.js index 0dbd92b2..8b3b2f89 100644 --- a/client/modules/edit-names-vim.js +++ b/client/modules/edit-names-vim.js @@ -1,9 +1,12 @@ -'use strict'; +import * as Events from '#dom/events'; -/* global CloudCmd */ -CloudCmd.EditNamesVim = exports; +const {CloudCmd} = globalThis; -const Events = require('#dom/events'); +CloudCmd.EditNamesVim = { + init, + show, + hide, +}; const {Key} = CloudCmd; const ConfigView = { @@ -14,22 +17,20 @@ const ConfigView = { }, }; -module.exports.init = async () => { +export async function init() { await CloudCmd.EditNames(); -}; +} -module.exports.show = () => { +export function show() { Events.addKey(listener); CloudCmd.EditNames .show(ConfigView) .getEditor() .setKeyMap('vim'); -}; +} -module.exports.hide = hide; - -function hide() { +export function hide() { CloudCmd.Edit.hide(); } diff --git a/client/modules/edit-names.js b/client/modules/edit-names.js index 710ed7ea..7bc29054 100644 --- a/client/modules/edit-names.js +++ b/client/modules/edit-names.js @@ -1,13 +1,16 @@ -'use strict'; +import {tryToCatch} from 'try-to-catch'; +import exec from 'execon'; +import {supermenu} from 'supermenu'; +import {multiRename} from 'multi-rename'; -const {tryToCatch} = require('try-to-catch'); +const {CloudCmd, DOM} = globalThis; -/* global CloudCmd, DOM */ -CloudCmd.EditNames = exports; - -const exec = require('execon'); -const supermenu = require('supermenu'); -const {multiRename} = require('multi-rename'); +CloudCmd.EditNames = { + init, + show, + hide, + isChanged, +}; const Info = DOM.CurrentInfo; const {Dialog} = DOM; @@ -22,13 +25,13 @@ const ConfigView = { }, }; -module.exports.init = async () => { +export async function init() { await CloudCmd.Edit(); setListeners(); -}; +} -module.exports.show = (options) => { +export function show(options) { const names = getActiveNames().join('\n'); const config = { ...ConfigView, @@ -50,7 +53,7 @@ module.exports.show = (options) => { CloudCmd.Edit.show(config); return CloudCmd.Edit; -}; +} async function keyListener(event) { const ctrl = event.ctrlKey; @@ -60,7 +63,10 @@ async function keyListener(event) { if (ctrlMeta && event.keyCode === Key.S) { hide(); - } else if (ctrlMeta && event.keyCode === Key.P) { + return; + } + + if (ctrlMeta && event.keyCode === Key.P) { const [, pattern] = await Dialog.prompt('Apply pattern:', '[n][e]'); pattern && applyPattern(pattern); } @@ -77,9 +83,7 @@ function getActiveNames() { return DOM.getFilenames(DOM.getActiveFiles()); } -module.exports.hide = hide; - -function hide() { +export function hide() { CloudCmd.Edit.hide(); } @@ -201,9 +205,7 @@ function setMenu(event) { Menu.show(position.x, position.y); } -module.exports.isChanged = isChanged; - -async function isChanged() { +export async function isChanged() { const editor = CloudCmd.Edit.getEditor(); const msg = 'Apply new names?'; diff --git a/client/modules/edit.js b/client/modules/edit.js index 7aa6715c..593a11cb 100644 --- a/client/modules/edit.js +++ b/client/modules/edit.js @@ -1,23 +1,28 @@ /* global CloudCmd */ +import {montag} from 'montag'; +import {promisify} from 'es6-promisify'; +import {tryToCatch} from 'try-to-catch'; +import createElement from '@cloudcmd/create-element'; +import load from 'load.js'; +import {MAX_FILE_SIZE as maxSize} from '#common/cloudfunc'; +import {time, timeEnd} from '#common/util'; -'use strict'; +export function getEditor() { + return editor; +} -const montag = require('montag'); - -const {promisify} = require('es6-promisify'); -const {tryToCatch} = require('try-to-catch'); -const createElement = require('@cloudcmd/create-element'); -const load = require('load.js'); -const {MAX_FILE_SIZE: maxSize} = require('../../common/cloudfunc.mjs'); - -const {time, timeEnd} = require('#common/util'); -const getEditor = () => editor; const isFn = (a) => typeof a === 'function'; const loadJS = load.js; const Name = 'Edit'; -CloudCmd[Name] = exports; +CloudCmd[Name] = { + init, + show, + hide, + getEditor, + getElement, +}; const EditorName = CloudCmd.config('editor'); @@ -33,12 +38,12 @@ const ConfigView = { }, }; -module.exports.init = async () => { +export async function init() { const element = create(); await CloudCmd.View(); await loadFiles(element); -}; +} function create() { const element = createElement('div', { @@ -79,7 +84,7 @@ function initConfig(options = {}) { return config; } -module.exports.show = (options) => { +export function show(options) { if (Loading) return; @@ -88,15 +93,15 @@ module.exports.show = (options) => { getEditor().setOptions({ fontSize: 16, }); -}; +} -module.exports.getEditor = getEditor; +export function getElement() { + return Element; +} -module.exports.getElement = () => Element; - -module.exports.hide = () => { +export function hide() { CloudCmd.View.hide(); -}; +} const loadFiles = async (element) => { const prefix = `${CloudCmd.prefix}/${EditorName}`; diff --git a/client/modules/help.js b/client/modules/help.js index 242b7c16..74657a90 100644 --- a/client/modules/help.js +++ b/client/modules/help.js @@ -1,18 +1,18 @@ -'use strict'; +import * as Images from '#dom/images'; -/* global CloudCmd */ -CloudCmd.Help = exports; +const {CloudCmd} = globalThis; -const Images = require('../dom/images.mjs'); - -module.exports.init = () => { - Images.show.load('top'); +CloudCmd.Help = { + init, + show, + hide, }; -module.exports.show = show; -module.exports.hide = hide; +export function init() { + Images.show.load('top'); +} -function show() { +export function show() { const positionLoad = 'top'; const relative = true; @@ -22,6 +22,6 @@ function show() { }); } -function hide() { +export function hide() { CloudCmd.View.hide(); } diff --git a/client/modules/konsole.js b/client/modules/konsole.js index ae5bc42c..e9a465af 100644 --- a/client/modules/konsole.js +++ b/client/modules/konsole.js @@ -1,18 +1,20 @@ -'use strict'; - /* global CloudCmd */ /* global Util */ /* global DOM */ /* global Console */ -CloudCmd.Konsole = exports; +import exec from 'execon'; +import currify from 'currify'; +import {tryToCatch} from 'try-to-catch'; +import {js as loadJS} from 'load.js'; +import createElement from '@cloudcmd/create-element'; +import * as Images from '#dom/images'; -const exec = require('execon'); -const currify = require('currify'); -const {tryToCatch} = require('try-to-catch'); -const loadJS = require('load.js').js; -const createElement = require('@cloudcmd/create-element'); +CloudCmd.Konsole = { + init, + show, + hide, +}; -const Images = require('../dom/images.mjs'); const {Dialog, CurrentInfo: Info} = DOM; const rmLastSlash = (a) => a.replace(/\/$/, '') || '/'; @@ -27,7 +29,7 @@ const Name = 'Konsole'; let Element; let Loaded; -module.exports.init = async () => { +export async function init() { if (!config('console')) return; @@ -36,13 +38,13 @@ module.exports.init = async () => { await CloudCmd.View(); await load(); await create(); -}; +} -module.exports.hide = () => { +export function hide() { CloudCmd.View.hide(); -}; +} -module.exports.clear = () => { +export const clear = () => { konsole.clear(); }; @@ -109,7 +111,7 @@ function authCheck(konsole) { }); } -module.exports.show = (callback) => { +export function show(callback) { if (!Loaded) return; @@ -122,7 +124,7 @@ module.exports.show = (callback) => { exec(callback); }, }); -}; +} const load = async () => { Util.time(`${Name} load`); diff --git a/client/modules/markdown.js b/client/modules/markdown.js index 3ccd5805..c94c64e8 100644 --- a/client/modules/markdown.js +++ b/client/modules/markdown.js @@ -1,26 +1,26 @@ -'use strict'; +import createElement from '@cloudcmd/create-element'; +import * as Images from '#dom/images'; +import {Markdown} from '#dom/rest'; +import {alert} from '#dom/dialog'; -/* global CloudCmd */ -CloudCmd.Markdown = exports; +const {CloudCmd} = globalThis; -const createElement = require('@cloudcmd/create-element'); +CloudCmd.Markdown = { + init, + show, + hide, +}; -const Images = require('../dom/images.mjs'); -const {Markdown} = require('#dom/rest'); -const {alert} = require('#dom/dialog'); - -module.exports.init = async () => { +export async function init() { Images.show.load('top'); await CloudCmd.View(); -}; +} -module.exports.show = show; - -module.exports.hide = () => { +export function hide() { CloudCmd.View.hide(); -}; +} -async function show(name, options = {}) { +export async function show(name, options = {}) { const {positionLoad, relative} = options; Images.show.load(positionLoad); diff --git a/client/modules/menu/cloudmenu.mjs b/client/modules/menu/cloudmenu.js similarity index 83% rename from client/modules/menu/cloudmenu.mjs rename to client/modules/menu/cloudmenu.js index 1fbe5165..8f7c1e68 100644 --- a/client/modules/menu/cloudmenu.mjs +++ b/client/modules/menu/cloudmenu.js @@ -1,4 +1,4 @@ -import supermenu from 'supermenu'; +import {supermenu} from 'supermenu'; const noop = () => {}; const {CloudCmd} = globalThis; @@ -14,8 +14,9 @@ export const createCloudMenu = async (fm, options, menuData) => { async function loadMenu() { if (CloudCmd.config('menu') === 'aleman') { + const {prefix} = CloudCmd; const {host, protocol} = globalThis.location; - const url = `${protocol}//${host}/node_modules/aleman/menu/menu.js`; + const url = `${protocol}//${host}${prefix}/node_modules/aleman/menu/menu.js`; const {createMenu} = await import(/* webpackIgnore: true */url); return createMenu; diff --git a/client/modules/menu/index.mjs b/client/modules/menu/index.js similarity index 98% rename from client/modules/menu/index.mjs rename to client/modules/menu/index.js index a8c7d0df..7235591e 100644 --- a/client/modules/menu/index.mjs +++ b/client/modules/menu/index.js @@ -1,11 +1,11 @@ -/* global CloudCmd, DOM */ import exec from 'execon'; import wrap from 'wraptile'; import createElement from '@cloudcmd/create-element'; import {getIdBySrc} from '#dom/load'; import * as RESTful from '#dom/rest'; -import {FS} from '../../../common/cloudfunc.mjs'; +import {FS} from '#common/cloudfunc'; +const {CloudCmd, DOM} = globalThis; const {config, Key} = CloudCmd; const { @@ -45,7 +45,7 @@ export async function init() { type: 'file', }); - const {createCloudMenu} = await import('./cloudmenu.mjs'); + const {createCloudMenu} = await import('./cloudmenu.js'); const {name} = fm.dataset; diff --git a/client/modules/operation/format.mjs b/client/modules/operation/format.js similarity index 100% rename from client/modules/operation/format.mjs rename to client/modules/operation/format.js diff --git a/client/modules/operation/get-next-current-name.mjs b/client/modules/operation/get-next-current-name.js similarity index 100% rename from client/modules/operation/get-next-current-name.mjs rename to client/modules/operation/get-next-current-name.js diff --git a/client/modules/operation/index.mjs b/client/modules/operation/index.js similarity index 97% rename from client/modules/operation/index.mjs rename to client/modules/operation/index.js index a2e7c0e7..f349f7d8 100644 --- a/client/modules/operation/index.mjs +++ b/client/modules/operation/index.js @@ -4,10 +4,10 @@ import {promisify} from 'es6-promisify'; import exec from 'execon'; import load from 'load.js'; import {tryToCatch} from 'try-to-catch'; -import {encode} from '../../../common/entity.js'; -import {removeExtension} from './remove-extension.mjs'; -import {setListeners} from './set-listeners.mjs'; -import {getNextCurrentName} from './get-next-current-name.mjs'; +import {encode} from '#common/entity'; +import {removeExtension} from './remove-extension.js'; +import {setListeners} from './set-listeners.js'; +import {getNextCurrentName} from './get-next-current-name.js'; const {DOM, CloudCmd} = globalThis; @@ -190,11 +190,11 @@ function getPacker(type) { return packTarFn; } -export const hide = () => { +export function hide() { CloudCmd.View.hide(); -}; +} -export const show = (operation, data) => { +export function show(operation, data) { if (!Loaded) return; @@ -215,7 +215,7 @@ export const show = (operation, data) => { if (operation === 'extract') return Operation.extract(); -}; +} Operation.copy = processFiles({ type: 'copy', diff --git a/client/modules/operation/remove-extension.mjs b/client/modules/operation/remove-extension.js similarity index 100% rename from client/modules/operation/remove-extension.mjs rename to client/modules/operation/remove-extension.js diff --git a/client/modules/operation/remove-extension.spec.mjs b/client/modules/operation/remove-extension.spec.js similarity index 92% rename from client/modules/operation/remove-extension.spec.mjs rename to client/modules/operation/remove-extension.spec.js index 8dbacb61..fa11e9e2 100644 --- a/client/modules/operation/remove-extension.spec.mjs +++ b/client/modules/operation/remove-extension.spec.js @@ -1,5 +1,5 @@ import test from 'supertape'; -import {removeExtension} from './remove-extension.mjs'; +import {removeExtension} from './remove-extension.js'; test('cloudcmd: client: modules: operation: removeExtension: .tar.gz', (t) => { const name = 'hello'; diff --git a/client/modules/operation/set-listeners.mjs b/client/modules/operation/set-listeners.js similarity index 85% rename from client/modules/operation/set-listeners.mjs rename to client/modules/operation/set-listeners.js index 355aee79..e54e7d14 100644 --- a/client/modules/operation/set-listeners.mjs +++ b/client/modules/operation/set-listeners.js @@ -1,7 +1,7 @@ /* global DOM */ import forEachKey from 'for-each-key'; import wraptile from 'wraptile'; -import {format} from './format.mjs'; +import {format} from './format.js'; const {Dialog, Images} = DOM; @@ -15,15 +15,12 @@ export const setListeners = (options) => (emitter) => { } = options; let done; - let lastError; const onAbort = wraptile(({emitter, operation}) => { emitter.abort(); const msg = `${operation} aborted`; - lastError = true; - Dialog.alert(msg, { cancel: false, }); @@ -40,13 +37,10 @@ export const setListeners = (options) => (emitter) => { operation, })); - let noProgress = true; - const listeners = { progress: (value) => { done = value === 100; progress.setProgress(value); - noProgress = false; }, end: () => { @@ -54,13 +48,10 @@ export const setListeners = (options) => (emitter) => { forEachKey(removeListener, listeners); progress.remove(); - if (lastError || done || noProgress) - callback(); + callback(); }, error: async (error) => { - lastError = error; - if (noContinue) { listeners.end(error); Dialog.alert(error); diff --git a/client/modules/polyfill.js b/client/modules/polyfill.js index ff0e59c5..90b62689 100644 --- a/client/modules/polyfill.js +++ b/client/modules/polyfill.js @@ -1,12 +1,8 @@ -'use strict'; - -require('domtokenlist-shim'); - -const _scrollIntoViewIfNeeded = require('scroll-into-view-if-needed'); +import _scrollIntoViewIfNeeded from 'scroll-into-view-if-needed'; globalThis.DOM = globalThis.DOM || {}; -const scrollIntoViewIfNeeded = (el, overrides = {}) => { +export const scrollIntoViewIfNeeded = (el, overrides = {}) => { const { scroll = _scrollIntoViewIfNeeded, } = overrides; @@ -17,4 +13,3 @@ const scrollIntoViewIfNeeded = (el, overrides = {}) => { }; globalThis.DOM.scrollIntoViewIfNeeded = scrollIntoViewIfNeeded; -module.exports.scrollIntoViewIfNeeded = scrollIntoViewIfNeeded; diff --git a/client/modules/polyfill.spec.js b/client/modules/polyfill.spec.js index 79d9dfda..740947eb 100644 --- a/client/modules/polyfill.spec.js +++ b/client/modules/polyfill.spec.js @@ -1,9 +1,7 @@ -'use strict'; +import {test, stub} from 'supertape'; +import {scrollIntoViewIfNeeded} from './polyfill.js'; -const {test, stub} = require('supertape'); -const {scrollIntoViewIfNeeded} = require('./polyfill'); - -test('cloudcmd: client: polyfill: scrollIntoViewIfNeaded', (t) => { +test('cloudcmd: client: polyfill: scrollIntoViewIfNeeded', (t) => { const scroll = stub(); const el = {}; @@ -17,6 +15,6 @@ test('cloudcmd: client: polyfill: scrollIntoViewIfNeaded', (t) => { }, ]; - t.calledWith(scroll, args, 'should call scrollIntoViewIfNeaded'); + t.calledWith(scroll, args, 'should call scrollIntoViewIfNeeded'); t.end(); }); diff --git a/client/modules/terminal-run.js b/client/modules/terminal-run.js index 8b24fd2a..029415ea 100644 --- a/client/modules/terminal-run.js +++ b/client/modules/terminal-run.js @@ -1,21 +1,21 @@ -'use strict'; - -/* global CloudCmd, gritty */ -const {promisify} = require('es6-promisify'); -const {tryToCatch} = require('try-to-catch'); -const {fullstore} = require('fullstore'); - -require('../../css/terminal.css'); - -const exec = require('execon'); -const load = require('load.js'); -const DOM = require('../dom/index.mjs'); -const Images = require('../dom/images.mjs'); +import '../../css/terminal.css'; +import {promisify} from 'es6-promisify'; +import {tryToCatch} from 'try-to-catch'; +import {fullstore} from 'fullstore'; +import exec from 'execon'; +import load from 'load.js'; +import DOM from '#dom'; +import * as Images from '#dom/images'; const {Dialog} = DOM; +const {CloudCmd} = globalThis; const {Key, config} = CloudCmd; -CloudCmd.TerminalRun = exports; +CloudCmd.TerminalRun = { + init, + show, + hide, +}; let Loaded; let Terminal; @@ -40,7 +40,7 @@ const loadAll = async () => { Loaded = true; }; -module.exports.init = async () => { +export async function init() { if (!config('terminal')) return; @@ -48,9 +48,13 @@ module.exports.init = async () => { await CloudCmd.View(); await loadAll(); -}; +} -module.exports.show = promisify((options = {}, fn) => { +export async function show(options = {}) { + return await runTerminal(options); +} + +const runTerminal = promisify((options, fn) => { if (!Loaded) return fn(null, -1); @@ -69,9 +73,7 @@ module.exports.show = promisify((options = {}, fn) => { }); }); -module.exports.hide = hide; - -function hide() { +export function hide() { CloudCmd.View.hide(); } @@ -108,7 +110,7 @@ function create(createOptions) { let commandExit = false; - const {socket, terminal} = gritty(document.body, options); + const {socket, terminal} = globalThis.gritty(document.body, options); Socket = socket; Terminal = terminal; diff --git a/client/modules/terminal.js b/client/modules/terminal.js index b5850526..186ee0b2 100644 --- a/client/modules/terminal.js +++ b/client/modules/terminal.js @@ -1,22 +1,22 @@ -'use strict'; - -/* global CloudCmd */ -/* global gritty */ -const {tryToCatch} = require('try-to-catch'); - -require('../../css/terminal.css'); - -const exec = require('execon'); -const load = require('load.js'); -const DOM = require('../dom/index.mjs'); -const Images = require('../dom/images.mjs'); +import '#css/terminal.css'; +import {tryToCatch} from 'try-to-catch'; +import exec from 'execon'; +import load from 'load.js'; +import * as Images from '#dom/images'; +import DOM from '#dom'; const loadParallel = load.parallel; -const {Dialog} = DOM; -const {Key, config} = CloudCmd; +const {CloudCmd} = globalThis; -CloudCmd.Terminal = exports; +const {Dialog} = DOM; +const {Key, config} = globalThis.CloudCmd; + +CloudCmd.Terminal = { + init, + show, + hide, +}; let Loaded; let Terminal; @@ -39,7 +39,7 @@ const loadAll = async () => { Loaded = true; }; -module.exports.init = async () => { +export async function init() { if (!config('terminal')) return; @@ -48,12 +48,9 @@ module.exports.init = async () => { await CloudCmd.View(); await loadAll(); create(); -}; +} -module.exports.show = show; -module.exports.hide = hide; - -function hide() { +export function hide() { CloudCmd.View.hide(); } @@ -78,7 +75,7 @@ function create() { fontFamily: 'Droid Sans Mono', }; - const {socket, terminal} = gritty(document.body, options); + const {socket, terminal} = globalThis.gritty(document.body, options); Socket = socket; Terminal = terminal; @@ -101,7 +98,7 @@ function authCheck(spawn) { }); } -function show() { +export function show() { if (!Loaded) return; diff --git a/client/modules/upload.mjs b/client/modules/upload.js similarity index 96% rename from client/modules/upload.mjs rename to client/modules/upload.js index 152f8b97..8433fbf5 100644 --- a/client/modules/upload.mjs +++ b/client/modules/upload.js @@ -2,7 +2,7 @@ import createElement from '@cloudcmd/create-element'; import * as Files from '#dom/files'; import {uploadFiles} from '#dom/upload-files'; -import * as Images from '../dom/images.mjs'; +import * as Images from '#dom/images'; CloudCmd.Upload = { init, diff --git a/client/modules/user-menu/get-user-menu.mjs b/client/modules/user-menu/get-user-menu.js similarity index 100% rename from client/modules/user-menu/get-user-menu.mjs rename to client/modules/user-menu/get-user-menu.js diff --git a/client/modules/user-menu/get-user-menu.spec.mjs b/client/modules/user-menu/get-user-menu.spec.js similarity index 89% rename from client/modules/user-menu/get-user-menu.spec.mjs rename to client/modules/user-menu/get-user-menu.spec.js index aae42909..606eb820 100644 --- a/client/modules/user-menu/get-user-menu.spec.mjs +++ b/client/modules/user-menu/get-user-menu.spec.js @@ -1,5 +1,5 @@ import test from 'supertape'; -import {getUserMenu} from './get-user-menu.mjs'; +import {getUserMenu} from './get-user-menu.js'; test('user-menu: getUserMenu', (t) => { const menu = `module.exports = { diff --git a/client/modules/user-menu/index.mjs b/client/modules/user-menu/index.js similarity index 91% rename from client/modules/user-menu/index.mjs rename to client/modules/user-menu/index.js index 9b85612c..60d32d95 100644 --- a/client/modules/user-menu/index.mjs +++ b/client/modules/user-menu/index.js @@ -1,4 +1,4 @@ -/* global CloudCmd, DOM */ +import '../../../css/user-menu.css'; import currify from 'currify'; import wraptile from 'wraptile'; import {fullstore} from 'fullstore'; @@ -8,17 +8,22 @@ import {tryCatch} from 'try-catch'; import {tryToCatch} from 'try-to-catch'; import {codeFrameColumns} from '@babel/code-frame'; import * as Dialog from '#dom/dialog'; -import '../../../css/user-menu.css'; -import * as Images from '../../dom/images.mjs'; -import {getUserMenu} from './get-user-menu.mjs'; -import {navigate} from './navigate.mjs'; -import {parseError} from './parse-error.mjs'; -import {parseUserMenu} from './parse-user-menu.mjs'; -import {runSelected} from './run.mjs'; +import * as Images from '#dom/images'; +import {getUserMenu} from './get-user-menu.js'; +import {navigate} from './navigate.js'; +import {parseError} from './parse-error.js'; +import {parseUserMenu} from './parse-user-menu.js'; +import {runSelected} from './run.js'; const loadCSS = load.css; const sourceStore = fullstore(); +const { + CloudCmd, + DOM, + CloudFunc, +} = globalThis; + const Name = 'UserMenu'; CloudCmd[Name] = { @@ -146,6 +151,7 @@ const runUserMenu = async (fn) => { const [error] = await tryToCatch(fn, { DOM, CloudCmd, + CloudFunc, tryToCatch, }); diff --git a/client/modules/user-menu/navigate.mjs b/client/modules/user-menu/navigate.js similarity index 97% rename from client/modules/user-menu/navigate.mjs rename to client/modules/user-menu/navigate.js index 2ebebd17..0fa4d672 100644 --- a/client/modules/user-menu/navigate.mjs +++ b/client/modules/user-menu/navigate.js @@ -4,7 +4,7 @@ import { K, UP, DOWN, -} from '../../key/key.mjs'; +} from '../../key/key.js'; const store = fullstore(1); const isDigit = (a) => /^\d+$/.test(a); diff --git a/client/modules/user-menu/navigate.spec.mjs b/client/modules/user-menu/navigate.spec.js similarity index 97% rename from client/modules/user-menu/navigate.spec.mjs rename to client/modules/user-menu/navigate.spec.js index a2de328b..6539c8e9 100644 --- a/client/modules/user-menu/navigate.spec.mjs +++ b/client/modules/user-menu/navigate.spec.js @@ -1,11 +1,11 @@ import test from 'supertape'; -import {navigate} from './navigate.mjs'; +import {navigate} from './navigate.js'; import { UP, DOWN, J, K, -} from '../../key/key.mjs'; +} from '../../key/key.js'; test('cloudcmd: user-menu: navigate: DOWN', (t) => { const el = { diff --git a/client/modules/user-menu/parse-error.mjs b/client/modules/user-menu/parse-error.js similarity index 100% rename from client/modules/user-menu/parse-error.mjs rename to client/modules/user-menu/parse-error.js diff --git a/client/modules/user-menu/parse-error.spec.mjs b/client/modules/user-menu/parse-error.spec.js similarity index 94% rename from client/modules/user-menu/parse-error.spec.mjs rename to client/modules/user-menu/parse-error.spec.js index cef3ae55..5ec8da45 100644 --- a/client/modules/user-menu/parse-error.spec.mjs +++ b/client/modules/user-menu/parse-error.spec.js @@ -1,5 +1,5 @@ import test from 'supertape'; -import {parseError} from './parse-error.mjs'; +import {parseError} from './parse-error.js'; test('user-menu: parse-error', (t) => { const result = parseError({ diff --git a/client/modules/user-menu/parse-user-menu.mjs b/client/modules/user-menu/parse-user-menu.js similarity index 100% rename from client/modules/user-menu/parse-user-menu.mjs rename to client/modules/user-menu/parse-user-menu.js diff --git a/client/modules/user-menu/parse-user-menu.spec.mjs b/client/modules/user-menu/parse-user-menu.spec.js similarity index 91% rename from client/modules/user-menu/parse-user-menu.spec.mjs rename to client/modules/user-menu/parse-user-menu.spec.js index a7914152..fb763209 100644 --- a/client/modules/user-menu/parse-user-menu.spec.mjs +++ b/client/modules/user-menu/parse-user-menu.spec.js @@ -1,9 +1,10 @@ import {test, stub} from 'supertape'; -import {parseUserMenu} from './parse-user-menu.mjs'; +import {parseUserMenu} from './parse-user-menu.js'; test('cloudcmd: user menu: parse', (t) => { const fn = stub(); const __settings = {}; + const result = parseUserMenu({ __settings, 'F2 - Rename file': fn, diff --git a/client/modules/user-menu/run.mjs b/client/modules/user-menu/run.js similarity index 100% rename from client/modules/user-menu/run.mjs rename to client/modules/user-menu/run.js diff --git a/client/modules/user-menu/run.spec.mjs b/client/modules/user-menu/run.spec.js similarity index 89% rename from client/modules/user-menu/run.spec.mjs rename to client/modules/user-menu/run.spec.js index 8cac782d..86738712 100644 --- a/client/modules/user-menu/run.spec.mjs +++ b/client/modules/user-menu/run.spec.js @@ -1,5 +1,5 @@ import {test, stub} from 'supertape'; -import {runSelected} from './run.mjs'; +import {runSelected} from './run.js'; test('cloudcmd: client: user menu: run', async (t) => { const runUserMenu = stub(); diff --git a/client/modules/view/get-type.js b/client/modules/view/get-type.js index 9fc1df1d..a061399f 100644 --- a/client/modules/view/get-type.js +++ b/client/modules/view/get-type.js @@ -1,6 +1,5 @@ -'use strict'; +import currify from 'currify'; -const currify = require('currify'); const testRegExp = currify((name, reg) => reg.test(name)); const getRegExp = (ext) => RegExp(`\\.${ext}$`, 'i'); @@ -8,7 +7,7 @@ const isPDF = (a) => /\.pdf$/i.test(a); const isHTML = (a) => a.endsWith('.html'); const isMarkdown = (a) => /.\.md$/.test(a); -module.exports = (name) => { +export default (name) => { if (isPDF(name)) return 'pdf'; diff --git a/client/modules/view/index.js b/client/modules/view/index.js index 47724798..6626f1aa 100644 --- a/client/modules/view/index.js +++ b/client/modules/view/index.js @@ -1,35 +1,25 @@ -/* global CloudCmd, DOM */ - -'use strict'; - -const CloudCmd = globalThis.CloudCmd || {}; -const DOM = globalThis.DOM || {}; - -require('../../../css/view.css'); - -const rendy = require('rendy'); -const currify = require('currify'); -const wraptile = require('wraptile'); -const {tryToCatch} = require('try-to-catch'); -const load = require('load.js'); - -const _modal = require('@cloudcmd/modal'); -const _createElement = require('@cloudcmd/create-element'); - -const {time} = require('#common/util'); -const {FS} = require('../../../common/cloudfunc.mjs'); - -const { +import '#css/view.css'; +import {rendy} from 'rendy'; +import currify from 'currify'; +import wraptile from 'wraptile'; +import {tryToCatch} from 'try-to-catch'; +import load from 'load.js'; +import * as _modal from '@cloudcmd/modal'; +import _createElement from '@cloudcmd/create-element'; +import {time} from '#common/util'; +import * as Files from '#dom/files'; +import * as Events from '#dom/events'; +import {FS} from '#common/cloudfunc'; +import * as Images from '#dom/images'; +import {encode} from '#common/entity'; +import { isImage, isAudio, getType, -} = require('./types'); +} from './types.js'; -const Files = require('#dom/files'); -const Events = require('#dom/events'); -const Images = require('../../dom/images.mjs'); - -const {encode} = require('../../../common/entity'); +const CloudCmd = globalThis.CloudCmd || {}; +const DOM = globalThis.DOM || {}; const isString = (a) => typeof a === 'string'; const {assign} = Object; const {isArray} = Array; @@ -47,14 +37,15 @@ const addEvent = lifo(Events.add); const loadCSS = load.css; -module.exports.show = show; -module.exports.hide = hide; - let Loading = false; const Name = 'View'; -CloudCmd[Name] = module.exports; +CloudCmd[Name] = { + init, + show, + hide, +}; const Info = DOM.CurrentInfo; const {Key} = CloudCmd; @@ -91,9 +82,9 @@ const Config = { }, }; -module.exports._Config = Config; +export const _Config = Config; -module.exports.init = async () => { +export async function init() { await loadAll(); const events = [ @@ -105,9 +96,9 @@ module.exports.init = async () => { Overlay, onOverlayClick, )); -}; +} -async function show(data, options = {}) { +export async function show(data, options = {}) { const prefixURL = CloudCmd.prefixURL + FS; if (Loading) @@ -159,7 +150,8 @@ async function show(data, options = {}) { } } -module.exports._createIframe = createIframe; +export const _createIframe = createIframe; + function createIframe(src, overrides = {}) { const { createElement = _createElement, @@ -178,7 +170,8 @@ function createIframe(src, overrides = {}) { return element; } -module.exports._viewHtml = viewHtml; +export const _viewHtml = viewHtml; + function viewHtml(src, overrides = {}) { const {modal = _modal} = overrides; modal.open(createIframe(src), Config); @@ -234,7 +227,8 @@ async function viewFile() { const copy = (a) => assign({}, a); -module.exports._initConfig = initConfig; +export const _initConfig = initConfig; + function initConfig(options) { const config = copy(Config); @@ -260,7 +254,7 @@ function initConfig(options) { return config; } -function hide() { +export function hide() { _modal.close(); } diff --git a/client/modules/view/index.spec.js b/client/modules/view/index.spec.js index cc46d07d..abfbe3f4 100644 --- a/client/modules/view/index.spec.js +++ b/client/modules/view/index.spec.js @@ -1,17 +1,14 @@ -'use strict'; - -require('css-modules-require-hook/preset'); - -const autoGlobals = require('auto-globals'); -const {stub} = require('@cloudcmd/stub'); - -const test = autoGlobals(require('supertape')); -const { +import autoGlobals from 'auto-globals'; +import {stub} from '@cloudcmd/stub'; +import {test as tape} from 'supertape'; +import { _initConfig, _viewHtml, _Config, _createIframe, -} = require('.'); +} from './index.js'; + +const test = autoGlobals(tape); test('cloudcmd: client: view: initConfig', (t) => { let config; @@ -34,8 +31,10 @@ test('cloudcmd: client: view: initConfig', (t) => { test('cloudcmd: client: view: initConfig: no options', (t) => { const config = _initConfig(); + const result = typeof config; + const expected = 'object'; - t.equal(typeof config, 'object'); + t.equal(result, expected); t.end(); }); diff --git a/client/modules/view/types.js b/client/modules/view/types.js index d94dd971..320a4bc8 100644 --- a/client/modules/view/types.js +++ b/client/modules/view/types.js @@ -1,8 +1,8 @@ -'use strict'; +import {extname} from 'node:path'; +import currify from 'currify'; + +export const isAudio = (name) => /\.(mp3|ogg|m4a|flac)$/i.test(name); -const {extname} = require('node:path'); -const currify = require('currify'); -const isAudio = (name) => /\.(mp3|ogg|m4a)$/i.test(name); const testRegExp = currify((name, reg) => reg.test(name)); const getRegExp = (ext) => RegExp(`\\.${ext}$`, 'i'); @@ -10,7 +10,7 @@ const isPDF = (a) => /\.pdf$/i.test(a); const isHTML = (a) => a.endsWith('.html'); const isMarkdown = (a) => /.\.md$/.test(a); -module.exports.getType = async (path) => { +export const getType = async (path) => { const ext = extname(path); if (!ext) @@ -32,8 +32,7 @@ module.exports.getType = async (path) => { return 'markdown'; }; -module.exports.isImage = isImage; -function isImage(name) { +export function isImage(name) { const images = [ 'jp(e|g|eg)', 'gif', @@ -53,13 +52,12 @@ function isMedia(name) { return isAudio(name) || isVideo(name); } -module.exports.isAudio = isAudio; - function isVideo(name) { return /\.(mp4|avi|webm)$/i.test(name); } -module.exports._detectType = detectType; +export const _detectType = detectType; + async function detectType(path) { const {headers} = await fetch(path, { method: 'HEAD', diff --git a/client/modules/view/types.spec.js b/client/modules/view/types.spec.js index d8004f9e..f0542c18 100644 --- a/client/modules/view/types.spec.js +++ b/client/modules/view/types.spec.js @@ -1,7 +1,5 @@ -'use strict'; - -const {test, stub} = require('supertape'); -const {isAudio, _detectType} = require('./types'); +import {test, stub} from 'supertape'; +import {isAudio, _detectType} from './types.js'; test('cloudcmd: client: view: types: isAudio', (t) => { const result = isAudio('hello.mp3'); @@ -10,6 +8,13 @@ test('cloudcmd: client: view: types: isAudio', (t) => { t.end(); }); +test('cloudcmd: client: view: types: isAudio: flac', (t) => { + const result = isAudio('hello.flac'); + + t.ok(result); + t.end(); +}); + test('cloudcmd: client: view: types: isAudio: no', (t) => { const result = isAudio('hello'); diff --git a/client/sort.mjs b/client/sort.js similarity index 95% rename from client/sort.mjs rename to client/sort.js index 8ac12452..ed0971f3 100644 --- a/client/sort.mjs +++ b/client/sort.js @@ -1,6 +1,6 @@ /* global CloudCmd */ import {fullstore} from 'fullstore'; -import DOM from './dom/index.mjs'; +import DOM from '#dom'; const sortPrevious = fullstore(); diff --git a/client/sw/register.mjs b/client/sw/register.js similarity index 90% rename from client/sw/register.mjs rename to client/sw/register.js index da3ff0f2..928d63c9 100644 --- a/client/sw/register.mjs +++ b/client/sw/register.js @@ -16,7 +16,7 @@ export async function registerSW(prefix) { const {serviceWorker} = navigator; const register = serviceWorker.register.bind(serviceWorker); - const [e, sw] = await tryToCatch(register, `${prefix}/sw.mjs`); + const [e, sw] = await tryToCatch(register, `${prefix}/sw.js`); if (e) return null; diff --git a/client/sw/register.spec.mjs b/client/sw/register.spec.js similarity index 92% rename from client/sw/register.spec.mjs rename to client/sw/register.spec.js index 5734a130..52699f0b 100644 --- a/client/sw/register.spec.mjs +++ b/client/sw/register.spec.js @@ -6,7 +6,7 @@ import { listenSW, registerSW, unregisterSW, -} from './register.mjs'; +} from './register.js'; const test = autoGlobals(tape); @@ -65,7 +65,7 @@ test('sw: register: registerSW: http', async (t, {location, navigator}) => { test('sw: register: registerSW: https self-signed', async (t, {location, navigator}) => { Object.assign(location, { - protocol: 'https', + protocol: 'https:', hostname: 'self-signed.badssl.com', }); @@ -84,7 +84,7 @@ test('sw: register: registerSW', async (t, {location, navigator}) => { const {register} = navigator.serviceWorker; await registerSW('/hello'); - t.calledWith(register, ['/hello/sw.mjs'], 'should call register'); + t.calledWith(register, ['/hello/sw.js'], 'should call register'); t.end(); }); @@ -98,6 +98,6 @@ test('sw: register: unregisterSW', async (t, {location, navigator}) => { await unregisterSW('/hello'); - t.calledWith(register, ['/hello/sw.mjs'], 'should call register'); + t.calledWith(register, ['/hello/sw.js'], 'should call register'); t.end(); }); diff --git a/client/sw/sw.mjs b/client/sw/sw.js similarity index 100% rename from client/sw/sw.mjs rename to client/sw/sw.js diff --git a/common/callbackify.js b/common/callbackify.js index c2040a3d..71160ac7 100644 --- a/common/callbackify.js +++ b/common/callbackify.js @@ -1,8 +1,6 @@ -'use strict'; - const success = (f) => (data) => f(null, data); -module.exports = (promise) => (...a) => { +export default (promise) => (...a) => { const fn = a.pop(); promise(...a) diff --git a/common/callbackify.spec.js b/common/callbackify.spec.js index 0a46d9e9..d1586d5a 100644 --- a/common/callbackify.spec.js +++ b/common/callbackify.spec.js @@ -1,11 +1,7 @@ -'use strict'; - -const {promisify} = require('node:util'); -const {tryToCatch} = require('try-to-catch'); - -const {test, stub} = require('supertape'); - -const callbackify = require('./callbackify'); +import {promisify} from 'node:util'; +import {tryToCatch} from 'try-to-catch'; +import {test, stub} from 'supertape'; +import callbackify from './callbackify.js'; test('cloudcmd: common: callbackify: error', async (t) => { const promise = stub().rejects(Error('hello')); diff --git a/common/cloudfunc.mjs b/common/cloudfunc.js similarity index 93% rename from common/cloudfunc.mjs rename to common/cloudfunc.js index 00a48360..7884d069 100644 --- a/common/cloudfunc.mjs +++ b/common/cloudfunc.js @@ -1,17 +1,16 @@ -import rendy from 'rendy'; +import {rendy} from 'rendy'; import currify from 'currify'; -import store from 'fullstore'; -import {encode} from './entity.js'; +import {fullstore} from 'fullstore'; +import {encode} from '#common/entity'; -export const getHeaderField = currify(_getHeaderField); - -/* КОНСТАНТЫ (общие для клиента и сервера)*/ -/* название программы */ +const id = (a) => a; const NAME = 'Cloud Commander'; +export const dateFormatter = fullstore(id); +export const getHeaderField = currify(_getHeaderField); export const FS = '/fs'; -const Path = store(); +const Path = fullstore(); Path('/'); @@ -123,12 +122,13 @@ export const buildFromJSON = (params) => { showDotFiles, } = params; + const formatDate = dateFormatter(); + const templateFile = template.file; const templateLink = template.link; const json = params.data; const path = encode(json.path); - const {files} = json; /* @@ -151,6 +151,7 @@ export const buildFromJSON = (params) => { const name = getFieldName('name'); const size = getFieldName('size'); const date = getFieldName('date'); + const time = getFieldName('time'); const header = rendy(templateFile, { tag: 'div', @@ -160,6 +161,7 @@ export const buildFromJSON = (params) => { name, size, date, + time, owner, mode, }); @@ -192,6 +194,7 @@ export const buildFromJSON = (params) => { name: linkResult, size: '<dir>', date: '--.--.----', + time: '--:--:--', owner: '.', mode: '--- --- ---', }); @@ -210,6 +213,7 @@ export const buildFromJSON = (params) => { type, mode, date, + time, owner, size, } = file; @@ -231,7 +235,8 @@ export const buildFromJSON = (params) => { type, name: linkResult, size, - date, + date: formatDate(date), + time, owner, mode, }); @@ -246,6 +251,7 @@ export const buildFromJSON = (params) => { const updateField = (file) => ({ ...file, date: file.date || '--.--.----', + time: file.time || '--:--:--', owner: file.owner || 'root', size: getSize(file), }); diff --git a/common/cloudfunc.spec.js b/common/cloudfunc.spec.js new file mode 100644 index 00000000..b679feeb --- /dev/null +++ b/common/cloudfunc.spec.js @@ -0,0 +1,364 @@ +import {readFileSync} from 'node:fs'; +import test from 'supertape'; +import {montag} from 'montag'; +import * as cheerio from 'cheerio'; +import {tryCatch} from 'try-catch'; +import { + _getSize, + getPathLink, + buildFromJSON, + _getDataName, + dateFormatter, + formatMsg, + getTitle, + getDotDot, + getHeaderField, +} from '#common/cloudfunc'; + +const templatePath = new URL('../tmpl/fs', import.meta.url).pathname; + +const template = { + pathLink: readFileSync(`${templatePath}/pathLink.hbs`, 'utf8'), + path: readFileSync(`${templatePath}/path.hbs`, 'utf8'), + file: readFileSync(`${templatePath}/file.hbs`, 'utf8'), + link: readFileSync(`${templatePath}/link.hbs`, 'utf8'), +}; + +test('cloudfunc: buildFromJSON: ..', (t) => { + const data = { + path: '/media/', + files: [{ + date: '30.08.2016', + mode: 'rwx rwx rwx', + name: 'floppy', + owner: 'root', + size: '7b', + type: 'directory-link', + }], + }; + + const html = buildFromJSON({ + prefix: '', + template, + data, + }); + + const $ = cheerio.load(html); + const el = $('[data-name="js-file-Li4="]'); + + const result = el + .find('[data-name="js-name"]') + .text(); + + const expected = '..'; + + t.equal(result, expected); + t.end(); +}); + +test('cloudfunc: getPathLink: /', (t) => { + const {pathLink} = template; + const result = getPathLink('/', '', pathLink); + + const expected = montag` + / + `; + + t.equal(result, expected); + t.end(); +}); + +test('cloudfunc: getPathLink: /hello/world', (t) => { + const {pathLink} = template; + const result = getPathLink('/hello/world', '', pathLink); + + const expected = montag` + /hello/ + `; + + t.equal(result, expected); + t.end(); +}); + +test('cloudfunc: getPathLink: prefix', (t) => { + const {pathLink} = template; + const result = getPathLink('/hello/world', '/cloudcmd', pathLink); + + const expected = montag` + /hello/ + `; + + t.equal(result, expected); + t.end(); +}); + +test('cloudfunc: getSize: dir', (t) => { + const type = 'directory'; + const size = 0; + + const result = _getSize({ + type, + size, + }); + + const expected = '<dir>'; + + t.equal(result, expected); + t.end(); +}); + +test('cloudfunc: getSize: link: dir', (t) => { + const type = 'directory-link'; + const size = 0; + + const result = _getSize({ + type, + size, + }); + + const expected = '<link>'; + + t.equal(result, expected); + t.end(); +}); + +test('cloudfunc: getSize: link: file', (t) => { + const type = 'file-link'; + const size = 0; + + const result = _getSize({ + type, + size, + }); + + const expected = '<link>'; + + t.equal(result, expected); + t.end(); +}); + +test('cloudfunc: getSize: file', (t) => { + const type = 'file'; + const size = '100.00kb'; + + const result = _getSize({ + type, + size, + }); + + const expected = '100.00kb'; + + t.equal(result, expected); + t.end(); +}); + +test('cloudfunc: buildFromJSON: showDotFiles: false', (t) => { + const data = { + path: '/media/', + files: [{ + date: '30.08.2016', + mode: 'rwx rwx rwx', + name: '.floppy', + owner: 'root', + size: '7b', + type: 'directory-link', + }], + }; + + const html = buildFromJSON({ + prefix: '', + template, + data, + showDotFiles: false, + }); + + const $ = cheerio.load(html); + const el = $('[data-name="js-file-LmZsb3BweQ=="]'); + + const result = el + .find('[data-name="js-name"]') + .text(); + + const expected = ''; + + t.equal(result, expected); + t.end(); +}); + +test('cloudfunc: buildFromJSON: name: {{ }}', (t) => { + const data = { + path: '/media/', + files: [{ + date: '30.08.2016', + mode: 'rwx rwx rwx', + name: '{{}}', + owner: 'root', + size: '7b', + type: 'file', + }], + }; + + const html = buildFromJSON({ + prefix: '', + template, + data, + showDotFiles: false, + }); + + const $ = cheerio.load(html); + const el = $('[data-name="js-file-JTdCJTdCJTdEJTdE"]'); + + const result = el + .find('[data-name="js-name"]') + .text(); + + const expected = '{{}}'; + + t.equal(result, expected); + t.end(); +}); + +test('cloudfunc: _getDataName', (t) => { + const result = _getDataName('s'); + const expected = 'data-name="js-file-cw==" '; + + t.equal(result, expected); + t.end(); +}); + +test('cloudfunc: formatMsg: name', (t) => { + const result = formatMsg('hello', 'world'); + const expected = 'hello: ok("world")'; + + t.equal(result, expected, 'should format message with name'); + t.end(); +}); + +test('cloudfunc: formatMsg: no name', (t) => { + const result = formatMsg('hello'); + const expected = 'hello: ok'; + + t.equal(result, expected, 'should format message without name'); + t.end(); +}); + +test('cloudfunc: getTitle: no options', (t) => { + const result = getTitle(); + + t.ok(result, 'should return a title string even without options'); + t.end(); +}); + +test('cloudfunc: getTitle: with name', (t) => { + const result = getTitle({ + name: 'MyName', + }); + + t.match(result, 'MyName', 'should return title with name'); + t.end(); +}); + +test('cloudfunc: getHeaderField: sort not name', (t) => { + const result = getHeaderField('size', 'asc', 'name'); + + t.equal(result, 'name', 'should return plain name when sort does not match'); + t.end(); +}); + +test('cloudfunc: getHeaderField: sort name asc', (t) => { + const result = getHeaderField('name', 'asc', 'name'); + + t.equal(result, 'name', 'should return plain name when name asc'); + t.end(); +}); + +test('cloudfunc: getDotDot: root', (t) => { + const result = getDotDot('/'); + + t.equal(result, '/', 'should return / for root path'); + t.end(); +}); + +test('cloudfunc: getPathLink: /a/b/c', (t) => { + const {pathLink} = template; + const result = getPathLink('/a/b/c/', '', pathLink); + + t.ok(result, 'should build path link for 3-segment path'); + t.end(); +}); + +test('cloudfunc: getPathLink: no url', (t) => { + const [error] = tryCatch(getPathLink); + + t.equal(error.message, 'url could not be empty!', 'should throw when url is empty'); + t.end(); +}); + +test('cloudfunc: getPathLink: no template', (t) => { + const [error] = tryCatch(getPathLink, '/'); + + t.equal(error.message, 'template could not be empty!', 'should throw when template is empty'); + t.end(); +}); + +test('cloudfunc: getHeaderField: sort name desc', (t) => { + const result = getHeaderField('name', 'desc', 'name'); + const expected = 'name↓'; + + t.equal(result, expected, 'should return name with down arrow'); + t.end(); +}); + +test('cloudfunc: getDotDot: normal path', (t) => { + const result = getDotDot('/hello/world/'); + const expected = '/hello'; + + t.equal(result, expected, 'should return parent directory'); + t.end(); +}); + +test('cloudfunc: buildFromJSON: formatDate', (t) => { + const data = { + path: '/media/', + files: [{ + date: '30.08.2016', + mode: 'rwx rwx rwx', + name: '{{}}', + owner: 'root', + size: '7b', + type: 'file', + }], + }; + + const oldFormatter = dateFormatter(); + + const formatDate = (str) => { + const [day, month, year] = str.split('.'); + const date = new Date(year, month - 1, day); + + return date.toLocaleDateString('en-US'); + }; + + dateFormatter(formatDate); + + const html = buildFromJSON({ + prefix: '', + template, + data, + showDotFiles: false, + }); + + dateFormatter(oldFormatter); + + const $ = cheerio.load(html); + const el = $('[data-name="js-file-JTdCJTdCJTdEJTdE"]'); + + const result = el + .find('[data-name="js-date"]') + .text(); + + const expected = '8/30/2016'; + + t.equal(result, expected); + t.end(); +}); diff --git a/common/cloudfunc.spec.mjs b/common/cloudfunc.spec.mjs deleted file mode 100644 index 79ddfedf..00000000 --- a/common/cloudfunc.spec.mjs +++ /dev/null @@ -1,181 +0,0 @@ -import {readFileSync} from 'node:fs'; -import test from 'supertape'; -import montag from 'montag'; -import * as cheerio from 'cheerio'; -import { - _getSize, - getPathLink, - buildFromJSON, - _getDataName, -} from './cloudfunc.mjs'; - -const templatePath = new URL('../tmpl/fs', import.meta.url).pathname; - -const template = { - pathLink: readFileSync(`${templatePath}/pathLink.hbs`, 'utf8'), - path: readFileSync(`${templatePath}/path.hbs`, 'utf8'), - file: readFileSync(`${templatePath}/file.hbs`, 'utf8'), - link: readFileSync(`${templatePath}/link.hbs`, 'utf8'), -}; - -test('cloudfunc: buildFromJSON: ..', (t) => { - const data = { - path: '/media/', - files: [{ - date: '30.08.2016', - mode: 'rwx rwx rwx', - name: 'floppy', - owner: 'root', - size: '7b', - type: 'directory-link', - }], - }; - - const html = buildFromJSON({ - prefix: '', - template, - data, - }); - - const $ = cheerio.load(html); - const el = $('[data-name="js-file-Li4="]'); - - const result = el - .find('[data-name="js-name"]') - .text(); - - const expected = '..'; - - t.equal(result, expected); - t.end(); -}); - -test('cloudfunc: getPathLink: /', (t) => { - const {pathLink} = template; - const result = getPathLink('/', '', pathLink); - const expected = montag` - / - `; - - t.equal(result, expected); - t.end(); -}); - -test('cloudfunc: getPathLink: /hello/world', (t) => { - const {pathLink} = template; - const result = getPathLink('/hello/world', '', pathLink); - const expected = montag` - /hello/ - `; - - t.equal(result, expected); - t.end(); -}); - -test('cloudfunc: getPathLink: prefix', (t) => { - const {pathLink} = template; - const result = getPathLink('/hello/world', '/cloudcmd', pathLink); - const expected = montag` - /hello/ - `; - - t.equal(result, expected); - t.end(); -}); - -test('cloudfunc: getSize: dir', (t) => { - const type = 'directory'; - const size = 0; - const result = _getSize({ - type, - size, - }); - - const expected = '<dir>'; - - t.equal(result, expected); - t.end(); -}); - -test('cloudfunc: getSize: link: dir', (t) => { - const type = 'directory-link'; - const size = 0; - const result = _getSize({ - type, - size, - }); - - const expected = '<link>'; - - t.equal(result, expected); - t.end(); -}); - -test('cloudfunc: getSize: link: file', (t) => { - const type = 'file-link'; - const size = 0; - const result = _getSize({ - type, - size, - }); - - const expected = '<link>'; - - t.equal(result, expected); - t.end(); -}); - -test('cloudfunc: getSize: file', (t) => { - const type = 'file'; - const size = '100.00kb'; - const result = _getSize({ - type, - size, - }); - - const expected = '100.00kb'; - - t.equal(result, expected); - t.end(); -}); - -test('cloudfunc: buildFromJSON: showDotFiles: false', (t) => { - const data = { - path: '/media/', - files: [{ - date: '30.08.2016', - mode: 'rwx rwx rwx', - name: '.floppy', - owner: 'root', - size: '7b', - type: 'directory-link', - }], - }; - - const html = buildFromJSON({ - prefix: '', - template, - data, - showDotFiles: false, - }); - - const $ = cheerio.load(html); - const el = $('[data-name="js-file-LmZsb3BweQ=="]'); - - const result = el - .find('[data-name="js-name"]') - .text(); - - const expected = ''; - - t.equal(result, expected); - t.end(); -}); - -test('cloudfunc: _getDataName', (t) => { - const result = _getDataName('s'); - const expected = 'data-name="js-file-cw==" '; - - t.equal(result, expected); - t.end(); -}); diff --git a/common/datetime.js b/common/datetime.js index 131c27fb..d7982ab4 100644 --- a/common/datetime.js +++ b/common/datetime.js @@ -1,8 +1,6 @@ -'use strict'; +import shortdate from 'shortdate'; -const shortdate = require('shortdate'); - -module.exports = (date) => { +export default (date) => { date = date || new Date(); check(date); diff --git a/common/datetime.spec.js b/common/datetime.spec.js index cc321d8a..e3edb2ef 100644 --- a/common/datetime.spec.js +++ b/common/datetime.spec.js @@ -1,9 +1,6 @@ -'use strict'; - -const test = require('supertape'); -const {tryCatch} = require('try-catch'); - -const datetime = require('./datetime'); +import {test} from 'supertape'; +import {tryCatch} from 'try-catch'; +import datetime from './datetime.js'; test('common: datetime', (t) => { const dateStr = 'Fri, 17 Aug 2018 10:56:48'; diff --git a/common/entity.js b/common/entity.js index d6eeaa23..690bbd7a 100644 --- a/common/entity.js +++ b/common/entity.js @@ -1,15 +1,14 @@ -'use strict'; - const Entities = { - // ' ': ' ', '<': '<', '>': '>', '"': '"', + '{': '{', + '}': '}', }; const keys = Object.keys(Entities); -module.exports.encode = (str) => { +export const encode = (str) => { for (const code of keys) { const char = Entities[code]; const reg = RegExp(char, 'g'); @@ -20,7 +19,7 @@ module.exports.encode = (str) => { return str; }; -module.exports.decode = (str) => { +export const decode = (str) => { for (const code of keys) { const char = Entities[code]; const reg = RegExp(code, 'g'); diff --git a/common/entity.spec.js b/common/entity.spec.js index e83b75df..79313f7c 100644 --- a/common/entity.spec.js +++ b/common/entity.spec.js @@ -1,7 +1,5 @@ -'use strict'; - -const test = require('supertape'); -const entity = require('./entity'); +import {test} from 'supertape'; +import * as entity from '#common/entity'; test('cloudcmd: entity: encode', (t) => { const result = entity.encode(' '); @@ -11,6 +9,14 @@ test('cloudcmd: entity: encode', (t) => { t.end(); }); +test('cloudcmd: entity: {{}}', (t) => { + const result = entity.encode('{{}}'); + const expected = '{{}}'; + + t.equal(result, expected, 'should encode entity'); + t.end(); +}); + test('cloudcmd: entity: decode', (t) => { const result = entity.decode('<hello> '); const expected = ' '; diff --git a/common/omit.js b/common/omit.js new file mode 100644 index 00000000..284c2f5b --- /dev/null +++ b/common/omit.js @@ -0,0 +1,12 @@ +const difference = (a, b) => new Set(a).difference(new Set(b)); +const {keys} = Object; + +export const omit = (a, list) => { + const result = {}; + + for (const key of difference(keys(a), list)) { + result[key] = a[key]; + } + + return result; +}; diff --git a/common/omit.spec.js b/common/omit.spec.js new file mode 100644 index 00000000..ff70541c --- /dev/null +++ b/common/omit.spec.js @@ -0,0 +1,18 @@ +import {test} from 'supertape'; +import {omit} from '#common/omit'; + +test('cloudcmd: common: omit', (t) => { + const a = { + hello: 1, + world: 2, + }; + + const result = omit(a, ['world']); + + const expected = { + hello: 1, + }; + + t.deepEqual(result, expected); + t.end(); +}); diff --git a/common/try-to-promise-all.js b/common/try-to-promise-all.js index e839d27e..1dcfa3a8 100644 --- a/common/try-to-promise-all.js +++ b/common/try-to-promise-all.js @@ -1,9 +1,8 @@ -'use strict'; +import {tryToCatch} from 'try-to-catch'; -const {tryToCatch} = require('try-to-catch'); const all = Promise.all.bind(Promise); -module.exports = async (a) => { +export default async (a) => { const [e, result = []] = await tryToCatch(all, a); return [ diff --git a/common/try-to-promise-all.spec.js b/common/try-to-promise-all.spec.js index 6a7e3b81..eafb036c 100644 --- a/common/try-to-promise-all.spec.js +++ b/common/try-to-promise-all.spec.js @@ -1,7 +1,5 @@ -'use strict'; - -const test = require('supertape'); -const tryToPromiseAll = require('./try-to-promise-all'); +import {test} from 'supertape'; +import tryToPromiseAll from './try-to-promise-all.js'; const resolve = Promise.resolve.bind(Promise); const reject = Promise.reject.bind(Promise); diff --git a/common/util.mjs b/common/util.js similarity index 98% rename from common/util.mjs rename to common/util.js index 761b6345..422b3c22 100644 --- a/common/util.mjs +++ b/common/util.js @@ -45,7 +45,7 @@ export const getExt = (name) => { }; /** - * find object by name in arrray + * find object by name in array * * @param array * @param name diff --git a/common/util.spec.mjs b/common/util.spec.js similarity index 82% rename from common/util.spec.mjs rename to common/util.spec.js index 870e55e8..5a19a8be 100644 --- a/common/util.spec.mjs +++ b/common/util.spec.js @@ -5,6 +5,8 @@ import { getRegExp, escapeRegExp, getExt, + time, + timeEnd, } from '#common/util'; test('getExt: no extension', (t) => { @@ -63,6 +65,7 @@ test('util: findObjByNameInArr: object', (t) => { test('util: findObjByNameInArr: array', (t) => { const name = 'hello'; const data = 'abc'; + const item = { name, data, @@ -105,11 +108,31 @@ test('util: getRegExp: no', (t) => { }); test('util: escapeRegExp: no str', (t) => { - t.equal(escapeRegExp(1), 1); + const result = escapeRegExp(1); + const expected = 1; + + t.equal(result, expected); t.end(); }); test('util: escapeRegExp', (t) => { - t.equal(escapeRegExp('#hello'), '\\#hello'); + const result = escapeRegExp('#hello'); + const expected = '\\#hello'; + + t.equal(result, expected); + t.end(); +}); + +test('util: time', (t) => { + const [error] = tryCatch(time, 'test'); + + t.notOk(error, 'should not throw'); + t.end(); +}); + +test('util: timeEnd', (t) => { + const [error] = tryCatch(timeEnd, 'test'); + + t.notOk(error, 'should not throw'); t.end(); }); diff --git a/css/columns/name-size-date-time.css b/css/columns/name-size-date-time.css new file mode 100644 index 00000000..f46e63f8 --- /dev/null +++ b/css/columns/name-size-date-time.css @@ -0,0 +1,26 @@ +.name { + width: 35%; +} + +.size { + float: none; +} + +.owner { + display: none; +} + +.mode { + display: none; +} + +.date { + float: right; + width: 19%; +} + +.time { + display: inline; + float: right; + width: 20%; +} diff --git a/css/columns/name-size-date.css b/css/columns/name-size-date.css index 45c65c64..9248f700 100644 --- a/css/columns/name-size-date.css +++ b/css/columns/name-size-date.css @@ -18,3 +18,8 @@ float: right; width: 19%; } + +.time { + display: none; +} + diff --git a/css/columns/name-size-time.css b/css/columns/name-size-time.css new file mode 100644 index 00000000..1bbd59de --- /dev/null +++ b/css/columns/name-size-time.css @@ -0,0 +1,24 @@ +.name { + width: 55%; +} + +.size { + float: none; +} + +.owner { + display: none; +} + +.mode { + display: none; +} + +.date { + display: none; +} + +.time { + float: right; + width: 19%; +} diff --git a/css/columns/name-size.css b/css/columns/name-size.css index e58d1844..c02472a5 100644 --- a/css/columns/name-size.css +++ b/css/columns/name-size.css @@ -18,3 +18,8 @@ .date { display: none; } + +.time { + display: none; +} + diff --git a/css/query.css b/css/query.css index b5843b85..509f1b45 100644 --- a/css/query.css +++ b/css/query.css @@ -161,8 +161,9 @@ .size, .date, .owner, + .time, .mode { - display: none; + display: none !important; } .name { diff --git a/css/style.css b/css/style.css index cd7122f7..10820444 100644 --- a/css/style.css +++ b/css/style.css @@ -228,9 +228,15 @@ a:active { color: var(--column-color); } +.time { + color: var(--column-color); + display: none; +} + .owner { display: inline-block; - width: 13%; + width: 12%; + /* when inline-block * vertical align should be * set top to prevent additional @@ -242,7 +248,7 @@ a:active { .mode { float: right; - width: 18%; + width: 22%; color: var(--column-color); } diff --git a/cssnano.config.mjs b/cssnano.config.mjs deleted file mode 100644 index 44abaeaf..00000000 --- a/cssnano.config.mjs +++ /dev/null @@ -1,12 +0,0 @@ -// used by OptimizeCssAssetsPlugin -import defaultPreset from 'cssnano-preset-default'; - -export default defaultPreset({ - svgo: { - plugins: [{ - convertPathData: false, - }, { - convertShapeToPath: false, - }], - }, -}); diff --git a/docker/Dockerfile b/docker/Dockerfile index 39312e00..7c5d7b64 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,18 +1,22 @@ -FROM node:lts-bookworm +FROM node + LABEL maintainer="Coderaiser" LABEL org.opencontainers.image.source="https://github.com/coderaiser/cloudcmd" -RUN mkdir -p /usr/src/app -WORKDIR /usr/src/app +RUN mkdir -p /usr/src/cloudcmd -COPY package.json /usr/src/app/ +WORKDIR /usr/src/cloudcmd + +COPY package.json /usr/src/cloudcmd/ RUN curl -fsSL https://bun.com/install | bash && \ - ~/.bun/bin/bun i --production --no-save && \ - PATH=$PATH:~/.bun/bin bun i gritty --no-save && \ - ~/.bun/bin/bun pm cache rm + ln -s /root/.bun/bin/bun /usr/local/bin/bun && \ + chmod +x /usr/local/bin/bun && \ + bun r gritty --omit dev && \ + bun i gritty --omit dev && \ + bun pm cache rm -COPY . /usr/src/app +COPY . /usr/src/cloudcmd WORKDIR / @@ -22,4 +26,4 @@ ENV cloudcmd_open=false EXPOSE 8000 -ENTRYPOINT ["/usr/src/app/bin/cloudcmd.mjs"] +ENTRYPOINT ["/usr/src/cloudcmd/bin/cloudcmd.js"] diff --git a/docker/Dockerfile.alpine b/docker/Dockerfile.alpine index b8cc6e3e..5e38ab8a 100644 --- a/docker/Dockerfile.alpine +++ b/docker/Dockerfile.alpine @@ -1,30 +1,34 @@ FROM node:alpine + LABEL maintainer="Coderaiser" LABEL org.opencontainers.image.source="https://github.com/coderaiser/cloudcmd" -RUN mkdir -p /usr/src/app -WORKDIR /usr/src/app +RUN mkdir -p /usr/src/cloudcmd -COPY package.json /usr/src/app/ +WORKDIR /usr/src/cloudcmd + +COPY package.json /usr/src/cloudcmd/ RUN apk update && \ apk add --no-cache curl bash make g++ python3 && \ - curl -fsSL https://bun.com/install | bash && \ - ~/.bun/bin/bun i --production --no-save && \ - PATH=$PATH:~/.bun/bin bun i gritty --no-save && \ - ~/.bun/bin/bun pm cache rm && \ + curl -fsSL https://bun.sh/install | bash && \ + ln -s ~/.bun/bin/bun /usr/local/bin/bun && \ + chmod +x /usr/local/bin/bun && \ + bun r gritty --omit dev && \ + bun i gritty --omit dev && \ + bun pm cache rm && \ apk del make g++ python3 && \ rm -rf /usr/include /tmp/* /var/cache/apk/* -COPY . /usr/src/app +COPY . /usr/src/cloudcmd WORKDIR / -ENV cloudcmd_terminal true -ENV cloudcmd_terminal_path gritty -ENV cloudcmd_open false +ENV cloudcmd_terminal=true +ENV cloudcmd_terminal_path=gritty +ENV cloudcmd_open=false +ENV cloudcmd_vim=true EXPOSE 8000 -ENTRYPOINT ["/usr/src/app/bin/cloudcmd.mjs"] - +ENTRYPOINT ["/usr/src/cloudcmd/bin/cloudcmd.js"] diff --git a/docker/Dockerfile.io b/docker/Dockerfile.io new file mode 100644 index 00000000..d5a24459 --- /dev/null +++ b/docker/Dockerfile.io @@ -0,0 +1,113 @@ +FROM ubuntu:resolute + +LABEL maintainer="Coderaiser" +LABEL org.opencontainers.image.source="https://github.com/coderaiser/cloudcmd" + +RUN mkdir -p /usr/local/share/cloudcmd + +WORKDIR /usr/local/share/cloudcmd + +COPY package.json /usr/local/share/cloudcmd/ + +ENV DEBIAN_FRONTEND=noninteractive \ + NVM_DIR=/usr/local/share/nvm \ + npm_config_cache=/tmp/npm-cache \ + GOPATH=/usr/local/share/go \ + PATH=/usr/local/share/bun/bin:$PATH \ + BUN_INSTALL=/usr/local/share/bun \ + NPM_CONFIG_CACHE=/tmp/.npm \ + NPM_CONFIG_PREFIX=/usr/local \ + NPM_CONFIG_PACKAGE_LOCK=false \ + PALABRA_DIR=/usr/local/share \ + XDG_CONFIG_HOME=/usr/local/etc + +ARG UBUNTU_DEPS="libatomic1 curl wget git net-tools iproute2 software-properties-common" +ARG RUST_DEPS="build-essential" +ARG DEPS="pv gcc gdb strace upx-ucl less ffmpeg net-tools netcat-openbsd mc far2l iputils-ping vim bat fzf locales sudo command-not-found ncdu aptitude htop btop hexyl tmux" +ARG PALABRA_DEPS="nvm node rust go deno fasm nvchad rizin yara gdu f4 typos shellcheck gh" +ARG BUN_DEPS="palabra wisdom nupdate version-io redrun superc8 supertape madrun redlint putout renamify-cli runny redfork cline" + +RUN apt-get update && \ + apt-get upgrade -y && \ + apt-get autoremove && \ + apt-get install -y ${UBUNTU_DEPS} ${RUST_DEPES} ${DEPS} && \ + echo "> Install git" && \ + add-apt-repository ppa:git-core/ppa -y && \ + echo "> Update command-not-found database. Run 'sudo apt update' to populate it." && \ + apt-get update && \ + apt-get upgrade -y && \ + apt-get autoremove && \ + apt-get clean && \ + echo "> create user" && \ + useradd -m -s /bin/bash -u 1337 instalador && \ + chown -R instalador /usr/local && \ + chown -R instalador /tmp + +USER instalador + +RUN echo "> install bun" && \ + curl https://bun.sh/install | bash && \ + echo "> install npm globals" && \ + bun i ${BUN_DEPS} -g && \ + echo "> install rust go deno bun fasm nvim" && \ + bun ${BUN_INSTALL}/bin/palabra i ${PALABRA_DEPS} && \ + echo "> install node" && \ + . $NVM_DIR/nvm.sh + +USER root + +RUN echo "> remove user" && \ + userdel -r instalador && \ + echo "> install gritty" && \ + bun r gritty --omit dev && \ + bun i gritty --omit dev && \ + bun pm cache rm && \ + echo "> setup cloudcmd" && \ + ln -s /usr/local/share/cloudcmd/bin/cloudcmd.js /usr/local/bin/cloudcmd && \ + echo "> setup git" && \ + git config --global core.whitespace -trailing-space && \ + git config --global pull.rebase true && \ + git config --global init.defaultBranch master && \ + echo "> configure bash" && \ + echo "alias ls='ls --color=auto'" >> /etc/bash.bashrc && \ + echo "alias buni='bun i --no-save'" >> /etc/bash.bashrc && \ + echo "alias bat='batcat'" >> /etc/bash.bashrc && \ + echo ". /usr/local/share/nvm/nvm.sh" >> /etc/bash.bashrc && \ + echo ". /usr/share/bash-completion/completions/git" >> /etc/bash.bashrc && \ + echo 'PS1="\[\033[01;32m\]\u@\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]\$ "' >> /etc/bash.bashrc && \ + echo "> setup inputrc" && \ + echo "set editing-mode vi" >> /etc/inputrc && \ + echo "TAB: menu-complete" >> /etc/inputrc && \ + echo "set UTF-8" && \ + echo " > configure languages" && \ + echo "en_US.UTF-8 UTF-8" > /etc/locale.gen && \ + echo "ru_RU.UTF-8 UTF-8" >> /etc/locale.gen && \ + echo "uk_UA.UTF-8 UTF-8" >> /etc/locale.gen && \ + echo "es_ES.UTF-8 UTF-8" >> /etc/locale.gen && \ + echo "ja_JP.UTF-8 UTF-8" >> /etc/locale.gen && \ + echo "el_GR.UTF-8 UTF-8" >> /etc/locale.gen && \ + locale-gen + +COPY . /usr/local/share/cloudcmd + +WORKDIR / + +ENV cloudcmd_terminal=true \ + cloudcmd_terminal_path=gritty \ + cloudcmd_vim=true \ + cloudcmd_open=false \ + PATH=node_modules/.bin:$PATH \ + PATH=~/.local/bin:$PATH \ + BUN_INSTALL_CACHE_DIR=/tmp/bun-cache \ + DENO_DIR=/tmp/deno-cache \ + LANG=en_US.UTF-8 \ + LANGUAGE=en_US:en \ + LC_ALL=en_US.UTF-8 \ + TERM=xterm-256color \ + XDG_CACHE_HOME=/tmp \ + XDG_DATA_HOME=/usr/local/share \ + XDG_CONFIG_HOME=~/.config + +EXPOSE 8000 + +ENTRYPOINT ["/usr/local/share/cloudcmd/bin/cloudcmd.js"] diff --git a/docker/Dockerfile.slim b/docker/Dockerfile.slim new file mode 100644 index 00000000..c6f273b8 --- /dev/null +++ b/docker/Dockerfile.slim @@ -0,0 +1,39 @@ +FROM node:slim AS build + +RUN mkdir -p /usr/src/cloudcmd/ + +WORKDIR /usr/src/cloudcmd + +COPY package.json /usr/src/cloudcmd/ + +RUN apt-get update && \ + apt-get install -y build-essential python3 libncurses5-dev pkg-config && \ + apt-get install -y --no-install-recommends curl ca-certificates unzip && \ + curl -fsSL https://bun.sh/install | bash && \ + ln -s ~/.bun/bin/bun /usr/local/bin/bun && \ + chmod +x /usr/local/bin/bun && \ + bun r gritty --omit dev && \ + bun i gritty --omit dev && \ + ~/.bun/bin/bun pm cache rm && \ + rm -rf /var/lib/apt/lists/* + +COPY . /usr/src/cloudcmd + +FROM node:slim AS runtime + +COPY --from=build /usr/src/cloudcmd /usr/src/cloudcmd +COPY --from=build /root/.bun /root/.bun + +LABEL maintainer="Coderaiser" +LABEL org.opencontainers.image.source="https://github.com/coderaiser/cloudcmd" + +WORKDIR / + +ENV cloudcmd_terminal=true +ENV cloudcmd_terminal_path=gritty +ENV cloudcmd_open=false +ENV PATH="/root/.bun/bin:$PATH" + +EXPOSE 8000 + +ENTRYPOINT ["/usr/src/cloudcmd/bin/cloudcmd.js"] diff --git a/eslint.config.mjs b/eslint.config.js similarity index 70% rename from eslint.config.mjs rename to eslint.config.js index dc751dca..22add654 100644 --- a/eslint.config.mjs +++ b/eslint.config.js @@ -1,34 +1,25 @@ import {safeAlign} from 'eslint-plugin-putout'; import {defineConfig} from 'eslint/config'; -import n from 'eslint-plugin-n'; import globals from 'globals'; import {matchToFlat} from '@putout/eslint-flat'; export const match = { - 'bin/release.mjs': { + 'bin/release.js': { 'no-console': 'off', 'n/hashbang': 'off', }, 'client/dom/index.*': { 'no-multi-spaces': 'off', }, - '{client,static}/**/*.{js,mjs}': { - 'n/no-extraneous-require': 'off', + 'client/**': { 'n/no-unsupported-features/node-builtins': 'off', }, - 'bin/cloudcmd.js': { - 'no-console': 'off', - }, }; export default defineConfig([ safeAlign, { ignores: ['**/fixture'], rules: { 'key-spacing': 'off', - 'n/prefer-node-protocol': 'error', - }, - plugins: { - n, }, }, { files: ['{client,common,static}/**/*.js'], diff --git a/html/index.html b/html/index.html index 0cfc73c5..d366ed1c 100644 --- a/html/index.html +++ b/html/index.html @@ -9,7 +9,6 @@ - @@ -45,7 +44,7 @@