Module:Navbox: Difference between revisions
Jump to navigation
Jump to search
PhantomCaleb (talk | contribs) No edit summary |
PhantomCaleb (talk | contribs) No edit summary |
||
Line 3: | Line 3: | ||
local utilsArg = require("Module:UtilsArg") | local utilsArg = require("Module:UtilsArg") | ||
local utilsPage = require("Module:UtilsPage") | |||
local utilsTable = require("Module:UtilsTable") | local utilsTable = require("Module:UtilsTable") | ||
Line 13: | Line 14: | ||
local DEFAULT_IMG_SIZE = "150x150px" | local DEFAULT_IMG_SIZE = "150x150px" | ||
local MAX_RECOMMENDED_PARTITION_SIZE = require("Module:Constants/number/maxNavboxPartitionSize") | local MAX_RECOMMENDED_PARTITION_SIZE = require("Module:Constants/number/maxNavboxPartitionSize") | ||
function p.Main(frame) | function p.Main(frame) | ||
Line 28: | Line 20: | ||
if not args.title then | if not args.title then | ||
if isManualNavboxPage then | if h.isManualNavboxPage() then | ||
return "", errCategories.."[[Category:"..CATEGORY_NAVBOX_TEMPLATES.."]]" | return "", errCategories.."[[Category:"..CATEGORY_NAVBOX_TEMPLATES.."]]" | ||
elseif isTemplate then | elseif h.isTemplate() then | ||
return "", errCategories | return "", errCategories | ||
else | else | ||
Line 40: | Line 32: | ||
categories = categories..errCategories | categories = categories..errCategories | ||
if isManualNavboxPage then | if h.isManualNavboxPage() then | ||
return navbox, categories.."[[Category:"..CATEGORY_NAVBOX_TEMPLATES.."]]", h.printReport() | return navbox, categories.."[[Category:"..CATEGORY_NAVBOX_TEMPLATES.."]]", h.printReport() | ||
elseif isNavboxPage then | elseif h.isNavboxPage() then | ||
return navbox, categories, h.printReport() | return navbox, categories, h.printReport() | ||
elseif isTemplate then | elseif h.isTemplate() then | ||
return navbox, categories | return navbox, categories | ||
else | else | ||
Line 201: | Line 193: | ||
local display = pipe and string.sub(linkParts, pipe + 1) or page | local display = pipe and string.sub(linkParts, pipe + 1) or page | ||
if page == title.fullText then | if page == mw.title.getCurrentTitle().fullText then | ||
return "<b>"..display.."</b>" | return "<b>"..display.."</b>" | ||
else | else | ||
Line 213: | Line 205: | ||
local VAR_GROUP_SIZES = "navbox_group_size" | local VAR_GROUP_SIZES = "navbox_group_size" | ||
function h.storePage(page) | function h.storePage(page) | ||
if isNavboxPage then | if h.isNavboxPage() then | ||
local utilsVar = require("Module:UtilsVar") | local utilsVar = require("Module:UtilsVar") | ||
utilsVar.add(VAR_PAGES, page) | utilsVar.add(VAR_PAGES, page) | ||
Line 219: | Line 211: | ||
end | end | ||
function h.storeGroupSize(group) | function h.storeGroupSize(group) | ||
if isNavboxPage then | if h.isNavboxPage() then | ||
local utilsVar = require("Module:UtilsVar") | local utilsVar = require("Module:UtilsVar") | ||
utilsVar.add(VAR_GROUP_SIZES, group) | utilsVar.add(VAR_GROUP_SIZES, group) | ||
Line 232: | Line 224: | ||
local utilsLayout = require("Module:UtilsLayout") | local utilsLayout = require("Module:UtilsLayout") | ||
local utilsMarkup = require("Module:UtilsMarkup") | local utilsMarkup = require("Module:UtilsMarkup") | ||
local utilsTable = require("Module:UtilsTable") | local utilsTable = require("Module:UtilsTable") | ||
local utilsVar = require("Module:UtilsVar") | local utilsVar = require("Module:UtilsVar") | ||
Line 240: | Line 231: | ||
local pagesUsingNav = utilsPage.dpl({ | local pagesUsingNav = utilsPage.dpl({ | ||
uses = "Template:"..templatePage, | uses = "Template:"..h.templatePage(), | ||
notnamespace = {"User", "Template", "Category"}, | notnamespace = {"User", "Template", "Category"}, | ||
}) | }) | ||
Line 274: | Line 265: | ||
issues = issues..utilsMarkup.bulletList(missingLinks) | issues = issues..utilsMarkup.bulletList(missingLinks) | ||
end | end | ||
if #missingUses > 0 and isManualNavboxPage then -- missing uses is impossible if it's a category-based navbox and [[Template:Category]] is used everywhere | if #missingUses > 0 and h.isManualNavboxPage() then -- missing uses is impossible if it's a category-based navbox and [[Template:Category]] is used everywhere | ||
issues = issues.."\n====Missing Uses====\n" | issues = issues.."\n====Missing Uses====\n" | ||
missingUses = utilsTable.map(missingUses, utilsMarkup.link) | missingUses = utilsTable.map(missingUses, utilsMarkup.link) | ||
issues = issues.."The above navbox is missing from the following articles. Please add <code><nowiki>{{".. | issues = issues.."The above navbox is missing from the following articles. Please add <code><nowiki>{{"..h.templatePage().."}}</nowiki></code> to these articles to ensure that the navigation does not have dead ends." | ||
issues = issues..utilsMarkup.bulletList(missingUses) | issues = issues..utilsMarkup.bulletList(missingUses) | ||
end | end | ||
Line 323: | Line 314: | ||
end | end | ||
function h.reportRedirects(pagesInNav) | function h.reportRedirects(pagesInNav) | ||
local utilsTable = require("Module:UtilsTable") | local utilsTable = require("Module:UtilsTable") | ||
Line 338: | Line 328: | ||
end | end | ||
function h.reportCategoryMismatches(pagesInNav) | function h.reportCategoryMismatches(pagesInNav) | ||
local utilsTable = require("Module:UtilsTable") | local utilsTable = require("Module:UtilsTable") | ||
if isManualNavboxPage then | if h.isManualNavboxPage() then | ||
return nil, {}, {} | return nil, {}, {} | ||
end | end | ||
Line 347: | Line 336: | ||
uses = "Template:Categories/Navbox", | uses = "Template:Categories/Navbox", | ||
skipthispage = "no", | skipthispage = "no", | ||
titlematch = templatePage, | titlematch = h.templatePage(), | ||
}) | }) | ||
if #usingCategoryNavbox > 0 then | if #usingCategoryNavbox > 0 then | ||
Line 363: | Line 352: | ||
return category, missingPages, extraPages | return category, missingPages, extraPages | ||
end | end | ||
local title = mw.title.getCurrentTitle() | |||
function h.isTemplate() | |||
return title.nsText == "Template" | |||
end | |||
function h.isNavboxPage() | |||
return h.isTemplate() and title.rootText ~= "Navbox" and title.baseText ~= "Categories" and utilsPage.inCategory(CATEGORY_NAVBOX_TEMPLATES, title.fullText) | |||
end | |||
function h.isManualNavboxPage() -- to distinguish manually curated navboxes from automated ones | |||
return h.isNavboxPage() and title.baseText ~= "Categories/Navbox" | |||
end | |||
function h.templatePage() | |||
local templatePage = title.text | |||
if string.find("/Documentation$", templatePage) then | |||
templatePage = title.baseText | |||
end | |||
return templatePage | |||
end | |||
p.Templates = { | p.Templates = { |
Revision as of 16:12, 15 November 2022
This is the main module for the following templates:
local p = {}
local h = {}
local utilsArg = require("Module:UtilsArg")
local utilsPage = require("Module:UtilsPage")
local utilsTable = require("Module:UtilsTable")
local CATEGORY_INVALID_ARGS = "[[Category:"..require("Module:Constants/category/invalidArgs").."]]"
local CATEGORY_NAVBOXES_ATTENTION = "Navigation Templates Needing Attention"
local CATEGORY_NAVBOXES_OTHER = "[[Category:Navboxes with Other]]"
local CATEGORY_NAVBOX_TEMPLATES = "Navbox Templates"
local TITLE_IMG_SIZE = "28x28px"
local DEFAULT_IMG_SIZE = "150x150px"
local MAX_RECOMMENDED_PARTITION_SIZE = require("Module:Constants/number/maxNavboxPartitionSize")
function p.Main(frame)
local args, err = utilsArg.parse(frame:getParent().args, p.Templates.Navbox)
local errCategories = err and err.categoryText or ""
if not args.title then
if h.isManualNavboxPage() then
return "", errCategories.."[[Category:"..CATEGORY_NAVBOX_TEMPLATES.."]]"
elseif h.isTemplate() then
return "", errCategories
else
return ""
end
end
local navbox, categories = h.printNavbox(args)
categories = categories..errCategories
if h.isManualNavboxPage() then
return navbox, categories.."[[Category:"..CATEGORY_NAVBOX_TEMPLATES.."]]", h.printReport()
elseif h.isNavboxPage() then
return navbox, categories, h.printReport()
elseif h.isTemplate() then
return navbox, categories
else
return navbox
end
end
function h.printNavbox(args)
local categories = ""
-- [[MediaWiki:Gadget-Site.js]] automatically removes the "mw-collapsed" when there is only one navbox on the page.
local navbox = mw.html.create("div")
-- MediaWiki (Timeless?) automatically removes elements with class "navbox" on mobile
-- we _want_ to show navboxes on mobile since we've made them mobile friendly, so we use a different class name
:addClass("zw-navbox mw-collapsible mw-collapsed")
if args.type == "category" then
navbox:addClass("zw-navbox--category mw-collapsible mw-collapsed")
end
local id = args.id or args.title
id = id and "navbox-"..string.gsub(string.lower(id), " ", "-") -- to kebab case which is the standard for IDs
if id then
navbox:attr("id", id)
end
local titleImages = args.titleImages or {}
local navboxTitle = mw.html.create("div")
:addClass("zw-navbox__title")
if #titleImages > 0 then
local leftImage = string.format("[[%s|%s|link=]]", titleImages[1], TITLE_IMG_SIZE)
navboxTitle:tag("span")
:addClass("zw-navbox__title-image")
:wikitext(leftImage)
end
navboxTitle:tag("span")
:addClass("zw-navbox__title-text")
:wikitext(args.title)
if #titleImages > 0 then
local rightImage = string.format("[[%s|%s|link=]]", titleImages[2] or titleImages[1], TITLE_IMG_SIZE)
navboxTitle:tag("span")
:addClass("zw-navbox__title-image")
:wikitext(rightImage)
end
local navboxContent = navbox
:tag("div")
:addClass("zw-navbox__header mw-collapsible-toggle")
:tag("span")
-- Used to center the heading - see Template:Navbox/Styles.css
:addClass("zw-navbox__toggle-button-counterbalance")
:done()
:tag("span")
:addClass("zw-navbox__title")
:node(navboxTitle)
:done()
:tag("span")
:addClass("zw-navbox__toggle-button")
:tag("span")
:addClass("zw-navbox__toggle-button-text mw-collapsible-text")
:wikitext("hide ▲")
:done()
:done()
:done()
:done()
:tag("div")
:addClass("zw-navbox__content mw-collapsible-content")
local body = navboxContent:tag("div")
:addClass("zw-navbox__body")
local rows = body:tag("div")
:addClass("zw-navbox__rows")
for i, row in ipairs(args.rows) do
if row.group == "Other" or row.group == "Miscellaneous" then
categories = categories..CATEGORY_NAVBOXES_OTHER
end
h.storeGroupSize({
name = row.group or "Row "..i,
size = #(row.links or {}),
maxGroupSize = row.maxGroupSize or MAX_RECOMMENDED_PARTITION_SIZE
})
if row.group then
rows:tag("div")
:addClass("zw-navbox__row-header")
:tag("span")
:addClass("zw-navbox__row-header-text")
:wikitext(row.group)
:done()
:done()
elseif i ~= 1 or #args.rows > 1 then
local utilsError = require("Module:UtilsError")
utilsError.warn(string.format("<code>group%d</code> parameter is required when there is more than one group.", i))
categories = categories..CATEGORY_INVALID_ARGS
end
local links = {}
for j, link in ipairs(row.links or {}) do
link = h.link(link)
table.insert(links, '<span class="zw-navbox__link">'..link..'</span>')
end
local evenOdd = (i % 2 == 0) and "even" or "odd"
local rowModifiers = " zw-navbox__row-links--"..evenOdd
local groups = utilsTable.filter(args.rows, "group")
if #groups == 0 then
rowModifiers = rowModifiers.." zw-navbox__row-links--nogroups"
end
local links = table.concat(links, " • ")
rows:tag("div")
:addClass("zw-navbox__row-links"..rowModifiers)
:tag("div")
:addClass("zw-navbox__row-links-content")
:wikitext(links)
:done()
:done()
end
if args.image then
local filename = args.image
if not string.find(filename, "^File:") then
filename = "File:"..filename
end
local thumbnail = string.format("[[%s|%s]]", filename, DEFAULT_IMG_SIZE)
body:tag("div")
:addClass("zw-navbox__image")
:wikitext(thumbnail)
end
if args.footer then
navboxContent:tag("div")
:addClass("zw-navbox__footer")
:wikitext(args.footer)
end
local result = tostring(navboxContent:allDone())
return result, categories
end
-- Turns links like [[Link]] into <span class="plainlinks">[https://zeldawiki.wiki/wiki/Link Link]</span>
-- Prevents navbox entries from appearing in the Special:WhatLinksHere of every other navbox entry
-- Copied from Module:UtilsMarkup/Link for job queue optimization, plus some modifications
function h.link(links)
return string.gsub(links, "%[%[[^%]]+%]%]", h._link)
end
function h._link(link)
if string.find(link, "^%[%[Category:") then
return link
end
local linkParts = string.gsub(link, "^%[%[:?", "")
linkParts = string.gsub(linkParts, "%]%]$", "")
local pipe = string.find(linkParts, "|")
local page = string.sub(linkParts, 1, pipe and pipe - 1)
page = string.gsub(page, ",", ",") -- unescape any commas that were escaped in input due to splitting by ,
page = string.gsub(page, "#.*", "")
h.storePage(page)
local display = pipe and string.sub(linkParts, pipe + 1) or page
if page == mw.title.getCurrentTitle().fullText then
return "<b>"..display.."</b>"
else
local url = mw.site.server.."/"..mw.uri.encode(page, "WIKI")
return string.format('<span class="plainlinks">[%s %s]</span>', url, display)
end
end
-- Store data for the report function below
local VAR_PAGES = "navbox_pages"
local VAR_GROUP_SIZES = "navbox_group_size"
function h.storePage(page)
if h.isNavboxPage() then
local utilsVar = require("Module:UtilsVar")
utilsVar.add(VAR_PAGES, page)
end
end
function h.storeGroupSize(group)
if h.isNavboxPage() then
local utilsVar = require("Module:UtilsVar")
utilsVar.add(VAR_GROUP_SIZES, group)
end
end
-- Ensures that every page linked in the navbox uses that navbox
-- and that every page that uses the navbox is linked in the navbox
-- so that the navigation is bidirectional and doesn't have "dead ends"
function h.printReport()
local utilsError = require("Module:UtilsError")
local utilsLayout = require("Module:UtilsLayout")
local utilsMarkup = require("Module:UtilsMarkup")
local utilsTable = require("Module:UtilsTable")
local utilsVar = require("Module:UtilsVar")
local pagesInNav = utilsVar.get(VAR_PAGES) or {}
local groupSizes = utilsVar.get(VAR_GROUP_SIZES) or {}
local pagesUsingNav = utilsPage.dpl({
uses = "Template:"..h.templatePage(),
notnamespace = {"User", "Template", "Category"},
})
local missingLinks = utilsTable.difference(pagesUsingNav, pagesInNav)
local missingUses = utilsTable.difference(pagesInNav, pagesUsingNav)
local wantedPages = utilsTable.filter(pagesInNav, function(page)
return not utilsPage.exists(page)
end)
local redirects = h.reportRedirects(pagesInNav)
local bigGroups = utilsTable.filter(groupSizes, function(group)
return group.size > group.maxGroupSize
end)
local categoryName, missingPages, extraPages = h.reportCategoryMismatches(pagesInNav)
local issues = ""
if #wantedPages > 0 then
issues = issues.."\n====Red Links====\n"
wantedPages = utilsTable.map(wantedPages, utilsMarkup.link)
issues = issues.."The above navbox contains links to pages which do not exist. Please create these pages or remove them from the navbox:"
issues = issues..utilsMarkup.bulletList(wantedPages)
end
if #redirects > 0 then
issues = issues.."\n====Redirect Links====\n"
redirects = utilsTable.map(redirects, utilsMarkup.link)
issues = issues.."The above navbox contains links to redirects. Please update these links to refer to the redirect targets:"
issues = issues..utilsMarkup.bulletList(redirects)
end
if #missingLinks > 0 then
issues = issues.."\n====Missing Links====\n"
missingLinks = utilsTable.map(missingLinks, utilsMarkup.link)
issues = issues.."<p>The above navbox is missing the following links to articles that use it. Please add these articles to the navbox to ensure that the navigation does not have dead ends.</p>"
issues = issues..utilsMarkup.bulletList(missingLinks)
end
if #missingUses > 0 and h.isManualNavboxPage() then -- missing uses is impossible if it's a category-based navbox and [[Template:Category]] is used everywhere
issues = issues.."\n====Missing Uses====\n"
missingUses = utilsTable.map(missingUses, utilsMarkup.link)
issues = issues.."The above navbox is missing from the following articles. Please add <code><nowiki>{{"..h.templatePage().."}}</nowiki></code> to these articles to ensure that the navigation does not have dead ends."
issues = issues..utilsMarkup.bulletList(missingUses)
end
if #bigGroups > 0 then
issues = issues.."\n====Big Groups====\n"
local dataRows = utilsTable.map(bigGroups, function(group)
return {group.name, group.size, group.maxGroupSize}
end)
bigGroups = utilsLayout.table({
headers = {"Row", "Size", "Max Size"},
rows = dataRows,
})
issues = issues.."The following navbox rows exceed their recommended maximum size. Please subdivide these rows if possible."
issues = issues..bigGroups
issues = issues.."If you are certain that a row should not be subdivided despite its size, you can increase the <code>maxGroupSize</code> of the row. See [[Template:Categories/Navbox]] for more information."
end
if #extraPages > 0 or #missingPages > 0 then
issues = issues.."\n====Category Mismatch====\n"
if #missingPages > 0 then
missingPages = utilsTable.map(missingPages, utilsMarkup.link)
issues = issues
.."This navbox is missing entries from [[:Category:"..categoryName.."]]:"
..utilsMarkup.bulletList(missingPages)
end
if #extraPages > 0 then
extraPages = utilsTable.map(extraPages, utilsMarkup.link)
issues = issues
.."This navbox contains links to pages that are not in [[:Category:"..categoryName.."]]:"
..utilsMarkup.bulletList(extraPages)
end
issues = issues.."Usually {{Template|Categories/Navbox}} should be used instead of {{Template|Navbox}} for category-based navboxes."
end
local report = "\n==Report==\n"
if issues ~= "" then
report = report
.."\n===Issues===\n"
..issues
.."[[Category:"..CATEGORY_NAVBOXES_ATTENTION.."]]"
else
report = report.."<p>[[File:TFH Green Link ok.png|32px|link=]] No issues have been detected in this navbox.</p>"
end
return mw.getCurrentFrame():preprocess(report)
end
function h.reportRedirects(pagesInNav)
local utilsTable = require("Module:UtilsTable")
return utilsTable.filter(pagesInNav, function(page)
-- mw.title is more reliable than utilsPage for checking redirects
-- but mw.title uses an expensive parser function while utilsPage does not
-- we cannot "afford" the expensive parser function when there are too many pages in the nav
if #pagesInNav > 90 then
return utilsPage.isRedirect(page)
else
return mw.title.new(page).isRedirect
end
end)
end
function h.reportCategoryMismatches(pagesInNav)
local utilsTable = require("Module:UtilsTable")
if h.isManualNavboxPage() then
return nil, {}, {}
end
local usingCategoryNavbox = utilsPage.dpl({
uses = "Template:Categories/Navbox",
skipthispage = "no",
titlematch = h.templatePage(),
})
if #usingCategoryNavbox > 0 then
return nil, {}, {}
end
local category = title.subpageText
if title.subpageText == "Documentation" then
category = title.basePageTitle.subpageText
end
local pagesInCategory = utilsPage.dpl({ category = category, namespace = "" })
local missingPages = utilsTable.difference(pagesInCategory, pagesInNav)
local extraPages = utilsTable.difference(pagesInNav, pagesInCategory)
return category, missingPages, extraPages
end
local title = mw.title.getCurrentTitle()
function h.isTemplate()
return title.nsText == "Template"
end
function h.isNavboxPage()
return h.isTemplate() and title.rootText ~= "Navbox" and title.baseText ~= "Categories" and utilsPage.inCategory(CATEGORY_NAVBOX_TEMPLATES, title.fullText)
end
function h.isManualNavboxPage() -- to distinguish manually curated navboxes from automated ones
return h.isNavboxPage() and title.baseText ~= "Categories/Navbox"
end
function h.templatePage()
local templatePage = title.text
if string.find("/Documentation$", templatePage) then
templatePage = title.baseText
end
return templatePage
end
p.Templates = {
["Navbox"] = {
format = "block",
purpose = "Creates [[:Category:"..CATEGORY_NAVBOX_TEMPLATES.."|navbox templates]].",
categories = {"Metatemplates"},
paramOrder = {"id", "title", "titleImages", "image", "group", "links", "maxGroupSize", "footer", "type"},
repeatedGroup = {
name = "rows",
params = {"group", "links", "maxGroupSize"},
counts = {2, 3, 4, 5, 6, 7},
},
boilerplate = {
separateRequiredParams = false,
hiddenParams = {"maxGroupSize", "type"},
},
params = {
id = {
type = "string",
desc = "A unique ID for the navbox. Sets the [https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/id id HTML attribute] so that the navbox can be linked to. Defaults to the navbox title.",
trim = true,
nilIfEmpty = true,
},
title = {
required = true,
type = "content",
desc = "<p>The navbox title.</p><p>It is recommended not to place links in the title as this can create confusion between the clickable navbox header and the link within it. Category links should be placed in the footer.</p>",
trim = true,
nilIfEmpty = true,
},
titleImages = {
type = "string",
desc = "Two file names with the <code>File:</code> prefix, separated by a comma. Renders image to the left and right of the navbox title.",
trim = true,
nilIfEmpty = true,
split = true,
},
image = {
type = "wiki-file-name",
desc = "A file name, with the <code>File:</code> prefix. Renders an image in the navbox body.",
trim = true,
nilIfEmpty = true,
},
group = {
type = "string",
desc = "A header for the given row in the navbox. Required if there is more than one row.",
trim = true,
nilIfEmpty = true,
},
links = {
type = "content",
required = true,
desc = "A comma-separated list of links for the given row.",
trim = true,
nilIfEmpty = true,
split = true,
},
maxGroupSize = {
type = "number",
nilIfEmpty = true,
default = MAX_RECOMMENDED_PARTITION_SIZE,
desc = "Adds the template to [[:Category:"..CATEGORY_NAVBOXES_ATTENTION.."]] if the number of links in the group exceeds this number. In most cases this value should not be set higher than its default; navboxes with too many links per group can be difficult to read.",
},
footer = {
type = "content",
desc = "The navbox footer. Usually contains links to relevant categories.",
trim = true,
nilIfEmpty = true,
},
type = {
type = "string",
desc = "Internal parameter used by [[Template:Categories]].",
trim = true,
},
},
},
}
return p