@jbrowse/plugin-trix 4.2.1 → 5.0.0-beta.1
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/esm/TrixTextSearchAdapter/TrixTextSearchAdapter.d.ts +6 -2
- package/esm/TrixTextSearchAdapter/TrixTextSearchAdapter.js +60 -28
- package/esm/TrixTextSearchAdapter/configSchema.d.ts +66 -24
- package/esm/TrixTextSearchAdapter/configSchema.js +84 -17
- package/esm/index.js +2 -2
- package/package.json +7 -10
|
@@ -5,10 +5,14 @@ import type PluginManager from '@jbrowse/core/PluginManager';
|
|
|
5
5
|
import type { AnyConfigurationModel } from '@jbrowse/core/configuration';
|
|
6
6
|
import type { BaseTextSearchAdapter, BaseTextSearchArgs } from '@jbrowse/core/data_adapters/BaseAdapter';
|
|
7
7
|
import type { getSubAdapterType } from '@jbrowse/core/data_adapters/dataAdapterCache';
|
|
8
|
+
export declare function snippetAround(str: string, term: string, w?: number): string;
|
|
8
9
|
export default class TrixTextSearchAdapter extends BaseAdapter implements BaseTextSearchAdapter {
|
|
9
|
-
indexingAttributes?: string[];
|
|
10
10
|
trixJs: Trix;
|
|
11
|
-
tracksNames?: string[];
|
|
12
11
|
constructor(config: AnyConfigurationModel, getSubAdapter?: getSubAdapterType, pluginManager?: PluginManager);
|
|
12
|
+
/**
|
|
13
|
+
* Returns list of results
|
|
14
|
+
* @param args - search options/arguments include: search query,
|
|
15
|
+
* searchType (prefix | full | exact), etc.
|
|
16
|
+
*/
|
|
13
17
|
searchIndex(args: BaseTextSearchArgs): Promise<BaseResult[]>;
|
|
14
18
|
}
|
|
@@ -3,68 +3,100 @@ import BaseResult from '@jbrowse/core/TextSearch/BaseResults';
|
|
|
3
3
|
import { readConfObject } from '@jbrowse/core/configuration';
|
|
4
4
|
import { BaseAdapter } from '@jbrowse/core/data_adapters/BaseAdapter';
|
|
5
5
|
import { openLocation } from '@jbrowse/core/util/io';
|
|
6
|
+
import { checkStopToken } from '@jbrowse/core/util/stopToken';
|
|
6
7
|
function decodeURIComponentNoThrow(uri) {
|
|
7
8
|
try {
|
|
8
9
|
return decodeURIComponent(uri);
|
|
9
10
|
}
|
|
10
|
-
catch
|
|
11
|
+
catch {
|
|
11
12
|
return uri;
|
|
12
13
|
}
|
|
13
14
|
}
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
15
|
+
// Build a search-result snippet: a window of `w` characters either side of
|
|
16
|
+
// where `term` matched, ellipsized on whichever side was cut. Nothing like the
|
|
17
|
+
// core `shorten`, which just elides a long name.
|
|
18
|
+
export function snippetAround(str, term, w = 15) {
|
|
19
|
+
if (str.length < 40) {
|
|
20
|
+
return str;
|
|
21
|
+
}
|
|
22
|
+
const tidx = str.toLowerCase().indexOf(term.toLowerCase());
|
|
23
|
+
if (tidx === -1) {
|
|
24
|
+
return `${str.slice(0, 40).trim()}...`;
|
|
25
|
+
}
|
|
26
|
+
const start = Math.max(0, tidx - w);
|
|
27
|
+
const end = tidx + term.length + w;
|
|
28
|
+
return ((start > 0 ? '...' : '') +
|
|
29
|
+
str.slice(start, end).trim() +
|
|
30
|
+
(end < str.length ? '...' : ''));
|
|
21
31
|
}
|
|
22
32
|
export default class TrixTextSearchAdapter extends BaseAdapter {
|
|
23
|
-
indexingAttributes;
|
|
24
33
|
trixJs;
|
|
25
|
-
tracksNames;
|
|
26
34
|
constructor(config, getSubAdapter, pluginManager) {
|
|
27
35
|
super(config, getSubAdapter, pluginManager);
|
|
28
36
|
const ixFilePath = readConfObject(config, 'ixFilePath');
|
|
29
37
|
const ixxFilePath = readConfObject(config, 'ixxFilePath');
|
|
30
38
|
if (!ixFilePath) {
|
|
31
|
-
throw new Error('
|
|
39
|
+
throw new Error('TrixTextSearchAdapter requires an ixFilePath');
|
|
32
40
|
}
|
|
33
41
|
if (!ixxFilePath) {
|
|
34
|
-
throw new Error('
|
|
42
|
+
throw new Error('TrixTextSearchAdapter requires an ixxFilePath');
|
|
35
43
|
}
|
|
36
44
|
this.trixJs = new Trix(openLocation(ixxFilePath, pluginManager), openLocation(ixFilePath, pluginManager), 1500);
|
|
37
45
|
}
|
|
46
|
+
/**
|
|
47
|
+
* Returns list of results
|
|
48
|
+
* @param args - search options/arguments include: search query,
|
|
49
|
+
* searchType (prefix | full | exact), etc.
|
|
50
|
+
*/
|
|
38
51
|
async searchIndex(args) {
|
|
39
|
-
const query = args.queryString.toLowerCase();
|
|
40
|
-
|
|
52
|
+
const query = args.queryString.toLowerCase().trim();
|
|
53
|
+
// any run of whitespace separates words; splitting on a single ' ' let a
|
|
54
|
+
// double space or a tab produce an empty word that matches everything
|
|
55
|
+
const words = query.split(/\s+/);
|
|
41
56
|
const results = await this.trixJs.search(query);
|
|
57
|
+
// the index read has landed but nothing below it has run yet; a broad
|
|
58
|
+
// prefix returns thousands of records to JSON-parse, decode and snippet,
|
|
59
|
+
// and the keystroke that asked for them is 50ms stale by now
|
|
60
|
+
checkStopToken(args.stopToken);
|
|
42
61
|
const formatted = results
|
|
43
|
-
.filter(([, data]) => strs.every(r => decodeURIComponentNoThrow(data).toLowerCase().includes(r)))
|
|
44
62
|
.map(([term, data]) => {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
|
|
63
|
+
// record is ["loc"|"trackId"|"attr1"|...] with commas pre-encoded as
|
|
64
|
+
// pipes; swap them back to parse it as a JSON array, then URI-decode
|
|
65
|
+
const [loc, trackId, ...attrs] = JSON.parse(data.replaceAll('|', ',')).map(decodeURIComponentNoThrow);
|
|
66
|
+
return { term, loc, trackId, attrs };
|
|
67
|
+
})
|
|
68
|
+
// multi-word search: keep only entries whose attributes contain every word
|
|
69
|
+
.filter(({ attrs }) => {
|
|
70
|
+
const lower = attrs.join(' ').toLowerCase();
|
|
71
|
+
return words.every(w => lower.includes(w));
|
|
72
|
+
})
|
|
73
|
+
.map(({ term, loc, trackId, attrs }) => {
|
|
74
|
+
const termLower = term.toLowerCase();
|
|
75
|
+
const labelField = attrs.find(Boolean) ?? '';
|
|
76
|
+
const contextField = attrs.find(f => f.toLowerCase().includes(termLower));
|
|
77
|
+
const context = contextField
|
|
78
|
+
? snippetAround(contextField, term)
|
|
79
|
+
: undefined;
|
|
80
|
+
const label = snippetAround(labelField, term);
|
|
55
81
|
const displayString = !context || label.toLowerCase() === context.toLowerCase()
|
|
56
82
|
? label
|
|
57
83
|
: `${label} (${context})`;
|
|
84
|
+
// exact match succeeds when any indexed attribute (name, id,
|
|
85
|
+
// description, ...) equals the query, so e.g. searching an ID works.
|
|
86
|
+
// Carried on the result as well as filtered on: only this adapter can
|
|
87
|
+
// judge it, and a caller that has the flag can ask one unrestricted
|
|
88
|
+
// question instead of an exact one and then a broad one
|
|
89
|
+
const exact = attrs.some(a => a.toLowerCase() === query);
|
|
58
90
|
return new BaseResult({
|
|
59
91
|
locString: loc,
|
|
60
|
-
label
|
|
92
|
+
label,
|
|
61
93
|
displayString,
|
|
62
|
-
matchedObject: result.map(record => decodeURIComponent(record)),
|
|
63
94
|
trackId,
|
|
95
|
+
exact,
|
|
64
96
|
});
|
|
65
97
|
});
|
|
66
98
|
return args.searchType === 'exact'
|
|
67
|
-
? formatted.filter(r => r.
|
|
99
|
+
? formatted.filter(r => r.isExact())
|
|
68
100
|
: formatted;
|
|
69
101
|
}
|
|
70
102
|
}
|
|
@@ -1,34 +1,76 @@
|
|
|
1
|
+
export declare function normalizeSnapshot(snap: Record<string, unknown>): Record<string, unknown>;
|
|
2
|
+
/**
|
|
3
|
+
* #config TrixTextSearchAdapter
|
|
4
|
+
* #trackType TextSearchAdapter
|
|
5
|
+
* #fileFormat textsearch | Trix index (.ix/.ixx) | Built by `jbrowse text-index`
|
|
6
|
+
*
|
|
7
|
+
* #example
|
|
8
|
+
* `jbrowse text-index` writes this entry into `aggregateTextSearchAdapters` for
|
|
9
|
+
* you. The `uri` shorthand points at the `.ix`; the sibling `.ixx` and
|
|
10
|
+
* `_meta.json` are derived from it, so all three only need spelling out when
|
|
11
|
+
* they are named against convention.
|
|
12
|
+
* ```js
|
|
13
|
+
* {
|
|
14
|
+
* type: 'TrixTextSearchAdapter',
|
|
15
|
+
* textSearchAdapterId: 'hg38-index',
|
|
16
|
+
* uri: 'trix/hg38.ix',
|
|
17
|
+
* assemblyNames: ['hg38'],
|
|
18
|
+
* }
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
1
21
|
declare const TrixTextSearchAdapter: import("@jbrowse/core/configuration/configurationSchema").ConfigurationSchemaType<{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
22
|
+
/**
|
|
23
|
+
* #slot
|
|
24
|
+
* location of the Trix `.ix` index written by `jbrowse text-index`: the
|
|
25
|
+
* sorted term-to-feature table the search box reads.
|
|
26
|
+
*/
|
|
27
|
+
readonly ixFilePath: {
|
|
28
|
+
readonly type: "fileLocation";
|
|
29
|
+
readonly defaultValue: {
|
|
30
|
+
readonly uri: "out.ix";
|
|
31
|
+
readonly locationType: "UriLocation";
|
|
7
32
|
};
|
|
8
33
|
};
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
34
|
+
/**
|
|
35
|
+
* #slot
|
|
36
|
+
* location of the `.ixx` prefix index, which records where in the `.ix`
|
|
37
|
+
* each prefix begins. It is what makes a lookup a couple of range requests
|
|
38
|
+
* instead of a download of the whole index.
|
|
39
|
+
*/
|
|
40
|
+
readonly ixxFilePath: {
|
|
41
|
+
readonly type: "fileLocation";
|
|
42
|
+
readonly defaultValue: {
|
|
43
|
+
readonly uri: "out.ixx";
|
|
44
|
+
readonly locationType: "UriLocation";
|
|
14
45
|
};
|
|
15
46
|
};
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
47
|
+
/**
|
|
48
|
+
* #slot
|
|
49
|
+
* location of the `_meta.json` written beside the index, recording which
|
|
50
|
+
* tracks and assemblies it covers and the attributes it was built from.
|
|
51
|
+
*/
|
|
52
|
+
readonly metaFilePath: {
|
|
53
|
+
readonly type: "fileLocation";
|
|
54
|
+
readonly defaultValue: {
|
|
55
|
+
readonly uri: "meta.json";
|
|
56
|
+
readonly locationType: "UriLocation";
|
|
21
57
|
};
|
|
22
58
|
};
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
59
|
+
/**
|
|
60
|
+
* #slot
|
|
61
|
+
*/
|
|
62
|
+
readonly tracks: {
|
|
63
|
+
readonly type: "stringArray";
|
|
64
|
+
readonly defaultValue: readonly [];
|
|
65
|
+
readonly description: "List of tracks covered by text search adapter";
|
|
27
66
|
};
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
67
|
+
/**
|
|
68
|
+
* #slot
|
|
69
|
+
*/
|
|
70
|
+
readonly assemblyNames: {
|
|
71
|
+
readonly type: "stringArray";
|
|
72
|
+
readonly defaultValue: readonly [];
|
|
73
|
+
readonly description: "List of assemblies covered by text search adapter";
|
|
32
74
|
};
|
|
33
|
-
}, import("@jbrowse/core/configuration/configurationSchema").ConfigurationSchemaOptions<undefined,
|
|
75
|
+
}, import("@jbrowse/core/configuration/configurationSchema").ConfigurationSchemaOptions<undefined, undefined>>;
|
|
34
76
|
export default TrixTextSearchAdapter;
|
|
@@ -1,6 +1,50 @@
|
|
|
1
1
|
import { ConfigurationSchema } from '@jbrowse/core/configuration';
|
|
2
|
-
function
|
|
2
|
+
export function normalizeSnapshot(snap) {
|
|
3
|
+
return typeof snap.uri === 'string'
|
|
4
|
+
? {
|
|
5
|
+
...snap,
|
|
6
|
+
// `uri` points at the `.ix` file; `.ixx` and `_meta.json` sit beside it
|
|
7
|
+
// (the `jbrowse text-index` naming convention), so derive all three
|
|
8
|
+
ixFilePath: {
|
|
9
|
+
uri: snap.uri,
|
|
10
|
+
baseUri: snap.baseUri,
|
|
11
|
+
},
|
|
12
|
+
ixxFilePath: {
|
|
13
|
+
uri: `${snap.uri}x`,
|
|
14
|
+
baseUri: snap.baseUri,
|
|
15
|
+
},
|
|
16
|
+
metaFilePath: {
|
|
17
|
+
uri: `${snap.uri.replace(/\.ix$/, '')}_meta.json`,
|
|
18
|
+
baseUri: snap.baseUri,
|
|
19
|
+
},
|
|
20
|
+
}
|
|
21
|
+
: snap;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* #config TrixTextSearchAdapter
|
|
25
|
+
* #trackType TextSearchAdapter
|
|
26
|
+
* #fileFormat textsearch | Trix index (.ix/.ixx) | Built by `jbrowse text-index`
|
|
27
|
+
*
|
|
28
|
+
* #example
|
|
29
|
+
* `jbrowse text-index` writes this entry into `aggregateTextSearchAdapters` for
|
|
30
|
+
* you. The `uri` shorthand points at the `.ix`; the sibling `.ixx` and
|
|
31
|
+
* `_meta.json` are derived from it, so all three only need spelling out when
|
|
32
|
+
* they are named against convention.
|
|
33
|
+
* ```js
|
|
34
|
+
* {
|
|
35
|
+
* type: 'TrixTextSearchAdapter',
|
|
36
|
+
* textSearchAdapterId: 'hg38-index',
|
|
37
|
+
* uri: 'trix/hg38.ix',
|
|
38
|
+
* assemblyNames: ['hg38'],
|
|
39
|
+
* }
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
3
42
|
const TrixTextSearchAdapter = ConfigurationSchema('TrixTextSearchAdapter', {
|
|
43
|
+
/**
|
|
44
|
+
* #slot
|
|
45
|
+
* location of the Trix `.ix` index written by `jbrowse text-index`: the
|
|
46
|
+
* sorted term-to-feature table the search box reads.
|
|
47
|
+
*/
|
|
4
48
|
ixFilePath: {
|
|
5
49
|
type: 'fileLocation',
|
|
6
50
|
defaultValue: {
|
|
@@ -8,6 +52,12 @@ const TrixTextSearchAdapter = ConfigurationSchema('TrixTextSearchAdapter', {
|
|
|
8
52
|
locationType: 'UriLocation',
|
|
9
53
|
},
|
|
10
54
|
},
|
|
55
|
+
/**
|
|
56
|
+
* #slot
|
|
57
|
+
* location of the `.ixx` prefix index, which records where in the `.ix`
|
|
58
|
+
* each prefix begins. It is what makes a lookup a couple of range requests
|
|
59
|
+
* instead of a download of the whole index.
|
|
60
|
+
*/
|
|
11
61
|
ixxFilePath: {
|
|
12
62
|
type: 'fileLocation',
|
|
13
63
|
defaultValue: {
|
|
@@ -15,6 +65,11 @@ const TrixTextSearchAdapter = ConfigurationSchema('TrixTextSearchAdapter', {
|
|
|
15
65
|
locationType: 'UriLocation',
|
|
16
66
|
},
|
|
17
67
|
},
|
|
68
|
+
/**
|
|
69
|
+
* #slot
|
|
70
|
+
* location of the `_meta.json` written beside the index, recording which
|
|
71
|
+
* tracks and assemblies it covers and the attributes it was built from.
|
|
72
|
+
*/
|
|
18
73
|
metaFilePath: {
|
|
19
74
|
type: 'fileLocation',
|
|
20
75
|
defaultValue: {
|
|
@@ -22,11 +77,17 @@ const TrixTextSearchAdapter = ConfigurationSchema('TrixTextSearchAdapter', {
|
|
|
22
77
|
locationType: 'UriLocation',
|
|
23
78
|
},
|
|
24
79
|
},
|
|
80
|
+
/**
|
|
81
|
+
* #slot
|
|
82
|
+
*/
|
|
25
83
|
tracks: {
|
|
26
84
|
type: 'stringArray',
|
|
27
85
|
defaultValue: [],
|
|
28
86
|
description: 'List of tracks covered by text search adapter',
|
|
29
87
|
},
|
|
88
|
+
/**
|
|
89
|
+
* #slot
|
|
90
|
+
*/
|
|
30
91
|
assemblyNames: {
|
|
31
92
|
type: 'stringArray',
|
|
32
93
|
defaultValue: [],
|
|
@@ -34,21 +95,27 @@ const TrixTextSearchAdapter = ConfigurationSchema('TrixTextSearchAdapter', {
|
|
|
34
95
|
},
|
|
35
96
|
}, {
|
|
36
97
|
explicitlyTyped: true,
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
98
|
+
/**
|
|
99
|
+
* #identifier
|
|
100
|
+
* an explicit `textSearchAdapterId` is still honored when given
|
|
101
|
+
*/
|
|
102
|
+
implicitIdentifier: 'textSearchAdapterId',
|
|
103
|
+
/**
|
|
104
|
+
* #preProcessSnapshot
|
|
105
|
+
*
|
|
106
|
+
*
|
|
107
|
+
* preprocessor to allow minimal config: `uri` points at the `.ix` file and
|
|
108
|
+
* the sibling `.ixx` and `_meta.json` are derived from it (the
|
|
109
|
+
* `jbrowse text-index` naming convention):
|
|
110
|
+
* ```json
|
|
111
|
+
* {
|
|
112
|
+
* "type": "TrixTextSearchAdapter",
|
|
113
|
+
* "uri": "file.ix",
|
|
114
|
+
* "assemblyNames": ["hg19"],
|
|
115
|
+
* "textSearchAdapterId": "hg19SearchIndex"
|
|
116
|
+
* }
|
|
117
|
+
* ```
|
|
118
|
+
*/
|
|
119
|
+
preProcessSnapshot: normalizeSnapshot,
|
|
53
120
|
});
|
|
54
121
|
export default TrixTextSearchAdapter;
|
package/esm/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import Plugin from '@jbrowse/core/Plugin';
|
|
2
2
|
import TextSearchAdapterType from '@jbrowse/core/pluggableElementTypes/TextSearchAdapterType';
|
|
3
|
-
import configSchema from
|
|
3
|
+
import configSchema from './TrixTextSearchAdapter/configSchema.js';
|
|
4
4
|
export default class TrixPlugin extends Plugin {
|
|
5
5
|
name = 'TrixPlugin';
|
|
6
6
|
install(pluginManager) {
|
|
@@ -10,7 +10,7 @@ export default class TrixPlugin extends Plugin {
|
|
|
10
10
|
displayName: 'Trix text search adapter',
|
|
11
11
|
configSchema,
|
|
12
12
|
description: 'Trix text search adapter',
|
|
13
|
-
getAdapterClass: () => import(
|
|
13
|
+
getAdapterClass: () => import('./TrixTextSearchAdapter/TrixTextSearchAdapter.js').then(d => d.default),
|
|
14
14
|
});
|
|
15
15
|
});
|
|
16
16
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jbrowse/plugin-trix",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "5.0.0-beta.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "JBrowse 2 trix text search adapter",
|
|
6
6
|
"keywords": [
|
|
@@ -21,11 +21,11 @@
|
|
|
21
21
|
"esm"
|
|
22
22
|
],
|
|
23
23
|
"peerDependencies": {
|
|
24
|
-
"react": ">=
|
|
24
|
+
"react": ">=19.0.0"
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"@gmod/trix": "^3.0.
|
|
28
|
-
"@jbrowse/core": "^
|
|
27
|
+
"@gmod/trix": "^3.0.22",
|
|
28
|
+
"@jbrowse/core": "^5.0.0-beta.1"
|
|
29
29
|
},
|
|
30
30
|
"publishConfig": {
|
|
31
31
|
"access": "public"
|
|
@@ -33,16 +33,13 @@
|
|
|
33
33
|
"sideEffects": false,
|
|
34
34
|
"scripts": {
|
|
35
35
|
"build": "pnpm run /^build:/",
|
|
36
|
-
"test": "
|
|
36
|
+
"test": "jest --config ../../jest.config.js --passWithNoTests --testPathPatterns plugins/trix",
|
|
37
37
|
"prebuild": "pnpm clean",
|
|
38
|
-
"build:esm": "tsc
|
|
38
|
+
"build:esm": "node ../../node_modules/typescript7/bin/tsc --build tsconfig.build.esm.json",
|
|
39
39
|
"clean": "rimraf esm *.tsbuildinfo"
|
|
40
40
|
},
|
|
41
41
|
"types": "esm/index.d.ts",
|
|
42
42
|
"exports": {
|
|
43
|
-
".":
|
|
44
|
-
"types": "./esm/index.d.ts",
|
|
45
|
-
"import": "./esm/index.js"
|
|
46
|
-
}
|
|
43
|
+
".": "./esm/index.js"
|
|
47
44
|
}
|
|
48
45
|
}
|