website-highlighter 1.2.0 → 1.3.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 CHANGED
@@ -16,7 +16,7 @@ Import the default function and pass the text you want to find. The library find
16
16
  import WebsiteHighlighter from 'website-highlighter'
17
17
 
18
18
  const article = document.querySelector('article')
19
- const { range, value } = WebsiteHighlighter('custom highlight api', {
19
+ const { range, value } = await WebsiteHighlighter('custom highlight api', {
20
20
  root: article
21
21
  })
22
22
 
@@ -36,7 +36,19 @@ Add styles for the highlight name:
36
36
  If no root is passed, the library searches `document.body`.
37
37
 
38
38
  ```js
39
- WebsiteHighlighter('some text on the page')
39
+ await WebsiteHighlighter('some text on the page')
40
+ ```
41
+
42
+ ## CDN Usage
43
+
44
+ The browser bundle can be loaded from a CDN as a single file. Fuzzy matching uses an inline Blob worker when the browser allows it, so you do not need to host a separate worker script.
45
+
46
+ ```html
47
+ <script type="module">
48
+ import WebsiteHighlighter from 'https://cdn.example.com/website-highlighter.js'
49
+
50
+ await WebsiteHighlighter('some text on the page')
51
+ </script>
40
52
  ```
41
53
 
42
54
  ## Iframe Usage
@@ -71,14 +83,20 @@ highlightInIframe(iframe, 'text inside the iframe', 'https://example.com')
71
83
 
72
84
  ### `WebsiteHighlighter(text, options)`
73
85
 
74
- Finds the best fuzzy match for `text`, applies the `website-highlighter` custom highlight, and returns `{ range, value }` or resolves to it when threshold retries are enabled.
86
+ Finds the best fuzzy match for `text`, applies the `website-highlighter` custom highlight, and resolves to `{ range, value }`.
75
87
 
76
88
  - `text`: string to search for.
77
89
  - `options.root`: optional DOM node to search. Defaults to `document.body`.
78
- - `options.threshold`: optional minimum match score from `0` to `1`. When greater than `0`, the function returns a Promise and retries until the threshold is met or retries are exhausted.
90
+ - `options.threshold`: optional minimum match score from `0` to `1`. When greater than `0`, the function retries until the threshold is met or retries are exhausted.
79
91
  - `options.retries`: optional retry count. Defaults to `6`.
80
92
  - `options.retryInterval`: optional retry delay in milliseconds. Defaults to `500`.
81
93
 
94
+ Fuzzy matching runs in a worker when the browser allows it. If worker creation is unavailable or blocked, the library falls back to synchronous matching on the main thread.
95
+
96
+ ### `getMatcherMode()`
97
+
98
+ Returns `'worker'` or `'sync'` for the most recent match. This is intended for diagnostics and demos.
99
+
82
100
  ### `highlightInIframe(iframe, text, targetOrigin)`
83
101
 
84
102
  Posts a highlight request to an iframe.
@@ -1,4 +1,4 @@
1
- class M {
1
+ class I {
2
2
  /**
3
3
  * Calculates the Levenshtein distance for all substrings and returns their
4
4
  * distance and insertion-deletion offset.
@@ -9,16 +9,16 @@ class M {
9
9
  * @return {array} Array of all substring matches in pairs [distance, offset]
10
10
  */
11
11
  getEditDistances(e, t) {
12
- var n = new Array(t.length + 1).fill([0, 0]);
13
- for (let r = 0; r < e.length; r++) {
14
- let a = [[r + 1, 0]];
15
- for (let o = 0; o < t.length; o++) {
16
- let s = e[r] != t[o], l = n[o + 1][0] + 1, c = a[o][0] + 1, d = n[o][0] + s, u = Math.min(l, Math.min(c, d)), h = [u, n[o][1]];
17
- l === u ? h[1] = n[o + 1][1] - 1 : c === u && (h[1] = a[o][1] + 1), a.push(h);
12
+ var r = new Array(t.length + 1).fill([0, 0]);
13
+ for (let i = 0; i < e.length; i++) {
14
+ let o = [[i + 1, 0]];
15
+ for (let s = 0; s < t.length; s++) {
16
+ let a = e[i] != t[s], l = r[s + 1][0] + 1, c = o[s][0] + 1, g = r[s][0] + a, f = Math.min(l, Math.min(c, g)), d = [f, r[s][1]];
17
+ l === f ? d[1] = r[s + 1][1] - 1 : c === f && (d[1] = o[s][1] + 1), o.push(d);
18
18
  }
19
- n = a;
19
+ r = o;
20
20
  }
21
- return n;
21
+ return r;
22
22
  }
23
23
  /**
24
24
  * Search haystack for all instances of needle and returns an array of
@@ -30,149 +30,231 @@ class M {
30
30
  * @return {array} Array of best substring matches.
31
31
  */
32
32
  getMatches(e, t) {
33
- let n = this.getEditDistances(e, t), r = [0], a = n[0][0];
34
- for (let s = 1; s < n.length; s++) {
35
- let l = n[s][0];
36
- l < a ? (r = [s], a = l) : l == a && r.push(s);
33
+ let r = this.getEditDistances(e, t), i = [0], o = r[0][0];
34
+ for (let a = 1; a < r.length; a++) {
35
+ let l = r[a][0];
36
+ l < o ? (i = [a], o = l) : l == o && i.push(a);
37
37
  }
38
- let o = [];
39
- for (let s of r) {
40
- let l = n[s], c = {
38
+ let s = [];
39
+ for (let a of i) {
40
+ let l = r[a], c = {
41
41
  distance: l[0],
42
- start: s - e.length - l[1],
42
+ start: a - e.length - l[1],
43
43
  //simplification of startPos = endPos − (needleLength + insertions − deletions)
44
- end: s
44
+ end: a
45
45
  };
46
- o.push(c);
46
+ s.push(c);
47
47
  }
48
- return o;
48
+ return s;
49
49
  }
50
50
  }
51
- function T(i, e) {
52
- return new M().getMatches(i, e);
51
+ function T(n, e) {
52
+ return new I().getMatches(n, e);
53
53
  }
54
- function b(i, e) {
55
- const t = T(i, e);
56
- let n;
57
- for (const s of t)
58
- (n === void 0 || s.distance < n.distance) && (n = s);
59
- const r = Math.max(n.start, 0), a = Math.min(Math.max(n.end, r), e.length);
60
- return { value: e.slice(r, a), start: r, end: a, distance: n.distance };
54
+ function x(n, e) {
55
+ const t = T(n, e);
56
+ let r;
57
+ for (const a of t)
58
+ (r === void 0 || a.distance < r.distance) && (r = a);
59
+ const i = Math.max(r.start, 0), o = Math.min(Math.max(r.end, i), e.length);
60
+ return { value: e.slice(i, o), start: i, end: o, distance: r.distance };
61
61
  }
62
- function I(i, e, t) {
63
- const n = i.textContent ?? "";
64
- if (!Number.isInteger(e) || !Number.isInteger(t) || e < 0 || e >= t || t > n.length)
62
+ const y = `(function(){"use strict";class h{getEditDistances(s,i){var t=new Array(i.length+1).fill([0,0]);for(let e=0;e<s.length;e++){let r=[[e+1,0]];for(let l=0;l<i.length;l++){let n=s[e]!=i[l],a=t[l+1][0]+1,o=r[l][0]+1,m=t[l][0]+n,u=Math.min(a,Math.min(o,m)),f=[u,t[l][1]];a===u?f[1]=t[l+1][1]-1:o===u&&(f[1]=r[l][1]+1),r.push(f)}t=r}return t}getMatches(s,i){let t=this.getEditDistances(s,i),e=[0],r=t[0][0];for(let n=1;n<t.length;n++){let a=t[n][0];a<r?(e=[n],r=a):a==r&&e.push(n)}let l=[];for(let n of e){let a=t[n],o={distance:a[0],start:n-s.length-a[1],end:n};l.push(o)}return l}}function g(c,s){return new h().getMatches(c,s)}function d(c,s){const i=g(c,s);let t;for(const n of i)(t===void 0||n.distance<t.distance)&&(t=n);const e=Math.max(t.start,0),r=Math.min(Math.max(t.end,e),s.length);return{value:s.slice(e,r),start:e,end:r,distance:t.distance}}self.addEventListener("message",c=>{const{id:s,query:i,source:t}=c.data;try{self.postMessage({id:s,result:d(i,t)})}catch(e){self.postMessage({id:s,error:e instanceof Error?e.message:String(e)})}})})();
63
+ `, E = typeof self < "u" && self.Blob && new Blob(["(self.URL || self.webkitURL).revokeObjectURL(self.location.href);", y], { type: "text/javascript;charset=utf-8" });
64
+ function W(n) {
65
+ let e;
66
+ try {
67
+ if (e = E && (self.URL || self.webkitURL).createObjectURL(E), !e) throw "";
68
+ const t = new Worker(e, {
69
+ name: n?.name
70
+ });
71
+ return t.addEventListener("error", () => {
72
+ (self.URL || self.webkitURL).revokeObjectURL(e);
73
+ }), t;
74
+ } catch {
75
+ return new Worker(
76
+ "data:text/javascript;charset=utf-8," + encodeURIComponent(y),
77
+ {
78
+ name: n?.name
79
+ }
80
+ );
81
+ }
82
+ }
83
+ let u, S = 0, p = !1, M = "sync";
84
+ const h = /* @__PURE__ */ new Map();
85
+ function H(n) {
86
+ for (const { reject: e } of h.values())
87
+ e(n);
88
+ h.clear();
89
+ }
90
+ function b(n) {
91
+ p = !0, u && (u.terminate(), u = void 0), H(n);
92
+ }
93
+ function C() {
94
+ if (!(p || typeof Worker > "u")) {
95
+ if (u) return u;
96
+ try {
97
+ u = new W();
98
+ } catch {
99
+ p = !0;
100
+ return;
101
+ }
102
+ return u.addEventListener("message", (n) => {
103
+ const e = h.get(n.data?.id);
104
+ e && (h.delete(n.data.id), n.data.error ? e.reject(new Error(n.data.error)) : e.resolve(n.data.result));
105
+ }), u.addEventListener("error", (n) => {
106
+ b(n.error ?? new Error(n.message || "Worker matcher failed."));
107
+ }), u.addEventListener("messageerror", () => {
108
+ b(new Error("Worker matcher could not deserialize a message."));
109
+ }), u;
110
+ }
111
+ }
112
+ function O(n, e) {
113
+ const t = C();
114
+ if (!t) return;
115
+ const r = S++;
116
+ return new Promise((i, o) => {
117
+ h.set(r, { resolve: i, reject: o });
118
+ try {
119
+ t.postMessage({ id: r, query: n, source: e });
120
+ } catch (s) {
121
+ h.delete(r), o(s);
122
+ }
123
+ });
124
+ }
125
+ function D() {
126
+ return M;
127
+ }
128
+ async function U(n, e) {
129
+ const t = O(n, e);
130
+ if (t)
131
+ try {
132
+ const r = await t;
133
+ return M = "worker", r;
134
+ } catch (r) {
135
+ b(r);
136
+ }
137
+ return M = "sync", x(n, e);
138
+ }
139
+ function j(n, e, t) {
140
+ const r = n.textContent ?? "";
141
+ if (!Number.isInteger(e) || !Number.isInteger(t) || e < 0 || e >= t || t > r.length)
65
142
  throw new RangeError(
66
- `Invalid range [${e}, ${t}) for text length ${n.length}`
143
+ `Invalid range [${e}, ${t}) for text length ${r.length}`
67
144
  );
68
- const r = i.ownerDocument, a = r.createTreeWalker(
69
- i,
70
- r.defaultView.NodeFilter.SHOW_TEXT
145
+ const i = n.ownerDocument, o = i.createTreeWalker(
146
+ n,
147
+ i.defaultView.NodeFilter.SHOW_TEXT
71
148
  );
72
- let o = 0, s = null, l = 0, c = null, d = 0;
73
- for (let h = a.nextNode(); h; h = a.nextNode()) {
74
- const g = o + h.data.length;
75
- if (s === null && e >= o && e < g && (s = h, l = e - o), c === null && t > o && t <= g && (c = h, d = t - o), s && c)
149
+ let s = 0, a = null, l = 0, c = null, g = 0;
150
+ for (let d = o.nextNode(); d; d = o.nextNode()) {
151
+ const m = s + d.data.length;
152
+ if (a === null && e >= s && e < m && (a = d, l = e - s), c === null && t > s && t <= m && (c = d, g = t - s), a && c)
76
153
  break;
77
- o = g;
154
+ s = m;
78
155
  }
79
- if (!s || !c)
156
+ if (!a || !c)
80
157
  throw new Error(
81
158
  "Could not map offsets to the DOM. The DOM may have changed."
82
159
  );
83
- const u = r.createRange();
84
- return u.setStart(s, l), u.setEnd(c, d), u;
85
- }
86
- const f = "website-highlighter", w = `${f}:response`, m = 500;
87
- let y = 0;
88
- function H(i) {
89
- return new Promise((e) => globalThis.setTimeout(e, i));
90
- }
91
- function S(i, e) {
92
- const t = i.ownerDocument?.defaultView ?? window;
93
- return t.MutationObserver ? new Promise((n) => {
94
- let r;
95
- const a = new t.MutationObserver(() => {
96
- t.clearTimeout(r), a.disconnect(), n(!0);
160
+ const f = i.createRange();
161
+ return f.setStart(a, l), f.setEnd(c, g), f;
162
+ }
163
+ const w = "website-highlighter", k = `${w}:response`, R = 500;
164
+ let N = 0;
165
+ function $(n) {
166
+ return new Promise((e) => globalThis.setTimeout(e, n));
167
+ }
168
+ function q(n, e) {
169
+ const t = n.ownerDocument?.defaultView ?? window;
170
+ return t.MutationObserver ? new Promise((r) => {
171
+ let i;
172
+ const o = new t.MutationObserver(() => {
173
+ t.clearTimeout(i), o.disconnect(), r(!0);
97
174
  });
98
- r = t.setTimeout(() => {
99
- a.disconnect(), n(!1);
100
- }, e), a.observe(i, {
175
+ i = t.setTimeout(() => {
176
+ o.disconnect(), r(!1);
177
+ }, e), o.observe(n, {
101
178
  childList: !0,
102
179
  characterData: !0,
103
180
  subtree: !0
104
181
  });
105
- }) : H(e).then(() => !1);
182
+ }) : $(e).then(() => !1);
106
183
  }
107
- function x(i, e, t) {
108
- const n = Math.max(i.length, e.length);
109
- return n === 0 ? 1 : (n - t) / n;
184
+ function P(n, e, t) {
185
+ const r = Math.max(n.length, e.length);
186
+ return r === 0 ? 1 : (r - t) / r;
110
187
  }
111
- function p(i, e) {
112
- const t = e.textContent ?? "", { start: n, end: r, value: a, distance: o } = b(i, t), s = e.ownerDocument?.defaultView ?? window;
113
- if (!s.CSS?.highlights || !s.Highlight) throw new Error("This browser does not support the CSS Custom Highlight API.");
188
+ async function L(n, e) {
189
+ const t = e.textContent ?? "", r = e.ownerDocument?.defaultView ?? window;
190
+ if (!r.CSS?.highlights || !r.Highlight) throw new Error("This browser does not support the CSS Custom Highlight API.");
191
+ const { start: i, end: o, value: s, distance: a } = await U(n, t);
114
192
  return {
115
193
  haystack: t,
116
- range: n < r ? I(e, n, r) : null,
117
- value: a,
118
- view: s,
119
- score: x(i, a, o)
194
+ range: i < o ? j(e, i, o) : null,
195
+ value: s,
196
+ view: r,
197
+ score: P(n, s, a)
120
198
  };
121
199
  }
122
- function E({ range: i, value: e, view: t }) {
123
- if (!i) throw new Error("Could not find text to highlight.");
124
- return t.CSS.highlights.set(f, new t.Highlight(i)), { range: i, value: e };
200
+ function v({ range: n, value: e, view: t }) {
201
+ if (!n) throw new Error("Could not find text to highlight.");
202
+ return t.CSS.highlights.set(w, new t.Highlight(n)), { range: n, value: e };
125
203
  }
126
- async function v(i, e, t, n, r) {
127
- let a = 0;
204
+ async function _(n, e, t, r, i) {
205
+ let o = 0;
128
206
  for (; ; ) {
129
- const o = p(i, e);
130
- if (o.score >= t) return E(o);
131
- for (; a < n; ) {
132
- const s = await S(e, r);
133
- if (a += 1, s) break;
207
+ const s = await L(n, e);
208
+ if (s.score >= t) return v(s);
209
+ for (; o < r; ) {
210
+ const a = await q(e, i);
211
+ if (o += 1, a) break;
134
212
  }
135
- if (a >= n) throw new Error(`Could not find "${i}" with threshold ${t}. Best match was "${o.value}".`);
213
+ if (o >= r) throw new Error(`Could not find "${n}" with threshold ${t}. Best match was "${s.value}".`);
136
214
  }
137
215
  }
138
- function C(i, {
216
+ function A() {
217
+ return D();
218
+ }
219
+ async function z(n, {
139
220
  root: e = document.body,
140
221
  threshold: t = 0,
141
- retries: n = 6,
142
- retryInterval: r = m
222
+ retries: r = 6,
223
+ retryInterval: i = R
143
224
  } = {}) {
144
- return t > 0 ? v(i, e, t, n, r) : E(p(i, e));
145
- }
146
- function N(i, e, t = "*") {
147
- if (!i?.contentWindow) throw new TypeError("Expected an iframe with a contentWindow");
148
- const n = `${Date.now()}-${y++}`, r = i.contentWindow;
149
- let a;
150
- function o() {
151
- r.postMessage({
152
- type: f,
153
- id: n,
225
+ return t > 0 ? _(n, e, t, r, i) : v(await L(n, e));
226
+ }
227
+ function F(n, e, t = "*") {
228
+ if (!n?.contentWindow) throw new TypeError("Expected an iframe with a contentWindow");
229
+ const r = `${Date.now()}-${N++}`, i = n.contentWindow;
230
+ let o;
231
+ function s() {
232
+ i.postMessage({
233
+ type: w,
234
+ id: r,
154
235
  text: e
155
236
  }, t);
156
237
  }
157
- function s(l) {
158
- l.source === r && l.data?.type === w && l.data.id === n && (window.clearInterval(a), window.removeEventListener("message", s));
238
+ function a(l) {
239
+ l.source === i && l.data?.type === k && l.data.id === r && (window.clearInterval(o), window.removeEventListener("message", a));
159
240
  }
160
- window.addEventListener("message", s), o(), a = window.setInterval(o, m);
241
+ window.addEventListener("message", a), s(), o = window.setInterval(s, R);
161
242
  }
162
243
  if (typeof window < "u") {
163
- let i = function(t) {
244
+ let n = function(t) {
164
245
  return t.origin === "null" ? "*" : t.origin;
165
246
  }, e = function(t) {
166
247
  t.source?.postMessage({
167
- type: w,
248
+ type: k,
168
249
  id: t.data.id
169
- }, i(t));
250
+ }, n(t));
170
251
  };
171
252
  window.addEventListener("message", (t) => {
172
- t.data?.type === f && (e(t), C(t.data.text, { threshold: 0.9 }).catch((n) => console.error(n)));
253
+ t.data?.type === w && (e(t), z(t.data.text, { threshold: 0.9 }).catch((r) => console.error(r)));
173
254
  });
174
255
  }
175
256
  export {
176
- C as default,
177
- N as highlightInIframe
257
+ z as default,
258
+ A as getMatcherMode,
259
+ F as highlightInIframe
178
260
  };
@@ -1 +1,2 @@
1
- (function(c,f){typeof exports=="object"&&typeof module<"u"?f(exports):typeof define=="function"&&define.amd?define(["exports"],f):(c=typeof globalThis<"u"?globalThis:c||self,f(c.WebsiteHighlighter={}))})(this,(function(c){"use strict";class f{getEditDistances(t,e){var n=new Array(e.length+1).fill([0,0]);for(let o=0;o<t.length;o++){let l=[[o+1,0]];for(let r=0;r<e.length;r++){let s=t[o]!=e[r],a=n[r+1][0]+1,u=l[r][0]+1,w=n[r][0]+s,d=Math.min(a,Math.min(u,w)),h=[d,n[r][1]];a===d?h[1]=n[r+1][1]-1:u===d&&(h[1]=l[r][1]+1),l.push(h)}n=l}return n}getMatches(t,e){let n=this.getEditDistances(t,e),o=[0],l=n[0][0];for(let s=1;s<n.length;s++){let a=n[s][0];a<l?(o=[s],l=a):a==l&&o.push(s)}let r=[];for(let s of o){let a=n[s],u={distance:a[0],start:s-t.length-a[1],end:s};r.push(u)}return r}}function y(i,t){return new f().getMatches(i,t)}function I(i,t){const e=y(i,t);let n;for(const s of e)(n===void 0||s.distance<n.distance)&&(n=s);const o=Math.max(n.start,0),l=Math.min(Math.max(n.end,o),t.length);return{value:t.slice(o,l),start:o,end:l,distance:n.distance}}function S(i,t,e){const n=i.textContent??"";if(!Number.isInteger(t)||!Number.isInteger(e)||t<0||t>=e||e>n.length)throw new RangeError(`Invalid range [${t}, ${e}) for text length ${n.length}`);const o=i.ownerDocument,l=o.createTreeWalker(i,o.defaultView.NodeFilter.SHOW_TEXT);let r=0,s=null,a=0,u=null,w=0;for(let h=l.nextNode();h;h=l.nextNode()){const m=r+h.data.length;if(s===null&&t>=r&&t<m&&(s=h,a=t-r),u===null&&e>r&&e<=m&&(u=h,w=e-r),s&&u)break;r=m}if(!s||!u)throw new Error("Could not map offsets to the DOM. The DOM may have changed.");const d=o.createRange();return d.setStart(s,a),d.setEnd(u,w),d}const g="website-highlighter",p=`${g}:response`,b=500;let H=0;function v(i){return new Promise(t=>globalThis.setTimeout(t,i))}function x(i,t){const e=i.ownerDocument?.defaultView??window;return e.MutationObserver?new Promise(n=>{let o;const l=new e.MutationObserver(()=>{e.clearTimeout(o),l.disconnect(),n(!0)});o=e.setTimeout(()=>{l.disconnect(),n(!1)},t),l.observe(i,{childList:!0,characterData:!0,subtree:!0})}):v(t).then(()=>!1)}function C(i,t,e){const n=Math.max(i.length,t.length);return n===0?1:(n-e)/n}function T(i,t){const e=t.textContent??"",{start:n,end:o,value:l,distance:r}=I(i,e),s=t.ownerDocument?.defaultView??window;if(!s.CSS?.highlights||!s.Highlight)throw new Error("This browser does not support the CSS Custom Highlight API.");return{haystack:e,range:n<o?S(t,n,o):null,value:l,view:s,score:C(i,l,r)}}function M({range:i,value:t,view:e}){if(!i)throw new Error("Could not find text to highlight.");return e.CSS.highlights.set(g,new e.Highlight(i)),{range:i,value:t}}async function O(i,t,e,n,o){let l=0;for(;;){const r=T(i,t);if(r.score>=e)return M(r);for(;l<n;){const s=await x(t,o);if(l+=1,s)break}if(l>=n)throw new Error(`Could not find "${i}" with threshold ${e}. Best match was "${r.value}".`)}}function E(i,{root:t=document.body,threshold:e=0,retries:n=6,retryInterval:o=b}={}){return e>0?O(i,t,e,n,o):M(T(i,t))}function N(i,t,e="*"){if(!i?.contentWindow)throw new TypeError("Expected an iframe with a contentWindow");const n=`${Date.now()}-${H++}`,o=i.contentWindow;let l;function r(){o.postMessage({type:g,id:n,text:t},e)}function s(a){a.source===o&&a.data?.type===p&&a.data.id===n&&(window.clearInterval(l),window.removeEventListener("message",s))}window.addEventListener("message",s),r(),l=window.setInterval(r,b)}if(typeof window<"u"){let i=function(e){return e.origin==="null"?"*":e.origin},t=function(e){e.source?.postMessage({type:p,id:e.data.id},i(e))};window.addEventListener("message",e=>{e.data?.type===g&&(t(e),E(e.data.text,{threshold:.9}).catch(n=>console.error(n)))})}c.default=E,c.highlightInIframe=N,Object.defineProperties(c,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}})}));
1
+ (function(f,w){typeof exports=="object"&&typeof module<"u"?w(exports):typeof define=="function"&&define.amd?define(["exports"],w):(f=typeof globalThis<"u"?globalThis:f||self,w(f.WebsiteHighlighter={}))})(this,(function(f){"use strict";class w{getEditDistances(e,t){var r=new Array(t.length+1).fill([0,0]);for(let s=0;s<e.length;s++){let o=[[s+1,0]];for(let i=0;i<t.length;i++){let a=e[s]!=t[i],l=r[i+1][0]+1,u=o[i][0]+1,p=r[i][0]+a,h=Math.min(l,Math.min(u,p)),d=[h,r[i][1]];l===h?d[1]=r[i+1][1]-1:u===h&&(d[1]=o[i][1]+1),o.push(d)}r=o}return r}getMatches(e,t){let r=this.getEditDistances(e,t),s=[0],o=r[0][0];for(let a=1;a<r.length;a++){let l=r[a][0];l<o?(s=[a],o=l):l==o&&s.push(a)}let i=[];for(let a of s){let l=r[a],u={distance:l[0],start:a-e.length-l[1],end:a};i.push(u)}return i}}function x(n,e){return new w().getMatches(n,e)}function S(n,e){const t=x(n,e);let r;for(const a of t)(r===void 0||a.distance<r.distance)&&(r=a);const s=Math.max(r.start,0),o=Math.min(Math.max(r.end,s),e.length);return{value:e.slice(s,o),start:s,end:o,distance:r.distance}}const k=`(function(){"use strict";class h{getEditDistances(s,i){var t=new Array(i.length+1).fill([0,0]);for(let e=0;e<s.length;e++){let r=[[e+1,0]];for(let l=0;l<i.length;l++){let n=s[e]!=i[l],a=t[l+1][0]+1,o=r[l][0]+1,m=t[l][0]+n,u=Math.min(a,Math.min(o,m)),f=[u,t[l][1]];a===u?f[1]=t[l+1][1]-1:o===u&&(f[1]=r[l][1]+1),r.push(f)}t=r}return t}getMatches(s,i){let t=this.getEditDistances(s,i),e=[0],r=t[0][0];for(let n=1;n<t.length;n++){let a=t[n][0];a<r?(e=[n],r=a):a==r&&e.push(n)}let l=[];for(let n of e){let a=t[n],o={distance:a[0],start:n-s.length-a[1],end:n};l.push(o)}return l}}function g(c,s){return new h().getMatches(c,s)}function d(c,s){const i=g(c,s);let t;for(const n of i)(t===void 0||n.distance<t.distance)&&(t=n);const e=Math.max(t.start,0),r=Math.min(Math.max(t.end,e),s.length);return{value:s.slice(e,r),start:e,end:r,distance:t.distance}}self.addEventListener("message",c=>{const{id:s,query:i,source:t}=c.data;try{self.postMessage({id:s,result:d(i,t)})}catch(e){self.postMessage({id:s,error:e instanceof Error?e.message:String(e)})}})})();
2
+ `,R=typeof self<"u"&&self.Blob&&new Blob(["(self.URL || self.webkitURL).revokeObjectURL(self.location.href);",k],{type:"text/javascript;charset=utf-8"});function H(n){let e;try{if(e=R&&(self.URL||self.webkitURL).createObjectURL(R),!e)throw"";const t=new Worker(e,{name:n?.name});return t.addEventListener("error",()=>{(self.URL||self.webkitURL).revokeObjectURL(e)}),t}catch{return new Worker("data:text/javascript;charset=utf-8,"+encodeURIComponent(k),{name:n?.name})}}let c,O=0,M=!1,b="sync";const g=new Map;function C(n){for(const{reject:e}of g.values())e(n);g.clear()}function y(n){M=!0,c&&(c.terminate(),c=void 0),C(n)}function j(){if(!(M||typeof Worker>"u")){if(c)return c;try{c=new H}catch{M=!0;return}return c.addEventListener("message",n=>{const e=g.get(n.data?.id);e&&(g.delete(n.data.id),n.data.error?e.reject(new Error(n.data.error)):e.resolve(n.data.result))}),c.addEventListener("error",n=>{y(n.error??new Error(n.message||"Worker matcher failed."))}),c.addEventListener("messageerror",()=>{y(new Error("Worker matcher could not deserialize a message."))}),c}}function D(n,e){const t=j();if(!t)return;const r=O++;return new Promise((s,o)=>{g.set(r,{resolve:s,reject:o});try{t.postMessage({id:r,query:n,source:e})}catch(i){g.delete(r),o(i)}})}function U(){return b}async function N(n,e){const t=D(n,e);if(t)try{const r=await t;return b="worker",r}catch(r){y(r)}return b="sync",S(n,e)}function P(n,e,t){const r=n.textContent??"";if(!Number.isInteger(e)||!Number.isInteger(t)||e<0||e>=t||t>r.length)throw new RangeError(`Invalid range [${e}, ${t}) for text length ${r.length}`);const s=n.ownerDocument,o=s.createTreeWalker(n,s.defaultView.NodeFilter.SHOW_TEXT);let i=0,a=null,l=0,u=null,p=0;for(let d=o.nextNode();d;d=o.nextNode()){const E=i+d.data.length;if(a===null&&e>=i&&e<E&&(a=d,l=e-i),u===null&&t>i&&t<=E&&(u=d,p=t-i),a&&u)break;i=E}if(!a||!u)throw new Error("Could not map offsets to the DOM. The DOM may have changed.");const h=s.createRange();return h.setStart(a,l),h.setEnd(u,p),h}const m="website-highlighter",v=`${m}:response`,L=500;let $=0;function q(n){return new Promise(e=>globalThis.setTimeout(e,n))}function _(n,e){const t=n.ownerDocument?.defaultView??window;return t.MutationObserver?new Promise(r=>{let s;const o=new t.MutationObserver(()=>{t.clearTimeout(s),o.disconnect(),r(!0)});s=t.setTimeout(()=>{o.disconnect(),r(!1)},e),o.observe(n,{childList:!0,characterData:!0,subtree:!0})}):q(e).then(()=>!1)}function z(n,e,t){const r=Math.max(n.length,e.length);return r===0?1:(r-t)/r}async function T(n,e){const t=e.textContent??"",r=e.ownerDocument?.defaultView??window;if(!r.CSS?.highlights||!r.Highlight)throw new Error("This browser does not support the CSS Custom Highlight API.");const{start:s,end:o,value:i,distance:a}=await N(n,t);return{haystack:t,range:s<o?P(e,s,o):null,value:i,view:r,score:z(n,i,a)}}function I({range:n,value:e,view:t}){if(!n)throw new Error("Could not find text to highlight.");return t.CSS.highlights.set(m,new t.Highlight(n)),{range:n,value:e}}async function A(n,e,t,r,s){let o=0;for(;;){const i=await T(n,e);if(i.score>=t)return I(i);for(;o<r;){const a=await _(e,s);if(o+=1,a)break}if(o>=r)throw new Error(`Could not find "${n}" with threshold ${t}. Best match was "${i.value}".`)}}function F(){return U()}async function W(n,{root:e=document.body,threshold:t=0,retries:r=6,retryInterval:s=L}={}){return t>0?A(n,e,t,r,s):I(await T(n,e))}function B(n,e,t="*"){if(!n?.contentWindow)throw new TypeError("Expected an iframe with a contentWindow");const r=`${Date.now()}-${$++}`,s=n.contentWindow;let o;function i(){s.postMessage({type:m,id:r,text:e},t)}function a(l){l.source===s&&l.data?.type===v&&l.data.id===r&&(window.clearInterval(o),window.removeEventListener("message",a))}window.addEventListener("message",a),i(),o=window.setInterval(i,L)}if(typeof window<"u"){let n=function(t){return t.origin==="null"?"*":t.origin},e=function(t){t.source?.postMessage({type:v,id:t.data.id},n(t))};window.addEventListener("message",t=>{t.data?.type===m&&(e(t),W(t.data.text,{threshold:.9}).catch(r=>console.error(r)))})}f.default=W,f.getMatcherMode=F,f.highlightInIframe=B,Object.defineProperties(f,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}})}));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "website-highlighter",
3
- "version": "1.2.0",
3
+ "version": "1.3.0",
4
4
  "description": "Fuzzy text matching and highlighting for DOM content.",
5
5
  "type": "module",
6
6
  "main": "./dist/website-highlighter.umd.cjs",