Module:Navbox

From Zelda Wiki, the Zelda encyclopedia
Revision as of 05:11, 10 November 2022 by PhantomCaleb (talk | contribs)
This is the main module for the following templates:
local p = {}
local h = {}

local utilsArg = require("Module:UtilsArg")

local CATEGORY_INVALID_ARGS = "[[Category:"..require("Module:Constants/category/invalidArgs").."]]"
local CLASS_PIXEL_ART = require("Module:Constants/class/pixelArt")
local DEFAULT_IMG_SIZE = "150x150px"
local MAX_RECOMMENDED_GROUP_SIZE = 16

local title = mw.title.getCurrentTitle()
local isTemplate = title.nsText == "Template"
local isNavboxPage = isTemplate and title.rootText ~= "Navbox" and title.baseText ~= "Categories" and title.text ~= "Categories/Navbox/Documentation"
local isManualNavboxPage = isNavboxPage and title.baseText ~= "Categories/Navbox" -- to distinguish manually curated navboxes from automated ones

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 isManualNavboxPage then
			return "", errCategories.."[[Category:Navbox Templates]]"
		elseif isTemplate then
			return "", errCategories
		else
			return ""
		end
	end
	
	local navbox, categories = h.printNavbox(args)
	categories = categories..errCategories

	if isManualNavboxPage then
		return navbox, categories.."[[Category:Navbox Templates]]", h.printReport()
	elseif isNavboxPage then
		return navbox, categories, h.printReport()
	elseif 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")
	
	local id = args.id and "navbox-"..string.gsub(string.lower(args.id), " ", "-") -- to kebab case which is the standard for IDs
	if id then
		navbox:attr("id", id)
	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")
				:wikitext(args.title)
				: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
		h.storeGroupSize(row.group or "Row "..i, #(row.links or {}))
		if row.group then
			rows:tag("div")
					:addClass("zw-navbox__row-header")
					:wikitext(row.group)
					: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.killBacklinks(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
		if #args.rows == 1 and not args.rows[1].group then
			rowModifiers = rowModifiers.." zw-navbox__row-links--nogroups"
		end
		
		local links = table.concat(links, "&nbsp;• ")
		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 "..CLASS_PIXEL_ART)
			: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.killBacklinks(links)
	return string.gsub(links, "%[%[[^%]]+%]%]", h.killBacklink)
end
function h.killBacklink(link)
	local linkParts = string.gsub(link, "^%[%[:?", "")
	linkParts = string.gsub(linkParts, "%]%]$", "")
	local pipe = string.find(linkParts, "|")
	local page = string.sub(linkParts, 1, pipe and pipe - 1)
	h.storePage(page)
	local display = pipe and string.sub(linkParts, pipe + 1)
	
	local url = mw.site.server.."/"..mw.uri.encode(page, "WIKI")
	return string.format('<span class="plainlinks">[%s %s]</span>', url, display or page)
end

-- Store data for the report function below
local VAR_PAGES = "navbox_pages"
local VAR_GROUP_SIZES = "navbox_group_size"
function h.storePage(page)
	page = string.gsub(page, "#.*", "")
	if isNavboxPage then
		local utilsVar = require("Module:UtilsVar")
		utilsVar.add(VAR_PAGES, page)
	end
end
function h.storeGroupSize(group, size)
	if isNavboxPage then
		local utilsVar = require("Module:UtilsVar")
		utilsVar.add(VAR_GROUP_SIZES, { name = group, size = size})
	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 utilsMarkup = require("Module:UtilsMarkup")
	local utilsPage = require("Module:UtilsPage")
	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 templatePage = title.text
	if string.find("/Documentation$", templatePage) then
		templatePage = title.baseText
	end
	local pagesUsingNav = utilsPage.dpl({
		uses = "Template:"..templatePage,
		notnamespace = "User"
	})
	local usingCategoryNavbox = utilsPage.dpl({
		uses = "Template:Categories/Navbox",
		skipthispage = "no",
		titlematch = templatePage,
	})
	local isUsingCategoryNavbox = #usingCategoryNavbox > 0

	local missingPages = 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 = utilsTable.filter(pagesInNav, function(page)
		return mw.title.new(page).isRedirect
	end)
	local bigGroups = utilsTable.filter(groupSizes, function(group)
		return group.size > MAX_RECOMMENDED_GROUP_SIZE
	end)
	
	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 #missingPages > 0 then
		issues = issues.."\n====Missing Links====\n"
		missingPages = utilsTable.map(missingPages, 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(missingPages)
	end
	if #missingUses > 0 and isManualNavboxPage then -- missing uses is impossible for navboxes added by [[Template:Category]]
		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>{{"..title.baseText.."}}</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"
		bigGroups = utilsTable.map(bigGroups, function(group)
			return string.format("<li>%s: %d pages</li>", group.name, group.size)
		end)
		bigGroups = "<ul>"..table.concat(bigGroups, "").."</ul>"
		issues = issues..string.format("The above navbox has rows exceeding the recommended maximum size of %d entries. Please subdivide these rows if possible.", MAX_RECOMMENDED_GROUP_SIZE)
		issues = issues..bigGroups
	end
	if not isManualNavboxPage and not isUsingCategoryNavbox then
		issues = issues.."\n====[[Template:Categories/Navbox]] Required====\n"
		issues = issues.."<p>This template does not appear to be using {{Template|Categories/Navbox}}. Please use this template to ensure that all pages in the category are represented in the navbox.</p><p>If the correct template is in fact being used, purge the cache to remove this from the report.</p>"
	end
	
	
	local report = "\n==Report==\n"
	if issues ~= "" then
		report = report
		.."\n===Issues===\n"
		..issues
		.."[[Category:Navigation Templates Needing 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

p.Templates = {
	["Navbox"] = {
		format = "block",
		purpose = "Creates [[:Category:Navbox Templates|navbox templates]].",
		categories = {"Metatemplates"},
		boilerplate = {
			separateRequiredParams = false,
		},
		paramOrder = {"id", "title", "image", "group", "links", "footer"},
		repeatedGroup = {
			name = "rows",
			params = {"group", "links"},
			counts = {2, 3, 4, 5, 6, 7},
		},
		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.",
				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,
			},
			image = {
				type = "wiki-file-name",
				desc = "A file name, with the <code>File:</code> prefix.",
				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,
			},
			footer = {
				type = "content",
				desc = "The navbox footer. Usually contains links to relevant categories.",
				trim = true,
				nilIfEmpty = true,
			},
		},
	},
}

return p