Module:Signpost poll
| This module depends on the following other modules: |
This module implements Wikipedia:Wikipedia Signpost/Templates/Voter.
-- This module implements polls used in articles of the Signpost.
local CONFIG_MODULE = 'Module:Signpost poll/config'
local yesno = require('Module:Yesno')
local lang = mw.language.getContentLanguage()
-------------------------------------------------------------------------------
-- Message method
-- This method is available in every class, so it is defined separately.
-------------------------------------------------------------------------------
local function message(self, key, params, isPreprocessed)
local msg = self.cfg.msg[key]
if params and #params > 0 then
msg = mw.message.newRawMessage(msg, params):plain()
end
if isPreprocessed then
msg = self.frame:preprocess(msg)
end
return msg
end
-------------------------------------------------------------------------------
-- Option class
-------------------------------------------------------------------------------
local Option = {}
Option.__index = Option
Option.message = message
function Option.new(t)
local self = setmetatable({}, Option)
self.cfg = t.cfg
self.frame = t.frame
self.nOption = t.nOption
self.votePage = t.votePage
self.preload = t.preload
self.text = t.text
self.voteText = t.voteText
self.color = t.color
return self
end
function Option:getCount()
if self.count then
return self.count
else
self.count = mw.getCurrentFrame():expandTemplate{title="String count",args={
page = self.votePage,
search = self:getVoteText(n)
}}
return self.count
end
end
function Option:setVoteTotal(n)
self.total = n
end
function Option:getVoteTotal()
return self.total or error('total number of votes has not been set')
end
function Option:getPercentage()
if self.percentage then
return self.percentage
else
self.percentage = self:getCount() / self:getVoteTotal() * 100
return self.percentage
end
end
function Option:getColor()
-- Get the default color for option n
if self.color then
return self.color
end
local colors = self.cfg.colors
local color = colors[self.nOption]
if color then
self.color = color
else
-- Loop to find the length of colors. We can't use the # operator as
-- a metatable is set by mw.loadData. This is bad for polls with
-- more options than there are colors in the config, as we would loop
-- for every single option object. This will likely never be a problem
-- in practice, however.
local nColors = 0
for i in ipairs(colors) do
nColors = i
end
-- colors[nColors] is necessary as Lua arrays are indexed starting at
-- 1, and n % self.nColors might sometimes equal 0.
self.color = colors[self.nOption % nColors] or colors[nColors]
end
return self.color
end
function Option:getVoteText()
self.voteText = self.voteText or self:message(
'vote-default',
{self.nOption},
true
)
return self.voteText
end
function Option:makeVoteURL()
local url = mw.uri.fullUrl(
self.votePage,
{
action = 'edit',
section = 'new',
nosummary = 'true',
preload = self.preload,
['preloadparams[]'] = self:getVoteText()
}
)
return tostring(url)
end
function Option:renderButton()
local button = mw.html.create('span')
:addClass('mw-ui-button mw-ui-progressive')
:attr('role', 'button')
:attr('aria-disabled', 'false')
:wikitext(self.text)
local wrapper = mw.html.create('span')
:addClass('plainlinks')
:css('margin', '0 4px')
:wikitext(string.format(
'[%s %s]',
self:makeVoteURL(),
tostring(button)
))
return wrapper
end
function Option:renderLegendRow()
local legend = mw.html.create('div')
legend
:css('margin', '4px')
: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', self:getColor())
:css('text-align', 'center')
:wikitext(' ')
:done()
:wikitext(' ')
:wikitext(self:message('legend-option-text', {
self.text,
self:getCount(),
string.format('%.0f', self:getPercentage())
}, true))
return legend
end
-------------------------------------------------------------------------------
-- Poll class
-------------------------------------------------------------------------------
local Poll = {}
Poll.__index = Poll
Poll.message = message
function Poll.new(args, cfg, frame)
local self = setmetatable({}, Poll)
self.cfg = cfg or mw.loadData(CONFIG_MODULE)
self.frame = frame or mw.getCurrentFrame()
-- Set required fields
self.question = assert(args.question, self:message('no-question-error'))
self.votePage = assert(args.votepage, self:message('no-votepage-error'))
-- Set optional fields
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 preload = self:message('preload-page')
local i = 1
while true do
local key = 'option' .. tostring(i)
local text = args[key]
if not text then
break
end
table.insert(self.options, Option.new{
nOption = i,
text = text,
voteText = args[key .. 'vote'],
color = args[key .. 'color'],
cfg = self.cfg,
frame = self.frame,
votePage = self.votePage,
preload = preload
})
i = i + 1
end
if #self.options < 2 then
error(self:message('not-enough-options-error'))
end
end
-- Check for duplicate vote text
do
local votes = {}
for option in self:iterateOptions() do
if votes[option:getVoteText()] then
error(self:message(
'duplicate-vote-text-error',
{votes[option:getVoteText()], option.nOption},
true
))
else
votes[option:getVoteText()] = option.nOption
end
end
end
-- Prompt users to create the vote page if it doesn't exist.
do
local success, votePageContent = pcall(function ()
return mw.title.new(self.votePage):getContent()
end)
if not success or not votePageContent then
local createVotePageUrl = mw.uri.fullUrl(
self.votePage,
{
action = 'edit',
preload = self:message('vote-page-preload-default'),
['preloadparams[]'] = mw.title.getCurrentTitle().prefixedText,
summary = self:message('vote-page-create-summary'),
editintro = self:message('vote-page-create-editintro')
}
)
error(self:message(
'votepage-nonexistent-error',
{tostring(createVotePageUrl)}
), 0)
end
end
-- Find total number of votes
do
local total = 0
for option in self:iterateOptions() do
total = total + option:getCount()
end
for option in self:iterateOptions() do
option:setVoteTotal(total)
end
self.voteTotal = total
end
return self
end
-- Static methods
function Poll.getUnixDate(date)
date = lang:formatDate('U', date)
return tonumber(date)
end
-- Normal methods
function Poll:iterateOptions()
local i = 0
local n = #self.options
return function ()
i = i + 1
if i <= n then
return self.options[i]
end
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 0')
: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('line-height', '100%')
:css('font-size', '95%')
:wikitext(self.question)
return question
end
function Poll:renderVisualization()
local overlayWidth = '253px'
local vzn = mw.html.create('div')
:css('height', '250px')
:css('border-spacing', '0')
:css('width', overlayWidth)
:css('margin-left', 'auto')
:css('margin-right', 'auto')
-- Overlay
vzn
:tag('div')
:css('position', 'absolute')
:css('z-index', '2')
:css('padding', '0')
:css('margin', '0')
:wikitext(string.format(
'[[File:%s|%s|link=]] ',
self.overlay,
overlayWidth
))
-- Option colors
for option in self:iterateOptions() do
vzn:tag('div')
:css('background', option:getColor())
:css('padding', '0')
:css('margin', '0')
:css('width', '250px')
:css('height', string.format(
'%.3f%%', -- Round to 3 decimal places and add a percent sign
option:getPercentage()
))
:wikitext(' ')
end
return vzn
end
function Poll:renderLegend()
local legend = mw.html.create('div')
:css('margin-top', '3px')
:css('display', 'flex')
:css('justify-content', 'center')
local centered = legend:tag('div')
for option in self:iterateOptions() do
centered:node(option:renderLegendRow())
end
return legend
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 true
end
local wordCount = 0
for option in self:iterateOptions() do
wordCount = wordCount + mw.ustring.len(option.text)
end
if nOptions == 3 then
return wordCount >= 12
else
return wordCount >= 15
end
end
function Poll:renderButtons()
local hasBreaks = self:hasLineBreaks()
local buttons = mw.html.create('div')
:css('margin-top', '5px')
:css('display', 'flex')
:css('justify-content', 'center')
local centered = buttons:tag('div')
if not hasBreaks then
centered:css('text-align', 'center')
end
for option in self:iterateOptions() do
local button
if hasBreaks then
button = centered:tag('div')
:css('margin', '4px 0')
else
button = centered
end
button:node(option:renderButton())
end
return buttons
end
function Poll:renderWarning(s)
local warning = mw.html.create('div')
warning
:css('line-height', '90%')
:css('width', '100%')
:css('margin-top', '5px')
:css('text-align', 'center')
:css('color', 'red')
:css('font-size', '85%')
:wikitext(s)
return warning
end
function Poll:hasMinimumVoteCount()
return self.voteTotal >= self.minimum
end
function Poll:isOpen()
if self.expiry then
return self.getUnixDate() < self.getUnixDate(self.expiry)
else
return true
end
end
function Poll:__tostring()
local root = mw.html.create('div')
:css('width', '270px')
:css('float', 'right')
:css('clear', 'right')
:css('margin-bottom', '10px')
:css('margin-left', '10px')
:addClass('signpost-sidebar')
root:node(self:renderHeader())
root:node(self:renderQuestion())
-- Visualization and legend
if self:hasMinimumVoteCount() then
root:node(self:renderVisualization())
root:node(self:renderLegend())
else
root:node(self:renderWarning(self:message(
'not-enough-votes-warning',
{self.minimum - self.voteTotal},
true
)))
end
-- Buttons
if self:isOpen() then
root:node(self:renderButtons())
else
root:node(self:renderWarning(self:message('poll-closed-warning')))
end
return tostring(root)
end
-------------------------------------------------------------------------------
-- Exports
-------------------------------------------------------------------------------
local p = {}
function p._main(args, cfg, frame)
return tostring(Poll.new(args, cfg, frame))
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, frame)
end
return p
Content Disclaimer
Informasi ini disarikan dari Wikipedia dan disajikan kembali untuk tujuan edukasi. Konten tersedia di bawah lisensi CC BY-SA 3.0. Kami tidak bertanggung jawab atas ketidakakuratan data yang bersumber dari kontribusi publik tersebut.
- The information displayed on this website is sourced in part or in whole from Wikipedia and has been adapted for the purpose of restating it. We strive to provide accurate and relevant information, however:
- There is no guarantee of absolute accuracy. Wikipedia is an open, collaborative project that can be edited by anyone, so information is subject to change.
- It is not intended to constitute professional advice. The content displayed is for informational and educational purposes only. For important decisions (e.g., medical, legal, or financial), please consult a professional.
- Content copyright. Wikipedia is licensed under the Creative Commons Attribution-ShareAlike License (CC BY-SA). This means that content may be reused with appropriate attribution and shared under a similar license.
- Responsible use. Any risk arising from the use of information from this website is entirely the responsibility of the user.