Module:Simple navbox/sandbox
Jump to navigation
Jump to search
| This is the module sandbox page for Module:Simple navbox (diff). |
local p = {}
local getArgs = require('Module:Arguments').getArgs
--------------------------------------------------------------------------------
-- Utilities
--------------------------------------------------------------------------------
local function lc(v)
return type(v) == 'string' and mw.ustring.lower(v) or v
end
-- Check whether a style string sets a background property
local function hasBackground(s)
-- 'background' is a substring of 'background-color', so one check suffices
return type(s) == 'string' and s:find('background', 1, true)
end
-- Check whether a style string sets the font color property
local function hasColor(s)
if type(s) ~= 'string' then return false end
s = s:lower()
return s:match('^%s*color%s*:') or s:match('[;%s]color%s*:')
end
-- Returns true if the string contains constructs requiring parser expansion
local function needsPreprocess(s)
if type(s) ~= 'string' or s == '' then return false end
return s:find('{{', 1, true)
or s:find('{|', 1, true)
or s:find('<', 1, true)
end
-- Prepend basestyle to another style string, joining with semicolons
local function prependBaseStyle(a, b)
a, b = a or '', b or ''
if a ~= '' and b ~= '' then return a .. ';' .. b end
return a .. b
end
--------------------------------------------------------------------------------
-- Batch preprocess: runs frame:preprocess on a list of strings in one call
-- by joining with a unique separator, then splitting the result back apart.
-- This avoids per-item preprocess overhead on MediaWiki.
--------------------------------------------------------------------------------
local function batchPreprocess(frame, items)
if #items == 0 then return {} end
if #items == 1 then return { frame:preprocess(items[1]) } end
local sep = '@@SIMPLE_SEPARATOR@@'
local processed = frame:preprocess(table.concat(items, sep))
local results = {}
for part in mw.ustring.gmatch(processed, '(.-)' .. sep) do
table.insert(results, part)
end
-- The last segment isn't captured by the pattern above
if #results < #items then
table.insert(
results,
mw.ustring.match(processed, '.*' .. sep .. '(.*)') or items[#items]
)
end
return results
end
--------------------------------------------------------------------------------
-- Selective preprocess: only sends items through frame:preprocess if they
-- contain wiki markup (templates, tables, HTML tags). Plain-text items are
-- left as-is, avoiding unnecessary parser calls.
--------------------------------------------------------------------------------
local function selectivePreprocess(frame, items)
local toPreprocess = {}
local preprocessIndices = {}
for idx, val in ipairs(items) do
if needsPreprocess(val) then
table.insert(toPreprocess, val)
table.insert(preprocessIndices, idx)
end
end
local processed = batchPreprocess(frame, toPreprocess)
local results = {}
for idx, val in ipairs(items) do
results[idx] = val
end
for j, pos in ipairs(preprocessIndices) do
results[pos] = processed[j] or ''
end
return results
end
--------------------------------------------------------------------------------
-- Building blocks for the navbox
--------------------------------------------------------------------------------
local function buildHeader(frame, outerDiv, args, basestyle)
local headerstyle = prependBaseStyle(basestyle, args.headerstyle or args.titlestyle or '')
local headerClass = 'simple-header NavHead'
if headerstyle ~= '' and hasColor(headerstyle) then
headerClass = headerClass .. ' simple-header-modified'
end
local headerContent = {}
if args.name then
table.insert(headerContent, frame:expandTemplate{ title = 'simple navbar', args = { args.name } })
end
table.insert(headerContent, args.title or 'Default title: add a value for <code>title</code> in the template.')
outerDiv:tag('div')
:addClass(headerClass)
:cssText(headerstyle)
:wikitext(table.concat(headerContent))
end
local function buildGroupedContent(frame, contentDiv, args, basestyle)
local groupstyleBase = prependBaseStyle(basestyle, args.groupstyle or '')
local oddGStyle = groupstyleBase .. (args.oddgroupstyle and ';' .. args.oddgroupstyle or '')
local evenGStyle = groupstyleBase .. (args.evengroupstyle and ';' .. args.evengroupstyle or '')
local oddLStyle = (args.liststyle or '') .. (args.oddliststyle and ';' .. args.oddliststyle or '')
local evenLStyle = (args.liststyle or '') .. (args.evenliststyle and ';' .. args.evenliststyle or '')
local tableClass = 'simple-group-container'
if hasBackground(args.groupstyle) or hasBackground(args.oddgroupstyle) or hasBackground(args.evengroupstyle) then
tableClass = tableClass .. ' simple-group-modified'
end
if hasBackground(args.liststyle) or hasBackground(args.oddliststyle) or hasBackground(args.evenliststyle) then
tableClass = tableClass .. ' simple-list-modified'
end
if hasColor(oddGStyle) or hasColor(evenGStyle) then
tableClass = tableClass .. ' simple-group-inherit'
end
local groupTable = contentDiv:tag('table')
:addClass(tableClass)
:cssText(args.tablestyle or '')
-- Collect group indices and raw list values
local groupIndices = {}
local rawLists = {}
for i = 1, 50 do
if args['group' .. i] then
table.insert(groupIndices, i)
rawLists[#groupIndices] = args['list' .. i] or ''
end
end
-- Selectively preprocess only lists that contain wiki markup
local preprocessedLists = selectivePreprocess(frame, rawLists)
-- Build rows
for idx, i in ipairs(groupIndices) do
local group = args['group' .. i]
local listContent = preprocessedLists[idx] or ''
local odd = (i % 2 == 1)
local gstyle = (odd and oddGStyle or evenGStyle) .. ';' .. (args['group' .. i .. 'style'] or '')
local lstyle = (odd and oddLStyle or evenLStyle) .. ';' .. (args['list' .. i .. 'style'] or '')
local listClass = 'simple-list'
if listContent:find('simple%-child', 1, false) then
listClass = listClass .. ' simple-has-child'
end
local row = groupTable:tag('tr')
:addClass('simple-group-row')
:cssText(args.rowstyle or '')
row:tag('td')
:addClass('simple-group')
:cssText(gstyle)
:wikitext(group)
row:tag('td')
:addClass(listClass)
:cssText(lstyle)
:wikitext('\n' .. listContent .. '\n')
end
end
local function buildUngroupedContent(frame, contentDiv, args)
local list = args.list or args.list1 or 'Error: <code>list</code> is empty!'
if list ~= '' and needsPreprocess(list) then
list = frame:preprocess(list)
end
contentDiv:tag('div')
:addClass('simple-list simple-list-only')
:cssText(args.liststyle or '')
:wikitext('\n' .. list .. '\n')
end
local function buildImages(container, args, hasOuterImages)
local imageValign = args['image-valign'] or args.imagevalign or 'center'
-- Left inner image
local imageLeft = args.image2 or args.imageleft
if not hasOuterImages and imageLeft then
container:tag('div')
:addClass('simple-image')
:css('align-self', imageValign)
:wikitext(imageLeft)
end
-- Right inner image (added after content, so call separately)
return imageValign
end
--------------------------------------------------------------------------------
-- Main entry point
--------------------------------------------------------------------------------
function p.main(frame)
local args = getArgs(frame, { removeBlanks = false })
-- Stylesheets
local stylesheets = {}
table.insert(stylesheets, frame:extensionTag{ name = 'templatestyles', args = { src = 'Simple navbox/styles.css' } })
table.insert(stylesheets, frame:extensionTag{ name = 'templatestyles', args = { src = 'Hlist/styles.css' } })
if args.templatestyles then
table.insert(stylesheets, frame:extensionTag{ name = 'templatestyles', args = { src = args.templatestyles } })
end
local basestyle = args.basestyle or ''
-- State / collapse logic
local state = lc(args.collapse or args.collapsible or args.state or '')
if state == 'no' then state = 'plain' end
local collapsible = state ~= 'plain'
local allowOuterImages = not collapsible
local child = lc(args.child) or ''
-- Outer image handling
local userSetOuterImage = args['image-outer'] or args.imageouter or args.imageouterleft
or args['image-outer-right'] or args.imageouterright or args.imageouter2
if userSetOuterImage and collapsible then
mw.addWarning("<code>imageouter</code> type images will not be shown unless <code>|state = plain</code> or <code>|collapse = no</code>.")
end
local imageOuterLeft = allowOuterImages and (args['image-outer'] or args.imageouter or args.imageouterleft)
local imageOuterRight = allowOuterImages and (args['image-outer-right'] or args.imageouterright or args.imageouter2)
local hasOuterImages = imageOuterLeft or imageOuterRight
-- Warn if both inner and outer images are set
local hasInnerImages = args.image or args.image2 or args.imageleft
if hasOuterImages and hasInnerImages then
mw.addWarning("When <code>|state = plain</code> or <code>|collapse = no</code>, <code>image</code> ''or'' <code>imageouter</code> type images can be used, but ''not'' both.")
end
-- Assemble CSS classes for the outer div
local classes = { 'simple-navbox', args.bodyclass or args.class or '', 'noprint' }
if child == 'yes' then
table.insert(classes, 'simple-child')
end
if collapsible and child ~= 'yes' then
table.insert(classes, 'NavFrame')
if state == 'collapsed' or state == 'closed' then
table.insert(classes, 'NavClosed')
elseif state == 'expanded' or state == 'uncollapsed' or state == 'open' then
table.insert(classes, 'NavOpen')
end
elseif hasOuterImages then
table.insert(classes, 'simple-outer')
end
if basestyle ~= '' then
table.insert(classes, 'simple-basestyle')
end
if args.group1 and (hasBackground(args.groupstyle) or hasBackground(args.oddgroupstyle) or hasBackground(args.evengroupstyle)) then
table.insert(classes, 'simple-has-modified-groups')
end
-- Build outer div
local outerDiv = mw.html.create('div')
:addClass(table.concat(classes, ' '))
:cssText(args.bodystyle or '')
-- Outer left image
if imageOuterLeft then
outerDiv:tag('div')
:addClass('simple-outer-image')
:wikitext(imageOuterLeft)
end
-- Header
buildHeader(frame, outerDiv, args, basestyle)
-- Nav content wrapper
local navContent = outerDiv:tag('div')
:addClass('simple-navcontent NavContent')
:cssText(args.navcontentstyle or '')
-- Above block
if args.above then
local aboveVal = args.above
if needsPreprocess(aboveVal) then
aboveVal = frame:preprocess(aboveVal)
end
navContent:tag('div')
:addClass('simple-group-above')
:cssText(prependBaseStyle(basestyle, args.abovestyle or ''))
:wikitext('\n' .. aboveVal .. '\n')
end
-- Flex container for content + images
local imageAlign = lc(args['image-align'] or args.imagealign or '')
local flex = 'display:flex; flex-flow:row;'
if imageAlign == 'left' then
flex = 'display:flex; flex-flow:row-reverse; justify-content:flex-end;'
end
local container = navContent:tag('div')
:addClass('simple-content-container')
:cssText(flex .. ' ' .. (args.containerstyle or ''))
-- Left inner image
local imageValign = buildImages(container, args, hasOuterImages)
-- Content area
local contentDiv = container:tag('div')
:addClass('simple-content')
:addClass(args.listclass or '')
:cssText(args.contentstyle or '')
if args.group1 then
buildGroupedContent(frame, contentDiv, args, basestyle)
else
buildUngroupedContent(frame, contentDiv, args)
end
-- Right inner image
if not hasOuterImages and args.image then
container:tag('div')
:addClass('simple-image')
:css('align-self', imageValign)
:wikitext(args.image)
end
-- Below block
if args.below then
local belowVal = args.below
if needsPreprocess(belowVal) then
belowVal = frame:preprocess(belowVal)
end
navContent:tag('div')
:addClass('simple-group-above simple-group-below')
:cssText(prependBaseStyle(basestyle, args.belowstyle or args.abovestyle or ''))
:wikitext('\n' .. belowVal .. '\n')
end
-- Outer right image
if imageOuterRight then
outerDiv:tag('div')
:addClass('simple-outer-imageright')
:wikitext(imageOuterRight)
end
return table.concat(stylesheets) .. tostring(outerDiv)
end
return p