@moku-labs/web 1.11.0 → 1.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -5,13 +5,14 @@ import { appendFileSync, existsSync, readFileSync, readdirSync, realpathSync, st
5
5
  import { createHash, randomUUID } from "node:crypto";
6
6
  import { cp, mkdir, readFile, readdir, rm, stat, writeFile } from "node:fs/promises";
7
7
  import { homedir, networkInterfaces, tmpdir } from "node:os";
8
- import path from "node:path";
8
+ import minpath, { default as path } from "node:path";
9
9
  import { Feed } from "feed";
10
10
  import { h } from "preact";
11
11
  import { renderToString } from "preact-render-to-string";
12
12
  import { execFileSync } from "node:child_process";
13
13
  import { createInterface } from "node:readline";
14
- import { fileURLToPath } from "node:url";
14
+ import { fileURLToPath, fileURLToPath as urlToPath } from "node:url";
15
+ import minproc from "node:process";
15
16
  import matter from "gray-matter";
16
17
  import rehypeShiki from "@shikijs/rehype";
17
18
  import rehypeSanitize from "rehype-sanitize";
@@ -1570,14 +1571,15 @@ function validateContentConfig(config) {
1570
1571
  }
1571
1572
  /**
1572
1573
  * Validates the `fileSystemContent` provider options (fail-fast at provider
1573
- * construction). Throws when `mermaid` or `embed` is enabled without
1574
- * `trustedContent: true`: both emit raw HTML (inline SVG / the embed facade),
1575
- * which the sanitize pass (the untrusted-content XSS boundary) would strip — so
1576
- * the combination can never work. Errors use the `[web]` prefix.
1574
+ * construction). Throws when `mermaid`, `embed`, or `gallery` is enabled without
1575
+ * `trustedContent: true`: each emits raw HTML (inline SVG / the embed facade /
1576
+ * the gallery markup), which the sanitize pass (the untrusted-content XSS
1577
+ * boundary) would strip — so the combination can never work. Errors use the
1578
+ * `[web]` prefix.
1577
1579
  *
1578
1580
  * @param options - The provider options to validate.
1579
- * @throws {Error} If `mermaid` or `embed` is enabled while `trustedContent` is
1580
- * not `true`.
1581
+ * @throws {Error} If `mermaid`, `embed`, or `gallery` is enabled while
1582
+ * `trustedContent` is not `true`.
1581
1583
  * @example
1582
1584
  * ```ts
1583
1585
  * validateFileSystemContentOptions({ contentDir: "./content", trustedContent: true, mermaid: true });
@@ -1586,6 +1588,7 @@ function validateContentConfig(config) {
1586
1588
  function validateFileSystemContentOptions(options) {
1587
1589
  if (Boolean(options.mermaid) && options.trustedContent !== true) throw new Error("[web] content: `mermaid` requires `trustedContent: true`.\n Mermaid diagrams render to raw inline SVG, which the sanitize pass would strip.\n Set trustedContent: true ONLY for fully author-controlled Markdown.");
1588
1590
  if (Boolean(options.embed) && options.trustedContent !== true) throw new Error("[web] content: `embed` requires `trustedContent: true`.\n Embed directives render to a raw-HTML facade, which the sanitize pass would strip\n (and embedding third-party iframes is never safe for untrusted Markdown).\n Set trustedContent: true ONLY for fully author-controlled Markdown.");
1591
+ if (Boolean(options.gallery) && options.trustedContent !== true) throw new Error("[web] content: `gallery` requires `trustedContent: true`.\n Gallery directives render to raw-HTML markup, which the sanitize pass would strip.\n Set trustedContent: true ONLY for fully author-controlled Markdown.");
1589
1592
  }
1590
1593
  //#endregion
1591
1594
  //#region src/plugins/content/index.ts
@@ -11625,6 +11628,857 @@ function cloudflareBindings() {
11625
11628
  };
11626
11629
  }
11627
11630
  //#endregion
11631
+ //#region node_modules/unist-util-stringify-position/lib/index.js
11632
+ /**
11633
+ * @typedef {import('unist').Node} Node
11634
+ * @typedef {import('unist').Point} Point
11635
+ * @typedef {import('unist').Position} Position
11636
+ */
11637
+ /**
11638
+ * @typedef NodeLike
11639
+ * @property {string} type
11640
+ * @property {PositionLike | null | undefined} [position]
11641
+ *
11642
+ * @typedef PointLike
11643
+ * @property {number | null | undefined} [line]
11644
+ * @property {number | null | undefined} [column]
11645
+ * @property {number | null | undefined} [offset]
11646
+ *
11647
+ * @typedef PositionLike
11648
+ * @property {PointLike | null | undefined} [start]
11649
+ * @property {PointLike | null | undefined} [end]
11650
+ */
11651
+ /**
11652
+ * Serialize the positional info of a point, position (start and end points),
11653
+ * or node.
11654
+ *
11655
+ * @param {Node | NodeLike | Point | PointLike | Position | PositionLike | null | undefined} [value]
11656
+ * Node, position, or point.
11657
+ * @returns {string}
11658
+ * Pretty printed positional info of a node (`string`).
11659
+ *
11660
+ * In the format of a range `ls:cs-le:ce` (when given `node` or `position`)
11661
+ * or a point `l:c` (when given `point`), where `l` stands for line, `c` for
11662
+ * column, `s` for `start`, and `e` for end.
11663
+ * An empty string (`''`) is returned if the given value is neither `node`,
11664
+ * `position`, nor `point`.
11665
+ */
11666
+ function stringifyPosition(value) {
11667
+ if (!value || typeof value !== "object") return "";
11668
+ if ("position" in value || "type" in value) return position(value.position);
11669
+ if ("start" in value || "end" in value) return position(value);
11670
+ if ("line" in value || "column" in value) return point(value);
11671
+ return "";
11672
+ }
11673
+ /**
11674
+ * @param {Point | PointLike | null | undefined} point
11675
+ * @returns {string}
11676
+ */
11677
+ function point(point) {
11678
+ return index(point && point.line) + ":" + index(point && point.column);
11679
+ }
11680
+ /**
11681
+ * @param {Position | PositionLike | null | undefined} pos
11682
+ * @returns {string}
11683
+ */
11684
+ function position(pos) {
11685
+ return point(pos && pos.start) + "-" + point(pos && pos.end);
11686
+ }
11687
+ /**
11688
+ * @param {number | null | undefined} value
11689
+ * @returns {number}
11690
+ */
11691
+ function index(value) {
11692
+ return value && typeof value === "number" ? value : 1;
11693
+ }
11694
+ //#endregion
11695
+ //#region node_modules/vfile-message/lib/index.js
11696
+ /**
11697
+ * @import {Node, Point, Position} from 'unist'
11698
+ */
11699
+ /**
11700
+ * @typedef {object & {type: string, position?: Position | undefined}} NodeLike
11701
+ *
11702
+ * @typedef Options
11703
+ * Configuration.
11704
+ * @property {Array<Node> | null | undefined} [ancestors]
11705
+ * Stack of (inclusive) ancestor nodes surrounding the message (optional).
11706
+ * @property {Error | null | undefined} [cause]
11707
+ * Original error cause of the message (optional).
11708
+ * @property {Point | Position | null | undefined} [place]
11709
+ * Place of message (optional).
11710
+ * @property {string | null | undefined} [ruleId]
11711
+ * Category of message (optional, example: `'my-rule'`).
11712
+ * @property {string | null | undefined} [source]
11713
+ * Namespace of who sent the message (optional, example: `'my-package'`).
11714
+ */
11715
+ /**
11716
+ * Message.
11717
+ */
11718
+ var VFileMessage = class extends Error {
11719
+ /**
11720
+ * Create a message for `reason`.
11721
+ *
11722
+ * > 🪦 **Note**: also has obsolete signatures.
11723
+ *
11724
+ * @overload
11725
+ * @param {string} reason
11726
+ * @param {Options | null | undefined} [options]
11727
+ * @returns
11728
+ *
11729
+ * @overload
11730
+ * @param {string} reason
11731
+ * @param {Node | NodeLike | null | undefined} parent
11732
+ * @param {string | null | undefined} [origin]
11733
+ * @returns
11734
+ *
11735
+ * @overload
11736
+ * @param {string} reason
11737
+ * @param {Point | Position | null | undefined} place
11738
+ * @param {string | null | undefined} [origin]
11739
+ * @returns
11740
+ *
11741
+ * @overload
11742
+ * @param {string} reason
11743
+ * @param {string | null | undefined} [origin]
11744
+ * @returns
11745
+ *
11746
+ * @overload
11747
+ * @param {Error | VFileMessage} cause
11748
+ * @param {Node | NodeLike | null | undefined} parent
11749
+ * @param {string | null | undefined} [origin]
11750
+ * @returns
11751
+ *
11752
+ * @overload
11753
+ * @param {Error | VFileMessage} cause
11754
+ * @param {Point | Position | null | undefined} place
11755
+ * @param {string | null | undefined} [origin]
11756
+ * @returns
11757
+ *
11758
+ * @overload
11759
+ * @param {Error | VFileMessage} cause
11760
+ * @param {string | null | undefined} [origin]
11761
+ * @returns
11762
+ *
11763
+ * @param {Error | VFileMessage | string} causeOrReason
11764
+ * Reason for message, should use markdown.
11765
+ * @param {Node | NodeLike | Options | Point | Position | string | null | undefined} [optionsOrParentOrPlace]
11766
+ * Configuration (optional).
11767
+ * @param {string | null | undefined} [origin]
11768
+ * Place in code where the message originates (example:
11769
+ * `'my-package:my-rule'` or `'my-rule'`).
11770
+ * @returns
11771
+ * Instance of `VFileMessage`.
11772
+ */
11773
+ constructor(causeOrReason, optionsOrParentOrPlace, origin) {
11774
+ super();
11775
+ if (typeof optionsOrParentOrPlace === "string") {
11776
+ origin = optionsOrParentOrPlace;
11777
+ optionsOrParentOrPlace = void 0;
11778
+ }
11779
+ /** @type {string} */
11780
+ let reason = "";
11781
+ /** @type {Options} */
11782
+ let options = {};
11783
+ let legacyCause = false;
11784
+ if (optionsOrParentOrPlace) if ("line" in optionsOrParentOrPlace && "column" in optionsOrParentOrPlace) options = { place: optionsOrParentOrPlace };
11785
+ else if ("start" in optionsOrParentOrPlace && "end" in optionsOrParentOrPlace) options = { place: optionsOrParentOrPlace };
11786
+ else if ("type" in optionsOrParentOrPlace) options = {
11787
+ ancestors: [optionsOrParentOrPlace],
11788
+ place: optionsOrParentOrPlace.position
11789
+ };
11790
+ else options = { ...optionsOrParentOrPlace };
11791
+ if (typeof causeOrReason === "string") reason = causeOrReason;
11792
+ else if (!options.cause && causeOrReason) {
11793
+ legacyCause = true;
11794
+ reason = causeOrReason.message;
11795
+ options.cause = causeOrReason;
11796
+ }
11797
+ if (!options.ruleId && !options.source && typeof origin === "string") {
11798
+ const index = origin.indexOf(":");
11799
+ if (index === -1) options.ruleId = origin;
11800
+ else {
11801
+ options.source = origin.slice(0, index);
11802
+ options.ruleId = origin.slice(index + 1);
11803
+ }
11804
+ }
11805
+ if (!options.place && options.ancestors && options.ancestors) {
11806
+ const parent = options.ancestors[options.ancestors.length - 1];
11807
+ if (parent) options.place = parent.position;
11808
+ }
11809
+ const start = options.place && "start" in options.place ? options.place.start : options.place;
11810
+ /**
11811
+ * Stack of ancestor nodes surrounding the message.
11812
+ *
11813
+ * @type {Array<Node> | undefined}
11814
+ */
11815
+ this.ancestors = options.ancestors || void 0;
11816
+ /**
11817
+ * Original error cause of the message.
11818
+ *
11819
+ * @type {Error | undefined}
11820
+ */
11821
+ this.cause = options.cause || void 0;
11822
+ /**
11823
+ * Starting column of message.
11824
+ *
11825
+ * @type {number | undefined}
11826
+ */
11827
+ this.column = start ? start.column : void 0;
11828
+ /**
11829
+ * State of problem.
11830
+ *
11831
+ * * `true` — error, file not usable
11832
+ * * `false` — warning, change may be needed
11833
+ * * `undefined` — change likely not needed
11834
+ *
11835
+ * @type {boolean | null | undefined}
11836
+ */
11837
+ this.fatal = void 0;
11838
+ /**
11839
+ * Path of a file (used throughout the `VFile` ecosystem).
11840
+ *
11841
+ * @type {string | undefined}
11842
+ */
11843
+ this.file = "";
11844
+ /**
11845
+ * Reason for message.
11846
+ *
11847
+ * @type {string}
11848
+ */
11849
+ this.message = reason;
11850
+ /**
11851
+ * Starting line of error.
11852
+ *
11853
+ * @type {number | undefined}
11854
+ */
11855
+ this.line = start ? start.line : void 0;
11856
+ /**
11857
+ * Serialized positional info of message.
11858
+ *
11859
+ * On normal errors, this would be something like `ParseError`, buit in
11860
+ * `VFile` messages we use this space to show where an error happened.
11861
+ */
11862
+ this.name = stringifyPosition(options.place) || "1:1";
11863
+ /**
11864
+ * Place of message.
11865
+ *
11866
+ * @type {Point | Position | undefined}
11867
+ */
11868
+ this.place = options.place || void 0;
11869
+ /**
11870
+ * Reason for message, should use markdown.
11871
+ *
11872
+ * @type {string}
11873
+ */
11874
+ this.reason = this.message;
11875
+ /**
11876
+ * Category of message (example: `'my-rule'`).
11877
+ *
11878
+ * @type {string | undefined}
11879
+ */
11880
+ this.ruleId = options.ruleId || void 0;
11881
+ /**
11882
+ * Namespace of message (example: `'my-package'`).
11883
+ *
11884
+ * @type {string | undefined}
11885
+ */
11886
+ this.source = options.source || void 0;
11887
+ /**
11888
+ * Stack of message.
11889
+ *
11890
+ * This is used by normal errors to show where something happened in
11891
+ * programming code, irrelevant for `VFile` messages,
11892
+ *
11893
+ * @type {string}
11894
+ */
11895
+ this.stack = legacyCause && options.cause && typeof options.cause.stack === "string" ? options.cause.stack : "";
11896
+ /**
11897
+ * Specify the source value that’s being reported, which is deemed
11898
+ * incorrect.
11899
+ *
11900
+ * @type {string | undefined}
11901
+ */
11902
+ this.actual = void 0;
11903
+ /**
11904
+ * Suggest acceptable values that can be used instead of `actual`.
11905
+ *
11906
+ * @type {Array<string> | undefined}
11907
+ */
11908
+ this.expected = void 0;
11909
+ /**
11910
+ * Long form description of the message (you should use markdown).
11911
+ *
11912
+ * @type {string | undefined}
11913
+ */
11914
+ this.note = void 0;
11915
+ /**
11916
+ * Link to docs for the message.
11917
+ *
11918
+ * > 👉 **Note**: this must be an absolute URL that can be passed as `x`
11919
+ * > to `new URL(x)`.
11920
+ *
11921
+ * @type {string | undefined}
11922
+ */
11923
+ this.url = void 0;
11924
+ }
11925
+ };
11926
+ VFileMessage.prototype.file = "";
11927
+ VFileMessage.prototype.name = "";
11928
+ VFileMessage.prototype.reason = "";
11929
+ VFileMessage.prototype.message = "";
11930
+ VFileMessage.prototype.stack = "";
11931
+ VFileMessage.prototype.column = void 0;
11932
+ VFileMessage.prototype.line = void 0;
11933
+ VFileMessage.prototype.ancestors = void 0;
11934
+ VFileMessage.prototype.cause = void 0;
11935
+ VFileMessage.prototype.fatal = void 0;
11936
+ VFileMessage.prototype.place = void 0;
11937
+ VFileMessage.prototype.ruleId = void 0;
11938
+ VFileMessage.prototype.source = void 0;
11939
+ //#endregion
11940
+ //#region node_modules/vfile/lib/minurl.shared.js
11941
+ /**
11942
+ * Checks if a value has the shape of a WHATWG URL object.
11943
+ *
11944
+ * Using a symbol or instanceof would not be able to recognize URL objects
11945
+ * coming from other implementations (e.g. in Electron), so instead we are
11946
+ * checking some well known properties for a lack of a better test.
11947
+ *
11948
+ * We use `href` and `protocol` as they are the only properties that are
11949
+ * easy to retrieve and calculate due to the lazy nature of the getters.
11950
+ *
11951
+ * We check for auth attribute to distinguish legacy url instance with
11952
+ * WHATWG URL instance.
11953
+ *
11954
+ * @param {unknown} fileUrlOrPath
11955
+ * File path or URL.
11956
+ * @returns {fileUrlOrPath is URL}
11957
+ * Whether it’s a URL.
11958
+ */
11959
+ function isUrl(fileUrlOrPath) {
11960
+ return Boolean(fileUrlOrPath !== null && typeof fileUrlOrPath === "object" && "href" in fileUrlOrPath && fileUrlOrPath.href && "protocol" in fileUrlOrPath && fileUrlOrPath.protocol && fileUrlOrPath.auth === void 0);
11961
+ }
11962
+ //#endregion
11963
+ //#region node_modules/vfile/lib/index.js
11964
+ /**
11965
+ * @import {Node, Point, Position} from 'unist'
11966
+ * @import {Options as MessageOptions} from 'vfile-message'
11967
+ * @import {Compatible, Data, Map, Options, Value} from 'vfile'
11968
+ */
11969
+ /**
11970
+ * @typedef {object & {type: string, position?: Position | undefined}} NodeLike
11971
+ */
11972
+ /**
11973
+ * Order of setting (least specific to most), we need this because otherwise
11974
+ * `{stem: 'a', path: '~/b.js'}` would throw, as a path is needed before a
11975
+ * stem can be set.
11976
+ */
11977
+ const order = [
11978
+ "history",
11979
+ "path",
11980
+ "basename",
11981
+ "stem",
11982
+ "extname",
11983
+ "dirname"
11984
+ ];
11985
+ var VFile = class {
11986
+ /**
11987
+ * Create a new virtual file.
11988
+ *
11989
+ * `options` is treated as:
11990
+ *
11991
+ * * `string` or `Uint8Array` — `{value: options}`
11992
+ * * `URL` — `{path: options}`
11993
+ * * `VFile` — shallow copies its data over to the new file
11994
+ * * `object` — all fields are shallow copied over to the new file
11995
+ *
11996
+ * Path related fields are set in the following order (least specific to
11997
+ * most specific): `history`, `path`, `basename`, `stem`, `extname`,
11998
+ * `dirname`.
11999
+ *
12000
+ * You cannot set `dirname` or `extname` without setting either `history`,
12001
+ * `path`, `basename`, or `stem` too.
12002
+ *
12003
+ * @param {Compatible | null | undefined} [value]
12004
+ * File value.
12005
+ * @returns
12006
+ * New instance.
12007
+ */
12008
+ constructor(value) {
12009
+ /** @type {Options | VFile} */
12010
+ let options;
12011
+ if (!value) options = {};
12012
+ else if (isUrl(value)) options = { path: value };
12013
+ else if (typeof value === "string" || isUint8Array(value)) options = { value };
12014
+ else options = value;
12015
+ /**
12016
+ * Base of `path` (default: `process.cwd()` or `'/'` in browsers).
12017
+ *
12018
+ * @type {string}
12019
+ */
12020
+ this.cwd = "cwd" in options ? "" : minproc.cwd();
12021
+ /**
12022
+ * Place to store custom info (default: `{}`).
12023
+ *
12024
+ * It’s OK to store custom data directly on the file but moving it to
12025
+ * `data` is recommended.
12026
+ *
12027
+ * @type {Data}
12028
+ */
12029
+ this.data = {};
12030
+ /**
12031
+ * List of file paths the file moved between.
12032
+ *
12033
+ * The first is the original path and the last is the current path.
12034
+ *
12035
+ * @type {Array<string>}
12036
+ */
12037
+ this.history = [];
12038
+ /**
12039
+ * List of messages associated with the file.
12040
+ *
12041
+ * @type {Array<VFileMessage>}
12042
+ */
12043
+ this.messages = [];
12044
+ /**
12045
+ * Raw value.
12046
+ *
12047
+ * @type {Value}
12048
+ */
12049
+ this.value;
12050
+ /**
12051
+ * Source map.
12052
+ *
12053
+ * This type is equivalent to the `RawSourceMap` type from the `source-map`
12054
+ * module.
12055
+ *
12056
+ * @type {Map | null | undefined}
12057
+ */
12058
+ this.map;
12059
+ /**
12060
+ * Custom, non-string, compiled, representation.
12061
+ *
12062
+ * This is used by unified to store non-string results.
12063
+ * One example is when turning markdown into React nodes.
12064
+ *
12065
+ * @type {unknown}
12066
+ */
12067
+ this.result;
12068
+ /**
12069
+ * Whether a file was saved to disk.
12070
+ *
12071
+ * This is used by vfile reporters.
12072
+ *
12073
+ * @type {boolean}
12074
+ */
12075
+ this.stored;
12076
+ let index = -1;
12077
+ while (++index < order.length) {
12078
+ const field = order[index];
12079
+ if (field in options && options[field] !== void 0 && options[field] !== null) this[field] = field === "history" ? [...options[field]] : options[field];
12080
+ }
12081
+ /** @type {string} */
12082
+ let field;
12083
+ for (field in options) if (!order.includes(field)) this[field] = options[field];
12084
+ }
12085
+ /**
12086
+ * Get the basename (including extname) (example: `'index.min.js'`).
12087
+ *
12088
+ * @returns {string | undefined}
12089
+ * Basename.
12090
+ */
12091
+ get basename() {
12092
+ return typeof this.path === "string" ? minpath.basename(this.path) : void 0;
12093
+ }
12094
+ /**
12095
+ * Set basename (including extname) (`'index.min.js'`).
12096
+ *
12097
+ * Cannot contain path separators (`'/'` on unix, macOS, and browsers, `'\'`
12098
+ * on windows).
12099
+ * Cannot be nullified (use `file.path = file.dirname` instead).
12100
+ *
12101
+ * @param {string} basename
12102
+ * Basename.
12103
+ * @returns {undefined}
12104
+ * Nothing.
12105
+ */
12106
+ set basename(basename) {
12107
+ assertNonEmpty(basename, "basename");
12108
+ assertPart(basename, "basename");
12109
+ this.path = minpath.join(this.dirname || "", basename);
12110
+ }
12111
+ /**
12112
+ * Get the parent path (example: `'~'`).
12113
+ *
12114
+ * @returns {string | undefined}
12115
+ * Dirname.
12116
+ */
12117
+ get dirname() {
12118
+ return typeof this.path === "string" ? minpath.dirname(this.path) : void 0;
12119
+ }
12120
+ /**
12121
+ * Set the parent path (example: `'~'`).
12122
+ *
12123
+ * Cannot be set if there’s no `path` yet.
12124
+ *
12125
+ * @param {string | undefined} dirname
12126
+ * Dirname.
12127
+ * @returns {undefined}
12128
+ * Nothing.
12129
+ */
12130
+ set dirname(dirname) {
12131
+ assertPath(this.basename, "dirname");
12132
+ this.path = minpath.join(dirname || "", this.basename);
12133
+ }
12134
+ /**
12135
+ * Get the extname (including dot) (example: `'.js'`).
12136
+ *
12137
+ * @returns {string | undefined}
12138
+ * Extname.
12139
+ */
12140
+ get extname() {
12141
+ return typeof this.path === "string" ? minpath.extname(this.path) : void 0;
12142
+ }
12143
+ /**
12144
+ * Set the extname (including dot) (example: `'.js'`).
12145
+ *
12146
+ * Cannot contain path separators (`'/'` on unix, macOS, and browsers, `'\'`
12147
+ * on windows).
12148
+ * Cannot be set if there’s no `path` yet.
12149
+ *
12150
+ * @param {string | undefined} extname
12151
+ * Extname.
12152
+ * @returns {undefined}
12153
+ * Nothing.
12154
+ */
12155
+ set extname(extname) {
12156
+ assertPart(extname, "extname");
12157
+ assertPath(this.dirname, "extname");
12158
+ if (extname) {
12159
+ if (extname.codePointAt(0) !== 46) throw new Error("`extname` must start with `.`");
12160
+ if (extname.includes(".", 1)) throw new Error("`extname` cannot contain multiple dots");
12161
+ }
12162
+ this.path = minpath.join(this.dirname, this.stem + (extname || ""));
12163
+ }
12164
+ /**
12165
+ * Get the full path (example: `'~/index.min.js'`).
12166
+ *
12167
+ * @returns {string}
12168
+ * Path.
12169
+ */
12170
+ get path() {
12171
+ return this.history[this.history.length - 1];
12172
+ }
12173
+ /**
12174
+ * Set the full path (example: `'~/index.min.js'`).
12175
+ *
12176
+ * Cannot be nullified.
12177
+ * You can set a file URL (a `URL` object with a `file:` protocol) which will
12178
+ * be turned into a path with `url.fileURLToPath`.
12179
+ *
12180
+ * @param {URL | string} path
12181
+ * Path.
12182
+ * @returns {undefined}
12183
+ * Nothing.
12184
+ */
12185
+ set path(path) {
12186
+ if (isUrl(path)) path = urlToPath(path);
12187
+ assertNonEmpty(path, "path");
12188
+ if (this.path !== path) this.history.push(path);
12189
+ }
12190
+ /**
12191
+ * Get the stem (basename w/o extname) (example: `'index.min'`).
12192
+ *
12193
+ * @returns {string | undefined}
12194
+ * Stem.
12195
+ */
12196
+ get stem() {
12197
+ return typeof this.path === "string" ? minpath.basename(this.path, this.extname) : void 0;
12198
+ }
12199
+ /**
12200
+ * Set the stem (basename w/o extname) (example: `'index.min'`).
12201
+ *
12202
+ * Cannot contain path separators (`'/'` on unix, macOS, and browsers, `'\'`
12203
+ * on windows).
12204
+ * Cannot be nullified (use `file.path = file.dirname` instead).
12205
+ *
12206
+ * @param {string} stem
12207
+ * Stem.
12208
+ * @returns {undefined}
12209
+ * Nothing.
12210
+ */
12211
+ set stem(stem) {
12212
+ assertNonEmpty(stem, "stem");
12213
+ assertPart(stem, "stem");
12214
+ this.path = minpath.join(this.dirname || "", stem + (this.extname || ""));
12215
+ }
12216
+ /**
12217
+ * Create a fatal message for `reason` associated with the file.
12218
+ *
12219
+ * The `fatal` field of the message is set to `true` (error; file not usable)
12220
+ * and the `file` field is set to the current file path.
12221
+ * The message is added to the `messages` field on `file`.
12222
+ *
12223
+ * > 🪦 **Note**: also has obsolete signatures.
12224
+ *
12225
+ * @overload
12226
+ * @param {string} reason
12227
+ * @param {MessageOptions | null | undefined} [options]
12228
+ * @returns {never}
12229
+ *
12230
+ * @overload
12231
+ * @param {string} reason
12232
+ * @param {Node | NodeLike | null | undefined} parent
12233
+ * @param {string | null | undefined} [origin]
12234
+ * @returns {never}
12235
+ *
12236
+ * @overload
12237
+ * @param {string} reason
12238
+ * @param {Point | Position | null | undefined} place
12239
+ * @param {string | null | undefined} [origin]
12240
+ * @returns {never}
12241
+ *
12242
+ * @overload
12243
+ * @param {string} reason
12244
+ * @param {string | null | undefined} [origin]
12245
+ * @returns {never}
12246
+ *
12247
+ * @overload
12248
+ * @param {Error | VFileMessage} cause
12249
+ * @param {Node | NodeLike | null | undefined} parent
12250
+ * @param {string | null | undefined} [origin]
12251
+ * @returns {never}
12252
+ *
12253
+ * @overload
12254
+ * @param {Error | VFileMessage} cause
12255
+ * @param {Point | Position | null | undefined} place
12256
+ * @param {string | null | undefined} [origin]
12257
+ * @returns {never}
12258
+ *
12259
+ * @overload
12260
+ * @param {Error | VFileMessage} cause
12261
+ * @param {string | null | undefined} [origin]
12262
+ * @returns {never}
12263
+ *
12264
+ * @param {Error | VFileMessage | string} causeOrReason
12265
+ * Reason for message, should use markdown.
12266
+ * @param {Node | NodeLike | MessageOptions | Point | Position | string | null | undefined} [optionsOrParentOrPlace]
12267
+ * Configuration (optional).
12268
+ * @param {string | null | undefined} [origin]
12269
+ * Place in code where the message originates (example:
12270
+ * `'my-package:my-rule'` or `'my-rule'`).
12271
+ * @returns {never}
12272
+ * Never.
12273
+ * @throws {VFileMessage}
12274
+ * Message.
12275
+ */
12276
+ fail(causeOrReason, optionsOrParentOrPlace, origin) {
12277
+ const message = this.message(causeOrReason, optionsOrParentOrPlace, origin);
12278
+ message.fatal = true;
12279
+ throw message;
12280
+ }
12281
+ /**
12282
+ * Create an info message for `reason` associated with the file.
12283
+ *
12284
+ * The `fatal` field of the message is set to `undefined` (info; change
12285
+ * likely not needed) and the `file` field is set to the current file path.
12286
+ * The message is added to the `messages` field on `file`.
12287
+ *
12288
+ * > 🪦 **Note**: also has obsolete signatures.
12289
+ *
12290
+ * @overload
12291
+ * @param {string} reason
12292
+ * @param {MessageOptions | null | undefined} [options]
12293
+ * @returns {VFileMessage}
12294
+ *
12295
+ * @overload
12296
+ * @param {string} reason
12297
+ * @param {Node | NodeLike | null | undefined} parent
12298
+ * @param {string | null | undefined} [origin]
12299
+ * @returns {VFileMessage}
12300
+ *
12301
+ * @overload
12302
+ * @param {string} reason
12303
+ * @param {Point | Position | null | undefined} place
12304
+ * @param {string | null | undefined} [origin]
12305
+ * @returns {VFileMessage}
12306
+ *
12307
+ * @overload
12308
+ * @param {string} reason
12309
+ * @param {string | null | undefined} [origin]
12310
+ * @returns {VFileMessage}
12311
+ *
12312
+ * @overload
12313
+ * @param {Error | VFileMessage} cause
12314
+ * @param {Node | NodeLike | null | undefined} parent
12315
+ * @param {string | null | undefined} [origin]
12316
+ * @returns {VFileMessage}
12317
+ *
12318
+ * @overload
12319
+ * @param {Error | VFileMessage} cause
12320
+ * @param {Point | Position | null | undefined} place
12321
+ * @param {string | null | undefined} [origin]
12322
+ * @returns {VFileMessage}
12323
+ *
12324
+ * @overload
12325
+ * @param {Error | VFileMessage} cause
12326
+ * @param {string | null | undefined} [origin]
12327
+ * @returns {VFileMessage}
12328
+ *
12329
+ * @param {Error | VFileMessage | string} causeOrReason
12330
+ * Reason for message, should use markdown.
12331
+ * @param {Node | NodeLike | MessageOptions | Point | Position | string | null | undefined} [optionsOrParentOrPlace]
12332
+ * Configuration (optional).
12333
+ * @param {string | null | undefined} [origin]
12334
+ * Place in code where the message originates (example:
12335
+ * `'my-package:my-rule'` or `'my-rule'`).
12336
+ * @returns {VFileMessage}
12337
+ * Message.
12338
+ */
12339
+ info(causeOrReason, optionsOrParentOrPlace, origin) {
12340
+ const message = this.message(causeOrReason, optionsOrParentOrPlace, origin);
12341
+ message.fatal = void 0;
12342
+ return message;
12343
+ }
12344
+ /**
12345
+ * Create a message for `reason` associated with the file.
12346
+ *
12347
+ * The `fatal` field of the message is set to `false` (warning; change may be
12348
+ * needed) and the `file` field is set to the current file path.
12349
+ * The message is added to the `messages` field on `file`.
12350
+ *
12351
+ * > 🪦 **Note**: also has obsolete signatures.
12352
+ *
12353
+ * @overload
12354
+ * @param {string} reason
12355
+ * @param {MessageOptions | null | undefined} [options]
12356
+ * @returns {VFileMessage}
12357
+ *
12358
+ * @overload
12359
+ * @param {string} reason
12360
+ * @param {Node | NodeLike | null | undefined} parent
12361
+ * @param {string | null | undefined} [origin]
12362
+ * @returns {VFileMessage}
12363
+ *
12364
+ * @overload
12365
+ * @param {string} reason
12366
+ * @param {Point | Position | null | undefined} place
12367
+ * @param {string | null | undefined} [origin]
12368
+ * @returns {VFileMessage}
12369
+ *
12370
+ * @overload
12371
+ * @param {string} reason
12372
+ * @param {string | null | undefined} [origin]
12373
+ * @returns {VFileMessage}
12374
+ *
12375
+ * @overload
12376
+ * @param {Error | VFileMessage} cause
12377
+ * @param {Node | NodeLike | null | undefined} parent
12378
+ * @param {string | null | undefined} [origin]
12379
+ * @returns {VFileMessage}
12380
+ *
12381
+ * @overload
12382
+ * @param {Error | VFileMessage} cause
12383
+ * @param {Point | Position | null | undefined} place
12384
+ * @param {string | null | undefined} [origin]
12385
+ * @returns {VFileMessage}
12386
+ *
12387
+ * @overload
12388
+ * @param {Error | VFileMessage} cause
12389
+ * @param {string | null | undefined} [origin]
12390
+ * @returns {VFileMessage}
12391
+ *
12392
+ * @param {Error | VFileMessage | string} causeOrReason
12393
+ * Reason for message, should use markdown.
12394
+ * @param {Node | NodeLike | MessageOptions | Point | Position | string | null | undefined} [optionsOrParentOrPlace]
12395
+ * Configuration (optional).
12396
+ * @param {string | null | undefined} [origin]
12397
+ * Place in code where the message originates (example:
12398
+ * `'my-package:my-rule'` or `'my-rule'`).
12399
+ * @returns {VFileMessage}
12400
+ * Message.
12401
+ */
12402
+ message(causeOrReason, optionsOrParentOrPlace, origin) {
12403
+ const message = new VFileMessage(causeOrReason, optionsOrParentOrPlace, origin);
12404
+ if (this.path) {
12405
+ message.name = this.path + ":" + message.name;
12406
+ message.file = this.path;
12407
+ }
12408
+ message.fatal = false;
12409
+ this.messages.push(message);
12410
+ return message;
12411
+ }
12412
+ /**
12413
+ * Serialize the file.
12414
+ *
12415
+ * > **Note**: which encodings are supported depends on the engine.
12416
+ * > For info on Node.js, see:
12417
+ * > <https://nodejs.org/api/util.html#whatwg-supported-encodings>.
12418
+ *
12419
+ * @param {string | null | undefined} [encoding='utf8']
12420
+ * Character encoding to understand `value` as when it’s a `Uint8Array`
12421
+ * (default: `'utf-8'`).
12422
+ * @returns {string}
12423
+ * Serialized file.
12424
+ */
12425
+ toString(encoding) {
12426
+ if (this.value === void 0) return "";
12427
+ if (typeof this.value === "string") return this.value;
12428
+ return new TextDecoder(encoding || void 0).decode(this.value);
12429
+ }
12430
+ };
12431
+ /**
12432
+ * Assert that `part` is not a path (as in, does not contain `path.sep`).
12433
+ *
12434
+ * @param {string | null | undefined} part
12435
+ * File path part.
12436
+ * @param {string} name
12437
+ * Part name.
12438
+ * @returns {undefined}
12439
+ * Nothing.
12440
+ */
12441
+ function assertPart(part, name) {
12442
+ if (part && part.includes(minpath.sep)) throw new Error("`" + name + "` cannot be a path: did not expect `" + minpath.sep + "`");
12443
+ }
12444
+ /**
12445
+ * Assert that `part` is not empty.
12446
+ *
12447
+ * @param {string | undefined} part
12448
+ * Thing.
12449
+ * @param {string} name
12450
+ * Part name.
12451
+ * @returns {asserts part is string}
12452
+ * Nothing.
12453
+ */
12454
+ function assertNonEmpty(part, name) {
12455
+ if (!part) throw new Error("`" + name + "` cannot be empty");
12456
+ }
12457
+ /**
12458
+ * Assert `path` exists.
12459
+ *
12460
+ * @param {string | undefined} path
12461
+ * Path.
12462
+ * @param {string} name
12463
+ * Dependency name.
12464
+ * @returns {asserts path is string}
12465
+ * Nothing.
12466
+ */
12467
+ function assertPath(path, name) {
12468
+ if (!path) throw new Error("Setting `" + name + "` requires `path` to be set too");
12469
+ }
12470
+ /**
12471
+ * Assert `value` is an `Uint8Array`.
12472
+ *
12473
+ * @param {unknown} value
12474
+ * thing.
12475
+ * @returns {value is Uint8Array}
12476
+ * Whether `value` is an `Uint8Array`.
12477
+ */
12478
+ function isUint8Array(value) {
12479
+ return Boolean(value && typeof value === "object" && "byteLength" in value && "byteOffset" in value);
12480
+ }
12481
+ //#endregion
11628
12482
  //#region src/plugins/content/pipeline/frontmatter.ts
11629
12483
  /**
11630
12484
  * @file content pipeline — frontmatter parsing.
@@ -11795,7 +12649,7 @@ function parseDimensions(width, height) {
11795
12649
  * collectAttributes({ src: "x", poster: "/p.jpg", flag: null }); // { src: "x", poster: "/p.jpg" }
11796
12650
  * ```
11797
12651
  */
11798
- function collectAttributes(attributes) {
12652
+ function collectAttributes$1(attributes) {
11799
12653
  const out = {};
11800
12654
  for (const [key, value] of Object.entries(attributes ?? {})) if (typeof value === "string") out[key] = value;
11801
12655
  return out;
@@ -11868,7 +12722,7 @@ function embedTransform(facade, tree) {
11868
12722
  width: dimensions.width,
11869
12723
  height: dimensions.height
11870
12724
  } : {},
11871
- attributes: collectAttributes(node.attributes)
12725
+ attributes: collectAttributes$1(node.attributes)
11872
12726
  }, dimensions)
11873
12727
  };
11874
12728
  parent.children[index] = html;
@@ -11894,6 +12748,246 @@ function embedPlugin(options = {}) {
11894
12748
  return (tree) => embedTransform(facade, tree);
11895
12749
  }
11896
12750
  //#endregion
12751
+ //#region src/plugins/content/pipeline/gallery-default.tsx
12752
+ /** CSS class on the gallery's slide track. */
12753
+ const GALLERY_TRACK_CLASS = "gallery-track";
12754
+ /**
12755
+ * Default `::gallery` inner content: a single track holding every slide `<img>`
12756
+ * in folder order. A companion gallery island (consumer-provided) can enhance
12757
+ * the track with swipe/keyboard/lightbox; with no island and no CSS it is still
12758
+ * a plain horizontally-scrollable image strip. Provided as the default and as a
12759
+ * composable building block for custom galleries.
12760
+ *
12761
+ * @param props - The gallery props (the resolved `slides`).
12762
+ * @returns The gallery inner-content VNode.
12763
+ * @example
12764
+ * ```tsx
12765
+ * // Compose the default inside a richer custom gallery:
12766
+ * const MyGallery = (p: GalleryProps) => (
12767
+ * <figure><GalleryTrack {...p} /><figcaption>{p.caption}</figcaption></figure>
12768
+ * );
12769
+ * ```
12770
+ */
12771
+ function GalleryTrack(props) {
12772
+ return /* @__PURE__ */ jsx("div", {
12773
+ class: GALLERY_TRACK_CLASS,
12774
+ "data-gallery-track": true,
12775
+ children: props.slides.map((slide) => /* @__PURE__ */ jsx("img", {
12776
+ src: slide.src,
12777
+ alt: slide.alt
12778
+ }, slide.src))
12779
+ });
12780
+ }
12781
+ //#endregion
12782
+ //#region src/plugins/content/pipeline/gallery.ts
12783
+ /**
12784
+ * @file content pipeline — `::gallery` folder galleries.
12785
+ *
12786
+ * Rewrites `::gallery{src="./images/dir/" caption="…"}` leaf directives into a
12787
+ * static swipeable image set at the mdast stage (BEFORE the remark-rehype bridge):
12788
+ * a framework-owned `<div class="gallery" data-component="gallery">` carrying the
12789
+ * island hook, wrapping inner content rendered (at build time, to static markup)
12790
+ * by a Preact component — the built-in {@link GalleryTrack} by default, or a
12791
+ * consumer component via `gallery.component`.
12792
+ *
12793
+ * Unlike `::embed` (one src resolved later by the provider), a gallery's `src` is a
12794
+ * co-located FOLDER that must be listed at build time, which needs the article's
12795
+ * source path. The provider supplies the article `slug` via the VFile `data`
12796
+ * (providers.ts), and the `contentDir` is bound at pipeline-build time — so this
12797
+ * transform reads `<contentDir>/<slug>/<src>` from disk, sorts its images, and
12798
+ * resolves each to its shared `/<slug>/<dir>/<file>` URL (identical from every
12799
+ * locale page, mirroring co-located images). The companion gallery SPA island
12800
+ * (consumer-provided) wires swipe/keyboard/lightbox on `[data-component="gallery"]`.
12801
+ */
12802
+ /** CSS class on the `<div>` wrapping each gallery. */
12803
+ const GALLERY_WRAPPER_CLASS = "gallery";
12804
+ /** `data-component` name binding the gallery to its SPA island. */
12805
+ const GALLERY_COMPONENT_NAME = "gallery";
12806
+ /** Image file extensions a gallery folder expands over. */
12807
+ const IMAGE_EXTENSIONS = new Set([
12808
+ ".webp",
12809
+ ".jpg",
12810
+ ".jpeg",
12811
+ ".png",
12812
+ ".gif",
12813
+ ".avif"
12814
+ ]);
12815
+ /**
12816
+ * Type guard for a `::gallery` leaf directive.
12817
+ *
12818
+ * @param node - AST node to test.
12819
+ * @returns `true` when the node is a `::gallery` leaf directive.
12820
+ * @example
12821
+ * ```ts
12822
+ * if (isGalleryDirective(node)) console.log(node.attributes?.src);
12823
+ * ```
12824
+ */
12825
+ function isGalleryDirective(node) {
12826
+ return node.type === "leafDirective" && node.name === "gallery";
12827
+ }
12828
+ /**
12829
+ * Resolve `.`/`..` segments of a path built from `slug/src/file` into the single
12830
+ * shared absolute URL the content-assets build phase copies the folder to.
12831
+ *
12832
+ * @param slug - Article directory name.
12833
+ * @param src - The directive `src` (co-located relative folder, e.g. `./images/dir/`).
12834
+ * @param file - One image file name inside the folder.
12835
+ * @returns The shared absolute slide URL (`/<slug>/<dir>/<file>`).
12836
+ * @example
12837
+ * ```ts
12838
+ * slideUrl("post", "./images/mk/", "a.webp"); // "/post/images/mk/a.webp"
12839
+ * ```
12840
+ */
12841
+ function slideUrl(slug, src, file) {
12842
+ const resolved = [];
12843
+ for (const segment of `${slug}/${src}/${file}`.split("/")) {
12844
+ if (segment === "" || segment === ".") continue;
12845
+ if (segment === "..") resolved.pop();
12846
+ else resolved.push(segment);
12847
+ }
12848
+ return `/${resolved.join("/")}`;
12849
+ }
12850
+ /**
12851
+ * Read a gallery folder from disk and build its sorted slide list. Each slide
12852
+ * gets the directive `caption` plus a ` · N` index suffix as alt (or just `N`).
12853
+ *
12854
+ * @param contentDir - The provider's content directory.
12855
+ * @param slug - Article directory name (from the VFile data).
12856
+ * @param src - The directive `src` (co-located relative folder).
12857
+ * @param caption - The directive `caption` attribute (may be empty).
12858
+ * @returns The sorted slides.
12859
+ * @throws {Error} When the folder is missing or holds no images.
12860
+ * @example
12861
+ * ```ts
12862
+ * resolveSlides("./content", "post", "./images/mk/", "Our game");
12863
+ * ```
12864
+ */
12865
+ function resolveSlides(contentDir, slug, src, caption) {
12866
+ const folder = path.join(contentDir, slug, src);
12867
+ let entries;
12868
+ try {
12869
+ entries = readdirSync(folder);
12870
+ } catch {
12871
+ throw new Error(`[web] content: \`::gallery\` folder not found: "${src}" (looked in ${folder}).`);
12872
+ }
12873
+ const files = entries.filter((name) => IMAGE_EXTENSIONS.has(path.extname(name).toLowerCase())).toSorted((a, b) => a.localeCompare(b, "en"));
12874
+ if (files.length === 0) throw new Error(`[web] content: \`::gallery\` folder has no images: "${src}" (${folder}).`);
12875
+ return files.map((file, index) => ({
12876
+ src: slideUrl(slug, src, file),
12877
+ alt: caption ? `${caption} · ${index + 1}` : `${index + 1}`
12878
+ }));
12879
+ }
12880
+ /**
12881
+ * Collect the directive's raw attribute bag into a plain string record, dropping
12882
+ * `null`/`undefined` values (so a custom component can read arbitrary extra options).
12883
+ *
12884
+ * @param attributes - The raw directive attributes (or undefined).
12885
+ * @returns A string-valued attribute record.
12886
+ * @example
12887
+ * ```ts
12888
+ * collectAttributes({ src: "x", layout: "dots", flag: null }); // { src: "x", layout: "dots" }
12889
+ * ```
12890
+ */
12891
+ function collectAttributes(attributes) {
12892
+ const out = {};
12893
+ for (const [key, value] of Object.entries(attributes ?? {})) if (typeof value === "string") out[key] = value;
12894
+ return out;
12895
+ }
12896
+ /**
12897
+ * Build the static gallery HTML for one directive: the framework-owned `<div>`
12898
+ * (island hook in `data-component`) wrapping the component's inner content, SSR'd
12899
+ * to static markup.
12900
+ *
12901
+ * @param component - The gallery component (default {@link GalleryTrack}).
12902
+ * @param slides - The resolved slides.
12903
+ * @param caption - The directive `caption` attribute.
12904
+ * @param attributes - The raw directive attribute bag.
12905
+ * @returns The gallery HTML string.
12906
+ * @example
12907
+ * ```ts
12908
+ * galleryHtml(GalleryTrack, slides, "Our game", { src: "./images/mk/" });
12909
+ * ```
12910
+ */
12911
+ function galleryHtml(component, slides, caption, attributes) {
12912
+ return `<div class="${GALLERY_WRAPPER_CLASS}" data-component="${GALLERY_COMPONENT_NAME}">${renderToString(h(component, {
12913
+ slides,
12914
+ caption,
12915
+ attributes
12916
+ }))}</div>`;
12917
+ }
12918
+ /**
12919
+ * Mdast transformer rewriting every `::gallery` leaf directive to its gallery
12920
+ * HTML node. A directive missing `src`, or pointing at a missing/empty folder,
12921
+ * fails the build with the offending value quoted. Skipped entirely when the
12922
+ * VFile carries no `slug` (the standalone `render()` path has no article context).
12923
+ *
12924
+ * @param options - Resolved transform options (component + contentDir).
12925
+ * @param tree - The mdast tree to mutate.
12926
+ * @param file - The VFile (its `data.slug` locates the article on disk).
12927
+ * @throws {Error} When a `::gallery` directive is missing `src`, or its folder is
12928
+ * missing/empty.
12929
+ * @example
12930
+ * ```ts
12931
+ * galleryTransform({ component: GalleryTrack, contentDir: "./content" }, tree, file);
12932
+ * ```
12933
+ */
12934
+ function galleryTransform(options, tree, file) {
12935
+ const slug = typeof file.data.slug === "string" ? file.data.slug : void 0;
12936
+ const component = options.component ?? GalleryTrack;
12937
+ visit(tree, (node, index, parent) => {
12938
+ if (!isGalleryDirective(node)) return;
12939
+ if (parent === void 0 || index === void 0) return;
12940
+ if (slug === void 0) return;
12941
+ const src = node.attributes?.src ?? "";
12942
+ if (src === "") throw new Error("[web] content: `::gallery` requires a `src` folder, e.g. ::gallery{src=\"./images/dir/\"}.");
12943
+ const caption = node.attributes?.caption ?? "";
12944
+ const html = {
12945
+ type: "html",
12946
+ value: galleryHtml(component, resolveSlides(options.contentDir, slug, src, caption), caption, collectAttributes(node.attributes))
12947
+ };
12948
+ parent.children[index] = html;
12949
+ });
12950
+ }
12951
+ /**
12952
+ * Normalize the provider's `gallery` config value (`boolean | options`) plus the
12953
+ * provider `contentDir` into the resolved {@link GalleryTransformOptions} the
12954
+ * transform factory needs.
12955
+ *
12956
+ * @param gallery - The raw `FileSystemContentOptions.gallery` value (truthy).
12957
+ * @param contentDir - The provider's content directory.
12958
+ * @returns The resolved transform options.
12959
+ * @example
12960
+ * ```ts
12961
+ * normalizeGalleryOptions(true, "./content"); // { contentDir: "./content" }
12962
+ * normalizeGalleryOptions({ component: MyGallery }, "./content");
12963
+ * ```
12964
+ */
12965
+ function normalizeGalleryOptions(gallery, contentDir) {
12966
+ return typeof gallery === "boolean" ? { contentDir } : {
12967
+ ...gallery,
12968
+ contentDir
12969
+ };
12970
+ }
12971
+ /**
12972
+ * Remark transform factory: rewrites `::gallery{src="…"}` leaf directives into
12973
+ * static swipeable galleries (see the file header). Opt-in via the provider's
12974
+ * `gallery` option; requires `trustedContent: true` because the markup is raw HTML
12975
+ * the sanitize pass would strip. The inner content is rendered by
12976
+ * `options.component` (a consumer Preact component) or the built-in
12977
+ * {@link GalleryTrack}; folders are read from `options.contentDir` against the
12978
+ * per-article `slug` on the VFile.
12979
+ *
12980
+ * @param options - Resolved transform options (component + contentDir).
12981
+ * @returns An mdast tree transformer.
12982
+ * @example
12983
+ * ```ts
12984
+ * unified().use(galleryPlugin, { component: MyGallery, contentDir: "./content" });
12985
+ * ```
12986
+ */
12987
+ function galleryPlugin(options) {
12988
+ return (tree, file) => galleryTransform(options, tree, file);
12989
+ }
12990
+ //#endregion
11897
12991
  //#region src/plugins/content/pipeline/mermaid.ts
11898
12992
  /** CSS class on the `<figure>` wrapper around each rendered diagram. */
11899
12993
  const MERMAID_FIGURE_CLASS = "mermaid-diagram";
@@ -12203,6 +13297,7 @@ function defaultRemarkPlugins(config) {
12203
13297
  pullQuotePlugin
12204
13298
  ];
12205
13299
  if (config?.embed) plugins.push([embedPlugin, normalizeEmbedOptions(config.embed)]);
13300
+ if (config?.gallery) plugins.push([galleryPlugin, normalizeGalleryOptions(config.gallery, config.contentDir)]);
12206
13301
  if (config?.mermaid) plugins.push([remarkMermaidDiagrams, normalizeMermaidOptions(config.mermaid)]);
12207
13302
  plugins.push([remarkRehype, { allowDangerousHtml: true }]);
12208
13303
  return plugins;
@@ -12604,7 +13699,10 @@ function fileSystemContent(options) {
12604
13699
  state.dirtyPaths.delete(filePath);
12605
13700
  const { frontmatter, body } = parseFrontmatter(raw, options);
12606
13701
  const processor = ensureProcessor(state, options);
12607
- const html = rewriteEmbedUrls(rewriteImageUrls(String(await processor.process(body)), slug), slug);
13702
+ const html = rewriteEmbedUrls(rewriteImageUrls(String(await processor.process(new VFile({
13703
+ value: body,
13704
+ data: { slug }
13705
+ }))), slug), slug);
12608
13706
  const { readingTime, wordCount } = calculateReadingTime(body);
12609
13707
  return {
12610
13708
  frontmatter,
@@ -12728,4 +13826,4 @@ const createApp = core.createApp;
12728
13826
  */
12729
13827
  const createPlugin = core.createPlugin;
12730
13828
  //#endregion
12731
- export { types_exports as Build, types_exports$1 as Cli, types_exports$2 as Content, types_exports$3 as Data, types_exports$4 as Deploy, EmbedFacadeButton, types_exports$5 as Env, types_exports$6 as Head, types_exports$7 as Log, types_exports$8 as Router, types_exports$9 as Spa, browserEnv, buildArticleHead, buildPlugin, canonical, cliPlugin, cloudflareBindings, contentPlugin, createApp, createComponent, createPlugin, createUrls, dataPlugin, defineRoutes, deployPlugin, dotenv, envPlugin, feedLink, fileSystemContent, headPlugin, hreflang, i18nPlugin, jsonLd, lazyEmbed, logPlugin, meta, og, processEnv, route, routerPlugin, sitePlugin, spaPlugin, twitter };
13829
+ export { types_exports as Build, types_exports$1 as Cli, types_exports$2 as Content, types_exports$3 as Data, types_exports$4 as Deploy, EmbedFacadeButton, types_exports$5 as Env, GalleryTrack, types_exports$6 as Head, types_exports$7 as Log, types_exports$8 as Router, types_exports$9 as Spa, browserEnv, buildArticleHead, buildPlugin, canonical, cliPlugin, cloudflareBindings, contentPlugin, createApp, createComponent, createPlugin, createUrls, dataPlugin, defineRoutes, deployPlugin, dotenv, envPlugin, feedLink, fileSystemContent, headPlugin, hreflang, i18nPlugin, jsonLd, lazyEmbed, logPlugin, meta, og, processEnv, route, routerPlugin, sitePlugin, spaPlugin, twitter };