[
  {
    "path": ".eslintrc.js",
    "content": "module.exports = {\n  parser: 'vue-eslint-parser',\n  parserOptions: {\n    // set script parser\n    parser: '@typescript-eslint/parser', // Specifies the ESLint parser\n    ecmaVersion: 2021, // Allows for the parsing of modern ECMAScript features\n    sourceType: 'module', // Allows for the use of imports\n    ecmaFeatures: {\n      jsx: true, // Allows for the parsing of JSX\n    },\n    validate: [],\n  },\n  extends: [\n    'plugin:vue/vue3-recommended',\n    'plugin:@typescript-eslint/recommended', // Uses the recommended rules from the @typescript-eslint/eslint-plugin\n    'plugin:prettier/recommended',\n  ],\n  rules: {\n    // Place to specify ESLint rules. Can be used to overwrite rules specified from the extended configs\n    // e.g. \"@typescript-eslint/explicit-function-return-type\": \"off\",\n    '@typescript-eslint/no-unused-vars': 'off',\n    '@typescript-eslint/no-explicit-any': 'off',\n    '@typescript-eslint/no-empty-function': 'off',\n    '@typescript-eslint/no-var-requires': 'off',\n\n    'prettier/prettier': ['off', { endOfLine: 'auto' }],\n  },\n}\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.yml",
    "content": "name: \"Bug report\"\ndescription: 问题报告\nlabels: [pending triage]\nbody:\n  - type: markdown\n    attributes:\n      value: |\n        Thanks for taking the time to fill out this bug report!\n  - type: textarea\n    id: bug-description\n    attributes:\n      label: 问题描述 / Describe the bug\n    validations:\n      required: true\n  - type: dropdown\n    id: version\n    attributes:\n      label: Sharelist 版本 / Sharelist Version\n      description: Sharelist Version\n      options:\n        - v0.1\n        - next\n    validations:\n      required: true\n  - type: textarea\n    id: reproduction\n    attributes:\n      label: 复现链接 / Reproduction\n      description: |\n        请提供能复现此问题的链接\n        Please provide a link to a repo that can reproduce the problem you ran into. \n    validations:\n      required: false\n  - type: textarea\n    id: logs\n    attributes:\n      label: 日志 / Logs\n      description: |\n        请复制错误日志，或者截图\n        Please copy paste the log text.\n      render: shell"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "content": "blank_issues_enabled: false\ncontact_links:\n  - name: Questions & Discussions\n    url: https://github.com/reruin/sharelist/discussions\n    about: Use GitHub discussions for message-board style questions and discussions."
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.yml",
    "content": "name: \"Feature request\"\ndescription: 功能需求\nlabels: [\"enhancement: pending triage\"]\nbody:\n  - type: textarea\n    id: feature-description\n    attributes:\n      label: 需求描述 / Description of the feature\n    validations:\n      required: true\n  - type: textarea\n    id: suggested-solution\n    attributes:\n      label: 实现思路 / Suggested solution\n      description: 实现此需求的解决思路。\n    validations:\n      required: false\n  - type: textarea\n    id: additional-context\n    attributes:\n      label: 附件 / Additional context\n      description: |\n        相关的任何其他上下文或截图。 \n        Any other context or screenshots about the feature request here."
  },
  {
    "path": ".github/workflows/ci.yml",
    "content": "name: CI\n\non:\n  push:\n    tags:\n      - 'v*'\njobs:\n  build:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v2\n        with:\n          fetch-depth: 0\n      - name: Use Node.js\n        uses: actions/setup-node@v3\n        with:\n          node-version: '18'\n      - name: Get Env\n        uses: actions/github-script@v4\n        with:\n          script: |\n            const tag = process.env.GITHUB_REF.split('/').slice(-1)[0]\n            const createChangelog = require('./scripts/changelog.js')\n            const content = await createChangelog('https://github.com/'+process.env.GITHUB_REPOSITORY)\n            core.exportVariable('CHANGELOG', content)\n            core.exportVariable('VERSION', tag)\n      - name: Build\n        run: |\n          rm -rf ./packages/sharelist-webdav\n          yarn install\n          yarn build-manage\n          yarn build-web\n          mkdir -p ./packages/sharelist/web/default\n          mkdir -p ./packages/sharelist/manage\n          wget https://raw.githubusercontent.com/linkdrive/plugins/master/list_full.json -O ./packages/sharelist/plugins.json\n          cp -r ./packages/sharelist-web/dist/* ./packages/sharelist/web/default\n          cp -r ./packages/sharelist-manage/dist/* ./packages/sharelist/manage\n          yarn build-server\n      - name: Release\n        run: |\n          cd ./packages/sharelist/build\n          tar --transform='flags=r;s|sharelist-win-x64.exe|sharelist.exe|' -zcvf sharelist_windows_amd64.tar.gz sharelist-win-x64.exe\n          tar --transform='flags=r;s|sharelist-macos-x64|sharelist|' -zcvf sharelist_macos_amd64.tar.gz sharelist-macos-x64\n          tar --transform='flags=r;s|sharelist-linux-x64|sharelist|' -zcvf sharelist_linux_amd64.tar.gz sharelist-linux-x64\n          tar --transform='flags=r;s|sharelist-linux-arm64|sharelist|' -zcvf sharelist_linux_arm64.tar.gz sharelist-linux-arm64\n          tar --transform='flags=r;s|sharelist-linuxstatic-armv7|sharelist|' -zcvf sharelist_linux_armv7.tar.gz sharelist-linuxstatic-armv7\n          gh release create ${{ env.VERSION }} -n \"${{ env.NOTE }}\" -t \"${{ env.VERSION }}\" ${{ env.FILES }}\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n          VERSION: ${{ env.VERSION }}\n          NOTE: ${{ env.CHANGELOG }}\n          TITLE: ${{ env.VERSION }}\n          FILES: ./*.gz"
  },
  {
    "path": ".github/workflows/docker.yml",
    "content": "name: Docker\n\non:\n  push:\n    # branches:\n    #   - next\n    tags:\n      - 'v*'\njobs:\n  build:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v2\n      - name: Use Node.js\n        uses: actions/setup-node@v3\n        with:\n          node-version: '18'\n\n      - name: Install dependencies\n        run: yarn install\n      \n      - name: Pre Build\n        run: |\n          yarn build-web\n          yarn build-manage\n          mkdir -p ./packages/sharelist/web/default\n          mkdir -p ./packages/sharelist/manage\n          wget https://raw.githubusercontent.com/linkdrive/plugins/master/list_full.json -O ./packages/sharelist/plugins.json\n          cp -r ./packages/sharelist-web/dist/* ./packages/sharelist/web/default\n          cp -r ./packages/sharelist-manage/dist/* ./packages/sharelist/manage\n \n      - name: Login to Docker Hub\n        uses: docker/login-action@v1\n        with:\n          username: ${{ secrets.DOCKER_HUB_USERNAME }}\n          password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}\n      \n      - name: Set up QEMU\n        uses: docker/setup-qemu-action@v1\n\n      - name: Set up Docker Buildx\n        id: buildx\n        uses: docker/setup-buildx-action@v1\n      - name: Build and push\n        id: docker_build\n        uses: docker/build-push-action@v2\n        with:\n          context: ./packages/sharelist/\n          platforms: linux/amd64,linux/arm64\n          file: ./packages/sharelist/Dockerfile\n          builder: ${{ steps.buildx.outputs.name }}\n          push: true\n          tags:  ${{ secrets.DOCKER_HUB_USERNAME }}/sharelist:${{ env.VERSION }}\n        env:\n          VERSION: next\n      - name: Image digest\n        run: echo ${{ steps.docker_build.outputs.digest }}"
  },
  {
    "path": ".gitignore",
    "content": "node_modules\nbuild/\ndist/\n/packages/sharelist/manage/\n/packages/sharelist/theme/\n/packages/sharelist/cache/\n/packages/sharelist/web/\n/packages/sharelist/plugin/\n/packages/sharelist/example\n.vscode/\n*.lock\n*.log\n*.exe\npackage-lock.json\n.yarn/\nplugins.json"
  },
  {
    "path": ".prettierrc.js",
    "content": "// https://prettier.io/docs/en/configuration.html\nmodule.exports = {\n  //分号终止符\n  semi: false,\n\n  //行尾逗号\n  trailingComma: \"all\",\n\n  // 使用单引号, 默认false(在jsx中配置无效, 默认都是双引号)\n  singleQuote: true,\n\n  printWidth: 120,\n  // 换行符\n  endOfLine: \"auto\",\n\n  //缩进 default:2\n  tabWidth: 2,\n\n  endOfLine: \"auto\",\n\n  arrowParens: \"avoid\"\n}"
  },
  {
    "path": ".yarnrc",
    "content": "# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.\n# yarn lockfile v1\n\n\nyarn-path \".yarn/releases/yarn-1.18.0.cjs\"\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2018-present, Reruin and Sharelist contributors\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE."
  },
  {
    "path": "README.md",
    "content": "# ShareList\n\n[![Build Status](https://github.com/reruin/sharelist/actions/workflows/ci.yml/badge.svg)](https://github.com/reruin/sharelist/actions/workflows/ci.yml)\n\nShareList 是一个易用的网盘工具，支持快速挂载 GoogleDrive、OneDrive ，可通过插件扩展功能。  \n新版正在开发中，欢迎[提交反馈](https://github.com/reruin/sharelist/issues/new/choose)。[查看旧版](https://github.com/reruin/sharelist/tree/0.1)。\n\n## 文档\n[查看文档](https://reruin.github.io/sharelist/docs/#/zh-cn/)\n\n## 进度\n- [x] 核心库支持 \n- [x] 新主题 \n- [x] 自定义插件开发\n- [x] webdav\n- [x] 跨盘转移\n- [x] 秒传\n\n## 支持情况\n|       | 下载 | 上传 | 列目录 | 创建目录 | 删除 | 重命名 | 远程移动 | hash | 秒传\n| ----        | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- |\nOneDrive   |  ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | sha1 | x |\nGoogleDrive   |  ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | md5 | x |\nLocal File    |  ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | md5/sha1 | x |\nAliyunDrive   |  ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | sha1 | ✓ |\nCaiYun   |  ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | md5 | ✓ |\nCTCloud   |  ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | md5 | ✓ |\nBaidu Netdisk   |  ✓ | x | ✓ | ✓ | ✓ | ✓ | ✓ | encrypt(md5) | ✓ |\n\n## 安装\n```docker\ndocker run -d -v /etc/sharelist:/sharelist/cache -p 33001:33001 --name=\"sharelist\" reruin/sharelist:next\n```\n\n[release](https://github.com/reruin/sharelist/releases)下载二进制版。\n\n\n## 许可\n[MIT](https://opensource.org/licenses/MIT)   \nCopyright (c) 2018-present, Reruin\n"
  },
  {
    "path": "docs/.nojekyll",
    "content": ""
  },
  {
    "path": "docs/README.md",
    "content": "# ShareList\n\n[![Build Status](https://github.com/reruin/sharelist/actions/workflows/ci.yml/badge.svg)](https://github.com/reruin/sharelist/actions/workflows/ci.yml)\n\n## Introduction\nShareList is an easy-to-use netdisk tool which supports that quickly mount GoogleDrive and OneDrive, and extends functions with plugins.\n\n## Documentation\nCheck out [docs](https://reruin.github.io/sharelist/#/en/).\n\n## License\n[Apache-2](http://www.apache.org/licenses/LICENSE-2.0)\nCopyright (c) 2018-present, Reruin"
  },
  {
    "path": "docs/_navbar.md",
    "content": "- Translations\n  - [:uk: English](/)\n  - [:cn: 中文](/zh-cn/)\n"
  },
  {
    "path": "docs/en/README.md",
    "content": "# ShareList\n\n[![Build Status](https://api.travis-ci.com/reruin/sharelist.svg?branch=master)](https://travis-ci.com/reruin/sharelist)\n\n## Introduction\nShareList is an easy-to-use netdisk tool which supports that quickly mount GoogleDrive and OneDrive, and extends functions with plugins.\n\n## Documentation\nCheck out [docs](https://reruin.github.io/sharelist/#/en/).\n\n## License\n[Apache-2](http://www.apache.org/licenses/LICENSE-2.0)   \nCopyright (c) 2018-present, Reruin"
  },
  {
    "path": "docs/en/_navbar.md",
    "content": "- Translations\n  - [中文](/zh-cn/)\n  - [English](/en/)"
  },
  {
    "path": "docs/en/_sidebar.md",
    "content": "* [Installation](en/installation.md)\n* [Configuration](en/configuration.md)\n* [Advance](en/advance.md)"
  },
  {
    "path": "docs/en/installation.md",
    "content": "# Installation\n\nTranslation needed!\n"
  },
  {
    "path": "docs/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n  <meta charset=\"UTF-8\">\n  <title>ShareList Docs</title>\n  <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge,chrome=1\" />\n  <meta name=\"description\" content=\"Description\">\n  <meta name=\"viewport\" content=\"width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0\">\n  <!-- <link rel=\"stylesheet\" href=\"//cdn.jsdelivr.net/npm/docsify/lib/themes/vue.css\"> -->\n  <link rel=\"stylesheet\" href=\"//cdn.jsdelivr.net/npm/docsify/themes/vue.css\">\n  <style type=\"text/css\">\n  .sidebar ul li.active>a {\n    border-right: none;\n  }\n  </style>\n</head>\n\n<body>\n  <div id=\"app\"></div>\n  <script>\n  window.$docsify = {\n    name: 'ShareList',\n    repo: 'https://github.com/reruin/sharelist',\n    basePath:\n    'https://raw.githubusercontent.com/reruin/sharelist/master/docs/',\n    loadSidebar: true,\n    loadNavbar: true,\n    autoHeader: true,\n    subMaxLevel: 2,\n    search: {\n      placeholder: {\n        '/zh-cn/': '搜索',\n        '/en/': 'Search',\n      },\n      noData: {\n        '/zh-cn/': '找不到结果',\n        '/en/': 'noData',\n      },\n    },\n    nameLink: {\n      '/zh-cn/': '#/zh-cn/',\n      '/en/': '#/en/',\n    },\n  }\n  </script>\n  <script src=\"//cdn.jsdelivr.net/npm/docsify/lib/docsify.min.js\"></script>\n  <script src=\"//cdn.jsdelivr.net/npm/docsify/lib/plugins/search.min.js\"></script>\n</body>\n\n</html>"
  },
  {
    "path": "docs/zh-cn/README.md",
    "content": "# ShareList\n\n[![Build Status](https://github.com/reruin/sharelist/actions/workflows/ci.yml/badge.svg)](https://github.com/reruin/sharelist/actions/workflows/ci.yml)\n\n## 介绍\nShareList 是一个易用的网盘工具，支持快速挂载 GoogleDrive、OneDrive ，可通过插件扩展功能。\n\n## 许可\n[Apache-2](http://www.apache.org/licenses/LICENSE-2.0)   \nCopyright (c) 2018-present, Reruin"
  },
  {
    "path": "docs/zh-cn/_navbar.md",
    "content": "- Translations\n  - [中文](/zh-cn/)\n  - [English](/en/)"
  },
  {
    "path": "docs/zh-cn/_sidebar.md",
    "content": "* [安装](zh-cn/installation.md)\n* [配置](zh-cn/configuration.md)\n* [开发](zh-cn/dev.md)"
  },
  {
    "path": "docs/zh-cn/advance.md",
    "content": "## 目录加密\n在需加密目录内新建 ```.passwd``` 文件（此项可修改），```type```为验证方式，```data```为验证内容。  \n例如：    \n```yaml\ntype: basic\ndata:\n  - 123456\n  - abcdef\n``` \n\n可使用密码```123456```，```abcdef```验证。\n\n***\n\n## 获取文件夹ID\n保持后台登录状态，回到首页列表，点击文件夹后的 '!' 按钮 可查看文件夹ID。   \n\n***\n\n## Nginx(Caddy)反向代理\n使用反代时，请添加以下配置。  \n\n#### Nginx  \n```ini \n  proxy_set_header Host  $host;\n  proxy_set_header X-Real-IP $remote_addr;\n  proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n  proxy_set_header X-Forwarded-Proto $scheme;\n\n  proxy_set_header Range $http_range;\n  proxy_set_header If-Range $http_if_range;\n  proxy_no_cache $http_range $http_if_range;\n```   \n如果使用上传功能，请调整 nginx 上传文件大小限制。   \n```\n  client_max_body_size 8000m;\n```   \n#### Caddy   \n```ini\n  header_upstream Host {host}\n  header_upstream X-Real-IP {remote}\n  header_upstream X-Forwarded-For {remote}\n  header_upstream X-Forwarded-Proto {scheme}\n```"
  },
  {
    "path": "docs/zh-cn/configuration.md",
    "content": "\n## 常规\n访问 ```http://localhost:33001/@manage```，填写口令即可进入后台管理。\n\n### 后台管理\n设置后台管理密码。默认 ```sharelist```。\n\n### 网站标题\n设置网站标题。\n\n### 目录索引\n默认启用。如果只提供下载功能，可禁用此项。\n\n### 展开单一挂载盘\n默认启用。如果只有一个挂载盘，将默认展开。\n\n### 允许下载\n默认启用。\n\n### 忽略路径\n设置禁止访问的目录/文件路径。支持 [gitignore](http://git-scm.com/docs/gitignore) 表达式\n\n### 加密文件名\n默认```.passwd```，修改此项自定义加密文件名。\n\n### WebDAV 路径\nWebDAV路径。\n\n### WebDAV 代理\n默认启用。\n\n### WebDAV 用户\n默认 ```admin```。\n\n### WebDAV 密码\n默认 ```sharelist```。\n\n### 自定义脚本\n默认主题支持自定义脚本。可用于插入统计脚本。\n\n### 自定义样式\n默认主题支持自定义样式。\n\n\n## 高级用法\n在需加密目录内新建 ```.passwd``` 文件（此项可修改），```type```为验证方式，```data```为验证内容。  \n例如：    \n```yaml\ntype: basic\ndata:\n  - 123456\n  - abcdef\n``` \n\n可使用密码```123456```，```abcdef```验证。\n\n***\n\n### 获取文件夹ID\n保持后台登录状态，回到首页列表，点击文件夹后的 '!' 按钮 可查看文件夹ID。   \n\n***\n\n### 反向代理\n使用反代时，请添加以下配置。  \n\n#### Nginx  \n```ini \n  proxy_set_header Host  $host;\n  proxy_set_header X-Real-IP $remote_addr;\n  proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n  proxy_set_header X-Forwarded-Proto $scheme;\n\n  proxy_set_header Range $http_range;\n  proxy_set_header If-Range $http_if_range;\n  proxy_no_cache $http_range $http_if_range;\n```   \n如果使用上传功能，请调整 nginx 上传文件大小限制。   \n```\n  client_max_body_size 8000m;\n```   \n#### Caddy   \n```ini\n  header_upstream Host {host}\n  header_upstream X-Real-IP {remote}\n  header_upstream X-Forwarded-For {remote}\n  header_upstream X-Forwarded-Proto {scheme}\n```"
  },
  {
    "path": "docs/zh-cn/dev.md",
    "content": "## 接口\n\n### 获取文件列表\n```\nPOST /api/drive/list\n```\n请求参数\n参数名 | 含义 | 是否必须\n-|-|-\npath|路径|是\n\n返回结果\n名称 | 类型 | 说明\n-|-|-\nid|string|目录唯一ID\nfiles|Array\\<File\\>|目录内容集合\nfiles[0].id|string|文件ID\nfiles[0].name|string|文件名\nfiles[0].size|number|文件大小\nfiles[0].type|string|文件类型，目录(folder) 或 文件(file)\nfiles[0].ctime|number|文件创建时间戳\nfiles[0].mtime|number|文件修改时间戳\nfiles[0].path|string|文件相对路径\nfiles[0].download_url|string|文件下载地址\n\n\n### 获取配置文件\n```\nGET /api/setting\n```\n请求参数\n参数名 | 含义 | 是否必须\n-|-|-\ntoken|后台口令|是\n\n返回结果\n名称 | 类型 | 说明\n-|-|-\ndata|object|\ndata.token|string|后台口令\ndata.title|string|网站title\ndata.theme|string|当前主题\ndata.theme_options|Array\\<string\\>|可用主题\ndata.index_enable|boolean|是否启用目录索引\ndata.ignores|Array\\<string\\>|忽略路径\ndata.acl_file|string|加密文件对应名称\ndata.webdav_path|string|WebDAV 路径\ndata.webdav_user|string|WebDAV 用户名\ndata.webdav_pass|string|WebDAV 密码\ndata.webdav_proxy|boolean|是否启用WebDAV代理\n\n### 重启应用\n```\nPUT /api/reload?token={token}\n```\n\n### 清除缓存\n```\nPUT /api/cache/clear?token={token}\n```\n\n\n### 导出配置\n```\nGET /api/setting?raw=true&token={token}\n```\n\n# 主题开发\nSharelist 自定义主题存放路径为 ```cache/theme``` 目录。\n\n# 插件开发\nSharelist 自定义插件路径存放路径为 ```cache/plugins``` 目录。\n\n### 资源命名\nsharelist使用统一的资源命名方式,即 ```protocol://key/fid```。\n字段|含义\n-|-\nprotocol|挂载协议，仅用于区分挂载类型\nkey|挂载标记，用于标记挂载实例\nfid|资源的内部ID\n\n### 辅助函数\n插件在```onReady```时将接收```app```，```app```包含一些列辅助开发函数\n函数名|用途\n-|-\nrequest|用于完成 HTTP 请求\nerror|抛异常\ncreateReadStream|流读取函数\ngetDrives|获取挂载信息\nsaveDrive|保存挂载信息\n\n### 插件功能\n完整的插件应提供\n方法|描述|必须功能\n-|-|-\nlist|列目录|✓\nget|获取文件信息|✓\nmkdir|新建目录\nrm|删除\nrename|重命名\nmv|移动\nupload|上传\n\n\n### 简单示例\n```js\nmodule.exports = class Driver {\n  constructor() {\n    this.name = 'TestPlugin'\n    this.label = 'TestPlugin'\n\n    //可被挂载\n    this.mountable = true\n\n    //不使用缓存\n    this.cache = false\n\n    //版本\n    this.version = '1.0'\n\n    //协议名\n    this.protocol = 'test'\n\n    //挂载需要的参数\n    this.guide = [\n      { key: 'path', label: '目录地址', type: 'string', required: true },\n    ]\n  },\n\n  //初始化完毕，接收全局app 实例\n  onReady(app){\n    this.app = app\n  },\n  list(id){\n\n  },\n  get(id){\n\n  },\n  mkdir(parent_id,name){\n\n  },\n  rm(id){\n\n  },\n  mv(){\n\n  }\n}\n```"
  },
  {
    "path": "docs/zh-cn/installation.md",
    "content": "# 安装\nSharelist支持多种安装方式。\n\n## Docker\n```bash\ndocker run -d -v /etc/sharelist:/sharelist/cache -p 33001:33001 --name=\"sharelist\" reruin/sharelist:next\n```\n\n## 二进制版\n[release](https://github.com/reruin/sharelist/releases)下载二进制版。\n\n\n## Heroku\n请 Fork [sharelist-heroku](https://github.com/reruin/sharelist-heroku/tree/next)，然后在个人仓库下点 Deploy to HeroKu。\n\n\n安装完成首次访问 `http://localhost:33001`地址，将进入默认界面。访问`http://localhost:33001/@manage` 进入后台管理，默认口令为 ```sharelist```。"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"sharelist-monorepo\",\n  \"private\": true,\n  \"workspaces\": [\n    \"packages/*\"\n  ],\n  \"engines\": {\n    \"node\": \">=16.0.0\"\n  },\n  \"scripts\": {\n    \"dev\": \"cd packages/sharelist && yarn dev\",\n    \"build-server\": \"cd packages/sharelist && yarn pkg\",\n    \"dev-web\": \"cd packages/sharelist-web && yarn dev\",\n    \"dev-manage\": \"cd packages/sharelist-manage && yarn dev\",\n    \"build-manage\": \"cd packages/sharelist-manage && yarn build\",\n    \"build-web\": \"cd packages/sharelist-web && yarn build\",\n    \"build\": \"node scripts/build.js\",\n    \"lint\": \"eslint --ext .ts packages/*/src/**.ts\",\n    \"format\": \"prettier --write --parser typescript \\\"packages/**/*.ts?(x)\\\"\",\n    \"release\": \"node scripts/release.js\",\n    \"ci\": \"cd packages/sharelist && yarn release\",\n    \"changelog\": \"node scripts/changelog.js\",\n    \"pkg\": \"cd packages/sharelist && yarn pkg-local\"\n  },\n  \"devDependencies\": {\n    \"@babel/cli\": \"^7.16.8\",\n    \"@babel/core\": \"^7.16.7\",\n    \"@babel/plugin-proposal-optional-chaining\": \"^7.16.7\",\n    \"chalk\": \"^4.1.2\",\n    \"conventional-changelog-cli\": \"^2.1.1\",\n    \"cross-env\": \"^7.0.3\",\n    \"eslint\": \"^7.28.0\",\n    \"execa\": \"^5.1.1\",\n    \"minimist\": \"^1.2.5\",\n    \"nodemon\": \"^2.x\",\n    \"pkg\": \"^5.3.1\",\n    \"prompts\": \"^2.4.1\",\n    \"semver\": \"^7.3.5\"\n  }\n}"
  },
  {
    "path": "packages/sharelist/CHANGELOG.md",
    "content": "## [0.4.4](https://github.com/reruin/sharelist/compare/v0.4.3...v0.4.4) (2025-06-13)\n\n\n\n## [0.4.3](https://github.com/reruin/sharelist/compare/v0.4.2...v0.4.3) (2025-06-12)\n\n\n\n## [0.4.2](https://github.com/reruin/sharelist/compare/v0.4.1...v0.4.2) (2025-06-12)\n\n\n\n## 0.4.1 (2025-06-12)\n\n\n### Bug Fixes\n\n* fix some bugs ([23cd7f9](https://github.com/reruin/sharelist/commit/23cd7f99d5af27b08cba08109c2107a3a6d04089))\n* **guide:** fix wrong function call ([01e5a78](https://github.com/reruin/sharelist/commit/01e5a78f54b59ddcb8ac04b2d1c1297710f5946d))\n* **plugin:** baidu guide([#564](https://github.com/reruin/sharelist/issues/564)) ([219b524](https://github.com/reruin/sharelist/commit/219b524e0604751fae84eca3c65b350a479817eb))\n* **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))\n\n\n### Features\n\n* support custom script/css ([8af3902](https://github.com/reruin/sharelist/commit/8af390226a63373477d597a6f6b231e1c34f6cfa))\n* **webdav:** auth ([2499979](https://github.com/reruin/sharelist/commit/2499979dcd8392864f505268411dbce15cd810dc))\n* **webdav:** support option configuration of webdav ([d667a83](https://github.com/reruin/sharelist/commit/d667a830f8008a857d6ae827213d76992edbe306))\n\n\n\n## [0.3.15](https://github.com/reruin/sharelist/compare/v0.3.14...v0.3.15) (2022-01-05)\n\n\n\n## [0.3.14](https://github.com/reruin/sharelist/compare/v0.3.13...v0.3.14) (2022-01-05)\n\n\n\n## [0.3.13](https://github.com/reruin/sharelist/compare/v0.3.12...v0.3.13) (2022-01-04)\n\n\n### Bug Fixes\n\n* fix some bugs ([23cd7f9](https://github.com/reruin/sharelist/commit/23cd7f99d5af27b08cba08109c2107a3a6d04089))\n\n\n\n## [0.3.12](https://github.com/reruin/sharelist/compare/v0.3.11...v0.3.12) (2021-12-29)\n\n\n### Bug Fixes\n\n* **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))\n\n\n### Features\n\n* support custom script/css ([8af3902](https://github.com/reruin/sharelist/commit/8af390226a63373477d597a6f6b231e1c34f6cfa))\n\n\n\n## [0.3.11](https://github.com/reruin/sharelist/compare/v0.3.10...v0.3.11) (2021-11-05)\n\n\n\n## [0.3.10](https://github.com/reruin/sharelist/compare/v0.3.9...v0.3.10) (2021-11-01)\n\n\n\n## [0.3.9](https://github.com/reruin/sharelist/compare/v0.3.8...v0.3.9) (2021-10-23)\n\n\n\n## [0.3.8](https://github.com/reruin/sharelist/compare/v0.3.7...v0.3.8) (2021-10-19)\n\n\n\n## [0.3.7](https://github.com/reruin/sharelist/compare/v0.3.6...v0.3.7) (2021-10-14)\n\n\n### Features\n\n* **webdav:** support option configuration of webdav ([d667a83](https://github.com/reruin/sharelist/commit/d667a830f8008a857d6ae827213d76992edbe306))\n\n\n\n## [0.3.6](https://github.com/reruin/sharelist/compare/v0.3.5...v0.3.6) (2021-10-12)\n\n\n\n## [0.3.6](https://github.com/reruin/sharelist/compare/v0.3.5...v0.3.6) (2021-10-12)\n\n\n\n## [0.3.5](https://github.com/reruin/sharelist/compare/v0.3.4...v0.3.5) (2021-10-11)\n\n\n\n## [0.3.4](https://github.com/reruin/sharelist/compare/v0.3.3...v0.3.4) (2021-10-10)\n\n\n\n## [0.3.3](https://github.com/reruin/sharelist/compare/v0.3.2...v0.3.3) (2021-10-10)\n\n\n\n## [0.3.2](https://github.com/reruin/sharelist/compare/v0.3.1...v0.3.2) (2021-10-09)\n\n\n### Features\n\n* **webdav:** auth ([2499979](https://github.com/reruin/sharelist/commit/2499979dcd8392864f505268411dbce15cd810dc))\n\n\n\n## [0.3.1](https://github.com/reruin/sharelist/compare/v0.3.0...v0.3.1) (2021-10-09)\n\n\n\n# [0.3.0](https://github.com/reruin/sharelist/compare/v0.2.4...v0.3.0) (2021-10-09)\n\n\n\n## [0.2.3](https://github.com/reruin/sharelist/compare/v0.2.2...v0.2.3) (2021-08-21)\n\n\n### Bug Fixes\n\n* **guide:** fix wrong function call ([01e5a78](https://github.com/reruin/sharelist/commit/01e5a78f54b59ddcb8ac04b2d1c1297710f5946d))\n\n\n\n## [0.2.2](https://github.com/reruin/sharelist/compare/v0.2.1...v0.2.2) (2021-08-21)\n\n\n\n## 0.2.1 (2021-08-14)\n\n\n\n"
  },
  {
    "path": "packages/sharelist/Dockerfile",
    "content": "FROM node:20-alpine\nLABEL maintainer=reruin\n\nADD . /sharelist/\nWORKDIR /sharelist\nVOLUME /sharelist/cache\n\nRUN npm install --production\n\nENV HOST 0.0.0.0\nENV PORT 33001\n\nEXPOSE 33001\n\nCMD [\"npm\", \"start\"]"
  },
  {
    "path": "packages/sharelist/LICENSE",
    "content": "MIT License\n\nCopyright (c) 2018-present, Reruin and Sharelist contributors\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE."
  },
  {
    "path": "packages/sharelist/README.md",
    "content": "# ShareList Server"
  },
  {
    "path": "packages/sharelist/app/config.js",
    "content": "\nconst path = require('path')\n\nconst env = {\n  baseDir: path.join(__dirname, '../'),\n  cacheDir: path.join(!process.pkg ? __dirname : process.execPath, '../cache'),\n  pkg: !!process.pkg,\n  dev: process.env.NODE_ENV === 'dev'\n}\n\nmodule.exports = {\n  env,\n  cacheDir: env.cacheDir,\n  pluginDir: path.join(env.cacheDir, 'plugin'),\n  themeDir: [path.join(env.baseDir, 'web'), path.join(env.cacheDir, 'theme')],\n  manageDir: path.join(env.baseDir, 'manage'),\n  defaultPluginsFile: path.join(env.baseDir, 'plugins.json'),\n}"
  },
  {
    "path": "packages/sharelist/app/main.js",
    "content": "\nconst createSharelist = require('./modules/core')\nconst createWebDAV = require('./modules/webdav')\nconst createServer = require('./modules/server')\nconst createGuide = require('./modules/guide')\nconst config = require('./config')\nconst path = require('path')\nconst fs = require('fs')\n\nconst install = async () => {\n  let pluginDir = config.pluginDir\n  if (fs.existsSync(pluginDir) == false) {\n    fs.mkdirSync(pluginDir, { recursive: true })\n\n    try {\n      let plugins = JSON.parse(fs.readFileSync(config.defaultPluginsFile, 'utf-8'))\n\n      for (let i of plugins) {\n        console.log(i.name)\n        let filename = path.join(pluginDir, `${i.namespace}.js`)\n        fs.writeFileSync(filename, i.script)\n      }\n    } catch (e) {\n      console.log('Extract default plugins error:', e)\n    }\n\n  }\n\n  fs.mkdirSync(path.join(config.cacheDir, 'theme'), { recursive: true })\n}\n\nconst bootstrap = async () => {\n  await install()\n\n  const sharelist = await createSharelist(config)\n\n  const server = createServer(sharelist, config)\n\n  server.use(createWebDAV(sharelist))\n\n  server.use(createGuide(sharelist))\n\n  server.start()\n\n  return { app: server.app, port: config.port }\n}\n\nmodule.exports = bootstrap"
  },
  {
    "path": "packages/sharelist/app/modules/command/index.js",
    "content": "/**\n * 文件名寻址操作函数\n * @param {*} v \n * @returns \n */\nconst parsePath = v => v.replace(/(^\\/|\\/$)/g, '').split('/').map(decodeURIComponent).filter(Boolean)\n\nconst getRange = (r, total) => {\n  if (r) {\n    let [, start, end] = r.match(/(\\d*)-(\\d*)/);\n    start = start ? parseInt(start) : 0\n    end = end ? parseInt(end) : total - 1\n\n    return { start, end }\n  }\n}\n\nconst createHeaders = (data, { maxage, immutable, range } = { maxage: 0, immutable: false }) => {\n  let fileSize = data.size\n  let fileName = data.name\n\n  let headers = {}\n\n  headers['Last-Modified'] = new Date(data.mtime).toUTCString()\n\n  if (range) {\n    let { start, end } = range\n    headers['Content-Range'] = `bytes ${start}-${end}/${fileSize}`\n    headers['Content-Length'] = end - start + 1\n    headers['Accept-Ranges'] = 'bytes'\n  } else {\n    header['Content-Range'] = `bytes 0-${fileSize - 1}/${fileSize}`\n    headers['Content-Length'] = fileSize\n  }\n\n  headers['Content-Disposition'] = `attachment;filename=${encodeURIComponent(fileName)}`\n  return headers\n}\n\nconst createCommand = (driver, { useProxy, baseUrl } = {}) => ({\n  async ls(path) {\n    let p = path.replace(/(^\\/|\\/$)/g, '')\n    let data = await driver.list({\n      paths: p ? p.split('/').map(decodeURIComponent) : [],\n      ignoreInterceptor: true,\n      query: { pagination: false }\n    })\n    if (data.files?.length > 0) {\n      data.files\n        .sort((a, b) => (a.type == 'folder' ? -1 : 1))\n        .forEach((i) => {\n          if (i.type == 'file') {\n            i.download_url = baseUrl + '/api/drive/get?download=true&id=' + encodeURIComponent(i.id)\n          }\n        })\n    }\n    return data\n  },\n  async stat(path) {\n    //console.log('stat', parsePath(path),)\n    try {\n      return await driver.stat(parsePath(path))\n    } catch (error) {\n      return { error }\n    }\n  },\n  async get(path, options) {\n    let data = await driver.get({ paths: parsePath(path) })\n    if (!options.reqHeaders) options.reqHeaders = {}\n    delete options.reqHeaders.connection\n    options.reqHeaders['user-agent'] = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Safari/537.36'\n    if (data && data.download_url && !data.extra.proxy && !useProxy()) {\n      return {\n        status: 302,\n        body: data.download_url\n      }\n    } else {\n      let range = getRange(options.reqHeaders.range, data.size) || { start: 0, end: data.size ? (data.size - 1) : '' }\n      let { stream, status, headers, enableRanges = false } = await driver.createReadStream(data.id, range)\n      let isReqRange = !!options.reqHeaders.range\n      if (stream) {\n        let options = enableRanges ? { range } : {}\n        let resHeaders = headers || createHeaders(data, options)\n        return {\n          body: stream,\n          status: status || (isReqRange && enableRanges ? 206 : 200),\n          headers: resHeaders\n        }\n      }\n    }\n  },\n  async upload(path, stream, { size }) {\n    stream.pause?.()\n\n    let paths = parsePath(path)\n\n    let name = paths.pop()\n\n    let data = await driver.stat(paths)\n\n    console.log(data)\n\n    let existData = await driver.stat([...paths, name])\n\n    if (existData) {\n      await driver.rm(existData.id)\n    }\n\n    if (!data.id) {\n      return { error: { code: 404 } }\n    }\n    let ret = await driver.upload(data.id, stream, { name, size })\n    if (!ret) {\n      return {\n        error: { code: 500 }\n      }\n    } else {\n      return ret\n    }\n\n  },\n  async mkdir(path) {\n    let paths = parsePath(path)\n    let name = paths.pop()\n    let parentData = await driver.stat(paths)\n    return await driver.mkdir(parentData.id, name)\n  },\n  async rm(path) {\n    let paths = parsePath(path)\n    let data = await driver.stat(paths)\n    return await driver.rm(data.id)\n  },\n  // /d/e/1.txt -> /d\n  async mv(path, destPath, copy) {\n    // The destination path can NOT be in the source path (include the same path)\n    // e.g. /a/b => /a/b , /a/b => /a/b/c , ( /a/b => /a/b1, /a/b => /a/b1/c\n    console.log('mv', path, destPath)\n    let paths = parsePath(path)\n    let destPaths = parsePath(destPath)\n\n    if ((destPaths.join('/') + '/').startsWith(paths.join('/' + '/'))) throw { code: 409 }\n\n    let data = await driver.stat(paths)\n\n    if (!data?.id) throw { code: 404 }\n\n    let srcId = data.id\n\n    let isSameParent = paths.length == destPaths.length && paths.slice(0, -1).join('/') == destPaths.slice(0, -1).join('/')\n\n    // rename\n    if (isSameParent) {\n      if (paths.slice(-1)[0] == destPaths.slice(-1)[0]) throw { code: 409 }\n\n      if (!copy) {\n        await driver.rename(srcId, destPaths.slice(-1)[0])\n      }\n    }\n\n\n    let dest = await driver.stat(destPaths)\n\n    let destId, destName, srcName = paths.pop()\n\n    //if destination exists\n    if (dest?.id) {\n      // destination must be a folder\n      if (dest.type != 'folder') throw { code: 409 }\n\n      destId = dest.id\n\n    }\n    // destination does not exist\n\n    else {\n      let destParent = await driver.stat({ paths: destPaths.slice(0, -1) })\n      //dest parent must be a folder\n      if (destParent?.type != 'folder') throw { code: 404 }\n\n      destName = destPaths.pop()\n      destId = destParent.id\n\n    }\n\n    let isSameDrive = await driver.isSameDrive(srcId, destId)\n\n    if (!isSameDrive) throw ({ code: 501 })\n\n    let options = { copy }\n\n    //rename\n    if (destName && srcName != destName) options.name = destName\n\n    await driver.mv(srcId, destId, options)\n\n    return { status: 201 }\n  }\n\n})\n\n\nmodule.exports = (app) => {\n\n  app.addSingleton('command', () => {\n    console.log(';>>>command')\n\n    let ret = createCommand(app.sharelist.driver)\n    console.log(ret)\n    return ret\n  })\n}"
  },
  {
    "path": "packages/sharelist/app/modules/core/cache.js",
    "content": "const createDB = require('./db')\n\nmodule.exports = (path) => {\n  let { data, save } = createDB(path)\n\n  const get = (id) => {\n    if (id === undefined) return data\n    let ret = data[id]\n    if (ret) {\n      if (Date.now() > ret.expired_at) {\n        delete data[id]\n      } else {\n        return ret.data\n      }\n    }\n  }\n\n  const set = (id, value, max_age) => {\n    data[id] = { data: value, expired_at: Date.now() + max_age }\n    save()\n    return value\n  }\n\n  const clear = (key) => {\n    if (key) {\n      delete data[key]\n    } else {\n      for (let key in data) {\n        delete data[key]\n      }\n    }\n    save()\n  }\n\n  const remove = (key) => {\n    delete data[key]\n    save()\n  }\n\n  // remove expired data\n  for (let key in data) {\n    get(key, data)\n    save()\n  }\n\n  return {\n    get, set, remove, clear, save\n  }\n}"
  },
  {
    "path": "packages/sharelist/app/modules/core/config.js",
    "content": "const createDB = require('./db')\n\nconst qs = require('querystringify')\n\nconst { URL } = require('url')\n\nconst { watch } = require('@vue-reactivity/watch')\n\nconst { reactive } = require('@vue/reactivity')\n\nconst { nanoid } = require('nanoid')\n\nconst defaultConfig = {\n  token: 'sharelist',\n\n  proxy_enable: false,\n\n  index_enable: true,\n\n  expand_single_disk: true,\n\n  // fast_mode: true,\n\n  max_age_dir: 15 * 60 * 1000,\n\n  max_age_file: 5 * 60 * 1000,\n\n  // max_age_download: 0,\n\n  theme: 'default',\n\n  ignores: [],\n\n  acl_file: '.passwd',\n\n  max_age_download_sign: 'sl_' + Date.now(),\n\n  anonymous_upload_enable: false,\n\n  anonymous_download_enable: true,\n\n  webdav_path: '',\n  //代理路径\n  proxy_paths: [],\n\n  proxy_server: '',\n\n  proxy_override_content_type: 1,\n  webdav_proxy: true,\n\n  webdav_user: 'admin',\n\n  webdav_pass: 'sharelist',\n\n  ocr_server: '',\n\n  drives: [],\n\n  manage_path: '/@manage/',\n\n  proxy_url: '',\n\n  plugin_source: 'unpkg',\n\n  default_ua: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/106.0.0.0 Safari/537.36'\n}\n\nexports.openConfigKey = ['manage_path']\n\nexports.defaultConfigKey = Object.keys(defaultConfig)\n\nconst decode = (p) => {\n  let hasProtocol = p.includes('://')\n  if (!hasProtocol) p = 'sharelist://' + p\n  let data = new URL(p)\n  let protocol = data.protocol.replace(':', '')\n  let path = decodeURIComponent(data.pathname || '')\n  let result = {\n    protocol,\n    key: data.host,\n  }\n\n  if (path) path = path.replace(/^\\/+/, '')\n  if (hasProtocol) result.protocol = data.protocol.split(':')[0]\n  const config = { root: path }\n  for (const [key, value] of data.searchParams) {\n    config[key] = value\n  }\n  result.config = config\n\n  return result\n}\n\n\nconst encode = (data) => {\n  let { protocol, config: { key, root, ...options } } = data\n\n  let pathname = root === undefined || root == '' ? '/' : '/' + root\n  let search = qs.stringify(options)\n  if (search) search = '?' + search\n  let ret = `${key || ''}${pathname}${search}`\n  if (protocol) ret = `${protocol}://${ret}`\n\n  return ret\n}\n\nexports.createConfig = (path) => {\n  let { data, save } = createDB(path, { autoSave: false }, { ...defaultConfig })\n\n  let proxyData = reactive({ ...data, drives: data.drives.map(i => ({ name: i.name, id: i.id || nanoid(), ...decode(i.path) })) })\n\n  watch(proxyData, (nv) => {\n    Object.keys(nv).forEach((key) => {\n      if (key == 'drives') {\n        data.drives = nv.drives.map(i => ({ name: i.name, id: i.id, path: encode(i) }))\n      } else {\n        data[key] = nv[key]\n      }\n    })\n    save()\n  }, { deep: true })\n\n  return proxyData\n}\n"
  },
  {
    "path": "packages/sharelist/app/modules/core/db.js",
    "content": "const path = require('path')\n\nconst fs = require('fs')\n\nconst writeFileAtomic = require('write-file-atomic')\n\nconst mkdir = function (p) {\n  if (fs.existsSync(p) == false) {\n    mkdir(path.dirname(p))\n    fs.mkdirSync(p)\n  }\n}\n\nconst base64 = {\n  encode: (v) => Buffer.from(v).toString('base64'),\n  decode: (v) => Buffer.from(v, 'base64').toString(),\n}\n\nconst merge = function (dst, src) {\n  for (let key in src) {\n    if (!(key in dst)) {\n      dst[key] = src[key]\n      continue\n    } else {\n      if (typeof src[key] == 'object' || Array.isArray(src[key])) {\n        merge(dst[key], src[key])\n      } else {\n        dst[key] = src[key]\n      }\n    }\n  }\n  return dst\n}\n\nconst getData = (path, options = {}) => {\n  try {\n    let data = fs.readFileSync(path, 'utf8')\n\n    if (options.base64) {\n      data = base64.decode(data)\n    }\n\n    return JSON.parse(data)\n  } catch (error) {\n    //if it doesn't exist or permission error\n    if (error.code === 'ENOENT' || error.code === 'EACCES') {\n      return {}\n    }\n\n    //invalid JSON\n    if (error.name === 'SyntaxError') {\n      writeFileAtomic.sync(path, '')\n      return {}\n    }\n\n    throw error\n  }\n}\n\nconst setData = (filepath, { base64 } = {}, value) => {\n  try {\n    mkdir(path.dirname(filepath))\n\n    value = JSON.stringify(value)\n    if (base64) {\n      value = base64.encode(value)\n    }\n\n    writeFileAtomic.sync(filepath, value)\n  } catch (error) {\n    throw error;\n  }\n}\n\nconst createdb = (path, { autoSave, debug } = {}, defaults = {}) => {\n  let data = merge(defaults, getData(path))\n\n  let handler\n  const save = (nv) => {\n    if (debug) console.log('db changed', nv)\n    if (handler) {\n      clearImmediate(handler)\n    }\n\n    handler = setImmediate(() => {\n      setData(path, {}, nv || data)\n      handler = null\n    })\n  }\n\n\n  return { data, save }\n}\n\nmodule.exports = createdb\n"
  },
  {
    "path": "packages/sharelist/app/modules/core/http.js",
    "content": "class http {\n  static options = {\n    protocol: \"http\",\n    singleton: true,\n    mountable: false\n  }\n\n  constructor(app, config) {\n    this.app = app\n    this.config = config\n  }\n\n  pwd(id) {\n    let name = id.split('/').pop()\n    return [{ id, name, type: 'file' }]\n  }\n\n  async get(id) {\n    let url = id\n    let size = await this.getFileSize(url)\n    let name = url.split('/').pop()\n    return {\n      id,\n      size,\n      name,\n      type: 'file',\n      ctime: Date.now(),\n      mtime: Date.now(),\n      download_url: url,\n      extra: {}\n    }\n  }\n\n  async list(id) {\n\n  }\n\n  async getFileSize(url, headers = {}) {\n    try {\n      let controller = new AbortController()\n      let { headers: resHeaders } = await this.app.request(url, {\n        signal: controller.signal,\n        headers,\n        responseType: 'stream'\n      })\n      controller.abort()\n      let size = +resHeaders['content-length']\n      return size\n    } catch (e) {\n      console.log(e)\n      return null\n    }\n\n  }\n\n  // async createReadStream({ id, options = {} } = {}) {\n  //   let url = id\n  //   let size = await this.getFileSize(url)\n  //   let readstream = request({ url: decodeURIComponent(url), method: 'get' })\n  //   return wrapReadableStream(readstream, { size })\n  // }\n}\n\nclass https extends http {\n  static options = {\n    protocol: \"https\",\n    singleton: true,\n    mountable: false\n  }\n  constructor(app, config) {\n    super(app, config)\n    this.protocol = 'https'\n  }\n}\n\nexports.HTTPDriver = {\n  name: 'http', hash: 'sharelist.http', module: { driver: http }\n}\nexports.HTTPSDriver = {\n  name: 'https', hash: 'sharelist.https', module: { driver: https }\n}"
  },
  {
    "path": "packages/sharelist/app/modules/core/index.js",
    "content": "const path = require('path')\n\nconst { nanoid } = require('nanoid')\n\nconst { request, createPluginLoader } = require('@sharelist/core')\n\nconst createCache = require('./cache')\n\nconst { createConfig, defaultConfigKey } = require('./config')\n\nconst utils = require('./utils')\n\nconst { createPlugin } = require('./plugin')\n\nconst createTheme = require('./theme')\n\nconst { createTransfer } = require('./task')\n\nmodule.exports = async (options) => {\n  const config = createConfig(path.join(options.cacheDir, 'config.json'))\n\n  const cache = createCache(path.join(options.cacheDir, 'cache.json'))\n\n  const plugin = createPlugin({\n    pluginSource: () => config.plugin_source,\n    pluginDir: path.join(options.cacheDir, 'plugin'),\n    onUpdate(id) {\n      //driver has been changed.\n      driver.load(plugin.load())\n    },\n    onRemove(hash) {\n      console.log('onremove', hash)\n      driver.unload([hash])\n    }\n  })\n\n  // plugins manager\n  const driver = await createPluginLoader({ config, plugins: plugin.load(), cache })\n\n  const theme = createTheme(options.themeDir)\n\n  const transfer = createTransfer(options.cacheDir, driver, request)\n\n  const files = (...rest) => utils.getFiles(driver, config, ...rest)\n  const file = (...rest) => utils.getFile(driver, config, ...rest)\n\n  const getDownloadUrl = (...rest) => utils.getDownloadUrl(driver, config, ...rest)\n  const getPathById = (...rest) => utils.getPathById(driver, config, ...rest)\n  const getContent = (...rest) => driver.createReadStream(...rest)\n\n\n  const getThemeFile = (file) => theme.getFile(file, config.theme)\n\n  const checkAccess = (token) => config.token && config.token === token\n\n  const reload = async () => {\n    await driver.load(plugin.load())\n  }\n\n  const setDrives = (data) => {\n\n    for (let i of data) {\n      if (!i.id) {\n        i.id = nanoid()\n      }\n    }\n    config.drives = data\n    driver.loadConfig()\n\n  }\n\n  //if config update/create\n  // watch(\n  //   () => config.drives,\n  //   (nv, ov) => {\n  //     console.log('drive config changed')\n  //     driver.loadConfig()\n  //   },\n  // )\n\n  return {\n    config,\n    cache,\n    driver,\n    defaultConfigKey,\n    plugin,\n    theme,\n    files,\n    file,\n    getPathById,\n    getContent,\n    getDownloadUrl,\n    getThemeFile,\n    transfer,\n    checkAccess,\n    request,\n    reload,\n    setDrives\n  }\n}\n"
  },
  {
    "path": "packages/sharelist/app/modules/core/plugin.js",
    "content": "const { nanoid } = require('nanoid')\n\nconst crypto = require('crypto')\n\nconst fs = require('fs')\n\nconst path = require('path')\n\nconst compareSemver = require('semver-compare-lite')\n\nconst md5 = content => crypto.createHash('md5').update(content).digest(\"hex\")\n\nconst { request } = require('@sharelist/core')\n\nconst { HTTPDriver, HTTPSDriver } = require('./http')\n\nconst plugins_mirror = {\n  'github': 'https://raw.githubusercontent.com/linkdrive/plugins/master',\n  'unpkg': 'https://unpkg.com/@linkdrive/plugins',\n  'eleme': 'https://npm.elemecdn.com/@linkdrive/plugins'\n}\n\nconst parseMeta = (filepath, isPath = true, dir) => {\n  const content = isPath ? fs.readFileSync(filepath, 'utf-8') : filepath\n  const metaContent = content.match(/(?<===Sharelist==)[\\w\\W]+(?===\\/Sharelist==)/)?.[0]\n  const meta = {\n    hash: md5(content)\n  }\n\n  if (metaContent) {\n    let pairs = metaContent.split(/[\\r\\n]/).filter(Boolean).map(i => i.match(/(?<=@)([^\\s]+?)\\s+(.*)/)).filter(Boolean)\n    for (let i of pairs) {\n      meta[i[1]] = i[2]\n    }\n  }\n\n  if (isPath) {\n    if (!meta.name) meta.name = path.basename(filepath).replace(/\\.js$/i, '')\n    // if(meta.name)\n    meta.id = md5(meta.name || path)\n    meta.path = filepath\n  } else {\n    let name = nanoid()\n    meta.id = md5(meta.name || name)\n    if (dir) meta.path = path.join(dir, name + '.js')\n  }\n\n  return meta\n\n}\n\nconst clearNodeCache = (id) => {\n\n  delete require.cache[id]\n\n  Object.keys(module.constructor._pathCache).forEach(function (cacheKey) {\n    if (cacheKey.indexOf(id) > -1) {\n      delete module.constructor._pathCache[cacheKey];\n    }\n  });\n\n}\n\nclass Plugin {\n  constructor(options) {\n    this.options = options\n    this.plugins = {}\n    this.inited = false\n  }\n\n  /**\n   * extract plugin meta\n   */\n  scanMeta() {\n    let { options, plugins } = this\n    let dirs = [options.pluginDir].filter(Boolean)\n    let newLoad = []\n    for (let dir of dirs) {\n      try {\n        let files = fs.readdirSync(dir)\n        for (let i of files) {\n          let filepath = path.join(dir, i)\n          let file = fs.statSync(filepath)\n          if (file.isFile() && /\\.js$/i.test(i)) {\n            let meta = parseMeta(filepath)\n            //plugin content has changed\n\n            //test\n            // meta.hash = Math.random() + ''\n            if (plugins[meta.id]?.hash != meta.hash) {\n              newLoad.push(meta)\n              plugins[meta.id] = meta\n            }\n          }\n        }\n      } catch (e) {\n        console.log(e)\n      }\n    }\n\n    this.inited = true\n\n    return newLoad\n  }\n\n  load() {\n    console.log('load plugins')\n    const plugins = this.scanMeta()\n    const ret = []\n    for (let i of plugins) {\n      let item = { name: i.name, hash: i.hash }\n      try {\n        clearNodeCache(require.resolve(i.path))\n        item.module = require(i.path)\n        // item.code = fs.readFileSync(i.path)\n        ret.push(item)\n      } catch (e) {\n        console.log(e)\n      }\n    }\n\n    return [HTTPDriver, HTTPSDriver, ...ret]\n  }\n\n  get(id) {\n    if (!this.inited) {\n      this.scanMeta()\n    }\n\n    if (id) {\n      return this.plugins[id]\n    } else {\n      return Object.values(this.plugins)\n    }\n  }\n\n  set(id, data) {\n    let meta = this.plugins[id], filepath\n    let { pluginDir } = this.options\n    if (!meta) {\n      const newMeta = parseMeta(data, false, pluginDir)\n      if (!newMeta.name) {\n        throw new Error('invalid script / 脚本无效 - 缺少名称')\n      }\n      meta = newMeta\n      filepath = newMeta.path\n    } else {\n      filepath = meta.path\n    }\n    fs.writeFileSync(filepath, data)\n    this.options?.onUpdate(meta)\n  }\n\n  async createFromUrl(url) {\n    console.log('Download plugin:', url)\n    let res = await request(url, {\n      responseType: 'text'\n    })\n    this.set(null, res.data)\n  }\n\n  async getFromStore() {\n    const plugins = this.get()\n    console.log('SOURCE: ' + plugins_mirror[this.options.pluginSource()])\n    let { data } = await request(plugins_mirror[this.options.pluginSource()] + '/list.json?t=' + Date.now(), {\n      // responseType: 'text'\n      // headers: {\n      //   'cache-control': 'no-cache',\n      //   'pragma': 'no-cache'\n      // }\n    })\n    // data = JSON.parse(data)\n    // console.log(data)\n\n    data.forEach(i => {\n      if (i.updateURL.indexOf('raw.githubusercontent.com') >= 0) {\n        if (i.namespace) {\n          if (plugins.find(j => j.namespace == i.namespace)) {\n            i.installed = true\n          }\n        }\n        i.updateURL = this.replaceSource(i.updateURL)//.replace('/master', '@master')\n\n        if (i.namespace?.startsWith('http') && !i.supportURL) {\n          i.supportURL = i.namespace\n        }\n      }\n    })\n    return data\n  }\n\n  remove(id) {\n    let meta = this.plugins[id]\n    if (meta.path) {\n      fs.unlinkSync(meta.path)\n      delete this.plugins[id]\n      this.options?.onRemove(meta.hash)\n    } else {\n      throw new Error('invalid script / 脚本无效')\n    }\n  }\n\n  replaceSource(url) {\n    console.log(url, plugins_mirror.github)\n    return url.replace(plugins_mirror.github, plugins_mirror[this.options.pluginSource()])\n  }\n\n  async upgrade(id) {\n    let meta = this.plugins[id]\n    if (meta?.id && meta.updateURL) {\n      console.log('Upgrade Plugin:', meta.updateURL, this.replaceSource(meta.updateURL))\n      let { data } = await request(this.replaceSource(meta.updateURL), { responseType: 'text' })\n      if (!data) {\n        throw new Error('没有获取到有效内容 / Invalid content')\n      }\n\n      let newmeta = parseMeta(data, false)\n      if (meta.version && compareSemver(newmeta.version, meta.version) == 1) {\n        console.log('[UPDATE PLUGIN]' + meta.name + ` ${meta.version} --> ${newmeta.version}`)\n        this.set(meta.id, data)\n      } else {\n        throw new Error('已是最新版本')\n      }\n    } else {\n      throw new Error('无法完成更新: 插件不存在 或 未设置更新地址.Unable to complete update: the plugin does not set the update url')\n    }\n  }\n\n  getSources() {\n    return Object.keys(plugins_mirror)\n  }\n}\n\nexports.createPlugin = (options) => {\n  return new Plugin(options)\n}\n\nexports.pluginConfigKey = ['search', 'hash', 'isRoot', 'multiThreading', 'readonly']\n"
  },
  {
    "path": "packages/sharelist/app/modules/core/task.js",
    "content": "\n/**\n * type ITask = {\n *   total:number,\n *   success:number,\n *   error:number,\n *   totalSize:number,\n *   status: STATUS\n * } \n */\nconst path = require('path')\nconst { PassThrough, pipeline } = require('stream')\nconst fs = require('fs')\nconst createDb = require('./db')\nconst { md5, isUrl } = require('./utils')\n\nconst createAsyncCtrl = (beforeReject) => {\n  let error\n  let controller = new AbortController()\n\n  let promise = new Promise((_, reject) => {\n    error = (e) => {\n      controller.abort()\n      reject(e)\n    }\n  })\n  let run = (p) => Promise.race([p, promise])\n  return { error, run, signal: controller.signal }\n}\n\nconst STATUS = {\n  INIT: 1, //1 正在生成任务(解析文件)\n  INIT_ERROR: 2, //2 解析文件过程发生错误\n  PROGRESS: 3, //3 正在复制\n  SUCCESS: 4, //4 操作完成\n  DONE_WITH_ERROR: 5,//5.操作完成 但发生部分完成\n  ERROR: 6,//6 失败\n  PAUSE: 7,//已暂停\n}\n\nconst genKey = (input) => md5(input).substring(8, 24)\n\nconst parsePaths = v => v.replace(/(^\\/|\\/$)/g, '').split('/').map(decodeURIComponent).filter(Boolean)\n\nconst sleep = (t = 0) => new Promise((r) => { setTimeout(r, t) })\n\nconst statsStream = (stream, cb) => {\n\n  let loaded = 0, lastTime = Date.now(), chunkloaded = 0\n  stream.on('data', (chunk) => {\n    loaded += chunk.length\n    chunkloaded += chunk.length\n    let timePass = Date.now() - lastTime\n    if (timePass >= 1000) {\n      cb(loaded, chunkloaded * 1000 / timePass)\n      chunkloaded = 0\n      lastTime = Date.now()\n    }\n  })\n\n}\n\nconst ignoreStream = (readStream, length) => {\n  let stream = new PassThrough()\n  let count = 0\n\n  const onData = (chunk) => {\n    count += chunk.length\n    if (count >= length) {\n      readStream.off('data', onData)\n      stream.write(chunk.slice(count - length))\n      readStream.pipe(stream)\n    }\n  }\n  readStream.on('data', onData)\n\n  readStream.resume()\n\n  return stream\n}\nexports.createTransfer = (cacheDir, driver, request) => {\n  const TAG = 'transfer'\n\n  const basePath = path.join(cacheDir, TAG)\n  if (fs.existsSync(basePath) == false) {\n    fs.mkdirSync(basePath)\n  }\n\n  const { data: tasksMap } = createDb(path.join(basePath, `list.json`), { autoSave: true, debug: false })\n\n  const worker = {}\n\n  //取得目标目录，没有则创建\n  const changeDir = async (dest) => {\n    let paths = dest.split('/'), nonExistDir = []\n    let parent\n    while (true) {\n      try {\n        parent = await driver.stat(paths)\n        if (parent) {\n          break;\n        } else {\n          nonExistDir.unshift(paths.pop())\n        }\n      } catch (e) {\n        console.log('stat error', e)\n        nonExistDir.unshift(paths.pop())\n      }\n    }\n\n    for (let i = 0; i < nonExistDir.length; i++) {\n      parent = await driver.mkdir(parent.id, nonExistDir[i])\n    }\n\n    return parent\n  }\n\n  const pickup = (file, parent) => {\n    let r = { id: file.id, name: file.name, size: file.size, dest: parent }\n    if (file.extra) {\n      if (file.extra.md5) {\n        // r.md5 = file.extra.md5\n        r.hash = { md5: file.extra.md5 }\n        // r.hash_type = 'md5'\n      }\n      else if (file.extra.sha1) {\n        // r.sha1 = file.extra.sha1\n        r.hash = { sha1: file.extra.sha1 }\n        // r.hash_type = 'sha1'\n      }\n    }\n    if (!r.size) r.lazy = true\n    return r\n  }\n\n  const create = async (src, dest, idMode = false, { conflictBehavior = 1, threadNum = 1 } = {}) => {\n    let srcPaths = [], destPaths = [], srcId, destId\n\n    if (idMode) {\n      srcId = src\n      destId = dest\n      srcPaths = (await driver.pwd(src)).map(i => i.name)\n      destPaths = (await driver.pwd(dest)).map(i => i.name)\n    } else {\n      srcPaths = parsePaths(src)\n      destPaths = parsePaths(dest)\n    }\n    console.log('move:', srcPaths, '==>', destPaths)\n\n    const taskId = genKey(srcPaths.join('/') + '->' + destPaths.join('/'))\n\n    //相同\n    if (tasksMap[taskId]) {\n      let task = tasksMap[taskId]\n      // console.log('The same task exists.', task)\n\n      //if (task.status == STATUS.SUCCESS) {\n      //  delete tasksMap[taskId]\n      //} else {\n      throw { message: 'Task already exists / 存在相同的任务', code: 429 }\n      //}\n    }\n\n    tasksMap[taskId] = {\n      id: taskId,\n      count: 0,\n      status: STATUS.INIT,\n      src: srcPaths.join('/'),\n      srcId,\n      dest: destPaths.join('/'),\n      destId,\n      size: 0,\n      error: 0,\n      success: 0,\n      loaded: 0,\n      currentLoaded: 0,\n      speed: 0,\n      index: 0,\n      inited: false,\n      currentDir: '',\n      conflictBehavior,\n      threadNum,\n      retry: 0\n    }\n\n    createParseTask(taskId)\n  }\n\n\n  //读取目录\n  const createParseTask = async (taskId) => {\n\n    //当前任务的文件列表\n    let { save: saveFiles } = createDb(path.join(basePath, `${taskId}.json`), { autoSave: false }, [])\n\n    //任务元数据\n    let { data: taskData } = createDb(path.join(basePath, `${taskId}_data.json`), { autoSave: true }, { error: [], retried: [] })\n\n    let files = []\n\n    let abortSignal = false\n\n    let srcPaths = tasksMap[taskId].src.split('/').filter(Boolean)\n\n    let srcId = tasksMap[taskId].srcId\n\n    let destPaths = tasksMap[taskId].dest.split('/').filter(Boolean)\n\n    worker[taskId] = {\n      files,\n      data: taskData,\n      cancel: function cancel() {\n        abortSignal = true\n      }\n    }\n\n    //准备阶段，读取源目录信息\n    try {\n      // console.log('get', srcId, srcPaths)\n      let res = await driver.get({ id: srcId, paths: srcPaths }, { enableCache: false, more: true })\n\n      if (abortSignal) return\n      if (res.type == 'file') {\n        files.push(\n          pickup(res, destPaths.join('/'))\n        )\n      } else {\n        let dirs = [[res.id, [...destPaths, srcPaths.pop()].join('/')]]\n        while (dirs.length) {\n          let [id, destPath] = dirs.shift()\n\n          let children = (await driver.list({ id })).files\n\n          let subfiles = children.filter(i => i.type != 'folder').map((i) => pickup(i, destPath))\n\n          if (subfiles) {\n            files.push(\n              ...subfiles\n            )\n          }\n\n          dirs.push(...children.filter(i => i.type == 'folder').map(i => [i.id, destPath ? `${destPath}/${i.name}` : i.name]))\n\n          if (abortSignal) {\n            return\n          }\n        }\n      }\n\n      if (abortSignal) return\n\n      let fileTask = files.filter(i => !!i.id)\n      tasksMap[taskId].count = fileTask.length\n      tasksMap[taskId].size = fileTask.reduce((t, c) => t + c.size, 0)\n      tasksMap[taskId].inited = true\n      //tasksMap[taskId].status = STATUS.PROGRESS\n\n      //保存到文件\n      saveFiles(files)\n\n      createTransferTask(taskId)\n\n    } catch (e) {\n      console.trace(e)\n      tasksMap[taskId].status = STATUS.INIT_ERROR\n      tasksMap[taskId].message = e?.message\n    }\n  }\n\n\n  const createTransferTask = async (taskId) => {\n    let abortSignal = false\n    let currentDirData\n\n    const setState = (state) => {\n      let { data } = worker[taskId]\n      let { index, size: totalSize, count } = tasksMap[taskId]\n\n      let { $stats, ...rest } = state\n      let key = `${taskId}@${index}`\n\n      if (Object.keys(rest).length) {\n        data[key] = rest\n      }\n      // 单独处理状态报告\n      if ($stats) {\n        let { loaded, speed, total } = $stats\n        tasksMap[taskId].currentLoaded = loaded\n        tasksMap[taskId].progress = totalSize ? (loaded / totalSize) : ((index + loaded / total) / count)\n        tasksMap[taskId].speed = speed\n      }\n    }\n\n    const streamCreater = (id, ctrl, fileData) => async ({ start, end, state, supportStatsReport }) => {\n      let { stream: readStream, enableRanges } = await driver.createReadStream(id, {\n        start, end,\n        signal: ctrl.signal,\n      })\n\n      //! 如果传入流 无法进行续传，则等待该流到达指定位置\n      if (!enableRanges && start != 0) {\n        readStream = ignoreStream(readStream, start)\n      }\n\n      // 默认通过传入流进行间接计算 状态。但：\n      // 1. 当挂载源支持多线程时，应自行实现状态探针。\n      if (!supportStatsReport) {\n        statsStream(readStream, (loaded, speed) => {\n          setState({ '$stats': { loaded, speed, total: fileData } })\n        })\n      }\n\n      // work has been removed.\n      if (!worker[taskId]) {\n        return\n      }\n\n      readStream.once('error', ctrl.error)\n\n      if (tasksMap[taskId] && start) {\n        tasksMap[taskId].currentLoaded = start\n      }\n\n      if (state) setState(state)\n      return readStream\n    }\n\n\n    const next = async () => {\n      if (abortSignal) return\n\n      const { files, data } = worker[taskId]\n\n      let { index: taskIndex, conflictBehavior, retry, threadNum } = tasksMap[taskId]\n\n      //finish\n      if (taskIndex >= files.length || taskIndex == -1) {\n        tasksMap[taskId].status = tasksMap[taskId].error > 0 ? (tasksMap[taskId].error == tasksMap[taskId].count ? STATUS.ERROR : STATUS.DONE_WITH_ERROR) : STATUS.SUCCESS\n        return\n      }\n\n      console.log('currentIndex', taskIndex)\n\n      let file = files[taskIndex]\n\n      if (file.lazy) {\n        let res = await driver.get({ id: file.id }, { enableCache: false, more: true })\n        file = Object.assign({}, file, res)\n      }\n\n      let { id, name, size, dest, hash, hash_type } = file\n\n      //执行进入目录\n      if ((dest && dest != tasksMap[taskId].currentDir) || !currentDirData) {\n        currentDirData = await changeDir(dest)\n        tasksMap[taskId].currentDir = dest\n      }\n      //所有上传任务 都需要指明父目录\n      if (!currentDirData?.id) return\n\n      let key = `${taskId}@${taskIndex}`\n\n      const ctrl = createAsyncCtrl()\n\n      //当前任务的文件\n      tasksMap[taskId].current = name\n\n      worker[taskId].cancel = function cancel() {\n        abortSignal = true\n\n        ctrl.error({\n          type: 'aborted'\n        })\n      }\n\n      try {\n\n        await ctrl.run(driver.upload(currentDirData.id, streamCreater(id, ctrl, file), {\n          name, size, conflictBehavior, threadNum,\n\n          hash: hash || {}, hash_type,\n          signal: ctrl.signal,\n\n          state: data[key],\n          setState\n        }))\n\n        tasksMap[taskId].success++\n\n      } catch (e) {\n        console.log(e)\n        // 用户主动 abort 导致的异常 不计入异常文件\n        if (e.type == 'aborted') return\n\n        //连接重置 可能是网络问题，而非 serverside 服务异常\n        //if (e.type != 'ECONNRESET') {\n        //tasksMap[taskId].uploadId = ''\n        //}\n\n        if (!data.error.includes(taskIndex)) {\n          data.error.push(taskIndex)\n          tasksMap[taskId].error++\n        }\n\n        tasksMap[taskId].message = e.message\n      }\n\n      if (abortSignal) return\n\n      //计数\n      tasksMap[taskId].loaded += size\n      tasksMap[taskId].currentLoaded = 0\n\n      // 如果retry 模式，则跳转到下一个error 部分\n      if (tasksMap[taskId].retry) {\n        let errIdx = data.retried.indexOf(taskIndex)\n        if (errIdx >= 0) {\n          tasksMap[taskId].index = data.retried[errIdx + 1] || -1\n          data.retried.splice(errIdx, 1)\n        } else {\n          tasksMap[taskId].index = files.length\n        }\n      } else {\n        tasksMap[taskId].index = taskIndex + 1\n      }\n\n      delete data[key]\n\n      setTimeout(next, 0)\n    }\n\n\n    tasksMap[taskId].status = STATUS.PROGRESS\n\n    if (abortSignal) return\n\n    next()\n  }\n\n\n  const remove = (taskId) => {\n\n    if (tasksMap[taskId]) {\n      try {\n        // stopStream\n        worker[taskId]?.cancel?.()\n\n        //remove upload session \n        const { index, destId } = tasksMap[taskId]\n        const { data } = worker[taskId]\n\n        let key = `${taskId}@${index}`\n        console.log('clear session', destId, data[key])\n        driver?.clearSession?.(destId, data[key])\n\n        fs.rmSync(path.join(basePath, `${taskId}.json`), { force: false, recursive: true })\n        fs.rmSync(path.join(basePath, `${taskId}_data.json`), { force: false, recursive: true })\n      } catch (e) {\n\n      }\n      delete worker[taskId]\n      delete tasksMap[taskId]\n    } else {\n      return { error: { message: 'task does not exist' } }\n    }\n  }\n\n  /**\n   * 暂停\n   * @param {*} taskId \n   * @returns \n   */\n  const pause = (taskId) => {\n    if (!tasksMap[taskId]) {\n      return { error: { message: 'task does not exist' } }\n    }\n\n    //初始化 和 移动状态时可用\n    if (tasksMap[taskId].status != STATUS.PROGRESS && tasksMap[taskId].status != STATUS.INIT) {\n      throw { message: 'No need to pause in this state' }\n    }\n\n    try {\n      // stopStream\n      worker[taskId]?.cancel?.(true)\n\n    } catch (e) {\n      console.log(e)\n    }\n\n    tasksMap[taskId].status = STATUS.PAUSE\n\n  }\n\n  /**\n   * 启动/恢复\n   * @param {*} taskId \n   * @returns \n   */\n  const resume = async (taskId) => {\n    if (!tasksMap[taskId]) {\n      return { error: { message: 'task does not exist' } }\n    }\n\n    if (tasksMap[taskId].status == STATUS.PAUSE) {\n      //未读取完成\n      tasksMap[taskId].status = STATUS.INIT\n      if (!tasksMap[taskId].inited) {\n        createParseTask(taskId)\n      } else {\n        createTransferTask(taskId)\n      }\n    }\n  }\n\n  /**\n   * 读取进度文件，并将所有任务置于暂停状态\n   */\n  const init = () => {\n    for (let task of Object.values(tasksMap)) {\n      if (task.status == STATUS.PROGRESS) {\n        task.status = STATUS.PAUSE\n      }\n      let taskId = task.id\n      let { data: files } = createDb(path.join(basePath, `${taskId}.json`), { autoSave: true }, [])\n\n      tasksMap[taskId].currentDir = ''\n      //任务错误文件\n      let { data } = createDb(path.join(basePath, `${taskId}_data.json`), { autoSave: true }, { error: [], retried: [] })\n      worker[taskId] = {\n        files, data\n      }\n    }\n  }\n\n  const retry = (taskId) => {\n    if (!tasksMap[taskId]) {\n      throw { message: 'task does not exist' }\n    }\n\n    //重置files\n\n    const { files, data } = worker[taskId]\n    let successTotalSize = 0, successCount = 0\n    for (let i = 0; i < files.length; i++) {\n      let file = files[i]\n      if (!data.error.includes(i)) {\n        successCount++\n        successTotalSize += file.size\n      }\n    }\n\n    tasksMap[taskId].error = 0\n    tasksMap[taskId].status = STATUS.PROGRESS\n    tasksMap[taskId].currentDir = ''\n    tasksMap[taskId].loaded = successTotalSize\n    tasksMap[taskId].success = successCount\n    tasksMap[taskId].index = data.error[0]\n    tasksMap[taskId].retry = 1\n\n    data.retried = [...data.error]\n    data.error = []\n    createTransferTask(taskId)\n\n    return {}\n  }\n\n  const get = (taskId) => {\n    if (!tasksMap[taskId]) return { error: { message: 'task does not exist' } }\n\n    try {\n      // stopStream\n      let { ...ret } = tasksMap[taskId]\n      let files = worker[taskId].files\n      ret.error = worker[taskId].data.error\n      ret.files = files\n      return ret\n    } catch (e) {\n      console.log(e)\n    }\n  }\n\n  const getWorkers = () => {\n    return [...Object.values(tasksMap)].reverse()\n  }\n\n  init()\n\n  return { get, create, remove, pause, resume, all: getWorkers, retry }\n}"
  },
  {
    "path": "packages/sharelist/app/modules/core/theme.js",
    "content": "const fs = require('fs')\n\nconst path = require('path')\n\nmodule.exports = (themeDir = []) => {\n  let themes = []\n\n  const scanMeta = () => {\n    let dirs = themeDir.filter(Boolean)\n    let ret = {}\n    for (let dir of dirs) {\n      try {\n        let files = fs.readdirSync(dir)\n        for (let i of files) {\n          let filepath = path.join(dir, i)\n          let file = fs.statSync(filepath)\n\n          if (file.isDirectory()) {\n            let id = path.basename(i)\n            ret[id] = filepath\n          }\n        }\n      } catch (e) {\n        console.log(e)\n      }\n    }\n    themes = ret\n  }\n\n  const getFile = (file, theme) => {\n    const themePath = themes[theme || 'default']\n    if (themePath) {\n      return path.join(themePath, file)\n    } else {\n      return ''\n    }\n  }\n\n  const get = (id) => {\n    scanMeta()\n    return id ? themes[id] : Object.keys(themes)\n  }\n\n  scanMeta()\n\n  return {\n    getFile,\n    get\n  }\n\n}"
  },
  {
    "path": "packages/sharelist/app/modules/core/upload.js",
    "content": ""
  },
  {
    "path": "packages/sharelist/app/modules/core/utils.js",
    "content": "\nconst ignore = require('ignore')\nconst crypto = require('crypto')\nconst { pluginConfigKey } = require('./plugin')\n\n//过滤config 项目\nconst filterConfig = (config) => {\n  let ret = {}\n  for (let i of pluginConfigKey) {\n    if (config[i]) ret[i] = config[i]\n  }\n  return ret\n}\n\n//TODO 过滤屏蔽路径\nconst isForbiddenPath = (p) => {\n  return false\n}\n\nconst isIgnorePath = (p = '', config) => {\n  p = p.replace(/^\\//, '')\n  return p && ignore().add([].concat(config.acl_file, config.ignores)).ignores(p)\n}\n\nconst isProxyPath = (p, config) => {\n  p = p.replace(/^\\//, '')\n  return p && config.proxy_enable && ignore().add(config.proxy_paths).ignores(p)\n}\n\nexports.md5 = content => crypto.createHash('md5').update(content).digest(\"hex\")\n\nexports.getFiles = async (driver, config, runtime) => {\n  //使用路径模式，提前排除\n  if (runtime.path && isIgnorePath(runtime.path, config)) {\n    return { error: { code: 404 } }\n  }\n\n  if (runtime.path && isForbiddenPath(runtime.path, config)) {\n    return { error: { code: 404 } }\n  }\n\n  if (!config.index_enable) {\n    return { error: { code: 403 } }\n  }\n\n  let data\n  try {\n    data = await driver.list(runtime)\n  } catch (e) {\n    console.trace(e)\n    return { error: { ...e, code: e.code || 500, message: e.message } }\n  }\n\n  if (data.files?.length > 0) {\n    let base_url = runtime.path == '/' ? '' : runtime.path\n\n    data.files = data.files\n      .filter(i =>\n        !isIgnorePath((base_url + '/' + i.name).substring(1), config)\n        &&\n        i.hidden !== true\n      )\n\n    if (!runtime.params.search) {\n      data.files.forEach((i) => {\n        //路径，相对于drive的绝对路径\n        // TODO 搜索结果 应在 web端忽略path 内容\n        i.path = i.extra?.path ? [runtime.driveName, i.extra?.path].join('/').replace(/\\/{2,}/g, '/') : i.path\n\n        if (i.config) {\n          i.config = filterConfig(i.config)\n        }\n      })\n    }\n\n  }\n\n\n  if (data.config) {\n    // console.log('SET data config', runtime.driveName)\n    data.config = filterConfig(data.config)\n    data.config.drive = runtime.driveName\n  }\n\n  return { data }\n\n}\n\nexports.getFile = async (driver, config, runtime) => {\n\n  //使用路径模式，提前排除\n  if (runtime.path && isIgnorePath(runtime.path, config)) {\n    return { error: { code: 404 } }\n  }\n\n  if (runtime.path && isForbiddenPath(runtime.path, config)) {\n    return { error: { code: 404 } }\n  }\n\n  let data\n  try {\n    data = await driver.get(runtime)\n  } catch (e) {\n    return { error: { code: e.code || 500, msg: e.message } }\n  }\n\n  return { data }\n}\n\n\nexports.getPathById = async (driver, config, runtime) => {\n\n  //使用路径模式，提前排除\n  if (runtime.path && isIgnorePath(runtime.path, config)) {\n    return { error: { code: 404 } }\n  }\n\n  if (runtime.path && isForbiddenPath(runtime.path, config)) {\n    return { error: { code: 404 } }\n  }\n\n  let data\n  try {\n    data = await driver.pwd(runtime.id)\n  } catch (e) {\n    return { error: { code: e.code || 500, msg: e.message } }\n  }\n\n  return { data }\n}\n\nexports.getContent = async (driver) => {\n\n}\n\nexports.getDownloadUrl = async (config, driver, runtime) => {\n  //使用路径模式，提前排除\n  if (runtime.path && isIgnorePath(runtime.path, config)) {\n    return { error: { code: 404 } }\n  }\n\n  if (runtime.path && isForbiddenPath(runtime.path, config)) {\n    return { error: { code: 404 } }\n  }\n\n  try {\n    let { url } = await driver.get_download_url(runtime)\n    return { url }\n\n  } catch (error) {\n    return { error }\n  }\n}\n\nexports.isUrl = (s) => {\n  try {\n    let parsed = new URL(s)\n    return parsed.protocol == 'http:' || parsed.protocol == 'https:'\n  } catch (err) {\n    return false;\n  }\n}\n"
  },
  {
    "path": "packages/sharelist/app/modules/guide/driver/aliyundrive.js",
    "content": "const { getOAuthAccessToken, PROXY_URL, render } = require('./shared')\n\nmodule.exports = async function (ctx, next) {\n  render(ctx, `<div class=\"guide\">\n  <h3>挂载 Aliyun Drive</h3>\n  <p>参考<a href=\"https://media.cooluc.com/decode_token/\">此链接</a></p>\n  </div>`)\n}"
  },
  {
    "path": "packages/sharelist/app/modules/guide/driver/baidu.js",
    "content": "const { PROXY_URL, render, btoa, atob } = require('./shared')\nconst querystring = require('querystring')\n\nconst getOAuthAccessToken = async (app, url, { client_id, client_secret, redirect_uri, code }, options = {}) => {\n  let data = {\n    client_id,\n    client_secret,\n    redirect_uri,\n    code,\n    grant_type: 'authorization_code'\n  }\n\n  let resp\n\n  try {\n    resp = await app.request(url, {\n      method: 'get',\n      data,\n      ...options,\n    })\n  } catch (e) {\n    resp = { error: e?.message || e.toString() }\n  }\n  if (resp.data.error) {\n    return { error: resp.data.error_description || resp.data.error }\n  }\n\n  return resp.data\n}\n\nmodule.exports = async function (ctx, next, app) {\n  if (ctx.request.body && ctx.request.body.act && ctx.request.body.act == 'install') {\n    let { client_id, client_secret } = ctx.request.body\n    if (client_id && client_secret) {\n      let baseUrl = ctx.origin + '/@guide/baidu/' + btoa([client_id, client_secret].join('::')) + '/callback'\n\n      const opts = {\n        client_id: client_id,\n        scope: 'basic,netdisk',\n        response_type: 'code',\n        redirect_uri: PROXY_URL,\n        state: baseUrl\n      };\n\n      ctx.redirect(`https://openapi.baidu.com/oauth/2.0/authorize?${querystring.stringify(opts)}`)\n    }\n\n  } else if (ctx.params.pairs) {\n    let [client_id, client_secret] = atob(ctx.params.pairs).split('::')\n    if (ctx.query.code) {\n      let credentials = await getOAuthAccessToken(app, 'https://openapi.baidu.com/oauth/2.0/token', { client_id, client_secret, code: ctx.query.code, redirect_uri: PROXY_URL })\n      console.log(credentials)\n      if (credentials.error) {\n        ctx.body = credentials.error\n      } else {\n        let ret = { AppKey: client_id, SecretKey: client_secret, redirect_uri: PROXY_URL, access_token: credentials.access_token, refresh_token: credentials.refresh_token }\n\n        const cnt = Object.keys(ret).map(i => `<div><div class=\"label\">${i}:</div>${ret[i]}</div>`).join('<br />')\n        render(ctx, `\n        <div class=\"guide\">\n          ${cnt}\n        </div >\n      `)\n      }\n    }\n    else if (ctx.query.error) {\n      ctx.body = req.query.error\n    }\n  } else {\n    render(ctx, `\n      <div class=\"guide\">\n        <h3>挂载Baidu Netdisk</h3>\n        <p>1. 前往 <a target=\"_blank\" style=\"margin-right:5px;cursor:pointer;\" href=\"https://pan.baidu.com/union/console/createapp\">Baidu网盘开发平台</a> 注册应用获取 API KEY\" 和 SECRET KEY，注册类别 请选择为【软件】。<br />2. 前往 <a target=\"_blank\" style=\"margin-right:5px;cursor:pointer;\" href=\"https://pan.baidu.com/union/console/applist\">应用详情->安全设置</a> 将【OAuth授权回调页】设置为: </p>\n        <p><a target=\"_blank\" href=\"https://github.com/reruin/reruin.github.io/blob/master/sharelist/redirect.html\" style=\"font-size:12px;margin-right:5px;color:#337ab7;\">https://reruin.github.io/sharelist/redirect.html</a></p>\n  \n        <form class=\"form-horizontal\" method=\"post\">\n          <input type=\"hidden\" name=\"act\" value=\"install\" />\n          <div class=\"form-item\" style=\"font-size:12px;\"><label class=\"flex\"><input disabled checked=\"true\" name=\"custom\" id=\"j_custom\" type=\"checkbox\"> 使用自己的应用ID 和 应用机密</label>，请遵循 <a href=\"https://pan.baidu.com/union/document/protocol\" target=\"_blank\">使用协议</a>。</div>\n          <div class=\"form-item\"><input id=\"j_client_id\" class=\"sl-input\" type=\"text\" name=\"client_id\" placeholder=\"应用ID / AppKey\" /></div>\n          <div class=\"form-item\"><input id=\"j_client_secret\" class=\"sl-input\" type=\"text\" name=\"client_secret\" placeholder=\"应用机密 / SecretKey\" /></div>\n          <button class=\"sl-button btn-primary\" id=\"signin\" type=\"submit\">验证 / Verify</button>\n        </form>\n        <script>\n        function toggleCustom(){\n          var checked = $('#j_custom').prop(\"checked\")\n          if( checked ){\n            $('.custom').show()\n          }else{\n            $('.custom').hide()\n          }\n        }\n        $(function(){\n          $('#j_custom').on('change' , function(){\n            toggleCustom()\n          })\n        })\n        </script>\n      </div>\n    `)\n  }\n}"
  },
  {
    "path": "packages/sharelist/app/modules/guide/driver/googledrive.js",
    "content": "const { PROXY_URL, render } = require('./shared')\n\nconst getOAuthAccessToken = async (app, url, { client_id, client_secret, redirect_uri, code }, options = {}) => {\n  let data = {\n    client_id,\n    client_secret,\n    redirect_uri,\n    code,\n    grant_type: 'authorization_code'\n  }\n\n  let resp\n\n  try {\n    resp = await app.request.post(url, {\n      data,\n      contentType: 'json',\n      ...options,\n    })\n  } catch (e) {\n    console.log(e)\n    resp = { error: e?.message || e.toString() }\n  }\n  console.log(resp)\n  if (resp.data.error) {\n    return { error: resp.data.error_description || resp.data.error }\n  }\n\n  return resp.data\n}\nmodule.exports = async function (ctx, next, app) {\n  if (ctx.request.body && ctx.request.body.act && ctx.request.body.act == 'install') {\n    let { client_id, client_secret, redirect_uri, code } = ctx.request.body\n\n    let credentials = await getOAuthAccessToken(app, 'https://oauth2.googleapis.com/token', { client_id, client_secret, code, redirect_uri }, { proxy: app.config.proxy_url })\n    if (credentials.error) {\n      ctx.body = credentials.error\n    } else {\n      let ret = { client_id, client_secret, redirect_uri, refresh_token: credentials.refresh_token }\n      let cnt = Object.keys(ret).map(i => `<div><div class=\"label\">${i}:</div>${ret[i]}</div>`).join('<br />')\n      render(ctx, `\n      <div class=\"guide\">\n        ${cnt}\n      </div>`)\n    }\n  } else if (ctx.params.pairs) {\n    let [client_id, client_secret] = app.utils.atob(ctx.params.pairs).split('::')\n    if (ctx.query.code) {\n      let credentials = await getOAuthAccessToken(app, 'https://oauth2.googleapis.com/token', { client_id, client_secret, code: ctx.query.code, redirect_uri: PROXY_URL }, { proxy: app.config.proxy_url })\n      if (credentials.error) {\n        ctx.body = credentials.error\n      } else {\n        let cnt = Object.keys(credentials).map(i => `<div><div class=\"label\">${i}:</div>${credentials[i]}</div>`).join('<br />')\n        render(ctx, `\n        <div class=\"guide\">\n          ${cnt}\n        </div>`)\n      }\n    }\n    else if (ctx.query.error) {\n      ctx.body = req.query.error\n    }\n  } else {\n    render(ctx, `\n    <div class=\"guide\">\n      <h3>挂载GoogleDrive</h3>\n      <p>1. 请参考 <a target=\"_blank\" style=\"font-size:12px;margin-right:5px;color:#337ab7;\" href=\"https://developers.google.com/workspace/guides/create-project\">此链接</a>创建项目，获取 Client ID / Client Secret。你也可以使用 rclone 的这组,但是有一定几率触发 Rate Limit Exceeded </p>\n      <p>Client ID:202264815644.apps.googleusercontent.com</p>\n      <p>Client Secret:X4Z3ca8xfWDb1Voo-F9a7ZxJ</p>\n      <p>2. 在下方填写Client ID / Client Secret后，<a target=\"_blank\" style=\"font-size:12px;margin-right:5px;color:#337ab7;\" id=\"j_code_link\"  onclick=\"directToCodeUrl(this)\">点击获取验证code</a>，若出现[Google hasn't verified this app]，请展开Advanced，点击[Go to Quickstart (unsafe)]。 </p>\n\n      <form class=\"form-horizontal\"  method=\"post\">\n        <input type=\"hidden\" name=\"act\" value=\"install\" />\n        <input type=\"hidden\" name=\"redirect_uri\" id=\"j_direct_uri\" value=\"urn:ietf:wg:oauth:2.0:oob\" />\n        <div class=\"form-item\"><input id=\"j_client_id\" class=\"sl-input\" type=\"text\" name=\"client_id\" placeholder=\"应用ID / Client ID\" /></div>\n        <div class=\"form-item\"><input id=\"j_client_secret\" class=\"sl-input\" type=\"text\" name=\"client_secret\" placeholder=\"应用机密 / Client Secret\" /></div>\n        <div class=\"form-item\"><input id=\"j_code\" class=\"sl-input\" type=\"text\" name=\"code\" placeholder=\"code\" /></div>\n        <button class=\"sl-button btn-primary\" id=\"signin\" type=\"submit\">验证 / Verify</button>\n      </form>\n      <script>\n        var codeUrl;\n        function readFile(input){\n          if (window.FileReader) {\n            var file = input.files[0];\n            filename = file.name.split(\".\")[0];\n            var reader = new FileReader();\n            reader.onload = function() {\n              try{\n                var d = JSON.parse( this.result );\n                var data = Object.values(d)[0]\n                var client_id = data.client_id;\n                var client_secret = data.client_secret;\n                var redirect_uris = data.redirect_uris;\n\n                var hit = redirect_uris.find(function(i){\n                  return  i.indexOf('urn:ietf') == 0\n                })\n\n                document.querySelector('#j_client_id').value = client_id;\n                document.querySelector('#j_client_secret').value = client_secret;\n\n                if(hit){\n                  codeUrl = \"https://accounts.google.com/o/oauth2/auth?client_id=\"+client_id+\"&redirect_uri=\"+hit+\"&response_type=code&access_type=offline&scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fdrive&approval_prompt=auto\";\n\n                  document.querySelector('#j_direct_uri').value = hit;\n                }\n                \n              }catch(e){\n                console.log(e)\n                alert('文件无效')\n              }\n            }\n            reader.readAsText(file,\"UTF-8\");\n          }\n        }\n\n        function directToCodeUrl(el){\n          var client_id = document.querySelector('#j_client_id').value ;\n          var client_secret = document.querySelector('#j_client_secret').value;\n          if(client_id && client_secret){\n            var codeUrl = \"https://accounts.google.com/o/oauth2/auth?client_id=\"+client_id+\"&redirect_uri=urn:ietf:wg:oauth:2.0:oob&response_type=code&access_type=offline&scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fdrive&approval_prompt=auto\";\n\n            window.open(codeUrl)\n          }else{\n            alert('请输入Client ID / Client Secret')\n          }\n        }\n      </script>\n    </div> `)\n  }\n}"
  },
  {
    "path": "packages/sharelist/app/modules/guide/driver/onedrive.js",
    "content": "\nconst { PROXY_URL, render, btoa, atob } = require('./shared')\n\nconst querystring = require('querystring')\n\nconst support_zone = {\n  'GLOBAL': [\n    'https://login.microsoftonline.com',\n    'https://graph.microsoft.com',\n    'https://portal.azure.com',\n    '全球',\n    'https://www.office.com/',\n    ['00cdbfd5-15a5-422f-a7d7-75e8eddd8fa8', 'pTvE-.ooe8ou5p1552O8s.3WK996UZ.Z8M'],\n  ],\n  'CN': [\n    'https://login.chinacloudapi.cn',\n    'https://microsoftgraph.chinacloudapi.cn',\n    'https://portal.azure.cn',\n    '世纪互联',\n    'https://portal.partner.microsoftonline.cn/Home',\n    ['9430c343-440f-44f3-ba1d-18b77c0072af', '8f3.2dD-_.6mLv-VmMo6vCxuYcm5~Liqn4'],\n  ],\n  'DE': [\n    'https://login.microsoftonline.de',\n    'https://graph.microsoft.de',\n    'https://portal.microsoftazure.de',\n    'Azure Germany'\n  ],\n  'US': [\n    'https://login.microsoftonline.us',\n    'https://graph.microsoft.us',\n    'https://portal.azure.us',\n    'Azure US GOV',\n  ]\n}\n\nconst getAuthority = (zone, tenant_id) => {\n  return support_zone[zone || 'COMMON'][0] + '/' + (tenant_id || 'common')\n}\nconst getGraphEndpointSite = (zone, site_name) => {\n  return support_zone[zone || 'COMMON'][1] + '/v1.0/sites/root:/' + site_name\n}\n\nconst getDefaultConfig = (zone) => {\n  return support_zone[zone || 'COMMON'][5] || []\n}\n\nconst getAccessToken = async (app, data) => {\n  let { zone, site_name, ...formdata } = data\n  let metadata = getAuthority(zone)\n\n  formdata.redirect_uri = PROXY_URL\n  formdata.grant_type = 'authorization_code'\n  let res\n  try {\n    let resp = await app.request.post(`${metadata}/oauth2/v2.0/token`, {\n      data: formdata,\n      contentType: 'form'\n    })\n    res = resp.data\n  } catch (e) {\n    res = { error: e.toString() }\n  }\n\n  console.log(res)\n  if (res.error) {\n    return { error: `[${res.error}]${res.error_description}` }\n  }\n\n\n  let ret = { ...res }\n  // get sharepoint site id\n  if (site_name) {\n    let api = getGraphEndpointSite(zone, site_name)\n    try {\n      let resp = await app.curl(api, {\n        headers: {\n          'Authorization': 'Bearer ' + ret.access_token\n        }\n      })\n      ret.site_id = resp.body.id\n    } catch (e) {\n      return { error: 'parse site id error' }\n    }\n  }\n  return ret\n}\n\nmodule.exports = async function (ctx, next, app) {\n  if (ctx.request.body && ctx.request.body.act && ctx.request.body.act == 'install') {\n    let { client_id, client_secret, zone, tenant_id = 'common', custom, sharepoint_site, type } = ctx.request.body\n    let site_name, err\n    if (custom) {\n      if (!client_id || !client_secret) {\n        err = 'require client_id and client_secret'\n      }\n    } else {\n      [client_id, client_secret] = getDefaultConfig(zone)\n      if (!client_id) {\n        err = '暂不支持当前地域'\n      }\n    }\n\n    if (type == 'sharepoint') {\n      if (sharepoint_site) {\n        let obj = new URL(sharepoint_site)\n        site_name = obj.pathname\n      } else {\n        err = '请填写sharepoint站点URL<br/>require sharepoint site'\n      }\n    }\n\n    if (err) {\n      render(ctx, `\n        <div class=\"guide\">\n          '<h3>挂载向导</h3>'\n          <p style=\"font-size:12px;\">${err}<br /></p>\n          <p><a style=\"font-size:12px;cursor:pointer;\" onclick=\"location.href=location.pathname\">点击重新开始</a></p>\n        </div >\n      `)\n    }\n    else if (client_id && client_secret && zone) {\n      let site_name = ''\n      if (sharepoint_site) {\n        let obj = new URL(sharepoint_site)\n        site_name = obj.pathname\n      }\n\n      let baseUrl = ctx.origin + '/@guide/onedrive/' + btoa([client_id, client_secret, zone, site_name].join('::')) + '/callback'\n\n      const opts = {\n        client_id: client_id,\n        scope: [\n          'offline_access',\n          'files.readwrite.all'\n        ].join(' '),\n        response_type: 'code',\n        redirect_uri: PROXY_URL,\n        state: baseUrl\n      };\n      ctx.redirect(`${support_zone[zone][0] + '/' + (tenant_id || 'common')}/oauth2/v2.0/authorize?${querystring.stringify(opts)}`)\n    }\n  }\n  // 挂载验证回调\n  else if (ctx.params.pairs) {\n    let [client_id, client_secret, zone, site_name] = atob(ctx.params.pairs).split('::')\n    if (ctx.query.code) {\n      let credentials = await getAccessToken(app, { client_id, client_secret, code: ctx.query.code, zone, site_name })\n      if (credentials.error) {\n        ctx.body = credentials.error\n      } else {\n\n        let ret = { client_id, client_secret, redirect_uri: PROXY_URL, refresh_token: credentials.refresh_token }\n\n        console.log(ret)\n        if (site_name) {\n          let api = getGraphEndpointSite(zone, site_name)\n          try {\n            let resp = await app.request(api, {\n              headers: {\n                'Authorization': 'Bearer ' + credentials.access_token\n              }\n            })\n            ret.site_id = resp.data.id\n          } catch (e) {\n            ctx.body = 'parse site id error'\n            return\n          }\n        }\n\n        let cnt = Object.keys(ret).map(i => `<div><div class=\"label\">${i}:</div>${ret[i]}</div>`).join('<br />')\n        render(ctx, `\n        <div class=\"guide\">\n          ${cnt}\n        </div >\n      `)\n      }\n    }\n    else if (ctx.query.error) {\n      ctx.body = req.query.error\n    }\n  }\n\n  else {\n    let zone = [], types = ['onedrive', 'sharepoint']\n\n    for (let [key, value] of Object.entries(support_zone)) {\n      zone.push(`<option value=\"${key}\" data-sharepoint=\"${value[4] || ''}\" data-portal=\"${value[2] || ''}\" ${key == 'COM' ? 'selected' : ''}>${value[3]}</option>`)\n    }\n\n    render(ctx, `<div class=\"guide\">\n      <h3>OneDrive 挂载向导</h3>\n      \n      <form class=\"form-horizontal\" method=\"post\">\n        <div class=\"l-center\" style=\"font-size:13px;margin-bottom:24px;\">\n          <label><input type=\"radio\" name=\"type\" value=\"onedrive\" checked /> OneDrive 挂载</label>\n          <label><input type=\"radio\" name=\"type\" value=\"sharepoint\" /> SharePoint 挂载</label>\n          <label><input type=\"radio\" name=\"type\" value=\"sharelink\" /> SharePoint 分享链接挂载</label>\n        </div>\n        <input type=\"hidden\" name=\"act\" value=\"install\" />\n\n        <div class=\"form-body\">\n          <div class=\"form-item tab tab-onedrive tab-auto tab-sharepoint\">\n            <select id=\"j_zone\" name=\"zone\">\n              ${zone.join('')}\n            </select>\n          </div>\n          <div class=\"tab tab-sharepoint\">\n            <p>前往 <a style=\"margin-right:5px;cursor:pointer;\" id=\"j_portal_office\">office365</a>，点击应用 sharepoint，创建一个网站，将URL地址填入下方输入框中。</p>\n            <input class=\"sl-input zone_change_placeholder\" type=\"text\" name=\"sharepoint_site\" value=\"\" placeholder=\"URL https://xxxxxx.sharepoint.com/sites(teams)/xxxxxx\" />\n          </div>\n          <div class=\"tab tab-sharepoint tab-onedrive\">\n            <div class=\"form-item\" style=\"font-size:12px;\"><label><input name=\"custom\" id=\"j_custom\" type=\"checkbox\"> 使用自己的应用ID 和 应用机密</label></div>\n            <div class=\"tab-custom\">\n              <p>前往 <a style=\"margin-right:5px;cursor:pointer;\" id=\"j_portal\">Azure管理后台</a> 注册应用获取 应用ID 和 应用机密。重定向 URI 请设置为: </p>\n              <p><a target=\"_blank\" href=\"https://github.com/reruin/reruin.github.io/blob/master/sharelist/redirect.html\" style=\"font-size:12px;margin-right:5px;color:#337ab7;\">https://reruin.github.io/sharelist/redirect.html</a></p>\n              <div class=\"form-item\"><input class=\"sl-input\" type=\"text\" name=\"client_id\" value=\"\" placeholder=\"应用ID / app_id\" /></div>\n              <div class=\"form-item\"><input class=\"sl-input\" type=\"text\" name=\"client_secret\" value=\"\" placeholder=\"应用机密 / app_secret\" /></div>\n              <div class=\"form-item\"><input class=\"sl-input\" type=\"text\" name=\"tenant_id\" value=\"\" placeholder=\"租户ID / tenant_id (多租户可选)\" /></div>\n\n            </div>\n          </div>\n          \n\n          <div class=\"form-item tab tab-sharelink\"><input class=\"sl-input\" type=\"text\" name=\"share_url\" value=\"\" placeholder=\"URL https://xxxx.sharepoint.com/:f:/g/personal/xxxxxxxx/mmmmmmmmm?e=XXXX\" /></div>\n\n        </div>\n        <button class=\"sl-button btn-primary\" id=\"signin\" type=\"submit\">验证</button>\n      </form>\n      \n    </div>\n    <script>\n      function toggleType(type){\n        // $('.tab.tab-'+type).fadeIn().siblings('.tab').fadeOut() \n        $('.tab').hide()\n        $('.tab.tab-'+type).fadeIn(150)\n        toggleCustom()\n\n      }\n\n      function toggleCustom(){\n        var checked = $('#j_custom').prop(\"checked\")\n        if( checked ){\n          $('.tab-custom').show()\n        }else{\n          $('.tab-custom').hide()\n        }\n      }\n\n      $(function(){\n        $('input:radio[name=type]').on('change', function() {\n          toggleType(this.value)\n        });\n\n        $('#j_portal').on('click' , function(){\n          var option = $(\"#j_zone option:selected\")\n          var portal = option.attr('data-portal') + '/#blade/Microsoft_AAD_RegisteredApps/ApplicationsListBlade';\n\n          window.open(portal)\n        })\n\n        $('#j_portal_office').on('click' , function(){\n          var option = $(\"#j_zone option:selected\")\n          var portal = option.attr('data-sharepoint');\n          if( portal ){\n            window.open(portal)\n          }else{\n            alert('暂不支持当前地域')\n          }\n        })\n\n        $('#j_type').on('change' , function(){\n          $('.form-item').hide()\n          $('.form-item.tab-'+type).fadeIn(150)\n        })\n\n        $('#j_custom').on('change' , function(){\n          toggleCustom()\n        })\n\n        $('#j_zone').on('change' , function(){\n          let zone = $(this).val().toLowerCase()\n          $('input.zone_change_placeholder').each(function(){\n            var tip = $(this).attr('placeholder')\n            $(this).attr('placeholder' , tip.replace(/sharepoint\\.[a-z]+/,'sharepoint.'+zone))\n          })\n        })\n        \n        toggleType($('input:radio[name=type]:checked').val())\n      })\n\n\n    </script>\n  `)\n  }\n}"
  },
  {
    "path": "packages/sharelist/app/modules/guide/driver/shared.js",
    "content": "exports.PROXY_URL = 'https://reruin.github.io/sharelist/redirect.html'\n\nexports.render = (ctx, cnt) => {\n  return ctx.body = `<!DOCTYPE html><html><head><title>ShareList</title><meta charset=\"utf8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0,minimum-scale=1.0, maximum-scale=1.0, user-scalable=no\"><meta name=\"referrer\" content=\"never\">\n  <script src=\"https://cdn.staticfile.org/jquery/3.6.0/jquery.min.js\"></script>\n  </head>\n  <style>\n    body{\n      font-size:14px;\n      line-height:1.7em;\n    }\n    .guide{\n      width: 80%;\n      margin: 10% auto;\n      max-width: 720px;\n      word-wrap:break-word;\n      word-break:normal; \n    }\n    .l-center{\n      margin:auto;\n      text-align:center;\n    }\n    .label{\n      font-size:12px;\n      color:rgba(0,0,0,.5);\n    }\n    h3{\n      font-size:16px;\n      text-align:center;\n    }\n    .auth p,.auth a{\n      font-size:12px;\n    }\n    .auth a{\n      color:#337ab7;\n    }\n    \n    .form-item{\n      margin-bottom:8px;\n    }\n    .form-item.show{\n      display:block;\n    }\n    input[type='text'],select,button{\n      width:100%;padding:8px;\n      box-sizing:border-box;\n    }\n    button{\n      background-color: #0078e7;\n      color: #fff;\n      border:none;\n      outline:none;\n      padding:8px;\n      border-radius:5px;\n    }\n    .tab{\n      display:none;\n    }\n  </style>\n  <body>${cnt}</body></html>`\n}\n\nexports.btoa = v => Buffer.from(v).toString('base64')\n\nexports.atob = v => Buffer.from(v, 'base64').toString()"
  },
  {
    "path": "packages/sharelist/app/modules/guide/index.js",
    "content": "const baidu = require('./driver/baidu')\nconst onedrive = require('./driver/onedrive')\nconst googledrive = require('./driver/googledrive')\nconst aliyundrive = require('./driver/aliyundrive')\n\nmodule.exports = (inject) => {\n  const vendor = { onedrive, googledrive, baidu, aliyundrive }\n\n  return {\n    config() {\n      let guide = {}\n      for (let i in vendor) {\n        guide[i] = `/@guide/${i}`\n      }\n      return { guide }\n    },\n    route: [\n      {\n        method: 'all',\n        path: '/@guide/:type',\n        flush: 'pre',\n        handler: async (ctx, next) => {\n          await vendor[ctx.params.type]?.(ctx, next, inject)\n        }\n      },\n      {\n        method: 'get',\n        path: '/@guide/:type/:pairs(.*)/callback',\n        flush: 'pre',\n        handler: async (ctx, next) => {\n          await vendor[ctx.params.type]?.(ctx, next, inject)\n        }\n      }\n    ]\n  }\n}\n"
  },
  {
    "path": "packages/sharelist/app/modules/server/controller.js",
    "content": "const fs = require('fs')\nconst { nanoid } = require('nanoid')\nconst path = require('path')\nconst { createRuntime, selectSource, send, sendfile, uploadManage, emptyStream, createUpdateManage } = require('./runtime')\n\nmodule.exports = (sharelist, appConfig) => {\n\n  const getConfig = async (raw = false) => {\n    if (raw) return sharelist.config\n    let config = { ...sharelist.config }\n    // if (config.drives) {\n    //   config.drives = sharelist.getDisk()\n    // }\n    config.drivers = sharelist.driver.getDriver().filter(i => i.mountable !== false)\n\n    config.theme_options = sharelist.theme.get()\n\n    config.plugin_source_options = sharelist.plugin.getSources()\n\n    config.plugins = sharelist.plugin.get()\n\n    config.guide = appConfig.guide\n\n    config.pluginConfig = sharelist.driver.getPluginConfig()\n\n    return config\n  }\n\n  const getCustomConfig = () => {\n    const ret = {}\n    const defaultConfigKey = sharelist.defaultConfigKey\n    const config = { ...sharelist.config }\n    for (let i of Object.keys(config)) {\n\n      if (!defaultConfigKey.includes(i)) {\n        ret[i] = config[i]\n      }\n    }\n    ret.version = appConfig.version\n    return ret\n  }\n\n  const getManageFile = (file) => {\n    if (appConfig.manageDir) {\n      return path.join(appConfig.manageDir, file)\n    } else {\n      return ''\n    }\n  }\n\n  const get = async (runtime) => {\n    let { data, error } = await sharelist.file(runtime)\n\n    if (error) {\n      return { error }\n    }\n\n    if (data.type == 'file') {\n      if (runtime.params.download) {\n        if (runtime.params.preview) {\n          if (data.extra.preview_url) {\n            return await send(sharelist, { ...data, download_url: data.extra.preview_url, reqHeaders: runtime.headers })\n          }\n          else if (data.extra.sources) {\n            let download_url = selectSource(data.extra.sources, runtime.params.preview) || data.download_url\n            return await send(sharelist, { ...data, download_url, reqHeaders: runtime.headers })\n          } else {\n            console.log('here')\n            return await send(sharelist, { ...data, reqHeaders: runtime.headers })\n          }\n        } else {\n          if (sharelist.config.anonymous_download_enable) {\n            return await send(sharelist, { ...data, reqHeaders: runtime.headers })\n          }\n        }\n\n      } else {\n        if (!data.download_url || data.extra?.proxy) {\n          data.download_url = '/api/drive/get?download=true&id=' + encodeURIComponent(data.id)\n        }\n        return data\n      }\n    } else {\n      if (!data.download_url) {\n        data.download_url = '/api/drive/get?download=true&id=' + encodeURIComponent(data.id)\n      }\n      return data\n    }\n\n    return { status: 404 }\n  }\n  const list = async (runtime) => {\n\n    let st = Date.now()\n\n    let { data, error } = await sharelist.files(runtime)\n    if (error) {\n      return { error }\n    } else {\n      if (data.files?.length > 0) {\n        data.files\n          // .sort((a, b) => (a.type == 'folder' ? -1 : 1))\n          .forEach((i) => {\n            if (i.type == 'file') {\n              let download_url = '/api/drive/file/get?download=true&id=' + encodeURIComponent(i.id)\n              i.download_url = download_url\n              if (i.extra?.preview_url) {\n                i.preview_url = download_url + '&preview=true'\n              }\n              if (i.extra?.sources) {\n                i.sources = i.extra.sources.map(i => ({ quality: i.quality, src: download_url + '&preview=' + i.quality }))\n              }\n            }\n          })\n      }\n\n      return data\n    }\n\n  }\n\n  const manageReplacer = (data) => {\n    const basePath = sharelist.config.manage_path\n    return data.replace('<head>', `<head><script>window.MANAGE_BASE=\"${basePath}\"</script>`).replace('src=\"./', `src=\"${basePath}/`).replace('href=\"./', `href=\"${basePath}/`)\n  }\n  return {\n    async page(ctx, next) {\n      //let filepath = getFilePath(ctx.path == '/' ? 'index.html' : ctx.path.substring(1), this.app)\n      const { getThemeFile, config } = sharelist\n      const managePath = config.manage_path || '/@manage'\n      if (managePath && ctx.path.startsWith(managePath)) {\n        let filepath = ctx.path.replace(managePath, '')\n        if (filepath == '' && !ctx.path.endsWith('/')) {\n          ctx.redirect(ctx.path + '/')\n          return\n        }\n        filepath = getManageFile((filepath == '/' || filepath == '') ? 'index.html' : filepath)\n        let isFileExist = false\n        try {\n          if (fs.existsSync(filepath)) {\n            isFileExist = true\n          }\n        } catch (e) { }\n        if (!isFileExist) {\n          filepath = getManageFile('index.html')\n        }\n        let replacer = filepath.endsWith('index.html') ? manageReplacer : null\n\n        return await sendfile(ctx, filepath, replacer)\n      }\n\n      let filepath = getThemeFile(ctx.path == '/' ? 'index.html' : ctx.path.substring(1))\n\n      // if url is '/filename_path?download'\n      if ('download' in ctx.query) {\n        await get(ctx, next)\n      } else {\n        try {\n          if (!fs.existsSync(filepath)) {\n            filepath = getThemeFile('index.html')\n          }\n        } catch (e) {\n          filepath = getThemeFile('index.html')\n        }\n        console.log(Date.now())\n\n        let status = await sendfile(ctx, filepath)\n        if (!status) {\n          console.log(Date.now())\n\n          let r = await this.list(ctx, next)\n          console.log(Date.now())\n\n          return r\n        }\n      }\n\n    },\n\n    async setting(ctx, next) {\n      ctx.body = { data: await getConfig(!!ctx.query.raw) }\n    },\n    async userConfig(ctx, next) {\n      const data = getCustomConfig()\n      ctx.body = { status: 0, data }\n    },\n    async configField(ctx, next) {\n      const data = getCustomConfig()\n      const key = ctx.query.key || ctx.params.field\n      const ret = key && data[key] ? data[key] : ''\n      if (ctx.query['content-type']) {\n        ctx.set('content-type', ctx.query['content-type'])\n        ctx.body = ret\n      } else {\n        ctx.body = { status: 0, data: ret }\n      }\n    },\n    async reload(ctx, next) {\n      await sharelist.reload()\n      ctx.body = { status: 0 }\n    },\n    async reloadBench(ctx) {\n      for (let i = 0; i < 3000; i++) {\n        await sharelist.reload()\n      }\n      ctx.body = { status: 0 }\n    },\n    async updateSetting(ctx, next) {\n      let data = { ...ctx.request.body }\n      for (let i in data) {\n        let val = data[i]\n\n        if (i == 'drives') {\n          sharelist.setDrives(val)\n        } else {\n          sharelist.config[i] = val\n        }\n      }\n\n      ctx.body = { data: await getConfig() }\n    },\n\n    async getPlugin(ctx, next) {\n      const id = ctx.params.id\n      const ret = sharelist.plugin.get(id)\n      if (ret && ret.path) {\n        ctx.body = { status: 0, data: fs.readFileSync(ret.path, 'utf-8') }\n      } else {\n        ctx.body = { status: 0, data: '' }\n      }\n    },\n\n    async setPlugin(ctx, next) {\n      const { id, data } = ctx.request.body\n      try {\n        await sharelist.plugin.set(id, data)\n        ctx.body = { status: 0 }\n      } catch (e) {\n        ctx.body = { status: -1, msg: e.message || '保存失败' }\n      }\n    },\n    async removePlugin(ctx) {\n      const id = ctx.params.id\n      //try {\n      await sharelist.plugin.remove(id)\n      ctx.body = {}\n      //} catch (e) {\n      ctx.body = {}\n      //}\n    },\n    async upgradePlugin(ctx) {\n      const id = ctx.params.id\n      //try {\n      await sharelist.plugin.upgrade(id)\n      ctx.body = {}\n      //} catch (e) {\n      //  ctx.body = { status: -1, msg: e.message || '更新失败' }\n      //}\n    },\n    async clearCache(ctx, next) {\n      sharelist.cache.clear()\n      ctx.body = { status: 0 }\n    },\n\n    async getPath(ctx, next) {\n      // let { id } = ctx.request.body\n      let runtime = await createRuntime(ctx)\n      let { data, error } = await sharelist.getPathById(runtime)\n      if (error) {\n        ctx.body = { error }\n        return\n\n      }\n      console.log('getPath', data)\n      ctx.body = data\n    },\n\n    async get(ctx, next) {\n      let runtime = await createRuntime(ctx)\n      let data = await get(runtime)\n      if (data.status) {\n        ctx.status = data.status\n\n        if (data.headers) {\n          ctx.set(data.headers)\n        }\n\n        if (data.body) {\n          ctx.body = data.body\n        }\n      } else if (data.redirect) {\n        ctx.redirect(data.redirect)\n      } else {\n        ctx.body = data\n      }\n    },\n    async list(ctx, next) {\n      let runtime = await createRuntime(ctx)\n      ctx.body = await list(runtime)\n    },\n\n    //upload request 有滞后性，需要接口手动停止\n    async cancelUpload(ctx) {\n      uploadManage.remove(ctx.params.id)\n      ctx.body = {}\n    },\n\n    //查询/创建上传 ，即使后端服务重启后 查询依旧有效\n    async createUpload(ctx) {\n      let { task_id, ...rest } = ctx.request.body\n\n      if (task_id && uploadManage.getTask(task_id)) {\n        rest = uploadManage.getTask(task_id)\n      }\n\n      let { size, hash, hash_type, id, name, state, dest } = rest\n\n      let options = { size, name, state }\n\n      if (hash_type && hash) {\n        options.hash = hash\n        options.hash_type = hash_type\n      }\n\n      if (dest) {\n        let dests = dest.split('/')\n        let parent = await sharelist.driver.mkdir(id, dests, {}, true)\n        if (parent?.id) id = parent.id\n      }\n\n      let res = await sharelist.driver.upload(id, null, options)\n      if (res.completed) {\n        ctx.body = { completed: true }\n      } else {\n        let taskId = uploadManage.createTask({\n          id, name, size, hash, hash_type,\n          state: res.state,\n          dest\n        })\n\n        ctx.body = { taskId, start: res.start, completed: res.completed }\n      }\n    },\n    async upload(ctx) {\n      let { task_id, ...rest } = ctx.query\n\n      if (task_id && uploadManage.getTask(task_id)) {\n        rest = uploadManage.getTask(task_id)\n      }\n\n      let { size, hash, hash_type, id, name, upload_id } = rest\n\n      let stream = ctx.req\n\n      let options = { size, name, uploadId: upload_id }\n      if (hash_type && hash) {\n        options[hash_type] = hash.content\n        options.hash = hash\n        options.hash_type = hash_type\n      }\n\n      let controller = new AbortController()\n      options.signal = controller.signal\n\n      if (upload_id) {\n        uploadManage.add({ req: ctx.req, controller, taskId: task_id }, upload_id)\n      }\n\n      options.setUploadState = (extraData) => {\n        uploadManage.updateTask(task_id, { extraData })\n      }\n\n      stream.pause()\n\n      let res = await sharelist.driver.upload(id, stream, options)\n      ctx.body = res\n\n    },\n\n    async pluginStore(ctx) {\n      ctx.body = await sharelist.plugin.getFromStore()\n    },\n\n    async installPlugin(ctx) {\n      let { url } = ctx.request.body\n      try {\n        await sharelist.plugin.createFromUrl(url)\n        ctx.body = { status: 0 }\n      } catch (e) {\n        ctx.body = { error: { message: e.message || '安装失败' } }\n      }\n    },\n\n    async tasks(ctx) {\n      let tasks = await sharelist.transfer.all()\n\n      ctx.body = tasks\n    },\n    async transfer(ctx) {\n      let id = ctx.params.id\n      ctx.body = await sharelist.transfer.get(id)\n    },\n    async removeTransfer(ctx) {\n      let id = ctx.params.id\n      ctx.body = await sharelist.transfer.remove(id)\n    },\n    async resumeTransfer(ctx) {\n      let id = ctx.params.id\n      ctx.body = await sharelist.transfer.resume(id)\n    },\n    async pauseTransfer(ctx) {\n      let id = ctx.params.id\n      ctx.body = await sharelist.transfer.pause(id)\n    },\n    async retryTransfer(ctx) {\n      let id = ctx.params.id\n      ctx.body = await sharelist.transfer.retry(id)\n    },\n\n    async remove(ctx) {\n      let { id } = ctx.request.body\n      if (id) {\n        await sharelist.driver.rm(id)\n        ctx.body = { id }\n      }\n    },\n    async mkdir(ctx) {\n      let { id, name } = ctx.request.body\n      if (id) {\n        let data = await sharelist.driver.list({ id })\n        if (data.files && data.files.includes(i => i.name == name)) {\n          ctx.body = { error: { message: '此目录下已存在同名文件，请修改名称' } }\n        } else {\n          let res = await sharelist.driver.mkdir(id, name)\n          if (res.id) {\n            ctx.body = { id: res.id, name, type: 'folder' }\n          }\n        }\n      }\n    },\n    async hashSave(ctx) {\n      let { id, hash, name, size } = ctx.request.body\n      let res = await sharelist.driver.hashUpload(id, { hash, name, size })\n      if (res) {\n        res.name = name\n        res.type = 'file'\n      }\n      ctx.body = res\n    },\n\n    async update(ctx) {\n      let { id, name, dest, mode, threadNum } = ctx.request.body\n      if (name) {\n        let res = await sharelist.driver.rename(id, name)\n        ctx.body = { name: res.name }\n      }\n\n      // move / copy / transfer\n      else if (dest) {\n\n        let isSameDrive = await sharelist.driver.isSameDrive(id, dest)\n        if (isSameDrive) {\n          let res = await sharelist.driver.mv(id, dest, mode == 'copy')\n          ctx.body = {}\n        } else {\n          await sharelist.transfer.create(id, dest, true, { threadNum })\n          ctx.body = {}\n        }\n      }\n    },\n\n    async removeDisk(ctx) {\n      let { disks } = ctx.request.body\n    }\n  }\n\n}\n"
  },
  {
    "path": "packages/sharelist/app/modules/server/index.js",
    "content": "const Koa = require('koa')\nconst koaCors = require('@koa/cors')\nconst koaBody = require('koa-body')\nconst koaJson = require('koa-json')\nconst createRouter = require('./router')\nconst createApi = require('./controller')\nconst pkg = require('../../../package.json')\n\nclass Server {\n  constructor(sharelist, appConfig) {\n    this.modules = []\n    this.sharelist = sharelist\n    this.appConfig = appConfig\n\n    const app = new Koa()\n    app.use(koaCors())\n    app.use(koaBody())\n    app.use(koaJson())\n\n    app.use(async (ctx, next) => {\n      try {\n        await next()\n      } catch (error) {\n        console.log(error)\n        if (error instanceof Error) {\n          ctx.body = { error: { message: error.message } }\n        } else {\n          ctx.body = { error }\n        }\n      }\n    })\n\n    this.app = app\n  }\n\n  use(module) {\n    this.modules.push(module)\n  }\n\n  createConfig() {\n    let configs = this.modules.map(i => i.config).filter(Boolean)\n    let mergeConfig = { version: pkg.version, ...this.appConfig }\n    for (let config of configs) {\n      if (typeof config == 'function') {\n        config = config(mergeConfig)\n      }\n\n      Object.assign(mergeConfig, config)\n    }\n    return mergeConfig\n  }\n\n  start() {\n    createRouter(this.app, this.sharelist, createApi(this.sharelist, this.createConfig()), this.modules.map(i => i.route).filter(Boolean).flat())\n  }\n}\n\nfunction createServer(...args) {\n  return new Server(...args)\n}\nmodule.exports = createServer"
  },
  {
    "path": "packages/sharelist/app/modules/server/router.js",
    "content": "const Router = require('@koa/router')\nconst createAuth = (sharelist) => async (ctx, next) => {\n\n  let token = ctx.get('authorization') || ctx.query.token\n  let isAdmin = sharelist.checkAccess(token)\n\n  if (isAdmin) {\n    await next()\n  } else {\n    ctx.body = { error: { code: 401, message: 'Invalid password' } }\n  }\n}\n\nmodule.exports = (app, sharelist, api, mergeRoutes) => {\n  const auth = createAuth(sharelist)\n\n  const router = new Router()\n  mergeRoutes.filter(i => i.flush == 'pre').forEach(i => {\n    router[i.method](i.path, i.handler)\n  })\n  router\n    .get('/api', (ctx) => {\n      ctx.body = 'hello!'\n    })\n    .get('/api/setting', auth, api.setting)\n    .get('/api/user_config', api.userConfig)\n    .post('/api/setting', auth, api.updateSetting)\n    .put('/api/cache/clear', auth, api.clearCache)\n    .put('/api/reload', auth, api.reload)\n\n    .get('/api/reloadBench', api.reloadBench)\n    .get('/api/drive/file/get', api.get)\n    .post('/api/drive/file/get', api.get)\n    .post('/api/drive/file/list', api.list)\n    .post('/api/drive/file/delete', api.remove)\n    .post('/api/drive/file/update', api.update)\n    .post('/api/drive/file/mkdir', api.mkdir)\n    .post('/api/drive/file/upload', api.upload)\n\n    .post('/api/drive/file/create_upload', api.createUpload)\n    .post('/api/drive/file/hash_save', api.hashSave)\n    .get('/api/drive/file/cancel_upload/:id', api.cancelUpload)\n\n    .post('/api/drive/file/path', api.getPath)\n    .post('/api/drive/disk/delete', api.removeDisk)\n\n    .get('/api/drive/tasks', api.tasks)\n\n    .get('/api/drive/task/transfer/:id', api.transfer)\n    .delete('/api/drive/task/transfer/:id', api.removeTransfer)\n    .put('/api/drive/task/transfer/:id/resume', api.resumeTransfer)\n    .put('/api/drive/task/transfer/:id/pause', api.pauseTransfer)\n    .put('/api/drive/task/transfer/:id/retry', api.retryTransfer)\n\n    // .post('/api/drive/task/remote_download', api.remoteDownload)\n    // .put('/api/drive/task/remote_download/:id/pause', api.remoteDownloadPause)\n    // .put('/api/drive/task/remote_download/:id/resume', api.remoteDownloadResume)\n    // .delete('/api/drive/task/remote_download/:id', api.remoteDownloadRemove)\n\n\n    .get('/api/config/:field', api.configField)\n\n    .post('/api/plugin_store', auth, api.pluginStore)\n    .post('/api/plugin_store/install', auth, api.installPlugin)\n\n    .put('/api/plugin/:id(.*)/upgrade', auth, api.upgradePlugin)\n    .get('/api/plugin/:id(.*)', auth, api.getPlugin)\n    .post('/api/plugin', auth, api.setPlugin)\n    .delete('/api/plugin/:id(.*)', auth, api.removePlugin)\n    .get('/api/drive/path', api.list)\n    .get('/api/drive/path/:path(.*)', api.list)\n    .get('/api/drive/:path\\\\:file', api.get)\n\n    .get('/api/transfer', api.transfer)\n\n\n    .get('/:path(.*)', api.page)\n\n\n  mergeRoutes.filter(i => i.flush == 'post').forEach(i => {\n    router[i.method](i.path, i.handler)\n  })\n\n  app\n    .use(router.routes())\n    .use(router.allowedMethods());\n\n  return router\n}\n"
  },
  {
    "path": "packages/sharelist/app/modules/server/runtime.js",
    "content": "const { URLSearchParams } = require('url')\nconst promisify = require('util').promisify\nconst extname = require('path').extname\nconst fs = require('fs')\nconst calculate = require('etag')\nconst stat = promisify(fs.stat)\nconst mime = require('mime')\nconst { Readable } = require('stream')\nconst { nanoid } = require('nanoid')\n\nconst parseQuery = (str) => {\n  let params = new URLSearchParams(str)\n  let ret = {}\n  if (params.has('forward')) {\n    ret.forward = true\n  }\n  if (params.has('download')) {\n    ret.download = true\n  }\n  if (params.has('preview')) {\n    ret.preview = params.get('preview')\n  }\n\n  if (params.has('order_by')) {\n    let s = params.get('order_by')\n    let r = {}\n    for (let i of s.split('+')) {\n      let pairs = i.split(':')\n      if (pairs.length == 2) {\n        r[pairs[0]] = pairs[1]\n      }\n    }\n    ret.order_by = r\n  }\n\n  if (params.has('auth')) {\n    ret.auth = params.get('auth')\n  }\n  if (params.has('search')) {\n    ret.search = decodeURIComponent(params.get('search'))\n  }\n\n  return ret\n}\n\nconst mergeHeaders = (a, b) => {\n  const exclude = ['host', 'accept-encoding']\n  let pre = { ...a, ...b }\n  let headers = {}\n  for (let key in pre) {\n    if (exclude.includes(key) == false) {\n      headers[key] = pre[key]\n    }\n  }\n  return headers\n}\n\nconst getRange = (r, total) => {\n  if (r) {\n    let [, start, end] = r.match(/(\\d*)-(\\d*)/);\n    start = start ? parseInt(start) : 0\n    end = end ? parseInt(end) : total - 1\n\n    return { start, end }\n  }\n}\n\nconst createHeaders = (stats, { maxage, immutable, range } = { maxage: 0, immutable: false }) => {\n  let fileSize = stats.size\n  let fileName = stats.name\n\n  let headers = {}\n\n  headers['Last-Modified'] = new Date(stats.mtime).toUTCString()\n\n  headers['Content-Type'] = mime.getType(fileName)\n\n  if (range) {\n    let { start, end } = range\n    headers['Content-Range'] = `bytes ${start}-${end}/${fileSize}`\n    headers['Content-Length'] = end - start + 1\n    headers['Accept-Ranges'] = 'bytes'\n  } else {\n    header['Content-Range'] = `bytes 0-${fileSize - 1}/${fileSize}`\n    headers['Content-Length'] = fileSize\n  }\n\n  headers['Content-Disposition'] = `attachment;filename=${encodeURIComponent(fileName)}`\n  return headers\n}\n\nconst parseSort = (order_by) => {\n  let cats = ['name', 'size', 'ctime', 'mtime']\n  if (order_by) {\n    let [cat, type = 'asc'] = order_by.toLowerCase().split(' ')\n    if (cats.includes(cat)) {\n      return [cat, type == 'asc' ? 1 : 0]\n    }\n  }\n}\n\nconst parsePathAndDrive = (runtime, path) => {\n  runtime.paths = (path || '').replace(/\\/$/, '').split('/').filter(Boolean).map(decodeURIComponent)\n  runtime.path = '/' + runtime.paths.join('/')\n  runtime.driveName = runtime.paths[0]\n}\n\nexports.createRuntime = async (ctx) => {\n\n  let token = ctx.get('authorization') || ctx.query.token\n\n  let runtime = {\n    method: ctx.method,\n    token,\n    headers: ctx.headers\n  }\n\n  let { id, path, ...others } = ctx.method == 'POST' ? ctx.request.body : ctx.query\n\n  if (id) {\n    runtime.id = id\n  } else {\n    parsePathAndDrive(runtime, path)\n  }\n\n  let { order_by, next_page, ...options } = ctx.method == 'POST' ? others : parseQuery(ctx.querystring)\n  if (order_by) {\n    options.orderBy = parseSort(order_by)\n  }\n\n  if (next_page) {\n    options.nextPage = next_page\n  }\n\n  runtime.params = options\n\n  return runtime\n}\n\nexports.selectSource = (sources, quality = 'HD') => {\n  let map = {}\n  sources.forEach(i => {\n    map[i.quality] = i.src\n  })\n  return map[quality] || map['HD'] || map['SD'] || map['LD']\n}\n\nconst notfound = {\n  ENOENT: true,\n  ENAMETOOLONG: true,\n  ENOTDIR: true\n}\n\nexports.sendfile = async (ctx, path, replacer) => {\n  try {\n    const stats = await stat(path)\n\n    if (!stats) return null\n    if (!stats.isFile()) return stats\n    ctx.response.status = 200\n    ctx.response.lastModified = stats.mtime\n    ctx.response.type = mime.getType(path)\n    if (!replacer) {\n      ctx.response.length = stats.size\n      if (!ctx.response.etag) {\n        ctx.response.etag = calculate(stats, {\n          weak: true\n        })\n      }\n    }\n\n    console.log(ctx.request.method, path, ctx.request.fresh, !replacer)\n    // fresh based solely on last-modified\n    switch (ctx.request.method) {\n      case 'HEAD':\n        ctx.status = ctx.request.fresh && !replacer ? 304 : 200\n        break\n      case 'GET':\n        if (ctx.request.fresh && !replacer) {\n          ctx.status = 304\n        } else {\n          if (replacer) {\n            ctx.body = replacer(fs.readFileSync(path, 'utf-8'))\n          } else {\n            ctx.body = fs.createReadStream(path)\n          }\n        }\n        break\n    }\n\n    return stats\n  } catch (err) {\n    if (notfound[err.code]) return\n    err.status = 500\n    throw err\n  }\n}\n\n/**\n * send sharelist data\n * @param {ctx} ctx \n * @param {object} sharelist \n * @param {object} data \n */\nexports.send = async (sharelist, data) => {\n  let { download_url } = data\n  let isGlobalProxy = !!sharelist.config.proxy_enable\n  if (download_url) {\n    //the request need proxy\n    if (data.extra?.proxy || isGlobalProxy) {\n      let reqHeaders = mergeHeaders(data.reqHeaders, data.extra?.proxy?.headers || {})\n\n      let options = {\n        headers: reqHeaders, responseType: 'stream'\n      }\n      if (data.extra?.proxy_server) {\n        options.proxy = data.extra?.proxy_server\n      }\n      let { data: stream, status, error, headers } = await sharelist.request(download_url, options)\n\n      // compatible \n      if (headers['accept-ranges'] == 'bytes' && headers['content-range']) {\n        status = 206\n      }\n\n      if (error) {\n        return {\n          status: 500\n        }\n      } else {\n        return { headers, status, body: stream }\n      }\n    } else {\n      return { redirect: download_url }\n    }\n  } else {\n    let range = getRange(data.reqHeaders.range, data.size) || { start: 0, end: data.size - 1 }\n    let { stream, error, status, headers, enableRanges = false } = await sharelist.driver.createReadStream(data.id, range)\n    let isReqRange = !!data.reqHeaders.range\n    if (stream) {\n      let options = enableRanges ? { range } : {}\n      return {\n        headers: headers || createHeaders(data, options),\n        status: status || (isReqRange && enableRanges ? 206 : 200),\n        body: stream\n      }\n\n    } else {\n      return {\n        status: 404,\n        body: error?.message || `can't find stream`\n      }\n    }\n  }\n}\n\n// 暂停 : destroy() => close. 完成 end => close. 客户端异常 error => close\nconst createUploadManage = () => {\n  let tasks = {}\n  let tasksMetaMap = {}\n  const remove = (id) => {\n    if (id && tasks[id]) {\n      tasks[id].req.destroy(new Error('AbortError'))\n    }\n  }\n\n  const add = (data, id) => {\n    if (id && tasks[id]) {\n      remove(id)\n    }\n    tasks[id] = data\n\n    data.req.once('error', () => {\n      data.controller.abort()\n    })\n\n    data.req.once('close', () => {\n      if (tasksMetaMap[data.taskId]) {\n        delete tasksMetaMap[data.taskId]\n      }\n      delete tasks[id]\n    })\n  }\n\n  //临时上传链\n  const createTask = (data) => {\n    let taskId = nanoid()\n    tasksMetaMap[taskId] = data\n    return taskId\n  }\n  const getTask = (taskId) => tasksMetaMap[taskId]\n\n  const updateTask = (taskId, data) => {\n    let src = tasksMetaMap[taskId]\n    if (src) {\n      for (let i in data) {\n        src[i] = data[i]\n      }\n    }\n  }\n\n  return { remove, add, createTask, getTask, updateTask }\n}\n\nconst createUpdateManage = (sharelist) => {\n  let tasks = {}\n\n  const remove = (id) => {\n    if (id && tasks[id]) {\n      tasks[id].req.destroy(new Error('AbortError'))\n    }\n  }\n\n  const add = (data, id) => {\n    if (id && tasks[id]) {\n      remove(id)\n    }\n    tasks[id] = data\n\n    data.req.once('error', () => {\n      data.controller.abort()\n    })\n\n    data.req.once('close', () => {\n      delete tasks[id]\n    })\n  }\n\n  const get = () => {\n\n  }\n\n  return { remove, add, get }\n}\n\nexports.uploadManage = createUploadManage()\n\nexports.createUpdateManage = createUpdateManage\n\nexports.emptyStream = () => {\n  const controller = new AbortController();\n  const stream = new Readable({\n    read(size) {\n      this.destroy()\n    },\n    signal: controller.signal\n  })\n\n  stream.pause()\n\n  return { stream, controller }\n}"
  },
  {
    "path": "packages/sharelist/app/modules/webdav/index.js",
    "content": "const { WebDAVServer } = require('@sharelist/webdav')\n\nconst isWebDAVRequest = (ctx, webdavPath) => {\n  if (webdavPath == '/') {\n    return /(Microsoft\\-WebDAV|FileExplorer|WinSCP|WebDAVLib|WebDAVFS|rclone|Kodi|davfs2|sharelist\\-webdav|RaiDrive|nPlayer|LibVLC|PotPlayer|gvfs)/i.test(ctx.request.headers['user-agent']) || ('translate' in ctx.request.headers) || ('overwrite' in ctx.request.headers) || ('depth' in ctx.request.headers)\n  } else {\n    return ctx.params.path.startsWith(webdavPath)\n  }\n}\n\nmodule.exports = (sharelist) => {\n  const { config } = sharelist\n  const webdavPath = config.webdav_path || '/'\n  const webdavServer = new WebDAVServer({\n    driver: sharelist.driver.createAction({\n      useProxy: () => !!config.webdav_proxy,\n      baseUrl: webdavPath\n    }),\n    base: webdavPath,\n    auth: (user, pass) => {\n      return !config.webdav_pass || (config.webdav_user === user && config.webdav_pass === pass)\n    }\n  })\n\n  return {\n    route: [{\n      method: 'all',\n      path: ':path(.*)',\n      flush: 'pre',\n      handler: async (ctx, next) => {\n        let webdavPath = config.webdav_path || '/'\n\n        if (!isWebDAVRequest(ctx, webdavPath)) {\n          await next()\n          return\n        }\n        console.log('[WebDAV]', ctx.method, ctx.url, '<-->', ctx.ip)\n        // console.log(ctx.headers)\n        // if (ctx.method == 'PROPPATCH') console.log(ctx.headers)\n        let res\n        try {\n          res = await webdavServer.request(ctx.req, { base: webdavPath })\n        } catch (e) {\n          console.log(e)\n          res = { status: e?.code || 500 }\n        }\n        const { headers, status, body } = res\n        if (status == 302) {\n          ctx.redirect(body)\n        } else {\n\n          if (headers) {\n            ctx.set(headers)\n          }\n\n          if (status) {\n            ctx.status = parseInt(status)\n          }\n\n          if (body) {\n            // ctx.set('Content-Length', body.length)\n            ctx.body = body\n          }\n        }\n      }\n    }]\n  }\n\n}"
  },
  {
    "path": "packages/sharelist/app/shared/send.js",
    "content": ""
  },
  {
    "path": "packages/sharelist/app.js",
    "content": "#!/usr/bin/env node\n\n/**\n * Module dependencies.\n */\n\nconst bootstrap = require('./app/main')\nconst http = require('http')\nconst os = require('os')\nconst fs = require('fs')\n\nconst onError = (error) => {\n  if (error.syscall !== 'listen') {\n    throw error\n  }\n\n  // handle specific listen errors with friendly messages\n  switch (error.code) {\n    case 'EACCES':\n      console.error('Pipe requires elevated privileges')\n      process.exit(1)\n      break\n    case 'EADDRINUSE':\n      console.error('Port is already in use')\n      process.exit(1)\n      break\n    default:\n      throw error\n  }\n}\n\nconst getIpv4 = () => {\n  var ifaces = os.networkInterfaces()\n  for (var dev in ifaces) {\n    for (var i in ifaces[dev]) {\n      var details = ifaces[dev][i]\n      if (/^\\d+\\./.test(details.address)) {\n        return details.address\n      }\n    }\n  }\n}\n\n\nbootstrap().then(({ app, port }) => {\n  const server = http.createServer(app.callback())\n  server.on('error', onError).on('listening', () => {\n    console.log(`[${new Date().toISOString()}] Sharelist Server is running at http://` + getIpv4() + ':' + server.address().port + '/')\n  })\n  server.listen(process.env.PORT || port || 33001)\n})\n"
  },
  {
    "path": "packages/sharelist/docker-compose.yml",
    "content": "version: \"3\"\nservices:\n  sharelist:\n    image: reruin/sharelist\n    volumes:\n      - $HOME/sharelist:/sharelist/cache\n    ports:\n      - \"33001:33001\""
  },
  {
    "path": "packages/sharelist/package.json",
    "content": "{\n  \"name\": \"sharelist\",\n  \"version\": \"0.4.4\",\n  \"bin\": \"app.js\",\n  \"repository\": \"https://github.com/reruin/sharelist\",\n  \"license\": \"MIT\",\n  \"scripts\": {\n    \"start\": \"node app.js\",\n    \"dev\": \"cross-env NODE_ENV=dev nodemon app.js -i ./cache\",\n    \"test\": \"echo \\\"Error: no test specified\\\" && exit 1\",\n    \"changelog\": \"conventional-changelog -p angular -i CHANGELOG.md -s --commit-path .\",\n    \"pkg\": \"pkg . --output build/sharelist --targets linux-x64,linux-arm64,linuxstatic-armv7,macos-x64,win-x64 --public-packages '*'\",\n    \"pkg-local\": \"pkg . --output build/sharelist --targets node16-win-x64 --compress\",\n    \"pkg-linux\": \"pkg . --output build/sharelist --targets node16-linux-x64\",\n    \"release\": \"node ../../scripts/release.js --skipBuild --skipNpmPublish\"\n  },\n  \"dependencies\": {\n    \"@koa/cors\": \"^3.1.0\",\n    \"@koa/router\": \"^12.0.0\",\n    \"@sharelist/core\": \"^0.2\",\n    \"@sharelist/webdav\": \"^0.2\",\n    \"@vue-reactivity/watch\": \"^0.2.0\",\n    \"@vue/reactivity\": \"^3.2.33\",\n    \"bonjour\": \"^3.5.0\",\n    \"etag\": \"^1.8.1\",\n    \"global\": \"^4.4.0\",\n    \"html-entities\": \"^2.3.3\",\n    \"ignore\": \"^5.1.8\",\n    \"koa\": \"^2.13.1\",\n    \"koa-body\": \"^4.2.0\",\n    \"koa-json\": \"^2.0.2\",\n    \"koa-logger\": \"^3.2.1\",\n    \"koa-onerror\": \"^4.1.0\",\n    \"koa-router\": \"^10.0.0\",\n    \"koa-sendfile\": \"^3.0.0\",\n    \"koa-session-minimal\": \"^4.0.0\",\n    \"koa-static-cache\": \"^5.1.4\",\n    \"markdown-it\": \"^12.0.6\",\n    \"mime\": \"^2.5.2\",\n    \"nanoid\": \"^3.1.23\",\n    \"node-fetch\": \"^2.6.1\",\n    \"node-rsa\": \"^1.1.1\",\n    \"semver-compare-lite\": \"^0.1.0\",\n    \"write-file-atomic\": \"^3.0.3\"\n  },\n  \"pkg\": {\n    \"scripts\": [],\n    \"assets\": [\n      \"./web/**/*\",\n      \"./manage/**/*\",\n      \"./plugins.json\"\n    ]\n  }\n}\n"
  },
  {
    "path": "packages/sharelist-core/.prettierrc.js",
    "content": "// https://prettier.io/docs/en/configuration.html\nmodule.exports = {\n  //分号终止符\n  semi: false,\n\n  //行尾逗号\n  trailingComma: \"all\",\n\n  // 使用单引号, 默认false(在jsx中配置无效, 默认都是双引号)\n  singleQuote: true,\n\n  printWidth: 120,\n  // 换行符\n  endOfLine: \"auto\",\n\n  //缩进 default:2\n  tabWidth: 2\n}"
  },
  {
    "path": "packages/sharelist-core/CHANGELOG.md",
    "content": "## [0.1.7](https://github.com/reruin/sharelist/compare/v0.3.6...v0.1.7) (2021-10-14)\n\n\n### Bug Fixes\n\n* **core:** fix some bugs ([c8ee9e6](https://github.com/reruin/sharelist/commit/c8ee9e655687d49420c54c9331a91b19aae10beb))\n\n\n\n## [0.1.6](https://github.com/reruin/sharelist/compare/v0.3.5...v0.1.6) (2021-10-12)\n\n\n\n## [0.1.5](https://github.com/reruin/sharelist/compare/v0.3.3...v0.1.5) (2021-10-10)\n\n\n### Bug Fixes\n\n* **core:** add createReadStream ([27350fb](https://github.com/reruin/sharelist/commit/27350fb6a036ab13e65dc0b52dab3f85732d5667))\n\n\n\n"
  },
  {
    "path": "packages/sharelist-core/LICENSE",
    "content": "MIT License\n\nCopyright (c) 2021-present, Reruin and Sharelist contributors\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE."
  },
  {
    "path": "packages/sharelist-core/README.md",
    "content": "# @sharelist/core [![npm](https://img.shields.io/npm/v/@sharelist/core.svg)](https://npmjs.com/package/@sharelist/core)\n\nIt's a framework for mounting netdisk.\n\n## Useage\n\n```js\nconst sharelist = require('@sharelist/core')\n\nconst driver = sharelistCore({ config, plugins: [/* some sharelist plugins */]})\n\n// And you can use this driver\n\nconst disk = await driver.list()\n\n// find dir\nconst parentDir = disk.files.find(i => i.type == 'folder')\n\n// mkdir\nconst newDir = await driver.mkdir(parentDir.id)\n\n// rename\nawait driver.rename(newDir.id, {name:'new name'})\n\n// upload\nconst fileData = await driver.upload(newDir.id, stream,{ name,size })\n\n// download\ntry{\n  const { stream } = await driver.createReadStream(fileData.id)\n  stream.pipe( fs.createReadStream('./'+fileData.name))\n\n}catch(e){\n\n}\n\n// remove\nawait driver.rm(newDir.id)\n\n\n// Also you can use path locate\nconst disk = driver.createAction()\n\nawait disk.list('/')\n\n// mkdir create dir named 'new_dir'  at '/'\nawait disk.mkdir('/new_dir',)\n\n// rename\nawait disk.mv('/new_dir','/new_dir2')\n\n// move\nawait disk.mv('/new_dir','/some/new_dir')\n\n// rm\nawait disk.rm('/some/new_dir')\n\n//upload\nawait disk.upload('/some/newfile.txt',stream)\n\n```\n"
  },
  {
    "path": "packages/sharelist-core/index.js",
    "content": "module.exports = require('./lib/index')\n"
  },
  {
    "path": "packages/sharelist-core/lib/action.js",
    "content": "/**\n * 提供文件名寻址操作\n * ls / rm / mkdir / stat / upload / get / mv(rename)\n */\n\nconst parsePath = v => v.replace(/(^\\/|\\/$)/g, '').split('/').map(decodeURIComponent).filter(Boolean)\n\nconst getRange = (r, total) => {\n  if (r) {\n    let [, start, end] = r.match(/(\\d*)-(\\d*)/);\n    start = start ? parseInt(start) : 0\n    end = end ? parseInt(end) : total - 1\n\n    return { start, end }\n  }\n}\n\nconst createHeaders = (data, { maxage, immutable, range } = { maxage: 0, immutable: false }) => {\n  let fileSize = data.size\n  let fileName = data.name\n\n  let headers = {}\n\n  headers['Last-Modified'] = new Date(data.mtime).toUTCString()\n\n  if (range) {\n    let { start, end } = range\n    headers['Content-Range'] = `bytes ${start}-${end}/${fileSize}`\n    headers['Content-Length'] = end - start + 1\n    headers['Accept-Ranges'] = 'bytes'\n  } else {\n    header['Content-Range'] = `bytes 0-${fileSize - 1}/${fileSize}`\n    headers['Content-Length'] = fileSize\n  }\n\n  headers['Content-Disposition'] = `attachment;filename=${encodeURIComponent(fileName)}`\n  return headers\n}\n\nconst createAction = (driver, { useProxy, baseUrl } = {}) => ({\n  async ls(path) {\n    let p = path.replace(/(^\\/|\\/$)/g, '')\n    let data = await driver.list({\n      paths: p ? p.split('/').map(decodeURIComponent) : [],\n      params: { perPage: 0, sort: ['name', 1] }\n    })\n    return data?.files\n  },\n  async stat(path) {\n    try {\n      return await driver.stat(parsePath(path))\n    } catch (error) {\n      console.log(error)\n      return { error }\n    }\n  },\n  async get(path, options) {\n    let data = await driver.get({ paths: parsePath(path) })\n    if (!options.reqHeaders) options.reqHeaders = {}\n    delete options.reqHeaders.connection\n    options.reqHeaders['user-agent'] = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Safari/537.36'\n    if (data && data.download_url && !data.extra.proxy && !useProxy()) {\n      return {\n        status: 302,\n        body: data.download_url\n      }\n    } else {\n      let range = getRange(options.reqHeaders.range, data.size) || { start: 0, end: data.size ? (data.size - 1) : '' }\n      let { stream, status, headers, enableRanges = false } = await driver.createReadStream(data.id, range)\n      let isReqRange = !!options.reqHeaders.range\n      if (stream) {\n        let options = enableRanges ? { range } : {}\n        let resHeaders = headers || createHeaders(data, options)\n        return {\n          body: stream,\n          status: status || (isReqRange && enableRanges ? 206 : 200),\n          headers: resHeaders\n        }\n      }\n    }\n  },\n  async upload(path, stream, { size }) {\n    stream.pause?.()\n\n    let paths = parsePath(path)\n\n    let name = paths.pop()\n\n    let data = await driver.stat(paths)\n\n    let existData = await driver.stat([...paths, name])\n\n    if (existData) {\n      await driver.rm(existData.id)\n    }\n\n    if (!data.id) {\n      return { error: { code: 404 } }\n    }\n    let ret = await driver.upload(data.id, stream, { name, size, hash: {}, state: {} })\n    if (!ret) {\n      return {\n        error: { code: 500 }\n      }\n    } else {\n      return ret\n    }\n\n  },\n  async mkdir(path) {\n    let paths = parsePath(path)\n    let name = paths.pop()\n    let parentData = await driver.stat(paths)\n    return await driver.mkdir(parentData.id, name)\n  },\n  async rm(path) {\n    let paths = parsePath(path)\n    let data = await driver.stat(paths)\n    return await driver.rm(data.id)\n  },\n  // /d/e/1.txt -> /d\n  async mv(path, destPath, copy) {\n    // The destination path can NOT be in the source path (include the same path)\n    // e.g. /a/b => /a/b , /a/b => /a/b/c , ( /a/b => /a/b1, /a/b => /a/b1/c )\n    console.log('mv:', path, destPath)\n    let paths = parsePath(path)\n    let destPaths = parsePath(destPath)\n\n    if ((destPaths.join('/') + '/').startsWith(paths.join('/' + '/'))) throw { code: 409 }\n\n    let data = await driver.stat(paths)\n    if (!data?.id) throw { code: 404 }\n\n    let srcId = data.id\n\n    let isSameDir = paths.length == destPaths.length && paths.slice(0, -1).join('/') == destPaths.slice(0, -1).join('/')\n\n    // rename\n    if (isSameDir) {\n      if (paths.slice(-1)[0] == destPaths.slice(-1)[0]) throw { code: 409 }\n\n      if (!copy) {\n        await driver.rename(srcId, destPaths.slice(-1)[0])\n        return\n      }\n    }\n\n    let dest = await driver.stat(destPaths)\n\n    let destId, destName, srcName = paths.pop()\n\n    //if destination exists\n    if (dest?.id) {\n      // destination must be a folder\n      if (dest.type != 'folder' && dest.type != 'drive') throw { code: 409 }\n\n      destId = dest.id\n\n    }\n    // destination does not exist\n\n    else {\n      let destParent = await driver.stat(destPaths.slice(0, -1))\n      //dest parent must be a folder\n      if (destParent?.type != 'folder' && destParent?.type != 'drive') throw { code: 404 }\n\n      destName = destPaths.pop()\n      destId = destParent.id\n\n    }\n\n    let options = { copy }\n\n    //move and rename\n    if (destName && srcName != destName) options.name = destName\n\n    console.log('move:', options)\n    await driver.mv(srcId, destId, options)\n\n    return { status: 201 }\n  }\n\n})\n\n\nmodule.exports = createAction"
  },
  {
    "path": "packages/sharelist-core/lib/driver.js",
    "content": "const utils = require('./utils')\nconst request = require('./request')\nconst { PassThrough } = require('stream')\n\nconst clone = (obj) => {\n  // console.log(obj)\n  let type = typeof obj\n  if (type == 'number' || type == 'string' || type == 'boolean' || type === undefined || type === null) {\n    return obj\n  }\n\n  if (obj instanceof Array) {\n    return obj.map((i) => clone(i))\n  }\n\n  if (obj instanceof Object) {\n    let copy = {}\n    for (let attr in obj) {\n      if (obj.hasOwnProperty(attr)) copy[attr] = clone(obj[attr])\n    }\n    return copy\n  }\n\n  return obj\n}\n\nconst sortFiles = (files, orderBy) => {\n  let [key, isAsc] = orderBy\n  let aVal = isAsc ? 1 : -1\n  let bVal = isAsc ? -1 : 1\n  return files.sort((a, b) => a[key] > b[key] ? aVal : bVal).sort((a, b) => {\n    if (a.type == 'folder' && b.type != 'folder') {\n      return -1\n    } else if (a.type != 'folder' && b.type == 'folder') {\n      return 1\n    } else {\n      return 0\n    }\n  })\n\n}\n\n/**\n * driver 采用 id 作为首选寻址方式\n */\nmodule.exports = (app) => {\n\n  const getDrive = (id) => app.getDrive(id)\n\n  /**\n   * list\n   * @param {object} options \n   * @param {string} options.id\n   * @param {array} options.paths\n   * @param {object} options.params\n   * @param {boolean} options.ignoreInterceptor\n   * \n   * @returns {array<file>}\n   */\n  const list = async ({ paths = [], id, params, ignoreInterceptor = false } = {}) => {\n    await app.emit('beforeList', { paths, id, params })\n    let data = id ? await listById(id, params) : await listFromPathAddressing([...paths], params)\n\n    /*\n    if (params.orderBy && data.files) {\n      data.files = sortFiles([...data.files], params.orderBy)\n    }\n    */\n    await app.emit('afterList', { paths, id, data, params })\n\n    return clone(data)\n  }\n\n  /**\n   * @param {object} options \n   * @param {string} options.id\n   * @param {array} options.paths\n   * \n   * @returns {object}\n   */\n  const get = async ({ paths = [], id }, options) => {\n    let data\n\n    if (!id && paths) {\n      data = await stat(paths, more)\n      id = data.id\n    }\n    if (id) {\n      let r = await getById(id, options)\n      if (!r.error) {\n        data = r\n      }\n    }\n\n    if (!data) return app.error({ code: 404 })\n\n    // if (data.extra && data.extra.path) {\n    //   let drive = root().files.find((i) => id.startsWith(i.id))\n    //   data.path = drive.name + data.extra.path + '/' + data.name\n    // }\n    return { ...data }\n  }\n\n\n  /**\n   * get file by paths\n   * @param array<string> paths\n   * @returns \n   */\n  const stat = async (paths) => {\n    let parentPath = paths.slice(0, paths.length - 1)\n    let filename = paths[paths.length - 1]\n    let parent = await listFromPathAddressing(parentPath)\n    if (parent.error) return undefined\n\n    if (paths.length == 0) {\n      return {\n        id: parent.id,\n        type: 'folder',\n        name: '@sharelist_root',\n        size: parent.size || Number.MAX_SAFE_INTEGER\n      }\n    } else {\n      return parent?.files.find(i => i.name == filename)\n    }\n  }\n\n\n  /**\n   * 根据uri获取资源详情\n   * @param {*} uri \n   * @returns \n   */\n  const getById = async (uri, { more = false, enableCache = true } = {}) => {\n    let { drive, encode, config, id, name } = await getDrive(uri)\n\n    // if (isRoot(id)) {\n    //   return {\n    //     id: encode(id),\n    //     name,\n    //     type: 'folder'\n    //   }\n    // }\n\n    let cacheId = `${encode(id)}#get`\n    if (config.cache !== false && enableCache) {\n      let r = app.cache.get(cacheId)\n      if (r) {\n        console.log(`[CACHE] ${new Date().toISOString()} ${cacheId}`)\n        return r\n      }\n    }\n\n    if (!drive?.get) return { error: { message: '' } }\n\n    let data = await drive.get(id, more)\n    data.id = encode(data.id)\n\n    //鉴于某些driver get 无法获取name和size，此处需通过 parent files 进行补充\n    if (!data.name && data.extra?.parent_id) {\n      let parent = await listById(encode(data.extra.parent_id))\n      let last = parent.files.find(i => i.id == data.id)\n      if (last) {\n        data.name = last.name\n        data.size = last.size\n        if (last.type == 'file') {\n          if (last.extra.md5) data.extra.md5 = last.extra.md5\n          if (last.extra.sha1) data.extra.sha1 = last.extra.sha1\n        }\n      }\n    }\n\n    if (config.cache !== false) {\n      let max_age = data.max_age || 0\n\n      if (max_age) {\n        app.cache.set(cacheId, data, max_age)\n      }\n    }\n\n    return data\n  }\n\n\n  /**\n   * 获取可下载链接\n   * @param {*} uri \n   * @returns \n   */\n  const get_download_url = async (uri) => {\n    let cacheId = `${uri}#download`\n    let r = app.cache.get(cacheId)\n    if (r) {\n      console.log(`[CACHE] ${new Date().toISOString()} ${cacheId}`)\n      return r\n    }\n\n    let { drive, config, id } = await getDrive(uri)\n\n    if (drive?.get_download_url) {\n\n      let data = await drive.get_download_url(id)\n\n      if (data.url && config.cache !== false) {\n        if (data.max_age) {\n          app.cache.set(`${id}#download`, data, data.max_age)\n        }\n      }\n\n      return data\n    }\n  }\n\n  const getParentId = async (id) => {\n    let data = await getById(id)\n    return data?.extra?.parent_id\n  }\n\n\n  /**\n   * 返回指定id的只读流\n   * @param {string} uri\n   * @param {object} options \n   * @param {object | undefined} options.reqHeaders\n   * @param {number} options.start offset start\n   * @param {number} options.end offset end\n   * \n   * @returns { stream , enableRanges , headers? , status?  }\n   */\n  const createReadStream = async (uri, options = {}) => {\n    let { drive, id } = await getDrive(uri)\n\n    let default_ua = app.config.default_ua\n    console.log('dua', default_ua)\n    if (drive?.createReadStream) {\n      let stream = await drive.createReadStream(id, options)\n      stream.once('error', () => { })\n      return { stream, enableRanges: true }\n    } else {\n      let data = await get({ id: uri })\n      if (data.download_url) {\n        let { start, end, reqHeaders = {}, ...reqOptions } = options || {}\n\n        if (data.extra.proxy?.headers) {\n          Object.assign(reqHeaders, data.extra.proxy.headers)\n        }\n        if (data.extra.req_user_agent && !(reqHeaders['user-agent'] || reqHeaders['User-Agent'])) {\n          reqHeaders['user-agent'] = default_ua\n        }\n        if (options.start !== undefined) {\n          reqHeaders['range'] = `bytes=${start}-${end || ''}`\n        }\n        reqOptions.headers = reqHeaders\n        reqOptions.responseType = 'stream'\n\n        let { data: stream, headers, status, error } = await request(data.download_url, reqOptions)\n\n        if (!error) {\n          return { stream, headers, status, enableRanges: headers?.['accept-ranges'] == 'bytes' || status == 206 || headers?.['content-range'] }\n        }\n      }\n    }\n\n    throw { code: 501, message: \"Not implemented\" }\n  }\n\n  //获取文本内容\n  const getContent = async (id, charset = 'utf-8') => {\n    try {\n      let { stream } = await createReadStream(id)\n      if (!stream) return null\n      return await utils.transfromStreamToString(stream, charset)\n    } catch (e) {\n      return null\n    }\n  }\n\n  /**\n   * 返回指定可写流\n   * @param {string} uri\n   * @param {object} options \n   * @param {number} options.size\n   * @param {string} options.name\n   * @param {string} options.sha1?\n   * @param {string} options.md5?\n   * \n   * @returns { stream:WritableStream , doneHandler:Function  }\n   * @public\n   */\n  const createWriteStream = async (uri, options) => {\n    let { drive, id, encode, config } = await getDrive(uri)\n\n    if (drive?.upload) {\n      let passStream = new PassThrough()\n\n      let done = (cb) => {\n        done.cb = cb\n      }\n      drive.upload(id, passStream, { ...options }).then(res => {\n        done.cb?.(res)\n      })\n\n      return { stream: passStream, done }\n    }\n\n    app.error({ code: 501, message: \"Not implemented\" })\n  }\n\n  /**\n   * upload\n   * @param {string} uri\n   * @param {stream | () => stream} stream\n   * @param {object} options\n   * @param {number} options.size\n   * @param {string} options.name\n   * @param {string} options.hash\n   * @param {object} options.state\n   * @param {string} options.conflictBehavior replace|rename|fail\n   * \n   * @returns {object}\n   * \n   * @public\n   */\n  const upload = async (uri, stream, options) => {\n    let { drive, id, config, encode } = await getDrive(uri)\n    stream?.pause?.()\n\n    if (drive?.upload) {\n      if (!options.state) options.state = {}\n      if (!options.hash) options.hash = {}\n      let data = await drive.upload(id, stream, options)\n      if (data.id) {\n        data.id = encode(data.id)\n        if (config.cache !== false) {\n          app.cache.remove(`${encode(id)}#list`)\n        }\n      }\n\n      return data\n    }\n\n    app.error({ code: 501, message: \"Not implemented\" })\n  }\n\n  /**\n   * mkdir\n   * @param {string} uri\n   * @param {string|Array<string>} name\n   * @param {object} options\n   * @param {boolean} strict 严格模式，此模式会事先判断是否存在目录\n   * @returns {object}\n   * \n   * @public\n   */\n  const mkdir = async (uri, name, options = {}, strict = false) => {\n    let { drive, id, encode, config } = await getDrive(uri)\n\n    if (drive?.mkdir) {\n      if (typeof name == 'string') {\n        name = [name]\n      }\n      let data\n      while (name.length && id) {\n        let curName = name.shift(), targetExist\n        if (strict) {\n          let dir = await drive.list(id)\n          targetExist = dir.files?.find(i => i.name == curName)\n        }\n\n        if (targetExist) {\n          data = { id: targetExist.id, name: targetExist.name, parent_id: id }\n        } else {\n          data = await drive.mkdir(id, curName, options)\n          if (config.cache !== false) {\n            app.cache.remove(`${encode(id)}#list`)\n          }\n        }\n        if (data.id) id = data.id\n\n      }\n\n      if (data.id) data.id = encode(data.id)\n\n      return data\n    }\n\n    app.error({ code: 501, message: \"Not implemented\" })\n  }\n\n  /**\n   * remove\n   * @param {string|Array<string>} uri\n   * @param {object} options\n   * @returns {object}\n   * \n   * @public\n   */\n  const rm = async (uri) => {\n    let { drive, id, encode, config } = await getDrive(uri)\n    if (drive?.rm) {\n      let data = await drive.rm(id)\n\n      if (config.cache !== false) {\n        // clear cache\n        app.cache.remove(`${encode(id)}#get`)\n        if (!data.parent_id) {\n          data.parent_id = await getParentId(uri)\n        }\n        if (data.parent_id) app.cache.remove(`${encode(data.parent_id)}#list`)\n      }\n\n      return data\n    }\n    app.error({ code: 501, message: \"Not implemented\" })\n  }\n\n  /**\n   * rename\n   * @param {string} uri\n   * @param {string} name new file name\n   * @returns {object}\n   * \n   * @public\n   */\n  const rename = async (uri, name, options = {}) => {\n    let { drive, id, encode, config } = await getDrive(uri)\n    if (drive?.rename) {\n      let data = await drive.rename(id, name, options)\n\n      if (config.cache !== false) {\n        // clear cache\n        app.cache.remove(`${uri}#get`)\n\n        //clear parent cache\n        if (!data.parent_id) {\n          data.parent_id = await getParentId(uri)\n        }\n        if (data.parent_id) app.cache.remove(`${encode(data.parent_id)}#list`)\n      }\n\n      return data\n    }\n    app.error({ code: 501, message: \"Not implemented\" })\n  }\n\n  //only support same protocol  \n  const mv = async (uri, target_uri, options = {}) => {\n    if (app.isSameDrive(uri, target_uri)) {\n\n      let { drive, id, encode, config } = await getDrive(uri)\n\n      if (drive?.mv) {\n        let cache = config.cache !== false\n\n        let { id: target_id } = await getDrive(target_uri)\n\n        // get origin parent id\n        let originParentId\n        if (!options.copy) {\n          originParentId = await getParentId(uri)\n        }\n\n        let data = await drive.mv(id, target_id, { ...options })\n\n        // clear cache\n        if (cache) {\n          //在有缓存的情况下 取得的是原位置的父级id\n          if (!data.parent_id) {\n            data.parent_id = target_id\n          }\n\n          //clear new parent cache\n          app.cache.remove(`${encode(target_id)}#list`)\n\n          //clear target cache\n          app.cache.remove(`${uri}#get`)\n\n          //clear origin parent cache if request move\n          if (!options.copy) {\n            originParentId = data.origin_parent_id || originParentId\n            app.cache.remove(`${encode(originParentId)}#list`)\n          }\n        }\n\n        return data\n      }\n    }\n\n    app.error({ code: 501, message: \"Not implemented\" })\n  }\n\n  const listById = async (uri, params = {}) => {\n    let { drive, id, encode, config } = await getDrive(uri)\n\n    let { pagination, ...options } = params\n\n    if (app.config.per_page && pagination !== false) {\n      options.perPage = app.config.per_page\n    }\n    const cacheable = !options.search && config.cache !== false\n\n    let cacheId = `${encode(id)}#list`\n\n    if (cacheable) {\n      let r = app.cache.get(cacheId)\n      if (!!r) {\n        console.log(`[CACHE] ${new Date().toISOString()} ${cacheId}`)\n\n        //adjust sort\n        if (params.orderBy && r.files) {\n          r.files = sortFiles([...r.files], params.orderBy)\n        }\n        return { ...r, config }\n      }\n    }\n\n    if (!drive) return app.error({ code: 501, message: `Not implemented. (Resource URI:${uri})` })\n\n    let { id: realId, files, maxAge, nextPage } = await drive.list(id, options)\n\n    files.forEach(i => {\n      i.id = encode(i.id)\n      if (i.extra?.parent_id) {\n        i.extra.parent_id = encode(i.extra.parent_id)\n      }\n    })\n\n    let data = { id: encode(realId || id), files }\n\n    // Cache will be disabled if enable pagination\n    if (cacheable && files?.length > 0 && (!params.nextPage && !nextPage)) {\n      let maxAgeDir = maxAge || config.maxAgeDir || app.config.max_age_dir || 0\n\n      if (maxAgeDir) {\n        app.cache.set(cacheId, data, maxAgeDir)\n      }\n    }\n\n    return { ...data, config, nextPage }\n  }\n\n  /*\n   * Get data by path\n   *\n   * @param {string} [p] path id\n   * @param {function} [interceptor] interceptor\n   * @return {array|object}\n   * @api private\n   */\n  const listFromPathAddressing = async (paths, params) => {\n    let hit = await app.getRoot(params)\n    //扩展唯一的文件夹\n    if (app.config.expand_single_disk && hit.files && hit.files.length == 1) {\n      paths.unshift(hit.files[0].name)\n    }\n\n    // root path\n    if (paths.length == 0) {\n      return hit\n    }\n\n    for (let i = 0; i < paths.length; i++) {\n\n      if (hit.files) {\n        hit = hit.files.find((j) => j.name == paths[i])\n        if (!hit) {\n          return app.error({ code: 404, message: `Can't find [${paths[i]}] folder` })\n        }\n      }\n\n      //if (!drive) return app.error({ code: 501, message: \"Not implemented\" })\n      hit = await listById(hit.id, paths.length - 1 == i ? { ...params } : {})\n\n      await app.emit('afterList', { data: hit, params, paths: paths.slice(0, i + 1) })\n\n    }\n    return hit\n  }\n\n  /**\n   * 根据id 获取路径层级\n   * @param {*} uri \n   * @returns \n   */\n  const pwd = async (uri) => {\n    let dirs = []\n    let { encode, protocol, name, drive, id } = await getDrive(uri)\n    if (drive.pwd) {\n      dirs = await drive.pwd(id)\n      if (dirs) {\n        dirs.forEach(i => {\n          i.id = encode(i.id)\n        })\n      }\n    } else {\n      while (id) {\n\n        if (drive.isRoot(id)) break\n\n        let data = await getById(encode(id))\n\n        if (!data) break\n\n        dirs.unshift(data)\n\n        if (!data?.extra?.parent_id || data.id == '@drive_root') break\n\n        id = data?.extra.parent_id\n      }\n    }\n    console.log('>>>>>pwd', dirs)\n    return [{ id: 'root', name, type: 'folder' }, ...dirs.map(i => ({ id: i.id, name: i.name, type: i.type }))]\n    // if(parent?.extra)\n  }\n\n  const pwd_path = async (path) => {\n\n  }\n\n  const clearSession = async (uri, data) => {\n    let { drive, id } = await getDrive(uri)\n    drive.clearSession?.(id, data)\n  }\n\n  const hashUpload = async (uri, { hash, name, size }) => {\n    let { drive, id, config, encode } = await getDrive(uri)\n\n    if (!config?.hashUpload) {\n      app.error({ code: 429 })\n    }\n    let hashType = typeof config.hashUpload == 'string' ? config.hashUpload : config.hashUpload.type\n    let options = {\n      name,\n      state: {},\n      hash: {\n        [hashType]: hash\n      }\n    }\n    if (size) {\n      options.size = size\n    }\n    let { completed, ...data } = await drive.upload(id, null, options)\n\n    if (completed) {\n      data.id = encode(data.id)\n      if (config.cache !== false) {\n        app.cache.remove(`${encode(id)}#list`)\n      }\n      return data\n    }\n    // }\n    app.error({ code: 404, message: \"file is non-exist\" })\n  }\n\n  return {\n    list,\n    get,\n    stat,\n    mkdir,\n    rm,\n    rename,\n    mv,\n    pwd,\n    upload,\n    hashUpload,\n    clearSession,\n    createReadStream,\n    createWriteStream,\n    get_download_url,\n    getContent\n  }\n}\n"
  },
  {
    "path": "packages/sharelist-core/lib/index.js",
    "content": "const { URL } = require('url')\n\nconst utils = require('./utils')\n\nconst createDriver = require('./driver')\n\nconst actionFactory = require('./action')\n\nconst request = require('./request')\n\nconst { createRectifier, streamReader, createChunkStream } = require('./rectifier')\n\nconst { isFunction, isClass, createCache } = utils\n\nconst plugins = []\n\nconst DRIVE_KEY = Symbol('drive_key')\n\nconst error = (error) => {\n  console.trace(error)\n  throw error\n}\n\n\nconst isSameDrive = async (src, dest) => {\n  let a = decode(src), b = decode(dest)\n  return a.protocol === b.protocol && a.key === b.key\n}\n\nconst decode = (p) => {\n  let hasProtocol = p.includes('://')\n  if (!hasProtocol) p = 'sharelist://' + p\n  let data = new URL(p)\n  let protocol = data.protocol.replace(':', '')\n\n  let result = {\n    protocol,\n    key: data.host,\n    path: decodeURIComponent(data.pathname || ''),\n  }\n\n  if (result.path) result.path = result.path.replace(/^\\/+/, '')\n  if (hasProtocol) result.protocol = data.protocol.split(':')[0]\n\n  if (!result.path) result.path = undefined\n  return result\n}\n\nconst deduceConfig = (drive) => {\n  return {\n    readonly: !drive.upload\n  }\n}\nconst createSingleton = (plugin, inject) => {\n  let { module, protocol } = plugin\n\n  return {\n    id: protocol,\n\n    name: protocol,\n\n    config: {},\n\n    configHash: '',\n\n    encode: path => path,\n\n    decode: path => path,\n\n    drive: new module(inject),\n\n    isRoot: () => true\n  }\n}\n\nexports.createPluginLoader = async (options) => {\n\n  const config = options.config || {}\n\n  const cache = options.cache || createCache()\n\n  //save index of driver in plugins array\n  const driversMap = new Map()\n\n  const loadDrives = (plugin, inject, override = false) => {\n    let { module, protocol, options } = plugin\n\n    const drivesConfig = config.drives.filter(i => i.protocol === protocol)\n\n    const keyProp = options.key\n\n    const defaultRoot = options.defaultRoot\n\n    const activeDrives = []\n\n    drivesConfig.forEach(({ name, config, id }) => {\n\n      let configHash = utils.hash(JSON.stringify(config))\n\n      let driveIndex = plugin.drives.findIndex(i => i.id == id)\n\n      activeDrives.push(id)\n\n      // if config has not changed OR driver has not changed.\n      if (driveIndex >= 0 && plugin.drives[driveIndex].configHash == configHash && !override) {\n        return\n      }\n\n      console.log('  - mount: ' + id)\n\n      //privacy\n      let getKey = () => {\n        return utils.hash('sharelist_' + (config[DRIVE_KEY] || (keyProp ? config[keyProp] : id) || protocol))\n      }\n\n      let configer = {\n        get() {\n          return { ...config }\n        },\n        set(data) {\n          for (let i in data) {\n            config[i] = data[i]\n          }\n        }\n      }\n\n      const drive = new module(inject, config)\n      // inject\n      if (!drive.isRoot) {\n        drive.isRoot = function (path) {\n          return (config.root_id || defaultRoot) === path\n        }\n      }\n\n      const driveMeta = {\n        id,\n\n        // drive name (the disk name set by user)\n        name,\n\n        // Each drive has its unique ID, but it's not suitable for use as cache ID.\n        // Because they may use the same configuration.\n        // so it MUST be provide a another key for driver plugin. \n        getKey,\n\n        // drive config\n        config,\n\n        // drive config hash\n        configHash,\n\n        // uri encode\n        encode: (path) => {\n          let pathname = path === undefined || path === '' ? '/' : '/' + path\n          let ret = `${protocol}://${getKey() || ''}${pathname}`\n          return ret\n        },\n\n        // drive instance\n        drive\n        // drive: classMode ? new module(sharelist, drive.config) : module.call(module, sharelist, drive.config)\n      }\n\n      // update/create\n      if (driveIndex >= 0) {\n        plugin.drives[driveIndex] = driveMeta\n      } else {\n        plugin.drives.push(driveMeta)\n      }\n\n    })\n\n    //remove others\n    for (let i = plugin.drives.length; i--;) {\n      if (!activeDrives.includes(plugin.drives[i].id)) {\n        console.log('  - unmount: ' + plugin.drives[i].id)\n        plugin.drives[i].drive?.destroy?.()\n\n        plugin.drives[i].drive = null\n        plugin.drives[i].encode = null\n        plugin.drives[i].getKey = null\n        plugin.drives.splice(i, 1)\n      }\n    }\n\n    // driver.drives = driver.drives.filter(i => existDrives.includes(i.id))\n  }\n\n  /**\n   * 获取指定协议的挂载驱动\n   */\n  const getDriver = (protocol) => {\n    if (protocol) {\n      let idx = driversMap.get(protocol)\n      if (idx !== undefined) {\n        return plugins[idx]\n      }\n    } else {\n      return plugins.filter(i => i.isDriver).map(i => ({\n        name: i.name,\n        protocol: i.protocol,\n        guide: i.options.guide,\n        mountable: i.options.mountable !== false\n      }))\n    }\n  }\n\n  /**\n   * 根据URI获取磁盘元信息\n   */\n  const getDrive = async (uri) => {\n    let { protocol, key, path } = decode(uri)\n\n    let plugin = getDriver(protocol)\n\n    if (!plugin) return {}\n\n    let hit = plugin.singleton || plugin?.drives?.find(i => i.getKey() == key)\n\n    if (!hit) return {}\n\n    if (hit.decode) {\n      path = hit.decode(uri)\n    }\n\n    let config = Object.assign(deduceConfig(hit.drive), plugin.options || {}, await hit?.drive?.getOptions?.() || {})\n\n    return {\n      name: hit.name,\n      drive: hit.drive,\n      config,\n      id: path || plugin.options?.defaultRoot,\n      encode: hit.encode,\n      protocol,\n      isRoot: hit.isRoot\n    }\n  }\n\n  const getRoot = async ({ orderBy } = {}) => {\n    const disk = []\n    for (let i of config.drives) {\n      let { id, protocol, name, config } = i\n      let plugin = getDriver(protocol)\n      let hit = plugin?.drives?.find(i => i.id == id)\n      // sources 和 drive 未做区分\n\n      if (hit) {\n        let config = Object.assign({}, plugin.options || {}, await hit?.drive?.getOptions?.() || {})\n\n        disk.push({\n          id: hit.encode(config.root_id || plugin.options?.defaultRoot),//protocol + '://' + drive.getKey(),\n          name: name,\n          size: 0,\n          mtime: '',\n          ctime: '',\n          type: 'drive',\n          config,\n          extra: {\n            config_id: id,\n          }\n        })\n      }\n    }\n\n\n    if (orderBy?.[0] == 'name') {\n      let aVal = orderBy[1] ? 1 : -1\n      let bVal = aVal == 1 ? -1 : 1\n      disk.sort((a, b) => a.name > b.name ? aVal : bVal)\n    }\n\n    return { id: 'root:', type: 'drive', name: '', files: disk, config: { pagination: false, search: false, isRoot: true } }\n  }\n\n  // load plugin\n  const load = async (newPlugins, override = false) => {\n    if (!Array.isArray(newPlugins)) {\n      newPlugins = [newPlugins]\n    }\n    // preprocessing: \n    // plugins with the same name will be overwrittenexcept for drivers which differentiated by protocol.\n\n    let validPlugins = []\n\n    for (let { name, module, hash } of newPlugins) {\n      if (plugins.find(i => i.hash == hash)) {\n        continue\n      }\n\n      if (isFunction(module)) {\n        module = module(inject)\n      }\n\n      // plugin upgrade/downgrade\n      let { driver, ...pluginData } = module\n\n      pluginData.name = name\n      pluginData.hash = hash\n\n      if (!!driver) {\n\n        let options = driver.options\n        let idx = validPlugins.findIndex(i => i.protocol == options.protocol)\n        pluginData.isDriver = true\n        pluginData.options = options\n        pluginData.drives = []\n        pluginData.protocol = options.protocol\n        pluginData.module = driver\n\n        if (idx >= 0) {\n          validPlugins.splice(idx, 1)\n        }\n      }\n\n      validPlugins.push(pluginData)\n    }\n\n    for (let plugin of validPlugins) {\n      let { hash, isDriver } = plugin\n\n      if (isDriver) {\n        const protocol = plugin.protocol\n\n        // driver must be update when: \n        // 1. exist plugins that handle the same protocol.\n        // 2. plugin content has been changed.\n\n        let existPlugin = getDriver(protocol)\n        const isDriverUpdated = !existPlugin || existPlugin.hash != hash\n\n\n        if (isDriverUpdated) {\n          console.log('load driver ' + protocol, plugins.length)\n          driversMap.set(protocol, plugins.length)\n        }\n        //singleton\n        if (plugin.options.singleton) {\n          console.log('  - mount: singleton')\n          plugin.singleton = createSingleton(plugin, inject)\n        } else {\n          loadDrives(plugin, inject, isDriverUpdated)\n        }\n      } else {\n        let existIndex = plugins.findIndex(i => i.name === plugin.name && !i.isDriver)\n        if (existIndex >= 0) {\n          plugins.splice(existIndex, 1)\n        }\n      }\n\n      plugins.push(plugin)\n\n    }\n\n  }\n\n  const unload = (hashes) => {\n\n    for (let i = plugins.length; i--;) {\n      let plugin = plugins[i]\n      let { hash } = plugin\n      if (!hashes.includes(hash)) continue\n\n      //driver plugin\n      if (plugin.protocol) {\n\n        plugin.drives.forEach(i => {\n          console.log('  - unmount drive: ' + i.id)\n\n          i.drive?.destroy?.()\n          i.drive = null\n          i.encode = null\n          i.getKey = null\n        })\n\n        driversMap.delete(plugin.protocol)\n      }\n\n      plugins.splice(i, 1)\n    }\n\n  }\n\n  const loadConfig = () => {\n    driversMap.forEach(i => {\n      let plugin = plugins[i]\n      if (plugin) {\n        loadDrives(plugin, inject)\n      }\n    })\n  }\n\n  const ocr = async (image, type, lang) => {\n    if (config.ocr_server) {\n      let { data } = await request.post(config.ocr_server, {\n        method: 'post',\n        contentType: 'json',\n        data: { image }\n      })\n      return { code: data.result }\n    }\n    return { error: { message: 'ocr server is NOT ready!' } }\n  }\n\n  const getPluginConfig = () => {\n    const config = plugins.map((i) => i.config ? ({ name: i.name, config: i.config() }) : undefined).filter(Boolean)\n    return config\n  }\n\n  const emit = async (type, ...rest) => {\n    const fns = plugins.map((i) => i[type]).filter(Boolean)\n\n    for (let fn of fns) {\n      await fn(...rest)\n    }\n  }\n\n  const inject = {\n    DRIVE_KEY,\n    config,\n    cache,\n    createCache,\n    request,\n    utils,\n    error,\n    ocr,\n    isSameDrive,\n    emit,\n    getDriver,\n    getDrive,\n    getRoot,\n    createRectifier,\n    streamReader,\n    createChunkStream\n  }\n\n  //抽象驱动\n  const driver = createDriver(inject)\n\n  const createAction = (options) => actionFactory(driver, options)\n\n  inject.driver = driver\n\n  load(options.plugins)\n\n  return {\n    ...driver,\n    createAction,\n    load,\n    unload,\n    loadConfig,\n    getPluginConfig,\n    isSameDrive,\n    getDriver\n  }\n}\n\nexports.request = request\n"
  },
  {
    "path": "packages/sharelist-core/lib/rectifier.js",
    "content": "const { resolve4 } = require('dns')\nconst { Readable, Writable, PassThrough } = require('stream')\nconst request = require('./request')\n\n//限速\nconst throttleStream = (stream) => {\n  stream.pause()\n\n  setTimeout(() => {\n    stream.resume()\n  }, 1000)\n}\n\nexports.streamReader = function (readStream, { highWaterMark } = { highWaterMark: 10 * 1024 * 1024 }) {\n  const buffers = []// 缓冲区\n\n  let total = 0\n  let tasks = []\n  let ended = false\n  const sliceBuffer = (n) => {\n    let part = Buffer.alloc(n)\n    let b\n    let index = 0\n    let flag = false\n    while (null != (b = buffers.shift())) {\n      for (let i = 0; i < b.length; i++) {\n        part[index++] = b[i]\n        if (index == n) {//填充完毕\n          //将多出的部分存回头部\n          b = b.slice(i + 1)\n          buffers.unshift(b)\n          flag = true\n          break;\n        }\n      }\n      if (flag) break;\n    }\n    total -= n\n    return part\n  }\n\n\n  const check = () => {\n    let task = tasks[0]\n\n    if (task) {\n      if (total >= task.size) {\n        let chunk = sliceBuffer(task.size)\n        task.done(chunk)\n        tasks.shift()\n      } else {\n        if (ended) {\n          let chunk = sliceBuffer(total)\n          task.done(chunk)\n          tasks.shift()\n        }\n      }\n    }\n\n    if (!ended) {\n      if (tasks.length == 0 && total > highWaterMark) {\n        // 直接暂停 可能导致客户端 timeout\n        throttleStream(readStream)\n\n      } else {\n        if (readStream.isPaused()) {\n          readStream.resume()\n        }\n      }\n    }\n  }\n\n  const read = (size) => new Promise((resolve, reject) => {\n    if (ended && total == 0) {\n      resolve()\n    } else {\n      tasks.push({ size, done: resolve })\n      check()\n    }\n  })\n\n\n  readStream.on('data', (chunk) => {\n    total += chunk.length\n    buffers.push(chunk.slice(0))\n    check()\n\n  })\n\n  readStream.on('end', () => {\n    ended = true\n    check()\n    //let task = tasks.pop()\n  })\n  return { read }\n}\n\nexports.rectifier = function (size = 1 * 1024 * 1024, cb) {\n\n  const buffers = []// 缓冲区\n\n  let total = 0\n  let part = 0\n  let partSize = 0\n  let cacheRate = 3\n\n  const writable = new Writable({\n    write(chunk, encoding, callback) {\n      let bytesRead = chunk.length\n\n      partSize += bytesRead\n      total += bytesRead\n\n      buffers.push(chunk.slice(0))\n\n      //截取片段\n      if (partSize >= size) {\n        let n = size\n        let partBuffer = Buffer.alloc(n)\n        let b\n        let index = 0\n        let flag = false\n        while (null != (b = buffers.shift())) {\n          for (let i = 0; i < b.length; i++) {\n            partBuffer[index++] = b[i]\n            if (index == n) {//填充完毕\n\n              //将多出的部分存回头部\n              b = b.slice(i + 1)\n              buffers.unshift(b)\n              partSize = b.length\n              flag = true\n              break;\n            }\n          }\n          if (flag) break;\n        }\n\n        // console.log('partSize', size, partSize, total)\n        //缓冲\n        if (partSize < size * cacheRate) {\n          callback()\n        }\n\n        cb({ total, chunk: partBuffer, chunk_index: part++ }, () => {\n          //callback()\n        })\n      } else {\n        //继续写入\n        callback()\n      }\n    },\n\n    // 处理剩余的部分\n    final(callback) {\n      let n = total - size * part\n\n      // 文件大小是 size 的倍数时\n      if (n == 0) {\n        callback()\n        return\n      }\n\n      //处理掉剩余部分\n      let partBuffer = Buffer.alloc(n)\n      let b\n      let index = 0\n      while (null != (b = buffers.shift())) {\n        for (let i = 0; i < b.length; i++) {\n          partBuffer[index++] = b[i]\n        }\n      }\n\n      cb({ total, chunk: partBuffer, chunk_index: part++, ended: true }, () => {\n        callback()\n      })\n    }\n  })\n\n\n  return writable\n\n}\n\nconst createStream = options => {\n  const stream = new PassThrough({\n    highWaterMark: options?.highWaterMark || 1024 * 512,\n  });\n  stream._destroy = () => { stream.destroyed = true; };\n  return stream;\n};\n\n\nconst retry = (run, retryTimes = 3, maxTime = 6000) => new Promise((resolve, reject) => {\n  let lastError, time = 1\n  let error = (time) => new Promise((_, reject) => setTimeout(() => reject(new Error('timeout')), time))\n\n  const process = () => Promise.race([error(maxTime), run]).then(resolve).catch(e => {\n    if (e?.message) lastError = e\n    if (time++ <= retryTimes) {\n      console.log('retry:', time)\n      try {\n        process()\n      } catch (e) {\n        reject(e)\n      }\n    } else {\n      reject(lastError)\n    }\n  })\n  process()\n})\n\nexports.createChunkStream = (url, chunkSize = 10 * 1024 * 1024, options = {}) => {\n\n  let downloaded = 0\n  let stream = createStream(options.highWaterMark)\n  let willEnd = false\n\n  let pos = options.start || 0\n  let end = options.end || options.total || 0\n\n  const next = async () => {\n    let rangeEnd = pos + chunkSize\n    if (!end || rangeEnd >= end) {\n      rangeEnd = end\n      willEnd = true\n    }\n\n    let headers = {\n      range: `bytes=${pos}-${rangeEnd || ''}`,\n      ...(options.headers || {})\n    }\n\n    let res = await retry(request.get(url, {\n      headers,\n      responseType: 'stream',\n      retry: 0,\n      timeout: 5000\n    }), 3, 6000)\n\n    const ondata = chunk => {\n      downloaded += chunk.length;\n      stream.emit('progress', downloaded);\n    };\n\n    res.data.on('data', ondata)\n    res.data.on('error', (e) => {\n      console.log(e)\n    })\n    res.data.on('end', () => {\n\n      if (!willEnd) {\n        pos = rangeEnd + 1\n        next()\n      }\n\n    })\n    // stream\n    res.data.pipe(stream, { end: willEnd })\n  }\n\n  next()\n\n  return stream\n}\n\nexports.createChunkWriteStream = function (url, chunkSize = 10 * 1024 * 1024, total, onChunk) {\n\n  const stream = new PassThrough({\n    highWaterMark: 512 * 1024,\n  });\n\n  let pos = 0\n\n  let uploaded = 0, chunkUploaded = 0\n\n  let exceed, uploadStream\n\n  stream.on('data', (chunk) => {\n    let willPause = false\n    chunkUploaded += chunk.length\n    if (chunkUploaded >= chunkSize) {\n      exceed = chunk.slice(chunkSize)\n      willPause = uploadStream.end(chunk.slice(0, chunkSize))\n    } else {\n      willPause = uploadStream.write(chunk.slice(0, chunkSize))\n    }\n    if (flag == false) {\n      stream.pause()\n    }\n  })\n\n  stream.on('end', () => {\n\n  })\n\n  const next = async () => {\n\n    let rangeEnd = pos + chunkSize\n    if (!end || rangeEnd >= end) {\n      rangeEnd = end\n      willEnd = true\n    }\n\n    uploadStream = new PassThrough()\n    uploadStream.on('end', next)\n    chunkUploaded = 0\n\n    if (exceed) {\n      uploadStream.write(exceed)\n      exceed = null\n    }\n\n    let reqOptions = {\n      url, headers: {}, method: 'put', contentType: \"stream\",\n      body: uploadStream,\n      responseType: 'text',\n    }\n\n    if (onChunk) {\n      let res = await onChunk(pos, rangeEnd)\n      reqOptions = { ...reqOptions, ...res }\n    }\n\n    request(reqOptions.url, reqOptions)\n\n    stream.resume()\n  }\n\n  next()\n\n  return stream\n\n}"
  },
  {
    "path": "packages/sharelist-core/lib/request.js",
    "content": "const fetch = require('node-fetch')\n\nconst btoa = (v) => Buffer.from(v).toString('base64')\n\nconst https = require('https')\n\nconst http = require('http')\n\nconst { URL } = require('url')\n\nclass LRUCache {\n  constructor(size = 10) {\n    this.size = size\n    this.store = new Map()\n    this.index = []\n  }\n  update(key) {\n    let index = this.index\n    let idx = index.indexOf(key)\n    let cur = index[idx]\n\n    //update index\n    index.splice(idx, 1)\n    index.unshift(cur)\n  }\n  get(key) {\n    const { store } = this\n    // update\n    if (store.has(key)) {\n      this.update(key)\n      return store.get(key)\n    }\n  }\n  set(key, val) {\n    const { store, index } = this\n    if (store.has(key)) {\n      this.update(key)\n    } else {\n      if (store.size >= this.size) {\n        let delKey = index.pop()\n        store.delete(delKey)\n      }\n      index.unshift(key)\n    }\n    store.set(key, val)\n  }\n}\n\nconst lruCache = new LRUCache()\n\nconst createAgent = (proxy, isHttpsAgent = true) => {\n  const proxyParsed = typeof proxy === 'string'\n    ? new URL(proxy)\n    : proxy\n\n  const agent = isHttpsAgent ? new https.Agent() : new http.Agent()\n\n  agent.proxy = proxyParsed\n  agent.superCreateConnection = agent.createConnection\n  agent.createConnection = function (options, callback) {\n    const proxyParsed = this.proxy\n    const isHttpsAgent = this instanceof https.Agent\n\n    const isHttpsTunnel = proxyParsed.protocol === 'https:'\n\n    const requestOptions = {\n      method: 'CONNECT',\n      host: proxyParsed.hostname,\n      port: proxyParsed.port,\n      path: `${options.host}:${options.port}`,\n      setHost: false,\n      headers: { connection: this.keepAlive ? 'keep-alive' : 'close', host: `${options.host}:${options.port}` },\n      agent: false,\n      timeout: options.timeout || 0\n    }\n\n    if (proxyParsed.username || proxyParsed.password) {\n      const base64 = Buffer.from(`${decodeURIComponent(proxyParsed.username || '')}:${decodeURIComponent(proxyParsed.password || '')}`).toString('base64')\n      requestOptions.headers['proxy-authorization'] = `Basic ${base64}`\n    }\n\n    // Necessary for the TLS check with the proxy to succeed.\n    if (isHttpsTunnel) {\n      requestOptions.servername = this.proxy.hostname\n    }\n    //console.log('request', requestOptions)\n    const request = (isHttpsTunnel ? https : http).request(requestOptions)\n\n    request.once('connect', (response, socket, head) => {\n      request.removeAllListeners()\n      socket.removeAllListeners()\n      if (response.statusCode === 200) {\n        callback(null, isHttpsAgent ? this.superCreateConnection({ ...options, socket }) : socket)\n      } else {\n        callback(new Error(`Bad response: ${response.statusCode}`), null)\n      }\n    })\n\n    request.once('timeout', () => {\n      request.destroy(new Error('Proxy timeout'))\n    })\n\n    request.once('error', err => {\n      request.removeAllListeners()\n      callback(err, null)\n    })\n\n    request.end()\n  }\n  return agent\n}\n\nconst createProxyAgent = (proxy, isHttpsAgent = false) => {\n  let key = (isHttpsAgent ? 'https' : 'http') + '+' + proxy\n  let agent = lruCache.get(key)\n  if (!agent) {\n    try {\n      agent = createAgent(proxy, isHttpsAgent)\n      lruCache.set(key, agent)\n    } catch (e) {\n      console.log(e)\n      return\n    }\n\n  }\n  return agent\n}\n\nconst retryTime = (times, maxBackOffTime = 6 * 1000) => {\n  return Math.min(Math.pow(2, times) * 1000 + Math.floor(Math.random() * 1000), maxBackOffTime)\n}\n\nconst sleep = time => new Promise((resolve, reject) => setTimeout(resolve, time))\n\n// {\n//   // These properties are part of the Fetch Standard\n//   method: 'GET',\n//   headers: {},        // request headers. format is the identical to that accepted by the Headers constructor (see below)\n//   body: null,         // request body. can be null, a string, a Buffer, a Blob, or a Node.js Readable stream\n//   redirect: 'follow', // set to `manual` to extract redirect headers, `error` to reject redirect\n//   signal: null,       // pass an instance of AbortSignal to optionally abort requests\n\n//   // The following properties are node-fetch extensions\n//   follow: 20,         // maximum redirect count. 0 to not follow redirect\n//   timeout: 0,         // req/res timeout in ms, it resets on redirect. 0 to disable (OS limit applies). Signal is recommended instead.\n//   compress: true,     // support gzip/deflate content encoding. false to disable\n//   size: 0,            // maximum response body size in bytes. 0 to disable\n//   agent: null         // http(s).Agent instance or function that returns an instance (see below)\n// }\n\nconst each = (src, fn) => {\n  let ret = {}\n  for (let i in src) {\n    ret[i.toLowerCase()] = fn(src[i], i)\n  }\n  return ret\n}\n\nconst convToLowerCase = (props) => {\n  let ret = {}\n  Object.keys(props).forEach(key => {\n    ret[key.toLowerCase()] = props[key]\n  })\n  return ret\n}\n\nconst qs = (data) => {\n  let c = {}\n  Object.keys(data).forEach(i => {\n    c[i] = typeof data[i] == 'object' ? JSON.stringify(data[i]) : data[i]\n  })\n  return new URLSearchParams(c)\n}\n\nconst request = async (url, options = {}) => {\n  let {\n    data,\n    method = 'GET',\n    contentType,\n    responseType = 'json',\n    followRedirect = true,\n    maxRedirects = 10,\n    auth,\n    headers = {},\n    agent,\n    compress = false,\n    timeout = 5000,\n    retry = 2,\n    proxy,\n    ...rest\n  } = options\n  let args = { method: method.toUpperCase(), size: 0, agent, compress, timeout, headers: convToLowerCase(headers), ...rest }\n\n  if (proxy) {\n    args.agent = (urlParsed) => createProxyAgent(proxy, urlParsed.protocol === 'https:')\n  }\n  if (auth) {\n    args.headers['authorization'] = `Basic ${btoa(auth)}`\n  }\n\n  if (!args.headers['user-agent']) {\n    args.headers['user-agent'] = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Safari/537.36'\n  }\n\n  if (followRedirect) {\n    args.redirect = 'follow'\n    if (maxRedirects) args.follow = maxRedirects\n  } else {\n    args.redirect = 'manual'\n  }\n\n\n  if (data) {\n    if (Buffer.isBuffer(data) || !!data?.pipe) {\n      retry = 0\n    }\n    if (['GET', 'HEAD', 'OPTIONS'].includes(args.method)) {\n      url += (url.includes('?') ? '' : '?') + new URLSearchParams(data).toString()\n    } else {\n      if (contentType == 'json') {\n        args.body = JSON.stringify(data)\n        if (!args.headers['content-type']) {\n          args.headers['content-type'] = 'application/json'\n        }\n      } else if (contentType == 'form') {\n        args.body = qs(data)\n        // if (!args.headers['content-type']) {\n        //   args.headers['content-type'] = 'application/x-www-form-urlencoded'\n        // }\n      } else {\n        args.body = data\n        args.timeout = 0\n      }\n    }\n  }\n  let res, time = 0\n  while (true) {\n    try {\n      res = await fetch(url, args)\n      break\n    } catch (e) {\n      console.log('request retry', retry, e)\n\n      if (time++ < retry) {\n        await sleep(retryTime(time))\n        // return { error: { message: '[' + e.code + '] The error occurred during the request.' } }\n      } else {\n        let type = e.code || e.type\n        throw { message: '[' + type + '] The error occurred during the request.', type }\n      }\n    }\n  }\n\n  let status = res.status\n  let resHeaders = each(res.headers.raw(), (val) => val.join(','))\n  if (responseType == 'json' || responseType == 'text' || responseType == 'buffer') {\n    let data\n    try {\n      data = await res[responseType]()\n    } catch (e) {\n      console.error(e)\n    }\n\n    return {\n      status,\n      headers: resHeaders,\n      data,\n    }\n  } else {\n    return { status, headers: resHeaders, data: res.body }\n  }\n\n}\n\nrequest.post = (url, options) => request(url, { ...options, method: 'POST' })\n\nrequest.get = (url, options) => request(url, { ...options, method: 'GET' })\n\nmodule.exports = request"
  },
  {
    "path": "packages/sharelist-core/lib/utils.js",
    "content": "const crypto = require('crypto')\n\nconst { Transform } = require('node:stream')\n\nconst mimeParse = require('mime')\n\nconst YAML = require('yaml')\n\nconst htmlEntity = require('html-entities')\n\nconst isType = (type) => (obj) => Object.prototype.toString.call(obj) === `[object ${type}]`\n\nconst isArray = isType('Array')\n\nconst isObject = isType('Object')\n\nconst isString = isType('String')\n\nconst isDate = isType('Date')\n\nexports.hash = content => crypto.createHash('md5').update(content).digest(\"hex\")\n\nexports.isClass = (fn) => typeof fn == 'function' && /^\\s*class/.test(fn.toString())\n\nexports.base64 = {\n  encode: (v) => Buffer.from(v).toString('base64'),\n  decode: (v) => Buffer.from(v, 'base64').toString(),\n}\n\nexports.btoa = (v) => Buffer.from(v).toString('base64')\nexports.atob = (v) => Buffer.from(v, 'base64').toString()\n\nexports.isFunction = (fn) => typeof fn == 'function'\n\nconst datetime = (exports.datetime = (date, expr = 'iso') => {\n  var a = new Date()\n  if (isDate(date)) {\n    a = date\n  } else if (isString(date)) {\n    try {\n      a = new Date(date)\n    } catch (e) { }\n  }\n\n  var y = a.getFullYear(),\n    M = a.getMonth() + 1,\n    d = a.getDate(),\n    D = a.getDay(),\n    h = a.getHours(),\n    m = a.getMinutes(),\n    s = a.getSeconds()\n\n  function zeroize(v) {\n    v = parseInt(v)\n    return v < 10 ? '0' + v : v\n  }\n\n  if (expr === 'iso') {\n    return a.toISOString()\n  } else if (expr == 'ms') {\n    return a.getTime()\n  }\n  return expr.replace(/(?:s{1,2}|m{1,2}|h{1,2}|d{1,2}|M{1,4}|y{1,4})/g, function (str) {\n    switch (str) {\n      case 's':\n        return s\n      case 'ss':\n        return zeroize(s)\n      case 'm':\n        return m\n      case 'mm':\n        return zeroize(m)\n      case 'h':\n        return h\n      case 'hh':\n        return zeroize(h)\n      case 'd':\n        return d\n      case 'dd':\n        return zeroize(d)\n      case 'M':\n        return M\n      case 'MM':\n        return zeroize(M)\n      case 'MMMM':\n        return ['十二', '一', '二', '三', '四', '五', '六', '七', '八', '九', '十', '十一'][m] + '月'\n      case 'yy':\n        return String(y).substr(2)\n      case 'yyyy':\n        return y\n      default:\n        return str.substr(1, str.length - 2)\n    }\n  })\n})\n\nexports.byte = (v) => {\n  if (v === undefined || v === null || isNaN(v)) {\n    return '-'\n  }\n\n  let lo = 0\n\n  while (v >= 1024) {\n    v /= 1024\n    lo++\n  }\n\n  return Math.floor(v * 100) / 100 + ' ' + ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB'][lo]\n}\n\nconst byteMap = { B: 1, KB: 1e3, MB: 1e6, GB: 1e9, TB: 1e12, PB: 1e15, EB: 1e18 }\nexports.retrieveByte = (v) => {\n  if (/[\\d\\.]+\\s*(B|KB|MB|GB|TB|PB|EB|K|M|G|T|P|E)/.test(v)) {\n    let num = parseFloat(v)\n    let unit = (v.match(/(B|KB|MB|GB|TB|PB|EB|K|M|G|T|P|E)/) || [''])[0]\n\n    if (unit && num) {\n      if (!unit.endsWith('B')) unit += 'B'\n      return num * (byteMap[unit] || 0)\n    }\n  }\n\n  return 0\n}\n\nexports.extname = (p) => p.split('.').pop()\n\nexports.filetype = (v) => {\n  if (v) v = v.toLowerCase()\n  if (['mp4', 'mpeg', 'wmv', 'webm', 'avi', 'rmvb', 'mov', 'mkv', 'f4v', 'flv'].includes(v)) {\n    return 'video'\n  } else if (['mp3', 'm4a', 'wav', 'wma', 'ape', 'flac', 'ogg'].includes(v)) {\n    return 'audio'\n  } else if (['doc', 'docx', 'wps'].includes(v)) {\n    return 'word'\n  } else if (['pdf'].includes(v)) {\n    return 'pdf'\n  } else if (['doc', 'docx', 'ppt', 'pptx', 'xls', 'xlsx', 'pdf', 'txt', 'yaml', 'ini', 'cfg'].includes(v)) {\n    return 'doc'\n  } else if (['jpg', 'jpeg', 'png', 'gif', 'bmp', 'tiff', 'wmf', 'tif'].includes(v)) {\n    return 'image'\n  } else if (['zip', 'rar', '7z', 'tar', 'gz', 'gz2'].includes(v)) {\n    return 'archive'\n  } else {\n    return 'other'\n  }\n}\n\nexports.mime = (v) => mimeParse.getType(v)\n\nexports.timestamp = (v) => datetime(v, 'ms')\n\nexports.fit = (src, filter) => {\n  return Object.keys(filter).every((key) => filter[key] === src[key])\n}\n\nexports.useErrorHandle = (cb) =>\n  Promise.resolve(cb())\n    .then((data) => {\n      return Promise.resolve({ data })\n    })\n    .catch((err) => {\n      return Promise.resolve({ err })\n    })\n\nexports.videoQuality = (text) => {\n  return (\n    {\n      LD: 480,\n      SD: 576,\n      HD: 720,\n      FHD: 1080,\n      QHD: 1440,\n      UHD: 2160,\n    }[text] || 1080\n  )\n}\n\nexports.transfromStreamToString = (stream, encoding = 'utf-8') =>\n  new Promise((resolve, reject) => {\n    let str = ''\n    stream.on('data', (chunk) => {\n      str += chunk.toString(encoding)\n    })\n\n    stream.on('end', () => {\n      console.log(str)\n      resolve(str)\n    })\n  })\n\nexports.yaml = YAML\n\nexports.htmlEntity = htmlEntity\n\nexports.safeCall = async (fn, args = []) => {\n  let res\n  try {\n    res = args ? await fn(...args) : await fn()\n  } catch (err) {\n    console.log(err)\n  }\n  return res\n}\n\nexports.createCache = () => {\n  const data = {}\n  const get = (id, creator) => {\n    let ret = data[id]\n    if (ret) {\n      if (Date.now() > ret.expired_at) {\n        delete data[id]\n      } else {\n        return ret.data\n      }\n    }\n  }\n\n  const set = (id, value, max_age) => {\n    data[id] = { data: value, expired_at: Date.now() + max_age }\n    return value\n  }\n\n  const has = (id) => {\n    let ret = data[id]\n    if (ret) {\n      if (Date.now() > ret.expired_at) {\n        delete data[id]\n      } else {\n        return true\n      }\n    }\n    return false\n  }\n\n  const clear = () => {\n    for (let key in data) {\n      delete data[key]\n    }\n  }\n\n  for (let key in data) {\n    get(key, data)\n  }\n\n  return {\n    get, set, clear, has\n  }\n}\n\nexports.retryTime = (times, maxBackOffTime = 8 * 1000) => {\n  return Math.min(Math.pow(2, times) * 1000 + Math.floor(Math.random() * 1000), maxBackOffTime)\n}\nexports.waitStreamFinish = (src, dst) => new Promise((resolve) => {\n  dst.on('finish', () => resolve(true)).on('error', () => resolve(false))\n  src.pipe(dst)\n  src.resume?.()\n})\n\n\nexports.LRUCache = class {\n  constructor(size = 10) {\n    this.size = size\n    this.store = new Map()\n    this.index = []\n  }\n  update(key) {\n    let index = this.index\n    let idx = index.indexOf(key)\n    let cur = index[idx]\n\n    //update index\n    index.splice(idx, 1)\n    index.unshift(cur)\n  }\n  get(key) {\n    const { store } = this\n    // update\n    if (store.has(key)) {\n      this.update(key)\n      return store.get(key)\n    }\n  }\n  set(key, val) {\n    const { store, index } = this\n    if (store.has(key)) {\n      this.update(key)\n    } else {\n      if (store.size >= this.size) {\n        let delKey = index.pop()\n        store.delete(delKey)\n      }\n      index.unshift(key)\n    }\n    store.set(key, val)\n  }\n}\n\nexports.createCache = (options = {}) => {\n  const data = {}\n  const defaultMaxAge = options.defaultMaxAge || 0\n  const get = (id) => {\n    if (id === undefined) return data\n    let ret = data[id]\n    if (ret) {\n      if (Date.now() > ret.$expiredAt) {\n        delete data[id]\n      } else {\n        return ret.data\n      }\n    }\n  }\n\n  const set = (id, value, maxAge) => {\n    data[id] = { data: value, $expiredAt: Date.now() + (maxAge || defaultMaxAge) }\n    return value\n  }\n\n  const clear = (key) => {\n    if (key) {\n      delete data[key]\n    } else {\n      for (let key in data) {\n        delete data[key]\n      }\n    }\n  }\n\n  const remove = (key) => {\n    delete data[key]\n  }\n\n  const walk = () => {\n    // remove expired data\n    for (let key of Object.keys(data)) {\n      get(key)\n    }\n\n    if (Object.keys(data).length > 0) {\n      setTimeout(walk, 60 * 1000)\n    }\n\n  }\n\n  walk()\n\n  return {\n    get, set, remove, clear\n  }\n}\n\nexports.streamMonitor = (initData = {}) => {\n\n  let lastTime = Date.now(), chunkLoaded = 0\n\n  let stats = {\n    loaded: initData.loaded || 0,\n    total: initData.total\n    // parts: initData.parts || {}\n  }\n\n  let counter = 0\n  let setCount = (len, id) => {\n    //分段计数\n    // stats.parts[id] += len\n\n    //整体计数\n    stats.loaded += len\n\n    chunkLoaded += len\n\n    let timePass = Date.now() - lastTime\n    if (timePass >= 1000) {\n      stats.speed = Math.floor(chunkLoaded * 1000 / timePass)\n      lastTime = Date.now()\n      chunkLoaded = 0\n      initData.update?.({ ...stats })\n    }\n  }\n  const probe = () => {\n    let id = counter++\n    // stats.parts[id] = 0\n    return new Transform({\n      transform(chunk, encoding, callback) {\n        // ...\n        setCount(chunk.length, id)\n        callback(null, chunk)\n      }\n    })\n  };\n\n  return { stats, probe }\n\n}"
  },
  {
    "path": "packages/sharelist-core/package.json",
    "content": "{\n  \"name\": \"@sharelist/core\",\n  \"version\": \"0.2.0\",\n  \"repository\": \"https://github.com/reruin/sharelist\",\n  \"license\": \"MIT\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"build\": \"echo \\\"Info: no specified\\\" && exit \",\n    \"test\": \"node test/index.js\",\n    \"changelog\": \"conventional-changelog -p angular -i CHANGELOG.md -s --commit-path .\",\n    \"release\": \"node ../../scripts/release.js --commit-path .\"\n  },\n  \"dependencies\": {\n    \"html-entities\": \"^2.3.3\",\n    \"mime\": \"^2.5.2\",\n    \"node-fetch\": \"^2.6.1\",\n    \"querystringify\": \"^2.2.0\",\n    \"write-file-atomic\": \"^3.0.3\",\n    \"yaml\": \"^1.10.2\"\n  },\n  \"devDependencies\": {}\n}"
  },
  {
    "path": "packages/sharelist-core/test/index.js",
    "content": "const createDriver = require('../')\n\nconst path = require('path')\n\nconst testdriver = () => {\n  const list = (id) => {\n    return {\n      id: 'folder',\n      files: new Array(10).fill(0).map((i) => ({\n        id: 'folder' + i,\n        name: 'file' + i,\n        size: 0,\n        type: 'folder',\n        ctime: Date.now(),\n        mtime: Date.now(),\n      })),\n    }\n  }\n\n  const get = (id) => {\n    return {\n      id: id,\n      name: 'file' + id,\n      size: 0,\n      type: 'file',\n      ctime: Date.now(),\n      mtime: Date.now(),\n    }\n  }\n\n  return { name: 'test', protocol: 'test', mountable: true, list, get }\n};\n\n(async () => {\n  const driver = await createDriver({\n    plugins: [testdriver]\n  })\n\n  console.log(await driver.list())\n})()\n"
  },
  {
    "path": "packages/sharelist-core/test/plugin/driver.test.js",
    "content": "modules.export = () => {\n  const list = (id) => {\n    return {\n      id: 'folder',\n      files: new Array(10).fill(0).map((i) => ({\n        id: 'folder' + i,\n        name: 'file' + i,\n        size: 0,\n        type: 'folder',\n        ctime: Date.now(),\n        mtime: Date.now(),\n      })),\n    }\n  }\n\n  const get = (id) => {\n    return {\n      id: id,\n      name: 'file' + id,\n      size: 0,\n      type: 'file',\n      ctime: Date.now(),\n      mtime: Date.now(),\n    }\n  }\n\n  return { name: 'test', protocol: 'test', mountable: true, list, get }\n}\n"
  },
  {
    "path": "packages/sharelist-manage/.eslintrc.js",
    "content": "module.exports = {\n  parser: 'vue-eslint-parser',\n  parserOptions: {\n    // set script parser\n    parser: '@typescript-eslint/parser', // Specifies the ESLint parser\n    ecmaVersion: 2021, // Allows for the parsing of modern ECMAScript features\n    sourceType: 'module', // Allows for the use of imports\n    ecmaFeatures: {\n      jsx: true, // Allows for the parsing of JSX\n    },\n    validate: [],\n  },\n  extends: [\n    'plugin:vue/vue3-recommended',\n    'plugin:@typescript-eslint/recommended', // Uses the recommended rules from the @typescript-eslint/eslint-plugin\n    'plugin:prettier/recommended',\n  ],\n  rules: {\n    // Place to specify ESLint rules. Can be used to overwrite rules specified from the extended configs\n    // e.g. \"@typescript-eslint/explicit-function-return-type\": \"off\",\n    '@typescript-eslint/no-unused-vars': 'off',\n    '@typescript-eslint/no-explicit-any': 'off',\n    '@typescript-eslint/no-empty-function': 'off',\n\n    // 'object-curly-spacing': ['error', 'always'],\n  },\n}\n"
  },
  {
    "path": "packages/sharelist-manage/.gitignore",
    "content": "node_modules\n.DS_Store\ndist\ndist-ssr\n*.local\nyarn-error.log\npackage-lock.json\nyarn.lock"
  },
  {
    "path": "packages/sharelist-manage/.prettierrc.js",
    "content": "// https://prettier.io/docs/en/configuration.html\nmodule.exports = {\n  //分号终止符\n  semi: false,\n\n  //行尾逗号\n  trailingComma: \"all\",\n\n  // 使用单引号, 默认false(在jsx中配置无效, 默认都是双引号)\n  singleQuote: true,\n\n  printWidth: 120,\n  // 换行符\n  endOfLine: \"auto\",\n\n  //缩进 default:2\n  tabWidth: 2\n}"
  },
  {
    "path": "packages/sharelist-manage/CHANGELOG.md",
    "content": ""
  },
  {
    "path": "packages/sharelist-manage/README.md",
    "content": "# @sharelist/web [![npm](https://img.shields.io/npm/v/@sharelist/web.svg)](https://npmjs.com/package/@sharelist/web)\n\nIt's a part for sharelist\n\n## Useage\n"
  },
  {
    "path": "packages/sharelist-manage/package.json",
    "content": "{\n  \"name\": \"@sharelist/manage\",\n  \"version\": \"0.2.0\",\n  \"scripts\": {\n    \"dev\": \"vite\",\n    \"build\": \"vite build\",\n    \"changelog\": \"conventional-changelog -p angular -i CHANGELOG.md -s --commit-path .\",\n    \"release\": \"node ../../scripts/release.js --skipBuild --skipNpmPublish\"\n  },\n  \"dependencies\": {\n    \"@ant-design/icons-vue\": \"^6.0.1\",\n    \"@codemirror/basic-setup\": \"^0.19.1\",\n    \"@codemirror/commands\": \"^0.19.7\",\n    \"@codemirror/lang-javascript\": \"^0.19.6\",\n    \"@codemirror/view\": \"^0.19.39\",\n    \"@types/crypto-js\": \"^4.1.1\",\n    \"@types/spark-md5\": \"^3.0.2\",\n    \"ant-design-vue\": \"^3.2.5\",\n    \"axios\": \"^0.27.2\",\n    \"js-sha1\": \"^0.6.0\",\n    \"pinia\": \"^2.0.14\",\n    \"pinia-plugin-persist\": \"^1.0.0\",\n    \"spark-md5\": \"^3.0.2\",\n    \"vue\": \"3.x\",\n    \"vue-router\": \"^4.0.15\"\n  },\n  \"devDependencies\": {\n    \"@types/node\": \"^15.x\",\n    \"@typescript-eslint/eslint-plugin\": \"^4.23.0\",\n    \"@typescript-eslint/parser\": \"^4.23.0\",\n    \"@vitejs/plugin-legacy\": \"2.x\",\n    \"@vitejs/plugin-vue-jsx\": \"2.x\",\n    \"@vue/compiler-sfc\": \"^3.0.5\",\n    \"eslint\": \"^7.26.0\",\n    \"eslint-config-prettier\": \"^8.3.0\",\n    \"eslint-plugin-prettier\": \"^3.4.0\",\n    \"eslint-plugin-vue\": \"^7.9.0\",\n    \"less\": \"^4.1.1\",\n    \"minimist\": \"^1.2.5\",\n    \"prettier\": \"^2.3.0\",\n    \"terser\": \"^5.15.0\",\n    \"typescript\": \"^4.1.3\",\n    \"unplugin-vue-components\": \"0.x\",\n    \"vite\": \"3.x\",\n    \"vue-eslint-parser\": \"^7.6.0\",\n    \"vue-tsc\": \"^0.0.24\"\n  }\n}\n"
  },
  {
    "path": "packages/sharelist-manage/src/App.tsx",
    "content": "import { defineComponent } from 'vue'\nimport { ConfigProvider } from 'ant-design-vue'\nimport { RouterView } from 'vue-router'\nimport zhCN from 'ant-design-vue/es/locale/zh_CN';\n\nexport default defineComponent({\n  setup() {\n    return () => (\n      <ConfigProvider locale={zhCN}>\n        <RouterView />\n      </ConfigProvider>\n    )\n  },\n})"
  },
  {
    "path": "packages/sharelist-manage/src/assets/style/index.less",
    "content": "// @import './icon.less';\n\n@import 'ant-design-vue/lib/style/variable.less';\n@import 'ant-design-vue/lib/button/style/index-pure.less';\n@import 'ant-design-vue/lib/radio/style/index-pure.less';\n@import 'ant-design-vue/lib/breadcrumb/style/index-pure.less';\n@import 'ant-design-vue/lib/input/style/index-pure.less';\n@import 'ant-design-vue/lib/input-number/style/index-pure.less';\n@import 'ant-design-vue/lib/checkbox/style/index-pure.less';\n@import 'ant-design-vue/lib/message/style/index-pure.less';\n@import 'ant-design-vue/lib/modal/style/index-pure.less';\n@import 'ant-design-vue/lib/spin/style/index-pure.less';\n@import 'ant-design-vue/lib/switch/style/index-pure.less';\n@import 'ant-design-vue/lib/menu/style/index-pure.less';\n@import 'ant-design-vue/lib/dropdown/style/index-pure.less';\n@import 'ant-design-vue/lib/list/style/index-pure.less';\n@import 'ant-design-vue/lib/badge/style/index-pure.less';\n@import 'ant-design-vue/lib/progress/style/index-pure.less';\n@import 'ant-design-vue/lib/empty/style/index-pure.less';\n@import 'ant-design-vue/lib/tabs/style/index-pure.less';\n@import 'ant-design-vue/lib/form/style/index-pure.less';\n@import 'ant-design-vue/lib/select/style/index-pure.less';\n@import 'ant-design-vue/lib/popover/style/index-pure.less';\n@import 'ant-design-vue/lib/alert/style/index-pure.less';\n@import 'ant-design-vue/lib/tooltip/style/index-pure.less';\n@import 'ant-design-vue/lib/image/style/index-pure.less';\n@import 'ant-design-vue/lib/radio/style/index-pure.less';\n\n\n@import './var.less';\n\n@font-face {\n  font-family: 'Source Code Pro';\n  font-style: normal;\n  font-weight: 400;\n  src: local('Source Code Pro Regular'), local('SourceCodePro-Regular'), url(data:application/x-font-woff2;charset=utf-8;base64,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) format('woff2');\n  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;\n}\n\n\nbody{\n  -webkit-app-region: drag;\n  // padding:5px;\n  background: var(--background);\n  color:var(--primary-text-color);\n  font-size:14px;\n  font-family: 'Source Code Pro';\n}\n\nh1,h2,h3,h4,h5,h6{\n  color:var(--primary-text-color);\n}\nul,ol{\n  padding-left:0;\n  list-style: none;\n\n\n  &.split{\n    display: flex;\n    align-items: center;\n    li{\n      position: relative;\n      display: inline-block;\n    }\n    li:after{\n      content:'';\n      position: absolute;\n      top: 50%;\n      right: 0;\n      width: 1px;\n      height: 10px;\n      margin-top: -5px;\n      background-color: #f0f0f0;\n    }\n  }\n}\n.no-drag,input,button{\n  -webkit-app-region: no-drag;\n}\n\n\n.flex{\n  display: flex;\n  align-items: center;\n  \n  &.flex--between{\n    justify-content: space-between;\n  }\n  &.flex--block{\n    width:100%;\n  }\n}\n::-webkit-scrollbar-track {\n  // box-shadow: inset 0 0 1px rgba(0,0,0,0.2);\n  background-color: #f1f1f1;\n}\n\n::-webkit-scrollbar {\n  width: 5px;\n  // background-color: rgba(200,200,200,1);\n}\n::-webkit-scrollbar-thumb {\n  background-color: rgba(200,200,200,.6);\n}\n\n.pure-modal{\n  z-index:1001;\n  .ant-modal-content{ border-radius: 5px;}\n  .ant-modal-body{ padding:16px 20px;}\n  .anticon-exclamation-circle,\n  .anticon-info-circle\n  { display: none;}\n  .ant-modal-confirm-title{\n    // border-bottom:1px solid #ddd;\n    padding-bottom: 12px;\n    // width: fit-content\n  }\n  .ant-modal-confirm-content{\n    margin-left:0 !important;\n  }\n\n  .modal-header{\n    padding:16px;\n  }\n\n  .modal-footer{\n    margin-top:16px;\n    text-align: right;\n  }\n\n  .ant-modal-confirm-btns{\n    .ant-btn{\n      padding:0 2.5em;\n    }\n  }\n\n  &.pure-modal-hide-footer{\n    .ant-modal-confirm-btns{\n      display: none;\n    }\n\n  \n  }\n}\n\n\n.fix-modal--alone{\n  .ant-modal-body{ padding:16px;}\n  .ant-modal-confirm-btns,.anticon-exclamation-circle{ display: none;}\n  .ant-modal-confirm-content{\n    margin-top: 0;\n  }\n  .ant-modal-content{\n    border-radius: 6px;\n  }\n}\n.fix-form--inline{\n  display: flex;\n  flex-wrap: wrap;\n  .ant-form-item{\n    flex:0 0 50%;padding:0 16px;\n\n    &.fix-form-item--foot{\n      flex:100%;\n      margin-bottom: 0;\n    }\n  }\n}\n\n.ant-tabs-tab{\n  padding:16px 0;\n}\n.ant-dropdown-menu{\n  background-color:var(--context-background);\n  .ant-dropdown-menu-item:hover, .ant-dropdown-menu-submenu-title:hover{\n    background-color: var(--context-hover);\n  }\n  .ant-dropdown-menu-item-group-title{\n    color: var(--context-3);\n  }\n}\n.ant-checkbox-inner{\n  background-color:var(--context-background);\n}\n.ant-select-dropdown{\n  background-color:var(--context-background);\n  .ant-select-item{\n    color:var(--primary-text-color);\n  }\n  .ant-select-item-option-active:not(.ant-select-item-option-disabled){\n    background-color: var(--context-hover);\n  }\n}\n.ant-select{\n  color:var(--primary-text-color);\n}\n.ant-select:not(.ant-select-customize-input) .ant-select-selector{\n  border-color:var(--divider-3);\n  background-color:var(--divider-3);\n}\n\n.ant-alert-info{\n  // border-color:var(--primary-color);\n  border:none;\n  background-color:var(--primary-color-bg);\n  border-radius: 8px;\n  .ant-alert-message{\n    // color:var(--primary-color);\n    color: var(--context-2);\n    font-size:12px;\n   \n  }\n}\n\n.ant-popover-inner,.ant-popover-arrow-content,.ant-modal-content{\n  background-color:var(--context-background);\n}\n.ant-tabs,.ant-modal-confirm-body .ant-modal-confirm-title,.ant-modal-confirm-body .ant-modal-confirm-content{\n  color:var(--primary-text-color);\n}\n.ant-tabs-top>.ant-tabs-nav:before{\n  border-color:var(--divider-2);\n}\n\n.ant-list-empty-text,.ant-empty-normal{\n  color:var(--primary-text-color);\n}\n.ant-badge,.ant-radio-wrapper{\n  color:var(--context-1);\n}\n.ant-switch{\n  background-color:var(--context-3);\n}\n.ant-switch-checked {\n  background-color: var(--ant-primary-color);\n}\n\n.ant-input,.ant-input-number{\n  background-color:var(--divider-3);\n  border-color:var(--divider-2);\n  color:var(--primary-text-color);\n\n}\n\n.ant-input-affix-wrapper{\n  background-color:var(--divider-3);\n  border-color:var(--divider-2);\n  color:var(--primary-text-color);\n  .ant-input{\n    background-color: transparent;\n  }\n  \n  .ant-input-suffix,.ant-input-password-icon{\n    color:var(--primary-text-color);\n  }\n}\n.ant-message-notice-content{\n  background-color:var(--background);\n  color:var(--primary-text-color);\n}\n.ant-form-item-label>label{\n  color:var(--primary-text-color);\n}\n.ant-progress-text{\n  color:var(--primary-text-color);\n}\n.ant-empty-description{\n  color:var(--context-2);\n}\n\n.ant-btn-primary{\n  // background-color: var(--ant-primary-color-outline);\n  // color:var(--ant-primary-color);\n  // border:none;\n  padding:0 3em;\n  border-radius: 3px;\n}\n\n@media screen and (max-width:480px) {\n  .fix-modal--alone{\n    top:0;\n  }\n  .fix-form--inline{\n    .ant-form-item{flex:100%;}\n  }\n}\n\n.page{\n  .page__header {\n    display: flex;\n    align-items: center;\n    justify-content: space-between;\n    margin-bottom: 2em;\n    color: var(--primary-text-color);\n    font-size: 18px;\n    font-weight: 600;\n\n    em{\n      font-size:12px;\n      color:rgba(0,0,0,.45);\n      margin-left:5px;\n      font-style: normal;\n      font-weight: normal;\n    }\n\n    .page__action{\n      a{\n        color:var(--primary-text-color);\n        &:hover{\n          color:var(--primary-color);\n        }\n      }\n    }\n  }\n}\n\nF{\n  width:160px;\n  border-radius: 5px;\n  .dropdown-item{\n    padding-top:8px;\n    padding-bottom:8px;\n    color:var(--primary-text-color);\n  }\n  \n  .ant-dropdown-menu{\n    border-radius: 5px;\n  }\n\n  .ant-dropdown-menu-item-group-list{\n    margin:0;\n  }\n}\n\n.ant-modal-mask,.ant-modal-wrap{\n  z-index: 1030;\n}\n\n.sl-input{\n  border-radius: 6px;\n  // box-shadow: 0 1px 5px rgb(0 0 0 / 12%);\n  border-color:transparent;\n}\n\n.popover-padding-0{\n  .ant-popover-inner-content{ padding:0;}\n}\n\n\n.ellipsis-2{\n  overflow : hidden;\n\tdisplay: -webkit-box; \n\t-webkit-box-orient: vertical; \n\t-webkit-line-clamp: 2; \n\ttext-overflow: ellipsis; \n  word-break: break-all;\n}\n\n\n.ellipsis-1{\n  overflow : hidden;\n\tdisplay: -webkit-box; \n\t-webkit-box-orient: vertical; \n\t-webkit-line-clamp: 1; \n\ttext-overflow: ellipsis; \n  word-break: break-all;\n}\n.ellipsis{\n  overflow:hidden;\n  text-overflow: ellipsis;\n  white-space: nowrap;\n}\n\n.danger-aciton{\n  color:var(--danger-color);\n}\n\n\n.danger-aciton--hover:hover{\n  color:var(--danger-color) !important;\n}\n\n"
  },
  {
    "path": "packages/sharelist-manage/src/assets/style/var.less",
    "content": "\n\n:root{\n  // --theme:100,52,248;\n  --theme:100,58,218;\n  --primary-color:rgb(var(--theme)); //rgb(105,65,199);\n  --primary-color-bg:rgba(var(--theme),.1); //rgb(105,65,199);\n  --primary-text-color:rgb(37, 38, 43);\n  --primary-background:rgb(var(--theme));\n\n  // --icon-folder-color:var(--primary-color);\n  // --icon-other-color:var(--primary-color);\n  // --icon-audio-color:rgb(132,140,239);\n  // --icon-video-color:rgb(219,68,55);\n  // --icon-word-color:rgb(47,151,254);\n  // --icon-pdf-color:rgb(252,134,132);\n  // --icon-ppt-color:rgb(254,159,93);\n  // --icon-file-color:rgb(212,214,218);\n  // --icon-word-color:rgb(88,178,252);\n  // --icon-doc-color:rgb(88,178,252);\n  // --icon-image-color:rgb(252,132,129);\n\n  --primary-text-secondary-color:rgba(37, 38, 43,0.36);\n\n  --color-main:var(--primary-text-color);\n\n  --primary-hover-bg-color:rgba(132, 133, 141, 0.08);\n  --primary-hover-theme-color:rgba(var(--theme), 0.08);\n  --dark-background:rgb(49, 49, 54);\n  --color-white-1:rgb(255,255,255);\n  --color-white-2:rgba(255,255,255,0.72);\n  --color-white-3:rgba(255,255,255,0.36);\n  --color-white-4:rgba(255,255,255,0.18);\n\n  --color-red:rgb(243, 91, 81);\n\n  --background:#ffffff;\n  --background-dark:rgb(9,9,10);\n\n  --primary-text-color-dark:rgb(253,253,253);\n  --primary-progress:rgba(var(--theme),0.1);\n  --context:37, 38, 43;//0,0,0;\n  --context-1:rgb(var(--context));\n  --context-2:rgba(var(--context),0.72);\n  --context-3:rgba(var(--context),0.36);\n  --context-4:rgba(var(--context),0.18);\n\n  --context-background:#fff;\n  --context-background--dark:rgb(49,49,54);\n\n  --background-cover:rgb(248,249,252);\n  \n  --context-hover:#f5f5f5;\n  --context-hover--dark:rgba(132, 133, 141,0.12);;\n\n  --primary-text-color--dark:rgb(37, 38, 43);\n\n  --divider: 132, 133, 141;\n  --divider-1: rgba(var(--divider), 0.2);\n  --divider-2: rgba(var(--divider), 0.16);\n  --divider-3: rgba(var(--divider), 0.08);\n\n  --danger-color:#ff4d4f;\n\n\n  //player\n  --plyr-color-main:#00b3ff;\n  --plyr-audio-controls-background:rgba(0,0,0,0);\n  --plyr-audio-control-color:#ddd;\n  --plyr-control-radius:10px;\n\n  --plyr-audio-background:rgb(49,49,54);\n  --plyr-audio-control-background-hover:transparent;\n  --plyr-video-control-background-hover:transparent;\n  --plyr-video-background:rgba(0,0,0,0);\n  --player-color-main:#fff;\n\n  --player-hover-background:rgba(255,255,255,.1);\n\n  --player-fill:rgba(0,179,255,.5);\n  --plyr-menu-background:rgb(49,49,54);\n  // --plyr-audio-control-color-hover:#aaa;\n  --plyr-menu-color:#aaa;\n  --plyr-menu-back-border-color:rgba(255,255,255,0.06);\n  --plyr-menu-back-border-shadow-color:rgba(255,255,255,0.06);\n}\n\n@media (prefers-color-scheme: dark){\n  \n  *{\n    color-scheme:dark;\n  }\n  \n}\n\n@media (prefers-color-scheme: dark){\n  \n  // :root{\n  //   --background:var(--background-dark);\n  //   --primary-text-color:var(--primary-text-color-dark);\n  //   --context:255,255,255;\n  //   --context-background:var(--context-background--dark);\n  //   --context-hover:var(--context-hover--dark);\n  //   --primary-progress:rgba(var(--theme),0.2);\n  // }\n}"
  },
  {
    "path": "packages/sharelist-manage/src/components/code-editor/index.less",
    "content": "\n.cm-editor { max-height: 500px; border: 1px solid silver; font-size: 14px }\n.cm-scroller { overflow: auto; }\n.cm-content{ font-size:12px;}\n/* Code highlighting styles */\n.hl-keyword, .fn, .keyword {color: #708;}\n.hl-atom {color: #219;}\n.hl-number, .prim {color: #164;}\n.hl-def, .hl-attribute {color: #30b;}\n.hl-variable-2, .hl-type, .type {color: #05a;}\n.hl-comment {color: #a50;}\n.hl-string, .string {color: #a11;}\n.hl-string-2 {color: #f50;}\n.hl-meta {color: #555;}\n.hl-tag {color: #170;}"
  },
  {
    "path": "packages/sharelist-manage/src/components/code-editor/index.tsx",
    "content": "import './index.less'\nimport { ref, defineComponent, watch, onMounted, onUnmounted, toRefs, reactive, watchEffect } from 'vue'\nimport { javascript } from \"@codemirror/lang-javascript\"\nimport { EditorState, EditorView, basicSetup } from \"@codemirror/basic-setup\"\n\nexport default defineComponent({\n  emits: ['update'],\n  props: {\n    defaultValue: {\n      type: String,\n    }\n  },\n  setup(props, ctx) {\n    const el = ref()\n\n    const updateListenerExtension = EditorView.updateListener.of((update) => {\n      if (update.docChanged) {\n        // Handle the event here\n        ctx.emit('update', update.state.doc.toString())\n      }\n    });\n    let startState = EditorState.create({\n      doc: props.defaultValue,\n      extensions: [basicSetup, javascript(), updateListenerExtension]\n    })\n\n    let view\n    onMounted(() => {\n      view = new EditorView({\n        state: startState,\n        parent: el.value\n      })\n    })\n\n    onUnmounted(() => {\n\n    })\n    return () => <div ref={el}></div>\n  }\n})"
  },
  {
    "path": "packages/sharelist-manage/src/components/icon/icon-svg.js",
    "content": "!(function (t) {\n  var a,\n    l,\n    o,\n    i,\n    e,\n    c,\n    h =\n      '<svg><symbol id=\"icon-drive\" viewBox=\"0 0 1024 1024\"><path d=\"M989.86666667 750.93333333c0 44.02133333-35.62666667 79.63733333-79.648 79.63733334H113.78133333C69.76 830.57066667 34.13333333 794.95466667 34.13333333 750.93333333V591.648C34.13333333 547.62666667 69.76 512 113.78133333 512h796.448c44.01066667 0 79.63733333 35.62666667 79.63733334 79.648V750.93333333zM113.78133333 459.072c-14.76266667 0-37.664 4.576-51.14666666 10.176l160.05333333-240.20266667c12.98133333-19.59466667 42.752-35.616 66.15466667-35.616h445.54666666c23.66933333 0 53.184 15.776 66.41066667 35.616L960.85333333 469.248c-16.288-6.112-33.58933333-9.664-50.89066666-10.176H113.78133333z m504.32 212.224c0 29.25866667 23.66933333 53.184 53.184 53.184 29.25866667 0 53.184-23.65866667 53.184-53.184 0-29.25866667-23.91466667-53.184-53.184-53.184-29.25866667 0.256-53.184 23.92533333-53.184 53.184z m159.296 0c0 29.25866667 23.66933333 53.184 53.184 53.184 29.25866667 0 53.184-23.65866667 53.184-53.184 0-29.25866667-23.91466667-53.184-53.184-53.184-29.26933333 0.256-53.184 23.92533333-53.184 53.184z\"  ></path></symbol><symbol id=\"icon-upload-file-outline\" viewBox=\"0 0 1024 1024\"><path d=\"M529.067 298.667V128h51.2v170.667A59.733 59.733 0 0 0 640 358.4h192v51.2H640a110.933 110.933 0 0 1-110.933-110.933z\"  ></path><path d=\"M682.667 866.048a636.95 636.95 0 0 0 45.76-1.493c18.176-1.494 26.496-4.118 31.786-6.784a76.8 76.8 0 0 0 33.558-33.579c2.688-5.27 5.312-13.59 6.784-31.765 1.536-18.774 1.578-43.2 1.578-79.894V462.187c0-36.139-0.362-45.27-2.41-53.206a76.338 76.338 0 0 0-10.39-23.36c-4.522-6.826-11.05-13.226-37.866-37.44l-166.955-150.72c-23.296-21.034-29.376-26.133-35.627-29.653a76.8 76.8 0 0 0-20.97-8.085c-7.019-1.579-14.934-1.856-46.336-1.856H375.467c-36.694 0-61.12 0.042-79.894 1.578-18.176 1.494-26.496 4.118-31.786 6.784a76.8 76.8 0 0 0-33.558 33.579c-2.688 5.27-5.312 13.59-6.784 31.765-1.536 18.774-1.578 43.2-1.578 79.894v401.066c0 36.694 0.042 61.12 1.578 79.894 1.494 18.176 4.096 26.496 6.784 31.786a76.8 76.8 0 0 0 33.579 33.558c5.27 2.688 13.59 5.312 31.765 6.784 12.224 1.002 26.88 1.365 45.76 1.493v51.2c-49.706-0.341-78.122-2.347-100.778-13.867a128 128 0 0 1-55.936-55.936c-13.952-27.392-13.952-63.232-13.952-134.912V311.467c0-71.68 0-107.52 13.952-134.912a128 128 0 0 1 55.936-55.936c27.392-13.952 63.232-13.952 134.912-13.952h106.112c29.141 0 43.69 0 57.514 3.093a128 128 0 0 1 34.987 13.44c12.31 6.976 23.125 16.725 44.736 36.267l166.955 150.72c24.874 22.464 37.333 33.685 46.25 47.146a127.978 127.978 0 0 1 17.302 38.955c4.01 15.616 4.01 32.384 4.01 65.92v250.325c0 71.68 0 107.52-13.952 134.912a128 128 0 0 1-55.936 55.936c-22.634 11.52-51.072 13.526-100.778 13.867v-51.2z\"  ></path><path d=\"M657.024 628.203l-34.048 38.229L537.6 590.4v326.933h-51.2V590.4l-85.376 76.032-34.048-38.23L512 499.052l145.024 129.152z\"  ></path></symbol><symbol id=\"icon-upload-folder-outline\" viewBox=\"0 0 1024 1024\"><path d=\"M409.067 174.933c28.309 0 37.12 0.256 44.757 2.56a59.733 59.733 0 0 1 20.565 11.03c6.166 5.056 11.264 12.245 26.987 35.797l32.47 48.747H202.047v51.2h612.885c18.347 0 30.571 0 39.958 0.789 9.088 0.747 13.226 2.048 15.872 3.413a38.4 38.4 0 0 1 16.789 16.768c1.344 2.624 2.645 6.784 3.413 15.872 0.747 9.387 0.768 21.611 0.768 39.958V704c0 30.293 0 51.477-1.365 67.968-1.323 16.213-3.797 25.643-7.467 32.853a81.067 81.067 0 0 1-35.413 35.414c-7.21 3.669-16.64 6.144-32.853 7.466-16.491 1.344-37.675 1.366-67.968 1.366h-64v51.2h65.109c28.928 0 52.203 0 71.04-1.536 19.37-1.579 36.267-4.907 51.904-12.886a132.267 132.267 0 0 0 57.813-57.813c7.936-15.595 11.286-32.512 12.864-51.883 1.536-18.837 1.536-42.112 1.536-71.04V400.043c0-17.067 0-31.36-0.96-43.094-1.002-12.266-3.178-23.893-8.81-34.965a89.6 89.6 0 0 0-39.147-39.147c-11.05-5.632-22.699-7.808-34.965-8.81-11.734-0.96-26.027-0.96-43.094-0.96H595.371l-53.355-80.064c-12.907-19.414-22.336-33.536-35.115-44.032a110.933 110.933 0 0 0-38.186-20.459c-15.851-4.8-32.854-4.8-56.107-4.779H242.133c-22.997 0-41.792 0-57.066 1.238-15.808 1.301-30.08 4.053-43.435 10.858a110.933 110.933 0 0 0-48.47 48.47c-6.805 13.333-9.557 27.626-10.858 43.434-1.237 15.275-1.237 34.07-1.237 57.067v420.267c0 28.949 0 52.224 1.536 71.04 1.578 19.392 4.906 36.309 12.885 51.925a132.267 132.267 0 0 0 57.813 57.813c15.595 7.936 32.512 11.286 51.883 12.864 18.837 1.536 42.112 1.536 71.04 1.536h65.11v-51.2h-64c-30.294 0-51.478-0.021-67.969-1.365-16.213-1.323-25.642-3.797-32.853-7.467a81.067 81.067 0 0 1-35.413-35.413c-3.67-7.21-6.144-16.64-7.467-32.853-1.344-16.47-1.365-37.654-1.365-67.947V285.867c0-24.32 0-41.024 1.066-53.974 1.046-12.629 2.944-19.413 5.44-24.341a59.733 59.733 0 0 1 26.134-26.112c4.906-2.496 11.712-4.395 24.362-5.44 12.928-1.067 29.654-1.067 53.952-1.067h165.867z\"  ></path><path d=\"M622.976 649.301l34.048-38.25L512 481.92 366.976 611.05l34.048 38.251L486.4 573.27v326.934h51.2V573.269l85.376 76.032z\"  ></path></symbol><symbol id=\"icon-download\" viewBox=\"0 0 1144 1024\"><path d=\"M986.42996662 363.52602122h-242.97562454V-0.93741459H378.99090633v364.46343581H136.01528178l425.20734243 425.20734239 425.20734241-425.20734239zM136.01528178 910.22117489v121.48781126h850.41468484v-121.48781126H136.01528178z\"  ></path></symbol><symbol id=\"icon-back\" viewBox=\"0 0 1024 1024\"><path d=\"M953.86906294 456.76636665H280.01874173l309.30834393-309.30834393L512 70.13093706l-441.86906294 441.86906294 441.86906294 441.86906294 77.32708566-77.32708566L280.01874173 567.23363335H953.86906294v-110.4672667z\"  ></path></symbol><symbol id=\"icon-upload\" viewBox=\"0 0 1570 1024\"><path d=\"M1147.34375 445.20285034c0-3.51507568 0.59078979-7.03097534 0.59078979-10.546875C1147.93453979 244.25027466 998.33782959 90.125 813.80953979 90.125 680.69396972 90.125 566.30816651 170.39413453 512.59078979 286.43695068a169.11584472 169.11584472 0 0 0-77.34402466-19.0684204c-85.21875 0-156.26266479 64.15301513-169.81869506 147.93804931C163.19342041 451.08108521 90.125 550.41864013 90.125 667.27801513 90.125 814.34347534 205.91567993 933.875 348.59347534 933.875h295.98815918V699.50942993H505.39089966l231.609375-245.25027465 231.609375 244.96929931h-139.21875v234.36557007h318.54364013c130.52828979 0 235.94018555-109.88525391 235.94018555-244.34967041 0-134.46606445-105.97467041-243.75970459-236.53097534-244.04150391z\" fill=\"#666666\" ></path></symbol><symbol id=\"icon-folder-download\" viewBox=\"0 0 1024 1024\"><path d=\"M771.238 349.034c-48.762-111.806-160.142-190.034-289.912-190.034-174.674 0-316.28 141.604-316.28 316.278 0 0.954 0.134 1.872 0.142 2.824-89.786 15.028-158.282 92.89-158.282 186.944 0 104.806 84.964 189.768 189.77 189.768l600.928 0 0-2.186c124.78-15.594 221.398-121.828 221.398-250.838C1019 463.828 908.524 351.862 771.238 349.034zM512.954 759.93l-158.14-158.14 94.884 0L449.698 380.396l126.512 0 0 221.396 94.882 0L512.954 759.93z\"  ></path></symbol><symbol id=\"icon-other\" viewBox=\"0 0 1024 1024\"><path d=\"M128 512v512h800V300.8l-153.6-150.4L620.8 0H128v512z m608-304l172.8 176H544V208c0-96 3.2-176 9.6-176 3.2 0 86.4 80 182.4 176z\" fill=\"#4285f4\" ></path></symbol><symbol id=\"icon-archive\" viewBox=\"0 0 1032 1024\"><path d=\"M54.912029 52.69395c-73.186041 73.186041-73.186041 843.230478 0 916.416519 73.186041 73.186041 843.230478 73.186041 916.416519 0 73.186041-73.186041 73.186041-843.230478 0-916.416519C898.142506-20.492092 128.09807-20.492092 54.912029 52.69395zM767.680433 192.702029c0 57.276032-22.274013 63.640036-254.560145 63.640036s-254.560144-6.364004-254.560144-63.640036 22.274013-63.640036 254.560144-63.640036 254.560144 6.364004 254.560145 63.640036z m0 254.560144c0 57.276032-22.274013 63.640036-254.560145 63.640036s-254.560144-6.364004-254.560144-63.640036 22.274013-63.640036 254.560144-63.640036 254.560144 6.364004 254.560145 63.640036z m-120.916069 324.564184L513.120288 908.652435l-133.644075-136.826078L242.650135 638.182281h540.940307l-136.826078 133.644076z\"  ></path></symbol><symbol id=\"icon-doc2\" viewBox=\"0 0 1024 1024\"><path d=\"M12.8 41.6C6.4 64 3.2 291.2 6.4 544l9.6 464h992V16L518.4 6.4C137.6 0 25.6 6.4 12.8 41.6zM832 288c0 19.2-128 32-320 32S192 307.2 192 288s128-32 320-32 320 12.8 320 32z m0 128c0 19.2-128 32-320 32s-320-12.8-320-32 128-32 320-32 320 12.8 320 32z m0 128c0 19.2-128 32-320 32s-320-12.8-320-32 128-32 320-32 320 12.8 320 32z m-256 128c0 19.2-86.4 32-192 32s-192-12.8-192-32 86.4-32 192-32 192 12.8 192 32z\" fill=\"#4285f4\" ></path></symbol><symbol id=\"icon-grid\" viewBox=\"0 0 1024 1024\"><path d=\"M384 128H213.333333a85.333333 85.333333 0 0 0-85.333333 85.333333v170.666667a85.333333 85.333333 0 0 0 85.333333 85.333333h170.666667a85.333333 85.333333 0 0 0 85.333333-85.333333V213.333333a85.333333 85.333333 0 0 0-85.333333-85.333333zM810.666667 128h-170.666667a85.333333 85.333333 0 0 0-85.333333 85.333333v170.666667a85.333333 85.333333 0 0 0 85.333333 85.333333h170.666667a85.333333 85.333333 0 0 0 85.333333-85.333333V213.333333a85.333333 85.333333 0 0 0-85.333333-85.333333zM384 554.666667H213.333333a85.333333 85.333333 0 0 0-85.333333 85.333333v170.666667a85.333333 85.333333 0 0 0 85.333333 85.333333h170.666667a85.333333 85.333333 0 0 0 85.333333-85.333333v-170.666667a85.333333 85.333333 0 0 0-85.333333-85.333333zM810.666667 554.666667h-170.666667a85.333333 85.333333 0 0 0-85.333333 85.333333v170.666667a85.333333 85.333333 0 0 0 85.333333 85.333333h170.666667a85.333333 85.333333 0 0 0 85.333333-85.333333v-170.666667a85.333333 85.333333 0 0 0-85.333333-85.333333z\"  ></path></symbol><symbol id=\"icon-pdf2\" viewBox=\"0 0 1024 1024\"><path d=\"M981.333333 276.053333V981.333333a42.666667 42.666667 0 0 1-42.666666 42.666667H85.333333a42.666667 42.666667 0 0 1-42.666666-42.666667V42.666667a42.666667 42.666667 0 0 1 42.666666-42.666667h619.946667z\" fill=\"#FC5A5A\" ></path><path d=\"M705.28 233.386667V0L981.333333 276.053333H747.946667a42.666667 42.666667 0 0 1-42.666667-42.666666z\" fill=\"#FD9796\" ></path><path d=\"M252.586667 715.52H224A10.666667 10.666667 0 0 1 213.333333 704V490.666667a10.666667 10.666667 0 0 1 10.666667-10.666667h62.293333q95.786667-1.28 95.786667 72.746667-2.56 69.12-81.706667 72.96h-37.12V704a10.666667 10.666667 0 0 1-10.666666 11.52z m10.666666-196.906667v66.56q3.84 0 15.36 1.28c33.92 3.413333 50.56-8.106667 49.706667-34.56 0-23.04-16.426667-34.133333-49.706667-33.28a34.986667 34.986667 0 0 1-15.36 0zM417.493333 704V490.666667a10.666667 10.666667 0 0 1 10.666667-10.666667h69.76c78.506667 0 117.973333 40.533333 119.04 118.826667s-40.533333 117.546667-119.04 117.546666h-69.76a10.666667 10.666667 0 0 1-10.666667-12.373333z m49.706667-186.24v157.44h26.88q70.4 2.56 69.12-78.08t-69.12-79.36zM697.813333 715.52h-28.586666a10.666667 10.666667 0 0 1-10.666667-10.666667V490.666667a10.666667 10.666667 0 0 1 10.666667-10.666667h130.773333a10.666667 10.666667 0 0 1 10.666667 10.666667v18.133333a10.666667 10.666667 0 0 1-10.666667 10.666667h-91.52V576h85.333333a10.666667 10.666667 0 0 1 10.666667 10.666667v18.346666a10.666667 10.666667 0 0 1-10.666667 10.666667h-85.333333V704a10.666667 10.666667 0 0 1-10.666667 11.52z\" fill=\"#FFFFFF\" ></path></symbol><symbol id=\"icon-excel\" viewBox=\"0 0 1024 1024\"><path d=\"M136.533604 0.00026a49.119975 49.119975 0 0 0-35.839982 15.359992C91.307627 25.600247 85.33463 38.40024 85.33463 51.200234v921.599532a49.119975 49.119975 0 0 0 15.359992 35.839982 50.545974 50.545974 0 0 0 35.839982 15.359992h750.931619a49.119975 49.119975 0 0 0 35.839981-15.359992 50.546974 50.546974 0 0 0 15.359993-35.839982V290.134113L648.533344 0.00026z\" fill=\"#00B632\" ></path><path d=\"M938.666197 290.133113H699.733318a52.492973 52.492973 0 0 1-51.199974-51.199974V0.00026z\" fill=\"#7FDA98\" ></path><path d=\"M462.95969321 572.36898384L317.39898702 373.72683116h100.61033872l95.23467997 142.18962084L612.84549052 373.72683116h97.4030191L561.40169203 572.36898384l156.27648978 211.34320013H616.06285234l-104.98718655-150.67140562L406.3565285 784.68395902H306.72105396z\" fill=\"#FFFFFF\" ></path></symbol><symbol id=\"icon-PPT\" viewBox=\"0 0 1024 1024\"><path d=\"M136.533604 0.00026a49.119975 49.119975 0 0 0-35.839982 15.359992C91.307627 25.600247 85.33463 38.40024 85.33463 51.200234v921.599532a49.119975 49.119975 0 0 0 15.359992 35.839982 50.545974 50.545974 0 0 0 35.839982 15.359992h750.931619a49.119975 49.119975 0 0 0 35.839981-15.359992 50.546974 50.546974 0 0 0 15.359993-35.839982V290.134113L648.533344 0.00026z\" fill=\"#EB734C\" ></path><path d=\"M938.666197 290.133113H699.733318a52.492973 52.492973 0 0 1-51.199974-51.199974V0.00026z\" fill=\"#FCB7A0\" ></path><path d=\"M341.3335 362.000076h196.6079c112.916943 0 169.374914 47.823976 169.374914 144.134927 0 96.975951-57.122971 145.462926-170.699913 145.462926H413.732463v184.652906h-72.399963V362.000076z m72.399963 61.771969v166.053915h118.22994c35.867982 0 61.771969-6.641997 78.37796-19.926989 15.940992-13.283993 24.575988-34.538982 24.575988-63.764968s-8.634996-50.479974-25.239987-62.435968q-24.907987-19.92599-77.712961-19.92699H413.733463z\" fill=\"#FFFFFF\" ></path></symbol><symbol id=\"icon-word2\" viewBox=\"0 0 1024 1024\"><path d=\"M136.533604 0.00026a49.119975 49.119975 0 0 0-35.839982 15.359992C91.307627 25.600247 85.33463 38.40024 85.33463 51.200234v921.599532a49.119975 49.119975 0 0 0 15.359992 35.839982 50.545974 50.545974 0 0 0 35.839982 15.359992h750.931619a49.119975 49.119975 0 0 0 35.839981-15.359992 50.546974 50.546974 0 0 0 15.359993-35.839982V290.134113L648.533344 0.00026z\" fill=\"#2F97FE\" ></path><path d=\"M938.666197 290.133113H699.733318a52.492973 52.492973 0 0 1-51.199974-51.199974V0.00026z\" fill=\"#97C6FF\" ></path><path d=\"M242.08055 417.000048h88.622955l72.475964 268.788864L474.948432 417.000048h73.874963l71.776963 268.788864 72.472963-268.784864h88.622955l-122.447937 363.759816h-74.629963l-72.469963-265.927865-73.172963 265.927865h-74.570962z m0 0\" fill=\"#FFFFFF\" ></path></symbol><symbol id=\"icon-video2\" viewBox=\"0 0 1024 1024\"><path d=\"M147.2 0C102.4 0 65.6 36.8 65.6 81.6v860.8c0 44.8 36.8 81.6 81.6 81.6h731.2c44.8 0 81.6-36.8 81.6-81.6V324.8L657.6 0H147.2z\" fill=\"#db4437\" ></path><path d=\"M960 326.4v16H755.2s-100.8-20.8-99.2-108.8c0 0 4.8 92.8 97.6 92.8H960z\" fill=\"#db4437\" ></path><path d=\"M657.6 0v233.6c0 25.6 17.6 92.8 97.6 92.8H960L657.6 0z\" fill=\"#e37268\" ></path><path d=\"M456 728c0 6.4-1.6 12.8-6.4 16-3.2 3.2-84.8 70.4-190.4 113.6-1.6 1.6-4.8 1.6-8 1.6s-6.4-1.6-9.6-3.2c-6.4-3.2-9.6-8-11.2-16 0-1.6-4.8-54.4-4.8-112s4.8-108.8 4.8-112c1.6-6.4 4.8-11.2 11.2-16 3.2-1.6 6.4-3.2 9.6-3.2 3.2 0 6.4 1.6 8 3.2 105.6 41.6 187.2 110.4 190.4 113.6 4.8 3.2 6.4 9.6 6.4 14.4z\" fill=\"#FFFFFF\" ></path></symbol><symbol id=\"icon-list\" viewBox=\"0 0 1024 1024\"><path d=\"M287 265.90625V195.59375c0-15.53378906 12.59121094-28.125 28.125-28.125h618.75c15.53378906 0 28.125 12.59121094 28.125 28.125v70.3125c0 15.53378906-12.59121094 28.125-28.125 28.125H315.125c-15.53378906 0-28.125-12.59121094-28.125-28.125z m28.125 309.375h618.75c15.53378906 0 28.125-12.59121094 28.125-28.125v-70.3125c0-15.53378906-12.59121094-28.125-28.125-28.125H315.125c-15.53378906 0-28.125 12.59121094-28.125 28.125v70.3125c0 15.53378906 12.59121094 28.125 28.125 28.125z m0 281.25h618.75c15.53378906 0 28.125-12.59121094 28.125-28.125v-70.3125c0-15.53378906-12.59121094-28.125-28.125-28.125H315.125c-15.53378906 0-28.125 12.59121094-28.125 28.125v70.3125c0 15.53378906 12.59121094 28.125 28.125 28.125zM90.125 315.125h112.5c15.53378906 0 28.125-12.59121094 28.125-28.125V174.5c0-15.53378906-12.59121094-28.125-28.125-28.125H90.125C74.59121094 146.375 62 158.96621094 62 174.5v112.5c0 15.53378906 12.59121094 28.125 28.125 28.125z m0 281.25h112.5c15.53378906 0 28.125-12.59121094 28.125-28.125v-112.5c0-15.53378906-12.59121094-28.125-28.125-28.125H90.125c-15.53378906 0-28.125 12.59121094-28.125 28.125v112.5c0 15.53378906 12.59121094 28.125 28.125 28.125z m0 281.25h112.5c15.53378906 0 28.125-12.59121094 28.125-28.125v-112.5c0-15.53378906-12.59121094-28.125-28.125-28.125H90.125c-15.53378906 0-28.125 12.59121094-28.125 28.125v112.5c0 15.53378906 12.59121094 28.125 28.125 28.125z\"  ></path></symbol><symbol id=\"icon-search\" viewBox=\"0 0 1035 1024\"><path d=\"M1013.852766 1011.332492a42.225028 42.225028 0 0 1-59.70619 0L702.316509 759.502424a428.900723 428.900723 0 1 1 133.958901-196.00858 41.718328 41.718328 0 0 1-4.919216 14.166497c-1.330088 3.61024-2.385714 7.347155-3.800252 10.91517l-2.385714-2.385714a42.225028 42.225028 0 0 1-72.690386-29.13527l-0.380025-3.905815a41.950565 41.950565 0 0 1 11.379645-28.670794l-3.926928-3.905815a336.976836 336.976836 0 1 0-88.123633 150.764463 6.333754 6.333754 0 0 0 0.612262-0.928951l61.120729 1.055626 145.254096 145.232984 0.274463-0.274463 135.12009 135.12009a42.225028 42.225028 0 0 1 0.042225 59.79064z\"  ></path></symbol><symbol id=\"icon-home\" viewBox=\"0 0 1025 1024\"><path d=\"M938.977859 1024c-100.292785 0-198.718416 0-298.210992 0 0-113.362855 0-226.458974 0-340.355301-85.889034 0-170.17765 0-255.799948 0 0 112.829383 0 225.658765 0 339.821829-100.292785 0-199.251889 0-299.277937 0 0-4.534514 0-8.802292 0-13.07007 0-176.579318 0-352.891899 0.266736-529.471216 0-5.868195 3.46757-13.870279 8.002084-17.604585 138.436051-111.228966 277.138838-222.191196 416.108362-333.153425 0.533472-0.533472 1.600417-0.800208 3.200834-1.333681 45.345142 36.276114 91.223756 72.818963 136.835634 109.361813 91.490492 73.352436 182.980985 146.704871 275.004949 219.523834 10.402709 8.26882 14.403751 16.53764 14.403751 29.874446-0.533472 173.911956-0.266736 347.557176-0.266736 521.469133C938.977859 1013.864027 938.977859 1018.932014 938.977859 1024z\"  ></path><path d=\"M85.422245 85.889034c57.348268 0 113.096119 0 169.910914 0 0 38.410003 0 76.820005 0 119.497786 87.222714-69.61813 171.511331-137.10237 256.866892-205.386819 22.939307 18.404793 46.14535 36.809586 69.351394 55.214379 144.570982 115.76348 289.141964 231.52696 433.979682 347.023704 6.668403 5.334723 9.602501 10.135973 9.335765 18.671529-0.800208 13.603543-0.266736 27.207085-0.266736 44.011461C852.288617 327.285231 682.644439 191.516541 512.200052 55.214379 342.022402 191.516541 172.111487 327.285231 0.066684 464.921073c0-19.205001-0.266736-35.475905 0.266736-51.480073 0-3.200834 3.734306-6.668403 6.401667-9.069028 22.672571-18.404793 45.611878-36.809586 68.817921-54.680906 7.468612-5.868195 10.135973-12.003126 9.869237-21.33889C85.422245 252.599114 85.422245 177.11279 85.422245 101.626465 85.422245 96.825215 85.422245 92.023965 85.422245 85.889034z\"  ></path></symbol><symbol id=\"icon-license\" viewBox=\"0 0 1131 1024\"><path d=\"M617.969758 52.960171a52.967605 52.967605 0 0 0-105.93521 0v88.271908h-69.486064a124.594675 124.594675 0 0 0-61.368081 16.258267l-90.9556 52.038349a17.655868 17.655868 0 0 1-8.831652 2.408632H123.63518a52.967605 52.967605 0 0 0 0 105.920342h30.219412L4.712684 649.208139a52.967605 52.967605 0 0 0 10.853713 59.175037l37.437875-37.430441-37.430441 37.430441v0.074341l0.185851 0.185851 0.178417 0.185851 0.542686 0.542686 1.070503 1.070503 3.181773 2.824939a248.148584 248.148584 0 0 0 48.440269 31.780563 317.218341 317.218341 0 0 0 142.86757 32.063057c62.141223 0 109.875257-15.537164 142.860136-32.063057a248.222925 248.222925 0 0 0 48.440269-31.780563l3.181773-2.824939 1.070504-1.070503 0.542685-0.542686 0.178417-0.185851 0.074341-0.185851-37.348666-37.497348 37.430441 37.430441a52.967605 52.967605 0 0 0 10.853712-59.175037L270.190038 317.865104h11.448437c21.543876 0 42.656578-5.657312 61.368082-16.228531l91.02994-52.038349a17.655868 17.655868 0 0 1 8.749877-2.408632h69.486064v670.878394H335.721188a52.967605 52.967605 0 0 0 0 105.935211H794.751464a52.967605 52.967605 0 0 0 0-105.935211H618.200214V247.182158h69.486064a17.655868 17.655868 0 0 1 8.749877 2.401198l91.096846 51.978876a122.795635 122.795635 0 0 0 61.301175 16.310306h11.448437L711.133271 649.215573a52.967605 52.967605 0 0 0 10.861147 59.175037l37.430441-37.423007-37.430441 37.430441v0.074341l0.185851 0.185851 0.185851 0.185851 0.542686 0.542686 1.070503 1.063069 3.17434 2.824939a248.371606 248.371606 0 0 0 48.447702 31.780563 317.218341 317.218341 0 0 0 142.860136 32.063057c62.148657 0 109.882691-15.537164 142.860136-32.063057a248.014771 248.014771 0 0 0 48.447703-31.780563l3.181774-2.824939 0.817745-0.817745 0.542686-0.267626 0.542685-0.542686 0.178418-0.185851 0.07434-0.185851-37.3561-37.497348 37.430441 37.430441a52.967605 52.967605 0 0 0 10.890883-59.175037l-149.082436-331.35047h30.15994a52.967605 52.967605 0 0 0 0-105.927776h-157.765406a17.655868 17.655868 0 0 1-8.757311-2.393764l-91.096846-51.978876a123.583645 123.583645 0 0 0-61.226835-16.310306h-69.560404zM119.739737 651.594469c20.131407 9.530452 50.707654 19.269057 92.16735 19.269058 41.452262 0 72.028509-9.738605 92.152482-19.269058L211.921956 446.801264z m706.197566 0c20.123973 9.530452 50.70022 19.269057 92.152482 19.269058 41.452262 0 72.035943-9.738605 92.159916-19.269058l-92.159916-204.793205z\"  ></path></symbol><symbol id=\"icon-left\" viewBox=\"0 0 1024 1024\"><path d=\"M339.430666 573.930491L339.563696 573.797461 684.570357 918.803099 746.43331 856.939123 401.427672 511.933485 746.30028 167.060877 684.436304 105.196901 339.563696 450.069509 277.700743 511.933485 277.56669 512.066515Z\"  ></path></symbol><symbol id=\"icon-close\" viewBox=\"0 0 1024 1024\"><path d=\"M887.2 774.2 624.8 510.8l263-260c10.8-10.8 10.8-28.4 0-39.2l-74.8-75.2c-5.2-5.2-12.2-8-19.6-8-7.4 0-14.4 3-19.6 8L512 395.6 249.8 136.6c-5.2-5.2-12.2-8-19.6-8-7.4 0-14.4 3-19.6 8L136 211.8c-10.8 10.8-10.8 28.4 0 39.2l263 260L136.8 774.2c-5.2 5.2-8.2 12.2-8.2 19.6 0 7.4 2.8 14.4 8.2 19.6l74.8 75.2c5.4 5.4 12.4 8.2 19.6 8.2 7 0 14.2-2.6 19.6-8.2L512 626.2l261.4 262.2c5.4 5.4 12.4 8.2 19.6 8.2 7 0 14.2-2.6 19.6-8.2l74.8-75.2c5.2-5.2 8.2-12.2 8.2-19.6C895.4 786.4 892.4 779.4 887.2 774.2z\"  ></path></symbol><symbol id=\"icon-full_screen_full\" viewBox=\"0 0 1117 1024\"><path d=\"M93.090909 93.090909v232.727273a46.545455 46.545455 0 0 1-93.090909 0V46.545455a46.545455 46.545455 0 0 1 46.545455-46.545455h279.272727a46.545455 46.545455 0 0 1 0 93.090909z m0 837.818182h232.727273a46.545455 46.545455 0 0 1 0 93.090909H46.545455a46.545455 46.545455 0 0 1-46.545455-46.545455v-279.272727a46.545455 46.545455 0 0 1 93.090909 0zM930.909091 93.090909h-232.727273a46.545455 46.545455 0 0 1 0-93.090909h279.272727a46.545455 46.545455 0 0 1 46.545455 46.545455v279.272727a46.545455 46.545455 0 0 1-93.090909 0z m0 837.818182v-232.727273a46.545455 46.545455 0 0 1 93.090909 0v279.272727a46.545455 46.545455 0 0 1-46.545455 46.545455h-279.272727a46.545455 46.545455 0 0 1 0-93.090909z\" fill=\"#409EFF\" ></path><path d=\"M93.090909 93.090909v232.727273a46.545455 46.545455 0 0 1-93.090909 0V46.545455a46.545455 46.545455 0 0 1 46.545455-46.545455h279.272727a46.545455 46.545455 0 0 1 0 93.090909z m0 837.818182h232.727273a46.545455 46.545455 0 0 1 0 93.090909H46.545455a46.545455 46.545455 0 0 1-46.545455-46.545455v-279.272727a46.545455 46.545455 0 0 1 93.090909 0zM930.909091 93.090909h-232.727273a46.545455 46.545455 0 0 1 0-93.090909h279.272727a46.545455 46.545455 0 0 1 46.545455 46.545455v279.272727a46.545455 46.545455 0 0 1-93.090909 0z m0 837.818182v-232.727273a46.545455 46.545455 0 0 1 93.090909 0v279.272727a46.545455 46.545455 0 0 1-46.545455 46.545455h-279.272727a46.545455 46.545455 0 0 1 0-93.090909z\"  ></path></symbol><symbol id=\"icon-video\" viewBox=\"0 0 48 48\" fill=\"none\"><path data-follow-stroke=\"currentColor\" data-follow-fill=\"currentColor\" d=\"M10 44h28a2 2 0 0 0 2-2V14H30V4H10a2 2 0 0 0-2 2v36a2 2 0 0 0 2 2Z\" fill=\"currentColor\" stroke=\"currentColor\" stroke-width=\"4\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/><path data-follow-stroke=\"currentColor\" d=\"m30 4 10 10\" stroke=\"currentColor\" stroke-width=\"4\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/><path d=\"M14 21h13.493v3.5L34 22v11l-6.507-2.5V34H14V21Z\" fill=\"#FFF\" stroke=\"#FFF\" stroke-width=\"4\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/></symbol><symbol id=\"icon-doc\" viewBox=\"0 0 48 48\" fill=\"none\"><path data-follow-stroke=\"currentColor\" data-follow-fill=\"currentColor\" d=\"M10 44h28a2 2 0 0 0 2-2V14H30V4H10a2 2 0 0 0-2 2v36a2 2 0 0 0 2 2Z\" fill=\"currentColor\" stroke=\"currentColor\" stroke-width=\"4\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/><path data-follow-stroke=\"currentColor\" d=\"m30 4 10 10\" stroke=\"currentColor\" stroke-width=\"4\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/><path d=\"M24 22v14m-6-14h12\" stroke=\"#FFF\" stroke-width=\"4\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/></symbol><symbol id=\"icon-audio\" viewBox=\"0 0 48 48\" fill=\"none\"><path data-follow-stroke=\"currentColor\" data-follow-fill=\"currentColor\" stroke-linejoin=\"round\" stroke-linecap=\"round\" stroke-width=\"4\" stroke=\"currentColor\" fill=\"currentColor\" d=\"M10 44h28a2 2 0 0 0 2-2V14H30V4H10a2 2 0 0 0-2 2v36a2 2 0 0 0 2 2Z\"/><path data-follow-stroke=\"currentColor\" stroke-linejoin=\"round\" stroke-linecap=\"round\" stroke-width=\"4\" stroke=\"currentColor\" d=\"m30 4 10 10\"/><path stroke-linejoin=\"round\" stroke-linecap=\"round\" stroke-width=\"4\" stroke=\"#FFF\" d=\"m31 20-6 2.969V33.5\"/><circle stroke-linejoin=\"round\" stroke-linecap=\"round\" stroke-width=\"4\" stroke=\"#FFF\" fill=\"#FFF\" r=\"4\" cy=\"33\" cx=\"21\"/></symbol><symbol id=\"icon-ppt\" viewBox=\"0 0 48 48\" fill=\"none\"><path data-follow-stroke=\"currentColor\" data-follow-fill=\"currentColor\" stroke-linejoin=\"round\" stroke-linecap=\"round\" stroke-width=\"4\" stroke=\"currentColor\" fill=\"currentColor\" d=\"M10 44h28a2 2 0 0 0 2-2V14H30V4H10a2 2 0 0 0-2 2v36a2 2 0 0 0 2 2Z\"/><path data-follow-stroke=\"currentColor\" stroke-linejoin=\"round\" stroke-linecap=\"round\" stroke-width=\"4\" stroke=\"currentColor\" d=\"m30 4 10 10\"/><path xmlns=\"http://www.w3.org/2000/svg\" clip-rule=\"evenodd\" d=\"M18 18h12v7.992L18.008 26 18 18Z\" stroke=\"#FFF\" stroke-width=\"4\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/><path xmlns=\"http://www.w3.org/2000/svg\" d=\"M18 18v16\" stroke=\"#FFF\" stroke-width=\"4\" stroke-linecap=\"round\"/></symbol><symbol id=\"icon-word\" viewBox=\"0 0 48 48\" fill=\"none\"><path data-follow-stroke=\"currentColor\" data-follow-fill=\"currentColor\" d=\"M10 44h28a2 2 0 0 0 2-2V14H30V4H10a2 2 0 0 0-2 2v36a2 2 0 0 0 2 2Z\" fill=\"currentColor\" stroke=\"currentColor\" stroke-width=\"4\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/><path data-follow-stroke=\"currentColor\" d=\"m30 4 10 10\" stroke=\"currentColor\" stroke-width=\"4\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/><path stroke-linejoin=\"round\" stroke-linecap=\"round\" stroke-width=\"4\" stroke=\"#FFF\" d=\"m16.008 20 3 14 5-10 5 10 3-14\"/></symbol><symbol id=\"icon-file\" viewBox=\"0 0 48 48\" fill=\"none\"><path data-follow-stroke=\"currentColor\" data-follow-fill=\"currentColor\" d=\"M10 44h28a2 2 0 0 0 2-2V14H30V4H10a2 2 0 0 0-2 2v36a2 2 0 0 0 2 2Z\" fill=\"currentColor\" stroke=\"currentColor\" stroke-width=\"4\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/><path data-follow-stroke=\"currentColor\" d=\"m30 4 10 10\" stroke=\"currentColor\" stroke-width=\"4\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/></symbol><symbol id=\"icon-image\" viewBox=\"0 0 48 48\" fill=\"none\"><path data-follow-stroke=\"currentColor\" data-follow-fill=\"currentColor\" d=\"M10 44h28a2 2 0 0 0 2-2V14H30V4H10a2 2 0 0 0-2 2v36a2 2 0 0 0 2 2Z\" fill=\"currentColor\" stroke=\"currentColor\" stroke-width=\"4\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/><path data-follow-stroke=\"currentColor\" d=\"m30 4 10 10\" stroke=\"currentColor\" stroke-width=\"4\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/><path stroke-linejoin=\"round\" stroke-width=\"4\" stroke=\"#fff\" fill=\"#fff\" d=\"m19.5 27 2.5 2 3-3.5 8.5 6.5v2a1 1 0 0 1-1 1h-17a1 1 0 0 1-1-1v-2l5-5Z\"/><circle fill=\"#fff\" r=\"2\" cy=\"20\" cx=\"15\"/></symbol><symbol id=\"icon-pdf\" viewBox=\"0 0 48 48\" fill=\"none\"><path data-follow-stroke=\"currentColor\" data-follow-fill=\"currentColor\" stroke-linejoin=\"round\" stroke-linecap=\"round\" stroke-width=\"4\" stroke=\"currentColor\" fill=\"currentColor\" d=\"M10 44h28a2 2 0 0 0 2-2V14H30V4H10a2 2 0 0 0-2 2v36a2 2 0 0 0 2 2Z\"/><path data-follow-stroke=\"currentColor\" stroke-linejoin=\"round\" stroke-linecap=\"round\" stroke-width=\"4\" stroke=\"currentColor\" d=\"m30 4 10 10\"/><path d=\"M29.227 32.477a5.445 5.445 0 0 1-3.158-1.28c-1.749.384-3.413.939-5.077 1.622-1.323 2.346-2.56 3.541-3.627 3.541-.213 0-.469-.043-.64-.17A1.257 1.257 0 0 1 16 35.036c0-.384.085-1.45 4.139-3.2.938-1.706 1.664-3.456 2.261-5.29-.512-1.024-1.621-3.542-.853-4.822.256-.469.768-.725 1.322-.682.427 0 .854.213 1.11.554.554.768.512 2.39-.214 4.779a12.87 12.87 0 0 0 2.646 3.413c.896-.17 1.792-.298 2.688-.298 2.005.042 2.304.981 2.261 1.536 0 1.45-1.408 1.45-2.133 1.45zM17.28 35.123l.128-.043c.597-.213 1.067-.64 1.408-1.195a3.372 3.372 0 0 0-1.536 1.238zm5.675-12.8h-.128c-.043 0-.128 0-.171.042-.17.726-.043 1.494.256 2.176a3.586 3.586 0 0 0 .043-2.218zm.298 6.186-.042.086-.043-.043c-.384.981-.81 1.963-1.28 2.901l.085-.042v.085c.939-.341 1.963-.64 2.902-.853l-.043-.043h.128a13.074 13.074 0 0 1-1.707-2.09zm5.803 2.262c-.384 0-.725 0-1.11.085.427.213.854.299 1.28.341.3.043.598 0 .854-.085 0-.128-.17-.341-1.024-.341z\" fill=\"#fff\"/></symbol><symbol id=\"icon-code\" viewBox=\"0 0 48 48\" fill=\"none\"><path data-follow-stroke=\"currentColor\" data-follow-fill=\"currentColor\" stroke-linejoin=\"round\" stroke-linecap=\"round\" stroke-width=\"4\" stroke=\"currentColor\" fill=\"currentColor\" d=\"M10 44h28a2 2 0 0 0 2-2V14H30V4H10a2 2 0 0 0-2 2v36a2 2 0 0 0 2 2Z\"/><path data-follow-stroke=\"currentColor\" stroke-linejoin=\"round\" stroke-linecap=\"round\" stroke-width=\"4\" stroke=\"currentColor\" d=\"m30 4 10 10M30 4l10 10\"/><path d=\"m27 24 5 5-5 5m-6-10-5 5 5 5\" stroke=\"#fff\" stroke-width=\"4\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/></symbol><symbol id=\"icon-puzzle\" viewBox=\"0 0 48 48\" fill=\"none\"><path d=\"M4 24V12H13V10C13 6.68629 15.6863 4 19 4C22.3137 4 25 6.68629 25 10V12H34V24H38C41.3137 24 44 26.6863 44 30C44 33.3137 41.3137 36 38 36H34V44H4V36H8C11.3137 36 14 33.3137 14 30C14 26.6863 11.3137 24 8 24H4Z\" fill=\"currentColor\" stroke=\"currentColor\" stroke-width=\"4\" stroke-linejoin=\"miter\"/></symbol><symbol viewBox=\"0 0 1024 1024\" id=\"icon-folder\"><path d=\"M918.673 883H104.327C82.578 883 65 867.368 65 848.027V276.973C65 257.632 82.578 242 104.327 242h814.346C940.422 242 958 257.632 958 276.973v571.054C958 867.28 940.323 883 918.673 883z\" fill=\"#ffb900\"></path><path d=\"M512 411H65V210.37C65 188.597 82.598 171 104.371 171h305.92c17.4 0 32.71 11.334 37.681 28.036L512 411z\" fill=\"#FFB02C\"></path><path d=\"M918.673 883H104.327C82.578 883 65 865.42 65 843.668V335.332C65 313.58 82.578 296 104.327 296h814.346C940.422 296 958 313.58 958 335.332v508.336C958 865.32 940.323 883 918.673 883z\" fill=\"#FFCA28\"></path></symbol><symbol id=\"icon-folder2\" viewBox=\"0 0 1408 1024\"><path d=\"M566.38583472-10.59260571H175.88929636c-71.59103266 0-129.51468459 58.57448076-129.51468456 130.1655134L45.72378487 900.56598437c0 71.59103266 58.57448076 130.1655134 130.16551149 130.16551341h1041.32410353c71.59103266 0 130.1655134-58.57448076 130.16551148-130.16551341V249.7384211c0-71.59103266-58.57448076-130.1655134-130.16551148-130.16551341h-520.66205177l-130.1655134-130.1655134z\" fill=\"#f8d673\" ></path></symbol></svg>',\n    d = (d = document.getElementsByTagName('script'))[d.length - 1].getAttribute('data-injectcss')\n  if (d && !t.__iconfont__svg__cssinject__) {\n    t.__iconfont__svg__cssinject__ = !0\n    try {\n      document.write(\n        '<style>.svgfont {display: inline-block;width: 1em;height: 1em;fill: currentColor;vertical-align: -0.1em;font-size:16px;}</style>',\n      )\n    } catch (t) {\n      console && console.log(t)\n    }\n  }\n  function n() {\n    e || ((e = !0), o())\n  }\n  ; (a = function () {\n    var t, a, l\n      ; ((l = document.createElement('div')).innerHTML = h),\n        (h = null),\n        (a = l.getElementsByTagName('svg')[0]) &&\n        (a.setAttribute('aria-hidden', 'true'),\n          (a.style.position = 'absolute'),\n          (a.style.width = 0),\n          (a.style.height = 0),\n          (a.style.overflow = 'hidden'),\n          (t = a),\n          (l = document.body).firstChild ? (a = l.firstChild).parentNode.insertBefore(t, a) : l.appendChild(t))\n  }),\n    document.addEventListener\n      ? ~['complete', 'loaded', 'interactive'].indexOf(document.readyState)\n        ? setTimeout(a, 0)\n        : ((l = function () {\n          document.removeEventListener('DOMContentLoaded', l, !1), a()\n        }),\n          document.addEventListener('DOMContentLoaded', l, !1))\n      : document.attachEvent &&\n      ((o = a),\n        (i = t.document),\n        (e = !1),\n        (c = function () {\n          try {\n            i.documentElement.doScroll('left')\n          } catch (t) {\n            return void setTimeout(c, 50)\n          }\n          n()\n        })(),\n        (i.onreadystatechange = function () {\n          'complete' == i.readyState && ((i.onreadystatechange = null), n())\n        }))\n})(window)\n"
  },
  {
    "path": "packages/sharelist-manage/src/components/icon/index.less",
    "content": "// .sl-icon {\n//   display: inline-block;\n//   font-style: normal;\n//   vertical-align: -0.125em;\n//   text-align: center;\n//   text-transform: none;\n//   line-height: 0;\n//   text-rendering: optimizeLegibility;\n//   -webkit-font-smoothing: antialiased;\n// }\n\n/*\n--icon-folder-color:var(--primary-color);\n--icon-audio-color:rgb(132,140,239);\n--icon-video-color:rgb(219,68,55);\n--icon-word-color:rgb(47,151,254);\n--icon-pdf-color:rgb(252,134,132);\n--icon-ppt-color:rgb(254,159,93);\n--icon-file-color:rgb(212,214,218);\n--icon-word-color:rgb(88,178,252);\n--icon-doc-color:rgb(88,178,252);\n--icon-image-color:rgb(252,132,129);\n*/\n@type: {\n  icon-folder:#f8d673;\n  icon-file:rgb(188,190,194);//rgb(212,214,218);\n\n  icon-audio:rgb(223,94,83);\n  icon-video:rgb(223,94,83);\n  icon-image:rgb(223,94,83);\n\n  icon-word:rgb(96,181,252);\n  icon-doc:rgb(96,181,252);\n  icon-code:rgb(96,181,252);\n  icon-ppt:rgb(254,173,96);\n  icon-pdf:rgb(252,134,132);\n}\n// :root{\n//   each(@type, {\n//     --@{key}: @value;\n//   });\n// }\n\neach(@type, {\n  #@{key}{\n    color:~\"var(--@{key}-color,@{value})\";\n  }\n});"
  },
  {
    "path": "packages/sharelist-manage/src/components/icon/index.ts",
    "content": "import { createFromIconfontCN } from '@ant-design/icons-vue'\nimport config from '../../config/setting'\nimport './icon-svg'\nimport './index.less'\nconst IconFont = createFromIconfontCN({\n  scriptUrl: [],\n})\n\nexport default IconFont\n"
  },
  {
    "path": "packages/sharelist-manage/src/components/image/index.less",
    "content": ".hide-modal{\n  display: none;\n}"
  },
  {
    "path": "packages/sharelist-manage/src/components/image/index.tsx",
    "content": "import { Image, Modal } from 'ant-design-vue'\n\nexport const showImage = (urls: Array<string>, index: number) => {\n  const onVisibleChange = (e: any) => {\n    if (e === false) {\n      modal.destroy()\n    }\n  }\n  const modal = Modal.confirm({\n    style: { display: 'none' },\n    width: '500px',\n    closable: true,\n    content: (\n      <Image.PreviewGroup preview={{ visible: true, onVisibleChange }}  >\n        {\n          urls.map((url, idx) => <Image src={url} />)\n        }\n\n      </Image.PreviewGroup >\n    ),\n  })\n}"
  },
  {
    "path": "packages/sharelist-manage/src/components/modal/index.less",
    "content": ".hide--modal{\n  .ant-modal-confirm-btns,.anticon-exclamation-circle{\n    display: none;\n  }\n  .ant-modal-body{\n    padding:0;\n  }\n}\n.modal{\n  .modal__header{\n    padding:16px;\n  }\n\n  .modal__body{\n\n  }\n}"
  },
  {
    "path": "packages/sharelist-manage/src/components/modal/index.tsx",
    "content": "import { Modal } from 'ant-design-vue'\nimport './index.less'\nexport const modal = function (options: any) {\n  const { content, title, ...rest } = options\n\n  const node = () => <div class=\"modal\">\n    <div class=\"modal__header\">{title}</div>\n    <div class=\"modal__body\">{content}</div>\n  </div>\n  Modal.confirm({\n    ...rest,\n    class: 'hide--modal',\n    content: node\n  })\n}"
  },
  {
    "path": "packages/sharelist-manage/src/components/player/index.less",
    "content": ".widget-player{\n  position: fixed;\n  bottom:0;\n  opacity: 0;\n  pointer-events: none;\n  // transform:translate(-50%,0);\n  transition:all 0.3s;\n  // left:50%;\n  max-width: 560px;\n  left:0;right:0;\n  margin:auto;\n  &.widget-player--visible{\n    opacity: 1;\n    pointer-events: auto;\n    bottom:16px;\n  }\n\n  .widget-player__tip{\n    opacity: .64;\n    font-size:10px;\n  }\n\n  .widget-player__action{\n    display: flex;\n    align-items: center;\n    flex:none;\n  }\n\n  .widget-player__progress{\n    position: absolute;\n    display: none;\n    width: 0%;\n    height:3px;\n    left:0;\n    bottom: 0;\n    background-color: var(--plyr-color-main);\n    transition:all 0.3s;\n  }\n\n}\n\n.widget-player__list{\n  // max-height: 0;\n  height: 0;\n  display: flex;\n  flex-direction: column;\n  transition:all .5s cubic-bezier(0.66, 0, 0.01, 1);\n  opacity: 0;\n  &.widget-player__list--visible{\n    height: 260px;\n    opacity: 1;\n  }\n  .widget-player__list-header{\n    color:#fff;\n    padding:8px;\n    text-align: center;\n    border-bottom:1px solid rgba(132,133,141,.2);\n    display:flex;\n    align-items: center;\n    justify-content: center;\n  }\n  .widget-player__list-body{\n    overflow-y: auto;\n    overflow-x: hidden;\n    color:var(--player-color-main);\n    font-size: 13px;\n    margin-bottom: 0;\n    &::-webkit-scrollbar-track {\n      // box-shadow: inset 0 0 1px rgba(0,0,0,0.2);\n      background-color: rgba(0,0,0,0);\n    }\n    \n    &::-webkit-scrollbar {\n      width: 5px;\n      // background-color: rgba(200,200,200,1);\n    }\n    &::-webkit-scrollbar-thumb {\n      background-color: rgba(200,200,200,.6);\n    }\n  }\n\n  .widget-player__list-item{\n    display: flex;\n    align-items: center;\n    padding:6px 16px;\n    cursor: pointer;\n    transition: all 0.3s;\n    &:hover{\n      background-color: var(--player-hover-background,rgba(0,0,0,0));\n    }\n    .widget-player__list-no{\n      flex:none;\n      padding-right: 8px;\n      text-align: right;\n      width:36px;\n    }\n  }\n\n  .widget-player__list-item--playing{\n    background-color: var(--player-fill,rgba(0,0,0,0)) !important;\n  }\n\n}\n\n.widget-player-wrap{\n  box-shadow: 0 1px 5px rgba(0,0,0,0.2);\n  background-color:var(--plyr-audio-background,#fff);\n  border-radius: 8px;\n  // overflow: hidden;\n\n  .widget-player__content{\n    display: none;\n    padding:0 12px;\n    color:var(--player-color-main);\n  }\n  .widget-player__body{\n    display: flex;\n    align-items: center;\n    justify-content: space-between;\n    flex:none;\n    position: relative;\n    z-index:1;\n    // background-color:var(--plyr-audio-background,#fff);\n\n    .widget-player__toggle-expand{\n      font-size:18px;\n      padding:0 12px;\n      cursor: pointer;\n      color:var(--plyr-audio-control-color,#000);\n    }\n    .widget-player__close{\n      flex:none;\n      font-size:18px;\n      padding:0 12px;\n      cursor: pointer;\n      color:var(--plyr-audio-control-color,#000);\n    }\n    \n    .widget-player__btn-full{\n      flex:none;\n      font-size:18px;\n      padding:0 12px;\n      cursor: pointer;\n      color:var(--plyr-audio-control-color,#000);\n      display: none;\n    }\n\n    .widget-player__download{\n      flex:none;\n      font-size:18px;\n      padding:0 12px;\n      cursor: pointer;\n      color:var(--plyr-audio-control-color,#000);\n    }\n  }\n}\n\n.widget-player-audio{\n  .plyr{\n    min-width:300px;\n  }\n}\n.widget-player-video{\n  overflow: hidden;\n  .widget-player__progress{\n    display: block;\n  }\n  .widget-player__content{\n    display: block;\n    cursor: pointer;\n    min-width:200px;\n    .widget-player__content-title{\n      display: -webkit-box;    \n      -webkit-box-orient: vertical;    \n      -webkit-line-clamp: 2;    \n      overflow: hidden;\n    }\n  }\n\n  .widget-player__btn-full{\n    display: block !important;\n  }\n    \n  &.widget-player--mini{\n    .plyr{\n      width:33.3%;\n      height:70px;\n      min-width:100px;\n      flex:none;\n      .plyr__controls{\n        display: none;\n      }\n      video{\n        object-fit: cover;\n      }\n      .plyr__video-wrapper{\n        margin: 0;\n        height: 100%;\n      }\n    }\n  }\n}\n\n.app-light{\n  --plyr-color-main:#00b3ff;\n  --plyr-audio-controls-background:rgba(0,0,0,0);\n  --plyr-audio-control-color:#ddd;\n  --plyr-control-radius:10px;\n\n  --plyr-audio-background:rgb(49,49,54);\n  --plyr-audio-control-background-hover:transparent;\n  --plyr-video-control-background-hover:transparent;\n  --plyr-video-background:rgba(0,0,0,0);\n  --player-color-main:#fff;\n\n  --player-hover-background:rgba(255,255,255,.1);\n\n  --player-fill:rgba(0,179,255,.5);\n\n  \n  &::-webkit-scrollbar-track {\n    // box-shadow: inset 0 0 1px rgba(0,0,0,0.2);\n    background-color: #000;\n  }\n  \n  &::-webkit-scrollbar {\n    width: 5px;\n    // background-color: rgba(200,200,200,1);\n  }\n  &::-webkit-scrollbar-thumb {\n    background-color: rgba(200,200,200,.6);\n  }\n}\n\n@media screen and (max-width:480px) {\n  .widget-player{\n    transform:translate(0,0);\n    left:0;width:100%;\n    bottom:-16px;\n    .widget-player-wrap{\n      border-radius: 0;\n    }\n    .widget-player-audio .plyr{\n      width:auto;\n      min-width:0px;\n    }\n    &.widget-player--visible{\n      bottom:0;\n    }\n    .widget-player__content{\n      min-width: 0;\n    }\n  }\n}"
  },
  {
    "path": "packages/sharelist-manage/src/components/player/index.tsx",
    "content": "import Plyr from 'plyr'\nimport { ref, reactive, defineComponent, onMounted, onUnmounted, computed, watch, watchEffect } from 'vue'\nimport 'plyr/dist/plyr.css'\nimport './index.less'\nimport { OrderedListOutlined, CloseOutlined, FullscreenOutlined, DownloadOutlined, DownOutlined } from '@ant-design/icons-vue'\nimport { useBoolean, useState } from '@/hooks/useHooks'\n\nconst playerMap = new Map()\nexport const usePlayer = (id?: number | string): any => {\n  if (id && playerMap.has(id)) {\n    return playerMap.get(id)\n  }\n\n  const newId = id || playerMap.size + 1\n\n  const removePlayer = () => playerMap.delete(id)\n\n  const [state, setPlayer] = useState({\n    list: [],\n    type: '',\n    index: 0,\n    cur: { name: '', ctimeDisplay: '' },\n  })\n\n  const instance = {\n    id: newId,\n    data: state,\n    setPlayer,\n    removePlayer\n  }\n\n  playerMap.set(newId, instance)\n\n  return instance\n}\n\nexport default defineComponent({\n  props: {\n    meidaId: {\n      type: String,\n      required: true,\n    },\n  },\n\n  setup(props, ctx) {\n    const el = ref()\n\n    const { data, removePlayer } = usePlayer(props.meidaId)\n\n    const [visible, { setFalse: hidePlayer, setTrue: showPlayer }] = useBoolean()\n\n    const [visibleList, { toggle: toggleList }] = useBoolean()\n\n    const [fullscreen, { setFalse: existFullScreen, setTrue: enterFullScreen }] = useBoolean()\n\n    const playerProgress = ref('0%')\n\n    let player: any\n\n    const onClose = () => {\n      player.pause()\n      hidePlayer()\n      playerProgress.value = '0%'\n    }\n\n    const onSwitch = (idx: number) => {\n      const file: any = data.list[idx]\n      if (file) {\n        showPlayer()\n        data.index = idx\n        playerProgress.value = '0%'\n\n        //上传时间较短，预览地址可能尚未转码成功。\n        let source = Date.now() - file.ctime > 15 * 60 * 1000 ? (file.preview_url || file.download_url) : file.download_url\n        player.source = {\n          type: data.type,\n          title: file.name,\n          sources: [{ src: source + \"&t=\" + Date.now(), size: 'Raw' }],\n        }\n\n        data.cur = { ...file }\n        player.play().catch(() => { })\n      }\n    }\n\n    const onFullScreen = () => {\n      enterFullScreen()\n      player.fullscreen.enter()\n    }\n\n    const onDownload = () => {\n      window.open(data.cur.download_url)\n    }\n\n    const onProgress = (e: any) => {\n      const plyr = e.detail.plyr\n      if (plyr.currentTime && plyr.duration) {\n        playerProgress.value = Math.floor((100 * plyr.currentTime) / plyr.duration) + '%'\n      }\n    }\n\n    const onError = (e: any) => {\n      console.log(e)\n    }\n\n    const isIOS = /iphone|ipad|ipod/i.test(navigator.userAgent)\n\n    onMounted(() => {\n      player = new Plyr(el.value, {\n        fullscreen: { enabled: true, fallback: true, iosNative: isIOS, container: undefined },\n      })\n      player.on('exitfullscreen', existFullScreen)\n      player.on('timeupdate', onProgress)\n      player.on('error', onError)\n    })\n\n    onUnmounted(() => {\n      removePlayer()\n    })\n\n    watchEffect(() => {\n      onSwitch(data.index)\n    })\n\n    return () => (\n      <div class={['widget-player', visible.value ? 'widget-player--visible' : null]}>\n        <div\n          class={['widget-player-wrap', 'widget-player-' + data.type, !fullscreen.value ? 'widget-player--mini' : null]}\n        >\n          <div class={['widget-player__list', visibleList.value ? 'widget-player__list--visible' : null]}>\n            <div class=\"widget-player__list-header\" onClick={toggleList}>播放列表</div>\n            <ul class=\"widget-player__list-body\">\n              {data.list.map((i: any, idx: number) => {\n                return (\n                  <li\n                    class={['widget-player__list-item', data.index == idx ? 'widget-player__list-item--playing' : null]}\n                    onClick={() => onSwitch(idx)}\n                  >\n                    <div class=\"widget-player__list-no\">{idx + 1}.</div>\n                    <div>\n                      <div>{i.name}</div>\n                      <div class=\"widget-player__tip\">{i.ctimeDisplay}</div>\n                    </div>\n                  </li>\n                )\n              })}\n            </ul>\n          </div>\n          <div class={'widget-player__body'}>\n            <video ref={el}></video>\n            <div class=\"widget-player__content\" title={data.cur.name} onClick={onFullScreen}>\n              <div class=\"widget-player__content-title\">{data.cur.name}</div>\n              <div class=\"widget-player__tip\">{data.cur.ctimeDisplay}</div>\n            </div>\n            <div class=\"widget-player__action\">\n              <DownloadOutlined onClick={onDownload} class=\"widget-player__download\" />\n              <OrderedListOutlined onClick={toggleList} class=\"widget-player__toggle-expand\" />\n              <FullscreenOutlined class=\"widget-player__btn-full\" onClick={onFullScreen} />\n              <CloseOutlined onClick={onClose} class=\"widget-player__close\" />\n            </div>\n            <div class=\"widget-player__progress\" style={{ width: playerProgress.value }}></div>\n          </div>\n        </div>\n      </div>\n    )\n  },\n})\n"
  },
  {
    "path": "packages/sharelist-manage/src/components/sider/index.less",
    "content": ".sider {\n  display: flex;\n  flex-direction: column;\n  justify-content: space-between;\n  height: 100%;\n  width:64px;\n  .menu{\n  \n    .link{\n      color:var(--primary-text-color);\n      position: relative;\n      display: block;\n      transition: all 0.3s;\n      font-size:18px;\n      text-align: center;\n      padding: 1em 0;\n    \n      transition: all 0.3s;\n      cursor: pointer;\n\n      i{\n        color:currentColor;\n      }\n      &:hover{\n        color: var(--primary-background);\n      }\n\n      &.active{\n        background: var(--background-primary-1);\n        color: var(--primary-background);\n\n        &:after{\n          content:'';\n          position: absolute;\n          width:6px;height:6px;\n          border-radius: 3px;\n          background: var(--primary-background);\n          // display: block;\n        }\n      }\n    }\n  }\n\n  .logo {\n    display: flex;\n    justify-content: center;\n    align-items: center;\n    flex-direction: column;\n    font-size: 12px;\n    font-weight: bold;\n    color: #fff;\n    line-height: 1em;\n    font-family: 'Source Code Pro';\n    text-shadow: 0 0 1px rgba(0, 0, 0, .1);\n    background: var(--primary-background);\n    width:40px;\n    height:40px;\n    border-radius: 20px;\n    margin: 32px auto;\n    text-align: center;\n  }\n\n  .sider-footer {\n\n    font-size: 13px;\n    color: var(--primary-text-color);\n    margin: 0 auto;\n    padding: 16px 0;\n    display: flex;\n    flex-direction: column;\n    justify-content: center;\n    button{\n      border-color:transparent;\n      color:#999;\n    }\n  }\n}\n"
  },
  {
    "path": "packages/sharelist-manage/src/components/sider/index.tsx",
    "content": "import './index.less'\nimport { ref, defineComponent, computed, watch, onMounted, onUnmounted, toRefs, reactive, watchEffect } from 'vue'\nimport { DatabaseOutlined, AppstoreAddOutlined, SettingOutlined, ReloadOutlined, PoweroffOutlined } from '@ant-design/icons-vue'\nimport { useRouter, useRoute, RouterLink, onBeforeRouteUpdate } from 'vue-router'\nimport useConfirm from '@/hooks/useConfirm'\nimport { useSetting } from '@/hooks/useSetting'\nimport useDriveStore from '@/store/index'\nimport { Dropdown, Menu } from 'ant-design-vue'\n\nexport default defineComponent({\n  setup() {\n\n    let driveStore = useDriveStore()\n\n    onBeforeRouteUpdate((to, from) => {\n      if (from.name == 'drive') {\n        driveStore.savePath(from.fullPath)\n      }\n    })\n\n    const router = useRouter()\n    const route = useRoute()\n    const navToDrive = () => {\n      router.push(driveStore.path || '/drive/folder')\n    }\n\n    const { signout, reload } = useSetting()\n\n    const confirmSignout = useConfirm(signout, '确认', '确认退出？')\n    const confirmReload = useConfirm(reload, '确认', '确认重新加载插件？')\n\n    const curRoute = computed(() => route.name)\n\n    const onAction = ({ key }: { key: string }) => {\n      if (key == 'exit') {\n        confirmSignout()\n      } else if (key == 'reload') {\n        confirmReload()\n      }\n    }\n    return () => <div class=\"sider\">\n      <div class=\"sider-body\">\n        <div class=\"logo\">SL</div>\n        <div class=\"menu\">\n\n          <div class={{ link: true, active: curRoute.value == 'drive' || curRoute.value == 'drive-map' }} onClick={navToDrive}>\n            <DatabaseOutlined />\n          </div>\n          <RouterLink to=\"/plugin\" class={{ link: true, active: curRoute.value == 'plugin' }}>\n            <AppstoreAddOutlined />\n          </RouterLink>\n          <RouterLink to=\"/general\" class={{ link: true, active: curRoute.value == 'general' }}>\n            <SettingOutlined />\n          </RouterLink>\n\n        </div>\n      </div>\n      <div class=\"sider-footer\" >\n        {/* <ReloadOutlined onClick={confirmReload} style={{ fontSize: '16px', color: '#1b2539', marginBottom: '16px' }} /> */}\n        <Dropdown overlayClassName=\"dropdown--drive\" trigger={['click']}>\n          {{\n            default: () => <PoweroffOutlined style={{ fontSize: '18px' }} />,\n            overlay: () => (\n              <Menu onClick={onAction}>\n                <Menu.Item class=\"dropdown-item\" key=\"reload\"><ReloadOutlined style={{ fontSize: '18px', marginRight: '8px' }} />重载插件</Menu.Item>\n                <Menu.Item class=\"dropdown-item\" key=\"exit\"><PoweroffOutlined style={{ fontSize: '18px', marginRight: '8px' }} />退出</Menu.Item>\n              </Menu>\n            )\n          }}\n        </Dropdown>\n      </div>\n    </div >\n  }\n})"
  },
  {
    "path": "packages/sharelist-manage/src/components.d.ts",
    "content": "// generated by unplugin-vue-components\n// We suggest you to commit this file into source control\n// Read more: https://github.com/vuejs/core/pull/3399\nimport '@vue/runtime-core'\n\nexport {}\n\ndeclare module '@vue/runtime-core' {\n  export interface GlobalComponents {\n    RouterLink: typeof import('vue-router')['RouterLink']\n    RouterView: typeof import('vue-router')['RouterView']\n  }\n}\n"
  },
  {
    "path": "packages/sharelist-manage/src/config/api.ts",
    "content": "const api: Array<unknown> = [\n  ['siginin', 'POST /signin'],\n  ['list', 'GET /api/drive/path/:path'],\n  ['setting', 'GET /api/setting', { token: true }],\n  ['exportSetting', 'GET /api/setting?raw=true', { token: true }],\n  ['saveSetting', 'POST /api/setting', { token: true }],\n  ['config', 'GET /api/configs'],\n  ['clearCache', 'PUT /api/cache/clear', { token: true }],\n  ['reload', 'PUT /api/reload', { token: true }],\n  //\n  ['file', 'POST /api/drive/file/get', { token: true }],\n  ['files', 'POST /api/drive/file/list', { token: true }],\n  ['filePath', 'POST /api/drive/file/path', { token: true }],\n  ['fileUpdate', 'POST /api/drive/file/update', { token: true }],\n  ['fileDelete', 'POST /api/drive/file/delete', { token: true }],\n\n  ['fileCreateUpload', 'POST /api/drive/file/create_upload', { token: true }],\n  ['fileUpload', 'POST /api/drive/file/upload?task_id=:taskId', { token: true, contentType: 'stream' }],\n  ['fileUploadCancel', 'GET /api/drive/file/cancel_upload/:$1?t=:$R', { token: true }],\n  ['fileHashDownload', 'POST /api/drive/file/hash_save', { token: true }],\n  ['mkdir', 'POST /api/drive/file/mkdir', { token: true }],\n\n  ['diskDelete', 'POST /api/drive/disk/delete', { token: true }],\n\n  ['tasks', 'GET /api/drive/tasks?t=:$R', { token: true }],\n  ['task', 'GET /api/drive/task/transfer/:$1?t=:$R', { token: true }],\n  ['resumeTask', 'PUT /api/drive/task/transfer/:$1/resume?t=:$R', { token: true }],\n  ['pauseTask', 'PUT /api/drive/task/transfer/:$1/pause?t=:$R', { token: true }],\n  ['removeTask', 'DELETE /api/drive/task/transfer/:$1', { token: true }],\n  ['retryTask', 'PUT /api/drive/task/transfer/:$1/retry', { token: true }],\n  ['remoteDownload', 'POST /api/drive/task/remote_download', { token: true }],\n  ['pauseDownload', 'PUT /api/drive/task/remote_download/:$1/pause', { token: true }],\n  ['resumeDownload', 'PUT /api/drive/task/remote_download/:$1/resume', { token: true }],\n  ['removeDownloadTask', 'DELETE /api/drive/task/remote_download/:$1', { token: true }],\n\n  ['fileMove', 'POST /api/drive/move', { token: true }],\n  ['plugin', 'GET /api/plugin/:$1?t=:$R', { token: true }],\n  ['savePlugin', 'POST /api/plugin', { token: true }],\n  ['pluginStore', 'POST /api/plugin_store', { token: true }],\n  ['removePlugin', 'DELETE /api/plugin/:$1?t=:$R', { token: true }],\n  ['upgradePlugin', 'PUT /api/plugin/:$1/upgrade', { token: true }],\n  ['installPlugin', 'POST /api/plugin_store/install', { token: true }],\n  // ['parents', 'GET /api/drive/files/:fileId/parents', { token: true }],\n]\nexport default api\n"
  },
  {
    "path": "packages/sharelist-manage/src/config/setting.ts",
    "content": "export default {\n  iconFontCN: 'https://at.alicdn.com/t/font_2637962_voky2m76mr.js',\n}\n"
  },
  {
    "path": "packages/sharelist-manage/src/hooks/useApi.ts",
    "content": "import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios'\nimport { effectScope, EffectScope, App, InjectionKey, getCurrentInstance, inject } from 'vue'\n\nexport const apiSymbol = Symbol('api') as InjectionKey<IUseApi>\n\n// export type APIItemGroup = Array<[string, string | ((...args: Array<any>) => string), Record<string, any>]>\nexport type APIItem = [\n  name: string,\n  url: string | ((...args: Array<any>) => string),\n  options?: {\n    [key: string]: number | string | boolean | ((...rest: Array<any>) => any)\n  },\n]\nexport type APICall = (...rest: Array<any>) => Promise<AxiosResponse<ReqResponse>>\n\nexport interface IUseApi {\n  install?: (app: App) => void\n  _e?: EffectScope\n  _m?: any //Record<string, APICall>\n}\n\ntype RequestMethod = 'OPTIONS' | 'GET' | 'HEAD' | 'POST' | 'PUT' | 'DELETE' | 'TRACE' | 'CONNECT'\n\ntype ReqConfig = {\n  url: string\n  method: string\n  data?: any\n  params?: any\n  responseType?: string\n  token?: boolean\n  headers?: any\n}\n\naxios.defaults.timeout = 60 * 1000\n\nconst service: AxiosInstance = axios.create()\n\n// http response 拦截器\nservice.interceptors.response.use(\n  (response) => {\n    return response.data\n  },\n\n  (error) => {\n    // 由接口返回的错误\n    if (error.response) {\n      return { error: { code: error.response.status, message: error.response.statusText } }\n    } else {\n      log(`服务器错误！错误代码：${error}`)\n      return { error: { code: error?.code || 500, message: '' } }\n    }\n  },\n)\n\nconst log = (content: string, type = 'error'): void => {\n  console.log(content)\n}\n\nexport type ReqResponse = {\n  error?: { code: number; message?: string; scope?: Record<string, any> }\n  [key: string]: any\n  [key: number]: any\n}\n\ninterface APIOptions {\n  inScope?: boolean\n  baseURL?: string\n  onReq?: (d: Record<string, any>, itemOption: Record<string, any>) => void\n  onRes?: <T>(d: T) => void\n  onError?: (e: Error) => void\n}\n// const qs = (d: Record<string, string>) => Object.keys(d).map(i => `${i}=${encodeURI(d[i])}`).join('&')\n\nconst urlReplace = (url: string, params: Record<string, any>) =>\n  url.replace(/(?:\\:)([\\w\\$]+)/g, ($0, $1) => {\n    if ($1 in params) {\n      return params[$1]\n    } else {\n      return ''\n    }\n  })\n\nconst convFormData = (data: any) => {\n  const fd = new FormData()\n  for (const i in data) {\n    if (Array.isArray(data[i])) {\n      const item = []\n      data[i].forEach((j: any, idx: number) => {\n        fd.append(`${i}[${idx}]`, j)\n      })\n    } else {\n      fd.append(i, data[i])\n    }\n  }\n  return fd\n}\n\nexport const useApi = (options?: APIOptions) => {\n  if (globalApi) {\n    return globalApi._m as any\n  }\n\n  const currentInstance = getCurrentInstance()\n\n  const api = currentInstance && inject(apiSymbol)\n  if (!api) {\n    throw new Error(\n      'getActiveApi was called with no active api. Did you forget to install?\\n' +\n      '\\tconst api = createApi()\\n' +\n      '\\tapp.use(api)\\n' +\n      `This will fail in production.`,\n    )\n  }\n\n  return api._m as any\n}\n\nconst globalApi: IUseApi = {}\nexport const createApi = (apis: unknown, options?: APIOptions): IUseApi => {\n  type a = typeof apis\n  const pareKey: any = {}\n  for (const i of apis as Array<APIItem>) {\n    pareKey[i[0]] = 1\n  }\n  const apiMap: Record<keyof typeof pareKey, APICall> = {}\n\n  for (const i of apis as Array<APIItem>) {\n    // pareKey[apis[0]] =\n    apiMap[i[0]] = createRequest(i, options)\n  }\n\n  if (options?.inScope) {\n    const scope = effectScope(true)\n    const api: IUseApi = {\n      install(app: App) {\n        app.provide(apiSymbol, api)\n        app.config.globalProperties.$api = api\n      },\n      _e: scope,\n      _m: apiMap,\n    }\n    return api\n  } else {\n    globalApi._m = apiMap\n    return globalApi\n  }\n}\n\nfunction createRequest(api: APIItem, defaultOptions?: APIOptions): APICall {\n  return (...args: Array<any>) => {\n    const [name, url, options = {}] = api\n    const reqUrl = typeof url === 'function' ? url(...args) : url\n\n    let argsObj: Record<string, any> = {\n      $R: Math.random(),\n      $T: Date.now(),\n    }\n\n    if (typeof args[0] == 'object') {\n      argsObj = { ...argsObj, ...args[0] }\n    }\n\n    args.forEach((key, idx) => {\n      argsObj['$' + (idx + 1)] = key\n    })\n\n    const pairs = reqUrl.split(/\\s/)\n    const contentType = options.contentType || 'json'\n\n    const params: any = {\n      url: (defaultOptions?.baseURL || '') + urlReplace(pairs.slice(1).join(' '), argsObj),\n      method: <RequestMethod>pairs[0] || 'GET',\n      data: typeof args[0] == 'object' ? args[0] : {},\n      headers: options.header || {},\n    }\n\n    if (options.params) {\n      return params\n    }\n    // factory\n    if (typeof params.data?.customRequest == 'function') {\n      const ret: any = params.data.customRequest(params, options)\n      delete params.data.customRequest\n      if (ret) return ret\n      // return (params, options) => { }\n    }\n\n    if (contentType == 'formdata') {\n      params.headers['content-type'] = 'multipart/form-data'\n      if (params.data) {\n        params.data = convFormData(params.data)\n      }\n    } else if (contentType == 'stream') {\n      params.headers['content-type'] = 'application/octet-stream'\n      params.data = params.data.stream\n    } else {\n      params.headers['content-type'] = 'application/json'\n    }\n\n    defaultOptions?.onReq?.(params, options)\n\n    return service.request<ReqResponse>(params as AxiosRequestConfig)\n  }\n}\n"
  },
  {
    "path": "packages/sharelist-manage/src/hooks/useClipboard.ts",
    "content": "import { watch, onUnmounted, ref, Ref } from 'vue'\n\ntype Type = 'file' | 'string'\n\nconst watchOnce = (v: any, cb: any) => {\n  const handler = watch(\n    v,\n    (nv) => {\n      if (nv !== undefined) {\n        handler()\n        cb(nv)\n      }\n    },\n    { immediate: true },\n  )\n\n  return handler\n}\nexport const useClipboard = (cb: (...rest: Array<any>) => any, type: Type = 'string') => {\n  const node: Ref<Element | any> = ref()\n\n  const handler = (event: any) => {\n    const items = (event.clipboardData || event.originalEvent.clipboardData).items\n    const data: Array<any> = []\n    for (let i = 0; i < items.length; i++) {\n      if (items[i].kind === type) data.push(items[i])\n    }\n    if (type == 'file') {\n      cb(data.map((i) => i.getAsFile()))\n    } else if (type == 'string') {\n      cb(data.map((i) => i.getAsString()))\n    } else {\n      cb(data)\n    }\n  }\n\n  const cancel = watchOnce(node, () => {\n    node.value.addEventListener('paste', handler)\n  })\n\n  onUnmounted(() => {\n    cancel?.()\n    node.value?.removeEventListener('paste', handler)\n  })\n\n  return { node }\n}\n\nexport default (el: Element, type: Type, cb: (...rest: Array<any>) => any) => {\n  const handler = (event: any) => {\n    const items = (event.clipboardData || event.originalEvent.clipboardData).items\n    console.log(items)\n    const data: Array<any> = []\n    for (let i = 0; i < items.length; i++) {\n      if (items[i].kind === type) data.push(items[i])\n    }\n    console.log(data, items.length, type, '<<')\n    if (type == 'file') {\n      cb(data.map((i) => i.getAsFile()))\n    } else if (type == 'string') {\n      cb(data.map((i) => i.getAsString()))\n    } else {\n      cb(data)\n    }\n  }\n  el.addEventListener('paste', handler)\n\n  return () => el.removeEventListener('paste', handler)\n}\n"
  },
  {
    "path": "packages/sharelist-manage/src/hooks/useConfirm.ts",
    "content": "import { Modal, message } from 'ant-design-vue'\nimport { createVNode, VNode } from 'vue'\nimport { ExclamationCircleOutlined } from '@ant-design/icons-vue'\n\nexport default (fn: { (): any }, title = '确认', content = '') =>\n  (): { destroy(): void; update(p: any): void } => {\n    const modal = Modal.confirm({\n      title,\n      content,\n      icon: createVNode(ExclamationCircleOutlined),\n      onOk() {\n        fn()\n      },\n    })\n    return modal\n  }\n\ntype ConfirmOption = {\n  title: string\n  content: string\n  success?: string\n  icon?: VNode\n  onSuccess?: () => void\n}\nexport const useApiConfirm = (\n  fn: { (): Promise<any> },\n  { title, content, success, icon, onSuccess }: ConfirmOption = { title: '确认', content: '' },\n): void => {\n  Modal.confirm({\n    title,\n    content,\n    icon: icon || createVNode(ExclamationCircleOutlined),\n    onOk() {\n      return fn()\n        .then((res) => {\n          if (res.error) {\n            message.error(res.error.message)\n          } else {\n            message.success(success || '操作完成')\n            onSuccess?.()\n          }\n          return Promise.resolve()\n        })\n        .catch((e) => {\n          message.error(e.message)\n        })\n    },\n  })\n}\n"
  },
  {
    "path": "packages/sharelist-manage/src/hooks/useDirective.ts",
    "content": "import { VNode, withDirectives } from 'vue'\n\nconst focus = {\n  mounted: (el: any, { arg }: any) => {\n    el.focus()\n    if (arg == 'select') {\n      el.select()\n    }\n  },\n}\n\nconst select = {\n  mounted: (el: any) => {\n    el.select()\n  },\n}\n\nexport const useFocus = (node: VNode, select = false): VNode =>\n  withDirectives(node, [[focus, true, select ? 'select' : '']])\n"
  },
  {
    "path": "packages/sharelist-manage/src/hooks/useDom.ts",
    "content": "import { ref, Ref } from 'vue'\n\nexport const isDocumentVisible = (): boolean => {\n  return document?.visibilityState !== 'hidden'\n}\n\nconst docListeners: Array<() => any> = []\n\nexport const documentVisibility = ref(!document?.hidden)\nwindow.addEventListener(\n  'visibilitychange',\n  () => {\n    documentVisibility.value = !document.hidden\n\n    if (documentVisibility.value) {\n      for (let i = 0; i < docListeners.length; i++) {\n        const listener = docListeners[i]\n        listener()\n      }\n    }\n  },\n  false,\n)\n\nexport const whenDocumentVisible = (listener: () => any): (() => void) => {\n  docListeners.push(listener)\n  return function unsubscribe() {\n    const index = docListeners.indexOf(listener)\n    docListeners.splice(index, 1)\n  }\n}\n\nexport const useDocumentVisibility = (): Ref<boolean> => {\n  return documentVisibility\n}\n\nexport const useResize = () => {\n\n}"
  },
  {
    "path": "packages/sharelist-manage/src/hooks/useHooks.ts",
    "content": "import { ref, reactive, Ref, UnwrapRef, onMounted, onUnmounted, getCurrentInstance } from 'vue'\n\nexport function safeOnMounted(hook: () => any): void {\n  const instance = getCurrentInstance()\n  if (instance?.isMounted || (instance as any)?._isMounted) {\n    hook()\n  } else {\n    onMounted(hook)\n  }\n}\n\ntype ToggleValue = number | string | boolean | undefined\n\nexport const useToggle = (defaultValue: ToggleValue = false, reverseValue: ToggleValue = undefined): any => {\n  if (reverseValue === undefined) {\n    reverseValue = !Boolean(defaultValue)\n  }\n\n  const state: Ref<ToggleValue> = ref(defaultValue)\n\n  const toggle = () => {\n    state.value = state.value === defaultValue ? reverseValue : defaultValue\n  }\n\n  const setLeft = () => (state.value = defaultValue)\n\n  const setRight = () => (state.value = reverseValue)\n\n  return { state, toggle, setLeft, setRight }\n}\n\nexport const useBoolean = (defaultValue = false): any => {\n  const state: Ref<boolean> = ref(defaultValue)\n\n  const toggle = () => {\n    state.value = state.value === true ? false : true\n  }\n\n  const setTrue = () => (state.value = true)\n\n  const setFalse = () => (state.value = false)\n\n  return [state, { toggle, setTrue, setFalse }]\n}\n\nexport const useObject = (defaultValue: Record<string, any>): [any, (k?: Array<string>) => void] => {\n  const state = reactive(defaultValue)\n\n  const clear = (excludes: Array<string> = []): void => {\n    Object.keys(state as Record<string, any>)\n      .filter((i) => !excludes.includes(i))\n      .forEach((key: string) => Reflect.deleteProperty(state as Record<string, any>, key))\n  }\n\n  return [state, clear]\n}\n\nexport const useState = <T extends Record<string, any>>(initialState: T = {} as T): [T, (state: T) => T, () => T] => {\n  const state = reactive(initialState)\n  const setState = (val: T, clear = false) => {\n    Object.keys(val).forEach((key) => {\n      Reflect.set(state, key, val[key])\n    })\n    return state as T\n  }\n\n  const clearState = () => {\n    Object.keys(state).forEach((key) => Reflect.deleteProperty(state, key))\n    return state as T\n  }\n\n  return [state as T, setState, clearState]\n}\n\nconst singleHook = new WeakMap()\n// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types\nexport const useSingle = (hook: any, args: Array<any>): any => {\n  if (singleHook.has(hook)) {\n    return singleHook.get(hook)\n  }\n  const res = hook.apply(hook, args)\n  singleHook.set(hook, res)\n  return res\n}\n\n// const useBoot = (cb: any) => {\n//   useBoot.ready = true\n\n//   if (useBoot.ready) {\n//     cb()\n//   }\n// }\n\n// const useWindowEvent = (event: string) => {\n//   const handler = new Set()\n//   const eventMap = new Map()\n//   window.addEventListener(event, (event) => {\n//     console.log('location: ' + document.location + ', state: ' + JSON.stringify(event.state))\n//   })\n\n//   const onMessage = (cb: any) => {\n//     handler.add(cb)\n//   }\n\n//   const initListener = () => {\n//     if (document) {\n//       document.addEventListener('message', (e) => {\n//         console.log(e)\n//       })\n//     }\n//   }\n\n//   initListener()\n\n//   return {\n//     onMessage,\n//   }\n// }\n\ntype useTitleOptions = {\n  restoreOnUnmount: boolean\n}\nexport const useTitle = (title: string, options?: useTitleOptions): void => {\n  const lastTitle = ref('')\n\n  const run = () => {\n    lastTitle.value = document.title\n    document.title = title\n  }\n  safeOnMounted(() => {\n    run()\n  })\n\n  if (options?.restoreOnUnmount === true) {\n    onUnmounted(() => {\n      document.title = lastTitle.value\n    })\n  }\n\n  run()\n}\n\nexport const delayToggle = (setLeft: () => any, setRight: () => any, timeout = 200) => {\n  let handler: number | null\n  return {\n    true() {\n      if (timeout === 0) {\n        setLeft()\n      } else {\n        handler = setTimeout(() => {\n          setLeft()\n        }, timeout)\n      }\n    },\n    false() {\n      if (handler) {\n        clearTimeout(handler)\n        handler = null\n      }\n      setRight()\n    },\n  }\n}\n\nexport const cancelablePromise = <T>(origin: Promise<T>): (() => void) => {\n  let handler: (args: any) => void\n  Promise.race([\n    origin,\n    new Promise((resolve, reject) => {\n      handler = resolve\n    }),\n  ])\n  return () => handler?.({})\n}\n"
  },
  {
    "path": "packages/sharelist-manage/src/hooks/useLoad.ts",
    "content": "import { ref, Ref } from 'vue'\n\nexport const useLoad = (cb: (() => Promise<any>) | Promise<any>): Ref<boolean> => {\n  const loading = ref(false)\n  Promise.resolve(cb).then(() => {\n    loading.value = true\n  })\n  return loading\n}\n"
  },
  {
    "path": "packages/sharelist-manage/src/hooks/useLocalStorage.ts",
    "content": "import { Ref, ref, watch } from 'vue'\n\nexport type LocalStateKey = string\n\nexport function useLocalStorageState<T = any>(key: LocalStateKey, defaultValue?: T | (() => T)): Ref<T> {\n  const raw = localStorage.getItem(key)\n  if (raw) {\n    try {\n      defaultValue = JSON.parse(raw)\n    } catch {\n      //\n    }\n  }\n  if (typeof defaultValue === 'function') {\n    defaultValue = (defaultValue as () => T)()\n  }\n  const state = ref(defaultValue) as Ref<T>\n\n  const setState = () => {\n    localStorage.setItem(key, JSON.stringify(state.value))\n  }\n\n  watch(\n    state,\n    (nv) => {\n      setState()\n    },\n    { deep: true, immediate: false },\n  )\n\n  return state\n}\n"
  },
  {
    "path": "packages/sharelist-manage/src/hooks/useRequest.ts",
    "content": "import { reactive, ref, Ref, toRefs, watch, onUnmounted } from 'vue'\n\nexport const useState = <T extends Record<string, any>>(initialState: T = {} as T): [T, (state: T) => T, () => T] => {\n  const state = reactive(initialState)\n  const setState = (val: T, clear = false) => {\n    Object.keys(val).forEach((key) => {\n      Reflect.set(state, key, val[key])\n    })\n    return state as T\n  }\n\n  const clearState = () => {\n    Object.keys(state).forEach((key) => Reflect.deleteProperty(state, key))\n    return state as T\n  }\n\n  return [state as T, setState, clearState]\n}\n\nconst docListeners: Array<() => any> = []\n\nexport const documentVisibility = ref(!document?.hidden)\nwindow.addEventListener(\n  'visibilitychange',\n  () => {\n    documentVisibility.value = !document.hidden\n\n    if (documentVisibility.value) {\n      for (let i = 0; i < docListeners.length; i++) {\n        const listener = docListeners[i]\n        listener()\n      }\n    }\n  },\n  false,\n)\n\nexport const whenDocumentVisible = (listener: () => any): (() => void) => {\n  docListeners.push(listener)\n  return function unsubscribe() {\n    const index = docListeners.indexOf(listener)\n    docListeners.splice(index, 1)\n  }\n}\n\ntype Service<D, P extends any[]> = (...args: P) => Promise<D>\n\ninterface RequestOptions<T, P> {\n  immediate?: boolean\n  defaultParams?: P\n  mutate?: (data?: T | ((oldData?: T) => T | undefined), disableRequest?: boolean) => void\n  onBefore?: (param: P) => void\n  onSuccess?: (data: T, param: P) => void\n  onError?: (e: Error, param: P) => void\n  onFinally?: (param: P, data?: T, e?: Error) => void\n  loadingDelay?: number\n  cacheKey?: string\n  cacheTime?: number\n  refreshDeps?: Array<any>\n  ready?: Ref<boolean>\n  pollingInterval?: number\n  pollingWhenHidden?: boolean\n}\n\ntype RequestState<D, P extends Array<any>> = {\n  loading: boolean\n  params?: P\n  data?: D\n  error?: Error\n}\n\ninterface RequestActions<D, P extends Array<any>> {\n  cancel: () => void\n  refresh: () => void\n  refreshAsync: () => Promise<D>\n  run: (...param: P) => void\n  runAsync: (...param: P) => Promise<D>\n  mutate: (data: D) => void\n}\n\ninterface RequestCore<D, P extends any[]> extends RequestActions<D, P> {\n  state: RequestState<D, P>\n  use: (plugins: Array<Partial<PluginResult<D, P>>>) => void\n}\n\ntype RequestResult<D, P extends any[]> = RequestActions<D, P> &\n  {\n    [prop in keyof RequestState<D, P>]: Ref<RequestState<D, P>[prop]>\n  }\n\ninterface PluginResult<D = any, P extends any[] = any> {\n  before: (params: P) =>\n    | ({\n        stopNow?: boolean\n        returnNow?: boolean\n      } & Partial<RequestState<D, P>>)\n    | void\n\n  request: (service: Service<D, P>, params: P) => { service?: Promise<D> }\n\n  success: (data: D, params: P) => void\n  error: (e: Error, params: P) => void\n  finally: (params: P, data?: D, e?: Error) => void\n  cancel: () => void\n  mutate: (data: D) => void\n}\n\ninterface Plugin<D, P extends Array<any>> {\n  (requestInstance: RequestCore<D, P>, options: RequestOptions<D, P>): Partial<PluginResult<D, P>>\n}\n\ntype PickMethod<M, C> = {\n  [P in keyof C]: P extends M ? C[P] : never\n}[keyof C]\n\nconst useRequestCore = <D, P extends Array<any>>(service: Service<D, P>, options: RequestOptions<D, P> = {}) => {\n  const [state, setState] = useState<RequestState<D, P>>({\n    loading: false,\n    params: undefined,\n    data: undefined,\n    error: undefined,\n  })\n\n  const plugins: Array<Partial<PluginResult<D, P>>> = []\n\n  //const loading = ref(false)\n  // data: Ref<D | undefined> = ref()\n  // params: Ref<any> = ref(options.defaultParams)\n\n  state.params = options.defaultParams as P\n\n  let canceled = false\n\n  const emit = <T extends keyof PluginResult>(\n    type: T,\n    ...rest: Parameters<PickMethod<T, PluginResult>>\n  ): ReturnType<PickMethod<T, PluginResult>> => {\n    // @ts-expect-error: Unreachable code error\n    return { ...plugins.map((i) => i[type]?.(...rest)).filter(Boolean) }\n  }\n\n  const runAsync = async (...params: P) => {\n    //params.value = args\n    canceled = false\n    const { returnNow, ...newState } = emit('before', params) || {}\n    setState({\n      loading: true,\n      params,\n      ...newState,\n    })\n    if (returnNow) {\n      return Promise.resolve(state.data)\n    }\n    options.onBefore?.(params)\n\n    try {\n      let { service: servicePromise } = emit('request', service, params)\n      if (!servicePromise) {\n        servicePromise = service(...params)\n      }\n\n      const res = await servicePromise\n\n      if (canceled) {\n        return new Promise(() => {})\n      }\n\n      setState({\n        data: res,\n        error: undefined,\n        loading: false,\n      })\n\n      options.onSuccess?.(res, params)\n      emit('success', res, params)\n      options.onFinally?.(params, res)\n      emit('finally', res, params)\n      return res\n    } catch (error: any) {\n      if (canceled) {\n        return new Promise(() => {})\n      }\n\n      setState({\n        error,\n        loading: false,\n      })\n\n      options.onError?.(error, params)\n      emit('error', error, params)\n\n      options.onFinally?.(params, undefined, error)\n      emit('finally', params, undefined, error)\n\n      throw error\n    }\n  }\n\n  const run = (...args: P) => {\n    runAsync(...args).catch((e) => {\n      if (!options.onError) {\n        console.error(e)\n      }\n    })\n  }\n\n  const mutate = (data: D) => {\n    emit('mutate', data)\n    state.data = data\n  }\n\n  const cancel = () => {\n    canceled = true\n    state.loading = false\n    emit('cancel')\n  }\n\n  const use = (usePlugins: Array<Partial<PluginResult<D, P>>>) => {\n    plugins.push(...usePlugins)\n  }\n\n  const refresh = () => {\n    // @ts-expect-error: Unreachable code error\n    run(...(state.params || []))\n  }\n\n  const refreshAsync = () => {\n    // @ts-expect-error: Unreachable code error\n    return runAsync(...(state.params || []))\n  }\n\n  onUnmounted(cancel)\n\n  return {\n    state,\n    use,\n    run,\n    mutate,\n    cancel,\n    refresh,\n    runAsync,\n    refreshAsync,\n  }\n}\n\nexport const useRequest = <D, P extends Array<any> = Array<any>>(\n  service: Service<D, P>,\n  options: RequestOptions<D, P> = {},\n  plugins?: Array<Plugin<D, P>>,\n): RequestResult<D, P> => {\n  const pluginsFactory = [...(plugins || []), useDelay, usePolling, useAuto] as Array<Plugin<D, P>>\n\n  const requestInstance = useRequestCore<D, P>(service, options)\n\n  requestInstance.use(pluginsFactory.map((plugin) => plugin(requestInstance, options)))\n\n  const { loading, data, error, params } = toRefs(requestInstance.state)\n\n  return {\n    loading,\n    error,\n    data: data as Ref<D>,\n    params: params as Ref<P>,\n    run: requestInstance.run,\n    runAsync: requestInstance.runAsync,\n    mutate: requestInstance.mutate,\n    cancel: requestInstance.cancel,\n    refresh: requestInstance.refresh,\n    refreshAsync: requestInstance.refreshAsync,\n  }\n}\n\nconst usePolling: Plugin<any, Array<any>> = (request, { pollingInterval, pollingWhenHidden = true }) => {\n  if (!pollingInterval) return {}\n\n  let timer: number, docVisibleWatcher: () => void\n  const stop = () => {\n    if (timer) {\n      clearTimeout(timer)\n    }\n    docVisibleWatcher?.()\n  }\n\n  return {\n    before() {\n      stop()\n    },\n    finally() {\n      if (!pollingWhenHidden && !documentVisibility.value) {\n        docVisibleWatcher = whenDocumentVisible(request.refresh)\n        return\n      }\n      timer = setTimeout(request.refresh, pollingInterval)\n    },\n    cancel() {\n      stop()\n    },\n  }\n}\n\nconst useDelay: Plugin<any, Array<any>> = (request, { loadingDelay = 0 }) => {\n  if (!loadingDelay) return {}\n\n  let timer: number\n\n  const clear = () => {\n    if (timer) {\n      clearTimeout(timer)\n    }\n  }\n\n  return {\n    before() {\n      clear()\n\n      timer = setTimeout(() => {\n        request.state.loading = true\n      }, loadingDelay)\n\n      return {\n        loading: false,\n      }\n    },\n    finally() {\n      clear()\n    },\n    cancel() {\n      clear()\n    },\n  }\n}\n\nconst useAuto: Plugin<any, Array<any>> = (request, { ready = ref(true), immediate = false, refreshDeps = [] }) => {\n  if (refreshDeps) {\n    watch(refreshDeps, () => {\n      request.refresh()\n    })\n  }\n\n  if (immediate) {\n    if (ready.value) request.refresh()\n    else {\n      watch(ready, (nv) => {\n        if (nv) request.refresh()\n      })\n    }\n  }\n\n  return {\n    before() {\n      if (!ready || !ready.value) {\n        return {\n          stopNow: true,\n        }\n      }\n    },\n  }\n}\n"
  },
  {
    "path": "packages/sharelist-manage/src/hooks/useScroll.ts",
    "content": "import { ref, Ref, reactive, UnwrapRef } from 'vue'\n\ninterface RequestOptions<T, P> {\n  immediate?: boolean\n  isNoMore?: (data?: T) => boolean\n  onSuccess?: (data: T) => void\n  onError?: (e: Error) => void\n  target?: Ref<Element>\n  threshold?: number\n}\n\ninterface actions<T> {\n  setNode(el: Element): void\n  scrollTo(pos: number): void\n  cancel: () => void\n  checkScroll: () => void\n  isScroll: Ref<boolean>\n}\n\ntype useScrollOption = {\n  list: any[]\n  [key: string]: any\n}\nexport const useScroll = <T extends useScrollOption, P extends Array<any>>(\n  service: (args?: T) => Promise<T>,\n  options: RequestOptions<T, P> = {},\n): actions<T> => {\n  let el: Element\n  const isScroll = ref(false)\n  const setNode = (v: Element) => {\n    if (el) {\n      cancel()\n    }\n    el = v\n    el.addEventListener('scroll', onDomScroll)\n  }\n\n  const cancel = () => {\n    el?.removeEventListener('scroll', onDomScroll)\n  }\n\n  const onDomScroll = throttle(() => {\n    const { clientHeight, scrollTop, scrollHeight } = el\n    isScroll.value = scrollTop > 0\n    if (scrollHeight - scrollTop - clientHeight < (options?.threshold || 100)) {\n      service()\n    }\n  }, 200)\n\n  const scrollTo = (v: number) => {\n    if (el) {\n      el.scrollTo({ top: v })\n    }\n  }\n\n  const checkScroll = () => {\n    const { clientHeight, scrollTop, scrollHeight } = el\n    if (clientHeight == scrollHeight) service()\n  }\n  return {\n    setNode,\n    cancel,\n    scrollTo,\n    isScroll,\n    checkScroll,\n  }\n}\n\nconst throttle = function (fn: () => any, delay = 0) {\n  let now: number,\n    last = 0,\n    timer: number | null,\n    context: any,\n    args: Array<any> | null\n\n  const later = function () {\n    last = now\n    fn.apply(context, <[]>args)\n    timer = null\n    context = args = null\n  }\n\n  const listen = function (this: any, ...rest: Array<any>) {\n    args = rest\n\n    now = Date.now()\n\n    //剩余时间\n    const remaining = delay - (now - last)\n\n    if (remaining <= 0 || remaining > delay) {\n      if (timer) {\n        clearTimeout(timer)\n        timer = null\n      }\n\n      last = now\n\n      fn.apply(this, <[]>rest)\n      if (!timer) context = args = null\n    } else if (!timer) {\n      timer = setTimeout(later, remaining)\n    }\n  }\n\n  return listen\n}\n"
  },
  {
    "path": "packages/sharelist-manage/src/hooks/useSetting.ts",
    "content": "import { ref, Ref, reactive } from 'vue'\nimport { useApi, ReqResponse } from '@/hooks/useApi'\nimport { message } from 'ant-design-vue'\nimport { useBoolean } from './useHooks'\nimport { saveFile } from '../utils/format'\nimport useStore from '@/store/index'\n\ntype IUseSetting = {\n  (): IUseSettingResult\n  instance?: any\n}\nexport interface IUseSettingResult {\n  configFields: Ref<Array<fieldGroup>>\n  signout(): any\n  reload(): any\n  loginState: Ref<number>\n  isLoading: Ref<boolean>\n  getValue(key: string): any\n\n  config: any\n  setConfig(data: ISetting, msg?: string): Promise<any>\n  getConfig(token: string): void\n  exportConfig(): void\n  clearCache(): void\n\n  getPlugin(id: string): Promise<string>\n  setPlugin(id: string, data: string): Promise<any>\n  removePlugin(id: string): Promise<any>\n  upgradePlugin(id: string): Promise<any>\n  reloadConfig(): void\n}\nexport type ConfigFieldItem = {\n  code: string\n  label: string\n  help?: string\n  secret?: boolean\n  type: 'string' | 'number' | 'boolean' | 'option' | 'array' | 'textarea'\n  handler?: (...rest: any) => void\n  validator?: (...rest: any) => boolean\n}\ntype fieldGroup = {\n  title: string\n  children: Array<ConfigFieldItem>\n}\n\nconst fields: Array<fieldGroup> = [\n  {\n    title: '常规',\n    children: [\n      { code: 'title', label: '网站标题', type: 'string' },\n      {\n        code: 'manage_path',\n        label: '后台地址',\n        type: 'string',\n        help: '地址必须以 / 开头',\n        validator: (val) => /^\\//.test(val),\n        handler: (nv: string, ov: string) => (location.href = location.href.replace(ov, nv)),\n      },\n      { code: 'token', label: '后台密码', type: 'string', secret: true },\n      { code: 'proxy_enable', label: '全局代理', type: 'boolean' },\n      { code: 'index_enable', label: '目录浏览', type: 'boolean' },\n      {\n        code: 'proxy_override_content_type',\n        label: '代理时重写Content-Type',\n        help: '此项是为了兼容某些挂载源，因返回内容的content type异常，导致无法在线播放的问题。',\n        type: 'boolean',\n      },\n      {\n        code: 'anonymous_download_enable',\n        label: '允许下载',\n        type: 'boolean',\n        help: '禁用此项后，预览也将不可用。',\n      },\n      {\n        code: 'expand_single_disk',\n        label: '展开单一挂载盘',\n        help: '只有一个挂载盘时，直接展示改挂载盘内容。',\n        type: 'boolean',\n      },\n      {\n        code: 'per_page',\n        label: '列表分页大小',\n        help: '设置分页将自动禁用缓存。某些挂载源可能不支持自定义分页大小。0 表示不分页。',\n        type: 'number',\n      },\n    ],\n  },\n  {\n    title: '外观',\n    children: [\n      { code: 'theme', label: '主题', type: 'option' },\n      { code: 'script', label: '自定义脚本', type: 'textarea' },\n      { code: 'style', label: '自定义样式', type: 'textarea' },\n    ],\n  },\n  {\n    title: '传输设置',\n    children: [\n      { code: 'proxy_url', label: '代理地址', help: '当前支持 HTTP/HTTPS 代理。', type: 'string' },\n      { code: 'plugin_source', label: '插件源', type: 'option' },\n    ],\n  },\n  {\n    title: 'WebDAV',\n    children: [\n      { code: 'webdav_enable', label: '启用 WebDAV', type: 'boolean' },\n      { code: 'webdav_path', label: 'WebDAV 路径', type: 'string' },\n      { code: 'webdav_proxy', label: 'WebDAV 代理', type: 'boolean' },\n      { code: 'webdav_user', label: 'WebDAV 用户名', type: 'string' },\n      { code: 'webdav_pass', label: 'WebDAV 密码', type: 'string' },\n    ],\n  },\n]\n\nexport const useSetting: IUseSetting = (): IUseSettingResult => {\n  if (useSetting.instance) {\n    return useSetting.instance\n  }\n  const request = useApi()\n  const store = useStore()\n  const config: ISetting = reactive({})\n  const [isLoading, { setFalse: hideLoading }] = useBoolean(true)\n\n  const loginState = ref(0)\n\n  const configFields: Ref<Array<fieldGroup>> = ref(fields)\n\n  const getConfig = (val: string) => {\n    store.saveToken(val)\n    request.setting().then((resp: ReqResponse) => {\n      if (resp.error) {\n        if (resp.error.code == 401) {\n          store.saveToken('')\n          loginState.value = 2\n          if (resp.error.message) {\n            message.error(resp.error.message)\n          }\n        }\n      } else {\n        loginState.value = 1\n        updateSetting(resp.data as ISetting)\n      }\n\n      hideLoading()\n    })\n  }\n\n  const setConfig = (data: ISetting, msg = '已保存') => {\n    // console.log(data)\n    return request.saveSetting(data).then((resp: ReqResponse) => {\n      if (resp.error) {\n        message.error(resp.error.message || 'error')\n      } else {\n        updateSetting(resp.data as ISetting)\n        // return Promise.resolve(true)\n        message.success(msg)\n      }\n    })\n  }\n\n  const reloadConfig = () => {\n    getConfig(store.accessToken)\n  }\n\n  const updateSetting = (data: ISetting) => {\n    for (const i in data) {\n      config[i] = data[i]\n    }\n    configFields.value = [...fields, ...config.pluginConfig.map((i: any) => ({ title: i.name, children: i.config }))]\n  }\n\n  const getValue = (code: string) => {\n    return config[code]\n  }\n\n  const signout = () => {\n    store.removeToken()\n    loginState.value = 2\n    Object.keys(config).forEach((key) => Reflect.deleteProperty(config, key))\n  }\n\n  const reload = () => {\n    request.reload().then((resp: any) => {\n      // hidden()\n      if (resp.error) {\n        message.error(resp.error?.message)\n      } else {\n        message.success('操作成功')\n      }\n    })\n  }\n\n  const clearCache = () => {\n    // const hidden = message.loading('正在清除缓存', 0)\n    request.clearCache().then((resp: any) => {\n      // hidden()\n\n      if (resp.error) {\n        message.error(resp.error?.message)\n      } else {\n        message.success('操作成功')\n      }\n    })\n  }\n\n  const exportConfig = () => {\n    request.exportSetting().then((resp: any) => {\n      // hidden()\n      if (resp.error) {\n        message.error(resp.error?.message)\n      } else {\n        saveFile(JSON.stringify(resp.data), 'config.json')\n      }\n    })\n  }\n\n  const getPlugin = async (id: string): Promise<string> => {\n    return request.plugin(id).then((resp: any) => {\n      if (resp.error) {\n        message.error(resp.error?.message)\n      } else {\n        return resp.data\n      }\n    })\n  }\n\n  const setPlugin = async (id: string, data: string): Promise<any> => {\n    const res = await request.savePlugin({ id, data })\n    if (res.error) {\n      message.error(res.error?.message)\n      throw new Error(res.error?.message)\n    } else {\n      message.success('保存成功')\n      reloadConfig()\n    }\n  }\n\n  const removePlugin = async (id: string): Promise<any> => {\n    const res = await request.removePlugin(id)\n    if (res.error) {\n      message.error(res.error?.message)\n      //throw new Error(res.error?.message)\n    } else {\n      message.success('删除成功')\n      reloadConfig()\n    }\n  }\n\n  const upgradePlugin = async (id: string): Promise<any> => {\n    const res = await request.upgradePlugin(id)\n    if (res.error) {\n      message.error(res.error?.message)\n      //throw new Error(res.error?.message)\n    } else {\n      message.success('更新成功')\n      reloadConfig()\n    }\n  }\n\n  if (!config.token && store.accessToken) {\n    getConfig(store.accessToken)\n  } else {\n    loginState.value = 2\n    hideLoading()\n  }\n\n  return (useSetting.instance = {\n    signout,\n    reload,\n    loginState,\n    isLoading,\n    getValue,\n\n    configFields,\n    config,\n    setConfig,\n    getConfig,\n    exportConfig,\n    clearCache,\n\n    getPlugin,\n    setPlugin,\n    removePlugin,\n    upgradePlugin,\n    reloadConfig,\n  })\n}\n\nexport const useConfig: IUseSetting = (): any => {\n  const config: Record<string, any> = reactive({})\n  const request = useApi()\n\n  request.config().then((resp: ReqResponse) => {\n    if (!resp.error) {\n      for (const i in resp.data) {\n        config[i] = resp.data[i]\n      }\n    }\n  })\n\n  return { config }\n}\n"
  },
  {
    "path": "packages/sharelist-manage/src/hooks/useStore.ts",
    "content": "import { provide, inject, Ref, ref } from 'vue'\n\ntype InjectType = 'root' | 'optional'\n\nexport interface FunctionalStore<T> {\n  (): T\n  key?: symbol\n  root?: T\n}\n\nexport const regStore = <T>(store: FunctionalStore<T>): T => {\n  if (!store.key) {\n    store.key = Symbol('functional store')\n  }\n  const depends = store()\n  provide(store.key, depends)\n  return depends\n}\n\nexport const useStore = <T>(store: FunctionalStore<T>, type?: InjectType): any => {\n  const key = store.key\n  const root = store.root\n\n  switch (type) {\n    case 'optional':\n      return inject<T>(key) || store.root || null\n    case 'root':\n      if (!store.root) store.root = store()\n      return store.root\n    default:\n      if (inject(key)) {\n        return inject<T>(key)\n      }\n      if (root) return store.root\n      throw new Error(`状态钩子函数${store.name}未在上层组件通过调用useProvider提供`)\n  }\n}\n\nexport const useAsync = (cb: (() => Promise<any>) | Promise<any>): Ref => {\n  const val = ref()\n  Promise.resolve(typeof cb === 'function' ? cb() : cb).then((resp) => {\n    val.value = resp\n  })\n  return val\n}\n\nconst jsbridge = {\n  call: (name: string, param?: any, callback?: (value?: unknown) => void) => name,\n  getGrayScaleValue: (v: any) => v,\n}\n\ntype BridgeValue = {\n  returnValue: string\n}\n\ntype BridgeFunctions = 'getGrayScaleValue'\n\nexport const useBridgeValue = <T, P>(\n  fn: BridgeFunctions,\n  params: P,\n): { value: Ref<T | undefined>; ready: Ref<boolean> } => {\n  const value: Ref<T | undefined> = ref()\n  const ready = ref(false)\n  Promise.resolve(jsbridge[fn](params)).then((resp: T) => {\n    value.value = resp\n    ready.value = true\n  })\n  return { value, ready }\n}\n\ntype GrayParams = 'code1' | 'code2'\n\ntype GrapValue = {\n  returnValue: string\n}\nconst { ready, value } = useBridgeValue('getGrayScaleValue', 'code')\n\ntype ICdpSpaceInfo = {\n  returnValue: string\n}\nfunction getCdpSpaceInfo<T>(spaceCode: string): Promise<ICdpSpaceInfo> {\n  return new Promise((resolve) => {\n    jsbridge.call('getCdpSpaceInfo', { spaceCode }, (resp: unknown) => {\n      resolve(resp as ICdpSpaceInfo)\n    })\n  })\n}\n"
  },
  {
    "path": "packages/sharelist-manage/src/hooks/useUrlState.ts",
    "content": "import { useRouter, useRoute } from 'vue-router'\nimport { reactive, watch } from 'vue'\nimport { useState } from './useHooks'\ntype initial = {\n  params: Record<string, any>\n  query: Record<string, any>\n}\n\nexport default ({ params: initialParams, query: initialQuery }: initial = { params: {}, query: {} }): any => {\n  const router = useRouter()\n  const route = useRoute()\n\n  const [params, updateParams] = useState({\n    ...initialParams,\n    ...route.params,\n  })\n\n  const [query, updateQuery] = useState({\n    ...initialQuery,\n    ...route.query,\n  })\n\n  const setQuery = (data: Record<string, any>) => {\n    router.push({\n      query: {\n        ...route.query,\n        ...updateQuery(data),\n      },\n    })\n  }\n\n  const setParams = (data: Record<string, any>) => {\n    router.push({\n      ...route.params,\n      ...updateParams(data),\n    })\n  }\n\n  const setPath = (path: string) => {\n    router.push(path)\n  }\n\n  watch(() => route.params, updateParams)\n  watch(() => route.query, updateQuery)\n\n  watch(query, (nv) => {\n    console.log('>>>', nv)\n  })\n  return { params, query, setQuery, setParams, setPath }\n}\n"
  },
  {
    "path": "packages/sharelist-manage/src/hooks/useWorker.ts",
    "content": "const fnCache = new WeakMap()\n\ntype Fn = (...args: any[]) => any\n\nconst WORKER_SCRIPT = () => {\n  const methodsMap: Record<string, Fn> = {}\n\n  function invoke(name: string, params: unknown[], id: string) {\n    try {\n      if (!methodsMap[name]) {\n        throw new Error('function ' + name + ' is not registered.')\n      }\n      const result = methodsMap[name].apply(null, params)\n      Promise.resolve(result)\n        .then(function onresolve(res) {\n          self.postMessage(\n            JSON.stringify({\n              data: res,\n              name: name,\n              id: id,\n            }),\n          )\n        })\n        .catch(function onerror(error) {\n          throw error\n        })\n    } catch (error) {\n      throw error\n    }\n  }\n\n  self.onmessage = function (e) {\n    const data = JSON.parse(e.data)\n    const type = data.type\n    const name = data.name\n\n    switch (type) {\n      case 'add':\n        methodsMap[name] = eval(data.code)\n        break\n\n      case 'remove':\n        if (methodsMap[name]) {\n          delete methodsMap[name]\n        }\n        break\n\n      case 'clear':\n        methodsMap = {}\n        break\n\n      case 'invoke':\n        var params = data.params\n        var id = data.id\n        invoke(name, params, id)\n        break\n    }\n  }\n}\n\nclass WorkerFactory {\n  worker: Worker\n\n  constructor() {\n    const url = URL.createObjectURL(new Blob([`(${WORKER_SCRIPT.toString()})()`]))\n    this.worker = new Worker(url)\n  }\n}\nexport const useWorker = (fn: Fn) => {\n  if (useWorker.instance) {\n    return useWorker.instance\n  }\n\n  const name = fn.name\n  const url = URL.createObjectURL(new Blob([WORKER_SCRIPT.toString()]))\n\n  if (!fnCache.has(fn)) {\n    const url = URL.createObjectURL(new Blob([__WORKER_SCRIPT__]))\n    fnCache.set(fn, {\n      name,\n    })\n  }\n}\n"
  },
  {
    "path": "packages/sharelist-manage/src/hooks/utils.ts",
    "content": "import { getCurrentInstance, isRef, onMounted as vueOnMounted, onUnmounted as vueOnUnmounted, Ref } from 'vue'\n\nexport const onMounted = (cb: () => any): void => {\n  const instance = getCurrentInstance()\n  if (instance) {\n    if (instance?.isMounted) {\n      cb()\n    } else {\n      vueOnMounted(cb)\n    }\n  }\n}\n\nexport function onUnmounted(cb: () => any): void {\n  if (getCurrentInstance()) {\n    vueOnUnmounted(cb)\n  }\n}\n"
  },
  {
    "path": "packages/sharelist-manage/src/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n  <meta charset=\"UTF-8\" />\n  <link rel=\"icon\" href=\"/favicon.ico\" />\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n  <meta name=\"referrer\" content=\"never\">\n  <title>sharelist</title>\n</head>\n\n<body>\n  <div id=\"app\"></div>\n  <script type=\"module\" src=\"./main.ts\"></script>\n</body>\n\n</html>"
  },
  {
    "path": "packages/sharelist-manage/src/main.ts",
    "content": "import { createApp, h } from 'vue'\nimport App from './App'\nimport router from './router'\nimport { message, Spin, ConfigProvider } from 'ant-design-vue'\nimport { createPinia } from 'pinia'\nimport piniaPersist from 'pinia-plugin-persist'\nimport apis from '@/config/api'\nimport { createApi } from '@/hooks/useApi'\nimport useStore from '@/store/index'\n\n// import 'ant-design-vue/dist/antd.variable.less'\n\nimport '@/assets/style/index.less'\n\nimport { LoadingOutlined } from '@ant-design/icons-vue'\n\nSpin.setDefaultIndicator({\n  indicator: h(LoadingOutlined, {\n    style: {\n      fontSize: '24px',\n    },\n    spin: true,\n  }),\n})\n\nConfigProvider.config({\n  theme: {\n    primaryColor: 'rgb(100,58,218)',\n  },\n  autoInsertSpaceInButton: false,\n})\n\nconst pinia = createPinia()\npinia.use(piniaPersist)\n\ncreateApi(apis, {\n  onReq(params, options) {\n    if (options.token) {\n      params.headers['Authorization'] = useStore().accessToken\n    }\n  },\n})\n\ncreateApp(App)\n  .use(router)\n  .use(pinia)\n\n  // .use(\n  //   createApi(apis, {\n  //     onReq(params, options) {\n  //       if (options.token) {\n  //         params.headers['Authorization'] = useStore().accessToken\n  //       }\n  //     },\n  //   }),\n  // )\n  .provide('$message', message)\n  .mount('#app')\n"
  },
  {
    "path": "packages/sharelist-manage/src/router/index.ts",
    "content": "import { createRouter, createWebHashHistory, createWebHistory, RouteRecordRaw, onBeforeRouteLeave } from 'vue-router'\n\nconst routes: Array<RouteRecordRaw> = [\n  {\n    path: '/',\n    component: () => import('../views/home'),\n    redirect: '/drive/folder',\n    children: [\n      {\n        path: '/general',\n        name: 'general',\n        component: () => import('../views/general'),\n      },\n      {\n        path: '/drive/folder:path(.*)',\n        name: 'drive',\n        component: () => import('../views/disk'),\n        /*\n        children: [\n          {\n            path: 'folder:path(.*)',\n            name: 'file',\n            component: () => import('../views/disk/files'),\n          },\n        ],\n        */\n      },\n      {\n        path: '/plugin',\n        name: 'plugin',\n        component: () => import('../views/plugin'),\n      },\n    ],\n  },\n]\n\nconst router = createRouter({\n  history: createWebHistory((window as any).MANAGE_BASE),\n  routes,\n})\n\nexport default router\n"
  },
  {
    "path": "packages/sharelist-manage/src/store/index.ts",
    "content": "import { defineStore } from 'pinia'\n\nexport default defineStore('sharelist_manage', {\n  state: () => ({\n    accessToken: '',\n    layout: 'list',\n    theme: 'light',\n    path: '',\n  }),\n\n  actions: {\n    saveToken(token: string) {\n      this.accessToken = token\n    },\n    removeToken() {\n      this.accessToken = ''\n    },\n    setLayout(val: string) {\n      this.layout = val\n    },\n    savePath(input: string) {\n      this.path = input\n    },\n  },\n\n  persist: {\n    enabled: true,\n    strategies: [\n      { storage: localStorage, paths: ['layout'] },\n      { storage: sessionStorage, paths: ['accessToken'] },\n    ],\n  },\n})\n"
  },
  {
    "path": "packages/sharelist-manage/src/types/IDrive.ts",
    "content": "type DrivePath = {\n  protocol: string\n  [key: string]: string | number\n}\n\ndeclare type DriverField = {\n  key: string\n  label: string\n  value?: string | number | boolean\n  options?: Array<any>\n  type?: 'string' | 'hidden' | 'number' | 'boolean' | 'list'\n  help?: string\n  fields?: Array<DriverField>\n  required?: boolean\n}\n\ndeclare type IDrive = {\n  name: string\n  [key: string]: string | number\n}\n\ndeclare type IPlugin = {\n  name: string\n  id: string\n  [key: string]: string | number\n}\n\ndeclare type DriverGuide = {\n  key?: string\n  label?: string\n  fields: Array<DriverField>\n}\n\ndeclare type Driver = {\n  protocol: string\n  name?: string\n  guide?: Array<DriverField>\n}\n\ndeclare type IFile = {\n  id: string\n  name: string\n  size: number\n  type: 'folder' | 'file' | 'drive'\n  ctime: number\n  mtime: number\n  path: string\n  extra?: Record<string, any>\n  [key: string]: any\n}\n"
  },
  {
    "path": "packages/sharelist-manage/src/types/shim.d.ts",
    "content": "declare module '*.vue' {\n  import { DefineComponent } from 'vue'\n  const component: DefineComponent<{}, {}, any>\n  export default component\n}\n\ndeclare type ISetting = {\n  title?: string\n  index_enable?: boolean\n  default_ignores?: Array<string>\n  [key: string]: any\n}\n"
  },
  {
    "path": "packages/sharelist-manage/src/types/source.d.ts",
    "content": "declare module '*.json'\ndeclare module '*.png'\ndeclare module '*.jpg'\ndeclare module 'vue-infinite-scroll'\n"
  },
  {
    "path": "packages/sharelist-manage/src/utils/format.ts",
    "content": "const EXT_IMAGE = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'tiff', 'wmf', 'tif', 'svg', 'webp']\n\nconst EXT_AUDIO_SUPPORT = ['mp3', 'm4a', 'acc', 'wav', 'ogg', 'flac']\n\nconst EXT_VIDEO_SUPPORT = ['mp4', 'mpeg', '3gp', 'mkv']\n\nexport const getFileType = (v: string, type = 'file'): string => {\n  if (type == 'folder' || type == 'drive') {\n    return type\n  } else {\n    if (v) v = v.toLowerCase().split('.').pop() || ''\n    if (['mp4', 'mpeg', 'wmv', 'webm', 'avi', 'rmvb', 'mov', 'mkv', 'f4v', 'flv'].includes(v)) {\n      return 'video'\n    } else if (['mp3', 'm4a', 'wav', 'wma', 'ape', 'flac', 'ogg'].includes(v)) {\n      return 'audio'\n    } else if (['doc', 'docx', 'wps'].includes(v)) {\n      return 'word'\n    } else if (['ppt', 'pptx'].includes(v)) {\n      return 'ppt'\n    } else if (['pdf'].includes(v)) {\n      return 'pdf'\n    } else if (['xls', 'xlsx', 'pdf', 'txt', 'yaml', 'yml', 'ini', 'cfg', 'xml', 'md'].includes(v)) {\n      return 'doc'\n    } else if (EXT_IMAGE.includes(v)) {\n      return 'image'\n    } else if (\n      [\n        'js',\n        'ts',\n        'css',\n        'html',\n        'c',\n        'h',\n        'cpp',\n        'py',\n        'java',\n        'jsp',\n        'php',\n        'cs',\n        'go',\n        'swift',\n        'vue',\n        'rs',\n        'asp',\n        'sql',\n      ].includes(v)\n    ) {\n      return 'code'\n    }\n    // else if (['zip', 'rar', '7z', 'tar', 'gz', 'gz2'].includes(v)) {\n    //   return 'archive'\n    // }\n    else {\n      return 'file'\n    }\n  }\n}\n\nexport const byte = (v: number, fixed?: number): string => {\n  if (v === undefined || v === null || isNaN(v)) {\n    return '-'\n  }\n\n  let lo = 0\n\n  while (v >= 1024) {\n    v /= 1024\n    lo++\n  }\n\n  const val = Math.floor(v * 100) / 100\n\n  return (fixed ? val.toFixed(fixed) : val) + ' ' + ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB'][lo]\n}\n\nconst fix0 = (v: any) => (v > 9 ? v : '0' + v)\n\nexport const time = (v: number): string => {\n  if (!v) return '-'\n  const date = new Date(v)\n  const thisYear = new Date().getFullYear()\n  let ret: string =\n    fix0(date.getMonth() + 1) + '/' + fix0(date.getDay()) + ' ' + fix0(date.getHours()) + ':' + fix0(date.getMinutes())\n  if (thisYear != date.getFullYear()) {\n    ret = date.getFullYear() + '/' + ret\n  }\n\n  return ret\n}\n\nexport const formatFile = (file: IFile | Array<IFile>): IFile | Array<IFile> => {\n  if (Array.isArray(file)) {\n    file.forEach((i) => {\n      i.ext = i.name.split('.').pop()\n      i.mediaType = getFileType(i.name, i.type)\n      if (i.ctime) i.ctimeDisplay = time(i.ctime)\n      if (i.size) i.sizeDisplay = byte(i.size)\n    })\n  } else {\n    file.ext = file.name.split('.').pop()\n    file.mediaType = getFileType(file.name, file.type)\n    if (file.ctime) file.ctimeDisplay = time(file.ctime)\n    if (file.size) file.sizeDisplay = byte(file.size)\n  }\n  return file\n}\n\nexport const isMediaSupport = (name: string, type: 'audio' | 'video' | 'image'): boolean => {\n  const ext: string = name.split('.').pop() || ''\n  if (type == 'audio' && EXT_AUDIO_SUPPORT.includes(ext)) {\n    return true\n  } else if (type == 'video' && EXT_VIDEO_SUPPORT.includes(ext)) {\n    return true\n  } else if (type == 'image' && EXT_IMAGE.includes(ext)) {\n    return true\n  }\n  return false\n}\n\n// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types\nexport const isType = (type: string) => (obj: any) => Object.prototype.toString.call(obj) === `[object ${type}]`\n\nexport const isArray = isType('Array')\n\nexport const isObject = isType('Object')\n\nexport const isBlob = isType('Blob')\n\nexport const isString = (v: string): boolean => typeof v == 'string'\n\nexport const getBlob = (data: string, filename: string): Blob | undefined => {\n  let blob\n  try {\n    blob = new Blob([data], { type: 'application/octet-stream' })\n  } catch (e) {\n    /**/\n  }\n  return blob\n}\n\nexport const saveFile = (data: Blob | string, filename: string): void => {\n  let blob: Blob | undefined\n  if (isString(data as string)) {\n    blob = getBlob(data as string, filename)\n  } else {\n    blob = data as Blob\n  }\n\n  if (blob && isBlob(blob)) {\n    const URL = window.URL || window.webkitURL\n\n    const link = document.createElement('a')\n\n    link.href = URL.createObjectURL(blob)\n    link.download = filename\n\n    const evt = document.createEvent('MouseEvents')\n    evt.initEvent('click', false, false)\n    // link.click()\n    link.dispatchEvent(evt)\n    URL.revokeObjectURL(link.href)\n  }\n}\n"
  },
  {
    "path": "packages/sharelist-manage/src/views/disk/index.less",
    "content": ".drive{\n  display: flex;\n  flex-direction: column;\n  height:100vh;\n\n  &.drive--lite{\n    height:auto;\n  }\n  .drive__header-assistant{\n    display: flex;\n    justify-content: space-between;\n    margin:0 24px 8px 32px;\n    padding-bottom:12px;\n    border-bottom: 1px solid var(--primary-hover-bg-color);\n    font-size:13px;\n  }\n  .ant-checkbox-inner{\n    border-radius: 50%;\n    box-sizing: border-box;\n  }\n\n  .ant-checkbox-indeterminate{\n    .ant-checkbox-inner{\n      background-color: var(--ant-primary-color);\n      border-color: var(--ant-primary-color);\n\n      &:after{\n        // background-color: var(--context-background);\n        background-color: #fff;\n        height:2px;\n      }\n    }\n  }\n\n  .drive__header{\n    display: flex;\n    align-items: center;\n    justify-content: space-between;\n    color: rgba(0, 0, 0, .65);\n    font-size: 22px;\n    padding:32px 32px;\n    z-index:1;\n    // position: sticky;\n    // top:0;\n    .drive__header-back {\n      margin-right: 1em;\n    }\n    // border-bottom:1px solid #f5f6f7;\n  }\n  // .drive-breadcrumb{\n  //   overflow-x:auto ;\n  // }\n  .drive__actions{\n    flex:none;\n    color: var(--context-1);\n\n    .ant-badge-dot{\n      // background:var(--primary-background);\n    }\n  }\n\n\n  .drive-search{\n    max-width:560px;\n    width:90%;\n    margin: auto;\n    transform: translate(0,20vh);\n  }\n  .drive-body-wrap{\n    flex: 1 1 auto;\n    overflow-y: auto;\n    position: relative;\n  }\n  .drive-body{\n    flex:1 1 auto;\n    .item{\n      display: flex;\n      width:100%;\n      justify-content: space-between;\n      align-items: center;\n      padding:12px 0;\n      \n      transition: all 0.3s;\n      margin-bottom:1px;\n      cursor: pointer;\n      position: relative;\n      color:var(--primary-text-color);\n\n\n      &:hover{\n        background-color: var(--context-hover);\n        .item-check{\n          opacity: 1;\n        }\n      }\n      .item-check{\n        padding:0 8px;\n        opacity: 0;\n        transition:opacity 0.3s;\n        \n      }\n      \n      &.item--checked{\n        background-color: var(--primary-hover-theme-color);\n        .item-check{\n          opacity: 1;\n        }\n      }\n\n      &.item--disabled{\n        color: var(--context-3);\n        cursor:not-allowed;\n        pointer-events: none;\n      }\n     \n      .item-icon__ext--md{\n        display: none;\n      }\n      .item-icon__ext--sm{\n        display: none;\n      }\n      .item-info{\n        padding:0 6px;\n        color:rgba(0,0,0,.5);\n        flex:0 0 auto;\n        position:absolute;\n        right:0;\n        top:0;\n      }\n      .item-name{\n        flex: 1;\n        min-width: 0;\n        width:100%;\n        overflow: hidden;\n        text-overflow: ellipsis;\n        white-space: nowrap;\n        // display: flex;\n        position: relative;\n      }\n      .item-icon{\n        margin-right: 16px;\n        flex:none;\n        width:36px;\n        position: relative;\n      }\n  \n      .item-thumb{\n        width:100%;\n        height:30px;\n        background-repeat: no-repeat;\n        background-size:cover;\n        background-position: center center;\n        flex:none;\n        border-radius: 5px;\n      }\n      \n      .item-icon__ext{\n        position: absolute;\n        font-size: 12px;\n        bottom: 5px;\n        color:inherit;\n        text-transform: uppercase;\n        transform: scale(0.8);\n        transform-origin:center;\n        // width:42px;\n        text-align: center;\n        width:100%;\n        font-weight: bold;\n        // transform-origin: left;\n        // left: 16px;\n      }\n     \n      .item-meta{\n        flex:auto;\n        display: flex;\n        justify-content:space-between;\n        align-items: center;\n        overflow: hidden;\n        color:inherit;\n      }\n      .item-ctime{\n        flex: 0 0 auto;\n        // padding:0 24px;\n        // width: 200px;\n        color:var(--context-2);\n        font-size: 12px;\n        text-align: left;\n      }\n      \n      .item-size{\n        // color:rgba(37, 38, 43, 0.72);\n        color:var(--context-2);\n        font-size: 12px;\n        text-align: right;\n        flex:0 0 80px;\n      }\n    }\n  }\n\n  .drive-body-mask{\n    position: absolute;\n    top:0;left:0;\n    width:100%;\n    height:100%;\n    opacity: 0;\n  }\n  .drive-body--padding{\n    padding:0 24px;\n  }\n\n  .drive-body.drive-body--grid{\n    display: grid;\n    grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));\n    grid-gap: 8px;\n    .item{\n      padding-left:8px;\n      padding-right:8px;\n      flex-direction: column;\n      .item-check{\n        position: absolute;\n        right:0px;\n        top:0px;\n      }\n      .item-icon{\n        width:auto;\n        margin-right:0;\n        margin-bottom:12px;\n      }\n\n      .item-meta{\n        flex-direction: column;\n        // align-items: flex-start;\n        align-items: center;\n        width:100%;\n        overflow: hidden;\n      }\n      .item-thumb{\n        height:64px;\n        width:96px;\n      }\n      .item-name{\n        text-align: center;\n        font-size:13px;\n      }\n      .item-ctime{\n        // display: none;\n        color:var(--context-3);\n      }\n      .item-size{\n        display: none;\n      }\n      .item-icon__ext{\n        font-size:15px;\n      }\n    }\n  }\n\n}\n\n.drive-toolbar-wrap{\n  position: absolute;\n  left: 50%;\n  bottom: 0;\n  transform: translateX(-50%);\n  z-index: 1;\n  transition: all .3s ease;\n  opacity: 0;\n  pointer-events: none;\n\n  &.show{\n    opacity: 1;\n    pointer-events: auto;\n    bottom:32px;\n  }\n}\n.drive-toolbar{\n    display: flex;\n    align-items: center;\n    padding: 8px 16px;\n    border-radius: 10px;\n    background: var(--dark-background);\n    overflow: visible;\n    user-select: none;\n    box-shadow: 0 0 1px 1px rgb(28 28 32 / 5%), 0 8px 24px rgb(28 28 32 / 12%);\n    color:#fff;\n    border: 1px solid var(--divider-1);\n    .drive-toolbar-item{\n      font-size:16px;\n      padding:8px;\n      color:#fff;\n      margin:0 6px;\n      border-radius: 6px;\n      &:hover{\n        background-color: var(--color-white-4);\n      }\n    }\n}\n\n.drive-search{\n .tips{\n   font-size: 12px;\n   color:var(--context-2);\n   margin-top: 1em;\n }\n .search-history__header{\n   font-size: 12px;\n   color:var(--context);\n   margin:1em 0;\n }\n\n .drive-search-input{\n   padding:8px;\n   font-size:14px;\n }\n\n input{\n   border:none;\n   background-color: transparent;\n   flex:auto;\n  //  border-bottom: 1px solid var(--context-4);\n\n  outline:none;\n }\n .search-history__body{\n\n }\n\n .search-history__item{\n   display: flex;\n   align-items: center;\n   width:100%;\n   justify-content: space-between;\n   padding:0.8em 8px;\n   border-radius: 3px;\n   cursor: pointer;\n   &:hover{\n     background-color:var(--context-hover);\n   }\n   color:var(--context_primary);\n }\n}\n// .badge"
  },
  {
    "path": "packages/sharelist-manage/src/views/disk/index.tsx",
    "content": "import { h, ref, Ref, defineComponent, watch, onMounted, onUnmounted, nextTick, toRef, computed, withModifiers } from 'vue'\nimport useStore from '@/store/index'\nimport { storeToRefs } from 'pinia'\nimport { Spin, Modal, Dropdown, Popover, Menu, Badge, Checkbox, Tooltip, InputSearch, RadioGroup, Input } from 'ant-design-vue'\nimport Icon from '@/components/icon'\nimport useDisk, { IFile } from './partial/useDisk'\nimport './index.less'\nimport { isMediaSupport } from '@/utils/format'\nimport MediaPlayer, { usePlayer } from '@/components/player'\nimport Breadcrumb from './partial/breadcrumb'\nimport Error from './partial/error'\nimport { useSetting } from '@/hooks/useSetting'\nimport { InfoCircleOutlined, LoadingOutlined, ScissorOutlined, EditOutlined, HddOutlined, FolderAddOutlined, EllipsisOutlined, DeleteOutlined, CloudSyncOutlined, PlusOutlined, CloudDownloadOutlined, DownloadOutlined, CloseCircleFilled, SwapOutlined, SortDescendingOutlined, CalendarOutlined, FieldBinaryOutlined, ArrowUpOutlined, ArrowDownOutlined, ClockCircleOutlined, MinusCircleOutlined } from '@ant-design/icons-vue'\nimport useConfirm, { useApiConfirm } from '@/hooks/useConfirm'\nimport { useRoute, onBeforeRouteLeave } from 'vue-router'\nimport { useApi } from '@/hooks/useApi'\nimport { useActions } from './partial/action'\nimport Task from './partial/task'\nimport { useBoolean } from '@/hooks/useHooks'\nimport { useLocalStorageState } from '@/hooks/useLocalStorage'\nimport { Upload } from './partial/upload'\nimport { useScroll } from '@/hooks/useScroll'\nimport { useClipboard } from '@/hooks/useClipboard'\nimport { showImage } from '@/components/image'\n\nexport default defineComponent({\n  setup() {\n    const { setConfig, clearCache, config } = useSetting()\n\n    const searchHistory = useLocalStorageState<Array<string>>('search_history', [])\n\n    const confirmClearCache = useConfirm(clearCache, '确认', '确认清除缓存？')\n\n    const route = useRoute()\n\n    const store = useStore()\n\n    const diskIntance = useDisk()\n\n    const { loading, files, error, setPath, paths, loadMore, diskConfig, current: currentDisk, setAuth, setSort, sortConfig, onUpdate } = diskIntance\n\n    const { rename, move, remove, mkdir, flashDownload, uploadConfirm, addDisk, setDisk, remoteDownload, showInfo } = useActions(diskIntance)\n\n    const { setPlayer } = usePlayer('player')\n\n    const driveEl: Ref<Element | any> = ref()\n\n    const { node: pasteEl } = useClipboard((files) => {\n      let { id } = diskConfig.value\n      let dest = '/' + [...paths.value].join('/')\n      uploadConfirm(files, dest, id)\n    }, 'file')\n\n\n    const { scrollTo, setNode, cancel: cancelScroll, isScroll, checkScroll } = useScroll(loadMore)\n\n    onMounted(() => {\n      setNode(driveEl.value)\n      onUpdate(() => {\n        setTimeout(checkScroll, 0)\n      })\n    })\n\n    onUnmounted(() => {\n      cancelScroll?.()\n    })\n\n    const onClick = (data: IFile) => {\n      if (data.type == 'folder' || data.type == 'drive') {\n        let target: any = {\n          id: data.id\n        }\n\n        if (!data.path && !currentDisk.search) {\n          target.path = currentDisk.path + '/' + data.name\n        }\n        setPath(target)\n      } else if (data.type == 'file') {\n        let mediaType = data.mediaType\n        if ((data.mediaType == 'audio' || data.mediaType == 'video') && isMediaSupport(data.name, mediaType)) {\n\n          const list: Array<IFile> = files.value.filter((i: IFile) => isMediaSupport(i.name, mediaType))\n          setPlayer({\n            list,\n            type: mediaType,\n            index: list.findIndex((i: IFile) => i.id == data.id),\n          })\n\n        }\n        else if (data.mediaType == 'image') {\n          const list: Array<IFile> =\n            files.value.filter((i: IFile) => isMediaSupport(i.name, 'image'))\n\n          showImage(list.map(i => i.download_url), list.findIndex(i => i.id == data.id))\n        }\n        else {\n          window.open(data.download_url)\n        }\n      }\n    }\n\n\n    let currentFocusFile: Ref<IFile | undefined> = ref()\n\n    const onAction = ({ key }: { key: any }) => {\n      targetBind.value = false\n      if (key == 'info') {\n        showInfo(currentFocusFile.value as IFile)\n      } else if (key == 'mount_drive') {\n        addDisk()\n      } else if (key == 'mkdir') {\n        mkdir(diskConfig.value)\n      } else if (key == 'config') {\n        let name = currentFocusFile.value?.name\n        let idx = config.drives.findIndex((i: IDrive) => i.name == name)\n        if (idx >= 0) {\n          setDisk(config.drives[idx], idx)\n        }\n      } else if (key == 'rename') {\n        rename(currentFocusFile.value as IFile)\n      } else if (key == 'move') {\n        move(currentFocusFile.value as IFile)\n      } else if (key == 'delete') {\n        remove(currentFocusFile.value as IFile)\n      } else if (key == 'upload') {\n\n      } else if (key == 'flash_upload') {\n        flashDownload(diskConfig.value)\n      } else if (key == 'remote_download') {\n        remoteDownload(diskConfig.value)\n      }\n    }\n\n    let isActionHide = true\n    let targetBind = ref(false)\n    const onContextChange = (visible: boolean) => {\n      console.log('action:', visible)\n      // isActionHide = true\n\n      if (visible) {\n        if (targetBind.value == false) {\n          currentFocusFile.value = undefined\n        }\n        //isActionHide = false\n        // onActionVisibleChange(visible)\n        // currentFocusFile.value = undefined\n        //\n\n      } else {\n        // currentFocusFile.value = undefined\n        // targetBind = false\n        targetBind.value = false\n      }\n    }\n\n    const onHover = (i: IFile | null, e?: MouseEvent) => {\n      //if (!actionVisible.value) {\n      //willShow = !!i\n      //if (willShow) {\n      console.log('on hover')\n      //已存在\n      if (currentFocusFile.value) {\n\n      }\n      currentFocusFile.value = i\n      if (i) {\n        targetBind.value = true\n      }\n      //}\n      // onActionVisibleChange()\n      //}\n      /*\n      if (i === null) {\n        currentFocusFile.value = undefined\n      } else {\n        if (!actionVisible.value) {\n          currentFocusFile.value = i\n        }\n      }*/\n\n    }\n\n    const download = (file: IFile | Array<IFile>) => {\n      if (!Array.isArray(file)) {\n        file = [file]\n      }\n\n      file.forEach((i: IFile) => {\n        let a = document.createElement('a')\n        let e = document.createEvent('MouseEvents')\n        e.initEvent('click', false, false)\n        a.href = i.download_url // 设置下载地址\n        a.download = i.name\n        a.dispatchEvent(e)\n      })\n    }\n\n    const mainSlots = {\n      overlay: () => {\n        let isDriveLevel = diskConfig.value.isRoot\n        // setTimeout(() => {\n        //   currentFocusFile.value = undefined\n        // }, 0)\n        // blank area\n        if (currentFocusFile.value || targetBind.value) {\n          if (isDriveLevel) {\n            return <Menu onClick={onAction}>\n              <Menu.Item class=\"dropdown-item\" key=\"config\"><HddOutlined style={{ fontSize: '16px', marginRight: '8px' }} />修改配置</Menu.Item>\n              <Menu.Item class=\"dropdown-item\" key=\"delete\"><DeleteOutlined class=\"danger-aciton\" style={{ fontSize: '18px', marginRight: '8px' }} /><span class=\"danger-aciton\">删除</span></Menu.Item>\n            </Menu >\n          } else {\n            return <Menu onClick={onAction}>\n              <Menu.Item class=\"dropdown-item\" key=\"info\"><InfoCircleOutlined style={{ fontSize: '16px', marginRight: '8px' }} />信息</Menu.Item >\n              <Menu.Item class=\"dropdown-item\" key=\"rename\"><EditOutlined style={{ fontSize: '16px', marginRight: '8px' }} />重命名</Menu.Item >\n              <Menu.Item class=\"dropdown-item\" key=\"move\"><ScissorOutlined style={{ fontSize: '18px', marginRight: '8px' }} />移动</Menu.Item>\n              <Menu.Item class=\"dropdown-item\" key=\"delete\"><DeleteOutlined class=\"danger-aciton\" style={{ fontSize: '18px', marginRight: '8px' }} /><span class=\"danger-aciton\" >删除</span></Menu.Item>\n            </Menu>\n          }\n        } else {\n          if (isDriveLevel) {\n            return <Menu onClick={onAction}>\n              <Menu.Item class=\"dropdown-item\" key=\"mount_drive\"><HddOutlined style={{ fontSize: '16px', marginRight: '8px' }} />挂载网盘</Menu.Item>\n            </Menu>\n          } else {\n            return <Menu onClick={onAction}>\n              <Menu.Item class=\"dropdown-item\" key=\"mkdir\" ><FolderAddOutlined style={{ fontSize: '18px', marginRight: '8px' }} />新建文件夹</Menu.Item>\n              <Upload><Menu.Item class=\"dropdown-item\" key=\"upload\"><Icon type='icon-upload-file-outline' style={{ fontSize: '18px', marginRight: '8px' }} />上传文件</Menu.Item></Upload>\n              <Upload type=\"dir\"><Menu.Item class=\"dropdown-item\" key=\"upload_folder\"><Icon type='icon-upload-folder-outline' style={{ fontSize: '18px', marginRight: '8px' }} />上传文件夹</Menu.Item></Upload>\n              <Menu.Item class=\"dropdown-item\" key=\"flash_upload\"><CloudDownloadOutlined style={{ fontSize: '18px', marginRight: '8px' }} />云端秒传</Menu.Item>\n              <Menu.Item class=\"dropdown-item\" key=\"remote_download\"><DownloadOutlined style={{ fontSize: '18px', marginRight: '8px' }} />离线下载</Menu.Item>\n            </Menu>\n          }\n        }\n\n      }\n    }\n\n    watch(\n      route,\n      (nv) => {\n        if (route.name == 'drive') {\n          let target: any = {\n            path: '/' + (route.params.path as string).replace(/^\\//, '')\n          }\n          if (route.query.search) {\n            target.search = route.query.search\n          }\n          setPath(target)\n          scrollTo(0)\n          // getFiles({ path: route.params.path as string })\n        }\n      },\n      { immediate: true },\n    )\n\n    const onTagClick = ({ path, index }: any = {}) => {\n      if (path == '/') {\n        setPath({ path: '/' })\n      } else if (index < paths.value.length) {\n        setPath({ path: '/' + paths.value.slice(0, index).join('/') })\n      }\n    }\n\n    const onSelect = (i: any) => {\n      i.checked = !i.checked\n    }\n\n    const onUnselectAll = () => {\n      files.value.forEach((i: any) => (i.checked = false))\n    }\n    const onSelectAll = (e: { target: { checked: boolean } }) => {\n      let val = e.target.checked\n      files.value.forEach((i: any) => (i.checked = val))\n    }\n\n    const selectState = computed(() => {\n      let count = files.value.filter((i: IFile) => !!i.checked).length\n      let containDir = files.value.some((i: IFile) => !!i.checked && i.type == 'folder')\n      let containDrive = files.value.some((i: IFile) => !!i.checked && i.type == 'drive')\n      let containFile = files.value.some((i: IFile) => !!i.checked && i.type == 'file')\n      // 0 unselect, 1 partially selected, 2 select all,\n      let state = count == 0 ? 0 : count == files.value.length ? 2 : 1\n      return {\n        state, containDir, containDrive, count, total: files.value.length, containFile\n      }\n    })\n\n    const onToggleSearch = () => {\n      const onSearch = (value: string) => {\n        console.log(value)\n        if (value) {\n          // router.push({ path: router.currentRoute.value.path, query: { search: value } })\n          let historyRecords = [...searchHistory.value]\n\n          let idx = historyRecords.indexOf(value)\n          if (idx >= 0) {\n            historyRecords.splice(idx, 1)\n          }\n\n          historyRecords.unshift(value)\n\n          searchHistory.value = historyRecords\n\n          setPath({ search: value, path: currentDisk.path, id: currentDisk.id })\n          modal.destroy()\n        }\n      }\n\n      const remove = (value: string) => {\n        let idx = searchHistory.value.indexOf(value)\n        if (idx >= 0) {\n          searchHistory.value.splice(idx, 1)\n        }\n      }\n\n\n      const options: Array<any> = []\n      const searchMode = diskConfig.value.search\n\n      const tips = ref(searchMode == 1 ? '* 仅支持全局搜索' : searchMode == 2 ? '* 仅支持搜索当前目录（不含子目录）' : '')\n      /*\n      if (searchMode == 1) {\n        options.push({ label: '所有文件', value: 'global' })\n      }\n      if (searchMode > 1) {\n        options.push({ label: '当前目录', value: 'local' })\n      }\n      const searchType = ref(options[0]?.value)\n*/\n\n      const modal = Modal.confirm({\n        class: 'fix-modal--alone',\n        width: '560px',\n        maskClosable: true,\n        content: () => (\n          <div class=\"drive-search\">\n            <div class=\"flex\" >\n              {/* <input class=\"drive-search-input\" type=\"text\" placeholder=\"搜索文件\" onKeypress={withModifiers(onEnter, ['enter'])} /> */}\n              <Input class=\"drive-search-input\" enter-button placeholder=\"搜索文件\" onPressEnter={e => onSearch(e.target.value)} />\n\n            </div>\n            {tips.value ? <div class=\"tips\">{tips.value}</div> : null}\n            <div class=\"drive-search-history\">\n              <div class=\"search-history__header\">搜索记录</div>\n              <ul class=\"search-history__body\">\n                {\n                  searchHistory.value.map((i) => <li class=\"search-history__item\" onClick={() => onSearch(i)}><div class=\"flex\"><ClockCircleOutlined style=\"margin-right:8px;\" />{i}</div><MinusCircleOutlined onClick={withModifiers(() => remove(i), ['prevent', 'stop'])} /></li>)\n                }\n              </ul>\n\n            </div>\n            {/* {options.length ? <RadioGroup style=\"margin-top:8px;\" options={options} value={searchType.value} onChange={(e) => searchType.value = e.target.value} name=\"radioGroup\" ></RadioGroup> : null} */}\n          </div>\n        ),\n      })\n    }\n\n    const changeView = () => {\n      let val = store.layout == 'list' ? 'grid' : 'list'\n      store.setLayout(val)\n    }\n\n    return () => (\n      <div class=\"drive\" ref={pasteEl}>\n        <div class=\"drive__header\">\n          <Breadcrumb onTagClick={onTagClick} paths={paths.value} />\n          <div class=\"drive__actions\">\n            {(diskConfig.value.search) ? <Icon style={{ fontSize: '18px', marginRight: '16px' }} class=\"drive-action-search\" type=\"icon-search\" onClick={onToggleSearch} /> : null}\n            <Popover overlayClassName='popover-padding-0' placement=\"topRight\" destroyTooltipOnHide={true} arrowPointAtCenter={true} trigger={['click']}>\n              {{\n                default: () => <Badge class=\"task-badge\" dot><CloudSyncOutlined style={{ fontSize: '18px' }} /></Badge>,\n                content: () => <Task style=\"padding:0\" />\n              }}\n            </Popover>\n            <Dropdown overlayClassName=\"dropdown--drive\" trigger={['click']}>\n              {{\n                default: () => <PlusOutlined style={{ fontSize: '18px', marginLeft: '16px' }} />,\n                overlay: () => (\n                  <Menu onClick={onAction}>\n                    <Menu.ItemGroup title=\"网盘\">\n                      <Menu.Item class=\"dropdown-item\" key=\"mount_drive\"><HddOutlined style={{ fontSize: '16px', marginRight: '8px' }} />挂载网盘</Menu.Item>\n                    </Menu.ItemGroup>\n                    <Menu.ItemGroup title=\"文件\">\n                      <Menu.Item disabled={diskConfig.value.isRoot} class=\"dropdown-item\" key=\"mkdir\" ><FolderAddOutlined style={{ fontSize: '18px', marginRight: '8px' }} />新建文件夹</Menu.Item>\n                      <Upload disabled={diskConfig.value.isRoot}><Menu.Item disabled={diskConfig.value.isRoot} class=\"dropdown-item\" key=\"upload\"><Icon type='icon-upload-file-outline' style={{ fontSize: '18px', marginRight: '8px' }} />上传文件</Menu.Item></Upload>\n                      <Upload disabled={diskConfig.value.isRoot} type=\"dir\"><Menu.Item disabled={diskConfig.value.isRoot} class=\"dropdown-item\" key=\"upload_folder\"><Icon type='icon-upload-folder-outline' style={{ fontSize: '18px', marginRight: '8px' }} />上传文件夹</Menu.Item></Upload>\n                      <Menu.Item disabled={diskConfig.value.isRoot} class=\"dropdown-item\" key=\"flash_upload\"><CloudDownloadOutlined style={{ fontSize: '18px', marginRight: '8px' }} />云端秒传</Menu.Item>\n                      <Menu.Item disabled={diskConfig.value.isRoot} class=\"dropdown-item\" key=\"remote_download\"><DownloadOutlined style={{ fontSize: '18px', marginRight: '8px' }} />离线下载</Menu.Item>\n                    </Menu.ItemGroup>\n\n                  </Menu>\n                )\n              }}\n            </Dropdown>\n            <DeleteOutlined onClick={confirmClearCache} style={{ fontSize: '18px', marginLeft: '16px' }} />\n          </div>\n        </div>\n        {\n          error.code === 0 ? <div class=\"drive__header-assistant\">\n            <div class=\"drive__select\">\n              <Checkbox onChange={onSelectAll} checked={selectState.value.state == 2} indeterminate={selectState.value.state == 1} />\n              <span style=\"margin-left:8px;\">{selectState.value.state > 0 ? `已选 ${selectState.value.count} 项` : `共 ${files.value.length} 项`}</span>\n            </div>\n            <div class=\"drive__sort flex\">\n              <Dropdown overlayClassName=\"dropdown--drive\" trigger={['click']}>\n                {{\n                  default: () => <span><SwapOutlined style=\"transform:rotate(90deg);margin-right:4px;\" />按{sortConfig.value.key == 'name' ? '名称' : sortConfig.value.key == 'size' ? '文件大小' : '修改时间'}{sortConfig.value.type == 'asc' ? '升序' : '降序'}</span>,\n                  overlay: () => <Menu onClick={({ key }: { key: any }) => setSort(key)}>\n                    <Menu.Item class=\"dropdown-item\" key=\"name\"><div class=\"flex flex--between\"><div><SortDescendingOutlined style={{ fontSize: '18px', marginRight: '8px' }} />名称</div>{sortConfig.value.key == 'name' ? (sortConfig.value.type == 'asc' ? <ArrowUpOutlined /> : <ArrowDownOutlined />) : null}</div></Menu.Item>\n                    <Menu.Item class=\"dropdown-item flex\" key=\"mtime\"><div class=\"flex flex--between\"><div><CalendarOutlined style={{ fontSize: '18px', marginRight: '8px' }} />修改时间</div>{sortConfig.value.key == 'mtime' ? (sortConfig.value.type == 'asc' ? <ArrowUpOutlined /> : <ArrowDownOutlined />) : null}</div></Menu.Item>\n                    <Menu.Item class=\"dropdown-item flex\" key=\"size\"><div class=\"flex flex--between\"><div><FieldBinaryOutlined style={{ fontSize: '18px', marginRight: '8px' }} />文件大小</div>{sortConfig.value.key == 'size' ? (sortConfig.value.type == 'asc' ? <ArrowUpOutlined /> : <ArrowDownOutlined />) : null}</div></Menu.Item>\n                  </Menu>\n                }}\n              </Dropdown>\n              <Tooltip>\n                {{\n                  title: '切换视图',\n                  default: () => <Icon onClick={changeView} type={'icon-' + (store.layout == 'list' ? 'grid' : 'list')} style={{ fontSize: '16px', marginLeft: '8px', padding: '0 8px' }} />\n                }}\n              </Tooltip>\n\n            </div>\n          </div> : null\n        }\n        <Dropdown destroyPopupOnHide={false} trigger={['contextmenu']} onVisibleChange={onContextChange} overlayClassName=\"dropdown--drive\" v-slots={mainSlots}>\n          <div class=\"drive-body-wrap\" ref={driveEl}>\n            {/* <div class=\"drive-body-mask\" onContextmenu={() => onHover(null)}>\n\n            </div> */}\n            <Spin delay={150} spinning={loading.value}>\n              <Error value={error} onAuth={setAuth}>\n\n                <div class={['drive-body', 'drive-body--padding', 'drive-body--' + store.layout]} >\n                  {files.value.map((i: IFile) => {\n                    return (\n                      <a class={[\"item\", i.checked ? 'item--checked' : null]} onContextmenu={(e) => onHover(i, e)} href={paths.value.join('/') + '/' + i.name + '?download'} title={i.name} onClick={withModifiers(() => onClick(i), ['prevent'])}>\n                        <div class=\"item-check\" onClick={withModifiers(() => onSelect(i), ['stop', 'prevent'])}><Checkbox checked={i.checked} onClick={withModifiers(() => onSelect(i), ['stop', 'prevent'])} /></div>\n\n                        <div class=\"item-icon\">\n                          {\n                            i.thumb ?\n                              <div class=\"item-thumb\" style={{ 'backgroundImage': `url(${i.thumb})` }}></div>\n                              : [<Icon\n                                style={{ fontSize: store.layout == 'grid' ? '64px' : '36px' }}\n                                type={'icon-' + i.mediaType}\n                              />,\n                              i.mediaType == 'file' ? <div class={[\"item-icon__ext\", i.ext.length > 7 ? 'item-icon__ext--sm' : i.ext.length > 4 ? 'item-icon__ext--md' : '']}>{i.ext}</div> : null\n                              ]\n                          }\n                        </div>\n\n                        <div class=\"item-meta\">\n                          <div class=\"item-name\">{i.name}</div>\n                          <div class=\"item-ctime\">{i.ctimeDisplay}</div>\n                          <div class=\"item-size\">{i.sizeDisplay}</div>\n                        </div>\n                      </a>\n                    )\n                  })}\n                </div>\n\n              </Error>\n            </Spin >\n          </div>\n        </Dropdown >\n\n        <div class={[\"drive-toolbar-wrap\", selectState.value.state > 0 ? 'show' : null]}>\n          <div class=\"drive-toolbar\">\n            {\n              !selectState.value.containDir && selectState.value.containFile ? <Tooltip>\n                {{\n                  title: '下载',\n                  default: () => <DownloadOutlined onClick={() => download(files.value.filter((i: any) => (i.checked)))} class=\"drive-toolbar-item\" />\n                }}\n              </Tooltip> : null\n            }\n            <Tooltip>\n              {{\n                title: '删除',\n                default: () => <DeleteOutlined onClick={() => remove(files.value.filter((i: any) => (i.checked)))} class=\"drive-toolbar-item danger-aciton--hover\" />\n              }}\n            </Tooltip>\n\n            {\n              selectState.value.count == 1 ? <Tooltip>\n                {{\n                  title: '重命名',\n                  default: () => <EditOutlined class=\"drive-toolbar-item\" onClick={() => rename(files.value.filter((i: any) => (i.checked))[0])} />\n                }}\n              </Tooltip> : null\n            }\n            {\n              !selectState.value.containDrive ? <Tooltip>\n\n                {{\n                  title: '移动',\n                  default: () => <ScissorOutlined onClick={() => move(files.value.filter((i: any) => (i.checked)))} class=\"drive-toolbar-item\" />\n                }}\n              </Tooltip> : null\n            }\n            {\n              selectState.value.count == 1 ? <Tooltip>\n                {{\n                  title: '信息',\n                  default: () => <InfoCircleOutlined class=\"drive-toolbar-item\" onClick={() => showInfo(files.value.filter((i: any) => (i.checked))[0])} />\n                }}\n              </Tooltip> : null\n            }\n            <Tooltip>\n              {{\n                title: '取消多选',\n                default: () => <CloseCircleFilled onClick={onUnselectAll} class=\"drive-toolbar-item\" />\n              }}\n            </Tooltip>\n\n          </div>\n        </div>\n      </div >\n    )\n  },\n})\n"
  },
  {
    "path": "packages/sharelist-manage/src/views/disk/partial/action/index.less",
    "content": ".file-item{\n  padding-right:8px;\n  overflow-x: hidden;\n  .ant-checkbox-inner{\n    border-radius: 50%;\n    box-sizing: border-box;\n  }\n\n  .file-item__rm{\n    opacity: 0;\n    transition: opacity 0.3s;\n    padding:8px;\n    cursor: pointer;\n\n    &:hover{\n      color:var(--color-red);\n    }\n  }\n  &:hover{\n    background-color: var(--primary-hover-bg-color);\n    .file-item__rm{\n      opacity: 1;\n    }\n  }\n}\n\n.setting{\n  \n  .setting__item{\n    // display: flex;\n    // align-items: center;\n    padding:8px 0;\n    margin-top:8px;\n    .setting__item-label{\n      flex:none;\n      // width:100px;      \n      font-size:12px;\n      color:var(--context-2);\n      display: block;\n      margin-bottom:0.6em;\n    }\n  }\n\n  &.small{\n    font-size:12px;\n  }\n}\n\n.file-detail__item{\n  padding:8px 16px;\n  .file-detail__item-label{\n    font-size:12px;\n    color:var(--context-3);\n  }\n\n  .file-detail__item-content{\n    font-size:14px;\n    color:var(--primary-text-color);\n  }\n}"
  },
  {
    "path": "packages/sharelist-manage/src/views/disk/partial/action/index.tsx",
    "content": "import { defineComponent, withDirectives, ref, getCurrentInstance, Ref, computed, reactive } from \"vue\";\nimport { Input, Modal, message, Alert, Checkbox, InputNumber, Radio, Tooltip, Textarea } from 'ant-design-vue'\nimport useDisk from '../useDisk'\nimport { EditOutlined, ScissorOutlined, CloudDownloadOutlined, CloudUploadOutlined, DownloadOutlined, QuestionCircleOutlined } from '@ant-design/icons-vue'\nimport { useApi } from \"@/hooks/useApi\";\nimport { Meta, Tree } from '../meta'\nimport { useApiConfirm } from '@/hooks/useConfirm'\nimport { byte, getFileType, time } from '@/utils/format'\nimport { useUpload } from '../upload'\nimport Modifier from '../modifier'\nimport { useSetting } from '@/hooks/useSetting'\nimport './index.less'\nimport { useFocus } from '@/hooks/useDirective'\n\n// import { IUseSettingResult } from '../useDisk'\n\ninterface IFileError extends IFile {\n  errorMessage?: string\n}\n\nconst FileErrorList = defineComponent({\n  props: {\n    files: Array\n  },\n  emits: ['change'],\n  setup(props, { emit }) {\n    let unchecked: Ref<Array<boolean> | any> = ref(props.files?.map(i => true))\n\n    const toggle = (idx: number) => {\n      unchecked.value[idx] = !unchecked.value[idx]\n      emit('change', unchecked.value.reduce((t: Array<number>, c: boolean, idx: number) => c ? t.concat(idx) : t, []))\n    }\n    return () => <div>\n      <div class=\"modal\">\n        <div class=\"modal__body\" style=\"max-height:40vh;overflow-y:auto;\">\n          {\n            props.files?.map((i: any, idx) => <div onClick={() => toggle(idx)} class=\"flex flex--between file-item\"><Meta style=\"padding:8px;\" errorMode={true} data={i as unknown as IFile}></Meta><Checkbox checked={!!unchecked.value[idx]} /></div>)\n          }\n        </div>\n      </div>\n    </div>\n  }\n})\n\nconst FileSelect = defineComponent({\n  props: {\n    files: Array\n  },\n  emits: ['change'],\n  setup(props, { emit }) {\n    let unchecked: Ref<Array<boolean> | any> = ref(props.files?.map(i => true))\n\n    const toggle = (idx: number) => {\n      unchecked.value[idx] = !unchecked.value[idx]\n      emit('change', unchecked.value.reduce((t: Array<number>, c: boolean, idx: number) => c ? t.concat(idx) : t, []))\n    }\n    return () => <div>\n      <div class=\"modal\">\n        <div class=\"modal__body\" style=\"max-height:40vh;overflow-y:auto;\">\n          {\n            props.files?.map((i: any, idx) => <div onClick={() => toggle(idx)} class=\"flex flex--between file-item\"><Meta style=\"padding:8px;\" data={i as unknown as IFile}></Meta><Checkbox checked={!!unchecked.value[idx]} /></div>)\n          }\n        </div>\n      </div>\n    </div>\n  }\n})\n\nexport const useActions = (diskIntance: any) => {\n\n  let { mutate, setAuth, current, setPath, reload } = diskIntance\n\n  const appContext = getCurrentInstance()?.appContext;\n\n  let request = useApi()\n  const { setConfig, clearCache, config } = useSetting()\n\n  const rename = (i: IFile) => {\n    let name = i.name\n\n    const onChange = (e: any) => {\n      name = e.target.value\n    }\n\n    const onSave = async () => {\n      if (name != i.name) {\n        let res = await request.fileUpdate({ id: i.id, name })\n        // console.log(res)\n        if (res.error) {\n          message.error(res.error.message)\n          throw new Error()\n        } else {\n          i.name = name\n          mutate(i)\n          modal.destroy()\n        }\n      }\n    }\n\n    const modal = Modal.confirm({\n      class: 'pure-modal',\n      width: '500px',\n      closable: true,\n      appContext,\n      autoFocusButton: null,\n      title: <div><EditOutlined style={{ fontSize: '18px', marginRight: '8px' }} />重命名</div>,\n      content: () => (\n        <div>\n          <Meta style=\"margin:16px 0;\" data={i} />\n          {useFocus(<Input class=\"sl-input\" style=\"padding:1em;margin-top:16px;\" defaultValue={name} onChange={onChange} />, true)}\n        </div>\n      ),\n      onOk: onSave,\n    })\n  }\n\n  const move = (file: IFile | Array<IFile>) => {\n    let dest: String\n\n    let files: Array<IFile> = Array.isArray(file) ? file : [file]\n\n    let srcPath = diskIntance.current.path\n\n    let isTransfer = ref(false)\n\n    let threadNum = ref(1)\n\n    let buttonProps = reactive({ disabled: true })\n\n    let count = files.length\n    let isSingleTask = count == 1\n    let error: Array<any> = []\n    let current = ref(0)\n    let cancelFlag = ref(false)\n    let key = `${Math.random()}`\n\n    let targetMultiThreading = ref(false)\n    // 排除 当前目录 及 子目录\n    let excludePath = [\n      srcPath,\n      ...files.map(i => i.id)\n    ]\n\n    const onSelect = (e: IFile, targetDiskConfig: any) => {\n      let autoExpand = config.expand_single_disk\n\n      let diskCount = config.drives.length\n\n      let destPath = e.path\n\n      let destIsRoot = destPath == '/'\n\n      // 根目录是磁盘列表\n      let rootIsDiskList = (!autoExpand || diskCount > 1)\n\n      buttonProps.disabled = destPath == srcPath || (destIsRoot && rootIsDiskList)\n\n      isTransfer.value = rootIsDiskList && srcPath.split('/')[1] != destPath.split('/')[1] && !destIsRoot\n\n      dest = e.id\n\n      targetMultiThreading.value = targetDiskConfig.multiThreading\n    }\n\n    const execMove = async () => {\n\n      message.loading({ content: `正在${isTransfer.value ? '创建迁移任务' : '移动'}`, key, duration: 0 });\n\n      for (let i of files as Array<IFile>) {\n        if (cancelFlag.value) return\n        message.loading({ content: `[${current.value + 1}/${count}]` + '正在移动:' + i.name, key, duration: 0 });\n\n        let res = await request.fileUpdate({ id: i.id, dest, threadNum: threadNum.value })\n        if (res.error) {\n          i.error = res.error.message\n          error.push(i.id)\n        } else {\n          if (!isTransfer.value) mutate(i, true)\n        }\n        current.value++\n      }\n\n      let errorFiles = files.filter((i: IFile) => error.includes(i.id))\n\n      if (errorFiles.length) {\n        if (isSingleTask) {\n          message.error({ content: (files as Array<IFile>)[0].error, key, duration: 1 });\n        } else {\n          message.success({ content: '操作完成', key, duration: 0.01 });\n          errorModal(errorFiles)\n        }\n\n      } else {\n        message.success({ content: isTransfer.value ? '迁移任务创建成功' : '操作完成', key, duration: 1 });\n      }\n    }\n\n    const errorModal = (errorFiles: Array<IFile>) => {\n      let retryList: Array<number> = []\n      Modal.confirm({\n        class: 'pure-modal',\n        width: '500px',\n        closable: true,\n        title: () => <div><ScissorOutlined style={{ fontSize: '18px', marginRight: '8px' }} />移动</div>,\n        okText: '重试',\n        content: () => (\n          <div>\n            <div class=\"modal\">\n              <div class=\"modal__body\">\n                <Alert message=\"以下文件移动失败\" type=\"info\" />\n                <FileErrorList files={errorFiles} onChange={(val) => retryList = val} />\n              </div>\n            </div>\n          </div>\n        ),\n        onOk: () => {\n          if (retryList.length) {\n            files = (files as Array<IFile>).filter((_, idx: number) => retryList.includes(idx))\n            //move((files as Array<IFile>).filter((_, idx: number) => retryList.includes(idx)))\n            execMove()\n          }\n        },\n      })\n    }\n\n    const modal = Modal.confirm({\n      class: 'pure-modal',\n      width: '500px',\n      closable: true,\n      title: () => <div><ScissorOutlined style={{ fontSize: '18px', marginRight: '8px' }} />移动</div>,\n      content: () => (\n        <div>\n          <div class=\"modal\">\n            <div class=\"modal__body\">\n              {files.length == 1 ? <Meta style=\"margin:16px 0;\" data={(files as Array<IFile>)[0]} /> : null}\n\n              <Tree onSelect={onSelect} excludes={excludePath} treeStyle={{ height: '250px' }} />\n              {\n                isTransfer.value ? [\n                  <Alert style=\"margin:16px 0\" message='您选择的是跨盘移动，后台将自动创建迁移任务。迁移成功后原始位置的文件不会被删除。' type=\"info\" />,\n                  <div class=\"setting small\">\n                    {\n                      targetMultiThreading.value ? <div class=\"setting__item\">\n                        <div class=\"setting__item-label\">线程数<Tooltip title=\"在源挂载盘支持的情况下，设置下载线程数，可提高整体下载速度。建议不要超过10。\"><QuestionCircleOutlined style={{ marginLeft: '8px', 'fontSize': '13px' }} /></Tooltip></div>\n                        <InputNumber value={threadNum.value} min={1} onChange={(e) => { threadNum.value = e as number }}></InputNumber>\n                      </div> : null\n                    }\n\n                    {/* <div class=\"setting__item\">\n                      <div class=\"setting__item-label\">同名文件</div>\n                      <RadioGroup>\n                        <Radio value=\"rename\">重命名</Radio>\n                        <Radio value=\"replace\">替换</Radio>\n                        <Radio value=\"skip\">跳过</Radio>\n                      </RadioGroup>\n                    </div> */}\n                  </div>\n                ] : null\n              }\n            </div>\n          </div>\n        </div>\n      ),\n      okButtonProps: buttonProps,\n      onOk: () => {\n        execMove()\n      },\n    })\n\n  }\n\n  const mkdir = (i: IFile) => {\n\n    let name = '新建文件夹'\n\n    const onChange = (e: any) => {\n      name = e.target.value\n    }\n\n    const onSave = async () => {\n      if (name) {\n        let res = await request.mkdir({ id: i.id, name })\n        if (res.error) {\n          message.error(res.error.message)\n        } else {\n          mutate(res, 1)\n          modal.destroy()\n        }\n      }\n    }\n\n\n    const modal = Modal.confirm({\n      class: 'pure-modal',\n      width: '500px',\n      closable: true,\n      autoFocusButton: null,\n      title: () => <div><EditOutlined style={{ fontSize: '18px', marginRight: '8px' }} />新建文件夹</div>,\n      content: () => (\n        <div class=\"modal__body\">\n          {useFocus(<Input class=\"sl-input\" style=\"padding:1em;margin-top:16px;\" defaultValue={name} onChange={onChange} />, true)}\n        </div>\n      ),\n      onOk: onSave,\n    })\n  }\n\n  const flashDownload = (i: any) => {\n    let options = typeof i.hashUpload == 'string' ? { type: i.hashUpload } : i.hashUpload\n\n    let params = {\n      name: '',\n      hash: '',\n      size: 0\n    }\n\n\n    const onSave = async () => {\n      if (options.size && !params.size) return\n      if (!params.hash || !params.name) return\n\n      let res = await request.fileHashDownload({ id: i.id, ...params })\n      // console.log(res)\n      if (res.error) {\n        message.error(res.error.message)\n        throw new Error()\n      } else {\n        message.success('秒传成功')\n        // mutate(res)\n        modal.destroy()\n        reload()\n      }\n    }\n\n    const modal = Modal.confirm({\n      class: 'pure-modal',\n      width: '500px',\n      closable: true,\n      autoFocusButton: null,\n      title: () => <div><CloudDownloadOutlined style={{ fontSize: '18px', marginRight: '8px' }} />云端秒传</div>,\n      content: () => (\n        <div>\n          <div class=\"modal\">\n            <div class=\"modal__body\">\n              {\n                options?.type ? <Alert message={'当前云盘支持通过文件 ' + options.type + ' 值秒传'} type=\"info\" /> : <Alert message=\"当前云盘不支持此功能\" banner type=\"error\" />\n              }\n              <Input class=\"sl-input\" placeholder={`${options.type.toUpperCase() || ''}`} style=\"padding:1em;margin-top:16px;\" onChange={e => (params.hash = e.target.value as string)} />\n              <Input class=\"sl-input\" placeholder='文件名' style=\"padding:1em;margin-top:16px;\" onChange={e => (params.name = e.target.value as string)} />\n              {\n                options?.size ? <InputNumber class=\"sl-input\" placeholder='文件大小' style=\"padding:1em;width:100%;margin-top:16px;\" onChange={value => (params.size = value as number)} /> : null\n              }\n\n            </div>\n          </div>\n        </div>\n      ),\n      onOk: onSave,\n    })\n  }\n\n  const uploadConfirm = (files: Array<File>, dest: string, id: string) => {\n\n    const { create } = useUpload()\n\n    let checked: Array<number> = files.map((_, idx: number) => idx)\n\n    const onChecked = (ids: Array<number>) => {\n      checked = ids\n    }\n\n    const ensure = () => {\n      const checkedFiles = checked.map((idx: number) => files[idx])\n      create(checkedFiles, 'md5', dest, id)\n      message.success('任务创建成功')\n    }\n\n    const data = files.map((i, idx) => ({\n      idx: idx,\n      id: i.name,\n      size: i.size,\n      name: i.name,\n      ctime: i.lastModified,\n      ctimeDisplay: time(i.lastModified),\n      sizeDisplay: byte(i.size),\n      iconType: getFileType(i.name),\n      checked: true\n    }))\n\n\n    Modal.confirm({\n      class: 'pure-modal',\n      width: '500px',\n      closable: true,\n      title: () => <div><CloudUploadOutlined style={{ fontSize: '18px', marginRight: '8px' }} />文件上传</div>,\n      content: <FileSelect files={data} onChange={onChecked} />,\n      onOk: ensure,\n    })\n\n  }\n\n  const remove = (files: IFile | Array<IFile>) => {\n    if (!Array.isArray(files)) {\n      files = [files]\n    }\n\n    if (files.some(i => i.type == 'drive')) {\n      removeDisk(files.filter(i => i.type == 'drive'))\n      return\n    }\n    let count = files.length\n\n    const error: Array<any> = []\n    const current = ref(0)\n    const cancelFlag = ref(false)\n    const key = '' + Math.random()\n    const modal = Modal.confirm({\n      title: '删除文件',\n      content: `确认删除 ${count == 1 ? files[0].name : `${count} 项`}？`,\n      onOk() {\n        execRemove()\n      },\n      onCancel() {\n        cancelFlag.value = true\n      }\n    })\n\n    const execRemove = async () => {\n\n      message.loading({ content: '正在删除', key, duration: 0 });\n\n      for (let i of files as Array<IFile>) {\n        if (cancelFlag.value) return\n        message.loading({ content: `[${current.value + 1}/${count}]` + '正在删除:' + i.name, key, duration: 0 });\n\n        let res = await request.fileDelete({ id: i.id })\n        if (res.error) {\n          error.push(i.id)\n        } else {\n          mutate(i, true)\n        }\n        current.value++\n      }\n\n      let errorFiles = files.filter((i: IFile) => error.includes(i.id))\n      if (errorFiles.length) {\n        return modal.update({\n          content: () => <div><div>以下文件删除失败</div><FileSelect files={errorFiles} /></div>\n        })\n      } else {\n        message.success({ content: '已成功删除', key, duration: 1 });\n      }\n    }\n\n  }\n\n  const setDisk = (data: IDrive, idx = -1, msg: string = '修改成功') => {\n    const updateData = async (modifyData: IDrive) => {\n      const saveData = [...config.drives]\n      // console.log(saveData, idx)\n      if (idx == -1) {\n        saveData.push(modifyData)\n      } else {\n        saveData[idx] = modifyData\n      }\n      await setConfig({ drives: saveData }, msg)\n      // update files\n\n      if (!current.path || current.path == '/') {\n        setPath({ path: '/' }, true)\n      }\n      modal.destroy()\n    }\n\n    const modal = Modal.confirm({\n      class: 'fix-modal--alone',\n      width: '720px',\n      closable: true,\n      content: (\n        <div>\n          <Modifier defaultValue={data} onUpdate={updateData} />\n        </div>\n      ),\n      onOk: () => { },\n    })\n  }\n\n  const addDisk = () => {\n    setDisk(\n      {\n        name: '',\n        protocol: '',\n      },\n      -1,\n      '创建成功'\n    )\n  }\n\n  const removeDisk = async (files: IFile | Array<IFile>) => {\n    if (!Array.isArray(files)) {\n      files = [files]\n    }\n    const execRemove = () => {\n      const saveData = [...config.drives]\n\n      for (let i of files as Array<IFile>) {\n        let idx = saveData.findIndex(j => j.id == i.extra?.config_id)\n        if (idx) {\n          saveData.splice(idx, 1)\n        }\n      }\n\n      setConfig({ drives: saveData }, '删除成功')\n\n      if (!current.path || current.path == '/') {\n        setPath({ path: '/' }, true)\n      }\n    }\n\n    //await request.removeDrive({drive: files.map(i => i.id) })\n\n    let count = files.length\n    Modal.confirm({\n      title: '删除挂载盘',\n      content: `确认删除 ${count == 1 ? files[0].name : `${count} 项`}？`,\n\n      onOk() {\n        execRemove()\n      },\n      onCancel() {\n      }\n    })\n  }\n\n  const remoteDownload = (i: any) => {\n    let url = ref('')\n    let threadNum = ref(1)\n\n    const onSave = async () => {\n      if (url.value) {\n        let res = await request.fileUpdate({ dest: i.id, id: url.value, threadNum: threadNum.value })\n        if (res.error) {\n          message.error(res.error.message)\n          throw new Error()\n        } else {\n          message.success('任务创建成功')\n          modal.destroy()\n        }\n      }\n    }\n\n\n    const modal = Modal.confirm({\n      class: 'pure-modal',\n      width: '500px',\n      closable: true,\n      autoFocusButton: null,\n      title: () => <div><DownloadOutlined style={{ fontSize: '18px', marginRight: '8px' }} />离线下载</div>,\n      content: () => (\n        <div>\n          <div class=\"modal\">\n            <div class=\"modal__body\">\n              {\n                i.remoteDownload != false ? <Alert message={'sharelist将在后台创建下载任务。注意：此离线下载并非挂载盘原生提供的功能。'} type=\"info\" /> : <Alert message=\"当前挂载盘不支持此功能\" banner type=\"error\" />\n              }\n\n              {\n                useFocus(<Textarea class=\"sl-input\" placeholder={`请输入URL`} style=\"margin-top:16px;\" defaultValue={url.value} onChange={e => (url.value = e.target.value as string)} />)\n              }\n              {\n                i.multiThreading ? <div class=\"setting small\">\n                  <div class=\"setting__item\">\n                    <div class=\"setting__item-label\">线程数<Tooltip title=\"设置下载线程数，可提高整体下载速度。建议不要超过10。\"><QuestionCircleOutlined style={{ marginLeft: '8px', 'fontSize': '13px' }} /></Tooltip></div>\n                    <InputNumber class=\"sl-input\" value={threadNum.value} min={1} onChange={(e) => { threadNum.value = e as number }}></InputNumber>\n                  </div>\n                </div> : null\n              }\n\n            </div>\n          </div>\n        </div>\n      ),\n      onOk: onSave,\n    })\n  }\n\n\n  const showInfo = (file: IFile) => {\n    const modal = Modal.info({\n      class: 'pure-modal',\n      width: '500px',\n      closable: true,\n      appContext,\n      autoFocusButton: null,\n      // title: <div><InfoCircleOutlined style={{ fontSize: '18px', marginRight: '8px' }} />文件</div>,\n      content: () => (\n        <div>\n          <Meta style=\"margin:16px 0;\" data={file} />\n          <div class=\"file-detail\">\n            <div class=\"file-detail__item\">\n              <h4 class=\"file-detail__item-label\">全局URI</h4>\n              <div class=\"file-detail__item-content\" style=\"font-size:10px;\">{file.id}</div>\n            </div>\n            <div class=\"file-detail__item\">\n              <h4 class=\"file-detail__item-label\">目录ID</h4>\n              <div class=\"file-detail__item-content\" style=\"font-size:10px;\">{file.extra?.fid}</div>\n            </div>\n          </div>\n        </div>\n      ),\n    })\n  }\n\n\n  return {\n    rename, move, remove, mkdir, flashDownload, uploadConfirm,\n    setDisk, addDisk, removeDisk, remoteDownload, showInfo\n  }\n}"
  },
  {
    "path": "packages/sharelist-manage/src/views/disk/partial/auth/index.less",
    "content": ".auth-box{\n  height:70vh;\n  width:100%;\n  display:flex;\n  align-items:center;\n  justify-content:center;\n  flex-direction:column;\n  box-sizing:border-box;\n\n  .auth-box__wrap{\n    width:320px;\n  }\n  .auth-box__header{\n    font-size:14px;\n    word-wrap: break-word;\n    word-break: break-all;\n    color:var(--primary-text-color);\n    margin-bottom:24px;\n  }\n  .auth-box__input{\n    padding:8px 11px;\n  }\n  .auth-box__btn{\n    width:100%;\n    margin-top:24px;\n    height:42px;\n  }\n}\n\n"
  },
  {
    "path": "packages/sharelist-manage/src/views/disk/partial/auth/index.tsx",
    "content": "import { ref, defineComponent, watch, onMounted, toRef, watchEffect, reactive } from 'vue'\nimport { Button, Input } from 'ant-design-vue'\nimport useDisk from '../useDisk'\nimport { LockOutlined } from '@ant-design/icons-vue'\nimport './index.less'\n\nexport default defineComponent({\n  props: {\n    message: {\n      type: String\n    },\n    scope: {\n      type: Object\n    }\n  },\n  emits: ['auth'],\n  setup(props) {\n    const { setAuth } = useDisk()\n\n    const token = ref('')\n    const onEnter = () => {\n      let set: Record<string, string> = {\n        token: token.value\n      }\n      setAuth({ id: props.scope?.id, path: props.scope?.path, token: token.value })\n    }\n\n    watch(() => props.scope, () => {\n      token.value = ''\n    })\n\n    return () => (\n      <div class=\"auth-box\">\n\n        <div class=\"auth-box__wrap\">\n          {\n            props.scope?.path ? <h2 class=\"auth-box__header\">{props.scope?.path} 需要身份验证</h2> : null\n          }\n          <Input.Password\n            class=\"auth-box__input\"\n            value={token.value}\n            onChange={(e) => (token.value = e.target.value as string)}\n            placeholder=\"输入目录访问密码\"\n            onPressEnter={onEnter}\n          >\n            {{\n              prefix: () => <LockOutlined />\n            }}\n          </Input.Password>\n          <Button class=\"auth-box__btn\" type=\"primary\" onClick={onEnter}>\n            确定\n          </Button>\n        </div>\n      </div>\n    )\n  },\n})\n"
  },
  {
    "path": "packages/sharelist-manage/src/views/disk/partial/breadcrumb/index.less",
    "content": ".drive-breadcrumb{\n  a{\n    // color:var(--primary-color);\n    font-weight: 600;\n    max-width: 100%;\n    overflow: hidden;\n    white-space: nowrap;\n    -o-text-overflow: ellipsis;\n    text-overflow: ellipsis;\n    font-size:18px;\n  }\n  .ant-breadcrumb{\n    color:var(--context-3);\n    a,.ant-breadcrumb-separator{\n      color:var(--context-3);\n    }\n    a:hover{\n      color:var(--primary-color);\n    }\n  }\n  .ant-breadcrumb > span:last-child a {\n    color: var(--primary-text-color);\n  }\n\n  &.drive-breadcrumb--sm{\n    a{font-size: 12px;}\n  }\n}\n\n.drive-breadcrumb-pop-item{\n  padding:8px 16px;\n  &:hover{\n    background-color: var(--primary-hover-bg-color);\n    transition: all 0.3s;\n    cursor: pointer;\n  }\n}\n\n.drive-breadcrumb-wrap{\n  overflow-x: visible;\n}"
  },
  {
    "path": "packages/sharelist-manage/src/views/disk/partial/breadcrumb/index.tsx",
    "content": "import { ref, defineComponent, reactive, onMounted, onUnmounted, computed, watchEffect, PropType, watch } from 'vue'\nimport Icon from '@/components/icon'\nimport { Breadcrumb, Popover, List } from 'ant-design-vue'\nimport { EllipsisOutlined } from '@ant-design/icons-vue'\n\nimport './index.less'\nexport default defineComponent({\n  props: {\n    paths: {\n      type: Array as PropType<Array<string>>\n    },\n    size: {\n      type: String,\n      default: 'default'\n    }\n  },\n  emit: ['tagClick'],\n  setup(props, ctx) {\n\n    const el = ref()\n\n    const defaultClietHeight = ref(0)\n    const lastClientWidth = ref(0)\n    const onclick = (path: string, idx: number) => {\n      ctx.emit('tagClick', { path, index: idx })\n    }\n\n    let ellipsisRange = ref(1)\n\n    const onUpdate = () => {\n      let { clientWidth, clientHeight } = el.value\n      if (defaultClietHeight.value != clientHeight) {\n        ellipsisRange.value++\n        lastClientWidth.value = clientWidth\n      }\n    }\n\n    let observer: ResizeObserver | null\n    onMounted(() => {\n      if (el.value) {\n        if (!defaultClietHeight.value) {\n          defaultClietHeight.value = el.value.clientHeight\n        }\n        observer = new ResizeObserver(entries => {\n          onUpdate() // entries[0].contentRect\n        })\n        observer.observe(el.value)\n      }\n      window.addEventListener('resize', onUpdate)\n    })\n\n    onUnmounted(() => {\n      observer?.disconnect()\n      observer = null\n      window.removeEventListener('resize', onUpdate)\n\n    })\n\n    watch(() => props.paths, (nv, ov) => {\n      let nvl = nv?.length || 0\n      let ovl = ov?.length || 0\n      if (nvl < ovl) {\n        ellipsisRange.value = 1\n      }\n      onUpdate()\n    })\n\n    const createItem = () => {\n      const nodes = []\n      const paths = props.paths || []\n      if (paths.length == 0) return []\n\n      nodes.push(\n        <Breadcrumb.Item onClick={() => onclick(paths[0], 1)}>\n          <a>{paths[0]}</a>\n        </Breadcrumb.Item>\n      )\n      if (ellipsisRange.value > 1) {\n        let dataSrc = paths.slice(1, 1 + ellipsisRange.value)\n        nodes.push(\n          <Breadcrumb.Item><Popover overlayClassName='popover-padding-0' overlayStyle={{ 'padding': '8px' }} placement=\"topRight\" destroyTooltipOnHide={true} arrowPointAtCenter={true} trigger={['click']}>\n            {{\n              default: () => <EllipsisOutlined style={{ fontSize: '18px' }} />,\n              content: () => <List data-source={dataSrc}>{{\n                renderItem: ({ item, index }: { item: string, index: number }) => <List.Item onClick={() => onclick(item, ellipsisRange.value + index)} class=\"drive-breadcrumb-pop-item\">{item}</List.Item>\n              }}</List>\n            }}\n          </Popover></Breadcrumb.Item>\n        )\n\n        nodes.push(...paths.slice(1 + ellipsisRange.value).map((i, idx) => (\n          <Breadcrumb.Item onClick={() => onclick(i, idx + 1 + ellipsisRange.value)}>\n            <a>{i}</a>\n          </Breadcrumb.Item>\n        )))\n      } else {\n        nodes.push(...paths.slice(1).map((i, idx) => (\n          <Breadcrumb.Item onClick={() => onclick(i, idx + 1 + 1)}>\n            <a>{i}</a>\n          </Breadcrumb.Item>\n        )))\n      }\n\n      return nodes\n    }\n    return () => (\n      <div ref={el} class={['drive-breadcrumb', 'drive-breadcrumb--' + props.size /*routes.value.length == 0 ? 'drive-routes--hidden' : null*/]}>\n        <Breadcrumb separator=\">\">\n          <Breadcrumb.Item onClick={() => onclick('', 0)}>\n            <a>文件</a>\n          </Breadcrumb.Item>\n          {createItem()}\n        </Breadcrumb>\n      </div>\n    )\n  },\n})\n"
  },
  {
    "path": "packages/sharelist-manage/src/views/disk/partial/error/index.less",
    "content": "\n.err{\n  height:70vh;\n  width:100%;\n  display:flex;\n  align-items:center;\n  justify-content:center;\n  flex-direction:column;\n  box-sizing:border-box;\n  .err__status{\n    font-size: 36px;\n    font-family: inherit;\n    font-weight: 500;\n    line-height: 1.1;\n    color:var(--primary-text-color);\n  }\n  .err__msg{\n    color:var(--context-2);\n    font-size: 16px;\n    font-family: 'Source Code Pro','microsoft yahei',Lato,Helvetica,Arial,sans-serif;\n  }\n}"
  },
  {
    "path": "packages/sharelist-manage/src/views/disk/partial/error/index.tsx",
    "content": "import { defineComponent, watch } from 'vue'\nimport './index.less'\nimport { message } from 'ant-design-vue'\nimport AuthBox from '../auth'\n\nexport default defineComponent({\n  props: {\n    value: {\n      type: Object,\n      required: true,\n    },\n  },\n  emits: ['auth'],\n  setup(props, ctx) {\n    if (props.value.code == 401 && props.value.message) {\n      message.error(props.value.message)\n    }\n    return () => {\n      if (props.value.code) {\n        return props.value.code == 401 ? (\n          <AuthBox scope={props.value.scope} onAuth={(d) => ctx.emit('auth', d)} />\n        ) : (\n          <div class=\"err\">\n            <h1 class=\"err__status\">{props.value.code}</h1>\n            <div class=\"err__msg\">{props.value.message}</div>\n          </div>\n        )\n      } else {\n        return ctx.slots.default?.()\n      }\n    }\n  },\n})\n"
  },
  {
    "path": "packages/sharelist-manage/src/views/disk/partial/meta/index.less",
    "content": ".file-meta{\n  display: flex;\n  align-items: center;\n\n  .item-thumb{\n    width:60px;height:40px;\n    border-radius: 6px;\n    background-size: cover;\n    background-position: center center;\n    background-repeat: no-repeat;\n  }\n\n  .item-icon{\n    position: relative;\n    width:60px;\n    margin-right:8px;\n    text-align: center;\n    flex:none;\n  }\n  .item-icon--lite{\n    width:40px;\n  }\n  .item-icon__ext{\n    position: absolute;\n    font-size: 12px;\n    bottom: 5px;\n    color:inherit;\n    text-transform: uppercase;\n    transform: scale(0.8);\n    transform-origin:center;\n    // width:42px;\n    text-align: center;\n    width:100%;\n    font-weight: bold;\n  }\n\n  .item-desc{\n    font-size:12px;\n    color:var(--context-2);\n  }\n  .item-dot{\n    display: inline-block;\n    padding: 0 7px;\n    align-items: center;\n    &:before{\n      content: \"\";\n      display: block;\n      width: 2px;\n      height: 2px;\n      background: var(--primary-text-secondary-color);\n    }\n  }\n}"
  },
  {
    "path": "packages/sharelist-manage/src/views/disk/partial/meta/index.tsx",
    "content": "import { defineComponent, withModifiers, ref } from \"vue\";\nimport Icon from '@/components/icon'\nimport useDisk from '../useDisk'\nimport { Spin, Badge } from 'ant-design-vue'\nimport Error from '../error'\nimport './index.less'\nimport Breadcrumb from '../breadcrumb'\n\nexport const Meta = defineComponent({\n  props: {\n\n    data: {\n      type: Object as PropType<IFile>\n    },\n    errorMode: {\n      type: Boolean\n    }\n  },\n  setup(props) {\n    return () => <div class=\"file-meta\">\n      <div class=\"item-icon\">\n        {\n          props.data?.thumb ?\n            <div class=\"item-thumb\" style={{ 'backgroundImage': `url(${props.data?.thumb})` }}></div>\n            : [<Icon\n              style={{ fontSize: '42px' }}\n              type={'icon-' + props.data?.mediaType}\n            />,\n            props.data?.mediaType == 'file' && props.data?.ext.length <= 7 ? <div class=\"item-icon__ext\">{props.data?.ext}</div> : null\n            ]\n        }\n      </div>\n\n      <div>\n        <div class>{props.data?.name}</div>\n        {\n          props.errorMode ?\n            <div class=\"flex item-desc\">{props.data?.error}</div> :\n            <div class=\"flex item-desc\">\n              {\n                props.data?.ctimeDisplay ? <span>{props.data?.ctimeDisplay}</span> : null\n              }\n              {\n                props.data?.size ? [<span class=\"item-dot\"></span>, <span>{props.data?.sizeDisplay}</span>] : null\n              }\n            </div>\n        }\n\n      </div>\n    </div>\n  }\n})\n\nexport const MetaLite = defineComponent({\n  props: {\n    data: {\n      type: Object as PropType<IFile>\n    },\n  },\n  setup(props) {\n    return () => {\n      let status = props.data?.status\n      return <div class=\"file-meta\">\n        <Badge status={status == 0 ? 'default' : status == 1 ? 'processing' : status == 2 ? 'success' : status == 3 ? 'error' : 'default'} />\n        <div class=\"item-icon item-icon--lite\">\n          <Icon\n            style={{ fontSize: '36px' }}\n            type={'icon-' + props.data?.mediaType}\n          />\n          {props.data?.mediaType == 'file' && props.data?.ext.length <= 7 ? <div class=\"item-icon__ext\">{props.data?.ext}</div> : null}\n        </div>\n\n        <div>\n          <div class>{props.data?.name}</div>\n          {\n            props.data?.error ?\n              <div class=\"flex item-desc\">{props.data?.error}</div> :\n              <div class=\"flex item-desc\">\n                {\n                  props.data?.ctimeDisplay ? [<span>{props.data?.ctimeDisplay}</span>, <span class=\"item-dot\"></span>] : null\n                }\n                {\n                  props.data?.size ? <span>{props.data?.sizeDisplay}</span> : null\n                }\n              </div>\n          }\n\n        </div>\n      </div>\n    }\n  }\n})\nexport const Tree = defineComponent({\n  props: {\n    dirMode: {\n      type: Boolean\n    },\n    treeStyle: {\n      type: Object\n    },\n    excludes: {\n      type: Array\n    }\n  },\n  emits: ['select'],\n  setup(props, ctx) {\n    const routeStacks = ref<Array<any>>([])\n\n    const { loading, files, error, setPath, paths, loadMore, diskConfig, id, current, onUpdate } = useDisk({\n      routeSlient: true,\n      new: true,\n      filter: (i: IFile) => {\n        return (i.type == 'folder' || i.type == 'drive') && !props.excludes?.includes(i.id)\n      }\n    })\n\n    const onSelect = (data: Partial<IFile>, append = false) => {\n\n      let route: Partial<IFile> = {}\n\n      if (append) {\n        let lastPath = (routeStacks.value[routeStacks.value.length - 1]?.path || '')\n        route.id = data.id\n        route.path = `${lastPath == '/' ? '' : lastPath}` + '/' + data.name\n        route.name = data.name\n        routeStacks.value.push(route)\n      } else {\n        let idx = routeStacks.value.findIndex((i: IFile) => i.path == data.path)\n        console.log(idx)\n        // remote routeStacks\n        if (idx < routeStacks.value.length - 1) {\n          route = routeStacks.value[idx]\n          routeStacks.value.splice(idx + 1)\n        }\n      }\n\n      setPath(route)\n\n      console.log(diskConfig.value)\n\n      ctx.emit('select', route, data.config || diskConfig.value)\n    }\n\n    onUpdate(() => {\n      if (diskConfig.isRoot) {\n        console.log('disabled')\n      }\n    })\n\n    const onTagClick = ({ path, index }: any = {}) => {\n      console.log('tag', index)\n      onSelect(routeStacks.value[index])\n    }\n\n    onSelect({ path: '', name: '' }, true)\n    return () => (\n      <div class=\"drive drive--lite\">\n        <div class=\"drive-header\">\n          <Breadcrumb onTagClick={onTagClick} size=\"sm\" paths={routeStacks.value.slice(1).map((i: any) => i.name)} />\n        </div>\n        <div class=\"drive-body-wrap\">\n\n          <Spin delay={150} spinning={loading.value}>\n            <Error value={error}>\n              <div style={{ 'overflow': 'auto', 'height': '350px', ...(props.treeStyle || {}) }} class={['drive-body', 'drive-body--list']} >\n                {files.value.map((i: IFile) => {\n                  return (\n                    <a class={[\"item\", i.config?.readonly ? 'item--disabled' : null]} title={i.name} onClick={withModifiers(() => onSelect(i, true), ['prevent'])}>\n                      <div class=\"item-icon\">\n                        {\n                          i.thumb ?\n                            <div class=\"item-thumb\" style={{ 'backgroundImage': `url(${i.thumb})` }}></div>\n                            : <Icon\n                              style={{ fontSize: '36px' }}\n                              type={'icon-' + i.mediaType}\n                            />\n                        }\n                      </div>\n\n                      <div class=\"item-meta\">\n                        <div class=\"item-name\">{i.name}</div>\n                      </div>\n                    </a>\n                  )\n                })}\n              </div>\n            </Error>\n          </Spin >\n        </div>\n      </div>\n    )\n  },\n})\n"
  },
  {
    "path": "packages/sharelist-manage/src/views/disk/partial/modifier/index.less",
    "content": ".modifier{\n  .modifier__footer{\n    padding:0 16px;\n    display: flex;\n    align-items: center;\n    justify-content: space-between;\n    width:100%;\n  }\n}"
  },
  {
    "path": "packages/sharelist-manage/src/views/disk/partial/modifier/index.tsx",
    "content": "import { ref, Ref, defineComponent, watchEffect, toRaw, reactive, UnwrapRef, watch } from 'vue'\nimport { useSetting } from '@/hooks/useSetting'\nimport { Switch, Modal, Input, Form, Button, Select, Tooltip } from 'ant-design-vue'\nimport { useObject } from '@/hooks/useHooks'\nimport { QuestionCircleOutlined } from '@ant-design/icons-vue'\nimport './index.less'\n\nconst { Item: FormItem, useForm } = Form\n\ntype FormState = {\n  protocol: string\n  name: string\n  [key: string]: string | undefined\n}\n\nconst getFitDriver = (protocol: string | undefined, drivers: Array<Driver>) => {\n  const hit = drivers.find((i) => i.protocol == protocol)\n  if (hit) {\n    return hit\n  }\n}\n\nconst convBoolean = (v: string) => {\n  return v === 'true' ? true : false\n}\n\nconst parseFields = (\n  fields: Array<DriverField>,\n  formState: FormState,\n  defaultValues: any,\n  formItemsNode: any = [],\n  innerRule: any = [],\n): any => {\n  for (const i of fields) {\n    formState[i.key] = i.value === undefined ? (i.type == 'boolean' ? convBoolean(defaultValues[i.key]) : defaultValues[i.key]) : i.value\n\n    if (i.required) {\n      innerRule[i.key] = [{ required: true, message: '必填项 / required' }]\n    }\n\n    if (i.options) {\n      formItemsNode.push(\n        <FormItem name={i.key}>\n          {{\n            label: () => <div class=\"flex\">{i.label}{i.help ? <Tooltip title={i.help}><QuestionCircleOutlined style={{ marginLeft: '8px', 'fontSize': '13px' }} /></Tooltip> : null} </div>,\n            default: () => <Select v-model={[formState[i.key], 'value']} options={i.options} />\n          }}\n        </FormItem>,\n      )\n      if (i.fields) {\n        const hitVal = formState[i.key]\n        const hitIndex = i.options?.findIndex((i) => i.value == hitVal) || 0\n        const hitField = (i.fields[hitIndex] as any) || []\n        // console.log(i.options, formState, hitVal, hitField, 'hitField')\n        parseFields(hitField, formState, defaultValues, formItemsNode, innerRule)\n      }\n    } else if (i.type == 'boolean') {\n      formItemsNode.push(\n        <FormItem name={i.key} >\n          {{\n            label: () => <div class=\"flex\">{i.label}{i.help ? <Tooltip title={i.help}><QuestionCircleOutlined style={{ marginLeft: '8px', 'fontSize': '13px' }} /></Tooltip> : null} </div>,\n            default: () => <Switch v-model={[formState[i.key], 'checked']} />\n          }}\n        </FormItem>,\n      )\n      if (i.fields) {\n        const isTrue = formState[i.key]\n        if (isTrue) {\n          parseFields(i.fields, formState, defaultValues, formItemsNode, innerRule)\n        }\n      }\n    } else if (i.type != 'hidden') {\n      formItemsNode.push(\n        <FormItem name={i.key}>\n          {{\n            label: () => <div class=\"flex\">{i.label}{i.help ? <Tooltip title={i.help}><QuestionCircleOutlined style={{ marginLeft: '8px', 'fontSize': '13px' }} /></Tooltip> : null} </div>,\n            default: () => <Input v-model={[formState[i.key], 'value']} />\n          }}\n        </FormItem>,\n      )\n    }\n  }\n  return [formItemsNode, innerRule]\n}\nexport default defineComponent({\n  props: {\n    defaultValue: {\n      type: Object,\n      required: true,\n    },\n  },\n  emits: ['update'],\n\n  setup(props, ctx) {\n    const { config } = useSetting()\n\n    const formRef = ref()\n\n    const drivers: Array<Driver> = [\n      ...config.drivers,\n    ]\n\n    // const [rules, clearRules] = useObject()\n    const driverTypes = drivers.map((i: Driver) => ({ value: i.protocol, label: i.name }))\n\n    const [formState, clearFormState]: [UnwrapRef<FormState>, any] = useObject({\n      name: props.defaultValue.name,\n      protocol: props.defaultValue.protocol,\n      id: props.defaultValue.id\n    })\n\n    const rules: Ref<any> = ref({})\n\n    const formItems: Ref<any> = ref([])\n\n    const onSave = () => {\n      formRef.value\n        .validate()\n        .then(() => {\n          const { name, id, protocol, ...config } = toRaw(formState)\n          ctx.emit('update', { name, id, protocol, config })\n        })\n        .catch((err: any) => {\n          console.log('error', err)\n        })\n    }\n\n    watch(\n      () => formState.protocol,\n      () => {\n        clearFormState(['protocol', 'name'])\n      },\n    )\n\n    watchEffect(() => {\n      const defaultValues = { ...toRaw(formState), ...props.defaultValue.config }\n\n      formRef.value?.clearValidate()\n\n      const innerRule = {\n        name: [{ required: true, message: '必填项 / required' }],\n        protocol: [{ required: true, message: '必填项 / required' }],\n      }\n\n      const formItemsNode = [\n        <FormItem label=\"挂载类型 / protocol\" name=\"protocol\">\n          <Select v-model={[formState.protocol, 'value']} options={driverTypes}></Select>\n        </FormItem>,\n        <FormItem label=\"挂载名称 / name\" name=\"name\">\n          <Input v-model={[formState.name, 'value']} />\n        </FormItem>,\n      ]\n\n      const driver = getFitDriver(formState.protocol, drivers)\n\n      if (driver?.guide?.length) {\n        const [nodes, rules] = parseFields(driver.guide, formState, defaultValues)\n        if (nodes) {\n          formItemsNode.push(...nodes)\n        }\n        if (rules) {\n          Object.assign(innerRule, rules)\n        }\n      }\n\n      formItems.value = formItemsNode\n      rules.value = innerRule\n    })\n\n    return () => (\n      <div class=\"modifier\">\n        <Form\n          ref={(el: any) => (formRef.value = el)}\n          class=\"fix-form--inline\"\n          layout=\"vertical\"\n          model={formState}\n          rules={rules.value}\n        >\n          {formItems.value}\n        </Form>\n        <div class=\"modifier__footer\">\n          {config.guide[formState.protocol] ? (\n            <a target=\"_blank\" href={config.guide[formState.protocol]}>\n              挂载向导\n            </a>\n          ) : <div></div>}\n          <Button type=\"primary\" onClick={onSave}>\n            确定\n          </Button>\n        </div>\n      </div>\n    )\n  },\n})\n"
  },
  {
    "path": "packages/sharelist-manage/src/views/disk/partial/search/index.less",
    "content": ".search-box{\n  position: absolute;\n}"
  },
  {
    "path": "packages/sharelist-manage/src/views/disk/partial/search/index.tsx",
    "content": "import { ref, defineComponent, watch, onMounted } from 'vue'\nimport { InputSearch, RadioGroup, Radio } from 'ant-design-vue'\nimport './index.less'\nimport useDisk from '../useDisk'\n\nexport default defineComponent({\n  emits: ['search'],\n  setup(props, ctx) {\n    const { diskConfig, setQuery } = useDisk()\n\n    const onSearch = (value: string) => {\n      if (value) {\n        // router.push({ path: router.currentRoute.value.path, query: { search: value } })\n        setQuery({ search: value })\n        ctx.emit('search')\n      }\n    }\n\n\n    const options: Array<any> = []\n\n    if (diskConfig.globalSearch) {\n      options.push({ label: '所有文件', value: 'global' })\n    }\n    if (diskConfig.localSearch) {\n      options.push({ label: '当前目录', value: 'local' })\n    }\n\n    const searchType = ref(options[0]?.value)\n    return () => <>\n      <InputSearch enter-button placeholder=\"搜索内容\" onSearch={onSearch} />\n      {options.length ? <RadioGroup style=\"margin-top:8px;\" options={options} value={searchType.value} onChange={(e) => searchType.value = e.target.value} name=\"radioGroup\" ></RadioGroup> : null}\n    </>\n  },\n})\n"
  },
  {
    "path": "packages/sharelist-manage/src/views/disk/partial/task/index.less",
    "content": ".task{\n  width: 375px;\n  // background-color:var(--context-background);\n  \n  border-radius: 4px;\n  // box-shadow: 0 6px 16px -8px rgb(0 0 0 / 8%), 0 9px 28px 0 rgb(0 0 0 / 5%), 0 12px 48px 16px rgb(0 0 0 / 3%);\n\n  .task-list{\n    max-height: 500px;\n    overflow-y:auto;\n  }\n  .ant-tabs-nav{\n    margin-bottom:0;\n  }\n  .ant-popover-inner-content{\n    padding:0;\n  }\n\n  .ant-tabs-tab-active{\n    .anticon {\n      color:var(--primary-color);\n    }\n  }\n  .ant-list-items{\n    min-height: 320px;\n  }\n  .item{\n    padding:12px;\n    font-size:13px;\n    display: flex;\n    align-items: center;\n    width: 100%;\n    position: relative;\n    border-bottom: 1px solid var(--divider-3);\n    .item__body{ \n      flex:auto;\n      overflow: hidden;\n     }\n    .item__head{ margin-right:8px;}\n    .item__foot{\n      // position: absolute;\n      // right: 12px;\n    }\n    .item__title{\n      width:100%;\n      display: flex;\n      align-items: center;\n      justify-content: space-between;\n      a{\n        color:var(--primary-text-color);\n      }\n\n      .item__title-link{\n        color:var(--context-3);\n      }\n    }\n    \n    .item__dot{\n      display: inline-block;\n      padding: 0 7px;\n      align-items: center;\n      &:before{\n        content: \"\";\n        display: block;\n        width: 2px;\n        height: 2px;\n        background: var(--primary-text-secondary-color);\n      }\n    }\n    .item__progress{\n      position: absolute;\n      left:0;\n      top:0;\n      height:100%;\n      transition: all 0.3s;\n      background-color: var(--primary-progress);\n      pointer-events:none;\n    }\n    .item-action{\n      margin-left: 16px;\n    }\n    .item-meta-description{\n      font-size:12px;\n      color:var(--context-3);\n      margin-top:5px;\n    }\n\n    .action{\n      display: flex;\n      visibility: hidden;\n    }\n    .action span{\n      display: flex;\n      cursor: pointer;\n      align-items: center;\n      font-size: 20px;\n      width: 24px;\n      height: 24px;\n      border-radius: 50%;\n      justify-content: center;\n      transition: background-color 0.3s;\n      span:hover{\n        background-color: #84858d14;\n      }\n    }\n\n    &:hover .action{\n      visibility: visible;\n    }\n\n    \n  }\n}\n\n.task-error{\n  height:260px;overflow-y:auto;\n  .error-item{\n    padding:6px 0;\n    font-size:12px;\n  }\n}"
  },
  {
    "path": "packages/sharelist-manage/src/views/disk/partial/task/index.tsx",
    "content": "import { defineComponent, onUnmounted } from \"vue\"\nimport { useApi } from '@/hooks/useApi'\nimport { useRequest } from '@/hooks/useRequest'\nimport { List, Badge, message, Tabs, Modal, Spin, Alert, Button } from 'ant-design-vue'\nimport { CloudSyncOutlined, FileSyncOutlined, DeleteOutlined, InfoCircleOutlined, PauseOutlined, ReloadOutlined, DoubleRightOutlined, CloseOutlined, DownloadOutlined } from '@ant-design/icons-vue'\nimport useDisk from '../useDisk'\nimport { byte, formatFile } from '@/utils/format'\nimport { useUpload } from '../upload'\nimport './index.less'\nimport { Meta, MetaLite } from '../meta'\n\nexport enum STATUS {\n  INIT = 1, //1 正在生成任务(解析文件)\n  INIT_ERROR = 2, //2 解析文件过程发生错误\n  PROGRESS = 3, //3 正在复制\n  SUCCESS = 4, //4 操作完成\n  DONE_WITH_ERROR = 5,//5.操作完成 但发生部分完成\n  ERROR = 6,//6 失败\n  PAUSE = 7,//已暂停\n}\n\ntype ITask = {\n  src: string\n  dest: string\n  id: string\n  // 1 正在生成任务(解析文件)，2 解析文件过程发生错误, 3 正在复制，4 操作完成 且未发生错误,5. 操作完成 但发生部分完成 \n  status: STATUS\n  index: number,\n  current: string\n  count: number\n  size: number\n  currentLoaded: number\n  completed: number\n  message?: string\n  readCompleted?: number,\n  speed: number,\n  error?: Array<any>,\n  [key: string]: any\n}\n\ntype TaskSet = {\n  move: Array<ITask>,\n  download: Array<ITask>,\n}\n\nexport default defineComponent({\n  setup(props, ctx) {\n    let request = useApi()\n    const { setPath } = useDisk()\n    const { tasks: uploadTasks, remove: removeUpload, pause: pauseUpload, resume: resumeUpload } = useUpload()\n\n    const { data, runAsync, loading, cancel } = useRequest<TaskSet>(async () => {\n      let res = await request.tasks()\n      return {\n        move: res.filter((i: ITask) => !i.srcId.startsWith('http')),\n        download: res.filter((i: ITask) => i.srcId.startsWith('http'))\n      }\n    }, {\n      pollingInterval: 1000,\n      immediate: true,\n      loadingDelay: 1000,\n      pollingWhenHidden: false\n    })\n\n\n    const navSrc = (i: ITask) => {\n      if (!i.srcId.startsWith('http')) {\n        // it's a file\n        if (i.count == 1) {\n          setPath({ path: '/' + i.src.split('/').slice(0, -1).join('/') })\n        } else {\n          setPath({ path: '/' + i.src })\n        }\n      }\n    }\n\n    const navDest = (i: ITask) => {\n      setPath({ path: '/' + i.dest })\n    }\n\n    const onResume = async (i: ITask) => {\n      let res = await request.resumeTask(i.id)\n      if (res.error) {\n        message.error(res.error.message)\n      } else {\n        message.success('操作成功')\n      }\n    }\n\n    const onPause = async (i: ITask) => {\n      let res = await request.pauseTask(i.id)\n      if (res.error) {\n        message.error(res.error.message)\n      } else {\n        message.success('操作成功')\n      }\n    }\n\n    const retry = async (taskId: string, modal: any) => {\n      let res = await request.retryTask(taskId)\n      if (res.error) {\n        message.error(res.error.message)\n      } else {\n        message.success('操作成功')\n        modal.destroy()\n      }\n    }\n\n    const onQuery = async (i: ITask) => {\n      let res = await request.task(i.id)\n      if (res.error && !res.id) {\n        message.error(res.error.message)\n      } else {\n        let files = res.files || [], error = res.error || [], index = res.index\n        // 0 waiting 1 progress 2 success 3 fail\n        files.forEach((i: IFile, idx: number) => {\n          if (error.includes(idx)) {\n            i.status = 3\n          } else {\n            i.status = idx < index ? 2 : idx > index ? 0 : 1\n          }\n        })\n\n        useShowFiles(files, i.id, files.some((i: IFile) => i.status == 3))\n      }\n    }\n\n    const useShowFiles = (files: Array<any>, taskId: string, showRetryButton?: boolean) => {\n      let data = files.map((i: IFile) => {\n        i.name = (i.dest ? `${i.dest}/` : '') + i.name\n        i.type = 'file'\n        i.ext = i.name.split('.').pop()\n        formatFile(i)\n        return i\n      })\n      const modal = Modal.confirm({\n        class: 'pure-modal pure-modal-hide-footer',\n        width: '500px',\n        closable: true,\n        title: () => <div>任务详情</div>,\n        content: (\n          <div>\n            <div class=\"modal\">\n              <div class=\"modal-body\">\n                <div class=\"task-error\">\n                  {\n                    data.map(i => <MetaLite class=\"error-item\" data={i} />)\n                  }\n                </div>\n              </div>\n              {\n                showRetryButton ? <div class=\"modal-footer\">\n                  <Button type=\"primary\" onClick={() => retry(taskId, modal)}>重试</Button>\n                </div> : null\n              }\n\n            </div>\n          </div>\n        ),\n      })\n    }\n\n    const queryUpload = async (i: ITask) => {\n      useShowFiles(i.files.filter((i: IFile) => i.message), i.id, false)\n    }\n\n    onUnmounted(() => {\n      cancel()\n    })\n\n    let { loading: removeLoading, run: remove } = useRequest(async (task: ITask) => {\n      let res = await request.removeTask(task.id)\n      if (res.error) {\n        message.error(res.error.message)\n      } else {\n        await runAsync()\n      }\n    })\n\n    let { loading: removeDownloadLoading, run: removeDownloadTask } = useRequest(async (task: ITask) => {\n      let res = await request.removeDownloadTask(task.id)\n      if (res.error) {\n        message.error(res.error.message)\n      } else {\n        await runAsync()\n      }\n    })\n    //<Progress size=\"small\" percent={Math.floor(100 * (i.completed + i.currentLoaded) / i.size)} />\n\n    const createTitle = (i: ITask, mid: string) => {\n      let src = i.src.split('/').pop()\n      let dest = i.dest.split('/').pop()\n      let attrs: Record<string, any> = {}\n      if (i.srcId.startsWith('http')) {\n        attrs.href = i.srcId\n        attrs.target = '_blank'\n      }\n      return <div>\n        <div class=\"item__title\">\n          <a onClick={() => navSrc(i)} class=\"ellipsis\" style=\"width:45%\" title={i.src} {...attrs}>{src}</a>\n          <span class=\"item__title-link\" style=\"margin:0 5px;\">{mid}</span>\n          <a onClick={() => navDest(i)} class=\"ellipsis\" style=\"width:30%\" title={'/' + i.dest}>{dest}</a>\n        </div>\n        <div class=\"item-meta-description\">\n          {\n            i.status == STATUS.INIT ?\n              `正在创建中${i.readCompleted ? ('已读取' + Math.floor(100 * (i.readCompleted || 0) / i.size) + '%') : ''}`\n              : (i.status == STATUS.INIT_ERROR || i.status == STATUS.ERROR) ? `失败${i.message ? (':' + i.message) : ''}`\n                : i.status == STATUS.SUCCESS ? <div class=\"flex\"><span>{byte(i.loaded)}</span><span class=\"item__dot\"></span>已完成</div> : i.status == STATUS.DONE_WITH_ERROR ? '已完成 存在部分错误'\n                  : <div class=\"flex\"><span>{byte(i.loaded + i.currentLoaded, 2)}/{byte(i.size, 2)}</span><span class=\"item__dot\"></span><span>{i.status == STATUS.PAUSE ? '已暂停' : `${byte(i.speed)}/S`}</span></div>\n          }\n        </div>\n        {\n          i.status == STATUS.PROGRESS && i.count > 1 ? <div class=\"item-meta-description ellipsis\">\n            当前第 {`${i.index + 1} / ${i.count}`} 项 {i.current}\n          </div> : null\n        }\n        {/* <div style=\"flex:0 0 auto;margin-left:6px;\">共 {i.count} 项</div> */}\n      </div>\n    }\n\n    const renderItem = ({ item: i, index }: { item: ITask, index: number }) => {\n      let progress = `${Math.floor(100 * i.progress)}%`\n      return <div class=\"item\">\n        {\n          (i.status == STATUS.PROGRESS || i.status == STATUS.PAUSE) ? <div class=\"item__progress\" style={{ width: progress }}></div> : null\n        }\n        <div class=\"item__head\"><Badge status={(i.status == STATUS.INIT || i.status == STATUS.PROGRESS) ? 'processing' : i.status == STATUS.SUCCESS ? 'success' : i.status == STATUS.ERROR ? 'error' : i.status == STATUS.DONE_WITH_ERROR ? 'warning' : 'default'} /></div>\n        {/* <div class=\"item__head\"><Badge status={(i.status == STATUS.INIT || i.status == STATUS.PROGRESS) ? 'processing' : i.status == STATUS.SUCCESS ? 'success' : i.status == STATUS.ERROR ? 'error' : i.status == STATUS.DONE_WITH_ERROR ? 'warning' : 'default'} /></div> */}\n        <div class=\"item__body\">{createTitle(i, '迁移至')}</div>\n        <div class=\"item__foot\">\n          <div class=\"action\">\n            <span>{\n              i.status == STATUS.PAUSE ? <ReloadOutlined onClick={() => onResume(i)} style={{ fontSize: '12px' }} /> :\n                i.status == STATUS.PROGRESS ? <PauseOutlined onClick={() => onPause(i)} style={{ fontSize: '12px' }} /> : null}\n            </span>\n            {\n              i.status != STATUS.PROGRESS ? <span style=\"margin-left:5px;\" onClick={() => onQuery(i)}><InfoCircleOutlined style={{ fontSize: '12px' }} /></span> : null\n            }\n            <span style=\"margin-left:5px;\" onClick={() => remove(i)}><CloseOutlined style={{ fontSize: '12px' }} /></span>\n          </div>\n        </div>\n      </div>\n    }\n\n    const renderUploadItem = ({ item: i, index }: { item: ITask, index: number }) => <div class=\"item\">\n      {\n        i.status == 3 ? <div class=\"item__progress\" style={{ width: `${Math.floor(100 * (i.loaded + i.currentLoaded) / i.size)}%` }}></div> : null\n      }\n      <div class=\"item__head\"><Badge status={(i.status == STATUS.INIT || i.status == STATUS.PROGRESS) ? 'processing' : i.status == STATUS.SUCCESS ? 'success' : i.status == STATUS.ERROR ? 'error' : i.status == STATUS.DONE_WITH_ERROR ? 'warning' : 'default'} /></div>\n      <div class=\"item__body\">{createTitle(i, '上传至')}</div>\n      <div class=\"item__foot\">\n        <div class=\"action\">\n          <span>{\n            i.status == STATUS.PAUSE ? <ReloadOutlined onClick={() => resumeUpload(i)} style={{ fontSize: '12px' }} /> :\n              i.status == STATUS.PROGRESS ? <PauseOutlined onClick={() => pauseUpload(i)} style={{ fontSize: '12px' }} /> :\n                (i.status == STATUS.ERROR || i.status == STATUS.DONE_WITH_ERROR) ? <InfoCircleOutlined onClick={() => queryUpload(i)} style={{ fontSize: '12px' }} /> : null}\n          </span>\n          <span style=\"margin-left:5px;\" onClick={() => removeUpload(i)}><CloseOutlined style={{ fontSize: '12px' }} /></span>\n        </div>\n      </div>\n    </div>\n\n    return () => <div class=\"task\" onClick={e => e.stopPropagation()}>\n      <Tabs centered>\n        <Tabs.TabPane key=\"move\" v-slots={{ tab: () => <div><CloudSyncOutlined />跨盘迁移</div> }}>\n          <Spin delay={3000} spinning={loading.value || removeLoading.value}>\n            <List class=\"task-list\" dataSource={data?.value?.move} renderItem={renderItem}></List>\n          </Spin>\n        </Tabs.TabPane>\n        <Tabs.TabPane key=\"download\" v-slots={{ tab: () => <div><DownloadOutlined />离线下载</div> }}>\n          <Spin delay={3000} spinning={loading.value || removeDownloadLoading.value}>\n            <List class=\"task-list\" dataSource={data?.value?.download} renderItem={renderItem}></List>\n          </Spin>\n        </Tabs.TabPane>\n        <Tabs.TabPane key=\"upload\" v-slots={{ tab: () => <div><FileSyncOutlined />文件上传</div> }}>\n          <List class=\"task-list\" dataSource={uploadTasks.value} renderItem={renderUploadItem}></List>\n        </Tabs.TabPane>\n      </Tabs>\n\n    </div>\n  }\n})\n"
  },
  {
    "path": "packages/sharelist-manage/src/views/disk/partial/upload/index.tsx",
    "content": "\nimport { defineComponent } from 'vue'\nimport { useApi, ReqResponse } from '@/hooks/useApi'\nimport { reactive, ref, Ref } from 'vue'\nimport SparkMD5 from 'spark-md5'\nimport useDisk from '../useDisk'\nimport { STATUS } from '../task'\nimport { message } from 'ant-design-vue'\nimport SHA1 from 'js-sha1'\n\nconst parseSize = (v: string) => {\n  if (!v) return { type: undefined, size: 0 }\n\n  let unitMap: Record<string, number> = { 'k': 1024, 'm': 1024 * 1024 }\n  let [_, type, num, unit] = v.toLowerCase().match(/(md5|sha1)?\\-?(\\d+)(k|m)?/i) || []\n  let size = num ? parseInt(num) : 0\n  if (unit && unitMap[unit]) {\n    size *= unitMap[unit]\n  }\n  return { type, size }\n}\ninterface readChunkedOptions {\n  onChunk(...rest: Array<any>): any\n  onFinish(...rest: Array<any>): any\n  chunkSize: number\n}\nfunction readChunked(file: File, { onChunk, onFinish, chunkSize }: readChunkedOptions) {\n  const fileSize = file.size\n\n  let offset = 0\n\n  const reader = new FileReader()\n  reader.onload = function () {\n    if (reader.error) {\n      console.log(reader.error)\n      onFinish(reader.error || {})\n      return\n    }\n    offset += (reader as any).result.byteLength\n    // offset += (reader as any).result.length\n    // return\n    // callback for handling read chunk\n    // TODO: handle errors\n    onChunk(reader.result, offset, fileSize)\n\n    if (offset >= fileSize) {\n      onFinish(null)\n      return\n    } else {\n      readNext()\n    }\n  }\n\n  reader.onerror = function (err) {\n    onFinish(err || {})\n  }\n\n  function readNext() {\n    const fileSlice = file.slice(offset, offset + chunkSize)\n    reader.readAsArrayBuffer(fileSlice)\n  }\n  readNext()\n}\n\n//3032ac4e71df951cccff8fdebad5266c\n// single hash, head hash, chunk hash\nexport const getHash = async (file: File, hashtype: string) =>\n  new Promise((resolve, reject) => {\n    let parts = hashtype.split('_')\n    let type = parts[0]\n    let [headHash, partsHash] = parts.slice(1).map(parseSize)\n    const hash = type == 'md5' ? new SparkMD5.ArrayBuffer() : type == 'sha1' ? SHA1.create() : null\n\n    let defaultChunkSize = 50 * 1024 * 1024\n\n    let ret: any = {\n      head: '',\n      parts: []\n    } //Array<string> = []\n\n    if (partsHash?.size) {\n      defaultChunkSize = partsHash.size\n    }\n\n    const onEnd = () => resolve(ret)\n\n    if (hash) {\n      readChunked(file, {\n        chunkSize: defaultChunkSize,\n        onChunk(chunk: any, offset: number) {\n          // content hash update\n          if (type == 'md5') {\n            hash.append(chunk)\n          } else {\n            hash.update(chunk)\n          }\n\n          // console.log(ret.head, headHash)\n          // head hash\n          if (headHash?.size && !ret.head) {\n            let headHashType = headHash.type || type\n            if (headHashType) {\n              ret.head = SparkMD5.ArrayBuffer.hash(chunk.slice(0, headHash.size))\n            } else if (headHashType == 'sha1') {\n              ret.head = SHA1(chunk.slice(0, headHash.size))\n            }\n          }\n\n          // parts hash\n          if (partsHash?.size) {\n            let partsHashType = partsHash.type || type\n\n            if (partsHashType == 'md5') {\n              ret.parts.push(SparkMD5.ArrayBuffer.hash(chunk))\n            } else if (partsHashType == 'sha1') {\n              ret.parts.push(SHA1(chunk))\n            }\n          }\n        },\n        onFinish(err: Error) {\n          if (err) {\n            reject(err)\n          } else {\n            // TODO: Handle errors\n            // const final = hash.finalize()\n            // resolve(final.toString(CryptoJS.enc.Hex))\n\n            const final = type == 'md5' ? hash.end() : hash.hex()\n            ret[type] = final\n            onEnd()\n          }\n        }\n      }\n      )\n    }\n  })\n\ntype IUseUpload = {\n  (): any\n  instance?: any\n}\n\ninterface IUseUploadResult {\n  create(): Promise<void>\n}\n\nexport const useUpload: IUseUpload = (): IUseUploadResult => {\n  if (useUpload.instance) {\n    return useUpload.instance\n  }\n  const { reload, current } = useDisk()\n\n  const tasks: Ref<Array<any>> = ref([])\n\n  // 1 正在生成任务(解析文件/读取文件 .etc)，2 解析文件过程发生错误, 3 正在复制，4 操作完成 且未发生错误,5. 操作完成 但发生部分完成\n  const create = (files: Array<File>, hashType: string, dest: string, id: string, dir: boolean = false) => {\n    const src = dir ? files[0].webkitRelativePath.split('/')[0] : files[0].name\n\n    const size = files.reduce((t, c) => t + c.size, 0)\n\n    const task: Record<string, any> = {\n      id: '' + Date.now(),\n      count: files.length,\n      status: STATUS.INIT,\n      completed: 0,\n      currentCompleted: 0,\n      current: '',\n      readCompleted: 0,\n      size,\n      src,\n      srcId: 'local:' + src,\n      dest,\n      destId: id,\n      index: 0,\n      speed: 0,\n      hashType,\n      files: files.map(i => ({\n        name: i.name,\n        size: i.size,\n        dest: i.webkitRelativePath.split('/').slice(0, -1).filter(Boolean).join('/'),\n        file: i\n      })),\n      error: []\n    }\n\n    tasks.value.push(task)\n    createTransferTask(task.id)\n  }\n\n\n  const createTransferTask = async (taskId: string) => {\n    let idx = tasks.value.findIndex((i: any) => i.id == taskId)\n    const request = useApi()\n\n    if (tasks.value[idx].status == STATUS.PROGRESS) {\n      return\n    }\n\n    tasks.value[idx].status = STATUS.PROGRESS\n\n    while (tasks.value[idx].index < tasks.value[idx].files.length) {\n      let task = tasks.value[idx]\n      let { index, files, hashType, destId } = task\n\n      let { file, hash, dest, taskId } = files[index]\n\n      if (!hash && hashType) {\n        hash = await getHash(file, hashType)\n        console.log('hash:', hash)\n        files[index].hash = hash\n      }\n\n      const controller = new AbortController()\n\n      let lastTime = 0, lastDataCount = 0\n      let formData: Record<string, any> = {\n        id: destId,\n        size: file.size,\n        name: file.name,\n        hash,\n        hash_type: hashType,\n      }\n\n      tasks.value[idx].cancel = () => controller.abort()\n      tasks.value[idx].status = STATUS.PROGRESS\n      try {\n\n        //创建/查询任务\n        let taskData = await request.fileCreateUpload({ ...formData, dest, task_id: taskId })\n\n        //快速上传\n        if (taskData.completed) {\n          tasks.value[idx].completed += file.size\n          tasks.value[idx].currentCompleted = 0\n          tasks.value[idx].index = index + 1\n          tasks.value[idx].current = file.name\n          continue\n        }\n\n        //! 任务ID不存在，直接标记失败\n        if (!taskData.taskId) {\n          tasks.value[idx].status = STATUS.INIT_ERROR\n          if (taskData.error) {\n            tasks.value[idx].message = taskData.error.message\n            files[index].message = taskData.error.message\n          }\n\n          console.log('here')\n          throw taskData.error\n        }\n\n        //任务ID可能发生变化（如过期 导致原始的上传实例失效，后端会尝试生成新的上传实例，此时uploadId也会随之变化）\n        tasks.value[idx].taskId = taskData.taskId\n        files[index].taskId = taskData.taskId\n\n        let uploadFile = file\n\n        //续传\n        if (taskData.start) {\n          uploadFile = file.slice(taskData.start)\n          tasks.value[idx].currentCompleted = taskData.start\n        }\n\n        tasks.value[idx].current = file.name\n\n        let lastTime: number = Date.now(), lastLoaded = 0\n        let res = await request.fileUpload({\n          ...formData,\n          taskId: files[index].taskId,\n          create: 0,\n          stream: uploadFile,\n          slice_size: formData.size - taskData.start,\n          customRequest: (params: any) => {\n            params.timeout = 0\n            params.onUploadProgress = (progressEvent: any) => {\n              let ts = Date.now()\n              if (ts - lastTime > 1000) {\n                tasks.value[idx].speed = (progressEvent.loaded - lastLoaded) * 1000 / (ts - lastTime)\n                lastLoaded = progressEvent.loaded\n                lastTime = ts\n                console.log(tasks.value[idx].speed)\n              }\n              tasks.value[idx].currentCompleted = taskData.start + progressEvent.loaded\n\n            }\n            params.signal = controller.signal\n\n            //return upload(params)\n          },\n        })\n\n        if (res.error) {\n          console.log(res)\n\n          //abord\n          if (res.error.code == 'ERR_CANCELED') {\n            return\n          } else {\n            tasks.value[idx].status = STATUS.INIT_ERROR\n          }\n        } else {\n          tasks.value[idx].status = STATUS.SUCCESS\n        }\n      } catch (e) {\n        console.log('error==>', e)\n        if (!tasks.value[idx].error.includes(index)) {\n          tasks.value[idx].error.push(index)\n        }\n      }\n\n      tasks.value[idx].completed += file.size\n      tasks.value[idx].currentCompleted = 0\n      tasks.value[idx].index++\n    }\n\n    message.success(`${tasks.value[idx].src} 已完成`)\n\n    //finish\n    if (tasks.value[idx].index >= tasks.value[idx].files.length) {\n      tasks.value[idx].status = tasks.value[idx].error.length > 0 ? (tasks.value[idx].error.length == tasks.value[idx].count ? STATUS.ERROR : STATUS.DONE_WITH_ERROR) : STATUS.SUCCESS\n\n      // update page\n      console.log(current?.path, tasks.value[idx])\n      if (current?.path && current.path == tasks.value[idx].dest) {\n        reload()\n      }\n\n      return\n    }\n\n  }\n\n  const remove = async (task: any) => {\n    let hit = tasks.value.find((i: any) => i.id == task.id)\n    if (hit) {\n      tasks.value.splice(hit, 1)\n    }\n  }\n\n  const pause = async (task: any) => {\n    const request = useApi()\n\n    let idx = tasks.value.findIndex((i: any) => i.id == task.id)\n    if (idx == -1) {\n      message.error('没有此任务')\n    } else {\n      const res = await request.fileUploadCancel(tasks.value[idx].taskId)\n\n      tasks.value[idx].cancel()\n      tasks.value[idx].status = STATUS.PAUSE\n    }\n  }\n\n  const resume = async (task: any) => {\n    let hit = tasks.value.find((i: any) => i.id == task.id)\n    if (!hit) {\n      message.error('没有此任务')\n    } else {\n      createTransferTask(hit.id)\n    }\n  }\n  useUpload.instance = {\n    create,\n    tasks,\n    remove,\n    resume,\n    pause\n  }\n  return useUpload.instance\n}\n\n\nexport const Upload = defineComponent({\n  props: {\n    type: String,\n    disabled: {\n      type: Boolean,\n      default: false\n    }\n  },\n  setup(props, { slots }) {\n    const file = ref()\n    const { diskConfig, paths } = useDisk()\n    const request = useApi()\n\n    const { create } = useUpload()\n\n    const onOpenFileDialog = () => {\n      console.log('is open', props.disabled, file.value)\n      if (props.disabled) {\n        return\n      }\n      file.value?.click?.()\n    }\n\n    const onChange = async (e: any) => {\n      let { uploadHash, id } = diskConfig.value\n      let files = [].slice.call(e.target.files)\n      let dest = '/' + [...paths.value].join('/')\n      // console.log(uploadHash)\n      if (uploadHash) {\n        // let path = '/' + [...paths.value, file.name].join('/')\n        // console.log(file, paths)\n        await create(files, uploadHash, dest, id, props.type == 'dir')\n      } else {\n        await create(files, null, dest, id, props.type == 'dir')\n      }\n\n      //clear select\n      if (file.value) {\n        file.value.value = ''\n      }\n    }\n\n    let inputProps: Record<string, any> = props.type == 'dir' ? { webkitdirectory: true, directory: true, multiple: true } : {}\n    return () => <div onClick={onOpenFileDialog}>\n      <input {...inputProps} type=\"file\" onChange={onChange} ref={file} accept=\"\" onClick={e => e.stopPropagation()} capture={true} style=\"display:none;\" />\n      {slots.default?.()}\n    </div>\n\n  }\n})"
  },
  {
    "path": "packages/sharelist-manage/src/views/disk/partial/useDisk.ts",
    "content": "import { useApi, ReqResponse } from '@/hooks/useApi'\nimport { ref, Ref, watch, reactive, computed, inject, provide, getCurrentInstance, InjectionKey } from 'vue'\nimport { byte, getFileType, time, formatFile } from '@/utils/format'\nimport { useLocalStorageState } from '@/hooks/useLocalStorage'\nimport { message } from 'ant-design-vue'\nimport { useRouter, useRoute, onBeforeRouteUpdate } from 'vue-router'\n\ninterface Handler {\n  (): any\n}\n\nconst useFolderAuth = () => {\n  const data = useLocalStorageState<Record<string, any>>('auth', {})\n  const hasAuth = (path?: string) => path && !!data.value[path]\n  const addAuth = (path: string, v: any) => {\n    data.value[path] = v\n  }\n\n  const getAuth = (path?: string) => {\n    return path ? data.value[path] : undefined\n  }\n\n  const removeAuth = (path: string) => {\n    delete data.value[path]\n  }\n\n  //create a auth chain.\n  const geneAuth = (path = '') => {\n    const paths = path.split('/')\n    const ret: Record<string, string> = {}\n    for (let i = 0; i < paths.length; i++) {\n      const cur = paths.slice(0, i + 1).join('/')\n      if (data.value[cur]) {\n        const hit = data.value[cur]\n        ret[hit[0]] = hit[1]\n      }\n    }\n    return ret\n  }\n  return { hasAuth, addAuth, getAuth, geneAuth, removeAuth }\n}\n\ntype IUseDiskOption = {\n  id?: string\n  new?: boolean\n  routeSlient?: boolean\n  base?: string\n  filter?: (i: IFile) => boolean\n}\ntype IUseDisk = {\n  (options?: IUseDiskOption): any\n  [key: string]: any\n}\ntype IQuery = {\n  id?: string\n  name?: string\n  path?: string\n  search?: string\n  orderBy?: string\n  auth?: Record<string, string>\n}\n\ntype DiskState = {\n  id?: string\n  path?: string\n  search?: string\n  nextPage?: string\n  orderBy?: string\n}\n\ntype IUseDiskAction = any\n\nexport const diskSymbol = Symbol() as InjectionKey<IUseDiskAction>\nconst useDisk: IUseDisk = (diskOptions: IUseDiskOption = { base: '/drive/folder' }): any => {\n  if (inject(diskSymbol)) {\n    return inject(diskSymbol)\n  }\n\n  const request = useApi()\n  const router = useRouter()\n  const files: Ref<Array<any>> = ref([])\n  const loading = ref(false)\n  const error = reactive({ code: 0, message: '', scope: {} })\n  const diskConfig: Ref<any> = ref({})\n  const current = <DiskState>reactive({ id: undefined, path: undefined, search: '', nextPage: '', orderBy: '' })\n  const sortOption = useLocalStorageState<Record<string, any>>('ordeyBy', { key: 'name', type: 'asc' })\n\n  const { addAuth, removeAuth, geneAuth } = useFolderAuth()\n\n  const basePath = diskOptions.base || ''\n  const paths = computed(() => {\n    const ret: Array<string> = current.path?.substring(1).split('/').filter(Boolean) || []\n    if (current.search) {\n      ret.push(`${current.search} 的搜索结果`)\n    }\n    return ret\n  })\n\n  let controller: AbortController\n\n  const getFiles = async (options: IQuery = {}, clear = false): Promise<any> => {\n    const stateChange = options.path != current.path || !!options.search || (!options.search && current.search)\n\n    loading.value = true\n\n    const params: Record<string, any> = {\n      id: options.id,\n      path: options.path,\n      order_by: sortOption.value.key + ' ' + sortOption.value.type,\n    }\n\n    const auth: Record<string, string> = geneAuth(options.path)\n\n    if (options.auth) {\n      auth[options.auth.id] = options.auth.token\n    }\n\n    if (options.search) {\n      params.search = options.search\n    }\n    params.auth = auth\n\n    if (current.nextPage) {\n      params.next_page = current.nextPage\n    }\n\n    const usePage = !!params.next_page\n\n    controller = new AbortController()\n    params.customRequest = (p: any) => {\n      p.signal = controller.signal\n    }\n\n    const res: ReqResponse = await request.files(params)\n    if (res.error) {\n      error.code = res.error.code\n      error.message = res.error.message as string\n      //校验\n      if (error.code == 401) {\n        if (error.message) {\n          message.error(error.message)\n        }\n        if (params.auth) {\n          removeAuth(params.path)\n        }\n        if (res.error?.scope) {\n          //多重目录校验\n          if (res.error.scope.id != options.id) {\n            const cur: ReqResponse = await request.filePath({ id: res.error.scope.id })\n            const path = cur.map((i: IFile) => i.name).join('/')\n            error.scope = { ...res.error.scope, path }\n          } else {\n            error.scope = { id: options.id, path: options.path }\n          }\n          // 目录校验通过 需要保存\n          if (options.auth && options.auth.id != res.error.scope.id) {\n            addAuth('/' + options.auth.path, [options.auth.id, options.auth.token])\n          }\n          current.id = options.id\n          current.path = options.path\n        }\n      } else {\n        current.path = options.path\n        current.id = options.id\n        error.message = res.error.message || ''\n      }\n    } else {\n      if (res.files) {\n        formatFile(res.files)\n        if (clear) {\n          files.value = []\n        }\n\n        if (params.next_page) {\n          let appendData = res.files as Array<any>\n          if (diskOptions?.filter) {\n            appendData = appendData.filter(diskOptions?.filter)\n          }\n          files.value.push(...appendData)\n        } else {\n          files.value = diskOptions?.filter ? res.files.filter(diskOptions?.filter) : res.files\n        }\n      }\n\n      current.nextPage = res.nextPage\n\n      diskConfig.value = Object.assign(res.config || {}, { id: res.id })\n\n      error.code = 0\n      error.message = ''\n\n      // save/update\n      if (options.auth) {\n        addAuth('/' + options.auth.path, [options.auth.id, options.auth.token])\n      }\n    }\n\n    current.search = options.search || undefined\n    current.id = options.id\n    current.path = options.path\n\n    if (diskOptions?.routeSlient !== true && !usePage && stateChange) {\n      const target = options.path || ''\n\n      let url = (basePath + target).replace(/\\/+/g, '/')\n      if (current.search) {\n        url += '?search=' + current.search\n        // only support global search\n        if (diskConfig.value.search == 1) {\n          url = basePath + '/' + diskConfig.value.drive + '?search=' + current.search\n          current.path = '/' + diskConfig.value.drive\n        }\n      }\n\n      router.push(url)\n    }\n\n    loading.value = false\n\n    updateHandlers.forEach((cb: Handler) => cb())\n  }\n\n  const setPath = async ({ ...options }: IQuery = {}, reload = false, next = false) => {\n    if (options.path) {\n      options.path = options.path.replace(/\\/{2,}/g, '/')\n    }\n\n    const isSameQuery = options.path == current.path && options.search == current.search\n\n    if (isSameQuery && !options.auth && !reload) return\n\n    if (options.search) {\n      current.nextPage = undefined\n    }\n    controller?.abort()\n\n    loading.value = true\n\n    if (options.path != current.path) current.nextPage = ''\n\n    if (options.id && !options.path) {\n      const resp = await request.filePath({ id: options.id })\n      options.path = '/' + resp.map((i: IFile) => i.name).join('/')\n    }\n\n    getFiles(options, true)\n  }\n\n  const setAuth = (auth: Record<string, string>) => {\n    setPath({ auth, id: current.id, path: current.path })\n  }\n\n  const setSort = (key: string) => {\n    if (key === sortOption.value.key) {\n      sortOption.value.type = sortOption.value.type == 'asc' ? 'desc' : 'asc'\n    } else {\n      sortOption.value.key = key\n      sortOption.value.type = 'asc'\n    }\n    //全部加载完毕时\n    setPath({ id: current.id, path: current.path, search: current.search }, true)\n  }\n\n  const loadMore = () => {\n    console.log('====> load more', current.id, current.path)\n    if (loading.value || !current.nextPage) {\n      return\n    }\n    getFiles({\n      id: current.id,\n      path: current.path,\n      search: current.search,\n    })\n  }\n\n  const reload = () => {\n    setPath({ id: current.id, path: current.path, search: current.search }, true)\n  }\n  const mutate = (file: IFile, isRemove = false) => {\n    const idx = files.value.findIndex((i) => i.id == file.id)\n    if (idx >= 0) {\n      if (isRemove) {\n        files.value.splice(idx, 1)\n      } else {\n        files.value.splice(idx, 1, file)\n      }\n    } else {\n      files.value.unshift(formatFile(file))\n    }\n  }\n\n  const updateHandlers: Array<Handler> = []\n  const onUpdate = (cb: Handler) => {\n    const cancel = () => {\n      const idx = updateHandlers.indexOf(cb)\n      updateHandlers.splice(idx, 1)\n    }\n    updateHandlers.push(cb)\n    return cancel\n  }\n\n  const instance = {\n    setPath,\n    files,\n    paths,\n    loading,\n    error,\n    reload,\n    loadMore,\n    diskConfig,\n    mutate,\n    setAuth,\n    current,\n    setSort,\n    sortConfig: sortOption,\n    onUpdate,\n  }\n\n  provide(diskSymbol, instance)\n\n  return instance\n}\n\nexport default useDisk\n"
  },
  {
    "path": "packages/sharelist-manage/src/views/general/index.less",
    "content": ".page--setting {\n  // max-width:960px;\n\n  padding:32px;\n  .setting-drive__header {\n    text-align: right;\n  }\n\n  .item {\n    border-bottom: 1px solid #e8e8e8;\n    padding: 16px 8px;\n    display: flex;\n    align-items: center;\n\n    &:hover{\n      background-color: var(--context-hover);\n    }\n    .item__header {\n      flex: 1 1 auto;\n      display: flex;\n      align-items: center;\n    }\n\n    .item__icon {\n      margin-right: 8px;\n    }\n\n    .item__meta {\n      flex: 1 1 auto;\n    }\n\n    .item__meta-title {\n      color: var(--primary-text-color);\n\n      font-size: 14px;\n      line-height: 22px;\n      margin-bottom: 0;\n    }\n\n    .item__meta-desc {\n      color: var(--context-2);\n\n      font-size: 14px;\n      line-height: 22px;\n      margin-top: 4px;\n      a{\n        color:rgba(0,0,0,.85);\n        padding:0 8px;\n      }\n    }\n\n\n    .item-action a {\n      padding: 0 8px;\n    }\n\n    &.item--plugin{\n      .item__meta-desc {\n        color: rgba(0, 0, 0, .8);\n        font-size: 12px;\n        line-height: 18px;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "packages/sharelist-manage/src/views/general/index.tsx",
    "content": "import { ref, defineComponent, watch, onMounted, toRef, toRefs, reactive } from 'vue'\nimport { RadioGroup, Radio, message, Tooltip, Textarea } from 'ant-design-vue'\nimport { SaveOutlined, ImportOutlined, QuestionCircleOutlined, LoadingOutlined } from '@ant-design/icons-vue'\nimport { useSetting, ConfigFieldItem } from '@/hooks/useSetting'\nimport { Switch, Modal, Input, InputNumber, Alert, Tabs } from 'ant-design-vue'\nimport './index.less'\n\n\nconst valueDisplay = (value: any, type: string) => {\n  if (type == 'boolean') {\n    return Boolean(value) ? '启用' : '禁用'\n  }\n  else if (type == 'array') {\n    const len = value.length\n    const nodes = value.slice(0, 3).map((i: string) => <div>{i}</div>)\n    if (len > 3) {\n      nodes.push(<div>等{len}项</div>)\n    }\n    return nodes\n  }\n  else if (type == 'textarea') {\n    return value ? '已设置' : '未设置'\n  } else {\n    return value\n  }\n}\n\nconst StateLabel = defineComponent({\n  props: {\n    loading: {\n      type: Boolean\n    }\n  },\n  setup(props, { slots }) {\n    return () => props.loading ? <LoadingOutlined /> : slots.default?.()\n  }\n})\nexport default defineComponent({\n  setup() {\n\n    const { config, setConfig, exportConfig, configFields } = useSetting()\n\n    const state: Record<string, boolean> = reactive({})\n\n    const readFile = (e: any) => {\n      var reader = new FileReader(); //这是核心,读取操作就是由它完成.\n      reader.readAsText(e.target.files[0]); //读取文件的内容,也可以读取文件的URL\n      reader.onload = function () {\n        //当读取完成后回调这个函数,然后此时文件的内容存储到了result中,直接操作即可\n        try {\n          let data = JSON.parse(reader.result as string)\n          setConfig(data)\n        } catch (e) {\n          message.error('无法读取到配置信息')\n        }\n      }\n    }\n\n    const saveConfig = (code: string, val: any) => {\n      state[code] = true\n      setConfig({ [code]: val }).then(() => {\n        delete state[code]\n      })\n    }\n    const createInputModifier = ({ label, code, secret, type, help, handler, validator }: ConfigFieldItem) => {\n      const modifier = ref(secret ? '' : config[code])\n      const handleChange = (e: any) => modifier.value = e.target.value\n      const handleChangeValue = (e: unknown) => modifier.value = e as number\n\n      let lastVal = modifier.value\n      //modal 下的input v-model 有bug\n      Modal.confirm({\n        title: label,\n        class: 'pure-modal',\n        content: (\n          <div>\n            {\n              help ? <Alert message={help} type=\"info\" show-icon style=\"font-size:12px;margin-bottom:8px;\" /> : null\n            }\n            {\n              type == 'number' ?\n                <InputNumber defaultValue={modifier.value} onChange={handleChangeValue} style=\"width:100%;\" placeholder=\"请输入\" /> :\n                <Textarea defaultValue={modifier.value} onChange={handleChange} placeholder=\"请输入\" />\n            }\n\n          </div>\n        ),\n        onOk: () => {\n          if (validator) {\n            if (!validator(modifier.value, lastVal)) {\n              message.error('不符合要求')\n              return Promise.reject()\n            }\n          }\n          saveConfig(code, modifier.value)\n          handler?.(modifier.value, lastVal)\n        },\n      })\n    }\n\n    const createListModifier = (label: string, code: string) => {\n      const modifier = ref(config[code].join('\\n'))\n      const handleChange = (e: any) => modifier.value = e.target.value\n\n      Modal.confirm({\n        title: label,\n        class: 'pure-modal',\n        content: (\n          <div>\n            <Textarea defaultValue={modifier.value} onChange={handleChange} style={{ height: '150px' }} placeholder=\"请输入\" />\n          </div>\n        ),\n        onOk: () => {\n          saveConfig(code, modifier.value.split('\\n').filter(Boolean))\n        },\n      })\n    }\n\n    const createOptionModifier = (label: string, code: string) => {\n      console.log(label, code, config[code])\n      const modifier = ref(config[code])\n      const handleChange = (e: any) => {\n        modifier.value = e.target.value\n      }\n      const options = config[`${code}_options`]\n      Modal.confirm({\n        title: label,\n        class: 'pure-modal',\n        content: () => (\n          <div>\n            <RadioGroup value={modifier.value} onChange={handleChange}>\n              {\n                options.map((i: any) => <Radio style={{ display: 'block' }} value={i}>{i}</Radio>)\n              }\n            </RadioGroup>\n          </div>\n        ),\n        onOk: () => {\n          saveConfig(code, modifier.value)\n        },\n      })\n    }\n\n    return () => (\n      <div class=\"page page--setting\">\n        <div class=\"page__header\">\n          <div>设置</div>\n          <div class=\"page__action\">\n            <div style=\"display:flex;align-items:center;\">\n              <a style=\"cursor:pointer;font-size:12px;color:#666;\" title=\"保存配置 / Save config\" onClick={exportConfig} ><SaveOutlined style={{ fontSize: '15px', 'marginRight': '4px' }} />导出</a>\n              <a style=\"cursor:pointer;font-size:12px;color:#666;margin-left:12px;position:relative;\" title=\"导入配置 / Import config\"><ImportOutlined style={{ fontSize: '15px', 'marginRight': '4px' }} />导入<input type=\"file\" style=\"opacity:0;position:absolute;top:0;left:0;width:100%;height:100%;\" name=\"file\" id=\"file\" onChange={readFile} /></a>\n            </div>\n          </div>\n        </div>\n        <div class=\"setting__body\">\n          <Tabs >\n            {\n              configFields.value.map((i, idx) => <Tabs.TabPane key={idx}>\n                {{\n                  default: () => i.children.map((i) => (\n                    <div class=\"item\">\n                      <div class=\"item__header\">\n                        <div class=\"item__meta\">\n                          <h4 class=\"item__meta-title\">{i.label}{i.help ? <Tooltip title={i.help}><QuestionCircleOutlined style={{ marginLeft: '8px', 'fontSize': '13px' }} /></Tooltip> : null} </h4>\n                          <div class=\"item__meta-desc\">{i.secret ? '' : valueDisplay(config[i.code], i.type)}</div>\n                        </div>\n                      </div>\n                      <div class=\"item-action\">\n                        {i.type == 'boolean' ? (\n                          <Switch checked={config[i.code]} loading={state[i.code]} onChange={(e) => saveConfig(i.code, e)} />\n                        ) : i.type == 'string' || i.type == 'textarea' ? (\n                          <StateLabel loading={state[i.code]}><a onClick={() => createInputModifier(i)}>修改</a></StateLabel>\n                        ) : i.type == 'number' ? (\n                          <StateLabel loading={state[i.code]}><a onClick={() => createInputModifier(i)}>修改</a></StateLabel>\n                        ) : i.type == 'array' ? (\n                          <StateLabel loading={state[i.code]}><a onClick={() => createListModifier(i.label, i.code)}>修改</a></StateLabel>\n                        ) : i.type == 'option' ? (\n                          <StateLabel loading={state[i.code]}><a onClick={() => createOptionModifier(i.label, i.code)}>修改</a></StateLabel>\n                        ) : null}\n                      </div>\n                    </div>\n                  )),\n                  tab: () => (\n                    <span>{i.title}</span>\n                  ),\n                }}\n              </Tabs.TabPane>)\n            }\n          </Tabs>\n        </div>\n      </div>\n    )\n  },\n})\n"
  },
  {
    "path": "packages/sharelist-manage/src/views/home/index.less",
    "content": "\n.layout{\n  display: flex;\n  height: 100vh;\n  // background: var(--background_secondary);\n\n    // padding:30px;\n  // border-radius: 8px;\n  // box-sizing: border-box;\n  // background: rgb(222,225,231);\n  // overflow:hidden;\n  .layout__sider{\n    border-right:1px solid var(--divider-2);\n    \n  }\n\n  .layout__content{\n    flex:1 1 auto;\n    overflow-y: auto;\n    // padding:32px 0;\n  }\n}\n\n"
  },
  {
    "path": "packages/sharelist-manage/src/views/home/index.tsx",
    "content": "import Sider from '../../components/sider'\nimport { RouterView } from 'vue-router'\nimport { defineComponent } from 'vue'\nimport { useSetting } from '@/hooks/useSetting'\nimport Signin from '../signin'\n\nimport './index.less'\nimport MediaPlayer, { usePlayer } from '@/components/player'\n\nexport default defineComponent({\n\n  setup() {\n    const { loginState } = useSetting()\n    const { id } = usePlayer('player')\n\n    return () => (\n      loginState.value == 1 ?\n        <div class=\"layout\">\n          <div class=\"layout__sider\"><Sider /></div>\n          <div class=\"layout__content\"><RouterView /></div>\n          <div class=\"widget\">\n            <MediaPlayer meidaId={id} />\n          </div>\n        </div> : loginState.value == 2 ?\n          <Signin />\n          : null\n    )\n  }\n})"
  },
  {
    "path": "packages/sharelist-manage/src/views/plugin/index.less",
    "content": ".page--plugin{\n  padding:32px;\n\n  .page-content{\n    display: grid;\n    grid-template-columns: repeat(auto-fill, minmax(360px, 1fr));\n    grid-gap: 16px;\n  }\n\n  .item {\n    // border-bottom: 1px solid var(--divider-3);\n    // background:#fafcfe;\n    padding: 16px;\n    display: flex;\n    // align-items: center;\n\n    transition: all 0.25s;\n    &:hover{\n      background-color: var(--context-hover);\n    }\n    .item__header {\n      flex: 1 1 auto;\n      display: flex;\n      // align-items: center;\n    }\n\n    .item__icon {\n      width:42px;height:42px;\n      background-repeat: no-repeat;\n      background-size: contain;\n      background-position: center center;\n      margin-right: 12px;\n      color:var(--context-2);\n      text-align: center;\n      flex:none;\n    }\n\n    .item__meta {\n      flex: 1 1 auto;\n    }\n\n    .item__meta-head{\n      color:var(--context-1);\n\n      font-size: 12px;\n      line-height: 22px;\n    }\n\n    .item__meta-title {\n      color: var(--primary-text-color);\n      font-size: 14px;\n      line-height: 22px;\n      margin-bottom: 0;\n      font-weight: 600;\n    }\n\n    .item__meta-desc {\n      color:var(--context-2);\n\n      font-size: 12px;\n      line-height: 1.6em;\n      margin-top: 4px;\n      a{\n        color: var(--primary-text-color);\n        padding-right:8px;\n      }\n    }\n\n    \n    .item-action{\n      opacity: 0;\n      transition: all 0.3s;\n      flex:none;\n    }\n\n    &:hover,&--aciton-visible{\n      .item-action{\n        opacity: 1;\n      }\n    } \n    .item-action a {\n      padding: 0 8px;\n    }\n\n  }\n\n  \n}"
  },
  {
    "path": "packages/sharelist-manage/src/views/plugin/index.tsx",
    "content": "import { ref, defineComponent, watch, onMounted, toRef, toRefs, reactive, watchEffect } from 'vue'\nimport Icon from '@/components/icon'\nimport { useSetting } from '@/hooks/useSetting'\nimport { Button, Modal, Popconfirm, Tooltip, Dropdown, Menu, Empty } from 'ant-design-vue'\nimport { PlusOutlined, DeleteOutlined, EditOutlined, LoadingOutlined, HomeOutlined, SyncOutlined, AppstoreOutlined, EllipsisOutlined } from '@ant-design/icons-vue'\nimport CodeEditor from '@/components/code-editor'\nimport './index.less'\nimport Store from './partial/store'\n\nconst defaultNewPlugin = `//===Sharelist===\n// @name         插件名  e.g. NewSharelistPlugin\n// @namespace    命名空间 用于区分插件。e.g. https://new.sharelist.plugin\n// @version      版本号 e.g. 1.0.0\n// @license      协议 e.g. MIT\n// @description  描述\n// @author       作者\n// @supportURL   插件主页\n// @updateURL    插件更新地址\n// @icon         插件图标URL 支持base64\n//===/Sharelist==`\nexport default defineComponent({\n  setup() {\n    const { config, getPlugin, setPlugin, removePlugin, upgradePlugin } = useSetting()\n\n    const update = (data: IPlugin) => {\n      let newData = ''\n\n      const show = (input: string) => {\n        Modal.confirm({\n          class: 'pure-modal',\n          title: data.name,\n          width: '720px',\n          closable: true,\n          content: (\n            <div>\n              <CodeEditor style=\"max-height:65vh;\" defaultValue={input} onUpdate={(data: string) => newData = data} />\n            </div>\n          ),\n          onOk: () => setPlugin(data.id, newData),\n        })\n      }\n\n      if (data.id) {\n        getPlugin(data.id).then((res: any) => {\n          show(res)\n        })\n      } else {\n        show(defaultNewPlugin)\n      }\n\n    }\n\n    const remove = (data: IPlugin, idx: number) => {\n      Modal.confirm({\n        title: '移除插件',\n        content: `确认删除 ${data.name}？`,\n        onOk() {\n          removing[data.id] = true\n\n          removePlugin(data.id).then(() => {\n            delete removing[data.id]\n          })\n        },\n        onCancel() {\n        }\n      })\n\n    }\n\n    const installing: Record<string, boolean> = reactive({})\n\n    const removing: Record<string, boolean> = reactive({})\n\n    const upgrade = (data: IPlugin) => {\n      installing[data.id] = true\n\n      upgradePlugin(data.id).then((res: any) => {\n        delete installing[data.id]\n      })\n    }\n\n    const onAction = ({ key }: { key: any }) => {\n      if (key == 'store') {\n        const modal = Modal.confirm({\n          class: 'pure-modal pure-modal-hide-footer',\n          width: '890px',\n          closable: true,\n          title: () => <div><AppstoreOutlined style={{ fontSize: '18px', marginRight: '8px' }} />插件中心</div>,\n          maskClosable: false,\n          content: <Store />\n        })\n      } else {\n        update({ id: '', name: '新建插件' })\n      }\n    }\n    return () => (\n\n      <div class=\"page page--plugin\">\n        <div class=\"page__header\">\n          <div>插件</div>\n          <div class=\"page__action\">\n            <Dropdown overlayClassName=\"dropdown--drive\" trigger={['click']}>\n              {{\n                default: () => <PlusOutlined style={{ fontSize: '18px', marginLeft: '16px' }} />,\n                overlay: () => (\n                  <Menu onClick={onAction}>\n                    <Menu.Item class=\"dropdown-item\" key=\"store\" ><AppstoreOutlined style={{ fontSize: '18px', marginRight: '8px' }} />插件中心</Menu.Item>\n                    <Menu.Item class=\"dropdown-item\" key=\"create\"><EditOutlined style={{ fontSize: '18px', marginRight: '8px' }} />新建插件</Menu.Item>\n                  </Menu>\n                )\n              }}\n            </Dropdown>\n          </div>\n        </div>\n        <div>\n          {config.plugins.length == 0 ? <div class=\"page_empty\">\n            <Empty style={{ height: '60vh' }}>\n              {{\n                description: () => <div><a onClick={() => onAction({ key: 'new' })}>创建插件</a><span style=\"margin:0 8px;\">或者</span><a onClick={() => onAction({ key: 'store' })}>从插件中心安装</a></div>\n              }}\n            </Empty>\n          </div> : <div class=\"page-content\">{config.plugins?.map((i: IPlugin, idx: number) => (\n            <div class={['item', 'item--plugin', (installing[i.id] || removing[i.id]) ? 'item--aciton-visible' : null]}>\n              <div class=\"item__header\">\n                {\n                  i.icon ?\n                    <div class=\"item__icon\" style={{ backgroundImage: `url(${i.icon})` }}></div> :\n                    <Icon class=\"item__icon\" style={{ fontSize: '36px', color: '#888' }} type=\"icon-puzzle\" />\n                }\n                <div class=\"item__meta\">\n                  <div class=\"item__meta-head\">\n                    <div class=\"flex flex--between\">\n                      <h4 class=\"item__meta-title\">{i.name}</h4>\n                      <div class=\"item-action\">\n                        {i.supportURL && (i.supportURL as string).startsWith('http') ? <Tooltip title=\"主页\"><a target=\"_blank\" href={i.supportURL as string}><HomeOutlined /></a></Tooltip> : null}\n                        {i.updateURL ? <Tooltip title=\"更新\"><a onClick={() => upgrade(i)}><SyncOutlined spin={installing[i.id]} /></a></Tooltip> : null}\n                        <a onClick={() => update(i)}><EditOutlined style={{ fontSize: '15px' }} /></a>\n                        <a onClick={() => remove(i, idx)}>{removing[i.id] ? <LoadingOutlined style={{ fontSize: '15px' }} /> : <DeleteOutlined class=\"danger-aciton--hover\" style={{ fontSize: '15px' }} />}</a>\n                      </div>\n                    </div>\n                    {i.version ? <span>Version {i.version}</span> : null}\n                  </div>\n                  <div class=\"item__meta-desc ellipsis-2\">\n                    {i.description}\n                    {/* {i.supportURL && (i.supportURL as string).startsWith('http') ? <Tooltip title=\"主页\"><a target=\"_blank\" href={i.supportURL as string} style=\"padding:0 8px;\"><HomeOutlined /></a></Tooltip> : null}\n                    {i.license ? <a class=\"flex\">\n                      <Icon style={{ fontSize: '13px', 'marginRight': '5px' }} type=\"icon-license\" />\n                      {i.license}\n                    </a> : null} */}\n                  </div>\n                  {/* <div class=\"item__meta-desc flex\" style=\"align-items:center;margin-top:16px;\">\n                    <ul class=\"split\">\n                      {i.supportURL && (i.supportURL as string).startsWith('http') ? <Tooltip title=\"主页\"><li><a target=\"_blank\" href={i.supportURL as string}><HomeOutlined /></a></li></Tooltip> : null}\n\n                      {i.license ? <li><a class=\"flex\" style=\"margin-left:8px;\">\n                        <Icon style={{ fontSize: '12px', marginRight: '3px' }} type=\"icon-license\" />\n                        {i.license}\n                      </a></li> : null}\n                    </ul>\n                  </div> */}\n\n                </div>\n              </div>\n\n            </div>\n          ))}</div>}\n        </div>\n      </div>\n    )\n  },\n})\n"
  },
  {
    "path": "packages/sharelist-manage/src/views/plugin/partial/store/index.less",
    "content": ".plugin-store{\n  display: grid;\n  grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));\n  grid-gap: 12px;\n  height: 50vh;\n  overflow-y: auto;\n  .plugin-item{\n    display: flex;\n    padding:8px;\n    // justify-content: sp;\n    align-items: center;\n  }\n  .plugin-item__install{\n    font-size:12px;\n    padding:6px 0;\n    background:var(--background-cover);\n    color:var(--primary-text-color);\n    font-weight: 600;\n    border-radius: 3px;\n    width:90px;\n    text-align: center;\n    cursor: pointer;\n    transition: all 0.3s;\n    &.plugin-item__install--checked,&:hover{\n      background:var(--primary-color-bg);\n      color:var(--primary-color);\n    }\n  }\n  .plugin-item-content{\n    flex:1 1 auto;\n  }\n  .plugin-item-icon{\n    width:42px;height:42px;\n    background-repeat: no-repeat;\n    background-size: contain;\n    background-position: center center;\n    margin-right: 12px;\n    color:var(--context-2);\n    text-align: center;\n  }\n  .item-hd{\n    font-size:15px;\n    color:var(--primary-text-color);\n  }\n  .plugin-item__name{\n    color:var(--primary-text-color);\n    font-weight: 600;\n    font-size:15px;\n    margin:0.8em 0;\n  }\n  .item-bd{\n    color:var(--context-2);\n    font-size:12px;\n    margin-bottom:8px;\n  }\n  .item-ft{\n    display: flex;\n    font-size:12px;\n    line-height: 1.6em;\n    a{\n      color:var(--context-2);\n      margin-right:6px;\n    }\n  }\n}"
  },
  {
    "path": "packages/sharelist-manage/src/views/plugin/partial/store/index.tsx",
    "content": "import { ref, defineComponent, watch, onMounted, toRef, toRefs, reactive, watchEffect, Ref, computed } from 'vue'\nimport Icon from '@/components/icon'\nimport { useSetting } from '@/hooks/useSetting'\nimport { Button, Modal, Popconfirm, Tooltip, Dropdown, Menu, message, Spin } from 'ant-design-vue'\nimport { PlusOutlined, DeleteOutlined, EditOutlined, LoadingOutlined, HomeOutlined, SyncOutlined, AppstoreOutlined, CheckOutlined } from '@ant-design/icons-vue'\nimport CodeEditor from '@/components/code-editor'\nimport './index.less'\nimport { useApi } from \"@/hooks/useApi\";\nimport { useRequest } from '@/hooks/useRequest'\n\n\nexport default defineComponent({\n  setup() {\n    const request = useApi()\n    const { config } = useSetting()\n\n    const installingList: Record<string, boolean> = reactive({})\n    const { reloadConfig } = useSetting()\n    let { loading, run, data } = useRequest(async () => {\n      let res = await request.pluginStore()\n      if (res.error) {\n        message.error(res.error.message)\n      } else {\n        console.log(res)\n        return res\n      }\n    }, { immediate: true })\n\n    let { loading: installing, run: install } = useRequest(async (i) => {\n      installingList[i.namespace] = true\n      let res = await request.installPlugin({ url: i.updateURL })\n      delete installingList[i.namespace]\n      if (res.error) {\n        message.error(res.error.message)\n      } else {\n        message.success('安装成功')\n        reloadConfig()\n      }\n    })\n\n    const installed = computed(() => {\n      const ret: Record<string, boolean> = {}\n      config.plugins.forEach((i: any) => {\n        ret[i.namespace] = true\n      })\n      return ret\n    })\n    return () =>\n      <Spin delay={150} spinning={loading.value}>\n        <div class=\"plugin-store\">\n          {\n            data?.value?.map((i: any) => <div class=\"plugin-item\" >\n\n\n              <div class=\"plugin-item-content\" >\n\n                <div class=\"item-hd flex flex--between\">\n                  {\n                    i.icon ? <div class=\"plugin-item-icon\" style={{ backgroundImage: `url(${i.icon})` }}>\n                    </div> : <div class=\"plugin-item-icon\"><Icon type=\"icon-puzzle\" style={{ fontSize: '32px', 'color': '#aaa' }} /></div>\n                  }\n                  {\n                    installed.value[i.namespace] ?\n                      <div class=\"plugin-item__install  plugin-item__install--checked\" title={i.namespace}><CheckOutlined style={{ marginRight: '8px' }} />已安装</div> :\n                      installingList[i.namespace] ? <div class=\"plugin-item__install  plugin-item__install--checked\"><LoadingOutlined style={{ marginRight: '8px' }} />安装中</div> : <div class=\"plugin-item__install\" onClick={() => install(i)}>安装</div>\n                  }\n                </div>\n                <div class=\"item-bd\">\n                  <div class=\"plugin-item__name\">{i.name}</div>\n                  <div class=\"ellipsis-2\">{i.description}</div>\n                </div>\n                <div class=\"item-ft\">\n                  <ul class=\"split\">\n                    {i.supportURL && (i.supportURL as string).startsWith('http') ? <Tooltip title=\"主页\"><li><a target=\"_blank\" href={i.supportURL as string}><HomeOutlined /></a></li></Tooltip> : null}\n\n                    {i.license ? <li><a class=\"flex\" style=\"margin-left:8px;\">\n                      <Icon class=\"item__icon\" style={{ fontSize: '12px', marginRight: '3px' }} type=\"icon-license\" />\n                      {i.license}\n                    </a></li> : null}\n                    {i.version ? <li><span style=\"margin-left:8px;\">v{i.version}</span></li> : null}\n                  </ul>\n\n                </div>\n              </div>\n            </div>)\n          }\n        </div >\n      </Spin>\n  }\n})"
  },
  {
    "path": "packages/sharelist-manage/src/views/signin/index.less",
    "content": ".page-signin {\n\n\n  .page-signin-wrap{\n    position: fixed;\n    top: 45%;\n    left: 50%;\n    transform: translate(-50%, -50%);\n    width: 320px;\n  }\n  .page-logo{\n    position: absolute;\n    top:24px;left:24px;\n    font-size: 18px;\n    color:var(--primary-text-color);\n    font-weight: 600;\n    display: flex;\n    align-items: center;\n    line-height: 1em;\n    // &:before{\n    //   content:'';\n    //   width:10px;height: 10px;\n    //   background: var(--primary-text-color);\n    //   border-radius: 5px;\n    //   display: block;\n    //   margin-right:5px\n    // }\n  }\n  .page-signin__input {\n    padding: 8px 11px;\n  }\n\n  .page-signin__btn {\n    width: 100%;\n    margin-top: 36px;\n    height: 42px;\n  }\n\n  .page-header{\n    font-size: 18px;\n    color:var(--primary-text-color);\n    text-align:center;\n    margin-bottom: 32px;\n    font-weight: 600;\n  }\n}"
  },
  {
    "path": "packages/sharelist-manage/src/views/signin/index.tsx",
    "content": "import { ref, defineComponent } from 'vue'\nimport { Button, Input } from 'ant-design-vue'\nimport { useSetting } from '@/hooks/useSetting'\nimport './index.less'\n\nexport default defineComponent({\n  setup() {\n    const { getConfig } = useSetting()\n\n    const token = ref('')\n    const onEnter = () => {\n      getConfig(token.value)\n    }\n\n    return () => (\n      <div class=\"page-signin\">\n        <div class=\"page-logo\">\n          <span>sharelist</span>\n        </div>\n        <div class=\"page-signin-wrap\">\n          <div class=\"page-header\">登录 / Sign In</div>\n\n          <Input\n            class=\"page-signin__input\"\n            type=\"password\"\n            value={token.value}\n            onChange={(e) => (token.value = e.target.value)}\n            placeholder=\"输入口令\"\n            onPressEnter={onEnter}\n          />\n          <Button class=\"page-signin__btn\" type=\"primary\" onClick={onEnter}>\n            确定\n          </Button>\n        </div>\n      </div>\n    )\n  },\n})\n"
  },
  {
    "path": "packages/sharelist-manage/src/views/test/index.vue",
    "content": "<template>\n  <h1>Home</h1>\n  <nav><router-link to=\"/plugin\">plugin</router-link> |</nav>\n</template>\n\n<script lang=\"ts\">\nimport { ref } from 'vue'\nimport { onBeforeRouteLeave } from 'vue-router'\nimport { RouterLink } from 'vue-router'\nexport default {\n  setup() {\n    const promptExit = ref(true)\n    onBeforeRouteLeave((to, from, next) => {\n      console.log(to)\n      next()\n    })\n\n    return { promptExit }\n  },\n}\n</script>\n"
  },
  {
    "path": "packages/sharelist-manage/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"esnext\",\n    \"module\": \"esnext\",\n    \"moduleResolution\": \"node\",\n    \"strict\": true,\n    \"jsx\": \"preserve\",\n    \"sourceMap\": true,\n    \"resolveJsonModule\": true,\n    \"esModuleInterop\": true,\n    \"importHelpers\": true,\n    \"skipLibCheck\": true,\n    \"allowSyntheticDefaultImports\": true,\n    \"baseUrl\": \".\",\n    \"experimentalDecorators\": true,\n    \"noImplicitThis\": true,\n    \"lib\": [\n      \"esnext\",\n      \"dom\",\n      \"dom.iterable\",\n      \"scripthost\"\n    ],\n    \"types\": [\n      // \"vite/client\"\n    ],\n    \"paths\": {\n      \"@/*\": [\n        \"src/*\"\n      ]\n    },\n  },\n  \"include\": [\n    \"src/**/*.ts\",\n    \"src/**/*.d.ts\",\n    \"src/**/*.tsx\",\n    \"src/**/*.vue\",\n    \"tests/**/*.ts\",\n    \"tests/**/*.tsx\"\n  ],\n  \"exclude\": [\n    \"node_modules\"\n  ]\n}"
  },
  {
    "path": "packages/sharelist-manage/vite.config.ts",
    "content": "import { defineConfig } from 'vite'\nimport vueJsx from '@vitejs/plugin-vue-jsx'\nimport legacy from '@vitejs/plugin-legacy'\nimport path from 'path'\nimport { AntDesignVueResolver } from 'unplugin-vue-components/resolvers'\nimport Components from 'unplugin-vue-components/vite'\n\nconst root = path.resolve(__dirname, './src')\n\nexport default defineConfig({\n  root,\n  base: '',\n  resolve: {\n    alias: [{ find: '@', replacement: root }],\n    extensions: ['.js', '.ts', '.jsx', '.tsx', '.vue', '.json', '.less', '.css'],\n  },\n  build: {\n    outDir: path.join(__dirname, 'dist'),\n    sourcemap: false,\n    emptyOutDir: true,\n    assetsDir: '',\n    minify: 'esbuild',\n    reportCompressedSize: false,\n  },\n  css: {\n    preprocessorOptions: {\n      less: {\n        javascriptEnabled: true,\n        modifyVars: {\n          'preprocess-custom-color': 'green',\n        },\n      },\n    },\n  },\n  server: {\n    port: +process.env.PORT || 3000,\n    proxy: {\n      '/api': {\n        target: 'http://127.0.0.1:33001/',\n        changeOrigin: true,\n      },\n    },\n  },\n\n  plugins: [\n    vueJsx(),\n    // legacy({\n    //   targets: ['defaults', 'not IE 11'],\n    // }),\n    Components({\n      resolvers: [\n        AntDesignVueResolver({\n          importStyle: true,\n        }),\n      ],\n    }),\n  ],\n  optimizeDeps: {\n    exclude: [],\n  },\n})\n"
  },
  {
    "path": "packages/sharelist-web/.eslintrc.js",
    "content": "module.exports = {\n  parser: 'vue-eslint-parser',\n  parserOptions: {\n    // set script parser\n    parser: '@typescript-eslint/parser', // Specifies the ESLint parser\n    ecmaVersion: 2021, // Allows for the parsing of modern ECMAScript features\n    sourceType: 'module', // Allows for the use of imports\n    ecmaFeatures: {\n      jsx: true, // Allows for the parsing of JSX\n    },\n    validate: [],\n  },\n  extends: [\n    'plugin:vue/vue3-recommended',\n    'plugin:@typescript-eslint/recommended', // Uses the recommended rules from the @typescript-eslint/eslint-plugin\n    'plugin:prettier/recommended',\n  ],\n  rules: {\n    // Place to specify ESLint rules. Can be used to overwrite rules specified from the extended configs\n    // e.g. \"@typescript-eslint/explicit-function-return-type\": \"off\",\n    '@typescript-eslint/no-unused-vars': 'off',\n    '@typescript-eslint/no-explicit-any': 'off',\n    '@typescript-eslint/no-empty-function': 'off',\n\n    // 'object-curly-spacing': ['error', 'always'],\n  },\n}\n"
  },
  {
    "path": "packages/sharelist-web/.gitignore",
    "content": "node_modules\n.DS_Store\ndist\ndist-ssr\n*.local\nyarn-error.log\npackage-lock.json\nyarn.lock"
  },
  {
    "path": "packages/sharelist-web/.prettierrc.js",
    "content": "// https://prettier.io/docs/en/configuration.html\nmodule.exports = {\n  //分号终止符\n  semi: false,\n\n  //行尾逗号\n  trailingComma: \"all\",\n\n  // 使用单引号, 默认false(在jsx中配置无效, 默认都是双引号)\n  singleQuote: true,\n\n  printWidth: 120,\n  // 换行符\n  endOfLine: \"auto\",\n\n  //缩进 default:2\n  tabWidth: 2\n}"
  },
  {
    "path": "packages/sharelist-web/CHANGELOG.md",
    "content": ""
  },
  {
    "path": "packages/sharelist-web/README.md",
    "content": "# @sharelist/web [![npm](https://img.shields.io/npm/v/@sharelist/web.svg)](https://npmjs.com/package/@sharelist/web)\n\nIt's a part for sharelist\n\n## Useage\n"
  },
  {
    "path": "packages/sharelist-web/package.json",
    "content": "{\n  \"name\": \"@sharelist/web\",\n  \"version\": \"0.2.0\",\n  \"scripts\": {\n    \"dev\": \"vite\",\n    \"build\": \"vite build\",\n    \"changelog\": \"conventional-changelog -p angular -i CHANGELOG.md -s --commit-path .\",\n    \"release\": \"node ../../scripts/release.js --skipBuild --skipNpmPublish\"\n  },\n  \"dependencies\": {\n    \"@ant-design/icons-vue\": \"^6.0.1\",\n    \"ant-design-vue\": \"3.x\",\n    \"axios\": \"^0.21.0\",\n    \"pinia\": \"^2.0.18\",\n    \"pinia-plugin-persist\": \"^1.0.0\",\n    \"plyr\": \"^3.6.8\",\n    \"vue\": \"3.x\",\n    \"vue-router\": \"4.x\"\n  },\n  \"devDependencies\": {\n    \"@types/node\": \"^15.9.0\",\n    \"@typescript-eslint/eslint-plugin\": \"^4.23.0\",\n    \"@typescript-eslint/parser\": \"^4.23.0\",\n    \"@vitejs/plugin-legacy\": \"^2.x\",\n    \"@vitejs/plugin-vue\": \"^2.x\",\n    \"@vitejs/plugin-vue-jsx\": \"^2.x\",\n    \"@vue/compiler-sfc\": \"^3.0.5\",\n    \"eslint\": \"^7.26.0\",\n    \"eslint-config-prettier\": \"^8.3.0\",\n    \"eslint-plugin-prettier\": \"^3.4.0\",\n    \"eslint-plugin-vue\": \"^7.9.0\",\n    \"less\": \"^4.1.1\",\n    \"minimist\": \"^1.2.5\",\n    \"prettier\": \"^2.3.0\",\n    \"typescript\": \"^4.1.3\",\n    \"vite\": \"3.x\",\n    \"vue-eslint-parser\": \"^7.6.0\",\n    \"vue-tsc\": \"^0.0.24\"\n  }\n}"
  },
  {
    "path": "packages/sharelist-web/src/App.tsx",
    "content": "import { defineComponent } from 'vue'\nimport { ConfigProvider } from 'ant-design-vue'\nimport { RouterView } from 'vue-router'\nimport zhCN from 'ant-design-vue/es/locale/zh_CN';\nexport default defineComponent({\n  setup() {\n    return () => (\n      <ConfigProvider locale={zhCN}>\n        <RouterView />\n      </ConfigProvider>\n    )\n  },\n})\n"
  },
  {
    "path": "packages/sharelist-web/src/assets/style/index.less",
    "content": "// @import './icon.less';\n@import './var.less';\n\n@import 'ant-design-vue/lib/style/variable.less';\n@import 'ant-design-vue/lib/button/style/index-pure.less';\n@import 'ant-design-vue/lib/radio/style/index-pure.less';\n@import 'ant-design-vue/lib/breadcrumb/style/index-pure.less';\n@import 'ant-design-vue/lib/input/style/index-pure.less';\n@import 'ant-design-vue/lib/checkbox/style/index-pure.less';\n@import 'ant-design-vue/lib/message/style/index-pure.less';\n@import 'ant-design-vue/lib/modal/style/index-pure.less';\n@import 'ant-design-vue/lib/spin/style/index-pure.less';\n@import 'ant-design-vue/lib/empty/style/index-pure.less';\n@import 'ant-design-vue/lib/popover/style/index-pure.less';\n@import 'ant-design-vue/lib/alert/style/index-pure.less';\n@import 'ant-design-vue/lib/tooltip/style/index-pure.less';\n@import 'ant-design-vue/lib/image/style/index-pure.less';\n@import 'ant-design-vue/lib/radio/style/index-pure.less';\n@import 'ant-design-vue/lib/dropdown/style/index-pure.less';\n\n\n@font-face {\n  font-family: 'Source Code Pro';\n  font-style: normal;\n  font-weight: 400;\n  src: local('Source Code Pro Regular'), local('SourceCodePro-Regular'), url(data:application/x-font-woff2;charset=utf-8;base64,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) format('woff2');\n  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;\n}\n\n\nbody{\n  -webkit-app-region: drag;\n  // padding:5px;\n  background: var(--background);\n  color:var(--primary-text-color);\n  font-size:14px;\n  font-family: 'Source Code Pro';\n}\n\nh1,h2,h3,h4,h5,h6{\n  color:var(--primary-text-color);\n}\nul,ol{\n  padding-left:0;\n}\n.no-drag,input,button{\n  -webkit-app-region: no-drag;\n}\n\n.flex{\n  display: flex;\n  align-items: center;\n  \n  &.flex--between{\n    justify-content: space-between;\n  }\n  &.flex--block{\n    width:100%;\n  }\n}\n::-webkit-scrollbar-track {\n  // box-shadow: inset 0 0 1px rgba(0,0,0,0.2);\n  background-color: #eee;\n}\n\n::-webkit-scrollbar {\n  width: 10px;\n  // background-color: rgba(200,200,200,1);\n}\n::-webkit-scrollbar-thumb {\n  background-color: rgba(200,200,200,.6);\n}\n\n.pure-modal{\n \n  .ant-modal-content{ border-radius: 5px;}\n  .ant-modal-body{ padding:20px;}\n  .anticon-exclamation-circle{ display: none;}\n  .ant-modal-confirm-title{\n    // border-bottom:1px solid #ddd;\n    padding-bottom: 12px;\n    // width: fit-content\n  }\n  .ant-modal-confirm-content{\n    margin-left:0 !important;\n  }\n\n  .modal-header{\n    padding:16px;\n  }\n\n  .modal-footer{\n    margin-top:16px;\n    text-align: right;\n  }\n\n  &.pure-modal-hide-footer{\n    .ant-modal-confirm-btns{\n      display: none;\n    }\n\n  \n  }\n}\n.hide-modal{\n  display: none;\n}\n\n.fix-modal--alone{\n  .ant-modal-body{ padding:16px;}\n  .ant-modal-confirm-btns,.anticon-exclamation-circle{ display: none;}\n  .ant-modal-confirm-content{\n    margin-top: 0;\n  }\n  .ant-modal-content{\n    border-radius: 6px;\n  }\n}\n.fix-form--inline{\n  display: flex;\n  flex-wrap: wrap;\n  .ant-form-item{\n    flex:0 0 50%;padding:0 16px;\n\n    &.fix-form-item--foot{\n      flex:100%;\n      margin-bottom: 0;\n    }\n  }\n}\n\n.ant-dropdown-menu{\n  background-color:var(--context-background);\n  .ant-dropdown-menu-item:hover, .ant-dropdown-menu-submenu-title:hover{\n    background-color: var(--context-hover);\n  }\n  .ant-dropdown-menu-item-group-title{\n    color: var(--context-3);\n  }\n}\n.ant-checkbox-inner{\n  background-color:var(--context-background);\n}\n.ant-select-dropdown{\n  background-color:var(--context-background);\n  .ant-select-item{\n    color:var(--primary-text-color);\n  }\n  .ant-select-item-option-active:not(.ant-select-item-option-disabled){\n    background-color: var(--context-hover);\n  }\n}\n.ant-select{\n  color:var(--primary-text-color);\n}\n.ant-select:not(.ant-select-customize-input) .ant-select-selector{\n  border-color:var(--divider-3);\n  background-color:var(--divider-3);\n}\n\n.ant-alert-info{\n  border-color:var(--primary-color);\n  background-color:var(--primary-color-bg);\n  border-radius: 8px;\n  .ant-alert-message{\n    // color:var(--primary-color);\n    color: var(--context-3);\n   \n  }\n}\n\n.ant-popover-inner,.ant-popover-arrow-content,.ant-modal-content{\n  background-color:var(--context-background);\n}\n.ant-tabs,.ant-modal-confirm-body .ant-modal-confirm-title,.ant-modal-confirm-body .ant-modal-confirm-content{\n  color:var(--primary-text-color);\n}\n.ant-tabs-top>.ant-tabs-nav:before{\n  border-color:var(--divider-2);\n}\n\n.ant-list-empty-text,.ant-empty-normal{\n  color:var(--primary-text-color);\n}\n.ant-badge,.ant-radio-wrapper{\n  color:var(--context-1);\n}\n.ant-switch{\n  background-color:var(--context-3);\n}\n.ant-switch-checked {\n  background-color: var(--ant-primary-color);\n}\n\n.ant-input,.ant-input-number{\n  background-color:var(--divider-3);\n  border-color:var(--divider-2);\n  color:var(--primary-text-color);\n\n}\n\n.ant-input-affix-wrapper{\n  background-color:var(--divider-3);\n  border-color:var(--divider-2);\n  color:var(--primary-text-color);\n  .ant-input{\n    background-color: transparent;\n  }\n  \n  .ant-input-suffix,.ant-input-password-icon{\n    color:var(--primary-text-color);\n  }\n}\n.ant-message-notice-content{\n  background-color:var(--divider-3);\n  color:var(--primary-text-color);\n}\n.ant-form-item-label>label{\n  color:var(--primary-text-color);\n}\n.ant-progress-text{\n  color:var(--primary-text-color);\n}\n\n.sl-input{\n  border-radius: 6px;\n  box-shadow: 0 1px 5px rgb(0 0 0 / 12%);\n  border-color:transparent;\n}\n\n.popover-padding-0{\n  .ant-popover-inner-content{ padding:0;}\n}\n\n\n.ellipsis-2{\n  overflow : hidden;\n\tdisplay: -webkit-box; \n\t-webkit-box-orient: vertical; \n\t-webkit-line-clamp: 2; \n\ttext-overflow: ellipsis; \n  word-break: break-all;\n}\n\n\n.ellipsis-1{\n  overflow : hidden;\n\tdisplay: -webkit-box; \n\t-webkit-box-orient: vertical; \n\t-webkit-line-clamp: 1; \n\ttext-overflow: ellipsis; \n  word-break: break-all;\n}\n.ellipsis{\n  overflow:hidden;\n  text-overflow: ellipsis;\n  white-space: nowrap;\n}\n\n.danger-aciton{\n  color:var(--danger-color);\n}\n\n\n.danger-aciton--hover:hover{\n  color:var(--danger-color) !important;\n}\n\n.ant-spin{\n  max-height: 100vh;\n}\n\n.menu-style{\n  width:160px;\n  border-radius: 5px;\n  .menu-item{\n    padding-top:8px;\n    padding-bottom:8px;\n    color:var(--primary-text-color);\n  }\n  .ant-dropdown-menu{\n    border-radius: 5px;\n  }\n\n  .ant-dropdown-menu-item-group-list{\n    margin:0;\n  }\n}\n\n.popover-padding-0{\n  .ant-popover-inner-content{ padding:0;}\n}\n\n@media screen and (max-width:480px) {\n  .fix-modal--alone{\n    top:0;\n  }\n  .drive-breadcrumb{\n    padding:12px !important;\n  }\n  .fix-form--inline{\n    .ant-form-item{flex:100%;}\n  }\n}"
  },
  {
    "path": "packages/sharelist-web/src/assets/style/var.less",
    "content": "\n\n:root{\n  // --theme:100,52,248;\n  --theme:24,144,255;\n  --primary-color:rgb(var(--theme)); //rgb(105,65,199);\n  --primary-color-bg:rgba(var(--theme),.1); //rgb(105,65,199);\n  --primary-text-color:rgb(37, 38, 43);\n  --primary-background:rgb(var(--theme));\n\n  // --icon-folder-color:var(--primary-color);\n  // --icon-other-color:var(--primary-color);\n  // --icon-audio-color:rgb(132,140,239);\n  // --icon-video-color:rgb(219,68,55);\n  // --icon-word-color:rgb(47,151,254);\n  // --icon-pdf-color:rgb(252,134,132);\n  // --icon-ppt-color:rgb(254,159,93);\n  // --icon-file-color:rgb(212,214,218);\n  // --icon-word-color:rgb(88,178,252);\n  // --icon-doc-color:rgb(88,178,252);\n  // --icon-image-color:rgb(252,132,129);\n\n  --primary-text-secondary-color:rgba(37, 38, 43,0.36);\n\n  --color-main:var(--primary-text-color);\n\n  --primary-hover-bg-color:rgba(132, 133, 141, 0.08);\n  --primary-hover-theme-color:rgba(var(--theme), 0.08);\n  --dark-background:rgb(49, 49, 54);\n  --color-white-1:rgb(255,255,255);\n  --color-white-2:rgba(255,255,255,0.72);\n  --color-white-3:rgba(255,255,255,0.36);\n  --color-white-4:rgba(255,255,255,0.18);\n\n  --color-red:rgb(243, 91, 81);\n\n  --background:#ffffff;\n  --background-dark:rgb(17,17,19);\n  --background-header:#e3e6e9;\n  --background-header-dark:rgb(9,9,10);\n\n  --primary-text-color-dark:rgb(253,253,253);\n  --primary-progress:rgba(var(--theme),0.1);\n  --context:37, 38, 43;//0,0,0;\n  --context-1:rgb(var(--context));\n  --context-2:rgba(var(--context),0.72);\n  --context-3:rgba(var(--context),0.36);\n  --context-4:rgba(var(--context),0.18);\n\n  --context-background:#fff;\n  --context-background--dark:rgb(49,49,54);\n  --context-mask:#f8f9fa;\n  --context-hover:#f5f5f5;\n  --context-hover--dark:rgba(132, 133, 141,0.12);;\n\n  --primary-text-color--dark:rgb(37, 38, 43);\n\n  --divider: 132, 133, 141;\n  --divider-1: rgba(var(--divider), 0.2);\n  --divider-2: rgba(var(--divider), 0.16);\n  --divider-3: rgba(var(--divider), 0.08);\n\n  --danger-color:#ff4d4f;\n\n}\n\n\n@media (prefers-color-scheme: dark){\n  :root{\n    --background:var(--background-dark);\n    --background-header:var(--background-header-dark);\n    --primary-text-color:var(--primary-text-color-dark);\n    --context:255,255,255;\n    --context-background:var(--context-background--dark);\n    --context-hover:var(--context-hover--dark);\n    --primary-progress:rgba(var(--theme),0.2);\n  }\n}"
  },
  {
    "path": "packages/sharelist-web/src/components/icon/icon-svg.js",
    "content": "!(function (t) {\n  var a,\n    l,\n    o,\n    i,\n    e,\n    c,\n    h =\n      '<svg><symbol id=\"icon-drive\" viewBox=\"0 0 1024 1024\"><path d=\"M989.86666667 750.93333333c0 44.02133333-35.62666667 79.63733333-79.648 79.63733334H113.78133333C69.76 830.57066667 34.13333333 794.95466667 34.13333333 750.93333333V591.648C34.13333333 547.62666667 69.76 512 113.78133333 512h796.448c44.01066667 0 79.63733333 35.62666667 79.63733334 79.648V750.93333333zM113.78133333 459.072c-14.76266667 0-37.664 4.576-51.14666666 10.176l160.05333333-240.20266667c12.98133333-19.59466667 42.752-35.616 66.15466667-35.616h445.54666666c23.66933333 0 53.184 15.776 66.41066667 35.616L960.85333333 469.248c-16.288-6.112-33.58933333-9.664-50.89066666-10.176H113.78133333z m504.32 212.224c0 29.25866667 23.66933333 53.184 53.184 53.184 29.25866667 0 53.184-23.65866667 53.184-53.184 0-29.25866667-23.91466667-53.184-53.184-53.184-29.25866667 0.256-53.184 23.92533333-53.184 53.184z m159.296 0c0 29.25866667 23.66933333 53.184 53.184 53.184 29.25866667 0 53.184-23.65866667 53.184-53.184 0-29.25866667-23.91466667-53.184-53.184-53.184-29.26933333 0.256-53.184 23.92533333-53.184 53.184z\"  ></path></symbol><symbol id=\"icon-upload-file-outline\" viewBox=\"0 0 1024 1024\"><path d=\"M529.067 298.667V128h51.2v170.667A59.733 59.733 0 0 0 640 358.4h192v51.2H640a110.933 110.933 0 0 1-110.933-110.933z\"  ></path><path d=\"M682.667 866.048a636.95 636.95 0 0 0 45.76-1.493c18.176-1.494 26.496-4.118 31.786-6.784a76.8 76.8 0 0 0 33.558-33.579c2.688-5.27 5.312-13.59 6.784-31.765 1.536-18.774 1.578-43.2 1.578-79.894V462.187c0-36.139-0.362-45.27-2.41-53.206a76.338 76.338 0 0 0-10.39-23.36c-4.522-6.826-11.05-13.226-37.866-37.44l-166.955-150.72c-23.296-21.034-29.376-26.133-35.627-29.653a76.8 76.8 0 0 0-20.97-8.085c-7.019-1.579-14.934-1.856-46.336-1.856H375.467c-36.694 0-61.12 0.042-79.894 1.578-18.176 1.494-26.496 4.118-31.786 6.784a76.8 76.8 0 0 0-33.558 33.579c-2.688 5.27-5.312 13.59-6.784 31.765-1.536 18.774-1.578 43.2-1.578 79.894v401.066c0 36.694 0.042 61.12 1.578 79.894 1.494 18.176 4.096 26.496 6.784 31.786a76.8 76.8 0 0 0 33.579 33.558c5.27 2.688 13.59 5.312 31.765 6.784 12.224 1.002 26.88 1.365 45.76 1.493v51.2c-49.706-0.341-78.122-2.347-100.778-13.867a128 128 0 0 1-55.936-55.936c-13.952-27.392-13.952-63.232-13.952-134.912V311.467c0-71.68 0-107.52 13.952-134.912a128 128 0 0 1 55.936-55.936c27.392-13.952 63.232-13.952 134.912-13.952h106.112c29.141 0 43.69 0 57.514 3.093a128 128 0 0 1 34.987 13.44c12.31 6.976 23.125 16.725 44.736 36.267l166.955 150.72c24.874 22.464 37.333 33.685 46.25 47.146a127.978 127.978 0 0 1 17.302 38.955c4.01 15.616 4.01 32.384 4.01 65.92v250.325c0 71.68 0 107.52-13.952 134.912a128 128 0 0 1-55.936 55.936c-22.634 11.52-51.072 13.526-100.778 13.867v-51.2z\"  ></path><path d=\"M657.024 628.203l-34.048 38.229L537.6 590.4v326.933h-51.2V590.4l-85.376 76.032-34.048-38.23L512 499.052l145.024 129.152z\"  ></path></symbol><symbol id=\"icon-upload-folder-outline\" viewBox=\"0 0 1024 1024\"><path d=\"M409.067 174.933c28.309 0 37.12 0.256 44.757 2.56a59.733 59.733 0 0 1 20.565 11.03c6.166 5.056 11.264 12.245 26.987 35.797l32.47 48.747H202.047v51.2h612.885c18.347 0 30.571 0 39.958 0.789 9.088 0.747 13.226 2.048 15.872 3.413a38.4 38.4 0 0 1 16.789 16.768c1.344 2.624 2.645 6.784 3.413 15.872 0.747 9.387 0.768 21.611 0.768 39.958V704c0 30.293 0 51.477-1.365 67.968-1.323 16.213-3.797 25.643-7.467 32.853a81.067 81.067 0 0 1-35.413 35.414c-7.21 3.669-16.64 6.144-32.853 7.466-16.491 1.344-37.675 1.366-67.968 1.366h-64v51.2h65.109c28.928 0 52.203 0 71.04-1.536 19.37-1.579 36.267-4.907 51.904-12.886a132.267 132.267 0 0 0 57.813-57.813c7.936-15.595 11.286-32.512 12.864-51.883 1.536-18.837 1.536-42.112 1.536-71.04V400.043c0-17.067 0-31.36-0.96-43.094-1.002-12.266-3.178-23.893-8.81-34.965a89.6 89.6 0 0 0-39.147-39.147c-11.05-5.632-22.699-7.808-34.965-8.81-11.734-0.96-26.027-0.96-43.094-0.96H595.371l-53.355-80.064c-12.907-19.414-22.336-33.536-35.115-44.032a110.933 110.933 0 0 0-38.186-20.459c-15.851-4.8-32.854-4.8-56.107-4.779H242.133c-22.997 0-41.792 0-57.066 1.238-15.808 1.301-30.08 4.053-43.435 10.858a110.933 110.933 0 0 0-48.47 48.47c-6.805 13.333-9.557 27.626-10.858 43.434-1.237 15.275-1.237 34.07-1.237 57.067v420.267c0 28.949 0 52.224 1.536 71.04 1.578 19.392 4.906 36.309 12.885 51.925a132.267 132.267 0 0 0 57.813 57.813c15.595 7.936 32.512 11.286 51.883 12.864 18.837 1.536 42.112 1.536 71.04 1.536h65.11v-51.2h-64c-30.294 0-51.478-0.021-67.969-1.365-16.213-1.323-25.642-3.797-32.853-7.467a81.067 81.067 0 0 1-35.413-35.413c-3.67-7.21-6.144-16.64-7.467-32.853-1.344-16.47-1.365-37.654-1.365-67.947V285.867c0-24.32 0-41.024 1.066-53.974 1.046-12.629 2.944-19.413 5.44-24.341a59.733 59.733 0 0 1 26.134-26.112c4.906-2.496 11.712-4.395 24.362-5.44 12.928-1.067 29.654-1.067 53.952-1.067h165.867z\"  ></path><path d=\"M622.976 649.301l34.048-38.25L512 481.92 366.976 611.05l34.048 38.251L486.4 573.27v326.934h51.2V573.269l85.376 76.032z\"  ></path></symbol><symbol id=\"icon-download\" viewBox=\"0 0 1144 1024\"><path d=\"M986.42996662 363.52602122h-242.97562454V-0.93741459H378.99090633v364.46343581H136.01528178l425.20734243 425.20734239 425.20734241-425.20734239zM136.01528178 910.22117489v121.48781126h850.41468484v-121.48781126H136.01528178z\"  ></path></symbol><symbol id=\"icon-back\" viewBox=\"0 0 1024 1024\"><path d=\"M953.86906294 456.76636665H280.01874173l309.30834393-309.30834393L512 70.13093706l-441.86906294 441.86906294 441.86906294 441.86906294 77.32708566-77.32708566L280.01874173 567.23363335H953.86906294v-110.4672667z\"  ></path></symbol><symbol id=\"icon-upload\" viewBox=\"0 0 1570 1024\"><path d=\"M1147.34375 445.20285034c0-3.51507568 0.59078979-7.03097534 0.59078979-10.546875C1147.93453979 244.25027466 998.33782959 90.125 813.80953979 90.125 680.69396972 90.125 566.30816651 170.39413453 512.59078979 286.43695068a169.11584472 169.11584472 0 0 0-77.34402466-19.0684204c-85.21875 0-156.26266479 64.15301513-169.81869506 147.93804931C163.19342041 451.08108521 90.125 550.41864013 90.125 667.27801513 90.125 814.34347534 205.91567993 933.875 348.59347534 933.875h295.98815918V699.50942993H505.39089966l231.609375-245.25027465 231.609375 244.96929931h-139.21875v234.36557007h318.54364013c130.52828979 0 235.94018555-109.88525391 235.94018555-244.34967041 0-134.46606445-105.97467041-243.75970459-236.53097534-244.04150391z\" fill=\"#666666\" ></path></symbol><symbol id=\"icon-folder-download\" viewBox=\"0 0 1024 1024\"><path d=\"M771.238 349.034c-48.762-111.806-160.142-190.034-289.912-190.034-174.674 0-316.28 141.604-316.28 316.278 0 0.954 0.134 1.872 0.142 2.824-89.786 15.028-158.282 92.89-158.282 186.944 0 104.806 84.964 189.768 189.77 189.768l600.928 0 0-2.186c124.78-15.594 221.398-121.828 221.398-250.838C1019 463.828 908.524 351.862 771.238 349.034zM512.954 759.93l-158.14-158.14 94.884 0L449.698 380.396l126.512 0 0 221.396 94.882 0L512.954 759.93z\"  ></path></symbol><symbol id=\"icon-other\" viewBox=\"0 0 1024 1024\"><path d=\"M128 512v512h800V300.8l-153.6-150.4L620.8 0H128v512z m608-304l172.8 176H544V208c0-96 3.2-176 9.6-176 3.2 0 86.4 80 182.4 176z\" fill=\"#4285f4\" ></path></symbol><symbol id=\"icon-archive\" viewBox=\"0 0 1032 1024\"><path d=\"M54.912029 52.69395c-73.186041 73.186041-73.186041 843.230478 0 916.416519 73.186041 73.186041 843.230478 73.186041 916.416519 0 73.186041-73.186041 73.186041-843.230478 0-916.416519C898.142506-20.492092 128.09807-20.492092 54.912029 52.69395zM767.680433 192.702029c0 57.276032-22.274013 63.640036-254.560145 63.640036s-254.560144-6.364004-254.560144-63.640036 22.274013-63.640036 254.560144-63.640036 254.560144 6.364004 254.560145 63.640036z m0 254.560144c0 57.276032-22.274013 63.640036-254.560145 63.640036s-254.560144-6.364004-254.560144-63.640036 22.274013-63.640036 254.560144-63.640036 254.560144 6.364004 254.560145 63.640036z m-120.916069 324.564184L513.120288 908.652435l-133.644075-136.826078L242.650135 638.182281h540.940307l-136.826078 133.644076z\"  ></path></symbol><symbol id=\"icon-doc2\" viewBox=\"0 0 1024 1024\"><path d=\"M12.8 41.6C6.4 64 3.2 291.2 6.4 544l9.6 464h992V16L518.4 6.4C137.6 0 25.6 6.4 12.8 41.6zM832 288c0 19.2-128 32-320 32S192 307.2 192 288s128-32 320-32 320 12.8 320 32z m0 128c0 19.2-128 32-320 32s-320-12.8-320-32 128-32 320-32 320 12.8 320 32z m0 128c0 19.2-128 32-320 32s-320-12.8-320-32 128-32 320-32 320 12.8 320 32z m-256 128c0 19.2-86.4 32-192 32s-192-12.8-192-32 86.4-32 192-32 192 12.8 192 32z\" fill=\"#4285f4\" ></path></symbol><symbol id=\"icon-grid\" viewBox=\"0 0 1024 1024\"><path d=\"M384 128H213.333333a85.333333 85.333333 0 0 0-85.333333 85.333333v170.666667a85.333333 85.333333 0 0 0 85.333333 85.333333h170.666667a85.333333 85.333333 0 0 0 85.333333-85.333333V213.333333a85.333333 85.333333 0 0 0-85.333333-85.333333zM810.666667 128h-170.666667a85.333333 85.333333 0 0 0-85.333333 85.333333v170.666667a85.333333 85.333333 0 0 0 85.333333 85.333333h170.666667a85.333333 85.333333 0 0 0 85.333333-85.333333V213.333333a85.333333 85.333333 0 0 0-85.333333-85.333333zM384 554.666667H213.333333a85.333333 85.333333 0 0 0-85.333333 85.333333v170.666667a85.333333 85.333333 0 0 0 85.333333 85.333333h170.666667a85.333333 85.333333 0 0 0 85.333333-85.333333v-170.666667a85.333333 85.333333 0 0 0-85.333333-85.333333zM810.666667 554.666667h-170.666667a85.333333 85.333333 0 0 0-85.333333 85.333333v170.666667a85.333333 85.333333 0 0 0 85.333333 85.333333h170.666667a85.333333 85.333333 0 0 0 85.333333-85.333333v-170.666667a85.333333 85.333333 0 0 0-85.333333-85.333333z\"  ></path></symbol><symbol id=\"icon-pdf2\" viewBox=\"0 0 1024 1024\"><path d=\"M981.333333 276.053333V981.333333a42.666667 42.666667 0 0 1-42.666666 42.666667H85.333333a42.666667 42.666667 0 0 1-42.666666-42.666667V42.666667a42.666667 42.666667 0 0 1 42.666666-42.666667h619.946667z\" fill=\"#FC5A5A\" ></path><path d=\"M705.28 233.386667V0L981.333333 276.053333H747.946667a42.666667 42.666667 0 0 1-42.666667-42.666666z\" fill=\"#FD9796\" ></path><path d=\"M252.586667 715.52H224A10.666667 10.666667 0 0 1 213.333333 704V490.666667a10.666667 10.666667 0 0 1 10.666667-10.666667h62.293333q95.786667-1.28 95.786667 72.746667-2.56 69.12-81.706667 72.96h-37.12V704a10.666667 10.666667 0 0 1-10.666666 11.52z m10.666666-196.906667v66.56q3.84 0 15.36 1.28c33.92 3.413333 50.56-8.106667 49.706667-34.56 0-23.04-16.426667-34.133333-49.706667-33.28a34.986667 34.986667 0 0 1-15.36 0zM417.493333 704V490.666667a10.666667 10.666667 0 0 1 10.666667-10.666667h69.76c78.506667 0 117.973333 40.533333 119.04 118.826667s-40.533333 117.546667-119.04 117.546666h-69.76a10.666667 10.666667 0 0 1-10.666667-12.373333z m49.706667-186.24v157.44h26.88q70.4 2.56 69.12-78.08t-69.12-79.36zM697.813333 715.52h-28.586666a10.666667 10.666667 0 0 1-10.666667-10.666667V490.666667a10.666667 10.666667 0 0 1 10.666667-10.666667h130.773333a10.666667 10.666667 0 0 1 10.666667 10.666667v18.133333a10.666667 10.666667 0 0 1-10.666667 10.666667h-91.52V576h85.333333a10.666667 10.666667 0 0 1 10.666667 10.666667v18.346666a10.666667 10.666667 0 0 1-10.666667 10.666667h-85.333333V704a10.666667 10.666667 0 0 1-10.666667 11.52z\" fill=\"#FFFFFF\" ></path></symbol><symbol id=\"icon-excel\" viewBox=\"0 0 1024 1024\"><path d=\"M136.533604 0.00026a49.119975 49.119975 0 0 0-35.839982 15.359992C91.307627 25.600247 85.33463 38.40024 85.33463 51.200234v921.599532a49.119975 49.119975 0 0 0 15.359992 35.839982 50.545974 50.545974 0 0 0 35.839982 15.359992h750.931619a49.119975 49.119975 0 0 0 35.839981-15.359992 50.546974 50.546974 0 0 0 15.359993-35.839982V290.134113L648.533344 0.00026z\" fill=\"#00B632\" ></path><path d=\"M938.666197 290.133113H699.733318a52.492973 52.492973 0 0 1-51.199974-51.199974V0.00026z\" fill=\"#7FDA98\" ></path><path d=\"M462.95969321 572.36898384L317.39898702 373.72683116h100.61033872l95.23467997 142.18962084L612.84549052 373.72683116h97.4030191L561.40169203 572.36898384l156.27648978 211.34320013H616.06285234l-104.98718655-150.67140562L406.3565285 784.68395902H306.72105396z\" fill=\"#FFFFFF\" ></path></symbol><symbol id=\"icon-PPT\" viewBox=\"0 0 1024 1024\"><path d=\"M136.533604 0.00026a49.119975 49.119975 0 0 0-35.839982 15.359992C91.307627 25.600247 85.33463 38.40024 85.33463 51.200234v921.599532a49.119975 49.119975 0 0 0 15.359992 35.839982 50.545974 50.545974 0 0 0 35.839982 15.359992h750.931619a49.119975 49.119975 0 0 0 35.839981-15.359992 50.546974 50.546974 0 0 0 15.359993-35.839982V290.134113L648.533344 0.00026z\" fill=\"#EB734C\" ></path><path d=\"M938.666197 290.133113H699.733318a52.492973 52.492973 0 0 1-51.199974-51.199974V0.00026z\" fill=\"#FCB7A0\" ></path><path d=\"M341.3335 362.000076h196.6079c112.916943 0 169.374914 47.823976 169.374914 144.134927 0 96.975951-57.122971 145.462926-170.699913 145.462926H413.732463v184.652906h-72.399963V362.000076z m72.399963 61.771969v166.053915h118.22994c35.867982 0 61.771969-6.641997 78.37796-19.926989 15.940992-13.283993 24.575988-34.538982 24.575988-63.764968s-8.634996-50.479974-25.239987-62.435968q-24.907987-19.92599-77.712961-19.92699H413.733463z\" fill=\"#FFFFFF\" ></path></symbol><symbol id=\"icon-word2\" viewBox=\"0 0 1024 1024\"><path d=\"M136.533604 0.00026a49.119975 49.119975 0 0 0-35.839982 15.359992C91.307627 25.600247 85.33463 38.40024 85.33463 51.200234v921.599532a49.119975 49.119975 0 0 0 15.359992 35.839982 50.545974 50.545974 0 0 0 35.839982 15.359992h750.931619a49.119975 49.119975 0 0 0 35.839981-15.359992 50.546974 50.546974 0 0 0 15.359993-35.839982V290.134113L648.533344 0.00026z\" fill=\"#2F97FE\" ></path><path d=\"M938.666197 290.133113H699.733318a52.492973 52.492973 0 0 1-51.199974-51.199974V0.00026z\" fill=\"#97C6FF\" ></path><path d=\"M242.08055 417.000048h88.622955l72.475964 268.788864L474.948432 417.000048h73.874963l71.776963 268.788864 72.472963-268.784864h88.622955l-122.447937 363.759816h-74.629963l-72.469963-265.927865-73.172963 265.927865h-74.570962z m0 0\" fill=\"#FFFFFF\" ></path></symbol><symbol id=\"icon-video2\" viewBox=\"0 0 1024 1024\"><path d=\"M147.2 0C102.4 0 65.6 36.8 65.6 81.6v860.8c0 44.8 36.8 81.6 81.6 81.6h731.2c44.8 0 81.6-36.8 81.6-81.6V324.8L657.6 0H147.2z\" fill=\"#db4437\" ></path><path d=\"M960 326.4v16H755.2s-100.8-20.8-99.2-108.8c0 0 4.8 92.8 97.6 92.8H960z\" fill=\"#db4437\" ></path><path d=\"M657.6 0v233.6c0 25.6 17.6 92.8 97.6 92.8H960L657.6 0z\" fill=\"#e37268\" ></path><path d=\"M456 728c0 6.4-1.6 12.8-6.4 16-3.2 3.2-84.8 70.4-190.4 113.6-1.6 1.6-4.8 1.6-8 1.6s-6.4-1.6-9.6-3.2c-6.4-3.2-9.6-8-11.2-16 0-1.6-4.8-54.4-4.8-112s4.8-108.8 4.8-112c1.6-6.4 4.8-11.2 11.2-16 3.2-1.6 6.4-3.2 9.6-3.2 3.2 0 6.4 1.6 8 3.2 105.6 41.6 187.2 110.4 190.4 113.6 4.8 3.2 6.4 9.6 6.4 14.4z\" fill=\"#FFFFFF\" ></path></symbol><symbol id=\"icon-list\" viewBox=\"0 0 1024 1024\"><path d=\"M287 265.90625V195.59375c0-15.53378906 12.59121094-28.125 28.125-28.125h618.75c15.53378906 0 28.125 12.59121094 28.125 28.125v70.3125c0 15.53378906-12.59121094 28.125-28.125 28.125H315.125c-15.53378906 0-28.125-12.59121094-28.125-28.125z m28.125 309.375h618.75c15.53378906 0 28.125-12.59121094 28.125-28.125v-70.3125c0-15.53378906-12.59121094-28.125-28.125-28.125H315.125c-15.53378906 0-28.125 12.59121094-28.125 28.125v70.3125c0 15.53378906 12.59121094 28.125 28.125 28.125z m0 281.25h618.75c15.53378906 0 28.125-12.59121094 28.125-28.125v-70.3125c0-15.53378906-12.59121094-28.125-28.125-28.125H315.125c-15.53378906 0-28.125 12.59121094-28.125 28.125v70.3125c0 15.53378906 12.59121094 28.125 28.125 28.125zM90.125 315.125h112.5c15.53378906 0 28.125-12.59121094 28.125-28.125V174.5c0-15.53378906-12.59121094-28.125-28.125-28.125H90.125C74.59121094 146.375 62 158.96621094 62 174.5v112.5c0 15.53378906 12.59121094 28.125 28.125 28.125z m0 281.25h112.5c15.53378906 0 28.125-12.59121094 28.125-28.125v-112.5c0-15.53378906-12.59121094-28.125-28.125-28.125H90.125c-15.53378906 0-28.125 12.59121094-28.125 28.125v112.5c0 15.53378906 12.59121094 28.125 28.125 28.125z m0 281.25h112.5c15.53378906 0 28.125-12.59121094 28.125-28.125v-112.5c0-15.53378906-12.59121094-28.125-28.125-28.125H90.125c-15.53378906 0-28.125 12.59121094-28.125 28.125v112.5c0 15.53378906 12.59121094 28.125 28.125 28.125z\"  ></path></symbol><symbol id=\"icon-search\" viewBox=\"0 0 1035 1024\"><path d=\"M1013.852766 1011.332492a42.225028 42.225028 0 0 1-59.70619 0L702.316509 759.502424a428.900723 428.900723 0 1 1 133.958901-196.00858 41.718328 41.718328 0 0 1-4.919216 14.166497c-1.330088 3.61024-2.385714 7.347155-3.800252 10.91517l-2.385714-2.385714a42.225028 42.225028 0 0 1-72.690386-29.13527l-0.380025-3.905815a41.950565 41.950565 0 0 1 11.379645-28.670794l-3.926928-3.905815a336.976836 336.976836 0 1 0-88.123633 150.764463 6.333754 6.333754 0 0 0 0.612262-0.928951l61.120729 1.055626 145.254096 145.232984 0.274463-0.274463 135.12009 135.12009a42.225028 42.225028 0 0 1 0.042225 59.79064z\"  ></path></symbol><symbol id=\"icon-home\" viewBox=\"0 0 1025 1024\"><path d=\"M938.977859 1024c-100.292785 0-198.718416 0-298.210992 0 0-113.362855 0-226.458974 0-340.355301-85.889034 0-170.17765 0-255.799948 0 0 112.829383 0 225.658765 0 339.821829-100.292785 0-199.251889 0-299.277937 0 0-4.534514 0-8.802292 0-13.07007 0-176.579318 0-352.891899 0.266736-529.471216 0-5.868195 3.46757-13.870279 8.002084-17.604585 138.436051-111.228966 277.138838-222.191196 416.108362-333.153425 0.533472-0.533472 1.600417-0.800208 3.200834-1.333681 45.345142 36.276114 91.223756 72.818963 136.835634 109.361813 91.490492 73.352436 182.980985 146.704871 275.004949 219.523834 10.402709 8.26882 14.403751 16.53764 14.403751 29.874446-0.533472 173.911956-0.266736 347.557176-0.266736 521.469133C938.977859 1013.864027 938.977859 1018.932014 938.977859 1024z\"  ></path><path d=\"M85.422245 85.889034c57.348268 0 113.096119 0 169.910914 0 0 38.410003 0 76.820005 0 119.497786 87.222714-69.61813 171.511331-137.10237 256.866892-205.386819 22.939307 18.404793 46.14535 36.809586 69.351394 55.214379 144.570982 115.76348 289.141964 231.52696 433.979682 347.023704 6.668403 5.334723 9.602501 10.135973 9.335765 18.671529-0.800208 13.603543-0.266736 27.207085-0.266736 44.011461C852.288617 327.285231 682.644439 191.516541 512.200052 55.214379 342.022402 191.516541 172.111487 327.285231 0.066684 464.921073c0-19.205001-0.266736-35.475905 0.266736-51.480073 0-3.200834 3.734306-6.668403 6.401667-9.069028 22.672571-18.404793 45.611878-36.809586 68.817921-54.680906 7.468612-5.868195 10.135973-12.003126 9.869237-21.33889C85.422245 252.599114 85.422245 177.11279 85.422245 101.626465 85.422245 96.825215 85.422245 92.023965 85.422245 85.889034z\"  ></path></symbol><symbol id=\"icon-license\" viewBox=\"0 0 1131 1024\"><path d=\"M617.969758 52.960171a52.967605 52.967605 0 0 0-105.93521 0v88.271908h-69.486064a124.594675 124.594675 0 0 0-61.368081 16.258267l-90.9556 52.038349a17.655868 17.655868 0 0 1-8.831652 2.408632H123.63518a52.967605 52.967605 0 0 0 0 105.920342h30.219412L4.712684 649.208139a52.967605 52.967605 0 0 0 10.853713 59.175037l37.437875-37.430441-37.430441 37.430441v0.074341l0.185851 0.185851 0.178417 0.185851 0.542686 0.542686 1.070503 1.070503 3.181773 2.824939a248.148584 248.148584 0 0 0 48.440269 31.780563 317.218341 317.218341 0 0 0 142.86757 32.063057c62.141223 0 109.875257-15.537164 142.860136-32.063057a248.222925 248.222925 0 0 0 48.440269-31.780563l3.181773-2.824939 1.070504-1.070503 0.542685-0.542686 0.178417-0.185851 0.074341-0.185851-37.348666-37.497348 37.430441 37.430441a52.967605 52.967605 0 0 0 10.853712-59.175037L270.190038 317.865104h11.448437c21.543876 0 42.656578-5.657312 61.368082-16.228531l91.02994-52.038349a17.655868 17.655868 0 0 1 8.749877-2.408632h69.486064v670.878394H335.721188a52.967605 52.967605 0 0 0 0 105.935211H794.751464a52.967605 52.967605 0 0 0 0-105.935211H618.200214V247.182158h69.486064a17.655868 17.655868 0 0 1 8.749877 2.401198l91.096846 51.978876a122.795635 122.795635 0 0 0 61.301175 16.310306h11.448437L711.133271 649.215573a52.967605 52.967605 0 0 0 10.861147 59.175037l37.430441-37.423007-37.430441 37.430441v0.074341l0.185851 0.185851 0.185851 0.185851 0.542686 0.542686 1.070503 1.063069 3.17434 2.824939a248.371606 248.371606 0 0 0 48.447702 31.780563 317.218341 317.218341 0 0 0 142.860136 32.063057c62.148657 0 109.882691-15.537164 142.860136-32.063057a248.014771 248.014771 0 0 0 48.447703-31.780563l3.181774-2.824939 0.817745-0.817745 0.542686-0.267626 0.542685-0.542686 0.178418-0.185851 0.07434-0.185851-37.3561-37.497348 37.430441 37.430441a52.967605 52.967605 0 0 0 10.890883-59.175037l-149.082436-331.35047h30.15994a52.967605 52.967605 0 0 0 0-105.927776h-157.765406a17.655868 17.655868 0 0 1-8.757311-2.393764l-91.096846-51.978876a123.583645 123.583645 0 0 0-61.226835-16.310306h-69.560404zM119.739737 651.594469c20.131407 9.530452 50.707654 19.269057 92.16735 19.269058 41.452262 0 72.028509-9.738605 92.152482-19.269058L211.921956 446.801264z m706.197566 0c20.123973 9.530452 50.70022 19.269057 92.152482 19.269058 41.452262 0 72.035943-9.738605 92.159916-19.269058l-92.159916-204.793205z\"  ></path></symbol><symbol id=\"icon-left\" viewBox=\"0 0 1024 1024\"><path d=\"M339.430666 573.930491L339.563696 573.797461 684.570357 918.803099 746.43331 856.939123 401.427672 511.933485 746.30028 167.060877 684.436304 105.196901 339.563696 450.069509 277.700743 511.933485 277.56669 512.066515Z\"  ></path></symbol><symbol id=\"icon-close\" viewBox=\"0 0 1024 1024\"><path d=\"M887.2 774.2 624.8 510.8l263-260c10.8-10.8 10.8-28.4 0-39.2l-74.8-75.2c-5.2-5.2-12.2-8-19.6-8-7.4 0-14.4 3-19.6 8L512 395.6 249.8 136.6c-5.2-5.2-12.2-8-19.6-8-7.4 0-14.4 3-19.6 8L136 211.8c-10.8 10.8-10.8 28.4 0 39.2l263 260L136.8 774.2c-5.2 5.2-8.2 12.2-8.2 19.6 0 7.4 2.8 14.4 8.2 19.6l74.8 75.2c5.4 5.4 12.4 8.2 19.6 8.2 7 0 14.2-2.6 19.6-8.2L512 626.2l261.4 262.2c5.4 5.4 12.4 8.2 19.6 8.2 7 0 14.2-2.6 19.6-8.2l74.8-75.2c5.2-5.2 8.2-12.2 8.2-19.6C895.4 786.4 892.4 779.4 887.2 774.2z\"  ></path></symbol><symbol id=\"icon-full_screen_full\" viewBox=\"0 0 1117 1024\"><path d=\"M93.090909 93.090909v232.727273a46.545455 46.545455 0 0 1-93.090909 0V46.545455a46.545455 46.545455 0 0 1 46.545455-46.545455h279.272727a46.545455 46.545455 0 0 1 0 93.090909z m0 837.818182h232.727273a46.545455 46.545455 0 0 1 0 93.090909H46.545455a46.545455 46.545455 0 0 1-46.545455-46.545455v-279.272727a46.545455 46.545455 0 0 1 93.090909 0zM930.909091 93.090909h-232.727273a46.545455 46.545455 0 0 1 0-93.090909h279.272727a46.545455 46.545455 0 0 1 46.545455 46.545455v279.272727a46.545455 46.545455 0 0 1-93.090909 0z m0 837.818182v-232.727273a46.545455 46.545455 0 0 1 93.090909 0v279.272727a46.545455 46.545455 0 0 1-46.545455 46.545455h-279.272727a46.545455 46.545455 0 0 1 0-93.090909z\" fill=\"#409EFF\" ></path><path d=\"M93.090909 93.090909v232.727273a46.545455 46.545455 0 0 1-93.090909 0V46.545455a46.545455 46.545455 0 0 1 46.545455-46.545455h279.272727a46.545455 46.545455 0 0 1 0 93.090909z m0 837.818182h232.727273a46.545455 46.545455 0 0 1 0 93.090909H46.545455a46.545455 46.545455 0 0 1-46.545455-46.545455v-279.272727a46.545455 46.545455 0 0 1 93.090909 0zM930.909091 93.090909h-232.727273a46.545455 46.545455 0 0 1 0-93.090909h279.272727a46.545455 46.545455 0 0 1 46.545455 46.545455v279.272727a46.545455 46.545455 0 0 1-93.090909 0z m0 837.818182v-232.727273a46.545455 46.545455 0 0 1 93.090909 0v279.272727a46.545455 46.545455 0 0 1-46.545455 46.545455h-279.272727a46.545455 46.545455 0 0 1 0-93.090909z\"  ></path></symbol><symbol id=\"icon-video\" viewBox=\"0 0 48 48\" fill=\"none\"><path data-follow-stroke=\"currentColor\" data-follow-fill=\"currentColor\" d=\"M10 44h28a2 2 0 0 0 2-2V14H30V4H10a2 2 0 0 0-2 2v36a2 2 0 0 0 2 2Z\" fill=\"currentColor\" stroke=\"currentColor\" stroke-width=\"4\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/><path data-follow-stroke=\"currentColor\" d=\"m30 4 10 10\" stroke=\"currentColor\" stroke-width=\"4\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/><path d=\"M14 21h13.493v3.5L34 22v11l-6.507-2.5V34H14V21Z\" fill=\"#FFF\" stroke=\"#FFF\" stroke-width=\"4\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/></symbol><symbol id=\"icon-doc\" viewBox=\"0 0 48 48\" fill=\"none\"><path data-follow-stroke=\"currentColor\" data-follow-fill=\"currentColor\" d=\"M10 44h28a2 2 0 0 0 2-2V14H30V4H10a2 2 0 0 0-2 2v36a2 2 0 0 0 2 2Z\" fill=\"currentColor\" stroke=\"currentColor\" stroke-width=\"4\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/><path data-follow-stroke=\"currentColor\" d=\"m30 4 10 10\" stroke=\"currentColor\" stroke-width=\"4\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/><path d=\"M24 22v14m-6-14h12\" stroke=\"#FFF\" stroke-width=\"4\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/></symbol><symbol id=\"icon-audio\" viewBox=\"0 0 48 48\" fill=\"none\"><path data-follow-stroke=\"currentColor\" data-follow-fill=\"currentColor\" stroke-linejoin=\"round\" stroke-linecap=\"round\" stroke-width=\"4\" stroke=\"currentColor\" fill=\"currentColor\" d=\"M10 44h28a2 2 0 0 0 2-2V14H30V4H10a2 2 0 0 0-2 2v36a2 2 0 0 0 2 2Z\"/><path data-follow-stroke=\"currentColor\" stroke-linejoin=\"round\" stroke-linecap=\"round\" stroke-width=\"4\" stroke=\"currentColor\" d=\"m30 4 10 10\"/><path stroke-linejoin=\"round\" stroke-linecap=\"round\" stroke-width=\"4\" stroke=\"#FFF\" d=\"m31 20-6 2.969V33.5\"/><circle stroke-linejoin=\"round\" stroke-linecap=\"round\" stroke-width=\"4\" stroke=\"#FFF\" fill=\"#FFF\" r=\"4\" cy=\"33\" cx=\"21\"/></symbol><symbol id=\"icon-ppt\" viewBox=\"0 0 48 48\" fill=\"none\"><path data-follow-stroke=\"currentColor\" data-follow-fill=\"currentColor\" stroke-linejoin=\"round\" stroke-linecap=\"round\" stroke-width=\"4\" stroke=\"currentColor\" fill=\"currentColor\" d=\"M10 44h28a2 2 0 0 0 2-2V14H30V4H10a2 2 0 0 0-2 2v36a2 2 0 0 0 2 2Z\"/><path data-follow-stroke=\"currentColor\" stroke-linejoin=\"round\" stroke-linecap=\"round\" stroke-width=\"4\" stroke=\"currentColor\" d=\"m30 4 10 10\"/><path xmlns=\"http://www.w3.org/2000/svg\" clip-rule=\"evenodd\" d=\"M18 18h12v7.992L18.008 26 18 18Z\" stroke=\"#FFF\" stroke-width=\"4\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/><path xmlns=\"http://www.w3.org/2000/svg\" d=\"M18 18v16\" stroke=\"#FFF\" stroke-width=\"4\" stroke-linecap=\"round\"/></symbol><symbol id=\"icon-word\" viewBox=\"0 0 48 48\" fill=\"none\"><path data-follow-stroke=\"currentColor\" data-follow-fill=\"currentColor\" d=\"M10 44h28a2 2 0 0 0 2-2V14H30V4H10a2 2 0 0 0-2 2v36a2 2 0 0 0 2 2Z\" fill=\"currentColor\" stroke=\"currentColor\" stroke-width=\"4\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/><path data-follow-stroke=\"currentColor\" d=\"m30 4 10 10\" stroke=\"currentColor\" stroke-width=\"4\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/><path stroke-linejoin=\"round\" stroke-linecap=\"round\" stroke-width=\"4\" stroke=\"#FFF\" d=\"m16.008 20 3 14 5-10 5 10 3-14\"/></symbol><symbol id=\"icon-file\" viewBox=\"0 0 48 48\" fill=\"none\"><path data-follow-stroke=\"currentColor\" data-follow-fill=\"currentColor\" d=\"M10 44h28a2 2 0 0 0 2-2V14H30V4H10a2 2 0 0 0-2 2v36a2 2 0 0 0 2 2Z\" fill=\"currentColor\" stroke=\"currentColor\" stroke-width=\"4\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/><path data-follow-stroke=\"currentColor\" d=\"m30 4 10 10\" stroke=\"currentColor\" stroke-width=\"4\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/></symbol><symbol id=\"icon-image\" viewBox=\"0 0 48 48\" fill=\"none\"><path data-follow-stroke=\"currentColor\" data-follow-fill=\"currentColor\" d=\"M10 44h28a2 2 0 0 0 2-2V14H30V4H10a2 2 0 0 0-2 2v36a2 2 0 0 0 2 2Z\" fill=\"currentColor\" stroke=\"currentColor\" stroke-width=\"4\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/><path data-follow-stroke=\"currentColor\" d=\"m30 4 10 10\" stroke=\"currentColor\" stroke-width=\"4\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/><path stroke-linejoin=\"round\" stroke-width=\"4\" stroke=\"#fff\" fill=\"#fff\" d=\"m19.5 27 2.5 2 3-3.5 8.5 6.5v2a1 1 0 0 1-1 1h-17a1 1 0 0 1-1-1v-2l5-5Z\"/><circle fill=\"#fff\" r=\"2\" cy=\"20\" cx=\"15\"/></symbol><symbol id=\"icon-pdf\" viewBox=\"0 0 48 48\" fill=\"none\"><path data-follow-stroke=\"currentColor\" data-follow-fill=\"currentColor\" stroke-linejoin=\"round\" stroke-linecap=\"round\" stroke-width=\"4\" stroke=\"currentColor\" fill=\"currentColor\" d=\"M10 44h28a2 2 0 0 0 2-2V14H30V4H10a2 2 0 0 0-2 2v36a2 2 0 0 0 2 2Z\"/><path data-follow-stroke=\"currentColor\" stroke-linejoin=\"round\" stroke-linecap=\"round\" stroke-width=\"4\" stroke=\"currentColor\" d=\"m30 4 10 10\"/><path d=\"M29.227 32.477a5.445 5.445 0 0 1-3.158-1.28c-1.749.384-3.413.939-5.077 1.622-1.323 2.346-2.56 3.541-3.627 3.541-.213 0-.469-.043-.64-.17A1.257 1.257 0 0 1 16 35.036c0-.384.085-1.45 4.139-3.2.938-1.706 1.664-3.456 2.261-5.29-.512-1.024-1.621-3.542-.853-4.822.256-.469.768-.725 1.322-.682.427 0 .854.213 1.11.554.554.768.512 2.39-.214 4.779a12.87 12.87 0 0 0 2.646 3.413c.896-.17 1.792-.298 2.688-.298 2.005.042 2.304.981 2.261 1.536 0 1.45-1.408 1.45-2.133 1.45zM17.28 35.123l.128-.043c.597-.213 1.067-.64 1.408-1.195a3.372 3.372 0 0 0-1.536 1.238zm5.675-12.8h-.128c-.043 0-.128 0-.171.042-.17.726-.043 1.494.256 2.176a3.586 3.586 0 0 0 .043-2.218zm.298 6.186-.042.086-.043-.043c-.384.981-.81 1.963-1.28 2.901l.085-.042v.085c.939-.341 1.963-.64 2.902-.853l-.043-.043h.128a13.074 13.074 0 0 1-1.707-2.09zm5.803 2.262c-.384 0-.725 0-1.11.085.427.213.854.299 1.28.341.3.043.598 0 .854-.085 0-.128-.17-.341-1.024-.341z\" fill=\"#fff\"/></symbol><symbol id=\"icon-code\" viewBox=\"0 0 48 48\" fill=\"none\"><path data-follow-stroke=\"currentColor\" data-follow-fill=\"currentColor\" stroke-linejoin=\"round\" stroke-linecap=\"round\" stroke-width=\"4\" stroke=\"currentColor\" fill=\"currentColor\" d=\"M10 44h28a2 2 0 0 0 2-2V14H30V4H10a2 2 0 0 0-2 2v36a2 2 0 0 0 2 2Z\"/><path data-follow-stroke=\"currentColor\" stroke-linejoin=\"round\" stroke-linecap=\"round\" stroke-width=\"4\" stroke=\"currentColor\" d=\"m30 4 10 10M30 4l10 10\"/><path d=\"m27 24 5 5-5 5m-6-10-5 5 5 5\" stroke=\"#fff\" stroke-width=\"4\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/></symbol><symbol id=\"icon-puzzle\" viewBox=\"0 0 48 48\" fill=\"none\"><path d=\"M4 24V12H13V10C13 6.68629 15.6863 4 19 4C22.3137 4 25 6.68629 25 10V12H34V24H38C41.3137 24 44 26.6863 44 30C44 33.3137 41.3137 36 38 36H34V44H4V36H8C11.3137 36 14 33.3137 14 30C14 26.6863 11.3137 24 8 24H4Z\" fill=\"currentColor\" stroke=\"currentColor\" stroke-width=\"4\" stroke-linejoin=\"miter\"/></symbol><symbol viewBox=\"0 0 1024 1024\" id=\"icon-folder\"><path d=\"M918.673 883H104.327C82.578 883 65 867.368 65 848.027V276.973C65 257.632 82.578 242 104.327 242h814.346C940.422 242 958 257.632 958 276.973v571.054C958 867.28 940.323 883 918.673 883z\" fill=\"#ffb900\"></path><path d=\"M512 411H65V210.37C65 188.597 82.598 171 104.371 171h305.92c17.4 0 32.71 11.334 37.681 28.036L512 411z\" fill=\"#FFB02C\"></path><path d=\"M918.673 883H104.327C82.578 883 65 865.42 65 843.668V335.332C65 313.58 82.578 296 104.327 296h814.346C940.422 296 958 313.58 958 335.332v508.336C958 865.32 940.323 883 918.673 883z\" fill=\"#FFCA28\"></path></symbol><symbol id=\"icon-folder2\" viewBox=\"0 0 1408 1024\"><path d=\"M566.38583472-10.59260571H175.88929636c-71.59103266 0-129.51468459 58.57448076-129.51468456 130.1655134L45.72378487 900.56598437c0 71.59103266 58.57448076 130.1655134 130.16551149 130.16551341h1041.32410353c71.59103266 0 130.1655134-58.57448076 130.16551148-130.16551341V249.7384211c0-71.59103266-58.57448076-130.1655134-130.16551148-130.16551341h-520.66205177l-130.1655134-130.1655134z\" fill=\"#f8d673\" ></path></symbol></svg>',\n    d = (d = document.getElementsByTagName('script'))[d.length - 1].getAttribute('data-injectcss')\n  if (d && !t.__iconfont__svg__cssinject__) {\n    t.__iconfont__svg__cssinject__ = !0\n    try {\n      document.write(\n        '<style>.svgfont {display: inline-block;width: 1em;height: 1em;fill: currentColor;vertical-align: -0.1em;font-size:16px;}</style>',\n      )\n    } catch (t) {\n      console && console.log(t)\n    }\n  }\n  function n() {\n    e || ((e = !0), o())\n  }\n  ; (a = function () {\n    var t, a, l\n      ; ((l = document.createElement('div')).innerHTML = h),\n        (h = null),\n        (a = l.getElementsByTagName('svg')[0]) &&\n        (a.setAttribute('aria-hidden', 'true'),\n          (a.style.position = 'absolute'),\n          (a.style.width = 0),\n          (a.style.height = 0),\n          (a.style.overflow = 'hidden'),\n          (t = a),\n          (l = document.body).firstChild ? (a = l.firstChild).parentNode.insertBefore(t, a) : l.appendChild(t))\n  }),\n    document.addEventListener\n      ? ~['complete', 'loaded', 'interactive'].indexOf(document.readyState)\n        ? setTimeout(a, 0)\n        : ((l = function () {\n          document.removeEventListener('DOMContentLoaded', l, !1), a()\n        }),\n          document.addEventListener('DOMContentLoaded', l, !1))\n      : document.attachEvent &&\n      ((o = a),\n        (i = t.document),\n        (e = !1),\n        (c = function () {\n          try {\n            i.documentElement.doScroll('left')\n          } catch (t) {\n            return void setTimeout(c, 50)\n          }\n          n()\n        })(),\n        (i.onreadystatechange = function () {\n          'complete' == i.readyState && ((i.onreadystatechange = null), n())\n        }))\n})(window)\n"
  },
  {
    "path": "packages/sharelist-web/src/components/icon/index.less",
    "content": "// .sl-icon {\n//   display: inline-block;\n//   font-style: normal;\n//   vertical-align: -0.125em;\n//   text-align: center;\n//   text-transform: none;\n//   line-height: 0;\n//   text-rendering: optimizeLegibility;\n//   -webkit-font-smoothing: antialiased;\n// }\n\n/*\n--icon-folder-color:var(--primary-color);\n--icon-audio-color:rgb(132,140,239);\n--icon-video-color:rgb(219,68,55);\n--icon-word-color:rgb(47,151,254);\n--icon-pdf-color:rgb(252,134,132);\n--icon-ppt-color:rgb(254,159,93);\n--icon-file-color:rgb(212,214,218);\n--icon-word-color:rgb(88,178,252);\n--icon-doc-color:rgb(88,178,252);\n--icon-image-color:rgb(252,132,129);\n*/\n@type: {\n  icon-folder:#ffd55a;//#f8d673;\n  icon-file:rgb(188,190,194);//rgb(212,214,218);\n\n  icon-audio:rgb(223,94,83);\n  icon-video:rgb(223,94,83);\n  icon-image:rgb(223,94,83);\n\n  icon-word:rgb(96,181,252);\n  icon-doc:rgb(96,181,252);\n  icon-code:rgb(96,181,252);\n  icon-ppt:rgb(254,173,96);\n  icon-pdf:rgb(252,134,132);\n}\n// :root{\n//   each(@type, {\n//     --@{key}: @value;\n//   });\n// }\n\neach(@type, {\n  #@{key}{\n    color:~\"var(--@{key}-color,@{value})\";\n  }\n});"
  },
  {
    "path": "packages/sharelist-web/src/components/icon/index.ts",
    "content": "import { createFromIconfontCN } from '@ant-design/icons-vue'\nimport config from '../../config/setting'\nimport './icon-svg'\nimport './index.less'\nconst IconFont = createFromIconfontCN({\n  scriptUrl: [],\n})\n\nexport default IconFont\n"
  },
  {
    "path": "packages/sharelist-web/src/components/image/index.tsx",
    "content": "import { Image, Modal } from 'ant-design-vue'\n\nexport const showImage = (urls: Array<string>, index: number) => {\n  const onVisibleChange = (e: any) => {\n    console.log(e)\n    if (e === false) {\n      modal.destroy()\n    }\n  }\n  const modal = Modal.confirm({\n    class: 'hide-modal',\n    width: '500px',\n    closable: true,\n    content: (\n      <Image.PreviewGroup preview={{ visible: true, onVisibleChange }}  >\n        {\n          urls.map((url, idx) => <Image src={url} />)\n        }\n\n      </Image.PreviewGroup >\n    ),\n  })\n}"
  },
  {
    "path": "packages/sharelist-web/src/components/player/index.less",
    "content": ".widget-player{\n  position: fixed;\n  bottom:0;\n  opacity: 0;\n  pointer-events: none;\n  // transform:translate(-50%,0);\n  transition:all 0.3s;\n  // left:50%;\n  max-width: 560px;\n  left:0;right:0;\n  margin:auto;\n  &.widget-player--visible{\n    opacity: 1;\n    pointer-events: auto;\n    bottom:16px;\n  }\n\n  .widget-player__tip{\n    opacity: .64;\n    font-size:10px;\n  }\n\n  .widget-player__action{\n    display: flex;\n    align-items: center;\n    flex:none;\n  }\n\n  .widget-player__progress{\n    position: absolute;\n    display: none;\n    width: 0%;\n    height:3px;\n    left:0;\n    bottom: 0;\n    background-color: var(--plyr-color-main);\n    transition:all 0.3s;\n  }\n\n}\n\n.widget-player__list{\n  // max-height: 0;\n  height: 0;\n  display: flex;\n  flex-direction: column;\n  transition:all .5s cubic-bezier(0.66, 0, 0.01, 1);\n  opacity: 0;\n  &.widget-player__list--visible{\n    height: 260px;\n    opacity: 1;\n  }\n  .widget-player__list-header{\n    color:#fff;\n    padding:8px;\n    text-align: center;\n    border-bottom:1px solid rgba(132,133,141,.2)\n  }\n  .widget-player__list-body{\n    overflow-y: auto;\n    overflow-x: hidden;\n    color:var(--player-color-main);\n    font-size: 13px;\n    margin-bottom: 0;\n    &::-webkit-scrollbar-track {\n      // box-shadow: inset 0 0 1px rgba(0,0,0,0.2);\n      background-color: rgba(0,0,0,0);\n    }\n    \n    &::-webkit-scrollbar {\n      width: 5px;\n      // background-color: rgba(200,200,200,1);\n    }\n    &::-webkit-scrollbar-thumb {\n      background-color: rgba(200,200,200,.6);\n    }\n  }\n\n  .widget-player__list-item{\n    display: flex;\n    align-items: center;\n    padding:6px 16px;\n    cursor: pointer;\n    transition: all 0.3s;\n    &:hover{\n      background-color: var(--player-hover-background,rgba(0,0,0,0));\n    }\n    .widget-player__list-no{\n      flex:none;\n      padding-right: 8px;\n      text-align: right;\n      width:36px;\n    }\n  }\n\n  .widget-player__list-item--playing{\n    background-color: var(--player-fill,rgba(0,0,0,0)) !important;\n  }\n\n}\n\n.widget-player-wrap{\n  box-shadow: 0 1px 5px rgba(0,0,0,0.2);\n  background-color:var(--plyr-audio-background,#fff);\n  border-radius: 8px;\n  // overflow: hidden;\n\n  .widget-player__content{\n    display: none;\n    padding:0 12px;\n    color:var(--player-color-main);\n  }\n  .widget-player__body{\n    display: flex;\n    align-items: center;\n    justify-content: space-between;\n    flex:none;\n    position: relative;\n    z-index:1;\n    // background-color:var(--plyr-audio-background,#fff);\n\n    .widget-player__toggle-expand{\n      font-size:18px;\n      padding:0 12px;\n      cursor: pointer;\n      color:var(--plyr-audio-control-color,#000);\n    }\n    .widget-player__close{\n      flex:none;\n      font-size:18px;\n      padding:0 12px;\n      cursor: pointer;\n      color:var(--plyr-audio-control-color,#000);\n    }\n    \n    .widget-player__btn-full{\n      flex:none;\n      font-size:18px;\n      padding:0 12px;\n      cursor: pointer;\n      color:var(--plyr-audio-control-color,#000);\n      display: none;\n    }\n\n    .widget-player__download{\n      flex:none;\n      font-size:18px;\n      padding:0 12px;\n      cursor: pointer;\n      color:var(--plyr-audio-control-color,#000);\n    }\n  }\n}\n\n.widget-player-audio{\n  .plyr{\n    min-width:300px;\n  }\n}\n.widget-player-video{\n  overflow: hidden;\n  .widget-player__progress{\n    display: block;\n  }\n  .widget-player__content{\n    display: block;\n    cursor: pointer;\n    min-width:200px;\n    .widget-player__content-title{\n      display: -webkit-box;    \n      -webkit-box-orient: vertical;    \n      -webkit-line-clamp: 2;    \n      overflow: hidden;\n    }\n  }\n\n  .widget-player__btn-full{\n    display: block !important;\n  }\n    \n  &.widget-player--mini{\n    .plyr{\n      width:33.3%;\n      height:70px;\n      min-width:100px;\n      flex:none;\n      .plyr__controls{\n        display: none;\n      }\n      video{\n        object-fit: cover;\n      }\n      .plyr__video-wrapper{\n        margin: 0;\n        height: 100%;\n      }\n    }\n  }\n}\n\n.app-light{\n  --plyr-color-main:#00b3ff;\n  --plyr-audio-controls-background:rgba(0,0,0,0);\n  --plyr-audio-control-color:#ddd;\n  --plyr-control-radius:10px;\n\n  --plyr-audio-background:rgb(49,49,54);\n  --plyr-audio-control-background-hover:transparent;\n  --plyr-video-control-background-hover:transparent;\n  --plyr-video-background:rgba(0,0,0,0);\n  --player-color-main:#fff;\n\n  --player-hover-background:rgba(255,255,255,.1);\n\n  --player-fill:rgba(0,179,255,.5);\n\n  \n  &::-webkit-scrollbar-track {\n    // box-shadow: inset 0 0 1px rgba(0,0,0,0.2);\n    background-color: #000;\n  }\n  \n  &::-webkit-scrollbar {\n    width: 5px;\n    // background-color: rgba(200,200,200,1);\n  }\n  &::-webkit-scrollbar-thumb {\n    background-color: rgba(200,200,200,.6);\n  }\n}\n\n@media screen and (max-width:480px) {\n  .widget-player{\n    transform:translate(0,0);\n    left:0;width:100%;\n    bottom:-16px;\n    .widget-player-wrap{\n      border-radius: 0;\n    }\n    .widget-player-audio .plyr{\n      width:auto;\n      min-width:0px;\n    }\n    &.widget-player--visible{\n      bottom:0;\n    }\n    .widget-player__content{\n      min-width: 0;\n    }\n  }\n}"
  },
  {
    "path": "packages/sharelist-web/src/components/player/index.tsx",
    "content": "import Plyr from 'plyr'\nimport { ref, reactive, defineComponent, onMounted, onUnmounted, computed, watch, watchEffect } from 'vue'\nimport 'plyr/dist/plyr.css'\nimport './index.less'\nimport { OrderedListOutlined, CloseOutlined, FullscreenOutlined, DownloadOutlined } from '@ant-design/icons-vue'\nimport { useBoolean, useState } from '@/hooks/useHooks'\n\nconst playerMap = new Map()\nexport const usePlayer = (id?: number | string): any => {\n  if (id && playerMap.has(id)) {\n    return playerMap.get(id)\n  }\n\n  const newId = id || playerMap.size + 1\n\n  const removePlayer = () => playerMap.delete(id)\n\n  const [state, setPlayer] = useState({\n    list: [],\n    type: '',\n    index: 0,\n    cur: { name: '', ctimeDisplay: '' },\n  })\n\n  const instance = {\n    id: newId,\n    data: state,\n    setPlayer,\n    removePlayer\n  }\n\n  playerMap.set(newId, instance)\n\n  return instance\n}\n\nexport default defineComponent({\n  props: {\n    meidaId: {\n      type: Number,\n      required: true,\n    },\n  },\n\n  setup(props, ctx) {\n    const el = ref()\n\n    const { data, removePlayer } = usePlayer(props.meidaId)\n\n    const [visible, { setFalse: hidePlayer, setTrue: showPlayer }] = useBoolean()\n\n    const [visibleList, { toggle: toggleList }] = useBoolean()\n\n    const [fullscreen, { setFalse: existFullScreen, setTrue: enterFullScreen }] = useBoolean()\n\n    const playerProgress = ref('0%')\n\n    let player: any\n\n    const onClose = () => {\n      player.pause()\n      hidePlayer()\n      playerProgress.value = '0%'\n    }\n\n    const onSwitch = (idx: number) => {\n      const file: any = data.list[idx]\n      if (file) {\n        showPlayer()\n        data.index = idx\n        playerProgress.value = '0%'\n\n        player.source = {\n          type: data.type,\n          title: file.name,\n          sources: [{ src: file.preview_url || file.download_url, size: 'Raw' }],\n        }\n\n        data.cur = { ...file }\n        player.play()\n      }\n    }\n\n    const onFullScreen = () => {\n      enterFullScreen()\n      player.fullscreen.enter()\n    }\n\n    const onDownload = () => {\n      window.open(data.cur.download_url)\n    }\n\n    const onProgress = (e: any) => {\n      const plyr = e.detail.plyr\n      if (plyr.currentTime && plyr.duration) {\n        playerProgress.value = Math.floor((100 * plyr.currentTime) / plyr.duration) + '%'\n      }\n    }\n\n    const isIOS = /iphone|ipad|ipod/i.test(navigator.userAgent)\n\n    onMounted(() => {\n      player = new Plyr(el.value, {\n        fullscreen: { enabled: true, fallback: true, iosNative: isIOS, container: undefined }\n      })\n      player.on('exitfullscreen', existFullScreen)\n      player.on('timeupdate', onProgress)\n    })\n\n    onUnmounted(() => {\n      removePlayer()\n    })\n\n    watchEffect(() => {\n      onSwitch(data.index)\n    })\n\n    return () => (\n      <div class={['widget-player', visible.value ? 'widget-player--visible' : null]}>\n        <div\n          class={['widget-player-wrap', 'widget-player-' + data.type, !fullscreen.value ? 'widget-player--mini' : null]}\n        >\n          <div class={['widget-player__list', visibleList.value ? 'widget-player__list--visible' : null]}>\n            <div class=\"widget-player__list-header\">播放列表</div>\n            <ul class=\"widget-player__list-body\">\n              {data.list.map((i: any, idx: number) => {\n                return (\n                  <li\n                    class={['widget-player__list-item', data.index == idx ? 'widget-player__list-item--playing' : null]}\n                    onClick={() => onSwitch(idx)}\n                  >\n                    <div class=\"widget-player__list-no\">{idx + 1}.</div>\n                    <div>\n                      <div>{i.name}</div>\n                      <div class=\"widget-player__tip\">{i.ctimeDisplay}</div>\n                    </div>\n                  </li>\n                )\n              })}\n            </ul>\n          </div>\n          <div class={'widget-player__body'}>\n            <video ref={el}></video>\n            <div class=\"widget-player__content\" title={data.cur.name} onClick={onFullScreen}>\n              <div class=\"widget-player__content-title\">{data.cur.name}</div>\n              <div class=\"widget-player__tip\">{data.cur.ctimeDisplay}</div>\n            </div>\n            <div class=\"widget-player__action\">\n              <DownloadOutlined onClick={onDownload} class=\"widget-player__download\" />\n              <OrderedListOutlined onClick={toggleList} class=\"widget-player__toggle-expand\" />\n              <FullscreenOutlined class=\"widget-player__btn-full\" onClick={onFullScreen} />\n              <CloseOutlined onClick={onClose} class=\"widget-player__close\" />\n            </div>\n            <div class=\"widget-player__progress\" style={{ width: playerProgress.value }}></div>\n          </div>\n        </div>\n      </div>\n    )\n  },\n})\n"
  },
  {
    "path": "packages/sharelist-web/src/config/api.ts",
    "content": "export type IAPI = [\n  name: string,\n  url: string | ((...args: any[]) => string),\n  options?: {\n    [key: string]: number | string | boolean\n  },\n]\n\nconst api: IAPI[] = [\n  ['userConfig', 'GET /api/user_config'],\n  ['file', 'POST /api/drive/file/get', { token: true }],\n  ['files', 'POST /api/drive/file/list', { token: true }],\n  ['filePath', 'POST /api/drive/file/path', { token: true }],\n  // ['parents', 'GET /api/drive/files/:fileId/parents', { token: true }],\n]\nexport default api\n"
  },
  {
    "path": "packages/sharelist-web/src/config/setting.ts",
    "content": "export default {}\n"
  },
  {
    "path": "packages/sharelist-web/src/hooks/useApi.ts",
    "content": "import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios'\nimport { effectScope, EffectScope, App, InjectionKey, getCurrentInstance, inject } from 'vue'\n\nexport const apiSymbol = Symbol('api') as InjectionKey<IUseApi>\n\n// export type APIItemGroup = Array<[string, string | ((...args: Array<any>) => string), Record<string, any>]>\nexport type APIItem = [\n  name: string,\n  url: string | ((...args: Array<any>) => string),\n  options?: {\n    [key: string]: number | string | boolean | ((...rest: Array<any>) => any)\n  },\n]\nexport type APICall = (...rest: Array<any>) => Promise<AxiosResponse<ReqResponse>>\n\nexport interface IUseApi {\n  install?: (app: App) => void\n  _e?: EffectScope\n  _m?: any //Record<string, APICall>\n}\n\ntype RequestMethod = 'OPTIONS' | 'GET' | 'HEAD' | 'POST' | 'PUT' | 'DELETE' | 'TRACE' | 'CONNECT'\n\ntype ReqConfig = {\n  url: string\n  method: string\n  data?: any\n  params?: any\n  responseType?: string\n  token?: boolean\n  headers?: any\n}\n\naxios.defaults.timeout = 60 * 1000\n\nconst service: AxiosInstance = axios.create()\n\n// http response 拦截器\nservice.interceptors.response.use(\n  (response) => {\n    return response.data\n  },\n\n  (error) => {\n    // 由接口返回的错误\n    if (error.response) {\n      return { error: { code: error.response.status, message: error.response.statusText } }\n    } else {\n      log(`服务器错误！错误代码：${error}`)\n      return { error: { code: error?.code || 500, message: '' } }\n    }\n  },\n)\n\nconst log = (content: string, type = 'error'): void => {\n  console.log(content)\n}\n\nexport type ReqResponse = {\n  error?: { code: number; message?: string; scope?: Record<string, any> }\n  [key: string]: any\n  [key: number]: any\n}\n\ninterface APIOptions {\n  inScope?: boolean\n  baseURL?: string\n  onReq?: (d: Record<string, any>, itemOption: Record<string, any>) => void\n  onRes?: <T>(d: T) => void\n  onError?: (e: Error) => void\n}\n// const qs = (d: Record<string, string>) => Object.keys(d).map(i => `${i}=${encodeURI(d[i])}`).join('&')\n\nconst urlReplace = (url: string, params: Record<string, any>) =>\n  url.replace(/(?:\\:)([\\w\\$]+)/g, ($0, $1) => {\n    if ($1 in params) {\n      return params[$1]\n    } else {\n      return ''\n    }\n  })\n\nconst convFormData = (data: any) => {\n  const fd = new FormData()\n  for (const i in data) {\n    if (Array.isArray(data[i])) {\n      const item = []\n      data[i].forEach((j: any, idx: number) => {\n        fd.append(`${i}[${idx}]`, j)\n      })\n    } else {\n      fd.append(i, data[i])\n    }\n  }\n  return fd\n}\n\nexport const useApi = (options?: APIOptions) => {\n  if (globalApi) {\n    return globalApi._m as any\n  }\n\n  const currentInstance = getCurrentInstance()\n\n  const api = currentInstance && inject(apiSymbol)\n  if (!api) {\n    throw new Error(\n      'getActiveApi was called with no active api. Did you forget to install?\\n' +\n      '\\tconst api = createApi()\\n' +\n      '\\tapp.use(api)\\n' +\n      `This will fail in production.`,\n    )\n  }\n\n  return api._m as any\n}\n\nconst globalApi: IUseApi = {}\nexport const createApi = (apis: unknown, options?: APIOptions): IUseApi => {\n  type a = typeof apis\n  const pareKey: any = {}\n  for (const i of apis as Array<APIItem>) {\n    pareKey[i[0]] = 1\n  }\n  const apiMap: Record<keyof typeof pareKey, APICall> = {}\n\n  for (const i of apis as Array<APIItem>) {\n    // pareKey[apis[0]] =\n    apiMap[i[0]] = createRequest(i, options)\n  }\n\n  if (options?.inScope) {\n    const scope = effectScope(true)\n    const api: IUseApi = {\n      install(app: App) {\n        app.provide(apiSymbol, api)\n        app.config.globalProperties.$api = api\n      },\n      _e: scope,\n      _m: apiMap,\n    }\n    return api\n  } else {\n    globalApi._m = apiMap\n    return globalApi\n  }\n}\n\nfunction createRequest(api: APIItem, defaultOptions?: APIOptions): APICall {\n  return (...args: Array<any>) => {\n    const [name, url, options = {}] = api\n    const reqUrl = typeof url === 'function' ? url(...args) : url\n\n    let argsObj: Record<string, any> = {\n      $R: Math.random(),\n      $T: Date.now(),\n    }\n\n    if (typeof args[0] == 'object') {\n      argsObj = { ...argsObj, ...args[0] }\n    }\n\n    args.forEach((key, idx) => {\n      argsObj['$' + (idx + 1)] = key\n    })\n\n    const pairs = reqUrl.split(/\\s/)\n    const contentType = options.contentType || 'json'\n\n    const params: any = {\n      url: (defaultOptions?.baseURL || '') + urlReplace(pairs.slice(1).join(' '), argsObj),\n      method: <RequestMethod>pairs[0] || 'GET',\n      data: typeof args[0] == 'object' ? args[0] : {},\n      headers: options.header || {},\n    }\n\n    if (options.params) {\n      return params\n    }\n    // factory\n    if (typeof params.data?.customRequest == 'function') {\n      const ret: any = params.data.customRequest(params, options)\n      delete params.data.customRequest\n      if (ret) return ret\n      // return (params, options) => { }\n    }\n\n    if (contentType == 'formdata') {\n      params.headers['content-type'] = 'multipart/form-data'\n      if (params.data) {\n        params.data = convFormData(params.data)\n      }\n    } else if (contentType == 'stream') {\n      params.headers['content-type'] = 'application/octet-stream'\n      params.data = params.data.stream\n    } else {\n      params.headers['content-type'] = 'application/json'\n    }\n\n    defaultOptions?.onReq?.(params, options)\n\n    return service.request<ReqResponse>(params as AxiosRequestConfig)\n  }\n}\n"
  },
  {
    "path": "packages/sharelist-web/src/hooks/useDisk.ts",
    "content": "import { useApi, ReqResponse } from '@/hooks/useApi'\nimport { ref, Ref, watch, reactive, computed, inject, provide, getCurrentInstance, InjectionKey } from 'vue'\nimport { byte, getFileType, time } from '@/utils/format'\nimport { useLocalStorageState } from '@/hooks/useLocalStorage'\nimport { message } from 'ant-design-vue'\nimport { useRouter, useRoute, onBeforeRouteUpdate } from 'vue-router'\n\nexport type IFile = {\n  id: string\n  name: string\n  size: number\n  type: 'folder' | 'file' | 'drive'\n  ctime: number\n  mtime: number\n  path: string\n  extra?: Record<string, any>\n  [key: string]: any\n}\n\nconst formatItem = (i: IFile): IFile => {\n  i.ext = i.name.split('.').pop()\n  i.mediaType = getFileType(i.name, i.type)\n  i.ctimeDisplay = time(i.ctime)\n  i.sizeDisplay = byte(i.size)\n  return i\n}\nconst format = (d: Array<IFile>): Array<IFile> => {\n  d.forEach(formatItem)\n  return d\n}\n\nconst useFolderAuth = () => {\n  const data = useLocalStorageState<Record<string, any>>('auth', {})\n  const hasAuth = (path?: string) => path && !!data.value[path]\n  const addAuth = (path: string, v: any) => {\n    data.value[path] = v\n  }\n\n  const getAuth = (path?: string) => {\n    return path ? data.value[path] : undefined\n  }\n\n  const removeAuth = (path: string) => {\n    delete data.value[path]\n  }\n\n  //create a auth chain.\n  const geneAuth = (path = '') => {\n    const paths = path.split('/')\n    const ret: Record<string, string> = {}\n    for (let i = 0; i < paths.length; i++) {\n      const cur = paths.slice(0, i + 1).join('/')\n      if (data.value[cur]) {\n        const hit = data.value[cur]\n        ret[hit[0]] = hit[1]\n      }\n    }\n    return ret\n  }\n  return { hasAuth, addAuth, getAuth, geneAuth, removeAuth }\n}\n\ntype IUseDiskOption = {\n  id?: string\n  new?: boolean\n  routeSlient?: boolean\n  base?: string\n  filter?: (i: IFile) => boolean\n}\ntype IUseDisk = {\n  (options?: IUseDiskOption): any\n  [key: string]: any\n}\ntype IQuery = {\n  id?: string\n  name?: string\n  path?: string\n  search?: string\n  orderBy?: string\n  auth?: Record<string, string>\n}\n\ntype DiskState = {\n  id?: string\n  path?: string\n  search?: string\n  nextPage?: string\n  orderBy?: string\n}\n\ntype IUseDiskAction = any\n\nexport const diskSymbol = Symbol() as InjectionKey<IUseDiskAction>\nconst useDisk: IUseDisk = (diskOptions: IUseDiskOption = {}): any => {\n  if (inject(diskSymbol)) {\n    return inject(diskSymbol)\n  }\n\n  const request = useApi()\n  const router = useRouter()\n  const files: Ref<Array<any>> = ref([])\n  const loading = ref(false)\n  const error = reactive({ code: 0, message: '', scope: {} })\n  const diskConfig: Ref<any> = ref({})\n  const current = <DiskState>reactive({ id: undefined, path: undefined, search: '', nextPage: '', orderBy: '' })\n  const sortOption = useLocalStorageState<Record<string, any>>('ordeyBy', { key: 'name', type: 'asc' })\n\n  const { hasAuth, addAuth, getAuth, removeAuth, geneAuth } = useFolderAuth()\n\n  const basePath = diskOptions.base || ''\n  const paths = computed(() => {\n    const ret: Array<string> = current.path?.substring(1).split('/').filter(Boolean) || []\n    if (current.search) {\n      ret.push(`${current.search} 的搜索结果`)\n    }\n    return ret\n  })\n\n  let controller: AbortController\n\n  const getFiles = async (options: IQuery = {}, clear = false): Promise<any> => {\n    const stateChange = options.path != current.path || !!options.search || (!options.search && current.search)\n\n    loading.value = true\n\n    const params: Record<string, any> = {\n      id: options.id,\n      path: options.path,\n      order_by: sortOption.value.key + ' ' + sortOption.value.type,\n    }\n\n    const auth: Record<string, string> = geneAuth(options.path)\n\n    if (options.auth) {\n      auth[options.auth.id] = options.auth.token\n    }\n\n    if (options.search) {\n      params.search = options.search\n    }\n    params.auth = auth\n\n    if (current.nextPage) {\n      params.next_page = current.nextPage\n    }\n\n    const usePage = !!params.next_page\n\n    controller = new AbortController()\n    params.customRequest = (p: any) => {\n      p.signal = controller.signal\n    }\n\n    const res: ReqResponse = await request.files(params)\n    if (res.error) {\n      error.code = res.error.code\n      error.message = res.error.message as string\n      //校验\n      if (error.code == 401) {\n        if (error.message) {\n          message.error(error.message)\n        }\n        if (params.auth) {\n          removeAuth(params.path)\n        }\n        if (res.error?.scope) {\n          //多重目录校验\n          if (res.error.scope.id != options.id) {\n            const cur: ReqResponse = await request.filePath({ id: res.error.scope.id })\n            const path = cur.map((i: IFile) => i.name).join('/')\n            error.scope = { ...res.error.scope, path }\n          } else {\n            error.scope = { id: options.id, path: options.path }\n          }\n          // 目录校验通过 需要保存\n          if (options.auth && options.auth.id != res.error.scope.id) {\n            addAuth('/' + options.auth.path, [options.auth.id, options.auth.token])\n          }\n          current.id = options.id\n          current.path = options.path\n        }\n      } else {\n        current.path = options.path\n        current.id = options.id\n        error.message = res.error.message || ''\n      }\n    } else {\n      if (res.files) {\n        format(res.files)\n        if (clear) {\n          files.value = []\n        }\n\n        if (params.next_page) {\n          let appendData = res.files as Array<any>\n          if (diskOptions?.filter) {\n            appendData = appendData.filter(diskOptions?.filter)\n          }\n          files.value.push(...appendData)\n        } else {\n          files.value = diskOptions?.filter ? res.files.filter(diskOptions?.filter) : res.files\n        }\n      }\n\n      current.nextPage = res.nextPage\n\n      diskConfig.value = Object.assign(res.config || {}, { id: res.id })\n\n      current.path = options.path\n      current.id = options.id\n      current.search = options.search || undefined\n\n      error.code = 0\n      error.message = ''\n\n      // save/update\n      if (options.auth) {\n        addAuth('/' + options.auth.path, [options.auth.id, options.auth.token])\n      }\n    }\n    current.search = options.search || undefined\n\n    if (diskOptions?.routeSlient !== true && !usePage && stateChange) {\n      const target = options.path || ''\n\n      let url = (basePath + target).replace(/\\/+/g, '/')\n      if (current.search) {\n        url += '?search=' + current.search\n      }\n      router.push(url)\n    }\n\n    loading.value = false\n  }\n\n  const setPath = async ({ ...options }: IQuery = {}, reload = false) => {\n    if (options.search) {\n      reload = true\n    } else {\n      // if last route is search\n      if (current.search) {\n        reload = true\n      }\n    }\n\n    if (options.path == current.path && !options.auth && !reload) return\n\n    controller?.abort()\n\n    loading.value = true\n\n    if (options.id && !options.path) {\n      const resp = await request.filePath({ id: options.id })\n      options.path = '/' + resp.map((i: IFile) => i.name).join('/')\n    }\n\n    getFiles(options, true)\n  }\n\n  const setAuth = (auth: Record<string, string>) => {\n    setPath({ auth, id: current.id, path: current.path })\n  }\n\n  const setSort = (key: string) => {\n    if (key === sortOption.value.key) {\n      sortOption.value.type = sortOption.value.type == 'asc' ? 'desc' : 'asc'\n    } else {\n      sortOption.value.key = key\n      sortOption.value.type = 'asc'\n    }\n    //全部加载完毕时\n    setPath({ id: current.id, path: current.path, search: current.search }, true)\n  }\n\n  const loadMore = () => {\n    if (loading.value || !current.nextPage) {\n      return\n    }\n    getFiles({\n      id: current.id,\n      path: current.path,\n      search: current.search,\n    })\n  }\n\n  const mutate = (file: IFile, isRemove = false) => {\n    const idx = files.value.findIndex((i) => i.id == file.id)\n    if (idx >= 0) {\n      if (isRemove) {\n        files.value.splice(idx, 1)\n      } else {\n        files.value.splice(idx, 1, file)\n      }\n    } else {\n      files.value.unshift(formatItem(file))\n    }\n  }\n\n  const instance = {\n    setPath,\n    files,\n    paths,\n    loading,\n    error,\n    loadMore,\n    diskConfig,\n    mutate,\n    setAuth,\n    current,\n    setSort,\n    sortConfig: sortOption,\n  }\n\n  provide(diskSymbol, instance)\n\n  return instance\n}\n\nexport default useDisk\n"
  },
  {
    "path": "packages/sharelist-web/src/hooks/useHooks.ts",
    "content": "import { ref, reactive, Ref, UnwrapRef, onMounted, onUnmounted, getCurrentInstance } from 'vue'\n\nexport function safeOnMounted(hook: () => any): void {\n  const instance = getCurrentInstance()\n  console.trace()\n  if (instance?.isMounted || (instance as any)?._isMounted) {\n    hook()\n  } else {\n    onMounted(hook)\n  }\n}\n\ntype ToggleValue = number | string | boolean | undefined\n\nexport const useToggle = (defaultValue: ToggleValue = false, reverseValue: ToggleValue = undefined): any => {\n  if (reverseValue === undefined) {\n    reverseValue = !Boolean(defaultValue)\n  }\n\n  const state: Ref<ToggleValue> = ref(defaultValue)\n\n  const toggle = () => {\n    state.value = state.value === defaultValue ? reverseValue : defaultValue\n  }\n\n  const setLeft = () => (state.value = defaultValue)\n\n  const setRight = () => (state.value = reverseValue)\n\n  return { state, toggle, setLeft, setRight }\n}\n\nexport const useBoolean = (defaultValue = false): any => {\n  const state: Ref<boolean> = ref(defaultValue)\n\n  const toggle = () => {\n    state.value = state.value === true ? false : true\n  }\n\n  const setTrue = () => (state.value = true)\n\n  const setFalse = () => (state.value = false)\n\n  return [state, { toggle, setTrue, setFalse }]\n}\n\nexport const useObject = (defaultValue: Record<string, any>): [any, (k?: Array<string>) => void] => {\n  const state = reactive(defaultValue)\n\n  const clear = (excludes: Array<string> = []): void => {\n    Object.keys(state as Record<string, any>)\n      .filter((i) => !excludes.includes(i))\n      .forEach((key: string) => Reflect.deleteProperty(state as Record<string, any>, key))\n  }\n\n  return [state, clear]\n}\n\nexport const useState = <T extends Record<string, any>>(initialState: T = {} as T): [T, (state: T) => T, () => T] => {\n  const state = reactive(initialState)\n  const setState = (val: T, clear = false) => {\n    Object.keys(val).forEach((key) => {\n      Reflect.set(state, key, val[key])\n    })\n    return state as T\n  }\n\n  const clearState = () => {\n    Object.keys(state).forEach((key) => Reflect.deleteProperty(state, key))\n    return state as T\n  }\n\n  return [state as T, setState, clearState]\n}\n\nconst singleHook = new WeakMap()\n// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types\nexport const useSingle = (hook: any, args: Array<any>): any => {\n  if (singleHook.has(hook)) {\n    return singleHook.get(hook)\n  }\n  const res = hook.apply(hook, args)\n  singleHook.set(hook, res)\n  return res\n}\n\nconst useBoot = (cb: () => any): any => { }\n// const useBoot = (cb: any) => {\n//   useBoot.ready = true\n\n//   if (useBoot.ready) {\n//     cb()\n//   }\n// }\n\n// const useWindowEvent = (event: string) => {\n//   const handler = new Set()\n//   const eventMap = new Map()\n//   window.addEventListener(event, (event) => {\n//     console.log('location: ' + document.location + ', state: ' + JSON.stringify(event.state))\n//   })\n\n//   const onMessage = (cb: any) => {\n//     handler.add(cb)\n//   }\n\n//   const initListener = () => {\n//     if (document) {\n//       document.addEventListener('message', (e) => {\n//         console.log(e)\n//       })\n//     }\n//   }\n\n//   initListener()\n\n//   return {\n//     onMessage,\n//   }\n// }\n\ntype useTitleOptions = {\n  restoreOnUnmount: boolean\n}\nexport const useTitle = (title: string, options?: useTitleOptions): void => {\n  const lastTitle = ref('')\n\n  const run = () => {\n    lastTitle.value = document.title\n    document.title = title\n  }\n  safeOnMounted(() => {\n    run()\n  })\n\n  if (options?.restoreOnUnmount === true) {\n    onUnmounted(() => {\n      document.title = lastTitle.value\n    })\n  }\n\n  run()\n}\n\ninterface delayToggleActions {\n  true(immediate?: boolean): void\n  false(): void\n}\nexport const delayToggle = (setLeft: () => any, setRight: () => any, timeout = 200): delayToggleActions => {\n  let handler: number | null\n  return {\n    true(immediate = false) {\n      if (timeout === 0 || immediate) {\n        setLeft()\n      } else {\n        handler = setTimeout(() => {\n          setLeft()\n        }, timeout)\n      }\n    },\n    false() {\n      if (handler) {\n        clearTimeout(handler)\n        handler = null\n      }\n      setRight()\n    },\n  }\n}\n"
  },
  {
    "path": "packages/sharelist-web/src/hooks/useLocalStorage.ts",
    "content": "import { Ref, ref, watch } from 'vue'\n\nexport type LocalStateKey = string\n\nexport function useLocalStorageState<T = any>(key: LocalStateKey, defaultValue?: T | (() => T)): any {\n  const raw = localStorage.getItem(key)\n  if (raw) {\n    try {\n      defaultValue = JSON.parse(raw)\n    } catch {\n      //\n    }\n  }\n  if (typeof defaultValue === 'function') {\n    defaultValue = (defaultValue as () => T)()\n  }\n  const state = ref(defaultValue) as Ref<T | undefined>\n\n  const setState = () => {\n    localStorage.setItem(key, JSON.stringify(state.value))\n  }\n\n  watch(\n    state,\n    (nv) => {\n      setState()\n    },\n    { deep: true, immediate: false },\n  )\n\n  return state\n}\n"
  },
  {
    "path": "packages/sharelist-web/src/hooks/useScroll.ts",
    "content": "import { ref, Ref, reactive, UnwrapRef } from 'vue'\n\ninterface RequestOptions<T, P> {\n  immediate?: boolean\n  isNoMore?: (data?: T) => boolean\n  onSuccess?: (data: T) => void\n  onError?: (e: Error) => void\n  target?: Ref<Element>\n  threshold?: number\n}\n\ninterface actions<T> {\n  setNode(el: Element, target: Element | Window): void\n  scrollTo(pos: number): void\n  cancel: () => void\n  isScroll: Ref<boolean>\n}\n\ntype useScrollOption = {\n  list: any[]\n  [key: string]: any\n}\nexport const useScroll = <T extends useScrollOption, P extends Array<any>>(\n  service: (args?: T) => Promise<T>,\n  options: RequestOptions<T, P> = {},\n): actions<T> => {\n  let el: Element\n  const isScroll = ref(false)\n  const setNode = (v: Element, target: Element | Window) => {\n    if (el) {\n      cancel()\n    }\n    el = v\n    target.addEventListener('scroll', onDomScroll)\n  }\n\n  const cancel = () => {\n    el?.removeEventListener('scroll', onDomScroll)\n  }\n\n  const onDomScroll = throttle(() => {\n    const { clientHeight, scrollTop, scrollHeight } = el\n    isScroll.value = scrollTop > 0\n    if (scrollHeight - scrollTop - clientHeight < (options?.threshold || 100)) {\n      service()\n    }\n  }, 200)\n\n  const scrollTo = (v: number) => {\n    if (el) {\n      el.scrollTo({ top: v })\n    }\n  }\n  return {\n    setNode,\n    cancel,\n    scrollTo,\n    isScroll,\n  }\n}\n\nconst throttle = function (fn: () => any, delay = 0) {\n  let now: number,\n    last = 0,\n    timer: number | null,\n    context: any,\n    args: Array<any> | null\n\n  const later = function () {\n    last = now\n    fn.apply(context, <[]>args)\n    timer = null\n    context = args = null\n  }\n\n  const listen = function (this: any, ...rest: Array<any>) {\n    args = rest\n\n    now = Date.now()\n\n    //剩余时间\n    const remaining = delay - (now - last)\n\n    if (remaining <= 0 || remaining > delay) {\n      if (timer) {\n        clearTimeout(timer)\n        timer = null\n      }\n\n      last = now\n\n      fn.apply(this, <[]>rest)\n      if (!timer) context = args = null\n    } else if (!timer) {\n      timer = setTimeout(later, remaining)\n    }\n  }\n\n  return listen\n}\n"
  },
  {
    "path": "packages/sharelist-web/src/hooks/useSetting.ts",
    "content": "import { ref, Ref, reactive, unref, readonly, toRaw } from 'vue'\nimport { useApi, ReqResponse } from '@/hooks/useApi'\n\ntype IUseConfig = {\n  (): any\n  instance?: any\n}\n\nexport const useConfig: IUseConfig = (): any => {\n  if (useConfig.instance) {\n    return useConfig.instance\n  }\n  const config: Record<string, any> = reactive({})\n  const request = useApi()\n  request.userConfig().then((resp: ReqResponse) => {\n    if (!resp.error) {\n      for (const i in resp.data) {\n        config[i] = resp.data[i]\n      }\n      if (config.version) {\n        console.log(\n          `%c sharelist %c ${config.version} %c https://github.com/reruin/sharelist`,\n          'color: #fff; background: #5f5f5f',\n          'color: #fff; background: #4bc729',\n          '',\n        )\n      }\n    }\n  })\n\n  useConfig.instance = { config }\n\n  return config\n}\n"
  },
  {
    "path": "packages/sharelist-web/src/hooks/utils.ts",
    "content": "import { getCurrentInstance, isRef, onMounted as vueOnMounted, onUnmounted as vueOnUnmounted, Ref } from 'vue'\n\nexport const onMounted = (cb: () => any): void => {\n  const instance = getCurrentInstance()\n  if (instance) {\n    if (instance?.isMounted) {\n      cb()\n    } else {\n      vueOnMounted(cb)\n    }\n  }\n}\n\nexport function onUnmounted(cb: () => any): void {\n  if (getCurrentInstance()) {\n    vueOnUnmounted(cb)\n  }\n}\n"
  },
  {
    "path": "packages/sharelist-web/src/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n  <meta charset=\"UTF-8\" />\n  <link rel=\"icon\" href=\"/favicon.ico\" />\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n  <meta name=\"referrer\" content=\"never\">\n  <link rel=\"stylesheet\" href=\"/api/config/style?content-type=text/css\" type=\"text/css\" charset=\"utf-8\" />\n  <!-- <script src=\"/api/configs\"></script> -->\n  <!-- <link rel=\"stylesheet\" href=\"/assets/style.css\" type=\"text/css\" charset=\"utf-8\" /> -->\n  <title>sharelist</title>\n</head>\n\n<body>\n  <div id=\"app\"></div>\n  <script type=\"module\" src=\"/main.ts\"></script>\n  <script src=\"/api/config/script?content-type=application/x-javascript\"></script>\n</body>\n\n</html>"
  },
  {
    "path": "packages/sharelist-web/src/main.ts",
    "content": "import { createApp, h } from 'vue'\nimport App from './App'\nimport store from './store'\nimport router from './router'\nimport { message, Spin, ConfigProvider } from 'ant-design-vue'\nimport apis from '@/config/api'\nimport { createApi } from '@/hooks/useApi'\nimport { createPinia } from 'pinia'\nimport piniaPersist from 'pinia-plugin-persist'\n// import 'ant-design-vue/es/style/index.less'\n\nimport '@/assets/style/index.less'\n\nimport { LoadingOutlined } from '@ant-design/icons-vue'\n\nSpin.setDefaultIndicator({\n  indicator: h(LoadingOutlined, {\n    style: {\n      fontSize: '24px',\n    },\n    spin: true,\n  }),\n})\n\nConfigProvider.config({\n  theme: {\n    primaryColor: 'rgb(100,58,218)',\n  },\n  autoInsertSpaceInButton: false,\n})\n\nconst pinia = createPinia()\npinia.use(piniaPersist)\ncreateApi(apis)\n\ncreateApp(App).use(router).use(pinia).provide('$message', message).mount('#app')\n"
  },
  {
    "path": "packages/sharelist-web/src/router/index.ts",
    "content": "import { defineAsyncComponent } from 'vue'\nimport { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router'\n\nconst routes: Array<RouteRecordRaw> = [\n  {\n    path: '/:path(.*)',\n    name: 'home',\n    component: () => import('../views/home'),\n  },\n]\nconst router = createRouter({\n  history: createWebHistory(),\n  routes,\n})\n\nexport default router\n"
  },
  {
    "path": "packages/sharelist-web/src/store/index.ts",
    "content": "import { defineStore } from 'pinia'\n\nexport default defineStore('main', {\n  state: () => ({\n    theme: 'light',\n    layout: 'list',\n    sort: ['name', 'asc'],\n  }),\n\n  actions: {\n    setLayout(val: string) {\n      this.layout = val\n    },\n  },\n\n  persist: {\n    enabled: true,\n  },\n})\n"
  },
  {
    "path": "packages/sharelist-web/src/types/IDrive.ts",
    "content": "type DrivePath = {\n  protocol: string\n  [key: string]: string | number\n}\n\ndeclare type DriverField = {\n  key: string\n  label: string\n  value?: string | number | boolean\n  options?: Array<any>\n  type?: 'string' | 'hidden' | 'number' | 'boolean' | 'list'\n  help?: string\n  fields?: Array<DriverField>\n  required?: boolean\n}\n\ndeclare type IDrive = {\n  name: string\n  path: DrivePath\n}\n\ndeclare type IPlugin = {\n  name: string\n  id: string\n  [key: string]: string | number\n}\n\ndeclare type DriverGuide = {\n  key?: string\n  label?: string\n  fields: Array<DriverField>\n}\n\ndeclare type Driver = {\n  protocol: string\n  name?: string\n  guide?: Array<DriverField>\n}\n"
  },
  {
    "path": "packages/sharelist-web/src/types/IRequest.ts",
    "content": ""
  },
  {
    "path": "packages/sharelist-web/src/types/shim.d.ts",
    "content": "declare module '*.vue' {\n  import { DefineComponent } from 'vue'\n  const component: DefineComponent<{}, {}, any>\n  export default component\n}\n\ndeclare module 'aplayer' {\n  const aplayer: any\n  export default aplayer\n}\n\ndeclare type ISetting = {\n  title?: string\n  index_enable?: boolean\n  default_ignores?: Array<string>\n  [key: string]: any\n}\n"
  },
  {
    "path": "packages/sharelist-web/src/types/source.d.ts",
    "content": "declare module '*.json'\ndeclare module '*.png'\ndeclare module '*.jpg'\ndeclare module 'vue-infinite-scroll'\n"
  },
  {
    "path": "packages/sharelist-web/src/utils/format.ts",
    "content": "const EXT_IMAGE = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'tiff', 'wmf', 'tif', 'svg', 'webp']\n\nconst EXT_AUDIO_SUPPORT = ['mp3', 'm4a', 'acc', 'wav', 'ogg', 'flac']\n\nconst EXT_VIDEO_SUPPORT = ['mp4', 'mpeg', '3gp', 'mkv']\n\nexport const getFileType = (v: string, type = 'file'): string => {\n  if (type == 'folder' || type == 'drive') {\n    return type\n  } else {\n    if (v) v = v.toLowerCase().split('.').pop() || ''\n    if (['mp4', 'mpeg', 'wmv', 'webm', 'avi', 'rmvb', 'mov', 'mkv', 'f4v', 'flv'].includes(v)) {\n      return 'video'\n    } else if (['mp3', 'm4a', 'wav', 'wma', 'ape', 'flac', 'ogg'].includes(v)) {\n      return 'audio'\n    } else if (['doc', 'docx', 'wps'].includes(v)) {\n      return 'word'\n    } else if (['ppt', 'pptx'].includes(v)) {\n      return 'ppt'\n    } else if (['pdf'].includes(v)) {\n      return 'pdf'\n    } else if (['xls', 'xlsx', 'pdf', 'txt', 'yaml', 'yml', 'ini', 'cfg', 'xml', 'md'].includes(v)) {\n      return 'doc'\n    } else if (EXT_IMAGE.includes(v)) {\n      return 'image'\n    } else if (\n      [\n        'js',\n        'ts',\n        'css',\n        'html',\n        'c',\n        'h',\n        'cpp',\n        'py',\n        'java',\n        'jsp',\n        'php',\n        'cs',\n        'go',\n        'swift',\n        'vue',\n        'rs',\n        'asp',\n        'sql',\n      ].includes(v)\n    ) {\n      return 'code'\n    }\n    // else if (['zip', 'rar', '7z', 'tar', 'gz', 'gz2'].includes(v)) {\n    //   return 'archive'\n    // }\n    else {\n      return 'file'\n    }\n  }\n}\n\nexport const byte = (v: number, fixed?: number): string => {\n  if (v === undefined || v === null || isNaN(v)) {\n    return '-'\n  }\n\n  let lo = 0\n\n  while (v >= 1024) {\n    v /= 1024\n    lo++\n  }\n\n  const val = Math.floor(v * 100) / 100\n\n  return (fixed ? val.toFixed(fixed) : val) + ' ' + ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB'][lo]\n}\n\nconst fix0 = (v: any) => (v > 9 ? v : '0' + v)\n\nexport const time = (v: number): string => {\n  if (!v) return '-'\n  const date = new Date(v)\n  const thisYear = new Date().getFullYear()\n  let ret: string =\n    fix0(date.getMonth() + 1) + '/' + fix0(date.getDay()) + ' ' + fix0(date.getHours()) + ':' + fix0(date.getMinutes())\n  if (thisYear != date.getFullYear()) {\n    ret = date.getFullYear() + '/' + ret\n  }\n\n  return ret\n}\n\nexport const isMediaSupport = (name: string, type: 'audio' | 'video' | 'image'): boolean => {\n  const ext: string = name.split('.').pop() || ''\n  if (type == 'audio' && EXT_AUDIO_SUPPORT.includes(ext)) {\n    return true\n  } else if (type == 'video' && EXT_VIDEO_SUPPORT.includes(ext)) {\n    return true\n  } else if (type == 'image' && EXT_IMAGE.includes(ext)) {\n    return true\n  }\n  return false\n}\n\n// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types\nexport const isType = (type: string) => (obj: any) => Object.prototype.toString.call(obj) === `[object ${type}]`\n\nexport const isArray = isType('Array')\n\nexport const isObject = isType('Object')\n\nexport const isBlob = isType('Blob')\n\nexport const isString = (v: string): boolean => typeof v == 'string'\n\nexport const getBlob = (data: string, filename: string): Blob | undefined => {\n  let blob\n  try {\n    blob = new Blob([data], { type: 'application/octet-stream' })\n  } catch (e) {\n    /**/\n  }\n  return blob\n}\n\nexport const saveFile = (data: Blob | string, filename: string): void => {\n  let blob: Blob | undefined\n  if (isString(data as string)) {\n    blob = getBlob(data as string, filename)\n  } else {\n    blob = data as Blob\n  }\n\n  if (isBlob(blob)) {\n    const URL = window.URL || window.webkitURL\n\n    const link = document.createElement('a')\n\n    link.href = URL.createObjectURL(blob)\n    link.download = filename\n\n    const evt = document.createEvent('MouseEvents')\n    evt.initEvent('click', false, false)\n    // link.click()\n    link.dispatchEvent(evt)\n    URL.revokeObjectURL(link.href)\n  }\n}\n"
  },
  {
    "path": "packages/sharelist-web/src/views/home/index.less",
    "content": ".drive-search{\n  max-width:560px;\n  width:90%;\n  margin: auto;\n  transform: translate(0,20vh);\n}\n.drive-body{\n  padding:12px 28px;\n  flex:1 1 auto;\n  .item{\n    display: flex;\n    width:100%;\n    justify-content: space-between;\n    align-items: center;\n    padding:8px 8px;\n    transition: all 0.3s;\n    margin-bottom:1px;\n    cursor: pointer;\n    position: relative;\n    color:var(--primary-text-color);\n    &:hover{\n      background-color: var(--context-hover);\n      .item-check{\n        opacity: 1;\n      }\n    }\n    .item-check{\n      padding:0 8px;\n      opacity: 0;\n      transition:opacity 0.3s;\n      \n    }\n    \n    &.item--checked{\n      background-color: var(--primary-hover-theme-color);\n      .item-check{\n        opacity: 1;\n      }\n    }\n   \n    .item-icon__ext--md{\n      display: none;\n    }\n    .item-icon__ext--sm{\n      display: none;\n    }\n    .item-info{\n      padding:0 6px;\n      color:rgba(0,0,0,.5);\n      flex:0 0 auto;\n      position:absolute;\n      right:0;\n      top:0;\n    }\n    .item-name{\n      flex: 1;\n      min-width: 0;\n      width:100%;\n      overflow: hidden;\n      text-overflow: ellipsis;\n      white-space: nowrap;\n      // display: flex;\n      position: relative;\n    }\n    .item-icon{\n      margin-right: 16px;\n      flex:none;\n      width:36px;\n      position: relative;\n    }\n\n    .item-thumb{\n      width:100%;\n      height:30px;\n      background-repeat: no-repeat;\n      background-size:cover;\n      background-position: center center;\n      flex:none;\n      border-radius: 5px;\n    }\n    \n    .item-icon__ext{\n      position: absolute;\n      font-size: 12px;\n      bottom: 5px;\n      color:inherit;\n      text-transform: uppercase;\n      transform: scale(0.8);\n      transform-origin:center;\n      // width:42px;\n      text-align: center;\n      width:100%;\n      font-weight: bold;\n      // transform-origin: left;\n      // left: 16px;\n    }\n   \n    .item-meta{\n      flex:auto;\n      display: flex;\n      justify-content:space-between;\n      align-items: center;\n      overflow: hidden;\n      color:inherit;\n    }\n    .item-ctime{\n      flex: 0 0 auto;\n      // padding:0 24px;\n      // width: 200px;\n      color:var(--context-2);\n      font-size: 12px;\n      text-align: left;\n    }\n    \n    .item-size{\n      // color:rgba(37, 38, 43, 0.72);\n      color:var(--context-2);\n      font-size: 12px;\n      text-align: right;\n      flex:0 0 80px;\n    }\n  }\n}\n\n\n.drive-body--grid{\n  display: grid;\n  grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));\n  grid-gap: 8px;\n  .item{\n    padding-left:8px;\n    padding-right:8px;\n    flex-direction: column;\n    .item-check{\n      position: absolute;\n      right:0px;\n      top:0px;\n    }\n    .item-icon{\n      width:auto;\n      margin-right:0;\n      margin-bottom:12px;\n    }\n\n    .item-meta{\n      flex-direction: column;\n      // align-items: flex-start;\n      align-items: center;\n      width:100%;\n      overflow: hidden;\n    }\n    .item-thumb{\n      height:64px;\n      width:96px;\n    }\n    .item-name{\n      text-align: center;\n      font-size:13px;\n    }\n    .item-ctime{\n      // display: none;\n      color:var(--context-3);\n    }\n    .item-size{\n      display: none;\n    }\n    .item-icon__ext{\n      font-size:15px;\n    }\n  }\n}\n\n.drive-header{\n  padding:12px 40px;\n  background-color:var(--background-header);\n  color: var(--primary-text-color);\n  font-size: 21px;\n  overflow: hidden;\n  display: flex;\n  justify-content: space-between;\n  // border-bottom: 1px solid var(--context-4);\n  // position: sticky;\n  top:0;\n  z-index:1;\n  .drive-header__name{\n    cursor: pointer;\n  }\n  .drive-action{\n    color:var(--context-2);\n  }\n  .drive-action-search{\n    margin-right:16px;\n  }\n\n}\n\n.drive-routes{\n  padding:16px 40px 0px;\n  // background-color: #c1c1c1;\n  .ant-breadcrumb-link{\n    // background-color: rgb(66, 133, 244);\n    // border-radius: 20px;\n    // color:#fff;\n    // padding:6px 8px;\n    // a{\n    //   color: #fff;\n    // }\n  }\n}\n\n.drive-routes--hidden{\n  display: none;\n}\n\nfooter{\n  border-top: 1px solid var(--context-4);\n  margin: 0 15px;\n  text-align: center;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  height:7vh;\n  a{\n    padding:0 12px;\n    color:var(--context-2);\n  }\n  p{\n    margin-bottom: 0;\n  }\n}\n\n.layout{\n  min-height: 93vh;\n}\n@media screen and (max-width:480px) {\n  .drive-header{\n    padding:12px;\n  }\n  .drive-routes{\n    padding: 12px 12px 0px;\n  }\n  .drive-body{\n    padding:12px 0;\n    &.drive-body--grid {\n      display: grid;\n      grid-template-columns: 100%;\n      grid-gap: 0;\n    }\n\n    .item{\n      padding:8px;\n    }\n  }\n}"
  },
  {
    "path": "packages/sharelist-web/src/views/home/index.tsx",
    "content": "import { onMounted, onUnmounted, defineComponent, ref, reactive, toRef, watch, withModifiers } from 'vue'\nimport { Spin, Modal } from 'ant-design-vue'\nimport Icon from '@/components/icon'\nimport useDisk, { IFile } from '@/hooks/useDisk'\nimport './index.less'\nimport Header from './partial/header'\nimport { isMediaSupport } from '@/utils/format'\nimport MediaPlayer, { usePlayer } from '@/components/player'\nimport Breadcrumb from './partial/breadcrumb'\nimport Error from './partial/error'\nimport { InfoCircleOutlined, GithubOutlined, LoadingOutlined } from '@ant-design/icons-vue'\nimport { useConfig } from '@/hooks/useSetting'\nimport { useScroll } from '@/hooks/useScroll'\nimport { useRoute } from 'vue-router'\nimport useStore from '@/store/index'\nimport { showImage } from '@/components/image'\n\nexport default defineComponent({\n  setup() {\n    const state = useStore()\n\n    const { config } = useConfig()\n\n    const { loading, files, error, setPath, paths, loadMore, diskConfig, current: currentDisk, setAuth, setSort, sortConfig, onUpdate } = useDisk()\n\n    const { id: mediaId, setPlayer } = usePlayer('player')\n\n    const route = useRoute()\n\n    const { scrollTo, setNode, cancel: cancelScroll, isScroll } = useScroll(loadMore)\n\n    const driveEl = ref()\n\n    onMounted(() => {\n      setNode(document.documentElement, window)\n    })\n\n    onUnmounted(() => {\n      cancelScroll?.()\n    })\n\n    const onTagClick = ({ path, index }: any = {}) => {\n      if (path == '/') {\n        setPath({ path: '/' })\n      } else if (index < paths.value.length) {\n        setPath({ path: '/' + paths.value.slice(0, index).join('/') })\n      }\n    }\n\n\n    const onClick = (data: IFile) => {\n      if (data.type == 'folder' || data.type == 'drive') {\n        let target: any = {\n          id: data.id\n        }\n\n        if (!data.path && !currentDisk.search) {\n          target.path = (currentDisk.path || '') + '/' + data.name\n        }\n        setPath(target)\n      } else if (data.type == 'file') {\n        let mediaType = data.mediaType\n        if ((data.mediaType == 'audio' || data.mediaType == 'video') && isMediaSupport(data.name, mediaType)) {\n\n          const list: Array<IFile> = files.value.filter((i: IFile) => isMediaSupport(i.name, mediaType))\n          setPlayer({\n            list,\n            type: mediaType,\n            index: list.findIndex((i: IFile) => i.id == data.id),\n          })\n\n        }\n        else if (data.mediaType == 'image') {\n          const list: Array<IFile> =\n            files.value.filter((i: IFile) => isMediaSupport(i.name, 'image'))\n\n          showImage(list.map(i => i.download_url), list.findIndex(i => i.id == data.id))\n\n        }\n        else {\n          window.open(data.download_url)\n        }\n      }\n    }\n\n\n    watch(\n      route,\n      (nv) => {\n        let target: any = {\n          path: '/' + route.params.path as string\n        }\n        if (route.query.search) {\n          target.search = route.query.search\n        }\n        setPath(target)\n        scrollTo(0)\n      },\n      { immediate: true },\n    )\n\n    return () => (\n      [<div class=\"layout app-light\" ref={driveEl}>\n        <Header />\n        <Breadcrumb onTagClick={onTagClick} paths={paths.value} />\n        <Spin spinning={loading.value} >\n          <Error value={error} onAuth={setAuth}>\n            {\n              <div class={['drive-body', 'drive-body--' + state.layout]} >\n\n                {files.value.map((i: IFile) => {\n                  return (\n                    <a class=\"item\" href={paths.value.join('/') + '/' + i.name + '?download'} title={i.name} onClick={withModifiers(() => onClick(i), ['prevent'])}>\n\n                      <div class=\"item-icon\">\n                        {\n                          i.thumb ?\n                            <div class=\"item-thumb\" style={{ 'backgroundImage': `url(${i.thumb})` }}></div>\n                            : [<Icon\n                              style={{ fontSize: state.layout == 'grid' ? '64px' : '36px' }}\n                              type={'icon-' + i.mediaType}\n                            />,\n                            i.mediaType == 'file' ? <div class={[\"item-icon__ext\", i.ext.length > 7 ? 'item-icon__ext--sm' : i.ext.length > 4 ? 'item-icon__ext--md' : '']}>{i.ext}</div> : null\n                            ]\n                        }\n                      </div>\n\n                      <div class=\"item-meta\">\n                        <div class=\"item-name\">{i.name}</div>\n                        <div class=\"item-ctime\">{i.ctimeDisplay}</div>\n                        <div class=\"item-size\">{i.sizeDisplay}</div>\n                      </div>\n\n                    </a>\n                  )\n                })}\n              </div>\n            }\n          </Error>\n        </Spin >\n        <div class=\"widget\">\n          <MediaPlayer meidaId={mediaId} />\n        </div>\n      </div>,\n      <footer>\n        <p>\n          <a href=\"https://github.com/reruin/sharelist\" target=\"_blank\">\n            <GithubOutlined style=\"margin-right:8px;\" />GitHub\n          </a>\n        </p>\n      </footer>]\n    )\n  },\n})\n"
  },
  {
    "path": "packages/sharelist-web/src/views/home/partial/auth/index.less",
    "content": ".auth-box{\n  height:70vh;\n  width:100%;\n  display:flex;\n  align-items:center;\n  justify-content:center;\n  flex-direction:column;\n  box-sizing:border-box;\n\n  .auth-box__wrap{\n    width:320px;\n  }\n  .auth-box__header{\n    font-size:14px;\n    word-wrap: break-word;\n    word-break: break-all;\n    color:var(--primary-text-color);\n    margin-bottom:24px;\n  }\n  .auth-box__input{\n    padding:8px 11px;\n  }\n  .auth-box__btn{\n    width:100%;\n    margin-top:24px;\n    height:42px;\n  }\n}\n\n"
  },
  {
    "path": "packages/sharelist-web/src/views/home/partial/auth/index.tsx",
    "content": "import { ref, defineComponent, watch, onMounted, toRef, watchEffect, reactive } from 'vue'\nimport { Button, Input } from 'ant-design-vue'\nimport useDisk from '@/hooks/useDisk'\nimport { LockOutlined } from '@ant-design/icons-vue'\nimport './index.less'\n\nexport default defineComponent({\n  props: {\n    message: {\n      type: String\n    },\n    scope: {\n      type: Object\n    }\n  },\n  emits: ['auth'],\n  setup(props) {\n    console.log('auth init disk')\n    const { setAuth } = useDisk()\n\n    const token = ref('')\n    const onEnter = () => {\n      let set: Record<string, string> = {\n        token: token.value\n      }\n      setAuth({ id: props.scope?.id, path: props.scope?.path, token: token.value })\n    }\n\n    watch(() => props.scope, () => {\n      token.value = ''\n    })\n\n    return () => (\n      <div class=\"auth-box\">\n\n        <div class=\"auth-box__wrap\">\n          {\n            props.scope?.path ? <h2 class=\"auth-box__header\">{props.scope?.path} 需要身份验证</h2> : null\n          }\n          <Input.Password\n            class=\"auth-box__input\"\n            value={token.value}\n            onChange={(e) => (token.value = e.target.value as string)}\n            placeholder=\"输入目录访问密码\"\n            onPressEnter={onEnter}\n          >\n            {{\n              prefix: () => <LockOutlined />\n            }}\n          </Input.Password>\n          <Button class=\"auth-box__btn\" type=\"primary\" onClick={onEnter}>\n            确定\n          </Button>\n        </div>\n      </div>\n    )\n  },\n})\n"
  },
  {
    "path": "packages/sharelist-web/src/views/home/partial/breadcrumb/index.less",
    "content": ".drive-breadcrumb{\n  padding:12px 40px;\n  a{\n    // color:var(--primary-color);\n    font-weight: 600;\n    max-width: 100%;\n    overflow: hidden;\n    white-space: nowrap;\n    -o-text-overflow: ellipsis;\n    text-overflow: ellipsis;\n    font-size:18px;\n  }\n  .ant-breadcrumb{\n    color:var(--context-3);\n    a,.ant-breadcrumb-separator{\n      color:var(--context-3);\n    }\n    a:hover{\n      color:var(--primary-color);\n    }\n  }\n  .ant-breadcrumb > span:last-child a {\n    color: var(--primary-text-color);\n  }\n\n  &.drive-breadcrumb--sm{\n    a{font-size: 12px;}\n  }\n}\n\n.drive-breadcrumb-pop-item{\n  padding:8px 16px;\n  &:hover{\n    background-color: var(--primary-hover-bg-color);\n    transition: all 0.3s;\n    cursor: pointer;\n  }\n}\n\n.drive-breadcrumb-wrap{\n  overflow-x: visible;\n}"
  },
  {
    "path": "packages/sharelist-web/src/views/home/partial/breadcrumb/index.tsx",
    "content": "import { ref, defineComponent, reactive, onMounted, onUnmounted, computed, watchEffect, PropType, watch } from 'vue'\nimport Icon from '@/components/icon'\nimport { Breadcrumb, Popover, List } from 'ant-design-vue'\nimport { EllipsisOutlined } from '@ant-design/icons-vue'\n\nimport './index.less'\nexport default defineComponent({\n  props: {\n    paths: {\n      type: Array as PropType<Array<string>>\n    },\n    size: {\n      type: String,\n      default: 'default'\n    }\n  },\n  emit: ['tagClick'],\n  setup(props, ctx) {\n\n    const el = ref()\n\n    const defaultClietHeight = ref(0)\n    const lastClientWidth = ref(0)\n    const onclick = (path: string, idx: number) => {\n      ctx.emit('tagClick', { path, index: idx })\n    }\n\n    let ellipsisRange = ref(1)\n\n    const onUpdate = () => {\n      let { clientWidth, clientHeight } = el.value\n      if (defaultClietHeight.value != clientHeight) {\n        ellipsisRange.value++\n        lastClientWidth.value = clientWidth\n      }\n    }\n\n    let observer: ResizeObserver | null\n    onMounted(() => {\n      if (el.value) {\n        if (!defaultClietHeight.value) {\n          defaultClietHeight.value = el.value.clientHeight\n        }\n        observer = new ResizeObserver(entries => {\n          onUpdate() // entries[0].contentRect\n        })\n        observer.observe(el.value)\n      }\n      window.addEventListener('resize', onUpdate)\n    })\n\n    onUnmounted(() => {\n      observer?.disconnect()\n      observer = null\n      window.removeEventListener('resize', onUpdate)\n\n    })\n\n    watch(() => props.paths, (nv, ov) => {\n      let nvl = nv?.length || 0\n      let ovl = ov?.length || 0\n      if (nvl < ovl) {\n        ellipsisRange.value = 1\n      }\n      onUpdate()\n    })\n\n    const createItem = () => {\n      const nodes = []\n      const paths = props.paths || []\n      if (paths.length == 0) return []\n\n      nodes.push(\n        <Breadcrumb.Item onClick={() => onclick(paths[0], 1)}>\n          <a>{paths[0]}</a>\n        </Breadcrumb.Item>\n      )\n      if (ellipsisRange.value > 1) {\n        let dataSrc = paths.slice(1, 1 + ellipsisRange.value)\n        nodes.push(\n          <Breadcrumb.Item><Popover overlayClassName='popover-padding-0' overlayStyle={{ 'padding': '8px' }} placement=\"topRight\" destroyTooltipOnHide={true} arrowPointAtCenter={true} trigger={['click']}>\n            {{\n              default: () => <EllipsisOutlined style={{ fontSize: '18px' }} />,\n              content: () => <List data-source={dataSrc}>{{\n                renderItem: ({ item, index }: { item: string, index: number }) => <List.Item onClick={() => onclick(item, ellipsisRange.value + index)} class=\"drive-breadcrumb-pop-item\">{item}</List.Item>\n              }}</List>\n            }}\n          </Popover></Breadcrumb.Item>\n        )\n\n        nodes.push(...paths.slice(1 + ellipsisRange.value).map((i, idx) => (\n          <Breadcrumb.Item onClick={() => onclick(i, idx + 1 + ellipsisRange.value)}>\n            <a>{i}</a>\n          </Breadcrumb.Item>\n        )))\n      } else {\n        nodes.push(...paths.slice(1).map((i, idx) => (\n          <Breadcrumb.Item onClick={() => onclick(i, idx + 1 + 1)}>\n            <a>{i}</a>\n          </Breadcrumb.Item>\n        )))\n      }\n\n      return nodes\n    }\n    return () => (\n      <div ref={el} class={['drive-breadcrumb', 'drive-breadcrumb--' + props.size /*routes.value.length == 0 ? 'drive-routes--hidden' : null*/]}>\n        <Breadcrumb separator=\">\">\n          <Breadcrumb.Item onClick={() => onclick('', 0)}>\n            <a><Icon type=\"icon-home\" /></a>\n          </Breadcrumb.Item>\n          {createItem()}\n        </Breadcrumb>\n      </div>\n    )\n  },\n})\n"
  },
  {
    "path": "packages/sharelist-web/src/views/home/partial/error/index.less",
    "content": "\n.err{\n  height:70vh;\n  width:100%;\n  display:flex;\n  align-items:center;\n  justify-content:center;\n  flex-direction:column;\n  box-sizing:border-box;\n  .err__status{\n    font-size: 36px;\n    font-family: inherit;\n    font-weight: 500;\n    line-height: 1.1;\n    color:var(--primary-text-color);\n  }\n  .err__msg{\n    color:var(--context-2);\n    font-size: 16px;\n    font-family: 'Source Code Pro','microsoft yahei',Lato,Helvetica,Arial,sans-serif;\n  }\n}"
  },
  {
    "path": "packages/sharelist-web/src/views/home/partial/error/index.tsx",
    "content": "import { defineComponent, watch } from 'vue'\nimport './index.less'\nimport { message } from 'ant-design-vue'\nimport AuthBox from '../auth'\n\nexport default defineComponent({\n  props: {\n    value: {\n      type: Object,\n      required: true,\n    },\n  },\n  emits: ['auth'],\n  setup(props, ctx) {\n    if (props.value.code == 401 && props.value.message) {\n      message.error(props.value.message)\n    }\n    return () => {\n      if (props.value.code) {\n        return props.value.code == 401 ? (\n          <AuthBox scope={props.value.scope} onAuth={(d) => ctx.emit('auth', d)} />\n        ) : (\n          <div class=\"err\">\n            <h1 class=\"err__status\">{props.value.code}</h1>\n            <div class=\"err__msg\">{props.value.message}</div>\n          </div>\n        )\n      } else {\n        return ctx.slots.default?.()\n      }\n    }\n  },\n})\n"
  },
  {
    "path": "packages/sharelist-web/src/views/home/partial/header/index.less",
    "content": ""
  },
  {
    "path": "packages/sharelist-web/src/views/home/partial/header/index.tsx",
    "content": "import { ref, defineComponent, watch, onMounted } from 'vue'\nimport './index.less'\nimport Icon from '@/components/icon'\nimport { useToggle, useTitle } from '@/hooks/useHooks'\n// import Search from '../search'\nimport { Modal, InputSearch, RadioGroup, Radio, Space, Menu, Popover, Dropdown } from 'ant-design-vue'\nimport { useConfig } from '@/hooks/useSetting'\nimport useDisk from '@/hooks/useDisk'\nimport { ArrowDownOutlined, ArrowUpOutlined, SettingOutlined, CheckOutlined, SortDescendingOutlined, CalendarOutlined, FieldBinaryOutlined } from '@ant-design/icons-vue'\nimport useStore from '@/store/index'\n\nexport default defineComponent({\n  setup() {\n    const { config } = useConfig()\n\n    const { diskConfig, setPath, current, sortConfig, setSort } = useDisk()\n\n    const store = useStore()\n\n    watch(() => config.title, (nv) => {\n      if (document) {\n        document.title = config.title\n      }\n    })\n\n    const onToggleSearch = () => {\n      const onSearch = (value: string) => {\n        if (value) {\n          // router.push({ path: router.currentRoute.value.path, query: { search: value } })\n          setPath({ search: value, path: current.path, id: current.id })\n          modal.destroy()\n        }\n      }\n\n\n      const options: Array<any> = []\n\n      if (diskConfig.globalSearch) {\n        options.push({ label: '所有文件', value: 'global' })\n      }\n      if (diskConfig.localSearch) {\n        options.push({ label: '当前目录', value: 'local' })\n      }\n\n      const searchType = ref(options[0]?.value)\n\n      const modal = Modal.confirm({\n        class: 'fix-modal--alone',\n        width: '560px',\n        maskClosable: true,\n        content: () => (\n          <div>\n            <InputSearch enter-button placeholder=\"搜索内容\" onSearch={onSearch} />\n            {options.length ? <RadioGroup style=\"margin-top:8px;\" options={options} value={searchType.value} onChange={(e) => searchType.value = e.target.value} name=\"radioGroup\" ></RadioGroup> : null}\n          </div>\n        ),\n      })\n    }\n\n    const navHome = () => setPath({ path: '/' })\n\n    const onMenuAction = ({ key }: { key: any }) => {\n      let [type, val] = key.split('.')\n      if (type == 'layout') {\n        store.setLayout(val)\n      } else if (type == 'sort') {\n        setSort(val)\n      }\n    }\n    return () => (\n      <div class=\"drive-header\">\n\n        <div onClick={navHome} class=\"drive-header__name\">\n          {config.title || 'sharelist'}\n        </div>\n        <div class=\"drive-action\">\n          {(diskConfig.value.globalSearch || diskConfig.value.localSearch) ? <Icon class=\"drive-action-search\" type=\"icon-search\" onClick={onToggleSearch} /> : null}\n\n          <Dropdown overlayClassName='popover-padding-0 popover--narrow' placement=\"topRight\" destroyTooltipOnHide={true} arrowPointAtCenter={true} trigger={['click']}>\n            {{\n              default: () => <span><SettingOutlined /></span>,\n              overlay: () => <Menu class=\"menu-style\" onClick={onMenuAction}>\n                <Menu.ItemGroup title=\"视图\">\n                  <Menu.Item class=\"menu-item\" key=\"layout.list\">\n                    <div class=\"flex flex--between\"><div><Icon type={'icon-list'} style={{ fontSize: '18px', marginRight: '0px' }} /> 列表</div>{store.layout == 'list' ? <CheckOutlined /> : null}</div>\n                  </Menu.Item>\n                  <Menu.Item class=\"menu-item\" key=\"layout.grid\">\n                    <div class=\"flex flex--between\"><div><Icon type={'icon-grid'} style={{ fontSize: '18px', marginRight: '0px' }} /> 平铺</div>{store.layout == 'grid' ? <CheckOutlined /> : null}</div>\n                  </Menu.Item>\n                </Menu.ItemGroup>\n                <Menu.ItemGroup title=\"排序\">\n                  <Menu.Item class=\"menu-item\" key=\"sort.name\"><div class=\"flex flex--between\"><div><SortDescendingOutlined style={{ fontSize: '18px', marginRight: '8px' }} />名称</div>{sortConfig.value.key == 'name' ? (sortConfig.value.type == 'asc' ? <ArrowUpOutlined /> : <ArrowDownOutlined />) : null}</div></Menu.Item>\n                  <Menu.Item class=\"menu-item flex\" key=\"sort.mtime\"><div class=\"flex flex--between\"><div><CalendarOutlined style={{ fontSize: '18px', marginRight: '8px' }} />修改时间</div>{sortConfig.value.key == 'mtime' ? (sortConfig.value.type == 'asc' ? <ArrowUpOutlined /> : <ArrowDownOutlined />) : null}</div></Menu.Item>\n                  <Menu.Item class=\"menu-item flex\" key=\"sort.size\"><div class=\"flex flex--between\"><div><FieldBinaryOutlined style={{ fontSize: '18px', marginRight: '8px' }} />文件大小</div>{sortConfig.value.key == 'size' ? (sortConfig.value.type == 'asc' ? <ArrowUpOutlined /> : <ArrowDownOutlined />) : null}</div></Menu.Item>\n                </Menu.ItemGroup>\n              </Menu>\n            }}\n          </Dropdown>\n        </div>\n      </div>\n    )\n  },\n})\n"
  },
  {
    "path": "packages/sharelist-web/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"esnext\",\n    \"module\": \"esnext\",\n    \"moduleResolution\": \"node\",\n    \"strict\": true,\n    \"jsx\": \"preserve\",\n    \"sourceMap\": true,\n    \"resolveJsonModule\": true,\n    \"esModuleInterop\": true,\n    \"importHelpers\": true,\n    \"skipLibCheck\": true,\n    \"allowSyntheticDefaultImports\": true,\n    \"baseUrl\": \".\",\n    \"experimentalDecorators\": true,\n    \"noImplicitThis\": true,\n    \"lib\": [\n      \"esnext\",\n      \"dom\",\n      \"dom.iterable\",\n      \"scripthost\"\n    ],\n    \"types\": [\n      // \"vite/client\"\n    ],\n    \"paths\": {\n      \"@/*\": [\n        \"src/*\"\n      ]\n    },\n  },\n  \"include\": [\n    \"src/**/*.ts\",\n    \"src/**/*.d.ts\",\n    \"src/**/*.tsx\",\n    \"src/**/*.vue\",\n    \"tests/**/*.ts\",\n    \"tests/**/*.tsx\"\n  ],\n  \"exclude\": [\n    \"node_modules\"\n  ]\n}"
  },
  {
    "path": "packages/sharelist-web/vite.config.ts",
    "content": "import { defineConfig } from 'vite'\nimport vueJsx from '@vitejs/plugin-vue-jsx'\nimport legacy from '@vitejs/plugin-legacy'\nimport path from 'path'\nimport ViteComponents, { AntDesignVueResolver } from 'vite-plugin-components'\n\nconst root = path.resolve(__dirname, './src')\n\nexport default defineConfig({\n  root,\n  base: '/',\n  resolve: {\n    alias: [{ find: '@', replacement: root }],\n    extensions: ['.js', '.ts', '.jsx', '.tsx', '.vue', '.json', '.less', '.css'],\n  },\n  build: {\n    outDir: path.join(__dirname, 'dist'),\n    sourcemap: false,\n    emptyOutDir: true,\n    assetsDir: '',\n  },\n  css: {\n    preprocessorOptions: {\n      less: {\n        javascriptEnabled: true,\n        modifyVars: {\n          'preprocess-custom-color': 'green',\n        },\n      },\n    },\n  },\n  server: {\n    port: +process.env.PORT || 3000,\n    proxy: {\n      '/api': {\n        target: 'http://127.0.0.1:33001/',\n        changeOrigin: true,\n      },\n    },\n  },\n\n  plugins: [\n    vueJsx(),\n    legacy({\n      targets: ['defaults', 'not IE 11'],\n    }),\n    // ViteComponents({\n    //   customComponentResolvers: [AntDesignVueResolver()],\n    // }),\n  ],\n  optimizeDeps: {\n    exclude: [],\n  },\n})\n"
  },
  {
    "path": "packages/sharelist-webdav/CHANGELOG.md",
    "content": "## [0.1.11](https://github.com/reruin/sharelist/compare/v0.3.13...v0.1.11) (2022-01-05)\n\n\n### Bug Fixes\n\n* adapt webdav client ([41b2706](https://github.com/reruin/sharelist/commit/41b27063315c80323002416955cded8364f59526))\n\n\n\n## [0.1.10](https://github.com/reruin/sharelist/compare/v0.3.11...v0.1.10) (2021-12-29)\n\n\n### Bug Fixes\n\n* adapt webdav client([#733](https://github.com/reruin/sharelist/issues/733)) ([db2d66d](https://github.com/reruin/sharelist/commit/db2d66deb5ae2da43116a4f33936c088174f25d6))\n\n\n\n## [0.1.9](https://github.com/reruin/sharelist/compare/v0.3.9...v0.1.9) (2021-11-01)\n\n\n\n## [0.1.8](https://github.com/reruin/sharelist/compare/v0.3.7...v0.1.8) (2021-10-19)\n\n\n### Features\n\n* **webdav:** support anonymous account ([9573c95](https://github.com/reruin/sharelist/commit/9573c953eda8e2d3c0a64fc2d97e0094b5e9ed8d))\n\n\n\n## [0.1.7](https://github.com/reruin/sharelist/compare/v0.3.5...v0.1.7) (2021-10-12)\n\n\n### Bug Fixes\n\n* **webdav:** adapt RaiDrive ([6e0f37e](https://github.com/reruin/sharelist/commit/6e0f37e92ecf673656cb3194b3b97a932b607cc0))\n\n\n\n"
  },
  {
    "path": "packages/sharelist-webdav/README.md",
    "content": "# @sharelist/webdav [![npm](https://img.shields.io/npm/v/@sharelist/webdav.svg)](https://npmjs.com/package/@sharelist/webdav)\n\nIt's a framework for mounting netdisk.\n\n## Useage\n"
  },
  {
    "path": "packages/sharelist-webdav/package.json",
    "content": "{\n  \"name\": \"@sharelist/webdav\",\n  \"version\": \"0.2.0\",\n  \"repository\": \"https://github.com/reruin/sharelist\",\n  \"license\": \"MIT\",\n  \"main\": \"dist/index.js\",\n  \"files\": [\n    \"dist\",\n    \"src\"\n  ],\n  \"scripts\": {\n    \"dev\": \"tsc -w -p .\",\n    \"build\": \"rm -rf dist && tsc -p .\",\n    \"changelog\": \"conventional-changelog -p angular -i CHANGELOG.md -s --commit-path .\",\n    \"release\": \"node ../../scripts/release.js --commit-path .\"\n  },\n  \"devDependencies\": {\n    \"@types/koa\": \"^2.13.4\",\n    \"@types/xml2js\": \"^0.4.9\",\n    \"ts-node\": \"^10.2.1\",\n    \"typescript\": \"^4.3.5\"\n  },\n  \"dependencies\": {\n    \"xml2js\": \"^0.4.23\"\n  }\n}"
  },
  {
    "path": "packages/sharelist-webdav/src/context.ts",
    "content": "import http from 'http'\nimport { Context, WebDAVDepth } from './types'\nimport { parseXML } from './operations/shared'\nimport { URL } from 'url'\n\nexport default (req: http.IncomingMessage, base: string, allows: Array<string>): Context => {\n  const authorization = req.headers?.authorization?.split(' ')[1]\n\n  const path = new URL(req.url as string, `http://${req.headers.host}`).pathname\n  const ctx: Context = {\n    req: req,\n    depth: req.headers?.depth as WebDAVDepth,\n    method: (req.method as string || '').toLowerCase(),\n    path: path.replace(base, ''),\n    base,\n    config: {},\n    auth: { user: undefined, pass: undefined },\n    allows,\n    get(field: string): string | undefined {\n      const req: http.IncomingMessage = this.req\n      switch (field = field.toLowerCase()) {\n        case 'referer':\n        case 'referrer':\n          return req.headers.referrer as string || req.headers.referer || ''\n        default:\n          return req.headers[field] as string || ''\n      }\n    }\n  }\n  if (authorization) {\n    const pairs = Buffer.from(authorization, \"base64\").toString(\"utf8\").split(':')\n    ctx.auth = { user: pairs[0], pass: pairs[1] }\n  }\n  return ctx\n}"
  },
  {
    "path": "packages/sharelist-webdav/src/index.ts",
    "content": "//docs: https://github.com/FullStackPlayer/WebDAV-RFC4918-CN\n\nimport { WebDAVMethod, WebDAVRequest, Driver, DriverMethod, Context, Response, StatusCodes, WebDAVDepth } from \"./types\"\nimport Commands from './operations/commands'\nimport http from 'http'\nimport { parseXML } from './operations/shared'\nimport { URL } from 'url'\n\ninterface WebDAVAuth {\n  (user: string | undefined, pass: string | undefined): boolean,\n}\nexport type WebDAVServerOptions = {\n  driver?: Driver,\n  base?: string,\n  auth: WebDAVAuth,\n  redirect: boolean\n}\n\nexport class WebDAVServer {\n  public methods: { [methodName: string]: WebDAVMethod }\n\n  protected unknownMethod: WebDAVMethod | undefined\n\n  protected driver: Driver | undefined\n\n  protected base: string\n\n  protected auth: WebDAVAuth\n\n  protected config: Record<string, any>\n\n  protected allows: Array<string>\n\n  constructor({ driver, base, redirect, auth }: WebDAVServerOptions = { redirect: false, auth: () => true }) {\n    this.methods = {}\n    this.driver = driver\n    this.base = base || ''\n    this.auth = auth\n    this.config = { redirect }\n    const commands: { [key: string]: any } = Commands\n    for (const k in commands)\n      if (k === 'NotImplemented')\n        this.unknownMethod = commands[k]\n      else\n        this.methods[k.toLowerCase()] = commands[k]\n    this.allows = Object.keys(this.methods).map(i => i.toUpperCase())//.join(', ')\n  }\n\n  createContext(req: http.IncomingMessage, options: WebDAVServerOptions): Context {\n\n    const base = options?.base || this.base\n\n    const authorization = req.headers?.authorization?.split(' ')[1]\n\n    const path = new URL(req.url as string, `http://${req.headers.host}`).pathname\n\n    const ctx: Context = {\n      req: req,\n      driver: this.driver,\n      depth: req.headers?.depth as WebDAVDepth,\n      method: (req.method as string || '').toLowerCase(),\n      path: path.replace(base, ''),\n      base,\n      config: this.config,\n      auth: { user: undefined, pass: undefined },\n      allows: this.allows,\n      get(field: string): string | undefined {\n        const req: http.IncomingMessage = this.req\n        switch (field = field.toLowerCase()) {\n          case 'referer':\n          case 'referrer':\n            return req.headers.referrer as string || req.headers.referer || ''\n          default:\n            return req.headers[field] as string || ''\n        }\n      }\n    }\n    if (authorization) {\n      const pairs = Buffer.from(authorization, \"base64\").toString(\"utf8\").split(':')\n      ctx.auth = { user: pairs[0], pass: pairs[1] }\n    }\n    return ctx\n  }\n\n  async request(req: WebDAVRequest, options: WebDAVServerOptions): Promise<Response> {\n    const ctx: Context = this.createContext(req, options)\n    if (\n      !(ctx.method == 'options' && !ctx.path) &&\n      !this.auth(ctx.auth.user, ctx.auth.pass)\n    ) {\n      return {\n        headers: {\n          'X-WebDAV-Status': '401 ' + StatusCodes[401],\n          'WWW-Authenticate': `Basic realm=\"ShareList WebDAV\"`\n        },\n        status: '401'\n      }\n    }\n\n    const method = this.methods[ctx.method] || this.unknownMethod\n    const res: Response = await method(ctx)\n    res.headers ||= {}\n    if (res.status) {\n      // res.headers['X-WebDAV-Status'] = res.status + ' ' + StatusCodes[res.status]\n    }\n    return res\n  }\n}"
  },
  {
    "path": "packages/sharelist-webdav/src/operations/commands.ts",
    "content": "import Get from './get'\nimport Put from './put'\nimport Post from './post'\nimport Head from './head'\nimport Move from './move'\nimport Lock from './lock'\nimport Copy from './copy'\nimport Mkcol from './mkcol'\nimport Unlock from './unlock'\nimport Delete from './delete'\nimport Options from './options'\nimport Propfind from './propfind'\nimport Proppatch from './proppatch'\nimport NotImplemented from './not-implemented'\n\nexport default {\n  NotImplemented,\n  Proppatch,\n  Propfind,\n  Options,\n  Delete,\n  // Unlock,\n  Mkcol,\n  Copy,\n  // Lock,\n  Move,\n  // Head,\n  // Post,\n  Put,\n  Get\n}"
  },
  {
    "path": "packages/sharelist-webdav/src/operations/copy.ts",
    "content": "import { Context, Response } from '../types'\nimport { URL } from 'url'\n\nexport default async (ctx: Context): Promise<Response | undefined> => {\n  const dst = new URL(ctx.req.headers?.destination as string).pathname.replace(ctx.base, '')\n  const src = ctx.path\n  //The source URI and the destination URI are the same.\n  if (src === dst) {\n    return { status: '403' }\n  }\n\n  const res = await ctx.driver?.mv?.(src, dst, true)\n  if (res?.error) {\n    return {\n      status: res.error.code || '502'\n    }\n  } else {\n    return {\n      status: '201'\n    }\n  }\n\n}"
  },
  {
    "path": "packages/sharelist-webdav/src/operations/delete.ts",
    "content": "import { Context, Response } from '../types'\n\nexport default async (ctx: Context): Promise<Response | undefined> => {\n  const res = await ctx.driver?.rm?.(ctx.path)\n  if (res?.error) {\n    return {\n      status: res.error.code || '502'\n    }\n  } else {\n    return {\n      status: '204'\n    }\n  }\n}"
  },
  {
    "path": "packages/sharelist-webdav/src/operations/get.ts",
    "content": "import { parseXML } from './shared'\nimport { Context, Response } from '../types'\n\nconst filterHeaders = (headers: Record<string, any>): Record<string, any> => {\n  const effectFields = ['range', 'accept-encoding']\n  const ret: Record<string, any> = {}\n  Object.keys(headers).filter((i: string) => effectFields.includes(i.toLocaleLowerCase())).forEach((key: string) => {\n    ret[key] = headers[key]\n  })\n  return ret\n}\n\nexport default async (ctx: Context): Promise<Response | undefined> => {\n  const res = await ctx.driver?.get?.(ctx.path, { reqHeaders: filterHeaders(ctx.req.headers) })\n  if (res?.error) {\n    return {\n      status: res.error.code || '502'\n    }\n  } else {\n    return res as Response\n  }\n}"
  },
  {
    "path": "packages/sharelist-webdav/src/operations/head.ts",
    "content": "export default {\n\n}"
  },
  {
    "path": "packages/sharelist-webdav/src/operations/lock.ts",
    "content": "export default {\n\n}"
  },
  {
    "path": "packages/sharelist-webdav/src/operations/mkcol.ts",
    "content": "import { parseXML } from './shared'\nimport { Context, Response } from '../types'\n\nexport default async (ctx: Context): Promise<Response | undefined> => {\n  const data = await ctx.driver?.mkdir?.(ctx.path)\n  if (data?.error) {\n    if (data.error.code == 401) {\n      return {\n        headers: {\n          'WWW-Authenticate': `Basic realm=\"ShareList WebDAV\"`\n        },\n        status: '401'\n      }\n    } else {\n      return {\n        status: '404'\n      }\n    }\n  } else {\n    return {\n      status: '201'\n    }\n  }\n}"
  },
  {
    "path": "packages/sharelist-webdav/src/operations/move.ts",
    "content": "import { Context, Response } from '../types'\nimport { URL } from 'url'\n\nexport default async (ctx: Context): Promise<Response | undefined> => {\n  const dst = new URL(ctx.req.headers?.destination as string).pathname.replace(ctx.base, '')\n  const src = ctx.path\n  //The source URI and the destination URI are the same.\n  if (src === dst) {\n    return { status: '403' }\n  }\n\n  const res = await ctx.driver?.mv?.(src, dst)\n  if (res?.error) {\n    return {\n      status: res.error.code || '502'\n    }\n  } else {\n    return {\n      status: '201'\n    }\n  }\n\n}"
  },
  {
    "path": "packages/sharelist-webdav/src/operations/not-implemented.ts",
    "content": "import { Context, Response } from '../types'\nimport commands from './commands'\nexport default async (ctx: Context): Promise<Response | undefined> => {\n  return {\n    status: '405 Method not allowed',\n    headers: {\n      'Allow': Object.keys(commands).join(', ')\n    }\n  }\n}"
  },
  {
    "path": "packages/sharelist-webdav/src/operations/options.ts",
    "content": "import { parseXML } from './shared'\nimport { Context, Response } from '../types'\n\nexport default async (ctx: Context): Promise<Response | undefined> => {\n  const dav = [1]\n\n  if (ctx.allows?.includes('LOCK')) {\n    dav.push(2)\n  }\n\n  return {\n    headers: {\n      // For Microsoft clients\n      'MS-Author-Via': 'DAV',\n      'DAV': dav.join(', '),\n      'Allow': ctx.allows?.join(', ') || ''\n    },\n    status: '200'\n  }\n}"
  },
  {
    "path": "packages/sharelist-webdav/src/operations/post.ts",
    "content": "export default {\n\n}"
  },
  {
    "path": "packages/sharelist-webdav/src/operations/propfind.ts",
    "content": "import { parseXML, DEFAULT_PROPS, propParse } from './shared'\nimport { Context, Response } from '../types'\nimport xml2js from 'xml2js'\n\n\n/**\n * Create webdav responese xml by data and props options\n *\n * @param {object} [data] file data\n * @param {object} [options]\n * @param {object} [options.props] Available props\n * @param {object} [options.path]  Current folder path\n * @param {object} [options.ns]\n * @return {string} XML string\n */\n\nconst convData = (files: Array<any>, options: any) => {\n  const { path, base = '', depth, prop, ns: { prefix, uri } } = options\n\n  return files.map((file: any) => {\n    const item: Record<string, any> = {}\n    for (const key of prop) {\n      item[key] = file.name.replace(/&/g, \"&amp;\").replace(/</g, \"&lt;\").replace(/>/g, \"&gt;\").replace(/\"/g, \"&quot;\").replace(/'/g, \"&apos;\");\n\n      if (key == 'getcontentlength') {\n        item[key] = parseInt(file.size || 0)\n      } else if (key == 'resourcetype') {\n        item[key] = (file.type == 'folder' || file.type == 'drive') ? { collection: '' } : ''\n        // } else if (key == 'getcontenttype') {\n        //   item[key] = file.mime\n      } else if (key == 'creationdate' && file.ctime) {\n        item[key] = new Date(file.ctime).toUTCString()\n      } else if (key == 'getlastmodified' && file.mtime) {\n        item[key] = new Date(file.mtime).toUTCString()\n      }\n    }\n\n    const href = (base + path + (depth == '0' ? '' : ('/' + encodeURIComponent(file.name)))).replace(/\\/{2,}/g, '/')\n    //if (file.type == 'file' && file.download_url) href = file.download_url\n    return {\n      href,\n      propstat: {\n        status: 'HTTP/1.1 200 OK',\n        prop: item\n      }\n    }\n  })\n}\n\nconst fixNs = (data: any, prefix: string) => {\n  if (!prefix) return data\n  Object.keys(data).forEach((key: string) => {\n    const val = data[key]\n    if (key != '$' && prefix) {\n      if (Array.isArray(val) || typeof val == 'object') {\n        fixNs(val, prefix)\n      }\n      delete data[key]\n      data[`${prefix}:${key}`] = val\n    } else {\n      if (val.xmlns) {\n        val[`xmlns:${prefix}`] = val.xmlns\n        delete val.xmlns\n      }\n    }\n  })\n  return data\n}\n\nconst createXML = (data: any, options: any) => {\n  const { ns: { prefix, uri } } = options\n\n  const obj: any = {\n    multistatus: {\n      response: convData(data || [], options)\n    }\n  }\n  if (uri) {\n    obj.multistatus.$ = {\n      \"xmlns\": uri\n    }\n  }\n\n  const builder = new xml2js.Builder({\n    renderOpts: { pretty: false },\n    xmldec: { version: '1.0', encoding: 'UTF-8' }\n  })\n\n  const xml = builder.buildObject(fixNs(obj, prefix))\n  return xml\n}\n\nexport default async (ctx: Context): Promise<Response | undefined> => {\n  const options = Object.assign({\n    path: ctx.path,\n    base: ctx.base,\n    depth: ctx.depth,\n  }, propParse(await parseXML(ctx.req)))\n  const data: any = ctx.depth == '0' ? await ctx.driver?.stat(ctx.path) : await ctx.driver?.ls(ctx.path)\n  if (!data) return { status: '404' }\n\n  if (data.error) {\n    if (data.error.code == 401) {\n      // Windows seems to require this being the last header sent\n      // (changed according to PECL bug #3138)\n      return {\n        headers: {\n          'WWW-Authenticate': `Basic realm=\"ShareList WebDAV\"`\n        },\n        status: '401'\n      }\n    } else {\n      return {\n        status: '404'\n      }\n    }\n  }\n\n  //return itself\n  if (ctx.depth == '0') {\n    if (!data.files) {\n      return {\n        status: '207',\n        headers: {\n          'content-type': 'text/xml; charset=\"utf-8\"'\n        },\n        body: createXML([data], options)\n      }\n    }\n  }\n  else if (ctx.depth == '1') {\n    if (data) {\n      return {\n        status: '207',\n        headers: {\n          'content-type': 'text/xml; charset=\"utf-8\"'\n        },\n        body: createXML(data, options)\n      }\n    }\n  }\n  else if (ctx.depth == 'infinity') {\n    return {\n      status: '404'\n    }\n  }\n}"
  },
  {
    "path": "packages/sharelist-webdav/src/operations/proppatch.ts",
    "content": "import { parseXML, propParse } from './shared'\nimport { Context, Response } from '../types'\n\nexport default async (ctx: Context): Promise<Response | undefined> => {\n  // const options = Object.assign({\n  //   path: ctx.path,\n  //   base: ctx.base,\n  //   depth: ctx.depth,\n  // }, propParse(await parseXML(ctx.req)))\n  return {\n    status: '200'\n  }\n}"
  },
  {
    "path": "packages/sharelist-webdav/src/operations/put.ts",
    "content": "import { parseXML } from './shared'\nimport { Context, Response } from '../types'\n\nexport default async (ctx: Context): Promise<Response | undefined> => {\n  console.log('>>', ctx.req.isPaused(), ctx.req.headers)\n  ctx.req.headers.connection = 'keep-alive'\n  const size = parseInt(ctx.req.headers['content-length'] || '0')\n  const res = await ctx.driver?.upload?.(ctx.path, ctx.req, { size })\n\n  if (res?.error) {\n    return {\n      status: res.error.code || '502'\n    }\n  } else {\n    return {\n      status: '201'\n    }\n  }\n}"
  },
  {
    "path": "packages/sharelist-webdav/src/operations/shared.ts",
    "content": "import * as http from 'http'\nimport { Readable } from 'stream'\nimport { parseStringPromise, processors } from 'xml2js'\n\nconst saveStream = (stream: Readable, charset: BufferEncoding | undefined = 'utf8'): Promise<string> => {\n  return new Promise((resolve, reject) => {\n    const data: Array<Uint8Array> = []\n    stream\n      .on('data', chunk => {\n        data.push(chunk)\n      })\n      .on('error', reject)\n      .on('end', () => resolve(Buffer.concat(data).toString(charset)))\n  })\n}\n\n\nexport const parseXML = async (req: http.IncomingMessage): Promise<any> => {\n  const txt = await saveStream(req)\n  return await parseStringPromise(txt, {\n    // explicitChildren: true,\n    explicitArray: false,\n    tagNameProcessors: [processors.stripPrefix]\n  })\n}\n\n\nexport const DEFAULT_PROPS = [\n  'displayname',\n  'getcontentlength',\n  'resourcetype',\n  // 'getcontenttype',\n  'creationdate',\n  'getlastmodified'\n]\n\n/**\n * Parse props from webdav request\n * \n * @param {object} [data]\n * @return {object|boolean}\n */\nexport const propParse = (data: any) => {\n  if (!data) return {\n    ns: { prefix: 'D', uri: 'DAV:' },\n    prop: [...DEFAULT_PROPS]\n  }\n  let prop = [...DEFAULT_PROPS]\n  const prefix = Object.keys(data.propfind.$).find(i => i.startsWith('xmlns:'))?.split(':')[1] || ''\n  const uri = data.propfind.$?.[`xmlns${prefix ? `:${prefix}` : ''}`] || ''\n  if (data.propfind.hasOwnProperty('prop')) {\n    prop = Object.keys(data.propfind.prop)\n  }\n  return { ns: { prefix, uri }, prop }\n}"
  },
  {
    "path": "packages/sharelist-webdav/src/operations/unlock.ts",
    "content": "export default {\n\n}"
  },
  {
    "path": "packages/sharelist-webdav/src/types.ts",
    "content": "import http from 'http'\n\nexport type DriverMethod = 'stat' | 'get' | 'ls' | 'rm' | 'mkdir' | 'upload' | 'mv' | 'copy'\n\nexport type WebDAVDepth = \"0\" | \"1\" | \"1,noroot\" | \"infinity\"\n\nexport type WebDAVRequest = http.IncomingMessage\n\nexport type DriverMethodResponse = {\n  status?: string,\n  data?: any,\n  error?: { code?: string | number, message: string }\n}\n\nexport type Driver = {\n  [key in DriverMethod]: (...options: Array<any>) => DriverMethodResponse\n}\n\nexport type WebDAVAuthRecord = {\n  user: string | undefined,\n  pass: string | undefined\n}\nexport interface Context {\n  req: http.IncomingMessage,\n  depth: WebDAVDepth,\n  method: string,\n  path: string | undefined,\n  base: string,\n  get(field: string): any,\n  driver?: Driver,\n  allows?: Array<string>,\n  config: Record<string, any>,\n  auth: WebDAVAuthRecord\n}\n\n\nexport interface WebDAVMethod {\n  (ctx: Context): any\n}\n\nexport type Response = {\n  status: string | number,\n  headers?: Record<string, string | undefined>,\n  body?: any\n}\n\nexport const StatusCodes: Record<string | number, string> = {\n  200: 'OK',\n  201: 'Created',\n  204: 'No Content',\n  207: 'Multi Status',\n  302: 'Moved Temporarily',\n  401: 'Unauthorized',\n  403: 'Forbidden',\n  404: 'Not Found',\n  409: 'Conflict',\n  423: 'Locked'\n}"
  },
  {
    "path": "packages/sharelist-webdav/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"es2020\",\n    \"module\": \"commonjs\",\n    \"moduleResolution\": \"node\",\n    \"strict\": true,\n    // \"sourceMap\": true,\n    \"resolveJsonModule\": true,\n    \"esModuleInterop\": true,\n    \"importHelpers\": false,\n    \"skipLibCheck\": true,\n    \"allowSyntheticDefaultImports\": true,\n    \"baseUrl\": \"./src\",\n    \"experimentalDecorators\": true,\n    \"lib\": [\n      \"esnext\",\n      \"scripthost\"\n    ],\n    \"rootDir\": \"./src\",\n    \"outDir\": \"./dist\",\n    \"watch\": false\n  },\n  \"include\": [\n    \"src/**/*.ts\",\n    \"src/**/*.d.ts\",\n    \"src/**/*.tsx\",\n    \"src/**/*.vue\",\n    \"tests/**/*.ts\",\n    \"tests/**/*.tsx\"\n  ],\n  \"exclude\": [\n    \"node_modules\"\n  ]\n}"
  },
  {
    "path": "scripts/build.js",
    "content": "\nconst child_process = require('child_process')\nconst { readdir, copyFile } = require('fs/promises')\nconst path = require('path')\n\nconst run = cmd => new Promise((resolve, reject) => {\n  child_process.exec(cmd, (error, stdout, stderr) => {\n    if (error) {\n      resolve()\n    } else {\n      resolve(stdout)\n    }\n  })\n})\n\nconst cp = async (src, dst) => {\n  let files = await readdir(src)\n  for (let file of files) {\n    let name = path.basename(file)\n    let dst = path.join(dst, name)\n    await copyFile(file, dst)\n  }\n}\n\n// download default plugins\nconst preinstall = async () => {\n\n}\n\nconst main = async () => {\n  await run(`yarn build-web`)\n  await cp('../packages/sharelist-plugin/lib', '../packages/sharelist/plugins')\n  await run(`pkg ./packages/sharelist/ --output build/sharelist --targets linux-x64,macos-x64,macos-arm64,win-x64`)\n}"
  },
  {
    "path": "scripts/changelog.js",
    "content": "const child_process = require('child_process')\n\nconst COMMIT_PATTERN = /^([^)]*)(?:\\(([^)]*?)\\)|):(.*?(?:\\[([^\\]]+?)\\]|))\\s*$/\n\n// const PR_REGEX = /#[1-9][\\d]*/g\n\nconst SEPARATOR = '===END==='\n\nconst TYPES = {\n  breaking: 'Breaking Changes',\n  feat: 'New Features',\n  fix: 'Bug Fixes',\n}\n\nconst run = cmd => new Promise((resolve, reject) => {\n  child_process.exec(cmd, (error, stdout, stderr) => {\n    if (error) {\n      resolve()\n    } else {\n      resolve(stdout)\n    }\n  })\n})\n\nconst markdown = (commits, REPO_URL) => {\n  let content = []\n  Object.keys(commits).filter(type => TYPES[type]).forEach((type) => {\n    content.push('##### ' + TYPES[type])\n    content.push('');\n    Object.keys(commits[type]).forEach(category => {\n\n      let multiline = commits[type][category].length > 1;\n      let head = category ? `* **${category}:**` : '*'\n\n      if (multiline && category) {\n        content.push(head);\n        head = '  *'\n      }\n\n      commits[type][category].forEach((commit) => {\n        let hashLink = REPO_URL ? `[${commit.hash.substring(0, 8)}](${REPO_URL}/commit/${commit.hash})` : `${commit.hash.substring(0, 8)}`\n        content.push(`${head} ${commit.subject} (${hashLink})`)\n      });\n    })\n    content.push('');\n  })\n\n  return content.join('\\n')\n}\n\nconst main = async (REPO_URL) => {\n  let hash = await run(`git rev-list --tags --max-count=2`)\n\n  let fromVer = 'HEAD'\n\n  if (hash) {\n    let lastTagHash = hash.split(/[\\r\\n]/g)[1]\n    if (lastTagHash) {\n      let hit = await run(`git describe --abbrev=0 --tags ${lastTagHash}`)\n      if (hit) fromVer = hit.toString().trim()\n    }\n  }\n\n  const commits = await run(`git log -E --format=%H%n%s%n%b%n${SEPARATOR} ${fromVer}..`)\n\n  const content = markdown(commits.split('\\n' + SEPARATOR + '\\n').filter(Boolean).map(raw => {\n    const [hash, subject, ...body] = raw.split('\\n');\n    const commit = {\n      hash, subject\n    }\n\n    const parsed = commit.subject.match(COMMIT_PATTERN)\n\n    if (!parsed || !parsed[1] || !parsed[3]) {\n      return null\n    }\n\n    commit.type = parsed[1].toLowerCase()\n    commit.category = parsed[2] || ''\n    commit.subject = parsed[3]\n\n    return commit\n  }).filter(Boolean).reduce((t, c) => {\n    t[c.type] = t[c.type] || {}\n    t[c.type][c.category] = t[c.type][c.category] || []\n    t[c.type][c.category].push(c)\n    return t\n  }, {}), REPO_URL)\n\n  return content\n}\n\n// (async function () {\n//   console.log(await main())\n// })()\nmodule.exports = main\n"
  },
  {
    "path": "scripts/netinstall.sh",
    "content": "#!/bin/bash\n\nPATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\n\necho \"+============================================================+\"\necho \"|                    ShareList(Next) NetInstaller                  |\"\necho \"|                                                            |\"\necho \"|                                         <reruin@gmail.com> |\"\necho \"|------------------------------------------------------------|\"\necho \"|                                         https://reruin.net |\"\necho \"+============================================================+\"\necho \"\"\n\necho -e \"\\n|  ShareList(Next) is installing ... \"\n\n\necho -e \"|\\n|  Download ShareList Package ... \"\nwget -O sharelist-master.zip https://github.com/reruin/sharelist/archive/refs/heads/master.zip >/dev/null 2>&1\n\nunzip -q -o sharelist-master.zip -d ./\n\nmv sharelist-master sharelist\nrm -f sharelist-master.zip\n\ncd sharelist\necho -e \"|\\n|  Install Dependents ... \"\nnpm install yarn -g >/dev/null 2>&1\nnpm install pm2 -g >/dev/null 2>&1\n\nyarn install >/dev/null 2>&1\nyarn build-web\nmkdir -p ./packages/sharelist/theme/default\nmkdir -p ./packages/sharelist/plugins\ncp -r ./packages/sharelist-web/dist/* ./packages/sharelist/theme/default\ncp -r ./packages/sharelist-plugin/lib/* ./packages/sharelist/plugins\ncd packages/sharelist\n\npm2 start app.js --name sharelist-next >/dev/null 2>&1\npm2 save >/dev/null 2>&1\npm2 startup >/dev/null 2>&1\n\necho -e \"|\\n|  Success: ShareList(next) has been installed\\n\""
  },
  {
    "path": "scripts/release.js",
    "content": "\n/**\n * modified from https://github.com/vuejs/vue-next/blob/master/scripts/release.js\n */\nconst execa = require('execa')\nconst path = require('path')\nconst fs = require('fs')\nconst args = require('minimist')(process.argv.slice(2))\nconst semver = require('semver')\nconst chalk = require('chalk')\nconst prompts = require('prompts')\n\nconst pkgDir = process.cwd()\nconst pkgPath = path.resolve(pkgDir, 'package.json')\n\nconst remote = 'origin'\n\n/**\n * @type {{ name: string, version: string }}\n */\nconst pkg = require(pkgPath)\nconst pkgName = pkg.name.replace(/^@sharelist\\//, '')\nconst currentVersion = pkg.version\n\nconst isDryRun = args.dry\n\nconst skipBuild = args.skipBuild\n\nconst skipNpmPublish = args.skipNpmPublish\n\nconst commitPath = args['commit-path']\n\n/**\n * @type {import('semver').ReleaseType[]}\n */\nconst versionIncrements = [\n  'patch',\n  'minor',\n  'major',\n  'prepatch',\n  'preminor',\n  'premajor',\n  'prerelease'\n]\n\n\nconst inc = (i) => semver.inc(currentVersion, i, 'beta')\n\nconst run = isDryRun ? (bin, args, opts = {}) =>\n  console.log(chalk.blue(`[dryrun] ${bin} ${args.join(' ')}`), opts) : (bin, args, opts = {}) =>\n  execa(bin, args, { stdio: 'inherit', ...opts })\n\n\nconst step = (msg) => console.log(chalk.cyan(msg))\n\nasync function main() {\n  let targetVersion = args._[0]\n\n  if (!targetVersion) {\n    // no explicit version, offer suggestions\n    /**\n     * @type {{ release: string }}\n     */\n    const { release } = await prompts({\n      type: 'select',\n      name: 'release',\n      message: 'Select release type',\n      choices: versionIncrements\n        .map((i) => `${i} (${inc(i)})`)\n        .concat(['custom'])\n        .map((i) => ({ value: i, title: i }))\n    })\n\n    if (release === 'custom') {\n      /**\n       * @type {{ version: string }}\n       */\n      const res = await prompts({\n        type: 'text',\n        name: 'version',\n        message: 'Input custom version',\n        initial: currentVersion\n      })\n      targetVersion = res.version\n    } else {\n      targetVersion = release.match(/\\((.*)\\)/)[1]\n    }\n  }\n\n  if (!semver.valid(targetVersion)) {\n    throw new Error(`invalid target version: ${targetVersion}`)\n  }\n\n  const tag =\n    pkgName === 'sharelist' ? `v${targetVersion}` : `${pkgName}@${targetVersion}`\n\n  if (targetVersion.includes('beta') && !args.tag) {\n    /**\n     * @type {{ tagBeta: boolean }}\n     */\n    const { tagBeta } = await prompts({\n      type: 'confirm',\n      name: 'tagBeta',\n      message: `Publish under dist-tag \"beta\"?`\n    })\n\n    if (tagBeta) args.tag = 'beta'\n  }\n\n  /**\n   * @type {{ yes: boolean }}\n   */\n  const { yes } = await prompts({\n    type: 'confirm',\n    name: 'yes',\n    message: `Releasing ${tag}. Confirm?`\n  })\n\n  if (!yes) {\n    return\n  }\n\n  step('\\nUpdating package version...')\n  updateVersion(targetVersion)\n\n  step('\\nBuilding package...')\n  if (!skipBuild && !isDryRun) {\n    await run('yarn', ['build'])\n  } else {\n    console.log(`(skipped build)`)\n  }\n\n  step('\\nGenerating changelog...')\n  await run('yarn', ['changelog'])\n\n  const { stdout } = await run('git', ['diff'], { stdio: 'pipe' })\n  if (stdout) {\n    step('\\nCommitting changes...')\n    await run('git', ['add', '-A'].concat(commitPath || []))\n    await run('git', ['commit', '-m', `release: ${tag}`])\n  } else {\n    console.log('No changes to commit.')\n  }\n\n  if (!skipNpmPublish) {\n    step('\\nPublishing package...')\n    await publishPackage(targetVersion, run)\n  }\n\n  step('\\nPushing to GitHub...')\n  await run('git', ['tag', tag])\n  await run('git', ['push', remote, `refs/tags/${tag}`])\n  await run('git', ['push', remote, 'master'])\n\n  if (isDryRun) {\n    console.log(`\\nDry run finished - run git diff to see package changes.`)\n  }\n\n  console.log()\n}\n\n/**\n * @param {string} version\n */\nfunction updateVersion(version) {\n  const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'))\n  pkg.version = version\n  fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\\n')\n}\n\n/**\n * @param {string} version\n * @param {Function} run\n */\nasync function publishPackage(version, run) {\n  const publicArgs = [\n    'publish',\n    '--no-git-tag-version',\n    '--new-version',\n    version,\n    '--registry',\n    'http://registry.npmjs.org/',\n    '--access',\n    'public'\n  ]\n  if (args.tag) {\n    publicArgs.push(`--tag`, args.tag)\n  }\n  try {\n    await run('yarn', publicArgs, {\n      stdio: 'pipe'\n    })\n    console.log(chalk.green(`Successfully published ${pkgName}@${version}`))\n  } catch (e) {\n    if (e.stderr.match(/previously published/)) {\n      console.log(chalk.red(`Skipping already published: ${pkgName}`))\n    } else {\n      throw e\n    }\n  }\n}\n\nmain().catch((err) => {\n  console.error(err)\n})"
  }
]