Difference between revisions of "Module:Message box"
From D&D 5e
(better error message) |
(add mbox, various other fixes) |
||
Line 1: | Line 1: | ||
-- This is a meta-module for producing message box templates, including {{mbox}}, {{ambox}}, {{imbox}}, {{tmbox}}, {{ombox}}, {{cmbox}} and {{fmbox}}. | -- This is a meta-module for producing message box templates, including {{mbox}}, {{ambox}}, {{imbox}}, {{tmbox}}, {{ombox}}, {{cmbox}} and {{fmbox}}. | ||
+ | -- Require necessary modules. | ||
local htmlBuilder = require('Module:HtmlBuilder') | local htmlBuilder = require('Module:HtmlBuilder') | ||
− | |||
local categoryHandler = require('Module:Category handler').main | 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() | ||
+ | |||
+ | -- Set aliases for often-used functions to reduce table lookups. | ||
+ | local format = mw.ustring.format | ||
+ | local tinsert = table.insert | ||
+ | local tconcat = table.concat | ||
local p = {} | local p = {} | ||
Line 12: | Line 20: | ||
-- Get the title object, passing the function through pcall | -- Get the title object, passing the function through pcall | ||
-- in case we are over the expensive function count limit. | -- in case we are over the expensive function count limit. | ||
− | local | + | local success, title = pcall(mw.title.new, page) |
− | + | if success then | |
− | if | + | return title |
− | |||
end | end | ||
end | end | ||
− | |||
end | end | ||
Line 37: | Line 43: | ||
local preposition = 'from' | local preposition = 'from' | ||
if cat and date then | if cat and date then | ||
− | local catTitle = | + | local catTitle = format('Category:%s %s %s', cat, preposition, date) |
− | + | tinsert(ret, format('[[%s]]', catTitle)) | |
catTitle = getTitleObject(catTitle) | catTitle = getTitleObject(catTitle) | ||
if not catTitle or not catTitle.exists then | if not catTitle or not catTitle.exists then | ||
− | + | tinsert(ret, '[[Category:Articles with invalid date parameter in template]]') | |
end | end | ||
elseif cat and not date then | elseif cat and not date then | ||
− | + | tinsert(ret, format('[[Category:%s]]', cat)) | |
end | end | ||
if all then | if all then | ||
− | + | tinsert(ret, format('[[Category:%s]]', all)) | |
end | end | ||
− | return | + | return tconcat(ret) |
end | end | ||
Line 62: | Line 68: | ||
end | end | ||
local ret = {} | local ret = {} | ||
− | for k | + | for k in pairs(vals) do |
− | + | tinsert(ret, k) | |
end | end | ||
+ | table.sort(ret) | ||
return ret | return ret | ||
end | end | ||
Line 73: | Line 80: | ||
local num = mw.ustring.match(tostring(k), '^' .. prefix .. '([1-9]%d*)$') | local num = mw.ustring.match(tostring(k), '^' .. prefix .. '([1-9]%d*)$') | ||
if num then | if num then | ||
− | + | tinsert(nums, tonumber(num)) | |
end | end | ||
end | end | ||
table.sort(nums) | table.sort(nums) | ||
return nums | return nums | ||
+ | end | ||
+ | |||
+ | local function getNamespaceId(ns) | ||
+ | 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 | ||
+ | |||
+ | 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 p.build(boxType, args) | 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 title = getTitleObject(args.page) or mw.title.getCurrentTitle() | ||
+ | local nsid = getNamespaceId(args.demospace) or title.namespace | ||
+ | |||
-- Get the box config data from the data page. | -- 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 dataTables = mw.loadData('Module:Message box/data') | ||
local data = dataTables[boxType] | local data = dataTables[boxType] | ||
Line 87: | Line 136: | ||
local boxTypes = {} | local boxTypes = {} | ||
for k, v in pairs(dataTables) do | for k, v in pairs(dataTables) do | ||
− | + | tinsert(boxTypes, format('"%s"', k)) | |
end | end | ||
− | error( | + | tinsert(boxTypes, '"mbox"') |
+ | error(format('invalid message box type "%s"; valid types are %s', tostring(boxType), mw.text.listToText(boxTypes)), 2) | ||
end | end | ||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
------------------------ Process config data ---------------------------- | ------------------------ Process config data ---------------------------- | ||
Line 129: | Line 157: | ||
local sect = args.sect | local sect = args.sect | ||
if presentButBlank(sect) then | if presentButBlank(sect) then | ||
− | sect = | + | sect = format('This %s ', data.sectionDefault or 'page') |
elseif type(sect) == 'string' then | elseif type(sect) == 'string' then | ||
sect = 'This ' .. sect .. ' ' | sect = 'This ' .. sect .. ' ' | ||
Line 161: | Line 189: | ||
local talkText = ' Relevant discussion may be found on' | local talkText = ' Relevant discussion may be found on' | ||
if talkTitle.isTalkPage then | if talkTitle.isTalkPage then | ||
− | talkText = | + | talkText = format('%s [[%s|%s]].', talkText, talk, talkTitle.prefixedText) |
else | else | ||
− | talkText = | + | talkText = format('%s the [[%s#%s|talk page]].', talkText, talkTitle.prefixedText, talk) |
end | end | ||
talk = talkText | talk = talkText | ||
Line 200: | Line 228: | ||
local cat = args['cat' .. tostring(num)] or args['category' .. tostring(num)] | local cat = args['cat' .. tostring(num)] or args['category' .. tostring(num)] | ||
local all = args['all' .. tostring(num)] | local all = args['all' .. tostring(num)] | ||
− | + | tinsert(mainCats, formatCategory(cat, args.date, all)) | |
end | end | ||
end | end | ||
Line 207: | Line 235: | ||
local templateCats = {} | local templateCats = {} | ||
if data.templateCategory and not title.isSubpage and not yesno(args.nocat) then | if data.templateCategory and not title.isSubpage and not yesno(args.nocat) then | ||
− | + | tinsert(templateCats, format('[[Category:%s]]', data.templateCategory)) | |
end | end | ||
Line 215: | Line 243: | ||
local templateCat | local templateCat | ||
if not name and not title.isSubpage then | if not name and not title.isSubpage then | ||
− | templateCat = | + | templateCat = format('[[Category:%s]]', catName) |
elseif type(name) == 'string' and title.prefixedText == ('Template:' .. name) then | elseif type(name) == 'string' and title.prefixedText == ('Template:' .. name) then | ||
local paramsToCheck = data.templateErrorParamsToCheck or {} | local paramsToCheck = data.templateErrorParamsToCheck or {} | ||
Line 225: | Line 253: | ||
end | end | ||
if count > 0 then | if count > 0 then | ||
− | templateCat = | + | templateCat = format('[[Category:%s|%d]]', catName, count) |
end | end | ||
if origCategoryNums and #origCategoryNums > 0 then | if origCategoryNums and #origCategoryNums > 0 then | ||
− | templateCat = | + | templateCat = format('[[Category:%s|C]]', catName) |
end | end | ||
end | end | ||
− | + | tinsert(templateCats, templatecat) | |
end | end | ||
Line 238: | Line 266: | ||
if invalidType then | if invalidType then | ||
local catsort = (nsid == 0 and 'Main:' or '') .. title.prefixedText | local catsort = (nsid == 0 and 'Main:' or '') .. title.prefixedText | ||
− | + | tinsert(allCats, format('[[Category:Wikipedia message box parameter needs fixing|%s]]', catsort)) | |
end | end | ||
Line 251: | Line 279: | ||
.tag('b') | .tag('b') | ||
.addClass('error') | .addClass('error') | ||
− | .wikitext( | + | .wikitext(format( |
'Template <code>%s%s%s</code> has been incorrectly substituted.', | 'Template <code>%s%s%s</code> has been incorrectly substituted.', | ||
mw.text.nowiki('{{'), name, mw.text.nowiki('}}') | mw.text.nowiki('{{'), name, mw.text.nowiki('}}') | ||
)) | )) | ||
end | end | ||
− | + | tinsert(allCats, '[[Category:Pages with incorrectly substituted templates]]') | |
end | end | ||
Line 284: | Line 312: | ||
end | end | ||
imageLeftCell | imageLeftCell | ||
− | .wikitext(image or | + | .wikitext(image or format('[[File:%s|%s|link=|alt=]]', typeData.image, imageSize)) |
elseif data.imageEmptyCell then | elseif data.imageEmptyCell then | ||
row.tag('td') | row.tag('td') | ||
Line 310: | Line 338: | ||
end | end | ||
textCellSpan | textCellSpan | ||
− | .wikitext(date and | + | .wikitext(date and format(" <small>''(%s)''</small>", date)) |
if not isSmall then | if not isSmall then | ||
textCellSpan | textCellSpan | ||
Line 351: | Line 379: | ||
.addClass('error') | .addClass('error') | ||
.css('text-align', 'center') | .css('text-align', 'center') | ||
− | .wikitext( | + | .wikitext(format('This message box is using an invalid type parameter (<code>type=%s</code>) and needs fixing.', args.type or '')) |
end | end | ||
Line 357: | Line 385: | ||
root | root | ||
.wikitext(categoryHandler{ | .wikitext(categoryHandler{ | ||
− | main = | + | main = tconcat(mainCats), |
− | template = | + | template = tconcat(templateCats), |
− | all = | + | all = tconcat(allCats) |
}) | }) | ||
Line 395: | Line 423: | ||
end | end | ||
+ | p.mbox = makeWrapper('mbox') | ||
p.ambox = makeWrapper('ambox') | p.ambox = makeWrapper('ambox') | ||
+ | p.cmbox = makeWrapper('cmbox') | ||
p.fmbox = makeWrapper('fmbox') | p.fmbox = makeWrapper('fmbox') | ||
p.imbox = makeWrapper('imbox') | p.imbox = makeWrapper('imbox') | ||
p.ombox = makeWrapper('ombox') | p.ombox = makeWrapper('ombox') | ||
− | |||
p.tmbox = makeWrapper('tmbox') | p.tmbox = makeWrapper('tmbox') | ||
return p | return p |
Revision as of 04:46, 26 September 2013
Documentation for this module may be created at Module:Message box/doc
-- This is a meta-module for producing message box templates, including {{mbox}}, {{ambox}}, {{imbox}}, {{tmbox}}, {{ombox}}, {{cmbox}} and {{fmbox}}. -- Require necessary modules. local htmlBuilder = require('Module:HtmlBuilder') local categoryHandler = require('Module:Category handler').main local yesno = require('Module:Yesno') -- Get a language object for formatDate and ucfirst. local lang = mw.language.getContentLanguage() -- Set aliases for often-used functions to reduce table lookups. local format = mw.ustring.format local tinsert = table.insert local tconcat = table.concat local p = {} 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 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 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 local function union(t1, t2) -- Returns the union of two arrays. local vals = {} for i, v in ipairs(t1) do vals[v] = true end for i, v in ipairs(t2) do vals[v] = true end local ret = {} for k in pairs(vals) do tinsert(ret, k) end table.sort(ret) return ret end 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 local function getNamespaceId(ns) 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 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 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 title = getTitleObject(args.page) or mw.title.getCurrentTitle() local nsid = getNamespaceId(args.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 ------------------------ 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 -- 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 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. local origCatNums = getArgNums(args, 'cat') 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 templateCats = {} if data.templateCategory and not title.isSubpage and not yesno(args.nocat) then tinsert(templateCats, format('[[Category:%s]]', data.templateCategory)) end -- Add an error category for the template namespace if appropriate. if data.templateErrorCategory then local catName = data.templateErrorCategory 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) }) return tostring(root) 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 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 if v ~= '' or k == 'talk' or k == 'sect' or k == 'date' then args[k] = v end end return p.build(boxType, args) 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 p