Module:Message box

-- This is a meta-module for producing message box templates, including, , , , , and.

-- 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, '') end elseif cat and not date then tinsert(ret, format('', cat)) end if all then tinsert(ret, format('', 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.', talkText, talk, talkTitle.prefixedText) else talkText = format('%s the 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('', data.templateCategory)) end elseif not title.isSubpage then tinsert(templateCats, format('', 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('', 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('', catName, count) end if origCategoryNums and #origCategoryNums > 0 then templateCat = format('', 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('', 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  has been incorrectly substituted.', mw.text.nowiki('') ))       end tinsert(allCats, '') 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('', 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(" (%s) ", 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 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