@readium/navigator-html-injectables 1.2.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/README.MD +13 -0
- package/dist/index.js +3144 -0
- package/dist/index.umd.cjs +69 -0
- package/package.json +58 -0
- package/src/Loader.ts +85 -0
- package/src/comms/comms.ts +168 -0
- package/src/comms/index.ts +3 -0
- package/src/comms/keys.ts +42 -0
- package/src/comms/mid.ts +5 -0
- package/src/helpers/animation.ts +5 -0
- package/src/helpers/css.ts +16 -0
- package/src/helpers/document.ts +43 -0
- package/src/helpers/dom.ts +126 -0
- package/src/helpers/locator.ts +67 -0
- package/src/helpers/rect.ts +301 -0
- package/src/helpers/scrollSnapperHelper.ts +43 -0
- package/src/index.ts +3 -0
- package/src/modules/Decorator.ts +462 -0
- package/src/modules/Module.ts +23 -0
- package/src/modules/ModuleLibrary.ts +44 -0
- package/src/modules/Peripherals.ts +148 -0
- package/src/modules/index.ts +3 -0
- package/src/modules/setup/FixedSetup.ts +73 -0
- package/src/modules/setup/ReflowableSetup.ts +65 -0
- package/src/modules/setup/Setup.ts +131 -0
- package/src/modules/snapper/ColumnSnapper.ts +468 -0
- package/src/modules/snapper/ScrollSnapper.ts +168 -0
- package/src/modules/snapper/Snapper.ts +49 -0
- package/src/vendor/approx-string-match/LICENSE +21 -0
- package/src/vendor/approx-string-match/README.MD +1 -0
- package/src/vendor/approx-string-match/index.ts +362 -0
- package/src/vendor/hypothesis/README.MD +1 -0
- package/src/vendor/hypothesis/anchoring/api-types.ts +309 -0
- package/src/vendor/hypothesis/anchoring/html.ts +134 -0
- package/src/vendor/hypothesis/anchoring/match-quote.ts +163 -0
- package/src/vendor/hypothesis/anchoring/placeholder.ts +59 -0
- package/src/vendor/hypothesis/anchoring/text-range.ts +327 -0
- package/src/vendor/hypothesis/anchoring/trim-range.ts +220 -0
- package/src/vendor/hypothesis/anchoring/types.ts +377 -0
- package/src/vendor/hypothesis/anchoring/xpath.ts +164 -0
- package/src/vendor/hypothesis/tsconfig.json +33 -0
- package/src/vendor/hypothesis/types/shared.ts +40 -0
- package/types/src/Loader.d.ts +33 -0
- package/types/src/comms/comms.d.ts +40 -0
- package/types/src/comms/index.d.ts +3 -0
- package/types/src/comms/keys.d.ts +2 -0
- package/types/src/comms/mid.d.ts +1 -0
- package/types/src/helpers/animation.d.ts +1 -0
- package/types/src/helpers/css.d.ts +4 -0
- package/types/src/helpers/document.d.ts +8 -0
- package/types/src/helpers/dom.d.ts +15 -0
- package/types/src/helpers/locator.d.ts +2 -0
- package/types/src/helpers/rect.d.ts +10 -0
- package/types/src/helpers/scrollSnapperHelper.d.ts +5 -0
- package/types/src/index.d.ts +3 -0
- package/types/src/modules/Decorator.d.ts +43 -0
- package/types/src/modules/Module.d.ts +11 -0
- package/types/src/modules/ModuleLibrary.d.ts +5 -0
- package/types/src/modules/Peripherals.d.ts +39 -0
- package/types/src/modules/ReflowablePeripherals.d.ts +37 -0
- package/types/src/modules/index.d.ts +3 -0
- package/types/src/modules/setup/FixedSetup.d.ts +9 -0
- package/types/src/modules/setup/ReflowableSetup.d.ts +9 -0
- package/types/src/modules/setup/Setup.d.ts +17 -0
- package/types/src/modules/snapper/ColumnSnapper.d.ts +41 -0
- package/types/src/modules/snapper/ScrollSnapper.d.ts +16 -0
- package/types/src/modules/snapper/Snapper.d.ts +11 -0
- package/types/src/vendor/approx-string-match/index.d.ts +54 -0
- package/types/src/vendor/hypothesis/anchoring/api-types.d.ts +266 -0
- package/types/src/vendor/hypothesis/anchoring/html.d.ts +17 -0
- package/types/src/vendor/hypothesis/anchoring/match-quote.d.ts +30 -0
- package/types/src/vendor/hypothesis/anchoring/placeholder.d.ts +32 -0
- package/types/src/vendor/hypothesis/anchoring/text-range.d.ts +103 -0
- package/types/src/vendor/hypothesis/anchoring/trim-range.d.ts +17 -0
- package/types/src/vendor/hypothesis/anchoring/types.d.ts +102 -0
- package/types/src/vendor/hypothesis/anchoring/xpath.d.ts +15 -0
- package/types/src/vendor/hypothesis/types/shared.d.ts +32 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2020 Robert Knight
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
From https://github.com/robertknight/approx-string-match-js/tree/master
|
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Implementation of Myers' online approximate string matching algorithm [1],
|
|
3
|
+
* with additional optimizations suggested by [2].
|
|
4
|
+
*
|
|
5
|
+
* This has O((k/w) * n) expected-time where `n` is the length of the
|
|
6
|
+
* text, `k` is the maximum number of errors allowed (always <= the pattern
|
|
7
|
+
* length) and `w` is the word size. Because JS only supports bitwise operations
|
|
8
|
+
* on 32 bit integers, `w` is 32.
|
|
9
|
+
*
|
|
10
|
+
* As far as I am aware, there aren't any online algorithms which are
|
|
11
|
+
* significantly better for a wide range of input parameters. The problem can be
|
|
12
|
+
* solved faster using "filter then verify" approaches which first filter out
|
|
13
|
+
* regions of the text that cannot match using a "cheap" check and then verify
|
|
14
|
+
* the remaining potential matches. The verify step requires an algorithm such
|
|
15
|
+
* as this one however.
|
|
16
|
+
*
|
|
17
|
+
* The algorithm's approach is essentially to optimize the classic dynamic
|
|
18
|
+
* programming solution to the problem by computing columns of the matrix in
|
|
19
|
+
* word-sized chunks (ie. dealing with 32 chars of the pattern at a time) and
|
|
20
|
+
* avoiding calculating regions of the matrix where the minimum error count is
|
|
21
|
+
* guaranteed to exceed the input threshold.
|
|
22
|
+
*
|
|
23
|
+
* The paper consists of two parts, the first describes the core algorithm for
|
|
24
|
+
* matching patterns <= the size of a word (implemented by `advanceBlock` here).
|
|
25
|
+
* The second uses the core algorithm as part of a larger block-based algorithm
|
|
26
|
+
* to handle longer patterns.
|
|
27
|
+
*
|
|
28
|
+
* [1] G. Myers, “A Fast Bit-Vector Algorithm for Approximate String Matching
|
|
29
|
+
* Based on Dynamic Programming,” vol. 46, no. 3, pp. 395–415, 1999.
|
|
30
|
+
*
|
|
31
|
+
* [2] Šošić, M. (2014). An simd dynamic programming c/c++ library (Doctoral
|
|
32
|
+
* dissertation, Fakultet Elektrotehnike i računarstva, Sveučilište u Zagrebu).
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Represents a match returned by a call to `search`.
|
|
37
|
+
*/
|
|
38
|
+
export interface Match {
|
|
39
|
+
/** Start offset of match in text. */
|
|
40
|
+
start: number;
|
|
41
|
+
/** End offset of match in text. */
|
|
42
|
+
end: number;
|
|
43
|
+
/**
|
|
44
|
+
* The number of differences (insertions, deletions or substitutions) between
|
|
45
|
+
* the pattern and the approximate match in the text.
|
|
46
|
+
*/
|
|
47
|
+
errors: number;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function reverse(s: string) {
|
|
51
|
+
return s.split("").reverse().join("");
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Given the ends of approximate matches for `pattern` in `text`, find
|
|
56
|
+
* the start of the matches.
|
|
57
|
+
*
|
|
58
|
+
* @return Matches with the `start` property set.
|
|
59
|
+
*/
|
|
60
|
+
function findMatchStarts(text: string, pattern: string, matches: Match[]) {
|
|
61
|
+
const patRev = reverse(pattern);
|
|
62
|
+
|
|
63
|
+
return matches.map((m) => {
|
|
64
|
+
// Find start of each match by reversing the pattern and matching segment
|
|
65
|
+
// of text and searching for an approx match with the same number of
|
|
66
|
+
// errors.
|
|
67
|
+
const minStart = Math.max(0, m.end - pattern.length - m.errors);
|
|
68
|
+
const textRev = reverse(text.slice(minStart, m.end));
|
|
69
|
+
|
|
70
|
+
// If there are multiple possible start points, choose the one that
|
|
71
|
+
// maximizes the length of the match.
|
|
72
|
+
const start = findMatchEnds(textRev, patRev, m.errors).reduce((min, rm) => {
|
|
73
|
+
if (m.end - rm.end < min) {
|
|
74
|
+
return m.end - rm.end;
|
|
75
|
+
}
|
|
76
|
+
return min;
|
|
77
|
+
}, m.end);
|
|
78
|
+
|
|
79
|
+
return {
|
|
80
|
+
start,
|
|
81
|
+
end: m.end,
|
|
82
|
+
errors: m.errors,
|
|
83
|
+
};
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Internal context used when calculating blocks of a column.
|
|
89
|
+
*/
|
|
90
|
+
interface Context {
|
|
91
|
+
/**
|
|
92
|
+
* Bit-arrays of positive vertical deltas.
|
|
93
|
+
*
|
|
94
|
+
* ie. `P[b][i]` is set if the vertical delta for the i'th row in the b'th
|
|
95
|
+
* block is positive.
|
|
96
|
+
*/
|
|
97
|
+
P: Uint32Array;
|
|
98
|
+
/** Bit-arrays of negative vertical deltas. */
|
|
99
|
+
M: Uint32Array;
|
|
100
|
+
/** Bit masks with a single bit set indicating the last row in each block. */
|
|
101
|
+
lastRowMask: Uint32Array;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Return 1 if a number is non-zero or zero otherwise, without using
|
|
106
|
+
* conditional operators.
|
|
107
|
+
*
|
|
108
|
+
* This should get inlined into `advanceBlock` below by the JIT.
|
|
109
|
+
*
|
|
110
|
+
* Adapted from https://stackoverflow.com/a/3912218/434243
|
|
111
|
+
*/
|
|
112
|
+
function oneIfNotZero(n: number) {
|
|
113
|
+
return ((n | -n) >> 31) & 1;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Block calculation step of the algorithm.
|
|
118
|
+
*
|
|
119
|
+
* From Fig 8. on p. 408 of [1], additionally optimized to replace conditional
|
|
120
|
+
* checks with bitwise operations as per Section 4.2.3 of [2].
|
|
121
|
+
*
|
|
122
|
+
* @param ctx - The pattern context object
|
|
123
|
+
* @param peq - The `peq` array for the current character (`ctx.peq.get(ch)`)
|
|
124
|
+
* @param b - The block level
|
|
125
|
+
* @param hIn - Horizontal input delta ∈ {1,0,-1}
|
|
126
|
+
* @return Horizontal output delta ∈ {1,0,-1}
|
|
127
|
+
*/
|
|
128
|
+
function advanceBlock(ctx: Context, peq: Uint32Array, b: number, hIn: number) {
|
|
129
|
+
let pV = ctx.P[b];
|
|
130
|
+
let mV = ctx.M[b];
|
|
131
|
+
const hInIsNegative = hIn >>> 31; // 1 if hIn < 0 or 0 otherwise.
|
|
132
|
+
const eq = peq[b] | hInIsNegative;
|
|
133
|
+
|
|
134
|
+
// Step 1: Compute horizontal deltas.
|
|
135
|
+
const xV = eq | mV;
|
|
136
|
+
const xH = (((eq & pV) + pV) ^ pV) | eq;
|
|
137
|
+
|
|
138
|
+
let pH = mV | ~(xH | pV);
|
|
139
|
+
let mH = pV & xH;
|
|
140
|
+
|
|
141
|
+
// Step 2: Update score (value of last row of this block).
|
|
142
|
+
const hOut =
|
|
143
|
+
oneIfNotZero(pH & ctx.lastRowMask[b]) -
|
|
144
|
+
oneIfNotZero(mH & ctx.lastRowMask[b]);
|
|
145
|
+
|
|
146
|
+
// Step 3: Update vertical deltas for use when processing next char.
|
|
147
|
+
pH <<= 1;
|
|
148
|
+
mH <<= 1;
|
|
149
|
+
|
|
150
|
+
mH |= hInIsNegative;
|
|
151
|
+
pH |= oneIfNotZero(hIn) - hInIsNegative; // set pH[0] if hIn > 0
|
|
152
|
+
|
|
153
|
+
pV = mH | ~(xV | pH);
|
|
154
|
+
mV = pH & xV;
|
|
155
|
+
|
|
156
|
+
ctx.P[b] = pV;
|
|
157
|
+
ctx.M[b] = mV;
|
|
158
|
+
|
|
159
|
+
return hOut;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Find the ends and error counts for matches of `pattern` in `text`.
|
|
164
|
+
*
|
|
165
|
+
* Only the matches with the lowest error count are reported. Other matches
|
|
166
|
+
* with error counts <= maxErrors are discarded.
|
|
167
|
+
*
|
|
168
|
+
* This is the block-based search algorithm from Fig. 9 on p.410 of [1].
|
|
169
|
+
*/
|
|
170
|
+
function findMatchEnds(text: string, pattern: string, maxErrors: number) {
|
|
171
|
+
if (pattern.length === 0) {
|
|
172
|
+
return [];
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Clamp error count so we can rely on the `maxErrors` and `pattern.length`
|
|
176
|
+
// rows being in the same block below.
|
|
177
|
+
maxErrors = Math.min(maxErrors, pattern.length);
|
|
178
|
+
|
|
179
|
+
const matches = [];
|
|
180
|
+
|
|
181
|
+
// Word size.
|
|
182
|
+
const w = 32;
|
|
183
|
+
|
|
184
|
+
// Index of maximum block level.
|
|
185
|
+
const bMax = Math.ceil(pattern.length / w) - 1;
|
|
186
|
+
|
|
187
|
+
// Context used across block calculations.
|
|
188
|
+
const ctx = {
|
|
189
|
+
P: new Uint32Array(bMax + 1),
|
|
190
|
+
M: new Uint32Array(bMax + 1),
|
|
191
|
+
lastRowMask: new Uint32Array(bMax + 1),
|
|
192
|
+
};
|
|
193
|
+
ctx.lastRowMask.fill(1 << 31);
|
|
194
|
+
ctx.lastRowMask[bMax] = 1 << (pattern.length - 1) % w;
|
|
195
|
+
|
|
196
|
+
// Dummy "peq" array for chars in the text which do not occur in the pattern.
|
|
197
|
+
const emptyPeq = new Uint32Array(bMax + 1);
|
|
198
|
+
|
|
199
|
+
// Map of UTF-16 character code to bit vector indicating positions in the
|
|
200
|
+
// pattern that equal that character.
|
|
201
|
+
const peq = new Map<number, Uint32Array>();
|
|
202
|
+
|
|
203
|
+
// Version of `peq` that only stores mappings for small characters. This
|
|
204
|
+
// allows faster lookups when iterating through the text because a simple
|
|
205
|
+
// array lookup can be done instead of a hash table lookup.
|
|
206
|
+
const asciiPeq = [] as Uint32Array[];
|
|
207
|
+
for (let i = 0; i < 256; i++) {
|
|
208
|
+
asciiPeq.push(emptyPeq);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Calculate `ctx.peq` - a map of character values to bitmasks indicating
|
|
212
|
+
// positions of that character within the pattern, where each bit represents
|
|
213
|
+
// a position in the pattern.
|
|
214
|
+
for (let c = 0; c < pattern.length; c += 1) {
|
|
215
|
+
const val = pattern.charCodeAt(c);
|
|
216
|
+
if (peq.has(val)) {
|
|
217
|
+
// Duplicate char in pattern.
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const charPeq = new Uint32Array(bMax + 1);
|
|
222
|
+
peq.set(val, charPeq);
|
|
223
|
+
if (val < asciiPeq.length) {
|
|
224
|
+
asciiPeq[val] = charPeq;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
for (let b = 0; b <= bMax; b += 1) {
|
|
228
|
+
charPeq[b] = 0;
|
|
229
|
+
|
|
230
|
+
// Set all the bits where the pattern matches the current char (ch).
|
|
231
|
+
// For indexes beyond the end of the pattern, always set the bit as if the
|
|
232
|
+
// pattern contained a wildcard char in that position.
|
|
233
|
+
for (let r = 0; r < w; r += 1) {
|
|
234
|
+
const idx = b * w + r;
|
|
235
|
+
if (idx >= pattern.length) {
|
|
236
|
+
continue;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const match = pattern.charCodeAt(idx) === val;
|
|
240
|
+
if (match) {
|
|
241
|
+
charPeq[b] |= 1 << r;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Index of last-active block level in the column.
|
|
248
|
+
let y = Math.max(0, Math.ceil(maxErrors / w) - 1);
|
|
249
|
+
|
|
250
|
+
// Initialize maximum error count at bottom of each block.
|
|
251
|
+
const score = new Uint32Array(bMax + 1);
|
|
252
|
+
for (let b = 0; b <= y; b += 1) {
|
|
253
|
+
score[b] = (b + 1) * w;
|
|
254
|
+
}
|
|
255
|
+
score[bMax] = pattern.length;
|
|
256
|
+
|
|
257
|
+
// Initialize vertical deltas for each block.
|
|
258
|
+
for (let b = 0; b <= y; b += 1) {
|
|
259
|
+
ctx.P[b] = ~0;
|
|
260
|
+
ctx.M[b] = 0;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Process each char of the text, computing the error count for `w` chars of
|
|
264
|
+
// the pattern at a time.
|
|
265
|
+
for (let j = 0; j < text.length; j += 1) {
|
|
266
|
+
// Lookup the bitmask representing the positions of the current char from
|
|
267
|
+
// the text within the pattern.
|
|
268
|
+
const charCode = text.charCodeAt(j);
|
|
269
|
+
let charPeq;
|
|
270
|
+
|
|
271
|
+
if (charCode < asciiPeq.length) {
|
|
272
|
+
// Fast array lookup.
|
|
273
|
+
charPeq = asciiPeq[charCode];
|
|
274
|
+
} else {
|
|
275
|
+
// Slower hash table lookup.
|
|
276
|
+
charPeq = peq.get(charCode);
|
|
277
|
+
if (typeof charPeq === "undefined") {
|
|
278
|
+
charPeq = emptyPeq;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// Calculate error count for blocks that we definitely have to process for
|
|
283
|
+
// this column.
|
|
284
|
+
let carry = 0;
|
|
285
|
+
for (let b = 0; b <= y; b += 1) {
|
|
286
|
+
carry = advanceBlock(ctx, charPeq, b, carry);
|
|
287
|
+
score[b] += carry;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// Check if we also need to compute an additional block, or if we can reduce
|
|
291
|
+
// the number of blocks processed for the next column.
|
|
292
|
+
if (
|
|
293
|
+
score[y] - carry <= maxErrors &&
|
|
294
|
+
y < bMax &&
|
|
295
|
+
(charPeq[y + 1] & 1 || carry < 0)
|
|
296
|
+
) {
|
|
297
|
+
// Error count for bottom block is under threshold, increase the number of
|
|
298
|
+
// blocks processed for this column & next by 1.
|
|
299
|
+
y += 1;
|
|
300
|
+
|
|
301
|
+
ctx.P[y] = ~0;
|
|
302
|
+
ctx.M[y] = 0;
|
|
303
|
+
|
|
304
|
+
let maxBlockScore;
|
|
305
|
+
if (y === bMax) {
|
|
306
|
+
const remainder = pattern.length % w;
|
|
307
|
+
maxBlockScore = remainder === 0 ? w : remainder;
|
|
308
|
+
} else {
|
|
309
|
+
maxBlockScore = w;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
score[y] =
|
|
313
|
+
score[y - 1] +
|
|
314
|
+
maxBlockScore -
|
|
315
|
+
carry +
|
|
316
|
+
advanceBlock(ctx, charPeq, y, carry);
|
|
317
|
+
} else {
|
|
318
|
+
// Error count for bottom block exceeds threshold, reduce the number of
|
|
319
|
+
// blocks processed for the next column.
|
|
320
|
+
while (y > 0 && score[y] >= maxErrors + w) {
|
|
321
|
+
y -= 1;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// If error count is under threshold, report a match.
|
|
326
|
+
if (y === bMax && score[y] <= maxErrors) {
|
|
327
|
+
if (score[y] < maxErrors) {
|
|
328
|
+
// Discard any earlier, worse matches.
|
|
329
|
+
matches.splice(0, matches.length);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
matches.push({
|
|
333
|
+
start: -1,
|
|
334
|
+
end: j + 1,
|
|
335
|
+
errors: score[y],
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
// Because `search` only reports the matches with the lowest error count,
|
|
339
|
+
// we can "ratchet down" the max error threshold whenever a match is
|
|
340
|
+
// encountered and thereby save a small amount of work for the remainder
|
|
341
|
+
// of the text.
|
|
342
|
+
maxErrors = score[y];
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
return matches;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* Search for the closest matches for `pattern` in `text`.
|
|
351
|
+
*
|
|
352
|
+
* Returns all matches that have the lowest number of errors, or an empty
|
|
353
|
+
* array if no match was found with `maxErrors` or fewer errors.
|
|
354
|
+
*/
|
|
355
|
+
export default function search(
|
|
356
|
+
text: string,
|
|
357
|
+
pattern: string,
|
|
358
|
+
maxErrors: number
|
|
359
|
+
): Match[] {
|
|
360
|
+
const matches = findMatchEnds(text, pattern, maxErrors);
|
|
361
|
+
return findMatchStarts(text, pattern, matches);
|
|
362
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
The code in this module has been directly copied from https://github.com/hypothesis/client/tree/529a0f8e2242d07736d751715974a4356c0a69f1/src/annotator/anchoring, released under the 2-Clause BSD license.
|
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
import type { ClientAnnotationData } from '../types/shared';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Type definitions for objects returned from the Hypothesis API.
|
|
5
|
+
*
|
|
6
|
+
* The canonical reference is the API documentation at
|
|
7
|
+
* https://h.readthedocs.io/en/latest/api-reference/
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Metadata specifying how to call an API route.
|
|
12
|
+
*/
|
|
13
|
+
export type RouteMetadata = {
|
|
14
|
+
/** HTTP method */
|
|
15
|
+
method: string;
|
|
16
|
+
/** URL template */
|
|
17
|
+
url: string;
|
|
18
|
+
/** Description of API route */
|
|
19
|
+
desc: string;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
/** A nested map of API route name to route metadata. */
|
|
23
|
+
export type RouteMap = { [key: string]: RouteMap | RouteMetadata };
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Structure of the API index response (`/api`).
|
|
27
|
+
*/
|
|
28
|
+
export type IndexResponse = {
|
|
29
|
+
links: RouteMap;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Structure of the Hypothesis links response (`/api/links`).
|
|
34
|
+
*
|
|
35
|
+
* This is a map of link name (eg. "account.settings") to URL. The URL may
|
|
36
|
+
* include ":"-prefixed placeholders/variables.
|
|
37
|
+
*/
|
|
38
|
+
export type LinksResponse = Record<string, string>;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Selector which indicates the time range within a video or audio file that
|
|
42
|
+
* an annotation refers to.
|
|
43
|
+
*/
|
|
44
|
+
export type MediaTimeSelector = {
|
|
45
|
+
type: 'MediaTimeSelector';
|
|
46
|
+
|
|
47
|
+
/** Offset from start of media in seconds. */
|
|
48
|
+
start: number;
|
|
49
|
+
/** Offset from start of media in seconds. */
|
|
50
|
+
end: number;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Selector which identifies a document region using the selected text plus
|
|
55
|
+
* the surrounding context.
|
|
56
|
+
*/
|
|
57
|
+
export type TextQuoteSelector = {
|
|
58
|
+
type: 'TextQuoteSelector';
|
|
59
|
+
exact: string;
|
|
60
|
+
prefix?: string;
|
|
61
|
+
suffix?: string;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Selector which identifies a document region using UTF-16 character offsets
|
|
66
|
+
* in the document body's `textContent`.
|
|
67
|
+
*/
|
|
68
|
+
export type TextPositionSelector = {
|
|
69
|
+
type: 'TextPositionSelector';
|
|
70
|
+
start: number;
|
|
71
|
+
end: number;
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Selector which identifies a document region using XPaths and character offsets.
|
|
76
|
+
*/
|
|
77
|
+
export type RangeSelector = {
|
|
78
|
+
type: 'RangeSelector';
|
|
79
|
+
startContainer: string;
|
|
80
|
+
endContainer: string;
|
|
81
|
+
startOffset: number;
|
|
82
|
+
endOffset: number;
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Selector which identifies the Content Document within an EPUB that an
|
|
87
|
+
* annotation was made in.
|
|
88
|
+
*/
|
|
89
|
+
export type EPUBContentSelector = {
|
|
90
|
+
type: 'EPUBContentSelector';
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* URL of the content document. This should be an absolute HTTPS URL if
|
|
94
|
+
* available, but may be relative to the root of the EPUB.
|
|
95
|
+
*/
|
|
96
|
+
url: string;
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* EPUB Canonical Fragment Identifier for the table of contents entry that
|
|
100
|
+
* corresponds to the content document.
|
|
101
|
+
*/
|
|
102
|
+
cfi?: string;
|
|
103
|
+
|
|
104
|
+
/** Title of the content document. */
|
|
105
|
+
title?: string;
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Selector which identifies the page of a document that an annotation was made
|
|
110
|
+
* on.
|
|
111
|
+
*
|
|
112
|
+
* This selector is only applicable for document types where the association of
|
|
113
|
+
* content and page numbers can be done in a way that is independent of the
|
|
114
|
+
* viewer and display settings. This includes inherently paginated documents
|
|
115
|
+
* such as PDFs, but also content such as EPUBs when they include information
|
|
116
|
+
* about the location of page breaks in printed versions of a book. It does
|
|
117
|
+
* not include ordinary web pages or EPUBs without page break information
|
|
118
|
+
* however.
|
|
119
|
+
*/
|
|
120
|
+
export type PageSelector = {
|
|
121
|
+
type: 'PageSelector';
|
|
122
|
+
|
|
123
|
+
/** The zero-based index of the page in the document's page sequence. */
|
|
124
|
+
index: number;
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Either the page number that is displayed on the page, or the 1-based
|
|
128
|
+
* number of the page in the document's page sequence, if the pages do not
|
|
129
|
+
* have numbers on them.
|
|
130
|
+
*/
|
|
131
|
+
label?: string;
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Serialized representation of a region of a document which an annotation
|
|
136
|
+
* pertains to.
|
|
137
|
+
*/
|
|
138
|
+
export type Selector =
|
|
139
|
+
| TextQuoteSelector
|
|
140
|
+
| TextPositionSelector
|
|
141
|
+
| RangeSelector
|
|
142
|
+
| EPUBContentSelector
|
|
143
|
+
| MediaTimeSelector
|
|
144
|
+
| PageSelector;
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* An entry in the `target` field of an annotation which identifies the document
|
|
148
|
+
* and region of the document that it refers to.
|
|
149
|
+
*/
|
|
150
|
+
export type Target = {
|
|
151
|
+
/** URI of the document */
|
|
152
|
+
source: string;
|
|
153
|
+
/** Region of the document */
|
|
154
|
+
selector?: Selector[];
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
export type UserInfo = {
|
|
158
|
+
display_name: string | null;
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
export type APIAnnotationData = {
|
|
162
|
+
/**
|
|
163
|
+
* The server-assigned ID for the annotation. This is only set once the
|
|
164
|
+
* annotation has been saved to the backend.
|
|
165
|
+
*/
|
|
166
|
+
id?: string;
|
|
167
|
+
|
|
168
|
+
references?: string[];
|
|
169
|
+
created: string;
|
|
170
|
+
flagged?: boolean;
|
|
171
|
+
group: string;
|
|
172
|
+
updated: string;
|
|
173
|
+
tags: string[];
|
|
174
|
+
text: string;
|
|
175
|
+
uri: string;
|
|
176
|
+
user: string;
|
|
177
|
+
hidden: boolean;
|
|
178
|
+
|
|
179
|
+
document: {
|
|
180
|
+
title: string;
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
permissions: {
|
|
184
|
+
read: string[];
|
|
185
|
+
update: string[];
|
|
186
|
+
delete: string[];
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* The document and region this annotation refers to.
|
|
191
|
+
*
|
|
192
|
+
* The Hypothesis API structure allows for multiple targets, but the h
|
|
193
|
+
* server only supports one target per annotation.
|
|
194
|
+
*/
|
|
195
|
+
target: Target[];
|
|
196
|
+
|
|
197
|
+
moderation?: {
|
|
198
|
+
flagCount: number;
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
links: {
|
|
202
|
+
/**
|
|
203
|
+
* A "bouncer" URL that takes the user to see the annotation in context
|
|
204
|
+
*/
|
|
205
|
+
incontext?: string;
|
|
206
|
+
|
|
207
|
+
/** URL to view the annotation by itself. */
|
|
208
|
+
html?: string;
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
user_info?: UserInfo;
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
export type Annotation = ClientAnnotationData & APIAnnotationData;
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* An annotation which has been saved to the backend and assigned an ID.
|
|
218
|
+
*/
|
|
219
|
+
export type SavedAnnotation = Annotation & { id: string };
|
|
220
|
+
|
|
221
|
+
export type Profile = {
|
|
222
|
+
userid: string | null;
|
|
223
|
+
preferences: {
|
|
224
|
+
show_sidebar_tutorial?: boolean;
|
|
225
|
+
};
|
|
226
|
+
features: Record<string, boolean>;
|
|
227
|
+
user_info?: UserInfo;
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
export type Organization = {
|
|
231
|
+
name: string;
|
|
232
|
+
logo: string;
|
|
233
|
+
id: string;
|
|
234
|
+
default?: boolean;
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
export type GroupScopes = {
|
|
238
|
+
enforced: boolean;
|
|
239
|
+
uri_patterns: string[];
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
export type Group = {
|
|
243
|
+
/** The "pubid" of the group, unique per authority. */
|
|
244
|
+
id: string;
|
|
245
|
+
/** Fully-qualified ID with authority. */
|
|
246
|
+
groupid?: string;
|
|
247
|
+
type: 'private' | 'open';
|
|
248
|
+
/**
|
|
249
|
+
* Note: This field is nullable in the API, but we assign a default organization in the client.
|
|
250
|
+
*/
|
|
251
|
+
organization: Organization;
|
|
252
|
+
scopes: GroupScopes | null;
|
|
253
|
+
links: {
|
|
254
|
+
html?: string;
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
// Properties not present on API objects, but added in the client.
|
|
258
|
+
logo: string;
|
|
259
|
+
isMember: boolean;
|
|
260
|
+
isScopedToUri: boolean;
|
|
261
|
+
name: string;
|
|
262
|
+
canLeave: boolean;
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* All Groups have an `id`, which is a server-assigned identifier. This is the
|
|
267
|
+
* primary field used to identify a Group.
|
|
268
|
+
*
|
|
269
|
+
* In some cases, specifically LMS, it is necessary for an outside service to
|
|
270
|
+
* be able to specify its own identifier. This gets stored in the `groupid`
|
|
271
|
+
* field of a Group. Only some Groups have a `groupid`.
|
|
272
|
+
*
|
|
273
|
+
* Application logic operates on `id`s, but we may receive `groupid`s in some
|
|
274
|
+
* cases from outside sevices, e.g. the `changeFocusModeUser` RPC method.
|
|
275
|
+
*/
|
|
276
|
+
export type GroupIdentifier = NonNullable<Group['id'] | Group['groupid']>;
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Query parameters for an `/api/search` API call.
|
|
280
|
+
*
|
|
281
|
+
* This type currently includes params that we've actually used.
|
|
282
|
+
*
|
|
283
|
+
* See https://h.readthedocs.io/en/latest/api-reference/#tag/annotations/paths/~1search/get
|
|
284
|
+
* for the complete list and usage of each.
|
|
285
|
+
*/
|
|
286
|
+
export type SearchQuery = {
|
|
287
|
+
limit?: number;
|
|
288
|
+
uri?: string[];
|
|
289
|
+
group?: string;
|
|
290
|
+
order?: string;
|
|
291
|
+
references?: string;
|
|
292
|
+
search_after?: string;
|
|
293
|
+
sort?: string;
|
|
294
|
+
/** Undocument param that causes replies to be returned in a separate `replies` field. */
|
|
295
|
+
_separate_replies?: boolean;
|
|
296
|
+
};
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Response to an `/api/search` API call.
|
|
300
|
+
*
|
|
301
|
+
* See https://h.readthedocs.io/en/latest/api-reference/#tag/annotations/paths/~1search/get
|
|
302
|
+
*/
|
|
303
|
+
export type SearchResponse = {
|
|
304
|
+
total: number;
|
|
305
|
+
rows: Annotation[];
|
|
306
|
+
|
|
307
|
+
/** Undocumented property that is populated if `_separate_replies` query param was specified. */
|
|
308
|
+
replies?: Annotation[];
|
|
309
|
+
};
|