Module:Infobox: Difference between revisions
From The Lands Of Liberos Project
Content added Content deleted
m (1 revision imported) Tag: Reverted |
No edit summary Tag: Reverted |
||
Line 1: | Line 1: | ||
-- <nowiki> |
|||
-------------------------------------------------------------------------------- |
|||
-- Infobox template module for [[w:c:dev]] documentation. |
|||
-- |
|||
-- @see [[:Category:Infobox templates]] |
|||
-- @usage {{#invoke:Infobox}} |
|||
-- @module infobox |
|||
-- @alias p |
|||
-- @version 1.1.2 |
|||
-- @author Speedit |
|||
-- @author DarthKitty |
|||
-- |
|||
-- @todo Fill holes in the documentation by replacing question marks. |
|||
-- @todo Use the already loaded data for sorting category data by name |
|||
-- instead of re-reading the page with mw.title.new in p.categoryDoc |
|||
-------------------------------------------------------------------------------- |
|||
local p = {} |
local p = {} |
||
local |
local yesno = require('Dev:Yesno') |
||
local getArgs = require('Dev:Arguments').getArgs |
|||
local origArgs = {} |
|||
local userError = require('Dev:User error') |
|||
local root |
|||
local wdsButton = require('Dev:WDS Button') |
|||
local empty_row_categories = {} |
|||
local i18n = require('Dev:I18n').loadMessages( |
|||
local category_in_empty_row_pattern = '%[%[%s*[Cc][Aa][Tt][Ee][Gg][Oo][Rr][Yy]%s*:[^]]*]]' |
|||
'Infobox', |
|||
local has_rows = false |
|||
'Common', |
|||
'Testharness' |
|||
) |
|||
local entrypoint = require('Dev:Entrypoint') |
|||
local data = mw.loadData('Dev:Infobox/data') |
|||
local title = mw.title.getCurrentTitle() |
|||
require('Dev:No interwiki access') |
|||
-------------------------------------------------------------------------------- |
|||
local function fixChildBoxes(sval, tt) |
|||
-- Date formatter utility. |
|||
local function notempty( s ) return s and s:match( '%S' ) end |
|||
-- |
|||
-- @see [[Template:FormatDate]] |
|||
if notempty(sval) then |
|||
-- |
|||
local marker = '<span class=special_infobox_marker>' |
|||
-- @param {string} d |
|||
local s = sval |
|||
-- Unprocessed date. |
|||
-- start moving templatestyles and categories inside of table rows |
|||
-- @param {string} f |
|||
local slast = '' |
|||
-- Date format to use. |
|||
while slast ~= s do |
|||
-- @returns {string} |
|||
slast = s |
|||
-- Formatted, localised date. |
|||
s = mw.ustring.gsub(s, '(</[Tt][Rr]%s*>%s*)(%[%[%s*[Cc][Aa][Tt][Ee][Gg][Oo][Rr][Yy]%s*:[^]]*%]%])', '%2%1') |
|||
-------------------------------------------------------------------------------- |
|||
s = mw.ustring.gsub(s, '(</[Tt][Rr]%s*>%s*)(\127[^\127]*UNIQ%-%-templatestyles%-%x+%-QINU[^\127]*\127)', '%2%1') |
|||
local function dtfm(d, f) |
|||
end |
|||
return mw.getCurrentFrame():expandTemplate{ |
|||
-- end moving templatestyles and categories inside of table rows |
|||
title = 'FormatDate', |
|||
s = mw.ustring.gsub(s, '(<%s*[Tt][Rr])', marker .. '%1') |
|||
args = { |
|||
s = mw.ustring.gsub(s, '(</[Tt][Rr]%s*>)', '%1' .. marker) |
|||
[1] = d, |
|||
if s:match(marker) then |
|||
dateformat = f, |
|||
s = mw.ustring.gsub(s, marker .. '%s*' .. marker, '') |
|||
uselang = i18n:getLang() |
|||
s = mw.ustring.gsub(s, '([\r\n]|-[^\r\n]*[\r\n])%s*' .. marker, '%1') |
|||
} |
|||
s = mw.ustring.gsub(s, marker .. '%s*([\r\n]|-)', '%1') |
|||
} |
|||
s = mw.ustring.gsub(s, '(</[Cc][Aa][Pp][Tt][Ii][Oo][Nn]%s*>%s*)' .. marker, '%1') |
|||
s = mw.ustring.gsub(s, '(<%s*[Tt][Aa][Bb][Ll][Ee][^<>]*>%s*)' .. marker, '%1') |
|||
s = mw.ustring.gsub(s, '^(%{|[^\r\n]*[\r\n]%s*)' .. marker, '%1') |
|||
s = mw.ustring.gsub(s, '([\r\n]%{|[^\r\n]*[\r\n]%s*)' .. marker, '%1') |
|||
s = mw.ustring.gsub(s, marker .. '(%s*</[Tt][Aa][Bb][Ll][Ee]%s*>)', '%1') |
|||
s = mw.ustring.gsub(s, marker .. '(%s*\n|%})', '%1') |
|||
end |
|||
if s:match(marker) then |
|||
local subcells = mw.text.split(s, marker) |
|||
s = '' |
|||
for k = 1, #subcells do |
|||
if k == 1 then |
|||
s = s .. subcells[k] .. '</' .. tt .. '></tr>' |
|||
elseif k == #subcells then |
|||
local rowstyle = ' style="display:none"' |
|||
if notempty(subcells[k]) then rowstyle = '' end |
|||
s = s .. '<tr' .. rowstyle ..'><' .. tt .. ' colspan=2>\n' .. |
|||
subcells[k] |
|||
elseif notempty(subcells[k]) then |
|||
if (k % 2) == 0 then |
|||
s = s .. subcells[k] |
|||
else |
|||
s = s .. '<tr><' .. tt .. ' colspan=2>\n' .. |
|||
subcells[k] .. '</' .. tt .. '></tr>' |
|||
end |
|||
end |
|||
end |
|||
end |
|||
-- the next two lines add a newline at the end of lists for the PHP parser |
|||
-- [[Special:Diff/849054481]] |
|||
-- remove when [[:phab:T191516]] is fixed or OBE |
|||
s = mw.ustring.gsub(s, '([\r\n][%*#;:][^\r\n]*)$', '%1\n') |
|||
s = mw.ustring.gsub(s, '^([%*#;:][^\r\n]*)$', '%1\n') |
|||
s = mw.ustring.gsub(s, '^([%*#;:])', '\n%1') |
|||
s = mw.ustring.gsub(s, '^(%{%|)', '\n%1') |
|||
return s |
|||
else |
|||
return sval |
|||
end |
|||
end |
end |
||
-------------------------------------------------------------------------------- |
|||
-- Cleans empty tables |
|||
-- Breadcrumb link generator. |
|||
local function cleanInfobox() |
|||
-- |
|||
root = tostring(root) |
|||
-- @param {string} t |
|||
if has_rows == false then |
|||
-- Breadcrumb part. |
|||
root = mw.ustring.gsub(root, '<table[^<>]*>%s*</table>', '') |
|||
-- @param {table} parts |
|||
end |
|||
-- Collection of title parts. |
|||
-- @returns {string} |
|||
-- Breadcrumb chunk. |
|||
-------------------------------------------------------------------------------- |
|||
local function crumbpart(parts) |
|||
local d = #parts |
|||
return table.concat({ |
|||
(d == 1 and '< ' or ' | '), |
|||
'[[', |
|||
table.concat(parts, '/'), |
|||
'|', |
|||
parts[d], |
|||
']]' |
|||
}) |
|||
end |
end |
||
-------------------------------------------------------------------------------- |
|||
-- Returns the union of the values of two tables, as a sequence. |
|||
-- Infobox breadcrumb generator for mobile. |
|||
local function union(t1, t2) |
|||
-- |
|||
-- @param {Frame} frame |
|||
-- Frame invocation object. |
|||
-- @returns {string} |
|||
-- Breadcrumb designed for mobile. |
|||
-------------------------------------------------------------------------------- |
|||
function p.breadcrumbs(frame) |
|||
local parts = {} |
|||
local ret = mw.html.create('center') |
|||
for t in tostring(title.fullText):gmatch('[^/]+') do |
|||
local vals = {} |
|||
table.insert(parts, t) |
|||
for k, v in pairs(t1) do |
|||
ret:wikitext(crumbpart(parts)) |
|||
vals[v] = true |
|||
end |
|||
for k, v in pairs(t2) do |
|||
vals[v] = true |
|||
end |
|||
local ret = {} |
|||
for k, v in pairs(vals) do |
|||
table.insert(ret, k) |
|||
end |
|||
return ret |
|||
end |
|||
return frame:preprocess(tostring(ret)) |
|||
-- Returns a table containing the numbers of the arguments that exist |
|||
-- for the specified prefix. For example, if the prefix was 'data', and |
|||
-- 'data1', 'data2', and 'data5' exist, it would return {1, 2, 5}. |
|||
local function getArgNums(prefix) |
|||
local nums = {} |
|||
for k, v in pairs(args) do |
|||
local num = tostring(k):match('^' .. prefix .. '([1-9]%d*)$') |
|||
if num then table.insert(nums, tonumber(num)) end |
|||
end |
|||
table.sort(nums) |
|||
return nums |
|||
end |
end |
||
-------------------------------------------------------------------------------- |
|||
-- Adds a row to the infobox, with either a header cell |
|||
-- Infobox data argument handler. Substitutes '$n' arguments with version |
|||
-- or a label/data cell combination. |
|||
-- numbers. |
|||
local function addRow(rowArgs) |
|||
-- |
|||
-- @usage {{#invoke:infobox|data|{{{Data}}}|ucfirst=1}} |
|||
if rowArgs.header and rowArgs.header ~= '_BLANK_' then |
|||
-- |
|||
has_rows = true |
|||
-- @param {Frame} frame |
|||
root |
|||
-- Frame invocation object. |
|||
:tag('tr') |
|||
-- @param {string} frame.args[1] |
|||
:addClass(rowArgs.rowclass) |
|||
-- Infobox data input. |
|||
:cssText(rowArgs.rowstyle) |
|||
-- @param {string} frame.args.ucfirst |
|||
:tag('th') |
|||
-- Capitalization boolean. |
|||
:attr('colspan', '2') |
|||
-- @throws {string} |
|||
:addClass('infobox-header') |
|||
-- 'missing argument from Module:Infobox in p.data' |
|||
:addClass(rowArgs.class) |
|||
-- @returns {string} |
|||
:addClass(args.headerclass) |
|||
-- Argument-substituted infobox data. |
|||
-- @deprecated next; target .infobox-<name> .infobox-header |
|||
-------------------------------------------------------------------------------- |
|||
:cssText(args.headerstyle) |
|||
function p.data(frame) |
|||
:cssText(rowArgs.rowcellstyle) |
|||
if not (frame.args or {})[1] then |
|||
:wikitext(fixChildBoxes(rowArgs.header, 'th')) |
|||
error('missing argument from Module:Infobox in p.data') |
|||
if rowArgs.data then |
|||
end |
|||
root:wikitext( |
|||
'[[Category:Pages which use infobox templates with ignored data cells]]' |
|||
) |
|||
end |
|||
elseif rowArgs.data and rowArgs.data:gsub( |
|||
category_in_empty_row_pattern, '' |
|||
):match('^%S') then |
|||
has_rows = true |
|||
local row = root:tag('tr') |
|||
row:addClass(rowArgs.rowclass) |
|||
row:cssText(rowArgs.rowstyle) |
|||
if rowArgs.label then |
|||
row |
|||
:tag('th') |
|||
:attr('scope', 'row') |
|||
:addClass('infobox-label') |
|||
-- @deprecated next; target .infobox-<name> .infobox-label |
|||
:cssText(args.labelstyle) |
|||
:cssText(rowArgs.rowcellstyle) |
|||
:wikitext(rowArgs.label) |
|||
:done() |
|||
end |
|||
local tArgs = frame:getParent().args |
|||
local ret = frame.args[1] |
|||
dataCell |
|||
local uc1 = yesno(mw.text.trim(frame.args.ucfirst or '')) |
|||
:attr('colspan', not rowArgs.label and '2' or nil) |
|||
:addClass(not rowArgs.label and 'infobox-full-data' or 'infobox-data') |
|||
:addClass(rowArgs.class) |
|||
-- @deprecated next; target .infobox-<name> .infobox(-full)-data |
|||
:cssText(rowArgs.datastyle) |
|||
:cssText(rowArgs.rowcellstyle) |
|||
:wikitext(fixChildBoxes(rowArgs.data, 'td')) |
|||
else |
|||
table.insert(empty_row_categories, rowArgs.data or '') |
|||
end |
|||
end |
|||
if not string.find(ret, '%$') then |
|||
local function renderTitle() |
|||
return ret |
|||
end |
|||
-- Argument substitution. |
|||
has_rows = true |
|||
local function repl(d) |
|||
root |
|||
local rsub = d == '1' |
|||
:tag('caption') |
|||
and (tArgs.Submodule or i18n:msg('original')) |
|||
:addClass('infobox-title') |
|||
or (tArgs['Submodule' .. d] or i18n:msg('version', d)) |
|||
:addClass(args.titleclass) |
|||
-- @deprecated next; target .infobox-<name> .infobox-title |
|||
return uc1 |
|||
:cssText(args.titlestyle) |
|||
and rsub:gsub('^%l', mw.ustring.upper) |
|||
:wikitext(args.title) |
|||
or rsub |
|||
end |
|||
end |
|||
ret = ret:gsub('%$(%d+)', repl) |
|||
local function renderAboveRow() |
|||
if not args.above then return end |
|||
return ret |
|||
has_rows = true |
|||
root |
|||
:tag('tr') |
|||
:tag('th') |
|||
:attr('colspan', '2') |
|||
:addClass('infobox-above') |
|||
:addClass(args.aboveclass) |
|||
-- @deprecated next; target .infobox-<name> .infobox-above |
|||
:cssText(args.abovestyle) |
|||
:wikitext(fixChildBoxes(args.above,'th')) |
|||
end |
end |
||
-------------------------------------------------------------------------------- |
|||
local function renderBelowRow() |
|||
-- Infobox date list generator with version numbers. |
|||
if not args.below then return end |
|||
-- |
|||
-- @usage {{#invoke:infobox|date}} |
|||
-- |
|||
-- @param {Frame} frame |
|||
-- Frame invocation object. |
|||
-- @param {string} frame.args |
|||
-- Invocation arguments. |
|||
-- @param {string} frame.args.ext |
|||
-- Code type. |
|||
-- @param {table} frame:getParent().args |
|||
-- Template arguments. |
|||
-- @returns {string} |
|||
-- Formatted multi-line date string. |
|||
-------------------------------------------------------------------------------- |
|||
function p.date(frame) |
|||
local tArgs = frame:getParent().args |
|||
local dateFmt = tArgs.dateformat or '' |
|||
local ret = '' |
|||
if tArgs.Updated and #tArgs.Updated > 0 then |
|||
has_rows = true |
|||
-- Generated first formatted date. |
|||
root |
|||
ret = dtfm(tArgs.Updated, dateFmt) |
|||
:tag('tr') |
|||
:tag('td') |
|||
:attr('colspan', '2') |
|||
:addClass('infobox-below') |
|||
:addClass(args.belowclass) |
|||
-- @deprecated next; target .infobox-<name> .infobox-below |
|||
:cssText(args.belowstyle) |
|||
:wikitext(fixChildBoxes(args.below,'td')) |
|||
end |
|||
if tArgs.Submodule or tArgs.Updated2 then |
|||
local function addSubheaderRow(subheaderArgs) |
|||
-- Append first version tag. |
|||
if subheaderArgs.data and |
|||
local sub1 = tArgs.Submodule or i18n:msg('original') |
|||
subheaderArgs.data:gsub(category_in_empty_row_pattern, ''):match('^%S') then |
|||
has_rows = true |
|||
local row = root:tag('tr') |
|||
row:addClass(subheaderArgs.rowclass) |
|||
if sub1 ~= '' then |
|||
local dataCell = row:tag('td') |
|||
ret = ret .. ' (' .. sub1 .. ')' |
|||
dataCell |
|||
end |
|||
:attr('colspan', '2') |
|||
end |
|||
:addClass('infobox-subheader') |
|||
:addClass(subheaderArgs.class) |
|||
:cssText(subheaderArgs.datastyle) |
|||
:cssText(subheaderArgs.rowcellstyle) |
|||
:wikitext(fixChildBoxes(subheaderArgs.data, 'td')) |
|||
else |
|||
table.insert(empty_row_categories, subheaderArgs.data or '') |
|||
end |
|||
end |
|||
-- Handle further versions. |
|||
local function renderSubheaders() |
|||
for d = 2, math.huge do |
|||
if args.subheader then |
|||
local p = tArgs['Updated' .. d] |
|||
args.subheader1 = args.subheader |
|||
end |
|||
if args.subheaderrowclass then |
|||
args.subheaderrowclass1 = args.subheaderrowclass |
|||
end |
|||
local subheadernums = getArgNums('subheader') |
|||
for k, num in ipairs(subheadernums) do |
|||
addSubheaderRow({ |
|||
data = args['subheader' .. tostring(num)], |
|||
-- @deprecated next; target .infobox-<name> .infobox-subheader |
|||
datastyle = args.subheaderstyle, |
|||
rowcellstyle = args['subheaderstyle' .. tostring(num)], |
|||
class = args.subheaderclass, |
|||
rowclass = args['subheaderrowclass' .. tostring(num)] |
|||
}) |
|||
end |
|||
end |
|||
if not p then |
|||
local function addImageRow(imageArgs) |
|||
break |
|||
end |
|||
local s = tArgs['Submodule' .. d] or i18n:msg('version', d) |
|||
if imageArgs.data and |
|||
imageArgs.data:gsub(category_in_empty_row_pattern, ''):match('^%S') then |
|||
ret = (d == 2 and '* ' or '') .. ret |
|||
has_rows = true |
|||
.. '\n* ' |
|||
local row = root:tag('tr') |
|||
.. dtfm(p, dateFmt) |
|||
row:addClass(imageArgs.rowclass) |
|||
.. ' (' .. s .. ')' |
|||
end |
|||
-- Default date field. |
|||
local dataCell = row:tag('td') |
|||
elseif tArgs.Code then |
|||
dataCell |
|||
-- @todo Use DPL template to extract main code page? |
|||
:attr('colspan', '2') |
|||
local ext = frame.args.ext or 'js' |
|||
:addClass('infobox-image') |
|||
local suffix = '.' .. ext |
|||
:addClass(imageArgs.class) |
|||
:cssText(imageArgs.datastyle) |
|||
local u = frame:expandTemplate{ |
|||
:wikitext(fixChildBoxes(imageArgs.data, 'td')) |
|||
title = 'Updated', |
|||
else |
|||
args = { |
|||
table.insert(empty_row_categories, imageArgs.data or '') |
|||
'MediaWiki:' .. title.baseText .. suffix |
|||
end |
|||
} |
|||
} |
|||
ret = dtfm(u, dateFmt) |
|||
end |
|||
return ret |
|||
end |
end |
||
-------------------------------------------------------------------------------- |
|||
local function renderImages() |
|||
-- Category formatter. |
|||
if args.image then |
|||
-- |
|||
args.image1 = args.image |
|||
-- @param {table} tbl |
|||
end |
|||
-- Array of text items to be returned. |
|||
if args.caption then |
|||
-- @param {string} cat |
|||
args.caption1 = args.caption |
|||
-- Category name. |
|||
end |
|||
-- @param {string} sortkey |
|||
local imagenums = getArgNums('image') |
|||
-- Category sortkey. |
|||
for k, num in ipairs(imagenums) do |
|||
-------------------------------------------------------------------------------- |
|||
local caption = args['caption' .. tostring(num)] |
|||
local function category(tbl, cat, sortkey) |
|||
local data = mw.html.create():wikitext(args['image' .. tostring(num)]) |
|||
table.insert(tbl, '[[Category:') |
|||
if caption then |
|||
table.insert(tbl, cat) |
|||
data |
|||
:tag('div') |
|||
if sortkey and sortkey ~= '' then |
|||
:addClass('infobox-caption') |
|||
table.insert(tbl, '|') |
|||
-- @deprecated next; target .infobox-<name> .infobox-caption |
|||
table.insert(tbl, sortkey) |
|||
:cssText(args.captionstyle) |
|||
end |
|||
:wikitext(caption) |
|||
end |
|||
table.insert(tbl, ']]') |
|||
addImageRow({ |
|||
data = tostring(data), |
|||
-- @deprecated next; target .infobox-<name> .infobox-image |
|||
datastyle = args.imagestyle, |
|||
class = args.imageclass, |
|||
rowclass = args['imagerowclass' .. tostring(num)] |
|||
}) |
|||
end |
|||
end |
end |
||
-------------------------------------------------------------------------------- |
|||
-- When autoheaders are turned on, preprocesses the rows |
|||
-- Returns error for missing description param in infobox. |
|||
local function preprocessRows() |
|||
-- |
|||
if not args.autoheaders then return end |
|||
-- @returns {string} |
|||
-- Error message and trancking category |
|||
local rownums = union(getArgNums('header'), getArgNums('data')) |
|||
-------------------------------------------------------------------------------- |
|||
table.sort(rownums) |
|||
function p.description() |
|||
local lastheader |
|||
return userError('Description missing', 'Content without description') |
|||
for k, num in ipairs(rownums) do |
|||
if args['header' .. tostring(num)] then |
|||
if lastheader then |
|||
args['header' .. tostring(lastheader)] = nil |
|||
end |
|||
lastheader = num |
|||
elseif args['data' .. tostring(num)] and |
|||
args['data' .. tostring(num)]:gsub( |
|||
category_in_empty_row_pattern, '' |
|||
):match('^%S') then |
|||
local data = args['data' .. tostring(num)] |
|||
if data:gsub(category_in_empty_row_pattern, ''):match('%S') then |
|||
lastheader = nil |
|||
end |
|||
end |
|||
end |
|||
if lastheader then |
|||
args['header' .. tostring(lastheader)] = nil |
|||
end |
|||
end |
end |
||
-------------------------------------------------------------------------------- |
|||
-- Gets the union of the header and data argument numbers, |
|||
-- Infobox category generator for type subcategorization. |
|||
-- and renders them all in order |
|||
-- |
|||
local function renderRows() |
|||
-- @param {Frame} frame |
|||
-- Frame invocation object. |
|||
-- @returns {string} |
|||
-- Type categories corresponding to `Type` infobox argument. |
|||
-------------------------------------------------------------------------------- |
|||
function p.categories(frame) |
|||
local ret = {} |
|||
local typ = frame.args[1] |
|||
local tArgs = p.getParent(frame).args |
|||
local catKeys = tArgs.Type |
|||
if |
|||
local rownums = union(getArgNums('header'), getArgNums('data')) |
|||
mw.ustring.lower(tArgs.Status or '') == 'archive' or |
|||
table.sort(rownums) |
|||
not data.categories[typ or ''] or |
|||
for k, num in ipairs(rownums) do |
|||
title.namespace ~= 0 |
|||
addRow({ |
|||
then |
|||
header = args['header' .. tostring(num)], |
|||
return '' |
|||
label = args['label' .. tostring(num)], |
|||
end |
|||
data = args['data' .. tostring(num)], |
|||
datastyle = args.datastyle, |
|||
local sortkey = tArgs.Title or title.prefixedText |
|||
class = args['class' .. tostring(num)], |
|||
rowclass = args['rowclass' .. tostring(num)], |
|||
category(ret, typ, sortkey) |
|||
-- @deprecated next; target .infobox-<name> rowclass |
|||
table.insert(ret, '[[:Category:') |
|||
rowstyle = args['rowstyle' .. tostring(num)], |
|||
table.insert(ret, typ) |
|||
rowcellstyle = args['rowcellstyle' .. tostring(num)] |
|||
table.insert(ret, '|') |
|||
}) |
|||
table.insert(ret, typ) |
|||
end |
|||
table.insert(ret, ']]') |
|||
end |
|||
-- Maintenance category |
|||
local REPORTCAT = 'Content without type categorization' |
|||
local TYPEDOC = ':Category:' .. REPORTCAT .. '#Documentation' |
|||
if catKeys then |
|||
for v in mw.text.gsplit(mw.ustring.lower(catKeys), '%s*,%s*') do |
|||
local cat = data.categories[typ][mw.text.trim(v)] |
|||
if cat then |
|||
local function renderNavBar() |
|||
category(ret, cat, sortkey) |
|||
if not args.name then return end |
|||
end |
|||
end |
|||
elseif typ == 'JavaScript' then |
|||
table.insert(ret, '<br />') |
|||
table.insert(ret, userError('Type categorization missing', REPORTCAT)) |
|||
table.insert(ret, ' ([[:' .. TYPEDOC .. '|') |
|||
table.insert( |
|||
ret, |
|||
mw.message.new('oasis-more'):useDatabase(false):plain() |
|||
) |
|||
table.insert(ret, ']])') |
|||
end |
|||
return table.concat(ret) |
|||
has_rows = true |
|||
root |
|||
:tag('tr') |
|||
:tag('td') |
|||
:attr('colspan', '2') |
|||
:addClass('infobox-navbar') |
|||
:wikitext(require('Module:Navbar')._navbar{ |
|||
args.name, |
|||
mini = 1, |
|||
}) |
|||
end |
end |
||
-------------------------------------------------------------------------------- |
|||
local function renderItalicTitle() |
|||
-- Category documentation generator. |
|||
local italicTitle = args['italic title'] and mw.ustring.lower(args['italic title']) |
|||
-- |
|||
if italicTitle == '' or italicTitle == 'force' or italicTitle == 'yes' then |
|||
-- @param {Frame} frame |
|||
root:wikitext(mw.getCurrentFrame():expandTemplate({title = 'italic title'})) |
|||
-- Frame invocation object. |
|||
end |
|||
-- @param {table} frame.args |
|||
-- Frame argument table. |
|||
-- @param {string} frame.args[1] |
|||
-- Infobox type corresponding to [[Module:Infobox/data]]. |
|||
-- @returns {string} |
|||
-- Table of types against descriptions. |
|||
-------------------------------------------------------------------------------- |
|||
function p.categoryDoc(frame) |
|||
local typ = frame.args[1] or '' |
|||
local ret = mw.html.create('table'):attr { |
|||
['class'] = 'WikiaTable', |
|||
['border'] = '1', |
|||
['id'] = 'types' |
|||
} |
|||
ret:tag('tr') |
|||
:tag('th'):wikitext(i18n:msg('type')) |
|||
:done() |
|||
:tag('th'):wikitext(i18n:msg('description')) |
|||
:done() |
|||
if |
|||
not data.categories[typ] or |
|||
not data.descriptions[typ] |
|||
then |
|||
return ret |
|||
end |
|||
-- Extract categories from data. |
|||
local catData = {} |
|||
local catNames = {} |
|||
for k, n in pairs(data.categories[typ]) do |
|||
if not catData[n] then |
|||
catNames[#catNames + 1] = n |
|||
catData[n] = {k} |
|||
else |
|||
catData[n][#catData[n] + 1] = k |
|||
end |
|||
if |
|||
not catData[n].description and |
|||
data.descriptions[typ][k] |
|||
then |
|||
catData[n].description = data.descriptions[typ][k] |
|||
end |
|||
end |
|||
-- Sort category data by name. |
|||
local dataContent = mw.title.new('Module:Infobox/data'):getContent() |
|||
local function sortKey(a, b) |
|||
local i1, i2 = |
|||
dataContent:find('"' .. a .. '"'), |
|||
dataContent:find('"' .. b .. '"') |
|||
return i1 < i2 |
|||
end |
|||
table.sort(catNames, sortKey) |
|||
-- Render documentation table. |
|||
local cat, catRow, catKeys, desc |
|||
for i, n in ipairs(catNames) do |
|||
cat = catData[n] |
|||
catRow = ret:tag('tr') |
|||
catKeys = catRow:tag('td') |
|||
-- Handle multiple keys. |
|||
if #cat >= 2 then |
|||
catKeys = catKeys:tag('ul') |
|||
for i, k in ipairs(cat) do |
|||
catKeys |
|||
:tag('li'):tag('code') |
|||
:wikitext(k) |
|||
end |
|||
else |
|||
catKeys |
|||
:tag('code') |
|||
:wikitext(cat[1]) |
|||
end |
|||
-- Add description. |
|||
desc = cat.description:gsub('^%l', mw.ustring.upper) .. '.' |
|||
catRow:tag('td') |
|||
:wikitext('[[:Category:' .. n .. ']]') |
|||
:tag('p'):wikitext(desc) |
|||
end |
|||
return tostring(ret) |
|||
end |
end |
||
-------------------------------------------------------------------------------- |
|||
-- Categories in otherwise empty rows are collected in empty_row_categories. |
|||
-- Category description generator. Used on category pages to describe pages. |
|||
-- This function adds them to the module output. It is not affected by |
|||
-- |
|||
-- args.decat because this module should not prevent module-external categories |
|||
-- @param {Frame} frame |
|||
-- from rendering. |
|||
-- Frame invocation object. |
|||
local function renderEmptyRowCategories() |
|||
-- @returns {string} |
|||
for _, s in ipairs(empty_row_categories) do |
|||
-- Category description followed by parent category link. |
|||
root:wikitext(s) |
|||
-------------------------------------------------------------------------------- |
|||
end |
|||
function p.categoryDesc(frame) |
|||
local typ = frame.args[1] or '' |
|||
local key = frame.args[2] or '' |
|||
local ret = data.messages.description |
|||
local desc = (data.descriptions[typ] or {})[key] |
|||
if not desc then |
|||
error('misconfigured arguments in p.categoryDesc from Module:Install') |
|||
end |
|||
ret = ret:gsub('$1', desc) .. '[[Category:' .. typ .. '|{{SUBPAGENAME}}]]' |
|||
return frame:preprocess(ret) |
|||
end |
end |
||
-------------------------------------------------------------------------------- |
|||
-- Render tracking categories. args.decat == turns off tracking categories. |
|||
-- Alias mapper for {{t|Infobox Lua}} `Type` argument. |
|||
local function renderTrackingCategories() |
|||
-- |
|||
if args.decat == 'yes' then return end |
|||
-- @param {Frame} frame |
|||
if args.child == 'yes' then |
|||
-- Frame invocation object. |
|||
if args.title then |
|||
-- @returns {string} |
|||
root:wikitext( |
|||
-- Lua type (`'invocable'` or `'meta'`). |
|||
'[[Category:Pages which use embedded infobox templates with the title parameter]]' |
|||
-------------------------------------------------------------------------------- |
|||
) |
|||
function p.luaType(frame) |
|||
end |
|||
local tArgs = frame:getParent().args |
|||
elseif #(getArgNums('data')) == 0 and mw.title.getCurrentTitle().namespace == 0 then |
|||
local typ = tArgs.Type or tArgs.type |
|||
root:wikitext('[[Category:Articles which use infobox templates with no data rows]]') |
|||
end |
|||
return typ |
|||
and data.luaTypes[typ:lower()] |
|||
or '' |
|||
end |
end |
||
-------------------------------------------------------------------------------- |
|||
--[=[ |
|||
-- Test suite status. |
|||
Loads the templatestyles for the infobox. |
|||
-- |
|||
-- @param {Frame} frame |
|||
-- Frame invocation object. |
|||
-- @returns {string} |
|||
-- Test suite status badge. |
|||
-------------------------------------------------------------------------------- |
|||
function p.luaSuite(frame) |
|||
local page = |
|||
frame:getParent().Code or |
|||
mw.language.fetchLanguageName(title.subpageText) == '' |
|||
and title.subpageText |
|||
or title.baseText:match('/*([^/]+)$') |
|||
-- DPL query for categories |
|||
TODO: FINISH loading base templatestyles here rather than in |
|||
local query = table.concat({ |
|||
MediaWiki:Common.css. There are 4-5000 pages with 'raw' infobox tables. |
|||
'{{#dpl:', |
|||
See [[Mediawiki_talk:Common.css/to_do#Infobox]] and/or come help :). |
|||
'| debug = 0', |
|||
When we do this we should clean up the inline CSS below too. |
|||
'| mode = userformat', |
|||
Will have to do some bizarre conversion category like with sidebar. |
|||
'| allowcachedresults = 1', |
|||
'| category = Lua test suites', |
|||
'| titleregexp = ^' .. mw.uri.encode(page, 'WIKI') .. '/testcases$', |
|||
'| addcategories = true', |
|||
'| format = ,%CATNAMES%,,', |
|||
'}}' |
|||
}, '\n') |
|||
-- Fetch category data. |
|||
]=] |
|||
local cats = mw.text.split(frame:preprocess(query), ',') |
|||
local function loadTemplateStyles() |
|||
--[=[ |
|||
local frame = mw.getCurrentFrame() |
|||
-- @todo Fix this: |
|||
if cats[2] and data.luaStatus[cats[2]] |
|||
-- See function description |
|||
and cats[1] == 'Lua test suites' |
|||
local base_templatestyles = frame:extensionTag{ |
|||
and cats[2] ~= 'Lua test suites' |
|||
name = 'templatestyles', args = { src = 'Module:Infobox/styles.css' } |
|||
then |
|||
} |
|||
-- DPL sorts pages alphabetically, so if [[Module talk:PAGE/testcases]] |
|||
-- contains [[:Category:Lua test suites]], then [[:Category:Skipped Lua test suites]] |
|||
-- and [[:Category:Pages with script errors]] end up ingored, |
|||
-- and the test suite is incorrectly reported as passing. |
|||
cats[1], cats[2] = cats[2], cats[1] |
|||
end |
|||
]=] |
|||
local i18n_key = data.luaStatus[cats[1] or ''] or 'unknown' |
|||
-- Build badge. |
|||
local templatestyles = '' |
|||
local badge = mw.html.create('div') |
|||
if args['templatestyles'] then templatestyles = frame:extensionTag{ |
|||
:addClass('plainlinks') |
|||
name = 'templatestyles', args = { src = args['templatestyles'] } |
|||
:wikitext( |
|||
} |
|||
'[' .. |
|||
end |
|||
mw.site.server .. |
|||
mw.uri.fullUrl('Module talk:' .. page .. '/testcases').path .. |
|||
local child_templatestyles = '' |
|||
' ' .. |
|||
if args['child templatestyles'] then child_templatestyles = frame:extensionTag{ |
|||
wdsButton._badge(i18n:msg(i18n_key), i18n_key) .. |
|||
name = 'templatestyles', args = { src = args['child templatestyles'] } |
|||
']' |
|||
} |
|||
) |
|||
end |
|||
local grandchild_templatestyles = '' |
|||
if args['grandchild templatestyles'] then grandchild_templatestyles = frame:extensionTag{ |
|||
name = 'templatestyles', args = { src = args['grandchild templatestyles'] } |
|||
} |
|||
end |
|||
return tostring(badge) |
|||
base_templatestyles, -- see function description |
|||
templatestyles, |
|||
child_templatestyles, |
|||
grandchild_templatestyles |
|||
}) |
|||
end |
end |
||
--- Set of valid scope aliases |
|||
-- common functions between the child and non child cases |
|||
local scopeAliases = { |
|||
local function structure_infobox_common() |
|||
p = "personal", |
|||
renderSubheaders() |
|||
s = "site-wide", |
|||
renderImages() |
|||
mw = "vanilla mediawiki", |
|||
preprocessRows() |
|||
} |
|||
renderRows() |
|||
renderBelowRow() |
|||
--- Set of valid scope names |
|||
renderNavBar() |
|||
local validScopes = { |
|||
renderItalicTitle() |
|||
["personal"] = true, |
|||
renderEmptyRowCategories() |
|||
["site-wide"] = true, |
|||
renderTrackingCategories() |
|||
["vanilla mediawiki"] = true, |
|||
cleanInfobox() |
|||
} |
|||
end |
|||
--- Dictionary of "scope" → "i18n" key mapping |
|||
local scopeLangMap = { |
|||
["site-wide"] = "sitewide", |
|||
["vanilla mediawiki"] = "vanilla-mw" |
|||
} |
|||
--- Dictionary of "scope" → "category" subpage mapping |
|||
local scopeCategoryMap = { |
|||
["personal"] = "Personal use", |
|||
["site-wide"] = "Site-wide use", |
|||
["vanilla mediawiki"] = "Vanilla MediaWiki use" |
|||
} |
|||
--- The order in which scopes are displayed in the infobox |
|||
local scopeOrder = { |
|||
"personal", |
|||
"site-wide", |
|||
"vanilla mediawiki", |
|||
} |
|||
-------------------------------------------------------------------------------- |
|||
-- Function used by {{T|scope}}. |
|||
-- |
|||
-- @param {Frame|table} frame |
|||
-- Scribunto Frame object or table of arguments. |
|||
-- @returns {string} |
|||
-- The parsed scopes |
|||
-------------------------------------------------------------------------------- |
|||
function p.parseScope(frame) |
|||
local args = getArgs(frame) |
|||
local arg1 = mw.ustring.lower(args[1] or "") |
|||
-- Specify the overall layout of the infobox, with special settings if the |
|||
local type = args.type |
|||
-- infobox is used as a 'child' inside another infobox. |
|||
local |
local rec = yesno(args.pr) |
||
if args.child ~= 'yes' then |
|||
root = mw.html.create('table') |
|||
local usingLegacyAlias = false |
|||
root |
|||
local hasUnknownScope = false |
|||
:addClass(args.subbox == 'yes' and 'infobox-subbox' or 'infobox') |
|||
local scopes = {} |
|||
:addClass(args.bodyclass) |
|||
-- @deprecated next; target .infobox-<name> |
|||
:cssText(args.bodystyle) |
|||
local function explodeScope(scopeName, callback) |
|||
renderTitle() |
|||
if not scopeName or #scopeName == 0 then return end |
|||
renderAboveRow() |
|||
else |
|||
root = mw.html.create() |
|||
scopeName = scopeAliases[scopeName] or scopeName |
|||
root |
|||
if validScopes[scopeName] then |
|||
:wikitext(args.title) |
|||
scopes[scopeName] = true |
|||
elseif scopeName == "ps" then |
|||
-- Legacy compat |
|||
scopes["personal"] = true |
|||
scopes["site-wide"] = true |
|||
usingLegacyAlias = true |
|||
elseif callback then |
|||
callback(scopeName) |
|||
else |
|||
hasUnknownScope = true |
|||
end |
|||
end |
end |
||
structure_infobox_common() |
|||
return loadTemplateStyles() .. root |
|||
end |
|||
local function explodeScope2(splitSymbol) |
|||
-- If the argument exists and isn't blank, add it to the argument table. |
|||
for splitWS in mw.text.gsplit(splitSymbol, "%s+") do |
|||
-- Blank arguments are treated as nil to match the behaviour of ParserFunctions. |
|||
explodeScope(splitWS) |
|||
local function preprocessSingleArg(argName) |
|||
end |
|||
if origArgs[argName] and origArgs[argName] ~= '' then |
|||
args[argName] = origArgs[argName] |
|||
end |
end |
||
end |
|||
for splitBr in mw.text.gsplit(arg1, "%s-<br%f[%s/>].-/?>%s*") do |
|||
-- Assign the parameters with the given prefixes to the args table, in order, in |
|||
for splitSymbol in mw.text.gsplit(splitBr, "%s*[/,]%s*") do |
|||
-- batches of the step size specified. This is to prevent references etc. from |
|||
explodeScope(splitSymbol, explodeScope2) |
|||
-- appearing in the wrong order. The prefixTable should be an array containing |
|||
end |
|||
-- tables, each of which has two possible fields, a "prefix" string and a |
|||
-- "depend" table. The function always parses parameters containing the "prefix" |
|||
-- string, but only parses parameters in the "depend" table if the prefix |
|||
-- parameter is present and non-blank. |
|||
local function preprocessArgs(prefixTable, step) |
|||
if type(prefixTable) ~= 'table' then |
|||
error("Non-table value detected for the prefix table", 2) |
|||
end |
|||
if type(step) ~= 'number' then |
|||
error("Invalid step value detected", 2) |
|||
end |
end |
||
local cats = {} |
|||
-- Get arguments without a number suffix, and check for bad input. |
|||
local result = {} |
|||
for i,v in ipairs(prefixTable) do |
|||
if type(v) ~= 'table' or type(v.prefix) ~= "string" or |
|||
for _, scopeName in ipairs(scopeOrder) do |
|||
(v.depend and type(v.depend) ~= 'table') then |
|||
if scopes[scopeName] then |
|||
error('Invalid input detected to preprocessArgs prefix table', 2) |
|||
if type then |
|||
end |
|||
table.insert(cats, "[[Category:" .. type .. "/" .. scopeCategoryMap[scopeName] .. "]]") |
|||
preprocessSingleArg(v.prefix) |
|||
end |
|||
-- Only parse the depend parameter if the prefix parameter is present |
|||
local scopeString = i18n:msg(scopeLangMap[scopeName] or scopeName) |
|||
-- and not blank. |
|||
if |
if scopeName == "personal" and rec then |
||
scopeString = scopeString .. " (" .. i18n:msg("recommended") .. ")" |
|||
for j, dependValue in ipairs(v.depend) do |
|||
if type(dependValue) ~= 'string' then |
|||
error('Invalid "depend" parameter value detected in preprocessArgs') |
|||
end |
|||
preprocessSingleArg(dependValue) |
|||
end |
end |
||
table.insert(result, scopeString) |
|||
end |
end |
||
end |
end |
||
if (#result == 0 or hasUnknownScope) and type then |
|||
-- Get arguments with number suffixes. |
|||
table.insert(cats, "[[Category:" .. type .. "/Unknown use]]") |
|||
local a = 1 -- Counter variable. |
|||
end |
|||
local moreArgumentsExist = true |
|||
while moreArgumentsExist == true do |
|||
if usingLegacyAlias then |
|||
moreArgumentsExist = false |
|||
table.insert(cats, "[[Category:Pages using deprecated Template:Scope values]]") |
|||
for i = a, a + step - 1 do |
|||
end |
|||
for j,v in ipairs(prefixTable) do |
|||
local prefixArgName = v.prefix .. tostring(i) |
|||
if (#result > 1) then |
|||
local ul = mw.html.create("ul") |
|||
-- Do another loop if any arguments are found, even blank ones. |
|||
for _, v in ipairs(result) do |
|||
moreArgumentsExist = true |
|||
ul:tag("li"):wikitext(v) |
|||
preprocessSingleArg(prefixArgName) |
|||
end |
|||
-- Process the depend table if the prefix argument is present |
|||
-- and not blank, or we are processing "prefix1" and "prefix" is |
|||
-- present and not blank, and if the depend table is present. |
|||
if v.depend and (args[prefixArgName] or (i == 1 and args[v.prefix])) then |
|||
for j,dependValue in ipairs(v.depend) do |
|||
local dependArgName = dependValue .. tostring(i) |
|||
preprocessSingleArg(dependArgName) |
|||
end |
|||
end |
|||
end |
|||
end |
end |
||
return tostring(ul) .. table.concat(cats) |
|||
a = a + step |
|||
end |
end |
||
return (result[1] or i18n:msg("unknown")) .. table.concat(cats) |
|||
end |
end |
||
-------------------------------------------------------------------------------- |
|||
-- Parse the data parameters in the same order that the old {{infobox}} did, so |
|||
-- Template wrapper for [[Template:Infobox]]. |
|||
-- that references etc. will display in the expected places. Parameters that |
|||
-- |
|||
-- depend on another parameter are only processed if that parameter is present, |
|||
-- @usage {{#invoke:infobox|main}} |
|||
-- to avoid phantom references appearing in article reference lists. |
|||
-- |
|||
local function parseDataParameters() |
|||
-- @function p.main |
|||
-- @param {Frame} frame |
|||
-- Frame invocation object. |
|||
-- @returns {string|nil} |
|||
-- Package function output. |
|||
-------------------------------------------------------------------------------- |
|||
p.main = entrypoint(p) |
|||
-------------------------------------------------------------------------------- |
|||
preprocessSingleArg('autoheaders') |
|||
-- Returns topmost parent frame. |
|||
preprocessSingleArg('child') |
|||
-- |
|||
preprocessSingleArg('bodyclass') |
|||
-- @param {Frame} frame |
|||
preprocessSingleArg('subbox') |
|||
-- Frame invocation object. |
|||
preprocessSingleArg('bodystyle') |
|||
-- @returns {table} |
|||
preprocessSingleArg('title') |
|||
-- Highest parent frame. |
|||
preprocessSingleArg('titleclass') |
|||
-------------------------------------------------------------------------------- |
|||
preprocessSingleArg('titlestyle') |
|||
function p.getParent(frame) |
|||
preprocessSingleArg('above') |
|||
local cf = frame |
|||
preprocessSingleArg('aboveclass') |
|||
local pf = frame:getParent() |
|||
preprocessSingleArg('abovestyle') |
|||
preprocessArgs({ |
|||
{prefix = 'subheader', depend = {'subheaderstyle', 'subheaderrowclass'}} |
|||
}, 10) |
|||
preprocessSingleArg('subheaderstyle') |
|||
preprocessSingleArg('subheaderclass') |
|||
preprocessArgs({ |
|||
{prefix = 'image', depend = {'caption', 'imagerowclass'}} |
|||
}, 10) |
|||
preprocessSingleArg('captionstyle') |
|||
preprocessSingleArg('imagestyle') |
|||
preprocessSingleArg('imageclass') |
|||
preprocessArgs({ |
|||
{prefix = 'header'}, |
|||
{prefix = 'data', depend = {'label'}}, |
|||
{prefix = 'rowclass'}, |
|||
{prefix = 'rowstyle'}, |
|||
{prefix = 'rowcellstyle'}, |
|||
{prefix = 'class'} |
|||
}, 50) |
|||
preprocessSingleArg('headerclass') |
|||
preprocessSingleArg('headerstyle') |
|||
preprocessSingleArg('labelstyle') |
|||
preprocessSingleArg('datastyle') |
|||
preprocessSingleArg('below') |
|||
preprocessSingleArg('belowclass') |
|||
preprocessSingleArg('belowstyle') |
|||
preprocessSingleArg('name') |
|||
-- different behaviour for italics if blank or absent |
|||
args['italic title'] = origArgs['italic title'] |
|||
preprocessSingleArg('decat') |
|||
preprocessSingleArg('templatestyles') |
|||
preprocessSingleArg('child templatestyles') |
|||
preprocessSingleArg('grandchild templatestyles') |
|||
end |
|||
if pf then |
|||
-- If called via #invoke, use the args passed into the invoking template. |
|||
return p.getParent(pf) |
|||
-- Otherwise, for testing purposes, assume args are being passed directly in. |
|||
else |
|||
function p.infobox(frame) |
|||
return cf |
|||
if frame == mw.getCurrentFrame() then |
|||
end |
|||
origArgs = frame:getParent().args |
|||
else |
|||
origArgs = frame |
|||
end |
|||
parseDataParameters() |
|||
return _infobox() |
|||
end |
end |
||
-- For calling via #invoke within a template |
|||
function p.infoboxTemplate(frame) |
|||
origArgs = {} |
|||
for k,v in pairs(frame.args) do origArgs[k] = mw.text.trim(v) end |
|||
parseDataParameters() |
|||
return _infobox() |
|||
end |
|||
return p |
return p |
Revision as of 19:55, 28 November 2021
This Lua module is used on approximately 4,130,000 pages, or roughly 387793% of all pages. To avoid major disruption and server load, any changes should be tested in the module's /sandbox or /testcases subpages, or in your own module sandbox. The tested changes can be added to this page in a single edit. Consider discussing changes on the talk page before implementing them. |
This module is subject to page protection. It is a highly visible module in use by a very large number of pages, or is substituted very frequently. Because vandalism or mistakes would affect many pages, and even trivial editing might cause substantial load on the servers, it is protected from editing. |
This module depends on the following other modules: |
This module uses TemplateStyles: |
Module:Infobox is a module that implements the {{Infobox}} template. Please see the template page for usage instructions.
Tracking categories
- Category:Pages which use infobox templates with ignored data cells (0)
- Category:Articles which use infobox templates with no data rows (0)
- Category:Pages which use embedded infobox templates with the title parameter (0)
-- <nowiki>
--------------------------------------------------------------------------------
-- Infobox template module for [[w:c:dev]] documentation.
--
-- @see [[:Category:Infobox templates]]
-- @usage {{#invoke:Infobox}}
-- @module infobox
-- @alias p
-- @version 1.1.2
-- @author Speedit
-- @author DarthKitty
--
-- @todo Fill holes in the documentation by replacing question marks.
-- @todo Use the already loaded data for sorting category data by name
-- instead of re-reading the page with mw.title.new in p.categoryDoc
--------------------------------------------------------------------------------
local p = {}
local yesno = require('Dev:Yesno')
local getArgs = require('Dev:Arguments').getArgs
local userError = require('Dev:User error')
local wdsButton = require('Dev:WDS Button')
local i18n = require('Dev:I18n').loadMessages(
'Infobox',
'Common',
'Testharness'
)
local entrypoint = require('Dev:Entrypoint')
local data = mw.loadData('Dev:Infobox/data')
local title = mw.title.getCurrentTitle()
require('Dev:No interwiki access')
--------------------------------------------------------------------------------
-- Date formatter utility.
--
-- @see [[Template:FormatDate]]
--
-- @param {string} d
-- Unprocessed date.
-- @param {string} f
-- Date format to use.
-- @returns {string}
-- Formatted, localised date.
--------------------------------------------------------------------------------
local function dtfm(d, f)
return mw.getCurrentFrame():expandTemplate{
title = 'FormatDate',
args = {
[1] = d,
dateformat = f,
uselang = i18n:getLang()
}
}
end
--------------------------------------------------------------------------------
-- Breadcrumb link generator.
--
-- @param {string} t
-- Breadcrumb part.
-- @param {table} parts
-- Collection of title parts.
-- @returns {string}
-- Breadcrumb chunk.
--------------------------------------------------------------------------------
local function crumbpart(parts)
local d = #parts
return table.concat({
(d == 1 and '< ' or ' | '),
'[[',
table.concat(parts, '/'),
'|',
parts[d],
']]'
})
end
--------------------------------------------------------------------------------
-- Infobox breadcrumb generator for mobile.
--
-- @param {Frame} frame
-- Frame invocation object.
-- @returns {string}
-- Breadcrumb designed for mobile.
--------------------------------------------------------------------------------
function p.breadcrumbs(frame)
local parts = {}
local ret = mw.html.create('center')
for t in tostring(title.fullText):gmatch('[^/]+') do
table.insert(parts, t)
ret:wikitext(crumbpart(parts))
end
return frame:preprocess(tostring(ret))
end
--------------------------------------------------------------------------------
-- Infobox data argument handler. Substitutes '$n' arguments with version
-- numbers.
--
-- @usage {{#invoke:infobox|data|{{{Data}}}|ucfirst=1}}
--
-- @param {Frame} frame
-- Frame invocation object.
-- @param {string} frame.args[1]
-- Infobox data input.
-- @param {string} frame.args.ucfirst
-- Capitalization boolean.
-- @throws {string}
-- 'missing argument from Module:Infobox in p.data'
-- @returns {string}
-- Argument-substituted infobox data.
--------------------------------------------------------------------------------
function p.data(frame)
if not (frame.args or {})[1] then
error('missing argument from Module:Infobox in p.data')
end
local tArgs = frame:getParent().args
local ret = frame.args[1]
local uc1 = yesno(mw.text.trim(frame.args.ucfirst or ''))
if not string.find(ret, '%$') then
return ret
end
-- Argument substitution.
local function repl(d)
local rsub = d == '1'
and (tArgs.Submodule or i18n:msg('original'))
or (tArgs['Submodule' .. d] or i18n:msg('version', d))
return uc1
and rsub:gsub('^%l', mw.ustring.upper)
or rsub
end
ret = ret:gsub('%$(%d+)', repl)
return ret
end
--------------------------------------------------------------------------------
-- Infobox date list generator with version numbers.
--
-- @usage {{#invoke:infobox|date}}
--
-- @param {Frame} frame
-- Frame invocation object.
-- @param {string} frame.args
-- Invocation arguments.
-- @param {string} frame.args.ext
-- Code type.
-- @param {table} frame:getParent().args
-- Template arguments.
-- @returns {string}
-- Formatted multi-line date string.
--------------------------------------------------------------------------------
function p.date(frame)
local tArgs = frame:getParent().args
local dateFmt = tArgs.dateformat or ''
local ret = ''
if tArgs.Updated and #tArgs.Updated > 0 then
-- Generated first formatted date.
ret = dtfm(tArgs.Updated, dateFmt)
if tArgs.Submodule or tArgs.Updated2 then
-- Append first version tag.
local sub1 = tArgs.Submodule or i18n:msg('original')
if sub1 ~= '' then
ret = ret .. ' (' .. sub1 .. ')'
end
end
-- Handle further versions.
for d = 2, math.huge do
local p = tArgs['Updated' .. d]
if not p then
break
end
local s = tArgs['Submodule' .. d] or i18n:msg('version', d)
ret = (d == 2 and '* ' or '') .. ret
.. '\n* '
.. dtfm(p, dateFmt)
.. ' (' .. s .. ')'
end
-- Default date field.
elseif tArgs.Code then
-- @todo Use DPL template to extract main code page?
local ext = frame.args.ext or 'js'
local suffix = '.' .. ext
local u = frame:expandTemplate{
title = 'Updated',
args = {
'MediaWiki:' .. title.baseText .. suffix
}
}
ret = dtfm(u, dateFmt)
end
return ret
end
--------------------------------------------------------------------------------
-- Category formatter.
--
-- @param {table} tbl
-- Array of text items to be returned.
-- @param {string} cat
-- Category name.
-- @param {string} sortkey
-- Category sortkey.
--------------------------------------------------------------------------------
local function category(tbl, cat, sortkey)
table.insert(tbl, '[[Category:')
table.insert(tbl, cat)
if sortkey and sortkey ~= '' then
table.insert(tbl, '|')
table.insert(tbl, sortkey)
end
table.insert(tbl, ']]')
end
--------------------------------------------------------------------------------
-- Returns error for missing description param in infobox.
--
-- @returns {string}
-- Error message and trancking category
--------------------------------------------------------------------------------
function p.description()
return userError('Description missing', 'Content without description')
end
--------------------------------------------------------------------------------
-- Infobox category generator for type subcategorization.
--
-- @param {Frame} frame
-- Frame invocation object.
-- @returns {string}
-- Type categories corresponding to `Type` infobox argument.
--------------------------------------------------------------------------------
function p.categories(frame)
local ret = {}
local typ = frame.args[1]
local tArgs = p.getParent(frame).args
local catKeys = tArgs.Type
if
mw.ustring.lower(tArgs.Status or '') == 'archive' or
not data.categories[typ or ''] or
title.namespace ~= 0
then
return ''
end
local sortkey = tArgs.Title or title.prefixedText
category(ret, typ, sortkey)
table.insert(ret, '[[:Category:')
table.insert(ret, typ)
table.insert(ret, '|')
table.insert(ret, typ)
table.insert(ret, ']]')
-- Maintenance category
local REPORTCAT = 'Content without type categorization'
local TYPEDOC = ':Category:' .. REPORTCAT .. '#Documentation'
if catKeys then
for v in mw.text.gsplit(mw.ustring.lower(catKeys), '%s*,%s*') do
local cat = data.categories[typ][mw.text.trim(v)]
if cat then
category(ret, cat, sortkey)
end
end
elseif typ == 'JavaScript' then
table.insert(ret, '<br />')
table.insert(ret, userError('Type categorization missing', REPORTCAT))
table.insert(ret, ' ([[:' .. TYPEDOC .. '|')
table.insert(
ret,
mw.message.new('oasis-more'):useDatabase(false):plain()
)
table.insert(ret, ']])')
end
return table.concat(ret)
end
--------------------------------------------------------------------------------
-- Category documentation generator.
--
-- @param {Frame} frame
-- Frame invocation object.
-- @param {table} frame.args
-- Frame argument table.
-- @param {string} frame.args[1]
-- Infobox type corresponding to [[Module:Infobox/data]].
-- @returns {string}
-- Table of types against descriptions.
--------------------------------------------------------------------------------
function p.categoryDoc(frame)
local typ = frame.args[1] or ''
local ret = mw.html.create('table'):attr {
['class'] = 'WikiaTable',
['border'] = '1',
['id'] = 'types'
}
ret:tag('tr')
:tag('th'):wikitext(i18n:msg('type'))
:done()
:tag('th'):wikitext(i18n:msg('description'))
:done()
if
not data.categories[typ] or
not data.descriptions[typ]
then
return ret
end
-- Extract categories from data.
local catData = {}
local catNames = {}
for k, n in pairs(data.categories[typ]) do
if not catData[n] then
catNames[#catNames + 1] = n
catData[n] = {k}
else
catData[n][#catData[n] + 1] = k
end
if
not catData[n].description and
data.descriptions[typ][k]
then
catData[n].description = data.descriptions[typ][k]
end
end
-- Sort category data by name.
local dataContent = mw.title.new('Module:Infobox/data'):getContent()
local function sortKey(a, b)
local i1, i2 =
dataContent:find('"' .. a .. '"'),
dataContent:find('"' .. b .. '"')
return i1 < i2
end
table.sort(catNames, sortKey)
-- Render documentation table.
local cat, catRow, catKeys, desc
for i, n in ipairs(catNames) do
cat = catData[n]
catRow = ret:tag('tr')
catKeys = catRow:tag('td')
-- Handle multiple keys.
if #cat >= 2 then
catKeys = catKeys:tag('ul')
for i, k in ipairs(cat) do
catKeys
:tag('li'):tag('code')
:wikitext(k)
end
else
catKeys
:tag('code')
:wikitext(cat[1])
end
-- Add description.
desc = cat.description:gsub('^%l', mw.ustring.upper) .. '.'
catRow:tag('td')
:wikitext('[[:Category:' .. n .. ']]')
:tag('p'):wikitext(desc)
end
return tostring(ret)
end
--------------------------------------------------------------------------------
-- Category description generator. Used on category pages to describe pages.
--
-- @param {Frame} frame
-- Frame invocation object.
-- @returns {string}
-- Category description followed by parent category link.
--------------------------------------------------------------------------------
function p.categoryDesc(frame)
local typ = frame.args[1] or ''
local key = frame.args[2] or ''
local ret = data.messages.description
local desc = (data.descriptions[typ] or {})[key]
if not desc then
error('misconfigured arguments in p.categoryDesc from Module:Install')
end
ret = ret:gsub('$1', desc) .. '[[Category:' .. typ .. '|{{SUBPAGENAME}}]]'
return frame:preprocess(ret)
end
--------------------------------------------------------------------------------
-- Alias mapper for {{t|Infobox Lua}} `Type` argument.
--
-- @param {Frame} frame
-- Frame invocation object.
-- @returns {string}
-- Lua type (`'invocable'` or `'meta'`).
--------------------------------------------------------------------------------
function p.luaType(frame)
local tArgs = frame:getParent().args
local typ = tArgs.Type or tArgs.type
return typ
and data.luaTypes[typ:lower()]
or ''
end
--------------------------------------------------------------------------------
-- Test suite status.
--
-- @param {Frame} frame
-- Frame invocation object.
-- @returns {string}
-- Test suite status badge.
--------------------------------------------------------------------------------
function p.luaSuite(frame)
local page =
frame:getParent().Code or
mw.language.fetchLanguageName(title.subpageText) == ''
and title.subpageText
or title.baseText:match('/*([^/]+)$')
-- DPL query for categories
local query = table.concat({
'{{#dpl:',
'| debug = 0',
'| mode = userformat',
'| allowcachedresults = 1',
'| category = Lua test suites',
'| titleregexp = ^' .. mw.uri.encode(page, 'WIKI') .. '/testcases$',
'| addcategories = true',
'| format = ,%CATNAMES%,,',
'}}'
}, '\n')
-- Fetch category data.
local cats = mw.text.split(frame:preprocess(query), ',')
--[=[
-- @todo Fix this:
if cats[2] and data.luaStatus[cats[2]]
and cats[1] == 'Lua test suites'
and cats[2] ~= 'Lua test suites'
then
-- DPL sorts pages alphabetically, so if [[Module talk:PAGE/testcases]]
-- contains [[:Category:Lua test suites]], then [[:Category:Skipped Lua test suites]]
-- and [[:Category:Pages with script errors]] end up ingored,
-- and the test suite is incorrectly reported as passing.
cats[1], cats[2] = cats[2], cats[1]
end
]=]
local i18n_key = data.luaStatus[cats[1] or ''] or 'unknown'
-- Build badge.
local badge = mw.html.create('div')
:addClass('plainlinks')
:wikitext(
'[' ..
mw.site.server ..
mw.uri.fullUrl('Module talk:' .. page .. '/testcases').path ..
' ' ..
wdsButton._badge(i18n:msg(i18n_key), i18n_key) ..
']'
)
return tostring(badge)
end
--- Set of valid scope aliases
local scopeAliases = {
p = "personal",
s = "site-wide",
mw = "vanilla mediawiki",
}
--- Set of valid scope names
local validScopes = {
["personal"] = true,
["site-wide"] = true,
["vanilla mediawiki"] = true,
}
--- Dictionary of "scope" → "i18n" key mapping
local scopeLangMap = {
["site-wide"] = "sitewide",
["vanilla mediawiki"] = "vanilla-mw"
}
--- Dictionary of "scope" → "category" subpage mapping
local scopeCategoryMap = {
["personal"] = "Personal use",
["site-wide"] = "Site-wide use",
["vanilla mediawiki"] = "Vanilla MediaWiki use"
}
--- The order in which scopes are displayed in the infobox
local scopeOrder = {
"personal",
"site-wide",
"vanilla mediawiki",
}
--------------------------------------------------------------------------------
-- Function used by {{T|scope}}.
--
-- @param {Frame|table} frame
-- Scribunto Frame object or table of arguments.
-- @returns {string}
-- The parsed scopes
--------------------------------------------------------------------------------
function p.parseScope(frame)
local args = getArgs(frame)
local arg1 = mw.ustring.lower(args[1] or "")
local type = args.type
local rec = yesno(args.pr)
local usingLegacyAlias = false
local hasUnknownScope = false
local scopes = {}
local function explodeScope(scopeName, callback)
if not scopeName or #scopeName == 0 then return end
scopeName = scopeAliases[scopeName] or scopeName
if validScopes[scopeName] then
scopes[scopeName] = true
elseif scopeName == "ps" then
-- Legacy compat
scopes["personal"] = true
scopes["site-wide"] = true
usingLegacyAlias = true
elseif callback then
callback(scopeName)
else
hasUnknownScope = true
end
end
local function explodeScope2(splitSymbol)
for splitWS in mw.text.gsplit(splitSymbol, "%s+") do
explodeScope(splitWS)
end
end
for splitBr in mw.text.gsplit(arg1, "%s-<br%f[%s/>].-/?>%s*") do
for splitSymbol in mw.text.gsplit(splitBr, "%s*[/,]%s*") do
explodeScope(splitSymbol, explodeScope2)
end
end
local cats = {}
local result = {}
for _, scopeName in ipairs(scopeOrder) do
if scopes[scopeName] then
if type then
table.insert(cats, "[[Category:" .. type .. "/" .. scopeCategoryMap[scopeName] .. "]]")
end
local scopeString = i18n:msg(scopeLangMap[scopeName] or scopeName)
if scopeName == "personal" and rec then
scopeString = scopeString .. " (" .. i18n:msg("recommended") .. ")"
end
table.insert(result, scopeString)
end
end
if (#result == 0 or hasUnknownScope) and type then
table.insert(cats, "[[Category:" .. type .. "/Unknown use]]")
end
if usingLegacyAlias then
table.insert(cats, "[[Category:Pages using deprecated Template:Scope values]]")
end
if (#result > 1) then
local ul = mw.html.create("ul")
for _, v in ipairs(result) do
ul:tag("li"):wikitext(v)
end
return tostring(ul) .. table.concat(cats)
end
return (result[1] or i18n:msg("unknown")) .. table.concat(cats)
end
--------------------------------------------------------------------------------
-- Template wrapper for [[Template:Infobox]].
--
-- @usage {{#invoke:infobox|main}}
--
-- @function p.main
-- @param {Frame} frame
-- Frame invocation object.
-- @returns {string|nil}
-- Package function output.
--------------------------------------------------------------------------------
p.main = entrypoint(p)
--------------------------------------------------------------------------------
-- Returns topmost parent frame.
--
-- @param {Frame} frame
-- Frame invocation object.
-- @returns {table}
-- Highest parent frame.
--------------------------------------------------------------------------------
function p.getParent(frame)
local cf = frame
local pf = frame:getParent()
if pf then
return p.getParent(pf)
else
return cf
end
end
return p