Documentation for this module may be created at Module:Message box/doc

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

-- This is a meta-module for producing message box templates, including

-- , , Template:Imbox, , , Template:Cmbox and Template:Fmbox.

-- Load necessary modules. require('Module:No globals') local getArgs local categoryHandler = require('Module:Category handler')._main local yesno = require('Module:Yesno')

-- Get a language object for formatDate and ucfirst. local lang = mw.language.getContentLanguage()


-- Helper functions


local function getTitleObject(...) -- Get the title object, passing the function through pcall -- in case we are over the expensive function count limit. local success, title = pcall(mw.title.new, ...) if success then return title end end

local function union(t1, t2) -- Returns the union of two arrays. local vals = {} for i, v in ipairs(t1) do vals[v] = true end for i, v in ipairs(t2) do vals[v] = true end local ret = {} for k in pairs(vals) do table.insert(ret, k) end table.sort(ret) return ret end

local function getArgNums(args, prefix) local nums = {} for k, v in pairs(args) do local num = mw.ustring.match(tostring(k), '^' .. prefix .. '([1-9]%d*)$') if num then table.insert(nums, tonumber(num)) end end table.sort(nums) return nums end


-- Box class definition


local MessageBox = {} MessageBox.__index = MessageBox

function MessageBox.new(boxType, args, cfg) args = args or {} local obj = {}

-- Set the title object and the namespace. obj.title = getTitleObject(args.page) or mw.title.getCurrentTitle()

-- Set the config for our box type. obj.cfg = cfg[boxType] if not obj.cfg then local ns = obj.title.namespace -- boxType is "mbox" or invalid input if ns == 0 then obj.cfg = cfg.ambox -- main namespace elseif ns == 6 then obj.cfg = cfg.imbox -- file namespace elseif ns == 14 then obj.cfg = cfg.cmbox -- category namespace else local nsTable = mw.site.namespaces[ns] if nsTable and nsTable.isTalk then obj.cfg = cfg.tmbox -- any talk namespace else obj.cfg = cfg.ombox -- other namespaces or invalid input end end end

-- Set the arguments, and remove all blank arguments except for the ones -- listed in cfg.allowBlankParams. do local newArgs = {} for k, v in pairs(args) do if v ~= then newArgs[k] = v end end for i, param in ipairs(obj.cfg.allowBlankParams or {}) do newArgs[param] = args[param] end obj.args = newArgs end

-- Define internal data structure. obj.categories = {} obj.classes = {}

return setmetatable(obj, MessageBox) end

function MessageBox:addCat(ns, cat, sort) if not cat then return nil end if sort then cat = string.format(, cat, sort) else cat = string.format(, cat) end self.categories[ns] = self.categories[ns] or {} table.insert(self.categories[ns], cat) end

function MessageBox:addClass(class) if not class then return nil end table.insert(self.classes, class) end

function MessageBox:setParameters() local args = self.args local cfg = self.cfg

-- Get type data. self.type = args.type local typeData = cfg.types[self.type] self.invalidTypeError = cfg.showInvalidTypeError and self.type and not typeData typeData = typeData or cfg.types[cfg.default] self.typeClass = typeData.class self.typeImage = typeData.image

-- Find if the box has been wrongly substituted. self.isSubstituted = cfg.substCheck and args.subst == 'SUBST'

-- Find whether we are using a small message box. self.isSmall = cfg.allowSmall and ( cfg.smallParam and args.small == cfg.smallParam or not cfg.smallParam and yesno(args.small) )

-- Add attributes, classes and styles. self.id = args.id self:addClass( cfg.usePlainlinksParam and yesno(args.plainlinks or true) and 'plainlinks' ) for _, class in ipairs(cfg.classes or {}) do self:addClass(class) end if self.isSmall then self:addClass(cfg.smallClass or 'mbox-small') end self:addClass(self.typeClass) self:addClass(args.class) self.style = args.style self.attrs = args.attrs

-- Set text style. self.textstyle = args.textstyle

-- Find if we are on the template page or not. This functionality is only -- used if useCollapsibleTextFields is set, or if both cfg.templateCategory -- and cfg.templateCategoryRequireName are set. self.useCollapsibleTextFields = cfg.useCollapsibleTextFields if self.useCollapsibleTextFields or cfg.templateCategory and cfg.templateCategoryRequireName then self.name = args.name if self.name then local templateName = mw.ustring.match( self.name, '^[tT][eE][mM][pP][lL][aA][tT][eE][%s_]*:[%s_]*(.*)$' ) or self.name templateName = 'Template:' .. templateName self.templateTitle = getTitleObject(templateName) end self.isTemplatePage = self.templateTitle and mw.title.equals(self.title, self.templateTitle) end

-- Process data for collapsible text fields. At the moment these are only

-- used in .

if self.useCollapsibleTextFields then -- Get the self.issue value. if self.isSmall and args.smalltext then self.issue = args.smalltext else local sect if args.sect == then sect = 'This ' .. (cfg.sectionDefault or 'page') elseif type(args.sect) == 'string' then sect = 'This ' .. args.sect end local issue = args.issue issue = type(issue) == 'string' and issue ~= and issue or nil local text = args.text text = type(text) == 'string' and text or nil local issues = {} table.insert(issues, sect) table.insert(issues, issue) table.insert(issues, text) self.issue = table.concat(issues, ' ') end

-- Get the self.talk value. local talk = args.talk -- Show talk links on the template page or template subpages if the talk -- parameter is blank. if talk == and self.templateTitle and ( mw.title.equals(self.templateTitle, self.title) or self.title:isSubpageOf(self.templateTitle) ) then talk = '#' elseif talk == then talk = nil end if talk then -- If the talk value is a talk page, make a link to that page. Else -- assume that it's a section heading, and make a link to the talk -- page of the current page with that section heading. local talkTitle = getTitleObject(talk) local talkArgIsTalkPage = true if not talkTitle or not talkTitle.isTalkPage then talkArgIsTalkPage = false talkTitle = getTitleObject( self.title.text, mw.site.namespaces[self.title.namespace].talk.id ) end if talkTitle and talkTitle.exists then local talkText = 'Relevant discussion may be found on' if talkArgIsTalkPage then talkText = string.format( '%s %s.', talkText, talk, talkTitle.prefixedText ) else talkText = string.format( '%s the talk page.', talkText, talkTitle.prefixedText, talk ) end self.talk = talkText end end

-- Get other values. self.fix = args.fix ~= and args.fix or nil local date if args.date and args.date ~= then date = args.date elseif args.date == and self.isTemplatePage then date = lang:formatDate('F Y') end if date then self.date = string.format(" (%s)", date) end self.info = args.info end

-- Set the non-collapsible text field. At the moment this is used by all box -- types other than ambox, and also by ambox when small=yes. if self.isSmall then self.text = args.smalltext or args.text else self.text = args.text end

-- Set the below row. self.below = cfg.below and args.below

-- General image settings. self.imageCellDiv = not self.isSmall and cfg.imageCellDiv self.imageEmptyCell = cfg.imageEmptyCell if cfg.imageEmptyCellStyle then self.imageEmptyCellStyle = 'border:none;padding:0px;width:1px' end

-- Left image settings. local imageLeft = self.isSmall and args.smallimage or args.image if cfg.imageCheckBlank and imageLeft ~= 'blank' and imageLeft ~= 'none' or not cfg.imageCheckBlank and imageLeft ~= 'none' then self.imageLeft = imageLeft if not imageLeft then local imageSize = self.isSmall and (cfg.imageSmallSize or '30x30px') or '40x40px' self.imageLeft = string.format('%s', self.typeImage or 'Imbox notice.png', imageSize) end end

-- Right image settings. local imageRight = self.isSmall and args.smallimageright or args.imageright if not (cfg.imageRightNone and imageRight == 'none') then self.imageRight = imageRight end end

function MessageBox:setMainspaceCategories() local args = self.args local cfg = self.cfg

if not cfg.allowMainspaceCategories then return nil end

local nums = {} for _, prefix in ipairs{'cat', 'category', 'all'} do args[prefix .. '1'] = args[prefix] nums = union(nums, getArgNums(args, prefix)) end

-- The following is roughly equivalent to the old Template:Ambox/category. local date = args.date date = type(date) == 'string' and date local preposition = 'from' for _, num in ipairs(nums) do local mainCat = args['cat' .. tostring(num)] or args['category' .. tostring(num)] local allCat = args['all' .. tostring(num)] mainCat = type(mainCat) == 'string' and mainCat allCat = type(allCat) == 'string' and allCat if mainCat and date and date ~= then local catTitle = string.format('%s %s %s', mainCat, preposition, date) self:addCat(0, catTitle) catTitle = getTitleObject('Category:' .. catTitle) if not catTitle or not catTitle.exists then self:addCat(0, 'Articles with invalid date parameter in template') end elseif mainCat and (not date or date == ) then self:addCat(0, mainCat) end if allCat then self:addCat(0, allCat) end end end

function MessageBox:setTemplateCategories() local args = self.args local cfg = self.cfg

-- Add template categories. if cfg.templateCategory then if cfg.templateCategoryRequireName then if self.isTemplatePage then self:addCat(10, cfg.templateCategory) end elseif not self.title.isSubpage then self:addCat(10, cfg.templateCategory) end end

-- Add template error categories. if cfg.templateErrorCategory then local templateErrorCategory = cfg.templateErrorCategory local templateCat, templateSort if not self.name and not self.title.isSubpage then templateCat = templateErrorCategory elseif self.isTemplatePage then local paramsToCheck = cfg.templateErrorParamsToCheck or {} local count = 0 for i, param in ipairs(paramsToCheck) do if not args[param] then count = count + 1 end end if count > 0 then templateCat = templateErrorCategory templateSort = tostring(count) end if self.categoryNums and #self.categoryNums > 0 then templateCat = templateErrorCategory templateSort = 'C' end end self:addCat(10, templateCat, templateSort) end end

function MessageBox:setAllNamespaceCategories() -- Set categories for all namespaces. if self.invalidTypeError then local allSort = (self.title.namespace == 0 and 'Main:' or ) .. self.title.prefixedText self:addCat('all', 'Wikipedia message box parameter needs fixing', allSort) end if self.isSubstituted then self:addCat('all', 'Pages with incorrectly substituted templates') end end

function MessageBox:setCategories() if self.title.namespace == 0 then self:setMainspaceCategories() elseif self.title.namespace == 10 then self:setTemplateCategories() end self:setAllNamespaceCategories() end

function MessageBox:renderCategories() -- Convert category tables to strings and pass them through -- Module:Category handler. return categoryHandler{ main = table.concat(self.categories[0] or {}), template = table.concat(self.categories[10] or {}), all = table.concat(self.categories.all or {}), nocat = self.args.nocat, page = self.args.page } end

function MessageBox:export() local root = mw.html.create()

-- Add the subst check error. if self.isSubstituted and self.name then root:tag('b') :addClass('error') :wikitext(string.format( 'Template %s%s%s has been incorrectly substituted.', mw.text.nowiki('Template:'), self.name, self.name, mw.text.nowiki('') )) end

-- Create the box table. local boxTable = root:tag('table') boxTable:attr('id', self.id or nil) for i, class in ipairs(self.classes or {}) do boxTable:addClass(class or nil) end boxTable :cssText(self.style or nil) :attr('role', 'presentation')

if self.attrs then boxTable:attr(self.attrs) end

-- Add the left-hand image. local row = boxTable:tag('tr') if self.imageLeft then local imageLeftCell = row:tag('td'):addClass('mbox-image') if self.imageCellDiv then -- If we are using a div, redefine imageLeftCell so that the image -- is inside it. Divs use style="width: 52px;", which limits the -- image width to 52px. If any images in a div are wider than that, -- they may overlap with the text or cause other display problems. imageLeftCell = imageLeftCell:tag('div'):css('width', '52px') end imageLeftCell:wikitext(self.imageLeft or nil) elseif self.imageEmptyCell then -- Some message boxes define an empty cell if no image is specified, and -- some don't. The old template code in templates where empty cells are -- specified gives the following hint: "No image. Cell with some width -- or padding necessary for text cell to have 100% width." row:tag('td') :addClass('mbox-empty-cell') :cssText(self.imageEmptyCellStyle or nil) end

-- Add the text. local textCell = row:tag('td'):addClass('mbox-text') if self.useCollapsibleTextFields then -- The message box uses advanced text parameters that allow things to be -- collapsible. At the moment, only ambox uses this. textCell:cssText(self.textstyle or nil) local textCellSpan = textCell:tag('span') textCellSpan :addClass('mbox-text-span') :wikitext(self.issue or nil) if not self.isSmall then textCellSpan:tag('span') :addClass('hide-when-compact') :wikitext(self.talk and (' ' .. self.talk) or nil) :wikitext(self.fix and (' ' .. self.fix) or nil) end textCellSpan:wikitext(self.date and (' ' .. self.date) or nil) if not self.isSmall then textCellSpan :tag('span') :addClass('hide-when-compact') :wikitext(self.info and (' ' .. self.info) or nil) end else -- Default text formatting - anything goes. textCell :cssText(self.textstyle or nil) :wikitext(self.text or nil) end

-- Add the right-hand image. if self.imageRight then local imageRightCell = row:tag('td'):addClass('mbox-imageright') if self.imageCellDiv then -- If we are using a div, redefine imageRightCell so that the image -- is inside it. imageRightCell = imageRightCell:tag('div'):css('width', '52px') end imageRightCell :wikitext(self.imageRight or nil) end

-- Add the below row. if self.below then boxTable:tag('tr') :tag('td') :attr('colspan', self.imageRight and '3' or '2') :addClass('mbox-text') :cssText(self.textstyle or nil) :wikitext(self.below or nil) end

-- Add error message for invalid type parameters. if self.invalidTypeError then root:tag('div') :css('text-align', 'center') :wikitext(string.format( 'This message box is using an invalid "type=%s" parameter and needs fixing.', self.type or )) end

-- Add categories. root:wikitext(self:renderCategories() or nil)

return tostring(root) end


-- Exports


local p, mt = {}, {}

function p._exportClasses() -- For testing. return { MessageBox = MessageBox } end

function p.main(boxType, args, cfgTables) local box = MessageBox.new(boxType, args, cfgTables or mw.loadData('Module:Message box/configuration')) box:setParameters() box:setCategories() return box:export() end

function mt.__index(t, k) return function (frame) if not getArgs then getArgs = require('Module:Arguments').getArgs end return t.main(k, getArgs(frame, {trim = false, removeBlanks = false})) end end

return setmetatable(p, mt)