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
* - Configurable field names and styling
]]
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 →",
classPrefix = nil,
rowHeight = "40"
}
-- Cache for navigation detection results
local navigationCache = {}
-- ========== Navigation Detection ==========
-- Detect navigation links based on a list of patterns
-- @param pageName string The current page name
-- @param patterns table A list of patterns to try
-- @return table Navigation result with prev and next properties, or nil
function p.detectNavigation(pageName, patterns)
local cacheKey = pageName
if navigationCache[cacheKey] ~= nil then
return navigationCache[cacheKey]
end
local result = nil
for _, pattern in ipairs(patterns) do
local series, numberStr = pageName:match(pattern)
if series and numberStr then
local number = tonumber(numberStr)
if number then
local prevName = (number > 1) and string.format("%s %d", series, number - 1) or nil
local nextName = string.format("%s %d", series, number + 1)
local prevPage = prevName and mw.title.new(prevName)
local nextPage = mw.title.new(nextName)
result = {
prev = (prevPage and prevPage.exists) and prevName or nil,
next = (nextPage and nextPage.exists) and nextName or nil,
}
break -- Stop after the first successful match
end
end
end
navigationCache[cacheKey] = result
return result
end
-- ========== Blueprint Integration ==========
-- Helper function to merge two tables. The custom table's values override the base table's.
local function mergeConfigs(base, custom)
local merged = {}
for k, v in pairs(base) do
merged[k] = v
end
for k, v in pairs(custom) do
merged[k] = v
end
return merged
end
-- 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()
-- Merge default and template-specific configurations
local config = mergeConfigs(p.defaultConfig, template.config.navigation or {})
-- Automatic navigation detection
local autoNavigation
if config.autoDetect then
local pageName = mw.title.getCurrentTitle().text
-- The patterns are now expected to be an indexed table for ipairs
local patterns = {config.patterns.seriesNumber, config.patterns.seriesYear}
autoNavigation = p.detectNavigation(pageName, patterns) or {}
else
autoNavigation = {}
end
-- Determine final previous and next page links
local prevPage = args[config.prevField] or autoNavigation.prev
local nextPage = args[config.nextField] or autoNavigation.next
-- Exit if no navigation links are found
if not prevPage and not nextPage then
return ""
end
-- Helper to create a navigation link
local function createNavLink(page, label, class)
if not page then return '' end
local linkLabel = label:find("%%s") and string.format(label, page) or label
return string.format('<div class="%s">[[%s|%s]]</div>', class, page, linkLabel)
end
-- Build the navigation container
local output = {
'|-',
string.format('| colspan="2" height="%s" valign="middle" |', config.rowHeight),
'<div class="element-navigation-container">'
}
local prevLink = createNavLink(prevPage, config.prevLabel, config.prevClass)
local nextLink = createNavLink(nextPage, config.nextLabel, config.nextClass)
table.insert(output, prevLink)
table.insert(output, nextLink)
table.insert(output, '</div>')
return table.concat(output, '\n')
end)
end
end
return p