Module:ElementNavigation
Appearance
Documentation for this module may be created at Module:ElementNavigation/doc
--[[
* Module:ElementNavigation
* Provides navigation detection and rendering for sequential content
*
* This module detects previous/next pages based on naming patterns
* and renders navigation links. It's designed to be used as a block
* in the Blueprint template system.
*
* Features:
* - Automatic detection of previous/next pages based on patterns
* - Support for both series+number and series+year patterns
* - Caching of detection results for performance
* - Configurable field names and styling
* - Blueprint integration as a custom block
]]
local p = {}
p.elementName = "navigation"
p.defaultConfig = {
autoDetect = true,
patterns = {
seriesNumber = "([^%d]+)%s+(%d+)$",
seriesYear = "([^%d]+)%s+(%d%d%d%d)$"
},
prevField = "has_previous",
nextField = "has_next",
prevClass = "element-navigation-prev",
nextClass = "element-navigation-next",
prevLabel = "← Previous",
nextLabel = "Next →",
rowHeight = "40"
}
-- Cache for navigation detection results
local navigationCache = {}
-- ========== Navigation Detection ==========
-- Detect navigation links based on patterns
-- @param pageName string The current page name
-- @param patterns table Table of patterns to use
-- @return table Navigation result with prev and next properties
function p.detectNavigation(pageName, patterns)
-- Check cache first
local cacheKey = pageName
if navigationCache[cacheKey] ~= nil then
return navigationCache[cacheKey]
end
local result = nil
-- Try Series + Number pattern: "ICANN 76"
local series, number = pageName:match(patterns.seriesNumber or "([^%d]+)%s+(%d+)$")
if series and number then
number = tonumber(number)
local prev = (number > 1) and string.format("%s %d", series, number - 1) or nil
local next = string.format("%s %d", series, number + 1)
-- Check if pages exist
local prevPage = prev and mw.title.new(prev) or nil
local nextPage = mw.title.new(next)
result = {
prev = prevPage and prevPage.exists and prev or nil,
next = nextPage and nextPage.exists and next or nil
}
end
-- If no result yet, try Series + Year pattern: "IGF 2023"
if not result then
local series, year = pageName:match(patterns.seriesYear or "([^%d]+)%s+(%d%d%d%d)$")
if series and year then
year = tonumber(year)
local prev = string.format("%s %d", series, year - 1)
local next = string.format("%s %d", series, year + 1)
-- Check if pages exist
local prevPage = mw.title.new(prev)
local nextPage = mw.title.new(next)
result = {
prev = prevPage and prevPage.exists and prev or nil,
next = nextPage and nextPage.exists and next or nil
}
end
end
-- Store in cache (including nil results)
navigationCache[cacheKey] = result
return result
end
-- ========== Blueprint Integration ==========
-- Create a navigation block for Blueprint
-- @return function Block rendering function for Blueprint
function p.createBlock()
return function(template, args)
-- Get error context from template if available
local errorContext = template._errorContext
-- Protected execution if error context is available
local execute = function(func, ...)
if errorContext and errorContext.protect then
return errorContext.protect(
errorContext,
"NavigationBlock",
func,
"",
...
)
else
return func(...)
end
end
return execute(function()
-- Get configuration from template
local config = template.config.navigation or {}
-- Get current page name
local pageName = mw.title.getCurrentTitle().text
-- Detect navigation
local autoNavigation = nil
if config.autoDetect ~= false then
autoNavigation = p.detectNavigation(pageName, config.patterns or {})
end
-- Get field names
local prevField = config.prevField or "has_previous"
local nextField = config.nextField or "has_next"
-- Check for user-provided navigation values
local hasPrev = args[prevField]
local hasNext = args[nextField]
-- If no navigation is provided at all, return empty string
if (not hasPrev or hasPrev == "") and (not hasNext or hasNext == "") and not autoNavigation then
return ""
end
-- Determine previous and next pages
local prevPage = nil
local nextPage = nil
-- Process previous page
if hasPrev and hasPrev ~= "" then
if hasPrev ~= "yes" and hasPrev ~= "true" then
prevPage = hasPrev
elseif autoNavigation and autoNavigation.prev then
prevPage = autoNavigation.prev
end
elseif autoNavigation and autoNavigation.prev then
prevPage = autoNavigation.prev
end
-- Process next page
if hasNext and hasNext ~= "" then
if hasNext ~= "yes" and hasNext ~= "true" then
nextPage = hasNext
elseif autoNavigation and autoNavigation.next then
nextPage = autoNavigation.next
end
elseif autoNavigation and autoNavigation.next then
nextPage = autoNavigation.next
end
-- If no actual navigation links were found, return empty string
if not prevPage and not nextPage then
return ""
end
-- Get styling options
local prevClass = config.prevClass or "element-navigation-prev"
local nextClass = config.nextClass or "element-navigation-next"
local rawPrevLabel = config.prevLabel or "← Previous"
local rawNextLabel = config.nextLabel or "Next →"
local rowHeight = config.rowHeight or "40"
-- Compute dynamic labels based on page names
local prevLabel = ""
if prevPage then
if rawPrevLabel:find("%%s") then
prevLabel = string.format(rawPrevLabel, prevPage)
else
prevLabel = rawPrevLabel
end
end
local nextLabel = ""
if nextPage then
if rawNextLabel:find("%%s") then
nextLabel = string.format(rawNextLabel, nextPage)
else
nextLabel = rawNextLabel
end
end
-- Create navigation row
local output = {
'|-',
'| colspan="2" height="' .. rowHeight .. '" valign="middle" |'
}
-- Add previous link
if prevPage then
table.insert(output, string.format(
'<div class="element-navigation-prev">[[%s|%s]]</div>',
prevPage, prevLabel
))
else
table.insert(output, " ")
end
-- Add next link cell
-- Removed separate next cell; using single colspan cell
if nextPage then
table.insert(output, string.format(
'<div class="element-navigation-next">[[%s|%s]]</div>',
nextPage, nextLabel
))
else
table.insert(output, " ")
end
return table.concat(output, "\n")
end)
end
end
return p