@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
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
---
|
|
2
|
+
// `<AnnotEditorIframeModal>` — Phase 5e of
|
|
3
|
+
// `docs/plans/living-spec-authoring-roadmap.md`. The inline-
|
|
4
|
+
// mode counterpart to `<AnnotEditButton>`'s default newTab
|
|
5
|
+
// behaviour. Mount once per page (typically in the docs site's
|
|
6
|
+
// layout) and any `<AnnotEditButton mode="inline">` on the
|
|
7
|
+
// same page opens the modal instead of a new tab.
|
|
8
|
+
//
|
|
9
|
+
// Lazy-loaded: the modal HTML + script only ship if this
|
|
10
|
+
// component is mounted. Pages that only use `mode="newTab"`
|
|
11
|
+
// (the default per OQ-09) never download the modal code.
|
|
12
|
+
// `<AnnotEditButton mode="inline">` on a page without
|
|
13
|
+
// `<AnnotEditorIframeModal />` gracefully degrades to newTab
|
|
14
|
+
// + a one-time `console.warn`.
|
|
15
|
+
//
|
|
16
|
+
// MDX usage in a layout:
|
|
17
|
+
//
|
|
18
|
+
// ---
|
|
19
|
+
// import { AnnotEditorIframeModal } from "@ingcreators/annot-product-docs-astro/components/AnnotEditorIframeModal.astro";
|
|
20
|
+
// ---
|
|
21
|
+
// <slot />
|
|
22
|
+
// <AnnotEditorIframeModal />
|
|
23
|
+
//
|
|
24
|
+
// Then any `<AnnotEditButton mode="inline" ... />` in MDX
|
|
25
|
+
// opens the modal on click. The modal listens for
|
|
26
|
+
// `EditCommitted` / `EditAbandoned` / `ResizeNeeded` via the
|
|
27
|
+
// 5c postMessage dispatcher and dismisses itself accordingly.
|
|
28
|
+
|
|
29
|
+
interface Props {
|
|
30
|
+
/** Modal aria-label. Overridable for localisation. */
|
|
31
|
+
ariaLabel?: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const { ariaLabel = "Annot editor" } = Astro.props;
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
<dialog
|
|
38
|
+
class="annot-editor-iframe-modal"
|
|
39
|
+
data-annot-edit-modal-root
|
|
40
|
+
aria-label={ariaLabel}
|
|
41
|
+
>
|
|
42
|
+
<iframe
|
|
43
|
+
class="annot-editor-iframe-modal-frame"
|
|
44
|
+
title={ariaLabel}
|
|
45
|
+
allow="clipboard-read; clipboard-write"
|
|
46
|
+
referrerpolicy="origin"
|
|
47
|
+
></iframe>
|
|
48
|
+
</dialog>
|
|
49
|
+
|
|
50
|
+
<script>
|
|
51
|
+
import {
|
|
52
|
+
createEmbedHostMessenger,
|
|
53
|
+
type EmbedEvent,
|
|
54
|
+
type EmbedMessenger,
|
|
55
|
+
} from "@ingcreators/annot-embed-protocol";
|
|
56
|
+
|
|
57
|
+
interface OpenModalOptions {
|
|
58
|
+
readonly cloudUrl: string;
|
|
59
|
+
readonly editorUrl: string;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const MAX_HEIGHT_FRACTION = 0.95;
|
|
63
|
+
const DEFAULT_FRAME_HEIGHT_PX = 720;
|
|
64
|
+
|
|
65
|
+
function findModal(): HTMLDialogElement | null {
|
|
66
|
+
return document.querySelector<HTMLDialogElement>(
|
|
67
|
+
"dialog[data-annot-edit-modal-root]",
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function findFrame(modal: HTMLDialogElement): HTMLIFrameElement | null {
|
|
72
|
+
return modal.querySelector<HTMLIFrameElement>(
|
|
73
|
+
"iframe.annot-editor-iframe-modal-frame",
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function clampHeight(requested: number): number {
|
|
78
|
+
if (!Number.isFinite(requested) || requested <= 0) {
|
|
79
|
+
return DEFAULT_FRAME_HEIGHT_PX;
|
|
80
|
+
}
|
|
81
|
+
const max = window.innerHeight * MAX_HEIGHT_FRACTION;
|
|
82
|
+
return Math.min(requested, max);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function openModal(options: OpenModalOptions): void {
|
|
86
|
+
const modal = findModal();
|
|
87
|
+
if (!modal) {
|
|
88
|
+
// biome-ignore lint/suspicious/noConsole: lazy-mount contract violation surfaces here.
|
|
89
|
+
console.warn(
|
|
90
|
+
"[<AnnotEditorIframeModal>] dialog element not found — was the component mounted on this page?",
|
|
91
|
+
);
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
const frame = findFrame(modal);
|
|
95
|
+
if (!frame) return;
|
|
96
|
+
|
|
97
|
+
frame.style.height = `${DEFAULT_FRAME_HEIGHT_PX}px`;
|
|
98
|
+
frame.src = options.editorUrl;
|
|
99
|
+
|
|
100
|
+
let messenger: EmbedMessenger | null = null;
|
|
101
|
+
|
|
102
|
+
const teardown = (): void => {
|
|
103
|
+
if (messenger) {
|
|
104
|
+
messenger.cleanup();
|
|
105
|
+
messenger = null;
|
|
106
|
+
}
|
|
107
|
+
frame.removeAttribute("src");
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
const onEvent = (event: EmbedEvent): void => {
|
|
111
|
+
if (event.type === "EditCommitted") {
|
|
112
|
+
// Forward to the docs-site's `<AnnotEditCompleteListener>` so the
|
|
113
|
+
// post-edit toast surfaces consistently across newTab + inline
|
|
114
|
+
// modes (Phase 5g).
|
|
115
|
+
document.dispatchEvent(
|
|
116
|
+
new CustomEvent("annot:editor-iframe-committed", {
|
|
117
|
+
detail: {
|
|
118
|
+
editId: event.editId,
|
|
119
|
+
commitSha: event.commitSha,
|
|
120
|
+
branch: event.branch,
|
|
121
|
+
prUrl: event.prUrl,
|
|
122
|
+
},
|
|
123
|
+
}),
|
|
124
|
+
);
|
|
125
|
+
modal.close();
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
if (event.type === "EditAbandoned") {
|
|
129
|
+
modal.close();
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
if (event.type === "ResizeNeeded") {
|
|
133
|
+
frame.style.height = `${clampHeight(event.height)}px`;
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
modal.addEventListener(
|
|
138
|
+
"close",
|
|
139
|
+
() => {
|
|
140
|
+
teardown();
|
|
141
|
+
},
|
|
142
|
+
{ once: true },
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
// Set up the host messenger after the iframe loads — its
|
|
146
|
+
// `contentWindow` is null until then. Once loaded, attach
|
|
147
|
+
// the messenger; subsequent postMessages from the cloud
|
|
148
|
+
// editor route through it.
|
|
149
|
+
frame.addEventListener(
|
|
150
|
+
"load",
|
|
151
|
+
() => {
|
|
152
|
+
try {
|
|
153
|
+
messenger = createEmbedHostMessenger({
|
|
154
|
+
frame,
|
|
155
|
+
expectedOrigin: options.cloudUrl,
|
|
156
|
+
onEvent,
|
|
157
|
+
});
|
|
158
|
+
} catch (error) {
|
|
159
|
+
// biome-ignore lint/suspicious/noConsole: surface failed host-messenger setup.
|
|
160
|
+
console.error("[<AnnotEditorIframeModal>] failed to attach host messenger:", error);
|
|
161
|
+
modal.close();
|
|
162
|
+
}
|
|
163
|
+
},
|
|
164
|
+
{ once: true },
|
|
165
|
+
);
|
|
166
|
+
|
|
167
|
+
modal.showModal();
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Expose the open function for `<AnnotEditButton>`'s inline-
|
|
171
|
+
// mode click handler to call. Keep the surface narrow — just
|
|
172
|
+
// `open` — so future expansion (close-from-outside,
|
|
173
|
+
// post-event listener API) is additive.
|
|
174
|
+
type ModalApi = {
|
|
175
|
+
open: (options: OpenModalOptions) => void;
|
|
176
|
+
};
|
|
177
|
+
(window as unknown as { __annotEditorIframeModal?: ModalApi }).__annotEditorIframeModal =
|
|
178
|
+
{ open: openModal };
|
|
179
|
+
</script>
|
|
180
|
+
|
|
181
|
+
<style>
|
|
182
|
+
.annot-editor-iframe-modal {
|
|
183
|
+
width: min(95vw, 1280px);
|
|
184
|
+
max-width: 95vw;
|
|
185
|
+
max-height: 95vh;
|
|
186
|
+
padding: 0;
|
|
187
|
+
border: 0;
|
|
188
|
+
border-radius: 0.6em;
|
|
189
|
+
background: var(--annot-editor-modal-bg, #ffffff);
|
|
190
|
+
box-shadow: 0 0.5em 2em rgba(0, 0, 0, 0.4);
|
|
191
|
+
overflow: hidden;
|
|
192
|
+
}
|
|
193
|
+
.annot-editor-iframe-modal::backdrop {
|
|
194
|
+
background: rgba(0, 0, 0, 0.5);
|
|
195
|
+
}
|
|
196
|
+
.annot-editor-iframe-modal-frame {
|
|
197
|
+
display: block;
|
|
198
|
+
width: 100%;
|
|
199
|
+
min-height: 360px;
|
|
200
|
+
border: 0;
|
|
201
|
+
}
|
|
202
|
+
</style>
|
|
@@ -1,27 +1,47 @@
|
|
|
1
1
|
---
|
|
2
2
|
// `<Screen>` — annotated screenshot block.
|
|
3
3
|
//
|
|
4
|
-
// MDX usage:
|
|
4
|
+
// MDX usage (legacy, inline `<Overlay>` callouts):
|
|
5
5
|
// <Screen id="login" src="./shots/login.png">
|
|
6
6
|
// <Overlay match={...} number={1}>...</Overlay>
|
|
7
7
|
// </Screen>
|
|
8
8
|
//
|
|
9
|
+
// MDX usage (Phase 2b, yaml-driven callouts):
|
|
10
|
+
// <Screen id="login" src="./shots/login.png"
|
|
11
|
+
// annotations="./annotations/login.yaml">
|
|
12
|
+
// <AnnotCallout for="o1">...</AnnotCallout>
|
|
13
|
+
// </Screen>
|
|
14
|
+
//
|
|
9
15
|
// The annotated PNG is produced at build time by the Image
|
|
10
16
|
// Service in `@ingcreators/annot-product-docs-astro` (see
|
|
11
|
-
// `../render.ts`).
|
|
12
|
-
//
|
|
13
|
-
//
|
|
17
|
+
// `../render.ts`). When `annotations` is set, the service reads
|
|
18
|
+
// the yaml + composes badges from `overlays[]`; otherwise it
|
|
19
|
+
// resolves the inline `<Overlay match>` children. At render time
|
|
20
|
+
// we just emit an `<img>` that points at the cache hash; the
|
|
21
|
+
// Astro pipeline resolves the final URL.
|
|
14
22
|
|
|
15
23
|
interface Props {
|
|
16
24
|
id: string;
|
|
17
25
|
src: string;
|
|
18
26
|
alt?: string;
|
|
27
|
+
/**
|
|
28
|
+
* Phase 2b of `docs/plans/living-spec-authoring-roadmap.md`.
|
|
29
|
+
* Path (relative to the MDX file) to an `.annotations.yaml`
|
|
30
|
+
* file describing the screen's overlays. When set, the Image
|
|
31
|
+
* Service prefers yaml-driven badge composition over the
|
|
32
|
+
* legacy inline `<Overlay>` children.
|
|
33
|
+
*/
|
|
34
|
+
annotations?: string;
|
|
19
35
|
}
|
|
20
|
-
const { id, src, alt } = Astro.props;
|
|
36
|
+
const { id, src, alt, annotations } = Astro.props;
|
|
21
37
|
---
|
|
22
|
-
<figure
|
|
38
|
+
<figure
|
|
39
|
+
class="annot-screen"
|
|
40
|
+
data-screen-id={id}
|
|
41
|
+
data-annotations-path={annotations}
|
|
42
|
+
>
|
|
23
43
|
<img src={src} alt={alt ?? `Screen ${id}`} loading="lazy" />
|
|
24
|
-
<
|
|
44
|
+
<ol class="annot-screen-overlays" data-screen-overlays={id}>
|
|
25
45
|
<slot />
|
|
26
|
-
</
|
|
46
|
+
</ol>
|
|
27
47
|
</figure>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { EmbedMode } from '@ingcreators/annot-embed-protocol';
|
|
2
|
+
export interface ResolvedEditorConfig {
|
|
3
|
+
readonly embedMode: EmbedMode;
|
|
4
|
+
readonly cloudUrl: string;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Phase 5 OSS-side built-in defaults. Mirror the per-component
|
|
8
|
+
* defaults declared inside `<AnnotEditButton>` (`mode="newTab"`,
|
|
9
|
+
* `cloudUrl="https://annot.work"`) so consumers without the
|
|
10
|
+
* integration installed still see consistent behaviour.
|
|
11
|
+
*/
|
|
12
|
+
export declare const ANNOT_EDITOR_CONFIG: ResolvedEditorConfig;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
export type { CacheKeyInput, FileCache } from './cache.js';
|
|
2
2
|
export { cacheKey, createFileCache, createMemoryCache, RENDER_PIPELINE_VERSION, } from './cache.js';
|
|
3
3
|
export type { GraphDirection, GraphEdge, Match, OverlayIntent, ScreenListEntry, TransitionEntry, } from './components/types.js';
|
|
4
|
-
export type {
|
|
5
|
-
export {
|
|
4
|
+
export type { ResolvedEditorConfig } from './editor-config-virtual.js';
|
|
5
|
+
export { ANNOT_EDITOR_CONFIG } from './editor-config-virtual.js';
|
|
6
|
+
export type { ProductDocsEditorOptions, ProductDocsIntegrationOptions, } from './integration.js';
|
|
7
|
+
export { editorConfigVirtualPlugin, productDocsIntegration, resolveEditorConfig, } from './integration.js';
|
|
6
8
|
export type { RenderAnnotatedScreenOptions, RenderResult, } from './render.js';
|
|
7
|
-
export {
|
|
9
|
+
export { renderAnnotatedScreen, resolveMdxAnnotations, svgFromBboxAnnotations } from './render.js';
|
package/dist/index.js
CHANGED
|
@@ -1,18 +1,179 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
import { createHash as e } from "node:crypto";
|
|
2
|
+
import { mkdir as t, readFile as n, writeFile as r } from "node:fs/promises";
|
|
3
|
+
import { dirname as i, isAbsolute as a, join as o, resolve as s } from "node:path";
|
|
4
|
+
import { burnRedactions as c, createAnnotator as l } from "@ingcreators/annot-annotator";
|
|
5
|
+
import { readElementTreePng as u } from "@ingcreators/annot-core";
|
|
6
|
+
import { buildBadgeAnnotations as d, buildBadgeAnnotationsFromYaml as f, buildRasterRedactRegionsFromYaml as p, buildShapeAnnotationsFromYaml as m, elementTreeToBoxedEntries as h, emptyAnnotationsSvg as g, parseAnnotationsYaml as _, parseMdxFile as v, parseSnapshotBoxes as y, resolveMdxAnnotations as b, svgFromBboxAnnotations as x, svgFromBboxAnnotations as S, warnLegacyOverlay as C } from "@ingcreators/annot-product-docs";
|
|
7
|
+
//#region src/cache.ts
|
|
8
|
+
var w = 2;
|
|
9
|
+
function T(t) {
|
|
10
|
+
let n = e("sha256");
|
|
11
|
+
return n.update("v2\n"), n.update(t.mdxSource), n.update("\0"), n.update(t.screenId), t.editable && n.update("\0editable"), t.annotationsYamlSource !== void 0 && (n.update("\0annotationsYaml\0"), n.update(t.annotationsYamlSource)), n.digest("hex");
|
|
12
|
+
}
|
|
13
|
+
function E(e) {
|
|
14
|
+
return {
|
|
15
|
+
dir: e,
|
|
16
|
+
async get(t) {
|
|
17
|
+
try {
|
|
18
|
+
let r = await n(o(e, `${t}.png`));
|
|
19
|
+
return new Uint8Array(r.buffer, r.byteOffset, r.byteLength);
|
|
20
|
+
} catch (e) {
|
|
21
|
+
if (e.code === "ENOENT") return null;
|
|
22
|
+
throw e;
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
async set(n, a) {
|
|
26
|
+
let s = o(e, `${n}.png`);
|
|
27
|
+
await t(i(s), { recursive: !0 }), await r(s, a);
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
function D() {
|
|
32
|
+
let e = /* @__PURE__ */ new Map();
|
|
33
|
+
return {
|
|
34
|
+
dir: ":memory:",
|
|
35
|
+
async get(t) {
|
|
36
|
+
return e.get(t) ?? null;
|
|
37
|
+
},
|
|
38
|
+
async set(t, n) {
|
|
39
|
+
e.set(t, n);
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
//#endregion
|
|
44
|
+
//#region src/editor-config-virtual.ts
|
|
45
|
+
var O = {
|
|
46
|
+
embedMode: "newTab",
|
|
47
|
+
cloudUrl: "https://annot.work"
|
|
48
|
+
}, k = "@ingcreators/annot-product-docs-astro", A = {
|
|
49
|
+
embedMode: "newTab",
|
|
50
|
+
cloudUrl: "https://annot.work"
|
|
51
|
+
}, j = "virtual:annot-docs/editor-config", M = `\0${j}`, N = "editor-config-virtual";
|
|
52
|
+
function P(e) {
|
|
53
|
+
return {
|
|
54
|
+
name: "annot-docs:editor-config-virtual",
|
|
55
|
+
enforce: "pre",
|
|
56
|
+
resolveId(e) {
|
|
57
|
+
return e === j || e.endsWith(`${N}.ts`) || e.endsWith(`${N}.js`) ? M : null;
|
|
58
|
+
},
|
|
59
|
+
load(t) {
|
|
60
|
+
return t === M ? `export const ANNOT_EDITOR_CONFIG = ${JSON.stringify(e)};\n` : null;
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
function F(e) {
|
|
65
|
+
return {
|
|
66
|
+
embedMode: e?.embedMode ?? A.embedMode,
|
|
67
|
+
cloudUrl: e?.cloudUrl ?? A.cloudUrl
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
function I(e = {}) {
|
|
5
71
|
let t = {
|
|
6
72
|
contentDir: e.contentDir ?? "docs",
|
|
7
73
|
configPath: e.configPath ?? "annot-docs.config.ts",
|
|
74
|
+
editor: e.editor,
|
|
8
75
|
verbose: e.verbose ?? !1
|
|
9
|
-
};
|
|
76
|
+
}, n = F(t.editor);
|
|
10
77
|
return {
|
|
11
|
-
name:
|
|
12
|
-
hooks: { "astro:config:setup": ({ logger: e }) => {
|
|
13
|
-
t.verbose && e.info(`${
|
|
78
|
+
name: k,
|
|
79
|
+
hooks: { "astro:config:setup": ({ logger: e, updateConfig: r }) => {
|
|
80
|
+
t.verbose && e.info(`${k} installed — contentDir=${t.contentDir} configPath=${t.configPath} editor.embedMode=${n.embedMode} editor.cloudUrl=${n.cloudUrl}`), r({ vite: { plugins: [P(n)] } });
|
|
14
81
|
} }
|
|
15
82
|
};
|
|
16
83
|
}
|
|
17
84
|
//#endregion
|
|
18
|
-
|
|
85
|
+
//#region src/render.ts
|
|
86
|
+
async function L(e) {
|
|
87
|
+
let t = e.cwd ?? process.cwd(), n = a(e.mdxPath) ? e.mdxPath : s(t, e.mdxPath), r = await v(n);
|
|
88
|
+
if (!r) throw Error(`renderAnnotatedScreen: ${e.mdxPath} has no \`annot:\` frontmatter — cannot render.`);
|
|
89
|
+
let o = r.screens.find((t) => t.id === e.screenId);
|
|
90
|
+
if (!o) throw Error(`renderAnnotatedScreen: ${e.mdxPath} has no <Screen id="${e.screenId}"> block.`);
|
|
91
|
+
let u = R(e.editable), _ = i(n), b = await B(o.annotations, _), x = T({
|
|
92
|
+
mdxSource: r.source,
|
|
93
|
+
screenId: e.screenId,
|
|
94
|
+
editable: u !== null,
|
|
95
|
+
annotationsYamlSource: b?.source
|
|
96
|
+
});
|
|
97
|
+
if (e.cache) {
|
|
98
|
+
let t = await e.cache.get(x);
|
|
99
|
+
if (t) return {
|
|
100
|
+
bytes: t,
|
|
101
|
+
fromCache: !0,
|
|
102
|
+
hadBoundingBoxes: !0
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
let w = e.basePngBytes ?? await V(r, o.src, _), E = H(w), D, O = z(w);
|
|
106
|
+
D = O ? h(O) : y(r.commentBlocks.snapshot ?? "");
|
|
107
|
+
let k = b && b.file.annotations ? p(b.file.annotations, D) : [], A = k.length > 0 ? await c(w, k) : w;
|
|
108
|
+
!b && o.overlays.length > 0 && C({
|
|
109
|
+
mdxPath: e.mdxPath,
|
|
110
|
+
screenId: e.screenId,
|
|
111
|
+
overlayCount: o.overlays.length
|
|
112
|
+
});
|
|
113
|
+
let j = b ? f(b.file.overlays, D, E) : d(o.overlays, D, E), M = [...b && b.file.annotations ? m(b.file.annotations, D, E) : [], ...j], N, P;
|
|
114
|
+
if (M.length === 0) if (P = k.length > 0, u !== null) {
|
|
115
|
+
let e = `data:image/png;base64,${Buffer.from(A).toString("base64")}`;
|
|
116
|
+
N = l({ loadSystemFonts: !0 }).toEditablePng({
|
|
117
|
+
originalDataUrl: e,
|
|
118
|
+
annotationsSvg: g(),
|
|
119
|
+
width: E.width,
|
|
120
|
+
height: E.height,
|
|
121
|
+
tags: u.tags
|
|
122
|
+
});
|
|
123
|
+
} else N = A;
|
|
124
|
+
else {
|
|
125
|
+
let e = `data:image/png;base64,${Buffer.from(A).toString("base64")}`, t = S(M), n = l({ loadSystemFonts: !0 }), r = {
|
|
126
|
+
originalDataUrl: e,
|
|
127
|
+
annotationsSvg: t,
|
|
128
|
+
width: E.width,
|
|
129
|
+
height: E.height
|
|
130
|
+
};
|
|
131
|
+
N = u === null ? n.toPng(r) : n.toEditablePng({
|
|
132
|
+
...r,
|
|
133
|
+
tags: u.tags
|
|
134
|
+
}), P = !0;
|
|
135
|
+
}
|
|
136
|
+
return e.cache && await e.cache.set(x, N), {
|
|
137
|
+
bytes: N,
|
|
138
|
+
fromCache: !1,
|
|
139
|
+
hadBoundingBoxes: P
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
function R(e) {
|
|
143
|
+
return e ? e === !0 ? {} : e : null;
|
|
144
|
+
}
|
|
145
|
+
function z(e) {
|
|
146
|
+
try {
|
|
147
|
+
return u(e);
|
|
148
|
+
} catch {
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
async function B(e, t) {
|
|
153
|
+
if (!e) return null;
|
|
154
|
+
let r = a(e) ? e : s(t, e), i;
|
|
155
|
+
try {
|
|
156
|
+
i = await n(r, "utf8");
|
|
157
|
+
} catch (t) {
|
|
158
|
+
throw Error(`renderAnnotatedScreen: <Screen annotations="${e}"> — failed to read ${r}: ${t.message}`);
|
|
159
|
+
}
|
|
160
|
+
return {
|
|
161
|
+
file: _(i),
|
|
162
|
+
source: i
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
async function V(e, t, r) {
|
|
166
|
+
if (!t) throw Error("renderAnnotatedScreen: <Screen src=...> is required — cannot render without a base image.");
|
|
167
|
+
let i = await n(a(t) ? t : s(r, t));
|
|
168
|
+
return new Uint8Array(i.buffer, i.byteOffset, i.byteLength);
|
|
169
|
+
}
|
|
170
|
+
function H(e) {
|
|
171
|
+
if (e.length < 24) throw Error("renderAnnotatedScreen: base PNG too short to contain IHDR.");
|
|
172
|
+
let t = new DataView(e.buffer, e.byteOffset, e.byteLength);
|
|
173
|
+
return {
|
|
174
|
+
width: t.getUint32(16, !1),
|
|
175
|
+
height: t.getUint32(20, !1)
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
//#endregion
|
|
179
|
+
export { O as ANNOT_EDITOR_CONFIG, w as RENDER_PIPELINE_VERSION, T as cacheKey, E as createFileCache, D as createMemoryCache, P as editorConfigVirtualPlugin, I as productDocsIntegration, L as renderAnnotatedScreen, F as resolveEditorConfig, b as resolveMdxAnnotations, x as svgFromBboxAnnotations };
|
package/dist/integration.d.ts
CHANGED
|
@@ -1,4 +1,27 @@
|
|
|
1
|
+
import { EmbedMode } from '@ingcreators/annot-embed-protocol';
|
|
1
2
|
import { AstroIntegration } from 'astro';
|
|
3
|
+
import { ResolvedEditorConfig } from './editor-config-virtual.js';
|
|
4
|
+
export interface EditorConfigVitePlugin {
|
|
5
|
+
readonly name: string;
|
|
6
|
+
readonly enforce?: "pre" | "post";
|
|
7
|
+
readonly resolveId: (id: string) => string | null;
|
|
8
|
+
readonly load: (id: string) => string | null;
|
|
9
|
+
}
|
|
10
|
+
export interface ProductDocsEditorOptions {
|
|
11
|
+
/**
|
|
12
|
+
* Default embed mode for every `<AnnotEditButton>` in the
|
|
13
|
+
* site. Per-call `mode` prop wins when set. Defaults to
|
|
14
|
+
* `"newTab"`.
|
|
15
|
+
*/
|
|
16
|
+
embedMode?: EmbedMode;
|
|
17
|
+
/**
|
|
18
|
+
* Cloud editor origin override for on-prem deployments
|
|
19
|
+
* (e.g. `"https://annot.internal.example.com"`). Per-call
|
|
20
|
+
* `cloudUrl` prop wins when set. Defaults to
|
|
21
|
+
* `"https://annot.work"`.
|
|
22
|
+
*/
|
|
23
|
+
cloudUrl?: string;
|
|
24
|
+
}
|
|
2
25
|
export interface ProductDocsIntegrationOptions {
|
|
3
26
|
/**
|
|
4
27
|
* Directory (relative to the Astro project root) that contains
|
|
@@ -13,6 +36,11 @@ export interface ProductDocsIntegrationOptions {
|
|
|
13
36
|
* `meta` defaults + per-book template lookups.
|
|
14
37
|
*/
|
|
15
38
|
configPath?: string;
|
|
39
|
+
/**
|
|
40
|
+
* Project-wide `<AnnotEditButton>` defaults. Phase 5f of
|
|
41
|
+
* `docs/plans/living-spec-authoring-roadmap.md`.
|
|
42
|
+
*/
|
|
43
|
+
editor?: ProductDocsEditorOptions;
|
|
16
44
|
/**
|
|
17
45
|
* If `true`, the integration logs a debug line on every
|
|
18
46
|
* config:setup. Useful for verifying the integration is
|
|
@@ -20,6 +48,28 @@ export interface ProductDocsIntegrationOptions {
|
|
|
20
48
|
*/
|
|
21
49
|
verbose?: boolean;
|
|
22
50
|
}
|
|
51
|
+
/**
|
|
52
|
+
* Vite plugin that:
|
|
53
|
+
*
|
|
54
|
+
* 1. Generates a virtual module (`virtual:annot-docs/editor-config`)
|
|
55
|
+
* exporting the resolved editor config.
|
|
56
|
+
*
|
|
57
|
+
* 2. Aliases every import of the package's
|
|
58
|
+
* `editor-config-virtual.ts` to the virtual module so
|
|
59
|
+
* `<AnnotEditButton>` picks up the project-wide defaults.
|
|
60
|
+
*
|
|
61
|
+
* Exported here for vitest. Consumers don't need to import it
|
|
62
|
+
* directly — `productDocsIntegration` wires it up automatically.
|
|
63
|
+
*/
|
|
64
|
+
export declare function editorConfigVirtualPlugin(resolved: ResolvedEditorConfig): EditorConfigVitePlugin;
|
|
65
|
+
/**
|
|
66
|
+
* Merge the per-call editor options into the built-in defaults.
|
|
67
|
+
* Future revision (Phase 5f follow-up if needed) also reads
|
|
68
|
+
* `annot-docs.config.ts`'s `editor` config + merges in the
|
|
69
|
+
* middle of this precedence ladder; today the integration
|
|
70
|
+
* option is the only override layer.
|
|
71
|
+
*/
|
|
72
|
+
export declare function resolveEditorConfig(editor: ProductDocsEditorOptions | undefined): ResolvedEditorConfig;
|
|
23
73
|
/**
|
|
24
74
|
* Astro integration factory. Drop into `astro.config.mjs`:
|
|
25
75
|
*
|
|
@@ -28,19 +78,19 @@ export interface ProductDocsIntegrationOptions {
|
|
|
28
78
|
* import { productDocsIntegration } from "@ingcreators/annot-product-docs-astro";
|
|
29
79
|
*
|
|
30
80
|
* export default defineConfig({
|
|
31
|
-
* integrations: [
|
|
81
|
+
* integrations: [
|
|
82
|
+
* productDocsIntegration({
|
|
83
|
+
* editor: {
|
|
84
|
+
* embedMode: "newTab",
|
|
85
|
+
* cloudUrl: "https://annot.internal.example.com",
|
|
86
|
+
* },
|
|
87
|
+
* }),
|
|
88
|
+
* ],
|
|
32
89
|
* });
|
|
33
90
|
* ```
|
|
34
|
-
*
|
|
35
|
-
* Phase 2 PR 1 ships the scaffold — the hooks are no-ops aside
|
|
36
|
-
* from optional verbose logging. PR 2 adds the Image Service;
|
|
37
|
-
* PR 3 wires up the seven docs components.
|
|
38
91
|
*/
|
|
39
92
|
export declare function productDocsIntegration(options?: ProductDocsIntegrationOptions): AstroIntegration;
|
|
40
93
|
/**
|
|
41
94
|
* Default-export shape some Astro integration authors prefer.
|
|
42
|
-
* Both `import productDocsIntegration from ...` (default) and
|
|
43
|
-
* `import { productDocsIntegration }` (named) resolve to the
|
|
44
|
-
* same factory; consumers can pick whichever feels natural.
|
|
45
95
|
*/
|
|
46
96
|
export default productDocsIntegration;
|
|
@@ -1,5 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @deprecated Import `AnnotScreenshotOptions` from
|
|
3
|
+
* `@ingcreators/annot-playwright` — that package owns the
|
|
4
|
+
* canonical interface; `@ingcreators/annot-product-docs` augments
|
|
5
|
+
* it with `mdx`. Re-exporting here is back-compat only and will
|
|
6
|
+
* be removed in `@ingcreators/annot-product-docs-astro@0.5.0`.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* @deprecated Import the rebase helpers (`Clip`, `RebaseResult`,
|
|
10
|
+
* `describeAnnotation`, `rebaseAnnotations`) from
|
|
11
|
+
* `@ingcreators/annot-playwright`. Re-exporting here is
|
|
12
|
+
* back-compat only and will be removed in
|
|
13
|
+
* `@ingcreators/annot-product-docs-astro@0.5.0`.
|
|
14
|
+
*/
|
|
15
|
+
export type { AnnotScreenshotOptions, Clip, RebaseResult } from '@ingcreators/annot-playwright';
|
|
16
|
+
/**
|
|
17
|
+
* @deprecated Import `patchScreenshot` from
|
|
18
|
+
* `@ingcreators/annot-playwright`. Re-exporting here is
|
|
19
|
+
* back-compat only and will be removed in
|
|
20
|
+
* `@ingcreators/annot-product-docs-astro@0.5.0`.
|
|
21
|
+
*/
|
|
22
|
+
export { describeAnnotation, patchScreenshot, rebaseAnnotations, } from '@ingcreators/annot-playwright';
|
|
23
|
+
/**
|
|
24
|
+
* @deprecated Import `test` from `@ingcreators/annot-product-docs`
|
|
25
|
+
* (with MDX support) or `@ingcreators/annot-playwright` (without).
|
|
26
|
+
* Re-exporting here is back-compat only and will be removed in
|
|
27
|
+
* `@ingcreators/annot-product-docs-astro@0.5.0`.
|
|
28
|
+
*/
|
|
29
|
+
export { test } from '@ingcreators/annot-product-docs';
|
|
30
|
+
/**
|
|
31
|
+
* @deprecated Import `expect` from `@ingcreators/annot-playwright`
|
|
32
|
+
* or `@playwright/test` directly. Re-exporting here is back-compat
|
|
33
|
+
* only and will be removed in
|
|
34
|
+
* `@ingcreators/annot-product-docs-astro@0.5.0`.
|
|
35
|
+
*/
|
|
1
36
|
export { expect } from '@playwright/test';
|
|
2
|
-
export type { AnnotScreenshotOptions } from './fixture.js';
|
|
3
|
-
export { patchScreenshot, test } from './fixture.js';
|
|
4
|
-
export type { Clip, RebaseResult } from './rebase.js';
|
|
5
|
-
export { describeAnnotation, rebaseAnnotations } from './rebase.js';
|