Module:Automatic archive navigator
| Protected | 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 produces a banner for talk archive pages. The module detects surrounding archives automatically and creates navigational links to them, hence the name.
Usage edit source
This module is accessed via a template, Template:Automatic archive navigator. See the template page for documentation.
Dependencies edit source
This module uses a configuration module at Module:Automatic archive navigator/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 {{Automatic archive navigator}} and
-- {{Talk archive navigation}}.
-------------------------------------------------------------------------------
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
-------------------------------------------------------------------------------
-- 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
	)
	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
	if args[1] == '1' then
		-- The old template used "|1" to suppress the blurb.
		return ''
	else
		local ret
		if args.text then
			ret = args.text
		else
			local talkPage = self.currentTitle.nsText ..
				':' ..
				self.currentTitle.baseText
			if args.period then
				ret = self:message('blurb-period', talkPage, args.period)
			else
				ret = self:message('blurb-noperiod', talkPage)
			end
		end
		return ret
	end
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
	local mbox = require('Module:Message box').main('tmbox', {
		image = image,
		imageright = args.imageright,
		style = args.style or 'width:80%;margin-left:auto;margin-right:auto',
		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 lang = mw.language.getContentLanguage()
	local nums = self:getArchiveNums()
	local noLinks = #nums
	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 ret = {}
	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 = '50em'
	end
	ret[#ret + 1] = string.format(
		'{| style="width:%s;background:transparent;' ..
			'margin:0 auto 0.5em;text-align:center"',
		width
	)
	for i, s in ipairs(links) do
		if i % 20 == 1 then
			ret[#ret + 1] = '\n|-'
		end
		ret[#ret + 1] = '\n| '
		ret[#ret + 1] = s
	end
	ret[#ret + 1] = '\n|}'
	return table.concat(ret)
end
function Navigator:__tostring()
	return self:makeMessageBox() ..
		'\n' .. 
		self:makeArchiveLinksWikitable() .. 
		' __NONEWSECTIONLINK__ __NOEDITSECTION__'
end
-------------------------------------------------------------------------------
-- Exports
-------------------------------------------------------------------------------
local p = {}
function p._exportClasses()
	return {
		Navigator = Navigator
	}
end
function p._aan(args, cfg, currentTitle)
	cfg = cfg or mw.loadData('Module:Automatic archive navigator/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:Automatic archive navigator',
	})
	return p._aan(args)
end
return p
