@internetarchive/radio-player 0.0.4-alpha.1 → 0.1.1-alpha.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/LICENSE +661 -661
- package/README.md +142 -142
- package/index.d.ts +15 -15
- package/index.js +28 -28
- package/lib/src/assets/img/left.d.ts +2 -2
- package/lib/src/assets/img/left.js +15 -15
- package/lib/src/assets/img/right.d.ts +2 -2
- package/lib/src/assets/img/right.js +15 -15
- package/lib/src/models/music-zone.d.ts +12 -12
- package/lib/src/models/music-zone.js +15 -15
- package/lib/src/models/radio-player-config.d.ts +26 -26
- package/lib/src/models/radio-player-config.js +27 -27
- package/lib/src/radio-player.d.ts +589 -589
- package/lib/src/radio-player.js +1337 -1338
- package/lib/src/radio-player.js.map +1 -1
- package/lib/src/search-handler/search-backends/full-text-search-backend/full-text-search-backend.d.ts +82 -82
- package/lib/src/search-handler/search-backends/full-text-search-backend/full-text-search-backend.js +107 -107
- package/lib/src/search-handler/search-backends/full-text-search-backend/full-text-search-delegate.d.ts +9 -0
- package/lib/src/search-handler/search-backends/full-text-search-backend/full-text-search-delegate.js +1 -0
- package/lib/src/search-handler/search-backends/full-text-search-backend/full-text-search-delegate.js.map +1 -0
- package/lib/src/search-handler/search-backends/full-text-search-backend/full-text-search-response.d.ts +48 -48
- package/lib/src/search-handler/search-backends/full-text-search-backend/full-text-search-response.js +58 -58
- package/lib/src/search-handler/search-backends/full-text-search-backend/full-text-search-service-interface.d.ts +16 -16
- package/lib/src/search-handler/search-backends/full-text-search-backend/full-text-search-service-interface.js +1 -1
- package/lib/src/search-handler/search-backends/local-search-backend/local-search-backend.d.ts +27 -27
- package/lib/src/search-handler/search-backends/local-search-backend/local-search-backend.js +42 -42
- package/lib/src/search-handler/search-backends/search-backend-interface.d.ts +9 -9
- package/lib/src/search-handler/search-backends/search-backend-interface.js +1 -1
- package/lib/src/search-handler/search-handler-interface.d.ts +17 -17
- package/lib/src/search-handler/search-handler-interface.js +1 -1
- package/lib/src/search-handler/search-handler.d.ts +83 -83
- package/lib/src/search-handler/search-handler.js +177 -177
- package/lib/src/search-handler/search-helper.d.ts +12 -12
- package/lib/src/search-handler/search-helper.js +22 -22
- package/lib/src/search-handler/search-index.d.ts +53 -0
- package/lib/src/search-handler/search-index.js +118 -0
- package/lib/src/search-handler/search-index.js.map +1 -0
- package/lib/src/search-handler/search-models.d.ts +45 -45
- package/lib/src/search-handler/search-models.js +49 -49
- package/lib/src/search-handler/transcript-index-interface.d.ts +38 -38
- package/lib/src/search-handler/transcript-index-interface.js +1 -1
- package/lib/src/search-handler/transcript-index.d.ts +53 -53
- package/lib/src/search-handler/transcript-index.js +80 -80
- package/lib/src/search-results-switcher.d.ts +58 -58
- package/lib/src/search-results-switcher.js +133 -133
- package/package.json +93 -93
- package/src/assets/img/left.ts +17 -17
- package/src/assets/img/right.ts +17 -17
- package/src/models/music-zone.ts +17 -17
- package/src/models/radio-player-config.ts +47 -47
- package/src/radio-player.ts +1398 -1402
- package/src/search-handler/search-backends/full-text-search-backend/full-text-search-backend.ts +140 -140
- package/src/search-handler/search-backends/full-text-search-backend/full-text-search-response.ts +110 -110
- package/src/search-handler/search-backends/full-text-search-backend/full-text-search-service-interface.ts +17 -17
- package/src/search-handler/search-backends/local-search-backend/local-search-backend.ts +50 -50
- package/src/search-handler/search-backends/search-backend-interface.ts +10 -10
- package/src/search-handler/search-handler-interface.ts +18 -18
- package/src/search-handler/search-handler.ts +217 -217
- package/src/search-handler/search-helper.ts +25 -25
- package/src/search-handler/search-models.ts +69 -69
- package/src/search-handler/transcript-index-interface.ts +42 -42
- package/src/search-handler/transcript-index.ts +93 -93
- package/src/search-results-switcher.ts +131 -131
- package/lib/demo/full-text-search-service.d.ts +0 -14
- package/lib/demo/full-text-search-service.js +0 -34
- package/lib/demo/full-text-search-service.js.map +0 -1
- package/lib/demo/radio-player-controller.d.ts +0 -41
- package/lib/demo/radio-player-controller.js +0 -200
- package/lib/demo/radio-player-controller.js.map +0 -1
|
@@ -1,178 +1,178 @@
|
|
|
1
|
-
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
-
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
-
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
-
});
|
|
9
|
-
};
|
|
10
|
-
import { TranscriptConfig, TranscriptEntryConfig } from '@internetarchive/transcript-view';
|
|
11
|
-
import { Range, SearchResult } from './search-models';
|
|
12
|
-
import { SearchHelper } from './search-helper';
|
|
13
|
-
/**
|
|
14
|
-
* This is the main entrypoint into transcript searching. It has a single
|
|
15
|
-
* public method as defined by `SearchHandlerInterface`: `search(term: string)`,
|
|
16
|
-
* that returns a `Promise<TranscriptConfig>` with a search-modified transcript.
|
|
17
|
-
* This means the original transcript entries get merged with search results since
|
|
18
|
-
* search results can span across transcript entries.
|
|
19
|
-
*
|
|
20
|
-
* It offloads the searching work to a `SearchBackendInterface` object that
|
|
21
|
-
* is responsible for returning the search result `Range` objects based on some
|
|
22
|
-
* backend search like the `LocalSearchBackend` or `FullTextSearchBackend`.
|
|
23
|
-
*
|
|
24
|
-
* It also uses a `TranscriptIndexInterface` object that is responsible for indexing
|
|
25
|
-
* the transcipt to make reassembly much more efficient.
|
|
26
|
-
*/
|
|
27
|
-
export class SearchHandler {
|
|
28
|
-
constructor(searchBackend, transcriptIndex) {
|
|
29
|
-
this.searchBackend = searchBackend;
|
|
30
|
-
this.transcriptIndex = transcriptIndex;
|
|
31
|
-
}
|
|
32
|
-
/**
|
|
33
|
-
* This is the main method in here. It takes a search term and returns a TranscriptConfig
|
|
34
|
-
* that has been modified to insert the search results.
|
|
35
|
-
*
|
|
36
|
-
* @param {string} query
|
|
37
|
-
* @returns {TranscriptConfig}
|
|
38
|
-
* @memberof SearchHandler
|
|
39
|
-
*/
|
|
40
|
-
search(query) {
|
|
41
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
42
|
-
const searchSeparatedTranscript = yield this.getSearchSeparatedTranscript(query);
|
|
43
|
-
const newTranscriptEntries = [];
|
|
44
|
-
let searchResultIndex = 0;
|
|
45
|
-
let entryIdentifier = 1;
|
|
46
|
-
searchSeparatedTranscript.forEach((entry) => {
|
|
47
|
-
// If we encounter a match, just create a new transcript entry from it and append it.
|
|
48
|
-
// We don't care if it crosses over multiple transcript entries since we want one match,
|
|
49
|
-
// not multiple broken up by transcript entry.
|
|
50
|
-
if (entry.isSearchMatch) {
|
|
51
|
-
// find the closest source transcript to this entry
|
|
52
|
-
const startEntry = this.transcriptIndex.getTranscriptEntryAt(entry.range.startIndex);
|
|
53
|
-
if (!startEntry) {
|
|
54
|
-
return;
|
|
55
|
-
}
|
|
56
|
-
const { range } = entry;
|
|
57
|
-
const { endIndex } = range;
|
|
58
|
-
const endEntry = this.transcriptIndex.getTranscriptEntryAt(endIndex) || startEntry;
|
|
59
|
-
const newTranscriptEntry = this.createBlankTranscriptEntryConfig(startEntry.entry);
|
|
60
|
-
newTranscriptEntry.searchMatchIndex = searchResultIndex;
|
|
61
|
-
searchResultIndex += 1;
|
|
62
|
-
newTranscriptEntry.rawText = entry.text;
|
|
63
|
-
newTranscriptEntry.id = entryIdentifier;
|
|
64
|
-
entryIdentifier += 1;
|
|
65
|
-
newTranscriptEntry.end = endEntry.entry.end;
|
|
66
|
-
newTranscriptEntries.push(newTranscriptEntry);
|
|
67
|
-
return;
|
|
68
|
-
}
|
|
69
|
-
// Next loop through all of the source transcript entries to find the ones that intersect
|
|
70
|
-
// with this search result. If it intersects, we take the intersected characters from the
|
|
71
|
-
// merged transcript and make a new entry from that.
|
|
72
|
-
this.transcriptIndex.transcriptEntryRanges.forEach((indexMap) => {
|
|
73
|
-
const intersection = SearchHelper.getIntersection(entry.range, indexMap.range);
|
|
74
|
-
if (!intersection || intersection.length === 0) {
|
|
75
|
-
return;
|
|
76
|
-
}
|
|
77
|
-
const newTranscriptEntry = this.createBlankTranscriptEntryConfig(indexMap.entry);
|
|
78
|
-
const text = this.transcriptIndex.mergedTranscript.substring(intersection.startIndex, intersection.endIndex);
|
|
79
|
-
newTranscriptEntry.rawText = text.trim();
|
|
80
|
-
newTranscriptEntry.id = entryIdentifier;
|
|
81
|
-
entryIdentifier += 1;
|
|
82
|
-
newTranscriptEntries.push(newTranscriptEntry);
|
|
83
|
-
});
|
|
84
|
-
});
|
|
85
|
-
const newTranscript = new TranscriptConfig(newTranscriptEntries);
|
|
86
|
-
return new Promise(resolve => resolve(newTranscript));
|
|
87
|
-
});
|
|
88
|
-
}
|
|
89
|
-
/**
|
|
90
|
-
* Copy a transcript entry but leave the text and search result index empty.
|
|
91
|
-
*
|
|
92
|
-
* @private
|
|
93
|
-
* @param {TranscriptEntryConfig} sourceTranscriptConfig
|
|
94
|
-
* @returns {TranscriptEntryConfig}
|
|
95
|
-
* @memberof SearchHandler
|
|
96
|
-
*/
|
|
97
|
-
/* eslint-disable-next-line class-methods-use-this */
|
|
98
|
-
createBlankTranscriptEntryConfig(sourceTranscriptConfig) {
|
|
99
|
-
return new TranscriptEntryConfig(sourceTranscriptConfig.id, sourceTranscriptConfig.start, sourceTranscriptConfig.end, '', sourceTranscriptConfig.isMusic);
|
|
100
|
-
}
|
|
101
|
-
/**
|
|
102
|
-
* Search the full transcript and split up by search results and non-results. For instance,
|
|
103
|
-
* if the full transcript is `foo bar baz boop bump snap pop` and you search for `bump`,
|
|
104
|
-
* you'll get an array of 3 results back:
|
|
105
|
-
* 1. `foo bar baz boop `
|
|
106
|
-
* 2. `bump` <-- the match
|
|
107
|
-
* 3. ` snap pop`
|
|
108
|
-
*
|
|
109
|
-
* This is helpful when rebuilding the transcript later to be able to identify search results.
|
|
110
|
-
*
|
|
111
|
-
* @private
|
|
112
|
-
* @param {string} query
|
|
113
|
-
* @returns {SearchResult[]}
|
|
114
|
-
* @memberof SearchHandler
|
|
115
|
-
*/
|
|
116
|
-
getSearchSeparatedTranscript(query) {
|
|
117
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
118
|
-
const searchRanges = yield this.searchBackend.getSearchRanges(query);
|
|
119
|
-
const { mergedTranscript } = this.transcriptIndex;
|
|
120
|
-
// if there's no search results, just return a single SearchResult that is the full
|
|
121
|
-
// transcript marked as not a match.
|
|
122
|
-
if (searchRanges.length === 0) {
|
|
123
|
-
const range = new Range(0, mergedTranscript.length);
|
|
124
|
-
return new Promise(resolve => resolve([new SearchResult(range, mergedTranscript, false)]));
|
|
125
|
-
}
|
|
126
|
-
// Sort the search range results from start to end
|
|
127
|
-
// The search engine may return results in any order but while processing the results here,
|
|
128
|
-
// we are going from start to finish. If the search results are out of order, it rebuilds
|
|
129
|
-
// incorrect blocks of search results.
|
|
130
|
-
searchRanges.sort((a, b) => (a.startIndex - b.startIndex));
|
|
131
|
-
const transcriptEntries = [];
|
|
132
|
-
let startIndex = 0;
|
|
133
|
-
// Loop through all of the search result ranges and construct an array
|
|
134
|
-
// of SearchResult entries.
|
|
135
|
-
// For instance, if the full transcript is `foo bar baz boop bump snip snap`
|
|
136
|
-
// and you search for `baz`, we'll only have a single search range: Range(8, 11).
|
|
137
|
-
// We then take the text leading up to that range and store it as the "non result"
|
|
138
|
-
// so we now have Range(0, 7) and Range(8, 11).
|
|
139
|
-
// If there were more search results, we would continue from index 12 to build
|
|
140
|
-
// the next range to the next match.
|
|
141
|
-
// Finally once all the matches are finished, tack on the remaining text to the end.
|
|
142
|
-
searchRanges.forEach((searchRange) => {
|
|
143
|
-
// first find the "non-result" range, which is the current `startIndex` up to
|
|
144
|
-
// `range.startIndex` (the start of the search result)
|
|
145
|
-
const nonResultRange = new Range(startIndex, searchRange.startIndex);
|
|
146
|
-
const nonResultEntry = this.getSearchResult(nonResultRange, false);
|
|
147
|
-
transcriptEntries.push(nonResultEntry);
|
|
148
|
-
// get the search result match from the merged transcript
|
|
149
|
-
// using the current search range
|
|
150
|
-
const searchResultEntry = this.getSearchResult(searchRange, true);
|
|
151
|
-
transcriptEntries.push(searchResultEntry);
|
|
152
|
-
startIndex = searchRange.endIndex;
|
|
153
|
-
});
|
|
154
|
-
// add any remaining characters as the last final entry in the results
|
|
155
|
-
const finalResultRange = new Range(startIndex, mergedTranscript.length);
|
|
156
|
-
const finalResultEntry = this.getSearchResult(finalResultRange, false);
|
|
157
|
-
transcriptEntries.push(finalResultEntry);
|
|
158
|
-
return new Promise(resolve => resolve(transcriptEntries));
|
|
159
|
-
});
|
|
160
|
-
}
|
|
161
|
-
/**
|
|
162
|
-
* Generate a SearchResult entry from a given Range and annotate it
|
|
163
|
-
* with whether or not it is a search result match
|
|
164
|
-
*
|
|
165
|
-
* @private
|
|
166
|
-
* @param {Range} range
|
|
167
|
-
* @param {boolean} isSearchResult
|
|
168
|
-
* @returns {SearchResult}
|
|
169
|
-
* @memberof SearchHandler
|
|
170
|
-
*/
|
|
171
|
-
getSearchResult(range, isSearchResult) {
|
|
172
|
-
const { mergedTranscript } = this.transcriptIndex;
|
|
173
|
-
const text = mergedTranscript.substring(range.startIndex, range.endIndex);
|
|
174
|
-
const searchResult = new SearchResult(range, text, isSearchResult);
|
|
175
|
-
return searchResult;
|
|
176
|
-
}
|
|
177
|
-
}
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
import { TranscriptConfig, TranscriptEntryConfig } from '@internetarchive/transcript-view';
|
|
11
|
+
import { Range, SearchResult } from './search-models';
|
|
12
|
+
import { SearchHelper } from './search-helper';
|
|
13
|
+
/**
|
|
14
|
+
* This is the main entrypoint into transcript searching. It has a single
|
|
15
|
+
* public method as defined by `SearchHandlerInterface`: `search(term: string)`,
|
|
16
|
+
* that returns a `Promise<TranscriptConfig>` with a search-modified transcript.
|
|
17
|
+
* This means the original transcript entries get merged with search results since
|
|
18
|
+
* search results can span across transcript entries.
|
|
19
|
+
*
|
|
20
|
+
* It offloads the searching work to a `SearchBackendInterface` object that
|
|
21
|
+
* is responsible for returning the search result `Range` objects based on some
|
|
22
|
+
* backend search like the `LocalSearchBackend` or `FullTextSearchBackend`.
|
|
23
|
+
*
|
|
24
|
+
* It also uses a `TranscriptIndexInterface` object that is responsible for indexing
|
|
25
|
+
* the transcipt to make reassembly much more efficient.
|
|
26
|
+
*/
|
|
27
|
+
export class SearchHandler {
|
|
28
|
+
constructor(searchBackend, transcriptIndex) {
|
|
29
|
+
this.searchBackend = searchBackend;
|
|
30
|
+
this.transcriptIndex = transcriptIndex;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* This is the main method in here. It takes a search term and returns a TranscriptConfig
|
|
34
|
+
* that has been modified to insert the search results.
|
|
35
|
+
*
|
|
36
|
+
* @param {string} query
|
|
37
|
+
* @returns {TranscriptConfig}
|
|
38
|
+
* @memberof SearchHandler
|
|
39
|
+
*/
|
|
40
|
+
search(query) {
|
|
41
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
42
|
+
const searchSeparatedTranscript = yield this.getSearchSeparatedTranscript(query);
|
|
43
|
+
const newTranscriptEntries = [];
|
|
44
|
+
let searchResultIndex = 0;
|
|
45
|
+
let entryIdentifier = 1;
|
|
46
|
+
searchSeparatedTranscript.forEach((entry) => {
|
|
47
|
+
// If we encounter a match, just create a new transcript entry from it and append it.
|
|
48
|
+
// We don't care if it crosses over multiple transcript entries since we want one match,
|
|
49
|
+
// not multiple broken up by transcript entry.
|
|
50
|
+
if (entry.isSearchMatch) {
|
|
51
|
+
// find the closest source transcript to this entry
|
|
52
|
+
const startEntry = this.transcriptIndex.getTranscriptEntryAt(entry.range.startIndex);
|
|
53
|
+
if (!startEntry) {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
const { range } = entry;
|
|
57
|
+
const { endIndex } = range;
|
|
58
|
+
const endEntry = this.transcriptIndex.getTranscriptEntryAt(endIndex) || startEntry;
|
|
59
|
+
const newTranscriptEntry = this.createBlankTranscriptEntryConfig(startEntry.entry);
|
|
60
|
+
newTranscriptEntry.searchMatchIndex = searchResultIndex;
|
|
61
|
+
searchResultIndex += 1;
|
|
62
|
+
newTranscriptEntry.rawText = entry.text;
|
|
63
|
+
newTranscriptEntry.id = entryIdentifier;
|
|
64
|
+
entryIdentifier += 1;
|
|
65
|
+
newTranscriptEntry.end = endEntry.entry.end;
|
|
66
|
+
newTranscriptEntries.push(newTranscriptEntry);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
// Next loop through all of the source transcript entries to find the ones that intersect
|
|
70
|
+
// with this search result. If it intersects, we take the intersected characters from the
|
|
71
|
+
// merged transcript and make a new entry from that.
|
|
72
|
+
this.transcriptIndex.transcriptEntryRanges.forEach((indexMap) => {
|
|
73
|
+
const intersection = SearchHelper.getIntersection(entry.range, indexMap.range);
|
|
74
|
+
if (!intersection || intersection.length === 0) {
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
const newTranscriptEntry = this.createBlankTranscriptEntryConfig(indexMap.entry);
|
|
78
|
+
const text = this.transcriptIndex.mergedTranscript.substring(intersection.startIndex, intersection.endIndex);
|
|
79
|
+
newTranscriptEntry.rawText = text.trim();
|
|
80
|
+
newTranscriptEntry.id = entryIdentifier;
|
|
81
|
+
entryIdentifier += 1;
|
|
82
|
+
newTranscriptEntries.push(newTranscriptEntry);
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
const newTranscript = new TranscriptConfig(newTranscriptEntries);
|
|
86
|
+
return new Promise(resolve => resolve(newTranscript));
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Copy a transcript entry but leave the text and search result index empty.
|
|
91
|
+
*
|
|
92
|
+
* @private
|
|
93
|
+
* @param {TranscriptEntryConfig} sourceTranscriptConfig
|
|
94
|
+
* @returns {TranscriptEntryConfig}
|
|
95
|
+
* @memberof SearchHandler
|
|
96
|
+
*/
|
|
97
|
+
/* eslint-disable-next-line class-methods-use-this */
|
|
98
|
+
createBlankTranscriptEntryConfig(sourceTranscriptConfig) {
|
|
99
|
+
return new TranscriptEntryConfig(sourceTranscriptConfig.id, sourceTranscriptConfig.start, sourceTranscriptConfig.end, '', sourceTranscriptConfig.isMusic);
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Search the full transcript and split up by search results and non-results. For instance,
|
|
103
|
+
* if the full transcript is `foo bar baz boop bump snap pop` and you search for `bump`,
|
|
104
|
+
* you'll get an array of 3 results back:
|
|
105
|
+
* 1. `foo bar baz boop `
|
|
106
|
+
* 2. `bump` <-- the match
|
|
107
|
+
* 3. ` snap pop`
|
|
108
|
+
*
|
|
109
|
+
* This is helpful when rebuilding the transcript later to be able to identify search results.
|
|
110
|
+
*
|
|
111
|
+
* @private
|
|
112
|
+
* @param {string} query
|
|
113
|
+
* @returns {SearchResult[]}
|
|
114
|
+
* @memberof SearchHandler
|
|
115
|
+
*/
|
|
116
|
+
getSearchSeparatedTranscript(query) {
|
|
117
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
118
|
+
const searchRanges = yield this.searchBackend.getSearchRanges(query);
|
|
119
|
+
const { mergedTranscript } = this.transcriptIndex;
|
|
120
|
+
// if there's no search results, just return a single SearchResult that is the full
|
|
121
|
+
// transcript marked as not a match.
|
|
122
|
+
if (searchRanges.length === 0) {
|
|
123
|
+
const range = new Range(0, mergedTranscript.length);
|
|
124
|
+
return new Promise(resolve => resolve([new SearchResult(range, mergedTranscript, false)]));
|
|
125
|
+
}
|
|
126
|
+
// Sort the search range results from start to end
|
|
127
|
+
// The search engine may return results in any order but while processing the results here,
|
|
128
|
+
// we are going from start to finish. If the search results are out of order, it rebuilds
|
|
129
|
+
// incorrect blocks of search results.
|
|
130
|
+
searchRanges.sort((a, b) => (a.startIndex - b.startIndex));
|
|
131
|
+
const transcriptEntries = [];
|
|
132
|
+
let startIndex = 0;
|
|
133
|
+
// Loop through all of the search result ranges and construct an array
|
|
134
|
+
// of SearchResult entries.
|
|
135
|
+
// For instance, if the full transcript is `foo bar baz boop bump snip snap`
|
|
136
|
+
// and you search for `baz`, we'll only have a single search range: Range(8, 11).
|
|
137
|
+
// We then take the text leading up to that range and store it as the "non result"
|
|
138
|
+
// so we now have Range(0, 7) and Range(8, 11).
|
|
139
|
+
// If there were more search results, we would continue from index 12 to build
|
|
140
|
+
// the next range to the next match.
|
|
141
|
+
// Finally once all the matches are finished, tack on the remaining text to the end.
|
|
142
|
+
searchRanges.forEach((searchRange) => {
|
|
143
|
+
// first find the "non-result" range, which is the current `startIndex` up to
|
|
144
|
+
// `range.startIndex` (the start of the search result)
|
|
145
|
+
const nonResultRange = new Range(startIndex, searchRange.startIndex);
|
|
146
|
+
const nonResultEntry = this.getSearchResult(nonResultRange, false);
|
|
147
|
+
transcriptEntries.push(nonResultEntry);
|
|
148
|
+
// get the search result match from the merged transcript
|
|
149
|
+
// using the current search range
|
|
150
|
+
const searchResultEntry = this.getSearchResult(searchRange, true);
|
|
151
|
+
transcriptEntries.push(searchResultEntry);
|
|
152
|
+
startIndex = searchRange.endIndex;
|
|
153
|
+
});
|
|
154
|
+
// add any remaining characters as the last final entry in the results
|
|
155
|
+
const finalResultRange = new Range(startIndex, mergedTranscript.length);
|
|
156
|
+
const finalResultEntry = this.getSearchResult(finalResultRange, false);
|
|
157
|
+
transcriptEntries.push(finalResultEntry);
|
|
158
|
+
return new Promise(resolve => resolve(transcriptEntries));
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Generate a SearchResult entry from a given Range and annotate it
|
|
163
|
+
* with whether or not it is a search result match
|
|
164
|
+
*
|
|
165
|
+
* @private
|
|
166
|
+
* @param {Range} range
|
|
167
|
+
* @param {boolean} isSearchResult
|
|
168
|
+
* @returns {SearchResult}
|
|
169
|
+
* @memberof SearchHandler
|
|
170
|
+
*/
|
|
171
|
+
getSearchResult(range, isSearchResult) {
|
|
172
|
+
const { mergedTranscript } = this.transcriptIndex;
|
|
173
|
+
const text = mergedTranscript.substring(range.startIndex, range.endIndex);
|
|
174
|
+
const searchResult = new SearchResult(range, text, isSearchResult);
|
|
175
|
+
return searchResult;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
178
|
//# sourceMappingURL=search-handler.js.map
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { Range } from './search-models';
|
|
2
|
-
export declare class SearchHelper {
|
|
3
|
-
/**
|
|
4
|
-
* Get the intersection of two ranges and return a range or undefined if they do not intersect
|
|
5
|
-
*
|
|
6
|
-
* @param range1
|
|
7
|
-
* @param range2
|
|
8
|
-
*
|
|
9
|
-
* @returns Range|undefined
|
|
10
|
-
*/
|
|
11
|
-
static getIntersection(range1: Range, range2: Range): Range | undefined;
|
|
12
|
-
}
|
|
1
|
+
import { Range } from './search-models';
|
|
2
|
+
export declare class SearchHelper {
|
|
3
|
+
/**
|
|
4
|
+
* Get the intersection of two ranges and return a range or undefined if they do not intersect
|
|
5
|
+
*
|
|
6
|
+
* @param range1
|
|
7
|
+
* @param range2
|
|
8
|
+
*
|
|
9
|
+
* @returns Range|undefined
|
|
10
|
+
*/
|
|
11
|
+
static getIntersection(range1: Range, range2: Range): Range | undefined;
|
|
12
|
+
}
|
|
@@ -1,23 +1,23 @@
|
|
|
1
|
-
import { Range } from './search-models';
|
|
2
|
-
export class SearchHelper {
|
|
3
|
-
/**
|
|
4
|
-
* Get the intersection of two ranges and return a range or undefined if they do not intersect
|
|
5
|
-
*
|
|
6
|
-
* @param range1
|
|
7
|
-
* @param range2
|
|
8
|
-
*
|
|
9
|
-
* @returns Range|undefined
|
|
10
|
-
*/
|
|
11
|
-
static getIntersection(range1, range2) {
|
|
12
|
-
// get the range with the smaller starting point (min) and greater start (max)
|
|
13
|
-
const minRange = range1.startIndex < range2.startIndex ? range1 : range2;
|
|
14
|
-
const maxRange = minRange === range1 ? range2 : range1;
|
|
15
|
-
// min ends before max starts -> no intersection
|
|
16
|
-
if (minRange.endIndex < maxRange.startIndex) {
|
|
17
|
-
return undefined; // the ranges don't intersect
|
|
18
|
-
}
|
|
19
|
-
const endIndex = minRange.endIndex < maxRange.endIndex ? minRange.endIndex : maxRange.endIndex;
|
|
20
|
-
return new Range(maxRange.startIndex, endIndex);
|
|
21
|
-
}
|
|
22
|
-
}
|
|
1
|
+
import { Range } from './search-models';
|
|
2
|
+
export class SearchHelper {
|
|
3
|
+
/**
|
|
4
|
+
* Get the intersection of two ranges and return a range or undefined if they do not intersect
|
|
5
|
+
*
|
|
6
|
+
* @param range1
|
|
7
|
+
* @param range2
|
|
8
|
+
*
|
|
9
|
+
* @returns Range|undefined
|
|
10
|
+
*/
|
|
11
|
+
static getIntersection(range1, range2) {
|
|
12
|
+
// get the range with the smaller starting point (min) and greater start (max)
|
|
13
|
+
const minRange = range1.startIndex < range2.startIndex ? range1 : range2;
|
|
14
|
+
const maxRange = minRange === range1 ? range2 : range1;
|
|
15
|
+
// min ends before max starts -> no intersection
|
|
16
|
+
if (minRange.endIndex < maxRange.startIndex) {
|
|
17
|
+
return undefined; // the ranges don't intersect
|
|
18
|
+
}
|
|
19
|
+
const endIndex = minRange.endIndex < maxRange.endIndex ? minRange.endIndex : maxRange.endIndex;
|
|
20
|
+
return new Range(maxRange.startIndex, endIndex);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
23
|
//# sourceMappingURL=search-helper.js.map
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { TranscriptConfig } from '@internetarchive/transcript-view';
|
|
2
|
+
import { Range, TranscriptEntryRange } from './search-models';
|
|
3
|
+
export declare class SearchIndex {
|
|
4
|
+
/**
|
|
5
|
+
* Make these properties readonly. We don't want outside classes touching the private storage.
|
|
6
|
+
*
|
|
7
|
+
* @readonly
|
|
8
|
+
* @type {string}
|
|
9
|
+
* @memberof SearchIndex
|
|
10
|
+
*/
|
|
11
|
+
get mergedTranscript(): string;
|
|
12
|
+
get transcriptEntryRanges(): TranscriptEntryRange[];
|
|
13
|
+
/**
|
|
14
|
+
* This gets populated as part of the search index build. It merges all of the transcript
|
|
15
|
+
* entries together so we can search it as a single document instead of a bunch of
|
|
16
|
+
* individual entries. This allows searches to cross over transcript entries.
|
|
17
|
+
*
|
|
18
|
+
* It also indexes the entries so we can quickly look them up later.
|
|
19
|
+
*
|
|
20
|
+
* @private
|
|
21
|
+
* @memberof SearchIndex
|
|
22
|
+
*/
|
|
23
|
+
private searchIndexCache;
|
|
24
|
+
/**
|
|
25
|
+
* Find the closes TranscriptEntryRange to the given overallCharIndex
|
|
26
|
+
*
|
|
27
|
+
* @param overallCharIndex
|
|
28
|
+
* @returns {TranscriptEntryRange | undefined}
|
|
29
|
+
*/
|
|
30
|
+
getTranscriptEntryAt(overallCharIndex: number): TranscriptEntryRange | undefined;
|
|
31
|
+
/**
|
|
32
|
+
* Finds all of the ranges of all the search results across the entire transcript.
|
|
33
|
+
*
|
|
34
|
+
* @private
|
|
35
|
+
* @param {string} term
|
|
36
|
+
* @returns {Range[]}
|
|
37
|
+
* @memberof SearchIndex
|
|
38
|
+
*/
|
|
39
|
+
getSearchRanges(term: string): Range[];
|
|
40
|
+
constructor(transcriptConfig: TranscriptConfig);
|
|
41
|
+
/**
|
|
42
|
+
* Build the search index. This method does two things:
|
|
43
|
+
* 1. Builds the "merged transcript" with all of the text combined into one long transcript.
|
|
44
|
+
* This allows us to search the entire thing at once instead of entry by entry.
|
|
45
|
+
* 2. For each entry, get the start and end index of that particular entry within the overall
|
|
46
|
+
* transcript. This allows us to reconstruct the transcript later from the original transcript
|
|
47
|
+
* and the search results.
|
|
48
|
+
*
|
|
49
|
+
* @private
|
|
50
|
+
* @memberof SearchHandler
|
|
51
|
+
*/
|
|
52
|
+
private buildIndex;
|
|
53
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { Range, TranscriptEntryRange } from './search-models';
|
|
2
|
+
class SearchIndexCache {
|
|
3
|
+
constructor(mergedTranscript = '', transcriptEntryRanges = []) {
|
|
4
|
+
/**
|
|
5
|
+
* This gets populated as part of the search index build. It merges all of the transcript
|
|
6
|
+
* entries together so we can search it as a single document instead of a bunch of
|
|
7
|
+
* individual entries. This allows searches to cross over transcript entries.
|
|
8
|
+
*
|
|
9
|
+
* NOTE: When the mergedTranscript gets created, spaces are put between each transcript
|
|
10
|
+
* entry, otherwise the words would run into each other. We account for this in the
|
|
11
|
+
* indices above.
|
|
12
|
+
*
|
|
13
|
+
* @type {string}
|
|
14
|
+
* @memberof SearchIndexCache
|
|
15
|
+
*/
|
|
16
|
+
this.mergedTranscript = '';
|
|
17
|
+
this.mergedTranscriptLowercased = '';
|
|
18
|
+
/**
|
|
19
|
+
* This gets populated as part of the search index build. It maps the start and end indicies
|
|
20
|
+
* of all of the transcript entries so we can quickly look up where an entry is in the
|
|
21
|
+
* overall transcript.
|
|
22
|
+
*
|
|
23
|
+
* @type {TranscriptEntryRange[]}
|
|
24
|
+
* @memberof SearchIndexCache
|
|
25
|
+
*/
|
|
26
|
+
this.transcriptEntryRanges = [];
|
|
27
|
+
this.mergedTranscript = mergedTranscript;
|
|
28
|
+
this.transcriptEntryRanges = transcriptEntryRanges;
|
|
29
|
+
this.mergedTranscriptLowercased = mergedTranscript.toLowerCase();
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
export class SearchIndex {
|
|
33
|
+
constructor(transcriptConfig) {
|
|
34
|
+
/**
|
|
35
|
+
* This gets populated as part of the search index build. It merges all of the transcript
|
|
36
|
+
* entries together so we can search it as a single document instead of a bunch of
|
|
37
|
+
* individual entries. This allows searches to cross over transcript entries.
|
|
38
|
+
*
|
|
39
|
+
* It also indexes the entries so we can quickly look them up later.
|
|
40
|
+
*
|
|
41
|
+
* @private
|
|
42
|
+
* @memberof SearchIndex
|
|
43
|
+
*/
|
|
44
|
+
this.searchIndexCache = new SearchIndexCache();
|
|
45
|
+
this.buildIndex(transcriptConfig);
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Make these properties readonly. We don't want outside classes touching the private storage.
|
|
49
|
+
*
|
|
50
|
+
* @readonly
|
|
51
|
+
* @type {string}
|
|
52
|
+
* @memberof SearchIndex
|
|
53
|
+
*/
|
|
54
|
+
get mergedTranscript() {
|
|
55
|
+
return this.searchIndexCache.mergedTranscript;
|
|
56
|
+
}
|
|
57
|
+
get transcriptEntryRanges() {
|
|
58
|
+
return this.searchIndexCache.transcriptEntryRanges;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Find the closes TranscriptEntryRange to the given overallCharIndex
|
|
62
|
+
*
|
|
63
|
+
* @param overallCharIndex
|
|
64
|
+
* @returns {TranscriptEntryRange | undefined}
|
|
65
|
+
*/
|
|
66
|
+
getTranscriptEntryAt(overallCharIndex) {
|
|
67
|
+
return this.searchIndexCache.transcriptEntryRanges.find(entry => {
|
|
68
|
+
const { range } = entry;
|
|
69
|
+
return range.endIndex > overallCharIndex && range.startIndex <= overallCharIndex;
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Finds all of the ranges of all the search results across the entire transcript.
|
|
74
|
+
*
|
|
75
|
+
* @private
|
|
76
|
+
* @param {string} term
|
|
77
|
+
* @returns {Range[]}
|
|
78
|
+
* @memberof SearchIndex
|
|
79
|
+
*/
|
|
80
|
+
getSearchRanges(term) {
|
|
81
|
+
const ranges = [];
|
|
82
|
+
const termLowerCased = term.toLowerCase();
|
|
83
|
+
let index = -1;
|
|
84
|
+
/* eslint-disable-next-line no-cond-assign */
|
|
85
|
+
while ((index = this.searchIndexCache.mergedTranscriptLowercased.indexOf(termLowerCased, index + 1)) !== -1) {
|
|
86
|
+
const newRange = new Range(index, index + termLowerCased.length);
|
|
87
|
+
ranges.push(newRange);
|
|
88
|
+
}
|
|
89
|
+
return ranges;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Build the search index. This method does two things:
|
|
93
|
+
* 1. Builds the "merged transcript" with all of the text combined into one long transcript.
|
|
94
|
+
* This allows us to search the entire thing at once instead of entry by entry.
|
|
95
|
+
* 2. For each entry, get the start and end index of that particular entry within the overall
|
|
96
|
+
* transcript. This allows us to reconstruct the transcript later from the original transcript
|
|
97
|
+
* and the search results.
|
|
98
|
+
*
|
|
99
|
+
* @private
|
|
100
|
+
* @memberof SearchHandler
|
|
101
|
+
*/
|
|
102
|
+
buildIndex(transcriptConfig) {
|
|
103
|
+
let startIndex = 0;
|
|
104
|
+
const transcriptEntryRanges = [];
|
|
105
|
+
let mergedTranscript = '';
|
|
106
|
+
transcriptConfig.entries.forEach((entry) => {
|
|
107
|
+
const { displayText } = entry;
|
|
108
|
+
const indexMapRange = new Range(startIndex, startIndex + displayText.length);
|
|
109
|
+
const indexMap = new TranscriptEntryRange(entry, indexMapRange);
|
|
110
|
+
transcriptEntryRanges.push(indexMap);
|
|
111
|
+
mergedTranscript += `${entry.displayText} `;
|
|
112
|
+
startIndex = mergedTranscript.length;
|
|
113
|
+
});
|
|
114
|
+
mergedTranscript = mergedTranscript.trim();
|
|
115
|
+
this.searchIndexCache = new SearchIndexCache(mergedTranscript, transcriptEntryRanges);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
//# sourceMappingURL=search-index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"search-index.js","sourceRoot":"","sources":["../../../src/search-handler/search-index.ts"],"names":[],"mappings":"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"}
|