├── .eslintrc.js
├── .github
├── ISSUE_TEMPLATE
│ ├── bug_report.yml
│ ├── config.yml
│ └── feature_request.yml
└── workflows
│ ├── ci.yml
│ └── docker.yml
├── .gitignore
├── .prettierrc.js
├── LICENSE
├── README.md
├── docs
├── .nojekyll
├── README.md
├── _navbar.md
├── en
│ ├── README.md
│ ├── _navbar.md
│ ├── _sidebar.md
│ └── installation.md
├── index.html
└── zh-cn
│ ├── README.md
│ ├── _navbar.md
│ ├── _sidebar.md
│ ├── advance.md
│ ├── configuration.md
│ └── installation.md
├── package.json
├── packages
├── sharelist-core
│ ├── .prettierrc.js
│ ├── CHANGELOG.md
│ ├── LICENSE
│ ├── README.md
│ ├── index.js
│ ├── lib
│ │ ├── driver.js
│ │ ├── index.js
│ │ ├── rectifier.js
│ │ ├── request.js
│ │ └── utils.js
│ ├── package-lock.json
│ ├── package.json
│ └── test
│ │ ├── index.js
│ │ └── plugin
│ │ └── driver.test.js
├── sharelist-plugin
│ ├── CHANGELOG.md
│ ├── LICENSE
│ ├── lib
│ │ ├── auth.js
│ │ ├── driver.189cloud.js
│ │ ├── driver.aliyundrive.js
│ │ ├── driver.baidu.js
│ │ ├── driver.caiyun.js
│ │ ├── driver.fs.js
│ │ ├── driver.googledrive.js
│ │ ├── driver.http.js
│ │ ├── driver.https.js
│ │ ├── driver.onedrive.js
│ │ └── driver.sld.js
│ └── package.json
├── sharelist-web
│ ├── .env
│ ├── .eslintrc.js
│ ├── .gitignore
│ ├── .prettierrc.js
│ ├── CHANGELOG.md
│ ├── README.md
│ ├── package.json
│ ├── public
│ │ └── favicon.ico
│ ├── src
│ │ ├── App.tsx
│ │ ├── assets
│ │ │ ├── auto.mp3
│ │ │ ├── img
│ │ │ │ └── line.png
│ │ │ ├── logo.png
│ │ │ └── style
│ │ │ │ ├── icon.less
│ │ │ │ └── index.less
│ │ ├── components
│ │ │ ├── audio
│ │ │ │ ├── index.less
│ │ │ │ └── index.tsx
│ │ │ ├── header
│ │ │ │ ├── index.less
│ │ │ │ └── index.tsx
│ │ │ ├── hintbar
│ │ │ │ ├── index.less
│ │ │ │ └── index.tsx
│ │ │ ├── icon
│ │ │ │ ├── icon-svg.js
│ │ │ │ ├── index.less
│ │ │ │ └── index.ts
│ │ │ ├── player
│ │ │ │ ├── index.less
│ │ │ │ └── index.tsx
│ │ │ ├── power
│ │ │ │ └── index.js
│ │ │ ├── qrcode
│ │ │ │ └── index.tsx
│ │ │ ├── sider
│ │ │ │ ├── index.less
│ │ │ │ └── index.tsx
│ │ │ ├── subscribe
│ │ │ │ └── index.tsx
│ │ │ ├── switch-mode
│ │ │ │ └── index.tsx
│ │ │ └── user-menu
│ │ │ │ ├── index.less
│ │ │ │ └── index.tsx
│ │ ├── config
│ │ │ ├── api.ts
│ │ │ └── setting.ts
│ │ ├── hooks
│ │ │ ├── useConfirm.ts
│ │ │ ├── useDisk.ts
│ │ │ ├── useHooks.ts
│ │ │ ├── useLoad.ts
│ │ │ ├── useLocalStorage.ts
│ │ │ ├── useSetting.ts
│ │ │ ├── useStore.ts
│ │ │ ├── useUrlState.ts
│ │ │ └── utils.ts
│ │ ├── index.html
│ │ ├── main.ts
│ │ ├── router
│ │ │ └── index.ts
│ │ ├── store
│ │ │ ├── index.ts
│ │ │ ├── mutation-types.ts
│ │ │ └── state.ts
│ │ ├── types
│ │ │ ├── IDrive.ts
│ │ │ ├── IRequest.ts
│ │ │ ├── shim.d.ts
│ │ │ └── source.d.ts
│ │ ├── utils
│ │ │ ├── format.ts
│ │ │ ├── ipc.ts
│ │ │ ├── promisify.js
│ │ │ └── request.ts
│ │ └── views
│ │ │ ├── home
│ │ │ ├── index.less
│ │ │ ├── index.tsx
│ │ │ └── partial
│ │ │ │ ├── auth
│ │ │ │ ├── index.less
│ │ │ │ └── index.tsx
│ │ │ │ ├── breadcrumb
│ │ │ │ ├── index.less
│ │ │ │ └── index.tsx
│ │ │ │ ├── error
│ │ │ │ ├── index.less
│ │ │ │ └── index.tsx
│ │ │ │ ├── header
│ │ │ │ ├── index.less
│ │ │ │ └── index.tsx
│ │ │ │ └── search
│ │ │ │ ├── index.less
│ │ │ │ └── index.tsx
│ │ │ └── manage
│ │ │ ├── index.less
│ │ │ ├── index.tsx
│ │ │ └── partial
│ │ │ ├── disk.tsx
│ │ │ ├── drive-modifier.tsx
│ │ │ ├── general.tsx
│ │ │ └── signin.tsx
│ ├── tsconfig.json
│ └── vite.config.ts
├── sharelist-webdav
│ ├── CHANGELOG.md
│ ├── README.md
│ ├── package-lock.json
│ ├── package.json
│ ├── src
│ │ ├── context.ts
│ │ ├── index.ts
│ │ ├── operations
│ │ │ ├── commands.ts
│ │ │ ├── copy.ts
│ │ │ ├── delete.ts
│ │ │ ├── get.ts
│ │ │ ├── head.ts
│ │ │ ├── lock.ts
│ │ │ ├── mkcol.ts
│ │ │ ├── move.ts
│ │ │ ├── not-implemented.ts
│ │ │ ├── options.ts
│ │ │ ├── post.ts
│ │ │ ├── propfind.ts
│ │ │ ├── proppatch.ts
│ │ │ ├── put.ts
│ │ │ ├── shared.ts
│ │ │ └── unlock.ts
│ │ └── types.ts
│ └── tsconfig.json
└── sharelist
│ ├── CHANGELOG.md
│ ├── Dockerfile
│ ├── LICENSE
│ ├── README.md
│ ├── app.js
│ ├── app
│ ├── config.js
│ ├── controller
│ │ ├── api.js
│ │ └── shared.js
│ ├── core
│ │ ├── app.js
│ │ ├── loader.js
│ │ ├── request.js
│ │ └── utils.js
│ ├── index.js
│ ├── middleware
│ │ └── auth.js
│ └── router.js
│ ├── docker-compose.yml
│ ├── example
│ ├── 123.txt
│ ├── README.md
│ ├── fileSystem_current.d.ln
│ ├── filesystem_linux_root.d.ln
│ ├── filesystem_windows_disk_c.d.ln
│ ├── github.com.url
│ ├── google_drive.d.ln
│ ├── google_drive2.d.ln
│ ├── h5ai.d.ln
│ ├── joy.d.ln
│ ├── lanzou.d.ln
│ ├── linkTo_download_ubuntu_18.iso.ln
│ ├── link_webdav.d.ln
│ ├── magnet.txt
│ ├── one_drive.d.ln
│ ├── one_drive_url.d.ln
│ ├── preview
│ │ ├── 123.torrent
│ │ ├── 123.txt
│ │ ├── audio.mp3
│ │ ├── image.jpg
│ │ ├── movie.a.mp4
│ │ ├── movie.a.vtt
│ │ ├── pdf.pdf
│ │ ├── sintel.torrent
│ │ └── work.docx
│ ├── secret_folder
│ │ ├── .passwd
│ │ ├── Secret2
│ │ │ └── .passwd
│ │ └── hello.txt
│ ├── sharelist github repo.d.ln
│ └── sharelist_drive.sld.ln
│ ├── package.json
│ └── package
│ ├── guide
│ ├── driver
│ │ ├── aliyundrive.js
│ │ ├── baidu.js
│ │ ├── googledrive.js
│ │ ├── onedrive.js
│ │ └── shared.js
│ └── index.js
│ ├── sharelist
│ ├── cache.js
│ ├── config.js
│ ├── db.js
│ ├── index.js
│ ├── reactivity
│ │ ├── effect.js
│ │ ├── index.js
│ │ ├── reactive.js
│ │ ├── scheduler.js
│ │ ├── utils.js
│ │ └── watch.js
│ └── utils.js
│ └── webdav
│ └── index.js
└── scripts
├── build.js
├── changelog.js
├── netinstall.sh
└── release.js
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | parser: 'vue-eslint-parser',
3 | parserOptions: {
4 | // set script parser
5 | parser: '@typescript-eslint/parser', // Specifies the ESLint parser
6 | ecmaVersion: 2021, // Allows for the parsing of modern ECMAScript features
7 | sourceType: 'module', // Allows for the use of imports
8 | ecmaFeatures: {
9 | jsx: true, // Allows for the parsing of JSX
10 | },
11 | validate: [],
12 | },
13 | extends: [
14 | 'plugin:vue/vue3-recommended',
15 | 'plugin:@typescript-eslint/recommended', // Uses the recommended rules from the @typescript-eslint/eslint-plugin
16 | 'plugin:prettier/recommended',
17 | ],
18 | rules: {
19 | // Place to specify ESLint rules. Can be used to overwrite rules specified from the extended configs
20 | // e.g. "@typescript-eslint/explicit-function-return-type": "off",
21 | '@typescript-eslint/no-unused-vars': 'off',
22 | '@typescript-eslint/no-explicit-any': 'off',
23 | '@typescript-eslint/no-empty-function': 'off',
24 | '@typescript-eslint/no-var-requires': 'off',
25 |
26 | 'prettier/prettier': ['off', { endOfLine: 'auto' }],
27 | },
28 | }
29 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.yml:
--------------------------------------------------------------------------------
1 | name: "Bug report"
2 | description: 问题报告
3 | labels: [pending triage]
4 | body:
5 | - type: markdown
6 | attributes:
7 | value: |
8 | Thanks for taking the time to fill out this bug report!
9 | - type: textarea
10 | id: bug-description
11 | attributes:
12 | label: 问题描述 / Describe the bug
13 | validations:
14 | required: true
15 | - type: dropdown
16 | id: version
17 | attributes:
18 | label: Sharelist 版本 / Sharelist Version
19 | description: Sharelist Version
20 | options:
21 | - v0.1
22 | - next
23 | validations:
24 | required: true
25 | - type: textarea
26 | id: reproduction
27 | attributes:
28 | label: 复现链接 / Reproduction
29 | description: |
30 | 请提供能复现此问题的链接
31 | Please provide a link to a repo that can reproduce the problem you ran into.
32 | validations:
33 | required: false
34 | - type: textarea
35 | id: logs
36 | attributes:
37 | label: 日志 / Logs
38 | description: |
39 | 请复制错误日志,或者截图
40 | Please copy paste the log text.
41 | render: shell
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/config.yml:
--------------------------------------------------------------------------------
1 | blank_issues_enabled: false
2 | contact_links:
3 | - name: Questions & Discussions
4 | url: https://github.com/reruin/sharelist/discussions
5 | about: Use GitHub discussions for message-board style questions and discussions.
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.yml:
--------------------------------------------------------------------------------
1 | name: "Feature request"
2 | description: 功能需求
3 | labels: ["enhancement: pending triage"]
4 | body:
5 | - type: textarea
6 | id: feature-description
7 | attributes:
8 | label: 需求描述 / Description of the feature
9 | validations:
10 | required: true
11 | - type: textarea
12 | id: suggested-solution
13 | attributes:
14 | label: 实现思路 / Suggested solution
15 | description: 实现此需求的解决思路。
16 | validations:
17 | required: false
18 | - type: textarea
19 | id: additional-context
20 | attributes:
21 | label: 附件 / Additional context
22 | description: |
23 | 相关的任何其他上下文或截图。
24 | Any other context or screenshots about the feature request here.
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on:
4 | push:
5 | tags:
6 | - 'v*'
7 | jobs:
8 | build:
9 | runs-on: ubuntu-latest
10 | steps:
11 | - name: Checkout
12 | uses: actions/checkout@v2
13 | with:
14 | fetch-depth: 0
15 | - name: Use Node.js
16 | uses: actions/setup-node@v2
17 | with:
18 | node-version: '16'
19 | - name: Get Env
20 | uses: actions/github-script@v4
21 | with:
22 | script: |
23 | const tag = process.env.GITHUB_REF.split('/').slice(-1)[0]
24 | const createChangelog = require('./scripts/changelog.js')
25 | const content = await createChangelog('https://github.com/'+process.env.GITHUB_REPOSITORY)
26 | core.exportVariable('CHANGELOG', content)
27 | core.exportVariable('VERSION', tag)
28 | - name: Build
29 | run: |
30 | rm -rf ./packages/sharelist-webdav
31 | yarn install
32 | yarn build-web
33 | mkdir -p ./packages/sharelist/theme/default
34 | mkdir -p ./packages/sharelist/plugins
35 | cp -r ./packages/sharelist-web/dist/* ./packages/sharelist/theme/default
36 | cp -r ./packages/sharelist-plugin/lib/* ./packages/sharelist/plugins
37 | yarn build-server
38 | - name: Release
39 | run: |
40 | cd ./packages/sharelist/build
41 | tar --transform='flags=r;s|sharelist-win-x64.exe|sharelist.exe|' -zcvf sharelist_windows_amd64.tar.gz sharelist-win-x64.exe
42 | tar --transform='flags=r;s|sharelist-macos-x64|sharelist|' -zcvf sharelist_macos_amd64.tar.gz sharelist-macos-x64
43 | tar --transform='flags=r;s|sharelist-linux-x64|sharelist|' -zcvf sharelist_linux_amd64.tar.gz sharelist-linux-x64
44 | tar --transform='flags=r;s|sharelist-linux-arm64|sharelist|' -zcvf sharelist_linux_arm64.tar.gz sharelist-linux-arm64
45 | tar --transform='flags=r;s|sharelist-linuxstatic-armv7|sharelist|' -zcvf sharelist_linux_armv7.tar.gz sharelist-linuxstatic-armv7
46 | gh release create ${{ env.VERSION }} -n "${{ env.NOTE }}" -t "${{ env.VERSION }}" ${{ env.FILES }}
47 | env:
48 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
49 | VERSION: ${{ env.VERSION }}
50 | NOTE: ${{ env.CHANGELOG }}
51 | TITLE: ${{ env.VERSION }}
52 | FILES: ./*.gz
--------------------------------------------------------------------------------
/.github/workflows/docker.yml:
--------------------------------------------------------------------------------
1 | name: Docker
2 |
3 | on:
4 | push:
5 | # branches:
6 | # - next
7 | tags:
8 | - 'v*'
9 | jobs:
10 | build:
11 | runs-on: ubuntu-latest
12 | steps:
13 | - name: Checkout
14 | uses: actions/checkout@v2
15 | - name: Use Node.js
16 | uses: actions/setup-node@v2
17 | with:
18 | node-version: '14'
19 |
20 | - name: Install dependencies
21 | run: yarn install
22 |
23 | - name: Pre Build
24 | run: |
25 | yarn build-web
26 | mkdir -p ./packages/sharelist/theme/default
27 | mkdir -p ./packages/sharelist/plugins
28 | cp -r ./packages/sharelist-web/dist/* ./packages/sharelist/theme/default
29 | cp -r ./packages/sharelist-plugin/lib/* ./packages/sharelist/plugins
30 |
31 | - name: Login to Docker Hub
32 | uses: docker/login-action@v1
33 | with:
34 | username: ${{ secrets.DOCKER_HUB_USERNAME }}
35 | password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}
36 |
37 | - name: Set up QEMU
38 | uses: docker/setup-qemu-action@v1
39 |
40 | - name: Set up Docker Buildx
41 | id: buildx
42 | uses: docker/setup-buildx-action@v1
43 | - name: Build and push
44 | id: docker_build
45 | uses: docker/build-push-action@v2
46 | with:
47 | context: ./packages/sharelist/
48 | platforms: linux/amd64,linux/arm64
49 | file: ./packages/sharelist/Dockerfile
50 | builder: ${{ steps.buildx.outputs.name }}
51 | push: true
52 | tags: ${{ secrets.DOCKER_HUB_USERNAME }}/sharelist:${{ env.VERSION }}
53 | env:
54 | VERSION: next
55 | - name: Image digest
56 | run: echo ${{ steps.docker_build.outputs.digest }}
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | build/
3 | dist/
4 | /packages/sharelist/theme/
5 | /packages/sharelist/cache/
6 | .vscode/
7 | *.lock
8 | *.log
9 | *.exe
--------------------------------------------------------------------------------
/.prettierrc.js:
--------------------------------------------------------------------------------
1 | // https://prettier.io/docs/en/configuration.html
2 | module.exports = {
3 | //分号终止符
4 | semi: false,
5 |
6 | //行尾逗号
7 | trailingComma: "all",
8 |
9 | // 使用单引号, 默认false(在jsx中配置无效, 默认都是双引号)
10 | singleQuote: true,
11 |
12 | printWidth: 120,
13 | // 换行符
14 | endOfLine: "auto",
15 |
16 | //缩进 default:2
17 | tabWidth: 2,
18 |
19 | endOfLine: "auto",
20 |
21 | arrowParens: "avoid"
22 | }
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018-present, Reruin and Sharelist contributors
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ShareList
2 |
3 | [](https://github.com/reruin/sharelist/actions/workflows/ci.yml)
4 |
5 | ShareList 是一个易用的网盘工具,支持快速挂载 GoogleDrive、OneDrive ,可通过插件扩展功能。
6 | 新版正在开发中,欢迎[提交反馈](https://github.com/reruin/sharelist/issues/new/choose)。[查看旧版](https://github.com/reruin/sharelist/tree/0.1)。
7 |
8 | ## 文档
9 | [查看文档](https://reruin.github.io/sharelist/docs/#/zh-cn/)
10 |
11 | ## 进度
12 | - [x] 核心库支持
13 | - [x] 新主题
14 | - [x] 插件:onedrive/aliyundrive/caiyun/ctcloud/baidu/localfile
15 | - [x] webdav
16 |
17 | | | 下载 | 上传 | 列目录 | 创建目录 | 删除 | 重命名 | 远程移动 |
18 | | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- |
19 | Local File | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
20 | AliyunDrive | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
21 | CaiYun | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
22 | CTCloud | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
23 | Baidu Netdisk | ✓ | x | ✓ | ✓ | ✓ | ✓ | ✓ |
24 | OneDrive | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
25 | GoogleDrive | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
26 |
27 | - [ ] 中转器
28 |
29 |
30 | ## 安装
31 | ```docker
32 | docker run -d -v /etc/sharelist:/sharelist/cache -p 33001:33001 --name="sharelist" reruin/sharelist:next
33 | ```
34 |
35 | [release](https://github.com/reruin/sharelist/releases)下载二进制版。
36 |
37 |
38 | ## 许可
39 | [MIT](https://opensource.org/licenses/MIT)
40 | Copyright (c) 2018-present, Reruin
41 |
--------------------------------------------------------------------------------
/docs/.nojekyll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reruin/sharelist/8761eb527ea85737859e61f3a5bacd767601923d/docs/.nojekyll
--------------------------------------------------------------------------------
/docs/README.md:
--------------------------------------------------------------------------------
1 | # ShareList
2 |
3 | [](https://github.com/reruin/sharelist/actions/workflows/ci.yml)
4 |
5 | ## Introduction
6 | ShareList is an easy-to-use netdisk tool which supports that quickly mount GoogleDrive and OneDrive, and extends functions with plugins.
7 |
8 | ## Documentation
9 | Check out [docs](https://reruin.github.io/sharelist/#/en/).
10 |
11 | ## License
12 | [Apache-2](http://www.apache.org/licenses/LICENSE-2.0)
13 | Copyright (c) 2018-present, Reruin
--------------------------------------------------------------------------------
/docs/_navbar.md:
--------------------------------------------------------------------------------
1 | - Translations
2 | - [:uk: English](/)
3 | - [:cn: 中文](/zh-cn/)
4 |
--------------------------------------------------------------------------------
/docs/en/README.md:
--------------------------------------------------------------------------------
1 | # ShareList
2 |
3 | [](https://travis-ci.com/reruin/sharelist)
4 |
5 | ## Introduction
6 | ShareList is an easy-to-use netdisk tool which supports that quickly mount GoogleDrive and OneDrive, and extends functions with plugins.
7 |
8 | ## Documentation
9 | Check out [docs](https://reruin.github.io/sharelist/#/en/).
10 |
11 | ## License
12 | [Apache-2](http://www.apache.org/licenses/LICENSE-2.0)
13 | Copyright (c) 2018-present, Reruin
--------------------------------------------------------------------------------
/docs/en/_navbar.md:
--------------------------------------------------------------------------------
1 | - Translations
2 | - [中文](/zh-cn/)
3 | - [English](/en/)
--------------------------------------------------------------------------------
/docs/en/_sidebar.md:
--------------------------------------------------------------------------------
1 | * [Installation](en/installation.md)
2 | * [Configuration](en/configuration.md)
3 | * [Advance](en/advance.md)
--------------------------------------------------------------------------------
/docs/en/installation.md:
--------------------------------------------------------------------------------
1 | # Installation
2 |
3 | Translation needed!
4 |
--------------------------------------------------------------------------------
/docs/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | ShareList Docs
7 |
8 |
9 |
10 |
11 |
12 |
17 |
18 |
19 |
20 |
21 |
47 |
48 |
49 |
50 |
51 |
--------------------------------------------------------------------------------
/docs/zh-cn/README.md:
--------------------------------------------------------------------------------
1 | # ShareList
2 |
3 | [](https://github.com/reruin/sharelist/actions/workflows/ci.yml)
4 |
5 | ## 介绍
6 | ShareList 是一个易用的网盘工具,支持快速挂载 GoogleDrive、OneDrive ,可通过插件扩展功能。
7 |
8 | ## 许可
9 | [Apache-2](http://www.apache.org/licenses/LICENSE-2.0)
10 | Copyright (c) 2018-present, Reruin
--------------------------------------------------------------------------------
/docs/zh-cn/_navbar.md:
--------------------------------------------------------------------------------
1 | - Translations
2 | - [中文](/zh-cn/)
3 | - [English](/en/)
--------------------------------------------------------------------------------
/docs/zh-cn/_sidebar.md:
--------------------------------------------------------------------------------
1 | * [安装](zh-cn/installation.md)
2 | * [后台配置](zh-cn/configuration.md)
3 | * [高级用法](zh-cn/advance.md)
--------------------------------------------------------------------------------
/docs/zh-cn/advance.md:
--------------------------------------------------------------------------------
1 | ## 目录加密
2 | 在需加密目录内新建 ```.passwd``` 文件(此项可修改),```type```为验证方式,```data```为验证内容。
3 | 例如:
4 | ```yaml
5 | type: basic
6 | data:
7 | - 123456
8 | - abcdef
9 | ```
10 |
11 | 可使用密码```123456```,```abcdef```验证。
12 |
13 | ***
14 |
15 | ## 获取文件夹ID
16 | 保持后台登录状态,回到首页列表,点击文件夹后的 '!' 按钮 可查看文件夹ID。
17 |
18 | ***
19 |
20 | ## Nginx(Caddy)反向代理
21 | 使用反代时,请添加以下配置。
22 |
23 | #### Nginx
24 | ```ini
25 | proxy_set_header Host $host;
26 | proxy_set_header X-Real-IP $remote_addr;
27 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
28 | proxy_set_header X-Forwarded-Proto $scheme;
29 |
30 | proxy_set_header Range $http_range;
31 | proxy_set_header If-Range $http_if_range;
32 | proxy_no_cache $http_range $http_if_range;
33 | ```
34 | 如果使用上传功能,请调整 nginx 上传文件大小限制。
35 | ```
36 | client_max_body_size 8000m;
37 | ```
38 | #### Caddy
39 | ```ini
40 | header_upstream Host {host}
41 | header_upstream X-Real-IP {remote}
42 | header_upstream X-Forwarded-For {remote}
43 | header_upstream X-Forwarded-Proto {scheme}
44 | ```
--------------------------------------------------------------------------------
/docs/zh-cn/configuration.md:
--------------------------------------------------------------------------------
1 | # 后台管理
2 | 访问 ```http://localhost:33001/@manage```,填写口令即可进入后台管理。
3 |
4 | ## 常规
5 |
6 | ### 后台管理
7 | 设置后台管理密码。默认 ```sharelist```。
8 |
9 | ### 网站标题
10 | 设置网站标题。
11 |
12 | ### 目录索引
13 | 默认启用。如果只提供下载功能,可禁用此项。
14 |
15 | ### 展开单一挂载盘
16 | 默认启用。如果只有一个挂载盘,将默认展开。
17 |
18 | ### 允许下载
19 | 默认启用。
20 |
21 | ### 忽略路径
22 | 设置禁止访问的目录/文件路径。支持 [gitignore](http://git-scm.com/docs/gitignore) 表达式
23 |
24 | ### 加密文件名
25 | 默认```.passwd```,修改此项自定义加密文件名。
26 |
27 | ### WebDAV 路径
28 | WebDAV路径。
29 |
30 | ### WebDAV 代理
31 | 默认启用。
32 |
33 | ### WebDAV 用户
34 | 默认 ```admin```。
35 |
36 | ### WebDAV 密码
37 | 默认 ```sharelist```。
38 |
39 | ### 自定义脚本
40 | 默认主题支持自定义脚本。可用于插入统计脚本。
41 |
42 | ### 自定义样式
43 | 默认主题支持自定义样式。
--------------------------------------------------------------------------------
/docs/zh-cn/installation.md:
--------------------------------------------------------------------------------
1 | # 安装
2 | Sharelist支持多种安装方式。
3 |
4 | ## Docker
5 | ```bash
6 | docker run -d -v /etc/sharelist:/sharelist/cache -p 33001:33001 --name="sharelist" reruin/sharelist:next
7 | ```
8 |
9 | ## 二进制版
10 | [release](https://github.com/reruin/sharelist/releases)下载二进制版。
11 |
12 |
13 | ## Heroku
14 | 请 Fork [sharelist-heroku](https://github.com/reruin/sharelist-heroku/tree/next),然后在个人仓库下点 Deploy to HeroKu。
15 |
16 |
17 | 安装完成首次访问 `http://localhost:33001`地址,将进入默认界面。访问`http://localhost:33001/@manage` 进入后台管理,默认口令为 ```sharelist```。
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "sharelist-monorepo",
3 | "private": true,
4 | "workspaces": [
5 | "packages/*"
6 | ],
7 | "engines": {
8 | "node": ">=14.0.0"
9 | },
10 | "scripts": {
11 | "dev": "cd packages/sharelist && yarn dev",
12 | "build-server": "cd packages/sharelist && yarn pkg",
13 | "dev-web": "cd packages/sharelist-web && yarn dev",
14 | "build-web": "cd packages/sharelist-web && yarn build",
15 | "build": "node scripts/build.js",
16 | "lint": "eslint --ext .ts packages/*/src/**.ts",
17 | "format": "prettier --write --parser typescript \"packages/**/*.ts?(x)\"",
18 | "release": "node scripts/release.js",
19 | "ci": "cd packages/sharelist && yarn release",
20 | "changelog": "node scripts/changelog.js",
21 | "pkg": "cd packages/sharelist && yarn pkg-demo"
22 | },
23 | "devDependencies": {
24 | "chalk": "^4.1.2",
25 | "conventional-changelog-cli": "^2.1.1",
26 | "cross-env": "^7.0.3",
27 | "eslint": "^7.28.0",
28 | "execa": "^5.1.1",
29 | "minimist": "^1.2.5",
30 | "nodemon": "^2.x",
31 | "pkg": "^5.3.1",
32 | "prompts": "^2.4.1",
33 | "semver": "^7.3.5"
34 | }
35 | }
--------------------------------------------------------------------------------
/packages/sharelist-core/.prettierrc.js:
--------------------------------------------------------------------------------
1 | // https://prettier.io/docs/en/configuration.html
2 | module.exports = {
3 | //分号终止符
4 | semi: false,
5 |
6 | //行尾逗号
7 | trailingComma: "all",
8 |
9 | // 使用单引号, 默认false(在jsx中配置无效, 默认都是双引号)
10 | singleQuote: true,
11 |
12 | printWidth: 120,
13 | // 换行符
14 | endOfLine: "auto",
15 |
16 | //缩进 default:2
17 | tabWidth: 2
18 | }
--------------------------------------------------------------------------------
/packages/sharelist-core/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## [0.1.7](https://github.com/reruin/sharelist/compare/v0.3.6...v0.1.7) (2021-10-14)
2 |
3 |
4 | ### Bug Fixes
5 |
6 | * **core:** fix some bugs ([c8ee9e6](https://github.com/reruin/sharelist/commit/c8ee9e655687d49420c54c9331a91b19aae10beb))
7 |
8 |
9 |
10 | ## [0.1.6](https://github.com/reruin/sharelist/compare/v0.3.5...v0.1.6) (2021-10-12)
11 |
12 |
13 |
14 | ## [0.1.5](https://github.com/reruin/sharelist/compare/v0.3.3...v0.1.5) (2021-10-10)
15 |
16 |
17 | ### Bug Fixes
18 |
19 | * **core:** add createReadStream ([27350fb](https://github.com/reruin/sharelist/commit/27350fb6a036ab13e65dc0b52dab3f85732d5667))
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/packages/sharelist-core/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021-present, Reruin and Sharelist contributors
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/packages/sharelist-core/README.md:
--------------------------------------------------------------------------------
1 | # @sharelist/core [](https://npmjs.com/package/@sharelist/core)
2 |
3 | It's a framework for mounting netdisk.
4 |
5 | ## Useage
6 |
--------------------------------------------------------------------------------
/packages/sharelist-core/index.js:
--------------------------------------------------------------------------------
1 | module.exports = require('./lib/index')
2 |
--------------------------------------------------------------------------------
/packages/sharelist-core/lib/rectifier.js:
--------------------------------------------------------------------------------
1 | const { resolve4 } = require('dns')
2 | const { Readable, Writable } = require('stream')
3 |
4 | exports.createReadStream = function (readStream, { highWaterMark } = { highWaterMark: 10 * 1024 * 1024 }) {
5 | const buffers = []// 缓冲区
6 |
7 | let total = 0
8 | let tasks = []
9 | let ended = false
10 | const sliceBuffer = (n) => {
11 | let part = Buffer.alloc(n)
12 | let b
13 | let index = 0
14 | let flag = false
15 | while (null != (b = buffers.shift())) {
16 | for (let i = 0; i < b.length; i++) {
17 | part[index++] = b[i]
18 | if (index == n) {//填充完毕
19 | //将多出的部分存回头部
20 | b = b.slice(i + 1)
21 | buffers.unshift(b)
22 | flag = true
23 | break;
24 | }
25 | }
26 | if (flag) break;
27 | }
28 | total -= n
29 | return part
30 | }
31 |
32 |
33 | const check = () => {
34 | let task = tasks[0]
35 |
36 | if (task) {
37 | if (total >= task.size) {
38 | let chunk = sliceBuffer(task.size)
39 | task.done(chunk)
40 | tasks.shift()
41 | } else {
42 | if (ended) {
43 | let chunk = sliceBuffer(total)
44 | task.done(chunk)
45 | tasks.shift()
46 | }
47 | }
48 | }
49 |
50 | if (!ended) {
51 | if (tasks.length == 0 && total > highWaterMark) {
52 | readStream.pause()
53 | } else {
54 | if (readStream.isPaused()) {
55 | readStream.resume()
56 | }
57 | }
58 | }
59 | }
60 |
61 | const read = (size) => new Promise((resolve, reject) => {
62 | if (ended && total == 0) {
63 | resolve()
64 | } else {
65 | tasks.push({ size, done: resolve })
66 | check()
67 | }
68 | })
69 |
70 |
71 | readStream.on('data', (chunk) => {
72 | total += chunk.length
73 | buffers.push(chunk.slice(0))
74 | check()
75 | })
76 |
77 | readStream.on('end', () => {
78 | ended = true
79 | check()
80 | //let task = tasks.pop()
81 | })
82 | return { read }
83 | }
84 |
85 | exports.rectifier = function (size = 1 * 1024 * 1024, cb) {
86 |
87 | const buffers = []// 缓冲区
88 |
89 | let total = 0
90 | let part = 0
91 | let partSize = 0
92 | let cacheRate = 3
93 |
94 | const writable = new Writable({
95 | write(chunk, encoding, callback) {
96 | let bytesRead = chunk.length
97 |
98 | partSize += bytesRead
99 | total += bytesRead
100 |
101 | buffers.push(chunk.slice(0))
102 |
103 | //截取片段
104 | if (partSize >= size) {
105 | let n = size
106 | let partBuffer = Buffer.alloc(n)
107 | let b
108 | let index = 0
109 | let flag = false
110 | while (null != (b = buffers.shift())) {
111 | for (let i = 0; i < b.length; i++) {
112 | partBuffer[index++] = b[i]
113 | if (index == n) {//填充完毕
114 |
115 | //将多出的部分存回头部
116 | b = b.slice(i + 1)
117 | buffers.unshift(b)
118 | partSize = b.length
119 | flag = true
120 | break;
121 | }
122 | }
123 | if (flag) break;
124 | }
125 |
126 | // console.log('partSize', size, partSize, total)
127 | //缓冲
128 | if (partSize < size * cacheRate) {
129 | callback()
130 | }
131 |
132 | cb({ total, chunk: partBuffer, chunk_index: part++ }, () => {
133 | //callback()
134 | })
135 | } else {
136 | //继续写入
137 | callback()
138 | }
139 | },
140 |
141 | // 处理剩余的部分
142 | final(callback) {
143 | let n = total - size * part
144 |
145 | // 文件大小是 size 的倍数时
146 | if (n == 0) {
147 | callback()
148 | return
149 | }
150 |
151 | //处理掉剩余部分
152 | let partBuffer = Buffer.alloc(n)
153 | let b
154 | let index = 0
155 | while (null != (b = buffers.shift())) {
156 | for (let i = 0; i < b.length; i++) {
157 | partBuffer[index++] = b[i]
158 | }
159 | }
160 |
161 | cb({ total, chunk: partBuffer, chunk_index: part++, ended: true }, () => {
162 | callback()
163 | })
164 | }
165 | })
166 |
167 |
168 | return writable
169 |
170 | }
--------------------------------------------------------------------------------
/packages/sharelist-core/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@sharelist/core",
3 | "version": "0.1.2",
4 | "lockfileVersion": 1,
5 | "requires": true,
6 | "dependencies": {
7 | "imurmurhash": {
8 | "version": "0.1.4",
9 | "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
10 | "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o="
11 | },
12 | "is-typedarray": {
13 | "version": "1.0.0",
14 | "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
15 | "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo="
16 | },
17 | "mime": {
18 | "version": "2.5.2",
19 | "resolved": "https://registry.npmjs.org/mime/-/mime-2.5.2.tgz",
20 | "integrity": "sha512-tqkh47FzKeCPD2PUiPB6pkbMzsCasjxAfC62/Wap5qrUWcb+sFasXUC5I3gYM5iBM8v/Qpn4UK0x+j0iHyFPDg=="
21 | },
22 | "node-fetch": {
23 | "version": "2.6.1",
24 | "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz",
25 | "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw=="
26 | },
27 | "signal-exit": {
28 | "version": "3.0.3",
29 | "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz",
30 | "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA=="
31 | },
32 | "typedarray-to-buffer": {
33 | "version": "3.1.5",
34 | "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz",
35 | "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==",
36 | "requires": {
37 | "is-typedarray": "^1.0.0"
38 | }
39 | },
40 | "write-file-atomic": {
41 | "version": "3.0.3",
42 | "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz",
43 | "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==",
44 | "requires": {
45 | "imurmurhash": "^0.1.4",
46 | "is-typedarray": "^1.0.0",
47 | "signal-exit": "^3.0.2",
48 | "typedarray-to-buffer": "^3.1.5"
49 | }
50 | },
51 | "yaml": {
52 | "version": "1.10.2",
53 | "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz",
54 | "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg=="
55 | }
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/packages/sharelist-core/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@sharelist/core",
3 | "version": "0.1.7",
4 | "repository": "https://github.com/reruin/sharelist",
5 | "license": "MIT",
6 | "main": "index.js",
7 | "scripts": {
8 | "build": "echo \"Info: no specified\" && exit ",
9 | "test": "node test/index.js",
10 | "changelog": "conventional-changelog -p angular -i CHANGELOG.md -s --commit-path .",
11 | "release": "node ../../scripts/release.js --commit-path ."
12 | },
13 | "dependencies": {
14 | "mime": "^2.5.2",
15 | "node-fetch": "^2.6.1",
16 | "write-file-atomic": "^3.0.3",
17 | "yaml": "^1.10.2"
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/packages/sharelist-core/test/index.js:
--------------------------------------------------------------------------------
1 | const createDriver = require('../')
2 |
3 | const path = require('path')
4 |
5 | const testdriver = () => {
6 | const list = (id) => {
7 | return {
8 | id: 'folder',
9 | files: new Array(10).fill(0).map((i) => ({
10 | id: 'folder' + i,
11 | name: 'file' + i,
12 | size: 0,
13 | type: 'folder',
14 | ctime: Date.now(),
15 | mtime: Date.now(),
16 | })),
17 | }
18 | }
19 |
20 | const get = (id) => {
21 | return {
22 | id: id,
23 | name: 'file' + id,
24 | size: 0,
25 | type: 'file',
26 | ctime: Date.now(),
27 | mtime: Date.now(),
28 | }
29 | }
30 |
31 | return { name: 'test', protocol: 'test', mountable: true, list, get }
32 | };
33 |
34 | (async () => {
35 | const driver = await createDriver({
36 | plugins: [testdriver]
37 | })
38 |
39 | console.log(await driver.list())
40 | })()
41 |
--------------------------------------------------------------------------------
/packages/sharelist-core/test/plugin/driver.test.js:
--------------------------------------------------------------------------------
1 | modules.export = () => {
2 | const list = (id) => {
3 | return {
4 | id: 'folder',
5 | files: new Array(10).fill(0).map((i) => ({
6 | id: 'folder' + i,
7 | name: 'file' + i,
8 | size: 0,
9 | type: 'folder',
10 | ctime: Date.now(),
11 | mtime: Date.now(),
12 | })),
13 | }
14 | }
15 |
16 | const get = (id) => {
17 | return {
18 | id: id,
19 | name: 'file' + id,
20 | size: 0,
21 | type: 'file',
22 | ctime: Date.now(),
23 | mtime: Date.now(),
24 | }
25 | }
26 |
27 | return { name: 'test', protocol: 'test', mountable: true, list, get }
28 | }
29 |
--------------------------------------------------------------------------------
/packages/sharelist-plugin/CHANGELOG.md:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reruin/sharelist/8761eb527ea85737859e61f3a5bacd767601923d/packages/sharelist-plugin/CHANGELOG.md
--------------------------------------------------------------------------------
/packages/sharelist-plugin/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018-present, Reruin and Sharelist contributors
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/packages/sharelist-plugin/lib/auth.js:
--------------------------------------------------------------------------------
1 |
2 | const authMethods = {
3 | 'basic': (key, data) => {
4 | return data.find(i => '' + i == key)
5 | },
6 | 'http': (key, data, { request }) => {
7 | let { data: res } = request(data.replace('{key}', key), { responseType: 'text' })
8 | return res === 'success'
9 | }
10 | }
11 |
12 | const cache = {}
13 |
14 | module.exports = (app) => {
15 | const { onListed, config, driver, utils: { yaml, safeCall } } = app
16 |
17 | onListed(async (data, query) => {
18 | let hit = data?.files.find(i => i.name == config.acl_file)
19 | if (hit) {
20 | let auth = query?.auth
21 |
22 | if (auth) {
23 | let content
24 |
25 | if (cache[hit.id]) {
26 | content = cache[hit.id]
27 | } else {
28 | await safeCall(async () => {
29 | content = yaml.parse(await driver.getContent(hit.id))
30 | })
31 | }
32 |
33 | if (content) {
34 | cache[hit.id] = content
35 |
36 | if (authMethods?.[content.type]('' + auth, content.data, app)) {
37 | return data
38 | }
39 | }
40 |
41 | return { error: { code: 401, message: 'Invalid password' } }
42 | } else {
43 | return { error: { code: 401, message: 'Invalid password' } }
44 | }
45 | }
46 |
47 | return data
48 | })
49 |
50 | }
--------------------------------------------------------------------------------
/packages/sharelist-plugin/lib/driver.http.js:
--------------------------------------------------------------------------------
1 | /*
2 | * http
3 | */
4 |
5 | const name = 'HTTPFile'
6 |
7 | const version = '1.0'
8 |
9 | const protocol = 'http'
10 |
11 | module.exports = ({ wrapReadableStream, request }) => {
12 | const file = (id) => {
13 | return {
14 | id,
15 | name: id.split('/').pop(),
16 | ext: id.split('.').pop(),
17 | url: `http:${id}`,
18 | protocol: 'http',
19 | }
20 | }
21 |
22 | const folder = file
23 |
24 | const getFileSize = async (url, headers) => {
25 | try {
26 | let nh = await request.header(decodeURIComponent(url), { headers })
27 | if (nh && nh['content-length']) {
28 | return nh['content-length']
29 | } else {
30 | return null
31 | }
32 | } catch (e) {
33 | return null
34 | }
35 | }
36 |
37 | const createReadStream = async ({ id, options = {} } = {}) => {
38 | let url = encodeURI(`http:${id}`)
39 | let size = await getFileSize(url)
40 | console.log('get file size', size)
41 | let readstream = request({ url: decodeURIComponent(url), method: 'get' })
42 | return wrapReadableStream(readstream, { size })
43 | }
44 |
45 | return { name, version, drive: { protocol, folder, file, createReadStream, mountable: false } }
46 | }
47 |
--------------------------------------------------------------------------------
/packages/sharelist-plugin/lib/driver.https.js:
--------------------------------------------------------------------------------
1 | /*
2 | * https
3 | */
4 |
5 | const name = 'HTTPSFile'
6 |
7 | const version = '1.0'
8 |
9 | const protocol = 'https'
10 |
11 | module.exports = ({ wrapReadableStream, request }) => {
12 | const file = (id) => {
13 | return {
14 | id,
15 | name: id.split('/').pop(),
16 | ext: id.split('.').pop(),
17 | url: `https:${id}`,
18 | protocol: 'https',
19 | }
20 | }
21 |
22 | const folder = file
23 |
24 | const getFileSize = async (url, headers) => {
25 | try {
26 | let nh = await request.header(decodeURIComponent(url), { headers })
27 | if (nh && nh['content-length']) {
28 | return nh['content-length']
29 | } else {
30 | return null
31 | }
32 | } catch (e) {
33 | return null
34 | }
35 | }
36 |
37 | const createReadStream = async ({ id, options = {} } = {}) => {
38 | let url = encodeURI(`https:${id}`)
39 | let size = await getFileSize(url)
40 | console.log('get file size', size)
41 | let readstream = request({ url: decodeURIComponent(url), method: 'get' })
42 | return wrapReadableStream(readstream, { size })
43 | }
44 |
45 | return { name, version, drive: { protocol, folder, file, createReadStream, mountable: false } }
46 | }
47 |
--------------------------------------------------------------------------------
/packages/sharelist-plugin/lib/driver.sld.js:
--------------------------------------------------------------------------------
1 | /*
2 | * ShareListDrive 是 sharelist 内置的使用yaml/json描述的网盘系统 , 没有缓存
3 | */
4 |
5 | const name = 'ShareListDrive'
6 |
7 | const version = '1.0'
8 |
9 | const protocol = 'sld'
10 |
11 | const yaml = require('yaml')
12 |
13 | const { URL } = require('url')
14 |
15 | const crypto = require('crypto')
16 |
17 | const md5 = (v) => {
18 | return crypto.createHash('md5').update(v).digest('hex')
19 | }
20 |
21 | module.exports = (app) => {
22 | const diskMap = {}
23 |
24 | const parse = (id) => {
25 | let data = new URL(id)
26 |
27 | let rootId = data.hostname
28 | let path = data.pathname.replace(/^\/+/, '').split('/')
29 | return {
30 | disk: diskMap[rootId],
31 | path,
32 | }
33 | }
34 |
35 | /* 递归生成 索引 id */
36 | const createId = (d, rootId) => {
37 | d.forEach((i, index) => {
38 | if (isObject(i)) {
39 | let name = i.name.replace(/\.d\.ln$/, '').replace(/\.ln$/, '')
40 | i.id = rootId + '/' + name
41 | i.protocol = protocol
42 | if (i.children) {
43 | i.type = 'folder'
44 | i.protocol = protocol
45 | createId(i.children, i.id)
46 | } else {
47 | i.ext = i.name.split('.').pop()
48 | }
49 | } else if (isArray(i)) {
50 | createId(i, rootId)
51 | }
52 | })
53 | return d
54 | }
55 |
56 | const mount = async (key, data) => {
57 | if (data) {
58 | let key = md5(data)
59 |
60 | let id = protocol + '://' + key
61 |
62 | let resp = { id, type: 'folder', protocol: protocol }
63 |
64 | let json = yaml.parse(data)
65 | json = createId(json, id)
66 | resp.children = json
67 | resp.updated_at = Date.now()
68 |
69 | diskMap[key] = resp
70 | return resp
71 | } else {
72 | return undefined
73 | }
74 | }
75 |
76 | const findById = (id) => {
77 | let { disk, path } = parse(id)
78 | if (disk) {
79 | for (let i = 0; i < path.length && disk; i++) {
80 | disk = disk.children
81 | disk = disk.find((j) => {
82 | return j.name == decodeURIComponent(path[i])
83 |
84 | if (j.type == 'folder') {
85 | return `${j.name}.${j.ext}` == path[i]
86 | } else {
87 | return `${j.name}` == path[i]
88 | }
89 | }) //[ parseInt(path[i]) ]
90 | }
91 | return disk
92 | } else {
93 | return []
94 | }
95 | }
96 |
97 | const folder = async (id, { content } = {}) => {
98 | try {
99 | let json = yaml.parse(id)
100 | if (json && typeof json == 'object') {
101 | return mount(null, id)
102 | } else {
103 | throw new Error('')
104 | }
105 | } catch (e) {
106 | if (content) {
107 | return mount(id, content)
108 | } else {
109 | return findById(id)
110 | }
111 | }
112 | }
113 |
114 | const file = async (id) => {
115 | let data = findById(id)
116 | return data
117 | }
118 |
119 | return { name, version, drive: { protocol, folder, file } }
120 | }
121 |
--------------------------------------------------------------------------------
/packages/sharelist-plugin/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "sharelist-plugin",
3 | "version": "0.2.0",
4 | "private": true,
5 | "scripts": {
6 | "build": "echo \"\" && exit ",
7 | "changelog": "conventional-changelog -p angular -i CHANGELOG.md -s --commit-path .",
8 | "release": "node ../../scripts/release.js --skipNpmPublish"
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/packages/sharelist-web/.env:
--------------------------------------------------------------------------------
1 | PORT = 8889
2 | MAIN_ES = 0
--------------------------------------------------------------------------------
/packages/sharelist-web/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | parser: 'vue-eslint-parser',
3 | parserOptions: {
4 | // set script parser
5 | parser: '@typescript-eslint/parser', // Specifies the ESLint parser
6 | ecmaVersion: 2021, // Allows for the parsing of modern ECMAScript features
7 | sourceType: 'module', // Allows for the use of imports
8 | ecmaFeatures: {
9 | jsx: true, // Allows for the parsing of JSX
10 | },
11 | validate: [],
12 | },
13 | extends: [
14 | 'plugin:vue/vue3-recommended',
15 | 'plugin:@typescript-eslint/recommended', // Uses the recommended rules from the @typescript-eslint/eslint-plugin
16 | 'plugin:prettier/recommended',
17 | ],
18 | rules: {
19 | // Place to specify ESLint rules. Can be used to overwrite rules specified from the extended configs
20 | // e.g. "@typescript-eslint/explicit-function-return-type": "off",
21 | '@typescript-eslint/no-unused-vars': 'off',
22 | '@typescript-eslint/no-explicit-any': 'off',
23 | '@typescript-eslint/no-empty-function': 'off',
24 |
25 | // 'object-curly-spacing': ['error', 'always'],
26 | },
27 | }
28 |
--------------------------------------------------------------------------------
/packages/sharelist-web/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .DS_Store
3 | dist
4 | dist-ssr
5 | *.local
6 | yarn-error.log
7 | package-lock.json
8 | yarn.lock
--------------------------------------------------------------------------------
/packages/sharelist-web/.prettierrc.js:
--------------------------------------------------------------------------------
1 | // https://prettier.io/docs/en/configuration.html
2 | module.exports = {
3 | //分号终止符
4 | semi: false,
5 |
6 | //行尾逗号
7 | trailingComma: "all",
8 |
9 | // 使用单引号, 默认false(在jsx中配置无效, 默认都是双引号)
10 | singleQuote: true,
11 |
12 | printWidth: 120,
13 | // 换行符
14 | endOfLine: "auto",
15 |
16 | //缩进 default:2
17 | tabWidth: 2
18 | }
--------------------------------------------------------------------------------
/packages/sharelist-web/CHANGELOG.md:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reruin/sharelist/8761eb527ea85737859e61f3a5bacd767601923d/packages/sharelist-web/CHANGELOG.md
--------------------------------------------------------------------------------
/packages/sharelist-web/README.md:
--------------------------------------------------------------------------------
1 | # @sharelist/web [](https://npmjs.com/package/@sharelist/web)
2 |
3 | It's a part for sharelist
4 |
5 | ## Useage
6 |
--------------------------------------------------------------------------------
/packages/sharelist-web/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@sharelist/web",
3 | "version": "0.2.0",
4 | "scripts": {
5 | "dev": "vite",
6 | "build": "vite build",
7 | "changelog": "conventional-changelog -p angular -i CHANGELOG.md -s --commit-path .",
8 | "release": "node ../../scripts/release.js --skipBuild --skipNpmPublish"
9 | },
10 | "dependencies": {
11 | "@ant-design/icons-vue": "^6.0.1",
12 | "ant-design-vue": "2.x",
13 | "axios": "^0.21.0",
14 | "plyr": "^3.6.8",
15 | "store2": "^2.12.0",
16 | "vite": "2.3.8",
17 | "vue": "3.x",
18 | "vue-router": "4.x",
19 | "vuex": "4.x",
20 | "vuex-persistedstate": "^4.x"
21 | },
22 | "devDependencies": {
23 | "@types/node": "^15.9.0",
24 | "@typescript-eslint/eslint-plugin": "^4.23.0",
25 | "@typescript-eslint/parser": "^4.23.0",
26 | "@vitejs/plugin-legacy": "^1.5.1",
27 | "@vitejs/plugin-vue": "^1.2.2",
28 | "@vitejs/plugin-vue-jsx": "^1.1.4",
29 | "@vue/compiler-sfc": "^3.0.5",
30 | "eslint": "^7.26.0",
31 | "eslint-config-prettier": "^8.3.0",
32 | "eslint-plugin-prettier": "^3.4.0",
33 | "eslint-plugin-vue": "^7.9.0",
34 | "less": "^4.1.1",
35 | "minimist": "^1.2.5",
36 | "prettier": "^2.3.0",
37 | "typescript": "^4.1.3",
38 | "vite": "2.3.8",
39 | "vue-eslint-parser": "^7.6.0",
40 | "vue-tsc": "^0.0.24"
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/packages/sharelist-web/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reruin/sharelist/8761eb527ea85737859e61f3a5bacd767601923d/packages/sharelist-web/public/favicon.ico
--------------------------------------------------------------------------------
/packages/sharelist-web/src/App.tsx:
--------------------------------------------------------------------------------
1 | import { defineComponent } from 'vue'
2 | import { ConfigProvider } from 'ant-design-vue'
3 | import { RouterView } from 'vue-router'
4 |
5 | export default defineComponent({
6 | setup() {
7 | return () => (
8 |
9 |
10 |
11 | )
12 | },
13 | })
14 |
--------------------------------------------------------------------------------
/packages/sharelist-web/src/assets/auto.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reruin/sharelist/8761eb527ea85737859e61f3a5bacd767601923d/packages/sharelist-web/src/assets/auto.mp3
--------------------------------------------------------------------------------
/packages/sharelist-web/src/assets/img/line.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reruin/sharelist/8761eb527ea85737859e61f3a5bacd767601923d/packages/sharelist-web/src/assets/img/line.png
--------------------------------------------------------------------------------
/packages/sharelist-web/src/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reruin/sharelist/8761eb527ea85737859e61f3a5bacd767601923d/packages/sharelist-web/src/assets/logo.png
--------------------------------------------------------------------------------
/packages/sharelist-web/src/components/audio/index.less:
--------------------------------------------------------------------------------
1 | .widget-audio{
2 | display: none;
3 | position: fixed;
4 | bottom:16px;
5 | margin:auto;
6 | display: none;
7 | .aplayer{
8 | margin: auto;
9 | max-width: 500px;
10 | position: relative !important;
11 | }
12 |
13 | .aplayer.aplayer-fixed{
14 | .aplayer-body{
15 | position: relative !important;
16 | }
17 | .aplayer-list{
18 | margin-bottom: 0 !important;
19 | }
20 | }
21 | }
22 |
23 | .widget-audio--visible{
24 | display: block;
25 | // align-items: top;
26 | }
27 |
28 | .widget-close{
29 | position: absolute;
30 | z-index:1002;
31 | right:0;
32 | top:0;
33 | font-size:20px;
34 | color:#666;
35 | width:18px;height:18px;
36 | text-align: center;
37 | line-height: 18px;
38 | cursor: pointer;
39 |
40 | &:hover{
41 | color:rgb(223,73,62);
42 | }
43 | }
--------------------------------------------------------------------------------
/packages/sharelist-web/src/components/audio/index.tsx:
--------------------------------------------------------------------------------
1 | import { ref, defineComponent, onMounted, computed } from 'vue'
2 | import { CaretRightOutlined, PauseOutlined } from '@ant-design/icons-vue'
3 | import './index.less'
4 | import APlayer from 'aplayer'
5 | import 'aplayer/dist/APlayer.min.css'
6 | export default defineComponent({
7 | props: {
8 | src: {
9 | type: String,
10 | default: '',
11 | },
12 | },
13 | emits: ['loaded'],
14 | setup(props, ctx) {
15 | const el = ref()
16 | const visible = ref(false)
17 | let player: any
18 | const onAdd = () => {
19 | visible.value = true
20 | }
21 |
22 | const onClose = () => {
23 | player.pause()
24 | visible.value = false
25 | }
26 | onMounted(() => {
27 | player = new APlayer({
28 | container: el.value,
29 | fixed: true,
30 | // theme: '#000000',
31 | })
32 |
33 | player.on('listadd', onAdd)
34 | ctx.emit('loaded', player)
35 | })
36 |
37 | return () => (
38 |
44 | )
45 | },
46 | })
47 |
--------------------------------------------------------------------------------
/packages/sharelist-web/src/components/header/index.less:
--------------------------------------------------------------------------------
1 | .content-header {
2 | padding: 8px;
3 | // height:54px;
4 | background: #fff;
5 | position: relative;
6 |
7 | .content-header-fixed{
8 | position:absolute;
9 | right:0;
10 | top:0;
11 | margin:6px;
12 | }
13 | .content-header-body{
14 | display: flex;
15 | justify-content: space-between;
16 | align-items:center;
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/packages/sharelist-web/src/components/header/index.tsx:
--------------------------------------------------------------------------------
1 | import { computed, defineComponent, PropType, inject } from 'vue'
2 | import UserMenu from '../user-menu'
3 | import HintBar from '../hintbar'
4 | import SwitchMode from '../switch-mode'
5 | import Subscribe from '../subscribe'
6 |
7 | import './index.less'
8 |
9 | export default defineComponent({
10 | setup() {
11 | return (
12 |
21 | )
22 | },
23 | })
24 |
--------------------------------------------------------------------------------
/packages/sharelist-web/src/components/hintbar/index.less:
--------------------------------------------------------------------------------
1 | .hintbar {
2 | button {
3 | font-size: 12px;
4 | color: rgb(170, 173, 174);
5 | // padding:0 8px;
6 | }
7 |
8 | button:hover {
9 | color: #333;
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/packages/sharelist-web/src/components/hintbar/index.tsx:
--------------------------------------------------------------------------------
1 | import { computed, defineComponent, PropType, inject } from 'vue'
2 | import { Modal, message, Button } from 'ant-design-vue'
3 |
4 | import './index.less'
5 |
6 | type IStyle = {
7 | [key: string]: string | number
8 | }
9 | export default defineComponent({
10 | props: {
11 | color: {
12 | type: String,
13 | default: '',
14 | },
15 | },
16 | setup(props, ctx) {
17 | const { obj } = props
18 | const $electron = inject('$electron')
19 | const minimize = () => $electron.ipcRenderer.send('minimize')
20 |
21 | const close = () => {
22 | Modal.confirm({
23 | title: '提示',
24 | content: '此操作将Curve, 是否继续?',
25 | type: 'warning',
26 | onOk: () => {
27 | $electron.ipcRenderer.send('close')
28 | },
29 | })
30 | }
31 | const style: IStyle = {}
32 | // eslint-disable-next-line vue/no-setup-props-destructure
33 | if (props.color) style.color = props.color
34 | return (
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 | )
44 | },
45 | })
46 |
--------------------------------------------------------------------------------
/packages/sharelist-web/src/components/icon/index.less:
--------------------------------------------------------------------------------
1 | // .sl-icon {
2 | // display: inline-block;
3 | // font-style: normal;
4 | // vertical-align: -0.125em;
5 | // text-align: center;
6 | // text-transform: none;
7 | // line-height: 0;
8 | // text-rendering: optimizeLegibility;
9 | // -webkit-font-smoothing: antialiased;
10 | // }
11 | @type: {
12 | icon-folder:#f8d673;
13 | icon-other:#4285f4;
14 | icon-audio:#db4437;
15 | icon-video:#db4437;
16 | icon-word:#2F97FE;
17 | icon-pdf:#FC5A5A;
18 | icon-image:#db4437;
19 | icon-doc:#4285f4;
20 | icon-ppt:#db4437;
21 | }
22 | // :root{
23 | // each(@type, {
24 | // --@{key}: @value;
25 | // });
26 | // }
27 |
28 | each(@type, {
29 | #@{key}{
30 | color:~"var(--@{key},@{value})";
31 | }
32 | });
--------------------------------------------------------------------------------
/packages/sharelist-web/src/components/icon/index.ts:
--------------------------------------------------------------------------------
1 | import { createFromIconfontCN } from '@ant-design/icons-vue'
2 | import config from '../../config/setting'
3 | import './icon-svg'
4 | import './index.less'
5 | const IconFont = createFromIconfontCN({
6 | scriptUrl: [],
7 | })
8 |
9 | export default IconFont
10 |
--------------------------------------------------------------------------------
/packages/sharelist-web/src/components/power/index.js:
--------------------------------------------------------------------------------
1 | import IPC from '@/utils/ipc'
2 |
3 | export default {
4 | methods: {
5 | data() {
6 | return {}
7 | },
8 | created() {
9 | IPC.getStatus((resp) => { })
10 | },
11 | onChange(v) {
12 | IPC.setStatus(v)
13 | },
14 | },
15 | render() {
16 | /*
17 | this.link('option')} class={{'no-drag':true , 'active':this.active}} type="link">
18 |
19 |
20 | */
21 | return
22 | },
23 | }
24 |
--------------------------------------------------------------------------------
/packages/sharelist-web/src/components/qrcode/index.tsx:
--------------------------------------------------------------------------------
1 | import QRCode from 'qrcode'
2 | import { watchEffect, defineComponent, render } from 'vue'
3 | export default defineComponent({
4 | name: 'SlQrcode',
5 |
6 | props: {
7 | content: {
8 | type: String,
9 | default: '',
10 | },
11 | width: {
12 | type: Number,
13 | default: 200,
14 | },
15 | height: {
16 | type: Number,
17 | default: 200,
18 | },
19 | margin: {
20 | type: Number,
21 | default: 0,
22 | },
23 | transparent: {
24 | type: Boolean,
25 | default: false,
26 | },
27 | },
28 | setup(props) {
29 | const render = () => {
30 | const options = {
31 | margin: props.margin,
32 | width: props.width,
33 | height: props.height,
34 | }
35 |
36 | if (props.transparent) {
37 | options.color = {
38 | light: '#0000', // Transparent background
39 | }
40 | }
41 |
42 | QRCode.toCanvas(this.$el, this.content, options, (err, data) => {
43 | if (err) throw err
44 | })
45 | }
46 |
47 | watchEffect(() => {
48 | if (props.content) {
49 | render(props.content)
50 | }
51 | })
52 |
53 | return
54 | },
55 | })
56 |
--------------------------------------------------------------------------------
/packages/sharelist-web/src/components/sider/index.less:
--------------------------------------------------------------------------------
1 | .sider {
2 | display: flex;
3 | flex-direction: column;
4 | justify-content: space-between;
5 | height: 100%;
6 |
7 | .menu{
8 | button{
9 | color:#aaa;
10 | font-size:20px;
11 | display: block;
12 | width:100%;
13 | height:48px;
14 | line-height: 48px;
15 | text-align: center;
16 | border:none;
17 | border-radius:0;
18 | i{
19 | color:currentColor;
20 | }
21 |
22 | &:hover{
23 | color:#fff;
24 | }
25 |
26 | &.active{
27 | background:#bb040f;
28 | color:#fff;
29 | }
30 | }
31 | }
32 |
33 | .logo {
34 | text-align:center;
35 | font-size: 14px;
36 | padding:24px 0;
37 | font-weight: 600;
38 | color: #fff;
39 | line-height: 1em;
40 | font-family: -apple-system, BlinkMacSystemFont, Helvetica Neue, Helvetica, Roboto, Arial, PingFang SC, Hiragino Sans GB, Microsoft Yahei, Microsoft Jhenghei, sans-serif;
41 | text-shadow: 0 0 1px rgba(0, 0, 0, .1);
42 | }
43 |
44 | .sider-body{
45 |
46 | }
47 | .sider-footer {
48 |
49 | font-size: 13px;
50 | color: #fff;
51 | margin: 0 auto;
52 | padding: 16px 0;
53 | button{
54 | border-color:transparent;
55 | color:#aaa;
56 | &.active{
57 | color:#fff;
58 | }
59 | }
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/packages/sharelist-web/src/components/sider/index.tsx:
--------------------------------------------------------------------------------
1 | import { computed, defineComponent } from 'vue'
2 | import { useStore } from 'vuex'
3 | import { useRoute, useRouter } from 'vue-router'
4 | import { Modal, message, Button } from 'ant-design-vue'
5 | import { GlobalOutlined, SettingOutlined } from '@ant-design/icons-vue'
6 |
7 | import './index.less'
8 |
9 | export default defineComponent({
10 | setup() {
11 | const store = useStore()
12 | const route = useRoute()
13 | const router = useRouter()
14 |
15 | const defActive = computed(() => route.name)
16 | const link = (name: string) => router.push({ name })
17 |
18 | const signout = (): void => {
19 | Modal.confirm({
20 | title: '提示',
21 | content: '确定要注销登录吗 ?',
22 | onOk: () => {
23 | return store
24 | .dispatch('signout', {})
25 | .then(() => {
26 | window.location.reload()
27 | })
28 | .catch((err) => {
29 | message.error({
30 | title: '错误',
31 | description: err.message,
32 | })
33 | })
34 | },
35 | onCancel() {
36 | //
37 | },
38 | })
39 | }
40 | return (
41 |
42 |
43 |
CURVE
44 |
57 | {/*
58 |
59 |
60 | 用户
61 |
62 |
63 |
64 | 订单
65 |
66 |
67 |
68 | 设置
69 |
70 | */}
71 |
72 |
84 |
85 | )
86 | },
87 | })
88 |
--------------------------------------------------------------------------------
/packages/sharelist-web/src/components/subscribe/index.tsx:
--------------------------------------------------------------------------------
1 | import Qrcode from '../qrcode'
2 | import { ref, defineComponent } from 'vue'
3 | import { message, Modal } from 'ant-design-vue'
4 | import { AppleFilled } from '@ant-design/icons-vue'
5 | import request from '@/utils/request'
6 |
7 | //https://d3V0aW5nMDEyMkAxNjMuY29tOjA1NTkwNTU5QGtyLWEuaHVieS54eXo6NDQzMw?cert=&peer=#YL-SGP
8 |
9 | export default defineComponent({
10 | setup() {
11 | const visible = ref(false)
12 | const url = ref('')
13 | const handleClick = async () => {
14 | visible.value = true
15 | const resp = await request.subscribe()
16 | if (resp.status) {
17 | message.error(resp.result)
18 | } else {
19 | url.value = resp.result.url
20 | }
21 | }
22 |
23 | const handleClose = () => {
24 | visible.value = false
25 | }
26 |
27 | return (
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 | 打开小火箭扫码订阅
39 |
40 |
41 |
42 |
43 | )
44 | },
45 | })
46 |
--------------------------------------------------------------------------------
/packages/sharelist-web/src/components/switch-mode/index.tsx:
--------------------------------------------------------------------------------
1 | import { useStore } from 'vuex'
2 | import IPC from '@/utils/ipc'
3 | import { computed, defineComponent, PropType, inject } from 'vue'
4 |
5 | export default defineComponent({
6 | setup() {
7 | const store = useStore()
8 |
9 | const modeLabel = computed((): string => {
10 | const mode = store.state.app
11 | if (mode == 0) return '直连'
12 | else if (mode == 1) return '智能路由'
13 | else if (mode == 2) return '全局'
14 | else return ''
15 | })
16 |
17 | const handleChangeMode = (e: any) => {
18 | IPC.setMode(e.key)
19 | }
20 |
21 | return (
22 |
23 |
24 | 直连
25 | 智能路由
26 | 全局
27 |
28 | {modeLabel}
29 |
30 | )
31 | },
32 | })
33 |
--------------------------------------------------------------------------------
/packages/sharelist-web/src/components/user-menu/index.less:
--------------------------------------------------------------------------------
1 | .user-menu {
2 | display: flex;
3 | align-items: center;
4 | padding: 24px 12px;
5 |
6 | .avatar {
7 | margin-right: 12px;
8 | background-color:#bb040f;
9 | }
10 |
11 | .userinfo {
12 | h3 {
13 | color: rgba(0, 0, 0, .8);
14 | font-size: 14px;
15 | font-weight: 400;
16 | line-height: 1em;
17 | text-transform: uppercase;
18 | margin-bottom: 5px;
19 | }
20 |
21 | span {
22 | font-size: 11px;
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/packages/sharelist-web/src/components/user-menu/index.tsx:
--------------------------------------------------------------------------------
1 | import { computed, defineComponent } from 'vue'
2 | import { useStore } from 'vuex'
3 | import './index.less'
4 |
5 | export default defineComponent({
6 | setup() {
7 | const store = useStore()
8 | const user = computed(() => store.state.user)
9 |
10 | const username = computed(() => {
11 | if (store.state.user) {
12 | return store.state.user.name.split('@')[0]
13 | } else {
14 | return ''
15 | }
16 | })
17 |
18 | return (
19 |
28 | )
29 | },
30 | })
31 |
--------------------------------------------------------------------------------
/packages/sharelist-web/src/config/api.ts:
--------------------------------------------------------------------------------
1 | export type IAPI = [
2 | name: string,
3 | url: string | ((...args: any[]) => string),
4 | options?: {
5 | [key: string]: number | string | boolean
6 | },
7 | ]
8 |
9 | const api: IAPI[] = [
10 | ['siginin', 'POST /signin'],
11 | ['book', 'POST /search'],
12 | ['list', 'GET /api/drive/path/:path'],
13 | ['setting', 'GET /api/setting', { token: true }],
14 | ['exportSetting', 'GET /api/setting?raw=true', { token: true }],
15 | ['saveSetting', 'POST /api/setting', { token: true }],
16 | ['config', 'GET /api/configs'],
17 | ['clearCache', 'PUT /api/cache/clear'],
18 | ['reload', 'PUT /api/reload'],
19 | //
20 | ['file', 'POST /api/drive/get', { token: true }],
21 | ['files', 'POST /api/drive/list', { token: true }],
22 | // ['parents', 'GET /api/drive/files/:fileId/parents', { token: true }],
23 | ]
24 | export default api
25 |
--------------------------------------------------------------------------------
/packages/sharelist-web/src/config/setting.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | iconFontCN: 'https://at.alicdn.com/t/font_2637962_voky2m76mr.js',
3 | }
4 |
--------------------------------------------------------------------------------
/packages/sharelist-web/src/hooks/useConfirm.ts:
--------------------------------------------------------------------------------
1 | import { Modal } from 'ant-design-vue'
2 | import { createVNode } from 'vue'
3 | import { ExclamationCircleOutlined } from '@ant-design/icons-vue'
4 |
5 | export default (fn: { (): any }, title = '确认', content = '') =>
6 | (): { destroy(): void; update(p: any): void } => {
7 | const modal = Modal.confirm({
8 | title,
9 | content,
10 | icon: createVNode(ExclamationCircleOutlined),
11 | onOk() {
12 | fn()
13 | },
14 | })
15 | return modal
16 | }
17 |
--------------------------------------------------------------------------------
/packages/sharelist-web/src/hooks/useDisk.ts:
--------------------------------------------------------------------------------
1 | import request, { ReqResponse } from '@/utils/request'
2 | import { ref, Ref, watch, reactive, computed } from 'vue'
3 | import { byte, getFileType, time } from '@/utils/format'
4 | import { useRouter, useRoute } from 'vue-router'
5 | import { useLocalStorageState } from '@/hooks/useLocalStorage'
6 | import { message } from 'ant-design-vue'
7 |
8 | export type IFile = {
9 | id: string
10 | name: string
11 | size: number
12 | type: 'folder' | 'file'
13 | ctime: number
14 | mtime: number
15 | path: string
16 | extra?: Record
17 | [key: string]: any
18 | }
19 |
20 | const format = (d: Array, isSearch = false): Array => {
21 | d.forEach((i) => {
22 | i.ext = i.name.split('.').pop()
23 | i.iconType = getFileType(i.name, i.type)
24 | i.ctimeDisplay = time(i.ctime)
25 | i.sizeDisplay = byte(i.size)
26 | if (isSearch) {
27 | i.isSearchResult = isSearch
28 | }
29 | })
30 | return d
31 | }
32 |
33 | const useFolderAuth = () => {
34 | const data = useLocalStorageState('auth', {})
35 | const hasAuth = (path: string) => !!data.value[path]
36 |
37 | const addAuth = (path: string, v: any) => {
38 | data.value[path] = v
39 | }
40 |
41 | const getAuth = (path: string) => {
42 | return data.value[path]
43 | }
44 |
45 | const removeAuth = (path: string) => {
46 | delete data.value[path]
47 | }
48 | return { hasAuth, addAuth, getAuth, removeAuth }
49 | }
50 |
51 | type IUseDisk = {
52 | (): any
53 | [key: string]: any
54 | }
55 |
56 | const useDisk: IUseDisk = (): any => {
57 | if (useDisk.instance) {
58 | return useDisk.instance
59 | }
60 |
61 | const router = useRouter()
62 | const routes = useRoute()
63 |
64 | const files = ref([])
65 | const loading = ref(false)
66 | const error = reactive({ code: 0, message: '' })
67 |
68 | const { hasAuth, addAuth, getAuth, removeAuth } = useFolderAuth()
69 |
70 | const paths = computed(() => {
71 | const ret: Array = (routes.params.path as string).split('/').filter(Boolean)
72 | if (routes.query.search) {
73 | ret.push(`${routes.query.search} 的搜索结果`)
74 | }
75 | return ret
76 | })
77 |
78 | const getDiskContent = (): any => {
79 | loading.value = true
80 | const isSearch = Object.keys(routes.query).length > 0
81 | const params: Record = { path: routes.params.path, ...routes.query }
82 | if (!params.auth && hasAuth(params.path)) params.auth = getAuth(params.path)
83 |
84 | request.files(params).then((resp: ReqResponse) => {
85 | if (resp.error) {
86 | error.code = resp.error.code
87 | error.message = resp.error.message || ''
88 |
89 | if (error.code == 401) {
90 | if (error.message) {
91 | message.error(error.message)
92 | }
93 | removeAuth(params.path)
94 | }
95 | } else {
96 | if (resp.files) {
97 | format(resp.files, isSearch)
98 | files.value = resp.files
99 | }
100 | error.code = 0
101 | error.message = ''
102 |
103 | // save auth
104 | if (params.auth && getAuth(params.path) != params.auth) {
105 | addAuth(params.path, params.auth)
106 | }
107 | }
108 | loading.value = false
109 | })
110 | }
111 |
112 | const setPath = async (data: { id?: string; path?: string; isSearchResult?: boolean }) => {
113 | if (data.isSearchResult) {
114 | const resp = await request.file({ id: data.id })
115 | router.push('/' + resp.path)
116 | } else {
117 | router.push('/' + data.path)
118 | }
119 | }
120 |
121 | watch([() => routes.params, () => routes.query], getDiskContent, { immediate: true })
122 |
123 | useDisk.instance = {
124 | getDiskContent,
125 | setPath,
126 | files,
127 | paths,
128 | loading,
129 | error,
130 | }
131 |
132 | return useDisk.instance
133 | }
134 |
135 | export default useDisk
136 |
--------------------------------------------------------------------------------
/packages/sharelist-web/src/hooks/useHooks.ts:
--------------------------------------------------------------------------------
1 | import { ref, reactive, Ref, UnwrapRef, onMounted, onUnmounted, getCurrentInstance } from 'vue'
2 |
3 | export function safeOnMounted(hook: () => any): void {
4 | const instance = getCurrentInstance()
5 | console.log(instance)
6 | if (instance?.isMounted || (instance as any)?._isMounted) {
7 | hook()
8 | } else {
9 | onMounted(hook)
10 | }
11 | }
12 |
13 | type ToggleValue = number | string | boolean | undefined
14 |
15 | export const useToggle = (defaultValue: ToggleValue = false, reverseValue: ToggleValue = undefined): any => {
16 | if (reverseValue === undefined) {
17 | reverseValue = !Boolean(defaultValue)
18 | }
19 |
20 | const state: Ref = ref(defaultValue)
21 |
22 | const toggle = () => {
23 | state.value = state.value === defaultValue ? reverseValue : defaultValue
24 | }
25 |
26 | const setLeft = () => (state.value = defaultValue)
27 |
28 | const setRight = () => (state.value = reverseValue)
29 |
30 | return { state, toggle, setLeft, setRight }
31 | }
32 |
33 | export const useBoolean = (defaultValue = false): any => {
34 | const state: Ref = ref(defaultValue)
35 |
36 | const toggle = () => {
37 | state.value = state.value === true ? false : true
38 | }
39 |
40 | const setTrue = () => (state.value = true)
41 |
42 | const setFalse = () => (state.value = false)
43 |
44 | return [state, { toggle, setTrue, setFalse }]
45 | }
46 |
47 | export const useObject = (defaultValue: Record): [any, (k?: Array) => void] => {
48 | const state = reactive(defaultValue)
49 |
50 | const clear = (excludes: Array = []): void => {
51 | Object.keys(state as Record)
52 | .filter((i) => !excludes.includes(i))
53 | .forEach((key: string) => Reflect.deleteProperty(state as Record, key))
54 | }
55 |
56 | return [state, clear]
57 | }
58 |
59 | export const useState = >(initialState: T = {} as T): [T, (state: T) => T, () => T] => {
60 | const state = reactive(initialState)
61 | const setState = (val: T, clear = false) => {
62 | Object.keys(val).forEach((key) => {
63 | Reflect.set(state, key, val[key])
64 | })
65 | return state as T
66 | }
67 |
68 | const clearState = () => {
69 | Object.keys(state).forEach((key) => Reflect.deleteProperty(state, key))
70 | return state as T
71 | }
72 |
73 | return [state as T, setState, clearState]
74 | }
75 |
76 | const singleHook = new WeakMap()
77 | // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
78 | export const useSingle = (hook: any, args: Array): any => {
79 | if (singleHook.has(hook)) {
80 | return singleHook.get(hook)
81 | }
82 | const res = hook.apply(hook, args)
83 | singleHook.set(hook, res)
84 | return res
85 | }
86 |
87 | const useBoot = (cb: () => any): any => { }
88 | // const useBoot = (cb: any) => {
89 | // useBoot.ready = true
90 |
91 | // if (useBoot.ready) {
92 | // cb()
93 | // }
94 | // }
95 |
96 | // const useWindowEvent = (event: string) => {
97 | // const handler = new Set()
98 | // const eventMap = new Map()
99 | // window.addEventListener(event, (event) => {
100 | // console.log('location: ' + document.location + ', state: ' + JSON.stringify(event.state))
101 | // })
102 |
103 | // const onMessage = (cb: any) => {
104 | // handler.add(cb)
105 | // }
106 |
107 | // const initListener = () => {
108 | // if (document) {
109 | // document.addEventListener('message', (e) => {
110 | // console.log(e)
111 | // })
112 | // }
113 | // }
114 |
115 | // initListener()
116 |
117 | // return {
118 | // onMessage,
119 | // }
120 | // }
121 |
122 | type useTitleOptions = {
123 | restoreOnUnmount: boolean
124 | }
125 | export const useTitle = (title: string, options?: useTitleOptions): void => {
126 | const lastTitle = ref('')
127 |
128 | const run = () => {
129 | lastTitle.value = document.title
130 | document.title = title
131 | }
132 | safeOnMounted(() => {
133 | run()
134 | })
135 |
136 | if (options?.restoreOnUnmount === true) {
137 | onUnmounted(() => {
138 | document.title = lastTitle.value
139 | })
140 | }
141 |
142 | run()
143 | }
144 |
--------------------------------------------------------------------------------
/packages/sharelist-web/src/hooks/useLoad.ts:
--------------------------------------------------------------------------------
1 | import { ref, Ref } from 'vue'
2 |
3 | export const useLoad = (cb: (() => Promise) | Promise): Ref => {
4 | const loading = ref(false)
5 | Promise.resolve(cb).then(() => {
6 | loading.value = true
7 | })
8 | return loading
9 | }
10 |
--------------------------------------------------------------------------------
/packages/sharelist-web/src/hooks/useLocalStorage.ts:
--------------------------------------------------------------------------------
1 | import { Ref, ref, watch } from 'vue'
2 |
3 | export type LocalStateKey = string
4 |
5 | export function useLocalStorageState(key: LocalStateKey, defaultValue?: T | (() => T)): any {
6 | const raw = localStorage.getItem(key)
7 | if (raw) {
8 | try {
9 | defaultValue = JSON.parse(raw)
10 | } catch {
11 | //
12 | }
13 | }
14 | if (typeof defaultValue === 'function') {
15 | defaultValue = (defaultValue as () => T)()
16 | }
17 | const state = ref(defaultValue) as Ref
18 |
19 | const setState = () => {
20 | localStorage.setItem(key, JSON.stringify(state.value))
21 | }
22 |
23 | watch(
24 | state,
25 | (nv) => {
26 | setState()
27 | },
28 | { deep: true, immediate: false },
29 | )
30 |
31 | return state
32 | }
33 |
--------------------------------------------------------------------------------
/packages/sharelist-web/src/hooks/useSetting.ts:
--------------------------------------------------------------------------------
1 | import { ref, Ref, reactive, unref, readonly, toRaw } from 'vue'
2 | import request, { ReqResponse } from '@/utils/request'
3 | import storage from 'store2'
4 | import { message } from 'ant-design-vue'
5 | import { useBoolean } from './useHooks'
6 | import { saveFile } from '../utils/format'
7 |
8 | type IUseSetting = {
9 | (): any
10 | instance?: any
11 | }
12 | export const useSetting: IUseSetting = (): any => {
13 | if (useSetting.instance) {
14 | return useSetting.instance
15 | }
16 |
17 | const config: ISetting = reactive({})
18 | const [isLoading, { setFalse: hideLoading }] = useBoolean(true)
19 | const loginState = ref(0)
20 |
21 | const getConfig = (val: string) => {
22 | storage.set('ACCESS_TOKEN', val)
23 | request.setting().then((resp: ReqResponse) => {
24 | if (resp.error) {
25 | if (resp.error.code == 401) {
26 | storage.remove('ACCESS_TOKEN')
27 | loginState.value = 2
28 | if (resp.error.message) {
29 | message.error(resp.error.message)
30 | }
31 | }
32 | } else {
33 | loginState.value = 1
34 | updateSetting(resp.data as ISetting)
35 | }
36 |
37 | hideLoading()
38 | })
39 | }
40 |
41 | const setConfig = (data: ISetting) => {
42 | // console.log(data)
43 | request.saveSetting(data).then((resp: ReqResponse) => {
44 | if (resp.error) {
45 | message.error(resp.error.message || 'error')
46 | } else {
47 | updateSetting(resp.data as ISetting)
48 | }
49 | })
50 | }
51 |
52 | const updateSetting = (data: ISetting) => {
53 | for (const i in data) {
54 | config[i] = data[i]
55 | }
56 | }
57 | const clearSetting = () => {
58 | Object.keys(config).forEach((key) => Reflect.deleteProperty(config, key))
59 | }
60 |
61 | const getValue = (code: string) => {
62 | return config[code]
63 | }
64 |
65 | const signout = () => {
66 | storage.remove('ACCESS_TOKEN')
67 | loginState.value = 2
68 | Object.keys(config).forEach((key) => Reflect.deleteProperty(config, key))
69 | }
70 |
71 | const reload = () => {
72 | request.reload().then((resp: any) => {
73 | // hidden()
74 | if (resp.status) {
75 | message.error(resp.msg)
76 | } else {
77 | message.success('操作成功')
78 | }
79 | })
80 | }
81 | // eslint-disable-next-line prettier/prettier
82 | const noop = () => { }
83 |
84 | const clearCache = () => {
85 | // const hidden = message.loading('正在清除缓存', 0)
86 | request.clearCache().then((resp: any) => {
87 | // hidden()
88 | if (resp.status) {
89 | message.error(resp.msg)
90 | } else {
91 | message.success('操作成功')
92 | }
93 | })
94 | }
95 |
96 | const exportConfig = () => {
97 | request.exportSetting().then((resp: any) => {
98 | // hidden()
99 | if (resp.status) {
100 | message.error(resp.msg)
101 | } else {
102 | saveFile(JSON.stringify(resp.data), 'config.json')
103 | }
104 | })
105 | }
106 |
107 | if (!config.token && storage.get('ACCESS_TOKEN')) {
108 | getConfig(storage.get('ACCESS_TOKEN'))
109 | } else {
110 | loginState.value = 2
111 | hideLoading()
112 | }
113 |
114 | return (useSetting.instance = {
115 | signout,
116 | reload,
117 | loginState,
118 | isLoading,
119 | getValue,
120 |
121 | config,
122 | setConfig,
123 | getConfig,
124 | exportConfig,
125 | clearCache,
126 | })
127 | }
128 |
129 | export const useConfig: IUseSetting = (): any => {
130 | const config: Record = reactive({})
131 |
132 | request.config().then((resp: ReqResponse) => {
133 | if (!resp.error) {
134 | for (const i in resp.data) {
135 | config[i] = resp.data[i]
136 | }
137 | }
138 | })
139 |
140 | return { config }
141 | }
142 |
--------------------------------------------------------------------------------
/packages/sharelist-web/src/hooks/useStore.ts:
--------------------------------------------------------------------------------
1 | import { provide, inject, Ref, ref } from 'vue'
2 |
3 | type InjectType = 'root' | 'optional'
4 |
5 | export interface FunctionalStore {
6 | (): T
7 | key?: symbol
8 | root?: T
9 | }
10 |
11 | export const regStore = (store: FunctionalStore): T => {
12 | if (!store.key) {
13 | store.key = Symbol('functional store')
14 | }
15 | const depends = store()
16 | provide(store.key, depends)
17 | return depends
18 | }
19 |
20 | export const useStore = (store: FunctionalStore, type?: InjectType): any => {
21 | const key = store.key
22 | const root = store.root
23 |
24 | switch (type) {
25 | case 'optional':
26 | return inject(key) || store.root || null
27 | case 'root':
28 | if (!store.root) store.root = store()
29 | return store.root
30 | default:
31 | if (inject(key)) {
32 | return inject(key)
33 | }
34 | if (root) return store.root
35 | throw new Error(`状态钩子函数${store.name}未在上层组件通过调用useProvider提供`)
36 | }
37 | }
38 |
39 | export const useAsync = (cb: (() => Promise) | Promise): Ref => {
40 | const val = ref()
41 | Promise.resolve(typeof cb === 'function' ? cb() : cb).then((resp) => {
42 | val.value = resp
43 | })
44 | return val
45 | }
46 |
47 | const jsbridge = {
48 | call: (name: string, param?: any, callback?: (value?: unknown) => void) => name,
49 | getGrayScaleValue: (v: any) => v,
50 | }
51 |
52 | type BridgeValue = {
53 | returnValue: string
54 | }
55 |
56 | type BridgeFunctions = 'getGrayScaleValue'
57 |
58 | export const useBridgeValue = (
59 | fn: BridgeFunctions,
60 | params: P,
61 | ): { value: Ref; ready: Ref } => {
62 | const value: Ref = ref()
63 | const ready = ref(false)
64 | Promise.resolve(jsbridge[fn](params)).then((resp: T) => {
65 | value.value = resp
66 | ready.value = true
67 | })
68 | return { value, ready }
69 | }
70 |
71 | type GrayParams = 'code1' | 'code2'
72 |
73 | type GrapValue = {
74 | returnValue: string
75 | }
76 | const { ready, value } = useBridgeValue('getGrayScaleValue', 'code')
77 |
78 | type ICdpSpaceInfo = {
79 | returnValue: string
80 | }
81 | function getCdpSpaceInfo(spaceCode: string): Promise {
82 | return new Promise((resolve) => {
83 | jsbridge.call('getCdpSpaceInfo', { spaceCode }, (resp: unknown) => {
84 | resolve(resp as ICdpSpaceInfo)
85 | })
86 | })
87 | }
88 |
--------------------------------------------------------------------------------
/packages/sharelist-web/src/hooks/useUrlState.ts:
--------------------------------------------------------------------------------
1 | import { useRouter, useRoute } from 'vue-router'
2 | import { reactive, watch } from 'vue'
3 | import { useState } from './useHooks'
4 | type initial = {
5 | params: Record
6 | query: Record
7 | }
8 |
9 | export default ({ params: initialParams, query: initialQuery }: initial = { params: {}, query: {} }): any => {
10 | const router = useRouter()
11 | const route = useRoute()
12 |
13 | const [params, updateParams] = useState({
14 | ...initialParams,
15 | ...route.params,
16 | })
17 |
18 | const [query, updateQuery] = useState({
19 | ...initialQuery,
20 | ...route.query,
21 | })
22 |
23 | const setQuery = (data: Record) => {
24 | router.push({
25 | query: {
26 | ...route.query,
27 | ...updateQuery(data),
28 | },
29 | })
30 | }
31 |
32 | const setParams = (data: Record) => {
33 | router.push({
34 | ...route.params,
35 | ...updateParams(data),
36 | })
37 | }
38 |
39 | const setPath = (path: string) => {
40 | router.push(path)
41 | }
42 |
43 | watch(() => route.params, updateParams)
44 | watch(() => route.query, updateQuery)
45 |
46 | watch(query, (nv) => {
47 | console.log('>>>', nv)
48 | })
49 | return { params, query, setQuery, setParams, setPath }
50 | }
51 |
--------------------------------------------------------------------------------
/packages/sharelist-web/src/hooks/utils.ts:
--------------------------------------------------------------------------------
1 | import { getCurrentInstance, isRef, onMounted as vueOnMounted, onUnmounted as vueOnUnmounted, Ref } from 'vue'
2 |
3 | export const onMounted = (cb: () => any): void => {
4 | const instance = getCurrentInstance()
5 | if (instance) {
6 | if (instance?.isMounted) {
7 | cb()
8 | } else {
9 | vueOnMounted(cb)
10 | }
11 | }
12 | }
13 |
14 | export function onUnmounted(cb: () => any): void {
15 | if (getCurrentInstance()) {
16 | vueOnUnmounted(cb)
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/packages/sharelist-web/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | ShareList
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/packages/sharelist-web/src/main.ts:
--------------------------------------------------------------------------------
1 | import { createApp } from 'vue'
2 | import App from './App'
3 | import store from './store'
4 | import router from './router'
5 | import { message } from 'ant-design-vue'
6 | // console.log('electron-store', new Store())
7 | import 'ant-design-vue/dist/antd.less'
8 | import '@/assets/style/index.less'
9 |
10 | createApp(App).use(router).use(store).provide('$message', message).mount('#app')
11 |
--------------------------------------------------------------------------------
/packages/sharelist-web/src/router/index.ts:
--------------------------------------------------------------------------------
1 | import { defineAsyncComponent } from 'vue'
2 | import { createRouter, createWebHashHistory, createWebHistory, RouteRecordRaw } from 'vue-router'
3 |
4 | const routes: Array = [
5 | {
6 | path: '/:path(.*)',
7 | name: 'home',
8 | component: defineAsyncComponent(() => import('../views/home')),
9 | },
10 | {
11 | path: '/@manage',
12 | name: 'manage',
13 | component: defineAsyncComponent(() => import('../views/manage')),
14 | },
15 | ]
16 | console.log('here')
17 | const router = createRouter({
18 | history: createWebHistory(),
19 | routes,
20 | })
21 |
22 | export default router
23 |
--------------------------------------------------------------------------------
/packages/sharelist-web/src/store/index.ts:
--------------------------------------------------------------------------------
1 | import { state } from './state'
2 | import { createStore } from 'vuex'
3 | import storage from 'store2'
4 | import request from '@/utils/request'
5 | import { ACCESS_TOKEN, USER_NAME, THEME } from './mutation-types'
6 | import createPersistedState from 'vuex-persistedstate'
7 |
8 | export default createStore({
9 | state,
10 | mutations: {
11 | SET_TOKEN: (state, data) => {
12 | state.accessToken = data
13 | //storage.set('ACCESS_TOKEN', data)
14 | },
15 | SET_THEME: (state, data) => {
16 | state.setting.theme = data
17 | //storage.set('SETTING', state.setting)
18 | },
19 | SET_LAYOUT: (state, data) => {
20 | state.setting.layout = data
21 | //storage.set('SETTING', state.setting)
22 | },
23 | },
24 | actions: {
25 | signin({ commit }, data) {
26 | return new Promise((resolve, reject) => {
27 | request
28 | .signin(data)
29 | .then((response: any) => {
30 | const result = response.result
31 | //storage.set(ACCESS_TOKEN, result.token, 30 * 24 * 60 * 60 * 1000)
32 | //storage.set(USER_NAME, result.user_name, 30 * 24 * 60 * 60 * 1000)
33 | commit('SET_TOKEN', result.token)
34 | commit('SET_NAME', result.user_name)
35 | resolve(response)
36 | })
37 | .catch((error: any) => {
38 | reject(error)
39 | })
40 | })
41 | },
42 | signout({ commit }) {
43 | return new Promise((resolve, reject) => {
44 | commit('SET_TOKEN', '')
45 | //storage.remove(ACCESS_TOKEN)
46 | request
47 | .signout(state.accessToken)
48 | .then(resolve)
49 | .catch(() => {
50 | resolve({})
51 | })
52 | })
53 | },
54 | },
55 | modules: {},
56 | plugins: [createPersistedState()],
57 | })
58 |
--------------------------------------------------------------------------------
/packages/sharelist-web/src/store/mutation-types.ts:
--------------------------------------------------------------------------------
1 | export const ACCESS_TOKEN = 'Access-Token'
2 | export const USER_NAME = 'User-Name'
3 | export const THEME = 'THEME'
--------------------------------------------------------------------------------
/packages/sharelist-web/src/store/state.ts:
--------------------------------------------------------------------------------
1 | type ISetting = {
2 | theme: 'night' | 'dark'
3 | layout: 'list' | 'grid'
4 | }
5 |
6 | export type State = {
7 | accessToken: string
8 | setting: ISetting
9 | }
10 |
11 | export const state: State = {
12 | accessToken: '',
13 | setting: {
14 | theme: 'night',
15 | layout: 'list',
16 | },
17 | }
18 |
--------------------------------------------------------------------------------
/packages/sharelist-web/src/types/IDrive.ts:
--------------------------------------------------------------------------------
1 | type DrivePath = {
2 | protocol: string
3 | [key: string]: string | number
4 | }
5 |
6 | declare type DriverField = {
7 | key: string
8 | label: string
9 | value?: string | number | boolean
10 | options?: Array
11 | type?: 'string' | 'hidden' | 'number' | 'boolean' | 'list'
12 | help?: string
13 | fields?: Array
14 | required?: boolean
15 | }
16 |
17 | declare type IDrive = {
18 | name: string
19 | path: DrivePath
20 | }
21 |
22 | declare type DriverGuide = {
23 | key?: string
24 | label?: string
25 | fields: Array
26 | }
27 |
28 | declare type Driver = {
29 | protocol: string
30 | name?: string
31 | guide?: Array
32 | }
33 |
--------------------------------------------------------------------------------
/packages/sharelist-web/src/types/IRequest.ts:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reruin/sharelist/8761eb527ea85737859e61f3a5bacd767601923d/packages/sharelist-web/src/types/IRequest.ts
--------------------------------------------------------------------------------
/packages/sharelist-web/src/types/shim.d.ts:
--------------------------------------------------------------------------------
1 | declare module '*.vue' {
2 | import { DefineComponent } from 'vue'
3 | const component: DefineComponent<{}, {}, any>
4 | export default component
5 | }
6 |
7 | declare module 'aplayer' {
8 | const aplayer: any
9 | export default aplayer
10 | }
11 |
12 | declare type ISetting = {
13 | title?: string
14 | index_enable?: boolean
15 | default_ignores?: Array
16 | [key: string]: any
17 | }
18 |
--------------------------------------------------------------------------------
/packages/sharelist-web/src/types/source.d.ts:
--------------------------------------------------------------------------------
1 | declare module '*.json'
2 | declare module '*.png'
3 | declare module '*.jpg'
4 |
--------------------------------------------------------------------------------
/packages/sharelist-web/src/utils/format.ts:
--------------------------------------------------------------------------------
1 | export const getFileType = (v: string, type: string): string => {
2 | if (type == 'folder') {
3 | return 'folder'
4 | } else {
5 | if (v) v = v.toLowerCase().split('.').pop() || ''
6 | if (['mp4', 'mpeg', 'wmv', 'webm', 'avi', 'rmvb', 'mov', 'mkv', 'f4v', 'flv'].includes(v)) {
7 | return 'video'
8 | } else if (['mp3', 'm4a', 'wav', 'wma', 'ape', 'flac', 'ogg'].includes(v)) {
9 | return 'audio'
10 | } else if (['doc', 'docx', 'wps'].includes(v)) {
11 | return 'word'
12 | } else if (['ppt', 'pptx'].includes(v)) {
13 | return 'ppt'
14 | } else if (['pdf'].includes(v)) {
15 | return 'pdf'
16 | } else if (['doc', 'docx', 'ppt', 'pptx', 'xls', 'xlsx', 'pdf', 'txt', 'yaml', 'ini', 'cfg'].includes(v)) {
17 | return 'doc'
18 | } else if (['jpg', 'jpeg', 'png', 'gif', 'bmp', 'tiff', 'wmf', 'tif'].includes(v)) {
19 | return 'image'
20 | } else if (['zip', 'rar', '7z', 'tar', 'gz', 'gz2'].includes(v)) {
21 | return 'archive'
22 | } else {
23 | return 'other'
24 | }
25 | }
26 | }
27 |
28 | export const byte = (v: number): string => {
29 | if (v === undefined || v === null || isNaN(v)) {
30 | return '-'
31 | }
32 |
33 | let lo = 0
34 |
35 | while (v >= 1024) {
36 | v /= 1024
37 | lo++
38 | }
39 |
40 | return Math.floor(v * 100) / 100 + ' ' + ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB'][lo]
41 | }
42 |
43 | const fix0 = (v: any) => (v > 9 ? v : '0' + v)
44 |
45 | export const time = (v: number): string => {
46 | if (!v) return '-'
47 | const date = new Date(v)
48 | const thisYear = new Date().getFullYear()
49 | let ret: string =
50 | fix0(date.getMonth() + 1) + '/' + fix0(date.getDay()) + ' ' + fix0(date.getHours()) + ':' + fix0(date.getMinutes())
51 | if (thisYear != date.getFullYear()) {
52 | ret = date.getFullYear() + '/' + ret
53 | }
54 | return ret
55 | }
56 |
57 | export const isAudioSupport = (name: string): boolean => {
58 | const ext: string = name.split('.').pop() || ''
59 | return ['mp3', 'm4a', 'acc', 'wav', 'ogg'].includes(ext)
60 | }
61 |
62 | export const isVideoSupport = (name: string): boolean => {
63 | const ext: string = name.split('.').pop() || ''
64 | return ['mpeg', 'mp4', 'mkv'].includes(ext)
65 | }
66 |
67 | export const isSupportType = (name: string): string | undefined => {
68 | const ext: string = name.split('.').pop() || ''
69 | if (['mp3', 'm4a', 'acc', 'wav', 'ogg'].includes(ext)) {
70 | return 'audio'
71 | } else if (['mpeg', 'mp4', 'mkv'].includes(ext)) {
72 | return 'video'
73 | }
74 | }
75 |
76 | // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
77 | export const isType = (type: string) => (obj: any) => Object.prototype.toString.call(obj) === `[object ${type}]`
78 |
79 | export const isArray = isType('Array')
80 |
81 | export const isObject = isType('Object')
82 |
83 | export const isBlob = isType('Blob')
84 |
85 | export const isString = (v: string): boolean => typeof v == 'string'
86 |
87 | export const getBlob = (data: string, filename: string): Blob | undefined => {
88 | let blob
89 | try {
90 | blob = new Blob([data], { type: 'application/octet-stream' })
91 | } catch (e) {
92 | /**/
93 | }
94 | return blob
95 | }
96 |
97 | export const saveFile = (data: Blob | string, filename: string): void => {
98 | let blob: Blob | undefined
99 | if (isString(data as string)) {
100 | blob = getBlob(data as string, filename)
101 | } else {
102 | blob = data as Blob
103 | }
104 |
105 | if (isBlob(blob)) {
106 | const URL = window.URL || window.webkitURL
107 |
108 | const link = document.createElement('a')
109 |
110 | link.href = URL.createObjectURL(blob)
111 | link.download = filename
112 |
113 | const evt = document.createEvent('MouseEvents')
114 | evt.initEvent('click', false, false)
115 | // link.click()
116 | link.dispatchEvent(evt)
117 | URL.revokeObjectURL(link.href)
118 | }
119 | }
120 |
--------------------------------------------------------------------------------
/packages/sharelist-web/src/utils/ipc.ts:
--------------------------------------------------------------------------------
1 | import { ipcRenderer } from 'electron'
2 |
3 | const setMode = (v: string): void => {
4 | ipcRenderer.send('set_mode', v)
5 | }
6 |
7 | const setServer = (data: string): void => {
8 | ipcRenderer.send('set_server', data)
9 | }
10 |
11 | const getMode = (): void => {
12 | ipcRenderer.sendSync('get_mode')
13 | }
14 |
15 | const on = (evt: string, handler: (...args: any[]) => void): any => ipcRenderer.on(evt, handler)
16 |
17 | const send = (channel: string, ...args: any[]): void => ipcRenderer.send(channel, ...args)
18 |
19 | export default { setMode, setServer, getMode, on, send }
20 |
--------------------------------------------------------------------------------
/packages/sharelist-web/src/utils/promisify.js:
--------------------------------------------------------------------------------
1 | const customArgumentsToken = '__ES6-PROMISIFY--CUSTOM-ARGUMENTS__'
2 |
3 | // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
4 | export function promisify(original) {
5 | if (typeof original !== 'function') {
6 | throw new TypeError('Argument to promisify must be a function')
7 | }
8 |
9 | const argumentNames = original[customArgumentsToken]
10 |
11 | return function (...args) {
12 | return new Promise((resolve, reject) => {
13 | // Append the callback bound to the context
14 | args.push(function callback(err, ...values) {
15 | if (err) {
16 | return reject(err)
17 | }
18 |
19 | if (values.length === 1 || !argumentNames) {
20 | return resolve(values[0])
21 | }
22 |
23 | const o = {}
24 | values.forEach((value, index) => {
25 | const name = argumentNames[index]
26 | if (name) {
27 | o[name] = value
28 | }
29 | })
30 |
31 | resolve(o)
32 | })
33 |
34 | // Call the function.
35 | original.apply(this, args)
36 | })
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/packages/sharelist-web/src/utils/request.ts:
--------------------------------------------------------------------------------
1 | import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios'
2 | import apis, { IAPI } from '../config/api'
3 | import router from '../router'
4 | import storage from 'store2'
5 |
6 | type ReqConfig = {
7 | url: string
8 | method: string
9 | data?: any
10 | params?: any
11 | responseType?: string
12 | token?: boolean
13 | headers?: any
14 | }
15 |
16 | export type ReqResponse = {
17 | error?: { code: number; message?: string }
18 | [key: string]: any
19 | [key: number]: any
20 | }
21 |
22 | const methods: string[] = ['post', 'get', 'put', 'head', 'delete', 'patch']
23 |
24 | const baseURL = ''
25 |
26 | axios.defaults.timeout = 60 * 1000
27 | axios.defaults.baseURL = baseURL
28 |
29 | const service: AxiosInstance = axios.create()
30 |
31 | // http response 拦截器
32 | service.interceptors.response.use(
33 | (response) => {
34 | return response.data
35 | },
36 |
37 | (error) => {
38 | // 由接口返回的错误
39 | if (error.response) {
40 | switch (error.response.status) {
41 | case 401:
42 | router.push('error/401')
43 | case 403:
44 | router.push('error/403')
45 | }
46 | return { status: 500 }
47 | } else {
48 | log(`服务器错误!错误代码:${error}`)
49 | return { status: 500, msg: '' }
50 | }
51 | },
52 | )
53 |
54 | const log = (content: string, type = 'error'): void => {
55 | console.log(content)
56 | }
57 |
58 | const urlReplace = (url: string, params: any): string =>
59 | url.replace(/(?:\:)([\w\$]+)/g, ($0, $1) => {
60 | if ($1 in params) {
61 | return params[$1]
62 | } else {
63 | return $0
64 | }
65 | })
66 |
67 | const convFormData = (data: any) => {
68 | const fd = new FormData()
69 | for (const i in data) {
70 | if (Array.isArray(data[i])) {
71 | const item = []
72 | data[i].forEach((j: any, idx: number) => {
73 | fd.append(`${i}[${idx}]`, j)
74 | })
75 | } else {
76 | fd.append(i, data[i])
77 | }
78 | }
79 | return fd
80 | }
81 |
82 | const createReq = (opts: ReqConfig) => {
83 | if (opts.token !== false) {
84 | if (!opts.headers) {
85 | opts.headers = {}
86 | }
87 | const token: string = storage.get('ACCESS_TOKEN')
88 | if (token) {
89 | opts.headers['Authorization'] = `${token}`
90 | }
91 | }
92 |
93 | if (opts.headers['Content-Type'] == 'multipart/form-data') {
94 | opts.data = convFormData(opts.data)
95 | }
96 | return service.request(opts as AxiosRequestConfig)
97 | }
98 |
99 | const request: any = (name: string) => request[name]
100 | /*
101 | {
102 | call(name:string , ...rest:any){
103 | return request[name](...rest)
104 | }
105 | }
106 | */
107 | methods.forEach((method: string) => {
108 | request[method] = (options: any = {}) =>
109 | createReq({
110 | method,
111 | headers: {
112 | 'Content-Type': options.type == 'json' ? 'application/json' : 'multipart/form-data',
113 | },
114 | ...options,
115 | })
116 | })
117 |
118 | apis.forEach((item: IAPI) => {
119 | request[item[0]] = (...args: any[]) => {
120 | let url = item[1]
121 | if (typeof url === 'function') {
122 | url = url(...args)
123 | }
124 | const t = url.split(/\s/)
125 | const method: string = t[0] || 'GET'
126 | let argsObj: any = {}
127 | if (typeof args[0] == 'object') {
128 | argsObj = { ...args[0] }
129 | }
130 | args.forEach((key, idx) => {
131 | argsObj['$' + (idx + 1)] = key
132 | })
133 | url = t.slice(1).join(' ')
134 | url = urlReplace(url, argsObj)
135 | const options: any = item[2] || {}
136 |
137 | const params: any = {
138 | method,
139 | url,
140 | }
141 |
142 | if (params.method == 'GET') {
143 | if (argsObj.qs) {
144 | params['params'] = argsObj.qs
145 | }
146 | } else {
147 | params['data'] = args[0] || options.data || {}
148 | }
149 | params.headers = params.headers || {}
150 | params.responseType = options.responseType
151 |
152 | if (options.type == 'formdata') {
153 | params.headers['Content-Type'] = 'multipart/form-data'
154 | } else {
155 | params.headers['Content-Type'] = 'application/json'
156 | }
157 | // console.log(params)
158 | if (options.token) {
159 | params.token = true
160 | }
161 | return createReq(params)
162 | }
163 | })
164 |
165 | export default request
166 |
--------------------------------------------------------------------------------
/packages/sharelist-web/src/views/home/index.less:
--------------------------------------------------------------------------------
1 | .drive-body{
2 | padding:12px 28px;
3 | .item{
4 | display: flex;
5 | width:100%;
6 | justify-content: space-between;
7 | align-items: center;
8 | padding:12px;
9 | transition: all 0.3s;
10 | cursor: pointer;
11 | position: relative;
12 | &:hover{
13 | background-color: rgba(132,133,141,0.08);
14 | }
15 |
16 | .item-info{
17 | padding:0 6px;
18 | color:rgba(0,0,0,.5);
19 | flex:0 0 auto;
20 | position:absolute;
21 | right:0;
22 | top:0;
23 | }
24 | .item-name{
25 | flex: 1;
26 | min-width: 0;
27 | width:100%;
28 | overflow: hidden;
29 | text-overflow: ellipsis;
30 | white-space: nowrap;
31 | // display: flex;
32 | position: relative;
33 | }
34 | .item-icon{
35 | margin-right: 16px;
36 | flex:none;
37 | }
38 |
39 | .item-icon__ext{
40 | position: absolute;
41 | font-size: 12px;
42 | left:20px;
43 | bottom: 12px;
44 | color:#fff;
45 | text-transform: uppercase;
46 | transform: scale(0.8);
47 | width:42px;
48 | // text-align: center;
49 | transform-origin: left;
50 | }
51 |
52 | .item-meta{
53 | flex:auto;
54 | display: flex;
55 | justify-content:space-between;
56 | align-items: center;
57 | overflow: hidden;
58 | color:#25262b;
59 | }
60 | .item-ctime{
61 | flex: 0 0 auto;
62 | // padding:0 24px;
63 | // width: 200px;
64 | color:rgba(37, 38, 43, 0.72);
65 | font-size: 12px;
66 | text-align: left;
67 | }
68 |
69 | .item-size{
70 | color:rgba(37, 38, 43, 0.72);
71 | font-size: 12px;
72 | text-align: right;
73 | flex:0 0 80px;
74 | }
75 | }
76 | }
77 |
78 | .drive-body--grid{
79 | display: grid;
80 | grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
81 | grid-gap: 12px;
82 | .item{
83 | .item-meta{
84 | flex-direction: column;
85 | align-items: flex-start;
86 | width:100%;
87 | overflow: hidden;
88 | }
89 | .item-icon__ext--md{
90 | transform: scale(0.55);
91 | }
92 | .item-icon__ext--sm{
93 | display: none;
94 | }
95 | .item-ctime{
96 | // display: none;
97 | }
98 | .item-size{
99 | display: none;
100 | }
101 | }
102 | }
103 |
104 | .drive-header{
105 | padding:12px 40px;
106 | background-color: #e3e6e9;
107 | color: #fff;
108 | font-size: 21px;
109 | overflow: hidden;
110 | display: flex;
111 | justify-content: space-between;
112 | border-bottom: 1px solid rgba(0,0,0,.02);
113 | color:#555;
114 |
115 | .drive-header__name{
116 | cursor: pointer;
117 | }
118 | .drive-action{
119 | color:#555;
120 | }
121 | .drive-action-search{
122 | margin-right:16px;
123 | }
124 |
125 | }
126 |
127 | .drive-routes{
128 | padding:16px 40px 0px;
129 | // background-color: #c1c1c1;
130 | .ant-breadcrumb-link{
131 | // background-color: rgb(66, 133, 244);
132 | // border-radius: 20px;
133 | // color:#fff;
134 | // padding:6px 8px;
135 | // a{
136 | // color: #fff;
137 | // }
138 | }
139 | }
140 |
141 | .drive-routes--hidden{
142 | display: none;
143 | }
144 |
145 | footer{
146 | border-top: 1px solid #ddd;
147 | margin: 35px 15px;
148 | padding: 15px;
149 | text-align: center;
150 | a{
151 | padding:0 12px;
152 | color:#666;
153 | }
154 | }
155 | @media screen and (max-width:480px) {
156 | .drive-header{
157 | padding:12px;
158 | }
159 | .drive-routes{
160 | padding: 12px 12px 0px;
161 | }
162 | .drive-body{
163 | padding:12px 0;
164 | }
165 | }
--------------------------------------------------------------------------------
/packages/sharelist-web/src/views/home/partial/auth/index.less:
--------------------------------------------------------------------------------
1 | .auth-box{
2 | height:70vh;
3 | width:100%;
4 | display:flex;
5 | align-items:center;
6 | justify-content:center;
7 | flex-direction:column;
8 | box-sizing:border-box;
9 | .auth-box__input{
10 | padding:8px 11px;
11 | }
12 | .auth-box__btn{
13 | width:100%;
14 | margin-top:24px;
15 | height:42px;line-height: 42px;;
16 | }
17 | }
18 |
19 |
--------------------------------------------------------------------------------
/packages/sharelist-web/src/views/home/partial/auth/index.tsx:
--------------------------------------------------------------------------------
1 | import { ref, defineComponent, watch, onMounted, toRef, watchEffect, reactive } from 'vue'
2 | import { useStore } from 'vuex'
3 | import { RouterView, useRoute, useRouter } from 'vue-router'
4 | import { Layout, Button, Form, Input } from 'ant-design-vue'
5 | import { useSetting } from '@/hooks/useSetting'
6 | import useUrlState from '@/hooks/useUrlState'
7 |
8 | import './index.less'
9 |
10 | export default defineComponent({
11 | setup() {
12 | const { setQuery } = useUrlState()
13 |
14 | const token = ref('')
15 | const onEnter = () => {
16 | setQuery({ auth: token.value })
17 | }
18 |
19 | return () => (
20 |
21 |
22 | (token.value = e.target.value)}
26 | placeholder="输入密码"
27 | onPressEnter={onEnter}
28 | />
29 |
32 |
33 |
34 | )
35 | },
36 | })
37 |
--------------------------------------------------------------------------------
/packages/sharelist-web/src/views/home/partial/breadcrumb/index.less:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reruin/sharelist/8761eb527ea85737859e61f3a5bacd767601923d/packages/sharelist-web/src/views/home/partial/breadcrumb/index.less
--------------------------------------------------------------------------------
/packages/sharelist-web/src/views/home/partial/breadcrumb/index.tsx:
--------------------------------------------------------------------------------
1 | import { ref, defineComponent, watch, onMounted, computed, watchEffect } from 'vue'
2 | import Icon from '@/components/icon'
3 | import { Breadcrumb } from 'ant-design-vue'
4 | import useUrlState from '@/hooks/useUrlState'
5 | import useDisk from '@/hooks/useDisk'
6 |
7 | const { Item: BreadcrumbItem } = Breadcrumb
8 | import './index.less'
9 | export default defineComponent({
10 | props: {
11 | paths: {
12 | type: Array
13 | }
14 | },
15 | setup(props, ctx) {
16 | const { paths, setPath } = useDisk()
17 |
18 | const onclick = (path: string, idx: number) => {
19 | if (idx < paths.value.length) {
20 | setPath({ path: paths.value.slice(0, idx).join('/') })
21 | }
22 | }
23 |
24 | return () => (
25 |
26 |
27 | onclick('/', 0)}>
28 |
29 |
30 |
31 |
32 | {paths.value.map((i: string, idx: number) => (
33 | onclick(i, idx + 1)}>
34 | {i}
35 |
36 | ))}
37 |
38 |
39 | )
40 | },
41 | })
42 |
--------------------------------------------------------------------------------
/packages/sharelist-web/src/views/home/partial/error/index.less:
--------------------------------------------------------------------------------
1 |
2 | .err{
3 | height:70vh;
4 | width:100%;
5 | display:flex;
6 | align-items:center;
7 | justify-content:center;
8 | flex-direction:column;
9 | box-sizing:border-box;
10 | .err__status{
11 | font-size: 36px;
12 | font-family: inherit;
13 | font-weight: 500;
14 | line-height: 1.1;
15 | color:rgba(0,0,0,.8);
16 | }
17 | .err__msg{
18 | color:rgba(0,0,0,.45);
19 | font-size: 16px;
20 | font-family: 'Source Code Pro','microsoft yahei',Lato,Helvetica,Arial,sans-serif;
21 | }
22 | }
--------------------------------------------------------------------------------
/packages/sharelist-web/src/views/home/partial/error/index.tsx:
--------------------------------------------------------------------------------
1 | import { defineComponent, watch } from 'vue'
2 | import './index.less'
3 | import AuthBox from '../auth'
4 | import { message } from 'ant-design-vue'
5 |
6 | export default defineComponent({
7 | props: {
8 | value: {
9 | type: Object,
10 | required: true,
11 | },
12 | },
13 | setup(props, ctx) {
14 | if (props.value.code == 401 && props.value.message) {
15 | message.error(props.value.message)
16 | }
17 | console.log(props.value)
18 | return () => {
19 | if (props.value.code) {
20 | return props.value.code == 401 ? (
21 |
22 | ) : (
23 |
24 |
{props.value.code}
25 |
{props.value.message}
26 |
27 | )
28 | } else {
29 | return ctx.slots.default?.()
30 | }
31 | }
32 | },
33 | })
34 |
--------------------------------------------------------------------------------
/packages/sharelist-web/src/views/home/partial/header/index.less:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reruin/sharelist/8761eb527ea85737859e61f3a5bacd767601923d/packages/sharelist-web/src/views/home/partial/header/index.less
--------------------------------------------------------------------------------
/packages/sharelist-web/src/views/home/partial/header/index.tsx:
--------------------------------------------------------------------------------
1 | import { ref, defineComponent, watch, onMounted } from 'vue'
2 | import './index.less'
3 | import Icon from '@/components/icon'
4 | import { useStore } from 'vuex'
5 | import { useToggle, useTitle } from '@/hooks/useHooks'
6 | // import Search from '../search'
7 | import { Modal, InputSearch } from 'ant-design-vue'
8 | import { RouterView, useRoute, useRouter } from 'vue-router'
9 | import useUrlState from '@/hooks/useUrlState'
10 | import { useConfig } from '@/hooks/useSetting'
11 |
12 | export default defineComponent({
13 | setup() {
14 | const { state, commit } = useStore()
15 |
16 | const { setQuery, setPath } = useUrlState()
17 |
18 | const { config } = useConfig()
19 |
20 | watch(() => config.title, (nv) => {
21 | useTitle(nv)
22 | })
23 |
24 |
25 | const onChangeLayout = () => {
26 | commit('SET_LAYOUT', state.setting.layout == 'list' ? 'grid' : 'list')
27 | }
28 |
29 | const onToggleSearch = () => {
30 | const onSearch = (value: string) => {
31 | if (value) {
32 | // router.push({ path: router.currentRoute.value.path, query: { search: value } })
33 | setQuery({ search: value })
34 | modal.destroy()
35 | }
36 | }
37 |
38 | const modal = Modal.confirm({
39 | class: 'fix-modal--alone',
40 | width: '560px',
41 | maskClosable: true,
42 | content: (
43 |
44 |
45 |
46 | ),
47 | })
48 | }
49 |
50 | const navHome = () => setPath('/')
51 |
52 | return () => (
53 |
66 | )
67 | },
68 | })
69 |
--------------------------------------------------------------------------------
/packages/sharelist-web/src/views/home/partial/search/index.less:
--------------------------------------------------------------------------------
1 | .search-box{
2 | position: absolute;
3 | }
--------------------------------------------------------------------------------
/packages/sharelist-web/src/views/home/partial/search/index.tsx:
--------------------------------------------------------------------------------
1 | import { ref, defineComponent, watch, onMounted } from 'vue'
2 | import Icon from '@/components/icon'
3 | import { useStore } from 'vuex'
4 | import { Modal, InputSearch } from 'ant-design-vue'
5 | import { SearchOutlined } from '@ant-design/icons-vue'
6 | import './index.less'
7 | export default defineComponent({
8 | props: {
9 | visible: {
10 | type: Boolean,
11 | default: false,
12 | },
13 | },
14 | setup(props, ctx) {
15 | const visible = ref(false)
16 | const key = ref('')
17 |
18 | const setModalVisible = () => {
19 | visible.value = false
20 | }
21 |
22 | const onSearch = () => { }
23 | return () =>
24 | },
25 | })
26 |
--------------------------------------------------------------------------------
/packages/sharelist-web/src/views/manage/index.less:
--------------------------------------------------------------------------------
1 | .setting {
2 | // max-width:960px;
3 | margin: 0 auto;
4 | width: 90%;
5 | margin: 0 auto;
6 | padding: 25px 0;
7 |
8 | .setting-action {
9 | color: rgba(0, 0, 0, .65);
10 | display: flex;
11 | align-items: center;
12 | }
13 |
14 | .settiing-header {
15 | display: flex;
16 | align-items: center;
17 | justify-content: space-between;
18 | margin-bottom: 2em;
19 | color: rgba(0, 0, 0, .65);
20 | font-size: 22px;
21 |
22 | .settiing-header__back {
23 | margin-right: 1em;
24 | }
25 | }
26 |
27 | .setting-drive__header {
28 | text-align: right;
29 | }
30 |
31 | .item {
32 | border-bottom: 1px solid #e8e8e8;
33 | padding: 15px 0;
34 | display: flex;
35 | align-items: center;
36 |
37 | .item__header {
38 | flex: 1 1 auto;
39 | display: flex;
40 | align-items: center;
41 | }
42 |
43 | .item__icon {
44 | margin-right: 8px;
45 | }
46 |
47 | .item__meta {
48 | flex: 1 1 auto;
49 | }
50 |
51 | .item__meta-title {
52 | margin-bottom: 4px;
53 | color: rgba(0, 0, 0, .65);
54 | font-size: 14px;
55 | line-height: 22px;
56 | }
57 |
58 | .item__meta-desc {
59 | color: rgba(0, 0, 0, .45);
60 | font-size: 14px;
61 | line-height: 22px;
62 | }
63 |
64 | .item-action a {
65 | padding: 0 8px;
66 | }
67 | }
68 | }
69 |
70 | .setting-signin {
71 | position: fixed;
72 | top: 45%;
73 | left: 50%;
74 | transform: translate(-50%, -50%);
75 | width: 320px;
76 |
77 | .setting-signin__input {
78 | padding: 8px 11px;
79 | }
80 |
81 | .setting-signin__btn {
82 | width: 100%;
83 | margin-top: 24px;
84 | height: 42px;
85 | }
86 | }
--------------------------------------------------------------------------------
/packages/sharelist-web/src/views/manage/index.tsx:
--------------------------------------------------------------------------------
1 | import { ref, defineComponent, watch, onMounted, toRef, reactive } from 'vue'
2 | import { useStore } from 'vuex'
3 | import { RouterView, useRoute, useRouter } from 'vue-router'
4 | import { Layout, Button, Spin } from 'ant-design-vue'
5 | import request from '@/utils/request'
6 | import Icon from '@/components/icon'
7 | import './index.less'
8 | import { SettingOutlined, DatabaseOutlined, PoweroffOutlined, DeleteOutlined, ReloadOutlined, SaveOutlined } from '@ant-design/icons-vue'
9 | import { Tabs } from 'ant-design-vue'
10 | const { TabPane } = Tabs
11 | import General from './partial/general'
12 | import Drive from './partial/disk'
13 | import Signin from './partial/signin'
14 | import { useSetting, useConfig } from '@/hooks/useSetting'
15 | import useConfirm from '@/hooks/useConfirm'
16 | import { useTitle } from '@/hooks/useHooks'
17 |
18 | export default defineComponent({
19 | setup() {
20 | const { loginState, isLoading, signout, exportConfig, reload, clearCache } = useSetting()
21 |
22 | const confirmClearCache = useConfirm(clearCache, '确认', '确认清除缓存?')
23 |
24 | const confirmSignout = useConfirm(signout, '确认', '确认退出?')
25 |
26 | const confirmReload = useConfirm(reload, '确认', '确认重启?')
27 |
28 | const tabsSlots = {
29 | tabBarExtraContent: () => 导出配置
30 | }
31 | const { config } = useConfig()
32 |
33 | watch(() => config.title, (nv) => {
34 | useTitle(nv)
35 | })
36 |
37 | return () => (
38 |
39 |
50 |
51 | {loginState.value == 1 ? (
52 |
53 |
54 | {{
55 | default: () => ,
56 | tab: () => (
57 |
58 |
59 | 常规
60 |
61 | ),
62 | }}
63 |
64 |
65 | {{
66 | default: () => ,
67 | tab: () => (
68 |
69 |
70 | 挂载盘
71 |
72 | ),
73 | }}
74 |
75 |
76 | ) : loginState.value == 2 ? (
77 |
78 | ) : null}
79 |
80 |
81 | )
82 | },
83 | })
84 |
--------------------------------------------------------------------------------
/packages/sharelist-web/src/views/manage/partial/disk.tsx:
--------------------------------------------------------------------------------
1 | import { ref, defineComponent, watch, onMounted, toRef, toRefs, reactive, watchEffect } from 'vue'
2 | import Icon from '@/components/icon'
3 | import { useSetting } from '@/hooks/useSetting'
4 | import { Layout, Button, Form, Modal, Popconfirm } from 'ant-design-vue'
5 | import { PlusOutlined } from '@ant-design/icons-vue'
6 |
7 | import Modifier from './drive-modifier'
8 |
9 | export default defineComponent({
10 | setup() {
11 | const { config, setConfig } = useSetting()
12 |
13 | const createModifier = (data: IDrive, idx = -1) => {
14 | const updateData = (modifyData: IDrive) => {
15 | const saveData = [...config.drives]
16 | // console.log(saveData, idx)
17 | if (idx == -1) {
18 | saveData.push(modifyData)
19 | } else {
20 | saveData[idx] = modifyData
21 | }
22 | setConfig({ drives: saveData })
23 | modal.destroy()
24 | }
25 |
26 | const modal = Modal.confirm({
27 | class: 'fix-modal--alone',
28 | width: '720px',
29 | closable: true,
30 | content: (
31 |
32 |
33 |
34 | ),
35 | onOk: () => { },
36 | })
37 | }
38 |
39 | const onCreateDrive = () => {
40 | createModifier(
41 | {
42 | name: '',
43 | path: {
44 | protocol: '',
45 | },
46 | },
47 | -1,
48 | )
49 | }
50 |
51 | const remove = (data: IDrive, idx: number) => {
52 | setConfig({ drives: config.drives.filter((_: any, i: number) => i != idx) })
53 | }
54 |
55 | const orderUp = (data: IDrive, idx: number) => {
56 | if (idx > 0) {
57 | const drives = config.drives
58 | const saveData = [...drives]
59 | const a = drives[idx - 1],
60 | b = drives[idx]
61 | saveData[idx - 1] = b
62 | saveData[idx] = a
63 | setConfig({ drives: saveData })
64 | }
65 | }
66 |
67 | return () => (
68 |
69 |
77 |
78 | {config.drives.map((i: IDrive, idx: number) => (
79 |
95 | ))}
96 |
97 |
98 | )
99 | },
100 | })
101 |
--------------------------------------------------------------------------------
/packages/sharelist-web/src/views/manage/partial/general.tsx:
--------------------------------------------------------------------------------
1 | import { ref, defineComponent, watch, onMounted, toRef, toRefs, reactive } from 'vue'
2 | import { useStore } from 'vuex'
3 | import { RouterView, useRoute, useRouter } from 'vue-router'
4 | import { Layout, Button, Form } from 'ant-design-vue'
5 | import request from '@/utils/request'
6 | import Icon from '@/components/icon'
7 | import { SettingOutlined, DatabaseOutlined } from '@ant-design/icons-vue'
8 | import { useSetting } from '@/hooks/useSetting'
9 | import { Switch, Modal, Input } from 'ant-design-vue'
10 | const { TextArea } = Input
11 | const valueDisplay = (value: any, type: string) => {
12 | if (type == 'boolean') return Boolean(value) ? '启用' : '禁用'
13 | else if (type == 'string') return value
14 | else if (type == 'array') {
15 | const len = value.length
16 | const nodes = value.slice(0, 3).map((i: string) => {i}
)
17 | if (len > 3) {
18 | nodes.push(等{len}项
)
19 | }
20 | return nodes
21 | }
22 | }
23 |
24 | export default defineComponent({
25 | setup() {
26 | const fields = [
27 | { code: 'token', label: '后台密码', type: 'string', secret: true },
28 | { code: 'title', label: '网站标题', type: 'string' },
29 | { code: 'index_enable', label: '目录索引', type: 'boolean' },
30 | { code: 'expand_single_disk', label: '展开单一挂载盘', type: 'boolean' },
31 | { code: 'anonymous_download_enable', label: '允许下载', type: 'boolean' },
32 | { code: 'fast_mode', label: '快速模式', type: 'boolean' },
33 | { code: 'ignores', label: '忽略路径', type: 'array' },
34 | { code: 'acl_file', label: '加密文件名', type: 'string' },
35 | { code: 'webdav_path', label: 'WebDAV 路径', type: 'string' },
36 | { code: 'webdav_proxy', label: 'WebDAV 代理', type: 'boolean' },
37 | { code: 'webdav_user', label: 'WebDAV 用户名', type: 'string' },
38 | { code: 'webdav_pass', label: 'WebDAV 密码', type: 'string' },
39 | { code: 'script', label: '自定义脚本', type: 'string' },
40 | { code: 'style', label: '自定义样式', type: 'string' },
41 | ]
42 | const { config, setConfig } = useSetting()
43 |
44 | const createInputModifier = (label: string, code: string, isSecret: boolean | undefined) => {
45 | const modifier = ref(isSecret ? '' : config[code])
46 | const handleChange = (e: any) => modifier.value = e.target.value
47 |
48 | //modal 下的input v-model 有bug
49 | Modal.confirm({
50 | title: label,
51 | class: 'fix-modal--narrow-padding',
52 | content: (
53 |
54 |
55 |
56 | ),
57 | onOk: () => {
58 | setConfig({ [code]: modifier.value })
59 | },
60 | })
61 | }
62 |
63 | const createListModifier = (label: string, code: string) => {
64 | const modifier = ref(config[code].join('\n'))
65 | const handleChange = (e: any) => modifier.value = e.target.value
66 |
67 | Modal.confirm({
68 | title: label,
69 | class: 'fix-modal--narrow-padding',
70 | content: (
71 |
72 |
73 |
74 | ),
75 | onOk: () => {
76 | setConfig({ [code]: modifier.value.split('\n').filter(Boolean) })
77 | },
78 | })
79 | }
80 | return () => (
81 |
82 | {fields.map((i) => (
83 |
100 | ))}
101 |
102 | )
103 | },
104 | })
105 |
--------------------------------------------------------------------------------
/packages/sharelist-web/src/views/manage/partial/signin.tsx:
--------------------------------------------------------------------------------
1 | import { ref, defineComponent, watch, onMounted, toRef, watchEffect, reactive } from 'vue'
2 | import { useStore } from 'vuex'
3 | import { RouterView, useRoute, useRouter } from 'vue-router'
4 | import { Layout, Button, Form, Input } from 'ant-design-vue'
5 | import { useSetting } from '@/hooks/useSetting'
6 |
7 | export default defineComponent({
8 | setup() {
9 | const { getConfig } = useSetting()
10 |
11 | const token = ref('')
12 | const onEnter = () => {
13 | getConfig(token.value)
14 | }
15 |
16 | return () => (
17 |
18 |
19 | (token.value = e.target.value)}
24 | placeholder="输入口令"
25 | onPressEnter={onEnter}
26 | />
27 |
30 |
31 |
32 | )
33 | },
34 | })
35 |
--------------------------------------------------------------------------------
/packages/sharelist-web/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "esnext",
4 | "module": "esnext",
5 | "moduleResolution": "node",
6 | "strict": true,
7 | "jsx": "preserve",
8 | "sourceMap": true,
9 | "resolveJsonModule": true,
10 | "esModuleInterop": true,
11 | "importHelpers": true,
12 | "skipLibCheck": true,
13 | "allowSyntheticDefaultImports": true,
14 | "baseUrl": ".",
15 | "experimentalDecorators": true,
16 | "lib": [
17 | "esnext",
18 | "dom",
19 | "dom.iterable",
20 | "scripthost"
21 | ],
22 | "types": [
23 | // "vite/client"
24 | ],
25 | "paths": {
26 | "@/*": [
27 | "src/*"
28 | ]
29 | },
30 | },
31 | "include": [
32 | "src/**/*.ts",
33 | "src/**/*.d.ts",
34 | "src/**/*.tsx",
35 | "src/**/*.vue",
36 | "tests/**/*.ts",
37 | "tests/**/*.tsx"
38 | ],
39 | "exclude": [
40 | "node_modules"
41 | ]
42 | }
--------------------------------------------------------------------------------
/packages/sharelist-web/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { UserConfig } from 'vite'
2 | import vue from '@vitejs/plugin-vue'
3 | import vueJsx from '@vitejs/plugin-vue-jsx'
4 | import legacy from '@vitejs/plugin-legacy'
5 | import path from 'path'
6 |
7 | const root = path.resolve(__dirname, './src')
8 |
9 | const config: UserConfig = {
10 | root,
11 | resolve: {
12 | alias: [{ find: '@', replacement: root }],
13 | extensions: ['.js', '.ts', '.jsx', '.tsx', '.vue', '.json', '.less', '.css'],
14 | },
15 | build: {
16 | outDir: path.join(__dirname, 'dist'),
17 | sourcemap: false,
18 | emptyOutDir: true,
19 | // assetsDir: './',
20 | },
21 | css: {
22 | preprocessorOptions: {
23 | less: {
24 | javascriptEnabled: true,
25 | modifyVars: {
26 | 'preprocess-custom-color': 'green',
27 | },
28 | },
29 | },
30 | },
31 | server: {
32 | port: +process.env.PORT,
33 | proxy: {
34 | '/api': {
35 | target: 'http://127.0.0.1:33001/',
36 | changeOrigin: true,
37 | },
38 | },
39 | },
40 |
41 | plugins: [
42 | vue(),
43 | vueJsx(),
44 | legacy({
45 | targets: ['defaults', 'not IE 11'],
46 | }),
47 | ],
48 | optimizeDeps: {
49 | exclude: ['electron-is-dev', 'electron-store'],
50 | },
51 | }
52 |
53 | module.exports = config
54 |
--------------------------------------------------------------------------------
/packages/sharelist-webdav/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## [0.1.11](https://github.com/reruin/sharelist/compare/v0.3.13...v0.1.11) (2022-01-05)
2 |
3 |
4 | ### Bug Fixes
5 |
6 | * adapt webdav client ([41b2706](https://github.com/reruin/sharelist/commit/41b27063315c80323002416955cded8364f59526))
7 |
8 |
9 |
10 | ## [0.1.10](https://github.com/reruin/sharelist/compare/v0.3.11...v0.1.10) (2021-12-29)
11 |
12 |
13 | ### Bug Fixes
14 |
15 | * adapt webdav client([#733](https://github.com/reruin/sharelist/issues/733)) ([db2d66d](https://github.com/reruin/sharelist/commit/db2d66deb5ae2da43116a4f33936c088174f25d6))
16 |
17 |
18 |
19 | ## [0.1.9](https://github.com/reruin/sharelist/compare/v0.3.9...v0.1.9) (2021-11-01)
20 |
21 |
22 |
23 | ## [0.1.8](https://github.com/reruin/sharelist/compare/v0.3.7...v0.1.8) (2021-10-19)
24 |
25 |
26 | ### Features
27 |
28 | * **webdav:** support anonymous account ([9573c95](https://github.com/reruin/sharelist/commit/9573c953eda8e2d3c0a64fc2d97e0094b5e9ed8d))
29 |
30 |
31 |
32 | ## [0.1.7](https://github.com/reruin/sharelist/compare/v0.3.5...v0.1.7) (2021-10-12)
33 |
34 |
35 | ### Bug Fixes
36 |
37 | * **webdav:** adapt RaiDrive ([6e0f37e](https://github.com/reruin/sharelist/commit/6e0f37e92ecf673656cb3194b3b97a932b607cc0))
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/packages/sharelist-webdav/README.md:
--------------------------------------------------------------------------------
1 | # @sharelist/webdav [](https://npmjs.com/package/@sharelist/webdav)
2 |
3 | It's a framework for mounting netdisk.
4 |
5 | ## Useage
6 |
--------------------------------------------------------------------------------
/packages/sharelist-webdav/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@sharelist/webdav",
3 | "version": "0.1.11",
4 | "repository": "https://github.com/reruin/sharelist",
5 | "license": "MIT",
6 | "main": "dist/index.js",
7 | "files": [
8 | "dist",
9 | "src"
10 | ],
11 | "scripts": {
12 | "dev": "tsc -w -p .",
13 | "build": "rm -rf dist && tsc -p .",
14 | "changelog": "conventional-changelog -p angular -i CHANGELOG.md -s --commit-path .",
15 | "release": "node ../../scripts/release.js --commit-path ."
16 | },
17 | "devDependencies": {
18 | "@types/koa": "^2.13.4",
19 | "@types/xml2js": "^0.4.9",
20 | "ts-node": "^10.2.1",
21 | "typescript": "^4.3.5"
22 | },
23 | "dependencies": {
24 | "xml2js": "^0.4.23"
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/packages/sharelist-webdav/src/context.ts:
--------------------------------------------------------------------------------
1 | import http from 'http'
2 | import { Context, WebDAVDepth } from './types'
3 | import { parseXML } from './operations/shared'
4 | import { URL } from 'url'
5 |
6 | export default (req: http.IncomingMessage, base: string, allows: Array): Context => {
7 | const authorization = req.headers?.authorization?.split(' ')[1]
8 |
9 | const path = new URL(req.url as string, `http://${req.headers.host}`).pathname
10 | const ctx: Context = {
11 | req: req,
12 | depth: req.headers?.depth as WebDAVDepth,
13 | method: (req.method as string || '').toLowerCase(),
14 | path: path.replace(base, ''),
15 | base,
16 | config: {},
17 | auth: { user: undefined, pass: undefined },
18 | allows,
19 | get(field: string): string | undefined {
20 | const req: http.IncomingMessage = this.req
21 | switch (field = field.toLowerCase()) {
22 | case 'referer':
23 | case 'referrer':
24 | return req.headers.referrer as string || req.headers.referer || ''
25 | default:
26 | return req.headers[field] as string || ''
27 | }
28 | }
29 | }
30 | if (authorization) {
31 | const pairs = Buffer.from(authorization, "base64").toString("utf8").split(':')
32 | ctx.auth = { user: pairs[0], pass: pairs[1] }
33 | }
34 | return ctx
35 | }
--------------------------------------------------------------------------------
/packages/sharelist-webdav/src/index.ts:
--------------------------------------------------------------------------------
1 |
2 | import { WebDAVMethod, WebDAVRequest, Driver, DriverMethod, Context, Response, StatusCodes } from "./types"
3 | import Commands from './operations/commands'
4 | import { default as createContext } from './context'
5 |
6 | interface WebDAVAuth {
7 | (user: string | undefined, pass: string | undefined): boolean,
8 | }
9 | export type WebDAVServerOptions = {
10 | driver?: Driver,
11 | base?: string,
12 | auth: WebDAVAuth,
13 | redirect: boolean
14 | }
15 |
16 | const VirtualDriver: Driver = (actions: DriverMethod, options: any): any => {
17 | console.log(`VirtualDriver ${actions}`, options)
18 | return false
19 | }
20 |
21 | export class WebDAVServer {
22 | public methods: { [methodName: string]: WebDAVMethod }
23 |
24 | protected unknownMethod: WebDAVMethod | undefined
25 |
26 | protected driver: Driver | undefined
27 |
28 | protected base: string
29 |
30 | protected auth: WebDAVAuth
31 |
32 | protected config: Record
33 |
34 | protected allows: Array
35 |
36 | constructor({ driver, base, redirect, auth }: WebDAVServerOptions = { redirect: false, auth: () => true }) {
37 | this.methods = {}
38 | this.driver = driver || VirtualDriver
39 | this.base = base || ''
40 | this.auth = auth
41 | this.config = { redirect }
42 | const commands: { [key: string]: any } = Commands
43 | for (const k in commands)
44 | if (k === 'NotImplemented')
45 | this.unknownMethod = commands[k]
46 | else
47 | this.methods[k.toLowerCase()] = commands[k]
48 | this.allows = Object.keys(this.methods).map(i => i.toUpperCase())//.join(', ')
49 | }
50 |
51 | async request(req: WebDAVRequest, options?: WebDAVServerOptions): Promise {
52 | const ctx: Context = createContext(req, options?.base || this.base, this.allows)
53 | if (
54 | !(ctx.method == 'options' && !ctx.path) &&
55 | !this.auth(ctx.auth.user, ctx.auth.pass)
56 | ) {
57 | return {
58 | headers: {
59 | 'X-WebDAV-Status': '401 ' + StatusCodes[401],
60 | 'WWW-Authenticate': `Basic realm="ShareList WebDAV"`
61 | },
62 | status: '401'
63 | }
64 | }
65 |
66 | ctx.driver = this.driver
67 | ctx.config = this.config
68 | const method = this.methods[ctx.method] || this.unknownMethod
69 |
70 | const res: Response = await method(ctx)
71 | res.headers ||= {}
72 | if (res.status) {
73 | // res.headers['X-WebDAV-Status'] = res.status + ' ' + StatusCodes[res.status]
74 | }
75 | return res
76 | }
77 | }
--------------------------------------------------------------------------------
/packages/sharelist-webdav/src/operations/commands.ts:
--------------------------------------------------------------------------------
1 | import Get from './get'
2 | import Put from './put'
3 | import Post from './post'
4 | import Head from './head'
5 | import Move from './move'
6 | import Lock from './lock'
7 | import Copy from './copy'
8 | import Mkcol from './mkcol'
9 | import Unlock from './unlock'
10 | import Delete from './delete'
11 | import Options from './options'
12 | import Propfind from './propfind'
13 | import Proppatch from './proppatch'
14 | import NotImplemented from './not-implemented'
15 |
16 | export default {
17 | NotImplemented,
18 | Proppatch,
19 | Propfind,
20 | Options,
21 | Delete,
22 | // Unlock,
23 | Mkcol,
24 | // Copy,
25 | // Lock,
26 | Move,
27 | // Head,
28 | // Post,
29 | Put,
30 | Get
31 | }
--------------------------------------------------------------------------------
/packages/sharelist-webdav/src/operations/copy.ts:
--------------------------------------------------------------------------------
1 | import { Context, Response } from '../types'
2 | import { URL } from 'url'
3 |
4 | export default async (ctx: Context): Promise => {
5 | const dst = new URL(ctx.req.headers?.destination as string).pathname.replace(ctx.base, '')
6 | const src = ctx.path
7 | //The source URI and the destination URI are the same.
8 | if (src === dst) {
9 | return { status: '403' }
10 | }
11 |
12 | const res = await ctx.driver?.('mv', src, dst, true)
13 | if (res?.error) {
14 | return {
15 | status: res.error.code || '502'
16 | }
17 | } else {
18 | return {
19 | status: '201'
20 | }
21 | }
22 |
23 | }
--------------------------------------------------------------------------------
/packages/sharelist-webdav/src/operations/delete.ts:
--------------------------------------------------------------------------------
1 | import { Context, Response } from '../types'
2 |
3 | export default async (ctx: Context): Promise => {
4 | const res = await ctx.driver?.('rm', ctx.path)
5 | if (res?.error) {
6 | return {
7 | status: res.error.code || '502'
8 | }
9 | } else {
10 | return {
11 | status: '204'
12 | }
13 | }
14 | }
--------------------------------------------------------------------------------
/packages/sharelist-webdav/src/operations/get.ts:
--------------------------------------------------------------------------------
1 | import { parseXML } from './shared'
2 | import { Context, Response } from '../types'
3 |
4 | const filterHeaders = (headers: Record): Record => {
5 | const effectFields = ['range', 'accept-encoding']
6 | const ret: Record = {}
7 | Object.keys(headers).filter((i: string) => effectFields.includes(i.toLocaleLowerCase())).forEach((key: string) => {
8 | ret[key] = headers[key]
9 | })
10 | return ret
11 | }
12 |
13 | export default async (ctx: Context): Promise => {
14 | const res = await ctx.driver?.('get', ctx.path, { reqHeaders: filterHeaders(ctx.req.headers) })
15 | if (res?.error) {
16 | return {
17 | status: res.error.code || '502'
18 | }
19 | } else {
20 | return res as Response
21 | }
22 | }
--------------------------------------------------------------------------------
/packages/sharelist-webdav/src/operations/head.ts:
--------------------------------------------------------------------------------
1 | export default {
2 |
3 | }
--------------------------------------------------------------------------------
/packages/sharelist-webdav/src/operations/lock.ts:
--------------------------------------------------------------------------------
1 | export default {
2 |
3 | }
--------------------------------------------------------------------------------
/packages/sharelist-webdav/src/operations/mkcol.ts:
--------------------------------------------------------------------------------
1 | import { parseXML } from './shared'
2 | import { Context, Response } from '../types'
3 |
4 | export default async (ctx: Context): Promise => {
5 | const data = await ctx.driver?.('mkdir', ctx.path)
6 | if (data?.error) {
7 | if (data.error.code == 401) {
8 | return {
9 | headers: {
10 | 'WWW-Authenticate': `Basic realm="ShareList WebDAV"`
11 | },
12 | status: '401'
13 | }
14 | } else {
15 | return {
16 | status: '404'
17 | }
18 | }
19 | } else {
20 | return {
21 | status: '201'
22 | }
23 | }
24 | }
--------------------------------------------------------------------------------
/packages/sharelist-webdav/src/operations/move.ts:
--------------------------------------------------------------------------------
1 | import { Context, Response } from '../types'
2 | import { URL } from 'url'
3 |
4 | export default async (ctx: Context): Promise => {
5 | console.log(ctx.req.headers)
6 | const dst = new URL(ctx.req.headers?.destination as string).pathname.replace(ctx.base, '')
7 | const src = ctx.path
8 | //The source URI and the destination URI are the same.
9 | if (src === dst) {
10 | return { status: '403' }
11 | }
12 |
13 | const res = await ctx.driver?.('mv', src, dst)
14 | if (res?.error) {
15 | return {
16 | status: res.error.code || '502'
17 | }
18 | } else {
19 | return {
20 | status: '201'
21 | }
22 | }
23 |
24 | }
--------------------------------------------------------------------------------
/packages/sharelist-webdav/src/operations/not-implemented.ts:
--------------------------------------------------------------------------------
1 | import { Context, Response } from '../types'
2 | import commands from './commands'
3 | export default async (ctx: Context): Promise => {
4 | return {
5 | status: '405 Method not allowed',
6 | headers: {
7 | 'Allow': Object.keys(commands).join(', ')
8 | }
9 | }
10 | }
--------------------------------------------------------------------------------
/packages/sharelist-webdav/src/operations/options.ts:
--------------------------------------------------------------------------------
1 | import { parseXML } from './shared'
2 | import { Context, Response } from '../types'
3 |
4 | export default async (ctx: Context): Promise => {
5 | const dav = [1]
6 |
7 | if (ctx.allows?.includes('LOCK')) {
8 | dav.push(2)
9 | }
10 |
11 | return {
12 | headers: {
13 | // For Microsoft clients
14 | 'MS-Author-Via': 'DAV',
15 | 'DAV': dav.join(', '),
16 | 'Allow': ctx.allows?.join(', ') || ''
17 | },
18 | status: '200'
19 | }
20 | }
--------------------------------------------------------------------------------
/packages/sharelist-webdav/src/operations/post.ts:
--------------------------------------------------------------------------------
1 | export default {
2 |
3 | }
--------------------------------------------------------------------------------
/packages/sharelist-webdav/src/operations/proppatch.ts:
--------------------------------------------------------------------------------
1 | import { parseXML } from './shared'
2 | import { Context, Response } from '../types'
3 |
4 | export default async (ctx: Context): Promise => {
5 | return {
6 | status: '200'
7 | }
8 | }
--------------------------------------------------------------------------------
/packages/sharelist-webdav/src/operations/put.ts:
--------------------------------------------------------------------------------
1 | import { parseXML } from './shared'
2 | import { Context, Response } from '../types'
3 |
4 | export default async (ctx: Context): Promise => {
5 | console.log('>>', ctx.req.isPaused(), ctx.req.headers)
6 | ctx.req.headers.connection = 'keep-alive'
7 | const size = parseInt(ctx.req.headers['content-length'] || '0')
8 | const res = await ctx.driver?.('upload', ctx.path, ctx.req, { size })
9 |
10 | if (res?.error) {
11 | return {
12 | status: res.error.code || '502'
13 | }
14 | } else {
15 | return {
16 | status: '201'
17 | }
18 | }
19 | }
--------------------------------------------------------------------------------
/packages/sharelist-webdav/src/operations/shared.ts:
--------------------------------------------------------------------------------
1 | import * as http from 'http'
2 | import { Readable } from 'stream'
3 | import { parseStringPromise, processors } from 'xml2js'
4 |
5 | const saveStream = (stream: Readable, charset: BufferEncoding | undefined = 'utf8'): Promise => {
6 | return new Promise((resolve, reject) => {
7 | const data: Array = []
8 | stream
9 | .on('data', chunk => {
10 | data.push(chunk)
11 | })
12 | .on('error', reject)
13 | .on('end', () => resolve(Buffer.concat(data).toString(charset)))
14 | })
15 | }
16 |
17 |
18 | export const parseXML = async (req: http.IncomingMessage): Promise => {
19 | const txt = await saveStream(req)
20 | return await parseStringPromise(txt, {
21 | // explicitChildren: true,
22 | explicitArray: false,
23 | tagNameProcessors: [processors.stripPrefix]
24 | })
25 | }
--------------------------------------------------------------------------------
/packages/sharelist-webdav/src/operations/unlock.ts:
--------------------------------------------------------------------------------
1 | export default {
2 |
3 | }
--------------------------------------------------------------------------------
/packages/sharelist-webdav/src/types.ts:
--------------------------------------------------------------------------------
1 | import http from 'http'
2 |
3 | export type DriverMethod = 'stat' | 'get' | 'ls' | 'rm' | 'mkdir' | 'upload' | 'mv'
4 |
5 | export type WebDAVDepth = "0" | "1" | "1,noroot" | "infinity"
6 |
7 | export type WebDAVRequest = http.IncomingMessage
8 |
9 | export type DriverMethodResponse = {
10 | status?: string,
11 | data?: any,
12 | error?: { code?: string | number, message: string }
13 | }
14 |
15 | export type Driver = {
16 | // [key in DriverMethod]: (options: any) => any
17 | (actions: DriverMethod, ...options: Array): DriverMethodResponse
18 | }
19 |
20 | export type WebDAVAuthRecord = {
21 | user: string | undefined,
22 | pass: string | undefined
23 | }
24 | export interface Context {
25 | req: http.IncomingMessage,
26 | depth: WebDAVDepth,
27 | method: string,
28 | path: string | undefined,
29 | base: string,
30 | get(field: string): any,
31 | driver?: Driver,
32 | allows?: Array,
33 | config: Record,
34 | auth: WebDAVAuthRecord
35 | }
36 |
37 |
38 | export interface WebDAVMethod {
39 | (ctx: Context): any
40 | }
41 |
42 | export type Response = {
43 | status: string | number,
44 | headers?: Record,
45 | body?: any
46 | }
47 |
48 | export const StatusCodes: Record = {
49 | 200: 'OK',
50 | 201: 'Created',
51 | 204: 'No Content',
52 | 207: 'Multi Status',
53 | 302: 'Moved Temporarily',
54 | 401: 'Unauthorized',
55 | 403: 'Forbidden',
56 | 404: 'Not Found',
57 | 409: 'Conflict',
58 | 423: 'Locked'
59 | }
--------------------------------------------------------------------------------
/packages/sharelist-webdav/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es2015",
4 | "module": "commonjs",
5 | "moduleResolution": "node",
6 | "strict": true,
7 | // "sourceMap": true,
8 | "resolveJsonModule": true,
9 | "esModuleInterop": true,
10 | "importHelpers": false,
11 | "skipLibCheck": true,
12 | "allowSyntheticDefaultImports": true,
13 | "baseUrl": "./src",
14 | "experimentalDecorators": true,
15 | "lib": [
16 | "esnext",
17 | "scripthost"
18 | ],
19 | "rootDir": "./src",
20 | "outDir": "./dist",
21 | "watch": false
22 | },
23 | "include": [
24 | "src/**/*.ts",
25 | "src/**/*.d.ts",
26 | "src/**/*.tsx",
27 | "src/**/*.vue",
28 | "tests/**/*.ts",
29 | "tests/**/*.tsx"
30 | ],
31 | "exclude": [
32 | "node_modules"
33 | ]
34 | }
--------------------------------------------------------------------------------
/packages/sharelist/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## [0.3.15](https://github.com/reruin/sharelist/compare/v0.3.14...v0.3.15) (2022-01-05)
2 |
3 |
4 |
5 | ## [0.3.14](https://github.com/reruin/sharelist/compare/v0.3.13...v0.3.14) (2022-01-05)
6 |
7 |
8 |
9 | ## [0.3.13](https://github.com/reruin/sharelist/compare/v0.3.12...v0.3.13) (2022-01-04)
10 |
11 |
12 | ### Bug Fixes
13 |
14 | * fix some bugs ([23cd7f9](https://github.com/reruin/sharelist/commit/23cd7f99d5af27b08cba08109c2107a3a6d04089))
15 |
16 |
17 |
18 | ## [0.3.12](https://github.com/reruin/sharelist/compare/v0.3.11...v0.3.12) (2021-12-29)
19 |
20 |
21 | ### Bug Fixes
22 |
23 | * **web:** fix bugs([#723](https://github.com/reruin/sharelist/issues/723),[#747](https://github.com/reruin/sharelist/issues/747)) ([10d2550](https://github.com/reruin/sharelist/commit/10d25502248811a2d313d442f40592c66a5cd443))
24 |
25 |
26 | ### Features
27 |
28 | * support custom script/css ([8af3902](https://github.com/reruin/sharelist/commit/8af390226a63373477d597a6f6b231e1c34f6cfa))
29 |
30 |
31 |
32 | ## [0.3.11](https://github.com/reruin/sharelist/compare/v0.3.10...v0.3.11) (2021-11-05)
33 |
34 |
35 |
36 | ## [0.3.10](https://github.com/reruin/sharelist/compare/v0.3.9...v0.3.10) (2021-11-01)
37 |
38 |
39 |
40 | ## [0.3.9](https://github.com/reruin/sharelist/compare/v0.3.8...v0.3.9) (2021-10-23)
41 |
42 |
43 |
44 | ## [0.3.8](https://github.com/reruin/sharelist/compare/v0.3.7...v0.3.8) (2021-10-19)
45 |
46 |
47 |
48 | ## [0.3.7](https://github.com/reruin/sharelist/compare/v0.3.6...v0.3.7) (2021-10-14)
49 |
50 |
51 | ### Features
52 |
53 | * **webdav:** support option configuration of webdav ([d667a83](https://github.com/reruin/sharelist/commit/d667a830f8008a857d6ae827213d76992edbe306))
54 |
55 |
56 |
57 | ## [0.3.6](https://github.com/reruin/sharelist/compare/v0.3.5...v0.3.6) (2021-10-12)
58 |
59 |
60 |
61 | ## [0.3.6](https://github.com/reruin/sharelist/compare/v0.3.5...v0.3.6) (2021-10-12)
62 |
63 |
64 |
65 | ## [0.3.5](https://github.com/reruin/sharelist/compare/v0.3.4...v0.3.5) (2021-10-11)
66 |
67 |
68 |
69 | ## [0.3.4](https://github.com/reruin/sharelist/compare/v0.3.3...v0.3.4) (2021-10-10)
70 |
71 |
72 |
73 | ## [0.3.3](https://github.com/reruin/sharelist/compare/v0.3.2...v0.3.3) (2021-10-10)
74 |
75 |
76 |
77 | ## [0.3.2](https://github.com/reruin/sharelist/compare/v0.3.1...v0.3.2) (2021-10-09)
78 |
79 |
80 | ### Features
81 |
82 | * **webdav:** auth ([2499979](https://github.com/reruin/sharelist/commit/2499979dcd8392864f505268411dbce15cd810dc))
83 |
84 |
85 |
86 | ## [0.3.1](https://github.com/reruin/sharelist/compare/v0.3.0...v0.3.1) (2021-10-09)
87 |
88 |
89 |
90 | # [0.3.0](https://github.com/reruin/sharelist/compare/v0.2.4...v0.3.0) (2021-10-09)
91 |
92 |
93 |
94 | ## [0.2.3](https://github.com/reruin/sharelist/compare/v0.2.2...v0.2.3) (2021-08-21)
95 |
96 |
97 | ### Bug Fixes
98 |
99 | * **guide:** fix wrong function call ([01e5a78](https://github.com/reruin/sharelist/commit/01e5a78f54b59ddcb8ac04b2d1c1297710f5946d))
100 |
101 |
102 |
103 | ## [0.2.2](https://github.com/reruin/sharelist/compare/v0.2.1...v0.2.2) (2021-08-21)
104 |
105 |
106 |
107 | ## 0.2.1 (2021-08-14)
108 |
109 |
110 |
111 |
--------------------------------------------------------------------------------
/packages/sharelist/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:14-alpine
2 | LABEL maintainer=reruin
3 |
4 | ADD . /sharelist/
5 | WORKDIR /sharelist
6 | VOLUME /sharelist/cache
7 | VOLUME /sharelist/theme
8 | VOLUME /sharelist/plugin
9 |
10 | RUN npm install --production
11 |
12 | ENV HOST 0.0.0.0
13 | ENV PORT 33001
14 |
15 | EXPOSE 33001
16 |
17 | CMD ["npm", "start"]
--------------------------------------------------------------------------------
/packages/sharelist/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018-present, Reruin and Sharelist contributors
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/packages/sharelist/README.md:
--------------------------------------------------------------------------------
1 | # ShareList Server
--------------------------------------------------------------------------------
/packages/sharelist/app.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | /**
4 | * Module dependencies.
5 | */
6 |
7 | const app = require('./app/index')
8 | const http = require('http')
9 | const os = require('os')
10 | const fs = require('fs')
11 |
12 | const onError = (error) => {
13 | if (error.syscall !== 'listen') {
14 | throw error
15 | }
16 |
17 | var bind = typeof port === 'string' ? 'Pipe ' + port : 'Port ' + port
18 |
19 | // handle specific listen errors with friendly messages
20 | switch (error.code) {
21 | case 'EACCES':
22 | console.error(bind + ' requires elevated privileges')
23 | process.exit(1)
24 | break
25 | case 'EADDRINUSE':
26 | console.error(bind + ' is already in use')
27 | process.exit(1)
28 | break
29 | default:
30 | throw error
31 | }
32 | }
33 |
34 | const getIpv4 = () => {
35 | var ifaces = os.networkInterfaces()
36 | for (var dev in ifaces) {
37 | for (var i in ifaces[dev]) {
38 | var details = ifaces[dev][i]
39 | if (/^\d+\./.test(details.address)) {
40 | return details.address
41 | }
42 | }
43 | }
44 | }
45 |
46 | const onListening = () => {
47 | console.log(new Date().toISOString())
48 | console.log('Sharelist Server is running at http://' + getIpv4() + ':' + port + '/')
49 | }
50 |
51 | if (!fs.existsSync('./cache')) {
52 | fs.mkdirSync('./cache')
53 | }
54 |
55 | const port = process.env.PORT || 33001
56 |
57 | const server = http.createServer(app.callback())
58 | server.on('error', onError).on('listening', onListening)
59 | server.listen(port)
60 |
--------------------------------------------------------------------------------
/packages/sharelist/app/config.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 |
3 | const isPkg = process.pkg
4 |
5 | const isDev = process.env.NODE_ENV === 'dev'
6 |
7 | if (isPkg) {
8 | require('@koa/cors')
9 | require('koa-body')
10 | require('koa-json')
11 | }
12 |
13 | const pluginPath = isDev
14 | ? path.join(process.cwd(), '../sharelist-plugin/lib')
15 | : isPkg
16 | ? path.join(process.cwd(), './plugins')
17 | : null
18 |
19 | module.exports = (appInfo) => ({
20 | middleware: ['cors', 'koaBody', 'json'],
21 |
22 | plugin: ['sharelist', 'webdav', 'guide'],
23 |
24 | sharelist: {
25 | path: path.join(appInfo.baseDir, './package/sharelist/index.js'),
26 | client: {
27 | pluginDir: [path.join(appInfo.baseDir, './plugins'), pluginPath],
28 | cacheDir: path.join(isPkg ? process.cwd() : appInfo.baseDir, './cache'),
29 | },
30 | },
31 |
32 | webdav: {
33 | path: path.join(appInfo.baseDir, './package/webdav/index.js'),
34 | client: {
35 |
36 | },
37 | },
38 |
39 | guide: {
40 | path: path.join(appInfo.baseDir, './package/guide/index.js'),
41 | client: {},
42 | },
43 | theme: {
44 | options: {
45 | dir: path.join(appInfo.baseDir, './theme'),
46 | },
47 | },
48 | cors: {
49 | package: '@koa/cors',
50 | },
51 | koaBody: {
52 | package: 'koa-body',
53 | },
54 | json: {
55 | package: 'koa-json',
56 | },
57 | })
58 |
--------------------------------------------------------------------------------
/packages/sharelist/app/core/app.js:
--------------------------------------------------------------------------------
1 | const { loadDir, loadFile, loadUnit } = require('./loader')
2 | const Koa = require('koa')
3 | const path = require('path')
4 | const Router = require('koa-router')
5 | const utils = require('./utils')
6 | const createRequest = require('./request')
7 |
8 | class Controller {
9 | constructor(app, mix) {
10 | this.app = app
11 | this.config = app.config
12 |
13 | for (let i in mix) {
14 | this[i] = mix[i].bind(this)
15 | }
16 | }
17 | }
18 |
19 | class App extends Koa {
20 | constructor() {
21 | super()
22 |
23 | this.appInfo = {
24 | baseDir: path.join(__dirname, '../../'),
25 | env: process.pkg ? 'pkg' : process.env.NODE_ENV === 'dev' ? 'dev' : 'prod'
26 | }
27 |
28 | this.utils = utils
29 |
30 | this.loader = {
31 | loadToApp: this.loadToApp,
32 | loadToContext: this.loadToContext,
33 | }
34 |
35 | this.curl = createRequest(this)
36 |
37 | this.lifecycles = {
38 | beforeConfigLoad: [],
39 | configLoaded: [],
40 | loaded: [],
41 | ready: [],
42 | }
43 |
44 | this.init()
45 | }
46 |
47 | async init() {
48 | let units = await loadUnit({ path: path.join(this.appInfo.baseDir, 'app') }, null, this.appInfo)
49 |
50 | this.config = units.config
51 |
52 | this.router = Router()
53 |
54 | await this.hookLifeCycle('beforeConfigLoad')
55 |
56 | await this.loadPlugin(units)
57 |
58 | await this.hookLifeCycle('configLoaded')
59 |
60 | await this.loadService(units)
61 |
62 | await this.loadMiddleware(units)
63 |
64 | await this.loadController()
65 |
66 | await this.hookLifeCycle('loaded')
67 |
68 | await this.loadRouter()
69 |
70 | this.use(this.router.routes()).use(this.router.allowedMethods())
71 |
72 | await this.hookLifeCycle('ready')
73 | }
74 |
75 | async loadPlugin({ config, plugin }) {
76 | let enabled = config.plugin
77 | for (let name of enabled) {
78 | let creator = plugin[name]
79 | if (creator) {
80 | if (utils.isClass(creator)) {
81 | this.setLifeCycle(new creator(this))
82 | } else if (utils.isFunction) {
83 | creator(this, this.config[name])
84 | }
85 | }
86 | }
87 | }
88 |
89 | setLifeCycle(instance, scope) {
90 | for (let type in this.lifecycles) {
91 | if (instance[type]) {
92 | this.lifecycles[type].push(instance[type].bind(scope || instance))
93 | }
94 | }
95 | }
96 |
97 | async loadMiddleware({ config, middleware }) {
98 | // let appMiddleware = await loadDir(this, 'middleware')
99 | // let coreMiddleware = utils.each(this.coreMiddleware, (i) => loadFile(i.package))
100 | // let middlewares = { ...appMiddleware, ...coreMiddleware }
101 | let enabled = config.middleware
102 | for (let name of enabled) {
103 | if (middleware[name]) {
104 | let options = config[name]
105 | this.use(middleware[name](options, this))
106 | } else {
107 | console.log('miss middleware', name)
108 | }
109 | }
110 |
111 | for (let name in middleware) {
112 | this.middleware[name] = middleware[name]
113 | }
114 | // console.log(this.middleware)
115 | }
116 |
117 | async loadController() {
118 | this.controller = utils.each(
119 | await loadDir(path.join(this.appInfo.baseDir, 'app', 'controller')),
120 | (i) => new Controller(this, i),
121 | )
122 | }
123 |
124 | async loadService({ service }) {
125 | this.service = service
126 | }
127 |
128 | async loadRouter() {
129 | loadFile(path.join(this.appInfo.baseDir, 'app', 'router.js'))(this)
130 | }
131 |
132 | async loadTo(directory) {
133 | return await loadDir(directory)
134 | }
135 |
136 | async loadToApp(directory, name, options) {
137 | this[name] = loadDir(directory, options)
138 | }
139 |
140 | async loadToContext() { }
141 |
142 | async hookLifeCycle(type) {
143 | let handlers = this.lifecycles[type]
144 | for (let handler of handlers) {
145 | await handler()
146 | }
147 | //await Promise.all(handlers.map(handler => handler()))
148 | }
149 |
150 | addSingleton(name, factory) {
151 | this.lifecycles.configLoaded.push(async () => {
152 | this[name] = await factory(this.config[name], this)
153 | })
154 | }
155 | }
156 |
157 | exports.App = App
158 |
--------------------------------------------------------------------------------
/packages/sharelist/app/core/loader.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 |
3 | const fs = require('fs')
4 |
5 | const { stat, readdir } = require('fs/promises')
6 |
7 | const { isClass, isFunction } = require('./utils')
8 |
9 | const loadFile = (filepath) => {
10 | if (!filepath) return
11 |
12 | //is file
13 | let isfile = /[\.\:]/i.test(filepath)
14 | if (isfile && !fs.existsSync(filepath)) {
15 | return
16 | }
17 | let ret
18 | try {
19 | ret = require(filepath)
20 | } catch (e) {
21 | console.log(e)
22 | }
23 | return ret
24 | }
25 |
26 | const getDirPath = (dir, basepath) => {
27 | if (dir.path) {
28 | return dir.path
29 | }
30 |
31 | const name = dir.package || dir.name
32 | const lookupDirs = []
33 |
34 | //app
35 | lookupDirs.push(path.join(basepath, 'node_modules'))
36 |
37 | lookupDirs.push(path.join(process.cwd(), 'node_modules'))
38 | for (let dir of lookupDirs) {
39 | dir = path.join(dir, name)
40 | if (fs.existsSync(dir)) {
41 | return fs.realpathSync(dir)
42 | }
43 | }
44 |
45 | throw new Error(`Can not find plugin ${name} in "${lookupDirs.join(', ')}"`)
46 | }
47 |
48 | const loadDir = async (dir) => {
49 | if (Array.isArray(dir)) {
50 | let ret = {}
51 | for (let i of dir) {
52 | if (i) {
53 | ret = Object.assign(ret, await loadDir(i))
54 | }
55 | }
56 | return ret
57 | } else {
58 | let ret = {}
59 | if (fs.existsSync(dir)) {
60 | let files = await readdir(dir)
61 | for (let i of files) {
62 | let filepath = path.join(dir, i)
63 | let file = fs.statSync(filepath)
64 |
65 | if (file.isFile() && i.endsWith('.js')) {
66 | let instance = loadFile(filepath)
67 | let name = path.basename(i, '.js')
68 | ret[name] = instance
69 | } else if (file.isDirectory()) {
70 | //let name = path.basename(i)
71 | //ret[name] = await loadDir(app,path.join(mod,i))
72 | }
73 | }
74 | }
75 | return ret
76 | }
77 | }
78 |
79 | const loadUnit = async (dir, basepath, appInfo) => {
80 | if (dir.path) basepath = dir.path
81 |
82 | let dirpath = getDirPath(dir, basepath)
83 |
84 | let middleware = await loadDir(path.join(dirpath, 'middleware'))
85 |
86 | let service = await loadDir(path.join(dirpath, 'service'))
87 |
88 | // let plugins = loadFile(path.join(dirpath, 'plugin.js'))
89 |
90 | let plugin = {}, config = {}
91 |
92 | // load config
93 | let app = loadFile(path.join(dirpath, 'config.js'))
94 |
95 | if (isFunction(app)) app = app(appInfo)
96 |
97 | if (app) {
98 | config.middleware = app.middleware || []
99 | config.plugin = app.plugin || []
100 |
101 | if (app.middleware) {
102 | for (let name of app.middleware) {
103 | if (app[name]) {
104 | if (app[name].path || app[name].package) {
105 | middleware[name] = loadFile(app[name].path || app[name].package)
106 | }
107 | if (app[name].options) {
108 | config[name] = app[name].options
109 | }
110 | }
111 | }
112 | }
113 |
114 | if (app.plugin) {
115 | for (let name of app.plugin) {
116 | if (app[name].client) {
117 | config[name] = app[name].client
118 | }
119 |
120 | let unit = await loadUnit(app[name], dirpath, appInfo)
121 |
122 | Object.assign(middleware, unit.middleware)
123 | Object.assign(config, unit.config)
124 | Object.assign(plugin, unit.plugin)
125 |
126 | plugin[name] = loadFile(app[name].path ? path.join(app[name].path) : app[name].package)
127 | service[name] = unit.service
128 |
129 | if (unit.config) {
130 | if (unit.config.middleware) config.middleware.unshift(...unit.config.middleware)
131 | if (unit.config.plugin) config.plugin.unshift(...unit.config.plugin)
132 | }
133 | }
134 | }
135 | }
136 | return { middleware, config, plugin, service }
137 | }
138 |
139 | exports.loadDir = loadDir
140 | exports.loadFile = loadFile
141 | exports.loadUnit = loadUnit
142 |
--------------------------------------------------------------------------------
/packages/sharelist/app/core/request.js:
--------------------------------------------------------------------------------
1 | const fetch = require('node-fetch')
2 |
3 | const btoa = v => Buffer.from(v).toString('base64')
4 |
5 | const querystring = require('querystring')
6 |
7 | // {
8 | // // These properties are part of the Fetch Standard
9 | // method: 'GET',
10 | // headers: {}, // request headers. format is the identical to that accepted by the Headers constructor (see below)
11 | // body: null, // request body. can be null, a string, a Buffer, a Blob, or a Node.js Readable stream
12 | // redirect: 'follow', // set to `manual` to extract redirect headers, `error` to reject redirect
13 | // signal: null, // pass an instance of AbortSignal to optionally abort requests
14 |
15 | // // The following properties are node-fetch extensions
16 | // follow: 20, // maximum redirect count. 0 to not follow redirect
17 | // timeout: 0, // req/res timeout in ms, it resets on redirect. 0 to disable (OS limit applies). Signal is recommended instead.
18 | // compress: true, // support gzip/deflate content encoding. false to disable
19 | // size: 0, // maximum response body size in bytes. 0 to disable
20 | // agent: null // http(s).Agent instance or function that returns an instance (see below)
21 | // }
22 |
23 | const each = (src, fn) => {
24 | let ret = {}
25 | for (let i in src) {
26 | ret[i] = fn(src[i], i)
27 | }
28 | return ret
29 | }
30 |
31 | module.exports = (app) => {
32 | const request = async (url, options = {}) => {
33 | let { data, method = 'GET', contentType, responseType = 'json', followRedirect = true, maxRedirects = 10, auth, headers = {}, agent, compress = false, timeout = 3000, retry = 2 } = options
34 |
35 | let args = { method, size: 0, agent, compress, timeout, headers }
36 |
37 | if (auth) {
38 | args.headers['authorization'] = `Basic ${btoa(auth)}`
39 | }
40 |
41 | if (followRedirect) {
42 | args.redirect = 'follow'
43 | if (maxRedirects) args.follow = maxRedirects
44 | } else {
45 | args.redirect = 'manual'
46 | }
47 |
48 | if (!args.headers['content-type'] && method != 'GET') {
49 | if (contentType === 'json') {
50 | args.headers['content-type'] = 'application/json'
51 | } else {
52 | args.headers['content-type'] = 'application/x-www-form-urlencoded'
53 | }
54 | }
55 |
56 | if (data) {
57 | if (['GET', 'HEAD'].includes(method)) {
58 | url += (url.includes('?') ? '' : '?') + querystring.stringify(data)
59 | } else if (['POST', 'PUT', 'DELETE'].includes(method)) {
60 | if (args.headers['content-type'].includes('application/json')) {
61 | args.body = JSON.stringify(data)
62 | } else {
63 | args.body = querystring.stringify(data)
64 | }
65 | }
66 | }
67 |
68 | //url = 'https://api.reruin.net/proxy?url=' + encodeURIComponent(url)
69 |
70 | while (true) {
71 | try {
72 | // console.log(url, args)
73 | let res = await fetch(url, args)
74 | let status = res.status
75 | let headers = each(res.headers.raw(), val => val.join(','))
76 |
77 | if (responseType == 'json' || responseType == 'text' || responseType == 'buffer') {
78 | let data = await res[responseType]()
79 | return {
80 | status,
81 | headers,
82 | data,
83 | body: data
84 | }
85 | } else {
86 | return { status, headers, data: res.body }
87 | }
88 |
89 | } catch (e) {
90 | if (retry-- <= 0) {
91 | return { error: { message: '[' + e.code + '] The error occurred during the request.' } }
92 | }
93 | console.log('request retry', retry, e)
94 | }
95 | }
96 |
97 | // return fetch(url, args).then(res => {
98 | // let status = res.status
99 | // let headers = each(res.headers.raw(), val => val.join(','))
100 |
101 | // if (responseType == 'json' || responseType == 'text' || responseType == 'buffer') {
102 | // return res[responseType]().then(data => ({ status, headers, data, body: data }))
103 | // } else {
104 | // return { status, headers, data: res.body }
105 | // }
106 | // })
107 | }
108 |
109 | request.post = (url, options) => request(url, { ...options, method: 'POST' })
110 |
111 | request.get = (url, options) => request(url, { ...options, method: 'GET' })
112 |
113 | return request
114 | }
--------------------------------------------------------------------------------
/packages/sharelist/app/core/utils.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 |
3 | const isType = (type) => (obj) => (Object.prototype.toString.call(obj) === `[object ${type}]`)
4 |
5 | exports.isArray = isType('Array')
6 |
7 | exports.isObject = isType('Object')
8 |
9 | exports.isString = val => typeof val === 'string'
10 |
11 | exports.isDate = isType('Date')
12 |
13 | exports.each = (obj, callback) => {
14 | let ret = {}
15 | for (let i in obj) {
16 | ret[i] = callback(obj[i], i)
17 | }
18 | return ret
19 | }
20 |
21 | exports.isClass = fn => typeof fn == 'function' && /^\s*class/.test(fn.toString())
22 |
23 | exports.base64 = {
24 | encode: (v) => Buffer.from(v).toString('base64'),
25 | decode: (v) => Buffer.from(v, 'base64').toString()
26 | }
27 |
28 | exports.atob = v => Buffer.from(v, 'base64').toString()
29 | exports.btoa = v => Buffer.from(v).toString('base64')
30 |
31 | exports.isFunction = (fn) => typeof fn == 'function'
32 |
33 | exports.pathNormalize = (p, basepath = '') => path.posix.normalize(p);
34 |
35 | exports.isRelativePath = (v) => !/^http/.test(v)
36 |
--------------------------------------------------------------------------------
/packages/sharelist/app/index.js:
--------------------------------------------------------------------------------
1 | const { App } = require('./core/app')
2 |
3 | module.exports = new App()
--------------------------------------------------------------------------------
/packages/sharelist/app/middleware/auth.js:
--------------------------------------------------------------------------------
1 | const CTX_USER = Symbol('ctx#user');
2 | const { nanoid } = require('nanoid')
3 |
4 | module.exports = (options, app) => async (ctx, next) => {
5 |
6 | // const user = ctx[CTX_USER]
7 | let token = ctx.get('authorization') || ctx.query.token
8 | let isAdmin = app.sharelist.config.token && app.sharelist.config.token === token
9 |
10 | if (isAdmin) {
11 | await next()
12 | } else {
13 | ctx.body = { error: { code: 401, message: 'Invalid password' } }
14 | }
15 | }
--------------------------------------------------------------------------------
/packages/sharelist/app/router.js:
--------------------------------------------------------------------------------
1 | module.exports = app => {
2 | const { router, controller, middleware } = app;
3 | const auth = middleware.auth({}, app)
4 |
5 | router
6 | .get('/api/setting', auth, controller.api.setting)
7 | .get('/api/configs', controller.api.config)
8 | .post('/api/setting', auth, controller.api.updateSetting)
9 | .put('/api/cache/clear', auth, controller.api.clearCache)
10 | .put('/api/reload', auth, controller.api.reload)
11 |
12 | .post('/api/drive/list', controller.api.list)
13 | .post('/api/drive/get', controller.api.get)
14 | .get('/api/drive/get', controller.api.get)
15 |
16 | .get('/api/config/:field', controller.api.configField)
17 | // .get('/api/drive/download', controller.api.download)
18 |
19 | .get('/api/drive/path', controller.api.list)
20 | .get('/api/drive/path/:path(.*)', controller.api.list)
21 | .get('/api/drive/:path\\:file', controller.api.get)
22 |
23 | .get('/:path(.*)', controller.api.page)
24 |
25 | }
26 |
--------------------------------------------------------------------------------
/packages/sharelist/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: "3"
2 | services:
3 | sharelist:
4 | image: reruin/sharelist
5 | volumes:
6 | - $HOME/sharelist:/sharelist/cache
7 | ports:
8 | - "33001:33001"
--------------------------------------------------------------------------------
/packages/sharelist/example/123.txt:
--------------------------------------------------------------------------------
1 | Mitochondrial DNA became an area of research in phylogenetics in the late 1970s. Unlike genomic DNA, it offered advantages in that it did not undergo recombination. The process of recombination, if frequent enough, corrupts the ability to create parsimonious trees because of stretches of amino acid subsititions (SNPs).[clarification needed] When looking between distantly related species, recombination is less of a problem since recombination between branches from common ancestors is prevented after true speciation occurs. When examining closely related species, or branching within species, recombination creates a large number of 'irrelevant SNPs' for cladistic analysis. MtDNA, through the process of organelle division, became clonal over time; very little, or often none, of that paternal mtDNA is passed. While recombination may occur in mtDNA, there is little risk that it will be passed to the next generation. As a result, mtDNA become clonal copies of each other, except when a new mutation arises. As a result, mtDNA does not have pitfalls of autosomal loci when studied in interbreeding groups. Another advantage of mtDNA is that the hyper-variable regions evolve very quickly; this shows that certain regions of mitochondrial DNA approach neutrality. This allowed the use of mitochondrial DNA to determine that the relative age of the human population was small, having gone through a recent constriction at about 150,000 years ago (see #Causes of errors).
2 |
3 | Mitochondrial DNA has also been used to verify the proximity of chimpanzees to humans relative to gorillas, and to verify the relationship of these three species relative to the orangutan.
4 |
5 |
6 | A population bottleneck, as illustrated was detected by intrahuman mtDNA phylogenetic studies; the length of the bottleneck itself is indeterminate per mtDNA.
7 | More recently,[when?] the mtDNA genome has been used to estimate branching patterns in peoples around the world, such as when the new world was settled and how. The problem with these studies have been that they rely heavily on mutations in the coding region. Researchers have increasingly discovered that as humans moved from Africa's south-eastern regions, that more mutations accumulated in the coding region than expected, and in passage to the new world some groups are believed[citation needed] to have passed from the Asian tropics to Siberia to an ancient land region called Beringia and quickly migrated to South America. Many of the mtDNA have far more mutations and at rarely mutated coding sites relative to expectations of neutral mutations.
8 |
9 | Mitochondrial DNA offers another advantage over autosomal DNA. There are generally 2 to 4 copies of each chromosome in each cell (1 to 2 from each parent chromosome). For mtDNA there can be dozens to hundreds in each cell. This increases the amount of each mtDNA loci by at least a magnitude. For ancient DNA, in which the DNA is highly degraded, the number of copies of DNA is helpful in extending and bridging short fragments together, and decreases the amount of bone extracted from highly valuable fossil/ancient remains. Unlike Y chromosome, both male and female remains carry mtDNA in roughly equal quantities.
--------------------------------------------------------------------------------
/packages/sharelist/example/fileSystem_current.d.ln:
--------------------------------------------------------------------------------
1 | fs:./
--------------------------------------------------------------------------------
/packages/sharelist/example/filesystem_linux_root.d.ln:
--------------------------------------------------------------------------------
1 | fs:/
--------------------------------------------------------------------------------
/packages/sharelist/example/filesystem_windows_disk_c.d.ln:
--------------------------------------------------------------------------------
1 | fs:/c
--------------------------------------------------------------------------------
/packages/sharelist/example/github.com.url:
--------------------------------------------------------------------------------
1 | [InternetShortcut]
2 | URL=https://github.com/reruin/sharelist
3 |
--------------------------------------------------------------------------------
/packages/sharelist/example/google_drive.d.ln:
--------------------------------------------------------------------------------
1 | gd:1cEA4umECe_-7aqBvq44AiPYxQ95zP8jr
--------------------------------------------------------------------------------
/packages/sharelist/example/google_drive2.d.ln:
--------------------------------------------------------------------------------
1 | gd:0BwfTxffUGy_GNF9KQ25Xd0hMVXM
--------------------------------------------------------------------------------
/packages/sharelist/example/h5ai.d.ln:
--------------------------------------------------------------------------------
1 | h5ai:https://larsjung.de/h5ai/demo/
--------------------------------------------------------------------------------
/packages/sharelist/example/joy.d.ln:
--------------------------------------------------------------------------------
1 | joy://joy/
--------------------------------------------------------------------------------
/packages/sharelist/example/lanzou.d.ln:
--------------------------------------------------------------------------------
1 | lanzou:b480558
--------------------------------------------------------------------------------
/packages/sharelist/example/linkTo_download_ubuntu_18.iso.ln:
--------------------------------------------------------------------------------
1 | http://releases.ubuntu.com/18.04.1/ubuntu-18.04.1-desktop-amd64.iso
--------------------------------------------------------------------------------
/packages/sharelist/example/link_webdav.d.ln:
--------------------------------------------------------------------------------
1 | webdav:https://sharelist.reruin.net/webdav
--------------------------------------------------------------------------------
/packages/sharelist/example/magnet.txt:
--------------------------------------------------------------------------------
1 | magnet:?xt=urn:btih:08ada5a7a6183aae1e09d831df6748d566095a10&dn=Sintel&tr=udp%3A%2F%2Fexplodie.org%3A6969&tr=udp%3A%2F%2Ftracker.coppersurfer.tk%3A6969&tr=udp%3A%2F%2Ftracker.empire-js.us%3A1337&tr=udp%3A%2F%2Ftracker.leechers-paradise.org%3A6969&tr=udp%3A%2F%2Ftracker.opentrackr.org%3A1337&tr=wss%3A%2F%2Ftracker.btorrent.xyz&tr=wss%3A%2F%2Ftracker.fastcast.nz&tr=wss%3A%2F%2Ftracker.openwebtorrent.com&ws=https%3A%2F%2Fwebtorrent.io%2Ftorrents%2F&xs=https%3A%2F%2Fwebtorrent.io%2Ftorrents%2Fsintel.torrent
--------------------------------------------------------------------------------
/packages/sharelist/example/one_drive.d.ln:
--------------------------------------------------------------------------------
1 | od:s!Apo33BTbGqqHhx3q6Gtb62WI6p59
--------------------------------------------------------------------------------
/packages/sharelist/example/one_drive_url.d.ln:
--------------------------------------------------------------------------------
1 | odu:https://makedie-my.sharepoint.com/:f:/g/personal/mengskysama_makedie_onmicrosoft_com/EgpidLT7P8pGs8gh0eCPcNwBrIU3SjZri47asodOJx970A?e=0rbNSp
--------------------------------------------------------------------------------
/packages/sharelist/example/preview/123.torrent:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reruin/sharelist/8761eb527ea85737859e61f3a5bacd767601923d/packages/sharelist/example/preview/123.torrent
--------------------------------------------------------------------------------
/packages/sharelist/example/preview/123.txt:
--------------------------------------------------------------------------------
1 | 123
--------------------------------------------------------------------------------
/packages/sharelist/example/preview/audio.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reruin/sharelist/8761eb527ea85737859e61f3a5bacd767601923d/packages/sharelist/example/preview/audio.mp3
--------------------------------------------------------------------------------
/packages/sharelist/example/preview/image.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reruin/sharelist/8761eb527ea85737859e61f3a5bacd767601923d/packages/sharelist/example/preview/image.jpg
--------------------------------------------------------------------------------
/packages/sharelist/example/preview/movie.a.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reruin/sharelist/8761eb527ea85737859e61f3a5bacd767601923d/packages/sharelist/example/preview/movie.a.mp4
--------------------------------------------------------------------------------
/packages/sharelist/example/preview/movie.a.vtt:
--------------------------------------------------------------------------------
1 | WEBVTT
2 |
3 | 00:00:00.001 --> 00:00:03.000
4 | 鸟叫声
5 |
--------------------------------------------------------------------------------
/packages/sharelist/example/preview/pdf.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reruin/sharelist/8761eb527ea85737859e61f3a5bacd767601923d/packages/sharelist/example/preview/pdf.pdf
--------------------------------------------------------------------------------
/packages/sharelist/example/preview/sintel.torrent:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reruin/sharelist/8761eb527ea85737859e61f3a5bacd767601923d/packages/sharelist/example/preview/sintel.torrent
--------------------------------------------------------------------------------
/packages/sharelist/example/preview/work.docx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reruin/sharelist/8761eb527ea85737859e61f3a5bacd767601923d/packages/sharelist/example/preview/work.docx
--------------------------------------------------------------------------------
/packages/sharelist/example/secret_folder/.passwd:
--------------------------------------------------------------------------------
1 | type: basic
2 | data:
3 | - 1234561
4 | - abcdef
--------------------------------------------------------------------------------
/packages/sharelist/example/secret_folder/Secret2/.passwd:
--------------------------------------------------------------------------------
1 | type: basic
2 | data:
3 | - 1:1
--------------------------------------------------------------------------------
/packages/sharelist/example/secret_folder/hello.txt:
--------------------------------------------------------------------------------
1 | 兽头大门,门前列坐着十来个华冠丽服之人。正门却不开,只有东西两角门有人出入。正门之上有一匾,匾上大书“敕造宁国府”五个大字。黛玉想道:这必是外祖之长房了。想着,又往西行,不多远,照样也是三间大门,方是荣国府了。却不进正门,只进了西边角门。那轿夫抬进去,走了一射之地,将转弯时,便歇下退出去了。
--------------------------------------------------------------------------------
/packages/sharelist/example/sharelist github repo.d.ln:
--------------------------------------------------------------------------------
1 | github:reruin/sharelist
--------------------------------------------------------------------------------
/packages/sharelist/example/sharelist_drive.sld.ln:
--------------------------------------------------------------------------------
1 | -
2 | name: os
3 | children:
4 | -
5 | name: linux
6 | children:
7 | -
8 | name: ubuntu
9 | children:
10 | -
11 | name: ubuntu-18.04.1-desktop-amd64.iso
12 | url: http://releases.ubuntu.com/18.04.1/ubuntu-18.04.1-desktop-amd64.iso
13 | -
14 | name: ubuntu-18.04.1-live-server-amd64.iso
15 | url: http://releases.ubuntu.com/18.04.1/ubuntu-18.04.1-live-server-amd64.iso
16 | -
17 | name: ubuntu-16.04.5-server-amd64.iso
18 | url: http://releases.ubuntu.com/16.04.5/ubuntu-16.04.5-server-amd64.iso
19 | -
20 | name: ubuntu-16.04.5-desktop-amd64.iso
21 | url: http://releases.ubuntu.com/16.04.5/ubuntu-16.04.5-desktop-amd64.iso
22 | -
23 | name: debian
24 | children:
25 | -
26 | name: debian-9.5.0-amd64-DVD-1.iso
27 | url: https://cdimage.debian.org/debian-cd/current/amd64/iso-dvd/debian-9.5.0-amd64-DVD-1.iso
28 | -
29 | name: debian-8.11.0-amd64-DVD-1.iso
30 | url: https://cdimage.debian.org/cdimage/archive/8.11.0/amd64/iso-dvd/debian-8.11.0-amd64-DVD-1.iso
31 | -
32 | name: mssql
33 | children:
34 | -
35 | name: SQL SERVER 2008R2 EXPRESS.exe
36 | url: https://download.microsoft.com/download/9/4/8/948966AB-52CA-40F1-8051-0216481065E6/SQLEXPRWT_x64_CHS.exe
37 | -
38 | name: SQL SERVER 2012 EXPRESS.exe
39 | url: https://download.microsoft.com/download/A/4/3/A43F9D8D-5346-441A-ABAE-86C3AFE17B4D/SQLEXPRWT_x64_CHS.exe
40 | -
41 | name: SQL SERVER 2017 EXPRESS.exe
42 | url: https://download.microsoft.com/download/5/E/9/5E9B18CC-8FD5-467E-B5BF-BADE39C51F73/SQLServer2017-SSEI-Expr.exe
43 | -
44 | name: 嵌套GoodleDrive虚拟目录.d.ln
45 | content: gd:0B0vQvfdCBUFjdnRpVnZsWmlFanM
46 | -
47 | name: 嵌套windows本地D盘.d.ln
48 | content: fs:/d/a.b
49 | -
50 | name: 嵌套linux 根目录.d.ln
51 | content: fs:/
--------------------------------------------------------------------------------
/packages/sharelist/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "sharelist",
3 | "version": "0.3.15",
4 | "bin": "app.js",
5 | "repository": "https://github.com/reruin/sharelist",
6 | "license": "MIT",
7 | "scripts": {
8 | "start": "node app.js",
9 | "dev": "cross-env NODE_ENV=dev nodemon app.js -i ./cache",
10 | "test": "echo \"Error: no test specified\" && exit 1",
11 | "changelog": "conventional-changelog -p angular -i CHANGELOG.md -s --commit-path .",
12 | "pkg": "pkg . --output build/sharelist --targets linux-x64,linux-arm64,linuxstatic-armv7,macos-x64,win-x64 --public-packages '*'",
13 | "pkg-local": "pkg . --output build/sharelist",
14 | "release": "node ../../scripts/release.js --skipBuild --skipNpmPublish"
15 | },
16 | "dependencies": {
17 | "@koa/cors": "^3.1.0",
18 | "@sharelist/core": "^0.1",
19 | "@sharelist/webdav": "^0.1",
20 | "bonjour": "^3.5.0",
21 | "etag": "^1.8.1",
22 | "global": "^4.4.0",
23 | "ignore": "^5.1.8",
24 | "koa": "^2.13.1",
25 | "koa-body": "^4.2.0",
26 | "koa-json": "^2.0.2",
27 | "koa-logger": "^3.2.1",
28 | "koa-onerror": "^4.1.0",
29 | "koa-router": "^10.0.0",
30 | "koa-sendfile": "^3.0.0",
31 | "koa-session-minimal": "^4.0.0",
32 | "koa-static-cache": "^5.1.4",
33 | "markdown-it": "^12.0.6",
34 | "mime": "^2.5.2",
35 | "nanoid": "^3.1.23",
36 | "node-fetch": "^2.6.1",
37 | "node-rsa": "^1.1.1",
38 | "webdav": "^4.6.0",
39 | "write-file-atomic": "^3.0.3",
40 | "xml2js": "^0.4.23",
41 | "yaml": "^1.10.2"
42 | },
43 | "pkg": {
44 | "scripts": [
45 | "./app/**/*",
46 | "./plugins/*",
47 | "./package/**/*"
48 | ],
49 | "assets": [
50 | "./theme/**/*"
51 | ]
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/packages/sharelist/package/guide/driver/aliyundrive.js:
--------------------------------------------------------------------------------
1 | const { getOAuthAccessToken, PROXY_URL, render } = require('./shared')
2 |
3 | module.exports = async function (ctx, next) {
4 | render(ctx, `
5 |
挂载 Aliyun Drive
6 |
参考此链接
7 |
`)
8 | }
--------------------------------------------------------------------------------
/packages/sharelist/package/guide/driver/baidu.js:
--------------------------------------------------------------------------------
1 | const { getOAuthAccessToken, PROXY_URL, render } = require('./shared')
2 | const querystring = require('querystring')
3 |
4 | module.exports = async function (ctx, next) {
5 | if (ctx.request.body && ctx.request.body.act && ctx.request.body.act == 'install') {
6 | let { client_id, client_secret } = ctx.request.body
7 |
8 | if (client_id && client_secret) {
9 | let baseUrl = ctx.origin + '/@guide/baidu/' + this.app.utils.btoa([client_id, client_secret].join('::')) + '/callback'
10 |
11 | const opts = {
12 | client_id: client_id,
13 | scope: 'basic,netdisk',
14 | response_type: 'code',
15 | redirect_uri: PROXY_URL,
16 | state: baseUrl
17 | };
18 |
19 | ctx.redirect(`https://openapi.baidu.com/oauth/2.0/authorize?${querystring.stringify(opts)}`)
20 | }
21 |
22 | } else if (ctx.params.pairs) {
23 | let [client_id, client_secret] = this.app.utils.atob(ctx.params.pairs).split('::')
24 | console.log(client_id, client_secret)
25 | if (ctx.query.code) {
26 | let credentials = await getOAuthAccessToken(this.app, 'https://openapi.baidu.com/oauth/2.0/token', { client_id, client_secret, code: ctx.query.code, redirect_uri: PROXY_URL })
27 | if (credentials.error) {
28 | ctx.body = credentials.error
29 | } else {
30 | let ret = { AppKey: client_id, SecretKey: client_secret, redirect_uri: PROXY_URL, access_token: credentials.access_token, refresh_token: credentials.refresh_token }
31 |
32 | ctx.body = Object.keys(ret).map(i => `${i}:${ret[i]}
`).join('
')
33 | }
34 | }
35 | else if (ctx.query.error) {
36 | ctx.body = req.query.error
37 | }
38 | } else {
39 | render(ctx, `
40 |
68 | `)
69 | }
70 | }
--------------------------------------------------------------------------------
/packages/sharelist/package/guide/driver/shared.js:
--------------------------------------------------------------------------------
1 | exports.PROXY_URL = 'https://reruin.github.io/sharelist/redirect.html'
2 |
3 | exports.getOAuthAccessToken = async (app, url, { client_id, client_secret, redirect_uri, code }) => {
4 | let data = {
5 | client_id,
6 | client_secret,
7 | redirect_uri,
8 | code,
9 | grant_type: 'authorization_code'
10 | }
11 |
12 | let resp
13 | try {
14 | resp = await app.curl.post(url, { data })
15 | } catch (e) {
16 | resp = { error: e.toString() }
17 | }
18 |
19 | if (resp.error) return resp
20 |
21 | if (resp.body.error) {
22 | return { error: resp.body.error_description || resp.body.error }
23 | }
24 |
25 | return resp.data
26 | }
27 |
28 | exports.render = (ctx, cnt) => {
29 | return ctx.body = `ShareList
30 |
31 |
32 |
75 | ${cnt}`
76 | }
--------------------------------------------------------------------------------
/packages/sharelist/package/guide/index.js:
--------------------------------------------------------------------------------
1 | const baidu = require('./driver/baidu')
2 | const onedrive = require('./driver/onedrive')
3 | const googledrive = require('./driver/googledrive')
4 | const aliyundrive = require('./driver/aliyundrive')
5 |
6 | module.exports = (app) => {
7 | const vendor = { app, onedrive, googledrive, baidu, aliyundrive }
8 | app.router
9 | .all('/@guide/:type', async (ctx, next) => {
10 | await vendor[ctx.params.type]?.(ctx, next)
11 | })
12 | .get('/@guide/:type/:pairs(.*)/callback', async (ctx, next) => {
13 | await vendor[ctx.params.type]?.(ctx, next)
14 | })
15 |
16 | app.addSingleton('guide', async (options) => {
17 | let guide = {}
18 | for (let i in vendor) {
19 | guide[i] = `/@guide/${i}`
20 | }
21 | return guide
22 | })
23 | }
24 |
--------------------------------------------------------------------------------
/packages/sharelist/package/sharelist/cache.js:
--------------------------------------------------------------------------------
1 | const createDB = require('./db')
2 | module.exports = (path) => {
3 | let data = createDB(path, { raw: true, shallow: true })
4 |
5 | const get = (id) => {
6 | let ret = data[id]
7 | if (ret) {
8 | if (Date.now() > ret.expired_at) {
9 | delete data[id]
10 | } else {
11 | return ret.data
12 | }
13 | }
14 | }
15 |
16 | const set = (id, value, max_age) => {
17 | data[id] = { data: value, expired_at: Date.now() + max_age }
18 | return value
19 | }
20 |
21 | const clear = (key) => {
22 | if (key) {
23 | delete data[key]
24 | } else {
25 | for (let key in data) {
26 | delete data[key]
27 | }
28 | }
29 | }
30 |
31 | const remove = (key) => {
32 | console.log('remove', key)
33 | delete data[key]
34 | }
35 |
36 | // remove expired data
37 | for (let key in data) {
38 | get(key, data)
39 | }
40 |
41 | return {
42 | get, set, remove, clear
43 | }
44 | }
--------------------------------------------------------------------------------
/packages/sharelist/package/sharelist/config.js:
--------------------------------------------------------------------------------
1 | const createDB = require('./db')
2 |
3 | const defaultConfig = {
4 | token: 'sharelist',
5 |
6 | proxy_enable: false,
7 |
8 | index_enable: true,
9 |
10 | expand_single_disk: true,
11 |
12 | // fast_mode: true,
13 |
14 | max_age_dir: 15 * 60 * 1000,
15 |
16 | max_age_file: 5 * 60 * 1000,
17 |
18 | // max_age_download: 0,
19 |
20 | theme: 'default',
21 |
22 | ignores: [],
23 |
24 | acl_file: '.passwd',
25 |
26 | max_age_download_sign: 'sl_' + Date.now(),
27 |
28 | anonymous_upload_enable: false,
29 |
30 | anonymous_download_enable: true,
31 |
32 | webdav_path: '',
33 | //代理路径
34 | proxy_paths: [],
35 |
36 | proxy_server: '',
37 |
38 | webdav_proxy: true,
39 |
40 | webdav_user: 'admin',
41 |
42 | webdav_pass: 'sharelist',
43 |
44 | ocr_server: '',
45 |
46 | drives: [],
47 |
48 | proxy_url: 'https://reruin.github.io/sharelist/redirect.html',
49 | }
50 |
51 |
52 | exports.defaultConfigKey = Object.keys(defaultConfig)
53 |
54 | exports.createConfig = (path) => {
55 | return createDB(
56 | path,
57 | { raw: true },
58 | {
59 | ...defaultConfig,
60 | },
61 | )
62 | }
63 |
--------------------------------------------------------------------------------
/packages/sharelist/package/sharelist/db.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 | const os = require('os')
3 | const fs = require('fs')
4 | const writeFileAtomic = require('write-file-atomic')
5 | const { reactive, watch } = require('./reactivity')
6 |
7 | const mkdir = function (p) {
8 | if (fs.existsSync(p) == false) {
9 | mkdir(path.dirname(p))
10 | fs.mkdirSync(p)
11 | }
12 | }
13 |
14 | const base64 = {
15 | encode: (v) => Buffer.from(v).toString('base64'),
16 | decode: (v) => Buffer.from(v, 'base64').toString(),
17 | }
18 |
19 | const merge = function (dst, src) {
20 | for (let key in src) {
21 | if (!(key in dst)) {
22 | dst[key] = src[key]
23 | continue
24 | } else {
25 | if (typeof src[key] == 'object' || Array.isArray(src[key])) {
26 | merge(dst[key], src[key])
27 | } else {
28 | dst[key] = src[key]
29 | }
30 | }
31 | }
32 | return dst
33 | }
34 |
35 | const getData = (path, options) => {
36 | try {
37 | let data = fs.readFileSync(path, 'utf8')
38 |
39 | if (!options.raw) {
40 | data = base64.decode(data)
41 | }
42 |
43 | return JSON.parse(data)
44 | } catch (error) {
45 | //if it doesn't exist or permission error
46 | if (error.code === 'ENOENT' || error.code === 'EACCES') {
47 | return {}
48 | }
49 |
50 | //invalid JSON
51 | if (error.name === 'SyntaxError') {
52 | writeFileAtomic.sync(path, '')
53 | return {}
54 | }
55 |
56 | throw error
57 | }
58 | }
59 |
60 | const setData = (filepath, { raw }, value) => {
61 | try {
62 | mkdir(path.dirname(filepath))
63 |
64 | value = JSON.stringify(value)
65 | if (!raw) {
66 | value = base64.encode(value)
67 | }
68 |
69 | writeFileAtomic.sync(filepath, value)
70 | } catch (error) {
71 | //throw error;
72 | }
73 | }
74 |
75 | const createdb = (path, { raw, shallow } = { raw: false, shallow: false }, defaults = {}) => {
76 | let data = merge(defaults, getData(path, { raw }))
77 |
78 | const save = () => setImmediate(() => setData(path, { raw }, data))
79 |
80 | const db = reactive(data, shallow)
81 |
82 | watch(
83 | () => db,
84 | () => {
85 | save()
86 | },
87 | { deep: true },
88 | )
89 |
90 | return db
91 | }
92 |
93 | module.exports = createdb
94 |
--------------------------------------------------------------------------------
/packages/sharelist/package/sharelist/index.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 |
3 | const factory = require('@sharelist/core')
4 |
5 | const createCache = require('./cache')
6 |
7 | const { createConfig, defaultConfigKey } = require('./config')
8 |
9 | const { watch } = require('./reactivity')
10 |
11 | const utils = require('./utils')
12 |
13 | const diff = (nv, ov) => {
14 | let needUpdate = nv.filter((i) => ov.every((j) => j.path != i.path))
15 | let needRemove = ov.filter((i) => nv.every((j) => j.path != i.path))
16 |
17 | return [needUpdate, needRemove]
18 | }
19 |
20 | module.exports = (app) => {
21 | app.addSingleton('sharelist', async (options) => {
22 | const config = createConfig(path.join(options.cacheDir, 'config.json'))
23 |
24 | const cache = createCache(path.join(options.cacheDir, 'cache.json'))
25 |
26 | const plugins = Object.values(await app.loadTo(options.pluginDir))
27 |
28 | const driver = await factory({ config, plugins, cache })
29 |
30 | const getDisk = () => {
31 | return config.drives.map((i) => ({ name: i.name, path: driver.decode(i.path) }))
32 | }
33 |
34 | const setDisk = (data) => {
35 | data.forEach((i) => {
36 | i.path = driver.encode(i.path)
37 | })
38 | config.drives = data
39 | }
40 |
41 | const reload = async () => {
42 | driver.reload(Object.values(await app.loadTo(options.pluginDir)))
43 | }
44 |
45 | const instance = {
46 | config, cache, getDisk, setDisk, ...driver,
47 | }
48 |
49 | const getFiles = (...rest) => utils.getFiles(instance, ...rest)
50 | const getFile = (...rest) => utils.getFile(instance, ...rest)
51 | const getDownloadUrl = (...rest) => utils.getDownloadUrl(instance, ...rest)
52 | watch(
53 | () => config.drives,
54 | (nv, ov) => {
55 | let [updateDisk = [], removeDisk = []] = diff(nv, ov)
56 | reload()
57 | },
58 | )
59 |
60 | return {
61 | ...instance,
62 | defaultConfigKey,
63 | getFiles,
64 | getFile,
65 | getDownloadUrl
66 | }
67 | })
68 | }
69 |
--------------------------------------------------------------------------------
/packages/sharelist/package/sharelist/reactivity/effect.js:
--------------------------------------------------------------------------------
1 | const { isArray, isObject, isMap, isSet } = require('./utils')
2 |
3 | let shouldTrack = true
4 | let activeEffect
5 | const effectStack = []
6 | const trackStack = []
7 | const targetMap = new WeakMap()
8 |
9 | const ITERATE_KEY = Symbol('')
10 | const MAP_KEY_ITERATE_KEY = Symbol('')
11 |
12 | const pauseTracking = () => {
13 | trackStack.push(shouldTrack)
14 | shouldTrack = false
15 | }
16 |
17 | const enableTracking = () => {
18 | trackStack.push(shouldTrack)
19 | shouldTrack = true
20 | }
21 |
22 | const resetTracking = () => {
23 | const last = trackStack.pop()
24 | shouldTrack = last === undefined ? true : last
25 | }
26 |
27 | const effect = (fn, options = {}) => {
28 | const effectIns = function reactiveEffect() {
29 | if (!effectIns.active) {
30 | return fn()
31 | }
32 | if (!effectStack.includes(effectIns)) {
33 | clearEffect(effectIns)
34 | try {
35 | enableTracking()
36 | effectStack.push(effectIns)
37 | activeEffect = effectIns
38 | return fn()
39 | } catch (e) {
40 | console.log(e)
41 | } finally {
42 | effectStack.pop()
43 | resetTracking()
44 | activeEffect = effectStack[effectStack.length - 1]
45 | }
46 | }
47 | }
48 | effectIns.active = true
49 | effectIns.deps = []
50 | effectIns.options = options
51 | return effectIns
52 | }
53 |
54 | const clearEffect = (effect) => {
55 | const { deps } = effect
56 | if (deps.length) {
57 | for (let i = 0; i < deps.length; i++) {
58 | deps[i].delete(effect)
59 | }
60 | deps.length = 0
61 | }
62 | }
63 |
64 | const stopEffect = (effect) => {
65 | if (effect.active) {
66 | clearEffect(effect)
67 | if (effect.options.onStop) {
68 | effect.options.onStop()
69 | }
70 | effect.active = false
71 | }
72 | }
73 |
74 | const track = (target, key) => {
75 | if (!shouldTrack || activeEffect === undefined) {
76 | return
77 | }
78 | let depsMap = targetMap.get(target)
79 | if (!depsMap) {
80 | targetMap.set(target, (depsMap = new Map()))
81 | }
82 |
83 | let dep = depsMap.get(key)
84 | if (!dep) {
85 | depsMap.set(key, (dep = new Set()))
86 | }
87 |
88 | if (!dep.has(activeEffect)) {
89 | dep.add(activeEffect)
90 | activeEffect.deps.push(dep)
91 | }
92 | }
93 |
94 | const trigger = (target, type, key, newValue, oldValue) => {
95 | const depsMap = targetMap.get(target)
96 | if (!depsMap) {
97 | return
98 | }
99 |
100 | const effects = new Set()
101 |
102 | const add = (dep = []) => dep.forEach((effect) => effects.add(effect))
103 |
104 | if (type == 'clear') {
105 | depsMap.forEach(add)
106 | } else if (key === 'length' && isArray(target)) {
107 | depsMap.forEach((dep, key) => {
108 | if (key === 'length' || key >= newValue) {
109 | add(dep)
110 | }
111 | })
112 | } else {
113 | if (key) {
114 | add(depsMap.get(key))
115 | }
116 |
117 | switch (type) {
118 | case 'add':
119 | if (!isArray(target)) {
120 | //触发自身
121 | add(depsMap.get(ITERATE_KEY))
122 | if (isMap(target)) {
123 | add(depsMap.get(MAP_KEY_ITERATE_KEY))
124 | }
125 | } else if (isIntegerKey(key)) {
126 | // new index added to array -> length changes
127 | add(depsMap.get('length'))
128 | }
129 | break
130 | case 'delete':
131 | if (!isArray(target)) {
132 | add(depsMap.get(ITERATE_KEY))
133 | if (isMap(target)) {
134 | add(depsMap.get(MAP_KEY_ITERATE_KEY))
135 | }
136 | }
137 | break
138 | case 'set':
139 | if (isMap(target)) {
140 | add(depsMap.get(ITERATE_KEY))
141 | }
142 | break
143 | }
144 | }
145 |
146 | effects.forEach((effect) => {
147 | if (effect !== activeEffect) {
148 | if (effect.options.scheduler) {
149 | effect.options.scheduler(effect)
150 | } else {
151 | effect()
152 | }
153 | }
154 | })
155 | }
156 |
157 | exports.effect = effect
158 | exports.track = track
159 | exports.trigger = trigger
160 | exports.pauseTracking = pauseTracking
161 | exports.enableTracking = enableTracking
162 | exports.ITERATE_KEY = ITERATE_KEY
163 |
--------------------------------------------------------------------------------
/packages/sharelist/package/sharelist/reactivity/index.js:
--------------------------------------------------------------------------------
1 | const { reactive } = require('./reactive')
2 | const { watch } = require('./watch')
3 | exports.reactive = reactive
4 | exports.watch = watch
--------------------------------------------------------------------------------
/packages/sharelist/package/sharelist/reactivity/reactive.js:
--------------------------------------------------------------------------------
1 | const { track, trigger, pauseTracking, resetTracking, ITERATE_KEY } = require('./effect')
2 | const { isObject, isArray, hasOwn, hasChanged } = require('./utils')
3 |
4 | const arrayInstrumentations = createArrayInstrumentations()
5 | const reactiveMap = new WeakMap()
6 | const Flags = {
7 | IS_REACTIVE: Symbol('isReactive'),
8 | RAW: Symbol('raw')
9 | }
10 |
11 | function createArrayInstrumentations() {
12 | const instrumentations = {}
13 | // instrument identity-sensitive Array methods to account for possible reactive
14 | // values
15 | ; (['includes', 'indexOf', 'lastIndexOf']).forEach(key => {
16 | const method = Array.prototype[key]
17 | instrumentations[key] = function (scope, ...args) {
18 | const arr = [toRaw(scope)]
19 | for (let i = 0, l = this.length; i < l; i++) {
20 | track(arr, 'get', i + '')
21 | }
22 | // we run the method using the original args first (which may be reactive)
23 | const res = method.apply(arr, args)
24 | if (res === -1 || res === false) {
25 | // if that didn't work, run it again using raw values.
26 | return method.apply(arr, args.map(toRaw))
27 | } else {
28 | return res
29 | }
30 | }
31 | })
32 | // instrument length-altering mutation methods to avoid length being tracked
33 | // which leads to infinite loops in some cases (#2137)
34 | ; (['push', 'pop', 'shift', 'unshift', 'splice']).forEach(key => {
35 | const method = Array.prototype[key]
36 | instrumentations[key] = function (...args) {
37 | pauseTracking()
38 | const res = method.apply(this, args)
39 | resetTracking()
40 | return res
41 | }
42 | })
43 | return instrumentations
44 | }
45 |
46 | const reactive = (data, shallow = false) => {
47 | if (!isObject(data) || (data[Flags.RAW] && data[Flags.IS_REACTIVE])) {
48 | return data
49 | }
50 |
51 | const existingProxy = reactiveMap.get(data)
52 | if (existingProxy) {
53 | return existingProxy
54 | }
55 |
56 | const proxy = new Proxy(data, {
57 | get(target, key, receiver) {
58 | if (key === Flags.IS_REACTIVE) {
59 | return true
60 | }
61 | else if (key === Flags.RAW && receiver === reactiveMap.get(target)) {
62 | return target
63 | }
64 |
65 | if (isArray(target) && hasOwn(arrayInstrumentations, key)) {
66 | return Reflect.get(arrayInstrumentations, key, receiver)
67 | }
68 |
69 | const res = Reflect.get(target, key, receiver)
70 | track(target, key)
71 |
72 | if (!shallow && isObject(res)) {
73 | return reactive(res, shallow)
74 | }
75 |
76 | return res
77 | },
78 | set(target, key, value, receiver) {
79 | let oldValue = target[key]
80 | if (!shallow) {
81 | value = toRaw(value)
82 | oldValue = toRaw(oldValue)
83 | }
84 | const hadKey =
85 | isArray(target) && isIntegerKey(key)
86 | ? Number(key) < target.length
87 | : hasOwn(target, key)
88 |
89 | const result = Reflect.set(target, key, value, receiver)
90 | if (target === toRaw(receiver)) {
91 | if (!hadKey) {
92 | trigger(target, 'add', key, value)
93 | } else if (hasChanged(value, oldValue)) {
94 | trigger(target, 'set', key, value, oldValue)
95 | }
96 | }
97 | return result
98 | },
99 | deleteProperty(target, key) {
100 | const hasKey = hasOwn(target, key)
101 | const oldValue = target[key]
102 | const result = Reflect.deleteProperty(target, key)
103 | if (result && hasKey) {
104 | trigger(target, 'delete', key, undefined, oldValue)
105 | }
106 | return result
107 | },
108 | ownKeys(target) {
109 | track(target, isArray(target) ? 'length' : ITERATE_KEY)
110 | return Reflect.ownKeys(target)
111 | }
112 | })
113 |
114 | reactiveMap.set(data, proxy)
115 |
116 | return proxy
117 | }
118 |
119 | const isReactive = (value) => {
120 | return !!value[Flags.IS_REACTIVE]
121 | }
122 |
123 | const toRaw = (observed) => {
124 | return (
125 | (observed && toRaw(observed[Flags.RAW])) || observed
126 | )
127 | }
128 |
129 | exports.isReactive = isReactive
130 | exports.reactive = reactive
--------------------------------------------------------------------------------
/packages/sharelist/package/sharelist/reactivity/scheduler.js:
--------------------------------------------------------------------------------
1 | let queued = false
2 | const queue = []
3 | const p = Promise.resolve()
4 |
5 | exports.nextTick = (fn) => p.then(fn)
6 |
7 | exports.queueJob = (job) => {
8 | if (!queue.includes(job)) queue.push(job)
9 | if (!queued) {
10 | queued = true
11 | nextTick(flushJobs)
12 | }
13 | }
14 |
15 | const flushJobs = () => {
16 | for (let i = 0; i < queue.length; i++) {
17 | queue[i]()
18 | }
19 | queue.length = 0
20 | queued = false
21 | }
--------------------------------------------------------------------------------
/packages/sharelist/package/sharelist/reactivity/utils.js:
--------------------------------------------------------------------------------
1 | const { isMap, isSet, isProxy } = require('util').types
2 |
3 | exports.isObject = v => v !== null && typeof v === 'object'
4 |
5 | exports.isArray = v => Array.isArray(v)
6 |
7 | exports.isFunction = val => typeof val === 'function'
8 |
9 | exports.isString = val => typeof val === 'string'
10 |
11 | exports.isPlainObject = val => Object.prototype.toString.call(val) === '[object Object]'
12 |
13 | exports.isIntegerKey = (key) =>
14 | isString(key) &&
15 | key !== 'NaN' &&
16 | key[0] !== '-' &&
17 | '' + parseInt(key, 10) === key
18 |
19 | exports.hasChanged = (value, oldValue) => value !== oldValue && (value === value || oldValue === oldValue)
20 |
21 | exports.hasOwnProperty = Object.prototype.hasOwnProperty
22 |
23 | exports.hasOwn = (val, key) => hasOwnProperty.call(val, key)
24 |
25 | exports.isMap = isMap
26 |
27 | exports.isSet = isSet
28 |
29 | exports.isProxy = isProxy
30 |
--------------------------------------------------------------------------------
/packages/sharelist/package/sharelist/reactivity/watch.js:
--------------------------------------------------------------------------------
1 |
2 | const { isReactive } = require('./reactive')
3 | const { isFunction } = require('./utils')
4 | const { effect } = require('./effect')
5 | const { isObject, isArray, isPlainObject, hasChanged } = require('./utils')
6 |
7 | const watch = (source, cb, { immediate, deep } = { immediate: false, deep: false }) => {
8 | let oldValue = {}
9 | let getter
10 | if (isReactive(source)) {
11 | getter = () => source
12 | deep = true
13 | } else if (isFunction(source)) {
14 | getter = source
15 | } else {
16 | getter = () => { }
17 | }
18 | if (cb && deep) {
19 | const baseGetter = getter
20 | getter = () => {
21 | return traverse(baseGetter())
22 | }
23 | }
24 |
25 | const job = () => {
26 | if (!runner.active) {
27 | return
28 | }
29 | if (cb) {
30 | let newValue = runner()
31 | if (deep || hasChanged(newValue, oldValue)) {
32 | cb(newValue, oldValue)
33 | oldValue = newValue
34 | }
35 | }
36 | // watchEffect
37 | else {
38 | runner()
39 | }
40 | }
41 |
42 | const scheduler = job
43 | const runner = effect(getter, {
44 | scheduler
45 | })
46 |
47 | if (cb) {
48 | if (immediate) {
49 | scheduler()
50 | } else {
51 | oldValue = runner()
52 | }
53 | } else {
54 | runner()
55 | }
56 |
57 | return () => {
58 | stopEffect(runner)
59 | }
60 | }
61 |
62 | const watchEffect = (effect, options) => {
63 | return watch(effect, null, options)
64 | }
65 |
66 | const traverse = (value, seen = new Set()) => {
67 | if (
68 | !isObject(value) ||
69 | seen.has(value)
70 | ) {
71 | return value
72 | }
73 | seen.add(value)
74 | if (isArray(value)) {
75 | for (let i = 0; i < value.length; i++) {
76 | traverse(value[i], seen)
77 | }
78 | } else if (isPlainObject(value)) {
79 | for (const key in value) {
80 | traverse(value[key], seen)
81 | }
82 | }
83 | return value
84 | }
85 |
86 | exports.watch = watch
87 | exports.watchEffect = watchEffect
--------------------------------------------------------------------------------
/packages/sharelist/package/sharelist/utils.js:
--------------------------------------------------------------------------------
1 |
2 | const ignore = require('ignore')
3 |
4 | const isForbiddenPath = (p) => {
5 | return false
6 | }
7 |
8 | const isIgnorePath = (p = '', config) => {
9 | p = p.replace(/^\//, '')
10 | return p && ignore().add([].concat(config.acl_file, config.ignores)).ignores(p)
11 | }
12 |
13 | const isProxyPath = (p, config) => {
14 | p = p.replace(/^\//, '')
15 | return p && config.proxy_enable && ignore().add(config.proxy_paths).ignores(p)
16 | }
17 |
18 | exports.getFiles = async (sharelist, runtime) => {
19 | let { config } = sharelist
20 |
21 | //使用路径模式,提前排除
22 | if (runtime.path && isIgnorePath(runtime.path, config)) {
23 | return { error: { code: 404 } }
24 | }
25 |
26 | if (runtime.path && isForbiddenPath(runtime.path, config)) {
27 | return { error: { code: 404 } }
28 | }
29 |
30 | if (!config.index_enable) {
31 | return { error: { code: 403 } }
32 | }
33 |
34 | let data
35 | try {
36 | data = await sharelist.list(runtime)
37 | } catch (e) {
38 | //console.trace(e)
39 | return { error: { code: e.code || 500, message: e.message } }
40 | }
41 | if (data.files?.length > 0) {
42 | let base_url = runtime.path == '/' ? '' : runtime.path
43 | data.files = data.files
44 | .filter(i =>
45 | !isIgnorePath(decodeURIComponent(base_url + '/' + i.name).substring(1), config)
46 | &&
47 | i.hidden !== true
48 | )
49 | }
50 | return { data }
51 |
52 | }
53 |
54 | exports.getFile = async (sharelist, runtime) => {
55 | let { config } = sharelist
56 |
57 | //使用路径模式,提前排除
58 | if (runtime.path && isIgnorePath(runtime.path, config)) {
59 | return { error: { code: 404 } }
60 | }
61 |
62 | if (runtime.path && isForbiddenPath(runtime.path, config)) {
63 | return { error: { code: 404 } }
64 | }
65 |
66 | let data
67 | try {
68 | data = await sharelist.get(runtime)
69 | } catch (e) {
70 | return { error: { code: e.code || 500, msg: e.message } }
71 | }
72 |
73 | return { data }
74 | }
75 |
76 | exports.getDownloadUrl = async (runtime) => {
77 | //使用路径模式,提前排除
78 | if (runtime.path && isIgnorePath(runtime.path, config)) {
79 | return { error: { code: 404 } }
80 | }
81 |
82 | if (runtime.path && isForbiddenPath(runtime.path, config)) {
83 | return { error: { code: 404 } }
84 | }
85 |
86 | try {
87 | let { url } = await sharelist.get_download_url(runtime)
88 | return { url }
89 |
90 | } catch (error) {
91 | return { error }
92 | }
93 |
94 | }
95 |
--------------------------------------------------------------------------------
/scripts/build.js:
--------------------------------------------------------------------------------
1 |
2 | const child_process = require('child_process')
3 | const { readdir, copyFile } = require('fs/promises')
4 | const path = require('path')
5 |
6 | const run = cmd => new Promise((resolve, reject) => {
7 | child_process.exec(cmd, (error, stdout, stderr) => {
8 | if (error) {
9 | resolve()
10 | } else {
11 | resolve(stdout)
12 | }
13 | })
14 | })
15 |
16 | const cp = async (src, dst) => {
17 | let files = await readdir(src)
18 | for (let file of files) {
19 | let name = path.basename(file)
20 | let dst = path.join(dst, name)
21 | await copyFile(file, dst)
22 | }
23 | }
24 |
25 | const main = async () => {
26 | await run(`yarn build-web`)
27 | await cp('../packages/sharelist-plugin/lib', '../packages/sharelist/plugins')
28 | await run(`pkg ./packages/sharelist/ --output build/sharelist --targets linux-x64,macos-x64,macos-arm64,win-x64`)
29 | }
--------------------------------------------------------------------------------
/scripts/changelog.js:
--------------------------------------------------------------------------------
1 | const child_process = require('child_process')
2 |
3 | const COMMIT_PATTERN = /^([^)]*)(?:\(([^)]*?)\)|):(.*?(?:\[([^\]]+?)\]|))\s*$/
4 |
5 | // const PR_REGEX = /#[1-9][\d]*/g
6 |
7 | const SEPARATOR = '===END==='
8 |
9 | const TYPES = {
10 | breaking: 'Breaking Changes',
11 | feat: 'New Features',
12 | fix: 'Bug Fixes',
13 | }
14 |
15 | const run = cmd => new Promise((resolve, reject) => {
16 | child_process.exec(cmd, (error, stdout, stderr) => {
17 | if (error) {
18 | resolve()
19 | } else {
20 | resolve(stdout)
21 | }
22 | })
23 | })
24 |
25 | const markdown = (commits, REPO_URL) => {
26 | let content = []
27 | Object.keys(commits).filter(type => TYPES[type]).forEach((type) => {
28 | content.push('##### ' + TYPES[type])
29 | content.push('');
30 | Object.keys(commits[type]).forEach(category => {
31 |
32 | let multiline = commits[type][category].length > 1;
33 | let head = category ? `* **${category}:**` : '*'
34 |
35 | if (multiline && category) {
36 | content.push(head);
37 | head = ' *'
38 | }
39 |
40 | commits[type][category].forEach((commit) => {
41 | let hashLink = REPO_URL ? `[${commit.hash.substring(0, 8)}](${REPO_URL}/commit/${commit.hash})` : `${commit.hash.substring(0, 8)}`
42 | content.push(`${head} ${commit.subject} (${hashLink})`)
43 | });
44 | })
45 | content.push('');
46 | })
47 |
48 | return content.join('\n')
49 | }
50 |
51 | const main = async (REPO_URL) => {
52 | let hash = await run(`git rev-list --tags --max-count=2`)
53 |
54 | let fromVer = 'HEAD'
55 |
56 | if (hash) {
57 | let lastTagHash = hash.split(/[\r\n]/g)[1]
58 | if (lastTagHash) {
59 | let hit = await run(`git describe --abbrev=0 --tags ${lastTagHash}`)
60 | if (hit) fromVer = hit.toString().trim()
61 | }
62 | }
63 |
64 | const commits = await run(`git log -E --format=%H%n%s%n%b%n${SEPARATOR} ${fromVer}..`)
65 |
66 | const content = markdown(commits.split('\n' + SEPARATOR + '\n').filter(Boolean).map(raw => {
67 | const [hash, subject, ...body] = raw.split('\n');
68 | const commit = {
69 | hash, subject
70 | }
71 |
72 | const parsed = commit.subject.match(COMMIT_PATTERN)
73 |
74 | if (!parsed || !parsed[1] || !parsed[3]) {
75 | return null
76 | }
77 |
78 | commit.type = parsed[1].toLowerCase()
79 | commit.category = parsed[2] || ''
80 | commit.subject = parsed[3]
81 |
82 | return commit
83 | }).filter(Boolean).reduce((t, c) => {
84 | t[c.type] = t[c.type] || {}
85 | t[c.type][c.category] = t[c.type][c.category] || []
86 | t[c.type][c.category].push(c)
87 | return t
88 | }, {}), REPO_URL)
89 |
90 | return content
91 | }
92 |
93 | // (async function () {
94 | // console.log(await main())
95 | // })()
96 | module.exports = main
97 |
--------------------------------------------------------------------------------
/scripts/netinstall.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
4 |
5 | echo "+============================================================+"
6 | echo "| ShareList(Next) NetInstaller |"
7 | echo "| |"
8 | echo "| |"
9 | echo "|------------------------------------------------------------|"
10 | echo "| https://reruin.net |"
11 | echo "+============================================================+"
12 | echo ""
13 |
14 | echo -e "\n| ShareList(Next) is installing ... "
15 |
16 |
17 | echo -e "|\n| Download ShareList Package ... "
18 | wget -O sharelist-master.zip https://github.com/reruin/sharelist/archive/refs/heads/master.zip >/dev/null 2>&1
19 |
20 | unzip -q -o sharelist-master.zip -d ./
21 |
22 | mv sharelist-master sharelist
23 | rm -f sharelist-master.zip
24 |
25 | cd sharelist
26 | echo -e "|\n| Install Dependents ... "
27 | npm install yarn -g >/dev/null 2>&1
28 | npm install pm2 -g >/dev/null 2>&1
29 |
30 | yarn install >/dev/null 2>&1
31 | yarn build-web
32 | mkdir -p ./packages/sharelist/theme/default
33 | mkdir -p ./packages/sharelist/plugins
34 | cp -r ./packages/sharelist-web/dist/* ./packages/sharelist/theme/default
35 | cp -r ./packages/sharelist-plugin/lib/* ./packages/sharelist/plugins
36 | cd packages/sharelist
37 |
38 | pm2 start app.js --name sharelist-next >/dev/null 2>&1
39 | pm2 save >/dev/null 2>&1
40 | pm2 startup >/dev/null 2>&1
41 |
42 | echo -e "|\n| Success: ShareList(next) has been installed\n"
--------------------------------------------------------------------------------