|
|
(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 |