@difizen/libro-search 0.0.2-alpha.0
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/LICENSE +21 -0
- package/README.md +1 -0
- package/es/abstract-search-provider.d.ts +113 -0
- package/es/abstract-search-provider.d.ts.map +1 -0
- package/es/abstract-search-provider.js +126 -0
- package/es/index.d.ts +10 -0
- package/es/index.d.ts.map +1 -0
- package/es/index.js +9 -0
- package/es/index.less +107 -0
- package/es/libro-cell-search-provider.d.ts +10 -0
- package/es/libro-cell-search-provider.d.ts.map +1 -0
- package/es/libro-cell-search-provider.js +54 -0
- package/es/libro-search-engine-html.d.ts +3 -0
- package/es/libro-search-engine-html.d.ts.map +1 -0
- package/es/libro-search-engine-html.js +59 -0
- package/es/libro-search-engine-text.d.ts +10 -0
- package/es/libro-search-engine-text.d.ts.map +1 -0
- package/es/libro-search-engine-text.js +32 -0
- package/es/libro-search-generic-provider.d.ts +107 -0
- package/es/libro-search-generic-provider.d.ts.map +1 -0
- package/es/libro-search-generic-provider.js +467 -0
- package/es/libro-search-manager.d.ts +22 -0
- package/es/libro-search-manager.d.ts.map +1 -0
- package/es/libro-search-manager.js +102 -0
- package/es/libro-search-model.d.ts +111 -0
- package/es/libro-search-model.d.ts.map +1 -0
- package/es/libro-search-model.js +395 -0
- package/es/libro-search-protocol.d.ts +176 -0
- package/es/libro-search-protocol.d.ts.map +1 -0
- package/es/libro-search-protocol.js +36 -0
- package/es/libro-search-provider.d.ts +138 -0
- package/es/libro-search-provider.d.ts.map +1 -0
- package/es/libro-search-provider.js +759 -0
- package/es/libro-search-utils.d.ts +25 -0
- package/es/libro-search-utils.d.ts.map +1 -0
- package/es/libro-search-utils.js +85 -0
- package/es/libro-search-view.d.ts +59 -0
- package/es/libro-search-view.d.ts.map +1 -0
- package/es/libro-search-view.js +455 -0
- package/es/module.d.ts +4 -0
- package/es/module.d.ts.map +1 -0
- package/es/module.js +35 -0
- package/package.json +66 -0
- package/src/abstract-search-provider.ts +160 -0
- package/src/index.less +107 -0
- package/src/index.ts +9 -0
- package/src/libro-cell-search-provider.ts +39 -0
- package/src/libro-search-engine-html.ts +74 -0
- package/src/libro-search-engine-text.ts +34 -0
- package/src/libro-search-generic-provider.ts +303 -0
- package/src/libro-search-manager.ts +86 -0
- package/src/libro-search-model.ts +266 -0
- package/src/libro-search-protocol.ts +209 -0
- package/src/libro-search-provider.ts +507 -0
- package/src/libro-search-utils.spec.ts +37 -0
- package/src/libro-search-utils.ts +83 -0
- package/src/libro-search-view.tsx +404 -0
- package/src/module.ts +59 -0
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import 'reflect-metadata';
|
|
2
|
+
import assert from 'assert';
|
|
3
|
+
import { LibroSearchUtils } from './libro-search-utils.js';
|
|
4
|
+
import type { SearchMatch } from './libro-search-protocol.js';
|
|
5
|
+
|
|
6
|
+
describe('libro search utils', () => {
|
|
7
|
+
it('#find next', () => {
|
|
8
|
+
/**
|
|
9
|
+
* 示例文本:1010011000111
|
|
10
|
+
* 查找文本:0
|
|
11
|
+
*/
|
|
12
|
+
const instance = new LibroSearchUtils();
|
|
13
|
+
const matches: SearchMatch[] = [
|
|
14
|
+
{ position: 1, text: '0' }, // 0
|
|
15
|
+
{ position: 3, text: '0' }, // 1
|
|
16
|
+
{ position: 4, text: '0' }, // 2
|
|
17
|
+
{ position: 7, text: '0' }, // 3
|
|
18
|
+
{ position: 8, text: '0' }, // 4
|
|
19
|
+
{ position: 9, text: '0' }, // 5
|
|
20
|
+
];
|
|
21
|
+
assert(instance.findNext(matches, 0) === 0);
|
|
22
|
+
assert(instance.findNext(matches, 1) === 0);
|
|
23
|
+
assert(instance.findNext(matches, 2) === 1);
|
|
24
|
+
assert(instance.findNext(matches, 4) === 2);
|
|
25
|
+
assert(instance.findNext(matches, 5) === 3);
|
|
26
|
+
assert(instance.findNext(matches, 9) === 5);
|
|
27
|
+
assert(instance.findNext(matches, 10) === undefined);
|
|
28
|
+
assert(instance.findNext(matches, 5, 3) === 3);
|
|
29
|
+
});
|
|
30
|
+
it('#find next 0', () => {
|
|
31
|
+
const instance = new LibroSearchUtils();
|
|
32
|
+
const matches: SearchMatch[] = [
|
|
33
|
+
{ position: 7, text: '0' }, // 0
|
|
34
|
+
];
|
|
35
|
+
assert(instance.findNext(matches, 0, 0, 0) === 0);
|
|
36
|
+
});
|
|
37
|
+
});
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { singleton } from '@difizen/mana-app';
|
|
2
|
+
|
|
3
|
+
import type { SearchMatch } from './libro-search-protocol.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Search Utils
|
|
7
|
+
*/
|
|
8
|
+
@singleton()
|
|
9
|
+
export class LibroSearchUtils {
|
|
10
|
+
/**
|
|
11
|
+
* 查找当前 position 最靠近的匹配项,适用于文本中查找
|
|
12
|
+
* @param matches 匹配项列表,通常来自一个 cell 或者 output
|
|
13
|
+
* @param position 查找起点,当前位置的文本偏移量
|
|
14
|
+
* @param lower 查找范围下限,值为匹配列表 index
|
|
15
|
+
* @param higher 查找范围上限,值为匹配列表 index
|
|
16
|
+
* @returns 下一个匹配项的 index,如果没有找到则返回 null
|
|
17
|
+
*/
|
|
18
|
+
findNext(
|
|
19
|
+
matches: SearchMatch[],
|
|
20
|
+
position: number,
|
|
21
|
+
lower = 0,
|
|
22
|
+
higher = Infinity,
|
|
23
|
+
): number | undefined {
|
|
24
|
+
let higherBound = Math.min(matches.length - 1, higher);
|
|
25
|
+
let lowerBound = lower;
|
|
26
|
+
while (lowerBound <= higherBound) {
|
|
27
|
+
// 取中间匹配项
|
|
28
|
+
const middle = Math.floor(0.5 * (lowerBound + higherBound));
|
|
29
|
+
// 中间匹配项的文本偏移量
|
|
30
|
+
const currentPosition = matches[middle].position;
|
|
31
|
+
|
|
32
|
+
if (currentPosition < position) {
|
|
33
|
+
// 中间值的偏移量小于查找起点
|
|
34
|
+
lowerBound = middle + 1; // 场景1:查找范围下限太小,需要增大,更新中间值
|
|
35
|
+
if (lowerBound < matches.length && matches[lowerBound].position > position) {
|
|
36
|
+
return lowerBound; // 场景2:下限已经是要查找的前一个匹配项了,下个匹配项的偏移量会大于查找起点。
|
|
37
|
+
}
|
|
38
|
+
} else if (currentPosition > position) {
|
|
39
|
+
// 中间值的偏移量大于查找起点
|
|
40
|
+
higherBound = middle - 1; // 场景1:查找范围上限太大,需要减小,更新中间值
|
|
41
|
+
if (higherBound > 0 && matches[higherBound].position < position) {
|
|
42
|
+
return middle; // 场景2:上限已经是要查找的后一个匹配项了,下个匹配项的偏移量会小于查找起点。
|
|
43
|
+
}
|
|
44
|
+
} else {
|
|
45
|
+
return middle; // 直接命中查找起点,选择此匹配项
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
// 查找起点不在 match 范围内,要么在范围前面,要么在范围后面
|
|
49
|
+
const first = lowerBound > 0 ? lowerBound - 1 : 0;
|
|
50
|
+
const match = matches[first];
|
|
51
|
+
return match.position >= position ? first : undefined;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Build the regular expression to use for searching.
|
|
56
|
+
*
|
|
57
|
+
* @param queryString Query string
|
|
58
|
+
* @param caseSensitive Whether the search is case sensitive or not
|
|
59
|
+
* @param regex Whether the expression is a regular expression
|
|
60
|
+
* @returns The regular expression to use
|
|
61
|
+
*/
|
|
62
|
+
parseQuery(
|
|
63
|
+
queryString: string,
|
|
64
|
+
caseSensitive: boolean,
|
|
65
|
+
regex: boolean,
|
|
66
|
+
): RegExp | undefined {
|
|
67
|
+
const flag = caseSensitive ? 'g' : 'gi';
|
|
68
|
+
// escape regex characters in query if its a string search
|
|
69
|
+
const queryText = regex
|
|
70
|
+
? queryString
|
|
71
|
+
: queryString.replace(/[-[\]/{}()*+?.\\^$|]/g, '\\$&');
|
|
72
|
+
const ret = new RegExp(queryText, flag);
|
|
73
|
+
|
|
74
|
+
// If the empty string is hit, the search logic will freeze the browser tab
|
|
75
|
+
// Trying /^/ or /$/ on the codemirror search demo, does not find anything.
|
|
76
|
+
// So this is a limitation of the editor.
|
|
77
|
+
if (ret.test('')) {
|
|
78
|
+
return undefined;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return ret;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
@@ -0,0 +1,404 @@
|
|
|
1
|
+
import {
|
|
2
|
+
RightOutlined,
|
|
3
|
+
ArrowUpOutlined,
|
|
4
|
+
ArrowDownOutlined,
|
|
5
|
+
EllipsisOutlined,
|
|
6
|
+
CloseOutlined,
|
|
7
|
+
createFromIconfontCN,
|
|
8
|
+
} from '@ant-design/icons';
|
|
9
|
+
import type { LibroView } from '@difizen/libro-core';
|
|
10
|
+
import { LirboContextKey } from '@difizen/libro-core';
|
|
11
|
+
import { prop, useInject } from '@difizen/mana-app';
|
|
12
|
+
import { BaseView, view, ViewInstance } from '@difizen/mana-app';
|
|
13
|
+
import { inject, transient } from '@difizen/mana-app';
|
|
14
|
+
import { l10n } from '@difizen/mana-l10n';
|
|
15
|
+
import { Input, Button, Checkbox, Tag } from 'antd';
|
|
16
|
+
import type { CheckboxChangeEvent } from 'antd/es/checkbox';
|
|
17
|
+
import type { InputRef } from 'antd/es/input';
|
|
18
|
+
import classnames from 'classnames';
|
|
19
|
+
import type { FC } from 'react';
|
|
20
|
+
import { forwardRef, useEffect, useRef } from 'react';
|
|
21
|
+
|
|
22
|
+
import type { LibroSearchProvider } from './libro-search-provider.js';
|
|
23
|
+
import { LibroSearchProviderFactory } from './libro-search-provider.js';
|
|
24
|
+
import { LibroSearchUtils } from './libro-search-utils.js';
|
|
25
|
+
|
|
26
|
+
const IconFont = createFromIconfontCN({
|
|
27
|
+
scriptUrl: '//at.alicdn.com/t/a/font_3381673_65wfctnq7rt.js',
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
export const ReplaceToggle: FC = () => {
|
|
31
|
+
const instance = useInject<LibroSearchView>(ViewInstance);
|
|
32
|
+
return (
|
|
33
|
+
<div className="libro-search-replace-toggle" onClick={instance.toggleReplace}>
|
|
34
|
+
<RightOutlined
|
|
35
|
+
className={classnames({
|
|
36
|
+
'libro-search-replace-toggle-icon': true,
|
|
37
|
+
'libro-search-replace-toggle-replace-icon': instance.replaceVisible,
|
|
38
|
+
})}
|
|
39
|
+
/>
|
|
40
|
+
</div>
|
|
41
|
+
);
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export const SearchIndex: FC = () => {
|
|
45
|
+
const instance = useInject<LibroSearchView>(ViewInstance);
|
|
46
|
+
return (
|
|
47
|
+
<div className="libro-search-index">
|
|
48
|
+
{instance.currentMatchIndex ?? '-'}/{instance.matchesCount ?? '-'}
|
|
49
|
+
</div>
|
|
50
|
+
);
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
export const SearchContent: FC = () => {
|
|
54
|
+
const instance = useInject<LibroSearchView>(ViewInstance);
|
|
55
|
+
const findInputRef = useRef<InputRef>(null);
|
|
56
|
+
useEffect(() => {
|
|
57
|
+
if (findInputRef.current) {
|
|
58
|
+
findInputRef.current.focus();
|
|
59
|
+
}
|
|
60
|
+
instance.findInputRef = findInputRef;
|
|
61
|
+
if (instance.container?.current) {
|
|
62
|
+
const container = instance.container.current;
|
|
63
|
+
container.addEventListener('keydown', instance.handleKeydown);
|
|
64
|
+
return () => {
|
|
65
|
+
container.removeEventListener('keydown', instance.handleKeydown);
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
return;
|
|
69
|
+
}, [instance]);
|
|
70
|
+
return (
|
|
71
|
+
<div
|
|
72
|
+
className="libro-search-content"
|
|
73
|
+
style={{ height: `${instance.contentHeight}px` }}
|
|
74
|
+
>
|
|
75
|
+
<ReplaceToggle />
|
|
76
|
+
<table className="libro-search-input-area">
|
|
77
|
+
<tr className="libro-search-row">
|
|
78
|
+
<td className="libro-search-input">
|
|
79
|
+
<Input
|
|
80
|
+
ref={findInputRef}
|
|
81
|
+
value={instance.findStr}
|
|
82
|
+
onChange={instance.handleFindChange}
|
|
83
|
+
size="small"
|
|
84
|
+
suffix={
|
|
85
|
+
<span className="libro-search-input-suffix">
|
|
86
|
+
<IconFont
|
|
87
|
+
className={classnames({
|
|
88
|
+
'libro-search-input-suffix-active': instance.caseSensitive,
|
|
89
|
+
})}
|
|
90
|
+
onClick={instance.toggleCaseSensitive}
|
|
91
|
+
type="icon-Aa"
|
|
92
|
+
/>
|
|
93
|
+
|
|
94
|
+
<IconFont
|
|
95
|
+
className={classnames({
|
|
96
|
+
'libro-search-input-suffix-active': instance.useRegex,
|
|
97
|
+
})}
|
|
98
|
+
onClick={instance.toggleUseRegex}
|
|
99
|
+
type="icon-zhengzeshi"
|
|
100
|
+
/>
|
|
101
|
+
</span>
|
|
102
|
+
}
|
|
103
|
+
/>
|
|
104
|
+
</td>
|
|
105
|
+
<td className="libro-search-action">
|
|
106
|
+
<SearchIndex />
|
|
107
|
+
<div>
|
|
108
|
+
<Button
|
|
109
|
+
onClick={instance.previous}
|
|
110
|
+
icon={<ArrowUpOutlined />}
|
|
111
|
+
size="small"
|
|
112
|
+
/>
|
|
113
|
+
<Button
|
|
114
|
+
onClick={instance.next}
|
|
115
|
+
icon={<ArrowDownOutlined />}
|
|
116
|
+
size="small"
|
|
117
|
+
/>
|
|
118
|
+
<Button
|
|
119
|
+
onClick={instance.toggleSetting}
|
|
120
|
+
icon={<EllipsisOutlined />}
|
|
121
|
+
size="small"
|
|
122
|
+
/>
|
|
123
|
+
<Button onClick={instance.hide} icon={<CloseOutlined />} size="small" />
|
|
124
|
+
</div>
|
|
125
|
+
</td>
|
|
126
|
+
</tr>
|
|
127
|
+
{instance.replaceVisible && (
|
|
128
|
+
<tr className="libro-search-row">
|
|
129
|
+
<td className="libro-search-input">
|
|
130
|
+
<Input
|
|
131
|
+
value={instance.replaceStr}
|
|
132
|
+
onChange={instance.handleReplaceChange}
|
|
133
|
+
size="small"
|
|
134
|
+
/>
|
|
135
|
+
</td>
|
|
136
|
+
<td className="libro-search-action">
|
|
137
|
+
<div>
|
|
138
|
+
<Button
|
|
139
|
+
onClick={instance.replace}
|
|
140
|
+
icon={<IconFont type="icon-zifuchuantihuan_2" />}
|
|
141
|
+
size="small"
|
|
142
|
+
/>
|
|
143
|
+
|
|
144
|
+
<Button
|
|
145
|
+
onClick={instance.replaceAll}
|
|
146
|
+
icon={<IconFont type="icon-tihuantupian" />}
|
|
147
|
+
size="small"
|
|
148
|
+
/>
|
|
149
|
+
</div>
|
|
150
|
+
</td>
|
|
151
|
+
</tr>
|
|
152
|
+
)}
|
|
153
|
+
|
|
154
|
+
{instance.settingVisible && (
|
|
155
|
+
<div className="libro-search-row">
|
|
156
|
+
<Checkbox
|
|
157
|
+
checked={instance.searchProvider?.searchCellOutput}
|
|
158
|
+
onChange={instance.searchCellOutputChange}
|
|
159
|
+
disabled={instance.replaceVisible}
|
|
160
|
+
>
|
|
161
|
+
{l10n.t('在 output 中查找')}
|
|
162
|
+
</Checkbox>
|
|
163
|
+
{instance.replaceVisible && (
|
|
164
|
+
<Tag color="warning">{l10n.t('替换功能不能在 output 生效')}</Tag>
|
|
165
|
+
)}
|
|
166
|
+
</div>
|
|
167
|
+
)}
|
|
168
|
+
</table>
|
|
169
|
+
</div>
|
|
170
|
+
);
|
|
171
|
+
};
|
|
172
|
+
// TODO: 更改图标
|
|
173
|
+
export const SearchComponent = forwardRef<HTMLDivElement>(function SearchComponent(
|
|
174
|
+
_props: { top?: number },
|
|
175
|
+
ref,
|
|
176
|
+
) {
|
|
177
|
+
const instance = useInject<LibroSearchView>(ViewInstance);
|
|
178
|
+
return (
|
|
179
|
+
<div
|
|
180
|
+
tabIndex={1}
|
|
181
|
+
className="libro-search-overlay"
|
|
182
|
+
style={{ top: instance.getHeaderHeight() }}
|
|
183
|
+
ref={ref}
|
|
184
|
+
onBlur={(e) => instance.onBlur(e)}
|
|
185
|
+
onFocus={instance.onFocus}
|
|
186
|
+
>
|
|
187
|
+
{instance.searchVisible && <SearchContent />}
|
|
188
|
+
</div>
|
|
189
|
+
);
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
@transient()
|
|
193
|
+
@view('libro-search-view')
|
|
194
|
+
export class LibroSearchView extends BaseView {
|
|
195
|
+
findInputRef?: React.RefObject<InputRef> | null;
|
|
196
|
+
contextKey: LirboContextKey;
|
|
197
|
+
utils: LibroSearchUtils;
|
|
198
|
+
searchProviderFactory: LibroSearchProviderFactory;
|
|
199
|
+
|
|
200
|
+
constructor(
|
|
201
|
+
@inject(LirboContextKey) contextKey: LirboContextKey,
|
|
202
|
+
@inject(LibroSearchUtils) utils: LibroSearchUtils,
|
|
203
|
+
@inject(LibroSearchProviderFactory)
|
|
204
|
+
searchProviderFactory: LibroSearchProviderFactory,
|
|
205
|
+
) {
|
|
206
|
+
super();
|
|
207
|
+
this.contextKey = contextKey;
|
|
208
|
+
this.utils = utils;
|
|
209
|
+
this.searchProviderFactory = searchProviderFactory;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
protected _libro?: LibroView;
|
|
213
|
+
|
|
214
|
+
get libro(): LibroView | undefined {
|
|
215
|
+
return this._libro;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
set libro(value: LibroView | undefined) {
|
|
219
|
+
this._libro = value;
|
|
220
|
+
this.searchProvider = this.searchProviderFactory({ view: this.libro! });
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
@prop() searchProvider?: LibroSearchProvider;
|
|
224
|
+
@prop() searchVisible = false;
|
|
225
|
+
get replaceVisible(): boolean {
|
|
226
|
+
return this.searchProvider?.replaceMode ?? false;
|
|
227
|
+
}
|
|
228
|
+
@prop() settingVisible = false;
|
|
229
|
+
@prop() findStr?: string | undefined = undefined;
|
|
230
|
+
@prop() lastSearch?: string | undefined = undefined;
|
|
231
|
+
@prop() replaceStr = '';
|
|
232
|
+
@prop() caseSensitive = false;
|
|
233
|
+
@prop() useRegex = false;
|
|
234
|
+
|
|
235
|
+
override view = SearchComponent;
|
|
236
|
+
|
|
237
|
+
get contentHeight() {
|
|
238
|
+
let height = 32;
|
|
239
|
+
if (this.replaceVisible) {
|
|
240
|
+
height += 32;
|
|
241
|
+
}
|
|
242
|
+
if (this.settingVisible) {
|
|
243
|
+
height += 32;
|
|
244
|
+
}
|
|
245
|
+
return height;
|
|
246
|
+
}
|
|
247
|
+
get currentMatchIndex() {
|
|
248
|
+
const current = this.searchProvider?.currentMatchIndex;
|
|
249
|
+
if (current !== undefined) {
|
|
250
|
+
return current + 1;
|
|
251
|
+
}
|
|
252
|
+
return undefined;
|
|
253
|
+
}
|
|
254
|
+
get matchesCount() {
|
|
255
|
+
return this.searchProvider?.matchesCount;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
onviewWillUnmount = () => {
|
|
259
|
+
this.searchProvider?.endQuery();
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
show = () => {
|
|
263
|
+
this.contextKey.disableCommandMode();
|
|
264
|
+
this.searchVisible = true;
|
|
265
|
+
this.initialQuery();
|
|
266
|
+
this.findInputRef?.current?.focus();
|
|
267
|
+
};
|
|
268
|
+
hide = () => {
|
|
269
|
+
this.searchVisible = false;
|
|
270
|
+
this.contextKey.enableCommandMode();
|
|
271
|
+
this.searchProvider?.endQuery();
|
|
272
|
+
this.libro?.focus();
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
onFocus = () => {
|
|
276
|
+
this.contextKey.disableCommandMode();
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
onBlur = (e: React.FocusEvent<HTMLDivElement>) => {
|
|
280
|
+
if (this.libro?.container?.current?.contains(e.relatedTarget)) {
|
|
281
|
+
this.contextKey.enableCommandMode();
|
|
282
|
+
}
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
search = (hightlightNext = true) => {
|
|
286
|
+
if (this.searchProvider) {
|
|
287
|
+
this.lastSearch = this.findStr;
|
|
288
|
+
const query = this.utils.parseQuery(
|
|
289
|
+
this.findStr || '',
|
|
290
|
+
this.caseSensitive,
|
|
291
|
+
this.useRegex,
|
|
292
|
+
);
|
|
293
|
+
if (query) {
|
|
294
|
+
this.searchProvider?.startQuery(query, undefined, hightlightNext);
|
|
295
|
+
} else {
|
|
296
|
+
this.searchProvider?.endQuery();
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
};
|
|
300
|
+
toggleReplace = () => {
|
|
301
|
+
if (this.searchProvider) {
|
|
302
|
+
this.searchProvider.replaceMode = !this.replaceVisible;
|
|
303
|
+
this.search();
|
|
304
|
+
}
|
|
305
|
+
};
|
|
306
|
+
|
|
307
|
+
toggleSetting = () => {
|
|
308
|
+
this.settingVisible = !this.settingVisible;
|
|
309
|
+
this.search();
|
|
310
|
+
};
|
|
311
|
+
|
|
312
|
+
toggleCaseSensitive = () => {
|
|
313
|
+
this.caseSensitive = !this.caseSensitive;
|
|
314
|
+
this.search();
|
|
315
|
+
};
|
|
316
|
+
|
|
317
|
+
toggleUseRegex = () => {
|
|
318
|
+
this.useRegex = !this.useRegex;
|
|
319
|
+
};
|
|
320
|
+
|
|
321
|
+
next = () => {
|
|
322
|
+
this.searchProvider?.highlightNext();
|
|
323
|
+
};
|
|
324
|
+
|
|
325
|
+
previous = () => {
|
|
326
|
+
this.searchProvider?.highlightPrevious();
|
|
327
|
+
};
|
|
328
|
+
|
|
329
|
+
searchCellOutputChange = (e: CheckboxChangeEvent) => {
|
|
330
|
+
if (this.searchProvider) {
|
|
331
|
+
this.searchProvider.updateSearchCellOutput(e.target.checked);
|
|
332
|
+
}
|
|
333
|
+
};
|
|
334
|
+
|
|
335
|
+
replace = () => {
|
|
336
|
+
this.searchProvider?.replaceCurrentMatch(this.replaceStr);
|
|
337
|
+
};
|
|
338
|
+
replaceAll = () => {
|
|
339
|
+
this.searchProvider?.replaceAllMatches(this.replaceStr);
|
|
340
|
+
};
|
|
341
|
+
initialQuery = () => {
|
|
342
|
+
const init = this.searchProvider?.getInitialQuery();
|
|
343
|
+
if (init) {
|
|
344
|
+
this.findStr = init;
|
|
345
|
+
this.search(false);
|
|
346
|
+
}
|
|
347
|
+
};
|
|
348
|
+
getHeaderHeight = () => {
|
|
349
|
+
let height = 32;
|
|
350
|
+
const container = this.libro?.container?.current;
|
|
351
|
+
if (container) {
|
|
352
|
+
const elements = container.getElementsByClassName('libro-view-top');
|
|
353
|
+
if (elements.length > 0) {
|
|
354
|
+
height = elements[0].clientHeight;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
return height;
|
|
358
|
+
};
|
|
359
|
+
|
|
360
|
+
nextMatch = (reverse: boolean) => {
|
|
361
|
+
if (reverse) {
|
|
362
|
+
this.searchProvider?.highlightPrevious();
|
|
363
|
+
} else {
|
|
364
|
+
this.searchProvider?.highlightNext();
|
|
365
|
+
}
|
|
366
|
+
};
|
|
367
|
+
|
|
368
|
+
handleKeydown = (e: KeyboardEvent) => {
|
|
369
|
+
if (e.key === 'Escape') {
|
|
370
|
+
e.stopPropagation();
|
|
371
|
+
e.preventDefault();
|
|
372
|
+
this.hide();
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
if (e.key === 'Enter') {
|
|
376
|
+
e.stopPropagation();
|
|
377
|
+
e.preventDefault();
|
|
378
|
+
if (this.findStr !== this.lastSearch) {
|
|
379
|
+
this.search();
|
|
380
|
+
return;
|
|
381
|
+
}
|
|
382
|
+
if (this.matchesCount !== undefined && this.matchesCount > 0) {
|
|
383
|
+
this.nextMatch(e.shiftKey);
|
|
384
|
+
} else {
|
|
385
|
+
this.search();
|
|
386
|
+
}
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
};
|
|
390
|
+
|
|
391
|
+
protected doHandleFindChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
392
|
+
this.findStr = e.target.value;
|
|
393
|
+
};
|
|
394
|
+
|
|
395
|
+
handleFindChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
396
|
+
this.findStr = e.target.value;
|
|
397
|
+
if (this.findStr !== this.lastSearch) {
|
|
398
|
+
this.search(false);
|
|
399
|
+
}
|
|
400
|
+
};
|
|
401
|
+
handleReplaceChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
402
|
+
this.replaceStr = e.target.value;
|
|
403
|
+
};
|
|
404
|
+
}
|
package/src/module.ts
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { ManaModule } from '@difizen/mana-app';
|
|
2
|
+
|
|
3
|
+
import './index.less';
|
|
4
|
+
import { LibroCellSearchProvider } from './libro-cell-search-provider.js';
|
|
5
|
+
import {
|
|
6
|
+
GenericSearchProvider,
|
|
7
|
+
GenericSearchProviderFactory,
|
|
8
|
+
} from './libro-search-generic-provider.js';
|
|
9
|
+
import { LibroSearchManager } from './libro-search-manager.js';
|
|
10
|
+
import { LibroSearchModel } from './libro-search-model.js';
|
|
11
|
+
import {
|
|
12
|
+
CellSearchProviderContribution,
|
|
13
|
+
SearchProviderOption,
|
|
14
|
+
} from './libro-search-protocol.js';
|
|
15
|
+
import {
|
|
16
|
+
LibroSearchProvider,
|
|
17
|
+
LibroSearchProviderFactory,
|
|
18
|
+
} from './libro-search-provider.js';
|
|
19
|
+
import { LibroSearchUtils } from './libro-search-utils.js';
|
|
20
|
+
import { LibroSearchView } from './libro-search-view.js';
|
|
21
|
+
|
|
22
|
+
export const LibroSearchModule = ManaModule.create()
|
|
23
|
+
.register(
|
|
24
|
+
LibroSearchUtils,
|
|
25
|
+
LibroSearchManager,
|
|
26
|
+
LibroSearchView,
|
|
27
|
+
GenericSearchProvider,
|
|
28
|
+
LibroSearchModel,
|
|
29
|
+
LibroSearchProvider,
|
|
30
|
+
LibroCellSearchProvider,
|
|
31
|
+
LibroSearchUtils,
|
|
32
|
+
{
|
|
33
|
+
token: GenericSearchProviderFactory,
|
|
34
|
+
useFactory: (ctx) => {
|
|
35
|
+
return (option) => {
|
|
36
|
+
const child = ctx.container.createChild();
|
|
37
|
+
child.register({
|
|
38
|
+
token: SearchProviderOption,
|
|
39
|
+
useValue: option,
|
|
40
|
+
});
|
|
41
|
+
return child.get(GenericSearchProvider);
|
|
42
|
+
};
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
token: LibroSearchProviderFactory,
|
|
47
|
+
useFactory: (ctx) => {
|
|
48
|
+
return (option) => {
|
|
49
|
+
const child = ctx.container.createChild();
|
|
50
|
+
child.register({
|
|
51
|
+
token: SearchProviderOption,
|
|
52
|
+
useValue: option,
|
|
53
|
+
});
|
|
54
|
+
return child.get(LibroSearchProvider);
|
|
55
|
+
};
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
)
|
|
59
|
+
.contribution(CellSearchProviderContribution);
|