@dogsbay/plugin-image-zoom 0.2.0-beta.81
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +55 -0
- package/dist/client.d.ts +2 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +49 -0
- package/dist/client.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +63 -0
- package/dist/index.js.map +1 -0
- package/dist/schema.d.ts +49 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +70 -0
- package/dist/schema.js.map +1 -0
- package/dist/walker.d.ts +18 -0
- package/dist/walker.d.ts.map +1 -0
- package/dist/walker.js +71 -0
- package/dist/walker.js.map +1 -0
- package/package.json +48 -0
- package/src/client.ts +58 -0
- package/src/styles.css +24 -0
package/README.md
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# @dogsbay/plugin-image-zoom
|
|
2
|
+
|
|
3
|
+
Click-to-zoom for docs images. Drop-in for any Dogsbay site.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pnpm add @dogsbay/plugin-image-zoom
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Use
|
|
12
|
+
|
|
13
|
+
```yaml
|
|
14
|
+
# dogsbay.config.yml
|
|
15
|
+
plugins:
|
|
16
|
+
- "@dogsbay/plugin-image-zoom"
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
With options:
|
|
20
|
+
|
|
21
|
+
```yaml
|
|
22
|
+
plugins:
|
|
23
|
+
- name: "@dogsbay/plugin-image-zoom"
|
|
24
|
+
options:
|
|
25
|
+
background: "rgba(0,0,0,0.92)"
|
|
26
|
+
margin: 32
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Options
|
|
30
|
+
|
|
31
|
+
| Option | Default | Description |
|
|
32
|
+
|---|---|---|
|
|
33
|
+
| `selector` | `"article img"` | CSS selector for which images get the zoom UX. |
|
|
34
|
+
| `background` | `"rgba(0,0,0,0.85)"` | Overlay backdrop. |
|
|
35
|
+
| `margin` | `20` | Pixels of margin around the zoomed image. |
|
|
36
|
+
| `scrollOffset` | `40` | Scroll-to-close threshold. |
|
|
37
|
+
| `excludeInsideLinks` | `true` | Skip images inside `<a>` (the link is the click target). |
|
|
38
|
+
|
|
39
|
+
## How it works
|
|
40
|
+
|
|
41
|
+
1. **Build time** — the plugin walks every page's TreeNode AST and
|
|
42
|
+
adds `data-zoomable="true"` to images that aren't inside `<a>`
|
|
43
|
+
links.
|
|
44
|
+
2. **Run time** — a small client module attaches
|
|
45
|
+
[medium-zoom](https://github.com/francoischalifour/medium-zoom)
|
|
46
|
+
handlers to every `[data-zoomable]` element. The handlers are
|
|
47
|
+
re-bound on every `astro:page-load` so view transitions don't
|
|
48
|
+
leave stale state.
|
|
49
|
+
|
|
50
|
+
The runtime only attaches handlers to pre-tagged elements — no DOM
|
|
51
|
+
scan, no observer, nothing past the click handler itself.
|
|
52
|
+
|
|
53
|
+
## License
|
|
54
|
+
|
|
55
|
+
MIT.
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":""}
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client runtime for `@dogsbay/plugin-image-zoom`.
|
|
3
|
+
*
|
|
4
|
+
* Imported via the auto-generated `src/lib/plugin-runtime.ts`
|
|
5
|
+
* entry point in the user's Astro project. The build-time walker
|
|
6
|
+
* has already tagged article images with `data-zoomable="true"`,
|
|
7
|
+
* so this module only needs to (re)bind medium-zoom on every
|
|
8
|
+
* navigation event.
|
|
9
|
+
*
|
|
10
|
+
* Astro view transitions: every `astro:page-load` event causes a
|
|
11
|
+
* detach + reattach. `astro:before-swap` detaches before the new
|
|
12
|
+
* DOM lands so we don't carry stale references.
|
|
13
|
+
*/
|
|
14
|
+
import mediumZoom from "medium-zoom";
|
|
15
|
+
// Vite alias — emitted by `dogsbay site build` via
|
|
16
|
+
// `astro.config.plugins.mjs`. Resolves at build time to the
|
|
17
|
+
// JSON-serialized options the plugin baked in.
|
|
18
|
+
// @ts-expect-error: virtual module resolved by Vite alias.
|
|
19
|
+
import config from "virtual:dogsbay-plugin-config/@dogsbay/plugin-image-zoom";
|
|
20
|
+
const runtimeConfig = config;
|
|
21
|
+
let zoom = null;
|
|
22
|
+
function attach() {
|
|
23
|
+
if (zoom) {
|
|
24
|
+
zoom.detach();
|
|
25
|
+
zoom = null;
|
|
26
|
+
}
|
|
27
|
+
zoom = mediumZoom("[data-zoomable]", {
|
|
28
|
+
background: runtimeConfig.background,
|
|
29
|
+
margin: runtimeConfig.margin,
|
|
30
|
+
scrollOffset: runtimeConfig.scrollOffset,
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
function detach() {
|
|
34
|
+
if (zoom) {
|
|
35
|
+
zoom.detach();
|
|
36
|
+
zoom = null;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
if (typeof document !== "undefined") {
|
|
40
|
+
if (document.readyState === "loading") {
|
|
41
|
+
document.addEventListener("DOMContentLoaded", attach);
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
attach();
|
|
45
|
+
}
|
|
46
|
+
document.addEventListener("astro:page-load", attach);
|
|
47
|
+
document.addEventListener("astro:before-swap", detach);
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,UAAU,MAAM,aAAa,CAAC;AACrC,mDAAmD;AACnD,4DAA4D;AAC5D,+CAA+C;AAC/C,2DAA2D;AAC3D,OAAO,MAAM,MAAM,0DAA0D,CAAC;AAQ9E,MAAM,aAAa,GAAkB,MAAuB,CAAC;AAE7D,IAAI,IAAI,GAAyC,IAAI,CAAC;AAEtD,SAAS,MAAM;IACb,IAAI,IAAI,EAAE,CAAC;QACT,IAAI,CAAC,MAAM,EAAE,CAAC;QACd,IAAI,GAAG,IAAI,CAAC;IACd,CAAC;IACD,IAAI,GAAG,UAAU,CAAC,iBAAiB,EAAE;QACnC,UAAU,EAAE,aAAa,CAAC,UAAU;QACpC,MAAM,EAAE,aAAa,CAAC,MAAM;QAC5B,YAAY,EAAE,aAAa,CAAC,YAAY;KACzC,CAAC,CAAC;AACL,CAAC;AAED,SAAS,MAAM;IACb,IAAI,IAAI,EAAE,CAAC;QACT,IAAI,CAAC,MAAM,EAAE,CAAC;QACd,IAAI,GAAG,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,IAAI,OAAO,QAAQ,KAAK,WAAW,EAAE,CAAC;IACpC,IAAI,QAAQ,CAAC,UAAU,KAAK,SAAS,EAAE,CAAC;QACtC,QAAQ,CAAC,gBAAgB,CAAC,kBAAkB,EAAE,MAAM,CAAC,CAAC;IACxD,CAAC;SAAM,CAAC;QACN,MAAM,EAAE,CAAC;IACX,CAAC;IACD,QAAQ,CAAC,gBAAgB,CAAC,iBAAiB,EAAE,MAAM,CAAC,CAAC;IACrD,QAAQ,CAAC,gBAAgB,CAAC,mBAAmB,EAAE,MAAM,CAAC,CAAC;AACzD,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { DogsbayPlugin } from "@dogsbay/types";
|
|
2
|
+
import { type ImageZoomOptions } from "./schema.js";
|
|
3
|
+
export type { ImageZoomOptions } from "./schema.js";
|
|
4
|
+
export default function imageZoom(input?: ImageZoomOptions): DogsbayPlugin;
|
|
5
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAuBA,OAAO,KAAK,EAAE,aAAa,EAAuB,MAAM,gBAAgB,CAAC;AACzE,OAAO,EAAgB,KAAK,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAclE,YAAY,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAEpD,MAAM,CAAC,OAAO,UAAU,SAAS,CAAC,KAAK,CAAC,EAAE,gBAAgB,GAAG,aAAa,CA+BzE"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@dogsbay/plugin-image-zoom` — adds click-to-enlarge to docs
|
|
3
|
+
* images. Build-time TreeNode walk tags `<img>` nodes; runtime
|
|
4
|
+
* client module attaches medium-zoom handlers to the tagged
|
|
5
|
+
* elements. View-transition aware (rebinds on `astro:page-load`).
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
*
|
|
9
|
+
* # dogsbay.config.yml
|
|
10
|
+
* plugins:
|
|
11
|
+
* - "@dogsbay/plugin-image-zoom"
|
|
12
|
+
*
|
|
13
|
+
* # or with options
|
|
14
|
+
* plugins:
|
|
15
|
+
* - name: "@dogsbay/plugin-image-zoom"
|
|
16
|
+
* options:
|
|
17
|
+
* background: "rgba(0,0,0,0.92)"
|
|
18
|
+
* margin: 32
|
|
19
|
+
*
|
|
20
|
+
* See plans/plugin-api.md for the API contract.
|
|
21
|
+
*/
|
|
22
|
+
import path from "node:path";
|
|
23
|
+
import { fileURLToPath } from "node:url";
|
|
24
|
+
import { parseOptions } from "./schema.js";
|
|
25
|
+
import { tagImagesInTree } from "./walker.js";
|
|
26
|
+
// __dirname equivalent for an ESM-published package. Resolves the
|
|
27
|
+
// directory the built `dist/index.js` lives in so we can hand
|
|
28
|
+
// absolute paths to the Dogsbay plugin runtime.
|
|
29
|
+
const PLUGIN_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
30
|
+
// __dirname is `dist/`; the runtime emits absolute paths to the
|
|
31
|
+
// built `client.js` and the (un-built) `styles.css` shipped at the
|
|
32
|
+
// package root under `src/styles.css`.
|
|
33
|
+
const PACKAGE_ROOT = path.resolve(PLUGIN_DIR, "..");
|
|
34
|
+
const CLIENT_PATH = path.join(PLUGIN_DIR, "client.js");
|
|
35
|
+
const STYLES_PATH = path.join(PACKAGE_ROOT, "src", "styles.css");
|
|
36
|
+
export default function imageZoom(input) {
|
|
37
|
+
const opts = parseOptions(input);
|
|
38
|
+
return {
|
|
39
|
+
name: "@dogsbay/plugin-image-zoom",
|
|
40
|
+
onContentImported(pages, nav) {
|
|
41
|
+
const tagged = pages.map((page) => ({
|
|
42
|
+
...page,
|
|
43
|
+
tree: tagImagesInTree(page.tree, opts),
|
|
44
|
+
}));
|
|
45
|
+
return { pages: tagged, nav };
|
|
46
|
+
},
|
|
47
|
+
clientModules() {
|
|
48
|
+
return [CLIENT_PATH];
|
|
49
|
+
},
|
|
50
|
+
styles() {
|
|
51
|
+
return [STYLES_PATH];
|
|
52
|
+
},
|
|
53
|
+
defineClientConfig() {
|
|
54
|
+
// Only the runtime-relevant subset. JSON-safe.
|
|
55
|
+
return {
|
|
56
|
+
background: opts.background,
|
|
57
|
+
margin: opts.margin,
|
|
58
|
+
scrollOffset: opts.scrollOffset,
|
|
59
|
+
};
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,OAAO,EAAE,YAAY,EAAyB,MAAM,aAAa,CAAC;AAClE,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAE9C,kEAAkE;AAClE,8DAA8D;AAC9D,gDAAgD;AAChD,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AAChE,gEAAgE;AAChE,mEAAmE;AACnE,uCAAuC;AACvC,MAAM,YAAY,GAAG,IAAI,CAAC,OAAO,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC;AACpD,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,WAAW,CAAC,CAAC;AACvD,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,KAAK,EAAE,YAAY,CAAC,CAAC;AAIjE,MAAM,CAAC,OAAO,UAAU,SAAS,CAAC,KAAwB;IACxD,MAAM,IAAI,GAAG,YAAY,CAAC,KAAK,CAAC,CAAC;IAEjC,OAAO;QACL,IAAI,EAAE,4BAA4B;QAElC,iBAAiB,CAAC,KAAmB,EAAE,GAAc;YACnD,MAAM,MAAM,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;gBAClC,GAAG,IAAI;gBACP,IAAI,EAAE,eAAe,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC;aACvC,CAAC,CAAC,CAAC;YACJ,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC;QAChC,CAAC;QAED,aAAa;YACX,OAAO,CAAC,WAAW,CAAC,CAAC;QACvB,CAAC;QAED,MAAM;YACJ,OAAO,CAAC,WAAW,CAAC,CAAC;QACvB,CAAC;QAED,kBAAkB;YAChB,+CAA+C;YAC/C,OAAO;gBACL,UAAU,EAAE,IAAI,CAAC,UAAU;gBAC3B,MAAM,EAAE,IAAI,CAAC,MAAM;gBACnB,YAAY,EAAE,IAAI,CAAC,YAAY;aAChC,CAAC;QACJ,CAAC;KACF,CAAC;AACJ,CAAC"}
|
package/dist/schema.d.ts
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Options schema for `@dogsbay/plugin-image-zoom`.
|
|
3
|
+
*
|
|
4
|
+
* Validation is hand-rolled to avoid a Zod dep — the option set is
|
|
5
|
+
* small enough that the boilerplate is in noise. The `parseOptions`
|
|
6
|
+
* function returns a fully-resolved options object with every field
|
|
7
|
+
* set to either the user value or the documented default.
|
|
8
|
+
*/
|
|
9
|
+
export interface ImageZoomOptions {
|
|
10
|
+
/**
|
|
11
|
+
* CSS selector matched against TreeNode image nodes during the
|
|
12
|
+
* build-time walk. The walker tags matching `<img>` elements
|
|
13
|
+
* with `data-zoomable="true"` so the runtime doesn't need to
|
|
14
|
+
* scan the DOM. Default: every `<img>` inside `<article>`.
|
|
15
|
+
*
|
|
16
|
+
* NOTE: Currently informational — the build-time walker tags
|
|
17
|
+
* every image node that isn't inside a link. The selector
|
|
18
|
+
* primarily documents intent and may drive a future
|
|
19
|
+
* predicate-based filter. Use `excludeInsideLinks` and the
|
|
20
|
+
* runtime selector to control the runtime side.
|
|
21
|
+
*/
|
|
22
|
+
selector?: string;
|
|
23
|
+
/** Background colour of the zoom overlay. Default rgba(0,0,0,.85). */
|
|
24
|
+
background?: string;
|
|
25
|
+
/** Pixels of margin around the zoomed image. Default 20. */
|
|
26
|
+
margin?: number;
|
|
27
|
+
/** Pixels of scroll before the overlay closes. Default 40. */
|
|
28
|
+
scrollOffset?: number;
|
|
29
|
+
/**
|
|
30
|
+
* When true (default), images inside `<a>` links are not
|
|
31
|
+
* tagged — the click already navigates somewhere. Setting to
|
|
32
|
+
* false zooms every image regardless.
|
|
33
|
+
*/
|
|
34
|
+
excludeInsideLinks?: boolean;
|
|
35
|
+
}
|
|
36
|
+
export interface ResolvedImageZoomOptions {
|
|
37
|
+
selector: string;
|
|
38
|
+
background: string;
|
|
39
|
+
margin: number;
|
|
40
|
+
scrollOffset: number;
|
|
41
|
+
excludeInsideLinks: boolean;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Validate + resolve user options. Throws structured errors with
|
|
45
|
+
* the offending field path so users see "options.margin must be a
|
|
46
|
+
* non-negative integer" rather than a cryptic stack.
|
|
47
|
+
*/
|
|
48
|
+
export declare function parseOptions(input: unknown): ResolvedImageZoomOptions;
|
|
49
|
+
//# sourceMappingURL=schema.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,MAAM,WAAW,gBAAgB;IAC/B;;;;;;;;;;;OAWG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,sEAAsE;IACtE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,4DAA4D;IAC5D,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,8DAA8D;IAC9D,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;;OAIG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAC;CAC9B;AAED,MAAM,WAAW,wBAAwB;IACvC,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,MAAM,CAAC;IACrB,kBAAkB,EAAE,OAAO,CAAC;CAC7B;AAUD;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,wBAAwB,CAyDrE"}
|
package/dist/schema.js
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Options schema for `@dogsbay/plugin-image-zoom`.
|
|
3
|
+
*
|
|
4
|
+
* Validation is hand-rolled to avoid a Zod dep — the option set is
|
|
5
|
+
* small enough that the boilerplate is in noise. The `parseOptions`
|
|
6
|
+
* function returns a fully-resolved options object with every field
|
|
7
|
+
* set to either the user value or the documented default.
|
|
8
|
+
*/
|
|
9
|
+
const DEFAULTS = {
|
|
10
|
+
selector: "article img",
|
|
11
|
+
background: "rgba(0, 0, 0, 0.85)",
|
|
12
|
+
margin: 20,
|
|
13
|
+
scrollOffset: 40,
|
|
14
|
+
excludeInsideLinks: true,
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Validate + resolve user options. Throws structured errors with
|
|
18
|
+
* the offending field path so users see "options.margin must be a
|
|
19
|
+
* non-negative integer" rather than a cryptic stack.
|
|
20
|
+
*/
|
|
21
|
+
export function parseOptions(input) {
|
|
22
|
+
if (input === undefined || input === null)
|
|
23
|
+
return { ...DEFAULTS };
|
|
24
|
+
if (typeof input !== "object" || Array.isArray(input)) {
|
|
25
|
+
throw new Error(`@dogsbay/plugin-image-zoom: options must be an object, got ${describe(input)}.`);
|
|
26
|
+
}
|
|
27
|
+
const opts = input;
|
|
28
|
+
const out = { ...DEFAULTS };
|
|
29
|
+
if (opts.selector !== undefined) {
|
|
30
|
+
if (typeof opts.selector !== "string" || opts.selector.trim() === "") {
|
|
31
|
+
throw new Error(`@dogsbay/plugin-image-zoom: options.selector must be a non-empty string.`);
|
|
32
|
+
}
|
|
33
|
+
out.selector = opts.selector;
|
|
34
|
+
}
|
|
35
|
+
if (opts.background !== undefined) {
|
|
36
|
+
if (typeof opts.background !== "string" || opts.background.trim() === "") {
|
|
37
|
+
throw new Error(`@dogsbay/plugin-image-zoom: options.background must be a non-empty string.`);
|
|
38
|
+
}
|
|
39
|
+
out.background = opts.background;
|
|
40
|
+
}
|
|
41
|
+
if (opts.margin !== undefined) {
|
|
42
|
+
if (typeof opts.margin !== "number" || !Number.isInteger(opts.margin) || opts.margin < 0) {
|
|
43
|
+
throw new Error(`@dogsbay/plugin-image-zoom: options.margin must be a non-negative integer.`);
|
|
44
|
+
}
|
|
45
|
+
out.margin = opts.margin;
|
|
46
|
+
}
|
|
47
|
+
if (opts.scrollOffset !== undefined) {
|
|
48
|
+
if (typeof opts.scrollOffset !== "number" ||
|
|
49
|
+
!Number.isInteger(opts.scrollOffset) ||
|
|
50
|
+
opts.scrollOffset < 0) {
|
|
51
|
+
throw new Error(`@dogsbay/plugin-image-zoom: options.scrollOffset must be a non-negative integer.`);
|
|
52
|
+
}
|
|
53
|
+
out.scrollOffset = opts.scrollOffset;
|
|
54
|
+
}
|
|
55
|
+
if (opts.excludeInsideLinks !== undefined) {
|
|
56
|
+
if (typeof opts.excludeInsideLinks !== "boolean") {
|
|
57
|
+
throw new Error(`@dogsbay/plugin-image-zoom: options.excludeInsideLinks must be a boolean.`);
|
|
58
|
+
}
|
|
59
|
+
out.excludeInsideLinks = opts.excludeInsideLinks;
|
|
60
|
+
}
|
|
61
|
+
return out;
|
|
62
|
+
}
|
|
63
|
+
function describe(value) {
|
|
64
|
+
if (value === null)
|
|
65
|
+
return "null";
|
|
66
|
+
if (Array.isArray(value))
|
|
67
|
+
return "array";
|
|
68
|
+
return typeof value;
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=schema.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.js","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"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"}
|
package/dist/walker.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build-time TreeNode walker — tags every page image with
|
|
3
|
+
* `data-zoomable="true"` so the runtime can skip its own DOM
|
|
4
|
+
* scan and bind handlers only to pre-marked elements.
|
|
5
|
+
*
|
|
6
|
+
* Skips images inside link nodes when `excludeInsideLinks` is on
|
|
7
|
+
* (the link click already does something — zooming would be
|
|
8
|
+
* surprising).
|
|
9
|
+
*
|
|
10
|
+
* Tolerates the cross-format TreeNode shape variations:
|
|
11
|
+
* - inline images attached to a paragraph via `inline: [{type: "image"}]`
|
|
12
|
+
* - block-level images as `{type: "image"}` children
|
|
13
|
+
* - `link` nodes by either `type: "link"` or `type: "a"`
|
|
14
|
+
*/
|
|
15
|
+
import type { TreeNode } from "@dogsbay/types";
|
|
16
|
+
import type { ResolvedImageZoomOptions } from "./schema.js";
|
|
17
|
+
export declare function tagImagesInTree(nodes: TreeNode[], opts: ResolvedImageZoomOptions): TreeNode[];
|
|
18
|
+
//# sourceMappingURL=walker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"walker.d.ts","sourceRoot":"","sources":["../src/walker.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,KAAK,EAAE,QAAQ,EAAc,MAAM,gBAAgB,CAAC;AAC3D,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,aAAa,CAAC;AAE5D,wBAAgB,eAAe,CAC7B,KAAK,EAAE,QAAQ,EAAE,EACjB,IAAI,EAAE,wBAAwB,GAC7B,QAAQ,EAAE,CAEZ"}
|
package/dist/walker.js
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
export function tagImagesInTree(nodes, opts) {
|
|
2
|
+
return nodes.map((n) => walkNode(n, opts, false));
|
|
3
|
+
}
|
|
4
|
+
function walkNode(node, opts, insideLink) {
|
|
5
|
+
const isLink = node.type === "link" || node.type === "a";
|
|
6
|
+
const recurseInsideLink = insideLink || isLink;
|
|
7
|
+
// Block-level image — tag it.
|
|
8
|
+
if (node.type === "image") {
|
|
9
|
+
if (insideLink && opts.excludeInsideLinks)
|
|
10
|
+
return node;
|
|
11
|
+
return {
|
|
12
|
+
...node,
|
|
13
|
+
props: { ...(node.props ?? {}), "data-zoomable": "true" },
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
let next = node;
|
|
17
|
+
// Recurse into block children.
|
|
18
|
+
if (node.children?.length) {
|
|
19
|
+
next = {
|
|
20
|
+
...next,
|
|
21
|
+
children: node.children.map((c) => walkNode(c, opts, recurseInsideLink)),
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
// Recurse into inline children. Inline images live as an entry in
|
|
25
|
+
// the inline array; tag them via an inline-side helper.
|
|
26
|
+
if (node.inline?.length) {
|
|
27
|
+
next = {
|
|
28
|
+
...next,
|
|
29
|
+
inline: walkInline(node.inline, opts, recurseInsideLink),
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
// Special-case the "paragraph with only one inline image" shape
|
|
33
|
+
// — the format-astro serializer emits a single <BlockImage /> for
|
|
34
|
+
// that. We tag the paragraph itself so BlockImage emission can
|
|
35
|
+
// forward `data-zoomable`.
|
|
36
|
+
if (next.type === "paragraph" && next.inline?.length) {
|
|
37
|
+
const meaningful = next.inline.filter((n) => !(n.type === "text" && !("text" in n && n.text.trim())));
|
|
38
|
+
if (meaningful.length === 1 &&
|
|
39
|
+
meaningful[0].type === "image" &&
|
|
40
|
+
!(insideLink && opts.excludeInsideLinks)) {
|
|
41
|
+
next = {
|
|
42
|
+
...next,
|
|
43
|
+
props: { ...(next.props ?? {}), "data-zoomable": "true" },
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return next;
|
|
48
|
+
}
|
|
49
|
+
function walkInline(nodes, opts, insideLink) {
|
|
50
|
+
return nodes.map((n) => {
|
|
51
|
+
if (n.type === "image") {
|
|
52
|
+
if (insideLink && opts.excludeInsideLinks)
|
|
53
|
+
return n;
|
|
54
|
+
// InlineImage doesn't have a typed `data-*` slot; we attach
|
|
55
|
+
// via a meta-style cast. The astro serializer reads the
|
|
56
|
+
// resulting attribute when emitting the <img> element.
|
|
57
|
+
return {
|
|
58
|
+
...n,
|
|
59
|
+
// The serializer maps unknown extra props onto the <img>
|
|
60
|
+
// tag — see packages/format-astro/src/serialize.ts. Casting
|
|
61
|
+
// through unknown to keep the InlineNode union types clean.
|
|
62
|
+
...{ "data-zoomable": "true" },
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
if (n.type === "link" && n.children?.length) {
|
|
66
|
+
return { ...n, children: walkInline(n.children, opts, true) };
|
|
67
|
+
}
|
|
68
|
+
return n;
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
//# sourceMappingURL=walker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"walker.js","sourceRoot":"","sources":["../src/walker.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@dogsbay/plugin-image-zoom",
|
|
3
|
+
"version": "0.2.0-beta.81",
|
|
4
|
+
"description": "Click-to-zoom for images in Dogsbay docs sites.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js"
|
|
12
|
+
},
|
|
13
|
+
"./client": "./dist/client.js",
|
|
14
|
+
"./styles.css": "./src/styles.css"
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist",
|
|
18
|
+
"src/styles.css",
|
|
19
|
+
"src/client.ts",
|
|
20
|
+
"README.md"
|
|
21
|
+
],
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"medium-zoom": "^1.1.0",
|
|
24
|
+
"@dogsbay/types": "0.2.0-beta.81"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"typescript": "^5.7.0",
|
|
28
|
+
"vitest": "^3.0.0",
|
|
29
|
+
"@types/node": "^22.0.0"
|
|
30
|
+
},
|
|
31
|
+
"license": "MIT",
|
|
32
|
+
"keywords": [
|
|
33
|
+
"dogsbay",
|
|
34
|
+
"dogsbay-plugin",
|
|
35
|
+
"documentation",
|
|
36
|
+
"image-zoom"
|
|
37
|
+
],
|
|
38
|
+
"repository": {
|
|
39
|
+
"type": "git",
|
|
40
|
+
"url": "https://github.com/dogsbay/dogsbay.git",
|
|
41
|
+
"directory": "packages/plugin-image-zoom"
|
|
42
|
+
},
|
|
43
|
+
"scripts": {
|
|
44
|
+
"build": "tsc",
|
|
45
|
+
"test": "vitest run",
|
|
46
|
+
"test:watch": "vitest"
|
|
47
|
+
}
|
|
48
|
+
}
|
package/src/client.ts
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client runtime for `@dogsbay/plugin-image-zoom`.
|
|
3
|
+
*
|
|
4
|
+
* Imported via the auto-generated `src/lib/plugin-runtime.ts`
|
|
5
|
+
* entry point in the user's Astro project. The build-time walker
|
|
6
|
+
* has already tagged article images with `data-zoomable="true"`,
|
|
7
|
+
* so this module only needs to (re)bind medium-zoom on every
|
|
8
|
+
* navigation event.
|
|
9
|
+
*
|
|
10
|
+
* Astro view transitions: every `astro:page-load` event causes a
|
|
11
|
+
* detach + reattach. `astro:before-swap` detaches before the new
|
|
12
|
+
* DOM lands so we don't carry stale references.
|
|
13
|
+
*/
|
|
14
|
+
import mediumZoom from "medium-zoom";
|
|
15
|
+
// Vite alias — emitted by `dogsbay site build` via
|
|
16
|
+
// `astro.config.plugins.mjs`. Resolves at build time to the
|
|
17
|
+
// JSON-serialized options the plugin baked in.
|
|
18
|
+
// @ts-expect-error: virtual module resolved by Vite alias.
|
|
19
|
+
import config from "virtual:dogsbay-plugin-config/@dogsbay/plugin-image-zoom";
|
|
20
|
+
|
|
21
|
+
interface RuntimeConfig {
|
|
22
|
+
background: string;
|
|
23
|
+
margin: number;
|
|
24
|
+
scrollOffset: number;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const runtimeConfig: RuntimeConfig = config as RuntimeConfig;
|
|
28
|
+
|
|
29
|
+
let zoom: ReturnType<typeof mediumZoom> | null = null;
|
|
30
|
+
|
|
31
|
+
function attach(): void {
|
|
32
|
+
if (zoom) {
|
|
33
|
+
zoom.detach();
|
|
34
|
+
zoom = null;
|
|
35
|
+
}
|
|
36
|
+
zoom = mediumZoom("[data-zoomable]", {
|
|
37
|
+
background: runtimeConfig.background,
|
|
38
|
+
margin: runtimeConfig.margin,
|
|
39
|
+
scrollOffset: runtimeConfig.scrollOffset,
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function detach(): void {
|
|
44
|
+
if (zoom) {
|
|
45
|
+
zoom.detach();
|
|
46
|
+
zoom = null;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (typeof document !== "undefined") {
|
|
51
|
+
if (document.readyState === "loading") {
|
|
52
|
+
document.addEventListener("DOMContentLoaded", attach);
|
|
53
|
+
} else {
|
|
54
|
+
attach();
|
|
55
|
+
}
|
|
56
|
+
document.addEventListener("astro:page-load", attach);
|
|
57
|
+
document.addEventListener("astro:before-swap", detach);
|
|
58
|
+
}
|
package/src/styles.css
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Styles shipped by @dogsbay/plugin-image-zoom.
|
|
3
|
+
*
|
|
4
|
+
* Imported into the user's `src/styles/global.css` by `dogsbay
|
|
5
|
+
* site build` via the plugin runtime emitter.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
[data-zoomable] {
|
|
9
|
+
cursor: zoom-in;
|
|
10
|
+
transition: transform 200ms ease;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.medium-zoom-overlay {
|
|
14
|
+
z-index: 100;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.medium-zoom-image {
|
|
18
|
+
cursor: zoom-out;
|
|
19
|
+
z-index: 101;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.medium-zoom--opened [data-zoomable] {
|
|
23
|
+
cursor: default;
|
|
24
|
+
}
|