Module:Article history: Difference between revisions

Content deleted Content added
add a warning if a current status was specified without any actions
per edit request on talk page - remove line as unnecessary
 
(37 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 127:
obj.cfg = data.cfg
obj.currentTitle = data.currentTitle
obj.isSmall = data.isSmall
obj.makeData = data.makeData -- used by Row:getData
return obj
end
 
function Row:getData_cachedTry(articleHistoryObjcacheKey, errorCacheKey, func)
-- This method is for use in Row object methods that are called more than
if self.data == false then
-- 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
elseif self.data then
return self.data
end
local ret = self[cacheKey]
if not self.makeData then
if ret then
self.data = false
return ret
elseif ret == false then
return nil
end
local success
local data = self.makeData(articleHistoryObj)
if DEBUG_MODE then
self.data = data or false
success = true
return data
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, smallSize)
self.icon = icon
self.iconCaption = caption
self.iconSize = size
self.iconSmallSize = smallSize
end
 
Line 163 ⟶ 196:
 
function Row:getIconSize()
return self.iconSize or self.cfg.defaultIconSize or '30px'
if self.isSmall then
return self.iconSmallSize or self.cfg.defaultSmallIconSize or '15px'
else
return self.iconSize or self.cfg.defaultIconSize or '30px'
end
end
 
Line 239 ⟶ 268:
 
function Row:exportHtml(articleHistoryObj)
if self._html then
return self._html
end
local text = self:getText(articleHistoryObj)
if not text then
Line 252 ⟶ 284:
:addClass('mbox-text')
:wikitext(text)
self._html = html
return html
end
Line 308 ⟶ 341:
 
function Status:getIconSize()
ifreturn self.isSmall theniconSize
returnor self.statusCfg.smallIconSizeiconSize
or self.cfg.defaultSmallStatusIconSizedefaultStatusIconSize
or '30px50px'
else
return self.iconSize
or self.statusCfg.iconSize
or self.cfg.defaultStatusIconSize
or '50px'
end
end
 
function Status:getText(articleHistoryObj)
local text = Row.getText(self, articleHistoryObj)
if text then
return substituteParams(
return substituteParams(
text,
text,
self.currentTitle.subjectPageTitle.prefixedText,
self.currentTitle.textsubjectPageTitle.prefixedText,
self.currentTitle.text
)
)
end
end
 
Line 342 ⟶ 371:
setmetatable(obj, MultiStatus)
 
obj.id = data.id
obj.statusCfg = obj.cfg.statuses[data.id]
obj.name = obj.statusCfg.name
Line 356 ⟶ 386:
end
obj.statuses = {}
local defaultIconSize = obj.cfg.defaultSmallStatusIconSizedefaultMultiStatusIconSize or '30px'
for i_, id in ipairs(obj.statusCfg.statuses) do
table.insert(obj.statuses, Status.new(getChildStatusData(
data,
Line 370 ⟶ 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 378 ⟶ 408:
function MultiStatus:getCategories(articleHistoryObj)
local ret = {}
for i_, obj in ipairs(self.statuses) do
for j_, categoryObj in ipairs(obj:getCategories(articleHistoryObj)) do
ret[#ret + 1] = categoryObj
end
Line 388 ⟶ 418:
function MultiStatus:exportNoticeBarIcon()
local ret = {}
for i_, obj in ipairs(self.statuses) do
ret[#ret + 1] = obj:exportNoticeBarIcon()
end
Line 396 ⟶ 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 420 ⟶ 450:
data.icon,
data.iconCaption,
data.iconSize,
data.iconSmallSize
)
obj:setNoticeBarIconValues(
Line 523 ⟶ 552:
obj:addWarning(
obj:message(
'action-errorwarning-invalid-date',
data.date,
obj:getParameter('date')
),
obj:message('action-errorwarning-invalid-date-help')
)
end
Line 533 ⟶ 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 546 ⟶ 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
Line 599 ⟶ 629:
 
function Action:exportHtml(articleHistoryObj)
if self._html then
return self._html
end
 
local row = mw.html.create('tr')
 
Line 623 ⟶ 657:
self:getName(articleHistoryObj)
))
 
-- Result cell
row
Line 629 ⟶ 663:
:wikitext(self:getResult(articleHistoryObj))
 
self._html = row
return row
end
Line 648 ⟶ 683:
data.icon,
data.iconCaption,
data.iconSize,
data.iconSmallSize
)
obj:setNoticeBarIconValues(
Line 672 ⟶ 706:
 
function CollapsibleNotice:getIconSize()
ifreturn self.isSmall theniconSize
or self.cfg.defaultCollapsibleNoticeIconSize
return self.iconSmallSize
or '20px'
or self.cfg.defaultSmallCollapsibleNoticeIconSize
or '15px'
else
return self.iconSize
or self.cfg.defaultCollapsibleNoticeIconSize
or '20px'
end
end
 
function CollapsibleNotice:exportHtml(articleHistoryObj, isInCollapsibleTable)
local textcacheKey = self:getText(articleHistoryObj)isInCollapsibleTable
and '_htmlCacheCollapsible'
if not text then
or '_htmlCacheDefault'
return nil
return self:_cachedTry(cacheKey, '_isHtmlError', function ()
end
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
-- inside the cell with two rows, a header row with one cell and a collapsed
-- collapsed row with one cell. These are filled with text and collapsedText,
-- collapsedText, respectively. If no collapsible text is
-- specified, the text is added to the cell as-is.
if collapsibleText then
-- to the cell as-is.
cell
if collapsibleText then
:tag('div')
cell
:addClass('mw-collapsible mw-collapsed')
:tag('table')
:addClasstag('collapsible collapseddiv')
:css('margin', 0)
:css('padding', 0)
:css('border-collapse', 'collapse')
:css('width', '100%')
:css('background', 'transparent')
:tag('tr')
:tag('th')
:css('font-weight', 'normal')
:css('text-align', 'left')
:css('width', '100%')
:wikitext(text)
:done()
:donetag('div')
:tagaddClass('trmw-collapsible-content')
:tag('td')
:css('border', '1px silver solid')
:wikitext(collapsibleText)
else
cell:wikitext(text)
end
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
rowTextlocal textCell = texthtml
:tag('td')
:addClass('mbox-image')
:wikitext(icon)
:done()
:tag('td')
:addClass('mbox-text')
maybeMakeCollapsibleTable(textCell, text, collapsibleText)
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 764 ⟶ 787:
obj.args = args or {}
obj.currentTitle = currentTitle or mw.title.getCurrentTitle()
 
-- Set isSmall
obj.isSmall = yesno(obj.args.small) or false
 
-- Define object structure.
obj._errors = {}
obj._allObjectsCache = {}
 
-- Format the config
local function substituteAliases(t, ret)
Line 888 ⟶ 908:
local actions = {}
local suffixes = self.cfg.actionParamSuffixes
for i_, t in ipairs(actionArgs) do
local objArgs = {}
for k, v in pairs(t) do
Line 913 ⟶ 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 930 ⟶ 954:
local statusId
if self.cfg.getStatusIdFunction then
statusId = self:try(self.cfg.getStatusIdFunction(, self)
else
statusId = self:try(
Line 946 ⟶ 970:
if #actions < 1 then
self:addWarning(
self:message('articlehistory-warning-status-no-actions'),
self:message('articlehistory-warning-status-no-actions-help')
)
end
Line 955 ⟶ 979:
id = statusId,
currentTitle = self.currentTitle,
cfg = self.cfg,
isSmall = self.isSmall
}
local isMulti = self.cfg.statuses[statusId].isMulti
Line 977 ⟶ 1,000:
end
local ret = {}
for i_, t in ipairs(self.cfg[configKey] or {}) do
if t.isActive(self) then
local data = {}
Line 987 ⟶ 1,010:
data.cfg = self.cfg
data.currentTitle = self.currentTitle
data.isSmall = self.isSmall
ret[#ret + 1] = class.new(data)
end
Line 1,021 ⟶ 1,043:
self:getCollapsibleNoticeObjects()
}
for i_, t in ipairs(objTables) do
for j_, obj in ipairs(t) do
ret[#ret + 1] = obj
end
Line 1,061 ⟶ 1,083:
ret[#ret + 1] = msg
end
for i_, obj in ipairs(self:getAllObjects(true)) do
for j_, msg in ipairs(obj:getWarnings()) do
ret[#ret + 1] = msg
end
Line 1,069 ⟶ 1,091:
end
 
function ArticleHistory:renderHtmlcategoriesAreActive()
-- Returns a boolean indicating whether categories should be output or not.
if #self:getAllObjects() < 1 then
local title = self.currentTitle
return ''
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 roottableRoot = mw.html.createroot:tag('table')
roottableRoot:addClass('article-history tmbox tmbox-notice')
 
if self.isSmall then
root:addClass('mbox-small')
else
root:css('width', '80%')
end
-- Status
local statusObj = self:getStatusObj()
if statusObj then
roottableRoot:node(self:try(statusObj.exportHtml, statusObj, self))
end
 
Line 1,092 ⟶ 1,140:
local notices = self:getNoticeObjects()
for _, noticeObj in ipairs(notices) do
roottableRoot:node(self:try(noticeObj.exportHtml, noticeObj, self))
end
 
Line 1,114 ⟶ 1,162:
)
end
local noCollapsibleRowsnActionRows = #actionHtmlObjects + #collapsibleNoticeHtmlObjects
local nCollapsibleRows = nActionRows + #collapsibleNoticeHtmlObjects
 
-- Find out if we are collapsed or not.
local isCollapsed = yesno(self.args.collapse)
if self.cfg.uncollapsedRowsisCollapsed == 'all'nil then
if self.cfg.uncollapsedRows == 'all' then
isCollapsed = false
isCollapsed = false
elseif noCollapsibleRows == 1 then
elseif nCollapsibleRows == 1 then
isCollapsed = false
isCollapsed = false
else
else
isCollapsed = noCollapsibleRows > (tonumber(self.cfg.uncollapsedRows) or 3)
isCollapsed = nCollapsibleRows > (tonumber(self.cfg.uncollapsedRows) or 3)
end
end
 
Line 1,141 ⟶ 1,192:
-- notices are only included in the table if it is collapsed. Action rows
-- are always included.
local collapsibleTable
if isCollapsed or #actionHtmlObjects > 0 then
if isCollapsed or nActionRows > 0 then
-- Collapsible table base
local collapsibleTable = roottableRoot
:tag('tr')
:tag('td')
Line 1,149 ⟶ 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%')
 
-- Header row
if noCollapsibleRows > 1 then
local ctHeader = collapsibleTable
-- Header row
:tag('tr')
local ctHeader = collapsibleTable
:tag('trth')
:tagattr('thcolspan', 3)
:attrcss('colspanfont-size', 3'110%')
:css('font-size', '110%')
 
-- Notice bar
if isCollapsed then
local noticeBarIcons = self:getNoticeBarIcons()
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
ctHeader:wikitext(' ')
end
end
 
-- Header text
if mw.site.namespaces[self.currentTitle.namespace].subject.id == 0 then
ctHeader:wikitext(self:message('milestones-header'))
else
ctHeader:wikitext(self:message(
'milestones-header-other-ns',
self.currentTitle.subjectNsText
))
end
 
-- Subheadings
if #actionHtmlObjectsnActionRows > 0 then
collapsibleTable
: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
end
 
Line 1,205 ⟶ 1,254:
collapsibleTable:node(htmlObj)
end
end
 
-- Collapsible notices
-- Collapsible notices and current status
-- These are only included in the table if it is collapsed. Otherwise,
-- theyThese are addedonly afterwards,included soin thatthe theycollapsible cantable alignif withit theis statuscollapsed.
-- Otherwise, they are added afterwards, so that they align with the
-- and notices.
-- notices.
do
local tableNode, statusColspan
if isCollapsed then
tableNode = collapsibleTable
for _, obj in ipairs(collapsibleNotices) do
statusColspan = 3
collapsibleTable:node(self:try(obj.exportHtml, obj, self, true))
endelse
tableNode = tableRoot
statusColspan = 2
end
 
-- CurrentCollapsible status.notices
for _, obj in ipairs(collapsibleNotices) do
if isCollapsed and noCollapsibleRows > 1 then
tableNode:node(self:try(obj.exportHtml, obj, self, isCollapsed))
local statusText
end
if statusObj then
 
statusText = statusObj.name
-- Current status
end
if statusObj and nActionRows > 1 then
statusText = statusText or self:message('status-unknown')
tableNode
collapsibleTable
:tag('tr')
:tag('td')
:attr('colspan', 3statusColspan)
:wikitext(self:message('status-blurb', statusTextstatusObj.name))
end
end
 
-- Get the categories. We have to do this before the error row, so that
-- Add the collapsible notices if we are not collapsed.
-- category errors display.
if not isCollapsed then
local categories = self:renderCategories()
for _, obj in ipairs(collapsibleNotices) do
root:node(self:try(obj.exportHtml, obj, self, false))
end
end
 
-- Error row and error category
local errors = self:getErrorMessages()
local errorCategory
if #errors > 0 then
local errorList = roottableRoot
:tag('tr')
:tag('td')
Line 1,252 ⟶ 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 i, obj in ipairs(self:getAllObjects()) do
local categories = self:try(obj.getCategories, obj, self)
for j, 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
-- Extra categories
-- this check earlier as we don't know where the errors may be until we
for i, func in ipairs(self.cfg.extraCategories or {}) do
-- have finished rendering the banner.
local cats = func(self) or {}
elseif #self:getAllObjects() < 1 then
for i, categoryObj in ipairs(cats) do
return ''
ret[#ret + 1] = tostring(categoryObj)
end
end
 
-- ErrorAdd categorythe categories
root:wikitext(categories)
if #self:getErrorMessages() > 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)
-- XXX: If renderCategories is called after renderHtml in this function,
-- then category error messages aren't displayed. Need to think of a saner
-- way of doing this.
local categories = self:renderCategories()
local box = self:renderHtml()
return box .. categories
end
 
Line 1,309 ⟶ 1,345:
wrappers = WRAPPER_TEMPLATE
})
if frame:getTitle():find('sandbox', 1, true) then
CONFIG_PAGE = CONFIG_PAGE .. '/sandbox'
end
return p._main(args)
end