Module:Hatnote
Jump to navigation
Jump to search
Documentation for this module may be created at Module:Hatnote/doc
--------------------------------------------------------------------------------
-- Module:Hatnote
-- Unified hatnote system for disambiguation, navigation, and cross-references.
--
-- INSTALLATION:
-- 1. Create page: Module:Hatnote
-- 2. Paste this entire file
-- 3. Create TemplateStyles page: Module:Hatnote/styles.css
--
-- USAGE:
-- {{#invoke:Hatnote|hatnote|Custom text here}}
-- {{#invoke:Hatnote|about|topic|other topic|Other article}}
-- {{#invoke:Hatnote|main|Article name}}
-- {{#invoke:Hatnote|seealso|Article 1|Article 2}}
-- {{#invoke:Hatnote|further|Article name}}
-- {{#invoke:Hatnote|redirect|Term|description|Article}}
-- {{#invoke:Hatnote|distinguish|Article 1|Article 2}}
--
-- @author Remilia Wiki
-- @license MIT
--------------------------------------------------------------------------------
local p = {}
--------------------------------------------------------------------------------
-- CONFIGURATION
--------------------------------------------------------------------------------
-- Maximum number of links to support in multi-link templates
local MAX_LINKS = 10
--------------------------------------------------------------------------------
-- UTILITY FUNCTIONS
--------------------------------------------------------------------------------
local function trim(s)
if not s then return nil end
return tostring(s):match("^%s*(.-)%s*$")
end
local function isEmpty(s)
if s == nil then return true end
if type(s) == 'string' then
return trim(s) == ''
end
return false
end
local function hasValue(s)
return not isEmpty(s)
end
local function getArg(args, name, default)
local val = args[name]
if isEmpty(val) then return default end
return trim(val)
end
local function getFrameArgs(frame)
local args = {}
local parentArgs = frame:getParent() and frame:getParent().args or {}
local directArgs = frame.args or {}
for k, v in pairs(directArgs) do args[k] = v end
for k, v in pairs(parentArgs) do args[k] = v end
return args
end
--- Create a wikilink with optional display text
local function makeLink(target, display)
if isEmpty(target) then return nil end
target = trim(target)
if hasValue(display) then
return '[[' .. target .. '|' .. trim(display) .. ']]'
else
return '[[' .. target .. ']]'
end
end
--- Build a comma-separated list from numbered args, using Oxford comma
local function buildLinkList(args, startIndex, maxCount)
local links = {}
local count = maxCount or MAX_LINKS
for i = startIndex, startIndex + count - 1 do
local arg = getArg(args, i)
if hasValue(arg) then
table.insert(links, makeLink(arg))
else
break
end
end
local n = #links
if n == 0 then
return nil
elseif n == 1 then
return links[1]
elseif n == 2 then
return links[1] .. ' or ' .. links[2]
else
local list = table.concat(links, ', ', 1, n - 1)
return list .. ', or ' .. links[n]
end
end
--- Build a comma-separated list with "and" for final item
local function buildLinkListAnd(args, startIndex, maxCount)
local links = {}
local count = maxCount or MAX_LINKS
for i = startIndex, startIndex + count - 1 do
local arg = getArg(args, i)
if hasValue(arg) then
table.insert(links, makeLink(arg))
else
break
end
end
local n = #links
if n == 0 then
return nil
elseif n == 1 then
return links[1]
elseif n == 2 then
return links[1] .. ' and ' .. links[2]
else
local list = table.concat(links, ', ', 1, n - 1)
return list .. ', and ' .. links[n]
end
end
--------------------------------------------------------------------------------
-- CORE HATNOTE RENDERING
--------------------------------------------------------------------------------
--- Render a basic hatnote div
local function renderHatnote(text, extraClass)
if isEmpty(text) then
return ''
end
local div = mw.html.create('div')
:addClass('hatnote')
if hasValue(extraClass) then
div:addClass(extraClass)
end
div:wikitext(text)
return tostring(div)
end
--------------------------------------------------------------------------------
-- PUBLIC API
--------------------------------------------------------------------------------
--- Generic hatnote with custom text
-- Usage: {{#invoke:Hatnote|hatnote|Custom text here}}
function p.hatnote(frame)
local args = getFrameArgs(frame)
local text = getArg(args, 1) or getArg(args, 'text')
local extraClass = getArg(args, 'class')
if isEmpty(text) then
return '<span class="error">Error: Hatnote text required</span>'
end
return renderHatnote(text, extraClass)
end
--- About template - disambiguation at article top
-- Usage: {{#invoke:Hatnote|about|this topic|other topic|Other article|...}}
function p.about(frame)
local args = getFrameArgs(frame)
local thisTopic = getArg(args, 1)
if isEmpty(thisTopic) then
return '<span class="error">Error: Topic description required</span>'
end
local text = 'This article is about ' .. thisTopic .. '.'
-- Build "For X, see Y" pairs
local i = 2
while hasValue(getArg(args, i)) and hasValue(getArg(args, i + 1)) do
local forTopic = getArg(args, i)
local seeArticle = getArg(args, i + 1)
text = text .. ' For ' .. forTopic .. ', see ' .. makeLink(seeArticle) .. '.'
i = i + 2
end
return renderHatnote(text)
end
--- Main article template - summary sections
-- Usage: {{#invoke:Hatnote|main|Article 1|Article 2|...}}
function p.main(frame)
local args = getFrameArgs(frame)
local links = buildLinkListAnd(args, 1, 5)
if not links then
return '<span class="error">Error: Article name required</span>'
end
-- Count links to determine singular/plural
local count = 0
for i = 1, 5 do
if hasValue(getArg(args, i)) then count = count + 1 end
end
local prefix = count == 1 and 'Main article: ' or 'Main articles: '
return renderHatnote(prefix .. links, 'hatnote-main')
end
--- See also template - related articles
-- Usage: {{#invoke:Hatnote|seealso|Article 1|Article 2|...}}
function p.seealso(frame)
local args = getFrameArgs(frame)
local links = buildLinkListAnd(args, 1, 10)
if not links then
return '<span class="error">Error: Article name required</span>'
end
return renderHatnote('See also: ' .. links, 'hatnote-seealso')
end
--- Further information template
-- Usage: {{#invoke:Hatnote|further|Article 1|Article 2|...}}
function p.further(frame)
local args = getFrameArgs(frame)
local links = buildLinkListAnd(args, 1, 5)
if not links then
return '<span class="error">Error: Article name required</span>'
end
return renderHatnote('Further information: ' .. links, 'hatnote-further')
end
--- Redirect template - explains redirects
-- Usage: {{#invoke:Hatnote|redirect|Term|desc|Article|desc2|Article2|disambiguation}}
function p.redirect(frame)
local args = getFrameArgs(frame)
local term = getArg(args, 1)
if isEmpty(term) then
return '<span class="error">Error: Redirect term required</span>'
end
local text = '"' .. term .. '" redirects here.'
-- First alternative
local desc1 = getArg(args, 2)
local art1 = getArg(args, 3)
if hasValue(desc1) and hasValue(art1) then
text = text .. ' For ' .. desc1 .. ', see ' .. makeLink(art1) .. '.'
end
-- Second alternative
local desc2 = getArg(args, 4)
local art2 = getArg(args, 5)
if hasValue(desc2) and hasValue(art2) then
text = text .. ' For ' .. desc2 .. ', see ' .. makeLink(art2) .. '.'
end
-- Disambiguation link
local disambig = getArg(args, 6)
if hasValue(disambig) and disambig:lower() == 'disambiguation' then
text = text .. ' For other uses, see ' .. makeLink(term .. ' (disambiguation)') .. '.'
end
return renderHatnote(text, 'hatnote-redirect')
end
--- Distinguish template - "Not to be confused with"
-- Usage: {{#invoke:Hatnote|distinguish|Article 1|Article 2|...}}
function p.distinguish(frame)
local args = getFrameArgs(frame)
-- Check for custom text
local customText = getArg(args, 'text')
if hasValue(customText) then
return renderHatnote(customText, 'hatnote-distinguish')
end
local links = buildLinkList(args, 1, 10)
if not links then
return '<span class="error">Error: Article name required</span>'
end
return renderHatnote('Not to be confused with ' .. links .. '.', 'hatnote-distinguish')
end
--- For other uses template
-- Usage: {{#invoke:Hatnote|otheruses|Disambiguation page}}
function p.otheruses(frame)
local args = getFrameArgs(frame)
local disambig = getArg(args, 1)
local text
if hasValue(disambig) then
text = 'For other uses, see ' .. makeLink(disambig) .. '.'
else
-- Use current page title with (disambiguation)
local title = mw.title.getCurrentTitle()
text = 'For other uses, see ' .. makeLink(title.text .. ' (disambiguation)') .. '.'
end
return renderHatnote(text, 'hatnote-otheruses')
end
--- Short description alias (returns empty, handled elsewhere)
function p.shortdesc(frame)
local args = getFrameArgs(frame)
local desc = getArg(args, 1) or getArg(args, 'desc')
if isEmpty(desc) then
return ''
end
-- Just output the magic word
return '{{SHORTDESC:' .. desc .. '}}'
end
return p