Difference between revisions of "Module:Message box"

From MINR.ORG WIKI
(pass nocat, page, and demospace parameters to Module:Category handler, and add a choice for which arguments to allow as blank)
(rewrite with a "box" object to make the code a little less spaghetti-like)
Line 14: Line 14:
 
local tconcat = table.concat
 
local tconcat = table.concat
  
local p = {}
+
local box = {}
  
 
local function getTitleObject(page)
 
local function getTitleObject(page)
Line 34: Line 34:
 
         return false
 
         return false
 
     end
 
     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
 
end
  
Line 87: Line 65:
 
end
 
end
  
local function getNamespaceId(ns)
+
function box.getNamespaceId(ns)
 
     if not ns then return end
 
     if not ns then return end
 
     if type(ns) == 'string' then
 
     if type(ns) == 'string' then
Line 101: Line 79:
 
end
 
end
  
local function getMboxType(nsid)
+
function box.getMboxType(nsid)
 
     -- Gets the mbox type from a namespace number.
 
     -- Gets the mbox type from a namespace number.
 
     if nsid == 0 then
 
     if nsid == 0 then
Line 119: Line 97:
 
end
 
end
  
function p.build(boxType, args)
+
function box:addCat(ns, cat, sort)
     if type(args) ~= 'table' then
+
     if type(cat) ~= 'string' then return end
        error(format('invalid "args" parameter type; expected type "table", got type "%s"', type(args)), 2)
+
    local nsVals = {'main', 'template', 'all'}
 +
    local tname
 +
    for i, val in ipairs(nsVals) do
 +
        if ns == val then
 +
            tname = ns .. 'Cats'
 +
        end
 
     end
 
     end
 +
    if not tname then
 +
        for i, val in ipairs(nsVals) do
 +
            nsVals[i] = format('"%s"', val)
 +
        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
  
 +
function box:addClass(class)
 +
    if type(class) ~= 'string' then return end
 +
    self.classes = self.classes or {}
 +
    tinsert(self.classes, class)
 +
end
 +
 +
function box:setTitle(args)
 
     -- Get the title object and the namespace.
 
     -- Get the title object and the namespace.
 
     local pageTitle = getTitleObject(args.page ~= '' and args.page)
 
     local pageTitle = getTitleObject(args.page ~= '' and args.page)
     local title = pageTitle or mw.title.getCurrentTitle()
+
     self.title = pageTitle or mw.title.getCurrentTitle()
     local demospace = getNamespaceId(args.demospace ~= '' and args.demospace)
+
     local demospace = box.getNamespaceId(args.demospace ~= '' and args.demospace)
     local nsid = demospace or title.namespace
+
     self.nsid = demospace or self.title.namespace
 +
end
  
 +
function box:getConfig(boxType)
 
     -- Get the box config data from the data page.
 
     -- Get the box config data from the data page.
 
     if boxType == 'mbox' then
 
     if boxType == 'mbox' then
         boxType = getMboxType(nsid)
+
         boxType = box.getMboxType(self.nsid)
 
     end
 
     end
     local dataTables = mw.loadData('Module:Message box/data')
+
     local cfgTables = mw.loadData('Module:Message box/configuration')
     local data = dataTables[boxType]
+
     local cfg = cfgTables[boxType]
     if not data then
+
     if not cfg then
 
         local boxTypes = {}
 
         local boxTypes = {}
 
         for k, v in pairs(dataTables) do
 
         for k, v in pairs(dataTables) do
Line 144: Line 149:
 
         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
 +
    return cfg
 +
end
 
      
 
      
     -- Only allow blank arguments for the parameter names listed in data.allowBlankParams.
+
function box:removeBlankArgs(cfg, args)
 +
     -- Only allow blank arguments for the parameter names listed in cfg.allowBlankParams.
 
     local newArgs = {}
 
     local newArgs = {}
 
     for k, v in pairs(args) do
 
     for k, v in pairs(args) do
         for i, param in ipairs(data.allowBlankParams or {}) do
+
         for i, param in ipairs(cfg.allowBlankParams or {}) do
 
             if v ~= '' or k == param then
 
             if v ~= '' or k == param then
 
                 newArgs[k] = v
 
                 newArgs[k] = v
Line 154: Line 162:
 
         end
 
         end
 
     end
 
     end
     args = newArgs
+
     return newArgs
     newArgs = nil
+
end
 +
 
 +
function box:setBoxParameters(cfg, args)
 +
    -- Get type data.
 +
    self.type = args.type
 +
    local typeData = cfg.types[self.type]
 +
    self.invalidType = self.type and not typeData and true or false
 +
    typeData = typeData or cfg.types[cfg.default]
 +
    self.typeClass = typeData.class
 +
    self.typeImage = typeData.image
 +
 
 +
    -- 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.
 +
     self.isSmall = cfg.allowSmall and (args.small == 'yes' or args.small == true) and true or false
  
     ------------------------ Process config data ----------------------------
+
     -- Add attributes, classes and styles.
 +
    self.id = args.id
 +
    self:addClass(cfg.usePlainlinksParam and yesno(args.plainlinks or true) and 'plainlinks')
 +
    for _, class in ipairs(cfg.classes or {}) do
 +
        self:addClass(class)
 +
    end
 +
    if self.isSmall then
 +
        self:addClass(cfg.smallClass or 'mbox-small')
 +
    end
 +
    self:addClass(self.typeClass)
 +
    self.style = args.style
  
     -- Type data.
+
     -- Set text style.
     local typeData = data.types[args.type]
+
     self.textstyle = args.textstyle
    local invalidType = args.type and not typeData and true or false
 
    typeData = typeData or data.types[data.default]
 
  
     -- Process data for collapsible text fields
+
     -- Process data for collapsible text fields. At the moment these are only used in {{ambox}}.
     local name, issue, talk, fix, date, info
+
     self.useCollapsibleTextFields = cfg.useCollapsibleTextFields
     if data.useCollapsibleTextFields then
+
     if self.useCollapsibleTextFields then
         name = args.name
+
         self.name = args.name
 
         local nameTitle = getTitleObject(name)
 
         local nameTitle = getTitleObject(name)
         local isTemplatePage = nameTitle and title.prefixedText == ('Template:' .. nameTitle.text) and true or false
+
         self.isTemplatePage = nameTitle and title.prefixedText == ('Template:' .. nameTitle.text) and true or false
 +
 
 +
        -- Get the self.issue value.
 
         local sect = args.sect
 
         local sect = args.sect
 
         if presentButBlank(sect) then
 
         if presentButBlank(sect) then
             sect = format('This %s ', data.sectionDefault or 'page')
+
             sect = 'This ' .. (cfg.sectionDefault or 'page')
 
         elseif type(sect) == 'string' then
 
         elseif type(sect) == 'string' then
             sect = 'This ' .. sect .. ' '
+
             sect = 'This ' .. sect
 +
        else
 +
            sect = nil
 
         end
 
         end
         issue = (sect or '') .. (args.issue or '') .. ' ' .. (args.text or '')
+
         local issue = args.issue
         talk = args.talk
+
        issue = type(issue) == 'string' and issue or nil
         if presentButBlank(talk) and isTemplatePage then
+
        local text = args.text
 +
        text = type(text) == 'string' and text or nil
 +
        local issues = {}
 +
        tinsert(issues, sect)
 +
        tinsert(issues, issue)
 +
        tinsert(issues, text)
 +
        self.issue = tconcat(issues, ' ')
 +
 
 +
        -- Get the self.talk value.
 +
         local talk = args.talk
 +
         if presentButBlank(talk) and self.isTemplatePage then
 
             talk = '#'
 
             talk = '#'
 
         end
 
         end
         fix = args.fix
+
         if talk then
        date = args.date
+
            -- See if the talk link exists and is for a talk or a content namespace.
        if presentButBlank(date) and isTemplatePage then
+
            local talkTitle = getTitleObject(talk)
            date = lang:formatDate('F Y')
+
            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
 +
                self.talk = talkText
 +
            end
 
         end
 
         end
        info = args.info
 
    end
 
  
    -- Process the talk link, if present.
+
        -- Get other values.
    if talk then
+
        self.fix = args.fix
         -- See if the talk link exists and is for a talk or a content namespace.
+
         local date = args.date
         local talkTitle = type(talk) == 'string' and getTitleObject(talk)
+
         self.date = date and format(" <small>''(%s)''</small>", date)
         if not talkTitle or not talkTitle.isTalkPage then
+
         if presentButBlank(self.date) and self.isTemplatePage then
            -- If we couldn't process the talk page link, get the talk page of the current page.
+
             self.date = lang:formatDate('F Y')
            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
 +
        self.info = args.info
 
     end
 
     end
  
     -- Find whether we are using a small message box and process our data accordingly.
+
     -- Set the non-collapsible text field. At the moment this is used by all box types other than ambox,
     local isSmall = data.allowSmall and (args.small == 'yes' or args.small == true) and true or false
+
     -- and also by ambox when small=yes.
    local smallClass, image, imageRight, text, imageSize
+
     if self.isSmall then
     if isSmall then
+
         if self.useCollapsibleTextFields then
        smallClass = data.smallClass or 'mbox-small'
+
             self.text = args.smalltext or self.issue
        image = args.smallimage or args.image
 
        imageRight = args.smallimageright or args.imageright
 
         if data.useCollapsibleTextFields then
 
             text = args.smalltext or issue
 
 
         else
 
         else
             text = args.smalltext or args.text
+
             self.text = args.smalltext or args.text
 
         end
 
         end
        imageSize = data.imageSmallSize or '30x30px'
 
 
     else
 
     else
         image = args.image
+
         self.text = args.text
        imageRight = args.imageright
+
    end
         imageSize = '40x40px'
+
 
        text = args.text
+
    -- Set the below row.
 +
    self.below = cfg.below and args.below
 +
 
 +
    -- General image settings.
 +
    self.imageCellDiv = not self.isSmall and cfg.imageCellDiv and true or false
 +
    self.imageEmptyCell = cfg.imageEmptyCell
 +
    if cfg.imageEmptyCellStyle then
 +
         self.imageEmptyCellStyle = 'border:none;padding:0px;width:1px'
 
     end
 
     end
  
     -- Process mainspace categories.
+
     -- Left image settings.
     local mainCats = {}
+
     local imageCheckBlank = cfg.imageCheckBlank
     local origCategoryNums -- origCategoryNums might be used in computing the template error category.
+
     local imageLeft = self.isSmall and args.smallimage or args.image
     if data.allowMainspaceCategories then
+
    if imageLeft ~= 'none' and not imageCheckBlank or imageLeft ~= 'none' and imageCheckBlank and image ~= 'blank' then
         -- Categories for the main namespace.
+
        self.imageLeft = imageLeft
 +
        if not imageLeft then
 +
            local imageSize = self.isSmall and (cfg.imageSmallSize or '30x30px') or '40x40px'
 +
            self.imageLeft = format('[[File:%s|%s|link=|alt=]]', self.typeImage or 'Imbox notice.png', imageSize)
 +
        end
 +
    end
 +
 
 +
    -- Right image settings.
 +
    local imageRight = self.isSmall and args.smallimageright or args.imageright
 +
     if not (cfg.imageRightNone and imageRight == 'none') then
 +
         self.imageRight = imageRight
 +
    end
 +
 
 +
    -- Add mainspace categories. At the moment these are only used in {{ambox}}.
 +
    if cfg.allowMainspaceCategories then
 
         if args.cat then
 
         if args.cat then
 
             args.cat1 = args.cat
 
             args.cat1 = args.cat
 
         end
 
         end
         local origCatNums = getArgNums(args, 'cat')
+
         self.catNums = getArgNums(args, 'cat')
 
         if args.category then
 
         if args.category then
 
             args.category1 = args.category
 
             args.category1 = args.category
 
         end
 
         end
         local origCategoryNums = getArgNums(args, 'category')
+
         self.categoryNums = getArgNums(args, 'category')
         local catNums = union(origCatNums, origCategoryNums)
+
         self.categoryParamNums = union(self.catNums, self.categoryNums)
         for _, num in ipairs(catNums) do
+
        -- The following is roughly equivalent to the old {{Ambox/category}}.
             local cat = args['cat' .. tostring(num)] or args['category' .. tostring(num)]
+
        local date = args.date
             local all = args['all' .. tostring(num)]
+
        date = type(date) == 'string' and date
             tinsert(mainCats, formatCategory(cat, args.date, all))
+
        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 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 then
 +
                self:addCat('main', mainCat)
 +
            end
 +
            if allCat then
 +
                self:addCat('main', allCat)
 +
            end
 
         end
 
         end
 
     end
 
     end
  
     -- Process template namespace categories
+
     -- Add template-namespace categories.
     local isTemplatePage = type(name) == 'string' and title.prefixedText == ('Template:' .. name)
+
     self.isTemplatePage = type(self.name) == 'string' and title.prefixedText == ('Template:' .. self.name)
    local templateCats = {}
+
     if cfg.templateCategory then
     if data.templateCategory then
+
         if self.name then
         if name then
+
             if self.isTemplatePage then
             if isTemplatePage then
+
                 self:addCat('template', cfg.templateCategory)
                 tinsert(templateCats, format('[[Category:%s]]', data.templateCategory))
 
 
             end
 
             end
         elseif not title.isSubpage then
+
         elseif not self.title.isSubpage then
             tinsert(templateCats, format('[[Category:%s]]', data.templateCategory))
+
             self:addCat('template', cfg.templateCategory)
 
         end
 
         end
 
     end
 
     end
 
      
 
      
     -- Add an error category for the template namespace if appropriate.
+
     -- Add template error category.
     if data.templateErrorCategory then
+
     if cfg.templateErrorCategory then
         local catName = data.templateErrorCategory
+
         local templateErrorCategory = cfg.templateErrorCategory
         local templateCat
+
         local templateCat, templateSort
         if not name and not title.isSubpage then
+
         if not self.name and not self.title.isSubpage then
             templateCat = format('[[Category:%s]]', catName)
+
             templateCat = templateErrorCategory
         elseif type(name) == 'string' and title.prefixedText == ('Template:' .. name) then
+
         elseif type(self.name) == 'string' and title.prefixedText == ('Template:' .. name) then
             local paramsToCheck = data.templateErrorParamsToCheck or {}
+
             local paramsToCheck = cfg.templateErrorParamsToCheck or {}
 
             local count = 0
 
             local count = 0
 
             for i, param in ipairs(paramsToCheck) do
 
             for i, param in ipairs(paramsToCheck) do
Line 281: Line 362:
 
             end
 
             end
 
             if count > 0 then
 
             if count > 0 then
                 templateCat = format('[[Category:%s|%d]]', catName, count)
+
                 templateCat = templateErrorCategory
 +
                templateSort = tostring(count)
 
             end
 
             end
             if origCategoryNums and #origCategoryNums > 0 then
+
             if self.categoryNums and #self.categoryNums > 0 then
                 templateCat = format('[[Category:%s|C]]', catName)
+
                 templateCat = templateErrorCategory
 +
                templateSort = 'C'
 
             end
 
             end
 
         end
 
         end
         tinsert(templateCats, templatecat)
+
         self:addCat('template', templateCat, templateSort)
 
     end
 
     end
  
 
     -- Categories for all namespaces.
 
     -- Categories for all namespaces.
    local allCats = {}
+
     if self.invalidType then
     if invalidType then
+
         local allSort = (nsid == 0 and 'Main:' or '') .. title.prefixedText
         local catsort = (nsid == 0 and 'Main:' or '') .. title.prefixedText
+
         self:addCat('all', 'Wikipedia message box parameter needs fixing', allSort)
         tinsert(allCats, format('[[Category:Wikipedia message box parameter needs fixing|%s]]', catsort))
+
    end
 +
    if self.isSubstituted then
 +
        self:addCat('all', 'Pages with incorrectly substituted templates')
 
     end
 
     end
  
     ------------------------ Build the box ----------------------------
+
     -- Convert category tables to strings and pass them through [[Module:Category handler]].
      
+
    self.categories = categoryHandler{
 +
        main = tconcat(self.mainCats or {}),
 +
        template = tconcat(self.templateCats or {}),
 +
        all = tconcat(self.allCats or {}),
 +
        nocat = args.nocat,
 +
        demospace = self.demospace and args.demospace or nil,
 +
        page = self.pageTitle and pageTitle.prefixedText or nil
 +
     }
 +
end
 +
 
 +
function box:export()
 
     local root = htmlBuilder.create()
 
     local root = htmlBuilder.create()
  
     -- Do the subst check.
+
     -- Add the subst check error.
     if data.substCheck and args.subst == 'SUBST' then
+
     if self.isSubstituted and self.name then
         if type(name) == 'string' then
+
         root
             root
+
             .tag('b')
                .tag('b')
+
                .addClass('error')
                    .addClass('error')
+
                .wikitext(format(
                    .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('{{'), self.name, mw.text.nowiki('}}')
                        mw.text.nowiki('{{'), name, mw.text.nowiki('}}')
+
                ))
                    ))
 
        end
 
        tinsert(allCats, '[[Category:Pages with incorrectly substituted templates]]')
 
 
     end
 
     end
  
 
     -- Create the box table.
 
     -- Create the box table.
     local box = root.tag('table')
+
     local boxTable = root.tag('table')
     box
+
     boxTable
         .attr('id', args.id)
+
         .attr('id', self.id)
     for i, class in ipairs(data.classes) do
+
     for i, class in ipairs(self.classes or {}) do
         box
+
         boxTable
 
             .addClass(class)
 
             .addClass(class)
 
     end
 
     end
     box
+
     boxTable
        .addClass(isSmall and smallClass)
+
         .cssText(self.style)
        .addClass(data.classPlainlinksYesno and yesno(args.plainlinks or true) and 'plainlinks')
 
        .addClass(typeData.class)
 
        .addClass(args.class)
 
         .cssText(args.style)
 
 
         .attr('role', 'presentation')
 
         .attr('role', 'presentation')
  
 
     -- Add the left-hand image.
 
     -- Add the left-hand image.
     local row = box.tag('tr')
+
     local row = boxTable.tag('tr')
     local imageCheckBlank = data.imageCheckBlank
+
     if self.imageLeft then
    if image ~= 'none' and not imageCheckBlank or image ~= 'none' and imageCheckBlank and image ~= 'blank' then
 
 
         local imageLeftCell = row.tag('td').addClass('mbox-image')
 
         local imageLeftCell = row.tag('td').addClass('mbox-image')
         if not isSmall and data.imageCellDiv then
+
         if self.imageCellDiv then
             imageLeftCell = imageLeftCell.tag('div').css('width', '52px') -- If we are using a div, redefine imageLeftCell so that the image is inside it.
+
            -- If we are using a div, redefine imageLeftCell so that the image is inside it.
 +
            -- Not sure why only some box types use divs, but it probably has something to do
 +
            -- with that style="width: 52px;". @TODO: find out exactly what this does and fix this comment.
 +
             imageLeftCell = imageLeftCell.tag('div').css('width', '52px')  
 
         end
 
         end
 
         imageLeftCell
 
         imageLeftCell
             .wikitext(image or format('[[File:%s|%s|link=|alt=]]', typeData.image, imageSize))
+
             .wikitext(self.imageLeft)
     elseif data.imageEmptyCell then
+
     elseif self.imageEmptyCell then
 +
        -- Some message boxes define an empty cell if no image is specified, and some don't.
 +
        -- The old template code in templates where empty cells are specified gives the following hint:
 +
        -- "No image. Cell with some width or padding necessary for text cell to have 100% width."
 
         row.tag('td')
 
         row.tag('td')
             .addClass('mbox-empty-cell') -- No image. Cell with some width or padding necessary for text cell to have 100% width.
+
             .addClass('mbox-empty-cell')  
             .cssText(data.imageEmptyCellStyle and 'border:none;padding:0px;width:1px')
+
             .cssText(self.imageEmptyCellStyle)
 
     end
 
     end
  
 
     -- Add the text.
 
     -- Add the text.
 
     local textCell = row.tag('td').addClass('mbox-text')
 
     local textCell = row.tag('td').addClass('mbox-text')
     if data.useCollapsibleTextFields then
+
     if self.useCollapsibleTextFields then
 +
        -- The message box uses advanced text parameters that allow things to be collapsible. At the
 +
        -- moment, only ambox uses this.
 
         textCell
 
         textCell
             .cssText(args.textstyle)
+
             .cssText(self.textstyle)
 
         local textCellSpan = textCell.tag('span')
 
         local textCellSpan = textCell.tag('span')
 
         textCellSpan
 
         textCellSpan
 
             .addClass('mbox-text-span')
 
             .addClass('mbox-text-span')
             .wikitext(issue)
+
             .wikitext(self.issue)
 
         if not isSmall then
 
         if not isSmall then
 
             textCellSpan
 
             textCellSpan
 
                 .tag('span')
 
                 .tag('span')
 
                     .addClass('hide-when-compact')
 
                     .addClass('hide-when-compact')
                     .wikitext(talk)
+
                     .wikitext(self.talk and ' ' .. self.talk)
                     .wikitext(' ')
+
                     .wikitext(self.fix and ' ' .. self.fix)
                    .wikitext(fix)
 
                    .done()
 
 
         end
 
         end
 
         textCellSpan
 
         textCellSpan
             .wikitext(date and format(" <small>''(%s)''</small>", date))
+
             .wikitext(self.date and ' ' .. self.date)
 
         if not isSmall then
 
         if not isSmall then
 
             textCellSpan
 
             textCellSpan
 
                 .tag('span')
 
                 .tag('span')
 
                     .addClass('hide-when-compact')
 
                     .addClass('hide-when-compact')
                     .wikitext(info and ' ' .. info)
+
                     .wikitext(self.info and ' ' .. self.info)
 
         end
 
         end
 
     else
 
     else
 +
        -- Default text formatting - anything goes.
 
         textCell
 
         textCell
             .cssText(args.textstyle)
+
             .cssText(self.textstyle)
             .wikitext(text)
+
             .wikitext(self.text)
 
     end
 
     end
  
 
     -- Add the right-hand image.
 
     -- Add the right-hand image.
     if imageRight and not (data.imageRightNone and imageRight == 'none') then
+
     if self.imageRight then
 
         local imageRightCell = row.tag('td').addClass('mbox-imageright')
 
         local imageRightCell = row.tag('td').addClass('mbox-imageright')
         if not isSmall and data.imageCellDiv then
+
         if not self.imageCellDiv then
 
             imageRightCell = imageRightCell.tag('div').css('width', '52px') -- If we are using a div, redefine imageRightCell so that the image is inside it.
 
             imageRightCell = imageRightCell.tag('div').css('width', '52px') -- If we are using a div, redefine imageRightCell so that the image is inside it.
 
         end
 
         end
 
         imageRightCell
 
         imageRightCell
             .wikitext(imageRight)
+
             .wikitext(self.imageRight)
 
     end
 
     end
  
 
     -- Add the below row.
 
     -- Add the below row.
     if data.below and args.below then
+
     if self.below then
         box.tag('tr')
+
         boxTable.tag('tr')
 
             .tag('td')
 
             .tag('td')
                 .attr('colspan', args.imageright and '3' or '2')
+
                 .attr('colspan', self.imageRight and '3' or '2')
 
                 .addClass('mbox-text')
 
                 .addClass('mbox-text')
                 .cssText(args.textstyle)
+
                 .cssText(self.textstyle)
                 .wikitext(args.below)
+
                 .wikitext(self.below)
 
     end
 
     end
 
    ------------------------ Error messages and categories ----------------------------
 
  
 
     -- Add error message for invalid type parameters.
 
     -- Add error message for invalid type parameters.
     if invalidType then
+
     if self.invalidType then
 
         root
 
         root
 
             .tag('div')
 
             .tag('div')
 
                 .addClass('error')
 
                 .addClass('error')
 
                 .css('text-align', 'center')
 
                 .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 ''))
+
                 .wikitext(format('This message box is using an invalid type parameter (<code>type=%s</code>) and needs fixing.', self.type or ''))
 
     end
 
     end
  
     -- Add categories using categoryHandler.
+
     -- Add categories.
 
     root
 
     root
         .wikitext(categoryHandler{
+
         .wikitext(self.categories)
            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)
 
     return tostring(root)
 +
end
 +
 +
local function makeBox(boxType, args)
 +
    box:setTitle(args)
 +
    local cfg = box:getConfig(boxType)
 +
    args = box:removeBlankArgs(cfg, args)
 +
    box:setBoxParameters(cfg, args)
 +
    return box:export()
 
end
 
end
  
Line 430: Line 523:
 
         -- assume args are being passed directly in from the debug console
 
         -- assume args are being passed directly in from the debug console
 
         -- or from another Lua module.
 
         -- or from another Lua module.
         local origArgs
+
         local args
 
         if frame == mw.getCurrentFrame() then
 
         if frame == mw.getCurrentFrame() then
             origArgs = frame:getParent().args
+
             args = frame:getParent().args
 
             for k, v in pairs(frame.args) do
 
             for k, v in pairs(frame.args) do
                 origArgs = frame.args
+
                 args = frame.args
 
                 break
 
                 break
 
             end
 
             end
 
         else
 
         else
             origArgs = frame
+
             args = frame
 
         end
 
         end
        -- Trim whitespace and remove blank arguments.
+
         return makeBox(boxType, args)
        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
 
end
  
p.mbox = makeWrapper('mbox')
+
return {
p.ambox = makeWrapper('ambox')
+
    box = box,
p.cmbox = makeWrapper('cmbox')
+
    makeBox = makeBox,
p.fmbox = makeWrapper('fmbox')
+
    mbox = makeWrapper('mbox'),
p.imbox = makeWrapper('imbox')
+
    ambox = makeWrapper('ambox'),
p.ombox = makeWrapper('ombox')
+
    cmbox = makeWrapper('cmbox'),
p.tmbox = makeWrapper('tmbox')
+
    fmbox = makeWrapper('fmbox'),
 
+
    imbox = makeWrapper('imbox'),
return p
+
    ombox = makeWrapper('ombox'),
 +
    tmbox = makeWrapper('tmbox')
 +
}

Revision as of 09:15, 28 September 2013

Lua error in package.lua at line 80: module 'Module:HtmlBuilder' not found. Lua error in package.lua at line 80: module 'Module:HtmlBuilder' not found. Lua error in package.lua at line 80: module 'Module:HtmlBuilder' not found.

  1. REDIRECT Template:Intricate template

This is a meta-module that implements the message box templates {{mbox}}, {{ambox}}, {{cmbox}}, {{fmbox}}, {{imbox}}, {{ombox}}, and {{tmbox}}. It is intended to be used from Lua modules, and should not be used directly from wiki pages. If you want to use this module's functionality from a wiki page, please use the individual message box templates instead.

Usage

To use this module from another Lua module, first you need to load it.

<source lang="lua"> local messageBox = require('Module:Message box') </source>

To create a message box, use the main function. It takes two parameters: the first is the box type (as a string), and the second is a table containing the message box parameters.

<source lang="lua"> local box = messageBox.main( boxType, {

   param1 = param1,
   param2 = param2,
   -- More parameters...

}) </source>

There are seven available box types:

Box type Template Purpose
mbox {{mbox}} For message boxes to be used in multiple namespaces
ambox {{ambox}} For article message boxes
cmbox {{cmbox}} For category message boxes
fmbox {{fmbox}} For interface message boxes
imbox {{imbox}} For file namespace message boxes
tmbox {{tmbox}} For talk page message boxes
ombox {{ombox}} For message boxes in other namespaces

See the template page of each box type for the available parameters.

Usage from #invoke

As well as the main function, this module has separate functions for each box type. They are accessed using the code {{#invoke:Message box|mbox|...}}, {{#invoke:Message box|ambox|...}}, etc. These will work when called from other modules, but they access code used to process arguments passed from #invoke, and so calling them will be less efficient than calling main.

Technical details

The module uses the same basic code for each of the templates listed above; the differences between each of them are configured using the data at Module:Message box/configuration. Here are the various configuration options and what they mean:

  • types - a table containing data used by the type parameter of the message box. The table keys are the values that can be passed to the type parameter, and the table values are tables containing the class and the image used by that type.
  • default - the type to use if no value was passed to the type parameter, or if an invalid value was specified.
  • showInvalidTypeError - whether to show an error if the value passed to the type parameter was invalid.
  • allowBlankParams - usually blank values are stripped from parameters passed to the module. However, whitespace is preserved for the parameters included in the allowBlankParams table.
  • allowSmall - whether a small version of the message box can be produced with "small=yes".
  • smallParam - a custom name for the small parameter. For example, if set to "left" you can produce a small message box using "small=left".
  • smallClass - the class to use for small message boxes.
  • substCheck - whether to perform a subst check or not.
  • classes - an array of classes to use with the message box.
  • imageEmptyCell - whether to use an empty <td>...</td> cell if there is no image set. This is used to preserve spacing for message boxes with a width of less than 100% of the screen.
  • imageEmptyCellStyle - whether empty image cells should be styled.
  • imageCheckBlank - whether "image=blank" results in no image being displayed.
  • imageSmallSize - usually, images used in small message boxes are set to 30x30px. This sets a custom size.
  • imageCellDiv - whether to enclose the image in a div enforcing a maximum image size.
  • useCollapsibleTextFields - whether to use text fields that can be collapsed, i.e. "issue", "fix", "talk", etc. Currently only used in ambox.
  • imageRightNone - whether imageright=none results in no image being displayed on the right-hand side of the message box.
  • sectionDefault - the default name for the "section" parameter. Depends on useCollapsibleTextFields.
  • allowMainspaceCategories - allow categorisation in the main namespace.
  • templateCategory - the name of a category to be placed on the template page.
  • templateCategoryRequireName - whether the name parameter is required to display the template category.
  • templateErrorCategory - the name of the error category to be used on the template page.
  • templateErrorParamsToCheck - an array of parameter names to check. If any are absent, the templateErrorCategory is applied to the template page.

-- 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 box = {}

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 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

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
            return 'ombox' -- other namespaces or invalid input
        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
    if not tname then
        for i, val in ipairs(nsVals) do
            nsVals[i] = format('"%s"', val)
        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

function box:addClass(class)
    if type(class) ~= 'string' then return end
    self.classes = self.classes or {}
    tinsert(self.classes, class)
end

function box:setTitle(args)
    -- Get the title object and the namespace.
    local pageTitle = getTitleObject(args.page ~= '' and args.page)
    self.title = pageTitle or mw.title.getCurrentTitle()
    local demospace = box.getNamespaceId(args.demospace ~= '' and args.demospace)
    self.nsid = demospace or self.title.namespace
end

function box:getConfig(boxType)
    -- Get the box config data from the data page.
    if boxType == 'mbox' then
        boxType = box.getMboxType(self.nsid)
    end
    local cfgTables = mw.loadData('Module:Message box/configuration')
    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
    return cfg
end
    
function box:removeBlankArgs(cfg, args)
    -- Only allow blank arguments for the parameter names listed in cfg.allowBlankParams.
    local newArgs = {}
    for k, v in pairs(args) do
        for i, param in ipairs(cfg.allowBlankParams or {}) do
            if v ~= '' or k == param then
                newArgs[k] = v
            end
        end
    end
    return newArgs
end

function box:setBoxParameters(cfg, args)
    -- Get type data.
    self.type = args.type
    local typeData = cfg.types[self.type]
    self.invalidType = self.type and not typeData and true or false
    typeData = typeData or cfg.types[cfg.default]
    self.typeClass = typeData.class
    self.typeImage = typeData.image

    -- 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.
    self.isSmall = cfg.allowSmall and (args.small == 'yes' or args.small == true) and true or false

    -- Add attributes, classes and styles.
    self.id = args.id
    self:addClass(cfg.usePlainlinksParam and yesno(args.plainlinks or true) and 'plainlinks')
    for _, class in ipairs(cfg.classes or {}) do
        self:addClass(class)
    end
    if self.isSmall then
        self:addClass(cfg.smallClass or 'mbox-small')
    end
    self:addClass(self.typeClass)
    self.style = args.style

    -- Set text style.
    self.textstyle = args.textstyle

    -- Process data for collapsible text fields. At the moment these are only used in {{ambox}}.
    self.useCollapsibleTextFields = cfg.useCollapsibleTextFields 
    if self.useCollapsibleTextFields then
        self.name = args.name
        local nameTitle = getTitleObject(name)
        self.isTemplatePage = nameTitle and title.prefixedText == ('Template:' .. nameTitle.text) and true or false

        -- Get the self.issue value.
        local sect = args.sect
        if presentButBlank(sect) then
            sect = 'This ' .. (cfg.sectionDefault or 'page')
        elseif type(sect) == 'string' then
            sect = 'This ' .. sect
        else
            sect = nil
        end
        local issue = args.issue
        issue = type(issue) == 'string' and issue or nil
        local text = args.text
        text = type(text) == 'string' and text or nil
        local issues = {}
        tinsert(issues, sect)
        tinsert(issues, issue)
        tinsert(issues, text)
        self.issue = tconcat(issues, ' ')

        -- Get the self.talk value.
        local talk = args.talk
        if presentButBlank(talk) and self.isTemplatePage then
            talk = '#'
        end
        if talk then
            -- See if the talk link exists and is for a talk or a content namespace.
            local talkTitle = 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
                self.talk = talkText
            end
        end

        -- Get other values.
        self.fix = args.fix
        local date = args.date
        self.date = date and format(" <small>''(%s)''</small>", date)
        if presentButBlank(self.date) and self.isTemplatePage then
            self.date = lang:formatDate('F Y')
        end
        self.info = args.info
    end

    -- Set the non-collapsible text field. At the moment this is used by all box types other than ambox,
    -- and also by ambox when small=yes.
    if self.isSmall then
        if self.useCollapsibleTextFields then
            self.text = args.smalltext or self.issue
        else
            self.text = args.smalltext or args.text
        end
    else
        self.text = args.text
    end

    -- Set the below row.
    self.below = cfg.below and args.below

    -- General image settings.
    self.imageCellDiv = not self.isSmall and cfg.imageCellDiv and true or false
    self.imageEmptyCell = cfg.imageEmptyCell
    if cfg.imageEmptyCellStyle then
        self.imageEmptyCellStyle = 'border:none;padding:0px;width:1px'
    end

    -- Left image settings.
    local imageCheckBlank = cfg.imageCheckBlank
    local imageLeft = self.isSmall and args.smallimage or args.image
    if imageLeft ~= 'none' and not imageCheckBlank or imageLeft ~= 'none' and imageCheckBlank and image ~= 'blank' then
        self.imageLeft = imageLeft
        if not imageLeft then
            local imageSize = self.isSmall and (cfg.imageSmallSize or '30x30px') or '40x40px'
            self.imageLeft = format('[[File:%s|%s|link=|alt=]]', self.typeImage or 'Imbox notice.png', imageSize)
        end
    end

    -- Right image settings.
    local imageRight = self.isSmall and args.smallimageright or args.imageright
    if not (cfg.imageRightNone and imageRight == 'none') then
        self.imageRight = imageRight
    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
        end
        self.catNums = getArgNums(args, 'cat')
        if args.category then
            args.category1 = args.category
        end
        self.categoryNums = getArgNums(args, 'category')
        self.categoryParamNums = union(self.catNums, self.categoryNums)
        -- 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 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 then
                self:addCat('main', mainCat)
            end
            if allCat then
                self:addCat('main', allCat)
            end
        end
    end

    -- Add template-namespace categories.
    self.isTemplatePage = type(self.name) == 'string' and title.prefixedText == ('Template:' .. self.name)
    if cfg.templateCategory then
        if self.name then
            if self.isTemplatePage then
                self:addCat('template', cfg.templateCategory)
            end
        elseif not self.title.isSubpage then
            self:addCat('template', cfg.templateCategory)
        end
    end
    
    -- Add template error category.
    if cfg.templateErrorCategory then
        local templateErrorCategory = cfg.templateErrorCategory
        local templateCat, templateSort
        if not self.name and not self.title.isSubpage then
            templateCat = templateErrorCategory
        elseif type(self.name) == 'string' and title.prefixedText == ('Template:' .. name) then
            local paramsToCheck = cfg.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 = templateErrorCategory
                templateSort = tostring(count)
            end
            if self.categoryNums and #self.categoryNums > 0 then
                templateCat = templateErrorCategory
                templateSort = 'C'
            end
        end
        self:addCat('template', templateCat, templateSort)
    end

    -- Categories for all namespaces.
    if self.invalidType then
        local allSort = (nsid == 0 and 'Main:' or '') .. title.prefixedText
        self:addCat('all', 'Wikipedia message box parameter needs fixing', allSort)
    end
    if self.isSubstituted then
        self:addCat('all', 'Pages with incorrectly substituted templates')
    end

    -- Convert category tables to strings and pass them through [[Module:Category handler]].
    self.categories = categoryHandler{
        main = tconcat(self.mainCats or {}),
        template = tconcat(self.templateCats or {}),
        all = tconcat(self.allCats or {}),
        nocat = args.nocat,
        demospace = self.demospace and args.demospace or nil,
        page = self.pageTitle and pageTitle.prefixedText or nil
    }
end

function box:export()
    local root = htmlBuilder.create()

    -- Add the subst check error.
    if self.isSubstituted and self.name then
        root
            .tag('b')
                .addClass('error')
                .wikitext(format(
                    'Template <code>%s%s%s</code> has been incorrectly substituted.',
                    mw.text.nowiki('{{'), self.name, mw.text.nowiki('}}')
                ))
    end

    -- Create the box table.
    local boxTable = root.tag('table')
    boxTable
        .attr('id', self.id)
    for i, class in ipairs(self.classes or {}) do
        boxTable
            .addClass(class)
    end
    boxTable
        .cssText(self.style)
        .attr('role', 'presentation')

    -- Add the left-hand image.
    local row = boxTable.tag('tr')
    if self.imageLeft then
        local imageLeftCell = row.tag('td').addClass('mbox-image')
        if self.imageCellDiv then
            -- If we are using a div, redefine imageLeftCell so that the image is inside it.
            -- Not sure why only some box types use divs, but it probably has something to do
            -- with that style="width: 52px;". @TODO: find out exactly what this does and fix this comment.
            imageLeftCell = imageLeftCell.tag('div').css('width', '52px') 
        end
        imageLeftCell
            .wikitext(self.imageLeft)
    elseif self.imageEmptyCell then
        -- Some message boxes define an empty cell if no image is specified, and some don't.
        -- The old template code in templates where empty cells are specified gives the following hint:
        -- "No image. Cell with some width or padding necessary for text cell to have 100% width."
        row.tag('td')
            .addClass('mbox-empty-cell') 
            .cssText(self.imageEmptyCellStyle)
    end

    -- Add the text.
    local textCell = row.tag('td').addClass('mbox-text')
    if self.useCollapsibleTextFields then
        -- The message box uses advanced text parameters that allow things to be collapsible. At the
        -- moment, only ambox uses this.
        textCell
            .cssText(self.textstyle)
        local textCellSpan = textCell.tag('span')
        textCellSpan
            .addClass('mbox-text-span')
            .wikitext(self.issue)
        if not isSmall then
            textCellSpan
                .tag('span')
                    .addClass('hide-when-compact')
                    .wikitext(self.talk and ' ' .. self.talk)
                    .wikitext(self.fix and ' ' .. self.fix)
        end
        textCellSpan
            .wikitext(self.date and ' ' .. self.date)
        if not isSmall then
            textCellSpan
                .tag('span')
                    .addClass('hide-when-compact')
                    .wikitext(self.info and ' ' .. self.info)
        end
    else
        -- Default text formatting - anything goes.
        textCell
            .cssText(self.textstyle)
            .wikitext(self.text)
    end

    -- Add the right-hand image.
    if self.imageRight then
        local imageRightCell = row.tag('td').addClass('mbox-imageright')
        if not self.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(self.imageRight)
    end

    -- Add the below row.
    if self.below then
        boxTable.tag('tr')
            .tag('td')
                .attr('colspan', self.imageRight and '3' or '2')
                .addClass('mbox-text')
                .cssText(self.textstyle)
                .wikitext(self.below)
    end

    -- Add error message for invalid type parameters.
    if self.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.', self.type or ''))
    end

    -- Add categories.
    root
        .wikitext(self.categories)

    return tostring(root)
end

local function makeBox(boxType, args)
    box:setTitle(args)
    local cfg = box:getConfig(boxType)
    args = box:removeBlankArgs(cfg, args)
    box:setBoxParameters(cfg, args)
    return box:export()
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 args
        if frame == mw.getCurrentFrame() then
            args = frame:getParent().args
            for k, v in pairs(frame.args) do
                args = frame.args
                break
            end
        else
            args = frame
        end
        return makeBox(boxType, args)
    end
end

return {
    box = box,
    makeBox = makeBox,
    mbox = makeWrapper('mbox'),
    ambox = makeWrapper('ambox'),
    cmbox = makeWrapper('cmbox'),
    fmbox = makeWrapper('fmbox'),
    imbox = makeWrapper('imbox'),
    ombox = makeWrapper('ombox'),
    tmbox = makeWrapper('tmbox')
}