@weatherboard/annotation 0.3.0 → 0.6.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/ADOPTING.md +146 -11
- package/COMPATIBILITY.md +15 -0
- package/README.md +33 -8
- package/dist/anchor.cjs +1 -1
- package/dist/anchor.cjs.map +2 -2
- package/dist/anchor.mjs +1 -1
- package/dist/anchor.mjs.map +2 -2
- package/dist/arming.d.cts +1 -3
- package/dist/arming.d.mts +1 -3
- package/dist/arming.d.ts +1 -3
- package/dist/bootstrap-runtime.d.cts +30 -0
- package/dist/bootstrap-runtime.d.mts +30 -0
- package/dist/bootstrap-runtime.d.ts +30 -0
- package/dist/bootstrap.cjs +2 -0
- package/dist/bootstrap.cjs.map +7 -0
- package/dist/bootstrap.d.cts +7 -0
- package/dist/bootstrap.d.mts +7 -0
- package/dist/bootstrap.d.ts +7 -0
- package/dist/bootstrap.mjs +2 -0
- package/dist/bootstrap.mjs.map +7 -0
- package/dist/host.cjs +1 -1
- package/dist/host.cjs.map +3 -3
- package/dist/host.mjs +1 -1
- package/dist/host.mjs.map +3 -3
- package/dist/index.cjs +4 -4
- package/dist/index.cjs.map +4 -4
- package/dist/index.d.cts +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.mjs +4 -4
- package/dist/index.mjs.map +4 -4
- package/dist/markers.cjs +11 -4
- package/dist/markers.cjs.map +3 -3
- package/dist/markers.d.cts +39 -2
- package/dist/markers.d.mts +39 -2
- package/dist/markers.d.ts +39 -2
- package/dist/markers.mjs +11 -4
- package/dist/markers.mjs.map +3 -3
- package/dist/protocol.cjs +1 -1
- package/dist/protocol.cjs.map +3 -3
- package/dist/protocol.d.cts +7 -0
- package/dist/protocol.d.mts +7 -0
- package/dist/protocol.d.ts +7 -0
- package/dist/protocol.mjs +1 -1
- package/dist/protocol.mjs.map +3 -3
- package/dist/read.cjs +1 -1
- package/dist/read.cjs.map +3 -3
- package/dist/read.d.cts +46 -1
- package/dist/read.d.mts +46 -1
- package/dist/read.d.ts +46 -1
- package/dist/read.mjs +1 -1
- package/dist/read.mjs.map +3 -3
- package/dist/selection.cjs +5 -0
- package/dist/selection.cjs.map +7 -0
- package/dist/selection.d.cts +27 -0
- package/dist/selection.d.mts +27 -0
- package/dist/selection.d.ts +27 -0
- package/dist/selection.mjs +5 -0
- package/dist/selection.mjs.map +7 -0
- package/dist/session.d.cts +10 -0
- package/dist/session.d.mts +10 -0
- package/dist/session.d.ts +10 -0
- package/dist/transport.d.cts +10 -0
- package/dist/transport.d.mts +10 -0
- package/dist/transport.d.ts +10 -0
- package/package.json +37 -12
package/ADOPTING.md
CHANGED
|
@@ -98,23 +98,40 @@ pointed at production anyway.
|
|
|
98
98
|
|
|
99
99
|
```ts
|
|
100
100
|
// middleware.ts
|
|
101
|
-
import {
|
|
101
|
+
import {
|
|
102
|
+
ANNOTATION_ARMED_COOKIE,
|
|
103
|
+
ANNOTATION_ARMED_COOKIES,
|
|
104
|
+
ANNOTATION_TOKEN_PARAMS,
|
|
105
|
+
readAnnotationToken,
|
|
106
|
+
} from "@weatherboard/annotation/protocol";
|
|
102
107
|
|
|
103
108
|
const presented = readAnnotationToken(request.nextUrl.searchParams);
|
|
104
109
|
const expected = process.env.ANNOTATION_TOKEN;
|
|
110
|
+
const attempted = ANNOTATION_TOKEN_PARAMS.some((name) => request.nextUrl.searchParams.has(name));
|
|
111
|
+
const overHttps = request.nextUrl.protocol === "https:";
|
|
105
112
|
|
|
106
113
|
if (expected && presented && presented.length === expected.length && presented === expected) {
|
|
107
|
-
const overHttps = request.nextUrl.protocol === "https:";
|
|
108
114
|
response.cookies.set(ANNOTATION_ARMED_COOKIE, "1", {
|
|
109
115
|
httpOnly: false, // the client bundle is what reads it
|
|
110
116
|
sameSite: overHttps ? "none" : "lax",
|
|
111
117
|
secure: overHttps,
|
|
112
118
|
path: "/",
|
|
113
119
|
});
|
|
120
|
+
} else if (attempted) {
|
|
121
|
+
for (const name of ANNOTATION_ARMED_COOKIES) {
|
|
122
|
+
response.cookies.set(name, "", {
|
|
123
|
+
httpOnly: false,
|
|
124
|
+
sameSite: overHttps ? "none" : "lax",
|
|
125
|
+
secure: overHttps,
|
|
126
|
+
path: "/",
|
|
127
|
+
maxAge: 0,
|
|
128
|
+
expires: new Date(0),
|
|
129
|
+
});
|
|
130
|
+
}
|
|
114
131
|
}
|
|
115
132
|
```
|
|
116
133
|
|
|
117
|
-
|
|
134
|
+
Five details, each of which has cost somebody a day:
|
|
118
135
|
|
|
119
136
|
- **The cookie holds `1`, never the token.** It records that the token was
|
|
120
137
|
presented once, so navigation inside the frame stays armed without the secret
|
|
@@ -127,6 +144,10 @@ Four details, each of which has cost somebody a day:
|
|
|
127
144
|
- **…but `None` is only honoured with `Secure`**, and `Secure` is dropped over
|
|
128
145
|
plain http — so a local `next dev` would set a cookie the browser silently
|
|
129
146
|
discards. Fall back to `Lax`, which is same-origin anyway.
|
|
147
|
+
- **A fresh rejected token expires both cookie spellings.** A browser may still
|
|
148
|
+
carry a valid cookie from an earlier link. Leaving it in place turns the new
|
|
149
|
+
rejection into an accepted launch and lets the old capture/read/run context
|
|
150
|
+
survive under a different review link.
|
|
130
151
|
|
|
131
152
|
### First-party, the cookie is an ordinary one — use `armedCookie()` (A-241)
|
|
132
153
|
|
|
@@ -140,12 +161,63 @@ Do not re-derive the attributes. `@weatherboard/annotation/protocol` exports the
|
|
|
140
161
|
cookie itself, so the two halves cannot disagree:
|
|
141
162
|
|
|
142
163
|
```ts
|
|
143
|
-
import {
|
|
164
|
+
import {
|
|
165
|
+
armedCookie,
|
|
166
|
+
clearedCookie,
|
|
167
|
+
ANNOTATION_ARMED_COOKIES,
|
|
168
|
+
ANNOTATION_TOKEN_PARAMS,
|
|
169
|
+
readAnnotationToken,
|
|
170
|
+
} from "@weatherboard/annotation/protocol";
|
|
144
171
|
|
|
145
|
-
|
|
146
|
-
|
|
172
|
+
const presented = readAnnotationToken(request.nextUrl.searchParams);
|
|
173
|
+
const attempted = ANNOTATION_TOKEN_PARAMS.some((name) => request.nextUrl.searchParams.has(name));
|
|
174
|
+
if (presented && presented === process.env.ANNOTATION_TOKEN) {
|
|
175
|
+
response.headers.append("set-cookie", armedCookie());
|
|
176
|
+
} else if (attempted) {
|
|
177
|
+
for (const name of ANNOTATION_ARMED_COOKIES) {
|
|
178
|
+
response.headers.append("set-cookie", clearedCookie(name));
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
Put the package bootstrap in the first raw script in `<head>`, before styles,
|
|
184
|
+
analytics and hydration:
|
|
185
|
+
|
|
186
|
+
```tsx
|
|
187
|
+
import { annotationBootstrapScript } from "@weatherboard/annotation/bootstrap";
|
|
188
|
+
|
|
189
|
+
<script dangerouslySetInnerHTML={{ __html: annotationBootstrapScript() }} />
|
|
147
190
|
```
|
|
148
191
|
|
|
192
|
+
The script synchronously strips `ai_annotate`, its legacy spelling,
|
|
193
|
+
`ai_capture`, `ai_read` and `ai_run`. When middleware accepted the arming link,
|
|
194
|
+
it keeps capture/read/run in this tab's `sessionStorage`, so same-tab routing
|
|
195
|
+
and a hard reload remain armed after the URL is clean. When middleware rejected
|
|
196
|
+
the link, it persists only `{ status: "rejected", capture: "present" | "missing" }`.
|
|
197
|
+
No credential from an unarmed link is stored.
|
|
198
|
+
|
|
199
|
+
The client mount can consume that signal once and report a generic failure:
|
|
200
|
+
|
|
201
|
+
```ts
|
|
202
|
+
import {
|
|
203
|
+
consumeAnnotationAttempt,
|
|
204
|
+
reportAnnotationStartupFailure,
|
|
205
|
+
} from "@weatherboard/annotation/bootstrap";
|
|
206
|
+
|
|
207
|
+
const attempt = consumeAnnotationAttempt();
|
|
208
|
+
if (attempt?.status === "rejected") {
|
|
209
|
+
return reportAnnotationStartupFailure("rejected-arming");
|
|
210
|
+
}
|
|
211
|
+
if (attempt?.capture === "missing") {
|
|
212
|
+
return reportAnnotationStartupFailure("missing-capture");
|
|
213
|
+
}
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
The reporter owns one host, `#ai-annotation-startup-status`, and returns a
|
|
217
|
+
cleanup function. Import and mount failures use `"import-failed"` and
|
|
218
|
+
`"mount-failed"`. All copy is package-owned and contains no token or product
|
|
219
|
+
name.
|
|
220
|
+
|
|
149
221
|
It takes no value argument on purpose — the cookie holds a marker, never the
|
|
150
222
|
token, and the first bullet above is the reason. The page half calls
|
|
151
223
|
`readArming()`, which reads that cookie, strips the tokens out of the URL with
|
|
@@ -341,20 +413,36 @@ panel with the comment, who raised it, when, and what has happened to it.
|
|
|
341
413
|
|
|
342
414
|
```tsx
|
|
343
415
|
const { readToken } = readArming(); // from `ai_read`, stripped and stored
|
|
344
|
-
if (!readToken) return; // a link minted before markers shipped
|
|
345
416
|
|
|
346
417
|
const { createAnnotationReader } = await import("@weatherboard/annotation/read");
|
|
347
418
|
const { mountAnnotationMarkers } = await import("@weatherboard/annotation/markers");
|
|
348
419
|
|
|
349
420
|
const layer = mountAnnotationMarkers({
|
|
350
|
-
reader
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
421
|
+
// `reader` is a REQUIRED NULLABLE, not an optional. Mount whenever armed and
|
|
422
|
+
// pass null when there is no token — the count line then says so on the page.
|
|
423
|
+
// Returning early instead leaves an armed page blank, which is the one state
|
|
424
|
+
// this layer exists to make impossible. See "The count line is the feature".
|
|
425
|
+
reader: readToken
|
|
426
|
+
? createAnnotationReader({
|
|
427
|
+
endpoint: "https://<console>/ingest/annotations",
|
|
428
|
+
readToken,
|
|
429
|
+
})
|
|
430
|
+
: null,
|
|
354
431
|
});
|
|
355
432
|
await layer.refresh(); // and again after a route change
|
|
356
433
|
```
|
|
357
434
|
|
|
435
|
+
**Call `refresh()` even with a null reader.** It is what replaces the *"Looking
|
|
436
|
+
for annotations…"* line with *"No read credential, so annotations were not
|
|
437
|
+
requested."* Treating a null reader as "nothing to do" leaves an armed page
|
|
438
|
+
loading forever — a mistake that is not obvious from the signature, and one the
|
|
439
|
+
first adopter made.
|
|
440
|
+
|
|
441
|
+
**Treat an empty token as absent, not as a credential.** `createAnnotationReader`
|
|
442
|
+
throws on `""`, and `?ai_read=` with nothing after it is a query string anyone
|
|
443
|
+
can type. Guard on presence, or a stray character in a URL takes the whole mount
|
|
444
|
+
down and the toolbar with it.
|
|
445
|
+
|
|
358
446
|
**It is a second credential, not the capture token.** The console refuses a write
|
|
359
447
|
claim at the read endpoint and a read claim at the ingest, so handing
|
|
360
448
|
`captureToken` to the reader does not degrade to a read — it fails. The read
|
|
@@ -376,6 +464,34 @@ the read, whatever the answer: *"3 annotations here, 1 shown"*. Zero markers is
|
|
|
376
464
|
exactly what "working, nothing here" looks like and a dozen distinct causes
|
|
377
465
|
produce it, so a layer that drew only icons would be silent in every one.
|
|
378
466
|
|
|
467
|
+
**A finished capture is hidden, and the line says how many.** The console
|
|
468
|
+
suppresses `done` and `cancelled` by default, so the sentence gains a third
|
|
469
|
+
clause when it is holding something back — *"3 annotations here, 1 shown · 2
|
|
470
|
+
resolved"* — and the pill grows a button that re-reads with them included. You
|
|
471
|
+
do not have to wire either one up; both are in the layer.
|
|
472
|
+
|
|
473
|
+
The clause matters most on the page where `total` is **zero and history is
|
|
474
|
+
not**: without it, a component with three finished conversations behind it reads
|
|
475
|
+
*"No annotations on this page"*, and a reviewer who fixed something and came back
|
|
476
|
+
to check what they had said would find no evidence there had ever been anything.
|
|
477
|
+
`total` counts what the response is about, so a suppressed capture is outside it
|
|
478
|
+
and is never added in — that is what keeps *"5 annotations here, 2 shown"* from
|
|
479
|
+
appearing on a page where nothing is wrong.
|
|
480
|
+
|
|
481
|
+
Pass `showResolved: true` to start wide. The default is `false`, matching the
|
|
482
|
+
console's own default deliberately: a layer that always asked for everything
|
|
483
|
+
would move that decision to a place nobody reading the console's code would see.
|
|
484
|
+
|
|
485
|
+
**If you drive the reader yourself**, `read(path, { includeResolved })` is the
|
|
486
|
+
only way to see them, and the console honours **only** the literal
|
|
487
|
+
`?resolved=include` — `true`, `1`, `yes`, `Include` and a trailing space all get
|
|
488
|
+
the default silently. Use the option rather than building the query string.
|
|
489
|
+
|
|
490
|
+
**Overriding `labels.count`?** It now takes a third argument,
|
|
491
|
+
`(total, shown, resolvedHidden)`. A two-parameter override still compiles and
|
|
492
|
+
still works — and will not say what is being held back, which is the one thing
|
|
493
|
+
that argument is for.
|
|
494
|
+
|
|
379
495
|
**`refresh()` never rejects.** A refused CORS preflight arrives as `TypeError:
|
|
380
496
|
Failed to fetch` with no status and no body; it becomes a sentence in the count
|
|
381
497
|
line rather than an exception in your page.
|
|
@@ -446,3 +562,22 @@ misattributed.
|
|
|
446
562
|
Every step above is a thing that, when absent, produces no error, no warning and
|
|
447
563
|
no log line — only an absence. When you are debugging, prefer the check that
|
|
448
564
|
distinguishes causes over the check that confirms your guess.
|
|
565
|
+
|
|
566
|
+
## Host-owned contextual feedback
|
|
567
|
+
|
|
568
|
+
`@weatherboard/annotation/selection` exposes `relativePoint`,
|
|
569
|
+
`captureTextSelection`, `resolveTextSelection` and `prepareTextSelections` without
|
|
570
|
+
arming the developer SDK, mounting UI or sending network requests. Production
|
|
571
|
+
hosts such as Pitch own authentication, capture intent, target persistence and
|
|
572
|
+
permission-scoped reads. Import this subpath directly; importing the root SDK
|
|
573
|
+
still observes its existing production restrictions.
|
|
574
|
+
|
|
575
|
+
Mark authored text containers with `data-annotation-text="host-field-path"`.
|
|
576
|
+
Capture returns UTF-16 offsets, exact quote and bounded prefix/suffix using CRLF
|
|
577
|
+
normalisation only. A field path is evidence qualified by a host content revision,
|
|
578
|
+
not a permanent identity. Pass whether content is unchanged when resolving;
|
|
579
|
+
edited content requires a unique quote/context match. Missing or ambiguous
|
|
580
|
+
locations must remain reviewable as unplaced feedback. `prepareTextSelections`
|
|
581
|
+
shares one bounded text-map snapshot across annotations in a layout frame; discard
|
|
582
|
+
it after content or layout changes. Neither helper reads application controls as
|
|
583
|
+
authored text or persists DOM nodes.
|
package/COMPATIBILITY.md
CHANGED
|
@@ -95,3 +95,18 @@ whoever drops it, in the same change, and not by whoever finds out.
|
|
|
95
95
|
|
|
96
96
|
`0.x` while the wire protocol is still moving. **1.0 is the statement that a
|
|
97
97
|
hundred consumers can rely on it**, not a way to look finished.
|
|
98
|
+
|
|
99
|
+
### Additive selection subpath (A-391)
|
|
100
|
+
|
|
101
|
+
The workspace adds `./selection` with ESM, CommonJS and TypeScript exports.
|
|
102
|
+
Existing SDK wire versions and production arming rules are unchanged. Pitch uses
|
|
103
|
+
the workspace source; external consumers need a subsequently published package
|
|
104
|
+
version containing this entry point. The tarball smoke test verifies its exports;
|
|
105
|
+
this feature does not itself publish an npm release.
|
|
106
|
+
|
|
107
|
+
### Additive bootstrap subpath and hosted-mode metadata (A-1375)
|
|
108
|
+
|
|
109
|
+
Version 0.6.0 adds `./bootstrap` for synchronous credential stripping,
|
|
110
|
+
per-tab continuation and token-free startup status. It also adds the exact
|
|
111
|
+
`x-weatherboard-annotation-mode: first-party` constants to `./protocol`. The
|
|
112
|
+
wire payload version and all existing names remain unchanged.
|
package/README.md
CHANGED
|
@@ -20,23 +20,37 @@ adding to your supply chain.
|
|
|
20
20
|
npm i @weatherboard/annotation
|
|
21
21
|
```
|
|
22
22
|
|
|
23
|
-
##
|
|
23
|
+
## Eight entry points
|
|
24
24
|
|
|
25
25
|
They are split by **who imports them**, not by topic.
|
|
26
26
|
|
|
27
27
|
| Import | Runs where | For |
|
|
28
28
|
|---|---|---|
|
|
29
29
|
| `@weatherboard/annotation` | The browser | The page SDK — overlay, target resolution, state collection, screenshots |
|
|
30
|
+
| `@weatherboard/annotation/bootstrap` | The browser, before hydration | Immediate URL stripping, arming reads and token-free startup status |
|
|
30
31
|
| `@weatherboard/annotation/protocol` | Anywhere, no DOM | The wire names and the two readers. This is what a middleware imports |
|
|
31
32
|
| `@weatherboard/annotation/host` | The reviewing console | The parent half of the channel |
|
|
32
33
|
| `@weatherboard/annotation/anchor` | The browser | `resolveAnchor` — where a capture's marker goes, or why it goes nowhere |
|
|
33
34
|
| `@weatherboard/annotation/read` | The browser | `createAnnotationReader` — the captures already made on this page |
|
|
34
35
|
| `@weatherboard/annotation/markers` | The browser | The marker layer and its panel |
|
|
36
|
+
| `@weatherboard/annotation/selection` | The browser | Authored-content text/point selection and placement helpers |
|
|
35
37
|
|
|
36
|
-
The
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
is.
|
|
38
|
+
The final four remain separate because products can choose capture, marker
|
|
39
|
+
rendering and authored-content selection independently. A product that merely
|
|
40
|
+
captures should not pay for readers, marker UI or selection helpers. They are
|
|
41
|
+
subpaths rather than a tree-shaking promise for the same reason `/protocol` is.
|
|
42
|
+
|
|
43
|
+
`/bootstrap` is also a subpath rather than a tree-shaking promise. Put
|
|
44
|
+
`annotationBootstrapScript()` in the first raw script in `<head>`, then keep the
|
|
45
|
+
root entry behind a dynamic import. A normal visitor pays for URL and cookie
|
|
46
|
+
reads without downloading the toolbar or state collector.
|
|
47
|
+
|
|
48
|
+
Package-authored browser UI keeps its styles zero-dependency too. Declare
|
|
49
|
+
colour, spacing and radius literals once as local `--ai-*` custom properties,
|
|
50
|
+
then consume them with `var()` in the same style string. Do not import a
|
|
51
|
+
product's design tokens: a toolbar or startup notice runs inside somebody
|
|
52
|
+
else's page, where those names and values are not ours. New runtime surfaces
|
|
53
|
+
must pass `pnpm gyde:check`; do not add their findings to the allowance ledger.
|
|
40
54
|
|
|
41
55
|
`/protocol` is a subpath rather than a tree-shaking promise on purpose: an edge
|
|
42
56
|
middleware is often bundled as CJS, and CJS does not shake. Importing the root
|
|
@@ -63,11 +77,21 @@ right is yours, because only you hold the secret.
|
|
|
63
77
|
|
|
64
78
|
```ts
|
|
65
79
|
// middleware.ts
|
|
66
|
-
import {
|
|
80
|
+
import {
|
|
81
|
+
ANNOTATION_ARMED_COOKIE,
|
|
82
|
+
ANNOTATION_ARMED_COOKIES,
|
|
83
|
+
ANNOTATION_TOKEN_PARAMS,
|
|
84
|
+
readAnnotationToken,
|
|
85
|
+
} from "@weatherboard/annotation/protocol";
|
|
67
86
|
|
|
68
87
|
const token = readAnnotationToken(request.nextUrl.searchParams);
|
|
69
|
-
|
|
88
|
+
const attempted = ANNOTATION_TOKEN_PARAMS.some((name) => request.nextUrl.searchParams.has(name));
|
|
89
|
+
if (token !== null && token === process.env.ANNOTATION_TOKEN) {
|
|
70
90
|
response.cookies.set(ANNOTATION_ARMED_COOKIE, "1", { httpOnly: false, sameSite: "lax" });
|
|
91
|
+
} else if (attempted) {
|
|
92
|
+
for (const name of ANNOTATION_ARMED_COOKIES) {
|
|
93
|
+
response.cookies.set(name, "", { path: "/", maxAge: 0, expires: new Date(0) });
|
|
94
|
+
}
|
|
71
95
|
}
|
|
72
96
|
```
|
|
73
97
|
|
|
@@ -75,7 +99,8 @@ if (token !== undefined && token === process.env.ANNOTATION_TOKEN) {
|
|
|
75
99
|
for an absent *or empty* parameter. An empty token compared with `===` against
|
|
76
100
|
your secret is a rejection; an empty token treated as present is an app armed
|
|
77
101
|
with the empty string, which fails later and reads like a bad secret rather than
|
|
78
|
-
a missing one.
|
|
102
|
+
a missing one. A fresh rejected attempt must expire **both** supported cookie
|
|
103
|
+
spellings so an already-armed browser cannot reuse its previous session.
|
|
79
104
|
|
|
80
105
|
## Source refs
|
|
81
106
|
|
package/dist/anchor.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";var f=Object.defineProperty;var x=Object.getOwnPropertyDescriptor;var E=Object.getOwnPropertyNames;var T=Object.prototype.hasOwnProperty;var R=(e,n)=>{for(var t in n)f(e,t,{get:n[t],enumerable:!0})},b=(e,n,t,o)=>{if(n&&typeof n=="object"||typeof n=="function")for(let r of E(n))!T.call(e,r)&&r!==t&&f(e,r,{get:()=>n[r],enumerable:!(o=x(n,r))||o.enumerable});return e};var y=e=>b(f({},"__esModule",{value:!0}),e);var
|
|
1
|
+
"use strict";var f=Object.defineProperty;var x=Object.getOwnPropertyDescriptor;var E=Object.getOwnPropertyNames;var T=Object.prototype.hasOwnProperty;var R=(e,n)=>{for(var t in n)f(e,t,{get:n[t],enumerable:!0})},b=(e,n,t,o)=>{if(n&&typeof n=="object"||typeof n=="function")for(let r of E(n))!T.call(e,r)&&r!==t&&f(e,r,{get:()=>n[r],enumerable:!(o=x(n,r))||o.enumerable});return e};var y=e=>b(f({},"__esModule",{value:!0}),e);var O={};R(O,{anchorFromTarget:()=>S,resolveAnchor:()=>A,tallyAnchors:()=>_});module.exports=y(O);var s="data-src";function g(e){return e.selection==="region"?"region":"element"}var m=8;function u(e){if(!e)return null;let n=e.lastIndexOf(":");if(n<=0)return null;let t=e.lastIndexOf(":",n-1);if(t<=0)return null;let o=e.slice(0,t),r=Number(e.slice(t+1,n)),i=Number(e.slice(n+1));return!o||!Number.isFinite(r)||!Number.isFinite(i)?null:{file:o,line:r,column:i}}function d(e,n=m){let t=[],o=e;for(;o&&t.length<n;){let r=u(o.getAttribute(s));if(r){let i=t[t.length-1];i&&i.file===r.file&&i.line===r.line||t.push(r)}o=o.parentElement}return t}function h(e){let n=e;for(;n;){if(u(n.getAttribute(s)))return n;n=n.parentElement}return null}function l(e,n){return e.file!==n.file||e.line!==n.line?!1:e.column===void 0||n.column===void 0?!0:e.column===n.column}function k(e,n){if(e.length===0)return!1;if(e.length<m){if(n.length!==e.length)return!1}else if(n.length<e.length)return!1;return e.every((t,o)=>{let r=n[o];return r!==void 0&&l(t,r)})}function N(e,n){let t={file:e.file,line:e.line};e.column!==void 0&&(t.column=e.column);let o=n.querySelectorAll(`[${s}]`),r=[];for(let i=0;i<o.length;i++){let a=o[i],c=u(a.getAttribute(s));if(!c||!l(t,c))continue;let p=u(h(a.parentElement)?.getAttribute(s)??null);p&&l(p,c)||r.push(a)}return r}function A(e,n){switch(e.kind){case"route":{if(!e.path)return"none";let t=n.location?.pathname;return!t||t!==e.path?"none":n.body??"none"}case"dom-path":{if(!e.path)return"none";let t;try{t=n.querySelectorAll(e.path)}catch{return"none"}return t.length===0?"none":t.length>1?"ambiguous":t[0]}case"source-ref":{if(e.chain.length===0)return"none";let t=N(e,n).filter(o=>k(e.chain,d(o)));return t.length===0?"none":t.length>1?"ambiguous":t[0]}default:return"none"}}function S(e,n={}){let t=()=>n.path===void 0?{kind:"route"}:{kind:"route",path:n.path};if(g(e)==="region")return t();let o=e.sourceChain??[],r=o[0],i=e.source??r;if(i&&r&&l(i,r)){let a={kind:"source-ref",file:i.file,line:i.line,chain:o};return i.column!==void 0&&(a.column=i.column),n.ordinal!==void 0&&(a.ordinal=n.ordinal),a}return e.domPath?{kind:"dom-path",path:e.domPath}:t()}function _(e,n){let t={total:e.length,placed:0,ambiguous:0,unresolved:0};for(let o of e){let r=A(o,n);r==="ambiguous"?t.ambiguous+=1:r==="none"?t.unresolved+=1:t.placed+=1}return t}
|
|
2
2
|
//# sourceMappingURL=anchor.cjs.map
|