Jump to content

Module:ElementNavigation: Difference between revisions

// via Wikitext Extension for VSCode
// via Wikitext Extension for VSCode
 
(3 intermediate revisions by the same user not shown)
Line 1: Line 1:
--[[
--[[
* Module:ElementNavigation
* Name: ElementNavigation
* Provides navigation detection and rendering for sequential content
* Author: Mark W. Datysgeld
*
* Description: Element module that provides navigation detection and rendering for sequential content in Blueprint templates
* This module detects previous/next pages based on naming patterns
* Notes: Automatic detection of previous/next pages based on naming patterns; supports series+number and series+year patterns; configurable field names and styling; designed as Blueprint template system block
* 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
]]
]]


Line 39: Line 30:
-- ========== Navigation Detection ==========
-- ========== Navigation Detection ==========


-- Detect navigation links based on patterns
-- Detect navigation links based on a list of patterns
-- @param pageName string The current page name
-- @param pageName string The current page name
-- @param patterns table Table of patterns to use
-- @param patterns table A list of patterns to try
-- @return table Navigation result with prev and next properties
-- @return table Navigation result with prev and next properties, or nil
function p.detectNavigation(pageName, patterns)
function p.detectNavigation(pageName, patterns)
    -- Check cache first
     local cacheKey = pageName
     local cacheKey = pageName
     if navigationCache[cacheKey] ~= nil then
     if navigationCache[cacheKey] ~= nil then
         return navigationCache[cacheKey]
         return navigationCache[cacheKey]
     end
     end
   
 
     local result = nil
     local result = nil
      
     for _, pattern in ipairs(patterns) do
    -- Try Series + Number pattern: "ICANN 76"
        local series, numberStr = pageName:match(pattern)
    local series, number = pageName:match(patterns.seriesNumber or "([^%d]+)%s+(%d+)$")
        if series and numberStr then
    if series and number then
            local number = tonumber(numberStr)
        number = tonumber(number)
            if number then
        local prev = (number > 1) and string.format("%s %d", series, number - 1) or nil
                local prevName = (number > 1) and string.format("%s %d", series, number - 1) or nil
        local next = string.format("%s %d", series, number + 1)
                local nextName = string.format("%s %d", series, number + 1)
       
 
        -- Check if pages exist
                local prevPage = prevName and mw.title.new(prevName)
        local prevPage = prev and mw.title.new(prev) or nil
                local nextPage = mw.title.new(nextName)
        local nextPage = mw.title.new(next)
 
       
                result = {
        result = {
                    prev = (prevPage and prevPage.exists) and prevName or nil,
            prev = prevPage and prevPage.exists and prev or nil,
                    next = (nextPage and nextPage.exists) and nextName or nil,
            next = nextPage and nextPage.exists and next or nil
                }
        }
                break -- Stop after the first successful match
    end
             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
     end
     end
   
 
    -- Store in cache (including nil results)
     navigationCache[cacheKey] = result
     navigationCache[cacheKey] = result
     return result
     return result
Line 94: Line 66:


-- ========== Blueprint Integration ==========
-- ========== 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
-- Create a navigation block for Blueprint
Line 118: Line 102:
          
          
         return execute(function()
         return execute(function()
             -- Get configuration from template
             -- Merge default and template-specific configurations
             local config = template.config.navigation or {}
             local config = mergeConfigs(p.defaultConfig, template.config.navigation or {})
           
 
             -- Get current page name
             -- Automatic navigation detection
             local pageName = mw.title.getCurrentTitle().text
             local autoNavigation
           
            if config.autoDetect then
            -- Detect navigation
                local pageName = mw.title.getCurrentTitle().text
            local autoNavigation = nil
                -- The patterns are now expected to be an indexed table for ipairs
            if config.autoDetect ~= false then
                local patterns = {config.patterns.seriesNumber, config.patterns.seriesYear}
                 autoNavigation = p.detectNavigation(pageName, config.patterns or {})
                 autoNavigation = p.detectNavigation(pageName, patterns) or {}
            else
                autoNavigation = {}
             end
             end
           
 
             -- Get field names
             -- Determine final previous and next page links
             local prevField = config.prevField or "has_previous"
             local prevPage = args[config.prevField] or autoNavigation.prev
            local nextField = config.nextField or "has_next"
             local nextPage = args[config.nextField] or autoNavigation.next
           
 
            -- Check for user-provided navigation values
             -- Exit if no navigation links are found
             local hasPrev = args[prevField]
             if not prevPage and not nextPage then
            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 ""
                 return ""
             end
             end
           
 
             -- Determine previous and next pages
             -- Helper to create a navigation link
            local prevPage = nil
             local function createNavLink(page, label, class)
             local nextPage = nil
                if not page then return '' end
           
                 local linkLabel = label:find("%%s") and string.format(label, page) or label
            -- Process previous page
                 return string.format('<div class="%s">[[%s|%s]]</div>', class, page, linkLabel)
            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
             end
           
 
            -- Process next page
             -- Build the navigation container
            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 derivedPrevClass, derivedNextClass
if config.classPrefix then
    derivedPrevClass = config.classPrefix .. "-nav-prev"
    derivedNextClass = config.classPrefix .. "-nav-next"
end
local prevClass = config.prevClass or derivedPrevClass or p.defaultConfig.prevClass
local nextClass = config.nextClass or derivedNextClass or p.defaultConfig.nextClass
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 = {
             local output = {
                 '|-',
                 '|-',
                 '| colspan="2" height="' .. rowHeight .. '" valign="middle" |'
                 string.format('| colspan="2" height="%s" valign="middle" |', config.rowHeight),
                '<div class="element-navigation-container">'
             }
             }
              
              
             table.insert(output, '<div class="element-navigation-container">')
             local prevLink = createNavLink(prevPage, config.prevLabel, config.prevClass)
-- Add previous link
             local nextLink = createNavLink(nextPage, config.nextLabel, config.nextClass)
             if prevPage then
 
                -- Use "class=" parameter in MediaWiki link syntax for proper styling
             table.insert(output, prevLink)
                table.insert(output, string.format(
             table.insert(output, nextLink)
                    '<div class="element-navigation-prev">[[%s|%s]]</div>',
                    prevPage, prevLabel
                ))
            else
                table.insert(output, "&nbsp;")
            end
           
             -- Add next link cell
            -- Removed separate next cell; using single colspan cell
           
            if nextPage then
                -- Use "class=" parameter in MediaWiki link syntax for proper styling
                table.insert(output, string.format(
                    '<div class="element-navigation-next">[[%s|%s]]</div>',
                    nextPage, nextLabel
                ))
             else
                table.insert(output, "&nbsp;")
            end
           
             table.insert(output, '</div>')
             table.insert(output, '</div>')
             return table.concat(output, "\n")
 
             return table.concat(output, '\n')
         end)
         end)
     end
     end

Latest revision as of 02:58, 25 August 2025

Documentation for this module may be created at Module:ElementNavigation/doc

--[[
* Name: ElementNavigation
* Author: Mark W. Datysgeld
* Description: Element module that provides navigation detection and rendering for sequential content in Blueprint templates
* Notes: Automatic detection of previous/next pages based on naming patterns; supports series+number and series+year patterns; configurable field names and styling; designed as Blueprint template system 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 →",
    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