From Wikipedia, the free encyclopedia


-- 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.msgkey

	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 = colorsself.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 = colorsself.nOption % nColors or colorsnColors

	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 = argskey

			if not text then

				break

			end

			table.insert(self.options, Option.new{

				nOption = i,

				text = text,

				voteText = argskey .. 'vote'],

				color = argskey .. '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 votesoption:getVoteText()] then

				error(self:message(

					'duplicate-vote-text-error',

					{votesoption:getVoteText()], option.nOption},

					true

				))

			else

				votesoption: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.optionsi

		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=]] &nbsp;',

				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('&nbsp;')

	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('background', 'none')

		: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
From Wikipedia, the free encyclopedia


-- 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.msgkey

	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 = colorsself.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 = colorsself.nOption % nColors or colorsnColors

	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('&nbsp;')

			:done()

		:wikitext('&nbsp;')

		: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 = argskey

			if not text then

				break

			end

			table.insert(self.options, Option.new{

				nOption = i,

				text = text,

				voteText = argskey .. 'vote'],

				color = argskey .. '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 votesoption:getVoteText()] then

				error(self:message(

					'duplicate-vote-text-error',

					{votesoption:getVoteText()], option.nOption},

					true

				))

			else

				votesoption: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.optionsi

		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=]] &nbsp;',

				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('&nbsp;')

	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('background', 'none')

		: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

Videos

Youtube | Vimeo | Bing

Websites

Google | Yahoo | Bing

Encyclopedia

Google | Yahoo | Bing

Facebook