@ingcreators/annot-product-docs-astro 0.2.2 → 0.3.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/CHANGELOG.md +160 -0
- package/README.md +22 -33
- package/dist/cache.d.ts +24 -1
- package/dist/components/AnnotCallout.astro +32 -0
- package/dist/components/AnnotEditButton.astro +186 -0
- package/dist/components/AnnotEditCompleteListener.astro +186 -0
- package/dist/components/AnnotEditorIframeModal.astro +202 -0
- package/dist/components/Screen.astro +28 -8
- package/dist/editor-config-virtual.d.ts +12 -0
- package/dist/index.d.ts +5 -3
- package/dist/index.js +170 -9
- package/dist/integration.d.ts +58 -8
- package/dist/playwright/index.d.ts +35 -4
- package/dist/playwright/index.js +6 -224
- package/dist/render.d.ts +1 -57
- package/package.json +14 -14
- package/dist/playwright/fixture.d.ts +0 -72
- package/dist/playwright/rebase.d.ts +0 -36
- package/dist/render-BSQOeqPB.js +0 -164
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,165 @@
|
|
|
1
1
|
# @ingcreators/annot-product-docs-astro
|
|
2
2
|
|
|
3
|
+
## 0.3.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Updated dependencies [6124d59]
|
|
8
|
+
- Updated dependencies [b5d52f6]
|
|
9
|
+
- Updated dependencies [fa712fd]
|
|
10
|
+
- Updated dependencies [0c7ac26]
|
|
11
|
+
- Updated dependencies [f09a6b1]
|
|
12
|
+
- Updated dependencies [0d19345]
|
|
13
|
+
- Updated dependencies [64dc6e8]
|
|
14
|
+
- Updated dependencies [691bec5]
|
|
15
|
+
- Updated dependencies [9697f27]
|
|
16
|
+
- Updated dependencies [266b05a]
|
|
17
|
+
- @ingcreators/annot-annotator@0.6.0
|
|
18
|
+
- @ingcreators/annot-product-docs@0.4.0
|
|
19
|
+
- @ingcreators/annot-core@0.3.0
|
|
20
|
+
- @ingcreators/annot-playwright@0.4.1
|
|
21
|
+
|
|
22
|
+
## 0.3.0
|
|
23
|
+
|
|
24
|
+
### Minor Changes
|
|
25
|
+
|
|
26
|
+
- f5dc7cb: **`@ingcreators/annot-product-docs-astro/playwright` becomes a
|
|
27
|
+
deprecated re-export** — Phase 4 of
|
|
28
|
+
`docs/plans/playwright-screenshot-fixture-relayer.md`. The fixture
|
|
29
|
+
that originally lived here moved to `@ingcreators/annot-playwright`
|
|
30
|
+
(generic patch) + `@ingcreators/annot-product-docs` (MDX resolver)
|
|
31
|
+
in Phases 1–3 of the plan. Phase 4 deletes the duplicate code
|
|
32
|
+
from this package and converts the `/playwright` subpath into a
|
|
33
|
+
shim that re-exports the canonical surface so existing callers
|
|
34
|
+
keep compiling.
|
|
35
|
+
|
|
36
|
+
```ts
|
|
37
|
+
// Was:
|
|
38
|
+
import { test } from "@ingcreators/annot-product-docs-astro/playwright";
|
|
39
|
+
|
|
40
|
+
// Now (recommended):
|
|
41
|
+
import { test } from "@ingcreators/annot-product-docs"; // with MDX
|
|
42
|
+
// or
|
|
43
|
+
import { test } from "@ingcreators/annot-playwright"; // without MDX
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
The deprecated subpath emits a one-time
|
|
47
|
+
`process.emitWarning("DeprecationWarning", …)` at import time so
|
|
48
|
+
the migration prompt shows up in CI logs. **Reference equality
|
|
49
|
+
preserved** — `test`, `expect`, `patchScreenshot`,
|
|
50
|
+
`rebaseAnnotations`, `describeAnnotation` are reference-equal to
|
|
51
|
+
their canonical homes; a new
|
|
52
|
+
`packages/product-docs-astro/src/playwright/index.test.ts`
|
|
53
|
+
asserts this.
|
|
54
|
+
|
|
55
|
+
**Removal target**: `@ingcreators/annot-product-docs-astro@0.5.0`,
|
|
56
|
+
matching the OQ-2 decision (b) in the parent plan — visible
|
|
57
|
+
deprecation, known sunset.
|
|
58
|
+
|
|
59
|
+
## render.ts switches to the canonical helpers
|
|
60
|
+
|
|
61
|
+
`renderAnnotatedScreen()` previously carried its own copies of
|
|
62
|
+
`resolveMdxAnnotations` / `parseSnapshotBoxes` /
|
|
63
|
+
`buildBadgeAnnotations` / `svgFromBadges` / `svgFromBboxAnnotations`
|
|
64
|
+
/ `emptyAnnotationsSvg`. Phase 2 of the plan moved the canonical
|
|
65
|
+
home into `@ingcreators/annot-product-docs`; this PR deletes the
|
|
66
|
+
duplicates from `product-docs-astro/render.ts` and consumes the
|
|
67
|
+
ones in product-docs going forward.
|
|
68
|
+
|
|
69
|
+
`resolveMdxAnnotations` + `svgFromBboxAnnotations` are re-exported
|
|
70
|
+
from `render.ts` for one deprecation cycle so existing callers
|
|
71
|
+
that imported them from `@ingcreators/annot-product-docs-astro`
|
|
72
|
+
keep compiling. `parseSnapshotBoxes` is dropped from the public
|
|
73
|
+
surface — new code should import it from
|
|
74
|
+
`@ingcreators/annot-product-docs` directly.
|
|
75
|
+
|
|
76
|
+
## peerDependencies cleanup
|
|
77
|
+
|
|
78
|
+
`@playwright/test` is removed from `peerDependencies` (and from
|
|
79
|
+
`peerDependenciesMeta`). The package no longer has a Playwright
|
|
80
|
+
relationship to advertise — the `/playwright` subpath is purely
|
|
81
|
+
a re-export shim, and its types flow through the
|
|
82
|
+
`@ingcreators/annot-product-docs` workspace dep transitively.
|
|
83
|
+
This matches the OQ-3 decision (b) in the parent plan.
|
|
84
|
+
|
|
85
|
+
## Verified
|
|
86
|
+
- `pnpm -r typecheck` — 20 packages, all pass.
|
|
87
|
+
- `pnpm test` — 252 files, 3641 tests, 0 failures. New
|
|
88
|
+
`playwright/index.test.ts` (5 reference-equality assertions)
|
|
89
|
+
passes; the deprecation `process.emitWarning` fires on
|
|
90
|
+
import (visible in vitest output) but does not break anything.
|
|
91
|
+
- `pnpm lint` — exit 0; 29 pre-existing warnings unchanged.
|
|
92
|
+
- `pnpm --filter @ingcreators/annot-product-docs-astro build` —
|
|
93
|
+
emits `dist/index.js` (3.96 kB / 1.71 kB gzip) +
|
|
94
|
+
`dist/playwright/index.js` (0.85 kB / 0.39 kB gzip) — the
|
|
95
|
+
shrunken subpath bundle reflects the re-export-only shape.
|
|
96
|
+
|
|
97
|
+
### Patch Changes
|
|
98
|
+
|
|
99
|
+
- 85d40e6: **Docs + CLAUDE.md + plan archive** — Phase 5 (final) of
|
|
100
|
+
`docs/plans/_done/playwright-screenshot-fixture-relayer.md`.
|
|
101
|
+
Refreshes the doc surfaces, the operational CLAUDE.md notes,
|
|
102
|
+
and archives the relayer plan into `_done/`.
|
|
103
|
+
|
|
104
|
+
## Doc surface updates
|
|
105
|
+
- `packages/docs-site/src/content/docs/product-docs/playwright-fixture.mdx`
|
|
106
|
+
— recommended import paths updated; new "Choosing your import"
|
|
107
|
+
section covers the `@ingcreators/annot-product-docs` (MDX)
|
|
108
|
+
vs. `@ingcreators/annot-playwright` (no MDX) split; companion
|
|
109
|
+
helpers section now imports from the canonical homes; codegen
|
|
110
|
+
workflow example swapped to `@ingcreators/annot-product-docs`.
|
|
111
|
+
- `packages/docs-site/src/content/docs/api/create-annotator.mdx`
|
|
112
|
+
— "From a Playwright test" example imports from the canonical
|
|
113
|
+
home; mentions the no-MDX alternative.
|
|
114
|
+
- `packages/playwright/README.md` — adds the
|
|
115
|
+
`page.screenshot({ annot: { … } })` (recommended) section
|
|
116
|
+
above the existing `annotator.annotateScreenshot(...)` flow;
|
|
117
|
+
documents the `annotSourceResolvers` extension hook + the
|
|
118
|
+
coordinate-rebase helpers.
|
|
119
|
+
- `packages/product-docs/README.md` — adds a `page.screenshot({
|
|
120
|
+
annot })` Playwright fixture section explaining the
|
|
121
|
+
productDocs.sync + MDX-resolver bundle.
|
|
122
|
+
- `packages/product-docs-astro/README.md` — replaces the
|
|
123
|
+
Playwright fixture section with a Migration note pointing at
|
|
124
|
+
the canonical homes; documents the `0.5.0` removal target.
|
|
125
|
+
|
|
126
|
+
## CLAUDE.md monorepo layout
|
|
127
|
+
- `playwright/` entry now describes the canonical
|
|
128
|
+
`page.screenshot({ annot })` patch + `annotSourceResolvers`
|
|
129
|
+
extension hook. Version bumped to 0.4.0.
|
|
130
|
+
- `product-docs/` entry mentions the `productDocs` fixture
|
|
131
|
+
rename + the MDX-aware resolver registration via the hook
|
|
132
|
+
registry. Version bumped to 0.3.0.
|
|
133
|
+
- `product-docs-astro/` entry calls out the deprecated
|
|
134
|
+
`/playwright` re-export shim + 0.5.0 removal target.
|
|
135
|
+
`@playwright/test` no longer a peer dep. Version bumped to
|
|
136
|
+
0.3.0.
|
|
137
|
+
|
|
138
|
+
## Plan archive
|
|
139
|
+
- `docs/plans/playwright-screenshot-fixture-relayer.md` →
|
|
140
|
+
`docs/plans/_done/playwright-screenshot-fixture-relayer.md`.
|
|
141
|
+
Status header switched to `Done` with the four landing PRs
|
|
142
|
+
enumerated. Internal `./_done/...` link paths updated to
|
|
143
|
+
`./...` (the plan is itself inside `_done/` now).
|
|
144
|
+
- `docs/plans/README.md` — removed the active entry, added a
|
|
145
|
+
"Recently landed plans" row pointing at the archived plan
|
|
146
|
+
with a multi-phase summary covering all four landing PRs.
|
|
147
|
+
- `docs/plans/living-spec-authoring-roadmap.md` — three link
|
|
148
|
+
references updated to point at the archived path; the "How
|
|
149
|
+
this relates" line switched from "Already Draft" to "Landed
|
|
150
|
+
2026-05-22 (PRs #962 / #963 / #964 / #966)".
|
|
151
|
+
|
|
152
|
+
No source code changes; verified via `pnpm -r typecheck`,
|
|
153
|
+
`pnpm test`, `pnpm lint` regardless to confirm the doc/comment
|
|
154
|
+
edits parse cleanly.
|
|
155
|
+
|
|
156
|
+
- Updated dependencies [f979374]
|
|
157
|
+
- Updated dependencies [5778902]
|
|
158
|
+
- Updated dependencies [96e7625]
|
|
159
|
+
- Updated dependencies [85d40e6]
|
|
160
|
+
- @ingcreators/annot-playwright@0.4.0
|
|
161
|
+
- @ingcreators/annot-product-docs@0.3.0
|
|
162
|
+
|
|
3
163
|
## 0.2.2
|
|
4
164
|
|
|
5
165
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -89,41 +89,30 @@ When the stored snapshot lacks `[box=x,y,w,h]` markers (no
|
|
|
89
89
|
Playwright tour has run yet), the function returns the base
|
|
90
90
|
PNG verbatim with `hadBoundingBoxes: false`.
|
|
91
91
|
|
|
92
|
-
## Playwright fixture
|
|
92
|
+
## Migration — Playwright fixture moved
|
|
93
93
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
`
|
|
98
|
-
|
|
99
|
-
one
|
|
100
|
-
`page.screenshot` + `screen.capture` + `renderAnnotatedScreen`
|
|
101
|
-
+ `writeFile` quartet:
|
|
94
|
+
> The Playwright fixture that previously lived at
|
|
95
|
+
> `@ingcreators/annot-product-docs-astro/playwright` moved out
|
|
96
|
+
> of this package as part of
|
|
97
|
+
> [`docs/plans/_done/playwright-screenshot-fixture-relayer.md`](https://github.com/ingcreators/annot/blob/main/docs/plans/_done/playwright-screenshot-fixture-relayer.md)
|
|
98
|
+
> (Phases 1–4). Code authoring a Playwright spec should pick
|
|
99
|
+
> one of the two canonical homes:
|
|
102
100
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
Calls without `annot` (or with `annot: true` / `{}`) fall
|
|
119
|
-
through to vanilla Playwright byte-for-byte — codegen-emitted
|
|
120
|
-
calls keep working unedited. See the docs at
|
|
121
|
-
[`annot.work/docs/product-docs/playwright-fixture/`](https://annot.work/docs/product-docs/playwright-fixture/)
|
|
122
|
-
for the compositional vocabulary, locator screenshot semantics,
|
|
123
|
-
and the codegen→hand-edit workflow.
|
|
124
|
-
|
|
125
|
-
`@playwright/test` is an **optional peer** — Astro-only users
|
|
126
|
-
don't need to install it.
|
|
101
|
+
| Use case | Import |
|
|
102
|
+
|---|---|
|
|
103
|
+
| Living-product-docs tour (MDX-linked `annot.mdx`) | `import { test } from "@ingcreators/annot-product-docs";` |
|
|
104
|
+
| VRT specs / marketing screenshots / AI agent flows (no MDX) | `import { test } from "@ingcreators/annot-playwright";` |
|
|
105
|
+
|
|
106
|
+
The `@ingcreators/annot-product-docs-astro/playwright` subpath
|
|
107
|
+
remains as a deprecated re-export so existing callers still
|
|
108
|
+
compile; it emits a `DeprecationWarning` at import time and
|
|
109
|
+
will be removed in `@ingcreators/annot-product-docs-astro@0.5.0`.
|
|
110
|
+
`@playwright/test` is no longer a peer dep of this package —
|
|
111
|
+
the canonical homes carry the relationship now.
|
|
112
|
+
|
|
113
|
+
`renderAnnotatedScreen` (the Astro Image Service) lives on as
|
|
114
|
+
the build-time renderer for the `<Screen>` block; this section
|
|
115
|
+
isn't going anywhere.
|
|
127
116
|
|
|
128
117
|
## Tier
|
|
129
118
|
|
package/dist/cache.d.ts
CHANGED
|
@@ -4,8 +4,24 @@
|
|
|
4
4
|
* consumer the next time they build. Keep this small (a single
|
|
5
5
|
* digit per breaking change) — large jumps don't help and just
|
|
6
6
|
* obscure the upgrade history.
|
|
7
|
+
*
|
|
8
|
+
* Bumped to 2 in Phase 1h of
|
|
9
|
+
* `docs/plans/living-spec-authoring-roadmap.md` — the renderer now
|
|
10
|
+
* prefers PNG XMP `annot:elementTree` bboxes over the legacy
|
|
11
|
+
* `annot:snapshot` MDX comment block when both are present. Forcing
|
|
12
|
+
* a cold cache avoids the stale-cache window where a migrated MDX
|
|
13
|
+
* (legacy comment block stripped) + new PNG XMP would otherwise
|
|
14
|
+
* map to the same cache key as the pre-migration state.
|
|
15
|
+
*
|
|
16
|
+
* Known limitation: post-migration, re-running the Playwright tour
|
|
17
|
+
* mutates the PNG XMP without touching the MDX. Today's cache key
|
|
18
|
+
* doesn't include PNG content, so the cache won't auto-invalidate
|
|
19
|
+
* on tour re-runs. Workaround until that's fixed: delete the
|
|
20
|
+
* `.astro/annot-product-docs/` cache directory between tour runs
|
|
21
|
+
* (or bump this version every time the tour is expected to change
|
|
22
|
+
* output). A follow-up will fold a PNG content hash into the key.
|
|
7
23
|
*/
|
|
8
|
-
export declare const RENDER_PIPELINE_VERSION =
|
|
24
|
+
export declare const RENDER_PIPELINE_VERSION = 2;
|
|
9
25
|
export interface CacheKeyInput {
|
|
10
26
|
mdxSource: string;
|
|
11
27
|
screenId: string;
|
|
@@ -16,6 +32,13 @@ export interface CacheKeyInput {
|
|
|
16
32
|
* historical key shape for flat callers.
|
|
17
33
|
*/
|
|
18
34
|
editable?: boolean;
|
|
35
|
+
/**
|
|
36
|
+
* Phase 2b: raw bytes of the `<Screen annotations="…">` yaml,
|
|
37
|
+
* when set. Folded into the cache key so editing the yaml busts
|
|
38
|
+
* the cached PNG. Undefined when the screen uses the inline
|
|
39
|
+
* `<Overlay>` path (legacy callers' key shape is preserved).
|
|
40
|
+
*/
|
|
41
|
+
annotationsYamlSource?: string;
|
|
19
42
|
}
|
|
20
43
|
/**
|
|
21
44
|
* Compute the cache key for a `<Screen>` block. Stable across
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
---
|
|
2
|
+
// `<AnnotCallout>` — Phase 2b of
|
|
3
|
+
// `docs/plans/living-spec-authoring-roadmap.md`. Caption for one
|
|
4
|
+
// callout entry on a `<Screen annotations="…">`. Replaces the
|
|
5
|
+
// inline `<Overlay match intent number>` form: match / intent /
|
|
6
|
+
// number now live in the screen's annotation yaml, and this
|
|
7
|
+
// component carries only the visible body.
|
|
8
|
+
//
|
|
9
|
+
// MDX usage:
|
|
10
|
+
// <Screen id="login" src="./shots/login.png"
|
|
11
|
+
// annotations="./annotations/login.yaml">
|
|
12
|
+
// <AnnotCallout for="o1">**Email** — registered address.</AnnotCallout>
|
|
13
|
+
// <AnnotCallout for="o2">**Password** — characters hidden.</AnnotCallout>
|
|
14
|
+
// </Screen>
|
|
15
|
+
//
|
|
16
|
+
// The visible badge / number / intent border is composed onto the
|
|
17
|
+
// annotated PNG by the Image Service (see `../render.ts`). The
|
|
18
|
+
// HTML side emits the body inside a `<li>` so the legend
|
|
19
|
+
// auto-numbers via the surrounding `<ol class="annot-screen-overlays">`
|
|
20
|
+
// counter — no per-call `number` prop required.
|
|
21
|
+
|
|
22
|
+
interface Props {
|
|
23
|
+
/** Targets `overlays[].id` in the screen's annotation yaml. */
|
|
24
|
+
for: string;
|
|
25
|
+
}
|
|
26
|
+
const { for: forId } = Astro.props;
|
|
27
|
+
---
|
|
28
|
+
<li class="annot-callout" data-callout-for={forId}>
|
|
29
|
+
<div class="annot-callout-body">
|
|
30
|
+
<slot />
|
|
31
|
+
</div>
|
|
32
|
+
</li>
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
---
|
|
2
|
+
// `<AnnotEditButton>` — Phase 5d of
|
|
3
|
+
// `docs/plans/living-spec-authoring-roadmap.md`. Mountable
|
|
4
|
+
// "✏️ Edit" button that routes visitors to the annot-cloud
|
|
5
|
+
// editor at `${cloudUrl}/embed?...`. Phase 5 ships three embed
|
|
6
|
+
// modes:
|
|
7
|
+
//
|
|
8
|
+
// - `newTab` (default): click → `window.open(...)`. Works
|
|
9
|
+
// under every Enterprise constraint (CSP / SSO / on-prem /
|
|
10
|
+
// air-gapped). See OQ-09 in
|
|
11
|
+
// `living-spec-authoring-roadmap.md`.
|
|
12
|
+
// - `disabled`: button is not rendered. For read-only docs /
|
|
13
|
+
// air-gapped deployments without on-prem annot-cloud.
|
|
14
|
+
// - `inline` (opt-in): modal `<iframe>` mounted on the docs
|
|
15
|
+
// site. Requires `<AnnotEditorIframeModal />` (5e) to be
|
|
16
|
+
// mounted somewhere on the same page (typically in the
|
|
17
|
+
// layout). If the modal isn't present, `inline` gracefully
|
|
18
|
+
// degrades to `newTab` with a one-time `console.warn`.
|
|
19
|
+
//
|
|
20
|
+
// MDX usage:
|
|
21
|
+
//
|
|
22
|
+
// <AnnotEditButton
|
|
23
|
+
// repo="ingcreators/annot"
|
|
24
|
+
// pngPath="docs/shots/login.png"
|
|
25
|
+
// annotationsPath="docs/annotations/login.annotations.yaml"
|
|
26
|
+
// />
|
|
27
|
+
//
|
|
28
|
+
// `cloudUrl` defaults to `https://annot.work`. Override via
|
|
29
|
+
// per-call prop OR (in Phase 5f) `annot-docs.config.ts`'s
|
|
30
|
+
// `editor.cloudUrl` default. The defaults landed here are
|
|
31
|
+
// what Phase 5f will lift into the config; Phase 5d ships
|
|
32
|
+
// per-call props only.
|
|
33
|
+
|
|
34
|
+
import type { EmbedMode } from "@ingcreators/annot-embed-protocol";
|
|
35
|
+
import { ANNOT_EDITOR_CONFIG } from "../editor-config-virtual.js";
|
|
36
|
+
|
|
37
|
+
interface Props {
|
|
38
|
+
/** Owner/repo slug, e.g. `"ingcreators/annot"`. */
|
|
39
|
+
repo: string;
|
|
40
|
+
/** Path within the repo of the PNG being edited. */
|
|
41
|
+
pngPath: string;
|
|
42
|
+
/** Path within the repo of the linked `.annotations.yaml`. */
|
|
43
|
+
annotationsPath: string;
|
|
44
|
+
/** Embed mode. Defaults to the integration-supplied
|
|
45
|
+
* `editor.embedMode` (or `"newTab"` if unset). */
|
|
46
|
+
mode?: EmbedMode;
|
|
47
|
+
/** Cloud editor origin override. Defaults to the
|
|
48
|
+
* integration-supplied `editor.cloudUrl` (or
|
|
49
|
+
* `"https://annot.work"` if unset). */
|
|
50
|
+
cloudUrl?: string;
|
|
51
|
+
/** Visible button label. Defaults to `"✏️ Edit"`. */
|
|
52
|
+
label?: string;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const {
|
|
56
|
+
repo,
|
|
57
|
+
pngPath,
|
|
58
|
+
annotationsPath,
|
|
59
|
+
mode = ANNOT_EDITOR_CONFIG.embedMode,
|
|
60
|
+
cloudUrl = ANNOT_EDITOR_CONFIG.cloudUrl,
|
|
61
|
+
label = "✏️ Edit",
|
|
62
|
+
} = Astro.props;
|
|
63
|
+
---
|
|
64
|
+
{
|
|
65
|
+
mode !== "disabled" && (
|
|
66
|
+
<button
|
|
67
|
+
type="button"
|
|
68
|
+
class="annot-edit-button"
|
|
69
|
+
data-annot-edit-mode={mode}
|
|
70
|
+
data-annot-edit-cloud-url={cloudUrl}
|
|
71
|
+
data-annot-edit-repo={repo}
|
|
72
|
+
data-annot-edit-png-path={pngPath}
|
|
73
|
+
data-annot-edit-annotations-path={annotationsPath}
|
|
74
|
+
aria-label={`Edit ${pngPath}`}
|
|
75
|
+
>
|
|
76
|
+
{label}
|
|
77
|
+
</button>
|
|
78
|
+
)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
<script>
|
|
82
|
+
import { encodeEmbedRequestUrl } from "@ingcreators/annot-embed-protocol";
|
|
83
|
+
|
|
84
|
+
// One-time warning when a `mode="inline"` button is clicked
|
|
85
|
+
// but `<AnnotEditorIframeModal />` isn't on the page. The
|
|
86
|
+
// warning lives at the page-script level so multiple buttons
|
|
87
|
+
// share it.
|
|
88
|
+
let warnedMissingModal = false;
|
|
89
|
+
|
|
90
|
+
interface ModalApi {
|
|
91
|
+
open: (options: { cloudUrl: string; editorUrl: string }) => void;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function findModalApi(): ModalApi | undefined {
|
|
95
|
+
return (window as unknown as { __annotEditorIframeModal?: ModalApi })
|
|
96
|
+
.__annotEditorIframeModal;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function activate(button: HTMLButtonElement): void {
|
|
100
|
+
const mode = button.dataset.annotEditMode ?? "newTab";
|
|
101
|
+
const cloudUrl = button.dataset.annotEditCloudUrl ?? "";
|
|
102
|
+
const repo = button.dataset.annotEditRepo ?? "";
|
|
103
|
+
const pngPath = button.dataset.annotEditPngPath ?? "";
|
|
104
|
+
const annotationsPath = button.dataset.annotEditAnnotationsPath ?? "";
|
|
105
|
+
|
|
106
|
+
button.addEventListener("click", () => {
|
|
107
|
+
// Resolve the effective mode. `inline` falls back to
|
|
108
|
+
// `newTab` if `<AnnotEditorIframeModal />` isn't on the
|
|
109
|
+
// page so the button still does something useful (warn
|
|
110
|
+
// once so authors notice in DevTools).
|
|
111
|
+
let effectiveMode: "newTab" | "inline" = mode === "inline" ? "inline" : "newTab";
|
|
112
|
+
const modalApi = effectiveMode === "inline" ? findModalApi() : undefined;
|
|
113
|
+
if (effectiveMode === "inline" && !modalApi) {
|
|
114
|
+
if (!warnedMissingModal) {
|
|
115
|
+
// biome-ignore lint/suspicious/noConsole: degraded-UX surface for the missing modal.
|
|
116
|
+
console.warn(
|
|
117
|
+
'[<AnnotEditButton>] mode="inline" requires <AnnotEditorIframeModal /> on the page; falling back to mode="newTab". Mount the modal component in your layout to enable the inline flow.',
|
|
118
|
+
);
|
|
119
|
+
warnedMissingModal = true;
|
|
120
|
+
}
|
|
121
|
+
effectiveMode = "newTab";
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const returnUrl = window.location.href.split("#")[0];
|
|
125
|
+
try {
|
|
126
|
+
const url = encodeEmbedRequestUrl({
|
|
127
|
+
cloudUrl,
|
|
128
|
+
repo,
|
|
129
|
+
pngPath,
|
|
130
|
+
annotationsPath,
|
|
131
|
+
returnUrl,
|
|
132
|
+
mode: effectiveMode,
|
|
133
|
+
});
|
|
134
|
+
if (effectiveMode === "inline" && modalApi) {
|
|
135
|
+
modalApi.open({ cloudUrl, editorUrl: url });
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
window.open(url, "_blank", "noopener,noreferrer");
|
|
139
|
+
} catch (error) {
|
|
140
|
+
// biome-ignore lint/suspicious/noConsole: encoder errors surface as runtime warnings.
|
|
141
|
+
console.error("[<AnnotEditButton>] failed to build embed URL:", error);
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function init(): void {
|
|
147
|
+
const buttons = document.querySelectorAll<HTMLButtonElement>(
|
|
148
|
+
"button.annot-edit-button",
|
|
149
|
+
);
|
|
150
|
+
for (const button of Array.from(buttons)) {
|
|
151
|
+
activate(button);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (document.readyState === "loading") {
|
|
156
|
+
document.addEventListener("DOMContentLoaded", init);
|
|
157
|
+
} else {
|
|
158
|
+
init();
|
|
159
|
+
}
|
|
160
|
+
</script>
|
|
161
|
+
|
|
162
|
+
<style>
|
|
163
|
+
.annot-edit-button {
|
|
164
|
+
display: inline-flex;
|
|
165
|
+
align-items: center;
|
|
166
|
+
gap: 0.4em;
|
|
167
|
+
padding: 0.4em 0.8em;
|
|
168
|
+
font-family: inherit;
|
|
169
|
+
font-size: 0.9em;
|
|
170
|
+
line-height: 1.2;
|
|
171
|
+
color: var(--annot-edit-button-fg, #ffffff);
|
|
172
|
+
background: var(--annot-edit-button-bg, #3b82f6);
|
|
173
|
+
border: 0;
|
|
174
|
+
border-radius: 0.4em;
|
|
175
|
+
cursor: pointer;
|
|
176
|
+
transition: background 0.15s ease-out;
|
|
177
|
+
}
|
|
178
|
+
.annot-edit-button:hover,
|
|
179
|
+
.annot-edit-button:focus-visible {
|
|
180
|
+
background: var(--annot-edit-button-bg-hover, #2563eb);
|
|
181
|
+
outline: none;
|
|
182
|
+
}
|
|
183
|
+
.annot-edit-button:focus-visible {
|
|
184
|
+
box-shadow: 0 0 0 2px var(--annot-edit-button-focus-ring, #93c5fd);
|
|
185
|
+
}
|
|
186
|
+
</style>
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
---
|
|
2
|
+
// `<AnnotEditCompleteListener>` — Phase 5g of
|
|
3
|
+
// `docs/plans/living-spec-authoring-roadmap.md`.
|
|
4
|
+
//
|
|
5
|
+
// Invisible client-side component that surfaces a transient
|
|
6
|
+
// toast when the cloud editor returns control to the docs site
|
|
7
|
+
// after a save. Two trigger paths:
|
|
8
|
+
//
|
|
9
|
+
// - `newTab` mode: the cloud editor redirects to
|
|
10
|
+
// `${returnUrl}#edit-complete=<editId>`. On client-side mount
|
|
11
|
+
// this component parses `window.location.hash` via the 5b
|
|
12
|
+
// `parseEmbedReturnHash` codec, renders a toast when the
|
|
13
|
+
// hash carries an `EmbedReturnSignal` of kind `"complete"`,
|
|
14
|
+
// and dispatches an `annot:edit-complete` `CustomEvent` on
|
|
15
|
+
// `document` so downstream listeners (analytics, custom
|
|
16
|
+
// rebuild-status badges) can react.
|
|
17
|
+
//
|
|
18
|
+
// - `inline` mode: the 5e `<AnnotEditorIframeModal>` already
|
|
19
|
+
// dismisses itself on `EditCommitted`. This component
|
|
20
|
+
// listens for the same lifecycle event via an
|
|
21
|
+
// `annot-editor-iframe-modal-event` `CustomEvent` the 5e
|
|
22
|
+
// modal dispatches on `document`, then renders the same
|
|
23
|
+
// toast.
|
|
24
|
+
//
|
|
25
|
+
// Mount once per page (typically inside the layout). Multiple
|
|
26
|
+
// instances are safe — only the first activates; the rest
|
|
27
|
+
// short-circuit on init via the `data-annot-edit-complete-listener-active`
|
|
28
|
+
// marker.
|
|
29
|
+
|
|
30
|
+
interface Props {
|
|
31
|
+
/** Toast message override. Defaults to "Edit saved — site
|
|
32
|
+
* rebuilds in ~1 minute." */
|
|
33
|
+
message?: string;
|
|
34
|
+
/** Auto-dismiss timeout in milliseconds. Defaults to 8000.
|
|
35
|
+
* Set to `0` to keep the toast visible until the user
|
|
36
|
+
* dismisses it manually. */
|
|
37
|
+
dismissAfterMs?: number;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const { message = "Edit saved — site rebuilds in ~1 minute.", dismissAfterMs = 8000 } =
|
|
41
|
+
Astro.props;
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
<div
|
|
45
|
+
class="annot-edit-complete-listener"
|
|
46
|
+
data-annot-edit-complete-listener-root
|
|
47
|
+
data-annot-edit-complete-message={message}
|
|
48
|
+
data-annot-edit-complete-dismiss-ms={dismissAfterMs}
|
|
49
|
+
aria-live="polite"
|
|
50
|
+
aria-atomic="true"
|
|
51
|
+
>
|
|
52
|
+
<slot />
|
|
53
|
+
</div>
|
|
54
|
+
|
|
55
|
+
<script>
|
|
56
|
+
import { parseEmbedReturnHash } from "@ingcreators/annot-embed-protocol";
|
|
57
|
+
|
|
58
|
+
const ROOT_SELECTOR = "[data-annot-edit-complete-listener-root]";
|
|
59
|
+
const ACTIVE_MARKER = "data-annot-edit-complete-listener-active";
|
|
60
|
+
const TOAST_DISMISS_FALLBACK_MS = 8000;
|
|
61
|
+
|
|
62
|
+
interface Detail {
|
|
63
|
+
readonly editId: string;
|
|
64
|
+
readonly source: "newTab" | "inline";
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function findRoot(): HTMLElement | null {
|
|
68
|
+
// Pick the FIRST listener mounted on the page. Multiple
|
|
69
|
+
// copies are safe; subsequent ones short-circuit.
|
|
70
|
+
return document.querySelector<HTMLElement>(ROOT_SELECTOR);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function showToast(root: HTMLElement, detail: Detail): void {
|
|
74
|
+
const message = root.dataset.annotEditCompleteMessage ?? "Edit saved.";
|
|
75
|
+
const dismissAfterMs = Number(
|
|
76
|
+
root.dataset.annotEditCompleteDismissMs ?? TOAST_DISMISS_FALLBACK_MS,
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
const toast = document.createElement("div");
|
|
80
|
+
toast.className = "annot-edit-complete-toast";
|
|
81
|
+
toast.setAttribute("role", "status");
|
|
82
|
+
toast.dataset.annotEditCompleteId = detail.editId;
|
|
83
|
+
toast.dataset.annotEditCompleteSource = detail.source;
|
|
84
|
+
toast.textContent = message;
|
|
85
|
+
|
|
86
|
+
const dismissBtn = document.createElement("button");
|
|
87
|
+
dismissBtn.type = "button";
|
|
88
|
+
dismissBtn.className = "annot-edit-complete-toast-dismiss";
|
|
89
|
+
dismissBtn.setAttribute("aria-label", "Dismiss notification");
|
|
90
|
+
dismissBtn.textContent = "×";
|
|
91
|
+
dismissBtn.addEventListener("click", () => {
|
|
92
|
+
toast.remove();
|
|
93
|
+
});
|
|
94
|
+
toast.appendChild(dismissBtn);
|
|
95
|
+
|
|
96
|
+
root.appendChild(toast);
|
|
97
|
+
|
|
98
|
+
if (Number.isFinite(dismissAfterMs) && dismissAfterMs > 0) {
|
|
99
|
+
window.setTimeout(() => {
|
|
100
|
+
toast.remove();
|
|
101
|
+
}, dismissAfterMs);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
document.dispatchEvent(
|
|
105
|
+
new CustomEvent("annot:edit-complete", { detail }),
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function consumeReturnHash(root: HTMLElement): void {
|
|
110
|
+
const signal = parseEmbedReturnHash(window.location.hash);
|
|
111
|
+
if (!signal || signal.kind !== "complete") return;
|
|
112
|
+
showToast(root, { editId: signal.editId, source: "newTab" });
|
|
113
|
+
// Clear the hash so a page refresh doesn't re-toast.
|
|
114
|
+
if (typeof window.history.replaceState === "function") {
|
|
115
|
+
const cleanUrl = `${window.location.pathname}${window.location.search}`;
|
|
116
|
+
window.history.replaceState(null, "", cleanUrl);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function attachInlineListener(root: HTMLElement): void {
|
|
121
|
+
document.addEventListener("annot:editor-iframe-committed", (event: Event) => {
|
|
122
|
+
const detail = (event as CustomEvent<{ editId?: string }>).detail;
|
|
123
|
+
const editId = detail?.editId ?? "";
|
|
124
|
+
if (!editId) return;
|
|
125
|
+
showToast(root, { editId, source: "inline" });
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function init(): void {
|
|
130
|
+
const root = findRoot();
|
|
131
|
+
if (!root) return;
|
|
132
|
+
if (root.hasAttribute(ACTIVE_MARKER)) return;
|
|
133
|
+
root.setAttribute(ACTIVE_MARKER, "1");
|
|
134
|
+
|
|
135
|
+
consumeReturnHash(root);
|
|
136
|
+
attachInlineListener(root);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (document.readyState === "loading") {
|
|
140
|
+
document.addEventListener("DOMContentLoaded", init);
|
|
141
|
+
} else {
|
|
142
|
+
init();
|
|
143
|
+
}
|
|
144
|
+
</script>
|
|
145
|
+
|
|
146
|
+
<style>
|
|
147
|
+
.annot-edit-complete-listener {
|
|
148
|
+
position: fixed;
|
|
149
|
+
bottom: 1.5rem;
|
|
150
|
+
right: 1.5rem;
|
|
151
|
+
z-index: 9999;
|
|
152
|
+
display: flex;
|
|
153
|
+
flex-direction: column;
|
|
154
|
+
gap: 0.5rem;
|
|
155
|
+
pointer-events: none;
|
|
156
|
+
}
|
|
157
|
+
.annot-edit-complete-toast {
|
|
158
|
+
display: flex;
|
|
159
|
+
align-items: center;
|
|
160
|
+
gap: 0.75rem;
|
|
161
|
+
padding: 0.75rem 1rem;
|
|
162
|
+
color: var(--annot-edit-toast-fg, #ffffff);
|
|
163
|
+
background: var(--annot-edit-toast-bg, #16a34a);
|
|
164
|
+
border-radius: 0.4em;
|
|
165
|
+
box-shadow: 0 0.25rem 0.75rem rgba(0, 0, 0, 0.25);
|
|
166
|
+
font-family: inherit;
|
|
167
|
+
font-size: 0.95em;
|
|
168
|
+
line-height: 1.3;
|
|
169
|
+
pointer-events: auto;
|
|
170
|
+
}
|
|
171
|
+
.annot-edit-complete-toast-dismiss {
|
|
172
|
+
margin-left: auto;
|
|
173
|
+
background: transparent;
|
|
174
|
+
border: 0;
|
|
175
|
+
color: inherit;
|
|
176
|
+
font-size: 1.25em;
|
|
177
|
+
line-height: 1;
|
|
178
|
+
cursor: pointer;
|
|
179
|
+
padding: 0 0.25em;
|
|
180
|
+
}
|
|
181
|
+
.annot-edit-complete-toast-dismiss:hover,
|
|
182
|
+
.annot-edit-complete-toast-dismiss:focus-visible {
|
|
183
|
+
opacity: 0.8;
|
|
184
|
+
outline: none;
|
|
185
|
+
}
|
|
186
|
+
</style>
|