Module:Message box:修订间差异
外观
删除的内容 添加的内容
以 mworg 为基底,合并 enwiki attrs/imageClass/imageNeedsLink/mbox-with-below,中文本地化日期/干支/分类 |
融合重构:工程组织取 mw.org 版(strict/常量表/类/方法拆分),行为对齐求闻版(div 输出、dmbox、中文、干支)——输出与 rev 71078 等价,配置/模板/样式不动 |
||
| (未显示同一用户的3个中间版本) | |||
| 第1行: | 第1行: | ||
-- 融合版 Module:Message box |
|||
-- This is a meta-module for producing message box templates, including |
|||
-- 工程组织:mediawiki.org 版风格(require('strict')、常量表、辅助函数、类方法拆分、命名规范) |
|||
-- {{mbox}}, {{ambox}}, {{imbox}}, {{tmbox}}, {{ombox}}, {{cmbox}} and {{fmbox}}. |
|||
-- 行为基准:求闻版(rev 71078)——div.flex 输出结构、dmbox(disambig/setindex)、全中文文案、干支排序键 |
|||
-- 中文本地化版本,基于 mediawiki.org 上游,合并 enwiki 稳定增强 |
|||
-- 输出 HTML 与求闻版严格一致(样式/配置/模板均依赖该结构,零漂移) |
|||
-- Load necessary modules. |
-- Load necessary modules. |
||
require('strict') |
require('strict') |
||
local getArgs |
local getArgs = require('Module:Arguments').getArgs |
||
local categoryHandler = require('Module:Category handler').main |
|||
local yesno = require('Module:Yesno') |
local yesno = require('Module:Yesno') |
||
| 第13行: | 第15行: | ||
-- Define constants |
-- Define constants |
||
local CONFIG_MODULE = 'Module:Message box/configuration' |
local CONFIG_MODULE = 'Module:Message box/configuration' |
||
local DEFAULT_IMAGE = 'Information Mark (Blue).svg' |
|||
local DEMOSPACES = {talk = 'tmbox', image = 'imbox', file = 'imbox', category = 'cmbox', article = 'ambox', main = 'ambox'} |
|||
local TEMPLATE_STYLES = 'Module:Message box/%s.css' |
|||
-- Set aliases for often-used functions to reduce table lookups. |
-- Set aliases for often-used functions to reduce table lookups. |
||
| 第25行: | 第26行: | ||
-------------------------------------------------------------------------------- |
-------------------------------------------------------------------------------- |
||
local function getTitleObject(...) |
local function getTitleObject(page, ...) |
||
if type(page) == 'string' then |
|||
-- Get the title object, passing the function through pcall |
|||
-- 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, ...) |
|||
local success, title = pcall(mw.title.new, page, ...) |
|||
if success then |
|||
if success then |
|||
return title |
|||
return title |
|||
end |
|||
end |
end |
||
end |
end |
||
| 第61行: | 第64行: | ||
table.sort(nums) |
table.sort(nums) |
||
return nums |
return nums |
||
end |
|||
-------------------------------------------------------------------------------- |
|||
-- Namespace resolution |
|||
-------------------------------------------------------------------------------- |
|||
local function getNamespaceId(ns) |
|||
if not ns then return end |
|||
if type(ns) == 'string' then |
|||
ns = lang:ucfirst(mw.ustring.lower(ns)) |
|||
if ns == 'Main' then |
|||
ns = 0 |
|||
end |
|||
end |
|||
local nsTable = mw.site.namespaces[ns] |
|||
if nsTable then |
|||
return nsTable.id |
|||
end |
|||
end |
|||
local function getMboxType(nsid) |
|||
-- Gets the mbox type from a namespace number. |
|||
if nsid == 0 then |
|||
return 'ambox' -- main namespace |
|||
else |
|||
local nsTable = mw.site.namespaces[nsid] |
|||
if nsTable and nsTable.isTalk then |
|||
return 'tmbox' -- any talk namespace |
|||
else |
|||
return 'ombox' -- other namespaces or invalid input |
|||
end |
|||
end |
|||
end |
end |
||
| 第84行: | 第119行: | ||
MessageBox.__index = MessageBox |
MessageBox.__index = MessageBox |
||
function MessageBox.new(boxType, args, |
function MessageBox.new(boxType, args, cfgTables) |
||
args = args or {} |
args = args or {} |
||
local obj = {} |
local obj = {} |
||
setmetatable(obj, MessageBox) |
|||
obj.boxType = boxType |
obj.boxType = boxType |
||
-- |
-- Get the title object and the namespace. |
||
obj. |
obj.pageTitle = getTitleObject(args.page ~= '' and args.page) |
||
obj.title = obj.pageTitle or mw.title.getCurrentTitle() |
|||
obj.demospace = args.demospace ~= '' and args.demospace or nil |
|||
obj.nsid = getNamespaceId(obj.demospace) or obj.title.namespace |
|||
-- |
-- Resolve mbox to the concrete box type from the namespace. |
||
obj. |
if obj.boxType == 'mbox' then |
||
obj.boxType = getMboxType(obj.nsid) |
|||
if not obj.cfg then |
|||
local ns = obj.title.namespace |
|||
-- boxType is "mbox" or invalid input |
|||
if args.demospace and args.demospace ~= '' then |
|||
-- implement demospace parameter of mbox |
|||
local demospace = string.lower(args.demospace) |
|||
if DEMOSPACES[demospace] then |
|||
-- use template from DEMOSPACES |
|||
obj.cfg = cfg[DEMOSPACES[demospace]] |
|||
obj.boxType = DEMOSPACES[demospace] |
|||
elseif string.find(demospace, 'talk') then |
|||
-- demo as a talk page |
|||
obj.cfg = cfg.tmbox |
|||
obj.boxType = 'tmbox' |
|||
else |
|||
-- default to ombox |
|||
obj.cfg = cfg.ombox |
|||
obj.boxType = 'ombox' |
|||
end |
|||
elseif ns == 0 then |
|||
obj.cfg = cfg.ambox -- main namespace |
|||
obj.boxType = 'ambox' |
|||
elseif ns == 6 then |
|||
obj.cfg = cfg.imbox -- file namespace |
|||
obj.boxType = 'imbox' |
|||
elseif ns == 14 then |
|||
obj.cfg = cfg.cmbox -- category namespace |
|||
obj.boxType = 'cmbox' |
|||
else |
|||
local nsTable = mw.site.namespaces[ns] |
|||
if nsTable and nsTable.isTalk then |
|||
obj.cfg = cfg.tmbox -- any talk namespace |
|||
obj.boxType = 'tmbox' |
|||
else |
|||
obj.cfg = cfg.ombox -- other namespaces or invalid input |
|||
obj.boxType = 'ombox' |
|||
end |
|||
end |
|||
end |
end |
||
-- Get the config for our box type. |
|||
-- Set the arguments, and remove all blank arguments except for the ones |
|||
obj.cfg = cfgTables[obj.boxType] |
|||
-- listed in cfg.allowBlankParams. |
|||
if not obj.cfg then |
|||
do |
|||
local |
local boxTypes = {} |
||
for k, v in pairs( |
for k, v in pairs(cfgTables) do |
||
tinsert(boxTypes, format('"%s"', k)) |
|||
if v ~= '' then |
|||
newArgs[k] = v |
|||
end |
|||
end |
|||
for i, param in ipairs(obj.cfg.allowBlankParams or {}) do |
|||
newArgs[param] = args[param] |
|||
end |
end |
||
tinsert(boxTypes, '"mbox"') |
|||
obj.args = newArgs |
|||
error(format('无效的消息框类型「%s」;有效的类型为%s', tostring(obj.boxType), mw.text.listToText(boxTypes)), 2) |
|||
end |
end |
||
obj.args = args |
|||
-- Define internal data structure. |
|||
obj.categories = {} |
|||
obj.classes = {} |
obj.classes = {} |
||
obj.mainCats = nil |
|||
-- For lazy loading of [[Module:Category handler]]. |
|||
obj. |
obj.templateCats = nil |
||
obj.allCats = nil |
|||
return |
return obj |
||
end |
end |
||
function MessageBox:addCat(ns, cat, sort) |
function MessageBox:addCat(ns, cat, sort) |
||
if |
if type(cat) ~= 'string' then return end |
||
local nsVals = {'main', 'template', 'all'} |
|||
return nil |
|||
local tname |
|||
for i, val in ipairs(nsVals) do |
|||
if ns == val then |
|||
tname = ns .. 'Cats' |
|||
end |
|||
end |
end |
||
if |
if not tname then |
||
for i, val in ipairs(nsVals) do |
|||
cat = string.format('[[Category:%s|%s]]', cat, sort) |
|||
nsVals[i] = format('"%s"', val) |
|||
end |
|||
error('无效的ns参数传送到box:addCat;有效的数值为' .. mw.text.listToText(nsVals, '、', '或')) |
|||
end |
|||
self[tname] = self[tname] or {} |
|||
if type(sort) == 'string' then |
|||
tinsert(self[tname], format('[[Category:%s|%s]]', cat, sort)) |
|||
else |
else |
||
tinsert(self[tname], format('[[Category:%s]]', cat)) |
|||
end |
end |
||
self.hasCategories = true |
|||
self.categories[ns] = self.categories[ns] or {} |
|||
tinsert(self.categories[ns], cat) |
|||
end |
end |
||
function MessageBox:addClass(class) |
function MessageBox:addClass(class) |
||
if |
if type(class) ~= 'string' then return end |
||
tinsert(self.classes, class) |
|||
return nil |
|||
end |
|||
self.classes[class] = 1 |
|||
end |
end |
||
-------------------------------------------------------------------------------- |
|||
function MessageBox:removeClass(class) |
|||
-- Parameter processing |
|||
if not class then |
|||
-------------------------------------------------------------------------------- |
|||
return nil |
|||
end |
|||
self.classes[class] = nil |
|||
end |
|||
function MessageBox: |
function MessageBox:setTypeData() |
||
local args = self.args |
|||
local cfg = self.cfg |
local cfg = self.cfg |
||
local args = self.args |
|||
-- Get type data. |
-- Get type data. |
||
self.type = args.type |
self.type = args.type |
||
local typeData = cfg.types[self.type] |
local typeData = cfg.types[self.type] |
||
self.invalidTypeError = cfg.showInvalidTypeError |
self.invalidTypeError = cfg.showInvalidTypeError and self.type and not typeData and true or false |
||
and self.type |
|||
and not typeData |
|||
typeData = typeData or cfg.types[cfg.default] |
typeData = typeData or cfg.types[cfg.default] |
||
self.typeClass = typeData.class |
self.typeClass = typeData.class |
||
self.typeImage = typeData.image |
self.typeImage = typeData.image |
||
self.typeImageNeedsLink = typeData.imageNeedsLink |
|||
-- Find if the box has been wrongly substituted. |
-- Find if the box has been wrongly substituted. |
||
if cfg.substCheck and args.subst == 'SUBST' then |
|||
self.isSubstituted = true |
|||
end |
|||
-- Find whether we are using a small message box. |
-- Find whether we are using a small message box. |
||
if cfg.allowSmall and ( |
|||
cfg.smallParam and args.small == cfg.smallParam |
cfg.smallParam and args.small == cfg.smallParam |
||
or not cfg.smallParam and yesno(args.small) |
or not cfg.smallParam and yesno(args.small) |
||
) |
) |
||
then |
|||
self.isSmall = true |
|||
else |
|||
self.isSmall = false |
|||
end |
|||
end |
|||
function MessageBox:setClasses() |
|||
local cfg = self.cfg |
|||
local args = self.args |
|||
-- Add attributes, classes and styles. |
-- Add attributes, classes and styles. |
||
if cfg.allowId then |
|||
self.id = args.id |
|||
self.id = args.id |
|||
self.name = args.name |
|||
if self.name then |
|||
self:addClass('box-' .. string.gsub(self.name, ' ', '_')) |
|||
end |
|||
end |
|||
for _, class in ipairs(cfg.classes or {}) do |
for _, class in ipairs(cfg.classes or {}) do |
||
self:addClass(class) |
self:addClass(class) |
||
end |
|||
if self.name then |
|||
self:addClass('box-' .. string.gsub(self.name, ' ', '_')) |
|||
end |
|||
local plainlinks = yesno(args.plainlinks) |
|||
if plainlinks == true then |
|||
self:addClass('plainlinks') |
|||
elseif plainlinks == false then |
|||
self:removeClass('plainlinks') |
|||
end |
end |
||
if self.isSmall then |
if self.isSmall then |
||
self:addClass(cfg.smallClass or 'mbox-small') |
self:addClass(cfg.smallClass or 'mbox-small') |
||
end |
|||
if yesno(args.hidden) then |
|||
self:addClass('infobox editsection') |
|||
end |
end |
||
self:addClass(self.typeClass) |
self:addClass(self.typeClass) |
||
self:addClass(args.class) |
self:addClass(args.class) |
||
self.style = args.style |
self.style = args.style |
||
self.attrs = args.attrs |
|||
-- Set text style. |
-- Set text style. |
||
self.textstyle = args.textstyle |
self.textstyle = args.textstyle |
||
self.abovestyle = args.abovestyle |
|||
self.belowstyle = args.belowstyle |
|||
end |
|||
function MessageBox:setCollapsibleTextFields() |
|||
-- Set image classes (from enwiki). |
|||
local cfg = self.cfg |
|||
self.imageRightClass = args.imagerightclass or args.imageclass |
|||
local args = self.args |
|||
self.imageLeftClass = args.imageleftclass or args.imageclass |
|||
-- Find if we are on the template page or not. |
-- Find if we are on the template page or not. |
||
-- used if useCollapsibleTextFields is set, or if both cfg.templateCategory |
|||
-- and cfg.templateCategoryRequireName are set. |
|||
self.useCollapsibleTextFields = cfg.useCollapsibleTextFields |
self.useCollapsibleTextFields = cfg.useCollapsibleTextFields |
||
if self.useCollapsibleTextFields |
if self.useCollapsibleTextFields or cfg.templateCategory and cfg.templateCategoryRequireName then |
||
self.name = args.name |
|||
or cfg.templateCategory |
|||
and cfg.templateCategoryRequireName |
|||
then |
|||
if self.name then |
if self.name then |
||
local templateName = mw.ustring.match( |
local templateName = mw.ustring.match(self.name, '^[tT][eE][mM][pP][lL][aA][tT][eE][%s_]*:[%s_]*(.*)$') or self.name |
||
self.name, |
|||
'^[tT][eE][mM][pP][lL][aA][tT][eE][%s_]*:[%s_]*(.*)$' |
|||
) or self.name |
|||
templateName = 'Template:' .. templateName |
templateName = 'Template:' .. templateName |
||
self.templateTitle = getTitleObject(templateName) |
self.templateTitle = getTitleObject(templateName) |
||
end |
end |
||
self.isTemplatePage = self.templateTitle |
self.isTemplatePage = self.templateTitle and mw.title.equals(self.title, self.templateTitle) or false |
||
and mw.title.equals(self.title, self.templateTitle) |
|||
end |
end |
||
if not self.useCollapsibleTextFields then |
|||
-- Process data for collapsible text fields. At the moment these are only |
|||
return |
|||
-- used in {{ambox}}. |
|||
end |
|||
if self.useCollapsibleTextFields then |
|||
-- Get the self.issue value. |
|||
-- Get the self.issue value. |
|||
if self.isSmall and args.smalltext then |
|||
if self.isSmall and args.smalltext then |
|||
self.issue = args.smalltext |
|||
else |
|||
else |
|||
local sect |
|||
local sect |
|||
if args.sect == '' then |
|||
sect = '此' .. (cfg.sectionDefault or '页面') |
|||
elseif type(args.sect) == 'string' then |
|||
elseif type(args.sect) == 'string' then |
|||
sect = '此' .. 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 = {} |
|||
tinsert(issues, sect) |
|||
tinsert(issues, issue) |
|||
tinsert(issues, text) |
|||
self.issue = tconcat(issues) |
|||
end |
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 = {} |
|||
tinsert(issues, sect) |
|||
tinsert(issues, issue) |
|||
tinsert(issues, text) |
|||
self.issue = tconcat(issues) |
|||
end |
|||
-- Get the self.talk value. |
|||
local talk = args.talk |
|||
if talk == '' -- Show talk links on the template page or template subpages if the talk parameter is blank. |
|||
and self.templateTitle |
|||
-- parameter is blank. |
|||
and (mw.title.equals(self.templateTitle, self.title) or self.title:isSubpageOf(self.templateTitle)) |
|||
if talk == '' |
|||
then |
|||
and self.templateTitle |
|||
talk = '#' |
|||
elseif talk == '' then |
|||
mw.title.equals(self.templateTitle, self.title) |
|||
talk = nil |
|||
or self.title:isSubpageOf(self.templateTitle) |
|||
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, |
|||
talk = '#' |
|||
-- and make a link to the talk page of the current page with that section heading. |
|||
elseif talk == '' then |
|||
local talkTitle = getTitleObject(talk) |
|||
talk = nil |
|||
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 |
end |
||
if |
if talkTitle and talkTitle.exists then |
||
local talkText = '相关讨论可见于' |
|||
-- If the talk value is a talk page, make a link to that page. Else |
|||
if talkArgIsTalkPage then |
|||
-- assume that it's a section heading, and make a link to the talk |
|||
talkText = format('%s[[%s|%s]]。', talkText, talk, talkTitle.prefixedText) |
|||
-- page of the current page with that section heading. |
|||
else |
|||
local talkTitle = getTitleObject(talk) |
|||
talkText = format('%s[[%s#%s|讨论页]]。', talkText, talkTitle.prefixedText, 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 = '相關討論可見於' |
|||
if talkArgIsTalkPage then |
|||
talkText = format('%s[[%s|%s]]。', talkText, talk, talkTitle.prefixedText) |
|||
else |
|||
talkText = format('%s[[%s#%s|討論頁]]。', talkText, talkTitle.prefixedText, talk) |
|||
end |
|||
self.talk = talkText |
|||
end |
end |
||
self.talk = talkText |
|||
end |
end |
||
end |
|||
-- Get other values. |
|||
local date |
|||
self.fix = args.fix ~= '' and args.fix or nil |
|||
if args.date and args.date ~= '' then |
|||
local date |
|||
date = args.date |
|||
elseif args.time == '' and self.isTemplatePage then |
|||
date = args.date |
|||
date = lang:formatDate('Y年n月j日') |
|||
elseif args.date == '' and self.isTemplatePage then |
|||
elseif args.time and args.time ~= '' then |
|||
date = lang:formatDate('Y年n月j日') |
|||
date = lang:formatDate('Y年n月j日', args.time) |
|||
end |
|||
end |
|||
if date then |
|||
if date then |
|||
local ok, tempdate = pcall(lang.formatDate, lang, 'Y年n月j日', date) |
|||
local ok, tempdate = pcall(lang.formatDate, lang, 'Y年n月j日', date) -- 正規化日期 |
|||
if ok then |
|||
if ok then |
|||
date = tempdate |
|||
date = tempdate |
|||
end |
|||
end |
|||
if date then |
|||
self.date = string.format(" <small class='date-container'>''(<span class='date'>%s</span>)''</small>", date) |
|||
end |
|||
self.info = args.info |
|||
if yesno(args.removalnotice) then |
|||
self.removalNotice = cfg.removalNotice |
|||
end |
end |
||
end |
end |
||
if date then |
|||
self.date = string.format(" <small class='date-container'>''(<span class='date'>%s</span>)''</small>", date) |
|||
end |
|||
if args.fix and args.fix ~= '' then |
|||
self.fix = format('<br><small>%s</small>', args.fix) |
|||
else |
|||
self.fix = '' |
|||
end |
|||
self.info = args.info |
|||
end |
|||
function MessageBox:setTextFields() |
|||
-- Set the non-collapsible text field. At the moment this is used by all box |
|||
local args = self.args |
|||
-- types other than ambox, and also by ambox when small=yes. |
|||
-- Set the non-collapsible text field. |
|||
if self.isSmall then |
if self.isSmall then |
||
self.text = args.smalltext or args.text |
self.text = args.smalltext or args.text |
||
| 第354行: | 第354行: | ||
self.text = args.text |
self.text = args.text |
||
end |
end |
||
-- Set the above row. |
|||
self.above = args.above |
|||
-- Set the below row. |
-- Set the below row. |
||
self.below = |
self.below = args.below |
||
end |
|||
if self.below then |
|||
self:addClass('mbox-with-below') |
|||
function MessageBox:setImages() |
|||
end |
|||
local cfg = self.cfg |
|||
local args = self.args |
|||
-- General image settings. |
-- General image settings. |
||
self.imageCellDiv = not self.isSmall and cfg.imageCellDiv |
self.imageCellDiv = not self.isSmall and cfg.imageCellDiv and true or false |
||
self.imageEmptyCell = cfg.imageEmptyCell |
self.imageEmptyCell = cfg.imageEmptyCell |
||
if cfg.imageEmptyCellStyle then |
|||
self.imageEmptyCellStyle = 'border:none;padding:0px;width:1px' |
|||
end |
|||
-- Left image settings. |
-- Left image settings. |
||
| 第375行: | 第377行: | ||
self.imageLeft = imageLeft |
self.imageLeft = imageLeft |
||
if not imageLeft then |
if not imageLeft then |
||
local imageSize = self.isSmall |
local imageSize = self.isSmall and (cfg.imageSmallSize or '30x30px') or '40px' |
||
self.imageLeft = format('[[File:%s|%s|link=|alt=]]', self.typeImage or DEFAULT_IMAGE, imageSize) |
|||
and (cfg.imageSmallSize or '30x30px') |
|||
or '40x40px' |
|||
self.imageLeft = string.format('[[File:%s|%s%s|alt=]]', self.typeImage |
|||
or 'Imbox notice.png', imageSize, self.typeImageNeedsLink and '' or '|link=') |
|||
end |
end |
||
end |
end |
||
| 第389行: | 第388行: | ||
end |
end |
||
end |
end |
||
-------------------------------------------------------------------------------- |
|||
-- Category processing |
|||
-------------------------------------------------------------------------------- |
|||
function MessageBox:setMainspaceCategories() |
function MessageBox:setMainspaceCategories() |
||
local args = self.args |
|||
local cfg = self.cfg |
local cfg = self.cfg |
||
local args = self.args |
|||
if not cfg.allowMainspaceCategories then |
if not cfg.allowMainspaceCategories then |
||
return |
return |
||
end |
end |
||
if args.cat then |
|||
local nums = {} |
|||
args.cat1 = args.cat |
|||
for _, prefix in ipairs{'cat', 'category', 'all'} do |
|||
end |
|||
args[prefix .. '1'] = args[prefix] |
|||
self.catNums = getArgNums(args, 'cat') |
|||
if args.category then |
|||
args.category1 = args.category |
|||
end |
|||
self.categoryNums = getArgNums(args, 'category') |
|||
if args.all then |
|||
args.all1 = args.all |
|||
end |
end |
||
self.allNums = getArgNums(args, 'all') |
|||
self.categoryParamNums = union(self.catNums, self.categoryNums) |
|||
self.categoryParamNums = union(self.categoryParamNums, self.allNums) |
|||
-- The following is roughly equivalent to the old {{Ambox/category}}. |
-- The following is roughly equivalent to the old {{Ambox/category}}. |
||
local |
local date |
||
date = type(date) == 'string' and date |
|||
-- 中文日期格式化 |
|||
local sortDay |
local sortDay |
||
if date and date ~= '' then |
if args.date and args.date ~= '' then |
||
date = args.date |
|||
local ok, tempdate = pcall(lang.formatDate, lang, 'Y年n月', date) |
|||
local ok, tempdate = pcall(lang.formatDate, lang, 'Y年n月', date) -- 正規化日期 |
|||
if ok then |
if ok then |
||
date = tempdate |
date = tempdate |
||
| 第423行: | 第433行: | ||
date = type(date) == 'string' and date |
date = type(date) == 'string' and date |
||
local preposition = '自' |
local preposition = '自' |
||
for _, num in ipairs( |
for _, num in ipairs(self.categoryParamNums) do |
||
local mainCat = args['cat' .. tostring(num)] |
local mainCat = args['cat' .. tostring(num)] or args['category' .. tostring(num)] |
||
or args['category' .. tostring(num)] |
|||
local allCat = args['all' .. tostring(num)] |
local allCat = args['all' .. tostring(num)] |
||
mainCat = type(mainCat) == 'string' and mainCat |
mainCat = type(mainCat) == 'string' and mainCat |
||
allCat = type(allCat) == 'string' and allCat |
allCat = type(allCat) == 'string' and allCat |
||
if mainCat and date and date ~= '' then |
if mainCat and date and date ~= '' then |
||
local catTitle = |
local catTitle = format('%s%s%s', preposition, date, mainCat) |
||
if sortDay then |
if sortDay then |
||
self:addCat( |
self:addCat('main', catTitle, sortDay) |
||
else |
else |
||
self:addCat( |
self:addCat('main', catTitle) |
||
end |
end |
||
catTitle = getTitleObject('Category:' .. catTitle) |
catTitle = getTitleObject('Category:' .. catTitle) |
||
if not catTitle or not catTitle.exists then |
if not catTitle or not catTitle.exists then |
||
self:addCat( |
self:addCat('main', '模板中使用无效日期参数的条目') |
||
end |
end |
||
elseif mainCat and (not date or date == '') then |
elseif mainCat and (not date or date == '') then |
||
self:addCat( |
self:addCat('main', mainCat) |
||
end |
end |
||
if allCat then |
if allCat then |
||
self:addCat( |
self:addCat('main', allCat) |
||
end |
end |
||
end |
end |
||
| 第450行: | 第459行: | ||
function MessageBox:setTemplateCategories() |
function MessageBox:setTemplateCategories() |
||
local args = self.args |
|||
local cfg = self.cfg |
local cfg = self.cfg |
||
local args = self.args |
|||
-- Add template categories. |
-- Add template-namespace categories. |
||
if cfg.templateCategory then |
if cfg.templateCategory then |
||
if cfg.templateCategoryRequireName then |
if cfg.templateCategoryRequireName then |
||
if self.isTemplatePage then |
if self.isTemplatePage then |
||
self:addCat( |
self:addCat('template', cfg.templateCategory) |
||
end |
end |
||
elseif not self.title.isSubpage then |
elseif not self.title.isSubpage then |
||
self:addCat( |
self:addCat('template', cfg.templateCategory) |
||
end |
end |
||
end |
end |
||
-- Add template error |
-- Add template error category. |
||
if cfg.templateErrorCategory then |
if cfg.templateErrorCategory then |
||
local templateErrorCategory = cfg.templateErrorCategory |
local templateErrorCategory = cfg.templateErrorCategory |
||
| 第487行: | 第496行: | ||
end |
end |
||
end |
end |
||
self:addCat( |
self:addCat('template', templateCat, templateSort) |
||
end |
end |
||
end |
end |
||
function MessageBox:setAllNamespaceCategories() |
function MessageBox:setAllNamespaceCategories() |
||
-- |
-- Categories for all namespaces. |
||
if self.invalidTypeError then |
|||
local allSort = (self.nsid == 0 and 'Main:' or '') .. self.title.prefixedText |
|||
self:addCat('all', '需要修复的信息框', allSort) |
|||
end |
|||
if self.isSubstituted then |
if self.isSubstituted then |
||
self:addCat('all', ' |
self:addCat('all', '错误使用替换引用的页面') |
||
end |
end |
||
end |
end |
||
function MessageBox:setCategories() |
function MessageBox:setCategories() |
||
if self. |
if self.cfg.allowMainspaceCategories then |
||
self:setMainspaceCategories() |
self:setMainspaceCategories() |
||
end |
|||
elseif self.title.namespace == 10 then |
|||
if self.cfg.templateCategory then |
|||
self:setTemplateCategories() |
self:setTemplateCategories() |
||
end |
end |
||
| 第508行: | 第522行: | ||
function MessageBox:renderCategories() |
function MessageBox:renderCategories() |
||
-- Convert category tables to strings and pass them through [[Module:Category handler]]. |
|||
if not self.hasCategories then |
|||
return categoryHandler{ |
|||
-- No categories added, no need to pass them to Category handler so, |
|||
main = tconcat(self.mainCats or {}), |
|||
-- if it was invoked, it would return the empty string. |
|||
template = tconcat(self.templateCats or {}), |
|||
-- So we shortcut and return the empty string. |
|||
all = tconcat(self.allCats or {}), |
|||
return '' |
|||
end |
|||
-- Convert category tables to strings and pass them through |
|||
-- [[Module:Category handler]]. |
|||
return require('Module:Category handler')._main{ |
|||
main = tconcat(self.categories[0] or {}), |
|||
template = tconcat(self.categories[10] or {}), |
|||
all = tconcat(self.categories.all or {}), |
|||
nocat = self.args.nocat, |
nocat = self.args.nocat, |
||
demospace = self.demospace, |
|||
page = self.pageTitle and self.pageTitle.prefixedText or nil |
|||
} |
} |
||
end |
end |
||
-------------------------------------------------------------------------------- |
|||
-- Export |
|||
-------------------------------------------------------------------------------- |
|||
function MessageBox:export() |
function MessageBox:export() |
||
| 第530行: | 第542行: | ||
-- Add the subst check error. |
-- Add the subst check error. |
||
if self.isSubstituted and self.name then |
if self.isSubstituted and self.name then |
||
root |
root |
||
: |
:tag('b') |
||
:addClass('error') |
|||
:wikitext(string.format( |
|||
:wikitext(format( |
|||
'模板<code>%s[[Template:%s|%s]]%s</code>被錯誤地替代。', |
|||
'模板<code>%s[[Template:%s|%s]]%s</code>被錯誤地替代。', |
|||
mw.text.nowiki('{{'), self.name, self.name, mw.text.nowiki('}}') |
|||
mw.text.nowiki('{{'), self.name, self.name, mw.text.nowiki('}}') |
|||
)) |
|||
)) |
|||
end |
end |
||
-- Add TemplateStyles |
|||
root:wikitext(mw.getCurrentFrame():extensionTag{ |
|||
name = 'templatestyles', |
|||
args = { src = TEMPLATE_STYLES:format(self.boxType) }, |
|||
}) |
|||
-- Create the box table. |
-- Create the box table. |
||
local boxTable |
local boxTable = root:tag('div') |
||
boxTable |
|||
-- Check for fmbox because not all interface messages have mw-parser-output |
|||
:attr('id', self.id) |
|||
-- which is necessary for TemplateStyles. Add the wrapper class if it is and |
|||
for i, class in ipairs(self.classes or {}) do |
|||
-- then start the actual mbox, else start the mbox. |
|||
boxTable |
|||
if self.boxType == 'fmbox' then |
|||
:addClass(class) |
|||
boxTable = root:tag('div') |
|||
:addClass('mw-parser-output') |
|||
:tag('table') |
|||
else |
|||
boxTable = root:tag('table') |
|||
end |
|||
boxTable:attr('id', self.id or nil) |
|||
for class, _ in pairs(self.classes or {}) do |
|||
boxTable:addClass(class or nil) |
|||
end |
end |
||
boxTable |
boxTable |
||
:cssText(self.style |
:cssText(self.style) |
||
:attr('role', 'presentation') |
:attr('role', 'presentation') |
||
if self.attrs then |
|||
boxTable:attr(self.attrs) |
|||
end |
|||
-- Add the left-hand image. |
-- Add the left-hand image. |
||
local row = boxTable:tag(' |
local row = boxTable:tag('div'):addClass('mbox-body') |
||
if self.imageLeft then |
if self.imageLeft then |
||
local imageLeftCell = row:tag(' |
local imageLeftCell = row:tag('div'):addClass('mbox-image') |
||
if self.imageCellDiv then |
if self.imageCellDiv then |
||
-- If we are using a div, redefine imageLeftCell so that the image |
-- 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') |
imageLeftCell = imageLeftCell:tag('div') --:css('width', '52px') |
||
end |
end |
||
imageLeftCell |
imageLeftCell |
||
: |
:wikitext(self.imageLeft) |
||
:wikitext(self.imageLeft or nil) |
|||
elseif self.imageEmptyCell then |
elseif self.imageEmptyCell then |
||
-- Some message boxes define an empty cell if no image is specified, and |
-- 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('div') |
|||
-- or padding necessary for text cell to have 100% width." |
|||
row:tag('td') |
|||
:addClass('mbox-empty-cell') |
:addClass('mbox-empty-cell') |
||
end |
|||
:cssText(self.imageEmptyCellStyle or nil) |
|||
local textCell = row:tag('div'):addClass('mbox-text') |
|||
-- Add the above row. |
|||
if self.above then |
|||
textCell |
|||
:tag('div') |
|||
:attr('colspan', self.imageRight and '3' or '2') |
|||
:addClass('mbox-above') |
|||
:cssText(self.abovestyle) |
|||
:wikitext(self.above) |
|||
end |
end |
||
-- Add the text. |
-- Add the text. |
||
local textCell = row:tag('td'):addClass('mbox-text') |
|||
if self.useCollapsibleTextFields then |
if self.useCollapsibleTextFields then |
||
-- The message box uses advanced text parameters that allow things to be |
-- The message box uses advanced text parameters that allow things to be collapsible. At the |
||
-- |
-- moment, only ambox uses this. |
||
textCell |
textCell |
||
:cssText(self.textstyle) |
|||
local textCellDiv = textCell:tag('div') |
|||
local textCellSpan = textCell:tag('span') |
|||
textCellDiv |
|||
textCellSpan |
|||
:addClass('mbox-text-span') |
:addClass('mbox-text-span') |
||
:wikitext(self.issue |
:wikitext(self.issue) |
||
if |
if not self.isSmall then |
||
textCellSpan |
|||
textCellDiv:tag('span') |
|||
: |
:tag('span') |
||
:addClass('hide-when-compact') |
|||
:wikitext(self.talk and (' ' .. self.talk) or nil) |
|||
:wikitext(self. |
:wikitext(self.talk and self.talk) |
||
end |
end |
||
textCellSpan |
|||
textCellDiv:wikitext(self.date and (' ' .. self.date) or nil) |
|||
:wikitext(self.date and self.date) |
|||
if not self.isSmall and self.fix ~= '' then |
|||
textCellDiv |
|||
textCellSpan |
|||
:tag('span') |
:tag('span') |
||
:addClass('hide-when-compact') |
:addClass('hide-when-compact') |
||
:wikitext(self. |
:wikitext(self.fix and self.fix) |
||
end |
end |
||
if self. |
if not self.isSmall then |
||
textCellSpan |
|||
textCellDiv:tag('small') |
|||
:tag('span') |
|||
:addClass('hide-when-compact') |
:addClass('hide-when-compact') |
||
:wikitext(self.info and self.info) |
|||
:tag('i') |
|||
:wikitext(string.format('(%s)', self.removalNotice)) |
|||
end |
end |
||
else |
else |
||
-- Default text formatting - anything goes. |
-- Default text formatting - anything goes. |
||
textCell |
textCell |
||
:cssText(self.textstyle |
:cssText(self.textstyle) |
||
:wikitext(self.text |
:wikitext(self.text) |
||
end |
|||
-- Add the below row. |
|||
if self.below then |
|||
textCell |
|||
:tag('div') |
|||
:attr('colspan', self.imageRight and '3' or '2') |
|||
:addClass('mbox-below') |
|||
:cssText(self.belowstyle) |
|||
:wikitext(self.below) |
|||
end |
end |
||
-- Add the right-hand image. |
-- Add the right-hand image. |
||
if self.imageRight then |
if self.imageRight then |
||
local imageRightCell = row:tag(' |
local imageRightCell = row:tag('div'):addClass('mbox-imageright') |
||
if self.imageCellDiv then |
if self.imageCellDiv then |
||
-- If we are using a div, redefine imageRightCell so that the image |
imageRightCell = imageRightCell:tag('div') --:css('width', '52px') -- If we are using a div, redefine imageRightCell so that the image is inside it. |
||
-- is inside it. |
|||
imageRightCell = imageRightCell:tag('div'):css('width', '52px') |
|||
end |
end |
||
imageRightCell |
imageRightCell |
||
: |
:wikitext(self.imageRight) |
||
:wikitext(self.imageRight or nil) |
|||
end |
end |
||
-- Add __DISAMBIG__ for {{dmbox|type=disambig}} |
|||
-- Add the below row. |
|||
if self. |
if self.type == 'disambig' then |
||
if self.nsid == 10 then |
|||
boxTable:tag('tr') |
|||
root:wikitext('[[Category:消歧义消息模板]]') |
|||
:tag('td') |
|||
elseif self.nsid == 0 then |
|||
root:wikitext('__DISAMBIG__'):wikitext('[[Category:全部消歧义页面]]') |
|||
:addClass('mbox-text') |
|||
else |
|||
:cssText(self.textstyle or nil) |
|||
root:wikitext('<!-- omitted -->') |
|||
end |
|||
end |
|||
-- Add category for {{dmbox|type=setindex}} |
|||
if self.type == 'setindex' then |
|||
if self.nsid == 10 then |
|||
root:wikitext('[[Category:消歧义消息模板]]') |
|||
else |
|||
root:wikitext('__DISAMBIG__') |
|||
end |
|||
root:wikitext('[[Category:全部设置索引条目]]') |
|||
end |
end |
||
-- Add error message for invalid type parameters. |
-- Add error message for invalid type parameters. |
||
if self.invalidTypeError then |
if self.invalidTypeError then |
||
root |
root |
||
: |
:tag('div') |
||
:css('text-align', 'center') |
|||
:wikitext(string.format( |
|||
'此 |
:wikitext(format('此消息框使用无效的「type=%s」参数,需要修复。', self.type or '')) |
||
self.type or '' |
|||
)) |
|||
end |
end |
||
-- Add categories. |
-- Add categories. |
||
root |
|||
root:wikitext(self:renderCategories() or nil) |
|||
:wikitext(self:renderCategories()) |
|||
return tostring(root) |
return tostring(root) |
||
| 第672行: | 第693行: | ||
-------------------------------------------------------------------------------- |
-------------------------------------------------------------------------------- |
||
local |
local function main(boxType, args) |
||
local outputBox = MessageBox.new(boxType, args, mw.loadData(CONFIG_MODULE)) |
|||
outputBox.args = outputBox:removeBlankArgs(args) |
|||
outputBox:setTypeData() |
|||
outputBox:setClasses() |
|||
outputBox:setCollapsibleTextFields() |
|||
outputBox:setTextFields() |
|||
outputBox:setImages() |
|||
outputBox:setCategories() |
|||
return outputBox:export() |
|||
end |
|||
function |
function MessageBox:removeBlankArgs(args) |
||
-- Only allow blank arguments for the parameter names listed in cfg.allowBlankParams. |
|||
-- For testing. |
|||
local cfg = self.cfg |
|||
return { |
|||
local newArgs = {} |
|||
MessageBox = MessageBox |
|||
for k, v in pairs(args) do |
|||
} |
|||
if v ~= '' then |
|||
newArgs[k] = v |
|||
end |
|||
end |
|||
for i, param in ipairs(cfg.allowBlankParams or {}) do |
|||
newArgs[param] = args[param] |
|||
end |
|||
return newArgs |
|||
end |
end |
||
function |
local function makeWrapper(boxType) |
||
return function (frame) |
|||
local box = MessageBox.new(boxType, args, cfgTables or mw.loadData(CONFIG_MODULE)) |
|||
local args = getArgs(frame, {trim = false, removeBlanks = false}) |
|||
box:setParameters() |
|||
return main(boxType, args) |
|||
box:setCategories() |
|||
end |
|||
return box:export() |
|||
end |
end |
||
local p = { |
|||
function p.insertStyle(frame) |
|||
main = main, |
|||
-- 供外部模板单独调用以插入 TemplateStyles |
|||
mbox = makeWrapper('mbox') |
|||
-- Usage: {{#invoke:Message box|insertStyle|boxType}} |
|||
} |
|||
local boxType = frame.args[1] or 'mbox' |
|||
return mw.getCurrentFrame():extensionTag{ |
|||
for boxType in pairs(mw.loadData(CONFIG_MODULE)) do |
|||
name = 'templatestyles', |
|||
p[boxType] = makeWrapper(boxType) |
|||
} |
|||
end |
end |
||
function |
function p._exportClasses() |
||
-- For testing. |
|||
return function (frame) |
|||
return { |
|||
if not getArgs then |
|||
MessageBox = MessageBox |
|||
getArgs = require('Module:Arguments').getArgs |
|||
} |
|||
end |
|||
return t.main(k, getArgs(frame, {trim = false, removeBlanks = false})) |
|||
end |
|||
end |
end |
||
return |
return p |
||
2026年8月7日 (五) 10:16的最新版本
此模块基于以下Lua语言模块编写:
本模块适用于{{mbox}}、{{fmbox}}、{{dmbox}}。
用法
在其它模块中引用本模块,您需要事先加载:
local messageBox = require('Module:Message box')
为创建一个消息框,您需要使用main函数,并给出两个参数:第一个对应消息框类型(字符串),第二个对应消息。
local box = messageBox.main( boxType, {
param1 = param1,
param2 = param2,
-- More parameters...
})
可用信息框类型:
| 类型 | 原对应模板 | 用途 |
|---|---|---|
ambox |
{{ambox}} | 条目命名空间 |
dmbox |
{{dmbox}} | 消歧义页面 |
fmbox |
{{fmbox}} | 界面消息及表单 |
tmbox |
{{tmbox}} | 讨论命名空间 |
ombox |
{{ombox}} | 其他命名空间 |
“#invoke”引入
除main函数外,本模块为每个消息框类型对应了分别的函数,例如{{#invoke:Message box|ombox|...}}、{{#invoke:Message box|ambox|...}}等。
技术细节
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.allowId- whether it is possible to set an "id" attribute to the html table tag.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 Template:Tag 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 thenameparameter 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, thetemplateErrorCategoryis applied to the template page.
-- 融合版 Module:Message box
-- 工程组织:mediawiki.org 版风格(require('strict')、常量表、辅助函数、类方法拆分、命名规范)
-- 行为基准:求闻版(rev 71078)——div.flex 输出结构、dmbox(disambig/setindex)、全中文文案、干支排序键
-- 输出 HTML 与求闻版严格一致(样式/配置/模板均依赖该结构,零漂移)
-- Load necessary modules.
require('strict')
local getArgs = require('Module:Arguments').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()
-- Define constants
local CONFIG_MODULE = 'Module:Message box/configuration'
local DEFAULT_IMAGE = 'Information Mark (Blue).svg'
-- Set aliases for often-used functions to reduce table lookups.
local format = mw.ustring.format
local tconcat = table.concat
local tinsert = table.insert
--------------------------------------------------------------------------------
-- Helper functions
--------------------------------------------------------------------------------
local function getTitleObject(page, ...)
if type(page) == 'string' then
-- 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, page, ...)
if success then
return title
end
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
tinsert(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
tinsert(nums, tonumber(num))
end
end
table.sort(nums)
return nums
end
--------------------------------------------------------------------------------
-- Namespace resolution
--------------------------------------------------------------------------------
local function getNamespaceId(ns)
if not ns then return end
if type(ns) == 'string' then
ns = lang:ucfirst(mw.ustring.lower(ns))
if ns == 'Main' then
ns = 0
end
end
local nsTable = mw.site.namespaces[ns]
if nsTable then
return nsTable.id
end
end
local function getMboxType(nsid)
-- Gets the mbox type from a namespace number.
if nsid == 0 then
return 'ambox' -- main namespace
else
local nsTable = mw.site.namespaces[nsid]
if nsTable and nsTable.isTalk then
return 'tmbox' -- any talk namespace
else
return 'ombox' -- other namespaces or invalid input
end
end
end
--------------------------------------------------------------------------------
-- 干支日期排序键
--------------------------------------------------------------------------------
local dayName = {
[1] = '㏠', [2] = '㏡', [3] = '㏢', [4] = '㏣', [5] = '㏤',
[6] = '㏥', [7] = '㏦', [8] = '㏧', [9] = '㏨', [10] = '㏩',
[11] = '㏪', [12] = '㏫', [13] = '㏬', [14] = '㏭', [15] = '㏮',
[16] = '㏯', [17] = '㏰', [18] = '㏱', [19] = '㏲', [20] = '㏳',
[21] = '㏴', [22] = '㏵', [23] = '㏶', [24] = '㏷', [25] = '㏸',
[26] = '㏹', [27] = '㏺', [28] = '㏻', [29] = '㏼', [30] = '㏽',
[31] = '㏾'
}
--------------------------------------------------------------------------------
-- Box class definition
--------------------------------------------------------------------------------
local MessageBox = {}
MessageBox.__index = MessageBox
function MessageBox.new(boxType, args, cfgTables)
args = args or {}
local obj = {}
setmetatable(obj, MessageBox)
obj.boxType = boxType
-- Get the title object and the namespace.
obj.pageTitle = getTitleObject(args.page ~= '' and args.page)
obj.title = obj.pageTitle or mw.title.getCurrentTitle()
obj.demospace = args.demospace ~= '' and args.demospace or nil
obj.nsid = getNamespaceId(obj.demospace) or obj.title.namespace
-- Resolve mbox to the concrete box type from the namespace.
if obj.boxType == 'mbox' then
obj.boxType = getMboxType(obj.nsid)
end
-- Get the config for our box type.
obj.cfg = cfgTables[obj.boxType]
if not obj.cfg then
local boxTypes = {}
for k, v in pairs(cfgTables) do
tinsert(boxTypes, format('"%s"', k))
end
tinsert(boxTypes, '"mbox"')
error(format('无效的消息框类型「%s」;有效的类型为%s', tostring(obj.boxType), mw.text.listToText(boxTypes)), 2)
end
obj.args = args
obj.classes = {}
obj.mainCats = nil
obj.templateCats = nil
obj.allCats = nil
return obj
end
function MessageBox:addCat(ns, cat, sort)
if type(cat) ~= 'string' then return end
local nsVals = {'main', 'template', 'all'}
local tname
for i, val in ipairs(nsVals) do
if ns == val then
tname = ns .. 'Cats'
end
end
if not tname then
for i, val in ipairs(nsVals) do
nsVals[i] = format('"%s"', val)
end
error('无效的ns参数传送到box:addCat;有效的数值为' .. mw.text.listToText(nsVals, '、', '或'))
end
self[tname] = self[tname] or {}
if type(sort) == 'string' then
tinsert(self[tname], format('[[Category:%s|%s]]', cat, sort))
else
tinsert(self[tname], format('[[Category:%s]]', cat))
end
end
function MessageBox:addClass(class)
if type(class) ~= 'string' then return end
tinsert(self.classes, class)
end
--------------------------------------------------------------------------------
-- Parameter processing
--------------------------------------------------------------------------------
function MessageBox:setTypeData()
local cfg = self.cfg
local args = self.args
-- Get type data.
self.type = args.type
local typeData = cfg.types[self.type]
self.invalidTypeError = cfg.showInvalidTypeError and self.type and not typeData and true or false
typeData = typeData or cfg.types[cfg.default]
self.typeClass = typeData.class
self.typeImage = typeData.image
-- Find if the box has been wrongly substituted.
if cfg.substCheck and args.subst == 'SUBST' then
self.isSubstituted = true
end
-- Find whether we are using a small message box.
if cfg.allowSmall and (
cfg.smallParam and args.small == cfg.smallParam
or not cfg.smallParam and yesno(args.small)
)
then
self.isSmall = true
else
self.isSmall = false
end
end
function MessageBox:setClasses()
local cfg = self.cfg
local args = self.args
-- Add attributes, classes and styles.
if cfg.allowId then
self.id = args.id
self.name = args.name
if self.name then
self:addClass('box-' .. string.gsub(self.name, ' ', '_'))
end
end
for _, class in ipairs(cfg.classes or {}) do
self:addClass(class)
end
if self.isSmall then
self:addClass(cfg.smallClass or 'mbox-small')
end
if yesno(args.hidden) then
self:addClass('infobox editsection')
end
self:addClass(self.typeClass)
self:addClass(args.class)
self.style = args.style
-- Set text style.
self.textstyle = args.textstyle
self.abovestyle = args.abovestyle
self.belowstyle = args.belowstyle
end
function MessageBox:setCollapsibleTextFields()
local cfg = self.cfg
local args = self.args
-- Find if we are on the template page or not.
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) or false
end
if not self.useCollapsibleTextFields then
return
end
-- Get the self.issue value.
if self.isSmall and args.smalltext then
self.issue = args.smalltext
else
local sect
if args.sect == '' then
sect = '此' .. (cfg.sectionDefault or '页面')
elseif type(args.sect) == 'string' then
sect = '此' .. 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 = {}
tinsert(issues, sect)
tinsert(issues, issue)
tinsert(issues, text)
self.issue = tconcat(issues)
end
-- Get the self.talk value.
local talk = args.talk
if talk == '' -- Show talk links on the template page or template subpages if the talk parameter is blank.
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 = '相关讨论可见于'
if talkArgIsTalkPage then
talkText = format('%s[[%s|%s]]。', talkText, talk, talkTitle.prefixedText)
else
talkText = format('%s[[%s#%s|讨论页]]。', talkText, talkTitle.prefixedText, talk)
end
self.talk = talkText
end
end
-- Get other values.
local date
if args.date and args.date ~= '' then
date = args.date
elseif args.time == '' and self.isTemplatePage then
date = lang:formatDate('Y年n月j日')
elseif args.time and args.time ~= '' then
date = lang:formatDate('Y年n月j日', args.time)
end
if date then
local ok, tempdate = pcall(lang.formatDate, lang, 'Y年n月j日', date) -- 正規化日期
if ok then
date = tempdate
end
end
if date then
self.date = string.format(" <small class='date-container'>''(<span class='date'>%s</span>)''</small>", date)
end
if args.fix and args.fix ~= '' then
self.fix = format('<br><small>%s</small>', args.fix)
else
self.fix = ''
end
self.info = args.info
end
function MessageBox:setTextFields()
local args = self.args
-- Set the non-collapsible text field.
if self.isSmall then
self.text = args.smalltext or args.text
else
self.text = args.text
end
-- Set the above row.
self.above = args.above
-- Set the below row.
self.below = args.below
end
function MessageBox:setImages()
local cfg = self.cfg
local args = self.args
-- General image settings.
self.imageCellDiv = not self.isSmall and cfg.imageCellDiv and true or false
self.imageEmptyCell = cfg.imageEmptyCell
-- 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 '40px'
self.imageLeft = format('[[File:%s|%s|link=|alt=]]', self.typeImage or DEFAULT_IMAGE, 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
--------------------------------------------------------------------------------
-- Category processing
--------------------------------------------------------------------------------
function MessageBox:setMainspaceCategories()
local cfg = self.cfg
local args = self.args
if not cfg.allowMainspaceCategories then
return
end
if args.cat then
args.cat1 = args.cat
end
self.catNums = getArgNums(args, 'cat')
if args.category then
args.category1 = args.category
end
self.categoryNums = getArgNums(args, 'category')
if args.all then
args.all1 = args.all
end
self.allNums = getArgNums(args, 'all')
self.categoryParamNums = union(self.catNums, self.categoryNums)
self.categoryParamNums = union(self.categoryParamNums, self.allNums)
-- The following is roughly equivalent to the old {{Ambox/category}}.
local date
local sortDay
if args.date and args.date ~= '' then
date = args.date
local ok, tempdate = pcall(lang.formatDate, lang, 'Y年n月', date) -- 正規化日期
if ok then
date = tempdate
end
elseif args.time and args.time ~= '' then
date = lang:formatDate('Y年n月', args.time)
sortDay = lang:formatDate('j', args.time)
sortDay = tonumber(sortDay)
sortDay = dayName[sortDay]
end
date = type(date) == 'string' and date
local preposition = '自'
for _, num in ipairs(self.categoryParamNums) 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 = format('%s%s%s', preposition, date, mainCat)
if sortDay then
self:addCat('main', catTitle, sortDay)
else
self:addCat('main', catTitle)
end
catTitle = getTitleObject('Category:' .. catTitle)
if not catTitle or not catTitle.exists then
self:addCat('main', '模板中使用无效日期参数的条目')
end
elseif mainCat and (not date or date == '') then
self:addCat('main', mainCat)
end
if allCat then
self:addCat('main', allCat)
end
end
end
function MessageBox:setTemplateCategories()
local cfg = self.cfg
local args = self.args
-- Add template-namespace categories.
if cfg.templateCategory then
if cfg.templateCategoryRequireName then
if self.isTemplatePage then
self:addCat('template', cfg.templateCategory)
end
elseif not self.title.isSubpage then
self:addCat('template', cfg.templateCategory)
end
end
-- Add template error category.
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('template', templateCat, templateSort)
end
end
function MessageBox:setAllNamespaceCategories()
-- Categories for all namespaces.
if self.invalidTypeError then
local allSort = (self.nsid == 0 and 'Main:' or '') .. self.title.prefixedText
self:addCat('all', '需要修复的信息框', allSort)
end
if self.isSubstituted then
self:addCat('all', '错误使用替换引用的页面')
end
end
function MessageBox:setCategories()
if self.cfg.allowMainspaceCategories then
self:setMainspaceCategories()
end
if self.cfg.templateCategory 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 = tconcat(self.mainCats or {}),
template = tconcat(self.templateCats or {}),
all = tconcat(self.allCats or {}),
nocat = self.args.nocat,
demospace = self.demospace,
page = self.pageTitle and self.pageTitle.prefixedText or nil
}
end
--------------------------------------------------------------------------------
-- Export
--------------------------------------------------------------------------------
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(format(
'模板<code>%s[[Template:%s|%s]]%s</code>被錯誤地替代。',
mw.text.nowiki('{{'), self.name, self.name, mw.text.nowiki('}}')
))
end
-- Create the box table.
local boxTable = root:tag('div')
boxTable
:attr('id', self.id)
for i, class in ipairs(self.classes or {}) do
boxTable
:addClass(class)
end
boxTable
:cssText(self.style)
:attr('role', 'presentation')
-- Add the left-hand image.
local row = boxTable:tag('div'):addClass('mbox-body')
if self.imageLeft then
local imageLeftCell = row:tag('div'):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)
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('div')
:addClass('mbox-empty-cell')
end
local textCell = row:tag('div'):addClass('mbox-text')
-- Add the above row.
if self.above then
textCell
:tag('div')
:attr('colspan', self.imageRight and '3' or '2')
:addClass('mbox-above')
:cssText(self.abovestyle)
:wikitext(self.above)
end
-- Add the 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)
local textCellSpan = textCell:tag('span')
textCellSpan
:addClass('mbox-text-span')
:wikitext(self.issue)
if not self.isSmall then
textCellSpan
:tag('span')
:addClass('hide-when-compact')
:wikitext(self.talk and self.talk)
end
textCellSpan
:wikitext(self.date and self.date)
if not self.isSmall and self.fix ~= '' then
textCellSpan
:tag('span')
:addClass('hide-when-compact')
:wikitext(self.fix and self.fix)
end
if not self.isSmall then
textCellSpan
:tag('span')
:addClass('hide-when-compact')
:wikitext(self.info and self.info)
end
else
-- Default text formatting - anything goes.
textCell
:cssText(self.textstyle)
:wikitext(self.text)
end
-- Add the below row.
if self.below then
textCell
:tag('div')
:attr('colspan', self.imageRight and '3' or '2')
:addClass('mbox-below')
:cssText(self.belowstyle)
:wikitext(self.below)
end
-- Add the right-hand image.
if self.imageRight then
local imageRightCell = row:tag('div'):addClass('mbox-imageright')
if self.imageCellDiv then
imageRightCell = imageRightCell:tag('div') --:css('width', '52px') -- If we are using a div, redefine imageRightCell so that the image is inside it.
end
imageRightCell
:wikitext(self.imageRight)
end
-- Add __DISAMBIG__ for {{dmbox|type=disambig}}
if self.type == 'disambig' then
if self.nsid == 10 then
root:wikitext('[[Category:消歧义消息模板]]')
elseif self.nsid == 0 then
root:wikitext('__DISAMBIG__'):wikitext('[[Category:全部消歧义页面]]')
else
root:wikitext('<!-- omitted -->')
end
end
-- Add category for {{dmbox|type=setindex}}
if self.type == 'setindex' then
if self.nsid == 10 then
root:wikitext('[[Category:消歧义消息模板]]')
else
root:wikitext('__DISAMBIG__')
end
root:wikitext('[[Category:全部设置索引条目]]')
end
-- Add error message for invalid type parameters.
if self.invalidTypeError then
root
:tag('div')
:css('text-align', 'center')
:wikitext(format('此消息框使用无效的「type=%s」参数,需要修复。', self.type or ''))
end
-- Add categories.
root
:wikitext(self:renderCategories())
return tostring(root)
end
--------------------------------------------------------------------------------
-- Exports
--------------------------------------------------------------------------------
local function main(boxType, args)
local outputBox = MessageBox.new(boxType, args, mw.loadData(CONFIG_MODULE))
outputBox.args = outputBox:removeBlankArgs(args)
outputBox:setTypeData()
outputBox:setClasses()
outputBox:setCollapsibleTextFields()
outputBox:setTextFields()
outputBox:setImages()
outputBox:setCategories()
return outputBox:export()
end
function MessageBox:removeBlankArgs(args)
-- Only allow blank arguments for the parameter names listed in cfg.allowBlankParams.
local cfg = self.cfg
local newArgs = {}
for k, v in pairs(args) do
if v ~= '' then
newArgs[k] = v
end
end
for i, param in ipairs(cfg.allowBlankParams or {}) do
newArgs[param] = args[param]
end
return newArgs
end
local function makeWrapper(boxType)
return function (frame)
local args = getArgs(frame, {trim = false, removeBlanks = false})
return main(boxType, args)
end
end
local p = {
main = main,
mbox = makeWrapper('mbox')
}
for boxType in pairs(mw.loadData(CONFIG_MODULE)) do
p[boxType] = makeWrapper(boxType)
end
function p._exportClasses()
-- For testing.
return {
MessageBox = MessageBox
}
end
return p