置顶公告:【置顶】关于临时开启评论区所有功能的公告(2022.10.22) | 【置顶】关于本站Widget恢复使用的公告
  • 你好~!欢迎来到萌娘百科镜像站!如需查看或编辑,请联系本站管理员注册账号。
  • 本镜像站和其他萌娘百科的镜像站无关,请注意分别。

Module:Message box

猛汉♂百科,万男皆可猛的百科全书!转载请标注来源页面的网页链接,并声明引自猛汉百科。内容不可商用。
跳到导航 跳到搜索
Template-info.svg 模块文档  [创建] [刷新]
  1. -- This is a meta-module for producing message box templates, including {{mbox}}, {{ambox}}, {{imbox}}, {{tmbox}}, {{ombox}}, {{cmbox}} and {{fmbox}}.
  2. -- Require necessary modules.
  3. local getArgs = require('Module:Arguments').getArgs
  4. local categoryHandler = require('Module:Category handler').main
  5. local yesno = require('Module:Yesno')
  6. local templatestyles = 'Template:Mbox/style.css'
  7. -- Load the configuration page.
  8. local cfgTables = mw.loadData('Module:Message box/configuration')
  9. -- Get a language object for formatDate and ucfirst.
  10. local lang = mw.language.getContentLanguage()
  11. -- Set aliases for often-used functions to reduce table lookups.
  12. local format = mw.ustring.format
  13. local tinsert = table.insert
  14. local tconcat = table.concat
  15. local trim = mw.text.trim
  16. --------------------------------------------------------------------------------
  17. -- Helper functions
  18. --------------------------------------------------------------------------------
  19. local function getTitleObject(page, ...)
  20. if type(page) == 'string' then
  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, page, ...)
  24. if success then
  25. return title
  26. end
  27. end
  28. end
  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. tinsert(ret, k)
  41. end
  42. table.sort(ret)
  43. return ret
  44. end
  45. local function getArgNums(args, prefix)
  46. local nums = {}
  47. for k, v in pairs(args) do
  48. local num = mw.ustring.match(tostring(k), '^' .. prefix .. '([1-9]%d*)$')
  49. if num then
  50. tinsert(nums, tonumber(num))
  51. end
  52. end
  53. table.sort(nums)
  54. return nums
  55. end
  56. --------------------------------------------------------------------------------
  57. -- Box class definition
  58. --------------------------------------------------------------------------------
  59. local box = {}
  60. box.__index = box
  61. function box.new()
  62. local obj = {}
  63. setmetatable(obj, box)
  64. return obj
  65. end
  66. function box.getNamespaceId(ns)
  67. if not ns then return end
  68. if type(ns) == 'string' then
  69. ns = lang:ucfirst(mw.ustring.lower(ns))
  70. if ns == 'Main' then
  71. ns = 0
  72. end
  73. end
  74. local nsTable = mw.site.namespaces[ns]
  75. if nsTable then
  76. return nsTable.id
  77. end
  78. end
  79. function box.getMboxType(nsid)
  80. -- Gets the mbox type from a namespace number.
  81. if nsid == 0 then
  82. return 'ambox' -- main namespace
  83. elseif nsid == 6 then
  84. return 'imbox' -- file namespace
  85. elseif nsid == 14 then
  86. return 'cmbox' -- category namespace
  87. else
  88. local nsTable = mw.site.namespaces[nsid]
  89. if nsTable and nsTable.isTalk then
  90. return 'tmbox' -- any talk namespace
  91. else
  92. return 'ombox' -- other namespaces or invalid input
  93. end
  94. end
  95. end
  96. function box:addCat(ns, cat, sort)
  97. if type(cat) ~= 'string' then return end
  98. local nsVals = {'main', 'template', 'all'}
  99. local tname
  100. for i, val in ipairs(nsVals) do
  101. if ns == val then
  102. tname = ns .. 'Cats'
  103. end
  104. end
  105. if not tname then
  106. for i, val in ipairs(nsVals) do
  107. nsVals[i] = format('"%s"', val)
  108. end
  109. error('無效的ns參數傳送到box:addCat;有效的數值為' .. mw.text.listToText(nsVals, '、', '或'))
  110. end
  111. self[tname] = self[tname] or {}
  112. if type(sort) == 'string' then
  113. tinsert(self[tname], format('[[Category:%s|%s]]', cat, sort))
  114. else
  115. tinsert(self[tname], format('[[Category:%s]]', cat))
  116. end
  117. end
  118. function box:addClass(class)
  119. if type(class) ~= 'string' then return end
  120. self.classes = self.classes or {}
  121. tinsert(self.classes, class)
  122. end
  123. function box:setTitle(args)
  124. -- Get the title object and the namespace.
  125. self.pageTitle = getTitleObject(args.page ~= '' and args.page)
  126. self.title = self.pageTitle or mw.title.getCurrentTitle()
  127. self.demospace = args.demospace ~= '' and args.demospace or nil
  128. self.nsid = box.getNamespaceId(self.demospace) or self.title.namespace
  129. end
  130. function box:getConfig(boxType)
  131. -- Get the box config data from the data page.
  132. if boxType == 'mbox' then
  133. boxType = box.getMboxType(self.nsid)
  134. end
  135. local cfg = cfgTables[boxType]
  136. if not cfg then
  137. local boxTypes = {}
  138. for k, v in pairs(dataTables) do
  139. tinsert(boxTypes, format('"%s"', k))
  140. end
  141. tinsert(boxTypes, '"mbox"')
  142. error(format('無效的訊息框類型「%s」;有效的類型為%s', tostring(boxType), mw.text.listToText(boxTypes)), 2)
  143. end
  144. return cfg
  145. end
  146. function box:removeBlankArgs(cfg, args)
  147. -- Only allow blank arguments for the parameter names listed in cfg.allowBlankParams.
  148. local newArgs = {}
  149. for k, v in pairs(args) do
  150. if v ~= '' then
  151. newArgs[k] = v
  152. end
  153. end
  154. for i, param in ipairs(cfg.allowBlankParams or {}) do
  155. newArgs[param] = args[param]
  156. end
  157. return newArgs
  158. end
  159. function box:setBoxParameters(cfg, args)
  160. -- Get type data.
  161. self.type = args.type
  162. local typeData = cfg.types[self.type]
  163. self.invalidTypeError = cfg.showInvalidTypeError and self.type and not typeData and true or false
  164. typeData = typeData or cfg.types[cfg.default]
  165. self.typeClass = typeData.class
  166. self.typeImage = typeData.image
  167. -- Find if the box has been wrongly substituted.
  168. if cfg.substCheck and args.subst == 'SUBST' then
  169. self.isSubstituted = true
  170. end
  171. -- Find whether we are using a small message box.
  172. if cfg.allowSmall and (
  173. cfg.smallParam and args.small == cfg.smallParam
  174. or not cfg.smallParam and yesno(args.small)
  175. )
  176. then
  177. self.isSmall = true
  178. else
  179. self.isSmall = false
  180. end
  181. -- Add attributes, classes and styles.
  182. if cfg.allowId then
  183. self.id = args.id
  184. self.name = args.name
  185. if self.name then
  186. self:addClass('box-' .. string.gsub(self.name,' ','_'))
  187. end
  188. end
  189. self:addClass(cfg.usePlainlinksParam and yesno(args.plainlinks or true) and 'plainlinks')
  190. for _, class in ipairs(cfg.classes or {}) do
  191. self:addClass(class)
  192. end
  193. if self.isSmall then
  194. self:addClass(cfg.smallClass or 'mbox-small')
  195. end
  196. if yesno(args.hidden) then
  197. self:addClass('infobox editsection')
  198. end
  199. self:addClass(self.typeClass)
  200. self:addClass(args.class)
  201. self.style = args.style
  202. -- Set text style.
  203. self.textstyle = args.textstyle
  204. -- Find if we are on the template page or not. This functionality is only used if useCollapsibleTextFields is set,
  205. -- or if both cfg.templateCategory and cfg.templateCategoryRequireName are set.
  206. self.useCollapsibleTextFields = cfg.useCollapsibleTextFields
  207. if self.useCollapsibleTextFields or cfg.templateCategory and cfg.templateCategoryRequireName then
  208. self.name = args.name
  209. if self.name then
  210. local templateName = mw.ustring.match(self.name, '^[tT][eE][mM][pP][lL][aA][tT][eE][%s_]*:[%s_]*(.*)$') or self.name
  211. templateName = 'Template:' .. templateName
  212. self.templateTitle = getTitleObject(templateName)
  213. end
  214. self.isTemplatePage = self.templateTitle and mw.title.equals(self.title, self.templateTitle) or false
  215. end
  216. -- Process data for collapsible text fields. At the moment these are only 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 = '此' .. (cfg.sectionDefault or '頁面')
  225. elseif type(args.sect) == 'string' then
  226. sect = '此' .. 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. tinsert(issues, sect)
  234. tinsert(issues, issue)
  235. tinsert(issues, text)
  236. self.issue = tconcat(issues)
  237. end
  238. -- Get the self.talk value.
  239. local talk = args.talk
  240. if talk == '' -- Show talk links on the template page or template subpages if the talk parameter is blank.
  241. and self.templateTitle
  242. and (mw.title.equals(self.templateTitle, self.title) or self.title:isSubpageOf(self.templateTitle))
  243. then
  244. talk = '#'
  245. elseif talk == '' then
  246. talk = nil
  247. end
  248. if talk then
  249. -- If the talk value is a talk page, make a link to that page. Else assume that it's a section heading,
  250. -- and make a link to the talk page of the current page with that section heading.
  251. local talkTitle = getTitleObject(talk)
  252. local talkArgIsTalkPage = true
  253. if not talkTitle or not talkTitle.isTalkPage then
  254. talkArgIsTalkPage = false
  255. talkTitle = getTitleObject(self.title.text, mw.site.namespaces[self.title.namespace].talk.id)
  256. end
  257. if talkTitle and talkTitle.exists then
  258. local talkText = '相關討論可見於'
  259. if talkArgIsTalkPage then
  260. talkText = format('%s[[%s|%s]]。', talkText, talk, talkTitle.prefixedText)
  261. else
  262. talkText = format('%s[[%s#%s|討論頁]]。', talkText, talkTitle.prefixedText, talk)
  263. end
  264. self.talk = talkText
  265. end
  266. end
  267. -- Get other values.
  268. local date
  269. if args.date and args.date ~= '' then
  270. date = args.date
  271. elseif args.time == '' and self.isTemplatePage then
  272. date = lang:formatDate('Y年n月j日')
  273. elseif args.time and args.time ~= '' then
  274. date = lang:formatDate('Y年n月j日', args.time)
  275. end
  276. if date then
  277. local ok, tempdate = pcall(lang.formatDate, lang, 'Y年n月j日', date) -- 正規化日期
  278. if ok then
  279. date = tempdate
  280. end
  281. end
  282. if date then
  283. self.date = string.format(" <small class='date-container'>''(<span class='date'>%s</span>)''</small>", date)
  284. end
  285. if args.fix and args.fix ~= '' then
  286. self.fix = format("<br /><small>%s</small>", args.fix)
  287. else
  288. self.fix = ''
  289. end
  290. self.info = args.info
  291. end
  292. -- Set the non-collapsible text field. At the moment this is used by all box types other than ambox,
  293. -- and also by ambox when small=yes.
  294. if self.isSmall then
  295. self.text = args.smalltext or args.text
  296. else
  297. self.text = args.text
  298. end
  299. -- Set the below row.
  300. self.below = cfg.below and args.below
  301. -- General image settings.
  302. self.imageCellDiv = not self.isSmall and cfg.imageCellDiv and true or false
  303. self.imageEmptyCell = cfg.imageEmptyCell
  304. if cfg.imageEmptyCellStyle then
  305. self.imageEmptyCellStyle = 'border:none;padding:0px;width:1px'
  306. end
  307. -- Left image settings.
  308. local imageLeft = self.isSmall and args.smallimage or args.image
  309. if cfg.imageCheckBlank and imageLeft ~= 'blank' and imageLeft ~= 'none'
  310. or not cfg.imageCheckBlank and imageLeft ~= 'none'
  311. then
  312. self.imageLeft = imageLeft
  313. if not imageLeft then
  314. local imageSize = self.isSmall and (cfg.imageSmallSize or '30x30px') or '40x40px'
  315. self.imageLeft = format('[[File:%s|%s|link=|alt=]]', self.typeImage or 'Imbox notice.png', imageSize)
  316. end
  317. end
  318. -- Right image settings.
  319. local imageRight = self.isSmall and args.smallimageright or args.imageright
  320. if not (cfg.imageRightNone and imageRight == 'none') then
  321. self.imageRight = imageRight
  322. end
  323. -- Add mainspace categories. At the moment these are only used in {{ambox}}.
  324. if cfg.allowMainspaceCategories then
  325. if args.cat then
  326. args.cat1 = args.cat
  327. end
  328. self.catNums = getArgNums(args, 'cat')
  329. if args.category then
  330. args.category1 = args.category
  331. end
  332. self.categoryNums = getArgNums(args, 'category')
  333. if args.all then
  334. args.all1 = args.all
  335. end
  336. self.allNums = getArgNums(args, 'all')
  337. self.categoryParamNums = union(self.catNums, self.categoryNums)
  338. self.categoryParamNums = union(self.categoryParamNums, self.allNums)
  339. -- The following is roughly equivalent to the old {{Ambox/category}}.
  340. local date
  341. local sortDay
  342. local dayName = {
  343. [1] = '㏠',
  344. [2] = '㏡',
  345. [3] = '㏢',
  346. [4] = '㏣',
  347. [5] = '㏤',
  348. [6] = '㏥',
  349. [7] = '㏦',
  350. [8] = '㏧',
  351. [9] = '㏨',
  352. [10] = '㏩',
  353. [11] = '㏪',
  354. [12] = '㏫',
  355. [13] = '㏬',
  356. [14] = '㏭',
  357. [15] = '㏮',
  358. [16] = '㏯',
  359. [17] = '㏰',
  360. [18] = '㏱',
  361. [19] = '㏲',
  362. [20] = '㏳',
  363. [21] = '㏴',
  364. [22] = '㏵',
  365. [23] = '㏶',
  366. [24] = '㏷',
  367. [25] = '㏸',
  368. [26] = '㏹',
  369. [27] = '㏺',
  370. [28] = '㏻',
  371. [29] = '㏼',
  372. [30] = '㏽',
  373. [31] = '㏾'
  374. }
  375. if args.date and args.date ~= '' then
  376. date = args.date
  377. local ok, tempdate = pcall(lang.formatDate, lang, 'Y年n月', date) -- 正規化日期
  378. if ok then
  379. date = tempdate
  380. end
  381. elseif args.time and args.time ~= '' then
  382. date = lang:formatDate('Y年n月', args.time)
  383. sortDay = lang:formatDate('j', args.time)
  384. sortDay = tonumber(sortDay)
  385. sortDay = dayName[sortDay]
  386. end
  387. date = type(date) == 'string' and date
  388. local preposition = '自'
  389. for _, num in ipairs(self.categoryParamNums) do
  390. local mainCat = args['cat' .. tostring(num)] or args['category' .. tostring(num)]
  391. local allCat = args['all' .. tostring(num)]
  392. mainCat = type(mainCat) == 'string' and mainCat
  393. allCat = type(allCat) == 'string' and allCat
  394. if mainCat and date and date ~= '' then
  395. local catTitle = format('%s%s%s', preposition, date, mainCat)
  396. if sortDay then
  397. self:addCat('main', catTitle, sortDay)
  398. else
  399. self:addCat('main', catTitle)
  400. end
  401. catTitle = getTitleObject('Category:' .. catTitle)
  402. if not catTitle or not catTitle.exists then
  403. self:addCat('main', '模板中使用无效日期参数的条目')
  404. end
  405. elseif mainCat and (not date or date == '') then
  406. self:addCat('main', mainCat)
  407. end
  408. if allCat then
  409. self:addCat('main', allCat)
  410. end
  411. end
  412. end
  413. -- Add template-namespace categories.
  414. if cfg.templateCategory then
  415. if cfg.templateCategoryRequireName then
  416. if self.isTemplatePage then
  417. self:addCat('template', cfg.templateCategory)
  418. end
  419. elseif not self.title.isSubpage then
  420. self:addCat('template', cfg.templateCategory)
  421. end
  422. end
  423. -- Add template error category.
  424. if cfg.templateErrorCategory then
  425. local templateErrorCategory = cfg.templateErrorCategory
  426. local templateCat, templateSort
  427. if not self.name and not self.title.isSubpage then
  428. templateCat = templateErrorCategory
  429. elseif self.isTemplatePage then
  430. local paramsToCheck = cfg.templateErrorParamsToCheck or {}
  431. local count = 0
  432. for i, param in ipairs(paramsToCheck) do
  433. if not args[param] then
  434. count = count + 1
  435. end
  436. end
  437. if count > 0 then
  438. templateCat = templateErrorCategory
  439. templateSort = tostring(count)
  440. end
  441. if self.categoryNums and #self.categoryNums > 0 then
  442. templateCat = templateErrorCategory
  443. templateSort = 'C'
  444. end
  445. end
  446. self:addCat('template', templateCat, templateSort)
  447. end
  448. -- Categories for all namespaces.
  449. if self.invalidTypeError then
  450. local allSort = (self.nsid == 0 and 'Main:' or '') .. self.title.prefixedText
  451. self:addCat('all', '需要修复的信息框', allSort)
  452. end
  453. if self.isSubstituted then
  454. self:addCat('all', '错误使用替换引用的页面')
  455. end
  456. -- Convert category tables to strings and pass them through [[Module:Category handler]].
  457. self.categories = categoryHandler{
  458. main = tconcat(self.mainCats or {}),
  459. template = tconcat(self.templateCats or {}),
  460. all = tconcat(self.allCats or {}),
  461. nocat = args.nocat,
  462. demospace = self.demospace,
  463. page = self.pageTitle and self.pageTitle.prefixedText or nil
  464. }
  465. end
  466. function box:export()
  467. local root = mw.html.create()
  468. -- Add the subst check error.
  469. if self.isSubstituted and self.name then
  470. root
  471. :tag('b')
  472. :addClass('error')
  473. :wikitext(format(
  474. '模板<code>%s[[Template:%s|%s]]%s</code>被錯誤地替代。',
  475. mw.text.nowiki('{{'), self.name, self.name, mw.text.nowiki('}}')
  476. ))
  477. end
  478. -- Create the box table.
  479. local boxTable = root:tag('table')
  480. boxTable
  481. :attr('id', self.id)
  482. for i, class in ipairs(self.classes or {}) do
  483. boxTable
  484. :addClass(class)
  485. end
  486. boxTable
  487. :cssText(self.style)
  488. :attr('role', 'presentation')
  489. -- Add the left-hand image.
  490. local row = boxTable:tag('tr')
  491. if self.imageLeft then
  492. local imageLeftCell = row:tag('td'):addClass('mbox-image')
  493. if self.imageCellDiv then
  494. -- If we are using a div, redefine imageLeftCell so that the image is inside it.
  495. -- Divs use style="width: 52px;", which limits the image width to 52px. If any
  496. -- images in a div are wider than that, they may overlap with the text or cause
  497. -- other display problems.
  498. imageLeftCell = imageLeftCell:tag('div'):css('width', '52px')
  499. end
  500. imageLeftCell
  501. :wikitext(self.imageLeft)
  502. elseif self.imageEmptyCell then
  503. -- Some message boxes define an empty cell if no image is specified, and some don't.
  504. -- The old template code in templates where empty cells are specified gives the following hint:
  505. -- "No image. Cell with some width or padding necessary for text cell to have 100% width."
  506. row:tag('td')
  507. :addClass('mbox-empty-cell')
  508. :cssText(self.imageEmptyCellStyle)
  509. end
  510. -- Add the text.
  511. local textCell = row:tag('td'):addClass('mbox-text')
  512. if self.useCollapsibleTextFields then
  513. -- The message box uses advanced text parameters that allow things to be collapsible. At the
  514. -- moment, only ambox uses this.
  515. textCell
  516. :cssText(self.textstyle)
  517. local textCellSpan = textCell:tag('span')
  518. textCellSpan
  519. :addClass('mbox-text-span')
  520. :wikitext(self.issue)
  521. if not self.isSmall then
  522. textCellSpan
  523. :tag('span')
  524. :addClass('hide-when-compact')
  525. :wikitext(self.talk and self.talk)
  526. end
  527. textCellSpan
  528. :wikitext(self.date and self.date)
  529. if not self.isSmall and self.fix ~= '' then
  530. textCellSpan
  531. :tag('span')
  532. :addClass('hide-when-compact')
  533. :wikitext(self.fix and self.fix)
  534. end
  535. if not self.isSmall then
  536. textCellSpan
  537. :tag('span')
  538. :addClass('hide-when-compact')
  539. :wikitext(self.info and self.info)
  540. end
  541. else
  542. -- Default text formatting - anything goes.
  543. textCell
  544. :cssText(self.textstyle)
  545. :wikitext(self.text)
  546. end
  547. -- Add the right-hand image.
  548. if self.imageRight then
  549. local imageRightCell = row:tag('td'):addClass('mbox-imageright')
  550. if self.imageCellDiv then
  551. imageRightCell = imageRightCell:tag('div'):css('width', '52px') -- If we are using a div, redefine imageRightCell so that the image is inside it.
  552. end
  553. imageRightCell
  554. :wikitext(self.imageRight)
  555. end
  556. -- Add the below row.
  557. if self.below then
  558. boxTable:tag('tr')
  559. :tag('td')
  560. :attr('colspan', self.imageRight and '3' or '2')
  561. :addClass('mbox-text')
  562. :cssText(self.textstyle)
  563. :wikitext(self.below)
  564. end
  565. -- Add error message for invalid type parameters.
  566. if self.invalidTypeError then
  567. root
  568. :tag('div')
  569. :css('text-align', 'center')
  570. :wikitext(format('此訊息框使用無效的「type=%s」參數,需要修復。', self.type or ''))
  571. end
  572. -- Add categories.
  573. root
  574. :wikitext(self.categories)
  575. return tostring(root)
  576. end
  577. local function main(boxType, args)
  578. local outputBox = box.new()
  579. outputBox:setTitle(args)
  580. local cfg = outputBox:getConfig(boxType)
  581. args = outputBox:removeBlankArgs(cfg, args)
  582. outputBox:setBoxParameters(cfg, args)
  583. return outputBox:export()
  584. end
  585. local function makeWrapper(boxType)
  586. return function (frame)
  587. local args = getArgs(frame, {trim = false, removeBlanks = false})
  588. return main(boxType, args)
  589. end
  590. end
  591. local p = {
  592. main = main,
  593. mbox = makeWrapper('mbox')
  594. }
  595. for boxType in pairs(cfgTables) do
  596. p[boxType] = makeWrapper(boxType)
  597. end
  598. return p