xshell 1.1.8 → 1.1.10
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/antd.sass +43 -0
- package/file.js +7 -2
- package/i18n/dict.json +3 -0
- package/package.json +8 -11
- package/path.d.ts +3 -5
- package/path.js +11 -21
- package/process.d.ts +20 -10
- package/process.js +59 -23
- package/prototype.browser.d.ts +35 -23
- package/prototype.browser.js +89 -57
- package/prototype.d.ts +35 -24
- package/prototype.js +99 -54
- package/server.d.ts +14 -15
- package/server.js +17 -15
- package/storage.d.ts +3 -3
- package/storage.js +3 -2
- package/utils.browser.d.ts +12 -7
- package/utils.browser.js +39 -10
- package/utils.d.ts +12 -7
- package/utils.js +37 -8
- package/patches/@types__byte-size@8.1.2.patch +0 -14
package/server.d.ts
CHANGED
|
@@ -2,7 +2,16 @@ import { type Server as HttpServer, type IncomingHttpHeaders, type IncomingMessa
|
|
|
2
2
|
import { type Http2SecureServer, type IncomingHttpHeaders as IncomingHttp2Headers } from 'http2';
|
|
3
3
|
import type { Duplex } from 'stream';
|
|
4
4
|
import type { WebSocketServer } from 'ws';
|
|
5
|
-
import
|
|
5
|
+
import { default as Koa, type Context, type Next } from 'koa';
|
|
6
|
+
import { Remote, type RequestOptions, type RawResponse } from './net.ts';
|
|
7
|
+
declare module 'http' {
|
|
8
|
+
interface IncomingMessage {
|
|
9
|
+
body?: Buffer;
|
|
10
|
+
}
|
|
11
|
+
interface ServerResponse {
|
|
12
|
+
body?: Buffer;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
6
15
|
declare module 'koa' {
|
|
7
16
|
interface Request {
|
|
8
17
|
/** 经过 decodeURIComponent 后,在路径重写之前的路径 */
|
|
@@ -13,16 +22,6 @@ declare module 'koa' {
|
|
|
13
22
|
compress: boolean;
|
|
14
23
|
}
|
|
15
24
|
}
|
|
16
|
-
import type { UAParser } from 'ua-parser-js';
|
|
17
|
-
import { Remote, type RequestOptions, type RawResponse } from './net.ts';
|
|
18
|
-
declare module 'http' {
|
|
19
|
-
interface IncomingMessage {
|
|
20
|
-
body?: Buffer;
|
|
21
|
-
}
|
|
22
|
-
interface ServerResponse {
|
|
23
|
-
body?: Buffer;
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
25
|
export declare class Server {
|
|
27
26
|
/** proxy 时需要丢弃的 resposne headers */
|
|
28
27
|
static drop_response_headers: Set<string>;
|
|
@@ -34,10 +33,10 @@ export declare class Server {
|
|
|
34
33
|
};
|
|
35
34
|
/** sea 下最后修改时间,用于 http 资源缓存 */
|
|
36
35
|
last_modified_str?: string;
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
empty_body_statuses: Set<number>;
|
|
40
|
-
empty_body_methods: Set<string>;
|
|
36
|
+
static js_exts: Set<string>;
|
|
37
|
+
static logger_ignore_fexts: Set<string>;
|
|
38
|
+
static empty_body_statuses: Set<number>;
|
|
39
|
+
static empty_body_methods: Set<string>;
|
|
41
40
|
app: Koa;
|
|
42
41
|
handler: ReturnType<Koa['callback']>;
|
|
43
42
|
/** 启用 http server */
|
package/server.js
CHANGED
|
@@ -6,6 +6,12 @@ import fs from 'fs';
|
|
|
6
6
|
import { buffer as stream_to_buffer } from 'stream/consumers';
|
|
7
7
|
import util from 'util';
|
|
8
8
|
import node_sea from 'node:sea';
|
|
9
|
+
import { default as Koa } from 'koa';
|
|
10
|
+
import KoaCors from '@koa/cors';
|
|
11
|
+
import KoaCompress from 'koa-compress';
|
|
12
|
+
import { UAParser } from 'ua-parser-js';
|
|
13
|
+
import resolve_safely from 'resolve-path';
|
|
14
|
+
import { contentType as get_content_type } from 'mime-types';
|
|
9
15
|
// --- my libs
|
|
10
16
|
import { t } from "./i18n/instance.js";
|
|
11
17
|
import { request as _request, Remote } from "./net.js";
|
|
@@ -31,10 +37,10 @@ export class Server {
|
|
|
31
37
|
};
|
|
32
38
|
/** sea 下最后修改时间,用于 http 资源缓存 */
|
|
33
39
|
last_modified_str;
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
empty_body_statuses = new Set([304, 204, 205]);
|
|
37
|
-
empty_body_methods = new Set(['HEAD', 'OPTIONS']);
|
|
40
|
+
static js_exts = new Set(['.js', '.mjs', '.cjs']);
|
|
41
|
+
static logger_ignore_fexts = new Set(['.js', '.css', '.map', '.png', '.jpg', '.svg', '.ico', '.json', '.woff2', '.ttf', '.php']);
|
|
42
|
+
static empty_body_statuses = new Set([304, 204, 205]);
|
|
43
|
+
static empty_body_methods = new Set(['HEAD', 'OPTIONS']);
|
|
38
44
|
app;
|
|
39
45
|
handler;
|
|
40
46
|
/** 启用 http server */
|
|
@@ -93,14 +99,9 @@ export class Server {
|
|
|
93
99
|
}
|
|
94
100
|
/** start http server and listen */
|
|
95
101
|
async start() {
|
|
96
|
-
const { default: Koa } = await import('koa');
|
|
97
|
-
const { default: KoaCors } = await import('@koa/cors');
|
|
98
|
-
const { default: KoaCompress } = await import('koa-compress');
|
|
99
102
|
if (sea)
|
|
100
103
|
this.last_modified_str = (await fstat(exe_nodejs))
|
|
101
104
|
.mtime.toUTCString();
|
|
102
|
-
const { UAParser } = await import('ua-parser-js');
|
|
103
|
-
this.UAParser = UAParser;
|
|
104
105
|
// --- init koa app
|
|
105
106
|
let app = new Koa();
|
|
106
107
|
app.on('error', this.on_error.bind(this));
|
|
@@ -384,6 +385,8 @@ export class Server {
|
|
|
384
385
|
const { request } = ctx;
|
|
385
386
|
const { query: queries, body, path, _path, protocol, host, req: { httpVersion: http_version }, ip, headers, } = request;
|
|
386
387
|
let { method } = request;
|
|
388
|
+
if (Server.logger_ignore_fexts.has(path.fext))
|
|
389
|
+
return;
|
|
387
390
|
let s = '';
|
|
388
391
|
// 时间
|
|
389
392
|
s += `${this.log_date ? new Date().to_str() : new Date().to_time_str()} `;
|
|
@@ -442,7 +445,7 @@ export class Server {
|
|
|
442
445
|
}
|
|
443
446
|
format_ua(headers) {
|
|
444
447
|
const { rtt, 'device-memory': memory } = headers;
|
|
445
|
-
const { device, os, browser } =
|
|
448
|
+
const { device, os, browser } = UAParser(headers).withClientHints();
|
|
446
449
|
const vendor = device.vendor?.toLowerCase();
|
|
447
450
|
const model = device.model?.toLowerCase();
|
|
448
451
|
const osname = os.name?.toLowerCase();
|
|
@@ -522,7 +525,7 @@ export class Server {
|
|
|
522
525
|
// 关联了 response_.body 到 res 的 finish 事件,(koa/response.js#set body() 中 onFinish(res, destroy.bind(null, response_.body))
|
|
523
526
|
// 会让 response_.body 这个 UndiciBodyReadable 被提前 finish,而此时
|
|
524
527
|
// 流还没 emit end, 会触发错误 RequestAbortedError [AbortError]: Request aborted
|
|
525
|
-
if (
|
|
528
|
+
if (Server.empty_body_statuses.has(response_.status) || Server.empty_body_methods.has(method))
|
|
526
529
|
consume_stream(response_.body, true);
|
|
527
530
|
else
|
|
528
531
|
response.body = response_.body;
|
|
@@ -603,7 +606,6 @@ export class Server {
|
|
|
603
606
|
let { response } = ctx;
|
|
604
607
|
if (!absolute && !_sea) {
|
|
605
608
|
fp = fp.strip_if_start(fpd_root).strip_if_start('/');
|
|
606
|
-
const { default: resolve_safely } = await import('resolve-path');
|
|
607
609
|
try {
|
|
608
610
|
fp = resolve_safely(fpd_root, fp).fp;
|
|
609
611
|
}
|
|
@@ -652,10 +654,10 @@ export class Server {
|
|
|
652
654
|
if (download)
|
|
653
655
|
response.set('content-disposition', `attachment; filename="${encodeURIComponent(fp.fname)}"`);
|
|
654
656
|
if (!response.get('content-type')) {
|
|
655
|
-
const { contentType: get_content_type } = await import('mime-types');
|
|
656
657
|
const { fext } = fp;
|
|
657
|
-
response.set('content-type', (
|
|
658
|
-
|
|
658
|
+
response.set('content-type', (Server.js_exts.has(fext)
|
|
659
|
+
? 'text/javascript; chatset=utf-8'
|
|
660
|
+
: get_content_type(`file.${fext}`) || 'application/octet-stream'));
|
|
659
661
|
}
|
|
660
662
|
const modified_since_str = request.get('if-modified-since');
|
|
661
663
|
if ((method === 'GET' || method === 'HEAD') && modified_since_str === last_modified_str) {
|
package/storage.d.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
export declare let storage: {
|
|
2
2
|
/** 通过 key 获取字符串类型的值,不存在时返回空字符串 ('') */
|
|
3
|
-
getstr(key: string):
|
|
3
|
+
getstr<TString extends string = string>(key: string): TString;
|
|
4
4
|
/** 将字符串类型的值保存到 key */
|
|
5
5
|
setstr(key: string, value?: string): void;
|
|
6
6
|
/** 根据 key 获取 JSON 类型的值,不存在时返回通过第二个参数传入的默认值,默认为 null */
|
|
7
7
|
get<TValue = any>(key: string, _default?: TValue): TValue;
|
|
8
|
-
/** 保存 JSON 类型的值到 key */
|
|
9
|
-
set(key: string, value:
|
|
8
|
+
/** 保存 JSON 类型的值到 key, 并返回 value */
|
|
9
|
+
set<TValue>(key: string, value: TValue): TValue;
|
|
10
10
|
list(): string[];
|
|
11
11
|
delete(key: string): void;
|
|
12
12
|
};
|
package/storage.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export let storage = {
|
|
2
2
|
/** 通过 key 获取字符串类型的值,不存在时返回空字符串 ('') */
|
|
3
3
|
getstr(key) {
|
|
4
|
-
return localStorage.getItem(key) || '';
|
|
4
|
+
return (localStorage.getItem(key) || '');
|
|
5
5
|
},
|
|
6
6
|
/** 将字符串类型的值保存到 key */
|
|
7
7
|
setstr(key, value = '') {
|
|
@@ -14,9 +14,10 @@ export let storage = {
|
|
|
14
14
|
? _default
|
|
15
15
|
: JSON.parse(strvalue);
|
|
16
16
|
},
|
|
17
|
-
/** 保存 JSON 类型的值到 key */
|
|
17
|
+
/** 保存 JSON 类型的值到 key, 并返回 value */
|
|
18
18
|
set(key, value) {
|
|
19
19
|
localStorage.setItem(key, JSON.stringify(value));
|
|
20
|
+
return value;
|
|
20
21
|
},
|
|
21
22
|
list() {
|
|
22
23
|
return Object.keys(localStorage);
|
package/utils.browser.d.ts
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
import { type
|
|
2
|
-
export declare function assert(assertion:
|
|
1
|
+
import { type Mapper } from './prototype.browser.ts';
|
|
2
|
+
export declare function assert<T>(assertion: T, message?: string): T;
|
|
3
3
|
/** 做参数校验,逻辑检查 */
|
|
4
|
-
export declare function check(condition:
|
|
4
|
+
export declare function check<T>(condition: T, message?: string): T;
|
|
5
5
|
/** 通过 console.log 打印对象并返回
|
|
6
6
|
@example
|
|
7
7
|
log('label', obj)
|
|
8
8
|
log(obj) */
|
|
9
9
|
export declare function log<TObj>(obj: TObj): TObj;
|
|
10
10
|
export declare function log<TObj>(label: string, obj: TObj): TObj;
|
|
11
|
-
/** 数组或 iterable 去重(可按
|
|
12
|
-
-
|
|
13
|
-
export declare function unique<TObj>(iterable: TObj[] | Iterable<TObj>,
|
|
11
|
+
/** 数组或 iterable 去重(可按 mapper 选择或计算某个值来去重),重复值保留最后出现的那个
|
|
12
|
+
- mapper?: 可以是 key (string, number, symbol) 或 (obj: any) => any */
|
|
13
|
+
export declare function unique<TObj>(iterable: TObj[] | Iterable<TObj>, mapper?: keyof TObj | Mapper<TObj>): TObj[];
|
|
14
14
|
/** 生成 0, 1, ..., n - 1 (不包括 n) 的数组,支持传入 generator 函数,通过 index 生成各个元素
|
|
15
15
|
@example seq(10, i => `item-${i}`) */
|
|
16
16
|
export declare function seq<T = number>(n: number, generator?: (index: number) => T): T[];
|
|
@@ -79,11 +79,16 @@ export declare function encode(str: string): Uint8Array<ArrayBufferLike>;
|
|
|
79
79
|
在流式处理 (buffer 可能不完整) 时,应使用独立的 TextDecoder 实例调用 decode(buffer, { stream: true }) */
|
|
80
80
|
export declare function decode(buffer: Uint8Array): string;
|
|
81
81
|
/** 字符串字典序比较 */
|
|
82
|
-
export declare function strcmp(l: string, r: string):
|
|
82
|
+
export declare function strcmp(l: string, r: string): 1 | 0 | -1;
|
|
83
83
|
/** 比较 1.10.02 这种版本号
|
|
84
84
|
- l, r: 两个版本号字符串
|
|
85
85
|
- loose?: 宽松模式,允许两个版本号格式(位数)不一致 */
|
|
86
86
|
export declare function vercmp(l: string, r: string, loose?: boolean): number;
|
|
87
|
+
/** 模糊过滤字符串列表或对象列表,常用于根据用户输入补全或搜索过滤
|
|
88
|
+
- query: 查询字符串,要求为全小写
|
|
89
|
+
- list: 要过滤的列表
|
|
90
|
+
- list_lower?: 要过滤的列表对应的全小写字符串列表形式,传入时可复用缓存,加快搜索速度 */
|
|
91
|
+
export declare function fuzzyfilter<TItem = string>(query: string, list: TItem[], mapper?: keyof TItem | Mapper<TItem>, list_lower?: string[], single_char_startswith?: boolean): TItem[];
|
|
87
92
|
export declare function get<TReturn = any>(obj: any, keypath: string): TReturn;
|
|
88
93
|
export declare function global_get<TReturn = any>(keypath: string): TReturn;
|
|
89
94
|
export declare function invoke<TReturn = any>(obj: any, funcpath: string, args: any[]): TReturn;
|
package/utils.browser.js
CHANGED
|
@@ -1,17 +1,19 @@
|
|
|
1
|
-
import { is_key_type,
|
|
1
|
+
import { is_key_type, build_mapper, not_empty, ident } from "./prototype.browser.js";
|
|
2
2
|
import { t } from "./i18n/instance.js";
|
|
3
3
|
export function assert(assertion, message) {
|
|
4
4
|
if (!assertion) {
|
|
5
5
|
debugger;
|
|
6
|
-
throw new Error(
|
|
6
|
+
throw new Error(`${t('断言失败')}: ${message ? `${message}: ` : ''}`);
|
|
7
7
|
}
|
|
8
|
+
return assertion;
|
|
8
9
|
}
|
|
9
10
|
/** 做参数校验,逻辑检查 */
|
|
10
11
|
export function check(condition, message) {
|
|
11
12
|
if (!condition) {
|
|
12
13
|
debugger;
|
|
13
|
-
throw new Error(message || '检查失败');
|
|
14
|
+
throw new Error(message || t('检查失败'));
|
|
14
15
|
}
|
|
16
|
+
return condition;
|
|
15
17
|
}
|
|
16
18
|
export function log(...args) {
|
|
17
19
|
if (args.length === 2) {
|
|
@@ -25,16 +27,16 @@ export function log(...args) {
|
|
|
25
27
|
return obj;
|
|
26
28
|
}
|
|
27
29
|
}
|
|
28
|
-
/** 数组或 iterable 去重(可按
|
|
29
|
-
-
|
|
30
|
-
export function unique(iterable,
|
|
31
|
-
if (!
|
|
30
|
+
/** 数组或 iterable 去重(可按 mapper 选择或计算某个值来去重),重复值保留最后出现的那个
|
|
31
|
+
- mapper?: 可以是 key (string, number, symbol) 或 (obj: any) => any */
|
|
32
|
+
export function unique(iterable, mapper) {
|
|
33
|
+
if (!mapper)
|
|
32
34
|
return [...new Set(iterable)];
|
|
33
|
-
if (is_key_type(
|
|
34
|
-
|
|
35
|
+
if (is_key_type(mapper))
|
|
36
|
+
mapper = build_mapper(mapper);
|
|
35
37
|
let map = new Map();
|
|
36
38
|
for (const x of iterable)
|
|
37
|
-
map.set(
|
|
39
|
+
map.set(mapper(x), x);
|
|
38
40
|
return [...map.values()];
|
|
39
41
|
}
|
|
40
42
|
/** 生成 0, 1, ..., n - 1 (不包括 n) 的数组,支持传入 generator 函数,通过 index 生成各个元素
|
|
@@ -260,6 +262,33 @@ export function vercmp(l, r, loose = false) {
|
|
|
260
262
|
// loose 下按短的优先,否则应该一样,为 0
|
|
261
263
|
return lparts.length - rparts.length;
|
|
262
264
|
}
|
|
265
|
+
/** 模糊过滤字符串列表或对象列表,常用于根据用户输入补全或搜索过滤
|
|
266
|
+
- query: 查询字符串,要求为全小写
|
|
267
|
+
- list: 要过滤的列表
|
|
268
|
+
- list_lower?: 要过滤的列表对应的全小写字符串列表形式,传入时可复用缓存,加快搜索速度 */
|
|
269
|
+
export function fuzzyfilter(query, list, mapper = ident, list_lower, single_char_startswith = false) {
|
|
270
|
+
if (!query)
|
|
271
|
+
return list;
|
|
272
|
+
if (is_key_type(mapper))
|
|
273
|
+
mapper = build_mapper(mapper);
|
|
274
|
+
mapper ??= ident;
|
|
275
|
+
list_lower ??= list.map(item => mapper(item).toLowerCase());
|
|
276
|
+
if (single_char_startswith && query.length === 1) {
|
|
277
|
+
const c = query[0];
|
|
278
|
+
return list.filter((_, i) => list_lower[i].startsWith(c));
|
|
279
|
+
}
|
|
280
|
+
const query_lower = query.toLowerCase();
|
|
281
|
+
return list.filter((_, i) => {
|
|
282
|
+
const str_lower = list_lower[i];
|
|
283
|
+
let j = 0;
|
|
284
|
+
for (const c of query_lower) {
|
|
285
|
+
j = str_lower.indexOf(c, j) + 1;
|
|
286
|
+
if (!j) // 找不到则 j === 0
|
|
287
|
+
return false;
|
|
288
|
+
}
|
|
289
|
+
return true;
|
|
290
|
+
});
|
|
291
|
+
}
|
|
263
292
|
export function get(obj, keypath) {
|
|
264
293
|
let obj_ = obj;
|
|
265
294
|
for (const key of keypath.split('.'))
|
package/utils.d.ts
CHANGED
|
@@ -2,22 +2,22 @@ import { Writable, Transform, type Readable, type Duplex, type TransformCallback
|
|
|
2
2
|
import util from 'util';
|
|
3
3
|
import type { TimerOptions } from 'timers';
|
|
4
4
|
import type Vinyl from 'vinyl';
|
|
5
|
-
import { type
|
|
5
|
+
import { type Mapper } from './prototype.ts';
|
|
6
6
|
/** `230` term 字符宽度 (实际上有 240) */
|
|
7
7
|
export declare const output_width = 230;
|
|
8
8
|
export declare function set_inspect_options(colors?: boolean): void;
|
|
9
|
-
export declare function assert(assertion:
|
|
9
|
+
export declare function assert<T>(assertion: T, message?: string): T;
|
|
10
10
|
/** 做参数校验,逻辑检查 */
|
|
11
|
-
export declare function check(condition:
|
|
11
|
+
export declare function check<T>(condition: T, message?: string): T;
|
|
12
12
|
/** 通过 console.log 打印对象并返回
|
|
13
13
|
@example
|
|
14
14
|
log('label', obj)
|
|
15
15
|
log(obj) */
|
|
16
16
|
export declare function log<TObj>(obj: TObj): TObj;
|
|
17
17
|
export declare function log<TObj>(label: string, obj: TObj): TObj;
|
|
18
|
-
/** 数组或 iterable 去重(可按
|
|
19
|
-
-
|
|
20
|
-
export declare function unique<TObj>(iterable: TObj[] | Iterable<TObj>,
|
|
18
|
+
/** 数组或 iterable 去重(可按 mapper 选择或计算某个值来去重),重复值保留最后出现的那个
|
|
19
|
+
- mapper?: 可以是 key (string, number, symbol) 或 (obj: any) => any */
|
|
20
|
+
export declare function unique<TObj>(iterable: TObj[] | Iterable<TObj>, mapper?: keyof TObj | Mapper<TObj>): TObj[];
|
|
21
21
|
/** 生成 0, 1, ..., n - 1 (不包括 n) 的数组,支持传入 generator 函数,通过 index 生成各个元素
|
|
22
22
|
@example seq(10, i => `item-${i}`) */
|
|
23
23
|
export declare function seq<T = number>(n: number, generator?: (index: number) => T): T[];
|
|
@@ -40,11 +40,16 @@ export declare function filter_values<TObj extends Record<string, any>>(obj: TOb
|
|
|
40
40
|
/** 忽略对象中的 keys, 返回新对象 */
|
|
41
41
|
export declare function omit<TObj>(obj: TObj, omit_keys: string[]): TObj;
|
|
42
42
|
/** 字符串字典序比较 */
|
|
43
|
-
export declare function strcmp(l: string, r: string):
|
|
43
|
+
export declare function strcmp(l: string, r: string): 1 | 0 | -1;
|
|
44
44
|
/** 比较 1.10.02 这种版本号
|
|
45
45
|
- l, r: 两个版本号字符串
|
|
46
46
|
- loose?: 宽松模式,允许两个版本号格式(位数)不一致 */
|
|
47
47
|
export declare function vercmp(l: string, r: string, loose?: boolean): number;
|
|
48
|
+
/** 模糊过滤字符串列表或对象列表,常用于根据用户输入补全或搜索过滤
|
|
49
|
+
- query: 查询字符串,要求为全小写
|
|
50
|
+
- list: 要过滤的列表
|
|
51
|
+
- list_lower?: 要过滤的列表对应的全小写字符串列表形式,传入时可复用缓存,加快搜索速度 */
|
|
52
|
+
export declare function fuzzyfilter<TItem = string>(query: string, list: TItem[], mapper?: keyof TItem | Mapper<TItem>, list_lower?: string[], single_char_startswith?: boolean): TItem[];
|
|
48
53
|
export declare function get<TReturn = any>(obj: any, keypath: string): TReturn;
|
|
49
54
|
export declare function global_get<TReturn = any>(keypath: string): TReturn;
|
|
50
55
|
export declare function invoke<TReturn = any>(obj: any, funcpath: string, args: any[]): TReturn;
|
package/utils.js
CHANGED
|
@@ -2,7 +2,7 @@ import { Stream, Writable, Transform } from 'stream';
|
|
|
2
2
|
import util from 'util';
|
|
3
3
|
import timers from 'timers/promises';
|
|
4
4
|
import { t } from "./i18n/instance.js";
|
|
5
|
-
import {
|
|
5
|
+
import { build_mapper, not_empty, is_key_type, noop, ident } from "./prototype.js";
|
|
6
6
|
/** `230` term 字符宽度 (实际上有 240) */
|
|
7
7
|
export const output_width = 230;
|
|
8
8
|
export function set_inspect_options(colors = true) {
|
|
@@ -26,6 +26,7 @@ export function assert(assertion, message) {
|
|
|
26
26
|
debugger;
|
|
27
27
|
throw new Error(`${t('断言失败')}: ${message ? `${message}: ` : ''}`);
|
|
28
28
|
}
|
|
29
|
+
return assertion;
|
|
29
30
|
}
|
|
30
31
|
/** 做参数校验,逻辑检查 */
|
|
31
32
|
export function check(condition, message) {
|
|
@@ -33,6 +34,7 @@ export function check(condition, message) {
|
|
|
33
34
|
debugger;
|
|
34
35
|
throw new Error(message || t('检查失败'));
|
|
35
36
|
}
|
|
37
|
+
return condition;
|
|
36
38
|
}
|
|
37
39
|
export function log(...args) {
|
|
38
40
|
if (args.length === 2) {
|
|
@@ -46,16 +48,16 @@ export function log(...args) {
|
|
|
46
48
|
return obj;
|
|
47
49
|
}
|
|
48
50
|
}
|
|
49
|
-
/** 数组或 iterable 去重(可按
|
|
50
|
-
-
|
|
51
|
-
export function unique(iterable,
|
|
52
|
-
if (!
|
|
51
|
+
/** 数组或 iterable 去重(可按 mapper 选择或计算某个值来去重),重复值保留最后出现的那个
|
|
52
|
+
- mapper?: 可以是 key (string, number, symbol) 或 (obj: any) => any */
|
|
53
|
+
export function unique(iterable, mapper) {
|
|
54
|
+
if (!mapper)
|
|
53
55
|
return [...new Set(iterable)];
|
|
54
|
-
if (is_key_type(
|
|
55
|
-
|
|
56
|
+
if (is_key_type(mapper))
|
|
57
|
+
mapper = build_mapper(mapper);
|
|
56
58
|
let map = new Map();
|
|
57
59
|
for (const x of iterable)
|
|
58
|
-
map.set(
|
|
60
|
+
map.set(mapper(x), x);
|
|
59
61
|
return [...map.values()];
|
|
60
62
|
}
|
|
61
63
|
/** 生成 0, 1, ..., n - 1 (不包括 n) 的数组,支持传入 generator 函数,通过 index 生成各个元素
|
|
@@ -130,6 +132,33 @@ export function vercmp(l, r, loose = false) {
|
|
|
130
132
|
// loose 下按短的优先,否则应该一样,为 0
|
|
131
133
|
return lparts.length - rparts.length;
|
|
132
134
|
}
|
|
135
|
+
/** 模糊过滤字符串列表或对象列表,常用于根据用户输入补全或搜索过滤
|
|
136
|
+
- query: 查询字符串,要求为全小写
|
|
137
|
+
- list: 要过滤的列表
|
|
138
|
+
- list_lower?: 要过滤的列表对应的全小写字符串列表形式,传入时可复用缓存,加快搜索速度 */
|
|
139
|
+
export function fuzzyfilter(query, list, mapper = ident, list_lower, single_char_startswith = false) {
|
|
140
|
+
if (!query)
|
|
141
|
+
return list;
|
|
142
|
+
if (is_key_type(mapper))
|
|
143
|
+
mapper = build_mapper(mapper);
|
|
144
|
+
mapper ??= ident;
|
|
145
|
+
list_lower ??= list.map(item => mapper(item).toLowerCase());
|
|
146
|
+
if (single_char_startswith && query.length === 1) {
|
|
147
|
+
const c = query[0];
|
|
148
|
+
return list.filter((_, i) => list_lower[i].startsWith(c));
|
|
149
|
+
}
|
|
150
|
+
const query_lower = query.toLowerCase();
|
|
151
|
+
return list.filter((_, i) => {
|
|
152
|
+
const str_lower = list_lower[i];
|
|
153
|
+
let j = 0;
|
|
154
|
+
for (const c of query_lower) {
|
|
155
|
+
j = str_lower.indexOf(c, j) + 1;
|
|
156
|
+
if (!j) // 找不到则 j === 0
|
|
157
|
+
return false;
|
|
158
|
+
}
|
|
159
|
+
return true;
|
|
160
|
+
});
|
|
161
|
+
}
|
|
133
162
|
export function get(obj, keypath) {
|
|
134
163
|
let obj_ = obj;
|
|
135
164
|
for (const key of keypath.split('.'))
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
# 用来修复 `byte_size()` 函数的 ts 类型报错
|
|
2
|
-
|
|
3
|
-
diff --git a/package.json b/package.json
|
|
4
|
-
index dd8ed66fcb8b1ab93f0a3a015bad95bc31d3b18d..6a3753f42610451163fd7d502a93779fe3dca8db 100644
|
|
5
|
-
--- a/package.json
|
|
6
|
-
+++ b/package.json
|
|
7
|
-
@@ -11,6 +11,7 @@
|
|
8
|
-
"url": "https://github.com/lntel"
|
|
9
|
-
}
|
|
10
|
-
],
|
|
11
|
-
+ "type": "module",
|
|
12
|
-
"main": "",
|
|
13
|
-
"types": "index.d.ts",
|
|
14
|
-
"repository": {
|