Module:Portal: Difference between revisions

m
1 revision imported: templates
(use Module:Yesno for added flexibility (TPER))
 
m (1 revision imported: templates)
 
(5 intermediate revisions by 3 users not shown)
Line 42: Line 42:
local p = {}
local p = {}


local trackingEnabled = true
-- determine whether we're being called from a sandbox
local isSandbox = mw.getCurrentFrame():getTitle():find('sandbox', 1, true)
local sandbox = isSandbox and '/sandbox' or ''


local templatestyles = 'Module:Portal/styles.css'
local function sandboxVersion(s)
return isSandbox and s..'-sand' or s
end


local templatestyles = 'Module:Portal'..sandbox..'/styles.css'
local getArgs = require('Module:Arguments').getArgs
local yesno = require('Module:Yesno')
local yesno = require('Module:Yesno')
-- List of non-talk namespaces which should not be tracked (Talk pages are never tracked)
local badNamespaces = {'user','template','draft','wikipedia'}


-- Check whether to do tracking in this namespace
-- Check whether to do tracking in this namespace
-- Returns true unless the page is one of the banned namespaces
-- Returns true unless the page is one of the banned namespaces
local function checkTrackingNamespace()
local function checkTracking(title)
local thisPage = mw.title.getCurrentTitle()
local thisPage = title or mw.title.getCurrentTitle()
if (thisPage.namespace == 1) -- Talk
if thisPage.isTalkPage then
or (thisPage.namespace == 2) -- User
or (thisPage.namespace == 3) -- User talk
or (thisPage.namespace == 5) -- Wikipedia talk
or (thisPage.namespace == 7) -- File talk
or (thisPage.namespace == 11) -- Template talk
or (thisPage.namespace == 15) -- Category talk
or (thisPage.namespace == 101) -- Portal talk
or (thisPage.namespace == 109) -- Book talk
or (thisPage.namespace == 118) -- Draft
or (thisPage.namespace == 119) -- Draft talk
or (thisPage.namespace == 829) -- Module talk
then
return false
return false
end
end
return true
local ns = thisPage.nsText:lower()
end
for _, v in ipairs(badNamespaces) do
 
if ns == v then
-- Check whether to do tracking on this pagename
return false
-- Returns false if the page title matches one of the banned strings
end
-- Otherwise returns true
local function checkTrackingPagename()
local thisPage = mw.title.getCurrentTitle()
local thisPageLC = mw.ustring.lower(thisPage.text)
if (string.match(thisPageLC, "/archive") ~= nil) then
return false
end
if (string.match(thisPageLC, "/doc") ~= nil) then
return false
end
if (string.match(thisPageLC, "/test") ~= nil) then
return false
end
end
return true
return true
end
end


local function matchImagePage(s)
local function matchImagePage(s)
Line 96: Line 81:
local imagePage
local imagePage
if mw.ustring.find(firstLetter, '^[a-z]') then
if mw.ustring.find(firstLetter, '^[a-z]') then
imagePage = 'Module:Portal/images/' .. firstLetter
imagePage = 'Module:Portal/images/' .. firstLetter .. sandbox
else
else
imagePage = 'Module:Portal/images/other'
imagePage = 'Module:Portal/images/other' .. sandbox
end
end
return mw.loadData(imagePage)[s]
return mw.loadData(imagePage)[s]
Line 105: Line 90:
local function getAlias(s)
local function getAlias(s)
-- Gets an alias from the image alias data page.
-- Gets an alias from the image alias data page.
local aliasData = mw.loadData('Module:Portal/images/aliases')
local aliasData = mw.loadData('Module:Portal/images/aliases'..sandbox)
for portal, aliases in pairs(aliasData) do
for portal, aliases in pairs(aliasData) do
for _, alias in ipairs(aliases) do
for _, alias in ipairs(aliases) do
Line 114: Line 99:
end
end
end
end
local defaultImage = 'Portal-puzzle.svg|link=|alt='


local function getImageName(s)
local function getImageName(s)
-- Gets the image name for a given string.
-- Gets the image name for a given string.
local default = 'Portal-puzzle.svg|link=|alt='
if type(s) ~= 'string' or #s < 1 then
if type(s) ~= 'string' or #s < 1 then
return default
return defaultImage
end
end
s = mw.ustring.lower(s)
s = mw.ustring.lower(s)
return matchImagePage(s) or matchImagePage(getAlias(s)) or default
return matchImagePage(s) or matchImagePage(getAlias(s)) or defaultImage
end
end


local function checkPortalExists(portal)
-- Function to check argument portals for errors, generate tracking categories if needed
return not (mw.title.makeTitle(100, portal).id == 0)
--    Function first checks for too few/many portals provided
--    Then checks the portal list to purge any portals that don't exist
-- Arguments:
--  portals: raw list of portals
--  args.tracking: is tracking requested? (will not track on bad titles or namespaces)
--  args.redlinks: should redlinks be displayed?
--  args.minPortals: minimum number of portal arguments
--  args.maxPortals: maximum number of portal arguments
-- Returns:
--  portals = list of portals, with redlinks purged (if args.redlinks=false)
--  trackingCat = possible tracking category
--  errorMsg = error message
function p._checkPortals(portals, args)
local trackingCat = ''
local errMsg = nil
-- Tracking is on by default.
-- It is disabled if any of the following is true
-- 1/ the parameter "tracking" is set to 'no, 'n', or 'false'
-- 2/ the current page fails the namespace or pagename tests
local trackingEnabled = args.tracking and checkTracking()
args.minPortals = args.minPortals or 1
args.maxPortals = args.maxPortals or -1
-- check for too few portals
if #portals < args.minPortals then
errMsg = 'please specify at least '..args.minPortals..' portal'..(args.minPortals > 1 and 's' or '')
trackingCat = (trackingEnabled and '[[Category:Portal templates with too few portals]]' or '')
return portals, trackingCat, errMsg
end
-- check for too many portals
if args.maxPortals >= 0 and #portals > args.maxPortals then
errMsg = 'too many portals (maximum = '..args.maxPortals..')'
trackingCat = (trackingEnabled and '[[Category:Portal templates with too many portals]]' or '')
return portals, trackingCat, errMsg
end
if not args.redlinks or trackingEnabled then
-- make new list of portals that exist
local existingPortals = {}
for _, portal in ipairs(portals) do
local portalTitle = mw.title.new(portal,"Portal")
-- if portal exists, put it into list
if portalTitle and portalTitle.exists then
table.insert(existingPortals,portal)
-- otherwise set tracking cat
elseif trackingEnabled then
trackingCat = "[[Category:Portal templates with redlinked portals]]"
end
end
-- If redlinks is off, use portal list purged of redlinks
portals = args.redlinks and portals or existingPortals
-- if nothing left after purge, set tracking cat
if #portals == 0 and trackingEnabled then
trackingCat = trackingCat.."[[Category:Pages with empty portal template]]"
end
end
return portals, trackingCat, errMsg
end
end


function p._portal(portals, args)
local function portalBox(args)
-- This function builds the portal box used by the {{portal}} template.
return mw.html.create('ul')
local root = mw.html.create('div')
:attr('role', 'navigation')
:attr('role', 'navigation')
:attr('aria-label', 'Portals')
:attr('aria-label', 'Portals')
:addClass('noprint portal plainlist')
:addClass('noprint')
:addClass(args.left and 'tleft' or 'tright')
:addClass(args.error and '' or sandboxVersion('portalbox'))
:addClass(args.border and sandboxVersion('portalborder') or '')
:addClass(sandboxVersion(args.left and 'portalleft' or 'portalright'))
:css('margin', args.margin or nil)
:css('margin', args.margin or nil)
:newline()
:newline()
end


-- Tracking is on by default.
local function fillBox(root, contents)
-- It is disabled if any of the following is true
for _, item in ipairs(contents) do
-- 1/ the parameter "tracking" is set to 'no, 'n', or 'false'
local entry = root:tag('li')
-- 2/ the current page fails the namespace tests in checkTrackingNamespace()
entry:addClass(sandboxVersion('portalbox-entry'))
-- 3/ the current page fails the pagename tests in checkTrackingPagename()
local image = entry:tag('span')
trackingEnabled = yesno(args.tracking, trackingEnabled)
image:addClass(sandboxVersion('portalbox-image'))
if (checkTrackingNamespace() == false) then
image:wikitext(item[1])
trackingEnabled = false
local link = entry:tag('span')
link:addClass(sandboxVersion('portalbox-link'))
link:wikitext(item[2])
end
end
if (checkTrackingPagename() == false) then
return root
trackingEnabled = false
end
 
function p._portal(portals, args)
-- This function builds the portal box used by the {{portal}} template.
-- Normalize all arguments
if args.redlinks == 'include' then args.redlinks = true end
args.addBreak = args['break']
for key, default in pairs({left=false,tracking=true,nominimum=false,
                      redlinks=false,addBreak=false,border=true}) do
if args[key] == nil then args[key] = default end
args[key] = yesno(args[key], default)
end
end


-- If no portals have been specified, display an error and add the page to a tracking category.
local root = portalBox(args)
if not portals[1] then
 
if yesno(args.nominimum) then
local trackingCat = ''
-- if nominimum as been set to yes (or similar), omit the warning
local errMsg = nil
args.minPortals = args.nominimum and 0 or 1
else
args.maxPortals = -1
root:wikitext('<strong class="error">No portals specified: please specify at least one portal</strong>')
portals, trackingCat, errMsg = p._checkPortals(portals, args)
end
root:wikitext(trackingCat)
if (trackingEnabled) then
-- if error message, put it in the box and return
root:wikitext('[[Category:Portal templates without a parameter]]')
if errMsg then
if args.border then -- suppress error message when border=no
args.error = true  -- recreate box without fancy formatting
root = portalBox(args)
root:wikitext(trackingCat)
local errTag = root:tag('strong')
errTag:addClass('error')
errTag:css('padding','0.2em')
errTag:wikitext('Error: '..errMsg)
end
end
return tostring(root)
return tostring(root)
end
end
-- if no portals (and no error), just return tracking category
-- scan for nonexistent portals, if they exist remove them from the portals table. If redlinks=yes, then don't remove
if #portals == 0 then
local portallen = #portals
return trackingCat
-- traverse the list backwards to ensure that no portals are missed (table.remove also moves down the portals in the list, so that the next portal isn't checked if going fowards.
end
-- going backwards allows us to circumvent this issue
 
for i=portallen,1,-1 do
local contents = {}
-- the use of pcall here catches any errors that may occour when attempting to locate pages when the page name is invalid
-- Display the portals specified in the positional arguments.
-- if pcall returns true, then rerun the function to find if the page exists
local defaultUsed = nil
if not pcall(checkPortalExists, portals[i]) or not checkPortalExists(portals[i]) then
for _, portal in ipairs(portals) do
-- Getting here means a redlinked portal has been found
local portalImage = getImageName(portal)
if yesno(args.redlinks) or (args.redlinks == 'include') then
if portalImage == defaultImage then
-- if redlinks as been set to yes (or similar), add the cleanup category and then break the loop before the portal is removed from the list
defaultUsed = portal
if (trackingEnabled) then
root:wikitext('[[Category:Portal templates with redlinked portals]]')
end
break
end
-- remove the portal (this does not happen if redlinks=yes)
table.remove(portals,i)
end
end
local image = string.format('[[File:%s|32x28px|class=noviewer]]',
portalImage)
local link = string.format('[[Portal:%s|%s%sportal]]',
portal, portal, args.addBreak and '<br />' or ' ')
table.insert(contents, {image, link})
end
end
if defaultUsed and checkTracking() then
-- if the length of the table is different, then rows were removed from the table, so portals were removed. If this is the case add the cleanup category
local cat = string.format('[[Category:Portal templates with default image|%s]]',
if not (portallen == #portals) then
                      defaultUsed)
if (trackingEnabled) then
root:wikitext(cat)
if #portals == 0 then
return '[[Category:Portal templates with all redlinked portals]]'
else
root:wikitext('[[Category:Portal templates with redlinked portals]]')
end
end
end
end
return tostring(fillBox(root, contents))
end


-- Start the list. This corresponds to the start of the wikitext table in the old [[Template:Portal]].
function p._demo(imageList, args)
local listroot = root:tag('ul')
for key, default in pairs({left=false,border=true}) do
:css('width', type(args.boxsize) == 'string' and (args.boxsize .. 'px') or nil)
if args[key] == nil then args[key] = default end
args[key] = yesno(args[key], default)
end
local root = portalBox(args)


local contents = {}
-- Display the portals specified in the positional arguments.
-- Display the portals specified in the positional arguments.
for _, portal in ipairs(portals) do
for _, fn in ipairs(imageList) do
local image = getImageName(portal)
local image = string.format('[[File:%s|32x28px|class=noviewer]]',fn)
local link = string.format('[[:File:%s|%s]]',fn,fn)
table.insert(contents,{image,link})
end


-- Generate the html for the image and the portal name.
return tostring(fillBox(root,contents))
listroot
:newline()
:tag('li')
:tag('span')
:wikitext(string.format('[[File:%s|32x28px|class=noviewer]]', image))
:done()
:tag('span')
:wikitext(string.format('[[Portal:%s|%s%sportal]]', portal, portal, args['break'] and '<br />' or ' '))
end
return tostring(root)
end
end


function p._image(portals)
function p._image(portal,keep)
-- Wrapper function to allow getImageName() to be accessed through #invoke.
-- Wrapper function to allow getImageName() to be accessed through #invoke.
local name = getImageName(portals[1])
-- backward compatibility: if table passed, take first element
return name:match('^(.-)|') or name -- FIXME: use a more elegant way to separate borders etc. from the image name
if type(portal) == 'table' then
portal = portal[1]
end
local name = getImageName(portal)
-- If keep is yes (or equivalent), then allow all metadata (like image borders) to be returned
local keepargs = yesno(keep)
local args = mw.text.split(name, "|", true)
local result = {args[1]}  -- the filename always comes first
local category = ''
-- parse name, looking for category arguments
for i = 2,#args do
local m = mw.ustring.match(args[i],  "^%s*category%s*=")
if keepargs or m then
table.insert(result, args[i])
end
end
-- reassemble arguments
return table.concat(result,"|")
end
end


local function getAllImageTables()
 
local function getAllImageTable()
-- Returns an array containing all image subpages (minus aliases) as loaded by mw.loadData.
-- Returns an array containing all image subpages (minus aliases) as loaded by mw.loadData.
local images = {}
local images = {}
for i, subpage in ipairs{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'other'} do
for i, subpage in ipairs{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'other'} do
images[i] = mw.loadData('Module:Portal/images/' .. subpage)
local imageTable = mw.loadData('Module:Portal/images/' .. subpage .. sandbox)
for portal, image in pairs(imageTable) do
local args = mw.text.split(image,"|")
images[portal] = args[1] -- just use image filename
end
end
end
return images
return images
Line 239: Line 318:
-- names are capitalized, so the portal links may be broken.
-- names are capitalized, so the portal links may be broken.
local lang = mw.language.getContentLanguage()
local lang = mw.language.getContentLanguage()
local count = 1
portals = portals or {}
for _, imageTable in ipairs(getAllImageTables()) do
for portal in pairs(getAllImageTable()) do
for portal in pairs(imageTable) do
table.insert(portals,lang:ucfirst(portal))
portals[count] = lang:ucfirst(portal)
count = count + 1
end
end
end
table.sort(portals)
args.redlinks = args.redlinks or "yes"
return p._portal(portals, args)
return p._portal(portals, args)
end
end
Line 254: Line 332:
-- should be moved to a portal alias for ease of maintenance.
-- should be moved to a portal alias for ease of maintenance.
local exists, dupes = {}, {}
local exists, dupes = {}, {}
for _, imageTable in ipairs(getAllImageTables()) do
for portal, image in pairs(getAllImageTable()) do
for portal, image in pairs(imageTable) do
if not exists[image] then
if not exists[image] then
exists[image] = portal
exists[image] = portal
else
else
table.insert(dupes, string.format('The image "[[:File:%s|%s]]" is used for both portals "%s" and "%s".', image, image, exists[image], portal))
table.insert(dupes, string.format('The image "[[:File:%s|%s]]" is used for both portals "%s" and "%s".', image, image, exists[image], portal))
end
end
end
end
end
Line 290: Line 366:
end
end
return portals, namedArgs
return portals, namedArgs
end
-- Entry point for sorting portals from other named arguments
function p._processPortalArgs(args)
return processPortalArgs(args)
end
function p.image(frame)
local origArgs = getArgs(frame)
local portals, args = processPortalArgs(origArgs)
return p._image(portals[1],args.border)
end
function p.demo(frame)
local args = getArgs(frame)
local styles = frame:extensionTag{ name = 'templatestyles', args = { src = templatestyles} }
return styles..p._demo(args,args)
end
end


Line 298: Line 391:
-- template, or the args passed to #invoke if any exist. Otherwise
-- template, or the args passed to #invoke if any exist. Otherwise
-- assume args are being passed directly in from the debug console
-- assume args are being passed directly in from the debug console
-- or from another Lua module.
-- or from another Lua module.  
local origArgs
-- Also: trim whitespace and remove blank arguments
if type(frame.getParent) == 'function' then
local origArgs = getArgs(frame)
origArgs = frame:getParent().args
-- create two tables to pass to func: an array of portal names, and a table of named arguments.
for k, v in pairs(frame.args) do
local portals, args = processPortalArgs(origArgs)
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
local results = ''
local results = ''
if funcName == '_portal' or funcName == '_displayAll' then
if funcName == '_portal' or funcName == '_displayAll' then
results = frame:extensionTag{ name = 'templatestyles', args = { src = templatestyles} }
results = frame:extensionTag{ name = 'templatestyles', args = { src = templatestyles} }
end
end
return results .. p[funcName](processPortalArgs(args)) -- passes two tables to func: an array of portal names, and a table of named arguments.
return results .. p[funcName](portals, args)  
end
end
end
end


for _, funcName in ipairs{'portal', 'image', 'imageDupes', 'displayAll'} do
for _, funcName in ipairs{'portal', 'imageDupes', 'displayAll'} do
p[funcName] = makeWrapper('_' .. funcName)
p[funcName] = makeWrapper('_' .. funcName)
end
end


return p
return p