Module:Sidebar: Difference between revisions

m Protected Module:Sidebar: High-risk Lua module ([Edit=Block all non-admin users] (indefinite) [Move=Block all non-admin users] (indefinite))
Logan (talk | contribs)
m 44 revisions imported from wikipedia:Module:Sidebar
 
(30 intermediate revisions by 10 users not shown)
Line 1: Line 1:
--
require('strict')
-- This module implements {{Sidebar}}
local cfg = mw.loadData('Module:Sidebar/configuration')
--
 
local p = {}
local p = {}
 
local HtmlBuilder = require('Module:HtmlBuilder')
local getArgs = require('Module:Arguments').getArgs
local Navbar = require('Module:Navbar')
 
--[[
Categorizes calling templates and modules with a 'style' parameter of any sort
for tracking to convert to TemplateStyles.
 
TODO after a long cleanup: Catch sidebars in other namespaces than Template and Module.
TODO would probably want to remove /log and /archive as CS1 does
]]
local function categorizeTemplatesWithInlineStyles(args)
local title = mw.title.getCurrentTitle()
if title.namespace ~= 10 and title.namespace ~= 828 then return '' end
for _, pattern in ipairs (cfg.i18n.pattern.uncategorized_conversion_titles) do
if title.text:match(pattern) then return '' end
end
for key, _ in pairs(args) do
if mw.ustring.find(key, cfg.i18n.pattern.style_conversion) or key == 'width' then
return cfg.i18n.category.conversion
end
end
end
 
--[[
For compatibility with the original {{sidebar with collapsible lists}}
implementation, which passed some parameters through {{#if}} to trim their
whitespace. This also triggered the automatic newline behavior.
]]
-- See ([[meta:Help:Newlines and spaces#Automatic newline]])
local function trimAndAddAutomaticNewline(s)
local function trimAndAddAutomaticNewline(s)
    -- For compatibility with the original {{sidebar with collapsible lists}}
s = mw.ustring.gsub(s, "^%s*(.-)%s*$", "%1")
    -- implementation, which passed some parameters through {{#if}} to trim
if mw.ustring.find(s, '^[#*:;]') or mw.ustring.find(s, '^{|') then
    -- their whitespace. This also triggered the automatic newline behavior.
return '\n' .. s
    -- ([[meta:Help:Newlines and spaces#Automatic newline]])
else
    s = mw.ustring.gsub(s, "^%s*(.-)%s*$", "%1")
return s
    if mw.ustring.find(s, '^[#*:;]') or mw.ustring.find(s, '^{|') then
end
        return '\n' .. s
end
    else
 
        return s
--[[
    end
Finds whether a sidebar has a subgroup sidebar.
]]
local function hasSubgroup(s)
if mw.ustring.find(s, cfg.i18n.pattern.subgroup) then
return true
else
return false
end
end
 
local function has_navbar(navbar_mode, sidebar_name)
return navbar_mode ~= cfg.i18n.navbar_none and
navbar_mode ~= cfg.i18n.navbar_off and
(
sidebar_name or
mw.getCurrentFrame():getParent():getTitle():gsub(cfg.i18n.pattern.sandbox, '') ~=
cfg.i18n.title_not_to_add_navbar
)
end
 
local function has_list_class(args, htmlclass)
local patterns = {
'^' .. htmlclass .. '$',
'%s' .. htmlclass .. '$',
'^' .. htmlclass .. '%s',
'%s' .. htmlclass .. '%s'
}
for arg, value in pairs(args) do
if type(arg) == 'string' and mw.ustring.find(arg, 'class') then
for _, pattern in ipairs(patterns) do
if mw.ustring.find(args[arg] or '', pattern) then
return true
end
end
end
end
return false
end
 
-- there are a lot of list classes in the wild, so we add their TemplateStyles
local function add_list_styles(args)
local frame = mw.getCurrentFrame()
local function add_list_templatestyles(htmlclass, templatestyles)
if has_list_class(args, htmlclass) then
return frame:extensionTag{
name = 'templatestyles', args = { src = templatestyles }
}
else
return ''
end
end
local plainlist_styles = add_list_templatestyles('plainlist', cfg.i18n.plainlist_templatestyles)
local hlist_styles = add_list_templatestyles('hlist', cfg.i18n.hlist_templatestyles)
-- a second workaround for [[phab:T303378]]
-- when that issue is fixed, we can actually use has_navbar not to emit the
-- tag here if we want
if has_navbar(args.navbar, args.name) and hlist_styles == '' then
hlist_styles = frame:extensionTag{
name = 'templatestyles', args = { src = cfg.i18n.hlist_templatestyles}
}
end
 
-- hlist -> plainlist is best-effort to preserve old Common.css ordering. [hlist_note]
return hlist_styles .. plainlist_styles
end
end


local function _sidebar(args)
-- work around [[phab:T303378]]
    local root = HtmlBuilder.create('table')
-- for each arg: find all the templatestyles strip markers, insert them into a
   
-- table. then remove all templatestyles markers from the arg
    root
local function move_hiding_templatestyles(args)
        .addClass('vertical-navbox')
local gfind = string.gfind
        .addClass(args.wraplinks ~= 'true' and 'nowraplinks')
local gsub = string.gsub
        .addClass(args.bodyclass or args.class)
local templatestyles_markers = {}
        .attr('cellspacing', args.cellspacing or 5)
local strip_marker_pattern = '(\127[^\127]*UNIQ%-%-templatestyles%-%x+%-QINU[^\127]*\127)'
        .attr('cellpadding', args.cellpadding or 0)
for k, arg in pairs(args) do
        .css('float', args.float or 'right')
for marker in gfind(arg, strip_marker_pattern) do
        .css('clear', (args.float == 'none' and 'both') or args.float or 'right')
table.insert(templatestyles_markers, marker)
        .css('width', args.width or '22.0em')
end
        .css('margin', args.float == 'left' and '0 1.0em 1.0em 0' or '0 0 1.0em 1.0em')
args[k] = gsub(arg, strip_marker_pattern, '')
        .css('background', '#f9f9f9')
end
        .css('border', '1px solid #aaa')
return templatestyles_markers
        .css('padding', '0.2em')
end
        .css('border-spacing', '0.4em 0')
        .css('text-align', 'center')
        .css('line-height', '1.4em')
        .css('font-size', '88%')
        .cssText(args.bodystyle or args.style)


    if args.outertitle then
--[[
        root
Main sidebar function. Takes the frame, args, and an optional collapsibleClass.
            .tag('caption')
The collapsibleClass is and should be used only for sidebars with collapsible
                .addClass(args.outertitleclass)
lists, as in p.collapsible.
                .css('padding-bottom', '0.2em')
]]
                .css('font-size', '125%')
function p.sidebar(frame, args, collapsibleClass)
                .css('line-height', '1.2em')
if not args then
                .css('font-weight', 'bold')
args = getArgs(frame)
                .cssText(args.outertitlestyle)
end
                .wikitext(args.outertitle)
local hiding_templatestyles = table.concat(move_hiding_templatestyles(args))
    end
local root = mw.html.create()
local child = args.child and mw.text.trim(args.child) == cfg.i18n.child_yes


    if args.topimage then
root = root:tag('table')
        local imageCell = root.tag('tr').tag('td')
if not child then
       
root
        imageCell
:addClass(cfg.i18n.class.sidebar)
            .addClass(args.topimageclass)
-- force collapsibleclass to be sidebar-collapse otherwise output nothing
            .css('padding', '0.4em 0')
:addClass(collapsibleClass == cfg.i18n.class.collapse and cfg.i18n.class.collapse or nil)
            .cssText(args.topimagestyle)
:addClass('nomobile')
            .wikitext(args.topimage)
:addClass(args.float == cfg.i18n.float_none and cfg.i18n.class.float_none or nil)
       
:addClass(args.float == cfg.i18n.float_left and cfg.i18n.class.float_left or nil)
        if args.topcaption then
:addClass(args.wraplinks ~= cfg.i18n.wrap_true and cfg.i18n.class.wraplinks or nil)
            imageCell
:addClass(args.bodyclass or args.class)
                .tag('div')
:css('width', args.width or nil)
                    .css('padding-top', '0.2em')
:cssText(args.bodystyle or args.style)
                    .css('line-height', '1.2em')
                    .cssText(args.topcaptionstyle)
                    .wikitext(args.topcaption)
        end
    end
   
    if args.pretitle then
        root
            .tag('tr')
                .tag('td')
                    .addClass(args.pretitleclass)
                    .cssText(args.basestyle)
                    .css('padding-top', args.topimage and '0.2em' or '0.4em')
                    .css('line-height', '1.2em')
                    .cssText(args.pretitlestyle)
                    .wikitext(args.pretitle)
    end


    if args.title then
if args.outertitle then
        root
root
            .tag('tr')
:tag('caption')
                .tag('th')
:addClass(cfg.i18n.class.outer_title)
                    .addClass(args.titleclass)
:addClass(args.outertitleclass)
                    .cssText(args.basestyle)
:cssText(args.outertitlestyle)
                    .css('padding', '0.2em 0.4em 0.2em')
:wikitext(args.outertitle)
                    .css('padding-top', args.pretitle and 0)
end
                    .css('font-size', '145%')
                    .css('line-height', '1.2em')
                    .cssText(args.titlestyle)
                    .wikitext(args.title)
    end


    if args.image then
if args.topimage then
        local imageCell = root.tag('tr').tag('td')
local imageCell = root:tag('tr'):tag('td')
       
        imageCell
            .addClass(args.imageclass)
            .css('padding', '0.2em 0 0.4em')
            .cssText(args.imagestyle)
            .wikitext(args.image)
           
        if args.caption then
            imageCell
                .tag('div')
                    .css('padding-top', '0.2em')
                    .css('line-height', '1.2em')
                    .cssText(args.captionstyle)
                    .wikitext(args.caption)
        end
    end
   
    if args.above then
        root
            .tag('tr')
                .tag('td')
                    .addClass(args.aboveclass)
                    .css('padding', '0.3em 0.4em 0.3em')
                    .css('font-weight', 'bold')
                    .cssText(args.abovestyle)
                    .newline()      -- newline required for bullet-points to work
                    .wikitext(args.above)
    end


    local rowNums = {}
imageCell
    for k, v in pairs(args) do
:addClass(cfg.i18n.class.top_image)
        k = '' .. k
:addClass(args.topimageclass)
        local num = k:match('^heading(%d+)$') or k:match('^content(%d+)$')
:cssText(args.topimagestyle)
        if num then table.insert(rowNums, tonumber(num)) end
:wikitext(args.topimage)
    end
    table.sort(rowNums)
    -- remove duplicates from the list (e.g. 3 will be duplicated if both heading3 and content3 are specified)
    for i = #rowNums, 1, -1 do
        if rowNums[i] == rowNums[i - 1] then
            table.remove(rowNums, i)
        end
    end


    for i, num in ipairs(rowNums) do
if args.topcaption then
        local heading = args['heading' .. num]
imageCell
        if heading then
:tag('div')
            root
:addClass(cfg.i18n.class.top_caption)
                .tag('tr')
:cssText(args.topcaptionstyle)
                    .tag('th')
:wikitext(args.topcaption)
                        .addClass(args.headingclass)
end
                        .css('padding', '0.1em')
end
                        .cssText(args.basestyle)
                        .cssText(args.headingstyle)
                        .cssText(args['heading' .. num .. 'style'])
                        .wikitext(heading)
        end
       
        local content = args['content' .. num]
        if content then
            root
                .tag('tr')
                    .tag('td')
                        .addClass(args.contentclass)
                        .css('padding', '0 0.1em 0.4em')
                        .cssText(args.contentstyle)
                        .cssText(args['content' .. num .. 'style'])
                        .newline()
                        .wikitext(content)
                        .done()
                    .newline()  -- Without a linebreak after the </td>, a nested list like "* {{hlist| ...}}" doesn't parse correctly.
        end
    end


    if args.below then
if args.pretitle then
        root
root
            .tag('tr')
:tag('tr')
                .tag('td')
:tag('td')
                    .addClass(args.belowclass)
:addClass(args.topimage and cfg.i18n.class.pretitle_with_top_image
                    .css('padding', '0.3em 0.4em 0.3em')
or cfg.i18n.class.pretitle)
                    .css('font-weight', 'bold')
:addClass(args.pretitleclass)
                    .cssText(args.belowstyle)
:cssText(args.basestyle)
                    .newline()
:cssText(args.pretitlestyle)
                    .wikitext(args.below)
:wikitext(args.pretitle)
    end
end
else
root
:addClass(cfg.i18n.class.subgroup)
:addClass(args.bodyclass or args.class)
:cssText(args.bodystyle or args.style)
end


    local navbarArg = args.navbar or args.tnavbar
if args.title then
    if navbarArg ~= 'none' and navbarArg ~= 'off' then
if child then
        root
root
            .tag('tr')
:wikitext(args.title)
                .tag('td')
else
                    .css('text-align', 'right')
root
                    .css('font-size', '115%')
:tag('tr')
                    .cssText(args.navbarstyle or args.tnavbarstyle)
:tag('th')
                    .wikitext(Navbar.navbar({
:addClass(args.pretitle and cfg.i18n.class.title_with_pretitle
                        args.name or mw.title.getCurrentTitle().fullText,
or cfg.i18n.class.title)
                        mini = 1,
:addClass(args.titleclass)
                        fontstyle = args.navbarfontstyle or args.tnavbarfontstyle
:cssText(args.basestyle)
                    }))
:cssText(args.titlestyle)
    end  
:wikitext(args.title)
end
end


    return tostring(root)
if args.image then
local imageCell = root:tag('tr'):tag('td')
 
imageCell
:addClass(cfg.i18n.class.image)
:addClass(args.imageclass)
:cssText(args.imagestyle)
:wikitext(args.image)
 
if args.caption then
imageCell
:tag('div')
:addClass(cfg.i18n.class.caption)
:cssText(args.captionstyle)
:wikitext(args.caption)
end
end
 
if args.above then
root
:tag('tr')
:tag('td')
:addClass(cfg.i18n.class.above)
:addClass(args.aboveclass)
:cssText(args.abovestyle)
:newline() -- newline required for bullet-points to work
:wikitext(args.above)
end
 
local rowNums = {}
for k, v in pairs(args) do
k = '' .. k
local num = k:match('^heading(%d+)$') or k:match('^content(%d+)$')
if num then table.insert(rowNums, tonumber(num)) end
end
table.sort(rowNums)
-- remove duplicates from the list (e.g. 3 will be duplicated if both heading3
-- and content3 are specified)
for i = #rowNums, 1, -1 do
if rowNums[i] == rowNums[i - 1] then
table.remove(rowNums, i)
end
end
 
for i, num in ipairs(rowNums) do
local heading = args['heading' .. num]
if heading then
root
:tag('tr')
:tag('th')
:addClass(cfg.i18n.class.heading)
:addClass(args.headingclass)
:addClass(args['heading' .. num .. 'class'])
:cssText(args.basestyle)
:cssText(args.headingstyle)
:cssText(args['heading' .. num .. 'style'])
:newline()
:wikitext(heading)
end
 
local content = args['content' .. num]
if content then
root
:tag('tr')
:tag('td')
:addClass(hasSubgroup(content) and cfg.i18n.class.content_with_subgroup
or cfg.i18n.class.content)
:addClass(args.contentclass)
:addClass(args['content' .. num .. 'class'])
:cssText(args.contentstyle)
:cssText(args['content' .. num .. 'style'])
:newline()
:wikitext(content)
:done()
-- Without a linebreak after the </td>, a nested list like
-- "* {{hlist| ...}}" doesn't parse correctly.
:newline()
end
end
 
if args.below then
root
:tag('tr')
:tag('td')
:addClass(cfg.i18n.class.below)
:addClass(args.belowclass)
:cssText(args.belowstyle)
:newline()
:wikitext(args.below)
end
 
if not child and has_navbar(args.navbar, args.name) then
root
:tag('tr')
:tag('td')
:addClass(cfg.i18n.class.navbar)
:cssText(args.navbarstyle)
:wikitext(require('Module:Navbar')._navbar{
args.name,
mini = 1,
fontstyle = args.navbarfontstyle
})
end
local base_templatestyles = frame:extensionTag{
name = 'templatestyles', args = { src = cfg.i18n.templatestyles }
}
local templatestyles = ''
if args['templatestyles'] and args['templatestyles'] ~= '' then
templatestyles = frame:extensionTag{
name = 'templatestyles', args = { src = args['templatestyles'] }
}
end
local child_templatestyles = ''
if args['child templatestyles'] and args['child templatestyles'] ~= '' then
child_templatestyles = frame:extensionTag{
name = 'templatestyles', args = { src = args['child templatestyles'] }
}
end
local grandchild_templatestyles = ''
if args['grandchild templatestyles'] and args['grandchild templatestyles'] ~= '' then
grandchild_templatestyles = frame:extensionTag{
name = 'templatestyles', args = { src = args['grandchild templatestyles'] }
}
end
 
return table.concat({
add_list_styles(args), -- see [hlist_note] above about ordering
base_templatestyles,
templatestyles,
child_templatestyles,
grandchild_templatestyles,
hiding_templatestyles,
tostring(root),
(child and cfg.i18n.category.child or ''),
categorizeTemplatesWithInlineStyles(args)
})
end
end


function _collapsibleSidebar(args)
local function list_title(args, is_centered_list_titles, num)
    args.abovestyle = 'border-top: 1px solid #aaa; border-bottom: 1px solid #aaa;' .. (args.abovestyle or '')
    args.belowstyle = 'border-top: 1px solid #aaa; border-bottom: 1px solid #aaa;' .. (args.belowstyle or '')
local title_text = trimAndAddAutomaticNewline(args['list' .. num .. 'title']
    args.navbarstyle = 'padding-top: 0.6em;' .. (args.navbarstyle or args.tnavbarstyle or '')
or cfg.i18n.default_list_title)
   
    local contentArgs = {}
   
    for k, v in pairs(args) do
        local num = ('' .. k):match('^list(%d+)$')
        if num then
            local expand = args.expanded and (args.expanded == 'all' or args.expanded == args['list' .. num .. 'name'])
           
            local row = HtmlBuilder.create('div')
            row
                .addClass('NavFrame')
                .addClass((not expand) and 'collapsed')
                .css('border', 'none')
                .css('padding', 0)
                .cssText(args.listframestyle)
                .cssText(args['list' .. num .. 'framestyle'])
                .tag('div')
                    .addClass('NavHead')
                    .addClass(args.listtitleclass)
                    .css('font-size', '105%')
                    .css('background', 'transparent')
                    .css('text-align', 'left')
                    .cssText(args.basestyle)
                    .cssText(args.listtitlestyle)
                    .cssText(args['list' .. num .. 'titlestyle'])
                    .wikitext(trimAndAddAutomaticNewline(args['list' .. num .. 'title'] or 'List'))
                    .done()
                .tag('div')
                    .addClass('NavContent')
                    .addClass(args.listclass)
                    .addClass(args['list' .. num .. 'class'])
                    .css('font-size', '105%')
                    .css('padding', '0.2em 0 0.4em')
                    .css('text-align', 'center')
                    .cssText(args.liststyle)
                    .cssText(args['list' .. num .. 'style'])
                    .wikitext(trimAndAddAutomaticNewline(args['list' .. num]))
                   
            contentArgs['content' .. num] = tostring(row)
        end
    end


    for k, v in pairs(contentArgs) do
local title
        args[k] = v
if is_centered_list_titles then
    end
-- collapsible can be finicky, so provide some CSS/HTML to support
   
title = mw.html.create('div')
    return _sidebar(args)
:addClass(cfg.i18n.class.list_title_centered)
:wikitext(title_text)
else
title = mw.html.create()
:wikitext(title_text)
end
local title_container = mw.html.create('div')
:addClass(cfg.i18n.class.list_title)
-- don't /need/ a listnumtitleclass because you can do
-- .templateclass .listnumclass .sidebar-list-title
:addClass(args.listtitleclass)
:cssText(args.basestyle)
:cssText(args.listtitlestyle)
:cssText('color: var(--color-base)')
:cssText(args['list' .. num .. 'titlestyle'])
:node(title)
:done()
return title_container
end
end
                     
 
function makeWrapper(func)
--[[
    return function(frame)
Main entry point for sidebar with collapsible lists.
        local origArgs
Does the work of creating the collapsible lists themselves and including them
        if frame == mw.getCurrentFrame() then
into the args.
            -- We're being called via #invoke. If the invoking template passed any args, use
]]
            -- them. Otherwise, use the args that were passed into the template.
function p.collapsible(frame)
            origArgs = frame:getParent().args
local args = getArgs(frame)
            for k, v in pairs(frame.args) do
if not args.name and
                origArgs = frame.args
frame:getParent():getTitle():gsub(cfg.i18n.pattern.collapse_sandbox, '') ==
                break
cfg.i18n.collapse_title_not_to_add_navbar then
            end
args.navbar = cfg.i18n.navbar_none
        else
end
            -- We're being called from another module or from the debug console, so assume
 
            -- the args are passed in directly.
local contentArgs = {}
            origArgs = frame
        end
local is_centered_list_titles = false
   
if args['centered list titles'] and args['centered list titles'] ~= '' then
        -- ParserFunctions considers the empty string to be false, so to preserve the previous
is_centered_list_titles = true
        -- behavior of the template, change any empty arguments to nil, so Lua will consider
end
        -- them false too.
 
        local args = {}
for k, v in pairs(args) do
        for k, v in pairs(origArgs) do
local num = string.match(k, '^list(%d+)$')
            if v ~= '' then
if num then
                args[k] = v
local expand = args.expanded and
            end
(args.expanded == 'all' or args.expanded == args['list' .. num .. 'name'])
        end
local row = mw.html.create('div')
   
row
        return func(args)
:addClass(cfg.i18n.class.list)
    end
:addClass('mw-collapsible')
:addClass((not expand) and 'mw-collapsed' or nil)
:addClass(args['list' .. num .. 'class'])
:cssText(args.listframestyle)
:cssText(args['list' .. num .. 'framestyle'])
:node(list_title(args, is_centered_list_titles, num))
:tag('div')
:addClass(cfg.i18n.class.list_content)
:addClass('mw-collapsible-content')
-- don't /need/ a listnumstyleclass because you can do
-- .templatename .listnumclass .sidebar-list
:addClass(args.listclass)
:cssText(args.liststyle)
:cssText(args['list' .. num .. 'style'])
:wikitext(trimAndAddAutomaticNewline(args['list' .. num]))
 
contentArgs['content' .. num] = tostring(row)
end
end
 
for k, v in pairs(contentArgs) do
args[k] = v
end
 
return p.sidebar(frame, args, cfg.i18n.class.collapse)
end
end


return {
return p
    sidebar = makeWrapper(_sidebar),
    collapsible = makeWrapper(_collapsibleSidebar)
}