Module:Message box: Difference between revisions
en>Mr. Stradivarius add mbox, various other fixes |
en>Mr. Stradivarius pass nocat, page, and demospace parameters to Module:Category handler, and add a choice for which arguments to allow as blank |
||
Line 88: | Line 88: | ||
local function getNamespaceId(ns) | local function getNamespaceId(ns) | ||
if not ns then return end | |||
if type(ns) == 'string' then | if type(ns) == 'string' then | ||
ns = lang:ucfirst(mw.ustring.lower(ns)) | ns = lang:ucfirst(mw.ustring.lower(ns)) | ||
Line 124: | Line 125: | ||
-- Get the title object and the namespace. | -- Get the title object and the namespace. | ||
local | local pageTitle = getTitleObject(args.page ~= '' and args.page) | ||
local | 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. | -- Get the box config data from the data page. | ||
Line 141: | Line 144: | ||
error(format('invalid message box type "%s"; valid types are %s', tostring(boxType), mw.text.listToText(boxTypes)), 2) | error(format('invalid message box type "%s"; valid types are %s', tostring(boxType), mw.text.listToText(boxTypes)), 2) | ||
end | 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 ---------------------------- | ------------------------ Process config data ---------------------------- | ||
Line 222: | Line 237: | ||
if data.allowMainspaceCategories then | if data.allowMainspaceCategories then | ||
-- Categories for the main namespace. | -- Categories for the main namespace. | ||
if args.cat then | |||
args.cat1 = args.cat | |||
end | |||
local origCatNums = getArgNums(args, 'cat') | local origCatNums = getArgNums(args, 'cat') | ||
if args.category then | |||
args.category1 = args.category | |||
end | |||
local origCategoryNums = getArgNums(args, 'category') | local origCategoryNums = getArgNums(args, 'category') | ||
local catNums = union(origCatNums, origCategoryNums) | local catNums = union(origCatNums, origCategoryNums) | ||
Line 233: | Line 254: | ||
-- Process template namespace categories | -- Process template namespace categories | ||
local isTemplatePage = type(name) == 'string' and title.prefixedText == ('Template:' .. name) | |||
local templateCats = {} | local templateCats = {} | ||
if data.templateCategory | 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 | end | ||
-- Add an error category for the template namespace if appropriate. | -- Add an error category for the template namespace if appropriate. | ||
if data.templateErrorCategory then | if data.templateErrorCategory then | ||
Line 387: | Line 415: | ||
main = tconcat(mainCats), | main = tconcat(mainCats), | ||
template = tconcat(templateCats), | template = tconcat(templateCats), | ||
all = tconcat(allCats) | all = tconcat(allCats), | ||
nocat = args.nocat, | |||
demospace = demospace and args.demospace or nil, | |||
page = pageTitle and pageTitle.prefixedText or nil | |||
}) | }) | ||
Line 415: | Line 446: | ||
v = mw.text.trim(v) | v = mw.text.trim(v) | ||
end | end | ||
args[k] = v | |||
end | end | ||
return p.build(boxType, args) | return p.build(boxType, args) |
Revision as of 08:02, 27 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 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
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 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
-- 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.
if args.cat then
args.cat1 = args.cat
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
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
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
args[k] = v
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