Module:WikiProjectBanner/Banner

--- --                              Banner class                                -- -- This module contains the Banner class used in Module:WikiProjectBanner. -- -- It is used to generate the banner HTML and categories. -- ---

-- Load required modules. local yesno = require('Module:Yesno') local mShared = require('Module:WikiProjectBanner/shared')

-- Lazily load modules we might not need. local Grade

local Banner = {} Banner.__index = Banner

Banner.rowModules = { quality = 'Module:WikiProjectBanner/AssessmentRow', importance = 'Module:WikiProjectBanner/AssessmentRow', taskForces = 'Module:WikiProjectBanner/TaskForce', requests = 'Module:WikiProjectBanner/Note', notices = 'Module:WikiProjectBanner/Note' }

-- Define the classes table. This loads modules containing the row classes -- on demand; if it is not indexed, the class is not loaded. Banner.rowClasses = setmetatable({}, {	__index = function (t, key)		local module = Banner.rowModules[key]		if module then			module = require(module)			Banner.rowClasses[key] = module			return module		end	end })

function Banner.new(bannerName, args, cfg, bannerCfg) -- Set data we were passed. local obj, data = {}, {} data.bannerName = bannerName obj.args = args obj.cfg = cfg

-- Set title objects. data.currentTitle = mw.title.getCurrentTitle data.subjectTitle = data.currentTitle.subjectPageTitle

-- Set banner config. -- We use a metatable to make it read-only, to try and limit hook -- interaction. bannerCfg = bannerCfg or mShared.maybeRequire(		'Module:WikiProjectBanner/banners/' .. data.bannerName	) if not bannerCfg then error(			'banner data page Module:WikiProjectBanner/banners/' ..			bannerName ..			' does not exist',			0		) end bannerCfg.hooks = bannerCfg.hooks or {} obj.bannerCfg = setmetatable({}, {		__index = bannerCfg,		__newindex = function 			error('the banner config is read-only', 2)		end,		__pairs = function 			return pairs(bannerCfg)		end,		__ipairs = function 			return ipairs(bannerCfg)		end	})

-- Set banner data. do -- Find if we are in demo mode. local currentPage = data.currentTitle.prefixedText local template = mw.site.namespaces[10].name .. ':' .. data.bannerName if currentPage == template or currentPage == template .. '/sandbox' then data.isDemo = true end end data.pageType = require('Module:Pagetype')._main{} data.project = bannerCfg.project or bannerName:gsub('^WikiProject ', '') data.projectLink = bannerCfg.projectLink or 'Wikipedia:WikiProject ' .. data.project data.projectName = bannerCfg.projectName or 'WikiProject ' .. data.project data.projectScope = bannerCfg.projectScope or  .. data.project ..  data.projectLinkTalk = mw.title.new(data.projectLink).talkPageTitle.prefixedText data.isSmall = yesno(args.small)

-- Assessment link if bannerCfg.assessmentLink ~= nil then -- Custom link or false for no link data.assessmentLink = bannerCfg.assessmentLink else local assessmentPage = data.projectLink .. '/Assessment' local assessmentTitle = mw.title.new(assessmentPage) data.assessmentLink = assessmentTitle and assessmentTitle.exists and assessmentTitle.prefixedText end

-- Create the HTML fragments. do local fragmentKeys = { 'headerName', 'headerRating', 'blurbImageLeft', 'blurbText', 'blurbImageRight', }		local html = {} local create = mw.html.create for _, key in ipairs(fragmentKeys) do			html[key] = create end obj.html = html end

-- Define the rest of the object structure. data.grades = {} obj.data = data obj.categories = {} obj.rows = { quality = {}, importance = {}, taskForces = {}, requests = {}, notices = {} }

return setmetatable(obj, Banner) end

function Banner:addRow(key, isActiveByDefault, rowCfg, ...) -- This function adds one row object to the banner object's rows subtable. if not key or not Banner.rowModules[key] then error(string.format( "'%s' is not a valid key for Banner:addRow", key ), 2)	end local isActive = isActiveByDefault or mShared.isActiveRow(		self.args,		self.data,		self.cfg,		rowCfg	) if isActive then -- Create the object and add it to the banner's rows table. local aClass = Banner.rowClasses[key] local obj = aClass.new(self.args, self.data, self.cfg, rowCfg, ...) table.insert(self.rows[key], obj)

-- Add the categories to the banner object's categories table. for i, category in ipairs(obj:exportCategories) do			table.insert(self.categories, category) end end end

function Banner:addRows local bannerCfg = self.bannerCfg

-- Quality if bannerCfg.quality ~= false then Grade = Grade or require('Module:WikiProjectBanner/Grade') local gradeCfg = {} local gradeObj = Grade.new(			'quality',			self.args,			self.data,			self.cfg,			gradeCfg		) for i, category in ipairs(gradeObj:exportCategories) do			table.insert(self.categories, category) end local rowCfg = bannerCfg.quality or {} self:addRow('quality', true, rowCfg, gradeObj) self.data.grades.quality = gradeObj end

-- Importance if bannerCfg.importance ~= false then Grade = Grade or require('Module:WikiProjectBanner/Grade') local gradeCfg = {} local qualityGrade = self.data.grades.quality if qualityGrade then gradeCfg.forceGrade = qualityGrade:exportData.forceImportance end local gradeObj = Grade.new(			'importance',			self.args,			self.data,			self.cfg,			gradeCfg		) for i, category in ipairs(gradeObj:exportCategories) do			table.insert(self.categories, category) end local rowCfg = bannerCfg.importance or {} if gradeObj:exportData.short ~= 'NA' then self:addRow('importance', true, rowCfg, gradeObj) end end

-- Task forces, requests, and notices for i, key in ipairs{'taskForces', 'requests', 'notices'} do		for j, rowCfg in ipairs(bannerCfg[key] or {}) do			self:addRow(key, false, rowCfg) end end

-- Count the requests and notices. local nNotes = #self.rows.requests + #self.rows.notices if nNotes > (bannerCfg.collapsed or self.cfg.collapsed or 3) then self.data.isCollapsed = true end end

function Banner:callHook(key) local hookFunc = self.bannerCfg.hooks[key] if type(hookFunc) == 'function' then return hookFunc{ categories = self.categories, data = self.data, args = self.args, cfg = self.cfg, bannerCfg = self.bannerCfg }	end end function Banner:renderBlurb self:renderImage('Left')

if self.bannerCfg.portal and not self.data.isSmall then local portal = mShared.makePortal(self.bannerCfg.portal) self.html.blurbText:wikitext(portal) end

self:renderBlurbText self:renderImage('Right') end

function Banner:renderImage(position) -- Valid positions are "Left" and "Right". They must be capitalised. local filename = self.bannerCfg['image' .. position] if filename then local size = self.data.isSmall and (self.bannerCfg['image' .. position .. 'Small'] or "40px") or (self.bannerCfg['image' .. position .. 'Large'] or "80px") local fileLink = string.format('', filename, size) self.html['blurbImage' .. position]:wikitext(fileLink) end end

function Banner:renderBlurbText local blurbText = self:callHook('blurb') or self.bannerCfg.blurb if blurbText then self.html.blurbText:wikitext(blurbText) else local data = self.data local msg if data.isSmall then msg = "This $5 is within the scope of $1, a collaborative effort " .. "to improve the coverage of $3 on Wikipedia." else msg = "This $5 is within the scope of $1, a collaborative effort " .. "to improve the coverage of $3 on Wikipedia. If you would like to participate, " .. "please visit the project page, where you can join the discussion and " .. "see a list of open tasks." end msg = mShared.substituteParams(			msg,			data.projectName,			data.projectLink,			data.projectScope,			data.projectLinkTalk,			data.pageType		) self.html.blurbText:wikitext(msg) end

end

function Banner:getRowHtml(rowType) -- Render the rows. local rowHtml = mw.html.create for i, obj in ipairs(self.rows[rowType] or {}) do		rowHtml:node(obj:exportHtml) end return rowHtml end

function Banner:renderCategories local sortKey = self.args.listas local ret = {} if yesno(self.args.category) ~= false then local categoryNsText = mw.site.namespaces[14].name for _, category in ipairs(self.categories) do			if sortKey then ret[#ret + 1] = string.format(					'%s',					categoryNsText,					category,					sortKey				) else ret[#ret + 1] = string.format(					'%s:%s',					categoryNsText,					category				) end end end return table.concat(ret) end

function Banner:bindHtmlFragments local html = self.html

local root = mw.html.create

local wrapper = root :node(self:callHook('preWrapper')) :tag('table') :addClass('tmbox tmbox-notice collapsible innercollapse wpb') :addClass(self.data.isSmall and 'mbox-small' or nil)

wrapper :tag('tr') :addClass('wpb-header') :tag('td') :css('text-align', 'right') :css('padding', '0.3em 1em 0.3em 0.3em') :css('width', '50%') :css('font-weight', 'bold') :node(self:callHook('headerName')) :done :tag('th') :css('text-align', 'left') :css('width', '50%') :css('padding', '0.3em 0.3em 0.3em 0') :node(self:callHook('headerRating'))

local content = wrapper :tag('tr') :tag('td') :addClass('mbox-text') :css('padding', '3px 0 3px 5px') :attr('colspan', '2') :tag('table') :css('background', 'transparent') :css('border', 'none') :css('padding', '0') :css('width', '100%') :attr('cellspacing', '0')

content :node(self:callHook('preBlurb'))

local blurbRow = content:tag('tr')

-- blurb image left if self.bannerCfg.imageLeft then blurbRow:tag('td') :addClass('mbox-image') :node(html.blurbImageLeft) end

-- blurb text do local colspan = (self.bannerCfg.imageLeft and 1 or 0) + (self.bannerCfg.imageRight and 1 or 0) + 1		colspan = colspan > 1 and colspan or nil blurbRow:tag('td') :addClass('mbox-text') :attr('colspan', colspan) :node(html.blurbText) end

-- blurb image right if self.bannerCfg.imageRight then blurbRow:tag('td') :addClass('mbox-imageright') :node(html.blurbImageRight) end

content :node(self:callHook('postBlurb')) :node(self:getRowHtml('quality')) :node(self:callHook('postQuality')) :node(self:getRowHtml('importance')) :node(self:callHook('postImportance')) :node(self:getRowHtml('taskForces')) :node(self:callHook('postTaskForces')) if self.data.isCollapsed then -- We are collapsing requests and notices, so define the collapsible -- table and add the requests and notices to it. content :tag('tr') :tag('td') :attr('colspan', '3') :css('padding', '0') :tag('table') :addClass('collapsible collapsed') :css('width', '100%') :css('background', 'transparent') :tag('tr') :tag('th') :attr('colspan', '3') :css('text-align', 'left') :css('padding', '0.2em 2px 0.2em 0') :wikitext(									self.bannerCfg.moreHeader										or 'More information'								) :done :done :node(self:callHook('preCollapsedContent')) :node(self:getRowHtml('requests')) :node(self:callHook('postRequests')) :node(self:getRowHtml('notices')) :node(self:callHook('postNotices')) else -- We aren't collapsing requests and notices, so add them to the normal -- content node. content :node(self:getRowHtml('requests')) :node(self:callHook('postRequests')) :node(self:getRowHtml('notices')) :node(self:callHook('postNotices')) end

content :node(self:callHook('postContent'))

root :wikitext(self:renderCategories) :node(self:callHook('postWrapper'))

html.root = root end

function Banner:__tostring self:addRows self:callHook self:renderBlurb self:bindHtmlFragments local root = self.html.root or mw.html.create return tostring(root) end

return Banner