コンテンツにスキップ

モジュール:Navbox

提供:KANOTYPE WIKI

このモジュールについての説明文ページを モジュール:Navbox/doc に作成できます

  1 --
  2 -- This module implements {{Navbox}}
  3 --
  4 
  5 local p = {}
  6 
  7 local navbar = require('Module:Navbar')._navbar
  8 local getArgs -- lazily initialized
  9 
 10 local args
 11 local border
 12 local listnums
 13 local ODD_EVEN_MARKER = '\127_ODDEVEN_\127'
 14 local RESTART_MARKER = '\127_ODDEVEN0_\127'
 15 local REGEX_MARKER = '\127_ODDEVEN(%d?)_\127'
 16 
 17 local function striped(wikitext)
 18 	-- Return wikitext with markers replaced for odd/even striping.
 19 	-- Child (subgroup) navboxes are flagged with a category that is removed
 20 	-- by parent navboxes. The result is that the category shows all pages
 21 	-- where a child navbox is not contained in a parent navbox.
 22 	local orphanCat = '[[Category:Navbox orphans]]'
 23 	if border == 'subgroup' and args.orphan ~= 'yes' then
 24 		-- No change; striping occurs in outermost navbox.
 25 		return wikitext .. orphanCat
 26 	end
 27 	local first, second = 'odd', 'even'
 28 	if args.evenodd then
 29 		if args.evenodd == 'swap' then
 30 			first, second = second, first
 31 		else
 32 			first = args.evenodd
 33 			second = first
 34 		end
 35 	end
 36 	local changer
 37 	if first == second then
 38 		changer = first
 39 	else
 40 		local index = 0
 41 		changer = function (code)
 42 			if code == '0' then
 43 				-- Current occurrence is for a group before a nested table.
 44 				-- Set it to first as a valid although pointless class.
 45 				-- The next occurrence will be the first row after a title
 46 				-- in a subgroup and will also be first.
 47 				index = 0
 48 				return first
 49 			end
 50 			index = index + 1
 51 			return index % 2 == 1 and first or second
 52 		end
 53 	end
 54 	local regex = orphanCat:gsub('([%[%]])', '%%%1')
 55 	return (wikitext:gsub(regex, ''):gsub(REGEX_MARKER, changer))  -- () omits gsub count
 56 end
 57 
 58 local function processItem(item, nowrapitems)
 59 	if item:sub(1, 2) == '{|' then
 60 		-- Applying nowrap to lines in a table does not make sense.
 61 		-- Add newlines to compensate for trim of x in |parm=x in a template.
 62 		return '\n' .. item ..'\n'
 63 	end
 64 	if nowrapitems == 'yes' then
 65 		local lines = {}
 66 		for line in (item .. '\n'):gmatch('([^\n]*)\n') do
 67 			local prefix, content = line:match('^([*:;#]+)%s*(.*)')
 68 			if prefix and not content:match('^<span class="nowrap">') then
 69 				line = prefix .. '<span class="nowrap">' .. content .. '</span>'
 70 			end
 71 			table.insert(lines, line)
 72 		end
 73 		item = table.concat(lines, '\n')
 74 	end
 75 	if item:match('^[*:;#]') then
 76 		return '\n' .. item ..'\n'
 77 	end
 78 	return item
 79 end
 80 
 81 -- Separate function so that we can evaluate properly whether hlist should
 82 -- be added by the module
 83 local function has_navbar()
 84 	return args.navbar ~= 'off' and args.navbar ~= 'plain' and not
 85 		(not args.name and mw.getCurrentFrame():getParent():getTitle():gsub('/sandbox$', '') == 'Template:Navbox')
 86 end
 87 
 88 local function renderNavBar(titleCell)
 89 
 90 	if has_navbar() then
 91 		titleCell:wikitext(navbar{
 92 			args.name,
 93 			-- we depend on this being mini = 1 when the navbox module decides
 94 			-- to add hlist templatestyles. we also depend on navbar outputting
 95 			-- a copy of the hlist templatestyles.
 96 			mini = 1,
 97 			fontstyle = (args.basestyle or '') .. ';' .. (args.titlestyle or '') .. ';background:none transparent;border:none;box-shadow:none; padding:0;'
 98 		})
 99 	end
100 
101 end
102 
103 --
104 --   Title row
105 --
106 local function renderTitleRow(tbl)
107 	if not args.title then return end
108 
109 	local titleRow = tbl:tag('tr')
110 
111 	if args.titlegroup then
112 		titleRow
113 			:tag('th')
114 				:attr('scope', 'row')
115 				:addClass('navbox-group')
116 				:addClass(args.titlegroupclass)
117 				:cssText(args.basestyle)
118 				:cssText(args.groupstyle)
119 				:cssText(args.titlegroupstyle)
120 				:wikitext(args.titlegroup)
121 	end
122 
123 	local titleCell = titleRow:tag('th'):attr('scope', 'col')
124 
125 	if args.titlegroup then
126 		titleCell
127 			:addClass('navbox-title1')
128 	end
129 
130 	local titleColspan = 2
131 	if args.imageleft then titleColspan = titleColspan + 1 end
132 	if args.image then titleColspan = titleColspan + 1 end
133 	if args.titlegroup then titleColspan = titleColspan - 1 end
134 
135 	titleCell
136 		:cssText(args.basestyle)
137 		:cssText(args.titlestyle)
138 		:addClass('navbox-title')
139 		:attr('colspan', titleColspan)
140 
141 	renderNavBar(titleCell)
142 
143 	titleCell
144 		:tag('div')
145 			-- id for aria-labelledby attribute
146 			:attr('id', mw.uri.anchorEncode(args.title))
147 			:addClass(args.titleclass)
148 			:css('font-size', '114%')
149 			:css('margin', '0 4em')
150 			:wikitext(processItem(args.title))
151 end
152 
153 --
154 --   Above/Below rows
155 --
156 
157 local function getAboveBelowColspan()
158 	local ret = 2
159 	if args.imageleft then ret = ret + 1 end
160 	if args.image then ret = ret + 1 end
161 	return ret
162 end
163 
164 local function renderAboveRow(tbl)
165 	if not args.above then return end
166 
167 	tbl:tag('tr')
168 		:tag('td')
169 			:addClass('navbox-abovebelow')
170 			:addClass(args.aboveclass)
171 			:cssText(args.basestyle)
172 			:cssText(args.abovestyle)
173 			:attr('colspan', getAboveBelowColspan())
174 			:tag('div')
175 				-- id for aria-labelledby attribute, if no title
176 				:attr('id', args.title and nil or mw.uri.anchorEncode(args.above))
177 				:wikitext(processItem(args.above, args.nowrapitems))
178 end
179 
180 local function renderBelowRow(tbl)
181 	if not args.below then return end
182 
183 	tbl:tag('tr')
184 		:tag('td')
185 			:addClass('navbox-abovebelow')
186 			:addClass(args.belowclass)
187 			:cssText(args.basestyle)
188 			:cssText(args.belowstyle)
189 			:attr('colspan', getAboveBelowColspan())
190 			:tag('div')
191 				:wikitext(processItem(args.below, args.nowrapitems))
192 end
193 
194 --
195 --   List rows
196 --
197 local function renderListRow(tbl, index, listnum)
198 	local row = tbl:tag('tr')
199 
200 	if index == 1 and args.imageleft then
201 		row
202 			:tag('td')
203 				:addClass('navbox-image')
204 				:addClass(args.imageclass)
205 				:css('width', '1px')               -- Minimize width
206 				:css('padding', '0px 2px 0px 0px')
207 				:cssText(args.imageleftstyle)
208 				:attr('rowspan', #listnums)
209 				:tag('div')
210 					:wikitext(processItem(args.imageleft))
211 	end
212 
213 	if args['group' .. listnum] then
214 		local groupCell = row:tag('th')
215 
216 		-- id for aria-labelledby attribute, if lone group with no title or above
217 		if listnum == 1 and not (args.title or args.above or args.group2) then
218 			groupCell
219 				:attr('id', mw.uri.anchorEncode(args.group1))
220 		end
221 
222 		groupCell
223 			:attr('scope', 'row')
224 			:addClass('navbox-group')
225 			:addClass(args.groupclass)
226 			:cssText(args.basestyle)
227 			:css('width', args.groupwidth or '1%') -- If groupwidth not specified, minimize width
228 
229 		groupCell
230 			:cssText(args.groupstyle)
231 			:cssText(args['group' .. listnum .. 'style'])
232 			:wikitext(args['group' .. listnum])
233 	end
234 
235 	local listCell = row:tag('td')
236 
237 	if args['group' .. listnum] then
238 		listCell
239 			:addClass('navbox-list1')
240 	else
241 		listCell:attr('colspan', 2)
242 	end
243 
244 	if not args.groupwidth then
245 		listCell:css('width', '100%')
246 	end
247 
248 	local rowstyle  -- usually nil so cssText(rowstyle) usually adds nothing
249 	if index % 2 == 1 then
250 		rowstyle = args.oddstyle
251 	else
252 		rowstyle = args.evenstyle
253 	end
254 
255 	local listText = args['list' .. listnum]
256 	local oddEven = ODD_EVEN_MARKER
257 	if listText:sub(1, 12) == '</div><table' then
258 		-- Assume list text is for a subgroup navbox so no automatic striping for this row.
259 		oddEven = listText:find('<th[^>]*"navbox%-title"') and RESTART_MARKER or 'odd'
260 	end
261 	listCell
262 		:css('padding', '0px')
263 		:cssText(args.liststyle)
264 		:cssText(rowstyle)
265 		:cssText(args['list' .. listnum .. 'style'])
266 		:addClass('navbox-list')
267 		:addClass('navbox-' .. oddEven)
268 		:addClass(args.listclass)
269 		:addClass(args['list' .. listnum .. 'class'])
270 		:tag('div')
271 			:css('padding', (index == 1 and args.list1padding) or args.listpadding or '0em 0.25em')
272 			:wikitext(processItem(listText, args.nowrapitems))
273 
274 	if index == 1 and args.image then
275 		row
276 			:tag('td')
277 				:addClass('navbox-image')
278 				:addClass(args.imageclass)
279 				:css('width', '1px')               -- Minimize width
280 				:css('padding', '0px 0px 0px 2px')
281 				:cssText(args.imagestyle)
282 				:attr('rowspan', #listnums)
283 				:tag('div')
284 					:wikitext(processItem(args.image))
285 	end
286 end
287 
288 
289 --
290 --   Tracking categories
291 --
292 
293 local function needsHorizontalLists()
294 	if border == 'subgroup' or args.tracking == 'no' then
295 		return false
296 	end
297 	local listClasses = {
298 		['plainlist'] = true, ['hlist'] = true, ['hlist hnum'] = true,
299 		['hlist hwrap'] = true, ['hlist vcard'] = true, ['vcard hlist'] = true,
300 		['hlist vevent'] = true,
301 	}
302 	return not (listClasses[args.listclass] or listClasses[args.bodyclass])
303 end
304 
305 -- there are a lot of list classes in the wild, so we have a function to find
306 -- them and add their TemplateStyles
307 local function addListStyles()
308 	local frame = mw.getCurrentFrame()
309 	-- TODO?: Should maybe take a table of classes for e.g. hnum, hwrap as above
310 	-- I'm going to do the stupid thing first though
311 	-- Also not sure hnum and hwrap are going to live in the same TemplateStyles
312 	-- as hlist
313 	local function _addListStyles(htmlclass, templatestyles)
314 		local class_args = { -- rough order of probability of use
315 			'bodyclass', 'listclass', 'aboveclass', 'belowclass', 'titleclass',
316 			'navboxclass', 'groupclass', 'titlegroupclass', 'imageclass'
317 		}
318 		local patterns = {
319 			'^' .. htmlclass .. '$',
320 			'%s' .. htmlclass .. '$',
321 			'^' .. htmlclass .. '%s',
322 			'%s' .. htmlclass .. '%s'
323 		}
324 		
325 		local found = false
326 		for _, arg in ipairs(class_args) do
327 			for _, pattern in ipairs(patterns) do
328 				if mw.ustring.find(args[arg] or '', pattern) then
329 					found = true
330 					break
331 				end
332 			end
333 			if found then break end
334 		end
335 		if found then
336 			return frame:extensionTag{
337 				name = 'templatestyles', args = { src = templatestyles }
338 			}
339 		else
340 			return ''
341 		end
342 	end
343 	
344 	local hlist_styles = ''
345 	-- navbar always has mini = 1, so here (on this wiki) we can assume that
346 	-- we don't need to output hlist styles in navbox again.
347 	if not has_navbar() then
348 		hlist_styles = _addListStyles('hlist', 'Flatlist/styles.css')
349 	end
350 	local plainlist_styles = _addListStyles('plainlist', 'Plainlist/styles.css')
351 	
352 	return hlist_styles .. plainlist_styles
353 end
354 
355 local function hasBackgroundColors()
356 	for _, key in ipairs({'titlestyle', 'groupstyle', 'basestyle', 'abovestyle', 'belowstyle'}) do
357 		if tostring(args[key]):find('background', 1, true) then
358 			return true
359 		end
360 	end
361 end
362 
363 local function hasBorders()
364 	for _, key in ipairs({'groupstyle', 'basestyle', 'abovestyle', 'belowstyle'}) do
365 		if tostring(args[key]):find('border', 1, true) then
366 			return true
367 		end
368 	end
369 end
370 
371 local function isIllegible()
372 	-- require('Module:Color contrast') absent on mediawiki.org
373 	return false
374 end
375 
376 local function getTrackingCategories()
377 	local cats = {}
378 	if needsHorizontalLists() then table.insert(cats, 'Navigational boxes without horizontal lists') end
379 	if hasBackgroundColors() then table.insert(cats, 'Navboxes using background colours') end
380 	if isIllegible() then table.insert(cats, 'Potentially illegible navboxes') end
381 	if hasBorders() then table.insert(cats, 'Navboxes using borders') end
382 	return cats
383 end
384 
385 local function renderTrackingCategories(builder)
386 	local title = mw.title.getCurrentTitle()
387 	if title.namespace ~= 10 then return end -- not in template space
388 	local subpage = title.subpageText
389 	if subpage == 'doc' or subpage == 'sandbox' or subpage == 'testcases' then return end
390 
391 	for _, cat in ipairs(getTrackingCategories()) do
392 		builder:wikitext('[[Category:' .. cat .. ']]')
393 	end
394 end
395 
396 --
397 --   Main navbox tables
398 --
399 local function renderMainTable()
400 	local tbl = mw.html.create('table')
401 		:addClass('nowraplinks')
402 		:addClass(args.bodyclass)
403 
404 	if args.title and (args.state ~= 'plain' and args.state ~= 'off') then
405 		if args.state == 'collapsed' then args.state = 'mw-collapsed' end
406 		tbl
407 			:addClass('mw-collapsible')
408 			:addClass(args.state or 'autocollapse')
409 	end
410 
411 	tbl:css('border-spacing', 0)
412 	if border == 'subgroup' or border == 'none' then
413 		tbl
414 			:addClass('navbox-subgroup')
415 			:cssText(args.bodystyle)
416 			:cssText(args.style)
417 	else  -- regular navbox - bodystyle and style will be applied to the wrapper table
418 		tbl
419 			:addClass('navbox-inner')
420 			:css('background', 'transparent')
421 			:css('color', 'inherit')
422 	end
423 	tbl:cssText(args.innerstyle)
424 
425 	renderTitleRow(tbl)
426 	renderAboveRow(tbl)
427 	for i, listnum in ipairs(listnums) do
428 		renderListRow(tbl, i, listnum)
429 	end
430 	renderBelowRow(tbl)
431 
432 	return tbl
433 end
434 
435 function p._navbox(navboxArgs)
436 	args = navboxArgs
437 	listnums = {}
438 
439 	for k, _ in pairs(args) do
440 		if type(k) == 'string' then
441 			local listnum = k:match('^list(%d+)$')
442 			if listnum then table.insert(listnums, tonumber(listnum)) end
443 		end
444 	end
445 	table.sort(listnums)
446 
447 	border = mw.text.trim(args.border or args[1] or '')
448 	if border == 'child' then
449 		border = 'subgroup'
450 	end
451 
452 	-- render the main body of the navbox
453 	local tbl = renderMainTable()
454 	
455 	-- get templatestyles
456 	local frame = mw.getCurrentFrame()
457 	local base_templatestyles = frame:extensionTag{
458 		name = 'templatestyles', args = { src = 'Module:Navbox/styles.css' }
459 	}
460 	local templatestyles = ''
461 	if args.templatestyles and args.templatestyles ~= '' then
462 		templatestyles = frame:extensionTag{
463 			name = 'templatestyles', args = { src = args.templatestyles }
464 		}
465 	end
466 	
467 	local res = mw.html.create()
468 	-- 'navbox-styles' exists for two reasons:
469 	--  1. To wrap the styles to work around phab: T200206 more elegantly. Instead
470 	--	   of combinatorial rules, this ends up being linear number of CSS rules.
471 	--  2. To allow MobileFrontend to rip the styles out with 'nomobile' such that
472 	--     they are not dumped into the mobile view.
473 	res:tag('div')
474 		:addClass('navbox-styles')
475 		:addClass('nomobile')
476 		:wikitext(base_templatestyles .. templatestyles)
477 		:done()
478 	
479 	-- render the appropriate wrapper around the navbox, depending on the border param
480 	if border == 'none' then
481 		local nav = res:tag('div')
482 			:attr('role', 'navigation')
483 			:wikitext(addListStyles())
484 			:node(tbl)
485 		-- aria-labelledby title, otherwise above, otherwise lone group
486 		if args.title or args.above or (args.group1 and not args.group2) then
487 			nav:attr('aria-labelledby', mw.uri.anchorEncode(args.title or args.above or args.group1))
488 		else
489 			nav:attr('aria-label', 'Navbox')
490 		end
491 	elseif border == 'subgroup' then
492 		-- We assume that this navbox is being rendered in a list cell of a
493 		-- parent navbox, and is therefore inside a div with padding:0em 0.25em.
494 		-- We start with a </div> to avoid the padding being applied, and at the
495 		-- end add a <div> to balance out the parent's </div>
496 		res
497 			:wikitext('</div>')
498 			:wikitext(addListStyles())
499 			:node(tbl)
500 			:wikitext('<div>')
501 	else
502 		local nav = res:tag('div')
503 			:attr('role', 'navigation')
504 			:addClass('navbox')
505 			:addClass(args.navboxclass)
506 			:cssText(args.bodystyle)
507 			:cssText(args.style)
508 			:css('padding', '3px')
509 			:wikitext(addListStyles())
510 			:node(tbl)
511 		-- aria-labelledby title, otherwise above, otherwise lone group
512 		if args.title or args.above or (args.group1 and not args.group2) then
513 			nav:attr('aria-labelledby', mw.uri.anchorEncode(args.title or args.above or args.group1))
514 		else
515 			nav:attr('aria-label', 'Navbox')
516 		end
517 	end
518 
519 	if (args.nocat or 'false'):lower() == 'false' then
520 		renderTrackingCategories(res)
521 	end
522 
523 	return striped(tostring(res))
524 end
525 
526 function p.navbox(frame)
527 	if not getArgs then
528 		getArgs = require('Module:Arguments').getArgs
529 	end
530 	args = getArgs(frame, {wrappers = {'Template:Navbox', 'Template:Navbox subgroup'}})
531 	if frame.args.border then
532 		-- This allows Template:Navbox_subgroup to use {{#invoke:Navbox|navbox|border=...}}.
533 		args.border = frame.args.border
534 	end
535 
536 	-- Read the arguments in the order they'll be output in, to make references number in the right order.
537 	local _
538 	_ = args.title
539 	_ = args.above
540 	for i = 1, 20 do
541 		_ = args["group" .. tostring(i)]
542 		_ = args["list" .. tostring(i)]
543 	end
544 	_ = args.below
545 
546 	return p._navbox(args)
547 end
548 
549 return p