├── .github
├── FUNDING.yml
└── workflows
│ └── ci.yml
├── .gitignore
├── .gitpod.yml
├── .husky
└── pre-commit
├── .kktrc.ts
├── .prettierignore
├── .prettierrc
├── LICENSE
├── README.md
├── package.json
├── public
├── favicon.ico
└── index.html
├── renovate.json
├── src
├── assets
│ ├── logo-dark.svg
│ └── logo-light.svg
├── components
│ ├── Loading.tsx
│ ├── Search.module.less
│ └── Search.tsx
├── hook
│ └── usePath.ts
├── index.css
├── index.tsx
├── models
│ ├── global.ts
│ └── index.tsx
├── pages
│ ├── Home
│ │ ├── index.module.less
│ │ └── index.tsx
│ └── Preview
│ │ ├── Content.module.less
│ │ ├── Content.tsx
│ │ ├── DirectoryTrees.module.less
│ │ ├── DirectoryTrees.tsx
│ │ ├── github.svg
│ │ ├── home.svg
│ │ ├── icons
│ │ ├── css3.svg
│ │ ├── html5.svg
│ │ ├── javascript.map.svg
│ │ ├── javascript.svg
│ │ ├── json.svg
│ │ ├── license.svg
│ │ ├── markdown.svg
│ │ ├── react.svg
│ │ └── typescript.svg
│ │ ├── index.module.less
│ │ ├── index.tsx
│ │ └── npm.svg
├── react-app-env.d.ts
├── routers.ts
├── servers
│ └── unpkg.ts
└── utils
│ ├── request.ts
│ └── utils.ts
└── tsconfig.json
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | ko_fi: jaywcjlove
2 | buy_me_a_coffee: jaywcjlove
3 | custom: ["https://www.paypal.me/kennyiseeyou", "https://jaywcjlove.github.io/#/sponsor"]
4 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 | on:
3 | push:
4 | branches:
5 | - master
6 | jobs:
7 | build-deploy:
8 | runs-on: ubuntu-latest
9 | steps:
10 | - uses: actions/checkout@v4
11 | - uses: actions/setup-node@v4
12 | with:
13 | node-version: 20
14 | registry-url: 'https://registry.npmjs.org'
15 |
16 | - run: npm install
17 | - run: npm run build
18 |
19 | - name: Generate Contributors Images
20 | uses: jaywcjlove/github-action-contributors@main
21 | with:
22 | filter-author: (renovate\[bot\]|renovate-bot|dependabot\[bot\])
23 | output: build/CONTRIBUTORS.svg
24 | avatarSize: 42
25 |
26 | - name: Deploy
27 | uses: peaceiris/actions-gh-pages@v3
28 | with:
29 | github_token: ${{ secrets.GITHUB_TOKEN }}
30 | publish_dir: ./build
31 |
32 | - name: Is a tag created auto?
33 | id: create_tag
34 | uses: jaywcjlove/create-tag-action@main
35 | with:
36 | token: ${{ secrets.GITHUB_TOKEN }}
37 | package-path: ./package.json
38 |
39 | - name: Generate Changelog
40 | id: changelog
41 | uses: jaywcjlove/changelog-generator@main
42 | with:
43 | token: ${{ secrets.GITHUB_TOKEN }}
44 | head-ref: ${{steps.create_tag.outputs.version}}
45 | filter-author: (renovate-bot|Renovate Bot)
46 | filter: '[R|r]elease[d]\s+[v|V]\d(\.\d+){0,2}'
47 |
48 | - name: Create Release
49 | uses: ncipollo/release-action@v1
50 | if: steps.create_tag.outputs.successful
51 | with:
52 | allowUpdates: true
53 | token: ${{ secrets.GITHUB_TOKEN }}
54 | name: ${{ steps.create_tag.outputs.version }}
55 | tag: ${{ steps.create_tag.outputs.version }}
56 | body: |
57 | [](https://jaywcjlove.github.io/#/sponsor)
58 |
59 | ${{ steps.changelog.outputs.compareurl }}
60 |
61 | ${{ steps.changelog.outputs.changelog }}
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | build
2 | npm-debug.log*
3 | package-lock.json
4 | # package-lock.json
5 | # dependencies
6 | node_modules
7 | .pnp
8 | .pnp.js
9 |
10 | # testing
11 | /coverage
12 |
13 | # production
14 | /build
15 |
16 | # misc
17 | .DS_Store
18 | .env.local
19 | .env.development.local
20 | .env.test.local
21 | .env.production.local
22 |
23 | .eslintcache
24 | .cache
25 | .rdoc-dist
26 | .vscode
27 |
28 | *.bak
29 | *.tem
30 | *.temp
31 | #.swp
32 | *.*~
33 | ~*.*
34 |
--------------------------------------------------------------------------------
/.gitpod.yml:
--------------------------------------------------------------------------------
1 | ports:
2 | - port: 3000
3 | onOpen: open-preview
4 | tasks:
5 | - init: npm install
6 | command: npm run start
--------------------------------------------------------------------------------
/.husky/pre-commit:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 | . "$(dirname "$0")/_/husky.sh"
3 |
4 | npx lint-staged
--------------------------------------------------------------------------------
/.kktrc.ts:
--------------------------------------------------------------------------------
1 | import webpack, { Configuration } from 'webpack';
2 | import lessModules from '@kkt/less-modules';
3 | import { LoaderConfOptions } from 'kkt';
4 | import pkg from './package.json';
5 |
6 | export default (conf: Configuration, env: 'production' | 'development', options: LoaderConfOptions) => {
7 | conf = lessModules(conf, env, options);
8 |
9 | // Get the project version.
10 | conf.plugins!.push(
11 | new webpack.DefinePlugin({
12 | VERSION: JSON.stringify(pkg.version),
13 | }),
14 | );
15 |
16 | /** https://github.com/kktjs/kkt/issues/446 */
17 | conf.ignoreWarnings = [ { module: /node_modules[\\/]parse5[\\/]/ } ];
18 |
19 | if (env === 'production') {
20 | conf.output = { ...conf.output, publicPath: './' };
21 | conf.optimization = {
22 | ...conf.optimization,
23 | splitChunks: {
24 | cacheGroups: {
25 | reactvendor: {
26 | test: /[\\/]node_modules[\\/](react|react-dom)[\\/]/,
27 | name: 'react-vendor',
28 | chunks: 'all',
29 | },
30 | katex: {
31 | test: /[\\/]node_modules[\\/](katex)[\\/]/,
32 | name: 'katex-vendor',
33 | chunks: 'all',
34 | },
35 | prismjs: {
36 | test: /[\\/]node_modules[\\/](refractor)[\\/]/,
37 | name: 'refractor-vendor',
38 | chunks: 'all',
39 | },
40 | codemirror: {
41 | test: /[\\/]node_modules[\\/](@codemirror)[\\/]/,
42 | name: 'codemirror-vendor',
43 | chunks: 'all',
44 | },
45 | uiw: {
46 | test: /[\\/]node_modules[\\/](@uiw)[\\/]/,
47 | name: 'uiw-vendor',
48 | chunks: 'all',
49 | },
50 | parse5: {
51 | test: /[\\/]node_modules[\\/](parse5)[\\/]/,
52 | name: 'parse5-vendor',
53 | chunks: 'all',
54 | },
55 | },
56 | },
57 | };
58 | }
59 | return conf;
60 | };
61 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | **/*.md
2 | **/*.svg
3 | **/*.ejs
4 | **/*.yml
5 | package.json
6 | node_modules
7 | dist
8 | build
9 | lib
10 | test
11 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "singleQuote": true,
3 | "trailingComma": "all",
4 | "printWidth": 120,
5 | "overrides": [
6 | {
7 | "files": ".prettierrc",
8 | "options": { "parser": "json" }
9 | }
10 | ]
11 | }
12 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 uiw
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | NPM UNPKG
2 | ===
3 |
4 | [](https://jaywcjlove.github.io/#/sponsor)
5 | [](https://github.com/uiwjs/npm-unpkg/actions/workflows/ci.yml)
6 | [](https://gitpod.io/#https://github.com/uiwjs/npm-unpkg)
7 |
8 | A web application to view npm package files, Based on [`unpkg`](https://unpkg.com/).
9 |
10 | [](https://uiwjs.github.io/npm-unpkg)
11 |
12 | ## Quick Start
13 |
14 | **development**
15 |
16 | Runs the project in development mode.
17 |
18 | ```bash
19 | npm run start
20 | ```
21 |
22 | **production**
23 |
24 | Builds the app for production to the build folder.
25 |
26 | ```bash
27 | npm run build
28 | ```
29 |
30 | The build is minified and the filenames include the hashes.
31 | Your app is ready to be deployed!
32 |
33 | ## Contributors
34 |
35 | As always, thanks to our amazing contributors!
36 |
37 |
38 |
39 |
40 |
41 | Made with [github-action-contributors](https://github.com/jaywcjlove/github-action-contributors).
42 |
43 | ## License
44 |
45 | [MIT © Kenny Wong](https://github.com/kktjs/kkt/blob/master/LICENSE)
46 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "npm-unpkg",
3 | "version": "2.1.0",
4 | "description": "A web application to view npm package files, Based on unpkg.",
5 | "funding": "https://jaywcjlove.github.io/#/sponsor",
6 | "private": true,
7 | "scripts": {
8 | "prepare": "husky install",
9 | "start": "kkt start",
10 | "build": "kkt build",
11 | "prettier": "prettier --write \"**/*.{js,jsx,tsx,ts,less,md,json}\""
12 | },
13 | "repository": {
14 | "type": "git",
15 | "url": "https://github.com/uiwjs/npm-unpkg.git"
16 | },
17 | "author": "",
18 | "license": "MIT",
19 | "dependencies": {
20 | "@babel/runtime": "^7.18.9",
21 | "@codemirror/lang-css": "^6.0.0",
22 | "@codemirror/lang-html": "^6.1.0",
23 | "@codemirror/lang-javascript": "^6.0.2",
24 | "@codemirror/lang-json": "^6.0.0",
25 | "@codemirror/lang-xml": "^6.0.0",
26 | "@codemirror/legacy-modes": "^6.1.0",
27 | "@rematch/core": "~2.2.0",
28 | "@rematch/loading": "~2.1.2",
29 | "@uiw/react-codemirror": "^4.11.4",
30 | "@uiw/react-github-corners": "^1.5.14",
31 | "@uiw/react-markdown-preview": "^5.0.0",
32 | "@uiw/reset.css": "^1.0.6",
33 | "@wcj/dark-mode": "^1.0.15",
34 | "axios": "^0.27.2",
35 | "react": "^18.2.0",
36 | "react-dom": "^18.2.0",
37 | "react-redux": "~8.1.0",
38 | "react-router-dom": "^6.14.0",
39 | "rehype-rewrite": "^4.0.0",
40 | "rehype-video": "^2.0.0",
41 | "uiw": "~4.21.19"
42 | },
43 | "engines": {
44 | "node": "^16.0.0"
45 | },
46 | "lint-staged": {
47 | "*.{js,jsx,tsx,ts,less,md,json}": "prettier --write"
48 | },
49 | "devDependencies": {
50 | "@kkt/less-modules": "^7.2.0",
51 | "@types/react": "^18.0.15",
52 | "@types/react-dom": "^18.0.6",
53 | "@types/react-redux": "^7.1.24",
54 | "husky": "^8.0.1",
55 | "kkt": "^7.2.0",
56 | "lint-staged": "^13.0.3",
57 | "prettier": "^2.7.1"
58 | },
59 | "eslintConfig": {
60 | "extends": [
61 | "react-app",
62 | "react-app/jest"
63 | ]
64 | },
65 | "browserslist": {
66 | "production": [
67 | ">0.2%",
68 | "not dead",
69 | "not op_mini all"
70 | ],
71 | "development": [
72 | "last 1 chrome version",
73 | "last 1 firefox version",
74 | "last 1 safari version"
75 | ]
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/uiwjs/npm-unpkg/89322d9f8019e00dca099d669a078bf15a1c2c5f/public/favicon.ico
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | NPM UNPKG
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/renovate.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": ["config:base"],
3 | "packageRules": [
4 | {
5 | "matchPackagePatterns": ["*"],
6 | "rangeStrategy": "replace"
7 | }
8 | ]
9 | }
10 |
--------------------------------------------------------------------------------
/src/assets/logo-dark.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/logo-light.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/components/Loading.tsx:
--------------------------------------------------------------------------------
1 | export default function Loading() {
2 | return (
3 |
13 | Loading...
14 |
15 | );
16 | }
17 |
--------------------------------------------------------------------------------
/src/components/Search.module.less:
--------------------------------------------------------------------------------
1 | .input {
2 | max-width: 420px;
3 | }
4 |
5 | .egLink {
6 | color: #669eb9;
7 | padding-top: 10px;
8 | span {
9 | margin-right: 10px;
10 | }
11 | a + a {
12 | margin-left: 10px;
13 | }
14 | a {
15 | color: #669eb9;
16 | transition: color 0.3s;
17 | &:hover {
18 | color: #78bfe0;
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/components/Search.tsx:
--------------------------------------------------------------------------------
1 | import { useState, Fragment } from 'react';
2 | import Input from '@uiw/react-input';
3 | import Button from '@uiw/react-button';
4 | import { useDispatch, useSelector } from 'react-redux';
5 | import { Link, useNavigate } from 'react-router-dom';
6 | import { Dispatch, RootState } from '../models';
7 | import styles from './Search.module.less';
8 |
9 | export default function Search() {
10 | const { package: pkg, pkgname } = useSelector(({ global }: RootState) => ({
11 | package: global.package,
12 | pkgname: global.pkgname,
13 | }));
14 | const dispatch = useDispatch();
15 | const [value, setValue] = useState(pkgname);
16 | const navigate = useNavigate();
17 | const [links] = useState<{ to: string; label: string }[]>([
18 | {
19 | to: '/pkg/uiw',
20 | label: 'uiw',
21 | },
22 | {
23 | to: '/pkg/react',
24 | label: 'react',
25 | },
26 | {
27 | to: '/pkg/vue',
28 | label: 'vue',
29 | },
30 | {
31 | to: '/pkg/react@18',
32 | label: 'react@18',
33 | },
34 | {
35 | to: '/pkg/react@18.2.0',
36 | label: 'react@18.2.0',
37 | },
38 | ]);
39 | const name = pkg && pkg.name ? `${pkg.name}@${pkg.version}` : pkgname;
40 | const gotoPreview = () => {
41 | if (value) {
42 | dispatch.global.update({ showSearch: false });
43 | navigate(`/pkg/${value}`);
44 | }
45 | }
46 | return (
47 |
48 | {
54 | setValue(e.target.value);
55 | }}
56 | onKeyUp={(e) => {
57 | if (e.key === "Enter") {
58 | gotoPreview();
59 | }
60 | }}
61 | addonAfter={
62 |
69 | }
70 | placeholder="package or package@version"
71 | />
72 |
73 | E.g.
74 | {links.map((item, index) => {
75 | return (
76 | {
80 | dispatch.global.update({ showSearch: false });
81 | }}
82 | >
83 | {item.label}
84 |
85 | );
86 | })}
87 |
88 |
89 | );
90 | }
91 |
--------------------------------------------------------------------------------
/src/hook/usePath.ts:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from 'react';
2 | import { useParams } from 'react-router-dom';
3 | import { Params } from '../models/global';
4 |
5 | export function usePath() {
6 | const { org, name, filename, '*': other } = useParams();
7 | const [filePath, setFilePath] = useState('');
8 | const [pkgName, setPkgName] = useState('');
9 |
10 | useEffect(() => {
11 | if (filename) {
12 | setFilePath(`${filename}${other ? `/${other}` : ''}`);
13 | }
14 | }, [filename, other]);
15 |
16 | useEffect(() => {
17 | setPkgName(`${org ? `${org}/${name}` : name}`);
18 | }, [org, name]);
19 |
20 | return { org, name, filename, other, filePath, pkgName };
21 | }
22 |
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | font-family: -apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji;
3 | }
4 |
5 | [data-color-mode*='dark'],
6 | [data-color-mode*='dark'] body {
7 | --color-fg-default: #c9d1d9;
8 | --color-fg-muted: #8b949e;
9 | --color-fg-subtle: #484f58;
10 | --color-canvas-default: #0d1117;
11 | --color-canvas-subtle: #161b22;
12 | --color-border-default: #30363d;
13 | --color-border-muted: #21262d;
14 | --color-neutral-muted: rgba(110, 118, 129, 0.4);
15 | --color-accent-fg: #58a6ff;
16 | --color-accent-emphasis: #1f6feb;
17 | --color-attention-subtle: rgba(187, 128, 9, 0.15);
18 | --color-danger-fg: #f85149;
19 | }
20 | [data-color-mode*='light'],
21 | [data-color-mode*='light'] body {
22 | --color-fg-default: #24292f;
23 | --color-fg-muted: #57606a;
24 | --color-fg-subtle: #6e7781;
25 | --color-canvas-default: #ffffff;
26 | --color-canvas-subtle: #f6f8fa;
27 | --color-border-default: #d0d7de;
28 | --color-border-muted: hsla(210, 18%, 87%, 1);
29 | --color-neutral-muted: rgba(175, 184, 193, 0.2);
30 | --color-accent-fg: #0969da;
31 | --color-accent-emphasis: #0969da;
32 | --color-attention-subtle: #fff8c5;
33 | --color-danger-fg: #cf222e;
34 | }
35 |
36 | #root, body, html {
37 | height: 100%;
38 | background-color: #343a40;
39 | }
40 |
--------------------------------------------------------------------------------
/src/index.tsx:
--------------------------------------------------------------------------------
1 | import { createRoot } from 'react-dom/client';
2 | import { Provider } from 'react-redux';
3 | import Loader from '@uiw/react-loader';
4 | import GitHubCorners from '@uiw/react-github-corners';
5 | import { RouterProvider, createHashRouter } from 'react-router-dom';
6 | import '@uiw/reset.css';
7 | import { store } from './models';
8 | import './index.css';
9 | import { routes } from './routers';
10 |
11 | const Loading = () => (
12 |
13 |
14 |
15 | );
16 |
17 | const container = document.getElementById('root');
18 | const root = createRoot(container!);
19 |
20 | root.render(
21 |
22 |
23 | } />
24 |
25 | );
26 |
--------------------------------------------------------------------------------
/src/models/global.ts:
--------------------------------------------------------------------------------
1 | import { createModel } from '@rematch/core';
2 | import { RootModel } from './';
3 | import { getDirectoryTrees, getFileContent } from '../servers/unpkg';
4 | import { dataFilesSort } from '../utils/utils';
5 |
6 | export interface Files {
7 | path: string;
8 | type: 'directory' | 'file';
9 | extname: string;
10 | integrity: string;
11 | lastModified: string;
12 | size: number;
13 | files?: Files[];
14 | }
15 |
16 | export interface PackageJSON {
17 | name: string;
18 | version: string;
19 | description: string;
20 | author: string;
21 | homepage?: string;
22 | repository?:
23 | | {
24 | type?: string;
25 | url?: string;
26 | }
27 | | string;
28 | license: string;
29 | main: string;
30 | module: string;
31 | files: string[];
32 | publishConfig?: {
33 | [key: string]: any;
34 | };
35 | keywords: string[];
36 | peerDependencies?: {
37 | [key: string]: string;
38 | };
39 | dependencies?: {
40 | [key: string]: string;
41 | };
42 | devDependencies?: {
43 | [key: string]: string;
44 | };
45 | gitHead?: string;
46 | }
47 |
48 | export interface GlobalState {
49 | notFindPkg?: boolean;
50 | pkgname?: string;
51 | selectFile?: string;
52 | content?: string;
53 | extname?: string;
54 | showSearch?: boolean;
55 | package?: PackageJSON;
56 | files?: Files[];
57 | }
58 |
59 | export type Params = {
60 | org?: string;
61 | name?: string;
62 | filename?: string;
63 | '*'?: string;
64 | };
65 |
66 | export const global = createModel()({
67 | state: {
68 | notFindPkg: false,
69 | pkgname: '',
70 | files: [],
71 | selectFile: '',
72 | content: '',
73 | extname: '',
74 | package: {} as PackageJSON,
75 | showSearch: false,
76 | } as GlobalState,
77 | reducers: {
78 | update: (state, payload: GlobalState) => ({
79 | ...state,
80 | ...payload,
81 | }),
82 | },
83 | effects: (dispatch) => {
84 | const { global } = dispatch;
85 | return {
86 | async setPkgname(params: Params) {
87 | const { name, org } = params || {};
88 | const state: GlobalState = {};
89 | if (!org && name) {
90 | state.pkgname = name;
91 | } else if (org && name) {
92 | state.pkgname = `${org}/${name}`;
93 | }
94 | if (params.filename) {
95 | state.selectFile = params.filename;
96 | }
97 | global.update({ ...state });
98 | },
99 | async getDirectoryTrees(_, state): Promise {
100 | const data: Files = await getDirectoryTrees(state.global.pkgname!);
101 | if (data && data.files) {
102 | const dataSort = dataFilesSort(data.files);
103 | global.update({ files: dataSort });
104 | } else {
105 | global.update({ files: [] });
106 | }
107 | },
108 | async getPackageJSON(_, state) {
109 | const data: PackageJSON = await getFileContent(`${state.global.pkgname}/package.json`);
110 | if (data && typeof data === 'object') {
111 | global.update({ package: { ...data }, notFindPkg: false });
112 | } else {
113 | global.update({
114 | package: undefined,
115 | notFindPkg: true,
116 | });
117 | }
118 | },
119 | async getFileContent(filepath: string = '', state) {
120 | if (!filepath) {
121 | dispatch.global.update({ content: '', extname: '' });
122 | return;
123 | }
124 | const type = filepath.replace(/.+\./, '');
125 | const data: PackageJSON = await getFileContent(`${state.global.pkgname}/${filepath}`);
126 | if (typeof data === 'string' || !data) {
127 | dispatch.global.update({ content: data, extname: type });
128 | } else if (data && /\.(json|map)$/.test(filepath)) {
129 | dispatch.global.update({ content: JSON.stringify(data, null, 2), extname: type });
130 | }
131 | },
132 | };
133 | },
134 | });
135 |
--------------------------------------------------------------------------------
/src/models/index.tsx:
--------------------------------------------------------------------------------
1 | import { init, RematchRootState, RematchDispatch, Models } from '@rematch/core';
2 | import loadingPlugin, { ExtraModelsFromLoading } from '@rematch/loading';
3 | import { global } from './global';
4 |
5 | export interface RootModel extends Models, FullModel {
6 | global: typeof global;
7 | }
8 |
9 | type FullModel = ExtraModelsFromLoading;
10 |
11 | export const models: RootModel = { global } as RootModel;
12 | export const store = init({
13 | models,
14 | plugins: [loadingPlugin()],
15 | });
16 |
17 | export type Store = typeof store;
18 | export type Dispatch = RematchDispatch;
19 | export type RootState = RematchRootState;
20 |
--------------------------------------------------------------------------------
/src/pages/Home/index.module.less:
--------------------------------------------------------------------------------
1 | .content {
2 | background-color: #343a40;
3 | min-height: 100vh;
4 | display: flex;
5 | flex-direction: column;
6 | align-items: center;
7 | justify-content: center;
8 | // font-size: calc(10px + 2vmin);
9 | // color: #fff;
10 | > h1 {
11 | font-size: 24px;
12 | font-weight: bold;
13 | color: #fff;
14 | padding-bottom: 24px;
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/pages/Home/index.tsx:
--------------------------------------------------------------------------------
1 | import Search from '../../components/Search';
2 | import styles from './index.module.less';
3 |
4 | export function Component() {
5 | return (
6 |
7 |
NPM UNPKG
8 |
9 |
10 | );
11 | }
12 |
13 | Component.displayName = 'HomePage';
14 |
--------------------------------------------------------------------------------
/src/pages/Preview/Content.module.less:
--------------------------------------------------------------------------------
1 | .code {
2 | min-height: 100%;
3 | > pre {
4 | min-height: 100%;
5 | display: inline-block;
6 | }
7 | }
8 |
9 | .loader {
10 | min-width: 100%;
11 | min-height: 100%;
12 | max-width: 100%;
13 | display: flex;
14 | > * {
15 | min-height: 100%;
16 | min-width: 100%;
17 | // code[class*="language-"], pre[class*="language-"] {
18 | // min-height: 100%;
19 | // }
20 | }
21 | }
22 |
23 | .viewRaw {
24 | position: absolute;
25 | padding: 2px 5px;
26 | border-radius: 2px;
27 | font-size: 14px;
28 | right: 10px;
29 | top: 10px;
30 | &:hover {
31 | background-color: #333;
32 | color: #fff;
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/pages/Preview/Content.tsx:
--------------------------------------------------------------------------------
1 | import { useMemo, useEffect, useState, Fragment } from 'react';
2 | import { useSelector } from 'react-redux';
3 | import Loader from '@uiw/react-loader';
4 | import MarkdownPreview from '@uiw/react-markdown-preview';
5 | import rehypeVideo from 'rehype-video';
6 | import CodeMirror, { ReactCodeMirrorProps } from '@uiw/react-codemirror';
7 | import { Extension } from '@codemirror/state';
8 | import { StreamLanguage } from '@codemirror/language';
9 | import { stylus } from '@codemirror/legacy-modes/mode/stylus';
10 | import { yaml } from '@codemirror/legacy-modes/mode/yaml';
11 | import { toml } from '@codemirror/legacy-modes/mode/toml';
12 | import { javascript } from '@codemirror/lang-javascript';
13 | import { html } from '@codemirror/lang-html';
14 | import { xml } from '@codemirror/lang-xml';
15 | import { css } from '@codemirror/lang-css';
16 | import { json } from '@codemirror/lang-json';
17 | import { usePath } from '../../hook/usePath';
18 | import { RootState } from '../../models';
19 | import styles from './Content.module.less';
20 |
21 | const langs: Record = {
22 | javascript,
23 | js: () => javascript(),
24 | mjs: () => javascript({ jsx: true }),
25 | cjs: () => javascript(),
26 | jsx: () => javascript({ jsx: true }),
27 | json,
28 | css,
29 | less: () => css(),
30 | styl: () => StreamLanguage.define(stylus),
31 | '.editorconfig': () => StreamLanguage.define(toml),
32 | xml,
33 | lock: () => StreamLanguage.define(yaml),
34 | yml: () => StreamLanguage.define(yaml),
35 | html,
36 | htm: () => html(),
37 | map: () => json(),
38 | typescript: () => javascript({ typescript: true }),
39 | ts: () => javascript({ typescript: true }),
40 | tsx: () => javascript({ jsx: true, typescript: true }),
41 | };
42 |
43 | export default function DirectoryTrees() {
44 | const dark = document.documentElement.getAttribute('data-color-mode');
45 | const [theme, setTheme] = useState(dark === 'dark' ? 'dark' : 'light');
46 | useEffect(() => {
47 | setTheme(document.documentElement.getAttribute('data-color-mode') === 'dark' ? 'dark' : 'light');
48 | document.addEventListener('colorschemechange', (e) => {
49 | setTheme(e.detail.colorScheme as ReactCodeMirrorProps['theme']);
50 | });
51 | }, []);
52 | const { loading, content, extname } = useSelector(({ global, loading }: RootState) => ({
53 | loading: loading.effects.global.getFileContent,
54 | content: global.content,
55 | extname: global.extname,
56 | global,
57 | }));
58 | const path = usePath();
59 | const filePath = `https://unpkg.com/browse/${path.pkgName}/${path.filePath || ''}`;
60 | const contentView = useMemo(() => {
61 | // let ext = extname;
62 | // switch (extname) {
63 | // case 'tsx':
64 | // ext = 'typescript';
65 | // break;
66 | // case 'ts':
67 | // ext = 'typescript';
68 | // break;
69 | // case 'map':
70 | // ext = 'json';
71 | // break;
72 | // case 'markdown':
73 | // ext = 'md';
74 | // break;
75 | // default:
76 | // break;
77 | // }
78 | if (!content) return ;
79 | if (extname && /(md|markdown)$/.test(extname)) {
80 | return (
81 |
82 |
88 |
89 | );
90 | }
91 | if (extname || (extname && langs[extname])) {
92 | const extensions: Extension[] = [];
93 | if (langs[extname]) {
94 | extensions.push(langs[extname]());
95 | }
96 | return ;
97 | }
98 | return {content};
99 | }, [extname, content, filePath, theme]);
100 |
101 |
102 | return (
103 |
104 |
105 |
106 |
107 |
108 | {contentView}
109 |
110 |
111 | );
112 | }
113 |
--------------------------------------------------------------------------------
/src/pages/Preview/DirectoryTrees.module.less:
--------------------------------------------------------------------------------
1 | .tags {
2 | color: #333 !important;
3 | padding: 0px 3px;
4 | transform: scale(0.8);
5 | vertical-align: middle;
6 | line-height: 16px;
7 | align-self: center;
8 | margin-right: -6px !important;
9 | }
10 |
--------------------------------------------------------------------------------
/src/pages/Preview/DirectoryTrees.tsx:
--------------------------------------------------------------------------------
1 | import React, { useMemo } from 'react';
2 | import { useSelector } from 'react-redux';
3 | import Menu, { MenuItemProps } from '@uiw/react-menu';
4 | import Tag from '@uiw/react-tag';
5 | import Loader from '@uiw/react-loader';
6 | import { NavLink, useLocation } from 'react-router-dom';
7 | import { RootState } from '../../models';
8 | import { Files } from '../../models/global';
9 | import styles from './DirectoryTrees.module.less';
10 | import { ReactComponent as Markdown } from './icons/markdown.svg';
11 | import { ReactComponent as TypeScript } from './icons/typescript.svg';
12 | import { ReactComponent as CSS } from './icons/css3.svg';
13 | import { ReactComponent as JavaScript } from './icons/javascript.svg';
14 | import { ReactComponent as Json } from './icons/json.svg';
15 | import { ReactComponent as JavaScriptMap } from './icons/javascript.map.svg';
16 | import { ReactComponent as ReactSVG } from './icons/react.svg';
17 | import { ReactComponent as License } from './icons/license.svg';
18 |
19 | const prettyBytes = (num: number, precision = 3, addSpace = true) => {
20 | const UNITS = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
21 | if (Math.abs(num) < 1) return num + (addSpace ? ' ' : '') + UNITS[0];
22 | const exponent = Math.min(Math.floor(Math.log10(num < 0 ? -num : num) / 3), UNITS.length - 1);
23 | const n = Number(((num < 0 ? -num : num) / 1000 ** exponent).toPrecision(precision));
24 | return (num < 0 ? '-' : '') + n + (addSpace ? ' ' : '') + UNITS[exponent];
25 | };
26 |
27 | function MeunItemView(props: { path?: string; filepath?: string; size?: number } = {}) {
28 | const menuProps: MenuItemProps = {};
29 | const iconName = (props.filepath || '').toLocaleLowerCase();
30 | if (/.md$/.test(iconName)) {
31 | menuProps.icon = ;
32 | } else if (/.(ts|tsx)$/.test(iconName)) {
33 | menuProps.icon = ;
34 | } else if (/license$/.test(iconName)) {
35 | menuProps.icon = ;
36 | } else if (/.(css|styl|less)$/.test(iconName)) {
37 | menuProps.icon = ;
38 | } else if (/.(json)$/.test(iconName)) {
39 | menuProps.icon = ;
40 | } else if (/.(js\.map)$/.test(iconName)) {
41 | menuProps.icon = ;
42 | } else if (/.(js|mjs)$/.test(iconName)) {
43 | menuProps.icon = ;
44 | } else if (/.(jsx)$/.test(iconName)) {
45 | menuProps.icon = ;
46 | } else {
47 | menuProps.icon = 'file-text';
48 | }
49 |
50 | return useMemo(
51 | () => (
52 |
59 | }
60 | {...menuProps}
61 | text={props.filepath!.replace(/^\//, '')}
62 | />
63 | ),
64 | // eslint-disable-next-line react-hooks/exhaustive-deps
65 | [props.path, props.size, props.filepath],
66 | );
67 | }
68 |
69 | export default function DirectoryTrees() {
70 | const location = useLocation();
71 | const { loading, files, pkgname } = useSelector(({ global, loading }: RootState) => ({
72 | loading: loading.effects.global.getDirectoryTrees,
73 | files: global.files,
74 | pkgname: global.pkgname,
75 | }));
76 |
77 | const pathname = location.pathname.replace(new RegExp(`/pkg/${pkgname}/file`), '');
78 |
79 | function renderMenuItem(data: Files[] = [], menuItems: any = []) {
80 | data.forEach((item, idx) => {
81 | if (item.type === 'directory') {
82 | const collapse = new RegExp(`^${item.path}`).test(pathname);
83 | menuItems.push(
84 |
93 | {renderMenuItem(item.files)}
94 | ,
95 | );
96 | } else if (item.type === 'file') {
97 | const filename = item.path.replace(/(.+?)\//g, '');
98 | menuItems.push(
99 | ,
100 | );
101 | }
102 | });
103 | return menuItems;
104 | }
105 | return (
106 |
107 |
110 |
111 | );
112 | }
113 |
--------------------------------------------------------------------------------
/src/pages/Preview/github.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/pages/Preview/home.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/pages/Preview/icons/css3.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/pages/Preview/icons/html5.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/pages/Preview/icons/javascript.map.svg:
--------------------------------------------------------------------------------
1 |
7 |
--------------------------------------------------------------------------------
/src/pages/Preview/icons/javascript.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/pages/Preview/icons/json.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/pages/Preview/icons/license.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/pages/Preview/icons/markdown.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/pages/Preview/icons/react.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/pages/Preview/icons/typescript.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/pages/Preview/index.module.less:
--------------------------------------------------------------------------------
1 | .warpper {
2 | flex-grow: 1;
3 | display: flex;
4 | height: calc(100vh - 53px);
5 | }
6 |
7 | .header {
8 | background-color: var(--color-canvas-subtle);
9 | padding: 0 16px;
10 | display: flex;
11 | align-items: center;
12 | border-bottom: 1px solid var(--color-border-default);
13 | dark-mode {
14 | color: initial;
15 | }
16 | > a {
17 | display: flex;
18 | color: var(--color-theme-text);
19 | &.github {
20 | margin-right: 10px;
21 | margin-left: 10px;
22 | }
23 | &:hover {
24 | color: #cb3837;
25 | &.github {
26 | color: #a9a9ff;
27 | }
28 | }
29 | }
30 | > span {
31 | color: var(--color-fg-default);
32 | }
33 | > div:global(.w-divider) {
34 | background-color: var(--color-fg-subtle);
35 | margin-top: 2px;
36 | }
37 | .unpkg {
38 | text-decoration: none;
39 | font-weight: 900;
40 | }
41 | }
42 |
43 | :global(.w-layout) {
44 | background-color: var(--color-canvas-subtle);
45 | }
46 |
47 | :global(.w-menu:not(.w-menu-dark)) {
48 | background-color: transparent;
49 | color: var(--color-fg-default);
50 | a {
51 | color: var(--color-fg-default);
52 |
53 | }
54 | }
55 |
56 | :global(.w-split-horizontal > .w-split-bar::before),
57 |
58 | :global(.w-split-horizontal > .w-split-bar::after) {
59 | box-shadow: inset 0 1px 0 0 var(--color-fg-default), 0 1px 0 0 var(--color-fg-default);
60 | }
61 |
62 | :global(.w-split-bar) {
63 | background-color: var(--color-fg-subtle);
64 | }
65 |
66 | :global(.w-split-horizontal > .w-split-bar) {
67 | box-shadow: inset 1px 0 0 0 var(--color-border-default), 1px 0 0 0 var(--color-border-default);
68 | }
--------------------------------------------------------------------------------
/src/pages/Preview/index.tsx:
--------------------------------------------------------------------------------
1 | import { useEffect, Fragment, useMemo } from 'react';
2 | import { useSelector, useDispatch } from 'react-redux';
3 | import Divider from '@uiw/react-divider';
4 | import Layout from '@uiw/react-layout';
5 | import Button from '@uiw/react-button';
6 | import Split from '@uiw/react-split';
7 | import Modal from '@uiw/react-modal';
8 | import '@wcj/dark-mode';
9 | import Search from '../../components/Search';
10 | import { PackageJSON } from '../../models/global';
11 | import { RootState, Dispatch } from '../../models';
12 | import { ReactComponent as NPM } from './npm.svg';
13 | import { ReactComponent as Github } from './github.svg';
14 | import { ReactComponent as Home } from './home.svg';
15 | import DirectoryTrees from './DirectoryTrees';
16 | import ContentView from './Content';
17 | import { usePath } from '../../hook/usePath';
18 | import styles from './index.module.less';
19 |
20 | const { Header, Content } = Layout;
21 |
22 | export function Component() {
23 | const {
24 | showSearch,
25 | notFindPkg,
26 | package: Info,
27 | pkgname,
28 | } = useSelector(({ global, loading }: RootState) => ({
29 | loading: loading.effects.global.getDirectoryTrees,
30 | pkgname: global.pkgname,
31 | notFindPkg: global.notFindPkg,
32 | package: (global.package || {}) as PackageJSON,
33 | showSearch: global.showSearch,
34 | }));
35 | const dispatch = useDispatch();
36 | const path = usePath();
37 |
38 | useEffect(() => {
39 | if (path.pkgName && path.pkgName !== pkgname) {
40 | dispatch.global.setPkgname(path);
41 | dispatch.global.getDirectoryTrees({});
42 | dispatch.global.getPackageJSON({});
43 | }
44 | }, [pkgname, dispatch.global, path]);
45 |
46 | useEffect(() => {
47 | document.title = `${path.pkgName} - NPM UNPKG`;
48 | }, [path.pkgName]);
49 |
50 | useEffect(() => {
51 | dispatch.global.getFileContent(path.filePath);
52 | }, [dispatch.global, path.filePath]);
53 |
54 | const nameView = useMemo(
55 | () => (
56 |
64 | ),
65 | [Info, dispatch.global, path.pkgName],
66 | );
67 |
68 | const unPkgView = useMemo(
69 | () => (
70 |
71 | UNPKG
72 |
73 | ),
74 | [path.pkgName],
75 | );
76 | return (
77 |
78 |
79 |
80 | console.log('您点击了确定按钮!')}
87 | onCancel={() => console.log('您点击了取消按钮!')}
88 | onClosed={() => dispatch.global.update({ showSearch: false })}
89 | >
90 |
91 |
92 | {nameView}
93 |
94 |
95 | {unPkgView}
96 |
97 | {Info.name && (
98 |
99 |
100 |
101 |
102 |
103 | {Info.repository && (
104 |
105 |
113 |
114 |
115 |
116 | )}
117 | {Info.homepage && (
118 |
119 |
120 |
121 |
122 |
123 | )}
124 | {Info.license && (
125 |
126 |
127 | {Info.license}
128 |
129 | )}
130 | {Info.description && (
131 |
132 |
133 |
134 | {Info.description}
135 |
136 |
137 | )}
138 |
139 | )}
140 | {notFindPkg && (
141 |
142 |
143 | Cannot find package {path.pkgName}.
144 |
145 | )}
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 | );
159 | }
160 |
161 | Component.displayName = 'PreviewPage';
162 |
--------------------------------------------------------------------------------
/src/pages/Preview/npm.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/react-app-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 | ///
4 |
5 | declare namespace NodeJS {
6 | interface ProcessEnv {
7 | readonly NODE_ENV: 'development' | 'production' | 'test';
8 | readonly PUBLIC_URL: string;
9 | }
10 | }
11 |
12 | declare module '*.bmp' {
13 | const src: string;
14 | export default src;
15 | }
16 |
17 | declare module '*.gif' {
18 | const src: string;
19 | export default src;
20 | }
21 |
22 | declare module '*.jpg' {
23 | const src: string;
24 | export default src;
25 | }
26 |
27 | declare module '*.jpeg' {
28 | const src: string;
29 | export default src;
30 | }
31 |
32 | declare module '*.png' {
33 | const src: string;
34 | export default src;
35 | }
36 |
37 | declare module '*.webp' {
38 | const src: string;
39 | export default src;
40 | }
41 |
42 | declare module '*.svg' {
43 | import * as React from 'react';
44 |
45 | export const ReactComponent: React.FunctionComponent>;
46 |
47 | const src: string;
48 | export default src;
49 | }
50 |
51 | declare module '*.module.less' {
52 | const classes: { readonly [key: string]: string };
53 | export default classes;
54 | }
55 |
56 | declare module '*.less' {
57 | const src: string;
58 | export default src;
59 | }
60 | declare module '*.module.css' {
61 | const classes: { readonly [key: string]: string };
62 | export default classes;
63 | }
64 |
65 | declare module '*.module.scss' {
66 | const classes: { readonly [key: string]: string };
67 | export default classes;
68 | }
69 |
70 | declare module '*.module.sass' {
71 | const classes: { readonly [key: string]: string };
72 | export default classes;
73 | }
74 |
--------------------------------------------------------------------------------
/src/routers.ts:
--------------------------------------------------------------------------------
1 | import { RouteObject } from 'react-router-dom';
2 |
3 | export const routes: RouteObject[] = [
4 | {
5 | path: '/',
6 | lazy: () => import('./pages/Home'),
7 | },
8 | {
9 | path: '/pkg/:name',
10 | lazy: () => import('./pages/Preview'),
11 | },
12 | {
13 | path: '/pkg/:name/file/:filename/*',
14 | lazy: () => import('./pages/Preview'),
15 | },
16 | {
17 | path: '/pkg/:org/:name',
18 | lazy: () => import('./pages/Preview'),
19 | },
20 | {
21 | path: '/pkg/:org/:name/file/:filename/*',
22 | lazy: () => import('./pages/Preview'),
23 | },
24 | ];
25 |
--------------------------------------------------------------------------------
/src/servers/unpkg.ts:
--------------------------------------------------------------------------------
1 | import request from '../utils/request';
2 |
3 | export function getDirectoryTrees(pkg: string) {
4 | return request(`https://unpkg.com/${pkg}/?meta`, {
5 | method: 'GET',
6 | });
7 | }
8 |
9 | export function getFileContent(path: string) {
10 | return request(`https://unpkg.com/${path}`, {
11 | method: 'GET',
12 | });
13 | }
14 |
--------------------------------------------------------------------------------
/src/utils/request.ts:
--------------------------------------------------------------------------------
1 | import axios, { AxiosRequestConfig } from 'axios';
2 | // import history from '../routes/history';
3 | import { splitUrl } from './utils';
4 |
5 | // Get the current location.
6 | // const location = history.location;
7 | const codeMessage = {
8 | 200: '服务器成功返回请求的数据。',
9 | 201: '新建或修改数据成功。',
10 | 202: '一个请求已经进入后台排队(异步任务)。',
11 | 204: '删除数据成功。',
12 | 400: '发出的请求有错误,服务器没有进行新建或修改数据的操作。',
13 | 401: '用户没有权限(令牌、用户名、密码错误)。',
14 | 403: '用户得到授权,但是访问是被禁止的。',
15 | 404: '发出的请求针对的是不存在的记录,服务器没有进行操作。',
16 | 406: '请求的格式不可得。',
17 | 410: '请求的资源被永久删除,且不会再得到的。',
18 | 422: '当创建一个对象时,发生一个验证错误。',
19 | 500: '服务器发生错误,请检查服务器。',
20 | 502: '网关错误。',
21 | 503: '服务不可用,服务器暂时过载或维护。',
22 | 504: '网关超时。',
23 | };
24 |
25 | export interface Options extends AxiosRequestConfig {
26 | body?: any;
27 | }
28 |
29 | /**
30 | * Requests a URL, returning a promise.
31 | *
32 | * @param {string} url The URL we want to request
33 | * @param {object} [options] The options we want to pass to "fetch"
34 | * @return {object} An object containing either "data" or "err"
35 | */
36 | export default function request(url: string, options: Options = {}) {
37 | const method = options.method || 'GET';
38 | const newOptions: Options = {
39 | url,
40 | method,
41 | data: options.body,
42 | headers: {
43 | 'Content-Type': 'application/json; charset=utf-8',
44 | Accept: 'application/json',
45 | },
46 | };
47 |
48 | if (/(GET)/.test(method)) {
49 | newOptions.url = splitUrl(url, { ...options.body });
50 | delete newOptions.body;
51 | }
52 |
53 | return axios
54 | .request(newOptions)
55 | .then((response) => {
56 | return response.data;
57 | })
58 | .catch((err) => {
59 | const response = err.response;
60 | if (response && response.status >= 200 && response.status < 300) {
61 | return response;
62 | }
63 | if (!response) {
64 | return;
65 | }
66 | const status = response.status;
67 | const errortext = (codeMessage as any)[status] || response.statusText;
68 | // Notification.error({
69 | // message: '错误提示:',
70 | // description: (response.data && response.data.info) || '没有错误提示',
71 | // });
72 | const error = new Error(errortext);
73 | error.name = response.status;
74 | // error.response = response;
75 | if (response.data) {
76 | return response.data;
77 | }
78 | throw error;
79 | });
80 | }
81 |
--------------------------------------------------------------------------------
/src/utils/utils.ts:
--------------------------------------------------------------------------------
1 | import { Files } from '../models/global';
2 |
3 | // 拼接url参数
4 | export function splitUrl(url: string, options: { [x: string]: any }) {
5 | let urlNew = url;
6 | const paramsArray: string[] = [];
7 | // Object.keys(options).forEach(key => paramsArray.push(key + '=' + options[key]));
8 | Object.keys(options).forEach((key) => paramsArray.push(`${key}=${options[key]}`));
9 | if (Object.keys(options).length === 0) {
10 | return url;
11 | }
12 | if (/\?/.test(urlNew) === false) {
13 | urlNew = `${urlNew}?${paramsArray.join('&')}`;
14 | } else {
15 | urlNew += `&${paramsArray.join('&')}`;
16 | }
17 | return urlNew;
18 | }
19 |
20 | export function nameSort(data: Files[] = [], resule: Files[] = []) {
21 | let dirs: Files[] = [];
22 | let files: Files[] = [];
23 | data.forEach((item) => {
24 | if (item.type === 'directory') {
25 | dirs.push(item);
26 | } else if (item.type === 'file') {
27 | files.push(item);
28 | }
29 | });
30 | dirs = dirs.sort((a, b) => {
31 | return a.path.replace(/^\//, '').localeCompare(b.path.replace(/^\//, ''));
32 | });
33 | files = files.sort((a, b) => {
34 | return a.path.replace(/^\//, '').localeCompare(b.path.replace(/^\//, ''));
35 | });
36 | return [...dirs, ...files];
37 | }
38 |
39 | export function dataFilesSort(files: Files[] = [], resule: Files[] = []) {
40 | resule = nameSort(files);
41 | resule = resule.map((file) => {
42 | if (file.files) {
43 | file.files = dataFilesSort(file.files);
44 | }
45 | return file;
46 | });
47 | return resule;
48 | }
49 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "jsx": "react-jsx",
4 | "target": "esnext",
5 | "lib": ["dom", "dom.iterable", "esnext"],
6 | "allowJs": true,
7 | "skipLibCheck": true,
8 | "esModuleInterop": true,
9 | "allowSyntheticDefaultImports": true,
10 | "strict": true,
11 | "forceConsistentCasingInFileNames": true,
12 | "module": "esnext",
13 | "moduleResolution": "node",
14 | "resolveJsonModule": true,
15 | "isolatedModules": true,
16 | "declaration": true,
17 | "baseUrl": "./src",
18 | "noFallthroughCasesInSwitch": true,
19 | "noEmit": true
20 | }
21 | }
22 |
--------------------------------------------------------------------------------