Latest revision |
Your text |
Line 1: |
Line 1: |
| require('strict')
| | -- |
| local cfg = mw.loadData('Module:Sidebar/configuration')
| | -- This module implements {{Sidebar}} |
| | | -- |
| | |
| local p = {} | | local p = {} |
| | | |
| local getArgs = require('Module:Arguments').getArgs | | local getArgs |
| | | local HtmlBuilder = require('Module:HtmlBuilder') |
| --[[
| | local navbar = require('Module:Navbar')._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}} |
| | -- implementation, which passed some parameters through {{#if}} to trim |
| | -- their whitespace. This also triggered the automatic newline behavior. |
| | -- ([[meta:Help:Newlines and spaces#Automatic newline]]) |
| s = mw.ustring.gsub(s, "^%s*(.-)%s*$", "%1") | | s = mw.ustring.gsub(s, "^%s*(.-)%s*$", "%1") |
| if mw.ustring.find(s, '^[#*:;]') or mw.ustring.find(s, '^{|') then | | if mw.ustring.find(s, '^[#*:;]') or mw.ustring.find(s, '^{|') then |
Line 42: |
Line 22: |
| end | | end |
|
| |
|
| --[[
| | local function _sidebar(args) |
| Finds whether a sidebar has a subgroup sidebar.
| | local root = HtmlBuilder.create() |
| ]]
| | local child = args.child and mw.text.trim(args.child) == 'yes' |
| 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 not child then |
| if type(arg) == 'string' and mw.ustring.find(arg, 'class') then
| | root = root |
| for _, pattern in ipairs(patterns) do | | .tag('table') |
| if mw.ustring.find(args[arg] or '', pattern) then
| | .addClass('vertical-navbox') |
| return true
| | .addClass(args.wraplinks ~= 'true' and 'nowraplinks') |
| end
| | .addClass(args.bodyclass or args.class) |
| end | | .css('float', args.float or 'right') |
| end
| | .css('clear', (args.float == 'none' and 'both') or args.float or 'right') |
| end
| | .css('width', args.width or '22.0em') |
| return false
| | .css('margin', args.float == 'left' and '0 1.0em 1.0em 0' or '0 0 1.0em 1.0em') |
| end
| | .css('background', '#f9f9f9') |
| | | .css('border', '1px solid #aaa') |
| -- there are a lot of list classes in the wild, so we add their TemplateStyles
| | .css('padding', '0.2em') |
| local function add_list_styles(args)
| | .css('border-spacing', '0.4em 0') |
| local frame = mw.getCurrentFrame()
| | .css('text-align', 'center') |
| local function add_list_templatestyles(htmlclass, templatestyles)
| | .css('line-height', '1.4em') |
| if has_list_class(args, htmlclass) then
| | .css('font-size', '88%') |
| return frame:extensionTag{ | | .cssText(args.bodystyle or args.style) |
| 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
| |
|
| |
| -- work around [[phab:T303378]]
| |
| -- for each arg: find all the templatestyles strip markers, insert them into a
| |
| -- table. then remove all templatestyles markers from the arg
| |
| local function move_hiding_templatestyles(args)
| |
| local gfind = string.gfind
| |
| local gsub = string.gsub
| |
| local templatestyles_markers = {}
| |
| local strip_marker_pattern = '(\127[^\127]*UNIQ%-%-templatestyles%-%x+%-QINU[^\127]*\127)'
| |
| for k, arg in pairs(args) do
| |
| for marker in gfind(arg, strip_marker_pattern) do
| |
| table.insert(templatestyles_markers, marker)
| |
| end
| |
| args[k] = gsub(arg, strip_marker_pattern, '')
| |
| end
| |
| return templatestyles_markers
| |
| end
| |
|
| |
| --[[
| |
| Main sidebar function. Takes the frame, args, and an optional collapsibleClass.
| |
| The collapsibleClass is and should be used only for sidebars with collapsible
| |
| lists, as in p.collapsible.
| |
| ]]
| |
| function p.sidebar(frame, args, collapsibleClass)
| |
| if not args then
| |
| args = getArgs(frame)
| |
| end
| |
| local hiding_templatestyles = table.concat(move_hiding_templatestyles(args))
| |
| local root = mw.html.create()
| |
| local child = args.child and mw.text.trim(args.child) == cfg.i18n.child_yes
| |
|
| |
| root = root:tag('table')
| |
| if not child then
| |
| root
| |
| :addClass(cfg.i18n.class.sidebar)
| |
| -- force collapsibleclass to be sidebar-collapse otherwise output nothing
| |
| :addClass(collapsibleClass == cfg.i18n.class.collapse and cfg.i18n.class.collapse or nil)
| |
| :addClass('nomobile')
| |
| :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)
| |
| :addClass(args.wraplinks ~= cfg.i18n.wrap_true and cfg.i18n.class.wraplinks or nil)
| |
| :addClass(args.bodyclass or args.class)
| |
| :css('width', args.width or nil)
| |
| :cssText(args.bodystyle or args.style)
| |
|
| |
| if args.outertitle then | | if args.outertitle then |
| root | | root |
| :tag('caption') | | .tag('caption') |
| :addClass(cfg.i18n.class.outer_title) | | .addClass(args.outertitleclass) |
| :addClass(args.outertitleclass) | | .css('padding-bottom', '0.2em') |
| :cssText(args.outertitlestyle) | | .css('font-size', '125%') |
| :wikitext(args.outertitle) | | .css('line-height', '1.2em') |
| | .css('font-weight', 'bold') |
| | .cssText(args.outertitlestyle) |
| | .wikitext(args.outertitle) |
| end | | end |
| | | |
| if args.topimage then | | if args.topimage then |
| local imageCell = root:tag('tr'):tag('td') | | local imageCell = root.tag('tr').tag('td') |
| | | |
| imageCell | | imageCell |
| :addClass(cfg.i18n.class.top_image) | | .addClass(args.topimageclass) |
| :addClass(args.topimageclass) | | .css('padding', '0.4em 0') |
| :cssText(args.topimagestyle) | | .cssText(args.topimagestyle) |
| :wikitext(args.topimage) | | .wikitext(args.topimage) |
| | | |
| if args.topcaption then | | if args.topcaption then |
| imageCell | | imageCell |
| :tag('div') | | .tag('div') |
| :addClass(cfg.i18n.class.top_caption) | | .css('padding-top', '0.2em') |
| :cssText(args.topcaptionstyle) | | .css('line-height', '1.2em') |
| :wikitext(args.topcaption) | | .cssText(args.topcaptionstyle) |
| | .wikitext(args.topcaption) |
| end | | end |
| end | | end |
| | | |
| if args.pretitle then | | if args.pretitle then |
| root | | root |
| :tag('tr') | | .tag('tr') |
| :tag('td') | | .tag('td') |
| :addClass(args.topimage and cfg.i18n.class.pretitle_with_top_image | | .addClass(args.pretitleclass) |
| or cfg.i18n.class.pretitle)
| | .cssText(args.basestyle) |
| :addClass(args.pretitleclass) | | .css('padding-top', args.topimage and '0.2em' or '0.4em') |
| :cssText(args.basestyle) | | .css('line-height', '1.2em') |
| :cssText(args.pretitlestyle) | | .cssText(args.pretitlestyle) |
| :wikitext(args.pretitle) | | .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 | | end |
|
| |
|
Line 204: |
Line 93: |
| if child then | | if child then |
| root | | root |
| :wikitext(args.title) | | .wikitext(args.title) |
| | .tag('/th', {unclosed = true}) |
| | .tag('/tr', {unclosed = true}) |
| else | | else |
| root | | root |
| :tag('tr') | | .tag('tr') |
| :tag('th') | | .tag('th') |
| :addClass(args.pretitle and cfg.i18n.class.title_with_pretitle | | .addClass(args.titleclass) |
| or cfg.i18n.class.title)
| | .cssText(args.basestyle) |
| :addClass(args.titleclass) | | .css('padding', '0.2em 0.4em 0.2em') |
| :cssText(args.basestyle) | | .css('padding-top', args.pretitle and 0) |
| :cssText(args.titlestyle) | | .css('font-size', '145%') |
| :wikitext(args.title) | | .css('line-height', '1.2em') |
| | .cssText(args.titlestyle) |
| | .wikitext(args.title) |
| end | | end |
| end | | end |
|
| |
|
| if args.image then | | if args.image then |
| local imageCell = root:tag('tr'):tag('td') | | local imageCell = root.tag('tr').tag('td') |
| | | |
| imageCell | | imageCell |
| :addClass(cfg.i18n.class.image) | | .addClass(args.imageclass) |
| :addClass(args.imageclass) | | .css('padding', '0.2em 0 0.4em') |
| :cssText(args.imagestyle) | | .cssText(args.imagestyle) |
| :wikitext(args.image) | | .wikitext(args.image) |
| | | |
| if args.caption then | | if args.caption then |
| imageCell | | imageCell |
| :tag('div') | | .tag('div') |
| :addClass(cfg.i18n.class.caption) | | .css('padding-top', '0.2em') |
| :cssText(args.captionstyle) | | .css('line-height', '1.2em') |
| :wikitext(args.caption) | | .cssText(args.captionstyle) |
| | .wikitext(args.caption) |
| end | | end |
| end | | end |
| | | |
| if args.above then | | if args.above then |
| root | | root |
| :tag('tr') | | .tag('tr') |
| :tag('td') | | .tag('td') |
| :addClass(cfg.i18n.class.above) | | .addClass(args.aboveclass) |
| :addClass(args.aboveclass) | | .css('padding', '0.3em 0.4em 0.3em') |
| :cssText(args.abovestyle) | | .css('font-weight', 'bold') |
| :newline() -- newline required for bullet-points to work | | .cssText(args.abovestyle) |
| :wikitext(args.above) | | .newline() -- newline required for bullet-points to work |
| | .wikitext(args.above) |
| end | | end |
|
| |
|
Line 254: |
Line 149: |
| end | | end |
| table.sort(rowNums) | | table.sort(rowNums) |
| -- remove duplicates from the list (e.g. 3 will be duplicated if both heading3 | | -- remove duplicates from the list (e.g. 3 will be duplicated if both heading3 and content3 are specified) |
| -- and content3 are specified)
| |
| for i = #rowNums, 1, -1 do | | for i = #rowNums, 1, -1 do |
| if rowNums[i] == rowNums[i - 1] then | | if rowNums[i] == rowNums[i - 1] then |
Line 266: |
Line 160: |
| if heading then | | if heading then |
| root | | root |
| :tag('tr') | | .tag('tr') |
| :tag('th') | | .tag('th') |
| :addClass(cfg.i18n.class.heading) | | .addClass(args.headingclass) |
| :addClass(args.headingclass)
| | .css('padding', '0.1em') |
| :addClass(args['heading' .. num .. 'class']) | | .cssText(args.basestyle) |
| :cssText(args.basestyle) | | .cssText(args.headingstyle) |
| :cssText(args.headingstyle) | | .cssText(args['heading' .. num .. 'style']) |
| :cssText(args['heading' .. num .. 'style']) | | .newline() |
| :newline() | | .wikitext(heading) |
| :wikitext(heading) | |
| end | | end |
| | | |
| local content = args['content' .. num] | | local content = args['content' .. num] |
| if content then | | if content then |
| root | | root |
| :tag('tr') | | .tag('tr') |
| :tag('td') | | .tag('td') |
| :addClass(hasSubgroup(content) and cfg.i18n.class.content_with_subgroup | | .addClass(args.contentclass) |
| or cfg.i18n.class.content)
| | .css('padding', '0 0.1em 0.4em') |
| :addClass(args.contentclass)
| | .cssText(args.contentstyle) |
| :addClass(args['content' .. num .. 'class']) | | .cssText(args['content' .. num .. 'style']) |
| :cssText(args.contentstyle) | | .newline() |
| :cssText(args['content' .. num .. 'style']) | | .wikitext(content) |
| :newline() | | .done() |
| :wikitext(content) | | .newline() -- Without a linebreak after the </td>, a nested list like "* {{hlist| ...}}" doesn't parse correctly. |
| :done() | |
| -- Without a linebreak after the </td>, a nested list like
| |
| -- "* {{hlist| ...}}" doesn't parse correctly.
| |
| :newline()
| |
| end | | end |
| end | | end |
Line 300: |
Line 189: |
| if args.below then | | if args.below then |
| root | | root |
| :tag('tr') | | .tag('tr') |
| :tag('td') | | .tag('td') |
| :addClass(cfg.i18n.class.below) | | .addClass(args.belowclass) |
| :addClass(args.belowclass) | | .css('padding', '0.3em 0.4em 0.3em') |
| :cssText(args.belowstyle) | | .css('font-weight', 'bold') |
| :newline() | | .cssText(args.belowstyle) |
| :wikitext(args.below) | | .newline() |
| | .wikitext(args.below) |
| end | | end |
|
| |
|
| if not child and has_navbar(args.navbar, args.name) then | | if not child then |
| root
| | local navbarArg = args.navbar or args.tnavbar |
| :tag('tr')
| | if navbarArg ~= 'none' and navbarArg ~= 'off' then |
| :tag('td')
| | root |
| :addClass(cfg.i18n.class.navbar)
| | .tag('tr') |
| :cssText(args.navbarstyle)
| | .tag('td') |
| :wikitext(require('Module:Navbar')._navbar{
| | .css('text-align', 'right') |
| args.name,
| | .css('font-size', '115%') |
| mini = 1,
| | .cssText(args.navbarstyle or args.tnavbarstyle) |
| fontstyle = args.navbarfontstyle
| | .wikitext(navbar{ |
| })
| | args.name, |
| end
| | mini = 1, |
|
| | fontstyle = args.navbarfontstyle or args.tnavbarfontstyle |
| local base_templatestyles = frame:extensionTag{
| | }) |
| name = 'templatestyles', args = { src = cfg.i18n.templatestyles }
| | end |
| }
| |
|
| |
| 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 | | end |
|
| |
|
| return table.concat({ | | return tostring(root) |
| 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 |
|
| |
|
| local function list_title(args, is_centered_list_titles, num)
| | function _collapsibleSidebar(args) |
| | | args.abovestyle = 'border-top: 1px solid #aaa; border-bottom: 1px solid #aaa;' .. (args.abovestyle or '') |
| local title_text = trimAndAddAutomaticNewline(args['list' .. num .. 'title']
| | args.belowstyle = 'border-top: 1px solid #aaa; border-bottom: 1px solid #aaa;' .. (args.belowstyle or '') |
| or cfg.i18n.default_list_title)
| | args.navbarstyle = 'padding-top: 0.6em;' .. (args.navbarstyle or args.tnavbarstyle or '') |
| | |
| local title
| |
| if is_centered_list_titles then
| |
| -- collapsible can be finicky, so provide some CSS/HTML to support
| |
| title = mw.html.create('div')
| |
| :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
| |
|
| |
| --[[
| |
| Main entry point for sidebar with collapsible lists.
| |
| Does the work of creating the collapsible lists themselves and including them
| |
| into the args.
| |
| ]]
| |
| function p.collapsible(frame)
| |
| local args = getArgs(frame)
| |
| if not args.name and
| |
| frame:getParent():getTitle():gsub(cfg.i18n.pattern.collapse_sandbox, '') ==
| |
| cfg.i18n.collapse_title_not_to_add_navbar then
| |
| args.navbar = cfg.i18n.navbar_none
| |
| end
| |
|
| |
| local contentArgs = {} | | local contentArgs = {} |
| | | |
| local is_centered_list_titles = false
| |
| if args['centered list titles'] and args['centered list titles'] ~= '' then
| |
| is_centered_list_titles = true
| |
| end
| |
|
| |
| for k, v in pairs(args) do | | for k, v in pairs(args) do |
| local num = string.match(k, '^list(%d+)$') | | local num = ('' .. k):match('^list(%d+)$') |
| if num then | | if num then |
| local expand = args.expanded and | | local expand = args.expanded and (args.expanded == 'all' or args.expanded == args['list' .. num .. 'name']) |
| (args.expanded == 'all' or args.expanded == args['list' .. num .. 'name'])
| | |
| local row = mw.html.create('div') | | local row = HtmlBuilder.create('div') |
| row | | row |
| :addClass(cfg.i18n.class.list) | | .addClass('NavFrame') |
| :addClass('mw-collapsible')
| | .addClass((not expand) and 'collapsed') |
| :addClass((not expand) and 'mw-collapsed' or nil) | | .css('border', 'none') |
| :addClass(args['list' .. num .. 'class']) | | .css('padding', 0) |
| :cssText(args.listframestyle) | | .cssText(args.listframestyle) |
| :cssText(args['list' .. num .. 'framestyle'])
| | .cssText(args['list' .. num .. 'framestyle']) |
| :node(list_title(args, is_centered_list_titles, num))
| | .tag('div') |
| :tag('div') | | .addClass('NavHead') |
| :addClass(cfg.i18n.class.list_content) | | .addClass(args.listtitleclass) |
| :addClass('mw-collapsible-content') | | .css('font-size', '105%') |
| -- don't /need/ a listnumstyleclass because you can do | | .css('background', 'transparent') |
| -- .templatename .listnumclass .sidebar-list | | .css('text-align', 'left') |
| :addClass(args.listclass) | | .cssText(args.basestyle) |
| :cssText(args.liststyle) | | .cssText(args.listtitlestyle) |
| :cssText(args['list' .. num .. 'style']) | | .cssText(args['list' .. num .. 'titlestyle']) |
| :wikitext(trimAndAddAutomaticNewline(args['list' .. num])) | | .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) | | contentArgs['content' .. num] = tostring(row) |
| end | | end |
Line 442: |
Line 268: |
| args[k] = v | | args[k] = v |
| end | | end |
| | | |
| return p.sidebar(frame, args, cfg.i18n.class.collapse) | | return _sidebar(args) |
| | end |
| | |
| | function makeWrapper(func) |
| | return function(frame) |
| | if not getArgs then |
| | getArgs = require('Module:Arguments').getArgs |
| | end |
| | return func(getArgs(frame)) |
| | end |
| end | | end |
|
| |
|
| return p | | return { |
| | sidebar = makeWrapper(_sidebar), |
| | collapsible = makeWrapper(_collapsibleSidebar) |
| | } |