Editing Module:Message box
From MINR.ORG WIKI
Warning: You are not logged in. Your IP address will be publicly visible if you make any edits. If you log in or create an account, your edits will be attributed to your username, along with other benefits.
The edit can be undone.
Please check the comparison below to verify that this is what you want to do, and then save the changes below to finish undoing the edit.
Latest revision | Your text | ||
Line 2: | Line 2: | ||
-- {{mbox}}, {{ambox}}, {{imbox}}, {{tmbox}}, {{ombox}}, {{cmbox}} and {{fmbox}}. | -- {{mbox}}, {{ambox}}, {{imbox}}, {{tmbox}}, {{ombox}}, {{cmbox}} and {{fmbox}}. | ||
− | -- | + | -- Require necessary modules. |
− | require('Module: | + | local getArgs = require('Module:Arguments').getArgs |
− | local | + | local htmlBuilder = require('Module:HtmlBuilder') |
− | local categoryHandler = require('Module:Category handler'). | + | local categoryHandler = require('Module:Category handler').main |
local yesno = require('Module:Yesno') | local yesno = require('Module:Yesno') | ||
+ | |||
+ | -- Load the configuration page. | ||
+ | local cfgTables = mw.loadData('Module:Message box/configuration') | ||
-- Get a language object for formatDate and ucfirst. | -- Get a language object for formatDate and ucfirst. | ||
local lang = mw.language.getContentLanguage() | local lang = mw.language.getContentLanguage() | ||
− | -- | + | -- 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 trim = mw.text.trim | ||
-------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | ||
Line 18: | Line 24: | ||
-------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | ||
− | local function getTitleObject(...) | + | local function getTitleObject(page, ...) |
− | -- Get the title object, passing the function through pcall | + | 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 | end | ||
Line 38: | Line 46: | ||
local ret = {} | local ret = {} | ||
for k in pairs(vals) do | for k in pairs(vals) do | ||
− | + | tinsert(ret, k) | |
end | end | ||
table.sort(ret) | table.sort(ret) | ||
Line 49: | Line 57: | ||
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 | ||
Line 60: | Line 68: | ||
-------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | ||
− | local | + | local box = {} |
− | + | box.__index = box | |
− | function | + | function box.new() |
− | |||
local obj = {} | local obj = {} | ||
+ | setmetatable(obj, box) | ||
+ | return obj | ||
+ | end | ||
− | + | function box.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 | ||
− | -- | + | function box.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 | else | ||
− | + | return 'ombox' -- other namespaces or invalid input | |
− | |||
− | |||
− | |||
− | |||
− | |||
end | end | ||
end | end | ||
+ | end | ||
− | + | function box:addCat(ns, cat, sort) | |
− | + | if type(cat) ~= 'string' then return end | |
− | + | local nsVals = {'main', 'template', 'all'} | |
− | + | local tname | |
− | + | for i, val in ipairs(nsVals) do | |
− | + | if ns == val then | |
− | + | tname = ns .. 'Cats' | |
− | |||
end | end | ||
− | for i, | + | end |
− | + | if not tname then | |
+ | for i, val in ipairs(nsVals) do | ||
+ | nsVals[i] = format('"%s"', val) | ||
end | end | ||
− | + | error( | |
+ | 'invalid ns parameter passed to box:addCat; valid values are ' | ||
+ | .. mw.text.listToText(nsVals, nil, ' or ') | ||
+ | ) | ||
+ | end | ||
+ | self[tname] = self[tname] or {} | ||
+ | if type(sort) == 'string' then | ||
+ | tinsert(self[tname], format('[[Category:%s|%s]]', cat, sort)) | ||
+ | else | ||
+ | tinsert(self[tname], format('[[Category:%s]]', cat)) | ||
end | end | ||
+ | end | ||
+ | |||
+ | function box:addClass(class) | ||
+ | if type(class) ~= 'string' then return end | ||
+ | self.classes = self.classes or {} | ||
+ | tinsert(self.classes, class) | ||
+ | end | ||
− | + | function box:addAttr(attr, val) | |
− | + | if type(attr) ~= 'string' or type(val) ~= 'string' then return end | |
− | + | self.attrs = self.attrs or {} | |
+ | tinsert(self.attrs, attr) | ||
+ | end | ||
− | + | function box:setTitle(args) | |
+ | -- Get the title object and the namespace. | ||
+ | self.pageTitle = getTitleObject(args.page ~= '' and args.page) | ||
+ | self.title = self.pageTitle or mw.title.getCurrentTitle() | ||
+ | self.demospace = args.demospace ~= '' and args.demospace or nil | ||
+ | self.nsid = box.getNamespaceId(self.demospace) or self.title.namespace | ||
end | end | ||
− | function | + | function box:getConfig(boxType) |
− | if | + | -- Get the box config data from the data page. |
− | + | if boxType == 'mbox' then | |
+ | boxType = box.getMboxType(self.nsid) | ||
end | end | ||
− | if | + | local cfg = cfgTables[boxType] |
− | + | if not cfg 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 | end | ||
− | + | return cfg | |
− | |||
end | end | ||
− | function | + | function box:removeBlankArgs(cfg, args) |
− | if | + | -- Only allow blank arguments for the parameter names listed in |
− | + | -- cfg.allowBlankParams. | |
+ | local newArgs = {} | ||
+ | for k, v in pairs(args) do | ||
+ | if v ~= '' then | ||
+ | newArgs[k] = v | ||
+ | end | ||
end | end | ||
− | + | for i, param in ipairs(cfg.allowBlankParams or {}) do | |
+ | newArgs[param] = args[param] | ||
+ | end | ||
+ | return newArgs | ||
end | end | ||
− | function | + | function box:setBoxParameters(cfg, args) |
− | |||
− | |||
− | |||
-- Get type data. | -- Get type data. | ||
self.type = args.type | self.type = args.type | ||
Line 143: | Line 198: | ||
and self.type | and self.type | ||
and not typeData | and not typeData | ||
+ | and true | ||
+ | or false | ||
typeData = typeData or cfg.types[cfg.default] | typeData = typeData or cfg.types[cfg.default] | ||
self.typeClass = typeData.class | self.typeClass = typeData.class | ||
Line 148: | Line 205: | ||
-- Find if the box has been wrongly substituted. | -- Find if the box has been wrongly substituted. | ||
− | + | if cfg.substCheck and args.subst == 'SUBST' then | |
+ | self.isSubstituted = true | ||
+ | end | ||
-- Find whether we are using a small message box. | -- Find whether we are using a small message box. | ||
− | + | if cfg.allowSmall and ( | |
cfg.smallParam and args.small == cfg.smallParam | cfg.smallParam and args.small == cfg.smallParam | ||
or not cfg.smallParam and yesno(args.small) | or not cfg.smallParam and yesno(args.small) | ||
) | ) | ||
+ | then | ||
+ | self.isSmall = true | ||
+ | else | ||
+ | self.isSmall = false | ||
+ | end | ||
-- Add attributes, classes and styles. | -- Add attributes, classes and styles. | ||
− | self.id = args.id | + | if cfg.allowId then |
− | + | self.id = args.id | |
− | |||
end | end | ||
+ | self:addClass( | ||
+ | cfg.usePlainlinksParam and yesno(args.plainlinks or true) and 'plainlinks' | ||
+ | ) | ||
for _, class in ipairs(cfg.classes or {}) do | for _, class in ipairs(cfg.classes or {}) do | ||
self:addClass(class) | self:addClass(class) | ||
Line 194: | Line 260: | ||
self.isTemplatePage = self.templateTitle | self.isTemplatePage = self.templateTitle | ||
and mw.title.equals(self.title, self.templateTitle) | and mw.title.equals(self.title, self.templateTitle) | ||
+ | or false | ||
end | end | ||
Line 214: | Line 281: | ||
text = type(text) == 'string' and text or nil | text = type(text) == 'string' and text or nil | ||
local issues = {} | local issues = {} | ||
− | + | tinsert(issues, sect) | |
− | + | tinsert(issues, issue) | |
− | + | tinsert(issues, text) | |
− | self.issue = | + | self.issue = tconcat(issues, ' ') |
end | end | ||
Line 225: | Line 292: | ||
-- parameter is blank. | -- parameter is blank. | ||
if talk == '' | if talk == '' | ||
− | and self.templateTitle | + | and self.templateTitle |
and ( | and ( | ||
mw.title.equals(self.templateTitle, self.title) | mw.title.equals(self.templateTitle, self.title) | ||
Line 251: | Line 318: | ||
local talkText = 'Relevant discussion may be found on' | local talkText = 'Relevant discussion may be found on' | ||
if talkArgIsTalkPage then | if talkArgIsTalkPage then | ||
− | talkText = | + | talkText = format( |
'%s [[%s|%s]].', | '%s [[%s|%s]].', | ||
talkText, | talkText, | ||
Line 258: | Line 325: | ||
) | ) | ||
else | else | ||
− | talkText = | + | talkText = format( |
'%s the [[%s#%s|talk page]].', | '%s the [[%s#%s|talk page]].', | ||
talkText, | talkText, | ||
Line 278: | Line 345: | ||
end | end | ||
if date then | if date then | ||
− | self.date = | + | self.date = format(" <small>''(%s)''</small>", date) |
end | end | ||
self.info = args.info | self.info = args.info | ||
− | |||
− | |||
− | |||
end | end | ||
Line 298: | Line 362: | ||
-- General image settings. | -- General image settings. | ||
− | self.imageCellDiv = not self.isSmall and cfg.imageCellDiv | + | self.imageCellDiv = not self.isSmall and cfg.imageCellDiv and true or false |
self.imageEmptyCell = cfg.imageEmptyCell | self.imageEmptyCell = cfg.imageEmptyCell | ||
if cfg.imageEmptyCellStyle then | if cfg.imageEmptyCellStyle then | ||
Line 314: | Line 378: | ||
and (cfg.imageSmallSize or '30x30px') | and (cfg.imageSmallSize or '30x30px') | ||
or '40x40px' | or '40x40px' | ||
− | self.imageLeft = | + | self.imageLeft = format('[[File:%s|%s|link=|alt=]]', self.typeImage |
or 'Imbox notice.png', imageSize) | or 'Imbox notice.png', imageSize) | ||
end | end | ||
Line 324: | Line 388: | ||
self.imageRight = imageRight | self.imageRight = imageRight | ||
end | end | ||
− | |||
− | + | -- Add mainspace categories. At the moment these are only used in {{ambox}}. | |
− | + | if cfg.allowMainspaceCategories then | |
− | + | if args.cat then | |
− | + | args.cat1 = args.cat | |
− | if | + | end |
− | + | self.catNums = getArgNums(args, 'cat') | |
− | + | if args.category then | |
− | + | args.category1 = args.category | |
− | + | end | |
− | + | self.categoryNums = getArgNums(args, 'category') | |
− | args | + | if args.all then |
− | + | args.all1 = args.all | |
− | + | end | |
− | + | self.allNums = getArgNums(args, 'all') | |
− | + | self.categoryParamNums = union(self.catNums, self.categoryNums) | |
− | + | self.categoryParamNums = union(self.categoryParamNums, self.allNums) | |
− | + | -- The following is roughly equivalent to the old {{Ambox/category}}. | |
− | + | local date = args.date | |
− | + | date = type(date) == 'string' and date | |
− | + | local preposition = 'from' | |
− | + | for _, num in ipairs(self.categoryParamNums) do | |
− | + | local mainCat = args['cat' .. tostring(num)] | |
− | + | or args['category' .. tostring(num)] | |
− | + | local allCat = args['all' .. tostring(num)] | |
− | + | mainCat = type(mainCat) == 'string' and mainCat | |
− | + | allCat = type(allCat) == 'string' and allCat | |
− | + | if mainCat and date and date ~= '' then | |
− | + | local catTitle = format('%s %s %s', mainCat, preposition, date) | |
− | + | self:addCat('main', catTitle) | |
− | + | catTitle = getTitleObject('Category:' .. catTitle) | |
+ | if not catTitle or not catTitle.exists then | ||
+ | self:addCat( | ||
+ | 'main', | ||
+ | 'Articles with invalid date parameter in template' | ||
+ | ) | ||
+ | end | ||
+ | elseif mainCat and (not date or date == '') then | ||
+ | self:addCat('main', mainCat) | ||
+ | end | ||
+ | if allCat then | ||
+ | self:addCat('main', allCat) | ||
end | end | ||
− | |||
− | |||
− | |||
− | |||
− | |||
end | end | ||
end | end | ||
− | |||
− | |||
− | |||
− | |||
− | |||
− | -- Add template categories. | + | -- Add template-namespace categories. |
if cfg.templateCategory then | if cfg.templateCategory then | ||
if cfg.templateCategoryRequireName then | if cfg.templateCategoryRequireName then | ||
if self.isTemplatePage then | if self.isTemplatePage then | ||
− | self:addCat( | + | self:addCat('template', cfg.templateCategory) |
end | end | ||
elseif not self.title.isSubpage then | elseif not self.title.isSubpage then | ||
− | self:addCat( | + | self:addCat('template', cfg.templateCategory) |
end | end | ||
end | end | ||
− | -- Add template error | + | -- Add template error category. |
if cfg.templateErrorCategory then | if cfg.templateErrorCategory then | ||
local templateErrorCategory = cfg.templateErrorCategory | local templateErrorCategory = cfg.templateErrorCategory | ||
Line 404: | Line 468: | ||
end | end | ||
end | end | ||
− | self:addCat( | + | self:addCat('template', templateCat, templateSort) |
end | end | ||
− | |||
− | + | -- Categories for all namespaces. | |
− | -- | ||
if self.invalidTypeError then | if self.invalidTypeError then | ||
− | local allSort = (self. | + | local allSort = (self.nsid == 0 and 'Main:' or '') .. self.title.prefixedText |
self:addCat('all', 'Wikipedia message box parameter needs fixing', allSort) | self:addCat('all', 'Wikipedia message box parameter needs fixing', allSort) | ||
end | end | ||
Line 417: | Line 479: | ||
self:addCat('all', 'Pages with incorrectly substituted templates') | self:addCat('all', 'Pages with incorrectly substituted templates') | ||
end | end | ||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
-- Convert category tables to strings and pass them through | -- Convert category tables to strings and pass them through | ||
-- [[Module:Category handler]]. | -- [[Module:Category handler]]. | ||
− | + | self.categories = categoryHandler{ | |
− | main = | + | main = tconcat(self.mainCats or {}), |
− | template = | + | template = tconcat(self.templateCats or {}), |
− | all = | + | all = tconcat(self.allCats or {}), |
− | nocat = | + | nocat = args.nocat, |
− | page = self. | + | demospace = self.demospace, |
+ | page = self.pageTitle and self.pageTitle.prefixedText or nil | ||
} | } | ||
end | end | ||
− | function | + | function box:export() |
− | local root = | + | local root = htmlBuilder.create() |
-- Add the subst check error. | -- Add the subst check error. | ||
if self.isSubstituted and self.name then | if self.isSubstituted and self.name then | ||
− | root | + | root |
− | + | .tag('b') | |
− | + | .addClass('error') | |
− | + | .wikitext(format( | |
− | + | 'Template <code>%s[[Template:%s|%s]]%s</code> has been incorrectly substituted.', | |
− | + | mw.text.nowiki('{{'), self.name, self.name, mw.text.nowiki('}}') | |
+ | )) | ||
end | end | ||
-- Create the box table. | -- Create the box table. | ||
− | local boxTable = root | + | local boxTable = root.tag('table') |
− | boxTable | + | boxTable |
+ | .attr('id', self.id) | ||
for i, class in ipairs(self.classes or {}) do | for i, class in ipairs(self.classes or {}) do | ||
− | boxTable | + | boxTable |
+ | .addClass(class) | ||
end | end | ||
boxTable | boxTable | ||
− | + | .cssText(self.style) | |
− | + | .attr('role', 'presentation') | |
− | + | for attr, val in pairs(self.attrs or {}) do | |
− | + | boxTable | |
− | boxTable | + | .attr(attr, val) |
end | end | ||
-- Add the left-hand image. | -- Add the left-hand image. | ||
− | local row = boxTable | + | local row = boxTable.tag('tr') |
if self.imageLeft then | if self.imageLeft then | ||
− | local imageLeftCell = row | + | local imageLeftCell = row.tag('td').addClass('mbox-image') |
if self.imageCellDiv then | if self.imageCellDiv then | ||
-- If we are using a div, redefine imageLeftCell so that the image | -- If we are using a div, redefine imageLeftCell so that the image | ||
Line 476: | Line 531: | ||
-- image width to 52px. If any images in a div are wider than that, | -- image width to 52px. If any images in a div are wider than that, | ||
-- they may overlap with the text or cause other display problems. | -- they may overlap with the text or cause other display problems. | ||
− | imageLeftCell = imageLeftCell | + | imageLeftCell = imageLeftCell.tag('div').css('width', '52px') |
end | end | ||
− | imageLeftCell | + | imageLeftCell |
+ | .wikitext(self.imageLeft) | ||
elseif self.imageEmptyCell then | elseif self.imageEmptyCell then | ||
-- Some message boxes define an empty cell if no image is specified, and | -- Some message boxes define an empty cell if no image is specified, and | ||
Line 484: | Line 540: | ||
-- specified gives the following hint: "No image. Cell with some width | -- specified gives the following hint: "No image. Cell with some width | ||
-- or padding necessary for text cell to have 100% width." | -- or padding necessary for text cell to have 100% width." | ||
− | row | + | row.tag('td') |
− | + | .addClass('mbox-empty-cell') | |
− | + | .cssText(self.imageEmptyCellStyle) | |
end | end | ||
-- Add the text. | -- Add the text. | ||
− | local textCell = row | + | local textCell = row.tag('td').addClass('mbox-text') |
if self.useCollapsibleTextFields then | if self.useCollapsibleTextFields then | ||
-- The message box uses advanced text parameters that allow things to be | -- The message box uses advanced text parameters that allow things to be | ||
-- collapsible. At the moment, only ambox uses this. | -- collapsible. At the moment, only ambox uses this. | ||
− | textCell | + | textCell |
− | local textCellSpan = textCell | + | .cssText(self.textstyle) |
+ | local textCellSpan = textCell.tag('span') | ||
textCellSpan | textCellSpan | ||
− | + | .addClass('mbox-text-span') | |
− | + | .wikitext(self.issue) | |
− | if | + | if not self.isSmall then |
− | textCellSpan | + | textCellSpan |
− | + | .tag('span') | |
− | + | .addClass('hide-when-compact') | |
− | + | .wikitext(self.talk and ' ' .. self.talk) | |
+ | .wikitext(self.fix and ' ' .. self.fix) | ||
end | end | ||
− | textCellSpan | + | textCellSpan |
− | if | + | .wikitext(self.date and ' ' .. self.date) |
+ | if not self.isSmall then | ||
textCellSpan | textCellSpan | ||
− | + | .tag('span') | |
− | + | .addClass('hide-when-compact') | |
− | + | .wikitext(self.info and ' ' .. self.info) | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
end | end | ||
else | else | ||
-- Default text formatting - anything goes. | -- Default text formatting - anything goes. | ||
textCell | textCell | ||
− | + | .cssText(self.textstyle) | |
− | + | .wikitext(self.text) | |
end | end | ||
-- Add the right-hand image. | -- Add the right-hand image. | ||
if self.imageRight then | if self.imageRight then | ||
− | local imageRightCell = row | + | local imageRightCell = row.tag('td').addClass('mbox-imageright') |
if self.imageCellDiv then | if self.imageCellDiv then | ||
-- If we are using a div, redefine imageRightCell so that the image | -- If we are using a div, redefine imageRightCell so that the image | ||
-- is inside it. | -- is inside it. | ||
− | imageRightCell = imageRightCell | + | imageRightCell = imageRightCell.tag('div').css('width', '52px') |
end | end | ||
imageRightCell | imageRightCell | ||
− | + | .wikitext(self.imageRight) | |
end | end | ||
-- Add the below row. | -- Add the below row. | ||
if self.below then | if self.below then | ||
− | boxTable | + | boxTable.tag('tr') |
− | + | .tag('td') | |
− | + | .attr('colspan', self.imageRight and '3' or '2') | |
− | + | .addClass('mbox-text') | |
− | + | .cssText(self.textstyle) | |
− | + | .wikitext(self.below) | |
end | end | ||
-- Add error message for invalid type parameters. | -- Add error message for invalid type parameters. | ||
if self.invalidTypeError then | if self.invalidTypeError then | ||
− | root | + | root |
− | + | .tag('div') | |
− | + | .css('text-align', 'center') | |
− | + | .wikitext(format( | |
− | + | 'This message box is using an invalid "type=%s" parameter and needs fixing.', | |
− | + | self.type or '' | |
+ | )) | ||
end | end | ||
-- Add categories. | -- Add categories. | ||
− | root | + | root |
+ | .wikitext(self.categories) | ||
return tostring(root) | return tostring(root) | ||
end | end | ||
− | + | local function main(boxType, args) | |
− | + | local outputBox = box.new() | |
− | + | outputBox:setTitle(args) | |
+ | local cfg = outputBox:getConfig(boxType) | ||
+ | args = outputBox:removeBlankArgs(cfg, args) | ||
+ | outputBox:setBoxParameters(cfg, args) | ||
+ | return outputBox:export() | ||
+ | end | ||
− | local | + | local function makeWrapper(boxType) |
− | + | return function (frame) | |
− | function | + | local args = getArgs(frame, {trim = false, removeBlanks = false}) |
− | + | return main(boxType, args) | |
− | + | end | |
− | |||
− | |||
end | end | ||
− | + | local p = { | |
− | + | main = main, | |
− | + | mbox = makeWrapper('mbox') | |
− | + | } | |
− | |||
− | |||
− | + | for boxType in pairs(cfgTables) do | |
− | + | p[boxType] = makeWrapper(boxType) | |
− | |||
− | |||
− | |||
− | |||
− | |||
end | end | ||
− | return | + | return p |