@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
package/es/module.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { ManaModule } from '@difizen/mana-app';
|
|
2
|
+
import "./index.less";
|
|
3
|
+
import { LibroCellSearchProvider } from "./libro-cell-search-provider.js";
|
|
4
|
+
import { GenericSearchProvider, GenericSearchProviderFactory } from "./libro-search-generic-provider.js";
|
|
5
|
+
import { LibroSearchManager } from "./libro-search-manager.js";
|
|
6
|
+
import { LibroSearchModel } from "./libro-search-model.js";
|
|
7
|
+
import { CellSearchProviderContribution, SearchProviderOption } from "./libro-search-protocol.js";
|
|
8
|
+
import { LibroSearchProvider, LibroSearchProviderFactory } from "./libro-search-provider.js";
|
|
9
|
+
import { LibroSearchUtils } from "./libro-search-utils.js";
|
|
10
|
+
import { LibroSearchView } from "./libro-search-view.js";
|
|
11
|
+
export var LibroSearchModule = ManaModule.create().register(LibroSearchUtils, LibroSearchManager, LibroSearchView, GenericSearchProvider, LibroSearchModel, LibroSearchProvider, LibroCellSearchProvider, LibroSearchUtils, {
|
|
12
|
+
token: GenericSearchProviderFactory,
|
|
13
|
+
useFactory: function useFactory(ctx) {
|
|
14
|
+
return function (option) {
|
|
15
|
+
var child = ctx.container.createChild();
|
|
16
|
+
child.register({
|
|
17
|
+
token: SearchProviderOption,
|
|
18
|
+
useValue: option
|
|
19
|
+
});
|
|
20
|
+
return child.get(GenericSearchProvider);
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
}, {
|
|
24
|
+
token: LibroSearchProviderFactory,
|
|
25
|
+
useFactory: function useFactory(ctx) {
|
|
26
|
+
return function (option) {
|
|
27
|
+
var child = ctx.container.createChild();
|
|
28
|
+
child.register({
|
|
29
|
+
token: SearchProviderOption,
|
|
30
|
+
useValue: option
|
|
31
|
+
});
|
|
32
|
+
return child.get(LibroSearchProvider);
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
}).contribution(CellSearchProviderContribution);
|
package/package.json
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@difizen/libro-search",
|
|
3
|
+
"version": "0.0.2-alpha.0",
|
|
4
|
+
"description": "",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"libro",
|
|
7
|
+
"notebook"
|
|
8
|
+
],
|
|
9
|
+
"repository": "git@github.com:difizen/libro.git",
|
|
10
|
+
"license": "MIT",
|
|
11
|
+
"type": "module",
|
|
12
|
+
"exports": {
|
|
13
|
+
".": {
|
|
14
|
+
"typings": "./es/index.d.ts",
|
|
15
|
+
"default": "./es/index.js"
|
|
16
|
+
},
|
|
17
|
+
"./mock": {
|
|
18
|
+
"typings": "./es/mock/index.d.ts",
|
|
19
|
+
"default": "./es/mock/index.js"
|
|
20
|
+
},
|
|
21
|
+
"./es/mock": {
|
|
22
|
+
"typings": "./es/mock/index.d.ts",
|
|
23
|
+
"default": "./es/mock/index.js"
|
|
24
|
+
},
|
|
25
|
+
"./package.json": "./package.json"
|
|
26
|
+
},
|
|
27
|
+
"main": "es/index.js",
|
|
28
|
+
"module": "es/index.js",
|
|
29
|
+
"typings": "es/index.d.ts",
|
|
30
|
+
"files": [
|
|
31
|
+
"lib",
|
|
32
|
+
"es",
|
|
33
|
+
"src"
|
|
34
|
+
],
|
|
35
|
+
"dependencies": {
|
|
36
|
+
"@difizen/mana-app": "alpha",
|
|
37
|
+
"@difizen/mana-l10n": "alpha",
|
|
38
|
+
"@ant-design/icons": "^5.1.0",
|
|
39
|
+
"@difizen/libro-common": "^0.0.2-alpha.0",
|
|
40
|
+
"@difizen/libro-core": "^0.0.2-alpha.0",
|
|
41
|
+
"@types/lodash.debounce": "^4.0.7",
|
|
42
|
+
"classnames": "^2.3.2",
|
|
43
|
+
"lodash.debounce": "^4.0.8",
|
|
44
|
+
"reflect-metadata": "^0.1.13"
|
|
45
|
+
},
|
|
46
|
+
"peerDependencies": {
|
|
47
|
+
"antd": "^5.8.6",
|
|
48
|
+
"react": "^18.2.0"
|
|
49
|
+
},
|
|
50
|
+
"devDependencies": {
|
|
51
|
+
"@types/react": "^18.2.25"
|
|
52
|
+
},
|
|
53
|
+
"scripts": {
|
|
54
|
+
"setup": "father build",
|
|
55
|
+
"build": "father build",
|
|
56
|
+
"test": ": Note: lint task is delegated to test:* scripts",
|
|
57
|
+
"test:vitest": "vitest run",
|
|
58
|
+
"test:jest": "jest",
|
|
59
|
+
"coverage": ": Note: lint task is delegated to coverage:* scripts",
|
|
60
|
+
"coverage:vitest": "vitest run --coverage",
|
|
61
|
+
"coverage:jest": "jest --coverage",
|
|
62
|
+
"lint": ": Note: lint task is delegated to lint:* scripts",
|
|
63
|
+
"lint:eslint": "eslint src",
|
|
64
|
+
"lint:tsc": "tsc --noEmit"
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import type { Event } from '@difizen/mana-app';
|
|
2
|
+
import type { View } from '@difizen/mana-app';
|
|
3
|
+
import { Emitter } from '@difizen/mana-app';
|
|
4
|
+
import { transient } from '@difizen/mana-app';
|
|
5
|
+
|
|
6
|
+
import type {
|
|
7
|
+
SearchFilter,
|
|
8
|
+
SearchFilters,
|
|
9
|
+
SearchMatch,
|
|
10
|
+
SearchProvider,
|
|
11
|
+
} from './libro-search-protocol.js';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Abstract class implementing the search provider interface.
|
|
15
|
+
*/
|
|
16
|
+
@transient()
|
|
17
|
+
export abstract class AbstractSearchProvider implements SearchProvider {
|
|
18
|
+
// Needs to be protected so subclass can emit the signal too.
|
|
19
|
+
protected _stateChanged: Emitter<void> = new Emitter();
|
|
20
|
+
protected _disposed = false;
|
|
21
|
+
protected view: View;
|
|
22
|
+
get disposed(): boolean {
|
|
23
|
+
return this._disposed;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Constructor
|
|
27
|
+
*/
|
|
28
|
+
constructor(option: { view: View }) {
|
|
29
|
+
this.view = option.view;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Signal indicating that something in the search has changed, so the UI should update
|
|
34
|
+
*/
|
|
35
|
+
get stateChanged(): Event<void> {
|
|
36
|
+
return this._stateChanged.event;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* The current index of the selected match.
|
|
41
|
+
*/
|
|
42
|
+
get currentMatchIndex(): number | undefined {
|
|
43
|
+
return undefined;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Whether the search provider is disposed or not.
|
|
48
|
+
*/
|
|
49
|
+
get isDisposed(): boolean {
|
|
50
|
+
return this._disposed;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* The number of matches.
|
|
55
|
+
*/
|
|
56
|
+
get matchesCount(): number | undefined {
|
|
57
|
+
return undefined;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Set to true if the widget under search is read-only, false
|
|
62
|
+
* if it is editable. Will be used to determine whether to show
|
|
63
|
+
* the replace option.
|
|
64
|
+
*/
|
|
65
|
+
abstract get isReadOnly(): boolean;
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Dispose of the resources held by the search provider.
|
|
69
|
+
*
|
|
70
|
+
* #### Notes
|
|
71
|
+
* If the object's `dispose` method is called more than once, all
|
|
72
|
+
* calls made after the first will be a no-op.
|
|
73
|
+
*
|
|
74
|
+
* #### Undefined Behavior
|
|
75
|
+
* It is undefined behavior to use any functionality of the object
|
|
76
|
+
* after it has been disposed unless otherwise explicitly noted.
|
|
77
|
+
*/
|
|
78
|
+
dispose(): void {
|
|
79
|
+
if (this._disposed) {
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
this._disposed = true;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Get an initial query value if applicable so that it can be entered
|
|
87
|
+
* into the search box as an initial query
|
|
88
|
+
*
|
|
89
|
+
* @returns Initial value used to populate the search box.
|
|
90
|
+
*/
|
|
91
|
+
getInitialQuery(): string {
|
|
92
|
+
return '';
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Get the filters for the given provider.
|
|
97
|
+
*
|
|
98
|
+
* @returns The filters.
|
|
99
|
+
*
|
|
100
|
+
* ### Notes
|
|
101
|
+
* TODO For now it only supports boolean filters (represented with checkboxes)
|
|
102
|
+
*/
|
|
103
|
+
getFilters(): Record<string, SearchFilter> {
|
|
104
|
+
return {};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Start a search using the provided options.
|
|
109
|
+
*
|
|
110
|
+
* @param query A RegExp to be use to perform the search
|
|
111
|
+
* @param filters Filter parameters to pass to provider
|
|
112
|
+
*/
|
|
113
|
+
abstract startQuery(
|
|
114
|
+
query: RegExp,
|
|
115
|
+
filters: SearchFilters,
|
|
116
|
+
highlightNext?: boolean,
|
|
117
|
+
): Promise<void>;
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Stop a search and clear any internal state of ssthe search provider.
|
|
121
|
+
*/
|
|
122
|
+
abstract endQuery(): Promise<void>;
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Clear currently highlighted match.
|
|
126
|
+
*/
|
|
127
|
+
abstract clearHighlight(): Promise<void>;
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Highlight the next match.
|
|
131
|
+
*
|
|
132
|
+
* @returns The next match if available
|
|
133
|
+
*/
|
|
134
|
+
abstract highlightNext(): Promise<SearchMatch | undefined>;
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Highlight the previous match.
|
|
138
|
+
*
|
|
139
|
+
* @returns The previous match if available.
|
|
140
|
+
*/
|
|
141
|
+
abstract highlightPrevious(): Promise<SearchMatch | undefined>;
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Replace the currently selected match with the provided text
|
|
145
|
+
*
|
|
146
|
+
* @param newText The replacement text
|
|
147
|
+
*
|
|
148
|
+
* @returns A promise that resolves with a boolean indicating whether a replace occurred.
|
|
149
|
+
*/
|
|
150
|
+
abstract replaceCurrentMatch(newText: string): Promise<boolean>;
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Replace all matches in the widget with the provided text
|
|
154
|
+
*
|
|
155
|
+
* @param newText The replacement text
|
|
156
|
+
*
|
|
157
|
+
* @returns A promise that resolves with a boolean indicating whether a replace occurred.
|
|
158
|
+
*/
|
|
159
|
+
abstract replaceAllMatches(newText: string): Promise<boolean>;
|
|
160
|
+
}
|
package/src/index.less
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
.libro-search-overlay {
|
|
2
|
+
position: absolute;
|
|
3
|
+
top: 0;
|
|
4
|
+
right: 0;
|
|
5
|
+
box-shadow: 0 2px 2px 0 #7c68681a;
|
|
6
|
+
background-color: var(--mana-color-bg-elevated);
|
|
7
|
+
z-index: 2000;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.libro-search-content {
|
|
11
|
+
display: flex;
|
|
12
|
+
align-items: center;
|
|
13
|
+
padding: 2px 6px;
|
|
14
|
+
min-width: 320px;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.libro-search-row {
|
|
18
|
+
height: 32px;
|
|
19
|
+
|
|
20
|
+
input {
|
|
21
|
+
margin-right: 4px;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.ant-btn {
|
|
25
|
+
border: none;
|
|
26
|
+
box-shadow: none;
|
|
27
|
+
margin-left: 4px;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.libro-search-replace-toggle {
|
|
32
|
+
padding: 4px;
|
|
33
|
+
display: flex;
|
|
34
|
+
align-items: center;
|
|
35
|
+
height: 100%;
|
|
36
|
+
margin-right: 4px;
|
|
37
|
+
cursor: pointer;
|
|
38
|
+
|
|
39
|
+
&:hover {
|
|
40
|
+
background-color: var(--mana-activityBar-background);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.libro-search-input {
|
|
45
|
+
align-items: center;
|
|
46
|
+
flex: 1;
|
|
47
|
+
|
|
48
|
+
.ant-input-affix-wrapper-sm {
|
|
49
|
+
margin-right: 4px;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.libro-search-input-suffix {
|
|
54
|
+
span {
|
|
55
|
+
margin-left: 4px;
|
|
56
|
+
padding: 2px;
|
|
57
|
+
cursor: pointer;
|
|
58
|
+
|
|
59
|
+
&:hover {
|
|
60
|
+
background-color: var(--mana-activityBar-background);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.libro-search-input-suffix-active {
|
|
65
|
+
background-color: var(--mana-activityBar-background);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.libro-search-index {
|
|
70
|
+
// width: 24px;
|
|
71
|
+
display: flex;
|
|
72
|
+
align-items: center;
|
|
73
|
+
justify-content: center;
|
|
74
|
+
margin-left: 4px;
|
|
75
|
+
margin-right: 16px;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.libro-search-action {
|
|
79
|
+
display: flex;
|
|
80
|
+
align-items: center;
|
|
81
|
+
justify-content: space-between;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.libro-search-replace-toggle-icon {
|
|
85
|
+
font-size: 12px;
|
|
86
|
+
transition: transform 0.2s linear;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
.libro-search-replace-toggle-replace-icon {
|
|
90
|
+
transform: rotate(90deg);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
.libro-search-input-area {
|
|
94
|
+
flex: 1;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
.libro-selectedtext {
|
|
98
|
+
background-color: rgb(255, 225, 0);
|
|
99
|
+
|
|
100
|
+
span {
|
|
101
|
+
background-color: rgb(255, 225, 0);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
mark.libro-searching {
|
|
106
|
+
padding: 0;
|
|
107
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export * from './module.js';
|
|
2
|
+
export * from './libro-search-protocol.js';
|
|
3
|
+
export * from './libro-search-utils.js';
|
|
4
|
+
export * from './libro-search-manager.js';
|
|
5
|
+
export * from './libro-search-engine-text.js';
|
|
6
|
+
export * from './libro-search-engine-html.js';
|
|
7
|
+
export * from './libro-search-generic-provider.js';
|
|
8
|
+
export * from './libro-search-view.js';
|
|
9
|
+
export * from './abstract-search-provider.js';
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { CellView } from '@difizen/libro-core';
|
|
2
|
+
import type { Contribution } from '@difizen/mana-app';
|
|
3
|
+
import { Priority } from '@difizen/mana-app';
|
|
4
|
+
import { contrib, transient } from '@difizen/mana-app';
|
|
5
|
+
|
|
6
|
+
import { CellSearchProviderContribution } from './libro-search-protocol.js';
|
|
7
|
+
|
|
8
|
+
@transient()
|
|
9
|
+
export class LibroCellSearchProvider {
|
|
10
|
+
@contrib(CellSearchProviderContribution)
|
|
11
|
+
protected providerContribution: Contribution.Provider<CellSearchProviderContribution>;
|
|
12
|
+
|
|
13
|
+
createCellSearchProvider(cell: CellView) {
|
|
14
|
+
const ctrb = this.findCellSearchProviderContribution(cell);
|
|
15
|
+
if (ctrb) {
|
|
16
|
+
return ctrb.factory(cell);
|
|
17
|
+
}
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
getInitialQuery = (cell: CellView): string => {
|
|
22
|
+
const ctrb = this.findCellSearchProviderContribution(cell);
|
|
23
|
+
if (ctrb && ctrb.getInitialQuery) {
|
|
24
|
+
return ctrb.getInitialQuery(cell);
|
|
25
|
+
}
|
|
26
|
+
return '';
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
protected findCellSearchProviderContribution(
|
|
30
|
+
cell: CellView,
|
|
31
|
+
): CellSearchProviderContribution | undefined {
|
|
32
|
+
const prioritized = Priority.sortSync(
|
|
33
|
+
this.providerContribution.getContributions(),
|
|
34
|
+
(contribution) => contribution.canHandle(cell),
|
|
35
|
+
);
|
|
36
|
+
const sorted = prioritized.map((c) => c.value);
|
|
37
|
+
return sorted[0];
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/* eslint-disable no-param-reassign */
|
|
2
|
+
import type { HTMLSearchMatch } from './libro-search-protocol.js';
|
|
3
|
+
|
|
4
|
+
const UNSUPPORTED_ELEMENTS = [
|
|
5
|
+
'BASE',
|
|
6
|
+
'HEAD',
|
|
7
|
+
'LINK',
|
|
8
|
+
'META',
|
|
9
|
+
'STYLE',
|
|
10
|
+
'TITLE',
|
|
11
|
+
'SVG',
|
|
12
|
+
'SOURCE',
|
|
13
|
+
'SCRIPT',
|
|
14
|
+
'BODY',
|
|
15
|
+
'AREA',
|
|
16
|
+
'AUDIO',
|
|
17
|
+
'IMG',
|
|
18
|
+
'MAP',
|
|
19
|
+
'TRACK',
|
|
20
|
+
'VIDEO',
|
|
21
|
+
'APPLET',
|
|
22
|
+
'EMBED',
|
|
23
|
+
'IFRAME',
|
|
24
|
+
'NOEMBED',
|
|
25
|
+
'OBJECT',
|
|
26
|
+
'PARAM',
|
|
27
|
+
'PICTURE',
|
|
28
|
+
'CANVAS',
|
|
29
|
+
'NOSCRIPT',
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
export const searchInHTML = async (
|
|
33
|
+
query: RegExp,
|
|
34
|
+
rootNode: Node,
|
|
35
|
+
): Promise<HTMLSearchMatch[]> => {
|
|
36
|
+
if (!(rootNode instanceof Node)) {
|
|
37
|
+
console.warn(
|
|
38
|
+
'Unable to search with HTMLSearchEngine the provided object.',
|
|
39
|
+
rootNode,
|
|
40
|
+
);
|
|
41
|
+
return [];
|
|
42
|
+
}
|
|
43
|
+
if (!query.global) {
|
|
44
|
+
query = new RegExp(query.source, query.flags + 'g');
|
|
45
|
+
}
|
|
46
|
+
const matches: HTMLSearchMatch[] = [];
|
|
47
|
+
const walker = document.createTreeWalker(rootNode, NodeFilter.SHOW_TEXT, {
|
|
48
|
+
acceptNode: (node) => {
|
|
49
|
+
let parentElement = node.parentElement!;
|
|
50
|
+
while (parentElement !== rootNode) {
|
|
51
|
+
if (UNSUPPORTED_ELEMENTS.includes(parentElement.nodeName)) {
|
|
52
|
+
return NodeFilter.FILTER_REJECT;
|
|
53
|
+
}
|
|
54
|
+
parentElement = parentElement.parentElement!;
|
|
55
|
+
}
|
|
56
|
+
return query.test(node.textContent!)
|
|
57
|
+
? NodeFilter.FILTER_ACCEPT
|
|
58
|
+
: NodeFilter.FILTER_REJECT;
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
let node: Node | null = null;
|
|
62
|
+
while ((node = walker.nextNode()) !== null) {
|
|
63
|
+
query.lastIndex = 0;
|
|
64
|
+
let match: RegExpExecArray | null = null;
|
|
65
|
+
while ((match = query.exec(node.textContent!)) !== null) {
|
|
66
|
+
matches.push({
|
|
67
|
+
text: match[0],
|
|
68
|
+
position: match.index,
|
|
69
|
+
node: node as Text,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return Promise.resolve(matches);
|
|
74
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { SearchMatch } from './libro-search-protocol.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Search for regular expression matches in a string.
|
|
5
|
+
*
|
|
6
|
+
* @param query Query regular expression
|
|
7
|
+
* @param data String to look into
|
|
8
|
+
* @returns List of matches
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
export const searchText = (query: RegExp, data: string): Promise<SearchMatch[]> => {
|
|
12
|
+
let searchData = data;
|
|
13
|
+
let searchQuery = query;
|
|
14
|
+
if (typeof searchData !== 'string') {
|
|
15
|
+
try {
|
|
16
|
+
searchData = JSON.stringify(searchData);
|
|
17
|
+
} catch (reason) {
|
|
18
|
+
console.warn('Unable to search.', reason, searchData);
|
|
19
|
+
return Promise.resolve([]);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
if (!searchQuery.global) {
|
|
23
|
+
searchQuery = new RegExp(searchQuery.source, searchQuery.flags + 'g');
|
|
24
|
+
}
|
|
25
|
+
const matches: SearchMatch[] = [];
|
|
26
|
+
let match: RegExpExecArray | null = null;
|
|
27
|
+
while ((match = searchQuery.exec(data)) !== null) {
|
|
28
|
+
matches.push({
|
|
29
|
+
text: match[0],
|
|
30
|
+
position: match.index,
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
return Promise.resolve(matches);
|
|
34
|
+
};
|