Module:Archive
Appearance
(Redirected from Module:Automatic archive navigator)
This Lua module is used on approximately 305,000 pages. To avoid major disruption and server load, any changes should be tested in the module's /sandbox or /testcases subpages, or in your own module sandbox. The tested changes can be added to this page in a single edit. Consider discussing changes on the talk page before implementing them. |
This module is subject to page protection. It is a highly visible module in use by a very large number of pages, or is substituted very frequently. Because vandalism or mistakes would affect many pages, and even trivial editing might cause substantial load on the servers, it is protected from editing. |
This module depends on the following other modules: |
This module produces a banner for talk archive pages. The module detects surrounding archives automatically and creates navigational links to them, hence the name.
Usage
This module is accessed via a template, Template:Archive. See the template page for documentation.
Dependencies
This module uses a configuration module at Module:Archive/config. It also uses Module:Highest archive number, Module:Arguments, Module:Yesno, and Module:Message box.
-------------------------------------------------------------------------------
-- Automatic archive navigator
--
-- This module produces a talk archive banner, together with an automatically-
-- generated list of navigation links to other archives of the talk page in
-- question. It implements {{Archive}}.
-------------------------------------------------------------------------------
local yesno = require('Module:Yesno')
-------------------------------------------------------------------------------
-- Helper functions
-------------------------------------------------------------------------------
local function makeWikilink(page, display)
if display then
return string.format('[[%s|%s]]', page, display)
else
return string.format('[[%s]]', page)
end
end
local function escapePattern(s)
-- Escape punctuation in a string so it can be used in a Lua pattern.
s = s:gsub('%p', '%%%0')
return s
end
local function makeTable(width)
local archiveTable = mw.html.create('table')
archiveTable
:css({
['max-width'] = width,
['margin'] = '0 auto 0.5em',
['text-align'] = 'center'
})
-- Set width so that the table doesn't spill out on narrower skins
-- or when zooming in. It has to be defined multiple times because
-- "stretch" is experimental.
:cssText('width:100%;width:-moz-available;width:-webkit-fill-available;width:stretch')
return archiveTable
end
-------------------------------------------------------------------------------
-- Navigator class
-------------------------------------------------------------------------------
local Navigator = {}
Navigator.__index = Navigator
function Navigator.new(args, cfg, currentTitle)
local obj = setmetatable({}, Navigator)
-- Set inputs
obj.args = args
obj.cfg = cfg
obj.currentTitle = currentTitle
-- Archive prefix
-- Decode HTML entities so users can enter things like "Archive " from
-- wikitext.
obj.archivePrefix = obj.args.prefix or obj:message('archive-prefix')
obj.archivePrefix = mw.text.decode(obj.archivePrefix)
-- Current archive number
do
local pattern = string.format(
'^%s([1-9][0-9]*)$',
escapePattern(obj.archivePrefix)
)
obj.currentArchiveNum = obj.currentTitle.subpageText:match(pattern)
obj.currentArchiveNum = tonumber(obj.currentArchiveNum)
end
-- Highest archive number
obj.highestArchiveNum = require('Module:Highest archive number')._main(
obj.currentTitle.nsText ..
':' ..
obj.currentTitle.baseText ..
'/' ..
obj.archivePrefix,
obj.currentArchiveNum
)
return obj
end
function Navigator:message(key, ...)
local msg = self.cfg[key]
if select('#', ...) > 0 then
return mw.message.newRawMessage(msg, ...):plain()
else
return msg
end
end
function Navigator:makeBlurb()
local args = self.args
local current = self.currentTitle
local ret
-- Skip if user provides their own blurb.
if args.text then
ret = args.text
else
-- Set parent talk page.
local talkPage = current.nsText .. ':'
if args.prefix then
talkPage = talkPage .. current.baseText
else
talkPage = talkPage .. current.rootText
end
-- Check current namespace for blurb.
local namespace = 'main'
if current.isTalkPage == true then
namespace = 'talk'
end
-- Most talk pages are "about" an article. For user talk page archives
-- use "with" instead as User:X will also be a participant. User talk
-- archives will be found in both User and User talk namespaces (2,3).
-- For noticeboards and wikiprojects use "on" as the discussions are
-- typically not about the noticeboard itself (4,5).
local namespacePreposition = "about"
if current:inNamespaces(2, 3) == true then
namespacePreposition = "with"
elseif current:inNamespaces(4, 5) == true then
namespacePreposition = "on"
end
-- Set page under discussion.
local subjectPage = current.subjectNsText .. ':' .. current.rootText
-- Prepend colon for non-mainspace pages.
if current.subjectNsText ~= '' then
subjectPage = ':' .. subjectPage
end
subjectPage = makeWikilink(subjectPage)
if args.type == 'index' then
-- For manually-indexed archives only
ret = self:message('blurb-index', talkPage, subjectPage,
args.type, namespace, namespacePreposition)
elseif args.period then
ret = self:message('blurb-period', talkPage, subjectPage,
args.period, namespace, namespacePreposition)
else
ret = self:message('blurb-noperiod', talkPage, subjectPage,
'', namespace, namespacePreposition)
end
end
return ret
end
function Navigator:makeMessageBox()
local args = self.args
local image
if args.image then
image = args.image
else
local icon = args.icon or self:message('default-icon')
image = string.format(
'[[File:%s|%s|alt=|link=]]',
icon,
self:message('image-size')
)
end
-- Hardcode tmbox style on the template's page.
-- PS: Needs to be changed if the template is renamed!
local mainTemplatePage = ''
if self.currentTitle.fullText == 'Template:Archive' then
mainTemplatePage = 'talk'
end
local mbox = require('Module:Message box').main('mbox', {
demospace = args.demospace or mainTemplatePage,
image = image,
imageright = args.imageright,
style = args.style or '',
textstyle = args.textstyle or 'text-align:center',
text = self:makeBlurb(),
})
return mbox
end
function Navigator:getArchiveNums()
-- Returns an array of the archive numbers to format.
local noLinks = tonumber(self.args.links) or self:message('default-link-count')
noLinks = math.floor(noLinks)
-- If |noredlinks is "yes", true or absent, don't allow red links. If it is
-- 'no' or false, allow red links.
local allowRedLinks = yesno(self.args.noredlinks) == false
local current = self.currentArchiveNum
local highest = self.highestArchiveNum
if not current or not highest or noLinks < 1 then
return {}
elseif noLinks == 1 then
return {current}
end
local function getNum(i, current)
-- Gets an archive number given i, the position in the array away from
-- the current archive, and the current archive number. The first two
-- offsets are consecutive; the third offset is rounded up to the
-- nearest 5; and the fourth and subsequent offsets are rounded up to
-- the nearest 10. The offsets are calculated in such a way that archive
-- numbers will not be duplicated.
if -2 <= i and i <= 2 then
return current + i
elseif -3 <= i and i <= 3 then
return current + 2 - (current + 2) % 5 + (i / 3) * 5
elseif 4 <= i then
return current + 7 - (current + 7) % 10 + (i - 3) * 10
else
return current + 2 - (current + 2) % 10 + (i + 3) * 10
end
end
local nums = {}
-- Archive nums lower than the current page.
for i = -1, -math.floor((noLinks - 1) / 2), -1 do
local num = getNum(i, current)
if num <= 1 then
table.insert(nums, 1, 1)
break
else
table.insert(nums, 1, num)
end
end
-- Current page.
if nums[#nums] < current then
table.insert(nums, current)
end
-- Higher archive nums.
for i = 1, math.ceil((noLinks - 1) / 2) do
local num = getNum(i, current)
if num <= highest then
table.insert(nums, num)
elseif allowRedLinks and (i <= 2 or i <= 3 and num == nums[#nums] + 1) then
-- Only insert one red link, and only if it is consecutive.
table.insert(nums, highest + 1)
break
elseif nums[#nums] < highest then
-- Insert the highest archive number if it isn't already there.
table.insert(nums, highest)
break
else
break
end
end
return nums
end
function Navigator:makeArchiveLinksWikitable()
local args = self.args
local lang = mw.language.getContentLanguage()
local nums = self:getArchiveNums()
local noLinks = #nums
-- Skip number processing if |prev and |next are defined.
if args.prev or args.next then
local archives = {}
if args.prev then archives[#archives + 1] = mw.title.new(args.prev) end
archives[#archives + 1] = self.currentTitle
if args.next then archives[#archives + 1] = mw.title.new(args.next) end
local table = makeTable('30em')
for _, title in ipairs(archives) do
if tostring(title) == self.currentTitle.prefixedText then
table:tag("td"):wikitext(string.format(
'<span style="font-size:115%%;">%s</span>',
makeWikilink(title.fullText, title.subpageText)
))
else
table:tag("td"):wikitext(
makeWikilink(title.fullText, title.subpageText)
)
end
end
return tostring(table)
end
if noLinks < 1 then
return ''
end
-- Make the table of links.
local links = {}
local isCompact = noLinks > 7
local currentIndex
for i, num in ipairs(nums) do
local subpage = self.archivePrefix .. tostring(num)
local display
if isCompact then
display = tostring(num)
else
display = self:message('archive-link-display', num)
end
local link = makeWikilink('../' .. subpage, display)
if num == self.currentArchiveNum then
link = string.format('<span style="font-size:115%%;">%s</span>', link)
currentIndex = i
end
table.insert(links, link)
end
-- Add the arrows.
-- We must do the forwards arrow first as we are adding elements to the
-- links table. If we did the backwards arrow first the index for the
-- current archive would be wrong.
currentIndex = currentIndex or math.ceil(#links / 2)
for i = currentIndex + 1, #links do
if nums[i] - nums[i - 1] > 1 then
table.insert(links, i, lang:getArrow('forwards'))
break
end
end
for i = currentIndex - 1, 1, -1 do
if nums[i + 1] - nums[i] > 1 then
table.insert(links, i + 1, lang:getArrow('backwards'))
break
end
end
-- Output the wikitable.
local width
if noLinks <= 3 then
width = string.format('%dem', noLinks * 10)
elseif noLinks <= 7 then
width = string.format('%dem', (noLinks + 3) * 5)
else
width = '37em'
end
local table = makeTable(width)
for _, s in ipairs(links) do
table:tag("td"):wikitext(s)
end
return tostring(table)
end
function Navigator:__tostring()
local args = self.args
local boxComponents
-- Is |omit filled? If not, make the whole box.
if args.omit == nil then
boxComponents = self:makeMessageBox() .. '\n' .. self:makeArchiveLinksWikitable()
-- We're omitting the banner, so we should only make the links table.
elseif args.omit == 'banner' then
boxComponents = self:makeArchiveLinksWikitable()
-- We're omitting the archives, so we should only make the banner.
elseif args.omit == 'archives' then
boxComponents = self:makeMessageBox()
end
-- Allow for demo pages to be edited freely.
if not args.demospace then
boxComponents = boxComponents .. ' __NONEWSECTIONLINK__ __NOEDITSECTION__ __ARCHIVEDTALK__'
end
return boxComponents
end
-------------------------------------------------------------------------------
-- Exports
-------------------------------------------------------------------------------
local p = {}
function p._exportClasses()
return {
Navigator = Navigator
}
end
function p._aan(args, cfg, currentTitle)
cfg = cfg or mw.loadData('Module:Archive/config')
currentTitle = currentTitle or mw.title.getCurrentTitle()
local aan = Navigator.new(args, cfg, currentTitle)
return tostring(aan)
end
function p.aan(frame)
local args = require('Module:Arguments').getArgs(frame, {
wrappers = 'Template:Archive',
})
return p._aan(args)
end
return p