Edge Rewrite
// HTMLRewriter · presentation

This page was redesigned at the edge.

Cloudflare fetched the original article and streamed it through HTMLRewriter to apply an entirely new visual system without rebuilding the source page.

Jump to content

Module:Arbcom election banner

Permanently protected module
From Wikipedia, the free encyclopedia

local messageBox = require('Module:Message box')
local navbarModule = require('Module:Navbar')
local horizontal = require('Module:List').horizontal

local p = {}

-- Get constants.
local lang = mw.language.getContentLanguage()
local currentUnixDate = tonumber(lang:formatDate('U'))

local function err(msg)
	return mw.ustring.format('<span class="ace-banner-error">%s</span>', msg)
end

local function getUnixDate(date)
	local success, unixDate = pcall(lang.formatDate, lang, 'U', date)
	if success then
		return tonumber(unixDate)
	end
end

local function unixDateError(date)
	return err(tostring(date) .. ' is not a valid date.')
end

local function randomizeArray(t)
	-- Iterate through the array backwards, each time swapping the entry "i" with a random entry.
	-- Courtesy of Xinhuan at http://forums.wowace.com/showthread.php?p=279756
	math.randomseed(mw.site.stats.edits)
	for i = #t, 2, -1 do
		local r = math.random(i)
		t[i], t[r] = t[r], t[i]
	end
	return t
end

local function getArgNums(args, prefix)
	-- Returns a table containing the numbers of the arguments that exist for the specified prefix. For example, if the prefix
	-- was 'data', and 'data1', 'data2', and 'data5' exist, it would return {1, 2, 5}.
	local nums = {}
	for k, v in pairs(args) do
		k = tostring(k)
		local num = mw.ustring.match(k, '^' .. prefix .. '([1-9]%d*)$')
		if num then
			table.insert(nums, tonumber(num))
		end
	end
	table.sort(nums)
	return nums
end

local function showBeforeDate(datePairs)
	-- Shows a value if it is before a given date.
	for i, datePair in ipairs(datePairs) do
		local date = datePair.date
		local val = datePair.val
		if not date then -- No date specified, so assume we have no more dates to process.
			return val
		end
		local unixDate = getUnixDate(date)
		if not unixDate then return unixDateError(date) end
		if currentUnixDate < unixDate then -- The specified date is in the future.
			return val
		end
	end
end

local function countdown(date, event)
	if type(event) ~= 'string' then return err('No event name provided.') end
	-- Get the current date unix timestamp.
	local unixDate = getUnixDate(date)
	if not unixDate then return unixDateError(date) end
	unixDate = tonumber(unixDate)
	-- Subtract the timestamp from the current unix timestamp to find the time left, and output that in a readable way.
	local secondsLeft = unixDate - currentUnixDate
	if secondsLeft <= 0 then return end
	local timeLeft = lang:formatDuration(secondsLeft, {'weeks', 'days', 'hours'})
	-- Find whether we are plural or not.
	local isOrAre
	if mw.ustring.match(timeLeft, '^%d+') == '1' then
		isOrAre = 'is'
	else
		isOrAre = 'are'
	end
	local timeLeft = mw.ustring.gsub(timeLeft, '(%d+)', '<span class="ace-banner-timeleft">%1</span>')
	-- Make the refresh link, and join it all together.
	local refreshLink = mw.title.getCurrentTitle():fullUrl{action = 'purge'}
	refreshLink = mw.ustring.format('<span class="ace-banner-refresh plainlinks">([%s refresh])</span>', refreshLink)
	return mw.ustring.format('There %s %s until %s. %s', isOrAre, timeLeft, event, refreshLink)
end

local function format_guides(args, year)
	local guideNums = getArgNums(args, 'guide')
	local guides = {}
	for _, num in ipairs(guideNums) do
		table.insert(guides, args['guide' .. tostring(num)])
	end
	local guide_text = mw.ustring.format(
		'\nThese [[:Category:Wikipedia Arbitration Committee Elections %s voter guides|guides]] '
		.. 'represent the thoughts of their authors. All individually written '
		.. 'voter guides are eligible for inclusion.\n',
		year
	)
	guides = randomizeArray(guides)
	guides = horizontal(guides)
	
	local ret = [=[
<div class="mw-collapsible mw-collapsed">
<div class="ace-banner-guides-title"><div>Personal voter guides</div></div>
<div class="mw-collapsible-content">%s%s</div>
</div>]=]
	return mw.ustring.format(ret, guide_text, guides)
end

local function getElectionInfo(year, key)
	local frame = mw.getCurrentFrame()
	local result = frame:expandTemplate{title = 'Arbitration Committee candidate/data', args = {year, key}}
	if result ~= '' then
		return result
	else
		return nil
	end
end

local function getStartDate(year, key)
	local date = getElectionInfo(year, key)
	if not date then
		return nil
	else
		return '00:00, ' .. lang:formatDate('d F Y', date)
	end
end

local function getEndDate(year, key)
	local date = getElectionInfo(year, key)
	if not date then
		return nil
	else
		return '23:59, ' .. lang:formatDate('d F Y', date .. ' -1 day')
	end
end

local function getVotePage(year)
	local pollId = getElectionInfo(year, 'poll')
	if pollId then
		return string.format('[https://en.wikipedia.org/wiki/Special:SecurePoll/vote/%s Vote]', pollId)
	else
		return '<span class="ace-banner-placeholder">Vote</span>'
	end
end

local function getVoteLog(year)
	local sourceKey
	local locationString
	local yearNum = tonumber(year)
	
	if yearNum >= 2025 then
		sourceKey = 'poll'
		locationString = '[https://en.wikipedia.org/wiki/Special:SecurePoll/list/%s Voter log]'
	else
		sourceKey = 'votewikiid'
		locationString = '[https://vote.wikimedia.org/wiki/Special:SecurePoll/list/%s Voter log]'
	end
	local voteWikiId = getElectionInfo(year, sourceKey)
	if voteWikiId then
		return string.format(locationString, voteWikiId)
	else
		return '<span class="ace-banner-placeholder">Voter log</span>'
	end
end

function p._main(args)
	-- Get data for the box, plus the box title.
	local year = args.year or lang:formatDate('Y')
	local name = args.name or 'ACE' .. year
	local navbar = navbarModule.navbar{name, mini = '1'}
	local electionpage = args.electionpage or mw.ustring.format(
		'[[Wikipedia:Arbitration Committee Elections December %s|%s Arbitration Committee Elections]]',
		year, year
	)
	-- Get nomination or voting link, depending on the date.
	local beforenomlink = args.beforenomlink or mw.ustring.format('[[Wikipedia:Requests for comment/Arbitration Committee Elections December %s/Electoral Commission|Electoral Commission RFC]]', year)
	local nomstart = args.nomstart or getStartDate(year, 'nombegin') or error('No nomstart date supplied')
	local nomlink = args.nomlink or mw.ustring.format('[[Wikipedia:Arbitration Committee Elections December %s/Candidates|Nominate]]', year)
	local nomend = args.nomend or getEndDate(year, 'nomend') or error('No nomend date supplied')
	local votestart = args.votestart or getStartDate(year, 'begin') or error('No votestart date supplied')
	local votepage = args.votepage or getVotePage(year)
	local votelink = args.votelink or mw.ustring.format('<span class="ace-banner-votelink">%s</span>', votepage)
	local votelog = args.votelog or getVoteLog(year)
	local voteend = args.voteend or getEndDate(year, 'end') or error('No voteend date supplied')
	local voteendlink = args.voteendlink or votelog
	local scheduleText = showBeforeDate{
		{val = beforenomlink, date = nomstart},
		{val = nomlink, date = nomend},
		{val = countdown(votestart, 'voting begins'), date = votestart},
		{val = votelink, date = voteend},
		{val = voteendlink}
	}
	-- support votelog as its own element. must be done after we have scheduleText
	if scheduleText ~= votelink then
		votelog = nil
	end
	
	-- Get other links.
	local contact = args.contact or mw.ustring.format('[[Wikipedia talk:Arbitration Committee Elections December %s/Coordination|Contact the coordinators]]', year)
	local discuss = args.discuss or mw.ustring.format('[[Wikipedia talk:Arbitration Committee Elections December %s|Discuss the elections]]', year)
	local cguide = args.cguide or mw.ustring.format('[[Wikipedia:Arbitration Committee Elections December %s/Candidates/Guide|Candidate guide]]', year)
	local cstatements = args.cstatements or mw.ustring.format('[[Wikipedia:Arbitration Committee Elections December %s/Candidates|Candidate statements]]', year)
	local cquestions = args.cquestions or mw.ustring.format('[[Wikipedia:Arbitration Committee Elections December %s/Questions|Questions for the candidates]]', year)
	local cdiscuss = args.cdiscuss or mw.ustring.format('[[Wikipedia:Arbitration Committee Elections December %s/Candidates/Discussion|Discuss the candidates]]', year)
	local guidecat = args.guidecat or mw.ustring.format('')
	local guides = format_guides(args, year)
	
	-- Get the text field of ombox.
	local lead_links = horizontal({
		class = 'ace-banner-lead-links',
		'<span class="ace-banner-mainpage">' .. electionpage .. '</span>',
		scheduleText,
		votelog,
		contact,
		discuss,
		'[[Wikipedia:5-minute guide to ArbCom elections|Quick guide]]'
	})

	local candidate_links = horizontal({
		class = 'ace-banner-candidates-links inline',
		cguide,
		cstatements,
		cquestions,
		cdiscuss,
	})

	return messageBox.main('ombox', {
		image = args.image or '[[File:Judges cupola.svg|50px|ArbCom|link=]]',
		style = args.style or nil,
		text = mw.ustring.format(
			'<div class="ace-banner-navbar">%s</div>%s<span class="ace-banner-candidates">Candidates: </span>%s%s',
			navbar,
			lead_links,
			candidate_links,
			guides
		),
		templatestyles = 'Module:Arbcom election banner/styles.css',
		class = 'ace-banner'
	})
end
 
function p.main(frame)
	-- If called via #invoke, use the args passed into the invoking template, or the args passed to #invoke if any exist.
	-- Otherwise assume args are being passed directly in from the debug console or from another Lua module.
	local origArgs
	if frame == mw.getCurrentFrame() then
		origArgs = frame:getParent().args
		for k, v in pairs(frame.args) do
			origArgs = frame.args
			break
		end
	else
		origArgs = frame
	end
	-- Trim whitespace and remove blank arguments.
	local args = {}
	for k, v in pairs(origArgs) do
		if type(v) == 'string' then
			v = mw.text.trim(v)
		end
		if v ~= '' then
			args[k] = v
		end
	end
	return p._main(args)
end
 
return p