Module:Article history: Difference between revisions

Content deleted Content added
add Message:addWarning and Action:getParameter
per edit request on talk page - remove line as unnecessary
 
(84 intermediate revisions by 6 users not shown)
Line 10:
local CONFIG_PAGE = 'Module:Article history/config'
local WRAPPER_TEMPLATE = 'Template:Article history'
local DEBUG_MODE = false -- If true, errors are not caught.
 
-- Load required modules.
require('Module:No globalsstrict')
local Category = require('Module:Article history/Category')
local yesno = require('Module:Yesno')
Line 71 ⟶ 72:
 
function Message:message(key, ...)
-- This fetches the message from the config with the specified key, and
-- This
-- substitutes parameters $1, $2 etc. with the subsequent values it is
-- passed.
local msg = self.cfg.msg[key]
if select('#', ...) > 0 then
Line 77 ⟶ 80:
else
return msg
end
end
 
function Message:_formatError(msg, help)
if help then
return self:message('error-message-help', msg, help)
else
return self:message('error-message-nohelp', msg)
end
end
Line 92 ⟶ 87:
-- stops unless the error is caught. This is used for errors where
-- subsequent processing becomes impossible.
local errorText = self:_formatError(msg, help)
if help then
errorText = self:message('error-message-help', msg, help)
else
errorText = self:message('error-message-nohelp', msg)
end
error(errorText, 0)
end
Line 101:
-- prevent the module from outputting something useful.
self.warnings = self.warnings or {}
local warningText
table.insert(self.warnings, self:_formatError(msg, help))
if help then
warningText = self:message('warning-help', msg, help)
else
warningText = self:message('warning-nohelp', msg)
end
table.insert(self.warnings, warningText)
end
 
Line 121 ⟶ 127:
obj.cfg = data.cfg
obj.currentTitle = data.currentTitle
obj.isSmallmakeData = data.isSmallmakeData -- used by Row:getData
obj.categories = {}
return obj
end
 
function Row:setIcon_cachedTry(iconcacheKey, captionerrorCacheKey, sizefunc)
-- This method is for use in Row object methods that are called more than
-- once. The results of such methods should be cached to avoid unnecessary
-- processing. We also cache any errors found and abort if an error was
-- raised previously, otherwise error messages could be displayed multiple
-- times.
--
-- We use false as a key to cache nil results, so func cannot return false.
--
-- @param cacheKey The key to cache successful results with
-- @param errorCacheKey The key to cache errors with
-- @param func an anonymous function that returns the method result
if self[errorCacheKey] then
return nil
end
local ret = self[cacheKey]
if ret then
return ret
elseif ret == false then
return nil
end
local success
if DEBUG_MODE then
success = true
ret = func()
else
success, ret = pcall(func)
end
if success then
if ret then
self[cacheKey] = ret
return ret
else
self[cacheKey] = false
return nil
end
else
self[errorCacheKey] = true
-- We have already formatted the error message, so no need to format it
-- again.
error(ret, 0)
end
end
 
function Row:getData(articleHistoryObj)
return self:_cachedTry('_dataCache', '_isDataError', function ()
return self.makeData(articleHistoryObj)
end)
end
 
function Row:setIconValues(icon, caption, size)
self.icon = icon
self.captioniconCaption = caption
self.iconSize = size
end
 
function Row:setTextgetIcon(textarticleHistoryObj)
return maybeCallFunc(self.icon, articleHistoryObj, self)
self.text = text
end
 
function Row:exportHtmlgetIconCaption(articleHistoryObj)
return maybeCallFunc(self.iconCaption, articleHistoryObj, self)
local html = mw.html.create('tr')
end
 
function Row:getIconSize()
-- Render the icon link.
return self.iconSize or self.cfg.defaultIconSize or '30px'
local icon
end
if self.icon then
 
local iconSize
function Row:renderIcon(articleHistoryObj)
if self.isSmall then
local icon = self:getIcon(articleHistoryObj)
iconSize = self.iconSmallSize or self.cfg.defaultSmallIconSize or '15px'
if not icon then
else
return nil
iconSize = self.iconSize or self.cfg.defaultIconSize or '30px'
end
return renderImage(
icon,
self:getIconCaption(articleHistoryObj),
self:getIconSize()
)
end
 
function Row:setNoticeBarIconValues(icon, caption, size)
self.noticeBarIcon = icon
self.noticeBarIconCaption = caption
self.noticeBarIconSize = size
end
 
function Row:getNoticeBarIcon(articleHistoryObj)
local icon = maybeCallFunc(self.noticeBarIcon, articleHistoryObj, self)
if icon == true then
icon = self:getIcon(articleHistoryObj)
if not icon then
self:raiseError(
self:message('row-error-missing-icon'),
self:message('row-error-missing-icon-help')
)
end
icon = renderImage(self.icon, self.caption, iconSize)
end
return icon
end
 
function Row:getNoticeBarIconCaption(articleHistoryObj)
-- Render the HTML
local caption = maybeCallFunc(
html
self.noticeBarIconCaption,
:tag('td')
articleHistoryObj,
:addClass('mbox-image')
self
:wikitext(icon)
)
:done()
if not caption then
:tag('td')
caption = self:getIconCaption(articleHistoryObj)
:addClass('mbox-text')
end
:wikitext(self.text)
return caption
end
 
function Row:getNoticeBarIconSize()
return html
return self.noticeBarIconSize or self.cfg.defaultNoticeBarIconSize or '15px'
end
 
function Row:addCategoriesexportNoticeBarIcon(val, ...articleHistoryObj)
local icon = self:getNoticeBarIcon(articleHistoryObj)
-- Add categories to the object's categories table. val can be either an
if not icon then
-- array of strings or a function returning an array of category objects.
return nil
if type(val) == 'table' then
for _, cat in ipairs(val) do
table.insert(self.categories, Category.new(cat))
end
elseif type(val) == 'function' then
for _, categoryObj in ipairs(val(...) or {}) do
table.insert(self.categories, categoryObj)
end
end
return renderImage(
icon,
self:getNoticeBarIconCaption(articleHistoryObj),
self:getNoticeBarIconSize()
)
end
 
function Row:exportCategoriessetText(text)
return self.categoriestext = text
end
 
function Row:setNoticeBarIcongetText(icon, caption, sizearticleHistoryObj)
return maybeCallFunc(self.text, articleHistoryObj, self)
self.noticeBarIcon = icon
self.noticeBarCaption = caption
self.noticeBarIconSize = size
end
 
function Row:exportNoticeBarIconexportHtml(articleHistoryObj)
if not self.noticeBarIcon_html then
return self._html
end
local text = self:getText(articleHistoryObj)
if not text then
return nil
end
local html = mw.html.create('tr')
local size = self.noticeBarIconSize or self.cfg.defaultNoticeBarIconSize or '15px'
html
return renderImage(self.noticeBarIcon, size, self.noticeBarCaption)
:tag('td')
:addClass('mbox-image')
:wikitext(self:renderIcon(articleHistoryObj))
:done()
:tag('td')
:addClass('mbox-text')
:wikitext(text)
self._html = html
return html
end
 
function Row:setCategories(val)
-- Set the categories from the object's config. val can be either an array
-- of strings or a function returning an array of category objects.
self.categories = val
end
 
function Row:getCategories(articleHistoryObj)
local ret = {}
if type(self.categories) == 'table' then
for _, cat in ipairs(self.categories) do
ret[#ret + 1] = Category.new(cat)
end
elseif type(self.categories) == 'function' then
local t = self.categories(articleHistoryObj, self) or {}
for _, categoryObj in ipairs(t) do
ret[#ret + 1] = categoryObj
end
end
return ret
end
 
Line 209 ⟶ 322:
 
obj.id = data.id
local obj.statusCfg = obj.cfg.statuses[obj.id]
obj.statusCfgname = obj.statusCfg.name
obj:setIconValues(
obj.statusCfg.icon,
-- Set the icon
obj.statusCfg.iconCaption or obj.name,
local iconSize
data.iconSize
if obj.isSmall then
)
iconSize = statusCfg.smallIconSize or obj.cfg.defaultSmallStatusIconSize or '30px'
obj:setNoticeBarIconValues(
else
obj.statusCfg.noticeBarIcon,
iconSize = statusCfg.iconSize or obj.cfg.defaultStatusIconSize or '50px'
obj.statusCfg.noticeBarIconCaption or obj.name,
end
obj:setIcon(statusCfg.icon, statusCfg.caption, iconSize)noticeBarIconSize
)
obj:setText(obj.statusCfg.text)
obj:setCategories(obj.statusCfg.categories)
 
return obj
end
 
function Status:exportHtmlgetIconSize(articleHistoryObj)
return self.iconSize
self:setText(substituteParams(
maybeCallFunc(or self.statusCfg.text, articleHistoryObj),iconSize
or self.cfg.defaultStatusIconSize
self.currentTitle.subjectPageTitle.prefixedText,
or '50px'
self.currentTitle.text
))
return Row.exportHtml(self)
end
 
function Status:exportCategoriesgetText(articleHistoryObj)
self:addCategorieslocal text = Row.getText(self.statusCfg.categories, articleHistoryObj)
if text then
return Row.exportCategories(self)
return substituteParams(
text,
self.currentTitle.subjectPageTitle.prefixedText,
self.currentTitle.text
)
end
end
 
-------------------------------------------------------------------------------
-- DoubleStatusMultiStatus class
-- For when an article can have twomultiple distinct statuses, e.g. former featured
-- featured article status and good article status.
-------------------------------------------------------------------------------
 
local DoubleStatusMultiStatus = setmetatable({}, Row)
DoubleStatusMultiStatus.__index = DoubleStatusMultiStatus
 
function DoubleStatusMultiStatus.new(data)
local obj = Row.new(data)
setmetatable(obj, DoubleStatusMultiStatus)
 
obj.id = data.id
obj.statusCfg = obj.cfg.statuses[data.id]
obj.name = obj.statusCfg.name
 
-- Set child status objects
local function getChildStatusData(data, id, iconSize)
local ret = {}
for k, v in pairs(data) do
ret[k] = v
end
ret.id = id
ret.iconSize = iconSize
return ret
end
obj.statuses = {}
local defaultIconSize = obj.cfg.defaultMultiStatusIconSize or '30px'
for _, id in ipairs(obj.statusCfg.statuses) do
table.insert(obj.statuses, Status.new(getChildStatusData(
data,
id,
obj.cfg.statuses[id].iconMultiSize or defaultIconSize
)))
end
 
return obj
end
 
function MultiStatus:exportHtml(articleHistoryObj)
local ret = mw.html.create()
for _, obj in ipairs(self.statuses) do
ret:node(obj:exportHtml(articleHistoryObj))
end
return ret
end
 
function MultiStatus:getCategories(articleHistoryObj)
local ret = {}
for _, obj in ipairs(self.statuses) do
for _, categoryObj in ipairs(obj:getCategories(articleHistoryObj)) do
ret[#ret + 1] = categoryObj
end
end
return ret
end
 
function MultiStatus:exportNoticeBarIcon()
local ret = {}
for _, obj in ipairs(self.statuses) do
ret[#ret + 1] = obj:exportNoticeBarIcon()
end
return table.concat(ret)
end
 
function MultiStatus:getWarnings()
local ret = {}
for _, obj in ipairs(self.statuses) do
for _, msg in ipairs(obj:getWarnings()) do
ret[#ret + 1] = msg
end
end
return ret
end
 
Line 265 ⟶ 446:
local obj = Row.new(data)
setmetatable(obj, Notice)
 
obj:setIconValues(
data.icon,
data.iconCaption,
data.iconSize
)
obj:setNoticeBarIconValues(
data.noticeBarIcon,
data.noticeBarIconCaption,
data.noticeBarIconSize
)
obj:setText(data.text)
obj:setCategories(data.categories)
 
return obj
end
Line 288 ⟶ 483:
 
-- Set the ID
do
if not data.code then
if not data.code then
local codeParam = obj.cfg.actionParamPrefix .. tostring(obj.paramNum)
obj:raiseError(
obj:message('action-error-no-code', codeParamobj:getParameter('code')),
obj:message('action-error-no-code-help')
)
end
obj.id local = obj.cfg.actions[data.code] and= objmw.cfgustring.actions[upper(data.code].id)
obj.id = obj.cfg.actions[code] and obj.cfg.actions[code].id
if not obj.id then
if not obj:raiseError(.id then
obj:raiseError(
obj:message('action-error-invalid-code', data.code, obj:getParameter('code')),
obj:message(
'action-error-invalid-code-help'),
data.code,
)
obj:getParameter('code')
),
obj:message('action-error-invalid-code-help')
)
end
end
 
Line 310 ⟶ 511:
 
-- Set the result ID
do
if obj.actionCfg.results[data.resultCode] then
obj.resultIdlocal resultCode = obj.actionCfg.results[data.resultCode].id
and mw.ustring.lower(data.resultCode)
else
or '_BLANK'
-- @TODO: Error
if obj.actionCfg.results[resultCode] then
obj.resultId = obj.actionCfg.results[resultCode].id
elseif resultCode == '_BLANK' then
obj:raiseError(
obj:message(
'action-error-blank-result',
obj.id,
obj:getParameter('resultCode')
),
obj:message('action-error-blank-result-help')
)
else
obj:raiseError(
obj:message(
'action-error-invalid-result',
data.resultCode,
obj.id,
obj:getParameter('resultCode')
),
obj:message('action-error-invalid-result-help')
)
end
end
 
Line 327 ⟶ 550:
obj.date = date
else
obj:addWarning(
-- @TODO: Error
obj:message(
'action-warning-invalid-date',
data.date,
obj:getParameter('date')
),
obj:message('action-warning-invalid-date-help')
)
end
else
obj:addWarning(
-- @TODO: Error
obj:message(
'action-warning-no-date',
obj.paramNum,
obj:getParameter('date'),
obj:getParameter('code')
),
obj:message('action-warning-no-date-help')
)
end
obj.date = obj.date or obj:message('action-date-missing')
 
-- Set the oldid
obj.oldid = tonumber(data.oldid)
if data.oldid and (not obj.oldid or not isPositiveInteger(obj.oldid)) then
obj.oldid = nil
-- @TODO: Error
obj:addWarning(
obj:message(
'action-warning-invalid-oldid',
data.oldid,
obj:getParameter('oldid')
),
obj:message('action-warning-invalid-oldid-help')
)
end
 
-- Set the notice bar icon values
obj:setNoticeBarIconValues(
data.noticeBarIcon,
data.noticeBarIconCaption,
data.noticeBarIconSize
)
 
-- Set the categories
obj:setCategories(obj.actionCfg.categories)
 
return obj
Line 349 ⟶ 606:
for k, v in pairs(self.cfg.actionParamSuffixes) do
if v == key then
suffix = vk
break
end
Line 372 ⟶ 629:
 
function Action:exportHtml(articleHistoryObj)
if self._html then
return self._html
end
 
local row = mw.html.create('tr')
 
Line 396 ⟶ 657:
self:getName(articleHistoryObj)
))
 
-- Result cell
row
Line 402 ⟶ 663:
:wikitext(self:getResult(articleHistoryObj))
 
self._html = row
return row
end
 
function Action:exportCategories(articleHistoryObj)
local categories = self.actionCfg.categories
if type(categories) == 'table' then
local ret = {}
for _, cat in ipairs(categories) do
ret[#ret + 1] = Category.new(cat)
end
return ret
elseif type(categories) == 'function' then
return categories(self, articleHistoryObj)
end
end
 
Line 430 ⟶ 679:
local obj = Row.new(data)
setmetatable(obj, CollapsibleNotice)
 
obj:setIconValues(
data.icon,
data.iconCaption,
data.iconSize
)
obj:setNoticeBarIconValues(
data.noticeBarIcon,
data.noticeBarIconCaption,
data.noticeBarIconSize
)
obj:setText(data.text)
obj:setCollapsibleText(data.collapsibleText)
obj:setCategories(data.categories)
 
return obj
end
 
function CollapsibleNotice:setCollapsibleText(s)
self.collapsibleText = s
end
 
function CollapsibleNotice:getCollapsibleText(articleHistoryObj)
return maybeCallFunc(self.collapsibleText, articleHistoryObj, self)
end
 
function CollapsibleNotice:getIconSize()
return self.iconSize
or self.cfg.defaultCollapsibleNoticeIconSize
or '20px'
end
 
function CollapsibleNotice:exportHtml(articleHistoryObj, isInCollapsibleTable)
local cacheKey = isInCollapsibleTable
and '_htmlCacheCollapsible'
or '_htmlCacheDefault'
return self:_cachedTry(cacheKey, '_isHtmlError', function ()
local text = self:getText(articleHistoryObj)
if not text then
return nil
end
 
local function maybeMakeCollapsibleTable(cell, text, collapsibleText)
-- If collapsible text is specified, makes a collapsible table
-- inside the cell with two rows, a header row with one cell and a
-- collapsed row with one cell. These are filled with text and
-- collapsedText, respectively. If no collapsible text is
-- specified, the text is added to the cell as-is.
if collapsibleText then
cell
:tag('div')
:addClass('mw-collapsible mw-collapsed')
:tag('div')
:wikitext(text)
:done()
:tag('div')
:addClass('mw-collapsible-content')
:css('border', '1px silver solid')
:wikitext(collapsibleText)
else
cell:wikitext(text)
end
end
 
local html = mw.html.create('tr')
local icon = self:renderIcon(articleHistoryObj)
local collapsibleText = self:getCollapsibleText(articleHistoryObj)
if isInCollapsibleTable then
local textCell = html:tag('td')
:attr('colspan', 3)
:css('width', '100%')
local rowText
if icon then
rowText = icon .. ' ' .. text
else
rowText = text
end
maybeMakeCollapsibleTable(textCell, rowText, collapsibleText)
else
local textCell = html
:tag('td')
:addClass('mbox-image')
:wikitext(icon)
:done()
:tag('td')
:addClass('mbox-text')
maybeMakeCollapsibleTable(textCell, text, collapsibleText)
end
 
return html
end)
end
 
Line 449 ⟶ 788:
obj.currentTitle = currentTitle or mw.title.getCurrentTitle()
 
-- Define object structure.
-- Set isSmall
obj._errors = {}
obj.isSmall = yesno(obj.args.small) or false
obj._allObjectsCache = {}
 
-- Define the error table.
obj.errors = {}
-- Format the config
local function substituteAliases(t, ret)
Line 482 ⟶ 819:
end
obj.cfg = substituteAliases(cfg or require(CONFIG_PAGE))
 
--[[
-- Get a table of the arguments sorted by prefix and number. Non-string
-- keys and keys that don't contain a number are ignored. (This means that
-- positional parameters are ignored, as they are numbers, not strings.)
-- The parameter numbers are stored in the first positional parameter of
-- the subtables, and any gaps are removed so that the tables can be
-- iterated over with ipairs.
--
-- For example, these arguments:
-- {a1x = 'eggs', a1y = 'spam', a2x = 'chips', b1z = 'beans', b3x = 'bacon'}
-- would translate into this prefixArgs table.
-- {
-- a = {
-- {1, x = 'eggs', y = 'spam'},
-- {2, x = 'chips'}
-- },
-- b = {
-- {1, z = 'beans'},
-- {3, x = 'bacon'}
-- }
-- }
--]]
do
local prefixArgs = {}
for k, v in pairs(obj.args) do
if type(k) == 'string' then
local prefix, num, suffix = k:match('^(.-)([1-9][0-9]*)(.*)$')
if prefix then
num = tonumber(num)
prefixArgs[prefix] = prefixArgs[prefix] or {}
prefixArgs[prefix][num] = prefixArgs[prefix][num] or {}
prefixArgs[prefix][num][suffix] = v
prefixArgs[prefix][num][1] = num
end
end
end
-- Remove the gaps
local prefixArrays = {}
for prefix, prefixTable in pairs(prefixArgs) do
prefixArrays[prefix] = {}
local numKeys = {}
for num in pairs(prefixTable) do
numKeys[#numKeys + 1] = num
end
table.sort(numKeys)
for _, num in ipairs(numKeys) do
table.insert(prefixArrays[prefix], prefixTable[num])
end
end
obj.prefixArgs = prefixArrays
end
 
return obj
end
 
function ArticleHistory:try(func, ...)
if DEBUG_MODE then
local success, val = pcall(func, ...)
local val = func(...)
if success then
return val
else
local success, val = pcall(func, ...)
table.insert(self.errors, val)
if success then
return nil
return val
else
table.insert(self._errors, val)
return nil
end
end
end
Line 500 ⟶ 894:
-- user. We memoise this so that the parameters only have to be processed
-- once.
if self.actions ~= nil then
return self.actions
end
 
-- Get the action args, and exit if they don't exist.
-- Filter the arguments for actions.
local actionArgs = self.prefixArgs[self.cfg.actionParamPrefix]
local actionParams = {}
if not actionArgs then
local pattern = '^' .. self.cfg.actionParamPrefix .. '([1-9][0-9]*)(.-)$'
self.actions = {}
return self.actions
end
 
-- Make the objects.
local actions = {}
local suffixes = self.cfg.actionParamSuffixes
for k_, vt in pairsipairs(self.argsactionArgs) do
local objArgs = {}
if type(k) == 'string' then
localfor numk, suffixv =in k:matchpairs(patternt) do
iflocal numnewK and suffix and= suffixes[suffixk] then
if newK then
num = tonumber(num)
local t = actionParamsobjArgs[numnewK] or= {}v
t[suffixes[suffix]] = v
t.paramNum = num
actionParams[num] = t
end
end
objArgs.paramNum = t[1]
end
objArgs.cfg = self.cfg
objArgs.currentTitle = self.currentTitle
-- Sort the action parameters.
local actionObj = self:try(Action.new, objArgs)
local actionParamsSorted = {}
for num, t in pairs(actionParams) do
table.insert(actionParamsSorted, t)
end
table.sort(actionParamsSorted, function (t1, t2)
return t1.paramNum < t2.paramNum
end)
-- Create the action objects.
local actions = {}
for _, t in ipairs(actionParamsSorted) do
t.cfg = self.cfg
t.currentTitle = self.currentTitle
local actionObj = self:try(Action.new, t)
table.insert(actions, actionObj)
end
 
self.actions = actions
return actions
Line 550 ⟶ 933:
end
local statuses = self.cfg.statuses
codelocal codeUpper = mw.ustring.upper(code)
if statuses[codecodeUpper] then
return statuses[codecodeUpper].id
else
self:addWarning(
-- @TODO: Error
self:message('articlehistory-warning-invalid-status', code),
self:message('articlehistory-warning-invalid-status-help')
)
return nil
end
end
Line 560 ⟶ 947:
function ArticleHistory:getStatusObj()
-- Get the status object for the current status.
if self.statusObj ~== nilfalse then
return nil
elseif self.statusObj ~= nil then
return self.statusObj
end
local statusId
if self.cfg.getStatusIdFunction then
statusId = self:try(self.cfg.getStatusIdFunction(, self)
else
statusId = self:try(
self.getStatusIdForCode, self,
self.args[self.currentstatuscfg.currentStatusParam]
)
end
if not statusId then
self.statusObj = false
return falsenil
end
 
-- Check that some actions were specified, and if not add a warning.
local actions = self:getActionObjects()
if #actions < 1 then
self:addWarning(
self:message('articlehistory-warning-status-no-actions'),
self:message('articlehistory-warning-status-no-actions-help')
)
end
 
Line 581 ⟶ 979:
id = statusId,
currentTitle = self.currentTitle,
cfg = self.cfg,
isSmall = self.isSmall
}
local statusObjisMulti = self:try(Status.new, statusObjData) or falsecfg.statuses[statusId].isMulti
local initFunc = isMulti and MultiStatus.new or Status.new
self.statusObj = statusObj
returnlocal statusObj = self:try(initFunc, statusObjData)
self.statusObj = statusObj or false
return self.statusObj or nil
end
 
Line 594 ⟶ 993:
end
 
function ArticleHistory:getNoticeObjects_noticeFactory(memoizeKey, configKey, class)
-- This holds the logic for fetching tables of Notice and CollapsibleNotice
-- Returns an array of notice objects to be output.
-- objects.
if self[memoizeKey] then
return self[memoizeKey]
end
local ret = {}
for _, t in ipairs(self.cfg[configKey] or {}) do
if t.isActive(self) then
local data = {}
for k, v in pairs(t) do
if k ~= 'isActive' then
data[k] = v
end
end
data.cfg = self.cfg
data.currentTitle = self.currentTitle
ret[#ret + 1] = class.new(data)
end
end
self[memoizeKey] = ret
return ret
end
 
function ArticleHistory:getNoticeObjects()
return self:_noticeFactory('notices', 'notices', Notice)
end
 
function ArticleHistory:getCollapsibleNoticeObjects()
return self:_noticeFactory(
-- Returns an array of collapsible notice objects to be output.
'collapsibleNotices',
'collapsibleNotices',
CollapsibleNotice
)
end
 
function ArticleHistory:getAllObjects(addSelf)
local cacheKey = addSelf and 'addSelf' or 'default'
local ret = self._allObjectsCache[cacheKey]
if not ret then
ret = {}
local statusObj = self:getStatusObj()
if statusObj then
ret[#ret + 1] = statusObj
end
local objTables = {
self:getNoticeObjects(),
self:getActionObjects(),
self:getCollapsibleNoticeObjects()
}
for _, t in ipairs(objTables) do
for _, obj in ipairs(t) do
ret[#ret + 1] = obj
end
end
if addSelf then
ret[#ret + 1] = self
end
self._allObjectsCache[cacheKey] = ret
end
return ret
end
 
function ArticleHistory:getNoticeBarIcons()
local ret = {}
-- Icons that aren't part of a row.
if self.cfg.noticeBarIcons then
for _, data in ipairs(self.cfg.noticeBarIcons) do
if data.isActive(self) then
ret[#ret + 1] = renderImage(
data.icon,
nil,
data.size or self.cfg.defaultNoticeBarIconSize
)
end
end
end
-- Icons in row objects.
for _, obj in ipairs(self:getAllObjects()) do
ret[#ret + 1] = obj:exportNoticeBarIcon(self)
end
return ret
end
 
function ArticleHistory:renderBoxgetErrorMessages()
-- Returns an array of error/warning strings. Error strings come first.
local root = mw.html.create('table')
local ret = {}
root:addClass('tmbox tmbox-notice')
for _, msg in ipairs(self._errors) do
if self.isSmall then
ret[#ret + 1] = msg
root:addClass('mbox-small')
else
root:css('width', '80%')
end
for _, obj in ipairs(self:getAllObjects(true)) do
for _, msg in ipairs(obj:getWarnings()) do
ret[#ret + 1] = msg
end
end
return ret
end
 
function ArticleHistory:categoriesAreActive()
-- Returns a boolean indicating whether categories should be output or not.
local title = self.currentTitle
local ns = title.namespace
return title.isTalkPage
and ns ~= 3 -- not user talk
and ns ~= 119 -- not draft talk
end
 
function ArticleHistory:renderCategories()
local ret = {}
 
if self:categoriesAreActive() then
-- Child object categories
for _, obj in ipairs(self:getAllObjects()) do
local categories = self:try(obj.getCategories, obj, self)
for _, categoryObj in ipairs(categories or {}) do
ret[#ret + 1] = tostring(categoryObj)
end
end
 
-- Extra categories
for _, func in ipairs(self.cfg.extraCategories or {}) do
local cats = func(self) or {}
for _, categoryObj in ipairs(cats) do
ret[#ret + 1] = tostring(categoryObj)
end
end
end
 
return table.concat(ret)
end
 
function ArticleHistory:__tostring()
local root = mw.html.create()
 
-- Table root
local tableRoot = root:tag('table')
tableRoot:addClass('article-history tmbox tmbox-notice')
 
-- Status
local statusObj = self:getStatusObj()
if statusObj then
roottableRoot:node(self:try(statusObj.exportHtml, statusObj, self))
end
 
Line 624 ⟶ 1,140:
local notices = self:getNoticeObjects()
for _, noticeObj in ipairs(notices) do
roottableRoot:node(self:try(noticeObj.exportHtml, noticeObj, self))
end
 
-- Get action objects and the collapsible notice objects, and generate theirthe
-- HTML objects. Usefor the HTMLaction objects. toWe calculateneed the numberaction ofHTML objects collapsibleso
-- rows.that (Wewe can't useaccurately calculate the countnumber of action and collapsible noticerows, as objectssome
-- action objects may generate errors when the HTML is generated.
-- themselves, as it is inaccurate if they generate any errors.)
local actions = self:getActionObjects() or {}
local actionHtmlObjects, collapsibleNoticeHtmlObjects = {}, {}
local actionscollapsibleNotices = self:getActionObjectsgetCollapsibleNoticeObjects() or {}
local collapsibleNoticeHtmlObjects, actionHtmlObjects = {}, {}
local collapsibleNotices = self:getCollapsibleNoticeObjects()
for _, obj in ipairs(actions or {}) do
table.insert(
actionHtmlObjects,
Line 640 ⟶ 1,156:
)
end
for _, obj in ipairs(collapsibleNotices or {}) do
table.insert(
collapsibleNoticeHtmlObjects,
self:try(obj.exportHtml, obj, self, true) -- Render the collapsed version
)
end
local noCollapsibleRowsnActionRows = #actionHtmlObjects + #collapsibleNoticeHtmlObjects
local nCollapsibleRows = nActionRows + #collapsibleNoticeHtmlObjects
 
-- Find out if we are collapsed or not.
-- Collapsible table for actions and collapsible notices
local isCollapsed = yesno(self.args.collapse)
if noCollapsibleRows > 0 then
if isCollapsed == nil then
-- Find out if we are collapsed or not.
local isCollapsed
if self.cfg.uncollapsedRows == 'all' then
isCollapsed = false
elseif nCollapsibleRows == 1 then
isCollapsed = false
else
isCollapsed = noCollapsibleRowsnCollapsibleRows > (tonumber(self.cfg.uncollapsedRows) or 3)
end
end
 
-- If we are not collapsed, re-render the collapsible notices in the
-- non-collapsed version.
if not isCollapsed then
collapsibleNoticeHtmlObjects = {}
for _, obj in ipairs(collapsibleNotices) do
table.insert(
collapsibleNoticeHtmlObjects,
self:try(obj.exportHtml, obj, self, false)
)
end
end
 
-- Collapsible table for actions and collapsible notices. Collapsible
-- notices are only included in the table if it is collapsed. Action rows
-- are always included.
local collapsibleTable
if isCollapsed or nActionRows > 0 then
-- Collapsible table base
local collapsibleTable = roottableRoot
:tag('tr')
:tag('td')
Line 665 ⟶ 1,201:
:css('width', '100%')
:tag('table')
:addClass('AHarticle-history-milestones')
:addClass(isCollapsed and 'mw-collapsible mw-collapsed' or nil)
:css('width', '100%')
:css('background', 'transparent')
:css('font-size', '90%')
 
Line 677 ⟶ 1,212:
:attr('colspan', 3)
:css('font-size', '110%')
 
local noticeBarIcons = {}
-- Notice bar
for i, t in ipairs{notices, collapsibleNotices, actions} do
if isCollapsed then
for j, obj in ipairs(t) do
table.insert(local noticeBarIcons, obj= self:exportNoticeBarIcongetNoticeBarIcons())
if #noticeBarIcons > 0 then
local noticeBar = ctHeader:tag('span'):css('float', 'left')
for _, icon in ipairs(noticeBarIcons) do
noticeBar:wikitext(icon)
end
ctHeader:wikitext(' ')
end
end
 
if #noticeBarIcons > 0 then
-- Header text
local noticeBar = ctHeader:tag('span'):css('float', 'left')
for _, icon in ipairs(noticeBarIcons) do
noticeBar:wikitext(icon)
end
ctHeader:wikitext(' ')
end
if mw.site.namespaces[self.currentTitle.namespace].subject.id == 0 then
ctHeader:wikitext(self:message('milestones-header'))
Line 700 ⟶ 1,236:
 
-- Subheadings
if nActionRows > 0 then
collapsibleTable
collapsibleTable
:tag('tr')
:tag('tr')
:css('text-align', 'left')
:tag('th')
:wikitext(self:message('milestones-date-header'))
:done()
:tag('th')
:wikitext(self:message('milestones-process-header'))
:done()
:tag('th')
:wikitext(self:message('milestones-result-header'))
end
 
-- Actions and collapsible notices
for i_, thtmlObj in ipairs{(actionHtmlObjects, collapsibleNoticeHtmlObjects}) do
collapsibleTable:node(htmlObj)
for j, htmlObj in ipairs(t) do
collapsibleTable:node(htmlObj)
end
end
end
 
-- Collapsible notices and current status
-- Error row
-- These are only included in the collapsible table if it is collapsed.
if #self.errors > 0 then
-- Otherwise, they are added afterwards, so that they align with the
local errorList = root
-- notices.
do
local tableNode, statusColspan
if isCollapsed then
tableNode = collapsibleTable
statusColspan = 3
else
tableNode = tableRoot
statusColspan = 2
end
 
-- Collapsible notices
for _, obj in ipairs(collapsibleNotices) do
tableNode:node(self:try(obj.exportHtml, obj, self, isCollapsed))
end
 
-- Current status
if statusObj and nActionRows > 1 then
tableNode
:tag('tr')
:tag('td')
:attr('colspan', statusColspan)
:wikitext(self:message('status-blurb', statusObj.name))
end
end
 
-- Get the categories. We have to do this before the error row, so that
-- category errors display.
local categories = self:renderCategories()
 
-- Error row and error category
local errors = self:getErrorMessages()
local errorCategory
if #errors > 0 then
local errorList = tableRoot
:tag('tr')
:tag('td')
Line 730 ⟶ 1,301:
:addClass('error')
:css('font-weight', 'bold')
for _, msg in ipairs(self.errors) do
errorList:tag('li'):wikitext(msg)
end
if self:categoriesAreActive() then
end
errorCategory = tostring(Category.new(self:message(
 
'error-category'
return tostring(root)
)))
end
 
function ArticleHistory:renderCategories()
local ret = {}
-- Status object categories
local statusObj = self:getStatusObj()
if statusObj then
local categories = self:try(statusObj.exportCategories, statusObj, self)
for i, categoryObj in ipairs(categories or {}) do
ret[#ret + 1] = tostring(categoryObj)
end
end
 
-- If there are no errors and no active objects, then exit. We can't make
-- Action object categories
-- this check earlier as we don't know where the errors may be until we
for i, actionObj in ipairs(self:getActionObjects() or {}) do
-- have finished rendering the banner.
local categories = self:try(actionObj.exportCategories, actionObj, self)
elseif #self:getAllObjects() < 1 then
for j, categoryObj in ipairs(categories or {}) do
return ''
ret[#ret + 1] = tostring(categoryObj)
end
end
 
-- ErrorAdd categorythe categories
root:wikitext(categories)
if #self.errors > 0 then
root:wikitext(errorCategory)
ret[#ret + 1] = tostring(Category.new(self:message('error-category')))
end
local frame = mw.getCurrentFrame()
 
return table.concat(ret)frame:extensionTag{
name = 'templatestyles', args = { src = 'Module:Message box/tmbox.css' }
end
} .. frame:extensionTag{
 
name = 'templatestyles', args = { src = 'Module:Article history/styles.css' }
function ArticleHistory:__tostring()
} .. tostring(root)
return self:renderBox() .. self:renderCategories()
end
 
Line 786 ⟶ 1,345:
wrappers = WRAPPER_TEMPLATE
})
if frame:getTitle():find('sandbox', 1, true) then
CONFIG_PAGE = CONFIG_PAGE .. '/sandbox'
end
return p._main(args)
end
Line 794 ⟶ 1,356:
Row = Row,
Status = Status,
MultiStatus = MultiStatus,
Notice = Notice,
Action = Action,