Editing Module:Message box
Jump to navigation
Jump to search
The edit can be undone. Please check the comparison below to verify that this is what you want to do, and then publish the changes below to finish undoing the edit.
Latest revision | Your text | ||
Line 1: | Line 1: | ||
require('Module: | -- This is a meta-module for producing message box templates, including {{mbox}}, {{ambox}}, {{imbox}}, {{tmbox}}, {{ombox}}, {{cmbox}} and {{fmbox}}. | ||
local | |||
-- Require necessary modules. | |||
local htmlBuilder = require('Module:HtmlBuilder') | |||
local categoryHandler = require('Module:Category handler').main | |||
local yesno = require('Module:Yesno') | local yesno = require('Module:Yesno') | ||
-- Get a language object for formatDate and ucfirst. | |||
local lang = mw.language.getContentLanguage() | local lang = mw.language.getContentLanguage() | ||
local | -- Set aliases for often-used functions to reduce table lookups. | ||
local | local format = mw.ustring.format | ||
local tinsert = table.insert | |||
local tconcat = table.concat | |||
local p = {} | |||
local function getTitleObject( | local function getTitleObject(page) | ||
if type(page) == 'string' then | |||
-- Get the title object, passing the function through pcall | |||
-- in case we are over the expensive function count limit. | |||
local success, title = pcall(mw.title.new, page) | |||
if success then | |||
return title | |||
end | |||
end | |||
end | end | ||
local function | local function presentButBlank(s) | ||
if type(s) ~= 'string' then return end | |||
if s and not mw.ustring.find(s, '%S') then | |||
return true | |||
else | |||
return false | |||
end | |||
end | end | ||
local function | local function formatCategory(cat, date, all) | ||
local ret = {} | |||
cat = type(cat) == 'string' and cat | |||
date = type(date) == 'string' and date | |||
all = type(all) == 'string' and all | |||
local preposition = 'from' | |||
if cat and date then | |||
local catTitle = format('Category:%s %s %s', cat, preposition, date) | |||
tinsert(ret, format('[[%s]]', catTitle)) | |||
catTitle = getTitleObject(catTitle) | |||
if not catTitle or not catTitle.exists then | |||
tinsert(ret, '[[Category:Articles with invalid date parameter in template]]') | |||
end | |||
elseif cat and not date then | |||
tinsert(ret, format('[[Category:%s]]', cat)) | |||
end | |||
if all then | |||
tinsert(ret, format('[[Category:%s]]', all)) | |||
end | |||
return tconcat(ret) | |||
end | end | ||
local function union(t1, t2) | |||
-- Returns the union of two arrays. | |||
local vals = {} | |||
for i, v in ipairs(t1) do | |||
local | vals[v] = true | ||
end | |||
for i, v in ipairs(t2) do | |||
function | vals[v] = true | ||
end | |||
local ret = {} | |||
for k in pairs(vals) do | |||
tinsert(ret, k) | |||
end | |||
table.sort(ret) | |||
return ret | |||
end | end | ||
function | local function getArgNums(args, prefix) | ||
local nums = {} | |||
for k, v in pairs(args) do | |||
local num = mw.ustring.match(tostring(k), '^' .. prefix .. '([1-9]%d*)$') | |||
if num then | |||
tinsert(nums, tonumber(num)) | |||
end | |||
end | |||
table.sort(nums) | |||
return nums | |||
end | end | ||
function | local function getNamespaceId(ns) | ||
if not ns then return end | |||
if type(ns) == 'string' then | |||
ns = lang:ucfirst(mw.ustring.lower(ns)) | |||
if ns == 'Main' then | |||
ns = 0 | |||
end | |||
end | |||
local nsTable = mw.site.namespaces[ns] | |||
if nsTable then | |||
return nsTable.id | |||
end | |||
end | end | ||
function | local function getMboxType(nsid) | ||
-- Gets the mbox type from a namespace number. | |||
if nsid == 0 then | |||
return 'ambox' -- main namespace | |||
elseif nsid == 6 then | |||
return 'imbox' -- file namespace | |||
elseif nsid == 14 then | |||
return 'cmbox' -- category namespace | |||
else | |||
local nsTable = mw.site.namespaces[nsid] | |||
if nsTable and nsTable.isTalk then | |||
return 'tmbox' -- any talk namespace | |||
else | |||
return 'ombox' -- other namespaces or invalid input | |||
end | |||
end | |||
end | end | ||
function | function p.build(boxType, args) | ||
if type(args) ~= 'table' then | |||
error(format('invalid "args" parameter type; expected type "table", got type "%s"', type(args)), 2) | |||
end | |||
-- Get the title object and the namespace. | |||
local pageTitle = getTitleObject(args.page ~= '' and args.page) | |||
local title = pageTitle or mw.title.getCurrentTitle() | |||
local demospace = getNamespaceId(args.demospace ~= '' and args.demospace) | |||
local nsid = demospace or title.namespace | |||
-- Get the box config data from the data page. | |||
if boxType == 'mbox' then | |||
boxType = getMboxType(nsid) | |||
end | |||
local dataTables = mw.loadData('Module:Message box/data') | |||
local data = dataTables[boxType] | |||
if not data then | |||
local boxTypes = {} | |||
for k, v in pairs(dataTables) do | |||
tinsert(boxTypes, format('"%s"', k)) | |||
end | |||
tinsert(boxTypes, '"mbox"') | |||
error(format('invalid message box type "%s"; valid types are %s', tostring(boxType), mw.text.listToText(boxTypes)), 2) | |||
end | |||
-- Only allow blank arguments for the parameter names listed in data.allowBlankParams. | |||
local newArgs = {} | |||
for k, v in pairs(args) do | |||
for i, param in ipairs(data.allowBlankParams or {}) do | |||
if v ~= '' or k == param then | |||
newArgs[k] = v | |||
end | |||
end | |||
end | |||
args = newArgs | |||
newArgs = nil | |||
------------------------ Process config data ---------------------------- | |||
-- Type data. | |||
local typeData = data.types[args.type] | |||
local invalidType = args.type and not typeData and true or false | |||
typeData = typeData or data.types[data.default] | |||
-- Process data for collapsible text fields | |||
local name, issue, talk, fix, date, info | |||
if data.useCollapsibleTextFields then | |||
name = args.name | |||
local nameTitle = getTitleObject(name) | |||
local isTemplatePage = nameTitle and title.prefixedText == ('Template:' .. nameTitle.text) and true or false | |||
local sect = args.sect | |||
if presentButBlank(sect) then | |||
sect = format('This %s ', data.sectionDefault or 'page') | |||
elseif type(sect) == 'string' then | |||
sect = 'This ' .. sect .. ' ' | |||
end | |||
issue = (sect or '') .. (args.issue or '') .. ' ' .. (args.text or '') | |||
talk = args.talk | |||
if presentButBlank(talk) and isTemplatePage then | |||
talk = '#' | |||
end | |||
fix = args.fix | |||
date = args.date | |||
if presentButBlank(date) and isTemplatePage then | |||
date = lang:formatDate('F Y') | |||
end | |||
info = args.info | |||
end | |||
-- Process the talk link, if present. | |||
if talk then | |||
-- See if the talk link exists and is for a talk or a content namespace. | |||
local talkTitle = type(talk) == 'string' and getTitleObject(talk) | |||
if not talkTitle or not talkTitle.isTalkPage then | |||
-- If we couldn't process the talk page link, get the talk page of the current page. | |||
local success | |||
success, talkTitle = pcall(title.talkPageTitle, title) | |||
if not success then | |||
talkTitle = nil | |||
end | |||
end | |||
if talkTitle and talkTitle.exists then | |||
local talkText = ' Relevant discussion may be found on' | |||
if talkTitle.isTalkPage then | |||
talkText = format('%s [[%s|%s]].', talkText, talk, talkTitle.prefixedText) | |||
else | |||
talkText = format('%s the [[%s#%s|talk page]].', talkText, talkTitle.prefixedText, talk) | |||
end | |||
talk = talkText | |||
end | |||
end | |||
end | |||
-- Find whether we are using a small message box and process our data accordingly. | |||
local isSmall = data.allowSmall and (args.small == 'yes' or args.small == true) and true or false | |||
local smallClass, image, imageRight, text, imageSize | |||
if isSmall then | |||
smallClass = data.smallClass or 'mbox-small' | |||
image = args.smallimage or args.image | |||
imageRight = args.smallimageright or args.imageright | |||
if data.useCollapsibleTextFields then | |||
text = args.smalltext or issue | |||
end | else | ||
text = args.smalltext or args.text | |||
end | |||
imageSize = data.imageSmallSize or '30x30px' | |||
else | |||
image = args.image | |||
imageRight = args.imageright | |||
imageSize = '40x40px' | |||
text = args.text | |||
end | |||
-- Process mainspace categories. | |||
local mainCats = {} | |||
local origCategoryNums -- origCategoryNums might be used in computing the template error category. | |||
if data.allowMainspaceCategories then | |||
-- Categories for the main namespace. | |||
if args.cat then | |||
args.cat1 = args.cat | |||
end | end | ||
local origCatNums = getArgNums(args, 'cat') | |||
if args.category then | |||
args.category1 = args.category | |||
end | |||
local origCategoryNums = getArgNums(args, 'category') | |||
local catNums = union(origCatNums, origCategoryNums) | |||
for _, num in ipairs(catNums) do | |||
local cat = args['cat' .. tostring(num)] or args['category' .. tostring(num)] | |||
local all = args['all' .. tostring(num)] | |||
tinsert(mainCats, formatCategory(cat, args.date, all)) | |||
end | |||
end | |||
-- Process template namespace categories | |||
local isTemplatePage = type(name) == 'string' and title.prefixedText == ('Template:' .. name) | |||
local templateCats = {} | |||
if data.templateCategory then | |||
if name then | |||
if isTemplatePage then | |||
tinsert(templateCats, format('[[Category:%s]]', data.templateCategory)) | |||
end | |||
elseif not title.isSubpage then | |||
tinsert(templateCats, format('[[Category:%s]]', data.templateCategory)) | |||
end | |||
end | |||
-- Add an error category for the template namespace if appropriate. | |||
if data.templateErrorCategory then | |||
local catName = data.templateErrorCategory | |||
end | local templateCat | ||
if not name and not title.isSubpage then | |||
templateCat = format('[[Category:%s]]', catName) | |||
elseif type(name) == 'string' and title.prefixedText == ('Template:' .. name) then | |||
local paramsToCheck = data.templateErrorParamsToCheck or {} | |||
local count = 0 | |||
for i, param in ipairs(paramsToCheck) do | |||
if not args[param] then | |||
count = count + 1 | |||
end | |||
end | |||
if count > 0 then | |||
templateCat = format('[[Category:%s|%d]]', catName, count) | |||
end | |||
if origCategoryNums and #origCategoryNums > 0 then | |||
templateCat = format('[[Category:%s|C]]', catName) | |||
end | |||
end | |||
tinsert(templateCats, templatecat) | |||
end | |||
-- Categories for all namespaces. | |||
local allCats = {} | |||
if invalidType then | |||
local catsort = (nsid == 0 and 'Main:' or '') .. title.prefixedText | |||
tinsert(allCats, format('[[Category:Wikipedia message box parameter needs fixing|%s]]', catsort)) | |||
end | |||
------------------------ Build the box ---------------------------- | |||
local root = htmlBuilder.create() | |||
-- Do the subst check. | |||
if data.substCheck and args.subst == 'SUBST' then | |||
if type(name) == 'string' then | |||
root | |||
.tag('b') | |||
.addClass('error') | |||
.wikitext(format( | |||
'Template <code>%s%s%s</code> has been incorrectly substituted.', | |||
mw.text.nowiki('{{'), name, mw.text.nowiki('}}') | |||
)) | |||
end | |||
tinsert(allCats, '[[Category:Pages with incorrectly substituted templates]]') | |||
end | |||
-- Create the box table. | |||
local box = root.tag('table') | |||
box | |||
.attr('id', args.id) | |||
for i, class in ipairs(data.classes) do | |||
box | |||
.addClass(class) | |||
end | |||
box | |||
.addClass(isSmall and smallClass) | |||
.addClass(data.classPlainlinksYesno and yesno(args.plainlinks or true) and 'plainlinks') | |||
.addClass(typeData.class) | |||
.addClass(args.class) | |||
.cssText(args.style) | |||
.attr('role', 'presentation') | |||
-- Add the left-hand image. | |||
local row = box.tag('tr') | |||
local imageCheckBlank = data.imageCheckBlank | |||
if image ~= 'none' and not imageCheckBlank or image ~= 'none' and imageCheckBlank and image ~= 'blank' then | |||
local imageLeftCell = row.tag('td').addClass('mbox-image') | |||
if not isSmall and data.imageCellDiv then | |||
imageLeftCell = imageLeftCell.tag('div').css('width', '52px') -- If we are using a div, redefine imageLeftCell so that the image is inside it. | |||
end | |||
imageLeftCell | |||
.wikitext(image or format('[[File:%s|%s|link=|alt=]]', typeData.image, imageSize)) | |||
elseif data.imageEmptyCell then | |||
row.tag('td') | |||
.addClass('mbox-empty-cell') -- No image. Cell with some width or padding necessary for text cell to have 100% width. | |||
.cssText(data.imageEmptyCellStyle and 'border:none;padding:0px;width:1px') | |||
end | |||
-- Add the text. | |||
local textCell = row.tag('td').addClass('mbox-text') | |||
if data.useCollapsibleTextFields then | |||
textCell | |||
.cssText(args.textstyle) | |||
local textCellSpan = textCell.tag('span') | |||
textCellSpan | |||
.addClass('mbox-text-span') | |||
.wikitext(issue) | |||
if not isSmall then | |||
textCellSpan | |||
.tag('span') | |||
.addClass('hide-when-compact') | |||
.wikitext(talk) | |||
.wikitext(' ') | |||
.wikitext(fix) | |||
.done() | |||
end | |||
textCellSpan | |||
.wikitext(date and format(" <small>''(%s)''</small>", date)) | |||
if not isSmall then | |||
textCellSpan | |||
.tag('span') | |||
.addClass('hide-when-compact') | |||
.wikitext(info and ' ' .. info) | |||
end | |||
else | |||
textCell | |||
.cssText(args.textstyle) | |||
.wikitext(text) | |||
end | |||
-- Add the right-hand image. | |||
if imageRight and not (data.imageRightNone and imageRight == 'none') then | |||
local imageRightCell = row.tag('td').addClass('mbox-imageright') | |||
if not isSmall and data.imageCellDiv then | |||
imageRightCell = imageRightCell.tag('div').css('width', '52px') -- If we are using a div, redefine imageRightCell so that the image is inside it. | |||
end | |||
imageRightCell | |||
.wikitext(imageRight) | |||
end | |||
-- Add the below row. | |||
if data.below and args.below then | |||
box.tag('tr') | |||
.tag('td') | |||
.attr('colspan', args.imageright and '3' or '2') | |||
.addClass('mbox-text') | |||
.cssText(args.textstyle) | |||
.wikitext(args.below) | |||
end | |||
------------------------ Error messages and categories ---------------------------- | |||
-- Add error message for invalid type parameters. | |||
if invalidType then | |||
root | |||
.tag('div') | |||
.addClass('error') | |||
.css('text-align', 'center') | |||
.wikitext(format('This message box is using an invalid type parameter (<code>type=%s</code>) and needs fixing.', args.type or '')) | |||
end | |||
-- Add categories using categoryHandler. | |||
root | |||
.wikitext(categoryHandler{ | |||
main = tconcat(mainCats), | |||
template = tconcat(templateCats), | |||
all = tconcat(allCats), | |||
nocat = args.nocat, | |||
demospace = demospace and args.demospace or nil, | |||
page = pageTitle and pageTitle.prefixedText or nil | |||
}) | |||
return tostring(root) | |||
end | end | ||
---- | local function makeWrapper(boxType) | ||
-- | return function (frame) | ||
-- | -- If called via #invoke, use the args passed into the invoking | ||
-- template, or the args passed to #invoke if any exist. Otherwise | |||
-- assume args are being passed directly in from the debug console | |||
-- or from another Lua module. | |||
local origArgs | |||
if frame == mw.getCurrentFrame() then | |||
origArgs = frame:getParent().args | |||
for k, v in pairs(frame.args) do | |||
origArgs = frame.args | |||
end | break | ||
end | |||
else | |||
origArgs = frame | |||
end | |||
-- Trim whitespace and remove blank arguments. | |||
local args = {} | |||
for k, v in pairs(origArgs) do | |||
if type(v) == 'string' then | |||
v = mw.text.trim(v) | |||
end | |||
args[k] = v | |||
end | |||
return p.build(boxType, args) | |||
end | |||
end | end | ||
p.mbox = makeWrapper('mbox') | |||
p.ambox = makeWrapper('ambox') | |||
p.cmbox = makeWrapper('cmbox') | |||
p.fmbox = makeWrapper('fmbox') | |||
p.imbox = makeWrapper('imbox') | |||
p.ombox = makeWrapper('ombox') | |||
p.tmbox = makeWrapper('tmbox') | |||
return | return p |