@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.
Files changed (58) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1 -0
  3. package/es/abstract-search-provider.d.ts +113 -0
  4. package/es/abstract-search-provider.d.ts.map +1 -0
  5. package/es/abstract-search-provider.js +126 -0
  6. package/es/index.d.ts +10 -0
  7. package/es/index.d.ts.map +1 -0
  8. package/es/index.js +9 -0
  9. package/es/index.less +107 -0
  10. package/es/libro-cell-search-provider.d.ts +10 -0
  11. package/es/libro-cell-search-provider.d.ts.map +1 -0
  12. package/es/libro-cell-search-provider.js +54 -0
  13. package/es/libro-search-engine-html.d.ts +3 -0
  14. package/es/libro-search-engine-html.d.ts.map +1 -0
  15. package/es/libro-search-engine-html.js +59 -0
  16. package/es/libro-search-engine-text.d.ts +10 -0
  17. package/es/libro-search-engine-text.d.ts.map +1 -0
  18. package/es/libro-search-engine-text.js +32 -0
  19. package/es/libro-search-generic-provider.d.ts +107 -0
  20. package/es/libro-search-generic-provider.d.ts.map +1 -0
  21. package/es/libro-search-generic-provider.js +467 -0
  22. package/es/libro-search-manager.d.ts +22 -0
  23. package/es/libro-search-manager.d.ts.map +1 -0
  24. package/es/libro-search-manager.js +102 -0
  25. package/es/libro-search-model.d.ts +111 -0
  26. package/es/libro-search-model.d.ts.map +1 -0
  27. package/es/libro-search-model.js +395 -0
  28. package/es/libro-search-protocol.d.ts +176 -0
  29. package/es/libro-search-protocol.d.ts.map +1 -0
  30. package/es/libro-search-protocol.js +36 -0
  31. package/es/libro-search-provider.d.ts +138 -0
  32. package/es/libro-search-provider.d.ts.map +1 -0
  33. package/es/libro-search-provider.js +759 -0
  34. package/es/libro-search-utils.d.ts +25 -0
  35. package/es/libro-search-utils.d.ts.map +1 -0
  36. package/es/libro-search-utils.js +85 -0
  37. package/es/libro-search-view.d.ts +59 -0
  38. package/es/libro-search-view.d.ts.map +1 -0
  39. package/es/libro-search-view.js +455 -0
  40. package/es/module.d.ts +4 -0
  41. package/es/module.d.ts.map +1 -0
  42. package/es/module.js +35 -0
  43. package/package.json +66 -0
  44. package/src/abstract-search-provider.ts +160 -0
  45. package/src/index.less +107 -0
  46. package/src/index.ts +9 -0
  47. package/src/libro-cell-search-provider.ts +39 -0
  48. package/src/libro-search-engine-html.ts +74 -0
  49. package/src/libro-search-engine-text.ts +34 -0
  50. package/src/libro-search-generic-provider.ts +303 -0
  51. package/src/libro-search-manager.ts +86 -0
  52. package/src/libro-search-model.ts +266 -0
  53. package/src/libro-search-protocol.ts +209 -0
  54. package/src/libro-search-provider.ts +507 -0
  55. package/src/libro-search-utils.spec.ts +37 -0
  56. package/src/libro-search-utils.ts +83 -0
  57. package/src/libro-search-view.tsx +404 -0
  58. 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
+ };