Module:Navbox:修订间差异
小 导入1个版本 |
|||
| 第1行: | 第1行: | ||
-- | -- | ||
-- This module | -- This module implements {{Navbox}} | ||
-- | -- | ||
local p = {} | local p = {} | ||
local getArgs -- lazily initialized | local getArgs -- lazily initialized | ||
local args | local args | ||
local border | local border | ||
local listnums = {} | local listnums | ||
local ODD_EVEN_MARKER = '\127_ODDEVEN_\127' | |||
local RESTART_MARKER = '\127_ODDEVEN0_\127' | |||
local REGEX_MARKER = '\127_ODDEVEN(%d?)_\127' | |||
local lists = { | |||
plainlist_t = { | |||
patterns = { | |||
'^plainlist$', | |||
'%splainlist$', | |||
'^plainlist%s', | |||
'%splainlist%s' | |||
}, | |||
found = false, | |||
styles = 'Plainlist/styles.css' | |||
}, | |||
hlist_t = { | |||
patterns = { | |||
'^hlist$', | |||
'%shlist$', | |||
'^hlist%s', | |||
'%shlist%s' | |||
}, | |||
found = false, | |||
styles = 'Hlist/styles.css' | |||
} | |||
} | |||
local function has_list_class(args_to_check) | |||
for _, list in pairs(lists) do | |||
if not list.found then | |||
for _, arg in pairs(args_to_check) do | |||
for _, pattern in ipairs(list.patterns) do | |||
if mw.ustring.find(arg or '', pattern) then | |||
list.found = true | |||
break | |||
end | |||
end | |||
if list.found then break end | |||
end | |||
end | |||
end | |||
end | |||
local function | local function striped(wikitext) | ||
-- Return wikitext with markers replaced for odd/even striping. | |||
-- Child (subgroup) navboxes are flagged with a category that is removed | |||
-- by parent navboxes. The result is that the category shows all pages | |||
-- where a child navbox is not contained in a parent navbox. | |||
local orphanCat = '[[Category:孤立的子导航框]]' | |||
if border == 'subgroup' and args.orphan ~= 'yes' then | |||
-- No change; striping occurs in outermost navbox. | |||
return wikitext .. orphanCat | |||
end | |||
local first, second = 'odd', 'even' | |||
if args.evenodd then | |||
if args.evenodd == 'swap' then | |||
first, second = second, first | |||
else | |||
first = args.evenodd | |||
second = first | |||
end | |||
end | |||
local changer | |||
if first == second then | |||
changer = first | |||
else | |||
local index = 0 | |||
changer = function (code) | |||
if code == '0' then | |||
-- Current occurrence is for a group before a nested table. | |||
-- Set it to first as a valid although pointless class. | |||
-- The next occurrence will be the first row after a title | |||
-- in a subgroup and will also be first. | |||
index = 0 | |||
return first | |||
end | |||
index = index + 1 | |||
return index % 2 == 1 and first or second | |||
end | |||
end | |||
local regex = orphanCat:gsub('([%[%]])', '%%%1') | |||
return (wikitext:gsub(regex, ''):gsub(REGEX_MARKER, changer)) -- () omits gsub count | |||
end | end | ||
local function addNewline(s) | local function addNewline(s) | ||
if s:match('^[*:;#]') or s:match('^{|') then | |||
return '\n' .. s ..'\n' | |||
else | |||
return s | |||
end | |||
end | |||
local function has_collapsible_toggle() | |||
return args.state ~= 'off' | |||
and args.state ~= 'plain' | |||
end | |||
local function has_navbar() | |||
return args.navbar ~= 'off' | |||
and args.navbar ~= 'plain' | |||
and ( | |||
args.name | |||
or ( | |||
mw.getCurrentFrame():getParent():getTitle():gsub('/sandbox$', '') | |||
~= 'Template:Navbox' and | |||
mw.getCurrentFrame():getParent():getTitle():gsub('/sandbox$', '') | |||
~= 'Template:Navbox subgroup' | |||
) | |||
) | |||
end | end | ||
local function | -- extract text color from css, which is the only permitted inline CSS for the navbar | ||
local function extract_color(css_str) | |||
-- return nil because navbar takes its argument into mw.html which handles | |||
-- nil gracefully, removing the associated style attribute | |||
return mw.ustring.match(';' .. css_str .. ';', '.*;%s*([Cc][Oo][Ll][Oo][Rr]%s*:%s*.-)%s*;') or nil | |||
end | end | ||
local function renderNavBar(titleCell) | local function renderNavBar(titleCell) | ||
if has_navbar() then | |||
local navbar = require('Module:Navbar')._navbar | |||
titleCell:wikitext(navbar{ | |||
args.name, | |||
mini = 1, | |||
fontstyle = extract_color( | |||
(args.basestyle or '') .. ';' .. (args.titlestyle or '') | |||
) | |||
}) | |||
end | |||
end | end | ||
| 第80行: | 第147行: | ||
-- | -- | ||
local function renderTitleRow(tbl) | local function renderTitleRow(tbl) | ||
if not args.title then return end | |||
has_list_class({args.titleclass}) | |||
local titleRow = tbl:tag('tr') | |||
local titleCell = titleRow:tag('th'):attr('scope', 'col') | |||
if has_collapsible_toggle() then | |||
titleCell | |||
:addClass('collapsible-title') | |||
end | |||
local titleColspan = 2 | |||
if args.imageleft then titleColspan = titleColspan + 1 end | |||
if args.image then titleColspan = titleColspan + 1 end | |||
titleCell | |||
:cssText(args.basestyle) | |||
:cssText(args.titlestyle) | |||
:addClass('navbox-title') | |||
:attr('colspan', titleColspan) | |||
renderNavBar(titleCell) | |||
titleCell | |||
:tag('div') | |||
-- id for aria-labelledby attribute | |||
:attr('id', mw.uri.anchorEncode(args.title)) | |||
:addClass(args.titleclass) | |||
:css('font-size', '110%') | |||
:css('margin', '0 5em') | |||
:wikitext(addNewline(args.title)) | |||
end | end | ||
| 第129行: | 第187行: | ||
local function getAboveBelowColspan() | local function getAboveBelowColspan() | ||
local ret = 2 | |||
if args.imageleft then ret = ret + 1 end | |||
if args.image then ret = ret + 1 end | |||
return ret | |||
end | end | ||
local function renderAboveRow(tbl) | local function renderAboveRow(tbl) | ||
if not args.above then return end | |||
has_list_class({ args.aboveclass }) | |||
tbl:tag('tr') | |||
:tag('td') | |||
:addClass('navbox-abovebelow') | |||
:addClass(args.aboveclass) | |||
:cssText(args.basestyle) | |||
:cssText(args.abovestyle) | |||
:attr('colspan', getAboveBelowColspan()) | |||
:tag('div') | |||
-- id for aria-labelledby attribute, if no title | |||
:attr('id', (not args.title) and mw.uri.anchorEncode(args.above) or nil) | |||
:wikitext(addNewline(args.above)) | |||
end | end | ||
local function renderBelowRow(tbl) | local function renderBelowRow(tbl) | ||
if not args.below then return end | |||
has_list_class({ args.belowclass }) | |||
tbl:tag('tr') | |||
:tag('td') | |||
:addClass('navbox-abovebelow') | |||
:addClass(args.belowclass) | |||
:cssText(args.basestyle) | |||
:cssText(args.belowstyle) | |||
:attr('colspan', getAboveBelowColspan()) | |||
:tag('div') | |||
:wikitext(addNewline(args.below)) | |||
end | end | ||
-- | -- | ||
-- List rows | -- List rows | ||
-- | -- | ||
local function renderListRow(tbl, listnum) | local function renderListRow(tbl, listnum) | ||
local row = tbl:tag('tr') | |||
if listnum == 1 and args.imageleft then | |||
has_list_class({ args.imageclass }) | |||
row | |||
:tag('td') | |||
:addClass('noviewer') | |||
:addClass('navbox-image') | |||
:addClass(args.imageclass) | |||
:css('width', '1px') -- Minimize width | |||
:css('padding', '0px 2px 0px 0px') | |||
:cssText(args.imageleftstyle) | |||
:attr('rowspan', #listnums) | |||
:tag('div') | |||
:wikitext(addNewline(args.imageleft)) | |||
end | |||
if args['group' .. listnum] then | |||
has_list_class({ args.groupclass }) | |||
local groupCell = row:tag('th') | |||
-- id for aria-labelledby attribute, if lone group with no title or above | |||
if listnum == 1 and not (args.title or args.above or args.group2) then | |||
groupCell | |||
:attr('id', mw.uri.anchorEncode(args.group1)) | |||
end | |||
groupCell | |||
:attr('scope', 'row') | |||
:addClass('navbox-group') | |||
:addClass(args.groupclass) | |||
:cssText(args.basestyle) | |||
:css('width', args.groupwidth or '1%') -- If groupwidth not specified, minimize width | |||
groupCell | |||
:cssText(args.groupstyle) | |||
:cssText(args['group' .. listnum .. 'style']) | |||
:wikitext(args['group' .. listnum]) | |||
end | |||
has_list_class({ args.listclass, args['list' .. listnum .. 'class'] }) | |||
local listCell = row:tag('td') | |||
if args['group' .. listnum] then | |||
listCell | |||
:addClass('navbox-list-with-group') | |||
else | |||
listCell:attr('colspan', 2) | |||
end | |||
if not args.groupwidth then | |||
listCell:css('width', '100%') | |||
end | |||
local rowstyle -- usually nil so cssText(rowstyle) usually adds nothing | |||
if listnum % 2 == 1 then | |||
rowstyle = args.oddstyle | |||
else | |||
rowstyle = args.evenstyle | |||
end | |||
local listText = args['list' .. listnum] | |||
local oddEven = ODD_EVEN_MARKER | |||
if listText:sub(1, 12) == '</div><table' then | |||
-- Assume list text is for a subgroup navbox so no automatic striping for this row. | |||
oddEven = listText:find('<th[^>]*["? ?]navbox%-title"') and RESTART_MARKER or 'odd' | |||
end | |||
listCell | |||
:css('padding', '0px') | |||
:cssText(args.liststyle) | |||
:cssText(rowstyle) | |||
:cssText(args['list' .. listnum .. 'style']) | |||
:addClass('navbox-list') | |||
:addClass('navbox-' .. oddEven) | |||
:addClass(args.listclass) | |||
:addClass(args['list' .. listnum .. 'class']) | |||
:tag('div') | |||
:css('padding', (listnum == 1 and args.list1padding) or args.listpadding or '0em 0.25em') | |||
:wikitext(addNewline(listText)) | |||
if listnum == 1 and args.image then | |||
has_list_class({ args.imageclass }) | |||
row | |||
:tag('td') | |||
:addClass('noviewer') | |||
:addClass('navbox-image') | |||
:addClass(args.imageclass) | |||
:css('width', '1px') -- Minimize width | |||
:css('padding', '0px 0px 0px 2px') | |||
:cssText(args.imagestyle) | |||
:attr('rowspan', #listnums) | |||
:tag('div') | |||
:wikitext(addNewline(args.image)) | |||
end | |||
end | end | ||
-- | -- | ||
| 第259行: | 第336行: | ||
local function needsHorizontalLists() | local function needsHorizontalLists() | ||
if border == 'subgroup' or args.tracking == 'no' then | |||
return false | |||
end | |||
return not lists.hlist_t.found and not lists.plainlist_t.found | |||
end | end | ||
local function hasBackgroundColors() | local function hasBackgroundColors() | ||
for _, key in ipairs({'titlestyle', 'groupstyle', 'basestyle', 'abovestyle', 'belowstyle'}) do | |||
if tostring(args[key]):find('background', 1, true) then | |||
return true | |||
end | |||
end | |||
end | end | ||
local function argNameAndRealTitleAreDifferent() | local function argNameAndRealTitleAreDifferent() | ||
if | if border == 'subgroup' or args.tracking == 'no' then | ||
return false | |||
end | |||
if args.name ~= mw.title.getCurrentTitle().text then | if has_navbar() and args.name ~= mw.title.getCurrentTitle().text then | ||
return true | return true | ||
end | end | ||
return false | return false | ||
end | end | ||
local function getTrackingCategories() | local function getTrackingCategories() | ||
local cats = {} | |||
if needsHorizontalLists() then table.insert(cats, '没有使用水平列表的导航框') end | |||
if hasBackgroundColors() then table.insert(cats, '使用背景颜色的导航框') end | |||
if argNameAndRealTitleAreDifferent() then table.insert(cats, 'name參數和實際不同的導航框') end | |||
return cats | |||
end | end | ||
local function renderTrackingCategories(builder) | local function renderTrackingCategories(builder) | ||
local title = mw.title.getCurrentTitle() | |||
if title.namespace ~= 10 then return end -- not in template space | |||
local subpage = title.subpageText | |||
if subpage == 'doc' or subpage == 'sandbox' or subpage == 'testcases' then return end | |||
for _, cat in ipairs(getTrackingCategories()) do | |||
builder:wikitext('[[Category:' .. cat .. ']]') | |||
end | |||
end | |||
-- | |||
-- Load the templatestyles for the navbox | |||
-- | |||
local function loadTemplateStyles(hiding_templatestyles) | |||
local frame = mw.getCurrentFrame() | |||
local hlist_templatestyles = '' | |||
if lists.hlist_t.found then | |||
hlist_templatestyles = frame:extensionTag{ | |||
name = 'templatestyles', args = { src = lists.hlist_t.styles } | |||
} | |||
end | |||
-- 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() and hlist_templatestyles == '' then | |||
hlist_templatestyles = frame:extensionTag{ | |||
name = 'templatestyles', args = { src = lists.hlist_t.styles } | |||
} | |||
end | |||
local plainlist_templatestyles = '' | |||
if lists.plainlist_t.found then | |||
plainlist_templatestyles = frame:extensionTag{ | |||
name = 'templatestyles', args = { src = lists.plainlist_t.styles } | |||
} | |||
end | |||
local base_templatestyles = frame:extensionTag{ | |||
name = 'templatestyles', args = { src = 'Module:Navbox/styles.css' } | |||
} | |||
local templatestyles = '' | |||
if args['templatestyles'] then | |||
templatestyles = frame:extensionTag{ | |||
name = 'templatestyles', args = { src = args['templatestyles'] } | |||
} | |||
end | |||
local child_templatestyles = '' | |||
if args['child templatestyles'] then | |||
child_templatestyles = frame:extensionTag{ | |||
name = 'templatestyles', args = { src = args['child templatestyles'] } | |||
} | |||
end | |||
return mw.html.create('div') | |||
:addClass('navbox-styles') | |||
:wikitext( | |||
-- hlist -> plainlist -> base is best-effort to preserve old Common.css ordering. | |||
-- this ordering is not a guarantee because the rows of interest invoking | |||
-- each class may not be on a specific page | |||
hlist_templatestyles .. | |||
plainlist_templatestyles .. | |||
base_templatestyles .. | |||
templatestyles .. | |||
child_templatestyles .. | |||
table.concat(hiding_templatestyles) | |||
) | |||
:done() | |||
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 | end | ||
| 第307行: | 第466行: | ||
-- | -- | ||
local function renderMainTable() | local function renderMainTable() | ||
local tbl = mw.html.create('table') | |||
:addClass('nowraplinks') | |||
:addClass(args.bodyclass) | |||
has_list_class({ args.bodyclass }) | |||
if args.title and has_collapsible_toggle() then | |||
if args.state == 'collapsed' then args.state = 'mw-collapsed' end | |||
tbl | |||
:addClass('mw-collapsible') | |||
:addClass(args.state or 'autocollapse') | |||
end | |||
tbl:css('border-spacing', 0) | |||
if border == 'subgroup' or border == 'none' then | |||
tbl | |||
:addClass('navbox-subgroup') | |||
:cssText(args.bodystyle) | |||
:cssText(args.style) | |||
else -- regular navbox - bodystyle and style will be applied to the wrapper table | |||
tbl | |||
:addClass('navbox-inner') | |||
:css('background', 'transparent') | |||
:css('color', 'inherit') | |||
end | |||
tbl:cssText(args.innerstyle) | |||
renderTitleRow(tbl) | |||
renderAboveRow(tbl) | |||
for i, listnum in ipairs(listnums) do | |||
renderListRow(tbl, listnum) | |||
end | |||
renderBelowRow(tbl) | |||
return tbl | |||
end | end | ||
function p._navbox(navboxArgs) | function p._navbox(navboxArgs) | ||
args = navboxArgs | |||
local hiding_templatestyles = move_hiding_templatestyles(args) | |||
listnums = {} | |||
for k, v in pairs(args) do | |||
local listnum = ('' .. k):match('^list(%d+)$') | |||
if listnum then table.insert(listnums, tonumber(listnum)) end | |||
end | |||
table.sort(listnums) | |||
border = mw.text.trim(args.border or args[1] or '') | |||
if border == 'child' then | |||
border = 'subgroup' | |||
end | |||
-- render the main body of the navbox | |||
local tbl = renderMainTable() | |||
-- render the appropriate wrapper around the navbox, depending on the border param | |||
local res = mw.html.create() | |||
if border == 'none' then | |||
res:node(loadTemplateStyles(hiding_templatestyles)) | |||
local nav = res:tag('div') | |||
:attr('role', 'navigation') | |||
:node(tbl) | |||
-- aria-labelledby title, otherwise above, otherwise lone group | |||
if args.title or args.above or (args.group1 and not args.group2) then | |||
nav:attr('aria-labelledby', mw.uri.anchorEncode(args.title or args.above or args.group1)) | |||
else | |||
nav:attr('aria-label', 'Navbox') | |||
end | |||
elseif border == 'subgroup' then | |||
-- We assume that this navbox is being rendered in a list cell of a parent navbox, and is | |||
-- therefore inside a div with padding:0em 0.25em. We start with a </div> to avoid the | |||
-- padding being applied, and at the end add a <div> to balance out the parent's </div> | |||
res | |||
:wikitext('</div>') -- mw.html 未支持 unclosed | |||
:node(tbl) | |||
:wikitext('<div>') -- mw.html 未支持 unclosed | |||
else | |||
res:node(loadTemplateStyles(hiding_templatestyles)) | |||
has_list_class({ args.navboxclass }) | |||
local nav = res:tag('div') | |||
:attr('role', 'navigation') | |||
:addClass('navbox') | |||
:addClass(args.navboxclass) | |||
:cssText(args.bodystyle) | |||
:cssText(args.style) | |||
:css('padding', '3px') | |||
:node(tbl) | |||
-- aria-labelledby title, otherwise above, otherwise lone group | |||
if args.title or args.above or (args.group1 and not args.group2) then | |||
nav:attr('aria-labelledby', mw.uri.anchorEncode(args.title or args.above or args.group1)) | |||
else | |||
nav:attr('aria-label', 'Navbox') | |||
end | |||
end | |||
if (args.nocat or 'false'):lower() == 'false' then | |||
renderTrackingCategories(res) | |||
end | |||
return striped(tostring(res)) | |||
end | end | ||
function p.navbox(frame) | function p.navbox(frame) | ||
if not getArgs then | |||
getArgs = require('Module:Arguments').getArgs | |||
end | |||
args = getArgs(frame, {wrappers = {'Template:Navbox', 'Template:Navbox subgroup'}}) | |||
if frame.args.border then | |||
-- This allows Template:Navbox_subgroup to use {{#invoke:Navbox|navbox|border=...}}. | |||
args.border = frame.args.border | |||
end | |||
-- Read the arguments in the order they'll be output in, to make references number in the right order. | |||
local _ | |||
_ = args.title | |||
_ = args.above | |||
for i = 1, 35 do | |||
_ = args["group" .. tostring(i)] | |||
_ = args["list" .. tostring(i)] | |||
end | |||
_ = args.below | |||
return p._navbox(args) | |||
end | end | ||
return p | return p | ||