@jbrowse/plugin-trix 4.3.0 → 5.0.0-beta.10
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 +61 -29
- package/esm/TrixTextSearchAdapter/configSchema.d.ts +39 -28
- package/esm/TrixTextSearchAdapter/configSchema.js +68 -30
- 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
|
}
|
|
@@ -2,69 +2,101 @@ import Trix from '@gmod/trix';
|
|
|
2
2
|
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
|
+
import { checkAbortSignal } from '@jbrowse/core/util/aborting';
|
|
5
6
|
import { openLocation } from '@jbrowse/core/util/io';
|
|
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
|
-
|
|
41
|
-
|
|
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+/);
|
|
56
|
+
const results = await this.trixJs.search(query, { signal: args.signal });
|
|
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
|
+
checkAbortSignal(args.signal);
|
|
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,45 @@
|
|
|
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` and the sibling `.ixx` is derived
|
|
10
|
+
* from it, so the pair only needs spelling out when they are named against
|
|
11
|
+
* convention. Written by hand, the string `'trix/hg38.ix'` is the whole entry,
|
|
12
|
+
* the same as `{ uri: 'trix/hg38.ix' }`: a `.ix` implies this type, and
|
|
13
|
+
* `assemblyNames` defaults to the track's own for a per-track index or to the
|
|
14
|
+
* config's one assembly.
|
|
15
|
+
* ```js
|
|
16
|
+
* {
|
|
17
|
+
* type: 'TrixTextSearchAdapter',
|
|
18
|
+
* uri: 'trix/hg38.ix',
|
|
19
|
+
* assemblyNames: ['hg38'],
|
|
20
|
+
* }
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
1
23
|
declare const TrixTextSearchAdapter: import("@jbrowse/core/configuration/configurationSchema").ConfigurationSchemaType<{
|
|
2
|
-
|
|
3
|
-
type: string;
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
locationType: string;
|
|
7
|
-
};
|
|
24
|
+
type: {
|
|
25
|
+
type: "string";
|
|
26
|
+
} & {
|
|
27
|
+
defaultValue: string;
|
|
8
28
|
};
|
|
9
|
-
|
|
10
|
-
type:
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
locationType: string;
|
|
14
|
-
};
|
|
29
|
+
readonly ixFilePath: {
|
|
30
|
+
type: "fileLocation";
|
|
31
|
+
} & {
|
|
32
|
+
defaultValue: unknown;
|
|
15
33
|
};
|
|
16
|
-
|
|
17
|
-
type:
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
locationType: string;
|
|
21
|
-
};
|
|
34
|
+
readonly ixxFilePath: {
|
|
35
|
+
type: "fileLocation";
|
|
36
|
+
} & {
|
|
37
|
+
defaultValue: unknown;
|
|
22
38
|
};
|
|
23
|
-
|
|
24
|
-
type:
|
|
25
|
-
|
|
26
|
-
|
|
39
|
+
readonly assemblyNames: {
|
|
40
|
+
type: "stringArray";
|
|
41
|
+
} & {
|
|
42
|
+
defaultValue: unknown;
|
|
27
43
|
};
|
|
28
|
-
|
|
29
|
-
type: string;
|
|
30
|
-
defaultValue: never[];
|
|
31
|
-
description: string;
|
|
32
|
-
};
|
|
33
|
-
}, import("@jbrowse/core/configuration/configurationSchema").ConfigurationSchemaOptions<undefined, "textSearchAdapterId">>;
|
|
44
|
+
}, import("@jbrowse/core/configuration/configurationSchema").ConfigurationSchemaOptions<undefined, undefined, true, import("@jbrowse/core/configuration").ConfigurationSchemaRequirement<Partial<Record<string, string[]>>, string>, any>>;
|
|
34
45
|
export default TrixTextSearchAdapter;
|
|
@@ -1,6 +1,47 @@
|
|
|
1
|
-
import { ConfigurationSchema } from '@jbrowse/core/configuration';
|
|
2
|
-
function
|
|
1
|
+
import { ConfigurationSchema, fillLocations } from '@jbrowse/core/configuration';
|
|
2
|
+
export function normalizeSnapshot(snap) {
|
|
3
|
+
return typeof snap.uri === 'string'
|
|
4
|
+
? fillLocations(snap, {
|
|
5
|
+
// `uri` points at the `.ix` file and the `.ixx` sits beside it (the
|
|
6
|
+
// `jbrowse text-index` naming convention), so derive the pair
|
|
7
|
+
ixFilePath: {
|
|
8
|
+
uri: snap.uri,
|
|
9
|
+
baseUri: snap.baseUri,
|
|
10
|
+
},
|
|
11
|
+
ixxFilePath: {
|
|
12
|
+
uri: `${snap.uri}x`,
|
|
13
|
+
baseUri: snap.baseUri,
|
|
14
|
+
},
|
|
15
|
+
})
|
|
16
|
+
: snap;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* #config TrixTextSearchAdapter
|
|
20
|
+
* #trackType TextSearchAdapter
|
|
21
|
+
* #fileFormat textsearch | Trix index (.ix/.ixx) | Built by `jbrowse text-index`
|
|
22
|
+
*
|
|
23
|
+
* #example
|
|
24
|
+
* `jbrowse text-index` writes this entry into `aggregateTextSearchAdapters` for
|
|
25
|
+
* you. The `uri` shorthand points at the `.ix` and the sibling `.ixx` is derived
|
|
26
|
+
* from it, so the pair only needs spelling out when they are named against
|
|
27
|
+
* convention. Written by hand, the string `'trix/hg38.ix'` is the whole entry,
|
|
28
|
+
* the same as `{ uri: 'trix/hg38.ix' }`: a `.ix` implies this type, and
|
|
29
|
+
* `assemblyNames` defaults to the track's own for a per-track index or to the
|
|
30
|
+
* config's one assembly.
|
|
31
|
+
* ```js
|
|
32
|
+
* {
|
|
33
|
+
* type: 'TrixTextSearchAdapter',
|
|
34
|
+
* uri: 'trix/hg38.ix',
|
|
35
|
+
* assemblyNames: ['hg38'],
|
|
36
|
+
* }
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
3
39
|
const TrixTextSearchAdapter = ConfigurationSchema('TrixTextSearchAdapter', {
|
|
40
|
+
/**
|
|
41
|
+
* #slot
|
|
42
|
+
* location of the Trix `.ix` index written by `jbrowse text-index`: the
|
|
43
|
+
* sorted term-to-feature table the search box reads.
|
|
44
|
+
*/
|
|
4
45
|
ixFilePath: {
|
|
5
46
|
type: 'fileLocation',
|
|
6
47
|
defaultValue: {
|
|
@@ -8,6 +49,12 @@ const TrixTextSearchAdapter = ConfigurationSchema('TrixTextSearchAdapter', {
|
|
|
8
49
|
locationType: 'UriLocation',
|
|
9
50
|
},
|
|
10
51
|
},
|
|
52
|
+
/**
|
|
53
|
+
* #slot
|
|
54
|
+
* location of the `.ixx` prefix index, which records where in the `.ix`
|
|
55
|
+
* each prefix begins, so a lookup takes a couple of range requests instead
|
|
56
|
+
* of a download of the whole index.
|
|
57
|
+
*/
|
|
11
58
|
ixxFilePath: {
|
|
12
59
|
type: 'fileLocation',
|
|
13
60
|
defaultValue: {
|
|
@@ -15,18 +62,9 @@ const TrixTextSearchAdapter = ConfigurationSchema('TrixTextSearchAdapter', {
|
|
|
15
62
|
locationType: 'UriLocation',
|
|
16
63
|
},
|
|
17
64
|
},
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
uri: 'meta.json',
|
|
22
|
-
locationType: 'UriLocation',
|
|
23
|
-
},
|
|
24
|
-
},
|
|
25
|
-
tracks: {
|
|
26
|
-
type: 'stringArray',
|
|
27
|
-
defaultValue: [],
|
|
28
|
-
description: 'List of tracks covered by text search adapter',
|
|
29
|
-
},
|
|
65
|
+
/**
|
|
66
|
+
* #slot
|
|
67
|
+
*/
|
|
30
68
|
assemblyNames: {
|
|
31
69
|
type: 'stringArray',
|
|
32
70
|
defaultValue: [],
|
|
@@ -34,21 +72,21 @@ const TrixTextSearchAdapter = ConfigurationSchema('TrixTextSearchAdapter', {
|
|
|
34
72
|
},
|
|
35
73
|
}, {
|
|
36
74
|
explicitlyTyped: true,
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
75
|
+
/**
|
|
76
|
+
* #preProcessSnapshot
|
|
77
|
+
*
|
|
78
|
+
*
|
|
79
|
+
* preprocessor to allow minimal config: `uri` points at the `.ix` file and
|
|
80
|
+
* the sibling `.ixx` is derived from it (the `jbrowse text-index` naming
|
|
81
|
+
* convention):
|
|
82
|
+
* ```json
|
|
83
|
+
* {
|
|
84
|
+
* "type": "TrixTextSearchAdapter",
|
|
85
|
+
* "uri": "file.ix",
|
|
86
|
+
* "assemblyNames": ["hg19"]
|
|
87
|
+
* }
|
|
88
|
+
* ```
|
|
89
|
+
*/
|
|
90
|
+
preProcessSnapshot: normalizeSnapshot,
|
|
53
91
|
});
|
|
54
92
|
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.10",
|
|
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.10"
|
|
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
|
}
|