Module:Gallery
This module builds MediaWiki <gallery> tags from either hand-built item lists or Cargo query results. It is the single place gallery HTML gets generated on HopperWiki — templates should never write <gallery> markup by hand.
Architecture
The module is layered so that a new gallery "flavor" (a new Cargo table, a new JSON dataset, a new domain like species or resources) can be added without touching the rendering layer.
| Layer 1 · Render | Layer 2 · Normalize | Layer 3 · Pipeline | Layer 4 · Flavor |
|---|---|---|---|
|
|
|
|
Thin wrapper functions (e.g. |
Design intent: keep new domains as Layer 4 wrappers around the existing pipeline, not new copies of the pipeline. Only reach for a new Layer 2 normalizer (parallel to items_from_cargo_rows) if a data source genuinely isn't Cargo-shaped — e.g. a JSON-backed gallery. Don't build a config-driven meta-system to unify Cargo and JSON sources pre-emptively; two concrete flavors is not yet a pattern that needs its own abstraction.
Functions
p.build_gallery(items, options)
Lowest-level builder. Emits a raw <gallery> tag.
| Parameter | Notes |
|---|---|
items |
Array of tables: image (required — filename, with or without File: prefix), caption (optional, may contain wikilinks), link (optional click target), alt (optional alt text)
|
options.mode |
packed, packed-hover, nolines, traditional, slideshow
|
options.heights / widths |
e.g. "200px"
|
options.perrow |
Images per row |
options.caption |
Overall gallery caption (not per-image) |
options.showfilename |
"yes" or "no"
|
p.build_modern_gallery(items, options)
Same signature as build_gallery, wrapped in a modern-gallery styled <div>. Adds options.show_captions ("hover" default, or "always") which picks packed-hover vs nolines mode automatically when options.mode isn't set explicitly. Used directly by Module:Geography's species_gallery_for_geography, which builds its own items from a JSON dataset rather than Cargo.
p.items_from_cargo_rows(cargo_results, config)
Normalizes raw Cargo rows into the item shape build_gallery expects. This is the function to reuse if you're writing a new Cargo-backed flavor by hand rather than going through cargo_gallery_pipeline.
| Parameter | Notes |
|---|---|
config.focal_field |
Required. Field whose value becomes the caption text and the grouping key. |
config.image_field |
Required. Field containing the image filename. File: prefix is added automatically if missing (case-insensitive check).
|
config.placeholder_image |
Fallback image used when a row's image field is empty. |
config.name_array |
Optional ordered array of focal_field values — controls both which rows are included and their display order. Omit to include everything in query order.
|
config.link_config.image_link_field |
Cargo field to read for the image's click target. Default "image_link".
|
config.link_config.caption_link_field |
Cargo field to read for the caption's link target. Default "caption_link".
|
config.link_config.enable_caption_links |
Boolean, default true. Set false to render captions as plain text with no wikilink.
|
config.link_config.fallback_image_link / fallback_caption_link |
Used when the row has no value for the configured link field. If omitted, both default to the row's focal_field value — i.e. images and captions link to the item's own page unless told otherwise.
|
config.link_config.no_link_field |
Optional field name. When a row's value for this field is truthy (anything except empty/"0"/"false"/"no"), that row's caption renders as plain text, overriding enable_caption_links for that row only — other rows in the same gallery are unaffected. Use this to mix "will eventually get a page" items (still linked/red-linked) with "will never get its own page" items (e.g. an external organization or a person who's intentionally data-only) inside a single gallery.
|
Returns items, nil on success, or nil, error_message if there's nothing to render (empty results, or no rows matched a given name_array). Callers must check for nil before passing to build_gallery.
p.cargo_gallery_pipeline(frame)
The generic #invoke entry point. Runs a Cargo query from template args, then Layers 2 and 1.
Required args
| Parameter | Notes |
|---|---|
cargo_table |
Cargo table to query. |
cargo_fields |
Comma-separated fields to fetch. |
Field configuration
| Parameter | Default | Notes |
|---|---|---|
cargo_focal_field |
— | Shorthand that sets both search_field and display_field to the same field. Kept for backward compatibility with older templates.
|
search_field |
cargo_focal_field |
Field used in the WHERE clause. |
search_field_type |
cargo_focal_field_type, else "string" |
Passed to Module:Cargo query utilities' build_where_clause — use "list_of_string" for HOLDS-type fields.
|
display_field |
cargo_focal_field |
Field shown as the caption/grouping key. Can differ from search_field.
|
image_field |
"Image" |
Field holding the image filename. |
placeholder_image |
"No image available.svg" |
Used when a row's image field is empty. |
Filtering
| Parameter | Notes |
|---|---|
names |
Comma-separated values to include. Also controls display order (see name_array above). Filters on search_field.
|
not_values |
Comma-separated values to exclude. |
cargo_where |
Escape hatch — raw WHERE clause, bypasses names/not_values entirely when set.
|
limit |
Hard cap passed to the Cargo query. |
Linking (optional)
| Parameter | Notes |
|---|---|
image_link_field |
Cargo field for the image's click target. If omitted, images link to their own display_field page.
|
caption_link_field |
Cargo field for the caption's link target. If omitted, captions link to their own display_field page.
|
enable_caption_links |
"false" to render plain-text captions with no link. Anything else (including omitted) means true.
|
fallback_image_link / fallback_caption_link |
Override the "link to own page" default without pointing at a Cargo field. |
no_link_field |
Cargo field to check per-row for an "opt out of linking" flag (truthy value means plain text, overriding enable_caption_links for that row only). Use this to mix linkable and never-linkable items in one gallery — e.g. a boolean Never_gets_page column on the Organization or Person table.
|
Display
| Parameter | Default | Notes |
|---|---|---|
gallery_mode |
"packed" |
Passed straight through to build_gallery's options.mode.
|
heights |
"200px" |
Passed straight through to build_gallery's options.heights.
|
widths |
(unset) | Passed straight through to build_gallery's options.widths. Omitted entirely unless given.
|
perrow |
(unset) | Passed straight through to build_gallery's options.perrow.
|
p.get_species_gallery(frame)
Species-flavored wrapper. Pre-fills cargo_gallery_pipeline's args for the common case — querying Image filtered by Species — then delegates. See #Adding a new flavor for the pattern this demonstrates.
Notable implementation detail: it edits frame.args directly rather than calling getArgs(frame) first. getArgs returns a copy; edits to that copy would not be visible to cargo_gallery_pipeline's own getArgs(frame) call. Any new wrapper that pre-fills args before delegating to cargo_gallery_pipeline must follow the same approach.
Examples
Generic pipeline, minimal args
{{#invoke:Gallery
|cargo_gallery_pipeline
|cargo_table=Species
|cargo_fields=Species, Image
|cargo_focal_field=Species
}}From templates/Species gallery.wiki. Images and captions both link to each species' own page (no image_link_field/caption_link_field given, so the default-to-own-page fallback applies).
Generic pipeline, list-type search field with custom link fields
{{#invoke:Gallery|get_species_gallery
|cargo_table=Image
|cargo_fields=Species, Image,
|search_field=Species
|search_field_type=list_of_string
|display_field=Species
|image_field=Image
|image_link_field=Image
|caption_link_field=Species
|enable_caption_links=false
|gallery_mode=packed-hover
}}From templates/Images for species.wiki. Note enable_caption_links=false — captions render as plain species names, no link — while the image itself still links via image_link_field=Image (clicking the photo opens the file page).
Pass-through wrapper with a single templated arg
{{#invoke:Gallery|cargo_gallery_pipeline|limit={{{limit|}}}}}From templates/Get cargo gallery.wiki — the rest of the args are expected to be supplied by whatever calls this template.
Adding a new flavor
To add a gallery for a new Cargo-backed domain (a new table, or the same table queried differently):
- Write a wrapper function
p.get_<domain>_gallery(frame)in this module, modeled onp.get_species_gallery. - Edit
frame.argsdirectly (not agetArgs(frame)copy) to pre-fillcargo_table,cargo_fields,search_field,display_field,image_field, and any link/display defaults specific to the domain. - Delegate to
p.cargo_gallery_pipeline(frame)— do not duplicate the query/normalize/render logic. - Add a template under
templates/that calls{{#invoke:Gallery|get_<domain>_gallery|...}}, exposing only the args callers actually need to vary.
For a JSON-backed (non-Cargo) domain, follow Module:Geography's species_gallery_for_geography as the reference: build the items array by hand (matching the {image, caption, link, alt} shape) and call p.build_modern_gallery directly, skipping Layers 2–3 entirely since they're Cargo-specific. Only extract a shared JSON-side normalizer (parallel to items_from_cargo_rows) once a second JSON-backed gallery actually needs one — don't build it speculatively.
Known gotchas
| Situation | What happens | Fix |
|---|---|---|
Image filename already has a lowercase file: prefix |
build_gallery's prefix check is case-sensitive (^File:) — a lowercase prefix isn't recognized as already-prefixed and gets double-prefixed |
Don't pre-prefix filenames when building items by hand; let build_gallery or items_from_cargo_rows (which does a case-insensitive check) add it
|
| Breaks the gallery line's pipe-delimited syntax | — but avoid feeding free-text fields into caption/link without checking
| |
Calling items_from_cargo_rows or cargo_gallery_pipeline without checking for a nil return |
Passing nil items into build_gallery errors |
Always check the err second return before rendering; cargo_gallery_pipeline already does this and renders a gallery-error span instead
|
Module family
| Module | Role |
|---|---|
Module:Arguments |
Frame argument processing (getArgs)
|
Module:String utilities |
parse_csv_to_table for names/not_values
|
Module:Cargo query utilities |
build_where_clause — schema-aware WHERE clause construction
|
Module:Geography |
Calls p.build_modern_gallery directly for its JSON-backed species_gallery_for_geography flavor
|
-- ================================================================================
-- Module Dependencies
-- ================================================================================
local cargo = mw.ext.cargo
local getArgs = require('Module:Arguments').getArgs -- for processing arguments
local str = require("Module:String utilities")
local cq = require("Module:Cargo query utilities")
-- ================================================================================
-- Main Table
-- ================================================================================
local p = {}
-- ================================================================
-- Build MediaWiki Gallery
--
-- Creates a <gallery> tag with fine-grained control over images,
-- captions, and links.
--
-- Args:
-- items: Array of tables with:
-- - image: Required. Filename (with or without "File:" prefix)
-- - caption: Optional. Caption text (can include wikilinks)
-- - link: Optional. Where to go when clicking image
-- - alt: Optional. Alt text for accessibility
-- options: Optional table with gallery-wide settings:
-- - mode: "packed", "packed-hover", "nolines", "traditional", "slideshow"
-- - heights: e.g., "150px"
-- - widths: e.g., "200px"
-- - perrow: Number of images per row
-- - caption: Overall gallery caption
-- - showfilename: "yes" or "no"
--
-- Returns:
-- Wikitext string with <gallery> tag
-- ================================================================
function p.build_gallery(items, options)
options = options or {}
-- Start gallery tag with options
local gallery_attrs = {}
if options.mode then table.insert(gallery_attrs, 'mode="' .. options.mode .. '"') end
if options.heights then table.insert(gallery_attrs, 'heights="' .. options.heights .. '"') end
if options.widths then table.insert(gallery_attrs, 'widths="' .. options.widths .. '"') end
if options.perrow then table.insert(gallery_attrs, 'perrow="' .. options.perrow .. '"') end
if options.caption then table.insert(gallery_attrs, 'caption="' .. options.caption .. '"') end
if options.showfilename then table.insert(gallery_attrs, 'showfilename="' .. options.showfilename .. '"') end
local gallery = "<gallery"
if #gallery_attrs > 0 then
gallery = gallery .. " " .. table.concat(gallery_attrs, " ")
end
gallery = gallery .. ">\n"
-- Add each image
for _, item in ipairs(items) do
-- Build image line (str.file_title adds exactly one "File:", whatever the input carried)
local line = str.file_title(item.image)
if line ~= "" then
-- Add link parameter if specified
if item.link then
line = line .. "|link=" .. item.link
end
-- Add alt parameter if specified
if item.alt then
line = line .. "|alt=" .. item.alt
end
-- Add caption if specified
if item.caption then
line = line .. "|" .. item.caption
end
gallery = gallery .. line .. "\n"
end
end
gallery = gallery .. "</gallery>"
return gallery
end
-- ================================================================
-- Build Modern Gallery (enhanced styling wrapper)
--
-- Wraps the generic gallery builder with modern CSS class.
-- Uses same API as build_gallery() but applies modern theme.
--
-- Args: Same as build_gallery()
-- Returns: Gallery HTML wrapped in modern-gallery div
-- ================================================================
function p.build_modern_gallery(items, options)
options = options or {}
-- NEW: Allow caption display control
local show_captions = options.show_captions or "hover" -- "hover" or "always"
-- Set mode based on caption preference
if not options.mode then
if show_captions == "always" then
options.mode = "nolines" -- Captions always visible
else
options.mode = "packed-hover" -- Captions on hover (default)
end
end
if not options.heights then
options.heights = "200px"
end
local gallery = p.build_gallery(items, options)
local class = "modern-gallery"
if options.variant then
class = class .. " modern-gallery--" .. options.variant
end
return '<div class="' .. class .. '" style="width: 100%;">\n' .. gallery .. '\n</div>'
end
-- ================================================================
-- Hand-curated modern gallery (template entry point)
--
-- Lets a page list items by hand instead of pulling them from Cargo.
-- Each item is a numbered set of parameters:
-- imageN: Required. File name (with or without "File:" prefix).
-- Plain positional params work too: {{Modern gallery|A.pdf|B.pdf}}
-- labelN: Optional. Caption text (defaults to the file name)
-- urlN: Optional. Target for both the image and the caption link.
-- External URLs (http/https) become [url label]; anything
-- else is treated as a wiki page. When omitted, the file
-- itself is the target (so an uploaded PDF opens directly).
-- linkN: Optional. Override the image target only.
-- sourceN: Optional. Publisher shown as a small label above the title
-- (documents variant only).
-- Gallery-wide: widths, heights, perrow,
-- show_captions ("always" default, or "hover"),
-- variant ("documents" is automatic for all-PDF galleries; "none" opts out),
-- meta ("no" hides the automatic "PDF · 12 pages · 3.4 MB" line).
--
-- Returns: Preprocessed modern gallery
-- ================================================================
function p.manual_gallery(frame)
local args = getArgs(frame)
local function is_external(target)
return target:match("^https?://") ~= nil
end
-- "PDF · 12 pages · 3.4 MB" from the file's own metadata (file info is an
-- expensive lookup, so only done for the documents variant)
local function file_meta(image)
local title = mw.title.new(image, "File")
local file = title and title.file
if not (file and file.exists) then return nil end
local parts = {}
local ext = image:match("%.(%w+)$")
if ext then table.insert(parts, ext:upper()) end
if file.pages and #file.pages > 0 then
table.insert(parts, #file.pages == 1 and "1 page" or (#file.pages .. " pages"))
end
if file.size then
if file.size >= 1048576 then
table.insert(parts, string.format("%.1f MB", file.size / 1048576))
else
table.insert(parts, math.max(1, math.floor(file.size / 1024 + 0.5)) .. " KB")
end
end
return table.concat(parts, " · ")
end
-- Collect entries first; the variant decides how captions are built
local entries = {}
local i = 1
while args["image" .. i] or args[i] do
local image = str.remove_file_prefix(mw.text.trim(args["image" .. i] or args[i]))
local url = args["url" .. i] or frame:callParserFunction("filepath", image)
table.insert(entries, {
image = image,
label = args["label" .. i] or image:gsub("%.%w+$", ""),
url = url,
link = args["link" .. i] or url,
source = args["source" .. i],
})
i = i + 1
end
if #entries == 0 then
return '<span class="gallery-error">No images given (use image1, label1, url1, ...)</span>'
end
-- All-PDF galleries default to the uncropped document look
local variant = args.variant
if not variant then
variant = "documents"
for _, entry in ipairs(entries) do
if not entry.image:lower():match("%.pdf$") then
variant = nil
break
end
end
end
if variant == "none" then variant = nil end
local documents = (variant == "documents")
local items = {}
for _, entry in ipairs(entries) do
local caption
if is_external(entry.url) then
caption = string.format("[%s %s]", entry.url, entry.label)
else
-- Leading colon so File:/Category: targets link instead of embedding
caption = string.format("[[:%s|%s]]", entry.url:gsub("^:", ""), entry.label)
end
if documents then
caption = '<span class="modern-gallery__title">' .. caption .. '</span>'
if entry.source then
caption = '<span class="modern-gallery__eyebrow">' .. entry.source .. '</span>' .. caption
end
local meta = (args.meta ~= "no") and file_meta(entry.image)
if meta and meta ~= "" then
caption = caption .. '<span class="modern-gallery__meta">' .. meta .. '</span>'
end
end
table.insert(items, {
image = entry.image,
link = entry.link,
alt = entry.label,
caption = caption,
})
end
-- Documents render larger in the responsive grid; request sharper thumbs
local gallery_output = p.build_modern_gallery(items, {
variant = variant,
widths = args.widths or (documents and "260px" or "175px"),
heights = args.heights or (documents and "320px" or nil),
perrow = args.perrow,
show_captions = args.show_captions or "always",
})
return frame:preprocess(gallery_output)
end
-- ========================================
-- Species-specific gallery wrapper function
-- ========================================
--[[
This is slight overkill but this function is a wrapper around the cargo_gallery_pipeline that is more generic so it can handle
some upstream logic that is specific to the species theme.
--]]
function p.get_species_gallery(frame)
--[[ Work directly with frame.args instead of getArgs(). This is important because this is a
parent function that needs to pass the frame to it's child and when you use getArgs() you're pulling
out an additional copy of the arguments and editing them but those are no longer that is no longer part of the frame.
--]]
local args = frame.args
-- Get the species name from page title or override
local species_name
if args.species and args.species ~= "" then
species_name = args.species
else
species_name = str.title_to_sci(mw.title.getCurrentTitle().text)
end
-- Set the required parameters directly on frame.args
args.names = species_name
args.cargo_table = args.cargo_table or "Image"
args.search_field = args.search_field or "Species"
args.search_field_type = args.search_field_type or "list_of_string"
args.display_field = args.display_field or "Species"
args.image_field = args.image_field or "Image"
args.gallery_mode = args.gallery_mode or "packed"
-- Build the field list
local fields = {args.search_field, args.image_field}
if args.display_field ~= args.search_field then
table.insert(fields, args.display_field)
end
args.cargo_fields = table.concat(fields, ", ")
-- Debug
if args.debug == "true" then
return string.format("Species: '%s', cargo_fields: '%s'",
tostring(species_name),
tostring(args.cargo_fields))
end
-- Call the existing function - it should now see our modified frame.args
return p.cargo_gallery_pipeline(frame)
end
-- ================================================================
-- Normalize Cargo rows into gallery items
--
-- Takes raw Cargo query results and turns them into the {image, caption,
-- link, alt} item shape that build_gallery()/build_modern_gallery() expect.
-- This is the "querying meets rendering" seam: everything upstream of this
-- function is Cargo-specific, everything downstream is plain gallery HTML.
--
-- Args:
-- cargo_results: Array of Cargo rows
-- config: table with:
-- - focal_field: Field whose value becomes the caption/grouping key (required)
-- - image_field: Field containing the image filename (required)
-- - placeholder_image: Fallback image when image_field is empty (optional)
-- - name_array: Optional ordered array of focal_field values to include/order by
-- - link_config: Optional table controlling link behaviour:
-- - image_link_field: Field for image click target (default "image_link")
-- - caption_link_field: Field for caption link target (default "caption_link")
-- - enable_caption_links: boolean, default true
-- - fallback_image_link: default image link when field is absent
-- - fallback_caption_link: default caption link when field is absent
-- - no_link_field: Optional field name. When a row's value for this
-- field is truthy (anything except empty/"0"/"false"/"no"), that
-- row's caption is rendered as plain text, overriding
-- enable_caption_links for that row only. Use this for entities
-- that should never get a wiki page (e.g. an Organization or
-- Person row flagged "never gets its own page") while other rows
-- in the same gallery still get linked/red-linked normally.
--
-- Returns:
-- Array of gallery items, or nil + error message if there's nothing to show
-- ================================================================
function p.items_from_cargo_rows(cargo_results, config)
if not cargo_results or type(cargo_results) ~= "table" or #cargo_results == 0 then
return nil, "No images found for this query -- should be coming soon!"
end
config = config or {}
local focal_field = config.focal_field
local image_field = config.image_field
local placeholder_image = config.placeholder_image
local name_array = config.name_array
local link_config = config.link_config or {}
local image_link_field = link_config.image_link_field or "image_link"
local caption_link_field = link_config.caption_link_field or "caption_link"
local enable_caption_links = (link_config.enable_caption_links ~= false)
local fallback_image_link = link_config.fallback_image_link
local fallback_caption_link = link_config.fallback_caption_link
local no_link_field = link_config.no_link_field
local function link_is_suppressed(row)
if not no_link_field then return false end
local raw = row[no_link_field]
if raw == nil then return false end
local normalized = mw.text.trim(tostring(raw)):lower()
return normalized ~= "" and normalized ~= "0" and normalized ~= "false" and normalized ~= "no"
end
local file_name_map = {}
local ordered_names = {}
for _, row in ipairs(cargo_results) do
local name = row[focal_field]
if name then
local image = row[image_field] or placeholder_image
image = str.file_title(image)
local image_link_target
if row[image_link_field] ~= nil then
image_link_target = row[image_link_field]
elseif row.link ~= nil then
image_link_target = row.link
else
image_link_target = fallback_image_link or name
end
local caption_link_target
if link_is_suppressed(row) then
caption_link_target = ""
elseif row[caption_link_field] ~= nil then
caption_link_target = row[caption_link_field]
else
caption_link_target = fallback_caption_link or name
end
if not file_name_map[name] then
file_name_map[name] = {}
if not name_array then
table.insert(ordered_names, name)
end
end
table.insert(file_name_map[name], {
image = image,
image_link = image_link_target,
caption_link = caption_link_target,
})
end
end
if next(file_name_map) == nil then
return nil, "No images found for the provided names!"
end
local names_to_use = (type(name_array) == "table" and #name_array > 0)
and name_array or ordered_names
local items = {}
for _, name in ipairs(names_to_use) do
for _, entry in ipairs(file_name_map[name] or {}) do
local caption_text
if enable_caption_links and entry.caption_link ~= "" then
caption_text = "<center>[[" .. entry.caption_link .. "|" .. name .. "]]</center>"
else
caption_text = "<center>" .. name .. "</center>"
end
table.insert(items, {
image = entry.image,
link = entry.image_link,
caption = caption_text,
})
end
end
return items
end
-- ========================================
-- The core gallery building pipeline
-- ========================================
function p.cargo_gallery_pipeline(frame)
local args = getArgs(frame)
-- ========================================
-- Parse arguments (your existing logic)
-- ========================================
local gallery_mode = args.gallery_mode or "packed"
local name_array = str.parse_csv_to_table(args.names)
local not_values = str.parse_csv_to_table(args.not_values)
local cargo_table = args.cargo_table
local fields = args.cargo_fields
-- Field configuration - all configurable from template
local search_field = args.search_field or args.cargo_focal_field -- Field to search in for WHERE clause
local display_field = args.display_field or args.cargo_focal_field -- Field to display in gallery
local image_field = args.image_field or "Image" -- Field containing the image filename
local search_field_type = args.search_field_type or args.cargo_focal_field_type or "string"
local placeholder_image = args.placeholder_image or "No image available.svg"
-- For backward compatibility, if cargo_focal_field is explicitly set, use it for both search and display
if args.cargo_focal_field then
search_field = args.cargo_focal_field
display_field = args.cargo_focal_field
end
-- ========================================
-- NEW: Parse enhanced linking arguments (optional)
-- ========================================
local image_link_field = args.image_link_field
local caption_link_field = args.caption_link_field
local enable_caption_links = (args.enable_caption_links ~= "false")
local fallback_image_link = args.fallback_image_link
local fallback_caption_link = args.fallback_caption_link
local no_link_field = args.no_link_field
-- ========================================
-- NEW: Parse limit argument (optional)
-- ========================================
local limit = args.limit and tonumber(args.limit) or nil
-- ========================================
-- Build WHERE clause
-- ========================================
local cargo_where = args.cargo_where
if not cargo_where or cargo_where == "" then
cargo_where = cq.build_where_clause(search_field_type, search_field, name_array, not_values)
end
-- ========================================
-- Dynamically add link fields to query if specified
-- ========================================
local query_fields = fields
if image_link_field and image_link_field ~= "" then
query_fields = query_fields .. ", " .. image_link_field
end
if caption_link_field and caption_link_field ~= "" then
query_fields = query_fields .. ", " .. caption_link_field
end
if no_link_field and no_link_field ~= "" then
query_fields = query_fields .. ", " .. no_link_field
end
local cargo_args = { where = cargo_where }
-- Pass limit to Cargo query if specified
if limit then
cargo_args.limit = limit
end
-- ========================================
-- Run Cargo query
-- ========================================
if not fields then
return "ERROR: fields is nil. args.cargo_fields = " .. tostring(args.cargo_fields)
end
local cargo_results = cargo.query(cargo_table, query_fields, cargo_args)
-- ========================================
-- Post-process results
-- ========================================
if cargo_results and #cargo_results > 0 then
for _, row in ipairs(cargo_results) do
if image_link_field and row[image_link_field] then
row.image_link = mw.text.trim(row[image_link_field])
end
if caption_link_field and row[caption_link_field] then
row.caption_link = mw.text.trim(row[caption_link_field])
end
if no_link_field and row[no_link_field] then
row.no_link = row[no_link_field]
end
end
end
-- ========================================
-- Setup link configuration
-- ========================================
local link_config = {
image_link_field = "image_link",
caption_link_field = "caption_link",
enable_caption_links = enable_caption_links,
fallback_image_link = fallback_image_link,
fallback_caption_link = fallback_caption_link,
no_link_field = no_link_field and no_link_field ~= "" and "no_link" or nil
}
-- ========================================
-- Generate gallery
-- ========================================
local items, err = p.items_from_cargo_rows(cargo_results, {
focal_field = display_field,
image_field = image_field,
placeholder_image = placeholder_image,
name_array = (#name_array > 0) and name_array or nil,
link_config = link_config,
})
local gallery_wikitext
if not items then
gallery_wikitext = '<span class="gallery-error">' .. err .. '</span>'
else
gallery_wikitext = p.build_gallery(items, {
mode = gallery_mode,
heights = args.heights or "200px",
widths = args.widths,
perrow = args.perrow,
})
end
return frame:preprocess(gallery_wikitext)
end
return p