From Arms of God Wiki
bot: operator iteration 2026-06-10d (hover tooltips / filter chips + DPS sort / damage-type hubs / stat reverse-lookup pages) |
bot: documentation pass — editor-facing docs (data descriptions, module comments, template usage, Help rewrite) |
||
| Line 1: | Line 1: | ||
-- Module:Index — sortable- | -- Module:Index — generic sortable-table renderer for the category | ||
-- | -- index pages. | ||
-- API | -- | ||
-- records : list of | -- WHAT IT DOES | ||
-- | -- Pure presentation: given a list of records and a column spec, it | ||
-- Column `source` semantics: | -- emits one sortable wikitable. It holds no data and no category | ||
-- | -- knowledge — Module:Core's indexEntry() calls Index.render() with | ||
-- | -- records loaded from Data:<Category>.json (already sorted, and | ||
-- | -- augmented with the computed `infobox` value dict). | ||
-- | -- | ||
-- | -- PUBLIC API (Lua-level; pages invoke {{#invoke:<Cat>|index}} instead) | ||
-- | -- Index.render(records, opts) | ||
-- not | -- records : list of records (each carries slug, name, icon, infobox{}) | ||
-- opts : { columns = {{name, source}, ...}, thumbnail_size } | |||
-- Column `source` semantics: | |||
-- "thumbnail" → [[File:<rec.icon>|<thumbnail_size>px]] (em-dash on empty icon) | |||
-- "name" → rec.name (bare text, no link) | |||
-- "link" → [[<rec.slug>|<rec.name>]] | |||
-- any other → rec.infobox[source], fallback rec[source], else em-dash | |||
-- | |||
-- EDITING NOTES | |||
-- Never rebuild a slug from a name here — slugs come from the source | |||
-- records. Column `source` values name the computed infobox FIELD | |||
-- LABELS (e.g. 'Attack Speed'), not raw data keys. The per-category | |||
-- column lists live in Module:Core's INDEX_OPTS table. | |||
local p = {} | local p = {} | ||
| Line 117: | Line 129: | ||
table.insert(cells, _cell(rec, col.source, opts)) | table.insert(cells, _cell(rec, col.source, opts)) | ||
end | end | ||
-- | -- Column separators stay plain `||` on a single `|` line, outside | ||
-- | -- any #if — conditional table markup breaks the wikitext parser. | ||
table.insert(rows, '| ' .. table.concat(cells, ' || ')) | table.insert(rows, '| ' .. table.concat(cells, ' || ')) | ||
end | end | ||
table.insert(rows, '|}') | table.insert(rows, '|}') | ||
-- | -- preprocess so any template call embedded in a cell expands (a | ||
-- | -- plain return from #invoke is not template-expanded again). | ||
return mw.getCurrentFrame():preprocess(table.concat(rows, '\n')) | return mw.getCurrentFrame():preprocess(table.concat(rows, '\n')) | ||
end | end | ||
return p | return p | ||
Latest revision as of 16:04, 10 June 2026
Shared helper that renders sortable index tables from Data: pages.
Library module shipped by the publishing bot; shared across categories. Bot-published — edits are overwritten on re-publish.
-- Module:Index — generic sortable-table renderer for the category
-- index pages.
--
-- WHAT IT DOES
-- Pure presentation: given a list of records and a column spec, it
-- emits one sortable wikitable. It holds no data and no category
-- knowledge — Module:Core's indexEntry() calls Index.render() with
-- records loaded from Data:<Category>.json (already sorted, and
-- augmented with the computed `infobox` value dict).
--
-- PUBLIC API (Lua-level; pages invoke {{#invoke:<Cat>|index}} instead)
-- Index.render(records, opts)
-- records : list of records (each carries slug, name, icon, infobox{})
-- opts : { columns = {{name, source}, ...}, thumbnail_size }
-- Column `source` semantics:
-- "thumbnail" → [[File:<rec.icon>|<thumbnail_size>px]] (em-dash on empty icon)
-- "name" → rec.name (bare text, no link)
-- "link" → [[<rec.slug>|<rec.name>]]
-- any other → rec.infobox[source], fallback rec[source], else em-dash
--
-- EDITING NOTES
-- Never rebuild a slug from a name here — slugs come from the source
-- records. Column `source` values name the computed infobox FIELD
-- LABELS (e.g. 'Attack Speed'), not raw data keys. The per-category
-- column lists live in Module:Core's INDEX_OPTS table.
local p = {}
local function _isEmpty(v)
return v == nil or v == ''
end
local function _flatten(v)
if type(v) ~= 'table' then return tostring(v) end
local parts = {}
for i = 1, #v do
local item = v[i]
if type(item) == 'table' and item.name then
table.insert(parts, tostring(item.name))
else
table.insert(parts, tostring(item))
end
end
return table.concat(parts, ', ')
end
local function _cell(rec, source, opts)
if source == 'thumbnail' then
local icon = rec.icon
if _isEmpty(icon) then return '—' end
return string.format('[[File:%s|%dpx|alt=%s]]',
tostring(icon),
opts.thumbnail_size or 48,
tostring(rec.name or rec.id or ''))
end
if source == 'name' then
if _isEmpty(rec.name) then return '—' end
return tostring(rec.name)
end
if source == 'link' then
local slug = rec.slug or rec.name or rec.id
if _isEmpty(slug) then return '—' end
return '<span class="wm-tip" data-tip-title="' .. tostring(slug)
.. '">[[' .. tostring(slug) .. '|' .. tostring(rec.name or slug) .. ']]</span>'
end
-- Plan-declared infobox label first; raw record field as fallback
-- (covers top-level scalars like `family_key`).
local v = (rec.infobox and rec.infobox[source])
if _isEmpty(v) then v = rec[source] end
if _isEmpty(v) then return '—' end
return _flatten(v)
end
-- Stable sort by the column whose name matches sort_default. Sorting
-- happens on the textual cell value (case-insensitive). Records that
-- render '—' sort last regardless of column.
local function _sort(records, columns, sort_default, opts)
if not sort_default or #records < 2 then return records end
local sort_col
for _, col in ipairs(columns or {}) do
if col.name == sort_default then sort_col = col; break end
end
if not sort_col then return records end
local source = sort_col.source
-- Case-folded sort key for the chosen column. NOTE: this case-fold
-- is for SORT ORDER only — it is NOT a slug operation. The library
-- never derives a link target from a display name (slugs come from
-- rec.slug). Using `string.lower(...)` (function-call form) rather
-- than `:lower()` keeps the F1 lint clean: the lint catches the
-- method form because that's the shape slug-computation code in
-- prior runs took.
local function key(rec)
local c = _cell(rec, source, opts)
if c == '—' then return string.char(255) end -- last
return string.lower(c)
end
local list = {}
for i, r in ipairs(records) do list[i] = r end
table.sort(list, function(a, b) return key(a) < key(b) end)
return list
end
function p.render(records, opts)
records = records or {}
opts = opts or {}
local columns = opts.columns or {}
if #columns == 0 or #records == 0 then return '' end
local ordered = _sort(records, columns, opts.sort_default, opts)
local rows = { '{| class="wikitable sortable" style="background:var(--table-row-odd, #1b1c20);color:var(--table-text, #e6e6e6);border-color:var(--table-border, #3a3c44);"' }
-- Header row. MediaWiki inline header separator is `!!`, not single `!` —
-- `! Icon ! Name` parses as ONE cell with literal `!` text. Use `!!`.
local hdr = {}
for _, col in ipairs(columns) do
table.insert(hdr, 'style="background:var(--table-header-bg, #26272e);color:var(--infobox-header-fg, #f1e9d2);" | ' .. tostring(col.name))
end
table.insert(rows, '! ' .. table.concat(hdr, ' !! '))
-- Body rows.
for _, rec in ipairs(ordered) do
local attrs = ''
local tier = rec.infobox and rec.infobox.Tier
if tier ~= nil and tier ~= '' then
attrs = ' data-tier="' .. tostring(tier) .. '"'
end
table.insert(rows, '|-' .. attrs)
local cells = {}
for _, col in ipairs(columns) do
table.insert(cells, _cell(rec, col.source, opts))
end
-- Column separators stay plain `||` on a single `|` line, outside
-- any #if — conditional table markup breaks the wikitext parser.
table.insert(rows, '| ' .. table.concat(cells, ' || '))
end
table.insert(rows, '|}')
-- preprocess so any template call embedded in a cell expands (a
-- plain return from #invoke is not template-expanded again).
return mw.getCurrentFrame():preprocess(table.concat(rows, '\n'))
end
return p