mirror of
https://github.com/coderaiser/cloudcmd.git
synced 2026-07-22 23:50:33 +00:00
Compare commits
No commits in common. "master" and "v6.14.0" have entirely different histories.
405 changed files with 33703 additions and 24371 deletions
9
.babelrc
Normal file
9
.babelrc
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
"presets": [
|
||||||
|
"es2015"
|
||||||
|
],
|
||||||
|
"plugins": [
|
||||||
|
"transform-object-assign"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
{
|
|
||||||
"presets": [
|
|
||||||
"@babel/preset-env"
|
|
||||||
],
|
|
||||||
"plugins": [
|
|
||||||
"module:babel-plugin-macros",
|
|
||||||
"@babel/plugin-transform-optional-chaining"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
3
.bowerrc
Normal file
3
.bowerrc
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"directory" : "modules"
|
||||||
|
}
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
last 2 Chrome versions
|
|
||||||
last 2 Safari versions
|
|
||||||
Firefox ESR
|
|
||||||
not dead
|
|
||||||
|
|
@ -1,31 +0,0 @@
|
||||||
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 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,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
@ -1,23 +1,24 @@
|
||||||
.*
|
|
||||||
*.spec.js
|
|
||||||
node_modules
|
node_modules
|
||||||
npm-debug.log*
|
npm-debug.log*
|
||||||
coverage
|
coverage
|
||||||
test
|
|
||||||
manifest.yml
|
|
||||||
app.json
|
|
||||||
bower.json
|
|
||||||
yarn-error.log
|
|
||||||
yarn.lock
|
|
||||||
now.json
|
|
||||||
|
|
||||||
docker
|
modules/jquery/dist
|
||||||
|
modules/jquery/external
|
||||||
|
modules/jquery/src
|
||||||
|
|
||||||
webpack.config.js
|
!modules/jquery/dist/jquery.min.js
|
||||||
cssnano.config.js
|
|
||||||
|
|
||||||
bin/release.js
|
modules/fancybox/lib/
|
||||||
|
modules/fancybox/demo
|
||||||
|
modules/fancybox/sprite.psd
|
||||||
|
|
||||||
|
modules/execon
|
||||||
|
|
||||||
client
|
client
|
||||||
|
legacy
|
||||||
server_
|
server_
|
||||||
|
|
||||||
|
.nyc_output
|
||||||
|
|
||||||
|
*.swp
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ root = true
|
||||||
charset = utf-8
|
charset = utf-8
|
||||||
end_of_line = lf
|
end_of_line = lf
|
||||||
insert_final_newline = true
|
insert_final_newline = true
|
||||||
trim_trailing_whitespace = false
|
trim_trailing_whitespace = true
|
||||||
indent_style = space
|
indent_style = space
|
||||||
indent_size = 4
|
indent_size = 4
|
||||||
|
|
||||||
|
|
|
||||||
17
.eslintrc
Normal file
17
.eslintrc
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
{
|
||||||
|
"env": {
|
||||||
|
"es6": true,
|
||||||
|
"node": true,
|
||||||
|
"browser": true
|
||||||
|
},
|
||||||
|
"parserOptions": {
|
||||||
|
"ecmaVersion": 6,
|
||||||
|
"sourceType": "module"
|
||||||
|
},
|
||||||
|
"rules": {
|
||||||
|
"indent": ["error", 4],
|
||||||
|
"semi": "error",
|
||||||
|
"no-use-before-define": ["error", "nofunc"]
|
||||||
|
},
|
||||||
|
"extends": ["eslint:recommended"]
|
||||||
|
}
|
||||||
3
.github/FUNDING.yml
vendored
3
.github/FUNDING.yml
vendored
|
|
@ -1,3 +0,0 @@
|
||||||
github: coderaiser
|
|
||||||
open_collective: cloudcmd
|
|
||||||
ko_fi: coderaiser
|
|
||||||
12
.github/ISSUE_TEMPLATE.md
vendored
Normal file
12
.github/ISSUE_TEMPLATE.md
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
<!--
|
||||||
|
Thank you for reporting an issue. Please fill in the template below. If unsure
|
||||||
|
about something, just do as best as you're able.
|
||||||
|
-->
|
||||||
|
|
||||||
|
* **Version** (`cloudcmd -v`):
|
||||||
|
* **Node Version** `node -v`:
|
||||||
|
* **OS** (`uname -a` on Linux):
|
||||||
|
* **Browser name/version**:
|
||||||
|
* **Used Command Line Parameters**:
|
||||||
|
* **Changed Config**:
|
||||||
|
|
||||||
45
.github/ISSUE_TEMPLATE/bug_report.md
vendored
45
.github/ISSUE_TEMPLATE/bug_report.md
vendored
|
|
@ -1,45 +0,0 @@
|
||||||
---
|
|
||||||
|
|
||||||
name: Bug report
|
|
||||||
about: Create a report to help us improve
|
|
||||||
title: ''
|
|
||||||
labels: needs clarification
|
|
||||||
assignees: coderaiser
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Describe the bug**
|
|
||||||
A clear and concise description of what the bug is.
|
|
||||||
|
|
||||||
**To Reproduce**
|
|
||||||
Steps to reproduce the behavior:
|
|
||||||
|
|
||||||
1. Go to '...'
|
|
||||||
2. Click on '....'
|
|
||||||
3. Scroll down to '....'
|
|
||||||
4. See error
|
|
||||||
|
|
||||||
**Expected behavior**
|
|
||||||
A clear and concise description of what you expected to happen.
|
|
||||||
|
|
||||||
**Screenshots**
|
|
||||||
If applicable, add screenshots to help explain your problem.
|
|
||||||
|
|
||||||
**Desktop (please complete the following information):**
|
|
||||||
|
|
||||||
- **Version** (`cloudcmd -v`):
|
|
||||||
- **Node Version** `node -v`:
|
|
||||||
- **OS** (`uname -a` on Linux):
|
|
||||||
- **Browser name/version**:
|
|
||||||
- **Used Command Line Parameters**:
|
|
||||||
- **Changed Config**:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{}
|
|
||||||
```
|
|
||||||
- [ ] 🎁 **I'm ready to donate on https://opencollective.com/cloudcmd**
|
|
||||||
- [ ] 🎁 **I'm ready to donate on https://ko-fi.com/coderaiser**
|
|
||||||
- [ ] 💪 **I'm willing to work on this issue**
|
|
||||||
|
|
||||||
**Additional context**
|
|
||||||
Add any other context about the problem here.
|
|
||||||
5
.github/ISSUE_TEMPLATE/config.yml
vendored
5
.github/ISSUE_TEMPLATE/config.yml
vendored
|
|
@ -1,5 +0,0 @@
|
||||||
blank_issues_enabled: false
|
|
||||||
contact_links:
|
|
||||||
- name: Stack Overflow
|
|
||||||
url: https://stackoverflow.com/search?q=cloudcmd
|
|
||||||
about: Please ask and answer questions here.
|
|
||||||
21
.github/ISSUE_TEMPLATE/feature_request.md
vendored
21
.github/ISSUE_TEMPLATE/feature_request.md
vendored
|
|
@ -1,21 +0,0 @@
|
||||||
---
|
|
||||||
|
|
||||||
name: Feature request
|
|
||||||
about: Suggest an idea for this project
|
|
||||||
title: ''
|
|
||||||
labels: ''
|
|
||||||
assignees: ''
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Is your feature request related to a problem? Please describe.**
|
|
||||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
|
||||||
|
|
||||||
**Describe the solution you'd like**
|
|
||||||
A clear and concise description of what you want to happen.
|
|
||||||
|
|
||||||
**Describe alternatives you've considered**
|
|
||||||
A clear and concise description of any alternative solutions or features you've considered.
|
|
||||||
|
|
||||||
**Additional context**
|
|
||||||
Add any other context or screenshots about the feature request here.
|
|
||||||
24
.github/ISSUE_TEMPLATE/issue_template.md
vendored
24
.github/ISSUE_TEMPLATE/issue_template.md
vendored
|
|
@ -1,24 +0,0 @@
|
||||||
***
|
|
||||||
|
|
||||||
name: Tracking issue
|
|
||||||
about: Create an issue with bug report or feature request.
|
|
||||||
title: ""
|
|
||||||
labels: needs triage
|
|
||||||
assignees: coderaiser
|
|
||||||
|
|
||||||
***
|
|
||||||
|
|
||||||
- **Version** (`cloudcmd -v`):
|
|
||||||
- **Node Version** `node -v`:
|
|
||||||
- **OS** (`uname -a` on Linux):
|
|
||||||
- **Browser name/version**:
|
|
||||||
- **Used Command Line Parameters**:
|
|
||||||
- **Changed Config**:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{}
|
|
||||||
```
|
|
||||||
|
|
||||||
- [ ] 🎁 **I'm ready to donate on https://opencollective.com/cloudcmd**
|
|
||||||
- [ ] 🎁 **I'm ready to donate on https://ko-fi.com/coderaiser**
|
|
||||||
- [ ] 💪 **I'm willing to work on this issue**
|
|
||||||
3
.github/PULL_REQUEST_TEMPLATE.md
vendored
3
.github/PULL_REQUEST_TEMPLATE.md
vendored
|
|
@ -4,5 +4,6 @@ about something, just do as best as you're able.
|
||||||
-->
|
-->
|
||||||
|
|
||||||
- [ ] commit message named according to [Contributing Guide](https://github.com/coderaiser/cloudcmd/blob/master/CONTRIBUTING.md "Contributting Guide")
|
- [ ] commit message named according to [Contributing Guide](https://github.com/coderaiser/cloudcmd/blob/master/CONTRIBUTING.md "Contributting Guide")
|
||||||
- [ ] `npm run fix:lint` is OK
|
- [ ] `npm run codestyle` is OK
|
||||||
- [ ] `npm test` is OK
|
- [ ] `npm test` is OK
|
||||||
|
|
||||||
|
|
|
||||||
60
.github/workflows/docker-io.yml
vendored
60
.github/workflows/docker-io.yml
vendored
|
|
@ -1,60 +0,0 @@
|
||||||
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
|
|
||||||
88
.github/workflows/docker.yml
vendored
88
.github/workflows/docker.yml
vendored
|
|
@ -1,88 +0,0 @@
|
||||||
name: Docker CI
|
|
||||||
permissions:
|
|
||||||
contents: write
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
tags:
|
|
||||||
- "*"
|
|
||||||
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 22.x
|
|
||||||
uses: actions/setup-node@v6
|
|
||||||
with:
|
|
||||||
node-version: 22.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 base-image
|
|
||||||
uses: docker/build-push-action@v7
|
|
||||||
with:
|
|
||||||
context: .
|
|
||||||
file: docker/Dockerfile
|
|
||||||
platforms: linux/amd64,linux/arm64
|
|
||||||
push: true
|
|
||||||
tags: |
|
|
||||||
coderaiser/cloudcmd:latest
|
|
||||||
coderaiser/cloudcmd:${{ steps.build.outputs.version }}
|
|
||||||
ghcr.io/${{ github.repository }}:latest
|
|
||||||
ghcr.io/${{ github.repository }}:${{ steps.build.outputs.version }}
|
|
||||||
- name: Build and push alpine-image
|
|
||||||
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
|
|
||||||
53
.github/workflows/nodejs.yml
vendored
53
.github/workflows/nodejs.yml
vendored
|
|
@ -1,53 +0,0 @@
|
||||||
name: Node CI
|
|
||||||
on:
|
|
||||||
- push
|
|
||||||
permissions:
|
|
||||||
contents: write
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
env:
|
|
||||||
NAME: cloudcmd
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
node-version:
|
|
||||||
- 22.x
|
|
||||||
- 24.x
|
|
||||||
- 26.x
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v5
|
|
||||||
- uses: oven-sh/setup-bun@v2
|
|
||||||
with:
|
|
||||||
bun-version: latest
|
|
||||||
- name: Use Node.js ${{ matrix.node-version }}
|
|
||||||
uses: actions/setup-node@v6
|
|
||||||
with:
|
|
||||||
node-version: ${{ matrix.node-version }}
|
|
||||||
- name: Install Redrun
|
|
||||||
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
|
|
||||||
- name: Typos
|
|
||||||
uses: coderaiser/typos.ai@v1.1.8
|
|
||||||
with:
|
|
||||||
key: ${{ secrets.TYPOS_AI_KEY }}
|
|
||||||
- name: Commit fixes
|
|
||||||
uses: EndBug/add-and-commit@v10
|
|
||||||
continue-on-error: true
|
|
||||||
with:
|
|
||||||
message: "chore: ${{ env.NAME }}: actions: lint ☘️"
|
|
||||||
- name: Build
|
|
||||||
run: redrun build
|
|
||||||
- name: Test
|
|
||||||
run: redrun test
|
|
||||||
- name: Coverage
|
|
||||||
run: redrun coverage coverage:report
|
|
||||||
- name: Coveralls
|
|
||||||
uses: coverallsapp/github-action@v2
|
|
||||||
continue-on-error: true
|
|
||||||
with:
|
|
||||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
35
.gitignore
vendored
35
.gitignore
vendored
|
|
@ -1,17 +1,30 @@
|
||||||
*.swp
|
|
||||||
*.log
|
|
||||||
*.lock
|
|
||||||
|
|
||||||
.nyc_output
|
|
||||||
.DS_Store
|
|
||||||
.idea
|
|
||||||
|
|
||||||
package-lock.json
|
|
||||||
npm-debug.log*
|
|
||||||
|
|
||||||
node_modules
|
node_modules
|
||||||
|
npm-debug.log*
|
||||||
coverage
|
coverage
|
||||||
|
|
||||||
|
modules/jquery/dist
|
||||||
|
modules/jquery/external
|
||||||
|
modules/jquery/src
|
||||||
|
|
||||||
|
!modules/jquery/dist/jquery.min.js
|
||||||
|
|
||||||
|
modules/fancybox/lib/
|
||||||
|
modules/fancybox/demo
|
||||||
|
modules/fancybox/sprite.psd
|
||||||
|
modules/fancybox/*.json
|
||||||
|
modules/fancybox/gulpfile.js
|
||||||
|
modules/jquery-mousewheel
|
||||||
|
|
||||||
modules/execon
|
modules/execon
|
||||||
modules/emitify
|
modules/emitify
|
||||||
|
|
||||||
|
legacy
|
||||||
|
server_
|
||||||
|
|
||||||
|
.nyc_output
|
||||||
|
|
||||||
|
*.swp
|
||||||
|
|
||||||
dist
|
dist
|
||||||
dist-dev
|
dist-dev
|
||||||
|
|
||||||
|
|
|
||||||
20
.jscsrc
Normal file
20
.jscsrc
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
{
|
||||||
|
"preset": "crockford",
|
||||||
|
"requireSpaceAfterKeywords": false,
|
||||||
|
"requireCapitalizedConstructors": false,
|
||||||
|
"requireMultipleVarDecl": false,
|
||||||
|
"disallowTrailingWhitespace": "ignoreEmptyLines",
|
||||||
|
"disallowTrailingComma": false,
|
||||||
|
"disallowDanglingUnderscores": false,
|
||||||
|
"validateIndentation": false,
|
||||||
|
"requireCurlyBraces": false,
|
||||||
|
"requireVarDeclFirst": false,
|
||||||
|
"requireSpacesInAnonymousFunctionExpression": {
|
||||||
|
"beforeOpeningCurlyBrace": true
|
||||||
|
},
|
||||||
|
"excludeFiles": [
|
||||||
|
"node_modules/**",
|
||||||
|
"modules/**",
|
||||||
|
"bower_components/**"
|
||||||
|
]
|
||||||
|
}
|
||||||
24
.jshintrc
Normal file
24
.jshintrc
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
{
|
||||||
|
"esversion": 6,
|
||||||
|
"unused" : true,
|
||||||
|
"bitwise" : false,
|
||||||
|
"browser" : true,
|
||||||
|
"devel" : true,
|
||||||
|
"eqeqeq" : true,
|
||||||
|
"jquery" : false,
|
||||||
|
"newcap" : false,
|
||||||
|
"noarg" : true,
|
||||||
|
"node" : true,
|
||||||
|
"noempty" : true,
|
||||||
|
"nonew" : true,
|
||||||
|
"strict" : true,
|
||||||
|
"undef" : true,
|
||||||
|
"evil" : true,
|
||||||
|
"expr" : true,
|
||||||
|
"quotmark": "single",
|
||||||
|
"validthis": true,
|
||||||
|
"-W024": true,
|
||||||
|
"-W120": true,
|
||||||
|
"-W079": true,
|
||||||
|
"-W138": true
|
||||||
|
}
|
||||||
62
.madrun.js
62
.madrun.js
|
|
@ -1,62 +0,0 @@
|
||||||
import {run, cutEnv} from 'madrun';
|
|
||||||
import {defineEnv} from 'supertape/env';
|
|
||||||
|
|
||||||
const testEnv = defineEnv({
|
|
||||||
timeout: 7000,
|
|
||||||
css: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
const buildEnv = {
|
|
||||||
NODE_ENV: 'production',
|
|
||||||
};
|
|
||||||
|
|
||||||
export default {
|
|
||||||
'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',
|
|
||||||
]),
|
|
||||||
'lint:all': () => run('lint:progress'),
|
|
||||||
'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' '{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.js',
|
|
||||||
'coverage': async () => [testEnv, `c8 ${await cutEnv('test')}`],
|
|
||||||
'coverage:report': () => 'c8 report --reporter=lcov',
|
|
||||||
'report': () => 'c8 report --reporter=lcov',
|
|
||||||
'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',
|
|
||||||
}),
|
|
||||||
'pre6to5:client': () => 'rimraf dist',
|
|
||||||
'pre6to5:client:dev': () => 'rimraf dist-dev',
|
|
||||||
'watch:client': () => run('6to5:client', '--watch'),
|
|
||||||
'watch:client:dev': () => run('6to5:client:dev', '--watch'),
|
|
||||||
'watch:server': () => 'nodemon bin/cloudcmd.js',
|
|
||||||
'watch:test': async () => [testEnv, `nodemon -w client -w server -w test -w common -x ${await cutEnv('test')}`],
|
|
||||||
'watch:test:client': async () => `nodemon -w client -w test/client -x ${await run('test:client')}`,
|
|
||||||
'watch:test:server': async () => `nodemon -w client -w test/client -x ${await run('test:server')}`,
|
|
||||||
'watch:coverage': async () => [testEnv, `nodemon -w server -w test -w common -x ${await cutEnv('coverage')}`],
|
|
||||||
'watch:fix:lint': async () => `nodemon -w client -w server -w test -w common -x '${await run('fix:lint')}'`,
|
|
||||||
'build': async () => run('6to5:*'),
|
|
||||||
'build:dev': async () => run('build:client:dev'),
|
|
||||||
'build:client': () => run('6to5:client'),
|
|
||||||
'build:client:dev': () => run('6to5:client:dev'),
|
|
||||||
'heroku-postbuild': () => run('6to5:client'),
|
|
||||||
};
|
|
||||||
51
.npmignore
51
.npmignore
|
|
@ -1,29 +1,34 @@
|
||||||
*.spec.*
|
|
||||||
*.config.*
|
|
||||||
*.fixture.js*
|
|
||||||
*.ai
|
|
||||||
*.cdr
|
|
||||||
*.eps
|
|
||||||
*.log
|
|
||||||
*.lock
|
|
||||||
|
|
||||||
.*
|
.*
|
||||||
|
|
||||||
manifest.yml
|
manifest.yml
|
||||||
|
Dockerfile*
|
||||||
docker-compose.yml
|
docker-compose.yml
|
||||||
now.json
|
shell
|
||||||
|
test
|
||||||
|
coverage
|
||||||
|
|
||||||
|
modules/jquery/dist
|
||||||
|
modules/jquery/external
|
||||||
|
modules/jquery/src
|
||||||
|
|
||||||
|
!modules/jquery/dist/jquery.min.js
|
||||||
|
|
||||||
|
modules/fancybox/lib/
|
||||||
|
modules/fancybox/demo
|
||||||
|
modules/fancybox/sprite.psd
|
||||||
|
|
||||||
|
modules/execon
|
||||||
|
modules/emitify
|
||||||
|
|
||||||
app.json
|
app.json
|
||||||
bower.json
|
circle.yml
|
||||||
deno.json
|
|
||||||
bin/release.*
|
bin/release.js
|
||||||
img/logo/cloudcmd-hq.png
|
bin/legacy.js
|
||||||
|
|
||||||
|
client
|
||||||
|
|
||||||
|
legacy/bin/release.js
|
||||||
|
legacy/bin/legacy.js
|
||||||
|
|
||||||
webpack.config.js
|
webpack.config.js
|
||||||
|
|
||||||
docker
|
|
||||||
test*
|
|
||||||
fixture
|
|
||||||
fixture-*
|
|
||||||
coverage
|
|
||||||
css
|
|
||||||
html
|
|
||||||
client
|
|
||||||
|
|
|
||||||
15
.nycrc.json
15
.nycrc.json
|
|
@ -1,15 +0,0 @@
|
||||||
{
|
|
||||||
"checkCoverage": false,
|
|
||||||
"all": false,
|
|
||||||
"exclude": [
|
|
||||||
"**/*.spec.js",
|
|
||||||
"**/*.*.js",
|
|
||||||
"**/*.config.*",
|
|
||||||
"**/fixture",
|
|
||||||
"**/test/**"
|
|
||||||
],
|
|
||||||
"branches": 100,
|
|
||||||
"lines": 100,
|
|
||||||
"functions": 100,
|
|
||||||
"statements": 100
|
|
||||||
}
|
|
||||||
60
.putout.json
60
.putout.json
|
|
@ -1,60 +0,0 @@
|
||||||
{
|
|
||||||
"plugins": ["cloudcmd"],
|
|
||||||
"ignore": [
|
|
||||||
"*.md",
|
|
||||||
"app.json",
|
|
||||||
"fontello.json",
|
|
||||||
"html",
|
|
||||||
"fixture*"
|
|
||||||
],
|
|
||||||
"rules": {
|
|
||||||
"package-json/add-type": "off"
|
|
||||||
},
|
|
||||||
"match": {
|
|
||||||
".filesystem.json": {
|
|
||||||
"nodejs/rename-file-cjs-to-js": "off"
|
|
||||||
},
|
|
||||||
"base64": {
|
|
||||||
"types/convert-typeof-to-is-type": "off"
|
|
||||||
},
|
|
||||||
"*.md": {
|
|
||||||
"nodejs/convert-commonjs-to-esm": "on"
|
|
||||||
},
|
|
||||||
".webpack": {
|
|
||||||
"webpack": "on"
|
|
||||||
},
|
|
||||||
"server": {
|
|
||||||
"nodejs/remove-process-exit": "on"
|
|
||||||
},
|
|
||||||
"server/{server,exit}.js": {
|
|
||||||
"nodejs/remove-process-exit": "off"
|
|
||||||
},
|
|
||||||
"server/{server,exit,terminal,distribute/log}.{js,mjs}": {
|
|
||||||
"remove-console": "off"
|
|
||||||
},
|
|
||||||
"client/{client,cloudcmd,load-module}.{js,mjs}": {
|
|
||||||
"remove-console": "off"
|
|
||||||
},
|
|
||||||
"client": {
|
|
||||||
"nodejs": "off"
|
|
||||||
},
|
|
||||||
"client/sw": {
|
|
||||||
"remove-console": "off"
|
|
||||||
},
|
|
||||||
"test/common/cloudfunc.js": {
|
|
||||||
"remove-console": "off"
|
|
||||||
},
|
|
||||||
"storage.js": {
|
|
||||||
"promises/remove-useless-async": "off"
|
|
||||||
},
|
|
||||||
"docker.yml": {
|
|
||||||
"github/set-node-versions": "off"
|
|
||||||
},
|
|
||||||
"vim.js": {
|
|
||||||
"merge-duplicate-functions": "off"
|
|
||||||
},
|
|
||||||
"common": {
|
|
||||||
"nodejs/declare": "off"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,38 +0,0 @@
|
||||||
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(),
|
|
||||||
],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
@ -1,36 +0,0 @@
|
||||||
import {env} from 'node:process';
|
|
||||||
import HtmlWebpackPlugin from 'html-webpack-plugin';
|
|
||||||
|
|
||||||
const isDev = env.NODE_ENV === 'development';
|
|
||||||
|
|
||||||
export const plugins = [
|
|
||||||
new HtmlWebpackPlugin({
|
|
||||||
inject: false,
|
|
||||||
template: 'html/index.html',
|
|
||||||
minify: !isDev && getMinifyHtmlOptions(),
|
|
||||||
}),
|
|
||||||
];
|
|
||||||
|
|
||||||
function getMinifyHtmlOptions() {
|
|
||||||
return {
|
|
||||||
removeComments: true,
|
|
||||||
removeCommentsFromCDATA: true,
|
|
||||||
removeCDATASectionsFromCDATA: true,
|
|
||||||
collapseWhitespace: true,
|
|
||||||
collapseBooleanAttributes: true,
|
|
||||||
removeAttributeQuotes: true,
|
|
||||||
removeRedundantAttributes: true,
|
|
||||||
useShortDoctype: true,
|
|
||||||
removeEmptyAttributes: true,
|
|
||||||
/* оставляем, поскольку у нас
|
|
||||||
* в элемент fm генерируеться
|
|
||||||
* таблица файлов
|
|
||||||
*/
|
|
||||||
removeEmptyElements: false,
|
|
||||||
removeOptionalTags: true,
|
|
||||||
removeScriptTypeAttributes: true,
|
|
||||||
removeStyleLinkTypeAttributes: true,
|
|
||||||
|
|
||||||
minifyJS: true,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
173
.rspack/js.js
173
.rspack/js.js
|
|
@ -1,173 +0,0 @@
|
||||||
import {resolve, sep} from 'node:path';
|
|
||||||
import {fileURLToPath} from 'node:url';
|
|
||||||
import {env} from 'node:process';
|
|
||||||
import {rspack} from '@rspack/core';
|
|
||||||
|
|
||||||
const resolveModule = (a) => fileURLToPath(import.meta.resolve(a));
|
|
||||||
|
|
||||||
const {
|
|
||||||
EnvironmentPlugin,
|
|
||||||
NormalModuleReplacementPlugin,
|
|
||||||
ProvidePlugin,
|
|
||||||
} = rspack;
|
|
||||||
|
|
||||||
const modules = './modules';
|
|
||||||
const dirModules = './client/modules';
|
|
||||||
const dirCss = './css';
|
|
||||||
const dirThemes = `${dirCss}/themes`;
|
|
||||||
const dirColumns = `${dirCss}/columns`;
|
|
||||||
const dir = './client';
|
|
||||||
const {NODE_ENV} = env;
|
|
||||||
const isDev = NODE_ENV === 'development';
|
|
||||||
|
|
||||||
const rootDir = new URL('..', import.meta.url).pathname;
|
|
||||||
const dist = resolve(rootDir, 'dist');
|
|
||||||
const distDev = resolve(rootDir, 'dist-dev');
|
|
||||||
const devtool = isDev ? 'eval' : 'source-map';
|
|
||||||
|
|
||||||
const noParse = (a) => a.endsWith('.spec.js');
|
|
||||||
|
|
||||||
// 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',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}];
|
|
||||||
|
|
||||||
const plugins = [
|
|
||||||
new NormalModuleReplacementPlugin(/^node:/, (resource) => {
|
|
||||||
resource.request = resource.request.replace(/^node:/, '');
|
|
||||||
}),
|
|
||||||
new NormalModuleReplacementPlugin(/^putout$/, '@putout/bundle'),
|
|
||||||
new EnvironmentPlugin({
|
|
||||||
NODE_ENV,
|
|
||||||
}),
|
|
||||||
new ProvidePlugin({
|
|
||||||
process: 'process/browser',
|
|
||||||
}),
|
|
||||||
];
|
|
||||||
|
|
||||||
const splitChunks = {
|
|
||||||
chunks: 'all',
|
|
||||||
cacheGroups: {
|
|
||||||
abcCommon: {
|
|
||||||
name: 'cloudcmd.common',
|
|
||||||
chunks: (chunk) => {
|
|
||||||
const lazyChunks = [
|
|
||||||
'sw',
|
|
||||||
'nojs',
|
|
||||||
'view',
|
|
||||||
'edit',
|
|
||||||
'terminal',
|
|
||||||
'config',
|
|
||||||
'user-menu',
|
|
||||||
'help',
|
|
||||||
'themes/dark',
|
|
||||||
'themes/light',
|
|
||||||
'columns/name-size',
|
|
||||||
'columns/name-size-date',
|
|
||||||
'columns/name-size-time',
|
|
||||||
'columns/name-size-date-time',
|
|
||||||
];
|
|
||||||
|
|
||||||
return !lazyChunks.includes(chunk.name);
|
|
||||||
},
|
|
||||||
minChunks: 1,
|
|
||||||
enforce: true,
|
|
||||||
priority: -1,
|
|
||||||
reuseExistingChunk: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export default {
|
|
||||||
resolve: {
|
|
||||||
symlinks: false,
|
|
||||||
alias: {
|
|
||||||
'node:process': 'process',
|
|
||||||
'node:path': 'path',
|
|
||||||
},
|
|
||||||
fallback: {
|
|
||||||
path: resolveModule('path-browserify'),
|
|
||||||
process: resolveModule('process/browser'),
|
|
||||||
util: resolveModule('util'),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
devtool,
|
|
||||||
optimization: {
|
|
||||||
splitChunks,
|
|
||||||
},
|
|
||||||
entry: {
|
|
||||||
'themes/dark': `${dirThemes}/dark.css`,
|
|
||||||
'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.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.js`,
|
|
||||||
[`${modules}/view`]: `${dirModules}/view/index.js`,
|
|
||||||
[`${modules}/help`]: `${dirModules}/help.js`,
|
|
||||||
[`${modules}/markdown`]: `${dirModules}/markdown.js`,
|
|
||||||
[`${modules}/config`]: `${dirModules}/config/index.js`,
|
|
||||||
[`${modules}/contact`]: `${dirModules}/contact.js`,
|
|
||||||
[`${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.js`,
|
|
||||||
[`${modules}/user-menu`]: `${dirModules}/user-menu/index.js`,
|
|
||||||
[`${modules}/polyfill`]: `${dirModules}/polyfill.js`,
|
|
||||||
[`${modules}/command-line`]: `${dirModules}/command-line.js`,
|
|
||||||
},
|
|
||||||
output: {
|
|
||||||
filename: '[name].js',
|
|
||||||
path: isDev ? distDev : dist,
|
|
||||||
pathinfo: isDev,
|
|
||||||
devtoolModuleFilenameTemplate,
|
|
||||||
publicPath: '/dist/',
|
|
||||||
},
|
|
||||||
module: {
|
|
||||||
rules,
|
|
||||||
noParse,
|
|
||||||
},
|
|
||||||
plugins,
|
|
||||||
performance: {
|
|
||||||
maxEntrypointSize: 800_000,
|
|
||||||
maxAssetSize: 600_000,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
function devtoolModuleFilenameTemplate(info) {
|
|
||||||
const resource = info.absoluteResourcePath.replace(rootDir + sep, '');
|
|
||||||
return `file://cloudcmd/${resource}`;
|
|
||||||
}
|
|
||||||
15
.stylelintrc.yml
Normal file
15
.stylelintrc.yml
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
extends: stylelint-config-standard
|
||||||
|
rules:
|
||||||
|
indentation: 4
|
||||||
|
declaration-block-trailing-semicolon: always
|
||||||
|
declaration-colon-space-before: null
|
||||||
|
selector-list-comma-newline-after: null
|
||||||
|
comment-empty-line-before: null
|
||||||
|
number-leading-zero: null
|
||||||
|
number-no-trailing-zeros: null
|
||||||
|
string-quotes: single
|
||||||
|
function-url-quotes: never
|
||||||
|
no-eol-whitespace: null
|
||||||
|
font-family-name-quotes: always-unless-keyword
|
||||||
|
rule-empty-line-before: null
|
||||||
|
|
||||||
17
.travis.yml
Normal file
17
.travis.yml
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
language: node_js
|
||||||
|
node_js:
|
||||||
|
- 7
|
||||||
|
- 6
|
||||||
|
|
||||||
|
script:
|
||||||
|
- npm run codestyle
|
||||||
|
- npm run 6to5:client:dev
|
||||||
|
- npm run coverage && npm run report
|
||||||
|
|
||||||
|
notifications:
|
||||||
|
email:
|
||||||
|
on_success: never
|
||||||
|
on_failure: change
|
||||||
|
|
||||||
|
sudo: false
|
||||||
|
|
||||||
|
|
@ -1,2 +0,0 @@
|
||||||
[files]
|
|
||||||
extend-exclude = ["ChangeLog"]
|
|
||||||
22
.yaspellerrc
22
.yaspellerrc
|
|
@ -10,7 +10,6 @@
|
||||||
".md"
|
".md"
|
||||||
],
|
],
|
||||||
"dictionary":[
|
"dictionary":[
|
||||||
"CloudCmd",
|
|
||||||
"Dev",
|
"Dev",
|
||||||
"Dropbox",
|
"Dropbox",
|
||||||
"Deepword",
|
"Deepword",
|
||||||
|
|
@ -21,24 +20,17 @@
|
||||||
"Heroku",
|
"Heroku",
|
||||||
"Iptables",
|
"Iptables",
|
||||||
"JitSu",
|
"JitSu",
|
||||||
"Node",
|
|
||||||
"IO",
|
|
||||||
"Olena",
|
"Olena",
|
||||||
"TarZak",
|
"TarZak",
|
||||||
"Termux",
|
"Termux",
|
||||||
"Zalitok",
|
"Zalitok",
|
||||||
"WebSocket",
|
"WebSocket",
|
||||||
"auth",
|
|
||||||
"autostart",
|
|
||||||
"binded",
|
|
||||||
"cd",
|
"cd",
|
||||||
"cloudcmd",
|
"cloudcmd",
|
||||||
"coderaiser",
|
"coderaiser",
|
||||||
"com",
|
"com",
|
||||||
"deepword",
|
|
||||||
"dev",
|
"dev",
|
||||||
"destructuring",
|
"deepword",
|
||||||
"dropbox",
|
|
||||||
"dword",
|
"dword",
|
||||||
"edward",
|
"edward",
|
||||||
"favicon",
|
"favicon",
|
||||||
|
|
@ -46,27 +38,17 @@
|
||||||
"gz",
|
"gz",
|
||||||
"io",
|
"io",
|
||||||
"js",
|
"js",
|
||||||
"linux",
|
|
||||||
"maintainers",
|
|
||||||
"markdown",
|
|
||||||
"microservice",
|
|
||||||
"minification",
|
"minification",
|
||||||
"mouseup",
|
"mouseup",
|
||||||
"named",
|
|
||||||
"nginx",
|
"nginx",
|
||||||
"npm",
|
|
||||||
"or io",
|
"or io",
|
||||||
"patreon",
|
|
||||||
"rc",
|
|
||||||
"refactor",
|
"refactor",
|
||||||
"sexualized",
|
|
||||||
"sslPort",
|
"sslPort",
|
||||||
"unselect",
|
"unselect",
|
||||||
"util",
|
"util",
|
||||||
"v0",
|
"v0",
|
||||||
"v1",
|
"v1",
|
||||||
"v2",
|
"v2",
|
||||||
"yml",
|
"yml"
|
||||||
"systemd"
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,46 +0,0 @@
|
||||||
# Contributor Covenant Code of Conduct
|
|
||||||
|
|
||||||
## Our Pledge
|
|
||||||
|
|
||||||
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
|
|
||||||
|
|
||||||
## Our Standards
|
|
||||||
|
|
||||||
Examples of behavior that contributes to creating a positive environment include:
|
|
||||||
|
|
||||||
- Using welcoming and inclusive language
|
|
||||||
- Being respectful of differing viewpoints and experiences
|
|
||||||
- Gracefully accepting constructive criticism
|
|
||||||
- Focusing on what is best for the community
|
|
||||||
- Showing empathy towards other community members
|
|
||||||
|
|
||||||
Examples of unacceptable behavior by participants include:
|
|
||||||
|
|
||||||
- The use of sexualized language or imagery and unwelcome sexual attention or advances
|
|
||||||
- Trolling, insulting/derogatory comments, and personal or political attacks
|
|
||||||
- Public or private harassment
|
|
||||||
- Publishing others' private information, such as a physical or electronic address, without explicit permission
|
|
||||||
- Other conduct which could reasonably be considered inappropriate in a professional setting
|
|
||||||
|
|
||||||
## Our Responsibilities
|
|
||||||
|
|
||||||
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
|
|
||||||
|
|
||||||
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
|
|
||||||
|
|
||||||
## Scope
|
|
||||||
|
|
||||||
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
|
|
||||||
|
|
||||||
## Enforcement
|
|
||||||
|
|
||||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
|
|
||||||
|
|
||||||
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
|
|
||||||
|
|
||||||
## Attribution
|
|
||||||
|
|
||||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
|
|
||||||
|
|
||||||
[homepage]: http://contributor-covenant.org
|
|
||||||
[version]: http://contributor-covenant.org/version/1/4/
|
|
||||||
|
|
@ -1,30 +1,27 @@
|
||||||
## Commit
|
Commit
|
||||||
|
---------------
|
||||||
Format of the commit message: **type(scope) subject**
|
Format of the commit message: **type(scope) subject**
|
||||||
|
|
||||||
**Type**:
|
**Type**:
|
||||||
|
- feature(scope) subject
|
||||||
- feature: scope: subject
|
- fix(scope) subject
|
||||||
- fix: scope: subject
|
- docs(scope) subject
|
||||||
- docs: scope: subject
|
- refactor(scope) subject
|
||||||
- refactor: scope: subject
|
- test(scope) subject
|
||||||
- test: scope: subject
|
- chore(scope) subject
|
||||||
- chore: scope: subject
|
|
||||||
|
|
||||||
**Scope**:
|
**Scope**:
|
||||||
Scope could be anything specifying place of the commit change.
|
Scope could be anything specifying place of the commit change.
|
||||||
For example util, console, view, edit, style etc...
|
For example util, console, view, edit, style etc...
|
||||||
|
|
||||||
**Subject text**:
|
**Subject text**:
|
||||||
|
|
||||||
- use imperative, present tense: “change” not “changed” nor “changes”
|
- use imperative, present tense: “change” not “changed” nor “changes”
|
||||||
- don't capitalize first letter
|
- don't capitalize first letter
|
||||||
- no dot (.) at the end
|
- no dot (.) at the end
|
||||||
**Message body**:
|
**Message body**:
|
||||||
- just as in <subject> use imperative, present tense: “change” not “changed” nor “changes”
|
- just as in <subject> use imperative, present tense: “change” not “changed” nor “changes”
|
||||||
- includes motivation for the change and contrasts with previous behavior
|
- includes motivation for the change and contrasts with previous behavior
|
||||||
|
|
||||||
**Examples**:
|
**Examples**:
|
||||||
|
- [fix(style) .name{width}: 37% -> 35%](https://github.com/coderaiser/cloudcmd/commit/94b0642e3990c17b3a0ee3efeb75f343e1e7c050)
|
||||||
- [fix: style: .name{width}: 37% -> 35%](https://github.com/coderaiser/cloudcmd/commit/94b0642e3990c17b3a0ee3efeb75f343e1e7c050)
|
- [fix(console) dispatch: focus -> mouseup](https://github.com/coderaiser/cloudcmd/commit/f41ec5058d1411e86a881f8e8077e0572e0409ec)
|
||||||
- [fix: console: dispatch: focus -> mouseup](https://github.com/coderaiser/cloudcmd/commit/f41ec5058d1411e86a881f8e8077e0572e0409ec)
|
|
||||||
|
|
|
||||||
20
Dockerfile
Normal file
20
Dockerfile
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
FROM node
|
||||||
|
MAINTAINER Coderaiser
|
||||||
|
|
||||||
|
RUN mkdir -p /usr/src/app
|
||||||
|
WORKDIR /usr/src/app
|
||||||
|
|
||||||
|
COPY package.json /usr/src/app/
|
||||||
|
|
||||||
|
RUN npm install --production && \
|
||||||
|
npm i gritty
|
||||||
|
|
||||||
|
COPY . /usr/src/app
|
||||||
|
|
||||||
|
ENV cloudcmd_terminal true
|
||||||
|
ENV cloudcmd_terminal_path gritty
|
||||||
|
|
||||||
|
EXPOSE 8000
|
||||||
|
|
||||||
|
ENTRYPOINT ["bin/cloudcmd.js"]
|
||||||
|
|
||||||
24
Dockerfile.alpine
Normal file
24
Dockerfile.alpine
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
FROM mhart/alpine-node
|
||||||
|
MAINTAINER Coderaiser
|
||||||
|
|
||||||
|
RUN mkdir -p /usr/src/app
|
||||||
|
WORKDIR /usr/src/app
|
||||||
|
|
||||||
|
COPY package.json /usr/src/app/
|
||||||
|
|
||||||
|
RUN npm install --production && \
|
||||||
|
apk update && \
|
||||||
|
apk add --no-cache bash make g++ python && \
|
||||||
|
npm i gritty && \
|
||||||
|
apk del make g++ python && \
|
||||||
|
rm -rf /usr/include /tmp/* /var/cache/apk/*
|
||||||
|
|
||||||
|
COPY . /usr/src/app
|
||||||
|
|
||||||
|
ENV cloudcmd_terminal true
|
||||||
|
ENV cloudcmd_terminal_path gritty
|
||||||
|
|
||||||
|
EXPOSE 8000
|
||||||
|
|
||||||
|
ENTRYPOINT ["bin/cloudcmd.js"]
|
||||||
|
|
||||||
2
LICENSE
2
LICENSE
|
|
@ -1,6 +1,6 @@
|
||||||
(The MIT License)
|
(The MIT License)
|
||||||
|
|
||||||
Copyright (c) 2012-2025 Coderaiser <mnemonic.enemy@gmail.com>
|
Copyright (c) 2012-2017 Coderaiser <mnemonic.enemy@gmail.com>
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining
|
Permission is hereby granted, free of charge, to any person obtaining
|
||||||
a copy of this software and associated documentation files (the
|
a copy of this software and associated documentation files (the
|
||||||
|
|
|
||||||
197
README.md
197
README.md
|
|
@ -1,33 +1,41 @@
|
||||||
# Cloud Commander v19.19.1 [![Build Status][BuildStatusIMGURL]][BuildStatusURL] [![Codacy][CodacyIMG]][CodacyURL] [![Gitter][GitterIMGURL]][GitterURL] [![Deploy on InstaPods][DeployInstaPodsIMG]][DeployInstaPodsURL]
|
# Cloud Commander v6.14.0 [![License][LicenseIMGURL]][LicenseURL] [![NPM version][NPMIMGURL]][NPMURL] [![Dependency Status][DependencyStatusIMGURL]][DependencyStatusURL] [![Build Status][BuildStatusIMGURL]][BuildStatusURL] [![Package Quality][PackageQualityIMGURL]][PackageQualityURL] [![Codacy][CodacyIMG]][CodacyURL] [![Gitter][GitterIMGURL]][GitterURL] [](#backers) [](#sponsors)
|
||||||
|
|
||||||
### [Main][MainURL] [Blog][BlogURL] [Support][SupportURL] [Demo][DemoURL]
|
### [Main][MainURL] [Blog][BlogURL] Live([Heroku][HerokuURL])
|
||||||
|
|
||||||
[MainURL]: https://cloudcmd.io "Main"
|
[NPM_INFO_IMG]: https://nodei.co/npm/cloudcmd.png
|
||||||
[BlogURL]: https://blog.cloudcmd.io "Blog"
|
[MainURL]: http://cloudcmd.io "Main"
|
||||||
[SupportURL]: https://patreon.com/coderaiser "Patreon"
|
[BlogURL]: http://blog.cloudcmd.io "Blog"
|
||||||
[DemoURL]: https://cloudcmd-zdp6.onrender.com
|
[HerokuURL]: http://cloudcmd.herokuapp.com/ "Heroku"
|
||||||
[NPM_INFO_IMG]: https://nodei.co/npm/cloudcmd.png
|
[NPMURL]: https://npmjs.org/package/cloudcmd "npm"
|
||||||
[BuildStatusURL]: https://github.com/coderaiser/cloudcmd/actions/workflows/nodejs.yml "Build Status"
|
[NPMIMGURL]: https://img.shields.io/npm/v/cloudcmd.svg?style=flat
|
||||||
[BuildStatusIMGURL]: https://github.com/coderaiser/cloudcmd/actions/workflows/nodejs.yml/badge.svg
|
[LicenseURL]: https://tldrlegal.com/license/mit-license "MIT License"
|
||||||
[CodacyURL]: https://www.codacy.com/app/coderaiser/cloudcmd
|
[LicenseIMGURL]: https://img.shields.io/badge/license-MIT-317BF9.svg?style=flat
|
||||||
[CodacyIMG]: https://api.codacy.com/project/badge/Grade/ddda78be780549ce8754f8d47a8c0e36
|
[DependencyStatusURL]: https://gemnasium.com/coderaiser/cloudcmd "Dependency Status"
|
||||||
[GitterURL]: https://gitter.im/cloudcmd/hello
|
[DependencyStatusIMGURL]: https://img.shields.io/gemnasium/coderaiser/cloudcmd.svg?style=flat
|
||||||
[GitterIMGURL]: https://img.shields.io/gitter/room/coderaiser/cloudcmd.js.svg
|
[BuildStatusURL]: https://travis-ci.org/coderaiser/cloudcmd "Build Status"
|
||||||
[DeployURL]: https://heroku.com/deploy?template=https://github.com/coderaiser/cloudcmd "Deploy"
|
[BuildStatusIMGURL]: https://img.shields.io/travis/coderaiser/cloudcmd.svg?style=flat
|
||||||
[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.
|
[PackageQualityURL]: http://packagequality.com/#?package=cloudcmd "Package Quality"
|
||||||
|
[PackageQualityIMGURL]: http://packagequality.com/shield/cloudcmd.svg
|
||||||
|
|
||||||

|
[CodacyURL]: https://www.codacy.com/app/coderaiser/cloudcmd
|
||||||
|
[CodacyIMG]: https://api.codacy.com/project/badge/Grade/ddda78be780549ce8754f8d47a8c0e36
|
||||||
|
|
||||||
|
[GitterURL]: https://gitter.im/cloudcmd
|
||||||
|
[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
|
||||||
|
|
||||||
|
**Cloud Commander** is an orthodox web file manager with console and editor.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
## Install
|
## Install
|
||||||
|
|
||||||
```
|
```
|
||||||
npm i cloudcmd -g
|
npm i cloudcmd -g
|
||||||
```
|
```
|
||||||
|
|
||||||
## Start
|
## Start
|
||||||
|
|
||||||
For starting just type in console:
|
For starting just type in console:
|
||||||
|
|
@ -40,13 +48,7 @@ cloudcmd
|
||||||
|
|
||||||
Open url `http://localhost:8000` in browser.
|
Open url `http://localhost:8000` in browser.
|
||||||
|
|
||||||
### View
|
|
||||||
|
|
||||||
You will see something similar to this.
|
|
||||||

|
|
||||||
|
|
||||||
## Deploy
|
## Deploy
|
||||||
|
|
||||||
`Cloud Commander` could be easily deployed to [Heroku][DeployURL].
|
`Cloud Commander` could be easily deployed to [Heroku][DeployURL].
|
||||||
|
|
||||||
[![Deploy][DeployIMG]][DeployURL]
|
[![Deploy][DeployIMG]][DeployURL]
|
||||||
|
|
@ -70,72 +72,54 @@ npm i cloudcmd express socket.io -S
|
||||||
And create `index.js`:
|
And create `index.js`:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
import http from 'node:http';
|
const http = require('http');
|
||||||
import {cloudcmd} from 'cloudcmd';
|
const cloudcmd = require('cloudcmd');
|
||||||
import {Server} from 'socket.io';
|
const io = require('socket.io');
|
||||||
import express from 'express';
|
const app = require('express')();
|
||||||
|
|
||||||
const app = express();
|
|
||||||
|
|
||||||
const port = 1337;
|
const port = 1337;
|
||||||
const prefix = '/';
|
const prefix = '/cloudcmd';
|
||||||
|
|
||||||
const server = http.createServer(app);
|
const server = http.createServer(app);
|
||||||
const socket = new Server(server, {
|
const socket = io.listen(server, {
|
||||||
path: `${prefix}socket.io`,
|
path: `${prefix}/socket.io`
|
||||||
});
|
});
|
||||||
|
|
||||||
const config = {
|
const config = {
|
||||||
name: 'cloudcmd :)',
|
prefix /* base URL or function which returns base URL (optional) */
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const plugins = [
|
||||||
|
__dirname + '/plugin.js'
|
||||||
|
];
|
||||||
|
|
||||||
const filePicker = {
|
const filePicker = {
|
||||||
data: {
|
data: {
|
||||||
FilePicker: {
|
FilePicker: {
|
||||||
key: 'key',
|
key: 'key',
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// override option from json/modules.json
|
// override option from json/modules.json
|
||||||
const modules = {
|
const modules = {filePicker};
|
||||||
filePicker,
|
|
||||||
};
|
|
||||||
|
|
||||||
const {
|
app.use(cloudcmd({
|
||||||
createConfigManager,
|
socket, /* used by Config, Edit (optional) and Console (required) */
|
||||||
configPath,
|
config, /* config data (optional) */
|
||||||
} = cloudcmd;
|
plugins, /* optional */
|
||||||
|
modules, /* optional */
|
||||||
const configManager = createConfigManager({
|
|
||||||
configPath,
|
|
||||||
});
|
|
||||||
|
|
||||||
app.use(prefix, cloudcmd({
|
|
||||||
socket, // used by Config, Edit (optional) and Console (required)
|
|
||||||
config, // config data (optional)
|
|
||||||
modules, // optional
|
|
||||||
configManager, // optional
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
server.listen(port);
|
server.listen(port);
|
||||||
```
|
```
|
||||||
|
|
||||||
## Docker
|
Docker
|
||||||
|
---------------
|
||||||
The docker images are provided for multiple architectures and types. The following list shows all existing images:
|
|
||||||
|
|
||||||
| Architecture | Type |
|
|
||||||
|----------------|--------------|
|
|
||||||
| amd64 | linux |
|
|
||||||
| arm64 (arm/v8) | linux |
|
|
||||||
| amd64 | linux-alpine |
|
|
||||||
| arm64 (arm/v8) | linux-alpine |
|
|
||||||
|
|
||||||
`Cloud Commander` could be used as a [docker container](https://hub.docker.com/r/coderaiser/cloudcmd/ "Docker container") this way:
|
`Cloud Commander` could be used as a [docker container](https://hub.docker.com/r/coderaiser/cloudcmd/ "Docker container") this way:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
docker run -it --rm -v ~:/root -v /:/mnt/fs -w=/root -p 8000:8000 coderaiser/cloudcmd
|
docker run -v ~:/root -v /:/mnt/fs -t -p 8000:8000 coderaiser/cloudcmd
|
||||||
```
|
```
|
||||||
|
|
||||||
Config would be read from home directory, hosts root file system would be mount to `/mnt/fs`,
|
Config would be read from home directory, hosts root file system would be mount to `/mnt/fs`,
|
||||||
|
|
@ -161,19 +145,84 @@ When you create this file run:
|
||||||
docker-compose up
|
docker-compose up
|
||||||
```
|
```
|
||||||
|
|
||||||
## Documentation
|
Get involved
|
||||||
|
---------------
|
||||||
More documentation you can find on https://cloudcmd.io/.
|
|
||||||
|
|
||||||
## Get involved
|
|
||||||
|
|
||||||
There is a lot ways to be involved in `Cloud Commander` development:
|
There is a lot ways to be involved in `Cloud Commander` development:
|
||||||
|
|
||||||
- support project on patreon: https://patreon.com/coderaiser;
|
- if you find a bug or got idea to share [create issue](https://github.com/coderaiser/cloudcmd/issues/new "Create issue");
|
||||||
- if you find a bug or got idea to share [create an issue](https://github.com/coderaiser/cloudcmd/issues/new "Create issue");
|
|
||||||
- if you fixed a bug, typo or implemented new feature [create pull request](https://github.com/coderaiser/cloudcmd/compare "Create pull request");
|
- if you fixed a bug, typo or implemented new feature [create pull request](https://github.com/coderaiser/cloudcmd/compare "Create pull request");
|
||||||
- if you know languages you can help with [site translations](https://github.com/coderaiser/cloudcmd/wiki "Cloud Commander community wiki");
|
- if you know languages you can help with [site translations](https://github.com/coderaiser/cloudcmd/wiki "Cloud Commander community wiki");
|
||||||
|
|
||||||
|
## Backers
|
||||||
|
Support us with a monthly donation and help us continue our activities. [[Become a backer](https://opencollective.com/cloudcmd#backer)]
|
||||||
|
|
||||||
|
[](https://opencollective.com/cloudcmd/backer/0/website)
|
||||||
|
[](https://opencollective.com/cloudcmd/backer/1/website)
|
||||||
|
[](https://opencollective.com/cloudcmd/backer/2/website)
|
||||||
|
[](https://opencollective.com/cloudcmd/backer/3/website)
|
||||||
|
[](https://opencollective.com/cloudcmd/backer/4/website)
|
||||||
|
[](https://opencollective.com/cloudcmd/backer/5/website)
|
||||||
|
[](https://opencollective.com/cloudcmd/backer/6/website)
|
||||||
|
[](https://opencollective.com/cloudcmd/backer/7/website)
|
||||||
|
[](https://opencollective.com/cloudcmd/backer/8/website)
|
||||||
|
[](https://opencollective.com/cloudcmd/backer/9/website)
|
||||||
|
[](https://opencollective.com/cloudcmd/backer/10/website)
|
||||||
|
[](https://opencollective.com/cloudcmd/backer/11/website)
|
||||||
|
[](https://opencollective.com/cloudcmd/backer/12/website)
|
||||||
|
[](https://opencollective.com/cloudcmd/backer/13/website)
|
||||||
|
[](https://opencollective.com/cloudcmd/backer/14/website)
|
||||||
|
[](https://opencollective.com/cloudcmd/backer/15/website)
|
||||||
|
[](https://opencollective.com/cloudcmd/backer/16/website)
|
||||||
|
[](https://opencollective.com/cloudcmd/backer/17/website)
|
||||||
|
[](https://opencollective.com/cloudcmd/backer/18/website)
|
||||||
|
[](https://opencollective.com/cloudcmd/backer/19/website)
|
||||||
|
[](https://opencollective.com/cloudcmd/backer/20/website)
|
||||||
|
[](https://opencollective.com/cloudcmd/backer/21/website)
|
||||||
|
[](https://opencollective.com/cloudcmd/backer/22/website)
|
||||||
|
[](https://opencollective.com/cloudcmd/backer/23/website)
|
||||||
|
[](https://opencollective.com/cloudcmd/backer/24/website)
|
||||||
|
[](https://opencollective.com/cloudcmd/backer/25/website)
|
||||||
|
[](https://opencollective.com/cloudcmd/backer/26/website)
|
||||||
|
[](https://opencollective.com/cloudcmd/backer/27/website)
|
||||||
|
[](https://opencollective.com/cloudcmd/backer/28/website)
|
||||||
|
[](https://opencollective.com/cloudcmd/backer/29/website)
|
||||||
|
|
||||||
|
## Sponsors
|
||||||
|
Become a sponsor and get your logo on our README on Github with a link to your site. [[Become a sponsor](https://opencollective.com/cloudcmd#sponsor)]
|
||||||
|
|
||||||
|
[](https://opencollective.com/cloudcmd/sponsor/0/website)
|
||||||
|
[](https://opencollective.com/cloudcmd/sponsor/1/website)
|
||||||
|
[](https://opencollective.com/cloudcmd/sponsor/2/website)
|
||||||
|
[](https://opencollective.com/cloudcmd/sponsor/3/website)
|
||||||
|
[](https://opencollective.com/cloudcmd/sponsor/4/website)
|
||||||
|
[](https://opencollective.com/cloudcmd/sponsor/5/website)
|
||||||
|
[](https://opencollective.com/cloudcmd/sponsor/6/website)
|
||||||
|
[](https://opencollective.com/cloudcmd/sponsor/7/website)
|
||||||
|
[](https://opencollective.com/cloudcmd/sponsor/8/website)
|
||||||
|
[](https://opencollective.com/cloudcmd/sponsor/9/website)
|
||||||
|
[](https://opencollective.com/cloudcmd/sponsor/10/website)
|
||||||
|
[](https://opencollective.com/cloudcmd/sponsor/11/website)
|
||||||
|
[](https://opencollective.com/cloudcmd/sponsor/12/website)
|
||||||
|
[](https://opencollective.com/cloudcmd/sponsor/13/website)
|
||||||
|
[](https://opencollective.com/cloudcmd/sponsor/14/website)
|
||||||
|
[](https://opencollective.com/cloudcmd/sponsor/15/website)
|
||||||
|
[](https://opencollective.com/cloudcmd/sponsor/16/website)
|
||||||
|
[](https://opencollective.com/cloudcmd/sponsor/17/website)
|
||||||
|
[](https://opencollective.com/cloudcmd/sponsor/18/website)
|
||||||
|
[](https://opencollective.com/cloudcmd/sponsor/19/website)
|
||||||
|
[](https://opencollective.com/cloudcmd/sponsor/20/website)
|
||||||
|
[](https://opencollective.com/cloudcmd/sponsor/21/website)
|
||||||
|
[](https://opencollective.com/cloudcmd/sponsor/22/website)
|
||||||
|
[](https://opencollective.com/cloudcmd/sponsor/23/website)
|
||||||
|
[](https://opencollective.com/cloudcmd/sponsor/24/website)
|
||||||
|
[](https://opencollective.com/cloudcmd/sponsor/25/website)
|
||||||
|
[](https://opencollective.com/cloudcmd/sponsor/26/website)
|
||||||
|
[](https://opencollective.com/cloudcmd/sponsor/27/website)
|
||||||
|
[](https://opencollective.com/cloudcmd/sponsor/28/website)
|
||||||
|
[](https://opencollective.com/cloudcmd/sponsor/29/website)
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
MIT
|
MIT
|
||||||
|
|
||||||
|
|
|
||||||
125
app.json
125
app.json
|
|
@ -11,7 +11,6 @@
|
||||||
"folder",
|
"folder",
|
||||||
"orthodox"
|
"orthodox"
|
||||||
],
|
],
|
||||||
"stack": "heroku-18",
|
|
||||||
"env": {
|
"env": {
|
||||||
"NPM_CONFIG_PRODUCTION": {
|
"NPM_CONFIG_PRODUCTION": {
|
||||||
"description": "Keep false to install devDependencies and build frontend",
|
"description": "Keep false to install devDependencies and build frontend",
|
||||||
|
|
@ -23,31 +22,6 @@
|
||||||
"value": "edward",
|
"value": "edward",
|
||||||
"required": false
|
"required": false
|
||||||
},
|
},
|
||||||
"CLOUDCMD_CONTACT": {
|
|
||||||
"description": "enable contact",
|
|
||||||
"value": "true",
|
|
||||||
"required": false
|
|
||||||
},
|
|
||||||
"CLOUDCMD_CONFIG_DIALOG": {
|
|
||||||
"description": "show config dialog",
|
|
||||||
"value": "false",
|
|
||||||
"required": false
|
|
||||||
},
|
|
||||||
"CLOUDCMD_CONFIG_AUTH": {
|
|
||||||
"description": "disable auth change in config dialog",
|
|
||||||
"value": "false",
|
|
||||||
"required": false
|
|
||||||
},
|
|
||||||
"CLOUDCMD_CONSOLE": {
|
|
||||||
"description": "enable console",
|
|
||||||
"value": "true",
|
|
||||||
"required": false
|
|
||||||
},
|
|
||||||
"CLOUDCMD_SYNC_CONSOLE_PATH": {
|
|
||||||
"description": "sync console path",
|
|
||||||
"value": "false",
|
|
||||||
"required": false
|
|
||||||
},
|
|
||||||
"CLOUDCMD_TERMINAL": {
|
"CLOUDCMD_TERMINAL": {
|
||||||
"description": "enable terminal",
|
"description": "enable terminal",
|
||||||
"value": "true",
|
"value": "true",
|
||||||
|
|
@ -58,103 +32,8 @@
|
||||||
"value": "gritty",
|
"value": "gritty",
|
||||||
"required": false
|
"required": false
|
||||||
},
|
},
|
||||||
"CLOUDCMD_TERMINAL_COMMAND": {
|
"CLOUDCMD_CONFIG_DIALOG": {
|
||||||
"description": "set command to run in terminal (shell by default)",
|
"description": "show config dialog",
|
||||||
"value": "",
|
|
||||||
"required": false
|
|
||||||
},
|
|
||||||
"CLOUDCMD_ONE_FILE_PANEL": {
|
|
||||||
"description": "show one file panel",
|
|
||||||
"value": "false",
|
|
||||||
"required": false
|
|
||||||
},
|
|
||||||
"CLOUDCMD_KEYS_PANEL": {
|
|
||||||
"description": "show keys panel",
|
|
||||||
"value": "true",
|
|
||||||
"required": false
|
|
||||||
},
|
|
||||||
"CLOUDCMD_NAME": {
|
|
||||||
"description": "set tab name in web browser",
|
|
||||||
"value": "",
|
|
||||||
"required": false
|
|
||||||
},
|
|
||||||
"CLOUDCMD_AUTH": {
|
|
||||||
"description": "enable authorization",
|
|
||||||
"value": "false",
|
|
||||||
"required": false
|
|
||||||
},
|
|
||||||
"CLOUDCMD_USERNAME": {
|
|
||||||
"description": "set username",
|
|
||||||
"value": "root",
|
|
||||||
"required": false
|
|
||||||
},
|
|
||||||
"CLOUDCMD_PASSWORD": {
|
|
||||||
"description": "set password",
|
|
||||||
"value": "toor",
|
|
||||||
"required": false
|
|
||||||
},
|
|
||||||
"CLOUDCMD_ROOT": {
|
|
||||||
"description": "set root dir",
|
|
||||||
"value": "",
|
|
||||||
"required": false
|
|
||||||
},
|
|
||||||
"CLOUDCMD_VIM": {
|
|
||||||
"description": "enable vim hot keys",
|
|
||||||
"value": "false",
|
|
||||||
"required": false
|
|
||||||
},
|
|
||||||
"CLOUDCMD_COLUMNS": {
|
|
||||||
"description": "set visible columns",
|
|
||||||
"value": "name-size-date-owner-mode",
|
|
||||||
"required": false
|
|
||||||
},
|
|
||||||
"CLOUDCMD_OPEN": {
|
|
||||||
"description": "open web browser when server started",
|
|
||||||
"value": "false",
|
|
||||||
"required": false
|
|
||||||
},
|
|
||||||
"CLOUDCMD_CONFIRM_COPY": {
|
|
||||||
"description": "confirm copy",
|
|
||||||
"value": "true",
|
|
||||||
"required": false
|
|
||||||
},
|
|
||||||
"CLOUDCMD_CONFIRM_MOVE": {
|
|
||||||
"description": "confirm move",
|
|
||||||
"value": "true",
|
|
||||||
"required": false
|
|
||||||
},
|
|
||||||
"CLOUDCMD_SHOW_FILE_NAME": {
|
|
||||||
"description": "show file name in view and edit",
|
|
||||||
"value": "false",
|
|
||||||
"required": false
|
|
||||||
},
|
|
||||||
"CLOUDCMD_EXPORT": {
|
|
||||||
"description": "enable export of config through a server",
|
|
||||||
"value": "false",
|
|
||||||
"required": false
|
|
||||||
},
|
|
||||||
"CLOUDCMD_EXPORT_TOKEN": {
|
|
||||||
"description": "authorization token used by export server",
|
|
||||||
"value": "root",
|
|
||||||
"required": false
|
|
||||||
},
|
|
||||||
"CLOUDCMD_IMPORT": {
|
|
||||||
"description": "enable import of config",
|
|
||||||
"value": "false",
|
|
||||||
"required": false
|
|
||||||
},
|
|
||||||
"CLOUDCMD_IMPORT_TOKEN": {
|
|
||||||
"description": "authorization token used to connect to export server",
|
|
||||||
"value": "root",
|
|
||||||
"required": false
|
|
||||||
},
|
|
||||||
"CLOUDCMD_IMPORT_URL": {
|
|
||||||
"description": "url of an import server",
|
|
||||||
"value": "http://localhost:8000",
|
|
||||||
"required": false
|
|
||||||
},
|
|
||||||
"CLOUDCMD_IMPORT_LISTEN": {
|
|
||||||
"description": "enable listen on config updates from import server",
|
|
||||||
"value": "false",
|
"value": "false",
|
||||||
"required": false
|
"required": false
|
||||||
}
|
}
|
||||||
|
|
|
||||||
377
bin/cloudcmd.js
377
bin/cloudcmd.js
|
|
@ -1,50 +1,24 @@
|
||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
|
|
||||||
import process from 'node:process';
|
'use strict';
|
||||||
import {promisify} from 'node:util';
|
|
||||||
import {tryToCatch} from 'try-to-catch';
|
|
||||||
import parse from 'yargs-parser';
|
|
||||||
import exit from '../server/exit.js';
|
|
||||||
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.js';
|
|
||||||
import Info from '../package.json' with {
|
|
||||||
type: 'json',
|
|
||||||
};
|
|
||||||
|
|
||||||
process.on('unhandledRejection', exit);
|
const Info = require('../package');
|
||||||
|
const DIR_SERVER = '../server/';
|
||||||
|
|
||||||
const isUndefined = (a) => typeof a === 'undefined';
|
const exit = require(DIR_SERVER + 'exit');
|
||||||
|
const config = require(DIR_SERVER + 'config');
|
||||||
|
const env = require(DIR_SERVER + 'env');
|
||||||
|
|
||||||
const choose = (a, b) => {
|
const choose = (a, b) => {
|
||||||
if (isUndefined(a))
|
if (!a && typeof a !== 'boolean')
|
||||||
return b;
|
return b;
|
||||||
|
|
||||||
return a;
|
return a;
|
||||||
};
|
};
|
||||||
|
|
||||||
const config = createConfig({
|
const argv = process.argv;
|
||||||
configPath,
|
const args = require('minimist')(argv.slice(2), {
|
||||||
});
|
|
||||||
|
|
||||||
const maybeRoot = (a) => {
|
|
||||||
if (a === '.')
|
|
||||||
return process.cwd();
|
|
||||||
|
|
||||||
return a;
|
|
||||||
};
|
|
||||||
|
|
||||||
const yargsOptions = {
|
|
||||||
configuration: {
|
|
||||||
'strip-aliased': true,
|
|
||||||
'strip-dashed': true,
|
|
||||||
},
|
|
||||||
coerce: {
|
|
||||||
root: maybeRoot,
|
|
||||||
},
|
|
||||||
string: [
|
string: [
|
||||||
'name',
|
|
||||||
'port',
|
'port',
|
||||||
'password',
|
'password',
|
||||||
'username',
|
'username',
|
||||||
|
|
@ -53,16 +27,7 @@ const yargsOptions = {
|
||||||
'packer',
|
'packer',
|
||||||
'root',
|
'root',
|
||||||
'prefix',
|
'prefix',
|
||||||
'prefix-socket',
|
|
||||||
'terminal-path',
|
'terminal-path',
|
||||||
'terminal-command',
|
|
||||||
'columns',
|
|
||||||
'menu',
|
|
||||||
'theme',
|
|
||||||
'import-url',
|
|
||||||
'import-token',
|
|
||||||
'export-token',
|
|
||||||
'dropbox-token',
|
|
||||||
],
|
],
|
||||||
boolean: [
|
boolean: [
|
||||||
'auth',
|
'auth',
|
||||||
|
|
@ -71,91 +36,50 @@ const yargsOptions = {
|
||||||
'server',
|
'server',
|
||||||
'online',
|
'online',
|
||||||
'open',
|
'open',
|
||||||
|
'minify',
|
||||||
|
'progress',
|
||||||
'config-dialog',
|
'config-dialog',
|
||||||
'config-auth',
|
|
||||||
'config-port',
|
|
||||||
'console',
|
'console',
|
||||||
'sync-console-path',
|
|
||||||
'contact',
|
|
||||||
'terminal',
|
'terminal',
|
||||||
'terminal-auto-restart',
|
'one-panel-mode',
|
||||||
'one-file-panel',
|
'html-dialogs'
|
||||||
'confirm-copy',
|
|
||||||
'confirm-move',
|
|
||||||
'show-config',
|
|
||||||
'show-dot-files',
|
|
||||||
'show-file-name',
|
|
||||||
'vim',
|
|
||||||
'keys-panel',
|
|
||||||
'color',
|
|
||||||
'export',
|
|
||||||
'import',
|
|
||||||
'import-listen',
|
|
||||||
'log',
|
|
||||||
'zip',
|
|
||||||
'dropbox',
|
|
||||||
],
|
],
|
||||||
default: {
|
default: {
|
||||||
'server': true,
|
server : true,
|
||||||
'name': choose(env.parse('name'), config('name')),
|
auth : config('auth'),
|
||||||
'auth': choose(env.bool('auth'), config('auth')),
|
port : config('port'),
|
||||||
'port': config('port'),
|
minify : config('minify'),
|
||||||
'online': config('online'),
|
online : config('online'),
|
||||||
'open': choose(env.bool('open'), config('open')),
|
open : config('open'),
|
||||||
'editor': env.parse('editor') || config('editor'),
|
editor : choose(env('editor'), config('editor')),
|
||||||
'menu': env.parse('menu') || config('menu'),
|
packer : config('packer') || 'tar',
|
||||||
'packer': config('packer') || 'tar',
|
zip : config('zip'),
|
||||||
'zip': config('zip'),
|
username : config('username'),
|
||||||
'username': env.parse('username') || config('username'),
|
root : config('root'),
|
||||||
'root': choose(env.parse('root'), config('root')),
|
prefix : config('prefix'),
|
||||||
'prefix': choose(env.parse('prefix'), config('prefix')),
|
progress : config('progress'),
|
||||||
'console': choose(env.bool('console'), config('console')),
|
console : config('console'),
|
||||||
'contact': choose(env.bool('contact'), config('contact')),
|
terminal : choose(env.bool('terminal'), config('terminal')),
|
||||||
'terminal': choose(env.bool('terminal'), config('terminal')),
|
|
||||||
'columns': env.parse('columns') || config('columns') || '',
|
|
||||||
'theme': env.parse('theme') || config('theme') || '',
|
|
||||||
'vim': choose(env.bool('vim'), config('vim')),
|
|
||||||
'log': choose(env.bool('log'), config('log')),
|
|
||||||
|
|
||||||
'import-url': env.parse('import_url') || config('importUrl'),
|
'terminal-path': env('terminal_path') || config('terminalPath'),
|
||||||
'import-listen': choose(env.bool('import_listen'), config('importListen')),
|
|
||||||
'import': choose(env.bool('import'), config('import')),
|
|
||||||
'export': choose(env.bool('export'), config('export')),
|
|
||||||
|
|
||||||
'prefix-socket': config('prefixSocket'),
|
|
||||||
'show-dot-files': choose(env.bool('show_dot_files'), config('showDotFiles')),
|
|
||||||
'show-file-name': choose(env.bool('show_file_name'), config('showFileName')),
|
|
||||||
'sync-console-path': choose(env.bool('sync_console_path'), config('syncConsolePath')),
|
|
||||||
'config-dialog': choose(env.bool('config_dialog'), config('configDialog')),
|
'config-dialog': choose(env.bool('config_dialog'), config('configDialog')),
|
||||||
'config-auth': choose(env.bool('config_auth'), config('configAuth')),
|
'one-panel-mode': config('onePanelMode'),
|
||||||
'config-port': choose(env.bool('config_port'), config('configPort')),
|
'html-dialogs': config('htmlDialogs')
|
||||||
'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')),
|
|
||||||
'one-file-panel': choose(env.bool('one_file_panel'), config('oneFilePanel')),
|
|
||||||
'confirm-copy': choose(env.bool('confirm_copy'), config('confirmCopy')),
|
|
||||||
'confirm-move': choose(env.bool('confirm_move'), config('confirmMove')),
|
|
||||||
'keys-panel': env.bool('keys_panel') || config('keysPanel'),
|
|
||||||
'import-token': env.parse('import_token') || config('importToken'),
|
|
||||||
'export-token': env.parse('export_token') || config('exportToken'),
|
|
||||||
|
|
||||||
'dropbox': config('dropbox'),
|
|
||||||
'dropbox-token': config('dropboxToken') || '',
|
|
||||||
},
|
},
|
||||||
alias: {
|
alias: {
|
||||||
version: 'v',
|
v: 'version',
|
||||||
help: 'h',
|
h: 'help',
|
||||||
password: 'p',
|
p: 'password',
|
||||||
online: 'o',
|
o: 'online',
|
||||||
username: 'u',
|
u: 'username',
|
||||||
save: 's',
|
s: 'save',
|
||||||
auth: 'a',
|
a: 'auth',
|
||||||
config: 'c',
|
c: 'config'
|
||||||
},
|
},
|
||||||
};
|
unknown: (cmd) => {
|
||||||
|
exit('\'%s\' is not a cloudcmd option. See \'cloudcmd --help\'.', cmd);
|
||||||
const {argv} = process;
|
}
|
||||||
const args = parse(argv.slice(2), yargsOptions);
|
});
|
||||||
|
|
||||||
if (args.version)
|
if (args.version)
|
||||||
version();
|
version();
|
||||||
|
|
@ -164,112 +88,60 @@ else if (args.help)
|
||||||
else
|
else
|
||||||
main();
|
main();
|
||||||
|
|
||||||
async function main() {
|
function main() {
|
||||||
const {validateArgs} = await import('@putout/cli-validate-args');
|
|
||||||
|
|
||||||
const error = await validateArgs(args, [
|
|
||||||
...yargsOptions.boolean,
|
|
||||||
...yargsOptions.string,
|
|
||||||
]);
|
|
||||||
|
|
||||||
if (error)
|
|
||||||
return exit(error);
|
|
||||||
|
|
||||||
if (args.repl)
|
if (args.repl)
|
||||||
await repl();
|
repl();
|
||||||
|
|
||||||
validate.columns(args.columns);
|
checkUpdate();
|
||||||
validate.theme(args.theme);
|
|
||||||
|
|
||||||
port(args.port);
|
port(args.port);
|
||||||
|
|
||||||
config('name', args.name);
|
|
||||||
config('auth', args.auth);
|
config('auth', args.auth);
|
||||||
config('online', args.online);
|
config('online', args.online);
|
||||||
config('open', args.open);
|
config('open', args.open);
|
||||||
|
config('minify', args.minify);
|
||||||
config('username', args.username);
|
config('username', args.username);
|
||||||
|
config('progress', args.progress);
|
||||||
config('console', args.console);
|
config('console', args.console);
|
||||||
config('syncConsolePath', args.syncConsolePath);
|
|
||||||
config('showDotFiles', args.showDotFiles);
|
|
||||||
config('showFileName', args.showFileName);
|
|
||||||
config('contact', args.contact);
|
|
||||||
config('terminal', args.terminal);
|
config('terminal', args.terminal);
|
||||||
config('terminalPath', args.terminalPath);
|
config('terminalPath', args['terminal-path']);
|
||||||
config('terminalCommand', args.terminalCommand);
|
|
||||||
config('terminalAutoRestart', args.terminalAutoRestart);
|
|
||||||
config('editor', args.editor);
|
config('editor', args.editor);
|
||||||
config('menu', args.menu);
|
config('prefix', args.prefix);
|
||||||
config('prefix', prefixer(args.prefix));
|
config('root', args.root);
|
||||||
config('prefixSocket', prefixer(args.prefixSocket));
|
config('htmlDialogs', args['html-dialogs']);
|
||||||
config('root', args.root || '/');
|
config('onePanelMode', args['one-panel-mode']);
|
||||||
config('vim', args.vim);
|
config('configDialog', args['config-dialog']);
|
||||||
config('theme', args.theme);
|
|
||||||
config('columns', args.columns);
|
|
||||||
config('log', args.log);
|
|
||||||
config('confirmCopy', args.confirmCopy);
|
|
||||||
config('confirmMove', args.confirmMove);
|
|
||||||
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);
|
|
||||||
config('import', args.import);
|
|
||||||
config('importToken', args.importToken);
|
|
||||||
config('importListen', args.importListen);
|
|
||||||
config('importUrl', args.importUrl);
|
|
||||||
|
|
||||||
config('dropbox', args.dropbox);
|
readConfig(args.config);
|
||||||
config('dropboxToken', args.dropboxToken);
|
|
||||||
|
|
||||||
await readConfig(args.config);
|
|
||||||
|
|
||||||
const options = {
|
const options = {
|
||||||
root: config('root'),
|
root: args.root || '/', /* --no-root */
|
||||||
editor: config('editor'),
|
editor: args.editor,
|
||||||
packer: config('packer'),
|
packer: args.packer,
|
||||||
prefix: config('prefix'),
|
prefix: args.prefix
|
||||||
prefixSocket: config('prefixSocket'),
|
|
||||||
columns: config('columns'),
|
|
||||||
theme: config('theme'),
|
|
||||||
menu: config('menu'),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const password = env.parse('password') || args.password;
|
if (args.password)
|
||||||
|
config('password', getPassword(args.password));
|
||||||
|
|
||||||
if (password)
|
validateRoot(options.root);
|
||||||
config('password', await getPassword(password));
|
|
||||||
|
|
||||||
validateRoot(options.root, config);
|
if (!args.save)
|
||||||
|
return start(options);
|
||||||
|
|
||||||
if (args.showConfig)
|
config.save(() => {
|
||||||
await showConfig();
|
start(options);
|
||||||
|
});
|
||||||
const {distributeImport} = await import('../server/distribute/import.js');
|
|
||||||
const importConfig = promisify(distributeImport);
|
|
||||||
|
|
||||||
await start(options, config);
|
|
||||||
|
|
||||||
if (args.save)
|
|
||||||
config.write();
|
|
||||||
|
|
||||||
await tryToCatch(checkUpdate);
|
|
||||||
await importConfig(config);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function validateRoot(root, config) {
|
function validateRoot(root) {
|
||||||
validate.root(root, config);
|
const validate = require(DIR_SERVER + 'validate');
|
||||||
|
validate.root(root, console.log);
|
||||||
if (root === '/')
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (config('log'))
|
|
||||||
console.log(`root: ${root}`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getPassword(password) {
|
function getPassword(password) {
|
||||||
const {default: criton} = await import('criton');
|
const criton = require('criton');
|
||||||
|
|
||||||
return criton(password, config('algo'));
|
return criton(password, config('algo'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -277,86 +149,93 @@ function version() {
|
||||||
console.log('v' + Info.version);
|
console.log('v' + Info.version);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function start(options, config) {
|
function start(config) {
|
||||||
if (!args.server)
|
const SERVER = DIR_SERVER + 'server';
|
||||||
return;
|
|
||||||
|
|
||||||
const {default: server} = await import('../server/server.js');
|
if (args.server)
|
||||||
server(options, config);
|
require(SERVER)(config);
|
||||||
}
|
}
|
||||||
|
|
||||||
function port(arg) {
|
function port(arg) {
|
||||||
const number = parseInt(arg, 10);
|
const number = parseInt(arg, 10);
|
||||||
|
|
||||||
if (!isNaN(number))
|
if (!isNaN(number))
|
||||||
return config('port', number);
|
config('port', number);
|
||||||
|
else
|
||||||
exit('cloudcmd --port: should be a number');
|
exit('cloudcmd --port: should be a number');
|
||||||
}
|
}
|
||||||
|
|
||||||
async function showConfig() {
|
function readConfig(name) {
|
||||||
const {showConfig} = await import('../server/show-config.js');
|
|
||||||
const data = showConfig(config('*'));
|
|
||||||
|
|
||||||
console.log(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function readConfig(name) {
|
|
||||||
if (!name)
|
if (!name)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const {default: forEachKey} = await import('for-each-key');
|
const fs = require('fs');
|
||||||
|
const tryCatch = require('try-catch');
|
||||||
|
const jju = require('jju');
|
||||||
|
|
||||||
const data = await import(name, {
|
const readjsonSync = (name) => jju.parse(fs.readFileSync(name, 'utf8'), {
|
||||||
with: {
|
mode: 'json'
|
||||||
type: 'json',
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
forEachKey(config, data);
|
let data;
|
||||||
|
|
||||||
|
const error = tryCatch(() => {
|
||||||
|
data = readjsonSync(name);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (error)
|
||||||
|
return exit(error.message);
|
||||||
|
|
||||||
|
Object.keys(data).forEach((item) => {
|
||||||
|
config(item, data[item]);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function help() {
|
function help() {
|
||||||
const {default: bin} = await import('../json/help.json', {
|
const bin = require('../json/help');
|
||||||
with: {
|
|
||||||
type: 'json',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const {default: forEachKey} = await import('for-each-key');
|
|
||||||
const {default: currify} = await import('currify');
|
|
||||||
|
|
||||||
const usage = 'Usage: cloudcmd [options]';
|
const usage = 'Usage: cloudcmd [options]';
|
||||||
const url = Info.homepage;
|
const url = Info.homepage;
|
||||||
const log = currify((a, b, c) => console.log(a, b, c));
|
|
||||||
|
|
||||||
console.log(usage);
|
console.log(usage);
|
||||||
console.log('Options:');
|
console.log('Options:');
|
||||||
forEachKey(log(' %s %s'), bin);
|
|
||||||
|
Object.keys(bin).forEach((name) => {
|
||||||
|
console.log(' %s %s', name, bin[name]);
|
||||||
|
});
|
||||||
|
|
||||||
console.log('\nGeneral help using Cloud Commander: <%s>', url);
|
console.log('\nGeneral help using Cloud Commander: <%s>', url);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function repl() {
|
function repl() {
|
||||||
console.log('REPL mode enabled (telnet localhost 1337)');
|
console.log('REPL mode enabled (telnet localhost 1337)');
|
||||||
await import('../server/repl.js');
|
require(DIR_SERVER + 'repl');
|
||||||
}
|
}
|
||||||
|
|
||||||
async function checkUpdate() {
|
function checkUpdate() {
|
||||||
const {default: load} = await import('package-json');
|
const load = require('package-json');
|
||||||
const {version} = await load(Info.name, 'latest');
|
const noop = () => {};
|
||||||
|
|
||||||
await showUpdateInfo(version);
|
load(Info.name, 'latest')
|
||||||
|
.then(showUpdateInfo)
|
||||||
|
.catch(noop);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function showUpdateInfo(version) {
|
function showUpdateInfo(data) {
|
||||||
if (version === Info.version)
|
const version = data.version;
|
||||||
return;
|
|
||||||
|
|
||||||
const {default: chalk} = await import('chalk');
|
if (version !== Info.version) {
|
||||||
|
const chalk = require('chalk');
|
||||||
|
const rendy = require('rendy');
|
||||||
|
|
||||||
const latestVersion = chalk.green.bold(`v${version}`);
|
const latest = rendy('update available: {{ latest }}', {
|
||||||
const latest = `update available: ${latestVersion}`;
|
latest: chalk.green.bold('v' + version),
|
||||||
const current = chalk.dim(`(current: v${Info.version})`);
|
});
|
||||||
|
|
||||||
console.log('%s %s', latest, current);
|
const current = chalk.dim(rendy('(current: v{{ current }})', {
|
||||||
|
current: Info.version
|
||||||
|
}));
|
||||||
|
|
||||||
|
console.log('%s %s', latest, current);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,26 +0,0 @@
|
||||||
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();
|
|
||||||
});
|
|
||||||
29
bin/legacy.js
Executable file
29
bin/legacy.js
Executable file
|
|
@ -0,0 +1,29 @@
|
||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
const dir = path.join(__dirname, '../server');
|
||||||
|
const dir_ = path.join(__dirname, '../legacy/server');
|
||||||
|
|
||||||
|
const setDir = (name) => {
|
||||||
|
return path.join(dir_, name);
|
||||||
|
};
|
||||||
|
|
||||||
|
fs.readdirSync(dir)
|
||||||
|
.map(fillFile)
|
||||||
|
.map(writeFile);
|
||||||
|
|
||||||
|
function fillFile(name) {
|
||||||
|
return {
|
||||||
|
name: setDir(name),
|
||||||
|
data: `module.exports = require(\'../../server_/${name}\');`
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function writeFile({name, data}) {
|
||||||
|
return fs.writeFileSync(name, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -1,72 +1,70 @@
|
||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
|
|
||||||
import {promisify} from 'node:util';
|
'use strict';
|
||||||
import process from 'node:process';
|
|
||||||
import {tryToCatch} from 'try-to-catch';
|
|
||||||
import minor from 'minor';
|
|
||||||
import _place from 'place';
|
|
||||||
import {rendy} from 'rendy';
|
|
||||||
import shortdate from 'shortdate';
|
|
||||||
import Info from '../package.json' with {
|
|
||||||
type: 'json',
|
|
||||||
};
|
|
||||||
|
|
||||||
const place = promisify(_place);
|
const DIR = '../';
|
||||||
|
const Info = require(DIR + 'package');
|
||||||
|
|
||||||
await main();
|
const minor = require('minor');
|
||||||
|
const place = require('place');
|
||||||
|
const rendy = require('rendy');
|
||||||
|
const shortdate = require('shortdate');
|
||||||
|
|
||||||
async function main() {
|
const ERROR = Error('ERROR: version is missing. release --patch|--minor|--major');
|
||||||
const history = '## Version history\n\n';
|
|
||||||
const link = '//github.com/coderaiser/cloudcmd/releases/tag/';
|
|
||||||
|
|
||||||
const template = '- ' +
|
|
||||||
'*{{ date }}*, ' +
|
|
||||||
'**[v{{ version }}]' +
|
|
||||||
'(' +
|
|
||||||
link +
|
|
||||||
'v{{ version }})**\n';
|
|
||||||
|
|
||||||
const {version} = Info;
|
|
||||||
|
|
||||||
const [error, versionNew] = await tryToCatch(cl);
|
|
||||||
|
|
||||||
|
main((error) => {
|
||||||
if (error)
|
if (error)
|
||||||
return console.error(error);
|
console.error(error.message);
|
||||||
|
});
|
||||||
|
|
||||||
await replaceVersion('README.md', version, versionNew);
|
function main(callback) {
|
||||||
await replaceVersion('HELP.md', version, versionNew);
|
const history = 'Version history\n---------------\n';
|
||||||
|
const link = '//github.com/coderaiser/cloudcmd/releases/tag/';
|
||||||
|
const template = '- *{{ date }}*, ' +
|
||||||
|
'**[v{{ version }}]' +
|
||||||
|
'(' + link + 'v{{ version }})**\n';
|
||||||
|
|
||||||
const historyNew = history +
|
const version = Info.version;
|
||||||
rendy(template, {
|
|
||||||
date: shortdate(),
|
|
||||||
version: versionNew,
|
|
||||||
});
|
|
||||||
|
|
||||||
await replaceVersion('HELP.md', history, historyNew);
|
cl((error, versionNew) => {
|
||||||
|
if (error) {
|
||||||
|
callback(error);
|
||||||
|
} else {
|
||||||
|
replaceVersion('README.md', version, versionNew, callback);
|
||||||
|
replaceVersion('HELP.md', version, versionNew, function() {
|
||||||
|
const historyNew = history + rendy(template, {
|
||||||
|
date : shortdate(),
|
||||||
|
version : versionNew
|
||||||
|
});
|
||||||
|
|
||||||
|
replaceVersion('HELP.md', history, historyNew, callback);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function replaceVersion(name, version, versionNew) {
|
function replaceVersion(name, version, versionNew, callback) {
|
||||||
const [error] = await tryToCatch(place, name, version, versionNew);
|
place(name, version, versionNew, (error) => {
|
||||||
|
let msg;
|
||||||
|
|
||||||
if (error)
|
if (!error)
|
||||||
return console.error(error);
|
msg = 'done: ' + name;
|
||||||
|
|
||||||
console.log(`done: ${name}`);
|
callback(error, msg);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function cl() {
|
function cl(callback) {
|
||||||
const {argv} = process;
|
const argv = process.argv;
|
||||||
const length = argv.length - 1;
|
const length = argv.length - 1;
|
||||||
const last = process.argv[length];
|
const last = process.argv[length];
|
||||||
const regExp = /^--(major|minor|patch)$/;
|
const regExp = /^--(major|minor|patch)$/;
|
||||||
const [, match] = last.match(regExp) || [];
|
const [, match] = last.match(regExp) || [];
|
||||||
|
|
||||||
console.log(last);
|
|
||||||
|
|
||||||
if (!regExp.test(last))
|
if (!regExp.test(last))
|
||||||
throw Error('ERROR: version is missing. release --patch|--minor|--major');
|
return callback(ERROR);
|
||||||
|
|
||||||
return getVersionNew(last, match);
|
callback(null, getVersionNew(last, match));
|
||||||
}
|
}
|
||||||
|
|
||||||
function getVersionNew(last, match) {
|
function getVersionNew(last, match) {
|
||||||
|
|
@ -75,3 +73,4 @@ function getVersionNew(last, match) {
|
||||||
|
|
||||||
return last.substr(3);
|
return last.substr(3);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
40
bower.json
Normal file
40
bower.json
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
{
|
||||||
|
"name": "cloudcmd",
|
||||||
|
"homepage": "https://github.com/coderaiser/cloudcmd",
|
||||||
|
"authors": [
|
||||||
|
"coderaiser <mnemonic.enemy@gmail.com>"
|
||||||
|
],
|
||||||
|
"description": "orthodox web file manager with console and editor",
|
||||||
|
"main": "bin/cloudcmd.js",
|
||||||
|
"keywords": [
|
||||||
|
"console",
|
||||||
|
"editor",
|
||||||
|
"file",
|
||||||
|
"file manager",
|
||||||
|
"folder",
|
||||||
|
"orthodox",
|
||||||
|
"view",
|
||||||
|
"copy",
|
||||||
|
"rename",
|
||||||
|
"move",
|
||||||
|
"delete",
|
||||||
|
"file operation"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"private": true,
|
||||||
|
"ignore": [
|
||||||
|
"node_modules",
|
||||||
|
"modules",
|
||||||
|
"test"
|
||||||
|
],
|
||||||
|
"dependencies": {
|
||||||
|
"domtokenlist-shim": "~1.1.0",
|
||||||
|
"findit": "1.1.7",
|
||||||
|
"menu": "~1.0.2",
|
||||||
|
"olark": "^1.0.0",
|
||||||
|
"jquery": "3.2.1",
|
||||||
|
"fancybox": "^2.1.6",
|
||||||
|
"philip": "1.3.6",
|
||||||
|
"smalltalk": "2.3.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
634
client/client.js
634
client/client.js
|
|
@ -1,59 +1,51 @@
|
||||||
import process from 'node:process';
|
'use strict';
|
||||||
import Emitify from 'emitify';
|
|
||||||
import inherits from 'inherits';
|
const itype = require('itype/legacy');
|
||||||
import {rendy} from 'rendy';
|
const rendy = require('rendy');
|
||||||
import load from 'load.js';
|
const exec = require('execon');
|
||||||
import {tryToCatch} from 'try-to-catch';
|
const Images = require('./dom/images');
|
||||||
import {addSlashToEnd} from 'format-io';
|
const join = require('join-io/www/join');
|
||||||
import pascalCase from 'just-pascal-case';
|
|
||||||
import currify from 'currify';
|
const {
|
||||||
import {
|
|
||||||
apiURL,
|
apiURL,
|
||||||
formatMsg,
|
formatMsg,
|
||||||
buildFromJSON,
|
buildFromJSON,
|
||||||
} from '#common/cloudfunc';
|
} = require('../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$/, '');
|
/* global Util, DOM */
|
||||||
|
|
||||||
const isDev = process.env.NODE_ENV === 'development';
|
module.exports = new CloudCmdProto(Util, DOM);
|
||||||
|
|
||||||
inherits(CloudCmdProto, Emitify);
|
function CloudCmdProto(Util, DOM) {
|
||||||
|
let Key;
|
||||||
|
let Debug;
|
||||||
|
let Listeners;
|
||||||
|
|
||||||
export const createCloudCmd = ({DOM, Listeners}) => {
|
const log = (str) => {
|
||||||
return new CloudCmdProto({
|
if (!Debug)
|
||||||
DOM,
|
return;
|
||||||
Listeners,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
load.addErrorListener((e, src) => {
|
console.log(str);
|
||||||
const msg = `file ${src} could not be loaded`;
|
};
|
||||||
Images.show.error(msg);
|
|
||||||
});
|
|
||||||
|
|
||||||
function CloudCmdProto({DOM, Listeners}) {
|
|
||||||
Emitify.call(this);
|
|
||||||
|
|
||||||
const CloudCmd = this;
|
const CloudCmd = this;
|
||||||
const Info = DOM.CurrentInfo;
|
const Info = DOM.CurrentInfo;
|
||||||
|
const Storage = DOM.Storage;
|
||||||
|
const Files = DOM.Files;
|
||||||
|
|
||||||
const {Storage, Files} = DOM;
|
this.log = log;
|
||||||
|
this.PREFIX = '';
|
||||||
|
this.PREFIX_URL = '';
|
||||||
|
this.DIRCLIENT = '/dist/';
|
||||||
|
this.DIRCLIENT_MODULES = this.DIRCLIENT + 'modules/';
|
||||||
|
|
||||||
this.log = () => {
|
this.MIN_ONE_PANEL_WIDTH = 1155;
|
||||||
if (!isDev)
|
this.HOST = location.origin ||
|
||||||
return;
|
location.protocol + '//' + location.host;
|
||||||
};
|
|
||||||
this.prefix = '';
|
const TITLE = 'Cloud Commander';
|
||||||
this.prefixSocket = '';
|
this.TITLE = TITLE;
|
||||||
this.prefixURL = '';
|
|
||||||
|
|
||||||
this.MIN_ONE_PANEL_WIDTH = DOM.getCSSVar('min-one-panel-width');
|
|
||||||
this.HOST = location.origin || location.protocol + '//' + location.host;
|
|
||||||
this.sort = {
|
this.sort = {
|
||||||
left: 'name',
|
left: 'name',
|
||||||
right: 'name',
|
right: 'name',
|
||||||
|
|
@ -64,24 +56,43 @@ function CloudCmdProto({DOM, Listeners}) {
|
||||||
right: 'asc',
|
right: 'asc',
|
||||||
};
|
};
|
||||||
|
|
||||||
this.changeDir = async (path, overrides = {}) => {
|
log.enable = () => {
|
||||||
const {
|
Debug = true;
|
||||||
isRefresh,
|
};
|
||||||
panel,
|
|
||||||
history = true,
|
|
||||||
noCurrent,
|
|
||||||
currentName,
|
|
||||||
} = overrides;
|
|
||||||
|
|
||||||
const refresh = isRefresh;
|
log.disable = () => {
|
||||||
let panelChanged;
|
Debug = false;
|
||||||
|
};
|
||||||
|
|
||||||
if (!noCurrent && panel && panel !== Info.panel) {
|
const kebabToCamelCase = Util.kebabToCamelCase;
|
||||||
DOM.changePanel();
|
|
||||||
panelChanged = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
let imgPosition;
|
/**
|
||||||
|
* Функция привязываеться ко всем ссылкам и
|
||||||
|
* загружает содержимое каталогов
|
||||||
|
*
|
||||||
|
* @param params - {
|
||||||
|
* paramLink - ссылка
|
||||||
|
* needRefresh - необходимость обязательной загрузки данных с сервера
|
||||||
|
* panel
|
||||||
|
* }
|
||||||
|
* @param callback
|
||||||
|
*/
|
||||||
|
this.loadDir = (params, callback) => {
|
||||||
|
var imgPosition;
|
||||||
|
var panelChanged;
|
||||||
|
var p = params;
|
||||||
|
|
||||||
|
const refresh = p.isRefresh;
|
||||||
|
const panel = p.panel;
|
||||||
|
const history = p.history;
|
||||||
|
const noCurrent = p.noCurrent;
|
||||||
|
const currentName = p.currentName;
|
||||||
|
|
||||||
|
if (!noCurrent)
|
||||||
|
if (panel && panel !== Info.panel) {
|
||||||
|
DOM.changePanel();
|
||||||
|
panelChanged = true;
|
||||||
|
}
|
||||||
|
|
||||||
if (panelChanged || refresh || !history)
|
if (panelChanged || refresh || !history)
|
||||||
imgPosition = 'top';
|
imgPosition = 'top';
|
||||||
|
|
@ -89,50 +100,137 @@ function CloudCmdProto({DOM, Listeners}) {
|
||||||
Images.show.load(imgPosition, panel);
|
Images.show.load(imgPosition, panel);
|
||||||
|
|
||||||
/* загружаем содержимое каталога */
|
/* загружаем содержимое каталога */
|
||||||
await ajaxLoad(addSlashToEnd(path), {
|
ajaxLoad(p.path, {
|
||||||
refresh,
|
refresh,
|
||||||
history,
|
history,
|
||||||
noCurrent,
|
noCurrent,
|
||||||
currentName,
|
currentName,
|
||||||
showDotFiles: CloudCmd.config('showDotFiles'),
|
}, panel, callback);
|
||||||
}, panel);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* function load modules
|
||||||
|
* @params = {name, path, func, dobefore, arg}
|
||||||
|
*/
|
||||||
|
function loadModule(params) {
|
||||||
|
var name, path, func, doBefore,
|
||||||
|
funcName, isContain;
|
||||||
|
|
||||||
|
if (params) {
|
||||||
|
name = params.name,
|
||||||
|
path = params.path,
|
||||||
|
func = params.func,
|
||||||
|
funcName = params.funcName,
|
||||||
|
doBefore = params.dobefore;
|
||||||
|
|
||||||
|
if (path && !name)
|
||||||
|
name = kebabToCamelCase(path);
|
||||||
|
|
||||||
|
isContain = /\.js/.test(path);
|
||||||
|
|
||||||
|
if (!isContain)
|
||||||
|
path += '.js';
|
||||||
|
|
||||||
|
if (!CloudCmd[name]) {
|
||||||
|
CloudCmd[name] = (...args) => {
|
||||||
|
const prefix = CloudCmd.PREFIX;
|
||||||
|
const pathFull = prefix + CloudCmd.DIRCLIENT_MODULES + path;
|
||||||
|
|
||||||
|
exec(doBefore);
|
||||||
|
|
||||||
|
return DOM.load.js(pathFull, func ||
|
||||||
|
function(error) {
|
||||||
|
var Proto = CloudCmd[name];
|
||||||
|
|
||||||
|
if (!error && itype.function(Proto))
|
||||||
|
CloudCmd[name] = applyConstructor(Proto, args);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
CloudCmd[name][funcName] = CloudCmd[name];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* apply arguemnts to constructor
|
||||||
|
*
|
||||||
|
* @param constructor
|
||||||
|
* @param args
|
||||||
|
*/
|
||||||
|
function applyConstructor(constructor, args) {
|
||||||
|
var F = function () {
|
||||||
|
return constructor.apply(this, args);
|
||||||
|
};
|
||||||
|
|
||||||
|
F.prototype = constructor.prototype;
|
||||||
|
return new F();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Конструктор CloudClient, который
|
* Конструктор CloudClient, который
|
||||||
* выполняет весь функционал по
|
* выполняет весь функционал по
|
||||||
* инициализации
|
* инициализации
|
||||||
*/
|
*/
|
||||||
this.init = async (prefix, config) => {
|
this.init = (prefix, config) => {
|
||||||
CloudCmd.prefix = prefix;
|
const func = () => {
|
||||||
CloudCmd.prefixURL = `${prefix}${apiURL}`;
|
exec.series([
|
||||||
CloudCmd.prefixSocket = config.prefixSocket;
|
initModules,
|
||||||
CloudCmd.DIR_DIST = `${prefix}/dist`;
|
baseInit,
|
||||||
CloudCmd.DIR_MODULES = `${this.DIR_DIST}/modules`;
|
loadPlugins,
|
||||||
|
() => {
|
||||||
|
CloudCmd.route(location.hash);
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
};
|
||||||
|
|
||||||
|
const funcBefore = (callback) => {
|
||||||
|
const src = prefix + '/join:' + [
|
||||||
|
CloudCmd.DIRCLIENT_MODULES + 'polyfill.js',
|
||||||
|
'/modules/domtokenlist-shim/dist/domtokenlist.min.js',
|
||||||
|
].join(':');
|
||||||
|
|
||||||
|
DOM.loadJquery(() => {
|
||||||
|
DOM.load.js(src, callback);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
CloudCmd.PREFIX = prefix;
|
||||||
|
CloudCmd.PREFIX_URL = prefix + apiURL;
|
||||||
|
|
||||||
CloudCmd.config = (key) => config[key];
|
CloudCmd.config = (key) => config[key];
|
||||||
CloudCmd.config.if = currify((key, fn, a) => config[key] && fn(a));
|
|
||||||
CloudCmd._config = (key, value) => {
|
CloudCmd._config = (key, value) => {
|
||||||
/*
|
/*
|
||||||
* should be called from config.js only
|
* should be called from config.js only
|
||||||
* after successful update on server
|
* after successful update on server
|
||||||
*/
|
*/
|
||||||
if (key === 'password')
|
|
||||||
return;
|
|
||||||
|
|
||||||
config[key] = value;
|
config[key] = value;
|
||||||
};
|
};
|
||||||
|
|
||||||
if (config.oneFilePanel)
|
if (config.onePanelMode)
|
||||||
CloudCmd.MIN_ONE_PANEL_WIDTH = Infinity;
|
CloudCmd.MIN_ONE_PANEL_WIDTH = Infinity;
|
||||||
|
|
||||||
if (!document.body.scrollIntoViewIfNeeded)
|
exec.if(document.body.scrollIntoViewIfNeeded, func, funcBefore);
|
||||||
await load.js(`${CloudCmd.DIR_MODULES}/polyfill.js`);
|
};
|
||||||
|
|
||||||
await initModules();
|
function loadPlugins(callback) {
|
||||||
await baseInit();
|
const prefix = CloudCmd.PREFIX;
|
||||||
|
const plugins = prefix + '/plugins.js';
|
||||||
|
|
||||||
CloudCmd.route(location.hash);
|
DOM.load.js(plugins, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.join = (urls) => {
|
||||||
|
const prefix = CloudCmd.PREFIX;
|
||||||
|
|
||||||
|
if (!Array.isArray(urls))
|
||||||
|
throw Error('urls should be array!');
|
||||||
|
|
||||||
|
const noPrefixUrls = urls.map((url) => {
|
||||||
|
return url.replace(prefix, '');
|
||||||
|
});
|
||||||
|
|
||||||
|
return prefix + join(noPrefixUrls);
|
||||||
};
|
};
|
||||||
|
|
||||||
this.route = (path) => {
|
this.route = (path) => {
|
||||||
|
|
@ -142,15 +240,14 @@ function CloudCmdProto({DOM, Listeners}) {
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const [kebabModule] = query;
|
const [kebabModule] = query;
|
||||||
const module = noJS(pascalCase(kebabModule.slice(1)));
|
const module = kebabToCamelCase(kebabModule.slice(1));
|
||||||
|
|
||||||
const [, file] = query;
|
const file = query[1];
|
||||||
const current = DOM.getCurrentByName(file);
|
const current = DOM.getCurrentByName(file);
|
||||||
|
|
||||||
if (file && !current) {
|
if (file && !current) {
|
||||||
const msg = formatMsg('set current file', file, 'error');
|
const msg = formatMsg('set current file', file, 'error');
|
||||||
CloudCmd.log(msg);
|
CloudCmd.log(msg);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -158,122 +255,124 @@ function CloudCmdProto({DOM, Listeners}) {
|
||||||
CloudCmd.execFromModule(module, 'show');
|
CloudCmd.execFromModule(module, 'show');
|
||||||
};
|
};
|
||||||
|
|
||||||
this.logOut = async () => {
|
this.logOut = () => {
|
||||||
const url = CloudCmd.prefix + '/logout';
|
const url = CloudCmd.PREFIX + '/logout';
|
||||||
const error = () => document.location.reload();
|
const error = () => document.location.reload();
|
||||||
const {prefix} = CloudCmd;
|
|
||||||
|
|
||||||
await DOM.Storage.clear();
|
DOM.load.ajax({url, error});
|
||||||
unregisterSW(prefix);
|
|
||||||
DOM.load.ajax({
|
|
||||||
url,
|
|
||||||
error,
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const initModules = async () => {
|
function initModules(callback) {
|
||||||
CloudCmd.Key = Key;
|
exec.if(CloudCmd.Key, () => {
|
||||||
CloudCmd.Key.bind();
|
Key = new CloudCmd.Key();
|
||||||
|
CloudCmd.Key = Key;
|
||||||
|
Key.bind();
|
||||||
|
}, (func) => {
|
||||||
|
/* привязываем клавиши к функциям */
|
||||||
|
const path = 'key.js';
|
||||||
|
|
||||||
const [, modules] = await tryToCatch(Files.get, 'modules');
|
|
||||||
const showLoad = Images.show.load;
|
|
||||||
|
|
||||||
const doBefore = {
|
|
||||||
edit: showLoad,
|
|
||||||
menu: showLoad,
|
|
||||||
};
|
|
||||||
|
|
||||||
const load = (name, path, dobefore) => {
|
|
||||||
loadModule({
|
loadModule({
|
||||||
name,
|
|
||||||
path,
|
path,
|
||||||
dobefore,
|
func
|
||||||
});
|
});
|
||||||
};
|
});
|
||||||
|
|
||||||
if (!modules)
|
Files.get('modules', (error, modules) => {
|
||||||
return;
|
const showLoad = Images.show.load;
|
||||||
|
|
||||||
for (const module of modules.local) {
|
const doBefore = {
|
||||||
load(null, module, doBefore[module]);
|
'edit': showLoad,
|
||||||
}
|
'menu': showLoad,
|
||||||
};
|
};
|
||||||
|
|
||||||
async function saveCurrentName(currentName) {
|
const load = (name, path, dobefore) => {
|
||||||
await Storage.set('current-name', currentName);
|
const isTmpl = path === 'template';
|
||||||
|
const funcName = isTmpl ? 'get' : 'show';
|
||||||
|
|
||||||
|
loadModule({
|
||||||
|
name,
|
||||||
|
path,
|
||||||
|
dobefore,
|
||||||
|
funcName,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!modules)
|
||||||
|
modules = [];
|
||||||
|
|
||||||
|
modules.local.forEach((module) => {
|
||||||
|
load(null, module, doBefore[module]);
|
||||||
|
});
|
||||||
|
|
||||||
|
callback();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function baseInit() {
|
function baseInit(callback) {
|
||||||
const files = DOM.getFiles();
|
const files = DOM.getFiles();
|
||||||
|
|
||||||
CloudCmd.on('current-file', DOM.updateCurrentInfo);
|
|
||||||
CloudCmd.on('current-name', saveCurrentName);
|
|
||||||
|
|
||||||
const name = await Storage.get('current-name');
|
|
||||||
const currentFile = name && DOM.getCurrentByName(name) || files[0];
|
|
||||||
|
|
||||||
/* выделяем строку с первым файлом */
|
/* выделяем строку с первым файлом */
|
||||||
if (files)
|
if (files)
|
||||||
DOM.setCurrentFile(currentFile, {
|
DOM.setCurrentFile(files[0], {
|
||||||
// when hash is present
|
// when hash is present
|
||||||
// it should be handled with this.route
|
// it should be handled with this.route
|
||||||
// overwre otherwise
|
// overwre otherwise
|
||||||
history: !location.hash,
|
history: !location.hash
|
||||||
});
|
});
|
||||||
|
|
||||||
const dirPath = DOM.getCurrentDirPath();
|
const dirPath = DOM.getCurrentDirPath();
|
||||||
|
Listeners = CloudCmd.Listeners;
|
||||||
Listeners.init();
|
Listeners.init();
|
||||||
|
|
||||||
const panels = getPanels();
|
Listeners.setOnPanel('left');
|
||||||
panels.forEach(Listeners.setOnPanel);
|
Listeners.setOnPanel('right');
|
||||||
|
|
||||||
Listeners.initKeysPanel();
|
Listeners.initKeysPanel();
|
||||||
|
|
||||||
if (!CloudCmd.config('dirStorage'))
|
Storage.get(dirPath, (error, data) => {
|
||||||
return;
|
if (!data) {
|
||||||
|
data = getJSONfromFileTable();
|
||||||
|
Storage.set(dirPath, data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const data = await Storage.get(dirPath);
|
callback();
|
||||||
|
|
||||||
if (!data)
|
|
||||||
await Storage.setJson(dirPath, getJsonFromFileTable());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getPanels() {
|
this.execFromModule = (moduleName, funcName, ...args) => {
|
||||||
const panels = ['left'];
|
const obj = CloudCmd[moduleName];
|
||||||
|
const isObj = itype.object(obj);
|
||||||
|
|
||||||
if (CloudCmd.config('oneFilePanel'))
|
exec.if(isObj, () => {
|
||||||
return panels;
|
const obj = CloudCmd[moduleName];
|
||||||
|
const func = obj[funcName];
|
||||||
|
|
||||||
return [
|
func(...args);
|
||||||
...panels,
|
}, obj);
|
||||||
'right',
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
this.execFromModule = async (moduleName, funcName, ...args) => {
|
|
||||||
await CloudCmd[moduleName]();
|
|
||||||
|
|
||||||
const func = CloudCmd[moduleName][funcName];
|
|
||||||
func(...args);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
this.refresh = async (options = {}) => {
|
this.refresh = (options = {}, callback) => {
|
||||||
const {panel = Info.panel, currentName} = options;
|
if (!callback && typeof options === 'function') {
|
||||||
|
callback = options;
|
||||||
|
options = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
const panel = options.panel || Info.panel;
|
||||||
const path = DOM.getCurrentDirPath(panel);
|
const path = DOM.getCurrentDirPath(panel);
|
||||||
|
|
||||||
const isRefresh = true;
|
const isRefresh = true;
|
||||||
const history = false;
|
const history = false;
|
||||||
const noCurrent = options?.noCurrent;
|
const noCurrent = options ? options.noCurrent : false;
|
||||||
|
const currentName = options.currentName;
|
||||||
|
|
||||||
await CloudCmd.changeDir(path, {
|
CloudCmd.loadDir({
|
||||||
|
path,
|
||||||
isRefresh,
|
isRefresh,
|
||||||
history,
|
history,
|
||||||
panel,
|
panel,
|
||||||
noCurrent,
|
noCurrent,
|
||||||
currentName,
|
currentName,
|
||||||
});
|
}, callback);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -283,146 +382,177 @@ function CloudCmdProto({DOM, Listeners}) {
|
||||||
* @param options
|
* @param options
|
||||||
* { refresh, history } - необходимость обновить данные о каталоге
|
* { refresh, history } - необходимость обновить данные о каталоге
|
||||||
* @param panel
|
* @param panel
|
||||||
|
* @param callback
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
async function ajaxLoad(path, options = {}, panel) {
|
function ajaxLoad(path, options, panel, callback) {
|
||||||
const {RESTful} = DOM;
|
const create = (error, json) => {
|
||||||
|
const RESTful = DOM.RESTful;
|
||||||
|
const name = options.currentName || Info.name;
|
||||||
|
const obj = Util.json.parse(json);
|
||||||
|
const isRefresh = options.refresh;
|
||||||
|
const noCurrent = options.noCurrent;
|
||||||
|
|
||||||
CloudCmd.log(`reading dir: "${path}";`);
|
if (!isRefresh && json)
|
||||||
|
return createFileTable(obj, panel, options, callback);
|
||||||
|
|
||||||
const dirStorage = CloudCmd.config('dirStorage');
|
const position = DOM.getPanelPosition(panel);
|
||||||
const json = dirStorage && await Storage.getJson(path);
|
const sort = CloudCmd.sort[position];
|
||||||
|
const order = CloudCmd.order[position];
|
||||||
|
|
||||||
const name = options.currentName || Info.name;
|
const query = rendy('?sort={{ sort }}&order={{ order }}', {
|
||||||
const {noCurrent, refresh} = options;
|
sort,
|
||||||
|
order,
|
||||||
|
});
|
||||||
|
|
||||||
if (!refresh && json)
|
RESTful.read(path + query, 'json', (error, obj) => {
|
||||||
return await createFileTable(json, panel, options);
|
if (error)
|
||||||
|
return;
|
||||||
|
|
||||||
const position = DOM.getPanelPosition(panel);
|
Storage.set(path, obj);
|
||||||
const sort = CloudCmd.sort[position];
|
|
||||||
const order = CloudCmd.order[position];
|
|
||||||
|
|
||||||
const query = rendy('?sort={{ sort }}&order={{ order }}', {
|
options.sort = sort;
|
||||||
sort,
|
options.order = order;
|
||||||
order,
|
|
||||||
});
|
|
||||||
|
|
||||||
const [, newObj] = await RESTful.read(path + query, 'json');
|
createFileTable(obj, panel, options, () => {
|
||||||
|
if (isRefresh && !noCurrent)
|
||||||
|
DOM.setCurrentByName(name);
|
||||||
|
|
||||||
if (!newObj)
|
exec(callback);
|
||||||
// that's OK, error handled by RESTful
|
});
|
||||||
return;
|
});
|
||||||
|
};
|
||||||
|
|
||||||
options.sort = sort;
|
if (!options)
|
||||||
options.order = order;
|
options = {};
|
||||||
|
|
||||||
await createFileTable(newObj, panel, options);
|
CloudCmd.log('reading dir: "' + path + '";');
|
||||||
|
|
||||||
if (refresh && !noCurrent)
|
|
||||||
DOM.setCurrentByName(name);
|
|
||||||
|
|
||||||
if (!CloudCmd.config('dirStorage'))
|
if (!CloudCmd.config('dirStorage'))
|
||||||
return;
|
return create();
|
||||||
|
|
||||||
Storage.setJson(path, newObj);
|
Storage.get(path, create);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Функция строит файловую таблицу
|
* Функция строит файловую таблицу
|
||||||
* @param data - данные о файлах
|
* @param json - данные о файлах
|
||||||
* @param panelParam
|
* @param panelParam
|
||||||
* @param options - history, noCurrent, showDotFiles
|
* @param history
|
||||||
|
* @param callback
|
||||||
*/
|
*/
|
||||||
async function createFileTable(data, panelParam, options) {
|
function createFileTable(json, panelParam, options, callback) {
|
||||||
const {
|
var history = options.history,
|
||||||
history,
|
noCurrent = options.noCurrent,
|
||||||
noCurrent,
|
names = ['file', 'path', 'link', 'pathLink'];
|
||||||
showDotFiles,
|
|
||||||
} = options;
|
|
||||||
|
|
||||||
const names = [
|
Files.get(names, function(error, templFile, templPath, templLink, templPathLink) {
|
||||||
'file',
|
var childNodes, i,
|
||||||
'path',
|
Dialog = DOM.Dialog,
|
||||||
'link',
|
current,
|
||||||
'pathLink',
|
panel = panelParam || DOM.getPanel(),
|
||||||
];
|
|
||||||
|
|
||||||
const [error, [
|
dir = Info.dir,
|
||||||
file,
|
name = Info.name;
|
||||||
path,
|
|
||||||
link,
|
|
||||||
pathLink,
|
|
||||||
]] = await tryToCatch(Files.get, names);
|
|
||||||
|
|
||||||
if (error)
|
if (error)
|
||||||
return DOM.Dialog.alert(error.responseText);
|
return Dialog.alert(TITLE, error.responseText);
|
||||||
|
|
||||||
const panel = panelParam || DOM.getPanel();
|
childNodes = panel.childNodes;
|
||||||
const {prefix} = CloudCmd;
|
i = childNodes.length;
|
||||||
|
|
||||||
const {dir, name} = Info;
|
while (i--)
|
||||||
|
panel.removeChild(panel.lastChild);
|
||||||
|
|
||||||
const {childNodes} = panel;
|
panel.innerHTML = buildFromJSON({
|
||||||
let i = childNodes.length;
|
sort : options.sort,
|
||||||
|
order : options.order,
|
||||||
while (i--)
|
data : json,
|
||||||
panel.removeChild(panel.lastChild);
|
id : panel.id,
|
||||||
|
prefix : CloudCmd.PREFIX,
|
||||||
panel.innerHTML = buildFromJSON({
|
template : {
|
||||||
sort: options.sort,
|
file : templFile,
|
||||||
order: options.order,
|
path : templPath,
|
||||||
data,
|
pathLink : templPathLink,
|
||||||
id: panel.id,
|
link : templLink
|
||||||
prefix,
|
}
|
||||||
showDotFiles,
|
|
||||||
template: {
|
|
||||||
file,
|
|
||||||
path,
|
|
||||||
pathLink,
|
|
||||||
link,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
Listeners.setOnPanel(panel);
|
|
||||||
|
|
||||||
if (!noCurrent) {
|
|
||||||
let current;
|
|
||||||
|
|
||||||
if (name === '..' && dir !== '/')
|
|
||||||
current = DOM.getCurrentByName(dir);
|
|
||||||
|
|
||||||
if (!current)
|
|
||||||
[current] = DOM.getFiles(panel);
|
|
||||||
|
|
||||||
DOM.setCurrentFile(current, {
|
|
||||||
history,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
CloudCmd.emit('active-dir', Info.dirPath);
|
Listeners.setOnPanel(panel);
|
||||||
}
|
|
||||||
|
if (!noCurrent) {
|
||||||
|
if (name === '..' && dir !== '/')
|
||||||
|
current = DOM.getCurrentByName(dir);
|
||||||
|
|
||||||
|
if (!current)
|
||||||
|
current = DOM.getFiles(panel)[0];
|
||||||
|
|
||||||
|
DOM.setCurrentFile(current, {
|
||||||
|
history: history
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
exec(callback);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
this.goToParentDir = async () => {
|
/**
|
||||||
const {
|
* Функция генерирует JSON из html-таблицы файлов и
|
||||||
dir,
|
* используеться при первом заходе в корень
|
||||||
dirPath,
|
*/
|
||||||
parentDirPath,
|
function getJSONfromFileTable() {
|
||||||
panel,
|
var name, size, owner, mode, ret,
|
||||||
} = Info;
|
path = DOM.getCurrentDirPath(),
|
||||||
|
infoFiles = Info.files || [],
|
||||||
|
|
||||||
if (dirPath === parentDirPath)
|
fileTable = {
|
||||||
|
path : path,
|
||||||
|
files : []
|
||||||
|
},
|
||||||
|
|
||||||
|
files = fileTable.files;
|
||||||
|
|
||||||
|
[].forEach.call(infoFiles, function(current) {
|
||||||
|
name = DOM.getCurrentName(current);
|
||||||
|
size = DOM.getCurrentSize(current);
|
||||||
|
owner = DOM.getCurrentOwner(current);
|
||||||
|
mode = DOM.getCurrentMode(current);
|
||||||
|
|
||||||
|
if (name !== '..')
|
||||||
|
files.push({
|
||||||
|
name : name,
|
||||||
|
size : size,
|
||||||
|
mode : mode,
|
||||||
|
owner : owner
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
ret = Util.json.stringify(fileTable);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.goToParentDir = () => {
|
||||||
|
let path = Info.dirPath;
|
||||||
|
const dir = Info.dir;
|
||||||
|
const parentPath = Info.parentDirPath;
|
||||||
|
|
||||||
|
if (path === Info.parentDirPath)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const path = parentDirPath;
|
path = parentPath;
|
||||||
|
|
||||||
await CloudCmd.changeDir(path);
|
CloudCmd.loadDir({path}, () => {
|
||||||
|
const panel = Info.panel;
|
||||||
|
let current = DOM.getCurrentByName(dir);
|
||||||
|
|
||||||
const current = DOM.getCurrentByName(dir);
|
if (!current)
|
||||||
const [first] = DOM.getFiles(panel);
|
current = DOM.getFiles(panel)[0];
|
||||||
|
|
||||||
DOM.setCurrentFile(current || first, {
|
DOM.setCurrentFile(current, {
|
||||||
history,
|
history
|
||||||
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,44 +1,36 @@
|
||||||
import '../css/main.css';
|
'use strict';
|
||||||
import process from 'node:process';
|
|
||||||
import wraptile from 'wraptile';
|
|
||||||
import load from 'load.js';
|
|
||||||
import * as Util from '#common/util';
|
|
||||||
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';
|
// prevent additional loading of exec by spero, remedy, ishtar, salam, omnes
|
||||||
|
window.exec = require('execon');
|
||||||
|
|
||||||
export default init;
|
// prevent additional loading of emitify
|
||||||
|
window.Emitify = require('emitify/legacy');
|
||||||
|
|
||||||
globalThis.CloudCmd = init;
|
window.CloudCmd = (config) => {
|
||||||
|
window.Promise = window.Promise || require('es6-promise');
|
||||||
|
window.Util = require('../common/util');
|
||||||
|
window.CloudFunc = require('../common/cloudfunc');
|
||||||
|
|
||||||
async function init(config) {
|
const DOM = require('./dom');
|
||||||
globalThis.CloudCmd = createCloudCmd({
|
|
||||||
DOM,
|
|
||||||
Listeners,
|
|
||||||
});
|
|
||||||
globalThis.DOM = DOM;
|
|
||||||
globalThis.Util = Util;
|
|
||||||
globalThis.CloudFunc = CloudFunc;
|
|
||||||
|
|
||||||
await register(config);
|
window.DOM = DOM;
|
||||||
|
window.CloudCmd = require('./client');
|
||||||
|
|
||||||
|
const Dialog = require('./dom/dialog');
|
||||||
|
|
||||||
initSortPanel();
|
|
||||||
globalThis.CloudCmd.sortPanel = sortPanel;
|
|
||||||
const prefix = getPrefix(config.prefix);
|
const prefix = getPrefix(config.prefix);
|
||||||
|
const {htmlDialogs} = config;
|
||||||
|
|
||||||
globalThis.CloudCmd.init(prefix, config);
|
DOM.Dialog = Dialog(prefix, {
|
||||||
|
htmlDialogs
|
||||||
|
});
|
||||||
|
|
||||||
if (globalThis.CloudCmd.config('menu') === 'aleman')
|
require('./listeners');
|
||||||
setTimeout(() => {
|
require('./key');
|
||||||
import('https://esm.sh/@putout/processor-html');
|
require('./sort');
|
||||||
import('https://esm.sh/@putout/bundle@5.5');
|
|
||||||
}, 100);
|
window.CloudCmd.init(prefix, config);
|
||||||
}
|
};
|
||||||
|
|
||||||
function getPrefix(prefix) {
|
function getPrefix(prefix) {
|
||||||
if (!prefix)
|
if (!prefix)
|
||||||
|
|
@ -47,28 +39,6 @@ function getPrefix(prefix) {
|
||||||
if (!prefix.indexOf('/'))
|
if (!prefix.indexOf('/'))
|
||||||
return prefix;
|
return prefix;
|
||||||
|
|
||||||
return `/${prefix}`;
|
return '/' + prefix;
|
||||||
}
|
}
|
||||||
|
|
||||||
const onUpdateFound = wraptile(async (config) => {
|
|
||||||
if (isDev)
|
|
||||||
return;
|
|
||||||
|
|
||||||
const {DOM} = globalThis;
|
|
||||||
const prefix = getPrefix(config.prefix);
|
|
||||||
|
|
||||||
await load.js(`${prefix}/dist/cloudcmd.common.js`);
|
|
||||||
await load.js(`${prefix}/dist/cloudcmd.js`);
|
|
||||||
|
|
||||||
console.log('cloudcmd: sw: updated');
|
|
||||||
|
|
||||||
DOM.Events.removeAll();
|
|
||||||
globalThis.CloudCmd(config);
|
|
||||||
});
|
|
||||||
|
|
||||||
async function register(config) {
|
|
||||||
const {prefix} = config;
|
|
||||||
const sw = await registerSW(prefix);
|
|
||||||
|
|
||||||
listenSW(sw, 'updatefound', onUpdateFound(config));
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,124 +1,133 @@
|
||||||
/* global CloudCmd*/
|
'use strict';
|
||||||
import * as Storage from '#dom/storage';
|
|
||||||
import tryToPromiseAll from '../../common/try-to-promise-all.js';
|
|
||||||
|
|
||||||
const CLASS = 'cut-file';
|
/* global CloudCmd */
|
||||||
const COPY = 'copy';
|
|
||||||
const CUT = 'cut';
|
|
||||||
|
|
||||||
function showMessage(msg) {
|
const jonny = require('jonny');
|
||||||
globalThis.DOM.Dialog.alert(msg);
|
const exec = require('execon');
|
||||||
}
|
|
||||||
|
|
||||||
function getNames() {
|
const Storage = require('./storage');
|
||||||
const {DOM} = globalThis;
|
const DOM = require('./');
|
||||||
const files = DOM.getActiveFiles();
|
|
||||||
|
|
||||||
return DOM.getFilenames(files);
|
module.exports = new BufferProto();
|
||||||
}
|
|
||||||
|
|
||||||
function addCutClass() {
|
function BufferProto() {
|
||||||
const {DOM} = globalThis;
|
const Info = DOM.CurrentInfo;
|
||||||
const files = DOM.getActiveFiles();
|
const CLASS = 'cut-file';
|
||||||
|
const COPY = 'copy';
|
||||||
|
const CUT = 'cut';
|
||||||
|
const TITLE = 'Buffer';
|
||||||
|
|
||||||
for (const element of files) {
|
const Buffer = {
|
||||||
element.classList.add(CLASS);
|
cut : callIfEnabled.bind(null, cut),
|
||||||
|
copy : callIfEnabled.bind(null, copy),
|
||||||
|
clear : callIfEnabled.bind(null, clear),
|
||||||
|
paste : callIfEnabled.bind(null, paste)
|
||||||
|
};
|
||||||
|
|
||||||
|
function showMessage(msg) {
|
||||||
|
DOM.Dialog.alert(TITLE, msg);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
function rmCutClass() {
|
function getNames() {
|
||||||
const {DOM} = globalThis;
|
const files = DOM.getActiveFiles();
|
||||||
const files = DOM.getByClassAll(CLASS);
|
const names = DOM.getFilenames(files);
|
||||||
|
|
||||||
for (const element of files) {
|
return names;
|
||||||
element.classList.remove(CLASS);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function addCutClass() {
|
||||||
|
const files = DOM.getActiveFiles();
|
||||||
|
|
||||||
|
files.forEach((element) => {
|
||||||
|
element.classList.add(CLASS);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function rmCutClass() {
|
||||||
|
const files = DOM.getByClassAll(CLASS);
|
||||||
|
|
||||||
|
[...files].forEach((element) => {
|
||||||
|
element.classList.remove(CLASS);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function callIfEnabled(callback) {
|
||||||
|
const is = CloudCmd.config('buffer');
|
||||||
|
|
||||||
|
if (is)
|
||||||
|
return callback();
|
||||||
|
|
||||||
|
showMessage('Buffer disabled in config!');
|
||||||
|
}
|
||||||
|
|
||||||
|
function copy() {
|
||||||
|
const names = getNames();
|
||||||
|
const from = Info.dirPath;
|
||||||
|
|
||||||
|
clear();
|
||||||
|
|
||||||
|
if (!names.length)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Storage.remove(CUT)
|
||||||
|
.set(COPY, {
|
||||||
|
from : from,
|
||||||
|
names: names
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function cut() {
|
||||||
|
const names = getNames();
|
||||||
|
const from = Info.dirPath;
|
||||||
|
|
||||||
|
clear();
|
||||||
|
|
||||||
|
if (!names.length)
|
||||||
|
return;
|
||||||
|
|
||||||
|
addCutClass();
|
||||||
|
|
||||||
|
Storage
|
||||||
|
.set(CUT, {
|
||||||
|
from : from,
|
||||||
|
names: names
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function clear() {
|
||||||
|
Storage.remove(COPY)
|
||||||
|
.remove(CUT);
|
||||||
|
|
||||||
|
rmCutClass();
|
||||||
|
}
|
||||||
|
|
||||||
|
function paste() {
|
||||||
|
const copy = Storage.get.bind(Storage, COPY);
|
||||||
|
const cut = Storage.get.bind(Storage, CUT);
|
||||||
|
|
||||||
|
exec.parallel([copy, cut], function(error, cp, ct) {
|
||||||
|
const opStr = cp ? 'copy' : 'move';
|
||||||
|
const opData = cp || ct;
|
||||||
|
const Operation = CloudCmd.Operation;
|
||||||
|
const msg = 'Path is same!';
|
||||||
|
const path = Info.dirPath;
|
||||||
|
|
||||||
|
if (!error && !cp && !ct)
|
||||||
|
error = 'Buffer is empty!';
|
||||||
|
|
||||||
|
if (error)
|
||||||
|
return showMessage(error);
|
||||||
|
|
||||||
|
const data = jonny.parse(opData);
|
||||||
|
data.to = path;
|
||||||
|
|
||||||
|
if (data.from === path)
|
||||||
|
return showMessage(msg);
|
||||||
|
|
||||||
|
Operation.show(opStr, data);
|
||||||
|
clear();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return Buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
const checkEnabled = (fn) => () => {
|
|
||||||
const is = CloudCmd.config('buffer');
|
|
||||||
|
|
||||||
if (is)
|
|
||||||
return fn();
|
|
||||||
|
|
||||||
showMessage('Buffer disabled in config!');
|
|
||||||
};
|
|
||||||
|
|
||||||
async function readBuffer() {
|
|
||||||
const [e, cp, ct] = await tryToPromiseAll([
|
|
||||||
Storage.getJson(COPY),
|
|
||||||
Storage.getJson(CUT),
|
|
||||||
]);
|
|
||||||
|
|
||||||
return [
|
|
||||||
e,
|
|
||||||
cp,
|
|
||||||
ct,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
export const copy = checkEnabled(async () => {
|
|
||||||
const Info = globalThis.DOM.CurrentInfo;
|
|
||||||
const names = getNames();
|
|
||||||
const from = Info.dirPath;
|
|
||||||
|
|
||||||
await clear();
|
|
||||||
|
|
||||||
if (!names.length)
|
|
||||||
return;
|
|
||||||
|
|
||||||
await Storage.remove(CUT);
|
|
||||||
await Storage.setJson(COPY, {
|
|
||||||
from,
|
|
||||||
names,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
export const cut = checkEnabled(async () => {
|
|
||||||
const Info = globalThis.DOM.CurrentInfo;
|
|
||||||
const names = getNames();
|
|
||||||
const from = Info.dirPath;
|
|
||||||
|
|
||||||
await clear();
|
|
||||||
|
|
||||||
if (!names.length)
|
|
||||||
return;
|
|
||||||
|
|
||||||
addCutClass();
|
|
||||||
|
|
||||||
await Storage.setJson(CUT, {
|
|
||||||
from,
|
|
||||||
names,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
export const clear = checkEnabled(async () => {
|
|
||||||
await Storage.remove(COPY);
|
|
||||||
await Storage.remove(CUT);
|
|
||||||
|
|
||||||
rmCutClass();
|
|
||||||
});
|
|
||||||
|
|
||||||
export const paste = checkEnabled(async () => {
|
|
||||||
const Info = globalThis.DOM.CurrentInfo;
|
|
||||||
const [error, cp, ct] = await readBuffer();
|
|
||||||
|
|
||||||
if (error || !cp && !ct)
|
|
||||||
return showMessage(error || 'Buffer is empty!');
|
|
||||||
|
|
||||||
const opStr = cp ? 'copy' : 'move';
|
|
||||||
const data = cp || ct;
|
|
||||||
const {Operation} = CloudCmd;
|
|
||||||
const msg = 'Path is same!';
|
|
||||||
const to = Info.dirPath;
|
|
||||||
|
|
||||||
if (data.from === to)
|
|
||||||
return showMessage(msg);
|
|
||||||
|
|
||||||
Operation.show(opStr, {
|
|
||||||
...data,
|
|
||||||
to,
|
|
||||||
});
|
|
||||||
|
|
||||||
await clear();
|
|
||||||
});
|
|
||||||
|
|
|
||||||
|
|
@ -1,83 +0,0 @@
|
||||||
/* global DOM */
|
|
||||||
const SELECTED_FILE = 'selected-file';
|
|
||||||
const Cmd = {
|
|
||||||
getSelectedFiles,
|
|
||||||
isSelected,
|
|
||||||
unselectFile,
|
|
||||||
selectFile,
|
|
||||||
selectAllFiles,
|
|
||||||
toggleSelectedFile,
|
|
||||||
toggleAllSelectedFiles,
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* selected file check
|
|
||||||
*
|
|
||||||
* @param currentFile
|
|
||||||
*/
|
|
||||||
export function isSelected(currentFile) {
|
|
||||||
if (!currentFile)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
return DOM.isContainClass(currentFile, SELECTED_FILE);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* select current file
|
|
||||||
* @param currentFile
|
|
||||||
*/
|
|
||||||
export function selectFile(currentFile) {
|
|
||||||
const current = currentFile || DOM.getCurrentFile();
|
|
||||||
|
|
||||||
current.classList.add(SELECTED_FILE);
|
|
||||||
|
|
||||||
return Cmd;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function unselectFile(currentFile) {
|
|
||||||
const current = currentFile || DOM.getCurrentFile();
|
|
||||||
|
|
||||||
current.classList.remove(SELECTED_FILE);
|
|
||||||
|
|
||||||
return Cmd;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function toggleSelectedFile(currentFile) {
|
|
||||||
const current = currentFile || DOM.getCurrentFile();
|
|
||||||
const name = DOM.getCurrentName(current);
|
|
||||||
|
|
||||||
if (name === '..')
|
|
||||||
return Cmd;
|
|
||||||
|
|
||||||
current.classList.toggle(SELECTED_FILE);
|
|
||||||
|
|
||||||
return Cmd;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function toggleAllSelectedFiles() {
|
|
||||||
DOM
|
|
||||||
.getAllFiles()
|
|
||||||
.map(DOM.toggleSelectedFile);
|
|
||||||
|
|
||||||
return Cmd;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function selectAllFiles() {
|
|
||||||
DOM
|
|
||||||
.getAllFiles()
|
|
||||||
.map(DOM.selectFile);
|
|
||||||
|
|
||||||
return Cmd;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* unified way to get selected files
|
|
||||||
*
|
|
||||||
* @currentFile
|
|
||||||
*/
|
|
||||||
export function getSelectedFiles() {
|
|
||||||
const panel = DOM.getPanel();
|
|
||||||
const selected = DOM.getByClassAll(SELECTED_FILE, panel);
|
|
||||||
|
|
||||||
return Array.from(selected);
|
|
||||||
}
|
|
||||||
|
|
@ -1,311 +0,0 @@
|
||||||
/* global DOM */
|
|
||||||
/* global CloudCmd */
|
|
||||||
import createElement from '@cloudcmd/create-element';
|
|
||||||
import {getTitle, FS} from '#common/cloudfunc';
|
|
||||||
import {encode, decode} from '#common/entity';
|
|
||||||
|
|
||||||
let Title;
|
|
||||||
|
|
||||||
const CURRENT_FILE = 'current-file';
|
|
||||||
const encodeNBSP = (a) => a?.replace('\xa0', ' ');
|
|
||||||
const decodeNBSP = (a) => a?.replace(' ', '\xa0');
|
|
||||||
|
|
||||||
export const _CURRENT_FILE = CURRENT_FILE;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* set name from current (or param) file
|
|
||||||
*
|
|
||||||
* @param name
|
|
||||||
* @param current
|
|
||||||
*/
|
|
||||||
export const setCurrentName = (name, current) => {
|
|
||||||
const Info = DOM.CurrentInfo;
|
|
||||||
const {link} = Info;
|
|
||||||
const {prefix} = CloudCmd;
|
|
||||||
const dir = prefix + FS + Info.dirPath;
|
|
||||||
const encoded = encode(name);
|
|
||||||
|
|
||||||
link.title = encoded;
|
|
||||||
link.href = dir + encoded;
|
|
||||||
link.innerHTML = encoded;
|
|
||||||
|
|
||||||
current.setAttribute('data-name', createNameAttribute(name));
|
|
||||||
CloudCmd.emit('current-file', current);
|
|
||||||
|
|
||||||
return link;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* get name from current (or param) file
|
|
||||||
*
|
|
||||||
* @param currentFile
|
|
||||||
*/
|
|
||||||
export const getCurrentName = (currentFile) => {
|
|
||||||
const current = currentFile || DOM.getCurrentFile();
|
|
||||||
|
|
||||||
if (!current)
|
|
||||||
return '';
|
|
||||||
|
|
||||||
return parseNameAttribute(current.getAttribute('data-name'));
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generate a `data-name` attribute for the given filename
|
|
||||||
* @param name The string name to encode
|
|
||||||
*/
|
|
||||||
const createNameAttribute = (name) => {
|
|
||||||
const encoded = btoa(encodeURI(name));
|
|
||||||
return `js-file-${encoded}`;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parse a `data-name` attribute string back into the original filename
|
|
||||||
* @param attribute The string we wish to decode
|
|
||||||
*/
|
|
||||||
const parseNameAttribute = (attribute) => {
|
|
||||||
attribute = attribute.replace('js-file-', '');
|
|
||||||
return decodeNBSP(decodeURI(atob(attribute)));
|
|
||||||
};
|
|
||||||
|
|
||||||
export const _parseNameAttribute = parseNameAttribute;
|
|
||||||
|
|
||||||
const parseHrefAttribute = (prefix, attribute) => {
|
|
||||||
attribute = attribute.replace(RegExp('^' + prefix + FS), '');
|
|
||||||
return decode(decodeNBSP(attribute));
|
|
||||||
};
|
|
||||||
|
|
||||||
export const _parseHrefAttribute = parseHrefAttribute;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* get current directory path
|
|
||||||
*/
|
|
||||||
export const getCurrentDirPath = (panel = DOM.getPanel()) => {
|
|
||||||
const path = DOM.getByDataName('js-path', panel);
|
|
||||||
return path.textContent;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* get link from current (or param) file
|
|
||||||
*
|
|
||||||
* @param currentFile - current file by default
|
|
||||||
*/
|
|
||||||
export const getCurrentPath = (currentFile) => {
|
|
||||||
const current = currentFile || DOM.getCurrentFile();
|
|
||||||
const [element] = DOM.getByTag('a', current);
|
|
||||||
const {prefix} = CloudCmd;
|
|
||||||
|
|
||||||
return parseHrefAttribute(prefix, element.getAttribute('href'));
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* get current directory name
|
|
||||||
*/
|
|
||||||
export const getCurrentDirName = () => {
|
|
||||||
const href = DOM
|
|
||||||
.getCurrentDirPath()
|
|
||||||
.replace(/\/$/, '');
|
|
||||||
|
|
||||||
const substr = href.substr(href, href.lastIndexOf('/'));
|
|
||||||
|
|
||||||
return href.replace(`${substr}/`, '') || '/';
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* get current directory path
|
|
||||||
*/
|
|
||||||
export const getParentDirPath = (panel) => {
|
|
||||||
const path = DOM.getCurrentDirPath(panel);
|
|
||||||
const dirName = DOM.getCurrentDirName() + '/';
|
|
||||||
const index = path.lastIndexOf(dirName);
|
|
||||||
|
|
||||||
if (path === '/')
|
|
||||||
return path;
|
|
||||||
|
|
||||||
return path.slice(0, index);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* get not current directory path
|
|
||||||
*/
|
|
||||||
export const getNotCurrentDirPath = () => {
|
|
||||||
const panel = DOM.getPanel({
|
|
||||||
active: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
return DOM.getCurrentDirPath(panel);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* unified way to get current file
|
|
||||||
*
|
|
||||||
* @currentFile
|
|
||||||
*/
|
|
||||||
export const getCurrentFile = () => {
|
|
||||||
return DOM.getByClass(CURRENT_FILE);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* get current file by name
|
|
||||||
*/
|
|
||||||
export const getCurrentByName = (name, panel = DOM.CurrentInfo.panel) => {
|
|
||||||
const dataName = 'js-file-' + btoa(encodeURI(encodeNBSP(name)));
|
|
||||||
return DOM.getByDataName(dataName, panel);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* private function that unset currentFile
|
|
||||||
*
|
|
||||||
* @currentFile
|
|
||||||
*/
|
|
||||||
function unsetCurrentFile(currentFile) {
|
|
||||||
const is = DOM.isCurrentFile(currentFile);
|
|
||||||
|
|
||||||
if (!is)
|
|
||||||
return;
|
|
||||||
|
|
||||||
currentFile.classList.remove(CURRENT_FILE);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* unified way to set current file
|
|
||||||
*/
|
|
||||||
export const setCurrentFile = (currentFile, options) => {
|
|
||||||
const o = options;
|
|
||||||
const currentFileWas = DOM.getCurrentFile();
|
|
||||||
|
|
||||||
if (!currentFile)
|
|
||||||
return DOM;
|
|
||||||
|
|
||||||
let pathWas = '';
|
|
||||||
|
|
||||||
if (currentFileWas) {
|
|
||||||
pathWas = DOM.getCurrentDirPath();
|
|
||||||
unsetCurrentFile(currentFileWas);
|
|
||||||
}
|
|
||||||
|
|
||||||
currentFile.classList.add(CURRENT_FILE);
|
|
||||||
|
|
||||||
const path = DOM.getCurrentDirPath();
|
|
||||||
const name = CloudCmd.config('name');
|
|
||||||
|
|
||||||
if (path !== pathWas) {
|
|
||||||
DOM.setTitle(getTitle({
|
|
||||||
name,
|
|
||||||
path,
|
|
||||||
}));
|
|
||||||
|
|
||||||
/* history could be present
|
|
||||||
* but it should be false
|
|
||||||
* to prevent default behavior
|
|
||||||
*/
|
|
||||||
if (!o || o.history) {
|
|
||||||
const historyPath = path === '/' ? path : FS + path;
|
|
||||||
DOM.setHistory(historyPath, null, historyPath);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* scrolling to current file */
|
|
||||||
const CENTER = true;
|
|
||||||
|
|
||||||
DOM.scrollIntoViewIfNeeded(currentFile, CENTER);
|
|
||||||
|
|
||||||
CloudCmd.emit('current-file', currentFile);
|
|
||||||
CloudCmd.emit('current-path', path);
|
|
||||||
CloudCmd.emit('current-name', DOM.getCurrentName(currentFile));
|
|
||||||
|
|
||||||
return DOM;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const setCurrentByName = (name) => {
|
|
||||||
const current = DOM.getCurrentByName(name);
|
|
||||||
return DOM.setCurrentFile(current);
|
|
||||||
};
|
|
||||||
|
|
||||||
/*
|
|
||||||
* set current file by position
|
|
||||||
*
|
|
||||||
* @param layer - element
|
|
||||||
* @param - position {x, y}
|
|
||||||
*/
|
|
||||||
export const getCurrentByPosition = ({x, y}) => {
|
|
||||||
const element = document.elementFromPoint(x, y);
|
|
||||||
|
|
||||||
const getEl = (el) => {
|
|
||||||
const {tagName} = el;
|
|
||||||
const isChild = /A|SPAN|LI/.test(tagName);
|
|
||||||
|
|
||||||
if (!isChild)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
if (tagName === 'A')
|
|
||||||
return el.parentElement.parentElement;
|
|
||||||
|
|
||||||
if (tagName === 'SPAN')
|
|
||||||
return el.parentElement;
|
|
||||||
|
|
||||||
return el;
|
|
||||||
};
|
|
||||||
|
|
||||||
const el = getEl(element);
|
|
||||||
|
|
||||||
if (el && el.tagName !== 'LI')
|
|
||||||
return null;
|
|
||||||
|
|
||||||
return el;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* current file check
|
|
||||||
*
|
|
||||||
* @param currentFile
|
|
||||||
*/
|
|
||||||
export const isCurrentFile = (currentFile) => {
|
|
||||||
if (!currentFile)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
return DOM.isContainClass(currentFile, CURRENT_FILE);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* set title or create title element
|
|
||||||
*
|
|
||||||
* @param name
|
|
||||||
*/
|
|
||||||
export const setTitle = (name) => {
|
|
||||||
if (!Title)
|
|
||||||
Title = DOM.getByTag('title')[0] || createElement('title', {
|
|
||||||
innerHTML: name,
|
|
||||||
parent: document.head,
|
|
||||||
});
|
|
||||||
|
|
||||||
Title.textContent = name;
|
|
||||||
|
|
||||||
return DOM;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* check is current file is a directory
|
|
||||||
*
|
|
||||||
* @param currentFile
|
|
||||||
*/
|
|
||||||
export const isCurrentIsDir = (currentFile) => {
|
|
||||||
const current = currentFile || DOM.getCurrentFile();
|
|
||||||
const path = DOM.getCurrentPath(current);
|
|
||||||
const fileType = DOM.getCurrentType(current);
|
|
||||||
|
|
||||||
const isZip = path.endsWith('.zip');
|
|
||||||
const isDir = /^directory(-link)?/.test(fileType);
|
|
||||||
|
|
||||||
return isDir || isZip;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getCurrentType = (currentFile) => {
|
|
||||||
const current = currentFile || DOM.getCurrentFile();
|
|
||||||
const el = DOM.getByDataName('js-type', current);
|
|
||||||
|
|
||||||
const type = el.className
|
|
||||||
.split(' ')
|
|
||||||
.pop();
|
|
||||||
|
|
||||||
return type;
|
|
||||||
};
|
|
||||||
|
|
@ -1,308 +0,0 @@
|
||||||
import {test, stub} from 'supertape';
|
|
||||||
import {create} from 'auto-globals';
|
|
||||||
import wraptile from 'wraptile';
|
|
||||||
import * as currentFile from './current-file.js';
|
|
||||||
|
|
||||||
const id = (a) => a;
|
|
||||||
|
|
||||||
const returns = wraptile(id);
|
|
||||||
const {_CURRENT_FILE} = currentFile;
|
|
||||||
|
|
||||||
test('current-file: setCurrentName: setAttribute', (t) => {
|
|
||||||
const {DOM, CloudCmd} = globalThis;
|
|
||||||
|
|
||||||
globalThis.DOM = getDOM();
|
|
||||||
globalThis.CloudCmd = getCloudCmd();
|
|
||||||
|
|
||||||
const current = create();
|
|
||||||
const {setAttribute} = current;
|
|
||||||
|
|
||||||
currentFile.setCurrentName('hello', current);
|
|
||||||
|
|
||||||
globalThis.DOM = DOM;
|
|
||||||
globalThis.CloudCmd = CloudCmd;
|
|
||||||
|
|
||||||
t.calledWith(setAttribute, [
|
|
||||||
'data-name',
|
|
||||||
'js-file-aGVsbG8=',
|
|
||||||
], 'should call setAttribute');
|
|
||||||
t.end();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('current-file: setCurrentName: setAttribute: cyrillic', (t) => {
|
|
||||||
const {DOM, CloudCmd} = globalThis;
|
|
||||||
|
|
||||||
globalThis.DOM = getDOM();
|
|
||||||
globalThis.CloudCmd = getCloudCmd();
|
|
||||||
|
|
||||||
const current = create();
|
|
||||||
const {setAttribute} = current;
|
|
||||||
|
|
||||||
currentFile.setCurrentName('ай', current);
|
|
||||||
|
|
||||||
globalThis.DOM = DOM;
|
|
||||||
globalThis.CloudCmd = CloudCmd;
|
|
||||||
|
|
||||||
t.calledWith(setAttribute, [
|
|
||||||
'data-name',
|
|
||||||
'js-file-JUQwJUIwJUQwJUI5',
|
|
||||||
], 'should call setAttribute');
|
|
||||||
t.end();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('current-file: getCurrentName', (t) => {
|
|
||||||
const current = create();
|
|
||||||
current.getAttribute.returns('js-file-Ymlu');
|
|
||||||
|
|
||||||
const result = currentFile.getCurrentName(current);
|
|
||||||
|
|
||||||
t.equal(result, 'bin');
|
|
||||||
t.end();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('current-file: emit', (t) => {
|
|
||||||
const {DOM, CloudCmd} = globalThis;
|
|
||||||
|
|
||||||
const emit = stub();
|
|
||||||
|
|
||||||
globalThis.DOM = getDOM();
|
|
||||||
globalThis.CloudCmd = getCloudCmd({
|
|
||||||
emit,
|
|
||||||
});
|
|
||||||
|
|
||||||
const current = create();
|
|
||||||
|
|
||||||
currentFile.setCurrentName('hello', current);
|
|
||||||
|
|
||||||
globalThis.DOM = DOM;
|
|
||||||
globalThis.CloudCmd = CloudCmd;
|
|
||||||
|
|
||||||
t.calledWith(emit, ['current-file', current], 'should call emit');
|
|
||||||
t.end();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('current-file: setCurrentName: return', (t) => {
|
|
||||||
const {DOM, CloudCmd} = globalThis;
|
|
||||||
|
|
||||||
const link = {};
|
|
||||||
|
|
||||||
globalThis.DOM = getDOM({
|
|
||||||
link,
|
|
||||||
});
|
|
||||||
|
|
||||||
globalThis.CloudCmd = getCloudCmd();
|
|
||||||
|
|
||||||
const current = create();
|
|
||||||
const result = currentFile.setCurrentName('hello', current);
|
|
||||||
|
|
||||||
globalThis.DOM = DOM;
|
|
||||||
globalThis.CloudCmd = CloudCmd;
|
|
||||||
|
|
||||||
t.equal(result, link, 'should return link');
|
|
||||||
t.end();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('current-file: getParentDirPath: result', (t) => {
|
|
||||||
const {DOM} = globalThis;
|
|
||||||
|
|
||||||
const getCurrentDirPath = returns('/D/Films/+++favorite films/');
|
|
||||||
const getCurrentDirName = returns('+++favorite films');
|
|
||||||
|
|
||||||
globalThis.DOM = getDOM({
|
|
||||||
getCurrentDirPath,
|
|
||||||
getCurrentDirName,
|
|
||||||
});
|
|
||||||
|
|
||||||
const result = currentFile.getParentDirPath();
|
|
||||||
const expected = '/D/Films/';
|
|
||||||
|
|
||||||
globalThis.DOM = DOM;
|
|
||||||
|
|
||||||
t.equal(result, expected, 'should return parent dir path');
|
|
||||||
t.end();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('current-file: isCurrentFile: no', (t) => {
|
|
||||||
const {DOM, CloudCmd} = globalThis;
|
|
||||||
|
|
||||||
globalThis.DOM = getDOM();
|
|
||||||
globalThis.CloudCmd = getCloudCmd();
|
|
||||||
|
|
||||||
const result = currentFile.isCurrentFile();
|
|
||||||
|
|
||||||
globalThis.DOM = DOM;
|
|
||||||
globalThis.CloudCmd = CloudCmd;
|
|
||||||
|
|
||||||
t.notOk(result);
|
|
||||||
t.end();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('current-file: isCurrentFile', (t) => {
|
|
||||||
const {DOM, CloudCmd} = globalThis;
|
|
||||||
|
|
||||||
const isContainClass = stub();
|
|
||||||
|
|
||||||
globalThis.DOM = getDOM({
|
|
||||||
isContainClass,
|
|
||||||
});
|
|
||||||
|
|
||||||
globalThis.CloudCmd = getCloudCmd();
|
|
||||||
|
|
||||||
const current = {};
|
|
||||||
currentFile.isCurrentFile(current);
|
|
||||||
|
|
||||||
globalThis.DOM = DOM;
|
|
||||||
globalThis.CloudCmd = CloudCmd;
|
|
||||||
|
|
||||||
t.calledWith(isContainClass, [current, _CURRENT_FILE], 'should call isContainClass');
|
|
||||||
t.end();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('current-file: getCurrentType', (t) => {
|
|
||||||
const {DOM, CloudCmd} = globalThis;
|
|
||||||
|
|
||||||
globalThis.DOM = getDOM();
|
|
||||||
globalThis.CloudCmd = getCloudCmd();
|
|
||||||
|
|
||||||
const {getByDataName} = globalThis.DOM;
|
|
||||||
|
|
||||||
getByDataName.returns({
|
|
||||||
className: 'mini-icon directory',
|
|
||||||
});
|
|
||||||
|
|
||||||
const current = create();
|
|
||||||
|
|
||||||
currentFile.getCurrentType(current);
|
|
||||||
|
|
||||||
globalThis.DOM = DOM;
|
|
||||||
globalThis.CloudCmd = CloudCmd;
|
|
||||||
|
|
||||||
t.calledWith(getByDataName, ['js-type', current]);
|
|
||||||
t.end();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('current-file: isCurrentIsDir: getCurrentType', (t) => {
|
|
||||||
const {DOM, CloudCmd} = globalThis;
|
|
||||||
|
|
||||||
globalThis.DOM = getDOM();
|
|
||||||
globalThis.CloudCmd = getCloudCmd();
|
|
||||||
|
|
||||||
const {getCurrentType} = globalThis.DOM;
|
|
||||||
|
|
||||||
const current = create();
|
|
||||||
|
|
||||||
currentFile.isCurrentIsDir(current);
|
|
||||||
|
|
||||||
globalThis.DOM = DOM;
|
|
||||||
globalThis.CloudCmd = CloudCmd;
|
|
||||||
|
|
||||||
t.calledWith(getCurrentType, [current]);
|
|
||||||
t.end();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('current-file: isCurrentIsDir: directory', (t) => {
|
|
||||||
const {DOM, CloudCmd} = globalThis;
|
|
||||||
|
|
||||||
globalThis.DOM = getDOM({
|
|
||||||
getCurrentType: stub().returns('directory'),
|
|
||||||
});
|
|
||||||
|
|
||||||
globalThis.CloudCmd = getCloudCmd();
|
|
||||||
|
|
||||||
const current = create();
|
|
||||||
|
|
||||||
const result = currentFile.isCurrentIsDir(current);
|
|
||||||
|
|
||||||
globalThis.DOM = DOM;
|
|
||||||
globalThis.CloudCmd = CloudCmd;
|
|
||||||
|
|
||||||
t.ok(result);
|
|
||||||
t.end();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('current-file: isCurrentIsDir: directory-link', (t) => {
|
|
||||||
const {DOM, CloudCmd} = globalThis;
|
|
||||||
|
|
||||||
globalThis.DOM = getDOM({
|
|
||||||
getCurrentType: stub().returns('directory-link'),
|
|
||||||
});
|
|
||||||
|
|
||||||
globalThis.CloudCmd = getCloudCmd();
|
|
||||||
|
|
||||||
const current = create();
|
|
||||||
|
|
||||||
const result = currentFile.isCurrentIsDir(current);
|
|
||||||
|
|
||||||
globalThis.DOM = DOM;
|
|
||||||
globalThis.CloudCmd = CloudCmd;
|
|
||||||
|
|
||||||
t.ok(result);
|
|
||||||
t.end();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('current-file: isCurrentIsDir: file', (t) => {
|
|
||||||
const {DOM, CloudCmd} = globalThis;
|
|
||||||
|
|
||||||
globalThis.DOM = getDOM({
|
|
||||||
getCurrentType: stub().returns('file'),
|
|
||||||
});
|
|
||||||
|
|
||||||
globalThis.CloudCmd = getCloudCmd();
|
|
||||||
|
|
||||||
const current = create();
|
|
||||||
|
|
||||||
const result = currentFile.isCurrentIsDir(current);
|
|
||||||
|
|
||||||
globalThis.DOM = DOM;
|
|
||||||
globalThis.CloudCmd = CloudCmd;
|
|
||||||
|
|
||||||
t.notOk(result);
|
|
||||||
t.end();
|
|
||||||
});
|
|
||||||
|
|
||||||
const getCloudCmd = ({emit} = {}) => ({
|
|
||||||
prefix: '',
|
|
||||||
emit: emit || stub(),
|
|
||||||
});
|
|
||||||
|
|
||||||
test('current-file: parseNameAttribute', (t) => {
|
|
||||||
const result = currentFile._parseNameAttribute('js-file-aGVsbG8mbmJzcDt3b3JsZA==');
|
|
||||||
const expected = 'hello\xa0world';
|
|
||||||
|
|
||||||
t.equal(result, expected);
|
|
||||||
t.end();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('current-file: parseHrefAttribute', (t) => {
|
|
||||||
const prefix = '/api/v1';
|
|
||||||
const result = currentFile._parseHrefAttribute(prefix, '/api/v1/fs/hello world');
|
|
||||||
const expected = '/hello\xa0world';
|
|
||||||
|
|
||||||
t.equal(result, expected);
|
|
||||||
t.end();
|
|
||||||
});
|
|
||||||
|
|
||||||
function getDOM(overrides = {}) {
|
|
||||||
const {
|
|
||||||
link = {},
|
|
||||||
getCurrentDirPath = stub(),
|
|
||||||
getCurrentDirName = stub(),
|
|
||||||
getByDataName = stub(),
|
|
||||||
isContainClass = stub(),
|
|
||||||
getCurrentType = stub(),
|
|
||||||
getCurrentPath = stub().returns(''),
|
|
||||||
} = overrides;
|
|
||||||
|
|
||||||
return {
|
|
||||||
getCurrentDirPath,
|
|
||||||
getCurrentDirName,
|
|
||||||
getCurrentPath,
|
|
||||||
getByDataName,
|
|
||||||
isContainClass,
|
|
||||||
getCurrentType,
|
|
||||||
CurrentInfo: {
|
|
||||||
link,
|
|
||||||
dirPath: '/',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
@ -1,18 +1,56 @@
|
||||||
import {tryToCatch} from 'try-to-catch';
|
/* global DOM */
|
||||||
import * as smalltalk from 'smalltalk';
|
/* global smalltalk */
|
||||||
|
|
||||||
const title = 'Cloud Commander';
|
'use strict';
|
||||||
|
|
||||||
export const alert = (...a) => smalltalk.alert(title, ...a, {
|
module.exports = Dialog;
|
||||||
cancel: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const prompt = (...a) => tryToCatch(smalltalk.prompt, title, ...a);
|
function Dialog(prefix, config) {
|
||||||
export const confirm = (...a) => tryToCatch(smalltalk.confirm, title, ...a);
|
if (!(this instanceof Dialog))
|
||||||
export const progress = (...a) => smalltalk.progress(title, ...a);
|
return new Dialog(prefix, config);
|
||||||
|
|
||||||
|
load(config.htmlDialogs);
|
||||||
|
|
||||||
|
function getJsName(htmlDialogs) {
|
||||||
|
const is = window.Promise;
|
||||||
|
const js = '.min.js';
|
||||||
|
const jsName = is ? js : '.poly' + js;
|
||||||
|
|
||||||
|
if (!htmlDialogs)
|
||||||
|
return '.native' + jsName;
|
||||||
|
|
||||||
|
return jsName;
|
||||||
|
}
|
||||||
|
|
||||||
|
function load(htmlDialogs) {
|
||||||
|
const noop = () => {};
|
||||||
|
const name = 'smalltalk';
|
||||||
|
const dir = '/modules/' + name + '/dist/';
|
||||||
|
const jsName = getJsName(htmlDialogs);
|
||||||
|
|
||||||
|
const names = [jsName, '.min.css'].map((ext) => {
|
||||||
|
return prefix + dir + name + ext;
|
||||||
|
});
|
||||||
|
|
||||||
|
DOM.load.parallel(names, noop);
|
||||||
|
}
|
||||||
|
|
||||||
|
const alert = (title, message) => {
|
||||||
|
return smalltalk.alert(title, message);
|
||||||
|
};
|
||||||
|
|
||||||
|
this.alert = alert;
|
||||||
|
|
||||||
|
this.prompt = (title, message, value, options) => {
|
||||||
|
return smalltalk.prompt(title, message, value, options);
|
||||||
|
};
|
||||||
|
|
||||||
|
this.confirm = (title, message, options) => {
|
||||||
|
return smalltalk.confirm(title, message, options);
|
||||||
|
};
|
||||||
|
|
||||||
|
this.alert.noFiles = (title) => {
|
||||||
|
return alert(title, 'No files selected!');
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
alert.noFiles = () => {
|
|
||||||
return smalltalk.alert(title, 'No files selected!', {
|
|
||||||
cancel: false,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
|
||||||
|
|
@ -1,64 +1,90 @@
|
||||||
/* global DOM, CloudCmd */
|
/* global CloudCmd */
|
||||||
import philip from 'philip';
|
/* global DOM */
|
||||||
import * as Dialog from '#dom/dialog';
|
|
||||||
import {FS} from '#common/cloudfunc';
|
'use strict';
|
||||||
import * as Images from '#dom/images';
|
|
||||||
|
const Images = require('./images');
|
||||||
|
const {FS} = require('../../common/cloudfunc');
|
||||||
|
|
||||||
|
module.exports = (items) => {
|
||||||
|
const Info = DOM.CurrentInfo;
|
||||||
|
const load = DOM.load;
|
||||||
|
const Dialog = DOM.Dialog;
|
||||||
|
|
||||||
export const uploadDirectory = (items) => {
|
|
||||||
if (items.length)
|
if (items.length)
|
||||||
Images.show('top');
|
Images.show('top');
|
||||||
|
|
||||||
const entries = Array
|
const entries = [...items].map((item) => {
|
||||||
.from(items)
|
return item.webkitGetAsEntry();
|
||||||
.map((item) => item.webkitGetAsEntry());
|
|
||||||
|
|
||||||
const dirPath = DOM.getCurrentDirPath();
|
|
||||||
const path = dirPath.replace(/\/$/, '');
|
|
||||||
|
|
||||||
const progress = Dialog.progress('Uploading...');
|
|
||||||
|
|
||||||
progress.catch(() => {
|
|
||||||
Dialog.alert('Upload aborted');
|
|
||||||
uploader.abort();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const uploader = philip(entries, (type, name, data, i, n, callback) => {
|
const addDir = (name) => {
|
||||||
const {prefixURL} = CloudCmd;
|
return `/modules/${name}/lib/${name}.js`;
|
||||||
const full = prefixURL + FS + path + name;
|
};
|
||||||
|
|
||||||
let upload;
|
const array = [
|
||||||
switch(type) {
|
'findit',
|
||||||
case 'file':
|
'philip'
|
||||||
upload = uploadFile(full, data);
|
];
|
||||||
break;
|
|
||||||
|
|
||||||
case 'directory':
|
const url = CloudCmd.join(array.map(addDir));
|
||||||
upload = uploadDir(full);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
upload.on('end', callback);
|
load.js(url, () => {
|
||||||
|
const path = Info.dirPath
|
||||||
|
.replace(/\/$/, '');
|
||||||
|
|
||||||
upload.on('progress', (count) => {
|
const uploader = window.philip(entries, (type, name, data, i, n, callback) => {
|
||||||
const current = percent(i, n);
|
const prefixURL = CloudCmd.PREFIX_URL;
|
||||||
const next = percent(i + 1, n);
|
const full = prefixURL + FS + path + name;
|
||||||
const max = next - current;
|
|
||||||
const value = current + percent(count, 100, max);
|
|
||||||
|
|
||||||
progress.setProgress(value);
|
let upload;
|
||||||
|
switch(type) {
|
||||||
|
case 'file':
|
||||||
|
upload = uploadFile(full, data);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'directory':
|
||||||
|
upload = uploadDir(full);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
upload.on('end', callback);
|
||||||
|
|
||||||
|
upload.on('progress', (count) => {
|
||||||
|
const current = percent(i, n);
|
||||||
|
const next = percent(i + 1, n);
|
||||||
|
const max = next - current;
|
||||||
|
const value = current + percent(count, 100, max);
|
||||||
|
|
||||||
|
setProgress(value);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
uploader.on('error', (error) => {
|
uploader.on('error', (error) => {
|
||||||
Dialog.alert(error);
|
Dialog.alert(error);
|
||||||
uploader.abort();
|
uploader.abort();
|
||||||
});
|
});
|
||||||
|
|
||||||
uploader.on('end', CloudCmd.refresh);
|
uploader.on('progress', setProgress);
|
||||||
|
uploader.on('end', CloudCmd.refresh);
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const percent = (i, n, per = 100) => Math.round(i * per / n);
|
function percent(i, n, per = 100) {
|
||||||
|
return Math.round(i * per / n);
|
||||||
|
}
|
||||||
|
|
||||||
const uploadFile = (url, data) => DOM.load.put(url, data);
|
function setProgress(count) {
|
||||||
|
DOM.Images
|
||||||
|
.setProgress(count)
|
||||||
|
.show('top');
|
||||||
|
}
|
||||||
|
|
||||||
|
function uploadFile(url, data) {
|
||||||
|
return DOM.load.put(url, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
function uploadDir(url) {
|
||||||
|
return DOM.load.put(url + '?dir');
|
||||||
|
}
|
||||||
|
|
||||||
const uploadDir = (url) => DOM.load.put(`${url}?dir`);
|
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,6 @@
|
||||||
import currify from 'currify';
|
'use strict';
|
||||||
|
|
||||||
const DOM = {
|
const DOM = module.exports;
|
||||||
show,
|
|
||||||
hide,
|
|
||||||
getByClass,
|
|
||||||
getByClassAll,
|
|
||||||
getByDataName,
|
|
||||||
getById,
|
|
||||||
getByTag,
|
|
||||||
isContainClass,
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* check class of element
|
* check class of element
|
||||||
|
|
@ -17,76 +8,71 @@ const DOM = {
|
||||||
* @param element
|
* @param element
|
||||||
* @param className
|
* @param className
|
||||||
*/
|
*/
|
||||||
export function isContainClass(element, className) {
|
module.exports.isContainClass = (element, className) => {
|
||||||
if (!element)
|
if (!element)
|
||||||
throw Error('element could not be empty!');
|
throw Error('element could not be empty!');
|
||||||
|
|
||||||
if (!className)
|
if (!className)
|
||||||
throw Error('className could not be empty!');
|
throw Error('className could not be empty!');
|
||||||
|
|
||||||
if (Array.isArray(className))
|
const classList = element.classList;
|
||||||
return className.some(currify(
|
const ret = classList.contains(className);
|
||||||
isContainClass,
|
|
||||||
element,
|
|
||||||
));
|
|
||||||
|
|
||||||
const {classList} = element;
|
return ret;
|
||||||
|
};
|
||||||
return classList.contains(className);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Function search element by tag
|
* Function search element by tag
|
||||||
* @param tag - className
|
* @param tag - className
|
||||||
* @param element - element
|
* @param element - element
|
||||||
*/
|
*/
|
||||||
export function getByTag(tag, element = document) {
|
module.exports.getByTag = (tag, element = document) => {
|
||||||
return element.getElementsByTagName(tag);
|
return element.getElementsByTagName(tag);
|
||||||
}
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Function search element by id
|
* Function search element by id
|
||||||
* @param id
|
* @param Id - id
|
||||||
* @param element
|
|
||||||
*/
|
*/
|
||||||
export function getById(id, element = document) {
|
module.exports.getById = (id, element = document) => {
|
||||||
return element.querySelector(`#${id}`);
|
return element.querySelector('#' + id);
|
||||||
}
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Function search first element by class name
|
* Function search first element by class name
|
||||||
* @param className - className
|
* @param className - className
|
||||||
* @param element - element
|
* @param element - element
|
||||||
*/
|
*/
|
||||||
export function getByClass(className, element = document) {
|
module.exports.getByClass = (className, element = document) => {
|
||||||
return DOM.getByClassAll(className, element)[0];
|
return DOM.getByClassAll(className, element)[0];
|
||||||
}
|
};
|
||||||
|
|
||||||
export function getByDataName(attribute, element = document) {
|
module.exports.getByDataName = (attribute, element = document) => {
|
||||||
const selector = '[' + 'data-name="' + attribute + '"]';
|
const selector = '[' + 'data-name="' + attribute + '"]';
|
||||||
return element.querySelector(selector);
|
return element.querySelector(selector);
|
||||||
}
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Function search element by class name
|
* Function search element by class name
|
||||||
* @param className
|
* @param pClass - className
|
||||||
* @param element
|
* @param element - element
|
||||||
*/
|
*/
|
||||||
export function getByClassAll(className, element) {
|
module.exports.getByClassAll = (className, element) => {
|
||||||
return (element || document).getElementsByClassName(className);
|
return (element || document).getElementsByClassName(className);
|
||||||
}
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* add class=hidden to element
|
* add class=hidden to element
|
||||||
*
|
*
|
||||||
* @param element
|
* @param element
|
||||||
*/
|
*/
|
||||||
export function hide(element) {
|
module.exports.hide = (element) => {
|
||||||
element.classList.add('hidden');
|
element.classList.add('hidden');
|
||||||
return DOM;
|
return DOM;
|
||||||
}
|
};
|
||||||
|
|
||||||
export function show(element) {
|
module.exports.show = (element) => {
|
||||||
element.classList.remove('hidden');
|
element.classList.remove('hidden');
|
||||||
return DOM;
|
return DOM;
|
||||||
}
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,152 +0,0 @@
|
||||||
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();
|
|
||||||
});
|
|
||||||
309
client/dom/events.js
Normal file
309
client/dom/events.js
Normal file
|
|
@ -0,0 +1,309 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const itype = require('itype/legacy');
|
||||||
|
|
||||||
|
module.exports = new EventsProto();
|
||||||
|
|
||||||
|
function EventsProto() {
|
||||||
|
const Events = this;
|
||||||
|
|
||||||
|
function parseArgs(eventName, element, listener, callback) {
|
||||||
|
let isFunc;
|
||||||
|
|
||||||
|
const EVENT_NAME = 1;
|
||||||
|
const ELEMENT = 0;
|
||||||
|
const type = itype(eventName);
|
||||||
|
|
||||||
|
switch(type) {
|
||||||
|
default:
|
||||||
|
if (!/element$/.test(type))
|
||||||
|
throw Error('unknown eventName: ' + type);
|
||||||
|
|
||||||
|
parseArgs(
|
||||||
|
arguments[EVENT_NAME],
|
||||||
|
arguments[ELEMENT],
|
||||||
|
listener,
|
||||||
|
callback
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'string':
|
||||||
|
isFunc = itype.function(element);
|
||||||
|
|
||||||
|
if (isFunc) {
|
||||||
|
listener = element;
|
||||||
|
element = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!element)
|
||||||
|
element = window;
|
||||||
|
|
||||||
|
callback(element, [
|
||||||
|
eventName,
|
||||||
|
listener,
|
||||||
|
false
|
||||||
|
]);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'array':
|
||||||
|
eventName.forEach((eventName) => {
|
||||||
|
parseArgs(
|
||||||
|
eventName,
|
||||||
|
element,
|
||||||
|
listener,
|
||||||
|
callback
|
||||||
|
);
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'object':
|
||||||
|
Object.keys(eventName).forEach((name) => {
|
||||||
|
const eventListener = eventName[name];
|
||||||
|
|
||||||
|
parseArgs(
|
||||||
|
name,
|
||||||
|
element,
|
||||||
|
eventListener,
|
||||||
|
callback
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* safe add event listener
|
||||||
|
*
|
||||||
|
* @param type
|
||||||
|
* @param element {document by default}
|
||||||
|
* @param listener
|
||||||
|
*/
|
||||||
|
this.add = (type, element, listener) => {
|
||||||
|
checkType(type);
|
||||||
|
|
||||||
|
parseArgs(type, element, listener, function(element, args) {
|
||||||
|
element.addEventListener.apply(element, args);
|
||||||
|
});
|
||||||
|
|
||||||
|
return Events;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* safe add event listener
|
||||||
|
*
|
||||||
|
* @param type
|
||||||
|
* @param listener
|
||||||
|
* @param element {document by default}
|
||||||
|
*/
|
||||||
|
this.addOnce = (type, element, listener) => {
|
||||||
|
var once = (event) => {
|
||||||
|
Events.remove(type, element, once);
|
||||||
|
listener(event);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!listener) {
|
||||||
|
listener = element;
|
||||||
|
element = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.add(type, element, once);
|
||||||
|
|
||||||
|
return Events;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* safe remove event listener
|
||||||
|
*
|
||||||
|
* @param type
|
||||||
|
* @param listener
|
||||||
|
* @param element {document by default}
|
||||||
|
*/
|
||||||
|
this.remove = (type, element, listener) => {
|
||||||
|
checkType(type);
|
||||||
|
|
||||||
|
parseArgs(type, element, listener, (element, args) => {
|
||||||
|
element.removeEventListener.apply(element, args);
|
||||||
|
});
|
||||||
|
|
||||||
|
return Events;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* safe add event keydown listener
|
||||||
|
*
|
||||||
|
* @param listener
|
||||||
|
*/
|
||||||
|
this.addKey = function(...argsArr) {
|
||||||
|
const name = 'keydown';
|
||||||
|
const args = [name].concat(argsArr);
|
||||||
|
|
||||||
|
return this.add(...args);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* safe remove event click listener
|
||||||
|
*
|
||||||
|
* @param listener
|
||||||
|
*/
|
||||||
|
this.rmKey = function(...argsArr) {
|
||||||
|
const name = 'keydown';
|
||||||
|
const args = [name].concat(argsArr);
|
||||||
|
|
||||||
|
return this.remove(...args);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* safe add event click listener
|
||||||
|
*
|
||||||
|
* @param listener
|
||||||
|
*/
|
||||||
|
this.addClick = function(...argsArr) {
|
||||||
|
const name = 'click';
|
||||||
|
const args = [name].concat(argsArr);
|
||||||
|
|
||||||
|
return this.add(...args);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* safe remove event click listener
|
||||||
|
*
|
||||||
|
* @param listener
|
||||||
|
*/
|
||||||
|
this.rmClick = function(...argsArr) {
|
||||||
|
const name = 'click';
|
||||||
|
const args = [name].concat(argsArr);
|
||||||
|
|
||||||
|
return this.remove(...args);
|
||||||
|
};
|
||||||
|
|
||||||
|
this.addContextMenu = function(...argsArr) {
|
||||||
|
const name = 'contextmenu';
|
||||||
|
const args = [name].concat(argsArr);
|
||||||
|
|
||||||
|
return this.add(...args);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* safe add event click listener
|
||||||
|
*
|
||||||
|
* @param listener
|
||||||
|
*/
|
||||||
|
this.addError = function(...argsArr) {
|
||||||
|
const name = 'error';
|
||||||
|
const args = [name].concat(argsArr);
|
||||||
|
|
||||||
|
return this.add(...args);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* safe add load click listener
|
||||||
|
*
|
||||||
|
* @param listener
|
||||||
|
*/
|
||||||
|
this.addLoad = function(...argsArr) {
|
||||||
|
const name = 'load';
|
||||||
|
const args = [name].concat(argsArr);
|
||||||
|
|
||||||
|
return this.add(...args);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* crossbrowser create event
|
||||||
|
*
|
||||||
|
* @param eventName
|
||||||
|
* @param keyCode - not necessarily
|
||||||
|
*/
|
||||||
|
this.create = function(eventName, keyCode) {
|
||||||
|
const event = document.createEvent('Event');
|
||||||
|
|
||||||
|
event.initEvent(eventName, true, true);
|
||||||
|
|
||||||
|
if (keyCode)
|
||||||
|
event.keyCode = keyCode;
|
||||||
|
|
||||||
|
event.isDefaultPrevented = function() {
|
||||||
|
return this.defaultPrevented;
|
||||||
|
};
|
||||||
|
|
||||||
|
return event;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* create keydown event
|
||||||
|
*
|
||||||
|
* @param keyCode
|
||||||
|
*/
|
||||||
|
this.createKey = function(keyCode) {
|
||||||
|
return this.create('keydown', keyCode);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* create click event
|
||||||
|
*/
|
||||||
|
this.createClick = function() {
|
||||||
|
return this.create('click');
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* create click event
|
||||||
|
*/
|
||||||
|
this.createDblClick = function() {
|
||||||
|
return this.create('dblclick');
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* dispatch event
|
||||||
|
*
|
||||||
|
* @param event
|
||||||
|
*/
|
||||||
|
this.dispatch = (event, element) => {
|
||||||
|
let customEvent;
|
||||||
|
const isStr = itype.string(event);
|
||||||
|
|
||||||
|
if (isStr)
|
||||||
|
customEvent = Events.create(event);
|
||||||
|
else
|
||||||
|
customEvent = event;
|
||||||
|
|
||||||
|
return (element || window).dispatchEvent(customEvent);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* dispatch keydown event
|
||||||
|
*
|
||||||
|
* @param keyCode
|
||||||
|
* @param element
|
||||||
|
*/
|
||||||
|
this.dispatchKey = function(keyCode, element) {
|
||||||
|
const event = this.createKey(keyCode);
|
||||||
|
return this.dispatch(event, element);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* dispatch click event
|
||||||
|
*
|
||||||
|
* @param element
|
||||||
|
*/
|
||||||
|
this.dispatchClick = function(element) {
|
||||||
|
const event = this.createClick();
|
||||||
|
return this.dispatch(event, element);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* dispatch dblclick event
|
||||||
|
*
|
||||||
|
* @param element
|
||||||
|
*/
|
||||||
|
this.dispatchDblClick = function(element) {
|
||||||
|
const event = this.createDblClick();
|
||||||
|
|
||||||
|
return this.dispatch(event, element);
|
||||||
|
};
|
||||||
|
|
||||||
|
function checkType(type) {
|
||||||
|
if (!type)
|
||||||
|
throw Error('type could not be empty!');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
let list = [];
|
|
||||||
|
|
||||||
export const add = (el, name, fn) => {
|
|
||||||
list.push([
|
|
||||||
el,
|
|
||||||
name,
|
|
||||||
fn,
|
|
||||||
]);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const clear = () => {
|
|
||||||
list = [];
|
|
||||||
};
|
|
||||||
|
|
||||||
export const get = () => list;
|
|
||||||
|
|
@ -1,37 +0,0 @@
|
||||||
import {test} from 'supertape';
|
|
||||||
import * as eventStore from './event-store.js';
|
|
||||||
|
|
||||||
test('event-store: get', (t) => {
|
|
||||||
const el = {};
|
|
||||||
const name = 'click';
|
|
||||||
const fn = () => {};
|
|
||||||
|
|
||||||
eventStore.add(el, name, fn);
|
|
||||||
const result = eventStore.get();
|
|
||||||
|
|
||||||
const expected = [
|
|
||||||
[
|
|
||||||
el,
|
|
||||||
name,
|
|
||||||
fn,
|
|
||||||
],
|
|
||||||
];
|
|
||||||
|
|
||||||
t.deepEqual(result, expected);
|
|
||||||
t.end();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('event-store: clear', (t) => {
|
|
||||||
const el = {};
|
|
||||||
const name = 'click';
|
|
||||||
const fn = () => {};
|
|
||||||
|
|
||||||
eventStore.add(el, name, fn);
|
|
||||||
eventStore.clear();
|
|
||||||
|
|
||||||
const result = eventStore.get();
|
|
||||||
const expected = [];
|
|
||||||
|
|
||||||
t.deepEqual(result, expected);
|
|
||||||
t.end();
|
|
||||||
});
|
|
||||||
|
|
@ -1,203 +0,0 @@
|
||||||
import itype from 'itype';
|
|
||||||
import * as EventStore from './event-store.js';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* safe add event listener
|
|
||||||
*
|
|
||||||
* @param type
|
|
||||||
* @param element - document by default
|
|
||||||
* @param listener
|
|
||||||
*/
|
|
||||||
export const add = (type, element, listener) => {
|
|
||||||
checkType(type);
|
|
||||||
|
|
||||||
parseArgs(type, element, listener, (element, args) => {
|
|
||||||
const [name, fn, options] = args;
|
|
||||||
|
|
||||||
element.addEventListener(name, fn, options);
|
|
||||||
EventStore.add(element, name, fn);
|
|
||||||
});
|
|
||||||
|
|
||||||
return Events;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* safe add event listener
|
|
||||||
*
|
|
||||||
* @param type
|
|
||||||
* @param listener
|
|
||||||
* @param element - document by default
|
|
||||||
*/
|
|
||||||
export const addOnce = (type, element, listener) => {
|
|
||||||
const once = (event) => {
|
|
||||||
Events.remove(type, element, once);
|
|
||||||
listener(event);
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!listener) {
|
|
||||||
listener = element;
|
|
||||||
element = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
add(type, element, once);
|
|
||||||
|
|
||||||
return Events;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* safe remove event listener
|
|
||||||
*
|
|
||||||
* @param type
|
|
||||||
* @param listener
|
|
||||||
* @param element - document by default
|
|
||||||
*/
|
|
||||||
export const remove = (type, element, listener) => {
|
|
||||||
checkType(type);
|
|
||||||
|
|
||||||
parseArgs(type, element, listener, (element, args) => {
|
|
||||||
element.removeEventListener(...args);
|
|
||||||
});
|
|
||||||
|
|
||||||
return Events;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* remove all added event listeners
|
|
||||||
*/
|
|
||||||
export const removeAll = () => {
|
|
||||||
const events = EventStore.get();
|
|
||||||
|
|
||||||
for (const [el, name, fn] of events)
|
|
||||||
el.removeEventListener(name, fn);
|
|
||||||
|
|
||||||
EventStore.clear();
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* safe add event keydown listener
|
|
||||||
*
|
|
||||||
* @param args
|
|
||||||
*/
|
|
||||||
export const addKey = function(...args) {
|
|
||||||
return add('keydown', ...args);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* safe remove event click listener
|
|
||||||
*
|
|
||||||
* @param args
|
|
||||||
*/
|
|
||||||
export const rmKey = function(...args) {
|
|
||||||
return Events.remove('keydown', ...args);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* safe add event click listener
|
|
||||||
*/
|
|
||||||
export const addClick = function(...args) {
|
|
||||||
return Events.add('click', ...args);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* safe remove event click listener
|
|
||||||
*/
|
|
||||||
export const rmClick = function(...args) {
|
|
||||||
return remove('click', ...args);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const addContextMenu = function(...args) {
|
|
||||||
return add('contextmenu', ...args);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* safe add load listener
|
|
||||||
*/
|
|
||||||
export const addLoad = function(...args) {
|
|
||||||
return add('load', ...args);
|
|
||||||
};
|
|
||||||
|
|
||||||
function checkType(type) {
|
|
||||||
if (!type)
|
|
||||||
throw Error('type could not be empty!');
|
|
||||||
}
|
|
||||||
|
|
||||||
const getEventOptions = (eventName) => {
|
|
||||||
if (eventName !== 'touchstart')
|
|
||||||
return false;
|
|
||||||
|
|
||||||
return {
|
|
||||||
passive: true,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
function parseArgs(eventName, element, listener, callback) {
|
|
||||||
let isFunc;
|
|
||||||
const args = [
|
|
||||||
eventName,
|
|
||||||
element,
|
|
||||||
listener,
|
|
||||||
callback,
|
|
||||||
];
|
|
||||||
|
|
||||||
const EVENT_NAME = 1;
|
|
||||||
const ELEMENT = 0;
|
|
||||||
const type = itype(eventName);
|
|
||||||
|
|
||||||
switch(type) {
|
|
||||||
default:
|
|
||||||
if (!type.endsWith('element'))
|
|
||||||
throw Error(`unknown eventName: ${type}`);
|
|
||||||
|
|
||||||
parseArgs(args[EVENT_NAME], args[ELEMENT], listener, callback);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'string':
|
|
||||||
isFunc = itype.function(element);
|
|
||||||
|
|
||||||
if (isFunc) {
|
|
||||||
listener = element;
|
|
||||||
element = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!element)
|
|
||||||
element = window;
|
|
||||||
|
|
||||||
callback(element, [
|
|
||||||
eventName,
|
|
||||||
listener,
|
|
||||||
getEventOptions(eventName),
|
|
||||||
]);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'array':
|
|
||||||
|
|
||||||
for (const name of eventName) {
|
|
||||||
parseArgs(name, element, listener, callback);
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'object':
|
|
||||||
|
|
||||||
for (const name of Object.keys(eventName)) {
|
|
||||||
const eventListener = eventName[name];
|
|
||||||
|
|
||||||
parseArgs(name, element, eventListener, callback);
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const Events = {
|
|
||||||
add,
|
|
||||||
addClick,
|
|
||||||
addContextMenu,
|
|
||||||
addKey,
|
|
||||||
addLoad,
|
|
||||||
addOnce,
|
|
||||||
remove,
|
|
||||||
removeAll,
|
|
||||||
rmClick,
|
|
||||||
rmKey,
|
|
||||||
};
|
|
||||||
|
|
@ -1,82 +1,105 @@
|
||||||
/* global CloudCmd */
|
/* global CloudCmd */
|
||||||
import itype from 'itype';
|
|
||||||
import {promisify} from 'es6-promisify';
|
'use strict';
|
||||||
import * as load from '#dom/load';
|
|
||||||
import * as RESTful from '#dom/rest';
|
const itype = require('itype/legacy');
|
||||||
|
const currify = require('currify/legacy');
|
||||||
|
const exec = require('execon');
|
||||||
|
|
||||||
|
const Storage = require('./storage');
|
||||||
|
const load = require('./load');
|
||||||
|
const RESTful = require('./rest');
|
||||||
|
|
||||||
const Promises = {};
|
const Promises = {};
|
||||||
const FILES_JSON = 'config|modules';
|
const FILES_JSON = 'config|modules';
|
||||||
const FILES_HTML = 'file|path|link|pathLink|media';
|
const FILES_HTML = 'file|path|link|pathLink|media';
|
||||||
const FILES_HTML_ROOT = 'view/media-tmpl|config-tmpl|upload';
|
const FILES_HTML_ROOT = 'view/media-tmpl|config-tmpl|upload';
|
||||||
const DIR_HTML = '/tmpl/';
|
const DIR_HTML = '/tmpl/';
|
||||||
const DIR_HTML_FS = `${DIR_HTML}fs/`;
|
const DIR_HTML_FS = DIR_HTML + 'fs/';
|
||||||
const DIR_JSON = '/json/';
|
const DIR_JSON = '/json/';
|
||||||
const timeout = getTimeoutOnce(2000);
|
const timeout = getTimeoutOnce(2000);
|
||||||
|
|
||||||
export const get = getFile;
|
const get = currify(getFile);
|
||||||
|
const unaryMap = (array, fn) => array.map((a) => fn(a));
|
||||||
|
|
||||||
function getFile(name) {
|
module.exports.get = get;
|
||||||
|
|
||||||
|
function getFile(name, callback) {
|
||||||
const type = itype(name);
|
const type = itype(name);
|
||||||
check(name);
|
let array;
|
||||||
|
|
||||||
if (type === 'string')
|
check(name, callback);
|
||||||
return getModule(name);
|
|
||||||
|
|
||||||
if (type === 'array')
|
switch(type) {
|
||||||
return Promise.all(name.map(getFile));
|
case 'string':
|
||||||
|
getModule(name, callback);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'array':
|
||||||
|
array = unaryMap(name, get);
|
||||||
|
|
||||||
|
exec.parallel(array, callback);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function check(name) {
|
function check(name, callback) {
|
||||||
if (!name)
|
if (!name)
|
||||||
throw Error('name could not be empty!');
|
throw Error('name could not be empty!');
|
||||||
|
|
||||||
|
if (typeof callback !== 'function')
|
||||||
|
throw Error('callback should be a function');
|
||||||
}
|
}
|
||||||
|
|
||||||
function getModule(name) {
|
function getModule(name, callback) {
|
||||||
const regExpHTML = RegExp(FILES_HTML + '|' + FILES_HTML_ROOT);
|
let path;
|
||||||
const regExpJSON = RegExp(FILES_JSON);
|
|
||||||
|
const regExpHTML = new RegExp(FILES_HTML + '|' + FILES_HTML_ROOT);
|
||||||
|
const regExpJSON = new RegExp(FILES_JSON);
|
||||||
|
|
||||||
const isHTML = regExpHTML.test(name);
|
const isHTML = regExpHTML.test(name);
|
||||||
const isJSON = regExpJSON.test(name);
|
const isJSON = regExpJSON.test(name);
|
||||||
|
|
||||||
if (!isHTML && !isJSON)
|
if (!isHTML && !isJSON) {
|
||||||
return showError(name);
|
showError(name);
|
||||||
|
} else if (name === 'config') {
|
||||||
|
getConfig(callback);
|
||||||
|
} else {
|
||||||
|
path = getPath(name, isHTML, isJSON);
|
||||||
|
|
||||||
if (name === 'config')
|
getSystemFile(path, callback);
|
||||||
return getConfig();
|
}
|
||||||
|
|
||||||
const path = getPath(name, isHTML, isJSON);
|
|
||||||
|
|
||||||
return getSystemFile(path);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getPath(name, isHTML, isJSON) {
|
function getPath(name, isHTML, isJSON) {
|
||||||
let path;
|
let path;
|
||||||
const regExp = RegExp(FILES_HTML_ROOT);
|
const regExp = new RegExp(FILES_HTML_ROOT);
|
||||||
const isRoot = regExp.test(name);
|
const isRoot = regExp.test(name);
|
||||||
|
|
||||||
if (isHTML) {
|
if (isHTML) {
|
||||||
if (isRoot)
|
if (isRoot)
|
||||||
path = DIR_HTML + name.replace('-tmpl', '');
|
path = DIR_HTML + name.replace('-tmpl', '');
|
||||||
else
|
else
|
||||||
path = DIR_HTML_FS + name;
|
path = DIR_HTML_FS + name;
|
||||||
|
|
||||||
path += '.hbs';
|
path += '.hbs';
|
||||||
} else if (isJSON) {
|
} else if (isJSON) {
|
||||||
path = DIR_JSON + name + '.json';
|
path = DIR_JSON + name + '.json';
|
||||||
}
|
}
|
||||||
|
|
||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
|
|
||||||
function showError(name) {
|
function showError(name) {
|
||||||
const str = `Wrong file name: ${name}`;
|
const str = 'Wrong file name: ' + name;
|
||||||
const error = Error(str);
|
const error = new Error(str);
|
||||||
|
|
||||||
throw error;
|
throw(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
const getSystemFile = promisify((file, callback) => {
|
function getSystemFile(file, callback) {
|
||||||
const {prefix} = CloudCmd;
|
const prefix = CloudCmd.PREFIX;
|
||||||
|
|
||||||
if (!Promises[file])
|
if (!Promises[file])
|
||||||
Promises[file] = new Promise((success, error) => {
|
Promises[file] = new Promise((success, error) => {
|
||||||
|
|
@ -85,7 +108,7 @@ const getSystemFile = promisify((file, callback) => {
|
||||||
load.ajax({
|
load.ajax({
|
||||||
url,
|
url,
|
||||||
success,
|
success,
|
||||||
error,
|
error
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -95,29 +118,37 @@ const getSystemFile = promisify((file, callback) => {
|
||||||
Promises[file] = null;
|
Promises[file] = null;
|
||||||
callback(error);
|
callback(error);
|
||||||
});
|
});
|
||||||
});
|
}
|
||||||
|
|
||||||
const getConfig = async () => {
|
function getConfig(callback) {
|
||||||
let is;
|
let is;
|
||||||
|
|
||||||
if (!Promises.config)
|
if (!Promises.config)
|
||||||
Promises.config = () => {
|
Promises.config = new Promise((resolve, reject) => {
|
||||||
is = true;
|
is = true;
|
||||||
return RESTful.Config.read();
|
RESTful.Config.read((error, data) => {
|
||||||
};
|
if (error)
|
||||||
|
return reject(error);
|
||||||
|
|
||||||
const [, data] = await Promises.config();
|
resolve(data);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
if (data)
|
Promises.config.then(function(data) {
|
||||||
is = false;
|
is = false;
|
||||||
|
Storage.setAllowed(data.localStorage);
|
||||||
|
|
||||||
timeout(() => {
|
callback(null, data);
|
||||||
|
|
||||||
|
timeout(() => {
|
||||||
|
if (!is)
|
||||||
|
Promises.config = null;
|
||||||
|
});
|
||||||
|
}, function() {
|
||||||
if (!is)
|
if (!is)
|
||||||
Promises.config = null;
|
Promises.config = null;
|
||||||
});
|
});
|
||||||
|
}
|
||||||
return data;
|
|
||||||
};
|
|
||||||
|
|
||||||
function getTimeoutOnce(time) {
|
function getTimeoutOnce(time) {
|
||||||
let is;
|
let is;
|
||||||
|
|
@ -134,3 +165,4 @@ function getTimeoutOnce(time) {
|
||||||
}, time);
|
}, time);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,22 @@
|
||||||
/* global DOM */
|
/* global CloudCmd */
|
||||||
import createElement from '@cloudcmd/create-element';
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const DOM = require('./');
|
||||||
|
|
||||||
|
const Images = module.exports;
|
||||||
|
|
||||||
const LOADING = 'loading';
|
const LOADING = 'loading';
|
||||||
const HIDDEN = 'hidden';
|
const HIDDEN = 'hidden';
|
||||||
const ERROR = 'error';
|
const ERROR = 'error';
|
||||||
|
|
||||||
const getLoadingType = () => isSVG() ? '-svg' : '-gif';
|
const LoadingImage = LOADING + getLoadingType();
|
||||||
|
|
||||||
export const get = getElement;
|
function getLoadingType() {
|
||||||
|
return isSVG() ? '-svg' : '-gif';
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports.get = getElement;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* check SVG SMIL animation support
|
* check SVG SMIL animation support
|
||||||
|
|
@ -26,54 +35,48 @@ function isSVG() {
|
||||||
return /SVGAnimate/.test(name);
|
return /SVGAnimate/.test(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function getElement() {
|
function getElement() {
|
||||||
return createElement('span', {
|
return DOM.load({
|
||||||
id: 'js-status-image',
|
name : 'span',
|
||||||
className: 'icon',
|
id : 'js-status-image',
|
||||||
dataName: 'progress',
|
className : 'icon',
|
||||||
notAppend: true,
|
attribute : 'data-progress',
|
||||||
|
notAppend : true
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Функция создаёт картинку загрузки */
|
/* Функция создаёт картинку загрузки */
|
||||||
export const loading = () => {
|
module.exports.loading = () => {
|
||||||
const element = getElement();
|
const element = getElement();
|
||||||
const {classList} = element;
|
const classList = element.classList;
|
||||||
const loadingImage = LOADING + getLoadingType();
|
|
||||||
|
|
||||||
classList.add(LOADING, loadingImage);
|
classList.add(LOADING, LoadingImage);
|
||||||
classList.remove(ERROR, HIDDEN);
|
classList.remove(ERROR, HIDDEN);
|
||||||
|
|
||||||
return element;
|
return element;
|
||||||
};
|
};
|
||||||
|
|
||||||
/* Функция создаёт картинку ошибки загрузки */
|
/* Функция создаёт картинку ошибки загрузки */
|
||||||
export const error = () => {
|
module.exports.error = () => {
|
||||||
const element = getElement();
|
const element = getElement();
|
||||||
const {classList} = element;
|
const classList = element.classList;
|
||||||
const loadingImage = LOADING + getLoadingType();
|
|
||||||
|
|
||||||
classList.add(ERROR);
|
classList.add(ERROR);
|
||||||
classList.remove(HIDDEN, LOADING, loadingImage);
|
classList.remove(HIDDEN, LOADING, LoadingImage);
|
||||||
|
|
||||||
return element;
|
return element;
|
||||||
};
|
};
|
||||||
|
|
||||||
show.load = show;
|
module.exports.show = show;
|
||||||
show.error = (text) => {
|
module.exports.show.load = show;
|
||||||
const image = Images.error();
|
module.exports.show.error = error;
|
||||||
|
|
||||||
DOM.show(image);
|
|
||||||
image.title = text;
|
|
||||||
|
|
||||||
return image;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Function shows loading spinner
|
* Function shows loading spinner
|
||||||
* position = {top: true};
|
* position = {top: true};
|
||||||
*/
|
*/
|
||||||
export function show(position, panel) {
|
function show(position, panel) {
|
||||||
const image = Images.loading();
|
const image = Images.loading();
|
||||||
const parent = image.parentElement;
|
const parent = image.parentElement;
|
||||||
const refreshButton = DOM.getRefreshButton(panel);
|
const refreshButton = DOM.getRefreshButton(panel);
|
||||||
|
|
@ -91,7 +94,7 @@ export function show(position, panel) {
|
||||||
current = refreshButton.parentElement;
|
current = refreshButton.parentElement;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!parent || parent && parent !== current)
|
if (!parent || (parent && parent !== current))
|
||||||
current.appendChild(image);
|
current.appendChild(image);
|
||||||
|
|
||||||
DOM.show(image);
|
DOM.show(image);
|
||||||
|
|
@ -99,25 +102,36 @@ export function show(position, panel) {
|
||||||
return image;
|
return image;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function error(text) {
|
||||||
|
const image = Images.error();
|
||||||
|
|
||||||
|
DOM.show(image);
|
||||||
|
image.title = text;
|
||||||
|
|
||||||
|
CloudCmd.log(text);
|
||||||
|
|
||||||
|
return image;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* hide load image
|
* hide load image
|
||||||
*/
|
*/
|
||||||
export function hide() {
|
module.exports.hide = () => {
|
||||||
const element = Images.get();
|
const element = Images.get();
|
||||||
|
|
||||||
DOM.hide(element);
|
DOM.hide(element);
|
||||||
|
|
||||||
return Images;
|
return Images;
|
||||||
}
|
};
|
||||||
|
|
||||||
export const setProgress = (value, title) => {
|
module.exports.setProgress = (value, title) => {
|
||||||
const DATA = 'data-progress';
|
const DATA = 'data-progress';
|
||||||
const element = Images.get();
|
const element = Images.get();
|
||||||
|
|
||||||
if (!element)
|
if (!element)
|
||||||
return Images;
|
return Images;
|
||||||
|
|
||||||
element.setAttribute(DATA, `${value}%`);
|
element.setAttribute(DATA, value + '%');
|
||||||
|
|
||||||
if (title)
|
if (title)
|
||||||
element.title = title;
|
element.title = title;
|
||||||
|
|
@ -125,7 +139,7 @@ export const setProgress = (value, title) => {
|
||||||
return Images;
|
return Images;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const clearProgress = () => {
|
module.exports.clearProgress = () => {
|
||||||
const DATA = 'data-progress';
|
const DATA = 'data-progress';
|
||||||
const element = Images.get();
|
const element = Images.get();
|
||||||
|
|
||||||
|
|
@ -138,12 +152,3 @@ export const clearProgress = () => {
|
||||||
return Images;
|
return Images;
|
||||||
};
|
};
|
||||||
|
|
||||||
const Images = {
|
|
||||||
clearProgress,
|
|
||||||
setProgress,
|
|
||||||
show,
|
|
||||||
hide,
|
|
||||||
get,
|
|
||||||
error,
|
|
||||||
loading,
|
|
||||||
};
|
|
||||||
|
|
|
||||||
1955
client/dom/index.js
1955
client/dom/index.js
File diff suppressed because it is too large
Load diff
|
|
@ -1,52 +0,0 @@
|
||||||
import {test, stub} from 'supertape';
|
|
||||||
import {getCSSVar, goToDirectory} from '#dom';
|
|
||||||
|
|
||||||
globalThis.CloudCmd = {};
|
|
||||||
|
|
||||||
test('cloudcmd: client: dom: goToDirectory', async (t) => {
|
|
||||||
const path = '';
|
|
||||||
const changeDir = stub();
|
|
||||||
const prompt = stub().returns([null, path]);
|
|
||||||
|
|
||||||
await goToDirectory({
|
|
||||||
prompt,
|
|
||||||
changeDir,
|
|
||||||
});
|
|
||||||
|
|
||||||
t.calledWith(changeDir, [path]);
|
|
||||||
t.end();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('cloudcmd: client: dom: getCSSVar', (t) => {
|
|
||||||
const body = {};
|
|
||||||
const getPropertyValue = stub().returns(0);
|
|
||||||
|
|
||||||
globalThis.getComputedStyle = stub().returns({
|
|
||||||
getPropertyValue,
|
|
||||||
});
|
|
||||||
const result = getCSSVar('hello', {
|
|
||||||
body,
|
|
||||||
});
|
|
||||||
|
|
||||||
delete globalThis.getComputedStyle;
|
|
||||||
|
|
||||||
t.notOk(result);
|
|
||||||
t.end();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('cloudcmd: client: dom: getCSSVar: 1', (t) => {
|
|
||||||
const body = {};
|
|
||||||
const getPropertyValue = stub().returns(1);
|
|
||||||
|
|
||||||
globalThis.getComputedStyle = stub().returns({
|
|
||||||
getPropertyValue,
|
|
||||||
});
|
|
||||||
const result = getCSSVar('hello', {
|
|
||||||
body,
|
|
||||||
});
|
|
||||||
|
|
||||||
delete globalThis.getComputedStyle;
|
|
||||||
|
|
||||||
t.ok(result);
|
|
||||||
t.end();
|
|
||||||
});
|
|
||||||
|
|
@ -1,166 +0,0 @@
|
||||||
import {FS} from '#common/cloudfunc';
|
|
||||||
import {sendRequest as _sendRequest} from './send-request.js';
|
|
||||||
|
|
||||||
const {assign} = Object;
|
|
||||||
|
|
||||||
const imgPosition = {
|
|
||||||
top: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
export const remove = async (url, data, overrides = {}) => {
|
|
||||||
const {
|
|
||||||
sendRequest = _sendRequest,
|
|
||||||
} = overrides;
|
|
||||||
|
|
||||||
const request = {
|
|
||||||
method: 'DELETE',
|
|
||||||
url: FS + url,
|
|
||||||
imgPosition: {
|
|
||||||
top: Boolean(data),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
if (data)
|
|
||||||
assign(request, {
|
|
||||||
data,
|
|
||||||
url: `${request.url}?files`,
|
|
||||||
});
|
|
||||||
|
|
||||||
return await sendRequest(request);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const patch = async (url, data) => {
|
|
||||||
return await _sendRequest({
|
|
||||||
method: 'PATCH',
|
|
||||||
url: FS + url,
|
|
||||||
data,
|
|
||||||
imgPosition,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export const write = async (url, data) => {
|
|
||||||
return await _sendRequest({
|
|
||||||
method: 'PUT',
|
|
||||||
url: FS + url,
|
|
||||||
data,
|
|
||||||
imgPosition,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export const createDirectory = async (url, overrides = {}) => {
|
|
||||||
const {
|
|
||||||
sendRequest = _sendRequest,
|
|
||||||
} = overrides;
|
|
||||||
|
|
||||||
return await sendRequest({
|
|
||||||
method: 'PUT',
|
|
||||||
url: `${FS}${url}?dir`,
|
|
||||||
imgPosition,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export const read = async (url, dataType = 'text') => {
|
|
||||||
const notLog = !url.includes('?');
|
|
||||||
|
|
||||||
return await _sendRequest({
|
|
||||||
method: 'GET',
|
|
||||||
url: FS + url,
|
|
||||||
notLog,
|
|
||||||
dataType,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export const copy = async (from, to, names) => {
|
|
||||||
return await _sendRequest({
|
|
||||||
method: 'PUT',
|
|
||||||
url: '/copy',
|
|
||||||
data: {
|
|
||||||
from,
|
|
||||||
to,
|
|
||||||
names,
|
|
||||||
},
|
|
||||||
imgPosition,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export const pack = async (data) => {
|
|
||||||
return await _sendRequest({
|
|
||||||
method: 'PUT',
|
|
||||||
url: '/pack',
|
|
||||||
data,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export const extract = async (data) => {
|
|
||||||
return await _sendRequest({
|
|
||||||
method: 'PUT',
|
|
||||||
url: '/extract',
|
|
||||||
data,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export const move = async (from, to, names) => {
|
|
||||||
return await _sendRequest({
|
|
||||||
method: 'PUT',
|
|
||||||
url: '/move',
|
|
||||||
data: {
|
|
||||||
from,
|
|
||||||
to,
|
|
||||||
names,
|
|
||||||
},
|
|
||||||
imgPosition,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export const rename = async (from, to) => {
|
|
||||||
return await _sendRequest({
|
|
||||||
method: 'PUT',
|
|
||||||
url: '/rename',
|
|
||||||
data: {
|
|
||||||
from,
|
|
||||||
to,
|
|
||||||
},
|
|
||||||
imgPosition,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Config = {
|
|
||||||
read: async () => {
|
|
||||||
return await _sendRequest({
|
|
||||||
method: 'GET',
|
|
||||||
url: '/config',
|
|
||||||
imgPosition,
|
|
||||||
notLog: true,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
write: async (data) => {
|
|
||||||
return await _sendRequest({
|
|
||||||
method: 'PATCH',
|
|
||||||
url: '/config',
|
|
||||||
data,
|
|
||||||
imgPosition,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Markdown = {
|
|
||||||
read: async (url) => {
|
|
||||||
return await _sendRequest({
|
|
||||||
method: 'GET',
|
|
||||||
url: `/markdown${url}`,
|
|
||||||
imgPosition,
|
|
||||||
notLog: true,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
render: async (data) => {
|
|
||||||
return await _sendRequest({
|
|
||||||
method: 'PUT',
|
|
||||||
url: '/markdown',
|
|
||||||
data,
|
|
||||||
imgPosition,
|
|
||||||
notLog: true,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
@ -1,61 +0,0 @@
|
||||||
import {test, stub} from 'supertape';
|
|
||||||
import * as io from './index.js';
|
|
||||||
|
|
||||||
test('client: dom: io', (t) => {
|
|
||||||
const sendRequest = stub();
|
|
||||||
|
|
||||||
io.createDirectory('/hello', {
|
|
||||||
sendRequest,
|
|
||||||
});
|
|
||||||
|
|
||||||
const expected = {
|
|
||||||
imgPosition: {
|
|
||||||
top: true,
|
|
||||||
},
|
|
||||||
method: 'PUT',
|
|
||||||
url: '/fs/hello?dir',
|
|
||||||
};
|
|
||||||
|
|
||||||
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();
|
|
||||||
});
|
|
||||||
|
|
@ -1,48 +0,0 @@
|
||||||
/* global CloudCmd */
|
|
||||||
import {promisify} from 'es6-promisify';
|
|
||||||
import * as Images from '#dom/images';
|
|
||||||
import * as load from '#dom/load';
|
|
||||||
|
|
||||||
export const sendRequest = promisify((params, callback) => {
|
|
||||||
const p = params;
|
|
||||||
const {prefixURL} = CloudCmd;
|
|
||||||
|
|
||||||
p.url = prefixURL + p.url;
|
|
||||||
p.url = encodeURI(p.url);
|
|
||||||
|
|
||||||
p.url = replaceHash(p.url);
|
|
||||||
|
|
||||||
load.ajax({
|
|
||||||
method: p.method,
|
|
||||||
url: p.url,
|
|
||||||
data: p.data,
|
|
||||||
dataType: p.dataType,
|
|
||||||
error: (jqXHR) => {
|
|
||||||
const response = jqXHR.responseText;
|
|
||||||
|
|
||||||
const {statusText, status} = jqXHR;
|
|
||||||
|
|
||||||
const text = status === 404 ? response : statusText;
|
|
||||||
|
|
||||||
callback(Error(text));
|
|
||||||
},
|
|
||||||
success: (data) => {
|
|
||||||
Images.hide();
|
|
||||||
|
|
||||||
if (!p.notLog)
|
|
||||||
CloudCmd.log(data);
|
|
||||||
|
|
||||||
callback(null, data);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
export const _replaceHash = replaceHash;
|
|
||||||
|
|
||||||
function replaceHash(url) {
|
|
||||||
/*
|
|
||||||
* if we send ajax request -
|
|
||||||
* no need in hash so we escape #
|
|
||||||
*/
|
|
||||||
return url.replace(/#/g, '%23');
|
|
||||||
}
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
import {test} from 'supertape';
|
|
||||||
import {_replaceHash} from './send-request.js';
|
|
||||||
|
|
||||||
test('cloudcmd: client: io: replaceHash', (t) => {
|
|
||||||
const url = '/hello/####world';
|
|
||||||
const result = _replaceHash(url);
|
|
||||||
const expected = '/hello/%23%23%23%23world';
|
|
||||||
|
|
||||||
t.equal(result, expected);
|
|
||||||
t.end();
|
|
||||||
});
|
|
||||||
|
|
@ -1,58 +1,69 @@
|
||||||
/* global CloudCmd */
|
'use strict';
|
||||||
import {callbackify} from 'node:util';
|
|
||||||
import {rendy} from 'rendy';
|
|
||||||
import itype from 'itype';
|
|
||||||
import * as load from 'load.js';
|
|
||||||
import {tryToCatch} from 'try-to-catch';
|
|
||||||
import {findObjByNameInArr} from '#common/util';
|
|
||||||
import * as Files from '#dom/files';
|
|
||||||
|
|
||||||
export const loadRemote = callbackify(async (name, options) => {
|
/* global CloudCmd */
|
||||||
const {prefix, config} = CloudCmd;
|
|
||||||
|
const exec = require('execon');
|
||||||
|
const rendy = require('rendy');
|
||||||
|
const itype = require('itype/legacy');
|
||||||
|
const {findObjByNameInArr} = require('../../common/util');
|
||||||
|
|
||||||
|
const load = require('./load');
|
||||||
|
const Files = require('./files');
|
||||||
|
|
||||||
|
module.exports = (name, options, callback = options) => {
|
||||||
|
const {PREFIX, config} = CloudCmd;
|
||||||
const o = options;
|
const o = options;
|
||||||
|
|
||||||
if (o.name && window[o.name])
|
if (o.name && window[o.name])
|
||||||
return;
|
return callback();
|
||||||
|
|
||||||
const modules = await Files.get('modules');
|
Files.get('modules', (error, modules) => {
|
||||||
|
const online = config('online') && navigator.onLine;
|
||||||
|
const module = findObjByNameInArr(modules.remote, name);
|
||||||
|
|
||||||
const online = config('online') && navigator.onLine;
|
const isArray = itype.array(module.local);
|
||||||
const module = findObjByNameInArr(modules.remote, name);
|
const version = module.version;
|
||||||
|
|
||||||
const isArray = itype.array(module.local);
|
let remoteTmpls, local;
|
||||||
const {version} = module;
|
if (isArray) {
|
||||||
|
remoteTmpls = module.remote;
|
||||||
|
local = module.local;
|
||||||
|
} else {
|
||||||
|
remoteTmpls = [module.remote];
|
||||||
|
local = [module.local];
|
||||||
|
}
|
||||||
|
|
||||||
let remoteTmpls;
|
const localURL = local.map((url) => {
|
||||||
let local;
|
return PREFIX + url;
|
||||||
|
});
|
||||||
|
|
||||||
if (isArray) {
|
const remoteURL = remoteTmpls.map((tmpl) => {
|
||||||
remoteTmpls = module.remote;
|
return rendy(tmpl, {
|
||||||
({local} = module);
|
version: version
|
||||||
} else {
|
});
|
||||||
remoteTmpls = [module.remote];
|
});
|
||||||
local = [module.local];
|
|
||||||
}
|
|
||||||
|
|
||||||
const localURL = [];
|
const on = funcON(localURL, remoteURL, callback);
|
||||||
|
const off = funcOFF(localURL, callback);
|
||||||
|
|
||||||
for (const url of local) {
|
exec.if(online, on, off);
|
||||||
localURL.push(prefix + url);
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
const remoteURL = [];
|
function funcOFF(local, callback) {
|
||||||
|
return () => {
|
||||||
|
load.parallel(local, callback);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
for (const tmpl of remoteTmpls) {
|
function funcON (local, remote,callback) {
|
||||||
remoteURL.push(rendy(tmpl, {
|
return () => {
|
||||||
version,
|
load.parallel(remote, (error) => {
|
||||||
}));
|
if (error)
|
||||||
}
|
return funcOFF();
|
||||||
|
|
||||||
if (online) {
|
callback();
|
||||||
const [e] = await tryToCatch(load.parallel, remoteURL);
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
if (!e)
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await load.parallel(localURL);
|
|
||||||
});
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,130 @@
|
||||||
import itype from 'itype';
|
'use strict';
|
||||||
import jonny from 'jonny';
|
|
||||||
import Emitify from 'emitify';
|
const itype = require('itype/legacy');
|
||||||
import exec from 'execon';
|
const jonny = require('jonny');
|
||||||
import * as Images from '#dom/images';
|
const Emitify = require('emitify/legacy');
|
||||||
|
const exec = require('execon');
|
||||||
|
const Images = require('./images');
|
||||||
|
const Events = require('./events');
|
||||||
|
|
||||||
|
const {getExt} = require('../../common/util');
|
||||||
|
|
||||||
|
module.exports = load;
|
||||||
|
module.exports.getIdBySrc = getIdBySrc;
|
||||||
|
module.exports.ext = ext;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Функция создаёт элемент и загружает файл с src.
|
||||||
|
*
|
||||||
|
* @param params = {
|
||||||
|
* name, - название тэга
|
||||||
|
* src', - путь к файлу
|
||||||
|
* func, - обьект, содержаий одну из функций
|
||||||
|
* или сразу две onload и onerror
|
||||||
|
* {onload: function() {}, onerror: function();}
|
||||||
|
* style,
|
||||||
|
* id,
|
||||||
|
* element,
|
||||||
|
* async, - true by default
|
||||||
|
* inner: 'id{color:red, },
|
||||||
|
* class,
|
||||||
|
* notAppend - false by default
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
function load(params) {
|
||||||
|
const {
|
||||||
|
src,
|
||||||
|
id = getIdBySrc(params.src),
|
||||||
|
func,
|
||||||
|
name,
|
||||||
|
async,
|
||||||
|
inner,
|
||||||
|
style,
|
||||||
|
parent = document.body,
|
||||||
|
className,
|
||||||
|
attribute,
|
||||||
|
notAppend,
|
||||||
|
} = params;
|
||||||
|
|
||||||
|
let element = document.getElementById(id);
|
||||||
|
|
||||||
|
if (element) {
|
||||||
|
exec(func);
|
||||||
|
return element;
|
||||||
|
}
|
||||||
|
|
||||||
|
element = document.createElement(name);
|
||||||
|
|
||||||
|
const funcError = () => {
|
||||||
|
const msg = `file ${src} could not be loaded`;
|
||||||
|
const error = new Error(msg);
|
||||||
|
|
||||||
|
parent.removeChild(element);
|
||||||
|
|
||||||
|
Images.show.error(msg);
|
||||||
|
|
||||||
|
const callback = func && func.onerror || func.onload || func;
|
||||||
|
|
||||||
|
exec(callback, error);
|
||||||
|
};
|
||||||
|
|
||||||
|
const funcLoad = () => {
|
||||||
|
const callback = func && func.onload || func;
|
||||||
|
|
||||||
|
Events.remove('error', element, funcError);
|
||||||
|
|
||||||
|
exec(callback);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (/^(script|link)$/.test(name))
|
||||||
|
Events.addOnce('load', element, funcLoad)
|
||||||
|
.addError(element, funcError);
|
||||||
|
|
||||||
|
if (id)
|
||||||
|
element.id = id;
|
||||||
|
|
||||||
|
if (className)
|
||||||
|
element.className = className;
|
||||||
|
|
||||||
|
if (src) {
|
||||||
|
if (name !== 'link') {
|
||||||
|
element.src = src;
|
||||||
|
} else {
|
||||||
|
element.href = src;
|
||||||
|
element.rel = 'stylesheet';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (attribute) {
|
||||||
|
const type = itype(attribute);
|
||||||
|
|
||||||
|
switch(type) {
|
||||||
|
case 'string':
|
||||||
|
element.setAttribute(attribute, '');
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'object':
|
||||||
|
Object.keys(attribute).forEach((name) => {
|
||||||
|
element.setAttribute(name, attribute[name]);
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (style)
|
||||||
|
element.style.cssText = style;
|
||||||
|
|
||||||
|
if (async && name === 'script' || async === undefined)
|
||||||
|
element.async = true;
|
||||||
|
|
||||||
|
if (!notAppend)
|
||||||
|
parent.appendChild(element);
|
||||||
|
|
||||||
|
if (inner)
|
||||||
|
element.innerHTML = inner;
|
||||||
|
|
||||||
|
return element;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Function gets id by src
|
* Function gets id by src
|
||||||
|
|
@ -10,18 +132,17 @@ import * as Images from '#dom/images';
|
||||||
*
|
*
|
||||||
* Example: http://domain.com/1.js -> 1_js
|
* Example: http://domain.com/1.js -> 1_js
|
||||||
*/
|
*/
|
||||||
export function getIdBySrc(src) {
|
function getIdBySrc(src) {
|
||||||
const isStr = itype.string(src);
|
const isStr = itype.string(src);
|
||||||
|
|
||||||
if (!isStr)
|
if (!isStr)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (src.includes(':'))
|
if (~src.indexOf(':'))
|
||||||
src += '-join';
|
src += '-join';
|
||||||
|
|
||||||
const num = src.lastIndexOf('/') + 1;
|
const num = src.lastIndexOf('/') + 1;
|
||||||
const sub = src.substr(src, num);
|
const sub = src.substr(src, num);
|
||||||
|
|
||||||
const id = src
|
const id = src
|
||||||
.replace(sub, '')
|
.replace(sub, '')
|
||||||
.replace(/\./g, '-');
|
.replace(/\./g, '-');
|
||||||
|
|
@ -34,37 +155,34 @@ export function getIdBySrc(src) {
|
||||||
*
|
*
|
||||||
* @param params
|
* @param params
|
||||||
*/
|
*/
|
||||||
export const ajax = (params) => {
|
module.exports.ajax = (params) => {
|
||||||
const p = params;
|
const p = params;
|
||||||
const isObject = itype.object(p.data);
|
const isObject = itype.object(p.data);
|
||||||
const isArray = itype.array(p.data);
|
const isArray = itype.array(p.data);
|
||||||
const isArrayBuf = itype(p.data) === 'arraybuffer';
|
const isArrayBuf = itype(p.data) === 'arraybuffer';
|
||||||
const type = p.type || p.method || 'GET';
|
const type = p.type || p.method || 'GET';
|
||||||
|
const headers = p.headers || {};
|
||||||
const {headers = {}} = p;
|
|
||||||
|
|
||||||
const xhr = new XMLHttpRequest();
|
const xhr = new XMLHttpRequest();
|
||||||
|
|
||||||
xhr.open(type, p.url, true);
|
xhr.open(type, p.url, true);
|
||||||
|
|
||||||
for (const name of Object.keys(headers)) {
|
Object.keys(headers).forEach((name) => {
|
||||||
const value = headers[name];
|
const value = headers[name];
|
||||||
xhr.setRequestHeader(name, value);
|
xhr.setRequestHeader(name, value);
|
||||||
}
|
});
|
||||||
|
|
||||||
if (p.responseType)
|
if (p.responseType)
|
||||||
xhr.responseType = p.responseType;
|
xhr.responseType = p.responseType;
|
||||||
|
|
||||||
let data;
|
let data;
|
||||||
|
|
||||||
if (!isArrayBuf && isObject || isArray)
|
if (!isArrayBuf && isObject || isArray)
|
||||||
data = jonny.stringify(p.data);
|
data = jonny.stringify(p.data);
|
||||||
else
|
else
|
||||||
({data} = p);
|
data = p.data;
|
||||||
|
|
||||||
xhr.onreadystatechange = (event) => {
|
xhr.onreadystatechange = (event) => {
|
||||||
const xhr = event.target;
|
const xhr = event.target;
|
||||||
const OK = 200;
|
const OK = 200;
|
||||||
|
|
||||||
if (xhr.readyState !== xhr.DONE)
|
if (xhr.readyState !== xhr.DONE)
|
||||||
return;
|
return;
|
||||||
|
|
@ -72,16 +190,15 @@ export const ajax = (params) => {
|
||||||
Images.clearProgress();
|
Images.clearProgress();
|
||||||
|
|
||||||
const TYPE_JSON = 'application/json';
|
const TYPE_JSON = 'application/json';
|
||||||
const type = xhr.getResponseHeader('content-type');
|
const type = xhr.getResponseHeader('content-type');
|
||||||
|
|
||||||
if (xhr.status !== OK)
|
if (xhr.status !== OK)
|
||||||
return exec(p.error, xhr);
|
return exec(p.error, xhr);
|
||||||
|
|
||||||
const notText = p.dataType !== 'text';
|
const notText = p.dataType !== 'text';
|
||||||
const isContain = type.includes(TYPE_JSON);
|
const isContain = ~type.indexOf(TYPE_JSON);
|
||||||
|
|
||||||
let data = xhr.response;
|
let data = xhr.response;
|
||||||
|
|
||||||
if (type && isContain && notText)
|
if (type && isContain && notText)
|
||||||
data = jonny.parse(xhr.response) || xhr.response;
|
data = jonny.parse(xhr.response) || xhr.response;
|
||||||
|
|
||||||
|
|
@ -91,22 +208,25 @@ export const ajax = (params) => {
|
||||||
xhr.send(data);
|
xhr.send(data);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const put = (url, body) => {
|
module.exports.put = (url, body) => {
|
||||||
const emitter = Emitify();
|
const emitter = Emitify();
|
||||||
const xhr = new XMLHttpRequest();
|
const xhr = new XMLHttpRequest();
|
||||||
|
|
||||||
url = encodeURI(url).replace(/#/g, '#');
|
url = encodeURI(url)
|
||||||
|
.replace('#', '%23');
|
||||||
|
|
||||||
xhr.open('put', url, true);
|
xhr.open('put', url, true);
|
||||||
|
|
||||||
xhr.upload.onprogress = (event) => {
|
xhr.upload.onprogress = (event) => {
|
||||||
if (!event.lengthComputable)
|
var percent, count;
|
||||||
return;
|
|
||||||
|
|
||||||
const percent = event.loaded / event.total * 100;
|
if (event.lengthComputable) {
|
||||||
const count = Math.round(percent);
|
percent = (event.loaded / event.total) * 100;
|
||||||
|
count = Math.round(percent);
|
||||||
|
|
||||||
|
emitter.emit('progress', count);
|
||||||
|
}
|
||||||
|
|
||||||
emitter.emit('progress', count);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
xhr.onreadystatechange = () => {
|
xhr.onreadystatechange = () => {
|
||||||
|
|
@ -116,10 +236,8 @@ export const put = (url, body) => {
|
||||||
if (!over)
|
if (!over)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (xhr.status === OK) {
|
if (xhr.status === OK)
|
||||||
emitter.emit('progress', 100);
|
|
||||||
return emitter.emit('end');
|
return emitter.emit('end');
|
||||||
}
|
|
||||||
|
|
||||||
const error = Error(xhr.responseText);
|
const error = Error(xhr.responseText);
|
||||||
emitter.emit('error', error);
|
emitter.emit('error', error);
|
||||||
|
|
@ -129,3 +247,116 @@ export const put = (url, body) => {
|
||||||
|
|
||||||
return emitter;
|
return emitter;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function ext(src, func) {
|
||||||
|
switch (getExt(src)) {
|
||||||
|
case '.js':
|
||||||
|
return load.js(src, func);
|
||||||
|
|
||||||
|
case '.css':
|
||||||
|
return load.css(src, func);
|
||||||
|
|
||||||
|
default:
|
||||||
|
return load({
|
||||||
|
src,
|
||||||
|
func,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* create elements and load them to DOM-tree
|
||||||
|
* one-by-one
|
||||||
|
*
|
||||||
|
* @param params
|
||||||
|
* @param callback
|
||||||
|
*/
|
||||||
|
load.series = (params, callback) => {
|
||||||
|
if (!params)
|
||||||
|
return load;
|
||||||
|
|
||||||
|
const funcs = params
|
||||||
|
.map((url) => ext.bind(null, url))
|
||||||
|
.concat(callback);
|
||||||
|
|
||||||
|
exec.series(funcs);
|
||||||
|
|
||||||
|
return load;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* improve callback of funcs so
|
||||||
|
* we pop number of function and
|
||||||
|
* if it's last we call pCallBack
|
||||||
|
*
|
||||||
|
* @param params
|
||||||
|
* @param callback - onload function
|
||||||
|
*/
|
||||||
|
load.parallel = (params, callback) => {
|
||||||
|
if (!params)
|
||||||
|
return load;
|
||||||
|
|
||||||
|
const funcs = params.map((url) => {
|
||||||
|
return ext.bind(null, url);
|
||||||
|
});
|
||||||
|
|
||||||
|
exec.parallel(funcs, callback);
|
||||||
|
|
||||||
|
return load;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Функция загружает js-файл
|
||||||
|
*
|
||||||
|
* @param src
|
||||||
|
* @param func
|
||||||
|
*/
|
||||||
|
load.js = (src, func) => {
|
||||||
|
const name = 'script';
|
||||||
|
|
||||||
|
return load({
|
||||||
|
name,
|
||||||
|
src,
|
||||||
|
func,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
load.css = (src, func) => {
|
||||||
|
const name = 'link';
|
||||||
|
const {head:parent} = document;
|
||||||
|
|
||||||
|
return load({
|
||||||
|
name,
|
||||||
|
src,
|
||||||
|
parent,
|
||||||
|
func
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Функция создаёт елемент style и записывает туда стили
|
||||||
|
* @param params - структура параметров, заполняеться таким
|
||||||
|
* образом: {src: ' ',func: '', id: '', element: '', inner: ''}
|
||||||
|
* все параметры опциональны
|
||||||
|
*/
|
||||||
|
load.style = (params) => {
|
||||||
|
const {
|
||||||
|
id,
|
||||||
|
src,
|
||||||
|
name = 'style',
|
||||||
|
func,
|
||||||
|
inner,
|
||||||
|
parent = document.head,
|
||||||
|
element,
|
||||||
|
} = params;
|
||||||
|
|
||||||
|
return load({
|
||||||
|
id,
|
||||||
|
src,
|
||||||
|
func,
|
||||||
|
name,
|
||||||
|
inner,
|
||||||
|
parent,
|
||||||
|
element,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,66 +0,0 @@
|
||||||
/* global CloudCmd */
|
|
||||||
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';
|
|
||||||
|
|
||||||
export default async (current, overrides = {}) => {
|
|
||||||
const {
|
|
||||||
refresh = CloudCmd.refresh,
|
|
||||||
Dialog = _Dialog,
|
|
||||||
currentFile = _currentFile,
|
|
||||||
} = overrides;
|
|
||||||
|
|
||||||
const {
|
|
||||||
isCurrentFile,
|
|
||||||
getCurrentName,
|
|
||||||
getCurrentFile,
|
|
||||||
getCurrentByName,
|
|
||||||
getCurrentType,
|
|
||||||
getCurrentDirPath,
|
|
||||||
setCurrentName,
|
|
||||||
} = currentFile;
|
|
||||||
|
|
||||||
if (!isCurrentFile(current))
|
|
||||||
current = getCurrentFile();
|
|
||||||
|
|
||||||
const from = getCurrentName(current);
|
|
||||||
|
|
||||||
if (from === '..')
|
|
||||||
return Dialog.alert.noFiles();
|
|
||||||
|
|
||||||
const [cancel, to] = await Dialog.prompt('Rename', from);
|
|
||||||
|
|
||||||
if (cancel)
|
|
||||||
return;
|
|
||||||
|
|
||||||
const nextFile = getCurrentByName(to);
|
|
||||||
|
|
||||||
if (nextFile) {
|
|
||||||
const type = getCurrentType(nextFile);
|
|
||||||
const msg = `${capitalize(type)} "${to}" already exists. Proceed?`;
|
|
||||||
const [cancel] = await Dialog.confirm(msg);
|
|
||||||
|
|
||||||
if (cancel)
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (from === to)
|
|
||||||
return;
|
|
||||||
|
|
||||||
const dirPath = getCurrentDirPath();
|
|
||||||
|
|
||||||
const fromFull = `${dirPath}${from}`;
|
|
||||||
const toFull = `${dirPath}${to}`;
|
|
||||||
|
|
||||||
const [e] = await RESTful.rename(fromFull, toFull);
|
|
||||||
|
|
||||||
if (e)
|
|
||||||
return;
|
|
||||||
|
|
||||||
setCurrentName(to, current);
|
|
||||||
|
|
||||||
Storage.remove(dirPath);
|
|
||||||
refresh();
|
|
||||||
};
|
|
||||||
|
|
@ -1,86 +0,0 @@
|
||||||
import {test, stub} from 'supertape';
|
|
||||||
import renameCurrent from './rename-current.js';
|
|
||||||
|
|
||||||
test('cloudcmd: client: dom: renameCurrent: isCurrentFile', async (t) => {
|
|
||||||
const current = {};
|
|
||||||
const isCurrentFile = stub();
|
|
||||||
|
|
||||||
const currentFile = stubCurrentFile({
|
|
||||||
isCurrentFile,
|
|
||||||
});
|
|
||||||
|
|
||||||
await renameCurrent(current, {
|
|
||||||
Dialog: stubDialog(),
|
|
||||||
currentFile,
|
|
||||||
});
|
|
||||||
|
|
||||||
t.calledWith(isCurrentFile, [current], 'should call isCurrentFile');
|
|
||||||
t.end();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('cloudcmd: client: dom: renameCurrent: file exist', async (t) => {
|
|
||||||
const current = {};
|
|
||||||
const name = 'hello';
|
|
||||||
|
|
||||||
const prompt = stub().returns([null, name]);
|
|
||||||
const confirm = stub().returns([true]);
|
|
||||||
|
|
||||||
const getCurrentByName = stub().returns(current);
|
|
||||||
const getCurrentType = stub().returns('directory');
|
|
||||||
|
|
||||||
const Dialog = stubDialog({
|
|
||||||
confirm,
|
|
||||||
prompt,
|
|
||||||
});
|
|
||||||
|
|
||||||
const currentFile = stubCurrentFile({
|
|
||||||
getCurrentByName,
|
|
||||||
getCurrentType,
|
|
||||||
});
|
|
||||||
|
|
||||||
await renameCurrent(null, {
|
|
||||||
Dialog,
|
|
||||||
currentFile,
|
|
||||||
});
|
|
||||||
|
|
||||||
const expected = 'Directory "hello" already exists. Proceed?';
|
|
||||||
|
|
||||||
t.calledWith(confirm, [expected], 'should call confirm');
|
|
||||||
t.end();
|
|
||||||
});
|
|
||||||
|
|
||||||
const stubDialog = (fns = {}) => {
|
|
||||||
const {
|
|
||||||
alert = stub().returns([]),
|
|
||||||
confirm = stub().returns([]),
|
|
||||||
prompt = stub().returns([]),
|
|
||||||
} = fns;
|
|
||||||
|
|
||||||
return {
|
|
||||||
alert,
|
|
||||||
confirm,
|
|
||||||
prompt,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const stubCurrentFile = (fns = {}) => {
|
|
||||||
const {
|
|
||||||
isCurrentFile = stub(),
|
|
||||||
getCurrentName = stub(),
|
|
||||||
getCurrentFile = stub(),
|
|
||||||
getCurrentByName = stub(),
|
|
||||||
getCurrentType = stub(),
|
|
||||||
getCurrentDirPath = stub(),
|
|
||||||
setCurrentName = stub(),
|
|
||||||
} = fns;
|
|
||||||
|
|
||||||
return {
|
|
||||||
isCurrentFile,
|
|
||||||
getCurrentName,
|
|
||||||
getCurrentFile,
|
|
||||||
getCurrentByName,
|
|
||||||
getCurrentType,
|
|
||||||
getCurrentDirPath,
|
|
||||||
setCurrentName,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
@ -1,40 +1,211 @@
|
||||||
import {tryToCatch} from 'try-to-catch';
|
'use strict';
|
||||||
import * as Dialog from '#dom/dialog';
|
|
||||||
import * as Images from '#dom/images';
|
|
||||||
import {encode} from '#common/entity';
|
|
||||||
import * as IO from './io/index.js';
|
|
||||||
|
|
||||||
const handleError = (promise) => async (...args) => {
|
/* global CloudCmd, DOM */
|
||||||
const [e, data] = await tryToCatch(promise, ...args);
|
|
||||||
|
|
||||||
if (!e)
|
const itype = require('itype/legacy');
|
||||||
return [e, data];
|
|
||||||
|
|
||||||
const encoded = encode(e.message);
|
const {FS} = require('../../common/cloudfunc');
|
||||||
|
|
||||||
Images.show.error(encoded);
|
module.exports = new RESTful();
|
||||||
Dialog.alert(encoded);
|
|
||||||
|
|
||||||
return [e, data];
|
const Images = require('./images');
|
||||||
};
|
const load = require('./load');
|
||||||
|
|
||||||
export const remove = handleError(IO.remove);
|
function RESTful() {
|
||||||
export const patch = handleError(IO.patch);
|
this.delete = (url, data, callback) => {
|
||||||
export const write = handleError(IO.write);
|
const isFunc = itype.function(data);
|
||||||
export const createDirectory = handleError(IO.createDirectory);
|
|
||||||
export const read = handleError(IO.read);
|
|
||||||
export const copy = handleError(IO.copy);
|
|
||||||
export const pack = handleError(IO.pack);
|
|
||||||
export const extract = handleError(IO.extract);
|
|
||||||
export const move = handleError(IO.move);
|
|
||||||
export const rename = handleError(IO.rename);
|
|
||||||
|
|
||||||
export const Config = {
|
if (!callback && isFunc) {
|
||||||
read: handleError(IO.Config.read),
|
callback = data;
|
||||||
write: handleError(IO.Config.write),
|
data = null;
|
||||||
};
|
}
|
||||||
|
|
||||||
export const Markdown = {
|
sendRequest({
|
||||||
read: handleError(IO.Markdown.read),
|
method : 'DELETE',
|
||||||
render: handleError(IO.Markdown.render),
|
url : FS + url,
|
||||||
};
|
data,
|
||||||
|
callback,
|
||||||
|
imgPosition : { top: !!data }
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
this.patch = function(url, data, callback) {
|
||||||
|
var isFunc = itype.function(data);
|
||||||
|
|
||||||
|
if (!callback && isFunc) {
|
||||||
|
callback = data;
|
||||||
|
data = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
sendRequest({
|
||||||
|
method : 'PATCH',
|
||||||
|
url : FS + url,
|
||||||
|
data : data,
|
||||||
|
callback : callback,
|
||||||
|
imgPosition : { top: true }
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
this.write = function(url, data, callback) {
|
||||||
|
var isFunc = itype.function(data);
|
||||||
|
|
||||||
|
if (!callback && isFunc) {
|
||||||
|
callback = data;
|
||||||
|
data = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
sendRequest({
|
||||||
|
method : 'PUT',
|
||||||
|
url : FS + url,
|
||||||
|
data : data,
|
||||||
|
callback : callback,
|
||||||
|
imgPosition : { top: true }
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
this.read = function(url, dataType, callback) {
|
||||||
|
var isQuery = /\?/.test(url);
|
||||||
|
var isBeautify = /\?beautify$/.test(url);
|
||||||
|
var isMinify = /\?minify$/.test(url);
|
||||||
|
var notLog = !isQuery || isBeautify || isMinify;
|
||||||
|
var isFunc = itype.function(dataType);
|
||||||
|
|
||||||
|
if (!callback && isFunc) {
|
||||||
|
callback = dataType;
|
||||||
|
dataType = 'text';
|
||||||
|
}
|
||||||
|
|
||||||
|
sendRequest({
|
||||||
|
method: 'GET',
|
||||||
|
url: FS + url,
|
||||||
|
callback: callback,
|
||||||
|
notLog: notLog,
|
||||||
|
dataType: dataType
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
this.cp = function(data, callback) {
|
||||||
|
sendRequest({
|
||||||
|
method : 'PUT',
|
||||||
|
url : '/cp',
|
||||||
|
data : data,
|
||||||
|
callback : callback,
|
||||||
|
imgPosition : { top: true }
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
this.pack = function(data, callback) {
|
||||||
|
sendRequest({
|
||||||
|
method : 'PUT',
|
||||||
|
url : '/pack',
|
||||||
|
data : data,
|
||||||
|
callback : callback
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
this.extract = function(data, callback) {
|
||||||
|
sendRequest({
|
||||||
|
method : 'PUT',
|
||||||
|
url : '/extract',
|
||||||
|
data : data,
|
||||||
|
callback : callback
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
this.mv = function(data, callback) {
|
||||||
|
sendRequest({
|
||||||
|
method : 'PUT',
|
||||||
|
url : '/mv',
|
||||||
|
data : data,
|
||||||
|
callback : callback,
|
||||||
|
imgPosition : { top: true }
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
this.Config = {
|
||||||
|
read: function(callback) {
|
||||||
|
sendRequest({
|
||||||
|
method : 'GET',
|
||||||
|
url : '/config',
|
||||||
|
callback : callback,
|
||||||
|
imgPosition : { top: true },
|
||||||
|
notLog : true
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
write: function(data, callback) {
|
||||||
|
sendRequest({
|
||||||
|
method : 'PATCH',
|
||||||
|
url : '/config',
|
||||||
|
data : data,
|
||||||
|
callback : callback,
|
||||||
|
imgPosition : { top: true }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.Markdown = {
|
||||||
|
read : function(url, callback) {
|
||||||
|
sendRequest({
|
||||||
|
method : 'GET',
|
||||||
|
url : '/markdown' + url,
|
||||||
|
callback : callback,
|
||||||
|
imgPosition : { top: true },
|
||||||
|
notLog : true
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
render : function(data, callback) {
|
||||||
|
sendRequest({
|
||||||
|
method : 'PUT',
|
||||||
|
url : '/markdown',
|
||||||
|
data : data,
|
||||||
|
callback : callback,
|
||||||
|
imgPosition : { top: true },
|
||||||
|
notLog : true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function sendRequest(params) {
|
||||||
|
var p = params,
|
||||||
|
prefixUrl = CloudCmd.PREFIX_URL;
|
||||||
|
|
||||||
|
p.url = prefixUrl + p.url;
|
||||||
|
p.url = encodeURI(p.url);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* if we send ajax request -
|
||||||
|
* no need in hash so we escape #
|
||||||
|
*/
|
||||||
|
p.url = p.url.replace('#', '%23');
|
||||||
|
|
||||||
|
load.ajax({
|
||||||
|
method : p.method,
|
||||||
|
url : p.url,
|
||||||
|
data : p.data,
|
||||||
|
dataType : p.dataType,
|
||||||
|
error : (jqXHR) => {
|
||||||
|
const response = jqXHR.responseText;
|
||||||
|
const statusText = jqXHR.statusText;
|
||||||
|
const status = jqXHR.status;
|
||||||
|
const text = status === 404 ? response : statusText;
|
||||||
|
|
||||||
|
Images.show.error(text);
|
||||||
|
setTimeout(() => {
|
||||||
|
DOM.Dialog.alert(CloudCmd.TITLE, text);
|
||||||
|
}, 100);
|
||||||
|
|
||||||
|
p.callback(Error(text));
|
||||||
|
},
|
||||||
|
success: (data) => {
|
||||||
|
Images.hide();
|
||||||
|
|
||||||
|
if (!p.notLog)
|
||||||
|
CloudCmd.log(data);
|
||||||
|
|
||||||
|
p.callback(null, data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,46 +1,52 @@
|
||||||
import {alert, prompt} from '#dom/dialog';
|
'use strict';
|
||||||
import {getRegExp} from '#common/util';
|
|
||||||
import {getCurrentName} from './current-file.js';
|
/* global DOM */
|
||||||
import {
|
|
||||||
isSelected,
|
|
||||||
toggleSelectedFile,
|
|
||||||
} from './cmd.js';
|
|
||||||
|
|
||||||
let SelectType = '*.*';
|
let SelectType = '*.*';
|
||||||
|
const TITLE = 'Cloud Commander';
|
||||||
|
|
||||||
export const selectByPattern = async (msg, files) => {
|
const {getRegExp} = require('../../common/util');
|
||||||
if (!files)
|
|
||||||
return;
|
|
||||||
|
|
||||||
|
module.exports = (msg, files) => {
|
||||||
const allMsg = `Specify file type for ${msg} selection`;
|
const allMsg = `Specify file type for ${msg} selection`;
|
||||||
const [cancel, type] = await prompt(allMsg, SelectType);
|
const cancel = false;
|
||||||
|
const {Dialog} = DOM;
|
||||||
|
|
||||||
if (cancel)
|
Dialog.prompt(TITLE, allMsg, SelectType, {cancel}).then((type) => {
|
||||||
return;
|
SelectType = type;
|
||||||
|
|
||||||
SelectType = type;
|
const regExp = getRegExp(type);
|
||||||
|
|
||||||
const regExp = getRegExp(type);
|
if (!files)
|
||||||
let matches = 0;
|
return;
|
||||||
|
|
||||||
for (const current of files) {
|
let matches = 0;
|
||||||
const name = getCurrentName(current);
|
|
||||||
|
|
||||||
if (name === '..' || !regExp.test(name))
|
files.forEach((current) => {
|
||||||
continue;
|
const name = DOM.getCurrentName(current);
|
||||||
|
|
||||||
++matches;
|
if (name === '..')
|
||||||
|
return;
|
||||||
|
|
||||||
let selected = isSelected(current);
|
const isMatch = regExp.test(name);
|
||||||
const shouldSel = msg === 'expand';
|
|
||||||
|
|
||||||
if (shouldSel)
|
if (!isMatch)
|
||||||
selected = !selected;
|
return;
|
||||||
|
|
||||||
if (selected)
|
++matches;
|
||||||
toggleSelectedFile(current);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!matches)
|
let isSelected = DOM.isSelected(current);
|
||||||
alert('No matches found!');
|
const shouldSel = msg === 'expand';
|
||||||
|
|
||||||
|
if (shouldSel)
|
||||||
|
isSelected = !isSelected;
|
||||||
|
|
||||||
|
if (isSelected)
|
||||||
|
DOM.toggleSelectedFile(current);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!matches)
|
||||||
|
Dialog.alert('Select Files', 'No matches found!');
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,26 +1,84 @@
|
||||||
const {parse, stringify} = JSON;
|
'use strict';
|
||||||
|
|
||||||
export const set = (name, data) => {
|
const itype = require('itype/legacy');
|
||||||
localStorage.setItem(name, data);
|
const jonny = require('jonny');
|
||||||
|
const exec = require('execon');
|
||||||
|
|
||||||
|
/* приватный переключатель возможности работы с кэшем */
|
||||||
|
let Allowed;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* allow Storage usage
|
||||||
|
*/
|
||||||
|
module.exports.setAllowed = (isAllowed) => {
|
||||||
|
Allowed = isAllowed;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const setJson = (name, data) => {
|
/** remove element */
|
||||||
localStorage.setItem(name, stringify(data));
|
module.exports.remove = (item, callback) => {
|
||||||
|
if (Allowed)
|
||||||
|
localStorage.removeItem(item);
|
||||||
|
|
||||||
|
exec(callback, null, Allowed);
|
||||||
|
|
||||||
|
return module.exports;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const get = (name) => {
|
module.exports.removeMatch = (string, callback) => {
|
||||||
return localStorage.getItem(name);
|
const reg = RegExp('^' + string + '.*$');
|
||||||
|
const test = (a) => reg.test(a);
|
||||||
|
const remove = (a) => localStorage.removeItem(a);
|
||||||
|
|
||||||
|
Object.keys(localStorage)
|
||||||
|
.filter(test)
|
||||||
|
.forEach(remove);
|
||||||
|
|
||||||
|
exec(callback);
|
||||||
|
|
||||||
|
return module.exports;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getJson = (name) => {
|
/** если доступен localStorage и
|
||||||
const data = localStorage.getItem(name);
|
* в нём есть нужная нам директория -
|
||||||
return parse(data);
|
* записываем данные в него
|
||||||
|
*/
|
||||||
|
module.exports.set = (name, data, callback) => {
|
||||||
|
let str, error;
|
||||||
|
|
||||||
|
if (itype.object(data))
|
||||||
|
str = jonny.stringify(data);
|
||||||
|
|
||||||
|
if (Allowed && name)
|
||||||
|
error = exec.try(() => {
|
||||||
|
localStorage.setItem(name, str || data);
|
||||||
|
});
|
||||||
|
|
||||||
|
exec(callback, error);
|
||||||
|
|
||||||
|
return module.exports;
|
||||||
|
},
|
||||||
|
|
||||||
|
/** Если доступен Storage принимаем из него данные*/
|
||||||
|
module.exports.get = (name, callback) => {
|
||||||
|
let ret;
|
||||||
|
|
||||||
|
if (Allowed)
|
||||||
|
ret = localStorage.getItem(name);
|
||||||
|
|
||||||
|
exec(callback, null, ret);
|
||||||
|
|
||||||
|
return module.exports;
|
||||||
|
},
|
||||||
|
|
||||||
|
/** функция чистит весь кэш для всех каталогов*/
|
||||||
|
module.exports.clear = (callback) => {
|
||||||
|
const ret = Allowed;
|
||||||
|
|
||||||
|
if (ret)
|
||||||
|
localStorage.clear();
|
||||||
|
|
||||||
|
exec(callback, null, ret);
|
||||||
|
|
||||||
|
return module.exports;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const clear = () => {
|
|
||||||
localStorage.clear();
|
|
||||||
};
|
|
||||||
|
|
||||||
export const remove = (item) => {
|
|
||||||
localStorage.removeItem(item);
|
|
||||||
};
|
|
||||||
|
|
|
||||||
|
|
@ -1,105 +0,0 @@
|
||||||
import {test, stub} from 'supertape';
|
|
||||||
import * as storage from '#dom/storage';
|
|
||||||
|
|
||||||
const {stringify} = JSON;
|
|
||||||
|
|
||||||
test('cloudcmd: client: storage: set', async (t) => {
|
|
||||||
const {localStorage} = globalThis;
|
|
||||||
const setItem = stub();
|
|
||||||
|
|
||||||
globalThis.localStorage = {
|
|
||||||
setItem,
|
|
||||||
};
|
|
||||||
|
|
||||||
await storage.set('hello', 'world');
|
|
||||||
globalThis.localStorage = localStorage;
|
|
||||||
|
|
||||||
t.calledWith(setItem, ['hello', 'world'], 'should call setItem');
|
|
||||||
t.end();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('cloudcmd: client: storage: get', async (t) => {
|
|
||||||
const {localStorage} = globalThis;
|
|
||||||
const getItem = stub().returns('world');
|
|
||||||
|
|
||||||
globalThis.localStorage = {
|
|
||||||
getItem,
|
|
||||||
};
|
|
||||||
|
|
||||||
const result = await storage.get('hello');
|
|
||||||
|
|
||||||
globalThis.localStorage = localStorage;
|
|
||||||
|
|
||||||
t.equal(result, 'world');
|
|
||||||
t.end();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('cloudcmd: client: storage: getJson', async (t) => {
|
|
||||||
const {localStorage} = globalThis;
|
|
||||||
const expected = {
|
|
||||||
hello: 'world',
|
|
||||||
};
|
|
||||||
|
|
||||||
const getItem = stub().returns(stringify(expected));
|
|
||||||
|
|
||||||
globalThis.localStorage = {
|
|
||||||
getItem,
|
|
||||||
};
|
|
||||||
|
|
||||||
const result = await storage.getJson('hello');
|
|
||||||
|
|
||||||
globalThis.localStorage = localStorage;
|
|
||||||
|
|
||||||
t.deepEqual(result, expected);
|
|
||||||
t.end();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('cloudcmd: client: storage: setJson', async (t) => {
|
|
||||||
const {localStorage} = globalThis;
|
|
||||||
const data = {
|
|
||||||
hello: 'world',
|
|
||||||
};
|
|
||||||
|
|
||||||
const expected = stringify(data);
|
|
||||||
const setItem = stub();
|
|
||||||
|
|
||||||
globalThis.localStorage = {
|
|
||||||
setItem,
|
|
||||||
};
|
|
||||||
|
|
||||||
await storage.setJson('hello', data);
|
|
||||||
globalThis.localStorage = localStorage;
|
|
||||||
|
|
||||||
t.calledWith(setItem, ['hello', expected]);
|
|
||||||
t.end();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('cloudcmd: client: storage: remove', async (t) => {
|
|
||||||
const {localStorage} = globalThis;
|
|
||||||
const removeItem = stub();
|
|
||||||
|
|
||||||
globalThis.localStorage = {
|
|
||||||
removeItem,
|
|
||||||
};
|
|
||||||
|
|
||||||
await storage.remove('hello');
|
|
||||||
globalThis.localStorage = localStorage;
|
|
||||||
|
|
||||||
t.calledWith(removeItem, ['hello'], 'should call removeItem');
|
|
||||||
t.end();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('cloudcmd: client: storage: clear', async (t) => {
|
|
||||||
const {localStorage} = globalThis;
|
|
||||||
const clear = stub();
|
|
||||||
|
|
||||||
globalThis.localStorage = {
|
|
||||||
clear,
|
|
||||||
};
|
|
||||||
|
|
||||||
await storage.clear();
|
|
||||||
globalThis.localStorage = localStorage;
|
|
||||||
|
|
||||||
t.calledWithNoArgs(clear, 'should call clear');
|
|
||||||
t.end();
|
|
||||||
});
|
|
||||||
|
|
@ -1,19 +1,27 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
/* global CloudCmd */
|
/* global CloudCmd */
|
||||||
import {eachSeries} from 'execon';
|
|
||||||
import wraptile from 'wraptile';
|
|
||||||
import * as load from '#dom/load';
|
|
||||||
import {alert} from '#dom/dialog';
|
|
||||||
import {FS} from '#common/cloudfunc';
|
|
||||||
import * as Images from '#dom/images';
|
|
||||||
import {getCurrentDirPath} from './current-file.js';
|
|
||||||
|
|
||||||
const loadFile = wraptile(_loadFile);
|
const {eachSeries} = require('execon');
|
||||||
|
const wraptile = require('wraptile/legacy');
|
||||||
|
|
||||||
|
const DOM = require('.');
|
||||||
|
const load = require('./load');
|
||||||
|
const Images = require('./images');
|
||||||
|
|
||||||
|
const {FS} = require('../../common/cloudfunc');
|
||||||
|
|
||||||
const onEnd = wraptile(_onEnd);
|
const onEnd = wraptile(_onEnd);
|
||||||
|
const loadFile = wraptile(_loadFile);
|
||||||
|
|
||||||
export const uploadFiles = (dir, files) => {
|
const {
|
||||||
|
getCurrentDirPath: getPathWhenRootEmpty
|
||||||
|
} = DOM;
|
||||||
|
|
||||||
|
module.exports = (dir, files) => {
|
||||||
if (!files) {
|
if (!files) {
|
||||||
files = dir;
|
files = dir;
|
||||||
dir = getCurrentDirPath();
|
dir = getPathWhenRootEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
const n = files.length;
|
const n = files.length;
|
||||||
|
|
@ -21,7 +29,7 @@ export const uploadFiles = (dir, files) => {
|
||||||
if (!n)
|
if (!n)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const array = Array.from(files);
|
const array = [...files];
|
||||||
const {name} = files[0];
|
const {name} = files[0];
|
||||||
|
|
||||||
eachSeries(array, loadFile(dir, n), onEnd(name));
|
eachSeries(array, loadFile(dir, n), onEnd(name));
|
||||||
|
|
@ -29,17 +37,17 @@ export const uploadFiles = (dir, files) => {
|
||||||
|
|
||||||
function _onEnd(currentName) {
|
function _onEnd(currentName) {
|
||||||
CloudCmd.refresh({
|
CloudCmd.refresh({
|
||||||
currentName,
|
currentName
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function _loadFile(dir, n, file, callback) {
|
function _loadFile(dir, n, file, callback) {
|
||||||
let i = 0;
|
let i = 0;
|
||||||
|
|
||||||
const {name} = file;
|
const name = file.name;
|
||||||
const path = dir + name;
|
const path = dir + name;
|
||||||
const {prefixURL} = CloudCmd;
|
const {PREFIX_URL} = CloudCmd;
|
||||||
const api = prefixURL + FS;
|
const api = PREFIX_URL + FS;
|
||||||
|
|
||||||
const percent = (i, n, per = 100) => {
|
const percent = (i, n, per = 100) => {
|
||||||
return Math.round(i * per / n);
|
return Math.round(i * per / n);
|
||||||
|
|
@ -49,9 +57,7 @@ function _loadFile(dir, n, file, callback) {
|
||||||
|
|
||||||
++i;
|
++i;
|
||||||
|
|
||||||
load
|
load.put(api + path, file)
|
||||||
.put(api + path, file)
|
|
||||||
.on('error', showError)
|
|
||||||
.on('end', callback)
|
.on('end', callback)
|
||||||
.on('progress', (count) => {
|
.on('progress', (count) => {
|
||||||
const max = step(n);
|
const max = step(n);
|
||||||
|
|
@ -62,6 +68,3 @@ function _loadFile(dir, n, file, callback) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function showError({message}) {
|
|
||||||
alert(message);
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,44 +0,0 @@
|
||||||
/* global DOM */
|
|
||||||
/**
|
|
||||||
* Функция генерирует JSON из html-таблицы файлов и
|
|
||||||
* используеться при первом заходе в корень
|
|
||||||
*/
|
|
||||||
export const getJsonFromFileTable = () => {
|
|
||||||
const Info = DOM.CurrentInfo;
|
|
||||||
const path = DOM.getCurrentDirPath();
|
|
||||||
const infoFiles = Info.files || [];
|
|
||||||
|
|
||||||
const files = infoFiles
|
|
||||||
.filter(notParent)
|
|
||||||
.map(parse);
|
|
||||||
|
|
||||||
const fileTable = {
|
|
||||||
path,
|
|
||||||
files,
|
|
||||||
};
|
|
||||||
|
|
||||||
return fileTable;
|
|
||||||
};
|
|
||||||
|
|
||||||
const notParent = (current) => {
|
|
||||||
const name = DOM.getCurrentName(current);
|
|
||||||
return name !== '..';
|
|
||||||
};
|
|
||||||
|
|
||||||
const parse = (current) => {
|
|
||||||
const name = DOM.getCurrentName(current);
|
|
||||||
const size = DOM.getCurrentSize(current);
|
|
||||||
const owner = DOM.getCurrentOwner(current);
|
|
||||||
const mode = DOM.getCurrentMode(current);
|
|
||||||
const date = DOM.getCurrentDate(current);
|
|
||||||
const type = DOM.getCurrentType(current);
|
|
||||||
|
|
||||||
return {
|
|
||||||
name,
|
|
||||||
size,
|
|
||||||
mode,
|
|
||||||
owner,
|
|
||||||
date,
|
|
||||||
type,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
78
client/input.js
Normal file
78
client/input.js
Normal file
|
|
@ -0,0 +1,78 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const currify = require('currify/legacy');
|
||||||
|
|
||||||
|
const isType = currify((type, object, name) => {
|
||||||
|
return typeof object[name] === type;
|
||||||
|
});
|
||||||
|
|
||||||
|
const isBool = isType('boolean');
|
||||||
|
|
||||||
|
module.exports.getElementByName = getElementByName;
|
||||||
|
|
||||||
|
function getElementByName(selector, element) {
|
||||||
|
const str = `[data-name="js-${selector}"]`;
|
||||||
|
|
||||||
|
return element
|
||||||
|
.querySelector(str);
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports.getName = (element) => {
|
||||||
|
const name = element
|
||||||
|
.getAttribute('data-name')
|
||||||
|
.replace(/^js-/, '');
|
||||||
|
|
||||||
|
return name;
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports.convert = (config) => {
|
||||||
|
const result = Object.assign({}, config);
|
||||||
|
const array = Object.keys(result);
|
||||||
|
|
||||||
|
array
|
||||||
|
.filter(isBool(result))
|
||||||
|
.forEach((name) => {
|
||||||
|
const item = result[name];
|
||||||
|
|
||||||
|
result[name] = setState(item);
|
||||||
|
});
|
||||||
|
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
function setState(state) {
|
||||||
|
if (state)
|
||||||
|
return ' checked';
|
||||||
|
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports.getValue = (name, element) => {
|
||||||
|
const el = getElementByName(name, element);
|
||||||
|
const type = el.type;
|
||||||
|
|
||||||
|
switch(type) {
|
||||||
|
case 'checkbox':
|
||||||
|
return el.checked;
|
||||||
|
case 'number':
|
||||||
|
return Number(el.value);
|
||||||
|
default:
|
||||||
|
return el.value;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports.setValue = (name, value, element) => {
|
||||||
|
const el = getElementByName(name, element);
|
||||||
|
const type = el.type;
|
||||||
|
|
||||||
|
switch(type) {
|
||||||
|
case 'checkbox':
|
||||||
|
el.checked = value;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
el.value = value;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
573
client/key.js
Normal file
573
client/key.js
Normal file
|
|
@ -0,0 +1,573 @@
|
||||||
|
/* global CloudCmd, DOM */
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const Info = DOM.CurrentInfo;
|
||||||
|
|
||||||
|
const exec = require('execon');
|
||||||
|
|
||||||
|
const Events = require('./dom/events');
|
||||||
|
const Buffer = require('./dom/buffer');
|
||||||
|
const {escapeRegExp} = require('../common/util');
|
||||||
|
|
||||||
|
let Chars = [];
|
||||||
|
const KEY = {
|
||||||
|
BACKSPACE : 8,
|
||||||
|
TAB : 9,
|
||||||
|
ENTER : 13,
|
||||||
|
ESC : 27,
|
||||||
|
|
||||||
|
SPACE : 32,
|
||||||
|
PAGE_UP : 33,
|
||||||
|
PAGE_DOWN : 34,
|
||||||
|
END : 35,
|
||||||
|
HOME : 36,
|
||||||
|
|
||||||
|
LEFT : 37,
|
||||||
|
UP : 38,
|
||||||
|
RIGHT : 39,
|
||||||
|
DOWN : 40,
|
||||||
|
|
||||||
|
INSERT : 45,
|
||||||
|
DELETE : 46,
|
||||||
|
|
||||||
|
ZERO : 48,
|
||||||
|
|
||||||
|
A : 65,
|
||||||
|
|
||||||
|
C : 67,
|
||||||
|
D : 68,
|
||||||
|
|
||||||
|
G : 71,
|
||||||
|
|
||||||
|
M : 77,
|
||||||
|
|
||||||
|
O : 79,
|
||||||
|
Q : 81,
|
||||||
|
R : 82,
|
||||||
|
S : 83,
|
||||||
|
T : 84,
|
||||||
|
U : 85,
|
||||||
|
|
||||||
|
V : 86,
|
||||||
|
|
||||||
|
X : 88,
|
||||||
|
|
||||||
|
Z : 90,
|
||||||
|
|
||||||
|
INSERT_MAC : 96,
|
||||||
|
|
||||||
|
ASTERISK : 106,
|
||||||
|
PLUS : 107,
|
||||||
|
MINUS : 109,
|
||||||
|
|
||||||
|
F1 : 112,
|
||||||
|
F2 : 113,
|
||||||
|
F3 : 114,
|
||||||
|
F4 : 115,
|
||||||
|
F5 : 116,
|
||||||
|
F6 : 117,
|
||||||
|
F7 : 118,
|
||||||
|
F8 : 119,
|
||||||
|
F9 : 120,
|
||||||
|
F10 : 121,
|
||||||
|
|
||||||
|
EQUAL : 187,
|
||||||
|
HYPHEN : 189,
|
||||||
|
DOT : 190,
|
||||||
|
SLASH : 191,
|
||||||
|
TRA : 192, /* Typewritten Reverse Apostrophe (`) */
|
||||||
|
BACKSLASH : 220,
|
||||||
|
|
||||||
|
BRACKET_CLOSE: 221
|
||||||
|
};
|
||||||
|
|
||||||
|
KeyProto.prototype = KEY;
|
||||||
|
CloudCmd.Key = KeyProto;
|
||||||
|
|
||||||
|
function KeyProto() {
|
||||||
|
const Key = this;
|
||||||
|
|
||||||
|
let Binded;
|
||||||
|
|
||||||
|
this.isBind = () => {
|
||||||
|
return Binded;
|
||||||
|
};
|
||||||
|
|
||||||
|
this.setBind = () => {
|
||||||
|
Binded = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
this.unsetBind = () => {
|
||||||
|
Binded = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
this.bind = () => {
|
||||||
|
Events.addKey(listener);
|
||||||
|
Binded = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
function getChar(event) {
|
||||||
|
/*
|
||||||
|
* event.keyIdentifier deprecated in chrome v51
|
||||||
|
* but event.key is absent in chrome <= v51
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (event.key)
|
||||||
|
return event.key;
|
||||||
|
|
||||||
|
return fromCharCode(event.keyIdentifier);
|
||||||
|
}
|
||||||
|
|
||||||
|
function listener(event) {
|
||||||
|
const keyCode = event.keyCode;
|
||||||
|
const alt = event.altKey;
|
||||||
|
const ctrl = event.ctrlKey;
|
||||||
|
const shift = event.shiftKey;
|
||||||
|
const meta = event.metaKey;
|
||||||
|
const isBetween = keyCode >= KEY.ZERO && keyCode <= KEY.Z;
|
||||||
|
const isNumpad = /Numpad/.test(event.code);
|
||||||
|
|
||||||
|
let char = getChar(event);
|
||||||
|
let isSymbol = ~['.', '_', '-', '+', '='].indexOf(char);
|
||||||
|
|
||||||
|
if (!isSymbol) {
|
||||||
|
isSymbol = getSymbol(shift, keyCode);
|
||||||
|
|
||||||
|
if (isSymbol)
|
||||||
|
char = isSymbol;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* in case buttons can be processed */
|
||||||
|
if (Key.isBind())
|
||||||
|
if (!isNumpad && !alt && !ctrl && !meta && (isBetween || isSymbol))
|
||||||
|
setCurrentByChar(char);
|
||||||
|
else {
|
||||||
|
Chars = [];
|
||||||
|
switchKey(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSymbol(shift, keyCode) {
|
||||||
|
switch (keyCode) {
|
||||||
|
case KEY.DOT:
|
||||||
|
return '.';
|
||||||
|
|
||||||
|
case KEY.HYPHEN:
|
||||||
|
return shift ? '_' : '-';
|
||||||
|
|
||||||
|
case KEY.EQUAL:
|
||||||
|
return shift ? '+' : '=';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function fromCharCode(keyIdentifier) {
|
||||||
|
const code = keyIdentifier.substring(2);
|
||||||
|
const hex = parseInt(code, 16);
|
||||||
|
const char = String.fromCharCode(hex);
|
||||||
|
|
||||||
|
return char;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setCurrentByChar(char) {
|
||||||
|
let firstByName;
|
||||||
|
let skipCount = 0;
|
||||||
|
let setted = false;
|
||||||
|
let i = 0;
|
||||||
|
|
||||||
|
const escapeChar = escapeRegExp(char);
|
||||||
|
const regExp = new RegExp('^' + escapeChar + '.*$', 'i');
|
||||||
|
const {files} = Info;
|
||||||
|
const n = Chars.length;
|
||||||
|
|
||||||
|
while(i < n && char === Chars[i]) {
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!i)
|
||||||
|
Chars = [];
|
||||||
|
|
||||||
|
const skipN = skipCount = i;
|
||||||
|
Chars.push(char);
|
||||||
|
|
||||||
|
const names = DOM.getFilenames(files);
|
||||||
|
const isTest = (a) => regExp.test(a);
|
||||||
|
const isRoot = (a) => a === '..';
|
||||||
|
const not = (f) => (a) => !f(a);
|
||||||
|
const setCurrent = (name) => {
|
||||||
|
const byName = DOM.getCurrentByName(name);
|
||||||
|
|
||||||
|
if (!skipCount) {
|
||||||
|
setted = true;
|
||||||
|
DOM.setCurrentFile(byName);
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
if (skipN === skipCount)
|
||||||
|
firstByName = byName;
|
||||||
|
|
||||||
|
--skipCount;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
names
|
||||||
|
.filter(isTest)
|
||||||
|
.filter(not(isRoot))
|
||||||
|
.some(setCurrent);
|
||||||
|
|
||||||
|
if (!setted) {
|
||||||
|
DOM.setCurrentFile(firstByName);
|
||||||
|
Chars = [char];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function switchKey(event) {
|
||||||
|
let i, isSelected, prev, next;
|
||||||
|
let current = Info.element;
|
||||||
|
let name = Info.name;
|
||||||
|
|
||||||
|
const {Operation} = CloudCmd;
|
||||||
|
const panel = Info.panel;
|
||||||
|
const path = Info.path;
|
||||||
|
const isDir = Info.isDir;
|
||||||
|
|
||||||
|
const keyCode = event.keyCode;
|
||||||
|
const alt = event.altKey;
|
||||||
|
const shift = event.shiftKey;
|
||||||
|
const ctrl = event.ctrlKey;
|
||||||
|
const meta = event.metaKey;
|
||||||
|
const ctrlMeta = ctrl || meta;
|
||||||
|
|
||||||
|
if (current) {
|
||||||
|
prev = current.previousSibling;
|
||||||
|
next = current.nextSibling;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (keyCode) {
|
||||||
|
case Key.TAB:
|
||||||
|
DOM.changePanel();
|
||||||
|
event.preventDefault();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Key.INSERT:
|
||||||
|
DOM .toggleSelectedFile(current)
|
||||||
|
.setCurrentFile(next);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Key.INSERT_MAC:
|
||||||
|
DOM .toggleSelectedFile(current)
|
||||||
|
.setCurrentFile(next);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Key.DELETE:
|
||||||
|
if (shift)
|
||||||
|
Operation.show('delete:silent');
|
||||||
|
else
|
||||||
|
Operation.show('delete');
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Key.ASTERISK:
|
||||||
|
DOM.toggleAllSelectedFiles(current);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Key.PLUS:
|
||||||
|
DOM.expandSelection();
|
||||||
|
event.preventDefault();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Key.MINUS:
|
||||||
|
DOM.shrinkSelection();
|
||||||
|
event.preventDefault();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Key.F1:
|
||||||
|
CloudCmd.Help.show();
|
||||||
|
event.preventDefault();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Key.F2:
|
||||||
|
DOM.renameCurrent(current);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Key.F3:
|
||||||
|
if (shift)
|
||||||
|
CloudCmd.Markdown.show(path);
|
||||||
|
else if (ctrlMeta)
|
||||||
|
CloudCmd.sortPanel('name');
|
||||||
|
else
|
||||||
|
CloudCmd.View.show();
|
||||||
|
|
||||||
|
event.preventDefault();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Key.F4:
|
||||||
|
if (shift)
|
||||||
|
CloudCmd.EditFileVim.show();
|
||||||
|
else
|
||||||
|
CloudCmd.EditFile.show();
|
||||||
|
|
||||||
|
event.preventDefault();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Key.F5:
|
||||||
|
if (ctrlMeta)
|
||||||
|
CloudCmd.sortPanel('date');
|
||||||
|
else
|
||||||
|
Operation.show('copy');
|
||||||
|
|
||||||
|
event.preventDefault();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Key.F6:
|
||||||
|
if (ctrlMeta)
|
||||||
|
CloudCmd.sortPanel('size');
|
||||||
|
else
|
||||||
|
Operation.show('move');
|
||||||
|
|
||||||
|
event.preventDefault();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Key.F7:
|
||||||
|
if (shift)
|
||||||
|
DOM.promptNewFile();
|
||||||
|
else
|
||||||
|
DOM.promptNewDir();
|
||||||
|
|
||||||
|
event.preventDefault();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Key.F8:
|
||||||
|
Operation.show('delete');
|
||||||
|
event.preventDefault();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Key.F9:
|
||||||
|
CloudCmd.Menu.show();
|
||||||
|
event.preventDefault();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Key.F10:
|
||||||
|
CloudCmd.Config.show();
|
||||||
|
event.preventDefault();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Key.TRA:
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
if (shift)
|
||||||
|
return CloudCmd.Terminal.show();
|
||||||
|
|
||||||
|
CloudCmd.Konsole.show();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case KEY.BRACKET_CLOSE:
|
||||||
|
CloudCmd.Konsole.show();
|
||||||
|
event.preventDefault();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Key.SPACE:
|
||||||
|
if (!isDir || name === '..')
|
||||||
|
isSelected = true;
|
||||||
|
else
|
||||||
|
isSelected = DOM.isSelected(current);
|
||||||
|
|
||||||
|
exec.if(isSelected, () => {
|
||||||
|
DOM.toggleSelectedFile(current);
|
||||||
|
}, (callback) => {
|
||||||
|
DOM.loadCurrentSize(callback, current);
|
||||||
|
});
|
||||||
|
|
||||||
|
event.preventDefault();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Key.U:
|
||||||
|
if (ctrlMeta) {
|
||||||
|
DOM.swapPanels();
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
/* navigation on file table: *
|
||||||
|
* in case of pressing button 'up', *
|
||||||
|
* select previous row */
|
||||||
|
case Key.UP:
|
||||||
|
if (shift)
|
||||||
|
DOM.toggleSelectedFile(current);
|
||||||
|
|
||||||
|
DOM.setCurrentFile(prev);
|
||||||
|
event.preventDefault();
|
||||||
|
break;
|
||||||
|
|
||||||
|
/* in case of pressing button 'down', *
|
||||||
|
* select next row */
|
||||||
|
case Key.DOWN:
|
||||||
|
if (shift)
|
||||||
|
DOM.toggleSelectedFile(current);
|
||||||
|
|
||||||
|
DOM.setCurrentFile(next);
|
||||||
|
event.preventDefault();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Key.LEFT:
|
||||||
|
if (!alt)
|
||||||
|
return;
|
||||||
|
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
name = Info.panel.getAttribute('data-name');
|
||||||
|
|
||||||
|
if (name === 'js-right')
|
||||||
|
DOM.duplicatePanel();
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Key.RIGHT:
|
||||||
|
if (!alt)
|
||||||
|
return;
|
||||||
|
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
name = Info.panel.getAttribute('data-name');
|
||||||
|
|
||||||
|
if (name === 'js-left')
|
||||||
|
DOM.duplicatePanel();
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
/* in case of pressing button 'Home', *
|
||||||
|
* go to top element */
|
||||||
|
case Key.HOME:
|
||||||
|
DOM.setCurrentFile(Info.first);
|
||||||
|
event.preventDefault();
|
||||||
|
break;
|
||||||
|
|
||||||
|
/* in case of pressing button 'End', select last element */
|
||||||
|
case Key.END:
|
||||||
|
DOM.setCurrentFile(Info.last);
|
||||||
|
event.preventDefault();
|
||||||
|
break;
|
||||||
|
|
||||||
|
/* если нажали клавишу page down проматываем экран */
|
||||||
|
case Key.PAGE_DOWN:
|
||||||
|
DOM.scrollByPages(panel, 1);
|
||||||
|
|
||||||
|
for (i = 0; i < 30; i++) {
|
||||||
|
if (!current.nextSibling)
|
||||||
|
break;
|
||||||
|
|
||||||
|
current = current.nextSibling;
|
||||||
|
}
|
||||||
|
|
||||||
|
DOM.setCurrentFile(current);
|
||||||
|
event.preventDefault();
|
||||||
|
break;
|
||||||
|
|
||||||
|
/* если нажали клавишу page up проматываем экран */
|
||||||
|
case Key.PAGE_UP:
|
||||||
|
DOM.scrollByPages(panel, -1);
|
||||||
|
|
||||||
|
for (i = 0; i < 30; i++) {
|
||||||
|
if (!current.previousSibling)
|
||||||
|
break;
|
||||||
|
|
||||||
|
current = current.previousSibling;
|
||||||
|
}
|
||||||
|
|
||||||
|
DOM.setCurrentFile(current);
|
||||||
|
event.preventDefault();
|
||||||
|
break;
|
||||||
|
|
||||||
|
/* open directory */
|
||||||
|
case Key.ENTER:
|
||||||
|
if (Info.isDir)
|
||||||
|
CloudCmd.loadDir({
|
||||||
|
path: path === '/' ? '/' : path + '/'
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Key.BACKSPACE:
|
||||||
|
CloudCmd.goToParentDir();
|
||||||
|
event.preventDefault();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Key.BACKSLASH:
|
||||||
|
if (ctrlMeta)
|
||||||
|
CloudCmd.loadDir({
|
||||||
|
path: '/'
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Key.A:
|
||||||
|
if (ctrlMeta) {
|
||||||
|
DOM.selectAllFiles();
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Key.G:
|
||||||
|
if (alt) {
|
||||||
|
DOM.goToDirectory();
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Key.M:
|
||||||
|
if (ctrlMeta) {
|
||||||
|
CloudCmd.EditNames.show();
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* обновляем страницу,
|
||||||
|
* загружаем содержимое каталога
|
||||||
|
* при этом данные берём всегда с
|
||||||
|
* сервера, а не из кэша
|
||||||
|
* (обновляем кэш)
|
||||||
|
*/
|
||||||
|
case Key.R:
|
||||||
|
if (ctrlMeta) {
|
||||||
|
CloudCmd.log('reloading page...\n');
|
||||||
|
CloudCmd.refresh();
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Key.C:
|
||||||
|
if (ctrlMeta)
|
||||||
|
Buffer.copy();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Key.X:
|
||||||
|
if (ctrlMeta)
|
||||||
|
Buffer.cut();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Key.V:
|
||||||
|
if (ctrlMeta)
|
||||||
|
Buffer.paste();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Key.Z:
|
||||||
|
if (ctrlMeta)
|
||||||
|
Buffer.clear();
|
||||||
|
break;
|
||||||
|
|
||||||
|
/* чистим хранилище */
|
||||||
|
case Key.D:
|
||||||
|
if (ctrlMeta) {
|
||||||
|
CloudCmd.log('clearing storage...');
|
||||||
|
|
||||||
|
DOM.Storage.clear(() => {
|
||||||
|
CloudCmd.log('storage cleared');
|
||||||
|
});
|
||||||
|
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
export const createBinder = () => {
|
|
||||||
let binded = false;
|
|
||||||
|
|
||||||
return {
|
|
||||||
isBind() {
|
|
||||||
return binded;
|
|
||||||
},
|
|
||||||
setBind() {
|
|
||||||
binded = true;
|
|
||||||
},
|
|
||||||
unsetBind() {
|
|
||||||
binded = false;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
@ -1,28 +0,0 @@
|
||||||
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();
|
|
||||||
});
|
|
||||||
|
|
@ -1,544 +0,0 @@
|
||||||
/* global CloudCmd, DOM */
|
|
||||||
import clipboard from '@cloudcmd/clipboard';
|
|
||||||
import {fullstore} from 'fullstore';
|
|
||||||
import * as Events from '#dom/events';
|
|
||||||
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 (!config('vim') && keyCode === KEY.ESC)
|
|
||||||
_config('vim', true);
|
|
||||||
};
|
|
||||||
|
|
||||||
const isUndefined = (a) => typeof a === 'undefined';
|
|
||||||
|
|
||||||
Chars([]);
|
|
||||||
|
|
||||||
const {assign} = Object;
|
|
||||||
const binder = createBinder();
|
|
||||||
|
|
||||||
const bind = () => {
|
|
||||||
Events.addKey(listener, true);
|
|
||||||
binder.setBind();
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Key = assign(binder, KEY, {
|
|
||||||
bind,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const _listener = listener;
|
|
||||||
|
|
||||||
function getChar(event) {
|
|
||||||
/*
|
|
||||||
* event.keyIdentifier deprecated in chrome v51
|
|
||||||
* but event.key is absent in chrome <= v51
|
|
||||||
*/
|
|
||||||
const {
|
|
||||||
key,
|
|
||||||
shift,
|
|
||||||
keyCode,
|
|
||||||
keyIdentifier,
|
|
||||||
} = event;
|
|
||||||
|
|
||||||
const char = key || fromCharCode(keyIdentifier);
|
|
||||||
const symbol = getSymbol(shift, keyCode);
|
|
||||||
|
|
||||||
return [symbol, char];
|
|
||||||
}
|
|
||||||
|
|
||||||
async function listener(event, overrides = {}) {
|
|
||||||
const {
|
|
||||||
config = CloudCmd.config,
|
|
||||||
_config = CloudCmd._config,
|
|
||||||
switchKey = _switchKey,
|
|
||||||
vim = _vim,
|
|
||||||
} = overrides;
|
|
||||||
|
|
||||||
const {keyCode} = event;
|
|
||||||
|
|
||||||
// strange chrome bug calls listener twice
|
|
||||||
// in second time event misses a lot fields
|
|
||||||
if (isUndefined(event.altKey))
|
|
||||||
return;
|
|
||||||
|
|
||||||
const alt = event.altKey;
|
|
||||||
const ctrl = event.ctrlKey;
|
|
||||||
const meta = event.metaKey;
|
|
||||||
const isBetween = keyCode >= KEY.ZERO && keyCode <= KEY.Z;
|
|
||||||
const isNumpad = /Numpad/.test(event.code);
|
|
||||||
|
|
||||||
const [symbol, char] = getChar(event);
|
|
||||||
|
|
||||||
if (!binder.isBind())
|
|
||||||
return;
|
|
||||||
|
|
||||||
toggleVim(keyCode, {
|
|
||||||
config,
|
|
||||||
_config,
|
|
||||||
});
|
|
||||||
|
|
||||||
const isVim = config('vim');
|
|
||||||
|
|
||||||
if (!isVim && !isNumpad && !alt && !ctrl && !meta && (isBetween || symbol))
|
|
||||||
return setCurrentByChar(char, Chars);
|
|
||||||
|
|
||||||
Chars([]);
|
|
||||||
await switchKey(event);
|
|
||||||
|
|
||||||
if (keyCode >= KEY.F1 && keyCode <= KEY.F10)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (isVim)
|
|
||||||
vim(char, event);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getSymbol(shift, keyCode) {
|
|
||||||
switch(keyCode) {
|
|
||||||
case KEY.DOT:
|
|
||||||
return '.';
|
|
||||||
|
|
||||||
case KEY.HYPHEN:
|
|
||||||
return shift ? '_' : '-';
|
|
||||||
|
|
||||||
case KEY.EQUAL:
|
|
||||||
return shift ? '+' : '=';
|
|
||||||
}
|
|
||||||
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
function fromCharCode(keyIdentifier) {
|
|
||||||
const code = keyIdentifier.substring(2);
|
|
||||||
const hex = parseInt(code, 16);
|
|
||||||
|
|
||||||
return String.fromCharCode(hex);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function _switchKey(event) {
|
|
||||||
const Info = DOM.CurrentInfo;
|
|
||||||
let i;
|
|
||||||
let isSelected;
|
|
||||||
let prev;
|
|
||||||
let next;
|
|
||||||
let current = Info.element;
|
|
||||||
let dataName;
|
|
||||||
|
|
||||||
const {
|
|
||||||
name,
|
|
||||||
panel,
|
|
||||||
path,
|
|
||||||
isDir,
|
|
||||||
} = Info;
|
|
||||||
|
|
||||||
const {
|
|
||||||
Operation,
|
|
||||||
changeDir,
|
|
||||||
config,
|
|
||||||
} = CloudCmd;
|
|
||||||
|
|
||||||
const {keyCode} = event;
|
|
||||||
|
|
||||||
const alt = event.altKey;
|
|
||||||
const shift = event.shiftKey;
|
|
||||||
const ctrl = event.ctrlKey;
|
|
||||||
const meta = event.metaKey;
|
|
||||||
const ctrlMeta = ctrl || meta;
|
|
||||||
|
|
||||||
if (current) {
|
|
||||||
prev = current.previousSibling;
|
|
||||||
next = current.nextSibling;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch(keyCode) {
|
|
||||||
case KEY.TAB:
|
|
||||||
DOM.changePanel();
|
|
||||||
event.preventDefault();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case KEY.INSERT:
|
|
||||||
DOM
|
|
||||||
.toggleSelectedFile(current)
|
|
||||||
.setCurrentFile(next);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case KEY.INSERT_MAC:
|
|
||||||
DOM
|
|
||||||
.toggleSelectedFile(current)
|
|
||||||
.setCurrentFile(next);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case KEY.DELETE:
|
|
||||||
if (shift)
|
|
||||||
Operation.show('delete:silent');
|
|
||||||
else
|
|
||||||
Operation.show('delete');
|
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
case KEY.ASTERISK:
|
|
||||||
DOM.toggleAllSelectedFiles(current);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case KEY.PLUS:
|
|
||||||
DOM.expandSelection();
|
|
||||||
event.preventDefault();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case KEY.MINUS:
|
|
||||||
DOM.shrinkSelection();
|
|
||||||
event.preventDefault();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case KEY.F1:
|
|
||||||
CloudCmd.Help.show();
|
|
||||||
event.preventDefault();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case KEY.F2:
|
|
||||||
CloudCmd.UserMenu.show();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case KEY.F3:
|
|
||||||
event.preventDefault();
|
|
||||||
|
|
||||||
if (Info.isDir)
|
|
||||||
await changeDir(path);
|
|
||||||
else if (shift)
|
|
||||||
CloudCmd.View.show(null, {
|
|
||||||
raw: true,
|
|
||||||
});
|
|
||||||
else if (ctrlMeta)
|
|
||||||
CloudCmd.sortPanel('name');
|
|
||||||
else
|
|
||||||
CloudCmd.View.show();
|
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
case KEY.F4:
|
|
||||||
if (config('vim'))
|
|
||||||
CloudCmd.EditFileVim.show();
|
|
||||||
else
|
|
||||||
CloudCmd.EditFile.show();
|
|
||||||
|
|
||||||
event.preventDefault();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case KEY.F5:
|
|
||||||
if (ctrlMeta)
|
|
||||||
CloudCmd.sortPanel('date');
|
|
||||||
else if (alt)
|
|
||||||
Operation.show('pack');
|
|
||||||
else
|
|
||||||
Operation.show('copy');
|
|
||||||
|
|
||||||
event.preventDefault();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case KEY.F6:
|
|
||||||
if (ctrlMeta)
|
|
||||||
CloudCmd.sortPanel('size');
|
|
||||||
else if (shift)
|
|
||||||
DOM.renameCurrent(current);
|
|
||||||
else
|
|
||||||
Operation.show('move');
|
|
||||||
|
|
||||||
event.preventDefault();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case KEY.F7:
|
|
||||||
if (shift)
|
|
||||||
DOM.promptNewFile();
|
|
||||||
else
|
|
||||||
DOM.promptNewDir();
|
|
||||||
|
|
||||||
event.preventDefault();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case KEY.F8:
|
|
||||||
Operation.show('delete');
|
|
||||||
event.preventDefault();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case KEY.F9:
|
|
||||||
if (alt)
|
|
||||||
Operation.show('extract');
|
|
||||||
else
|
|
||||||
CloudCmd.Menu.show();
|
|
||||||
|
|
||||||
event.preventDefault();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case KEY.F10:
|
|
||||||
CloudCmd.Config.show();
|
|
||||||
event.preventDefault();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case KEY.TRA:
|
|
||||||
event.preventDefault();
|
|
||||||
|
|
||||||
if (shift)
|
|
||||||
return CloudCmd.Terminal.show();
|
|
||||||
|
|
||||||
CloudCmd.Konsole.show();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case KEY.BRACKET_CLOSE:
|
|
||||||
CloudCmd.Konsole.show();
|
|
||||||
event.preventDefault();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case KEY.SPACE:
|
|
||||||
event.preventDefault();
|
|
||||||
|
|
||||||
if (!isDir || name === '..')
|
|
||||||
isSelected = true;
|
|
||||||
else
|
|
||||||
isSelected = DOM.isSelected(current);
|
|
||||||
|
|
||||||
if (!isSelected)
|
|
||||||
await DOM.loadCurrentSize(current);
|
|
||||||
|
|
||||||
DOM.toggleSelectedFile(current);
|
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
case KEY.U:
|
|
||||||
if (ctrlMeta) {
|
|
||||||
DOM.swapPanels();
|
|
||||||
event.preventDefault();
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
/* navigation on file table: *
|
|
||||||
* in case of pressing button 'up', *
|
|
||||||
* select previous row */
|
|
||||||
case KEY.UP:
|
|
||||||
if (shift)
|
|
||||||
DOM.toggleSelectedFile(current);
|
|
||||||
|
|
||||||
DOM.setCurrentFile(prev);
|
|
||||||
event.preventDefault();
|
|
||||||
break;
|
|
||||||
|
|
||||||
/* in case of pressing button 'down', *
|
|
||||||
* select next row */
|
|
||||||
case KEY.DOWN:
|
|
||||||
if (shift)
|
|
||||||
DOM.toggleSelectedFile(current);
|
|
||||||
|
|
||||||
DOM.setCurrentFile(next);
|
|
||||||
event.preventDefault();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case KEY.LEFT:
|
|
||||||
if (!alt)
|
|
||||||
return;
|
|
||||||
|
|
||||||
event.preventDefault();
|
|
||||||
|
|
||||||
dataName = Info.panel.getAttribute('data-name');
|
|
||||||
|
|
||||||
if (dataName === 'js-right')
|
|
||||||
DOM.duplicatePanel();
|
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
case KEY.RIGHT:
|
|
||||||
if (!alt)
|
|
||||||
return;
|
|
||||||
|
|
||||||
event.preventDefault();
|
|
||||||
|
|
||||||
dataName = Info.panel.getAttribute('data-name');
|
|
||||||
|
|
||||||
if (dataName === 'js-left')
|
|
||||||
DOM.duplicatePanel();
|
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
/* in case of pressing button 'Home', *
|
|
||||||
* go to top element */
|
|
||||||
case KEY.HOME:
|
|
||||||
DOM.setCurrentFile(Info.first);
|
|
||||||
event.preventDefault();
|
|
||||||
break;
|
|
||||||
|
|
||||||
/* in case of pressing button 'End', select last element */
|
|
||||||
case KEY.END:
|
|
||||||
DOM.setCurrentFile(Info.last);
|
|
||||||
event.preventDefault();
|
|
||||||
break;
|
|
||||||
|
|
||||||
/* если нажали клавишу page down проматываем экран */
|
|
||||||
case KEY.PAGE_DOWN:
|
|
||||||
DOM.scrollByPages(panel, 1);
|
|
||||||
|
|
||||||
for (i = 0; i < 30; i++) {
|
|
||||||
if (!current.nextSibling)
|
|
||||||
break;
|
|
||||||
|
|
||||||
current = current.nextSibling;
|
|
||||||
}
|
|
||||||
|
|
||||||
DOM.setCurrentFile(current);
|
|
||||||
event.preventDefault();
|
|
||||||
break;
|
|
||||||
|
|
||||||
/* если нажали клавишу page up проматываем экран */
|
|
||||||
case KEY.PAGE_UP:
|
|
||||||
DOM.scrollByPages(panel, -1);
|
|
||||||
|
|
||||||
for (i = 0; i < 30; i++) {
|
|
||||||
if (!current.previousSibling)
|
|
||||||
break;
|
|
||||||
|
|
||||||
current = current.previousSibling;
|
|
||||||
}
|
|
||||||
|
|
||||||
DOM.setCurrentFile(current);
|
|
||||||
event.preventDefault();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case KEY.ENTER:
|
|
||||||
if (Info.isDir)
|
|
||||||
await changeDir(path);
|
|
||||||
else
|
|
||||||
CloudCmd.View.show();
|
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
case KEY.BACKSPACE:
|
|
||||||
CloudCmd.goToParentDir();
|
|
||||||
event.preventDefault();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case KEY.BACKSLASH:
|
|
||||||
if (ctrlMeta)
|
|
||||||
await changeDir('/');
|
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
case KEY.A:
|
|
||||||
if (ctrlMeta) {
|
|
||||||
DOM.selectAllFiles();
|
|
||||||
event.preventDefault();
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
case KEY.G:
|
|
||||||
if (alt) {
|
|
||||||
DOM.goToDirectory();
|
|
||||||
event.preventDefault();
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
case KEY.L:
|
|
||||||
if (ctrlMeta && shift) {
|
|
||||||
CloudCmd.logOut();
|
|
||||||
event.preventDefault();
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
case KEY.M:
|
|
||||||
if (ctrlMeta) {
|
|
||||||
if (config('vim'))
|
|
||||||
CloudCmd.EditNamesVim.show();
|
|
||||||
else
|
|
||||||
CloudCmd.EditNames.show();
|
|
||||||
|
|
||||||
event.preventDefault();
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
case KEY.P:
|
|
||||||
if (!ctrlMeta)
|
|
||||||
return;
|
|
||||||
|
|
||||||
event.preventDefault();
|
|
||||||
clipboard
|
|
||||||
.writeText(Info.dirPath)
|
|
||||||
.catch(CloudCmd.log);
|
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* обновляем страницу,
|
|
||||||
* загружаем содержимое каталога
|
|
||||||
* при этом данные берём всегда с
|
|
||||||
* сервера, а не из кэша
|
|
||||||
* (обновляем кэш)
|
|
||||||
*/
|
|
||||||
case KEY.R:
|
|
||||||
if (ctrlMeta) {
|
|
||||||
CloudCmd.log('reloading page...\n');
|
|
||||||
CloudCmd.refresh();
|
|
||||||
event.preventDefault();
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
case KEY.C:
|
|
||||||
if (ctrlMeta)
|
|
||||||
Buffer.copy();
|
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
case KEY.X:
|
|
||||||
if (ctrlMeta)
|
|
||||||
Buffer.cut();
|
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
case KEY.V:
|
|
||||||
if (ctrlMeta)
|
|
||||||
Buffer.paste();
|
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
case KEY.Z:
|
|
||||||
if (ctrlMeta)
|
|
||||||
Buffer.clear();
|
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
case KEY.COLON:
|
|
||||||
CloudCmd.CommandLine.show();
|
|
||||||
event.preventDefault();
|
|
||||||
break;
|
|
||||||
|
|
||||||
/* чистим хранилище */
|
|
||||||
case KEY.D:
|
|
||||||
if (ctrlMeta) {
|
|
||||||
CloudCmd.log('clearing storage...');
|
|
||||||
await DOM.Storage.clear();
|
|
||||||
CloudCmd.log('storage cleared');
|
|
||||||
event.preventDefault();
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
case KEY.DOT:
|
|
||||||
if (meta && shift) {
|
|
||||||
const showDotFiles = !CloudCmd.config('showDotFiles');
|
|
||||||
CloudCmd._config('showDotFiles', showDotFiles);
|
|
||||||
CloudCmd.refresh();
|
|
||||||
await DOM.RESTful.Config.write({
|
|
||||||
showDotFiles,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,56 +0,0 @@
|
||||||
import autoGlobals from 'auto-globals';
|
|
||||||
import supertape from 'supertape';
|
|
||||||
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;
|
|
||||||
|
|
||||||
globalThis.DOM = getDOM();
|
|
||||||
globalThis.CloudCmd = getCloudCmd();
|
|
||||||
|
|
||||||
test('cloudcmd: client: key: enable vim', async (t) => {
|
|
||||||
const vim = stub();
|
|
||||||
const config = stub().returns(true);
|
|
||||||
const _config = stub();
|
|
||||||
|
|
||||||
const event = {
|
|
||||||
keyCode: ESC,
|
|
||||||
key: 'Escape',
|
|
||||||
altKey: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
Key.setBind();
|
|
||||||
|
|
||||||
await _listener(event, {
|
|
||||||
vim,
|
|
||||||
config,
|
|
||||||
_config,
|
|
||||||
switchKey: stub(),
|
|
||||||
});
|
|
||||||
|
|
||||||
t.calledWith(vim, ['Escape', event]);
|
|
||||||
t.end();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('cloudcmd: client: key: disable vim', async (t) => {
|
|
||||||
const _config = stub();
|
|
||||||
const config = stub();
|
|
||||||
|
|
||||||
const event = {
|
|
||||||
keyCode: ESC,
|
|
||||||
key: 'Escape',
|
|
||||||
altKey: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
Key.setBind();
|
|
||||||
await _listener(event, {
|
|
||||||
config,
|
|
||||||
_config,
|
|
||||||
switchKey: stub(),
|
|
||||||
});
|
|
||||||
|
|
||||||
t.calledWith(_config, ['vim', true]);
|
|
||||||
t.end();
|
|
||||||
});
|
|
||||||
|
|
@ -1,58 +0,0 @@
|
||||||
export const BACKSPACE = 8;
|
|
||||||
export const TAB = 9;
|
|
||||||
export const ENTER = 13;
|
|
||||||
export const CAPSLOCK = 20;
|
|
||||||
export const ESC = 27;
|
|
||||||
export const SPACE = 32;
|
|
||||||
export const PAGE_UP = 33;
|
|
||||||
export const PAGE_DOWN = 34;
|
|
||||||
export const END = 35;
|
|
||||||
export const HOME = 36;
|
|
||||||
export const LEFT = 37;
|
|
||||||
export const UP = 38;
|
|
||||||
export const RIGHT = 39;
|
|
||||||
export const DOWN = 40;
|
|
||||||
export const INSERT = 45;
|
|
||||||
export const DELETE = 46;
|
|
||||||
export const ZERO = 48;
|
|
||||||
export const SEMICOLON = 52;
|
|
||||||
export const A = 65;
|
|
||||||
export const C = 67;
|
|
||||||
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;
|
|
||||||
export const Q = 81;
|
|
||||||
export const R = 82;
|
|
||||||
export const S = 83;
|
|
||||||
export const T = 84;
|
|
||||||
export const U = 85;
|
|
||||||
export const V = 86;
|
|
||||||
export const X = 88;
|
|
||||||
export const Z = 90;
|
|
||||||
export const INSERT_MAC = 96;
|
|
||||||
export const ASTERISK = 106;
|
|
||||||
export const PLUS = 107;
|
|
||||||
export const MINUS = 109;
|
|
||||||
export const F1 = 112;
|
|
||||||
export const F2 = 113;
|
|
||||||
export const F3 = 114;
|
|
||||||
export const F4 = 115;
|
|
||||||
export const F5 = 116;
|
|
||||||
export const F6 = 117;
|
|
||||||
export const F7 = 118;
|
|
||||||
export const F8 = 119;
|
|
||||||
export const F9 = 120;
|
|
||||||
export const F10 = 121;
|
|
||||||
export const COLON = 186;
|
|
||||||
export const EQUAL = 187;
|
|
||||||
export const HYPHEN = 189;
|
|
||||||
export const DOT = 190;
|
|
||||||
export const SLASH = 191;
|
|
||||||
export const TRA = 192;
|
|
||||||
export const BACKSLASH = 220;
|
|
||||||
export const BRACKET_CLOSE = 221;
|
|
||||||
|
|
@ -1,57 +0,0 @@
|
||||||
/* global DOM */
|
|
||||||
import {escapeRegExp} from '#common/util';
|
|
||||||
|
|
||||||
export default function setCurrentByChar(char, charStore) {
|
|
||||||
const Info = DOM.CurrentInfo;
|
|
||||||
let firstByName;
|
|
||||||
let skipCount = 0;
|
|
||||||
let set = false;
|
|
||||||
let i = 0;
|
|
||||||
|
|
||||||
const escapeChar = escapeRegExp(char);
|
|
||||||
const regExp = new RegExp(`^${escapeChar}.*$`, 'i');
|
|
||||||
const {files} = Info;
|
|
||||||
const chars = charStore();
|
|
||||||
const n = chars.length;
|
|
||||||
|
|
||||||
while (i < n && char === chars[i])
|
|
||||||
i++;
|
|
||||||
|
|
||||||
if (!i)
|
|
||||||
charStore([]);
|
|
||||||
|
|
||||||
const skipN = skipCount = i;
|
|
||||||
|
|
||||||
charStore(charStore().concat(char));
|
|
||||||
|
|
||||||
const names = DOM.getFilenames(files);
|
|
||||||
const isTest = (a) => regExp.test(a);
|
|
||||||
const isRoot = (a) => a === '..';
|
|
||||||
const not = (f) => (a) => !f(a);
|
|
||||||
|
|
||||||
const setCurrent = (name) => {
|
|
||||||
const byName = DOM.getCurrentByName(name);
|
|
||||||
|
|
||||||
if (!skipCount) {
|
|
||||||
set = true;
|
|
||||||
DOM.setCurrentFile(byName);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (skipN === skipCount)
|
|
||||||
firstByName = byName;
|
|
||||||
|
|
||||||
--skipCount;
|
|
||||||
};
|
|
||||||
|
|
||||||
names
|
|
||||||
.filter(isTest)
|
|
||||||
.filter(not(isRoot))
|
|
||||||
.some(setCurrent);
|
|
||||||
|
|
||||||
if (!set) {
|
|
||||||
DOM.setCurrentFile(firstByName);
|
|
||||||
charStore([char]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,47 +0,0 @@
|
||||||
import {fullstore} from 'fullstore';
|
|
||||||
import limier from 'limier';
|
|
||||||
|
|
||||||
const searchStore = fullstore([]);
|
|
||||||
const searchIndex = fullstore(0);
|
|
||||||
|
|
||||||
export const find = (value, names) => {
|
|
||||||
const result = limier(value, names);
|
|
||||||
|
|
||||||
searchStore(result);
|
|
||||||
searchIndex(0);
|
|
||||||
|
|
||||||
return result;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const findNext = () => {
|
|
||||||
const names = searchStore();
|
|
||||||
const index = next(searchIndex(), names.length);
|
|
||||||
|
|
||||||
searchIndex(index);
|
|
||||||
return names[searchIndex()];
|
|
||||||
};
|
|
||||||
|
|
||||||
export const findPrevious = () => {
|
|
||||||
const names = searchStore();
|
|
||||||
const index = previous(searchIndex(), names.length);
|
|
||||||
|
|
||||||
searchIndex(index);
|
|
||||||
return names[index];
|
|
||||||
};
|
|
||||||
|
|
||||||
export const _next = next;
|
|
||||||
export const _previous = previous;
|
|
||||||
|
|
||||||
function next(index, length) {
|
|
||||||
if (index === length - 1)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
return ++index;
|
|
||||||
}
|
|
||||||
|
|
||||||
function previous(index, length) {
|
|
||||||
if (!index)
|
|
||||||
return length - 1;
|
|
||||||
|
|
||||||
return --index;
|
|
||||||
}
|
|
||||||
|
|
@ -1,55 +0,0 @@
|
||||||
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();
|
|
||||||
});
|
|
||||||
|
|
@ -1,46 +0,0 @@
|
||||||
const noop = () => {};
|
|
||||||
|
|
||||||
export const getDOM = () => {
|
|
||||||
const prompt = Promise.resolve.bind(Promise);
|
|
||||||
const CurrentInfo = {
|
|
||||||
element: {},
|
|
||||||
files: [],
|
|
||||||
};
|
|
||||||
|
|
||||||
const Buffer = {
|
|
||||||
copy: noop,
|
|
||||||
paste: noop,
|
|
||||||
};
|
|
||||||
|
|
||||||
const Dialog = {
|
|
||||||
prompt,
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
|
||||||
Buffer,
|
|
||||||
CurrentInfo,
|
|
||||||
Dialog,
|
|
||||||
selectFile: noop,
|
|
||||||
unselectFile: noop,
|
|
||||||
unselectFiles: noop,
|
|
||||||
setCurrentFile: noop,
|
|
||||||
getCurrentName: noop,
|
|
||||||
setCurrentByName: noop,
|
|
||||||
toggleSelectedFile: noop,
|
|
||||||
prompNewDirectory: noop,
|
|
||||||
promptNewFile: noop,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getCloudCmd = () => {
|
|
||||||
const show = () => {};
|
|
||||||
|
|
||||||
return {
|
|
||||||
Operation: {
|
|
||||||
show,
|
|
||||||
},
|
|
||||||
|
|
||||||
config: noop,
|
|
||||||
_config: noop,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
@ -1,167 +0,0 @@
|
||||||
import vim from './vim.js';
|
|
||||||
import * as finder from './find.js';
|
|
||||||
import {
|
|
||||||
setCurrent,
|
|
||||||
selectFileNotParent,
|
|
||||||
} from './set-current.js';
|
|
||||||
|
|
||||||
export default (key, event, overrides = {}) => {
|
|
||||||
const defaults = {
|
|
||||||
...globalThis.DOM,
|
|
||||||
...globalThis.CloudCmd,
|
|
||||||
};
|
|
||||||
|
|
||||||
const deps = {
|
|
||||||
...defaults,
|
|
||||||
...overrides,
|
|
||||||
};
|
|
||||||
|
|
||||||
const operations = getOperations(event, deps);
|
|
||||||
|
|
||||||
vim(key, operations, deps);
|
|
||||||
};
|
|
||||||
|
|
||||||
const getOperations = (event, deps) => {
|
|
||||||
const {
|
|
||||||
Info = globalThis.DOM.CurrentInfo,
|
|
||||||
CloudCmd = globalThis.CloudCmd,
|
|
||||||
Operation,
|
|
||||||
unselectFiles,
|
|
||||||
setCurrentFile,
|
|
||||||
setCurrentByName,
|
|
||||||
getCurrentName,
|
|
||||||
prompt = globalThis.DOM.Dialog.prompt,
|
|
||||||
preventDefault = event?.preventDefault?.bind(event),
|
|
||||||
stopImmediatePropagation = event?.preventDefault?.bind(event),
|
|
||||||
promptNewFile = globalThis.DOM.promptNewFile,
|
|
||||||
toggleSelectedFile,
|
|
||||||
Buffer = {},
|
|
||||||
createFindNext = _createFindNext,
|
|
||||||
createFindPrevious = _createFindPrevious,
|
|
||||||
createMakeFile = _createMakeFile,
|
|
||||||
renameCurrent,
|
|
||||||
} = deps;
|
|
||||||
|
|
||||||
return {
|
|
||||||
makeFile: createMakeFile({
|
|
||||||
promptNewFile,
|
|
||||||
preventDefault,
|
|
||||||
stopImmediatePropagation,
|
|
||||||
}),
|
|
||||||
findNext: createFindNext({
|
|
||||||
setCurrentByName,
|
|
||||||
}),
|
|
||||||
findPrevious: createFindPrevious({
|
|
||||||
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();
|
|
||||||
globalThis.DOM.promptNewDir();
|
|
||||||
},
|
|
||||||
|
|
||||||
terminal: () => {
|
|
||||||
CloudCmd.Terminal.show();
|
|
||||||
},
|
|
||||||
|
|
||||||
edit: () => {
|
|
||||||
CloudCmd.EditFileVim.show();
|
|
||||||
},
|
|
||||||
|
|
||||||
copy: () => {
|
|
||||||
Buffer.copy();
|
|
||||||
unselectFiles();
|
|
||||||
},
|
|
||||||
|
|
||||||
select: () => {
|
|
||||||
const current = Info.element;
|
|
||||||
toggleSelectedFile(current);
|
|
||||||
},
|
|
||||||
|
|
||||||
paste: Buffer.paste,
|
|
||||||
|
|
||||||
moveNext: ({count, isVisual, isDelete}) => {
|
|
||||||
setCurrent('next', {
|
|
||||||
count,
|
|
||||||
isVisual,
|
|
||||||
isDelete,
|
|
||||||
}, {
|
|
||||||
Info,
|
|
||||||
setCurrentFile,
|
|
||||||
unselectFiles,
|
|
||||||
Operation,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
movePrevious: ({count, isVisual, isDelete}) => {
|
|
||||||
setCurrent('previous', {
|
|
||||||
count,
|
|
||||||
isVisual,
|
|
||||||
isDelete,
|
|
||||||
}, {
|
|
||||||
Info,
|
|
||||||
setCurrentFile,
|
|
||||||
unselectFiles,
|
|
||||||
Operation,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
find: async () => {
|
|
||||||
preventDefault();
|
|
||||||
const [, value] = await prompt('Find', '');
|
|
||||||
|
|
||||||
if (!value)
|
|
||||||
return;
|
|
||||||
|
|
||||||
const names = Info.files.map(getCurrentName);
|
|
||||||
const [result] = finder.find(value, names);
|
|
||||||
|
|
||||||
setCurrentByName(result);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export const selectFile = selectFileNotParent;
|
|
||||||
|
|
||||||
const _createFindPrevious = (overrides = {}) => () => {
|
|
||||||
const {setCurrentByName} = overrides;
|
|
||||||
const name = finder.findPrevious();
|
|
||||||
|
|
||||||
setCurrentByName(name);
|
|
||||||
};
|
|
||||||
|
|
||||||
const _createFindNext = (overrides = {}) => () => {
|
|
||||||
const {setCurrentByName} = overrides;
|
|
||||||
const name = finder.findNext();
|
|
||||||
|
|
||||||
setCurrentByName(name);
|
|
||||||
};
|
|
||||||
|
|
||||||
const _createMakeFile = (overrides = {}) => () => {
|
|
||||||
const {
|
|
||||||
promptNewFile,
|
|
||||||
stopImmediatePropagation,
|
|
||||||
preventDefault,
|
|
||||||
} = overrides;
|
|
||||||
|
|
||||||
stopImmediatePropagation();
|
|
||||||
preventDefault();
|
|
||||||
promptNewFile();
|
|
||||||
};
|
|
||||||
|
|
@ -1,756 +0,0 @@
|
||||||
import {test, stub} from 'supertape';
|
|
||||||
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();
|
|
||||||
|
|
||||||
const {assign} = Object;
|
|
||||||
const {DOM} = globalThis;
|
|
||||||
const {Buffer} = DOM;
|
|
||||||
|
|
||||||
test('cloudcmd: client: key: set next file: no', (t) => {
|
|
||||||
const element = {};
|
|
||||||
const setCurrentFile = stub();
|
|
||||||
const unselectFiles = stub();
|
|
||||||
|
|
||||||
const Info = {
|
|
||||||
element,
|
|
||||||
};
|
|
||||||
|
|
||||||
vim('j', {}, {
|
|
||||||
Info,
|
|
||||||
setCurrentFile,
|
|
||||||
unselectFiles,
|
|
||||||
});
|
|
||||||
|
|
||||||
t.calledWith(setCurrentFile, [element], 'should set next file');
|
|
||||||
t.end();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('cloudcmd: client: key: set next file current: j', async (t) => {
|
|
||||||
const nextSibling = 'hello';
|
|
||||||
const element = {
|
|
||||||
nextSibling,
|
|
||||||
};
|
|
||||||
|
|
||||||
const setCurrentFile = stub();
|
|
||||||
|
|
||||||
const Info = {
|
|
||||||
element,
|
|
||||||
};
|
|
||||||
|
|
||||||
await vim('j', {}, {
|
|
||||||
Info,
|
|
||||||
setCurrentFile,
|
|
||||||
unselectFiles: stub(),
|
|
||||||
});
|
|
||||||
|
|
||||||
t.calledWith(setCurrentFile, [nextSibling], 'should set next file');
|
|
||||||
t.end();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('cloudcmd: client: key: set next file current: mjj', (t) => {
|
|
||||||
const nextSibling = 'hello';
|
|
||||||
const element = {
|
|
||||||
nextSibling,
|
|
||||||
};
|
|
||||||
|
|
||||||
const setCurrentFile = stub();
|
|
||||||
|
|
||||||
const Info = {
|
|
||||||
element,
|
|
||||||
};
|
|
||||||
|
|
||||||
const deps = {
|
|
||||||
Info,
|
|
||||||
setCurrentFile,
|
|
||||||
unselectFiles: stub(),
|
|
||||||
};
|
|
||||||
|
|
||||||
vim('m', {}, deps);
|
|
||||||
vim('j', {}, deps);
|
|
||||||
vim('j', {}, deps);
|
|
||||||
|
|
||||||
t.calledWith(setCurrentFile, [nextSibling], 'should set next file');
|
|
||||||
t.end();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('cloudcmd: client: key: set next file current: g', (t) => {
|
|
||||||
const nextSibling = 'hello';
|
|
||||||
const element = {
|
|
||||||
nextSibling,
|
|
||||||
};
|
|
||||||
|
|
||||||
const setCurrentFile = stub();
|
|
||||||
|
|
||||||
const Info = {
|
|
||||||
element,
|
|
||||||
};
|
|
||||||
|
|
||||||
const deps = {
|
|
||||||
Info,
|
|
||||||
setCurrentFile,
|
|
||||||
unselectFiles: stub(),
|
|
||||||
};
|
|
||||||
|
|
||||||
vim('g', {}, deps);
|
|
||||||
vim('j', {}, deps);
|
|
||||||
|
|
||||||
t.calledWith(setCurrentFile, [nextSibling], 'should ignore g');
|
|
||||||
t.end();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('cloudcmd: client: key: set +2 file current', (t) => {
|
|
||||||
const last = {};
|
|
||||||
const setCurrentFile = stub();
|
|
||||||
const element = {};
|
|
||||||
|
|
||||||
const Info = {
|
|
||||||
element,
|
|
||||||
};
|
|
||||||
|
|
||||||
const deps = {
|
|
||||||
setCurrentFile,
|
|
||||||
Info,
|
|
||||||
unselectFiles: stub(),
|
|
||||||
};
|
|
||||||
|
|
||||||
const event = {};
|
|
||||||
|
|
||||||
vim('2', event, deps);
|
|
||||||
vim('j', event, deps);
|
|
||||||
|
|
||||||
t.calledWith(setCurrentFile, [last], 'should set next file');
|
|
||||||
t.end();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('cloudcmd: client: key: select +2 files from current before delete', (t) => {
|
|
||||||
const last = {};
|
|
||||||
const nextSibling = {
|
|
||||||
nextSibling: last,
|
|
||||||
};
|
|
||||||
|
|
||||||
const element = {
|
|
||||||
nextSibling,
|
|
||||||
};
|
|
||||||
|
|
||||||
const setCurrentFile = stub();
|
|
||||||
|
|
||||||
const Info = {
|
|
||||||
element,
|
|
||||||
};
|
|
||||||
|
|
||||||
const Operation = {
|
|
||||||
show: stub(),
|
|
||||||
};
|
|
||||||
|
|
||||||
const selectFile = stub();
|
|
||||||
const getCurrentName = stub().returns('x');
|
|
||||||
|
|
||||||
const event = {};
|
|
||||||
|
|
||||||
const deps = {
|
|
||||||
Info,
|
|
||||||
setCurrentFile,
|
|
||||||
selectFile,
|
|
||||||
getCurrentName,
|
|
||||||
Operation,
|
|
||||||
};
|
|
||||||
|
|
||||||
vim('d', event, deps);
|
|
||||||
vim('2', event, deps);
|
|
||||||
vim('j', event, deps);
|
|
||||||
|
|
||||||
t.calledWith(setCurrentFile, [last], 'should set next file');
|
|
||||||
t.end();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('cloudcmd: client: key: delete +2 files from current', (t) => {
|
|
||||||
const last = {};
|
|
||||||
const nextSibling = {
|
|
||||||
nextSibling: last,
|
|
||||||
};
|
|
||||||
|
|
||||||
const element = {
|
|
||||||
nextSibling,
|
|
||||||
};
|
|
||||||
|
|
||||||
const setCurrentFile = stub();
|
|
||||||
const show = stub();
|
|
||||||
|
|
||||||
const deps = {
|
|
||||||
Info: {
|
|
||||||
element,
|
|
||||||
},
|
|
||||||
Operation: {
|
|
||||||
show,
|
|
||||||
},
|
|
||||||
setCurrentFile,
|
|
||||||
selectFile: stub(),
|
|
||||||
getCurrentName: stub().returns('x'),
|
|
||||||
unselectFiles: stub(),
|
|
||||||
};
|
|
||||||
|
|
||||||
const event = {};
|
|
||||||
|
|
||||||
vim('d', event, deps);
|
|
||||||
vim('2', event, deps);
|
|
||||||
vim('j', event, deps);
|
|
||||||
|
|
||||||
t.calledWith(show, ['delete'], 'should call delete');
|
|
||||||
t.end();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('cloudcmd: client: key: set previous file current', (t) => {
|
|
||||||
const previousSibling = 'hello';
|
|
||||||
const element = {
|
|
||||||
previousSibling,
|
|
||||||
};
|
|
||||||
|
|
||||||
const setCurrentFile = stub();
|
|
||||||
const unselectFiles = stub();
|
|
||||||
|
|
||||||
const Info = {
|
|
||||||
element,
|
|
||||||
};
|
|
||||||
|
|
||||||
const deps = {
|
|
||||||
Info,
|
|
||||||
setCurrentFile,
|
|
||||||
unselectFiles,
|
|
||||||
};
|
|
||||||
|
|
||||||
vim('k', {}, deps);
|
|
||||||
|
|
||||||
t.calledWith(setCurrentFile, [previousSibling], 'should set previous file');
|
|
||||||
t.end();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('cloudcmd: client: key: copy: no', (t) => {
|
|
||||||
const copy = stub();
|
|
||||||
|
|
||||||
vim('y', {}, {
|
|
||||||
unselectFiles: stub(),
|
|
||||||
Buffer: {
|
|
||||||
copy,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
t.notCalled(copy, 'should not copy files');
|
|
||||||
t.end();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('cloudcmd: client: key: copy', (t) => {
|
|
||||||
const copy = stub();
|
|
||||||
const Info = {
|
|
||||||
element: {},
|
|
||||||
};
|
|
||||||
|
|
||||||
const toggleSelectedFile = stub();
|
|
||||||
const unselectFiles = stub();
|
|
||||||
|
|
||||||
const deps = {
|
|
||||||
Info,
|
|
||||||
unselectFiles,
|
|
||||||
toggleSelectedFile,
|
|
||||||
Buffer: {
|
|
||||||
copy,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
vim('v', {}, deps);
|
|
||||||
vim('y', {}, deps);
|
|
||||||
|
|
||||||
t.calledWithNoArgs(copy, 'should copy files');
|
|
||||||
t.end();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('cloudcmd: client: key: copy: unselectFiles', (t) => {
|
|
||||||
const unselectFiles = stub();
|
|
||||||
const Info = {
|
|
||||||
element: {},
|
|
||||||
};
|
|
||||||
|
|
||||||
const toggleSelectedFile = stub();
|
|
||||||
|
|
||||||
const deps = {
|
|
||||||
Info,
|
|
||||||
unselectFiles,
|
|
||||||
toggleSelectedFile,
|
|
||||||
Buffer: {
|
|
||||||
copy: stub(),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
vim('v', {}, deps);
|
|
||||||
vim('y', {}, deps);
|
|
||||||
|
|
||||||
t.calledWithNoArgs(unselectFiles, 'should unselect files');
|
|
||||||
t.end();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('cloudcmd: client: key: paste', (t) => {
|
|
||||||
const paste = stub();
|
|
||||||
|
|
||||||
Buffer.paste = paste;
|
|
||||||
|
|
||||||
vim('p', {}, {
|
|
||||||
Buffer,
|
|
||||||
});
|
|
||||||
|
|
||||||
t.calledWithNoArgs(paste, 'should paste files');
|
|
||||||
t.end();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('cloudcmd: client: key: selectFile: ..', (t) => {
|
|
||||||
const getCurrentName = stub().returns('..');
|
|
||||||
const selectFile = stub();
|
|
||||||
const current = {};
|
|
||||||
|
|
||||||
vimSelectFile(current, {
|
|
||||||
selectFile,
|
|
||||||
getCurrentName,
|
|
||||||
});
|
|
||||||
|
|
||||||
t.notCalled(selectFile, 'should not call selectFile');
|
|
||||||
t.end();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('cloudcmd: client: key: selectFile', (t) => {
|
|
||||||
const selectFile = stub();
|
|
||||||
const getCurrentName = stub().returns('x');
|
|
||||||
const current = {};
|
|
||||||
|
|
||||||
vimSelectFile(current, {
|
|
||||||
selectFile,
|
|
||||||
getCurrentName,
|
|
||||||
});
|
|
||||||
|
|
||||||
t.calledWith(selectFile, [current], 'should call selectFile');
|
|
||||||
t.end();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('cloudcmd: client: key: set last file current: shift + g', async (t) => {
|
|
||||||
const last = 'last';
|
|
||||||
const nextSibling = {
|
|
||||||
nextSibling: last,
|
|
||||||
};
|
|
||||||
|
|
||||||
const element = {
|
|
||||||
nextSibling,
|
|
||||||
};
|
|
||||||
|
|
||||||
const setCurrentFile = stub();
|
|
||||||
|
|
||||||
await vim('G', {}, {
|
|
||||||
Info: {
|
|
||||||
element,
|
|
||||||
},
|
|
||||||
setCurrentFile,
|
|
||||||
unselectFiles: stub(),
|
|
||||||
});
|
|
||||||
|
|
||||||
t.calledWith(setCurrentFile, [last], 'should set last file');
|
|
||||||
t.end();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('cloudcmd: client: key: set last file current: $', (t) => {
|
|
||||||
const last = 'last';
|
|
||||||
const nextSibling = {
|
|
||||||
nextSibling: last,
|
|
||||||
};
|
|
||||||
|
|
||||||
const element = {
|
|
||||||
nextSibling,
|
|
||||||
};
|
|
||||||
|
|
||||||
const setCurrentFile = stub();
|
|
||||||
|
|
||||||
vim('$', {}, {
|
|
||||||
Info: {
|
|
||||||
element,
|
|
||||||
},
|
|
||||||
setCurrentFile,
|
|
||||||
unselectFiles: stub(),
|
|
||||||
});
|
|
||||||
|
|
||||||
t.calledWith(setCurrentFile, [last], 'should set last file');
|
|
||||||
t.end();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('cloudcmd: client: key: set first file current: gg', (t) => {
|
|
||||||
const first = 'first';
|
|
||||||
const previousSibling = {
|
|
||||||
previousSibling: first,
|
|
||||||
};
|
|
||||||
|
|
||||||
const element = {
|
|
||||||
previousSibling,
|
|
||||||
};
|
|
||||||
|
|
||||||
const Operation = {
|
|
||||||
show: stub(),
|
|
||||||
};
|
|
||||||
|
|
||||||
const unselectFiles = stub();
|
|
||||||
const setCurrentFile = stub();
|
|
||||||
|
|
||||||
const deps = {
|
|
||||||
Operation,
|
|
||||||
unselectFiles,
|
|
||||||
setCurrentFile,
|
|
||||||
Info: {
|
|
||||||
element,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
vim('g', {}, deps);
|
|
||||||
vim('g', {}, deps);
|
|
||||||
|
|
||||||
t.calledWith(setCurrentFile, [first], 'should set first file');
|
|
||||||
t.end();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('cloudcmd: client: key: set first file current: ^', async (t) => {
|
|
||||||
const first = 'first';
|
|
||||||
const previousSibling = {
|
|
||||||
previousSibling: first,
|
|
||||||
};
|
|
||||||
|
|
||||||
const element = {
|
|
||||||
previousSibling,
|
|
||||||
};
|
|
||||||
|
|
||||||
const Operation = {
|
|
||||||
show: stub(),
|
|
||||||
};
|
|
||||||
|
|
||||||
const unselectFiles = stub();
|
|
||||||
const setCurrentFile = stub();
|
|
||||||
|
|
||||||
const deps = {
|
|
||||||
setCurrentFile,
|
|
||||||
Info: {
|
|
||||||
element,
|
|
||||||
},
|
|
||||||
unselectFiles,
|
|
||||||
Operation,
|
|
||||||
};
|
|
||||||
|
|
||||||
await vim('^', {}, deps);
|
|
||||||
|
|
||||||
t.calledWith(setCurrentFile, [first], 'should set first file');
|
|
||||||
t.end();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('cloudcmd: client: key: visual', (t) => {
|
|
||||||
const element = {};
|
|
||||||
const toggleSelectedFile = stub();
|
|
||||||
|
|
||||||
const Info = {
|
|
||||||
element,
|
|
||||||
};
|
|
||||||
|
|
||||||
vim('v', {}, {
|
|
||||||
Info,
|
|
||||||
toggleSelectedFile,
|
|
||||||
});
|
|
||||||
|
|
||||||
t.calledWith(toggleSelectedFile, [element], 'should toggle selection');
|
|
||||||
t.end();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('cloudcmd: client: key: ESC', (t) => {
|
|
||||||
const element = {};
|
|
||||||
const unselectFiles = stub();
|
|
||||||
|
|
||||||
const Info = {
|
|
||||||
element,
|
|
||||||
};
|
|
||||||
|
|
||||||
vim('Escape', null, {
|
|
||||||
Info,
|
|
||||||
unselectFiles,
|
|
||||||
});
|
|
||||||
|
|
||||||
t.calledWithNoArgs(unselectFiles, 'should toggle selection');
|
|
||||||
t.end();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('cloudcmd: client: key: Enter', async (t) => {
|
|
||||||
const nextSibling = 'hello';
|
|
||||||
const element = {
|
|
||||||
nextSibling,
|
|
||||||
};
|
|
||||||
|
|
||||||
const unselectFiles = stub();
|
|
||||||
const setCurrentFile = stub();
|
|
||||||
|
|
||||||
const Info = {
|
|
||||||
element,
|
|
||||||
};
|
|
||||||
|
|
||||||
await vim('Enter', null, {
|
|
||||||
Info,
|
|
||||||
setCurrentFile,
|
|
||||||
unselectFiles,
|
|
||||||
});
|
|
||||||
|
|
||||||
await vim('j', null, {
|
|
||||||
Info,
|
|
||||||
setCurrentFile,
|
|
||||||
unselectFiles,
|
|
||||||
});
|
|
||||||
|
|
||||||
t.calledWith(setCurrentFile, [nextSibling], 'should set next file');
|
|
||||||
t.end();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('cloudcmd: client: key: /', (t) => {
|
|
||||||
const preventDefault = stub();
|
|
||||||
const element = {};
|
|
||||||
|
|
||||||
const Info = {
|
|
||||||
element,
|
|
||||||
files: [],
|
|
||||||
};
|
|
||||||
|
|
||||||
const getCurrentName = stub().returns('');
|
|
||||||
|
|
||||||
const event = {
|
|
||||||
preventDefault,
|
|
||||||
};
|
|
||||||
|
|
||||||
const prompt = stub().returns([]);
|
|
||||||
|
|
||||||
vim('/', event, {
|
|
||||||
getCurrentName,
|
|
||||||
Info,
|
|
||||||
prompt,
|
|
||||||
});
|
|
||||||
|
|
||||||
t.calledWithNoArgs(preventDefault);
|
|
||||||
t.end();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('cloudcmd: client: find', (t) => {
|
|
||||||
assign(DOM.Dialog, {
|
|
||||||
prompt: stub().returns([]),
|
|
||||||
});
|
|
||||||
|
|
||||||
const setCurrentByName = stub();
|
|
||||||
|
|
||||||
assign(DOM, {
|
|
||||||
setCurrentByName,
|
|
||||||
});
|
|
||||||
|
|
||||||
const event = {
|
|
||||||
preventDefault: stub(),
|
|
||||||
};
|
|
||||||
|
|
||||||
vim('/', event);
|
|
||||||
|
|
||||||
t.notCalled(setCurrentByName);
|
|
||||||
t.end();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('cloudcmd: client: key: n', (t) => {
|
|
||||||
const findNext = stub();
|
|
||||||
const createFindNext = stub().returns(findNext);
|
|
||||||
|
|
||||||
const event = {};
|
|
||||||
|
|
||||||
vim('n', event, {
|
|
||||||
createFindNext,
|
|
||||||
});
|
|
||||||
|
|
||||||
t.calledWithNoArgs(findNext, 'should call findNext');
|
|
||||||
t.end();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('cloudcmd: client: key: N', (t) => {
|
|
||||||
const findPrevious = stub();
|
|
||||||
const createFindPrevious = stub().returns(findPrevious);
|
|
||||||
const event = {};
|
|
||||||
|
|
||||||
vim('N', event, {
|
|
||||||
createFindPrevious,
|
|
||||||
});
|
|
||||||
|
|
||||||
t.calledWithNoArgs(findPrevious);
|
|
||||||
t.end();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('cloudcmd: client: key: make directory', async (t) => {
|
|
||||||
const {DOM} = globalThis;
|
|
||||||
|
|
||||||
assign(DOM, {
|
|
||||||
promptNewDir: stub(),
|
|
||||||
});
|
|
||||||
|
|
||||||
const event = {
|
|
||||||
stopImmediatePropagation: stub(),
|
|
||||||
preventDefault: stub(),
|
|
||||||
};
|
|
||||||
|
|
||||||
await vim('m', event);
|
|
||||||
await vim('d', event);
|
|
||||||
|
|
||||||
t.calledWithNoArgs(DOM.promptNewDir);
|
|
||||||
t.end();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('cloudcmd: client: key: make file', (t) => {
|
|
||||||
const promptNewFile = stub();
|
|
||||||
|
|
||||||
const event = {
|
|
||||||
stopImmediatePropagation: stub(),
|
|
||||||
preventDefault: stub(),
|
|
||||||
};
|
|
||||||
|
|
||||||
vim('m', event);
|
|
||||||
vim('f', event, {
|
|
||||||
promptNewFile,
|
|
||||||
});
|
|
||||||
|
|
||||||
t.calledWithNoArgs(promptNewFile);
|
|
||||||
t.end();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('cloudcmd: client: vim: terminal', (t) => {
|
|
||||||
const CloudCmd = {
|
|
||||||
Terminal: {
|
|
||||||
show: stub(),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const event = {};
|
|
||||||
|
|
||||||
vim('t', event, {
|
|
||||||
CloudCmd,
|
|
||||||
});
|
|
||||||
vim('t', event, {
|
|
||||||
CloudCmd,
|
|
||||||
});
|
|
||||||
|
|
||||||
t.calledWithNoArgs(CloudCmd.Terminal.show);
|
|
||||||
t.end();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('cloudcmd: client: vim: edit', async (t) => {
|
|
||||||
globalThis.DOM = getDOM();
|
|
||||||
globalThis.CloudCmd = getCloudCmd();
|
|
||||||
|
|
||||||
const {CloudCmd} = globalThis;
|
|
||||||
|
|
||||||
assign(CloudCmd, {
|
|
||||||
EditFileVim: {
|
|
||||||
show: stub(),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const event = {};
|
|
||||||
|
|
||||||
await vim('e', event);
|
|
||||||
|
|
||||||
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();
|
|
||||||
});
|
|
||||||
|
|
@ -1,33 +0,0 @@
|
||||||
/* global DOM */
|
|
||||||
export function selectFileNotParent(current, {getCurrentName, selectFile} = DOM) {
|
|
||||||
const name = getCurrentName(current);
|
|
||||||
|
|
||||||
if (name === '..')
|
|
||||||
return;
|
|
||||||
|
|
||||||
selectFile(current);
|
|
||||||
}
|
|
||||||
|
|
||||||
export const setCurrent = (sibling, {count, isVisual, isDelete}, {Info, setCurrentFile, unselectFiles, Operation}) => {
|
|
||||||
let current = Info.element;
|
|
||||||
const select = isVisual ? selectFileNotParent : unselectFiles;
|
|
||||||
|
|
||||||
select(current);
|
|
||||||
|
|
||||||
const position = `${sibling}Sibling`;
|
|
||||||
|
|
||||||
for (let i = 0; i < count; i++) {
|
|
||||||
const next = current[position];
|
|
||||||
|
|
||||||
if (!next)
|
|
||||||
break;
|
|
||||||
|
|
||||||
current = next;
|
|
||||||
select(current);
|
|
||||||
}
|
|
||||||
|
|
||||||
setCurrentFile(current);
|
|
||||||
|
|
||||||
if (isDelete)
|
|
||||||
Operation.show('delete');
|
|
||||||
};
|
|
||||||
|
|
@ -1,220 +0,0 @@
|
||||||
import {fullstore} from 'fullstore';
|
|
||||||
|
|
||||||
const store = fullstore('');
|
|
||||||
const visual = fullstore(false);
|
|
||||||
|
|
||||||
const stopVisual = () => {
|
|
||||||
visual(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
const end = () => {
|
|
||||||
store('');
|
|
||||||
};
|
|
||||||
|
|
||||||
const rmFirst = (a) => {
|
|
||||||
return a
|
|
||||||
.split('')
|
|
||||||
.slice(1)
|
|
||||||
.join('');
|
|
||||||
};
|
|
||||||
|
|
||||||
const noop = () => {};
|
|
||||||
|
|
||||||
export default (key, operations = {}) => {
|
|
||||||
const prevStore = store();
|
|
||||||
const isVisual = visual();
|
|
||||||
const value = store(prevStore.concat(key));
|
|
||||||
|
|
||||||
const {
|
|
||||||
escape = noop,
|
|
||||||
moveNext = noop,
|
|
||||||
movePrevious = noop,
|
|
||||||
remove = noop,
|
|
||||||
copy = noop,
|
|
||||||
paste = noop,
|
|
||||||
select = noop,
|
|
||||||
find = noop,
|
|
||||||
findNext = noop,
|
|
||||||
findPrevious = noop,
|
|
||||||
makeFile = noop,
|
|
||||||
makeDirectory = noop,
|
|
||||||
terminal = noop,
|
|
||||||
edit = noop,
|
|
||||||
operationCopy = noop,
|
|
||||||
operationMove = noop,
|
|
||||||
rename = noop,
|
|
||||||
} = operations;
|
|
||||||
|
|
||||||
if (key === 'Enter')
|
|
||||||
return end();
|
|
||||||
|
|
||||||
if (key === 'Escape') {
|
|
||||||
visual(false);
|
|
||||||
escape();
|
|
||||||
|
|
||||||
return end();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (key === 'j' || key === 'w') {
|
|
||||||
const {
|
|
||||||
count,
|
|
||||||
isDelete,
|
|
||||||
isVisual,
|
|
||||||
} = handleDelete(prevStore);
|
|
||||||
|
|
||||||
!isNaN(count) && moveNext({
|
|
||||||
count,
|
|
||||||
isVisual,
|
|
||||||
isDelete,
|
|
||||||
});
|
|
||||||
|
|
||||||
return end();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (key === 'k' || key === 'b') {
|
|
||||||
const {
|
|
||||||
count,
|
|
||||||
isDelete,
|
|
||||||
isVisual,
|
|
||||||
} = handleDelete(prevStore);
|
|
||||||
|
|
||||||
!isNaN(count) && movePrevious({
|
|
||||||
count,
|
|
||||||
isVisual,
|
|
||||||
isDelete,
|
|
||||||
});
|
|
||||||
|
|
||||||
return end();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (value === 'gg' || key === '^') {
|
|
||||||
const {isDelete, isVisual} = handleDelete(prevStore);
|
|
||||||
|
|
||||||
movePrevious({
|
|
||||||
count: Infinity,
|
|
||||||
isVisual,
|
|
||||||
isDelete,
|
|
||||||
});
|
|
||||||
|
|
||||||
return end();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (value === 'md') {
|
|
||||||
makeDirectory();
|
|
||||||
return end();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (value === 'tt') {
|
|
||||||
terminal();
|
|
||||||
return end();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (value === 'e') {
|
|
||||||
edit();
|
|
||||||
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();
|
|
||||||
|
|
||||||
return end();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (key === 'G' || key === '$') {
|
|
||||||
moveNext({
|
|
||||||
count: Infinity,
|
|
||||||
isVisual,
|
|
||||||
});
|
|
||||||
|
|
||||||
return end();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (key === 'y') {
|
|
||||||
if (!visual())
|
|
||||||
return end();
|
|
||||||
|
|
||||||
stopVisual();
|
|
||||||
copy();
|
|
||||||
|
|
||||||
return end();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (/^p$/i.test(key)) {
|
|
||||||
paste();
|
|
||||||
return end();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (/^v$/i.test(key)) {
|
|
||||||
visual(!visual());
|
|
||||||
select();
|
|
||||||
|
|
||||||
return end();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (key === '/') {
|
|
||||||
find();
|
|
||||||
return end();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (key === 'n') {
|
|
||||||
findNext();
|
|
||||||
return end();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (key === 'N') {
|
|
||||||
findPrevious();
|
|
||||||
return end();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (key === ' ')
|
|
||||||
return end();
|
|
||||||
};
|
|
||||||
|
|
||||||
function handleDelete(prevStore) {
|
|
||||||
const isDelete = prevStore[0] === 'd';
|
|
||||||
|
|
||||||
if (isDelete) {
|
|
||||||
visual(true);
|
|
||||||
prevStore = rmFirst(prevStore);
|
|
||||||
}
|
|
||||||
|
|
||||||
const count = getNumber(prevStore);
|
|
||||||
const isVisual = visual();
|
|
||||||
|
|
||||||
return {
|
|
||||||
count,
|
|
||||||
isDelete,
|
|
||||||
isVisual,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function getNumber(value) {
|
|
||||||
if (!value)
|
|
||||||
return 1;
|
|
||||||
|
|
||||||
if (value === 'g')
|
|
||||||
return 1;
|
|
||||||
|
|
||||||
return parseInt(value);
|
|
||||||
}
|
|
||||||
|
|
@ -1,100 +0,0 @@
|
||||||
import {test, stub} from 'supertape';
|
|
||||||
import vim from './vim.js';
|
|
||||||
|
|
||||||
test('vim: no operations', (t) => {
|
|
||||||
const result = vim('hello', {});
|
|
||||||
|
|
||||||
t.notOk(result);
|
|
||||||
t.end();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('vim: space', (t) => {
|
|
||||||
const moveNext = stub();
|
|
||||||
|
|
||||||
vim(' ');
|
|
||||||
vim('j', {
|
|
||||||
moveNext,
|
|
||||||
});
|
|
||||||
|
|
||||||
const args = [{
|
|
||||||
count: 1,
|
|
||||||
isDelete: false,
|
|
||||||
isVisual: false,
|
|
||||||
}];
|
|
||||||
|
|
||||||
t.calledWith(moveNext, args);
|
|
||||||
t.end();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('vim: ^', (t) => {
|
|
||||||
const movePrevious = stub();
|
|
||||||
|
|
||||||
vim('^', {
|
|
||||||
movePrevious,
|
|
||||||
});
|
|
||||||
|
|
||||||
const expected = {
|
|
||||||
count: Infinity,
|
|
||||||
isVisual: false,
|
|
||||||
isDelete: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
t.calledWith(movePrevious, [expected], 'should call movePrevious');
|
|
||||||
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();
|
|
||||||
|
|
||||||
vim('w', {
|
|
||||||
moveNext,
|
|
||||||
});
|
|
||||||
|
|
||||||
const expected = {
|
|
||||||
count: 1,
|
|
||||||
isVisual: false,
|
|
||||||
isDelete: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
t.calledWith(moveNext, [expected], 'should call moveNext');
|
|
||||||
t.end();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('vim: b', (t) => {
|
|
||||||
const movePrevious = stub();
|
|
||||||
|
|
||||||
vim('b', {
|
|
||||||
movePrevious,
|
|
||||||
});
|
|
||||||
|
|
||||||
const expected = {
|
|
||||||
count: 1,
|
|
||||||
isVisual: false,
|
|
||||||
isDelete: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
t.calledWith(movePrevious, [expected], 'should call movePrevious');
|
|
||||||
t.end();
|
|
||||||
});
|
|
||||||
|
|
@ -1,10 +1,11 @@
|
||||||
import currify from 'currify';
|
'use strict';
|
||||||
|
|
||||||
export const getIndex = currify((array, item) => {
|
module.exports = (array, item) => {
|
||||||
const index = array.indexOf(item);
|
const index = array.indexOf(item);
|
||||||
|
|
||||||
if (!~index)
|
if (!~index)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
return index;
|
return index;
|
||||||
});
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
export const getRange = (indexFrom, indexTo, files) => {
|
'use strict';
|
||||||
|
|
||||||
|
module.exports = (indexFrom, indexTo, files) => {
|
||||||
if (indexFrom < indexTo)
|
if (indexFrom < indexTo)
|
||||||
return files.slice(indexFrom, indexTo + 1);
|
return files.slice(indexFrom, indexTo + 1);
|
||||||
|
|
||||||
|
|
@ -7,3 +9,4 @@ export const getRange = (indexFrom, indexTo, files) => {
|
||||||
|
|
||||||
return [files[indexFrom]];
|
return [files[indexFrom]];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,52 +0,0 @@
|
||||||
import test from 'supertape';
|
|
||||||
import {getRange} from './get-range.js';
|
|
||||||
|
|
||||||
test('cloudcmd: client: listeners: getRange: direct', (t) => {
|
|
||||||
const expected = [
|
|
||||||
'hello',
|
|
||||||
'world',
|
|
||||||
];
|
|
||||||
|
|
||||||
const files = [
|
|
||||||
...expected,
|
|
||||||
'how',
|
|
||||||
'come',
|
|
||||||
];
|
|
||||||
|
|
||||||
const result = getRange(0, 1, files);
|
|
||||||
|
|
||||||
t.deepEqual(result, expected, 'should return range');
|
|
||||||
t.end();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('cloudcmd: client: listeners: getRange: reverse', (t) => {
|
|
||||||
const expected = [
|
|
||||||
'hello',
|
|
||||||
'world',
|
|
||||||
];
|
|
||||||
|
|
||||||
const files = [
|
|
||||||
...expected,
|
|
||||||
'how',
|
|
||||||
'come',
|
|
||||||
];
|
|
||||||
|
|
||||||
const result = getRange(1, 0, files);
|
|
||||||
|
|
||||||
t.deepEqual(result, expected, 'should return range');
|
|
||||||
t.end();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('cloudcmd: client: listeners: getRange: one', (t) => {
|
|
||||||
const expected = ['hello'];
|
|
||||||
const files = [
|
|
||||||
...expected,
|
|
||||||
'how',
|
|
||||||
'come',
|
|
||||||
];
|
|
||||||
|
|
||||||
const result = getRange(0, 0, files);
|
|
||||||
|
|
||||||
t.deepEqual(result, expected, 'should return range');
|
|
||||||
t.end();
|
|
||||||
});
|
|
||||||
|
|
@ -1,30 +1,35 @@
|
||||||
/* global DOM, CloudCmd */
|
/* global DOM, CloudCmd */
|
||||||
import exec from 'execon';
|
|
||||||
import itype from 'itype';
|
|
||||||
import currify from 'currify';
|
|
||||||
import {tryToCatch} from 'try-to-catch';
|
|
||||||
import clipboard from '@cloudcmd/clipboard';
|
|
||||||
import * as Events from '#dom/events';
|
|
||||||
import {uploadFiles} from '#dom/upload-files';
|
|
||||||
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');
|
'use strict';
|
||||||
const SPACE = ' ';
|
|
||||||
|
|
||||||
export async function init() {
|
const exec = require('execon');
|
||||||
|
const itype = require('itype/legacy');
|
||||||
|
const currify = require('currify/legacy');
|
||||||
|
const getRange = require('./get-range');
|
||||||
|
|
||||||
|
const getIndex = currify(require('./get-index'));
|
||||||
|
|
||||||
|
const uploadFiles = require('../dom/upload-files');
|
||||||
|
|
||||||
|
const {
|
||||||
|
FS,
|
||||||
|
apiURL
|
||||||
|
} = require('../../common/cloudfunc');
|
||||||
|
|
||||||
|
module.exports.init = () => {
|
||||||
contextMenu();
|
contextMenu();
|
||||||
dragndrop();
|
dragndrop();
|
||||||
unload();
|
unload();
|
||||||
pop();
|
pop();
|
||||||
resize();
|
resize();
|
||||||
|
config();
|
||||||
header();
|
header();
|
||||||
await config();
|
};
|
||||||
}
|
|
||||||
|
CloudCmd.Listeners = module.exports;
|
||||||
|
|
||||||
const unselect = (event) => {
|
const unselect = (event) => {
|
||||||
const isMac = /Mac/.test(globalThis.navigator.platform);
|
const isMac = /Mac/.test(window.navigator.platform);
|
||||||
const {
|
const {
|
||||||
shiftKey,
|
shiftKey,
|
||||||
metaKey,
|
metaKey,
|
||||||
|
|
@ -38,25 +43,31 @@ const unselect = (event) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const execAll = currify((funcs, event) => {
|
const execAll = currify((funcs, event) => {
|
||||||
for (const fn of funcs)
|
funcs.forEach((fn) => {
|
||||||
fn(event);
|
fn(event);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
const Info = DOM.CurrentInfo;
|
||||||
|
const Storage = DOM.Storage;
|
||||||
|
const Events = DOM.Events;
|
||||||
const EventsFiles = {
|
const EventsFiles = {
|
||||||
mousedown: exec.with(execIfNotUL, setCurrentFileByEvent),
|
mousedown: exec.with(execIfNotUL, setCurrentFileByEvent),
|
||||||
click: execAll([onClick, exec.with(execIfNotMobile, unselect)]),
|
click: execAll([onClick, unselect]),
|
||||||
dragstart: exec.with(execIfNotUL, onDragStart),
|
dragstart: exec.with(execIfNotUL, onDragStart),
|
||||||
dblclick: exec.with(execIfNotUL, onDblClick),
|
dblclick: exec.with(execIfNotUL, onDblClick),
|
||||||
touchstart: exec.with(execIfNotUL, onTouch),
|
touchstart: exec.with(execIfNotUL, onTouch)
|
||||||
};
|
};
|
||||||
|
|
||||||
let EXT;
|
let EXT;
|
||||||
|
|
||||||
function header() {
|
function header() {
|
||||||
const fm = DOM.getFM();
|
const fm = DOM.getFM();
|
||||||
|
|
||||||
const isDataset = (el) => el.dataset;
|
const isDataset = (el) => el.dataset;
|
||||||
|
|
||||||
const isPanel = (el) => {
|
var isPanel = (el) => {
|
||||||
return /^js-(left|right)$/.test(el.dataset.name);
|
return /^js-(left|right)$/.test(el.dataset.name);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -67,7 +78,8 @@ function header() {
|
||||||
if (parent.dataset.name !== 'js-fm-header')
|
if (parent.dataset.name !== 'js-fm-header')
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const name = (el.dataset.name || '').replace('js-', '');
|
const name = (el.dataset.name || '')
|
||||||
|
.replace('js-', '');
|
||||||
|
|
||||||
if (!/^(name|size|date)$/.test(name))
|
if (!/^(name|size|date)$/.test(name))
|
||||||
return;
|
return;
|
||||||
|
|
@ -88,46 +100,42 @@ function getPath(el, path = []) {
|
||||||
return getPath(el.parentElement, path.concat(el));
|
return getPath(el.parentElement, path.concat(el));
|
||||||
}
|
}
|
||||||
|
|
||||||
async function config() {
|
function config() {
|
||||||
const [, config] = await tryToCatch(DOM.Files.get, 'config');
|
DOM.Files.get('config', (e, config) => {
|
||||||
const type = config?.packer;
|
const type = config && config.packer;
|
||||||
|
EXT = DOM.getPackerExt(type);
|
||||||
EXT = DOM.getPackerExt(type);
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export const initKeysPanel = () => {
|
module.exports.initKeysPanel = () => {
|
||||||
const keysElement = DOM.getById('js-keyspanel');
|
const keysElement = DOM.getById('js-keyspanel');
|
||||||
|
|
||||||
if (!keysElement)
|
if (!keysElement)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
Events.addClick(keysElement, (event) => {
|
Events.addClick(keysElement, ({target}) => {
|
||||||
const {target} = event;
|
const id = target.id;
|
||||||
const {id} = target;
|
|
||||||
|
|
||||||
const operation = (name) => {
|
const operation = (name) => {
|
||||||
const {Operation} = CloudCmd;
|
const Operation = CloudCmd.Operation;
|
||||||
|
const fn = Operation.show.bind(null, name);
|
||||||
|
|
||||||
return Operation.show.bind(null, name);
|
return fn;
|
||||||
};
|
};
|
||||||
|
|
||||||
const clickFuncs = {
|
const clickFuncs = {
|
||||||
'f1': CloudCmd.Help.show,
|
'f1' : CloudCmd.Help.show,
|
||||||
'f2': CloudCmd.UserMenu.show,
|
'f2' : DOM.renameCurrent,
|
||||||
'f3': CloudCmd.View.show,
|
'f3' : CloudCmd.View.show,
|
||||||
'f4': CloudCmd.EditFile.show,
|
'f4' : CloudCmd.EditFile.show,
|
||||||
'f5': operation('copy'),
|
'f5' : operation('copy'),
|
||||||
'f6': operation('move'),
|
'f6' : operation('move'),
|
||||||
'f7': DOM.promptNewDir,
|
'f7' : DOM.promptNewDir,
|
||||||
'f8': operation('delete'),
|
'f8' : operation('delete'),
|
||||||
'f9': () => {
|
'f9' : CloudCmd.Menu.show,
|
||||||
event.stopPropagation();
|
'f10' : CloudCmd.Config.show,
|
||||||
CloudCmd.Menu.show();
|
'~' : CloudCmd.Konsole.show,
|
||||||
},
|
'shift~' : CloudCmd.Terminal.show,
|
||||||
'f10': CloudCmd.Config.show,
|
'contact' : CloudCmd.Contact.show,
|
||||||
'~': CloudCmd.Konsole.show,
|
|
||||||
'shift~': CloudCmd.Terminal.show,
|
|
||||||
'contact': CloudCmd.Contact.show,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
exec(clickFuncs[id]);
|
exec(clickFuncs[id]);
|
||||||
|
|
@ -138,10 +146,10 @@ const getPanel = (side) => {
|
||||||
if (!itype.string(side))
|
if (!itype.string(side))
|
||||||
return side;
|
return side;
|
||||||
|
|
||||||
return DOM.getByDataName(`js-${side}`);
|
return DOM.getByDataName('js-' + side);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const setOnPanel = (side) => {
|
module.exports.setOnPanel = (side) => {
|
||||||
const panel = getPanel(side);
|
const panel = getPanel(side);
|
||||||
|
|
||||||
const filesElement = DOM.getByDataName('js-files', panel);
|
const filesElement = DOM.getByDataName('js-files', panel);
|
||||||
|
|
@ -157,71 +165,61 @@ function getPathListener(panel) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function isNoCurrent(panel) {
|
function isNoCurrent(panel) {
|
||||||
const Info = DOM.CurrentInfo;
|
|
||||||
const infoPanel = Info.panel;
|
const infoPanel = Info.panel;
|
||||||
|
|
||||||
if (!infoPanel)
|
|
||||||
return true;
|
|
||||||
|
|
||||||
const namePanel = panel.getAttribute('data-name');
|
const namePanel = panel.getAttribute('data-name');
|
||||||
const nameInfoPanel = infoPanel.getAttribute('data-name');
|
const nameInfoPanel = infoPanel.getAttribute('data-name');
|
||||||
|
|
||||||
return namePanel !== nameInfoPanel;
|
if (namePanel !== nameInfoPanel)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
function decodePath(path) {
|
function onPathElementClick(panel, event) {
|
||||||
const url = CloudCmd.HOST;
|
let link, href, url, noCurrent;
|
||||||
const {prefix} = CloudCmd;
|
|
||||||
const prefixReg = RegExp('^' + prefix + FS);
|
|
||||||
|
|
||||||
return decodeURI(path)
|
|
||||||
.replace(url, '')
|
|
||||||
.replace(prefixReg, '') // browser doesn't replace % -> %25% do it for him
|
|
||||||
.replace('%%', '%25%')
|
|
||||||
.replace(NBSP_REG, SPACE) || '/';
|
|
||||||
}
|
|
||||||
|
|
||||||
async function onPathElementClick(panel, event) {
|
|
||||||
const Info = DOM.CurrentInfo;
|
|
||||||
event.preventDefault();
|
|
||||||
|
|
||||||
|
const prefix = CloudCmd.PREFIX;
|
||||||
const element = event.target;
|
const element = event.target;
|
||||||
const attr = element.getAttribute('data-name');
|
const attr = element.getAttribute('data-name');
|
||||||
const noCurrent = isNoCurrent(panel);
|
|
||||||
|
|
||||||
if (attr === 'js-copy-path')
|
switch (attr) {
|
||||||
return copyPath(element);
|
case 'js-clear-storage':
|
||||||
|
Storage.clear();
|
||||||
|
break;
|
||||||
|
|
||||||
if (attr === 'js-refresh')
|
case 'js-refresh':
|
||||||
return CloudCmd.refresh({
|
noCurrent = isNoCurrent(panel);
|
||||||
|
|
||||||
|
CloudCmd.refresh({
|
||||||
panel,
|
panel,
|
||||||
noCurrent,
|
noCurrent
|
||||||
});
|
});
|
||||||
|
|
||||||
if (attr !== 'js-path-link')
|
event.preventDefault();
|
||||||
return;
|
break;
|
||||||
|
|
||||||
const {href} = element;
|
case 'js-path-link':
|
||||||
const path = decodePath(href);
|
url = CloudCmd.HOST;
|
||||||
|
href = element.href;
|
||||||
|
link = href.replace(url, '');
|
||||||
|
/**
|
||||||
|
* browser doesn't replace % -> %25%
|
||||||
|
* do it for him
|
||||||
|
*/
|
||||||
|
link = link.replace('%%', '%25%');
|
||||||
|
link = decodeURI(link);
|
||||||
|
link = link.replace(RegExp('^' + prefix + FS), '') || '/';
|
||||||
|
|
||||||
await CloudCmd.changeDir(path, {
|
noCurrent = isNoCurrent(panel);
|
||||||
isRefresh: false,
|
|
||||||
panel: noCurrent ? panel : Info.panel,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function copyPath(el) {
|
CloudCmd.loadDir({
|
||||||
clipboard
|
path : link,
|
||||||
.writeText(el.parentElement.title)
|
isRefresh : false,
|
||||||
.then(CloudCmd.log)
|
panel : noCurrent ? panel : Info.panel
|
||||||
.catch(CloudCmd.log);
|
});
|
||||||
}
|
|
||||||
|
|
||||||
function execIfNotMobile(callback, event) {
|
event.preventDefault();
|
||||||
const isMobile = DOM.getCSSVar('is-mobile');
|
}
|
||||||
|
|
||||||
if (!isMobile)
|
|
||||||
callback(event);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function execIfNotUL(callback, event) {
|
function execIfNotUL(callback, event) {
|
||||||
|
|
@ -233,27 +231,30 @@ function execIfNotUL(callback, event) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function onClick(event) {
|
function onClick(event) {
|
||||||
event.preventDefault();
|
const ctrl = event.ctrlKey;
|
||||||
|
|
||||||
|
if (!ctrl)
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
changePanel(event.target);
|
changePanel(event.target);
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleSelect(key, files) {
|
function toggleSelect(key, files) {
|
||||||
const isMac = /Mac/.test(globalThis.navigator.platform);
|
const isMac = /Mac/.test(window.navigator.platform);
|
||||||
|
|
||||||
if (!key)
|
if (!key)
|
||||||
throw Error('key should not be undefined!');
|
throw Error('key should not be undefined!');
|
||||||
|
|
||||||
const [file] = files;
|
const [file] = files;
|
||||||
|
|
||||||
if (isMac && key.meta)
|
if (isMac && key.meta || key.ctrl)
|
||||||
return DOM.toggleSelectedFile(file);
|
return DOM.toggleSelectedFile(file);
|
||||||
|
|
||||||
if (key.shift)
|
if (key.shift)
|
||||||
return files.map(DOM.selectFile);
|
return files.forEach(DOM.selectFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
function changePanel(element) {
|
function changePanel(element) {
|
||||||
const Info = DOM.CurrentInfo;
|
|
||||||
const {panel} = Info;
|
const {panel} = Info;
|
||||||
const files = DOM.getByDataName('js-files', panel);
|
const files = DOM.getByDataName('js-files', panel);
|
||||||
const ul = getULElement(element);
|
const ul = getULElement(element);
|
||||||
|
|
@ -262,20 +263,21 @@ function changePanel(element) {
|
||||||
DOM.changePanel();
|
DOM.changePanel();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function onDblClick(event) {
|
function onDblClick(event) {
|
||||||
event.preventDefault();
|
|
||||||
|
|
||||||
const current = getLIElement(event.target);
|
const current = getLIElement(event.target);
|
||||||
const isDir = DOM.isCurrentIsDir(current);
|
const isDir = DOM.isCurrentIsDir(current);
|
||||||
const path = DOM.getCurrentPath(current);
|
const path = DOM.getCurrentPath(current);
|
||||||
|
|
||||||
if (!isDir)
|
if (isDir) {
|
||||||
return CloudCmd.View.show();
|
CloudCmd.loadDir({
|
||||||
|
path: path === '/' ? '/' : path + '/'
|
||||||
|
});
|
||||||
|
|
||||||
await CloudCmd.changeDir(path);
|
event.preventDefault();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function onTouch(event) {
|
function onTouch(event) {
|
||||||
const current = getLIElement(event.target);
|
const current = getLIElement(event.target);
|
||||||
const isDir = DOM.isCurrentIsDir(current);
|
const isDir = DOM.isCurrentIsDir(current);
|
||||||
|
|
||||||
|
|
@ -287,18 +289,20 @@ async function onTouch(event) {
|
||||||
if (!isCurrent)
|
if (!isCurrent)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
await CloudCmd.changeDir(DOM.getCurrentPath(current));
|
CloudCmd.loadDir({
|
||||||
|
path: DOM.getCurrentPath(current)
|
||||||
|
});
|
||||||
|
|
||||||
|
event.preventDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* download file from browser to desktop
|
* download file from browser to desktop
|
||||||
* in Chrome (HTML5)
|
* in Chrome (HTML5)
|
||||||
*/
|
*/
|
||||||
function onDragStart(event) {
|
function onDragStart(event) {
|
||||||
const Info = DOM.CurrentInfo;
|
|
||||||
const {prefixURL} = CloudCmd;
|
|
||||||
const element = getLIElement(event.target);
|
const element = getLIElement(event.target);
|
||||||
const {isDir} = Info;
|
const isDir = Info.isDir;
|
||||||
let link = DOM.getCurrentLink(element);
|
let link = DOM.getCurrentLink(element);
|
||||||
let name = DOM.getCurrentName(element);
|
let name = DOM.getCurrentName(element);
|
||||||
|
|
||||||
|
|
@ -307,15 +311,13 @@ function onDragStart(event) {
|
||||||
name += EXT;
|
name += EXT;
|
||||||
link = document.createElement('a');
|
link = document.createElement('a');
|
||||||
link.textContent = name;
|
link.textContent = name;
|
||||||
link.href = prefixURL + '/pack' + Info.path + EXT;
|
link.href = apiURL + '/pack' + Info.path + EXT;
|
||||||
}
|
}
|
||||||
|
|
||||||
event.dataTransfer.setData(
|
event.dataTransfer.setData('DownloadURL',
|
||||||
'DownloadURL',
|
'application/octet-stream' + ':' +
|
||||||
'application/octet-stream' + ':' + name +
|
name + ':' +
|
||||||
':' +
|
link);
|
||||||
link,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getLIElement(element) {
|
function getLIElement(element) {
|
||||||
|
|
@ -336,14 +338,13 @@ function getULElement(element) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function setCurrentFileByEvent(event) {
|
function setCurrentFileByEvent(event) {
|
||||||
const Info = DOM.CurrentInfo;
|
|
||||||
const BUTTON_LEFT = 0;
|
const BUTTON_LEFT = 0;
|
||||||
|
|
||||||
const key = {
|
const key = {
|
||||||
alt: event.altKey,
|
alt: event.altKey,
|
||||||
ctrl: event.ctrlKey,
|
ctrl: event.ctrlKey,
|
||||||
meta: event.metaKey,
|
meta: event.metaKey,
|
||||||
shift: event.shiftKey,
|
shift: event.shiftKey
|
||||||
};
|
};
|
||||||
|
|
||||||
const element = getLIElement(event.target);
|
const element = getLIElement(event.target);
|
||||||
|
|
@ -380,7 +381,7 @@ function contextMenu() {
|
||||||
Events.addOnce('contextmenu', fm, (event) => {
|
Events.addOnce('contextmenu', fm, (event) => {
|
||||||
CloudCmd.Menu.show({
|
CloudCmd.Menu.show({
|
||||||
x: event.clientX,
|
x: event.clientX,
|
||||||
y: event.clientY,
|
y: event.clientY
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -391,17 +392,21 @@ function contextMenu() {
|
||||||
|
|
||||||
function dragndrop() {
|
function dragndrop() {
|
||||||
const panels = DOM.getByClassAll('panel');
|
const panels = DOM.getByClassAll('panel');
|
||||||
const select = ({target}) => {
|
const select = () => {
|
||||||
target.classList.add('selected-panel');
|
[...panels].forEach((panel) => {
|
||||||
|
panel.classList.add('selected-panel');
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const unselect = ({target}) => {
|
const unselect = () => {
|
||||||
target.classList.remove('selected-panel');
|
[...panels].forEach((panel) => {
|
||||||
|
panel.classList.remove('selected-panel');
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const onDrop = (event) => {
|
const onDrop = (event) => {
|
||||||
const {files, items} = event.dataTransfer;
|
const files = event.dataTransfer.files;
|
||||||
|
const items = event.dataTransfer.items;
|
||||||
const {length: filesCount} = files;
|
const {length: filesCount} = files;
|
||||||
|
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
@ -410,28 +415,22 @@ function dragndrop() {
|
||||||
return uploadFiles(files);
|
return uploadFiles(files);
|
||||||
|
|
||||||
const isFile = (item) => item.kind === 'file';
|
const isFile = (item) => item.kind === 'file';
|
||||||
|
const dirFiles = [...items].filter(isFile);
|
||||||
const dirFiles = Array
|
|
||||||
.from(items)
|
|
||||||
.filter(isFile);
|
|
||||||
|
|
||||||
if (dirFiles.length)
|
if (dirFiles.length)
|
||||||
return DOM.uploadDirectory(dirFiles);
|
return DOM.uploadDirectory(dirFiles);
|
||||||
|
|
||||||
const {Operation} = CloudCmd;
|
return CloudCmd.Operation.show('copy');
|
||||||
const operation = event.shiftKey ? 'move' : 'copy';
|
|
||||||
|
|
||||||
return Operation.show(operation);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* In macOS Chrome dropEffect = 'none'
|
* In Mac OS Chrome dropEffect = 'none'
|
||||||
* so drop do not firing up when try
|
* so drop do not firing up when try
|
||||||
* to upload file from download bar
|
* to upload file from download bar
|
||||||
*/
|
*/
|
||||||
const onDragOver = (event) => {
|
const onDragOver = (event) => {
|
||||||
const {dataTransfer} = event;
|
const dataTransfer = event.dataTransfer;
|
||||||
const {effectAllowed} = dataTransfer;
|
const effectAllowed = dataTransfer.effectAllowed;
|
||||||
|
|
||||||
if (/move|linkMove/.test(effectAllowed))
|
if (/move|linkMove/.test(effectAllowed))
|
||||||
dataTransfer.dropEffect = 'move';
|
dataTransfer.dropEffect = 'move';
|
||||||
|
|
@ -441,18 +440,19 @@ function dragndrop() {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
};
|
};
|
||||||
|
|
||||||
for (const panel of panels)
|
Events.add('dragenter', select);
|
||||||
Events
|
Events.add(['dragleave', 'drop'], unselect);
|
||||||
.add('dragover', panel, onDragOver)
|
|
||||||
.add('drop', panel, onDrop)
|
[...panels].forEach((panel) => {
|
||||||
.add('dragenter', select)
|
Events.add('dragover', panel, onDragOver)
|
||||||
.add(['dragleave', 'drop'], unselect);
|
.add('drop', panel, onDrop);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function unload() {
|
function unload() {
|
||||||
Events.add(['unload', 'beforeunload'], (event) => {
|
DOM.Events.add(['unload', 'beforeunload'], (event) => {
|
||||||
const {Key} = CloudCmd;
|
const Key = CloudCmd.Key;
|
||||||
const isBind = Key?.isBind();
|
const isBind = Key && Key.isBind();
|
||||||
|
|
||||||
if (isBind)
|
if (isBind)
|
||||||
return;
|
return;
|
||||||
|
|
@ -463,15 +463,15 @@ function unload() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function pop() {
|
function pop() {
|
||||||
Events.add('popstate', async ({state}) => {
|
Events.add('popstate', (event) => {
|
||||||
const path = (state || '').replace(FS, '');
|
const path = event.state.replace(FS, '');
|
||||||
|
|
||||||
if (!path)
|
if (!path)
|
||||||
return CloudCmd.route(location.hash);
|
return CloudCmd.route(location.hash);
|
||||||
|
|
||||||
const history = false;
|
const history = false;
|
||||||
|
CloudCmd.loadDir({
|
||||||
await CloudCmd.changeDir(path, {
|
path,
|
||||||
history,
|
history,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
@ -479,8 +479,7 @@ function pop() {
|
||||||
|
|
||||||
function resize() {
|
function resize() {
|
||||||
Events.add('resize', () => {
|
Events.add('resize', () => {
|
||||||
const Info = DOM.CurrentInfo;
|
const is = window.innerWidth < CloudCmd.MIN_ONE_PANEL_WIDTH;
|
||||||
const is = globalThis.innerWidth < CloudCmd.MIN_ONE_PANEL_WIDTH;
|
|
||||||
|
|
||||||
if (!is)
|
if (!is)
|
||||||
return;
|
return;
|
||||||
|
|
@ -500,3 +499,4 @@ function resize() {
|
||||||
DOM.changePanel();
|
DOM.changePanel();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,56 +0,0 @@
|
||||||
/* global CloudCmd */
|
|
||||||
import exec from 'execon';
|
|
||||||
import {tryToCatch} from 'try-to-catch';
|
|
||||||
import {js as loadJS} from 'load.js';
|
|
||||||
import pascalCase from 'just-pascal-case';
|
|
||||||
|
|
||||||
const noJS = (a) => a.replace(/.js$/, '');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* function load modules
|
|
||||||
* @params = {name, path, func, dobefore, arg}
|
|
||||||
*/
|
|
||||||
export const loadModule = (params) => {
|
|
||||||
if (!params)
|
|
||||||
return;
|
|
||||||
|
|
||||||
const {path} = params;
|
|
||||||
|
|
||||||
const name = path && noJS(pascalCase(path));
|
|
||||||
const doBefore = params.dobefore;
|
|
||||||
|
|
||||||
if (CloudCmd[name])
|
|
||||||
return;
|
|
||||||
|
|
||||||
CloudCmd[name] = async () => {
|
|
||||||
exec(doBefore);
|
|
||||||
|
|
||||||
const {DIR_MODULES} = CloudCmd;
|
|
||||||
const pathFull = `${DIR_MODULES}/${path}.js`;
|
|
||||||
|
|
||||||
await loadJS(pathFull);
|
|
||||||
const newModule = async (f) => f && f();
|
|
||||||
const module = CloudCmd[name];
|
|
||||||
|
|
||||||
Object.assign(newModule, module);
|
|
||||||
|
|
||||||
CloudCmd[name] = newModule;
|
|
||||||
CloudCmd.log('init', name);
|
|
||||||
|
|
||||||
await module.init();
|
|
||||||
|
|
||||||
return newModule;
|
|
||||||
};
|
|
||||||
|
|
||||||
CloudCmd[name].show = async (...args) => {
|
|
||||||
CloudCmd.log('show', name, args);
|
|
||||||
const m = CloudCmd[name];
|
|
||||||
|
|
||||||
const [e, a] = await tryToCatch(m);
|
|
||||||
|
|
||||||
if (e)
|
|
||||||
return;
|
|
||||||
|
|
||||||
return await a.show(...args);
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
@ -1,64 +1,66 @@
|
||||||
/* global CloudCmd, filepicker */
|
/* global CloudCmd, filepicker */
|
||||||
import exec from 'execon';
|
|
||||||
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';
|
|
||||||
|
|
||||||
const {log} = CloudCmd;
|
'use strict';
|
||||||
|
|
||||||
const upload = currify(_upload);
|
CloudCmd.Cloud = CloudProto;
|
||||||
|
|
||||||
const Name = 'Cloud';
|
const exec = require('execon');
|
||||||
|
|
||||||
CloudCmd[Name] = {
|
const load = require('../dom/load');
|
||||||
init,
|
const Files = require('../dom/files');
|
||||||
uploadFile,
|
const Images = require('../dom/images');
|
||||||
saveFile,
|
const Util = require('../../common/util');
|
||||||
};
|
|
||||||
|
|
||||||
export async function init() {
|
function CloudProto(callback) {
|
||||||
const [modules] = await loadFiles();
|
exec.series([
|
||||||
const {key} = modules.data.FilePicker;
|
loadFiles,
|
||||||
|
callback,
|
||||||
|
]);
|
||||||
|
|
||||||
filepicker.setKey(key);
|
return module.exports;
|
||||||
Images.hide();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function uploadFile(filename, data) {
|
module.exports.uploadFile = (filename, data) => {
|
||||||
|
const {log} = CloudCmd;
|
||||||
const mimetype = '';
|
const mimetype = '';
|
||||||
|
|
||||||
filepicker.store(data, {
|
filepicker.store(data, {
|
||||||
mimetype,
|
mimetype,
|
||||||
filename,
|
filename,
|
||||||
}, (fpFile) => {
|
}, (fpFile) => {
|
||||||
|
log(fpFile);
|
||||||
filepicker.exportFile(fpFile, log, log);
|
filepicker.exportFile(fpFile, log, log);
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
export function saveFile(callback) {
|
module.exports.saveFile = (callback) => {
|
||||||
filepicker.pick(upload(callback));
|
filepicker.pick((fpFile) => {
|
||||||
}
|
CloudCmd.log(fpFile);
|
||||||
|
const {url} = fpFile;
|
||||||
|
const responseType = 'arraybuffer';
|
||||||
|
const success = exec.with(callback, fpFile.filename);
|
||||||
|
|
||||||
function _upload(callback, file) {
|
load.ajax({
|
||||||
const {url, filename} = file;
|
url,
|
||||||
|
responseType,
|
||||||
|
success,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const responseType = 'arraybuffer';
|
function loadFiles(callback) {
|
||||||
const success = exec.with(callback, filename);
|
Util.time('filepicker load');
|
||||||
|
|
||||||
ajax({
|
load.js('//api.filepicker.io/v1/filepicker.js', () => {
|
||||||
url,
|
Files.get('modules', (error, modules) => {
|
||||||
responseType,
|
const {key} = modules.data.FilePicker;
|
||||||
success,
|
|
||||||
|
filepicker.setKey(key);
|
||||||
|
|
||||||
|
Images.hide();
|
||||||
|
Util.timeEnd('filepicker loaded');
|
||||||
|
exec(callback);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadFiles() {
|
|
||||||
const js = '//api.filepicker.io/v2/filepicker.js';
|
|
||||||
|
|
||||||
return Promise.all([
|
|
||||||
Files.get('modules'),
|
|
||||||
load.js(js),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue