@connextar/house 0.17.0 → 0.19.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.
Files changed (123) hide show
  1. package/README.md +850 -0
  2. package/dist/admin/access.d.ts +134 -0
  3. package/dist/admin/access.d.ts.map +1 -0
  4. package/dist/admin/access.js +157 -0
  5. package/dist/admin/access.js.map +1 -0
  6. package/dist/admin/audit.d.ts +130 -0
  7. package/dist/admin/audit.d.ts.map +1 -0
  8. package/dist/admin/audit.js +132 -0
  9. package/dist/admin/audit.js.map +1 -0
  10. package/dist/admin/bootstrap.d.ts +80 -0
  11. package/dist/admin/bootstrap.d.ts.map +1 -0
  12. package/dist/admin/bootstrap.js +106 -0
  13. package/dist/admin/bootstrap.js.map +1 -0
  14. package/dist/admin/client-index.d.ts +7 -0
  15. package/dist/admin/client-index.d.ts.map +1 -0
  16. package/dist/admin/client-index.js +6 -0
  17. package/dist/admin/client-index.js.map +1 -0
  18. package/dist/admin/client.d.ts +72 -0
  19. package/dist/admin/client.d.ts.map +1 -0
  20. package/dist/admin/client.js +90 -0
  21. package/dist/admin/client.js.map +1 -0
  22. package/dist/admin/index.d.ts +13 -0
  23. package/dist/admin/index.d.ts.map +1 -0
  24. package/dist/admin/index.js +13 -0
  25. package/dist/admin/index.js.map +1 -0
  26. package/dist/admin/proof.d.ts +34 -0
  27. package/dist/admin/proof.d.ts.map +1 -0
  28. package/dist/admin/proof.js +30 -0
  29. package/dist/admin/proof.js.map +1 -0
  30. package/dist/admin/second-factor-index.d.ts +8 -0
  31. package/dist/admin/second-factor-index.d.ts.map +1 -0
  32. package/dist/admin/second-factor-index.js +8 -0
  33. package/dist/admin/second-factor-index.js.map +1 -0
  34. package/dist/admin/second-factor.d.ts +156 -0
  35. package/dist/admin/second-factor.d.ts.map +1 -0
  36. package/dist/admin/second-factor.js +271 -0
  37. package/dist/admin/second-factor.js.map +1 -0
  38. package/dist/admin/testing-index.d.ts +6 -0
  39. package/dist/admin/testing-index.d.ts.map +1 -0
  40. package/dist/admin/testing-index.js +6 -0
  41. package/dist/admin/testing-index.js.map +1 -0
  42. package/dist/admin/testing.d.ts +84 -0
  43. package/dist/admin/testing.d.ts.map +1 -0
  44. package/dist/admin/testing.js +223 -0
  45. package/dist/admin/testing.js.map +1 -0
  46. package/dist/admin/view-as.d.ts +209 -0
  47. package/dist/admin/view-as.d.ts.map +1 -0
  48. package/dist/admin/view-as.js +251 -0
  49. package/dist/admin/view-as.js.map +1 -0
  50. package/dist/guides/guide-article.d.ts +13 -2
  51. package/dist/guides/guide-article.d.ts.map +1 -1
  52. package/dist/guides/guide-article.js +5 -3
  53. package/dist/guides/guide-article.js.map +1 -1
  54. package/dist/guides/guide-figure-zoom.d.ts +73 -0
  55. package/dist/guides/guide-figure-zoom.d.ts.map +1 -0
  56. package/dist/guides/guide-figure-zoom.js +279 -0
  57. package/dist/guides/guide-figure-zoom.js.map +1 -0
  58. package/dist/guides/guide-figure.d.ts +21 -0
  59. package/dist/guides/guide-figure.d.ts.map +1 -0
  60. package/dist/guides/guide-figure.js +19 -0
  61. package/dist/guides/guide-figure.js.map +1 -0
  62. package/dist/guides/index.d.ts +2 -0
  63. package/dist/guides/index.d.ts.map +1 -1
  64. package/dist/guides/index.js +2 -0
  65. package/dist/guides/index.js.map +1 -1
  66. package/dist/guides/library.d.ts +19 -1
  67. package/dist/guides/library.d.ts.map +1 -1
  68. package/dist/guides/library.js +9 -0
  69. package/dist/guides/library.js.map +1 -1
  70. package/dist/health/health.d.ts +65 -0
  71. package/dist/health/health.d.ts.map +1 -0
  72. package/dist/health/health.js +93 -0
  73. package/dist/health/health.js.map +1 -0
  74. package/dist/health/index.d.ts +2 -0
  75. package/dist/health/index.d.ts.map +1 -0
  76. package/dist/health/index.js +2 -0
  77. package/dist/health/index.js.map +1 -0
  78. package/dist/markdown/figure.d.ts +44 -0
  79. package/dist/markdown/figure.d.ts.map +1 -0
  80. package/dist/markdown/figure.js +56 -0
  81. package/dist/markdown/figure.js.map +1 -0
  82. package/dist/markdown/figures.d.ts +94 -0
  83. package/dist/markdown/figures.d.ts.map +1 -0
  84. package/dist/markdown/figures.js +126 -0
  85. package/dist/markdown/figures.js.map +1 -0
  86. package/dist/markdown/guide.d.ts +26 -7
  87. package/dist/markdown/guide.d.ts.map +1 -1
  88. package/dist/markdown/guide.js +27 -6
  89. package/dist/markdown/guide.js.map +1 -1
  90. package/dist/markdown/index.d.ts +2 -0
  91. package/dist/markdown/index.d.ts.map +1 -1
  92. package/dist/markdown/index.js +2 -0
  93. package/dist/markdown/index.js.map +1 -1
  94. package/dist/release/changelog.d.ts +19 -0
  95. package/dist/release/changelog.d.ts.map +1 -1
  96. package/dist/release/changelog.js +37 -6
  97. package/dist/release/changelog.js.map +1 -1
  98. package/dist/release/index.d.ts +1 -1
  99. package/dist/release/index.d.ts.map +1 -1
  100. package/dist/release/index.js +1 -1
  101. package/dist/release/index.js.map +1 -1
  102. package/dist/whats-new/body.d.ts +81 -0
  103. package/dist/whats-new/body.d.ts.map +1 -0
  104. package/dist/whats-new/body.js +29 -0
  105. package/dist/whats-new/body.js.map +1 -0
  106. package/dist/whats-new/index.d.ts +24 -0
  107. package/dist/whats-new/index.d.ts.map +1 -0
  108. package/dist/whats-new/index.js +24 -0
  109. package/dist/whats-new/index.js.map +1 -0
  110. package/dist/whats-new/seen.d.ts +23 -0
  111. package/dist/whats-new/seen.d.ts.map +1 -0
  112. package/dist/whats-new/seen.js +33 -0
  113. package/dist/whats-new/seen.js.map +1 -0
  114. package/dist/whats-new/select.d.ts +89 -0
  115. package/dist/whats-new/select.d.ts.map +1 -0
  116. package/dist/whats-new/select.js +117 -0
  117. package/dist/whats-new/select.js.map +1 -0
  118. package/dist/whats-new/use-whats-new.d.ts +36 -0
  119. package/dist/whats-new/use-whats-new.d.ts.map +1 -0
  120. package/dist/whats-new/use-whats-new.js +81 -0
  121. package/dist/whats-new/use-whats-new.js.map +1 -0
  122. package/docs/ReleaseProcess.md +12 -0
  123. package/package.json +25 -1
@@ -0,0 +1,251 @@
1
+ import { createHmac, timingSafeEqual } from "node:crypto";
2
+ import { AdminRefusalError, adminRefusal } from "./access.js";
3
+ import { emitSafely, viewAsStarted, viewAsStopped, } from "./audit.js";
4
+ const DEFAULT_MINUTES = 60;
5
+ const MAX_COOKIE = 1024;
6
+ const b64 = (value) => Buffer.from(value).toString("base64url");
7
+ export function viewAs(options) {
8
+ const { cookieName, secret, access } = options;
9
+ const minutes = options.minutes ?? DEFAULT_MINUTES;
10
+ const now = options.now ?? Date.now;
11
+ const logger = options.logger ?? console;
12
+ const standing = options.standing ?? "admin";
13
+ if (!cookieName || !/^[\w.-]+$/.test(cookieName)) {
14
+ throw new Error(`[admin] viewAs cookieName must be a plain cookie name, got "${cookieName}".`);
15
+ }
16
+ if (typeof secret !== "string" || secret.length < 32) {
17
+ throw new Error("[admin] viewAs needs a secret of at least 32 characters to sign its cookie.");
18
+ }
19
+ if (!Number.isFinite(minutes) || minutes <= 0 || minutes > 24 * 60) {
20
+ throw new Error(`[admin] viewAs minutes must be between 0 and a day, got ${String(minutes)}.`);
21
+ }
22
+ const sign = (payload) => createHmac("sha256", secret).update(`view-as:${cookieName}:${payload}`).digest();
23
+ function encode(view) {
24
+ const payload = b64(JSON.stringify({ v: 1, t: view.targetId, a: view.adminId, e: view.expiresAt.getTime() }));
25
+ return `${payload}.${b64(sign(payload))}`;
26
+ }
27
+ /** The signed view in the cookie, whoever it belongs to and whether or not it has lapsed. */
28
+ function decode(raw) {
29
+ if (!raw || raw.length > MAX_COOKIE)
30
+ return null;
31
+ const [payload, signature, extra] = raw.split(".");
32
+ if (!payload || !signature || extra !== undefined)
33
+ return null;
34
+ const given = Buffer.from(signature, "base64url");
35
+ const expected = sign(payload);
36
+ if (given.length !== expected.length || !timingSafeEqual(given, expected))
37
+ return null;
38
+ try {
39
+ const data = JSON.parse(Buffer.from(payload, "base64url").toString("utf8"));
40
+ if (data.v !== 1 || typeof data.t !== "string" || typeof data.a !== "string" || typeof data.e !== "number") {
41
+ return null;
42
+ }
43
+ if (!data.t || !data.a || !Number.isFinite(data.e))
44
+ return null;
45
+ return { targetId: data.t, adminId: data.a, expiresAt: new Date(data.e) };
46
+ }
47
+ catch {
48
+ return null;
49
+ }
50
+ }
51
+ /** The view in the cookie if it is this administrator's and has not lapsed — the binding, before any lookup. */
52
+ async function bound(adminId) {
53
+ const jar = await options.cookies();
54
+ const view = decode(jar.get(cookieName)?.value);
55
+ if (!view || view.adminId !== adminId || view.targetId === adminId)
56
+ return null;
57
+ if (view.expiresAt.getTime() <= now())
58
+ return null;
59
+ return view;
60
+ }
61
+ async function nameOf(id, given) {
62
+ if (given)
63
+ return given;
64
+ try {
65
+ return (await options.names?.(id)) || id;
66
+ }
67
+ catch {
68
+ return id;
69
+ }
70
+ }
71
+ return {
72
+ cookieName,
73
+ minutes,
74
+ async start(admin, target, meta = {}) {
75
+ const targetRef = typeof target === "string" ? { id: target } : target;
76
+ if (!targetRef.id || targetRef.id === admin.id) {
77
+ return { ok: false, reason: "self", status: 400, message: options.words?.self ?? "You are already yourself." };
78
+ }
79
+ const decision = await access.check(admin, { viewingAs: false });
80
+ if (!decision.ok)
81
+ return decision;
82
+ if (await access.isAdmin(targetRef.id)) {
83
+ return {
84
+ ok: false,
85
+ reason: "target-is-admin",
86
+ status: 400,
87
+ message: options.words?.targetIsAdmin ?? "Viewing as another administrator is not allowed — their view is this one.",
88
+ };
89
+ }
90
+ const view = {
91
+ targetId: targetRef.id,
92
+ adminId: admin.id,
93
+ expiresAt: new Date(now() + minutes * 60_000),
94
+ };
95
+ const jar = await options.cookies();
96
+ jar.set(cookieName, encode(view), {
97
+ httpOnly: true,
98
+ sameSite: "lax",
99
+ secure: options.secure ?? process.env.NODE_ENV === "production",
100
+ path: options.path ?? "/",
101
+ maxAge: minutes * 60,
102
+ });
103
+ await emitSafely(options.audit, viewAsStarted({
104
+ actor: { id: admin.id, name: await nameOf(admin.id, admin.name), standing },
105
+ target: { id: view.targetId, name: await nameOf(view.targetId, "name" in targetRef ? targetRef.name : null) },
106
+ expiresAt: view.expiresAt,
107
+ ...(options.scope ? { scope: options.scope } : {}),
108
+ ...meta,
109
+ }), logger);
110
+ return { ok: true, view, minutes };
111
+ },
112
+ async stop(realAdmin, meta = {}) {
113
+ const view = realAdmin ? await bound(realAdmin.id) : null;
114
+ const jar = await options.cookies();
115
+ jar.delete(cookieName);
116
+ if (view && realAdmin) {
117
+ await emitSafely(options.audit, viewAsStopped({
118
+ actor: { id: realAdmin.id, name: await nameOf(realAdmin.id, realAdmin.name), standing },
119
+ target: { id: view.targetId, name: await nameOf(view.targetId) },
120
+ ...(options.scope ? { scope: options.scope } : {}),
121
+ ...meta,
122
+ }), logger);
123
+ }
124
+ return { stopped: view };
125
+ },
126
+ async read(realAdmin) {
127
+ if (!realAdmin?.id || !realAdmin.sessionId)
128
+ return null;
129
+ const view = await bound(realAdmin.id);
130
+ if (!view)
131
+ return null;
132
+ // Checked on every request rather than when the cookie was set: a demotion, a revoked session or a sign-in
133
+ // without the second factor has to end the view immediately.
134
+ if (!(await access.check(realAdmin, { viewingAs: false })).ok)
135
+ return null;
136
+ if (await access.isAdmin(view.targetId))
137
+ return null;
138
+ return view;
139
+ },
140
+ };
141
+ }
142
+ /** The sentence a refusal under "view as" says. */
143
+ export function viewingAsMessage(name, what) {
144
+ return `You’re viewing as ${name || "somebody else"}. Stop viewing as them before ${what}.`;
145
+ }
146
+ /**
147
+ * Refuse, under "view as", a request that acts for the viewed account where the write guard cannot see it: a Stripe
148
+ * call, an email, a rate-limit bucket. Call it first in the route, before anything is spent.
149
+ */
150
+ export function refuseWhileViewingAs(user, what, options = {}) {
151
+ if (!user || (!user.impersonatedBy && !user.viewingAs))
152
+ return;
153
+ const name = typeof user.viewingAs === "object" && user.viewingAs?.name ? user.viewingAs.name : user.name;
154
+ const message = viewingAsMessage(name, what);
155
+ if (options.makeError)
156
+ throw options.makeError(message);
157
+ throw new AdminRefusalError(adminRefusal("viewing-as", { "viewing-as": { message } }));
158
+ }
159
+ /**
160
+ * The fail-closed default for what reaches outside the database: call `assertNotViewingAs` once inside the app's own
161
+ * `sendEmail`, `sendSms`, Stripe, GoCardless, Xero and AI wrappers, and every route that uses them is covered —
162
+ * including the next one written. Quickie had to add `refuseWhileViewingAs` to thirty handlers by hand.
163
+ *
164
+ * It reads the app's request-scoped actor rather than keeping a store of its own. An `AsyncLocalStorage` set where the
165
+ * view is resolved — inside `getCurrentUser`, an awaited callee — does not reach the route's later calls: a store
166
+ * entered in a callee after its first await (the session read) never propagates back to the caller's continuation
167
+ * (`view-as.test.ts` pins it), and wrapping
168
+ * every handler in `withViewingAs(…, fn)` is the per-route step this exists to remove. Quickie's and AltEd's actor
169
+ * is keyed on the request itself (Next's `headers()` object), which every call in the request shares.
170
+ */
171
+ export function viewingAsGate(options) {
172
+ async function lookup() {
173
+ return options.getViewing();
174
+ }
175
+ return {
176
+ async isViewingAs() {
177
+ return !!(await lookup());
178
+ },
179
+ async assertNotViewingAs(what) {
180
+ let viewing;
181
+ try {
182
+ viewing = await lookup();
183
+ }
184
+ catch (error) {
185
+ const message = `Could not tell whether this request is a “view as”, so ${what} was refused.`;
186
+ if (options.makeError)
187
+ throw options.makeError(message);
188
+ throw new AdminRefusalError(adminRefusal("viewing-as", { "viewing-as": { message } }), { cause: error });
189
+ }
190
+ if (!viewing)
191
+ return;
192
+ const message = viewingAsMessage(typeof viewing === "object" ? viewing.name : null, what);
193
+ if (options.makeError)
194
+ throw options.makeError(message);
195
+ throw new AdminRefusalError(adminRefusal("viewing-as", { "viewing-as": { message } }));
196
+ },
197
+ };
198
+ }
199
+ /** Every Prisma write operation. A read is never refused — that is the whole point of a view. */
200
+ export const PRISMA_WRITE_OPERATIONS = new Set([
201
+ "create",
202
+ "createMany",
203
+ "createManyAndReturn",
204
+ "update",
205
+ "updateMany",
206
+ "updateManyAndReturn",
207
+ "upsert",
208
+ "delete",
209
+ "deleteMany",
210
+ ]);
211
+ /** `allow` from a map of model → operations: `allowWhileViewing({ Session: ["update", "updateMany"] })`. */
212
+ export function allowWhileViewing(list) {
213
+ const sets = new Map(Object.entries(list).map(([model, operations]) => [model, new Set(operations)]));
214
+ return (model, operation) => !!model && !!sets.get(model)?.has(operation);
215
+ }
216
+ /**
217
+ * Make "view as" read-only at the client: `prisma.$extends(viewAsWriteGuard({ … }))`. Every write that arrives while
218
+ * the request is viewing as somebody is refused, whichever handler made it, unless it is on the allow-list.
219
+ *
220
+ * The rule is inverted from "block what is audited", which AltEd learnt by running it: deleting an account ran
221
+ * `session.deleteMany` and `passkey.deleteMany` before it touched the user row, so the deletion was refused —
222
+ * correctly — after signing the family out of every device. So everything is refused except what is allowed.
223
+ *
224
+ * Typed structurally, so this package imports no Prisma. If an app's Prisma types object to it, wrap it:
225
+ * `Prisma.defineExtension(viewAsWriteGuard({ … }))`.
226
+ */
227
+ export function viewAsWriteGuard(options) {
228
+ const isWrite = options.isWrite ?? ((operation) => PRISMA_WRITE_OPERATIONS.has(operation));
229
+ return {
230
+ name: options.name ?? "view-as-write-guard",
231
+ query: {
232
+ $allModels: {
233
+ async $allOperations({ model, operation, args, query }) {
234
+ if (isWrite(operation) && !options.allow(model, operation)) {
235
+ // Fail-closed: a `getViewing` that throws fails the write, rather than letting it through unchecked.
236
+ const viewing = await options.getViewing();
237
+ if (viewing) {
238
+ const name = typeof viewing === "object" ? viewing.name : null;
239
+ const message = viewingAsMessage(name, "changing anything");
240
+ if (options.makeError)
241
+ throw options.makeError(message);
242
+ throw new AdminRefusalError(adminRefusal("viewing-as", { "viewing-as": { message } }));
243
+ }
244
+ }
245
+ return query(args);
246
+ },
247
+ },
248
+ },
249
+ };
250
+ }
251
+ //# sourceMappingURL=view-as.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"view-as.js","sourceRoot":"","sources":["../../src/admin/view-as.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,6 @@
1
+ import type { GuideFigures } from "../markdown/figures.js";
1
2
  import { type GuideClassNames } from "../markdown/guide.js";
3
+ import type { GuideZoomClassNames } from "./guide-figure-zoom.js";
2
4
  import { type GuidesLabels } from "./library.js";
3
5
  export interface GuideArticleProps {
4
6
  /** The guide as `parseGuideFrontmatter` splits it. */
@@ -15,13 +17,22 @@ export interface GuideArticleProps {
15
17
  category?: string;
16
18
  labels?: Partial<GuidesLabels>;
17
19
  classNames?: Partial<GuideClassNames>;
20
+ /**
21
+ * The pictures this app's guides may show (`![alt](/guide-images/x.webp "Caption")` and
22
+ * `![alt](diagram:name)` on a line of their own). Unregistered ones are left out.
23
+ */
24
+ figures?: GuideFigures;
25
+ /** Open a picture full-size on a click, Enter or Space. Default: on. */
26
+ zoom?: boolean;
27
+ zoomClassNames?: Partial<GuideZoomClassNames>;
18
28
  }
19
29
  /**
20
30
  * One guide, laid out for reading inside `GuidesShell`: a way back to all guides, the category,
21
31
  * the title and lede, the article drawn by `GuideMarkdown`, and "On this page" — a rail that
22
32
  * follows the reader on wide screens, a collapsible list above the article on narrow ones. The
23
33
  * contents use the same heading ids the renderer gives, so `#<heading id>` links land.
24
- * Server-safe; the contents are the only client part.
34
+ * Registered pictures are drawn where the guide names them, each opening full-size.
35
+ * Server-safe; the contents and the zoom are the only client parts.
25
36
  */
26
- export declare function GuideArticle({ guide, guidesPath, indexSlug, category, labels, classNames }: GuideArticleProps): import("react").JSX.Element;
37
+ export declare function GuideArticle({ guide, guidesPath, indexSlug, category, labels, classNames, figures, zoom, zoomClassNames, }: GuideArticleProps): import("react").JSX.Element;
27
38
  //# sourceMappingURL=guide-article.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"guide-article.d.ts","sourceRoot":"","sources":["../../src/guides/guide-article.tsx"],"names":[],"mappings":"AAGA,OAAO,EAAkC,KAAK,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAE5F,OAAO,EAAiB,KAAK,YAAY,EAAE,MAAM,cAAc,CAAC;AAEhE,MAAM,WAAW,iBAAiB;IAChC,sDAAsD;IACtD,KAAK,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IACtD,iFAAiF;IACjF,UAAU,EAAE,MAAM,CAAC;IACnB,4FAA4F;IAC5F,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,2CAA2C;IAC3C,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,OAAO,CAAC,YAAY,CAAC,CAAC;IAC/B,UAAU,CAAC,EAAE,OAAO,CAAC,eAAe,CAAC,CAAC;CACvC;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,EAAE,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,EAAE,iBAAiB,+BA0B7G"}
1
+ {"version":3,"file":"guide-article.d.ts","sourceRoot":"","sources":["../../src/guides/guide-article.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAC3D,OAAO,EAAkC,KAAK,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAE5F,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAElE,OAAO,EAAiB,KAAK,YAAY,EAAE,MAAM,cAAc,CAAC;AAEhE,MAAM,WAAW,iBAAiB;IAChC,sDAAsD;IACtD,KAAK,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IACtD,iFAAiF;IACjF,UAAU,EAAE,MAAM,CAAC;IACnB,4FAA4F;IAC5F,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,2CAA2C;IAC3C,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,OAAO,CAAC,YAAY,CAAC,CAAC;IAC/B,UAAU,CAAC,EAAE,OAAO,CAAC,eAAe,CAAC,CAAC;IACtC;;;OAGG;IACH,OAAO,CAAC,EAAE,YAAY,CAAC;IACvB,wEAAwE;IACxE,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,cAAc,CAAC,EAAE,OAAO,CAAC,mBAAmB,CAAC,CAAC;CAC/C;AAED;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,EAC3B,KAAK,EACL,UAAU,EACV,SAAS,EACT,QAAQ,EACR,MAAM,EACN,UAAU,EACV,OAAO,EACP,IAAI,EACJ,cAAc,GACf,EAAE,iBAAiB,+BA0CnB"}
@@ -2,6 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { ChevronLeft } from "lucide-react";
3
3
  import { Link } from "../internal/next-link.js";
4
4
  import { extractHeadings, GuideMarkdown } from "../markdown/guide.js";
5
+ import { GuideFigure } from "./guide-figure.js";
5
6
  import { GuideToc } from "./guide-toc.js";
6
7
  import { GUIDES_LABELS } from "./library.js";
7
8
  /**
@@ -9,11 +10,12 @@ import { GUIDES_LABELS } from "./library.js";
9
10
  * the title and lede, the article drawn by `GuideMarkdown`, and "On this page" — a rail that
10
11
  * follows the reader on wide screens, a collapsible list above the article on narrow ones. The
11
12
  * contents use the same heading ids the renderer gives, so `#<heading id>` links land.
12
- * Server-safe; the contents are the only client part.
13
+ * Registered pictures are drawn where the guide names them, each opening full-size.
14
+ * Server-safe; the contents and the zoom are the only client parts.
13
15
  */
14
- export function GuideArticle({ guide, guidesPath, indexSlug, category, labels, classNames }) {
16
+ export function GuideArticle({ guide, guidesPath, indexSlug, category, labels, classNames, figures, zoom, zoomClassNames, }) {
15
17
  const text = { ...GUIDES_LABELS, ...labels };
16
18
  const headings = extractHeadings(guide.body);
17
- return (_jsxs("div", { className: "xl:grid xl:grid-cols-[minmax(0,1fr)_13rem] xl:gap-12", children: [_jsxs("article", { className: "max-w-3xl min-w-0", children: [_jsxs(Link, { href: guidesPath, className: "inline-flex items-center gap-1 text-sm text-muted-foreground hover:text-foreground", children: [_jsx(ChevronLeft, { className: "size-4", "aria-hidden": true }), " ", text.allGuides] }), category ? (_jsx("p", { className: "mt-5 text-xs font-bold tracking-[0.14em] text-primary uppercase", children: category })) : null, _jsx("h1", { className: "mt-2 text-3xl font-bold tracking-tight text-foreground sm:text-4xl", children: guide.title }), guide.lede ? _jsx("p", { className: "mt-3 text-lg text-muted-foreground", children: guide.lede }) : null, _jsx("div", { className: "mt-8", children: _jsx(GuideToc, { headings: headings, variant: "inline", labels: labels }) }), _jsx(GuideMarkdown, { content: guide.body, guidesPath: guidesPath, indexSlug: indexSlug, classNames: classNames })] }), _jsx(GuideToc, { headings: headings, variant: "rail", labels: labels })] }));
19
+ return (_jsxs("div", { className: "xl:grid xl:grid-cols-[minmax(0,1fr)_13rem] xl:gap-12", children: [_jsxs("article", { className: "max-w-3xl min-w-0", children: [_jsxs(Link, { href: guidesPath, className: "inline-flex items-center gap-1 text-sm text-muted-foreground hover:text-foreground", children: [_jsx(ChevronLeft, { className: "size-4", "aria-hidden": true }), " ", text.allGuides] }), category ? (_jsx("p", { className: "mt-5 text-xs font-bold tracking-[0.14em] text-primary uppercase", children: category })) : null, _jsx("h1", { className: "mt-2 text-3xl font-bold tracking-tight text-foreground sm:text-4xl", children: guide.title }), guide.lede ? _jsx("p", { className: "mt-3 text-lg text-muted-foreground", children: guide.lede }) : null, _jsx("div", { className: "mt-8", children: _jsx(GuideToc, { headings: headings, variant: "inline", labels: labels }) }), _jsx(GuideMarkdown, { content: guide.body, guidesPath: guidesPath, indexSlug: indexSlug, classNames: classNames, figures: figures, renderFigure: (figure) => (_jsx(GuideFigure, { figure: figure, figures: figures, zoom: zoom, labels: labels, classNames: classNames?.figure, zoomClassNames: zoomClassNames })) })] }), _jsx(GuideToc, { headings: headings, variant: "rail", labels: labels })] }));
18
20
  }
19
21
  //# sourceMappingURL=guide-article.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"guide-article.js","sourceRoot":"","sources":["../../src/guides/guide-article.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAE3C,OAAO,EAAE,IAAI,EAAE,MAAM,0BAA0B,CAAC;AAChD,OAAO,EAAE,eAAe,EAAE,aAAa,EAAwB,MAAM,sBAAsB,CAAC;AAC5F,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAqB,MAAM,cAAc,CAAC;AAehE;;;;;;GAMG;AACH,MAAM,UAAU,YAAY,CAAC,EAAE,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAqB;IAC5G,MAAM,IAAI,GAAG,EAAE,GAAG,aAAa,EAAE,GAAG,MAAM,EAAE,CAAC;IAC7C,MAAM,QAAQ,GAAG,eAAe,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAE7C,OAAO,CACL,eAAK,SAAS,EAAC,sDAAsD,aACnE,mBAAS,SAAS,EAAC,mBAAmB,aACpC,MAAC,IAAI,IACH,IAAI,EAAE,UAAU,EAChB,SAAS,EAAC,oFAAoF,aAE9F,KAAC,WAAW,IAAC,SAAS,EAAC,QAAQ,wBAAe,OAAE,IAAI,CAAC,SAAS,IACzD,EACN,QAAQ,CAAC,CAAC,CAAC,CACV,YAAG,SAAS,EAAC,iEAAiE,YAAE,QAAQ,GAAK,CAC9F,CAAC,CAAC,CAAC,IAAI,EACR,aAAI,SAAS,EAAC,oEAAoE,YAAE,KAAK,CAAC,KAAK,GAAM,EACpG,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,YAAG,SAAS,EAAC,oCAAoC,YAAE,KAAK,CAAC,IAAI,GAAK,CAAC,CAAC,CAAC,IAAI,EACvF,cAAK,SAAS,EAAC,MAAM,YACnB,KAAC,QAAQ,IAAC,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAC,QAAQ,EAAC,MAAM,EAAE,MAAM,GAAI,GAC7D,EACN,KAAC,aAAa,IAAC,OAAO,EAAE,KAAK,CAAC,IAAI,EAAE,UAAU,EAAE,UAAU,EAAE,SAAS,EAAE,SAAS,EAAE,UAAU,EAAE,UAAU,GAAI,IACpG,EACV,KAAC,QAAQ,IAAC,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAC,MAAM,EAAC,MAAM,EAAE,MAAM,GAAI,IAC3D,CACP,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"guide-article.js","sourceRoot":"","sources":["../../src/guides/guide-article.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAE3C,OAAO,EAAE,IAAI,EAAE,MAAM,0BAA0B,CAAC;AAEhD,OAAO,EAAE,eAAe,EAAE,aAAa,EAAwB,MAAM,sBAAsB,CAAC;AAC5F,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAEhD,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAqB,MAAM,cAAc,CAAC;AAuBhE;;;;;;;GAOG;AACH,MAAM,UAAU,YAAY,CAAC,EAC3B,KAAK,EACL,UAAU,EACV,SAAS,EACT,QAAQ,EACR,MAAM,EACN,UAAU,EACV,OAAO,EACP,IAAI,EACJ,cAAc,GACI;IAClB,MAAM,IAAI,GAAG,EAAE,GAAG,aAAa,EAAE,GAAG,MAAM,EAAE,CAAC;IAC7C,MAAM,QAAQ,GAAG,eAAe,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAE7C,OAAO,CACL,eAAK,SAAS,EAAC,sDAAsD,aACnE,mBAAS,SAAS,EAAC,mBAAmB,aACpC,MAAC,IAAI,IACH,IAAI,EAAE,UAAU,EAChB,SAAS,EAAC,oFAAoF,aAE9F,KAAC,WAAW,IAAC,SAAS,EAAC,QAAQ,wBAAe,OAAE,IAAI,CAAC,SAAS,IACzD,EACN,QAAQ,CAAC,CAAC,CAAC,CACV,YAAG,SAAS,EAAC,iEAAiE,YAAE,QAAQ,GAAK,CAC9F,CAAC,CAAC,CAAC,IAAI,EACR,aAAI,SAAS,EAAC,oEAAoE,YAAE,KAAK,CAAC,KAAK,GAAM,EACpG,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,YAAG,SAAS,EAAC,oCAAoC,YAAE,KAAK,CAAC,IAAI,GAAK,CAAC,CAAC,CAAC,IAAI,EACvF,cAAK,SAAS,EAAC,MAAM,YACnB,KAAC,QAAQ,IAAC,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAC,QAAQ,EAAC,MAAM,EAAE,MAAM,GAAI,GAC7D,EACN,KAAC,aAAa,IACZ,OAAO,EAAE,KAAK,CAAC,IAAI,EACnB,UAAU,EAAE,UAAU,EACtB,SAAS,EAAE,SAAS,EACpB,UAAU,EAAE,UAAU,EACtB,OAAO,EAAE,OAAO,EAChB,YAAY,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,CACxB,KAAC,WAAW,IACV,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,OAAO,EAChB,IAAI,EAAE,IAAI,EACV,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,UAAU,EAAE,MAAM,EAC9B,cAAc,EAAE,cAAc,GAC9B,CACH,GACD,IACM,EACV,KAAC,QAAQ,IAAC,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAC,MAAM,EAAC,MAAM,EAAE,MAAM,GAAI,IAC3D,CACP,CAAC;AACJ,CAAC"}
@@ -0,0 +1,73 @@
1
+ import { type ReactNode } from "react";
2
+ import { type GuidesLabels } from "./library.js";
3
+ /**
4
+ * Every element the zoom draws. Each value replaces the default for that element. A part added after
5
+ * 0.18.0 is optional, so an app that writes out a whole `GuideZoomClassNames` keeps typechecking.
6
+ */
7
+ export interface GuideZoomClassNames {
8
+ /** The button around the picture in the article. */
9
+ trigger: string;
10
+ /**
11
+ * The row under the picture with "Click to enlarge" (or "Tap to enlarge"): under it rather than over
12
+ * its corner, so it never covers part of a small diagram on a phone.
13
+ */
14
+ hint: string;
15
+ /** The words shown where the pointer is a mouse or a trackpad. Optional: a later addition. */
16
+ hintPointer?: string;
17
+ /** The words shown on a touch screen (`@media (pointer: coarse)`). Optional: a later addition. */
18
+ hintTouch?: string;
19
+ overlay: string;
20
+ /** The dialog: the whole screen on a phone, inset on wider ones. */
21
+ content: string;
22
+ toolbar: string;
23
+ instructions: string;
24
+ button: string;
25
+ /** The zoom level, a button that switches between fit and full size. */
26
+ level: string;
27
+ /** The area the picture is zoomed and dragged in. */
28
+ stage: string;
29
+ /** Under the picture: the caption and the alt text. */
30
+ footer: string;
31
+ caption: string;
32
+ alt: string;
33
+ }
34
+ export declare const GUIDE_ZOOM_CLASS_NAMES: Required<GuideZoomClassNames>;
35
+ export interface GuideFigureZoomProps {
36
+ /** The picture as the article draws it: the button's content. */
37
+ children: ReactNode;
38
+ /** What the dialog shows, laid out at the natural size and scaled. Default: `children` again. */
39
+ full?: ReactNode;
40
+ /**
41
+ * The picture's natural size in CSS pixels — a screenshot's registered size, so "full size"
42
+ * is one image pixel to one CSS pixel. Left out (a diagram), it is measured from the article's
43
+ * picture when the dialog opens, and full size is twice the fit.
44
+ */
45
+ width?: number;
46
+ height?: number;
47
+ alt: string;
48
+ caption?: string;
49
+ labels?: Partial<GuidesLabels>;
50
+ classNames?: Partial<GuideZoomClassNames>;
51
+ }
52
+ interface Size {
53
+ w: number;
54
+ h: number;
55
+ }
56
+ /** Each button press or key multiplies or divides the zoom by this. */
57
+ export declare const ZOOM_STEP = 1.25;
58
+ /** The scale that fits `natural` inside `stage`, never past full size for a picture with a natural size. */
59
+ export declare function fitScale(natural: Size, stage: Size, capAtFull: boolean): number;
60
+ /**
61
+ * A guide's picture, opened full-size: a button around the article's picture (with a "Click to
62
+ * enlarge" hint under it, "Tap to enlarge" on a touch screen) that opens a dialog fitting it to
63
+ * the screen. In there it zooms with the buttons, `+` and `−`, ⌘/Ctrl + scroll (a trackpad
64
+ * pinch) and a touch pinch; drags to pan when it is bigger than the screen (and scrolls, and
65
+ * moves with the arrow keys); a double-click or double-tap switches between fit and full size,
66
+ * and `0` fits again. Escape closes it and focus goes back to the picture. The caption and alt
67
+ * text are shown under it.
68
+ *
69
+ * Zooming eases unless the reader prefers reduced motion; dragging and pinching never do.
70
+ */
71
+ export declare function GuideFigureZoom({ children, full, width, height, alt, caption, labels, classNames, }: GuideFigureZoomProps): import("react").JSX.Element;
72
+ export {};
73
+ //# sourceMappingURL=guide-figure-zoom.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"guide-figure-zoom.d.ts","sourceRoot":"","sources":["../../src/guides/guide-figure-zoom.tsx"],"names":[],"mappings":"AAIA,OAAO,EAOL,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AAGf,OAAO,EAAiB,KAAK,YAAY,EAAE,MAAM,cAAc,CAAC;AAEhE;;;GAGG;AACH,MAAM,WAAW,mBAAmB;IAClC,oDAAoD;IACpD,OAAO,EAAE,MAAM,CAAC;IAChB;;;OAGG;IACH,IAAI,EAAE,MAAM,CAAC;IACb,8FAA8F;IAC9F,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,kGAAkG;IAClG,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,oEAAoE;IACpE,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,wEAAwE;IACxE,KAAK,EAAE,MAAM,CAAC;IACd,qDAAqD;IACrD,KAAK,EAAE,MAAM,CAAC;IACd,uDAAuD;IACvD,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;CACb;AAED,eAAO,MAAM,sBAAsB,EAAE,QAAQ,CAAC,mBAAmB,CAqBhE,CAAC;AAEF,MAAM,WAAW,oBAAoB;IACnC,iEAAiE;IACjE,QAAQ,EAAE,SAAS,CAAC;IACpB,iGAAiG;IACjG,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB;;;;OAIG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,OAAO,CAAC,YAAY,CAAC,CAAC;IAC/B,UAAU,CAAC,EAAE,OAAO,CAAC,mBAAmB,CAAC,CAAC;CAC3C;AAED,UAAU,IAAI;IACZ,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;CACX;AAMD,uEAAuE;AACvE,eAAO,MAAM,SAAS,OAAO,CAAC;AAS9B,4GAA4G;AAC5G,wBAAgB,QAAQ,CAAC,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,GAAG,MAAM,CAM/E;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,CAAC,EAC9B,QAAQ,EACR,IAAI,EACJ,KAAK,EACL,MAAM,EACN,GAAG,EACH,OAAO,EACP,MAAM,EACN,UAAU,GACX,EAAE,oBAAoB,+BA0VtB"}