@antdv-vue/route-utils 0.0.0-stage → 1.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +13 -0
- package/README.md +67 -2
- package/dist/getFlatMenus/index.d.ts +11 -0
- package/dist/getFlatMenus/index.js +24 -0
- package/dist/getMatchMenu/index.d.ts +20 -0
- package/dist/getMatchMenu/index.js +56 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +6 -0
- package/dist/path-to-regexp.d.ts +130 -0
- package/dist/path-to-regexp.js +371 -0
- package/dist/sha265.d.ts +5 -0
- package/dist/sha265.js +218 -0
- package/dist/transformRoute/index.d.ts +17 -0
- package/dist/transformRoute/index.js +208 -0
- package/dist/typing.d.ts +31 -0
- package/dist/typing.js +1 -0
- package/package.json +49 -3
package/CHANGELOG.md
ADDED
package/README.md
CHANGED
|
@@ -1,3 +1,68 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @antdv-vue/route-utils
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Route transformation, menu data generation, and path matching utilities for Pro Layout.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @antdv-vue/route-utils
|
|
9
|
+
# or
|
|
10
|
+
pnpm add @antdv-vue/route-utils
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## What's Included
|
|
14
|
+
|
|
15
|
+
| Export | Description |
|
|
16
|
+
|--------|-------------|
|
|
17
|
+
| `transformRoute` | Transform route configuration into menu data |
|
|
18
|
+
| `getFlatMenus` | Flatten nested menu structure |
|
|
19
|
+
| `getMatchMenu` | Get matched menu items by path |
|
|
20
|
+
| `path-to-regexp` | Path pattern matching utilities |
|
|
21
|
+
| `sha265` | SHA-256 hashing utility |
|
|
22
|
+
|
|
23
|
+
## Core Types
|
|
24
|
+
|
|
25
|
+
### MenuDataItem
|
|
26
|
+
|
|
27
|
+
```ts
|
|
28
|
+
interface MenuDataItem {
|
|
29
|
+
children?: MenuDataItem[]
|
|
30
|
+
meta?: MetaRecord
|
|
31
|
+
path: string
|
|
32
|
+
key?: string
|
|
33
|
+
name?: string | symbol
|
|
34
|
+
[key: string]: any
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### MetaRecord
|
|
39
|
+
|
|
40
|
+
```ts
|
|
41
|
+
interface MetaRecord {
|
|
42
|
+
hideInMenu?: boolean // Hide this item and children from menu
|
|
43
|
+
hideChildrenInMenu?: boolean // Hide children from menu
|
|
44
|
+
icon?: any // Menu icon
|
|
45
|
+
title?: string // Menu title
|
|
46
|
+
locale?: string | false // Custom i18n key
|
|
47
|
+
[key: string]: any
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Usage
|
|
52
|
+
|
|
53
|
+
```ts
|
|
54
|
+
import { transformRoute, getFlatMenus, getMatchMenu } from '@antdv-vue/route-utils'
|
|
55
|
+
|
|
56
|
+
// Transform route config to menu data
|
|
57
|
+
const { menuData } = transformRoute(routes)
|
|
58
|
+
|
|
59
|
+
// Flatten menus
|
|
60
|
+
const flatMenus = getFlatMenus(menuData)
|
|
61
|
+
|
|
62
|
+
// Match current path
|
|
63
|
+
const matched = getMatchMenu('/dashboard', menuData)
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## License
|
|
67
|
+
|
|
68
|
+
[MIT](../../LICENSE)
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { MenuDataItem } from "../typing.js";
|
|
2
|
+
|
|
3
|
+
//#region src/getFlatMenus/index.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* 获取打平的 menuData
|
|
6
|
+
* 以 path 为 key
|
|
7
|
+
* @param menuData
|
|
8
|
+
*/
|
|
9
|
+
declare function getFlatMenus(menuData?: MenuDataItem[]): Record<string, MenuDataItem>;
|
|
10
|
+
//#endregion
|
|
11
|
+
export { getFlatMenus };
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { stripQueryStringAndHashFromPath } from "../transformRoute/index.js";
|
|
2
|
+
//#region src/getFlatMenus/index.ts
|
|
3
|
+
/**
|
|
4
|
+
* 获取打平的 menuData
|
|
5
|
+
* 以 path 为 key
|
|
6
|
+
* @param menuData
|
|
7
|
+
*/
|
|
8
|
+
function getFlatMenus(menuData = []) {
|
|
9
|
+
let menus = {};
|
|
10
|
+
menuData.forEach((mapItem) => {
|
|
11
|
+
const item = { ...mapItem };
|
|
12
|
+
if (!item || !item.key) return;
|
|
13
|
+
const routerChildren = item.children || [];
|
|
14
|
+
menus[stripQueryStringAndHashFromPath(item.path || item.key || "/") || ""] = { ...item };
|
|
15
|
+
menus[item.key || item.path || "/"] = { ...item };
|
|
16
|
+
if (routerChildren.length) menus = {
|
|
17
|
+
...menus,
|
|
18
|
+
...getFlatMenus(routerChildren)
|
|
19
|
+
};
|
|
20
|
+
});
|
|
21
|
+
return menus;
|
|
22
|
+
}
|
|
23
|
+
//#endregion
|
|
24
|
+
export { getFlatMenus };
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { MenuDataItem } from "../typing.js";
|
|
2
|
+
|
|
3
|
+
//#region src/getMatchMenu/index.d.ts
|
|
4
|
+
declare function getMenuMatches(flatMenuKeys: string[] | undefined, path: string, exact?: boolean): string[] | undefined;
|
|
5
|
+
/**
|
|
6
|
+
* 获取当前的选中菜单列表
|
|
7
|
+
* @param path
|
|
8
|
+
* @param menuData
|
|
9
|
+
* @param fullKeys
|
|
10
|
+
* @param exact
|
|
11
|
+
* @returns MenuDataItem[]
|
|
12
|
+
*/
|
|
13
|
+
declare function getMatchMenu(path: string, menuData: MenuDataItem[],
|
|
14
|
+
/**
|
|
15
|
+
* 要不要展示全部的 key
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
fullKeys?: boolean, exact?: boolean): MenuDataItem[];
|
|
19
|
+
//#endregion
|
|
20
|
+
export { getMatchMenu, getMenuMatches };
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { pathToRegexp } from "../path-to-regexp.js";
|
|
2
|
+
import { isUrl, stripQueryStringAndHashFromPath } from "../transformRoute/index.js";
|
|
3
|
+
import { getFlatMenus } from "../getFlatMenus/index.js";
|
|
4
|
+
//#region src/getMatchMenu/index.ts
|
|
5
|
+
function getMenuMatches(flatMenuKeys = [], path, exact) {
|
|
6
|
+
return flatMenuKeys.filter((item) => {
|
|
7
|
+
if (item === "/" && path === "/") return true;
|
|
8
|
+
if (item !== "/" && item !== "/*" && item && !isUrl(item)) {
|
|
9
|
+
const pathKey = stripQueryStringAndHashFromPath(item);
|
|
10
|
+
try {
|
|
11
|
+
if (exact) {
|
|
12
|
+
if (pathToRegexp(`${pathKey}`).test(path)) return true;
|
|
13
|
+
}
|
|
14
|
+
if (pathToRegexp(`${pathKey}`, []).test(path)) return true;
|
|
15
|
+
if (pathToRegexp(`${pathKey}/(.*)`).test(path)) return true;
|
|
16
|
+
} catch (error) {
|
|
17
|
+
console.log(error, path);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return false;
|
|
21
|
+
}).sort((a, b) => {
|
|
22
|
+
if (a === path) return 10;
|
|
23
|
+
if (b === path) return -10;
|
|
24
|
+
return a.substring(1).split("/").length - b.substring(1).split("/").length;
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* 获取当前的选中菜单列表
|
|
29
|
+
* @param path
|
|
30
|
+
* @param menuData
|
|
31
|
+
* @param fullKeys
|
|
32
|
+
* @param exact
|
|
33
|
+
* @returns MenuDataItem[]
|
|
34
|
+
*/
|
|
35
|
+
function getMatchMenu(path, menuData, fullKeys, exact) {
|
|
36
|
+
const flatMenus = getFlatMenus(menuData);
|
|
37
|
+
let menuPathKeys = getMenuMatches(Object.keys(flatMenus), path || "/", exact);
|
|
38
|
+
if (!menuPathKeys || menuPathKeys.length < 1) return [];
|
|
39
|
+
if (!fullKeys) menuPathKeys = [menuPathKeys[menuPathKeys.length - 1]];
|
|
40
|
+
return (menuPathKeys || []).map((menuPathKey) => {
|
|
41
|
+
const menuItem = flatMenus[menuPathKey] || {
|
|
42
|
+
meta: { pro_layout_parentKeys: [] },
|
|
43
|
+
key: ""
|
|
44
|
+
};
|
|
45
|
+
const map = /* @__PURE__ */ new Map();
|
|
46
|
+
const parentItems = (menuItem.meta?.pro_layout_parentKeys || []).map((key) => {
|
|
47
|
+
if (map.has(key)) return null;
|
|
48
|
+
map.set(key, true);
|
|
49
|
+
return flatMenus[key];
|
|
50
|
+
}).filter((item) => item);
|
|
51
|
+
if (menuItem.key) parentItems.push(menuItem);
|
|
52
|
+
return parentItems;
|
|
53
|
+
}).flat(1);
|
|
54
|
+
}
|
|
55
|
+
//#endregion
|
|
56
|
+
export { getMatchMenu, getMenuMatches };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { MenuDataItem, MessageDescriptor, MetaRecord } from "./typing.js";
|
|
2
|
+
import { getFlatMenus } from "./getFlatMenus/index.js";
|
|
3
|
+
import { getMatchMenu, getMenuMatches } from "./getMatchMenu/index.js";
|
|
4
|
+
import { getKeyByPath, isUrl, stripQueryStringAndHashFromPath, transformRoute } from "./transformRoute/index.js";
|
|
5
|
+
import { Key, Match, MatchFunction, MatchResult, ParseOptions, Path, PathFunction, RegexpToFunctionOptions, Token, TokensToFunctionOptions, TokensToRegexpOptions, compile, match, parse, pathToRegexp, regexpToFunction, tokensToFunction, tokensToRegexp } from "./path-to-regexp.js";
|
|
6
|
+
import { sha256_encode_bytes } from "./sha265.js";
|
|
7
|
+
export { Key, Match, MatchFunction, MatchResult, MenuDataItem, MessageDescriptor, MetaRecord, ParseOptions, Path, PathFunction, RegexpToFunctionOptions, Token, TokensToFunctionOptions, TokensToRegexpOptions, compile, getFlatMenus, getKeyByPath, getMatchMenu, getMenuMatches, isUrl, match, parse, pathToRegexp, regexpToFunction, sha256_encode_bytes, stripQueryStringAndHashFromPath, tokensToFunction, tokensToRegexp, transformRoute };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { compile, match, parse, pathToRegexp, regexpToFunction, tokensToFunction, tokensToRegexp } from "./path-to-regexp.js";
|
|
2
|
+
import { sha256_encode_bytes } from "./sha265.js";
|
|
3
|
+
import { getKeyByPath, isUrl, stripQueryStringAndHashFromPath, transformRoute } from "./transformRoute/index.js";
|
|
4
|
+
import { getFlatMenus } from "./getFlatMenus/index.js";
|
|
5
|
+
import { getMatchMenu, getMenuMatches } from "./getMatchMenu/index.js";
|
|
6
|
+
export { compile, getFlatMenus, getKeyByPath, getMatchMenu, getMenuMatches, isUrl, match, parse, pathToRegexp, regexpToFunction, sha256_encode_bytes, stripQueryStringAndHashFromPath, tokensToFunction, tokensToRegexp, transformRoute };
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
//#region src/path-to-regexp.d.ts
|
|
2
|
+
interface ParseOptions {
|
|
3
|
+
/**
|
|
4
|
+
* Set the default delimiter for repeat parameters. (default: `'/'`)
|
|
5
|
+
*/
|
|
6
|
+
delimiter?: string;
|
|
7
|
+
/**
|
|
8
|
+
* List of characters to automatically consider prefixes when parsing.
|
|
9
|
+
*/
|
|
10
|
+
prefixes?: string;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Parse a string for the raw tokens.
|
|
14
|
+
*/
|
|
15
|
+
declare function parse(str: string, options?: ParseOptions): Token[];
|
|
16
|
+
interface TokensToFunctionOptions {
|
|
17
|
+
/**
|
|
18
|
+
* When `true` the regexp will be case-sensitive. (default: `false`)
|
|
19
|
+
*/
|
|
20
|
+
sensitive?: boolean;
|
|
21
|
+
/**
|
|
22
|
+
* Function for encoding input strings for output.
|
|
23
|
+
*/
|
|
24
|
+
encode?: (value: string, token: Key) => string;
|
|
25
|
+
/**
|
|
26
|
+
* When `false` the function can produce an invalid (unmatched) path. (default: `true`)
|
|
27
|
+
*/
|
|
28
|
+
validate?: boolean;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Compile a string to a template function for the path.
|
|
32
|
+
*/
|
|
33
|
+
declare function compile<P extends object = object>(str: string, options?: ParseOptions & TokensToFunctionOptions): PathFunction<P>;
|
|
34
|
+
type PathFunction<P extends object = object> = (data?: P) => string;
|
|
35
|
+
/**
|
|
36
|
+
* Expose a method for transforming tokens into the path function.
|
|
37
|
+
*/
|
|
38
|
+
declare function tokensToFunction<P extends object = object>(tokens: Token[], options?: TokensToFunctionOptions): PathFunction<P>;
|
|
39
|
+
interface RegexpToFunctionOptions {
|
|
40
|
+
/**
|
|
41
|
+
* Function for decoding strings for params.
|
|
42
|
+
*/
|
|
43
|
+
decode?: (value: string, token: Key) => string;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* A match result contains data about the path match.
|
|
47
|
+
*/
|
|
48
|
+
interface MatchResult<P extends object = object> {
|
|
49
|
+
path: string;
|
|
50
|
+
index: number;
|
|
51
|
+
params: P;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* A match is either `false` (no match) or a match result.
|
|
55
|
+
*/
|
|
56
|
+
type Match<P extends object = object> = false | MatchResult<P>;
|
|
57
|
+
/**
|
|
58
|
+
* The match function takes a string and returns whether it matched the path.
|
|
59
|
+
*/
|
|
60
|
+
type MatchFunction<P extends object = object> = (path: string) => Match<P>;
|
|
61
|
+
/**
|
|
62
|
+
* Create path match function from `path-to-regexp` spec.
|
|
63
|
+
*/
|
|
64
|
+
declare function match<P extends object = object>(str: Path, options?: ParseOptions & TokensToRegexpOptions & RegexpToFunctionOptions): MatchFunction<P>;
|
|
65
|
+
/**
|
|
66
|
+
* Create a path match function from `path-to-regexp` output.
|
|
67
|
+
*/
|
|
68
|
+
declare function regexpToFunction<P extends object = object>(re: RegExp, keys: Key[], options?: RegexpToFunctionOptions): MatchFunction<P>;
|
|
69
|
+
/**
|
|
70
|
+
* Metadata about a key.
|
|
71
|
+
*/
|
|
72
|
+
interface Key {
|
|
73
|
+
name: string | number;
|
|
74
|
+
prefix: string;
|
|
75
|
+
suffix: string;
|
|
76
|
+
pattern: string;
|
|
77
|
+
modifier: string;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* A token is a string (nothing special) or key metadata (capture group).
|
|
81
|
+
*/
|
|
82
|
+
type Token = string | Key;
|
|
83
|
+
interface TokensToRegexpOptions {
|
|
84
|
+
/**
|
|
85
|
+
* When `true` the regexp will be case-sensitive. (default: `false`)
|
|
86
|
+
*/
|
|
87
|
+
sensitive?: boolean;
|
|
88
|
+
/**
|
|
89
|
+
* When `true` the regexp won't allow an optional trailing delimiter to match. (default: `false`)
|
|
90
|
+
*/
|
|
91
|
+
strict?: boolean;
|
|
92
|
+
/**
|
|
93
|
+
* When `true` the regexp will match to the end of the string. (default: `true`)
|
|
94
|
+
*/
|
|
95
|
+
end?: boolean;
|
|
96
|
+
/**
|
|
97
|
+
* When `true` the regexp will match from the beginning of the string. (default: `true`)
|
|
98
|
+
*/
|
|
99
|
+
start?: boolean;
|
|
100
|
+
/**
|
|
101
|
+
* Sets the final character for non-ending optimistic matches. (default: `/`)
|
|
102
|
+
*/
|
|
103
|
+
delimiter?: string;
|
|
104
|
+
/**
|
|
105
|
+
* List of characters that can also be "end" characters.
|
|
106
|
+
*/
|
|
107
|
+
endsWith?: string;
|
|
108
|
+
/**
|
|
109
|
+
* Encode path tokens for use in the `RegExp`.
|
|
110
|
+
*/
|
|
111
|
+
encode?: (value: string) => string;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Expose a function for taking tokens and returning a RegExp.
|
|
115
|
+
*/
|
|
116
|
+
declare function tokensToRegexp(tokens: Token[], keys?: Key[], options?: TokensToRegexpOptions): RegExp;
|
|
117
|
+
/**
|
|
118
|
+
* Supported `path-to-regexp` input types.
|
|
119
|
+
*/
|
|
120
|
+
type Path = string | RegExp | Array<string | RegExp>;
|
|
121
|
+
/**
|
|
122
|
+
* Normalize the given path string, returning a regular expression.
|
|
123
|
+
*
|
|
124
|
+
* An empty array can be passed in for the keys, which will hold the
|
|
125
|
+
* placeholder key descriptions. For example, using `/user/:id`, `keys` will
|
|
126
|
+
* contain `[{ name: 'id', delimiter: '/', optional: false, repeat: false }]`.
|
|
127
|
+
*/
|
|
128
|
+
declare function pathToRegexp(path: Path, keys?: Key[], options?: TokensToRegexpOptions & ParseOptions): RegExp;
|
|
129
|
+
//#endregion
|
|
130
|
+
export { Key, Match, MatchFunction, MatchResult, ParseOptions, Path, PathFunction, RegexpToFunctionOptions, Token, TokensToFunctionOptions, TokensToRegexpOptions, compile, match, parse, pathToRegexp, regexpToFunction, tokensToFunction, tokensToRegexp };
|
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
//#region src/path-to-regexp.ts
|
|
2
|
+
/**
|
|
3
|
+
* Tokenize input string.
|
|
4
|
+
*/
|
|
5
|
+
function lexer(str) {
|
|
6
|
+
const tokens = [];
|
|
7
|
+
let i = 0;
|
|
8
|
+
while (i < str.length) {
|
|
9
|
+
const char = str[i];
|
|
10
|
+
if (char === "*" || char === "+" || char === "?") {
|
|
11
|
+
tokens.push({
|
|
12
|
+
type: "MODIFIER",
|
|
13
|
+
index: i,
|
|
14
|
+
value: str[i++] || ""
|
|
15
|
+
});
|
|
16
|
+
continue;
|
|
17
|
+
}
|
|
18
|
+
if (char === "\\") {
|
|
19
|
+
tokens.push({
|
|
20
|
+
type: "ESCAPED_CHAR",
|
|
21
|
+
index: i++,
|
|
22
|
+
value: str[i++] || ""
|
|
23
|
+
});
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
if (char === "{") {
|
|
27
|
+
tokens.push({
|
|
28
|
+
type: "OPEN",
|
|
29
|
+
index: i,
|
|
30
|
+
value: str[i++] || ""
|
|
31
|
+
});
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
if (char === "}") {
|
|
35
|
+
tokens.push({
|
|
36
|
+
type: "CLOSE",
|
|
37
|
+
index: i,
|
|
38
|
+
value: str[i++] || ""
|
|
39
|
+
});
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
if (char === ":") {
|
|
43
|
+
let name = "";
|
|
44
|
+
let j = i + 1;
|
|
45
|
+
while (j < str.length) {
|
|
46
|
+
const code = str.charCodeAt(j);
|
|
47
|
+
if (code >= 48 && code <= 57 || code >= 65 && code <= 90 || code >= 97 && code <= 122 || code === 95) {
|
|
48
|
+
name += str[j++];
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
break;
|
|
52
|
+
}
|
|
53
|
+
if (!name) throw new TypeError(`Missing parameter name at ${i}`);
|
|
54
|
+
tokens.push({
|
|
55
|
+
type: "NAME",
|
|
56
|
+
index: i,
|
|
57
|
+
value: name
|
|
58
|
+
});
|
|
59
|
+
i = j;
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
if (char === "(") {
|
|
63
|
+
let count = 1;
|
|
64
|
+
let pattern = "";
|
|
65
|
+
let j = i + 1;
|
|
66
|
+
if (str[j] === "?") throw new TypeError(`Pattern cannot start with "?" at ${j}`);
|
|
67
|
+
while (j < str.length) {
|
|
68
|
+
if (str[j] === "\\") {
|
|
69
|
+
pattern += (str[j++] || "") + str[j++];
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
if (str[j] === ")") {
|
|
73
|
+
count--;
|
|
74
|
+
if (count === 0) {
|
|
75
|
+
j++;
|
|
76
|
+
break;
|
|
77
|
+
}
|
|
78
|
+
} else if (str[j] === "(") {
|
|
79
|
+
count++;
|
|
80
|
+
if (str[j + 1] !== "?") throw new TypeError(`Capturing groups are not allowed at ${j}`);
|
|
81
|
+
}
|
|
82
|
+
pattern += str[j++];
|
|
83
|
+
}
|
|
84
|
+
if (count) throw new TypeError(`Unbalanced pattern at ${i}`);
|
|
85
|
+
if (!pattern) throw new TypeError(`Missing pattern at ${i}`);
|
|
86
|
+
tokens.push({
|
|
87
|
+
type: "PATTERN",
|
|
88
|
+
index: i,
|
|
89
|
+
value: pattern
|
|
90
|
+
});
|
|
91
|
+
i = j;
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
tokens.push({
|
|
95
|
+
type: "CHAR",
|
|
96
|
+
index: i,
|
|
97
|
+
value: str[i++] || ""
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
tokens.push({
|
|
101
|
+
type: "END",
|
|
102
|
+
index: i,
|
|
103
|
+
value: ""
|
|
104
|
+
});
|
|
105
|
+
return tokens;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Parse a string for the raw tokens.
|
|
109
|
+
*/
|
|
110
|
+
function parse(str, options = {}) {
|
|
111
|
+
const tokens = lexer(str);
|
|
112
|
+
const { prefixes = "./", delimiter = "/#?" } = options;
|
|
113
|
+
const result = [];
|
|
114
|
+
let key = 0;
|
|
115
|
+
let i = 0;
|
|
116
|
+
let path = "";
|
|
117
|
+
const tryConsume = (type) => {
|
|
118
|
+
if (i < tokens.length && tokens[i]?.type === type) return tokens[i++]?.value;
|
|
119
|
+
};
|
|
120
|
+
const mustConsume = (type) => {
|
|
121
|
+
const value = tryConsume(type);
|
|
122
|
+
if (value !== void 0) return value;
|
|
123
|
+
const { type: nextType, index } = tokens[i] || {};
|
|
124
|
+
throw new TypeError(`Unexpected ${nextType} at ${index}, expected ${type}`);
|
|
125
|
+
};
|
|
126
|
+
const consumeText = () => {
|
|
127
|
+
let result = "";
|
|
128
|
+
let value;
|
|
129
|
+
while (value = tryConsume("CHAR") || tryConsume("ESCAPED_CHAR")) result += value;
|
|
130
|
+
return result;
|
|
131
|
+
};
|
|
132
|
+
const isSafe = (value) => {
|
|
133
|
+
for (const char of delimiter) if (value.includes(char)) return true;
|
|
134
|
+
return false;
|
|
135
|
+
};
|
|
136
|
+
const safePattern = (prefix) => {
|
|
137
|
+
const prev = result[result.length - 1];
|
|
138
|
+
const prevText = prefix || (prev && typeof prev === "string" ? prev : "");
|
|
139
|
+
if (prev && !prevText) throw new TypeError(`Must have text between two parameters, missing text after "${prev.name}"`);
|
|
140
|
+
if (!prevText || isSafe(prevText)) return `[^${escapeString(delimiter)}]+?`;
|
|
141
|
+
return `(?:(?!${escapeString(prevText)})[^${escapeString(delimiter)}])+?`;
|
|
142
|
+
};
|
|
143
|
+
while (i < tokens.length) {
|
|
144
|
+
const char = tryConsume("CHAR");
|
|
145
|
+
const name = tryConsume("NAME");
|
|
146
|
+
const pattern = tryConsume("PATTERN");
|
|
147
|
+
if (name || pattern) {
|
|
148
|
+
let prefix = char || "";
|
|
149
|
+
if (!prefixes.includes(prefix)) {
|
|
150
|
+
path += prefix;
|
|
151
|
+
prefix = "";
|
|
152
|
+
}
|
|
153
|
+
if (path) {
|
|
154
|
+
result.push(path);
|
|
155
|
+
path = "";
|
|
156
|
+
}
|
|
157
|
+
result.push({
|
|
158
|
+
name: name || key++,
|
|
159
|
+
prefix,
|
|
160
|
+
suffix: "",
|
|
161
|
+
pattern: pattern || safePattern(prefix),
|
|
162
|
+
modifier: tryConsume("MODIFIER") || ""
|
|
163
|
+
});
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
const value = char || tryConsume("ESCAPED_CHAR");
|
|
167
|
+
if (value) {
|
|
168
|
+
path += value;
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
if (path) {
|
|
172
|
+
result.push(path);
|
|
173
|
+
path = "";
|
|
174
|
+
}
|
|
175
|
+
if (tryConsume("OPEN")) {
|
|
176
|
+
const prefix = consumeText();
|
|
177
|
+
const name = tryConsume("NAME") || "";
|
|
178
|
+
const pattern = tryConsume("PATTERN") || "";
|
|
179
|
+
const suffix = consumeText();
|
|
180
|
+
mustConsume("CLOSE");
|
|
181
|
+
result.push({
|
|
182
|
+
name: name || (pattern ? key++ : ""),
|
|
183
|
+
pattern: name && !pattern ? safePattern(prefix) : pattern,
|
|
184
|
+
prefix,
|
|
185
|
+
suffix,
|
|
186
|
+
modifier: tryConsume("MODIFIER") || ""
|
|
187
|
+
});
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
mustConsume("END");
|
|
191
|
+
}
|
|
192
|
+
return result;
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Compile a string to a template function for the path.
|
|
196
|
+
*/
|
|
197
|
+
function compile(str, options) {
|
|
198
|
+
return tokensToFunction(parse(str, options), options);
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Expose a method for transforming tokens into the path function.
|
|
202
|
+
*/
|
|
203
|
+
function tokensToFunction(tokens, options = {}) {
|
|
204
|
+
const reFlags = flags(options);
|
|
205
|
+
const { encode = (x) => x, validate = true } = options;
|
|
206
|
+
const matches = tokens.map((token) => {
|
|
207
|
+
if (typeof token === "object") return new RegExp(`^(?:${token.pattern})$`, reFlags);
|
|
208
|
+
});
|
|
209
|
+
return (data) => {
|
|
210
|
+
let path = "";
|
|
211
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
212
|
+
const token = tokens[i];
|
|
213
|
+
if (typeof token === "string") {
|
|
214
|
+
path += token;
|
|
215
|
+
continue;
|
|
216
|
+
}
|
|
217
|
+
const value = data ? data[token?.name || ""] : void 0;
|
|
218
|
+
const optional = token?.modifier === "?" || token?.modifier === "*";
|
|
219
|
+
const repeat = token?.modifier === "*" || token?.modifier === "+";
|
|
220
|
+
if (Array.isArray(value)) {
|
|
221
|
+
if (!repeat) throw new TypeError(`Expected "${token?.name}" to not repeat, but got an array`);
|
|
222
|
+
if (value.length === 0) {
|
|
223
|
+
if (optional) continue;
|
|
224
|
+
throw new TypeError(`Expected "${token.name}" to not be empty`);
|
|
225
|
+
}
|
|
226
|
+
for (let j = 0; j < value.length; j++) {
|
|
227
|
+
const segment = encode(value[j], token);
|
|
228
|
+
if (validate && !matches[i].test(segment)) throw new TypeError(`Expected all "${token.name}" to match "${token.pattern}", but got "${segment}"`);
|
|
229
|
+
path += token.prefix + segment + token.suffix;
|
|
230
|
+
}
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
233
|
+
if (typeof value === "string" || typeof value === "number") {
|
|
234
|
+
const segment = encode(String(value), token);
|
|
235
|
+
if (validate && !matches[i].test(segment)) throw new TypeError(`Expected "${token?.name}" to match "${token?.pattern}", but got "${segment}"`);
|
|
236
|
+
path += token?.prefix + segment + token?.suffix;
|
|
237
|
+
continue;
|
|
238
|
+
}
|
|
239
|
+
if (optional) continue;
|
|
240
|
+
const typeOfMessage = repeat ? "an array" : "a string";
|
|
241
|
+
throw new TypeError(`Expected "${token?.name}" to be ${typeOfMessage}`);
|
|
242
|
+
}
|
|
243
|
+
return path;
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Create path match function from `path-to-regexp` spec.
|
|
248
|
+
*/
|
|
249
|
+
function match(str, options) {
|
|
250
|
+
const keys = [];
|
|
251
|
+
return regexpToFunction(pathToRegexp(str, keys, options), keys, options);
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Create a path match function from `path-to-regexp` output.
|
|
255
|
+
*/
|
|
256
|
+
function regexpToFunction(re, keys, options = {}) {
|
|
257
|
+
const { decode = (x) => x } = options;
|
|
258
|
+
return function(pathname) {
|
|
259
|
+
const m = re.exec(pathname);
|
|
260
|
+
if (!m) return false;
|
|
261
|
+
const { 0: path, index } = m;
|
|
262
|
+
const params = Object.create(null);
|
|
263
|
+
for (let i = 1; i < m.length; i++) {
|
|
264
|
+
if (m[i] === void 0) continue;
|
|
265
|
+
const key = keys[i - 1];
|
|
266
|
+
if (key.modifier === "*" || key.modifier === "+") params[key.name] = m[i]?.split(key.prefix + key.suffix).map((value) => {
|
|
267
|
+
return decode(value, key);
|
|
268
|
+
});
|
|
269
|
+
else params[key.name] = decode(m[i], key);
|
|
270
|
+
}
|
|
271
|
+
return {
|
|
272
|
+
path,
|
|
273
|
+
index,
|
|
274
|
+
params
|
|
275
|
+
};
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* Escape a regular expression string.
|
|
280
|
+
*/
|
|
281
|
+
function escapeString(str) {
|
|
282
|
+
return str.replace(/([.+*?=^!:${}()[\]|/\\])/g, "\\$1");
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* Get the flags for a regexp from the options.
|
|
286
|
+
*/
|
|
287
|
+
function flags(options) {
|
|
288
|
+
return options && options.sensitive ? "" : "i";
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* Pull out keys from a regexp.
|
|
292
|
+
*/
|
|
293
|
+
function regexpToRegexp(path, keys) {
|
|
294
|
+
if (!keys) return path;
|
|
295
|
+
const groupsRegex = /\((?:\?<(.*?)>)?(?!\?)/g;
|
|
296
|
+
let index = 0;
|
|
297
|
+
let execResult = groupsRegex.exec(path.source);
|
|
298
|
+
while (execResult) {
|
|
299
|
+
keys.push({
|
|
300
|
+
name: execResult[1] || index++,
|
|
301
|
+
prefix: "",
|
|
302
|
+
suffix: "",
|
|
303
|
+
modifier: "",
|
|
304
|
+
pattern: ""
|
|
305
|
+
});
|
|
306
|
+
execResult = groupsRegex.exec(path.source);
|
|
307
|
+
}
|
|
308
|
+
return path;
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
311
|
+
* Transform an array into a regexp.
|
|
312
|
+
*/
|
|
313
|
+
function arrayToRegexp(paths, keys, options) {
|
|
314
|
+
const parts = paths.map((path) => pathToRegexp(path, keys, options).source);
|
|
315
|
+
return new RegExp(`(?:${parts.join("|")})`, flags(options));
|
|
316
|
+
}
|
|
317
|
+
/**
|
|
318
|
+
* Create a path regexp from string input.
|
|
319
|
+
*/
|
|
320
|
+
function stringToRegexp(path, keys, options) {
|
|
321
|
+
return tokensToRegexp(parse(path, options), keys, options);
|
|
322
|
+
}
|
|
323
|
+
/**
|
|
324
|
+
* Expose a function for taking tokens and returning a RegExp.
|
|
325
|
+
*/
|
|
326
|
+
function tokensToRegexp(tokens, keys, options = {}) {
|
|
327
|
+
const { strict = false, start = true, end = true, encode = (x) => x, delimiter = "/#?", endsWith = "" } = options;
|
|
328
|
+
const endsWithRe = `[${escapeString(endsWith)}]|$`;
|
|
329
|
+
const delimiterRe = `[${escapeString(delimiter)}]`;
|
|
330
|
+
let route = start ? "^" : "";
|
|
331
|
+
for (const token of tokens) if (typeof token === "string") route += escapeString(encode(token));
|
|
332
|
+
else {
|
|
333
|
+
const prefix = escapeString(encode(token.prefix));
|
|
334
|
+
const suffix = escapeString(encode(token.suffix));
|
|
335
|
+
if (token.pattern) {
|
|
336
|
+
if (keys) keys.push(token);
|
|
337
|
+
if (prefix || suffix) if (token.modifier === "+" || token.modifier === "*") {
|
|
338
|
+
const mod = token.modifier === "*" ? "?" : "";
|
|
339
|
+
route += `(?:${prefix}((?:${token.pattern})(?:${suffix}${prefix}(?:${token.pattern}))*)${suffix})${mod}`;
|
|
340
|
+
} else route += `(?:${prefix}(${token.pattern})${suffix})${token.modifier}`;
|
|
341
|
+
else {
|
|
342
|
+
if (token.modifier === "+" || token.modifier === "*") throw new TypeError(`Can not repeat "${token.name}" without a prefix and suffix`);
|
|
343
|
+
route += `(${token.pattern})${token.modifier}`;
|
|
344
|
+
}
|
|
345
|
+
} else route += `(?:${prefix}${suffix})${token.modifier}`;
|
|
346
|
+
}
|
|
347
|
+
if (end) {
|
|
348
|
+
if (!strict) route += `${delimiterRe}?`;
|
|
349
|
+
route += !options.endsWith ? "$" : `(?=${endsWithRe})`;
|
|
350
|
+
} else {
|
|
351
|
+
const endToken = tokens[tokens.length - 1];
|
|
352
|
+
const isEndDelimited = typeof endToken === "string" ? delimiterRe.includes(endToken[endToken.length - 1]) : endToken === void 0;
|
|
353
|
+
if (!strict) route += `(?:${delimiterRe}(?=${endsWithRe}))?`;
|
|
354
|
+
if (!isEndDelimited) route += `(?=${delimiterRe}|${endsWithRe})`;
|
|
355
|
+
}
|
|
356
|
+
return new RegExp(route, flags(options));
|
|
357
|
+
}
|
|
358
|
+
/**
|
|
359
|
+
* Normalize the given path string, returning a regular expression.
|
|
360
|
+
*
|
|
361
|
+
* An empty array can be passed in for the keys, which will hold the
|
|
362
|
+
* placeholder key descriptions. For example, using `/user/:id`, `keys` will
|
|
363
|
+
* contain `[{ name: 'id', delimiter: '/', optional: false, repeat: false }]`.
|
|
364
|
+
*/
|
|
365
|
+
function pathToRegexp(path, keys, options) {
|
|
366
|
+
if (path instanceof RegExp) return regexpToRegexp(path, keys);
|
|
367
|
+
if (Array.isArray(path)) return arrayToRegexp(path, keys, options);
|
|
368
|
+
return stringToRegexp(path, keys, options);
|
|
369
|
+
}
|
|
370
|
+
//#endregion
|
|
371
|
+
export { compile, match, parse, pathToRegexp, regexpToFunction, tokensToFunction, tokensToRegexp };
|
package/dist/sha265.d.ts
ADDED
package/dist/sha265.js
ADDED
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
//#region src/sha265.ts
|
|
2
|
+
function rotateRight(n, x) {
|
|
3
|
+
return x >>> n | x << 32 - n;
|
|
4
|
+
}
|
|
5
|
+
function choice(x, y, z) {
|
|
6
|
+
return x & y ^ ~x & z;
|
|
7
|
+
}
|
|
8
|
+
function majority(x, y, z) {
|
|
9
|
+
return x & y ^ x & z ^ y & z;
|
|
10
|
+
}
|
|
11
|
+
function sha256_Sigma0(x) {
|
|
12
|
+
return rotateRight(2, x) ^ rotateRight(13, x) ^ rotateRight(22, x);
|
|
13
|
+
}
|
|
14
|
+
function sha256_Sigma1(x) {
|
|
15
|
+
return rotateRight(6, x) ^ rotateRight(11, x) ^ rotateRight(25, x);
|
|
16
|
+
}
|
|
17
|
+
function sha256_sigma0(x) {
|
|
18
|
+
return rotateRight(7, x) ^ rotateRight(18, x) ^ x >>> 3;
|
|
19
|
+
}
|
|
20
|
+
function sha256_sigma1(x) {
|
|
21
|
+
return rotateRight(17, x) ^ rotateRight(19, x) ^ x >>> 10;
|
|
22
|
+
}
|
|
23
|
+
function sha256_expand(W, j) {
|
|
24
|
+
return W[j & 15] += sha256_sigma1(W[j + 14 & 15]) + W[j + 9 & 15] + sha256_sigma0(W[j + 1 & 15]);
|
|
25
|
+
}
|
|
26
|
+
const K256 = [
|
|
27
|
+
1116352408,
|
|
28
|
+
1899447441,
|
|
29
|
+
3049323471,
|
|
30
|
+
3921009573,
|
|
31
|
+
961987163,
|
|
32
|
+
1508970993,
|
|
33
|
+
2453635748,
|
|
34
|
+
2870763221,
|
|
35
|
+
3624381080,
|
|
36
|
+
310598401,
|
|
37
|
+
607225278,
|
|
38
|
+
1426881987,
|
|
39
|
+
1925078388,
|
|
40
|
+
2162078206,
|
|
41
|
+
2614888103,
|
|
42
|
+
3248222580,
|
|
43
|
+
3835390401,
|
|
44
|
+
4022224774,
|
|
45
|
+
264347078,
|
|
46
|
+
604807628,
|
|
47
|
+
770255983,
|
|
48
|
+
1249150122,
|
|
49
|
+
1555081692,
|
|
50
|
+
1996064986,
|
|
51
|
+
2554220882,
|
|
52
|
+
2821834349,
|
|
53
|
+
2952996808,
|
|
54
|
+
3210313671,
|
|
55
|
+
3336571891,
|
|
56
|
+
3584528711,
|
|
57
|
+
113926993,
|
|
58
|
+
338241895,
|
|
59
|
+
666307205,
|
|
60
|
+
773529912,
|
|
61
|
+
1294757372,
|
|
62
|
+
1396182291,
|
|
63
|
+
1695183700,
|
|
64
|
+
1986661051,
|
|
65
|
+
2177026350,
|
|
66
|
+
2456956037,
|
|
67
|
+
2730485921,
|
|
68
|
+
2820302411,
|
|
69
|
+
3259730800,
|
|
70
|
+
3345764771,
|
|
71
|
+
3516065817,
|
|
72
|
+
3600352804,
|
|
73
|
+
4094571909,
|
|
74
|
+
275423344,
|
|
75
|
+
430227734,
|
|
76
|
+
506948616,
|
|
77
|
+
659060556,
|
|
78
|
+
883997877,
|
|
79
|
+
958139571,
|
|
80
|
+
1322822218,
|
|
81
|
+
1537002063,
|
|
82
|
+
1747873779,
|
|
83
|
+
1955562222,
|
|
84
|
+
2024104815,
|
|
85
|
+
2227730452,
|
|
86
|
+
2361852424,
|
|
87
|
+
2428436474,
|
|
88
|
+
2756734187,
|
|
89
|
+
3204031479,
|
|
90
|
+
3329325298
|
|
91
|
+
];
|
|
92
|
+
/**
|
|
93
|
+
* global arrays
|
|
94
|
+
*/
|
|
95
|
+
let ihash;
|
|
96
|
+
let count;
|
|
97
|
+
let buffer;
|
|
98
|
+
const sha256_hex_digits = "0123456789abcdef";
|
|
99
|
+
function safe_add(x, y) {
|
|
100
|
+
const lsw = (x & 65535) + (y & 65535);
|
|
101
|
+
return (x >> 16) + (y >> 16) + (lsw >> 16) << 16 | lsw & 65535;
|
|
102
|
+
}
|
|
103
|
+
function sha256_init() {
|
|
104
|
+
ihash = Array.from({ length: 8 });
|
|
105
|
+
count = Array.from({ length: 2 });
|
|
106
|
+
buffer = Array.from({ length: 64 });
|
|
107
|
+
count[0] = count[1] = 0;
|
|
108
|
+
ihash[0] = 1779033703;
|
|
109
|
+
ihash[1] = 3144134277;
|
|
110
|
+
ihash[2] = 1013904242;
|
|
111
|
+
ihash[3] = 2773480762;
|
|
112
|
+
ihash[4] = 1359893119;
|
|
113
|
+
ihash[5] = 2600822924;
|
|
114
|
+
ihash[6] = 528734635;
|
|
115
|
+
ihash[7] = 1541459225;
|
|
116
|
+
}
|
|
117
|
+
function sha256_transform() {
|
|
118
|
+
let a;
|
|
119
|
+
let b;
|
|
120
|
+
let c;
|
|
121
|
+
let d;
|
|
122
|
+
let e;
|
|
123
|
+
let f;
|
|
124
|
+
let g;
|
|
125
|
+
let h;
|
|
126
|
+
let T1;
|
|
127
|
+
let T2;
|
|
128
|
+
const W = Array.from({ length: 16 });
|
|
129
|
+
a = ihash[0];
|
|
130
|
+
b = ihash[1];
|
|
131
|
+
c = ihash[2];
|
|
132
|
+
d = ihash[3];
|
|
133
|
+
e = ihash[4];
|
|
134
|
+
f = ihash[5];
|
|
135
|
+
g = ihash[6];
|
|
136
|
+
h = ihash[7];
|
|
137
|
+
for (let i = 0; i < 16; i++) W[i] = buffer[(i << 2) + 3] | buffer[(i << 2) + 2] << 8 | buffer[(i << 2) + 1] << 16 | buffer[i << 2] << 24;
|
|
138
|
+
for (let j = 0; j < 64; j++) {
|
|
139
|
+
T1 = h + sha256_Sigma1(e) + choice(e, f, g) + K256[j];
|
|
140
|
+
if (j < 16) T1 += W[j];
|
|
141
|
+
else T1 += sha256_expand(W, j);
|
|
142
|
+
T2 = sha256_Sigma0(a) + majority(a, b, c);
|
|
143
|
+
h = g;
|
|
144
|
+
g = f;
|
|
145
|
+
f = e;
|
|
146
|
+
e = safe_add(d, T1);
|
|
147
|
+
d = c;
|
|
148
|
+
c = b;
|
|
149
|
+
b = a;
|
|
150
|
+
a = safe_add(T1, T2);
|
|
151
|
+
}
|
|
152
|
+
ihash[0] += a;
|
|
153
|
+
ihash[1] += b;
|
|
154
|
+
ihash[2] += c;
|
|
155
|
+
ihash[3] += d;
|
|
156
|
+
ihash[4] += e;
|
|
157
|
+
ihash[5] += f;
|
|
158
|
+
ihash[6] += g;
|
|
159
|
+
ihash[7] += h;
|
|
160
|
+
}
|
|
161
|
+
function sha256_update(data, inputLen) {
|
|
162
|
+
let i;
|
|
163
|
+
let index;
|
|
164
|
+
let curpos = 0;
|
|
165
|
+
index = count[0] >> 3 & 63;
|
|
166
|
+
const remainder = inputLen & 63;
|
|
167
|
+
if ((count[0] += inputLen << 3) < inputLen << 3) count[1]++;
|
|
168
|
+
count[1] += inputLen >> 29;
|
|
169
|
+
for (i = 0; i + 63 < inputLen; i += 64) {
|
|
170
|
+
for (let j = index; j < 64; j++) buffer[j] = data.charCodeAt(curpos++);
|
|
171
|
+
sha256_transform();
|
|
172
|
+
index = 0;
|
|
173
|
+
}
|
|
174
|
+
for (let j = 0; j < remainder; j++) buffer[j] = data.charCodeAt(curpos++);
|
|
175
|
+
}
|
|
176
|
+
function sha256_final() {
|
|
177
|
+
let index = count[0] >> 3 & 63;
|
|
178
|
+
buffer[index++] = 128;
|
|
179
|
+
if (index <= 56) for (let i = index; i < 56; i++) buffer[i] = 0;
|
|
180
|
+
else {
|
|
181
|
+
for (let i = index; i < 64; i++) buffer[i] = 0;
|
|
182
|
+
sha256_transform();
|
|
183
|
+
for (let i = 0; i < 56; i++) buffer[i] = 0;
|
|
184
|
+
}
|
|
185
|
+
buffer[56] = count[1] >>> 24 & 255;
|
|
186
|
+
buffer[57] = count[1] >>> 16 & 255;
|
|
187
|
+
buffer[58] = count[1] >>> 8 & 255;
|
|
188
|
+
buffer[59] = count[1] & 255;
|
|
189
|
+
buffer[60] = count[0] >>> 24 & 255;
|
|
190
|
+
buffer[61] = count[0] >>> 16 & 255;
|
|
191
|
+
buffer[62] = count[0] >>> 8 & 255;
|
|
192
|
+
buffer[63] = count[0] & 255;
|
|
193
|
+
sha256_transform();
|
|
194
|
+
}
|
|
195
|
+
function sha256_encode_bytes() {
|
|
196
|
+
let j = 0;
|
|
197
|
+
const output = Array.from({ length: 32 });
|
|
198
|
+
for (let i = 0; i < 8; i++) {
|
|
199
|
+
output[j++] = ihash[i] >>> 24 & 255;
|
|
200
|
+
output[j++] = ihash[i] >>> 16 & 255;
|
|
201
|
+
output[j++] = ihash[i] >>> 8 & 255;
|
|
202
|
+
output[j++] = ihash[i] & 255;
|
|
203
|
+
}
|
|
204
|
+
return output;
|
|
205
|
+
}
|
|
206
|
+
function sha256_encode_hex() {
|
|
207
|
+
let output = String();
|
|
208
|
+
for (let i = 0; i < 8; i++) for (let j = 28; j >= 0; j -= 4) output += sha256_hex_digits.charAt(ihash[i] >>> j & 15);
|
|
209
|
+
return output;
|
|
210
|
+
}
|
|
211
|
+
function digest(data) {
|
|
212
|
+
sha256_init();
|
|
213
|
+
sha256_update(data, data.length);
|
|
214
|
+
sha256_final();
|
|
215
|
+
return sha256_encode_hex();
|
|
216
|
+
}
|
|
217
|
+
//#endregion
|
|
218
|
+
export { digest as default, sha256_encode_bytes };
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { MenuDataItem, MessageDescriptor } from "../typing.js";
|
|
2
|
+
|
|
3
|
+
//#region src/transformRoute/index.d.ts
|
|
4
|
+
declare const stripQueryStringAndHashFromPath: (url: string) => string | undefined;
|
|
5
|
+
declare function isUrl(path: string): boolean;
|
|
6
|
+
declare function getKeyByPath(item: MenuDataItem): string | undefined;
|
|
7
|
+
/**
|
|
8
|
+
* @param routeList 路由配置
|
|
9
|
+
* @param locale 是否使用国际化
|
|
10
|
+
* @param formatMessage 国际化的程序
|
|
11
|
+
*/
|
|
12
|
+
declare function transformRoute(routeList: MenuDataItem[], locale?: boolean, formatMessage?: (message: MessageDescriptor) => string | undefined): {
|
|
13
|
+
breadcrumb: Map<string, MenuDataItem>;
|
|
14
|
+
menuData: MenuDataItem[];
|
|
15
|
+
};
|
|
16
|
+
//#endregion
|
|
17
|
+
export { getKeyByPath, isUrl, stripQueryStringAndHashFromPath, transformRoute };
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import { pathToRegexp } from "../path-to-regexp.js";
|
|
2
|
+
import digest from "../sha265.js";
|
|
3
|
+
//#region src/transformRoute/index.ts
|
|
4
|
+
const stripQueryStringAndHashFromPath = (url) => url.split("?")[0]?.split("#")[0];
|
|
5
|
+
function isUrl(path) {
|
|
6
|
+
if (!path.startsWith("http")) return false;
|
|
7
|
+
try {
|
|
8
|
+
return !!new URL(path);
|
|
9
|
+
} catch (error) {
|
|
10
|
+
console.log(error);
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
function getKeyByPath(item) {
|
|
15
|
+
const { path } = item;
|
|
16
|
+
if (!path || path === "/") try {
|
|
17
|
+
return `/${digest(JSON.stringify(item))}`;
|
|
18
|
+
} catch (error) {
|
|
19
|
+
console.log(error);
|
|
20
|
+
}
|
|
21
|
+
return path ? stripQueryStringAndHashFromPath(path) : path;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* 如果不是 / 开头的和父节点做一下合并
|
|
25
|
+
* 如果是 / 开头的不作任何处理
|
|
26
|
+
* 如果是 url 也直接返回
|
|
27
|
+
* @param path
|
|
28
|
+
* @param parentPath
|
|
29
|
+
*/
|
|
30
|
+
function mergePath(path = "", parentPath = "/") {
|
|
31
|
+
if ((path || parentPath).startsWith("/")) return path;
|
|
32
|
+
if (isUrl(path)) return path;
|
|
33
|
+
return `/${parentPath}/${path}`.replace(/\/\//g, "/").replace(/\/\//g, "/");
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* 获取locale,增加了一个功能,如果 locale = false,将不使用国际化
|
|
37
|
+
* @param item
|
|
38
|
+
* @param parentTitle
|
|
39
|
+
*/
|
|
40
|
+
function getItemLocaleName(item, parentTitle) {
|
|
41
|
+
const { meta } = item;
|
|
42
|
+
if ("locale" in meta && meta?.locale === false || !meta?.title) return false;
|
|
43
|
+
return item.meta?.locale || `${parentTitle}.${meta?.title.toString().toLowerCase()}`;
|
|
44
|
+
}
|
|
45
|
+
const notNullArray = (value) => Array.isArray(value) && value.length > 0;
|
|
46
|
+
/**
|
|
47
|
+
*
|
|
48
|
+
* @param props
|
|
49
|
+
* @param parent
|
|
50
|
+
*/
|
|
51
|
+
function formatter(props, parent = { path: "/" }) {
|
|
52
|
+
const { data, formatMessage, parentTitle, locale: menuLocale } = props;
|
|
53
|
+
if (!data || !Array.isArray(data)) return [];
|
|
54
|
+
return data.filter((item) => {
|
|
55
|
+
if (!item) return false;
|
|
56
|
+
if (notNullArray(item.children)) return true;
|
|
57
|
+
if (item.path) return true;
|
|
58
|
+
if (item.redirect) return false;
|
|
59
|
+
return false;
|
|
60
|
+
}).map((item = { path: "/" }) => {
|
|
61
|
+
const routerChildren = item.children || [];
|
|
62
|
+
const path = mergePath(item.path, parent ? parent.path : "/");
|
|
63
|
+
const { title } = item.meta;
|
|
64
|
+
const locale = getItemLocaleName(item, parentTitle || "menu");
|
|
65
|
+
const localeTitle = locale !== false && menuLocale !== false && formatMessage && locale ? formatMessage({
|
|
66
|
+
id: locale,
|
|
67
|
+
defaultMessage: title
|
|
68
|
+
}) : title;
|
|
69
|
+
const { children, meta, redirect, ...restParent } = parent;
|
|
70
|
+
let finallyItem = {
|
|
71
|
+
...restParent,
|
|
72
|
+
...item,
|
|
73
|
+
meta: {
|
|
74
|
+
...item.meta,
|
|
75
|
+
pro_layout_parentKeys: [],
|
|
76
|
+
locale
|
|
77
|
+
},
|
|
78
|
+
key: item.key || getKeyByPath({
|
|
79
|
+
...item,
|
|
80
|
+
path
|
|
81
|
+
}),
|
|
82
|
+
path
|
|
83
|
+
};
|
|
84
|
+
if (meta) {
|
|
85
|
+
const { flatMenu, pro_layout_parentKeys = [], icon, ...restParentMeta } = meta;
|
|
86
|
+
const item_pro_layout_parentKeys = new Set([...pro_layout_parentKeys, ...item.meta?.parentKeys || []]);
|
|
87
|
+
if (parent.key) item_pro_layout_parentKeys.add(parent.key);
|
|
88
|
+
finallyItem = {
|
|
89
|
+
...restParent,
|
|
90
|
+
...item,
|
|
91
|
+
meta: {
|
|
92
|
+
...restParentMeta,
|
|
93
|
+
...item.meta,
|
|
94
|
+
pro_layout_parentKeys: Array.from(item_pro_layout_parentKeys).filter((key) => key && key !== "/"),
|
|
95
|
+
locale
|
|
96
|
+
},
|
|
97
|
+
key: item.key || getKeyByPath({
|
|
98
|
+
...item,
|
|
99
|
+
path
|
|
100
|
+
}),
|
|
101
|
+
path
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
if (finallyItem.meta) if (localeTitle) finallyItem.meta.title = localeTitle;
|
|
105
|
+
else delete finallyItem.meta.title;
|
|
106
|
+
if (notNullArray(routerChildren)) {
|
|
107
|
+
const formatterChildren = formatter({
|
|
108
|
+
...props,
|
|
109
|
+
data: routerChildren,
|
|
110
|
+
parentTitle: locale || ""
|
|
111
|
+
}, finallyItem);
|
|
112
|
+
if (notNullArray(formatterChildren)) finallyItem.children = formatterChildren;
|
|
113
|
+
}
|
|
114
|
+
return bigfishConversions(finallyItem, props);
|
|
115
|
+
}).flat(1);
|
|
116
|
+
}
|
|
117
|
+
function bigfishConversions(route, props) {
|
|
118
|
+
const childrenList = route.children || [];
|
|
119
|
+
const result = { ...route };
|
|
120
|
+
if (childrenList && childrenList.length) {
|
|
121
|
+
/** 在菜单中隐藏子项 */
|
|
122
|
+
if (result.meta?.hideChildrenInMenu) {
|
|
123
|
+
delete result.children;
|
|
124
|
+
return result;
|
|
125
|
+
}
|
|
126
|
+
const finalChildren = formatter({
|
|
127
|
+
...props,
|
|
128
|
+
data: childrenList
|
|
129
|
+
}, route);
|
|
130
|
+
/** 在菜单中只隐藏此项,子项往上提,仍旧展示 */
|
|
131
|
+
if (result.meta?.flatMenu) return finalChildren;
|
|
132
|
+
}
|
|
133
|
+
return result;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* 删除 hideInMenu 和 title 不存在的
|
|
137
|
+
*/
|
|
138
|
+
function filterMenuData(menuData = []) {
|
|
139
|
+
return menuData.filter((item) => item && item.meta && item.meta.title && !item.meta.hideInMenu || notNullArray(item.children)).map((item) => {
|
|
140
|
+
const newItem = { ...item };
|
|
141
|
+
const routerChildren = newItem.children || [];
|
|
142
|
+
if (notNullArray(routerChildren) && newItem.meta && !newItem.meta.hideChildrenInMenu && routerChildren.some((child) => child && child.meta && !!child.meta.title)) {
|
|
143
|
+
const newChildren = filterMenuData(routerChildren);
|
|
144
|
+
if (newChildren.length) return {
|
|
145
|
+
...newItem,
|
|
146
|
+
children: newChildren
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
return { ...item };
|
|
150
|
+
}).filter((item) => item);
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* support pathToRegexp get string
|
|
154
|
+
*/
|
|
155
|
+
var RouteListMap = class extends Map {
|
|
156
|
+
get(pathname) {
|
|
157
|
+
let routeValue;
|
|
158
|
+
try {
|
|
159
|
+
for (const [key, value] of this.entries()) {
|
|
160
|
+
const path = stripQueryStringAndHashFromPath(key);
|
|
161
|
+
if (!isUrl(key) && pathToRegexp(path || "", []).test(pathname)) {
|
|
162
|
+
routeValue = value;
|
|
163
|
+
break;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
} catch (error) {
|
|
167
|
+
console.log(error);
|
|
168
|
+
routeValue = void 0;
|
|
169
|
+
}
|
|
170
|
+
return routeValue;
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
/**
|
|
174
|
+
* 获取面包屑映射
|
|
175
|
+
* @param menuData 菜单配置
|
|
176
|
+
*/
|
|
177
|
+
function getBreadcrumbNameMap(menuData) {
|
|
178
|
+
const routerMap = new RouteListMap();
|
|
179
|
+
const flattenMenuData = (data, parent) => {
|
|
180
|
+
data.forEach((menuItem) => {
|
|
181
|
+
const routerChildren = menuItem.children || [];
|
|
182
|
+
if (notNullArray(routerChildren)) flattenMenuData(routerChildren, menuItem);
|
|
183
|
+
const path = mergePath(menuItem.path, parent ? parent.path : "/");
|
|
184
|
+
if (!isUrl(path)) routerMap.set(stripQueryStringAndHashFromPath(path) || "", menuItem);
|
|
185
|
+
});
|
|
186
|
+
};
|
|
187
|
+
flattenMenuData(menuData);
|
|
188
|
+
return routerMap;
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* @param routeList 路由配置
|
|
192
|
+
* @param locale 是否使用国际化
|
|
193
|
+
* @param formatMessage 国际化的程序
|
|
194
|
+
*/
|
|
195
|
+
function transformRoute(routeList, locale, formatMessage) {
|
|
196
|
+
const originalMenuData = formatter({
|
|
197
|
+
data: routeList,
|
|
198
|
+
formatMessage,
|
|
199
|
+
locale
|
|
200
|
+
});
|
|
201
|
+
const menuData = filterMenuData(originalMenuData);
|
|
202
|
+
return {
|
|
203
|
+
breadcrumb: getBreadcrumbNameMap(originalMenuData),
|
|
204
|
+
menuData
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
//#endregion
|
|
208
|
+
export { getKeyByPath, isUrl, stripQueryStringAndHashFromPath, transformRoute };
|
package/dist/typing.d.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
//#region src/typing.d.ts
|
|
2
|
+
interface MessageDescriptor {
|
|
3
|
+
id: string;
|
|
4
|
+
description?: string;
|
|
5
|
+
defaultMessage?: string;
|
|
6
|
+
}
|
|
7
|
+
interface MetaRecord {
|
|
8
|
+
/** @name hideInMenu 在菜单中隐藏自己和子节点 */
|
|
9
|
+
hideInMenu?: boolean;
|
|
10
|
+
/** @name hideChildrenInMenu 在菜单中隐藏子节点 */
|
|
11
|
+
hideChildrenInMenu?: boolean;
|
|
12
|
+
/** @name icon 菜单的icon */
|
|
13
|
+
icon?: any;
|
|
14
|
+
/** @name title 菜单的标题 */
|
|
15
|
+
title?: string;
|
|
16
|
+
/** @name locale 自定义菜单的国际化 key */
|
|
17
|
+
locale?: string | false;
|
|
18
|
+
pro_layout_parentKeys?: string[];
|
|
19
|
+
parentKeys?: string[];
|
|
20
|
+
[key: string]: any;
|
|
21
|
+
}
|
|
22
|
+
interface MenuDataItem {
|
|
23
|
+
children?: MenuDataItem[];
|
|
24
|
+
meta?: MetaRecord;
|
|
25
|
+
path: string;
|
|
26
|
+
key?: string;
|
|
27
|
+
name?: string | symbol;
|
|
28
|
+
[key: string]: any;
|
|
29
|
+
}
|
|
30
|
+
//#endregion
|
|
31
|
+
export { MenuDataItem, MessageDescriptor, MetaRecord };
|
package/dist/typing.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,52 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@antdv-vue/route-utils",
|
|
3
|
-
"
|
|
4
|
-
"
|
|
5
|
-
"description": "
|
|
3
|
+
"type": "module",
|
|
4
|
+
"version": "1.0.7",
|
|
5
|
+
"description": "@antdv-vue/route-utils",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "https://github.com/bunnyblueair/pro-components.git",
|
|
9
|
+
"directory": "packages/route-utils"
|
|
10
|
+
},
|
|
11
|
+
"keywords": [],
|
|
12
|
+
"exports": {
|
|
13
|
+
".": {
|
|
14
|
+
"types": "./dist/index.d.ts",
|
|
15
|
+
"import": "./dist/index.js",
|
|
16
|
+
"default": "./dist/index.js"
|
|
17
|
+
},
|
|
18
|
+
"./route-utils": {
|
|
19
|
+
"types": "./dist/route-utils/index.d.ts",
|
|
20
|
+
"import": "./dist/route-utils/index.js",
|
|
21
|
+
"default": "./dist/route-utils/index.js"
|
|
22
|
+
},
|
|
23
|
+
"./dist/*": {
|
|
24
|
+
"types": "./dist/*.d.ts",
|
|
25
|
+
"import": "./dist/*.js",
|
|
26
|
+
"default": "./dist/*.js"
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
"main": "./dist/index.js",
|
|
30
|
+
"module": "./dist/index.js",
|
|
31
|
+
"types": "./dist/index.d.ts",
|
|
32
|
+
"files": [
|
|
33
|
+
"CHANGELOG.md",
|
|
34
|
+
"dist",
|
|
35
|
+
"package.json"
|
|
36
|
+
],
|
|
37
|
+
"scripts": {
|
|
38
|
+
"test": "vitest run",
|
|
39
|
+
"test:watch": "vitest watch",
|
|
40
|
+
"test:ui": "vitest --ui",
|
|
41
|
+
"test:coverage": "vitest run --coverage",
|
|
42
|
+
"prepare": "yarn build",
|
|
43
|
+
"build": "tsdown",
|
|
44
|
+
"dev": "tsdown --watch"
|
|
45
|
+
},
|
|
46
|
+
"publishConfig": {
|
|
47
|
+
"access": "public"
|
|
48
|
+
},
|
|
49
|
+
"authors": [
|
|
50
|
+
"lucasjeke <lucasjekeqd@gmail.com> (https://github.com/lucasjeke)"
|
|
51
|
+
]
|
|
6
52
|
}
|