Module:Signpost poll
Appearance
![]() | This module depends on the following other modules: |
This module implements Wikipedia:Wikipedia Signpost/Templates/Voter.
-- This module implments polls used in articles of the Signpost.
local CONFIG_MODULE = 'Module:Signpost poll/config'
local yesno = require('Module:Yesno')
local mStringCount = require('Module:String count')
local lang = mw.language.getContentLanguage()
local config = {
wrappers = 'Wikipedia:Wikipedia Signpost/Templates/Voter',
colors = {
'#006699', -- Foundation blue
'#339966', -- Foundation red
'#990000', -- Foundation green
-- From http://stackoverflow.com/questions/470690/how-to-automatically-generate-n-distinct-colors
-- with some colors similar to Foundation colors removed.
'#FFB300',
'#803E75',
'#FF6800',
'#A6BDD7',
'#CEA262',
'#817066',
'#F6768E',
'#FF7A5C',
'#53377A',
'#FF8E00',
'#B32851',
'#F4C800',
'#93AA00',
'#593315',
'#F13A13',
'#232C16',
},
msg = {
-- The default vote preload text.
-- $1 is the option number.
['vote-default'] = 'Voting for option $1.',
-- The text that appears beside each option in the legend.
-- $1 is the option text,
-- $2 is the number of votes, and
-- $3 is the percentage of the total votes.
['legend-option-text'] = '$1 ($3%; $2 votes)',
['not-enough-votes'] = "Need '''$1''' more votes to display results—" ..
"if you haven't already, consider voting!",
['preload-default'] = 'Wikipedia:Wikipedia Signpost/Templates/Voter/Vote preload',
['icon-default'] = 'WikipediaSignpostIcon.svg',
['overlay-default'] = 'Foundation Logo Transparent.svg',
['minimum-default'] = 10,
['header-text'] = "''Signpost'' poll",
},
}
-------------------------------------------------------------------------------
-- Helper functions
-------------------------------------------------------------------------------
local function getUnixDate(date)
date = lang:formatDate('U', date)
return tonumber(date)
end
local function makeUrlLink(url, display)
return string.format('[%s %s]', url, display)
end
-------------------------------------------------------------------------------
-- Poll class
-------------------------------------------------------------------------------
local Poll = {}
Poll.__index = Poll
function Poll.new(args, cfg)
local self = setmetatable({}, Poll)
self.cfg = cfg or config
-- Set required fields
self.question = assert(args.question, 'please specify a question')
self.votepage = assert(args.votepage, 'please specify a vote page')
-- Set optional fields
self.preload = args.preload or self:message('preload-default')
self.headerText = args.header or self:message('header-text')
self.icon = args.icon or self:message('icon-default')
self.overlay = args.overlay or self:message('overlay-default')
self.minimum = tonumber(args.minimum) or self:message('minimum-default')
self.expiry = args.expiry
self.lineBreak = args['break']
-- Set options
self.options = {}
do
local i = 1
while true do
local key = 'option' .. tostring(i)
local option = args[key]
if not option then
break
end
table.insert(self.options, {
option = option,
vote = args[key .. 'vote'] or self:message('vote-default', i),
color = args[key .. 'color'] or self:getDefaultColor(i),
})
i = i + 1
end
if #self.options < 2 then
error('polls must have at least two options')
end
end
-- Count votes
for i, option in ipairs(self.options) do
self.options[i].count = self:countVote(i)
end
-- Find total number of votes
do
local total = 0
for i, option in ipairs(self.options) do
total = total + option.count
end
self.voteTotal = total
end
-- Calculate percentages
for i, option in ipairs(self.options) do
self.options[i].percentage = option.count / self.voteTotal * 100
end
return self
end
function Poll:message(key, ...)
local msg = self.cfg.msg[key]
if select('#', ...) > 0 then
return mw.message.newRawMessage(msg, ...):plain()
else
return msg
end
end
function Poll:getVoteText(n)
return self.options[n].vote
end
function Poll:countVote(n)
return mStringCount._count{
page = self.votepage,
search = self:getVoteText(n)
}
end
function Poll:getDefaultColor(n)
-- Get the default color for option n
local colors = self.cfg.colors
-- colors[#colors] is necessary as Lua arrays are indexed starting at 1,
-- and n % #colors might sometimes equal 0.
return colors[n] or colors[n % #colors] or colors[#colors]
end
function Poll:hasMinimumVoteCount()
return self.voteTotal >= self.minimum
end
function Poll:isOpen()
if self.expiry then
return getUnixDate() < getUnixDate(self.expiry)
else
return true
end
end
function Poll:renderHeader()
local headerDiv = mw.html.create('div')
headerDiv
:css('border-top', '1px solid #CCC')
:css('font-family', 'Georgia, Palatino, Palatino Linotype, Times, Times New Roman, serif')
:css('color', '#333')
:css('padding', '5px 0px')
:css('line-height', '120%')
:wikitext(string.format(
'[[File:%s|right|30px|link=]]',
self.icon
))
:tag('span')
:css('text-transform', 'uppercase')
:css('color', '#999')
:css('font-size', '105%')
:css('font-weight', 'bold')
:wikitext(self.headerText)
return headerDiv
end
function Poll:renderQuestion()
local question = mw.html.create('div')
:css('margin-top', '10px')
:css('margin-bottom', '10px')
:css('font-size', 'smaller')
:wikitext(self.question)
return question
end
function Poll:renderVisualization()
local vzn = mw.html.create('table')
-- Overlay row
vzn
:css('height', '250px')
:css('border-spacing', '0px')
:tag('tr')
:tag('td')
:css('position', 'absolute')
:css('z-index', '2')
:css('padding', '0px')
:css('margin', '0px')
:wikitext(string.format(
'[[File:%s|253px|link=]] ',
self.overlay
))
-- Option color rows
for i, option in ipairs(self.options) do
vzn:tag('tr'):tag('td')
:css('background', option.color)
:css('padding', '0px')
:css('margin', '0px')
:css('width', '250px')
:css('height', string.format(
'%.3f%%', -- Round to 3 decimal places and add a percent sign
option.percentage
))
:wikitext(' ')
end
return vzn
end
function Poll:renderLegend()
local legend = mw.html.create('table')
for i, option in ipairs(self.options) do
legend:tag('tr'):tag('td')
:tag('span')
:css('display', 'inline-block')
:css('width', '1.5em')
:css('height', '1.5em')
:css('margin', '1px 0')
:css('border', '1px solid black')
:css('background-color', option.color)
:css('text-align', 'center')
:done()
:wikitext(' ')
:wikitext(option.option)
:wikitext(' ')
:tag('small')
:wikitext(self:message(
'legend-option-text',
option.option,
option.count,
string.format('%.0f', option.percentage)
))
end
return legend
end
function Poll:makeVoteURL(n)
local url = mw.uri.fullUrl(
self.votepage,
{
action = 'edit',
section = 'new',
nosummary = 'true',
preload = self.preload,
['preloadparams[]'] = self:getVoteText(n)
}
)
return tostring(url)
end
function Poll:hasLineBreaks()
-- Try to auto-detect whether we should have line breaks
if self.lineBreak then
return yesno(self.lineBreak) or true
end
local nOptions = #self.options
if nOptions > 3 then
return false
end
local wordCount = 0
for i, option in ipairs(self.options) do
wordCount = wordCount + mw.ustring.len(option.option)
end
if nOptions == 3 then
return wordCount >= 12
else
return wordCount >= 15
end
end
function Poll:renderButtons()
local hasBreaks = self:hasLineBreaks()
local buttonRoot = mw.html.create('div')
if not hasBreaks then
buttonRoot:addClass('center')
end
for i, option in ipairs(self.options) do
buttonRoot:tag('span')
:addClass('mw-ui-button mw-ui-progressive')
:attr('role', 'button')
:attr('aria-disabled', 'false')
:css('margin', '2px')
:wikitext(makeUrlLink(
self:makeVoteURL(i),
option.option
))
if hasBreaks then
buttonRoot:tag('div'):css('clear', 'both')
end
end
return buttonRoot
end
function Poll:__tostring()
local root = mw.html.create('div')
:css('width', '270px')
:css('float', 'right')
:css('clear', 'right')
:css('background', 'none')
:css('margin-bottom', '10px')
:css('margin-left', '10px')
:addClass('signpost-sidebar')
root:node(self:renderHeader())
root:node(self:renderQuestion())
local centered = root:tag('div')
:css('margin-left', 'auto')
:css('margin-right', 'auto')
centered:node(self:renderVisualization())
centered:node(self:renderLegend())
centered:node(self:renderButtons())
return tostring(root)
end
-------------------------------------------------------------------------------
-- Exports
-------------------------------------------------------------------------------
local p = {}
function p._main(args, cfg)
cfg = cfg or mw.loadData(CONFIG_MODULE)
return tostring(Poll.new(args, cfg))
end
function p.main(frame, cfg)
cfg = cfg or mw.loadData(CONFIG_MODULE)
local args = require('Module:Arguments').getArgs(frame, {
wrappers = cfg.wrappers
})
return p._main(args, cfg)
end
-- return p
return Poll