xshell 1.1.9 → 1.1.11
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/package.json +6 -6
- 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 +28 -21
- package/prototype.browser.js +57 -54
- package/prototype.d.ts +27 -20
- package/prototype.js +53 -39
- package/server.d.ts +13 -12
- package/server.js +10 -6
- package/stdin.d.ts +4 -0
- package/stdin.js +23 -0
- 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/prototype.d.ts
CHANGED
|
@@ -71,8 +71,7 @@ declare global {
|
|
|
71
71
|
surround_tag(this: string, tag_name: string): string;
|
|
72
72
|
to_lf(this: string): string;
|
|
73
73
|
/** 'xxx'.replace(/pattern/g, '')
|
|
74
|
-
如果 pattern 是 string 则在创建 RegExp 时自动加上 flags (默认 'g'), 否则忽略 flags
|
|
75
|
-
*/
|
|
74
|
+
如果 pattern 是 string 则在创建 RegExp 时自动加上 flags (默认 'g'), 否则忽略 flags */
|
|
76
75
|
rm(this: string, pattern: string | RegExp, flags?: string): string;
|
|
77
76
|
readonly red: string;
|
|
78
77
|
readonly red_: string;
|
|
@@ -127,17 +126,25 @@ declare global {
|
|
|
127
126
|
slice_to(this: string, search: string, options?: SliceOptions): string;
|
|
128
127
|
/** 等价于 .endsWith('/') */
|
|
129
128
|
isdir: boolean;
|
|
130
|
-
/**
|
|
129
|
+
/** 转换为以 `/` 分割的路径,可能以 / 结尾 */
|
|
131
130
|
fp: string;
|
|
132
|
-
/**
|
|
131
|
+
/** 转换为以 `/` 分割的文件夹路径,一定以 / 结尾 */
|
|
133
132
|
fpd: string;
|
|
134
|
-
/**
|
|
133
|
+
/** 父文件夹路径,一定以 `/` 结尾,或为空
|
|
134
|
+
特殊情况:
|
|
135
|
+
- '/'.fdir === ''
|
|
136
|
+
- 'D:/'.fdir === '' */
|
|
135
137
|
fdir: string;
|
|
136
|
-
/**
|
|
138
|
+
/** 文件或文件夹名, 保留结尾的 /
|
|
139
|
+
常规情况:
|
|
137
140
|
- D:/0/aaa.txt -> aaa.txt
|
|
138
|
-
- D:/aaa/ -> aaa/
|
|
141
|
+
- D:/aaa/ -> aaa/
|
|
142
|
+
特殊情况:
|
|
143
|
+
- '/'.fname === '/'
|
|
144
|
+
- 'D:/'.fname === 'D:/' */
|
|
139
145
|
fname: string;
|
|
140
|
-
/**
|
|
146
|
+
/** 文件后缀名,不带点,如: txt, zip,没有后缀时返回空字符串
|
|
147
|
+
特殊情况: .aaa 的 fext 为 '' */
|
|
141
148
|
fext: string;
|
|
142
149
|
to_backslash(this: string): string;
|
|
143
150
|
}
|
|
@@ -173,15 +180,15 @@ declare global {
|
|
|
173
180
|
log(this: string[], limit?: number): void;
|
|
174
181
|
indent(this: string[], width?: number, c?: string): string[];
|
|
175
182
|
indent2to4(this: string[]): string[];
|
|
176
|
-
/** 对数组中所有元素求和 (+), 返回结果,可传入
|
|
177
|
-
sum<TReturn = T>(this: T[],
|
|
178
|
-
/** 查找数组中最大的元素,可传入
|
|
179
|
-
max(this: T[],
|
|
180
|
-
/** 查找数组中最小的元素,可传入
|
|
181
|
-
min(this: T[],
|
|
182
|
-
/** 去除重复元素(可按
|
|
183
|
-
-
|
|
184
|
-
unique(this: T[],
|
|
183
|
+
/** 对数组中所有元素求和 (+), 返回结果,可传入 mapper 计算出某个值,用作求和 */
|
|
184
|
+
sum<TReturn = T>(this: T[], mapper?: keyof T | Mapper<T>): TReturn;
|
|
185
|
+
/** 查找数组中最大的元素,可传入 mapper 计算出某个值,用作大小比较 */
|
|
186
|
+
max(this: T[], mapper?: keyof T | Mapper<T>): T;
|
|
187
|
+
/** 查找数组中最小的元素,可传入 mapper 计算出某个值,用作大小比较 */
|
|
188
|
+
min(this: T[], mapper?: keyof T | Mapper<T>): T;
|
|
189
|
+
/** 去除重复元素(可按 mapper 选择或计算某个值来去重),重复值保留最后出现的那个
|
|
190
|
+
- mapper?: 可以是 key (string, number, symbol) 或 (obj: any) => any */
|
|
191
|
+
unique(this: T[], mapper?: keyof T | Mapper<T>): T[];
|
|
185
192
|
/**
|
|
186
193
|
- trim_line?: `true`
|
|
187
194
|
- rm_empty_lines?: `true`
|
|
@@ -209,7 +216,7 @@ declare global {
|
|
|
209
216
|
toJSON(this: Error): string;
|
|
210
217
|
}
|
|
211
218
|
interface Set<T> {
|
|
212
|
-
map<TResult>(
|
|
219
|
+
map<TResult>(mapper: (value: T, index: number) => TResult): TResult[];
|
|
213
220
|
}
|
|
214
221
|
}
|
|
215
222
|
interface SliceOptions {
|
|
@@ -219,8 +226,8 @@ interface SliceOptions {
|
|
|
219
226
|
export declare const emoji_regex: RegExp;
|
|
220
227
|
export declare const noop: () => void;
|
|
221
228
|
export declare const ident: <T>(x: T) => T;
|
|
222
|
-
export declare const
|
|
223
|
-
export type
|
|
229
|
+
export declare const build_mapper: <TObj>(key: keyof TObj) => (obj: TObj) => TObj[keyof TObj];
|
|
230
|
+
export type Mapper<TObj = any, TKey extends keyof TObj = keyof TObj> = (obj: TObj) => TObj[TKey];
|
|
224
231
|
/** value 不为 null 或 undefined */
|
|
225
232
|
export declare const not_empty: (value: any) => boolean;
|
|
226
233
|
export declare const empty: (value: any) => boolean;
|
package/prototype.js
CHANGED
|
@@ -2,11 +2,10 @@ import util from 'util';
|
|
|
2
2
|
import EmojiRegex from 'emoji-regex';
|
|
3
3
|
export const emoji_regex = EmojiRegex();
|
|
4
4
|
import strip_ansi from 'strip-ansi';
|
|
5
|
-
import { to_fp, dirname, basename, extname } from "./path.js";
|
|
6
5
|
import { t } from "./i18n/instance.js";
|
|
7
6
|
export const noop = () => { };
|
|
8
7
|
export const ident = (x) => x;
|
|
9
|
-
export const
|
|
8
|
+
export const build_mapper = (key) => (obj) => obj[key];
|
|
10
9
|
/** value 不为 null 或 undefined */
|
|
11
10
|
export const not_empty = (value) => value !== null && value !== undefined;
|
|
12
11
|
export const empty = (value) => value === undefined || value === null;
|
|
@@ -247,9 +246,6 @@ if (!globalThis.my_prototype_defined) {
|
|
|
247
246
|
to_lf() {
|
|
248
247
|
return this.replace(/\r\n/g, '\n');
|
|
249
248
|
},
|
|
250
|
-
to_crlf() {
|
|
251
|
-
return this.replace(/\n/g, '\r\n');
|
|
252
|
-
},
|
|
253
249
|
rm(pattern, flags = 'g') {
|
|
254
250
|
if (typeof pattern === 'string')
|
|
255
251
|
pattern = new RegExp(pattern, flags);
|
|
@@ -371,28 +367,47 @@ if (!globalThis.my_prototype_defined) {
|
|
|
371
367
|
return this.endsWith('/');
|
|
372
368
|
},
|
|
373
369
|
fp() {
|
|
374
|
-
|
|
370
|
+
if (!this)
|
|
371
|
+
return this;
|
|
372
|
+
const fp = this.replaceAll('\\', '/');
|
|
373
|
+
// 转换小写盘符开头的路径
|
|
374
|
+
return fp[1] === ':' && 'a' <= fp[0] && fp[0] <= 'z'
|
|
375
|
+
? fp[0].toUpperCase() + fp.slice(1)
|
|
376
|
+
: fp;
|
|
375
377
|
},
|
|
376
378
|
fpd() {
|
|
377
|
-
const fp =
|
|
379
|
+
const { fp } = this;
|
|
378
380
|
return fp.endsWith('/') ? fp : `${fp}/`;
|
|
379
381
|
},
|
|
380
382
|
fdir() {
|
|
381
|
-
|
|
382
|
-
// 有可能 fpd 是 '/'
|
|
383
|
-
return fpd.endsWith('/') ? fpd : `${fpd}/`;
|
|
383
|
+
return this.strip_end(this.fname);
|
|
384
384
|
},
|
|
385
385
|
fname() {
|
|
386
|
-
|
|
386
|
+
const ilast = this.lastIndexOf('/');
|
|
387
|
+
if (ilast === -1)
|
|
388
|
+
return this; // 没有斜杠时返回整个字符串
|
|
389
|
+
// 以斜杠结尾的情况
|
|
390
|
+
if (ilast === this.length - 1) {
|
|
391
|
+
const iprev = this.lastIndexOf('/', ilast - 1);
|
|
392
|
+
return iprev === -1
|
|
393
|
+
? this // 只有一个斜杠且在末尾
|
|
394
|
+
: this.slice(iprev + 1);
|
|
395
|
+
}
|
|
396
|
+
// 返回最后一个斜杠后的内容
|
|
397
|
+
return this.slice(ilast + 1);
|
|
387
398
|
},
|
|
388
399
|
fext() {
|
|
389
|
-
|
|
390
|
-
|
|
400
|
+
const { fname } = this;
|
|
401
|
+
const index = fname.lastIndexOf('.');
|
|
402
|
+
return index <= 0
|
|
403
|
+
? ''
|
|
404
|
+
: fname.slice(index + 1);
|
|
405
|
+
}
|
|
391
406
|
}),
|
|
392
407
|
...to_method_property_descriptors({
|
|
393
408
|
to_backslash() {
|
|
394
409
|
return this.replaceAll('/', '\\');
|
|
395
|
-
}
|
|
410
|
+
}
|
|
396
411
|
})
|
|
397
412
|
});
|
|
398
413
|
// ------------------------------------ Date.prototype
|
|
@@ -449,10 +464,9 @@ if (!globalThis.my_prototype_defined) {
|
|
|
449
464
|
return String(hour).padStart(2, '0') + splitter +
|
|
450
465
|
String(date.getMinutes()).padStart(2, '0') + splitter +
|
|
451
466
|
String(date.getSeconds()).padStart(2, '0') +
|
|
452
|
-
(ms
|
|
453
|
-
'.' + String(date.getMilliseconds()).padStart(3, '0')
|
|
454
|
-
:
|
|
455
|
-
'');
|
|
467
|
+
(ms
|
|
468
|
+
? '.' + String(date.getMilliseconds()).padStart(3, '0')
|
|
469
|
+
: '');
|
|
456
470
|
}
|
|
457
471
|
// ------------------------------------ Number.prototype
|
|
458
472
|
Object.defineProperties(Number.prototype, to_method_property_descriptors({
|
|
@@ -528,27 +542,27 @@ if (!globalThis.my_prototype_defined) {
|
|
|
528
542
|
return this.split_indents()
|
|
529
543
|
.map(line => ' '.repeat(line.indent * 2) + line.text);
|
|
530
544
|
},
|
|
531
|
-
sum(
|
|
545
|
+
sum(mapper) {
|
|
532
546
|
if (!this.length)
|
|
533
547
|
return undefined;
|
|
534
548
|
// 快捷路径
|
|
535
549
|
const first = this[0];
|
|
536
|
-
if ((typeof first === 'number' || typeof first === 'bigint') && !
|
|
550
|
+
if ((typeof first === 'number' || typeof first === 'bigint') && !mapper)
|
|
537
551
|
return this.reduce((acc, x) => acc + x, first);
|
|
538
|
-
if (is_key_type(
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
return this.reduce((acc, x) => acc +
|
|
552
|
+
if (is_key_type(mapper))
|
|
553
|
+
mapper = build_mapper(mapper);
|
|
554
|
+
mapper ??= ident;
|
|
555
|
+
return this.reduce((acc, x) => acc + mapper(x), mapper(first));
|
|
542
556
|
},
|
|
543
|
-
max(
|
|
557
|
+
max(mapper = ident) {
|
|
544
558
|
if (!this.length)
|
|
545
559
|
return undefined;
|
|
546
|
-
if (is_key_type(
|
|
547
|
-
|
|
548
|
-
let max =
|
|
560
|
+
if (is_key_type(mapper))
|
|
561
|
+
mapper = build_mapper(mapper);
|
|
562
|
+
let max = mapper(this[0]);
|
|
549
563
|
let imax = 0;
|
|
550
564
|
for (let i = 0; i < this.length; i++) {
|
|
551
|
-
const value =
|
|
565
|
+
const value = mapper(this[i]);
|
|
552
566
|
if (value > max) {
|
|
553
567
|
max = value;
|
|
554
568
|
imax = i;
|
|
@@ -556,15 +570,15 @@ if (!globalThis.my_prototype_defined) {
|
|
|
556
570
|
}
|
|
557
571
|
return this[imax];
|
|
558
572
|
},
|
|
559
|
-
min(
|
|
573
|
+
min(mapper = ident) {
|
|
560
574
|
if (!this.length)
|
|
561
575
|
return undefined;
|
|
562
|
-
if (is_key_type(
|
|
563
|
-
|
|
564
|
-
let min =
|
|
576
|
+
if (is_key_type(mapper))
|
|
577
|
+
mapper = build_mapper(mapper);
|
|
578
|
+
let min = mapper(this[0]);
|
|
565
579
|
let imin = 0;
|
|
566
580
|
for (let i = 0; i < this.length; i++) {
|
|
567
|
-
const value =
|
|
581
|
+
const value = mapper(this[i]);
|
|
568
582
|
if (value < min) {
|
|
569
583
|
min = value;
|
|
570
584
|
imin = i;
|
|
@@ -572,14 +586,14 @@ if (!globalThis.my_prototype_defined) {
|
|
|
572
586
|
}
|
|
573
587
|
return this[imin];
|
|
574
588
|
},
|
|
575
|
-
unique(
|
|
576
|
-
if (!
|
|
589
|
+
unique(mapper) {
|
|
590
|
+
if (!mapper)
|
|
577
591
|
return [...new Set(this)];
|
|
578
|
-
if (is_key_type(
|
|
579
|
-
|
|
592
|
+
if (is_key_type(mapper))
|
|
593
|
+
mapper = build_mapper(mapper);
|
|
580
594
|
let map = new Map();
|
|
581
595
|
for (const x of this)
|
|
582
|
-
map.set(
|
|
596
|
+
map.set(mapper(x), x);
|
|
583
597
|
return [...map.values()];
|
|
584
598
|
},
|
|
585
599
|
join_lines(append = true) {
|
package/server.d.ts
CHANGED
|
@@ -3,6 +3,15 @@ import { type Http2SecureServer, type IncomingHttpHeaders as IncomingHttp2Header
|
|
|
3
3
|
import type { Duplex } from 'stream';
|
|
4
4
|
import type { WebSocketServer } from 'ws';
|
|
5
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,15 +22,6 @@ declare module 'koa' {
|
|
|
13
22
|
compress: boolean;
|
|
14
23
|
}
|
|
15
24
|
}
|
|
16
|
-
import { Remote, type RequestOptions, type RawResponse } from './net.ts';
|
|
17
|
-
declare module 'http' {
|
|
18
|
-
interface IncomingMessage {
|
|
19
|
-
body?: Buffer;
|
|
20
|
-
}
|
|
21
|
-
interface ServerResponse {
|
|
22
|
-
body?: Buffer;
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
25
|
export declare class Server {
|
|
26
26
|
/** proxy 时需要丢弃的 resposne headers */
|
|
27
27
|
static drop_response_headers: Set<string>;
|
|
@@ -33,9 +33,10 @@ export declare class Server {
|
|
|
33
33
|
};
|
|
34
34
|
/** sea 下最后修改时间,用于 http 资源缓存 */
|
|
35
35
|
last_modified_str?: string;
|
|
36
|
-
js_exts: Set<string>;
|
|
37
|
-
|
|
38
|
-
|
|
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>;
|
|
39
40
|
app: Koa;
|
|
40
41
|
handler: ReturnType<Koa['callback']>;
|
|
41
42
|
/** 启用 http server */
|
package/server.js
CHANGED
|
@@ -37,9 +37,10 @@ export class Server {
|
|
|
37
37
|
};
|
|
38
38
|
/** sea 下最后修改时间,用于 http 资源缓存 */
|
|
39
39
|
last_modified_str;
|
|
40
|
-
js_exts = new Set(['.js', '.mjs', '.cjs']);
|
|
41
|
-
|
|
42
|
-
|
|
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']);
|
|
43
44
|
app;
|
|
44
45
|
handler;
|
|
45
46
|
/** 启用 http server */
|
|
@@ -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()} `;
|
|
@@ -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;
|
|
@@ -652,8 +655,9 @@ export class Server {
|
|
|
652
655
|
response.set('content-disposition', `attachment; filename="${encodeURIComponent(fp.fname)}"`);
|
|
653
656
|
if (!response.get('content-type')) {
|
|
654
657
|
const { fext } = fp;
|
|
655
|
-
response.set('content-type', (
|
|
656
|
-
|
|
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'));
|
|
657
661
|
}
|
|
658
662
|
const modified_since_str = request.get('if-modified-since');
|
|
659
663
|
if ((method === 'GET' || method === 'HEAD') && modified_since_str === last_modified_str) {
|
package/stdin.d.ts
ADDED
package/stdin.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import process from 'process';
|
|
2
|
+
/** 监听终端按键 (输入),并调用 key_processor 处理
|
|
3
|
+
- on_key: 按键处理函数
|
|
4
|
+
- on_exit?: ctrl + c 会退出进程,可加入退出前自定义处理逻辑 */
|
|
5
|
+
export function process_stdin(on_key, on_exit) {
|
|
6
|
+
// https://stackoverflow.com/a/12506613/7609214
|
|
7
|
+
let { stdin } = process;
|
|
8
|
+
stdin.setRawMode(true);
|
|
9
|
+
stdin.resume();
|
|
10
|
+
stdin.setEncoding('utf-8');
|
|
11
|
+
// on any data into stdin
|
|
12
|
+
stdin.on('data', async (key) => {
|
|
13
|
+
// ctrl-c ( end of text )
|
|
14
|
+
if (key === '\u0003') {
|
|
15
|
+
await on_exit?.();
|
|
16
|
+
process.exit();
|
|
17
|
+
}
|
|
18
|
+
// write the key to stdout all normal like
|
|
19
|
+
console.log(key);
|
|
20
|
+
await on_key(key);
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=stdin.js.map
|
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;
|