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