Module:Article history: Difference between revisions

Content deleted Content added
use MultiStatus and isMulti to reflect the fact that any number of statuses can be included in the class
per edit request on talk page - remove line as unnecessary
 
(76 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 = truefalse -- 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 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 78 ⟶ 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 93 ⟶ 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 102 ⟶ 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 122 ⟶ 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 210 ⟶ 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
 
Line 252 ⟶ 371:
setmetatable(obj, MultiStatus)
 
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
Line 261 ⟶ 382:
end
ret.id = id
ret.iconSize = iconSize
return ret
end
obj.statuses = {}
local defaultIconSize = obj.cfg.defaultMultiStatusIconSize or '30px'
for i, id in ipairs(obj.statusCfg.statuses) do
for _, id in ipairs(obj.statusCfg.statuses) do
table.insert(obj.statuses, Status.new(getChildStatusData(data, id)))
table.insert(obj.statuses, Status.new(getChildStatusData(
data,
id,
obj.cfg.statuses[id].iconMultiSize or defaultIconSize
)))
end
 
Line 273 ⟶ 400:
function MultiStatus:exportHtml(articleHistoryObj)
local ret = mw.html.create()
for i_, obj in ipairs(self.statuses) do
ret:node(obj:exportHtml(articleHistoryObj))
end
Line 279 ⟶ 406:
end
 
function MultiStatus:exportCategoriesgetCategories(articleHistoryObj)
local ret = {}
for i_, obj in ipairs(self.statuses) do
for j_, categoryObj in ipairs(obj:exportCategoriesgetCategories(articleHistoryObj)) do
ret[#ret + 1] = categoryObj
end
Line 291 ⟶ 418:
function MultiStatus:exportNoticeBarIcon()
local ret = {}
for i_, obj in ipairs(self.statuses) do
ret[#ret + 1] = obj:exportNoticeBarIcon()
end
Line 299 ⟶ 426:
function MultiStatus:getWarnings()
local ret = {}
for i_, obj in ipairs(self.statuses) do
for j_, msg in ipairs(obj:getWarnings()) do
ret[#ret + 1] = msg
end
Line 319 ⟶ 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 411 ⟶ 552:
obj:addWarning(
obj:message(
'action-errorwarning-invalid-date',
data.date,
obj:getParameter('date')
),
obj:message('action-errorwarning-invalid-date-help')
)
end
Line 421 ⟶ 562:
obj:addWarning(
obj:message(
'action-errorwarning-no-date',
obj.paramNum,
obj:getParameter('date'),
obj:getParameter('code')
),
obj:message('action-errorwarning-no-date-help')
)
end
Line 433 ⟶ 575:
obj.oldid = tonumber(data.oldid)
if data.oldid and (not obj.oldid or not isPositiveInteger(obj.oldid)) then
obj:raiseError(.oldid = nil
obj:addWarning(
obj:message(
'action-errorwarning-invalid-oldid',
data.oldid,
obj:getParameter('oldid')
),
obj:message('action-errorwarning-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 476 ⟶ 629:
 
function Action:exportHtml(articleHistoryObj)
if self._html then
return self._html
end
 
local row = mw.html.create('tr')
 
Line 500 ⟶ 657:
self:getName(articleHistoryObj)
))
 
-- Result cell
row
Line 506 ⟶ 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 534 ⟶ 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 553 ⟶ 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 586 ⟶ 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
Line 599 ⟶ 884:
return val
else
table.insert(self.errors_errors, val)
return nil
end
Line 609 ⟶ 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 659 ⟶ 933:
end
local statuses = self.cfg.statuses
codelocal codeUpper = mw.ustring.upper(code)
if statuses[codecodeUpper] then
return statuses[codecodeUpper].id
else
self:addWarning(
self:message('articlehistory-warning-invalid-status', code),
self:message('articlehistory-warning-invalid-status-help')
)
return nil
end
end
Line 669 ⟶ 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 690 ⟶ 979:
id = statusId,
currentTitle = self.currentTitle,
cfg = self.cfg,
isSmall = self.isSmall
}
local isMulti = self.cfg.statuses[statusId].isMulti
local initFunc = isMulti and MultiStatus.new or Status.new
local statusObj = self:try(initFunc, statusObjData) or false
self.statusObj = statusObj or false
return self.statusObj or nil
end
 
Line 705 ⟶ 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',
local ret = {}
'collapsibleNotices',
return ret
CollapsibleNotice
)
end
 
function ArticleHistory:allObjectsgetAllObjects(addSelf)
local cacheKey = addSelf and 'addSelf' or 'default'
-- An iterator for all child objects. If addSelf is true, the current
local ret = self._allObjectsCache[cacheKey]
-- ArticleHistory object is iterated over as well. It works like ipairs;
if not ret then
-- it returns the current array key
local t ret = {}
local statusObj = self:getStatusObj()
if statusObj then
t ret[#tret + 1] = statusObj
end
local objTables = {
self:getNoticeObjects(),
self:getActionObjects(),
self:getCollapsibleNoticeObjects()
}
for i_, objTablet in ipairs(objTables) do
for j_, obj in ipairs(objTablet) do
t ret[#tret + 1] = obj
end
end
if addSelf then
ret[#ret + 1] = self
end
self._allObjectsCache[cacheKey] = ret
end
return ret
if addSelf then
end
t[#t + 1] = self
 
end
function ArticleHistory:getNoticeBarIcons()
local i = 0
local nret = #t{}
-- Icons that aren't part of a row.
return function ()
if self.cfg.noticeBarIcons then
i = i + 1
for _, data in ipairs(self.cfg.noticeBarIcons) do
if i <= n then
if data.isActive(self) then
return t[i]
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
 
Line 752 ⟶ 1,080:
-- Returns an array of error/warning strings. Error strings come first.
local ret = {}
for _, msg in ipairs(self.errors_errors) do
ret[#ret + 1] = msg
end
for _, obj in ipairs(self:allObjectsgetAllObjects(true)) do
for _, msg in ipairs(obj:getWarnings()) do
ret[#ret + 1] = msg
Line 763 ⟶ 1,091:
end
 
function ArticleHistory:renderBoxcategoriesAreActive()
-- Returns a boolean indicating whether categories should be output or not.
local root = mw.html.create('table')
local title = self.currentTitle
root:addClass('tmbox tmbox-notice')
local ns = title.namespace
if self.isSmall then
return title.isTalkPage
root:addClass('mbox-small')
and ns ~= 3 -- not user talk
else
and ns ~= 119 -- not draft talk
root:css('width', '80%')
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 781 ⟶ 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 797 ⟶ 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 822 ⟶ 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 834 ⟶ 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 857 ⟶ 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.
-- Otherwise, they are added afterwards, so that they align with the
-- 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 = roottableRoot
:tag('tr')
:tag('td')
Line 891 ⟶ 1,304:
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 = {}
-- Child object categories
for obj in self:allObjects() do
local categories = self:try(obj.exportCategories, obj, self)
for _, 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
-- Error category
-- this check earlier as we don't know where the errors may be until we
if #self:getErrorMessages() > 0 then
-- have finished rendering the banner.
ret[#ret + 1] = tostring(Category.new(self:message('error-category')))
elseif #self:getAllObjects() < 1 then
return ''
end
 
-- Add the categories
return table.concat(ret)
root:wikitext(categories)
end
root:wikitext(errorCategory)
 
function ArticleHistory:__tostring()
local frame = mw.getCurrentFrame()
-- XXX: If renderCategories is called after renderBox in this function,
return frame:extensionTag{
-- then category error messages aren't displayed. Need to think of a saner
name = 'templatestyles', args = { src = 'Module:Message box/tmbox.css' }
-- way of doing this.
} .. frame:extensionTag{
local categories = self:renderCategories()
name = 'templatestyles', args = { src = 'Module:Article history/styles.css' }
local box = self:renderBox()
} .. tostring(root)
return box .. categories
end
 
Line 940 ⟶ 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 948 ⟶ 1,356:
Row = Row,
Status = Status,
MultiStatus = MultiStatus,
Notice = Notice,
Action = Action,