コンテンツにスキップ

モジュール:Message box

提供:KANOTYPE WIKI

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

  1 require('strict')
  2 local getArgs
  3 local yesno = require('Module:Yesno')
  4 local lang = mw.language.getContentLanguage()
  5 
  6 local CONFIG_MODULE = 'Module:Message box/configuration'
  7 local DEMOSPACES = {talk = 'tmbox', image = 'imbox', file = 'imbox', category = 'cmbox', article = 'ambox', main = 'ambox'}
  8 
  9 --------------------------------------------------------------------------------
 10 -- Helper functions
 11 --------------------------------------------------------------------------------
 12 
 13 local function getTitleObject(...)
 14 	-- Get the title object, passing the function through pcall
 15 	-- in case we are over the expensive function count limit.
 16 	local success, title = pcall(mw.title.new, ...)
 17 	if success then
 18 		return title
 19 	end
 20 end
 21 
 22 local function union(t1, t2)
 23 	-- Returns the union of two arrays.
 24 	local vals = {}
 25 	for i, v in ipairs(t1) do
 26 		vals[v] = true
 27 	end
 28 	for i, v in ipairs(t2) do
 29 		vals[v] = true
 30 	end
 31 	local ret = {}
 32 	for k in pairs(vals) do
 33 		table.insert(ret, k)
 34 	end
 35 	table.sort(ret)
 36 	return ret
 37 end
 38 
 39 local function getArgNums(args, prefix)
 40 	local nums = {}
 41 	for k, v in pairs(args) do
 42 		local num = mw.ustring.match(tostring(k), '^' .. prefix .. '([1-9]%d*)$')
 43 		if num then
 44 			table.insert(nums, tonumber(num))
 45 		end
 46 	end
 47 	table.sort(nums)
 48 	return nums
 49 end
 50 
 51 --------------------------------------------------------------------------------
 52 -- Box class definition
 53 --------------------------------------------------------------------------------
 54 
 55 local MessageBox = {}
 56 MessageBox.__index = MessageBox
 57 
 58 function MessageBox.new(boxType, args, cfg)
 59 	args = args or {}
 60 	local obj = {}
 61 
 62 	-- Set the title object and the namespace.
 63 	obj.title = getTitleObject(args.page) or mw.title.getCurrentTitle()
 64 
 65 	-- Set the config for our box type.
 66 	obj.cfg = cfg[boxType]
 67 	if not obj.cfg then
 68 		local ns = obj.title.namespace
 69 		-- boxType is "mbox" or invalid input
 70 		if args.demospace and args.demospace ~= '' then
 71 			-- implement demospace parameter of mbox
 72 			local demospace = string.lower(args.demospace)
 73 			if DEMOSPACES[demospace] then
 74 				-- use template from DEMOSPACES
 75 				obj.cfg = cfg[DEMOSPACES[demospace]]
 76 			elseif string.find( demospace, 'talk' ) then
 77 				-- demo as a talk page
 78 				obj.cfg = cfg.tmbox
 79 			else
 80 				-- default to ombox
 81 				obj.cfg = cfg.ombox
 82 			end
 83 		elseif ns == 0 then
 84 			obj.cfg = cfg.ambox -- main namespace
 85 		elseif ns == 6 then
 86 			obj.cfg = cfg.imbox -- file namespace
 87 		elseif ns == 14 then
 88 			obj.cfg = cfg.cmbox -- category namespace
 89 		else
 90 			local nsTable = mw.site.namespaces[ns]
 91 			if nsTable and nsTable.isTalk then
 92 				obj.cfg = cfg.tmbox -- any talk namespace
 93 			else
 94 				obj.cfg = cfg.ombox -- other namespaces or invalid input
 95 			end
 96 		end
 97 	end
 98 
 99 	-- Set the arguments, and remove all blank arguments except for the ones
100 	-- listed in cfg.allowBlankParams.
101 	do
102 		local newArgs = {}
103 		for k, v in pairs(args) do
104 			if v ~= '' then
105 				newArgs[k] = v
106 			end
107 		end
108 		for i, param in ipairs(obj.cfg.allowBlankParams or {}) do
109 			newArgs[param] = args[param]
110 		end
111 		obj.args = newArgs
112 	end
113 
114 	-- Define internal data structure.
115 	obj.categories = {}
116 	obj.classes = {}
117 	-- For lazy loading of [[Module:Category handler]].
118 	obj.hasCategories = false
119 
120 	return setmetatable(obj, MessageBox)
121 end
122 
123 function MessageBox:addCat(ns, cat, sort)
124 	if not cat then
125 		return nil
126 	end
127 	if sort then
128 		cat = string.format('[[Category:%s|%s]]', cat, sort)
129 	else
130 		cat = string.format('[[Category:%s]]', cat)
131 	end
132 	self.hasCategories = true
133 	self.categories[ns] = self.categories[ns] or {}
134 	table.insert(self.categories[ns], cat)
135 end
136 
137 function MessageBox:addClass(class)
138 	if not class then
139 		return nil
140 	end
141 	table.insert(self.classes, class)
142 end
143 
144 function MessageBox:setParameters()
145 	local args = self.args
146 	local cfg = self.cfg
147 
148 	-- Get type data.
149 	self.type = args.type
150 	local typeData = cfg.types[self.type]
151 	self.invalidTypeError = cfg.showInvalidTypeError
152 		and self.type
153 		and not typeData
154 	typeData = typeData or cfg.types[cfg.default]
155 	self.typeClass = typeData.class
156 	self.typeImage = typeData.image
157 	self.typeImageNeedsLink = typeData.imageNeedsLink
158 
159 	-- Find if the box has been wrongly substituted.
160 	self.isSubstituted = cfg.substCheck and args.subst == 'SUBST'
161 
162 	-- Find whether we are using a small message box.
163 	self.isSmall = cfg.allowSmall and (
164 		cfg.smallParam and args.small == cfg.smallParam
165 		or not cfg.smallParam and yesno(args.small)
166 	)
167 
168 	-- Add attributes, classes and styles.
169 	self.id = args.id
170 	self.name = args.name
171 	if self.name then
172 		self:addClass('box-' .. string.gsub(self.name,' ','_'))
173 	end
174 	if yesno(args.plainlinks) ~= false then
175 		self:addClass('plainlinks')
176 	end
177 	for _, class in ipairs(cfg.classes or {}) do
178 		self:addClass(class)
179 	end
180 	if self.isSmall then
181 		self:addClass(cfg.smallClass or 'mbox-small')
182 	end
183 	self:addClass(self.typeClass)
184 	self:addClass(args.class)
185 	self.style = args.style
186 	self.attrs = args.attrs
187 
188 	-- Set text style.
189 	self.textstyle = args.textstyle
190 	
191 	-- Set image classes.
192 	self.imageRightClass = args.imagerightclass or args.imageclass
193 	self.imageLeftClass = args.imageleftclass or args.imageclass
194 
195 	-- Find if we are on the template page or not. This functionality is only
196 	-- used if useCollapsibleTextFields is set, or if both cfg.templateCategory
197 	-- and cfg.templateCategoryRequireName are set.
198 	self.useCollapsibleTextFields = cfg.useCollapsibleTextFields
199 	if self.useCollapsibleTextFields
200 		or cfg.templateCategory
201 		and cfg.templateCategoryRequireName
202 	then
203 		if self.name then
204 			local templateName = mw.ustring.match(
205 				self.name,
206 				'^[tT][eE][mM][pP][lL][aA][tT][eE][%s_]*:[%s_]*(.*)$'
207 			) or self.name
208 			templateName = 'Template:' .. templateName
209 			self.templateTitle = getTitleObject(templateName)
210 		end
211 		self.isTemplatePage = self.templateTitle
212 			and mw.title.equals(self.title, self.templateTitle)
213 	end
214 	
215 	-- Process data for collapsible text fields. At the moment these are only
216 	-- used in {{ambox}}.
217 	if self.useCollapsibleTextFields then
218 		-- Get the self.issue value.
219 		if self.isSmall and args.smalltext then
220 			self.issue = args.smalltext
221 		else
222 			local sect
223 			if args.sect == '' then
224 				sect = 'This ' .. (cfg.sectionDefault or 'page')
225 			elseif type(args.sect) == 'string' then
226 				sect = 'This ' .. args.sect
227 			end
228 			local issue = args.issue
229 			issue = type(issue) == 'string' and issue ~= '' and issue or nil
230 			local text = args.text
231 			text = type(text) == 'string' and text or nil
232 			local issues = {}
233 			table.insert(issues, sect)
234 			table.insert(issues, issue)
235 			table.insert(issues, text)
236 			self.issue = table.concat(issues, ' ')
237 		end
238 
239 		-- Get the self.talk value.
240 		local talk = args.talk
241 		-- Show talk links on the template page or template subpages if the talk
242 		-- parameter is blank.
243 		if talk == ''
244 			and self.templateTitle
245 			and (
246 				mw.title.equals(self.templateTitle, self.title)
247 				or self.title:isSubpageOf(self.templateTitle)
248 			)
249 		then
250 			talk = '#'
251 		elseif talk == '' then
252 			talk = nil
253 		end
254 		if talk then
255 			-- If the talk value is a talk page, make a link to that page. Else
256 			-- assume that it's a section heading, and make a link to the talk
257 			-- page of the current page with that section heading.
258 			local talkTitle = getTitleObject(talk)
259 			local talkArgIsTalkPage = true
260 			if not talkTitle or not talkTitle.isTalkPage then
261 				talkArgIsTalkPage = false
262 				talkTitle = getTitleObject(
263 					self.title.text,
264 					mw.site.namespaces[self.title.namespace].talk.id
265 				)
266 			end
267 			if talkTitle and talkTitle.exists then
268                 local talkText
269                 if self.isSmall then
270                     local talkLink = talkArgIsTalkPage and talk or (talkTitle.prefixedText .. (talk == '#' and '' or '#') .. talk)
271                     talkText = string.format('([[%s|talk]])', talkLink)
272                 else
273                     talkText = 'Relevant discussion may be found on'
274                     if talkArgIsTalkPage then
275                         talkText = string.format(
276                             '%s [[%s|%s]].',
277                             talkText,
278                             talk,
279                             talkTitle.prefixedText
280                         )
281                     else
282                         talkText = string.format(
283                             '%s the [[%s' .. (talk == '#' and '' or '#') .. '%s|talk page]].',
284                             talkText,
285                             talkTitle.prefixedText,
286                             talk
287                         )
288                     end
289                 end
290 				self.talk = talkText
291 			end
292 		end
293 
294 		-- Get other values.
295 		self.fix = args.fix ~= '' and args.fix or nil
296 		local date
297 		if args.date and args.date ~= '' then
298 			date = args.date
299 		elseif args.date == '' and self.isTemplatePage then
300 			date = lang:formatDate('F Y')
301 		end
302 		if date then
303 			self.date = string.format(" <span class='date-container'><i>(<span class='date'>%s</span>)</i></span>", date)
304 		end
305 		self.info = args.info
306 		if yesno(args.removalnotice) then
307 			self.removalNotice = cfg.removalNotice
308 		end
309 	end
310 
311 	-- Set the non-collapsible text field. At the moment this is used by all box
312 	-- types other than ambox, and also by ambox when small=yes.
313 	if self.isSmall then
314 		self.text = args.smalltext or args.text
315 	else
316 		self.text = args.text
317 	end
318 
319 	-- Set the below row.
320 	self.below = cfg.below and args.below
321 
322 	-- General image settings.
323 	self.imageCellDiv = not self.isSmall and cfg.imageCellDiv
324 	self.imageEmptyCell = cfg.imageEmptyCell
325 
326 	-- Left image settings.
327 	local imageLeft = self.isSmall and args.smallimage or args.image
328 	if cfg.imageCheckBlank and imageLeft ~= 'blank' and imageLeft ~= 'none'
329 		or not cfg.imageCheckBlank and imageLeft ~= 'none'
330 	then
331 		self.imageLeft = imageLeft
332 		if not imageLeft then
333 			local imageSize = self.isSmall
334 				and (cfg.imageSmallSize or '30x30px')
335 				or '40x40px'
336 			self.imageLeft = string.format('[[File:%s|%s%s|alt=]]', self.typeImage
337 				or 'Information icon4.svg', imageSize, self.typeImageNeedsLink and "" or "|link=" )
338 		end
339 	end
340 
341 	-- Right image settings.
342 	local imageRight = self.isSmall and args.smallimageright or args.imageright
343 	if not (cfg.imageRightNone and imageRight == 'none') then
344 		self.imageRight = imageRight
345 	end
346 	
347 	-- set templatestyles
348 	self.base_templatestyles = cfg.templatestyles
349 	self.templatestyles = args.templatestyles
350 end
351 
352 function MessageBox:setMainspaceCategories()
353 	local args = self.args
354 	local cfg = self.cfg
355 
356 	if not cfg.allowMainspaceCategories then
357 		return nil
358 	end
359 
360 	local nums = {}
361 	for _, prefix in ipairs{'cat', 'category', 'all'} do
362 		args[prefix .. '1'] = args[prefix]
363 		nums = union(nums, getArgNums(args, prefix))
364 	end
365 
366 	-- The following is roughly equivalent to the old {{Ambox/category}}.
367 	local date = args.date
368 	date = type(date) == 'string' and date
369 	local preposition = 'from'
370 	for _, num in ipairs(nums) do
371 		local mainCat = args['cat' .. tostring(num)]
372 			or args['category' .. tostring(num)]
373 		local allCat = args['all' .. tostring(num)]
374 		mainCat = type(mainCat) == 'string' and mainCat
375 		allCat = type(allCat) == 'string' and allCat
376 		if mainCat and date and date ~= '' then
377 			local catTitle = string.format('%s %s %s', mainCat, preposition, date)
378 			self:addCat(0, catTitle)
379 			catTitle = getTitleObject('Category:' .. catTitle)
380 			if not catTitle or not catTitle.exists then
381 				self:addCat(0, 'Articles with invalid date parameter in template')
382 			end
383 		elseif mainCat and (not date or date == '') then
384 			self:addCat(0, mainCat)
385 		end
386 		if allCat then
387 			self:addCat(0, allCat)
388 		end
389 	end
390 end
391 
392 function MessageBox:setTemplateCategories()
393 	local args = self.args
394 	local cfg = self.cfg
395 
396 	-- Add template categories.
397 	if cfg.templateCategory then
398 		if cfg.templateCategoryRequireName then
399 			if self.isTemplatePage then
400 				self:addCat(10, cfg.templateCategory)
401 			end
402 		elseif not self.title.isSubpage then
403 			self:addCat(10, cfg.templateCategory)
404 		end
405 	end
406 
407 	-- Add template error categories.
408 	if cfg.templateErrorCategory then
409 		local templateErrorCategory = cfg.templateErrorCategory
410 		local templateCat, templateSort
411 		if not self.name and not self.title.isSubpage then
412 			templateCat = templateErrorCategory
413 		elseif self.isTemplatePage then
414 			local paramsToCheck = cfg.templateErrorParamsToCheck or {}
415 			local count = 0
416 			for i, param in ipairs(paramsToCheck) do
417 				if not args[param] then
418 					count = count + 1
419 				end
420 			end
421 			if count > 0 then
422 				templateCat = templateErrorCategory
423 				templateSort = tostring(count)
424 			end
425 			if self.categoryNums and #self.categoryNums > 0 then
426 				templateCat = templateErrorCategory
427 				templateSort = 'C'
428 			end
429 		end
430 		self:addCat(10, templateCat, templateSort)
431 	end
432 end
433 
434 function MessageBox:setAllNamespaceCategories()
435 	-- Set categories for all namespaces.
436 	if self.invalidTypeError then
437 		local allSort = (self.title.namespace == 0 and 'Main:' or '') .. self.title.prefixedText
438 		self:addCat('all', 'Wikipedia message box parameter needs fixing', allSort)
439 	end
440 	if self.isSubstituted then
441 		self:addCat('all', 'Pages with incorrectly substituted templates')
442 	end
443 end
444 
445 function MessageBox:setCategories()
446 	if self.title.namespace == 0 then
447 		self:setMainspaceCategories()
448 	elseif self.title.namespace == 10 then
449 		self:setTemplateCategories()
450 	end
451 	self:setAllNamespaceCategories()
452 end
453 
454 function MessageBox:renderCategories()
455 	if not self.hasCategories then
456 		-- No categories added, no need to pass them to Category handler so,
457 		-- if it was invoked, it would return the empty string.
458 		-- So we shortcut and return the empty string.
459 		return ""
460 	end
461 	-- Convert category tables to strings and pass them through
462 	-- [[Module:Category handler]].
463 	return require('Module:Category handler')._main{
464 		main = table.concat(self.categories[0] or {}),
465 		template = table.concat(self.categories[10] or {}),
466 		all = table.concat(self.categories.all or {}),
467 		nocat = self.args.nocat,
468 		page = self.args.page
469 	}
470 end
471 
472 function MessageBox:export()
473 	local root = mw.html.create()
474 
475 	-- Add the subst check error.
476 	if self.isSubstituted and self.name then
477 		root:tag('b')
478 			:addClass('error')
479 			:wikitext(string.format(
480 				'Template <code>%s[[Template:%s|%s]]%s</code> has been incorrectly substituted.',
481 				mw.text.nowiki('{{'), self.name, self.name, mw.text.nowiki('}}')
482 			))
483 	end
484 
485 	local frame = mw.getCurrentFrame()
486 	root:wikitext(frame:extensionTag{
487 		name = 'templatestyles',
488 		args = { src = self.base_templatestyles },
489 	})
490 	-- Add support for a single custom templatestyles sheet. Undocumented as
491 	-- need should be limited and many templates using mbox are substed; we
492 	-- don't want to spread templatestyles sheets around to arbitrary places
493 	if self.templatestyles then
494 		root:wikitext(frame:extensionTag{
495 			name = 'templatestyles',
496 			args = { src = self.templatestyles },
497 		})
498 	end
499 
500 	-- Create the box table.
501 	local boxTable = root:tag('table')
502 	boxTable:attr('id', self.id or nil)
503 	for i, class in ipairs(self.classes or {}) do
504 		boxTable:addClass(class or nil)
505 	end
506 	boxTable
507 		:cssText(self.style or nil)
508 		:attr('role', 'presentation')
509 
510 	if self.attrs then
511 		boxTable:attr(self.attrs)
512 	end
513 
514 	-- Add the left-hand image.
515 	local row = boxTable:tag('tr')
516 	if self.imageLeft then
517 		local imageLeftCell = row:tag('td'):addClass('mbox-image')
518 		if self.imageCellDiv then
519 			-- If we are using a div, redefine imageLeftCell so that the image
520 			-- is inside it. Divs use style="width: 52px;", which limits the
521 			-- image width to 52px. If any images in a div are wider than that,
522 			-- they may overlap with the text or cause other display problems.
523 			imageLeftCell = imageLeftCell:tag('div'):addClass('mbox-image-div')
524 		end
525 		imageLeftCell
526 			:addClass(self.imageLeftClass)
527 			:wikitext(self.imageLeft or nil)
528 	elseif self.imageEmptyCell then
529 		-- Some message boxes define an empty cell if no image is specified, and
530 		-- some don't. The old template code in templates where empty cells are
531 		-- specified gives the following hint: "No image. Cell with some width
532 		-- or padding necessary for text cell to have 100% width."
533 		row:tag('td')
534 			:addClass('mbox-empty-cell')
535 	end
536 
537 	-- Add the text.
538 	local textCell = row:tag('td'):addClass('mbox-text')
539 	if self.useCollapsibleTextFields then
540 		-- The message box uses advanced text parameters that allow things to be
541 		-- collapsible. At the moment, only ambox uses this.
542 		textCell:cssText(self.textstyle or nil)
543 		local textCellDiv = textCell:tag('div')
544 		textCellDiv
545 			:addClass('mbox-text-span')
546 			:wikitext(self.issue or nil)
547 		if (self.talk or self.fix) then
548 			textCellDiv:tag('span')
549 				:addClass('hide-when-compact')
550 				:wikitext(self.talk and (' ' .. self.talk) or nil)
551 				:wikitext(self.fix and (' ' .. self.fix) or nil)
552 		end
553 		textCellDiv:wikitext(self.date and (' ' .. self.date) or nil)
554 		if self.info and not self.isSmall then
555 			textCellDiv
556 				:tag('span')
557 				:addClass('hide-when-compact')
558 				:wikitext(self.info and (' ' .. self.info) or nil)
559 		end
560 		if self.removalNotice then
561 			textCellDiv:tag('span')
562 				:addClass('hide-when-compact')
563 				:tag('i')
564 					:wikitext(string.format(" (%s)", self.removalNotice))
565 		end
566 	else
567 		-- Default text formatting - anything goes.
568 		textCell
569 			:cssText(self.textstyle or nil)
570 			:wikitext(self.text or nil)
571 	end
572 
573 	-- Add the right-hand image.
574 	if self.imageRight then
575 		local imageRightCell = row:tag('td'):addClass('mbox-imageright')
576 		if self.imageCellDiv then
577 			-- If we are using a div, redefine imageRightCell so that the image
578 			-- is inside it.
579 			imageRightCell = imageRightCell:tag('div'):addClass('mbox-image-div')
580 		end
581 		imageRightCell
582 			:addClass(self.imageRightClass)
583 			:wikitext(self.imageRight or nil)
584 	end
585 
586 	-- Add the below row.
587 	if self.below then
588 		boxTable:tag('tr')
589 			:tag('td')
590 				:attr('colspan', self.imageRight and '3' or '2')
591 				:addClass('mbox-text')
592 				:cssText(self.textstyle or nil)
593 				:wikitext(self.below or nil)
594 	end
595 
596 	-- Add error message for invalid type parameters.
597 	if self.invalidTypeError then
598 		root:tag('div')
599 			:addClass('mbox-invalid-type')
600 			:wikitext(string.format(
601 				'This message box is using an invalid "type=%s" parameter and needs fixing.',
602 				self.type or ''
603 			))
604 	end
605 
606 	-- Add categories.
607 	root:wikitext(self:renderCategories() or nil)
608 
609 	return tostring(root)
610 end
611 
612 --------------------------------------------------------------------------------
613 -- Exports
614 --------------------------------------------------------------------------------
615 
616 local p, mt = {}, {}
617 
618 function p._exportClasses()
619 	-- For testing.
620 	return {
621 		MessageBox = MessageBox
622 	}
623 end
624 
625 function p.main(boxType, args, cfgTables)
626 	local box = MessageBox.new(boxType, args, cfgTables or mw.loadData(CONFIG_MODULE))
627 	box:setParameters()
628 	box:setCategories()
629 	return box:export()
630 end
631 
632 function mt.__index(t, k)
633 	return function (frame)
634 		if not getArgs then
635 			getArgs = require('Module:Arguments').getArgs
636 		end
637 		return t.main(k, getArgs(frame, {trim = false, removeBlanks = false}))
638 	end
639 end
640 
641 return setmetatable(p, mt)