Module:Archive: Difference between revisions

Content deleted Content added
fix unneeded arrow
moved sandbox changes live per talk page notes and TfD discussion; this allows the template to better handle annually archived pages
 
(30 intermediate revisions by 9 users not shown)
Line 1:
-------------------------------------------------------------------------------
-- Automatic archive navigator
--
-- This module implementsproduces {{Automatica talk archive navigator}}banner, together with an automatically-
-- generated list of navigation links to other archives of the talk page in
--
-- question. It implements {{Archive}}.
-------------------------------------------------------------------------------
local p = {}
local args
local frame
local thisPage = mw.title.getCurrentTitle()
 
local yesno = require('Module:Yesno')
-- Get the name of the subpage a certain distance away.
 
-- e.g. if the current subpage is /Archive 27, getSubpageName(3) returns 'Archive 30'
-------------------------------------------------------------------------------
local function getSubpageName(offset)
-- Helper functions
local startIdx, endIdx, archiveNum = mw.ustring.find(thisPage.subpageText, '^Archive ([0-9]+)')
-------------------------------------------------------------------------------
if archiveNum then
 
return 'Archive ' .. (archiveNum + offset)
local function makeWikilink(page, display)
else
if display then
return thisPage.subpageText + offset
return string.format('[[%s|%s]]', page, display)
end
else
return string.format('[[%s]]', page)
end
end
 
local function escapePattern(s)
-- Get a formatted link to the subpage a certain distance away, or nil
-- Escape punctuation in a string so it can be used in a Lua pattern.
-- if that subpage does not exist.
s = s:gsub('%p', '%%%0')
local function getSubpageLink(offset)
return s
local subpageName = getSubpageName(offset)
local page = mw.title.new(thisPage.baseText .. '/' .. subpageName, thisPage.namespace)
if page.exists then
return '[[../' .. subpageName .. '|' .. subpageName .. ']]'
else
return nil
end
end
 
local function getLinksTextmakeTable(width)
local archiveTable = mw.html.create('table')
local arrowSpacer = '  '
archiveTable
local linkSpacer = '    '
:css({
if mw.ustring.len(thisPage.subpageText) <= 4 then
['max-width'] = width,
-- If page names are short, we want more space. But why the mix of regular, non-breaking, and em spaces?
['margin'] = '0 auto 0.5em',
local emSpace = frame:expandTemplate({title = 'Unicode', args = {mw.ustring.char(8195)}})
['text-align'] = 'center'
arrowSpacer = '&nbsp; ' .. emSpace .. ' &nbsp;&nbsp;'
})
linkSpacer = '&nbsp; ' .. emSpace .. ' &nbsp; ' .. emSpace .. '&nbsp;'
-- 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
 
-- Check to see if the page is likely an annual archive. No talk pages exist
-- before 2001. Some pages have the next year created in advance.
local function isYearlyArchive(num)
local currentYear = tonumber(os.date("%Y"))
return num and num >= 2001 and num <= currentYear + 1 -- possible years
end
local function detectArchiveFormat(title, args)
-- Check if next/previous are set. Some archives swap between annual and
-- sequential archives at some point and will need to accept whatever an
-- editor says the next/previous link should be for these kind of weird
-- or unsual orders.
if args and (args.prev or args.next) then
return nil, nil, nil
end
-- Check if "/Archive 2" exists to prevent false positives on noticeboards
local s = ''
local archiveBase = title.baseText
local archive2Title = mw.title.new(archiveBase .. "/Archive 2")
for offset = -5, -3 do
if archive2Title and archive2Title.exists then
local link = getSubpageLink(offset)
return nil, nil, nil -- Exit early for sequential archives
if link then
if offset == -3 then
s = s .. link .. linkSpacer
else
s = s .. link .. ' ←' .. arrowSpacer
end
break
end
end
for offset = -2, -1 do
local link = getSubpageLink(offset)
if link then
s = s .. link .. linkSpacer
end
end
 
-- How is the year formatted?
s = s .. '<span style="font-size:115%;">[[' .. thisPage.fullText .. '|' .. getSubpageName(0) .. ']]</span>'
local patterns = {
{pattern = "^(.-)/Archive (%d+)$", prefix = nil}, -- "Talk:Base page/Archive YYYY"
for offset = 1, 2 do
-- "nil" triggers the default. There is some kind of quirk with how
local link = getSubpageLink(offset)
-- the module give spaces that Template:Yearly archive list cannot
if link then
-- read with either sa =space sor ..the linkSpacerHTML ..space linkentity
{pattern = "^(.-)/Archive(%d+)$", prefix = "/Archive"}, -- "Talk:Base page/ArchiveYYYY"
end
{pattern = "^(.-)/Archive/(%d+)$", prefix = "/Archive/"}, -- "Talk:Base page/Archive/YYYY"
end
{pattern = "^(.-)/Archives/(%d+)$", prefix = "/Archives/"}, -- "Talk:Base page/Archives/YYYY"
{pattern = "^(.-)/(%d+)$", prefix = "/"} -- "Talk:Base page/YYYY"
for offset = 5, 3, -1 do
}
local link = getSubpageLink(offset)
 
if link then
for _, p in ipairs(patterns) do
if offset == 3 then
local basePage, archiveNum = title.prefixedText:match(p.pattern)
s = s .. linkSpacer .. link
archiveNum = elsetonumber(archiveNum)
if basePage and isYearlyArchive(archiveNum) then
s = s .. arrowSpacer .. '→ ' .. link
endreturn basePage, true, p.prefix
break
end
end
return nil, nil, nil
end
 
 
return s
 
-------------------------------------------------------------------------------
-- 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&#32;" 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
 
local function getMessageNavigator:message(key, ...)
local msg = self.cfg[key]
if args[1] == '1' then
if select('#', ...) > 0 then
return ''
return mw.message.newRawMessage(msg, ...):plain()
else
else
local msg = '----\n'
return msg
if args.text then
end
msg = msg .. args.text
else
msg = msg .. "This is an '''[[Help:Archiving a talk page|archive]]''' of past discussions"
if args.period then
msg = msg .. "&#32;for the period '''" .. args.period .. "'''"
end
msg = msg .. ". '''Do not edit the contents of this page.''' If you wish to start a new discussion or revive an old one, please do so on the "
msg = msg .. "[[" .. thisPage.baseText .. "|current talk page]]."
end
return msg
end
end
 
local function _aanNavigator:getNamespacePreposition()
-- Most talk archives are about a subject. Some will be "with" an editor
frame = mw.getCurrentFrame()
-- or "on" a noticeboard.
-- Function to get the namespace preposition
-- For testing purposes, allow passing in the page name as a param.
if args.title then
-- Namespace number where transcluded
thisPage = mw.title.new(args.title)
local namespaceNumber = mw.title.getCurrentTitle().namespace
else
-- Preposition from table to make it easy for wikis to translate or ignore
thisPage = mw.title.getCurrentTitle()
local namespacePrepositionTable = self.cfg['namespace-prepositions']
end
 
-- Default preposition if not exception from above
local image = args.image
return namespacePrepositionTable[namespaceNumber] or self.cfg["preposition-default"]
if not image then
end
image = '[[File:' .. (args.icon or 'Replacement filing cabinet.svg') .. '|40x40px|alt=|link=]]'
 
end
function Navigator:makeBlurb()
local mboxargs = frame:expandTemplate({title = 'tmbox', self.args = {
local current = self.currentTitle
image = image,
local ret
imageright = args.imageright,
style = args.style or 'width:80%;margin-left:auto;margin-right:auto;',
-- Skip if user provides their own blurb.
textstyle = args.textstyle or 'text-align:center;',
if args.text then
text = getLinksText() .. '\n' .. getMessage()
ret = args.text
}})
else
-- Set parent talk page.
return mbox .. '__NONEWSECTIONLINK__ __NOEDITSECTION__'
local parentTalkPage = current.basePageTitle
local talkPageTitle
local pageUnderDiscussion
-- If the parent talk page exists (and it's not the root talk page)
-- we should link to it in both the "main talk page" and
-- "discussions about" parts of the blurb.
if args.prefix or (parentTalkPage.exists and parentTalkPage.isRedirect == false and current.baseText ~= current.rootText) then
talkPageTitle = parentTalkPage.fullText
pageUnderDiscussion = talkPageTitle
-- If it doesn't, set "main talk page" to the root talk page
-- and "discussions about" to the root subject.
else
talkPageTitle = current.nsText .. ':' .. parentTalkPage.rootText
-- Set page under discussion.
pageUnderDiscussion = current.subjectNsText .. ':' .. current.rootText
-- Prepend colon for non-mainspace pages.
if current.subjectNsText ~= '' then
pageUnderDiscussion = ':' .. pageUnderDiscussion
end
end
-- Check current namespace for blurb.
local namespace = 'main'
if current.isTalkPage == true then
namespace = 'talk'
end
-- What kind of blurb to use in the message box?
local function getBlurbKey(args)
if args.type == 'index' then
-- For manually-indexed archives only
return 'blurb-index', args.type
elseif args.type == 'annual' then
-- Grab the year of the current archive.
return 'blurb-annual', mw.getCurrentFrame():expandTemplate {
title = 'Title year', args = { page = current.fullText }
}
elseif args.period then
return 'blurb-period', args.period
else
return 'blurb-noperiod', ''
end
end
 
-- Generate a blurb from Module:Archive/config
local blurbKey, argValue = getBlurbKey(args)
local namespacePreposition = self:getNamespacePreposition()
ret = self:message(blurbKey, talkPageTitle, pageUnderDiscussion, argValue, namespace, namespacePreposition)
 
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
 
-- Check for annual archives
local currentTitle = self.currentTitle
local yearlyBase, isYearly, yearlyPattern = detectArchiveFormat(currentTitle, args)
 
if isYearly then
-- Use a yearly archive format
local linksYearlyListSeparator = "&#32;" -- Space separator
local linksYearlyList = mw.getCurrentFrame():expandTemplate {
title = 'Yearly archive list',
args = {
root = yearlyBase,
sep = linksYearlyListSeparator,
prefix = yearlyPattern
}
}
boxComponents = self:makeMessageBox() .. '\n' ..
'<div style="font-size:115%; width:100%; word-spacing:1em; text-align:center;">' .. linksYearlyList .. '</div>'
else
-- Default numbered archive format
boxComponents = self:makeMessageBox() .. '\n' .. self:makeArchiveLinksWikitable()
end -- 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, {
local origArgs
wrappers = 'Template:Archive',
-- If called via #invoke, use the args passed into the invoking template.
})
-- Otherwise, for testing purposes, assume args are being passed directly in.
return p._aan(args)
if frame == mw.getCurrentFrame() then
origArgs = frame:getParent().args
else
origArgs = frame
end
-- ParserFunctions considers the empty string to be false, so to preserve the previous
-- template behavior, change any empty arguments to nil, so Lua will consider
-- them false too.
args = {}
for k, v in pairs(origArgs) do
if v ~= '' then
args[k] = v
end
end
return _aan()
end
 
return p