@wdprlib/parser 4.2.0 → 4.3.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/README.md CHANGED
@@ -43,7 +43,7 @@ const resolved = await resolveModules(ast, {
43
43
  // value — look it up with a parameterized query and return the normalized name
44
44
  const found = category ? await findCategory(category) : null // your lookup
45
45
  if (category && !found) return { status: 'category-not-found', category }
46
- return { status: 'ok', tags: [{ tag: 'scp', weight: 42 }], category: found?.unixName ?? null }
46
+ return { status: 'ok', tags: [{ tag: 'apple', weight: 42 }], category: found?.unixName ?? null }
47
47
  },
48
48
  getPageTags: () => ['tag1', 'tag2'],
49
49
  }, {
@@ -58,6 +58,8 @@ const resolved = await resolveModules(ast, {
58
58
  - Wikidot markup parsing (bold, italic, links, images, tables, etc.)
59
59
  - Include resolution (`[[include page]]`)
60
60
  - Module support (ListPages, ListUsers, TagCloud, IfTags, etc.)
61
+ - `[[gallery]]` (auto-collection uses the page's attachments passed to `@wdprlib/render` as `page.files`; lightbox via `@wdprlib/runtime`)
62
+
61
63
  - Data extraction for server-side rendering
62
64
 
63
65
  ## Related Packages
package/dist/index.cjs CHANGED
@@ -9318,6 +9318,177 @@ var bibliographyRule = {
9318
9318
  }
9319
9319
  };
9320
9320
 
9321
+ // packages/parser/src/parser/rules/block/gallery/items.ts
9322
+ function parseGalleryItemLine(content) {
9323
+ const spacePos = content.indexOf(" ");
9324
+ let source = spacePos < 0 ? content : content.slice(0, spacePos);
9325
+ const attrText = spacePos < 0 ? "" : content.slice(spacePos + 1);
9326
+ let newWindow = false;
9327
+ if (source.startsWith("*")) {
9328
+ source = source.slice(1);
9329
+ newWindow = true;
9330
+ }
9331
+ const attrs = parseItemAttrs(attrText);
9332
+ let link = attrs.get("link") ?? null;
9333
+ if (link !== null && link.startsWith("*")) {
9334
+ newWindow = true;
9335
+ link = link.slice(1);
9336
+ }
9337
+ const alt = attrs.get("alt") ?? null;
9338
+ return { source, link, alt, newWindow };
9339
+ }
9340
+ function parseItemAttrs(text) {
9341
+ const attrs = new Map;
9342
+ const parts = text.trim().split('="');
9343
+ let key = parts[0]?.trim() ?? "";
9344
+ for (let i = 1;i < parts.length; i++) {
9345
+ const val = parts[i] ?? "";
9346
+ const quotePos = val.lastIndexOf('"');
9347
+ if (quotePos < 0) {
9348
+ attrs.set(key, "");
9349
+ key = val.slice(1).trim();
9350
+ } else {
9351
+ attrs.set(key, stripslashes(val.slice(0, quotePos)));
9352
+ key = val.slice(quotePos + 1).trim();
9353
+ }
9354
+ }
9355
+ return attrs;
9356
+ }
9357
+ function stripslashes(value) {
9358
+ return value.replace(/\\(.)/gs, "$1").replace(/\\$/, "");
9359
+ }
9360
+
9361
+ // packages/parser/src/parser/rules/block/gallery/index.ts
9362
+ var GALLERY_SIZES = ["small", "medium", "thumbnail", "square", "original"];
9363
+ function normalizeSize(value) {
9364
+ return value !== undefined && GALLERY_SIZES.includes(value) ? value : "thumbnail";
9365
+ }
9366
+ function normalizeViewer(value) {
9367
+ return value !== "no" && value !== "false";
9368
+ }
9369
+ function normalizeOrder(value) {
9370
+ switch (value) {
9371
+ case "name":
9372
+ case "name desc":
9373
+ case "created_at":
9374
+ case "created_at desc":
9375
+ return value;
9376
+ case "nameDesc":
9377
+ return "name desc";
9378
+ case "dateAdded":
9379
+ return "created_at";
9380
+ case "dateAddedDesc":
9381
+ return "created_at desc";
9382
+ case "name desc desc":
9383
+ return "name";
9384
+ case "created_at desc desc":
9385
+ return "created_at";
9386
+ default:
9387
+ return "name";
9388
+ }
9389
+ }
9390
+ function tryParseGalleryContent(ctx, pos) {
9391
+ const start = pos;
9392
+ if (ctx.tokens[pos]?.type !== "NEWLINE") {
9393
+ return null;
9394
+ }
9395
+ const lines = [];
9396
+ let p = pos + 1;
9397
+ for (;; ) {
9398
+ const colon = ctx.tokens[p];
9399
+ if (colon?.type !== "COLON" || !colon.lineStart) {
9400
+ break;
9401
+ }
9402
+ const space = ctx.tokens[p + 1];
9403
+ if (space?.type !== "WHITESPACE" || !space.value.startsWith(" ")) {
9404
+ break;
9405
+ }
9406
+ let content = "";
9407
+ let q = p + 1;
9408
+ while (q < ctx.tokens.length) {
9409
+ const token5 = ctx.tokens[q];
9410
+ if (!token5 || token5.type === "NEWLINE" || token5.type === "EOF") {
9411
+ break;
9412
+ }
9413
+ content += token5.value;
9414
+ q++;
9415
+ }
9416
+ if (content === " ") {
9417
+ return null;
9418
+ }
9419
+ if (ctx.tokens[q]?.type !== "NEWLINE") {
9420
+ return null;
9421
+ }
9422
+ lines.push(content.trim());
9423
+ p = q + 1;
9424
+ }
9425
+ if (lines.length === 0) {
9426
+ return null;
9427
+ }
9428
+ if (ctx.tokens[p]?.type !== "BLOCK_END_OPEN") {
9429
+ return null;
9430
+ }
9431
+ const nameResult = parseBlockName(ctx, p + 1);
9432
+ if (!nameResult || nameResult.name !== "gallery") {
9433
+ return null;
9434
+ }
9435
+ const closePos = p + 1 + nameResult.consumed;
9436
+ if (ctx.tokens[closePos]?.type !== "BLOCK_CLOSE") {
9437
+ return null;
9438
+ }
9439
+ return { lines, consumed: closePos + 1 - start };
9440
+ }
9441
+ var galleryRule = {
9442
+ name: "gallery",
9443
+ startTokens: ["BLOCK_OPEN"],
9444
+ requiresLineStart: true,
9445
+ parse(ctx) {
9446
+ if (ctx.tokens[ctx.pos]?.type !== "BLOCK_OPEN") {
9447
+ return { success: false };
9448
+ }
9449
+ let pos = ctx.pos + 1;
9450
+ const nameResult = parseBlockName(ctx, pos);
9451
+ if (!nameResult || nameResult.name !== "gallery") {
9452
+ return { success: false };
9453
+ }
9454
+ pos += nameResult.consumed;
9455
+ const attrResult = parseAttributesRaw(ctx, pos);
9456
+ pos += attrResult.consumed;
9457
+ if (ctx.tokens[pos]?.type !== "BLOCK_CLOSE") {
9458
+ return { success: false };
9459
+ }
9460
+ pos++;
9461
+ const size = normalizeSize(attrResult.attrs.size);
9462
+ const viewer = normalizeViewer(attrResult.attrs.viewer);
9463
+ const order = normalizeOrder(attrResult.attrs.order);
9464
+ const openConsumed = pos - ctx.pos;
9465
+ const content = tryParseGalleryContent(ctx, pos);
9466
+ if (!content) {
9467
+ return {
9468
+ success: true,
9469
+ elements: [
9470
+ {
9471
+ element: "gallery",
9472
+ data: { size, order, viewer, content: { type: "auto", files: null } }
9473
+ }
9474
+ ],
9475
+ consumed: openConsumed
9476
+ };
9477
+ }
9478
+ const items = content.lines.map(parseGalleryItemLine);
9479
+ return {
9480
+ success: true,
9481
+ elements: [
9482
+ {
9483
+ element: "gallery",
9484
+ data: { size, order, viewer, content: { type: "items", items } }
9485
+ }
9486
+ ],
9487
+ consumed: openConsumed + content.consumed
9488
+ };
9489
+ }
9490
+ };
9491
+
9321
9492
  // packages/parser/src/parser/rules/block/index.ts
9322
9493
  var blockRules = [
9323
9494
  blockCommentRule,
@@ -9347,6 +9518,7 @@ var blockRules = [
9347
9518
  iframeRule,
9348
9519
  iftagsRule,
9349
9520
  bibliographyRule,
9521
+ galleryRule,
9350
9522
  divRule
9351
9523
  ];
9352
9524
  // packages/parser/src/parser/rules/inline/formatting/container.ts
package/dist/index.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- import { Position as Position2, Point, Version as Version2, Element as Element8, SyntaxTree as SyntaxTree3, ContainerType, ContainerData, AttributeMap, VariableMap, Alignment, LinkType, LinkLocation, LinkLabel, PageRef as PageRef3, ImageSource, FloatAlignment, ListType, ListItem, ListData, CodeBlockData as CodeBlockData2, TabData, TableCell, TableRow, TableData, DefinitionListItem, Module as Module5, CollapsibleData, ClearFloat, AnchorTarget, HeaderType, AlignType, HeadingLevel, Heading, DateItem, Embed, TocEntry as TocEntry2, Diagnostic as Diagnostic2, DiagnosticSeverity, ParseResult as ParseResult3 } from "@wdprlib/ast";
1
+ import { Position as Position2, Point, Version as Version2, Element as Element8, SyntaxTree as SyntaxTree3, ContainerType, ContainerData, AttributeMap, VariableMap, Alignment, LinkType, LinkLocation, LinkLabel, PageRef as PageRef3, ImageSource, FloatAlignment, ListType, ListItem, ListData, CodeBlockData as CodeBlockData2, TabData, TableCell, TableRow, TableData, DefinitionListItem, Module as Module5, CollapsibleData, ClearFloat, AnchorTarget, HeaderType, AlignType, HeadingLevel, Heading, DateItem, Embed, TocEntry as TocEntry2, GallerySize, GalleryOrder, GalleryItem, GalleryContent, GalleryData, Diagnostic as Diagnostic2, DiagnosticSeverity, ParseResult as ParseResult3 } from "@wdprlib/ast";
2
2
  import { createPoint, createPosition, text, container, paragraph, bold, italics, heading, lineBreak, horizontalRule, link, list, listItemElements, listItemSubList } from "@wdprlib/ast";
3
3
  import { WikitextMode, WikitextSettings as WikitextSettings4 } from "@wdprlib/ast";
4
4
  import { createSettings, DEFAULT_SETTINGS } from "@wdprlib/ast";
@@ -1155,4 +1155,4 @@ interface ResolveOptions {
1155
1155
  */
1156
1156
  declare function resolveModules(ast: SyntaxTree2, dataProvider: DataProvider, options: ResolveOptions): Promise<SyntaxTree2>;
1157
1157
  import { STYLE_SLOT_PREFIX } from "@wdprlib/ast";
1158
- export { tokenize, text, resolveTagCloud, resolveModules, resolveListUsers, resolveIncludesWithTrace, resolveIncludesAsync, resolveIncludes, preprocessIftags, parseTags, parseParent, parseOrder, parseNumericSelector, parseDateSelector, parseCategory, parse, paragraph, normalizeQuery, listItemSubList, listItemElements, list, link, lineBreak, italics, isTagCloudModule, isListUsersModule, horizontalRule, heading, extractListUsersVariables, extractIncludeReferences, extractDataRequirements, createToken, createSettings, createPosition, createPoint, container, compileTemplate, compileListUsersTemplate, bold, WikitextSettings4 as WikitextSettings, WikitextMode, Version2 as Version, VariableMap, VariableContext, UserInfo, TokenType, Token, TocEntry2 as TocEntry, TagCloudTagData, TagCloudModuleData, TagCloudExternalData, TagCloudDataRequirement, TagCloudDataFetcher, TableRow, TableData, TableCell, TabData, SyntaxTree3 as SyntaxTree, SiteContext, STYLE_SLOT_PREFIX, ResolveOptions, ResolveIncludesTraceResult, ResolveIncludesOptions, Position2 as Position, Point, ParserOptions, Parser, ParseResult3 as ParseResult, ParseFunction, PageRef3 as PageRef, PageData, NormalizedTags, NormalizedParent, NormalizedOrder, NormalizedNumericSelector, NormalizedListPagesQuery, NormalizedDateSelector, NormalizedCategory, ModuleSourceTransform, Module5 as Module, ListUsersVariableContext, ListUsersVariable, ListUsersUserData, ListUsersExternalData, ListUsersDataRequirement, ListUsersDataFetcher, ListUsersCompiledTemplate, ListType, ListPagesVariable, ListPagesQuery, ListPagesExternalData, ListPagesDataRequirement, ListPagesDataFetcher, ListItem, ListData, LinkType, LinkLocation, LinkLabel, LexerOptions, Lexer, IncludeReference, IncludeIterationTrace, IncludeFetcher, IncludeDependency, ImageSource, HeadingLevel, Heading, HeaderType, FloatAlignment, ExtractionResult, Embed, Element8 as Element, DiagnosticSeverity, Diagnostic2 as Diagnostic, DefinitionListItem, DateItem, DataRequirements, DataProvider, DEFAULT_SETTINGS, ContainerType, ContainerData, CompiledTemplate, CollapsibleData, CodeBlockData2 as CodeBlockData, ClearFloat, AttributeMap, AsyncIncludeFetcher, AnchorTarget, Alignment, AlignType };
1158
+ export { tokenize, text, resolveTagCloud, resolveModules, resolveListUsers, resolveIncludesWithTrace, resolveIncludesAsync, resolveIncludes, preprocessIftags, parseTags, parseParent, parseOrder, parseNumericSelector, parseDateSelector, parseCategory, parse, paragraph, normalizeQuery, listItemSubList, listItemElements, list, link, lineBreak, italics, isTagCloudModule, isListUsersModule, horizontalRule, heading, extractListUsersVariables, extractIncludeReferences, extractDataRequirements, createToken, createSettings, createPosition, createPoint, container, compileTemplate, compileListUsersTemplate, bold, WikitextSettings4 as WikitextSettings, WikitextMode, Version2 as Version, VariableMap, VariableContext, UserInfo, TokenType, Token, TocEntry2 as TocEntry, TagCloudTagData, TagCloudModuleData, TagCloudExternalData, TagCloudDataRequirement, TagCloudDataFetcher, TableRow, TableData, TableCell, TabData, SyntaxTree3 as SyntaxTree, SiteContext, STYLE_SLOT_PREFIX, ResolveOptions, ResolveIncludesTraceResult, ResolveIncludesOptions, Position2 as Position, Point, ParserOptions, Parser, ParseResult3 as ParseResult, ParseFunction, PageRef3 as PageRef, PageData, NormalizedTags, NormalizedParent, NormalizedOrder, NormalizedNumericSelector, NormalizedListPagesQuery, NormalizedDateSelector, NormalizedCategory, ModuleSourceTransform, Module5 as Module, ListUsersVariableContext, ListUsersVariable, ListUsersUserData, ListUsersExternalData, ListUsersDataRequirement, ListUsersDataFetcher, ListUsersCompiledTemplate, ListType, ListPagesVariable, ListPagesQuery, ListPagesExternalData, ListPagesDataRequirement, ListPagesDataFetcher, ListItem, ListData, LinkType, LinkLocation, LinkLabel, LexerOptions, Lexer, IncludeReference, IncludeIterationTrace, IncludeFetcher, IncludeDependency, ImageSource, HeadingLevel, Heading, HeaderType, GallerySize, GalleryOrder, GalleryItem, GalleryData, GalleryContent, FloatAlignment, ExtractionResult, Embed, Element8 as Element, DiagnosticSeverity, Diagnostic2 as Diagnostic, DefinitionListItem, DateItem, DataRequirements, DataProvider, DEFAULT_SETTINGS, ContainerType, ContainerData, CompiledTemplate, CollapsibleData, CodeBlockData2 as CodeBlockData, ClearFloat, AttributeMap, AsyncIncludeFetcher, AnchorTarget, Alignment, AlignType };
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { Position as Position2, Point, Version as Version2, Element as Element8, SyntaxTree as SyntaxTree3, ContainerType, ContainerData, AttributeMap, VariableMap, Alignment, LinkType, LinkLocation, LinkLabel, PageRef as PageRef3, ImageSource, FloatAlignment, ListType, ListItem, ListData, CodeBlockData as CodeBlockData2, TabData, TableCell, TableRow, TableData, DefinitionListItem, Module as Module5, CollapsibleData, ClearFloat, AnchorTarget, HeaderType, AlignType, HeadingLevel, Heading, DateItem, Embed, TocEntry as TocEntry2, Diagnostic as Diagnostic2, DiagnosticSeverity, ParseResult as ParseResult3 } from "@wdprlib/ast";
1
+ import { Position as Position2, Point, Version as Version2, Element as Element8, SyntaxTree as SyntaxTree3, ContainerType, ContainerData, AttributeMap, VariableMap, Alignment, LinkType, LinkLocation, LinkLabel, PageRef as PageRef3, ImageSource, FloatAlignment, ListType, ListItem, ListData, CodeBlockData as CodeBlockData2, TabData, TableCell, TableRow, TableData, DefinitionListItem, Module as Module5, CollapsibleData, ClearFloat, AnchorTarget, HeaderType, AlignType, HeadingLevel, Heading, DateItem, Embed, TocEntry as TocEntry2, GallerySize, GalleryOrder, GalleryItem, GalleryContent, GalleryData, Diagnostic as Diagnostic2, DiagnosticSeverity, ParseResult as ParseResult3 } from "@wdprlib/ast";
2
2
  import { createPoint, createPosition, text, container, paragraph, bold, italics, heading, lineBreak, horizontalRule, link, list, listItemElements, listItemSubList } from "@wdprlib/ast";
3
3
  import { WikitextMode, WikitextSettings as WikitextSettings4 } from "@wdprlib/ast";
4
4
  import { createSettings, DEFAULT_SETTINGS } from "@wdprlib/ast";
@@ -1155,4 +1155,4 @@ interface ResolveOptions {
1155
1155
  */
1156
1156
  declare function resolveModules(ast: SyntaxTree2, dataProvider: DataProvider, options: ResolveOptions): Promise<SyntaxTree2>;
1157
1157
  import { STYLE_SLOT_PREFIX } from "@wdprlib/ast";
1158
- export { tokenize, text, resolveTagCloud, resolveModules, resolveListUsers, resolveIncludesWithTrace, resolveIncludesAsync, resolveIncludes, preprocessIftags, parseTags, parseParent, parseOrder, parseNumericSelector, parseDateSelector, parseCategory, parse, paragraph, normalizeQuery, listItemSubList, listItemElements, list, link, lineBreak, italics, isTagCloudModule, isListUsersModule, horizontalRule, heading, extractListUsersVariables, extractIncludeReferences, extractDataRequirements, createToken, createSettings, createPosition, createPoint, container, compileTemplate, compileListUsersTemplate, bold, WikitextSettings4 as WikitextSettings, WikitextMode, Version2 as Version, VariableMap, VariableContext, UserInfo, TokenType, Token, TocEntry2 as TocEntry, TagCloudTagData, TagCloudModuleData, TagCloudExternalData, TagCloudDataRequirement, TagCloudDataFetcher, TableRow, TableData, TableCell, TabData, SyntaxTree3 as SyntaxTree, SiteContext, STYLE_SLOT_PREFIX, ResolveOptions, ResolveIncludesTraceResult, ResolveIncludesOptions, Position2 as Position, Point, ParserOptions, Parser, ParseResult3 as ParseResult, ParseFunction, PageRef3 as PageRef, PageData, NormalizedTags, NormalizedParent, NormalizedOrder, NormalizedNumericSelector, NormalizedListPagesQuery, NormalizedDateSelector, NormalizedCategory, ModuleSourceTransform, Module5 as Module, ListUsersVariableContext, ListUsersVariable, ListUsersUserData, ListUsersExternalData, ListUsersDataRequirement, ListUsersDataFetcher, ListUsersCompiledTemplate, ListType, ListPagesVariable, ListPagesQuery, ListPagesExternalData, ListPagesDataRequirement, ListPagesDataFetcher, ListItem, ListData, LinkType, LinkLocation, LinkLabel, LexerOptions, Lexer, IncludeReference, IncludeIterationTrace, IncludeFetcher, IncludeDependency, ImageSource, HeadingLevel, Heading, HeaderType, FloatAlignment, ExtractionResult, Embed, Element8 as Element, DiagnosticSeverity, Diagnostic2 as Diagnostic, DefinitionListItem, DateItem, DataRequirements, DataProvider, DEFAULT_SETTINGS, ContainerType, ContainerData, CompiledTemplate, CollapsibleData, CodeBlockData2 as CodeBlockData, ClearFloat, AttributeMap, AsyncIncludeFetcher, AnchorTarget, Alignment, AlignType };
1158
+ export { tokenize, text, resolveTagCloud, resolveModules, resolveListUsers, resolveIncludesWithTrace, resolveIncludesAsync, resolveIncludes, preprocessIftags, parseTags, parseParent, parseOrder, parseNumericSelector, parseDateSelector, parseCategory, parse, paragraph, normalizeQuery, listItemSubList, listItemElements, list, link, lineBreak, italics, isTagCloudModule, isListUsersModule, horizontalRule, heading, extractListUsersVariables, extractIncludeReferences, extractDataRequirements, createToken, createSettings, createPosition, createPoint, container, compileTemplate, compileListUsersTemplate, bold, WikitextSettings4 as WikitextSettings, WikitextMode, Version2 as Version, VariableMap, VariableContext, UserInfo, TokenType, Token, TocEntry2 as TocEntry, TagCloudTagData, TagCloudModuleData, TagCloudExternalData, TagCloudDataRequirement, TagCloudDataFetcher, TableRow, TableData, TableCell, TabData, SyntaxTree3 as SyntaxTree, SiteContext, STYLE_SLOT_PREFIX, ResolveOptions, ResolveIncludesTraceResult, ResolveIncludesOptions, Position2 as Position, Point, ParserOptions, Parser, ParseResult3 as ParseResult, ParseFunction, PageRef3 as PageRef, PageData, NormalizedTags, NormalizedParent, NormalizedOrder, NormalizedNumericSelector, NormalizedListPagesQuery, NormalizedDateSelector, NormalizedCategory, ModuleSourceTransform, Module5 as Module, ListUsersVariableContext, ListUsersVariable, ListUsersUserData, ListUsersExternalData, ListUsersDataRequirement, ListUsersDataFetcher, ListUsersCompiledTemplate, ListType, ListPagesVariable, ListPagesQuery, ListPagesExternalData, ListPagesDataRequirement, ListPagesDataFetcher, ListItem, ListData, LinkType, LinkLocation, LinkLabel, LexerOptions, Lexer, IncludeReference, IncludeIterationTrace, IncludeFetcher, IncludeDependency, ImageSource, HeadingLevel, Heading, HeaderType, GallerySize, GalleryOrder, GalleryItem, GalleryData, GalleryContent, FloatAlignment, ExtractionResult, Embed, Element8 as Element, DiagnosticSeverity, Diagnostic2 as Diagnostic, DefinitionListItem, DateItem, DataRequirements, DataProvider, DEFAULT_SETTINGS, ContainerType, ContainerData, CompiledTemplate, CollapsibleData, CodeBlockData2 as CodeBlockData, ClearFloat, AttributeMap, AsyncIncludeFetcher, AnchorTarget, Alignment, AlignType };
package/dist/index.js CHANGED
@@ -9257,6 +9257,177 @@ var bibliographyRule = {
9257
9257
  }
9258
9258
  };
9259
9259
 
9260
+ // packages/parser/src/parser/rules/block/gallery/items.ts
9261
+ function parseGalleryItemLine(content) {
9262
+ const spacePos = content.indexOf(" ");
9263
+ let source = spacePos < 0 ? content : content.slice(0, spacePos);
9264
+ const attrText = spacePos < 0 ? "" : content.slice(spacePos + 1);
9265
+ let newWindow = false;
9266
+ if (source.startsWith("*")) {
9267
+ source = source.slice(1);
9268
+ newWindow = true;
9269
+ }
9270
+ const attrs = parseItemAttrs(attrText);
9271
+ let link = attrs.get("link") ?? null;
9272
+ if (link !== null && link.startsWith("*")) {
9273
+ newWindow = true;
9274
+ link = link.slice(1);
9275
+ }
9276
+ const alt = attrs.get("alt") ?? null;
9277
+ return { source, link, alt, newWindow };
9278
+ }
9279
+ function parseItemAttrs(text) {
9280
+ const attrs = new Map;
9281
+ const parts = text.trim().split('="');
9282
+ let key = parts[0]?.trim() ?? "";
9283
+ for (let i = 1;i < parts.length; i++) {
9284
+ const val = parts[i] ?? "";
9285
+ const quotePos = val.lastIndexOf('"');
9286
+ if (quotePos < 0) {
9287
+ attrs.set(key, "");
9288
+ key = val.slice(1).trim();
9289
+ } else {
9290
+ attrs.set(key, stripslashes(val.slice(0, quotePos)));
9291
+ key = val.slice(quotePos + 1).trim();
9292
+ }
9293
+ }
9294
+ return attrs;
9295
+ }
9296
+ function stripslashes(value) {
9297
+ return value.replace(/\\(.)/gs, "$1").replace(/\\$/, "");
9298
+ }
9299
+
9300
+ // packages/parser/src/parser/rules/block/gallery/index.ts
9301
+ var GALLERY_SIZES = ["small", "medium", "thumbnail", "square", "original"];
9302
+ function normalizeSize(value) {
9303
+ return value !== undefined && GALLERY_SIZES.includes(value) ? value : "thumbnail";
9304
+ }
9305
+ function normalizeViewer(value) {
9306
+ return value !== "no" && value !== "false";
9307
+ }
9308
+ function normalizeOrder(value) {
9309
+ switch (value) {
9310
+ case "name":
9311
+ case "name desc":
9312
+ case "created_at":
9313
+ case "created_at desc":
9314
+ return value;
9315
+ case "nameDesc":
9316
+ return "name desc";
9317
+ case "dateAdded":
9318
+ return "created_at";
9319
+ case "dateAddedDesc":
9320
+ return "created_at desc";
9321
+ case "name desc desc":
9322
+ return "name";
9323
+ case "created_at desc desc":
9324
+ return "created_at";
9325
+ default:
9326
+ return "name";
9327
+ }
9328
+ }
9329
+ function tryParseGalleryContent(ctx, pos) {
9330
+ const start = pos;
9331
+ if (ctx.tokens[pos]?.type !== "NEWLINE") {
9332
+ return null;
9333
+ }
9334
+ const lines = [];
9335
+ let p = pos + 1;
9336
+ for (;; ) {
9337
+ const colon = ctx.tokens[p];
9338
+ if (colon?.type !== "COLON" || !colon.lineStart) {
9339
+ break;
9340
+ }
9341
+ const space = ctx.tokens[p + 1];
9342
+ if (space?.type !== "WHITESPACE" || !space.value.startsWith(" ")) {
9343
+ break;
9344
+ }
9345
+ let content = "";
9346
+ let q = p + 1;
9347
+ while (q < ctx.tokens.length) {
9348
+ const token5 = ctx.tokens[q];
9349
+ if (!token5 || token5.type === "NEWLINE" || token5.type === "EOF") {
9350
+ break;
9351
+ }
9352
+ content += token5.value;
9353
+ q++;
9354
+ }
9355
+ if (content === " ") {
9356
+ return null;
9357
+ }
9358
+ if (ctx.tokens[q]?.type !== "NEWLINE") {
9359
+ return null;
9360
+ }
9361
+ lines.push(content.trim());
9362
+ p = q + 1;
9363
+ }
9364
+ if (lines.length === 0) {
9365
+ return null;
9366
+ }
9367
+ if (ctx.tokens[p]?.type !== "BLOCK_END_OPEN") {
9368
+ return null;
9369
+ }
9370
+ const nameResult = parseBlockName(ctx, p + 1);
9371
+ if (!nameResult || nameResult.name !== "gallery") {
9372
+ return null;
9373
+ }
9374
+ const closePos = p + 1 + nameResult.consumed;
9375
+ if (ctx.tokens[closePos]?.type !== "BLOCK_CLOSE") {
9376
+ return null;
9377
+ }
9378
+ return { lines, consumed: closePos + 1 - start };
9379
+ }
9380
+ var galleryRule = {
9381
+ name: "gallery",
9382
+ startTokens: ["BLOCK_OPEN"],
9383
+ requiresLineStart: true,
9384
+ parse(ctx) {
9385
+ if (ctx.tokens[ctx.pos]?.type !== "BLOCK_OPEN") {
9386
+ return { success: false };
9387
+ }
9388
+ let pos = ctx.pos + 1;
9389
+ const nameResult = parseBlockName(ctx, pos);
9390
+ if (!nameResult || nameResult.name !== "gallery") {
9391
+ return { success: false };
9392
+ }
9393
+ pos += nameResult.consumed;
9394
+ const attrResult = parseAttributesRaw(ctx, pos);
9395
+ pos += attrResult.consumed;
9396
+ if (ctx.tokens[pos]?.type !== "BLOCK_CLOSE") {
9397
+ return { success: false };
9398
+ }
9399
+ pos++;
9400
+ const size = normalizeSize(attrResult.attrs.size);
9401
+ const viewer = normalizeViewer(attrResult.attrs.viewer);
9402
+ const order = normalizeOrder(attrResult.attrs.order);
9403
+ const openConsumed = pos - ctx.pos;
9404
+ const content = tryParseGalleryContent(ctx, pos);
9405
+ if (!content) {
9406
+ return {
9407
+ success: true,
9408
+ elements: [
9409
+ {
9410
+ element: "gallery",
9411
+ data: { size, order, viewer, content: { type: "auto", files: null } }
9412
+ }
9413
+ ],
9414
+ consumed: openConsumed
9415
+ };
9416
+ }
9417
+ const items = content.lines.map(parseGalleryItemLine);
9418
+ return {
9419
+ success: true,
9420
+ elements: [
9421
+ {
9422
+ element: "gallery",
9423
+ data: { size, order, viewer, content: { type: "items", items } }
9424
+ }
9425
+ ],
9426
+ consumed: openConsumed + content.consumed
9427
+ };
9428
+ }
9429
+ };
9430
+
9260
9431
  // packages/parser/src/parser/rules/block/index.ts
9261
9432
  var blockRules = [
9262
9433
  blockCommentRule,
@@ -9286,6 +9457,7 @@ var blockRules = [
9286
9457
  iframeRule,
9287
9458
  iftagsRule,
9288
9459
  bibliographyRule,
9460
+ galleryRule,
9289
9461
  divRule
9290
9462
  ];
9291
9463
  // packages/parser/src/parser/rules/inline/formatting/container.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wdprlib/parser",
3
- "version": "4.2.0",
3
+ "version": "4.3.0",
4
4
  "description": "Parser for Wikidot markup",
5
5
  "keywords": [
6
6
  "ast",
@@ -41,6 +41,6 @@
41
41
  },
42
42
  "dependencies": {
43
43
  "@braintree/sanitize-url": "^7.1.1",
44
- "@wdprlib/ast": "2.2.0"
44
+ "@wdprlib/ast": "2.3.0"
45
45
  }
46
46
  }
package/src/index.ts CHANGED
@@ -58,6 +58,12 @@ export type {
58
58
  DateItem,
59
59
  Embed,
60
60
  TocEntry,
61
+ // Gallery
62
+ GallerySize,
63
+ GalleryOrder,
64
+ GalleryItem,
65
+ GalleryContent,
66
+ GalleryData,
61
67
  // Diagnostics
62
68
  Diagnostic,
63
69
  DiagnosticSeverity,
@@ -0,0 +1,215 @@
1
+ /**
2
+ *
3
+ * Block rule for the Wikidot `[[gallery]]` image gallery.
4
+ *
5
+ * ```
6
+ * [[gallery size="thumbnail" order="name" viewer="no"]]
7
+ * : first-image.jpg
8
+ * : *page/other-image.jpg link="some-page" alt="Alt text"
9
+ * [[/gallery]]
10
+ * ```
11
+ *
12
+ * The content form requires the `]]` to be followed immediately by one or
13
+ * more `: source` lines and a `[[/gallery]]` close tag (Wikidot regex
14
+ * `\[\[gallery(\s[^\]]*?)?\]\](?:((?:\n: [^\n]+)+)\n\[\[\/gallery\]\])?`).
15
+ * Anything else falls back to the standalone content-less form, which shows
16
+ * the current page's image attachments after data resolution
17
+ * (`content: { type: "auto", files: null }`); the following text then
18
+ * parses normally.
19
+ *
20
+ * Honored opening-tag attributes are `size` (small / medium / thumbnail /
21
+ * square / original, fallback thumbnail), `viewer` (`"no"`/`"false"`
22
+ * disable the lightbox) and `order` (auto-collection sort order, with
23
+ * Wikidot's deprecated aliases normalized).
24
+ *
25
+ * Deliberate differences from the Wikidot parser: the opening tag must
26
+ * fit on one line and attribute names are lowercased with unquoted values
27
+ * accepted (both shared with wdpr's other block rules), and `flickr:`
28
+ * sources get no special treatment — they resolve like any other
29
+ * filename (wdpr does not call the Flickr API).
30
+ *
31
+ * @module
32
+ */
33
+ import type { Element, GalleryItem, GalleryOrder, GallerySize } from "@wdprlib/ast";
34
+ import type { BlockRule, ParseContext, RuleResult } from "../../types";
35
+ import { parseAttributesRaw, parseBlockName } from "../utils";
36
+ import { parseGalleryItemLine } from "./items";
37
+
38
+ export { parseGalleryItemLine } from "./items";
39
+
40
+ const GALLERY_SIZES: readonly string[] = ["small", "medium", "thumbnail", "square", "original"];
41
+
42
+ /** Validate a size keyword, falling back to thumbnail like Wikidot. */
43
+ function normalizeSize(value: string | undefined): GallerySize {
44
+ return value !== undefined && GALLERY_SIZES.includes(value)
45
+ ? (value as GallerySize)
46
+ : "thumbnail";
47
+ }
48
+
49
+ /** `viewer="no"` / `viewer="false"` disable the lightbox; anything else enables it. */
50
+ function normalizeViewer(value: string | undefined): boolean {
51
+ return value !== "no" && value !== "false";
52
+ }
53
+
54
+ /**
55
+ * Normalize the `order` attribute, folding Wikidot's deprecated aliases
56
+ * (`nameDesc`, `dateAdded`, `dateAddedDesc`) and the documented
57
+ * ListPages-compatibility forms (`"name desc desc"` / `"created_at desc
58
+ * desc"`, which mean the same as without the `desc desc`).
59
+ */
60
+ function normalizeOrder(value: string | undefined): GalleryOrder {
61
+ switch (value) {
62
+ case "name":
63
+ case "name desc":
64
+ case "created_at":
65
+ case "created_at desc":
66
+ return value;
67
+ case "nameDesc":
68
+ return "name desc";
69
+ case "dateAdded":
70
+ return "created_at";
71
+ case "dateAddedDesc":
72
+ return "created_at desc";
73
+ case "name desc desc":
74
+ return "name";
75
+ case "created_at desc desc":
76
+ return "created_at";
77
+ default:
78
+ return "name";
79
+ }
80
+ }
81
+
82
+ interface GalleryContentResult {
83
+ /** Trimmed line contents (after the leading `: `) */
84
+ lines: string[];
85
+ /** Token count from the content start (the NEWLINE after `]]`) through `[[/gallery]]` */
86
+ consumed: number;
87
+ }
88
+
89
+ /**
90
+ * Try to match the content form starting at `pos` (the token right after the
91
+ * opening tag's `]]`): one NEWLINE, then consecutive `: source` lines, then a
92
+ * NEWLINE directly followed by `[[/gallery]]`. Returns null when the token
93
+ * stream deviates from the Wikidot regex, which makes the gallery standalone.
94
+ */
95
+ function tryParseGalleryContent(ctx: ParseContext, pos: number): GalleryContentResult | null {
96
+ const start = pos;
97
+ if (ctx.tokens[pos]?.type !== "NEWLINE") {
98
+ return null;
99
+ }
100
+
101
+ const lines: string[] = [];
102
+ let p = pos + 1;
103
+
104
+ for (;;) {
105
+ const colon = ctx.tokens[p];
106
+ if (colon?.type !== "COLON" || !colon.lineStart) {
107
+ break;
108
+ }
109
+ // Wikidot requires a literal space after the colon (`\n: `)
110
+ const space = ctx.tokens[p + 1];
111
+ if (space?.type !== "WHITESPACE" || !space.value.startsWith(" ")) {
112
+ break;
113
+ }
114
+
115
+ let content = "";
116
+ let q = p + 1;
117
+ while (q < ctx.tokens.length) {
118
+ const token = ctx.tokens[q];
119
+ if (!token || token.type === "NEWLINE" || token.type === "EOF") {
120
+ break;
121
+ }
122
+ content += token.value;
123
+ q++;
124
+ }
125
+ // `: [^\n]+` needs at least one character after the space
126
+ if (content === " ") {
127
+ return null;
128
+ }
129
+ if (ctx.tokens[q]?.type !== "NEWLINE") {
130
+ // line hit EOF: the close tag can no longer follow on its own line
131
+ return null;
132
+ }
133
+
134
+ lines.push(content.trim());
135
+ p = q + 1;
136
+ }
137
+
138
+ if (lines.length === 0) {
139
+ return null;
140
+ }
141
+
142
+ // The last consumed NEWLINE must be directly followed by [[/gallery]]
143
+ if (ctx.tokens[p]?.type !== "BLOCK_END_OPEN") {
144
+ return null;
145
+ }
146
+ const nameResult = parseBlockName(ctx, p + 1);
147
+ if (!nameResult || nameResult.name !== "gallery") {
148
+ return null;
149
+ }
150
+ const closePos = p + 1 + nameResult.consumed;
151
+ if (ctx.tokens[closePos]?.type !== "BLOCK_CLOSE") {
152
+ return null;
153
+ }
154
+
155
+ return { lines, consumed: closePos + 1 - start };
156
+ }
157
+
158
+ export const galleryRule: BlockRule = {
159
+ name: "gallery",
160
+ startTokens: ["BLOCK_OPEN"],
161
+ requiresLineStart: true,
162
+
163
+ parse(ctx: ParseContext): RuleResult<Element> {
164
+ if (ctx.tokens[ctx.pos]?.type !== "BLOCK_OPEN") {
165
+ return { success: false };
166
+ }
167
+
168
+ let pos = ctx.pos + 1;
169
+ const nameResult = parseBlockName(ctx, pos);
170
+ if (!nameResult || nameResult.name !== "gallery") {
171
+ return { success: false };
172
+ }
173
+ pos += nameResult.consumed;
174
+
175
+ const attrResult = parseAttributesRaw(ctx, pos);
176
+ pos += attrResult.consumed;
177
+
178
+ if (ctx.tokens[pos]?.type !== "BLOCK_CLOSE") {
179
+ return { success: false };
180
+ }
181
+ pos++;
182
+
183
+ const size = normalizeSize(attrResult.attrs.size);
184
+ const viewer = normalizeViewer(attrResult.attrs.viewer);
185
+ const order = normalizeOrder(attrResult.attrs.order);
186
+ const openConsumed = pos - ctx.pos;
187
+
188
+ const content = tryParseGalleryContent(ctx, pos);
189
+ if (!content) {
190
+ return {
191
+ success: true,
192
+ elements: [
193
+ {
194
+ element: "gallery",
195
+ data: { size, order, viewer, content: { type: "auto", files: null } },
196
+ },
197
+ ],
198
+ consumed: openConsumed,
199
+ };
200
+ }
201
+
202
+ const items: GalleryItem[] = content.lines.map(parseGalleryItemLine);
203
+
204
+ return {
205
+ success: true,
206
+ elements: [
207
+ {
208
+ element: "gallery",
209
+ data: { size, order, viewer, content: { type: "items", items } },
210
+ },
211
+ ],
212
+ consumed: openConsumed + content.consumed,
213
+ };
214
+ },
215
+ };
@@ -0,0 +1,62 @@
1
+ import type { GalleryItem } from "@wdprlib/ast";
2
+
3
+ /**
4
+ * Parse one gallery content line (the text after the leading `: `,
5
+ * already trimmed) into a {@link GalleryItem}.
6
+ *
7
+ * The text before the first space is the source; the rest is parsed as
8
+ * `key="value"` attributes of which `link` and `alt` are honored. A
9
+ * leading `*` on the source or on the link value requests a new window.
10
+ */
11
+ export function parseGalleryItemLine(content: string): GalleryItem {
12
+ const spacePos = content.indexOf(" ");
13
+ let source = spacePos < 0 ? content : content.slice(0, spacePos);
14
+ const attrText = spacePos < 0 ? "" : content.slice(spacePos + 1);
15
+
16
+ let newWindow = false;
17
+ if (source.startsWith("*")) {
18
+ source = source.slice(1);
19
+ newWindow = true;
20
+ }
21
+
22
+ const attrs = parseItemAttrs(attrText);
23
+ let link = attrs.get("link") ?? null;
24
+ if (link !== null && link.startsWith("*")) {
25
+ newWindow = true;
26
+ link = link.slice(1);
27
+ }
28
+ const alt = attrs.get("alt") ?? null;
29
+
30
+ return { source, link, alt, newWindow };
31
+ }
32
+
33
+ /**
34
+ * Parse `key="value"` attribute pairs from a gallery item line, following
35
+ * the splitting behavior of Wikidot's gallery `getAttrs()`: fragments are
36
+ * separated by `="`, values run to the last `"` in each fragment and are
37
+ * backslash-unescaped, and keys are not lowercased.
38
+ */
39
+ function parseItemAttrs(text: string): Map<string, string> {
40
+ const attrs = new Map<string, string>();
41
+ const parts = text.trim().split('="');
42
+ let key = parts[0]?.trim() ?? "";
43
+
44
+ for (let i = 1; i < parts.length; i++) {
45
+ const val = parts[i] ?? "";
46
+ const quotePos = val.lastIndexOf('"');
47
+ if (quotePos < 0) {
48
+ attrs.set(key, "");
49
+ key = val.slice(1).trim();
50
+ } else {
51
+ attrs.set(key, stripslashes(val.slice(0, quotePos)));
52
+ key = val.slice(quotePos + 1).trim();
53
+ }
54
+ }
55
+
56
+ return attrs;
57
+ }
58
+
59
+ /** PHP-style `stripslashes()`: drop each escaping backslash, and a trailing lone one. */
60
+ function stripslashes(value: string): string {
61
+ return value.replace(/\\(.)/gs, "$1").replace(/\\$/, "");
62
+ }
@@ -49,6 +49,7 @@ import { iftagsRule } from "./iftags";
49
49
  import { tocRule } from "./toc";
50
50
  import { orphanLiRule } from "./orphan-li";
51
51
  import { bibliographyRule } from "./bibliography";
52
+ import { galleryRule } from "./gallery";
52
53
 
53
54
  export { headingRule } from "./heading";
54
55
  export { horizontalRuleRule } from "./horizontal-rule";
@@ -79,6 +80,7 @@ export { iftagsRule } from "./iftags";
79
80
  export { tocRule } from "./toc";
80
81
  export { orphanLiRule } from "./orphan-li";
81
82
  export { bibliographyRule } from "./bibliography";
83
+ export { galleryRule } from "./gallery";
82
84
 
83
85
  /**
84
86
  * All block rules in priority order.
@@ -120,6 +122,7 @@ export const blockRules: BlockRule[] = [
120
122
  iframeRule,
121
123
  iftagsRule,
122
124
  bibliographyRule,
125
+ galleryRule,
123
126
  divRule,
124
127
  // paragraphRule is not included - used as fallback
125
128
  ];